1use std::collections::{BTreeMap, BTreeSet};
25
26use serde::Serialize;
27
28use super::ir::*;
29use super::passes::from_qwen_ops::elem_precision;
30use super::passes::from_tensor_op::{
31 elementwise_int, ident_map, items_per_tile_grid, space, tile_grid_steps, Template,
32};
33use super::passes::shard::{crossing_handshake, crossing_kind, ShardError};
34use super::passes::SpatialReject;
35use super::pipeline::{run_passes, run_passes_uncertified, SectionCertification};
36use super::target::SpatialTarget;
37use crate::codegen_sva::fabric_target::Precision;
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
41pub enum SliceAxis {
42 Rows,
43 Cols,
44}
45
46#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
54pub struct TpSlice {
55 pub of: String,
56 pub of_shape: (usize, usize),
57 pub axis: SliceAxis,
58 pub ranges: Vec<(usize, usize)>,
59}
60
61impl TpSlice {
62 pub fn len(&self) -> usize {
64 self.ranges.iter().map(|(_, n)| n).sum()
65 }
66
67 fn full_coord(&self, x: usize) -> usize {
69 let mut at = x;
70 for (start, n) in &self.ranges {
71 if at < *n {
72 return start + at;
73 }
74 at -= n;
75 }
76 panic!(
77 "coordinate {x} is past the slice's {} of `{}`",
78 self.len(),
79 self.of
80 )
81 }
82
83 pub fn full_index(&self, r: usize, c: usize) -> usize {
85 match self.axis {
86 SliceAxis::Rows => self.full_coord(r) * self.of_shape.1 + c,
87 SliceAxis::Cols => r * self.of_shape.1 + self.full_coord(c),
88 }
89 }
90
91 pub fn shape(&self) -> (usize, usize) {
93 match self.axis {
94 SliceAxis::Rows => (self.len(), self.of_shape.1),
95 SliceAxis::Cols => (self.of_shape.0, self.len()),
96 }
97 }
98}
99
100#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
103pub struct TpCollective {
104 pub out: String,
105 pub partials: Vec<String>,
106 pub folds: Vec<String>,
107}
108
109#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
111pub struct TpManifest {
112 pub chips: usize,
113 pub chip_of: BTreeMap<String, u16>,
115 pub slices: BTreeMap<String, TpSlice>,
117 pub collectives: Vec<TpCollective>,
118}
119
120impl TpManifest {
121 pub fn new(chips: usize) -> Self {
122 TpManifest {
123 chips,
124 ..Default::default()
125 }
126 }
127
128 pub fn place(&mut self, name: &str, chip: usize, slice: Option<TpSlice>) {
130 self.chip_of.insert(name.to_string(), chip as u16);
131 if let Some(s) = slice {
132 self.slices.insert(name.to_string(), s);
133 }
134 }
135
136 pub fn collective_folding(&self, binder: &str) -> Option<&TpCollective> {
138 self.collectives
139 .iter()
140 .find(|c| c.folds.iter().any(|f| f == binder))
141 }
142
143 pub fn chip_running(&self, name: &str) -> u16 {
145 self.chip_of.get(name).copied().unwrap_or(0)
146 }
147}
148
149pub struct TensorParallelShim<'a> {
158 pub base: &'a dyn crate::tensor_graph::shim::Shim,
159 pub manifest: &'a TpManifest,
160}
161
162impl crate::tensor_graph::shim::Shim for TensorParallelShim<'_> {
163 fn name(&self) -> String {
164 format!("{}:tp{}", self.base.name(), self.manifest.chips)
165 }
166
167 fn supports(&self, class: crate::tensor_graph::symbols::ClassId) -> crate::tensor_graph::shim::Support {
168 self.base.supports(class)
169 }
170
171 fn fits(&self, op: &crate::tensor_graph::graph::Op, g: &crate::tensor_graph::graph::TensorGraph) -> Result<(), String> {
172 if matches!(op.sym, crate::tensor_graph::graph::Sym::Fused(_)) {
173 let members = crate::tensor_graph::fuse::leaves(op.clone());
174 if let Some(m) = members.iter().find(|m| self.manifest.collective_folding(&m.out).is_some()) {
175 return Err(format!("`{}` folds a collective, which lowers as its all-reduce", m.out));
176 }
177 let chip = |m: &crate::tensor_graph::graph::Op| self.manifest.chip_of.get(&m.out).copied();
178 if let Some(m) = members.iter().find(|m| chip(m) != chip(&members[0])) {
179 return Err(format!("`{}` and `{}` run on different chips", members[0].out, m.out));
180 }
181 }
182 self.base.fits(op, g)
183 }
184
185 fn activation(
186 &self,
187 consumer: &crate::tensor_graph::graph::Op,
188 operand: usize,
189 g: &crate::tensor_graph::graph::TensorGraph,
190 ) -> Option<crate::tensor_graph::graph::QuantFormat> {
191 self.base.activation(consumer, operand, g)
192 }
193
194 fn rules(&self) -> Vec<(crate::tensor_graph::shim::Position, crate::tensor_graph::fuse::ChipRule<'_>)> {
195 self.base.rules()
196 }
197
198 fn numerics(&self) -> Option<crate::tensor_graph::numerics::Contract> {
199 self.base.numerics()
200 }
201}
202
203#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
206pub struct ChipTransfer {
207 pub tensor: String,
208 pub from: u16,
209 pub to: u16,
210}
211
212pub fn transfers(stages: &[super::passes::recognize_stages::RecognizedStage], m: &TpManifest) -> Vec<ChipTransfer> {
217 let mut writer: BTreeMap<&str, u16> = BTreeMap::new();
218 let mut out: Vec<ChipTransfer> = Vec::new();
219 for s in stages {
220 let here = m.chip_running(&s.out);
221 for name in &s.ins {
222 let Some(&from) = writer.get(name.as_str()) else {
223 continue;
224 };
225 if from != here && !out.iter().any(|t| t.tensor == *name && t.to == here) {
226 out.push(ChipTransfer {
227 tensor: name.clone(),
228 from,
229 to: here,
230 });
231 }
232 }
233 writer.insert(&s.out, here);
234 }
235 out
236}
237
238pub fn link_points(p: &SpatialProgram, chip: u16, transfers: &[ChipTransfer]) -> Result<Vec<LinkPoint>, TpReject> {
242 let touch = |name: &str| -> (Vec<usize>, Vec<usize>, Option<TensorId>) {
243 let (mut stores, mut loads) = (Vec::new(), Vec::new());
244 for (k, sec) in p.sections.iter().enumerate() {
245 for st in &p.stages[sec.stages.0..sec.stages.1] {
246 match &st.kind {
247 StageKind::Store { tensor, .. } if p.tensor(*tensor).name == name => stores.push(k),
248 StageKind::Load { tensor, .. } if p.tensor(*tensor).name == name => loads.push(k),
249 _ => {}
250 }
251 }
252 }
253 (stores, loads, p.tensors.iter().find(|t| t.name == name).map(|t| t.id))
254 };
255 let unlinked = |t: &ChipTransfer, why: &'static str| TpReject::Unlinked {
256 tensor: t.tensor.clone(),
257 chip,
258 why,
259 };
260 fn point(at: &mut BTreeMap<Option<usize>, LinkPoint>, after: Option<usize>) -> &mut LinkPoint {
261 at.entry(after).or_insert(LinkPoint {
262 after,
263 sends: Vec::new(),
264 receives: Vec::new(),
265 })
266 }
267 let mut at: BTreeMap<Option<usize>, LinkPoint> = BTreeMap::new();
268 for t in transfers {
269 if t.from == chip {
270 let (stores, _, id) = touch(&t.tensor);
271 let (Some(last), Some(id)) = (stores.last(), id) else {
272 return Err(unlinked(t, "it sends a tensor no section of it stores"));
273 };
274 point(&mut at, Some(*last)).sends.push(id);
275 }
276 if t.to == chip {
277 let (stores, loads, id) = touch(&t.tensor);
278 if !stores.is_empty() {
279 return Err(unlinked(t, "it receives a tensor a section of its own stores"));
280 }
281 let (Some(first), Some(id)) = (loads.first(), id) else {
282 return Err(unlinked(t, "it receives a tensor no section of it loads"));
283 };
284 point(&mut at, first.checked_sub(1)).receives.push(id);
285 }
286 }
287 Ok(at.into_values().collect())
288}
289
290#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
292pub enum RelayStep {
293 Run { chip: u16, to: usize },
296 Copy { tensor: String, from: u16, to: u16 },
298}
299
300pub fn relay_schedule(chips: &[SpatialProgram], transfers: &[ChipTransfer]) -> Result<Vec<RelayStep>, String> {
304 let named = |p: &SpatialProgram, ids: &[TensorId]| -> Vec<String> { ids.iter().map(|t| p.tensor(*t).name.clone()).collect() };
305 let mut at: Vec<Option<usize>> = vec![None; chips.len()];
306 let mut sent: BTreeSet<String> = BTreeSet::new();
307 let mut copied: BTreeSet<(String, u16)> = BTreeSet::new();
308 let mut steps = Vec::new();
309 while at.iter().zip(chips).any(|(a, p)| *a != Some(p.links.len())) {
310 let mut moved = false;
311 for (c, p) in chips.iter().enumerate() {
312 if at[c] == Some(p.links.len()) {
313 continue;
314 }
315 let wants: Vec<String> = at[c].map(|i| named(p, &p.links[i].receives)).unwrap_or_default();
316 if !wants.iter().all(|t| sent.contains(t) || copied.contains(&(t.clone(), c as u16))) {
317 continue;
318 }
319 for t in wants {
320 if copied.insert((t.clone(), c as u16)) {
321 let from = transfers
322 .iter()
323 .find(|x| x.tensor == t && x.to == c as u16)
324 .map(|x| x.from)
325 .ok_or_else(|| format!("chip {c} receives {t}, which no transfer brings it"))?;
326 steps.push(RelayStep::Copy { tensor: t, from, to: c as u16 });
327 }
328 }
329 let next = at[c].map_or(0, |i| i + 1);
330 steps.push(RelayStep::Run { chip: c as u16, to: next });
331 if let Some(l) = p.links.get(next) {
332 sent.extend(named(p, &l.sends));
333 }
334 at[c] = Some(next);
335 moved = true;
336 }
337 if !moved {
338 return Err(format!("no chip can move: each stands at {at:?} of {:?} halts, with {sent:?} sent", chips.iter().map(|p| p.links.len()).collect::<Vec<_>>()));
339 }
340 }
341 Ok(steps)
342}
343
344#[derive(Clone, Debug, PartialEq)]
346pub enum TpReject {
347 Multicast {
349 program: String,
350 },
351 NoCrossing(ShardError),
353 Passes(SpatialReject),
354 Unlinked {
357 tensor: String,
358 chip: u16,
359 why: &'static str,
360 },
361}
362
363impl From<SpatialReject> for TpReject {
364 fn from(e: SpatialReject) -> Self {
365 TpReject::Passes(e)
366 }
367}
368
369fn on_chip(s: &mut Site, chip: u16) {
370 s.chip = chip;
371}
372
373pub fn relocate(
380 p: &SpatialProgram,
381 binder: &str,
382 m: &TpManifest,
383) -> Result<SpatialProgram, TpReject> {
384 let chip = m.chip_running(binder);
385 let chip_local = m.chip_of.contains_key(binder);
386 let mut q = p.clone();
387 if q.placement.as_ref().is_some_and(|pl| !pl.groups.is_empty()) {
388 return Err(TpReject::Multicast {
389 program: q.name.clone(),
390 });
391 }
392 super::passes::pack::for_each_site_mut(&mut q, &mut |s| s.chip = chip);
393 let stored: BTreeSet<TensorId> = q
394 .stages
395 .iter()
396 .filter_map(|s| match &s.kind {
397 StageKind::Store { tensor, .. } => Some(*tensor),
398 _ => None,
399 })
400 .collect();
401 let chip_of = |name: &str| {
403 m.chip_of.get(name).copied().or_else(|| {
404 super::passes::from_q8::stored_part(name).and_then(|(w, _)| m.chip_of.get(w).copied())
405 })
406 };
407 for t in &mut q.tensors {
408 t.chip = chip_of(&t.name)
409 .or((chip_local && stored.contains(&t.id)).then_some(chip))
410 .map(ChipId);
411 }
412 q.certificate = None;
413 Ok(q)
414}
415
416pub fn allreduce_section(
432 col: &TpCollective,
433 shape: (usize, usize),
434 floats: bool,
435 target: &SpatialTarget,
436 certification: SectionCertification,
437 narrow: &mut dyn FnMut(SpatialProgram) -> SpatialProgram,
438) -> Result<SpatialProgram, TpReject> {
439 let chips = col.partials.len();
440 let (rows, cols) = shape;
441 let dt: Precision = if floats {
442 elem_precision(target)?
443 } else {
444 elementwise_int(target).ok_or(TpReject::Passes(SpatialReject::MissingCompute {
445 needed: "an integer elementwise datapath",
446 target: target.name,
447 }))?
448 };
449 let mut t = Template::new(target)?;
450 let parts: Vec<TensorId> = col
451 .partials
452 .iter()
453 .map(|n| t.tensor(n, TensorRole::Input, rows, cols, dt, TensorInit::External))
454 .collect();
455 let out = t.tensor(
456 &col.out,
457 TensorRole::Output,
458 rows,
459 cols,
460 dt,
461 TensorInit::Zero,
462 );
463 let (rows_t, cols_t) = (
464 rows.div_ceil(target.tile.rows),
465 cols.div_ceil(target.tile.cols),
466 );
467 let steps = tile_grid_steps(rows_t, cols_t);
468 let ins: Vec<BufferId> = (0..chips)
469 .map(|c| t.buffer(&format!("partial_{c}"), 1, dt))
470 .collect();
471 let loads: Vec<StageId> = (0..chips)
472 .map(|c| {
473 t.load(
474 &format!("load_{}", col.partials[c]),
475 parts[c],
476 steps.clone(),
477 1,
478 ins[c],
479 )
480 })
481 .collect();
482 let summed = t.buffer("sum", 1, dt);
483 let w = target.tile.cols;
484 let combine = ComputeOp::Generic {
485 iters: space(target, w, None),
486 ins: vec![ident_map(w); chips],
487 out: ident_map(w),
488 body: (1..chips).fold(TensorBody::Operand(0), |acc, i| {
489 TensorBody::Add(Box::new(acc), Box::new(TensorBody::Operand(i)))
490 }),
491 };
492 let fold = t.stage(
493 &format!("allreduce_{}", col.out),
494 StageKind::Compute(combine),
495 ins.clone(),
496 vec![summed],
497 );
498 for (l, b) in loads.iter().zip(&ins) {
499 t.edge(*l, *b, fold);
500 }
501 let st = t.store(&format!("store_{}", col.out), out, steps, 1, summed);
502 t.edge(fold, summed, st);
503 let (acc, claim) = if floats {
504 (
505 AccPolicy::F32,
506 NumericsClaim::Approx {
507 reason: format!("all-reduce of {chips} partials in {dt:?}"),
508 },
509 )
510 } else {
511 (AccPolicy::I64, NumericsClaim::Exact)
512 };
513 let proto = run_passes_uncertified(
514 narrow(t.finish(
515 format!("allreduce_{}_{chips}_chips", col.out),
516 items_per_tile_grid(rows_t, cols_t),
517 acc,
518 claim,
519 target,
520 )?),
521 target,
522 )?;
523
524 let crossing = crossing_kind(target).map_err(TpReject::NoCrossing)?;
526 let mut q = proto;
527 let chip_of_tensor: BTreeMap<TensorId, u16> = parts
528 .iter()
529 .enumerate()
530 .map(|(c, t)| (*t, c as u16))
531 .collect();
532 let mut chip_of_buffer: BTreeMap<BufferId, u16> = BTreeMap::new();
533 for s in &mut q.stages {
534 match &s.kind {
535 StageKind::Load { tensor, .. } if chip_of_tensor.contains_key(tensor) => {
536 let c = chip_of_tensor[tensor];
537 if let Some(x) = &mut s.site {
538 on_chip(x, c);
539 }
540 chip_of_buffer.extend(s.outs.iter().map(|b| (*b, c)));
541 }
542 StageKind::Compute(op @ ComputeOp::Generic { .. }) => {
543 s.kind = StageKind::AllReduce {
544 pool: ReducePool::Sum,
545 tensor: out,
546 combine: Box::new(op.clone()),
547 };
548 }
549 _ => {}
550 }
551 }
552 for b in &mut q.buffers {
556 if let (Some(c), Some(x)) = (chip_of_buffer.get(&b.id), &mut b.site) {
557 on_chip(x, *c);
558 if let (
559 true,
560 BufferKind::Fifo {
561 depth_pages,
562 page_tiles,
563 },
564 ) = (*c != 0, b.kind)
565 {
566 b.kind = BufferKind::Interm {
567 depth_pages,
568 page_tiles,
569 };
570 }
571 }
572 }
573 for e in &mut q.edges {
574 if chip_of_buffer.get(&e.buffer).is_some_and(|c| *c != 0) {
575 e.handshake = crossing_handshake(crossing);
576 }
577 }
578 if let Some(pl) = &mut q.placement {
579 for (i, s) in q.stages.iter().enumerate() {
580 if let Some(x) = s.site {
581 pl.site_of[i] = x;
582 pl.engine_of[i].site = x;
583 }
584 }
585 let mut hosting: BTreeSet<(Site, Vec<WorkItemId>)> = BTreeSet::new();
586 for s in q
587 .stages
588 .iter()
589 .filter_map(|s| s.site)
590 .filter(|x| x.chip != 0)
591 {
592 let home = Site { chip: 0, ..s };
593 hosting.insert((s, pl.items_at(home).to_vec()));
594 }
595 pl.items_of_site.extend(hosting);
596 }
597 for t in &mut q.tensors {
598 t.chip = chip_of_tensor.get(&t.id).map(|c| ChipId(*c));
599 }
600 q.requires.handshakes.insert(crossing);
601 q.semaphores.clear();
602 q.memory = None;
603 q.schedule = None;
604 q.certificate = None;
605 Ok(match certification {
606 SectionCertification::Composed => run_passes_uncertified(q, target)?,
607 SectionCertification::EachSection => run_passes(q, target)?,
608 })
609}
610
611#[derive(Default)]
615struct FreePhases {
616 next: Vec<usize>,
618}
619
620impl FreePhases {
621 fn first_free(&mut self, x: usize) -> usize {
623 let mut root = x;
624 while root < self.next.len() && self.next[root] != root {
625 root = self.next[root];
626 }
627 let mut y = x;
628 while y < self.next.len() && self.next[y] != y {
629 let n = self.next[y];
630 self.next[y] = root;
631 y = n;
632 }
633 root
634 }
635
636 fn take(&mut self, x: usize) {
638 if self.next.len() <= x {
639 let from = self.next.len();
640 self.next.extend(from..=x);
641 }
642 self.next[x] = x + 1;
643 }
644}
645
646#[derive(Clone, Debug, Default, PartialEq, Eq)]
648pub struct SectionTouch {
649 pub reads: BTreeSet<String>,
650 pub writes: BTreeSet<String>,
651 pub chips: BTreeSet<u16>,
652}
653
654impl SectionTouch {
655 pub fn of(p: &SpatialProgram) -> Self {
656 let mut t = SectionTouch::default();
657 for s in &p.stages {
658 match &s.kind {
659 StageKind::Load { tensor, .. } => {
660 t.reads.insert(p.tensor(*tensor).name.clone());
661 }
662 StageKind::Store { tensor, .. } => {
663 t.writes.insert(p.tensor(*tensor).name.clone());
664 }
665 _ => {}
666 }
667 if let Some(site) = s.site {
668 t.chips.insert(site.chip);
669 }
670 }
671 t
672 }
673}
674
675pub fn phases(sections: &[SpatialProgram]) -> (Vec<usize>, Vec<bool>) {
680 phase_order(§ions.iter().map(SectionTouch::of).collect::<Vec<_>>())
681}
682
683pub fn phase_order(touches: &[SectionTouch]) -> (Vec<usize>, Vec<bool>) {
695 let (mut written_in, mut read_in): (BTreeMap<&str, usize>, BTreeMap<&str, usize>) =
696 (BTreeMap::new(), BTreeMap::new());
697 let mut phase_of: Vec<usize> = Vec::with_capacity(touches.len());
698 let mut free: BTreeMap<u16, FreePhases> = BTreeMap::new();
699 for t in touches {
700 let raw = t.reads.iter().filter_map(|n| written_in.get(n.as_str()));
701 let war_waw = t
702 .writes
703 .iter()
704 .flat_map(|n| [written_in.get(n.as_str()), read_in.get(n.as_str())])
705 .flatten();
706 let after = raw.chain(war_waw).map(|ph| ph + 1).max().unwrap_or(0);
707 let mut ph = after;
708 loop {
709 let next = t
710 .chips
711 .iter()
712 .map(|c| free.entry(*c).or_default().first_free(ph))
713 .max()
714 .unwrap_or(ph);
715 if next == ph {
716 break;
717 }
718 ph = next;
719 }
720 for c in &t.chips {
721 free.entry(*c).or_default().take(ph);
722 }
723 phase_of.push(ph);
724 for n in &t.reads {
725 let r = read_in.entry(n.as_str()).or_insert(ph);
726 *r = (*r).max(ph);
727 }
728 for n in &t.writes {
729 let w = written_in.entry(n.as_str()).or_insert(ph);
730 *w = (*w).max(ph);
731 }
732 }
733 let mut order: Vec<usize> = (0..touches.len()).collect();
734 order.sort_by_key(|&k| (phase_of[k], k));
735 let barrier_after = order
736 .iter()
737 .enumerate()
738 .map(|(pos, &k)| {
739 order
740 .get(pos + 1)
741 .is_some_and(|&next| phase_of[next] != phase_of[k])
742 })
743 .collect();
744 (order, barrier_after)
745}