Files
foxhunt/docs
jgrusewski 4d966e62f4 refactor(cuda): migrate gpu_backtest_evaluator buffers to MappedF32Buffer (Fix 30 Stale-B prereq)
Prerequisite for the deferred Fix 30 Stale-B kernel-side fix
(`backtest_plan_kernel.cu` raw_close source). The DtoD-via-pinned
pre-commit guard (`scripts/pre-commit-hook.sh::check_no_dtod_via_pinned`,
commit `5275932f4`) blocks any commit that stages `gpu_backtest_evaluator.rs`
against the file's 5 pre-existing `clone_to_device_*_via_pinned` callers
that landed before the guard. Stale-B has to stage this file (to thread
`prices_buf` into the `backtest_plan_state_isv` launcher), so the
migration must land first as its own atomic commit per
`feedback_no_partial_refactor`.

Migration scope. Single file, four buffer fields:
  - prices_buf       CudaSlice<f32> → MappedF32Buffer  [n*max_len*4]
  - features_buf     CudaSlice<f32> → MappedF32Buffer  [n*max_len*feat_dim]
  - portfolio_buf    CudaSlice<f32> → MappedF32Buffer  [n*8] (kernel-mutated)
  - window_lens_buf  CudaSlice<i32> → MappedI32Buffer  [n]

Init sites (lines ~651-664 post-edit). 4 `clone_to_device_*_via_pinned`
calls replaced with `MappedF32Buffer::new(host.len())` +
`write_from_slice(host)`. No memcpy_dtod_async + stream.synchronize()
pair at construction — mapped-pinned coherence makes the kernel see
host writes after the next stream-sync barrier.

Reset site (`reset_evaluation_state`, line ~1485 post-edit). In-place
`self.portfolio_buf.write_from_slice(&portfolio_init)` replaces the
prior buffer-replacement
`self.portfolio_buf = clone_to_device_f32_via_pinned(...)`. No alloc
churn per epoch; the device pointer stays stable across resets which
matches MappedF32Buffer's intended use.

Consumer sites (17 kernel arg passes). `arg(&self.X_buf)` →
`arg(&self.X_buf.dev_ptr)` so the launcher receives the device pointer
the kernel expects. Sites: launch_gather (×2), launch_gather_chunk,
launch_env_step, env_batch_kernel chunked path, plan_state_isv kernel,
metrics_kernel. CUdeviceptr (u64) is passed by reference exactly as
metrics_dev_ptr already does at the metrics launch site (~line 2497).

Kernel-mutated MappedF32Buffer precedent. portfolio_buf is mutated by
the env_step kernel every step. The same file's `plan_diag_buf` (a
MappedF32Buffer) is also kernel-written via dev_ptr (lines ~1230-1256)
and host-read via host_ptr — direct precedent. The IQN τ migration
(commit `facbf76eb`) confirms cuMemHostAlloc DEVICEMAP for kernel
reads. The mapped_pinned.rs module docstring states explicitly:
"Kernels write through dev_ptr (with __threadfence_system())".

What this change touches:
  - crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs
    Field types (lines 299-321), init block (lines 641-665), reset
    (lines 1462-1471), 17 kernel arg passes across 6 launchers.
  - docs/dqn-wire-up-audit.md
    Fix 30 Stale-B paragraph extended with the prereq commit summary.
    Stale-B itself remains DEFERRED (kernel-side fix lands next).

Verification:
  - SQLX_OFFLINE=true cargo check -p ml --offline (44.38s) clean.
  - SQLX_OFFLINE=true cargo build -p ml --release --offline
    --features cuda (53.78s) clean.
  - grep -n "clone_to_device_.*_via_pinned\|upload_.*_via_pinned"
    crates/ml/src/cuda_pipeline/gpu_backtest_evaluator.rs returns
    zero hits.
  - grep -n "self\.\(prices\|features\|window_lens\|portfolio\)_buf"
    shows every kernel-arg site followed by `.dev_ptr`. The only
    non-`.dev_ptr` references are the field declarations, the
    constructor moves into `Self { ... }`, and the
    `write_from_slice` calls in `reset_evaluation_state`.
  - Pre-commit DtoD-via-pinned guard now passes on staging this file.

Eliminates the last 5 `_via_pinned` callers in
`gpu_backtest_evaluator.rs`. Per
`feedback_no_htod_htoh_only_mapped_pinned`, `feedback_no_partial_refactor`
(every consumer of a field-type change migrates in one commit),
`feedback_no_hiding` (no `--no-verify`; the migration IS the fix the
guard is asking for).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 22:51:04 +02:00
..