L2 anchor regularization toward initialization (axis B). Anchors
horizon_tokens + Q + MoE experts toward their init values to prevent
the calibration drift observed in v1 (where val_loss climbed as α
opened past epoch 1 in 2 of 3 folds).
KERNEL (`anchor_l2_fwd_bwd`):
loss_out = λ · Σ_i (p[i] − p_init[i])²
grad_p[i] += 2λ · (p[i] − p_init[i])
- Warp-shuffle reduce; one block per parameter group; strided thread
loop over n. Cross-warp reduce uses one __syncthreads.
- Coalesced grad write via stride loop.
- λ passed as device-side [1]-buffer (host writes scalar before launch
— capture-safe).
CONTROLLER (`trainer::anchor_controller::AnchorController`):
- Signal-driven λ floor: λ_floor = ‖p_init‖ / (100 · √numel).
Cross-fold-persistent per pearl_kelly_cap_signal_driven_floors.
- Wiener-α smoother (α = diff_var / (diff_var + sample_var + ε))
on val_loss change; α floored at 0.4 per
pearl_wiener_alpha_floor_for_nonstationary.
- λ blends toward target = |ema_change|·scale with α; floored at
λ_floor per pearl_blend_formulas_must_have_permanent_floor.
- First-observation bootstrap (sentinel state replaced directly on
first step) per pearl_first_observation_bootstrap.
- 4/4 unit tests PASS: signal-floor init, bootstrap returns floor,
floor protection across 1000 steps, λ_max cap.
NUMGRAD VERIFICATION (RTX 3050 sm_86):
anchor_l2_numgrad PASSES with closed-form parity (machine precision)
and central-difference parity (4 random positions) within 5e-2 rel.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
121 lines
4.1 KiB
Rust
121 lines
4.1 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",
|
||
"bce_loss_multi_horizon_sigma", // v2-A: Kendall σ-weighted BCE
|
||
"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: learned context summary at CfC k=0
|
||
"horizon_token_attention_pool", // v2-C: horizon-token K-prepend single-Q attention
|
||
"inverted_attention_pool", // v2-E: iTransformer-style cross-variate attention
|
||
"regime_moe_gate", // v2-D: top-1 MoE gate + expert dispatch + aux loss
|
||
"anchor_l2", // v2-B: L2 anchor regularization toward init
|
||
"reduce_axis0", // Phase B: cross-batch param-grad reducer
|
||
];
|
||
|
||
// Cache bust v11 (2026-05-17): K-loop parallelization Phase B —
|
||
// new reduce_axis0.cu kernel + block-per-batch refactor of
|
||
// cfc_step_batched (fwd+bwd). Old cubins don't have the new symbols.
|
||
// Force fresh nvcc compile.
|
||
|
||
fn main() {
|
||
println!("cargo:rerun-if-changed=build.rs");
|
||
|
||
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"))
|
||
}
|