Files
realAsma b9cfdce8dc docs: add Local Hessian NVFP4 weight-scale announcement blog (#2417)
### What does this PR do?

Type of change: documentation

Adds a Local Hessian announcement blog at
`docs/source/announcements/local-hessian.rst`, covering the NVFP4
per-block
weight-scale rule that minimizes output error instead of weight error.

Contents:

- Derivation of the per-block output-error objective and its `16x16`
local
  Hessian, with numbered equations.
- Results on Qwen3.5-9B: scale-setting comparison against max, MSE, and
  Four-over-six, plus composition with GPTQ.
- Figure 1, a grouped bar chart of the Qwen3.8-27B W4A4 candidate scores
  (BF16 in gray, the two scale rules in NVIDIA greens).
- A "Using Local Hessian" section with the config example and the
  end-to-end `hf_ptq.py` command.

Two supporting changes outside the blog:

- `docs/source/_static/announcements.css`: the `shibuya` theme has no
`span.eqno` rule, so Sphinx's default `float: right` on equation numbers
cannot share a line with MathJax's full-width display block and the
number
renders *above* the equation. This anchors it to the right of the
equation
  instead, and shrinks the table-note class.
-
`docs/source/announcements/assets/qwen3-27b-w4a4-scale-rule-accuracy.png`:
  the Figure 1 asset.

### Usage

```python
import modelopt.torch.quantization as mtq

config = {
    "quant_cfg": [...],  # quantizer configuration
    "algorithm": {"method": "local_hessian", "fp8_scale_sweep": True},
}

model = mtq.quantize(model, config, forward_loop)
```

### Testing

Documentation only; no code paths change. The `.rst` parses cleanly
under
docutils. The rendered page has not been checked with a full
`sphinx-build`,
so the equation-number CSS fix and the figure placement are worth an
eyeball
on the built docs before merge.

### Before your PR is "*Ready for review*"

- Is this change backward compatible?: N/A
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: N/A
- Did you write any new necessary tests?: N/A
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
N/A
- Did you get Claude approval on this PR?: ❌

### Additional Information

Two items to settle before this is ready to publish:

1. The `--recipe` example points at

`modelopt_recipes/models/Qwen/Qwen3.8-27B/ptq/nvfp4_local_hessian-fp8_attn-kv_fp8_cast.yaml`,
a placeholder path derived from the existing recipe naming convention.
It
   needs to match whatever lands in #2363.
2. The tables report single-run team measurements; the blog says so and
makes
   no significance claims.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **Documentation**
- Added guidance on NVFP4 Local-Hessian weight-scale selection,
including mathematical details, accuracy comparisons, runtime
considerations, limitations, configuration examples, and reproduction
steps.
- Updated announcement labels, headings, metadata, descriptions, and
filtering text to use “Local-Hessian.”

- **Style**
- Improved announcement formatting for equation labels, display-equation
spacing, Hessian results, table headers, and explanatory notes.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: realAsma <akuriparambi@nvidia.com>
2026-09-16 21:48:11 +00:00

332 lines
11 KiB
ReStructuredText

:orphan:
Improving NVFP4 Accuracy with Local-Hessian Weight Scales
#########################################################
:Author: Model Optimizer Team
:Date: September 9, 2026
:Tags: local-hessian, quantization, nvfp4, calibration, modelopt
.. role:: local-hessian-result(strong)
.. role:: table-header-note
In this blog, we share about Model Optimizer 'Local-Hessian', an algorithm for NVFP4 per-block scale selection
to minimize the output error. We used this algorithm to create a low loss checkpoint
`nvidia/Qwen3.8-27B-NVFP4 <https://huggingface.co/nvidia/Qwen3.8-27B-NVFP4>`_ which can leverage NVFP4 tensor cores for performant inference on Blackwell GPUs.
Here is a comparison of accuracy results we observed for 'Local-Hessian' algorithm compared to the default max algorithm:
.. image:: assets/qwen3-27b-w4a4-scale-rule-accuracy.png
:alt: Qwen3.8-27B scores by NVFP4 weight-scale rule, BF16 baseline in gray
:width: 100%
**Figure 1. Qwen3.8-27B NVFP4 accuracy comparison between the default NVFP4 algorithm (max) and 'Local-Hessian'.**
Scale Selection For NVFP4
**************************
NVFP4 represents each group of 16 weights with FP4 values and an FP8 block
scale [1]_. This block scale is used to scale the per-block values so to NVFP4 E2M1 range (-6.0, 6.0).
The default way is to set the block scale based on the per-block maximum value (max scaling) [1]_.
As originally shown in the 'Four-Over-Six' paper [2]_, this block scale can be selected based on other
criteria such as per-block error. 'Four-Over-Six' selects the per-block scale from 2 candidates, while the
:func:`Model-Optimizer Mean Square Error (MSE) <modelopt.torch.quantization.model_calib.mse_calibrate>` algorithm uses an exhaustive sweep over all positive, non-zero FP8
scales (126 values).
Both of these approaches for scale selection consider only weight tensor-level error, which we find does not correlate
well with downstream accuracy evaluation results.
How Local-Hessian Works
***********************
NVFP4 **Local-Hessian** chooses each per-block weight scale to minimize
the *output* error of the matrix multiplication rather than the weight
error. Nothing about the format changes -- we just compute the per-block
scales differently from max scaling.
Consider a linear layer :math:`Y=WX` with weights
:math:`W\in\mathbb{R}^{C_{\mathrm{out}}\times C_{\mathrm{in}}}` and
calibration inputs
:math:`X\in\mathbb{R}^{C_{\mathrm{in}}\times N}`, where :math:`N` is the
number of calibration tokens. Quantizing divides the weights by a scale
and casts, where the cast rounds each value onto the grid of the
low-precision format:
.. math::
:label: lh-quant
\mathcal{Q}(W,s)=\operatorname{Cast}(W/s)\cdot s.
This leaves a quantization error
.. math::
:label: lh-quant-error
\Delta(W,s)=\mathcal{Q}(W,s)-W.
What we actually care about is the error this puts on the layer output,
measured over the calibration set:
.. math::
:label: lh-output-error
E(s) &= \lVert WX-\mathcal{Q}(W,s)\,X\rVert_F^2
= \lVert \Delta(W,s)\,X\rVert_F^2 \\
&= \operatorname{tr}\!\left(\Delta(W,s)\,(XX^{\top})\,
\Delta(W,s)^{\top}\right).
The input second-moment matrix
:math:`XX^{\top}\in\mathbb{R}^{C_{\mathrm{in}}\times C_{\mathrm{in}}}` is
half the Hessian of the output error,
:math:`\partial^2E(s)/\partial\Delta(W,s)^2=2XX^{\top}`: it weights each
weight error by how much that input coordinate actually moves the
output. The trace operator :math:`\operatorname{tr}(\cdot)` sums one
quadratic-error term per output channel, allowing the channels to be optimized
independently.
For NVFP4, :math:`s` is not a scalar: each output channel has
:math:`C_{\mathrm{in}}/16` blocks, one scale each. With :math:`M`
candidates per block, minimizing :math:`E(s)` jointly means searching
:math:`M^{C_{\mathrm{in}}/16}` combinations -- this is not tractable. To make
the search tractable, Local-Hessian uses a block-diagonal approximation: it adds
the output error from each block independently and ignores interactions between
quantization errors in different blocks. For block :math:`b`,
.. math::
:label: lh-block-error
E_b(s_b) = \Delta(W_b,s_b)\,(X_bX_b^{\top})\,\Delta(W_b,s_b)^{\top},
where :math:`W_b\in\mathbb{R}^{1\times16}` is one NVFP4 block of weights,
:math:`s_b` is that block's scale, and
:math:`X_b\in\mathbb{R}^{16\times N}` holds the rows of :math:`X` the
block multiplies, so the local Hessian :math:`X_bX_b^{\top}` is only
:math:`16\times16`. We sweep all 126 candidate FP8 scales per block, just
as the MSE algorithm does; because blocks are independent, a
:func:`Triton kernel <modelopt.torch.kernels.quantization.gemm.nvfp4_fp8_sweep.nvfp4_fp8_scale_sweep_hessian>`
does the whole layer at once. See the
:func:`Model Optimizer Local-Hessian code <modelopt.torch.quantization.model_calib.local_hessian_calibrate>`
for details.
Local-Hessian Results
**********************
Accuracy Comparison
====================
In Table 1 we compare Local-Hessian against other scale-selection algorithms for weights on Qwen3.5-9B.
Local-Hessian gives the overall best accuracy among the NVFP4
weight-scale selection methods, cutting the average drop from 5.10 to 3.10
points against the default max rule. We get that from nothing but a
smarter way of computing the weight scale -- which says something about
micro-block formats like NVFP4: **the scale carries a lot of
information, and it pays to set it diligently.**
.. list-table::
:header-rows: 1
* - Weight scale selection method
- MMLU
- HellaSwag
- WinoGrande
- GSM8K
- Average drop :table-header-note:`(lower is better)`
- WikiText PPL :table-header-note:`(lower is better)`
* - BF16 reference
- 78.69
- 78.04
- 73.40
- 87.64
- 0.00
- 9.20
* - Max scale
- 75.81
- 76.33
- 70.64
- 74.60
- 5.10
- 10.08
* - MSE scale
- 76.49
- 76.61
- **72.45**
- 76.72
- 3.87
- 9.98
* - Four-over-six scale
- 75.32
- **76.62**
- 70.40
- 76.42
- 4.75
- 10.02
* - Local-Hessian scale
- **76.81**
- 76.50
- 71.19
- **80.89**
- :local-hessian-result:`3.10`
- :local-hessian-result:`9.90`
.. rst-class:: table-note
**Table 1: Scale selection algorithm comparison.** All layers except the final
output layer (``lm_head``) use NVFP4 weight and activation quantization (W4A4).
Local-Hessian With GPTQ
=================================
Local-Hessian changes scales; GPTQ [3]_ changes weight rounding to
minimize per-layer output error. The two are orthogonal, so they
compose: Local-Hessian rounds to nearest (RTN) by default, and GPTQ can
replace that rounding step once the scales are set. In Table 2, we show
that Local-Hessian scales improve GPTQ as well.
Two things stand out:
#. Local-Hessian scale selection alone (3.10 average drop) beats GPTQ with
max scales (4.84).
#. Combining Local-Hessian scales with GPTQ improves the average drop further,
from 3.10 to 2.94.
.. list-table::
:header-rows: 1
* - Method
- MMLU
- HellaSwag
- WinoGrande
- GSM8K
- Average drop
- WikiText PPL
* - Max scale + GPTQ
- 75.77
- 76.51
- 70.17
- 75.97
- 4.84
- 10.02
* - Local-Hessian scale + GPTQ
- **76.98**
- **76.59**
- 70.96
- 81.50
- :local-hessian-result:`2.94`
- :local-hessian-result:`9.91`
.. rst-class:: table-note
**Table 2: GPTQ composition on Qwen3.5-9B, NVFP4 W4A4.**
.. note::
Composition is not always a win: on Qwen3.8-27B, Local-Hessian + GPTQ scored
below Local-Hessian alone, so the published checkpoint uses Local-Hessian
only. This shows the best algorithm could vary depending on the model.
Scale Selection Reshapes Distribution
*************************************
Figure 2 plots the scaled weights :math:`W/s` -- the values handed to the
E2M1 cast. Max scaling piles the mass up near 6.0, the largest E2M1 value,
while MSE and Local-Hessian both cluster it on the representable E2M1 grid
values. Grid alignment alone does not explain the accuracy gains. Although
MSE and Local-Hessian both align weights to the E2M1 grid, Local-Hessian's
layer-output-error objective yields larger downstream accuracy improvements
in our results.
.. image:: assets/qwen3-27b-scaled-weight-distribution.png
:alt: Scaled weight distribution for max, MSE and Local-Hessian scales against the NVFP4 E2M1 grid
:width: 100%
.. rst-class:: table-note
**Figure 2. Scaled weight distribution across algorithms.** Blue
dashed lines are the NVFP4 E2M1 representable values. Distribution is for the
first gate-projection layer of Qwen3.8-27B.
Just Better Scales, No Runtime Cost
***********************************
Local-Hessian and the other ModelOpt scale-selection algorithms for
NVFP4 weight scales are free. Weight scales are computed only once, at
checkpoint creation, and that same scale is reused on every
deployment. Selecting scales this way improves accuracy without
incurring any deployment throughput penalty.
Using Local-Hessian
*******************
See the
:func:`local_hessian_calibrate API <modelopt.torch.quantization.model_calib.local_hessian_calibrate>`
for the calibration entry point.
To use it in your own configuration, set the ``algorithm`` field:
.. code-block:: python
import modelopt.torch.quantization as mtq
config = {
"quant_cfg": [...], # quantizer configuration
"algorithm": {
"method": "local_hessian",
"layerwise": {"enable": True},
},
}
model = mtq.quantize(model, config, forward_loop)
See :ref:`quant-cfg` for how to write the ``quant_cfg`` field.
To reproduce the published Qwen3.8-27B checkpoint end to end:
.. code-block:: bash
python examples/hf_ptq/hf_ptq.py \
--pyt_ckpt_path Qwen/Qwen3.8-27B \
--recipe modelopt_recipes/models/Qwen/Qwen3.8-27B/ptq/nvfp4_w4a4_mlp_fp8_attn_local_hessian.yaml \
--dataset nemotron-post-training-v3 \
--calib_size 512 \
--calib_seq 2048 \
--batch_size 1 \
--export_path <export_dir>
.. note::
For Local-Hessian and GPTQ, we recommend enabling
:func:`layerwise_calibrate <modelopt.torch.quantization.model_calib.layerwise_calibrate>`
with ``"layerwise": {"enable": True}`` and using calibration batch size 1.
Layerwise calibration exposes each layer to the fake-quantized outputs of the
preceding layer, approximating quantized deployment, while batch size 1 prevents
padding tokens from contaminating activation statistics.
Next steps
**********
- **Adapt Local-Hessian for sparse MoEs.** Many experts in a sparse MoE
see very little calibration data. Local-Hessian workflow needs to be adapted to that
low-data regime.
.. _local-hessian-references:
References
**********
.. [1] E. Alvarez, O. Almog, E. Chung, S. Layton, D. Stosic, R. Krashinsky,
and K. Aubrey. `Introducing NVFP4 for Efficient and Accurate Low-Precision
Inference <https://developer.nvidia.com/blog/introducing-nvfp4-for-efficient-and-accurate-low-precision-inference/>`_.
NVIDIA Technical Blog, 2025.
.. [2] J. Cook, J. Guo, G. Xiao, Y. Lin, K. Wyss, M. Nazemi, A. Mishra,
C. del Mundo, T. Blankevoort, and S. Han. `Four Over Six: More Accurate
NVFP4 Quantization with Adaptive Block Scaling
<https://arxiv.org/abs/2512.02010>`_. arXiv:2512.02010, 2025.
.. [3] E. Frantar, S. Ashkboos, T. Hoefler, and D. Alistarh. `GPTQ: Accurate
Post-Training Quantization for Generative Pre-trained Transformers
<https://arxiv.org/abs/2210.17323>`_. ICLR, 2023.