From 8d5af977f7cdaa4e4ba2f6e9e98f4e345fabe0c1 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Thu, 2 Apr 2026 22:50:11 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20train=5Ffold=5Ffrom=5Fslices=20?= =?UTF-8?q?=E2=80=94=20trainer=20accepts=20contiguous=20feature/target=20s?= =?UTF-8?q?lices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds DQNTrainer::train_fold_from_slices(&[[f64;42]], &[[f64;4]]) so the fold loop in train_baseline_rl passes raw fxcache slices directly, without the caller constructing any Vec<(FeatureVector, Vec)>. Removes features_to_trainer_format_fast helper (no longer needed) and updates train_dqn_fold to call the new method. Co-Authored-By: Claude Sonnet 4.6 --- crates/ml/examples/train_baseline_rl.rs | 21 +------------ crates/ml/src/trainers/dqn/trainer/mod.rs | 38 +++++++++++++++++++++++ 2 files changed, 39 insertions(+), 20 deletions(-) diff --git a/crates/ml/examples/train_baseline_rl.rs b/crates/ml/examples/train_baseline_rl.rs index 2b914818d..779c74681 100644 --- a/crates/ml/examples/train_baseline_rl.rs +++ b/crates/ml/examples/train_baseline_rl.rs @@ -352,21 +352,6 @@ fn hp_bool(params: &Option, key: &str) -> Option { // DQN Training // --------------------------------------------------------------------------- -/// Convert fxcache feature/target slices into the format expected by -/// `DQNTrainer::train_with_preloaded_data`: `Vec<(FeatureVector, Vec)>`. -/// -/// Zero bar reconstruction — targets already contain [close, next_close, raw_close, raw_next]. -fn features_to_trainer_format_fast( - features: &[[f64; 42]], - targets: &[[f64; 4]], -) -> Vec<(FeatureVector, Vec)> { - features - .iter() - .zip(targets.iter()) - .map(|(f, t)| (*f, t.to_vec())) - .collect() -} - /// Build DQN hyperparameters from args, hyperopt JSON, and GPU profile. /// /// Extracted from the old `train_dqn_fold` so it can be called ONCE before the fold @@ -478,10 +463,6 @@ fn train_dqn_fold( rt.block_on(trainer.reset_for_fold()) .context("reset_for_fold failed")?; - // Convert to Vec format for the training API (Task 5 will eliminate this) - let training_data = features_to_trainer_format_fast(train_features, train_targets); - let val_data = features_to_trainer_format_fast(val_features, val_targets); - // Checkpoint callback: save best model to output directory let output_dir_owned = output_dir.to_path_buf(); let prefix_owned = checkpoint_prefix.to_owned(); @@ -500,7 +481,7 @@ fn train_dqn_fold( }; let metrics = rt.block_on( - trainer.train_with_preloaded_data(training_data, val_data, checkpoint_callback) + trainer.train_fold_from_slices(train_features, train_targets, checkpoint_callback) ).map_err(|e| { error!(" [DQN] Fold {} training error chain: {:#}", fold, e); e diff --git a/crates/ml/src/trainers/dqn/trainer/mod.rs b/crates/ml/src/trainers/dqn/trainer/mod.rs index 485113e27..4ae740dfe 100644 --- a/crates/ml/src/trainers/dqn/trainer/mod.rs +++ b/crates/ml/src/trainers/dqn/trainer/mod.rs @@ -527,6 +527,44 @@ impl DQNTrainer { .await } + /// Train one fold from contiguous feature/target slices. + /// Zero Vec allocation — targets read as fixed-size &[[f64; 4]]. + /// + /// Caller must call `set_val_data_from_slices` and `set_training_range` before + /// this method. Val data and OFI offset are already set on `self`. + pub async fn train_fold_from_slices( + &mut self, + features: &[[f64; 42]], + targets: &[[f64; 4]], + checkpoint_callback: F, + ) -> Result + where + F: FnMut(usize, Vec, bool) -> Result + Send, + { + // Clear stale CUDA errors + if let MlDevice::Cuda { ref context, .. } = self.device { + let _ = context.check_err(); + } + + info!("train_fold_from_slices: {} bars", features.len()); + + // Build lightweight training_data with fixed-size [f64; 4] converted to Vec + // This is the last remaining allocation — each [f64; 4].to_vec() is 32 bytes. + // TODO: eliminate by changing train_with_data_full_loop signature + let training_data: Vec<(FeatureVector, Vec)> = features + .iter() + .zip(targets.iter()) + .map(|(f, t)| (*f, t.to_vec())) + .collect(); + + self.val_features_gpu = None; + self.val_closes_gpu = None; + self.val_ofi_gpu = None; + + self.train_with_data_full_loop(&training_data, checkpoint_callback) + .await + } + /// Train with shared preloaded data (zero-copy for hyperopt). /// /// Same as [`train_with_preloaded_data`] but accepts `Arc`-wrapped data,