
d1a-e2b-it-qat-4bit的MLX 4-bit逐比特移植原理276个int4层如何实现0.0反量化误差d1a-e2b-it-qat-4bit 是一个运行在 Apple Silicon 上的 MLX 4-bit 量化决策模型它基于 Google 的量化感知训练QAT权重把 276 个 int4 线性层逐比特原样搬进 MLX 格式实现 0.0 反量化误差。这篇文章用大白话讲清楚它的移植原理为什么直接拷贝比重新量化更好4-bit 分组量化到底怎么工作以及模型文件里各部分的来历。先搞懂两个概念QAT 和普通 4-bit 量化有什么不同大多数人接触到的4-bit 模型是这样做出来的先有一个 bf1616 位浮点的完整模型再把它压成 4-bit这个过程叫训练后量化PTQ。压的时候每个权重都要四舍五入舍入会累积误差模型效果会掉。而 d1a-e2b-it-qat-4bit 的底座google/gemma-4-E2B-it-qat-w4a16-ct走的是另一条路——QATQuantization-Aware Training量化感知训练对比项普通 PTQ 量化QAT本项目的做法什么时候定权值训练完之后压训练过程中就按 int4 来学4-bit 值怎么来从 bf16 舍入得到模型自己学出来的整数值额外舍入误差有无——权重天生就是 int4命名含义w4a16权重 4-bit、激活 16-bit相同QAT 的产物里权重本身就是 int4 整数加一组缩放参数根本不存在一个更精确的 bf16 原版需要你去压。这正是逐比特移植成为可能的原因既然权重天生就是整数那把它原样搬走一个比特都不会变。4-bit 分组量化每 32 个权重配一套尺子MLX 里的 4-bit 存储采用分组仿射量化affine本项目在 config.json 中声明了这套规则group_size 32每 32 个 int4 权重为一组每组配一个 **scale标度**和一个bias零点偏移均以 bf16 存储还原时权重 ≈ scale × (int4 值 bias)。可以把它理解成32 个格子共享一把尺子整数只记录第几格尺子负责还原真实大小。这样 32 个 bf16 权重占 64 字节换成 int4 后只需 16 字节加 4 字节参数体积大约压到原来的 1/3。这套规则在 d1a_config.json 的quantization字段里也有完整记录其中grid: ct-int4表示权值网格与 Google 的 QAT 检查点完全一致。276 个 int4 层是从哪里来的打开 model.safetensors.index.json 的权重映射表可以数出来底座 Gemma 4 E2B-it 共有35 个隐藏层其中带缩放参数的线性层恰好是276 个分布为每层的 7 个主干线性层 × 35 层 245 个q_proj、o_proj、mlp.gate_proj、mlp.up_proj、mlp.down_proj、per_layer_input_gate、per_layer_projectionk_proj与v_proj各 15 个仅部分层量化1 个per_layer_model_projection。245 30 1 276与 README 声称的数量完全吻合。除了这 276 个线性层还有 2 张嵌入表embed_tokens与embed_tokens_per_layer也量化成了 4-bit但用的是group_size 64的分组——QAT 只负责线性层的 32 分组嵌入表是发布方自己量化的。改用 64 分组后体积再小 0.17 GB各测试集精度基本不变hard-v1 71.1 → 71.1documents-v1 86.8 → 86.8。最终整个文本主干只有 model.safetensors2.7 GB4.6B 参数加上 media/ 目录下 1.0 GB 的视觉/音频编码器保持 bf16全部装进 Apple Silicon 的统一内存。0.0 反量化误差是怎么做到的不量化只搬运关键就在逐比特三个字。传统移植流程是拿到 bf16 权重 → 2. 重新分组 → 3. 重新计算 scale/bias → 4. 重新舍入为 int4。每一步都可能引入新的舍入。而本项目的做法是跳过全部重算直接读取 QAT 检查点里已经打好的 int4packed words打包后的整数连同每组的scales 和 biases原封不动写入 MLX 的 safetensors反量化公式scale × 整数与 Google 训练时用的完全一致。因为一个字都没重写过所以做逐层比对时276/276 个量化线性层的词整数、标度、偏置三方全部相同反量化误差就是0.0。这不是误差很小而是根本没有发生量化。为什么 D1A 适配层必须跟着重训既然主干一个比特都没变为什么还要重训适配层因为 QAT 权重和 bf16 权重并不相同——两者逐元素余弦相似度只有 0.992。旧的 v0.6 适配层是配合 bf16 主干调的原样搬过来后精度直接掉了v0.6 适配层放在hard-v1devtools-v1documents-v1bf16 主干原版0.6890.7090.862本 QAT 主干未重训0.6690.6830.857所以适配层在 QAT 主干上重训了一轮主干冻结在 QAT 检查点的值上按scale × 整数精确反量化2,929 步在 M1 Max 上跑了 12.5 小时。重训后 22 个测试集合并0.40PR 严重度 3.5每次决策还快 11%延迟比 0.89整体替换了旧版 v0.6。重训后的适配层以 LoRArank 1697 MB形式放在 adapter/ 中未合并关掉适配层它就是一个能聊天的 Gemma 4 E2B-it QAThead.safetensors 则是指向头配合 d1a_config.json 里拟合出的校准温度 1.55把预期校准误差 ECE 从 0.064 压到 0.018让模型对每个选项给出的概率更靠谱。文件都放在哪里文件作用model.safetensors / model.safetensors.index.json2.7 GB 的 4-bit 主干与权重索引276 个 int4 层 2 张嵌入表config.json模型架构与量化配置group_size、bits、affine 模式d1a_config.jsonD1A 格式声明LoRA 16、温度 1.551、grid: ct-int4、温度拟合记录adapter/adapter_model.safetensors在 QAT 主干上重训的 LoRA 适配层head.safetensors指向头pointer headmedia/视觉与音频编码器bf161.0 GB及其配置tokenizer.json / chat_template.jinja分词器与对话模板快速跑起来依赖d1a包含 MLX 后端后一条命令即可在本机起服务python -m d1a.serving.serve --run mlx-community/d1a-e2b-it-qat-4bit --port 8009随后通过/v1/systemone做决策、/v1/chat/completions聊天此时自动关闭适配层、/v1/systemone/media处理照片、语音和视频详见 README.md 中的示例请求。小结d1a-e2b-it-qat-4bit 的核心思路一句话就能讲完QAT 权重天生是 int4所以不量化、只搬运。276 个线性层的打包整数、标度和偏置逐比特照抄换来 0.0 的反量化误差再配合在 QAT 主干上重训的适配层与 1.55 的校准温度最终得到一个 2.7 GB、比旧版快 11% 的 MLX 4-bit 决策模型。对新手来说这也是一个值得记住的模式当源权重本身就是量化格式时精确搬运永远优于重新压缩。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考