1. 为什么同一模型换个精度结果能差出一倍大模型量化从0到1这个系列写到第十六篇终于到了横向实测环节。BF16、AWQ、GPTQ、GGUF、NF4、FP8 这六个词你肯定都见过但真正把它们放在同一台机器、同一个模型上跑一遍的人并不多。原因很简单这六种精度根本不在同一个抽象层AWQ 和 GPTQ 是量化算法GGUF 是文件格式加运行生态NF4 是 bitsandbytes 的一种数据类型FP8 是数值格式BF16 是基线。把它们塞进同一张跑分表得到的往往是完整部署栈的混合结果而不是量化算法本身的强弱。我这次用 Qwen3-8B 做统一实验对象从同一份 BF16 snapshot 出发分别生成 AWQ INT4、GPTQ INT4、GGUF Q4_K_M、NF4 和 FP8 六种配置再用 TaoToken 的统一 Key 把云端 API 调用和本地推理串起来方便你在同一套代码里切换精度做对比。适合需要在本地或云端快速切换量化格式的开发者尤其是手上有 24GB 单卡、想搞清楚到底该选哪种格式的人。整篇会交付可复制的 config.toml 与 settings.json 骨架、TaoToken 统一 Key 接入步骤以及逐项精度、显存、吞吐验证动作。你跟着做一遍就能拿到属于自己的六格式对比结果。2. TaoToken 前置统一 Key 怎么拿、怎么配在开始量化实验之前先把 TaoToken 的接入准备好。它的作用是让你用同一个 Key 同时调用云端模型对话和本地推理服务省去为每个后端单独配鉴权的麻烦。2.1 获取 API Key打开 https://taotoken.net/api-keys 登录后创建一个新的 API Key。建议给这个 Key 起个名字比如quant-bench方便后面在多个实验里区分。创建完成后复制 Key它只会显示一次。如果你还没有账号先到 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册整个过程几分钟。2.2 配置环境变量拿到 Key 之后不要硬编码到脚本里。用环境变量管理export TAOTOKEN_API_KEYsk-你的实际Key export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用.env文件管理记得把.env加入.gitignore避免 Key 泄露。2.3 验证 Key 是否可用在正式跑量化实验前先用一个最小请求确认 Key 能通curl -s https://taotoken.net/api/v1/models \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ | python -m json.tool | head -30返回模型列表就说明 Key 正常。如果返回 401检查 Key 是否复制完整、有没有多余空格。注意TaoToken 的 API 地址是 https://taotoken.net/api 不要在后面加斜杠或路径否则可能 404。3. 可复制配置config.toml 与 settings.json 骨架这一节给出六种精度共用的配置骨架。你可以直接复制到项目根目录按需修改路径和参数。3.1 config.toml量化实验主配置# config.toml - 六格式横向实测配置 [project] name quant-bench-qwen3-8b model_repo Qwen/Qwen3-8B model_revision REPLACE_WITH_FULL_SHA base_dir ./models data_dir ./data results_dir ./results [taotoken] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY default_model qwen3-8b-bf16 [calibration] dataset HuggingFaceH4/ultrachat_200k split train_sft num_samples 256 max_length 2048 seed 42 enable_thinking false [formats.bf16] enabled true dtype bfloat16 port 8001 [formats.awq] enabled true bits 4 group_size 128 scheme W4A16_ASYM ignore [lm_head] port 8002 [formats.gptq] enabled true bits 4 group_size 128 batch_size 1 port 8003 [formats.nf4] enabled true quant_type nf4 compute_dtype bfloat16 double_quant true port 8004 [formats.fp8] enabled true quantization fp8_per_block port 8005 [formats.gguf] enabled true quant_type Q4_K_M port 8080 [benchmark] concurrency_levels [1, 2, 4, 8, 16, 32] repeat 3 warmup_requests 5 max_new_tokens 2563.2 settings.json服务端与客户端参数{ server: { max_model_len: 32768, kv_cache_dtype: auto, gpu_memory_utilization: 0.90, disable_log_requests: true, enable_prefix_caching: false, enable_speculative_decoding: false }, client: { timeout_seconds: 120, stream: true, temperature: 0.0, top_p: 1.0, ignore_eos: true }, quality: { ppl_max_length: 4096, ppl_stride: 2048, tasks: [mmlu, gsm8k, ifeval], seed: 42 }, telemetry: { sample_interval_seconds: 0.1, record_gpu_util: true, record_memory: true, record_power: true } }3.3 目录结构mkdir -p quant-bench-qwen3-8b/{models,data,manifests,scripts,logs,results/{controlled,best_stack}} cd quant-bench-qwen3-8b把上面两个配置文件放到根目录后面所有脚本都从这里读取参数。4. 六种精度的生成与启动命令这一节给出每种精度的实际生成或启动命令。受控赛道统一用 vLLMGGUF 用 llama.cpp。4.1 BF16 基线vllm serve ./models/qwen3-8b-bf16 \ --served-model-name bf16 \ --dtype bfloat16 \ --max-model-len 32768 \ --kv-cache-dtype auto \ --gpu-memory-utilization 0.90 \ --disable-log-requests \ --port 80014.2 AWQ INT4用 LLM Compressor 从同一份 BF16 snapshot 生成from llmcompressor import oneshot from llmcompressor.modifiers.quantization import QuantizationModifier from llmcompressor.modifiers.transform.awq import AWQModifier recipe [ AWQModifier(duo_scalingboth), QuantizationModifier( ignore[lm_head], schemeW4A16_ASYM, targets[Linear], ), ] oneshot( modelmodel, datasetcalibration_ds, reciperecipe, max_seq_length2048, num_calibration_samples256, ) model.save_pretrained(./models/qwen3-8b-awq, save_compressedTrue)启动vllm serve ./models/qwen3-8b-awq \ --served-model-name awq \ --max-model-len 32768 \ --gpu-memory-utilization 0.90 \ --port 80024.3 GPTQ INT4from gptqmodel import GPTQConfig, GPTQModel quant_config GPTQConfig(bits4, group_size128) model GPTQModel.load(./models/qwen3-8b-bf16, quant_config) model.quantize(calibration_texts, batch_size1) model.save(./models/qwen3-8b-gptq)启动vllm serve ./models/qwen3-8b-gptq \ --served-model-name gptq \ --max-model-len 32768 \ --gpu-memory-utilization 0.90 \ --port 80034.4 NF4import torch from transformers import AutoModelForCausalLM, BitsAndBytesConfig bnb_config BitsAndBytesConfig( load_in_4bitTrue, bnb_4bit_quant_typenf4, bnb_4bit_compute_dtypetorch.bfloat16, bnb_4bit_use_double_quantTrue, ) model AutoModelForCausalLM.from_pretrained( ./models/qwen3-8b-bf16, quantization_configbnb_config, dtypetorch.bfloat16, device_mapauto, ) model.save_pretrained(./models/qwen3-8b-nf4, safe_serializationTrue)启动vllm serve ./models/qwen3-8b-nf4 \ --served-model-name nf4 \ --dtype bfloat16 \ --max-model-len 32768 \ --gpu-memory-utilization 0.90 \ --port 80044.5 FP8 per-blockvllm serve ./models/qwen3-8b-bf16 \ --served-model-name fp8-per-block \ --quantization fp8_per_block \ --max-model-len 32768 \ --gpu-memory-utilization 0.90 \ --port 80054.6 GGUF Q4_K_M# 转 BF16 GGUF python convert_hf_to_gguf.py \ --outfile ./models/qwen3-8b-bf16.gguf \ --outtype bf16 \ ./models/qwen3-8b-bf16 # 生成 imatrix ./build/bin/llama-imatrix \ -m ./models/qwen3-8b-bf16.gguf \ -f ./data/calibration.txt \ -o ./models/imatrix.gguf \ --parse-special --no-ppl -ngl 99 # 量化到 Q4_K_M ./build/bin/llama-quantize \ --imatrix ./models/imatrix.gguf \ ./models/qwen3-8b-bf16.gguf \ ./models/qwen3-8b-q4_k_m.gguf \ Q4_K_M启动./build/bin/llama-server \ -m ./models/qwen3-8b-q4_k_m.gguf \ -ngl 99 -c 32768 -np 1 -fa on --jinja \ --host 127.0.0.1 --port 80805. 验证请求与成功结果六种服务都起来之后用同一套客户端脚本验证。这里给一个最小可用的 Python 验证脚本import os, time, json, requests BASE os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) KEY os.environ[TAOTOKEN_API_KEY] def bench(endpoint, model, prompt, max_tokens256): url f{endpoint}/v1/chat/completions headers {Authorization: fBearer {KEY}, Content-Type: application/json} payload { model: model, messages: [{role: user, content: prompt}], max_tokens: max_tokens, temperature: 0.0, stream: True, } t0 time.time() ttft None tokens 0 with requests.post(url, headersheaders, jsonpayload, streamTrue) as r: for line in r.iter_lines(): if not line or not line.startswith(bdata: ): continue data line[6:] if data b[DONE]: break chunk json.loads(data) delta chunk[choices][0].get(delta, {}) if delta.get(content): if ttft is None: ttft time.time() - t0 tokens 1 total time.time() - t0 tpot (total - ttft) / max(tokens - 1, 1) return {ttft: ttft, tpot: tpot, tokens: tokens, total: total} if __name__ __main__: prompt 用三句话解释什么是量化。 for port, name in [(8001, bf16), (8002, awq), (8003, gptq), (8004, nf4), (8005, fp8), (8080, gguf)]: try: r bench(fhttp://127.0.0.1:{port}, name, prompt) print(f{name:8s} TTFT{r[ttft]:.3f}s TPOT{r[tpot]:.4f}s tokens{r[tokens]}) except Exception as e: print(f{name:8s} FAILED: {e})成功结果应该类似bf16 TTFT0.312s TPOT0.0281s tokens256 awq TTFT0.198s TPOT0.0192s tokens256 gptq TTFT0.205s TPOT0.0198s tokens256 nf4 TTFT0.287s TPOT0.0265s tokens256 fp8 TTFT0.221s TPOT0.0203s tokens256 gguf TTFT0.245s TPOT0.0221s tokens256具体数字取决于你的硬件但趋势通常是 INT4 和 FP8 在 Decode 阶段比 BF16 快NF4 介于中间GGUF 在 llama.cpp 下表现稳定。5.1 质量验证PPL 对比import torch from transformers import AutoModelForCausalLM, AutoTokenizer def compute_ppl(model_dir, texts, max_length4096, stride2048): tokenizer AutoTokenizer.from_pretrained(model_dir) model AutoModelForCausalLM.from_pretrained(model_dir, dtypetorch.bfloat16, device_mapauto) model.eval() nlls [] for text in texts: ids tokenizer(text, return_tensorspt).input_ids.to(model.device) for i in range(0, ids.size(1), stride): begin max(i - (max_length - stride), 0) end min(i stride, ids.size(1)) target ids[:, begin:end].clone() target[:, :max_length - stride] -100 with torch.no_grad(): out model(ids[:, begin:end], labelstarget) nlls.append(out.loss.float() * (end - begin)) return torch.exp(torch.stack(nlls).sum() / ids.size(1))对六种模型分别跑同一批文本记录相对 BF16 的退化百分比。经验上 AWQ 和 GPTQ 的 PPL 退化在 1% 到 3% 之间FP8 通常在 0.5% 以内NF4 和 Q4_K_M 在 2% 到 5% 之间。6. 本篇常见错排查6.1 启动报 OOM最常见的原因是--gpu-memory-utilization设太高或者--max-model-len太大导致 KV Cache 预分配过多。先把max-model-len降到 8192 试一下确认能起来再逐步加大。如果 BF16 都起不来说明你的卡显存不够跑 8B 全精度直接跳到 INT4 或 FP8。6.2 AWQ/GPTQ 加载后速度没提升检查启动日志里实际用的 Kernel。vLLM 在 Ampere 上可能走 Marlin在 Ada 上可能走 Machete。如果日志显示 fallback 到通用 Kernel速度就不会有明显改善。确认你的 checkpoint 配置和 vLLM 版本匹配。6.3 NF4 服务启动特别慢bitsandbytes 在加载时做量化启动时间会比预量化 checkpoint 长很多。如果你要反复重启做实验建议先save_pretrained保存 NF4 checkpoint之后直接加载。6.4 GGUF 在 vLLM 里跑不动vLLM 对 GGUF 的支持目前还是实验性的需要单独装vllm-gguf-plugin。如果报错不要花太多时间折腾直接用 llama.cpp 的llama-server那才是 GGUF 的主场。6.5 FP8 在 Ampere 上速度反而慢Ampere 没有原生 FP8 Tensor CorevLLM 会走 W8A16 fallback。这时候 FP8 只省显存不提速。如果你在 A100 或 3090 上优先测 AWQ 和 GPTQ。6.6 TaoToken Key 报 401检查三件事Key 有没有复制完整、环境变量有没有 export 成功、请求头是不是Authorization: Bearer sk-xxx。如果用的是.env文件确认加载顺序在请求之前。6.7 六种模型结果不可比最常见的原因是 tokenizer 或 chat template 不一致。确保所有模型都用同一份 tokenizer 文件并且enable_thinkingFalse。如果某个后端默认开了思考模式输出长度会差好几倍速度和质量都没法比。7. 下一步把结果用起来跑完这一轮你手上应该有一张六格式的对比表。接下来怎么用取决于你的场景。如果你要做长期编码或 Agent 任务建议到 https://taotoken.net/coding-plan 看看 Coding Plan它适合需要稳定调用和批量推理的场景。如果你只是想快速验证某个模型对话效果直接到 https://taotoken.net/models 用模型对话功能试一下不用自己部署。接入文档在 https://taotoken.net/doc 里面有完整的 API 参数说明和示例代码。如果你在配置过程中遇到问题先对照第 6 节的排查清单大部分坑都在那里。实测下来没有哪种格式是全能冠军。BF16 是质量基线FP8 在 Hopper 上兼顾质量和吞吐AWQ/GPTQ 在 24GB 单卡上最实用GGUF 在 CPU 和 Apple Silicon 上无可替代NF4 适合快速原型和 QLoRA。你的硬件和业务负载决定了谁更适合而不是某个格式的名字。