diff --git a/crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs b/crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs index adf99f783..c7f69e664 100644 --- a/crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs +++ b/crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs @@ -9919,7 +9919,8 @@ impl GpuDqnTrainer { // Fixed clip norm of 1.0 — selectivity gate is a small sigmoid head, clip at 1.0 norm. let mut sel_clip_buf = stream.alloc_zeros::(1) .map_err(|e| MLError::ModelError(format!("alloc sel_clip_buf: {e}")))?; - super::htod_f32(&stream, &[1.0_f32], &mut sel_clip_buf)?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &[1.0_f32], &mut sel_clip_buf) + .map_err(|e| MLError::ModelError(format!("sel_clip_buf upload via pinned: {e}")))?; // Selectivity Adam step counter — pinned device-mapped (no per-step HtoD copy) let sel_t_pinned: *mut i32 = unsafe { let flags = cudarc::driver::sys::CU_MEMHOSTALLOC_DEVICEMAP; @@ -10072,10 +10073,14 @@ impl GpuDqnTrainer { let mut spec_v_s1 = alloc_f32(&stream, ml_core::state_layout::STATE_DIM, "spec_v_s1")?; let mut spec_u_s2 = alloc_f32(&stream, config.shared_h2, "spec_u_s2")?; let mut spec_v_s2 = alloc_f32(&stream, config.shared_h1, "spec_v_s2")?; - super::htod_f32(&stream, &init_u_s1, &mut spec_u_s1)?; - super::htod_f32(&stream, &init_v_s1, &mut spec_v_s1)?; - super::htod_f32(&stream, &init_u_s2, &mut spec_u_s2)?; - super::htod_f32(&stream, &init_v_s2, &mut spec_v_s2)?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &init_u_s1, &mut spec_u_s1) + .map_err(|e| MLError::ModelError(format!("spec_u_s1 upload via pinned: {e}")))?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &init_v_s1, &mut spec_v_s1) + .map_err(|e| MLError::ModelError(format!("spec_v_s1 upload via pinned: {e}")))?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &init_u_s2, &mut spec_u_s2) + .map_err(|e| MLError::ModelError(format!("spec_u_s2 upload via pinned: {e}")))?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &init_v_s2, &mut spec_v_s2) + .map_err(|e| MLError::ModelError(format!("spec_v_s2 upload via pinned: {e}")))?; // Head spectral norm vectors: value head, adv/branch heads let vh = config.value_h; @@ -10092,8 +10097,10 @@ impl GpuDqnTrainer { let mut $v_name = alloc_f32(&stream, $v_n, $lbl_v)?; let init_u = rand_unit($u_n, &mut rng_state); let init_v = rand_unit($v_n, &mut rng_state); - super::htod_f32(&stream, &init_u, &mut $u_name)?; - super::htod_f32(&stream, &init_v, &mut $v_name)?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &init_u, &mut $u_name) + .map_err(|e| MLError::ModelError(format!("{} upload via pinned: {e}", $lbl_u)))?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &init_v, &mut $v_name) + .map_err(|e| MLError::ModelError(format!("{} upload via pinned: {e}", $lbl_v)))?; }; } @@ -11273,7 +11280,8 @@ impl GpuDqnTrainer { } let mut graph_params = stream.alloc_zeros::(60) .map_err(|e| MLError::ModelError(format!("alloc graph_params: {e}")))?; - super::htod_f32(&stream, &graph_params_host, &mut graph_params)?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &graph_params_host, &mut graph_params) + .map_err(|e| MLError::ModelError(format!("graph_params upload via pinned: {e}")))?; info!("GpuDqnTrainer: branch_graph_message_pass kernel loaded (60 params, near-identity W_msg init)"); // ── Diffusion Q-refinement ──────────────────────────────────────────────── @@ -11327,7 +11335,8 @@ impl GpuDqnTrainer { } let mut denoise_params = stream.alloc_zeros::(DENOISE_TOTAL_PARAMS) .map_err(|e| MLError::ModelError(format!("alloc denoise_params: {e}")))?; - super::htod_f32(&stream, &denoise_params_host, &mut denoise_params)?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &denoise_params_host, &mut denoise_params) + .map_err(|e| MLError::ModelError(format!("denoise_params upload via pinned: {e}")))?; let cpbi_module_denoise = stream.context().load_cubin(EXPECTED_Q_CUBIN.to_vec()) .map_err(|e| MLError::ModelError(format!("cpbi cubin (denoise): {e}")))?; let q_denoise_kernel = cpbi_module_denoise.load_function("q_denoise_step") @@ -11473,7 +11482,8 @@ impl GpuDqnTrainer { } let mut qlstm_weights = stream.alloc_zeros::(QLSTM_WEIGHT_COUNT) .map_err(|e| MLError::ModelError(format!("alloc qlstm_weights: {e}")))?; - super::htod_f32(&stream, &qlstm_weights_host, &mut qlstm_weights)?; + super::mapped_pinned::upload_f32_via_pinned(&stream, &qlstm_weights_host, &mut qlstm_weights) + .map_err(|e| MLError::ModelError(format!("qlstm_weights upload via pinned: {e}")))?; let qlstm_c = stream.alloc_zeros::(QLSTM_HEAD_DIM * QLSTM_HEAD_DIM) // [64] .map_err(|e| MLError::ModelError(format!("alloc qlstm_c: {e}")))?; let qlstm_n = stream.alloc_zeros::(QLSTM_HEAD_DIM) // [8] diff --git a/docs/dqn-gpu-hot-path-audit.md b/docs/dqn-gpu-hot-path-audit.md index 1de5d1a3a..28dd658b1 100644 --- a/docs/dqn-gpu-hot-path-audit.md +++ b/docs/dqn-gpu-hot-path-audit.md @@ -159,3 +159,13 @@ Out-of-scope (remaining host-side stream syncs in eval, not part of this fix): ` - Constructor (cold): `portfolio_states` init (htod_f32) → `upload_f32_via_pinned`; `rng_states` init (memcpy_htod) → `upload_u32_via_pinned`. - `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` / `Vec` 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 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). +- `:10075-10078` spectral-norm `init_u_s1` / `init_v_s1` / `init_u_s2` / `init_v_s2` (4 separate uploads of trunk-encoder spectral u/v vectors). +- `:10095-10096` `alloc_spec_pair!` macro body — both u/v init uploads inside the macro now go through `upload_f32_via_pinned`. The macro's `$lbl_u` / `$lbl_v` are reused in the error-message format so per-pair failures (`spec_u_v1`, `spec_v_v1`, `spec_u_a1`, …) stay diagnostically distinct. +- `:11276` `graph_params_host` (60 floats: cross-branch graph message passing weights, 4 edges × 15 params each, near-identity W_msg init). +- `:11330` `denoise_params_host` (1800 floats: 2-step diffusion Q-refinement Xavier init for W1[24,24] + W2[12,24]). +- `:11476` `qlstm_weights_host` (528 floats: QLSTM Xavier init for 6 gates × 11 input × 8 head). +All sites use `mapped_pinned::upload_f32_via_pinned` (canonical mapped-pinned + DtoD staging helper). The helper returns `Result<_, String>` whereas the ctor returns `Result<_, MLError>`, so each site wraps the error via `.map_err(|e| MLError::ModelError(format!(" upload via pinned: {e}")))`. Site labels preserved so backtraces remain readable.