From 208cee4cf5a2e1aec198b21af7af818f2fe34211 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Wed, 11 Mar 2026 09:40:51 +0100 Subject: [PATCH] docs: fix 5 chunk 2 re-review issues in CUDA backtest plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Task 9: memcpy_stod_inplace → memcpy_htod (cudarc 0.17 API) - Task 9: Fix transposed actions_history layout — accumulate CPU-side in window-major [window][step] layout, upload once before metrics kernel - Task 9: Replace non-existent Tensor::from_raw_buffer with download- and-reupload pattern (temporary, replaced by Task 11 GPU gather kernel) - Task 10: DqnOptimizer → DQNTrainer (hyperopt adapter) (actual struct name) - Task 10: internal_trainer type → &InternalDQNTrainer (avoids name conflict with adapter's own DQNTrainer alias) Co-Authored-By: Claude Opus 4.6 --- ...-03-11-cuda-backtest-gpu-residency-plan.md | 55 ++++++++++++------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/docs/plans/2026-03-11-cuda-backtest-gpu-residency-plan.md b/docs/plans/2026-03-11-cuda-backtest-gpu-residency-plan.md index f5a165154..2286b1626 100644 --- a/docs/plans/2026-03-11-cuda-backtest-gpu-residency-plan.md +++ b/docs/plans/2026-03-11-cuda-backtest-gpu-residency-plan.md @@ -1169,7 +1169,11 @@ pub struct GpuBacktestEvaluator { actions_history_buf: CudaSlice, // [n_windows * max_len] // Output - metrics_buf: CudaSlice, // [n_windows * 6] + metrics_buf: CudaSlice, // [n_windows * 10] + + // CPU-side action history (window-major: [window][step]) + // Accumulated during step loop, uploaded once before metrics kernel. + actions_history_cpu: Vec, // [n_windows * max_len] // Config n_windows: usize, @@ -1280,6 +1284,7 @@ impl GpuBacktestEvaluator { prices_buf, features_buf, window_lens_buf, portfolio_buf, step_rewards_buf, step_returns_buf, done_buf, actions_buf, actions_history_buf, metrics_buf, + actions_history_cpu: vec![0_i32; n_windows * max_len], n_windows, max_len, feature_dim, config, }) } @@ -1308,9 +1313,17 @@ impl GpuBacktestEvaluator { // For initial correctness, we use Candle narrow ops + small portfolio download. // Use Candle narrow ops to gather from GPU features tensor // without downloading the full buffer. - let features_tensor = Tensor::from_raw_buffer( - &self.features_buf, DType::F32, - &[self.n_windows, self.max_len, self.feature_dim], device, + // Wrap pre-uploaded CudaSlice as Candle Tensor. + // NOTE: `Tensor::from_raw_buffer` does not exist in Candle. Use the established + // pattern: allocate zeros tensor, extract CudaSlice via storage_and_layout(), + // then memcpy_dtod_async. Or simply download-and-reupload for this temporary path. + // This gather_states is replaced by a GPU gather kernel in Task 11 anyway. + let mut flat_feats = vec![0.0_f32; self.n_windows * self.max_len * self.feature_dim]; + self.stream.memcpy_dtoh(&self.features_buf, &mut flat_feats) + .map_err(|e| MLError::ModelError(format!("features download: {e}")))?; + let features_tensor = Tensor::from_vec( + flat_feats, + (self.n_windows, self.max_len, self.feature_dim), device, ).map_err(|e| MLError::ModelError(format!("features tensor: {e}")))?; // Narrow to current step: [n_windows, feat_dim] @@ -1366,20 +1379,16 @@ impl GpuBacktestEvaluator { let actions_vec: Vec = actions_tensor.to_vec1()?; let actions_i32: Vec = actions_vec.iter().map(|&a| a as i32).collect(); - // Upload actions - self.stream.memcpy_stod_inplace(&actions_i32, &mut self.actions_buf) + // Upload actions to GPU (cudarc 0.17: memcpy_htod, not memcpy_stod_inplace) + self.stream.memcpy_htod(&actions_i32, &mut self.actions_buf) .map_err(|e| MLError::ModelError(format!("actions upload: {e}")))?; - // Copy actions to history buffer for metrics kernel trade counting. - // GPU-to-GPU copy at correct offset. cudarc memcpy_dtod with offset: - let history_offset = step * self.n_windows; - unsafe { - self.stream - .memcpy_dtod( - &self.actions_buf, - &mut self.actions_history_buf.slice(history_offset..history_offset + self.n_windows), - ) - .map_err(|e| MLError::ModelError(format!("actions history copy: {e}")))?; + // Track actions in CPU-side history (window-major layout: [window][step]). + // The metrics kernel reads actions_history[w * max_len + i], so we must match + // that layout. A single contiguous DtoD copy can't scatter to strided offsets, + // so we accumulate on CPU and upload once before the metrics kernel. + for w in 0..self.n_windows { + self.actions_history_cpu[w * self.max_len + step] = actions_i32[w]; } // NOTE: Task 11 replaces the CPU gather_states above with a GPU gather kernel @@ -1422,7 +1431,11 @@ impl GpuBacktestEvaluator { } } - // 5. Launch metrics reduction kernel + // 5. Upload accumulated actions history for metrics kernel trade counting + self.stream.memcpy_htod(&self.actions_history_cpu, &mut self.actions_history_buf) + .map_err(|e| MLError::ModelError(format!("actions_history upload: {e}")))?; + + // 6. Launch metrics reduction kernel let shmem_bytes = (256 * 6 * 4) as u32; // 6 reduction arrays × 256 threads × f32 let metrics_config = LaunchConfig { grid_dim: (self.n_windows as u32, 1, 1), @@ -1535,9 +1548,9 @@ Wire `GpuBacktestEvaluator` into the DQN hyperopt adapter's backtest evaluation. - Modify: `crates/ml/src/hyperopt/adapters/dqn.rs:2862-3165` (backtest evaluation section) - Test: `SQLX_OFFLINE=true cargo test -p ml --lib -- hyperopt_dqn --no-capture` -- [ ] **Step 1: Add GPU evaluator field to DqnOptimizer** +- [ ] **Step 1: Add GPU evaluator field to DQNTrainer (hyperopt adapter)** -In `dqn.rs`, find the `DqnOptimizer` struct fields and add: +In `dqn.rs`, find the `DQNTrainer (hyperopt adapter)` struct fields and add: ```rust /// GPU backtest evaluator (initialized on first use) @@ -1549,14 +1562,14 @@ Initialize as `None` in the builder. - [ ] **Step 2: Add GPU evaluation method** -Add a method to `DqnOptimizer`: +Add a method to `DQNTrainer (hyperopt adapter)`: ```rust /// Run backtest evaluation on GPU (zero CPU roundtrips during eval). #[cfg(feature = "cuda")] fn evaluate_gpu( &mut self, - internal_trainer: &DQNTrainer, + internal_trainer: &InternalDQNTrainer, val_close_prices: &[f64], window_size: usize, stride: usize,