☰
PicoLM量化反量化完全解析:Q4_K 4位权重如何恢复成FP32
2026/10/8 13:24:17 网站建设 项目流程

PicoLM量化反量化完全解析:Q4_K 4位权重如何恢复成FP32

【免费下载链接】picolmRun a 1-billion parameter LLM on a $10 board with 256MB RAM项目地址: https://gitcode.com/gh_mirrors/pi/picolm

PicoLM 是一个用纯 C 编写的极简 LLM 推理引擎,能在 $10 开发板(256MB RAM)上运行 10 亿参数大模型。它的核心秘密之一就是Q4_K 量化:把 4.4GB 的 FP32 权重压缩到 638MB,推理时再把 4 位整数反量化还原成浮点数参与计算。本文带你完整拆解这条"压缩 → 解压"流水线,看看每 144 字节里藏着的 256 个权重是如何一步步恢复成 FP32 的。

一、为什么需要量化:4.4GB 如何变成 638MB

TinyLlama 1.1B 有 11 亿个参数。如果每个参数用 FP32(4 字节)存储,光权重就要4.4GB——远超一块 $10 板子的全部内存。量化就是答案:用更少的位来"近似"每个权重。

PicoLM 支持的量化格式定义在 quant.h 中,各格式每 256 个权重的字节数对比(来自 gguf_type_quant_size):

格式256 个权重占用每权重位数特点
Q2_K84 字节~2.6 bit最小,质量损失较大
Q3_K110 字节~3.4 bit偏小
Q4_K144 字节~4.5 bit质量/体积最佳平衡(推荐)
Q6_K210 字节~6.5 bit高质量
Q8_032×34 字节8 bit接近无损

可以看到,Q4_K 下每个权重平均只用144/256 ≈ 0.56 字节,不到 FP32 的 1/7。模型文件从 4.4GB 缩到 638MB,就能放在 SD 卡里、通过mmap按层流式读入内存。

二、Q4_K 块结构:256 个权重如何塞进 144 字节

Q4_K 的基本单位是一个"块"(block),每个块打包256 个 4 位量化值。它的内存布局定义在 quant.h 的 block_q4_K 结构体:

字段大小含义
d2 字节(FP16)超级块缩放因子(super-block scale)
dmin2 字节(FP16)超级块最小值因子(super-block min)
scales[12]12 字节8 个子块的6 位缩放因子 + 6 位最小值(位级打包)
qs[128]128 字节256 个 4 位量化值(nibble,两两打包进 1 字节)

设计思想是"两级缩放":

256 个权重(一个 Q4_K 块, 144 字节) ┌────────────────────────────────────────────────────┐ │ d(FP16) dmin(FP16) ← 超级块全局缩放/最小值 │ ├────────────────────────────────────────────────────┤ │ scales[12] ← 8 个子块 × (6bit scale + 6bit min) │ ├────────────────────────────────────────────────────┤ │ 子块0(32个) 子块1(32个) … 子块7(32个) │ │ 每个子块内部再分 2 组 × 16 个 4 位 nibble │ └────────────────────────────────────────────────────┘

为什么是两级?因为 256 个权重的数值范围各不相同,只用一个全局缩放因子会损失精度。Q4_K 把它们切成 8 个子块(每 32 个一组),每个子块再各带 1 个 6 位缩放因子sc和 1 个 6 位最小值mn——共 16 个 6 位参数,巧妙挤进 12 个字节(8×12=96 bit = 12 B,刚好不浪费)。

三、反量化公式:4 位数字如何变回浮点数

反量化的核心公式极其简洁(见 dequantize_row_q4_K):

y = (d × sc) × q − (dmin × mn)

其中:

  • d、dmin:块头两个 FP16 因子,先经 fp16_to_fp32 软件位运算转成 FP32(无需硬件 FP16 支持);
  • sc、mn:该 32 权重子块的 6 位缩放/最小值;
  • q:0~15 的 4 位无符号整数,从qs字节中按低 4 位 / 高 4 位拆开(q[l] & 0xF与q[l] >> 4)。

也就是说,每个权重被近似成"缩放 × 4位整数 − 偏移"。4 位整数只有 16 档精度,但两级缩放让它能紧贴每个子块的真实数值范围,因此精度损失很小。

一个容易踩坑的细节:8 个 6 位参数无法直接存进字节对齐的位置,get_scale_min_k4 用位运算从scales[12]里"抠"出它们——前 4 个子块直接取 6 位(q[j] & 63),后 4 个子块的高 2 位被借存在前面字节的最高 2 位里(q[j-4] >> 6)。这种"位级借位"是 K-Quant 格式紧凑的关键。

四、融合点积:反量化与乘法一次完成 ⚡

既然推理只需要"权重 × 输入向量"的点积,何必先把整行权重解压进内存再读出来?PicoLM 的 vec_dot_q4_K_f32 采用融合(fused)策略:

  1. 按子块累加两个中间量:Σ q·x(量化值与输入的加权和)与Σ x(输入分段和);
  2. 最后一步才乘上d·sc和dmin·mn合并出结果。

数学上等价,但反量化后的 FP32 权重从不落地内存——直接省掉一半访存量。在 ARM 平台上,该函数还有 NEON 向量化版本(一次处理 8 个字节:vmovl_u8 → vmovl_u16 → vcvtq_f32_u32),x86 则有 SSE2 版本,构建时自动检测(quant.h 的 SIMD 宏)。

这条融合点积正是矩阵乘法matmul的热路径:tensor.c 的 matmul 把输出行分给多个线程,每行调用一次vec_dot,在 4 核 Pi 上可近似线性加速。

五、在推理流水线中的位置

反量化并不只服务于矩阵乘法,PicoLM 中有两类调用点:

场景入口说明
矩阵乘法(Q/K/V/FFN 投影)tensor.c 的 matmul →vec_dot走融合路径,不产生临时 FP32 缓冲
嵌入查表 / Norm 权重model.c 中 dequantize_row启动时预解压 RMSNorm 权重;每个 token 从token_embd解压一行嵌入

统一的分发逻辑在 dequantize_row:按 GGUF 头里声明的类型(GGUF_TYPE_Q4_K等)switch 到对应内核,所以换个 Q2_K/Q6_K 的模型文件无需改任何代码。

六、总结:一张表看懂 Q4_K 反量化

步骤做什么对应源码
① 读块头FP16 的d、dmin软转 FP32fp16_to_fp32
② 拆 6 位参数从scales[12]位运算解出 8 组 sc/mnget_scale_min_k4
③ 拆 nibble每字节拆成 2 个 4 位量化值dequantize_row_q4_K
④ 还原权重y = d·sc·q − dmin·mn同上
⑤ 融合计算边拆边乘,不落地 FP32vec_dot_q4_K_f32

核心要点:Q4_K 用"两级缩放 + 6 位打包参数 + 4 位量化值"三板斧,把 4 字节的 FP32 压进平均 0.56 字节;反量化时只需几次数乘和一次位拆分,就能在 $10 板子上实时恢复出参与矩阵运算的浮点权重——这正是 PicoLM 以 45MB 运行内存驱动 1B 模型的底层功臣。

【免费下载链接】picolmRun a 1-billion parameter LLM on a $10 board with 256MB RAM项目地址: https://gitcode.com/gh_mirrors/pi/picolm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询