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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
- 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>
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>
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>