perf(htod): migrate gpu_backtest_evaluator.rs to mapped-pinned (5 sites)
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<T> 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) <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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<T>` 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`.
|
||||
|
||||
Reference in New Issue
Block a user