Final 58 compile errors fixed + GPU violation analysis: - 23 files across ml, ml-dqn, ml-supervised, services - flash_attention: CudaBlas + stream fields, GPU matmul/transpose - ensemble adapters (tggn, tlob, mamba2, tft, ppo): StreamTensor/GpuLinear - diffusion/xlstm/mamba trainable: StreamTensor ↔ GpuTensor conversion - hyperopt adapters: fixed API signatures - trainers (liquid, mamba2): checkpoint save via safetensors - benchmarks: fixed to_scalar, RainbowAgent API - services: MlDevice, PPO::load_checkpoint, Mamba2SSM constructor Host-side softmax/distributional functions analyzed — operating on legitimately-downloaded small output tensors at computation endpoints. Not GPU violations (cold paths, <50 elements). FULL WORKSPACE: 0 errors, 56 warnings, 0 candle dependencies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6.2 KiB
GPU-Resident Training Pipeline Refactor
For agentic workers: REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Eliminate ALL GPU→CPU→GPU roundtrips from the ML training pipeline. Data stays GPU-resident from collection through forward/backward/optimizer. Only scalar metrics come to CPU.
Architecture: Experience batches use CudaSlice<f32> / GpuTensor fields (not Vec<f32>). model.update() accepts GPU-resident data directly. Delete all CPU-side batch conversion functions.
Tech Stack: cudarc 0.19, CudaSlice, GpuTensor, cuBLAS
Current Roundtrips (what to eliminate)
| # | Path | Data/epoch | Fix |
|---|---|---|---|
| 1 | PPO: GPU kernel → Vec<f32> (PpoExperienceBatch) → Vec<Vec<f32>> (TrajectoryBatch) → GPU re-upload |
123 MB | CudaSlice fields, delete conversion |
| 2 | DQN: GPU kernel → Vec<f32> (ExperienceBatch) — parallel GpuExperienceBatch exists but may not be primary |
1.95 GB | Unify on GpuExperienceBatch |
| 3 | PPO: GPU softmax → to_vec1() → CPU rand sampling per step |
16K syncs | GPU sampling kernel or batch |
Task 1: PPO Experience Batch GPU-Resident
Files:
- Modify:
crates/ml/src/cuda_pipeline/gpu_ppo_collector.rs - Modify:
crates/ml/src/trainers/ppo.rs - Modify:
crates/ml-ppo/src/ppo.rs
Subtask 1a: Change PpoExperienceBatch to GPU-resident
- Step 1: In
gpu_ppo_collector.rs, changePpoExperienceBatchfields:
// BEFORE:
pub struct PpoExperienceBatch {
pub states: Vec<f32>, // downloaded from GPU
pub actions: Vec<i32>,
pub log_probs: Vec<f32>,
pub advantages: Vec<f32>,
pub returns: Vec<f32>,
pub done_flags: Vec<i32>,
pub n_episodes: usize,
pub timesteps: usize,
}
// AFTER:
pub struct PpoExperienceBatch {
pub states: CudaSlice<f32>, // stays on GPU
pub actions: CudaSlice<i32>,
pub log_probs: CudaSlice<f32>,
pub advantages: CudaSlice<f32>,
pub returns: CudaSlice<f32>,
pub done_flags: CudaSlice<i32>,
pub n_episodes: usize,
pub timesteps: usize,
pub state_dim: usize,
pub stream: Arc<CudaStream>,
}
-
Step 2: In
collect_experiences(), remove ALLmemcpy_dtohcalls. Return the GPU buffers directly. -
Step 3: Add
PpoExperienceBatch::download()method for debugging/checkpoint only:
impl PpoExperienceBatch {
pub fn download_states(&self) -> Result<Vec<f32>, MLError> {
self.stream.clone_dtoh(&self.states).map_err(...)
}
}
Subtask 1b: Delete cpu_batch_to_trajectory_batch
-
Step 4: In
ppo.rs, DELETEgpu_batch_to_trajectory_batch()function entirely. -
Step 5: Change
train_gpu()to passPpoExperienceBatchdirectly to model.update():
// BEFORE:
let batch = collector.collect_experiences(...)?;
let training_batch = gpu_batch_to_trajectory_batch(&batch); // GPU→CPU conversion
let (policy_loss, value_loss) = model.update(&mut training_batch)?; // CPU→GPU re-upload
// AFTER:
let batch = collector.collect_experiences(...)?; // stays on GPU
let (policy_loss, value_loss) = model.update_gpu(&batch)?; // pure GPU
Subtask 1c: Add PPOAgent::update_gpu()
- Step 6: In
crates/ml-ppo/src/ppo.rs, addupdate_gpu()method:
pub fn update_gpu(&mut self, batch: &PpoExperienceBatch) -> Result<(f32, f32), MLError> {
// Forward pass directly on CudaSlice data — no upload needed
// Loss computation on GPU
// Backward + optimizer step on GPU
// Only readback: 2 scalar losses
}
- Step 7: Compile check:
SQLX_OFFLINE=true cargo check -p ml-ppo -p ml
Task 2: DQN Experience Batch Unification
Files:
- Modify:
crates/ml/src/cuda_pipeline/gpu_experience_collector.rs - Modify:
crates/ml/src/trainers/dqn/trainer/train_step.rs
Subtask 2a: Audit which batch type is primary
- Step 1: Search for all
ExperienceBatchvsGpuExperienceBatchusage:
rg "ExperienceBatch\b" crates/ml/ --type rust -n | grep -v "Gpu"
rg "GpuExperienceBatch" crates/ml/ --type rust -n
-
Step 2: If
ExperienceBatch(CPU) is primary in training loop:- Change training loop to use
GpuExperienceBatchinstead - Delete
ExperienceBatchstruct OR keep only for checkpoint/debugging
- Change training loop to use
-
Step 3: If
GpuExperienceBatchis already primary:- Delete
ExperienceBatchstruct - Remove
collect_experiences()CPU download path - Keep only
collect_experiences_gpu()path
- Delete
Subtask 2b: Verify fused CUDA trainer uses GPU batch
- Step 4: Check that
GpuDqnTrainer::train_step()takes CudaSlice/Tensor directly (not Vec) - Step 5: Compile check
Task 3: PPO Action Sampling on GPU
Files:
-
Modify:
crates/ml/src/trainers/ppo.rs(CPU fallback path) -
Modify:
crates/ml/src/cuda_pipeline/gpu_ppo_collector.rs(GPU path — already correct) -
Step 1: Verify GPU collection path already samples actions on-device (should be in kernel)
-
Step 2: For CPU fallback path: batch-sample N steps at once instead of per-step sync:
// BEFORE (per-step):
for step in 0..num_steps {
let probs = actor.forward(&state)?;
let probs_vec = probs.to_vec1::<f32>()?; // GPU SYNC per step
let action = sample_from_probs(&probs_vec);
}
// AFTER (batched):
let all_probs = actor.forward_batch(&all_states)?; // single forward
let all_probs_vec = all_probs.to_vec2::<f32>()?; // single download
for (step, probs) in all_probs_vec.iter().enumerate() {
let action = sample_from_probs(probs);
}
- Step 3: Compile check
Task 4: Verify Supervised Models Are Clean
- Step 1: Confirm TFT, Mamba2, Liquid, TLOB, KAN, xLSTM, Diffusion training loops only do scalar readbacks
- Step 2: No changes expected — just audit and document
Execution Strategy
Task 1 (PPO) is the biggest win (123 MB/epoch eliminated). Tasks 2-3 can follow.
Recommended: 2 agents
- Agent A: Task 1 (PPO GPU-resident batch)
- Agent B: Task 2 (DQN batch unification)
- Task 3 after both complete
- Task 4 is read-only audit
Estimated: 2-4 hours with 2 agents.