1#![allow(dead_code)]
16#![allow(unsafe_code)]
21
22#[cfg(feature = "cuda")]
25const OP_RMSNORM_ATTN: usize = 0;
26#[cfg(feature = "cuda")]
27const OP_QKV_GEMM: usize = 1;
28#[cfg(feature = "cuda")]
29const OP_ATTENTION: usize = 2;
30#[cfg(feature = "cuda")]
31const OP_O_PROJ: usize = 3;
32#[cfg(feature = "cuda")]
33const OP_RMSNORM_FFN: usize = 4;
34#[cfg(feature = "cuda")]
35const OP_GATE_UP_GEMM: usize = 5;
36#[cfg(feature = "cuda")]
37const OP_SILU: usize = 6;
38#[cfg(feature = "cuda")]
39const OP_DOWN_GEMM: usize = 7;
40
41#[cfg(feature = "cuda")]
43const OP_LORA_FWD: usize = 8;
44#[cfg(feature = "cuda")]
45const OP_DOWN_BWD: usize = 9;
46#[cfg(feature = "cuda")]
47const OP_SWIGLU_BWD: usize = 10;
48#[cfg(feature = "cuda")]
49const OP_GATE_UP_BWD: usize = 11;
50#[cfg(feature = "cuda")]
51const OP_ATTN_BWD: usize = 12;
52#[cfg(feature = "cuda")]
53const OP_QKV_BWD: usize = 13;
54#[cfg(feature = "cuda")]
55const OP_NORM_BWD: usize = 14;
56#[cfg(feature = "cuda")]
57const OP_LORA_BWD: usize = 15;
58
59#[cfg(feature = "cuda")]
60use std::sync::Arc;
61
62#[cfg(feature = "cuda")]
63#[inline]
64fn saturating_u32(v: usize) -> u32 {
65 v.min(u32::MAX as usize) as u32
66}
67
68#[cfg(feature = "cuda")]
70#[inline]
71fn leak<T>(val: T) {
72 let _ = std::mem::ManuallyDrop::new(val);
73}
74
75#[cfg(feature = "cuda")]
80#[inline]
81pub(crate) fn nan_scan_enabled() -> bool {
82 static ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
83 *ENABLED.get_or_init(|| std::env::var("APR_NAN_SCAN").as_deref() == Ok("1"))
84}
85
86#[cfg(feature = "cuda")]
89pub(crate) fn nan_scan_f32(
90 tag: &str,
91 buf: &GpuBuffer<f32>,
92 n: usize,
93 stream: &CudaStream,
94) -> usize {
95 if !nan_scan_enabled() {
96 return 0;
97 }
98 if let Err(e) = stream.synchronize() {
99 eprintln!("[NAN-SCAN] {tag}: sync failed: {e:?}");
100 return 0;
101 }
102 let n = n.min(buf.len());
103 let mut host = vec![0.0f32; n];
104 if let Err(e) = buf.copy_to_host(&mut host) {
105 eprintln!("[NAN-SCAN] {tag}: download failed: {e:?}");
106 return 0;
107 }
108 let bad = host.iter().filter(|v| !v.is_finite()).count();
109 if bad > 0 {
110 let first_idx = host.iter().position(|v| !v.is_finite()).unwrap_or(0);
111 eprintln!(
112 "[NAN-SCAN] {tag}: {bad}/{n} non-finite (first at [{first_idx}]={})",
113 host[first_idx]
114 );
115 }
116 bad
117}
118
119#[cfg(feature = "cuda")]
120use trueno_gpu::driver::{CudaContext, CudaStream, GpuBuffer};
121
122#[cfg(feature = "cuda")]
123use crate::autograd::cuda_backward::{
124 batched_softmax_backward, gemm_backward_a, gemm_backward_a_fp16_dispatch,
125 gemm_backward_a_fp16_dispatch_accumulate, gemm_backward_b, rms_norm_backward, silu_backward,
126};
127#[cfg(feature = "cuda")]
128use crate::autograd::cuda_forward::{
129 batched_4d_gemm_forward, batched_rope_neox_backward, batched_rope_neox_forward,
130 batched_softmax_forward, batched_to_interleaved_forward, batched_transpose_forward,
131 cast_f32_to_f16_gpu, elementwise_mul_forward, expand_kv_heads, fused_residual_rmsnorm_forward,
132 fused_swiglu_forward, gemm_f16_to_f32_forward, gemm_forward, interleaved_to_batched_forward,
133 per_head_rmsnorm_forward, residual_add_forward, rms_norm_forward, rms_norm_forward_with_eps,
134 scale_forward, silu_forward,
135};
136#[cfg(feature = "cuda")]
137use crate::autograd::cuda_optim::{adamw_step_cuda, gradient_clip_cuda, squared_sum_cuda};
138#[cfg(feature = "cuda")]
139use crate::autograd::cuda_tensor::Result;
140
141#[cfg(feature = "cuda")]
142use super::config::TransformerConfig;
143
144#[cfg(feature = "cuda")]
148pub struct CudaTransformerBlock {
149 config: TransformerConfig,
151 layer_idx: usize,
153 input_norm_weight: GpuBuffer<f32>,
155 post_attn_norm_weight: GpuBuffer<f32>,
157 w_q: GpuBuffer<f32>,
159 w_k: GpuBuffer<f32>,
161 w_v: GpuBuffer<f32>,
163 w_o: GpuBuffer<f32>,
165 w_gate: GpuBuffer<f32>,
167 w_up: GpuBuffer<f32>,
169 w_down: GpuBuffer<f32>,
171 ctx: Arc<CudaContext>,
173 scratch: CudaBlockScratch,
175 norm_zero_buf: Vec<f32>,
177 q_norm_weight: Option<GpuBuffer<f32>>,
179 k_norm_weight: Option<GpuBuffer<f32>>,
180 b_q_replicated: Option<GpuBuffer<f32>>,
189 b_k_replicated: Option<GpuBuffer<f32>>,
190 b_v_replicated: Option<GpuBuffer<f32>>,
191}
192
193#[cfg(feature = "cuda")]
204pub(crate) struct CudaBlockScratch {
205 norm1_out: GpuBuffer<f32>,
207 q: GpuBuffer<f32>,
209 k: GpuBuffer<f32>,
211 v: GpuBuffer<f32>,
213 attn_scores: GpuBuffer<f32>,
215 attn_out: GpuBuffer<f32>,
217 o_proj_out: GpuBuffer<f32>,
219 residual1: GpuBuffer<f32>,
221 norm2_out: GpuBuffer<f32>,
223 gate_out: GpuBuffer<f32>,
225 up_out: GpuBuffer<f32>,
227 swiglu_out: GpuBuffer<f32>,
229 ffn_out: GpuBuffer<f32>,
231 norm1_out_f16: Option<GpuBuffer<u16>>,
234 attn_out_f16: Option<GpuBuffer<u16>>,
235 norm2_out_f16: Option<GpuBuffer<u16>>,
236 swiglu_out_f16: Option<GpuBuffer<u16>>,
237 grad_hidden: GpuBuffer<f32>,
240 grad_swiglu: GpuBuffer<f32>,
242 attn_q_batched: GpuBuffer<f32>,
245 attn_kv_temp: GpuBuffer<f32>,
247 attn_kv_temp2: GpuBuffer<f32>,
249 grad_attn_scores: GpuBuffer<f32>,
253 lora_inter: GpuBuffer<f32>,
256 lora_temp: GpuBuffer<f32>,
259 rope_positions: GpuBuffer<u32>,
261 causal_mask_contiguous: GpuBuffer<f32>,
263 pub(crate) causal_mask_cached_seq_len: usize,
265 pub(crate) op_us: [u64; 16],
269 pub(crate) op_profiling_enabled: bool,
271}
272
273#[cfg(feature = "cuda")]
274impl CudaBlockScratch {
275 #[inline]
277 pub(crate) fn op_begin(&self) -> Option<std::time::Instant> {
278 if self.op_profiling_enabled {
279 Some(std::time::Instant::now())
280 } else {
281 None
282 }
283 }
284
285 #[inline]
287 pub(crate) fn op_end(&mut self, start: Option<std::time::Instant>, op: usize) {
288 if let Some(t) = start {
289 if op < 16 {
290 self.op_us[op] += t.elapsed().as_micros() as u64;
291 }
292 }
293 }
294
295 pub(crate) fn max_seq_len(&self, hidden_size: usize) -> usize {
299 self.norm1_out.len() / hidden_size.max(1)
300 }
301
302 #[rustfmt::skip]
303 pub(crate) fn zero_forward_buffers(&mut self, stream: &CudaStream) {
304 let z = |b: &mut GpuBuffer<f32>| { b.zero_async(stream).ok(); };
305 z(&mut self.norm1_out); z(&mut self.q); z(&mut self.k); z(&mut self.v); z(&mut self.attn_scores); z(&mut self.attn_out);
306 z(&mut self.o_proj_out); z(&mut self.residual1); z(&mut self.norm2_out); z(&mut self.gate_out); z(&mut self.up_out);
307 z(&mut self.swiglu_out); z(&mut self.ffn_out); z(&mut self.attn_q_batched); z(&mut self.attn_kv_temp); z(&mut self.attn_kv_temp2);
308 z(&mut self.grad_hidden); z(&mut self.grad_swiglu); z(&mut self.grad_attn_scores); z(&mut self.lora_inter); z(&mut self.lora_temp);
309 self.causal_mask_cached_seq_len = 0;
310 }
311
312 pub(crate) fn new(
318 config: &TransformerConfig,
319 max_seq_len: usize,
320 ctx: &Arc<CudaContext>,
321 lora_rank: usize,
322 ) -> Result<Self> {
323 let hidden_size = config.hidden_size;
324 let q_dim = config.q_dim();
325 let kv_hidden_size = config.num_kv_heads * config.head_dim();
326 let intermediate_size = config.intermediate_size;
327 let num_heads = config.num_attention_heads;
328 let head_dim = config.head_dim();
329
330 let max_proj_dim = q_dim.max(kv_hidden_size);
332 let lora_inter_size = (max_seq_len * lora_rank).max(1);
334 let lora_temp_size = (max_seq_len * max_proj_dim).max(1);
335
336 let causal_mask_data: Vec<f32> = (0..max_seq_len * max_seq_len)
339 .map(|idx| {
340 let row = idx / max_seq_len;
341 let col = idx % max_seq_len;
342 if col <= row {
343 0.0f32
344 } else {
345 f32::NEG_INFINITY
346 }
347 })
348 .collect();
349 Ok(Self {
350 norm1_out: GpuBuffer::new(ctx, max_seq_len * hidden_size)?,
351 q: GpuBuffer::new(ctx, max_seq_len * q_dim)?,
352 k: GpuBuffer::new(ctx, max_seq_len * kv_hidden_size)?,
353 v: GpuBuffer::new(ctx, max_seq_len * kv_hidden_size)?,
354 attn_scores: GpuBuffer::new(ctx, num_heads * max_seq_len * max_seq_len)?,
355 attn_out: GpuBuffer::new(ctx, max_seq_len * q_dim)?,
356 o_proj_out: GpuBuffer::new(ctx, max_seq_len * hidden_size)?,
357 residual1: GpuBuffer::new(ctx, max_seq_len * hidden_size)?,
358 norm2_out: GpuBuffer::new(ctx, max_seq_len * hidden_size)?,
359 gate_out: GpuBuffer::new(ctx, max_seq_len * intermediate_size)?,
360 up_out: GpuBuffer::new(ctx, max_seq_len * intermediate_size)?,
361 swiglu_out: GpuBuffer::new(ctx, max_seq_len * intermediate_size)?,
362 ffn_out: GpuBuffer::new(ctx, max_seq_len * hidden_size)?,
363 norm1_out_f16: None,
364 attn_out_f16: None,
365 norm2_out_f16: None,
366 swiglu_out_f16: None,
367 grad_hidden: GpuBuffer::new(ctx, max_seq_len * hidden_size)?,
368 grad_swiglu: GpuBuffer::new(ctx, max_seq_len * intermediate_size)?,
369 attn_q_batched: GpuBuffer::new(ctx, num_heads * max_seq_len * head_dim)?,
370 attn_kv_temp: GpuBuffer::new(ctx, num_heads * max_seq_len * head_dim)?,
371 attn_kv_temp2: GpuBuffer::new(ctx, num_heads * max_seq_len * head_dim)?,
372 grad_attn_scores: GpuBuffer::new(
373 ctx,
374 num_heads * max_seq_len * max_seq_len.max(head_dim),
375 )?,
376 lora_inter: GpuBuffer::new(ctx, lora_inter_size)?,
377 lora_temp: GpuBuffer::new(ctx, lora_temp_size)?,
378 rope_positions: {
379 let positions: Vec<u32> = (0..max_seq_len as u32).collect();
380 let mut buf = GpuBuffer::new(ctx, max_seq_len)?;
381 buf.copy_from_host(&positions)?;
382 buf
383 },
384 causal_mask_contiguous: GpuBuffer::from_host(ctx, &causal_mask_data)?,
385 causal_mask_cached_seq_len: max_seq_len,
386 op_us: [0u64; 16],
387 op_profiling_enabled: false,
388 })
389 }
390
391 pub(crate) fn prepare_causal_mask(
394 &mut self,
395 seq_len: usize,
396 ctx: &Arc<CudaContext>,
397 ) -> crate::autograd::cuda_tensor::Result<()> {
398 if seq_len == self.causal_mask_cached_seq_len {
399 return Ok(());
400 }
401 let mask_data: Vec<f32> = (0..seq_len * seq_len)
402 .map(|idx| {
403 let row = idx / seq_len;
404 let col = idx % seq_len;
405 if col <= row {
406 0.0f32
407 } else {
408 f32::NEG_INFINITY
409 }
410 })
411 .collect();
412 self.causal_mask_contiguous = GpuBuffer::from_host(ctx, &mask_data)?;
413 self.causal_mask_cached_seq_len = seq_len;
414 Ok(())
415 }
416}
417
418#[cfg(feature = "cuda")]
432pub struct CudaGradWorkspace {
433 pub(crate) grad_input_norm: GpuBuffer<f32>,
435 pub(crate) grad_post_attn_norm: GpuBuffer<f32>,
437 pub(crate) grad_gate: GpuBuffer<f32>,
439 pub(crate) grad_up: GpuBuffer<f32>,
441 pub(crate) grad_down: GpuBuffer<f32>,
443 pub(crate) grad_w_q: GpuBuffer<f32>,
445 pub(crate) grad_w_k: GpuBuffer<f32>,
447 pub(crate) grad_w_v: GpuBuffer<f32>,
449 pub(crate) grad_w_o: GpuBuffer<f32>,
451}
452
453#[cfg(feature = "cuda")]
454impl CudaGradWorkspace {
455 pub fn new(ctx: &Arc<CudaContext>, config: &TransformerConfig) -> Result<Self> {
461 let h = config.hidden_size;
462 let q = config.q_dim();
463 let kv = config.num_kv_heads * config.head_dim();
464 let i = config.intermediate_size;
465
466 Ok(Self {
467 grad_input_norm: GpuBuffer::new(ctx, h)?,
468 grad_post_attn_norm: GpuBuffer::new(ctx, h)?,
469 grad_gate: GpuBuffer::new(ctx, h * i)?,
470 grad_up: GpuBuffer::new(ctx, h * i)?,
471 grad_down: GpuBuffer::new(ctx, i * h)?,
472 grad_w_q: GpuBuffer::new(ctx, q * h)?,
473 grad_w_k: GpuBuffer::new(ctx, h * kv)?,
474 grad_w_v: GpuBuffer::new(ctx, h * kv)?,
475 grad_w_o: GpuBuffer::new(ctx, h * q)?,
476 })
477 }
478
479 pub fn zero_norm_grads(&mut self, zero_buf: &[f32]) -> Result<()> {
485 let n = self.grad_input_norm.len();
486 self.grad_input_norm.copy_from_host(&zero_buf[..n]).map_err(|e| {
487 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
488 "Failed to zero grad_input_norm: {e:?}"
489 ))
490 })?;
491 self.grad_post_attn_norm.copy_from_host(&zero_buf[..n]).map_err(|e| {
492 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
493 "Failed to zero grad_post_attn_norm: {e:?}"
494 ))
495 })?;
496 Ok(())
497 }
498}
499
500#[cfg(feature = "cuda")]
512pub struct GpuBlockOptimizerState {
513 m_w_q: GpuBuffer<f32>,
515 v_w_q: GpuBuffer<f32>,
516 m_w_k: GpuBuffer<f32>,
517 v_w_k: GpuBuffer<f32>,
518 m_w_v: GpuBuffer<f32>,
519 v_w_v: GpuBuffer<f32>,
520 m_w_o: GpuBuffer<f32>,
521 v_w_o: GpuBuffer<f32>,
522 m_w_gate: GpuBuffer<f32>,
524 v_w_gate: GpuBuffer<f32>,
525 m_w_up: GpuBuffer<f32>,
526 v_w_up: GpuBuffer<f32>,
527 m_w_down: GpuBuffer<f32>,
528 v_w_down: GpuBuffer<f32>,
529 m_input_norm: GpuBuffer<f32>,
531 v_input_norm: GpuBuffer<f32>,
532 m_post_attn_norm: GpuBuffer<f32>,
533 v_post_attn_norm: GpuBuffer<f32>,
534}
535
536#[cfg(feature = "cuda")]
538impl GpuBlockOptimizerState {
539 pub fn download_to_host(
542 &self,
543 ) -> crate::autograd::cuda_tensor::Result<Vec<(String, Vec<f32>)>> {
544 let dl = |name: &str,
545 buf: &GpuBuffer<f32>|
546 -> crate::autograd::cuda_tensor::Result<(String, Vec<f32>)> {
547 let mut host = vec![0.0f32; buf.len()];
548 buf.copy_to_host(&mut host).map_err(|e| {
549 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
550 "optimizer D2H {name}: {e}"
551 ))
552 })?;
553 Ok((name.to_string(), host))
554 };
555 Ok(vec![
556 dl("m.w_q", &self.m_w_q)?,
557 dl("v.w_q", &self.v_w_q)?,
558 dl("m.w_k", &self.m_w_k)?,
559 dl("v.w_k", &self.v_w_k)?,
560 dl("m.w_v", &self.m_w_v)?,
561 dl("v.w_v", &self.v_w_v)?,
562 dl("m.w_o", &self.m_w_o)?,
563 dl("v.w_o", &self.v_w_o)?,
564 dl("m.w_gate", &self.m_w_gate)?,
565 dl("v.w_gate", &self.v_w_gate)?,
566 dl("m.w_up", &self.m_w_up)?,
567 dl("v.w_up", &self.v_w_up)?,
568 dl("m.w_down", &self.m_w_down)?,
569 dl("v.w_down", &self.v_w_down)?,
570 dl("m.input_norm", &self.m_input_norm)?,
571 dl("v.input_norm", &self.v_input_norm)?,
572 dl("m.post_attn_norm", &self.m_post_attn_norm)?,
573 dl("v.post_attn_norm", &self.v_post_attn_norm)?,
574 ])
575 }
576
577 pub fn restore_from_host(
580 &mut self,
581 data: &std::collections::HashMap<String, Vec<f32>>,
582 ) -> crate::autograd::cuda_tensor::Result<()> {
583 let ul = |name: &str,
584 buf: &mut GpuBuffer<f32>,
585 data: &std::collections::HashMap<String, Vec<f32>>|
586 -> crate::autograd::cuda_tensor::Result<()> {
587 if let Some(host_data) = data.get(name) {
588 if host_data.len() == buf.len() {
589 buf.copy_from_host(host_data).map_err(|e| {
590 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
591 "optimizer H2D {name}: {e}"
592 ))
593 })?;
594 }
595 }
596 Ok(())
597 };
598 ul("m.w_q", &mut self.m_w_q, data)?;
599 ul("v.w_q", &mut self.v_w_q, data)?;
600 ul("m.w_k", &mut self.m_w_k, data)?;
601 ul("v.w_k", &mut self.v_w_k, data)?;
602 ul("m.w_v", &mut self.m_w_v, data)?;
603 ul("v.w_v", &mut self.v_w_v, data)?;
604 ul("m.w_o", &mut self.m_w_o, data)?;
605 ul("v.w_o", &mut self.v_w_o, data)?;
606 ul("m.w_gate", &mut self.m_w_gate, data)?;
607 ul("v.w_gate", &mut self.v_w_gate, data)?;
608 ul("m.w_up", &mut self.m_w_up, data)?;
609 ul("v.w_up", &mut self.v_w_up, data)?;
610 ul("m.w_down", &mut self.m_w_down, data)?;
611 ul("v.w_down", &mut self.v_w_down, data)?;
612 ul("m.input_norm", &mut self.m_input_norm, data)?;
613 ul("v.input_norm", &mut self.v_input_norm, data)?;
614 ul("m.post_attn_norm", &mut self.m_post_attn_norm, data)?;
615 ul("v.post_attn_norm", &mut self.v_post_attn_norm, data)?;
616 Ok(())
617 }
618}
619
620#[cfg(feature = "cuda")]
621impl CudaTransformerBlock {
622 #[allow(clippy::too_many_arguments)]
626 pub fn new(
627 config: &TransformerConfig,
628 layer_idx: usize,
629 ctx: Arc<CudaContext>,
630 input_norm_weight: &[f32],
631 post_attn_norm_weight: &[f32],
632 w_q: &[f32],
633 w_k: &[f32],
634 w_v: &[f32],
635 w_o: &[f32],
636 w_gate: &[f32],
637 w_up: &[f32],
638 w_down: &[f32],
639 max_seq_len: usize,
640 b_q: Option<&[f32]>,
646 b_k: Option<&[f32]>,
647 b_v: Option<&[f32]>,
648 ) -> Result<Self> {
649 let hidden_size = config.hidden_size;
650 let q_dim = config.q_dim(); let kv_hidden_size = config.num_kv_heads * config.head_dim();
652 let intermediate_size = config.intermediate_size;
653 let num_heads = config.num_attention_heads;
654
655 let input_norm_weight = GpuBuffer::from_host(&ctx, input_norm_weight)?;
657 let post_attn_norm_weight = GpuBuffer::from_host(&ctx, post_attn_norm_weight)?;
658 let w_q = GpuBuffer::from_host(&ctx, w_q)?;
659 let w_k = GpuBuffer::from_host(&ctx, w_k)?;
660 let w_v = GpuBuffer::from_host(&ctx, w_v)?;
661 let w_o = GpuBuffer::from_host(&ctx, w_o)?;
662 let w_gate = GpuBuffer::from_host(&ctx, w_gate)?;
663 let w_up = GpuBuffer::from_host(&ctx, w_up)?;
664 let w_down = GpuBuffer::from_host(&ctx, w_down)?;
665
666 let single_mask: Vec<f32> = (0..max_seq_len * max_seq_len)
668 .map(|idx| {
669 let row = idx / max_seq_len;
670 let col = idx % max_seq_len;
671 if col <= row {
672 0.0f32
673 } else {
674 f32::NEG_INFINITY
675 }
676 })
677 .collect();
678 let scratch = CudaBlockScratch {
680 norm1_out: GpuBuffer::new(&ctx, max_seq_len * hidden_size)?,
681 q: GpuBuffer::new(&ctx, max_seq_len * q_dim)?,
682 k: GpuBuffer::new(&ctx, max_seq_len * kv_hidden_size)?,
683 v: GpuBuffer::new(&ctx, max_seq_len * kv_hidden_size)?,
684 attn_scores: GpuBuffer::new(&ctx, num_heads * max_seq_len * max_seq_len)?,
685 attn_out: GpuBuffer::new(&ctx, max_seq_len * q_dim)?,
686 o_proj_out: GpuBuffer::new(&ctx, max_seq_len * hidden_size)?,
687 residual1: GpuBuffer::new(&ctx, max_seq_len * hidden_size)?,
688 norm2_out: GpuBuffer::new(&ctx, max_seq_len * hidden_size)?,
689 gate_out: GpuBuffer::new(&ctx, max_seq_len * intermediate_size)?,
690 up_out: GpuBuffer::new(&ctx, max_seq_len * intermediate_size)?,
691 swiglu_out: GpuBuffer::new(&ctx, max_seq_len * intermediate_size)?,
692 ffn_out: GpuBuffer::new(&ctx, max_seq_len * hidden_size)?,
693 norm1_out_f16: None,
694 attn_out_f16: None,
695 norm2_out_f16: None,
696 swiglu_out_f16: None,
697 grad_hidden: GpuBuffer::new(&ctx, max_seq_len * hidden_size)?,
699 grad_swiglu: GpuBuffer::new(&ctx, max_seq_len * intermediate_size)?,
700 attn_q_batched: GpuBuffer::new(&ctx, num_heads * max_seq_len * config.head_dim())?,
702 attn_kv_temp: GpuBuffer::new(&ctx, num_heads * max_seq_len * config.head_dim())?,
703 attn_kv_temp2: GpuBuffer::new(&ctx, num_heads * max_seq_len * config.head_dim())?,
704 grad_attn_scores: GpuBuffer::new(
707 &ctx,
708 num_heads * max_seq_len * max_seq_len.max(config.head_dim()),
709 )?,
710 lora_inter: GpuBuffer::new(&ctx, 1)?,
712 lora_temp: GpuBuffer::new(&ctx, 1)?,
713 rope_positions: {
714 let positions: Vec<u32> = (0..max_seq_len as u32).collect();
715 let mut buf = GpuBuffer::new(&ctx, max_seq_len)?;
716 buf.copy_from_host(&positions)?;
717 buf
718 },
719 causal_mask_contiguous: GpuBuffer::from_host(&ctx, &single_mask)?,
720 causal_mask_cached_seq_len: max_seq_len,
721 op_us: [0u64; 16],
722 op_profiling_enabled: false,
723 };
724
725 let replicate = |bias: Option<&[f32]>, dim: usize| -> Result<Option<GpuBuffer<f32>>> {
731 match bias {
732 Some(slice) => {
733 debug_assert_eq!(
734 slice.len(),
735 dim,
736 "bias slice len {} != expected dim {dim}",
737 slice.len()
738 );
739 let mut repl: Vec<f32> = Vec::with_capacity(max_seq_len * dim);
740 for _ in 0..max_seq_len {
741 repl.extend_from_slice(slice);
742 }
743 Ok(Some(GpuBuffer::from_host(&ctx, &repl)?))
744 }
745 None => Ok(None),
746 }
747 };
748 let b_q_replicated = replicate(b_q, q_dim)?;
749 let b_k_replicated = replicate(b_k, kv_hidden_size)?;
750 let b_v_replicated = replicate(b_v, kv_hidden_size)?;
751
752 Ok(Self {
753 config: config.clone(),
754 layer_idx,
755 input_norm_weight,
756 post_attn_norm_weight,
757 w_q,
758 w_k,
759 w_v,
760 w_o,
761 w_gate,
762 w_up,
763 w_down,
764 ctx,
765 scratch,
766 norm_zero_buf: vec![0.0f32; hidden_size],
767 q_norm_weight: None, k_norm_weight: None,
769 b_q_replicated,
770 b_k_replicated,
771 b_v_replicated,
772 })
773 }
774
775 #[allow(dead_code)]
777 pub fn set_qk_norm(&mut self, q_norm: &[f32], k_norm: &[f32]) -> Result<()> {
778 self.q_norm_weight = Some(GpuBuffer::from_host(&self.ctx, q_norm)?);
779 self.k_norm_weight = Some(GpuBuffer::from_host(&self.ctx, k_norm)?);
780 Ok(())
781 }
782
783 pub fn forward(
791 &mut self,
792 input: &GpuBuffer<f32>,
793 output: &mut GpuBuffer<f32>,
794 seq_len: usize,
795 stream: &CudaStream,
796 ) -> Result<()> {
797 let hidden_size = self.config.hidden_size;
798 let q_dim = self.config.q_dim();
799 let kv_hidden_size = self.config.num_kv_heads * self.config.head_dim();
800 let intermediate_size = self.config.intermediate_size;
801
802 rms_norm_forward_with_eps(
807 input,
808 &self.input_norm_weight,
809 &mut self.scratch.norm1_out,
810 saturating_u32(seq_len),
811 saturating_u32(hidden_size),
812 self.config.rms_norm_eps,
813 stream,
814 )?;
815
816 gemm_forward(
819 &self.scratch.norm1_out,
820 &self.w_q,
821 &mut self.scratch.q,
822 saturating_u32(seq_len),
823 saturating_u32(hidden_size),
824 saturating_u32(q_dim),
825 stream,
826 )?;
827 if let Some(b_q_repl) = self.b_q_replicated.as_ref() {
832 cuda_add_inplace(&mut self.scratch.q, b_q_repl, seq_len * q_dim, stream)?;
833 }
834
835 gemm_forward(
836 &self.scratch.norm1_out,
837 &self.w_k,
838 &mut self.scratch.k,
839 saturating_u32(seq_len),
840 saturating_u32(hidden_size),
841 saturating_u32(kv_hidden_size),
842 stream,
843 )?;
844 if let Some(b_k_repl) = self.b_k_replicated.as_ref() {
845 cuda_add_inplace(&mut self.scratch.k, b_k_repl, seq_len * kv_hidden_size, stream)?;
846 }
847
848 gemm_forward(
849 &self.scratch.norm1_out,
850 &self.w_v,
851 &mut self.scratch.v,
852 saturating_u32(seq_len),
853 saturating_u32(hidden_size),
854 saturating_u32(kv_hidden_size),
855 stream,
856 )?;
857 if let Some(b_v_repl) = self.b_v_replicated.as_ref() {
858 cuda_add_inplace(&mut self.scratch.v, b_v_repl, seq_len * kv_hidden_size, stream)?;
859 }
860
861 self.compute_attention_cuda(seq_len, stream)?;
863
864 gemm_forward(
867 &self.scratch.attn_out,
868 &self.w_o,
869 &mut self.scratch.o_proj_out,
870 saturating_u32(seq_len),
871 saturating_u32(q_dim),
872 saturating_u32(hidden_size),
873 stream,
874 )?;
875
876 cuda_add(
878 input,
879 &self.scratch.o_proj_out,
880 &mut self.scratch.residual1,
881 seq_len * hidden_size,
882 stream,
883 )?;
884
885 rms_norm_forward_with_eps(
888 &self.scratch.residual1,
889 &self.post_attn_norm_weight,
890 &mut self.scratch.norm2_out,
891 saturating_u32(seq_len),
892 saturating_u32(hidden_size),
893 self.config.rms_norm_eps,
894 stream,
895 )?;
896
897 gemm_forward(
899 &self.scratch.norm2_out,
900 &self.w_gate,
901 &mut self.scratch.gate_out,
902 saturating_u32(seq_len),
903 saturating_u32(hidden_size),
904 saturating_u32(intermediate_size),
905 stream,
906 )?;
907
908 gemm_forward(
909 &self.scratch.norm2_out,
910 &self.w_up,
911 &mut self.scratch.up_out,
912 saturating_u32(seq_len),
913 saturating_u32(hidden_size),
914 saturating_u32(intermediate_size),
915 stream,
916 )?;
917
918 fused_swiglu_forward(
920 &self.scratch.gate_out,
921 &self.scratch.up_out,
922 &mut self.scratch.swiglu_out,
923 saturating_u32(seq_len * intermediate_size),
924 stream,
925 )?;
926
927 gemm_forward(
929 &self.scratch.swiglu_out,
930 &self.w_down,
931 &mut self.scratch.ffn_out,
932 saturating_u32(seq_len),
933 saturating_u32(intermediate_size),
934 saturating_u32(hidden_size),
935 stream,
936 )?;
937
938 cuda_add(
940 &self.scratch.residual1,
941 &self.scratch.ffn_out,
942 output,
943 seq_len * hidden_size,
944 stream,
945 )?;
946
947 Ok(())
948 }
949
950 fn compute_attention_cuda(&mut self, seq_len: usize, stream: &CudaStream) -> Result<()> {
968 let num_heads = self.config.num_attention_heads;
969 let num_kv_heads = self.config.num_kv_heads;
970 let head_dim = self.config.head_dim();
971 let heads_per_kv = num_heads / num_kv_heads;
972 let scale = 1.0 / (head_dim as f32).sqrt();
973
974 let seq = saturating_u32(seq_len);
975 let nh = saturating_u32(num_heads);
976 let nkv = saturating_u32(num_kv_heads);
977 let hd = saturating_u32(head_dim);
978
979 self.scratch.prepare_causal_mask(seq_len, &self.ctx)?;
981
982 if let Some(ref q_norm) = self.q_norm_weight {
987 for pos in 0..seq_len {
988 let q_ref = unsafe { &*(std::ptr::addr_of!(self.scratch.q)) };
990 per_head_rmsnorm_forward(q_ref, q_norm, &mut self.scratch.q, nh, hd, pos, stream)?;
991 }
992 }
993 if let Some(ref k_norm) = self.k_norm_weight {
994 for pos in 0..seq_len {
995 let k_ref = unsafe { &*(std::ptr::addr_of!(self.scratch.k)) };
997 per_head_rmsnorm_forward(k_ref, k_norm, &mut self.scratch.k, nkv, hd, pos, stream)?;
998 }
999 }
1000
1001 let rope_theta = self.config.rope_theta;
1004 {
1005 let q_ref = unsafe { &*(std::ptr::addr_of!(self.scratch.q)) };
1007 batched_rope_neox_forward(
1008 q_ref,
1009 &mut self.scratch.q,
1010 &self.scratch.rope_positions,
1011 nh,
1012 hd,
1013 seq,
1014 rope_theta,
1015 stream,
1016 )?;
1017 let k_ref = unsafe { &*(std::ptr::addr_of!(self.scratch.k)) };
1019 batched_rope_neox_forward(
1020 k_ref,
1021 &mut self.scratch.k,
1022 &self.scratch.rope_positions,
1023 nkv,
1024 hd,
1025 seq,
1026 rope_theta,
1027 stream,
1028 )?;
1029 }
1030
1031 interleaved_to_batched_forward(
1033 &self.scratch.q,
1034 &mut self.scratch.attn_q_batched,
1035 seq,
1036 nh,
1037 hd,
1038 stream,
1039 )?;
1040
1041 interleaved_to_batched_forward(
1043 &self.scratch.k,
1044 &mut self.scratch.attn_kv_temp,
1045 seq,
1046 nkv,
1047 hd,
1048 stream,
1049 )?;
1050
1051 if heads_per_kv == 1 {
1053 batched_transpose_forward(
1055 &self.scratch.attn_kv_temp,
1056 &mut self.scratch.attn_kv_temp2,
1057 nh,
1058 seq,
1059 hd,
1060 stream,
1061 )?;
1062 } else {
1063 expand_kv_heads(
1065 &self.scratch.attn_kv_temp,
1066 &mut self.scratch.attn_kv_temp2,
1067 num_kv_heads,
1068 heads_per_kv,
1069 seq_len * head_dim,
1070 stream,
1071 )?;
1072 batched_transpose_forward(
1074 &self.scratch.attn_kv_temp2,
1075 &mut self.scratch.attn_kv_temp,
1076 nh,
1077 seq,
1078 hd,
1079 stream,
1080 )?;
1081 unsafe {
1085 self.scratch
1086 .attn_kv_temp2
1087 .copy_from_buffer_async(&self.scratch.attn_kv_temp, stream)
1088 .map_err(|e| {
1089 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
1090 "K^T buffer copy failed: {e}"
1091 ))
1092 })?;
1093 }
1094 }
1095
1096 batched_4d_gemm_forward(
1101 &self.scratch.attn_q_batched,
1102 &self.scratch.attn_kv_temp2,
1103 &mut self.scratch.attn_scores,
1104 1,
1105 nh,
1106 seq,
1107 seq,
1108 hd,
1109 stream,
1110 )?;
1111
1112 let total_scores = nh * seq * seq;
1114 {
1115 let scores_view = unsafe {
1119 GpuBuffer::<f32>::from_raw_parts(
1120 self.scratch.attn_scores.as_ptr(),
1121 self.scratch.attn_scores.len(),
1122 )
1123 };
1124 scale_forward(
1125 &scores_view,
1126 &mut self.scratch.attn_scores,
1127 scale,
1128 total_scores,
1129 stream,
1130 )?;
1131 leak(scores_view);
1132 }
1133
1134 {
1140 let seq_sq = (seq * seq) as usize;
1141 let mask_ptr = self.scratch.causal_mask_contiguous.as_ptr();
1142 let scores_base = self.scratch.attn_scores.as_ptr();
1143 for head in 0..nh as usize {
1144 let byte_offset = (head * seq_sq * 4) as u64; let head_ptr = scores_base + byte_offset;
1146 let mask_view = unsafe { GpuBuffer::<f32>::from_raw_parts(mask_ptr, seq_sq) };
1150 let scores_view = unsafe { GpuBuffer::<f32>::from_raw_parts(head_ptr, seq_sq) };
1152 let mut out_view = unsafe { GpuBuffer::<f32>::from_raw_parts(head_ptr, seq_sq) };
1154 residual_add_forward(&mask_view, &scores_view, &mut out_view, seq * seq, stream)?;
1155 leak(mask_view);
1156 leak(scores_view);
1157 leak(out_view);
1158 }
1159 }
1160
1161 let total_rows = nh * seq;
1163 {
1164 let scores_view = unsafe {
1168 GpuBuffer::<f32>::from_raw_parts(
1169 self.scratch.attn_scores.as_ptr(),
1170 self.scratch.attn_scores.len(),
1171 )
1172 };
1173 batched_softmax_forward(
1174 &scores_view,
1175 &mut self.scratch.attn_scores,
1176 total_rows,
1177 seq,
1178 stream,
1179 )?;
1180 leak(scores_view);
1181 }
1182
1183 interleaved_to_batched_forward(
1185 &self.scratch.v,
1186 &mut self.scratch.attn_kv_temp,
1187 seq,
1188 nkv,
1189 hd,
1190 stream,
1191 )?;
1192
1193 if heads_per_kv == 1 {
1194 } else {
1196 expand_kv_heads(
1198 &self.scratch.attn_kv_temp,
1199 &mut self.scratch.attn_kv_temp2,
1200 num_kv_heads,
1201 heads_per_kv,
1202 seq_len * head_dim,
1203 stream,
1204 )?;
1205 unsafe {
1208 self.scratch
1209 .attn_kv_temp
1210 .copy_from_buffer_async(&self.scratch.attn_kv_temp2, stream)
1211 .map_err(|e| {
1212 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
1213 "V expanded buffer copy failed: {e}"
1214 ))
1215 })?;
1216 }
1217 }
1218
1219 batched_4d_gemm_forward(
1224 &self.scratch.attn_scores,
1225 &self.scratch.attn_kv_temp,
1226 &mut self.scratch.attn_q_batched,
1227 1,
1228 nh,
1229 seq,
1230 hd,
1231 seq,
1232 stream,
1233 )?;
1234
1235 batched_to_interleaved_forward(
1237 &self.scratch.attn_q_batched,
1238 &mut self.scratch.attn_out,
1239 seq,
1240 nh,
1241 hd,
1242 stream,
1243 )?;
1244
1245 Ok(())
1246 }
1247
1248 pub fn layer_idx(&self) -> usize {
1250 self.layer_idx
1251 }
1252
1253 pub fn config(&self) -> &TransformerConfig {
1255 &self.config
1256 }
1257
1258 #[provable_contracts_macros::contract("backward-pass-v1", equation = "backward")]
1276 pub fn backward(
1277 &mut self,
1278 input: &GpuBuffer<f32>,
1279 grad_output: &GpuBuffer<f32>,
1280 grad_input: &mut GpuBuffer<f32>,
1281 seq_len: usize,
1282 stream: &CudaStream,
1283 grad_ws: &mut CudaGradWorkspace,
1284 ) -> Result<()> {
1285 let hidden_size = self.config.hidden_size;
1286 let intermediate_size = self.config.intermediate_size;
1287 let eps = 1e-5_f32;
1288
1289 grad_ws.zero_norm_grads(&self.norm_zero_buf)?;
1293
1294 self.backward_ffn(grad_output, seq_len, hidden_size, intermediate_size, stream, grad_ws)?;
1297
1298 self.backward_post_attn_norm(grad_input, seq_len, hidden_size, eps, stream, grad_ws)?;
1300
1301 cuda_add_inplace(grad_input, grad_output, seq_len * hidden_size, stream)?;
1306
1307 self.backward_attention(grad_input, seq_len, stream, grad_ws)?;
1310
1311 self.backward_residual_and_input_norm(
1313 input,
1314 grad_output,
1315 grad_input,
1316 seq_len,
1317 hidden_size,
1318 eps,
1319 stream,
1320 grad_ws,
1321 )?;
1322
1323 Ok(())
1324 }
1325
1326 fn backward_ffn(
1341 &mut self,
1342 grad_output: &GpuBuffer<f32>,
1343 seq_len: usize,
1344 hidden_size: usize,
1345 intermediate_size: usize,
1346 stream: &CudaStream,
1347 grad_ws: &mut CudaGradWorkspace,
1348 ) -> Result<()> {
1349 let n_inter = saturating_u32(seq_len * intermediate_size);
1350 let n_hidden = saturating_u32(seq_len * hidden_size);
1351
1352 gemm_backward_a(
1354 grad_output,
1355 &self.w_down,
1356 &mut self.scratch.grad_swiglu,
1357 saturating_u32(seq_len),
1358 saturating_u32(intermediate_size),
1359 saturating_u32(hidden_size),
1360 stream,
1361 )?;
1362
1363 gemm_backward_b(
1366 &self.scratch.swiglu_out,
1367 grad_output,
1368 &mut grad_ws.grad_down,
1369 saturating_u32(seq_len),
1370 saturating_u32(intermediate_size),
1371 saturating_u32(hidden_size),
1372 stream,
1373 )?;
1374
1375 elementwise_mul_forward(
1379 &self.scratch.grad_swiglu,
1380 &self.scratch.up_out,
1381 &mut self.scratch.swiglu_out,
1382 n_inter,
1383 stream,
1384 )?;
1385
1386 silu_backward(
1389 &self.scratch.gate_out,
1390 &self.scratch.swiglu_out,
1391 &mut self.scratch.up_out,
1392 stream,
1393 )?;
1394 silu_forward(&self.scratch.gate_out, &mut self.scratch.swiglu_out, n_inter, stream)?;
1398
1399 elementwise_mul_forward(
1401 &self.scratch.grad_swiglu,
1402 &self.scratch.swiglu_out,
1403 &mut self.scratch.gate_out,
1404 n_inter,
1405 stream,
1406 )?;
1407 gemm_backward_b(
1413 &self.scratch.norm2_out,
1414 &self.scratch.up_out,
1415 &mut grad_ws.grad_gate,
1416 saturating_u32(seq_len),
1417 saturating_u32(hidden_size),
1418 saturating_u32(intermediate_size),
1419 stream,
1420 )?;
1421
1422 gemm_backward_b(
1424 &self.scratch.norm2_out,
1425 &self.scratch.gate_out,
1426 &mut grad_ws.grad_up,
1427 saturating_u32(seq_len),
1428 saturating_u32(hidden_size),
1429 saturating_u32(intermediate_size),
1430 stream,
1431 )?;
1432
1433 gemm_backward_a(
1437 &self.scratch.up_out,
1438 &self.w_gate,
1439 &mut self.scratch.ffn_out,
1440 saturating_u32(seq_len),
1441 saturating_u32(hidden_size),
1442 saturating_u32(intermediate_size),
1443 stream,
1444 )?;
1445
1446 gemm_backward_a(
1448 &self.scratch.gate_out,
1449 &self.w_up,
1450 &mut self.scratch.grad_hidden,
1451 saturating_u32(seq_len),
1452 saturating_u32(hidden_size),
1453 saturating_u32(intermediate_size),
1454 stream,
1455 )?;
1456
1457 residual_add_forward(
1459 &self.scratch.ffn_out,
1460 &self.scratch.grad_hidden,
1461 &mut self.scratch.norm2_out,
1462 n_hidden,
1463 stream,
1464 )?;
1465
1466 Ok(())
1467 }
1468
1469 fn backward_post_attn_norm(
1471 &mut self,
1472 grad_input: &mut GpuBuffer<f32>,
1473 seq_len: usize,
1474 hidden_size: usize,
1475 eps: f32,
1476 stream: &CudaStream,
1477 grad_ws: &mut CudaGradWorkspace,
1478 ) -> Result<()> {
1479 unsafe {
1482 self.scratch
1483 .grad_hidden
1484 .copy_from_buffer_async(&self.scratch.norm2_out, stream)
1485 .map_err(|e| {
1486 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
1487 "Backward norm D2D copy failed: {e}"
1488 ))
1489 })?;
1490 }
1491
1492 rms_norm_backward(
1493 &self.scratch.residual1,
1494 &self.post_attn_norm_weight,
1495 &self.scratch.grad_hidden,
1496 grad_input,
1497 &mut grad_ws.grad_post_attn_norm,
1498 saturating_u32(seq_len),
1499 saturating_u32(hidden_size),
1500 eps,
1501 stream,
1502 )
1503 }
1504
1505 fn backward_attention(
1518 &mut self,
1519 grad_input: &mut GpuBuffer<f32>,
1520 seq_len: usize,
1521 stream: &CudaStream,
1522 grad_ws: &mut CudaGradWorkspace,
1523 ) -> Result<()> {
1524 let hidden_size = self.config.hidden_size;
1525 let q_dim = self.config.q_dim();
1526 let kv_hidden_size = self.config.num_kv_heads * self.config.head_dim();
1527 let num_heads = self.config.num_attention_heads;
1528 let num_kv_heads = self.config.num_kv_heads;
1529 let head_dim = self.config.head_dim();
1530 let heads_per_kv = num_heads / num_kv_heads;
1531 let scale = 1.0 / (head_dim as f32).sqrt();
1532
1533 let seq = saturating_u32(seq_len);
1534 let nh = saturating_u32(num_heads);
1535 let nkv = saturating_u32(num_kv_heads);
1536 let hd = saturating_u32(head_dim);
1537
1538 gemm_backward_a(
1543 grad_input,
1544 &self.w_o,
1545 &mut self.scratch.grad_hidden,
1546 seq,
1547 saturating_u32(q_dim),
1548 saturating_u32(hidden_size),
1549 stream,
1550 )?;
1551
1552 gemm_backward_b(
1554 &self.scratch.attn_out,
1555 grad_input,
1556 &mut grad_ws.grad_w_o,
1557 seq,
1558 saturating_u32(q_dim),
1559 saturating_u32(hidden_size),
1560 stream,
1561 )?;
1562
1563 interleaved_to_batched_forward(
1567 &self.scratch.grad_hidden,
1568 &mut self.scratch.attn_q_batched,
1569 seq,
1570 nh,
1571 hd,
1572 stream,
1573 )?;
1574
1575 interleaved_to_batched_forward(
1579 &self.scratch.v,
1580 &mut self.scratch.attn_kv_temp,
1581 seq,
1582 nkv,
1583 hd,
1584 stream,
1585 )?;
1586
1587 if heads_per_kv > 1 {
1589 expand_kv_heads(
1590 &self.scratch.attn_kv_temp,
1591 &mut self.scratch.attn_kv_temp2,
1592 num_kv_heads,
1593 heads_per_kv,
1594 seq_len * head_dim,
1595 stream,
1596 )?;
1597 unsafe {
1599 self.scratch
1600 .attn_kv_temp
1601 .copy_from_buffer_async(&self.scratch.attn_kv_temp2, stream)
1602 .map_err(|e| {
1603 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
1604 "Attn backward V expand D2D copy failed: {e}"
1605 ))
1606 })?;
1607 }
1608 }
1609 batched_transpose_forward(
1613 &self.scratch.attn_kv_temp,
1614 &mut self.scratch.attn_kv_temp2,
1615 nh,
1616 seq,
1617 hd,
1618 stream,
1619 )?;
1620 batched_4d_gemm_forward(
1624 &self.scratch.attn_q_batched,
1625 &self.scratch.attn_kv_temp2,
1626 &mut self.scratch.grad_attn_scores,
1627 1,
1628 nh,
1629 seq,
1630 seq,
1631 hd,
1632 stream,
1633 )?;
1634
1635 batched_transpose_forward(
1645 &self.scratch.attn_q_batched, &mut self.scratch.attn_kv_temp, nh,
1648 seq,
1649 hd,
1650 stream,
1651 )?;
1652
1653 batched_4d_gemm_forward(
1655 &self.scratch.attn_kv_temp, &self.scratch.attn_scores, &mut self.scratch.attn_kv_temp2, 1,
1659 nh,
1660 hd, seq, seq, stream,
1664 )?;
1665
1666 batched_transpose_forward(
1668 &self.scratch.attn_kv_temp2, &mut self.scratch.attn_kv_temp, nh,
1671 hd,
1672 seq,
1673 stream,
1674 )?;
1675 let total_rows = nh * seq;
1682 {
1683 let grad_scores_view = unsafe {
1687 GpuBuffer::<f32>::from_raw_parts(
1688 self.scratch.grad_attn_scores.as_ptr(),
1689 self.scratch.grad_attn_scores.len(),
1690 )
1691 };
1692 batched_softmax_backward(
1693 &self.scratch.attn_scores,
1694 &grad_scores_view,
1695 &mut self.scratch.grad_attn_scores,
1696 total_rows,
1697 seq,
1698 stream,
1699 )?;
1700 leak(grad_scores_view);
1701 }
1702 let total_scores = nh * seq * seq;
1707 {
1708 let scores_view = unsafe {
1710 GpuBuffer::<f32>::from_raw_parts(
1711 self.scratch.grad_attn_scores.as_ptr(),
1712 self.scratch.grad_attn_scores.len(),
1713 )
1714 };
1715 scale_forward(
1716 &scores_view,
1717 &mut self.scratch.grad_attn_scores,
1718 scale,
1719 total_scores,
1720 stream,
1721 )?;
1722 leak(scores_view);
1723 }
1724
1725 interleaved_to_batched_forward(
1729 &self.scratch.k,
1730 &mut self.scratch.attn_kv_temp2,
1731 seq,
1732 nkv,
1733 hd,
1734 stream,
1735 )?;
1736
1737 if heads_per_kv > 1 {
1738 unsafe {
1741 self.scratch
1742 .attn_q_batched
1743 .copy_from_buffer_async(&self.scratch.attn_kv_temp2, stream)
1744 .map_err(|e| {
1745 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
1746 "Attn backward K copy for GQA expand failed: {e}"
1747 ))
1748 })?;
1749 }
1750 expand_kv_heads(
1751 &self.scratch.attn_q_batched,
1752 &mut self.scratch.attn_kv_temp2,
1753 num_kv_heads,
1754 heads_per_kv,
1755 seq_len * head_dim,
1756 stream,
1757 )?;
1758 }
1759 batched_4d_gemm_forward(
1763 &self.scratch.grad_attn_scores,
1764 &self.scratch.attn_kv_temp2,
1765 &mut self.scratch.attn_q_batched,
1766 1,
1767 nh,
1768 seq,
1769 hd,
1770 seq,
1771 stream,
1772 )?;
1773
1774 interleaved_to_batched_forward(
1778 &self.scratch.q,
1779 &mut self.scratch.o_proj_out, seq,
1781 nh,
1782 hd,
1783 stream,
1784 )?;
1785
1786 batched_transpose_forward(
1788 &self.scratch.o_proj_out,
1789 &mut self.scratch.attn_kv_temp2, nh,
1791 seq,
1792 hd,
1793 stream,
1794 )?;
1795
1796 batched_4d_gemm_forward(
1798 &self.scratch.attn_kv_temp2,
1799 &self.scratch.grad_attn_scores,
1800 &mut self.scratch.ffn_out, 1,
1802 nh,
1803 hd,
1804 seq,
1805 seq,
1806 stream,
1807 )?;
1808
1809 batched_transpose_forward(
1811 &self.scratch.ffn_out,
1812 &mut self.scratch.attn_kv_temp2, nh,
1814 hd,
1815 seq,
1816 stream,
1817 )?;
1818
1819 if heads_per_kv > 1 {
1822 self.reduce_gqa_gradients(num_kv_heads, heads_per_kv, seq_len, head_dim, stream)?;
1823 }
1824
1825 batched_to_interleaved_forward(
1828 &self.scratch.attn_q_batched,
1829 &mut self.scratch.o_proj_out,
1830 seq,
1831 nh,
1832 hd,
1833 stream,
1834 )?;
1835
1836 batched_to_interleaved_forward(
1838 &self.scratch.attn_kv_temp2,
1839 &mut self.scratch.norm2_out,
1840 seq,
1841 nkv,
1842 hd,
1843 stream,
1844 )?;
1845
1846 batched_to_interleaved_forward(
1848 &self.scratch.attn_kv_temp,
1849 &mut self.scratch.ffn_out,
1850 seq,
1851 nkv,
1852 hd,
1853 stream,
1854 )?;
1855
1856 let rope_theta = self.config.rope_theta;
1861 {
1862 let q_ref = unsafe { &*(std::ptr::addr_of!(self.scratch.o_proj_out)) };
1865 batched_rope_neox_backward(
1866 q_ref,
1867 &mut self.scratch.o_proj_out,
1868 &self.scratch.rope_positions,
1869 nh,
1870 hd,
1871 seq,
1872 rope_theta,
1873 stream,
1874 )?;
1875 let k_ref = unsafe { &*(std::ptr::addr_of!(self.scratch.norm2_out)) };
1878 batched_rope_neox_backward(
1879 k_ref,
1880 &mut self.scratch.norm2_out,
1881 &self.scratch.rope_positions,
1882 nkv,
1883 hd,
1884 seq,
1885 rope_theta,
1886 stream,
1887 )?;
1888 }
1889
1890 gemm_backward_a(
1895 &self.scratch.o_proj_out, &self.w_q,
1897 &mut self.scratch.grad_hidden,
1898 seq,
1899 saturating_u32(hidden_size),
1900 saturating_u32(q_dim),
1901 stream,
1902 )?;
1903
1904 gemm_backward_a(
1909 &self.scratch.norm2_out, &self.w_k,
1911 &mut self.scratch.grad_attn_scores, seq,
1913 saturating_u32(hidden_size),
1914 saturating_u32(kv_hidden_size),
1915 stream,
1916 )?;
1917 cuda_add_inplace(
1918 &mut self.scratch.grad_hidden,
1919 &self.scratch.grad_attn_scores,
1920 seq_len * hidden_size,
1921 stream,
1922 )?;
1923
1924 gemm_backward_a(
1928 &self.scratch.ffn_out, &self.w_v,
1930 &mut self.scratch.grad_attn_scores, seq,
1932 saturating_u32(hidden_size),
1933 saturating_u32(kv_hidden_size),
1934 stream,
1935 )?;
1936 cuda_add_inplace(
1937 &mut self.scratch.grad_hidden,
1938 &self.scratch.grad_attn_scores,
1939 seq_len * hidden_size,
1940 stream,
1941 )?;
1942
1943 gemm_backward_b(
1945 &self.scratch.norm1_out,
1946 &self.scratch.o_proj_out, &mut grad_ws.grad_w_q,
1948 seq,
1949 saturating_u32(hidden_size),
1950 saturating_u32(q_dim),
1951 stream,
1952 )?;
1953
1954 gemm_backward_b(
1956 &self.scratch.norm1_out,
1957 &self.scratch.norm2_out, &mut grad_ws.grad_w_k,
1959 seq,
1960 saturating_u32(hidden_size),
1961 saturating_u32(kv_hidden_size),
1962 stream,
1963 )?;
1964
1965 gemm_backward_b(
1967 &self.scratch.norm1_out,
1968 &self.scratch.ffn_out, &mut grad_ws.grad_w_v,
1970 seq,
1971 saturating_u32(hidden_size),
1972 saturating_u32(kv_hidden_size),
1973 stream,
1974 )?;
1975
1976 unsafe {
1979 grad_input.copy_from_buffer_async(&self.scratch.grad_hidden, stream).map_err(|e| {
1980 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
1981 "Attn backward grad_hidden → grad_input D2D copy failed: {e}"
1982 ))
1983 })?;
1984 }
1985
1986 Ok(())
1987 }
1988
1989 fn reduce_gqa_gradients(
1997 &mut self,
1998 num_kv_heads: usize,
1999 heads_per_kv: usize,
2000 seq_len: usize,
2001 head_dim: usize,
2002 stream: &CudaStream,
2003 ) -> Result<()> {
2004 let elems_per_head = seq_len * head_dim;
2005
2006 self.reduce_single_gqa_gradient(true, num_kv_heads, heads_per_kv, elems_per_head, stream)?;
2008
2009 self.reduce_single_gqa_gradient(false, num_kv_heads, heads_per_kv, elems_per_head, stream)?;
2011
2012 let kv_elems = num_kv_heads * elems_per_head;
2014 unsafe {
2016 self.scratch
2017 .attn_kv_temp2
2018 .copy_from_buffer_at_async(&self.scratch.grad_attn_scores, 0, 0, kv_elems, stream)
2019 .map_err(|e| {
2020 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
2021 "GQA grad_K reduced final copy failed: {e}"
2022 ))
2023 })?;
2024 self.scratch
2025 .attn_kv_temp
2026 .copy_from_buffer_at_async(&self.scratch.ffn_out, 0, 0, kv_elems, stream)
2027 .map_err(|e| {
2028 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
2029 "GQA grad_V reduced final copy failed: {e}"
2030 ))
2031 })?;
2032 }
2033 Ok(())
2034 }
2035
2036 fn reduce_single_gqa_gradient(
2042 &mut self,
2043 is_k: bool,
2044 num_kv_heads: usize,
2045 heads_per_kv: usize,
2046 elems_per_head: usize,
2047 stream: &CudaStream,
2048 ) -> Result<()> {
2049 let label = if is_k { "K" } else { "V" };
2050
2051 for kv_h in 0..num_kv_heads {
2052 let dst_offset = kv_h * elems_per_head;
2053 let first_h = kv_h * heads_per_kv;
2054 let src_offset = first_h * elems_per_head;
2055
2056 unsafe {
2059 let (dst, src) = if is_k {
2060 (&mut self.scratch.grad_attn_scores, &self.scratch.attn_kv_temp2)
2061 } else {
2062 (&mut self.scratch.ffn_out, &self.scratch.attn_kv_temp)
2063 };
2064 dst.copy_from_buffer_at_async(src, dst_offset, src_offset, elems_per_head, stream)
2065 .map_err(|e| {
2066 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
2067 "GQA grad_{label} reduce base copy failed: {e}"
2068 ))
2069 })?;
2070 }
2071
2072 for rep in 1..heads_per_kv {
2074 let h = kv_h * heads_per_kv + rep;
2075 let h_offset = h * elems_per_head;
2076
2077 unsafe {
2080 let src =
2081 if is_k { &self.scratch.attn_kv_temp2 } else { &self.scratch.attn_kv_temp };
2082 self.scratch
2083 .o_proj_out
2084 .copy_from_buffer_at_async(src, 0, h_offset, elems_per_head, stream)
2085 .map_err(|e| {
2086 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
2087 "GQA grad_{label} reduce head copy failed: {e}"
2088 ))
2089 })?;
2090 }
2091
2092 unsafe {
2095 let dst_buf =
2096 if is_k { &self.scratch.grad_attn_scores } else { &self.scratch.ffn_out };
2097 let dst_view = GpuBuffer::<f32>::from_raw_parts(
2098 dst_buf.as_ptr() + (dst_offset as u64 * 4),
2099 elems_per_head,
2100 );
2101 let src_view = GpuBuffer::<f32>::from_raw_parts(
2102 self.scratch.o_proj_out.as_ptr(),
2103 elems_per_head,
2104 );
2105 let mut sum_view = GpuBuffer::<f32>::from_raw_parts(
2106 self.scratch.grad_hidden.as_ptr(),
2107 elems_per_head,
2108 );
2109 residual_add_forward(
2110 &dst_view,
2111 &src_view,
2112 &mut sum_view,
2113 saturating_u32(elems_per_head),
2114 stream,
2115 )?;
2116 let dst_buf = if is_k {
2118 &mut self.scratch.grad_attn_scores
2119 } else {
2120 &mut self.scratch.ffn_out
2121 };
2122 dst_buf
2123 .copy_from_buffer_at_async(
2124 &self.scratch.grad_hidden,
2125 dst_offset,
2126 0,
2127 elems_per_head,
2128 stream,
2129 )
2130 .map_err(|e| {
2131 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
2132 "GQA grad_{label} reduce sum copy failed: {e}"
2133 ))
2134 })?;
2135 leak(dst_view);
2136 leak(src_view);
2137 leak(sum_view);
2138 }
2139 }
2140 }
2141 Ok(())
2142 }
2143
2144 fn backward_residual_and_input_norm(
2146 &mut self,
2147 input: &GpuBuffer<f32>,
2148 grad_output: &GpuBuffer<f32>,
2149 grad_input: &mut GpuBuffer<f32>,
2150 seq_len: usize,
2151 hidden_size: usize,
2152 eps: f32,
2153 stream: &CudaStream,
2154 grad_ws: &mut CudaGradWorkspace,
2155 ) -> Result<()> {
2156 unsafe {
2167 self.scratch.grad_hidden.copy_from_buffer_async(grad_input, stream).map_err(|e| {
2168 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
2169 "Backward residual grad_hidden D2D copy failed: {e}"
2170 ))
2171 })?;
2172 }
2173
2174 rms_norm_backward(
2176 input,
2177 &self.input_norm_weight,
2178 &self.scratch.grad_hidden,
2179 grad_input,
2180 &mut grad_ws.grad_input_norm,
2181 saturating_u32(seq_len),
2182 saturating_u32(hidden_size),
2183 eps,
2184 stream,
2185 )?;
2186
2187 cuda_add_inplace(grad_input, grad_output, seq_len * hidden_size, stream)
2189 }
2190
2191 pub fn init_optimizer_state(&self) -> Result<GpuBlockOptimizerState> {
2203 let hidden = self.config.hidden_size;
2204 let q_dim = self.config.q_dim();
2205 let kv_hidden = self.config.num_kv_heads * self.config.head_dim();
2206 let intermediate = self.config.intermediate_size;
2207
2208 let z = |n: usize| -> Result<GpuBuffer<f32>> {
2212 Ok(GpuBuffer::from_host(&self.ctx, &vec![0.0f32; n])?)
2213 };
2214 Ok(GpuBlockOptimizerState {
2215 m_w_q: z(q_dim * hidden)?,
2216 v_w_q: z(q_dim * hidden)?,
2217 m_w_k: z(hidden * kv_hidden)?,
2218 v_w_k: z(hidden * kv_hidden)?,
2219 m_w_v: z(hidden * kv_hidden)?,
2220 v_w_v: z(hidden * kv_hidden)?,
2221 m_w_o: z(hidden * q_dim)?,
2222 v_w_o: z(hidden * q_dim)?,
2223 m_w_gate: z(hidden * intermediate)?,
2224 v_w_gate: z(hidden * intermediate)?,
2225 m_w_up: z(hidden * intermediate)?,
2226 v_w_up: z(hidden * intermediate)?,
2227 m_w_down: z(intermediate * hidden)?,
2228 v_w_down: z(intermediate * hidden)?,
2229 m_input_norm: z(hidden)?,
2230 v_input_norm: z(hidden)?,
2231 m_post_attn_norm: z(hidden)?,
2232 v_post_attn_norm: z(hidden)?,
2233 })
2234 }
2235
2236 pub fn optimizer_step(
2249 &mut self,
2250 state: &mut GpuBlockOptimizerState,
2251 step: u32,
2252 lr: f32,
2253 beta1: f32,
2254 beta2: f32,
2255 eps: f32,
2256 weight_decay: f32,
2257 stream: &CudaStream,
2258 grad_ws: &CudaGradWorkspace,
2259 ) -> Result<()> {
2260 debug_assert!(step > 0, "C-OPTSTEP-001: step must be > 0 for bias adjust");
2261
2262 let n_wq = self.w_q.len() as u32;
2265 let n_wk = self.w_k.len() as u32;
2266 let n_wv = self.w_v.len() as u32;
2267 let n_wo = self.w_o.len() as u32;
2268 let n_gate = self.w_gate.len() as u32;
2269 let n_up = self.w_up.len() as u32;
2270 let n_down = self.w_down.len() as u32;
2271 let n_inorm = self.input_norm_weight.len() as u32;
2272 let n_panorm = self.post_attn_norm_weight.len() as u32;
2273
2274 adamw_step_cuda(
2276 &mut self.w_q,
2277 &grad_ws.grad_w_q,
2278 &mut state.m_w_q,
2279 &mut state.v_w_q,
2280 lr,
2281 beta1,
2282 beta2,
2283 eps,
2284 weight_decay,
2285 step,
2286 n_wq,
2287 stream,
2288 )?;
2289 adamw_step_cuda(
2290 &mut self.w_k,
2291 &grad_ws.grad_w_k,
2292 &mut state.m_w_k,
2293 &mut state.v_w_k,
2294 lr,
2295 beta1,
2296 beta2,
2297 eps,
2298 weight_decay,
2299 step,
2300 n_wk,
2301 stream,
2302 )?;
2303 adamw_step_cuda(
2304 &mut self.w_v,
2305 &grad_ws.grad_w_v,
2306 &mut state.m_w_v,
2307 &mut state.v_w_v,
2308 lr,
2309 beta1,
2310 beta2,
2311 eps,
2312 weight_decay,
2313 step,
2314 n_wv,
2315 stream,
2316 )?;
2317 adamw_step_cuda(
2318 &mut self.w_o,
2319 &grad_ws.grad_w_o,
2320 &mut state.m_w_o,
2321 &mut state.v_w_o,
2322 lr,
2323 beta1,
2324 beta2,
2325 eps,
2326 weight_decay,
2327 step,
2328 n_wo,
2329 stream,
2330 )?;
2331
2332 adamw_step_cuda(
2334 &mut self.w_gate,
2335 &grad_ws.grad_gate,
2336 &mut state.m_w_gate,
2337 &mut state.v_w_gate,
2338 lr,
2339 beta1,
2340 beta2,
2341 eps,
2342 weight_decay,
2343 step,
2344 n_gate,
2345 stream,
2346 )?;
2347 adamw_step_cuda(
2348 &mut self.w_up,
2349 &grad_ws.grad_up,
2350 &mut state.m_w_up,
2351 &mut state.v_w_up,
2352 lr,
2353 beta1,
2354 beta2,
2355 eps,
2356 weight_decay,
2357 step,
2358 n_up,
2359 stream,
2360 )?;
2361 adamw_step_cuda(
2362 &mut self.w_down,
2363 &grad_ws.grad_down,
2364 &mut state.m_w_down,
2365 &mut state.v_w_down,
2366 lr,
2367 beta1,
2368 beta2,
2369 eps,
2370 weight_decay,
2371 step,
2372 n_down,
2373 stream,
2374 )?;
2375
2376 adamw_step_cuda(
2378 &mut self.input_norm_weight,
2379 &grad_ws.grad_input_norm,
2380 &mut state.m_input_norm,
2381 &mut state.v_input_norm,
2382 lr,
2383 beta1,
2384 beta2,
2385 eps,
2386 weight_decay,
2387 step,
2388 n_inorm,
2389 stream,
2390 )?;
2391 adamw_step_cuda(
2392 &mut self.post_attn_norm_weight,
2393 &grad_ws.grad_post_attn_norm,
2394 &mut state.m_post_attn_norm,
2395 &mut state.v_post_attn_norm,
2396 lr,
2397 beta1,
2398 beta2,
2399 eps,
2400 weight_decay,
2401 step,
2402 n_panorm,
2403 stream,
2404 )?;
2405
2406 Ok(())
2407 }
2408
2409 pub fn download_weights(&self) -> Result<BlockWeights> {
2419 let download = |buf: &GpuBuffer<f32>| -> Result<Vec<f32>> {
2420 let mut host = vec![0.0f32; buf.len()];
2421 buf.copy_to_host(&mut host).map_err(|e| {
2422 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
2423 "Weight download failed: {e}"
2424 ))
2425 })?;
2426 Ok(host)
2427 };
2428
2429 Ok(BlockWeights {
2430 w_q: download(&self.w_q)?,
2431 w_k: download(&self.w_k)?,
2432 w_v: download(&self.w_v)?,
2433 w_o: download(&self.w_o)?,
2434 w_gate: download(&self.w_gate)?,
2435 w_up: download(&self.w_up)?,
2436 w_down: download(&self.w_down)?,
2437 input_norm_weight: download(&self.input_norm_weight)?,
2438 post_attn_norm_weight: download(&self.post_attn_norm_weight)?,
2439 })
2440 }
2441}
2442
2443#[cfg(feature = "cuda")]
2449pub struct BlockWeights {
2450 pub w_q: Vec<f32>,
2451 pub w_k: Vec<f32>,
2452 pub w_v: Vec<f32>,
2453 pub w_o: Vec<f32>,
2454 pub w_gate: Vec<f32>,
2455 pub w_up: Vec<f32>,
2456 pub w_down: Vec<f32>,
2457 pub input_norm_weight: Vec<f32>,
2458 pub post_attn_norm_weight: Vec<f32>,
2459}
2460
2461#[cfg(feature = "cuda")]
2465fn cuda_add(
2466 a: &GpuBuffer<f32>,
2467 b: &GpuBuffer<f32>,
2468 output: &mut GpuBuffer<f32>,
2469 n: usize,
2470 stream: &CudaStream,
2471) -> Result<()> {
2472 residual_add_forward(a, b, output, saturating_u32(n), stream)
2473}
2474
2475#[cfg(feature = "cuda")]
2483pub(crate) fn cuda_add_inplace(
2484 target: &mut GpuBuffer<f32>,
2485 source: &GpuBuffer<f32>,
2486 n: usize,
2487 stream: &CudaStream,
2488) -> Result<()> {
2489 let target_ref: &GpuBuffer<f32> = unsafe { &*std::ptr::from_ref::<GpuBuffer<f32>>(target) };
2493 residual_add_forward(target_ref, source, target, saturating_u32(n), stream)
2494}
2495
2496#[cfg(feature = "cuda")]
2500fn cuda_mul(
2501 a: &GpuBuffer<f32>,
2502 b: &GpuBuffer<f32>,
2503 output: &mut GpuBuffer<f32>,
2504 n: usize,
2505 stream: &CudaStream,
2506) -> Result<()> {
2507 crate::autograd::cuda_forward::elementwise_mul_forward(a, b, output, saturating_u32(n), stream)
2508}
2509
2510#[cfg(not(feature = "cuda"))]
2512pub struct CudaTransformerBlock;
2513
2514#[cfg(not(feature = "cuda"))]
2515impl CudaTransformerBlock {
2516 pub fn layer_idx(&self) -> usize {
2517 0
2518 }
2519}
2520
2521#[cfg(feature = "cuda")]
2530pub enum CudaBlock {
2531 Fp32(CudaTransformerBlock),
2533 Nf4(CudaNf4TransformerBlock),
2535}
2536
2537#[cfg(feature = "cuda")]
2538impl CudaBlock {
2539 pub(crate) fn forward(
2544 &mut self,
2545 input: &GpuBuffer<f32>,
2546 output: &mut GpuBuffer<f32>,
2547 seq_len: usize,
2548 stream: &CudaStream,
2549 shared_scratch: Option<&mut CudaBlockScratch>,
2550 ) -> Result<()> {
2551 match self {
2552 CudaBlock::Fp32(b) => b.forward(input, output, seq_len, stream),
2553 CudaBlock::Nf4(b) => {
2554 let scratch =
2555 shared_scratch.expect("C-SCRATCH-001: NF4 blocks require shared scratch");
2556 b.forward(input, output, seq_len, stream, scratch)
2557 }
2558 }
2559 }
2560
2561 pub fn layer_idx(&self) -> usize {
2563 match self {
2564 CudaBlock::Fp32(b) => b.layer_idx(),
2565 CudaBlock::Nf4(b) => b.layer_idx,
2566 }
2567 }
2568
2569 pub fn backward(
2574 &mut self,
2575 input: &GpuBuffer<f32>,
2576 grad_output: &GpuBuffer<f32>,
2577 grad_input: &mut GpuBuffer<f32>,
2578 seq_len: usize,
2579 stream: &CudaStream,
2580 grad_ws: &mut CudaGradWorkspace,
2581 ) -> Result<()> {
2582 match self {
2583 CudaBlock::Fp32(b) => {
2584 b.backward(input, grad_output, grad_input, seq_len, stream, grad_ws)
2585 }
2586 CudaBlock::Nf4(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2587 "backward not supported on NF4 blocks (frozen weights)".into(),
2588 )),
2589 }
2590 }
2591
2592 pub fn init_optimizer_state(&self) -> Result<GpuBlockOptimizerState> {
2594 match self {
2595 CudaBlock::Fp32(b) => b.init_optimizer_state(),
2596 CudaBlock::Nf4(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2597 "init_optimizer_state not supported on NF4 blocks".into(),
2598 )),
2599 }
2600 }
2601
2602 pub fn download_weights(&self) -> Result<BlockWeights> {
2604 match self {
2605 CudaBlock::Fp32(b) => b.download_weights(),
2606 CudaBlock::Nf4(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2607 "download_weights not supported on NF4 blocks".into(),
2608 )),
2609 }
2610 }
2611
2612 pub fn optimizer_step(
2614 &mut self,
2615 state: &mut GpuBlockOptimizerState,
2616 step: u32,
2617 lr: f32,
2618 beta1: f32,
2619 beta2: f32,
2620 eps: f32,
2621 weight_decay: f32,
2622 stream: &CudaStream,
2623 grad_ws: &CudaGradWorkspace,
2624 ) -> Result<()> {
2625 match self {
2626 CudaBlock::Fp32(b) => {
2627 b.optimizer_step(state, step, lr, beta1, beta2, eps, weight_decay, stream, grad_ws)
2628 }
2629 CudaBlock::Nf4(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2630 "optimizer_step not supported on NF4 blocks (frozen weights)".into(),
2631 )),
2632 }
2633 }
2634
2635 #[allow(clippy::too_many_arguments)]
2639 pub(crate) fn backward_nf4(
2640 &self,
2641 layer_input: &GpuBuffer<f32>,
2642 grad_output: &GpuBuffer<f32>,
2643 grad_input: &mut GpuBuffer<f32>,
2644 output_scratch: &mut GpuBuffer<f32>,
2645 seq_len: usize,
2646 stream: &CudaStream,
2647 shared_scratch: &mut CudaBlockScratch,
2648 grad_lora: &mut CudaLoraGradWorkspace,
2649 ) -> Result<()> {
2650 match self {
2651 CudaBlock::Nf4(b) => b.backward(
2652 layer_input,
2653 grad_output,
2654 grad_input,
2655 output_scratch,
2656 seq_len,
2657 stream,
2658 shared_scratch,
2659 grad_lora,
2660 ),
2661 CudaBlock::Fp32(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2662 "backward_nf4 only supported on NF4 blocks".into(),
2663 )),
2664 }
2665 }
2666
2667 pub(crate) fn init_lora_optimizer_state(&self) -> Result<GpuLoraOptimizerState> {
2669 match self {
2670 CudaBlock::Nf4(b) => b.init_lora_optimizer_state(),
2671 CudaBlock::Fp32(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2672 "init_lora_optimizer_state only supported on NF4 blocks".into(),
2673 )),
2674 }
2675 }
2676
2677 #[allow(clippy::too_many_arguments)]
2679 pub(crate) fn lora_optimizer_step(
2680 &mut self,
2681 state: &mut GpuLoraOptimizerState,
2682 step: u32,
2683 lr: f32,
2684 beta1: f32,
2685 beta2: f32,
2686 eps: f32,
2687 weight_decay: f32,
2688 stream: &CudaStream,
2689 grad_lora: &CudaLoraGradWorkspace,
2690 ) -> Result<()> {
2691 match self {
2692 CudaBlock::Nf4(b) => b.lora_optimizer_step(
2693 state,
2694 step,
2695 lr,
2696 beta1,
2697 beta2,
2698 eps,
2699 weight_decay,
2700 stream,
2701 grad_lora,
2702 ),
2703 CudaBlock::Fp32(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2704 "lora_optimizer_step only supported on NF4 blocks".into(),
2705 )),
2706 }
2707 }
2708
2709 pub fn download_lora_weights(&self) -> Result<(Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>)> {
2711 match self {
2712 CudaBlock::Nf4(b) => b.download_lora_weights(),
2713 CudaBlock::Fp32(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2714 "download_lora_weights only supported on NF4 blocks".into(),
2715 )),
2716 }
2717 }
2718
2719 pub fn upload_lora_weights(
2721 &mut self,
2722 a_q: &[f32],
2723 b_q: &[f32],
2724 a_v: &[f32],
2725 b_v: &[f32],
2726 ) -> Result<()> {
2727 match self {
2728 CudaBlock::Nf4(b) => b.upload_lora_weights(a_q, b_q, a_v, b_v),
2729 CudaBlock::Fp32(_) => Err(crate::autograd::cuda_tensor::CudaTensorError::KernelError(
2730 "upload_lora_weights only supported on NF4 blocks".into(),
2731 )),
2732 }
2733 }
2734}
2735
2736#[cfg(not(feature = "cuda"))]
2738pub enum CudaBlock {
2739 Fp32(CudaTransformerBlock),
2740}
2741
2742#[cfg(feature = "cuda")]
2763pub struct CudaNf4TransformerBlock {
2764 config: TransformerConfig,
2765 layer_idx: usize,
2766 input_norm_weight: GpuBuffer<f32>,
2768 post_attn_norm_weight: GpuBuffer<f32>,
2769 w_q_nf4: GpuBuffer<u8>,
2771 w_q_scales: GpuBuffer<f32>,
2772 w_k_nf4: GpuBuffer<u8>,
2773 w_k_scales: GpuBuffer<f32>,
2774 w_v_nf4: GpuBuffer<u8>,
2775 w_v_scales: GpuBuffer<f32>,
2776 w_o_nf4: GpuBuffer<u8>,
2777 w_o_scales: GpuBuffer<f32>,
2778 w_gate_nf4: GpuBuffer<u8>,
2779 w_gate_scales: GpuBuffer<f32>,
2780 w_up_nf4: GpuBuffer<u8>,
2781 w_up_scales: GpuBuffer<f32>,
2782 w_down_nf4: GpuBuffer<u8>,
2783 w_down_scales: GpuBuffer<f32>,
2784 w_q_fp32: GpuBuffer<f32>,
2786 w_k_fp32: GpuBuffer<f32>,
2787 w_v_fp32: GpuBuffer<f32>,
2788 w_o_fp32: GpuBuffer<f32>,
2789 w_gate_fp32: GpuBuffer<f32>,
2790 w_up_fp32: GpuBuffer<f32>,
2791 w_down_fp32: GpuBuffer<f32>,
2792 lora_a_q: Option<GpuBuffer<f32>>, lora_b_q: Option<GpuBuffer<f32>>, lora_a_v: Option<GpuBuffer<f32>>, lora_b_v: Option<GpuBuffer<f32>>, lora_scale: f32,
2799 lora_rank: usize,
2800 q_norm_weight: Option<GpuBuffer<f32>>,
2802 k_norm_weight: Option<GpuBuffer<f32>>,
2803 b_q_replicated: Option<GpuBuffer<f32>>,
2809 b_k_replicated: Option<GpuBuffer<f32>>,
2810 b_v_replicated: Option<GpuBuffer<f32>>,
2811 w_q_fp16: Option<GpuBuffer<u16>>,
2814 w_k_fp16: Option<GpuBuffer<u16>>,
2815 w_v_fp16: Option<GpuBuffer<u16>>,
2816 w_o_fp16: Option<GpuBuffer<u16>>,
2817 w_gate_fp16: Option<GpuBuffer<u16>>,
2818 w_up_fp16: Option<GpuBuffer<u16>>,
2819 w_down_fp16: Option<GpuBuffer<u16>>,
2820 ctx: Arc<CudaContext>,
2821 }
2823
2824#[cfg(feature = "cuda")]
2825impl CudaNf4TransformerBlock {
2826 #[allow(clippy::too_many_arguments)]
2831 pub fn new(
2832 config: &TransformerConfig,
2833 layer_idx: usize,
2834 ctx: Arc<CudaContext>,
2835 input_norm_weight: &[f32],
2836 post_attn_norm_weight: &[f32],
2837 w_q: &[f32],
2838 w_k: &[f32],
2839 w_v: &[f32],
2840 w_o: &[f32],
2841 w_gate: &[f32],
2842 w_up: &[f32],
2843 w_down: &[f32],
2844 max_seq_len: usize, q_lora: Option<(&[f32], &[f32])>,
2847 v_lora: Option<(&[f32], &[f32])>,
2848 lora_scale: f32,
2849 lora_rank: usize,
2850 q_norm: Option<&[f32]>,
2852 k_norm: Option<&[f32]>,
2853 b_q: Option<&[f32]>,
2856 b_k: Option<&[f32]>,
2857 b_v: Option<&[f32]>,
2858 ) -> Result<Self> {
2859 use trueno_gpu::kernels::{quantize_nf4, NF4_BLOCK_SIZE};
2860
2861 let hidden_size = config.hidden_size;
2862 let q_dim = config.q_dim(); let kv_hidden_size = config.num_kv_heads * config.head_dim();
2864 let intermediate_size = config.intermediate_size;
2865
2866 assert_eq!(
2871 w_q.len(),
2872 q_dim * hidden_size,
2873 "C-NF4SHAPE-001: w_q expected {}, got {} (q_dim={q_dim}, hidden={hidden_size})",
2874 q_dim * hidden_size,
2875 w_q.len()
2876 );
2877 assert_eq!(
2878 w_k.len(),
2879 kv_hidden_size * hidden_size,
2880 "C-NF4SHAPE-001: w_k expected {}, got {}",
2881 kv_hidden_size * hidden_size,
2882 w_k.len()
2883 );
2884 assert_eq!(
2885 w_v.len(),
2886 kv_hidden_size * hidden_size,
2887 "C-NF4SHAPE-001: w_v expected {}, got {}",
2888 kv_hidden_size * hidden_size,
2889 w_v.len()
2890 );
2891 assert_eq!(
2892 w_o.len(),
2893 hidden_size * q_dim,
2894 "C-NF4SHAPE-001: w_o expected {}, got {}",
2895 hidden_size * q_dim,
2896 w_o.len()
2897 );
2898 assert_eq!(
2899 w_gate.len(),
2900 intermediate_size * hidden_size,
2901 "C-NF4SHAPE-001: w_gate expected {}, got {}",
2902 intermediate_size * hidden_size,
2903 w_gate.len()
2904 );
2905 assert_eq!(
2906 w_up.len(),
2907 intermediate_size * hidden_size,
2908 "C-NF4SHAPE-001: w_up expected {}, got {}",
2909 intermediate_size * hidden_size,
2910 w_up.len()
2911 );
2912 assert_eq!(
2913 w_down.len(),
2914 hidden_size * intermediate_size,
2915 "C-NF4SHAPE-001: w_down expected {}, got {}",
2916 hidden_size * intermediate_size,
2917 w_down.len()
2918 );
2919
2920 let input_norm_weight = GpuBuffer::from_host(&ctx, input_norm_weight)?;
2922 let post_attn_norm_weight = GpuBuffer::from_host(&ctx, post_attn_norm_weight)?;
2923
2924 let quantize_and_upload = |weights: &[f32],
2928 total: usize|
2929 -> Result<(
2930 GpuBuffer<u8>,
2931 GpuBuffer<f32>,
2932 trueno_gpu::kernels::Nf4Quantized,
2933 )> {
2934 assert_eq!(weights.len(), total, "weight length mismatch");
2935 assert!(
2936 total.is_multiple_of(NF4_BLOCK_SIZE),
2937 "weight count {total} not divisible by NF4 block size {NF4_BLOCK_SIZE}"
2938 );
2939
2940 let q = quantize_nf4(weights, total / NF4_BLOCK_SIZE, NF4_BLOCK_SIZE);
2941 let nf4_buf = GpuBuffer::from_host(&ctx, &q.data)?;
2942 let scales_buf = GpuBuffer::from_host(&ctx, &q.scales)?;
2943 Ok((nf4_buf, scales_buf, q))
2944 };
2945
2946 let (w_q_nf4, w_q_scales, w_q_nf4_q) = quantize_and_upload(w_q, q_dim * hidden_size)?;
2948 let (w_k_nf4, w_k_scales, w_k_nf4_q) =
2949 quantize_and_upload(w_k, kv_hidden_size * hidden_size)?;
2950 let (w_v_nf4, w_v_scales, w_v_nf4_q) =
2951 quantize_and_upload(w_v, kv_hidden_size * hidden_size)?;
2952 let (w_o_nf4, w_o_scales, w_o_nf4_q) = quantize_and_upload(w_o, hidden_size * q_dim)?;
2953 let (w_gate_nf4, w_gate_scales, w_gate_nf4_q) =
2954 quantize_and_upload(w_gate, intermediate_size * hidden_size)?;
2955 let (w_up_nf4, w_up_scales, w_up_nf4_q) =
2956 quantize_and_upload(w_up, intermediate_size * hidden_size)?;
2957 let (w_down_nf4, w_down_scales, w_down_nf4_q) =
2958 quantize_and_upload(w_down, hidden_size * intermediate_size)?;
2959
2960 use trueno_gpu::kernels::dequantize_nf4;
2972 let dequant_transpose_upload = |q: &trueno_gpu::kernels::Nf4Quantized,
2973 n: usize,
2974 k: usize|
2975 -> std::result::Result<
2976 GpuBuffer<f32>,
2977 crate::autograd::cuda_tensor::CudaTensorError,
2978 > {
2979 let deq = dequantize_nf4(q); let nonzero = deq.iter().filter(|&&x| x != 0.0).count();
2981 eprintln!(
2982 "[TRACE] dequant n={n} k={k} len={} nonzero={nonzero} first5={:?}",
2983 deq.len(),
2984 &deq[..5.min(deq.len())]
2985 );
2986 assert_eq!(deq.len(), n * k, "dequant size mismatch: {} vs {}x{}", deq.len(), n, k);
2987 let mut transposed = vec![0.0f32; n * k];
2989 for row in 0..n {
2990 for col in 0..k {
2991 transposed[col * n + row] = deq[row * k + col];
2992 }
2993 }
2994 let buf = GpuBuffer::from_host(&ctx, &transposed).map_err(|e| {
2995 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
2996 "dequant transpose upload: {e:?}"
2997 ))
2998 })?;
2999 let mut verify_full = vec![0.0f32; buf.len()];
3001 let verify_ok = buf.copy_to_host(&mut verify_full).is_ok();
3002 let verify5: Vec<f32> = verify_full.iter().copied().take(5).collect();
3003 let nz = verify_full.iter().filter(|&&x| x != 0.0).count();
3004 eprintln!("[TRACE] uploaded ptr={:?} len={} copy_ok={verify_ok} nonzero={nz} verify[:5]={verify5:?}", buf.as_ptr(), buf.len());
3005 Ok(buf)
3006 };
3007 let w_q_fp32 = dequant_transpose_upload(&w_q_nf4_q, q_dim, hidden_size)?;
3010 let w_k_fp32 = dequant_transpose_upload(&w_k_nf4_q, kv_hidden_size, hidden_size)?;
3011 let w_v_fp32 = dequant_transpose_upload(&w_v_nf4_q, kv_hidden_size, hidden_size)?;
3012 let w_o_fp32 = dequant_transpose_upload(&w_o_nf4_q, hidden_size, q_dim)?;
3013 let w_gate_fp32 = dequant_transpose_upload(&w_gate_nf4_q, intermediate_size, hidden_size)?;
3014 let w_up_fp32 = dequant_transpose_upload(&w_up_nf4_q, intermediate_size, hidden_size)?;
3015 let w_down_fp32 = dequant_transpose_upload(&w_down_nf4_q, hidden_size, intermediate_size)?;
3016
3017 let (lora_a_q, lora_b_q) = match q_lora {
3024 Some((a_data, b_data)) => {
3025 let a = GpuBuffer::from_host(&ctx, a_data)?;
3026 let scaled_b: Vec<f32> = b_data.iter().map(|&v| v * lora_scale).collect();
3027 let b = GpuBuffer::from_host(&ctx, &scaled_b)?;
3028 (Some(a), Some(b))
3029 }
3030 None => (None, None),
3031 };
3032 let (lora_a_v, lora_b_v) = match v_lora {
3033 Some((a_data, b_data)) => {
3034 let a = GpuBuffer::from_host(&ctx, a_data)?;
3035 let scaled_b: Vec<f32> = b_data.iter().map(|&v| v * lora_scale).collect();
3036 let b = GpuBuffer::from_host(&ctx, &scaled_b)?;
3037 (Some(a), Some(b))
3038 }
3039 None => (None, None),
3040 };
3041
3042 let q_norm_weight = match q_norm {
3044 Some(w) => {
3045 assert_eq!(
3046 w.len(),
3047 config.head_dim(),
3048 "ENT-270: q_norm weight expected [head_dim={}], got [{}]",
3049 config.head_dim(),
3050 w.len()
3051 );
3052 Some(GpuBuffer::from_host(&ctx, w)?)
3053 }
3054 None => None,
3055 };
3056 let k_norm_weight = match k_norm {
3057 Some(w) => {
3058 assert_eq!(
3059 w.len(),
3060 config.head_dim(),
3061 "ENT-270: k_norm weight expected [head_dim={}], got [{}]",
3062 config.head_dim(),
3063 w.len()
3064 );
3065 Some(GpuBuffer::from_host(&ctx, w)?)
3066 }
3067 None => None,
3068 };
3069
3070 let replicate = |bias: Option<&[f32]>, dim: usize| -> Result<Option<GpuBuffer<f32>>> {
3075 match bias {
3076 Some(slice) => {
3077 assert_eq!(
3078 slice.len(),
3079 dim,
3080 "NF4 bias slice len {} != expected dim {dim}",
3081 slice.len()
3082 );
3083 let mut repl: Vec<f32> = Vec::with_capacity(max_seq_len * dim);
3084 for _ in 0..max_seq_len {
3085 repl.extend_from_slice(slice);
3086 }
3087 Ok(Some(GpuBuffer::from_host(&ctx, &repl)?))
3088 }
3089 None => Ok(None),
3090 }
3091 };
3092 let b_q_replicated = replicate(b_q, q_dim)?;
3093 let b_k_replicated = replicate(b_k, kv_hidden_size)?;
3094 let b_v_replicated = replicate(b_v, kv_hidden_size)?;
3095
3096 Ok(Self {
3097 config: config.clone(),
3098 layer_idx,
3099 input_norm_weight,
3100 post_attn_norm_weight,
3101 w_q_nf4,
3102 w_q_scales,
3103 w_k_nf4,
3104 w_k_scales,
3105 w_v_nf4,
3106 w_v_scales,
3107 w_o_nf4,
3108 w_o_scales,
3109 w_gate_nf4,
3110 w_gate_scales,
3111 w_up_nf4,
3112 w_up_scales,
3113 w_down_nf4,
3114 w_down_scales,
3115 w_q_fp32,
3116 w_k_fp32,
3117 w_v_fp32,
3118 w_o_fp32,
3119 w_gate_fp32,
3120 w_up_fp32,
3121 w_down_fp32,
3122 lora_a_q,
3123 lora_b_q,
3124 lora_a_v,
3125 lora_b_v,
3126 lora_scale,
3127 lora_rank,
3128 q_norm_weight,
3129 k_norm_weight,
3130 b_q_replicated,
3131 b_k_replicated,
3132 b_v_replicated,
3133 w_q_fp16: None,
3135 w_k_fp16: None,
3136 w_v_fp16: None,
3137 w_o_fp16: None,
3138 w_gate_fp16: None,
3139 w_up_fp16: None,
3140 w_down_fp16: None,
3141 ctx,
3142 })
3143 }
3144
3145 pub fn set_fp16_weights(&mut self, stream: &CudaStream) -> Result<()> {
3147 let cast_weight = |w_fp32: &GpuBuffer<f32>, ctx: &CudaContext| -> Result<GpuBuffer<u16>> {
3148 let n = w_fp32.len();
3149 let mut w_fp16 = GpuBuffer::<u16>::new(ctx, n)?;
3150 cast_f32_to_f16_gpu(w_fp32, &mut w_fp16, n as u32, stream)?;
3151 Ok(w_fp16)
3152 };
3153
3154 self.w_q_fp16 = Some(cast_weight(&self.w_q_fp32, &self.ctx)?);
3155 self.w_k_fp16 = Some(cast_weight(&self.w_k_fp32, &self.ctx)?);
3156 self.w_v_fp16 = Some(cast_weight(&self.w_v_fp32, &self.ctx)?);
3157 self.w_o_fp16 = Some(cast_weight(&self.w_o_fp32, &self.ctx)?);
3158 self.w_gate_fp16 = Some(cast_weight(&self.w_gate_fp32, &self.ctx)?);
3159 self.w_up_fp16 = Some(cast_weight(&self.w_up_fp32, &self.ctx)?);
3160 self.w_down_fp16 = Some(cast_weight(&self.w_down_fp32, &self.ctx)?);
3161
3162 stream.synchronize().map_err(|e| {
3163 crate::autograd::cuda_tensor::CudaTensorError::KernelError(format!(
3164 "FP16 weight cast sync failed: {e:?}"
3165 ))
3166 })?;
3167 let dummy = |ctx: &CudaContext| GpuBuffer::<f32>::new(ctx, 1).unwrap();
3170 self.w_q_fp32 = dummy(&self.ctx);
3171 self.w_k_fp32 = dummy(&self.ctx);
3172 self.w_v_fp32 = dummy(&self.ctx);
3173 self.w_o_fp32 = dummy(&self.ctx);
3174 self.w_gate_fp32 = dummy(&self.ctx);
3175 self.w_up_fp32 = dummy(&self.ctx);
3176 self.w_down_fp32 = dummy(&self.ctx);
3177 eprintln!("[FP16] Weights cast + fp32 dropped (~2.6 GB freed)");
3178
3179 Ok(())
3180 }
3181
3182 #[rustfmt::skip]
3184 pub(crate) fn forward(
3185 &self,
3186 input: &GpuBuffer<f32>,
3187 output: &mut GpuBuffer<f32>,
3188 seq_len: usize,
3189 stream: &CudaStream,
3190 scratch: &mut CudaBlockScratch,
3191 ) -> Result<()> {
3192 use crate::autograd::cuda_forward::{gemm_forward, gemm_nf4_forward, gemm_nf4_tc_forward};
3193
3194 let hidden_size = self.config.hidden_size;
3195 let q_dim = self.config.q_dim();
3196 let kv_hidden_size = self.config.num_kv_heads * self.config.head_dim();
3197 let intermediate_size = self.config.intermediate_size;
3198
3199 scratch.prepare_causal_mask(seq_len, &self.ctx)?;
3201
3202 let _t = scratch.op_begin();
3206 rms_norm_forward_with_eps(
3207 input,
3208 &self.input_norm_weight,
3209 &mut scratch.norm1_out,
3210 saturating_u32(seq_len),
3211 saturating_u32(hidden_size),
3212 self.config.rms_norm_eps,
3213 stream,
3214 )?;
3215 scratch.op_end(_t, OP_RMSNORM_ATTN);
3216
3217 if nan_scan_enabled() {
3218 let l = self.layer_idx;
3219 nan_scan_f32(&format!("L{l} input"), input, seq_len * hidden_size, stream);
3220 nan_scan_f32(&format!("L{l} norm1"), &scratch.norm1_out, seq_len * hidden_size, stream);
3221 }
3222
3223 static USE_NF4_GEMM: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3229 let nf4_gemm = *USE_NF4_GEMM.get_or_init(|| std::env::var("NF4_FUSED_GEMM").as_deref() == Ok("1"));
3230 static USE_NF4_TC_GEMM: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3231 let nf4_tc_gemm = *USE_NF4_TC_GEMM.get_or_init(|| std::env::var("NF4_TC_GEMM").as_deref() == Ok("1"));
3232 static USE_FP16_GEMM: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
3233 let fp16_gemm = *USE_FP16_GEMM.get_or_init(|| std::env::var("FP16_GEMM").as_deref() == Ok("1"));
3234
3235 let act_n = (seq_len * hidden_size) as u32;
3237 if fp16_gemm && self.w_q_fp16.is_some() {
3238 if scratch.norm1_out_f16.is_none() {
3240 scratch.norm1_out_f16 = Some(GpuBuffer::new(&self.ctx, seq_len * hidden_size)?);
3241 }
3242 let f16_buf = scratch.norm1_out_f16.as_mut().unwrap();
3243 cast_f32_to_f16_gpu(&scratch.norm1_out, f16_buf, act_n, stream)?;
3244 }
3245
3246 let _t = scratch.op_begin(); if fp16_gemm && self.w_q_fp16.is_some() {
3248 let f16_act = scratch.norm1_out_f16.as_ref().unwrap();
3249 gemm_f16_to_f32_forward(f16_act, self.w_q_fp16.as_ref().unwrap(), &mut scratch.q,
3250 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(q_dim), stream)?;
3251 } else if nf4_tc_gemm {
3252 gemm_nf4_tc_forward(&scratch.norm1_out, &self.w_q_nf4, &self.w_q_scales, &mut scratch.q,
3253 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(q_dim), stream)?;
3254 } else if nf4_gemm {
3255 gemm_nf4_forward(&scratch.norm1_out, &self.w_q_nf4, &self.w_q_scales, &mut scratch.q,
3256 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(q_dim), stream)?;
3257 } else {
3258 gemm_forward(&scratch.norm1_out, &self.w_q_fp32, &mut scratch.q,
3259 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(q_dim), stream)?;
3260 }
3261
3262 if let Some(b_q_repl) = self.b_q_replicated.as_ref() {
3266 cuda_add_inplace(&mut scratch.q, b_q_repl, seq_len * q_dim, stream)?;
3267 }
3268
3269 if let (Some(a_q), Some(b_q)) = (&self.lora_a_q, &self.lora_b_q) {
3271 let s = saturating_u32(seq_len);
3272 let h = saturating_u32(hidden_size);
3273 let r = saturating_u32(self.lora_rank);
3274 let qd = saturating_u32(q_dim);
3275 gemm_forward(&scratch.norm1_out, a_q, &mut scratch.lora_inter, s, h, r, stream)?;
3277 gemm_forward(&scratch.lora_inter, b_q, &mut scratch.lora_temp, s, r, qd, stream)?;
3279 cuda_add_inplace(&mut scratch.q, &scratch.lora_temp, seq_len * q_dim, stream)?;
3281 }
3282
3283 if fp16_gemm && self.w_k_fp16.is_some() {
3284 let f16_act = scratch.norm1_out_f16.as_ref().unwrap();
3285 gemm_f16_to_f32_forward(f16_act, self.w_k_fp16.as_ref().unwrap(), &mut scratch.k,
3286 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(kv_hidden_size), stream)?;
3287 gemm_f16_to_f32_forward(f16_act, self.w_v_fp16.as_ref().unwrap(), &mut scratch.v,
3288 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(kv_hidden_size), stream)?;
3289 } else if nf4_tc_gemm {
3290 gemm_nf4_tc_forward(&scratch.norm1_out, &self.w_k_nf4, &self.w_k_scales, &mut scratch.k,
3292 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(kv_hidden_size), stream)?;
3293 gemm_nf4_tc_forward(&scratch.norm1_out, &self.w_v_nf4, &self.w_v_scales, &mut scratch.v,
3294 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(kv_hidden_size), stream)?;
3295 } else if nf4_gemm {
3296 crate::autograd::cuda_forward::gemm_nf4_gate_up_forward(
3298 &scratch.norm1_out,
3299 &self.w_k_nf4, &self.w_k_scales,
3300 &self.w_v_nf4, &self.w_v_scales,
3301 &mut scratch.k, &mut scratch.v,
3302 saturating_u32(seq_len), saturating_u32(hidden_size),
3303 saturating_u32(kv_hidden_size), stream,
3304 )?;
3305 } else {
3306 gemm_forward(&scratch.norm1_out, &self.w_k_fp32, &mut scratch.k,
3307 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(kv_hidden_size), stream)?;
3308 gemm_forward(&scratch.norm1_out, &self.w_v_fp32, &mut scratch.v,
3309 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(kv_hidden_size), stream)?;
3310 }
3311
3312 if let Some(b_k_repl) = self.b_k_replicated.as_ref() {
3315 cuda_add_inplace(&mut scratch.k, b_k_repl, seq_len * kv_hidden_size, stream)?;
3316 }
3317 if let Some(b_v_repl) = self.b_v_replicated.as_ref() {
3318 cuda_add_inplace(&mut scratch.v, b_v_repl, seq_len * kv_hidden_size, stream)?;
3319 }
3320
3321 scratch.op_end(_t, OP_QKV_GEMM); if let (Some(a_v), Some(b_v)) = (&self.lora_a_v, &self.lora_b_v) {
3325 let s = saturating_u32(seq_len);
3326 let h = saturating_u32(hidden_size);
3327 let r = saturating_u32(self.lora_rank);
3328 let vd = saturating_u32(kv_hidden_size);
3329 gemm_forward(&scratch.norm1_out, a_v, &mut scratch.lora_inter, s, h, r, stream)?;
3331 gemm_forward(&scratch.lora_inter, b_v, &mut scratch.lora_temp, s, r, vd, stream)?;
3333 cuda_add_inplace(&mut scratch.v, &scratch.lora_temp, seq_len * kv_hidden_size, stream)?;
3335 }
3336
3337 if nan_scan_enabled() {
3338 let l = self.layer_idx;
3339 nan_scan_f32(&format!("L{l} q-proj"), &scratch.q, seq_len * q_dim, stream);
3340 nan_scan_f32(&format!("L{l} k-proj"), &scratch.k, seq_len * kv_hidden_size, stream);
3341 nan_scan_f32(&format!("L{l} v-proj"), &scratch.v, seq_len * kv_hidden_size, stream);
3342 }
3343
3344 let _t = scratch.op_begin();
3346 self.compute_attention_cuda(seq_len, stream, scratch)?;
3347 scratch.op_end(_t, OP_ATTENTION);
3348
3349 if nan_scan_enabled() {
3350 let l = self.layer_idx;
3351 nan_scan_f32(&format!("L{l} attn-out"), &scratch.attn_out, seq_len * q_dim, stream);
3352 }
3353
3354 let _t = scratch.op_begin();
3356 if fp16_gemm && self.w_o_fp16.is_some() {
3357 if scratch.attn_out_f16.is_none() {
3358 scratch.attn_out_f16 = Some(GpuBuffer::new(&self.ctx, seq_len * q_dim)?);
3359 }
3360 let f16_buf = scratch.attn_out_f16.as_mut().unwrap();
3361 cast_f32_to_f16_gpu(&scratch.attn_out, f16_buf, (seq_len * q_dim) as u32, stream)?;
3362 gemm_f16_to_f32_forward(f16_buf, self.w_o_fp16.as_ref().unwrap(), &mut scratch.o_proj_out,
3363 saturating_u32(seq_len), saturating_u32(q_dim), saturating_u32(hidden_size), stream)?;
3364 } else if nf4_tc_gemm {
3365 gemm_nf4_tc_forward(&scratch.attn_out, &self.w_o_nf4, &self.w_o_scales, &mut scratch.o_proj_out,
3366 saturating_u32(seq_len), saturating_u32(q_dim), saturating_u32(hidden_size), stream)?;
3367 } else if nf4_gemm {
3368 gemm_nf4_forward(&scratch.attn_out, &self.w_o_nf4, &self.w_o_scales, &mut scratch.o_proj_out,
3369 saturating_u32(seq_len), saturating_u32(q_dim), saturating_u32(hidden_size), stream)?;
3370 } else {
3371 gemm_forward(&scratch.attn_out, &self.w_o_fp32, &mut scratch.o_proj_out,
3372 saturating_u32(seq_len), saturating_u32(q_dim), saturating_u32(hidden_size), stream)?;
3373 }
3374
3375 scratch.op_end(_t, OP_O_PROJ);
3376
3377 if nan_scan_enabled() {
3378 let l = self.layer_idx;
3379 nan_scan_f32(&format!("L{l} o-proj"), &scratch.o_proj_out, seq_len * hidden_size, stream);
3380 }
3381
3382 let _t = scratch.op_begin();
3384 fused_residual_rmsnorm_forward(
3388 input,
3389 &scratch.o_proj_out,
3390 &mut scratch.residual1,
3391 &mut scratch.norm2_out,
3392 &self.post_attn_norm_weight,
3393 saturating_u32(seq_len),
3394 saturating_u32(hidden_size),
3395 self.config.rms_norm_eps,
3396 stream,
3397 )?;
3398
3399 scratch.op_end(_t, OP_RMSNORM_FFN); if nan_scan_enabled() {
3402 let l = self.layer_idx;
3403 nan_scan_f32(&format!("L{l} residual1"), &scratch.residual1, seq_len * hidden_size, stream);
3404 nan_scan_f32(&format!("L{l} norm2"), &scratch.norm2_out, seq_len * hidden_size, stream);
3405 }
3406
3407 let _t = scratch.op_begin(); if fp16_gemm && self.w_gate_fp16.is_some() {
3410 if scratch.norm2_out_f16.is_none() {
3411 scratch.norm2_out_f16 = Some(GpuBuffer::new(&self.ctx, seq_len * hidden_size)?);
3412 }
3413 let f16_buf = scratch.norm2_out_f16.as_mut().unwrap();
3414 cast_f32_to_f16_gpu(&scratch.norm2_out, f16_buf, (seq_len * hidden_size) as u32, stream)?;
3415 gemm_f16_to_f32_forward(f16_buf, self.w_gate_fp16.as_ref().unwrap(), &mut scratch.gate_out,
3416 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(intermediate_size), stream)?;
3417 gemm_f16_to_f32_forward(f16_buf, self.w_up_fp16.as_ref().unwrap(), &mut scratch.up_out,
3418 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(intermediate_size), stream)?;
3419 } else if nf4_tc_gemm {
3420 gemm_nf4_tc_forward(&scratch.norm2_out, &self.w_gate_nf4, &self.w_gate_scales, &mut scratch.gate_out,
3422 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(intermediate_size), stream)?;
3423 gemm_nf4_tc_forward(&scratch.norm2_out, &self.w_up_nf4, &self.w_up_scales, &mut scratch.up_out,
3424 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(intermediate_size), stream)?;
3425 } else if nf4_gemm {
3426 crate::autograd::cuda_forward::gemm_nf4_gate_up_forward(
3428 &scratch.norm2_out,
3429 &self.w_gate_nf4, &self.w_gate_scales,
3430 &self.w_up_nf4, &self.w_up_scales,
3431 &mut scratch.gate_out, &mut scratch.up_out,
3432 saturating_u32(seq_len), saturating_u32(hidden_size),
3433 saturating_u32(intermediate_size), stream,
3434 )?;
3435 } else {
3436 gemm_forward(&scratch.norm2_out, &self.w_gate_fp32, &mut scratch.gate_out,
3437 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(intermediate_size), stream)?;
3438 gemm_forward(&scratch.norm2_out, &self.w_up_fp32, &mut scratch.up_out,
3439 saturating_u32(seq_len), saturating_u32(hidden_size), saturating_u32(intermediate_size), stream)?;
3440 }
3441
3442 scratch.op_end(_t, OP_GATE_UP_GEMM);
3443
3444 if nan_scan_enabled() {
3445 let l = self.layer_idx;
3446 nan_scan_f32(&format!("L{l} gate"), &scratch.gate_out, seq_len * intermediate_size, stream);
3447 nan_scan_f32(&format!("L{l} up"), &scratch.up_out, seq_len * intermediate_size, stream);
3448 }
3449
3450 let _t = scratch.op_begin();
3452 fused_swiglu_forward(&scratch.gate_out, &scratch.up_out, &mut scratch.swiglu_out,
3453 saturating_u32(seq_len * intermediate_size), stream)?;
3454 scratch.op_end(_t, OP_SILU);
3455
3456 if nan_scan_enabled() {
3457 let l = self.layer_idx;
3458 nan_scan_f32(&format!("L{l} swiglu"), &scratch.swiglu_out, seq_len * intermediate_size, stream);
3459 }
3460
3461 let _t = scratch.op_begin();
3463 if fp16_gemm && self.w_down_fp16.is_some() {
3464 if scratch.swiglu_out_f16.is_none() {
3465 scratch.swiglu_out_f16 = Some(GpuBuffer::new(&self.ctx, seq_len * intermediate_size)?);
3466 }
3467 let f16_buf = scratch.swiglu_out_f16.as_mut().unwrap();
3468 cast_f32_to_f16_gpu(&scratch.swiglu_out, f16_buf, (seq_len * intermediate_size) as u32, stream)?;
3469 gemm_f16_to_f32_forward(f16_buf, self.w_down_fp16.as_ref().unwrap(), &mut scratch.ffn_out,
3470 saturating_u32(seq_len), saturating_u32(intermediate_size), saturating_u32(hidden_size), stream)?;
3471 } else if nf4_tc_gemm {
3472 gemm_nf4_tc_forward(&scratch.swiglu_out, &self.w_down_nf4, &self.w_down_scales, &mut scratch.ffn_out,
3473 saturating_u32(seq_len), saturating_u32(intermediate_size), saturating_u32(hidden_size), stream)?;
3474 } else if nf4_gemm {
3475 gemm_nf4_forward(&scratch.swiglu_out, &self.w_down_nf4, &self.w_down_scales, &mut scratch.ffn_out,
3476 saturating_u32(seq_len), saturating_u32(intermediate_size), saturating_u32(hidden_size), stream)?;
3477 } else {
3478 gemm_forward(&scratch.swiglu_out, &self.w_down_fp32, &mut scratch.ffn_out,
3479 saturating_u32(seq_len), saturating_u32(intermediate_size), saturating_u32(hidden_size), stream)?;
3480 }
3481
3482 scratch.op_end(_t, OP_DOWN_GEMM);
3483
3484 if nan_scan_enabled() {
3485 let l = self.layer_idx;
3486 nan_scan_f32(&format!("L{l} down"), &scratch.ffn_out, seq_len * hidden_size, stream);
3487 }
3488
3489 cuda_add(&scratch.residual1, &scratch.ffn_out, output, seq_len * hidden_size, stream)?;
3491
3492 if nan_scan_enabled() {
3493 let l = self.layer_idx;
3494 nan_scan_f32(&format!("L{l} block-out"), output, seq_len * hidden_size, stream);
3495 }
3496
3497 Ok(())
3498 }
3499
3500 pub fn layer_idx(&self) -> usize {
3502 self.layer_idx
3503 }
3504}
3505
3506#[cfg(feature = "cuda")]
3511impl CudaNf4TransformerBlock {
3512 fn compute_attention_cuda(
3513 &self,
3514 seq_len: usize,
3515 stream: &CudaStream,
3516 scratch: &mut CudaBlockScratch,
3517 ) -> Result<()> {
3518 let num_heads = self.config.num_attention_heads;
3519 let num_kv_heads = self.config.num_kv_heads;
3520 let head_dim = self.config.head_dim();
3521 let heads_per_kv = num_heads / num_kv_heads;
3522
3523 let s = saturating_u32(seq_len);
3524 let nh = saturating_u32(num_heads);
3525 let nkv = saturating_u32(num_kv_heads);
3526 let hd = saturating_u32(head_dim);
3527
3528 if let Some(ref q_norm) = self.q_norm_weight {
3533 for pos in 0..seq_len {
3534 let q_ref = unsafe { &*(std::ptr::addr_of!(scratch.q)) };
3536 per_head_rmsnorm_forward(q_ref, q_norm, &mut scratch.q, nh, hd, pos, stream)?;
3537 }
3538 }
3539 if let Some(ref k_norm) = self.k_norm_weight {
3540 for pos in 0..seq_len {
3541 let k_ref = unsafe { &*(std::ptr::addr_of!(scratch.k)) };
3543 per_head_rmsnorm_forward(k_ref, k_norm, &mut scratch.k, nkv, hd, pos, stream)?;
3544 }
3545 }
3546
3547 let rope_theta = self.config.rope_theta;
3550 {
3551 let q_ref = unsafe { &*(std::ptr::addr_of!(scratch.q)) };
3553 batched_rope_neox_forward(
3554 q_ref,
3555 &mut scratch.q,
3556 &scratch.rope_positions,
3557 nh,
3558 hd,
3559 s,
3560 rope_theta,
3561 stream,
3562 )?;
3563 let k_ref = unsafe { &*(std::ptr::addr_of!(scratch.k)) };
3565 batched_rope_neox_forward(
3566 k_ref,
3567 &mut scratch.k,
3568 &scratch.rope_positions,
3569 nkv,
3570 hd,
3571 s,
3572 rope_theta,
3573 stream,
3574 )?;
3575 }
3576
3577 if nan_scan_enabled() {
3578 let l = self.layer_idx;
3579 nan_scan_f32(
3580 &format!("L{l} attn.rope-q"),
3581 &scratch.q,
3582 seq_len * num_heads * head_dim,
3583 stream,
3584 );
3585 nan_scan_f32(
3586 &format!("L{l} attn.rope-k"),
3587 &scratch.k,
3588 seq_len * num_kv_heads * head_dim,
3589 stream,
3590 );
3591 }
3592
3593 interleaved_to_batched_forward(&scratch.q, &mut scratch.attn_q_batched, s, nh, hd, stream)?;
3595
3596 interleaved_to_batched_forward(&scratch.k, &mut scratch.attn_kv_temp, s, nkv, hd, stream)?;
3598
3599 if heads_per_kv > 1 {
3600 expand_kv_heads(
3601 &scratch.attn_kv_temp,
3602 &mut scratch.attn_kv_temp2,
3603 num_kv_heads,
3604 heads_per_kv,
3605 seq_len * head_dim,
3606 stream,
3607 )?;
3608 } else {
3609 unsafe {
3611 scratch
3612 .attn_kv_temp2
3613 .copy_from_buffer_async(&scratch.attn_kv_temp, stream)
3614 .map_err(|e| {
3615 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
3616 "K copy failed: {e:?}"
3617 ))
3618 })?;
3619 }
3620 }
3621
3622 batched_transpose_forward(
3624 &scratch.attn_kv_temp2,
3625 &mut scratch.attn_kv_temp,
3626 nh,
3627 s,
3628 hd,
3629 stream,
3630 )?;
3631
3632 batched_4d_gemm_forward(
3634 &scratch.attn_q_batched,
3635 &scratch.attn_kv_temp,
3636 &mut scratch.attn_scores,
3637 1,
3638 nh,
3639 s,
3640 s,
3641 hd,
3642 stream,
3643 )?;
3644
3645 if nan_scan_enabled() {
3646 let l = self.layer_idx;
3647 nan_scan_f32(
3648 &format!("L{l} attn.q-batched"),
3649 &scratch.attn_q_batched,
3650 num_heads * seq_len * head_dim,
3651 stream,
3652 );
3653 nan_scan_f32(
3654 &format!("L{l} attn.kT"),
3655 &scratch.attn_kv_temp,
3656 num_heads * seq_len * head_dim,
3657 stream,
3658 );
3659 nan_scan_f32(
3660 &format!("L{l} attn.scores-raw"),
3661 &scratch.attn_scores,
3662 num_heads * seq_len * seq_len,
3663 stream,
3664 );
3665 }
3666
3667 let scale_factor = 1.0 / (head_dim as f32).sqrt();
3669 let total_scores = num_heads * seq_len * seq_len;
3670 let scores_view = unsafe {
3672 GpuBuffer::<f32>::from_raw_parts(
3673 scratch.attn_scores.as_ptr(),
3674 scratch.attn_scores.len(),
3675 )
3676 };
3677 scale_forward(
3678 &scores_view,
3679 &mut scratch.attn_scores,
3680 scale_factor,
3681 saturating_u32(total_scores),
3682 stream,
3683 )?;
3684 leak(scores_view);
3685
3686 {
3692 let seq_sq = seq_len * seq_len;
3693 let mask_ptr = scratch.causal_mask_contiguous.as_ptr();
3694 let scores_base = scratch.attn_scores.as_ptr();
3695 for head in 0..num_heads {
3696 let byte_offset = (head * seq_sq * 4) as u64;
3697 let head_ptr = scores_base + byte_offset;
3698 let mask_view = unsafe { GpuBuffer::<f32>::from_raw_parts(mask_ptr, seq_sq) };
3700 let scores_view = unsafe { GpuBuffer::<f32>::from_raw_parts(head_ptr, seq_sq) };
3702 let mut out_view = unsafe { GpuBuffer::<f32>::from_raw_parts(head_ptr, seq_sq) };
3704 residual_add_forward(
3705 &mask_view,
3706 &scores_view,
3707 &mut out_view,
3708 saturating_u32(seq_sq),
3709 stream,
3710 )?;
3711 leak(mask_view);
3712 leak(scores_view);
3713 leak(out_view);
3714 }
3715 }
3716
3717 if nan_scan_enabled() {
3718 let l = self.layer_idx;
3719 if stream.synchronize().is_ok() {
3721 let n = num_heads * seq_len * seq_len;
3722 let mut host = vec![0.0f32; n.min(scratch.attn_scores.len())];
3723 if scratch.attn_scores.copy_to_host(&mut host).is_ok() {
3724 let nan = host.iter().filter(|v| v.is_nan()).count();
3725 if nan > 0 {
3726 eprintln!("[NAN-SCAN] L{l} attn.scores-masked: {nan}/{n} NaN");
3727 }
3728 }
3729 }
3730 }
3731
3732 let scores_view = unsafe {
3735 GpuBuffer::<f32>::from_raw_parts(
3736 scratch.attn_scores.as_ptr(),
3737 scratch.attn_scores.len(),
3738 )
3739 };
3740 batched_softmax_forward(
3741 &scores_view,
3742 &mut scratch.attn_scores,
3743 saturating_u32(num_heads * seq_len),
3744 s,
3745 stream,
3746 )?;
3747 leak(scores_view);
3748
3749 if nan_scan_enabled() {
3750 let l = self.layer_idx;
3751 nan_scan_f32(
3752 &format!("L{l} attn.softmax"),
3753 &scratch.attn_scores,
3754 num_heads * seq_len * seq_len,
3755 stream,
3756 );
3757 }
3758
3759 interleaved_to_batched_forward(&scratch.v, &mut scratch.attn_kv_temp, s, nkv, hd, stream)?;
3761
3762 if heads_per_kv > 1 {
3763 expand_kv_heads(
3764 &scratch.attn_kv_temp,
3765 &mut scratch.attn_kv_temp2,
3766 num_kv_heads,
3767 heads_per_kv,
3768 seq_len * head_dim,
3769 stream,
3770 )?;
3771 } else {
3772 unsafe {
3776 scratch
3777 .attn_kv_temp2
3778 .copy_from_buffer_async(&scratch.attn_kv_temp, stream)
3779 .map_err(|e| {
3780 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
3781 "V copy failed: {e:?}"
3782 ))
3783 })?;
3784 }
3785 }
3786
3787 if nan_scan_enabled() {
3788 let l = self.layer_idx;
3789 nan_scan_f32(
3790 &format!("L{l} attn.v-expanded"),
3791 &scratch.attn_kv_temp2,
3792 num_heads * seq_len * head_dim,
3793 stream,
3794 );
3795 }
3796
3797 batched_4d_gemm_forward(
3799 &scratch.attn_scores,
3800 &scratch.attn_kv_temp2,
3801 &mut scratch.attn_q_batched,
3802 1,
3803 nh,
3804 s,
3805 hd,
3806 s,
3807 stream,
3808 )?;
3809
3810 if nan_scan_enabled() {
3811 let l = self.layer_idx;
3812 nan_scan_f32(
3813 &format!("L{l} attn.scoresV"),
3814 &scratch.attn_q_batched,
3815 num_heads * seq_len * head_dim,
3816 stream,
3817 );
3818 }
3819
3820 batched_to_interleaved_forward(
3822 &scratch.attn_q_batched,
3823 &mut scratch.attn_out,
3824 s,
3825 nh,
3826 hd,
3827 stream,
3828 )?;
3829
3830 Ok(())
3831 }
3832}
3833
3834#[cfg(feature = "cuda")]
3850pub(crate) struct CudaLoraGradWorkspace {
3851 pub(crate) grad_lora_a_q: GpuBuffer<f32>,
3853 pub(crate) grad_lora_b_q: GpuBuffer<f32>,
3855 pub(crate) grad_lora_a_v: GpuBuffer<f32>,
3857 pub(crate) grad_lora_b_v: GpuBuffer<f32>,
3859 pub(crate) grad_input_norm: GpuBuffer<f32>,
3861 pub(crate) grad_post_attn_norm: GpuBuffer<f32>,
3863}
3864
3865#[cfg(feature = "cuda")]
3866impl CudaLoraGradWorkspace {
3867 pub(crate) fn new(
3869 ctx: &Arc<CudaContext>,
3870 config: &super::config::TransformerConfig,
3871 lora_rank: usize,
3872 ) -> Result<Self> {
3873 let h = config.hidden_size;
3874 let q_dim = config.q_dim();
3875 let kv = config.num_kv_heads * config.head_dim();
3876 let r = lora_rank;
3877
3878 Ok(Self {
3879 grad_lora_a_q: GpuBuffer::new(ctx, h * r)?,
3880 grad_lora_b_q: GpuBuffer::new(ctx, r * q_dim)?,
3881 grad_lora_a_v: GpuBuffer::new(ctx, h * r)?,
3882 grad_lora_b_v: GpuBuffer::new(ctx, r * kv)?,
3883 grad_input_norm: GpuBuffer::new(ctx, h)?,
3884 grad_post_attn_norm: GpuBuffer::new(ctx, h)?,
3885 })
3886 }
3887
3888 pub(crate) fn clip_gradients(&mut self, max_norm: f32, stream: &CudaStream) {
3898 let sq_a_q = squared_sum_cuda(&self.grad_lora_a_q, self.grad_lora_a_q.len() as u32, stream)
3900 .unwrap_or(0.0);
3901 let sq_b_q = squared_sum_cuda(&self.grad_lora_b_q, self.grad_lora_b_q.len() as u32, stream)
3902 .unwrap_or(0.0);
3903 let sq_a_v = squared_sum_cuda(&self.grad_lora_a_v, self.grad_lora_a_v.len() as u32, stream)
3904 .unwrap_or(0.0);
3905 let sq_b_v = squared_sum_cuda(&self.grad_lora_b_v, self.grad_lora_b_v.len() as u32, stream)
3906 .unwrap_or(0.0);
3907 let sq_in =
3908 squared_sum_cuda(&self.grad_input_norm, self.grad_input_norm.len() as u32, stream)
3909 .unwrap_or(0.0);
3910 let sq_pa = squared_sum_cuda(
3911 &self.grad_post_attn_norm,
3912 self.grad_post_attn_norm.len() as u32,
3913 stream,
3914 )
3915 .unwrap_or(0.0);
3916 let total_norm = (sq_a_q + sq_b_q + sq_a_v + sq_b_v + sq_in + sq_pa).sqrt();
3917
3918 if total_norm <= max_norm {
3919 return;
3920 }
3921
3922 let clip_scale = max_norm / (total_norm + 1e-6);
3924 let n_aq = self.grad_lora_a_q.len() as u32;
3925 let n_bq = self.grad_lora_b_q.len() as u32;
3926 let n_av = self.grad_lora_a_v.len() as u32;
3927 let n_bv = self.grad_lora_b_v.len() as u32;
3928 let n_in = self.grad_input_norm.len() as u32;
3929 let n_pa = self.grad_post_attn_norm.len() as u32;
3930 let _ = gradient_clip_cuda(&mut self.grad_lora_a_q, clip_scale, n_aq, stream);
3931 let _ = gradient_clip_cuda(&mut self.grad_lora_b_q, clip_scale, n_bq, stream);
3932 let _ = gradient_clip_cuda(&mut self.grad_lora_a_v, clip_scale, n_av, stream);
3933 let _ = gradient_clip_cuda(&mut self.grad_lora_b_v, clip_scale, n_bv, stream);
3934 let _ = gradient_clip_cuda(&mut self.grad_input_norm, clip_scale, n_in, stream);
3935 let _ = gradient_clip_cuda(&mut self.grad_post_attn_norm, clip_scale, n_pa, stream);
3936 }
3937}
3938
3939#[cfg(feature = "cuda")]
3951pub(crate) struct GpuLoraOptimizerState {
3952 m_lora_a_q: GpuBuffer<f32>,
3953 v_lora_a_q: GpuBuffer<f32>,
3954 m_lora_b_q: GpuBuffer<f32>,
3955 v_lora_b_q: GpuBuffer<f32>,
3956 m_lora_a_v: GpuBuffer<f32>,
3957 v_lora_a_v: GpuBuffer<f32>,
3958 m_lora_b_v: GpuBuffer<f32>,
3959 v_lora_b_v: GpuBuffer<f32>,
3960 m_input_norm: GpuBuffer<f32>,
3961 v_input_norm: GpuBuffer<f32>,
3962 m_post_attn_norm: GpuBuffer<f32>,
3963 v_post_attn_norm: GpuBuffer<f32>,
3964}
3965
3966#[cfg(feature = "cuda")]
3967impl GpuLoraOptimizerState {
3968 fn new(
3969 ctx: &Arc<CudaContext>,
3970 config: &super::config::TransformerConfig,
3971 lora_rank: usize,
3972 ) -> Result<Self> {
3973 let h = config.hidden_size;
3974 let q_dim = config.q_dim();
3975 let kv = config.num_kv_heads * config.head_dim();
3976 let r = lora_rank;
3977
3978 let z = |n: usize| -> Result<GpuBuffer<f32>> {
3981 Ok(GpuBuffer::from_host(ctx, &vec![0.0f32; n])?)
3982 };
3983 Ok(Self {
3984 m_lora_a_q: z(h * r)?,
3985 v_lora_a_q: z(h * r)?,
3986 m_lora_b_q: z(r * q_dim)?,
3987 v_lora_b_q: z(r * q_dim)?,
3988 m_lora_a_v: z(h * r)?,
3989 v_lora_a_v: z(h * r)?,
3990 m_lora_b_v: z(r * kv)?,
3991 v_lora_b_v: z(r * kv)?,
3992 m_input_norm: z(h)?,
3993 v_input_norm: z(h)?,
3994 m_post_attn_norm: z(h)?,
3995 v_post_attn_norm: z(h)?,
3996 })
3997 }
3998}
3999
4000#[cfg(feature = "cuda")]
4005impl CudaNf4TransformerBlock {
4006 #[allow(clippy::too_many_arguments)]
4014 pub(crate) fn backward(
4015 &self,
4016 layer_input: &GpuBuffer<f32>,
4017 grad_output: &GpuBuffer<f32>,
4018 grad_input: &mut GpuBuffer<f32>,
4019 output_scratch: &mut GpuBuffer<f32>,
4020 seq_len: usize,
4021 stream: &CudaStream,
4022 scratch: &mut CudaBlockScratch,
4023 grad_lora: &mut CudaLoraGradWorkspace,
4024 ) -> Result<()> {
4025 let hidden_size = self.config.hidden_size;
4026 let _q_dim = self.config.q_dim();
4027 let _kv_hidden_size = self.config.num_kv_heads * self.config.head_dim();
4028 let intermediate_size = self.config.intermediate_size;
4029 let eps = 1e-5_f32;
4030
4031 self.forward(layer_input, output_scratch, seq_len, stream, scratch).map_err(|e| {
4034 eprintln!(
4035 "[backward] Layer {} activation-checkpoint forward FAILED: {e:?}",
4036 self.layer_idx
4037 );
4038 e
4039 })?;
4040
4041 self.backward_nf4_ffn(
4043 grad_output,
4044 seq_len,
4045 hidden_size,
4046 intermediate_size,
4047 stream,
4048 scratch,
4049 )?;
4050
4051 let _t = scratch.op_begin(); rms_norm_backward(
4054 &scratch.residual1,
4055 &self.post_attn_norm_weight,
4056 &scratch.grad_hidden, grad_input, &mut grad_lora.grad_post_attn_norm,
4059 saturating_u32(seq_len),
4060 saturating_u32(hidden_size),
4061 eps,
4062 stream,
4063 )?;
4064
4065 cuda_add_inplace(grad_input, grad_output, seq_len * hidden_size, stream)?;
4068
4069 self.backward_nf4_attention(
4071 grad_input, seq_len, stream, scratch, grad_lora,
4073 )?;
4074
4075 rms_norm_backward(
4079 layer_input,
4080 &self.input_norm_weight,
4081 &scratch.grad_hidden, grad_input, &mut grad_lora.grad_input_norm,
4084 saturating_u32(seq_len),
4085 saturating_u32(hidden_size),
4086 eps,
4087 stream,
4088 )?;
4089
4090 scratch.op_end(_t, OP_NORM_BWD);
4091
4092 Ok(())
4093 }
4094
4095 fn backward_nf4_ffn(
4101 &self,
4102 grad_output: &GpuBuffer<f32>,
4103 seq_len: usize,
4104 hidden_size: usize,
4105 intermediate_size: usize,
4106 stream: &CudaStream,
4107 scratch: &mut CudaBlockScratch,
4108 ) -> Result<()> {
4109 let s = saturating_u32(seq_len);
4110 let h = saturating_u32(hidden_size);
4111 let i_size = saturating_u32(intermediate_size);
4112 let n_inter = saturating_u32(seq_len * intermediate_size);
4113
4114 static USE_NF4_TC_BWD: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4116 let nf4_tc_bwd =
4117 *USE_NF4_TC_BWD.get_or_init(|| std::env::var("NF4_TC_BWD_GEMM").as_deref() == Ok("1"));
4118
4119 let _t = scratch.op_begin(); if nf4_tc_bwd {
4122 crate::autograd::cuda_forward::gemm_nf4_tc_backward_a(
4124 grad_output,
4125 &self.w_down_nf4,
4126 &self.w_down_scales,
4127 &mut scratch.grad_swiglu,
4128 s,
4129 h, i_size, stream,
4132 )?;
4133 } else {
4134 gemm_backward_a_fp16_dispatch(
4135 grad_output,
4136 self.w_down_fp16.as_ref(),
4137 &self.w_down_fp32,
4138 &mut scratch.grad_swiglu,
4139 s,
4140 i_size,
4141 h,
4142 stream,
4143 &self.ctx,
4144 )?;
4145 }
4146
4147 scratch.op_end(_t, OP_DOWN_BWD);
4148
4149 let _t = scratch.op_begin(); elementwise_mul_forward(
4156 &scratch.grad_swiglu,
4157 &scratch.up_out,
4158 &mut scratch.swiglu_out,
4159 n_inter,
4160 stream,
4161 )?;
4162
4163 silu_backward(
4167 &scratch.gate_out,
4168 &scratch.swiglu_out,
4169 &mut scratch.up_out, stream,
4171 )?;
4172
4173 silu_forward(&scratch.gate_out, &mut scratch.swiglu_out, n_inter, stream)?;
4176 elementwise_mul_forward(
4178 &scratch.grad_swiglu,
4179 &scratch.swiglu_out,
4180 &mut scratch.gate_out, n_inter,
4182 stream,
4183 )?;
4184
4185 scratch.op_end(_t, OP_SWIGLU_BWD);
4186
4187 let _t = scratch.op_begin(); static USE_FUSED_BWD: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4192 let fused_bwd = *USE_FUSED_BWD
4193 .get_or_init(|| std::env::var("NF4_FUSED_BWD_GEMM").as_deref() == Ok("1"));
4194
4195 if nf4_tc_bwd {
4196 crate::autograd::cuda_forward::gemm_nf4_tc_backward_a(
4199 &scratch.gate_out, &self.w_up_nf4,
4201 &self.w_up_scales,
4202 &mut scratch.grad_hidden,
4203 s,
4204 i_size, h, stream,
4207 )?;
4208 crate::autograd::cuda_forward::gemm_nf4_tc_backward_a(
4210 &scratch.up_out, &self.w_gate_nf4,
4212 &self.w_gate_scales,
4213 &mut scratch.ffn_out,
4214 s,
4215 i_size, h, stream,
4218 )?;
4219 cuda_add_inplace(
4221 &mut scratch.grad_hidden,
4222 &scratch.ffn_out,
4223 seq_len * hidden_size,
4224 stream,
4225 )?;
4226 } else if fused_bwd {
4227 gemm_backward_a_fp16_dispatch(
4229 &scratch.gate_out,
4230 self.w_up_fp16.as_ref(),
4231 &self.w_up_fp32,
4232 &mut scratch.grad_hidden,
4233 s,
4234 h,
4235 i_size,
4236 stream,
4237 &self.ctx,
4238 )?;
4239 gemm_backward_a_fp16_dispatch_accumulate(
4241 &scratch.up_out,
4242 self.w_gate_fp16.as_ref(),
4243 &self.w_gate_fp32,
4244 &mut scratch.grad_hidden,
4245 s,
4246 h,
4247 i_size,
4248 stream,
4249 &self.ctx,
4250 )?;
4251 } else {
4252 gemm_backward_a_fp16_dispatch(
4254 &scratch.up_out,
4255 self.w_gate_fp16.as_ref(),
4256 &self.w_gate_fp32,
4257 &mut scratch.ffn_out,
4258 s,
4259 h,
4260 i_size,
4261 stream,
4262 &self.ctx,
4263 )?;
4264 gemm_backward_a_fp16_dispatch(
4265 &scratch.gate_out,
4266 self.w_up_fp16.as_ref(),
4267 &self.w_up_fp32,
4268 &mut scratch.grad_hidden,
4269 s,
4270 h,
4271 i_size,
4272 stream,
4273 &self.ctx,
4274 )?;
4275
4276 cuda_add_inplace(
4278 &mut scratch.grad_hidden,
4279 &scratch.ffn_out,
4280 seq_len * hidden_size,
4281 stream,
4282 )?;
4283 }
4284 scratch.op_end(_t, OP_GATE_UP_BWD);
4285
4286 Ok(())
4287 }
4288
4289 fn backward_nf4_attention(
4295 &self,
4296 grad_residual1: &GpuBuffer<f32>,
4297 seq_len: usize,
4298 stream: &CudaStream,
4299 scratch: &mut CudaBlockScratch,
4300 grad_lora: &mut CudaLoraGradWorkspace,
4301 ) -> Result<()> {
4302 use crate::autograd::cuda_forward::gemm_forward;
4303
4304 let hidden_size = self.config.hidden_size;
4305 let q_dim = self.config.q_dim();
4306 let kv_hidden_size = self.config.num_kv_heads * self.config.head_dim();
4307 let num_heads = self.config.num_attention_heads;
4308 let head_dim = self.config.head_dim();
4309
4310 let s = saturating_u32(seq_len);
4311 let h = saturating_u32(hidden_size);
4312 let qd = saturating_u32(q_dim);
4313 let kvh = saturating_u32(kv_hidden_size);
4314
4315 static USE_NF4_TC_BWD_O: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4317 let nf4_tc_bwd_o = *USE_NF4_TC_BWD_O
4318 .get_or_init(|| std::env::var("NF4_TC_BWD_GEMM").as_deref() == Ok("1"));
4319
4320 let _t = scratch.op_begin(); if nf4_tc_bwd_o {
4322 crate::autograd::cuda_forward::gemm_nf4_tc_backward_a(
4323 grad_residual1,
4324 &self.w_o_nf4,
4325 &self.w_o_scales,
4326 &mut scratch.attn_out,
4327 s,
4328 h, qd, stream,
4331 )?;
4332 } else {
4333 gemm_backward_a_fp16_dispatch(
4334 grad_residual1,
4335 self.w_o_fp16.as_ref(),
4336 &self.w_o_fp32,
4337 &mut scratch.attn_out,
4338 s,
4339 qd,
4340 h,
4341 stream,
4342 &self.ctx,
4343 )?;
4344 }
4345
4346 self.backward_nf4_attention_mechanism(seq_len, num_heads, head_dim, stream, scratch)?;
4350
4351 let rope_theta = self.config.rope_theta;
4358 let num_kv_heads = self.config.num_kv_heads;
4359 let nkv = saturating_u32(num_kv_heads);
4360 let nh = saturating_u32(num_heads);
4361 let hd = saturating_u32(head_dim);
4362 {
4363 let q_ref = unsafe { &*(std::ptr::addr_of!(scratch.q)) };
4365 batched_rope_neox_backward(
4366 q_ref,
4367 &mut scratch.q,
4368 &scratch.rope_positions,
4369 nh,
4370 hd,
4371 s,
4372 rope_theta,
4373 stream,
4374 )?;
4375 let k_ref = unsafe { &*(std::ptr::addr_of!(scratch.k)) };
4377 batched_rope_neox_backward(
4378 k_ref,
4379 &mut scratch.k,
4380 &scratch.rope_positions,
4381 nkv,
4382 hd,
4383 s,
4384 rope_theta,
4385 stream,
4386 )?;
4387 }
4388
4389 scratch.op_end(_t, OP_ATTN_BWD);
4390
4391 static USE_NF4_TC_BWD_ATTN: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4393 let nf4_tc_bwd = *USE_NF4_TC_BWD_ATTN
4394 .get_or_init(|| std::env::var("NF4_TC_BWD_GEMM").as_deref() == Ok("1"));
4395
4396 let _t = scratch.op_begin(); if nf4_tc_bwd {
4398 crate::autograd::cuda_forward::gemm_nf4_tc_backward_a(
4400 &scratch.q,
4401 &self.w_q_nf4,
4402 &self.w_q_scales,
4403 &mut scratch.o_proj_out,
4404 s,
4405 qd, h, stream,
4408 )?;
4409 } else {
4410 gemm_backward_a_fp16_dispatch(
4411 &scratch.q,
4412 self.w_q_fp16.as_ref(),
4413 &self.w_q_fp32,
4414 &mut scratch.o_proj_out,
4415 s,
4416 h,
4417 qd,
4418 stream,
4419 &self.ctx,
4420 )?;
4421 }
4422
4423 if let (Some(a_q), Some(b_q)) = (&self.lora_a_q, &self.lora_b_q) {
4425 let r = saturating_u32(self.lora_rank);
4426
4427 gemm_forward(&scratch.norm1_out, a_q, &mut scratch.lora_inter, s, h, r, stream)?;
4429
4430 gemm_backward_b(
4433 &scratch.lora_inter,
4434 &scratch.q,
4435 &mut grad_lora.grad_lora_b_q,
4436 s,
4437 r,
4438 qd,
4439 stream,
4440 )?;
4441
4442 gemm_backward_a(
4444 &scratch.q,
4445 b_q,
4446 &mut scratch.lora_inter, s,
4448 qd,
4449 r,
4450 stream,
4451 )?;
4452
4453 gemm_backward_b(
4455 &scratch.norm1_out,
4456 &scratch.lora_inter,
4457 &mut grad_lora.grad_lora_a_q,
4458 s,
4459 h,
4460 r,
4461 stream,
4462 )?;
4463
4464 gemm_backward_a(
4466 &scratch.lora_inter,
4467 a_q,
4468 &mut scratch.lora_temp, s,
4470 r,
4471 h,
4472 stream,
4473 )?;
4474 cuda_add_inplace(
4475 &mut scratch.o_proj_out,
4476 &scratch.lora_temp,
4477 seq_len * hidden_size,
4478 stream,
4479 )?;
4480 }
4481
4482 static USE_FUSED_BWD_ATTN: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
4484 let fused_bwd = *USE_FUSED_BWD_ATTN
4485 .get_or_init(|| std::env::var("NF4_FUSED_BWD_GEMM").as_deref() == Ok("1"));
4486
4487 if nf4_tc_bwd {
4488 crate::autograd::cuda_forward::gemm_nf4_tc_backward_a(
4490 &scratch.k,
4491 &self.w_k_nf4,
4492 &self.w_k_scales,
4493 &mut scratch.ffn_out,
4494 s,
4495 kvh, h, stream,
4498 )?;
4499 cuda_add_inplace(
4500 &mut scratch.o_proj_out,
4501 &scratch.ffn_out,
4502 seq_len * hidden_size,
4503 stream,
4504 )?;
4505 crate::autograd::cuda_forward::gemm_nf4_tc_backward_a(
4506 &scratch.v,
4507 &self.w_v_nf4,
4508 &self.w_v_scales,
4509 &mut scratch.ffn_out,
4510 s,
4511 kvh, h, stream,
4514 )?;
4515 cuda_add_inplace(
4516 &mut scratch.o_proj_out,
4517 &scratch.ffn_out,
4518 seq_len * hidden_size,
4519 stream,
4520 )?;
4521 } else if fused_bwd {
4522 gemm_backward_a_fp16_dispatch_accumulate(
4524 &scratch.k,
4525 self.w_k_fp16.as_ref(),
4526 &self.w_k_fp32,
4527 &mut scratch.o_proj_out,
4528 s,
4529 h,
4530 kvh,
4531 stream,
4532 &self.ctx,
4533 )?;
4534 gemm_backward_a_fp16_dispatch_accumulate(
4535 &scratch.v,
4536 self.w_v_fp16.as_ref(),
4537 &self.w_v_fp32,
4538 &mut scratch.o_proj_out,
4539 s,
4540 h,
4541 kvh,
4542 stream,
4543 &self.ctx,
4544 )?;
4545 } else {
4546 gemm_backward_a_fp16_dispatch(
4548 &scratch.k,
4549 self.w_k_fp16.as_ref(),
4550 &self.w_k_fp32,
4551 &mut scratch.ffn_out,
4552 s,
4553 h,
4554 kvh,
4555 stream,
4556 &self.ctx,
4557 )?;
4558 cuda_add_inplace(
4559 &mut scratch.o_proj_out,
4560 &scratch.ffn_out,
4561 seq_len * hidden_size,
4562 stream,
4563 )?;
4564
4565 gemm_backward_a_fp16_dispatch(
4566 &scratch.v,
4567 self.w_v_fp16.as_ref(),
4568 &self.w_v_fp32,
4569 &mut scratch.ffn_out,
4570 s,
4571 h,
4572 kvh,
4573 stream,
4574 &self.ctx,
4575 )?;
4576 cuda_add_inplace(
4577 &mut scratch.o_proj_out,
4578 &scratch.ffn_out,
4579 seq_len * hidden_size,
4580 stream,
4581 )?;
4582 }
4583
4584 if let (Some(a_v), Some(b_v)) = (&self.lora_a_v, &self.lora_b_v) {
4586 let r = saturating_u32(self.lora_rank);
4587
4588 gemm_forward(&scratch.norm1_out, a_v, &mut scratch.lora_inter, s, h, r, stream)?;
4590
4591 gemm_backward_b(
4593 &scratch.lora_inter,
4594 &scratch.v,
4595 &mut grad_lora.grad_lora_b_v,
4596 s,
4597 r,
4598 kvh,
4599 stream,
4600 )?;
4601
4602 gemm_backward_a(&scratch.v, b_v, &mut scratch.lora_inter, s, kvh, r, stream)?;
4604
4605 gemm_backward_b(
4607 &scratch.norm1_out,
4608 &scratch.lora_inter,
4609 &mut grad_lora.grad_lora_a_v,
4610 s,
4611 h,
4612 r,
4613 stream,
4614 )?;
4615
4616 gemm_backward_a(&scratch.lora_inter, a_v, &mut scratch.lora_temp, s, r, h, stream)?;
4618 cuda_add_inplace(
4619 &mut scratch.o_proj_out,
4620 &scratch.lora_temp,
4621 seq_len * hidden_size,
4622 stream,
4623 )?;
4624 }
4625
4626 scratch.op_end(_t, OP_QKV_BWD);
4627
4628 unsafe {
4631 scratch.grad_hidden.copy_from_buffer_async(&scratch.o_proj_out, stream).map_err(
4632 |e| {
4633 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
4634 "grad_norm1 copy failed: {e}"
4635 ))
4636 },
4637 )?;
4638 }
4639
4640 Ok(())
4641 }
4642
4643 fn backward_nf4_attention_mechanism(
4665 &self,
4666 seq_len: usize,
4667 num_heads: usize,
4668 head_dim: usize,
4669 stream: &CudaStream,
4670 scratch: &mut CudaBlockScratch,
4671 ) -> Result<()> {
4672 let num_kv_heads = self.config.num_kv_heads;
4673 let heads_per_kv = num_heads / num_kv_heads;
4674 let s = saturating_u32(seq_len);
4675 let nh = saturating_u32(num_heads);
4676 let nkv = saturating_u32(num_kv_heads);
4677 let hd = saturating_u32(head_dim);
4678 let scale = 1.0 / (head_dim as f32).sqrt();
4679
4680 interleaved_to_batched_forward(
4683 &scratch.attn_out,
4684 &mut scratch.attn_q_batched, s,
4686 nh,
4687 hd,
4688 stream,
4689 )?;
4690
4691 interleaved_to_batched_forward(&scratch.v, &mut scratch.attn_kv_temp, s, nkv, hd, stream)?;
4695
4696 if heads_per_kv > 1 {
4697 expand_kv_heads(
4698 &scratch.attn_kv_temp,
4699 &mut scratch.attn_kv_temp2,
4700 num_kv_heads,
4701 heads_per_kv,
4702 seq_len * head_dim,
4703 stream,
4704 )?;
4705 } else {
4706 unsafe {
4708 scratch
4709 .attn_kv_temp2
4710 .copy_from_buffer_async(&scratch.attn_kv_temp, stream)
4711 .map_err(|e| {
4712 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
4713 "V copy for attn backward: {e:?}"
4714 ))
4715 })?;
4716 }
4717 }
4718 batched_transpose_forward(
4722 &scratch.attn_kv_temp2,
4723 &mut scratch.attn_kv_temp, nh,
4725 s,
4726 hd,
4727 stream,
4728 )?;
4729
4730 batched_4d_gemm_forward(
4733 &scratch.attn_q_batched,
4734 &scratch.attn_kv_temp,
4735 &mut scratch.grad_attn_scores,
4736 1,
4737 nh,
4738 s,
4739 s,
4740 hd,
4741 stream,
4742 )?;
4743
4744 batched_transpose_forward(
4750 &scratch.attn_q_batched,
4751 &mut scratch.attn_kv_temp, nh,
4753 s,
4754 hd,
4755 stream,
4756 )?;
4757
4758 batched_4d_gemm_forward(
4760 &scratch.attn_kv_temp,
4761 &scratch.attn_scores, &mut scratch.attn_kv_temp2, 1,
4764 nh,
4765 hd,
4766 s,
4767 s,
4768 stream,
4769 )?;
4770
4771 batched_transpose_forward(
4773 &scratch.attn_kv_temp2,
4774 &mut scratch.attn_kv_temp, nh,
4776 hd,
4777 s,
4778 stream,
4779 )?;
4780 let total_rows = nh * s;
4785 {
4786 let grad_scores_view = unsafe {
4788 GpuBuffer::<f32>::from_raw_parts(
4789 scratch.grad_attn_scores.as_ptr(),
4790 scratch.grad_attn_scores.len(),
4791 )
4792 };
4793 batched_softmax_backward(
4794 &scratch.attn_scores,
4795 &grad_scores_view,
4796 &mut scratch.grad_attn_scores,
4797 total_rows,
4798 s,
4799 stream,
4800 )?;
4801 leak(grad_scores_view);
4802 }
4803
4804 let total_scores = saturating_u32(num_heads * seq_len * seq_len);
4806 {
4807 let scores_view = unsafe {
4809 GpuBuffer::<f32>::from_raw_parts(
4810 scratch.grad_attn_scores.as_ptr(),
4811 scratch.grad_attn_scores.len(),
4812 )
4813 };
4814 scale_forward(
4815 &scores_view,
4816 &mut scratch.grad_attn_scores,
4817 scale,
4818 total_scores,
4819 stream,
4820 )?;
4821 leak(scores_view);
4822 }
4823
4824 interleaved_to_batched_forward(&scratch.k, &mut scratch.attn_kv_temp2, s, nkv, hd, stream)?;
4826
4827 if heads_per_kv > 1 {
4828 unsafe {
4830 scratch
4831 .attn_q_batched
4832 .copy_from_buffer_async(&scratch.attn_kv_temp2, stream)
4833 .map_err(|e| {
4834 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
4835 "K copy for GQA expand: {e}"
4836 ))
4837 })?;
4838 }
4839 expand_kv_heads(
4840 &scratch.attn_q_batched,
4841 &mut scratch.attn_kv_temp2,
4842 num_kv_heads,
4843 heads_per_kv,
4844 seq_len * head_dim,
4845 stream,
4846 )?;
4847 }
4848 batched_4d_gemm_forward(
4852 &scratch.grad_attn_scores,
4853 &scratch.attn_kv_temp2,
4854 &mut scratch.attn_q_batched,
4855 1,
4856 nh,
4857 s,
4858 hd,
4859 s,
4860 stream,
4861 )?;
4862
4863 interleaved_to_batched_forward(
4867 &scratch.q,
4868 &mut scratch.o_proj_out, s,
4870 nh,
4871 hd,
4872 stream,
4873 )?;
4874
4875 batched_transpose_forward(
4877 &scratch.o_proj_out,
4878 &mut scratch.attn_kv_temp2, nh,
4880 s,
4881 hd,
4882 stream,
4883 )?;
4884
4885 batched_4d_gemm_forward(
4887 &scratch.attn_kv_temp2,
4888 &scratch.grad_attn_scores,
4889 &mut scratch.ffn_out, 1,
4891 nh,
4892 hd,
4893 s,
4894 s,
4895 stream,
4896 )?;
4897
4898 batched_transpose_forward(
4900 &scratch.ffn_out,
4901 &mut scratch.attn_kv_temp2, nh,
4903 hd,
4904 s,
4905 stream,
4906 )?;
4907
4908 if heads_per_kv > 1 {
4910 self.reduce_gqa_gradients_nf4(
4911 num_kv_heads,
4912 heads_per_kv,
4913 seq_len,
4914 head_dim,
4915 stream,
4916 scratch,
4917 )?;
4918 }
4919
4920 batched_to_interleaved_forward(&scratch.attn_q_batched, &mut scratch.q, s, nh, hd, stream)?;
4923
4924 batched_to_interleaved_forward(&scratch.attn_kv_temp2, &mut scratch.k, s, nkv, hd, stream)?;
4926
4927 batched_to_interleaved_forward(&scratch.attn_kv_temp, &mut scratch.v, s, nkv, hd, stream)?;
4930
4931 Ok(())
4932 }
4933
4934 fn reduce_gqa_gradients_nf4(
4938 &self,
4939 num_kv_heads: usize,
4940 heads_per_kv: usize,
4941 seq_len: usize,
4942 head_dim: usize,
4943 stream: &CudaStream,
4944 scratch: &mut CudaBlockScratch,
4945 ) -> Result<()> {
4946 let chunk = seq_len * head_dim;
4947 for g in 0..num_kv_heads {
4948 let dst_off = g * chunk;
4949 let src_off = g * heads_per_kv * chunk;
4951 {
4953 let src = unsafe {
4955 GpuBuffer::<f32>::from_raw_parts(
4956 scratch.attn_kv_temp2.as_ptr() + (src_off * 4) as u64,
4957 chunk,
4958 )
4959 };
4960 let mut dst = unsafe {
4962 GpuBuffer::<f32>::from_raw_parts(
4963 scratch.attn_kv_temp2.as_ptr() + (dst_off * 4) as u64,
4964 chunk,
4965 )
4966 };
4967 if src_off != dst_off {
4968 unsafe {
4970 dst.copy_from_buffer_async(&src, stream).map_err(|e| {
4971 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
4972 "GQA K reduce copy: {e}"
4973 ))
4974 })?;
4975 }
4976 }
4977 leak(src);
4978 leak(dst);
4979 }
4980 for h in 1..heads_per_kv {
4982 let add_off = (g * heads_per_kv + h) * chunk;
4983 let src = unsafe {
4985 GpuBuffer::<f32>::from_raw_parts(
4986 scratch.attn_kv_temp2.as_ptr() + (add_off * 4) as u64,
4987 chunk,
4988 )
4989 };
4990 let mut dst = unsafe {
4992 GpuBuffer::<f32>::from_raw_parts(
4993 scratch.attn_kv_temp2.as_ptr() + (dst_off * 4) as u64,
4994 chunk,
4995 )
4996 };
4997 cuda_add_inplace(&mut dst, &src, chunk, stream)?;
4998 leak(src);
4999 leak(dst);
5000 }
5001 {
5003 let src = unsafe {
5005 GpuBuffer::<f32>::from_raw_parts(
5006 scratch.attn_kv_temp.as_ptr() + (src_off * 4) as u64,
5007 chunk,
5008 )
5009 };
5010 let mut dst = unsafe {
5012 GpuBuffer::<f32>::from_raw_parts(
5013 scratch.attn_kv_temp.as_ptr() + (dst_off * 4) as u64,
5014 chunk,
5015 )
5016 };
5017 if src_off != dst_off {
5018 unsafe {
5020 dst.copy_from_buffer_async(&src, stream).map_err(|e| {
5021 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
5022 "GQA V reduce copy: {e}"
5023 ))
5024 })?;
5025 }
5026 }
5027 leak(src);
5028 leak(dst);
5029 }
5030 for h in 1..heads_per_kv {
5031 let add_off = (g * heads_per_kv + h) * chunk;
5032 let src = unsafe {
5034 GpuBuffer::<f32>::from_raw_parts(
5035 scratch.attn_kv_temp.as_ptr() + (add_off * 4) as u64,
5036 chunk,
5037 )
5038 };
5039 let mut dst = unsafe {
5041 GpuBuffer::<f32>::from_raw_parts(
5042 scratch.attn_kv_temp.as_ptr() + (dst_off * 4) as u64,
5043 chunk,
5044 )
5045 };
5046 cuda_add_inplace(&mut dst, &src, chunk, stream)?;
5047 leak(src);
5048 leak(dst);
5049 }
5050 }
5051 Ok(())
5052 }
5053
5054 pub(crate) fn init_lora_optimizer_state(&self) -> Result<GpuLoraOptimizerState> {
5056 GpuLoraOptimizerState::new(&self.ctx, &self.config, self.lora_rank)
5057 }
5058
5059 #[allow(clippy::too_many_arguments)]
5061 pub(crate) fn lora_optimizer_step(
5062 &mut self,
5063 state: &mut GpuLoraOptimizerState,
5064 step: u32,
5065 lr: f32,
5066 beta1: f32,
5067 beta2: f32,
5068 eps: f32,
5069 weight_decay: f32,
5070 stream: &CudaStream,
5071 grad_lora: &CudaLoraGradWorkspace,
5072 ) -> Result<()> {
5073 let h = self.config.hidden_size;
5074 let q_dim = self.config.q_dim();
5075 let kv = self.config.num_kv_heads * self.config.head_dim();
5076 let r = self.lora_rank;
5077
5078 if let Some(ref mut a_q) = self.lora_a_q {
5080 adamw_step_cuda(
5081 a_q,
5082 &grad_lora.grad_lora_a_q,
5083 &mut state.m_lora_a_q,
5084 &mut state.v_lora_a_q,
5085 lr,
5086 beta1,
5087 beta2,
5088 eps,
5089 weight_decay,
5090 step,
5091 saturating_u32(h * r),
5092 stream,
5093 )?;
5094 }
5095 if let Some(ref mut b_q) = self.lora_b_q {
5096 adamw_step_cuda(
5097 b_q,
5098 &grad_lora.grad_lora_b_q,
5099 &mut state.m_lora_b_q,
5100 &mut state.v_lora_b_q,
5101 lr,
5102 beta1,
5103 beta2,
5104 eps,
5105 weight_decay,
5106 step,
5107 saturating_u32(r * q_dim),
5108 stream,
5109 )?;
5110 }
5111 if let Some(ref mut a_v) = self.lora_a_v {
5112 adamw_step_cuda(
5113 a_v,
5114 &grad_lora.grad_lora_a_v,
5115 &mut state.m_lora_a_v,
5116 &mut state.v_lora_a_v,
5117 lr,
5118 beta1,
5119 beta2,
5120 eps,
5121 weight_decay,
5122 step,
5123 saturating_u32(h * r),
5124 stream,
5125 )?;
5126 }
5127 if let Some(ref mut b_v) = self.lora_b_v {
5128 adamw_step_cuda(
5129 b_v,
5130 &grad_lora.grad_lora_b_v,
5131 &mut state.m_lora_b_v,
5132 &mut state.v_lora_b_v,
5133 lr,
5134 beta1,
5135 beta2,
5136 eps,
5137 weight_decay,
5138 step,
5139 saturating_u32(r * kv),
5140 stream,
5141 )?;
5142 }
5143
5144 adamw_step_cuda(
5146 &mut self.input_norm_weight,
5147 &grad_lora.grad_input_norm,
5148 &mut state.m_input_norm,
5149 &mut state.v_input_norm,
5150 lr,
5151 beta1,
5152 beta2,
5153 eps,
5154 weight_decay,
5155 step,
5156 saturating_u32(h),
5157 stream,
5158 )?;
5159 adamw_step_cuda(
5160 &mut self.post_attn_norm_weight,
5161 &grad_lora.grad_post_attn_norm,
5162 &mut state.m_post_attn_norm,
5163 &mut state.v_post_attn_norm,
5164 lr,
5165 beta1,
5166 beta2,
5167 eps,
5168 weight_decay,
5169 step,
5170 saturating_u32(h),
5171 stream,
5172 )?;
5173
5174 Ok(())
5175 }
5176
5177 pub fn download_lora_weights(&self) -> Result<(Vec<f32>, Vec<f32>, Vec<f32>, Vec<f32>)> {
5183 let download = |buf: &GpuBuffer<f32>| -> Result<Vec<f32>> {
5184 let mut host = vec![0.0f32; buf.len()];
5185 buf.copy_to_host(&mut host).map_err(|e| {
5186 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
5187 "LoRA weight download failed: {e}"
5188 ))
5189 })?;
5190 Ok(host)
5191 };
5192 let a_q = self.lora_a_q.as_ref().map(&download).transpose()?.unwrap_or_default();
5193 let b_q = self.lora_b_q.as_ref().map(&download).transpose()?.unwrap_or_default();
5194 let a_v = self.lora_a_v.as_ref().map(&download).transpose()?.unwrap_or_default();
5195 let b_v = self.lora_b_v.as_ref().map(&download).transpose()?.unwrap_or_default();
5196 Ok((a_q, b_q, a_v, b_v))
5197 }
5198
5199 pub fn upload_lora_weights(
5205 &mut self,
5206 a_q: &[f32],
5207 b_q: &[f32],
5208 a_v: &[f32],
5209 b_v: &[f32],
5210 ) -> Result<()> {
5211 let upload = |buf: &mut GpuBuffer<f32>, data: &[f32], name: &str| -> Result<()> {
5212 if data.len() != buf.len() {
5213 return Err(crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(
5214 format!(
5215 "LoRA {name} size mismatch: checkpoint has {} but GPU buffer expects {}",
5216 data.len(),
5217 buf.len()
5218 ),
5219 ));
5220 }
5221 buf.copy_from_host(data).map_err(|e| {
5222 crate::autograd::cuda_tensor::CudaTensorError::TransferFailed(format!(
5223 "LoRA {name} upload failed: {e}"
5224 ))
5225 })
5226 };
5227 if let Some(ref mut buf) = self.lora_a_q {
5228 upload(buf, a_q, "a_q")?;
5229 }
5230 if let Some(ref mut buf) = self.lora_b_q {
5231 upload(buf, b_q, "b_q")?;
5232 }
5233 if let Some(ref mut buf) = self.lora_a_v {
5234 upload(buf, a_v, "a_v")?;
5235 }
5236 if let Some(ref mut buf) = self.lora_b_v {
5237 upload(buf, b_v, "b_v")?;
5238 }
5239 Ok(())
5240 }
5241}
5242
5243#[cfg(all(test, feature = "cuda"))]
5244#[path = "cuda_block_parity_probe.rs"]
5245mod parity_probe;
5246
5247#[cfg(test)]
5248mod tests {
5249 #[test]
5250 fn test_cuda_block_compiles() {
5251 #[cfg(feature = "cuda")]
5253 {
5254 use super::*;
5255 let _ = std::mem::size_of::<CudaTransformerBlock>();
5256 let _ = std::mem::size_of::<CudaNf4TransformerBlock>();
5257 }
5258 }
5259}