florianvoss's picture
Upload Qwen2.5-0.5B-Instruct-Autoround-Safetensors
eb0bed5 verified
Raw
History Blame Contribute Delete
9.61 kB
#!/usr/bin/env python3
"""Quantize Qwen2.5-0.5B-Instruct with UltraChat AutoRound plus a GPTQ lm_head."""
import argparse
import importlib.metadata
import platform
import shutil
from pathlib import Path
import torch
from compressed_tensors.utils import match_named_modules
from datasets import Dataset, load_dataset
from llmcompressor import oneshot
from llmcompressor.modifiers.autoround import AutoRoundModifier
from llmcompressor.modifiers.quantization import GPTQModifier
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_LABEL = "Qwen2.5-0.5B-Instruct"
BASE_MODEL = "Qwen/Qwen2.5-0.5B-Instruct"
SOURCE_REVISION = "7ae557604adf67be50417f59c2c2f167def9a775"
CALIBRATION_DATASET = "HuggingFaceH4/ultrachat_200k"
CALIBRATION_SPLIT = "train_sft[:512]"
NUM_CALIBRATION_SAMPLES = 512
MAX_SEQ_LENGTH = 1024
AUTOROUND_BITS = 4
AUTOROUND_GROUP_SIZE = 128
AUTOROUND_ITERS = 200
AUTOROUND_BATCH_SIZE = 1
LM_HEAD_BITS = 4
LM_HEAD_GROUP_SIZE = 128
GPTQ_BLOCK_SIZE = 128
GPTQ_DAMPENING_FRAC = 0.01
def package_version(*names):
for name in names:
try:
return importlib.metadata.version(name)
except importlib.metadata.PackageNotFoundError:
continue
return "not-installed"
def weight_config(bits, group_size):
return {
"num_bits": bits,
"type": "int",
"symmetric": True,
"group_size": group_size,
"strategy": "group",
"dynamic": False,
}
def calibration_dataset(tokenizer):
if not tokenizer.chat_template:
raise ValueError(
"Tokenizer has no chat template; refusing untemplated calibration."
)
source = load_dataset(CALIBRATION_DATASET, split=CALIBRATION_SPLIT)
if "messages" not in source.column_names:
raise ValueError(
f"{CALIBRATION_DATASET} {CALIBRATION_SPLIT} lacks messages."
)
packed_ids, buffer = [], []
for example in source:
text = tokenizer.apply_chat_template(
example["messages"], tokenize=False, add_generation_prompt=False
)
buffer.extend(tokenizer(text, add_special_tokens=False)["input_ids"])
while (
len(buffer) >= MAX_SEQ_LENGTH
and len(packed_ids) < NUM_CALIBRATION_SAMPLES
):
packed_ids.append(buffer[:MAX_SEQ_LENGTH])
buffer = buffer[MAX_SEQ_LENGTH:]
if len(packed_ids) == NUM_CALIBRATION_SAMPLES:
break
if len(packed_ids) != NUM_CALIBRATION_SAMPLES:
raise ValueError(
f"UltraChat did not provide {NUM_CALIBRATION_SAMPLES} complete "
f"{MAX_SEQ_LENGTH}-token spans."
)
dataset = Dataset.from_dict(
{
"input_ids": packed_ids,
"attention_mask": [[1] * MAX_SEQ_LENGTH for _ in packed_ids],
}
)
dataset.set_format(type="torch")
return dataset
def validate_targets(model):
decoder = list(
match_named_modules(model, targets=["Linear"], ignore=["lm_head"])
)
head = list(match_named_modules(model, targets=["lm_head"]))
if not decoder or [name for name, _ in head] != ["lm_head"]:
raise ValueError(
f"Invalid targets: decoder={len(decoder)}, "
f"head={[name for name, _ in head]}"
)
return len(decoder)
def recipe():
return [
AutoRoundModifier(
targets=["Linear"],
ignore=["lm_head"],
iters=AUTOROUND_ITERS,
batch_size=AUTOROUND_BATCH_SIZE,
device_ids="0",
enable_torch_compile=False,
config_groups={
"decoder_autoround": {
"targets": ["Linear"],
"weights": weight_config(
AUTOROUND_BITS, AUTOROUND_GROUP_SIZE
),
}
},
),
GPTQModifier(
targets=["lm_head"],
block_size=GPTQ_BLOCK_SIZE,
dampening_frac=GPTQ_DAMPENING_FRAC,
actorder="static",
offload_hessians=False,
config_groups={
"lm_head_gptq": {
"targets": ["lm_head"],
"weights": weight_config(
LM_HEAD_BITS, LM_HEAD_GROUP_SIZE
),
}
},
),
]
def write_release_files(source, output, decoder_count):
for item in source.iterdir():
if (
item.is_file()
and item.suffix in {".json", ".txt", ".model", ".jinja"}
and not item.name.endswith(".index.json")
and not (output / item.name).exists()
):
shutil.copy2(item, output / item.name)
shutil.copy2(Path(__file__).resolve(), output / "quantize.py")
output.joinpath("recipe.yaml").write_text(
f"# Effective {MODEL_LABEL} UltraChat AutoRound recipe\n"
"decoder_autoround:\n"
" method: AutoRound\n"
" targets: Linear excluding lm_head\n"
f" iters: {AUTOROUND_ITERS}\n"
f" batch_size: {AUTOROUND_BATCH_SIZE}\n"
f" weights: {{num_bits: {AUTOROUND_BITS}, type: int, symmetric: true, "
f"strategy: group, group_size: {AUTOROUND_GROUP_SIZE}}}\n"
" calibration:\n"
f" dataset: {CALIBRATION_DATASET}\n"
f" split: {CALIBRATION_SPLIT}\n"
" preprocessing: apply_chat_template; deterministic packed spans; "
"add_special_tokens=false\n"
f" samples: {NUM_CALIBRATION_SAMPLES}\n"
f" sequence_length: {MAX_SEQ_LENGTH}\n"
" shuffle: false\n"
"lm_head_gptq:\n"
" method: GPTQ\n"
" target: lm_head\n"
f" block_size: {GPTQ_BLOCK_SIZE}\n"
f" dampening_frac: {GPTQ_DAMPENING_FRAC}\n"
" actorder: static\n"
f" weights: {{num_bits: {LM_HEAD_BITS}, type: int, symmetric: true, "
f"strategy: group, group_size: {LM_HEAD_GROUP_SIZE}}}\n"
f"decoder_target_count: {decoder_count}\n"
f"base_model: {BASE_MODEL}\n"
f"source_revision: {SOURCE_REVISION}\n"
)
output.joinpath("versions.txt").write_text(
"\n".join(
(
f"Python: {platform.python_version()}",
f"torch: {torch.__version__}",
f"CUDA: {torch.version.cuda or 'not-built'}",
f"transformers: {package_version('transformers')}",
f"llm-compressor: {package_version('llmcompressor', 'llm-compressor')}",
f"auto-round: {package_version('auto-round', 'auto_round')}",
f"compressed-tensors: {package_version('compressed-tensors')}",
)
)
+ "\n"
)
def validate_scales(output):
from safetensors.torch import safe_open
count = 0
for shard in output.glob("*.safetensors"):
with safe_open(shard, framework="pt", device="cpu") as tensors:
for name in tensors.keys():
if "scale" not in name.lower():
continue
value = tensors.get_tensor(name)
count += 1
if value.is_floating_point() and not torch.isfinite(
value.float()
).all():
raise ValueError(f"Non-finite scale: {shard.name}:{name}")
if not count:
raise ValueError("No saved scale tensors found.")
def smoke_test(output):
tokenizer = AutoTokenizer.from_pretrained(output, trust_remote_code=False)
model = AutoModelForCausalLM.from_pretrained(
output,
device_map="auto",
torch_dtype="auto",
trust_remote_code=False,
)
prompt = tokenizer.apply_chat_template(
[{"role": "user", "content": "Reply with one word: ready."}],
add_generation_prompt=True,
tokenize=False,
)
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.inference_mode():
generated = model.generate(
**inputs, max_new_tokens=4, do_sample=False
)
if generated.shape[-1] <= inputs["input_ids"].shape[-1]:
raise ValueError("Generation smoke test produced no new tokens.")
def quantize(model_path, output_dir):
source, output = Path(model_path), Path(output_dir)
if output.exists() and any(output.iterdir()):
raise FileExistsError(f"Refusing to overwrite: {output}")
model = AutoModelForCausalLM.from_pretrained(
source,
device_map="cpu",
torch_dtype="auto",
trust_remote_code=False,
)
tokenizer = AutoTokenizer.from_pretrained(
source, trust_remote_code=False
)
decoder_count = validate_targets(model)
oneshot(
model=model,
dataset=calibration_dataset(tokenizer),
recipe=recipe(),
max_seq_length=MAX_SEQ_LENGTH,
num_calibration_samples=NUM_CALIBRATION_SAMPLES,
shuffle_calibration_samples=False,
)
output.mkdir(parents=True, exist_ok=True)
model.save_pretrained(output, save_compressed=True)
tokenizer.save_pretrained(output)
write_release_files(source, output, decoder_count)
validate_scales(output)
smoke_test(output)
print(f"Quantized model written to: {output}")
def main():
parser = argparse.ArgumentParser(
description=f"{MODEL_LABEL} UltraChat AutoRound/GPTQ quantization"
)
parser.add_argument("--model-path", required=True)
parser.add_argument("--output-dir", required=True)
args = parser.parse_args()
quantize(args.model_path, args.output_dir)
if __name__ == "__main__":
main()