File size: 6,951 Bytes
8899636 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | #!/usr/bin/env python3
"""Lượng tử hoá beyoru/Opera sang NVFP4 theo ĐÚNG recipe của unsloth/Qwen3.6-27B-NVFP4.
Recipe đọc ngược từ `quantization_config` của unsloth (compressed-tensors,
format=mixed-precision, version 0.17.2.a20260707):
group_0 — FP8 W8A8
W: 8b float, symmetric, strategy=channel, dynamic=False
A: 8b float, symmetric, strategy=token, dynamic=True
targets: self_attn.(q|k|v|o)_proj
linear_attn.(in_proj_qkv|in_proj_z|out_proj)
lm_head
layers.(56..63).mlp.(gate|up|down)_proj <-- 8 LỚP CUỐI giữ FP8
group_1 — NVFP4 W4A4
W: 4b float, gs=16, symmetric, strategy=tensor_group, dynamic=False
A: 4b float, gs=16, symmetric, strategy=tensor_group, dynamic=local
targets: mlp.(gate|up|down)_proj
ignore: toàn bộ model.visual.* (vision tower giữ bf16)
Ba mẹo giữ chất lượng, đều suy ra từ config chứ không phải đoán:
1. 8 lớp cuối MLP giữ FP8 — lớp cuối nhạy nhất với lượng tử hoá.
2. Attention + GDN projection giữ FP8; chỉ MLP thân giữa xuống W4A4.
3. Vision tower nguyên bf16.
⚠️ QUAN TRỌNG — recipe này DATA-FREE: cả hai group đều có activation dynamic
(`token` / `local`), và scale của weight suy ra từ chính weight. Nên KHÔNG cần
dataset hiệu chuẩn. Đừng thêm calibration vào, sẽ lệch khỏi bản của unsloth.
Opera cùng kiến trúc với Qwen3.6-27B (64 lớp, hidden 5120, 24 head, interval 4)
nên các regex chuyển sang dùng được nguyên, KHÔNG cần sửa chỉ số lớp.
Chạy:
pip install llmcompressor compressed-tensors
python3 quantize_opera_nvfp4.py \
--model beyoru/Opera \
--output ./Opera-NVFP4
"""
import argparse
import re
# 8 lớp cuối giữ FP8. Opera có 64 lớp (0..63) => 56..63, y hệt unsloth.
LAST_LAYERS_FP8 = list(range(56, 64))
FP8_TARGETS = [
r"re:.*self_attn\.(q|k|v|o)_proj$",
r"re:.*linear_attn\.(in_proj_qkv|in_proj_z|out_proj)$",
r"re:.*lm_head",
r"re:.*layers\.(%s)\.mlp\.(gate|up|down)_proj$"
% "|".join(str(i) for i in LAST_LAYERS_FP8),
]
NVFP4_TARGETS = [r"re:.*mlp\.(gate|up|down)_proj$"]
def build_recipe():
"""Recipe llmcompressor khớp từng trường với config của unsloth."""
from llmcompressor.modifiers.quantization import QuantizationModifier
return QuantizationModifier(
config_groups={
# ---- group_0: FP8 W8A8 -------------------------------------------
"group_0": {
"targets": FP8_TARGETS,
"weights": {
"num_bits": 8,
"type": "float",
"symmetric": True,
"strategy": "channel",
"dynamic": False,
},
"input_activations": {
"num_bits": 8,
"type": "float",
"symmetric": True,
"strategy": "token",
"dynamic": True,
},
},
# ---- group_1: NVFP4 W4A4 -----------------------------------------
"group_1": {
"targets": NVFP4_TARGETS,
"weights": {
"num_bits": 4,
"type": "float",
"group_size": 16,
"symmetric": True,
"strategy": "tensor_group",
"dynamic": False,
},
"input_activations": {
"num_bits": 4,
"type": "float",
"group_size": 16,
"symmetric": True,
"strategy": "tensor_group",
"dynamic": "local",
},
},
},
# Vision tower giữ bf16. Dùng regex thay vì liệt kê 303 tên như unsloth --
# tương đương về hiệu lực nhưng không phụ thuộc số block của vision.
# Lưu ý: KHÔNG đưa lm_head vào đây -- nó thuộc group_0 (FP8) trong recipe gốc.
ignore=["re:.*visual\\..*"],
)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--model", default="beyoru/Opera")
ap.add_argument("--output", default="./Opera-NVFP4")
ap.add_argument(
"--dry-run",
action="store_true",
help="chi in recipe + doi chieu voi unsloth, khong nap model",
)
args = ap.parse_args()
print("=== recipe se ap dung ===")
print(" group_0 (FP8 W8A8) targets:")
for t in FP8_TARGETS:
print(" ", t)
print(" group_1 (NVFP4 W4A4) targets:")
for t in NVFP4_TARGETS:
print(" ", t)
print(" ignore: re:.*visual\\..* (vision tower giu bf16)")
print(" 8 lop cuoi giu FP8:", LAST_LAYERS_FP8)
print(" KHONG dung calibration dataset (recipe data-free)")
if args.dry_run:
print("\n[dry-run] dung o day.")
return
from llmcompressor import oneshot
from transformers import AutoConfig, AutoModelForCausalLM, AutoTokenizer
# Opera là `Qwen3_5ForConditionalGeneration` (có vision tower 333 tensor), KHÔNG phải
# CausalLM thuần. Dùng AutoModelForCausalLM sẽ nạp thiếu/sai phần visual. Chọn class
# theo `architectures` trong config thay vì đoán.
cfg = AutoConfig.from_pretrained(args.model)
arch = (getattr(cfg, "architectures", None) or [""])[0]
print(f"\n=== nap {args.model} (bf16, ~55 GB) ===")
print(f" architectures: {arch}")
if "ConditionalGeneration" in arch or "ImageTextToText" in arch:
from transformers import AutoModelForImageTextToText
loader = AutoModelForImageTextToText
print(" -> dung AutoModelForImageTextToText (model da mode)")
else:
loader = AutoModelForCausalLM
print(" -> dung AutoModelForCausalLM")
model = loader.from_pretrained(args.model, dtype="auto", device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(args.model)
# ⚠️ PHẢI truyền pipeline="datafree". Mặc định là "independent", nó dispatch sang
# "sequential" — pipeline đó cần dataloader để trace model qua từng lớp, không có
# dataset thì vỡ ngay: `TypeError: 'NoneType' object is not iterable` tại
# pipelines/sequential/pipeline.py:103 (`next(iter(dataloader))`).
# Recipe này data-free thật (activation dynamic, scale weight suy từ chính weight)
# nhưng llmcompressor không tự suy ra được — phải nói thẳng.
print("=== oneshot quantize (pipeline=datafree) ===")
oneshot(model=model, recipe=build_recipe(), pipeline="datafree")
print(f"=== luu {args.output} ===")
model.save_pretrained(args.output, save_compressed=True)
tokenizer.save_pretrained(args.output)
print("XONG")
if __name__ == "__main__":
main()
|