题目重点:Nvidia NG SDE 面经中的 FP32 tensor 转 INT8 编程题:先按 scale 缩放,再扩展 asymmetric zero point;本文明确截断与饱和规则并验证边界。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- 训练输入是一维有限浮点数组;多维 tensor 可逐元素映射并保留 shape。scale 为正且有限,zero point 是 [-128,127] 范围内整数。
- 题目说“除后 cast”;本训练版先向零截断,再加 zero point,最后饱和到有符号 INT8 范围。生产量化常会另选 round-to-nearest 等规则,不能把本规则说成题面唯一要求。
- NaN、无限值、非正 scale 和非法 zero point 明确报错;极大商值直接饱和,避免转换时溢出。
解题路线
- 先写对称版 q=clamp(trunc(x/scale), -128,127),逐元素处理并保留原 tensor 的结构。
- 加入非零 zero point 后变为 q=clamp(trunc(x/scale)+zero_point,-128,127);注意先截断再加整数 zero point,与先相加再截断在负数附近可能不同。
- 检查 0、正负小数、上下边界和极大值。再讨论 scale、zero point 如何由校准数据估计,per-tensor 与 per-channel 对误差和内存读取的影响。
手动走一遍
scale=0.5、zero_point=3 时,x=-1.25 的缩放值为 -2.5,向零截断得 -2,输出 1;x=1.25 得 2.5→2→5。若缩放结果加零点超过 127,输出饱和为 127。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
from math import isfinite
def quantize_int8(values, scale, zero_point=0):
"""Training convention: truncate toward zero, add zero point, saturate."""
if not isfinite(scale) or scale <= 0:
raise ValueError("scale must be positive and finite")
if not isinstance(zero_point, int) or not -128 <= zero_point <= 127:
raise ValueError("zero point must be an int8 integer")
output = []
for value in values:
if not isfinite(value):
raise ValueError("tensor contains a non-finite value")
scaled = value / scale
if not isfinite(scaled):
output.append(127 if scaled > 0 else -128)
continue
quantized = int(scaled) + zero_point
output.append(max(-128, min(127, quantized)))
return output
复杂度
N 个元素逐一扫描,时间 O(N),返回数组空间 O(N);若接口允许原地写且输出 dtype 不同,需要重新分配。真正 tensor kernel 的性能还受连续内存、向量化、跨设备传输和 per-channel scale 读取影响。
易错点与追问
容易失分的边界
- 直接把超出 INT8 范围的值 cast,语言和硬件上的溢出行为可能产生错误结果;先明确饱和口径。
- 把 zero point 当作浮点加到原值后再除 scale,写成 (x+z)/scale,单位不一致。
- 把训练版截断规则说成所有量化都采用的舍入规则。
- 只测试正常值,漏掉负数截断、零点边界、非法 scale 与分布偏斜。
继续深挖
- 若面试官要求 round-to-nearest,先确认 tie-break 规则,再修改算式和测试。
- 若 zero point 和 scale 每个通道不同,明确轴、广播形状和连续内存访问代价。
- 用反量化 x̂=scale·(q−zero_point) 评估饱和比例、绝对误差及模型最终指标。
