CB1+CB2 swapped labels D→A+B; this swaps the kernels to match. aux_heads.cu — 4-output structure: - Forward: 12 outputs per snapshot = 4 per direction × N_HORIZONS (prof_long_logit, size_long_pred, prof_short_logit, size_short_pred, each [N_HORIZONS]). Linear projections; sigmoid applied in BCE kernel. - Backward: accepts 4 grad_y inputs, produces 8 grad_W + 8 grad_b + grad_h_aux. Cooperative h_aux staging in shmem once per block. - 8 weight matrices total, Xavier × 0.1 init under scoped_init_seed. aux_loss.cu — 2 kernels: - aux_bce_loss_fwd_bwd: class-weighted BCE+sigmoid fused. pos_weight in shared mem; scales positive-class gradient. NaN-mask y_true. - aux_huber_masked_fwd_bwd: Huber w/ NaN-mask. CB1's y_size=NaN at y_prof=0 provides the conditional-Huber semantics naturally — no separate mask buffer needed. aux_heads.rs: - AuxHeads + AuxHeadsWeights: 8 buffer fields (4 W + 4 b) - AuxBceLoss + AuxMaskedHuberLoss wrappers replace AuxHuberLoss - POS_WEIGHT_MIN/MAX = [1.0, 50.0] clamps per E3 - aux_heads_fwd_gpu/aux_heads_bwd_gpu/aux_bce_loss_gpu/aux_huber_masked_loss_gpu perception.rs (minimal compile-keeping signature updates only): - Renamed/added buffers: 4 prediction (prof/size × long/short), 4 label staging, 4 grad_y per-K, 8 head grad scratches, 8 head Adam optimizers, 2 pos_weight buffers (device + staging) - HOLDING PATTERN: bwd zeroes the 4 grad_y_per_K buffers each step so the head Adam updates are no-ops on grad=0 (no aux gradient signal this commit). CB5 wires the actual aux_bce + aux_huber_masked calls. - BCE direction signal + dir_acc readouts updated to use the new prof_long/prof_short prediction buffers (so existing perception_overfit aux test still passes). 7 GPU oracle tests on RTX 3050 sm_86, all pass in 2.43s: - fwd_matches_naive_reference, bwd_finite_diff_matches_bias_sample, aux_bce_loss_matches_naive_reference, aux_bce_pos_weight_scales_positive_gradient (verified: pos_weight=10 → 10× gradient ratio within 1e-4), aux_huber_masked_does_not_propagate, aux_huber_masked_covers_both_branches, aux_bce_nan_mask_does_not_propagate Cubins rebuilt: aux_heads (8736→10528 bytes, +20% for 4-head fwd/bwd), aux_loss (6944→13344 bytes, +92% for 2 kernels). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
128 lines
5.4 KiB
Rust
128 lines
5.4 KiB
Rust
//! Pre-compile all ml-alpha CUDA kernels into arch-specific cubins.
|
||
//!
|
||
//! Per `feedback_no_nvrtc.md`: no runtime kernel compilation.
|
||
//! Per `pearl_build_rs_rerun_if_env_changed.md`: every `std::env::var`
|
||
//! is paired with `cargo:rerun-if-env-changed`.
|
||
|
||
use std::path::{Path, PathBuf};
|
||
use std::process::Command;
|
||
|
||
const KERNELS: &[&str] = &[
|
||
"mamba2_alpha_kernel", // Mamba2 SSM scan kernel (used by PerceptionTrainer's encoder prefix)
|
||
"snap_feature_assemble",
|
||
"cfc_step",
|
||
"multi_horizon_heads",
|
||
"projection",
|
||
"bce_loss_multi_horizon", // Kendall σ-weighted multi-horizon BCE (axis A)
|
||
"adamw_step",
|
||
"grad_norm",
|
||
"horizon_lambda", // ISV-driven per-horizon gradient scaler (EMA + lambda)
|
||
"layer_norm", // Phase 1: trunk pre-CfC normalisation
|
||
"variable_selection", // Phase 2D: TFT-style per-feature gating
|
||
"attention_pool", // Phase 3: single-Q learned content summary at CfC k=0
|
||
"reduce_axis0", // Phase B: cross-batch param-grad reducer
|
||
"output_smoothness", // CRT.train: per-horizon adjacent-position prob-jitter penalty
|
||
"smoothness_lambda_controller", // CRT.train: ISV-driven λ controller anchored on h30 jitter
|
||
"gpu_log_ring", // GPU diagnostic log ring — tick kernel + log_record helper
|
||
"bucket_transition_kernels", // Per-horizon CfC Phase 1→2 transition: tau_sort, bucket_assign, bucket_iqr, channels_in_bucket, heads_compact, zero_off_bucket (ALPHA fix 2026-05-21)
|
||
"cfc_step_per_branch", // Per-horizon CfC Phase 2: fused per-(batch, branch) fwd + bwd over [25,25,25,25,28] buckets
|
||
"heads_block_diagonal_fwd", // Per-horizon CfC Phase 2: heads w_skip projection with compact ragged storage (640→128 floats)
|
||
"aux_trunk", // SDD-3 Layer B3: smaller single-bucket CfC trunk (AUX_HIDDEN=64) for outcome-supervision (D-labels)
|
||
"aux_heads", // SDD-3 Layer B4: per-direction linear regression heads on AuxTrunk output (long + short, N_AUX_HORIZONS each)
|
||
"aux_loss", // SDD-3 Layer B4: Huber loss + grad for aux trade-outcome regression targets (NaN-masked)
|
||
"aux_vec_add", // SDD-3 Layer B5: element-wise dst += src for aux→encoder gradient accumulation (lifted stop-grad)
|
||
];
|
||
|
||
// Cache bust v19 (2026-05-22): SDD-3 CB3+CB4 — aux_heads.cu rewritten for A+B paired (4 heads × 2 dirs = 8 weight matrices, 4 outputs per direction); aux_loss.cu replaced Huber-only with class-weighted BCE (sigmoid-fused, pos_weight per horizon) + NaN-masked Huber (conditional via loader's y_size = NaN @ y_prof = 0).
|
||
|
||
fn main() {
|
||
println!("cargo:rerun-if-changed=build.rs");
|
||
// Track shared headers so .cuh / .h edits trigger rebuilds of every
|
||
// .cu that #includes them. Without these, an edit to a helper header
|
||
// leaves a stale cubin.
|
||
println!("cargo:rerun-if-changed=cuda/gpu_log_ids.h");
|
||
println!("cargo:rerun-if-changed=cuda/gpu_log_helpers.cuh");
|
||
|
||
println!("cargo:rerun-if-env-changed=CARGO_FEATURE_CUDA");
|
||
if std::env::var("CARGO_FEATURE_CUDA").is_err() {
|
||
eprintln!(" ml-alpha: cuda feature disabled, skipping kernel build");
|
||
return;
|
||
}
|
||
|
||
println!("cargo:rerun-if-env-changed=CUDA_COMPUTE_CAP");
|
||
println!("cargo:rerun-if-env-changed=CUDA_HOME");
|
||
let cap = std::env::var("CUDA_COMPUTE_CAP").unwrap_or_else(|_| "80".to_string());
|
||
let arch = format!("sm_{cap}");
|
||
|
||
let nvcc = match find_nvcc() {
|
||
Some(p) => p,
|
||
None => {
|
||
eprintln!(" ml-alpha: nvcc not found, skipping kernel build (set CUDA_HOME or install CUDA toolkit)");
|
||
return;
|
||
}
|
||
};
|
||
|
||
let out = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set by cargo"));
|
||
|
||
for k in KERNELS {
|
||
let src = PathBuf::from(format!("cuda/{k}.cu"));
|
||
if !src.exists() {
|
||
eprintln!(" ml-alpha: skipping {k} — source not yet present");
|
||
continue;
|
||
}
|
||
println!("cargo:rerun-if-changed={}", src.display());
|
||
let cubin = out.join(format!("{k}.cubin"));
|
||
compile(&nvcc, &src, &cubin, &arch);
|
||
}
|
||
}
|
||
|
||
fn compile(nvcc: &Path, src: &Path, cubin: &Path, arch: &str) {
|
||
let status = Command::new(nvcc)
|
||
.args([
|
||
"-cubin",
|
||
&format!("-arch={arch}"),
|
||
"-O3",
|
||
"--use_fast_math",
|
||
"--ftz=true",
|
||
"--fmad=true",
|
||
"-o",
|
||
cubin.to_str().unwrap(),
|
||
src.to_str().unwrap(),
|
||
])
|
||
.status()
|
||
.unwrap_or_else(|e| panic!("nvcc spawn failed for {}: {e}", src.display()));
|
||
if !status.success() {
|
||
panic!(
|
||
"nvcc failed for {} (exit {})",
|
||
src.display(),
|
||
status.code().unwrap_or(-1)
|
||
);
|
||
}
|
||
eprintln!(
|
||
" ml-alpha: compiled {} -> {} ({arch})",
|
||
src.display(),
|
||
cubin.display()
|
||
);
|
||
}
|
||
|
||
fn find_nvcc() -> Option<PathBuf> {
|
||
if let Ok(home) = std::env::var("CUDA_HOME") {
|
||
let p = PathBuf::from(home).join("bin/nvcc");
|
||
if p.exists() {
|
||
return Some(p);
|
||
}
|
||
}
|
||
for cand in ["/usr/local/cuda/bin/nvcc", "/usr/bin/nvcc"] {
|
||
let p = PathBuf::from(cand);
|
||
if p.exists() {
|
||
return Some(p);
|
||
}
|
||
}
|
||
Command::new("nvcc")
|
||
.arg("--version")
|
||
.output()
|
||
.ok()
|
||
.filter(|o| o.status.success())
|
||
.map(|_| PathBuf::from("nvcc"))
|
||
}
|