1use anyhow::{Context, Result, bail, ensure};
17use metrale_circuit::planner::{BufferPlan, Layout};
18use metrale_circuit::{Circuit, FusionPlan, Group, Mode};
19use metrale_config::ModelConfig;
20use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
21
22use super::bindings::{BoundWeight, CircuitLayer, HeadBinding, WeightSlot};
23use super::emitters::emitter;
24pub use super::fixed::{DraftFixed, Fixed};
25use super::kernels::KernelTable;
26use super::program::{Launch, LaunchKind, Program, RunFn};
27use crate::layer::AttnMetadataDev;
28
29pub(crate) struct GroupRef<'a> {
31 pub circuit: &'a Circuit,
32 pub group: &'a Group,
33 pub index: usize,
34}
35
36impl<'a> GroupRef<'a> {
37 pub fn node(&self, i: usize) -> &'a metrale_circuit::ir::Node {
39 &self.circuit.nodes[self.group.nodes[i]]
40 }
41
42 pub fn input(&self, i: usize, j: usize) -> Result<usize> {
44 let n = self.node(i);
45 n.inputs
46 .get(j)
47 .copied()
48 .with_context(|| format!("`{}` has no input {j}", n.id))
49 }
50
51 pub fn output(&self, i: usize, j: usize) -> Result<usize> {
53 let n = self.node(i);
54 n.outputs
55 .get(j)
56 .copied()
57 .with_context(|| format!("`{}` has no output {j}", n.id))
58 }
59
60 pub fn expect_ops(&self, emitter: &str, ops: &[&str]) -> Result<()> {
62 let got: Vec<String> = self
63 .group
64 .nodes
65 .iter()
66 .map(|&n| self.circuit.nodes[n].op.name())
67 .collect();
68 ensure!(
69 got.len() == ops.len() && got.iter().zip(ops).all(|(g, w)| g.starts_with(w)),
70 "emitter `{emitter}` cannot launch group {} ({:?}); it takes {ops:?}",
71 self.index,
72 got
73 );
74 Ok(())
75 }
76}
77
78pub(crate) struct Cx<'a> {
80 pub g: GroupRef<'a>,
81 pub gpu: &'a dyn GpuBackend,
82 pub mode: Mode,
83 pub rows: u64,
84 pub config: &'a ModelConfig,
85 pub fixed: &'a Fixed,
86 pub layers: &'a [CircuitLayer],
87 pub head: &'a HeadBinding,
88 pub draft: Option<&'a CircuitLayer>,
89 table: Option<&'a metrale_circuit::RowTable>,
90 ptrs: &'a [Option<DevicePtr>],
91 strides: &'a [Option<u64>],
92 formats: &'a [metrale_circuit::Format],
93 handles: Vec<KernelHandle>,
94 launches: Vec<Launch>,
95}
96
97impl<'a> Cx<'a> {
98 fn buffer(&self, edge: usize) -> Result<DevicePtr> {
99 self.ptrs[edge].with_context(|| {
100 format!(
101 "edge `{}` has no buffer in this plan (fused away or outside the section)",
102 self.g.circuit.edges[edge].id
103 )
104 })
105 }
106
107 fn row_bytes(&self, edge: usize) -> Result<u64> {
108 let e = &self.g.circuit.edges[edge];
109 self.formats[edge]
110 .bytes(1, e.dim_value)
111 .with_context(|| format!("edge `{}` has no row size", e.id))
112 }
113
114 fn stride_bytes(&self, edge: usize) -> Result<u64> {
116 match self.strides[edge] {
117 Some(s) => Ok(s),
118 None => self.row_bytes(edge),
119 }
120 }
121
122 pub fn ptr(&self, edge: usize) -> Result<DevicePtr> {
125 let p = self.buffer(edge)?;
126 ensure!(
127 self.rows <= 1 || self.stride_bytes(edge)? == self.row_bytes(edge)?,
128 "edge `{}` is laid out in a row pack; it must be read with its row stride",
129 self.g.circuit.edges[edge].id
130 );
131 Ok(p)
132 }
133
134 pub fn strided(&self, edge: usize) -> Result<(DevicePtr, u32)> {
137 let e = &self.g.circuit.edges[edge];
138 let (stride, row) = (self.stride_bytes(edge)?, self.row_bytes(edge)?);
139 ensure!(
140 e.dim_value > 0 && row % e.dim_value == 0 && stride % (row / e.dim_value) == 0,
141 "edge `{}`: a {stride}-byte row stride is not whole elements",
142 e.id
143 );
144 Ok((
145 self.buffer(edge)?,
146 u32::try_from(stride / (row / e.dim_value))?,
147 ))
148 }
149
150 pub fn row_ptr(&self, edge: usize, row: usize) -> Result<DevicePtr> {
152 Ok(self
153 .buffer(edge)?
154 .offset(row * self.stride_bytes(edge)? as usize))
155 }
156
157 pub fn meta(&self) -> Result<AttnMetadataDev> {
159 Ok(match self.mode {
160 Mode::Draft => self.draft_fixed()?.meta_rows(self.rows)?,
161 Mode::Decode => self.fixed.meta,
162 Mode::MultiSeq => self.fixed.batch_meta,
163 Mode::Verify => self.fixed.verify_meta,
164 Mode::VerifyBatch => self.fixed.verify_batch_meta,
165 })
166 }
167
168 pub fn table(&self) -> Option<&'a metrale_circuit::RowTable> {
170 self.table
171 }
172
173 pub fn run_handle(&self, k: &metrale_circuit::KernelId) -> Result<KernelHandle> {
175 let i = self
176 .g
177 .group
178 .kernels
179 .iter()
180 .position(|x| x == k)
181 .with_context(|| format!("group {} does not list {k}", self.g.index))?;
182 self.handle(i)
183 }
184
185 pub fn push_kernel(&mut self, k: &metrale_circuit::KernelId, run: RunFn) -> Result<()> {
187 let i = self
188 .g
189 .group
190 .kernels
191 .iter()
192 .position(|x| x == k)
193 .with_context(|| format!("group {} does not list {k}", self.g.index))?;
194 self.push(i, run)
195 }
196
197 pub fn draft_fixed(&self) -> Result<&'a DraftFixed> {
199 self.fixed
200 .draft
201 .as_ref()
202 .context("a draft plan without the draft head's buffers")
203 }
204
205 pub fn state_row(&self, row: usize) -> usize {
208 if self.mode == Mode::MultiSeq { row } else { 0 }
209 }
210
211 pub fn reps(&self) -> usize {
213 self.g.group.repeat.count(self.rows).unwrap_or(0) as usize
214 }
215
216 pub fn handle(&self, i: usize) -> Result<KernelHandle> {
218 self.handles
219 .get(i)
220 .copied()
221 .with_context(|| format!("group {} has no kernel {i}", self.g.index))
222 }
223
224 pub fn layer(&self, i: usize) -> Result<&'a CircuitLayer> {
226 let n = self.g.node(i);
227 if self.mode == Mode::Draft {
228 return self
229 .draft
230 .with_context(|| format!("`{}`: no draft head is bound", n.id));
231 }
232 let l = n
233 .layer
234 .with_context(|| format!("`{}` is outside the layers", n.id))?;
235 self.layers
236 .get(l)
237 .with_context(|| format!("no binding for layer {l}"))
238 }
239
240 pub fn weight(&self, i: usize, slot: WeightSlot) -> Result<BoundWeight> {
243 let n = self.g.node(i);
244 let w = *self
245 .layer(i)?
246 .weights
247 .get(&slot)
248 .with_context(|| format!("`{}`: its layer binds no {slot:?}", n.id))?;
249 if let Some(f) = n.weight {
250 ensure!(
251 f.name().starts_with(w.family()),
252 "`{}`: the plan resolved {} but the layer holds {}",
253 n.id,
254 f.name(),
255 w.family()
256 );
257 }
258 Ok(w)
259 }
260
261 pub fn push(&mut self, k: usize, run: RunFn) -> Result<()> {
263 let kid = self
264 .g
265 .group
266 .kernels
267 .get(k)
268 .with_context(|| format!("group {} has no kernel {k}", self.g.index))?;
269 self.launches.push(Launch {
270 group: self.g.index,
271 kernel: format!("{}::{}", kid.module, kid.func),
272 kind: LaunchKind::Kernel,
273 run,
274 });
275 Ok(())
276 }
277
278 pub fn push_copy(&mut self, run: RunFn) -> Result<()> {
280 ensure!(
281 self.g.group.copies.is_some() || self.g.group.runs.iter().any(|r| r.copies.is_some()),
282 "group {} declares no copies",
283 self.g.index
284 );
285 self.launches.push(Launch {
286 group: self.g.index,
287 kernel: "copy".to_string(),
288 kind: LaunchKind::Copy,
289 run,
290 });
291 Ok(())
292 }
293}
294
295pub(crate) trait OpEmitter: Sync {
297 fn id(&self) -> &'static str;
299
300 fn constrain(&self, _g: &GroupRef<'_>, _layout: &mut Layout) -> Result<()> {
302 Ok(())
303 }
304
305 fn emit(&self, cx: &mut Cx<'_>) -> Result<()>;
307}
308
309pub fn layout(circuit: &Circuit, plan: &FusionPlan) -> Result<Layout> {
311 let mut layout = Layout::default();
312 let materialized =
313 |e: usize| plan.edge_states[e] == Some(metrale_circuit::EdgeState::Materialized);
314 for b in &circuit.blocks {
315 for e in b.stream_in.into_iter().chain(b.stream_out) {
316 if materialized(e) {
317 layout.external.insert(e);
318 }
319 }
320 }
321 for (e, edge) in circuit.edges.iter().enumerate() {
324 if edge.is_output && materialized(e) {
325 layout.external.insert(e);
326 }
327 }
328 for (index, group) in plan.groups.iter().enumerate() {
329 let g = GroupRef {
330 circuit,
331 group,
332 index,
333 };
334 emitter(&group.emitter)?.constrain(&g, &mut layout)?;
335 }
336 Ok(layout)
337}
338
339pub(super) fn external_buffer(
343 circuit: &Circuit,
344 e: usize,
345 fixed: &Fixed,
346 mode: Mode,
347) -> Result<DevicePtr> {
348 let edge = &circuit.edges[e];
349 let stream = circuit
350 .blocks
351 .iter()
352 .any(|b| b.stream_in == Some(e) || b.stream_out == Some(e));
353 if stream {
354 return Ok(fixed.hidden);
355 }
356 use metrale_circuit::model_buffer::ModelBuffer;
357 match edge.binds {
358 Some(ModelBuffer::Logits) => Ok(fixed.logits),
359 Some(ModelBuffer::Tokens) if mode == Mode::VerifyBatch => Ok(fixed.verify_batch_tokens),
360 Some(ModelBuffer::Tokens) => Ok(fixed.tokens),
361 Some(ModelBuffer::DraftEmbed) => fixed
362 .draft
363 .as_ref()
364 .map(|d| d.embed)
365 .with_context(|| format!("`{}`: no draft embedding buffer", edge.id)),
366 None => bail!(
367 "`{}` is read outside the program but binds no model buffer: an unbound output",
368 edge.id
369 ),
370 }
371}
372
373pub struct Inputs<'a> {
375 pub gpu: &'a dyn GpuBackend,
376 pub config: &'a ModelConfig,
377 pub kernels: &'a KernelTable,
378 pub fixed: &'a Fixed,
379 pub layers: &'a [CircuitLayer],
380 pub head: &'a HeadBinding,
381 pub draft: Option<&'a CircuitLayer>,
382}
383
384pub fn compile(
386 circuit: &Circuit,
387 plan: &FusionPlan,
388 layout: &Layout,
389 buffers: &BufferPlan,
390 workspace: DevicePtr,
391 inp: &Inputs<'_>,
392) -> Result<Program> {
393 let mut ptrs: Vec<Option<DevicePtr>> = vec![None; circuit.edges.len()];
394 let mut strides: Vec<Option<u64>> = vec![None; circuit.edges.len()];
395 for s in &buffers.slots {
396 ptrs[s.edge] = Some(workspace.offset(s.offset as usize));
397 strides[s.edge] = Some(s.row_stride);
398 }
399 for &e in &layout.external {
400 ptrs[e] = Some(external_buffer(circuit, e, inp.fixed, plan.mode)?);
401 }
402 let mut launches = Vec::with_capacity(plan.launches() as usize);
403 for (index, group) in plan.groups.iter().enumerate() {
404 let g = GroupRef {
405 circuit,
406 group,
407 index,
408 };
409 let handles = group
410 .kernels
411 .iter()
412 .map(|k| inp.kernels.handle(k))
413 .collect::<Result<Vec<_>>>()?;
414 let mut cx = Cx {
415 g,
416 gpu: inp.gpu,
417 mode: plan.mode,
418 rows: plan.rows,
419 config: inp.config,
420 fixed: inp.fixed,
421 layers: inp.layers,
422 head: inp.head,
423 draft: inp.draft,
424 table: plan.table.as_ref(),
425 ptrs: &ptrs,
426 strides: &strides,
427 formats: &plan.edge_formats,
428 handles,
429 launches: Vec::new(),
430 };
431 emitter(&group.emitter)?
432 .emit(&mut cx)
433 .with_context(|| format!("group {index} (rule `{}`)", group.rule))?;
434 let want = group.launch_count(plan.rows);
435 let want_copies = group.copy_count(plan.rows);
436 let copies = cx
437 .launches
438 .iter()
439 .filter(|l| l.kind == LaunchKind::Copy)
440 .count() as u64;
441 let kernels = cx.launches.len() as u64 - copies;
442 if (kernels, copies) != (want, want_copies) {
443 bail!(
444 "emitter `{}` queued {kernels} launches and {copies} copies for group {index}; \
445 the plan counts {want} and {want_copies}",
446 group.emitter,
447 );
448 }
449 launches.append(&mut cx.launches);
450 }
451 ensure!(launches.len() as u64 == plan.launches() + plan.copies());
452 Ok(Program {
453 mode: plan.mode,
454 rows: plan.rows,
455 plan_digest: plan.digest.clone(),
456 launches,
457 })
458}