Commit Graph

5658 Commits

Author SHA1 Message Date
jgrusewski
6f645df11f feat(rl): wire lobsim book data from GPU SoA — proper rewards in GPU loader path
Add apply_snapshot_from_device to RlLobBackend: DtoD copy from
perception SoA buffers into lobsim book arrays + book_update kernel.
No host roundtrip, no sync (stream-ordered).

step_with_lobsim_gpu now calls apply_snapshot_from_device with batch 0's
last-snapshot (K-1) book data. Produces non-zero dones, pnl, wr.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:29:05 +02:00
jgrusewski
3552d08501 fix(rl): streaming per-file GPU upload — prevent host OOM at 45M snaps
Convert + upload one file at a time instead of all 45M snapshots at
once. Peak host memory drops from ~10GB (all converted snapshots) to
~2GB (one file). Fixes OOMKilled on 40Gi L40S pods.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 21:04:30 +02:00
jgrusewski
1f672c3b09 perf(rl): GPU-resident data loader — zero CPU per step
Upload all pre-converted snapshots + FRD labels to GPU at init (1.2GB
for 5.6M snapshots / 2 files locally, ~11GB for 45M / 9 files on L40S).

New GPU kernels: gpu_sample_and_gather (PRNG sampling + AoS-to-SoA),
gpu_gather_next/gpu_gather_current (anchor offset re-gather),
gpu_gather_frd_labels (per-horizon label gather).

step_with_lobsim_gpu: GPU-only data path replacing host-side loader.
Encoder forwards via forward_encoder_from_device. Eliminates 7700 heap
allocs + 418k scalar copies + 16ms CPU work per step at b=256.

Init uploads use cuMemcpyHtoD_v2 (synchronous, one-time).

Note: apply_snapshot skipped (lobsim book stale, dones/rewards=0).
Follow-up: copy last-snapshot book data from SoA to lobsim buffers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 20:51:06 +02:00
jgrusewski
a429c3f901 perf(rl): move diag sync_and_swap after step — overlap with GPU compute
diag_staging.sync_and_swap() was at the START of the step loop,
blocking host for ~5.5ms while GPU was idle. Moved to AFTER
step_with_lobsim (which ends with end-of-step sync that guarantees
previous diag copies completed). Eliminates one of the two per-step
host-blocking syncs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:58:07 +02:00
jgrusewski
6eedfea593 perf(rl): async double-buffered data prefetch — overlap loader with GPU
Background thread runs loader.next_sequence_pair() × n_batch while
GPU trains on the current batch. sync_channel(2) double-buffer.
Eliminates loader latency from the critical path at b=256 where
256 sequential next_sequence_pair calls took ~19ms/step (the entire
step budget at 52 sps).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:39:28 +02:00
jgrusewski
3eb8c62b7d perf(rl): GPU AoS→SoA scatter replaces host-side snapshot staging
New snapshot_aos_to_soa.cu kernel: one thread per snapshot, reads
contiguous Mbp10RawInput structs from mapped-pinned AoS buffer,
scatters fields to 10 SoA device buffers. Replaces 418k scalar
host copies + 10 DtoD memcpys with single memcpy + one kernel launch.

Add #[repr(C)] to Mbp10RawInput with 216-byte compile-time assertion.
Single MappedRecordBuffer replaces 10 separate staging buffers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 19:18:26 +02:00
jgrusewski
107649c03b perf(rl): eliminate 2 host-blocking syncs per step from forward_encoder
Split forward_only into forward_only_dispatch (no sync, no download)
and forward_only (sync + download). forward_encoder now calls the
dispatch-only path — it only needs h_t_d on device, stream-ordered.

Was: 2 × (raw_stream_sync + probs download) per step from the two
forward_encoder calls in step_with_lobsim. Each sync drained the
entire GPU pipeline while CPU did host staging for the next call.
GPU was 98.5% idle per nsys.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 18:54:32 +02:00
jgrusewski
a5b39ecc61 perf(rl): fix dqn_bwd occupancy 21→256 threads + zero launch_builder
dqn_distributional_q_bwd: restructure from 21 threads/block (Q_N_ATOMS)
to 256 threads/block. Each thread maps to one (action, atom) pair.
Softmax reduction stays in warp 0; all 231 threads write gradients in
parallel. Block tree-reduce for loss accumulation (no intra-block
atomicAdd). Was 23% of L40S GPU time at 167μs/call.

Convert ALL remaining 46 launch_builder calls to raw_launch across
dqn.rs (9), ppo.rs (6), outcome_head.rs (7), frd.rs (4), noisy.rs (3),
bucket_routing.rs (5), cfc/step.rs, aux_trunk.rs, snap_features.rs,
loss.rs. Zero launch_builder in crate (1 comment only).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 18:27:59 +02:00
jgrusewski
dcd851a62d perf(rl): cuBLAS SGEMM for DQN/IQN + Bellman/outcome kernel fusion
DQN: replace hand-written fwd/bwd/grad (27.5% GPU time) with cuBLAS
SGEMM tensor-core operations. Eliminates 30MiB per-batch grad scratch.

IQN: replace embedding matmul + output projection with cuBLAS SGEMM.
Pre-allocate scratch buffers for graph-capture safety. Fix device_ptr
→ raw_ptr in helpers. Convert remaining 6 launch_builder to raw_launch.

Bellman: fuse select_action_atoms + bellman_target_projection into
single kernel with shared-memory intermediate. Saves 424 launches.

Outcome: fuse fwd + CE + bwd into single kernel with shared-memory
logits/grad. Saves 848 launches.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 17:17:56 +02:00
jgrusewski
5f86664526 perf(rl): fuse adamw_increment into adamw_step + tau into iqn_forward
adamw: host-side step counter replaces device-resident counter +
separate increment kernel. Eliminates 3407 launches/run.

iqn: inline xorshift32 tau sampling into rl_iqn_forward kernel.
Eliminates 847 launches/run. Total: 5721 fewer launches (27825→22104).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 16:46:10 +02:00
jgrusewski
eca0642261 perf(rl): eliminate ALL device_ptr/device_ptr_mut SyncOnDrop from hot path
Replace 83 device_ptr()/device_ptr_mut() calls with raw_ptr() across
perception.rs (65), integrated.rs (8), loss.rs (3), cfc/step.rs (2),
snap_features.rs (2), isv.rs (2), mamba2_block.rs (1). Each device_ptr
created a SyncOnDrop guard with cuEventRecord overhead.

Also converts remaining cudarc memcpy_dtod_async and .synchronize()
to raw equivalents. Fix unused mut/import warnings.

Note: nsys reveals the REAL bottleneck is CudaSlice::drop() calling
stream.synchronize() before cuMemFree (has_async_alloc=false). 956
temp alloc drops over 200 steps = ~5 syncs/step from drops alone.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 16:20:12 +02:00
jgrusewski
da68220a01 perf(rl): eliminate ALL cudarc launch_builder from encoder hot path
Convert 110 remaining launch_builder calls across perception.rs (74),
mamba2_block.rs (31), optim.rs (2), heads.rs (3) to raw_launch() with
RawArgs. Also convert device_ptr()/memcpy_dtod_async/synchronize to
raw equivalents. Zero cudarc driver API in the entire step pipeline.

Combined with the previous 61 integrated.rs conversions, every kernel
launch in the training loop now bypasses cudarc's event tracking.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 16:04:56 +02:00
jgrusewski
9d6545bebb perf(rl): eliminate ALL 61 cudarc launch_builder calls from hot path
Convert every kernel launch to raw_launch() with RawArgs — zero cudarc
driver API in the steady-state step loop. Cache raw_stream/raw_train_stream
CUstream handles at init. Replace all stream.cu_stream() with cached
field reads. This eliminates ~122 spurious cuStreamWaitEvent+cuEventRecord
calls per step from cudarc's multi-stream event tracking.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:46:42 +02:00
jgrusewski
a9c417a79e perf(rl): replace ALL cudarc memcpy/sync with raw CUDA API in hot path
15 diag staging DtoD + 1 FRD labels DtoD + diag sync all converted
to raw_memcpy_dtod_async / raw_stream_sync. Zero cudarc driver calls
in steady-state step loop.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:20:30 +02:00
jgrusewski
f1093e9a07 perf(cudarc): kill stream sync management — always returns false
Force cudarc's is_managing_stream_synchronization() to false.
Eliminates ALL internal event tracking (cuStreamSynchronize,
cuEventRecord, cuStreamWaitEvent) that was adding 18.7 syncs/step.

All stream synchronization is managed manually via raw_launch events.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:13:25 +02:00
jgrusewski
2bcfadf5f4 fix(infra): auto-detect CUDA_COMPUTE_CAP from nvidia-smi + show build stderr
Sets CUDA_COMPUTE_CAP dynamically from the GPU on the node. Clears
both ml-alpha and ml-core build caches. Captures build.rs stderr in
pod logs for debugging.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 14:03:12 +02:00
jgrusewski
c899fe9f1b fix(infra): remove hardcoded CUDA_COMPUTE_CAP=89 — let nvidia-smi auto-detect
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:57:20 +02:00
jgrusewski
b05dc1b40d fix(build): auto-detect GPU arch — sm_90 on H100, sm_89 on L40S, sm_86 local
Replace fatbin with single-arch cubin auto-detected via nvidia-smi.
Each node compiles for its own GPU — faster builds, guaranteed compat.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:51:10 +02:00
jgrusewski
1668eb4283 fix(infra): clear stale ml-alpha build cache for fatbin migration
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:44:08 +02:00
jgrusewski
b531bd7148 perf(rl): mapped-pinned ISV + loss — zero DtoD copies forever
Replace isv_d CudaSlice with isv_mapped MappedF32Buffer. GPU writes
via dev_ptr, host reads via host_ptr. Zero copies, zero syncs.
Same for 4 loss accumulators (q/pi/v/frd).

Delete: isv_host Vec, isv_staging, loss_staging, loss_dtod_done_event,
all DtoD copies for ISV/losses, all event record/sync for losses.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:27:39 +02:00
jgrusewski
0064ad17fa perf(rl): mapped-pinned ISV + loss — zero DtoD copies forever
Replace isv_d CudaSlice with isv_mapped MappedF32Buffer. GPU writes
via dev_ptr, host reads via host_ptr. Zero copies, zero syncs.
Same for 4 loss accumulators (q/pi/v/frd) and 2 entropy accumulators.

Delete: isv_host Vec, isv_staging, frd_loss_staging, scalar_staging,
loss_staging_3, loss_dtod_done_event, all DtoD copies for ISV/losses,
all event record/sync for losses.

ISV is ~2.3KB, losses are 24B — PCIe bandwidth penalty negligible.
Mapped-pinned dev_ptr is stable → CUDA graph capture unaffected.

External head methods (DqnHead, PolicyHead, ValueHead, FrdHead)
changed from &CudaSlice<f32> to &u64 for ISV/loss device pointers —
.arg(&u64) pushes the same 8-byte CUdeviceptr as .arg(&CudaSlice).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 13:25:08 +02:00
jgrusewski
b5e99472c9 perf(rl): bypass cudarc entirely — raw graph launch + sync + events
Replace all cudarc graph.launch(), event.record/synchronize(),
stream.wait(), and memset_zeros in the hot path with direct CUDA
driver API calls. Eliminates cudarc's internal bind_to_thread +
event tracking overhead (~200us per call x ~40 calls/step).

Steady-state hot path is now zero cudarc calls:
- 4x graph.launch() → raw_graph_launch (cuGraphLaunch)
- 1x event.synchronize() → raw_event_sync (cuEventSynchronize)
- 5x event.record() → raw_event_record (cuEventRecord)
- 4x stream.wait() → raw_stream_wait_event (cuStreamWaitEvent)
- 28x memset_zeros → raw_memset_d8_zero (cuMemsetD8Async)

Also removed 36 redundant memset_zeros inside training graph
capture — backward kernels overwrite every element, so these
burned memset bandwidth on every graph replay for zero benefit.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:47:02 +02:00
jgrusewski
707e0dfe85 perf(rl): eliminate last hot-path sync — fully async loss readback
Async loss readback via loss_dtod_done_event — zero syncs in steady-
state step. Return previous step's cached stats (one-step delay).
DtoDs complete by stream ordering before next step's event wait.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:23:37 +02:00
jgrusewski
1d167af7b0 perf(diag): migrate snapshot_async to raw ptrs — 15 fewer device_ptr() per step
Replace 15 CudaSlice device_ptr() calls (each triggers cudarc event
tracking) with direct raw_ptr() u64 reads. Zero CUDA driver overhead
in the diag staging path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:20:02 +02:00
jgrusewski
148454d37d feat(cuda): curriculum weights E8 + adversarial regime boost kernels
rl_curriculum_weights: per-segment Sharpe → z-score → softmax difficulty
weights. Harder segments sampled more. Block tree-reduce, no atomics.

rl_adversarial_boost: multiply PER priority by boost factor for
negative-reward transitions. Self-regulating — fewer losses → fewer
boosts. ISV-driven threshold + boost magnitude.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 12:00:21 +02:00
jgrusewski
4223ba676f fix(build): fat cubins sm_86+sm_89+sm_90 — RTX 3050 + L40S + H100
Replace -cubin -arch=sm_XX with -fatbin -gencode for all 3 GPU archs.
All include_bytes! paths updated from .cubin to .fatbin. Removes
CUDA_COMPUTE_CAP env var dependency. cuModuleLoadData transparently
selects the right arch from the fat binary at runtime.

Fixes CUDA_ERROR_NO_BINARY_FOR_GPU on H100 (sm_90).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:58:14 +02:00
jgrusewski
63e447a182 feat(rl): outcome head backward + diag JSONL enrichment
Wire outcome CE backward → grad_W/b → Adam → grad_h_accumulate.
Add PopArt, spectral, Q-bias, per-branch LR, outcome metrics to
diag JSONL for training validation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:47:19 +02:00
jgrusewski
823a15f282 feat(scripts): alpha-rl-run.sh production wrapper + session memory updates
One-command production run: push, submit, monitor, download, analyze.
Memory: cudarc overhead pearl, nsys workflow reference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:39:53 +02:00
jgrusewski
e5a69a589b perf(rl): amortize spectral norm + eliminate hot-path sync/device_ptr/alloc
- Spectral norm: every 100 steps (843us/step -> 8.4us amortized)
  via Weyl's inequality (sigma_max shifts <= ||DW||_2 per Adam step)
- Replace train_stream.synchronize() with event-based GPU-side wait
  (train_done_event record/wait pattern, no host stall)
- Convert dqn_replay_step Q-loss readback to deferred one-step-delayed
  pattern: async DtoD into scalar_staging, read previous value via
  volatile read (eliminates sync + device_ptr per replay iteration)
- Delete dead read_scalar_via_staging (last caller removed)

Steady-state hot path (steps 3+) now has:
  - 1 synchronize (step_synthetic batched loss, unavoidable)
  - 0 device_ptr() calls
  - 0 per-step allocs
  - 0 read_scalar_via_staging
  - Spectral norm: 3 launches / 100 steps (was 3 / step)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 11:15:43 +02:00
jgrusewski
4a4e3171a7 perf(rl): pre-extract raw CUdeviceptr — zero device_ptr() in hot path
Cache all hot-path device pointers as raw u64 in HotPathPtrs struct at
trainer init. Replaces 9 device_ptr()/device_ptr_mut() calls (each
triggers cudarc event wait+record → cuStreamSynchronize) with direct
u64 reads from the cached struct.

Affected call sites:
- ISV staging DtoD (step_synthetic)
- Batched loss readback: pi/Q/V/FRD DtoD copies (step_synthetic)
- h_t→h_tp1 encoder DtoD copy (step_with_lobsim)
- trade_context/multires_output ptr pass to perception encoder

All pointers are stable (buffers pre-allocated at init, never
reallocated). Utility functions (read_slice_d, write_slice_f32_d, etc.)
retain device_ptr() as they operate on arbitrary caller-provided buffers
and each already does its own stream sync.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 10:46:43 +02:00
jgrusewski
41881f7724 perf(rl): migrate 25 hot-path launches to raw cuLaunchKernel
Replace all launch_builder().arg().launch() outside CUDA graph capture
with raw_launch() calling cuLaunchKernel directly. Eliminates ~200μs
per-launch cudarc overhead (trait dispatch, Arc, event guards).

25 launches migrated: PER push/sample/update/rebuild, HER track/inject
/forward, fused controllers, EMA producers, LR controller, spectral
norm, Q-bias, per-branch LR, grad accumulate, reduce_axis0.

61 launches inside graph capture regions kept as launch_builder
(only execute during warmup/capture steps 0-1).

Expected: ~7ms/step host overhead eliminated.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 10:15:40 +02:00
jgrusewski
7e12099934 feat(rl): wire P1+P2 — PopArt, spectral norm/decouple, outcome head, Q-bias, per-branch LR
All unconditional. No feature flags. ISV controls magnitudes.

PopArt normalize replaces apply_reward_scale in step_with_lobsim,
with V-correct on v_pred_d and v_pred_tp1_d. Spectral norm runs
one power iteration per step on DQN/IQN/policy weight matrices
after each Adam step in dqn_replay_step. Spectral decouple adds
lambda*mean(logits^2) penalty to Q and pi loss in step_synthetic.
K=3 outcome classifier forward+CE loss runs each step; labels
assigned from reward/done signals. Q-bias correction tracks
mean(Q - return) and emits correction to ISV. Per-branch LR
controller adjusts per-head LR scale factors from loss deltas.

ISV slots 553-572 bootstrapped. Per-branch LR kernel ABI fixed:
current losses passed as args (eliminates slot 572-575 collision
with RL_OUTCOME_AUX_LAMBDA_INDEX).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 09:49:57 +02:00
jgrusewski
3be864d9f9 feat(rl): raw CUDA launch wrapper + cudarc cu_function() accessor
raw_launch.rs: RawArgs fixed-array u64 storage + raw_launch() calling
cuLaunchKernel directly. Bypasses PushKernelArg trait dispatch, Arc
bookkeeping, event guards. Foundation for 500+ sps hot path.

vendor/cudarc: added CudaFunction::cu_function() accessor mirroring
CudaStream::cu_stream() pattern.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 09:45:57 +02:00
jgrusewski
dac9cfef5c spec: bypass cudarc for hot path — raw CUDA driver API + mega-kernel fusion
cudarc adds 18.8ms/step overhead at b=256 (GPU kernels take 0.16ms).
Phase 1: raw cuLaunchKernel wrapper. Phase 2: pre-extracted raw ptrs.
Phase 3: fused mega-kernels (20 launches → 3).
Target: 1ms/step → 500+ sps.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 09:39:17 +02:00
jgrusewski
62adf13187 feat(rl): P1+P2 kernels — PopArt, spectral norm, outcome head, Q-bias, per-branch LR
9 new CUDA kernels + OutcomeHead Rust struct + ISV slots 553-572:

PopArt: Welford-EMA reward normalization + V-head correction
Spectral: power iteration σ_max + L2 logit decoupling penalty
Outcome: K=3 trade-outcome classifier (Profit/Timeout/Loss)
Q-bias: EMA correction clipped ±10 for Bellman targets
Per-branch LR: adaptive per-head [0.5, 2.0] scaling from loss improvement

All unconditional — no feature flags. ISV-driven magnitudes.
No atomicAdd. Pre-compiled cubins.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 09:36:21 +02:00
jgrusewski
99707f7e4e perf(rl): eliminate 5 of 7 stream.synchronize() from step hot path
Replace per-step cuStreamSynchronize calls with deferred-read and
event-based cross-stream sync patterns:

- ISV staging reads (2 syncs): use previous step's staging data
  (one-step delay acceptable for slow-moving EMA controllers).
  Queue DtoD async for next step's read, flushed by batched
  loss sync.

- Loss scalar reads (3 syncs → 1): batch 3 individual
  read_scalar_via_staging calls (each with DtoD+sync) + FRD
  DtoD+sync into 4 concurrent DtoDs + 1 sync via pre-allocated
  loss_staging_3 MappedF32Buffer.

- Cross-stream syncs (3 syncs → 0 host-blocking): replace
  cuStreamSynchronize with CudaEvent record/wait pairs for
  push_done, sample_done, replay_done inter-stream dependencies.
  Events sync GPU timelines without blocking the host.

Hot path: 7 explicit syncs → 1 batched loss sync + 1 step-start
train_stream sync. Expected: ~5× reduction in host-wait time
per step (14ms → 2-4ms).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 09:26:16 +02:00
jgrusewski
274ff82494 perf(rl): eliminate per-step mapped-pinned allocs — pre-allocated staging
Replace 3 read_slice_d calls (each allocs+frees MappedF32Buffer) and
4 read_scalar_d calls with pre-allocated persistent isv_staging,
frd_loss_staging, and scalar_staging buffers. Deletes the now-unused
read_scalar_d function.

Eliminates ~20 cuMemHostAlloc + ~20 cuMemFree per step from the hot
path. GPU completes all kernels in 160us/step — host overhead was 14ms.
Target: <1ms host overhead at b=256.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 09:12:35 +02:00
jgrusewski
e0ea71e90e spec+plan: DQN concepts adoption — PopArt, spectral norm, outcome head, enrichment E1-E8
7 tasks: PopArt normalization, spectral norm+decoupling, K=3 outcome
aux head, Q-bias correction, per-branch LR, curriculum weights,
adversarial regime injection.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 08:59:51 +02:00
jgrusewski
4fc7d63185 feat(rl): wire bidirectional HER — track + inject + forward in step pipeline
Backward HER: on trade close, injects synthetic with peak PnL if
peak > 1.5× actual. Forward HER: evaluates closed trades after 50
steps, injects if holding would have been better.

ISV bootstrap: threshold=1.5, priority_boost=3.0, lookahead=50.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 08:48:11 +02:00
jgrusewski
029f2956f1 feat(cuda): bidirectional HER kernels — track, inject, forward
rl_hindsight_track: per-step mid accumulation + peak tracking.
rl_hindsight_inject: backward HER on done, prefix-sum replay write.
rl_hindsight_forward: forward HER after lookahead, prefix-sum write.
No atomicAdd.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 08:41:05 +02:00
jgrusewski
95af0db54f feat(rl): GpuHindsight struct + ISV slots for bidirectional HER
Device-resident buffers: mid_ring (backward peak tracking),
closed_ring + closed_h_t (forward continuation evaluation).
ISV slots 549-552 for threshold, priority_boost, inject_count,
forward_lookahead.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 08:36:05 +02:00
jgrusewski
b06355456a feat(rl): multi-stream — train_stream for PER sample/priority/rebuild
PER sample, update_priority, and tree_rebuild run on a dedicated
train_stream. The env-step pipeline (encoder through push_flush)
runs on the main stream. Sync points:

  1. Top of step_with_lobsim: train_stream.synchronize() ensures
     previous step's priority/rebuild completed before push touches
     the replay buffer.
  2. Before K-loop: stream.synchronize() ensures push_flush is done
     before train_stream's rl_per_sample reads the replay buffer.
  3. After rl_per_sample: train_stream.synchronize() ensures sampled_*
     buffers are written before step_synthetic/dqn_replay_step reads
     them on the main stream.
  4. After dqn_replay_step (k>0 path): stream.synchronize() ensures
     td_per_sample_d is written before train_stream's priority update.
     (step_synthetic already syncs internally.)

The last K-iter's priority update + tree rebuild are NOT synced at
step end — they overlap with the next step's encoder forward on the
main stream, hiding ~1ms of PER maintenance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:55:56 +02:00
jgrusewski
aab7a0b180 feat(rl): wire coalesced push_ring + push_flush, delete old rl_per_push
Two-kernel PER push: ring (Grid=B, Block=128 coalesced h_t copy) +
flush (block-0 prefix-sum + coalesced replay write). Replaces the old
single-block sequential kernel. Expected 8× faster (124μs → ~15μs).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:47:57 +02:00
jgrusewski
9ad7eb257c feat(cuda): coalesced PER push — ring + flush kernels (Grid=B, Block=128)
rl_per_push_ring: 128 threads copy h_t in one coalesced 512-byte
transaction. Thread 0 writes scalars + flush_flag.

rl_per_push_flush: block 0 prefix-sums flush_flags, signals via
volatile ready flag. All flushing blocks do coalesced 128-thread
h_t + h_tp1 + scalars write to replay. No atomicAdd.

Expected: 124μs → ~15μs per step (8× faster).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:41:15 +02:00
jgrusewski
be3d5ceeae plan: multi-stream + per-push rewrite — 5 tasks
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:32:56 +02:00
jgrusewski
63416f7c12 spec: multi-stream pipeline + per-push rewrite — target 100 sps at b=256
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:30:29 +02:00
jgrusewski
09873de1f5 perf(rl): zero-HtoD FRD labels via mapped-pinned staging + make stream pub
Replace write_slice_i32_d_pub (sync + alloc + DtoD) with persistent
MappedI32Buffer staging. Host writes to host_ptr, async DtoD to
frd_labels_d on the train stream — zero sync, zero alloc per step.

Also: make stream field pub for external DtoD access.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:21:07 +02:00
jgrusewski
e716d57d6f perf(cuda): vectorize rl_per_push — float4 loads (4× fewer memory transactions)
Replace 3 sequential 128-float copy loops with 32 float4 vectorized
loads each. Reduces per-thread memory transactions 4× for the heaviest
kernel (46% of GPU time at 123μs/call → expected ~30μs).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:14:17 +02:00
jgrusewski
5f3e3578a0 perf(diag): move ALL remaining device reads to async staging — zero syncs
Expanded DiagStaging with 8 more fields (position_lots, pyramid_count,
unit_entry_price, unit_entry_step, unit_lots, unit_trail,
close_unit_index, frd_logits). Eliminates 36 stream syncs per step
that consumed 90.9% of CUDA API time per nsys profile. unit_active
derived from unit_lots != 0 on host (avoids u8-to-f32 DtoD mismatch).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:11:59 +02:00
jgrusewski
5dd5a08963 feat(infra): optional nsys profiling in Argo template (-p nsys-profile=true)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:00:42 +02:00