对一些主流模型的结构解析(pt/onnx/openvino/gguf)
ptonnxopenvinogguf经过前面的学习我们大概都知道有这些不同的模型了也知道一些他们的运用场景然后我想再学习一下他们都是由什么组成的pytorch文档pt构成首先如图从 PyTorch 1.6.0 开始.pt文件实际上是一个 ZIP64 压缩档案data.pkl这是通过 Python 的 pickle 模块序列化的对象包含是传递给torch.save()对象的 pickle 序列化结果包含模型的元数据和结构信息但不包含实际的张量存储数据具体包括模型的类定义引用张量的形状shape、数据类型dtype、步长stride等元数据张量之间的视图关系view relationshipsstate_dict 的字典结构如果保存的是 state_dict对data/中存储文件的引用指针作用描述模型的架构和组织结构记录如何重建模型对象的蓝图指向实际数据在data/文件夹中的位置data 文件夹包含所有的torch.Storage对象实际的张量数据每个存储storage是一个单独的文件文件通常以数字命名0, 1, 2, 3...存储内容包括模型的权重weights偏置biases批量归一化的运行统计量running_mean, running_var所有可学习参数和持久化缓冲区作用存储模型的实际参数数据张量的底层存储实现存储共享storage sharing如果多个张量共享同一个底层存储如视图关系只保存一份数据节省空间并保持张量间的关系version包含 PyTorch 的版本信息用于兼容性检查保存过程序列化元数据 →data.pkl将 state_dict 的字典结构、键名等 pickle 化记录每个张量的元信息提取存储对象 →data/将每个独立的torch.Storage提取出来保存为单独的文件打包成 ZIP64 →.pt文件将所有文件压缩归档加载过程解压 ZIP64 归档读取 data.pkl 重建字典结构和元数据从 data/ 加载存储填充实际数据重建张量恢复视图关系拿我们HIMLoco训练的pt模型来举例import torch model_data torch.load(/home/extra/zhy/桌面/IsaacGym_Preview_4_Package/HIMLoco-main/himloco_gym/logs/rough_go1/good/model_10000.pt, map_locationcpu) print(所有键名:, list(model_data.keys())) # 常见的键名模式 possible_keys [model_state_dict, model, actor, critic, policy, state_dict, network, actor_critic] for key in possible_keys: if key in model_data: print(f\n找到模型权重键: {key}) weights model_data[key] if isinstance(weights, dict): total_params sum(v.numel() for v in weights.values() if isinstance(v, torch.Tensor)) print(f 参数量: {total_params:,} ({total_params/1e6:.2f}M)) print(f 键数量: {len(weights)}) print(f 键名: {list(weights.keys())[:]}) # 如果上面没找到遍历所有键 print(\n *60) print(检查所有键是否包含张量:) print(*60) for key, value in model_data.items(): if isinstance(value, dict): tensor_count sum(1 for v in value.values() if isinstance(v, torch.Tensor)) if tensor_count 0: total_params sum(v.numel() for v in value.values() if isinstance(v, torch.Tensor)) print(f {key}: 包含 {tensor_count} 个张量总参数 {total_params:,})那么什么是键呢一个键 一个可学习参数张量一个可调节的数字参数而这个参数就是我们最后需要得到的东西就是我输入特定格式的东西根据他神经网络的结构加上这些参数各种计算最后得出特定格式输出所以说 data.pkl 是整体结构(结构 键名 地址索引)data文件夹里面就是具体的数值TorchScript这东西也是一个用来部署的一个pt他本质上是PyTorch 的编译版本他的推理是比正常的pytorch要快的也可以支持cpp推理但是比onnx还是差一些主要还是和PyTorch 生态绑定的这是解压缩后的code文件夹存储 TorchScript 编译后的计算图代码包含了模型的静态计算图表示计算图可以理解为提前把整个网络的结构写好这样用的时候就不用一行行解析python代码了所以也实现了跨语言速度也更快当然快不过onnx其他serialization_idTorchScript 特有的序列化标识符constants.pkl存储常量数据如形状、配置data.pkl主数据文件元数据、结构信息byteorder字节序信息大端/小端version格式版本号特点就是计算图编译优化在 C 环境中加载不包含优化器状态无法继续训练结构固定无法修改网络onnx他是Protobuf 二进制结构ONNX 模型 计算图 (Computational Graph) 计算图包含 4 大部分 ┌───────────────────────────────────────────────── │ 1️⃣ 输入 (Inputs) │ 比如270 维传感器数据 ├───────────────────────────────────────────────── │ 2️⃣ 输出 (Outputs) │ 比如12 维关节控制命令 ├───────────────────────────────────────────────── │ 3️⃣ 节点 (Nodes) - 计算步骤 │ 比如矩阵乘法、激活函数、加法... ├───────────────────────────────────────────────── │ 4️⃣ 初始化器 (Initializers) - 参数值 │ 比如30 个键的具体数值 (weight/bias) └─────────────────────────────────────────────────pt是框架和具体数值是分开的onnx是混在一起的还是以HIMLoco的为例子代码import onnx # 加载模型 model onnx.load(/home/extra/zhy/桌面/IsaacGym_Preview_4_Package/HIMLoco-main/himloco_gym/logs/rough_go1/good/model_10000.onnx) # 查看基本信息 print(*60) print(f模型版本{model.opset_import[0].version}) print(f生产者{model.producer_name}) print(*60) # 查看输入 print(\n 输入:) for inp in model.graph.input: shape [d.dim_value for d in inp.type.tensor_type.shape.dim] print(f 名称{inp.name}) print(f 形状{shape}) # 查看输出 print(\n 输出:) for out in model.graph.output: shape [d.dim_value for d in out.type.tensor_type.shape.dim] print(f 名称{out.name}) print(f 形状{shape}) # 查看节点 (计算步骤) print(\n 计算节点:) print(f 总节点数{len(model.graph.node)}) for i, node in enumerate(model.graph.node[:10]): # 只显示前 10 个 print(f [{i}] {node.op_type}: {node.input} → {node.output}) # 查看参数 (初始化器) print(\n️ 参数 (初始化器):) print(f 总参数数量{len(model.graph.initializer)}) total_params 0 for init in model.graph.initializer: shape list(init.dims) params 1 for s in shape: params * s total_params params print(f - {init.name}: {shape} ({params:,} 参数)) print(f\n 总参数量{total_params:,} ({total_params/1e6:.2f}M))所以就很明确了整个模型 一个计算图每个节点 一个计算操作初始化器 pt的键值输入输出关于键值因为我这里onnx是纯推理而且由于HIMLoco的框架设计就导致实际的onnx中的初始化器个数比pt的键值少所以这就是为啥 onnx 不能和 pt 一样继续训练的原因了openvino结构他的整个结构类似于onnx这里我是用yolo的pt转成openvino的best.xm l就于是整个神经网络的计算图也就是整个策略的结构这里要注意的是虽然这里叫计算图但是他只负责静态拓扑和操作语义权重被刻意剥离到独立的 best.bin 中best.bin 就是 权重的具体信息 也就是 data 文件夹里面的东西metadata.yaml 就是用来描述模型的辅助文件记录导出时间、框架来源、量化方式、输入输出名称等便于后续加载或调试ggufgguf就是很多本地大模型用的比如ollamaLMstudio啥的这里我以 gemma-3n-E4B-it-Q4_K_M.gguf 为例子分析//from llama_cpp import Llama # 你的 GGUF 文件路径 model_path rE:\ai\models\lmstudio-community\gemma-3n-E4B-it-text-GGUF\gemma-3n-E4B-it-Q4_K_M.gguf try: # n_gpu_layers0 表示纯 CPU 加载只用来读信息不推理 llm Llama( model_pathmodel_path, n_gpu_layers0, # 只读元数据不加载权重到 GPU verboseFalse, # Ture的话会打印加载时的 GGUF 元数据 n_ctx512, # 随便设小一点 load_formatgguf # 强制 GGUF ) # 加载成功后llm.metadata 就是字典形式的元数据 print(\n *60) print(GGUF 元数据llama-cpp-python 解析) for key, value in sorted(llm.metadata.items()): if isinstance(value, (list, tuple)) and len(value) 10: print(f {key}: list/tuple with {len(value)} items) else: print(f {key}: {value}) print(\n模型基本信息) print(f 架构: {llm.metadata.get(general.architecture, 未知)}) print(f 名字: {llm.metadata.get(general.name, 未知)}) print(f 量化类型: {llm.metadata.get(general.file_type, 未知)} (或从文件名推断 Q4_K_M)) print(f 上下文长度: {llm.metadata.get(llama.context_length, 未知)}) except Exception as e: print(f加载失败: {e})从名字开始gemma-3n-E4B-it-Q4_K_M.ggufgemma模型家族Google 的Gemma系列开源轻量模型。Gemma 是 Google DeepMind 推出的高效开源模型家族继 Gemma 1、Gemma 2 之后这是第 3 代变体。3n版本标识Gemma 3nGemma 3 的 “n” 变体。“n” 代表nested / nested MatFormer架构Matryoshka-like Transformer这是 Gemma 3n 的核心创新支持动态提取子模型比如从 8B 里切出 4B 或 2B 有效参数的子网络。它让模型在边缘设备上更灵活可以根据硬件资源选择不同“深度/宽度”。E4BEffective 4 Billion有效参数量约 4B。虽然底层总参数可能接近 6.9B–8B但通过 MatFormer 共享 KV 交替投影AltUp等技术实际计算和内存开销相当于传统 4B 模型。这是 Gemma 3n 系列最关键的卖点把大模型能力塞进小内存。itInstruct-Tuned指令微调版。说明这个模型经过了指令跟随instruction tuning和对话优化不是原始的 base 模型。适合聊天、问答、工具调用、角色扮演等任务输出更听话、更符合人类偏好。Q4_K_M量化类型Q4_K_M4-bit 量化 K-means clustering Medium 变体。这是 llama.cpp / GGUF 生态中最受欢迎的量化方案之一Q44-bit 每权重K使用 K-means 聚类优化量化误差M中等质量/速度平衡比 Q4_K_S 稍大但质量更好比 Q5_K_M 小但更快实际效果在 4-bit 下性能损失很小通常 perplexity 只涨 5–10%但文件大小和内存占用减少 60–70%。 GGUF 元数据llama-cpp-python 解析 # gemma3n.* 系列字段Gemma 3n 专有架构参数 gemma3n.altup.active_idx: 0 # AltUp机制的激活索引用于模型中的自适应层上采样 gemma3n.altup.num_inputs: 4 # AltUp机制的输入数量 gemma3n.attention.head_count: 8 # 注意力头的数量这里是8个头 gemma3n.attention.head_count_kv: 2 # Key-Value头的数量使用GQAGrouped Query Attention # 2个KV头对应8个查询头 gemma3n.attention.key_length: 256 # 注意力机制中Key向量的长度 gemma3n.attention.layer_norm_rms_epsilon: 0.000001 # RMSNormRoot Mean Square Normalization的 epsilon 值 1e-6。用于稳定 LayerNorm 计算避免除零 gemma3n.attention.shared_kv_layers: 15.000000 # 共享KV的层数跨层共享Key-Value缓存以节省内存 gemma3n.attention.sliding_window: 512 # 滑动窗口注意力的大小限制每个token只能关注最近的512个token gemma3n.attention.value_length: 256 # 注意力机制中Value向量的长度 gemma3n.block_count: 35 # Transformer块的总数即模型有35层 gemma3n.context_length: 32768 # 最大上下文长度可以处理32768个token gemma3n.embedding_length: 2048 # 主嵌入向量的维度 gemma3n.embedding_length_per_layer_input: 256 # 每层输入的嵌入维度 gemma3n.feed_forward_length: 16384 # 前馈网络FFN的隐藏层维度 gemma3n.rope.freq_base: 1000000.000000 # RoPE旋转位置编码的频率基数 # general.* 系列字段通用信息 general.architecture: gemma3n # 模型架构类型基于Google的Gemma 3n架构 general.basename: gg-hf-gm_gemma # 模型基础名称 general.file_type: 15 # GGUF文件类型编码对应Q4_K_M量化 general.finetune: 3n-E4B-it # 微调版本信息3n架构4B参数instruction-tuned general.name: Gg Hf Gm_Gemma 3n E4B It # 模型完整名称 general.quantization_version: 2 # 量化版本号 general.size_label: 6.9B # 模型大小标签约6.9B参数 general.type: model # 文件类型这是一个模型文件 # 分词器信息 tokenizer.chat_template: {{ bos_token }} # 对话模板定义了如何格式化对话历史支持 # 系统消息处理 # 用户/助手角色交替检查 # 多模态内容音频、图像、文本 # BOS/EOS token插入 {%- if messages[0][role] system -%} {%- if messages[0][content] is string -%} {%- set first_user_prefix messages[0][content] -%} {%- else -%} {%- set first_user_prefix messages[0][content][0][text] -%} {%- endif -%} {%- set loop_messages messages[1:] -%} {%- else -%} {%- set first_user_prefix -%} {%- set loop_messages messages -%} {%- endif -%} {%- for message in loop_messages -%} {%- if (message[role] user) ! (loop.index0 % 2 0) -%} {{ raise_exception(Conversation roles must alternate user/assistant/user/assistant/...) }} {%- endif -%} {%- if (message[role] assistant) -%} {%- set role model -%} {%- else -%} {%- set role message[role] -%} {%- endif -%} {{ start_of_turn role (first_user_prefix if loop.first else ) }} {%- if message[content] is string -%} {{ message[content] | trim }} {%- elif message[content] is iterable -%} {%- for item in message[content] -%} {%- if item[type] audio -%} {{ audio_soft_token }} {%- elif item[type] image -%} {{ image_soft_token }} {%- elif item[type] text -%} {{ item[text] | trim }} {%- endif -%} {%- endfor -%} {%- else -%} {{ raise_exception(Invalid content type) }} {%- endif -%} {{ end_of_turn }} {%- endfor -%} {%- if add_generation_prompt -%} {{start_of_turnmodel }} {%- endif -%} tokenizer.ggml.add_bos_token: true # 是否在序列开始添加BOS token tokenizer.ggml.add_eos_token: false # 是否在序列结束添加EOS token tokenizer.ggml.add_sep_token: false # 是否添加分隔符token tokenizer.ggml.add_space_prefix: false # 是否添加空格前缀 tokenizer.ggml.bos_token_id: 2 # Beginning of Sequence token的ID tokenizer.ggml.eos_token_id: 1 # End of Sequence token的ID tokenizer.ggml.model: llama # 使用的分词器模型类型 tokenizer.ggml.padding_token_id: 0 # Padding token的ID tokenizer.ggml.pre: default # 预处理方式 tokenizer.ggml.unknown_token_id: 3 # Unknown token的ID 模型基本信息 架构: gemma3n 名字: Gg Hf Gm_Gemma 3n E4B It 量化类型: 15 (或从文件名推断 Q4_K_M) 上下文长度: 未知生成流程输入阶段 1前端 / 客户端预处理分词Tokenization使用模型自带的 tokenizerSentencePiece 或类似 Llama 风格的分词器把你的文字切成 token。 根据元数据tokenizer.ggml.add_bos_token: true → 会在最前面自动加 BOS tokenid2add_space_prefix: false → 不会在开头加空格chat_template 会把你的输入包装成对话格式因为是 instruct 模型阶段 2模型加载阶段程序启动时一次性完成GGUF 文件被 mmap内存映射加载到内存几乎零拷贝速度极快。根据元数据35 层 Transformer block 被加载所有权重Q4_K_M 量化后的 6.9B 参数被解码到内存KV Cache 预分配根据上下文长度 32768但实际只用你需要的部分RoPE 基频设为 1,000,000支持长序列注意力机制使用 GQA8 query heads 共享 2 kv heads 滑动窗口 512 共享 KV 层 15 层阶段 3Prefill预填充阶段 —— 处理你的输入模型一次性处理你全部的输入 token包括 BOS、start_of_turnuser ... end_of_turn start_of_turnmodel这是计算最密集的部分所有层都要完整跑一遍输出最后一个位置的隐藏状态2048 维向量以及所有历史 KV CacheKey 和 Value 缓存关键优化点Gemma 3n 专有shared_kv_layers15 → 中间 15 层的 KV 被复用节省大量内存和计算sliding_window512 → 超过 512 个 token 的历史注意力被截断只保留最近 512 个加速长对话altup / PLE → 每层输入嵌入使用低维缓存256 维进一步省内存阶段 4生成阶段Autoregressive 自回归生成这是模型真正“写文章”的部分一次生成一个 token循环进行。循环过程重复 N 次直到生成结束取上一步的输出 token第一次是 prefill 最后的隐藏状态输入到下一层 Transformer用 RoPE 编码当前位置freq_base1000000注意力计算GQA 滑动窗口 KV 共享前馈网络16384 维中间层RMSNormepsilon1e-6得到 logits词汇表大小的概率分布通常 256000 个词汇采样下一个 tokentemperature默认 0.7–1.0top_p / top_krepetition_penaltymirostat / DRY 等高级采样例如采样到 token id1234对应某个词把新 token 加到序列末尾更新 KV Cache重复步骤 1–5直到生成到 EOS tokenid1达到最大长度max_tokens或手动停阶段 5后处理 输出把生成的 token id 序列送回 tokenizer 解码成文字去掉 start_of_turnmodel 等特殊标记返回给用户