在 Hugging Face 找一个模型,经常会撞见这样的名字:

Qwen-...-GPTQ-Int4
Qwen-...-AWQ

点进去,权重文件却都叫 model-00001-of-00004.safetensors。启动时又有人用:

vllm serve <model>

也有人用:

python -m sglang.launch_server --model-path <model>

五个名字挤在一条链路里,很容易被误认为五种互相竞争的“模型格式”。其实它们分属三个楼层:

量化算法层怎样把 W 近似成 ŴGPTQAWQ张量容器层怎样把张量装进文件*.safetensors推理引擎层怎样加载、调度、执行vLLMSGLang低比特张量 + 量化配置读取分片 + 选择内核不是五选一:算法决定“怎么压”,容器决定“怎么装”,引擎决定“怎么跑”
五个名字的三层地图:GPTQ / AWQ 决定怎样近似权重,safetensors 决定怎样装进文件,vLLM / SGLang 决定怎样加载、调度并执行请求
  • GPTQ、AWQ 是量化算法:决定“哪些小数变成哪些低比特整数,怎样把误差压小”;
  • safetensors 是张量容器:决定“张量如何安全、快速地装进文件”;
  • vLLM、SGLang 是推理引擎:决定“请求怎么排队、KV Cache 怎么放、调用哪个 GPU 内核”。

一句话先把全文串起来:

原始 BF16 权重经过 GPTQ 或 AWQ 离线量化,作为若干张打包后的张量存进 safetensors;vLLM 或 SGLang 读取配置与张量,选中匹配的量化内核,再把很多用户的请求一起送上 GPU。

下面从这条流水线最底层的问题讲起:为什么要把 16 bit 压成 4 bit?

一、量化首先是在救显存带宽

1.1 7B 模型的第一本账

一个 7B 模型若把全部参数存成 BF16,每个参数 2 字节,光权重理论上就要:

70 亿 × 2 字节 ≈ 14 GB

若主体权重压成 4 bit:

70 亿 × 0.5 字节 ≈ 3.5 GB

实际文件会再多出 scale、zero-point、索引和未量化层,所以不会刚好等于 3.5 GB;但“接近四分之一”已经足以让原本塞不进显存的模型跑起来。

更微妙的是速度。自回归解码每生成一个 token,模型都要把大量权重从显存搬到计算单元。batch 较小时,GPU 往往不是算不动,而是权重搬得不够快。权重缩到四分之一,同样的显存带宽便能喂更多次矩阵乘法。

1.2 W4A16:仓库里住 4 bit,计算时仍用 16 bit

常见 GPTQ / AWQ checkpoint 属于 weight-only quantization(仅权重量化),通常简写成 W4A16:

  • W4:权重以 4 bit 整数码打包存储;
  • A16:激活仍是 FP16 或 BF16;
  • 乘法前或乘法过程中,用 scale 把 4 bit 码解释回较高精度。

它并不是先把整个权重矩阵还原成 FP16,再额外存一份。高性能内核会把“读取压缩权重 → 解包 / 反量化 → 矩阵乘法”融合起来,让还原值尽量只活在寄存器或片上缓存里。

GPU 显存打包 INT4 权重码8 个码只占 4 字节scale / zero-point按 group 共用融合 GPU 内核解包 4 bit 整数码寄存器内反量化与 BF16 激活乘加输出BF16 / FP16下一层激活不会把一整份 FP16 权重写回显存:压缩带来的带宽收益才得以保留
W4A16 推理:显存中读取紧凑的 INT4 码与 scale,在融合内核中边解包边乘 BF16 激活,不在显存里展开出一整份 FP16 权重

这也解释了为什么 4 bit 不保证更快:

  • 小 batch 解码常受显存带宽限制,少搬权重通常很赚;
  • 大 batch 或长 prompt 的 prefill 更可能受计算限制,反量化开销可能抵消收益;
  • GPU 是否有匹配的 Marlin、ExLlama、Triton 或厂商内核,往往比后缀名本身更重要;
  • group size、zero-point、act-order、模型形状和硬件代际都可能改变内核选择。

所以量化的确定收益是“权重更小”;速度收益则必须在目标硬件和真实流量上测。

1.3 从“还原权重”换成“保住输出”

一层线性变换可以写成:

y = W x

最朴素的量化只关心每个权重自身的误差:

让 Ŵ 尽量接近 W

但模型真正使用的是输出 Wx。某个权重误差看起来很大,若对应输入几乎总是 0,实际影响可能很小;另一个误差虽小,若总被巨大激活放大,反而很危险。

GPTQ 与 AWQ 的共同进步,就是把校准数据里的激活 x 拉进视野:

让 Ŵx 尽量接近 Wx

两者的路线却完全不同。GPTQ 像一位会“挪账”的会计:量化一个位置后,把它造成的输出误差补偿到尚未量化的位置;AWQ 像一位提前调增益的录音师:先找出经常承受大信号的通道,重新缩放后再量化。

二、GPTQ:量化一列,补偿后面的列

GPTQ 全名是 GPT Quantization。它来自 2022 年的论文 GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers,是一种 PTQ(post-training quantization,训练后量化):不重新训练模型,只用一小批校准样本观察各层输入。

2.1 校准数据到底在做什么

让若干段文本跑过原模型,把某一层看到的输入激活收集成矩阵 X。这层量化的目标可直观写成:

最小化 || W X - Ŵ X ||²

注意它不是要求量化权重 Ŵ 在几何上最像 W,而是要求二者喂入典型输入 X 后,输出尽量相同。

输入中哪些方向经常出现、幅度多大、彼此怎样相关,可以由近似二阶信息描述。GPTQ 延续 Optimal Brain Quantization 的思路,用由 XXᵀ 得到的 Hessian 近似及其逆,估计“改动一个权重后,剩余权重怎样调整最划算”。

不必被 Hessian 吓到。把一行权重想成四位合奏者:

原输出 = w₁x₁ + w₂x₂ + w₃x₃ + w₄x₄

把 w₁ 四舍五入成 4 bit 后,第一位跑调了。若 x₁ 与 x₂ 在校准数据里经常一起变化,就可以稍微调整尚未量化的 w₂,让整段合奏的输出重新靠近原版。GPTQ 做的就是一套高效、逐步的“跑调补偿”。

STEP 1观察校准激活 X哪些输入大?哪些列经常一起变?H ≈ XXᵀSTEP 2量化当前列w₁w₂w₃w₄舍入产生误差 e当前列从此固定STEP 3把误差挪给后面q₁w₂′w₃′w₄′按 H⁻¹ 调整未量化列然后轮到下一列目标不是每个权重各自最像,而是让整层输出 ŴX 尽量接近 WX
GPTQ 的三步:用校准激活估计输入相关性,量化当前列产生误差,再把误差按二阶信息分摊到尚未量化的列

2.2 为什么要逐列处理

对一层矩阵,GPTQ 大致重复:

  1. 选择当前要量化的一列(实现中会分 block 处理);
  2. 把浮点权重舍入到有限的量化刻度;
  3. 计算这次舍入造成的误差;
  4. 根据逆 Hessian 的相关项,更新后面尚未量化的权重;
  5. 继续下一列。

这比“所有权重各自就近取整”更慢,却仍是一次性的离线工作。量化结果保存后,线上推理不再重复 Hessian 计算。

常见配置里的几个黑话也有了落点:

字段直观含义常见取舍
bits主体整数码位宽4 bit 最常见
group_size多少个权重共用一组 scale / zero-point128 常见;更小通常更准但元数据更多
sym是否采用对称量化会影响误差与内核兼容性
desc_act / act-order是否优先处理激活较大的列质量可能更稳,但布局与内核支持要核对
damp_percent给 Hessian 对角线加多少阻尼防止数值不稳定

2.3 GPTQ 的强项与代价

GPTQ 的优点是目标直接:每层都在努力保住真实校准输入下的输出,并用二阶信息系统地补偿误差。代价也来自同一处:

  • 量化过程比简单取整更重;
  • 结果可能对校准数据和配置敏感;
  • checkpoint 的具体打包格式、对称性和 act-order 会影响推理内核兼容性;
  • “GPTQ”只说明算法家族,不保证两个仓库生成的文件能被同一套旧内核无差别读取。

工具生态也在变化。当前 Hugging Face Transformers 文档已经把 GPTQModel 列为活跃后端,并明确说明 AutoGPTQ 不再受支持。这里退场的是某个实现,不是 GPTQ 这个算法。

三、AWQ:先保护大激活经过的通道

AWQ 全名是 Activation-aware Weight Quantization,来自 2023 年论文 AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration。

它的出发点是一条观察:权重的重要性不能只看权重绝对值,还要看输入激活。论文发现,只需重点保护大约 1% 的显著权重,就能明显降低量化误差;而找它们时,看激活分布比只看权重更可靠。

3.1 大激活为什么会放大量化误差

仍看一项乘法:

yⱼ = wⱼ xⱼ

若量化把 wⱼ 变成 wⱼ + Δwⱼ,输出误差就是:

Δyⱼ = Δwⱼ xⱼ

同样是 0.02 的权重误差:

xⱼ = 0.1   → 输出误差 0.002
xⱼ = 100   → 输出误差 2

第二条通道显然更值得保护。AWQ 先跑少量校准样本,统计各输入通道的激活幅度,再搜索一组合适的 per-channel scale。

3.2 不用混合精度,也能“偏心”重要通道

最直觉的保护方式,是把重要权重留成 FP16、其余权重压成 INT4。但这种零散混合精度会破坏规则矩阵布局,GPU 很难高效执行。

AWQ 的妙处是做一个数学等价的缩放。若用行向量写线性层:

y = x W = (x / s) · (s W)

这里 s 按输入通道缩放:把显著通道对应的权重先放大,再量化;同时把输入通道反向缩小,所以未量化时的函数完全不变。放大后的重要权重占据更多量化刻度,相对舍入误差更小。

OBSERVE看激活,不只看权重x₁x₂x₃x₄x₂ 经常很大 → 通道 2 显著RESCALE做等价通道缩放W₂ ← s · W₂重要权重先放大×x₂ ← x₂ / sxW = (x/s)(sW)QUANTIZE再落到统一 4 bit 网格放大后:相对舍入误差小普通通道:照常量化保持规则矩阵,不散落 FP16 小岛AWQ 的“保护”来自缩放后的误差分配,不是把显著权重永久留成混合精度
AWQ 的等价缩放:激活越显著的通道越值得保护;权重乘 s、输入除 s 后函数不变,但重要权重在量化网格上获得更细的相对分辨率

关键是:AWQ 论文里的“保护 1% 显著权重”不等于最终文件里真的散落着 1% FP16 权重。它通过通道缩放实现保护,最终仍可保持规则的低比特矩阵和硬件友好的内核。

AWQ 不做反向传播,也不逐层重建输出;它搜索缩放因子后再进行 weight-only 量化。和 GPTQ 相比,思路更轻、更贴近硬件布局,但“更轻”不等于任何模型上都必然更准。

3.3 GPTQ 与 AWQ,到底选谁

GPTQAWQ
看什么层输入的相关性与近似二阶信息各输入通道的激活显著性
核心动作量化当前列,再补偿未量化列等价缩放重要通道,再统一量化
是否训练否,训练后量化否,训练后量化
是否需要校准数据需要需要
常见部署形态W4A16 / W3A16 等以 W4A16 最常见
工程风险act-order、对称性、打包格式与内核要匹配GEMM / GEMV 版本、group size 与内核要匹配

实际选择顺序不该是“谁的论文更新”,而应是:

  1. 目标模型有没有可信的现成量化版本;
  2. 目标 GPU 和引擎有没有成熟内核;
  3. 你的 batch、prompt 长度与并发量下谁更快;
  4. 你的真实任务上谁的质量损失更小。

还有一条容易踩坑的现状:AutoAWQ 仓库已经归档,但 AWQ 算法没有消失。 原仓库说明其能力已由 vLLM 项目的 llm-compressor 等后续工具承接。下载旧教程时,要分清“算法名”和“某个曾经流行的 Python 包名”。

四、safetensors:它只负责装,不负责量

现在回答开头的问题:为什么 GPTQ 与 AWQ 模型的文件都可以叫 .safetensors?

因为 safetensors 根本不规定张量是“怎么量化出来的”。它只规定张量的名字、dtype、shape、偏移和原始字节怎样安全地写进文件。

4.1 一个 safetensors 文件只有三段

官方格式非常克制:

  1. 前 8 字节:一个小端序无符号整数 N,表示 header 长度;
  2. 接下来 N 字节:UTF-8 JSON header;
  3. 剩余部分:连续的张量字节缓冲区。

header 为每个张量记录:

{
  "model.layers.0.self_attn.q_proj.weight": {
    "dtype": "BF16",
    "shape": [4096, 4096],
    "data_offsets": [0, 33554432]
  }
}
8 bytesNheader 长度N bytesJSON headername / dtype / shape / offsetsrest of file连续 tensor byte-buffer完整覆盖、无重叠、无空洞config.json模型结构quantization_config怎样解释 qweight*.index.json张量在哪个分片tokenizer / template怎样把文本变 tokensafetensors 管“张量怎么装”;下面这些外部文件共同构成可运行模型仓库安全边界也只覆盖权重容器:trust_remote_code 仍是另一回事
safetensors 解剖:8 字节 header 长度、可读的 JSON 张量目录、连续且无空洞的数据区;模型配置和 tokenizer 仍是外部文件

它比 pickle 安全,是因为文件是数据而不是可执行的 Python 对象图,加载权重时不会顺手执行任意 pickle 代码。连续数据与明确偏移也便于 mmap、按需读取与分布式切片。

但“safe”不能无限外推:

  • safetensors 只保护这个权重容器的反序列化路径;
  • config.json、tokenizer、聊天模板仍在外部;
  • 若启动时开启 trust_remote_code=True,远程模型代码仍是另一条安全边界;
  • 文件安全不等于模型输出安全,也不等于仓库里的所有文件都可信。

4.2 4 bit 到底怎样塞进 safetensors

传统张量 API 对半字节并不总有自然表示。常见 GPTQ / AWQ checkpoint 会把若干个 4 bit 码打包进 INT32 容器张量,同时另存:

qweight   打包后的低比特权重
qzeros    打包后的 zero-point(若使用)
scales    每组缩放因子
g_idx     分组 / act-order 相关索引(部分 GPTQ 格式)

safetensors 忠实保存这些张量,却不知道 qweight 应该怎样解包。真正的说明书通常在 config.json:

{
  "quantization_config": {
    "quant_method": "awq",
    "bits": 4,
    "group_size": 128,
    "zero_point": true,
    "version": "gemm"
  }
}

因此一个可部署模型仓库不是“几个 safetensors 就够了”,而是一套契约:

权重字节(*.safetensors)
+ 张量分片索引(*.safetensors.index.json)
+ 模型结构(config.json)
+ 量化解释(quantization_config)
+ tokenizer / chat template
+ 推理引擎中匹配的解包与 GEMM 内核

大模型分成多个 safetensors 只是为了下载、上传和并行加载方便。model.safetensors.index.json 会告诉加载器“哪个张量在哪个分片”,并不改变量化算法。

4.3 它与 GGUF 的边界

上一篇 GGUF 讲过,GGUF 更像一只自带模型元数据与 tokenizer 的单文件行李箱,围绕 llama.cpp 的端侧运行方式设计;safetensors 更像通用张量货柜,通常与 Hugging Face 的多个 JSON、tokenizer 文件一起出现。

二者都能装低精度权重,也都避免 pickle 式可执行反序列化,但“都安全、都能装权重”不代表布局和运行生态相同。一个为 CPU / 端侧块量化优化的 GGUF,不能仅靠改扩展名变成 GPU 友好的 AWQ checkpoint。

五、引擎真正加载了什么

到这里,离线工作已经结束。线上启动 vLLM 或 SGLang 时,大致会发生:

123456读配置architecturequant method找分片index.jsonoffsets切张量TP rank 0TP rank 1选内核MarlinTriton / vendor留显存KV blocksworkspace跑请求continuousbatching支持范围 = 模型架构 ∩ 量化变体 ∩ GPU ∩ 并行方式 ∩ 内核“支持 AWQ / GPTQ”只是入口;真正是否走到快内核,要看这五项能否同时对上启动日志里的 kernel / backend 选择,比仓库名后缀更诚实
从仓库到 GPU:引擎读取模型与量化配置,定位 safetensors 分片,按 tensor parallel 切分张量,选择兼容的量化内核,最后进入连续批处理
  1. 读取 config.json,识别 Llama、Qwen、DeepSeek 等架构;
  2. 读取 quantization_config,识别 GPTQ / AWQ、位宽、group size、zero-point 与格式版本;
  3. 根据 tensor parallel 切分规则,只把当前 GPU 负责的张量切片读进来;
  4. 检查当前 GPU、dtype、模型层形状是否有兼容内核;
  5. 必要时把 checkpoint 布局重排成 Marlin 等内核偏好的布局;
  6. 预留 KV Cache 与工作区,启动调度器;
  7. 收到请求后做 continuous batching,让新请求能插入、结束请求能及时退出。

这里最重要的工程事实是:

“引擎声称支持 AWQ”只是第一关;模型架构、量化变体、硬件、并行方式与内核的交集,才是真正的支持范围。

vLLM 当前稳定文档的兼容表就把 AWQ、GPTQ、Marlin 分成不同条目,并按 Volta、Turing、Ampere、Ada、Hopper、Intel GPU 与 CPU 分列。SGLang 当前文档也分别列出 NVIDIA、AMD、Ascend 上的实现路径。版本更新很快,部署前应看你安装版本的文档和启动日志,而不是记住一张永不过期的截图。

六、vLLM 与 SGLang:分页放置和前缀复用不是二选一

两者都是面向 GPU 高吞吐服务的推理引擎,都支持 OpenAI 风格 API、连续批处理、tensor parallel、量化内核、前缀缓存和大量现代模型。今天再把它们概括成“vLLM 只会分页、SGLang 只会前缀缓存”,已经不准确。

但理解它们各自出名的核心抽象,仍然很有用。

6.1 vLLM 的 PagedAttention:先解决 KV Cache 放不下、放不齐

每个请求的 token 数不同,生成还会随时继续。若为每个请求预留一整块“最大长度”的连续 KV Cache,会产生巨大的内部碎片;若频繁申请不同大小的连续显存,又会产生外部碎片。

PagedAttention 借用了操作系统虚拟内存的思路:

  • 把 KV Cache 切成固定 token 数的 block;
  • 每个序列维护逻辑 block 到物理 block 的映射;
  • 物理块不必连续;
  • 序列增长时再分配新块,结束时归还。

这解决的是放置与碎片问题:KV Cache 应该住在哪些物理显存块里。

6.2 SGLang 的 RadixAttention:再解决同一前缀算了又算

很多真实请求共享前缀:

  • 多轮聊天共享旧对话;
  • few-shot 任务共享示例;
  • RAG 请求共享系统提示;
  • agent 的多条分支共享搜索历史;
  • self-consistency 采样共享同一道题。

RadixAttention 把 token 序列作为 key,把对应 KV Cache 作为 value,组织进 radix tree。新请求到来时先做最长前缀匹配,命中的 KV 不必重新 prefill;空间不足时再按缓存策略淘汰叶子。

这解决的是内容复用问题:哪些请求其实已经算过同一段前缀。

PagedAttentionRadixAttention一条序列的 KV 放在哪里?多条请求的哪些 KV 算过了?逻辑 0逻辑 1逻辑 2逻辑 3物理 0物理 1物理 2空闲空闲空闲固定小块、按需分配、物理地址不必连续共同 System Prompt用户 A 历史用户 B 历史新问题 A新问题 B最长前缀匹配:树根的 KV 只算一次一个管“物理放置”,一个管“内容复用”——可以同时存在
PagedAttention 与 RadixAttention 的维度不同:前者把单条序列的 KV 分页放进非连续物理块,后者把多条请求的共同 token 前缀组织成可复用的基数树

两者可以共存。SGLang 的 radix tree 对应的 KV 也放在 paged memory pool 中;vLLM 也提供 Automatic Prefix Caching。如今更合适的理解是:

关注点vLLM 的传统强项SGLang 的传统强项
核心出发点通用、高吞吐、显存高利用率的 LLM 服务复杂 LLM 程序与跨请求前缀复用
代表性抽象PagedAttentionRadixAttention
适合重点验证广泛模型 / 硬件 / 部署生态,常规 API 流量多轮、few-shot、agent、分支采样等高前缀复用流量
今天的现实也有自动前缀缓存、结构化输出等也有分页、连续批处理和广泛模型支持

不要拿不同版本、不同内核、不同 batch 配置的一张吞吐图宣布永久冠军。更换一个模型架构、CUDA 版本、prompt 长度分布或输出长度,结论就可能翻转。

七、权重省下的显存,可能被 KV Cache 吃回去

量化主要压缩静态权重;服务运行时还有 KV Cache、激活、CUDA Graph、通信 buffer 和临时 workspace。

对标准多头 / 分组查询注意力,一条序列的 KV Cache 可粗略估算为:

KV 字节数
= 2                    # K 和 V
 × 层数
 × KV heads
 × head_dim
 × token 数
 × 每元素字节数
 × 并发序列数

假设一个模型有 32 层、8 个 KV heads、head_dim = 128,KV 用 BF16,每条序列长 32k:

2 × 32 × 8 × 128 × 32768 × 2 字节
= 4 GiB / 序列

这已经比 7B 模型的理想 4 bit 权重 3.5 GB 还大。并发再乘上去,KV 很快成为主角。

静态:模型权重BF167B≈ 14 GBW4≈ 3.5 GB*scale未量化层额外开销动态:随流量增长KV Cache8k context≈ 1 GiBKV Cache32k context≈ 4 GiB激活workspaceCUDA Graph也要留示例 KV:2 × 32 层 × 8 KV heads × 128 × 32k tokens × 2 bytes = 4 GiB / 序列* 3.5 GB 是不计 scale、zero-point 与未量化张量的理想下限;并发会继续成倍增加 KV
显存账本:4 bit 主要砍掉静态权重,但长上下文和高并发会让 KV Cache 反超;此外还要给激活、CUDA Graph 和工作区留空间

因此:

  • 模型装不下:权重量化最有效;
  • 长上下文装不下:看 GQA / MLA、KV Cache dtype、分页与上下文长度;
  • 并发上不去:看 KV Cache 容量、调度、prefix cache 命中率;
  • 吞吐不高:还要区分 prefill 与 decode、memory-bound 与 compute-bound。

这也是为什么只比较模型文件大小,无法预测服务能扛多少并发。

八、从下载到上线:一条不容易踩坑的路径

8.1 下载前先读配置,不只看仓库名

至少确认:

architectures
quant_method
bits
group_size
zero_point / sym
version / format
desc_act(GPTQ 常见)

再去目标引擎当前版本的兼容表核对:

  • 模型架构是否支持;
  • GPU compute capability 是否支持;
  • tensor parallel 下是否支持;
  • 量化格式能否走优化内核,还是只能走慢速 fallback;
  • 多模态、LoRA、speculative decoding 等附加特性能否同时开启。

8.2 预量化 checkpoint 通常让引擎自动识别

以官方文档的常规路径为例,仓库中已有正确 quantization_config 时,可以直接启动。vLLM:

vllm serve <org>/<awq-or-gptq-model> \
  --host 0.0.0.0 \
  --port 8000

SGLang:

python -m sglang.launch_server \
  --model-path <org>/<awq-or-gptq-model> \
  --host 0.0.0.0 \
  --port 30000

不要机械照抄旧教程再加一个 --quantization awq。SGLang 当前文档明确区分:

  • offline quantization:加载已经量化的权重,从仓库配置识别;
  • online quantization:加载高精度权重,启动时动态计算 scale,需要显式参数。

两者不是同一件事。vLLM 也会根据 checkpoint 的量化配置选择加载路径;若手动覆盖,必须知道自己覆盖了什么。

8.3 启动成功不等于部署正确

上线前至少做四类验证:

  1. 正确性:固定一组 prompt,对比 BF16 与量化版的任务指标或人工结果;
  2. 容量:测最长 prompt、最长输出和目标并发下是否 OOM;
  3. 延迟:分别看 TTFT(首 token 延迟)与 TPOT / ITL(后续 token 间隔);
  4. 吞吐:在真实到达率和长度分布下测 output tokens/s,而不是只跑 batch=1。

建议把测试分成四格:

短 prompt长 prompt
低并发观察单请求 decode 与量化内核观察 prefill、峰值显存
高并发观察连续批处理与调度观察 KV Cache、prefix reuse、排队延迟

若业务有稳定的 system prompt、多轮会话或 agent 分支,再单独记录 prefix cache hit rate。它可能比 GPTQ 与 AWQ 的差别更影响总体成本。

九、六个常见误解

误解一:.safetensors 就代表 FP16 / BF16

不对。它可以装浮点张量,也可以装打包后的 GPTQ / AWQ 张量。算法写在配置与张量语义里,不写在扩展名里。

误解二:AWQ 比 GPTQ 新,所以一定更好

不对。质量、速度和兼容性都依赖模型、校准数据、配置、内核和硬件。工程上应测目标 workload。

误解三:4 bit 模型的所有数都是 4 bit

不对。常见 W4A16 只压主体权重;激活、部分层、scale、KV Cache 往往仍是 16 bit 或其他精度。

误解四:权重缩四倍,显存一定缩四倍

不对。运行时还有 KV Cache、激活与工作区。上下文足够长时,KV 可以比权重更大。

误解五:量化越小,吞吐一定越高

不对。memory-bound 场景通常受益;compute-bound 场景可能被反量化和不理想内核拖慢。

误解六:PagedAttention 与 RadixAttention 是竞品

不对。一个主要解决物理 KV 块的放置与碎片,一个主要解决共同前缀的识别与复用;现代引擎会同时吸收两类思想。

十、写在最后

现在再看开头那条命令,背后的分工应该清楚了:

GPTQ / AWQ
  决定怎样把高精度权重近似成低比特权重

safetensors + config.json
  决定这些张量怎样存,以及加载器怎样理解它们

vLLM / SGLang + quant kernel
  决定怎样把权重放上 GPU,怎样管理 KV Cache,
  又怎样把许多请求高效地跑完

真正的部署选型从来不是“五选一”,而是每层各选一块彼此咬合的齿轮。

如果只记住一条经验,就记住这句:

先用量化解决“模型能否装下”,再用引擎解决“流量能否跑满”;最后用自己的模型、硬件和请求分布,把质量、延迟、吞吐一起测出来。

参考