Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Changelog
*Quantization*

- Add an end-to-end PETRv1 and PETRv2 ONNX PTQ example with nuScenes calibration and accuracy evaluation, INT8 and FP8 backbone quantization, FP16 heads, and TensorRT engine benchmarking.
- Add an end-to-end BEVFormer ONNX PTQ example with temporal calibration data generation, INT8 and FP8 quantization, TensorRT engine building, and nuScenes accuracy evaluation. See `examples/onnx_ptq/bevformer/README.md <https://github.com/NVIDIA/Model-Optimizer/tree/main/examples/onnx_ptq/bevformer>`_ for details.
- Add the ``nvfp4_act_headroom`` calibration algorithm for NVFP4 **activation** global scales. Instead of setting the global scale from the largest per-block amax seen during calibration (plain ``max``, which leaves no room above it so any larger activation saturates), it anchors the scale to a low percentile of the per-block amax distribution, leaving the rest of the FP8 block-scale range as headroom: ``amax = max(rho * anchor, upper)``, where ``anchor`` and ``upper`` are the per-block amaxes at ``anchor_percentile`` (default 1) and ``upper_percentile`` (default 99.99; set to 100 to never clip calibration data), and ``rho`` (default 16384) is the headroom factor. Applies only to NVFP4 dynamic-block input quantizers; ``SequentialQuantizer`` activation quantizers raise. Weight scales are an orthogonal axis selected by a nested ``weight_scale_algorithm`` (``max`` by default, or ``mse`` / ``local_hessian``), so one recipe can combine a weight calibration with this activation policy in a single pass. Ships ``modelopt_recipes/general/ptq/nvfp4_act_headroom-kv_fp8_cast.yaml``, which mirrors ``nvfp4_default-kv_fp8_cast`` with only the calibration algorithm swapped and exports a standard NVFP4 checkpoint.

*Megatron Framework (M-LM / M-Bridge)*
Expand Down
4 changes: 4 additions & 0 deletions examples/onnx_ptq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ Inference latency of the model is <X> ms

The [FAR3D example](./far3d/) demonstrates an end-to-end workflow that exports and quantizes the FAR3D ONNX image encoder, builds TensorRT engines, and evaluates 3D object detection mAP on the Argoverse 2 validation set.

### BEVFormer 3D object detection

The [BEVFormer example](./bevformer/) exports BEVFormer-tiny to ONNX, generates temporal calibration data, quantizes the model to INT8 or FP8, builds TensorRT engines, and evaluates NDS and mAP on the nuScenes validation set.

### PETR 3D object detection

The [PETR example](./petr/) demonstrates an end-to-end workflow that exports and quantizes PETRv1 and PETRv2 ONNX backbones and heads, builds TensorRT engines, and evaluates 3D object detection mAP on the nuScenes validation set.
Expand Down
85 changes: 85 additions & 0 deletions examples/onnx_ptq/bevformer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

FROM nvcr.io/nvidia/pytorch:26.01-py3

ARG NUM_JOBS=8
ARG DL4AGX_COMMIT=9f7b29104c253d5bc68334e7b83b3eecb72d4572
ARG BEVFORMER_TRT_COMMIT=303d3140c14016047c07f9db73312af364f0dd7c
ARG TENSORRT_COMMIT=3b4ddc1d45f11167f2ea53dc6046bba8df800e2d
ARG MMCV_COMMIT=235c0253ab8806a2a2ee6954b4258a95358497ac
ARG MMDET_COMMIT=3b72b12fe9b14de906d1363982b9fba05e7d47c1

ENV FORCE_CUDA=1
ENV TORCH_CUDA_ARCH_LIST="7.5;8.0;8.6;8.9+PTX"
ENV TRT_LIBPATH=/usr/lib/x86_64-linux-gnu
ENV LD_LIBRARY_PATH=/usr/local/cuda/compat/lib:/usr/local/nvidia/lib:/usr/local/nvidia/lib64:/usr/lib/x86_64-linux-gnu:/usr/local/cuda-13.1/targets/x86_64-linux/lib:${LD_LIBRARY_PATH}

RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
libgl1 \
libglib2.0-0 && \
rm -rf /var/lib/apt/lists/*

COPY . /opt/Model-Optimizer
COPY examples/onnx_ptq/bevformer/requirements.txt /tmp/bevformer-requirements.txt
# Stable ONNX Runtime wheels do not support the base image's CUDA 13 runtime.
RUN env -u PIP_CONSTRAINT python -m pip install --no-cache-dir \
--upgrade pip setuptools wheel && \
env -u PIP_CONSTRAINT python -m pip install --no-cache-dir cuda-python==13.1.0 && \
env -u PIP_CONSTRAINT python -m pip install --no-cache-dir \
-e "/opt/Model-Optimizer[onnx]" && \
env -u PIP_CONSTRAINT python -m pip install --no-cache-dir --pre \
--index-url https://aiinfra.pkgs.visualstudio.com/PublicPackages/_packaging/ort-cuda-13-nightly/pypi/simple/ \
--force-reinstall --no-deps \
onnxruntime-gpu==1.24.0.dev20260123002 && \
env -u PIP_CONSTRAINT python -m pip install --no-cache-dir \
-r /tmp/bevformer-requirements.txt

RUN git clone https://github.com/NVIDIA/DL4AGX.git /workspace/DL4AGX && \
git -C /workspace/DL4AGX checkout ${DL4AGX_COMMIT} && \
git clone https://github.com/DerryHub/BEVFormer_tensorrt.git \
/workspace/BEVFormer_tensorrt && \
git -C /workspace/BEVFormer_tensorrt checkout ${BEVFORMER_TRT_COMMIT} && \
git -C /workspace/BEVFormer_tensorrt apply \
/workspace/DL4AGX/AV-Solutions/bevformer-int8-eq/bevformer_trt10.patch

RUN git clone https://github.com/NVIDIA/TensorRT.git /workspace/TensorRT && \
git -C /workspace/TensorRT checkout ${TENSORRT_COMMIT} && \
cd /workspace/TensorRT/tools/pytorch-quantization && \
MAX_JOBS=${NUM_JOBS} env -u PIP_CONSTRAINT python setup.py install

RUN git clone https://github.com/open-mmlab/mmcv.git \
/workspace/BEVFormer_tensorrt/third_party/mmcv && \
git -C /workspace/BEVFormer_tensorrt/third_party/mmcv checkout ${MMCV_COMMIT} && \
sed -i 's/c++14/c++17/g' \
/workspace/BEVFormer_tensorrt/third_party/mmcv/setup.py && \
env -u PIP_CONSTRAINT python -m pip install --no-cache-dir \
-r /workspace/BEVFormer_tensorrt/third_party/mmcv/requirements/optional.txt && \
cd /workspace/BEVFormer_tensorrt/third_party/mmcv && \
MAX_JOBS=${NUM_JOBS} MMCV_WITH_OPS=1 \
env -u PIP_CONSTRAINT python -m pip install --no-cache-dir -v -e . \
--no-build-isolation

RUN git clone https://github.com/open-mmlab/mmdetection.git \
/workspace/BEVFormer_tensorrt/third_party/mmdetection && \
git -C /workspace/BEVFormer_tensorrt/third_party/mmdetection checkout ${MMDET_COMMIT} && \
env -u PIP_CONSTRAINT python -m pip install --no-cache-dir -v \
/workspace/BEVFormer_tensorrt/third_party/mmdetection --no-build-isolation && \
cd /workspace/BEVFormer_tensorrt/third_party/bev_mmdet3d && \
MAX_JOBS=${NUM_JOBS} env -u PIP_CONSTRAINT \
python -m pip install --no-cache-dir -v -e . \
--no-build-isolation

WORKDIR /workspace/BEVFormer_tensorrt
208 changes: 208 additions & 0 deletions examples/onnx_ptq/bevformer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
# BEVFormer ONNX PTQ and nuScenes evaluation

This example exports BEVFormer-tiny to ONNX, prepares temporal calibration data, quantizes the model to INT8 or FP8 with Model Optimizer, builds TensorRT engines, and evaluates NDS and mAP on the nuScenes validation set. It extends the [NVIDIA DL4AGX BEVFormer INT8 workflow](https://github.com/NVIDIA/DL4AGX/tree/9f7b29104c253d5bc68334e7b83b3eecb72d4572/AV-Solutions/bevformer-int8-eq) with FP8 and reuses the calibration reader shared by the Model Optimizer FAR3D and PETR examples.

The container pins [BEVFormer_tensorrt](https://github.com/DerryHub/BEVFormer_tensorrt/tree/303d3140c14016047c07f9db73312af364f0dd7c) and applies the TensorRT 10 compatibility patch from DL4AGX. The exported model uses custom TensorRT plugins, so export, calibration, engine building, and evaluation must use the plugin library built in the container.

## 1. Build and start the container

Build from the Model Optimizer repository root:

```bash
docker build \
-f examples/onnx_ptq/bevformer/Dockerfile \
-t bevformer-modelopt .
```

Download the nuScenes v1.0 trainval set and CAN bus expansion data. Use the dataset, CAN bus data, and checkpoint only under their upstream terms, including the [nuScenes terms of use](https://www.nuscenes.org/terms-of-use). Start the container with the dataset and an artifact directory mounted:

```bash
mkdir -p bevformer_artifacts
docker run --rm -it --gpus=all --network=host --shm-size=20g \
-v /path/to/nuscenes:/workspace/BEVFormer_tensorrt/data/nuscenes \
-v /path/to/can_bus:/workspace/BEVFormer_tensorrt/data/can_bus \
-v "$(pwd)/bevformer_artifacts:/artifacts" \
bevformer-modelopt
```

The remaining commands run inside the container:

```bash
export BEVFORMER_ROOT=/workspace/BEVFormer_tensorrt
export DL4AGX_ROOT=/workspace/DL4AGX
export MODELOPT_ROOT=/opt/Model-Optimizer
export PLUGIN_PATH=${BEVFORMER_ROOT}/TensorRT/lib/libtensorrt_ops.so
export CONFIG=${BEVFORMER_ROOT}/configs/bevformer/plugin/bevformer_tiny_trt_p2.py
cd ${BEVFORMER_ROOT}
```

Build the custom plugins inside the GPU-enabled container. The build targets the compute capability of its active GPU:

```bash
cmake -S ${BEVFORMER_ROOT}/TensorRT \
-B ${BEVFORMER_ROOT}/TensorRT/build \
-DCMAKE_TENSORRT_PATH=/usr
cmake --build ${BEVFORMER_ROOT}/TensorRT/build --parallel
cmake --install ${BEVFORMER_ROOT}/TensorRT/build
```

Generate the temporal train and validation metadata required by BEVFormer:

```bash
bash samples/bevformer/create_data.sh
```

This creates the following files in the mounted nuScenes directory:

```text
nuscenes/
├── nuscenes_infos_temporal_train.pkl
└── nuscenes_infos_temporal_val.pkl
```

## 2. Export BEVFormer to ONNX

Download the published BEVFormer-tiny checkpoint:

```bash
wget --continue \
-O /artifacts/bevformer_tiny_epoch_24.pth \
https://github.com/zhiqi-li/storage/releases/download/v1.0/bevformer_tiny_epoch_24.pth

echo "7305046dbaa4fe8b1fa6d6acb9e0e3d605a70a3c473f763e936103428d2b2f12 /artifacts/bevformer_tiny_epoch_24.pth" | \
sha256sum --check
```

Export the `nv_half2` plugin variant at opset 13 and copy it to the artifact directory:

```bash
python tools/pth2onnx.py \
${CONFIG} \
/artifacts/bevformer_tiny_epoch_24.pth \
--opset_version=13 \
--cuda \
--flag=cp2_op13

cp checkpoints/onnx/bevformer_tiny_epoch_24_cp2_op13.onnx /artifacts/
export ONNX_PATH=/artifacts/bevformer_tiny_epoch_24_cp2_op13.onnx
```

Post-process a copy for ONNX Runtime. This copy is used only while generating calibration data; quantization uses the original export:

```bash
python ${DL4AGX_ROOT}/AV-Solutions/bevformer-int8-eq/tools/onnx_postprocess.py \
--onnx=${ONNX_PATH} \
--trt_plugins=${PLUGIN_PATH}

export CALIBRATION_ONNX=/artifacts/bevformer_tiny_epoch_24_cp2_op13_post.onnx
```

## 3. Prepare calibration data

Generate 600 temporal samples from the nuScenes training split:

```bash
PYTHONPATH=${BEVFORMER_ROOT} \
python ${MODELOPT_ROOT}/examples/onnx_ptq/bevformer/prepare_calibration.py \
${CONFIG} \
--onnx=${CALIBRATION_ONNX} \
--trt-plugin=${PLUGIN_PATH} \
--output-dir=/artifacts/calibration \
--num-samples=600
```

The script runs the post-processed model with ONNX Runtime and carries `prev_bev` across frames while resetting it between scenes. It saves one NPZ file per sample instead of building one large archive in memory. The output directory must be empty.

## 4. Quantize to INT8 and FP8

INT8 uses entropy calibration by default. All `MatMul` nodes remain in FP16, matching the DL4AGX recommendation:

```bash
python ${MODELOPT_ROOT}/examples/onnx_ptq/bevformer/quantize.py \
--onnx=${ONNX_PATH} \
--calibration-dir=/artifacts/calibration \
--trt-plugins=${PLUGIN_PATH} \
--quantization-mode=int8 \
--output=/artifacts/bevformer_tiny_epoch_24_cp2_op13.int8.onnx
```

FP8 uses max calibration by default and requires a GPU with compute capability 8.9 or later:

```bash
python ${MODELOPT_ROOT}/examples/onnx_ptq/bevformer/quantize.py \
--onnx=${ONNX_PATH} \
--calibration-dir=/artifacts/calibration \
--trt-plugins=${PLUGIN_PATH} \
--quantization-mode=fp8 \
--output=/artifacts/bevformer_tiny_epoch_24_cp2_op13.fp8.onnx
```

The custom TensorRT plugins and all `MatMul` nodes remain at higher precision. Model Optimizer automatically upgrades the FP8 model to the required ONNX opset.

## 5. Build TensorRT engines

Build the FP16 baseline from the original ONNX model:

```bash
trtexec \
--onnx=${ONNX_PATH} \
--saveEngine=/artifacts/bevformer_tiny_epoch_24_cp2_op13.fp16.engine \
--staticPlugins=${PLUGIN_PATH} \
--fp16 \
--skipInference
```

Build strongly typed engines from the explicitly quantized models:

```bash
for precision in int8 fp8; do
trtexec \
--onnx=/artifacts/bevformer_tiny_epoch_24_cp2_op13.${precision}.onnx \
--saveEngine=/artifacts/bevformer_tiny_epoch_24_cp2_op13.${precision}.engine \
--staticPlugins=${PLUGIN_PATH} \
--stronglyTyped \
--skipInference
done
```

Serialized TensorRT engines are specific to the TensorRT version and GPU architecture used to build them.

## 6. Evaluate accuracy and latency

Evaluate all 6,019 nuScenes validation samples:

```bash
for precision in fp16 int8 fp8; do
python tools/bevformer/evaluate_trt.py \
${CONFIG} \
/artifacts/bevformer_tiny_epoch_24_cp2_op13.${precision}.engine \
--trt_plugins=${PLUGIN_PATH} | \
tee /artifacts/evaluate_${precision}.log
done
```

Measure TensorRT GPU compute time independently of data loading and post-processing:

```bash
for precision in fp16 int8 fp8; do
trtexec \
--loadEngine=/artifacts/bevformer_tiny_epoch_24_cp2_op13.${precision}.engine \
--staticPlugins=${PLUGIN_PATH} \
--warmUp=1000 \
--duration=10 \
--iterations=100 | \
tee /artifacts/trtexec_${precision}.log
done
```

## Results on nuScenes validation

Measurements use an NVIDIA RTX 6000 Ada Generation GPU (compute capability 8.9), TensorRT 10.14.1.48, CUDA 13.1, and ONNX Runtime 1.24.0.dev20260123002. Quantization uses 600 training samples. NDS and mAP are produced after all 6,019 validation samples complete; GPU compute time is the median reported by the command above and excludes data loading and post-processing.

The INT8 and FP8 rows are mixed-precision graphs. Quantized operators use the listed format, while the custom TensorRT plugins, excluded `MatMul` nodes, and other unsupported paths remain in FP16. External inputs and outputs remain FP32.

| Precision | TensorRT GPU compute time (median, ms) | NDS | mAP |
| --- | ---: | ---: | ---: |
| FP16 | 4.597 | 0.3546 | 0.2515 |
| INT8/FP16 | 3.180 | 0.3512 | 0.2505 |
| FP8/FP16 | 4.106 | 0.3526 | 0.2489 |
Loading