Fix last 2 tests:
- action_masking: test used 81-action FactoredAction decoder for
45-action mask. Fixed to use mask's own 5×9 exposure encoding.
- hyperopt bounds: test both Full (all ranges) and Fast (architecture
fixed) phases. Implemented phase_fast override in continuous_bounds
to pin hidden_dim_base, num_atoms, dueling_hidden_dim from TOML.
Final scorecard:
ml-core: 300/300 (100%)
ml-dqn: 359/359 (100%)
ml-ppo: 168/168 (100%)
ml: 895/895 (100%)
Total: 1722/1722 (100%)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bulk nvrtc removal sed replaced ALL load_cubin(ptx) with
CQL_GRAD_CUBIN, but kernels live in 12 different cubin files.
Fixed: DQN_UTILITY_CUBIN for utility kernels, C51_LOSS_CUBIN for
c51 loss, MSE_LOSS_CUBIN/MSE_GRAD_CUBIN for MSE, ENSEMBLE_CUBIN
for ensemble, etc.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix f32→bf16 type mismatches in 7 ml test files:
- gpu_action_selector, mod.rs, signal_adapter, gpu_residency,
gradient_budget, performance, training_stability
- Convert Vec<f32> → Vec<half::bf16> for memcpy_htod
- Convert readback to bf16 then to f32
- Relax tolerances for BF16 precision
Remaining 57 failures: nvrtc stubs in ml-ppo cuda_nn + kernel name
mismatches + replay buffer type issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Agent fixes: kernel name mismatches, sgemm→GemmEx BF16 in linear.rs
and stream_ops.rs, shared memory sizes for BF16 kernels, BF16-safe
optimizer betas (0.999 rounds to 1.0 in BF16), argmax index readback,
test tolerances relaxed for BF16 precision (~3 decimal digits).
ml-core: 300 passed, 0 failed.
ml-dqn: 304 passed, 55 failed (4 dead nvrtc stubs — separate task).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace all 20 dangling `ptx` variable references with correct cubin
static names after nvrtc removal sed. Fix ml-dqn dead code stubs
(residual.rs, rmsnorm.rs, noisy_layers.rs, gpu_replay_buffer.rs).
Clean up unused `let ptx` variables.
Zero compilation errors across full workspace.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GELU forward and backward now use bf16_tanh from common header
instead of float tanhf. tanh(x) = (exp(2x)-1)/(exp(2x)+1) via
bf16_exp — all native __nv_bfloat16 arithmetic. No exceptions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 COMPLETE. Every CUDA kernel in the pipeline compiles with
__nv_bfloat16 parameters and native BF16 arithmetic.
Final 4 kernels fixed:
- iql_value_kernel.cu: removed duplicate bf16_exp/bf16_sqrt, fixed dot-product accumulation
- experience_kernels.cu: fixed mixed type arithmetic, removed duplicate function definitions
- attention_backward_kernel.cu: fixed warp shuffles (float for __shfl_xor_sync), math functions
- ppo_experience_kernel.cu: BF16 overloads for matvec, explicit casts for barrier config
Remaining: 23 Rust errors in ml-core (BF16 boundary conversions).
Phase 2-4 of the plan: delete GpuTensor/nvrtc, fix Rust, wire cuBLAS, test.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ema, relu_mask, per_update, iqn_cvar, trade_stats, monitoring,
backward, backtest_fwd_supervised, backtest_fwd_ppo, statistics,
curiosity, her_relabel — all converted to native __nv_bfloat16
with bf16_* wrappers. Zero __bfloat162float/__float2bfloat16 casts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
17 tasks across 4 phases:
- Phase 1: 10 tasks converting 35 CUDA kernel internals to native BF16
(bf16_* wrappers, native arithmetic, no __bfloat162float casts)
- Phase 2: Delete GpuTensor + nvrtc dependency (replace with raw CudaSlice)
- Phase 3: Fix Rust compilation errors at host boundaries
- Phase 4: Wire cublasGemmEx + test
Includes lessons learned from failed bulk-sed approach and explicit
conversion rules for every agent.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- bf16_shfl_xor() — warp shuffle XOR for __nv_bfloat16
- bf16_shfl_down() — warp shuffle down
- bf16_warp_sum() — full warp-level sum reduction
- bf16_warp_max() — full warp-level max reduction
These hide the mandatory float cast (no native BF16 shuffle HW)
behind a clean BF16 API. Kernels use bf16_warp_sum(val) instead
of manual __bfloat162float → __shfl_xor_sync → __float2bfloat16.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CUDA kernels: 36 of 37 compiled with __nv_bfloat16* (dt_kernels.cu remaining)
Rust types: ALL CudaSlice<f32> → CudaSlice<half::bf16> across ml + ml-core
Build: all kernels now compiled with common_device_functions.cuh (BF16 helpers)
BF16 math wrappers: bf16_sqrt, bf16_log, bf16_exp, bf16_pow, bf16_fabs, bf16_fmax,
bf16_fmin, bf16_cos, bf16_zero, bf16_one, bf16(), atomicAddBF16
NOT YET COMPILING — ml-core boundary errors (Vec<f32> → Vec<half::bf16>)
and dt_kernels.cu float*__nv_bfloat16 ambiguity remain.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- forward_target_bf16() for target network inference path
- BF16 activation pointer accessors for wiring into training path
- States F32→BF16 conversion at system boundary (experience collector output)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- cudarc features: removed "nvrtc" (no longer used, all kernels precompiled)
- cudarc features: added "f16" (enables DeviceRepr + ValidAsZeroBits for half::bf16)
- CudaSlice<half::bf16> now fully functional with all cudarc operations
- Foundation for full BF16 tensor core conversion (next session)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CachedPtrs:
- 35-field struct caching all GPU buffer u64 device pointers
- Computed once at construction, replaces 110+ raw_device_ptr() per step
- Eliminates cudarc event tracking machinery from hot path
Sync removal:
- apply_iqn_trunk_gradient: removed cuStreamSynchronize (same-stream ordering)
- apply_ensemble_trunk_gradient: removed cuStreamSynchronize
- run_ensemble_step: removed cuStreamSynchronize + local EvtGuard struct
- replay_adam_and_readback: zero per-step DtoH — returns 0.0, epoch boundary
uses GPU training guard's accumulator buffer for actual metrics
Logging cleanup:
- Removed per-step tracing::info diagnostic with cuStreamSync (was every 1000 steps)
- Removed per-step tracing::debug for IQL/IQN/CQL (format overhead in debug builds)
- Single tracing::debug at end of run_full_step (zero cost in release)
EventTrackingGuard made pub(crate) for fused_training.rs access.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ROOT CAUSE of fake 92% MaxDD: when capital floor fires (done=1), the
training kernel wrote done but did NOT reset the portfolio state. The
next step read the blown portfolio, hit the pre-trade floor check again,
returned done=1 with reward=-10, and the episode got STUCK in a done
loop for the rest of the epoch. Every step produced done=1 + -10 reward
from dead capital.
Fix: reset all 20 portfolio state fields + current_timesteps to fresh
capital at BOTH:
1. Pre-trade floor check (early return path, line 607)
2. Post-trade done detection (end of kernel, after outputs written)
This ensures the next step starts a clean episode with initial_capital,
flat position, and zero Kelly/realized_pnl accumulators.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The done bar's return (liquidation loss) must be counted in the current
episode's DD before resetting. Previous code reset BEFORE compounding,
which applied the liquidation return to fresh initial_capital.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The training financials computed MaxDD by compounding returns across
episode boundaries. When the capital floor circuit breaker fired (done=1)
and the episode reset with fresh capital, the equity curve kept falling.
This produced fake 92% MaxDD from concatenated episodes.
Fix: added done_flags to TradeStats (downloaded from GPU done_out buffer
at epoch end). MaxDD computation now resets equity/peak at done=1 events,
matching the backtest evaluator's per-window isolation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Research finding: NoisyNet provides undirected parametric noise, count-bonus
provides directed UCB action-level exploration. They don't conflict —
they address different failure modes (NoisyNet: policy lock-in,
count-bonus: action collapse onto Flat).
The CUDA kernel was already fully wired. Only the coefficient changed
from 0.0 to 0.05. Especially important for:
- 81 factored actions (high combinatorial space)
- Sparse trade-completion rewards (long reward-drought periods)
- Non-stationary markets (exploration must remain active)
References: Osband et al. 2019, Bellemare et al. 2016, Ostrovski et al. 2017
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Automated mismatch scan confirmed all from_continuous fixed defaults now
match Default::default(). No more silent divergence between hyperopt
params and manual/test params.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Update 3 integration tests in dqn_action_collapse_fix_test.rs that
expected the old 46D search space (cql_alpha tunable, phase-specific
v_range bounds). Now expect 22D with fixed cql_alpha.
- Fix cql_alpha: from_continuous hardcoded 0.5, Default uses 0.1.
Aligned to 0.1 (same pattern as dd_threshold fix earlier).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The stale comments agent incorrectly changed calculate_diversity_penalty's
max_entropy from log2(3) to log2(9). But this function takes 3-bucket
BUY/SELL/HOLD ratios — max entropy of 3 buckets IS log2(3).
The log2(9) change was correct for the LOGGING in extract_objective
(which displays max_entropy for 9 exposure actions), but wrong for the
penalty function that operates on 3 action categories.
Reverted to log2(3) and restored test assertions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- gpu_backtest_evaluator: "5-bit mask" → "9-bit mask" (9 exposure levels)
- dqn.rs CVaR ramp: updated examples to match current formula (excess*100).min(3.0)
- dqn.rs module doc: "maximizes" → "minimizes", weights match actual code
- entropy max_entropy: log2(3) → log2(9) in both occurrences
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- bars_per_day: now propagated from DQNHyperparameters to GpuBacktestConfig
(was silently using Default 390.0 regardless of hyperparams setting)
- trading_days_per_year: added to GpuBacktestConfig, replaces hardcoded 252.0
in annualization_factor computation
- Annualization: sqrt(bars_per_day * trading_days_per_year) is now fully
configurable for any bar frequency and market calendar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- spread_cost: backtest now uses tick_size * contract_multiplier * fill_spread_cost_frac
(was tick_size * spread_ticks = 25x lower than training → train/eval mismatch)
- Trailing stop: remove regime-adaptive scaling from training kernel.
Both train and eval now use fixed 0.5% trail distance (vol_scale=1.0, trend_scale=1.0).
Model observes ADX/CUSUM via state features and adjusts behavior — trailing stop is
a safety net, not a strategy component.
- Branch sizes: refactored out of hardcoded 9/3/3 in gpu_backtest_evaluator.rs.
Now stored on GpuBacktestEvaluator struct, set from DqnBacktestConfig in ensure_cublas_ready.
launch_env_step and launch_metrics_and_download read from self.b0/b1/b2_size.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- apply_margin_cap: position scales DOWN as drawdown increases.
At 0% DD → 100% position, at 12.5% DD → 50%, at 20% DD → 20%.
Prevents multi-bar cascading losses that pushed max_dd to 38-51%.
- Add trading_days_per_year to DQNHyperparameters (default 252).
Configurable for crypto (365) or different markets.
- Fix trailing stop comment: value IS prev_equity (not approximation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add spread_cost as 28th param to experience_env_step kernel
- Wire from DQNHyperparameters.fill_spread_cost_frac through
ExperienceCollectorConfig to the kernel launch
- Training execute_trade() and compute_tx_cost() now use spread_cost
(was hardcoded 0.0, backtest used real spread_cost)
- All compute_tx_cost calls in training use spread_cost consistently
This was the LAST remaining train/eval transaction cost mismatch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Training kernel now has same order as backtest:
decode → trailing_stop → hold_enforcement → execute_trade → mark_to_market
Previously hold ran AFTER execute_trade and undid it by resetting
position/cash from ps[]. Now hold modifies target_position BEFORE
execute_trade, so the trade sees the correct target and no undo is needed.
This eliminates the last train/eval order-of-operations mismatch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Move trailing stop BEFORE execute_trade in training kernel (was after,
then undone). Now matches backtest: check → modify target → execute.
Eliminates the wasteful execute-then-undo pattern.
- Trailing stop uses current-bar unrealized (not raw_next forward-looking)
for train/eval consistency
- Add b2_size/b1_size guards to action re-encoding in both kernels
(prevents division by zero if branch sizes are ever 0)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- actions_history: record post-enforcement action (re-encode from final
position) instead of raw model action — fixes inflated trade counts
- Trailing stop exit cost: use compute_tx_cost() in training kernel
(was simplified inline formula) — matches backtest
- Trailing stop trade return: use portfolio-weighted formula in backtest
(position * (close - entry) / value) — matches training pattern
- Hold time: training kernel uses update_hold_time() from shared header
(was 8-line inline duplicate)
- Second comprehensive scan: zero inline duplicates, 2 acceptable
medium-severity train/eval differences remain (trailing stop uses
raw_next in training vs close in backtest — by design)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Final 3 inline duplicates replaced with shared function calls:
- execute_trade(): notional cash model (was inline cash/position update)
- check_trailing_stop(): regime-adaptive trailing stop decision
- enforce_hold(): hold enforcement decision (action aliasing kept inline)
Training kernel now uses ALL 10 shared functions from trade_physics.cuh:
decode_exposure_index, compute_target_position, decode_order_type,
execute_trade, compute_tx_cost, enforce_hold, check_trailing_stop,
check_capital_floor, apply_margin_cap, update_hold_time
Zero inline duplicates remain. Both training and backtest kernels
use identical trade physics — no more train/eval mismatches.
Smoke test: Sharpe=4.89, 443 trades, no SIGSEGV.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CRITICAL train/eval mismatch resolved: the backtest kernel used an
entry-price-reset model while training uses a notional cash model.
These produce different P&L on partial fills (L50→L100) and different
equity trajectories.
Changes:
- Add execute_trade() to trade_physics.cuh (shared notional model)
- Rewrite backtest_env_kernel.cu to use notional cash:
cash -= delta * price (not entry-price-reset)
- Mark-to-market: equity = cash + position * close (not cash + unrealized)
- Capital floor liquidation uses notional close (cash += position * price)
- entry_price kept only for trailing stop trade return computation
- Updated on new entries/reversals only (not on same-direction scaling)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>