diff --git a/crates/ml/src/cuda_pipeline/gpu_attention.rs b/crates/ml/src/cuda_pipeline/gpu_attention.rs index 62db95108..f60d6a5af 100644 --- a/crates/ml/src/cuda_pipeline/gpu_attention.rs +++ b/crates/ml/src/cuda_pipeline/gpu_attention.rs @@ -161,6 +161,27 @@ impl GpuAttention { Ok(&self.output_buf) } + /// Backward pass (Phase A: residual passthrough). + /// + /// For frozen attention weights, the gradient flows entirely through + /// the residual connection: d_input = d_output. This is a no-op when + /// d_input and d_output are the same buffer (which they are in + /// apply_iqn_trunk_gradient where bw_d_h_s2 is used for both). + /// + /// Phase B (unfrozen weights) would add attention path gradients + /// and accumulate weight gradients via atomicAdd. + pub fn backward_residual( + &self, + _d_output: &CudaSlice, + _d_input: &mut CudaSlice, + _batch_size: usize, + ) -> Result<(), MLError> { + // Phase A: d_input = d_output through residual path. + // When d_input and d_output alias (same bw_d_h_s2 buffer), + // this is a literal no-op — the gradient is already in place. + Ok(()) + } + /// Get the output buffer reference. pub fn output(&self) -> &CudaSlice { &self.output_buf