* 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>
112 lines
3.9 KiB
Python
112 lines
3.9 KiB
Python
"""End-to-end GPU guard for batched left-padded generation (issues #1066, #3699).
|
|
|
|
Greedy generation in a left-padded batch must match solo batch-size-1
|
|
generation for the first PREFIX_TOKENS tokens (the bug makes padded rows
|
|
diverge into garbage immediately; a full-length match would be flaky due to
|
|
benign batch-numerics tie-flips deep in the sequence) and must not be
|
|
gibberish. Skipped without a GPU. Run: `python -m pytest
|
|
tests/utils/test_batched_leftpad_generation_gpu.py -v`.
|
|
"""
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
cuda_available = torch.cuda.is_available()
|
|
xpu_available = hasattr(torch, "xpu") and torch.xpu.is_available()
|
|
device = "cuda" if cuda_available else "xpu" if xpu_available else "cpu"
|
|
|
|
# Non-strict rather than CUDA-only: keeps the XPU divergence visible, and goes
|
|
# green by itself once XPU generation is fixed.
|
|
pytestmark = [
|
|
pytest.mark.skipif(not (cuda_available or xpu_available), reason = "requires a CUDA or XPU GPU"),
|
|
pytest.mark.xfail(
|
|
xpu_available and not cuda_available,
|
|
reason = "batched left-padded generation diverges on XPU",
|
|
strict = False,
|
|
),
|
|
]
|
|
|
|
MODEL_NAME = "unsloth/Qwen2.5-0.5B-Instruct"
|
|
MAX_NEW_TOKENS = 32
|
|
PREFIX_TOKENS = 16
|
|
|
|
PROMPTS = [
|
|
"Give me a short introduction to large language model.",
|
|
"Here is an experiment log: "
|
|
+ " ".join(f"run {i} completed with stable throughput and no anomalies;" for i in range(1, 41))
|
|
+ " In one sentence, what is the overall conclusion?",
|
|
]
|
|
|
|
|
|
@pytest.fixture(scope = "module")
|
|
def model_and_tokenizer():
|
|
from unsloth import FastLanguageModel
|
|
|
|
model, tokenizer = FastLanguageModel.from_pretrained(
|
|
model_name = MODEL_NAME,
|
|
max_seq_length = 2048,
|
|
load_in_4bit = True,
|
|
)
|
|
FastLanguageModel.for_inference(model)
|
|
tokenizer.padding_side = "left"
|
|
if tokenizer.pad_token_id is None:
|
|
tokenizer.pad_token_id = tokenizer.eos_token_id
|
|
return model, tokenizer
|
|
|
|
|
|
def _chat(tokenizer, prompt):
|
|
return tokenizer.apply_chat_template(
|
|
[{"role": "user", "content": prompt}],
|
|
tokenize = False,
|
|
add_generation_prompt = True,
|
|
)
|
|
|
|
|
|
def _generate(model, tokenizer, texts):
|
|
inputs = tokenizer(texts, return_tensors = "pt", padding = True, add_special_tokens = False).to(
|
|
device
|
|
)
|
|
with torch.inference_mode():
|
|
out = model.generate(
|
|
**inputs,
|
|
max_new_tokens = MAX_NEW_TOKENS,
|
|
do_sample = False,
|
|
temperature = None,
|
|
top_p = None,
|
|
top_k = None,
|
|
use_cache = True,
|
|
pad_token_id = tokenizer.pad_token_id,
|
|
)
|
|
suffixes = out[:, inputs["input_ids"].shape[1] :]
|
|
return [row.tolist() for row in suffixes]
|
|
|
|
|
|
def _looks_gibberish(text):
|
|
if not text.strip():
|
|
return True
|
|
exclam = text.count("!") / max(len(text), 1)
|
|
nonascii = sum(1 for c in text if ord(c) > 0x2FFF) / max(len(text), 1)
|
|
return exclam > 0.3 or nonascii > 0.5
|
|
|
|
|
|
def test_batched_leftpad_matches_solo_generation(model_and_tokenizer):
|
|
model, tokenizer = model_and_tokenizer
|
|
|
|
texts = [_chat(tokenizer, p) for p in PROMPTS]
|
|
solo = [_generate(model, tokenizer, [t])[0] for t in texts]
|
|
batched = _generate(model, tokenizer, texts)
|
|
|
|
for i, prompt in enumerate(PROMPTS):
|
|
solo_text = tokenizer.decode(solo[i], skip_special_tokens = True)
|
|
batch_text = tokenizer.decode(batched[i], skip_special_tokens = True)
|
|
assert batched[i][:PREFIX_TOKENS] == solo[i][:PREFIX_TOKENS], (
|
|
f"prompt {i} ({prompt[:30]!r}...) diverged from solo generation "
|
|
f"within the first {PREFIX_TOKENS} tokens inside a left-padded "
|
|
"batch; batched left-padded generation is broken again "
|
|
f"(issues #1066, #3699).\n"
|
|
f"solo : {solo_text!r}\nbatched: {batch_text!r}"
|
|
)
|
|
assert not _looks_gibberish(batch_text), (
|
|
f"prompt {i} produced gibberish in a left-padded batch "
|
|
f"(issues #1066, #3699): {batch_text!r}"
|
|
)
|