<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>模型量化 &#8211; Blog of Code</title>
	<atom:link href="https://www.cztcode.com/tag/%E6%A8%A1%E5%9E%8B%E9%87%8F%E5%8C%96/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.cztcode.com</link>
	<description>模型推理加速与编程实践</description>
	<lastBuildDate>Sat, 05 Sep 2026 02:29:52 +0000</lastBuildDate>
	<language>zh-Hans</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://www.cztcode.com/wp-content/uploads/2024/02/cropped-logo-32x32.webp</url>
	<title>模型量化 &#8211; Blog of Code</title>
	<link>https://www.cztcode.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">217219486</site>	<item>
		<title>4bit 量化为什么不一定更快：权重变小之后，瓶颈去了哪里</title>
		<link>https://www.cztcode.com/2026/5358/</link>
					<comments>https://www.cztcode.com/2026/5358/#respond</comments>
		
		<dc:creator><![CDATA[Jellow]]></dc:creator>
		<pubDate>Sat, 05 Sep 2026 02:29:52 +0000</pubDate>
				<category><![CDATA[模型推理加速]]></category>
		<category><![CDATA[KV Cache]]></category>
		<category><![CDATA[性能分析]]></category>
		<category><![CDATA[推理加速]]></category>
		<category><![CDATA[模型量化]]></category>
		<guid isPermaLink="false">https://www.cztcode.com/?p=5358</guid>

					<description><![CDATA[4bit 权重量化省下了显存，为什么生成速度没有同比提升？从权重与 KV 读取量出发，推导不同批次下的收益边界，用可运行代码区分访存、计算和反量化开销，再设计固定负载与容量两组对照实验。]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><blockquote>
<p><strong>Jellow 编辑发布，AI 辅助生成并完成技术核对。</strong> 计算示例和实测结果会在文中分别说明。</p>
</blockquote>
<p>把 BF16 模型换成 4bit，显存占用明显降了，生成速度却只涨了一点。该怀疑量化方案，还是推理框架？</p>
<p>先别急着换框架。模型文件的大小、一次前向需要搬运的数据量、GPU 完成这些工作的时间，是三件不同的事。量化直接改变其中一部分，剩下的部分可能很快成为主要开销。</p>
<p>这篇文章只讨论稠密自回归模型的权重量化，重点是 W4A16 的 Decode 阶段。这里 W 表示权重精度，A 表示激活精度；不把原生低精度计算、KV Cache 量化、MoE 或跨卡通信混到同一个加速数字里。Prefill 与 Decode 的基本区别，可以先看<a href="https://www.cztcode.com/2026/5348/">系列第一篇</a>。</p>
<h2>4bit 描述的是哪一段数据</h2>
<p>W4A16 的含义不是“整个模型用 4bit 计算”。以 TensorRT-LLM 文档中的 weight-only 路径为例，权重以低位宽存储，线性层使用时进行反量化，激活仍为 FP16 或 BF16。<a href="https://nvidia.github.io/TensorRT-LLM/reference/precision.html" target="_blank" rel="noopener">官方精度说明</a>把 W4A16 与其他精度方案分开列出。</p>
<p>只算权重存储，也未必能从 16bit 精确降到四分之一。假设原权重占 W 字节，其中比例 f 被量化；每 G 个被量化的权重共用一个 16bit scale，暂不考虑 zero-point、对齐和其他元数据，那么：</p>
<pre><code class="language-text">量化后权重 / 原权重 ≈ (1 - f) + f / 4 + f / G</code></pre>
<p>最后一项来自 scale：原来每个权重是 2 字节，一组 G 个权重额外保存 2 字节，所以相对于原权重的比例是 <code>f / G</code>。</p>
<p>取 <code>f = 0.9，G = 128</code>，结果约为 0.332。这个假想格式的压缩比约 3.01 倍，尚未计入工作区、激活、KV Cache 和分配器预留。它说明为什么“4bit 模型”不足以推算进程总显存；实际数字要看格式和加载结果。</p>
<h2>Decode 每一步，还要读什么</h2>
<p>考虑一个简化场景：单卡、稠密模型、完整因果注意力，一个 Decode 批次包含 B 条序列，每条生成一个 token。忽略片上缓存复用差异，假设批内共享一次权重读取，历史 KV 各读一次。</p>
<p>这一步的主要读取量可以粗估为：</p>
<pre><code class="language-text">读取量 ≈ W + B × K</code></pre>
<p>W 是整次前向需要读取的权重字节数；K 是一条序列当前历史 KV 的总字节数。式子省略了激活读写、新 KV 写入、scale 读取以及其他算子流量，因此只是定位瓶颈的模型，不能直接当作 profiler 结果。</p>
<p>对使用常规 K、V 布局的完整注意力，单条序列每个历史 token 的 KV 大小为：</p>
<pre><code class="language-text">c = 2 × L × Hkv × D × bytes_per_element
K = S × c</code></pre>
<p>其中 2 对应 K 和 V，L 为层数，Hkv 为 KV 头数，D 为头维度，S 为上下文长度。假设 <code>L=32、Hkv=8、D=128</code>，KV 使用 2 字节精度，则 <code>c=128 KiB/token</code>。当 <code>S=8192</code> 时，每条序列的历史 KV 就有 1 GiB。</p>
<p>这一步没有改变 KV 精度。权重量化之后，这 1 GiB 仍然在。</p>
<p>下面另设一组便于心算的权重数字：BF16 路径每步读取 14 GiB，量化路径连同相关元数据读取 4 GiB。<strong>这两个数是独立的假设输入，不是上面 f、G 示例的测量结果。</strong> 假设两条路径的有效带宽均为 1 TiB/s，暂时忽略计算和额外开销：</p>
<table>
<thead>
<tr>
<th>Decode 批次 B</th>
<th style="text-align: right">BF16 读取时间</th>
<th style="text-align: right">量化读取时间</th>
<th style="text-align: right">仅按读取估计的加速比</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td style="text-align: right">14.65 ms</td>
<td style="text-align: right">4.88 ms</td>
<td style="text-align: right">3.00×</td>
</tr>
<tr>
<td>8</td>
<td style="text-align: right">21.48 ms</td>
<td style="text-align: right">11.72 ms</td>
<td style="text-align: right">1.83×</td>
</tr>
<tr>
<td>32</td>
<td style="text-align: right">44.92 ms</td>
<td style="text-align: right">35.16 ms</td>
<td style="text-align: right">1.28×</td>
</tr>
</tbody>
</table>
<p>所有数字都是计算示例。两条路径各有相同的 B，不是在拿单请求和高并发互相比。</p>
<p>加速比来自：</p>
<pre><code class="language-text">Sread = (W16 + B × K) / (W4 + B × K)</code></pre>
<p>当 <code>B × K</code> 越来越大，分子和分母里相同的部分占比升高，加速比便趋近 1。反过来，如果上下文很短、批次很小，权重流量占主导，权重量化就更值得优先验证。</p>
<p>还有一个容易误读的地方：B 增加时，表中的一步耗时也增加，但一步产出了 B 个 token。系统吞吐应按 <code>B / step_time</code> 算，不能只看一步越来越慢就认定总吞吐下降；同样，总吞吐增加也不意味着每个用户的输出间隔更短。</p>
<h2>把反量化和计算放回去</h2>
<p>TensorRT 的 weight-only 实现会读取 4bit 权重，再反量化并做高精度点积。低位宽存储因此不自动等于同倍数的算术加速。<a href="https://docs.nvidia.com/deeplearning/tensorrt/11.2.1/inference-library/quantized-types-explicit-quantization.html#weight-only-quantization" target="_blank" rel="noopener">TensorRT 11.2.1 的实现说明</a>直接描述了这条执行路径。</p>
<p>用一个粗略的 roofline 视角，可以把单步时间写成：</p>
<pre><code class="language-text">Tstep ≳ max(实际搬运字节 / 有效带宽, 实际运算量 / 有效算力)</code></pre>
<p>这不是把整个模型当成一个能完美重叠的大算子。真实前向由许多有依赖的算子组成，还要考虑调度、同步和启动开销。它的用处是提醒我们：降低字节数，只压低了其中一个约束。</p>
<p>反量化也不能机械地当成一段独立时间加上去。有的实现把它融合到矩阵乘里，与访存或计算部分重叠；有的形状却会因解包、寄存器压力或低效内核丢掉收益。应该看实际执行的 kernel 和时间分布。</p>
<p>为了估算余量，可以暂时再作一个更强的假设：两条路径都只受带宽限制，而且量化路径多出一段<strong>无法重叠的额外耗时 Δ</strong>。那么量化更快的条件是：</p>
<pre><code class="language-text">Δ &lt; (W16 - W4) / BW</code></pre>
<p>在上表的假设下，右边约为 9.77 ms。超过它，少搬权重省出的时间就被抵消。这个阈值与 B 无关，是因为模型假设两条路径的 KV 流量、带宽完全相同；实际内核一变，这个简化条件也要重算。</p>
<h2>一个可以自己改参数的计算器</h2>
<p>下面的 Python 只依赖标准库，保存后直接运行。它不调用 GPU，也不预测某张显卡的性能。<code>compute_floor_ms</code> 和 <code>extra_ms</code> 是留给读者探索边界的假设参数，不能不经测量就填成“真实开销”。</p>
<pre><code class="language-python">GiB = 2**30
TiB = 2**40

def step_ms(weight_gib, batch, kv_gib=1.0, bandwidth=TiB,
            compute_floor_ms=0.0, extra_ms=0.0):
    read_ms = (weight_gib + batch * kv_gib) * GiB / bandwidth * 1000
    return max(read_ms, compute_floor_ms) + extra_ms

print("B  bf16_ms  w4_ms  speedup")
for batch in (1, 8, 32):
    bf16 = step_ms(14, batch)
    w4 = step_ms(4, batch)
    print(f"{batch:2d} {bf16:8.2f} {w4:6.2f} {bf16 / w4:7.2f}")

# 人为加入 12 ms 无法重叠的额外开销，构造更慢的反例。
print("extra_12ms_speedup:", round(step_ms(14, 1) /
      step_ms(4, 1, extra_ms=12), 3))

# 同样的 50 ms 计算约束主导两条路径：搬运减少也不改变结果。
print("compute_bound_speedup:", step_ms(14, 1, compute_floor_ms=50) /
      step_ms(4, 1, compute_floor_ms=50))</code></pre>
<p>最后两行分别得到约 <code>0.868</code> 和 <code>1.0</code>。前者是额外开销吃掉收益的反例，后者是计算约束盖住访存收益的反例。这两种情况都不需要“量化失败”才能发生。</p>
<p>长输入 Prefill 应单独测量。一次处理更多 token 会改变矩阵形状和权重复用程度；Decode 上得到的比例，没有理由原样搬到 Prefill。</p>
<h2>实际选型时，把两场实验分开做</h2>
<p>第一场实验固定工作负载，回答“相同工作是不是更快”。使用同一模型来源、tokenizer、输入 token、输出长度设置和 KV 精度，在同一 GPU 上比较 BF16 与 W4A16。保持推理框架、注意力实现等尽可能一致，记录无法一致的部分；固定一个可容纳的批次后，再分别测短、长上下文。单独记录 Prefill、Decode、端到端延迟，以及真正执行的 kernel。</p>
<p>第二场实验允许利用量化省下的显存，回答“同一硬件能服务多少人”。逐步增加负载，比较满足首 token 延迟和输出间隔要求的吞吐。此时更高吞吐可能来自容纳了更多 KV，而不是每个请求本身更快。报告里应把这个原因写出来。</p>
<p>两场实验都需要先过质量门槛。位宽和误差之间没有一个适用于所有模型、所有任务的固定换算。<a href="https://arxiv.org/abs/2306.00978" target="_blank" rel="noopener">AWQ 论文</a>利用激活统计识别重要通道并调整缩放，说明权重如何量化同样关键；论文中的效果不能替代你自己的任务检查。</p>
<p>做代码生成，就检查可执行正确率；做结构化提取，就检查字段准确率和格式通过率。测试数据与量化校准数据分开，尤其保留真实业务中较长、较难的输入。若质量不达标，先比较更合适的校准方式或更高精度方案，再讨论吞吐。</p>
<p>当结果不符合预期，可以按观察到的现象缩小范围：</p>
<table>
<thead>
<tr>
<th>现象</th>
<th>下一步检查</th>
</tr>
</thead>
<tbody>
<tr>
<td>短上下文、小批次也更慢</td>
<td>是否使用预期的量化 kernel；是否存在解包、转换或回退路径</td>
</tr>
<tr>
<td>短上下文有效，长上下文收益缩小</td>
<td>实际 KV 流量和注意力耗时是否已占主要部分</td>
</tr>
<tr>
<td>显存降了，固定批次速度接近</td>
<td>原瓶颈是否在计算、启动或其他未被量化的操作</td>
</tr>
<tr>
<td>高并发吞吐更高，输出间隔变大</td>
<td>是否只是批次增大；延迟约束下的有效吞吐是否仍有收益</td>
</tr>
</tbody>
</table>
<p>量化值得做的理由可以是省显存、降低单请求延迟，也可以是在同样延迟要求下多接请求。先把希望得到的收益说清楚，后面的测试才不会被一个漂亮的“4bit”标签带偏。</p>
<hr />
<p>技术资料核对日期：2026-09-05。本文代码用于验证计算关系，未进行 GPU 性能实测；运行时请以所用版本的文档和 profiler 结果为准。</p>
<p><a href="https://www.cztcode.com/llm-inference-acceleration/">模型推理加速专题</a> · <a href="https://www.cztcode.com/ai-content-policy/">内容生成、审核与纠错说明</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.cztcode.com/2026/5358/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5358</post-id>	</item>
	</channel>
</rss>
