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:
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user