-
Notifications
You must be signed in to change notification settings - Fork 535
Add FP8 Vision Encoder quantization for Qwen3-VL and Qwen3.5 #2083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -663,11 +663,15 @@ def load_model(args: argparse.Namespace): | |
| default_padding_side = tokenizer.padding_side | ||
| tokenizer.padding_side = "left" | ||
|
|
||
| # Quantize only the language model, but keep the full_model for calibration forward. | ||
| 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 | ||
| # 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 | ||
| else: | ||
| if args.specdec_offline_dataset is not None: | ||
| language_model = full_model | ||
|
|
@@ -762,7 +766,7 @@ def mono_quantize( | |
| calib_dataloader: DataLoader, | ||
| is_nemotron_vl_model: bool, | ||
| ): | ||
| """Plain quantization of the given language model to a single quantization configuration.""" | ||
| """Plain quantization of the selected model target to one quantization configuration.""" | ||
|
|
||
| model_is_already_quantized = is_quantized(language_model) | ||
|
|
||
|
|
@@ -781,9 +785,10 @@ def mono_quantize( | |
| warnings.warn("Dynamic quantization. Calibration skipped.") | ||
| calibrate_loop = None | ||
| if use_calibration: | ||
| # For Nemotron VL image calibration, the dataloader yields multimodal kwargs (e.g., pixel_values). | ||
| # Those kwargs must be consumed by the *full* VLM model, not the extracted language_model. | ||
| if args.calib_with_images and is_nemotron_vl_model: | ||
| # 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: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||
| calibrate_loop = create_vlm_calibration_loop(full_model, calib_dataloader) | ||
| else: | ||
| calibrate_loop = create_forward_loop( | ||
|
|
@@ -801,7 +806,7 @@ def mono_quantize( | |
| language_model = mtq.quantize(language_model, quant_cfg, forward_loop=calibrate_loop) | ||
|
|
||
| # For VL models, update full_model to use the quantized language model | ||
| if is_nemotron_vl_model: | ||
| if is_nemotron_vl_model and language_model is not full_model: | ||
| language_model_lineage = get_language_model_from_vl(full_model) | ||
| if language_model_lineage is not None: | ||
| print("Updating full_model with quantized language_model...") | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # 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. | ||
|
|
||
| # Dense Qwen3.5 Vision Encoder FP8 with a high-precision language model and KV cache. | ||
|
|
||
| imports: | ||
| base_disable_all: configs/ptq/units/base_disable_all | ||
| vision_fp8: huggingface/qwen3_vl/ptq/vision_fp8.quant_cfg | ||
|
|
||
| metadata: | ||
| recipe_type: ptq | ||
| description: >- | ||
| FP8 quantization of dense Qwen3.5 Vision Encoder Linear layers, including any merger Linears; | ||
| the language model, KV cache, patch embedding, and vision-attention operands remain in high | ||
| precision. | ||
|
|
||
| quantize: | ||
| algorithm: max | ||
| quant_cfg: | ||
| - $import: base_disable_all | ||
| - $import: vision_fp8 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # 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. | ||
|
|
||
| # Dense Qwen3.5 Vision Encoder and language model FP8 with FP8 KV-cache cast. | ||
|
|
||
| imports: | ||
| base_disable_all: configs/ptq/units/base_disable_all | ||
| default_disabled_quantizers: configs/ptq/units/default_disabled_quantizers | ||
| kv_fp8_cast: configs/ptq/units/kv_fp8_cast | ||
| vision_fp8: huggingface/qwen3_vl/ptq/vision_fp8.quant_cfg | ||
| w8a8_fp8_fp8: configs/ptq/units/w8a8_fp8_fp8 | ||
|
|
||
| metadata: | ||
| recipe_type: ptq | ||
| description: >- | ||
| W8A8 FP8 quantization of dense Qwen3.5 Vision Encoder and language-model Linear layers, | ||
| including any merger Linears, with FP8 KV-cache cast; patch embedding and vision-attention | ||
| operands remain in high precision. | ||
|
|
||
| quantize: | ||
| algorithm: max | ||
| quant_cfg: | ||
| - $import: base_disable_all | ||
| - $import: w8a8_fp8_fp8 | ||
| - $import: kv_fp8_cast | ||
| - $import: default_disabled_quantizers | ||
| # Re-enable visual Linears after the standard vision exclusion. The imported snippet also | ||
| # keeps vision-attention BMM quantizers disabled as a final defense-in-depth rule. | ||
| - $import: vision_fp8 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # 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. | ||
|
|
||
| # Qwen3-VL Vision Encoder FP8 with a high-precision language model and KV cache. | ||
|
|
||
| imports: | ||
| base_disable_all: configs/ptq/units/base_disable_all | ||
| vision_fp8: huggingface/qwen3_vl/ptq/vision_fp8.quant_cfg | ||
|
|
||
| metadata: | ||
| recipe_type: ptq | ||
| description: >- | ||
| FP8 quantization of Qwen3-VL Vision Encoder Linear layers, including primary and deepstack | ||
| merger Linears; the language model, KV cache, patch embedding, and vision-attention operands | ||
| remain in high precision. | ||
|
|
||
| quantize: | ||
| algorithm: max | ||
| quant_cfg: | ||
| - $import: base_disable_all | ||
| - $import: vision_fp8 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| # 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. | ||
|
|
||
| # Qwen3-VL Vision Encoder and language model FP8 with FP8 KV-cache cast. | ||
|
|
||
| imports: | ||
| base_disable_all: configs/ptq/units/base_disable_all | ||
| default_disabled_quantizers: configs/ptq/units/default_disabled_quantizers | ||
| kv_fp8_cast: configs/ptq/units/kv_fp8_cast | ||
| vision_fp8: huggingface/qwen3_vl/ptq/vision_fp8.quant_cfg | ||
| w8a8_fp8_fp8: configs/ptq/units/w8a8_fp8_fp8 | ||
|
|
||
| metadata: | ||
| recipe_type: ptq | ||
| description: >- | ||
| W8A8 FP8 quantization of Qwen3-VL Vision Encoder and language-model Linear layers, including | ||
| primary and deepstack merger Linears, with FP8 KV-cache cast; patch embedding and | ||
| vision-attention operands remain in high precision. | ||
|
|
||
| quantize: | ||
| algorithm: max | ||
| quant_cfg: | ||
| - $import: base_disable_all | ||
| - $import: w8a8_fp8_fp8 | ||
| - $import: kv_fp8_cast | ||
| - $import: default_disabled_quantizers | ||
| # Re-enable visual Linears after the standard vision exclusion. The imported snippet also | ||
| # keeps vision-attention BMM quantizers disabled as a final defense-in-depth rule. | ||
| - $import: vision_fp8 |
There was a problem hiding this comment.
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 executevisual, so the enabled*visual.*input_quantizerentries 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