1use serde::Serialize;
20
21use super::rdu_chip::{Fact, Provenance};
22use crate::codegen_sva::fabric_target::Precision;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
26pub enum MemLevel {
27 Dram,
29 Noc,
31 Local,
33}
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
39pub enum StreamMix {
40 Any,
41 Read,
42 Write,
43 Copy,
44 Triad,
45}
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
49pub enum Scope {
50 Chip,
52 Site,
54 Link,
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
60pub enum Engine {
61 Matrix,
63 Vector,
65}
66
67#[derive(
70 Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, serde::Deserialize,
71)]
72pub enum Transfer {
73 Bytes,
74 Elements,
75}
76
77#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
79pub enum Rate {
80 PerSecond(f64),
81 PerCycle(f64),
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
87pub struct MemRoof {
88 pub level: MemLevel,
89 pub mix: StreamMix,
90 pub scope: Scope,
91 pub unit: Transfer,
92 pub rate: Fact<Rate>,
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
100pub struct ComputeRoof {
101 pub engine: Engine,
102 pub precision: Precision,
103 pub scope: Scope,
104 pub ops: Fact<Rate>,
105}
106
107#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
109pub struct Converted {
110 pub value: f64,
111 pub provenance: Provenance,
112}
113
114#[derive(Clone, Debug, PartialEq, Serialize)]
116pub struct Roofs {
117 pub clock_ghz: Fact<f64>,
119 pub mem: Vec<MemRoof>,
120 pub compute: Vec<ComputeRoof>,
121 pub sources: &'static [&'static str],
123}
124
125impl Roofs {
126 pub fn mem(&self, level: MemLevel, mix: StreamMix) -> Option<&MemRoof> {
129 let at = |m: StreamMix| self.mem.iter().find(|r| r.level == level && r.mix == m);
130 at(mix).or_else(|| at(StreamMix::Any))
131 }
132
133 pub fn compute(&self, engine: Engine, precision: Precision) -> Option<&ComputeRoof> {
135 self.compute
136 .iter()
137 .find(|r| r.engine == engine && r.precision == precision)
138 }
139
140 pub fn per_cycle(&self, rate: &Fact<Rate>) -> Converted {
142 match rate.value {
143 Rate::PerCycle(v) => Converted {
144 value: v,
145 provenance: rate.provenance,
146 },
147 Rate::PerSecond(v) => Converted {
148 value: v / (self.clock_ghz.value * 1e9),
149 provenance: rate.provenance.max(self.clock_ghz.provenance),
150 },
151 }
152 }
153
154 pub fn per_second(&self, rate: &Fact<Rate>) -> Converted {
156 match rate.value {
157 Rate::PerSecond(v) => Converted {
158 value: v,
159 provenance: rate.provenance,
160 },
161 Rate::PerCycle(v) => Converted {
162 value: v * self.clock_ghz.value * 1e9,
163 provenance: rate.provenance.max(self.clock_ghz.provenance),
164 },
165 }
166 }
167
168 pub fn facts(&self) -> Vec<(String, Provenance, &'static str)> {
170 let mut out = vec![(
171 "clock_ghz".to_string(),
172 self.clock_ghz.provenance,
173 self.clock_ghz.cite,
174 )];
175 for r in &self.mem {
176 out.push((
177 format!("mem.{:?}.{:?}.{:?}", r.level, r.mix, r.scope),
178 r.rate.provenance,
179 r.rate.cite,
180 ));
181 }
182 for r in &self.compute {
183 out.push((
184 format!("compute.{:?}.{:?}.{:?}", r.engine, r.precision, r.scope),
185 r.ops.provenance,
186 r.ops.cite,
187 ));
188 }
189 out
190 }
191}
192
193const TT_WORMHOLE_SPECS: &str =
196 "docs.tenstorrent.com/docs-test/core/latest/aibs/wormhole/specifications.html";
197const TT_BLACKHOLE_SPECS: &str = "docs.tenstorrent.com/aibs/blackhole/index.html";
198const TT_GEMM_FLOPS: &str =
199 "github.com/tenstorrent/tt-metal/blob/main/tech_reports/GEMM_FLOPS/GEMM_FLOPS.md";
200const TT_WORMHOLE_NOC: &str =
201 "github.com/tenstorrent/tt-isa-documentation/blob/main/WormholeB0/NoC/README.md";
202const TT_BLACKHOLE_NOC: &str =
203 "github.com/tenstorrent/tt-isa-documentation/blob/main/BlackholeA0/NoC/README.md";
204const NV_RTX3060_SPECS: &str = "nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3060-3060ti";
205const NV_GA102_WHITEPAPER: &str =
206 "nvidia.com/content/PDF/nvidia-ampere-ga-102-gpu-architecture-whitepaper-v2.pdf";
207pub(crate) const NV_H100_PAGE: &str = "nvidia.com/en-us/data-center/h100";
208pub(crate) const NV_H100_NVL_BRIEF: &str =
209 "nvidia.com/content/dam/en-zz/Solutions/Data-Center/h100/PB-11773-001_v01.pdf";
210pub(crate) const NV_A100_PAGE: &str = "nvidia.com/en-us/data-center/a100";
211pub(crate) const NV_DGX_B200_PAGE: &str = "nvidia.com/en-us/data-center/dgx-b200";
212pub(crate) const NV_HGX_PAGE: &str = "nvidia.com/en-us/data-center/hgx";
213const FABRIC_TWIN: &str = "spatial::lower::fabric::fabric_cycle_model, cycle-exact against the RTL by spatial_fabric_lowering_lock";
214const SKY130_SYSTOLIC_SIGNOFF: &str = "benchmarks/silicon/systolic_pipelined_signoff.json";
215const DIGILENT_NEXYS_CLOCK: &str = "digilent.com/reference/programmable-logic/nexys-a7/reference-manual and \
216 digilent.com/reference/programmable-logic/nexys-video/reference-manual: a 100 MHz crystal oscillator on the FPGA's \
217 clock-capable input";
218
219const TENSIX_MATRIX: &str = "github.com/tenstorrent/tt-metal/blob/main/tech_reports/GEMM_FLOPS/GEMM_FLOPS.md: \
221 \"The matrix engine performs 8x16 x 16x16 = 8x16 in a single cycle. This is 2*8*16*16 = 4096 multiply-adds in a \
222 single cycle\" (4096 operations: 2048 multiply-adds); \"LoFi ~4 TFLOPS, HiFi2 ~2 TFLOPS, HiFi4 ~1 TFLOPS\" per engine \
223 at 1 GHz, so one pass per cycle";
224
225fn dense_from_sparse(rows: &[(Precision, f64, &'static str)]) -> Vec<ComputeRoof> {
227 rows.iter()
228 .map(|&(precision, sparse, cite)| ComputeRoof {
229 engine: Engine::Matrix,
230 precision,
231 scope: Scope::Chip,
232 ops: Fact::derived(Rate::PerSecond(sparse / 2.0), cite),
233 })
234 .collect()
235}
236
237fn tensix_matrix(precisions: &[Precision]) -> Vec<ComputeRoof> {
238 precisions
239 .iter()
240 .map(|&precision| ComputeRoof {
241 engine: Engine::Matrix,
242 precision,
243 scope: Scope::Site,
244 ops: Fact::published(Rate::PerCycle(4096.0), TENSIX_MATRIX),
245 })
246 .collect()
247}
248
249impl Roofs {
250 pub fn wormhole() -> Roofs {
252 Roofs {
253 clock_ghz: Fact::published(1.0, "docs.tenstorrent.com/docs-test/core/latest/aibs/wormhole/specifications.html: n150s \"AI Clock: 1 GHz\""),
254 mem: vec![
255 MemRoof {
256 level: MemLevel::Dram,
257 mix: StreamMix::Any,
258 scope: Scope::Chip,
259 unit: Transfer::Bytes,
260 rate: Fact::published(
261 Rate::PerSecond(288e9),
262 "docs.tenstorrent.com/docs-test/core/latest/aibs/wormhole/specifications.html: n150s \"Memory: 12GB GDDR6\", \
263 \"Memory Speed: 12 GT/sec\", \"Memory Bandwidth: 288 GB/sec\"",
264 ),
265 },
266 MemRoof {
267 level: MemLevel::Noc,
268 mix: StreamMix::Any,
269 scope: Scope::Link,
270 unit: Transfer::Bytes,
271 rate: Fact::published(
272 Rate::PerCycle(32.0),
273 "github.com/tenstorrent/tt-isa-documentation/blob/main/WormholeB0/NoC/README.md: \"Each flit consists of exactly \
274 256 bits (32 bytes)\", \"One flit (256 bits) per cycle\"",
275 ),
276 },
277 ],
278 compute: tensix_matrix(&[Precision::Bf16, Precision::Fp16]),
279 sources: &[TT_WORMHOLE_SPECS, TT_GEMM_FLOPS, TT_WORMHOLE_NOC],
280 }
281 }
282
283 pub fn blackhole() -> Roofs {
285 Roofs {
286 clock_ghz: Fact::published(1.35, "docs.tenstorrent.com/aibs/blackhole/index.html: p150a/p150b \"AI Clock: Up to 1.35 GHz\""),
287 mem: vec![
288 MemRoof {
289 level: MemLevel::Dram,
290 mix: StreamMix::Any,
291 scope: Scope::Chip,
292 unit: Transfer::Bytes,
293 rate: Fact::published(
294 Rate::PerSecond(512e9),
295 "docs.tenstorrent.com/aibs/blackhole/index.html: p150a/p150b \"32 GB GDDR6\", \"Memory Speed: 16 GT/sec\", \
296 \"Memory Bandwidth: 512 GB/sec\"",
297 ),
298 },
299 MemRoof {
300 level: MemLevel::Noc,
301 mix: StreamMix::Any,
302 scope: Scope::Link,
303 unit: Transfer::Bytes,
304 rate: Fact::published(
305 Rate::PerCycle(64.0),
306 "github.com/tenstorrent/tt-isa-documentation/blob/main/BlackholeA0/NoC/README.md: \"Each flit consists of exactly \
307 512 bits (64 bytes)\", \"One flit (512 bits) per cycle per axis\"",
308 ),
309 },
310 ],
311 compute: tensix_matrix(&[Precision::Bf16, Precision::Fp16]),
312 sources: &[TT_BLACKHOLE_SPECS, TT_GEMM_FLOPS, TT_BLACKHOLE_NOC],
313 }
314 }
315
316 pub fn rtx3060() -> Roofs {
320 Roofs {
321 clock_ghz: Fact::published(1.78, "nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3060-3060ti: RTX 3060 \"Boost Clock: 1.78 GHz\""),
322 mem: vec![
323 MemRoof {
324 level: MemLevel::Dram,
325 mix: StreamMix::Any,
326 scope: Scope::Chip,
327 unit: Transfer::Bytes,
328 rate: Fact::modeled(
329 Rate::PerSecond(360e9),
330 "not published by NVIDIA: its RTX 3060 table gives \"12 GB GDDR6\" on a \"192-bit\" interface and no data \
331 rate; the board partners' 15 Gbps GDDR6 gives 192 x 15 / 8 = 360 GB/s",
332 ),
333 },
334 MemRoof {
335 level: MemLevel::Local,
336 mix: StreamMix::Any,
337 scope: Scope::Chip,
338 unit: Transfer::Bytes,
339 rate: Fact::derived(
340 Rate::PerCycle(28.0 * 128.0),
341 "nvidia.com/content/PDF/nvidia-ampere-ga-102-gpu-architecture-whitepaper-v2.pdf: GA10x shared memory \
342 \"128 bytes/clock per SM\"; nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3060-3060ti: \"CUDA Cores: \
343 3584\" at the whitepaper's \"CUDA Cores / SM 128\" = 28 SMs; 28 x 128 B per cycle",
344 ),
345 },
346 ],
347 compute: [Precision::Bf16, Precision::Fp16]
348 .into_iter()
349 .map(|precision| ComputeRoof {
350 engine: Engine::Matrix,
351 precision,
352 scope: Scope::Chip,
353 ops: Fact::derived(
354 Rate::PerCycle(28.0 * 512.0),
355 "nvidia.com/content/PDF/nvidia-ampere-ga-102-gpu-architecture-whitepaper-v2.pdf: GA10x \"Total FP16 FMA \
356 operations per SM Dense: 512\", 1024 operations per SM per cycle with FP16 accumulate and half that with FP32 \
357 accumulate (RTX 3080: \"Peak FP16 Tensor TFLOPS with FP16 Accumulate 119\", \"with FP32 Accumulate 59.5\"); \
358 nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3060-3060ti: \"CUDA Cores: 3584\" = 28 SMs at the \
359 whitepaper's \"CUDA Cores / SM 128\"; 28 x 512 at FP32 accumulate",
360 ),
361 })
362 .chain(std::iter::once(ComputeRoof {
363 engine: Engine::Matrix,
364 precision: Precision::Int8,
365 scope: Scope::Chip,
366 ops: Fact::derived(
367 Rate::PerCycle(28.0 * 2048.0),
368 "nvidia.com/content/PDF/nvidia-ampere-ga-102-gpu-architecture-whitepaper-v2.pdf: INT8 tensor at twice the \
369 FP16-accumulate rate (RTX 3080: \"Peak INT8 Tensor TOPS 238\" against FP16 119, 68 SMs at 1710 MHz = 2048 \
370 per SM per cycle); nvidia.com/en-us/geforce/graphics-cards/30-series/rtx-3060-3060ti: \"CUDA Cores: 3584\" = \
371 28 SMs; 28 x 2048",
372 ),
373 }))
374 .collect(),
375 sources: &[NV_RTX3060_SPECS, NV_GA102_WHITEPAPER],
376 }
377 }
378
379 pub fn h100_sxm() -> Roofs {
383 Roofs {
384 clock_ghz: Fact::modeled(
385 1.98,
386 "not published on NVIDIA's H100 product page; inherited from the third-party spec databases' 1980 MHz \
387 boost — every rate below is per second, so no rate is converted through it",
388 ),
389 mem: vec![MemRoof {
390 level: MemLevel::Dram,
391 mix: StreamMix::Any,
392 scope: Scope::Chip,
393 unit: Transfer::Bytes,
394 rate: Fact::published(
395 Rate::PerSecond(3.35e12),
396 "nvidia.com/en-us/data-center/h100: H100 SXM \"GPU Memory Bandwidth 3.35TB/s\"",
397 ),
398 }],
399 compute: dense_from_sparse(&[
400 (Precision::Int8, 3958e12, "nvidia.com/en-us/data-center/h100: H100 SXM \"INT8 Tensor Core* 3,958 TOPS\", \"* With sparsity\"; dense is half"),
401 (Precision::Fp8, 3958e12, "nvidia.com/en-us/data-center/h100: H100 SXM \"FP8 Tensor Core* 3,958 teraFLOPS\", \"* With sparsity\"; dense is half"),
402 (Precision::Bf16, 1979e12, "nvidia.com/en-us/data-center/h100: H100 SXM \"BFLOAT16 Tensor Core* 1,979 teraFLOPS\", \"* With sparsity\"; dense is half"),
403 (Precision::Fp16, 1979e12, "nvidia.com/en-us/data-center/h100: H100 SXM \"FP16 Tensor Core* 1,979 teraFLOPS\", \"* With sparsity\"; dense is half"),
404 ]),
405 sources: &[NV_H100_PAGE],
406 }
407 }
408
409 pub fn h100_nvl() -> Roofs {
412 Roofs {
413 clock_ghz: Fact::published(
414 1.785,
415 "nvidia.com/content/dam/en-zz/Solutions/Data-Center/h100/PB-11773-001_v01.pdf: Table 1 \"GPU clocks Base: \
416 1,080 MHz Boost: 1,785 MHz\"",
417 ),
418 mem: vec![MemRoof {
419 level: MemLevel::Dram,
420 mix: StreamMix::Any,
421 scope: Scope::Chip,
422 unit: Transfer::Bytes,
423 rate: Fact::published(
424 Rate::PerSecond(3938e9),
425 "nvidia.com/content/dam/en-zz/Solutions/Data-Center/h100/PB-11773-001_v01.pdf: Table 2 \"Peak memory \
426 bandwidth 3,938 GB/s\" (the product page rounds it to \"3.9TB/s\")",
427 ),
428 }],
429 compute: dense_from_sparse(&[
430 (Precision::Int8, 3341e12, "nvidia.com/en-us/data-center/h100: H100 NVL \"INT8 Tensor Core* 3,341 TOPS\", \"* With sparsity\"; dense is half"),
431 (Precision::Fp8, 3341e12, "nvidia.com/en-us/data-center/h100: H100 NVL \"FP8 Tensor Core* 3,341 teraFLOPS\", \"* With sparsity\"; dense is half"),
432 (Precision::Bf16, 1671e12, "nvidia.com/en-us/data-center/h100: H100 NVL \"BFLOAT16 Tensor Core* 1,671 teraFLOPS\", \"* With sparsity\"; dense is half"),
433 (Precision::Fp16, 1671e12, "nvidia.com/en-us/data-center/h100: H100 NVL \"FP16 Tensor Core* 1,671 teraFLOPS\", \"* With sparsity\"; dense is half"),
434 ]),
435 sources: &[NV_H100_PAGE, NV_H100_NVL_BRIEF],
436 }
437 }
438
439 pub fn a100_sxm4_80gb() -> Roofs {
441 Roofs {
442 clock_ghz: Fact::modeled(
443 1.41,
444 "not published on NVIDIA's A100 product page; inherited from the A100 whitepaper's 1410 MHz boost — every \
445 rate below is per second, so no rate is converted through it",
446 ),
447 mem: vec![MemRoof {
448 level: MemLevel::Dram,
449 mix: StreamMix::Any,
450 scope: Scope::Chip,
451 unit: Transfer::Bytes,
452 rate: Fact::published(
453 Rate::PerSecond(2039e9),
454 "nvidia.com/en-us/data-center/a100: A100 80GB SXM \"GPU Memory Bandwidth 2,039 GB/s\"",
455 ),
456 }],
457 compute: [
458 (Precision::Int8, 624e12, "nvidia.com/en-us/data-center/a100: A100 80GB SXM \"INT8 Tensor Core 624 TOPS | 1248 TOPS*\", \"* With sparsity\""),
459 (Precision::Bf16, 312e12, "nvidia.com/en-us/data-center/a100: A100 80GB SXM \"BFLOAT16 Tensor Core 312 TFLOPS | 624 TFLOPS*\", \"* With sparsity\""),
460 (Precision::Fp16, 312e12, "nvidia.com/en-us/data-center/a100: A100 80GB SXM \"FP16 Tensor Core 312 TFLOPS | 624 TFLOPS*\", \"* With sparsity\""),
461 ]
462 .into_iter()
463 .map(|(precision, ops, cite)| ComputeRoof {
464 engine: Engine::Matrix,
465 precision,
466 scope: Scope::Chip,
467 ops: Fact::published(Rate::PerSecond(ops), cite),
468 })
469 .collect(),
470 sources: &[NV_A100_PAGE],
471 }
472 }
473
474 pub fn b200() -> Roofs {
477 Roofs {
478 clock_ghz: Fact::modeled(
479 1.965,
480 "not published on NVIDIA's DGX B200 or HGX pages; inherited from third-party spec databases — every rate \
481 below is per second, so no rate is converted through it",
482 ),
483 mem: vec![MemRoof {
484 level: MemLevel::Dram,
485 mix: StreamMix::Any,
486 scope: Scope::Chip,
487 unit: Transfer::Bytes,
488 rate: Fact::derived(
489 Rate::PerSecond(64e12 / 8.0),
490 "nvidia.com/en-us/data-center/dgx-b200: \"8x NVIDIA Blackwell GPUs\", \"1,440 GB total, 64 TB/s HBM3e \
491 bandwidth\"; 64 TB/s over 8 GPUs",
492 ),
493 }],
494 compute: [
495 (Precision::Int8, 72e15, "nvidia.com/en-us/data-center/hgx: HGX B200 \"8x NVIDIA Blackwell SXM\", \"INT8 Tensor Core 72 POPS\", footnote \"Specification in Sparse. Dense is ½ sparse spec shown.\"; halved, over 8 GPUs"),
496 (Precision::Fp8, 72e15, "nvidia.com/en-us/data-center/hgx: HGX B200 \"8x NVIDIA Blackwell SXM\", \"FP8/FP6 Tensor Core 72 PFLOPS\", footnote \"Specification in Sparse. Dense is ½ sparse spec shown.\"; halved, over 8 GPUs"),
497 (Precision::Bf16, 36e15, "nvidia.com/en-us/data-center/hgx: HGX B200 \"8x NVIDIA Blackwell SXM\", \"FP16/BF16 Tensor Core 36 PFLOPS\", footnote \"Specification in Sparse. Dense is ½ sparse spec shown.\"; halved, over 8 GPUs"),
498 (Precision::Fp16, 36e15, "nvidia.com/en-us/data-center/hgx: HGX B200 \"8x NVIDIA Blackwell SXM\", \"FP16/BF16 Tensor Core 36 PFLOPS\", footnote \"Specification in Sparse. Dense is ½ sparse spec shown.\"; halved, over 8 GPUs"),
499 ]
500 .into_iter()
501 .map(|(precision, sparse_system, cite)| ComputeRoof {
502 engine: Engine::Matrix,
503 precision,
504 scope: Scope::Chip,
505 ops: Fact::derived(Rate::PerSecond(sparse_system / 2.0 / 8.0), cite),
506 })
507 .collect(),
508 sources: &[NV_DGX_B200_PAGE, NV_HGX_PAGE],
509 }
510 }
511
512 pub fn catalog() -> Vec<(&'static str, Roofs)> {
514 vec![
515 ("wormhole", Roofs::wormhole()),
516 ("blackhole", Roofs::blackhole()),
517 ("rtx3060", Roofs::rtx3060()),
518 ("sky130-fabric", Roofs::sky_fabric()),
519 ("a100-sxm4-80gb", Roofs::a100_sxm4_80gb()),
520 ("h100-sxm", Roofs::h100_sxm()),
521 ("h100-nvl", Roofs::h100_nvl()),
522 ("b200", Roofs::b200()),
523 ]
524 }
525
526 pub fn sky_fabric() -> Roofs {
531 Roofs {
532 clock_ghz: Fact::modeled(
533 1.0 / 19.5,
534 "inherited: the largest signed-off systolic array, systolic_pipe16_realbist, closes at clock_period_ns 19.5 with \
535 setup_worst_slack_ns 0.25 under OpenLane Sky130 STA (benchmarks/silicon/systolic_pipelined_signoff.json); a \
536 program's own array is not signed off",
537 ),
538 mem: vec![MemRoof {
539 level: MemLevel::Dram,
540 mix: StreamMix::Any,
541 scope: Scope::Site,
542 unit: Transfer::Elements,
543 rate: Fact::derived(
544 Rate::PerCycle(1.0),
545 "spatial::lower::fabric::fabric_cycle_model, cycle-exact against the RTL by spatial_fabric_lowering_lock: \
546 ReadTiles and WriteTiles take one cycle per element whatever its width",
547 ),
548 }],
549 compute: Vec::new(),
550 sources: &[FABRIC_TWIN, SKY130_SYSTOLIC_SIGNOFF],
551 }
552 }
553
554 pub fn xc7_fabric() -> Roofs {
557 Roofs {
558 clock_ghz: Fact::modeled(
559 0.1,
560 "the Nexys boards' 100 MHz system oscillator (Digilent Nexys A7 and Nexys Video reference manuals), the \
561 clock an image divides down from; a program's routed Fmax is recorded beside its bitstream",
562 ),
563 sources: &[FABRIC_TWIN, DIGILENT_NEXYS_CLOCK],
564 ..Roofs::sky_fabric()
565 }
566 }
567}
568
569#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
573pub enum Resource {
574 Dram,
576 NocLink,
579 Matrix,
582}
583
584#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
586pub struct Bound {
587 pub resource: Resource,
588 pub work: f64,
590 pub per_cycle: f64,
592 pub cycles: f64,
594 pub provenance: Provenance,
596}
597
598#[derive(Clone, Debug, PartialEq, Serialize)]
601pub struct Roofline {
602 pub bounds: Vec<Bound>,
603 pub ops: f64,
606 pub dram_bytes: f64,
608}
609
610impl Roofline {
611 pub fn binding(&self) -> Option<&Bound> {
613 self.bounds
614 .iter()
615 .max_by(|a, b| a.cycles.total_cmp(&b.cycles))
616 }
617
618 pub fn cycles(&self) -> f64 {
620 self.binding().map_or(0.0, |b| b.cycles)
621 }
622
623 pub fn intensity(&self) -> Option<f64> {
626 (self.dram_bytes > 0.0).then(|| self.ops / self.dram_bytes)
627 }
628}
629
630#[derive(Clone, Copy, Debug, PartialEq, Serialize, serde::Deserialize)]
632pub struct PerCycle {
633 pub value: f64,
634 pub provenance: Provenance,
635}
636
637#[derive(Clone, Copy, Debug, PartialEq, Serialize, serde::Deserialize)]
639pub struct MatrixRate {
640 pub per_cycle: PerCycle,
641 pub chip_wide: bool,
643 pub ops_per_issue: f64,
645 pub arithmetic_per_issue: f64,
647}
648
649#[derive(Clone, Copy, Debug, PartialEq, Serialize, serde::Deserialize)]
651pub struct LaneRate {
652 pub per_cycle: PerCycle,
653 pub unit: Transfer,
654}
655
656#[derive(Clone, Copy, Debug, PartialEq, Serialize, serde::Deserialize)]
659pub struct Rates {
660 pub dram: Option<LaneRate>,
661 pub link: Option<LaneRate>,
662 pub matrix: Option<MatrixRate>,
663 pub arithmetic_per_issue: f64,
665 pub elements_per_tile: f64,
667}
668
669impl Rates {
670 pub fn of(t: &super::target::SpatialTarget, element: Precision) -> Rates {
675 let r = &t.roofs;
676 let per = |rate: &Fact<Rate>| {
677 let c = r.per_cycle(rate);
678 PerCycle {
679 value: c.value,
680 provenance: c.provenance,
681 }
682 };
683 let ops_per_pass = 2.0 * (t.tile.rows * t.tile.cols * t.tile.cols) as f64;
684 let per_tile = super::work::mvmul_per_tile_matmul(
685 t.matmul_fidelity()
686 .unwrap_or(super::target::Fidelity::HiFi4),
687 );
688 let arithmetic_per_issue = ops_per_pass / per_tile as f64;
689 let lane = |m: &MemRoof| LaneRate {
690 per_cycle: per(&m.rate),
691 unit: m.unit,
692 };
693 Rates {
694 dram: r.mem(MemLevel::Dram, StreamMix::Any).map(lane),
695 link: r.mem(MemLevel::Noc, StreamMix::Any).map(lane),
696 matrix: r.compute(Engine::Matrix, element).map(|c| MatrixRate {
697 per_cycle: per(&c.ops),
698 chip_wide: c.scope == Scope::Chip,
699 ops_per_issue: ops_per_pass / super::work::MVMUL_PER_TILE_MATMUL_PER_PASS as f64,
700 arithmetic_per_issue,
701 }),
702 arithmetic_per_issue,
703 elements_per_tile: t.tile.elems() as f64,
704 }
705 }
706
707 pub fn roofline(&self, w: &super::work::Work) -> Roofline {
709 let mut bounds = Vec::new();
710 let mut bound = |resource: Resource, work: f64, rate: PerCycle| {
711 bounds.push(Bound {
712 resource,
713 work,
714 per_cycle: rate.value,
715 cycles: work / rate.value,
716 provenance: rate.provenance,
717 });
718 };
719 if let Some(lane) = self.dram {
720 let work = match lane.unit {
721 Transfer::Bytes => w.dram_bytes as f64,
722 Transfer::Elements => w.dram_tiles as f64 * self.elements_per_tile,
723 };
724 bound(Resource::Dram, work, lane.per_cycle);
725 }
726 if let Some(lane) = self.link.filter(|l| l.unit == Transfer::Bytes) {
729 bound(Resource::NocLink, w.engine_noc_bytes as f64, lane.per_cycle);
730 }
731 if let Some(m) = self.matrix {
732 let issues = if m.chip_wide {
733 w.matmul_retired
734 } else {
735 w.site_matmul_retired
736 };
737 bound(
738 Resource::Matrix,
739 issues as f64 * m.ops_per_issue,
740 m.per_cycle,
741 );
742 }
743 Roofline {
744 bounds,
745 ops: w.matmul_retired as f64 * self.arithmetic_per_issue,
746 dram_bytes: w.dram_bytes as f64,
747 }
748 }
749}
750
751pub fn roofline(
753 w: &super::work::Work,
754 t: &super::target::SpatialTarget,
755 element: Precision,
756) -> Roofline {
757 Rates::of(t, element).roofline(w)
758}