From 4c71835a84067bbd23dfecadf9deaf5d6fd9e7e9 Mon Sep 17 00:00:00 2001 From: jgrusewski Date: Tue, 28 Apr 2026 21:08:19 +0200 Subject: [PATCH] perf(htod): migrate gpu_backtest_evaluator.rs to mapped-pinned (5 sites) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 5 HtoD sites in GpuBacktestEvaluator::new + reset_evaluation_state: - prices_buf (f32): stream.clone_htod → clone_to_device_f32_via_pinned - features_buf (f32): clone_htod_f32 → clone_to_device_f32_via_pinned - window_lens_buf (i32): stream.clone_htod → clone_to_device_i32_via_pinned - portfolio_buf init (f32): stream.clone_htod → clone_to_device_f32_via_pinned - portfolio_buf reset (f32, per-eval): stream.clone_htod → pinned Fields stay typed CudaSlice because the env_step kernel mutates portfolio_buf each backtest step (must remain device-resident); the read-only fields keep CudaSlice so the existing `.arg(&self.field)` kernel-launch sites at lines 885+ continue to work without cascade. Audit row appended (Fix 12) in docs/dqn-gpu-hot-path-audit.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../cuda_pipeline/gpu_backtest_evaluator.rs | 27 ++++++++++--------- docs/dqn-gpu-hot-path-audit.md | 3 +++ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs b/crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs index e947c8d57..96ac021e8 100644 --- a/crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs +++ b/crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs @@ -576,18 +576,18 @@ impl GpuBacktestEvaluator { .load_function("backtest_state_gather") .map_err(|e| MLError::ModelError(format!("backtest_state_gather load: {e}")))?; - // ── Upload read-only data ───────────────────────────────────────── - let prices_buf = stream.clone_htod(&flat_prices) - .map_err(|e| MLError::ModelError(format!("prices upload: {e}")))?; - let features_buf = super::clone_htod_f32(&stream, &flat_features)?; - let window_lens_buf = stream - .clone_htod(&window_lens) - .map_err(|e| MLError::ModelError(format!("window_lens upload: {e}")))?; + // ── Upload read-only data via mapped-pinned staging (no HtoD) ───── + let prices_buf = super::mapped_pinned::clone_to_device_f32_via_pinned(&stream, &flat_prices) + .map_err(|e| MLError::ModelError(format!("prices upload via pinned: {e}")))?; + let features_buf = super::mapped_pinned::clone_to_device_f32_via_pinned(&stream, &flat_features) + .map_err(|e| MLError::ModelError(format!("features upload via pinned: {e}")))?; + let window_lens_buf = super::mapped_pinned::clone_to_device_i32_via_pinned(&stream, &window_lens) + .map_err(|e| MLError::ModelError(format!("window_lens upload via pinned: {e}")))?; // ── Allocate mutable state buffers ──────────────────────────────── let portfolio_init = Self::init_portfolio_state(n_windows, config.initial_capital); - let portfolio_buf = stream.clone_htod(&portfolio_init) - .map_err(|e| MLError::ModelError(format!("portfolio upload: {e}")))?; + let portfolio_buf = super::mapped_pinned::clone_to_device_f32_via_pinned(&stream, &portfolio_init) + .map_err(|e| MLError::ModelError(format!("portfolio upload via pinned: {e}")))?; // Kelly stats buffer — zero-initialized (no trades yet). The Kelly // priors + warmup floor in trade_physics.cuh handle the cold start @@ -1310,10 +1310,13 @@ impl GpuBacktestEvaluator { /// across epochs — otherwise done_flags=1 from the previous evaluation causes /// the env_step to skip immediately, producing constant metrics. fn reset_evaluation_state(&mut self) -> Result<(), MLError> { - // Re-init portfolio to initial capital + // Re-init portfolio to initial capital. Uploads via mapped-pinned + // staging + DtoD per `feedback_no_htod_htoh_only_mapped_pinned.md`. + // The portfolio_buf field stays device-resident (the env_step kernel + // mutates it on every backtest step). let portfolio_init = Self::init_portfolio_state(self.n_windows, self.config.initial_capital); - let portfolio_f32 = self.stream.clone_htod(&portfolio_init) - .map_err(|e| MLError::ModelError(format!("reset portfolio upload: {e}")))?; + let portfolio_f32 = super::mapped_pinned::clone_to_device_f32_via_pinned(&self.stream, &portfolio_init) + .map_err(|e| MLError::ModelError(format!("reset portfolio upload via pinned: {e}")))?; self.portfolio_buf = portfolio_f32; // Zero done flags, step returns, step rewards diff --git a/docs/dqn-gpu-hot-path-audit.md b/docs/dqn-gpu-hot-path-audit.md index 59b104706..086e56d61 100644 --- a/docs/dqn-gpu-hot-path-audit.md +++ b/docs/dqn-gpu-hot-path-audit.md @@ -116,6 +116,9 @@ ### Fix 10 — Migrate `gpu_her.rs` to mapped-pinned + add MappedU32Buffer (3 sites) `gpu_her.rs`: 1 cold-path constructor `stream.memcpy_htod` (rng_states u32 init) and 2 warm-path `memcpy_htod` (source_indices, donor_indices in `relabel_batch`) rewritten to `mapped_pinned::upload_{u32,i32}_via_pinned`. The warm sites re-allocate pinned staging per relabel call — acceptable on this cold path (relabel runs once per epoch). New `MappedU32Buffer` type added to `mapped_pinned.rs` mirroring `MappedI32Buffer`; carries a manual `Debug` impl so the workspace warning count stays at 13. +### Fix 12 — Migrate `gpu_backtest_evaluator.rs` to mapped-pinned (5 sites) +`gpu_backtest_evaluator.rs`: 5 HtoD sites in `GpuBacktestEvaluator::new` + `reset_evaluation_state` rewritten to mapped-pinned staging. Sites: prices_buf (f32), features_buf (f32 — was already going through `clone_htod_f32` helper), window_lens_buf (i32), portfolio_buf init (f32), and the per-evaluation reset of portfolio_buf. The fields stay typed `CudaSlice` because the kernel mutates `portfolio_buf` each step (must remain device-resident); the read-only fields could become `MappedXxxBuffer` but keeping them as `CudaSlice` preserves the kernel-launch contract `.arg(&self.field)` already used at the many launch sites in the file. + ### Fix 11 — Migrate `gpu_ppo_collector.rs` to mapped-pinned (6 sites + persistent staging) `gpu_ppo_collector.rs`: all 6 HtoD sites migrated. - Constructor (cold): `portfolio_states` init (htod_f32) → `upload_f32_via_pinned`; `rng_states` init (memcpy_htod) → `upload_u32_via_pinned`.