feat: diffusion Q-refinement — 3-step denoiser conditioned on Var[Q] (2,700 params)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -393,11 +393,11 @@ pub(crate) fn compute_param_sizes(cfg: &GpuDqnTrainConfig) -> [usize; NUM_WEIGHT
|
||||
cfg.adv_h, // [13] b_b1fc
|
||||
cfg.branch_1_size * cfg.num_atoms * cfg.adv_h, // [14] w_b1out
|
||||
cfg.branch_1_size * cfg.num_atoms, // [15] b_b1out
|
||||
cfg.adv_h * cfg.shared_h2, // [16] w_b2fc
|
||||
cfg.adv_h * (cfg.shared_h2 + 3), // [16] w_b2fc (OFI-conditioned)
|
||||
cfg.adv_h, // [17] b_b2fc
|
||||
cfg.branch_2_size * cfg.num_atoms * cfg.adv_h, // [18] w_b2out
|
||||
cfg.branch_2_size * cfg.num_atoms, // [19] b_b2out
|
||||
cfg.adv_h * cfg.shared_h2, // [20] w_b3fc
|
||||
cfg.adv_h * (cfg.shared_h2 + 3), // [20] w_b3fc (OFI-conditioned)
|
||||
cfg.adv_h, // [21] b_b3fc
|
||||
cfg.branch_3_size * cfg.num_atoms * cfg.adv_h, // [22] w_b3out
|
||||
cfg.branch_3_size * cfg.num_atoms, // [23] b_b3out
|
||||
@@ -418,9 +418,9 @@ pub(crate) fn compute_param_sizes(cfg: &GpuDqnTrainConfig) -> [usize; NUM_WEIGHT
|
||||
cfg.adv_h, // [35] b_gate_0
|
||||
cfg.adv_h * (cfg.shared_h2 + 3), // [36] w_gate_1 [AH, SH2+3]
|
||||
cfg.adv_h, // [37] b_gate_1
|
||||
cfg.adv_h * cfg.shared_h2, // [38] w_gate_2
|
||||
cfg.adv_h * (cfg.shared_h2 + 3), // [38] w_gate_2 (OFI-conditioned)
|
||||
cfg.adv_h, // [39] b_gate_2
|
||||
cfg.adv_h * cfg.shared_h2, // [40] w_gate_3
|
||||
cfg.adv_h * (cfg.shared_h2 + 3), // [40] w_gate_3 (OFI-conditioned)
|
||||
cfg.adv_h, // [41] b_gate_3
|
||||
// ── KAN spline coefficients (8 bases per neuron) + residual weight ──
|
||||
cfg.adv_h * 8, // [42] kan_coeff_0 [AH, 8]
|
||||
@@ -488,6 +488,8 @@ struct CachedPtrs {
|
||||
save_h_b3: u64,
|
||||
mag_concat_buf: u64,
|
||||
d_mag_concat_buf: u64,
|
||||
pub(crate) ord_concat_buf: u64,
|
||||
pub(crate) urg_concat_buf: u64,
|
||||
bw_d_h_s1: u64,
|
||||
bw_d_h_s2: u64,
|
||||
bw_d_h_v: u64,
|
||||
@@ -1043,6 +1045,24 @@ pub struct GpuDqnTrainer {
|
||||
graph_adam_step: i32,
|
||||
/// branch_graph_message_pass kernel handle.
|
||||
graph_msg_kernel: CudaFunction,
|
||||
|
||||
// ── Diffusion Q-refinement (3-step denoiser conditioned on Var[Q]) ─
|
||||
/// Flat params for 3 denoising steps: 3 × (W1[24,24]+b1[24]+W2[12,24]+b2[12]) = 3 × 900 = 2700.
|
||||
denoise_params: CudaSlice<f32>,
|
||||
/// Adam first moment for denoise_params [2700].
|
||||
denoise_adam_m: CudaSlice<f32>,
|
||||
/// Adam second moment for denoise_params [2700].
|
||||
denoise_adam_v: CudaSlice<f32>,
|
||||
/// Adam step counter for denoise optimizer.
|
||||
denoise_adam_step: i32,
|
||||
/// Ping-pong buffer A for iterative denoising [B, 12].
|
||||
denoise_buf_a: CudaSlice<f32>,
|
||||
/// Ping-pong buffer B for iterative denoising [B, 12].
|
||||
denoise_buf_b: CudaSlice<f32>,
|
||||
/// Var[Q] buffer for training path [B, 12] — populated by compute_expected_q.
|
||||
q_var_buf_trainer: CudaSlice<f32>,
|
||||
/// q_denoise_step kernel handle.
|
||||
q_denoise_kernel: CudaFunction,
|
||||
}
|
||||
|
||||
impl GpuDqnTrainer {
|
||||
@@ -1361,6 +1381,71 @@ impl GpuDqnTrainer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run 3-step diffusion denoising on q_coord_buf [batch_size, 12] conditioned on Var[Q].
|
||||
///
|
||||
/// Each step: input = [Q_prev; sqrt(Var[Q]) * schedule] → FC(24→24)-SiLU-FC(24→12) → Q_next.
|
||||
/// Ping-pong: a→b (k=1), b→a (k=2), a→b (k=3). Result (in buf_b) copied back to q_coord_buf.
|
||||
/// Must be called AFTER launch_graph_message_pass. Reads q_var_buf_trainer (populated by
|
||||
/// compute_expected_q with the training path's variance output).
|
||||
pub(crate) fn launch_q_denoise(&self, batch_size: usize) -> Result<(), MLError> {
|
||||
let b = batch_size as i32;
|
||||
let blocks = ((batch_size as u32 + 255) / 256).max(1);
|
||||
let copy_bytes = batch_size * 12 * std::mem::size_of::<f32>();
|
||||
let q_coord_ptr = self.q_coord_buf.raw_ptr();
|
||||
let buf_a = self.denoise_buf_a.raw_ptr();
|
||||
let buf_b = self.denoise_buf_b.raw_ptr();
|
||||
let var_ptr = self.q_var_buf_trainer.raw_ptr();
|
||||
let params_ptr = self.denoise_params.raw_ptr();
|
||||
|
||||
// Copy q_coord_buf → denoise_buf_a as the denoiser starting point.
|
||||
unsafe {
|
||||
cudarc::driver::result::memcpy_dtod_async(buf_a, q_coord_ptr, copy_bytes, self.stream.cu_stream())
|
||||
.map_err(|e| MLError::ModelError(format!("denoise init copy: {e}")))?;
|
||||
}
|
||||
|
||||
// Layout per step: W1[24,24]=576 + b1[24]=24 + W2[12,24]=288 + b2[12]=12 = 900 floats.
|
||||
const STEP_PARAMS: usize = 900;
|
||||
// 3 ping-pong steps: k=1(a→b), k=2(b→a), k=3(a→b). After step 3, result is in buf_b.
|
||||
for k in 0..3_i32 {
|
||||
let (in_ptr, out_ptr) = if k % 2 == 0 { (buf_a, buf_b) } else { (buf_b, buf_a) };
|
||||
let step_off = k as usize * STEP_PARAMS;
|
||||
let w1_ptr = params_ptr + (step_off * std::mem::size_of::<f32>()) as u64;
|
||||
let b1_ptr = w1_ptr + (576 * std::mem::size_of::<f32>()) as u64;
|
||||
let w2_ptr = b1_ptr + (24 * std::mem::size_of::<f32>()) as u64;
|
||||
let b2_ptr = w2_ptr + (288 * std::mem::size_of::<f32>()) as u64;
|
||||
let step_k = k + 1_i32;
|
||||
let total_steps = 3_i32;
|
||||
unsafe {
|
||||
self.stream
|
||||
.launch_builder(&self.q_denoise_kernel)
|
||||
.arg(&in_ptr)
|
||||
.arg(&out_ptr)
|
||||
.arg(&var_ptr)
|
||||
.arg(&w1_ptr)
|
||||
.arg(&b1_ptr)
|
||||
.arg(&w2_ptr)
|
||||
.arg(&b2_ptr)
|
||||
.arg(&b)
|
||||
.arg(&step_k)
|
||||
.arg(&total_steps)
|
||||
.launch(LaunchConfig {
|
||||
grid_dim: (blocks, 1, 1),
|
||||
block_dim: (256, 1, 1),
|
||||
shared_mem_bytes: 0,
|
||||
})
|
||||
.map_err(|e| MLError::ModelError(format!("q_denoise_step k={}: {e}", k + 1)))?;
|
||||
}
|
||||
}
|
||||
|
||||
// After 3 steps (k=0,1,2), the last output is buf_b. Copy buf_b → q_coord_buf.
|
||||
unsafe {
|
||||
cudarc::driver::result::memcpy_dtod_async(q_coord_ptr, buf_b, copy_bytes, self.stream.cu_stream())
|
||||
.map_err(|e| MLError::ModelError(format!("denoise final copy: {e}")))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Center Q-values in q_out_buf by subtracting their global mean.
|
||||
///
|
||||
/// Two-phase GPU operation (no CPU roundtrip):
|
||||
@@ -3455,6 +3540,54 @@ impl GpuDqnTrainer {
|
||||
super::htod_f32(&stream, &graph_params_host, &mut graph_params)?;
|
||||
info!("GpuDqnTrainer: branch_graph_message_pass kernel loaded (60 params, near-identity W_msg init)");
|
||||
|
||||
// ── Diffusion Q-refinement ────────────────────────────────────────────────
|
||||
// 3 steps × (W1[24,24]=576 + b1[24]=24 + W2[12,24]=288 + b2[12]=12) = 3 × 900 = 2700 floats.
|
||||
const DENOISE_PARAMS_PER_STEP: usize = 576 + 24 + 288 + 12; // 900
|
||||
const DENOISE_TOTAL_PARAMS: usize = 3 * DENOISE_PARAMS_PER_STEP; // 2700
|
||||
let denoise_adam_m = stream.alloc_zeros::<f32>(DENOISE_TOTAL_PARAMS)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc denoise_adam_m: {e}")))?;
|
||||
let denoise_adam_v = stream.alloc_zeros::<f32>(DENOISE_TOTAL_PARAMS)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc denoise_adam_v: {e}")))?;
|
||||
let denoise_buf_a = alloc_f32(&stream, b * 12, "denoise_buf_a")?;
|
||||
let denoise_buf_b = alloc_f32(&stream, b * 12, "denoise_buf_b")?;
|
||||
let q_var_buf_trainer = alloc_f32(&stream, b * 12, "q_var_buf_trainer")?;
|
||||
// Xavier uniform init for W1 and W2; biases stay zero.
|
||||
let mut denoise_params_host = vec![0.0_f32; DENOISE_TOTAL_PARAMS];
|
||||
{
|
||||
let mut rng = 0xDEAD_C0DE_u64.wrapping_add(DENOISE_TOTAL_PARAMS as u64);
|
||||
let mut off = 0usize;
|
||||
for _step in 0..3 {
|
||||
// W1 [24, 24]: fan_in=24, fan_out=24 → limit = sqrt(6/48)
|
||||
let w1_limit = (6.0_f64 / (24 + 24) as f64).sqrt() as f32;
|
||||
for w in &mut denoise_params_host[off..off + 576] {
|
||||
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
||||
let u = (rng >> 33) as f32 / (1u64 << 31) as f32 - 0.5;
|
||||
*w = u * 2.0 * w1_limit;
|
||||
}
|
||||
off += 576;
|
||||
// b1 [24]: stays zero
|
||||
off += 24;
|
||||
// W2 [12, 24]: fan_in=24, fan_out=12 → limit = sqrt(6/36)
|
||||
let w2_limit = (6.0_f64 / (12 + 24) as f64).sqrt() as f32;
|
||||
for w in &mut denoise_params_host[off..off + 288] {
|
||||
rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
||||
let u = (rng >> 33) as f32 / (1u64 << 31) as f32 - 0.5;
|
||||
*w = u * 2.0 * w2_limit;
|
||||
}
|
||||
off += 288;
|
||||
// b2 [12]: stays zero
|
||||
off += 12;
|
||||
}
|
||||
}
|
||||
let mut denoise_params = stream.alloc_zeros::<f32>(DENOISE_TOTAL_PARAMS)
|
||||
.map_err(|e| MLError::ModelError(format!("alloc denoise_params: {e}")))?;
|
||||
super::htod_f32(&stream, &denoise_params_host, &mut denoise_params)?;
|
||||
let cpbi_module_denoise = stream.context().load_cubin(EXPECTED_Q_CUBIN.to_vec())
|
||||
.map_err(|e| MLError::ModelError(format!("cpbi cubin (denoise): {e}")))?;
|
||||
let q_denoise_kernel = cpbi_module_denoise.load_function("q_denoise_step")
|
||||
.map_err(|e| MLError::ModelError(format!("q_denoise_step load: {e}")))?;
|
||||
info!("GpuDqnTrainer: q_denoise_step kernel loaded (2700 params, 3-step diffusion denoiser)");
|
||||
|
||||
// v8: Pessimistic Q-value initialization — shift value head bias to -0.1
|
||||
// Pessimistic Q-init REMOVED — incompatible with per-sample support.
|
||||
// Xavier init gives near-zero value head output, correct for adaptive C51.
|
||||
@@ -3727,6 +3860,14 @@ impl GpuDqnTrainer {
|
||||
graph_adam_v,
|
||||
graph_adam_step: 0,
|
||||
graph_msg_kernel,
|
||||
denoise_params,
|
||||
denoise_adam_m,
|
||||
denoise_adam_v,
|
||||
denoise_adam_step: 0,
|
||||
denoise_buf_a,
|
||||
denoise_buf_b,
|
||||
q_var_buf_trainer,
|
||||
q_denoise_kernel,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4944,8 +5085,8 @@ impl GpuDqnTrainer {
|
||||
.map_err(|e| MLError::ModelError(format!("atom_stats_buf memset: {e}")))?;
|
||||
let atom_stats_ptr = self.atom_stats_buf.raw_ptr();
|
||||
let support_ptr = self.per_sample_support_ptr;
|
||||
let q_var_ptr = self.q_var_buf_trainer.raw_ptr();
|
||||
unsafe {
|
||||
let null_q_var = 0u64; // No variance output needed for Q-stats path
|
||||
self.stream
|
||||
.launch_builder(&self.expected_q_kernel)
|
||||
.arg(&on_v_ptr)
|
||||
@@ -4959,7 +5100,7 @@ impl GpuDqnTrainer {
|
||||
.arg(&b3)
|
||||
.arg(&support_ptr)
|
||||
.arg(&atom_stats_ptr)
|
||||
.arg(&null_q_var)
|
||||
.arg(&q_var_ptr)
|
||||
.launch(LaunchConfig {
|
||||
grid_dim: (eq_blocks, 1, 1),
|
||||
block_dim: (256, 1, 1),
|
||||
@@ -4978,6 +5119,9 @@ impl GpuDqnTrainer {
|
||||
// Graph message passing: structural coordination across branches (in-place on q_coord_buf).
|
||||
self.launch_graph_message_pass(batch_size)?;
|
||||
|
||||
// Diffusion Q-refinement: 3-step denoiser conditioned on Var[Q].
|
||||
self.launch_q_denoise(batch_size)?;
|
||||
|
||||
// Stats reduction on q_out_buf.
|
||||
let num_atoms = na;
|
||||
unsafe {
|
||||
|
||||
@@ -1990,6 +1990,13 @@ impl FusedTrainingCtx {
|
||||
.map_err(|e| anyhow::anyhow!("launch_graph_message_pass: {e}"))
|
||||
}
|
||||
|
||||
/// Run 3-step diffusion denoising on q_coord_buf conditioned on Var[Q].
|
||||
/// Must be called AFTER launch_graph_message_pass.
|
||||
pub(crate) fn launch_q_denoise(&self, batch_size: usize) -> Result<()> {
|
||||
self.trainer.launch_q_denoise(batch_size)
|
||||
.map_err(|e| anyhow::anyhow!("launch_q_denoise: {e}"))
|
||||
}
|
||||
|
||||
/// Center Q-values in q_out_buf by subtracting their global mean.
|
||||
/// Prevents bootstrapping drift. Must be called after compute_expected_q,
|
||||
/// before launch_q_attention.
|
||||
|
||||
Reference in New Issue
Block a user