Skip to content

Add FP8 Vision Encoder quantization for Qwen3-VL and Qwen3.5 - #2083

Open
mpariente-nvda wants to merge 1 commit into
NVIDIA:mainfrom
mpariente-nvda:feat/vision-encoder-quantization
Open

Add FP8 Vision Encoder quantization for Qwen3-VL and Qwen3.5#2083
mpariente-nvda wants to merge 1 commit into
NVIDIA:mainfrom
mpariente-nvda:feat/vision-encoder-quantization

Conversation

@mpariente-nvda

@mpariente-nvda mpariente-nvda commented Aug 5, 2026

Copy link
Copy Markdown

What does this PR do?

Type of change: New feature

Adds opt-in FP8 Vision Encoder quantization recipes for Qwen3-VL and dense Qwen3.5:

  • fp8_vision-kv_none: FP8 Vision Encoder Linears, with the LLM and KV cache kept in high precision.
  • fp8_vision_lm-kv_fp8_cast: FP8 Vision Encoder and LLM Linears, with FP8 KV-cache cast.

Patch embedding and vision-attention BMM operands remain in high precision. Recipe-based image calibration now keeps the complete VLM so multimodal inputs exercise the selected vision and language quantizers. The existing non-recipe VLM path remains language-model-only.

Usage

# Vision Encoder only
python examples/hf_ptq/hf_ptq.py \
  --pyt_ckpt_path <Qwen3-VL-checkpoint> \
  --recipe huggingface/qwen3_vl/ptq/fp8_vision-kv_none \
  --calib_with_images \
  --calib_size 512 \
  --skip_generate \
  --export_path <output-checkpoint>

# Vision Encoder + LLM + KV cache
python examples/hf_ptq/hf_ptq.py \
  --pyt_ckpt_path <Qwen3-VL-checkpoint> \
  --recipe huggingface/qwen3_vl/ptq/fp8_vision_lm-kv_fp8_cast \
  --calib_with_images \
  --calib_size 512 \
  --skip_generate \
  --export_path <output-checkpoint>

For dense Qwen3.5, replace qwen3_vl with qwen3_5 in the recipe path.

Testing

  • Validated recipe selection, image calibration, and GPU calibration/export for Qwen3-VL and Qwen3.5, in both vision-only and joint configurations.
  • Consolidated test run: 259 passed.
  • Ruff, recipe validation, and git diff --check passed.
  • Transformers 4.57 compatibility verified for Qwen3-VL; Qwen3.5 tests capability-skip when the required Transformers classes are unavailable.

Deployment evidence with Qwen3-VL-2B on RTX PRO 6000 BSE, eight fixed frames and a BF16 LLM:

Configuration Accuracy mean Vision Encoder kernel time Full-request GPU kernel time
BF16 48.75 25.45 ms 47.43 ms
Standard FP8 48.42 19.35 ms (24.0% faster) 41.39 ms (12.7% faster)

The accuracy mean covers MMMU, RealWorldQA, Video-MMMU, MVBench, and Video-MME. Serving reached 7.7% lower end-to-end latency and 7.9% higher throughput at concurrency 16.

Runtime support for quantized Vision Encoder Linears is separate from this ModelOpt checkpoint-generation change.

Before your PR is "Ready for review"

  • Is this change backward compatible?: ❌ — recipe-based image calibration now applies recipes to the complete VLM; custom recipes must explicitly exclude vision modules when required.
  • 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?: ✅
  • Did you update Changelog?: ✅
  • Did you get Claude approval on this PR?: ❌ — will run /claude review after opening the PR.

Additional Information

Summary by CodeRabbit

  • New Features
    • Added opt-in FP8 vision quantization recipes for Qwen3-VL and Qwen3.5.
    • Added NVFP4 activation headroom calibration and recipe support.
    • Added MLflow run logging and CLI integration for Hugging Face PTQ.
  • Bug Fixes
    • Improved multimodal image-text calibration by forwarding complete vision-language inputs while targeting only the selected component.
  • Documentation
    • Expanded support guidance, commands, calibration requirements, precision boundaries, and runtime limitations for vision quantization.

…and Qwen3.5

Signed-off-by: mpariente <mpariente@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Qwen Vision FP8 PTQ

Layer / File(s) Summary
Multimodal PTQ target and calibration flow
examples/hf_ptq/hf_ptq.py, tests/examples/hf_ptq/*
Recipe and AutoQuantize flows retain the complete VLM. Image calibration forwards batches through the complete VLM while instrumenting the selected target.
FP8 vision recipe definitions and documentation
modelopt_recipes/huggingface/qwen3_vl/ptq/*, modelopt_recipes/huggingface/qwen3_5/ptq/*, modelopt_recipes/ptq.md, examples/hf_ptq/README.md, CHANGELOG.rst
Adds vision-only and joint FP8 recipes for Qwen3-VL and Qwen3.5. The recipes preserve selected components in high precision and optionally cast KV caches to FP8.
Recipe compatibility and validation
tests/_test_utils/torch/transformers_models.py, tests/unit/recipe/*, tests/gpu/torch/export/*
Adds offline Qwen3.5-VL model construction and validates quantizer selection, calibration, exported metadata, and expected scale tensors.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: sugunav14

Sequence Diagram(s)

sequenceDiagram
  participant hf_ptq.py
  participant ImageCalibrationDataloader
  participant FullVLM
  participant Quantizer
  hf_ptq.py->>ImageCalibrationDataloader: load image-text batches
  ImageCalibrationDataloader->>FullVLM: forward multimodal batches
  FullVLM->>Quantizer: instrument selected quantization target
  Quantizer-->>hf_ptq.py: return quantized model
Loading
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding FP8 Vision Encoder quantization for Qwen3-VL and Qwen3.5.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Anti-Patterns ✅ Passed The PR adds no forbidden loader settings, hardcoded trust_remote_code=True, external eval/exec, or # nosec; no modelopt or dependency-manifest files changed.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

CodeRabbit couldn't request changes on this pull request because it doesn't have sufficient GitHub permissions.

Please grant CodeRabbit Pull requests: Read and write permission and re-run the review.

👉 Steps to fix this

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/hf_ptq/hf_ptq.py`:
- Around line 666-674: Require --calib_with_images when running vision FP8
recipes, including the corresponding validation path near the
recipe/auto-quantization handling and the related logic around this block.
Reject the configuration with a clear error before calibration starts, while
preserving existing behavior for non-vision recipes and image-enabled runs, and
add a regression test covering the rejection.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5ed83f54-53df-4161-bb6a-ff16600c34dd

📥 Commits

Reviewing files that changed from the base of the PR and between 19e0121 and 80cc5b2.

📒 Files selected for processing (13)
  • CHANGELOG.rst
  • examples/hf_ptq/README.md
  • examples/hf_ptq/hf_ptq.py
  • modelopt_recipes/huggingface/qwen3_5/ptq/fp8_vision-kv_none.yaml
  • modelopt_recipes/huggingface/qwen3_5/ptq/fp8_vision_lm-kv_fp8_cast.yaml
  • modelopt_recipes/huggingface/qwen3_vl/ptq/fp8_vision-kv_none.yaml
  • modelopt_recipes/huggingface/qwen3_vl/ptq/fp8_vision_lm-kv_fp8_cast.yaml
  • modelopt_recipes/huggingface/qwen3_vl/ptq/vision_fp8.quant_cfg.yaml
  • modelopt_recipes/ptq.md
  • tests/_test_utils/torch/transformers_models.py
  • tests/examples/hf_ptq/test_hf_ptq_vision_quantization.py
  • tests/gpu/torch/export/test_qwen_vision_recipe_export.py
  • tests/unit/recipe/test_qwen_vision_recipe.py

Comment thread examples/hf_ptq/hf_ptq.py
Comment on lines +666 to +674
# Plain PTQ quantizes only the language model. Recipes and AutoQuantize keep the complete
# VLM so their quantizer rules can target vision and language components in one state.
if args.recipe is None and args.auto_quantize_bits is None:
extracted_lm, extracted_model_type = extract_and_prepare_language_model_from_vl(
full_model
)
if extracted_lm is not None:
language_model = extracted_lm
model_type = extracted_model_type

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require image calibration for the vision FP8 recipes.

Without --calib_with_images, this flow retains the complete VLM but creates a text-only calibration loop. That loop does not execute visual, so the enabled *visual.*input_quantizer entries receive no activation calibration data. Reject vision FP8 recipes without --calib_with_images, and add a regression test for that error.

Also applies to: 788-792

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/hf_ptq/hf_ptq.py` around lines 666 - 674, Require
--calib_with_images when running vision FP8 recipes, including the corresponding
validation path near the recipe/auto-quantization handling and the related logic
around this block. Reject the configuration with a clear error before
calibration starts, while preserving existing behavior for non-vision recipes
and image-enabled runs, and add a regression test covering the rejection.

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.16%. Comparing base (19e0121) to head (80cc5b2).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2083      +/-   ##
==========================================
+ Coverage   67.15%   67.16%   +0.01%     
==========================================
  Files         521      521              
  Lines       59857    59857              
==========================================
+ Hits        40199    40205       +6     
+ Misses      19658    19652       -6     
Flag Coverage Δ
unit 55.39% <ø> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment thread examples/hf_ptq/hf_ptq.py
# Image calibration batches contain multimodal kwargs (for example pixel_values).
# They must be consumed by the complete VLM even when only a nested component is the
# quantization target; the full forward still exercises that component's quantizers.
if args.calib_with_images:

@juhi10071998 juhi10071998 Aug 5, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Edwardf0t1 , overall looks good to me. I have a comment though about the behavior change for non-Nemotron VLM models that were previously running with -calib-with-image flag

Before this PR, users were (unknowingly) getting a text-only calibration loop even with the flag set. After this PR, the full VLM forward runs — pixel values go through the vision encoder, get projected into the LM's embedding space, and the LM quantizers see those activations. That changes the amax distributions and therefore the output scales, even when the vision encoder itself has no quantizers enabled.

So a Qwen2.5-VL or Phi-4-multimodal user running the same --calib_with_images PTQ command before and after this PR will get a different quantized checkpoint.

If my understanding is correct, should we mention this somewhere in the CHANGELOG so the users are aware of this behavior change?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the full VLM forward now makes the LM quantizers observe vision conditioned activations, so existing --calib_with_images commands may produce different activation ranges and scales. The vision branch still remains unquantized unless explicitly enabled by a recipe. The calibration fix is briefly mentioned in the CHANGELOG, but I can expand the entry to make the checkpoint impact explicit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants