1use super::{dummy_storage, groups64, uniform, ComputeEngine};
4use crate::{UnifiedTensor, WgpuContext};
5use wgpu::Buffer;
6
7impl ComputeEngine {
8 pub fn rope(&self, ctx: &WgpuContext, tensor: &UnifiedTensor, head_dim: usize, pos: usize) {
13 self.rope_theta(ctx, tensor, head_dim, pos, 10000.0);
14 }
15
16 pub fn rope_theta(
19 &self,
20 ctx: &WgpuContext,
21 tensor: &UnifiedTensor,
22 head_dim: usize,
23 pos: usize,
24 theta: f32,
25 ) {
26 let (seq_len, tokens) = match tensor.shape.len() {
27 0 | 1 => (1, 1),
28 2 => (tensor.shape[0], tensor.shape[0]),
29 _ => (tensor.shape[1], tensor.shape[0] * tensor.shape[1]),
30 };
31 let total = tensor.shape.iter().product::<usize>();
32 let heads = total / (tokens * head_dim).max(1);
33 let params = uniform(
34 ctx,
35 bytemuck::cast_slice(&[
36 head_dim as u32,
37 seq_len as u32,
38 pos as u32,
39 heads as u32,
40 theta.to_bits(),
41 0,
42 0,
43 0,
44 ]),
45 );
46 let pairs = tensor.shape.iter().product::<usize>() / 2;
47 self.run(
48 ctx,
49 &self.rope,
50 &[(0, tensor.buffer()), (1, ¶ms)],
51 groups64(pairs),
52 );
53 }
54
55 pub fn flash_attention(
59 &self,
60 ctx: &WgpuContext,
61 q: &UnifiedTensor,
62 k: &UnifiedTensor,
63 v: &UnifiedTensor,
64 out: &UnifiedTensor,
65 causal: bool,
66 ) {
67 let [q4, k4, v4, o4] = [q, k, v, out].map(single_head_view);
68 self.flash_attention_v2(ctx, &q4, &k4, &v4, &o4, causal, 0.0, 0, None);
69 }
70
71 #[allow(clippy::too_many_arguments)]
86 pub fn flash_attention_v2(
87 &self,
88 ctx: &WgpuContext,
89 q: &UnifiedTensor,
90 k: &UnifiedTensor,
91 v: &UnifiedTensor,
92 out: &UnifiedTensor,
93 causal: bool,
94 dropout_prob: f32,
95 seed: u64,
96 mask: Option<&UnifiedTensor>,
97 ) {
98 let opts = AttentionOptions {
99 causal,
100 dropout_prob,
101 seed,
102 ..AttentionOptions::default()
103 };
104 self.flash_attention_v2_with(ctx, q, k, v, out, &opts, mask);
105 }
106
107 #[allow(clippy::too_many_arguments)]
109 pub fn flash_attention_v2_with(
110 &self,
111 ctx: &WgpuContext,
112 q: &UnifiedTensor,
113 k: &UnifiedTensor,
114 v: &UnifiedTensor,
115 out: &UnifiedTensor,
116 opts: &AttentionOptions,
117 mask: Option<&UnifiedTensor>,
118 ) {
119 let dims = AttentionDims::new(q, k);
120 let params = dims.params(ctx, opts, mask);
121 let dummy;
122 let mask_buf = match mask {
123 Some(m) => m.buffer(),
124 None => {
125 dummy = dummy_storage(ctx);
126 &dummy
127 }
128 };
129 self.run(
130 ctx,
131 &self.flash_attn_v2,
132 &[
133 (0, q.buffer()),
134 (1, k.buffer()),
135 (2, v.buffer()),
136 (3, out.buffer()),
137 (4, ¶ms),
138 (5, mask_buf),
139 ],
140 (1, dims.seq_q.div_ceil(128), dims.batch * dims.heads),
141 );
142 }
143
144 #[allow(clippy::too_many_arguments)]
147 pub fn flash_attention_backward(
148 &self,
149 ctx: &WgpuContext,
150 q: &UnifiedTensor,
151 k: &UnifiedTensor,
152 v: &UnifiedTensor,
153 out: &UnifiedTensor,
154 grad_out: &UnifiedTensor,
155 grad_q: &UnifiedTensor,
156 grad_k: &UnifiedTensor,
157 grad_v: &UnifiedTensor,
158 causal: bool,
159 ) {
160 let views = [q, k, v, out, grad_out, grad_q, grad_k, grad_v].map(single_head_view);
161 let [q4, k4, v4, o4, go4, gq4, gk4, gv4] = &views;
162 self.flash_attention_v2_backward(ctx, q4, k4, v4, o4, go4, gq4, gk4, gv4, causal);
163 }
164
165 #[allow(clippy::too_many_arguments)]
169 pub fn flash_attention_v2_backward(
170 &self,
171 ctx: &WgpuContext,
172 q: &UnifiedTensor,
173 k: &UnifiedTensor,
174 v: &UnifiedTensor,
175 out: &UnifiedTensor,
176 grad_out: &UnifiedTensor,
177 grad_q: &UnifiedTensor,
178 grad_k: &UnifiedTensor,
179 grad_v: &UnifiedTensor,
180 causal: bool,
181 ) {
182 let opts = AttentionOptions {
183 causal,
184 ..AttentionOptions::default()
185 };
186 self.flash_attention_v2_backward_with(
187 ctx, q, k, v, out, grad_out, grad_q, grad_k, grad_v, &opts, None,
188 );
189 }
190
191 #[allow(clippy::too_many_arguments)]
199 pub fn flash_attention_v2_backward_with(
200 &self,
201 ctx: &WgpuContext,
202 q: &UnifiedTensor,
203 k: &UnifiedTensor,
204 v: &UnifiedTensor,
205 out: &UnifiedTensor,
206 grad_out: &UnifiedTensor,
207 grad_q: &UnifiedTensor,
208 grad_k: &UnifiedTensor,
209 grad_v: &UnifiedTensor,
210 opts: &AttentionOptions,
211 mask: Option<&UnifiedTensor>,
212 ) {
213 let dims = AttentionDims::new(q, k);
214 let params = dims.params_bwd(ctx, opts, mask);
215 let dummy;
216 let mask_buf = match mask {
217 Some(m) => m.buffer(),
218 None => {
219 dummy = dummy_storage(ctx);
220 &dummy
221 }
222 };
223 let stats = ctx.device.create_buffer(&wgpu::BufferDescriptor {
225 label: Some("Flash Bwd Stats"),
226 size: (dims.batch * dims.heads * dims.seq_q).max(1) as u64 * 8,
227 usage: wgpu::BufferUsages::STORAGE,
228 mapped_at_creation: false,
229 });
230 let (b, h, hkv) = (dims.batch, dims.heads, dims.kv_heads);
231 self.run(
232 ctx,
233 &self.flash_attn_v2_bw_dq,
234 &[
235 (0, q.buffer()),
236 (1, k.buffer()),
237 (2, v.buffer()),
238 (3, out.buffer()),
239 (4, grad_out.buffer()),
240 (5, grad_q.buffer()),
241 (8, ¶ms),
242 (9, mask_buf),
243 (10, &stats),
244 ],
245 (1, dims.seq_q.div_ceil(128), b * h),
246 );
247 self.run(
248 ctx,
249 &self.flash_attn_v2_bw_dkdv,
250 &[
251 (0, q.buffer()),
252 (1, k.buffer()),
253 (2, v.buffer()),
254 (4, grad_out.buffer()),
255 (6, grad_k.buffer()),
256 (7, grad_v.buffer()),
257 (8, ¶ms),
258 (9, mask_buf),
259 (10, &stats),
260 ],
261 (1, dims.seq_k.div_ceil(128), b * hkv),
262 );
263 }
264}
265
266fn single_head_view(t: &UnifiedTensor) -> UnifiedTensor {
268 assert_eq!(t.shape.len(), 2, "expected a [seq_len, head_dim] tensor");
269 t.reshaped(&[1, t.shape[0], 1, t.shape[1]])
270}
271
272struct AttentionDims {
274 batch: u32,
275 seq_q: u32,
276 seq_k: u32,
277 heads: u32,
278 kv_heads: u32,
279 head_dim: u32,
280}
281
282impl AttentionDims {
283 fn new(q: &UnifiedTensor, k: &UnifiedTensor) -> Self {
284 assert_eq!(q.shape.len(), 4, "q must be [batch, seq, heads, head_dim]");
285 assert_eq!(
286 k.shape.len(),
287 4,
288 "k must be [batch, seq, kv_heads, head_dim]"
289 );
290 let dims = Self {
291 batch: q.shape[0] as u32,
292 seq_q: q.shape[1] as u32,
293 heads: q.shape[2] as u32,
294 head_dim: q.shape[3] as u32,
295 seq_k: k.shape[1] as u32,
296 kv_heads: k.shape[2] as u32,
297 };
298 assert!(
299 dims.head_dim <= 128,
300 "flash attention supports head_dim <= 128, got {}",
301 dims.head_dim
302 );
303 assert!(
304 dims.kv_heads > 0 && dims.heads.is_multiple_of(dims.kv_heads),
305 "num_heads ({}) must be a multiple of num_kv_heads ({})",
306 dims.heads,
307 dims.kv_heads
308 );
309 dims
310 }
311
312 fn scale(&self) -> f32 {
313 1.0 / (self.head_dim as f32).sqrt()
314 }
315
316 fn params(
318 &self,
319 ctx: &WgpuContext,
320 opts: &AttentionOptions,
321 mask: Option<&UnifiedTensor>,
322 ) -> Buffer {
323 let (has_mask, s) = mask_strides(mask);
324 let params: [u32; 20] = [
325 self.batch,
326 self.heads,
327 self.seq_q,
328 self.seq_k,
329 self.head_dim,
330 opts.causal as u32,
331 self.scale().to_bits(),
332 self.kv_heads,
333 opts.dropout_prob.to_bits(),
334 opts.seed as u32,
335 has_mask,
336 opts.causal_offset,
337 s[0],
338 s[1],
339 s[2],
340 s[3],
341 opts.window,
342 0,
343 0,
344 0,
345 ];
346 uniform(ctx, bytemuck::cast_slice(¶ms))
347 }
348
349 fn params_bwd(
351 &self,
352 ctx: &WgpuContext,
353 opts: &AttentionOptions,
354 mask: Option<&UnifiedTensor>,
355 ) -> Buffer {
356 let (has_mask, s) = mask_strides(mask);
357 let params: [u32; 20] = [
358 self.batch,
359 self.heads,
360 self.seq_q,
361 self.seq_k,
362 self.head_dim,
363 self.kv_heads,
364 self.scale().to_bits(),
365 opts.dropout_prob.to_bits(),
366 opts.seed as u32,
367 opts.causal as u32,
368 has_mask,
369 opts.causal_offset,
370 s[0],
371 s[1],
372 s[2],
373 s[3],
374 opts.window,
375 0,
376 0,
377 0,
378 ];
379 uniform(ctx, bytemuck::cast_slice(¶ms))
380 }
381}
382
383#[derive(Debug, Clone, Copy, Default)]
390pub struct AttentionOptions {
391 pub causal: bool,
393 pub causal_offset: u32,
396 pub window: u32,
398 pub dropout_prob: f32,
400 pub seed: u64,
402}
403
404fn mask_strides(mask: Option<&UnifiedTensor>) -> (u32, [u32; 4]) {
407 let Some(m) = mask else {
408 return (0, [0; 4]);
409 };
410 let shape = &m.shape;
411 if shape.len() != 4 {
412 return (1, [0; 4]);
413 }
414 let dim_b = shape[0] as u32;
415 let dim_h = shape[1] as u32;
416 let dim_q = shape[2] as u32;
417 let dim_k = shape[3] as u32;
418 let s = |dim: u32, stride: u32| if dim > 1 { stride } else { 0 };
419 (
420 1,
421 [
422 s(dim_b, dim_k * dim_q * dim_h),
423 s(dim_h, dim_k * dim_q),
424 s(dim_q, dim_k),
425 s(dim_k, 1),
426 ],
427 )
428}