perf(htod): migrate gpu_experience_collector.rs::upload_ofi_features to mapped-pinned (1 site)

The remaining `super::clone_htod_f32` call in `upload_ofi_features`
(`:4258`) is rewritten to `mapped_pinned::clone_to_device_f32_via_pinned`
per `feedback_no_htod_htoh_only_mapped_pinned.md`. Called from
training_loop's data-load phase before each fold's training run, so
the OFI tensor (4M bars × 32 dims) now stages through mapped-pinned
+ DtoD just like all other large data uploads.

Error-type shift: `mapped_pinned::clone_to_device_f32_via_pinned`
returns `Result<CudaSlice<f32>, String>` whereas `clone_htod_f32`
returned `Result<CudaSlice<f32>, MLError>`. Wrapped with
`.map_err(|e| MLError::ModelError(format!("upload_ofi_features: {e}")))`
to preserve the call-site label in error messages.

docs/dqn-gpu-hot-path-audit.md updated with Fix 15 entry.

cargo check -p ml --lib clean at 12 warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-04-28 21:23:38 +02:00
parent 24afb2baf0
commit 25b7771994
2 changed files with 5 additions and 1 deletions

View File

@@ -4255,7 +4255,8 @@ impl GpuExperienceCollector {
if ofi_flat.is_empty() {
return Err(MLError::ModelError("OFI data empty — cannot upload empty features to GPU".into()));
}
let gpu_buf = super::clone_htod_f32(&self.stream, ofi_flat)?;
let gpu_buf = super::mapped_pinned::clone_to_device_f32_via_pinned(&self.stream, ofi_flat)
.map_err(|e| MLError::ModelError(format!("upload_ofi_features: {e}")))?;
info!(
"OFI features uploaded to GPU: {} bars x {} dims ({:.1} KB)",
ofi_flat.len() / self.ofi_dim,

View File

@@ -160,6 +160,9 @@ Out-of-scope (remaining host-side stream syncs in eval, not part of this fix): `
- `collect_experiences` (warm but per-collect): `episode_starts_buf` (memcpy_htod) → `upload_i32_via_pinned`; `barrier_config` (htod_f32) → `upload_f32_via_pinned`.
- `reset_episodes` (warm, per-epoch): replaced the `Vec<f32>` / `Vec<u32>` host staging fields with persistent `MappedF32Buffer` / `MappedU32Buffer` allocated once at construction. Resets fill via `host_slice_mut()` (zero-allocation, zero-HtoD); a single async DtoD seeds the persistent device-resident `portfolio_states` / `rng_states` (which the kernel mutates each step). Adds `host_slice_mut()` accessor on both mapped-pinned types.
### Fix 15 — Migrate `gpu_experience_collector.rs::upload_ofi_features` (1 site, 2026-04-28)
`gpu_experience_collector.rs:4258`: warm-path `super::clone_htod_f32` upload of OFI features rewritten to `mapped_pinned::clone_to_device_f32_via_pinned`. Called from training_loop's data-load phase before each fold's training run, so the OFI tensor (4M bars × 32 dims = ~512 MB) now stages through mapped-pinned + DtoD just like all other large data uploads. Error-type shift handled with `.map_err(|e| MLError::ModelError(format!("upload_ofi_features: {e}")))?` since the helper returns `Result<_, String>` and the function returns `Result<_, MLError>`.
### Fix 14 — Migrate `gpu_dqn_trainer.rs` residual COLD ctor sites (8 sites, 2026-04-28)
`gpu_dqn_trainer.rs`: residual COLD ctor sites missed by the original Fix-1..13 audit are now migrated off explicit `super::htod_f32` per `feedback_no_htod_htoh_only_mapped_pinned.md`.
- `:9922` `sel_clip_buf` 1-element init — selectivity gate clip-norm seed (1.0).