1use super::ir::SpatialProgram;
21use super::pipeline::compile_program_to_spatial_tp_agreeing_stored;
22use super::pipeline::{compile_program_to_spatial_agreeing_stored, dense_tables_of};
23use super::target::SpatialTarget;
24use super::tensor_parallel::TpManifest;
25use crate::model::author::{layer_input, stacked_layer_input, TpAuthored};
26use crate::model::program::{written_in, written_split, Cut, Identity};
27use crate::model::bind::{bind, stored_as_q8_planes, Source};
28use crate::model::reference::WeightSource;
29use crate::model::spec::{Ffn, Mixer, ModelSpec};
30use std::collections::BTreeSet;
31
32#[derive(Clone, Copy)]
34pub enum Storage<'a> {
35 AllQ8,
37 Checkpoint(&'a dyn WeightSource),
39}
40
41impl Storage<'_> {
42 pub fn q8(&self, spec: &ModelSpec, var: &str) -> bool {
44 match self {
45 Storage::AllQ8 => true,
46 Storage::Checkpoint(source) => match bind(spec, var) {
47 Ok(Source::Weight(b)) => stored_as_q8_planes(&b, *source).unwrap_or(false),
48 _ => false,
49 },
50 }
51 }
52}
53
54#[derive(Clone, Debug)]
56pub struct LayerKind {
57 pub name: String,
60 pub representative: usize,
62 pub identity: Identity,
64 pub program: SpatialProgram,
65 pub tp: Option<TpManifest>,
68 pub carried: BTreeSet<String>,
71}
72
73#[derive(Clone, Debug, PartialEq, Eq)]
76pub struct LayerBinding {
77 pub layer: usize,
78 pub kind: usize,
79 pub tensors: Vec<(String, String)>,
80 pub identity: Identity,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
87pub struct StreamedReject {
88 pub program: String,
90 pub why: String,
91}
92
93impl std::fmt::Display for StreamedReject {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 write!(
96 f,
97 "the {} program does not lower: {}",
98 self.program, self.why
99 )
100 }
101}
102
103#[derive(Clone, Debug)]
104pub struct StreamedModel {
105 pub embed: Option<SpatialProgram>,
108 pub embed_identity: Option<Identity>,
110 pub kinds: Vec<LayerKind>,
111 pub head: SpatialProgram,
112 pub head_identity: Identity,
114 pub layers: Vec<LayerBinding>,
115 pub residency: super::residency::ResidencyPlan,
118}
119
120pub fn rebind(name: &str, from: usize, to: usize) -> String {
131 rebind_via(name, from, to, layer_input)
132}
133
134pub fn rebind_via(name: &str, from: usize, to: usize, input: fn(usize) -> String) -> String {
137 if name == input(from) {
138 return input(to);
139 }
140 if crate::model::bind::HostTable::named(name).is_some() {
143 return name.to_string();
144 }
145 let (head, tail) = match name.find('_') {
146 Some(i) => name.split_at(i),
147 None => (name, ""),
148 };
149 let suffix = from.to_string();
150 match head.strip_suffix(suffix.as_str()) {
151 Some(stem) if !stem.is_empty() && !stem.ends_with(|c: char| c.is_ascii_digit()) => {
152 format!("{stem}{to}{tail}")
153 }
154 _ => name.to_string(),
155 }
156}
157
158type Generated = (String, Option<TpManifest>, BTreeSet<String>, Identity);
161
162fn tokens(src: &str) -> Vec<&str> {
164 let mut out = Vec::new();
165 let mut rest = src.trim_start();
166 while let Some(c) = rest.chars().next() {
167 let len = if c.is_ascii_alphanumeric() || c == '_' {
168 rest.find(|c: char| !(c.is_ascii_alphanumeric() || c == '_')).unwrap_or(rest.len())
169 } else {
170 c.len_utf8()
171 };
172 out.push(&rest[..len]);
173 rest = rest[len..].trim_start();
174 }
175 out
176}
177
178fn binders<'a>(tokens: &[&'a str]) -> BTreeSet<&'a str> {
181 tokens.windows(2).filter(|w| w[0] == "Let").map(|w| w[1]).collect()
182}
183
184fn shape_digest(src: &str, stored: &BTreeSet<String>) -> [u8; 32] {
188 let toks = tokens(src);
189 let names = binders(&toks);
190 let mut order: std::collections::HashMap<&str, usize> = std::collections::HashMap::new();
191 let mut canonical = String::with_capacity(src.len());
192 for t in toks {
193 if names.contains(t) {
194 let next = order.len();
195 canonical.push_str(&format!("\u{1}{}", order.entry(t).or_insert(next)));
196 if stored.contains(t) {
197 canonical.push('\u{2}');
198 }
199 } else {
200 canonical.push_str(t);
201 }
202 canonical.push(' ');
203 }
204 crate::crypto_accel::sha256(canonical.as_bytes())
205}
206
207pub fn renaming(from: &str, to: &str) -> Option<std::collections::BTreeMap<String, String>> {
211 let (a, b) = (tokens(from), tokens(to));
212 if a.len() != b.len() {
213 return None;
214 }
215 let (names_a, names_b) = (binders(&a), binders(&b));
216 let (mut forward, mut back) = (std::collections::BTreeMap::new(), std::collections::BTreeMap::new());
217 for (x, y) in a.into_iter().zip(b) {
218 if names_a.contains(x) != names_b.contains(y) {
219 return None;
220 }
221 if names_a.contains(x) {
222 if *forward.entry(x.to_string()).or_insert_with(|| y.to_string()) != y
223 || *back.entry(y.to_string()).or_insert_with(|| x.to_string()) != x
224 {
225 return None;
226 }
227 } else if x != y {
228 return None;
229 }
230 }
231 Some(forward)
232}
233
234pub fn layer_kinds(spec: &ModelSpec, source_of: &dyn Fn(usize) -> (String, BTreeSet<String>)) -> Vec<(String, Vec<usize>)> {
239 let mut kinds: Vec<([u8; 32], Vec<usize>)> = Vec::new();
240 for l in 0..spec.layers.len() {
241 let (src, stored) = source_of(l);
242 let digest = shape_digest(&src, &stored);
243 match kinds.iter_mut().find(|(d, _)| *d == digest) {
244 Some((_, layers)) => layers.push(l),
245 None => kinds.push((digest, vec![l])),
246 }
247 }
248 let describe = |l: usize| {
249 let layer = &spec.layers[l];
250 let mut name = match &layer.mixer {
251 Mixer::Attention(a) => match a.window {
252 Some(crate::model::spec::Window::Sliding(w)) => format!("attention, window {w}"),
253 Some(crate::model::spec::Window::Chunked(c)) => format!("attention, chunks of {c}"),
254 None => "attention".to_string(),
255 },
256 Mixer::GatedDelta(_) => "gated deltanet".to_string(),
257 Mixer::Mla(_) => "latent attention".to_string(),
258 };
259 if matches!(layer.ffn, Ffn::Moe(_)) {
260 name.push_str(", experts");
261 }
262 name
263 };
264 let names: Vec<String> = kinds.iter().map(|(_, layers)| describe(layers[0])).collect();
265 kinds
266 .into_iter()
267 .zip(&names)
268 .map(|((_, layers), name)| {
269 let unique = names.iter().filter(|n| *n == name).count() == 1;
271 (if unique { name.clone() } else { format!("{name} (layer {})", layers[0]) }, layers)
272 })
273 .collect()
274}
275
276fn q8_slices<V>(m: &TpManifest, full: &std::collections::HashMap<String, V>, spec: &ModelSpec, storage: Storage) -> BTreeSet<String> {
281 let whole_blocks = |s: &super::tensor_parallel::TpSlice| {
282 s.axis == super::tensor_parallel::SliceAxis::Cols
283 || s.ranges.iter().all(|(start, n)| {
284 start % super::passes::from_q8::Q8_BLOCK == 0
285 && n % super::passes::from_q8::Q8_BLOCK == 0
286 })
287 };
288 m.slices
289 .iter()
290 .filter(|(_, s)| full.contains_key(&s.of) && storage.q8(spec, &s.of) && whole_blocks(s))
291 .map(|(name, _)| name.clone())
292 .collect()
293}
294
295fn q8_names<V>(table: &std::collections::HashMap<String, V>, spec: &ModelSpec, storage: Storage) -> BTreeSet<String> {
296 table.keys().filter(|v| storage.q8(spec, v)).cloned().collect()
297}
298
299fn lower_certified(
300 src: &str,
301 target: &SpatialTarget,
302 program: &str,
303 dense: &BTreeSet<String>,
304 tp: Option<&TpManifest>,
305 q8: &BTreeSet<String>,
306) -> Result<SpatialProgram, StreamedReject> {
307 lower_certified_carrying(src, target, program, dense, tp, q8).map(|(p, _)| p)
308}
309
310fn lower_certified_carrying(
312 src: &str,
313 target: &SpatialTarget,
314 program: &str,
315 dense: &BTreeSet<String>,
316 tp: Option<&TpManifest>,
317 q8: &BTreeSet<String>,
318) -> Result<(SpatialProgram, BTreeSet<String>), StreamedReject> {
319 let reject = |why: String| StreamedReject {
320 program: program.to_string(),
321 why,
322 };
323 let lowering = match tp {
324 Some(m) => compile_program_to_spatial_tp_agreeing_stored(src, target, m, dense, q8),
325 None => compile_program_to_spatial_agreeing_stored(src, target, dense, q8),
326 }
327 .map_err(|e| reject(format!("{e:?}")))?;
328 let carried = lowering.carried.iter().cloned().collect();
329 let p = lowering.into_whole().map_err(reject)?;
330 if p.certificate.is_none() {
331 return Err(reject("the lowering carries no certificate".to_string()));
332 }
333 Ok((p, carried))
334}
335
336pub fn lower_forward_streamed(
342 spec: &ModelSpec,
343 storage: Storage,
344 target: &SpatialTarget,
345 fill: impl Fn(&str) -> String,
346) -> Result<StreamedModel, StreamedReject> {
347 let ((head, _, head_q8), head_id) = written_in(spec, Cut::ForwardHead, "/w");
348 lower_streamed(
349 spec,
350 target,
351 &fill,
352 None,
353 &|l| {
354 let ((src, _, q8), id) = written_in(spec, Cut::ForwardLayer { layer: l }, "/w");
355 (src, None, q8_names(&q8, spec, storage), id)
356 },
357 (&head, &q8_names(&head_q8, spec, storage), head_id),
358 layer_input,
359 )
360}
361
362pub fn lower_decode_streamed(
367 spec: &ModelSpec,
368 storage: Storage,
369 ctx: usize,
370 target: &SpatialTarget,
371 fill: impl Fn(&str) -> String,
372) -> Result<StreamedModel, StreamedReject> {
373 if ctx == 0 {
374 return Err(StreamedReject {
375 program: "bundle".into(),
376 why: "a decode bundle with an empty KV cache".into(),
377 });
378 }
379 let ((embed, _, _), embed_id) = written_in(spec, Cut::DecodeEmbed, "/w");
380 let ((head, _, head_q8), head_id) = written_in(spec, Cut::DecodeHead, "/w");
381 lower_streamed(
382 spec,
383 target,
384 &fill,
385 Some((&embed, embed_id)),
386 &|l| {
387 let ((src, _, q8), id) = written_in(spec, Cut::DecodeLayer { layer: l, ctx }, "/w");
388 (src, None, q8_names(&q8, spec, storage), id)
389 },
390 (&head, &q8_names(&head_q8, spec, storage), head_id),
391 layer_input,
392 )
393}
394
395pub fn lower_tp_decode_streamed(
401 spec: &ModelSpec,
402 storage: Storage,
403 ctx: usize,
404 tp: usize,
405 target: &SpatialTarget,
406 fill: impl Fn(&str) -> String,
407) -> Result<StreamedModel, StreamedReject> {
408 if ctx == 0 {
409 return Err(StreamedReject {
410 program: "bundle".into(),
411 why: "a decode bundle with an empty KV cache".into(),
412 });
413 }
414 if let Err(r) = super::fit::admits_tp(spec, tp) {
415 return Err(StreamedReject {
416 program: "bundle".into(),
417 why: format!("tensor parallel over {tp} chips: {}", r.why),
418 });
419 }
420 let ((embed, _, _), embed_id) = written_in(spec, Cut::DecodeEmbed, "/w");
421 let ((head, _, head_q8), head_id) = written_in(spec, Cut::DecodeHead, "/w");
422 lower_streamed(
423 spec,
424 target,
425 &fill,
426 Some((&embed, embed_id)),
427 &|l| {
428 let ((src, _, q8w, m), id) = split(spec, tp, written_split(spec, Cut::DecodeLayer { layer: l, ctx }, "/w", tp));
429 let q8 = q8_slices(&m, &q8w, spec, storage);
430 (src, Some(m), q8, id)
431 },
432 (&head, &q8_names(&head_q8, spec, storage), head_id),
433 layer_input,
434 )
435}
436
437pub fn lower_batched_decode_streamed(
442 spec: &ModelSpec,
443 storage: Storage,
444 ctx: usize,
445 batch: usize,
446 target: &SpatialTarget,
447 fill: impl Fn(&str) -> String,
448) -> Result<StreamedModel, StreamedReject> {
449 if ctx == 0 {
450 return Err(StreamedReject {
451 program: "bundle".into(),
452 why: "a decode bundle with an empty KV cache".into(),
453 });
454 }
455 if batch == 0 {
456 return Err(StreamedReject {
457 program: "bundle".into(),
458 why: "a batched decode bundle serving no sequence".into(),
459 });
460 }
461 let ((embed, _, _), embed_id) = written_in(spec, Cut::BatchEmbed { rows: batch }, "/w");
462 let ((head, _, head_q8), head_id) = written_in(spec, Cut::BatchHead { rows: batch }, "/w");
463 lower_streamed(
464 spec,
465 target,
466 &fill,
467 Some((&embed, embed_id)),
468 &|l| {
469 let ((src, _, q8), id) = written_in(spec, Cut::BatchLayer { layer: l, rows: batch, ctx }, "/w");
470 (src, None, q8_names(&q8, spec, storage), id)
471 },
472 (&head, &q8_names(&head_q8, spec, storage), head_id),
473 stacked_layer_input,
474 )
475}
476
477pub fn lower_prefill_streamed(
482 spec: &ModelSpec,
483 storage: Storage,
484 ctx: usize,
485 chunk: usize,
486 target: &SpatialTarget,
487 fill: impl Fn(&str) -> String,
488) -> Result<StreamedModel, StreamedReject> {
489 if ctx == 0 {
490 return Err(StreamedReject {
491 program: "bundle".into(),
492 why: "a prefill bundle with an empty KV cache".into(),
493 });
494 }
495 if chunk == 0 || chunk > ctx {
496 return Err(StreamedReject {
497 program: "bundle".into(),
498 why: format!("a prefill chunk of {chunk} tokens in a context of {ctx}"),
499 });
500 }
501 let ((embed, _, _), embed_id) = written_in(spec, Cut::BatchEmbed { rows: chunk }, "/w");
502 let ((head, _, head_q8), head_id) = written_in(spec, Cut::PrefillHead { rows: chunk }, "/w");
503 lower_streamed(
504 spec,
505 target,
506 &fill,
507 Some((&embed, embed_id)),
508 &|l| {
509 let ((src, _, q8), id) = written_in(spec, Cut::PrefillLayer { layer: l, rows: chunk, ctx }, "/w");
510 (src, None, q8_names(&q8, spec, storage), id)
511 },
512 (&head, &q8_names(&head_q8, spec, storage), head_id),
513 stacked_layer_input,
514 )
515}
516
517pub fn lower_batched_tp_decode_streamed(
521 spec: &ModelSpec,
522 storage: Storage,
523 ctx: usize,
524 batch: usize,
525 tp: usize,
526 target: &SpatialTarget,
527 fill: impl Fn(&str) -> String,
528) -> Result<StreamedModel, StreamedReject> {
529 if ctx == 0 {
530 return Err(StreamedReject {
531 program: "bundle".into(),
532 why: "a decode bundle with an empty KV cache".into(),
533 });
534 }
535 if batch == 0 {
536 return Err(StreamedReject {
537 program: "bundle".into(),
538 why: "a batched decode bundle serving no sequence".into(),
539 });
540 }
541 if let Err(r) = super::fit::admits_tp(spec, tp) {
542 return Err(StreamedReject {
543 program: "bundle".into(),
544 why: format!("tensor parallel over {tp} chips: {}", r.why),
545 });
546 }
547 let ((embed, _, _), embed_id) = written_in(spec, Cut::BatchEmbed { rows: batch }, "/w");
548 let ((head, _, head_q8), head_id) = written_in(spec, Cut::BatchHead { rows: batch }, "/w");
549 lower_streamed(
550 spec,
551 target,
552 &fill,
553 Some((&embed, embed_id)),
554 &|l| {
555 let ((src, _, q8w, m), id) =
556 split(spec, tp, written_split(spec, Cut::BatchLayer { layer: l, rows: batch, ctx }, "/w", tp));
557 let q8 = q8_slices(&m, &q8w, spec, storage);
558 (src, Some(m), q8, id)
559 },
560 (&head, &q8_names(&head_q8, spec, storage), head_id),
561 stacked_layer_input,
562 )
563}
564
565fn split(spec: &ModelSpec, tp: usize, form: Result<(TpAuthored, Identity), crate::model::author::NotYet>) -> (TpAuthored, Identity) {
567 form.unwrap_or_else(|e| panic!("{} does not split over {tp} chips: {e}", spec.arch))
568}
569
570fn lower_streamed(
571 spec: &ModelSpec,
572 target: &SpatialTarget,
573 fill: &dyn Fn(&str) -> String,
574 embed: Option<(&str, Identity)>,
575 layer: &dyn Fn(usize) -> Generated,
576 (head, head_q8, head_identity): (&str, &BTreeSet<String>, Identity),
577 input: fn(usize) -> String,
578) -> Result<StreamedModel, StreamedReject> {
579 let embed_identity = embed.map(|(_, id)| id);
580 let embed = embed.map(|(src, _)| src);
581 let tables = |src: &str, program: String| {
587 dense_tables_of(src, target).map_err(|e| StreamedReject {
588 program,
589 why: format!("{e:?}"),
590 })
591 };
592 let mut dense: BTreeSet<String> = BTreeSet::new();
593 if let Some(src) = embed {
594 dense.extend(tables(src, "embed".into())?);
595 }
596 dense.extend(tables(head, "head".into())?);
597 let generated: Vec<Generated> = (0..spec.layers.len()).map(layer).collect();
598 let kind_layers = layer_kinds(spec, &|l| (generated[l].0.clone(), generated[l].2.clone()));
599 let renames: Vec<std::collections::BTreeMap<String, String>> = (0..spec.layers.len())
602 .map(|l| {
603 let rep = kind_layers.iter().find(|(_, m)| m.contains(&l)).expect("every layer is of a kind").1[0];
604 renaming(&generated[rep].0, &generated[l].0).expect("layers of one kind are one program up to their names")
605 })
606 .collect();
607 let name_in = |l: usize, rep: usize, name: &str| -> String {
608 if let Some(to) = renames[l].get(name) {
609 return to.clone();
610 }
611 match name.split_once("__") {
612 Some((stem, derived)) if renames[l].contains_key(stem) => format!("{}__{derived}", renames[l][stem]),
613 _ => rebind_via(name, rep, l, input),
614 }
615 };
616 for (_, members) in &kind_layers {
617 let rep = members[0];
618 let kind = tables(&generated[rep].0, format!("layer {rep}"))?;
619 for &l in members {
620 dense.extend(kind.iter().map(|n| name_in(l, rep, n)));
621 }
622 }
623 let none = BTreeSet::new();
624 let embed = embed
625 .map(|src| lower_certified(&fill(src), target, "embed", &dense, None, &none))
626 .transpose()?;
627 let mut kinds: Vec<LayerKind> = Vec::new();
628 let mut layers = Vec::with_capacity(spec.layers.len());
629 for l in 0..spec.layers.len() {
630 let name = &kind_layers
631 .iter()
632 .find(|(_, members)| members.contains(&l))
633 .expect("every layer is of a kind")
634 .0;
635 let kind = match kinds.iter().position(|k| &k.name == name) {
636 Some(k) => k,
637 None => {
638 let (src, tp, q8, identity) = generated[l].clone();
639 let (program, carried) = lower_certified_carrying(
640 &fill(&src),
641 target,
642 &format!("layer {l} ({name})"),
643 &dense,
644 tp.as_ref(),
645 &q8,
646 )?;
647 kinds.push(LayerKind {
648 name: name.clone(),
649 representative: l,
650 identity,
651 program,
652 tp,
653 carried,
654 });
655 kinds.len() - 1
656 }
657 };
658 let rep = kinds[kind].representative;
659 let tensors = kinds[kind]
660 .program
661 .tensors
662 .iter()
663 .map(|t| (t.name.clone(), name_in(l, rep, &t.name)))
664 .collect();
665 layers.push(LayerBinding {
666 layer: l,
667 kind,
668 tensors,
669 identity: generated[l].3,
670 });
671 }
672 let mut head = lower_certified(&fill(head), target, "head", &dense, None, head_q8)?;
673 let mut embed = embed;
674 let instances: Vec<usize> = (0..kinds.len())
675 .map(|k| layers.iter().filter(|l| l.kind == k).count())
676 .collect();
677 let residency = {
678 let mut programs: Vec<super::residency::DeployedProgram> = Vec::new();
679 if let Some(e) = embed.as_mut() {
680 programs.push(("embed", 1, e, None));
681 }
682 for (k, n) in kinds.iter_mut().zip(&instances) {
683 let LayerKind { name, program, carried, .. } = k;
684 programs.push((name.as_str(), *n, program, Some(carried)));
685 }
686 programs.push(("head", 1, &mut head, None));
687 super::residency::plan_deployment(&mut programs, target)
688 };
689 Ok(StreamedModel {
690 embed,
691 embed_identity,
692 kinds,
693 head,
694 head_identity,
695 layers,
696 residency,
697 })
698}