【Bug已解决】KeyError: ‘layers.0.mlp.gate_up_proj.g_idx‘ of GLM-OCR GPTQ Int8 解决方案
【Bug已解决】KeyError: layers.0.mlp.gate_up_proj.g_idx of GLM-OCR GPTQ Int8 解决方案一、现象长什么样加载GLM-OCR 的 GPTQ Int8量化模型时权重加载阶段抛KeyError进程崩溃。典型日志KeyError: layers.0.mlp.gate_up_proj.g_idx或者更笼统KeyError: layers.0.mlp.gate_up_proj.g_idx of GLM-OCR GPTQ Int8几个特征帮你判断是不是同一个坑报错是KeyError且 key 形如layers.0.mlp.gate_up_proj.g_idx——这是 **GPTQ 量化的「分组排列索引」**字段。错误发生在GPTQ 权重加载阶段不是推理、不是模型结构。缺失的是g_idx不是权重本身说明qweight/scales/qzeros可能都在唯独g_idx分组顺序索引在这个投影上找不到。可能只有某个投影缺g_idx比如gate_up_proj缺但down_proj有说明不是全局都没而是部分投影的 GPTQ 元数据不完整。用strictFalse强行跳过进程不崩但推理结果错——权重没被正确理解。二、背景GPTQ 量化在存储时除了量化的权重qweight还需要一些「元数据」来正确反量化qweight量化后的权重打包的 4 位/8 位整数。qzeros每组的零点量化空间。scales每组的缩放因子。g_idx分组索引group index记录每个输出行/权重属于哪个 quant group 的排列顺序。它用于「把按组重排过的权重还原回原始顺序」。g_idx的来龙去脉GPTQ 在量化时为了提升精度常对权重做按组重排把相似大小的权重聚到同组g_idx记录这个重排的映射反量化时要用它「还原顺序」。为什么 GLM-OCR 的 GPTQ Int8 会缺g_idx该投影用了「无重排」GPTQ有些 GPTQ 变体/实现对某个投影如 gate_up_proj合并了 gateup选择不做按组重排于是g_idx被省略等价于恒等排列不需要存。但加载器默认所有 GPTQ 投影都有g_idx于是按固定 key 取 → 缺失 → KeyError。g_idx是共享/全局的某些格式把g_idx存成「每层一个」或「全局一个」而不是「每个投影一个」。加载器按layers.0.mlp.gate_up_proj.g_idx每投影去找但文件里是layers.0.mlp.g_idx每层共享→ 找不到。导出脚本漏存GLM-OCR 的 GPTQ Int8 导出脚本在写gate_up_proj时漏了g_idx字段数据本身没坏只是元数据缺一项。Int8 GPTQ 的特殊性Int8 量化相对 4 位有时用不同的分组策略g_idx的存在性在不同位宽下不一致加载器没覆盖。合并投影命名差异gate_up_proj是合并投影其g_idx可能以gate_proj.g_idxup_proj.g_idx两个独立形式存在加载器按合并名找 → 缺。核心g_idx在某些 GPTQ 变体/投影上是「可选」的恒等重排可省略但 GLM-OCR 的加载器把它当「必填」去取遇到省略的投影就 KeyError。三、根因根因一句话GLM-OCR 的 GPTQ Int8 checkpoint 里部分投影如gate_up_proj的g_idx分组排列索引被省略因为采用了恒等重排或全局共享 g_idx但加载器假设「所有 GPTQ 投影都必含 per-projg_idx」按固定 keylayers.0.mlp.gate_up_proj.g_idx去取取不到就抛出KeyError。具体成因g_idx可选性未处理恒等重排的投影无需g_idx加载器却当必填。g_idx共享/全局文件里是layers.0.mlp.g_idx每层而非 per-proj加载器按 per-proj 找错。合并投影命名gate_up_proj.g_idx实际是gate_proj.g_idxup_proj.g_idx加载器按合并名找。导出漏存GLM-OCR 导出脚本漏写该投影的g_idx。Int8 分组策略差异Int8 GPTQ 的g_idx存在性与 4 位不同加载器未覆盖。strictFalse掩盖强行跳过让权重没正确加载推理结果错。核心矛盾g_idx的「存在性」在 GPTQ 变体间不一致可选/共享/合并但加载器假设它「per-proj 必存在」于是遇到省略就 KeyError。四、最小可运行复现下面用纯 Python 模拟「加载器按固定 key 取 g_idx但该投影省略了 g_idx → KeyError」# reproduce_gidx.py # 复现加载器假设每投影都有 g_idx, 但 gate_up_proj 省略 - KeyError CKPT { layers.0.mlp.gate_up_proj.qweight: Q, layers.0.mlp.gate_up_proj.scales: S, # 注意: 没有 g_idx (恒等重排, 可省略) layers.0.mlp.down_proj.qweight: Q, layers.0.mlp.down_proj.g_idx: G, # down_proj 有 g_idx } def load_buggy(): # 假设每个 GPTQ 投影都有 g_idx for proj in (gate_up_proj, down_proj): _ CKPT[flayers.0.mlp.{proj}.g_idx] # gate_up_proj 缺 - KeyError def load_fixed(): out {} for proj in (gate_up_proj, down_proj): key flayers.0.mlp.{proj}.g_idx # g_idx 可选: 缺则用恒等排列(0,1,2,...) out[proj] CKPT.get(key, identity) return out if __name__ __main__: try: load_buggy() except KeyError as e: print(复现成功:, e) print(修复:, load_fixed())运行python reproduce_gidx.py会看到按固定 key 取 g_idx 在省略投影上 KeyError而修复版用get 恒等默认兼容可选性。五、解决方案第一层最小直接修复最小修复加载 GPTQ 权重时把g_idx当成可选字段——用CKPT.get(key, default)取值缺失时回退到「恒等排列」即[0,1,2,...,n-1]因为省略 g_idx 通常意味着量化时用了恒等重排反量化无需还原顺序。# fix_layer1_gidx.py def load_gptq_proj(ckpt: dict, layer: int, proj: str, out_features: int): base flayers.{layer}.mlp.{proj} qweight ckpt[f{base}.qweight] scales ckpt[f{base}.scales] # g_idx 可选: 缺失 - 恒等排列 g_idx ckpt.get(f{base}.g_idx) if g_idx is None: g_idx list(range(out_features)) # 恒等 return {qweight: qweight, scales: scales, g_idx: g_idx} if __name__ __main__: ckpt {layers.0.mlp.gate_up_proj.qweight: Q, layers.0.mlp.gate_up_proj.scales: S} print(load_gptq_proj(ckpt, 0, gate_up_proj, out_features4))这一层把「假设必含 g_idx」改成「可选 恒等默认」缺 g_idx 的投影不再 KeyError且语义正确恒等重排无需还原。六、解决方案第二层结构性改进把「GPTQ 元数据兼容性」做成模块自动探测g_idx的存在形式per-proj / 每层共享 / 合并投影拆分并统一归一# fix_layer2_gptq.py from dataclasses import dataclass, field dataclass class GptqMetaResolver: num_layers: int def resolve_gidx(self, ckpt: dict, layer: int, proj: str, out_features: int): per_proj flayers.{layer}.mlp.{proj}.g_idx per_layer flayers.{layer}.mlp.g_idx # 每层共享 if per_proj in ckpt: return ckpt[per_proj] if per_layer in ckpt: return ckpt[per_layer] # 复用共享 g_idx # 合并投影 gate_up_proj: 尝试 gate_proj/up_proj 拆分 if proj gate_up_proj: for sub in (gate_proj, up_proj): k flayers.{layer}.mlp.{sub}.g_idx if k in ckpt: return ckpt[k] return list(range(out_features)) # 恒等 def load_layer(self, ckpt: dict, layer: int, out_features: int) - dict: res {} for proj in (gate_up_proj, down_proj, gate_proj, up_proj): key flayers.{layer}.mlp.{proj}.qweight if key in ckpt: res[proj] self.resolve_gidx(ckpt, layer, proj, out_features) return res if __name__ __main__: ckpt { layers.0.mlp.gate_up_proj.qweight: Q, layers.0.mlp.down_proj.qweight: Q, layers.0.mlp.down_proj.g_idx: G, # 仅 down 有 g_idx layers.0.mlp.g_idx: L, # 每层共享 g_idx } r GptqMetaResolver(num_layers1).load_layer(ckpt, 0, out_features4) print(gate_up 用共享 g_idx:, r[gate_up_proj]) # L这样换 checkpoint 格式per-proj / 共享 / 合并拆分时统一经GptqMetaResolver归一缺 g_idx 自动回退不再 KeyError。七、解决方案第三层断言 / CI 守护把「GPTQ g_idx 可选性 共享/合并归一」钉进断言和 CI# fix_layer3_guard.py # ---- pytest 用例进 CI ---- def test_missing_gidx_identity(): from fix_layer1_gidx import load_gptq_proj ckpt {layers.0.mlp.gate_up_proj.qweight: Q, layers.0.mlp.gate_up_proj.scales: S} r load_gptq_proj(ckpt, 0, gate_up_proj, 4) assert r[g_idx] [0, 1, 2, 3] def test_shared_gidx_used(): from fix_layer2_gptq import GptqMetaResolver ckpt {layers.0.mlp.gate_up_proj.qweight: Q, layers.0.mlp.g_idx: L} r GptqMetaResolver(1).load_layer(ckpt, 0, 4) assert r[gate_up_proj] L def test_merged_gidx_fallback(): from fix_layer2_gptq import GptqMetaResolver ckpt {layers.0.mlp.gate_up_proj.qweight: Q, layers.0.mlp.gate_proj.g_idx: G} r GptqMetaResolver(1).load_layer(ckpt, 0, 4) assert r[gate_up_proj] G再加加载断言def assert_gptq_loadable(ckpt, layer, out_features): from fix_layer2_gptq import GptqMetaResolver GptqMetaResolver(1).load_layer(ckpt, layer, out_features) # 内部已兼容八、排查清单GLM-OCR GPTQ Int8 报KeyError: ...gate_up_proj.g_idx按序查先确认缺的是 g_idx 不是权重qweight/scales 都在、只 g_idx 缺说明是元数据可选性。g_idx 是否恒等可省该投影可能量化时未重排g_idx 省略恒等加载器应回退。是否每层共享 g_idx文件里或许是layers.0.mlp.g_idx每层而非 per-proj改用共享。合并投影拆分gate_up_proj.g_idx可能以gate_proj/up_proj.g_idx存在按子投影找。改加载逻辑用ckpt.get(key, identity)取 g_idx缺失回退恒等排列。导出脚本漏存确认 GLM-OCR 导出是否漏写该投影 g_idx必要时重导出。Int8 策略差异Int8 GPTQ 的 g_idx 存在性可能和 4 位不同加载器需覆盖。别用 strictFalse 蒙混强行跳过会让权重没正确反量化推理错。确认 out_features恒等 g_idx 长度需 输出特征数别写错长度。最后才动模型优先在加载侧做 g_idx 兼容归一不要为某个 checkpoint 改模型结构。九、小结GLM-OCR GPTQ Int8 报KeyError: layers.0.mlp.gate_up_proj.g_idx根子是该投影的g_idx分组排列索引被省略恒等重排可省、或每层共享、或合并投影拆分存储但加载器假设「所有 GPTQ 投影 per-proj 必含 g_idx」并按固定 key 取取不到就 KeyError。修复三层第一层用ckpt.get(key, identity)取 g_idx缺失回退恒等排列量化未重排时语义正确第二层抽GptqMetaResolver自动探测 g_idx 形式per-proj/共享/合并拆分并统一归一第三层用 pytest 把「缺 g_idx 回退恒等」「共享 g_idx 复用」「合并投影拆分兜底」钉进 CI。核心认识——g_idx在 GPTQ 变体间是「可选元数据」其存在性取决于量化时是否重排加载器必须把它当可选字段处理缺失恒等并兼容共享/合并存储形式而不是假设 per-proj 必存在。