1use super::{SpatialReject, LV_GEN_COL, LV_GEN_ROW, LV_ITEM_X, LV_ITEM_Y, LV_KBLOCK};
8use crate::codegen_sva::fabric_target::Precision;
9use crate::spatial::ir::*;
10use crate::spatial::target::{precision_int_bits, SpatialTarget};
11use crate::tensor_recognize::{ElementwiseOp, Fill, MatmulSpec, RowReduceKind};
12
13#[derive(Clone, Debug, PartialEq, Eq)]
15pub struct MatmulNames {
16 pub a: String,
17 pub b: String,
18 pub c: String,
19}
20
21pub fn init_of(fill: &Fill, n: usize) -> TensorInit {
22 match fill {
23 Fill::Const(0) => TensorInit::Zero,
24 Fill::Const(c) => TensorInit::Pattern {
25 modulus: 1,
26 shift: *c,
27 },
28 Fill::Pattern { modulus, shift } => TensorInit::Pattern {
29 modulus: *modulus,
30 shift: *shift,
31 },
32 Fill::Overrides { base, sets } => {
33 let base = init_of(base, n);
34 let mut vals: Vec<i64> = (0..n).map(|i| base.element(i).unwrap_or(0)).collect();
35 for (i, v) in sets {
36 if *i < n {
37 vals[*i] = *v;
38 }
39 }
40 TensorInit::Values(vals)
41 }
42 Fill::ConstF(b) => TensorInit::ConstF(*b),
43 Fill::FloatOverrides { base, sets } => {
44 let mut vals = vec![*base; n];
45 for (i, v) in sets {
46 if *i < n {
47 vals[*i] = *v;
48 }
49 }
50 TensorInit::FloatValues(vals)
51 }
52 Fill::RealPattern { .. } | Fill::Unknown => TensorInit::External,
56 }
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
63pub struct ElementwiseNames {
64 pub a: String,
65 pub b: String,
66 pub c: String,
67}
68
69#[derive(Clone, Debug, PartialEq, Eq)]
70pub struct SwiGluNames {
71 pub gate: String,
72 pub up: String,
73 pub y: String,
74}
75
76#[derive(Clone, Debug, PartialEq, Eq)]
77pub struct FusedRowNames {
78 pub x: String,
79 pub y: String,
80 pub weight: Option<String>,
81 pub residual: Option<String>,
82 pub bias: Option<String>,
83}
84
85#[derive(Clone, Debug, PartialEq, Eq)]
86pub struct FusedRowFills {
87 pub x: Fill,
88 pub weight: Option<Fill>,
89 pub residual: Option<Fill>,
90 pub bias: Option<Fill>,
91}
92
93pub struct Template {
99 tile: crate::spatial::target::TileShape,
100 local: Handshake,
101 tensors: Vec<TensorDecl>,
102 stages: Vec<Stage>,
103 buffers: Vec<Buffer>,
104 edges: Vec<Edge>,
105 trips: Vec<Trip>,
106}
107
108impl Template {
109 pub fn new(target: &SpatialTarget) -> Result<Template, SpatialReject> {
110 Ok(Template {
111 tile: target.tile,
112 local: Handshake::Fifo,
113 tensors: Vec::new(),
114 stages: Vec::new(),
115 buffers: Vec::new(),
116 edges: Vec::new(),
117 trips: Vec::new(),
118 })
119 }
120 pub fn trip(&mut self, trip: Trip) -> TripId {
122 self.trips.push(trip);
123 TripId::new(self.trips.len() - 1)
124 }
125 pub fn tensor(
126 &mut self,
127 name: &str,
128 role: TensorRole,
129 rows: usize,
130 cols: usize,
131 dtype: Precision,
132 init: TensorInit,
133 ) -> TensorId {
134 let id = TensorId::new(self.tensors.len());
135 self.tensors.push(TensorDecl {
136 id,
137 name: name.to_string(),
138 role,
139 elems: ElemShape { rows, cols },
140 tiles: TileGrid {
141 rows_t: rows.div_ceil(self.tile.rows),
142 cols_t: cols.div_ceil(self.tile.cols),
143 },
144 dtype,
145 layout: Layout::RowMajorTiles,
146 space: TensorSpace::Dram,
147 init,
148 range: None,
149 chip: None,
150 });
151 id
152 }
153 pub fn buffer(&mut self, name: &str, page_tiles: usize, dtype: Precision) -> BufferId {
154 let id = BufferId::new(self.buffers.len());
155 self.buffers.push(Buffer {
156 id,
157 name: name.into(),
158 kind: BufferKind::Fifo {
159 depth_pages: 2,
160 page_tiles,
161 },
162 dtype,
163 space: MemSpace::Local,
164 site: None,
165 origin: None,
166 alias_of: None,
167 });
168 id
169 }
170 pub fn buffer_deep(
179 &mut self,
180 name: &str,
181 depth_pages: usize,
182 page_tiles: usize,
183 dtype: Precision,
184 ) -> BufferId {
185 let id = BufferId::new(self.buffers.len());
186 self.buffers.push(Buffer {
187 id,
188 name: name.into(),
189 kind: BufferKind::Fifo {
190 depth_pages,
191 page_tiles,
192 },
193 dtype,
194 space: MemSpace::Local,
195 site: None,
196 origin: None,
197 alias_of: None,
198 });
199 id
200 }
201 pub fn scratch_deep(
203 &mut self,
204 name: &str,
205 depth_pages: usize,
206 page_tiles: usize,
207 dtype: Precision,
208 ) -> BufferId {
209 let id = BufferId::new(self.buffers.len());
210 self.buffers.push(Buffer {
211 id,
212 name: name.into(),
213 kind: BufferKind::Interm {
214 depth_pages,
215 page_tiles,
216 },
217 dtype,
218 space: MemSpace::Local,
219 site: None,
220 origin: None,
221 alias_of: None,
222 });
223 id
224 }
225 pub fn scratch(&mut self, name: &str, page_tiles: usize, dtype: Precision) -> BufferId {
226 let id = BufferId::new(self.buffers.len());
227 self.buffers.push(Buffer {
228 id,
229 name: name.into(),
230 kind: BufferKind::Interm {
231 depth_pages: 2,
232 page_tiles,
233 },
234 dtype,
235 space: MemSpace::Local,
236 site: None,
237 origin: None,
238 alias_of: None,
239 });
240 id
241 }
242 pub fn stage(
243 &mut self,
244 name: &str,
245 kind: StageKind,
246 ins: Vec<BufferId>,
247 outs: Vec<BufferId>,
248 ) -> StageId {
249 let id = StageId::new(self.stages.len());
250 let role = kind.role();
251 self.stages.push(Stage {
252 id,
253 name: name.into(),
254 kind,
255 role,
256 ins,
257 outs,
258 site: None,
259 item: None,
260 origin: None,
261 });
262 id
263 }
264 pub fn load(
266 &mut self,
267 name: &str,
268 tensor: TensorId,
269 steps: Vec<(LoopVar, usize)>,
270 block_w: usize,
271 out: BufferId,
272 ) -> StageId {
273 let cols_t = self.tensors[tensor.idx()].tiles.cols_t;
274 self.stage(
275 name,
276 StageKind::Load {
277 tensor,
278 pattern: AccessPattern {
279 origin: TileAddr {
280 base: 0,
281 steps,
282 indirect: None,
283 },
284 block_h: 1,
285 block_w,
286 stride_w: 1,
287 stride_h: cols_t,
288 },
289 num_blocks: 1,
290 multicast: None,
291 },
292 vec![],
293 vec![out],
294 )
295 }
296 pub fn load_block(
300 &mut self,
301 name: &str,
302 tensor: TensorId,
303 steps: Vec<(LoopVar, usize)>,
304 block_h: usize,
305 block_w: usize,
306 stride_h: usize,
307 out: BufferId,
308 ) -> StageId {
309 self.stage(
310 name,
311 StageKind::Load {
312 tensor,
313 pattern: AccessPattern {
314 origin: TileAddr {
315 base: 0,
316 steps,
317 indirect: None,
318 },
319 block_h,
320 block_w,
321 stride_w: 1,
322 stride_h,
323 },
324 num_blocks: 1,
325 multicast: None,
326 },
327 vec![],
328 vec![out],
329 )
330 }
331 pub fn store_block(
334 &mut self,
335 name: &str,
336 tensor: TensorId,
337 base: usize,
338 steps: Vec<(LoopVar, usize)>,
339 block_h: usize,
340 block_w: usize,
341 stride_h: usize,
342 input: BufferId,
343 ) -> StageId {
344 self.stage(
345 name,
346 StageKind::Store {
347 tensor,
348 pattern: AccessPattern {
349 origin: TileAddr {
350 base,
351 steps,
352 indirect: None,
353 },
354 block_h,
355 block_w,
356 stride_w: 1,
357 stride_h,
358 },
359 sub_blocks: (1, 1),
360 },
361 vec![input],
362 vec![],
363 )
364 }
365 #[allow(clippy::too_many_arguments)]
369 pub fn store_block_over(
370 &mut self,
371 name: &str,
372 tensor: TensorId,
373 base: usize,
374 steps: Vec<(LoopVar, usize)>,
375 block_h: usize,
376 block_w: usize,
377 stride_h: usize,
378 sub_blocks: (usize, usize),
379 input: BufferId,
380 ) -> StageId {
381 self.stage(
382 name,
383 StageKind::Store {
384 tensor,
385 pattern: AccessPattern {
386 origin: TileAddr {
387 base,
388 steps,
389 indirect: None,
390 },
391 block_h,
392 block_w,
393 stride_w: 1,
394 stride_h,
395 },
396 sub_blocks,
397 },
398 vec![input],
399 vec![],
400 )
401 }
402 pub fn load_block_at(
404 &mut self,
405 name: &str,
406 tensor: TensorId,
407 base: usize,
408 steps: Vec<(LoopVar, usize)>,
409 block_h: usize,
410 block_w: usize,
411 stride_h: usize,
412 out: BufferId,
413 ) -> StageId {
414 self.stage(
415 name,
416 StageKind::Load {
417 tensor,
418 pattern: AccessPattern {
419 origin: TileAddr {
420 base,
421 steps,
422 indirect: None,
423 },
424 block_h,
425 block_w,
426 stride_w: 1,
427 stride_h,
428 },
429 num_blocks: 1,
430 multicast: None,
431 },
432 vec![],
433 vec![out],
434 )
435 }
436 pub fn store(
437 &mut self,
438 name: &str,
439 tensor: TensorId,
440 steps: Vec<(LoopVar, usize)>,
441 block_w: usize,
442 input: BufferId,
443 ) -> StageId {
444 let cols_t = self.tensors[tensor.idx()].tiles.cols_t;
445 self.stage(
446 name,
447 StageKind::Store {
448 tensor,
449 pattern: AccessPattern {
450 origin: TileAddr {
451 base: 0,
452 steps,
453 indirect: None,
454 },
455 block_h: 1,
456 block_w,
457 stride_w: 1,
458 stride_h: cols_t,
459 },
460 sub_blocks: (1, 1),
461 },
462 vec![input],
463 vec![],
464 )
465 }
466 pub fn stage_reads(&self, s: StageId, b: BufferId) -> bool {
468 self.stages[s.idx()].ins.contains(&b)
469 }
470 pub fn stage_output(&self, s: StageId) -> BufferId {
472 self.stages[s.idx()].outs[0]
473 }
474 pub fn dtype(&self, b: BufferId) -> Precision {
476 self.buffers[b.idx()].dtype
477 }
478 pub fn page_tiles(&self, b: BufferId) -> usize {
480 self.buffers[b.idx()].kind.page_tiles()
481 }
482 pub fn edge_many(&mut self, from: StageId, buffer: BufferId, to: Vec<StageId>) {
486 let id = EdgeId::new(self.edges.len());
487 self.edges.push(Edge {
488 id,
489 from,
490 buffer,
491 to,
492 handshake: self.local.clone(),
493 });
494 }
495 pub fn edge(&mut self, from: StageId, buffer: BufferId, to: StageId) {
496 let id = EdgeId::new(self.edges.len());
497 self.edges.push(Edge {
498 id,
499 from,
500 buffer,
501 to: vec![to],
502 handshake: self.local.clone(),
503 });
504 }
505 pub fn finish(
506 self,
507 name: String,
508 items: Vec<WorkItem>,
509 acc: AccPolicy,
510 claim: NumericsClaim,
511 target: &SpatialTarget,
512 ) -> Result<SpatialProgram, SpatialReject> {
513 let mut requires = Requirements::default();
514 requires.handshakes.insert(self.local.kind());
515 let p = SpatialProgram {
516 name,
517 tile: self.tile,
518 tensors: self.tensors,
519 stages: self.stages,
520 buffers: self.buffers,
521 edges: self.edges,
522 semaphores: Vec::new(),
523 sections: Vec::new(),
524 trips: self.trips,
525 links: Vec::new(),
526 work: WorkList {
527 items,
528 k_tiles: 1,
529 plan: None,
530 k_trip: None,
531 },
532 numerics: NumericsPolicy::Exact,
533 claim,
534 acc_tiles_used: target.effective_acc_tiles(acc, true),
535 mem_banks: target.site.mem_banks,
536 requires,
537 placement: None,
538 memory: None,
539 schedule: None,
540 certificate: None,
541 };
542 p.validate()?;
543 Ok(p)
544 }
545}
546
547fn elementwise_precisions(
548 target: &SpatialTarget,
549) -> Option<&std::collections::BTreeSet<Precision>> {
550 target.compute.iter().find_map(|c| match c {
551 crate::spatial::target::ComputeCap::Elementwise { precisions } => Some(precisions),
552 _ => None,
553 })
554}
555
556fn row_reduce_precisions(target: &SpatialTarget) -> Option<&std::collections::BTreeSet<Precision>> {
557 target.compute.iter().find_map(|c| match c {
558 crate::spatial::target::ComputeCap::RowReduce { precisions } => Some(precisions),
559 _ => None,
560 })
561}
562
563pub fn elementwise_float(target: &SpatialTarget) -> Option<Precision> {
566 elementwise_precisions(target).and_then(float_precision)
567}
568
569pub fn elementwise_int(target: &SpatialTarget) -> Option<Precision> {
572 let ps = elementwise_precisions(target)?;
573 [Precision::Int64, Precision::Int32, Precision::Int16, Precision::Int8]
574 .into_iter()
575 .find(|p| ps.contains(p))
576}
577
578fn float_precision(ps: &std::collections::BTreeSet<Precision>) -> Option<Precision> {
580 [Precision::Fp32, Precision::Bf16, Precision::Fp16]
581 .into_iter()
582 .find(|p| ps.contains(p))
583}
584
585pub(crate) fn items_per_tile(cols_t: usize) -> Vec<WorkItem> {
587 items_per_tile_grid(1, cols_t)
588}
589
590pub(crate) fn items_per_tile_grid(rows_t: usize, cols_t: usize) -> Vec<WorkItem> {
593 (0..rows_t)
594 .flat_map(|ry| (0..cols_t).map(move |cx| (ry, cx)))
595 .enumerate()
596 .map(|(id, (ry, cx))| WorkItem {
597 id: WorkItemId::new(id),
598 coord: (ry, cx),
599 out: TileRect {
600 row0_t: ry,
601 col0_t: cx,
602 h_t: 1,
603 w_t: 1,
604 },
605 })
606 .collect()
607}
608
609pub(crate) fn tile_grid_steps(rows_t: usize, cols_t: usize) -> Vec<(LoopVar, usize)> {
612 if rows_t > 1 {
613 vec![(LV_ITEM_Y, cols_t), (LV_ITEM_X, 1)]
614 } else {
615 vec![(LV_ITEM_X, 1)]
616 }
617}
618
619pub fn from_pipe(sends: &[i64], target: &SpatialTarget) -> Result<SpatialProgram, SpatialReject> {
641 let n = sends.len();
642 if n == 0 {
643 return Err(SpatialReject::NotLowered("a pipe with no sends"));
644 }
645 const EXACT_FP32: i64 = 1 << 23;
648 let bound: i64 = sends
649 .iter()
650 .map(|v| v.saturating_abs())
651 .try_fold(0i64, |a: i64, v| a.checked_add(v))
652 .unwrap_or(i64::MAX);
653 if bound >= EXACT_FP32 {
654 return Err(SpatialReject::NotLowered(
655 "a pipe whose fold leaves the exactly-representable fp32 range",
656 ));
657 }
658 let caps = elementwise_precisions(target).ok_or(SpatialReject::MissingCompute {
659 needed: "elementwise",
660 target: target.name,
661 })?;
662 let precision = float_precision(caps).ok_or(SpatialReject::MissingCompute {
663 needed: "a float elementwise datapath",
664 target: target.name,
665 })?;
666
667 let mut t = Template::new(target)?;
668 let ch = t.tensor(
669 "ch",
670 TensorRole::Input,
671 1,
672 n,
673 precision,
674 TensorInit::Values(sends.to_vec()),
675 );
676 let total = t.tensor(
677 "total",
678 TensorRole::Output,
679 1,
680 1,
681 precision,
682 TensorInit::Zero,
683 );
684 let cols_t = t.tensors[ch.idx()].tiles.cols_t;
685 let pipe = t.buffer("pipe", cols_t, precision);
686 let out = t.buffer("out", 1, precision);
687 let scaler_elems = target.tile.rows * target.tile.cols;
692 let ones = t.tensor(
693 "pipe_scaler",
694 TensorRole::Param,
695 target.tile.rows,
696 target.tile.cols,
697 precision,
698 TensorInit::FloatValues(vec![1.0f64.to_bits(); scaler_elems]),
699 );
700 let ones_in = t.buffer("in_scaler", 1, precision);
701 let l_ones = t.load("load_scaler", ones, vec![], 1, ones_in);
702 let send = t.load("send", ch, vec![(LV_ITEM_X, 1)], cols_t, pipe);
704 let fold = t.stage(
706 "receive_fold",
707 StageKind::Compute(ComputeOp::Generic {
708 iters: space(target, target.tile.cols, Some(ReducePool::Sum)),
709 ins: vec![ident_map(cols_t * target.tile.cols), const_map()],
710 out: const_map(),
711 body: TensorBody::Operand(0),
712 }),
713 vec![pipe, ones_in],
714 vec![out],
715 );
716 let show = t.store("show", total, vec![(LV_ITEM_X, 1)], 1, out);
717 t.edge(send, pipe, fold);
718 t.edge(l_ones, ones_in, fold);
719 t.edge(fold, out, show);
720 t.finish(
721 format!("pipe_{n}"),
722 items_per_tile(1),
723 AccPolicy::F32,
724 NumericsClaim::Exact,
725 target,
726 )
727}
728
729pub fn from_elementwise(
731 spec: &crate::tensor_recognize::ElementwiseSpec,
732 names: &ElementwiseNames,
733 fills: (&Fill, &Fill),
734 target: &SpatialTarget,
735) -> Result<SpatialProgram, SpatialReject> {
736 from_elementwise_with(spec, names, fills, false, (1, spec.n), target)
737}
738
739pub fn from_elementwise_with(
743 spec: &crate::tensor_recognize::ElementwiseSpec,
744 names: &ElementwiseNames,
745 fills: (&Fill, &Fill),
746 float_hint: bool,
747 shape: (usize, usize),
748 target: &SpatialTarget,
749) -> Result<SpatialProgram, SpatialReject> {
750 let caps = elementwise_precisions(target).ok_or(SpatialReject::MissingCompute {
751 needed: "elementwise",
752 target: target.name,
753 })?;
754 let (rows, cols) = if shape.0 * shape.1 == spec.n {
755 shape
756 } else {
757 (1, spec.n)
758 };
759 let init_a = init_of(fills.0, spec.n);
760 let init_b = init_of(fills.1, spec.n);
761 let float_data = float_hint || init_a.is_float() || init_b.is_float();
762 let precision = if !float_data && caps.contains(&Precision::Int64) {
763 Precision::Int64
764 } else {
765 float_precision(caps).ok_or(SpatialReject::MissingCompute {
766 needed: "a float elementwise datapath",
767 target: target.name,
768 })?
769 };
770 let bound = init_a
771 .abs_bound()
772 .unwrap_or(i64::MAX)
773 .max(init_b.abs_bound().unwrap_or(i64::MAX));
774 let claim = match precision {
775 Precision::Int64 => NumericsClaim::Exact,
776 Precision::Fp32 if !float_data && bound < (1 << 23) => NumericsClaim::Exact,
777 _ => NumericsClaim::Approx {
778 reason: format!("elementwise in {precision:?}"),
779 },
780 };
781 let body = match spec.op {
782 ElementwiseOp::Add => TensorBody::Add(Box::new(TensorBody::Operand(0)), Box::new(TensorBody::Operand(1))),
783 ElementwiseOp::Mul => TensorBody::Mul(Box::new(TensorBody::Operand(0)), Box::new(TensorBody::Operand(1))),
784 };
785 if let Some(w) = spec.row {
787 if w == 0 || spec.n % w != 0 {
788 return Err(SpatialReject::NotLowered("an elementwise op whose row does not divide its operand"));
789 }
790 return row_broadcast(spec, names, fills, (rows, cols), w, precision, claim, body, target);
791 }
792 if rows == 1 && cols.div_ceil(target.tile.cols) > 1 && !super::from_qwen_ops::row_needs_blocking(cols, target) {
796 return super::from_qwen_ops::row_binary_map(
797 format!("elementwise_{}_{}", spec.op.c_op(), spec.n),
798 "elementwise",
799 body,
800 (&names.a, fills.0),
801 (&names.b, fills.1),
802 &names.c,
803 cols,
804 precision,
805 claim,
806 target,
807 );
808 }
809 let mut t = Template::new(target)?;
810 let a = t.tensor(&names.a, TensorRole::Input, rows, cols, precision, init_a);
811 let b = t.tensor(&names.b, TensorRole::Input, rows, cols, precision, init_b);
812 let c = t.tensor(
813 &names.c,
814 TensorRole::Output,
815 rows,
816 cols,
817 precision,
818 TensorInit::Zero,
819 );
820 let (rows_t, cols_t) = (
821 t.tensors[c.idx()].tiles.rows_t,
822 t.tensors[c.idx()].tiles.cols_t,
823 );
824 let in0 = t.buffer("in0", 1, precision);
825 let in1 = t.buffer("in1", 1, precision);
826 let out = t.buffer("out", 1, precision);
827 let la = t.load("load_a", a, tile_grid_steps(rows_t, cols_t), 1, in0);
828 let lb = t.load("load_b", b, tile_grid_steps(rows_t, cols_t), 1, in1);
829 let ew = t.stage(
830 "elementwise",
831 StageKind::Compute(ComputeOp::Generic {
832 iters: space(target, target.tile.cols, None),
833 ins: vec![ident_map(target.tile.cols), ident_map(target.tile.cols)],
834 out: ident_map(target.tile.cols),
835 body,
840 }),
841 vec![in0, in1],
842 vec![out],
843 );
844 let st = t.store("store_c", c, tile_grid_steps(rows_t, cols_t), 1, out);
845 t.edge(la, in0, ew);
846 t.edge(lb, in1, ew);
847 t.edge(ew, out, st);
848 let acc = if precision_int_bits(precision).is_some() {
849 AccPolicy::I64
850 } else {
851 AccPolicy::F32
852 };
853 let name = if rows > 1 {
854 format!("elementwise_{}_{rows}x{cols}", spec.op.c_op())
855 } else {
856 format!("elementwise_{}_{}", spec.op.c_op(), spec.n)
857 };
858 t.finish(
859 name,
860 items_per_tile_grid(rows_t, cols_t),
861 acc,
862 claim,
863 target,
864 )
865}
866
867#[allow(clippy::too_many_arguments)]
871fn row_broadcast(
872 spec: &crate::tensor_recognize::ElementwiseSpec,
873 names: &ElementwiseNames,
874 fills: (&Fill, &Fill),
875 (rows, cols): (usize, usize),
876 w: usize,
877 precision: Precision,
878 claim: NumericsClaim,
879 body: TensorBody,
880 target: &SpatialTarget,
881) -> Result<SpatialProgram, SpatialReject> {
882 if rows == 1 || cols != w {
883 return super::from_qwen_ops::flat_row_broadcast(spec.op, names, fills, spec.n, w, precision, claim, target);
884 }
885 let mut t = Template::new(target)?;
886 let a = t.tensor(&names.a, TensorRole::Input, rows, cols, precision, init_of(fills.0, spec.n));
887 let b = t.tensor(&names.b, TensorRole::Input, 1, w, precision, init_of(fills.1, w));
888 let c = t.tensor(&names.c, TensorRole::Output, rows, cols, precision, TensorInit::Zero);
889 let (rows_t, cols_t) = (t.tensors[c.idx()].tiles.rows_t, t.tensors[c.idx()].tiles.cols_t);
890 let in0 = t.buffer("in0", 1, precision);
891 let in1 = t.buffer("in1", 1, precision);
892 let out = t.buffer("out", 1, precision);
893 let la = t.load("load_a", a, tile_grid_steps(rows_t, cols_t), 1, in0);
894 let lb = t.load("load_row", b, vec![(LV_ITEM_X, 1)], 1, in1);
895 let tw = target.tile.cols;
896 let ew = t.stage(
897 "elementwise_row",
898 StageKind::Compute(ComputeOp::Generic {
899 iters: space(target, tw, None),
900 ins: vec![ident_map(tw), per_col_map()],
901 out: ident_map(tw),
902 body,
903 }),
904 vec![in0, in1],
905 vec![out],
906 );
907 let st = t.store("store_c", c, tile_grid_steps(rows_t, cols_t), 1, out);
908 t.edge(la, in0, ew);
909 t.edge(lb, in1, ew);
910 t.edge(ew, out, st);
911 let acc = if precision_int_bits(precision).is_some() { AccPolicy::I64 } else { AccPolicy::F32 };
912 t.finish(format!("elementwise_{}_row_{rows}x{cols}", spec.op.c_op()), items_per_tile_grid(rows_t, cols_t), acc, claim, target)
913}
914
915pub fn page_width(page_tiles: usize, target: &SpatialTarget) -> usize {
919 page_tiles * target.tile.cols
920}
921
922pub fn ident_map(width: usize) -> ElemAccess {
925 ElemAccess {
926 base: 0,
927 terms: vec![(LV_GEN_ROW, width as i64), (LV_GEN_COL, 1)],
928 indirect: None,
929 }
930}
931
932pub fn per_row_map(width: usize) -> ElemAccess {
935 ElemAccess {
936 base: 0,
937 terms: vec![(LV_GEN_ROW, width as i64)],
938 indirect: None,
939 }
940}
941
942pub fn per_col_map() -> ElemAccess {
944 ElemAccess {
945 base: 0,
946 terms: vec![(LV_GEN_COL, 1)],
947 indirect: None,
948 }
949}
950
951pub fn const_map() -> ElemAccess {
954 ElemAccess {
955 base: 0,
956 terms: Vec::new(),
957 indirect: None,
958 }
959}
960
961pub(crate) fn space(
970 target: &SpatialTarget,
971 cols: usize,
972 reduce: Option<ReducePool>,
973) -> Vec<IterSpec> {
974 vec![
975 IterSpec {
976 var: LV_GEN_ROW,
977 extent: target.tile.rows,
978 kind: IterKind::Parallel,
979 },
980 IterSpec {
981 var: LV_GEN_COL,
982 extent: cols.max(1),
983 kind: match reduce {
984 Some(pool) => IterKind::Reduce { pool },
985 None => IterKind::Parallel,
986 },
987 },
988 ]
989}
990
991pub fn from_swiglu(
992 spec: &crate::tensor_recognize::SwiGluSpec,
993 names: &SwiGluNames,
994 fills: (&Fill, &Fill),
995 target: &SpatialTarget,
996) -> Result<SpatialProgram, SpatialReject> {
997 from_swiglu_shaped(spec, names, fills, (1, spec.total), target)
998}
999
1000pub fn from_swiglu_shaped(
1003 spec: &crate::tensor_recognize::SwiGluSpec,
1004 names: &SwiGluNames,
1005 fills: (&Fill, &Fill),
1006 shape: (usize, usize),
1007 target: &SpatialTarget,
1008) -> Result<SpatialProgram, SpatialReject> {
1009 let caps = elementwise_precisions(target).ok_or(SpatialReject::MissingCompute {
1010 needed: "elementwise",
1011 target: target.name,
1012 })?;
1013 let precision = float_precision(caps).ok_or(SpatialReject::MissingCompute {
1014 needed: "a float elementwise datapath with exp (swiglu)",
1015 target: target.name,
1016 })?;
1017 let (rows, cols) = if shape.0 * shape.1 == spec.total {
1018 shape
1019 } else {
1020 (1, spec.total)
1021 };
1022 let mut t = Template::new(target)?;
1023 let g = t.tensor(
1024 &names.gate,
1025 TensorRole::Input,
1026 rows,
1027 cols,
1028 precision,
1029 init_of(fills.0, spec.total),
1030 );
1031 let u = t.tensor(
1032 &names.up,
1033 TensorRole::Input,
1034 rows,
1035 cols,
1036 precision,
1037 init_of(fills.1, spec.total),
1038 );
1039 let y = t.tensor(
1040 &names.y,
1041 TensorRole::Output,
1042 rows,
1043 cols,
1044 precision,
1045 TensorInit::Zero,
1046 );
1047 let (rows_t, cols_t) = (
1048 t.tensors[y.idx()].tiles.rows_t,
1049 t.tensors[y.idx()].tiles.cols_t,
1050 );
1051 let in0 = t.buffer("in0", 1, precision);
1052 let in1 = t.buffer("in1", 1, precision);
1053 let out = t.buffer("out", 1, precision);
1054 let lg = t.load("load_gate", g, tile_grid_steps(rows_t, cols_t), 1, in0);
1055 let lu = t.load("load_up", u, tile_grid_steps(rows_t, cols_t), 1, in1);
1056 let act = t.scratch("silu_g", 1, AccPolicy::F32.interm_precision());
1059 let s1 = t.stage(
1060 "silu",
1061 StageKind::Compute(ComputeOp::Generic {
1062 iters: space(target, target.tile.cols, None),
1063 ins: vec![ident_map(target.tile.cols)],
1064 out: ident_map(target.tile.cols),
1065 body: TensorBody::Unary {
1066 kind: SfpuKind::Silu,
1067 arg: Box::new(TensorBody::Operand(0)),
1068 },
1069 }),
1070 vec![in0],
1071 vec![act],
1072 );
1073 let sw = t.stage(
1074 "swiglu",
1075 StageKind::Compute(ComputeOp::Generic {
1076 iters: space(target, target.tile.cols, None),
1077 ins: vec![ident_map(target.tile.cols), ident_map(target.tile.cols)],
1078 out: ident_map(target.tile.cols),
1079 body: TensorBody::Mul(
1080 Box::new(TensorBody::Operand(0)),
1081 Box::new(TensorBody::Operand(1)),
1082 ),
1083 }),
1084 vec![act, in1],
1085 vec![out],
1086 );
1087 let st = t.store("store_y", y, tile_grid_steps(rows_t, cols_t), 1, out);
1088 t.edge(lg, in0, s1);
1089 t.edge(s1, act, sw);
1090 t.edge(lu, in1, sw);
1091 t.edge(sw, out, st);
1092 let name = if rows > 1 {
1093 format!("swiglu_{rows}x{cols}")
1094 } else {
1095 format!("swiglu_{}", spec.total)
1096 };
1097 t.finish(
1098 name,
1099 items_per_tile_grid(rows_t, cols_t),
1100 AccPolicy::F32,
1101 NumericsClaim::Approx {
1102 reason: format!("sigmoid in {precision:?}"),
1103 },
1104 target,
1105 )
1106}
1107
1108pub(crate) fn eps_nano(eps: crate::tensor_recognize::Epsilon) -> Result<u64, SpatialReject> {
1112 let nano = eps.get() * 1e9;
1113 let whole = nano.round();
1114 if whole < 1.0 || (nano - whole).abs() > 1e-6 * whole {
1115 return Err(SpatialReject::NotLowered("a norm epsilon that is not a whole number of nano"));
1116 }
1117 Ok(whole as u64)
1118}
1119
1120pub fn from_fused_row(
1121 spec: &crate::tensor_recognize::FusedRowSpec,
1122 names: &FusedRowNames,
1123 fills: &FusedRowFills,
1124 target: &SpatialTarget,
1125) -> Result<SpatialProgram, SpatialReject> {
1126 let caps = row_reduce_precisions(target).ok_or(SpatialReject::MissingCompute {
1127 needed: "a row-reduce engine (rmsnorm / softmax / layernorm)",
1128 target: target.name,
1129 })?;
1130 let precision = float_precision(caps).ok_or(SpatialReject::MissingCompute {
1131 needed: "a float row-reduce datapath",
1132 target: target.name,
1133 })?;
1134 let (rows, cols) = (spec.rows, spec.cols);
1135 let kind = spec.reduce;
1136 let mut t = Template::new(target)?;
1137 let x = t.tensor(
1138 &names.x,
1139 TensorRole::Input,
1140 rows,
1141 cols,
1142 precision,
1143 init_of(&fills.x, rows * cols),
1144 );
1145 let residual = match (&names.residual, &fills.residual) {
1146 (Some(n), Some(f)) => Some(t.tensor(
1147 n,
1148 TensorRole::Input,
1149 rows,
1150 cols,
1151 precision,
1152 init_of(f, rows * cols),
1153 )),
1154 _ => None,
1155 };
1156 let weight = match (&names.weight, &fills.weight) {
1157 (Some(n), Some(f)) => {
1158 Some(t.tensor(n, TensorRole::Param, 1, cols, precision, init_of(f, cols)))
1159 }
1160 _ => None,
1161 };
1162 let bias = match (&names.bias, &fills.bias) {
1163 (Some(n), Some(f)) => {
1164 Some(t.tensor(n, TensorRole::Param, 1, cols, precision, init_of(f, cols)))
1165 }
1166 _ => None,
1167 };
1168 let y = t.tensor(
1169 &names.y,
1170 TensorRole::Output,
1171 rows,
1172 cols,
1173 precision,
1174 TensorInit::Zero,
1175 );
1176 let (rows_t, cols_t) = (
1177 t.tensors[y.idx()].tiles.rows_t,
1178 t.tensors[y.idx()].tiles.cols_t,
1179 );
1180 if cols_t > target.effective_acc_tiles(AccPolicy::F32, true) {
1181 return Err(SpatialReject::AccTilesExceeded {
1182 needed: cols_t,
1183 available: target.effective_acc_tiles(AccPolicy::F32, true),
1184 acc: AccPolicy::F32,
1185 });
1186 }
1187 let row_steps = vec![(LV_ITEM_Y, cols_t)];
1188 let xin = t.buffer("in_x", cols_t, precision);
1189 let lx = t.load("load_x", x, row_steps.clone(), cols_t, xin);
1190 let mut loads = vec![(lx, xin)];
1191 let res_in = residual.map(|r| {
1192 let b = t.buffer("in_res", cols_t, precision);
1193 let l = t.load("load_res", r, row_steps.clone(), cols_t, b);
1194 loads.push((l, b));
1195 b
1196 });
1197 let w_in = weight.map(|w| {
1198 let b = t.buffer("in_w", cols_t, precision);
1199 let l = t.load("load_w", w, vec![], cols_t, b);
1200 loads.push((l, b));
1201 b
1202 });
1203 let b_in = bias.map(|bt| {
1204 let b = t.buffer("in_b", cols_t, precision);
1205 let l = t.load("load_b", bt, vec![], cols_t, b);
1206 loads.push((l, b));
1207 b
1208 });
1209 let out = t.buffer("out", cols_t, precision);
1210 let wide = page_width(cols_t, target);
1211 let one = page_width(1, target);
1212 if kind != RowReduceKind::RmsNorm {
1230 let acc_path = AccPolicy::F32.interm_precision();
1231 let mut chain: Vec<StageId> = Vec::new();
1232 let scaler_tile = target.tile.rows * target.tile.cols;
1233 let ones = t.tensor(
1237 "row_scaler",
1238 TensorRole::Param,
1239 target.tile.rows,
1240 target.tile.cols,
1241 precision,
1242 TensorInit::FloatValues(vec![1.0f64.to_bits(); scaler_tile]),
1243 );
1244 let ones_in = t.buffer("in_scaler", 1, precision);
1245 let l_ones = t.load("load_scaler", ones, vec![], 1, ones_in);
1246 loads.push((l_ones, ones_in));
1247
1248 let mk = |t: &mut Template,
1249 name: &str,
1250 iters: Vec<IterSpec>,
1251 ins: Vec<BufferId>,
1252 maps: Vec<ElemAccess>,
1253 omap: ElemAccess,
1254 body: TensorBody,
1255 dst: BufferId|
1256 -> StageId {
1257 t.stage(
1258 name,
1259 StageKind::Compute(ComputeOp::Generic {
1260 iters,
1261 ins: maps,
1262 out: omap,
1263 body,
1264 }),
1265 ins,
1266 vec![dst],
1267 )
1268 };
1269 let out_stage;
1270 if kind == RowReduceKind::Softmax {
1271 let rmax = t.scratch("row_max", 1, acc_path);
1273 chain.push(mk(
1274 &mut t,
1275 "row_max",
1276 space(target, cols, Some(ReducePool::Max)),
1277 vec![xin, ones_in],
1278 vec![ident_map(wide), const_map()],
1279 per_row_map(one),
1280 TensorBody::Operand(0),
1281 rmax,
1282 ));
1283 let cx = t.scratch("centred", cols_t, acc_path);
1284 chain.push(mk(
1285 &mut t,
1286 "centre",
1287 space(target, cols, None),
1288 vec![xin, rmax],
1289 vec![ident_map(wide), per_row_map(one)],
1290 ident_map(wide),
1291 TensorBody::Sub(
1292 Box::new(TensorBody::Operand(0)),
1293 Box::new(TensorBody::Operand(1)),
1294 ),
1295 cx,
1296 ));
1297 let ex = t.scratch("expx", cols_t, acc_path);
1298 chain.push(mk(
1299 &mut t,
1300 "exp",
1301 space(target, cols, None),
1302 vec![cx],
1303 vec![ident_map(wide)],
1304 ident_map(wide),
1305 TensorBody::Unary {
1306 kind: SfpuKind::Exp,
1307 arg: Box::new(TensorBody::Operand(0)),
1308 },
1309 ex,
1310 ));
1311 let rsum = t.scratch("row_sum", 1, acc_path);
1312 chain.push(mk(
1313 &mut t,
1314 "row_sum",
1315 space(target, cols, Some(ReducePool::Sum)),
1316 vec![ex, ones_in],
1317 vec![ident_map(wide), const_map()],
1318 per_row_map(one),
1319 TensorBody::Operand(0),
1320 rsum,
1321 ));
1322 let inv = t.scratch("row_inv", 1, acc_path);
1323 chain.push(mk(
1324 &mut t,
1325 "recip",
1326 space(target, target.tile.cols, None),
1327 vec![rsum],
1328 vec![per_row_map(one)],
1329 per_row_map(one),
1330 TensorBody::Unary {
1331 kind: SfpuKind::Recip,
1332 arg: Box::new(TensorBody::Operand(0)),
1333 },
1334 inv,
1335 ));
1336 out_stage = mk(
1337 &mut t,
1338 "scale",
1339 space(target, cols, None),
1340 vec![ex, inv],
1341 vec![ident_map(wide), per_row_map(one)],
1342 ident_map(wide),
1343 TensorBody::Mul(
1344 Box::new(TensorBody::Operand(0)),
1345 Box::new(TensorBody::Operand(1)),
1346 ),
1347 out,
1348 );
1349 chain.push(out_stage);
1350 } else {
1351 let inv_cols = (1.0f64 / cols as f64).to_bits();
1353 let sum = t.scratch("row_sum", 1, acc_path);
1354 chain.push(mk(
1355 &mut t,
1356 "row_sum",
1357 space(target, cols, Some(ReducePool::Sum)),
1358 vec![xin, ones_in],
1359 vec![ident_map(wide), const_map()],
1360 per_row_map(one),
1361 TensorBody::Operand(0),
1362 sum,
1363 ));
1364 let mean = t.scratch("row_mean", 1, acc_path);
1365 chain.push(mk(
1366 &mut t,
1367 "mean",
1368 space(target, target.tile.cols, None),
1369 vec![sum],
1370 vec![per_row_map(one)],
1371 per_row_map(one),
1372 TensorBody::Mul(
1373 Box::new(TensorBody::Operand(0)),
1374 Box::new(TensorBody::Const(inv_cols)),
1375 ),
1376 mean,
1377 ));
1378 let dev = t.scratch("dev", cols_t, acc_path);
1379 chain.push(mk(
1380 &mut t,
1381 "centre",
1382 space(target, cols, None),
1383 vec![xin, mean],
1384 vec![ident_map(wide), per_row_map(one)],
1385 ident_map(wide),
1386 TensorBody::Sub(
1387 Box::new(TensorBody::Operand(0)),
1388 Box::new(TensorBody::Operand(1)),
1389 ),
1390 dev,
1391 ));
1392 let dsq = t.scratch("dev_sq", cols_t, acc_path);
1393 chain.push(mk(
1394 &mut t,
1395 "square",
1396 space(target, cols, None),
1397 vec![dev, dev],
1398 vec![ident_map(wide), ident_map(wide)],
1399 ident_map(wide),
1400 TensorBody::Mul(
1401 Box::new(TensorBody::Operand(0)),
1402 Box::new(TensorBody::Operand(1)),
1403 ),
1404 dsq,
1405 ));
1406 let vsum = t.scratch("var_sum", 1, acc_path);
1407 chain.push(mk(
1408 &mut t,
1409 "var",
1410 space(target, cols, Some(ReducePool::Sum)),
1411 vec![dsq, ones_in],
1412 vec![ident_map(wide), const_map()],
1413 per_row_map(one),
1414 TensorBody::Operand(0),
1415 vsum,
1416 ));
1417 let inv = t.scratch("row_inv", 1, acc_path);
1418 chain.push(mk(
1419 &mut t,
1420 "rsqrt",
1421 space(target, target.tile.cols, None),
1422 vec![vsum],
1423 vec![per_row_map(one)],
1424 per_row_map(one),
1425 TensorBody::Unary {
1426 kind: SfpuKind::Rsqrt {
1427 eps_nano: eps_nano(spec.eps)?,
1428 over: cols,
1429 },
1430 arg: Box::new(TensorBody::Operand(0)),
1431 },
1432 inv,
1433 ));
1434 let mut prev = dev;
1437 let mut steps: Vec<(&str, BufferId, ElemAccess, TensorBody)> = vec![(
1438 "normalize",
1439 inv,
1440 per_row_map(one),
1441 TensorBody::Mul(
1442 Box::new(TensorBody::Operand(0)),
1443 Box::new(TensorBody::Operand(1)),
1444 ),
1445 )];
1446 if let Some(w) = w_in {
1447 steps.push((
1448 "weight",
1449 w,
1450 per_col_map(),
1451 TensorBody::Mul(
1452 Box::new(TensorBody::Operand(0)),
1453 Box::new(TensorBody::Operand(1)),
1454 ),
1455 ));
1456 }
1457 if let Some(b) = b_in {
1458 steps.push((
1459 "bias",
1460 b,
1461 per_col_map(),
1462 TensorBody::Add(
1463 Box::new(TensorBody::Operand(0)),
1464 Box::new(TensorBody::Operand(1)),
1465 ),
1466 ));
1467 }
1468 let last = steps.len() - 1;
1469 let mut tail = None;
1470 for (i, (name, second, m, body)) in steps.into_iter().enumerate() {
1471 let dst = if i == last {
1472 out
1473 } else {
1474 t.scratch(&format!("{name}_out"), cols_t, acc_path)
1475 };
1476 let sid = mk(
1477 &mut t,
1478 name,
1479 space(target, cols, None),
1480 vec![prev, second],
1481 vec![ident_map(wide), m],
1482 ident_map(wide),
1483 body,
1484 dst,
1485 );
1486 chain.push(sid);
1487 prev = dst;
1488 tail = Some(sid);
1489 }
1490 out_stage = tail.expect("the layernorm chain ends in the stage that writes the output");
1491 }
1492 let st = t.store("store_y", y, row_steps, cols_t, out);
1493 for (l, b) in loads {
1494 let consumer = *chain
1495 .iter()
1496 .find(|c| t.stages[c.idx()].ins.contains(&b))
1497 .expect("every load feeds a chain stage");
1498 t.edge(l, b, consumer);
1499 }
1500 for pair in chain.windows(2) {
1501 let b = t.stages[pair[0].idx()].outs[0];
1502 if t.stages[pair[1].idx()].ins.contains(&b) {
1503 t.edge(pair[0], b, pair[1]);
1504 } else {
1505 let later = *chain
1506 .iter()
1507 .find(|c| t.stages[c.idx()].ins.contains(&b))
1508 .expect("every chain output is read");
1509 t.edge(pair[0], b, later);
1510 }
1511 }
1512 t.edge(out_stage, out, st);
1513 let items: Vec<WorkItem> = (0..rows_t)
1514 .map(|oy| WorkItem {
1515 id: WorkItemId::new(oy),
1516 coord: (oy, 0),
1517 out: TileRect {
1518 row0_t: oy,
1519 col0_t: 0,
1520 h_t: 1,
1521 w_t: cols_t,
1522 },
1523 })
1524 .collect();
1525 return t.finish(
1526 format!("{}_{}x{}", kind.name(), rows, cols),
1527 items,
1528 AccPolicy::F32,
1529 NumericsClaim::Approx {
1530 reason: format!("row reduction in {precision:?}"),
1531 },
1532 target,
1533 );
1534 }
1535 let acc_path = AccPolicy::F32.interm_precision();
1564 let interm = precision;
1565 let mut chain: Vec<StageId> = Vec::new();
1566 let mut src = xin;
1567 if let Some(r) = res_in {
1568 let xr = t.scratch("xr", cols_t, interm);
1569 chain.push(t.stage(
1570 "add_residual",
1571 StageKind::Compute(ComputeOp::Generic {
1572 iters: space(target, cols, None),
1573 ins: vec![ident_map(wide), ident_map(wide)],
1574 out: ident_map(wide),
1575 body: TensorBody::Add(
1576 Box::new(TensorBody::Operand(0)),
1577 Box::new(TensorBody::Operand(1)),
1578 ),
1579 }),
1580 vec![xin, r],
1581 vec![xr],
1582 ));
1583 src = xr;
1584 }
1585 let xsq = t.scratch("xsq", cols_t, acc_path);
1586 chain.push(t.stage(
1587 "square",
1588 StageKind::Compute(ComputeOp::Generic {
1589 iters: space(target, cols, None),
1590 ins: vec![ident_map(wide), ident_map(wide)],
1591 out: ident_map(wide),
1592 body: TensorBody::Mul(
1593 Box::new(TensorBody::Operand(0)),
1594 Box::new(TensorBody::Operand(1)),
1595 ),
1596 }),
1597 vec![src, src],
1598 vec![xsq],
1599 ));
1600 let tile_elems = target.tile.rows * target.tile.cols;
1604 let recip = 1.0f64;
1607 let ones = t.tensor(
1608 "rms_scaler",
1609 TensorRole::Param,
1610 target.tile.rows,
1611 target.tile.cols,
1612 precision,
1613 TensorInit::FloatValues(vec![recip.to_bits(); tile_elems]),
1614 );
1615 let ones_in = t.buffer("in_scaler", 1, precision);
1616 let l_ones = t.load("load_scaler", ones, vec![], 1, ones_in);
1617 loads.push((l_ones, ones_in));
1618 let ssum = t.scratch("row_mean", 1, acc_path);
1619 let reduce = t.stage(
1623 "reduce_rows",
1624 StageKind::Compute(ComputeOp::Generic {
1625 iters: space(target, cols, Some(ReducePool::Sum)),
1629 ins: vec![ident_map(wide), const_map()],
1630 out: per_row_map(one),
1631 body: TensorBody::Operand(0),
1632 }),
1633 vec![xsq, ones_in],
1634 vec![ssum],
1635 );
1636 chain.push(reduce);
1637 let scale = t.scratch("row_scale", 1, acc_path);
1638 chain.push(t.stage(
1639 "rsqrt",
1640 StageKind::Compute(ComputeOp::Generic {
1641 iters: space(target, target.tile.cols, None),
1642 ins: vec![per_row_map(one)],
1643 out: per_row_map(one),
1644 body: TensorBody::Unary {
1645 kind: SfpuKind::Rsqrt {
1646 eps_nano: eps_nano(spec.eps)?,
1647 over: cols,
1648 },
1649 arg: Box::new(TensorBody::Operand(0)),
1650 },
1651 }),
1652 vec![ssum],
1653 vec![scale],
1654 ));
1655 let mut steps: Vec<(&str, ComputeOp, BufferId)> = vec![(
1659 "rescale",
1660 ComputeOp::Generic {
1661 iters: space(target, cols, None),
1664 ins: vec![ident_map(wide), per_row_map(one)],
1665 out: ident_map(wide),
1666 body: TensorBody::Mul(
1667 Box::new(TensorBody::Operand(0)),
1668 Box::new(TensorBody::Operand(1)),
1669 ),
1670 },
1671 scale,
1672 )];
1673 if let Some(w) = w_in {
1674 steps.push((
1675 "weight",
1676 ComputeOp::Generic {
1677 iters: space(target, cols, None),
1679 ins: vec![ident_map(wide), per_col_map()],
1680 out: ident_map(wide),
1681 body: TensorBody::Mul(
1682 Box::new(TensorBody::Operand(0)),
1683 Box::new(TensorBody::Operand(1)),
1684 ),
1685 },
1686 w,
1687 ));
1688 }
1689 if let Some(b) = b_in {
1690 steps.push((
1691 "bias",
1692 ComputeOp::Generic {
1693 iters: space(target, cols, None),
1694 ins: vec![ident_map(wide), per_col_map()],
1695 out: ident_map(wide),
1696 body: TensorBody::Add(
1697 Box::new(TensorBody::Operand(0)),
1698 Box::new(TensorBody::Operand(1)),
1699 ),
1700 },
1701 b,
1702 ));
1703 }
1704 let last = steps.len() - 1;
1705 let mut prev = src;
1706 for (i, (name, op, second)) in steps.into_iter().enumerate() {
1707 let dst = if i == last {
1708 out
1709 } else {
1710 t.scratch(&format!("{name}_out"), cols_t, interm)
1711 };
1712 chain.push(t.stage(name, StageKind::Compute(op), vec![prev, second], vec![dst]));
1713 prev = dst;
1714 }
1715 let rescale = *chain
1716 .last()
1717 .expect("the chain ends in the stage that writes the output");
1718 let st = t.store("store_y", y, row_steps, cols_t, out);
1719 for (l, b) in loads {
1721 let consumer = *chain
1722 .iter()
1723 .find(|c| t.stages[c.idx()].ins.contains(&b))
1724 .expect("every load feeds a chain stage");
1725 t.edge(l, b, consumer);
1726 }
1727 t.edge(chain[0], t.stages[chain[0].idx()].outs[0], chain[1]);
1728 for pair in chain.windows(2).skip(1) {
1729 let b = t.stages[pair[0].idx()].outs[0];
1730 t.edge(pair[0], b, pair[1]);
1731 }
1732 t.edge(rescale, out, st);
1733 let items: Vec<WorkItem> = (0..rows_t)
1734 .map(|oy| WorkItem {
1735 id: WorkItemId::new(oy),
1736 coord: (oy, 0),
1737 out: TileRect {
1738 row0_t: oy,
1739 col0_t: 0,
1740 h_t: 1,
1741 w_t: cols_t,
1742 },
1743 })
1744 .collect();
1745 t.finish(
1746 format!("{}_{}x{}", kind.name(), rows, cols),
1747 items,
1748 AccPolicy::F32,
1749 NumericsClaim::Approx {
1750 reason: format!("row reduction in {precision:?}"),
1751 },
1752 target,
1753 )
1754}
1755
1756fn exact_int_bound(p: Precision) -> i64 {
1758 match p {
1759 Precision::Int8 => 127,
1760 Precision::Int16 => 32767,
1761 Precision::Int32 => i32::MAX as i64,
1762 Precision::Int64 => i64::MAX,
1763 Precision::Fp8 => 15,
1764 Precision::Bf16 => 256,
1765 Precision::Fp16 => 2048,
1766 Precision::Fp32 => 1 << 24,
1767 }
1768}
1769
1770pub fn choose_matmul_numerics(
1773 target: &SpatialTarget,
1774 bound_a: Option<i64>,
1775 bound_b: Option<i64>,
1776 k: usize,
1777) -> Result<(Precision, AccPolicy, Precision, NumericsClaim), SpatialReject> {
1778 let symmetric = |b: Option<i64>| b.map(|b| (-b, b));
1779 choose_matmul_numerics_ranged(target, symmetric(bound_a), symmetric(bound_b), k)
1780}
1781
1782fn int_holds(p: Precision, (lo, hi): (i64, i64)) -> bool {
1784 match crate::spatial::target::precision_int_bits(p) {
1785 Some(bits) if bits >= 64 => true,
1786 Some(bits) => lo >= -(1i64 << (bits - 1)) && hi < (1i64 << (bits - 1)),
1787 None => false,
1788 }
1789}
1790
1791pub fn choose_matmul_numerics_ranged(
1796 target: &SpatialTarget,
1797 range_a: Option<(i64, i64)>,
1798 range_b: Option<(i64, i64)>,
1799 k: usize,
1800) -> Result<(Precision, AccPolicy, Precision, NumericsClaim), SpatialReject> {
1801 let magnitude = |(lo, hi): (i64, i64)| (lo as i128).abs().max((hi as i128).abs());
1802 let product_bound: Option<i128> = match (range_a, range_b) {
1805 (Some(a), Some(b)) => {
1806 let (lo, hi) = crate::tensor_graph::facts::product_sums(a, b, k);
1807 (lo > i128::MIN && hi < i128::MAX).then(|| lo.unsigned_abs().max(hi.unsigned_abs()) as i128)
1808 }
1809 _ => None,
1810 };
1811 for p in [
1813 Precision::Int8,
1814 Precision::Int16,
1815 Precision::Int32,
1816 Precision::Int64,
1817 ] {
1818 if !target.matmul_supports(p) {
1819 continue;
1820 }
1821 let fits = match (range_a, range_b) {
1822 (Some(a), Some(b)) => int_holds(p, a) && int_holds(p, b),
1823 _ => p == Precision::Int64,
1824 };
1825 if !fits {
1826 continue;
1827 }
1828 let acc = if target.matmul_acc_int_bits() >= 64 {
1829 AccPolicy::I64
1830 } else {
1831 AccPolicy::I32
1832 };
1833 let limit: i128 = 1i128 << (acc.bits() - 1);
1834 let claim = match product_bound {
1835 Some(pb) if pb < limit => NumericsClaim::Exact,
1836 Some(_) => NumericsClaim::Approx {
1837 reason: format!("partial sums may exceed the {}-bit accumulator", acc.bits()),
1838 },
1839 None => NumericsClaim::Approx {
1840 reason: "operand range unknown (external data)".into(),
1841 },
1842 };
1843 let out =
1846 crate::spatial::target::int_precision_of_bits(acc.bits()).unwrap_or(Precision::Int64);
1847 return Ok((p, acc, out, claim));
1848 }
1849 for p in [Precision::Bf16, Precision::Fp16, Precision::Fp32] {
1852 if !target.matmul_supports(p) {
1853 continue;
1854 }
1855 if !target.matmul_fp32_acc() {
1856 return Err(SpatialReject::MissingCompute {
1857 needed: "fp32 accumulate",
1858 target: target.name,
1859 });
1860 }
1861 let exact = exact_int_bound(p) as i128;
1862 let claim = match (range_a.map(magnitude), range_b.map(magnitude), product_bound) {
1863 (Some(a), Some(b), Some(pb)) if a <= exact && b <= exact && pb < (1i128 << 24) => NumericsClaim::Exact,
1864 (Some(a), Some(b), _) if a > exact || b > exact => NumericsClaim::Approx {
1865 reason: format!("operands exceed the exact integer range of {p:?}"),
1866 },
1867 (Some(_), Some(_), _) => NumericsClaim::Approx {
1868 reason: "partial sums exceed fp32's 24-bit mantissa".into(),
1869 },
1870 _ => NumericsClaim::Approx {
1871 reason: "operand range unknown (external data)".into(),
1872 },
1873 };
1874 return Ok((p, AccPolicy::F32, Precision::Fp32, claim));
1875 }
1876 Err(SpatialReject::MissingCompute {
1877 needed: "tile matmul",
1878 target: target.name,
1879 })
1880}
1881
1882pub fn from_matmul(
1884 spec: &MatmulSpec,
1885 names: &MatmulNames,
1886 fills: (&Fill, &Fill),
1887 ranges: (Option<(i64, i64)>, Option<(i64, i64)>),
1888 numerics: NumericsPolicy,
1889 target: &SpatialTarget,
1890) -> Result<SpatialProgram, SpatialReject> {
1891 let tile = target.tile;
1892 let init_a = init_of(fills.0, spec.m * spec.k);
1893 let init_b = init_of(fills.1, spec.k * spec.n);
1894 let (operand, acc, mut out_dtype, mut claim) = choose_matmul_numerics_ranged(
1895 target,
1896 ranges.0.or(init_a.range()),
1897 ranges.1.or(init_b.range()),
1898 spec.k,
1899 )?;
1900 if numerics == NumericsPolicy::AllowSpillRounding && acc == AccPolicy::F32 {
1902 out_dtype = operand;
1903 claim = NumericsClaim::Approx {
1904 reason: "spill and output rounding to the operand width accepted".into(),
1905 };
1906 }
1907 let dataflow =
1908 target
1909 .matmul_dataflows()
1910 .first()
1911 .copied()
1912 .ok_or(SpatialReject::MissingCompute {
1913 needed: "tile matmul",
1914 target: target.name,
1915 })?;
1916 let local = Handshake::Fifo;
1917
1918 let grid = |rows: usize, cols: usize| TileGrid {
1919 rows_t: rows.div_ceil(tile.rows),
1920 cols_t: cols.div_ceil(tile.cols),
1921 };
1922 let tensors = vec![
1923 TensorDecl {
1924 id: TensorId(0),
1925 name: names.a.clone(),
1926 role: TensorRole::Input,
1927 elems: ElemShape {
1928 rows: spec.m,
1929 cols: spec.k,
1930 },
1931 tiles: grid(spec.m, spec.k),
1932 dtype: operand,
1933 layout: Layout::RowMajorTiles,
1934 space: TensorSpace::Dram,
1935 init: init_a,
1936 range: ranges.0,
1937 chip: None,
1938 },
1939 TensorDecl {
1940 id: TensorId(1),
1941 name: names.b.clone(),
1942 role: TensorRole::Param,
1943 elems: ElemShape {
1944 rows: spec.k,
1945 cols: spec.n,
1946 },
1947 tiles: grid(spec.k, spec.n),
1948 dtype: operand,
1949 layout: Layout::RowMajorTiles,
1950 space: TensorSpace::Dram,
1951 init: init_b,
1952 range: ranges.1,
1953 chip: None,
1954 },
1955 TensorDecl {
1956 id: TensorId(2),
1957 name: names.c.clone(),
1958 role: TensorRole::Output,
1959 elems: ElemShape {
1960 rows: spec.m,
1961 cols: spec.n,
1962 },
1963 tiles: grid(spec.m, spec.n),
1964 dtype: out_dtype,
1965 layout: Layout::RowMajorTiles,
1966 space: TensorSpace::Dram,
1967 init: TensorInit::Zero,
1968 range: None,
1969 chip: None,
1970 },
1971 ];
1972 let (mt, nt, kt) = (
1973 tensors[2].tiles.rows_t,
1974 tensors[2].tiles.cols_t,
1975 tensors[0].tiles.cols_t,
1976 );
1977
1978 let buffers = vec![
1979 Buffer {
1980 id: BufferId(0),
1981 name: "in0".into(),
1982 kind: BufferKind::Fifo {
1983 depth_pages: 2,
1984 page_tiles: 1,
1985 },
1986 dtype: operand,
1987 space: MemSpace::Local,
1988 site: None,
1989 origin: None,
1990 alias_of: None,
1991 },
1992 Buffer {
1993 id: BufferId(1),
1994 name: "in1".into(),
1995 kind: BufferKind::Fifo {
1996 depth_pages: 2,
1997 page_tiles: 1,
1998 },
1999 dtype: operand,
2000 space: MemSpace::Local,
2001 site: None,
2002 origin: None,
2003 alias_of: None,
2004 },
2005 Buffer {
2006 id: BufferId(2),
2007 name: "out".into(),
2008 kind: BufferKind::Fifo {
2009 depth_pages: 2,
2010 page_tiles: 1,
2011 },
2012 dtype: out_dtype,
2013 space: MemSpace::Local,
2014 site: None,
2015 origin: None,
2016 alias_of: None,
2017 },
2018 ];
2019
2020 let stages = vec![
2021 Stage {
2022 id: StageId(0),
2023 name: "load_a".into(),
2024 kind: StageKind::Load {
2025 tensor: TensorId(0),
2026 pattern: AccessPattern {
2027 origin: TileAddr {
2028 base: 0,
2029 steps: vec![(LV_ITEM_Y, kt), (LV_KBLOCK, 1)],
2030 indirect: None,
2031 },
2032 block_h: 1,
2033 block_w: 1,
2034 stride_w: 1,
2035 stride_h: kt,
2036 },
2037 num_blocks: kt,
2038 multicast: None,
2039 },
2040 role: SiteRole::Movement,
2041 ins: vec![],
2042 outs: vec![BufferId(0)],
2043 site: None,
2044 item: None,
2045 origin: None,
2046 },
2047 Stage {
2048 id: StageId(1),
2049 name: "load_b".into(),
2050 kind: StageKind::Load {
2051 tensor: TensorId(1),
2052 pattern: AccessPattern {
2053 origin: TileAddr {
2054 base: 0,
2055 steps: vec![(LV_ITEM_X, 1), (LV_KBLOCK, nt)],
2056 indirect: None,
2057 },
2058 block_h: 1,
2059 block_w: 1,
2060 stride_w: 1,
2061 stride_h: nt,
2062 },
2063 num_blocks: kt,
2064 multicast: None,
2065 },
2066 role: SiteRole::Movement,
2067 ins: vec![],
2068 outs: vec![BufferId(1)],
2069 site: None,
2070 item: None,
2071 origin: None,
2072 },
2073 Stage {
2074 id: StageId(2),
2075 name: "mm".into(),
2076 kind: StageKind::Compute(ComputeOp::MatmulTiles {
2077 block: BlockShape {
2078 osh: 1,
2079 osw: 1,
2080 ibw: 1,
2081 },
2082 sub_blocks: (1, 1),
2083 num_blocks: kt,
2084 operand,
2085 acc,
2086 dataflow,
2087 interm: None,
2088 granularity: Granularity::PerTile,
2089 }),
2090 role: SiteRole::Compute,
2091 ins: vec![BufferId(0), BufferId(1)],
2092 outs: vec![BufferId(2)],
2093 site: None,
2094 item: None,
2095 origin: None,
2096 },
2097 Stage {
2098 id: StageId(3),
2099 name: "store_c".into(),
2100 kind: StageKind::Store {
2101 tensor: TensorId(2),
2102 pattern: AccessPattern {
2103 origin: TileAddr {
2104 base: 0,
2105 steps: vec![(LV_ITEM_Y, nt), (LV_ITEM_X, 1)],
2106 indirect: None,
2107 },
2108 block_h: 1,
2109 block_w: 1,
2110 stride_w: 1,
2111 stride_h: nt,
2112 },
2113 sub_blocks: (1, 1),
2114 },
2115 role: SiteRole::Movement,
2116 ins: vec![BufferId(2)],
2117 outs: vec![],
2118 site: None,
2119 item: None,
2120 origin: None,
2121 },
2122 ];
2123 let edges = vec![
2124 Edge {
2125 id: EdgeId(0),
2126 from: StageId(0),
2127 buffer: BufferId(0),
2128 to: vec![StageId(2)],
2129 handshake: local.clone(),
2130 },
2131 Edge {
2132 id: EdgeId(1),
2133 from: StageId(1),
2134 buffer: BufferId(1),
2135 to: vec![StageId(2)],
2136 handshake: local.clone(),
2137 },
2138 Edge {
2139 id: EdgeId(2),
2140 from: StageId(2),
2141 buffer: BufferId(2),
2142 to: vec![StageId(3)],
2143 handshake: local.clone(),
2144 },
2145 ];
2146 let mut items = Vec::with_capacity(mt * nt);
2147 for oy in 0..mt {
2148 for ox in 0..nt {
2149 items.push(WorkItem {
2150 id: WorkItemId::new(items.len()),
2151 coord: (oy, ox),
2152 out: TileRect {
2153 row0_t: oy,
2154 col0_t: ox,
2155 h_t: 1,
2156 w_t: 1,
2157 },
2158 });
2159 }
2160 }
2161 let mut requires = Requirements::default();
2162 requires.handshakes.insert(local.kind());
2163 let p = SpatialProgram {
2164 name: format!("matmul_{}x{}x{}", spec.m, spec.n, spec.k),
2165 tile,
2166 tensors,
2167 stages,
2168 buffers,
2169 edges,
2170 semaphores: Vec::new(),
2171 sections: Vec::new(),
2172 trips: Vec::new(),
2173 links: Vec::new(),
2174 work: WorkList {
2175 items,
2176 k_tiles: kt,
2177 plan: None,
2178 k_trip: None,
2179 },
2180 numerics,
2181 claim,
2182 acc_tiles_used: target.effective_acc_tiles(acc, numerics == NumericsPolicy::Exact),
2183 mem_banks: target.site.mem_banks,
2184 requires,
2185 placement: None,
2186 memory: None,
2187 schedule: None,
2188 certificate: None,
2189 };
2190 p.validate()?;
2191 Ok(p)
2192}
2193
2194pub fn operand_bits(p: Precision) -> Option<u32> {
2196 precision_int_bits(p)
2197}
2198
2199pub fn local_handshake(target: &SpatialTarget) -> Result<Handshake, SpatialReject> {
2204 use crate::spatial::target::HandshakeKind;
2205 if target.provides_handshake(HandshakeKind::Fifo) {
2206 Ok(Handshake::Fifo)
2207 } else if target.provides_handshake(HandshakeKind::ValidReady) {
2208 Ok(Handshake::ValidReady)
2209 } else if target.provides_handshake(HandshakeKind::Token) {
2210 Ok(Handshake::Token)
2211 } else {
2212 Err(SpatialReject::MissingHandshake {
2213 needed: HandshakeKind::Fifo,
2214 target: target.name,
2215 })
2216 }
2217}
2218
2219pub fn from_systolic(
2222 sys: &crate::codegen_sva::systolic::Systolic,
2223 m: usize,
2224 target: &SpatialTarget,
2225) -> Result<SpatialProgram, SpatialReject> {
2226 let spec = MatmulSpec {
2227 m,
2228 n: sys.n,
2229 k: sys.k,
2230 a: Default::default(),
2231 b: Default::default(),
2232 c: Default::default(),
2233 };
2234 let names = MatmulNames {
2235 a: "a".into(),
2236 b: "w".into(),
2237 c: "o".into(),
2238 };
2239 let weights = sys.weights.iter().fold(None, |h: Option<(i64, i64)>, w| Some(h.map_or((*w, *w), |(lo, hi)| (lo.min(*w), hi.max(*w)))));
2240 let mut p = from_matmul(
2241 &spec,
2242 &names,
2243 (&Fill::Unknown, &Fill::Unknown),
2244 (Some((-128, 127)), Some(weights.unwrap_or((0, 0)))),
2245 NumericsPolicy::Exact,
2246 target,
2247 )?;
2248 p.tensors[1].init = TensorInit::Values(sys.weights.clone());
2249 use crate::spatial::target::DataflowPolicy;
2250 let offered = target.matmul_dataflows();
2251 let policy = if offered.contains(&DataflowPolicy::WeightStationary) {
2252 DataflowPolicy::WeightStationary
2253 } else if offered.contains(&DataflowPolicy::OutputStationary) {
2254 DataflowPolicy::OutputStationary
2255 } else {
2256 return Err(SpatialReject::MissingCompute {
2257 needed: "a systolic dataflow (weight- or output-stationary)",
2258 target: target.name,
2259 });
2260 };
2261 for s in p.stages.iter_mut() {
2262 if let StageKind::Compute(ComputeOp::MatmulTiles { dataflow, .. }) = &mut s.kind {
2263 *dataflow = policy;
2264 }
2265 }
2266 p.name = format!("systolic_{}x{}_m{m}", sys.k, sys.n);
2267 p.validate()?;
2268 Ok(p)
2269}
2270
2271pub fn from_fused_block(
2274 fb: &crate::codegen_sva::systolic::FusedBlock,
2275 m: usize,
2276 target: &SpatialTarget,
2277) -> Result<SpatialProgram, SpatialReject> {
2278 let window = target.scalar_target().map(|t| t.fusion_window).unwrap_or(1);
2279 if window < 4 {
2280 return Err(SpatialReject::MissingCompute {
2281 needed: "fusion window of 4 (matmul→requant→bias→relu)",
2282 target: target.name,
2283 });
2284 }
2285 let mut p = from_systolic(&fb.sys, m, target)?;
2286 let local = Handshake::Fifo;
2287 let out_dtype = p.tensors[2].dtype;
2288 let bias_id = TensorId::new(p.tensors.len());
2290 let n = fb.sys.n;
2291 p.tensors.push(TensorDecl {
2292 id: bias_id,
2293 name: "bias".into(),
2294 role: TensorRole::Param,
2295 elems: ElemShape { rows: 1, cols: n },
2296 tiles: TileGrid {
2297 rows_t: 1,
2298 cols_t: n.div_ceil(p.tile.cols),
2299 },
2300 dtype: out_dtype,
2301 layout: Layout::RowMajorTiles,
2302 space: TensorSpace::Dram,
2303 init: TensorInit::Values(fb.bias.clone()),
2304 range: fb.bias.iter().fold(None, |h: Option<(i64, i64)>, b| Some(h.map_or((*b, *b), |(lo, hi)| (lo.min(*b), hi.max(*b))))),
2305 chip: None,
2306 });
2307 let store = p
2309 .stages
2310 .iter()
2311 .position(|s| matches!(s.kind, StageKind::Store { .. }))
2312 .unwrap();
2313 let mm_out = p
2314 .stages
2315 .iter()
2316 .find(|s| matches!(s.kind, StageKind::Compute(ComputeOp::MatmulTiles { .. })))
2317 .unwrap()
2318 .outs[0];
2319 let new_buf = |p: &mut SpatialProgram, name: &str| {
2320 let id = BufferId::new(p.buffers.len());
2321 p.buffers.push(Buffer {
2322 id,
2323 name: name.into(),
2324 kind: BufferKind::Fifo {
2325 depth_pages: 2,
2326 page_tiles: 1,
2327 },
2328 dtype: out_dtype,
2329 space: MemSpace::Local,
2330 site: None,
2331 origin: None,
2332 alias_of: None,
2333 });
2334 id
2335 };
2336 let rq_out = new_buf(&mut p, "rq");
2337 let bias_in = new_buf(&mut p, "bias_in");
2338 let bias_out = new_buf(&mut p, "biased");
2339 let relu_out = new_buf(&mut p, "relu");
2340 let mm_id = p
2341 .stages
2342 .iter()
2343 .find(|s| matches!(s.kind, StageKind::Compute(ComputeOp::MatmulTiles { .. })))
2344 .unwrap()
2345 .id;
2346 let nt = p.tensors[2].tiles.cols_t;
2347 let push_stage = |p: &mut SpatialProgram,
2348 name: &str,
2349 kind: StageKind,
2350 role: SiteRole,
2351 ins: Vec<BufferId>,
2352 outs: Vec<BufferId>|
2353 -> StageId {
2354 let id = StageId::new(p.stages.len());
2355 p.stages.push(Stage {
2356 id,
2357 name: name.into(),
2358 kind,
2359 role,
2360 ins,
2361 outs,
2362 site: None,
2363 item: None,
2364 origin: None,
2365 });
2366 id
2367 };
2368 let rq = push_stage(
2369 &mut p,
2370 "requant",
2371 StageKind::Compute(ComputeOp::Requant {
2372 m: fb.requant_m,
2373 s: fb.requant_s,
2374 }),
2375 SiteRole::Compute,
2376 vec![mm_out],
2377 vec![rq_out],
2378 );
2379 let load_bias = push_stage(
2380 &mut p,
2381 "load_bias",
2382 StageKind::Load {
2383 tensor: bias_id,
2384 pattern: AccessPattern {
2385 origin: TileAddr {
2386 base: 0,
2387 steps: vec![(LV_ITEM_X, 1)],
2388 indirect: None,
2389 },
2390 block_h: 1,
2391 block_w: 1,
2392 stride_w: 1,
2393 stride_h: nt,
2394 },
2395 num_blocks: 1,
2396 multicast: None,
2397 },
2398 SiteRole::Movement,
2399 vec![],
2400 vec![bias_in],
2401 );
2402 let bias = push_stage(
2403 &mut p,
2404 "bias",
2405 StageKind::Compute(ComputeOp::Bias { bias: bias_in }),
2406 SiteRole::Compute,
2407 vec![rq_out, bias_in],
2408 vec![bias_out],
2409 );
2410 let relu = push_stage(
2411 &mut p,
2412 "relu",
2413 StageKind::Compute(ComputeOp::Relu),
2414 SiteRole::Compute,
2415 vec![bias_out],
2416 vec![relu_out],
2417 );
2418 let store_id = p.stages[store].id;
2420 p.stages[store].ins = vec![relu_out];
2421 for e in p.edges.iter_mut() {
2422 if e.from == mm_id {
2423 e.to = vec![rq];
2424 }
2425 }
2426 let add_edge = |p: &mut SpatialProgram, from: StageId, buffer: BufferId, to: StageId| {
2427 let id = EdgeId::new(p.edges.len());
2428 p.edges.push(Edge {
2429 id,
2430 from,
2431 buffer,
2432 to: vec![to],
2433 handshake: local.clone(),
2434 });
2435 };
2436 add_edge(&mut p, rq, rq_out, bias);
2437 add_edge(&mut p, load_bias, bias_in, bias);
2438 add_edge(&mut p, bias, bias_out, relu);
2439 add_edge(&mut p, relu, relu_out, store_id);
2440 p.name = format!("fused_block_{}x{}_m{m}", fb.sys.k, fb.sys.n);
2441 p.validate()?;
2442 Ok(p)
2443}
2444
2445pub fn from_scalar_body(
2448 df: &crate::codegen_sva::fabric_rtl::DecisionFabric,
2449 n: usize,
2450 target: &SpatialTarget,
2451) -> Result<SpatialProgram, SpatialReject> {
2452 if target.scalar_target().is_none() {
2453 return Err(SpatialReject::MissingCompute {
2454 needed: "scalar datapath",
2455 target: target.name,
2456 });
2457 }
2458 if df.n_in != 1 {
2459 return Err(SpatialReject::NotLowered("multi-input scalar bodies"));
2460 }
2461 let local = Handshake::Fifo;
2462 let tile = target.tile;
2463 let grid = |rows: usize, cols: usize| TileGrid {
2464 rows_t: rows.div_ceil(tile.rows),
2465 cols_t: cols.div_ceil(tile.cols),
2466 };
2467 let tensors = vec![
2468 TensorDecl {
2469 id: TensorId(0),
2470 name: "x".into(),
2471 role: TensorRole::Input,
2472 elems: ElemShape { rows: 1, cols: n },
2473 tiles: grid(1, n),
2474 dtype: Precision::Int64,
2475 layout: Layout::RowMajorTiles,
2476 space: TensorSpace::Dram,
2477 init: TensorInit::External,
2478 range: None,
2479 chip: None,
2480 },
2481 TensorDecl {
2482 id: TensorId(1),
2483 name: "y".into(),
2484 role: TensorRole::Output,
2485 elems: ElemShape { rows: 1, cols: n },
2486 tiles: grid(1, n),
2487 dtype: Precision::Int64,
2488 layout: Layout::RowMajorTiles,
2489 space: TensorSpace::Dram,
2490 init: TensorInit::Zero,
2491 range: None,
2492 chip: None,
2493 },
2494 ];
2495 let nt = tensors[1].tiles.cols_t;
2496 let buffers = vec![
2497 Buffer {
2498 id: BufferId(0),
2499 name: "in0".into(),
2500 kind: BufferKind::Fifo {
2501 depth_pages: 2,
2502 page_tiles: 1,
2503 },
2504 dtype: Precision::Int64,
2505 space: MemSpace::Local,
2506 site: None,
2507 origin: None,
2508 alias_of: None,
2509 },
2510 Buffer {
2511 id: BufferId(1),
2512 name: "out".into(),
2513 kind: BufferKind::Fifo {
2514 depth_pages: 2,
2515 page_tiles: 1,
2516 },
2517 dtype: Precision::Int64,
2518 space: MemSpace::Local,
2519 site: None,
2520 origin: None,
2521 alias_of: None,
2522 },
2523 ];
2524 let stages = vec![
2525 Stage {
2526 id: StageId(0),
2527 name: "load_x".into(),
2528 kind: StageKind::Load {
2529 tensor: TensorId(0),
2530 pattern: AccessPattern {
2531 origin: TileAddr {
2532 base: 0,
2533 steps: vec![(LV_ITEM_Y, nt), (LV_ITEM_X, 1)],
2534 indirect: None,
2535 },
2536 block_h: 1,
2537 block_w: 1,
2538 stride_w: 1,
2539 stride_h: nt,
2540 },
2541 num_blocks: 1,
2542 multicast: None,
2543 },
2544 role: SiteRole::Movement,
2545 ins: vec![],
2546 outs: vec![BufferId(0)],
2547 site: None,
2548 item: None,
2549 origin: None,
2550 },
2551 Stage {
2552 id: StageId(1),
2553 name: "body".into(),
2554 kind: StageKind::Scalar(ScalarBody {
2555 n_in: df.n_in,
2556 cells: df.cells.clone(),
2557 result: df.result,
2558 }),
2559 role: SiteRole::Compute,
2560 ins: vec![BufferId(0)],
2561 outs: vec![BufferId(1)],
2562 site: None,
2563 item: None,
2564 origin: None,
2565 },
2566 Stage {
2567 id: StageId(2),
2568 name: "store_y".into(),
2569 kind: StageKind::Store {
2570 tensor: TensorId(1),
2571 pattern: AccessPattern {
2572 origin: TileAddr {
2573 base: 0,
2574 steps: vec![(LV_ITEM_Y, nt), (LV_ITEM_X, 1)],
2575 indirect: None,
2576 },
2577 block_h: 1,
2578 block_w: 1,
2579 stride_w: 1,
2580 stride_h: nt,
2581 },
2582 sub_blocks: (1, 1),
2583 },
2584 role: SiteRole::Movement,
2585 ins: vec![BufferId(1)],
2586 outs: vec![],
2587 site: None,
2588 item: None,
2589 origin: None,
2590 },
2591 ];
2592 let edges = vec![
2593 Edge {
2594 id: EdgeId(0),
2595 from: StageId(0),
2596 buffer: BufferId(0),
2597 to: vec![StageId(1)],
2598 handshake: local.clone(),
2599 },
2600 Edge {
2601 id: EdgeId(1),
2602 from: StageId(1),
2603 buffer: BufferId(1),
2604 to: vec![StageId(2)],
2605 handshake: local.clone(),
2606 },
2607 ];
2608 let mut items = Vec::new();
2609 for ox in 0..nt {
2610 items.push(WorkItem {
2611 id: WorkItemId::new(items.len()),
2612 coord: (0, ox),
2613 out: TileRect {
2614 row0_t: 0,
2615 col0_t: ox,
2616 h_t: 1,
2617 w_t: 1,
2618 },
2619 });
2620 }
2621 let mut requires = Requirements::default();
2622 requires.handshakes.insert(local.kind());
2623 let p = SpatialProgram {
2624 name: "scalar_body".into(),
2625 tile,
2626 tensors,
2627 stages,
2628 buffers,
2629 edges,
2630 semaphores: Vec::new(),
2631 sections: Vec::new(),
2632 trips: Vec::new(),
2633 links: Vec::new(),
2634 work: WorkList {
2635 items,
2636 k_tiles: 1,
2637 plan: None,
2638 k_trip: None,
2639 },
2640 numerics: NumericsPolicy::Exact,
2641 claim: NumericsClaim::Exact,
2642 acc_tiles_used: target.site.acc_tiles.max(1),
2643 mem_banks: target.site.mem_banks,
2644 requires,
2645 placement: None,
2646 memory: None,
2647 schedule: None,
2648 certificate: None,
2649 };
2650 p.validate()?;
2651 Ok(p)
2652}