The constructor hardcoded num_actions: 9 (old 9-exposure scheme).
IQN head computed total_branch_actions = b0+b1+b2 = 15, missing b3.
Both caused SIGSEGV on H100 from buffer overrun.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add magnitude head (branch 3) to BranchingWeightSet, GpuBranchPtrs, and all
BF16 mirror structs. Update flatten/unflatten from 20 to 24 individual tensors
(indices 24-25 = bottleneck remain flat-buffer-only). Fix bottleneck index
in experience collector (was 20, now 24). Update all construction sites:
fused_training clone, gradient_budget smoke tests, backtest evaluator.
Fix monitoring comments/labels for 4-branch encoding (dir*mag 3x3), delete
dead `if false` block, delete DELETED marker comments.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove dead code from the 3-branch DQN era:
- CUDA kernels: exposure_aux_grad_kernel, exposure_pretrain_step
- GpuDqnTrainer: 16 struct fields (aux Adam optimizer, scratch buffers, kernel handles)
- Methods: set_exposure_aux_weight, launch_exposure_aux_grad, run_pretrain_step
- Constructor: aux allocations, kernel loading, struct literal entries
- compile_training_kernels: reduced from 35 to 33 return values
Note: compute_target_position() in trade_physics.cuh retained — still used
by backtest_env_step kernel (single-step evaluator).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire 4th magnitude branch (size=3) into BranchingDuelingQNetwork construction,
DQNAgentType::branch_sizes() return type, training loop diversity metrics,
backtest config in metrics.rs and hyperopt adapter, and DT pre-training config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove exposure_aux_weight, exposure_targets, best_exposure_out fields and
associated methods (run_pretrain_step, replay_adam_and_readback_pretrain) from
fused_training.rs and gpu_experience_collector.rs. Delete corresponding
callers in training_loop.rs: pretrain loop, exposure copy block, aux weight
decay. All were exposure-branch artifacts incompatible with the 4-branch
(direction/magnitude/order/urgency) factorization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces broken 9-output exposure branch with two 3-output branches.
Each 3-output branch has -1/2 gradient (33%) — strong enough for dueling.
Eliminates i%3 Q-value degeneracy permanently.
3 novel gems: direction-specific dense reward, per-branch epsilon,
Flat detection shortcut with gradient zeroing.
~480 lines of dead code removed (aux optimizer, CEA, pre-training).
20+ files changed. Full CUDA, no CPU path, no stubs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
With separate optimizer, no gradient competition — safe to increase.
Smoke test shows within-group differentiation starting (S100≠S25≠L50).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dueling A'[i] = A[i] - mean(A) gives identical -1/9 gradient to all
8 non-selected exposure outputs, causing i%3 Q-value degeneracy.
For d==0 (exposure, 9 outputs): skip mean subtraction entirely.
Non-selected outputs get ZERO C51 gradient — only the separate aux
optimizer updates them with unique per-output directional signal.
Branches d=1,2 (order=3, urgency=3) keep mean subtraction — the -1/2
gradient (33%) is strong enough to differentiate 3 outputs.
4 locations patched in the C51 loss kernel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The exposure aux loss now has its OWN Adam state (m, v, t) and updates
the exposure output weights INDEPENDENTLY from the main C51 optimizer.
No shared gradient buffer, no gradient clipping competition.
This is multi-task learning done right — each objective has its own
optimizer that doesn't interfere with the other.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The exposure aux backward GEMM amplifies gradients through the
hidden activation matrix (d_logits × h_b0), producing grad_norm=5000+.
With max_grad_norm=10, this throttles ALL gradients by 500x (lr/500).
Fix: reduce aux_weight 0.5→0.01 (50x) + clip_grad_buf_inplace after
aux GEMM to cap combined gradient at max_grad_norm.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix 7: Merge spectral+forward+aux into graph_mega — 2 graph launches per step
instead of 3-4. Batch upload, stochastic depth mask, HER donors, and adam step
counter run outside the graph. Exposure aux GEMM moved into submit_aux_ops so
it's captured inside graph_mega. Falls back to individual graphs if mega capture
fails.
Fix 3: Validation runs on dedicated CUDA stream forked in constructor. Uses
one-epoch-delayed cached result to avoid blocking the training stream. Epoch 0
runs synchronously to bootstrap the async pipeline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed exposure_aux_grad_kernel parameter from `float aux_weight` to
`const float* aux_weight_ptr`. GPU-resident scalar updated via async
HtoD before each launch. Enables capturing aux GEMM in CUDA graph.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix 2: Eliminate validation graph re-capture (DtoD into same buffers)
Fix 3: Async validation on separate CUDA stream (overlap with experience)
Fix 4: Aux GEMM in graph_aux (GPU scalar + kernel sig change)
Fix 7: Mega-graph fusion (spectral+forward+aux → 2 launches)
Combined with 5 implemented fixes: 47s → ~16s/epoch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TD(λ): self-bootstrap with rewards as Q(s') approximation, overwrites n-step
Hindsight: relabel fraction of experience rewards with optimal exit PnL
Curriculum: sort bars by difficulty, restrict to easy bars early, expand over training
No stubs, no debug placeholders — all fully wired.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pre-training: 50 batch supervised direction init at epoch 0
- Exposure aux targets: DtoD copy from collector to fused context
- PopArt: wired behind config flag (disabled by default)
- TD(λ)/hindsight/curriculum: stubs with config guards
- Loss threshold 500→100K for v8 reward distribution
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pre-training: 50 batch supervised direction init at epoch 0 (exposure branch)
- Exposure aux targets: DtoD copy from collector to fused context after experience collection
- PopArt: normalize_rewards_popart_inplace in fused training step (disabled by default)
- TD(λ): stub with debug log (kernel loaded, awaiting V(s) estimates for full wiring)
- Hindsight/curriculum: stubs with config guards (disabled by default)
- Added rewards_buf_mut() and normalize_rewards_popart_inplace() to GpuDqnTrainer
- Added run_pretrain_step() and replay_adam_and_readback_pretrain() wrappers to FusedTrainingCtx
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
At 0.001, the dense micro-reward was 1/1000th the magnitude of the
sparse exit reward (±10). Too weak to meaningfully bootstrap Q-values.
At 0.01, it's 1/100th — strong enough to provide directional feedback
while still subordinate to the exit signal.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v8 reward changes (soft-clamp, CEA, micro-reward) produce higher
initial losses as C51 atoms calibrate to the new distribution.
Values up to ~50K are normal in early epochs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The action_select kernel signature changed from single `float epsilon`
to 4 args (eps_start, eps_end, current_epoch, total_epochs) for GPU-side
cosine schedule. The backtest evaluator wasn't updated, causing a
segfault from misaligned kernel args.
Fix: pass eps_start=0, eps_end=0 (greedy in backtest, no exploration).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CRITICAL: softmax_confidence created new CUDA context+stream per call.
Now reuses self.stream — eliminates severe inference latency.
HIGH: GpuPrioritized::add() was a silent no-op hiding bugs.
Now returns error + logs to catch invalid single-experience insertion.
MEDIUM: Duplicated epsilon logic across select_action methods.
Now uses get_effective_epsilon() consistently.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update n_steps test expectations from 3→5 to match current dqn-smoketest.toml
and dqn-production.toml configs. Update reward v7→v8 comments in experience_kernels.cu.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Forces exploration by starting value head biases at -0.1 for all atoms,
so the model must discover positive-value states through experience rather
than confidently exploiting random positives from zero-init.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Exposure aux kernel -> f32 scratch -> bf16 cast -> backward_fc_layer GEMM
- Weight + bias gradients accumulated into grad_buf (pure GPU, zero CPU sync)
- CEA warmup: linear decay 1.0->base over 25% of epochs
- OFI epoch gate: disabled for first 5 epochs
- Exposure aux decay: base -> 10% over warmup_epochs
- Removed dead v6 fields: loss_aversion, beta_penalty, regret_blend, trade_clustering_penalty
from ExperienceCollectorConfig (kernel no longer reads them)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5 categories, 18 items:
A) v7.1 fixes: aux GEMM, CEA warmup, OFI gate, dead code
B) Bootstrap trap: GPU cosine epsilon, n-step→5, dense micro-reward
C) Initialization: supervised pre-train, pessimistic Q-init, phase schedule
D) Advanced: TD(λ), PopArt normalization, curriculum learning, hindsight relabel
E) Dead code removal
4 new CUDA kernels, 3 modified, 12 new config fields, 4 removed.
Full GPU, no CPU path, no memory copies. Target: 50%+ win rate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>