metrale_circuit/hardware/exec.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-30: How a weight-reading node executes its DECLARED formats on a device. The declared
4//! precision is never changed here; what varies per device is the instruction that carries it:
5//! - the activation format picks the MMA kind (BF16 activations: BF16 MMA, any weight format
6//! widened in registers; FP8: the FP8 MMA; NVFP4: the block-scaled FP4 MMA);
7//! - a device without the FP4 MMA but with the FP8 one runs NVFP4 operands through the exact
8//! E2M1 -> E4M3 conversion (`(q & 0x80808080) | ((q & 0x70707070) >> 2)` is value x 2^-6 for
9//! all 16 codes; the 2^6 folds into the group scale), block scales applied in FP32 per K=16
10//! step: the same products as the declared W4A4, not an upcast;
11//! - anything else has no path, which the plan states.
12//!
13//! A device's instruction is not enough: a node runs on the FP4 MMA only where the device's class
14//! compiles an FP4 block-scale kernel for it ([`super::avail::without_fp4_kernel`]); elsewhere it
15//! takes the same path as on a device without that MMA. [`node_exec`] is the one answer per node:
16//! the report's execution columns, its "FP4 costing" row and every node's cost read it.
17//!
18//! Owner: metrale-circuit (hardware).
19//! Invariants: no silent upcast. An activation format with no native or exact path is
20//! [`Exec::NoPath`], never re-planned at a wider format.
21
22use super::device::{Device, MmaKind};
23use super::estimate::activation_of;
24use crate::format::Format;
25use crate::ir::{Circuit, Node};
26use crate::venn::families::Roofline;
27
28/// 2026-09-30: The execution of one node's declared formats.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub enum Exec {
31 /// 2026-09-30: The device's MMA of this kind runs it (weights narrower than the activation
32 /// widen in registers: W4A16, W8A16).
33 Native(MmaKind),
34 /// 2026-09-30: NVFP4 operands converted exactly to E4M3 and run on the FP8 MMA.
35 ExactFp8Emulation,
36 /// 2026-09-30: No MMA of the device can run the activation format.
37 NoPath(MmaKind),
38 /// 2026-10-01: The device has the FP4 block-scale MMA but its class compiles no FP4 kernel
39 /// for the node: the exact E2M1 -> E4M3 path on the FP8 MMA where the device has one
40 /// (`fp8`), else no path.
41 NoFp4Kernel {
42 /// 2026-10-01: The device has the FP8 MMA.
43 fp8: bool,
44 },
45}
46
47impl Exec {
48 /// 2026-09-30: The report spelling.
49 pub fn describe(self) -> String {
50 match self {
51 Exec::Native(k) => format!("native {}", k.name()),
52 Exec::ExactFp8Emulation => {
53 "exact E2M1->E4M3 on the FP8 MMA, group-16 scales in FP32 (no native MMA for the pair)".into()
54 }
55 Exec::NoPath(k) => format!("no path: the device has no {} MMA", k.name()),
56 Exec::NoFp4Kernel { fp8: true } => {
57 "no FP4 block-scale kernel compiled for this class: exact E2M1->E4M3 on the FP8 MMA, group-16 scales in FP32".into()
58 }
59 Exec::NoFp4Kernel { fp8: false } => {
60 "no path: no FP4 block-scale kernel compiled for this class and no FP8 MMA".into()
61 }
62 }
63 }
64
65 /// 2026-10-01: The tensor peak of `r` this execution runs at, and its name.
66 pub fn peak(self, r: &Roofline) -> (f64, &'static str) {
67 match self {
68 Exec::Native(MmaKind::Fp4BlockScale) => (r.nvfp4_tflops, "the NVFP4 peak"),
69 Exec::ExactFp8Emulation | Exec::NoFp4Kernel { fp8: true } => {
70 (r.fp8_tflops, "the FP8 peak (exact E2M1->E4M3)")
71 }
72 Exec::Native(MmaKind::Fp8 | MmaKind::Fp4Fp8Nvfp4) => (r.fp8_tflops, "the FP8 peak"),
73 Exec::Native(_) | Exec::NoPath(_) | Exec::NoFp4Kernel { fp8: false } => {
74 (r.bf16_tflops, "the BF16 peak")
75 }
76 }
77 }
78}
79
80/// 2026-10-01: How `device` runs an NVFP4 activation without an FP4 block-scale kernel: on the
81/// FP8 MMA through the exact conversion where it has one, else no path; [`Exec::NoFp4Kernel`]
82/// when the device has the FP4 MMA and only the kernel is missing.
83pub fn fp4_fallback(device: &Device) -> Exec {
84 let fp8 = device.runs(MmaKind::Fp8);
85 if device.runs(MmaKind::Fp4BlockScale) {
86 Exec::NoFp4Kernel { fp8 }
87 } else if fp8 {
88 Exec::ExactFp8Emulation
89 } else {
90 Exec::NoPath(MmaKind::Fp4BlockScale)
91 }
92}
93
94/// 2026-10-01: How node `n` of `c` runs on `device` (weight-reading nodes only): its declared
95/// formats on the device's instructions ([`exec_of`]), or [`fp4_fallback`] when the class
96/// compiles no FP4 block-scale kernel for it (`no_fp4_kernel`).
97pub fn node_exec(device: &Device, c: &Circuit, n: &Node, no_fp4_kernel: bool) -> Option<Exec> {
98 let (w, a) = (n.weight?, activation_of(c, n)?);
99 Some(if no_fp4_kernel {
100 fp4_fallback(device)
101 } else {
102 exec_of(device, w, a)
103 })
104}
105
106/// 2026-09-30: The MMA kind the activation format needs.
107pub fn kind_of(activation: Format) -> MmaKind {
108 match activation {
109 Format::Fp8E4m3 { .. } => MmaKind::Fp8,
110 Format::Nvfp4 { .. } => MmaKind::Fp4BlockScale,
111 Format::Bf16 | Format::F32 | Format::I32 => MmaKind::Bf16,
112 }
113}
114
115/// 2026-09-30: How `device` executes a node of `weight` reading `activation`. NVFP4 weights
116/// under FP8 activations (W4A8 with group-16 E4M3 scales) have no single-instruction MMA on any
117/// device unless `native_mma` says so (`fp4_fp8_nvfp4_scaled`); they take the exact E4M3 path.
118pub fn exec_of(device: &Device, weight: Format, activation: Format) -> Exec {
119 let kind = kind_of(activation);
120 let w4a8 = matches!(weight, Format::Nvfp4 { .. }) && kind == MmaKind::Fp8;
121 if w4a8 {
122 return if device.runs(MmaKind::Fp4Fp8Nvfp4) {
123 Exec::Native(MmaKind::Fp4Fp8Nvfp4)
124 } else if device.runs(MmaKind::Fp8) {
125 Exec::ExactFp8Emulation
126 } else {
127 Exec::NoPath(MmaKind::Fp8)
128 };
129 }
130 if device.runs(kind) {
131 Exec::Native(kind)
132 } else if kind == MmaKind::Fp4BlockScale {
133 fp4_fallback(device)
134 } else {
135 Exec::NoPath(kind)
136 }
137}
138
139/// 2026-09-30: The declared pair as `W4A16`-style text.
140pub fn pair_name(weight: Format, activation: Format) -> String {
141 let bits = |f: Format| match f {
142 Format::Nvfp4 { .. } => "4",
143 Format::Fp8E4m3 { .. } => "8",
144 Format::Bf16 | Format::F32 | Format::I32 => "16",
145 };
146 format!("W{}A{}", bits(weight), bits(activation))
147}