ferrotensor_graph/flash_attention.rs
1//! Flash Attention - Memory-Efficient Attention Mechanism
2//!
3//! Implements FlashAttention-2 algorithm for O(N) memory instead of O(N²).
4//! Uses tiled matrix multiplication with online softmax normalization.
5
6use ferrotensor_backend::{AttentionOptions, ComputeEngine, UnifiedTensor, WgpuContext};
7use std::sync::Arc;
8
9/// Flash Attention layer with tiled computation and online softmax.
10///
11/// Memory efficient: O(N) instead of O(N²) for standard attention.
12/// Speed: 2-4x faster than naive implementation.
13///
14/// # Example
15/// ```no_run
16/// # use ferrotensor_backend::{ComputeEngine, UnifiedTensor, WgpuContext};
17/// # use ferrotensor_graph::flash_attention::{FlashAttention};
18/// let ctx = pollster::block_on(WgpuContext::new());
19/// let engine = ComputeEngine::new(&ctx);
20/// // [batch, seq_len, hidden]
21/// let q = UnifiedTensor::zeros(&ctx, &[1, 128, 768]);
22/// let k = UnifiedTensor::zeros(&ctx, &[1, 128, 768]);
23/// let v = UnifiedTensor::zeros(&ctx, &[1, 128, 768]);
24/// let flash_attn = FlashAttention::new(768, 12, true);
25/// let output = flash_attn.forward(&q, &k, &v, &ctx, &engine);
26/// ```
27pub struct FlashAttention {
28 dim: usize,
29 num_heads: usize,
30 head_dim: usize,
31 causal: bool,
32 #[allow(dead_code)]
33 scale: f32,
34
35 // Optional: Use optimized multi-head kernel
36 #[allow(dead_code)]
37 use_multi_head_kernel: bool,
38}
39
40/// KV-Cache for efficient autoregressive generation
41pub struct KVCache {
42 /// Cached keys for each layer
43 pub k_cache: Vec<Arc<UnifiedTensor>>,
44 /// Cached values for each layer
45 pub v_cache: Vec<Arc<UnifiedTensor>>,
46 /// Maximum sequence length
47 pub max_seq_len: usize,
48 /// Current position in cache
49 pub current_pos: usize,
50}
51
52impl FlashAttention {
53 /// Create a new FlashAttention layer.
54 ///
55 /// # Arguments
56 /// * `dim` - Total dimension (will be split across heads)
57 /// * `num_heads` - Number of attention heads
58 /// * `causal` - Whether to apply causal masking (for autoregressive models)
59 pub fn new(dim: usize, num_heads: usize, causal: bool) -> Self {
60 let head_dim = dim / num_heads;
61 let scale = 1.0 / (head_dim as f32).sqrt();
62 Self {
63 dim,
64 num_heads,
65 head_dim,
66 causal,
67 scale,
68 use_multi_head_kernel: num_heads > 1,
69 }
70 }
71
72 /// `[seq, dim]`, `[batch, seq, dim]` or `[batch, seq, heads, head_dim]`
73 /// viewed as `[batch, seq, num_heads, head_dim]`.
74 fn heads_view(&self, t: &UnifiedTensor) -> UnifiedTensor {
75 let (batch, seq) = match t.shape.len() {
76 2 => (1, t.shape[0]),
77 3 | 4 => (t.shape[0], t.shape[1]),
78 n => panic!("FlashAttention expects a 2-D, 3-D or 4-D tensor, got {n}-D"),
79 };
80 assert_eq!(
81 t.shape.iter().product::<usize>(),
82 batch * seq * self.dim,
83 "tensor {:?} does not have dim {} per token",
84 t.shape,
85 self.dim
86 );
87 t.reshaped(&[batch, seq, self.num_heads, self.head_dim])
88 }
89
90 /// Forward pass using Flash Attention algorithm.
91 ///
92 /// # Arguments
93 /// * `q` - Query tensor `[batch, seq_q, dim]`, `[seq_q, dim]` or
94 /// `[batch, seq_q, num_heads, head_dim]`
95 /// * `k` - Key tensor with the same layout and `seq_k` tokens
96 /// * `v` - Value tensor with the same layout as `k`
97 ///
98 /// # Returns
99 /// Output tensor with the same shape as `q`.
100 pub fn forward(
101 &self,
102 q: &UnifiedTensor,
103 k: &UnifiedTensor,
104 v: &UnifiedTensor,
105 ctx: &WgpuContext,
106 engine: &ComputeEngine,
107 ) -> UnifiedTensor {
108 let output = UnifiedTensor::empty(ctx, &q.shape);
109 engine.flash_attention_v2(
110 ctx,
111 &self.heads_view(q),
112 &self.heads_view(k),
113 &self.heads_view(v),
114 &self.heads_view(&output),
115 self.causal,
116 0.0,
117 0,
118 None,
119 );
120 output
121 }
122
123 /// Multi-head attention forward pass with automatic head splitting.
124 ///
125 /// This method automatically splits the input into heads and applies
126 /// Flash Attention to each head in parallel.
127 ///
128 /// # Arguments
129 /// * `q` - Query tensor [batch, seq_q, dim]
130 /// * `k` - Key tensor [batch, seq_k, dim]
131 /// * `v` - Value tensor [batch, seq_k, dim]
132 ///
133 /// # Returns
134 /// Output tensor [batch, seq_q, dim]
135 pub fn forward_multi_head(
136 &self,
137 q: &UnifiedTensor,
138 k: &UnifiedTensor,
139 v: &UnifiedTensor,
140 ctx: &WgpuContext,
141 engine: &ComputeEngine,
142 ) -> UnifiedTensor {
143 // TODO: Implement proper head splitting and parallel computation
144 // For now, delegate to simple forward
145 self.forward(q, k, v, ctx, engine)
146 }
147
148 /// Multi-head attention with Flash Attention.
149 ///
150 /// Handles Q, K, V projection and output projection internally.
151 pub fn multi_head_forward(
152 &self,
153 hidden: &UnifiedTensor,
154 ctx: &WgpuContext,
155 engine: &ComputeEngine,
156 ) -> UnifiedTensor {
157 // For simple case, treat as self-attention
158 self.forward(hidden, hidden, hidden, ctx, engine)
159 }
160
161 /// Get memory savings compared to standard attention.
162 pub fn memory_savings(&self, seq_len: usize) -> String {
163 let standard_memory = seq_len * seq_len * 4; // O(N²) for attention matrix
164 let flash_memory = seq_len * self.head_dim * 4; // O(N) for accumulator
165 let savings = standard_memory as f32 / flash_memory as f32;
166 format!(
167 "{:.1}x less memory ({}MB vs {}MB for seq={})",
168 savings,
169 flash_memory / 1_000_000,
170 standard_memory / 1_000_000,
171 seq_len
172 )
173 }
174
175 /// Backward pass for Flash Attention.
176 ///
177 /// Computes gradients dQ, dK, dV using recomputation to avoid
178 /// storing the full attention matrix.
179 ///
180 /// # Arguments
181 /// * `q` - Query tensor from forward pass
182 /// * `k` - Key tensor from forward pass
183 /// * `v` - Value tensor from forward pass
184 /// * `output` - Output from forward pass
185 /// * `grad_output` - Gradient of the output
186 ///
187 /// # Returns
188 /// FlashAttentionGrads containing dQ, dK, dV
189 #[allow(clippy::too_many_arguments)]
190 pub fn backward(
191 &self,
192 q: &UnifiedTensor,
193 k: &UnifiedTensor,
194 v: &UnifiedTensor,
195 output: &UnifiedTensor,
196 grad_output: &UnifiedTensor,
197 ctx: &WgpuContext,
198 engine: &ComputeEngine,
199 ) -> FlashAttentionGrads {
200 let grad_q = UnifiedTensor::empty(ctx, &q.shape);
201 let grad_k = UnifiedTensor::empty(ctx, &k.shape);
202 let grad_v = UnifiedTensor::empty(ctx, &v.shape);
203
204 engine.flash_attention_v2_backward(
205 ctx,
206 &self.heads_view(q),
207 &self.heads_view(k),
208 &self.heads_view(v),
209 &self.heads_view(output),
210 &self.heads_view(grad_output),
211 &self.heads_view(&grad_q),
212 &self.heads_view(&grad_k),
213 &self.heads_view(&grad_v),
214 self.causal,
215 );
216
217 FlashAttentionGrads {
218 grad_q,
219 grad_k,
220 grad_v,
221 }
222 }
223}
224
225/// Gradients from Flash Attention backward pass.
226#[derive(Debug)]
227pub struct FlashAttentionGrads {
228 /// Gradient with respect to queries
229 pub grad_q: UnifiedTensor,
230 /// Gradient with respect to keys
231 pub grad_k: UnifiedTensor,
232 /// Gradient with respect to values
233 pub grad_v: UnifiedTensor,
234}
235
236impl std::fmt::Debug for FlashAttention {
237 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 f.debug_struct("FlashAttention")
239 .field("dim", &self.dim)
240 .field("num_heads", &self.num_heads)
241 .field("head_dim", &self.head_dim)
242 .field("causal", &self.causal)
243 .finish()
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_memory_savings() {
253 let attn = FlashAttention::new(512, 8, true);
254 let savings = attn.memory_savings(2048);
255 assert!(savings.contains("x less memory"));
256 }
257
258 #[test]
259 fn test_creation() {
260 let attn = FlashAttention::new(768, 12, true);
261 assert_eq!(attn.dim, 768);
262 assert_eq!(attn.num_heads, 12);
263 assert_eq!(attn.head_dim, 64);
264 assert!(attn.causal);
265 }
266}
267
268// ============================================================================
269// GROUPED QUERY ATTENTION (GQA) - Used by Llama 3, Mistral, etc.
270// ============================================================================
271
272/// Grouped Query Attention (GQA) - Efficient attention with fewer KV heads
273///
274/// GQA uses fewer key-value heads than query heads, reducing memory and
275/// computation while maintaining quality. This is the attention mechanism
276/// used by Llama 3, Mistral, and many modern LLMs.
277///
278/// # Memory Savings
279/// - Standard MHA: `num_heads` K and V projections
280/// - GQA: `num_kv_heads` K and V projections (typically 2-8x fewer)
281///
282/// # Example
283/// ```no_run
284/// # use ferrotensor_backend::{ComputeEngine, UnifiedTensor, WgpuContext};
285/// # use ferrotensor_graph::flash_attention::{GroupedQueryAttention};
286/// let ctx = pollster::block_on(WgpuContext::new());
287/// let engine = ComputeEngine::new(&ctx);
288/// // [batch, seq_len, hidden]
289/// let q = UnifiedTensor::zeros(&ctx, &[1, 128, 4096]);
290/// let k = UnifiedTensor::zeros(&ctx, &[1, 128, 1024]);
291/// let v = UnifiedTensor::zeros(&ctx, &[1, 128, 1024]);
292/// // Llama 3 8B: 32 query heads share 8 KV heads (no dropout, no mask)
293/// let gqa = GroupedQueryAttention::new(4096, 32, 8, true, 0.0);
294/// let output = gqa.forward(&q, &k, &v, None, &ctx, &engine);
295/// ```
296pub struct GroupedQueryAttention {
297 /// Total hidden dimension
298 pub dim: usize,
299 /// Number of query heads
300 pub num_heads: usize,
301 /// Number of key-value heads (fewer than query heads)
302 pub num_kv_heads: usize,
303 /// Dimension per head
304 pub head_dim: usize,
305 /// Number of query heads per KV head
306 pub num_groups: usize,
307 /// Whether to use causal masking
308 pub causal: bool,
309 /// Scaling factor for attention scores
310 #[allow(dead_code)]
311 scale: f32,
312 /// Dropout probability (0.0 = no dropout)
313 pub dropout_prob: f32,
314}
315
316impl GroupedQueryAttention {
317 /// Create a new GQA layer.
318 ///
319 /// # Arguments
320 /// * `dim` - Total hidden dimension
321 /// * `num_heads` - Number of query heads
322 /// * `num_kv_heads` - Number of key-value heads (must divide num_heads evenly)
323 /// * `causal` - Whether to apply causal masking
324 /// * `dropout_prob` - Dropout probability
325 ///
326 /// # Panics
327 /// Panics if `num_heads` is not divisible by `num_kv_heads`
328 pub fn new(
329 dim: usize,
330 num_heads: usize,
331 num_kv_heads: usize,
332 causal: bool,
333 dropout_prob: f32,
334 ) -> Self {
335 assert!(
336 num_heads.is_multiple_of(num_kv_heads),
337 "num_heads ({}) must be divisible by num_kv_heads ({})",
338 num_heads,
339 num_kv_heads
340 );
341
342 let head_dim = dim / num_heads;
343 let num_groups = num_heads / num_kv_heads;
344 let scale = 1.0 / (head_dim as f32).sqrt();
345
346 Self {
347 dim,
348 num_heads,
349 num_kv_heads,
350 head_dim,
351 num_groups,
352 causal,
353 scale,
354 dropout_prob,
355 }
356 }
357
358 /// Forward pass with grouped query attention.
359 ///
360 /// # Arguments
361 /// * `q` - Query tensor [batch, seq_q, num_heads, head_dim]
362 /// * `k` - Key tensor [batch, seq_k, num_kv_heads, head_dim]
363 /// * `v` - Value tensor [batch, seq_k, num_kv_heads, head_dim]
364 ///
365 /// # Returns
366 /// Output tensor [batch, seq_q, num_heads, head_dim]
367 pub fn forward(
368 &self,
369 q: &UnifiedTensor,
370 k: &UnifiedTensor,
371 v: &UnifiedTensor,
372 mask: Option<&UnifiedTensor>,
373 ctx: &WgpuContext,
374 engine: &ComputeEngine,
375 ) -> UnifiedTensor {
376 self.forward_at(q, k, v, mask, 0, ctx, engine)
377 }
378
379 /// Like [`forward`](Self::forward), with the first query at absolute
380 /// position `pos`: query `i` attends to keys `0..=pos + i` when causal.
381 /// Used to decode against a KV cache, where `k`/`v` hold the cache.
382 #[allow(clippy::too_many_arguments)]
383 pub fn forward_at(
384 &self,
385 q: &UnifiedTensor,
386 k: &UnifiedTensor,
387 v: &UnifiedTensor,
388 mask: Option<&UnifiedTensor>,
389 pos: usize,
390 ctx: &WgpuContext,
391 engine: &ComputeEngine,
392 ) -> UnifiedTensor {
393 // Validation: Expect 4D tensors [batch, seq, heads, dim]
394 if q.shape.len() != 4 || k.shape.len() != 4 || v.shape.len() != 4 {
395 panic!("GroupedQueryAttention expects 4D tensors (batch, seq, heads, dim)");
396 }
397
398 let batch_size = q.shape[0];
399 let seq_q = q.shape[1];
400
401 let output = UnifiedTensor::empty(ctx, &[batch_size, seq_q, self.num_heads, self.head_dim]);
402
403 // Use time-based seed for stochastic dropout
404 let seed = std::time::SystemTime::now()
405 .duration_since(std::time::UNIX_EPOCH)
406 .unwrap_or_default()
407 .as_nanos() as u64;
408
409 let opts = AttentionOptions {
410 causal: self.causal,
411 causal_offset: pos as u32,
412 dropout_prob: self.dropout_prob,
413 seed,
414 ..AttentionOptions::default()
415 };
416 engine.flash_attention_v2_with(ctx, q, k, v, &output, &opts, mask);
417
418 output
419 }
420
421 /// Get memory savings compared to standard MHA
422 pub fn memory_savings(&self) -> String {
423 let mha_kv_params = 2 * self.num_heads * self.head_dim;
424 let gqa_kv_params = 2 * self.num_kv_heads * self.head_dim;
425 let savings = mha_kv_params as f32 / gqa_kv_params as f32;
426
427 format!(
428 "{:.1}x KV memory savings ({} vs {} KV heads)",
429 savings, self.num_kv_heads, self.num_heads
430 )
431 }
432}
433
434// ============================================================================
435// MULTI-QUERY ATTENTION (MQA) - Single KV head for maximum efficiency
436// ============================================================================
437
438/// Multi-Query Attention (MQA) - Extreme efficiency with single KV head
439///
440/// MQA uses a single key-value head shared across all query heads.
441/// This provides maximum memory efficiency at some quality cost.
442///
443/// # Use Cases
444/// - Inference-optimized models
445/// - Memory-constrained deployments
446/// - High-throughput serving
447///
448/// # Example
449/// ```no_run
450/// # use ferrotensor_backend::{ComputeEngine, UnifiedTensor, WgpuContext};
451/// # use ferrotensor_graph::flash_attention::{MultiQueryAttention};
452/// let ctx = pollster::block_on(WgpuContext::new());
453/// let engine = ComputeEngine::new(&ctx);
454/// // [batch, seq_len, hidden]
455/// let q = UnifiedTensor::zeros(&ctx, &[1, 128, 768]);
456/// let k = UnifiedTensor::zeros(&ctx, &[1, 128, 64]);
457/// let v = UnifiedTensor::zeros(&ctx, &[1, 128, 64]);
458/// // 12 query heads share one KV head
459/// let mqa = MultiQueryAttention::new(768, 12, true, 0.0);
460/// let output = mqa.forward(&q, &k, &v, None, &ctx, &engine);
461/// ```
462pub struct MultiQueryAttention {
463 /// Total hidden dimension
464 pub dim: usize,
465 /// Number of query heads
466 pub num_heads: usize,
467 /// Dimension per head
468 pub head_dim: usize,
469 /// Whether to use causal masking
470 pub causal: bool,
471 /// Internal GQA with num_kv_heads = 1
472 gqa: GroupedQueryAttention,
473}
474
475impl MultiQueryAttention {
476 /// Create a new MQA layer.
477 ///
478 /// # Arguments
479 /// * `dim` - Total hidden dimension
480 /// * `num_heads` - Number of query heads
481 /// * `causal` - Whether to apply causal masking
482 pub fn new(dim: usize, num_heads: usize, causal: bool, dropout_prob: f32) -> Self {
483 let head_dim = dim / num_heads;
484 let gqa = GroupedQueryAttention::new(dim, num_heads, 1, causal, dropout_prob);
485
486 Self {
487 dim,
488 num_heads,
489 head_dim,
490 causal,
491 gqa,
492 }
493 }
494
495 /// Forward pass with multi-query attention.
496 ///
497 /// # Arguments
498 /// * `q` - Query tensor [batch, seq_q, num_heads, head_dim]
499 /// * `k` - Key tensor [batch, seq_k, 1, head_dim]
500 /// * `v` - Value tensor [batch, seq_k, 1, head_dim]
501 ///
502 /// # Returns
503 /// Output tensor [batch, seq_q, num_heads, head_dim]
504 pub fn forward(
505 &self,
506 q: &UnifiedTensor,
507 k: &UnifiedTensor,
508 v: &UnifiedTensor,
509 mask: Option<&UnifiedTensor>,
510 ctx: &WgpuContext,
511 engine: &ComputeEngine,
512 ) -> UnifiedTensor {
513 // Delegate to GQA with num_kv_heads = 1
514 self.gqa.forward(q, k, v, mask, ctx, engine)
515 }
516
517 /// Get memory savings compared to MHA
518 pub fn memory_savings(&self) -> String {
519 format!(
520 "{}x KV memory savings (1 vs {} KV heads)",
521 self.num_heads, self.num_heads
522 )
523 }
524}
525
526// ============================================================================
527// SLIDING WINDOW ATTENTION - For long sequences
528// ============================================================================
529
530/// Sliding Window Attention for efficient long-context processing
531///
532/// Limits attention to a local window around each position, enabling
533/// linear memory scaling for very long sequences.
534///
535/// # Use Cases
536/// - Long document processing (100K+ tokens)
537/// - Streaming input processing
538/// - Memory-efficient inference
539///
540/// # Example
541/// ```no_run
542/// # use ferrotensor_backend::{ComputeEngine, UnifiedTensor, WgpuContext};
543/// # use ferrotensor_graph::flash_attention::{SlidingWindowAttention};
544/// let ctx = pollster::block_on(WgpuContext::new());
545/// let engine = ComputeEngine::new(&ctx);
546/// // [batch, seq_len, hidden]
547/// let q = UnifiedTensor::zeros(&ctx, &[1, 128, 768]);
548/// let k = UnifiedTensor::zeros(&ctx, &[1, 128, 768]);
549/// let v = UnifiedTensor::zeros(&ctx, &[1, 128, 768]);
550/// let swa = SlidingWindowAttention::new(768, 12, 4096, true);
551/// let output = swa.forward(&q, &k, &v, &ctx, &engine);
552/// ```
553pub struct SlidingWindowAttention {
554 /// Total hidden dimension
555 pub dim: usize,
556 /// Number of attention heads
557 pub num_heads: usize,
558 /// Dimension per head
559 pub head_dim: usize,
560 /// Window size for local attention
561 pub window_size: usize,
562 /// Whether to use causal masking
563 pub causal: bool,
564 #[allow(dead_code)]
565 scale: f32,
566}
567
568impl SlidingWindowAttention {
569 /// Create a new sliding window attention layer.
570 ///
571 /// # Arguments
572 /// * `dim` - Total hidden dimension
573 /// * `num_heads` - Number of attention heads
574 /// * `window_size` - Maximum distance for attention (tokens only attend within this window)
575 /// * `causal` - Whether to apply causal masking
576 pub fn new(dim: usize, num_heads: usize, window_size: usize, causal: bool) -> Self {
577 let head_dim = dim / num_heads;
578 let scale = 1.0 / (head_dim as f32).sqrt();
579
580 Self {
581 dim,
582 num_heads,
583 head_dim,
584 window_size,
585 causal,
586 scale,
587 }
588 }
589
590 /// Forward pass with sliding window attention.
591 ///
592 /// Each position can only attend to positions within `window_size` distance.
593 ///
594 /// # Arguments
595 /// * `q` - Query tensor [batch, seq_q, dim]
596 /// * `k` - Key tensor [batch, seq_k, dim]
597 /// * `v` - Value tensor [batch, seq_k, dim]
598 ///
599 /// # Returns
600 /// Output tensor [batch, seq_q, dim]
601 pub fn forward(
602 &self,
603 q: &UnifiedTensor,
604 k: &UnifiedTensor,
605 v: &UnifiedTensor,
606 ctx: &WgpuContext,
607 engine: &ComputeEngine,
608 ) -> UnifiedTensor {
609 let view = |t: &UnifiedTensor| {
610 t.reshaped(&[t.shape[0], t.shape[1], self.num_heads, self.head_dim])
611 };
612 let output = UnifiedTensor::empty(ctx, &q.shape);
613 let opts = AttentionOptions {
614 causal: self.causal,
615 window: self.window_size as u32,
616 ..AttentionOptions::default()
617 };
618 engine.flash_attention_v2_with(
619 ctx,
620 &view(q),
621 &view(k),
622 &view(v),
623 &view(&output),
624 &opts,
625 None,
626 );
627 output
628 }
629
630 /// Get memory savings compared to full attention
631 pub fn memory_savings(&self, seq_len: usize) -> String {
632 let full_attention = seq_len * seq_len;
633 let window_attention = seq_len * self.window_size.min(seq_len);
634 let savings = full_attention as f32 / window_attention as f32;
635
636 format!(
637 "{:.1}x attention savings (window={}, seq={})",
638 savings, self.window_size, seq_len
639 )
640 }
641}
642
643// ============================================================================
644// ATTENTION MASK - Custom masking patterns
645// ============================================================================
646
647/// Attention mask for custom attention patterns
648#[derive(Debug, Clone)]
649pub enum AttentionMask {
650 /// No mask (full attention)
651 None,
652 /// Causal mask (lower triangular)
653 Causal,
654 /// Padding mask to ignore padded tokens
655 Padding(Vec<usize>), // lengths of non-padded sequences
656 /// Sliding window mask
657 SlidingWindow(usize),
658 /// Custom mask tensor
659 Custom(Arc<UnifiedTensor>),
660}
661
662impl AttentionMask {
663 /// Create a causal mask
664 pub fn causal() -> Self {
665 Self::Causal
666 }
667
668 /// Create a padding mask from sequence lengths
669 pub fn from_lengths(lengths: Vec<usize>) -> Self {
670 Self::Padding(lengths)
671 }
672
673 /// Create a sliding window mask
674 pub fn sliding_window(window_size: usize) -> Self {
675 Self::SlidingWindow(window_size)
676 }
677}
678
679impl std::fmt::Debug for GroupedQueryAttention {
680 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
681 f.debug_struct("GroupedQueryAttention")
682 .field("dim", &self.dim)
683 .field("num_heads", &self.num_heads)
684 .field("num_kv_heads", &self.num_kv_heads)
685 .field("num_groups", &self.num_groups)
686 .field("head_dim", &self.head_dim)
687 .field("causal", &self.causal)
688 .field("dropout", &self.dropout_prob)
689 .finish()
690 }
691}
692
693impl std::fmt::Debug for MultiQueryAttention {
694 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
695 f.debug_struct("MultiQueryAttention")
696 .field("dim", &self.dim)
697 .field("num_heads", &self.num_heads)
698 .field("head_dim", &self.head_dim)
699 .field("causal", &self.causal)
700 .finish()
701 }
702}
703
704impl std::fmt::Debug for SlidingWindowAttention {
705 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
706 f.debug_struct("SlidingWindowAttention")
707 .field("dim", &self.dim)
708 .field("num_heads", &self.num_heads)
709 .field("head_dim", &self.head_dim)
710 .field("window_size", &self.window_size)
711 .field("causal", &self.causal)
712 .finish()
713 }
714}