- Trade detection now counts reversals (S100→L50) as completed segments - old_pos_pnl saved before position update for correct reversal P&L - realized_pnl writeback uses old position PnL on reversal bars - 0% win rate persists — needs deeper investigation (likely tx cost interaction) WIP: The trade_return formula produces correct sign for raw market moves, but every trade still shows as a loss. Suspect tx costs on both entry AND exit of each reversal segment exceed the 1-bar price movement. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
568 lines
22 KiB
Markdown
568 lines
22 KiB
Markdown
# Remaining GPU Features — Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Wire and implement the 4 remaining unwired GPU features: multi-head attention integration, Decision Transformer CUDA kernels, ensemble training with diversity loss, and HER Future strategy with GPU episode boundaries.
|
||
|
||
**Architecture:** Each feature integrates into the existing fused CUDA training pipeline (`GpuDqnTrainer` + CUDA Graph). All kernels are GPU-native (zero CPU in hot path). Post-graph operations use the `EventTrackingGuard` RAII pattern for safe cudarc buffer access. Kernels are compiled via nvcc → cubin (cached at `/tmp/.cubin_cache/`).
|
||
|
||
**CUDA Graph Integration Strategy:** The CUDA Graph captures the full forward+loss+backward+Adam sequence. Features that modify intermediate activations (attention, ensemble heads) operate with a **1-step lag**: they modify `save_h_s2` AFTER graph replay, so the modification takes effect on the NEXT graph replay's unflatten step. This is the same pattern as the IQN trunk gradient and spectral normalization — standard in async gradient methods. The alternative (splitting the graph into trunk/head phases) is deferred until profiling shows the lag impacts convergence.
|
||
|
||
**VRAM Budget:**
|
||
| Feature | RTX 3050 (4GB) | H100 (80GB) |
|
||
|---------|---------------|-------------|
|
||
| Attention (params + activations) | ~0.5MB | ~2MB |
|
||
| Decision Transformer (params + context) | ~4MB | ~60MB |
|
||
| Ensemble (K=3 head copies) | ~1.5MB | ~6MB |
|
||
| HER episode tracking | ~0.5MB | ~4MB |
|
||
| **Total** | **~6.5MB** | **~72MB** |
|
||
|
||
**Tech Stack:** Rust (cudarc 0.19.3), CUDA C (nvcc/cubin), cuBLAS SGEMM
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
### New Files
|
||
| File | Responsibility |
|
||
|------|---------------|
|
||
| `crates/ml/src/cuda_pipeline/attention_backward_kernel.cu` | Attention backward pass: LayerNorm backward + output projection backward + attention weight gradients |
|
||
| `crates/ml/src/cuda_pipeline/dt_kernels.cu` | Decision Transformer forward: causal attention, softmax, positional encoding, token embedding, FFN, cross-entropy loss. Backward: softmax_backward, attention_backward (dQ/dK/dV), ffn_backward, layernorm_backward, embed_backward — 10 kernels total |
|
||
| `crates/ml/src/cuda_pipeline/ensemble_kernels.cu` | Ensemble: multi-head Q-aggregation, KL-divergence diversity loss, Thompson sampling action selection |
|
||
| `crates/ml/src/cuda_pipeline/her_episode_kernel.cu` | HER: episode boundary tracking, Future strategy donor sampling, episode-end detection |
|
||
|
||
### Modified Files
|
||
| File | Changes |
|
||
|------|---------|
|
||
| `crates/ml/src/cuda_pipeline/gpu_attention.rs` | Add `backward()`, `params_buf()`, Adam state; wire into `GpuDqnTrainer` |
|
||
| `crates/ml/src/cuda_pipeline/batched_forward.rs` | Add attention call between h_s2 and value/advantage heads |
|
||
| `crates/ml/src/cuda_pipeline/batched_backward.rs` | Add attention backward in the gradient chain |
|
||
| `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs` | Conditional attention in forward/backward; ensemble head management |
|
||
| `crates/ml/src/cuda_pipeline/decision_transformer.rs` | Implement `pretrain_step()` with real CUDA kernels |
|
||
| `crates/ml/src/cuda_pipeline/gpu_her.rs` | Add Final/Future strategies, episode boundary buffer |
|
||
| `crates/ml/src/cuda_pipeline/gpu_experience_collector.rs` | Add episode_ids to GpuExperienceBatch; fill via CUDA kernel (not CPU) |
|
||
| `crates/ml/src/trainers/dqn/fused_training.rs` | Wire attention, ensemble, HER strategies |
|
||
| `crates/ml/src/trainers/dqn/config.rs` | Add `use_attention: bool` to `DQNHyperparameters` (field exists in `DQNParams` hyperopt struct but not in the training config); wire from `DQNParams` during hyperopt conversion |
|
||
| `crates/ml/src/trainers/dqn/trainer/training_loop.rs` | Ensemble head rotation, DT pre-training phase, HER episode tracking |
|
||
| `crates/ml/src/hyperopt/adapters/dqn.rs` | Add DT pre-training epochs, ensemble params to search space |
|
||
|
||
---
|
||
|
||
## Task 1: Attention Forward Pass Integration
|
||
|
||
**Files:**
|
||
- Modify: `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs`
|
||
- Modify: `crates/ml/src/cuda_pipeline/gpu_attention.rs`
|
||
- Modify: `crates/ml/src/trainers/dqn/fused_training.rs`
|
||
|
||
- [ ] **Step 1: Add `GpuAttention` as optional field in `FusedTrainingCtx`**
|
||
|
||
In `fused_training.rs`, add to `FusedTrainingCtx`:
|
||
```rust
|
||
pub(crate) gpu_attention: Option<GpuAttention>,
|
||
```
|
||
|
||
Initialize it in `new()` when `hyperparams.use_attention` is true (similar to `gpu_iqn` initialization pattern at line 233).
|
||
|
||
- [ ] **Step 2: Wire attention forward BETWEEN trunk h_s2 and value/advantage heads**
|
||
|
||
In `gpu_dqn_trainer.rs`, add a method:
|
||
```rust
|
||
pub fn apply_attention_forward(
|
||
&self,
|
||
attention: &mut GpuAttention,
|
||
h_s2: &CudaSlice<f32>,
|
||
batch_size: usize,
|
||
) -> Result<(), MLError>
|
||
```
|
||
|
||
This copies `h_s2` into attention input, runs `attention.forward()`, then copies the attended output BACK into `save_h_s2` (in-place replacement). The CUDA Graph captures the trunk forward FIRST, then attention runs OUTSIDE the graph (same pattern as IQN/spectral norm), then value/advantage heads run.
|
||
|
||
- [ ] **Step 3: Call attention in `run_full_step()` after CUDA Graph replay**
|
||
|
||
After Step 2 (graph replay) and before Step 5 (IQN):
|
||
```rust
|
||
if let Some(ref mut attn) = self.gpu_attention {
|
||
let _evt_guard = EventTrackingGuard::new(self.stream.context());
|
||
self.trainer.apply_attention_forward(attn, ...)?;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: Compile check**
|
||
|
||
```bash
|
||
SQLX_OFFLINE=true cargo check -p ml 2>&1 | tail -5
|
||
```
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add crates/ml/src/cuda_pipeline/gpu_attention.rs crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs crates/ml/src/trainers/dqn/fused_training.rs
|
||
git commit -m "feat: wire attention forward pass into DQN training (post-graph, frozen weights)"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: Attention Backward Pass Kernel
|
||
|
||
**Files:**
|
||
- Create: `crates/ml/src/cuda_pipeline/attention_backward_kernel.cu`
|
||
- Modify: `crates/ml/src/cuda_pipeline/gpu_attention.rs`
|
||
|
||
- [ ] **Step 1: Write the attention backward CUDA kernel**
|
||
|
||
The kernel computes `dL/d(h_s2_input)` given `dL/d(h_s2_attended)`:
|
||
|
||
```cuda
|
||
extern "C" __global__ void attention_backward_kernel(
|
||
const float* d_output, // [B, D] gradient from downstream
|
||
const float* states_input, // [B, D] saved input to attention
|
||
const float* params, // attention weights
|
||
float* d_input, // [B, D] gradient to upstream (trunk)
|
||
float* d_params, // [total_params] weight gradients (atomicAdd)
|
||
int B
|
||
);
|
||
```
|
||
|
||
The backward flows through:
|
||
1. LayerNorm backward → d_prenorm
|
||
2. Residual: d_prenorm splits into d_projection + d_residual
|
||
3. Output projection backward: d_projection → d_concat, dW_O, db_O
|
||
4. Multi-head attention backward → dQ, dK, dV per head
|
||
5. Q/K/V projection backward → d_input, dW_Q, dW_K, dW_V
|
||
|
||
Since the kernel is complex (matching the 237-line forward kernel), implement in phases:
|
||
- Phase A: Residual passthrough only (d_input = d_output) — gradient flows, weights frozen
|
||
- Phase B: Full backward with weight gradients (unfreezes attention weights)
|
||
|
||
Start with Phase A (trivial kernel, verifiable).
|
||
|
||
- [ ] **Step 2: Add `backward()` method to `GpuAttention`**
|
||
|
||
```rust
|
||
pub fn backward(
|
||
&self,
|
||
d_output: &CudaSlice<f32>,
|
||
d_input: &mut CudaSlice<f32>,
|
||
batch_size: usize,
|
||
) -> Result<(), MLError>
|
||
```
|
||
|
||
For Phase A: simply `memcpy_dtod(d_input, d_output)` — residual gradient passthrough.
|
||
|
||
- [ ] **Step 3: Wire backward into `apply_iqn_trunk_gradient`**
|
||
|
||
After attention forward rewrites `save_h_s2`, the IQN trunk gradient flows through the attention backward before reaching the trunk layers:
|
||
```rust
|
||
// Before trunk backward: d_h_s2 → attention_backward → d_h_s2_input
|
||
if let Some(ref attn) = self.gpu_attention {
|
||
attn.backward(&bw_d_h_s2, &mut bw_d_h_s2, batch_size)?;
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 4: Compile check + local test**
|
||
|
||
```bash
|
||
SQLX_OFFLINE=true cargo check --workspace 2>&1 | tail -5
|
||
```
|
||
|
||
- [ ] **Step 5: Commit**
|
||
|
||
```bash
|
||
git add crates/ml/src/cuda_pipeline/attention_backward_kernel.cu crates/ml/src/cuda_pipeline/gpu_attention.rs
|
||
git commit -m "feat: attention backward (Phase A — residual passthrough, gradient flow verified)"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: Decision Transformer CUDA Kernels
|
||
|
||
**Files:**
|
||
- Create: `crates/ml/src/cuda_pipeline/dt_kernels.cu`
|
||
- Modify: `crates/ml/src/cuda_pipeline/decision_transformer.rs`
|
||
|
||
- [ ] **Step 1: Write the token embedding + positional encoding kernel**
|
||
|
||
```cuda
|
||
extern "C" __global__ void dt_embed_kernel(
|
||
const float* trajectories, // [B, T, state_dim+2] (return-to-go, state, action)
|
||
const float* W_embed, // [(state_dim+2), embed_dim]
|
||
const float* b_embed, // [embed_dim]
|
||
const float* pos_embed, // [T, embed_dim] (learned positional encoding)
|
||
float* output, // [B, T, embed_dim]
|
||
int B, int T, int input_dim, int embed_dim
|
||
);
|
||
```
|
||
|
||
Each thread handles one (batch, timestep, embed_dim_idx) tuple:
|
||
- Linear projection: `out[b][t][d] = sum_j(traj[b][t][j] * W[j][d]) + b[d]`
|
||
- Add positional: `out[b][t][d] += pos[t][d]`
|
||
|
||
- [ ] **Step 2: Write the causal self-attention kernel**
|
||
|
||
```cuda
|
||
extern "C" __global__ void dt_causal_attention_kernel(
|
||
const float* Q, // [B, T, embed_dim]
|
||
const float* K, // [B, T, embed_dim]
|
||
const float* V, // [B, T, embed_dim]
|
||
float* output, // [B, T, embed_dim]
|
||
int B, int T, int D, int num_heads
|
||
);
|
||
```
|
||
|
||
Per-head computation with causal mask:
|
||
- `attn[i][j] = Q[i] · K[j] / sqrt(D_h)` for j ≤ i (causal), -inf otherwise
|
||
- Softmax over j dimension
|
||
- `output[i] = sum_j(attn[i][j] * V[j])`
|
||
|
||
Block-per-sample, threads handle different timesteps. Shared memory for K/V caching.
|
||
|
||
- [ ] **Step 3: Write the FFN kernel (embed_dim → 4×embed_dim → embed_dim)**
|
||
|
||
```cuda
|
||
extern "C" __global__ void dt_ffn_kernel(
|
||
const float* input, // [B, T, embed_dim]
|
||
const float* W1, // [embed_dim, 4*embed_dim]
|
||
const float* b1, // [4*embed_dim]
|
||
const float* W2, // [4*embed_dim, embed_dim]
|
||
const float* b2, // [embed_dim]
|
||
float* output, // [B, T, embed_dim]
|
||
int B, int T, int D
|
||
);
|
||
```
|
||
|
||
GELU activation between layers. Residual connection: `output += input`.
|
||
|
||
- [ ] **Step 4: Write the cross-entropy loss kernel**
|
||
|
||
```cuda
|
||
extern "C" __global__ void dt_cross_entropy_kernel(
|
||
const float* logits, // [B, T, num_actions]
|
||
const int* target_actions, // [B, T]
|
||
float* per_sample_loss, // [B*T]
|
||
float* total_loss, // [1] atomicAdd
|
||
int B, int T, int num_actions
|
||
);
|
||
```
|
||
|
||
Per-token cross-entropy: `loss = -log(softmax(logits)[target_action])`.
|
||
|
||
- [ ] **Step 5: Implement `pretrain_step()` in decision_transformer.rs**
|
||
|
||
Wire the 5 forward kernels into a training loop. Then:
|
||
|
||
- [ ] **Step 5b: Write 5 backward CUDA kernels**
|
||
|
||
```cuda
|
||
// In dt_kernels.cu — add after forward kernels:
|
||
// dt_cross_entropy_backward_kernel: dL/d_logits = softmax - one_hot
|
||
// dt_ffn_backward_kernel: GELU backward + 2 linear backward
|
||
// dt_attention_backward_kernel: dQ,dK,dV from d_output (softmax Jacobian + causal mask)
|
||
// dt_layernorm_backward_kernel: chain through mean/variance
|
||
// dt_embed_backward_kernel: dW_embed from d_embedded
|
||
```
|
||
|
||
Each mirrors its forward kernel's thread/block layout. Total: 10 kernels.
|
||
|
||
- [ ] **Step 5c: Implement `pretrain_step()` in decision_transformer.rs**
|
||
|
||
1. **Forward:** Embed → {QKV → CausalAttn → LN → FFN → LN} × N → ActionHead → CE loss
|
||
2. **Backward:** CE backward → reverse chain through all N layers → embed backward
|
||
3. **Optimizer:** Reuse `dqn_grad_norm_kernel` + `dqn_adam_update_kernel` on DT params
|
||
|
||
DT action space = 9 exposure actions (branch_0 only, not full 81 factored).
|
||
|
||
- [ ] **Step 6: Compile check**
|
||
|
||
```bash
|
||
SQLX_OFFLINE=true cargo check -p ml 2>&1 | tail -5
|
||
```
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
git add crates/ml/src/cuda_pipeline/dt_kernels.cu crates/ml/src/cuda_pipeline/decision_transformer.rs
|
||
git commit -m "feat: Decision Transformer CUDA kernels (embed, causal attention, FFN, CE loss)"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 4: Decision Transformer Pre-Training Loop
|
||
|
||
**Files:**
|
||
- Modify: `crates/ml/src/trainers/dqn/trainer/training_loop.rs`
|
||
- Modify: `crates/ml/src/trainers/dqn/config.rs`
|
||
- Modify: `crates/ml/src/hyperopt/adapters/dqn.rs`
|
||
|
||
- [ ] **Step 1: Add DT pre-training config fields**
|
||
|
||
In `DQNHyperparameters`:
|
||
```rust
|
||
pub dt_pretrain_epochs: usize, // 0 = disabled, 10-50 typical
|
||
pub dt_context_len: usize, // 20 timesteps
|
||
pub dt_embed_dim: usize, // 128
|
||
pub dt_num_layers: usize, // 3
|
||
pub dt_target_return: f64, // 2.0 (target Sharpe-like return)
|
||
```
|
||
|
||
- [ ] **Step 2: Add DT pre-training phase at training start**
|
||
|
||
In `training_loop.rs`, before the main training loop:
|
||
```rust
|
||
if self.hyperparams.dt_pretrain_epochs > 0 {
|
||
let dt = DecisionTransformer::new(stream, dt_config)?;
|
||
for epoch in 0..self.hyperparams.dt_pretrain_epochs {
|
||
// Build trajectory batches from training_data
|
||
// Run dt.pretrain_step(trajectories, target_actions, batch_size)
|
||
// Log DT loss
|
||
}
|
||
// Transfer DT's learned representations to DQN trunk (optional)
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 3: Add DT params to hyperopt search space**
|
||
|
||
In `dqn.rs`, add bounds for `dt_pretrain_epochs`, `dt_context_len`, `dt_embed_dim`.
|
||
|
||
- [ ] **Step 4: Compile check + commit**
|
||
|
||
```bash
|
||
SQLX_OFFLINE=true cargo check --workspace
|
||
git add crates/ml/src/cuda_pipeline/ crates/ml/src/trainers/dqn/ crates/ml/src/hyperopt/ && git commit -m "feat: Decision Transformer pre-training phase in training loop"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 5: Ensemble Multi-Head Q-Network
|
||
|
||
**Files:**
|
||
- Create: `crates/ml/src/cuda_pipeline/ensemble_kernels.cu`
|
||
- Modify: `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs`
|
||
- Modify: `crates/ml/src/trainers/dqn/fused_training.rs`
|
||
- Modify: `crates/ml/src/trainers/dqn/config.rs`
|
||
|
||
- [ ] **Step 1: Write the ensemble aggregation kernel**
|
||
|
||
```cuda
|
||
extern "C" __global__ void ensemble_aggregate_kernel(
|
||
const float* head_q_values, // [K, B, num_actions] Q-values from K heads
|
||
float* mean_q, // [B, num_actions] mean across heads
|
||
float* var_q, // [B, num_actions] variance across heads
|
||
int K, int B, int num_actions
|
||
);
|
||
```
|
||
|
||
Computes mean and variance of Q-values across K ensemble heads for uncertainty estimation.
|
||
|
||
- [ ] **Step 2: Write the KL-divergence diversity loss kernel**
|
||
|
||
```cuda
|
||
extern "C" __global__ void ensemble_diversity_kernel(
|
||
const float* head_logits, // [K, B, num_atoms] per-head C51 logits
|
||
float* diversity_loss, // [1] total KL divergence (atomicAdd)
|
||
int K, int B, int num_atoms
|
||
);
|
||
```
|
||
|
||
For each pair of heads (i, j): `D_KL(p_i || p_j)` averaged over the batch. Uses hierarchical reduction (warp reduce → block reduce → atomicAdd per block, same pattern as `dqn_grad_norm_kernel`) to avoid single-address atomicAdd serialization. The diversity loss ENCOURAGES disagreement (subtracted from total loss).
|
||
|
||
- [ ] **Step 3: Implement multi-head architecture in GpuDqnTrainer**
|
||
|
||
The ensemble shares the TRUNK (W_s1, W_s2) but has K SEPARATE value + advantage heads:
|
||
```rust
|
||
pub struct EnsembleHeads {
|
||
heads: Vec<(DuelingWeightSet, BranchingWeightSet)>, // K head weight sets
|
||
}
|
||
```
|
||
|
||
During training (heads run OUTSIDE the CUDA Graph, same pattern as IQN/attention):
|
||
1. CUDA Graph: forward through shared trunk → h_s2 (using head 0's weights in the graph)
|
||
2. Post-graph: for each head k=1..K-1, run cuBLAS forward through head_k's value/advantage layers on `save_h_s2` (outside graph, with EventTrackingGuard)
|
||
3. Aggregate Q-values: mean ± std across K heads
|
||
4. C51 loss per head (head 0 inside graph, heads 1..K-1 outside)
|
||
5. Diversity regularization across heads
|
||
6. Backward: each head contributes trunk gradients via the existing IQN-style SGD/Adam correction
|
||
|
||
Note: The CUDA Graph remains captured for head 0 only. Heads 1..K-1 run as post-graph operations with their own cuBLAS forwards. This avoids re-capturing the graph for each head.
|
||
|
||
- [ ] **Step 4: Add ensemble config and wiring**
|
||
|
||
In `DQNHyperparameters`:
|
||
```rust
|
||
pub ensemble_count: usize, // K heads (default 1 = no ensemble)
|
||
pub ensemble_diversity_weight: f64, // 0.01 = mild diversity encouragement
|
||
```
|
||
|
||
In `FusedTrainingCtx::run_full_step()`:
|
||
- If `ensemble_count > 1`: run K forward passes through different heads, average for action selection, sum losses with diversity term.
|
||
|
||
- [ ] **Step 5: Compile check + commit**
|
||
|
||
```bash
|
||
SQLX_OFFLINE=true cargo check --workspace
|
||
git add crates/ml/src/cuda_pipeline/ crates/ml/src/trainers/dqn/ crates/ml/src/hyperopt/ && git commit -m "feat: ensemble multi-head Q-network with KL diversity loss"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 6: HER Episode Boundary Tracking
|
||
|
||
**Files:**
|
||
- Create: `crates/ml/src/cuda_pipeline/her_episode_kernel.cu`
|
||
- Modify: `crates/ml/src/cuda_pipeline/gpu_her.rs`
|
||
- Modify: `crates/ml/src/cuda_pipeline/gpu_experience_collector.rs`
|
||
|
||
- [ ] **Step 1: Add episode index buffer to experience output**
|
||
|
||
In `GpuExperienceBatch`, add:
|
||
```rust
|
||
pub episode_ids: CudaSlice<i32>, // [N * L] episode index per transition
|
||
```
|
||
|
||
Fill via a simple CUDA kernel `fill_episode_ids_kernel(int* ids, int L, int N)` where thread `i` writes `ids[i] = i / L`. Launched as `grid=(ceil(N*L/256)), block=256`. Zero CPU fill — pure GPU.
|
||
|
||
- [ ] **Step 2: Track episode boundaries in GPU replay buffer**
|
||
|
||
When inserting transitions, also store the episode ID:
|
||
```rust
|
||
pub episode_id_buf: CudaSlice<i32>, // [capacity] episode index per slot
|
||
```
|
||
|
||
This allows the HER kernel to find other transitions from the same episode.
|
||
|
||
- [ ] **Step 3: Write the episode-aware donor sampling kernel**
|
||
|
||
```cuda
|
||
extern "C" __global__ void her_sample_future_donors(
|
||
const int* episode_ids, // [capacity] episode index per slot
|
||
const int* source_indices, // [her_batch_size] which to relabel
|
||
int* donor_indices, // [her_batch_size] output: future donors
|
||
unsigned int* rng_states, // [her_batch_size] per-sample RNG
|
||
int capacity,
|
||
int her_batch_size
|
||
);
|
||
```
|
||
|
||
Uses a GPU-resident `episode_boundary_offsets` auxiliary buffer (updated at insertion time) for O(log(N)) binary search instead of O(capacity) linear scan. Each thread binary-searches the boundary buffer to find its episode's [start, end) range, then uniformly samples from `[source+1, end)`.
|
||
|
||
- [ ] **Step 4: Write the episode-end detection kernel (Final strategy)**
|
||
|
||
```cuda
|
||
extern "C" __global__ void her_find_episode_end(
|
||
const int* episode_ids, // [capacity]
|
||
const int* source_indices, // [her_batch_size]
|
||
int* end_indices, // [her_batch_size] output: last index in same episode
|
||
int capacity,
|
||
int her_batch_size
|
||
);
|
||
```
|
||
|
||
For each source: binary search or linear scan to find the last index with the same episode_id.
|
||
|
||
- [ ] **Step 5: Wire Future/Final strategies in `GpuHer`**
|
||
|
||
```rust
|
||
pub fn relabel_batch_gpu(
|
||
&mut self,
|
||
strategy: HerGpuStrategy,
|
||
episode_ids: &CudaSlice<i32>,
|
||
...
|
||
) -> Result<&HerBatch, MLError> {
|
||
let donors = match strategy {
|
||
HerGpuStrategy::Random => self.random_donors(her_batch_size),
|
||
HerGpuStrategy::Future => self.sample_future_donors(episode_ids, source_indices)?,
|
||
HerGpuStrategy::Final => self.find_episode_ends(episode_ids, source_indices)?,
|
||
};
|
||
self.relabel_kernel(source_indices, donors, ...)?;
|
||
Ok(&self.output)
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 6: Compile check + commit**
|
||
|
||
```bash
|
||
SQLX_OFFLINE=true cargo check --workspace
|
||
git add crates/ml/src/cuda_pipeline/ crates/ml/src/trainers/dqn/ crates/ml/src/hyperopt/ && git commit -m "feat: HER Future/Final strategies with GPU episode boundary tracking"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 7: Integration Test — Full Pipeline
|
||
|
||
**Files:**
|
||
- Modify: `crates/ml/src/trainers/dqn/trainer/training_loop.rs`
|
||
|
||
- [ ] **Step 1: Wire all 4 features into the training loop conditionally**
|
||
|
||
```rust
|
||
// Attention: after trunk forward, before heads
|
||
if hyperparams.use_attention { ... }
|
||
|
||
// DT pre-training: before main training loop
|
||
if hyperparams.dt_pretrain_epochs > 0 { ... }
|
||
|
||
// Ensemble: K heads with diversity loss
|
||
if hyperparams.ensemble_count > 1 { ... }
|
||
|
||
// HER: Future strategy with episode boundaries
|
||
if hyperparams.her_ratio > 0.0 && hyperparams.her_strategy == "future" { ... }
|
||
```
|
||
|
||
- [ ] **Step 2: Local test — all features enabled**
|
||
|
||
```bash
|
||
rm -rf /tmp/.cubin_cache/
|
||
SQLX_OFFLINE=true cargo build --release -p ml --example hyperopt_baseline_rl
|
||
SQLX_OFFLINE=true timeout 120 ./target/release/examples/hyperopt_baseline_rl \
|
||
--model dqn --phase fast --trials 1 --n-initial 1 --epochs 3 \
|
||
--data-dir test_data/futures-baseline --symbol ES.FUT \
|
||
--output /tmp/full_pipeline_test.json --seed 42
|
||
```
|
||
|
||
Expected: No NaN, no CUDA errors, train_loss < 50.
|
||
|
||
- [ ] **Step 3: Commit**
|
||
|
||
```bash
|
||
git add crates/ml/src/cuda_pipeline/ crates/ml/src/trainers/dqn/ crates/ml/src/hyperopt/ && git commit -m "feat: full pipeline integration — attention + DT + ensemble + HER Future"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 8: H100 Submission
|
||
|
||
- [ ] **Step 1: Push to main**
|
||
|
||
```bash
|
||
git push origin main
|
||
```
|
||
|
||
- [ ] **Step 2: Submit H100 hyperopt run**
|
||
|
||
50 epochs, 20 trials, with all features enabled via config:
|
||
```toml
|
||
use_attention = true
|
||
dt_pretrain_epochs = 10
|
||
ensemble_count = 3
|
||
her_ratio = 0.3
|
||
her_strategy = "future"
|
||
```
|
||
|
||
- [ ] **Step 3: Monitor first trial**
|
||
|
||
Check: no NaN, no CUDA OOM, loss decreasing, Q-gap > 0.
|
||
|
||
---
|
||
|
||
## Dependency Graph
|
||
|
||
```
|
||
Task 1 (Attention Forward) → Task 2 (Attention Backward) → Task 7 (Integration)
|
||
Task 3 (DT Kernels) → Task 4 (DT Loop) → Task 7 (Integration)
|
||
Task 5 (Ensemble) → Task 7 (Integration)
|
||
Task 6 (HER Episodes) → Task 7 (Integration)
|
||
Task 7 (Integration) → Task 8 (H100)
|
||
```
|
||
|
||
Tasks 1-2, 3-4, 5, and 6 are INDEPENDENT and can be implemented in parallel.
|