Commit Graph

4015 Commits

Author SHA1 Message Date
jgrusewski
502fc469fe fix: TPE evaluate_point catches training errors as penalty, not abort
NaN/divergence during training is a valid PSO/TPE outcome — it means
the sampled hyperparameters are unstable. Score failed trials with 1e6
penalty so the optimizer learns to avoid that region, instead of
aborting all remaining trials.

The PSO/argmin paths already handled this (lines 1118, 1261). Only
the shared evaluate_point (TPE path) propagated errors as fatal.

Tested: 6 trials × 20 epochs on RTX 3050 — 2 NaN trials scored as
penalty, optimizer completed all 6 trials in 597s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:48:40 +01:00
jgrusewski
a36f682661 fix: fresh MlDevice per hyperopt trial — cuDevicePrimaryCtx bind fix
After trial N's DQNTrainer drops and releases the primary context,
trial N+1's bind_to_thread() fails with CUDA_ERROR_INVALID_VALUE on
RTX 3050 drivers. Creating a fresh MlDevice per trial (new primary
context retain) avoids the stale context state.

3 consecutive trials now complete successfully on RTX 3050 (12s/trial).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:25:28 +01:00
jgrusewski
b19e952920 fix: proper CUDA resource cleanup — Drop for GpuExperienceCollector,
GpuReplayBuffer, DQNTrainer

GpuExperienceCollector + GpuReplayBuffer: added Drop with cuStreamSync
to prevent cuMemFree racing with pending GPU work.

DQNTrainer: explicit Drop that releases GPU resources (fused_ctx,
experience collector) BEFORE cuda_stream and CudaContext drop. Without
this, the Drop order follows declaration order — GPU resources that sync
on the stream would sync on an already-destroyed stream.

hidden_dim_base from_continuous: clamp floor 256→64 to allow smoketest
and Phase Fast (hidden_dim=128) networks.

VRAM is properly released (nvidia-smi shows 0 MiB after trial).
Trial 2 CUDA_ERROR_INVALID_VALUE on bind_to_thread is a cudarc 0.19
primary context reuse issue — not VRAM related.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:21:05 +01:00
jgrusewski
085be5f5b1 fix: update DQN bounds test for Fast phase (default)
Default phase is now Fast — architecture dims (v_range, hidden_dim,
num_atoms, dueling_hidden, branch_hidden) are fixed to [phase_fast]
TOML values. Test assertions updated to expect single-point bounds.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:00:49 +01:00
jgrusewski
3ad2344c87 fix: smoke test — enable training + limit walk-forward data
Root cause: the test never actually trained — min_replay_size=1000 but
only 800 experiences generated, so can_train()=false. Training was
entirely skipped, and the NaN error came from validation loss.

With min_replay_size=100 (from smoketest TOML), training runs properly.
But ASSERT 7 (walk-forward validation) loaded 146K bars × 15 folds ×
29K GPU forward passes = hours in debug mode.

Fixes:
- Load config from dqn-smoketest.toml (batch=64, lr=0.0003, hidden=64,
  max_steps=50, min_replay=100)
- drop(trainer) before ASSERT 7 to release CUDA context — avoids GPU
  command queue serialization between trainer and validation DQN
- Limit walk-forward to last 2000 bars (3 folds × 400 steps = seconds)
- Read epsilon from metrics instead of async lock (avoids RwLock
  contention after training)

Test passes locally in 48s (RTX 3050, debug mode).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 13:55:09 +01:00
jgrusewski
c68ab6f030 wip: smoke test uses TOML profile — needs root cause investigation
Smoke test loads config from dqn-smoketest.toml instead of hardcoding.
Test hangs on RTX 3050 — root cause unresolved (not config, not OOM,
not duplicate processes). Needs strace/cuda-gdb to find blocking call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:13:48 +01:00
jgrusewski
dffe97a922 fix: log stale CUDA errors instead of discarding, remove max_steps_per_epoch from smoketest
check_err() now logs warnings for real kernel errors instead of let _ =.
Removed max_steps_per_epoch from smoketest TOML to match working config.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:10:45 +01:00
jgrusewski
6219491db6 refactor: move smoke test config to dqn-smoketest.toml, restore check_err
Smoke test loads all hyperparams from TOML profile instead of hardcoding.
TOML: hidden_dim=64, batch=64, lr=0.0003 (stable on RTX 3050 + H100).

Restored check_err() drain in device.rs — required to clear stale CUDA
errors from primary context reuse between tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 12:07:49 +01:00
jgrusewski
1f79c1bfed fix: move all smoke test config to dqn-smoketest.toml — zero hardcoding
Smoke test now loads all hyperparams from dqn-smoketest.toml profile.
TOML values are CI-safe on both RTX 3050 (4GB) and H100 (80GB):
  hidden_dim=64, batch=16, epochs=3, gpu_episodes=16, lr=0.0001

NoisyNet action diversity test: sigma=2.0 so noise exceeds Q-value
gaps on 256-wide networks. H100 profile test assertions updated.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 11:47:29 +01:00
jgrusewski
3d46dd87e8 fix: NoisyNet test uses sigma=2.0 for action diversity on wide networks
With 256-wide hidden layers (H100 gpu profile), Xavier-initialized Q-value
gaps are O(1/sqrt(256)) ≈ 0.06. The old sigma=0.5 produced NoisyNet noise
O(sigma/sqrt(fan_in)) ≈ 0.03 which couldn't flip argmax → all-same-action.
sigma=2.0 makes noise ≈ 0.12, exceeding Q-value gaps on any network width.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 11:19:27 +01:00
jgrusewski
adce841e6b fix: H100 GPU test failures — stale profile assertions + smoke test stability
- test_embedded_h100_parses: update assertions to match h100.toml values
  (gpu_n_episodes=2048, gpu_timesteps_per_episode=100)
- dqn_training_smoke_test: apply dqn-smoketest profile to cap hidden_dim=32.
  H100's gpu profile sets hidden_dim_base=256 which causes loss explosion
  (375x in 3 epochs) with lr=0.001.
- Revert gpu-test-pipeline DAG to compile-and-test (RWO PVC constraint)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 11:08:23 +01:00
jgrusewski
87a37ac020 fix: detect config/training + config/gpu changes as ml-changed
TOML training profiles are include_str!() into the ml binary at compile
time. Changes to config/training/ or config/gpu/ must trigger GPU test
rebuild, otherwise the binary runs with stale embedded defaults.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:56:44 +01:00
jgrusewski
f4bdd2934b ci: trigger GPU test with fixed executor memory limits
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:55:51 +01:00
jgrusewski
78cd46953f fix: bump both main+wait executor memory to 256Mi for submit-gpu-test
The Argo 'main' (emissary) container was the OOMKilled one, not 'wait'.
podSpecPatch now targets both containers to prevent executor OOM when
tracking large child workflow status JSON.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:53:19 +01:00
jgrusewski
dc18db2eb0 docs: fix stale doc comment in get_hyperopt_phase
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:49:05 +01:00
jgrusewski
3616739af9 ci: retrigger after submit-gpu-test OOM fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:48:17 +01:00
jgrusewski
a7c22ca5aa fix: CI OOMKilled on submit-gpu-test + YAML indentation fixes
submit-gpu-test wait sidecar had 64Mi limit — OOMKilled when tracking
large child workflow status JSON. Bumped to 256Mi.

Fixed YAML indentation errors in compile-and-train and training-pipeline
templates (misaligned labels, duplicate component keys).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:45:43 +01:00
jgrusewski
ca94c0e851 feat: wire two-phase hyperopt into Argo training workflows
Phase 1 writes ${MODEL}_phase1_results.json to PVC, Phase 2 reads it
via --hyperopt-params. Phase 2 uses half the trials but double epochs.
train-best downstream reads final ${MODEL}_hyperopt_results.json unchanged.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:39:36 +01:00
jgrusewski
286839b499 refactor: remove dead --phase single code path
Two phases only: fast (default) and full. No legacy 31D single-phase
search — it's a dead path that was never the right choice.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:37:31 +01:00
jgrusewski
43998a330a feat: two-phase hyperopt + backtest evaluator VRAM leak fix
Two-phase hyperopt splits 31D PSO search into sequential phases:
- Phase 1 (--phase fast, default): fix architecture to small network
  (hidden_dim=128, num_atoms=11), search learning dynamics (~15D).
- Phase 2 (--phase full): fix dynamics from Phase 1 JSON, search
  architecture (~5D). Halves dimensionality per phase → better convergence.
- Phase 1 output includes best_continuous_vector for Phase 2 consumption.

GpuBacktestEvaluator Drop impl: sync forked stream, destroy CUDA graph
and cuBLAS handles before CudaSlice buffers drop. Fixes 261MB/trial
VRAM leak on H100 hyperopt.

ml-core clippy fixes: hex literal, remove dead check_err drain,
unnecessary safety comment, unused OnceLock import.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:32:37 +01:00
jgrusewski
41eaa6522c perf: cap hyperopt hidden_dim=256, num_atoms=51 — 4x faster trials
Production defaults are sufficient for hyperopt exploration. Larger networks
can be tested in a separate phase with the best hyperparams found.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:02:01 +01:00
jgrusewski
e97c30b50a perf: cap hyperopt hidden_dim=256, num_atoms=51 — 4x faster trials
Production defaults are sufficient for hyperopt exploration. Larger networks
can be tested in a separate phase with the best hyperparams found.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 10:00:26 +01:00
jgrusewski
11855e958a feat: wire hyperopt search bounds from dqn-hyperopt.toml — zero hardcoded bounds
All 31 PSO search space bounds now loaded from config/training/dqn-hyperopt.toml
via HyperoptProfile::bound(). To change search ranges, edit the TOML — no code
changes needed.

Log-scale transforms (learning_rate, buffer_size, weight_decay, etc.) applied
in the adapter; TOML stores human-readable linear values.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 09:40:48 +01:00
jgrusewski
3c8e177932 feat: HyperoptProfile with TOML search space bounds
Add SearchSpaceSection, PsoSection, HyperoptProfile structs to
training_profile.rs. All 31 PSO search bounds now configurable in
config/training/dqn-hyperopt.toml — no code changes needed to
adjust search ranges.

HyperoptProfile::bound("field", default) returns the TOML value
or falls back to the hardcoded default. Adapter wiring is next step.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 09:33:55 +01:00
jgrusewski
720bccfb37 perf: chunked backtest evaluator — 7.8x fewer kernel launches
Batch 64 steps per cuBLAS forward call instead of 1 step at a time.
[n_windows × 64, state_dim] = [320, state_dim] per GEMM instead of
[5, state_dim]. Reduces kernel launches from 576K to 74K for 32K bars.

cuBLAS forward + expected_q + action_select batched across chunk.
env_step remains per-step sequential (stateful portfolio simulation).

Expected: backtest eval 107s → ~15s for large networks.
868 tests pass, 0 failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 09:29:17 +01:00
jgrusewski
d1d406dd95 fix: cap hyperopt hidden_dim_base at 512 — backtest evaluator is O(bars × dim²)
1024-dim network causes 107s/epoch in backtest (32K bars × 5 windows).
With 512 max: ~25s/epoch. Training itself is only 0.7ms/step regardless.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 09:16:11 +01:00
jgrusewski
7be2efd3e0 perf: replace backtest evaluator warp-matvec with cuBLAS SGEMM
Last component using old shared-memory-tiling kernel. With hidden_dim=768,
shared memory exceeded 49KB → CUDA_ERROR_INVALID_VALUE.

Replace with CublasForward::forward_online() + compute_expected_q +
experience_action_select kernels. Same cuBLAS pipeline as training
and experience collection.

Delete backtest_forward_kernel.cu (old warp-matvec kernel).
Fix hyperopt bounds test assertions for updated search ranges.
868 tests pass, 0 failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 08:47:26 +01:00
jgrusewski
b759467259 fix: add pushgateway port 9091 to GPU test network policy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:22:11 +01:00
jgrusewski
ae5ae056d6 fix: add gpu-test network policy label to all GPU workflow templates
5 templates were missing app.kubernetes.io/component=gpu-test label,
causing MinIO log archival to fail (port 9000 blocked by network policy).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 02:08:50 +01:00
jgrusewski
109f4e9414 perf: allow num_atoms up to 101 — H100 handles larger C51 distributions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:42:38 +01:00
jgrusewski
e464f89a04 perf: widen hidden_dim_base to 1024 — H100 has headroom at 0.7ms/step
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:41:44 +01:00
jgrusewski
6e40e86c09 perf: constrain hyperopt search space + increase H100 experience episodes
Hyperopt:
- num_atoms: 51-201 → 11-51 (GPU profile caps to hardware limit)
- hidden_dim_base: 256-1024 → 128-512 (1024+ is wasteful for 2-layer net)
- Prevents wildly oversized networks (num_atoms=200 caused 25s/epoch)

H100 experience:
- gpu_n_episodes: 256 → 2048 (8x larger cuBLAS batch saturates 132 SMs)
- gpu_timesteps_per_episode: 500 → 100 (fewer steps, more parallel episodes)
- Total experiences: 204K/epoch (was 128K) with better GPU utilization
- Expected: experience 357ms → ~100ms (SM utilization 5% → 40%+)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:40:16 +01:00
jgrusewski
897c063d7d fix: force use_branching=true in hyperopt — GPU pipeline requires branching
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 01:28:29 +01:00
jgrusewski
e3b8830a63 perf: GPU segment tree for O(log n) PER sampling — replaces O(n) prefix sum
Binary segment tree as flat CudaSlice<f32>[2*capacity_pow2]. Three kernels:
- seg_tree_update: batch priority update + propagate sums to root
- seg_tree_insert: insert raw priorities during replay buffer fill
- seg_tree_sample: parallel root-to-leaf traversal with Philox RNG

Replaces: prefix_sum (3 kernels), searchsorted, pow_alpha, per_gen_thresholds.
Deleted: prefix_sum_kernel.cu, searchsorted_kernel.cu, per_threshold_kernel.cu.

Sampling cost: O(n) growing with buffer fill → O(log n) constant.
Expected: H100 training 12→23ms/step (growing) → ~5ms/step (constant).
All 1,225 tests pass (359 ml-dqn + 866 ml).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:52:37 +01:00
jgrusewski
1ce22c7e9c docs: GPU segment tree spec + plan for O(log n) PER sampling
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:39:34 +01:00
jgrusewski
e8c0b83361 fix: prefix sum block_dim from device capability, not hardcoded
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:21:24 +01:00
jgrusewski
c0b43c9fad perf: fix PER prefix sum single-block fallback at 500K buffer
Root cause of H100 training time growth (12ms→23ms/step across epochs):
pfx_sum() fell back to single-block O(n) scan when num_blocks > 1024.

With block_dim=256 and buffer_size=500K: num_blocks = 1953 > 1024 → fallback.
Fix: increase block_dim to 1024 (capped at device max_threads_per_block).
Now: num_blocks = 500000/1024 = 489 < 1024 → multi-block 3-phase scan works.

Expected impact: PER sampling cost stays constant as buffer fills instead
of growing linearly. Training step time should be stable across epochs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 00:19:44 +01:00
jgrusewski
49444c19d8 fix: memcpy_dtoh buffer size mismatch in Q-value readback
q_out_buf is [config.batch_size, total_actions] but host readback buffers
were sized for [sample_size, total_actions]. When sample_size < batch_size,
cudarc's memcpy_dtoh assertion (dst.len >= src.len) panics with SIGABRT.

Fix: allocate host buffer to match q_out.len(), then truncate to sample_size.
Affects: compute_epoch_q_diagnostics, compute_validation_loss, PER refresh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:58:01 +01:00
jgrusewski
a75a98bd0d feat: TOML training profile system — config-driven hyperparameters
Training Profile Loader:
- 3-tier resolution: $FOXHUNT_TRAINING_PROFILE > filesystem > embedded defaults
- DqnTrainingProfile with 10 sections, all Option<T> for sparse profiles
- apply_to() applies only Some fields, preserving struct defaults
- 11 unit tests, all passing

TOML Profiles (config/training/):
- dqn-production.toml: full Rainbow DQN (40+ params)
- dqn-smoketest.toml: CI fast path (sparse, 8 overrides)
- dqn-hyperopt.toml: PSO search space ranges + fixed flags
- ppo-production.toml, ppo-smoketest.toml
- supervised-production.toml, supervised-smoketest.toml
- walk-forward.toml: window sizes

CLI Integration:
- train_baseline_rl: --training-profile (default: dqn-production)
- train_baseline_supervised: --training-profile (default: supervised-production)
- Merge priority: CLI args > TOML profile > GPU profile > struct defaults

Smoke Tests:
- smoke_params() now loads dqn-smoketest.toml instead of hardcoding
- Production features set as manual overrides (testing flags, not config)

Infrastructure:
- K8s job-template.yaml: TRAINING_PROFILE env var + --training-profile arg
- Delete old config/ml/training.toml (replaced, zero callers)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:35:41 +01:00
jgrusewski
baf8308931 docs: training profile configuration system — spec + plan
TOML-based per-model training profiles replacing hardcoded hyperparameters.
8 config files (DQN/PPO/supervised × production/smoketest + hyperopt + walk-forward).
3-tier loading: env var > filesystem > embedded defaults.
Full CLI coverage for all profile fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:22:58 +01:00
jgrusewski
8a2fcc968d cleanup: remove all debug eprintln, log_gpu_memory, unnecessary check_err drains
- Remove 6 eprintln!("[GPU-DEBUG]...") statements from gpu_dqn_trainer.rs,
  constructor.rs, and elementwise.rs
- Delete log_gpu_memory() function and all 18 callers in smoke test files
- Remove check_err() drains from GpuDqnTrainer::new() and
  GpuExperienceCollector::new() — root cause is fixed (per-context kernel cache)
- Keep check_err() after CUDA Graph capture (legitimate — drains event tracking errors)
- Fix broken import lines in smoke test files after sed cleanup

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 23:10:27 +01:00
jgrusewski
da1cea1181 fix: sequential GPU test contamination — per-context ElementwiseKernels cache
Root cause: ElementwiseKernels was cached in a static OnceLock, compiled
once on the first test's CudaContext. When the second test created a new
CudaContext (fresh Arc), the cached CudaFunction handles were stale,
causing CUDA_ERROR_INVALID_VALUE on alloc_zeros (n=128, op=abs).

Fix: Replace OnceLock with a Mutex<HashMap<usize, Arc<ElementwiseKernels>>>
keyed by Arc<CudaContext> pointer address. Each distinct CudaContext gets
fresh kernel compilation. Old entries are evicted on context change.

Also: eliminate ALL GpuTensor from DQN training path (metrics.rs,
training_loop.rs). Replace with CPU computation for validation (cold path)
and raw CudaSlice for replay buffer insertion. Log deferred CUDA errors
instead of silently swallowing.

Result: ALL 6 sequential GPU smoke tests pass (was 1 failing).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:53:57 +01:00
jgrusewski
a143566549 fix: eliminate ALL GpuTensor from DQN training path
- Replace GpuTensor gradient accumulation with CPU-side BTreeMap<String, Vec<f32>>
- Replace GpuTensor validation loss computation with CPU Sharpe (cold path)
- Remove GpuTensor import from training_loop.rs and metrics.rs
- Delete dead cuda_slice_to_tensor conversion helpers
- Change val_features_gpu/val_closes_gpu/val_ofi_gpu from GpuTensor to Vec<f32>
- Replace PER priority refresh GpuTensor::from_vec with stream.clone_htod
- Log deferred CUDA errors instead of silently swallowing via check_err()
- Delete get_q_values() (dead, no callers)

Zero GpuTensor (ml-core elementwise kernels) in the DQN training pipeline.
All GPU operations use raw CudaSlice via cudarc or cuBLAS.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 22:05:08 +01:00
jgrusewski
129417ce7a fix: bypass GpuTensor for replay buffer insertion — direct CudaSlice path
Replace CudaSlice→GpuTensor→CudaSlice roundtrip in experience batch
insertion with direct CudaSlice insertion into GpuReplayBuffer.

- experience_kernels.cu: output dones as float (0.0/1.0) instead of int
- GpuExperienceBatch.dones: CudaSlice<i32> → CudaSlice<f32>
- training_loop.rs: call gpu_buf.gpu.insert_batch() with raw CudaSlice
- Remove cuda_slice_to_tensor_f32 conversion (GpuTensor bridge)
- Remove log_gpu_memory helper (was a debug hack, not a fix)

Remaining: 1 sequential test still fails — GpuTensor operations in the
gradient accumulation path (lines 1015-1128) cache kernel handles on the
cudarc context. Needs systematic instrumented debugging to locate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 21:25:17 +01:00
jgrusewski
73b6513cff fix: sequential GPU test contamination — remove static OnceLock<MlDevice>
Root cause: `SMOKE_CUDA: OnceLock<MlDevice>` held a static Arc<CudaContext>
for the process lifetime. CudaSlice Drop from test N recorded errors on
this shared context's error_state, causing test N+1's bind_to_thread() to
fail with CUDA_ERROR_INVALID_VALUE.

Fix: create a fresh MlDevice per test (no static caching). Each test gets
its own CudaContext Arc with clean error_state.

Also: convert all GPU smoke tests from #[tokio::test] to synchronous #[test]
with explicit tokio::runtime::Builder::new_current_thread(). The runtime is
explicitly dropped between tests, ensuring all Arc<CudaContext> refs are freed.

Result: 5 of 6 sequential smoke tests now pass. The remaining 1 failure is
a real Candle GpuTensor bug: the replay buffer insertion path still uses
Candle's elementwise kernels, which cache CudaFunction handles that become
stale across test boundaries. Fix: eliminate Candle from replay buffer path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 21:06:40 +01:00
jgrusewski
3df8b2fc79 infra: resize test-data PVC to 50Gi for 3Q MBP-10 + trades data
3Q of MBP-10 order book data for ES.FUT is ~18GB compressed.
10Gi PVC was insufficient for the full OHLCV + MBP-10 + trades dataset.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:28:08 +01:00
jgrusewski
922fd5e93e ci: add DQN perf benchmark step + 3Q test data for CI
GPU test pipeline:
- Add perf-benchmark step after compile-and-test
- Runs DQN training on 3Q ES.FUT (OHLCV + MBP-10 + trades)
- Reports epoch time (ms), fails if > 500ms (H100 regression guard)
- batch_size=1024, 5 epochs × 100 steps, skips epoch 1 (init)

Populate test data job:
- Copy 3 quarters per symbol (was 1) for all data types
- OHLCV: ~6MB/symbol for walk-forward + perf benchmarks
- MBP-10: 3Q for OFI feature pipeline testing
- Trades: 3Q for trade-flow feature testing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:17:27 +01:00
jgrusewski
fafec932dd perf: eliminate all Candle forward() + fix sequential test Drop
- Remove ALL agent.forward() from DQN training hot paths
- Replace per-step Q-divergence check with cuBLAS compute_q_stats
- Remove dead test_training_rejects_missing_gpu_collector (tests dead code)
- Add Drop impls: FusedTrainingCtx syncs stream + drains errors before drop
- GpuDqnTrainer Drop: drain deferred CUDA errors via check_err()
- Sequential GPU test contamination: cudarc in-process context limitation,
  run ignored GPU tests via separate cargo invocations (CI already does this)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 18:06:09 +01:00
jgrusewski
d7cdd778c8 perf: eliminate ALL Candle forward() from DQN training pipeline
Replace every agent.forward() (Candle dispatch chain) in the training
hot path with cuBLAS forward via fused_ctx.compute_q_values/compute_q_stats.

Removed:
- Per-step Q-divergence Candle forward in train_step_single_batch
- Per-step Q-divergence Candle forward in train_step_with_accumulation
- collect_qvalue_statistics (dead Candle path, zero callers)
- select_actions_batch_gpu (dead, GPU collector uses experience_action_select kernel)
- Candle forward in compute_epoch_q_diagnostics → cuBLAS
- Candle forward in compute_validation_loss → cuBLAS
- Candle forward in refresh_stale_per_priorities → cuBLAS

Zero Candle involvement in the DQN training pipeline.
All Q-value computation uses cuBLAS SGEMM + GPU reduction kernels.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 17:29:49 +01:00
jgrusewski
0c0873d075 perf: replace Candle Q-value estimation with cuBLAS + GPU reduction
Replace agent.forward() (Candle dispatch chain) with cuBLAS SGEMM forward +
compute_expected_q kernel + q_stats_reduce kernel. Zero Candle involvement in
the DQN training path. Only 20 bytes (5 scalars) read from GPU at epoch end.

Validation phase: 27ms → 0ms on RTX 3050.
Total epoch: 78ms → 50ms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 16:58:28 +01:00