1use super::args::{ArgSource, Axis, RuntimeArgLayout};
6use super::cb::{data_format, CbPlan};
7use super::roles::{rect_decompose, KernelClass};
8use super::{template_name, template_of, SpatialLowerError};
9use crate::codegen_sva::fabric_target::Precision;
10use crate::spatial::ir::*;
11use crate::spatial::target::{Fidelity, SpatialTarget};
12
13pub(super) fn ident(s: &str) -> String {
14 let mut out: String = s
15 .chars()
16 .map(|c| {
17 if c.is_ascii_alphanumeric() || c == '_' {
18 c
19 } else {
20 '_'
21 }
22 })
23 .collect();
24 if out
25 .chars()
26 .next()
27 .map(|c| c.is_ascii_digit())
28 .unwrap_or(true)
29 {
30 out.insert(0, 't');
31 }
32 out
33}
34
35fn ranges_cpp(rects: &[Rect]) -> String {
36 let items: Vec<String> = rects
37 .iter()
38 .map(|r| format!("CoreRange({{{}, {}}}, {{{}, {}}})", r.x0, r.y0, r.x1, r.y1))
39 .collect();
40 format!(
41 "CoreRangeSet(std::vector<CoreRange>{{{}}})",
42 items.join(", ")
43 )
44}
45
46fn fidelity_cpp(f: Fidelity) -> &'static str {
47 match f {
48 Fidelity::LoFi => "MathFidelity::LoFi",
49 Fidelity::HiFi2 => "MathFidelity::HiFi2",
50 Fidelity::HiFi3 => "MathFidelity::HiFi3",
51 Fidelity::HiFi4 | Fidelity::Exact => "MathFidelity::HiFi4",
52 }
53}
54
55fn fidelity_label(f: Fidelity) -> &'static str {
56 match f {
57 Fidelity::LoFi => "LoFi",
58 Fidelity::HiFi2 => "HiFi2",
59 Fidelity::HiFi3 => "HiFi3",
60 Fidelity::HiFi4 | Fidelity::Exact => "HiFi4",
61 }
62}
63
64fn unpack_to_dest_cpp(p: &SpatialProgram, cbs: &CbPlan, exact_f32: bool) -> String {
69 let mut marked: Vec<u32> = Vec::new();
70 if exact_f32 {
71 if let Some(sched) = &p.schedule {
72 for prog in &sched.programs {
73 walk_ops(
74 &prog.ops.iter().map(|t| t.op.clone()).collect::<Vec<_>>(),
75 1,
76 &mut |op, _| {
77 if let ChoreoOp::Compute {
78 acc_reload: Some(b),
79 ..
80 } = op
81 {
82 let slot = cbs.slot(*b);
83 if slot.dtype == Precision::Fp32 && !marked.contains(&slot.index) {
84 marked.push(slot.index);
85 }
86 }
87 },
88 );
89 }
90 }
91 }
92 if marked.is_empty() {
93 return "{}".into();
94 }
95 marked.sort();
96 let sets: Vec<String> = marked
97 .iter()
98 .map(|i| format!("utd[{i}] = UnpackToDestMode::UnpackToDestFp32;"))
99 .collect();
100 format!("[]{{ std::vector<UnpackToDestMode> utd(NUM_CIRCULAR_BUFFERS, UnpackToDestMode::Default); {} return utd; }}()", sets.join(" "))
101}
102
103pub fn placed_extent(pl: &Placement) -> (usize, usize) {
105 let sites = pl.sites();
106 (
107 sites.iter().map(|s| s.x as usize + 1).max().unwrap_or(0),
108 sites.iter().map(|s| s.y as usize + 1).max().unwrap_or(0),
109 )
110}
111
112pub fn cfg_line(p: &SpatialProgram) -> String {
114 let s = p.summary();
115 let b = s.block.unwrap_or(BlockShape {
116 osh: 1,
117 osw: 1,
118 ibw: 1,
119 });
120 let pl = p.placement.as_ref().expect("a placed program");
121 format!(
122 "LOGOS_MM_CFG strategy={} grid={}x{} per_core={}x{} out_block={}x{} in0_block_w={} subblock={}x{} origin=\"compiler-emitted, all kernels generated\" mesh={}x{} sites={}",
123 s.arrangement_label(),
124 s.grid.1,
125 s.grid.0,
126 s.per_site_block.0,
127 s.per_site_block.1,
128 s.per_site_block.0,
129 s.per_site_block.1,
130 b.ibw,
131 b.osh,
132 b.osw,
133 pl.mesh.x,
134 pl.mesh.y,
135 pl.sites().len()
136 )
137}
138
139fn receivers(
141 p: &SpatialProgram,
142 site: Site,
143 operand: Operand,
144) -> Result<(Rect, Site, usize), SpatialLowerError> {
145 let pl = p.placement.as_ref().unwrap();
146 let g = pl
147 .groups
148 .iter()
149 .find(|g| g.operand == operand && g.rect.contains(site))
150 .ok_or(SpatialLowerError::Unsupported(
151 "a site outside every group of its operand",
152 ))?;
153 let r = g.rect;
154 let rect = match g.direction {
155 Direction::Row => {
156 if r.y0 != r.y1
157 || g.sender
158 != (Site {
159 x: r.x0,
160 y: r.y0,
161 chip: 0,
162 })
163 {
164 return Err(SpatialLowerError::GroupSpansSites { group: g.id });
165 }
166 Rect {
167 x0: r.x0 + 1,
168 y0: r.y0,
169 x1: r.x1,
170 y1: r.y1,
171 }
172 }
173 Direction::Col => {
174 if r.x0 != r.x1
175 || g.sender
176 != (Site {
177 x: r.x0,
178 y: r.y0,
179 chip: 0,
180 })
181 {
182 return Err(SpatialLowerError::GroupSpansSites { group: g.id });
183 }
184 Rect {
185 x0: r.x0,
186 y0: r.y0 + 1,
187 x1: r.x1,
188 y1: r.y1,
189 }
190 }
191 };
192 if rect.x1 < rect.x0 || rect.y1 < rect.y0 {
193 return Err(SpatialLowerError::GroupSpansSites { group: g.id });
194 }
195 Ok((rect, g.sender, r.len() - 1))
196}
197
198pub fn mcast_corners(
203 p: &SpatialProgram,
204 site: Site,
205 operand: Operand,
206 noc: u8,
207) -> Result<((u16, u16), (u16, u16)), SpatialLowerError> {
208 let (r, _, _) = receivers(p, site, operand)?;
209 Ok(if noc == 1 {
210 ((r.x1, r.y1), (r.x0, r.y0))
211 } else {
212 ((r.x0, r.y0), (r.x1, r.y1))
213 })
214}
215
216fn arg_value(
217 p: &SpatialProgram,
218 site: Site,
219 source: &ArgSource,
220 noc: u8,
221) -> Result<String, SpatialLowerError> {
222 let pl = p.placement.as_ref().unwrap();
223 let items = pl.items_at(site);
224 Ok(match source {
225 ArgSource::TensorAddr(t) => {
226 format!("(uint32_t)buf_{}->address()", ident(&p.tensor(*t).name))
227 }
228 ArgSource::CyclesAddr => "(uint32_t)buf_cycles->address()".into(),
229 ArgSource::CyclesSlot(engine) => format!(
230 "{}u",
231 2 * pl
232 .items_of_site
233 .iter()
234 .position(|(x, _)| *x == site)
235 .unwrap_or(0)
236 + *engine as usize
237 ),
238 ArgSource::ItemStart => format!("{}u", items.first().map(|i| i.idx()).unwrap_or(0)),
239 ArgSource::ItemCount => format!("{}u", items.len()),
240 ArgSource::ItemY0 => format!(
241 "{}u",
242 items
243 .first()
244 .map(|i| p.work.items[i.idx()].coord.0)
245 .unwrap_or(0)
246 ),
247 ArgSource::ItemX0 => format!(
248 "{}u",
249 items
250 .first()
251 .map(|i| p.work.items[i.idx()].coord.1)
252 .unwrap_or(0)
253 ),
254 ArgSource::ItemCountX => {
255 let xs: std::collections::BTreeSet<usize> = items
256 .iter()
257 .map(|i| p.work.items[i.idx()].coord.1)
258 .collect();
259 format!("{}u", xs.len().max(1))
260 }
261 ArgSource::McastStart { operand, axis } => {
262 let ((x, y), _) = mcast_corners(p, site, *operand, noc)?;
263 match axis {
264 Axis::X => format!("(uint32_t)P({x}, {y}).x"),
265 Axis::Y => format!("(uint32_t)P({x}, {y}).y"),
266 }
267 }
268 ArgSource::McastEnd { operand, axis } => {
269 let (_, (x, y)) = mcast_corners(p, site, *operand, noc)?;
270 match axis {
271 Axis::X => format!("(uint32_t)P({x}, {y}).x"),
272 Axis::Y => format!("(uint32_t)P({x}, {y}).y"),
273 }
274 }
275 ArgSource::McastNumDests { operand } => format!("{}u", receivers(p, site, *operand)?.2),
276 ArgSource::McastSender { operand, axis } => {
277 let (_, s, _) = receivers(p, site, *operand)?;
278 match axis {
279 Axis::X => format!("(uint32_t)P({}, {}).x", s.x, s.y),
280 Axis::Y => format!("(uint32_t)P({}, {}).y", s.x, s.y),
281 }
282 }
283 ArgSource::SemHandle(s) => format!("(uint32_t)sem_{}", s.idx()),
284 ArgSource::TensorDim { tensor, axis } => {
285 let tiles = p.tensor(*tensor).tiles;
286 format!(
287 "{}u",
288 match axis {
289 Axis::Y => tiles.rows_t,
290 Axis::X => tiles.cols_t,
291 }
292 )
293 }
294 })
295}
296
297fn host_elem_type(p: Precision) -> Result<&'static str, SpatialLowerError> {
305 Ok(match p {
306 Precision::Bf16 => "bfloat16",
307 Precision::Fp32 => "float",
308 other => return Err(SpatialLowerError::Precision { needed: other }),
309 })
310}
311
312fn cpp_float(v: f64, suffix: &str) -> String {
315 if v.is_nan() {
316 "NAN".into()
317 } else if v.is_infinite() {
318 if v < 0.0 {
319 "-INFINITY".into()
320 } else {
321 "INFINITY".into()
322 }
323 } else {
324 format!("{v:?}{suffix}")
325 }
326}
327
328fn fill_lines(
329 p: &SpatialProgram,
330 t: &TensorDecl,
331 var: &str,
332 padded_cols: usize,
333) -> Result<Vec<String>, SpatialLowerError> {
334 let (rows, cols) = (t.elems.rows, t.elems.cols);
335 let ety = host_elem_type(t.dtype)?;
336 let mut out = Vec::new();
337 let assign = |expr: &str| {
338 format!("for (uint32_t r = 0; r < {rows}u; ++r) for (uint32_t c = 0; c < {cols}u; ++c) {{ const uint32_t p = r * {cols}u + c; {var}[r * {padded_cols}u + c] = {ety}((float)({expr})); }}")
339 };
340 match &t.init {
341 TensorInit::Zero => {}
342 TensorInit::Pattern { modulus, shift } => {
343 out.push(assign(&format!("(int64_t)(p % {modulus}u) + ({shift})")))
344 }
345 TensorInit::ConstF(b) => out.push(assign(&cpp_float(f64::from_bits(*b), "f"))),
347 TensorInit::Values(v) => {
348 if v.len() > 65536 {
349 return Err(SpatialLowerError::TooBig {
350 what: "literal tensor values",
351 need: v.len(),
352 have: 65536,
353 });
354 }
355 let lits: Vec<String> = v.iter().map(|x| x.to_string()).collect();
356 out.push(format!(
357 "static const int64_t {var}_values[{}] = {{{}}};",
358 v.len().max(1),
359 lits.join(", ")
360 ));
361 out.push(assign(&format!("{var}_values[p]")));
362 }
363 TensorInit::FloatValues(v) => {
364 if v.len() > 65536 {
365 return Err(SpatialLowerError::TooBig {
366 what: "literal tensor values",
367 need: v.len(),
368 have: 65536,
369 });
370 }
371 let lits: Vec<String> = v
372 .iter()
373 .map(|b| cpp_float(f64::from_bits(*b), ""))
374 .collect();
375 out.push(format!(
376 "static const double {var}_values[{}] = {{{}}};",
377 v.len().max(1),
378 lits.join(", ")
379 ));
380 out.push(assign(&format!("{var}_values[p]")));
381 }
382 TensorInit::RandomBf16 { seed, shift_milli } => {
383 let copy = if ety == "bfloat16" {
384 "rnd[r * {cols}u + c]".to_string()
385 } else {
386 format!("{ety}(static_cast<float>(rnd[r * {{cols}}u + c]))")
387 };
388 let copy = copy.replace("{cols}", &cols.to_string());
389 out.push(format!("{{ auto rnd = create_random_vector_of_bfloat16_native(2u * {rows}u * {cols}u, 1, {seed}, {}f); for (uint32_t r = 0; r < {rows}u; ++r) for (uint32_t c = 0; c < {cols}u; ++c) {var}[r * {padded_cols}u + c] = {copy}; }}", *shift_milli as f64 / 1000.0));
390 }
391 TensorInit::External => {
392 return Err(SpatialLowerError::Unsupported(
393 "a tensor whose data is external to the program",
394 ))
395 }
396 }
397 let _ = p;
398 Ok(out)
399}
400
401fn golden_lines(
411 p: &SpatialProgram,
412 ids: &[(String, usize, usize)],
413 tile: usize,
414) -> Result<Vec<String>, SpatialLowerError> {
415 let sched = p.schedule.as_ref().ok_or(SpatialLowerError::Unsupported(
416 "a golden for an unscheduled program",
417 ))?;
418 let mut from_tensor: std::collections::BTreeMap<BufferId, TensorId> = Default::default();
422 let mut steps: Vec<(ComputeOp, Vec<BufferId>, BufferId)> = Vec::new();
423 let mut pending: Option<(ComputeOp, Vec<BufferId>)> = None;
424 for prog in &sched.programs {
425 let ops: Vec<ChoreoOp> = prog.ops.iter().map(|t| t.op.clone()).collect();
426 walk_ops(&ops, 1, &mut |op, _| match op {
427 ChoreoOp::ReadTiles { tensor, into, .. } => {
428 from_tensor.entry(*into).or_insert(*tensor);
429 }
430 ChoreoOp::Compute { op, ins, .. } => pending = Some((op.clone(), ins.clone())),
431 ChoreoOp::PackAcc { to, .. } => {
432 if let Some((op, ins)) = pending.take() {
433 steps.push((op, ins, *to));
434 }
435 }
436 _ => {}
437 });
438 }
439 if steps.is_empty() {
440 return Err(SpatialLowerError::Unsupported(
441 "a golden for a program with no compute stage",
442 ));
443 }
444 let mut seen: std::collections::BTreeSet<(Vec<BufferId>, BufferId)> = Default::default();
446 steps.retain(|(_, ins, dst)| {
447 let key = (
448 ins.iter()
449 .map(|b| template_of(p.buffer(*b)))
450 .collect::<Vec<_>>(),
451 template_of(p.buffer(*dst)),
452 );
453 seen.insert(key)
454 });
455 let mut plane: std::collections::BTreeMap<BufferId, usize> = Default::default();
463 for (b, t) in &from_tensor {
464 plane.insert(template_of(p.buffer(*b)), p.tensor(*t).tiles.cols_t * tile);
465 }
466 for (op, ins, dst) in &steps {
467 let w = match op {
468 ComputeOp::Generic { iters, .. }
469 if iters
470 .get(1)
471 .map(|a| matches!(a.kind, IterKind::Reduce { .. }))
472 .unwrap_or(false) =>
473 {
474 tile
475 }
476 _ => ins
477 .first()
478 .map(|b| {
479 plane
480 .get(&template_of(p.buffer(*b)))
481 .copied()
482 .unwrap_or(tile)
483 })
484 .unwrap_or(tile),
485 };
486 plane.insert(template_of(p.buffer(*dst)), w);
487 }
488 let width = |b: BufferId| {
489 plane
490 .get(&template_of(p.buffer(b)))
491 .copied()
492 .unwrap_or_else(|| p.buffer(b).kind.page_tiles() * tile)
493 };
494 let gvar = |b: BufferId| format!("g_{}", ident(&template_name(p, p.buffer(b))));
497 let host_of = |b: BufferId| -> Option<(String, usize, usize)> {
498 let t = from_tensor.get(&b)?;
499 let name = ident(&p.tensor(*t).name);
500 ids.iter()
501 .find(|(id, _, _)| *id == name)
502 .map(|(id, rp, cp)| (format!("{id}_host"), *rp, *cp))
503 };
504 let mut o = Vec::new();
505 let mut declared: std::collections::BTreeSet<String> = Default::default();
506 let declare = |o: &mut Vec<String>,
507 declared: &mut std::collections::BTreeSet<String>,
508 b: BufferId| {
509 let (v, w) = (gvar(b), width(b));
510 if !declared.insert(v.clone()) {
511 return;
512 }
513 o.push(format!("std::vector<double> {v}(Mp * {w}u, 0.0);"));
514 if let Some((host, rp, cp)) = host_of(b) {
515 o.push(format!(
519 "for (uint32_t r = 0; r < Mp && r < {rp}u; ++r) for (uint32_t c = 0; c < {w}u && c < {cp}u; ++c) {v}[r * {w}u + c] = static_cast<double>({host}[r * {cp}u + c]);"
520 ));
521 }
522 };
523 for (op, ins, dst) in &steps {
524 for b in ins {
525 declare(&mut o, &mut declared, *b);
526 }
527 declare(&mut o, &mut declared, *dst);
528 let (d, wd) = (gvar(*dst), width(*dst));
529 match op {
530 ComputeOp::Generic {
531 iters,
532 ins: gmaps,
533 body,
534 ..
535 } => {
536 if iters.len() != 2 {
537 return Err(SpatialLowerError::Unsupported("a golden for a Generic with a contraction axis (LV_GEN_K, a third folded iteration variable)"));
538 }
539 let (row_ax, col_ax) = (iters[0], iters[1]);
540 if matches!(row_ax.kind, IterKind::Reduce { .. }) {
544 return Err(SpatialLowerError::Unsupported(
545 "a golden for a Generic reduction over the row axis on Tensix",
546 ));
547 }
548 for (i, m) in gmaps.iter().enumerate() {
549 let width = ins
550 .get(i)
551 .map(|b| p.buffer(*b).kind.page_tiles() * tile)
552 .unwrap_or(tile);
553 if m.broadcast_class(row_ax.var, col_ax.var, width).is_none() {
554 return Err(SpatialLowerError::Unsupported("a golden for a Generic access map with an offset, a stride or an indirect term on Tensix"));
555 }
556 }
557 let coord = |i: usize, ax: IterSpec, v: &str| -> String {
560 match gmaps.get(i).map(|m| m.varies_along(ax.var)).unwrap_or(true) {
561 true => v.to_string(),
562 false => "0".to_string(),
563 }
564 };
565 let expr = |b: &TensorBody| -> Result<String, SpatialLowerError> {
566 fn go(
567 b: &TensorBody,
568 ins: &[BufferId],
569 gv: &dyn Fn(BufferId) -> String,
570 wd: &dyn Fn(BufferId) -> usize,
571 rc: &dyn Fn(usize) -> (String, String),
572 ) -> Result<String, SpatialLowerError> {
573 Ok(match b {
574 TensorBody::Operand(i) => {
575 let buf = *ins.get(*i).ok_or(SpatialLowerError::Unsupported(
576 "a Generic body naming an absent operand",
577 ))?;
578 let (r, c) = rc(*i);
579 format!("{}[({r}) * {}u + ({c})]", gv(buf), wd(buf))
580 }
581 TensorBody::Const(bits) => {
582 format!("({})", cpp_float(f64::from_bits(*bits), ""))
583 }
584 TensorBody::Min(..)
585 | TensorBody::Max(..)
586 | TensorBody::FloorDiv(..)
587 | TensorBody::Shr { .. } => {
588 return Err(SpatialLowerError::Unsupported(
589 "an integer model's min, max, floor division or arithmetic shift on the Tensix host — it prints none of them",
590 ))
591 }
592 TensorBody::Add(x, y) => format!(
593 "({} + {})",
594 go(x, ins, gv, wd, rc)?,
595 go(y, ins, gv, wd, rc)?
596 ),
597 TensorBody::Sub(x, y) => format!(
598 "({} - {})",
599 go(x, ins, gv, wd, rc)?,
600 go(y, ins, gv, wd, rc)?
601 ),
602 TensorBody::Mul(x, y) => format!(
603 "({} * {})",
604 go(x, ins, gv, wd, rc)?,
605 go(y, ins, gv, wd, rc)?
606 ),
607 TensorBody::Unary { kind, arg } => {
608 let a = go(arg, ins, gv, wd, rc)?;
609 match kind {
610 SfpuKind::Exp => format!("std::exp({a})"),
611 SfpuKind::Recip => format!("(1.0 / ({a}))"),
612 SfpuKind::Rsqrt { eps_nano, over } => format!(
613 "(1.0 / std::sqrt(({a}) / {}.0 + {:e}))",
614 (*over).max(1),
615 *eps_nano as f64 * 1e-9
616 ),
617 SfpuKind::Silu => format!("(({a}) / (1.0 + std::exp(-({a}))))"),
618 SfpuKind::Relu => format!("std::fmax(0.0, {a})"),
619 SfpuKind::Ln => {
620 return Err(SpatialLowerError::Unsupported(
621 "a golden for a natural-log transform on Tensix",
622 ))
623 }
624 }
625 }
626 TensorBody::Select { .. } => {
627 return Err(SpatialLowerError::Unsupported(
628 "a golden for a piecewise Select body on Tensix",
629 ))
630 }
631 })
632 }
633 let rc = |i: usize| (coord(i, row_ax, "r"), coord(i, col_ax, "c"));
634 go(b, ins, &|b| gvar(b), &|b| width(b), &rc)
635 };
636 let e = expr(body)?;
637 if let IterKind::Reduce { pool } = col_ax.kind {
638 let (init, step) = match pool {
639 ReducePool::Sum => ("0.0", "acc += v;"),
640 ReducePool::Max => ("-INFINITY", "acc = v > acc ? v : acc;"),
641 ReducePool::ArgMax => {
642 return Err(SpatialLowerError::Unsupported(
643 "a golden for an index-valued ArgMax reduction on Tensix",
644 ))
645 }
646 };
647 o.push("for (uint32_t r = 0; r < Mp; ++r) {".into());
648 o.push(format!(" double acc = {init};"));
649 o.push(format!(
650 " for (uint32_t c = 0; c < {}u; ++c) {{ const double v = {e}; {step} }}",
651 col_ax.extent
652 ));
653 o.push(format!(
654 " for (uint32_t c = 0; c < {wd}u; ++c) {d}[r * {wd}u + c] = acc;"
655 ));
656 o.push("}".into());
657 } else {
658 o.push(format!(
659 "for (uint32_t r = 0; r < Mp; ++r) for (uint32_t c = 0; c < {wd}u; ++c)"
660 ));
661 o.push(format!(" {d}[r * {wd}u + c] = {e};"));
662 }
663 }
664 ComputeOp::MatmulTiles { .. } => {
667 return Err(SpatialLowerError::Unsupported(
668 "a matmul whose shapes are not a GEMM",
669 ))
670 }
671 ComputeOp::Requant { .. } => {
672 return Err(SpatialLowerError::Unsupported(
673 "a golden for a requant epilogue on Tensix (P4b)",
674 ))
675 }
676 ComputeOp::Bias { .. } => {
677 return Err(SpatialLowerError::Unsupported(
678 "a golden for a bias epilogue on Tensix (P4)",
679 ))
680 }
681 ComputeOp::Relu => {
682 return Err(SpatialLowerError::Unsupported(
683 "a golden for relu on Tensix (P4)",
684 ))
685 }
686 ComputeOp::Scalar(_) => {
687 return Err(SpatialLowerError::Unsupported(
688 "a golden for a scalar fabric body on Tensix (P4)",
689 ))
690 }
691 }
692 }
693 let (last, wl) = {
694 let (_, _, d) = steps.last().unwrap();
695 (gvar(*d), width(*d))
696 };
697 o.push(format!("for (uint32_t r = 0; r < M; ++r) for (uint32_t c = 0; c < N; ++c) golden[r * Np + c] = static_cast<float>({last}[r * {wl}u + c]);"));
698 Ok(o)
699}
700
701pub fn print_host(
702 p: &SpatialProgram,
703 target: &SpatialTarget,
704 cbs: &CbPlan,
705 classes: &[KernelClass],
706 layout: &RuntimeArgLayout,
707 exact_f32: bool,
708 fidelity: Fidelity,
709 cfg_line: &str,
710) -> Result<String, SpatialLowerError> {
711 let pl = p.placement.as_ref().unwrap();
712 let n_sites = pl.items_of_site.len();
713 let inputs: Vec<&TensorDecl> = p
714 .tensors
715 .iter()
716 .filter(|t| t.role != TensorRole::Output)
717 .collect();
718 let out = p
719 .output_tensor()
720 .ok_or(SpatialLowerError::Unsupported("an output tensor"))?;
721 if inputs.is_empty() {
722 return Err(SpatialLowerError::Unsupported(
723 "a host for a program with at least one input tensor",
724 ));
725 }
726 let tile = target.tile.rows;
727 let a = inputs[0];
732 let gemm = inputs.len() == 2
733 && inputs[1].elems.rows == a.elems.cols
734 && out.elems.rows == a.elems.rows
735 && out.elems.cols == inputs[1].elems.cols;
736 let (m, k, n) = if gemm {
737 (a.elems.rows, a.elems.cols, inputs[1].elems.cols)
738 } else {
739 (out.elems.rows, a.elems.cols, out.elems.cols)
742 };
743 let (mp, kp, np) = if gemm {
744 (
745 a.tiles.rows_t * tile,
746 a.tiles.cols_t * tile,
747 inputs[1].tiles.cols_t * tile,
748 )
749 } else {
750 (
751 out.tiles.rows_t * tile,
752 a.tiles.cols_t * tile,
753 out.tiles.cols_t * tile,
754 )
755 };
756 let out_f32 = out.dtype == Precision::Fp32;
757 let floor = if p.claim == NumericsClaim::Exact {
758 "0.9999"
759 } else {
760 "0.99"
761 };
762 let all_sites: std::collections::BTreeSet<Site> = p.sites();
763 let all_rects = rect_decompose(&all_sites);
764 let mut h = String::new();
765 let w = |h: &mut String, s: &str| {
766 h.push_str(s);
767 h.push('\n');
768 };
769 w(&mut h, &format!("// host.cpp — printed from the spatial IR ({}). Every semaphore, circular buffer and kernel below is the", p.name));
770 w(
771 &mut h,
772 "// image of one IR object; the plan it runs is the emergent one the passes chose.",
773 );
774 for inc in [
775 "<tt-metalium/host_api.hpp>",
776 "<tt-metalium/constants.hpp>",
777 "<tt-metalium/bfloat16.hpp>",
778 "<tt-metalium/tilize_utils.hpp>",
779 "<tt-metalium/device.hpp>",
780 "<tt-metalium/tt_metal.hpp>",
781 "<tt-metalium/tensor_accessor_args.hpp>",
782 "<tt-metalium/distributed.hpp>",
783 "<fmt/core.h>",
784 "<cmath>",
785 "<cstdint>",
786 "<cstdlib>",
787 "<string>",
788 "<vector>",
789 ] {
790 w(&mut h, &format!("#include {inc}"));
791 }
792 w(&mut h, "using namespace tt;");
793 w(&mut h, "using namespace tt::tt_metal;");
794 w(&mut h, "");
795 w(
796 &mut h,
797 "static float logos_pcc(const std::vector<float>& x, const std::vector<float>& y) {",
798 );
799 w(&mut h, " double mx = 0, my = 0;");
800 w(
801 &mut h,
802 " for (size_t i = 0; i < x.size(); ++i) { mx += x[i]; my += y[i]; }",
803 );
804 w(
805 &mut h,
806 " mx /= (double)x.size(); my /= (double)y.size();",
807 );
808 w(&mut h, " double sxy = 0, sxx = 0, syy = 0;");
809 w(&mut h, " for (size_t i = 0; i < x.size(); ++i) { const double dx = x[i] - mx, dy = y[i] - my; sxy += dx * dy; sxx += dx * dx; syy += dy * dy; }");
810 w(
811 &mut h,
812 " if (sxx == 0 || syy == 0) return (sxx == 0 && syy == 0 && x == y) ? 1.0f : 0.0f;",
813 );
814 w(
815 &mut h,
816 " return static_cast<float>(sxy / std::sqrt(sxx * syy));",
817 );
818 w(&mut h, "}");
819 w(&mut h, "");
820 w(&mut h, "int main() {");
821 if gemm {
824 w(
825 &mut h,
826 &format!(" constexpr uint32_t M = {m}, N = {n}, K = {k};"),
827 );
828 w(
829 &mut h,
830 &format!(" constexpr uint32_t Mp = {mp}, Np = {np}, Kp = {kp};"),
831 );
832 } else {
833 w(&mut h, &format!(" constexpr uint32_t M = {m}, N = {n};"));
834 w(
835 &mut h,
836 &format!(" constexpr uint32_t Mp = {mp}, Np = {np};"),
837 );
838 }
839 w(
840 &mut h,
841 " auto mesh_device = distributed::MeshDevice::create_unit_mesh(0);",
842 );
843 let (px, py) = placed_extent(pl);
844 w(
845 &mut h,
846 " const CoreCoord device_grid = mesh_device->compute_with_storage_grid_size();",
847 );
848 w(
849 &mut h,
850 &format!(" constexpr uint32_t PLACED_X = {px}, PLACED_Y = {py};"),
851 );
852 w(
853 &mut h,
854 " if (device_grid.x < PLACED_X || device_grid.y < PLACED_Y) {",
855 );
856 w(&mut h, " fmt::print(\"LOGOS_GRID_MISMATCH device={}x{} placed={}x{}\\n\", device_grid.x, device_grid.y, PLACED_X, PLACED_Y);");
857 w(&mut h, " mesh_device->close();");
858 w(&mut h, " return 2;");
859 w(&mut h, " }");
860 w(&mut h, " fmt::print(\"LOGOS_GRID device={}x{} placed={}x{}\\n\", device_grid.x, device_grid.y, PLACED_X, PLACED_Y);");
861 w(
862 &mut h,
863 " distributed::MeshCommandQueue& cq = mesh_device->mesh_command_queue();",
864 );
865 w(
866 &mut h,
867 " auto device_range = distributed::MeshCoordinateRange(mesh_device->shape());",
868 );
869 w(&mut h, " Program program{};");
870 w(
871 &mut h,
872 &format!(" fmt::print(\"{}\\n\");", cfg_line.replace('"', "\\\"")),
873 );
874 w(&mut h, "");
875 for t in &p.tensors {
877 let id = ident(&t.name);
878 let tb = target.tile_bytes(t.dtype);
879 w(&mut h, &format!(" distributed::DeviceLocalBufferConfig dram_{id}{{.page_size = {tb}, .buffer_type = BufferType::DRAM}};"));
880 w(
881 &mut h,
882 &format!(
883 " distributed::ReplicatedBufferConfig cfg_{id}{{.size = {}}};",
884 tb * t.tiles.count()
885 ),
886 );
887 w(&mut h, &format!(" auto buf_{id} = distributed::MeshBuffer::create(cfg_{id}, dram_{id}, mesh_device.get());"));
888 }
889 w(&mut h, &format!(" distributed::DeviceLocalBufferConfig dram_cycles{{.page_size = 32, .buffer_type = BufferType::L1}};"));
894 w(
895 &mut h,
896 &format!(
897 " distributed::ReplicatedBufferConfig cfg_cycles{{.size = {}}};",
898 64 * n_sites.max(1)
899 ),
900 );
901 w(&mut h, " auto buf_cycles = distributed::MeshBuffer::create(cfg_cycles, dram_cycles, mesh_device.get());");
902 w(&mut h, "");
903 for s in cbs.slots.iter().filter(|s| s.alias_of.is_none()) {
906 let sites: std::collections::BTreeSet<Site> = s.sites.iter().copied().collect();
907 let rects = if sites.is_empty() {
908 all_rects.clone()
909 } else {
910 rect_decompose(&sites)
911 };
912 let indices: Vec<&super::cb::CbSlot> = std::iter::once(s)
913 .chain(cbs.slots.iter().filter(|a| a.alias_of == Some(s.template)))
914 .collect();
915 let total = indices.iter().map(|x| x.total_bytes()).max().unwrap_or(0);
916 let formats = indices
917 .iter()
918 .map(|x| Ok(format!("{{{}, {}}}", x.index, data_format(x.dtype)?)))
919 .collect::<Result<Vec<_>, SpatialLowerError>>()?
920 .join(", ");
921 let pages: String = indices
922 .iter()
923 .map(|x| {
924 format!(
925 " cb_{}.set_page_size({}, {});",
926 s.name, x.index, x.tile_bytes
927 )
928 })
929 .collect();
930 w(&mut h, &format!(" CircularBufferConfig cb_{0}({1}, {{{2}}});{3} CreateCircularBuffer(program, {4}, cb_{0});", s.name, total, formats, pages, ranges_cpp(&rects)));
931 }
932 w(&mut h, "");
933 for s in &p.semaphores {
935 let sites: std::collections::BTreeSet<Site> = s.sites.iter().copied().collect();
936 let rects = if sites.is_empty() {
937 all_rects.clone()
938 } else {
939 rect_decompose(&sites)
940 };
941 w(
942 &mut h,
943 &format!(
944 " auto sem_{} = CreateSemaphore(program, {}, {});",
945 s.id.idx(),
946 ranges_cpp(&rects),
947 s.init
948 ),
949 );
950 }
951 w(&mut h, "");
952 for (i, c) in classes.iter().enumerate() {
954 match c.engine {
955 Engine::Movement(e) => {
956 let mut tensors: Vec<TensorId> = Vec::new();
958 let rep = &p.schedule.as_ref().unwrap().programs[c.representative];
959 let ops: Vec<ChoreoOp> = rep.ops.iter().map(|t| t.op.clone()).collect();
960 walk_ops(&ops, 1, &mut |op, _| {
961 if let ChoreoOp::ReadTiles { tensor, .. }
962 | ChoreoOp::WriteTiles { tensor, .. } = op
963 {
964 if !tensors.contains(tensor) {
965 tensors.push(*tensor);
966 }
967 }
968 });
969 tensors.sort();
970 let mut line = format!(" std::vector<uint32_t> ca_{i};");
971 for t in &tensors {
972 line.push_str(&format!(
973 " TensorAccessorArgs(*buf_{}).append_to(ca_{i});",
974 ident(&p.tensor(*t).name)
975 ));
976 }
977 if super::args::stamps_cycles(&ops) {
980 line.push_str(&format!(
981 " TensorAccessorArgs(*buf_cycles).append_to(ca_{i});"
982 ));
983 }
984 w(&mut h, &line);
985 for (j, l) in c.launches.iter().enumerate() {
986 let noc = l.noc.map(|n| n.0).unwrap_or(0);
987 w(&mut h, &format!(
988 " auto k_{i}_{j} = CreateKernel(program, LOGOS_KERNEL_DIR \"{}\", {}, DataMovementConfig{{.processor = DataMovementProcessor::RISCV_{e}, .noc = NOC::RISCV_{noc}_default, .compile_args = ca_{i}}});",
989 c.file_name,
990 ranges_cpp(&l.rects)
991 ));
992 }
993 }
994 Engine::Compute => {
995 for (j, l) in c.launches.iter().enumerate() {
996 w(&mut h, &format!(
997 " auto k_{i}_{j} = CreateKernel(program, LOGOS_KERNEL_DIR \"{}\", {}, ComputeConfig{{.math_fidelity = {}, .fp32_dest_acc_en = {}, .unpack_to_dest_mode = {}, .compile_args = {{}}}});",
998 c.file_name,
999 ranges_cpp(&l.rects),
1000 fidelity_cpp(fidelity),
1001 if exact_f32 { "true" } else { "false" },
1002 unpack_to_dest_cpp(p, cbs, exact_f32)
1003 ));
1004 }
1005 }
1006 }
1007 }
1008 w(&mut h, "");
1009 w(&mut h, " auto P = [&](uint32_t x, uint32_t y) { return mesh_device->worker_core_from_logical_core(CoreCoord{x, y}); };");
1010 w(&mut h, " (void)P;");
1011 for (i, c) in classes.iter().enumerate() {
1013 let slots = &layout
1014 .per_kernel
1015 .iter()
1016 .find(|(n, _)| *n == c.file_name)
1017 .map(|(_, s)| s.clone())
1018 .unwrap_or_default();
1019 for (j, l) in c.launches.iter().enumerate() {
1020 let noc = l.noc.map(|n| n.0).unwrap_or(0);
1021 for site in &l.sites {
1022 let vals: Result<Vec<String>, _> = slots
1023 .iter()
1024 .map(|s| arg_value(p, *site, &s.source, noc))
1025 .collect();
1026 w(&mut h, &format!(" SetRuntimeArgs(program, k_{i}_{j}, CoreCoord{{{}, {}}}, std::vector<uint32_t>{{{}}});", site.x, site.y, vals?.join(", ")));
1027 }
1028 }
1029 }
1030 let _ = pl;
1031 w(&mut h, "");
1032 let idc = ident(&out.name);
1034 if gemm {
1035 let (ida, idb) = (ident(&a.name), ident(&inputs[1].name));
1036 w(
1037 &mut h,
1038 &format!(" std::vector<bfloat16> {ida}_host(Mp * Kp, bfloat16(0.0f));"),
1039 );
1040 w(
1041 &mut h,
1042 &format!(" std::vector<bfloat16> {idb}_host(Kp * Np, bfloat16(0.0f));"),
1043 );
1044 for l in fill_lines(p, a, &format!("{ida}_host"), kp)? {
1045 w(&mut h, &format!(" {l}"));
1046 }
1047 for l in fill_lines(p, inputs[1], &format!("{idb}_host"), np)? {
1048 w(&mut h, &format!(" {l}"));
1049 }
1050 w(&mut h, " std::vector<float> golden(Mp * Np, 0.0f);");
1051 w(
1052 &mut h,
1053 " for (uint32_t i = 0; i < M; ++i) for (uint32_t kk = 0; kk < K; ++kk) {",
1054 );
1055 w(
1056 &mut h,
1057 &format!(" const float av = static_cast<float>({ida}_host[i * Kp + kk]);"),
1058 );
1059 w(&mut h, &format!(" for (uint32_t j = 0; j < N; ++j) golden[i * Np + j] += av * static_cast<float>({idb}_host[kk * Np + j]);"));
1060 w(&mut h, " }");
1061 w(
1062 &mut h,
1063 &format!(" auto {ida}_tiled = tilize_nfaces({ida}_host, Mp, Kp);"),
1064 );
1065 w(
1066 &mut h,
1067 &format!(" auto {idb}_tiled = tilize_nfaces({idb}_host, Kp, Np);"),
1068 );
1069 w(
1070 &mut h,
1071 &format!(" distributed::EnqueueWriteMeshBuffer(cq, buf_{ida}, {ida}_tiled, false);"),
1072 );
1073 w(
1074 &mut h,
1075 &format!(" distributed::EnqueueWriteMeshBuffer(cq, buf_{idb}, {idb}_tiled, false);"),
1076 );
1077 } else {
1078 let mut ids: Vec<(String, usize, usize)> = Vec::new();
1082 for t in &inputs {
1083 let id = ident(&t.name);
1084 let ety = host_elem_type(t.dtype)?;
1085 let (rp, cp) = (t.tiles.rows_t * tile, t.tiles.cols_t * tile);
1086 w(
1087 &mut h,
1088 &format!(" std::vector<{ety}> {id}_host({rp}u * {cp}u, {ety}(0.0f));"),
1089 );
1090 for l in fill_lines(p, t, &format!("{id}_host"), cp)? {
1091 w(&mut h, &format!(" {l}"));
1092 }
1093 ids.push((id, rp, cp));
1094 }
1095 w(&mut h, " std::vector<float> golden(Mp * Np, 0.0f);");
1096 for l in golden_lines(p, &ids, tile)? {
1097 w(&mut h, &format!(" {l}"));
1098 }
1099 for (id, rp, cp) in &ids {
1100 w(
1101 &mut h,
1102 &format!(" auto {id}_tiled = tilize_nfaces({id}_host, {rp}u, {cp}u);"),
1103 );
1104 w(
1105 &mut h,
1106 &format!(
1107 " distributed::EnqueueWriteMeshBuffer(cq, buf_{id}, {id}_tiled, false);"
1108 ),
1109 );
1110 }
1111 }
1112 w(&mut h, " distributed::MeshWorkload workload; workload.add_program(device_range, std::move(program));");
1113 w(
1114 &mut h,
1115 " distributed::EnqueueMeshWorkload(cq, workload, false);",
1116 );
1117 if out_f32 {
1118 w(
1119 &mut h,
1120 &format!(" std::vector<float> {idc}_tiled(Mp * Np);"),
1121 );
1122 w(
1123 &mut h,
1124 &format!(" distributed::EnqueueReadMeshBuffer(cq, {idc}_tiled, buf_{idc}, true);"),
1125 );
1126 w(
1127 &mut h,
1128 &format!(" auto {idc}_host = untilize_nfaces({idc}_tiled, Mp, Np);"),
1129 );
1130 w(&mut h, &format!(" std::vector<float> got(M * N); for (uint32_t i = 0; i < M; ++i) for (uint32_t j = 0; j < N; ++j) got[i * N + j] = {idc}_host[i * Np + j];"));
1131 } else {
1132 w(
1133 &mut h,
1134 &format!(" std::vector<bfloat16> {idc}_tiled(Mp * Np, bfloat16(0.0f));"),
1135 );
1136 w(
1137 &mut h,
1138 &format!(" distributed::EnqueueReadMeshBuffer(cq, {idc}_tiled, buf_{idc}, true);"),
1139 );
1140 w(
1141 &mut h,
1142 &format!(" auto {idc}_host = untilize_nfaces({idc}_tiled, Mp, Np);"),
1143 );
1144 w(&mut h, &format!(" std::vector<float> got(M * N); for (uint32_t i = 0; i < M; ++i) for (uint32_t j = 0; j < N; ++j) got[i * N + j] = static_cast<float>({idc}_host[i * Np + j]);"));
1145 }
1146 w(&mut h, " std::vector<float> want(M * N); for (uint32_t i = 0; i < M; ++i) for (uint32_t j = 0; j < N; ++j) want[i * N + j] = golden[i * Np + j];");
1147 w(
1148 &mut h,
1149 " long long checksum = 0; for (float v : got) checksum += (long long)std::llround(v);",
1150 );
1151 w(&mut h, " long long want_checksum = 0; for (float v : want) want_checksum += (long long)std::llround(v);");
1152 w(&mut h, " const float pcc = logos_pcc(want, got);");
1153 w(
1154 &mut h,
1155 " fmt::print(\"LOGOS_CHECKSUM {} golden {}\\n\", checksum, want_checksum);",
1156 );
1157 w(
1158 &mut h,
1159 &format!(
1160 " fmt::print(\"LOGOS spatial-IR matmul ({}) -- PCC = {{}}\\n\", pcc);",
1161 target.name
1162 ),
1163 );
1164 let exact_claim = p.claim == NumericsClaim::Exact;
1165 let ok = if exact_claim {
1166 format!("(pcc >= {floor}f && checksum == want_checksum)")
1167 } else {
1168 format!("(pcc >= {floor}f)")
1169 };
1170 w(&mut h, &format!(" const bool ok = {ok};"));
1171 w(&mut h, &format!(" if (ok) fmt::print(\"LOGOS_MM_PASS fidelity={} fp32={} l1acc={} pcc={{}} floor={floor}\\n\", pcc);", fidelity_label(fidelity), if exact_f32 { 1 } else { 0 }, p.work.plan.map(|pl| pl.pack_accumulate).unwrap_or(false) as u8));
1172 w(&mut h, " else fmt::print(\"LOGOS_MM_FAIL pcc={} checksum={} golden={}\\n\", pcc, checksum, want_checksum);");
1173 w(
1174 &mut h,
1175 &format!(" std::vector<uint32_t> cyc({});", 16 * n_sites.max(1)),
1176 );
1177 w(
1178 &mut h,
1179 " distributed::EnqueueReadMeshBuffer(cq, cyc, buf_cycles, true);",
1180 );
1181 w(&mut h, " uint32_t device_cycles = 0; for (size_t i = 0; i < cyc.size(); i += 8) device_cycles = std::max(device_cycles, cyc[i]);");
1184 w(
1185 &mut h,
1186 " fmt::print(\"LOGOS_MM_DEVICE_CYCLES={}\\n\", device_cycles);",
1187 );
1188 w(&mut h, " mesh_device->close();");
1189 w(&mut h, " return ok ? 0 : 1;");
1190 w(&mut h, "}");
1191 Ok(h)
1192}
1193
1194pub fn print_cmake() -> String {
1195 [
1196 "cmake_minimum_required(VERSION 3.22...4.2)",
1197 "project(metal_example_logos_spatial)",
1198 "add_executable(metal_example_logos_spatial host.cpp)",
1199 "if(NOT TARGET TT::Metalium)",
1200 " find_package(TT-Metalium REQUIRED)",
1201 "endif()",
1202 "target_link_libraries(metal_example_logos_spatial PUBLIC TT::Metalium)",
1203 "target_compile_definitions(metal_example_logos_spatial PRIVATE LOGOS_KERNEL_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/kernels/\")",
1204 "if(TARGET TT::CommonPCH)",
1205 " tt_reuse_precompile_headers(metal_example_logos_spatial TT::CommonPCH)",
1206 "endif()",
1207 "",
1208 ]
1209 .join("\n")
1210}