[Migration] Migrate bitsandbytes support to OOT plugin (#43529)
Signed-off-by: Isotr0py <mozf@mail2.sysu.edu.cn> Signed-off-by: Isotr0py <Isotr0py@outlook.com>
Isotr0py committed
Aug 9, 2026 at 10:27 UTC
073c510c916f385315a5366173c883781762bb9e
31 files changed
+187
-1895
.buildkite/test_areas/plugins.yaml
+28
@@ -63,3 +63,31 @@ steps:
63
commands:
64
- pip install "vllm-gguf-plugin >= 0.0.2"
65
- pytest -v -s plugins_tests/gguf
66
+
67
+- label: BitsAndBytes Plugin
68
+ key: bitsandbytes-plugin
69
+ device: h200_18gb
70
+ timeout_in_minutes: 30
71
+ working_dir: "/vllm-workspace/tests"
72
+ soft_fail: true
73
+ optional: true
74
+ source_file_dependencies:
75
+ - vllm/model_executor/layers/quantization
76
+ - tests/plugins_tests/bitsandbytes
77
+ commands:
78
+ - pip install "vllm-bnb-plugin >= 0.0.1"
79
+ - pytest -v -s plugins_tests/bitsandbytes -m 'not distributed'
80
+
81
+- label: BitsAndBytes Plugin (2 GPUs)
82
+ key: bitsandbytes-plugin-2-gpus
83
+ timeout_in_minutes: 15
84
+ working_dir: "/vllm-workspace/tests"
85
+ num_devices: 2
86
+ soft_fail: true
87
+ optional: true
88
+ source_file_dependencies:
89
+ - vllm/model_executor/layers/quantization
90
+ - tests/plugins_tests/bitsandbytes
91
+ commands:
92
+ - pip install "vllm-bnb-plugin >= 0.0.1"
93
+ - pytest -v -s plugins_tests/bitsandbytes -m 'distributed(num_gpus=2)'
docker/Dockerfile
+1
-9
@@ -779,20 +779,12 @@ RUN set -eux; \
779
780
# Install vllm-openai dependencies (saves ~2.6s per build)
781
# These are stable packages that don't depend on vLLM itself
782
-# From versions.json: .bitsandbytes.x86_64, .bitsandbytes.arm64
782
# From versions.json: .openai_server_extras.timm, .openai_server_extras.runai_model_streamer
784
-ARG BITSANDBYTES_VERSION_X86=0.46.1
785
-ARG BITSANDBYTES_VERSION_ARM64=0.42.0
783
ARG TIMM_VERSION=">=1.0.17"
784
ARG RUNAI_MODEL_STREAMER_VERSION=">=0.15.7"
785
RUN --mount=type=cache,target=/opt/uv/cache \
789
- if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \
790
- BITSANDBYTES_VERSION="${BITSANDBYTES_VERSION_ARM64}"; \
791
- else \
792
- BITSANDBYTES_VERSION="${BITSANDBYTES_VERSION_X86}"; \
793
- fi; \
786
uv pip install --system accelerate 'modelscope<1.38' \
795
- "bitsandbytes>=${BITSANDBYTES_VERSION}" "timm${TIMM_VERSION}" "runai-model-streamer[s3,gcs,azure]${RUNAI_MODEL_STREAMER_VERSION}"
787
+ "timm${TIMM_VERSION}" "runai-model-streamer[s3,gcs,azure]${RUNAI_MODEL_STREAMER_VERSION}"
788
789
# ============================================================
790
# VLLM INSTALLATION (depends on build stage)
docker/versions.json
-6
@@ -76,12 +76,6 @@
76
"GDRCOPY_OS_VERSION": {
77
"default": "Ubuntu24_04"
78
},
79
- "BITSANDBYTES_VERSION_X86": {
80
- "default": "0.46.1"
81
- },
82
- "BITSANDBYTES_VERSION_ARM64": {
83
- "default": "0.42.0"
84
- },
79
"TIMM_VERSION": {
80
"default": ">=1.0.17"
81
},
docs/features/quantization/bnb.md
+4
-2
@@ -4,10 +4,12 @@ vLLM now supports [BitsAndBytes](https://github.com/TimDettmers/bitsandbytes) fo
4
BitsAndBytes quantizes models to reduce memory usage and enhance performance without significantly sacrificing accuracy.
5
Compared to other quantization methods, BitsAndBytes eliminates the need for calibrating the quantized model with input data.
6
7
-Below are the steps to utilize BitsAndBytes with vLLM.
7
+BitsAndBytes support is provided by the out-of-tree
8
+[`vllm-bnb-plugin`](https://github.com/vllm-project/vllm-bnb-plugin).
9
+Install the plugin before using BitsAndBytes with vLLM:
10
11
```bash
10
-pip install bitsandbytes>=0.49.2
12
+uv pip install vllm-bnb-plugin
13
```
14
15
vLLM reads the model's config file and supports both in-flight quantization and pre-quantized checkpoint.
examples/features/lora/lora_with_quantization_offline.py
+1
@@ -5,6 +5,7 @@ This example shows how to use LoRA with different quantization techniques
5
for offline inference.
6
7
Requires HuggingFace credentials for access.
8
+The bitsandbytes example also requires installing vllm-bnb-plugin.
9
"""
10
11
import gc
requirements/test/cpu.txt
-5
@@ -70,8 +70,6 @@ azure-storage-blob==12.28.0
70
# via runai-model-streamer-azure
71
backoff==2.2.1
72
# via -r requirements/test/cuda.in
73
-bitsandbytes==0.49.2
74
- # via -r requirements/test/cuda.in
73
black==24.10.0
74
# via datamodel-code-generator
75
blake3==1.0.9
@@ -535,7 +533,6 @@ numpy==2.2.6
533
# -r requirements/test/../common.txt
534
# accelerate
535
# albumentations
538
- # bitsandbytes
536
# bm25s
537
# contourpy
538
# cupy-cuda12x
@@ -642,7 +639,6 @@ outlines-core==0.2.14
639
packaging==24.2
640
# via
641
# accelerate
645
- # bitsandbytes
642
# black
643
# datamodel-code-generator
644
# datasets
@@ -1126,7 +1122,6 @@ torch==2.13.0+cpu
1122
# via
1123
# -r requirements/test/cuda.in
1124
# accelerate
1129
- # bitsandbytes
1125
# compressed-tensors
1126
# encodec
1127
# instanttensor
requirements/test/cuda.in
-1
@@ -43,7 +43,6 @@ transformers==5.14.1
43
tokenizers==0.22.2
44
schemathesis>=4.0.0 # Required for openai schema test.
45
# quantization
46
-bitsandbytes==0.49.2
46
buildkite-test-collector==0.1.9
47
torch-abi-audit # CI check for PyTorch stable ABI compliance
48
requirements/test/cuda.txt
-5
@@ -75,8 +75,6 @@ azure-storage-blob==12.28.0
75
# via runai-model-streamer-azure
76
backoff==2.2.1
77
# via -r requirements/test/cuda.in
78
-bitsandbytes==0.49.2
79
- # via -r requirements/test/cuda.in
78
black==24.10.0
79
# via datamodel-code-generator
80
blake3==1.0.9
@@ -566,7 +564,6 @@ numpy==2.2.6
564
# -r requirements/test/../common.txt
565
# accelerate
566
# albumentations
569
- # bitsandbytes
567
# bm25s
568
# contourpy
569
# cupy-cuda12x
@@ -726,7 +723,6 @@ outlines-core==0.2.14
723
packaging==24.2
724
# via
725
# accelerate
729
- # bitsandbytes
726
# black
727
# datamodel-code-generator
728
# datasets
@@ -1225,7 +1221,6 @@ torch==2.13.0+cu130
1221
# -c requirements/cuda.txt
1222
# -r requirements/test/cuda.in
1223
# accelerate
1228
- # bitsandbytes
1224
# compressed-tensors
1225
# encodec
1226
# instanttensor
requirements/test/nightly-torch.txt
-1
@@ -33,7 +33,6 @@ transformers==5.14.1
33
tokenizers==0.22.2
34
schemathesis>=4.0.0 # Required for openai schema test.
35
# quantization
36
-bitsandbytes>=0.49.2
36
buildkite-test-collector==0.1.9
37
38
requirements/test/rocm.in
-1
@@ -39,7 +39,6 @@ transformers==5.14.1
39
tokenizers==0.22.2
40
schemathesis>=4.0.0 # Required for openai schema test
41
# quantization
42
-bitsandbytes==0.49.2
42
buildkite-test-collector==0.1.9
43
tilelang==0.1.10
44
requirements/test/rocm.txt
-4
@@ -72,8 +72,6 @@ azure-storage-blob==12.28.0
72
# via runai-model-streamer-azure
73
backoff==2.2.1
74
# via -r requirements/test/rocm.in
75
-bitsandbytes==0.49.2
76
- # via -r requirements/test/rocm.in
75
black==26.3.1
76
# via datamodel-code-generator
77
blake3==1.0.8
@@ -568,7 +566,6 @@ numpy==2.2.6
566
# -r requirements/test/../common.txt
567
# accelerate
568
# albumentations
571
- # bitsandbytes
569
# bm25s
570
# contourpy
571
# cupy-cuda12x
@@ -698,7 +695,6 @@ packaging==26.0
695
# via
696
# -c requirements/rocm.txt
697
# accelerate
701
- # bitsandbytes
698
# black
699
# datasets
700
# evaluate
tests/conftest.py
+1
-4
@@ -521,10 +521,7 @@ class HfRunner:
521
):
522
model = model.to(dtype=self.dtype)
523
524
- if (
525
- getattr(model, "quantization_method", None) != "bitsandbytes"
526
- and len({p.device for p in model.parameters()}) < 2
527
- ):
524
+ if len({p.device for p in model.parameters()}) < 2:
525
model = model.to(device=self.device)
526
527
self.model = model
tests/kernels/moe/test_moe_weight_loading_padded.py
-34
@@ -343,40 +343,6 @@ class TestWeightLoadingWithPaddedHiddenSize:
343
torch.zeros(original_hidden, padded_intermediate - original_intermediate),
344
)
345
346
- def test_bnb_shape_mismatch_raises(self):
347
- """BnB + padded hidden_size should raise via weight_loader."""
348
- from unittest.mock import MagicMock
349
-
350
- num_experts = 1
351
- padded_packed = 3072 # padded packed size
352
- original_packed = 2688 # original packed size
353
-
354
- # Build a param that looks like a BnB 4-bit MoE weight.
355
- param_data = torch.zeros(num_experts, padded_packed, 1, dtype=torch.uint8)
356
- param = torch.nn.Parameter(param_data, requires_grad=False)
357
- param.use_bitsandbytes_4bit = True
358
-
359
- loaded_weight = torch.randint(0, 255, (original_packed, 1), dtype=torch.uint8)
360
-
361
- # Minimal RoutedExperts mock so weight_loader reaches the BnB path.
362
- moe = MagicMock(spec=RoutedExperts)
363
- moe.quant_config = None
364
- moe.quant_method = MagicMock()
365
- moe.quant_method.__class__.__name__ = "BitsAndBytesMethod"
366
- moe._expert_map = None
367
- moe.tp_rank = 0
368
-
369
- # Call the real weight_loader (unbound) with our mock as self.
370
- with pytest.raises(ValueError, match="BitsAndBytes"):
371
- RoutedExperts.weight_loader(
372
- moe,
373
- param,
374
- loaded_weight,
375
- weight_name="w2",
376
- shard_id="w2",
377
- expert_id=0,
378
- )
379
-
346
347
class TestLoadWeightsExpertBias:
348
"""Some quantized exports (e.g. GPTQ, llm-compressor NVFP4) materialize
tests/models/transformers/test_backend.py
-7
@@ -189,13 +189,6 @@ def test_distributed(
189
[
190
("TheBloke/TinyLlama-1.1B-Chat-v0.3-AWQ", {}),
191
("TheBloke/TinyLlama-1.1B-Chat-v0.3-GPTQ", {}),
192
- (
193
- "meta-llama/Llama-3.2-1B-Instruct",
194
- {
195
- "quantization": "bitsandbytes",
196
- },
197
- ),
198
- ("unsloth/tinyllama-bnb-4bit", {}),
192
],
193
)
194
@pytest.mark.parametrize("max_tokens", [32])
tests/plugins_tests/bitsandbytes/__init__.py
new
+2
@@ -0,0 +1,2 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
tests/plugins_tests/bitsandbytes/test_bitsandbytes.py
renamed
+78
-130
@@ -1,9 +1,6 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-"""Tests whether bitsandbytes computation is enabled correctly.
4
-
5
-Run `pytest tests/quantization/test_bitsandbytes.py`.
6
-"""
3
+"""End-to-end tests for the out-of-tree bitsandbytes plugin."""
4
5
import types
6
from unittest.mock import MagicMock, patch
@@ -12,13 +9,12 @@ import pytest
9
from packaging.version import Version
10
from transformers import BitsAndBytesConfig
11
from transformers import __version__ as TRANSFORMERS_VERSION
12
+from vllm_bnb_plugin import bitsandbytes_loader as bnb
13
16
-from tests.quantization.utils import is_quant_method_supported
17
-from vllm.model_executor.model_loader import bitsandbytes_loader as bnb
14
from vllm.platforms import current_platform
15
20
-from ...utils import compare_two_settings, multi_gpu_test
21
-from ..utils import check_embeddings_close, check_logprobs_close
16
+from ...models.utils import check_embeddings_close, check_logprobs_close
17
+from ...utils import multi_gpu_test
18
19
if current_platform.is_rocm():
20
from vllm.platforms.rocm import on_cdna
@@ -44,7 +40,7 @@ models_4bit_to_moe_test = [
40
("allenai/OLMoE-1B-7B-0125-Instruct", "quantize moe model inflight"),
41
]
42
47
-models_pre_qaunt_4bit_to_test = [
43
+models_pre_quant_4bit_to_test = [
44
(
45
"PrunaAI/Einstein-v6.1-Llama3-8B-bnb-4bit-smashed",
46
"read pre-quantized 4-bit FP4 model",
@@ -58,10 +54,64 @@ models_pre_quant_8bit_to_test = [
54
]
55
56
61
-@pytest.mark.skipif(
62
- not is_quant_method_supported("bitsandbytes"),
63
- reason="bitsandbytes is not supported on this GPU type.",
64
-)
57
+def log_generated_texts(prompts, outputs, runner_name):
58
+ logged_texts = []
59
+ for i, (_, generated_text) in enumerate(outputs):
60
+ logged_texts.append(
61
+ {
62
+ "prompt": prompts[i],
63
+ "runner_name": runner_name,
64
+ "generated_text": generated_text,
65
+ }
66
+ )
67
+ return logged_texts
68
+
69
+
70
+def validate_generated_texts(
71
+ hf_runner,
72
+ vllm_runner,
73
+ prompts,
74
+ model_name,
75
+ pre_quant=False,
76
+ hf_model_kwargs=None,
77
+ vllm_tp_size=1,
78
+ max_tokens=8,
79
+):
80
+ with vllm_runner(
81
+ model_name,
82
+ quantization=None if pre_quant else "bitsandbytes",
83
+ tensor_parallel_size=vllm_tp_size,
84
+ enforce_eager=False,
85
+ default_torch_num_threads=1,
86
+ tokenizer_mode="hf",
87
+ load_format="hf",
88
+ config_format="hf",
89
+ ) as llm:
90
+ vllm_outputs = llm.generate_greedy(prompts, max_tokens)
91
+ vllm_logs = log_generated_texts(prompts, vllm_outputs, "VllmRunner")
92
+
93
+ if hf_model_kwargs is None:
94
+ hf_model_kwargs = {}
95
+
96
+ with hf_runner(
97
+ model_name, model_kwargs=hf_model_kwargs, default_torch_num_threads=1
98
+ ) as llm:
99
+ hf_outputs = llm.generate_greedy(prompts, max_tokens)
100
+ hf_logs = log_generated_texts(prompts, hf_outputs, "HfRunner")
101
+
102
+ for hf_log, vllm_log in zip(hf_logs, vllm_logs):
103
+ hf_str = hf_log["generated_text"]
104
+ vllm_str = vllm_log["generated_text"]
105
+ prompt = hf_log["prompt"]
106
+ assert hf_str == vllm_str, (
107
+ f"Model: {model_name}"
108
+ f"Mismatch between HF and vLLM outputs:\n"
109
+ f"Prompt: {prompt}\n"
110
+ f"HF Output: '{hf_str}'\n"
111
+ f"vLLM Output: '{vllm_str}'"
112
+ )
113
+
114
+
115
@pytest.mark.parametrize("model_name, description", models_4bit_to_test)
116
def test_load_4bit_bnb_model(
117
hf_runner, vllm_runner, example_prompts, model_name, description
@@ -72,11 +122,7 @@ def test_load_4bit_bnb_model(
122
)
123
124
75
-@pytest.mark.skipif(
76
- not is_quant_method_supported("bitsandbytes"),
77
- reason="bitsandbytes is not supported on this GPU type.",
78
-)
79
-@pytest.mark.parametrize("model_name, description", models_pre_qaunt_4bit_to_test)
125
+@pytest.mark.parametrize("model_name, description", models_pre_quant_4bit_to_test)
126
def test_load_pre_quant_4bit_bnb_model(
127
hf_runner, vllm_runner, example_prompts, model_name, description
128
) -> None:
@@ -85,10 +131,6 @@ def test_load_pre_quant_4bit_bnb_model(
131
)
132
133
88
-@pytest.mark.skipif(
89
- not is_quant_method_supported("bitsandbytes"),
90
- reason="bitsandbytes is not supported on this GPU type.",
91
-)
134
@pytest.mark.parametrize("model_name, description", models_pre_quant_8bit_to_test)
135
def test_load_8bit_bnb_model(
136
hf_runner, vllm_runner, example_prompts, model_name, description
@@ -98,10 +140,6 @@ def test_load_8bit_bnb_model(
140
)
141
142
101
-@pytest.mark.skipif(
102
- not is_quant_method_supported("bitsandbytes"),
103
- reason="bitsandbytes is not supported on this GPU type.",
104
-)
143
@pytest.mark.parametrize("model_name, description", models_4bit_to_test)
144
@multi_gpu_test(num_gpus=2)
145
def test_load_tp_4bit_bnb_model(
@@ -119,44 +157,28 @@ def test_load_tp_4bit_bnb_model(
157
)
158
159
122
-@pytest.mark.skipif(
123
- not is_quant_method_supported("bitsandbytes"),
124
- reason="bitsandbytes is not supported on this GPU type.",
125
-)
160
@pytest.mark.parametrize("model_name, description", models_4bit_to_test)
161
@multi_gpu_test(num_gpus=2)
128
-def test_load_pp_4bit_bnb_model(model_name, description) -> None:
129
- common_args = [
130
- "--disable-log-stats",
131
- "--dtype",
132
- "bfloat16",
133
- "--enable-prefix-caching",
134
- "--quantization",
135
- "bitsandbytes",
136
- "--gpu-memory-utilization",
137
- "0.7",
138
- ]
139
- pp_args = [
140
- *common_args,
141
- "--pipeline-parallel-size",
142
- "2",
143
- ]
144
- compare_two_settings(
162
+def test_load_pp_4bit_bnb_model(
163
+ hf_runner, vllm_runner, example_prompts, model_name, description
164
+) -> None:
165
+ hf_model_kwargs = dict(quantization_config=BitsAndBytesConfig(load_in_4bit=True))
166
+ validate_generated_texts(
167
+ hf_runner,
168
+ vllm_runner,
169
+ example_prompts[:1],
170
model_name,
146
- common_args,
147
- pp_args,
171
+ False,
172
+ hf_model_kwargs,
173
+ vllm_tp_size=2,
174
)
175
176
177
@pytest.mark.skipif(
178
Version(TRANSFORMERS_VERSION) >= Version("5.0.0"),
153
- reason="Need to add support for quantizing MoE experts with bnb"
154
- " in transformers v5. See"
155
- " https://github.com/bitsandbytes-foundation/bitsandbytes/issues/1849",
156
-)
157
-@pytest.mark.skipif(
158
- not is_quant_method_supported("bitsandbytes"),
159
- reason="bitsandbytes is not supported on this GPU type.",
179
+ reason="Need to add support for quantizing MoE experts with bnb in "
180
+ "transformers v5. See https://github.com/bitsandbytes-foundation/"
181
+ "bitsandbytes/issues/1849",
182
)
183
@pytest.mark.parametrize("model_name, description", models_4bit_to_moe_test)
184
def test_4bit_bnb_moe_model(
@@ -193,10 +215,6 @@ def test_4bit_bnb_moe_model(
215
)
216
217
196
-@pytest.mark.skipif(
197
- not is_quant_method_supported("bitsandbytes"),
198
- reason="bitsandbytes is not supported on this GPU type.",
199
-)
218
@pytest.mark.parametrize("model_name, description", models_4bit_to_embedding_test)
219
@pytest.mark.parametrize("dtype", ["half"])
220
def test_4bit_bnb_embedding_model(
@@ -207,15 +225,8 @@ def test_4bit_bnb_embedding_model(
225
example_prompts,
226
dtype: str,
227
) -> None:
210
- # The example_prompts has ending "\n", for example:
211
- # "Write a short story about a robot that dreams for the first time.\n"
212
- # sentence_transformers will strip the input texts, see:
213
- # https://github.com/UKPLab/sentence-transformers/blob/v3.1.1/sentence_transformers/models/Transformer.py#L159
214
- # This makes the input_ids different between hf_model and vllm_model.
215
- # So we need to strip the input texts to avoid test failing.
228
example_prompts = [str(s).strip() for s in example_prompts]
229
218
- # Inflight 4bit quantization
230
with vllm_runner(
231
model_name,
232
runner="pooling",
@@ -245,70 +256,7 @@ def test_4bit_bnb_embedding_model(
256
)
257
258
248
-def log_generated_texts(prompts, outputs, runner_name):
249
- logged_texts = []
250
- for i, (_, generated_text) in enumerate(outputs):
251
- log_entry = {
252
- "prompt": prompts[i],
253
- "runner_name": runner_name,
254
- "generated_text": generated_text,
255
- }
256
- logged_texts.append(log_entry)
257
- return logged_texts
258
-
259
-
260
-def validate_generated_texts(
261
- hf_runner,
262
- vllm_runner,
263
- prompts,
264
- model_name,
265
- pre_quant=False,
266
- hf_model_kwargs=None,
267
- vllm_tp_size=1,
268
- max_tokens=8,
269
-):
270
- # NOTE: run vLLM first, as it requires a clean process
271
- # when using distributed inference
272
- with vllm_runner(
273
- model_name,
274
- quantization=None if pre_quant else "bitsandbytes",
275
- tensor_parallel_size=vllm_tp_size,
276
- enforce_eager=False,
277
- default_torch_num_threads=1,
278
- tokenizer_mode="hf",
279
- load_format="hf",
280
- config_format="hf",
281
- ) as llm:
282
- vllm_outputs = llm.generate_greedy(prompts, max_tokens)
283
- vllm_logs = log_generated_texts(prompts, vllm_outputs, "VllmRunner")
284
-
285
- if hf_model_kwargs is None:
286
- hf_model_kwargs = {}
287
-
288
- # Run with HF runner
289
- with hf_runner(
290
- model_name, model_kwargs=hf_model_kwargs, default_torch_num_threads=1
291
- ) as llm:
292
- hf_outputs = llm.generate_greedy(prompts, max_tokens)
293
- hf_logs = log_generated_texts(prompts, hf_outputs, "HfRunner")
294
-
295
- # Compare the generated strings
296
- for hf_log, vllm_log in zip(hf_logs, vllm_logs):
297
- hf_str = hf_log["generated_text"]
298
- vllm_str = vllm_log["generated_text"]
299
- prompt = hf_log["prompt"]
300
- assert hf_str == vllm_str, (
301
- f"Model: {model_name}"
302
- f"Mismatch between HF and vLLM outputs:\n"
303
- f"Prompt: {prompt}\n"
304
- f"HF Output: '{hf_str}'\n"
305
- f"vLLM Output: '{vllm_str}'"
306
- )
307
-
308
-
259
def test_bitsandbytes_passes_revision_by_name():
310
- # revision must reach download_safetensors_index_file_from_hf as the
311
- # ``revision`` keyword, not a positional slot.
260
fake_self = types.SimpleNamespace(
261
load_config=types.SimpleNamespace(download_dir="/cache"),
262
_get_weight_files=MagicMock(
tests/plugins_tests/bitsandbytes/test_transformers.py
new
+70
@@ -0,0 +1,70 @@
1
+# SPDX-License-Identifier: Apache-2.0
2
+# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+"""Transformers backend tests for bitsandbytes plugin models."""
4
+
5
+import pytest
6
+
7
+from vllm.platforms import current_platform
8
+
9
+from ...models.utils import check_logprobs_close
10
+
11
+if current_platform.is_rocm():
12
+ from vllm.platforms.rocm import on_cdna
13
+
14
+ pytestmark = pytest.mark.skipif(
15
+ on_cdna(),
16
+ reason="bitsandbytes not supported on CDNA (warp size 64 limitation)",
17
+ )
18
+
19
+
20
+@pytest.mark.parametrize(
21
+ "model, quantization_kwargs",
22
+ [
23
+ (
24
+ "meta-llama/Llama-3.2-1B-Instruct",
25
+ {
26
+ "quantization": "bitsandbytes",
27
+ },
28
+ ),
29
+ ("unsloth/tinyllama-bnb-4bit", {}),
30
+ ],
31
+)
32
+@pytest.mark.parametrize("max_tokens", [32])
33
+@pytest.mark.parametrize("num_logprobs", [5])
34
+def test_transformers_backend_quantization(
35
+ vllm_runner,
36
+ example_prompts,
37
+ model: str,
38
+ quantization_kwargs: dict[str, str],
39
+ max_tokens: int,
40
+ num_logprobs: int,
41
+) -> None:
42
+ with vllm_runner(
43
+ model,
44
+ model_impl="auto",
45
+ enforce_eager=True,
46
+ **quantization_kwargs, # type: ignore[arg-type]
47
+ ) as vllm_model:
48
+ vllm_outputs = vllm_model.generate_greedy_logprobs(
49
+ example_prompts, max_tokens=max_tokens, num_logprobs=num_logprobs
50
+ )
51
+
52
+ with vllm_runner(
53
+ model,
54
+ model_impl="transformers",
55
+ enforce_eager=True,
56
+ **quantization_kwargs, # type: ignore[arg-type]
57
+ ) as vllm_model:
58
+ model_config = vllm_model.llm.llm_engine.model_config
59
+ assert model_config.using_transformers_backend()
60
+
61
+ transformers_outputs = vllm_model.generate_greedy_logprobs(
62
+ example_prompts, max_tokens=max_tokens, num_logprobs=num_logprobs
63
+ )
64
+
65
+ check_logprobs_close(
66
+ outputs_0_lst=transformers_outputs,
67
+ outputs_1_lst=vllm_outputs,
68
+ name_0="transformers",
69
+ name_1="vllm",
70
+ )
vllm/config/load.py
-1
@@ -49,7 +49,6 @@ class LoadConfig:
49
Streamer.
50
- "runai_streamer_sharded" will load weights from pre-sharded checkpoint
51
files using Run:ai Model Streamer.
52
- - "bitsandbytes" will load the weights using bitsandbytes quantization.
52
- "sharded_state" will load weights from pre-sharded checkpoint files,
53
supporting efficient loading of tensor-parallel models.
54
- "mistral" will load weights from consolidated safetensors files used by
vllm/config/model.py
-29
@@ -849,7 +849,6 @@ class ModelConfig:
849
self._try_verify_and_update_model_config()
850
self._verify_quantization()
851
self._verify_cuda_graph()
852
- self._verify_bnb_config()
852
853
def _supports_multimodal_for_mm_prefix(self) -> bool:
854
"""Whether multimodal inputs can still appear for this deployment.
@@ -1317,34 +1316,6 @@ class ModelConfig:
1316
)
1317
self.enforce_eager = True
1318
1320
- def _verify_bnb_config(self) -> None:
1321
- """
1322
- The current version of bitsandbytes (0.46.1) with 8-bit models does not
1323
- yet support CUDA graph.
1324
- # TODO Remove this when bitsandbytes supports.
1325
- """
1326
- is_bitsandbytes = self.quantization == "bitsandbytes"
1327
- has_quantization_config = self.model_arch_config.quantization_config is not None
1328
- is_8bit = (
1329
- self.model_arch_config.quantization_config.get("load_in_8bit", False) # type: ignore[union-attr]
1330
- if has_quantization_config
1331
- else False
1332
- )
1333
- if all(
1334
- [
1335
- is_bitsandbytes,
1336
- has_quantization_config,
1337
- is_8bit,
1338
- not self.enforce_eager,
1339
- ]
1340
- ):
1341
- logger.warning(
1342
- "CUDA graph is not supported on BitsAndBytes 8bit yet, "
1343
- "fallback to the eager mode."
1344
- )
1345
-
1346
- self.enforce_eager = True
1347
-
1319
def _verify_with_expert_parallelism(self) -> None:
1320
if not self.is_moe:
1321
raise ValueError(
vllm/engine/arg_utils.py
-7
@@ -1794,9 +1794,6 @@ class EngineArgs:
1794
)
1795
1796
def create_load_config(self) -> LoadConfig:
1797
- if self.quantization == "bitsandbytes":
1798
- self.load_format = "bitsandbytes"
1799
-
1797
if self.load_format == "tensorizer":
1798
if hasattr(self.model_loader_extra_config, "to_serializable"):
1799
self.model_loader_extra_config = (
@@ -2360,10 +2357,6 @@ class EngineArgs:
2357
"decreasing num_speculative_tokens"
2358
)
2359
2363
- # bitsandbytes pre-quantized model need a specific model loader
2364
- if model_config.quantization == "bitsandbytes":
2365
- self.quantization = self.load_format = "bitsandbytes"
2366
-
2360
# Attention config overrides
2361
attention_config = copy.deepcopy(self.attention_config)
2362
if self.attention_backend is not None:
vllm/model_executor/layers/fused_moe/routed_experts.py
-38
@@ -674,44 +674,6 @@ class RoutedExperts(PluggableLayer):
674
# based on the shard id. This will be whatever
675
# dimension intermediate_size_per_partition is used.
676
SHARD_ID_TO_SHARDED_DIM = {"w1": 0, "w2": 1, "w3": 0}
677
-
678
- # Case for BitsAndBytes
679
- use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
680
- if use_bitsandbytes_4bit:
681
- shard_dim = 0
682
-
683
- expert_data = param.data[expert_id]
684
- if shard_id == "w2":
685
- # BnB params are stored as flat packed tensors (e.g.
686
- # (packed_size, 1)), not in the logical weight layout.
687
- # Narrowing packed data for hidden-dim padding is not
688
- # meaningful, so require an exact shape match.
689
- if expert_data.shape != loaded_weight.shape:
690
- raise ValueError(
691
- "BitsAndBytes quantization with padded hidden_size "
692
- "(e.g., from DeepEP) is not supported. "
693
- f"Parameter shape {tuple(expert_data.shape)} != "
694
- f"checkpoint shape {tuple(loaded_weight.shape)}"
695
- )
696
- expert_data.copy_(loaded_weight)
697
- elif shard_id in ("w1", "w3"):
698
- # BnB stores weights as flat packed tensors. _load_w13 is
699
- # still used to split the w1/w3 portions along shard_dim.
700
- # _narrow_expert_data_for_padding will be a no-op since
701
- # packed sizes should already match; if DeepEP padding
702
- # causes a mismatch the copy_() will fail with a clear
703
- # shape error.
704
- full_load = True
705
- self._load_w13(
706
- shard_id=shard_id,
707
- shard_dim=shard_dim,
708
- loaded_weight=loaded_weight,
709
- expert_data=expert_data,
710
- tp_rank=self.moe_config.tp_rank,
711
- load_full=full_load,
712
- )
713
- return True if return_success else None
714
-
677
shard_dim = SHARD_ID_TO_SHARDED_DIM[shard_id]
678
if is_transposed:
679
shard_dim = int(not shard_dim)
vllm/model_executor/layers/linear.py
-113
@@ -1,7 +1,6 @@
1
# SPDX-License-Identifier: Apache-2.0
2
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4
-import itertools
4
from abc import abstractmethod
5
from collections.abc import Iterable
6
from typing import Any
@@ -96,23 +95,6 @@ def adjust_block_scale_shard(
95
return shard_size, shard_offset
96
97
99
-def adjust_bitsandbytes_4bit_shard(
100
- param: Parameter,
101
- shard_offsets: dict[str, tuple[int, int]],
102
- loaded_shard_id: str,
103
-) -> tuple[int, int]:
104
- """Adjust the quantization offsets and sizes for BitsAndBytes sharding."""
105
-
106
- total, _ = shard_offsets["total"]
107
- orig_offset, orig_size = shard_offsets[loaded_shard_id]
108
-
109
- quantized_total = param.data.shape[0]
110
- quantized_offset = orig_offset * quantized_total // total
111
- quantized_size = orig_size * quantized_total // total
112
-
113
- return quantized_size, quantized_offset
114
-
115
-
98
def adjust_scalar_to_fused_array(
99
param_data: torch.Tensor,
100
loaded_weight: torch.Tensor,
@@ -561,10 +543,6 @@ class ColumnParallelLinear(LinearBase):
543
output_dim = getattr(param, "output_dim", None)
544
545
is_sharded_weight = getattr(param, "is_sharded_weight", False)
564
- use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
565
- # bitsandbytes loads the weights of the specific portion
566
- # no need to narrow
567
- is_sharded_weight = is_sharded_weight or use_bitsandbytes_4bit
546
547
param_data = param.data
548
if output_dim is not None and not is_sharded_weight:
@@ -775,16 +753,6 @@ class MergedColumnParallelLinear(ColumnParallelLinear):
753
else self.output_sizes
754
)
755
current_shard_offset = 0
778
- use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
779
- if (
780
- use_bitsandbytes_4bit
781
- and isinstance(loaded_shard_id, tuple)
782
- and self.tp_size > 1
783
- ):
784
- raise NotImplementedError(
785
- "Shard id with multiple indices is not supported "
786
- "for BNB quantization with TP yet."
787
- )
756
shard_offsets: list[tuple[int, int, int]] = []
757
for i, output_size in enumerate(output_sizes):
758
shard_offsets.append((i, current_shard_offset, output_size))
@@ -809,17 +777,6 @@ class MergedColumnParallelLinear(ColumnParallelLinear):
777
param, shard_size, shard_offset
778
)
779
812
- if use_bitsandbytes_4bit:
813
- index = list(itertools.accumulate([0] + self.output_sizes))
814
- orig_offsets = {
815
- str(i): (index[i], size)
816
- for i, size in enumerate(self.output_sizes)
817
- }
818
- orig_offsets["total"] = (self.output_size, 0)
819
- shard_size, shard_offset = adjust_bitsandbytes_4bit_shard(
820
- param, orig_offsets, str(shard_id)
821
- )
822
-
780
loaded_weight_shard = loaded_weight.narrow(
781
output_dim, shard_offset, shard_size
782
)
@@ -851,21 +808,7 @@ class MergedColumnParallelLinear(ColumnParallelLinear):
808
param, shard_size, shard_offset
809
)
810
854
- use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
811
is_sharded_weight = getattr(param, "is_sharded_weight", False)
856
- # bitsandbytes loads the weights of the specific portion
857
- # no need to narrow
858
- is_sharded_weight = is_sharded_weight or use_bitsandbytes_4bit
859
-
860
- if use_bitsandbytes_4bit:
861
- index = list(itertools.accumulate([0] + self.output_sizes))
862
- orig_offsets = {
863
- str(i): (index[i], size) for i, size in enumerate(self.output_sizes)
864
- }
865
- orig_offsets["total"] = (self.output_size, 0)
866
- shard_size, shard_offset = adjust_bitsandbytes_4bit_shard(
867
- param, orig_offsets, str(loaded_shard_id)
868
- )
812
param_data = param_data.narrow(output_dim, shard_offset, shard_size)
813
start_idx = self.tp_rank * shard_size
814
if not is_sharded_weight:
@@ -1259,8 +1202,6 @@ class QKVParallelLinear(ColumnParallelLinear):
1202
self.total_num_kv_heads * self.v_head_size,
1203
),
1204
]
1262
- use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
1263
-
1205
packed_dim = getattr(param, "packed_dim", None)
1206
for shard_id, shard_offset, shard_size in shard_offsets:
1207
# Special case for Quantized Weights.
@@ -1282,30 +1223,6 @@ class QKVParallelLinear(ColumnParallelLinear):
1223
param, shard_size, shard_offset
1224
)
1225
1285
- if use_bitsandbytes_4bit:
1286
- orig_qkv_offsets = {
1287
- "q": (0, self.total_num_heads * self.head_size),
1288
- "k": (
1289
- self.total_num_heads * self.head_size,
1290
- self.total_num_kv_heads * self.head_size,
1291
- ),
1292
- "v": (
1293
- (self.total_num_heads + self.total_num_kv_heads)
1294
- * self.head_size,
1295
- self.total_num_kv_heads * self.v_head_size,
1296
- ),
1297
- "total": (
1298
- (self.total_num_heads + self.total_num_kv_heads)
1299
- * self.head_size
1300
- + self.total_num_kv_heads * self.v_head_size,
1301
- 0,
1302
- ),
1303
- }
1304
-
1305
- shard_size, shard_offset = adjust_bitsandbytes_4bit_shard(
1306
- param, orig_qkv_offsets, shard_id
1307
- )
1308
-
1226
loaded_weight_shard = loaded_weight.narrow(
1227
output_dim, shard_offset, shard_size
1228
)
@@ -1345,33 +1262,7 @@ class QKVParallelLinear(ColumnParallelLinear):
1262
param, shard_size, shard_offset
1263
)
1264
1348
- use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
1265
is_sharded_weight = getattr(param, "is_sharded_weight", False)
1350
- # bitsandbytes loads the weights of the specific portion
1351
- # no need to narrow
1352
- is_sharded_weight = is_sharded_weight or use_bitsandbytes_4bit
1353
-
1354
- if use_bitsandbytes_4bit:
1355
- orig_qkv_offsets = {
1356
- "q": (0, self.num_heads * self.head_size),
1357
- "k": (
1358
- self.num_heads * self.head_size,
1359
- self.num_kv_heads * self.head_size,
1360
- ),
1361
- "v": (
1362
- (self.num_heads + self.num_kv_heads) * self.head_size,
1363
- self.num_kv_heads * self.v_head_size,
1364
- ),
1365
- "total": (
1366
- (self.num_heads + self.num_kv_heads) * self.head_size
1367
- + self.num_kv_heads * self.v_head_size,
1368
- 0,
1369
- ),
1370
- }
1371
- shard_size, shard_offset = adjust_bitsandbytes_4bit_shard(
1372
- param, orig_qkv_offsets, loaded_shard_id
1373
- )
1374
-
1266
param_data = param_data.narrow(output_dim, shard_offset, shard_size)
1267
if loaded_shard_id == "q":
1268
shard_rank = self.tp_rank
@@ -1716,11 +1607,7 @@ class RowParallelLinear(LinearBase):
1607
1608
def weight_loader(self, param: Parameter, loaded_weight: torch.Tensor):
1609
input_dim = getattr(param, "input_dim", None)
1719
- use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
1610
is_sharded_weight = getattr(param, "is_sharded_weight", False)
1721
- # bitsandbytes loads the weights of the specific portion
1722
- # no need to narrow
1723
- is_sharded_weight = is_sharded_weight or use_bitsandbytes_4bit
1611
1612
param_data = param.data
1613
if input_dim is not None and not is_sharded_weight:
vllm/model_executor/layers/quantization/__init__.py
-3
@@ -25,7 +25,6 @@ QuantizationMethods = Literal[
25
"awq_marlin",
26
"humming",
27
"compressed-tensors",
28
- "bitsandbytes",
28
"experts_int8",
29
"quark",
30
"moe_wna16",
@@ -117,7 +116,6 @@ def get_quantization_config(quantization: str) -> type[QuantizationConfig]:
116
117
from .auto_awq import AutoAWQConfig
118
from .auto_gptq import AutoGPTQConfig
120
- from .bitsandbytes import BitsAndBytesConfig
119
from .compressed_tensors.compressed_tensors import (
120
CompressedTensorsConfig,
121
)
@@ -153,7 +151,6 @@ def get_quantization_config(quantization: str) -> type[QuantizationConfig]:
151
"gptq": AutoGPTQConfig,
152
"gptq_marlin": AutoGPTQConfig,
153
"compressed-tensors": CompressedTensorsConfig,
156
- "bitsandbytes": BitsAndBytesConfig,
154
"experts_int8": ExpertsInt8Config,
155
"quark": QuarkConfig,
156
"moe_wna16": MoeWNA16Config,
vllm/model_executor/layers/quantization/bitsandbytes.py
deleted
-614
@@ -1,614 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-
4
-from functools import cached_property
5
-from typing import Any, Union
6
-
7
-import torch
8
-from packaging import version
9
-
10
-from vllm.model_executor.layers.fused_moe import (
11
- FusedMoEConfig,
12
- FusedMoEMethodBase,
13
- FusedMoEQuantConfig,
14
- RoutedExperts,
15
- SharedExperts,
16
-)
17
-from vllm.model_executor.layers.linear import (
18
- LinearBase,
19
- LinearMethodBase,
20
- UnquantizedLinearMethod,
21
- set_weight_attrs,
22
-)
23
-from vllm.model_executor.layers.quantization import (
24
- QuantizationConfig,
25
- QuantizationMethods,
26
-)
27
-from vllm.platforms import current_platform
28
-from vllm.utils.torch_utils import direct_register_custom_op
29
-
30
-
31
-def _check_bitsandbytes_version():
32
- min_version = "0.49.2" if current_platform.is_rocm() else "0.48.1"
33
- try:
34
- import bitsandbytes
35
-
36
- if version.parse(bitsandbytes.__version__) < version.parse(min_version):
37
- raise ImportError(
38
- "bitsandbytes version is wrong. Please "
39
- f"install bitsandbytes>={min_version}."
40
- )
41
- except ImportError as err:
42
- raise ImportError(
43
- f"Please install bitsandbytes>={min_version} via "
44
- f"`pip install bitsandbytes>={min_version}` to use "
45
- "bitsandbytes quantizer."
46
- ) from err
47
-
48
-
49
-class BitsAndBytesConfig(QuantizationConfig):
50
- """Config class for BitsAndBytes Quantization.
51
-
52
- Reference: https://arxiv.org/abs/2305.14314
53
- """
54
-
55
- def __init__(
56
- self,
57
- load_in_8bit: bool = False,
58
- load_in_4bit: bool = True,
59
- bnb_4bit_compute_dtype: str = "float32",
60
- bnb_4bit_quant_storage: str = "uint8",
61
- bnb_4bit_quant_type: str = "fp4",
62
- bnb_4bit_use_double_quant: bool = False,
63
- llm_int8_enable_fp32_cpu_offload: bool = False,
64
- llm_int8_has_fp16_weight: bool = False,
65
- llm_int8_skip_modules: list[str] | None = None,
66
- llm_int8_threshold: float = 6.0,
67
- ) -> None:
68
- super().__init__()
69
- self.load_in_8bit = load_in_8bit
70
- self.load_in_4bit = load_in_4bit
71
- self.bnb_4bit_compute_dtype = bnb_4bit_compute_dtype
72
- self.bnb_4bit_quant_storage = bnb_4bit_quant_storage
73
- self.bnb_4bit_quant_type = bnb_4bit_quant_type
74
- self.bnb_4bit_use_double_quant = bnb_4bit_use_double_quant
75
- self.llm_int8_enable_fp32_cpu_offload = llm_int8_enable_fp32_cpu_offload
76
- self.llm_int8_has_fp16_weight = llm_int8_has_fp16_weight
77
- self.llm_int8_skip_modules = llm_int8_skip_modules or []
78
- self.llm_int8_threshold = llm_int8_threshold
79
-
80
- if self.bnb_4bit_quant_storage not in ["uint8"]:
81
- raise ValueError(
82
- f"Unsupported bnb_4bit_quant_storage: {self.bnb_4bit_quant_storage}"
83
- )
84
-
85
- def __repr__(self) -> str:
86
- return (
87
- f"BitsAndBytesConfig(load_in_8bit={self.load_in_8bit}, "
88
- f"load_in_4bit={self.load_in_4bit}, "
89
- f"bnb_4bit_compute_dtype={self.bnb_4bit_compute_dtype}, "
90
- f"bnb_4bit_quant_storage={self.bnb_4bit_quant_storage}, "
91
- f"bnb_4bit_quant_type={self.bnb_4bit_quant_type}, "
92
- f"llm_int8_skip_modules={self.llm_int8_skip_modules})"
93
- )
94
-
95
- @classmethod
96
- def get_name(self) -> QuantizationMethods:
97
- return "bitsandbytes"
98
-
99
- @classmethod
100
- def get_supported_act_dtypes(self) -> list[torch.dtype]:
101
- return [torch.float32, torch.float16, torch.bfloat16]
102
-
103
- @classmethod
104
- def get_min_capability(cls) -> int:
105
- return 70
106
-
107
- @staticmethod
108
- def get_config_filenames() -> list[str]:
109
- return []
110
-
111
- @classmethod
112
- def from_config(cls, config: dict[str, Any]) -> "BitsAndBytesConfig":
113
- def get_safe_value(config, keys, default_value=None):
114
- try:
115
- value = cls.get_from_keys(config, keys)
116
- return value if value is not None else default_value
117
- except ValueError:
118
- return default_value
119
-
120
- load_in_8bit = get_safe_value(config, ["load_in_8bit"], default_value=False)
121
- load_in_4bit = get_safe_value(config, ["load_in_4bit"], default_value=True)
122
- bnb_4bit_compute_dtype = get_safe_value(
123
- config, ["bnb_4bit_compute_dtype"], default_value="float32"
124
- )
125
- bnb_4bit_quant_storage = get_safe_value(
126
- config, ["bnb_4bit_quant_storage"], default_value="uint8"
127
- )
128
- bnb_4bit_quant_type = get_safe_value(
129
- config, ["bnb_4bit_quant_type"], default_value="fp4"
130
- )
131
- bnb_4bit_use_double_quant = get_safe_value(
132
- config, ["bnb_4bit_use_double_quant"], default_value=False
133
- )
134
- llm_int8_enable_fp32_cpu_offload = get_safe_value(
135
- config, ["llm_int8_enable_fp32_cpu_offload"], default_value=False
136
- )
137
- llm_int8_has_fp16_weight = get_safe_value(
138
- config, ["llm_int8_has_fp16_weight"], default_value=False
139
- )
140
- llm_int8_skip_modules = get_safe_value(
141
- config, ["llm_int8_skip_modules"], default_value=[]
142
- )
143
- llm_int8_threshold = get_safe_value(
144
- config, ["llm_int8_threshold"], default_value=6.0
145
- )
146
-
147
- return cls(
148
- load_in_8bit=load_in_8bit,
149
- load_in_4bit=load_in_4bit,
150
- bnb_4bit_compute_dtype=bnb_4bit_compute_dtype,
151
- bnb_4bit_quant_storage=bnb_4bit_quant_storage,
152
- bnb_4bit_quant_type=bnb_4bit_quant_type,
153
- bnb_4bit_use_double_quant=bnb_4bit_use_double_quant,
154
- llm_int8_enable_fp32_cpu_offload=llm_int8_enable_fp32_cpu_offload,
155
- llm_int8_has_fp16_weight=llm_int8_has_fp16_weight,
156
- llm_int8_skip_modules=llm_int8_skip_modules,
157
- llm_int8_threshold=llm_int8_threshold,
158
- )
159
-
160
- def get_quant_method(
161
- self, layer: torch.nn.Module, prefix: str
162
- ) -> Union["LinearMethodBase", "BitsAndBytesMoEMethod"] | None:
163
- if isinstance(layer, LinearBase):
164
- if is_layer_skipped_bnb(prefix, self.llm_int8_skip_modules):
165
- return UnquantizedLinearMethod()
166
- return BitsAndBytesLinearMethod(self)
167
- elif isinstance(layer, RoutedExperts):
168
- return BitsAndBytesMoEMethod(self, layer.moe_config)
169
- return None
170
-
171
-
172
-class BitsAndBytesWeightParameter(torch.nn.Parameter):
173
- @cached_property
174
- def dtype(self) -> torch.dtype:
175
- return torch.get_default_dtype()
176
-
177
-
178
-def is_layer_skipped_bnb(prefix: str, llm_int8_skip_modules: list[str]):
179
- # Split the prefix into its dot-separated components
180
- components = prefix.split(".")
181
-
182
- # Check if any of the skip modules exactly matches any component
183
- substr_check = any(
184
- module_name in components for module_name in llm_int8_skip_modules
185
- )
186
-
187
- # Allow certain layers to not be quantized
188
- set_components = set(".".join(components[: i + 1]) for i in range(len(components)))
189
- set_llm_int8_skip_modules = set(llm_int8_skip_modules)
190
- prefix_check = len(set_llm_int8_skip_modules & set_components) != 0
191
-
192
- return substr_check or prefix_check
193
-
194
-
195
-def calculate_quant_ratio(dtype):
196
- if dtype.is_floating_point:
197
- return torch.finfo(dtype).bits // torch.iinfo(torch.uint8).bits
198
- else:
199
- return torch.iinfo(dtype).bits // torch.iinfo(torch.uint8).bits
200
-
201
-
202
-class BitsAndBytesLinearMethod(LinearMethodBase):
203
- """Linear method for BitsAndBytes.
204
-
205
- Args:
206
- quant_config: The BitsAndBytes quantization config.
207
- """
208
-
209
- def __init__(self, quant_config: BitsAndBytesConfig):
210
- _check_bitsandbytes_version()
211
- self.quant_config = quant_config
212
-
213
- def create_weights(
214
- self,
215
- layer: torch.nn.Module,
216
- input_size_per_partition: int,
217
- output_partition_sizes: list[int],
218
- input_size: int,
219
- output_size: int,
220
- params_dtype: torch.dtype,
221
- **extra_weight_attrs,
222
- ):
223
- from bitsandbytes.nn import Int8Params
224
-
225
- def create_qweight_for_8bit():
226
- qweight = Int8Params(
227
- data=torch.empty(
228
- sum(output_partition_sizes),
229
- input_size_per_partition,
230
- dtype=torch.int8,
231
- ),
232
- has_fp16_weights=self.quant_config.llm_int8_has_fp16_weight,
233
- requires_grad=False,
234
- )
235
- set_weight_attrs(
236
- qweight,
237
- {
238
- "input_dim": 0,
239
- "output_dim": 0,
240
- "pack_factor": 1,
241
- "use_bitsandbytes_8bit": True,
242
- "generation": 0,
243
- },
244
- )
245
- return qweight
246
-
247
- def create_qweight_for_4bit():
248
- quant_ratio = calculate_quant_ratio(params_dtype)
249
-
250
- total_size = input_size_per_partition * sum(output_partition_sizes)
251
- if total_size % quant_ratio != 0:
252
- raise ValueError(
253
- "The input size is not aligned with the quantized weight shape."
254
- )
255
-
256
- qweight = BitsAndBytesWeightParameter(
257
- torch.empty(total_size // quant_ratio, 1, dtype=torch.uint8),
258
- requires_grad=False,
259
- )
260
- set_weight_attrs(
261
- qweight,
262
- {
263
- "input_dim": 0,
264
- "output_dim": 0,
265
- "pack_factor": quant_ratio,
266
- "use_bitsandbytes_4bit": True,
267
- },
268
- )
269
- return qweight
270
-
271
- if self.quant_config.load_in_8bit:
272
- qweight = create_qweight_for_8bit()
273
- else:
274
- qweight = create_qweight_for_4bit()
275
- # Enable parameters to have the same name as in the BNB
276
- # checkpoint format.
277
- layer.register_parameter("weight", qweight)
278
- set_weight_attrs(qweight, extra_weight_attrs)
279
-
280
- def apply(
281
- self,
282
- layer: torch.nn.Module,
283
- x: torch.Tensor,
284
- bias: torch.Tensor | None = None,
285
- ) -> torch.Tensor:
286
- if self.quant_config.load_in_8bit:
287
- return self._apply_8bit_weight(layer, x, bias)
288
- else:
289
- return self._apply_4bit_weight(layer, x, bias)
290
-
291
- def _apply_8bit_weight(
292
- self,
293
- layer: torch.nn.Module,
294
- x: torch.Tensor,
295
- bias: torch.Tensor | None = None,
296
- ) -> torch.Tensor:
297
- # only load the bitsandbytes module when needed
298
- from bitsandbytes import MatmulLtState, matmul
299
-
300
- original_type = x.dtype
301
- original_shape = x.shape
302
- reshape_after_matmul = False
303
- if x.ndim > 2:
304
- x = x.reshape(-1, x.size(-1))
305
- reshape_after_matmul = True
306
- bf_x = x.to(torch.bfloat16)
307
-
308
- qweight = layer.weight
309
- offsets = qweight.bnb_shard_offsets
310
- quant_states = qweight.bnb_quant_state
311
- matmul_states = qweight.matmul_state
312
- generation = qweight.generation
313
-
314
- out_dim_0 = x.shape[0]
315
- out_dim_1 = sum(
316
- [quant_state[1].shape[0] for quant_state in quant_states.items()]
317
- )
318
- out = torch.empty(out_dim_0, out_dim_1, dtype=torch.float16, device=x.device)
319
-
320
- current_index = 0
321
- for i in range(len(quant_states)):
322
- output_size = quant_states[i].shape[0]
323
-
324
- # in profile_run or the first generation of inference,
325
- # create new matmul_states
326
- if generation == 0 or generation == 1:
327
- matmul_states[i] = MatmulLtState()
328
- matmul_states[i].CB = qweight[offsets[i] : offsets[i + 1]]
329
- matmul_states[i].SCB = quant_states[i].to(x.device)
330
- matmul_states[i].threshold = self.quant_config.llm_int8_threshold
331
- matmul_states[
332
- i
333
- ].has_fp16_weights = self.quant_config.llm_int8_has_fp16_weight
334
- matmul_states[i].is_training = False
335
- if (
336
- matmul_states[i].threshold > 0.0
337
- and not matmul_states[i].has_fp16_weights
338
- ):
339
- matmul_states[i].use_pool = True
340
-
341
- new_x = bf_x.unsqueeze(0)
342
-
343
- out[:, current_index : current_index + output_size] = matmul(
344
- new_x, qweight[offsets[i] : offsets[i + 1]], state=matmul_states[i]
345
- )
346
-
347
- current_index += output_size
348
-
349
- out = out.to(original_type)
350
-
351
- if reshape_after_matmul:
352
- out = out.view(*original_shape[:-1], out.size(-1))
353
-
354
- if bias is not None:
355
- out += bias
356
-
357
- qweight.generation += 1
358
-
359
- return out
360
-
361
- def _apply_4bit_weight(
362
- self,
363
- layer: torch.nn.Module,
364
- x: torch.Tensor,
365
- bias: torch.Tensor | None = None,
366
- ) -> torch.Tensor:
367
- original_type = x.dtype
368
- original_shape = x.shape
369
- reshape_after_matmul = False
370
- if x.ndim > 2:
371
- x = x.reshape(-1, x.size(-1))
372
- reshape_after_matmul = True
373
- bf_x = x.to(torch.bfloat16)
374
-
375
- qweight = layer.weight
376
- quant_states = qweight.bnb_quant_state
377
- offsets = qweight.bnb_shard_offsets
378
-
379
- out_dim_0 = x.shape[0]
380
- out_dim_1 = sum(
381
- [quant_state[1].shape[0] for quant_state in quant_states.items()]
382
- )
383
- out = torch.empty(out_dim_0, out_dim_1, dtype=torch.bfloat16, device=x.device)
384
- apply_bnb_4bit(bf_x, qweight, offsets, out)
385
- out = out.to(original_type)
386
-
387
- if reshape_after_matmul:
388
- out = out.view(*original_shape[:-1], out.size(-1))
389
-
390
- if bias is not None:
391
- out += bias
392
-
393
- return out
394
-
395
-
396
-def _apply_bnb_4bit(
397
- x: torch.Tensor,
398
- weight: torch.Tensor,
399
- offsets: torch.Tensor,
400
- out: torch.Tensor,
401
-) -> None:
402
- # only load the bitsandbytes module when needed
403
- from bitsandbytes import matmul_4bit
404
-
405
- quant_states = weight.bnb_quant_state
406
- current_index = 0
407
- for i in range(len(quant_states)):
408
- output_size = quant_states[i].shape[0]
409
- # It is more efficient to use out kwarg like
410
- # matmul_4bit(..., out = ...). Infeasible now due to the bug
411
- # https://github.com/TimDettmers/bitsandbytes/issues/1235.
412
- # Need to change after the bug is fixed.
413
- out[:, current_index : current_index + output_size] = matmul_4bit(
414
- x, weight[offsets[i] : offsets[i + 1]].t(), quant_states[i]
415
- )
416
- current_index += output_size
417
-
418
-
419
-def _apply_bnb_4bit_fake(
420
- x: torch.Tensor,
421
- weight: torch.Tensor,
422
- offsets: torch.Tensor,
423
- out: torch.Tensor,
424
-) -> None:
425
- return
426
-
427
-
428
-try:
429
- direct_register_custom_op(
430
- op_name="apply_bnb_4bit",
431
- op_func=_apply_bnb_4bit,
432
- mutates_args=["out"],
433
- fake_impl=_apply_bnb_4bit_fake,
434
- dispatch_key=current_platform.dispatch_key,
435
- )
436
- apply_bnb_4bit = torch.ops.vllm.apply_bnb_4bit
437
-
438
-except AttributeError as error:
439
- raise error
440
-
441
-
442
-class BitsAndBytesMoEMethod(FusedMoEMethodBase):
443
- """MoE method for BitsAndBytes.
444
-
445
- Args:
446
- quant_config: The BitsAndBytes quantization config.
447
- """
448
-
449
- def __init__(
450
- self,
451
- quant_config: BitsAndBytesConfig,
452
- moe: FusedMoEConfig,
453
- ):
454
- super().__init__(moe)
455
- _check_bitsandbytes_version()
456
- self.quant_config = quant_config
457
-
458
- def create_weights(
459
- self,
460
- layer: RoutedExperts,
461
- num_experts: int,
462
- hidden_size: int,
463
- intermediate_size_per_partition: int,
464
- params_dtype: torch.dtype,
465
- **extra_weight_attrs,
466
- ):
467
- if self.quant_config.load_in_8bit:
468
- call_fun = self._create_weights_8bit
469
- else:
470
- call_fun = self._create_weights_4bit
471
- call_fun(
472
- layer,
473
- num_experts,
474
- hidden_size,
475
- intermediate_size_per_partition,
476
- params_dtype,
477
- **extra_weight_attrs,
478
- )
479
-
480
- def get_fused_moe_quant_config(
481
- self, layer: RoutedExperts
482
- ) -> FusedMoEQuantConfig | None:
483
- return None
484
-
485
- def apply(
486
- self,
487
- layer: RoutedExperts,
488
- x: torch.Tensor,
489
- topk_weights: torch.Tensor,
490
- topk_ids: torch.Tensor,
491
- shared_experts: SharedExperts | None,
492
- shared_experts_input: torch.Tensor | None,
493
- ) -> torch.Tensor:
494
- from vllm.model_executor.layers.fused_moe import fused_experts
495
-
496
- # TODO(bnell): Do these need to be called on the hot path?
497
- if self.quant_config.load_in_8bit:
498
- w13, w2 = self._apply_8bit_dequant(layer)
499
- else:
500
- w13, w2 = self._apply_4bit_dequnt(layer)
501
- return fused_experts(
502
- hidden_states=x,
503
- w1=w13,
504
- w2=w2,
505
- topk_weights=topk_weights,
506
- topk_ids=topk_ids,
507
- activation=layer.activation,
508
- apply_router_weight_on_input=layer.apply_router_weight_on_input,
509
- global_num_experts=layer.global_num_experts,
510
- expert_map=layer.expert_map,
511
- quant_config=self.moe_quant_config,
512
- )
513
-
514
- def _create_weights_4bit(
515
- self,
516
- layer: torch.nn.Module,
517
- num_experts: int,
518
- hidden_size: int,
519
- intermediate_size_per_partition: int,
520
- params_dtype: torch.dtype,
521
- **extra_weight_attrs,
522
- ):
523
- quant_ratio = calculate_quant_ratio(params_dtype)
524
- # Fused gate_up_proj (column parallel)
525
- w13_total_size = (
526
- hidden_size * self.moe.w13_num_shards * intermediate_size_per_partition
527
- ) // quant_ratio
528
- w13_qweight = torch.nn.Parameter(
529
- torch.empty(
530
- num_experts,
531
- w13_total_size,
532
- 1,
533
- dtype=torch.uint8,
534
- ),
535
- requires_grad=False,
536
- )
537
- layer.register_parameter("w13_weight", w13_qweight)
538
- set_weight_attrs(w13_qweight, extra_weight_attrs)
539
- set_weight_attrs(
540
- w13_qweight,
541
- {
542
- "num_experts": num_experts,
543
- "input_dim": hidden_size,
544
- "output_dim": self.moe.w13_num_shards * intermediate_size_per_partition,
545
- "experts_shape": (
546
- num_experts,
547
- intermediate_size_per_partition * self.moe.w13_num_shards,
548
- hidden_size,
549
- ),
550
- "pack_factor": quant_ratio,
551
- "use_bitsandbytes_4bit": True,
552
- },
553
- )
554
- # down_proj (row parallel)
555
- w2_total_size = (hidden_size * intermediate_size_per_partition) // quant_ratio
556
- w2_qweight = torch.nn.Parameter(
557
- torch.empty(
558
- num_experts,
559
- w2_total_size,
560
- 1,
561
- dtype=torch.uint8,
562
- ),
563
- requires_grad=False,
564
- )
565
- set_weight_attrs(
566
- w2_qweight,
567
- {
568
- "num_experts": num_experts,
569
- "input_dim": intermediate_size_per_partition,
570
- "output_dim": hidden_size,
571
- "experts_shape": (
572
- num_experts,
573
- hidden_size,
574
- intermediate_size_per_partition,
575
- ),
576
- "pack_factor": quant_ratio,
577
- "use_bitsandbytes_4bit": True,
578
- },
579
- )
580
- layer.register_parameter("w2_weight", w2_qweight)
581
- set_weight_attrs(w2_qweight, extra_weight_attrs)
582
-
583
- def _create_weights_8bit(
584
- self,
585
- layer: torch.nn.Module,
586
- num_experts: int,
587
- hidden_size: int,
588
- intermediate_size_per_partition: int,
589
- params_dtype: torch.dtype,
590
- **extra_weight_attrs,
591
- ):
592
- raise NotImplementedError
593
-
594
- def _apply_4bit_dequnt(
595
- self, layer: torch.nn.Module
596
- ) -> tuple[torch.Tensor, torch.Tensor]:
597
- from bitsandbytes.functional import dequantize_4bit
598
-
599
- w13 = dequantize_4bit(
600
- layer.w13_weight.reshape(-1, 1),
601
- layer.w13_weight.bnb_quant_state,
602
- )
603
- w2 = dequantize_4bit(
604
- layer.w2_weight.reshape(-1, 1),
605
- layer.w2_weight.bnb_quant_state,
606
- )
607
- w13 = w13.reshape(layer.w13_weight.experts_shape)
608
- w2 = w2.reshape(layer.w2_weight.experts_shape)
609
- return w13, w2
610
-
611
- def _apply_8bit_dequant(
612
- self, layer: torch.nn.Module
613
- ) -> tuple[torch.Tensor, torch.Tensor]:
614
- raise NotImplementedError
vllm/model_executor/model_loader/__init__.py
-4
@@ -9,7 +9,6 @@ from vllm.config import ModelConfig, VllmConfig
9
from vllm.config.load import LoadConfig
10
from vllm.logger import init_logger
11
from vllm.model_executor.model_loader.base_loader import BaseModelLoader
12
-from vllm.model_executor.model_loader.bitsandbytes_loader import BitsAndBytesModelLoader
12
from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
13
from vllm.model_executor.model_loader.dummy_loader import DummyModelLoader
14
from vllm.model_executor.model_loader.modelexpress_loader import (
@@ -33,7 +32,6 @@ logger = init_logger(__name__)
32
LoadFormats = Literal[
33
"auto",
34
"hf",
36
- "bitsandbytes",
35
"dummy",
36
"fastsafetensors",
37
"instanttensor",
@@ -50,7 +48,6 @@ LoadFormats = Literal[
48
_LOAD_FORMAT_TO_MODEL_LOADER: dict[str, type[BaseModelLoader]] = {
49
"auto": DefaultModelLoader,
50
"hf": DefaultModelLoader,
53
- "bitsandbytes": BitsAndBytesModelLoader,
51
"dummy": DummyModelLoader,
52
"fastsafetensors": DefaultModelLoader,
53
"instanttensor": DefaultModelLoader,
@@ -150,7 +147,6 @@ __all__ = [
147
"get_model_cls",
148
"register_model_loader",
149
"BaseModelLoader",
153
- "BitsAndBytesModelLoader",
150
"ModelExpressModelLoader",
151
"DefaultModelLoader",
152
"DummyModelLoader",
vllm/model_executor/model_loader/bitsandbytes_loader.py
deleted
-834
@@ -1,834 +0,0 @@
1
-# SPDX-License-Identifier: Apache-2.0
2
-# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
-import fnmatch
4
-import glob
5
-import itertools
6
-import math
7
-import os
8
-from collections.abc import Callable, Generator
9
-from typing import Any
10
-
11
-import numpy as np
12
-import torch
13
-from packaging import version
14
-from torch import nn
15
-from transformers.utils import SAFE_WEIGHTS_INDEX_NAME
16
-
17
-from vllm.config import ModelConfig
18
-from vllm.config.load import LoadConfig
19
-from vllm.distributed import (
20
- get_tensor_model_parallel_rank,
21
- get_tensor_model_parallel_world_size,
22
-)
23
-from vllm.logger import init_logger
24
-from vllm.lora.utils import is_moe_model
25
-from vllm.model_executor.layers.fused_moe import RoutedExperts
26
-from vllm.model_executor.layers.linear import (
27
- LinearBase,
28
- MergedColumnParallelLinear,
29
- QKVParallelLinear,
30
- ReplicatedLinear,
31
- RowParallelLinear,
32
-)
33
-from vllm.model_executor.model_loader.base_loader import BaseModelLoader
34
-from vllm.model_executor.model_loader.utils import ParamMapping
35
-from vllm.model_executor.model_loader.weight_utils import (
36
- download_safetensors_index_file_from_hf,
37
- download_weights_from_hf,
38
- filter_duplicate_safetensors_files,
39
- filter_files_not_needed_for_inference,
40
- pt_weights_iterator,
41
- safetensors_weights_iterator,
42
-)
43
-from vllm.model_executor.models import is_pooling_model
44
-from vllm.model_executor.utils import (
45
- get_moe_expert_mapping,
46
- get_packed_modules_mapping,
47
- set_weight_attrs,
48
-)
49
-from vllm.platforms import current_platform
50
-from vllm.transformers_utils.repo_utils import hf_api
51
-from vllm.utils.torch_utils import set_default_torch_dtype
52
-
53
-logger = init_logger(__name__)
54
-
55
-
56
-class BitsAndBytesModelLoader(BaseModelLoader):
57
- """Model loader to load model weights with BitsAndBytes quantization."""
58
-
59
- possible_config_file_names = ["adapter_config.json"]
60
-
61
- def __init__(self, load_config: LoadConfig):
62
- super().__init__(load_config)
63
-
64
- # Save the module names without sharding.
65
- self.unsharded_weights_modules: list[str] = []
66
- # Save the module names that are sharded by column.
67
- self.column_sharded_weights_modules: list[str] = []
68
- # Modules whose weights might have fused on disk
69
- # we need their output_sizes to make shard in flight correctly with TP
70
- self.maybe_fused_weights_modules: dict[str, list[int]] = {}
71
- # Store all module names (from transformers) that support
72
- # BNB quantization.
73
- self.target_modules: list[str] = []
74
- self.tp_disabled_modules: list[str] = []
75
- # Store the mapping of expert parameters for MoE models.
76
- self.expert_params_mapping: list[tuple[str, str, int, str]] = []
77
- # mapping weight names from transformers to vllm.
78
- self.weight_mapper: Callable = lambda name: name
79
- self.pre_quant: bool = False
80
- self.load_8bit: bool = False
81
- self.is_pool_model: bool = False
82
-
83
- def _get_weight_files(
84
- self,
85
- model_name_or_path: str,
86
- allowed_patterns: list[str],
87
- revision: str | None = None,
88
- ) -> tuple[str, list[str], str]:
89
- """Retrieve weight files. Download the files if necessary.
90
-
91
- Return the weight files and the file pattern."""
92
- is_local = os.path.isdir(model_name_or_path)
93
-
94
- if is_local:
95
- for pattern in allowed_patterns:
96
- weight_files = glob.glob(os.path.join(model_name_or_path, pattern))
97
- if weight_files:
98
- return model_name_or_path, weight_files, pattern
99
- else:
100
- repo_files = hf_api().list_repo_files(repo_id=model_name_or_path)
101
- for pattern in allowed_patterns:
102
- matching_files = fnmatch.filter(repo_files, pattern)
103
- if matching_files:
104
- hf_folder = download_weights_from_hf(
105
- model_name_or_path,
106
- self.load_config.download_dir,
107
- [pattern],
108
- revision,
109
- ignore_patterns=self.load_config.ignore_patterns,
110
- )
111
- return (
112
- hf_folder,
113
- glob.glob(os.path.join(hf_folder, pattern)),
114
- pattern,
115
- )
116
-
117
- raise RuntimeError(f"No model weights found in: `{model_name_or_path}`")
118
-
119
- def _prepare_weights(
120
- self, model_name_or_path: str, revision: str | None
121
- ) -> tuple[list[str], bool]:
122
- """Prepare weight files for the model."""
123
-
124
- allowed_patterns = ["*.safetensors", "*.bin", "*.pt"]
125
-
126
- hf_folder, hf_weights_files, matched_pattern = self._get_weight_files(
127
- model_name_or_path, allowed_patterns, revision
128
- )
129
-
130
- use_safetensors = matched_pattern == "*.safetensors"
131
- is_local = os.path.isdir(model_name_or_path)
132
- index_file = SAFE_WEIGHTS_INDEX_NAME
133
- if use_safetensors:
134
- # For models like Mistral-7B-Instruct-v0.3
135
- # there are both sharded safetensors files and a consolidated
136
- # safetensors file. Using both breaks.
137
- # Here, we download the `model.safetensors.index.json` and filter
138
- # any files not found in the index.
139
- if not is_local:
140
- download_safetensors_index_file_from_hf(
141
- model_name_or_path,
142
- index_file,
143
- cache_dir=self.load_config.download_dir,
144
- revision=revision,
145
- )
146
- hf_weights_files = filter_duplicate_safetensors_files(
147
- hf_weights_files, hf_folder, index_file
148
- )
149
- else:
150
- hf_weights_files = filter_files_not_needed_for_inference(hf_weights_files)
151
-
152
- if len(hf_weights_files) == 0:
153
- raise RuntimeError(
154
- f"Cannot find any model weights with `{model_name_or_path}`"
155
- )
156
-
157
- return hf_weights_files, use_safetensors
158
-
159
- def _hf_weight_iter(self, hf_weights_files, use_safetensors: bool):
160
- def _maybe_pool_model(module_name: str):
161
- # For pool model, we need to add the prefix `model.`
162
- # for the weight name if possible.
163
- if (
164
- self.is_pool_model
165
- and self.target_modules[0].startswith("model.")
166
- and not module_name.startswith("model.")
167
- ):
168
- return "model." + module_name
169
-
170
- return module_name
171
-
172
- if use_safetensors:
173
- iterator = safetensors_weights_iterator(
174
- hf_weights_files,
175
- self.load_config.use_tqdm_on_load,
176
- )
177
- else:
178
- iterator = pt_weights_iterator(
179
- hf_weights_files,
180
- self.load_config.use_tqdm_on_load,
181
- self.load_config.pt_load_map_location,
182
- )
183
- for org_name, param in iterator:
184
- # mapping weight names from transformers to vllm while preserving
185
- # original names.
186
- mapped_name = self.weight_mapper(org_name)
187
- mapped_name = _maybe_pool_model(mapped_name)
188
-
189
- yield org_name, mapped_name, param
190
-
191
- def _get_quantized_weights_iterator(
192
- self,
193
- model_name_or_path: str,
194
- revision: str | None,
195
- ) -> tuple[Generator[tuple[str, torch.Tensor], None, None], dict[str, Any]]:
196
- """Get an iterator to the model weights with bitsandbytes quantization,
197
- as well as the quantization state dictionary."""
198
-
199
- # only load the bitsandbytes module when needed
200
- try:
201
- import bitsandbytes
202
-
203
- if version.parse(bitsandbytes.__version__) < version.parse("0.46.1"):
204
- raise ImportError(
205
- "bitsandbytes version is wrong. Please "
206
- "install bitsandbytes>=0.46.1."
207
- )
208
- except ImportError as err:
209
- raise ImportError(
210
- "Please install bitsandbytes>=0.46.1 via "
211
- "`pip install bitsandbytes>=0.46.1` to use "
212
- "bitsandbytes quantizer."
213
- ) from err
214
-
215
- hf_weights_files, use_safetensors = self._prepare_weights(
216
- model_name_or_path, revision
217
- )
218
-
219
- quant_state_dict: dict[str, Any] = {}
220
-
221
- if self.pre_quant:
222
- if self.load_8bit:
223
- return self._quantized_8bit_generator(
224
- hf_weights_files, use_safetensors, quant_state_dict
225
- ), quant_state_dict
226
- else:
227
- return self._quantized_4bit_generator(
228
- hf_weights_files, use_safetensors, quant_state_dict
229
- ), quant_state_dict
230
-
231
- return self._unquantized_generator(
232
- hf_weights_files, use_safetensors, quant_state_dict
233
- ), quant_state_dict
234
-
235
- def _is_8bit_weight_name(self, weight_name: str):
236
- quantized_suffix = {".scb", ".weight_format"}
237
- return any(weight_name.lower().endswith(suffix) for suffix in quantized_suffix)
238
-
239
- def _is_4bit_weight_name(self, weight_name: str):
240
- quantized_suffix = {
241
- "absmax",
242
- "quant_map",
243
- "nested_absmax",
244
- "nested_quant_map",
245
- "bitsandbytes",
246
- }
247
- suffix = weight_name.split(".")[-1]
248
- return any(q_suffix in suffix for q_suffix in quantized_suffix)
249
-
250
- def _quantized_8bit_generator(
251
- self, hf_weights_files, use_safetensors, quant_state_dict
252
- ) -> Generator:
253
- for (
254
- org_weight_name,
255
- mapped_weight_name,
256
- weight_tensor,
257
- ) in self._hf_weight_iter(hf_weights_files, use_safetensors):
258
- if not mapped_weight_name.lower().endswith(".scb"):
259
- continue
260
-
261
- weight_key = mapped_weight_name.lower().replace(".scb", ".weight")
262
- quant_state_dict[weight_key] = weight_tensor
263
-
264
- for (
265
- org_weight_name,
266
- mapped_weight_name,
267
- weight_tensor,
268
- ) in self._hf_weight_iter(hf_weights_files, use_safetensors):
269
- if self._is_8bit_weight_name(mapped_weight_name):
270
- continue
271
-
272
- if mapped_weight_name in quant_state_dict:
273
- set_weight_attrs(weight_tensor, {"load_in_8bit": True})
274
- yield org_weight_name, weight_tensor
275
- else:
276
- yield org_weight_name, weight_tensor
277
-
278
- def _quantized_4bit_generator(
279
- self, hf_weights_files, use_safetensors, quant_state_dict
280
- ) -> Generator:
281
- from bitsandbytes.functional import QuantState
282
-
283
- # First iterate over all quant state weights
284
- weight_iterator = self._hf_weight_iter(hf_weights_files, use_safetensors)
285
- temp_state_dict = {}
286
- for (
287
- org_weight_name,
288
- mapped_weight_name,
289
- weight_tensor,
290
- ) in weight_iterator:
291
- if not self._is_4bit_weight_name(mapped_weight_name):
292
- continue
293
- # bitsandbytes library requires
294
- # weight.quant_state.bitsandbytes__* in CPU
295
- if "quant_state.bitsandbytes" in mapped_weight_name:
296
- temp_state_dict[mapped_weight_name] = weight_tensor.cpu().data
297
- else:
298
- temp_state_dict[mapped_weight_name] = weight_tensor
299
-
300
- # Closure to parse quant_state for each prequant weight
301
- def _parse_quant_state(param_name: str, temp_state_dict: dict) -> QuantState:
302
- quant_state = {}
303
- for k in temp_state_dict:
304
- if param_name + "." in k:
305
- quant_state[k] = temp_state_dict[k]
306
-
307
- return QuantState.from_dict(
308
- quant_state, device=current_platform.device_type
309
- )
310
-
311
- # Second iterate over all prequant and normal weights
312
- # pre quantized weights would have a quant_state
313
- for (
314
- org_weight_name,
315
- mapped_weight_name,
316
- weight_tensor,
317
- ) in self._hf_weight_iter(hf_weights_files, use_safetensors):
318
- if self._is_4bit_weight_name(mapped_weight_name):
319
- continue
320
-
321
- if (
322
- f"{mapped_weight_name}.quant_state.bitsandbytes__nf4" in temp_state_dict
323
- ) or (
324
- f"{mapped_weight_name}.quant_state.bitsandbytes__fp4" in temp_state_dict
325
- ):
326
- quant_state = _parse_quant_state(mapped_weight_name, temp_state_dict)
327
- quant_state_dict[mapped_weight_name] = quant_state
328
- yield org_weight_name, weight_tensor
329
- else:
330
- yield org_weight_name, weight_tensor
331
-
332
- def _unquantized_generator(
333
- self, hf_weights_files, use_safetensors, quant_state_dict
334
- ) -> Generator:
335
- from bitsandbytes.functional import quantize_4bit
336
-
337
- global_tp_size = get_tensor_model_parallel_world_size()
338
- global_tp_rank = get_tensor_model_parallel_rank()
339
- check_match = (
340
- lambda weight_name, module_name: weight_name.removesuffix(".weight")
341
- == module_name
342
- )
343
- for (
344
- org_weight_name,
345
- mapped_weight_name,
346
- weight_tensor,
347
- ) in self._hf_weight_iter(hf_weights_files, use_safetensors):
348
- # override tp_size and tp_rank if the module has disabled TP
349
- if any(
350
- tp_disabled_module in mapped_weight_name
351
- for tp_disabled_module in self.tp_disabled_modules
352
- ):
353
- tp_size = 1
354
- tp_rank = 0
355
- else:
356
- tp_size = global_tp_size
357
- tp_rank = global_tp_rank
358
-
359
- if any(
360
- target_module in mapped_weight_name
361
- for target_module in self.target_modules
362
- ) and mapped_weight_name.endswith(".weight"):
363
- # Without sharding
364
- if any(
365
- check_match(mapped_weight_name, module)
366
- for module in self.unsharded_weights_modules
367
- ):
368
- weight_sub_tensor = weight_tensor
369
- # Shard by column
370
- elif any(
371
- check_match(mapped_weight_name, module)
372
- for module in self.column_sharded_weights_modules
373
- ):
374
- total_size = weight_tensor.size(-1)
375
- start_index = total_size // tp_size * tp_rank
376
- end_index = total_size // tp_size * (tp_rank + 1)
377
- weight_sub_tensor = weight_tensor[..., start_index:end_index]
378
- # Weights have fused on disk. In this case, we assume that the
379
- # weight and module use same name.
380
- elif any(
381
- check_match(mapped_weight_name, module)
382
- for module in self.maybe_fused_weights_modules
383
- ):
384
- # special case for fused weights
385
- # get the size of each shard weight tensor
386
- total_shard_sizes = next(
387
- (
388
- sizes
389
- for module, sizes in self.maybe_fused_weights_modules.items() # noqa: E501
390
- if check_match(mapped_weight_name, module)
391
- )
392
- )
393
- total_size = weight_tensor.size(0)
394
- assert total_size == sum(total_shard_sizes)
395
- # get the start/end index of each shard weight tensor
396
- total_start_index = list(
397
- itertools.accumulate([0] + total_shard_sizes)
398
- )[:-1]
399
- shard_weights_index = [
400
- (
401
- idx + size // tp_size * tp_rank,
402
- idx + size // tp_size * (tp_rank + 1),
403
- )
404
- for idx, size in zip(total_start_index, total_shard_sizes)
405
- ]
406
- # slice and reorder the weight tensor
407
- weight_tensor = [
408
- weight_tensor[start_index:end_index, ...]
409
- for start_index, end_index in shard_weights_index
410
- ]
411
- weight_sub_tensor = torch.cat(weight_tensor, dim=0)
412
- # Shard by row
413
- else:
414
- total_size = weight_tensor.size(0)
415
- start_index = total_size // tp_size * tp_rank
416
- end_index = total_size // tp_size * (tp_rank + 1)
417
- weight_sub_tensor = weight_tensor[start_index:end_index, ...]
418
-
419
- # bitsandbytes requires data in GPU
420
- if weight_sub_tensor.is_cuda:
421
- loaded_weight = weight_sub_tensor
422
- else:
423
- loaded_weight = weight_sub_tensor.to(
424
- device=current_platform.device_type
425
- )
426
-
427
- # remove the following after the issue is fixed:
428
- # https://github.com/bitsandbytes-foundation/bitsandbytes/issues/1342
429
- if loaded_weight.is_contiguous() is False:
430
- loaded_weight = loaded_weight.contiguous()
431
-
432
- with set_default_torch_dtype(torch.float32):
433
- processed_weight, quant_state = quantize_4bit(
434
- loaded_weight,
435
- compress_statistics=True,
436
- quant_type="nf4",
437
- )
438
-
439
- quant_state_dict[mapped_weight_name] = quant_state
440
- else:
441
- processed_weight = weight_tensor
442
- yield org_weight_name, processed_weight
443
-
444
- def _get_bnb_target_modules(self, model: nn.Module) -> None:
445
- """
446
- Identify and collect all modules that support BitsAndBytes
447
- quantization.
448
- """
449
- for name, module in model.named_modules():
450
- if isinstance(module, LinearBase) and hasattr(
451
- module.quant_method, "quant_config"
452
- ):
453
- if modules_info := self.modules_mapping.get_sub_modules(name):
454
- # Map vllm's names to transformers's names.
455
- rep_name, sub_modules = modules_info
456
- for sub_name in sub_modules:
457
- new_name = name.replace(rep_name, sub_name)
458
- self.target_modules.append(new_name)
459
- if module.disable_tp:
460
- self.tp_disabled_modules.append(new_name)
461
- # Add original module name even if the module has stacked map,
462
- # in case model has a mixture of disk-merged and disk-split
463
- # weights with same last name.
464
- self.target_modules.append(name)
465
- if module.disable_tp:
466
- self.tp_disabled_modules.append(name)
467
- elif isinstance(module, RoutedExperts) and hasattr(
468
- module.quant_method, "quant_config"
469
- ):
470
- # TODO: support RoutedExperts with prequant and 8bit.
471
- if self.pre_quant and self.load_8bit:
472
- raise ValueError(
473
- "Prequant BitsAndBytes 8bit models with RoutedExperts "
474
- "is not supported yet."
475
- )
476
- # Get the corresponding weight name using module name and
477
- # expert_params_mapping.
478
-
479
- for exp in self.expert_params_mapping:
480
- weight_name = exp[1]
481
- rep_name = name.replace("experts", "") + weight_name.removesuffix(
482
- "."
483
- )
484
- self.target_modules.append(rep_name)
485
-
486
- assert self.target_modules, (
487
- "vLLM currently does not support BNB quantization for"
488
- )
489
- f" {type(model).__name__}"
490
-
491
- def _classify_module_sharding(self, model: nn.Module):
492
- """
493
- Categorize modules based on their weight sharding requirements
494
- for tensor parallelism.
495
- """
496
- for name, module in model.named_modules():
497
- # Some modules like `ReplicatedLinear` should not have their weights
498
- # sharded. The reason for implementing it this way is to avoid new
499
- # static variable in the model implementation.
500
- if isinstance(module, (ReplicatedLinear,)):
501
- self.unsharded_weights_modules.append(name)
502
- # `QKVParallelLinear` and `MergedColumnParallelLinear` might have
503
- # fused weights on disk. We need to use the output sizes of these
504
- # modules to shard the weights correctly.
505
- elif isinstance(module, (QKVParallelLinear, MergedColumnParallelLinear)):
506
- self.maybe_fused_weights_modules[name] = module.output_sizes
507
- # In TP, these weights are partitioned along the column
508
- # dimension (dim=-1)
509
- elif isinstance(module, (RowParallelLinear,)):
510
- self.column_sharded_weights_modules.append(name)
511
- elif isinstance(module, RoutedExperts):
512
- expert_mapping = self.expert_params_mapping
513
- for exp in expert_mapping:
514
- if exp[-1] == "w2":
515
- weight_name = exp[1]
516
- rep_name = name.replace(
517
- "experts", ""
518
- ) + weight_name.removesuffix(".")
519
- self.column_sharded_weights_modules.append(rep_name)
520
-
521
- def _verify_model_compatibility(
522
- self, model: nn.Module, model_config: ModelConfig
523
- ) -> None:
524
- """
525
- Verify that the model is compatible with BitsAndBytes quantization.
526
- """
527
- if not hasattr(model, "load_weights"):
528
- raise AttributeError(
529
- "The required method 'load_weights' is not defined in class"
530
- f" {type(model).__name__}."
531
- )
532
-
533
- if not hasattr(model, "packed_modules_mapping"):
534
- raise AttributeError(
535
- f"Model {type(model).__name__} does not support BitsAndBytes "
536
- "quantization yet. No 'packed_modules_mapping' found."
537
- )
538
-
539
- quant_config = getattr(model_config.hf_config, "quantization_config", None)
540
- if quant_config and (quant_method := quant_config.get("quant_method")):
541
- if quant_method == "bitsandbytes":
542
- self.pre_quant = True
543
- else:
544
- raise ValueError(
545
- f"BitsAndBytes loader does not support {quant_method} quantization"
546
- )
547
-
548
- # The quant_states in pre_quantized models cannot work with a split
549
- # weight tensor. So TP does not work with pre_quantized bnb models.
550
- if self.pre_quant and get_tensor_model_parallel_world_size() > 1:
551
- raise ValueError(
552
- "Prequant BitsAndBytes models with tensor parallelism is not "
553
- "supported. Please try with pipeline parallelism."
554
- )
555
- if quant_config and self.pre_quant:
556
- self.load_8bit = quant_config.get("load_in_8bit", False)
557
-
558
- def _initialize_loader_state(
559
- self, model: nn.Module, model_config: ModelConfig
560
- ) -> None:
561
- """
562
- Initialize the loader's internal state based on the model and
563
- configuration.
564
- """
565
- self.is_pool_model = is_pooling_model(model)
566
- self.modules_mapping = ParamMapping(get_packed_modules_mapping(model))
567
-
568
- if is_moe_model(model):
569
- self.expert_params_mapping = get_moe_expert_mapping(model)
570
- # For some models like Molmo, we need to use hf_to_vllm_mapper
571
- # to ensure correct loading of weights.
572
- if hf_to_vllm_mapper := getattr(model, "hf_to_vllm_mapper", None):
573
- unstacked_mapper = hf_to_vllm_mapper.get_unstacked_mapper()
574
- self.weight_mapper = lambda name, m=unstacked_mapper: m._map_name(name)
575
-
576
- self._get_bnb_target_modules(model)
577
- self._classify_module_sharding(model)
578
-
579
- def _dequantize_dq(self, quant_states: Any):
580
- """
581
- When BNB employs Double Quantization, we perform the dequantization of
582
- these constants during weight loading rather than at inference time,
583
- thereby avoiding this computational overhead during inference. This
584
- comes at the cost of increased memory usage.
585
- """
586
- from bitsandbytes.functional import QuantState, dequantize_blockwise
587
-
588
- def _dequantize_single_state(quant_state):
589
- """Helper function to dequantize a single QuantState object."""
590
- if not (isinstance(quant_state, QuantState) and quant_state.nested):
591
- return
592
-
593
- # Copied from: https://github.com/bitsandbytes-foundation/bitsandbytes/blob/0.45.3/bitsandbytes/functional.py#L1352-#L1356
594
- absmax = dequantize_blockwise(quant_state.absmax, quant_state.state2)
595
- absmax += quant_state.offset
596
-
597
- # Ensure float32 dtype
598
- if absmax.dtype != torch.float32:
599
- absmax = absmax.float()
600
-
601
- quant_state.absmax = absmax
602
- quant_state.nested = False
603
- quant_state.offset = None
604
- quant_state.state2 = None
605
-
606
- if isinstance(quant_states, dict):
607
- for quant_state in quant_states.values():
608
- _dequantize_single_state(quant_state)
609
- else:
610
- _dequantize_single_state(quant_states)
611
- return quant_states
612
-
613
- def _fuse_moe_quant_states(self, model: nn.Module, quant_states_dict: dict) -> dict:
614
- """
615
-
616
- This function consolidates individual expert quantization states into
617
- fused representations for w13 and w2.
618
- """
619
- from bitsandbytes.functional import QuantState
620
-
621
- if not self.expert_params_mapping:
622
- return dict()
623
-
624
- expert_mapping = self.expert_params_mapping
625
- expert_qs_dict = {}
626
- for name, module in model.named_modules():
627
- if not isinstance(module, RoutedExperts):
628
- continue
629
- w1_states_lst = []
630
- w2_states_lst = []
631
- w3_states_lst = []
632
- for exp in expert_mapping:
633
- shard_id = exp[-1]
634
- if shard_id not in ("w1", "w2", "w3"):
635
- raise ValueError(
636
- f"shard_id must be ['w1','w2','w3'] but got {shard_id}."
637
- )
638
- layer_prefix = name.split("experts")[0]
639
- weight_qual_name = layer_prefix + exp[1] + "weight"
640
- quant_state = self._dequantize_dq(quant_states_dict[weight_qual_name])
641
- if shard_id == "w1":
642
- w1_states_lst.append(quant_state)
643
- elif shard_id == "w2":
644
- w2_states_lst.append(quant_state)
645
- else:
646
- w3_states_lst.append(quant_state)
647
- del quant_states_dict[weight_qual_name]
648
- assert len(w1_states_lst) == len(w2_states_lst) == len(w3_states_lst)
649
- w13_absmax_lst = []
650
- w2_absmax_lst = []
651
- w13_total_dim0 = 0
652
- w2_total_dim0 = 0
653
- for w1_qs, w2_qs, w3_qs in zip(w1_states_lst, w2_states_lst, w3_states_lst):
654
- assert w1_qs.shape == w3_qs.shape
655
- assert w1_qs.blocksize == w2_qs.blocksize == w3_qs.blocksize
656
- assert w1_qs.dtype == w2_qs.dtype == w3_qs.dtype
657
- # w1 and w3 are interleaved in storage
658
- w13_absmax_lst.append(w1_qs.absmax)
659
- w13_absmax_lst.append(w3_qs.absmax)
660
- w2_absmax_lst.append(w2_qs.absmax)
661
- w13_total_dim0 += w1_qs.shape[0] + w3_qs.shape[0]
662
- w2_total_dim0 += w2_qs.shape[0]
663
-
664
- w13_absmax = torch.cat(w13_absmax_lst)
665
- w2_absmax = torch.cat(w2_absmax_lst)
666
- # Create fused quantization state for w13.
667
- w13_qs = QuantState(
668
- absmax=w13_absmax,
669
- shape=(w13_total_dim0, w1_states_lst[0].shape[1]),
670
- code=w1_states_lst[0].code,
671
- blocksize=w1_states_lst[0].blocksize,
672
- quant_type="nf4",
673
- dtype=w1_states_lst[0].dtype,
674
- )
675
- # Create fused quantization state for w2.
676
- w2_qs = QuantState(
677
- absmax=w2_absmax,
678
- shape=(w2_total_dim0, w2_states_lst[0].shape[1]),
679
- code=w2_states_lst[0].code,
680
- blocksize=w2_states_lst[0].blocksize,
681
- quant_type="nf4",
682
- dtype=w2_states_lst[0].dtype,
683
- )
684
- # The weight suffixes .w13_weight and .w2_weight are consistent
685
- # with the param in BitsAndBytesMoEMethod.
686
- w13_weight_name = name + ".w13_weight"
687
- w2_weight_name = name + ".w2_weight"
688
- expert_qs_dict[w13_weight_name] = w13_qs
689
- expert_qs_dict[w2_weight_name] = w2_qs
690
- return expert_qs_dict
691
-
692
- def _stack_quantization_states(
693
- self, model: nn.Module, quant_state_dict: dict
694
- ) -> dict[str, dict[int, Any]]:
695
- stacked_quant_state_dict: dict[str, dict[int, Any]] = {}
696
- # TODO: Change this lazy import to normal import
697
- # after the checks are updated to run on a new version
698
- from vllm.model_executor.models.utils import is_pp_missing_parameter
699
-
700
- param_dict = dict(model.named_parameters())
701
- for quant_param_name in quant_state_dict:
702
- if is_pp_missing_parameter(quant_param_name, model):
703
- continue
704
-
705
- non_stacked_param_name = quant_param_name
706
-
707
- shard_index = 0
708
- for shard_name, (
709
- weight_name,
710
- index,
711
- ) in self.modules_mapping.inverse_packed_mapping.items():
712
- # Some models, such as MiniCPM V2.5/2.6, contain both
713
- # module names 'kv_proj' and 'qkv_proj'. To prevent 'kv_proj'
714
- # from being incorrectly identified as being present in
715
- # 'vpm.encoder.layers.0.self_attn.qkv_proj.weight
716
- shard_pos = quant_param_name.find(shard_name)
717
- can_correct_rename = (shard_pos > 0) and (
718
- quant_param_name[shard_pos - 1] == "."
719
- )
720
- # If the quant_param_name is packed, it won't occur in the
721
- # param_dict before renaming.
722
- new_quant_param_name = quant_param_name.replace(shard_name, weight_name)
723
- need_rename = (quant_param_name not in param_dict) and (
724
- new_quant_param_name in param_dict
725
- )
726
- if can_correct_rename and need_rename:
727
- shard_index = index
728
- quant_param_name = new_quant_param_name
729
- break
730
-
731
- # Models like Clip/Siglip may skip some layers in initialization,
732
- # causing unused quant_param_name in state_dict.
733
- if quant_param_name not in param_dict:
734
- continue
735
-
736
- if quant_param_name not in stacked_quant_state_dict:
737
- stacked_quant_state_dict[quant_param_name] = {}
738
-
739
- stacked_quant_state_dict[quant_param_name][shard_index] = quant_state_dict[
740
- non_stacked_param_name
741
- ]
742
-
743
- # repeat k_proj for v_proj for k_eq_v models (e.g. Gemma4)
744
- config = getattr(model, "config", None)
745
- if config is not None:
746
- text_config = config.get_text_config()
747
- if getattr(text_config, "attention_k_eq_v", False):
748
- shard_packed = {
749
- name
750
- for name, subs in self.modules_mapping.packed_mapping.items()
751
- if len(subs) == 3
752
- }
753
- for param_name, shards in stacked_quant_state_dict.items():
754
- is_target = (
755
- isinstance(shards, dict)
756
- and len(shards) == 2
757
- and any(
758
- param_name.endswith(f"{p}.weight") for p in shard_packed
759
- )
760
- )
761
- if is_target:
762
- assert 1 in shards and 2 not in shards
763
- shards[2] = shards[1]
764
-
765
- return stacked_quant_state_dict
766
-
767
- def _bind_quant_states_to_params(
768
- self, model: nn.Module, stacked_quant_state_dict: dict
769
- ) -> None:
770
- # save quant_states and offsets as the attributes of the parameters
771
- param_dict = dict(model.named_parameters())
772
- for param_name, param in param_dict.items():
773
- if param_name in stacked_quant_state_dict:
774
- quant_states = stacked_quant_state_dict[param_name]
775
- # Dequantize double quantized values during weight loading.
776
- self._dequantize_dq(quant_states)
777
- set_weight_attrs(param, {"bnb_quant_state": quant_states})
778
- if not isinstance(quant_states, dict):
779
- continue
780
-
781
- pack_ratio = getattr(param, "pack_factor", -1)
782
- if pack_ratio == -1:
783
- raise ValueError(f"pack_factor not set for parameter {param_name}.")
784
-
785
- num_elements = [0] * len(quant_states)
786
- for seq, quant_state in quant_states.items():
787
- num_elements[seq] = math.prod(quant_state.shape) // pack_ratio
788
-
789
- offsets = np.concatenate(([0], np.cumsum(num_elements)))
790
- # Make torch infer_schema happy
791
- offsets = torch.tensor(offsets).cpu()
792
- set_weight_attrs(param, {"bnb_shard_offsets": offsets})
793
-
794
- if self.load_8bit:
795
- set_weight_attrs(
796
- param, {"matmul_state": [None] * len(quant_states)}
797
- )
798
-
799
- def load_weights(self, model: nn.Module, model_config: ModelConfig) -> None:
800
- self._verify_model_compatibility(model, model_config)
801
- self._initialize_loader_state(model, model_config)
802
-
803
- logger.info(
804
- "Loading weights with BitsAndBytes quantization. May take a while ..."
805
- )
806
- qweight_iterator, quant_state_dict = self._get_quantized_weights_iterator(
807
- model_config.model,
808
- model_config.revision,
809
- )
810
- weights_to_load = {name for name, _ in model.named_parameters()}
811
- loaded_weights = model.load_weights(qweight_iterator)
812
- # Some models may have weights loading tracker unimplemented.
813
- if loaded_weights is not None:
814
- weights_not_loaded = weights_to_load - loaded_weights
815
- if weights_not_loaded:
816
- raise ValueError(
817
- "Following weights were not initialized from "
818
- f"checkpoint: {weights_not_loaded}"
819
- )
820
- expert_quant_state_dict = self._fuse_moe_quant_states(model, quant_state_dict)
821
-
822
- stacked_quant_state_dict = self._stack_quantization_states(
823
- model, quant_state_dict
824
- )
825
-
826
- stacked_quant_state_dict = {
827
- **expert_quant_state_dict,
828
- **stacked_quant_state_dict,
829
- }
830
- self._bind_quant_states_to_params(model, stacked_quant_state_dict)
831
- torch.accelerator.empty_cache()
832
-
833
- def download_model(self, model_config: ModelConfig) -> None:
834
- self._prepare_weights(model_config.model, model_config.revision)
vllm/model_executor/model_loader/utils.py
-30
@@ -5,7 +5,6 @@
5
import inspect
6
import warnings
7
from contextlib import contextmanager
8
-from dataclasses import dataclass, field
8
from typing import Any
9
10
import torch
@@ -263,35 +262,6 @@ def get_architecture_class_name(model_config: ModelConfig) -> str:
262
return get_model_architecture(model_config)[1]
263
264
266
-@dataclass
267
-class ParamMapping:
268
- """
269
- A class to handle parameter mapping for model weight loading.
270
- It creates a bidirectional mapping between packed parameters and their
271
- constituent parts.
272
- """
273
-
274
- packed_mapping: dict[str, list[str]]
275
- inverse_packed_mapping: dict[str, tuple[str, int]] = field(default_factory=dict)
276
-
277
- def __post_init__(self):
278
- for packed_name, sub_params in self.packed_mapping.items():
279
- # Skip self-contained cases (e.g., {"W_pack": ["W_pack"]})
280
- if len(sub_params) == 1 and sub_params[0] == packed_name:
281
- continue
282
- for index, param_name in enumerate(sub_params):
283
- self.inverse_packed_mapping[param_name] = (
284
- packed_name,
285
- index,
286
- )
287
-
288
- def get_sub_modules(self, module_name: str) -> tuple[str, list[str]] | None:
289
- for key, value in self.packed_mapping.items():
290
- if module_name.endswith(key):
291
- return key, value
292
- return None
293
-
294
-
265
def configure_quant_config(
266
quant_config: QuantizationConfig, model_class: type[nn.Module]
267
):
vllm/model_executor/model_loader/weight_utils.py
+1
-6
@@ -330,9 +330,6 @@ def get_quant_config(
330
assert isinstance(model_config.quantization_config, QuantizationConfigArgs)
331
return OnlineQuantizationConfig(args=model_config.quantization_config)
332
333
- # Inflight BNB quantization
334
- if model_config.quantization == "bitsandbytes":
335
- return quant_cls.from_config({})
333
model_name_or_path = (
334
maybe_download_from_modelscope(
335
model_config.model,
@@ -380,9 +377,7 @@ def get_quant_config(
377
with open(quant_config_file) as f:
378
config = json.load(f)
379
383
- if model_config.quantization == "bitsandbytes":
384
- config["adapter_name_or_path"] = model_config.model
385
- elif model_config.quantization in ("modelopt", "modelopt_mixed"):
380
+ if model_config.quantization in ("modelopt", "modelopt_mixed"):
381
if config.get("producer", {}).get("name") == "modelopt":
382
return quant_cls.from_config(config)
383
else:
vllm/model_executor/models/openpangu.py
-4
@@ -699,10 +699,6 @@ class OpenPanguSinkAttention(nn.Module):
699
output_dim = getattr(param, "output_dim", None)
700
701
is_sharded_weight = getattr(param, "is_sharded_weight", False)
702
- use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
703
- # bitsandbytes loads the weights of the specific portion
704
- # no need to narrow
705
- is_sharded_weight = is_sharded_weight or use_bitsandbytes_4bit
702
703
param_data = param.data
704
if output_dim is not None and not is_sharded_weight:
vllm/platforms/rocm.py
-1
@@ -524,7 +524,6 @@ class RocmPlatform(Platform):
524
"mxfp4",
525
"mxfp8",
526
"torchao",
527
- "bitsandbytes",
527
"modelopt",
528
"modelopt_fp4",
529
"modelopt_mxfp8",
vllm/v1/metrics/perf.py
+1
-2
@@ -46,7 +46,7 @@ class InvalidComponent(Exception):
46
# FfnQuantizationConfigParser to determine the weight_byte_size for
47
# flops/memory estimation.
48
#
49
-# NOTE: Methods like GPTQ and BitsAndBytes support variable bit-widths
49
+# NOTE: Methods like GPTQ support variable bit-widths
50
# (e.g., 4-bit and 8-bit). We default to 4-bit (0.5 bytes) since this
51
# is by far the most common configuration.
52
_QUANT_WEIGHT_BYTE_SIZE: dict[str, float] = {
@@ -63,7 +63,6 @@ _QUANT_WEIGHT_BYTE_SIZE: dict[str, float] = {
63
"awq_marlin": 0.5,
64
"gptq": 0.5,
65
"gptq_marlin": 0.5,
66
- "bitsandbytes": 0.5,
66
"modelopt_fp4": 0.5,
67
"petit_nvfp4": 0.5,
68
"compressed-tensors": 0.5,