大语言模型 从主流大模型到llama权重文件解析和参数计算
大语言模型 从主流大模型到llama权重文件解析和参数计算
一、大语言模型
目前我们关注的GPT、Llama都属于大语言模型。
- Llama:已在大量数据集上完成训练,具备文本分类、生成、摘要等能力;通过“迁移学习”,可在特定任务上快速微调。
- 大语言模型的核心特点:
- 更大规模的训练数据:覆盖各类知识、文本类型,提升泛化能力;
- 更复杂的模型结构:通常采用更深的神经网络结构;
- 更强的生成能力:能生成连贯、有意义的文本;
- 更强的泛化能力:少量数据即可适配不同任务;
- 零样本学习能力:无需小样本,仅通过指令即可完成任务。
二、GPT简介
GPT是OpenAI基于Transformer架构的语言模型,核心特点是自回归预训练,可用于文本生成、翻译、问答等任务。
- ChatGPT:基于GPT系列模型的对话微调版本,通过大规模对话数据优化,提升了上下文理解与对话流畅度;
- GPT-4:在多模态能力(文本+图像)、逻辑推理等方面升级,2023年3月发布,性能优于此前的GPT-3系列。
三、Llama简介
Llama是Meta推出的开源大语言模型(含Llama 1、Llama 2),特点是仅使用公开数据训练,性能接近GPT-3.5。
- Llama 2:在Llama 1基础上升级,支持更长上下文、更强数据清洗;其对话版本Llama 2-Chat通过人类反馈优化(RLHF),提升了安全性与实用性。
- Llama 2开源模型名称:
| 模型 | Llama 2-Chat |
|---|---|
| 7B | Llama-2-7b-chat-hf |
| 13B | Llama-2-13b-chat-hf |
| 70B | Llama-2-70b-chat-hf |
四、Llama的训练
1. 训练数据
Llama仅使用公开可得数据训练,Llama 1预训练数据约含1.4T tokens
Llama 2训练数据扩展至2万亿标记,同时剔除了含私人信息的数据源。
2. 预训练
Llama 2基于Llama 1的架构,优化点包括:更强数据清洗、更长上下文长度、分组查询注意力机制。
训练损失:在2T标记的预训练后,各参数模型(7B/13B/70B)均未出现损失饱和。
五、Llama 2 Chat
Llama 2 Chat是Llama 2基础上针对对话微调的模型,通过监督微调+人类反馈强化学习(RLHF) 优化。
1. 监督微调
- 基于预训练的Llama 2,在人工编写的指令数据集(27540条prompt+answer)上微调;
- 合并prompt与answer,保证序列长度为4096,训练时仅对回答部分反向传播。
2. 基于人类反馈的强化学习(RLHF)
目标是对齐人类偏好与指令遵循,核心步骤:
- 收集人类偏好数据:通过二元比较标注模型回应的“有用性”“安全性”,构建百万级数据集;
- 奖励模型:训练两个独立的奖励模型(有用性RM/安全性RM),避免两者相互抵消;
- 数据配比:有用性RM采用“Meta Helpfulness: (Meta Safety+开源)=1:1”,安全性RM采用“Meta Safety: (Meta Helpfulness+开源)=9:1”;
- 迭代微调:使用近端策略优化(PPO)、拒绝采样微调两种算法,提升模型的对话效果。
六、Llama 2权重文件夹内容整理
1、权重文件格式分类
Llama 2发布时提供两种权重文件格式:
- pth格式(原始格式):如 Llama-2-7b ,目录包含 checklist.chk 、 unconsolidated.00.pth 、 params.json 等文件;
- hf格式(HuggingFace格式):如 Llama-2-7b-hf ,是主流使用格式。
2、Llama-2-7b-hf权重文件夹目录
plaintext
├── config.json # 模型配置(架构、超参数、特殊标记)
├── generation_config.json # 生成器配置(文本生成方式)
├── pytorch_model-00001-of-00002.bin # 权重文件1(约9.9GB)
├── pytorch_model-00002-of-00002.bin # 权重文件2(约3.5GB)
├── pytorch_model.bin.index.json # 权重文件索引(快速加载)
├── README.md # 说明文档
├── special_tokens_map.json # 特殊标记定义(起始/结束/未知标记)
├── tokenizer.model # 分词器二进制文件
├── tokenizer.json # 分词器配置(JSON格式)
└── tokenizer_config.json # 分词器初始化配置
3、核心文件说明
- .bin权重文件
- 模型训练后的参数文件,因体积(13G)超过10GB被拆分;
- 包含神经网络的权重、偏置等可学习参数。
- 配置类文件
- config.json :定义模型架构(如 LlamaForCausalLM )、嵌入维度( hidden_size )、注意力头数( num_attention_heads )等;
- generation_config.json :控制文本生成的参数(如 bos_token_id 、 eos_token_id );
- special_tokens_map.json :定义特殊标记(如 bos_token:
、 eos_token:)。
- 分词器相关文件
- tokenizer.model / tokenizer.json :存储词表与分词规则;
- tokenizer_config.json :分词器初始化参数。
七、Llama 2模型参数计算与权重文件解析(含代码实战)
1、Llama 2模型超参数
Llama 2不同尺寸模型的核心超参数如下:
| 参数数量 | 模型大小 | 嵌入维度 d_model | 层数 L | 中间维度 d_ff |
|---|---|---|---|---|
| 6.7B | 13476839424 | 4096 | 32 | 11008 |
| 13.0B | 26031738880 | 5120 | 40 | 13824 |
| 32.5B | 65057902592 | 6656 | 60 | 17920 |
| 70B | 137953316864 | 8192 | 80 | 28672 |
2、标准Transformer解码器参数计算
以GPT-3(125M参数)为例,参数来自词嵌入层、Transformer层、输出层:
- 词嵌入层: V × d_model ( V 为词汇表大小)
- Transformer层:每层包含自注意力机制、前馈网络、层归一化,公式为:
4×dmodel2+2×df×dmodel+df+5×dmodel4×d_model² + 2×d_f×d_model + d_f + 5×d_model4×dmodel2+2×df×dmodel+df+5×dmodel
- 输出层:与词嵌入层参数共享,数量为 V × d_model
3、Llama 2权重文件解析(HF格式)
HF格式的Llama 2权重以 .bin 文件存储(拆分多个文件),每个文件是zip压缩包,包含 data.pkl (序列化的参数数据)。
代码示例:解析Llama 2权重文件,用于读取HF格式的Llama 2权重文件,解析参数形状、类型并统计数量:
import pickle import zipfile from pathlib import Path from typing import IO, Any, Callable, Optional, List, Dict from dataclasses import dataclass import torch # 需提前安装PyTorch# 定义数据类型枚举(对应权重存储的类型) DT_F16 ='F16'# 半精度浮点 DT_F32 ='F32'# 单精度浮点 DT_BF16 ='DT_BF16'# 脑浮点数 DT_I32 ='I32'# 32位整数# -------------------------- 数据类定义:描述权重存储信息 --------------------------@dataclassclassLazyStorageKind:"""描述权重的存储数据类型""" data_type:str# 对应DT_F16/DT_F32等@dataclassclassLazyStorage:"""描述权重的存储位置与类型""" kind: LazyStorageKind # 数据类型 description:str# 存储路径等描述信息@dataclassclassLazyTensor:"""描述权重张量的形状、类型""" shape: List[int]# 张量形状(如[32000, 4096]) data_type:str# 数据类型 description:str# 附加描述# -------------------------- 自定义反序列化类 --------------------------classLazyUnpickler(pickle.Unpickler):""" 继承pickle.Unpickler,重写persistent_load和find_class方法, 用于解析HF格式权重文件中的序列化数据 """# 映射Torch存储类型到自定义的LazyStorageKind CLASSES ={('torch.util','_rebuild_tensor_v2'):lambda*args: args,('torch','BFloat16Storage'): LazyStorageKind(DT_BF16),('torch','HalfStorage'): LazyStorageKind(DT_F16),# Half对应F16('torch','FloatStorage'): LazyStorageKind(DT_F32),('torch','IntStorage'): LazyStorageKind(DT_I32)}def__init__(self, fp: IO[bytes], data_base_path:str, zip_file: zipfile.ZipFile):super().__init__(fp) self.data_base_path = data_base_path # 权重数据在zip包中的基础路径 self.zip_file = zip_file # zip文件对象defpersistent_load(self, pid: Any)-> Any:""" 处理持久化对象(HF权重文件中用pid标记权重存储位置) """# 从pid中解析数据类型、文件名、偏移量等信息 data_type = pid[1] filename =f"{self.data_base_path}/{pid[2]}"# 构造LazyStorage对象,描述权重的存储信息 description =f"persistent data_type={data_type} path-in-zip={filename}"return LazyStorage(kind=self.CLASSES[('torch', data_type)], description=description)deffind_class(self, module:str, name:str)-> Any:""" 重写类查找逻辑,映射Torch的存储类到自定义的LazyStorageKind """ifnot module.startswith('torch'):# 非Torch类,使用默认查找逻辑returnsuper().find_class(module, name)# Torch类,使用CLASSES映射return self.CLASSES.get((module, name),super().find_class(module, name))# -------------------------- 主函数:解析权重文件 --------------------------if __name__ =='__main__':# 1. 指定要解析的权重文件路径(替换为你的Llama 2权重文件路径) weight_path = Path(r"")#此处填你的模型根目录# 2. 以二进制模式打开权重文件(HF的.bin是zip压缩包)withopen(weight_path,'rb')as fp:# 3. 打开zip压缩包with zipfile.ZipFile(fp)as zip_file:# 4. 找到zip包中的data.pkl文件(序列化的权重数据) pickle_paths =[name for name in zip_file.namelist()if name.endswith('.pkl')]assertlen(pickle_paths)==1,"权重文件中应只有一个data.pkl"# 校验# 5. 打开data.pkl文件with zip_file.open(pickle_paths[0],'r')as pickle_fp:# 6. 使用自定义的LazyUnpickler反序列化数据 unpickler = LazyUnpickler( fp=pickle_fp, data_base_path=pickle_paths[0][:-4],# 去掉.pkl后缀,得到基础路径 zip_file=zip_file ) model_data = unpickler.load()# 反序列化得到模型权重字典# 7. 统计并打印权重参数信息 total_params =0# 总参数数量 total_bytes =0# 总字节数# 遍历权重字典中的每个参数for param_name, lazy_tensor in model_data.items():# 打印参数名、形状、类型print(f"{param_name}: shape={lazy_tensor.shape} type={lazy_tensor.data_type}")# 计算当前参数的数量 param_count =1for dim in lazy_tensor.shape: param_count *= dim # 计算当前参数的字节数(F16占2字节,F32占4字节)if lazy_tensor.data_type == DT_F16: param_bytes = param_count *2elif lazy_tensor.data_type == DT_F32: param_bytes = param_count *4else: param_bytes =0# 其他类型暂不统计# 累加总数 total_params += param_count total_bytes += param_bytes # 打印统计结果print(f"\n总参数数量: {total_params}")print(f"总字节数: {total_bytes} (约{total_bytes/1024/1024/1024:.2f}GB)")第82行填写你自己的模型根目录喵
输出大概是这样
model.embed_tokens.weight: shape=[32000, 4096] type=F16 model.layers.0.self_attn.q_proj.weight: shape=[4096, 4096] type=F16 model.layers.0.self_attn.k_proj.weight: shape=[4096, 4096] type=F16 model.layers.0.self_attn.v_proj.weight: shape=[4096, 4096] type=F16 model.layers.0.self_attn.o_proj.weight: shape=[4096, 4096] type=F16 model.layers.0.self_attn.rotary_emb.inv_freq: shape=[64] type=F32 model.layers.0.mlp.gate_proj.weight: shape=[11008, 4096] type=F16 model.layers.0.mlp.up_proj.weight: shape=[11008, 4096] type=F16 model.layers.0.mlp.down_proj.weight: shape=[4096, 11008] type=F16 model.layers.0.input_layernorm.weight: shape=[4096] type=F16 model.layers.0.post_attention_layernorm.weight: shape=[4096] ...... model.layers.23.self_attn.q_proj.weight: shape=[4096, 4096] type=F16 model.layers.23.self_attn.k_proj.weight: shape=[4096, 4096] type=F16 model.layers.23.self_attn.v_proj.weight: shape=[4096, 4096] type=F16 model.layers.23.self_attn.o_proj.weight: shape=[4096, 4096] type=F16 model.layers.23.self_attn.rotary_emb.inv_freq: shape=[64] type=F32 model.layers.23.mlp.gate_proj.weight: shape=[11008, 4096] type=F16 model.layers.23.mlp.up_proj.weight: shape=[11008, 4096] type=F16 model.layers.23.mlp.down_proj.weight: shape=[4096, 11008] type=F16 model.layers.23.input_layernorm.weight: shape=[4096] type=F16 model.layers.23.post_attention_layernorm.weight: shape=[4096] type=F16 参数数量=4988274176,字节数=9976551424