[Cleanup] Remove online quantization support in `fp8.py` in favor of online shorthands (#53585)
Signed-off-by: Felix Marty <Felix.Marty@amd.com> Signed-off-by: mgoin <mgoin64@gmail.com> Co-authored-by: mgoin <mgoin64@gmail.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
fxmarty-amd committed
Sep 24, 2026 at 21:48 UTC
90a951500699b1aa499f350b3352450cdcc13340
15 files changed
+240
-269
.buildkite/intel_jobs/test-intel.yaml
+3
-3
@@ -38,7 +38,7 @@ steps:
38
python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 -O3 -cc.cudagraph_mode=NONE &&
39
python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager -tp 2 --distributed-executor-backend mp &&
40
python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --attention-backend=TRITON_ATTN &&
41
- python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --quantization fp8 &&
41
+ python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --quantization fp8_per_tensor &&
42
python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --kv-cache-dtype fp8 &&
43
python3 examples/basic/offline_inference/generate.py --model nvidia/Llama-3.1-8B-Instruct-FP8 --block-size 64 --enforce-eager --quantization modelopt --kv-cache-dtype fp8 --attention-backend TRITON_ATTN --max-model-len 4096 &&
44
python3 examples/basic/offline_inference/generate.py --model superjob/Qwen3-4B-Instruct-2507-GPTQ-Int4 --block-size 64 --enforce-eager --max-model-len 8192 &&
@@ -74,8 +74,8 @@ steps:
74
python3 examples/basic/offline_inference/generate.py --linear-backend torch --model RedHatAI/Meta-Llama-3.1-8B-Instruct-FP8 --enforce-eager --max-model-len 4096 &&
75
python3 examples/basic/offline_inference/generate.py --linear-backend xpu --model neuralmagic/Llama-3.2-1B-Instruct-FP8-dynamic --enforce-eager --max-model-len 4096 &&
76
python3 examples/basic/offline_inference/generate.py --linear-backend torch --model Qwen/Qwen3-4B-Instruct-2507-FP8 --enforce-eager --max-model-len 4096 &&
77
- python3 examples/basic/offline_inference/generate.py --linear-backend xpu --model meta-llama/Llama-3.2-1B-Instruct --quantization fp8 --enforce-eager --max-model-len 4096 &&
78
- python3 examples/basic/offline_inference/generate.py --linear-backend torch --model meta-llama/Llama-3.2-1B-Instruct --quantization fp8 --enforce-eager --max-model-len 4096
77
+ python3 examples/basic/offline_inference/generate.py --linear-backend xpu --model meta-llama/Llama-3.2-1B-Instruct --quantization fp8_per_tensor --enforce-eager --max-model-len 4096 &&
78
+ python3 examples/basic/offline_inference/generate.py --linear-backend torch --model meta-llama/Llama-3.2-1B-Instruct --quantization fp8_per_tensor --enforce-eager --max-model-len 4096
79
'
80
- label: "XPU V1 test"
81
key: xpu-v1-test
.buildkite/scripts/hardware_ci/run-intel-ci-test.sh
+3
-3
@@ -17,7 +17,7 @@ case "${test_suite}" in
17
python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 -O3 -cc.cudagraph_mode=NONE
18
python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager -tp 2 --distributed-executor-backend mp
19
python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --attention-backend=TRITON_ATTN
20
- python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --quantization fp8
20
+ python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --quantization fp8_per_tensor
21
python3 examples/basic/offline_inference/generate.py --model facebook/opt-125m --block-size 64 --enforce-eager --kv-cache-dtype fp8
22
python3 examples/basic/offline_inference/generate.py --model nvidia/Llama-3.1-8B-Instruct-FP8 --block-size 64 --enforce-eager --quantization modelopt --kv-cache-dtype fp8 --attention-backend TRITON_ATTN --max-model-len 4096
23
python3 examples/basic/offline_inference/generate.py --model superjob/Qwen3-4B-Instruct-2507-GPTQ-Int4 --block-size 64 --enforce-eager --max-model-len 8192
@@ -30,8 +30,8 @@ case "${test_suite}" in
30
python3 examples/basic/offline_inference/generate.py --linear-backend xpu --model RedHatAI/Meta-Llama-3.1-8B-Instruct-FP8 --enforce-eager --max-model-len 4096
31
python3 examples/basic/offline_inference/generate.py --linear-backend torch --model RedHatAI/Meta-Llama-3.1-8B-Instruct-FP8 --enforce-eager --max-model-len 4096
32
python3 examples/basic/offline_inference/generate.py --linear-backend xpu --model neuralmagic/Llama-3.2-1B-Instruct-FP8-dynamic --enforce-eager --max-model-len 4096
33
- python3 examples/basic/offline_inference/generate.py --linear-backend xpu --model meta-llama/Llama-3.2-1B-Instruct --quantization fp8 --enforce-eager --max-model-len 4096
34
- python3 examples/basic/offline_inference/generate.py --linear-backend torch --model meta-llama/Llama-3.2-1B-Instruct --quantization fp8 --enforce-eager --max-model-len 4096
33
+ python3 examples/basic/offline_inference/generate.py --linear-backend xpu --model meta-llama/Llama-3.2-1B-Instruct --quantization fp8_per_tensor --enforce-eager --max-model-len 4096
34
+ python3 examples/basic/offline_inference/generate.py --linear-backend torch --model meta-llama/Llama-3.2-1B-Instruct --quantization fp8_per_tensor --enforce-eager --max-model-len 4096
35
;;
36
v1)
37
cd tests
docs/features/quantization/llm_compressor/fp8.md
+2
-2
@@ -143,14 +143,14 @@ If you encounter any issues or have feature requests, please open an issue on th
143
144
## Online Dynamic Quantization
145
146
-Dynamic quantization of an original precision BF16/FP16 model to FP8 can be achieved with vLLM without any calibration data required. You can enable the feature by specifying `--quantization="fp8"` in the command line or setting `quantization="fp8"` in the LLM constructor.
146
+Dynamic quantization of an original precision BF16/FP16 model to FP8 can be achieved with vLLM without any calibration data required. You can enable the feature by specifying `--quantization="fp8_per_tensor"` in the command line or setting `quantization="fp8_per_tensor"` in the LLM constructor.
147
148
In this mode, all Linear modules (except for the final `lm_head`) have their weights quantized down to FP8_E4M3 precision with a per-tensor scale. Activations have their minimum and maximum values calculated during each forward pass to provide a dynamic per-tensor scale for high accuracy. As a result, latency improvements are limited in this mode.
149
150
```python
151
from vllm import LLM
152
153
-llm = LLM("facebook/opt-125m", quantization="fp8")
153
+llm = LLM("facebook/opt-125m", quantization="fp8_per_tensor")
154
# INFO 06-10 17:55:42 model_runner.py:157] Loading model weights took 0.1550 GB
155
result = llm.generate("Hello, my name is")
156
print(result[0].outputs[0].text)
tests/compile/passes/test_mla_attn_quant_fusion.py
-1
@@ -271,7 +271,6 @@ class TestMLAAttentionFp8GroupQuantPatternModel(MLAAttentionQuantPatternModel):
271
272
quant_key = kFp8Dynamic128Sym
273
quant_config = Fp8Config(
274
- is_checkpoint_fp8_serialized=True,
274
weight_block_size=[128, 128],
275
)
276
tests/kernels/moe/test_moe_layer.py
+2
-2
@@ -796,11 +796,11 @@ def make_quant_config(
796
return None, QuantizedWeights(w13_weight=w1, w2_weight=w2)
797
798
if quantization == "fp8":
799
- return Fp8Config(True), _quantize_fp8_halves(w1, w2)
799
+ return Fp8Config(), _quantize_fp8_halves(w1, w2)
800
801
if quantization == "fp8_blocked":
802
block_shape = [128, 128]
803
- return Fp8Config(True, weight_block_size=block_shape), _quantize_fp8_halves(
803
+ return Fp8Config(weight_block_size=block_shape), _quantize_fp8_halves(
804
w1, w2, block_shape
805
)
806
tests/kernels/moe/utils.py
+1
-1
@@ -641,7 +641,7 @@ def make_shared_experts_with_weights(
641
if quant_dtype == torch.float8_e4m3fn:
642
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
643
644
- quant_config = Fp8Config(True)
644
+ quant_config = Fp8Config()
645
else:
646
quant_config = None
647
tests/model_executor/layers/test_fused_shared_expert.py
+1
-2
@@ -1227,7 +1227,6 @@ def test_non_quark_shared_expert_fse_is_incompatible() -> None:
1227
1228
def _fp8_config(**kwargs: Any) -> Fp8Config:
1229
return Fp8Config(
1230
- is_checkpoint_fp8_serialized=True,
1230
activation_scheme="dynamic",
1231
weight_block_size=[128, 128],
1232
**kwargs,
@@ -1248,7 +1247,7 @@ def test_block_fp8_shared_expert_fse_is_compatible() -> None:
1247
def test_per_tensor_fp8_shared_expert_fse_is_incompatible() -> None:
1248
"""Per-tensor scales are 0-D, so the shared-expert chunker cannot slice them."""
1249
compatible, reason = is_shared_expert_quant_fse_compatible(
1251
- Fp8Config(is_checkpoint_fp8_serialized=True, activation_scheme="dynamic"),
1250
+ Fp8Config(activation_scheme="dynamic"),
1251
"model.layers.0.mlp.experts",
1252
"model.layers.0.mlp.shared_experts",
1253
)
tests/model_executor/model_loader/test_reload.py
+3
-3
@@ -1158,13 +1158,13 @@ def test_kv_scale_reload(vllm_runner):
1158
"Qwen/Qwen3-0.6B",
1159
"inference-optimization/Qwen3-0.6B-debug-multiply",
1160
"inference-optimization/Qwen3-0.6B-debug-add",
1161
- "fp8",
1161
+ "fp8_per_tensor",
1162
),
1163
pytest.param(
1164
"inference-optimization/DeepSeek-V3-debug-empty",
1165
"inference-optimization/DeepSeek-V3-debug-multiply",
1166
"inference-optimization/DeepSeek-V3-debug-add",
1167
- "fp8",
1167
+ "fp8_per_tensor",
1168
marks=[pytest.mark.slow_test],
1169
),
1170
pytest.param(
@@ -1192,7 +1192,7 @@ def test_online_quantize_reload(
1192
if current_platform.device_count() < tp_size:
1193
pytest.skip(reason="Not enough GPU devices")
1194
1195
- if quantization == "fp8" and _fp8_reload_unsupported():
1195
+ if quantization == "fp8_per_tensor" and _fp8_reload_unsupported():
1196
pytest.skip(reason="Requires FP8 support")
1197
1198
with vllm_runner(
tests/models/qwen4_exp/test_ple.py
+6
-6
@@ -14,11 +14,13 @@ from torch.nn import functional as F
14
import vllm.model_executor.layers.vocab_parallel_embedding as embedding_module
15
import vllm.model_executor.parameter as parameter_module
16
import vllm.models.qwen4_exp.nvidia.ngram_embedding as ngram_embedding_module
17
+from vllm.config.quantization import QuantizationConfigArgs
18
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
19
from vllm.model_executor.layers.quantization.modelopt import (
20
ModelOptMixedPrecisionConfig,
21
ModelOptNvFp4Config,
22
)
23
+from vllm.model_executor.layers.quantization.online.base import OnlineQuantizationConfig
24
from vllm.models.qwen4_exp.common.ple import (
25
PLEShardOverlap,
26
compute_ple_shard_overlap,
@@ -396,7 +398,6 @@ def test_ple_fp8_embedding_uses_int8_for_parallel_reduce(monkeypatch) -> None:
398
def test_ple_fp8_embedding_respects_checkpoint_shard_exclusions() -> None:
399
prefix = "model.layers.1.ple.ple_embedding.ngram_embedding"
400
quant_config = Fp8Config(
399
- is_checkpoint_fp8_serialized=True,
401
ignored_layers=[],
402
weight_block_size=[128, 128],
403
)
@@ -422,12 +423,11 @@ def test_ple_embedding_rejects_unsupported_quantization_configs() -> None:
423
with pytest.raises(NotImplementedError, match="ModelOptNvFp4Config"):
424
Qwen4ExpPLEEmbeddingMethod.from_quant_config(nvfp4_config, prefix)
425
425
- dynamic_fp8_config = Fp8Config(
426
- is_checkpoint_fp8_serialized=False,
427
- ignored_layers=[],
426
+ online_fp8_config = OnlineQuantizationConfig(
427
+ QuantizationConfigArgs(linear="fp8_per_tensor")
428
)
429
- with pytest.raises(NotImplementedError, match="serialized FP8"):
430
- Qwen4ExpPLEEmbeddingMethod.from_quant_config(dynamic_fp8_config, prefix)
429
+ with pytest.raises(NotImplementedError, match="OnlineQuantizationConfig"):
430
+ Qwen4ExpPLEEmbeddingMethod.from_quant_config(online_fp8_config, prefix)
431
432
433
def test_ple_embedding_respects_modelopt_exclusion() -> None:
tests/quantization/test_fp8.py
-196
@@ -5,11 +5,9 @@
5
Run `pytest tests/quantization/test_fp8.py --forked`.
6
"""
7
8
-import logging
8
from types import SimpleNamespace
9
10
import pytest
12
-import regex as re
11
import torch
12
13
from tests.quantization.utils import (
@@ -22,9 +20,6 @@ from vllm.config.cache import CacheConfig
20
from vllm.config.kernel import KernelConfig
21
from vllm.config.model import ModelConfig
22
from vllm.forward_context import set_forward_context
25
-from vllm.model_executor.kernels.linear.scaled_mm import (
26
- MarlinFP8ScaledMMLinearKernel,
27
-)
23
from vllm.model_executor.layers.attention import Attention
24
from vllm.model_executor.layers.attention.attention import (
25
set_default_quant_scales,
@@ -32,15 +27,11 @@ from vllm.model_executor.layers.attention.attention import (
27
from vllm.model_executor.layers.fused_moe import FusedMoEFactory
28
from vllm.model_executor.layers.quantization.fp8 import (
29
Fp8Config,
35
- Fp8KVCacheMethod,
30
Fp8LinearMethod,
31
Fp8MoEMethod,
32
)
33
from vllm.model_executor.layers.quantization.kv_cache import BaseKVCacheMethod
34
from vllm.model_executor.layers.quantization.modelopt import ModelOptLinearMethod
41
-from vllm.model_executor.layers.quantization.online.fp8 import (
42
- Fp8PerTensorOnlineLinearMethod,
43
-)
35
from vllm.model_executor.layers.quantization.utils import flashinfer_utils
36
from vllm.model_executor.layers.quantization.utils.flashinfer_utils import (
37
prepare_fp8_moe_layer_for_fi,
@@ -531,187 +522,6 @@ def test_model_load_and_run(
522
model(input_ids, positions, None)
523
524
534
-@pytest.mark.skipif(
535
- not is_quant_method_supported("fp8"),
536
- reason="FP8 is not supported on this GPU type.",
537
-)
538
-@pytest.mark.parametrize("kv_cache_dtype", ["auto", "fp8"])
539
-@pytest.mark.parametrize(
540
- "force_marlin", [True, False] if current_platform.is_cuda() else [False]
541
-)
542
-@pytest.mark.parametrize(
543
- "use_rocm_aiter", [True, False] if current_platform.is_rocm() else [False]
544
-)
545
-def test_online_quantization(
546
- vllm_runner,
547
- kv_cache_dtype: str,
548
- force_marlin: bool,
549
- use_rocm_aiter: bool,
550
- monkeypatch,
551
-) -> None:
552
- if use_rocm_aiter:
553
- monkeypatch.setenv("VLLM_ROCM_USE_AITER", "1")
554
-
555
- # `LLM.apply_model` requires pickling a function.
556
- monkeypatch.setenv("VLLM_ALLOW_INSECURE_SERIALIZATION", "1")
557
-
558
- kwargs = {}
559
- if force_marlin:
560
- kwargs["linear_backend"] = "marlin"
561
- kwargs["moe_backend"] = "marlin"
562
-
563
- model_dtype = "auto"
564
- if kv_cache_dtype == "fp8" and current_platform.is_device_capability_family(90):
565
- # FA3 requires BF16 output when the query input is FP8.
566
- model_dtype = "bfloat16"
567
-
568
- with vllm_runner(
569
- "facebook/opt-125m",
570
- quantization="fp8",
571
- dtype=model_dtype,
572
- enforce_eager=True,
573
- kv_cache_dtype=kv_cache_dtype,
574
- **kwargs,
575
- ) as llm:
576
-
577
- def check_model(model):
578
- fc1 = model.model.decoder.layers[0].fc1
579
- assert isinstance(fc1.quant_method, Fp8PerTensorOnlineLinearMethod)
580
- if kv_cache_dtype == "fp8":
581
- attn = model.model.decoder.layers[0].self_attn.attn
582
- assert isinstance(attn.quant_method, Fp8KVCacheMethod)
583
- assert attn._k_scale == 1.0
584
- assert attn._v_scale == 1.0
585
-
586
- if current_platform.is_cuda() or current_platform.is_xpu():
587
- if current_platform.supports_fp8() and not force_marlin:
588
- # For GPUs with hardware support, we keep weights in fp8
589
- assert fc1.weight.dtype == torch.float8_e4m3fn
590
- assert not isinstance(
591
- fc1.quant_method.fp8_linear, MarlinFP8ScaledMMLinearKernel
592
- )
593
- else:
594
- # For GPUs without hardware support, we pack the fp8 weights
595
- # for weight-only quantization using Marlin kernels
596
- assert fc1.weight.dtype == torch.int32
597
- assert isinstance(
598
- fc1.quant_method.fp8_linear, MarlinFP8ScaledMMLinearKernel
599
- )
600
- elif current_platform.is_rocm():
601
- if current_platform.supports_fp8() and not force_marlin:
602
- # For GPUs with hardware support, we keep weights in fp8
603
- assert fc1.weight.dtype == current_platform.fp8_dtype()
604
- else: # unsupported ROCm platform
605
- pytest.skip(
606
- "Skip `test_load_fp16_model`. "
607
- "It only runs on ROCm platform with FP8 compute."
608
- " e.g. MI300X and above."
609
- )
610
- else: # unsupported platform
611
- pytest.skip(
612
- "Skip `test_load_fp16_model`. "
613
- "It only runs on CUDA and ROCm platform."
614
- )
615
-
616
- llm.apply_model(check_model)
617
-
618
- outputs = llm.generate_greedy(["Hello my name is"], max_tokens=4)
619
- print(outputs[0][1])
620
-
621
-
622
-@pytest.mark.skipif(
623
- not is_quant_method_supported("fp8"),
624
- reason="FP8 is not supported on this GPU type.",
625
-)
626
-def test_online_quant_peak_mem(
627
- vllm_runner,
628
- caplog_mp_spawn,
629
- monkeypatch,
630
-) -> None:
631
- # Note: `allenai/OLMoE-1B-7B-0125-Instruct` was selected because:
632
- # 1. it covers both Linear and MoE paths
633
- # 2. it is already used by other tests in CI, so adding it here
634
- # does not increase disk space for CI runners
635
- # I really wanted to use `ibm-granite/granite-3.0-1b-a400m-base`
636
- # which I think is the smallest MoE model in vLLM (2.5 GiB bf16,
637
- # 1.3 GiB fp8), but could not as adding one more model makes CI
638
- # run out of disk space.
639
- model_name = "allenai/OLMoE-1B-7B-0125-Instruct"
640
-
641
- # Force spawn to ensure caplog_mp_spawn works consistently
642
- # (it relies on VLLM_LOGGING_CONFIG_PATH which spawn reads but fork ignores)
643
- monkeypatch.setenv("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
644
-
645
- with (
646
- caplog_mp_spawn(logging.DEBUG) as log_holder,
647
- vllm_runner(
648
- model_name,
649
- quantization="fp8",
650
- enforce_eager=True,
651
- ) as llm,
652
- ):
653
- outputs = llm.generate_greedy(["The future of AI is"], max_tokens=4)
654
- print(outputs[0][1])
655
-
656
- log_text = log_holder.text
657
-
658
- # Parse memory usage from captured logs
659
- model_memory_gib = None
660
- peak_memory_gib = None
661
- for line in log_text.splitlines():
662
- if model_memory_gib is None:
663
- match = re.search(r"Model loading took ([\d.]+) GiB memory", line)
664
- if match:
665
- model_memory_gib = float(match.group(1))
666
- if peak_memory_gib is None:
667
- match = re.search(
668
- r"Peak GPU memory after loading weights: ([\d.]+) GiB", line
669
- )
670
- if match:
671
- peak_memory_gib = float(match.group(1))
672
-
673
- assert model_memory_gib is not None, "Could not find model loading memory log"
674
- assert peak_memory_gib is not None, "Could not find peak memory log"
675
- print(f"GPU memory used after loading weights: {model_memory_gib} GiB")
676
- print(f"Peak GPU memory usage while loading weights: {peak_memory_gib} GiB")
677
-
678
- # model specific, allenai/OLMoE-1B-7B-0125-Instruct fp8 online quant
679
- # uses 6.65 GiB for weight loading (bf16 checkpoint is ~12.89 GiB)
680
- expected_model_memory_gib = 6.7
681
-
682
- # for allenai/OLMoE-1B-7B-0125-Instruct the number we see today is 9.06
683
- # GiB, which is 1.36x above model_memory_gib. A slightly higher number is
684
- # expected as when we load and quantize weights in a streaming fashion we
685
- # need to have individual weights in bf16 + fp8 alive at the same time.
686
- expected_peak_memory_gib = expected_model_memory_gib * 1.4
687
-
688
- assert model_memory_gib < expected_model_memory_gib, (
689
- f"{model_memory_gib=} higher than {expected_model_memory_gib}"
690
- )
691
- assert peak_memory_gib < expected_peak_memory_gib, (
692
- f"{peak_memory_gib=} higher than {expected_peak_memory_gib}"
693
- )
694
-
695
-
696
-@pytest.mark.skipif(
697
- not is_quant_method_supported("fp8"),
698
- reason="FP8 is not supported on this GPU type.",
699
-)
700
-def test_online_quant_load_format_dummy(
701
- vllm_runner,
702
- monkeypatch,
703
- caplog,
704
-) -> None:
705
- with vllm_runner(
706
- "ibm-granite/granite-3.0-1b-a400m-base",
707
- quantization="fp8",
708
- enforce_eager=True,
709
- load_format="dummy",
710
- ) as llm:
711
- outputs = llm.generate_greedy(["The future of AI is"], max_tokens=4)
712
- print(outputs[0][1])
713
-
714
-
525
@pytest.mark.skipif(
526
not is_quant_method_supported("fp8"),
527
reason="FP8 is not supported on this GPU type.",
@@ -799,7 +609,6 @@ def test_scaled_fp8_quant(dtype) -> None:
609
)
610
@pytest.mark.parametrize("method_cls", [Fp8LinearMethod, Fp8MoEMethod])
611
# FP8 weight reloading does not support online quantization
802
-@pytest.mark.parametrize("is_checkpoint_fp8_serialized", [True]) # skip False
612
@pytest.mark.parametrize("weight_block_size", [None, [128, 128]])
613
# any postprocessing that is applied to the weights such as padding and repacking
614
# (excluding device sharding) must also be applied to the reloaded weights
@@ -809,7 +618,6 @@ def test_scaled_fp8_quant(dtype) -> None:
618
def test_fp8_reloading(
619
default_vllm_config,
620
method_cls,
812
- is_checkpoint_fp8_serialized,
621
weight_block_size,
622
use_marlin,
623
dist_init,
@@ -820,9 +628,6 @@ def test_fp8_reloading(
628
# we set fp8_backend to None, which sidestepped the issue.
629
monkeypatch.setenv("VLLM_USE_DEEP_GEMM", "0")
630
823
- if is_checkpoint_fp8_serialized is False:
824
- pytest.skip("FP8 weight reloading does not support online quantization")
825
-
631
if method_cls is Fp8MoEMethod and weight_block_size is None:
632
pytest.skip(
633
"FP8 Tensor weight reloading does not support fusing w13_weight_scale. "
@@ -835,7 +640,6 @@ def test_fp8_reloading(
640
layer_size = 128 if weight_block_size is not None else 1
641
with torch.device(f"{DEVICE_TYPE}:0"):
642
config = Fp8Config(
838
- is_checkpoint_fp8_serialized=is_checkpoint_fp8_serialized,
643
weight_block_size=weight_block_size,
644
)
645
tests/quantization/test_online.py
+180
-20
@@ -20,6 +20,8 @@ from tests.quantization.utils import (
20
from vllm import _custom_ops as ops
21
from vllm._aiter_ops import rocm_aiter_ops
22
from vllm._custom_ops import scaled_fp4_quant
23
+from vllm.config.cache import CacheConfig
24
+from vllm.config.kernel import KernelConfig
25
from vllm.config.load import LoadConfig
26
from vllm.config.model import ModelConfig
27
from vllm.config.quantization import (
@@ -34,6 +36,9 @@ from vllm.model_executor.kernels.linear.mxfp8.emulation import (
36
from vllm.model_executor.kernels.linear.mxfp8.marlin import (
37
MarlinMxfp8LinearKernel,
38
)
39
+from vllm.model_executor.kernels.linear.scaled_mm import (
40
+ MarlinFP8ScaledMMLinearKernel,
41
+)
42
from vllm.model_executor.layers.attention import Attention
43
from vllm.model_executor.layers.fused_moe import FusedMoEFactory
44
from vllm.model_executor.layers.fused_moe.oracle import nvfp4 as nvfp4_oracle
@@ -125,6 +130,81 @@ PARTIALLY_PREQUANTIZED_MODEL_NAME = (
130
)
131
132
133
+@pytest.mark.parametrize(
134
+ "overrides",
135
+ [
136
+ None,
137
+ {"ignore": ["model.layers.0.self_attn.o_proj"]},
138
+ {
139
+ "linear": "fp8_per_block",
140
+ "ignore": ["model.layers.0.self_attn.o_proj"],
141
+ },
142
+ ],
143
+ ids=["defaults", "ignore", "linear-and-ignore"],
144
+)
145
+def test_legacy_fp8_online_quantization_uses_per_tensor_shorthand(
146
+ tmp_path, caplog, disable_log_dedup, overrides
147
+) -> None:
148
+ """The legacy alias preserves overrides and fills in per-tensor defaults."""
149
+ _write_minimal_llama_config(tmp_path)
150
+ model_config = ModelConfig(
151
+ model=str(tmp_path), quantization="fp8", quantization_config=overrides
152
+ )
153
+
154
+ result = weight_utils.get_quant_config(model_config, LoadConfig())
155
+
156
+ assert isinstance(result, OnlineQuantizationConfig)
157
+ assert result.args == resolve_quantization_config("fp8_per_tensor", overrides)
158
+ assert "--quantization fp8 is deprecated for online quantization" in caplog.text
159
+ assert "--quantization fp8_per_tensor instead" in caplog.text
160
+
161
+
162
+@pytest.mark.parametrize(
163
+ "shorthand,method_cls",
164
+ [
165
+ ("fp8_per_tensor", Fp8PerTensorOnlineLinearMethod),
166
+ ("fp8_per_block", Fp8PerBlockOnlineLinearMethod),
167
+ ("fp8_per_channel", Fp8PtpcOnlineLinearMethod),
168
+ ],
169
+)
170
+def test_qianfan_online_fp8_keeps_vision_layers_unquantized(
171
+ shorthand, method_cls
172
+) -> None:
173
+ """Model exclusions preserve the language backbone and shared defaults."""
174
+ from vllm.model_executor.models.qianfan_ocr import (
175
+ QianfanOCRForConditionalGeneration,
176
+ )
177
+
178
+ args = resolve_quantization_config(shorthand, None)
179
+ assert args is not None
180
+ config = OnlineQuantizationConfig(args)
181
+ other_config = OnlineQuantizationConfig(args)
182
+ model = QianfanOCRForConditionalGeneration.__new__(
183
+ QianfanOCRForConditionalGeneration
184
+ )
185
+ model._patch_quant_config(
186
+ SimpleNamespace(vision_config=SimpleNamespace(num_hidden_layers=2)),
187
+ config,
188
+ )
189
+
190
+ layer = Mock(spec=LinearBase)
191
+ for prefix in (
192
+ "vision_model.encoder.layers.0.attn.qkv",
193
+ "vision_model.encoder.layers.1.mlp.fc2",
194
+ "language_model.lm_head",
195
+ "mlp1.1",
196
+ "mlp1.3",
197
+ ):
198
+ assert config.resolve_quant_method_cls(layer, prefix) is None
199
+
200
+ resolved = config.resolve_quant_method_cls(
201
+ layer, "language_model.model.layers.0.self_attn.qkv_proj"
202
+ )
203
+ assert resolved is not None and resolved[-1] is method_cls
204
+ assert other_config.ignored_layers == []
205
+ assert args.ignore == []
206
+
207
+
208
def test_online_nvfp4_reuses_kernel_when_weights_are_reprocessed(
209
monkeypatch,
210
) -> None:
@@ -579,33 +659,81 @@ def test_nvfp4_one_sided_rejects_unsupported_input_dtype(input_dtype: torch.dtyp
659
reason="FP8 is not supported on this GPU type.",
660
)
661
@pytest.mark.parametrize(
582
- ("model_name,quant_scheme,online_quant_args,expected_linear_cls,expected_moe_cls"),
662
+ (
663
+ "model_name,quant_scheme,online_quant_args,expected_linear_cls,expected_moe_cls,extra_runner_kwargs"
664
+ ),
665
[
666
# simple case - quantization='fp8_per_tensor'
585
- (
667
+ pytest.param(
668
+ GRANITE_MODEL_NAME,
669
+ "fp8_per_tensor",
670
+ None,
671
+ Fp8PerTensorOnlineLinearMethod,
672
+ Fp8PerTensorOnlineMoEMethod,
673
+ {},
674
+ id="fp8_per_tensor",
675
+ ),
676
+ # FP8 KV cache with the fp8_per_tensor shorthand.
677
+ pytest.param(
678
+ GRANITE_MODEL_NAME,
679
+ "fp8_per_tensor",
680
+ None,
681
+ Fp8PerTensorOnlineLinearMethod,
682
+ Fp8PerTensorOnlineMoEMethod,
683
+ {"kv_cache_dtype": "fp8"},
684
+ id="fp8_per_tensor-fp8-kv-cache",
685
+ ),
686
+ pytest.param(
687
+ GRANITE_MODEL_NAME,
688
+ "fp8_per_tensor",
689
+ None,
690
+ Fp8PerTensorOnlineLinearMethod,
691
+ Fp8PerTensorOnlineMoEMethod,
692
+ {"linear_backend": "marlin", "moe_backend": "marlin"},
693
+ marks=pytest.mark.skipif(
694
+ not current_platform.is_cuda(),
695
+ reason="Marlin online FP8 coverage is CUDA-only.",
696
+ ),
697
+ id="fp8_per_tensor-marlin",
698
+ ),
699
+ pytest.param(
700
GRANITE_MODEL_NAME,
701
"fp8_per_tensor",
702
None,
703
Fp8PerTensorOnlineLinearMethod,
704
Fp8PerTensorOnlineMoEMethod,
705
+ {
706
+ "kv_cache_dtype": "fp8",
707
+ "linear_backend": "marlin",
708
+ "moe_backend": "marlin",
709
+ },
710
+ marks=pytest.mark.skipif(
711
+ not current_platform.is_cuda(),
712
+ reason="Marlin online FP8 coverage is CUDA-only.",
713
+ ),
714
+ id="fp8_per_tensor-fp8-kv-cache-marlin",
715
),
716
# simple case - quantization='fp8_per_block'
593
- (
717
+ pytest.param(
718
GRANITE_MODEL_NAME,
719
"fp8_per_block",
720
None,
721
Fp8PerBlockOnlineLinearMethod,
722
Fp8PerBlockOnlineMoEMethod,
723
+ {},
724
+ id="fp8_per_block",
725
),
600
- (
726
+ pytest.param(
727
GRANITE_MODEL_NAME,
728
"fp8_per_channel",
729
None,
730
Fp8PtpcOnlineLinearMethod,
731
Fp8PtpcOnlineMoEMethod,
732
+ {},
733
+ id="fp8_per_channel",
734
),
735
# quantization='online' with per-layer-kind overrides
608
- (
736
+ pytest.param(
737
GRANITE_MODEL_NAME,
738
"online",
739
{
@@ -614,9 +742,11 @@ def test_nvfp4_one_sided_rejects_unsupported_input_dtype(input_dtype: torch.dtyp
742
},
743
Fp8PerBlockOnlineLinearMethod,
744
Fp8PerTensorOnlineMoEMethod,
745
+ {},
746
+ id="per_layer_kind_overrides",
747
),
748
# quantization='online' with per-layer target patterns
619
- (
749
+ pytest.param(
750
GRANITE_MODEL_NAME,
751
"online",
752
{
@@ -627,9 +757,11 @@ def test_nvfp4_one_sided_rejects_unsupported_input_dtype(input_dtype: torch.dtyp
757
},
758
Fp8PerBlockOnlineLinearMethod,
759
Fp8PerTensorOnlineMoEMethod,
760
+ {},
761
+ id="targets",
762
),
763
# ignore with direct layer name
632
- (
764
+ pytest.param(
765
GRANITE_MODEL_NAME,
766
"fp8_per_tensor",
767
# qkv_proj is fused from q_proj/k_proj/v_proj. The shard regex
@@ -637,13 +769,17 @@ def test_nvfp4_one_sided_rejects_unsupported_input_dtype(input_dtype: torch.dtyp
769
{"ignore": ["model.layers.1.self_attn.o_proj", "re:.*[qkv]_proj"]},
770
Fp8PerTensorOnlineLinearMethod,
771
Fp8PerTensorOnlineMoEMethod,
772
+ {},
773
+ id="ignore",
774
),
641
- (
775
+ pytest.param(
776
GRANITE_MODEL_NAME,
777
"mxfp4",
778
None,
779
Mxfp4OnlineLinearMethod,
780
Mxfp4OnlineMoEMethod,
781
+ {},
782
+ id="mxfp4",
783
),
784
pytest.param(
785
PARTIALLY_PREQUANTIZED_MODEL_NAME,
@@ -651,19 +787,10 @@ def test_nvfp4_one_sided_rejects_unsupported_input_dtype(input_dtype: torch.dtyp
787
{"targets": {"model.layers.1.self_attn.o_proj": "mxfp8"}},
788
Mxfp8OnlineLinearMethod,
789
CompressedTensorsMoEMethod,
790
+ {},
791
id="partially_prequantized_checkpoint",
792
),
793
],
657
- ids=[
658
- "fp8_per_tensor",
659
- "fp8_per_block",
660
- "fp8_per_channel",
661
- "per_layer_kind_overrides",
662
- "targets",
663
- "ignore",
664
- "mxfp4",
665
- "partially_prequantized_checkpoint",
666
- ],
794
)
795
@pytest.mark.parametrize(
796
"use_rocm_aiter", [True, False] if current_platform.is_rocm() else [False]
@@ -674,6 +801,7 @@ def test_online_quantization(
801
online_quant_args: dict | None,
802
expected_linear_cls,
803
expected_moe_cls,
804
+ extra_runner_kwargs: dict,
805
use_rocm_aiter: bool,
806
monkeypatch,
807
dist_init,
@@ -700,14 +828,31 @@ def test_online_quantization(
828
):
829
pytest.skip(f"Skip test for online {quant_scheme} on XPU platform.")
830
831
+ runner_kwargs = dict(extra_runner_kwargs)
832
+ kv_cache_dtype = runner_kwargs.get("kv_cache_dtype", "auto")
833
+ force_marlin = extra_runner_kwargs.get("linear_backend") == "marlin"
834
+ if kv_cache_dtype == "fp8" and current_platform.is_device_capability_family(90):
835
+ # FA3 requires BF16 output when the query input is FP8.
836
+ runner_kwargs["dtype"] = "bfloat16"
837
+
838
+ vllm_config_kwargs = {
839
+ "cache_config": CacheConfig(cache_dtype=kv_cache_dtype),
840
+ "kernel_config": KernelConfig(
841
+ linear_backend=runner_kwargs.get("linear_backend", "auto"),
842
+ moe_backend=runner_kwargs.get("moe_backend", "auto"),
843
+ ),
844
+ }
845
+
846
if model_name == PARTIALLY_PREQUANTIZED_MODEL_NAME:
847
model, vllm_config = load_model_without_vllm_runner(
848
model_name,
849
+ dtype=runner_kwargs.get("dtype", "bfloat16"),
850
model_config_kwargs={
851
"quantization_config": resolve_quantization_config(
852
None, online_quant_args
853
)
854
},
855
+ vllm_config_kwargs=vllm_config_kwargs,
856
model_loader_cls=DummyModelLoader,
857
)
858
assert isinstance(
@@ -725,7 +870,7 @@ def test_online_quantization(
870
else:
871
model, vllm_config = load_model_without_vllm_runner(
872
model_name,
728
- dtype="bfloat16",
873
+ dtype=runner_kwargs.get("dtype", "bfloat16"),
874
quantization=quant_scheme,
875
model_config_kwargs={
876
"quantization_config": resolve_quantization_config(
@@ -743,6 +888,7 @@ def test_online_quantization(
888
"num_experts_per_tok": 2,
889
},
890
},
891
+ vllm_config_kwargs=vllm_config_kwargs,
892
model_loader_cls=DummyModelLoader,
893
)
894
@@ -770,7 +916,16 @@ def test_online_quantization(
916
elif quant_scheme == "mxfp4":
917
assert o_proj.weight.dtype == torch.uint8
918
elif current_platform.is_cuda() or current_platform.is_xpu():
773
- assert o_proj.weight.dtype == torch.float8_e4m3fn
919
+ if current_platform.supports_fp8() and not force_marlin:
920
+ assert o_proj.weight.dtype == torch.float8_e4m3fn
921
+ assert not isinstance(
922
+ o_proj.quant_method.fp8_linear, MarlinFP8ScaledMMLinearKernel
923
+ )
924
+ else:
925
+ assert o_proj.weight.dtype == torch.int32
926
+ assert isinstance(
927
+ o_proj.quant_method.fp8_linear, MarlinFP8ScaledMMLinearKernel
928
+ )
929
elif current_platform.is_rocm():
930
assert o_proj.weight.dtype == current_platform.fp8_dtype()
931
else:
@@ -794,6 +949,11 @@ def test_online_quantization(
949
layer.self_attn.qkv_proj.quant_method, UnquantizedLinearMethod
950
)
951
952
+ if kv_cache_dtype == "fp8":
953
+ attn = model.model.layers[0].self_attn.attn
954
+ assert attn._k_scale == 1.0
955
+ assert attn._v_scale == 1.0
956
+
957
if (
958
model_name == GRANITE_MODEL_NAME
959
and isinstance(online_quant_args, dict)
tests/v1/spec_decode/test_speculators_correctness.py
+1
-1
@@ -37,7 +37,7 @@ DFLASH_CONFIG = SpeculatorTestConfig(
37
acceptance_len_rtol=0.15,
38
expected_per_pos_acceptance_rates=(0.795, 0.611, 0.429, 0.282),
39
per_pos_rtol=0.15,
40
- quantization="fp8",
40
+ quantization="fp8_per_tensor",
41
)
42
43
PEAGLE_CONFIG = SpeculatorTestConfig(
vllm/model_executor/layers/quantization/fp8.py
+12
-27
@@ -93,7 +93,7 @@ class Fp8Config(QuantizationConfig):
93
94
def __init__(
95
self,
96
- is_checkpoint_fp8_serialized: bool = False,
96
+ is_checkpoint_fp8_serialized: bool = True,
97
activation_scheme: str = "dynamic",
98
ignored_layers: list[str] | None = None,
99
weight_block_size: list[int] | None = None,
@@ -102,6 +102,13 @@ class Fp8Config(QuantizationConfig):
102
super().__init__()
103
104
self.is_checkpoint_fp8_serialized = is_checkpoint_fp8_serialized
105
+ if not is_checkpoint_fp8_serialized:
106
+ raise ValueError(
107
+ "The `fp8` quantization method no longer supports online "
108
+ "quantization. Please use `--quantization fp8_per_tensor` "
109
+ "instead. See "
110
+ "https://docs.vllm.ai/en/stable/features/quantization/online/"
111
+ )
112
113
if activation_scheme not in ACTIVATION_SCHEMES:
114
raise ValueError(f"Unsupported activation scheme {activation_scheme}")
@@ -112,11 +119,6 @@ class Fp8Config(QuantizationConfig):
119
)
120
self.store_dtype = store_dtype
121
if weight_block_size is not None:
115
- if not is_checkpoint_fp8_serialized:
116
- raise ValueError(
117
- "The block-wise quantization only supports fp8-serialized "
118
- "checkpoint for now."
119
- )
122
if len(weight_block_size) != 2:
123
raise ValueError(
124
"The quantization block size of weight must have 2 "
@@ -182,18 +184,9 @@ class Fp8Config(QuantizationConfig):
184
match_mode=self.ignored_layers_match_mode,
185
):
186
return UnquantizedLinearMethod()
185
- if not self.is_checkpoint_fp8_serialized:
186
- from vllm.model_executor.layers.quantization.online.fp8 import (
187
- Fp8PerTensorOnlineLinearMethod,
188
- )
189
-
190
- online_method = Fp8PerTensorOnlineLinearMethod()
191
- online_method.marlin_input_dtype = get_marlin_input_dtype(prefix)
192
- return online_method
193
- else:
194
- offline_method = Fp8LinearMethod(self)
195
- offline_method.marlin_input_dtype = get_marlin_input_dtype(prefix)
196
- return offline_method
187
+ offline_method = Fp8LinearMethod(self)
188
+ offline_method.marlin_input_dtype = get_marlin_input_dtype(prefix)
189
+ return offline_method
190
elif isinstance(layer, RoutedExperts):
191
if is_layer_skipped(
192
prefix=prefix,
@@ -208,14 +201,7 @@ class Fp8Config(QuantizationConfig):
201
)
202
203
return Mxfp4MoEMethod(layer.moe_config)
211
- if self.is_checkpoint_fp8_serialized:
212
- return Fp8MoEMethod(self, layer)
213
- else:
214
- from vllm.model_executor.layers.quantization.online.fp8 import (
215
- Fp8PerTensorOnlineMoEMethod,
216
- )
217
-
218
- return Fp8PerTensorOnlineMoEMethod(moe=layer.moe_config)
204
+ return Fp8MoEMethod(self, layer)
205
elif isinstance(layer, Attention):
206
return Fp8KVCacheMethod(self)
207
return None
@@ -548,7 +534,6 @@ class Fp8MoEMethod(FusedMoEMethodBase):
534
layer.orig_dtype = params_dtype
535
layer.weight_block_size = None
536
551
- assert self.quant_config.is_checkpoint_fp8_serialized
537
params_dtype = torch.float8_e4m3fn
538
539
if self.block_quant:
vllm/model_executor/model_loader/weight_utils.py
+13
-1
@@ -195,7 +195,11 @@ def get_quant_config(
195
if model_config.quantization is None:
196
raise ValueError("Model quantization method is not specified in the config.")
197
quant_cls = get_quantization_config(model_config.quantization)
198
- from vllm.config.quantization import _ONLINE_SHORTHANDS, QuantizationConfigArgs
198
+ from vllm.config.quantization import (
199
+ _ONLINE_SHORTHANDS,
200
+ QuantizationConfigArgs,
201
+ resolve_quantization_config,
202
+ )
203
from vllm.model_executor.layers.quantization.online.base import (
204
OnlineQuantizationConfig,
205
)
@@ -335,6 +339,14 @@ def get_quant_config(
339
340
# If the quantization config is not found, use the default config.
341
if not possible_config_filenames:
342
+ if model_config.quantization == "fp8":
343
+ logger.warning_once(
344
+ "--quantization fp8 is deprecated for online quantization; "
345
+ "use --quantization fp8_per_tensor instead."
346
+ )
347
+ fp8_args = resolve_quantization_config("fp8_per_tensor", online_args)
348
+ assert fp8_args is not None
349
+ return OnlineQuantizationConfig(args=fp8_args)
350
if model_config.quantization in _ONLINE_SHORTHANDS:
351
args = online_args or _ONLINE_SHORTHANDS[model_config.quantization]
352
assert isinstance(args, QuantizationConfigArgs)
vllm/model_executor/models/qianfan_ocr.py
+13
-1
@@ -9,6 +9,12 @@ from transformers import PreTrainedConfig
9
10
from vllm.model_executor.layers.quantization import QuantizationConfig
11
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
12
+from vllm.model_executor.layers.quantization.online.base import OnlineQuantizationConfig
13
+from vllm.model_executor.layers.quantization.utils.quant_utils import (
14
+ kFp8Static128BlockSym,
15
+ kFp8StaticChannelSym,
16
+ kFp8StaticTensorSym,
17
+)
18
from vllm.multimodal import MULTIMODAL_REGISTRY
19
from vllm.transformers_utils.processors.internvl import (
20
InternVLImageProcessor,
@@ -71,7 +77,12 @@ class QianfanOCRForConditionalGeneration(InternVLChatModel):
77
) -> None:
78
super()._patch_quant_config(config, quant_config)
79
# ignore vit layers to preserve model performance
74
- if isinstance(quant_config, Fp8Config):
80
+ if isinstance(quant_config, Fp8Config) or (
81
+ isinstance(quant_config, OnlineQuantizationConfig)
82
+ and quant_config.args.linear is not None
83
+ and quant_config.args.linear.weight
84
+ in (kFp8StaticTensorSym, kFp8Static128BlockSym, kFp8StaticChannelSym)
85
+ ):
86
_FP8_IGNORED_LAYERS = [
87
*(
88
layer
@@ -87,6 +98,7 @@ class QianfanOCRForConditionalGeneration(InternVLChatModel):
98
"mlp1.1",
99
"mlp1.3",
100
]
101
+ quant_config.ignored_layers = list(quant_config.ignored_layers)
102
for layer in _FP8_IGNORED_LAYERS:
103
if layer not in quant_config.ignored_layers:
104
quant_config.ignored_layers.append(layer)