返回关卡地图
预判 0 / 3 闯关题:0 / 3 XP 0 / 300

Level 25 | 零基础导学关卡

W8A16 量化

W8A16 Quantization

先通过图解和小例子理解原理,再做预测与练习;不懂的地方可以反复尝试,最后把理解写进官方 Notebook。

本课官方 Notebook ↗
25_Quantization_W8A16.ipynb 量化压缩W8A16Linear
NEW · INTERACTIVE MODEL FACTORY

先进入 Transformer 内部,再回来写量化代码

跟随一个 token 穿过 Embedding、Block、Attention、SwiGLU 和 Linear;一路拆到权重矩阵与单个字节,亲手完成 W8A16 改造。

启动模型工厂 →
Mission 1 概念练习

先定标尺:用 absmax 把浮点范围对齐到 INT8

🎯 先猜一猜

输入 x 是一个全 0 张量,此时 torch.abs(x).max() 也是 0。

如果不做保护就计算 127.0 / absmax,最可能发生什么?

先猜一个答案,再对照下面的讲解。可以随时修改选择,猜错会得到解释。

先补的知识

  • 张量中的每个权重原本是浮点数;量化不是直接改类型,而是先决定浮点数与整数之间的换算比例。
  • 本页采用 per-tensor 对称量化:整个 x 共享一个标量 scale,正负两侧都围绕 0。
  • torch.abs(x) 逐元素取绝对值,torch.max(...) 再从所有元素中找出最大的幅度。
  • 如果 x 全为 0,absmax 也是 0;直接计算 127 / absmax 会产生 Inf,所以必须先做除零保护。

图解原理

把 INT8 的 -127 到 127 想成一把只有 255 个刻度的尺子。absmax 告诉我们原张量最远离 0 的位置,scale 则说明每 1 个浮点单位要放大成多少个整数刻度。先把尺子定好,后面才能舍入和存储。

读范围找到 x 中最大的绝对值 absmax 防除零全 0 时换成很小的正数 定标尺scale = 127 / absmax

变量先翻译成人话

x任意 shape 的浮点权重张量 absmax标量张量,表示最大绝对幅度 scale标量张量,表示浮点值到整数刻度的放大倍数

一个只算标尺的小例子

若权重是 [-2.0, 0.5, 1.0],最大的绝对值是 2.0。

abs(x)
[2.0, 0.5, 1.0]
absmax
2.0
scale
127 / 2

最大幅度 2.0 会被放大到 127;其他数按同一比例缩放,所以相对大小仍被保留。

全 0 是测试里的明确边界

全 0 张量没有可用范围。把 absmax 临时替换成很小的正数后,scale 保持有限,0 乘任何有限 scale 仍是 0。

为什么使用 127

Notebook 要做对称量化,让最大正幅度落在 127。虽然 int8 还能表示 -128,但这里的正负有效刻度围绕 0 对称。

先确认语义再写公式:scale 是“量化时乘上去”的放大倍数,因此后面的反量化要除以同一个 scale。

语法热身:用温度读数练习 absmax、标量判断和安全 scale

readings = torch.tensor([-1.2, 0.0, 0.8])
largest = torch.abs(readings).max()

if largest == 0:
    largest = torch.tensor(1e-8, device=readings.device)

gain = 31.0 / largest  # 练习映射到另一套整数刻度
print(largest.shape)   # torch.Size([]),标量张量

例子中的变量 -> Notebook TODO 变量/操作

  • readings -> x:输入数据不同,但都先保留原 shape。
  • largest -> absmax:先逐元素取绝对值,再做全局最大值归约。
  • 31.0 -> Notebook 的 INT8 正上界 127.0
  • gain -> scale:都是“整数上界 / 最大绝对值”。

巩固一下

对张量 [-0.8, 1.5, -3.0, 2.5, 0.0] 做本页对称量化时,absmax 应是多少?

学完这一段,试着做

用一个小动作确认自己理解了;最后再进入官方题目。

Mission 2 概念练习

再离散化:乘、舍入、截断、转 int8 一个都不能乱

🎯 先猜一猜

torch.round(torch.tensor(105.8)) 的数值已经是 106。

只调用 torch.round 后,是否已经保证结果 dtype 是 torch.int8?

先猜一个答案,再对照下面的讲解。可以随时修改选择,猜错会得到解释。

先补的知识

  • scale 只是浮点标尺;x * scale 的结果仍是浮点张量,还不能获得 INT8 的存储收益。
  • torch.round 负责把连续值落到最近的整数刻度,但返回 dtype 通常仍是浮点类型。
  • torch.clamp 把异常值限制到允许范围;最后的 .to(torch.int8) 才改变存储类型。
  • 这些操作都是逐元素的,因此 x_quant 的 shape 必须与 x 完全相同。

图解原理

量化像把连续的刻度读数抄到只能写整数的表格:先按 scale 放大,再四舍五入;为了不写出表格范围之外的数,再做截断;最后才把整张表换成 int8 存储。

1x_scaled = x * scale浮点值对齐到 INT8 刻度
2rounded = round(x_scaled)把连续值变成整数数值,但尚未改 dtype
3bounded = clamp(...)保证数值落在 Notebook 指定区间
4x_quant = ...to(int8)shape 不变,存储类型变成 1 字节整数
阶段示例值shapedtype 关注点
原值2.5与 x 相同浮点
乘 scale105.833...与 x 相同仍是浮点
round106与 x 相同数值像整数,dtype 未必是 int8
cast106与 x 相同torch.int8

顺序错误会改变答案

若在 round 之前就转 int8,小数部分会直接丢失;若完全不 clamp,超范围转换可能产生错误的整数表示。

测试中的确定值

absmax=3.0 时,scale=127/3。样本 2.5 被放大为约 105.83,四舍五入后应为 106。

语法热身:把音频幅度映射到较小的整数区间

audio = torch.tensor([-1.0, -0.26, 0.24, 1.2])
gain = 15.0 / audio.abs().max()
scaled = audio * gain
rounded = torch.round(scaled)
codes = torch.clamp(rounded, -16, 15).to(torch.int8)

assert codes.shape == audio.shape
assert codes.dtype == torch.int8

例子中的变量 -> Notebook TODO 变量/操作

  • audio -> x:都按元素量化,shape 不变。
  • gain -> scale:Notebook 使用 127 对齐 INT8 范围。
  • scaled -> x_scaled:保持浮点,交给 round。
  • codes -> x_quant:Notebook 的 clamp 边界按 TODO 提示设置,并最终转成 torch.int8。

巩固一下

TODO 3 中哪一种顺序符合 Notebook 的量化链路?

学完这一段,试着做

用一个小动作确认自己理解了;最后再进入官方题目。

Mission 3 概念练习

最后做前向:INT8 负责存,输入 dtype 负责算

🎯 先猜一猜

weight_int8 的 shape 是 [64, 128],x 的 shape 是 [2, 10, 128]。

F.linear(x, w_dequant, bias) 的输出 shape 是什么?

先猜一个答案,再对照下面的讲解。可以随时修改选择,猜错会得到解释。

先补的知识

  • W8A16 中 W8 表示权重以 INT8 存储;本 Notebook 的激活 x 保持浮点,测试实际会传入 float32。
  • nn.Linear 的权重 shape 是 [out_features, in_features];F.linear 会用 x 的最后一维与 in_features 对齐。
  • 量化时做的是 x * scale,所以反量化必须先转回浮点,再除以同一个 scale。
  • register_buffer 创建的 weight_int8 与 scale 不参与梯度更新,但会跟随模块移动 device;bias 仍是 Parameter。

图解原理

这个模拟层把权重长期放在紧凑的 INT8 仓库里。每次 forward 时,先按输入 x 的计算类型取出一份近似浮点权重,再调用普通线性层。这样展示的是“存储省内存、计算前恢复”的链路。

weight_int8
[out, in] / int8
转成 x.dtype
[out, in]
除以 scale
近似浮点权重
F.linear(x, w, bias)
[batch, seq, out]
变量Notebook shapedtype/device 契约
x[batch, seq, in_features]激活的浮点 dtype;应与层位于同一 device
weight_int8[out_features, in_features]存储时固定为 torch.int8
w_dequant[out_features, in_features]计算时跟随 x.dtype
out[batch, seq, out_features]浮点输出

存储测试

同样数量的 FP32 权重每个 4 字节,INT8 每个 1 字节,所以 weight_int8 字节数应为四分之一。

近似测试

随机层比较原输出与量化层输出的余弦相似度,要求大于 0.99,而不是逐元素完全相等。

公式测试

确定性小矩阵会用同一份反量化权重调用 F.linear,要求你的 forward 与参考结果在 1e-6 内一致。

不要把 scale 再乘回去:本 Notebook 保存的是量化时的放大倍数,恢复数值范围使用除法。

语法热身:用压缩库存权重练习恢复后做线性计算

packed_weight = torch.tensor([[6, -3, 9], [2, 4, -8]], dtype=torch.int8)
restore_factor = torch.tensor(3.0)
features = torch.randn(5, 3, dtype=torch.float32)
offset = torch.zeros(2, dtype=features.dtype)

weight_for_compute = packed_weight.to(features.dtype) / restore_factor
scores = F.linear(features, weight_for_compute, offset)
assert scores.shape == (5, 2)

例子中的变量 -> Notebook TODO 变量/操作

  • packed_weight -> self.weight_int8:都是 [out, in] 的整数存储。
  • features.dtype -> x.dtype:反量化权重先跟随输入的计算类型。
  • restore_factor -> self.scale:恢复时使用除法。
  • scores -> out:使用 F.linear,并带上 Notebook 的 self.bias

巩固一下

量化时使用 x_quant ≈ round(x * scale),反量化权重应采用哪种操作?

学完这一段,试着做

用一个小动作确认自己理解了;最后再进入官方题目。

把理解变成自己的代码

准备好,去官方题目试一试

在本页用小例子建立直觉,再去官方 Notebook 完成实现。先运行你自己的测试,遇到困难时再查看官方提示与参考答案。

前往本课官方 Notebook ↗

这节课会遇到的代码对象

absmax_quantize · W8A16Linear · from_float · forward

先找题目里的输入、输出与 TODO,再把本课的手算过程对应进去;以官方题目中的函数说明和测试为准。

练习来源:Datawhale 官方仓库 · 518cc45。这里的入口直接打开官方版本,不读取或分享你的本地 Notebook。

完成 3 道闯关题后,本关即算完成;作业 checklist 用来辅助你回 notebook 练习。
进度只保存在当前浏览器 localStorage,分享 HTML 不会带走你的记录。
上一关:L24 下一关:L26