直接回答
大模型量化不是一个通用的压缩开关,而是一份完整的部署契约:它必须说明量化对象是权重、激活还是 KV Cache,数值表示是 W4A16 还是 W8A8,量化算法与校准数据如何生成制品,以及哪个运行时内核负责执行。更低的名义位宽可能减少存储或内存流量,却不保证延迟更低、总显存更少或模型行为合格。只有在目标硬件和真实负载上同时通过业务质量、安全、延迟、显存与 Goodput 门禁,候选制品才具备生产价值。
核心要点
- 必须把 GPTQ、AWQ、SmoothQuant 等算法,与 INT4/INT8 数值表示、GGUF 容器和推理运行时分层讨论。
- 名义位宽只能估算权重负载下界;KV Cache、缩放参数、工作区、内存分配器与并发共同决定服务容量。
- 基础 Checkpoint、Tokenizer、Chat Template、量化器版本、校准数据、打包布局和制品校验和都属于模型契约。
- 基准应拆成同引擎受控赛道与最佳生态赛道,不能拼接不同引擎的 tokens/s 后宣布算法排名。
- 灰度只接纳同时满足业务质量、安全、延迟、显存和 Goodput 门槛的量化制品。
大模型量化究竟改变了什么
量化使用有限的离散值近似原始实数张量。常见的仿射量化可以写成:
q = clip(round(x / scale) + zero_point, q_min, q_max)
x_hat = scale * (q - zero_point)
缩放因子与可选零点可以按整个张量共享,也可以按通道、分组或数据块计算。粒度越细,通常越能跟随局部分布,但会引入更多元数据,并依赖不同的打包方式与内核。
W4A16 表示 4 位权重和 16 位激活,W8A8 表示 8 位权重和 8 位激活。但这些标签仍未说明分组大小、对称或非对称映射、累加精度、未量化模块、张量布局,以及 KV Cache 是否仍使用 FP16 或 BF16。
权重、激活与 KV Cache 是三个独立对象
三类量化解决的瓶颈不同,不能用“模型已经 INT4”一笔带过。
| 量化对象 | 主要目标 | 典型风险 | 必测指标 |
|---|---|---|---|
| 权重 | 减少制品体积和权重内存流量 | 层级质量退化、打包布局无内核支持 | 业务质量、加载时间、Decode 表现 |
| 激活 | 使用低精度矩阵计算 | 异常值和累加误差 | 任务切片质量、Prefill 延迟 |
| KV Cache | 降低上下文长度和并发带来的内存增长 | 长上下文质量退化 | 长上下文验收率、容量、ITL |
仅权重量化不会自动减少 KV Cache。长 Prompt 和高并发场景中,KV Cache 可能反而成为显存主导项。分页、隔离与容量规划可参考独立的 KV Cache 工程指南。
算法、格式、容器与运行时不能混排
GPTQ、AWQ、GGUF、bitsandbytes 和 llama.cpp 经常出现在同一张“量化方法对比表”中,但它们解决的是不同层级的问题。
| 层级 | 代表对象 | 回答的问题 |
|---|---|---|
| 数值表示 | INT8、INT4、FP8、NF4 | 张量使用哪些值与位布局? |
| 量化算法 | GPTQ、AWQ、SmoothQuant | 如何选择缩放参数或舍入后的权重? |
| 加载或转换路径 | bitsandbytes、LLM Compressor、TorchAO | 在哪个阶段完成转换或低精度加载? |
| 模型容器 | GGUF | 张量与元数据如何打包? |
| 推理运行时 | vLLM、llama.cpp、Ollama、TensorRT-LLM | 哪些内核负责调度并执行请求? |
GGUF 官方规范 定义的是带可扩展元数据、张量信息、对齐规则和内存映射能力的二进制文件格式。GGUF 可以承载不同编码的量化张量,但它本身不是与 GPTQ 或 AWQ 同层的算法。
运行时支持也会持续变化。vLLM 当前量化文档 维护方法与硬件兼容矩阵,并没有承诺每种量化制品都能在所有设备上高效运行。生成制品前应固定运行时版本,并根据当前官方矩阵验证目标路径。
把兼容性作为版本化发布证据
“vLLM 支持”如果没有运行时版本、模型架构、量化方法、制品布局、加速器代际与内核路径,就是不完整结论。官方兼容矩阵会随方法和硬件后端演进而变化。vLLM 还允许通过外部量化插件扩展识别或执行能力,但这不代表插件路径与内置优化内核具有相同效率。
每个候选至少应保存:
- 精确 Runtime Image 与 vLLM 版本;
- 模型架构与不可变制品 Checksum;
- 量化方法、张量格式、Group Size 与排除模块;
- 加速器型号、Compute Capability、Driver 与库版本;
- 实际选择的内核,以及所有重打包、反量化或算子回退;
- 使用插件时的包名、版本、来源与兼容合同;
- 同一部署镜像下的加载、质量、延迟、显存和 Goodput 结果。
成功加载只能证明运行时解析了足够多的制品信息并启动,不能证明每层都使用预期低精度内核,也不能证明不存在回退或执行高效。应把回退行为显式化;一旦出现未经批准的内核路径,发布门禁必须失败。
GPTQ、AWQ 与 SmoothQuant 的机制差异
三种方法优化的是不同近似问题。论文能够证明机制及其报告实验,不能证明一个方法在所有模型、硬件和运行时上永久胜出。
GPTQ 使用近似二阶信息选择舍入
GPTQ 原始论文 描述了一种训练后、仅权重的一次性量化方法。它利用近似二阶信息,在逐层量化过程中更新舍入决策。生产落地时真正需要回答的是:目标模型是否受支持,校准样本能否代表业务输入,以及部署运行时是否拥有匹配制品布局的高效内核。
论文中的量化耗时、质量和加速结果只属于其报告的模型、硬件、软件与参数,不能直接迁移成新服务的容量结论。
AWQ 通过激活感知保护关键通道
AWQ 原始论文 描述的是激活感知的仅权重量化。它观察激活分布,识别关键权重通道,并搜索等价的逐通道缩放以降低关键通道的量化误差,而不是保留一个混合精度子集。该方法不依赖反向传播或权重重建。
AWQ 不保证必然比 GPTQ 更快或质量更高。模型结构、分组大小、校准数据、运行时内核与工作负载会共同改变结果。AWQ 术语页 进一步解释其机制和适用边界。
SmoothQuant 把激活难题迁移到权重
SmoothQuant 原始论文 面向 W8A8 推理。它通过等价变换平滑激活异常值,将部分量化难度迁移到权重,使两个矩阵乘操作数更适合整数计算。迁移强度和运行时实现仍然是具体部署中的参数,而不是跨模型常量。
为什么位宽不等于服务显存
理论权重负载只能作为下界:
ideal_weight_bytes = parameter_count * bits_per_weight / 8
真实文件和常驻权重还包含缩放因子、零点、分组元数据、未量化层、Tokenizer、对齐填充和容器开销。服务运行时还会分配 KV Cache、临时激活、CUDA Graph 或编译缓冲区、通信缓冲区、队列与内存碎片。
因此,“7B INT4 只需要 4 GB”不是完整的工程结论。它没有说明 4 GB 指模型文件、加载后权重、空闲进程、单请求峰值,还是满足并发目标的安全容量。LLM 推理工程指南 解释了为什么 TTFT、Token 间延迟、批处理和调度必须联合评测。
低精度不保证推理加速
只有当内存流量或低精度计算收益大于额外开销时,量化路径才会更快。常见失败模式包括:
- 运行时在每次算子执行前把权重反量化到更宽类型;
- 制品打包布局与现有内核不匹配;
- 未支持模块回退到更慢的算子;
- 短 Prompt、小 Batch 无法摊薄启动与转换开销;
- Decode 受 KV Cache 流量或调度限制,而不是受权重读取限制;
- 启动阶段重打包增加加载耗时和发布抖动。
Prefill 与 Decode 必须分开统计。候选制品可能提高吞吐却恶化首 Token 延迟,也可能减少权重显存却不能提升有效并发。
建立可复现的量化制品契约
量化制品必须能够追溯到不可变基线和完整配方。这份契约应与制品一同保存,而不是只存在于容易丢失的部署工单中。
{
"artifact_id": "support-model-w4a16-awq-r3",
"base_checkpoint": "registry.example/model",
"base_revision": "immutable-revision",
"tokenizer_revision": "immutable-revision",
"chat_template_sha256": "sha256:...",
"quantizer": {
"name": "awq",
"version": "pinned-version",
"weight_bits": 4,
"activation_bits": 16,
"group_size": 128,
"symmetric": false,
"excluded_modules": ["output_head"]
},
"calibration": {
"dataset_id": "approved-calibration-slice",
"dataset_sha256": "sha256:...",
"sampling_policy": "language-and-task-stratified"
},
"packaging": {
"format": "runtime-specific",
"runtime": "pinned-runtime",
"kernel": "verified-kernel-family"
},
"artifact_sha256": "sha256:...",
"license": "verified"
}
校准集需要覆盖生产语言、Prompt 长度、业务领域、工具调用和安全敏感输入。“几百条通用样本就足够”不是普遍规则,样本数量只有与覆盖度和稳定性检验一起记录才有意义。
Tokenizer 与 Chat Template 也必须固定。模板变化可能比量化本身产生更明显的输出差异,从而破坏归因。
用双赛道建立可复现基准
可靠评测需要把算法效果与生态成熟度分开。
同引擎受控赛道
固定基础 Checkpoint、Revision、Tokenizer、Chat Template、校准切片、评测集、服务版本、硬件、请求调度与测量代码。在可行时只改变量化配方。它回答:在受控条件下,量化选择究竟改变了什么?
最佳生态赛道
允许每个候选使用其最成熟的运行时和内核,但保持模型身份、工作负载、质量门禁和硬件等级可比。它回答:每套可部署技术栈在生产形态下能够交付什么?
不能把不同引擎、Batch、Prompt 长度与输出长度下的 tokens/s 放在同一张表中,随后宣布算法排名。
| 维度 | 必须保留的证据 |
|---|---|
| 业务质量 | 业务验收率或任务分数,并给出置信区间 |
| 行为完整性 | 工具调用有效率、JSON/Schema 有效率、拒答和安全切片 |
| 上下文 | 短、中位与尾部 Prompt,长上下文检索或推理 |
| 延迟 | TTFT、ITL 或 TPOT、端到端延迟分位数 |
| 容量 | 峰值显存、稳定并发、排队时间、OOM 率 |
| 效率 | 相同服务目标下的吞吐和 Goodput |
| 运维 | 启动时间、制品来源、回滚耗时、错误率 |
Goodput 只统计同时满足质量和延迟目标的请求,可以避免“吞吐很高但结果不可用”的候选获胜。
可运行的量化候选门禁
下面的 Go 程序不执行量化,而是验证已经测量的候选,防止团队仅凭位宽或吞吐决定晋级。它只依赖标准库。
package main
import (
"encoding/json"
"fmt"
"os"
"sort"
"strings"
)
type MetricRange struct {
Lower float64
Upper *float64
}
func upper(value float64) *float64 {
return &value
}
var requiredMetrics = map[string]MetricRange{
"business_acceptance": {Lower: 0, Upper: upper(1)},
"structured_output_validity": {Lower: 0, Upper: upper(1)},
"safety_pass_rate": {Lower: 0, Upper: upper(1)},
"ttft_p95_ms": {Lower: 0},
"itl_p95_ms": {Lower: 0},
"peak_memory_gib": {Lower: 0},
"goodput_rps": {Lower: 0},
}
type Gate struct {
Op string `json:"op"`
Value float64 `json:"value"`
}
type Candidate struct {
ArtifactID string `json:"artifact_id"`
BaseRevision string `json:"base_revision"`
ArtifactSHA256 string `json:"artifact_sha256"`
Metrics map[string]float64 `json:"metrics"`
Gates map[string]Gate `json:"gates"`
}
func validate(candidate Candidate) []string {
var errors []string
if strings.TrimSpace(candidate.ArtifactID) == "" {
errors = append(errors, "artifact_id must be a non-empty string")
}
if strings.TrimSpace(candidate.BaseRevision) == "" {
errors = append(errors, "base_revision must be a non-empty string")
}
if strings.TrimSpace(candidate.ArtifactSHA256) == "" {
errors = append(errors, "artifact_sha256 must be a non-empty string")
}
if candidate.Metrics == nil || candidate.Gates == nil {
errors = append(errors, "metrics and gates must be objects")
return errors
}
for name, bounds := range requiredMetrics {
value, ok := candidate.Metrics[name]
if !ok || value < bounds.Lower ||
bounds.Upper != nil && value > *bounds.Upper {
errors = append(errors, "invalid metric: "+name)
}
}
for name, rule := range candidate.Gates {
if _, ok := requiredMetrics[name]; !ok {
errors = append(errors, "unknown gate: "+name)
continue
}
if rule.Op != "min" && rule.Op != "max" {
errors = append(errors, "invalid gate: "+name)
continue
}
measured, ok := candidate.Metrics[name]
if !ok {
continue
}
passed := rule.Op == "min" && measured >= rule.Value ||
rule.Op == "max" && measured <= rule.Value
if !passed {
errors = append(errors, fmt.Sprintf(
"gate failed: %s measured=%g %s=%g",
name, measured, rule.Op, rule.Value,
))
}
}
sort.Strings(errors)
return errors
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "usage: quantization_gate candidate.json")
os.Exit(2)
}
data, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
var candidate Candidate
if err := json.Unmarshal(data, &candidate); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
if errors := validate(candidate); len(errors) > 0 {
for _, message := range errors {
fmt.Fprintln(os.Stderr, "ERROR:", message)
}
os.Exit(1)
}
fmt.Println("candidate passed:", candidate.ArtifactID)
}
保存为 quantization_gate.py,再检查一份真实测量结果:
go run quantization_gate.go candidate.json
输入应来自固定测试条件,而不是估算。以下数值只用于展示数据结构,不代表任何模型或算法的性能:
{
"artifact_id": "support-model-w4a16-awq-r3",
"base_revision": "immutable-revision",
"artifact_sha256": "sha256:...",
"metrics": {
"business_acceptance": 0.94,
"structured_output_validity": 0.99,
"safety_pass_rate": 0.98,
"ttft_p95_ms": 420,
"itl_p95_ms": 38,
"peak_memory_gib": 18.6,
"goodput_rps": 7.4
},
"gates": {
"business_acceptance": {"op": "min", "value": 0.92},
"structured_output_validity": {"op": "min", "value": 0.98},
"safety_pass_rate": {"op": "min", "value": 0.97},
"ttft_p95_ms": {"op": "max", "value": 500},
"itl_p95_ms": {"op": "max", "value": 45},
"peak_memory_gib": {"op": "max", "value": 20},
"goodput_rps": {"op": "min", "value": 6.5}
}
}
灰度晋级与回滚
量化模型晋级应遵循模型和服务变更的同等门禁。离线基准通过后,不能直接替换全部基线实例。
- 检查 Manifest 完整性和制品校验和。
- 使用精确基线完成离线质量与性能门禁。
- 使用代表性影子流量验证,不向用户返回候选输出。
- 按租户或工作负载灰度,避免不可追踪的随机制品切换。
- 监控质量代理指标、工具错误、Schema 失败、延迟、显存和 Goodput。
- 任一硬门禁失败时回滚到固定基线。
灰度期间必须保留与候选容量兼容的基线。需要重新构建或临时下载旧制品的流程不是真正可执行的回滚。
常见失败模式
用固定排行榜选型
“AWQ 适合速度、GPTQ 适合精度、GGUF 适合 CPU”不是长期有效的规则。它隐藏了模型支持、内核成熟度、制品来源和工作负载差异。
只测困惑度
困惑度可以暴露部分分布变化,却不能证明工具调用正确、结构化输出有效、多语言行为稳定、拒答策略一致或业务任务通过。
忽略校准数据治理
未版本化的校准数据会让制品无法复现。直接使用敏感生产 Prompt 还会引入隐私和授权风险。应记录数据血缘,并使用获批且具有代表性的切片。
比较不同模型 Revision
如果候选来自不同 Checkpoint、Tokenizer 或 Chat Template,就无法隔离量化影响。所有组件都应固定后再做质量归因。
把成功加载当成兼容
运行时能够加载制品,不代表它使用了预期内核。它可能重打包、对部分层回退,甚至走低效路径。必须检查日志和 Profile,再测量完整请求链路。
常见问题
INT8 一定比 INT4 更适合生产吗?
不一定。更宽的表示通常有更多质量余量,但生产适配是测量结果,不是位宽标签。拥有成熟内核并通过业务门禁的 INT4 制品,可能优于存在算子回退或容量不足的 INT8 路径。两者必须接受相同门禁。
量化一定能降低云端推理成本吗?
不一定。只有显存节省转化为有效并发,或低精度内核提高 Goodput 时,成本才可能下降。如果系统受队列、KV Cache 或低效内核限制,成本可能不变。应计算满足 SLO 的每个已验收请求成本,而不是原始 Token 成本。
QAT 一定比 PTQ 更准确吗?
不一定。量化感知训练允许优化器适应低精度,但结果取决于训练数据、目标函数、稳定性和最终部署格式。强 PTQ 方法可能已经足够,而控制不当的 QAT 也可能导致行为回归。必须比较最终制品。
同一个量化制品能在所有运行时中执行吗?
不能。容器、张量编码、打包布局、算子和内核都可能依赖运行时。应核对当前官方兼容文档、固定版本、记录所有外部插件,并验证实际内核与回退路径。插件已注册或制品成功加载,都不能证明执行高效。
微调前还是微调后量化?
取决于工作流。QLoRA 在训练 Adapter 时加载量化且冻结的基础模型,这与生成最终推理制品不是同一件事。Adapter 合并或组合后,仍需对实际服务制品重新评测与打包。可结合 LoRA 术语 与 本地大模型部署指南 理解完整链路。
总结
大模型量化是一项端到端推理工程决策,不是固定压缩比例。先分清量化对象、数值表示、算法、容器和运行时,再保存完整制品契约。用同引擎受控赛道与最佳生态赛道评测候选,并把业务质量、安全、延迟、显存和 Goodput 设为晋级门禁,才能把一个低位宽文件转化为可部署、可审计、可回滚的模型制品。