Pe un laptop AI PC, quantizarea int4 nu înseamnă automat că modelul rulează pe NPU. În acest tutorial folosim o variantă sigură și reproductibilă: CPU local, TorchAO, Hugging Face Transformers și un model mic, public, facebook/opt-125m. Rezultatul final este un director artifacts/opt-125m-torchao-int4-cpu/ cu modelul quantizat, un fișier artifacts/report.json cu loss-ul comparativ și un test de inferență determinist.
Dacă ai lucrat deja cu AWQ, GPTQ sau FP8, vezi și ghidul despre cum măsori compromisul dintre memorie, viteză și calitatea răspunsurilor. Aici rămânem strict pe TorchAO int4 weight-only, pe un singur flux local.
Ce construim
Construim un mic pipeline local pentru inferență LLM:
- descarcă
facebook/opt-125m; - aplică TorchAO int4 weight-only pe CPU, cu
Int4WeightOnlyConfig; - salvează modelul quantizat în format compatibil
save_pretrained; - reîncarcă modelul quantizat de pe disc;
- calculează loss pe un set mic de evaluare local;
- rulează o inferență scurtă și scrie totul în
artifacts/report.json.
Atenție: exemplul este pentru laborator local, nu pentru producție. Pentru servire cu mai mulți utilizatori, ai nevoie de altă arhitectură, iar pentru asta e relevant ghidul despre deploy cu vLLM, PagedAttention și KV cache.
Mediul exact
Mediu folosit în tutorial:
- Ubuntu 24.04 LTS;
- Python 3.11;
- pip într-un virtual environment;
- PyTorch instalat din indexul oficial CPU;
torchaoversiunea 0.15.0 sau mai nouă, deoarece documentația Transformers menționează suportulsave_pretrainedpentru TorchAO de la 0.15;transformers,accelerate,safetensors,psutil.
Resurse minime realiste pentru acest exemplu:
- 8 GB RAM;
- 4 GB spațiu liber;
- internet pentru descărcarea modelului;
- 15-30 de minute la prima rulare, în funcție de conexiune și CPU.
Modelul folosit este facebook/opt-125m, ales pentru că este suficient de mic pentru test local. Nu folosi acest tutorial ca evaluare de calitate pentru modele mari; aici verificăm fluxul tehnic TorchAO int4.
Ce instalezi
Creează directorul proiectului:
mkdir -p torchao-int4-local
cd torchao-int4-local
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
Instalează PyTorch CPU din indexul oficial PyTorch:
pip install torch --index-url https://download.pytorch.org/whl/cpu
Instalează pachetele pentru quantizare și rulare:
pip install "torchao>=0.15.0" "transformers" "accelerate" "safetensors" "psutil"
Ce trebuie să vezi:
Successfully installed ...
Verifică versiunile:
python - <<'PY'
import torch
import torchao
import transformers
print("torch:", torch.__version__)
print("torchao:", torchao.__version__)
print("transformers:", transformers.__version__)
print("cuda available:", torch.cuda.is_available())
PY
Exemplu de output:
torch: 2.x.x+cpu
torchao: 0.15.0 sau mai nou
transformers: 4.x.x sau mai nou
cuda available: False
Pasul 1: creezi setul local de evaluare
Facem un set mic, local, fără date personale. Nu este un benchmark public, ci o verificare reproductibilă că modelul base și modelul int4 pot calcula loss pe aceleași texte.
mkdir -p data artifacts
cat > data/eval_prompts.jsonl <<'EOF'
{"text":"Cybersecurity teams should keep offline backups and test restore procedures."}
{"text":"A small business can reduce account takeover risk with passkeys and multi-factor authentication."}
{"text":"Local language models can be useful for drafting, but sensitive data still needs governance."}
EOF
Ce trebuie să vezi după verificare:
wc -l data/eval_prompts.jsonl
Output așteptat:
3 data/eval_prompts.jsonl
Pasul 2: scrii scriptul complet
Fișierul de mai jos face tot fluxul: încarcă modelul base, calculează loss, încarcă modelul quantizat int4, îl salvează, îl reîncarcă și scrie raportul.
cat > run_torchao_int4_cpu.py <<'PY'
import json
import os
import time
from pathlib import Path
import psutil
import torch
import torchao
import transformers
from packaging.version import Version
from torchao.quantization import Int4WeightOnlyConfig
from torchao.quantization.quantize_.workflows import Int4PackingFormat
from transformers import AutoModelForCausalLM, AutoTokenizer, TorchAoConfig
MODEL_ID = "facebook/opt-125m"
EVAL_FILE = Path("data/eval_prompts.jsonl")
OUT_DIR = Path("artifacts/opt-125m-torchao-int4-cpu")
REPORT_FILE = Path("artifacts/report.json")
GROUP_SIZE = 128
MAX_NEW_TOKENS = 24
def rss_mb():
return round(psutil.Process(os.getpid()).memory_info().rss / 1024 / 1024, 2)
def read_eval_texts():
texts = []
with EVAL_FILE.open("r", encoding="utf-8") as handle:
for line in handle:
item = json.loads(line)
texts.append(item["text"])
return texts
def mean_loss(model, tokenizer, texts):
losses = []
model.eval()
with torch.inference_mode():
for text in texts:
encoded = tokenizer(text, return_tensors="pt")
encoded = {key: value.to(model.device) for key, value in encoded.items()}
output = model(**encoded, labels=encoded["input_ids"])
losses.append(float(output.loss.detach().cpu()))
return sum(losses) / len(losses)
def generate_text(model, tokenizer):
prompt = "Local AI inference helps teams"
encoded = tokenizer(prompt, return_tensors="pt")
encoded = {key: value.to(model.device) for key, value in encoded.items()}
with torch.inference_mode():
generated = model.generate(
**encoded,
max_new_tokens=MAX_NEW_TOKENS,
do_sample=False,
pad_token_id=tokenizer.eos_token_id,
)
return tokenizer.decode(generated[0], skip_special_tokens=True)
def dir_size_mb(path):
total = 0
for file_path in path.rglob("*"):
if file_path.is_file():
total += file_path.stat().st_size
return round(total / 1024 / 1024, 2)
def main():
if Version(torchao.__version__) < Version("0.15.0"):
raise RuntimeError("TorchAO 0.15.0 sau mai nou este necesar pentru acest flux.")
OUT_DIR.mkdir(parents=True, exist_ok=True)
REPORT_FILE.parent.mkdir(parents=True, exist_ok=True)
texts = read_eval_texts()
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
started = time.time()
mem_before_base = rss_mb()
base_model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
device_map="cpu",
dtype="auto",
)
base_loss = mean_loss(base_model, tokenizer, texts)
base_generation = generate_text(base_model, tokenizer)
mem_after_base = rss_mb()
del base_model
quant_config = Int4WeightOnlyConfig(
group_size=GROUP_SIZE,
int4_packing_format=Int4PackingFormat.PLAIN_INT32,
)
quantization_config = TorchAoConfig(quant_type=quant_config)
mem_before_quant = rss_mb()
quantized_model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
device_map="cpu",
dtype="auto",
quantization_config=quantization_config,
)
quant_loss = mean_loss(quantized_model, tokenizer, texts)
quant_generation = generate_text(quantized_model, tokenizer)
mem_after_quant = rss_mb()
quantized_model.save_pretrained(OUT_DIR)
tokenizer.save_pretrained(OUT_DIR)
saved_size = dir_size_mb(OUT_DIR)
del quantized_model
reloaded_model = AutoModelForCausalLM.from_pretrained(
OUT_DIR,
device_map="cpu",
dtype=torch.bfloat16,
)
reloaded_loss = mean_loss(reloaded_model, tokenizer, texts)
reloaded_generation = generate_text(reloaded_model, tokenizer)
report = {
"model_id": MODEL_ID,
"device": "cpu",
"torch": torch.__version__,
"torchao": torchao.__version__,
"transformers": transformers.__version__,
"group_size": GROUP_SIZE,
"int4_packing_format": "PLAIN_INT32",
"eval_items": len(texts),
"base_loss": round(base_loss, 6),
"quantized_loss": round(quant_loss, 6),
"reloaded_loss": round(reloaded_loss, 6),
"base_generation": base_generation,
"quantized_generation": quant_generation,
"reloaded_generation": reloaded_generation,
"rss_mb": {
"before_base": mem_before_base,
"after_base": mem_after_base,
"before_quant": mem_before_quant,
"after_quant": mem_after_quant,
},
"saved_model_dir": str(OUT_DIR),
"saved_model_size_mb": saved_size,
"elapsed_seconds": round(time.time() - started, 2),
}
REPORT_FILE.write_text(json.dumps(report, indent=2, ensure_ascii=False), encoding="utf-8")
print(json.dumps(report, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()
PY
Ce trebuie să vezi:
test -f run_torchao_int4_cpu.py && echo "script creat"
Output așteptat:
script creat
Pasul 3: rulezi quantizarea int4
Prima rulare descarcă modelul de pe Hugging Face Hub. Nu întrerupe procesul în timpul salvării.
python run_torchao_int4_cpu.py
Ce trebuie să vezi la final: un JSON tipărit în terminal, cu câmpuri ca acestea:
"model_id": "facebook/opt-125m"
"device": "cpu"
"group_size": 128
"int4_packing_format": "PLAIN_INT32"
"eval_items": 3
"saved_model_dir": "artifacts/opt-125m-torchao-int4-cpu"
Valorile pentru base_loss, quantized_loss, reloaded_loss, elapsed_seconds și rss_mb pot diferi între laptopuri. Important este ca scriptul să ajungă la final și să scrie artifacts/report.json.
Dacă vrei o evaluare mai serioasă a răspunsurilor, nu te baza pe o singură generare. Pentru partea de evaluare cu arbitri LLM, vezi ghidul despre LLM-as-a-Judge fără rezultate înșelătoare.
Pasul 4: verifici fișierele produse
Verifică directorul modelului quantizat:
find artifacts/opt-125m-torchao-int4-cpu -maxdepth 1 -type f | sort
Ce trebuie să vezi: fișiere de configurare, tokenizer și greutățile modelului. Numele exact al fișierului de greutăți poate depinde de versiunea Transformers, dar directorul trebuie să conțină modelul salvat cu save_pretrained.
Verifică raportul:
python - <<'PY'
import json
from pathlib import Path
report = json.loads(Path("artifacts/report.json").read_text(encoding="utf-8"))
required = [
"model_id",
"torch",
"torchao",
"transformers",
"base_loss",
"quantized_loss",
"reloaded_loss",
"saved_model_dir",
"saved_model_size_mb",
]
missing = [key for key in required if key not in report]
if missing:
raise SystemExit(f"Lipsesc câmpuri: {missing}")
print("OK: raport complet")
print("Model:", report["model_id"])
print("TorchAO:", report["torchao"])
print("Loss base:", report["base_loss"])
print("Loss int4:", report["quantized_loss"])
print("Loss reîncărcat:", report["reloaded_loss"])
print("Director:", report["saved_model_dir"])
PY
Output așteptat:
OK: raport complet
Model: facebook/opt-125m
TorchAO: 0.15.0 sau mai nou
Loss base: valoare numerică
Loss int4: valoare numerică
Loss reîncărcat: valoare numerică
Director: artifacts/opt-125m-torchao-int4-cpu
Pasul 5: rulezi inferență doar din modelul reîncărcat
Acum confirmăm că nu depindem de modelul base din memorie. Folosim doar directorul salvat.
cat > infer_from_saved_int4.py <<'PY'
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
MODEL_DIR = "artifacts/opt-125m-torchao-int4-cpu"
PROMPT = "Local AI inference is useful because"
tokenizer = AutoTokenizer.from_pretrained(MODEL_DIR)
model = AutoModelForCausalLM.from_pretrained(
MODEL_DIR,
device_map="cpu",
dtype=torch.bfloat16,
)
encoded = tokenizer(PROMPT, return_tensors="pt")
encoded = {key: value.to(model.device) for key, value in encoded.items()}
with torch.inference_mode():
generated = model.generate(
**encoded,
max_new_tokens=24,
do_sample=False,
pad_token_id=tokenizer.eos_token_id,
)
print(tokenizer.decode(generated[0], skip_special_tokens=True))
PY
python infer_from_saved_int4.py
Ce trebuie să vezi:
Local AI inference is useful because ...
Textul complet poate varia între versiuni de biblioteci. Verificarea importantă este că scriptul încarcă modelul din artifacts/opt-125m-torchao-int4-cpu și produce text fără să redescarce modelul base.
Dacă folosești modelul într-un classifier, partea de quantizare nu rezolvă singură calibrarea probabilităților. Pentru asta, vezi tutorialul despre ECE și temperature scaling pentru probabilități LLM.
Verificarea finală
Rulează toate verificările într-o singură comandă:
python run_torchao_int4_cpu.py
python infer_from_saved_int4.py
python - <<'PY'
import json
from pathlib import Path
model_dir = Path("artifacts/opt-125m-torchao-int4-cpu")
report_file = Path("artifacts/report.json")
assert model_dir.exists(), "Directorul modelului lipsește"
assert report_file.exists(), "Raportul lipsește"
report = json.loads(report_file.read_text(encoding="utf-8"))
assert report["model_id"] == "facebook/opt-125m"
assert report["device"] == "cpu"
assert report["group_size"] == 128
assert report["int4_packing_format"] == "PLAIN_INT32"
assert report["eval_items"] == 3
for key in ["base_loss", "quantized_loss", "reloaded_loss"]:
assert isinstance(report[key], float), f"{key} nu este numeric"
print("VERIFICARE COMPLETĂ: model int4 salvat, reîncărcat și evaluat.")
PY
Output așteptat:
VERIFICARE COMPLETĂ: model int4 salvat, reîncărcat și evaluat.
Dacă vezi acest mesaj, ai un flux local complet: date de evaluare, model base, quantizare int4 TorchAO, salvare, reîncărcare, loss și inferență.
Probleme frecvente
RuntimeError: TorchAO 0.15.0 sau mai nou este necesar pentru acest flux.
Cauza: ai o versiune veche de TorchAO. Rezolvare:
pip install --upgrade "torchao>=0.15.0"
ModuleNotFoundError: No module named 'torchao'
Cauza: mediul virtual nu este activ sau pachetul nu a fost instalat. Rezolvare:
source .venv/bin/activate
pip install "torchao>=0.15.0"
OSError: facebook/opt-125m is not a local folder and is not a valid model identifier
Cauza: nu ai conexiune la Hugging Face Hub sau există o problemă de acces la rețea. Rezolvare: verifică internetul și rulează din nou aceeași comandă.
python run_torchao_int4_cpu.py
RuntimeError la încărcarea modelului int4 pe alt dispozitiv
Cauza: documentația Transformers precizează că pentru int4 modelul trebuie încărcat pe același tip de dispozitiv pe care a fost quantizat, deoarece layout-ul este specific dispozitivului. În acest tutorial quantizăm și reîncărcăm pe CPU. Rezolvare: păstrează device_map="cpu" în scripturile de quantizare și inferență.
Procesul se oprește din lipsă de memorie
Cauza: deși OPT-125M este mic, în timpul fluxului există momente în care modelul base și modelul quantizat sunt încărcate succesiv. Rezolvare: închide aplicațiile mari și rulează din nou într-un terminal curat.
python run_torchao_int4_cpu.py
Revenire și curățenie
Dacă vrei să revii la starea inițială, șterge doar artefactele create de tutorial și mediul virtual:
rm -rf artifacts data run_torchao_int4_cpu.py infer_from_saved_int4.py .venv
Ce trebuie să vezi:
test ! -d artifacts && test ! -d .venv && echo "curățenie completă"
Output așteptat:
curățenie completă
Comanda nu atinge alte directoare. Nu rula variante cu rm -rf / sau căi absolute pe care nu le-ai verificat.
Mai departe
Următorul pas practic este să înlocuiești facebook/opt-125m cu un model mai apropiat de cazul tău, dar să păstrezi aceeași structură: evaluare înainte, quantizare, salvare, reîncărcare, evaluare după. Al doilea pas este să extinzi data/eval_prompts.jsonl cu exemple reale, fără date personale sau secrete. Al treilea pas este să compari TorchAO int4 cu o altă metodă de quantizare, folosind aceeași evaluare, nu impresii dintr-un singur prompt.
Întrebări frecvente
Rulează acest tutorial pe NPU-ul unui AI PC?
Nu. Tutorialul folosește CPU local cu TorchAO și Transformers. „AI PC” înseamnă aici rulare locală pe laptop, nu accelerare NPU.
De ce folosim facebook/opt-125m și nu un model de 7B?
Pentru reproductibilitate pe laptopuri obișnuite. Modelul OPT-125M permite testarea fluxului TorchAO int4 fără cerințe mari de memorie.
Pot încărca modelul int4 salvat pe GPU după ce l-am quantizat pe CPU?
Pentru int4, documentația Transformers precizează că modelul trebuie încărcat pe același tip de dispozitiv pe care a fost quantizat. În tutorial, salvarea și reîncărcarea sunt pe CPU.
Loss-ul quantizat trebuie să fie identic cu loss-ul modelului base?
Nu. Tutorialul verifică faptul că fluxul rulează complet și că loss-ul este numeric pentru același set local. Quantizarea poate schimba loss-ul și textul generat.

















































