refactor(per-horizon): N_HORIZONS 5→3 — remaining ml-alpha tests

Five test files migrated to clear the last cargo check --all-targets errors:

- tests/multi_horizon_loader.rs:22,64 — hardcoded [usize;5] horizons literal
  → ml_alpha::heads::HORIZONS; for-loop bounds 0..5 → 0..N_HORIZONS
- tests/output_smoothness_grad_finite_diff.rs:198 — [0.1, 0.3, 1.0, 3.0, 10.0]
  → [0.1, 1.0, 10.0] preserving 100× span across horizons
- src/data/loader.rs:560,640 (inline lib tests) — hardcoded [30,100,300,
  1000,6000] literal → crate::heads::HORIZONS
- tests/perception_overfit.rs:318,358 (audit-discovered 5-isms) —
  cfg.seq_len * 5 → cfg.seq_len * N_HORIZONS
- tests/gpu_log_ring_invariants.rs:173 (audit-discovered) — payload field
  name v["payload"]["raw_h30"] → "raw_h10" (matches gpu_log.rs schema
  migrated in Task 5)

cargo check --workspace --all-targets: clean (only pre-existing third-party
cudarc cupti example error, unrelated).
cargo test -p ml-alpha --lib: 33 passed, 0 failed, 6 ignored — baseline.

Golden fixtures deferred to runtime regeneration:
- tests/fixtures/perception_forward_golden.bin (644 → 388 bytes post-rebase).
  Test is #[ignore]-d and rewrites if missing; regenerate during Task 9
  local validation by deleting the .bin and re-running with --ignored.

gpu_log.rs migration verified complete by Task 5 (no remnant 5-horizon
field names in payload_json decoders for RT_INPUT/RT_STATE/RT_OUTPUT).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-22 01:45:45 +02:00
parent 0d9fbc16b0
commit 2efedcd6b8
5 changed files with 27 additions and 25 deletions

View File

@@ -557,7 +557,7 @@ mod inference_mode_tests {
files,
predecoded_dir: mbp10.clone(),
seq_len: 1,
horizons: [30, 100, 300, 1000, 6000],
horizons: crate::heads::HORIZONS,
n_max_sequences: 1,
seed: 0xCAFEF00D,
inference_only,
@@ -637,7 +637,7 @@ mod inference_mode_tests {
files: vec![std::path::PathBuf::from("/tmp/__nonexistent_foxhunt.dbn.zst")],
predecoded_dir: std::path::PathBuf::from("/tmp"),
seq_len: 1,
horizons: [30, 100, 300, 1000, 6000],
horizons: crate::heads::HORIZONS,
n_max_sequences: 1,
seed: 0,
inference_only: false,

View File

@@ -170,7 +170,7 @@ fn magic_validation_skips_torn() {
assert_eq!(v["kname"], "smoothness_controller");
assert_eq!(v["rt_name"], "input");
// Payload field comes from the smoothness_input schema.
assert!((v["payload"]["raw_h30"].as_f64().unwrap() - 100.0).abs() < 1e-6);
assert!((v["payload"]["raw_h10"].as_f64().unwrap() - 100.0).abs() < 1e-6);
}
#[test]

View File

@@ -5,6 +5,7 @@
use ml_alpha::data::loader::{
discover_mbp10_files_sorted, MultiHorizonLoader, MultiHorizonLoaderConfig,
};
use ml_alpha::heads::{HORIZONS, N_HORIZONS};
use std::path::PathBuf;
fn cfg_from_env() -> Option<MultiHorizonLoaderConfig> {
@@ -19,7 +20,7 @@ fn cfg_from_env() -> Option<MultiHorizonLoaderConfig> {
files,
predecoded_dir: predec,
seq_len: 64,
horizons: [30, 100, 300, 1000, 6000],
horizons: HORIZONS,
n_max_sequences: 100,
seed: 0xA1A2_A3A4,
inference_only: false,
@@ -39,7 +40,7 @@ fn loader_yields_seq_with_valid_labels() {
let mut count = 0;
while let Some(seq) = loader.next_sequence().expect("next") {
assert_eq!(seq.snapshots.len(), cfg.seq_len);
for h in 0..5 {
for h in 0..N_HORIZONS {
assert_eq!(seq.labels[h].len(), cfg.seq_len);
for &v in &seq.labels[h] {
assert!(v.is_nan() || v == 0.0 || v == 1.0, "label not binary: {v}");
@@ -61,7 +62,7 @@ fn loader_errors_on_empty_files() {
files: Vec::new(),
predecoded_dir: PathBuf::from("/tmp/does_not_exist_foxhunt_test"),
seq_len: 64,
horizons: [30, 100, 300, 1000, 6000],
horizons: HORIZONS,
n_max_sequences: 10,
seed: 0,
inference_only: false,

View File

@@ -195,7 +195,7 @@ fn smoothness_grad_matches_finite_difference() {
// Vary in k so derivatives are non-trivial.
probs[i] = 0.2 + 0.05 * ((i % 7) as f32) + 0.03 * ((i / 7) as f32 % 5.0);
}
let lambda: [f32; N_HORIZONS] = [0.1, 0.3, 1.0, 3.0, 10.0]; // mimic the production ratio
let lambda: [f32; N_HORIZONS] = [0.1, 1.0, 10.0]; // mimic the production ratio (3 horizons)
let zero_init = vec![0.0_f32; probs.len()];
let base = run_smoothness_kernel(&dev, &probs, &lambda, k, b, &zero_init).unwrap();

View File

@@ -1,12 +1,13 @@
//! PerceptionTrainer synthetic-overfit smoke.
//!
//! Mirrors `perception_overfit.rs` but on the stacked trainer. The
//! signal is constant direction=+1, label=[1; 5]. Asserts the BCE loss
//! signal is constant direction=+1, label=[1; N_HORIZONS]. Asserts the BCE loss
//! shrinks at least 40% over the training budget — proves the
//! full forward + backward (Mamba2 + CfC + heads) wires up correctly
//! end-to-end and that all 6 AdamW optimizers actually move weights.
use ml_alpha::cfc::snap_features::Mbp10RawInput;
use ml_alpha::heads::N_HORIZONS;
use ml_alpha::trainer::perception::{PerceptionTrainer, PerceptionTrainerConfig};
use ml_core::device::MlDevice;
@@ -18,7 +19,7 @@ fn synthetic_seq(
seq_len: usize,
mut prev_mid: f32,
mut ts_ns: u64,
) -> (Vec<Mbp10RawInput>, Vec<[f32; 5]>) {
) -> (Vec<Mbp10RawInput>, Vec<[f32; N_HORIZONS]>) {
let mut out = Vec::with_capacity(seq_len);
for k in 0..seq_len {
let next_mid = prev_mid + 0.25;
@@ -49,7 +50,7 @@ fn synthetic_seq(
// Per-position labels: every position knows the next K snapshots all
// move up (synthetic monotone ramp), so label = 1.0 for every horizon
// at every position. Drives the trainer to learn "always predict 1".
let labels = vec![[1.0; 5]; seq_len];
let labels = vec![[1.0; N_HORIZONS]; seq_len];
(out, labels)
}
@@ -70,7 +71,7 @@ fn stacked_trainer_loss_shrinks_on_constant_signal() {
lr_cfc: 3e-3,
lr_mamba2: 1e-3,
seed: 0x4242,
horizon_weights: [1.0; 5],
horizon_weights: [1.0; N_HORIZONS],
n_batch: 1,
smoothness_base_lambda: 0.0,
kernel_step_trace_path: None,
@@ -146,7 +147,7 @@ fn stacked_trainer_loss_shrinks_with_stride_4() {
lr_cfc: 3e-3,
lr_mamba2: 1e-3,
seed: 0xC4C4,
horizon_weights: [1.0; 5],
horizon_weights: [1.0; N_HORIZONS],
n_batch: 1,
smoothness_base_lambda: 0.0,
kernel_step_trace_path: None,
@@ -202,7 +203,7 @@ fn stacked_trainer_loss_shrinks_at_batch_32() {
lr_cfc: 3e-3,
lr_mamba2: 1e-3,
seed: 0xB32B,
horizon_weights: [1.0; 5],
horizon_weights: [1.0; N_HORIZONS],
n_batch: 32,
smoothness_base_lambda: 0.0,
kernel_step_trace_path: None,
@@ -215,9 +216,9 @@ fn stacked_trainer_loss_shrinks_at_batch_32() {
let make_batch = |prev_mid: &mut f32, ts_base: &mut u64,
cfg: &PerceptionTrainerConfig|
-> (Vec<Vec<Mbp10RawInput>>, Vec<Vec<[f32; 5]>>) {
-> (Vec<Vec<Mbp10RawInput>>, Vec<Vec<[f32; N_HORIZONS]>>) {
let mut seqs: Vec<Vec<Mbp10RawInput>> = Vec::with_capacity(cfg.n_batch);
let mut labels: Vec<Vec<[f32; 5]>> = Vec::with_capacity(cfg.n_batch);
let mut labels: Vec<Vec<[f32; N_HORIZONS]>> = Vec::with_capacity(cfg.n_batch);
for _ in 0..cfg.n_batch {
let (seq, lbl) = synthetic_seq(cfg.seq_len, *prev_mid, *ts_base);
*prev_mid = 0.5 * (seq.last().unwrap().bid_px[0] + seq.last().unwrap().ask_px[0]);
@@ -232,7 +233,7 @@ fn stacked_trainer_loss_shrinks_at_batch_32() {
for warmup in 0..4 {
let (seqs, labels) = make_batch(&mut prev_mid, &mut ts_base, &cfg);
let seq_refs: Vec<&[Mbp10RawInput]> = seqs.iter().map(|s| s.as_slice()).collect();
let lbl_refs: Vec<&[[f32; 5]]> = labels.iter().map(|l| l.as_slice()).collect();
let lbl_refs: Vec<&[[f32; N_HORIZONS]]> = labels.iter().map(|l| l.as_slice()).collect();
let l = trainer.step_batched(&seq_refs, &lbl_refs).expect("warm step");
if warmup >= 2 {
initial += l;
@@ -244,7 +245,7 @@ fn stacked_trainer_loss_shrinks_at_batch_32() {
for _ in 0..200 {
let (seqs, labels) = make_batch(&mut prev_mid, &mut ts_base, &cfg);
let seq_refs: Vec<&[Mbp10RawInput]> = seqs.iter().map(|s| s.as_slice()).collect();
let lbl_refs: Vec<&[[f32; 5]]> = labels.iter().map(|l| l.as_slice()).collect();
let lbl_refs: Vec<&[[f32; N_HORIZONS]]> = labels.iter().map(|l| l.as_slice()).collect();
trainer.step_batched(&seq_refs, &lbl_refs).expect("train step");
}
@@ -252,7 +253,7 @@ fn stacked_trainer_loss_shrinks_at_batch_32() {
for _ in 0..4 {
let (seqs, labels) = make_batch(&mut prev_mid, &mut ts_base, &cfg);
let seq_refs: Vec<&[Mbp10RawInput]> = seqs.iter().map(|s| s.as_slice()).collect();
let lbl_refs: Vec<&[[f32; 5]]> = labels.iter().map(|l| l.as_slice()).collect();
let lbl_refs: Vec<&[[f32; N_HORIZONS]]> = labels.iter().map(|l| l.as_slice()).collect();
final_loss += trainer.step_batched(&seq_refs, &lbl_refs).expect("final step");
}
final_loss /= 4.0;
@@ -302,7 +303,7 @@ fn evaluate_alone_succeeds() {
lr_cfc: 3e-3,
lr_mamba2: 1e-3,
seed: 0x6262,
horizon_weights: [1.0; 5],
horizon_weights: [1.0; N_HORIZONS],
n_batch: 1,
smoothness_base_lambda: 0.0,
kernel_step_trace_path: None,
@@ -314,7 +315,7 @@ fn evaluate_alone_succeeds() {
let (seq, labels) = synthetic_seq(cfg.seq_len, prev_mid, ts);
let (loss, probs) = trainer.evaluate(&seq, labels.as_slice()).expect("eval alone");
assert!(loss.is_finite(), "eval loss must be finite, got {loss}");
assert_eq!(probs.len(), cfg.seq_len * 5);
assert_eq!(probs.len(), cfg.seq_len * N_HORIZONS);
}
/// Regression test for z2w9w cluster run: training step (which captures
@@ -332,7 +333,7 @@ fn evaluate_works_after_captured_training_step() {
lr_cfc: 3e-3,
lr_mamba2: 1e-3,
seed: 0x5151,
horizon_weights: [1.0; 5],
horizon_weights: [1.0; N_HORIZONS],
n_batch: 1,
smoothness_base_lambda: 0.0,
kernel_step_trace_path: None,
@@ -354,7 +355,7 @@ fn evaluate_works_after_captured_training_step() {
let (seq, labels) = synthetic_seq(cfg.seq_len, prev_mid, ts);
let (loss, probs) = trainer.evaluate(&seq, labels.as_slice()).expect("evaluate after train");
assert!(loss.is_finite(), "eval loss must be finite, got {loss}");
assert_eq!(probs.len(), cfg.seq_len * 5, "eval probs must be [K, 5] flat");
assert_eq!(probs.len(), cfg.seq_len * N_HORIZONS, "eval probs must be [K, N_HORIZONS] flat");
assert!(probs.iter().all(|p| p.is_finite()), "eval probs must be finite");
}
@@ -369,7 +370,7 @@ fn evaluate_works_after_capture_no_replay() {
lr_cfc: 3e-3,
lr_mamba2: 1e-3,
seed: 0x8181,
horizon_weights: [1.0; 5],
horizon_weights: [1.0; N_HORIZONS],
n_batch: 1,
smoothness_base_lambda: 0.0,
kernel_step_trace_path: None,
@@ -401,7 +402,7 @@ fn horizon_ema_and_lambda_track_after_training() {
lr_cfc: 3e-3,
lr_mamba2: 1e-3,
seed: 0x9292,
horizon_weights: [1.0; 5],
horizon_weights: [1.0; N_HORIZONS],
n_batch: 1,
smoothness_base_lambda: 0.0,
kernel_step_trace_path: None,
@@ -459,7 +460,7 @@ fn evaluate_works_after_warmup_only() {
lr_cfc: 3e-3,
lr_mamba2: 1e-3,
seed: 0x7171,
horizon_weights: [1.0; 5],
horizon_weights: [1.0; N_HORIZONS],
n_batch: 1,
smoothness_base_lambda: 0.0,
kernel_step_trace_path: None,