* fix: add XPU device support and update hardcoded CUDA selections * fix: add XPU device support for pytest CUDA skipped tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix device handling for PR #7401 - perplexity_eval.py: use DEVICE_TYPE_TORCH, not DEVICE_TYPE. The latter can be "hip" or "mlx", which .to() rejects, so this regressed ROCm. - test_batched_leftpad_generation_gpu.py: XPU diverges here today, so mark it non-strict xfail on XPU instead of reverting to a CUDA-only guard. Keeps the real XPU gap visible and turns green once it is fixed. - Guard torch.xpu.is_available() with hasattr, matching device_type.py. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Re-enable the flash varlen attention test in CI for PR #7401 attention_dispatch.py now predefines flash_attn_func / flash_attn_varlen_func as None, so test_run_attention_flash_varlen_receives_window_and_softcap no longer needs flash_attn importable to be monkeypatched. Verified on a runner shaped like the CPU-only one: the test fails against main's attention_dispatch and passes at this head, so the deselect is now dead weight. * Tighten comments for PR #7401 Drop the hasattr rationale: torch.xpu has existed since torch 2.3 and the dependency floor is 2.4, so no supported build predates the namespace. The guard stays as cheap defence, but the comment claimed something untrue. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: danielhanchen <danielhanchen@gmail.com>
173 lines
6.9 KiB
Python
173 lines
6.9 KiB
Python
from unsloth import FastLanguageModel
|
|
|
|
from typing import Dict
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
try:
|
|
from torchao.quantization.qat import FakeQuantizedLinear
|
|
from torchao.quantization.qat.fake_quantizer import (
|
|
FakeQuantizerBase,
|
|
Float8FakeQuantizer,
|
|
Int4WeightFakeQuantizer,
|
|
IntxFakeQuantizer,
|
|
)
|
|
except ImportError:
|
|
print(
|
|
"Missing torchao import, please install or upgrade torchao with: pip install 'torchao>=0.15.0'"
|
|
)
|
|
|
|
|
|
class _CountingFakeQuantizer(torch.nn.Module):
|
|
"""Fake quantizer that counts how many times it was called."""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.count = 0
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
self.count += 1
|
|
return x
|
|
|
|
|
|
def _get_model(qat_scheme: str, full_finetuning: bool):
|
|
"""Return (model, tokenizer) configured for QAT; LoRA model when full_finetuning is False."""
|
|
model, tokenizer = FastLanguageModel.from_pretrained(
|
|
model_name = "unsloth/Qwen3-1.7B",
|
|
load_in_4bit = False,
|
|
full_finetuning = full_finetuning,
|
|
qat_scheme = qat_scheme if full_finetuning else None,
|
|
)
|
|
if not full_finetuning:
|
|
model = FastLanguageModel.get_peft_model(
|
|
model,
|
|
qat_scheme = qat_scheme,
|
|
)
|
|
return model, tokenizer
|
|
|
|
|
|
def _test_linear_is_fake_quantized(linear: torch.nn.Linear, qat_scheme: str):
|
|
"""Verify the linear contains fake quantizers matching `qat_scheme`."""
|
|
weight_only = False
|
|
if qat_scheme == "fp8-int4":
|
|
act_fq_class = Float8FakeQuantizer
|
|
weight_fq_class = Int4WeightFakeQuantizer
|
|
min_in_features = 128
|
|
elif qat_scheme == "fp8-fp8":
|
|
act_fq_class = Float8FakeQuantizer
|
|
weight_fq_class = Float8FakeQuantizer
|
|
min_in_features = -1
|
|
elif qat_scheme == "int8":
|
|
act_fq_class = None
|
|
weight_fq_class = IntxFakeQuantizer
|
|
min_in_features = 128
|
|
weight_only = True
|
|
elif qat_scheme == "cactus":
|
|
act_fq_class = None
|
|
weight_fq_class = IntxFakeQuantizer
|
|
min_in_features = 32
|
|
weight_only = True
|
|
else:
|
|
raise ValueError(f"Unknown qat_scheme: {qat_scheme}")
|
|
|
|
# Check base layer activations and weights.
|
|
base_layer = getattr(linear, "base_layer", linear)
|
|
if base_layer.in_features >= min_in_features:
|
|
assert isinstance(base_layer, FakeQuantizedLinear)
|
|
if not weight_only:
|
|
assert isinstance(base_layer.activation_fake_quantizer, act_fq_class)
|
|
assert isinstance(base_layer.weight_fake_quantizer, weight_fq_class)
|
|
|
|
# Check lora A and B (full_finetuning=False only).
|
|
if hasattr(linear, "lora_A") and hasattr(linear, "lora_B"):
|
|
lora_A = linear.lora_A.default
|
|
lora_B = linear.lora_B.default
|
|
if lora_A.in_features >= min_in_features:
|
|
assert isinstance(lora_A, FakeQuantizedLinear)
|
|
if not weight_only:
|
|
assert isinstance(lora_A.activation_fake_quantizer, act_fq_class)
|
|
assert isinstance(lora_A.weight_fake_quantizer, weight_fq_class)
|
|
if lora_B.in_features >= min_in_features:
|
|
assert isinstance(lora_B, FakeQuantizedLinear)
|
|
if not weight_only:
|
|
assert isinstance(lora_B.activation_fake_quantizer, act_fq_class)
|
|
assert isinstance(lora_B.weight_fake_quantizer, weight_fq_class)
|
|
|
|
|
|
def _test_fake_quantizers_are_called(
|
|
model: torch.nn.Module, example_inputs: Dict, full_finetuning: bool, qat_scheme: str
|
|
):
|
|
"""Verify the fake quantizers are actually called during a forward pass."""
|
|
weight_only = qat_scheme in ["int8", "cactus"]
|
|
|
|
def _swap_fake_quantizers(model: torch.nn.Module):
|
|
for name, child in model.named_children():
|
|
if isinstance(child, FakeQuantizerBase):
|
|
setattr(model, name, _CountingFakeQuantizer())
|
|
|
|
def _assert_fake_quantizers_are_called(model: torch.nn.Module):
|
|
for name, child in model.named_children():
|
|
if full_finetuning:
|
|
if isinstance(child, FakeQuantizedLinear):
|
|
if not weight_only:
|
|
assert child.activation_fake_quantizer.count == 1
|
|
assert child.weight_fake_quantizer.count == 1
|
|
else:
|
|
# LoRA fake-quantizes input activations once per block:
|
|
# self_attn via q_proj, mlp via gate_proj.
|
|
if name == "self_attn":
|
|
base_layer = child.q_proj.base_layer
|
|
if not weight_only:
|
|
assert hasattr(base_layer, "activation_fake_quantizer")
|
|
assert base_layer.activation_fake_quantizer.count == 1
|
|
elif name == "mlp":
|
|
base_layer = child.gate_proj.base_layer
|
|
if not weight_only:
|
|
assert hasattr(base_layer, "activation_fake_quantizer")
|
|
assert base_layer.activation_fake_quantizer.count == 1
|
|
elif isinstance(child, FakeQuantizedLinear):
|
|
# Weight fake quantizers must always be called.
|
|
assert child.weight_fake_quantizer.count == 1
|
|
|
|
if torch.cuda.is_available():
|
|
device = torch.device("cuda")
|
|
elif torch.xpu.is_available():
|
|
device = torch.device("xpu")
|
|
else:
|
|
pytest.skip("No GPU available")
|
|
for k, v in example_inputs.items():
|
|
example_inputs[k] = v.to(device)
|
|
model.apply(_swap_fake_quantizers)
|
|
model(**example_inputs)
|
|
model.apply(_assert_fake_quantizers_are_called)
|
|
|
|
|
|
def _test_model_fake_quantize(qat_scheme: str, full_finetuning: bool):
|
|
"""All linear layers in the model are fake quantized per `qat_scheme`."""
|
|
model, tokenizer = _get_model(qat_scheme, full_finetuning)
|
|
if full_finetuning:
|
|
model = model.model
|
|
else:
|
|
model = model.base_model.model.model
|
|
for layer in model.layers:
|
|
_test_linear_is_fake_quantized(layer.self_attn.q_proj, qat_scheme)
|
|
_test_linear_is_fake_quantized(layer.self_attn.k_proj, qat_scheme)
|
|
_test_linear_is_fake_quantized(layer.self_attn.v_proj, qat_scheme)
|
|
_test_linear_is_fake_quantized(layer.mlp.gate_proj, qat_scheme)
|
|
_test_linear_is_fake_quantized(layer.mlp.up_proj, qat_scheme)
|
|
_test_linear_is_fake_quantized(layer.mlp.down_proj, qat_scheme)
|
|
inputs = tokenizer("How are you?", return_tensors = "pt")
|
|
_test_fake_quantizers_are_called(model, inputs, full_finetuning, qat_scheme)
|
|
|
|
|
|
# TODO: there are bad interactions across tests right now, need to figure out
|
|
# how to disable model caching before re-enabling this test
|
|
@pytest.mark.parametrize("qat_scheme", ["fp8-int4", "fp8-fp8", "int8", "cactus"])
|
|
def _test_full_model_fake_quantize(qat_scheme: str):
|
|
_test_model_fake_quantize(qat_scheme, full_finetuning = True)
|
|
|
|
|
|
@pytest.mark.parametrize("qat_scheme", ["fp8-int4", "fp8-fp8", "int8", "cactus"])
|
|
def test_lora_model_fake_quantize(qat_scheme: str):
|
|
_test_model_fake_quantize(qat_scheme, full_finetuning = False)
|