1use std::sync::OnceLock;
7
8use cudarc::driver::{
9 CudaGraph, CudaSlice, DevicePtr, LaunchConfig, PinnedHostSlice, PushKernelArg, result, sys,
10};
11use kime_tensor::plan::{Epilogue, Graph, Op, Rows, Val, layout};
12use kime_tensor::{Backend, Batch, Bucket, Caps, Error, HostTensor, Outputs, Result};
13
14use crate::lt::{self, Ty};
15use crate::tune::{self, Key};
16use crate::{CudaBackend, Precision, WORKSPACE, dev};
17
18const HEAD: usize = 64;
20
21pub(crate) const ATT_Q: usize = 16;
23pub(crate) const ATT_W: u32 = 8;
24
25pub(crate) const LN_ROWS: usize = 4;
27
28struct Tensor {
30 shape: Vec<usize>,
31 f32: CudaSlice<f32>,
32 f16: OnceLock<CudaSlice<u16>>,
33}
34
35pub struct Weights(Vec<Tensor>);
37
38impl std::fmt::Debug for Weights {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 f.debug_struct("Weights").field("tensors", &self.0.len()).finish_non_exhaustive()
41 }
42}
43
44#[derive(Debug, Clone, Copy)]
46struct Loc {
47 ptr: u64,
48 rows: Rows,
49 width: usize,
50 ty: Ty,
51}
52
53impl Loc {
54 fn half(self) -> bool {
55 self.ty == Ty::F16
56 }
57}
58
59fn kind(r: Rows) -> i32 {
60 match r {
61 Rows::Tokens => 0,
62 Rows::Seqs => 1,
63 Rows::Markers => 2,
64 }
65}
66
67#[derive(Debug, Clone, Copy)]
68enum Step {
69 Embed {
70 table: u64,
71 out: Loc,
72 },
73 LayerNorm {
74 x: Loc,
75 w: u64,
76 b: u64,
77 eps: f32,
78 out: Loc,
79 },
80 ToHalf {
83 x: u64,
84 len: u64,
85 },
86 Gemm {
88 gemm: usize,
89 b: u64,
90 act: i32,
91 out: Loc,
92 },
93 Rope {
94 qkv: Loc,
95 cos: u64,
96 sin: u64,
97 },
98 Attention {
100 qkv: Loc,
101 cos: u64,
102 sin: u64,
103 window: i32,
104 out: Loc,
105 },
106 GeGlu {
107 x: Loc,
108 out: Loc,
109 },
110 AddType {
111 h: Loc,
112 table: u64,
113 },
114 Gather {
115 h: Loc,
116 out: Loc,
117 },
118 ActFeatures {
119 h: Loc,
120 logits: Loc,
121 out: Loc,
122 },
123}
124
125impl Step {
126 fn name(&self) -> &'static str {
127 match self {
128 Step::Embed { .. } => "embed",
129 Step::LayerNorm { .. } => "layer norm",
130 Step::ToHalf { .. } => "to half",
131 Step::Gemm { .. } => "gemm",
132 Step::Rope { .. } => "rope",
133 Step::Attention { .. } => "attention",
134 Step::GeGlu { .. } => "geglu",
135 Step::AddType { .. } => "type embedding",
136 Step::Gather { .. } => "gather markers",
137 Step::ActFeatures { .. } => "act features",
138 }
139 }
140}
141
142#[derive(Debug, Clone, Copy)]
144struct Index {
145 ids: usize,
146 pos: usize,
147 seq: usize,
148 cu: usize,
149 mcu: usize,
150 mrow: usize,
151 qtype: usize,
152 tile: usize,
153 len: usize,
154}
155
156impl Index {
157 fn new(b: Bucket) -> Self {
158 let ids = 4;
159 let pos = ids + b.tokens;
160 let seq = pos + b.tokens;
161 let cu = seq + b.tokens;
162 let mcu = cu + b.seqs + 1;
163 let mrow = mcu + b.seqs + 1;
164 let qtype = mrow + b.markers;
165 let tile = qtype + b.seqs;
166 Self { ids, pos, seq, cu, mcu, mrow, qtype, tile, len: tile + att_blocks(b) }
167 }
168}
169
170#[derive(Debug, Clone, Copy)]
172struct Ptrs {
173 n: u64,
174 ids: u64,
175 pos: u64,
176 seq: u64,
177 cu: u64,
178 mcu: u64,
179 mrow: u64,
180 qtype: u64,
181 tile: u64,
182}
183
184impl Ptrs {
185 fn new(base: u64, at: Index) -> Self {
186 let a = |o: usize| base + 4 * o as u64;
187 Self {
188 n: base,
189 ids: a(at.ids),
190 pos: a(at.pos),
191 seq: a(at.seq),
192 cu: a(at.cu),
193 mcu: a(at.mcu),
194 mrow: a(at.mrow),
195 qtype: a(at.qtype),
196 tile: a(at.tile),
197 }
198 }
199}
200
201pub struct CudaPlan {
203 bucket: Bucket,
204 steps: Vec<Step>,
205 gemms: Vec<lt::Gemm>,
206 arena: CudaSlice<u8>,
207 _stage: CudaSlice<u16>,
209 stage: u64,
210 _ropes: Vec<(u64, CudaSlice<f32>, CudaSlice<f32>)>,
212 _index: CudaSlice<u32>,
213 index_host: PinnedHostSlice<u32>,
215 at: Index,
216 ptrs: Ptrs,
217 logits: Loc,
218 act: Loc,
219 host_out: PinnedHostSlice<f32>,
221 graph: Option<Captured>,
222 profile: Option<Vec<u64>>,
223 profiled: u64,
225}
226
227struct Captured(CudaGraph);
229
230unsafe impl Send for Captured {}
233
234impl std::fmt::Debug for CudaPlan {
235 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236 f.debug_struct("CudaPlan")
237 .field("bucket", &self.bucket)
238 .field("steps", &self.steps.len())
239 .field("arena", &self.arena.len())
240 .finish_non_exhaustive()
241 }
242}
243
244impl CudaPlan {
245 #[must_use]
247 pub fn bucket(&self) -> Bucket {
248 self.bucket
249 }
250
251 #[must_use]
253 pub fn arena_bytes(&self) -> usize {
254 self.arena.len()
255 }
256
257 pub fn profile(&mut self) {
260 self.profile = Some(vec![0; self.steps.len()]);
261 self.profiled = 0;
262 }
263
264 #[must_use]
267 pub fn gemm_timings(&self) -> Vec<((usize, usize, usize), u64, u64)> {
268 let mut by: Vec<((usize, usize, usize), u64, u64)> = Vec::new();
269 for (s, &ns) in self.steps.iter().zip(self.profile.iter().flatten()) {
270 if let Step::Gemm { gemm, .. } = *s {
271 let d = self.gemms[gemm].dims;
272 match by.iter_mut().find(|b| b.0 == d) {
273 Some(b) => {
274 b.1 += self.profiled;
275 b.2 += ns;
276 }
277 None => by.push((d, self.profiled, ns)),
278 }
279 }
280 }
281 by.sort_by_key(|b| std::cmp::Reverse(b.2));
282 by
283 }
284
285 #[must_use]
287 pub fn timings(&self) -> Vec<(&'static str, u64)> {
288 let mut by: Vec<(&'static str, u64)> = Vec::new();
289 for (s, &ns) in self.steps.iter().zip(self.profile.iter().flatten()) {
290 match by.iter_mut().find(|b| b.0 == s.name()) {
291 Some(b) => b.1 += ns,
292 None => by.push((s.name(), ns)),
293 }
294 }
295 by.sort_by_key(|b| std::cmp::Reverse(b.1));
296 by
297 }
298}
299
300fn types(graph: &Graph, precision: Precision) -> Vec<Ty> {
310 let n = graph.vals.len();
311 if precision == Precision::F32 {
312 return vec![Ty::F32; n];
313 }
314 let mut ty = vec![Ty::F16; n];
315 let mut half = vec![false; n];
316 for op in &graph.ops {
317 match *op {
318 Op::Embed { out, .. } | Op::ActFeatures { out, .. } => ty[out.0 as usize] = Ty::F32,
319 Op::Gemm { epilogue: Epilogue::Accumulate, out, .. } => ty[out.0 as usize] = Ty::F32,
320 Op::AddType { h, .. } => ty[h.0 as usize] = Ty::F32,
321 Op::Attention { qkv, out, .. } => {
322 ty[qkv.0 as usize] = Ty::F32;
323 half[out.0 as usize] = true;
324 }
325 Op::GeGlu { out, .. } => half[out.0 as usize] = true,
326 _ => {}
327 }
328 }
329 for v in [graph.logits, graph.act].into_iter().flatten() {
330 ty[v.0 as usize] = Ty::F32;
331 }
332 loop {
333 let mut changed = false;
334 for op in &graph.ops {
335 let (a, out) = match *op {
336 Op::Gemm { a, out, .. } => (a, out),
337 Op::GatherMarkers { h, out } => (h, out),
338 _ => continue,
339 };
340 let o = out.0 as usize;
341 if ty[a.0 as usize] == Ty::F32 && ty[o] == Ty::F16 && !half[o] {
342 ty[o] = Ty::F32;
343 changed = true;
344 }
345 }
346 if !changed {
347 return ty;
348 }
349 }
350}
351
352fn rows(n: usize, threads: u32) -> LaunchConfig {
354 LaunchConfig { grid_dim: (n as u32, 1, 1), block_dim: (threads, 1, 1), shared_mem_bytes: 0 }
355}
356
357impl CudaBackend {
358 fn tensor<'w>(&self, w: &'w Weights, i: usize) -> Result<&'w Tensor> {
359 w.0.get(i).ok_or_else(|| Error::Unsupported(format!("weight {i} is not in the checkpoint")))
360 }
361
362 fn ptr32(&self, w: &Weights, i: usize) -> Result<u64> {
363 Ok(self.tensor(w, i)?.f32.device_ptr(&self.stream).0)
364 }
365
366 fn ptr16(&self, w: &Weights, i: usize) -> Result<u64> {
368 let t = self.tensor(w, i)?;
369 if let Some(h) = t.f16.get() {
370 return Ok(h.device_ptr(&self.stream).0);
371 }
372 let len = t.f32.len();
373 let mut h = unsafe { self.stream.alloc::<u16>(len) }.map_err(dev)?;
375 let blocks = len.div_ceil(256).min(65_535) as u32;
376 let cfg =
377 LaunchConfig { grid_dim: (blocks, 1, 1), block_dim: (256, 1, 1), shared_mem_bytes: 0 };
378 let n = len as u64;
379 let mut l = self.stream.launch_builder(&self.k.to_f16);
380 l.arg(&mut h).arg(&t.f32).arg(&n);
381 unsafe { l.launch(cfg) }.map_err(dev)?;
383 Ok(t.f16.get_or_init(|| h).device_ptr(&self.stream).0)
384 }
385
386 fn launch_step(&self, p: &CudaPlan, step: &Step) -> Result<()> {
387 let b = p.bucket;
388 let Ptrs { n, ids, pos, seq, cu, mcu, mrow, qtype, tile } = p.ptrs;
389 let s = &self.stream;
390 match *step {
394 Step::Embed { table, out } => {
395 let d = out.width as i32;
396 let mut l = s.launch_builder(&self.k.embed);
397 l.arg(&out.ptr).arg(&table).arg(&ids).arg(&n).arg(&d);
398 unsafe { l.launch(rows(b.tokens, 256)) }.map_err(dev)?;
400 }
401 Step::LayerNorm { x, w, b: bias, eps, out } => {
402 let f = &self.k.ln[2 * usize::from(x.half()) + usize::from(out.half())];
403 let (k, d) = (kind(x.rows), x.width as i32);
404 let mut l = s.launch_builder(f);
405 l.arg(&out.ptr).arg(&x.ptr).arg(&w).arg(&bias).arg(&n).arg(&k).arg(&d).arg(&eps);
406 let cfg = LaunchConfig {
407 grid_dim: (b.rows(x.rows).div_ceil(LN_ROWS) as u32, 1, 1),
408 block_dim: (32 * LN_ROWS as u32, 1, 1),
409 shared_mem_bytes: 0,
410 };
411 unsafe { l.launch(cfg) }.map_err(dev)?;
413 }
414 Step::ToHalf { x, len } => {
415 let blocks = len.div_ceil(256).min(65_535) as u32;
416 let cfg = LaunchConfig {
417 grid_dim: (blocks, 1, 1),
418 block_dim: (256, 1, 1),
419 shared_mem_bytes: 0,
420 };
421 let mut l = s.launch_builder(&self.k.to_f16);
422 l.arg(&p.stage).arg(&x).arg(&len);
423 unsafe { l.launch(cfg) }.map_err(dev)?;
425 }
426 Step::Gemm { gemm, b: bias, act, out } => {
427 let ws = self.workspace_ptr();
428 unsafe { p.gemms[gemm].run(&self.lt, ws, WORKSPACE, s.cu_stream().cast()) }?;
430 if bias != 0 || act != 0 {
431 let f = &self.k.bias_act[usize::from(out.half())];
432 let (k, w) = (kind(out.rows), out.width as i32);
433 let mut l = s.launch_builder(f);
434 l.arg(&out.ptr).arg(&bias).arg(&n).arg(&k).arg(&w).arg(&act);
435 unsafe { l.launch(rows(b.rows(out.rows), 256)) }.map_err(dev)?;
437 }
438 }
439 Step::Rope { qkv, cos, sin } => {
440 let heads = (qkv.width / 3 / HEAD) as i32;
441 let mut l = s.launch_builder(&self.k.rope[usize::from(qkv.half())]);
442 l.arg(&qkv.ptr).arg(&cos).arg(&sin).arg(&pos).arg(&n).arg(&heads);
443 unsafe { l.launch(rows(b.tokens, 256)) }.map_err(dev)?;
445 }
446 Step::Attention { qkv, cos, sin, window, out } => {
447 let heads = (out.width / HEAD) as i32;
448 let mut l = s.launch_builder(
449 &self.k.attention[2 * usize::from(qkv.half()) + usize::from(out.half())],
450 );
451 l.arg(&out.ptr).arg(&qkv.ptr).arg(&cos).arg(&sin).arg(&pos);
452 l.arg(&seq).arg(&cu).arg(&tile).arg(&n);
453 l.arg(&heads).arg(&window);
454 let cfg = LaunchConfig {
455 grid_dim: (att_blocks(b) as u32, heads as u32, 1),
456 block_dim: (32 * ATT_W, 1, 1),
457 shared_mem_bytes: 0,
458 };
459 unsafe { l.launch(cfg) }.map_err(dev)?;
461 }
462 Step::GeGlu { x, out } => {
463 let inter = out.width as i32;
464 let mut l = s.launch_builder(
465 &self.k.geglu[2 * usize::from(x.half()) + usize::from(out.half())],
466 );
467 l.arg(&out.ptr).arg(&x.ptr).arg(&n).arg(&inter);
468 unsafe { l.launch(rows(b.tokens, 256)) }.map_err(dev)?;
470 }
471 Step::AddType { h, table } => {
472 let d = h.width as i32;
473 let mut l = s.launch_builder(&self.k.add_type);
474 l.arg(&h.ptr).arg(&table).arg(&seq).arg(&qtype).arg(&n).arg(&d);
475 unsafe { l.launch(rows(b.tokens, 256)) }.map_err(dev)?;
477 }
478 Step::Gather { h, out } => {
479 let d = h.width as i32;
480 let mut l = s.launch_builder(&self.k.gather[usize::from(h.half())]);
481 l.arg(&out.ptr).arg(&h.ptr).arg(&mrow).arg(&n).arg(&d);
482 unsafe { l.launch(rows(b.markers, 256)) }.map_err(dev)?;
484 }
485 Step::ActFeatures { h, logits, out } => {
486 let d = h.width as i32;
487 let mut l = s.launch_builder(&self.k.act_features);
488 l.arg(&out.ptr).arg(&h.ptr).arg(&logits.ptr).arg(&cu).arg(&mcu);
489 l.arg(&n).arg(&d);
490 unsafe { l.launch(rows(b.seqs, 256)) }.map_err(dev)?;
492 }
493 }
494 Ok(())
495 }
496
497 fn copy_in(&self, p: &CudaPlan) -> Result<()> {
499 let src = p.index_host.as_slice().map_err(dev)?;
500 unsafe { result::memcpy_htod_async(p.ptrs.n, src, self.stream.cu_stream()) }.map_err(dev)
503 }
504
505 fn copy_out(&self, p: &mut CudaPlan) -> Result<()> {
507 let (lp, ap, m) = (p.logits.ptr, p.act.ptr, p.bucket.markers);
508 let cu = self.stream.cu_stream();
509 let dst = p.host_out.as_mut_slice().map_err(dev)?;
510 let (logits, act) = dst.split_at_mut(m);
511 unsafe {
514 result::memcpy_dtoh_async(logits, lp, cu).map_err(dev)?;
515 result::memcpy_dtoh_async(act, ap, cu).map_err(dev)
516 }
517 }
518
519 fn capture(&self, p: &mut CudaPlan) -> Result<Captured> {
521 let s = &self.stream;
522 s.begin_capture(sys::CUstreamCaptureMode::CU_STREAM_CAPTURE_MODE_RELAXED).map_err(dev)?;
525 let mut enqueue = || {
526 self.copy_in(p)?;
527 for step in &p.steps {
528 self.launch_step(p, step)?;
529 }
530 self.copy_out(p)
531 };
532 let queued = enqueue();
533 let flags = sys::CUgraphInstantiate_flags::CUDA_GRAPH_INSTANTIATE_FLAG_AUTO_FREE_ON_LAUNCH;
534 let graph = s.end_capture(flags).map_err(dev)?;
535 queued?;
536 let graph = graph.ok_or_else(|| Error::Device("graph capture recorded nothing".into()))?;
537 graph.upload().map_err(dev)?;
538 Ok(Captured(graph))
539 }
540}
541
542impl Backend for CudaBackend {
543 type Weights = Weights;
544 type Plan = CudaPlan;
545
546 fn caps(&self) -> Caps {
547 Caps { name: "cuda", threads: 1, graphs: true, unified_memory: false }
548 }
549
550 fn weight_bytes(&self, w: &Weights) -> usize {
551 w.0.iter().map(|t| 4 * t.f32.len() + t.f16.get().map_or(0, |h| 2 * h.len())).sum()
552 }
553
554 fn plan_bytes(&self, p: &CudaPlan) -> usize {
555 p.arena.len() + 2 * p._stage.len()
556 }
557
558 fn upload(&self, tensors: &[HostTensor<'_>], _graph: &Graph) -> Result<Weights> {
559 let mut out = Vec::with_capacity(tensors.len());
560 let mut host = Vec::new();
561 for (i, h) in tensors.iter().enumerate() {
562 let n = h.bytes.len() / h.dtype.size();
563 if n != h.shape.iter().product::<usize>() {
564 return Err(Error::Unsupported(format!(
565 "tensor {i} has {n} values for {:?}",
566 h.shape
567 )));
568 }
569 host.clear();
570 host.extend((0..n).map(|j| h.dtype.read_f32(h.bytes, j)));
571 let f32 = self.stream.clone_htod(&host).map_err(dev)?;
572 out.push(Tensor { shape: h.shape.to_vec(), f32, f16: OnceLock::new() });
573 }
574 self.stream.synchronize().map_err(dev)?;
575 Ok(Weights(out))
576 }
577
578 #[allow(clippy::too_many_lines)]
579 fn lower(&self, w: &Weights, graph: &Graph, bucket: Bucket) -> Result<CudaPlan> {
580 let lay = layout(graph, |r| bucket.rows(r));
581 let ty = types(graph, self.precision);
582 let arena = self.stream.alloc_zeros::<u8>(4 * lay.len.max(1)).map_err(dev)?;
583 let base = arena.device_ptr(&self.stream).0;
584 let loc = |v: Val| {
585 let s = graph.shape(v);
586 let i = v.0 as usize;
587 Loc { ptr: base + 4 * lay.offsets[i] as u64, rows: s.rows, width: s.width, ty: ty[i] }
588 };
589 let bad = |m: String| Err(Error::Unsupported(m));
590 let shape = |i: usize| self.tensor(w, i).map(|t| t.shape.as_slice());
591 let bias = |b: Option<usize>| b.map_or(Ok(0), |b| self.ptr32(w, b));
592 let mut ropes: Vec<(u64, CudaSlice<f32>, CudaSlice<f32>)> = Vec::new();
593 let stage_len = graph
594 .ops
595 .iter()
596 .filter_map(|op| match *op {
597 Op::Gemm { a, out, .. }
598 if ty[a.0 as usize] == Ty::F32 && ty[out.0 as usize] == Ty::F16 =>
599 {
600 let s = graph.shape(a);
601 Some(bucket.rows(s.rows) * s.width)
602 }
603 _ => None,
604 })
605 .max()
606 .unwrap_or(0);
607 let stage = self.stream.alloc_zeros::<u16>(stage_len.max(1)).map_err(dev)?;
608 let stage_base = stage.device_ptr(&self.stream).0;
609 let (mut gemms, mut keys) = (Vec::new(), Vec::new());
610 let mut steps = Vec::with_capacity(graph.ops.len());
611 let mut fused = None;
612 for (i, op) in graph.ops.iter().enumerate() {
613 let step = match *op {
614 Op::Embed { table, out } => {
615 let out = loc(out);
616 if shape(table)?.get(1) != Some(&out.width) || out.rows != Rows::Tokens {
617 return bad(format!("op {i}: embedding table does not match its output"));
618 }
619 Step::Embed { table: self.ptr32(w, table)?, out }
620 }
621 Op::LayerNorm { x, w: nw, b, eps, out } => {
622 let (x, out) = (loc(x), loc(out));
623 let ok = shape(nw)? == [x.width]
624 && b.map_or(Ok(true), |b| shape(b).map(|s| s == [x.width]))?
625 && x.width == out.width
626 && x.width <= 1024
627 && x.rows == out.rows;
628 if !ok {
629 return bad(format!(
630 "op {i}: layer norm shapes do not match or are over 1024"
631 ));
632 }
633 let eps = eps as f32;
634 Step::LayerNorm { x, w: self.ptr32(w, nw)?, b: bias(b)?, eps, out }
635 }
636 Op::Gemm { a, w: gw, b, epilogue, out } => {
637 let (a, out) = (loc(a), loc(out));
638 let ok = shape(gw)? == [out.width, a.width]
639 && b.map_or(Ok(true), |b| shape(b).map(|s| s == [out.width]))?
640 && a.rows == out.rows;
641 if !ok {
642 return bad(format!("op {i}: gemm shapes do not match"));
643 }
644 let m = bucket.rows(a.rows);
645 if m == 0 {
646 continue;
647 }
648 let mut x = a.ptr;
649 let (wp, ab) = match (a.ty, out.ty) {
650 (Ty::F32, Ty::F32) => (self.ptr32(w, gw)?, Ty::F32),
651 (Ty::F32, Ty::F16) => {
652 let len = m * a.width;
653 steps.push(Step::ToHalf { x, len: len as u64 });
654 x = stage_base;
655 (self.ptr16(w, gw)?, Ty::F16)
656 }
657 (Ty::F16, _) => (self.ptr16(w, gw)?, Ty::F16),
658 };
659 let (act, acc) = match epilogue {
660 Epilogue::None => (0, false),
661 Epilogue::Gelu => (1, false),
662 Epilogue::Relu => (2, false),
663 Epilogue::Accumulate => (0, true),
664 };
665 let dims = (lt::RANKED_ROWS, a.width, out.width);
668 let key = Key { dims, ab, c: out.ty, acc };
669 let g = lt::Gemm::new(
670 &self.lt,
671 (m, a.width, out.width),
672 ab,
673 out.ty,
674 acc,
675 WORKSPACE,
676 (wp, x, out.ptr),
677 self.picks.get(&key),
678 )?;
679 gemms.push(g);
680 keys.push(key);
681 Step::Gemm { gemm: gemms.len() - 1, b: bias(b)?, act, out }
682 }
683 Op::Rope { qkv, theta } => {
684 let qkv = loc(qkv);
685 if !qkv.width.is_multiple_of(3 * HEAD) || qkv.rows != Rows::Tokens {
686 return bad(format!("op {i}: rope needs token rows of 3 heads 64"));
687 }
688 let at = match ropes.iter().position(|r| r.0 == theta.to_bits()) {
689 Some(at) => at,
690 None => {
691 let (c, s) = rope_tables(theta, bucket.tokens.max(1));
692 let c = self.stream.clone_htod(&c).map_err(dev)?;
693 let s = self.stream.clone_htod(&s).map_err(dev)?;
694 ropes.push((theta.to_bits(), c, s));
695 ropes.len() - 1
696 }
697 };
698 let cos = ropes[at].1.device_ptr(&self.stream).0;
699 let sin = ropes[at].2.device_ptr(&self.stream).0;
700 if matches!(graph.ops.get(i + 1), Some(Op::Attention { qkv: v, .. }) if loc(*v).ptr == qkv.ptr)
703 {
704 fused = Some((qkv.ptr, cos, sin));
705 continue;
706 }
707 Step::Rope { qkv, cos, sin }
708 }
709 Op::Attention { qkv, window, out } => {
710 let (qkv, out) = (loc(qkv), loc(out));
711 let ok = qkv.width.is_multiple_of(3 * HEAD)
712 && out.width * 3 == qkv.width
713 && qkv.rows == Rows::Tokens
714 && out.rows == Rows::Tokens;
715 if !ok {
716 return bad(format!("op {i}: attention shapes or types do not match"));
717 }
718 let window = window.map_or(Ok(-1), |w| {
719 i32::try_from(w).map_err(|_| Error::Unsupported(format!("op {i}: window")))
720 })?;
721 let (cos, sin) = match fused.take() {
722 Some((p, cos, sin)) if p == qkv.ptr => (cos, sin),
723 _ => (0, 0),
724 };
725 Step::Attention { qkv, cos, sin, window, out }
726 }
727 Op::GeGlu { x, out } => {
728 let (x, out) = (loc(x), loc(out));
729 if x.width != 2 * out.width || x.rows != out.rows {
730 return bad(format!("op {i}: geglu needs an input twice its output"));
731 }
732 Step::GeGlu { x, out }
733 }
734 Op::AddType { h, table } => {
735 let h = loc(h);
736 if shape(table)?.get(1) != Some(&h.width) || h.rows != Rows::Tokens {
737 return bad(format!("op {i}: type table does not match"));
738 }
739 Step::AddType { h, table: self.ptr32(w, table)? }
740 }
741 Op::GatherMarkers { h, out } => {
742 let (h, out) = (loc(h), loc(out));
743 let ok = h.width == out.width
744 && h.rows == Rows::Tokens
745 && out.rows == Rows::Markers
746 && h.ty == out.ty;
747 if !ok {
748 return bad(format!("op {i}: gather shapes do not match"));
749 }
750 Step::Gather { h, out }
751 }
752 Op::ActFeatures { h, logits, out } => {
753 let (h, logits, out) = (loc(h), loc(logits), loc(out));
754 let ok = out.width == h.width + 4
755 && logits.width == 1
756 && logits.rows == Rows::Markers
757 && out.rows == Rows::Seqs
758 && !h.half()
759 && !logits.half();
760 if !ok {
761 return bad(format!("op {i}: act feature shapes do not match"));
762 }
763 Step::ActFeatures { h, logits, out }
764 }
765 Op::MeanPool { .. } => return bad(format!("op {i}: mean pool is not on CUDA yet")),
766 };
767 steps.push(step);
768 }
769 let (Some(logits), Some(act)) = (graph.logits, graph.act) else {
770 return bad("the graph has no logits or act output".into());
771 };
772 let (logits, act) = (loc(logits), loc(act));
773 if logits.width != 1
774 || logits.rows != Rows::Markers
775 || act.width != 2
776 || act.rows != Rows::Seqs
777 {
778 return bad("outputs are not one logit per marker and two per sequence".into());
779 }
780 let at = Index::new(bucket);
781 let index = self.stream.alloc_zeros::<u32>(at.len).map_err(dev)?;
782 let ptrs = Ptrs::new(index.device_ptr(&self.stream).0, at);
783 let ctx = self.stream.context();
784 let (mut index_host, mut host_out) = unsafe {
786 (
787 ctx.alloc_pinned::<u32>(at.len).map_err(dev)?,
788 ctx.alloc_pinned::<f32>(bucket.markers + 2 * bucket.seqs).map_err(dev)?,
789 )
790 };
791 index_host.as_mut_slice().map_err(dev)?.fill(0);
792 host_out.as_mut_slice().map_err(dev)?.fill(0.0);
793 self.stream.synchronize().map_err(dev)?;
794 let mut plan = CudaPlan {
795 bucket,
796 steps,
797 gemms,
798 arena,
799 _stage: stage,
800 stage: stage_base,
801 _ropes: ropes,
802 _index: index,
803 index_host,
804 at,
805 ptrs,
806 logits,
807 act,
808 host_out,
809 graph: None,
810 profile: None,
811 profiled: 0,
812 };
813 if tune::enabled() {
814 let ws = self.workspace_ptr();
815 tune::tune(&self.lt, &self.stream, ws, &mut plan.gemms, &keys, &self.name)?;
816 }
817 plan.graph = Some(self.capture(&mut plan)?);
818 Ok(plan)
819 }
820
821 fn run(&self, p: &mut CudaPlan, batch: &Batch<'_>, out: &mut Outputs) -> Result<()> {
822 let (t, s, m) = (batch.ids.len(), batch.seqs(), batch.markers.len());
823 let at = p.at;
824 let h = p.index_host.as_mut_slice().map_err(dev)?;
825 let blocks = att_tiles(batch.cu, &mut h[at.tile..at.tile + att_blocks(p.bucket)]);
826 h[..4].copy_from_slice(&[t as u32, s as u32, m as u32, blocks as u32]);
827 h[at.ids..at.ids + t].copy_from_slice(batch.ids);
828 h[at.cu..=at.cu + s].copy_from_slice(batch.cu);
829 h[at.mcu..=at.mcu + s].copy_from_slice(batch.mcu);
830 for q in 0..s {
831 let (lo, hi) = (batch.cu[q] as usize, batch.cu[q + 1] as usize);
832 for r in lo..hi {
833 h[at.pos + r] = (r - lo) as u32;
834 h[at.seq + r] = q as u32;
835 }
836 for k in batch.mcu[q] as usize..batch.mcu[q + 1] as usize {
837 h[at.mrow + k] = lo as u32 + batch.markers[k];
838 }
839 h[at.qtype + q] = u32::from(batch.qtype[q]);
840 }
841 if p.profile.is_some() {
842 self.copy_in(p)?;
843 self.stream.synchronize().map_err(dev)?;
844 for i in 0..p.steps.len() {
845 let t = std::time::Instant::now();
846 self.launch_step(p, &p.steps[i])?;
847 self.stream.synchronize().map_err(dev)?;
848 let ns = t.elapsed().as_nanos() as u64;
849 if let Some(v) = p.profile.as_mut() {
850 v[i] += ns;
851 }
852 }
853 self.copy_out(p)?;
854 p.profiled += 1;
855 } else if let Some(g) = &p.graph {
856 g.0.launch().map_err(dev)?;
857 }
858 self.stream.synchronize().map_err(dev)?;
859 let host = p.host_out.as_slice().map_err(dev)?;
860 let (logits, act) = host.split_at(p.bucket.markers);
861 out.logits.clear();
862 out.logits.extend_from_slice(&logits[..m]);
863 out.act.clear();
864 out.act.extend(act[..2 * s].as_chunks::<2>().0.iter().copied());
865 Ok(())
866 }
867}
868
869pub(crate) fn att_blocks(b: Bucket) -> usize {
871 b.tokens.div_ceil(ATT_Q) + b.seqs
872}
873
874pub(crate) fn att_tiles(cu: &[u32], out: &mut [u32]) -> usize {
879 let mut k = 0;
880 for w in cu.windows(2) {
881 for i in (w[0]..w[1]).step_by(ATT_Q) {
882 out[k] = i;
883 k += 1;
884 }
885 }
886 k
887}
888
889pub(crate) fn rope_tables(theta: f64, len: usize) -> (Vec<f32>, Vec<f32>) {
891 let half = HEAD / 2;
892 let inv: Vec<f32> = (0..half)
893 .map(|i| {
894 let e = (2 * i) as f32 / HEAD as f32;
895 1.0 / (theta.powf(f64::from(e)) as f32)
896 })
897 .collect();
898 let mut cos = Vec::with_capacity(len * half);
899 let mut sin = Vec::with_capacity(len * half);
900 for p in 0..len {
901 for &f in &inv {
902 let a = f64::from(p as f32 * f);
903 cos.push(a.cos() as f32);
904 sin.push(a.sin() as f32);
905 }
906 }
907 (cos, sin)
908}