:在 model_mapper.py 中添加四层字段映射,让模型正确加载`)
MNN 新 LLM 模型支持步骤 2在 model_mapper.py 中添加四层字段映射让模型正确加载【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN本篇文章是 MNN 支持新 LLM 模型六步流程中的步骤 2添加映射实战指南。它讲解 MNN 如何通过config / model / decoder / attention四层映射将 HuggingFaceHF模型结构转换为统一接口并给出 6 项检查、3 种注册方案的完整代码示例与可执行的测试脚本。读完本文你将掌握在 transformers/llm/export/utils/model_mapper.py 中为任意新模型注册映射的方法并能独立判断模型应复用 default_map 还是自定义映射。本步骤的前置条件是步骤 1 已通过模型已下载到本地、test_origin.py输出正确、5 个关键架构问题权重路径、Attention 投影层名、Decoder 子模块名、残差连接方式、RoPE 类型已有明确答案。若尚未完成请先阅读 skills/support-new-llm/step1-analyze.md。2.1 理解映射系统MNN 用 4 层映射把 HF 结构翻译成统一接口MNN 的 LLM 模型导出本质上是对照 HF transformers 库原始模型的实现将其计算逻辑映射到 MNN 统一框架。ModelMapper通过4 层映射外加 2 个可选层完成这一翻译每一层解决一个层面的名字对应问题映射键作用说明configHF config.json 字段 → LlmConfig 属性把模型配置正确读入modelHF 模型权重路径 → LlmModel 属性找到 embed/layers/norm/lm_headdecoderHF Decoder 层子模块 → Decoder 属性找到 attn/mlp/layernormattentionHF Attention 子模块 → Attention 属性找到 q/k/v/o 投影层mlp可选HF MoE 子模块 → Mlp 属性仅 MoE 模型需要linear_attention可选HF LinearAttn 子模块 → LinearAttention 属性仅特殊架构需要映射规则一句话左边是 MNN 统一的键名右边是 HF 模型中实际的属性名。如果一致就直接复用 default如果不同就自定义。default_map 的完整定义以 LlamaForCausalLM 为基准默认映射以LlamaForCausalLM为基准定义在 model_mapper.py 的init_default_map()中default_config { hidden_size: hidden_size, head_dim: head_dim, num_attention_heads: num_attention_heads, num_hidden_layers: num_hidden_layers, num_key_value_heads: num_key_value_heads, rope_theta: rope_theta, rope_scaling: rope_scaling, max_position_embeddings: max_position_embeddings } # ⚠️ 注意如果模型的 rope_theta 不在顶层而是在 rope_parameters 中 # 需要额外映射 rope_parameters: rope_parameters #参见 common-pitfalls.md 第 13 节 default_model { lm: lm_head, embed: model.embed_tokens, blocks: model.layers, final_layernorm: model.norm, visual: visual } default_decoder { self_attn: self_attn, linear_attn: linear_attn, mlp: mlp, input_layernorm: input_layernorm, post_attention_layernorm: post_attention_layernorm } default_attention { qkv_proj: qkv_proj, q_proj: q_proj, k_proj: k_proj, v_proj: v_proj, o_proj: o_proj, q_norm: q_norm, k_norm: k_norm }源码级原理映射是如何被消费的要真正理解映射需要看懂ModelMapper的三个核心机制均在 model_mapper.pyregist()校验第 21-25 行注册任何模型时强制断言config、decoder、attention三个键必须存在缺一不可。mlp、linear_attention等可选键可有可无符合映射表中可选的定位。init_models()自动发现第 27-32 行__init__会扫描self上所有以regist_开头的方法并逐一调用所以你新增一个regist_xxx(self)方法后无需手动修改调用清单构造ModelMapper()时就会自动执行注册。do_map()静默取值第 1245-1277 行真正执行映射的函数按src_path用.逐级getattr取源值。关键行为源属性不存在时不会报错而是静默赋None——这是后面常见陷阱的根源之一也是为什么rope_theta这类字段必须单独验证。do_map的另一个细节它会按目标键名排序后依次设置sorted(mapping.items(), keylambda x: x[0])确保父节点如visual先于子节点如visual.connector被赋值——这是为 SmolVLM、FastVLM 这类带点号嵌套键的模型准备的。LlmConfig.from_pretrainedconfig.py会读取config.json通过ModelMapper().get_map(config)按model_type找到映射随后调用ModelMapper.do_map(llm_config, config, model_map[config])把 config 字段灌入LlmConfig。找不到映射时get_map会回退到self.default_map第 10-19 行。2.2 根据步骤 1 的结果选择修改方案6 项检查逐一定夺对照步骤 1 的 5 个问题答案逐一执行以下 6 项检查决定哪些层需要自定义。检查 1config 是否需要自定义判断条件config.json 的字段是否直接在顶层字段直接在顶层如hidden_size→ 直接读→ 使用self.default_config字段在text_config下如text_config.hidden_size→需要自定义 config典型Gemma3、Qwen3-VL 等多模态模型配置被嵌套在text_config中⚠️ rope_theta 特殊检查确认rope_theta是否在顶层。如果不在顶层但存在rope_parametersdict必须额外映射rope_parameters: rope_parameters参见 skills/support-new-llm/common-pitfalls.md 第 13 节。LFM2 系列就是典型它的rope_theta存在rope_parametersdict 中若漏映射会静默回退到默认值 10000。# 自定义 config 示例 new_config { hidden_size: text_config.hidden_size, head_dim: text_config.head_dim, num_attention_heads: text_config.num_attention_heads, num_hidden_layers: text_config.num_hidden_layers, num_key_value_heads: text_config.num_key_value_heads, rope_theta: text_config.rope_theta, rope_parameters: text_config.rope_parameters, # ← 如果 rope_theta 在此 dict 中 rope_scaling: text_config.rope_scaling, max_position_embeddings: text_config.max_position_embeddings }源码佐证regist_gemma3、regist_qwen3_vlregist_qwenvl内的 config 映射均使用text_config.前缀而regist_qwen3_text变体用{k: v.replace(text_config., ) for k, v in ...}一行代码生成顶层版本是同模型双 config 布局的现成范例。检查 2model 路径是否需要自定义判断条件步骤 1 问题 1 的回答。路径是标准的model.embed_tokens、model.layers、model.norm、lm_head→ 使用self.default_model路径不同 →需要自定义 model# 自定义 model 示例嵌套在 language_model 下 new_model { lm: language_model.lm_head, embed: language_model.model.embed_tokens, blocks: language_model.model.layers, final_layernorm: language_model.model.norm, }源码佐证DeepSeek-VLregist_deepseek_vl、Qwen2.5-Omniregist_qwen_omni、Qwen2-Audio、Gemma3 等模型都是文本模型被嵌套在language_model.model下的实例而 Qwen2-VL 是例外文本模型直接在model.layers顶层无需language_model前缀见 common-pitfalls.md 第 12 节。blocks路径还支持copy.deepcopy(default_model)后追加字段的写法例如regist_mimo追加mtp: model.mtp_layers、regist_fastvlm追加visual: model.vision_tower。检查 3attention 是否需要自定义判断条件步骤 1 问题 2 的回答。标准q_proj/k_proj/v_proj/o_proj→ 使用self.default_attentionFused QKV如c_attn、W_pack→ 需要自定义映射qkv_projO 投影名不同如dense、c_proj→ 需要自定义# 自定义 attention 示例fused QKV new_attention { qkv_proj: c_attn, # fused QKV 层名 o_proj: c_proj # output 投影层名 }源码佐证model_mapper.py 内多处现成案例Baichuanqkv_proj: W_packregist_llama内基于 default 深拷贝后仅替换 attentionQwen1qkv_proj: c_attno_proj: c_projregist_qwenChatGLMqkv_proj: query_key_valueo_proj: denseregist_glm/regist_glm2OpenELMqkv_proj: qkv_projo_proj: out_projregister_openelm如果模型在 Q/K 投影后有额外的q_norm/k_norm如 Qwen3、InternVL、Hunyuan需要在 attention 映射中补充q_norm: q_norm、k_norm: k_normHunyuan 的 norm 名是query_layernorm/key_layernorm注意按实际属性名映射regist_hunyuan_v1_dense。检查 4decoder 是否需要自定义判断条件步骤 1 问题 3 和问题 4 的回答。标准名称 → 使用self.default_decoder名称不同 → 需要自定义有额外 LayerNorm → 需要自定义并添加额外字段没有post_attention_layernorm→ 需要自定义省略该字段先确定新模型属于哪种残差模式参考 common-pitfalls.md 第 6 节的速查表和 Gemma2 映射示例模式Decoder 中 LayerNorm 数量典型模型需要额外映射字段Standard2input_layernormpost_attention_layernormLlama, Qwen2, Qwen3无Gemma24-norm4input post_self_attn post_attn post_mlpGemma2, glm_ocrpre_feedforward_layernorm,post_feedforward_layernormPhi并行各异Phi特殊分支MiniCPM缩放2 scale_depthMiniCPMconfig 中添加scale_depthGemma2 风格的 4-norm 模式中MNN 统一键名与 HF 实际属性名的对应关系可能反直觉务必按下面的对照编写new_decoder { self_attn: self_attn, mlp: mlp, input_layernorm: input_layernorm, post_attention_layernorm: post_self_attn_layernorm, # MNN的post_attn → HF的post_self_attn pre_feedforward_layernorm: post_attention_layernorm, # MNN的pre_ff → HF的post_attn post_feedforward_layernorm: post_mlp_layernorm # MNN的post_ff → HF的post_mlp }源码佐证regist_gemma2通过copy.deepcopy(self.default_decoder)后追加pre_feedforward_layernorm、post_feedforward_layernorm实现regist_glm_ocr则直接手写完整 decoder 映射其post_attention_layernorm映射到post_self_attn_layernorm。此外还有两种 decoder 变体值得参考层名不同Qwen1 的 decoder 是attn/mlp/ln_1/ln_2OpenELM 是attn/ffn/attn_norm/ffn_normLFM2 是self_attn/feed_forward/operator_norm/ffn_norm。混合架构LFM2、Qwen3.5decoder 同时包含self_attn和linear_attn两个互斥入口Decoder.__init__会根据哪个非 None 创建标准 Attention 或 LinearAttention见 common-pitfalls.md 第 8 节。检查 5config.json 是否有非标准字段判断条件config.json 中是否有LlmConfig.__init__尚未定义的字段且该字段会通过 config 映射被使用LlmConfig.__init__config.py已定义的字段有默认值无需添加hidden_size, num_attention_heads, num_hidden_layers, num_key_value_heads, head_dim, rope_theta, rope_ratio, sliding_window, layer_types, attention_type, tie_word_embeddings, conv_L_cache此外源码中还有scale_emb、rope_parameters、qk_norm_after_rope、model_map等字段使用前请以 config.py 当前实现为准。所需字段已在上述列表中 → 无需修改需要新字段如某模型引入了新的配置参数→需要在config.py的LlmConfig.__init__中添加# config.py 中添加新字段示例 class LlmConfig(PretrainedConfig): def __init__(self, **kwargs): # ... 已有字段 ... self.new_field kwargs.pop(new_field, default_value) # ← 添加为什么需要这步ModelMapper.do_map使用setattr设置值即使不在__init__中定义也能工作。但添加默认值有两个好处(1) 其他模型不会因缺少该属性而报AttributeError(2) 代码中可以用config.new_field直接访问而不需要hasattr保护。默认值应设为无效值如0、None、[]使不具备该字段的模型行为不变common-pitfalls.md 第 7 节。源码佐证conv_L_cacheLFM2 系列、scale_embMiniCPM都是为此添加的新字段LlmConfig.from_pretrained中rope_theta的兜底逻辑第 109-124 行会依次尝试rope_parametersdict、顶层rope_theta、text_config.rope_theta最终才回退到 10000.0这就是静默回退的完整链路。检查 6是否需要 mlp 映射MoE 模型判断条件config.json 中是否有num_expertsTier 3 模型。没有 → 不需要 mlp 映射有 → 需要添加 mlp 映射# MoE mlp 映射示例 new_mlp { num_experts: num_experts, top_k: top_k, norm_topk_prob: norm_topk_prob, gate: gate, experts: experts } # 如果有 shared_expert new_mlp[shared_expert] shared_expert new_mlp[shared_expert_gate] shared_expert_gate源码佐证regist_qwen3_moe的 mlp 映射使用num_experts: experts.num_experts、top_k: gate.top_k这种带前缀的嵌套路径注意实际命名与上文示例不同说明必须对照 HF 源码确认regist_qwen3_5为 MoE 变体补充了shared_expert_gate、shared_expertregist_gpt_oss使用num_experts: router.num_experts、top_k: router.top_kregist_lfm2_moe还包含expert_bias、routed_scaling_factor等 sigmoid routing 专属字段。⚠️ Tier 3 并不简单MoE 涉及 expert 权重拆分在mnn_converter.py的convert_expert()中沿 axis0 切片为独立 subgraph、routing 算法差异softmax→topk / topk→softmax / sigmoidbias、dense/MoE 层混合num_dense_layers等问题详见 common-pitfalls.md 第 9 节。2.3 编写映射代码三种方案按需选择在 model_mapper.py 中为新模型添加注册方法按架构差异程度选择方案。方案 A完全匹配 default_mapTier 1 最简单情况找到regist_llama方法在其中追加一行def regist_llama(self): llama_map self.default_map self.regist(llama, llama_map) self.regist(qwen2, llama_map) # ... 其他已有模型 self.regist(新的model_type, llama_map) # ← 添加这一行这是最简单路径default_map是 Llama 的完整映射config model decoder attention 四层直接复用即可。由于init_models()会自动调用所有regist_方法追加这行后无需其他改动。方案 B需要自定义映射创建新的注册方法并在__init__中调用# 1. 在 ModelMapper 类中添加新方法 def regist_new_model(self): # 按照 2.2 中的检查结果组装映射 new_map { config: self.default_config, # 或自定义 model: self.default_model, # 或自定义 decoder: self.default_decoder, # 或自定义 attention: self.default_attention # 或自定义 } self.regist(model_type值, new_map) # 2. 在 __init__ 中调用这个方法 def __init__(self): # ... 已有的注册方法 self.regist_new_model() # ← 添加这一行实际上__init__只需调用init_models()它会自动发现regist_方法因此新方法无需手动在__init__里登记——这一步只是为了强调方法的组织方式。组装映射时建议大量使用copy.deepcopy(self.default_map)后再局部替换源码中 Baichuan、Gemma2、GPT-OSS、MiniCPM、FastVLM、Qwen3.5-MoE 等均采用这种继承默认 增量修改的写法可以避免重复定义且不容易漏字段。方案 C多模态模型额外需要注册模型类如果模型不能用AutoModelForCausalLM加载多模态模型通常不行需要在 model.py 的MODEL_CLASS_MAPPING中添加# model.py 中的 get_model_class 方法 MODEL_CLASS_MAPPING { # ... 已有映射 new_model_type: NewModelForConditionalGeneration, }源码佐证MODEL_CLASS_MAPPING已包含qwen2_vl、qwen3_vl、qwen3_5、glm_ocr、gemma4、lfm2_vl、hunyuan_vl等模型的专用类smolvlm和idefics3分别映射到AutoModelForImageTextToText、AutoModelForVision2Seqfunaudiochat映射到AutoModelForSeq2SeqLMqwen3_asr/qwen3_tts映射到AutoModel。get_model_class找不到映射时回退到AutoModelForCausalLM第 142-143 行。部分外部包模型还需在 config.py 的EXTERNAL_MODEL_REGISTRY中注册 import 模块如funaudiochat、qwen3_asr、qwen3_tts详见 common-pitfalls.md 第 13 节。步骤 2 测试标准测试方法执行以下命令测试模型是否能正确加载并验证关键 config 值cd transformers/llm/export python3 -c from utils.model import LlmModel import argparse args argparse.Namespace(lora_pathNone, lora_splitFalse, skip_weightFalse, testFalse, eagle_pathNone) model LlmModel.from_pretrained(/path/to/model, argsargs) print(✅ 模型加载成功) print(f hidden_size: {model.config.hidden_size}) print(f num_layers: {model.config.num_hidden_layers}) print(f num_heads: {model.config.num_attention_heads}) print(f num_kv_heads: {model.config.num_key_value_heads}) print(f head_dim: {model.config.head_dim}) print(f blocks 数量: {len(model.blocks)}) print(f embed 类型: {type(model.embed)}) print(f lm 类型: {type(model.lm)}) # 关键验证 config 值与 HF 原始 config 一致 # rope_theta 是高频出错项必须检查 print(f rope_theta (Rotary): {model.rotary.rope_theta}) # 与原始 config 对比手动确认与 config.json 中的值一致 origin model.config.origin_config print(f [对比] origin rope_theta: {getattr(origin, \rope_theta\, \NOT_FOUND\)}) if hasattr(origin, rope_parameters) and origin.rope_parameters: print(f [对比] origin rope_parameters: {origin.rope_parameters}) # 验证 config 映射完整性检查每个映射字段是否在源 config 中存在 print() print(Config 映射检查:) model_map model.config.model_map for dst, src in model_map.get(config, {}).items(): val origin for attr in src.split(.): val getattr(val, attr, None) if val is None: break status ✅ if val is not None else ⚠️ None print(f {status} {dst} - {src} {val if not isinstance(val, dict) else type(val).__name__}) 通过标准不报 KeyError说明 config 映射正确不报 AttributeError说明 model/decoder/attention 路径正确hidden_size / num_layers / num_heads 值正确与 config.json 中一致blocks 数量正确等于 num_hidden_layersembed 和 lm 类型不是 Nonerope_theta 值正确与 HF config 中的值一致不是默认值 10000除非模型确实使用 10000Config 映射检查无 ⚠️ None 项所有映射的源字段都存在如果有 None 项需确认是否需要替换映射路径或添加间接映射如rope_parameters为什么 rope_theta 值得单独加粗因为do_map源属性缺失时静默赋 None、post-processing 再用默认值 10000 覆盖映射错误不会报任何异常common-pitfalls.md 第 14 节。若步骤 3 的 layer0 检查点出错但权重匹配优先怀疑 rope_theta。常见错误与修复错误原因修复KeyError: hidden_sizeconfig 字段在 text_config 下自定义 config 映射加上text_config.前缀AttributeError: NoneType has no attribute embed_tokensmodel 路径错误检查 embed/blocks/norm 的实际路径blocks 数量为 0blocks 路径错误检查 layers 的实际路径embed 或 lm 为 None路径不存在检查实际的权重名称定位路径问题的实用技巧用safetensors.safe_open列出权重 key 的前缀来确认 model 层路径如看到model.language_model.layers.0.self_attn.q_proj.weightblocks 就应映射为model.language_model.layers见 common-pitfalls.md 第 12 节。失败处理如果测试失败阅读报错信息定位是哪个映射出了问题使用工具查看 HF 模型的实际属性名print(original_model)或state_dict().keys()修改映射后重新测试在修复问题之前不要进入步骤 3下一步步骤 2 通过后进入 skills/support-new-llm/step3-test-python.md步骤 3Python 推理测试——用 hook 机制对比 transformers 原始模型与LlmModel在 embed、第 0 层、最后一层、final norm、logits 五个检查点的中间结果分 prefill 与 first decode 两个阶段确保映射后的推理逻辑与 HF 完全对齐。延伸阅读六步流程总览与 Tier 判定速查skills/support-new-llm/SKILL.md步骤 1下载、理解与测试模型、回答 5 个关键问题skills/support-new-llm/step1-analyze.md常见陷阱速查RoPE 变体、残差模式、MoE 要点、do_map 静默失败等skills/support-new-llm/common-pitfalls.md映射核心实现transformers/llm/export/utils/model_mapper.py配置类与 rope_theta 兜底逻辑transformers/llm/export/utils/config.py统一模型类与 MODEL_CLASS_MAPPINGtransformers/llm/export/utils/model.py【免费下载链接】MNNMNN: A blazing-fast, lightweight inference engine battle-tested by Alibaba, powering high-performance on-device LLMs and Edge AI.项目地址: https://gitcode.com/GitHub_Trending/mn/MNN创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。