diff --git a/tech_report/dgebwd.png b/tech_report/MXFP4/dgebwd.png
similarity index 100%
rename from tech_report/dgebwd.png
rename to tech_report/MXFP4/dgebwd.png
diff --git a/tech_report/dgefwd.png b/tech_report/MXFP4/dgefwd.png
similarity index 100%
rename from tech_report/dgefwd.png
rename to tech_report/MXFP4/dgefwd.png
diff --git a/tech_report/flow.png b/tech_report/MXFP4/flow.png
similarity index 100%
rename from tech_report/flow.png
rename to tech_report/MXFP4/flow.png
diff --git a/tech_report/report.md b/tech_report/MXFP4/report.md
similarity index 100%
rename from tech_report/report.md
rename to tech_report/MXFP4/report.md
diff --git a/tech_report/vallossgbs16.png b/tech_report/MXFP4/vallossgbs16.png
similarity index 100%
rename from tech_report/vallossgbs16.png
rename to tech_report/MXFP4/vallossgbs16.png
diff --git a/tech_report/vallossgbs64.png b/tech_report/MXFP4/vallossgbs64.png
similarity index 100%
rename from tech_report/vallossgbs64.png
rename to tech_report/MXFP4/vallossgbs64.png
diff --git a/tech_report/NVFP4/figures/amdfp4_deosc_vs_base_val_loss_final_last5.png b/tech_report/NVFP4/figures/amdfp4_deosc_vs_base_val_loss_final_last5.png
new file mode 100644
index 0000000..9cf59b5
Binary files /dev/null and b/tech_report/NVFP4/figures/amdfp4_deosc_vs_base_val_loss_final_last5.png differ
diff --git a/tech_report/NVFP4/figures/gpt_oss_4bit_mlperf1p2m_4way_loss_gradnorm.png b/tech_report/NVFP4/figures/gpt_oss_4bit_mlperf1p2m_4way_loss_gradnorm.png
new file mode 100644
index 0000000..3df1fba
Binary files /dev/null and b/tech_report/NVFP4/figures/gpt_oss_4bit_mlperf1p2m_4way_loss_gradnorm.png differ
diff --git a/tech_report/NVFP4/figures/nvfp4_diagram1_forward.mmd b/tech_report/NVFP4/figures/nvfp4_diagram1_forward.mmd
new file mode 100644
index 0000000..37e14ac
--- /dev/null
+++ b/tech_report/NVFP4/figures/nvfp4_diagram1_forward.mmd
@@ -0,0 +1,21 @@
+flowchart TD
+ X["X (BF16, token activations)"]
+ Wlp["W_lp (FP4 stored, E2M1 codes)"]
+ SX["scales_X (E4M3) + pts_X (FP32)"]
+ SW["scales_W (E4M3) + pts_W (FP32)"]
+
+ X --> QF["Block-wise quantize
elem=E2M1, block_scale=E4M3,
tensorwise PTS, rounding=RNE
(no Hadamard on forward path)"]
+ QF --> Xlp["X_lp (FP4 codes)"]
+ Xlp --> DQX["Dequantize X
X_dq = X_lp * block_scale * pts"]
+ DQX --> Xdq["X_dq (BF16)"]
+ SX --> DQX
+
+ Wlp --> DQW["Dequantize W"]
+ SW --> DQW
+ DQW --> Wdq["W_dq (BF16)"]
+
+ Xdq --> GEMM["BF16 GEMM (QDQ emulation path)
Y = X_dq @ W_dq^T"]
+ Wdq --> GEMM
+ GEMM --> Y["Y (BF16) -> next layer"]
+
+ X -. saved for wgrad .-> Save["Hadamard H + block-quantize
(stored on ctx as x_for_wgrad)"]
diff --git a/tech_report/NVFP4/figures/nvfp4_diagram1_forward.png b/tech_report/NVFP4/figures/nvfp4_diagram1_forward.png
new file mode 100644
index 0000000..1fd67d9
Binary files /dev/null and b/tech_report/NVFP4/figures/nvfp4_diagram1_forward.png differ
diff --git a/tech_report/NVFP4/figures/nvfp4_diagram2_backward.mmd b/tech_report/NVFP4/figures/nvfp4_diagram2_backward.mmd
new file mode 100644
index 0000000..fd67938
--- /dev/null
+++ b/tech_report/NVFP4/figures/nvfp4_diagram2_backward.mmd
@@ -0,0 +1,25 @@
+flowchart TD
+ dY["dY (BF16) from layer L+1"]
+ Wlp["W_lp (FP4 stored)"]
+ Xsaved["x_for_wgrad (saved BF16 or pre-rotated FP4)"]
+
+ dY --> QDg["SR-block-quantize on dY
elem=E2M1, scale=E4M3, PTS,
rounding=SR (no Hadamard on dgrad)
injects fresh noise eps_d"]
+ QDg --> dYdq["dY_dq (BF16) = true dY + eps_d"]
+ dYdq --> Gd["BF16 GEMM (QDQ emulation)
dX = dY_dq @ W_dq"]
+ Wlp --> DqW1["Dequantize W"]
+ DqW1 --> Gd
+ Gd --> dX["dX (BF16)
flows to layer L-1 as dY_{L-1}
(cross-layer noise carrier)"]
+
+ dY --> HW["Hadamard rotation H
(applied to dY for wgrad only)"]
+ HW --> QWg["SR-block-quantize
(same block params, SR=ON)"]
+ QWg --> dYHdq["dY_H_dq (BF16) = true dY rotated + eps_w"]
+
+ Xsaved --> HX["Hadamard H on saved X"]
+ HX --> QXw["block-quantize"]
+ QXw --> Xhdq["X_H_dq (BF16)"]
+
+ dYHdq --> Gw["BF16 GEMM (QDQ emulation)
dW = dY_H_dq^T @ X_H_dq"]
+ Xhdq --> Gw
+ Gw --> dW["dW (BF16)"]
+ dW --> Opt["AdamW optimizer"]
+ dW --> GN["grad_norm = sqrt(sum over layers ||dW||_2^2)
pre-clip global L2"]
diff --git a/tech_report/NVFP4/figures/nvfp4_diagram2_backward.png b/tech_report/NVFP4/figures/nvfp4_diagram2_backward.png
new file mode 100644
index 0000000..32d7f4d
Binary files /dev/null and b/tech_report/NVFP4/figures/nvfp4_diagram2_backward.png differ
diff --git a/tech_report/NVFP4/figures/nvfp4_diagram3_stacked.mmd b/tech_report/NVFP4/figures/nvfp4_diagram3_stacked.mmd
new file mode 100644
index 0000000..4c3cb27
--- /dev/null
+++ b/tech_report/NVFP4/figures/nvfp4_diagram3_stacked.mmd
@@ -0,0 +1,13 @@
+flowchart LR
+ dYLp1["dY_{L+1}"]
+ dYLp1 --> N3["layer L+1 dgrad
SR + GEMM, adds eps_{L+1}"]
+ N3 --> dYL["dX_{L+1} = dY_L
variance sigma_{L+1}^2"]
+
+ dYL --> N2["layer L dgrad
SR + GEMM, adds eps_L"]
+ N2 --> dYLm1["dX_L = dY_{L-1}
sigma_L^2 ~ G_L * sigma_{L+1}^2 + eps_L^2"]
+
+ dYLm1 --> N1["layer L-1 dgrad
SR + GEMM, adds eps_{L-1}"]
+ N1 --> dYLm2["dX_{L-1} = dY_{L-2}
sigma_{L-1}^2 ~ G_{L-1} * sigma_L^2 + eps_{L-1}^2"]
+
+ Note["Recurrence (no Hadamard on dgrad):
sigma_d^2 = G_d * sigma_{d+1}^2 + eps_d^2
eps_d^2 ∝ ||grad_output||_inf^2 (Tseng Thm 3.2)"]
+ N2 -.-> Note
diff --git a/tech_report/NVFP4/figures/nvfp4_diagram3_stacked.png b/tech_report/NVFP4/figures/nvfp4_diagram3_stacked.png
new file mode 100644
index 0000000..0d14f72
Binary files /dev/null and b/tech_report/NVFP4/figures/nvfp4_diagram3_stacked.png differ
diff --git a/tech_report/NVFP4/figures/nvfp4_grad_output_backward_path.mmd b/tech_report/NVFP4/figures/nvfp4_grad_output_backward_path.mmd
new file mode 100644
index 0000000..cdf9719
--- /dev/null
+++ b/tech_report/NVFP4/figures/nvfp4_grad_output_backward_path.mmd
@@ -0,0 +1,48 @@
+flowchart LR
+ IN["grad_output (dY, BF16)
from layer L+1 autograd
(upstream chain rule)"]
+
+ HAD["Hadamard rotate H
left_mul=True
(wgrad path only;
dgrad path does NOT rotate)"]
+
+ DGRAD_QDQ["SR-QDQ #1 (root cause)
_qdq(grad_output,
axis=-1, use_sr=True)
autograd.py:200-203"]
+
+ WGRAD_QDQ["SR-QDQ #2 (root cause)
_qdq(grad_output_H,
axis=0, use_sr=True)
cg_backward.py:108-119
(inside _nvfp4_grouped_wgrad)"]
+
+ GD["_nvfp4_grouped_dgrad
dX = g_dq @ w_bwd
(BF16 GEMM, QDQ emulation)"]
+
+ GW["_nvfp4_grouped_wgrad
dW = g_m_dq^T @ x_bwd
(BF16 GEMM, QDQ emulation)"]
+
+ W_BWD["w_bwd (saved from forward)
_qdq(W, axis=-2)
ABL.2 D2: innocent"]
+
+ X_BWD["x_bwd (saved from forward)
_qdq(Hadamard(X), axis=0)
ABL.2 D4: innocent"]
+
+ DX["dX (BF16)
-> layer L-1's grad_output
This carries SR noise upstream;
cross-layer ε² accumulates here"]
+
+ DW["dW (BF16)
-> AdamW optimizer
-> contributes to global grad_norm"]
+
+ IN -->|"dgrad path
(no Hadamard)"| DGRAD_QDQ
+ IN -->|"wgrad path"| HAD
+ HAD --> WGRAD_QDQ
+
+ DGRAD_QDQ --> GD
+ W_BWD --> GD
+
+ WGRAD_QDQ --> GW
+ X_BWD --> GW
+
+ GD --> DX
+ GW --> DW
+
+ classDef incoming fill:#cce5ff,stroke:#0056b3,color:#000,stroke-width:2px
+ classDef rootcause fill:#f8d7da,stroke:#dc3545,color:#000,stroke-width:3px
+ classDef safe fill:#d4edda,stroke:#28a745,color:#000,stroke-width:2px
+ classDef gemm fill:#fff3cd,stroke:#ffc107,color:#000,stroke-width:2px
+ classDef output fill:#e2e3e5,stroke:#383d41,color:#000,stroke-width:2px
+ classDef neutral fill:#ffffff,stroke:#6c757d,color:#000,stroke-width:2px
+
+ class IN incoming
+ class HAD neutral
+ class DGRAD_QDQ,WGRAD_QDQ rootcause
+ class W_BWD,X_BWD safe
+ class GD,GW gemm
+ class DX output
+ class DW output
diff --git a/tech_report/NVFP4/figures/nvfp4_grad_output_backward_path.png b/tech_report/NVFP4/figures/nvfp4_grad_output_backward_path.png
new file mode 100644
index 0000000..211e81e
Binary files /dev/null and b/tech_report/NVFP4/figures/nvfp4_grad_output_backward_path.png differ
diff --git a/tech_report/NVFP4/figures/phase_b_ablation_summary_lr.mmd b/tech_report/NVFP4/figures/phase_b_ablation_summary_lr.mmd
new file mode 100644
index 0000000..a2b7f13
--- /dev/null
+++ b/tech_report/NVFP4/figures/phase_b_ablation_summary_lr.mmd
@@ -0,0 +1,23 @@
+flowchart LR
+ P["Problem
NVFP4 cumulative grad_norm
explodes 100x-2000x
MXFP4 same SR does not"]
+
+ A["B-ABL.1 fwd vs bwd (24 runs)
━━━━━━━━━━━━━
Backward = 100% the source"]
+
+ B["B-ABL.2 bwd tensor bypass (36 runs)
━━━━━━━━━━━━━
Root op = 2x _qdq(grad_output, SR)
stride / non-contig hypothesis killed"]
+
+ C["B-ABL.5 op-internal bisect (S1+S7+F.2)
━━━━━━━━━━━━━
Noise = SR randomness only
NVFP4 single-op quieter than MXFP4 (paradox)"]
+
+ D["PENDING
━━━━━━━━━━━━━
S3: block_size 16 -> 32
answers: does it explain
'MXFP4 doesn't explode'?"]
+
+ P -->|switch to precision-ablation| A
+ A -->|bwd locked| B
+ B -->|op locked, H1 dead| C
+ C -.->|cross-layer propagation open| D
+
+ classDef problem fill:#cce5ff,stroke:#0056b3,color:#000,stroke-width:2px
+ classDef confirmed fill:#d4edda,stroke:#28a745,color:#000,stroke-width:2px
+ classDef pending fill:#fff3cd,stroke:#ffc107,stroke-width:2px,stroke-dasharray:5 5,color:#000
+
+ class P problem
+ class A,B,C confirmed
+ class D pending
diff --git a/tech_report/NVFP4/figures/phase_b_ablation_summary_lr.png b/tech_report/NVFP4/figures/phase_b_ablation_summary_lr.png
new file mode 100644
index 0000000..9aec616
Binary files /dev/null and b/tech_report/NVFP4/figures/phase_b_ablation_summary_lr.png differ
diff --git a/tech_report/NVFP4/figures/rewind_4way_valloss_last5.png b/tech_report/NVFP4/figures/rewind_4way_valloss_last5.png
new file mode 100644
index 0000000..4cd1422
Binary files /dev/null and b/tech_report/NVFP4/figures/rewind_4way_valloss_last5.png differ
diff --git a/tech_report/NVFP4/report.md b/tech_report/NVFP4/report.md
new file mode 100644
index 0000000..8edd3c9
--- /dev/null
+++ b/tech_report/NVFP4/report.md
@@ -0,0 +1,258 @@
+# Training LLM with NVFP4: Two-Level Microscaling, Numerical Stability, and Evaluation on the MLPerf Small MoE Benchmark
+
+> **DRAFT v0.1 (2026-07-10).** Companion to the MXFP4 report (`../MXFP4/report.md`). All end-to-end results are on the same hardware generation (8×AMD MI300X) under the MLPerf-aligned 1.2M-step learning-rate schedule; multi-seed numbers are extracted from existing TensorBoard logs.
+
+---
+
+## Abstract
+
+Training large language models in 4-bit floating point promises large memory and compute savings, but the choice of FP4 microscaling format materially affects accuracy. We present an open-source training recipe ([ALTO](https://github.com/AMD-AGI/ALTO)) for **GPT-OSS-20B**—a 20-billion-parameter mixture-of-experts (MoE) model—on the **MLPerf Small MoE** benchmark under the **NVFP4** microscaling format. NVFP4 encodes values as E2M1 with a **two-level scale**: a fine-grained E4M3 scale per 16-element block, normalized by a per-tensor FP32 outer scale. Relative to MXFP4 (32-element blocks with a power-of-two UE8M0 scale), the finer block size and the higher-resolution two-level scale reduce quantization error. Our recipe composes hybrid 1D/2D block quantization, randomized Hadamard transforms (RHT), and stochastic rounding (SR) from [arXiv:2509.25149] with an optional weight de-oscillation scheme from TetraJet-v2 [arXiv:2510.27527]. On C4 validation with global batch size 16, NVFP4 reaches a validation loss of **3.3459 ± 0.0024** (3 seeds) at the 16,128-step slice, within **0.0195** of the BF16 baseline (**3.3264 ± 0.0031**) and consistently below MXFP4 (**3.3548 ± 0.0016**) on every seed. We report operator-level SNR against BF16 under both a synthetic injected-outlier protocol and a real-activation protocol, and we characterize the already-merged AMD-FP4 (UE5M3 inner scale) variant. All experiments use fake-quantized NVFP4 kernels; because the current native FP4 GEMM path does not support NVFP4's continuous scales, execution is quantize–dequantize (QDQ) emulation and wall-clock speed is not optimized.
+
+---
+
+## 1. Introduction
+
+Low-precision training has progressed from FP16 and BF16 to FP8 and, recently, 4-bit floating-point formats. Two 4-bit microscaling families are now prominent: the Open Compute Project (OCP) **MXFP4** format (32-element blocks, power-of-two UE8M0 block scale) and NVIDIA's **NVFP4** format (16-element blocks, an E4M3 block scale normalized by a per-tensor FP32 outer scale). Both encode elements as E2M1 (one sign, two exponent, one mantissa bit; magnitudes in $\{0,0.5,1,1.5,2,3,4,6\}$). For sparse MoE models such as GPT-OSS-20B, where expert matrices dominate compute and memory, 4-bit training could unlock disproportionate efficiency gains.
+
+The two formats trade off differently. MXFP4's UE8M0 scale is a pure power of two: cheap and directly consumable by a native power-of-two-scaled FP4 GEMM, but coarse. NVFP4's two-level scale carries mantissa bits (E4M3) and a continuous per-tensor normalizer, giving finer resolution and better outlier absorption—at the cost that the current native FP4 GEMM path supports only power-of-two scales, so NVFP4 cannot use it and must run as software QDQ emulation.
+
+| Property | MXFP4 | NVFP4 |
+|---|---|---|
+| Inner block size | 32 | **16** (finer) |
+| Inner (block) scale | E8M0 power-of-two (uint8) | **E4M3** (max 448), stored in FP32 |
+| Outer scale | none | **per-tensor FP32** (two-level scaling) |
+| Native FP4 GEMM support | yes | **no** — software QDQ emulation |
+| Dominant error | resolution | resolution + dynamic range |
+
+This report describes ALTO's methodology for NVFP4 training on the MLPerf Small MoE task. Hybrid 1D/2D block quantization, RHT, and SR are adopted from [arXiv:2509.25149]; weight de-oscillation is adapted from TetraJet-v2 [arXiv:2510.27527]. Our contributions are: (i) a composable NVFP4 training recipe for MX-style MoE models implemented in a unified kernel and modifier stack; and (ii) a systematic NVFP4-vs-MXFP4 comparison at the operator level (synthetic and real data) and end-to-end (multi-seed, MLPerf-aligned). ALTO also supports replacing the E4M3 inner scale with the wider-range UE5M3 grid—the already-merged **AMD-FP4** variant—which we report as a reference point (Section 4.1). We state explicitly where the implementation remains a research prototype rather than a performance-optimized production system.
+
+---
+
+## 2. Problem Setting
+
+### 2.1 Benchmark: MLPerf Small MoE
+
+We evaluate on the **MLPerf Small MoE** training benchmark, which specifies pretraining of a sparse MoE LLM on a subset of the C4 dataset. Our target model is **GPT-OSS-20B** (~20.9B parameters, ~3.6B active per token), an open-source MoE architecture released by OpenAI. The official MLPerf task is a BF16 workload; running FP4 under its protocol is an internal research exercise on capability and convergence, not a compliant MLPerf submission.
+
+### 2.2 Training Protocol
+
+Training hyperparameters follow the ALTO configuration `gpt_oss_20b_lpt`:
+
+| Hyperparameter | Value |
+|----------------|-------|
+| Training / validation data | Pre-tokenized C4 subset / C4 validation set |
+| Sequence length | 8,192 tokens |
+| Global batch size | 16 (local batch 1, gradient accumulation 2) |
+| LR-decay horizon | **1,200,000 steps** (MLPerf-aligned cosine), run as a 16,128-step slice |
+| Base / min learning rate | $4\times10^{-4}$ / $4\times10^{-5}$ (min-LR factor 0.1) |
+| Scheduler / warmup | cosine decay / 128 steps |
+| Optimizer | AdamW ($\beta_1{=}0.9,\ \beta_2{=}0.95,\ \varepsilon{=}10^{-5}$, weight decay 0.1), grad-norm clip 1.0 |
+| Parallelism | expert parallelism 8; tensor parallelism 1; FSDP shard 8; activation checkpointing none |
+| Validation | every 768 steps, 64 validation steps; seeds 1234 / 2024 / 4242 |
+
+Two protocol points are essential for comparability. First, the learning-rate cosine decays over the **MLPerf 1.2M-step horizon**; we run only the first **16,128 steps**, so the learning rate remains near its peak throughout—matching the MLPerf reference regime. All results in this report use this single caliber. Second, validation uses a **rewind dataloader** (torchtitan `82084e7`): each evaluation rebuilds the dataloader from the start of the validation set, so every eval point scores the same fixed samples, as MLPerf requires.
+
+NVFP4 is applied to all `Linear` layers and `GptOssGroupedExperts` modules; the `lm_head` projection and MoE router gates are kept in BF16, as routing is sensitive to small perturbations.
+
+### 2.3 Evaluation Methodology
+
+We evaluate at two levels.
+
+**Operator-level SNR.** We verify that ALTO's NVFP4 Linear and grouped-GEMM autograd paths reproduce a high-precision reference. Each configuration runs a forward pass and a full backward pass, comparing the NVFP4 outputs and gradients against a BF16 reference. We report signal-to-noise ratio in decibels,
+
+$$
+\mathrm{SNR} = 10 \log_{10} \frac{\sum_i \mathbf{X}_i^2}{\sum_i (\mathbf{X}_i - \hat{\mathbf{X}}_i)^2},
+$$
+
+computed in FP32, for forward output $\mathbf{O}$, input gradient $\mathrm{d}\mathbf{X}$, and weight gradient $\mathrm{d}\mathbf{W}$. We use **two data protocols**: (a) a *synthetic injected-outlier* generator identical to the MXFP4 report—each tensor is $\mathbf{T} = \mathcal{N}(0,1) + \mathrm{Bernoulli}(0.005)\odot\mathcal{N}(0,10^4)$, i.e. ~0.5% of elements receive a ~100× heavy-tail perturbation; and (b) a *real-activation* protocol that feeds real C4 batches through the true GPT-OSS-20B forward/backward with HF BF16 weights, capturing $x/w/y/\mathrm{d}y$ at representative layers (L1/L12/L20) for attention `wq` (dense) and MoE `experts` (grouped GEMM).
+
+**End-to-end.** We report validation cross-entropy on the held-out C4 set at fixed step checkpoints. The BF16 baseline uses identical data, hyperparameters, and parallelism, differing only in the absence of FP4 quantization.
+
+---
+
+## 3. Method
+
+The recommended NVFP4 recipe (`nvfp4_alllayers_rank0.yaml`) enables hybrid block quantization, RHT, SR, and the two-level tensorwise scale; weight de-oscillation is optional.
+
+### 3.1 NVFP4 Two-Level Microscaling Format
+
+NVFP4 partitions each tensor into blocks of **16 consecutive elements** and quantizes each element to E2M1. Unlike MXFP4's single power-of-two scale per block, NVFP4 uses a **two-level scale**: a per-block **inner** scale $s_{\mathrm{inner}}$ on the E4M3 grid (a general float, max 448), and a per-tensor **outer** scale $s_{\mathrm{outer}}$ in FP32 (the NVFP4 spec's $s_{\mathrm{global}}$). The outer scale first normalizes the tensor to a magnitude the E4M3 grid can represent precisely; the inner scale then exploits E4M3's mantissa bits for fine per-block resolution. Given a tensor with maximum magnitude $a=\max_i|x_i|$ and a block with maximum $b$,
+
+$$
+s_{\mathrm{outer}} = \frac{a}{448 \times 6} = \frac{a}{2688}, \qquad
+s_{\mathrm{inner}} = \mathcal{Q}_{\mathrm{E4M3}}\!\left(\mathrm{clip}\!\left(\frac{b}{6\, s_{\mathrm{outer}}},\ [\varepsilon_{\mathrm{E4M3}},\ 448]\right)\right),
+$$
+
+$$
+\hat{x}_i = \mathcal{Q}_{\mathrm{E2M1}}\!\left(\frac{x_i}{s_{\mathrm{inner}}\, s_{\mathrm{outer}}}\right), \qquad
+\tilde{x}_i = \hat{x}_i \cdot s_{\mathrm{inner}}\, s_{\mathrm{outer}},
+$$
+
+where $6$ is the largest E2M1 magnitude and $\varepsilon_{\mathrm{E4M3}}$ is the smallest E4M3 normal. The outer scale is kept in FP32, floored at $10^{-30}$ only as a divide-by-zero guard.
+
+As illustrated in Figure 1, each linear layer has three underlying GEMMs: a forward GEMM producing $\mathbf{O}$, and backward GEMMs producing $\mathrm{d}\mathbf{X}$ and $\mathrm{d}\mathbf{W}$. Because the current native FP4 GEMM path accepts only power-of-two scales, NVFP4 cannot use it; ALTO performs **fake quantization**—operands are quantized to NVFP4 and immediately dequantized to BF16 before a standard high-precision GEMM (`BF16 → quant → FP4 → dequant → BF16 → GEMM`)—faithfully reproducing FP4 numerical error without realizing memory-bandwidth savings (Section 6).
+
+**Figure 1.** NVFP4 two-level-scale compute flow across the three GEMMs (forward + backward).
+
+
+
+
+
+
+
+