1use std::collections::BTreeMap;
7
8use super::kernels;
9use super::plan::{quant_in_place, Fill, Launch, Schedule, Touch, QBLOCK};
10use crate::codegen_cuda::qwen_emit::Q8MatKind;
11
12pub const ALIGN: usize = 256;
14
15#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct Placed {
18 pub name: String,
19 pub touch: Touch,
20 pub offset: usize,
21 pub bytes: usize,
22 pub first: usize,
23 pub last: usize,
24}
25
26#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct PlacedScratch {
29 pub launch: usize,
30 pub tag: &'static str,
31 pub offset: usize,
32 pub bytes: usize,
33}
34
35#[derive(Clone, Debug, Default, PartialEq, Eq)]
37pub struct Arena {
38 pub placed: Vec<Placed>,
39 pub scratch: Vec<PlacedScratch>,
40 pub bytes: usize,
41}
42
43impl Arena {
44 pub fn at(&self, name: &str, touch: Touch) -> Option<&Placed> {
46 self.placed.iter().find(|p| p.name == name && p.touch == touch)
47 }
48
49 pub fn scratch_at(&self, launch: usize, tag: &str) -> Option<&PlacedScratch> {
51 self.scratch.iter().find(|p| p.launch == launch && p.tag == tag)
52 }
53}
54
55pub fn home<'a>(s: &'a Schedule, name: &'a str, touch: Touch) -> &'a str {
58 let mut at = name;
59 while let Some(Fill::Alias { of, offset }) = s.buffers.get(at).map(|b| &b.fill) {
60 if touch == Touch::Quant && !quant_in_place(*offset) {
61 break;
62 }
63 at = of;
64 }
65 at
66}
67
68#[derive(Clone, Debug, PartialEq, Eq)]
72pub struct Scratch {
73 pub tag: &'static str,
74 pub ty: &'static str,
75 pub bytes: usize,
76 pub counter: bool,
77}
78
79pub fn scratch(l: &Launch, numerics: Q8MatKind) -> Vec<Scratch> {
86 let m = kernels::mode(numerics);
87 let two = numerics == Q8MatKind::TwoPassInt8;
88 let (acc, acc_ty) = kernels::accumulator(m);
89 let t = |tag, ty, bytes| Scratch { tag, ty, bytes, counter: false };
90 let counter = |tag, bytes| Scratch { tag, ty: "int", bytes, counter: true };
91 let quantized = |v: &mut Vec<Scratch>, [q, lo, sc]: [&'static str; 3], n: usize| {
93 v.push(t(q, "signed char", n));
94 if two {
95 v.push(t(lo, "signed char", n));
96 }
97 v.push(t(sc, "float", n / QBLOCK * 4));
98 };
99 match l {
100 Launch::Delta { hv, d, rows, .. } if l.delta_in_chunks(numerics) => {
102 let [kg, qg, tb, sc, dec] = kernels::delta_chunk_scratch(rows.div_ceil(kernels::DELTA_CHUNK), *hv, *d);
103 vec![
104 t("o", "float", rows * hv * d * 4),
105 t("dk", "float", kg * 4),
106 t("dq", "float", qg * 4),
107 t("dt", "float", tb * 4),
108 t("dp", "float", sc * 4),
109 t("dg", "float", dec * 4),
110 ]
111 }
112 Launch::Delta { hv, d, rows, seqs, .. } => vec![t("o", "float", rows * hv * d * seqs * 4), counter("dc", hv * seqs * 4)],
115 Launch::GemvF32 { n, m, split, .. } if *split > 1 && l.multiplies_in_tiles(numerics) => {
117 let tiles = n.div_ceil(64) * m.div_ceil(64);
118 vec![t("fp", "float", tiles * split * 256 * 16 * 4), counter("fq", tiles * 4)]
119 }
120 Launch::Moe { d, dff, k, experts, rows, router_split, .. } if *rows > 1 => {
121 let choices = rows * k;
122 let mut v = vec![
123 t("i", "int", choices * 4),
124 t("g", "float", choices * 4),
125 t("rl", "float", rows * experts * 4),
126 counter("rc", rows * 4),
127 t("ec", "int", experts * 4),
128 t("eo", "int", (experts + 1) * 4),
129 t("el", "int", choices * 4),
130 t("ep", "int", choices * 4),
131 t("pa", acc_ty, choices * d * acc),
132 ];
133 if !l.pairs_gate_and_up(numerics) {
135 v.push(t("h", "float", choices * dff * 4));
136 }
137 if m == 0 {
138 v.push(t("gx", "float", choices * d * 4));
139 } else {
140 quantized(&mut v, ["gq", "gl", "gs"], choices * d);
141 quantized(&mut v, ["hq", "hl", "hs"], choices * dff);
142 }
143 if l.on_tensor_cores(numerics) && !l.pairs_gate_and_up(numerics) {
144 v.extend([t("hg", "float", choices * dff * 4), t("hu", "float", choices * dff * 4)]);
145 }
146 if *router_split > 1 {
148 let tiles = experts.div_ceil(64) * rows.div_ceil(64);
149 v.extend([t("rp", "float", tiles * router_split * 256 * 16 * 4), counter("rq", tiles * 4)]);
150 }
151 v
152 }
153 Launch::Moe { dff, k, experts, shared, .. } => {
154 let hidden = k * dff + shared.as_ref().map_or(0, |e| e.dff);
155 let mut v = vec![
156 t("i", "int", k * 4),
157 t("g", "float", k * 4),
158 t("h", "float", hidden * 4),
159 t("rl", "float", experts * 4),
160 counter("rc", 4),
161 ];
162 if m > 0 {
163 quantized(&mut v, ["hq", "hl", "hs"], hidden);
164 }
165 v
166 }
167 Launch::Gemv { m, tail: Some((whole, split)), .. }
170 | Launch::GemmParts { m, tail: Some((whole, split)), .. }
171 | Launch::GemvGlu { m, tail: Some((whole, split)), .. }
172 if l.on_tensor_cores(numerics) =>
173 {
174 let rows = l.gemm_rows(numerics);
175 let tiles = l.gemm_tiles_wide() * m.div_ceil(rows);
176 let parts = (tiles - whole) * split;
177 vec![t("tp", "float", parts * rows * kernels::GEMM_TILE_N * 4), counter("tc", (tiles - whole) * 4)]
178 }
179 Launch::Fold { first, then } => {
180 let mut v = scratch(first, numerics);
181 v.extend(match (first.as_ref(), then.as_ref()) {
182 (Launch::Gather { .. }, _) => None,
184 (_, Launch::RopeParts { sides, .. }) => Some(counter("gc", sides.iter().map(|s| s.heads).sum::<usize>() * 4)),
186 (_, Launch::Binary { .. }) => None,
188 _ => match l.finished_mixture() {
190 Some((Launch::Moe { rows, .. }, ..)) if *rows > 1 => None,
191 _ => Some(counter("gc", 4)),
193 },
194 });
195 v
196 }
197 Launch::GemvGlu { n, m, .. } if l.batch_on_tensor_cores(numerics) => vec![t("mg", "float", m * n * 4), t("mu", "float", m * n * 4)],
199 Launch::ArgMax { n, rows, .. } => {
200 let blocks = kernels::argmax_blocks(*n);
201 vec![t("av", "float", rows * blocks * 4), t("ai", "int", rows * blocks * 4), counter("ac", rows * 4)]
202 }
203 Launch::Attention { heads, group, hd, vd, len, .. } if l.attends_in_two_words(numerics) => {
205 let [k, v, sc] = kernels::attn_i8_scratch(*len, heads / group, *hd, *vd);
206 vec![t("ak", "signed char", k), t("aw", "signed char", v), t("as", "float", sc)]
207 }
208 Launch::Attention { heads, vd, rows, shared: Some(splits), .. } if *splits > 1 => vec![t("ap", "float", rows * heads * splits * (vd + 2) * 4)],
210 Launch::Attention { shared: Some(_), .. } => Vec::new(),
211 Launch::Attention { heads, vd, rows, per_block, splits, parallel: true, shared: None, .. } if !l.attends_in_tiles(numerics) => {
212 vec![t("ap", acc_ty, rows * heads * splits * (vd + 2) * acc), counter("ac", rows * heads / per_block * 4)]
213 }
214 _ => Vec::new(),
215 }
216}
217
218fn aligned(n: usize) -> usize {
219 n.next_multiple_of(ALIGN)
220}
221
222pub fn array_bytes(len: usize, touch: Touch, two: bool) -> usize {
225 match touch {
226 Touch::Plain => aligned(len * 4),
227 Touch::Quant => aligned(len) * if two { 2 } else { 1 } + aligned(len / QBLOCK * 4),
228 }
229}
230
231pub fn quant_parts(offset: usize, len: usize, two: bool) -> (usize, Option<usize>, usize) {
234 let words = if two { 2 } else { 1 };
235 (offset, two.then(|| offset + aligned(len)), offset + aligned(len) * words)
236}
237
238#[derive(Clone, Debug)]
240enum Written {
241 Whole,
243 Parts(usize, Vec<(usize, usize)>),
245 Unsafe,
247}
248
249impl Written {
250 fn then(self, read: bool, whole: bool, parts: &[(usize, usize, usize)], other: bool) -> Written {
253 let open = match self {
254 Written::Parts(pitch, have) => Some((pitch, have)),
255 done => return done,
256 };
257 if read {
258 return Written::Unsafe;
259 }
260 if whole {
261 return Written::Whole;
262 }
263 if other || parts.is_empty() {
264 return Written::Unsafe;
265 }
266 let (pitch, mut have) = open.unwrap_or((parts[0].0, Vec::new()));
267 for &(p, from, width) in parts {
268 if p != pitch {
269 return Written::Unsafe;
270 }
271 have.push((from, from + width));
272 }
273 have.sort_unstable();
274 let mut reach = 0;
275 for &(from, to) in &have {
276 if from > reach {
277 break;
278 }
279 reach = reach.max(to);
280 }
281 if reach >= pitch {
282 Written::Whole
283 } else {
284 Written::Parts(pitch, have)
285 }
286 }
287}
288
289pub fn place(s: &Schedule) -> Arena {
295 let two = s.numerics == Q8MatKind::TwoPassInt8;
296 let io = s.io.names();
297 let viewed = |name: &str, touch: Touch| home(s, name, touch).to_string();
300 #[derive(Default)]
301 struct Here {
302 read: bool,
303 whole: bool,
304 parts: Vec<(usize, usize, usize)>,
305 other: bool,
306 }
307 let mut life: BTreeMap<(String, Touch), (usize, usize, Written)> = BTreeMap::new();
308 for (i, l) in s.launches.iter().enumerate() {
309 let (reads, writes) = l.touches(s.numerics);
310 let mut here: BTreeMap<(String, Touch), Here> = BTreeMap::new();
311 for (t, touch) in reads {
312 here.entry((viewed(t, touch), touch)).or_default().read = true;
313 }
314 for (t, touch) in writes {
315 let at = viewed(t, touch);
316 let len = s.buffers.get(t).map_or(0, |b| b.len);
317 let own = at == t;
318 let e = here.entry((at, touch)).or_default();
319 if own && l.writes_whole(t, touch, len) {
320 e.whole = true;
321 } else if let (true, Touch::Plain, Some(part)) = (own, touch, l.writes_part(t, len)) {
322 e.parts.push(part);
323 } else {
324 e.other = true;
325 }
326 }
327 for (k, h) in here {
328 let e = life.entry(k).or_insert((i, i, Written::Parts(0, Vec::new())));
329 e.1 = i;
330 let first = e.0 == i;
331 let state = std::mem::replace(&mut e.2, Written::Unsafe);
332 e.2 = if first {
333 Written::Parts(h.parts.first().map_or(0, |p| p.0), Vec::new()).then(h.read, h.whole, &h.parts, h.other)
334 } else {
335 state.then(h.read, h.whole, &h.parts, h.other)
336 };
337 }
338 }
339 #[derive(PartialEq, Eq, PartialOrd, Ord)]
340 enum Item {
341 Array(String, Touch),
342 Scratch(usize, &'static str),
343 }
344 let mut todo: Vec<(usize, usize, usize, Item)> = life
345 .into_iter()
346 .filter(|(_, (.., written))| matches!(written, Written::Whole))
347 .filter_map(|((name, touch), (first, last, _))| {
348 let b = s.buffers.get(&name)?;
349 let allocated = if touch == Touch::Plain { b.plain } else { b.quantized };
350 (b.fill == Fill::Computed && allocated && !io.contains(&name.as_str()))
351 .then(|| (array_bytes(b.len, touch, two), first, last, Item::Array(name, touch)))
352 })
353 .collect();
354 for (i, l) in s.launches.iter().enumerate() {
355 for sc in scratch(l, s.numerics).into_iter().filter(|sc| !sc.counter) {
356 todo.push((aligned(sc.bytes), i, i, Item::Scratch(i, sc.tag)));
357 }
358 }
359 todo.sort_by(|a, b| b.0.cmp(&a.0).then(a.1.cmp(&b.1)).then(a.3.cmp(&b.3)));
360 let mut arena = Arena::default();
361 let mut spans: Vec<(usize, usize, usize, usize)> = Vec::new();
362 for (bytes, first, last, item) in todo {
363 let mut busy: Vec<(usize, usize)> =
364 spans.iter().filter(|&&(_, _, f, l)| f <= last && first <= l).map(|&(o, b, _, _)| (o, o + b)).collect();
365 busy.sort_unstable();
366 let mut offset = 0;
367 for (lo, hi) in busy {
368 if offset + bytes <= lo {
369 break;
370 }
371 offset = offset.max(hi);
372 }
373 arena.bytes = arena.bytes.max(offset + bytes);
374 spans.push((offset, bytes, first, last));
375 match item {
376 Item::Array(name, touch) => arena.placed.push(Placed { name, touch, offset, bytes, first, last }),
377 Item::Scratch(launch, tag) => arena.scratch.push(PlacedScratch { launch, tag, offset, bytes }),
378 }
379 }
380 arena
381}