main
py 413 lines 14.7 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import importlib.metadata
4 import importlib.util
5
6 import pytest
7 import torch
8 from packaging import version
9
10 from tests.quantization.utils import load_model_without_vllm_runner
11 from vllm.config import set_current_vllm_config
12 from vllm.forward_context import set_forward_context
13 from vllm.model_executor.layers.attention import Attention
14 from vllm.model_executor.model_loader import get_model_loader
15 from vllm.model_executor.model_loader.default_loader import DefaultModelLoader
16 from vllm.platforms import current_platform
17
18 if current_platform.is_rocm():
19 from vllm.platforms.rocm import on_gfx950
20 else:
21
22 def on_gfx950() -> bool:
23 return False
24
25
26 DEVICE_TYPE = current_platform.device_type
27 DTYPE = ["bfloat16"]
28
29 TORCHAO_AVAILABLE = importlib.util.find_spec("torchao") is not None
30 TORCHAO_VERSION_0_18_AVAILABLE = TORCHAO_AVAILABLE and version.parse(
31 importlib.metadata.version("torchao")
32 ) >= version.parse("0.18.0")
33
34
35 # Fix for leaking `torch.set_float32_matmul_precision("high")`
36 # https://github.com/pytorch/ao/blob/v0.17.0/torchao/quantization/utils.py#L696
37 @pytest.fixture(autouse=True)
38 def restore_float32_matmul_precision():
39 original_precision = torch.get_float32_matmul_precision()
40 try:
41 yield
42 finally:
43 torch.set_float32_matmul_precision(original_precision)
44
45
46 @pytest.mark.skipif(
47 current_platform.is_rocm() and current_platform.is_fp8_fnuz(),
48 reason="Only fp8_fnuz supported on CDNA3 architecture",
49 )
50 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
51 def test_pre_quantized_model(monkeypatch, dist_init, workspace_init):
52 model, vllm_config = load_model_without_vllm_runner(
53 "torchao-testing/opt-125m-Float8WeightOnlyConfig-v2-0.15.0",
54 quantization="torchao",
55 dtype="bfloat16",
56 )
57
58 monkeypatch.setattr(Attention, "forward", lambda _, q, k, v: q.contiguous())
59 input_ids = torch.tensor([1, 2, 3, 4], device=DEVICE_TYPE)
60 positions = torch.arange(input_ids.numel(), device=DEVICE_TYPE)
61 with (
62 set_current_vllm_config(vllm_config),
63 set_forward_context(None, vllm_config, num_tokens=input_ids.numel()),
64 ):
65 hidden_states = model(input_ids, positions, None)
66 logits = model.compute_logits(hidden_states)
67 assert torch.isfinite(logits).all()
68
69
70 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
71 @pytest.mark.skip(
72 reason="since torchao nightly is only compatible with torch nightly"
73 "currently https://github.com/pytorch/ao/issues/2919, we'll have to skip "
74 "torchao tests that requires newer versions (0.14.0.dev+) for now"
75 )
76 def test_opt_125m_awq_int4wo_model_loading_with_params(vllm_runner):
77 torch._dynamo.reset()
78 model_name = "torchao-testing/opt-125m-AWQConfig-Int4WeightOnlyConfig-v2-0.14.0.dev"
79 with vllm_runner(
80 model_name=model_name,
81 quantization="torchao",
82 dtype="bfloat16",
83 pt_load_map_location=f"{DEVICE_TYPE}:0",
84 ) as llm:
85 output = llm.generate_greedy(["The capital of France is"], max_tokens=4)
86
87 assert output
88
89
90 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
91 @pytest.mark.skipif(
92 on_gfx950() and not TORCHAO_VERSION_0_18_AVAILABLE,
93 reason="requires torchao>=0.18.0 on gfx950",
94 )
95 def test_online_quant_config_dict_json(vllm_runner, enable_pickle):
96 """Testing online quantization, load_weights integration point,
97 with config dict serialized to json string
98 """
99 torch._dynamo.reset()
100 model_name = "facebook/opt-125m"
101
102 import json
103
104 from torchao.core.config import config_to_dict
105 from torchao.quantization import Float8DynamicActivationFloat8WeightConfig, PerRow
106
107 torchao_quant_config = Float8DynamicActivationFloat8WeightConfig(
108 granularity=PerRow()
109 )
110 hf_overrides = {
111 "quantization_config_dict_json": json.dumps(
112 config_to_dict(torchao_quant_config)
113 )
114 }
115 with vllm_runner(
116 model_name=model_name,
117 dtype="bfloat16",
118 pt_load_map_location=f"{DEVICE_TYPE}:0",
119 quantization="torchao",
120 hf_overrides=hf_overrides,
121 enforce_eager=True,
122 ) as llm:
123 output = llm.generate_greedy(["The capital of France is"], max_tokens=4)
124
125 load_config = llm.llm.llm_engine.vllm_config.load_config
126 model_config = llm.llm.llm_engine.vllm_config.model_config
127
128 def load_weights(model):
129 model_loader = get_model_loader(load_config)
130 assert isinstance(model_loader, DefaultModelLoader)
131 weights_iterator = model_loader.get_all_weights(model_config, model)
132 model.load_weights(weights_iterator)
133
134 llm.apply_model(load_weights)
135
136 reload_output = llm.generate_greedy(["The capital of France is"], max_tokens=4)
137 assert output[0][0] == reload_output[0][0]
138
139
140 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
141 @pytest.mark.skipif(
142 on_gfx950() and not TORCHAO_VERSION_0_18_AVAILABLE,
143 reason="requires torchao>=0.18.0 on gfx950",
144 )
145 def test_online_quant_config_file(vllm_runner):
146 """Testing on the fly quantization, load_weights integration point,
147 with config file
148 """
149 torch._dynamo.reset()
150 model_name = "facebook/opt-125m"
151 import json
152 from tempfile import NamedTemporaryFile
153
154 from torchao.core.config import config_to_dict
155 from torchao.quantization import Float8DynamicActivationFloat8WeightConfig, PerRow
156
157 config = Float8DynamicActivationFloat8WeightConfig(granularity=PerRow())
158
159 with NamedTemporaryFile(mode="w", delete=False) as f:
160 f.write(json.dumps(config_to_dict(config)))
161 # close the file to save it
162 f.close()
163 config_file_name = str(f.name)
164
165 hf_overrides = {"quantization_config_file": config_file_name}
166 with vllm_runner(
167 model_name=model_name,
168 dtype="bfloat16",
169 pt_load_map_location=f"{DEVICE_TYPE}:0",
170 quantization="torchao",
171 hf_overrides=hf_overrides,
172 enforce_eager=True,
173 ) as llm:
174 output = llm.generate_greedy(["The capital of France is"], max_tokens=4)
175
176 assert output
177
178
179 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
180 @pytest.mark.skipif(
181 on_gfx950() and not TORCHAO_VERSION_0_18_AVAILABLE,
182 reason="requires torchao>=0.18.0 on gfx950",
183 )
184 def test_reload_weights():
185 import json
186
187 from torchao.core.config import config_to_dict
188 from torchao.quantization import Float8DynamicActivationFloat8WeightConfig, PerRow
189
190 from vllm import LLM, SamplingParams
191
192 torchao_quant_config = Float8DynamicActivationFloat8WeightConfig(
193 granularity=PerRow()
194 )
195
196 hf_overrides = {
197 "quantization_config_dict_json": json.dumps(
198 config_to_dict(torchao_quant_config)
199 )
200 }
201
202 llm = LLM(
203 model="Qwen/Qwen3-0.6B",
204 dtype="bfloat16",
205 load_format="dummy",
206 enforce_eager=True,
207 quantization="torchao",
208 hf_overrides=hf_overrides,
209 )
210 # Update load format from `dummy` to `auto`
211 llm.collective_rpc(
212 "update_config", args=({"load_config": {"load_format": "auto"}},)
213 )
214 # Now reload real weights inplace
215 llm.collective_rpc("reload_weights")
216 prompts = [
217 "Hello, my name is",
218 "The president of the United States is",
219 "The capital of France is",
220 "The future of AI is",
221 ]
222 # Create a sampling params object.
223 sampling_params = SamplingParams(temperature=0, top_p=0.95)
224 outputs = llm.generate(prompts, sampling_params)
225 # make sure it runs
226 for output in outputs:
227 generated_text = output.outputs[0].text
228 assert generated_text
229 # can also uncomment locally to make sure the generated
230 # output makes sense
231 # prompt = output.prompt
232 # print(f"Prompt: {prompt!r}")
233 # print(f"Output: {generated_text!r}")
234 # print("-" * 60)
235
236
237 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
238 @pytest.mark.skip(
239 reason="since torchao nightly is only compatible with torch nightly"
240 "currently https://github.com/pytorch/ao/issues/2919, we'll have to skip "
241 "torchao tests that requires newer versions (0.15.0.dev+) for now"
242 )
243 def test_safetensors_model_loading_with_params(vllm_runner):
244 torch._dynamo.reset()
245 # using this model to test safetensors loading with file sharding
246 model_name = "torchao-testing/Qwen3-8B-INT4-0.15.0dev-safetensors"
247 with vllm_runner(model_name=model_name, dtype="bfloat16") as llm:
248 output = llm.generate_greedy(["The capital of France is"], max_tokens=4)
249
250 assert output
251
252
253 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
254 @pytest.mark.skip(
255 reason="since torchao nightly is only compatible with torch nightly"
256 "currently https://github.com/pytorch/ao/issues/2919, we'll have to skip "
257 "torchao tests that requires newer versions (0.14.0.dev+) for now"
258 )
259 def test_opt_125m_module_fqn_to_config_regex_model(vllm_runner):
260 torch._dynamo.reset()
261 model_name = "torchao-testing/opt-125m-ModuleFqnToConfig-v1-regex-0.14.0.dev"
262 with vllm_runner(
263 model_name=model_name, dtype="bfloat16", pt_load_map_location=f"{DEVICE_TYPE}:0"
264 ) as llm:
265 output = llm.generate_greedy(["The capital of France is"], max_tokens=4)
266
267 assert output
268
269
270 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
271 @pytest.mark.skip(
272 reason="since torchao nightly is only compatible with torch nightly"
273 "currently https://github.com/pytorch/ao/issues/2919, we'll have to skip "
274 "torchao tests that requires newer versions (0.14.0.dev+) for now"
275 )
276 def test_opt_125m_int4wo_model_running_preshuffled_kernel(vllm_runner, monkeypatch):
277 """We load a model with Int4Tensor (plain format) linear weights
278 and verify that the weight is updated to Int4PreshuffledTensor
279 after loading in vllm
280 """
281 from torchao.quantization import Int4PreshuffledTensor
282 from torchao.utils import _is_fbgemm_gpu_genai_available, is_sm_at_least_90
283
284 torch._dynamo.reset()
285 monkeypatch.setenv("VLLM_ALLOW_INSECURE_SERIALIZATION", "1")
286 model_name = "torchao-testing/opt-125m-Int4WeightOnlyConfig-v2-0.14.0.dev"
287 # Note: using enforce_eager=True because the `bf16i4bf16_shuffled` doesn't
288 # have meta kernel implemented yet, can remove this flag after that is implemented
289 with vllm_runner(
290 model_name=model_name,
291 quantization="torchao",
292 dtype="bfloat16",
293 pt_load_map_location=f"{DEVICE_TYPE}:0",
294 enforce_eager=True,
295 ) as llm:
296
297 def has_int4_preshuffled_tensor_weight(model):
298 return isinstance(
299 model.model.decoder.layers[0].self_attn.qkv_proj.weight,
300 Int4PreshuffledTensor,
301 )
302
303 def get_weight_attrs(model):
304 weight = model.model.decoder.layers[0].self_attn.qkv_proj.weight
305 return [
306 weight.requires_grad,
307 weight.input_dim,
308 weight.output_dim,
309 hasattr(weight, "weight_loader"),
310 ]
311
312 llm_engine = llm.get_llm().llm_engine
313 has_int4_preshuffled_tensor = any(
314 llm_engine.apply_model(has_int4_preshuffled_tensor_weight)
315 )
316 weight_attrs = llm_engine.apply_model(get_weight_attrs)[0]
317
318 # making sure we are using Int4PreshuffledTensor on H100 GPU, when
319 # fbgemm_gpu_genai
320 # library is installed, otherwise it should be using Int4Tensor
321 if _is_fbgemm_gpu_genai_available() and is_sm_at_least_90():
322 assert has_int4_preshuffled_tensor
323 else:
324 assert not has_int4_preshuffled_tensor
325
326 assert weight_attrs == [False, 1, 0, True]
327 output = llm.generate_greedy(["The capital of France is"], max_tokens=32)
328
329 assert output
330
331
332 @pytest.mark.skipif(not TORCHAO_AVAILABLE, reason="torchao is not available")
333 @pytest.mark.skip(
334 reason="since torchao nightly is only compatible with torch nightly"
335 "currently https://github.com/pytorch/ao/issues/2919, we'll have to skip "
336 "torchao tests that requires newer versions (0.14.0.dev+) for now"
337 )
338 def test_opt_125m_int4wo_model_running_preshuffled_kernel_online_quant(
339 vllm_runner, monkeypatch
340 ):
341 """We load a bf16 model and online quantize the model to int4, then verify that
342 the weights are updated to Int4PreshuffledTensor after online quantization
343 """
344 from torchao.quantization import Int4PreshuffledTensor
345 from torchao.utils import _is_fbgemm_gpu_genai_available, is_sm_at_least_90
346
347 torch._dynamo.reset()
348 model_name = "facebook/opt-125m"
349
350 monkeypatch.setenv("VLLM_ALLOW_INSECURE_SERIALIZATION", "1")
351
352 import json
353
354 from torchao.core.config import config_to_dict
355 from torchao.quantization import Int4WeightOnlyConfig
356
357 torchao_quant_config = Int4WeightOnlyConfig(
358 group_size=128, int4_packing_format="plain"
359 )
360 hf_overrides = {
361 "quantization_config_dict_json": json.dumps(
362 config_to_dict(torchao_quant_config)
363 )
364 }
365
366 # Note: using enforce_eager=True because the `bf16i4bf16_shuffled` doesn't
367 # have meta kernel implemented yet, can remove this flag after that is implemented
368 with vllm_runner(
369 model_name=model_name,
370 quantization="torchao",
371 dtype="bfloat16",
372 pt_load_map_location=f"{DEVICE_TYPE}:0",
373 hf_overrides=hf_overrides,
374 enforce_eager=True,
375 ) as llm:
376
377 def has_int4_preshuffled_tensor_weight(model):
378 return isinstance(
379 model.model.decoder.layers[0].self_attn.qkv_proj.weight,
380 Int4PreshuffledTensor,
381 )
382
383 def get_weight_attrs(model):
384 weight = model.model.decoder.layers[0].self_attn.qkv_proj.weight
385 return [
386 weight.requires_grad,
387 weight.input_dim,
388 weight.output_dim,
389 hasattr(weight, "weight_loader"),
390 ]
391
392 llm_engine = llm.get_llm().llm_engine
393 has_int4_preshuffled_tensor = any(
394 llm_engine.apply_model(has_int4_preshuffled_tensor_weight)
395 )
396 weight_attrs = llm_engine.apply_model(get_weight_attrs)[0]
397
398 # making sure we are using Int4PreshuffledTensor on H100 GPU, when
399 # fbgemm_gpu_genai
400 # library is installed, otherwise it should be using Int4Tensor
401 if _is_fbgemm_gpu_genai_available() and is_sm_at_least_90():
402 assert has_int4_preshuffled_tensor
403 else:
404 assert not has_int4_preshuffled_tensor
405
406 assert weight_attrs == [False, 1, 0, True]
407 output = llm.generate_greedy(["The capital of France is"], max_tokens=4)
408
409 assert output
410
411
412 if __name__ == "__main__":
413 pytest.main([__file__])