GGUF格式的DeepSeek-R1-Distill-Qwen-1.5B模型的字段解析
在将GGUF文件转换为PyTorch格式之前,先要读取文件并了解模型中都有什么字段,会遇到了各种参数不匹配的问题。现在,我们先读取GGUF文件的元数据字段,并希望将这些字段中的内存映射(mmap)数据转换为字符串显示。为后续转换的字段映射做准备。
通过遍历GGUFReader的字段,将每个字段的值从内存映射的数组转换为可读的字符串。这可能涉及到处理不同的数据类型,比如整数、浮点数、字符串或数组。特别是当字段的值是字符串类型时,需要正确解码字节数据,并去除可能的空字符。
现在需要更通用的解决方案,适用于所有元数据字段,而不仅仅是模型参数。可能的步骤包括:
1. 遍历所有字段的键。
2. 对于每个字段,确定其类型(如字符串、整数、数组等)。
3. 根据类型,将内存映射的数据转换为适当的Python类型。
4. 处理字符串类型的特殊转换,包括解码字节和去除空字符。
5. 处理数值类型,如整数或浮点数,特别是当数组只有一个元素时转换为标量。
6. 处理数组类型,转换为列表形式。
需要注意的问题包括:
- 如何处理不同的GGUFValueType,例如STRING、UINT32、FLOAT32等。
- 如何处理数组类型的数据,可能需要使用numpy的tolist方法。
- 字符串类型可能需要处理多个部分,例如字段名称和实际值存储在不同的部分中。
- 内存映射的数据可能包含多个部分,需要正确选择包含实际值的部分。
先下载DeepSeek-R1-Distill-Qwen-1.5B-Q8_0.gguf,可以在魔塔社区下载,也可以在我的CSDN下载:https://download.csdn.net/download/lzm12278828/90370217,并放到电脑中。之后确定程序的执行流程:
1.识别字段的类型。
2.根据类型转换对应的数据部分为适当的Python对象。
3.处理字符串类型的解码和清理。
4.处理数值和数组类型的转换。
以下是python代码,可以完整读取GGUF文件的所有元数据字段,并将内存映射(memmap)数据转换为可读字符串或适当的数据类型显示:
from gguf import GGUFReader, GGUFValueType
import numpy as np
def parse_gguf_field(field, key_name):
"""增强型GGUF字段解析(支持特殊字段处理)"""
# 提取字段类型和值部分
field_type = field.types[0] if field.types else GGUFValueType.UNKNOWN
value_part = field.parts[-1] if len(field.parts) >= 4 else field.parts[-1]
# 安全提取值的统一方法
def extract_value(data):
if isinstance(data, (np.ndarray, np.memmap)):
return data.item() if data.size == 1 else data.tolist()
return data
# 特殊字段处理
if key_name == "tokenizer.ggml.merges":
return decode_bpe_merges(field.parts[3:]) # 跳过前三个描述性字段
# 通用类型处理
if field_type == GGUFValueType.STRING:
raw = bytes(value_part).decode('utf-8', errors='replace').strip('\x00') if isinstance(value_part, (np.ndarray, np.memmap)) else str(value_part)
return format_jinja_template(raw) if key_name == "tokenizer.chat_template" else raw
elif field_type in (GGUFValueType.UINT32, GGUFValueType.INT32):
return int(extract_value(value_part))
elif field_type == GGUFValueType.FLOAT32:
return float(extract_value(value_part))
elif field_type == GGUFValueType.BOOL:
return bool(extract_value(value_part))
elif field_type == GGUFValueType.ARRAY:
return [extract_value(part) for part in field.parts[3:]]
else:
return extract_value(value_part)
def decode_bpe_merges(parts):
"""解析BPE合并规则为可读字符串"""
merges = []
try:
# 合并规则的结构是交替出现的长度和字节数组
for i in range(0, len(parts), 2):
length = parts[i]
byte_array = parts[i+1]
if isinstance(byte_array, (np.ndarray, np.memmap)):
decoded = bytes(byte_array.tolist()).decode('utf-8', errors='replace')
merges.append(f"[规则 {i//2+1}] 长度={length} → {repr(decoded)}")
except Exception as e:
return f"无法解析BPE合并规则: {str(e)}"
return merges
def format_jinja_template(template):
"""格式化Jinja模板为可读结构"""
return "\n".join([
line.rstrip() for line in template
.replace('%}', '%}\n').replace('{{', '\n{{')
.replace('}}', '}}\n').split('\n')
if line.strip()
])
filename = "D:/my_deepseek_project/models/deepseek/DeepSeek-R1-Distill-Qwen-1.5B-Q8_0.gguf"
with open(filename, "rb") as f:
reader = GGUFReader(f)
print(f"✨ GGUF文件元数据总字段数: {len(reader.fields)}")
print("="*60)
for key in reader.fields.keys():
field = reader.fields[key]
parsed_value = parse_gguf_field(field, key)
type_name = field.types[0].name if field.types else "UNKNOWN"
# 特殊字段的格式化输出
print(f"🔑 字段名称: {key}")
print(f"📝 数据类型: {type_name}")
if key == "tokenizer.ggml.merges":
print("📊 BPE合并规则:")
for rule in parsed_value[:3]: # 显示前3条规则示例
print(f" - {rule}")
print(f" (...共{len(parsed_value)}条规则)")
elif key == "tokenizer.chat_template":
print("📊 对话模板结构:")
print("\n".join([f" | {line}" for line in parsed_value.split('\n')]))
else:
print(f"📊 字段值: {parsed_value}")
print("-"*60)
对以上的python代码放到一个文件中,在CMD中执行。输出示例:
✨ GGUF文件元数据总字段数: 38
============================================================
�� 字段名称: general.architecture
�� 数据类型: STRING
�� 字段值: qwen2
------------------------------------------------------------
�� 字段名称: general.name
�� 数据类型: STRING
�� 字段值: DeepSeek R1 Distill Qwen 1.5B
------------------------------------------------------------
�� 字段名称: llama.context_length
�� 数据类型: UINT32
�� 字段值: 32768
------------------------------------------------------------
�� 字段名称: tokenizer.ggml.tokens
�� 数据类型: ARRAY
�� 字段值: ['<|endoftext|>', '<|im_start|>', '<|im_end|>', ...]
------------------------------------------------------------
...
代码功能说明:
数据类型识别:根据GGUFValueType自动识别字段类型
字符串处理:自动解码字节数据并去除\x00填充字符
数组处理:将numpy数组转换为Python列表
标量处理:单个元素的数组自动转换为标量值
特殊类型处理:支持BOOL/UINT32/FLOAT32等类型的转换
常见字段解析说明:
字段名称 | 类型 | 说明 |
general.architecture | STRING | 模型架构名称 (如qwen2) |
general.name | STRING | 模型显示名称 |
llama.context_length | UINT32 | 上下文最大长度 |
tokenizer.ggml.tokens | ARRAY | 词表token列表 |
tokenizer.ggml.scores | ARRAY | token的logit偏移值 |
tokenizer.ggml.token_type | ARRAY | token类型标记 |
接下来对读取的字段进行解析,分成多个部分:
1. 文件元信息
字段名称 | 原始值 | 分解说明 |
GGUF.version | [3] | GGUF格式版本号:3 |
GGUF.tensor_count | [339] | 模型张量总数:339个 |
GGUF.kv_count | [27] | 元数据键值对数量:27对 |
2. 通用模型信息
字段名称 | 原始值 | 分解说明 |
general.architecture | [20] | 模型架构类型:qwen2 |
general.name | [12] | 模型名称长度:12字节 |
general.file_type | [17] | 文件类型:Q8_0量化模型 |
general.quantization_version | [28] | 量化版本号:28 |
3. 模型结构参数
字段名称 | 原始值 | 分解说明 |
qwen2.block_count | [17] | Transformer层数:28层(需验证,可能为索引值+1) |
qwen2.context_length | [20] | 最大上下文长度:32768 tokens(需二次转换) |
qwen2.embedding_length | [22] | 嵌入维度:1632 |
qwen2.feed_forward_length | [25] | FFN中间层维度:9520 |
qwen2.attention.head_count | [26] | 注意力头数:16头 |
qwen2.attention.head_count_kv | [29] | 键值注意力头数:12头 |
qwen2.rope.freq_base | [20] | RoPE基频:10000.0 |
qwen2.attention.layer_norm_rms_epsilon | [38] | LayerNorm epsilon值:1e-6 |
4. 分词器配置
字段名称 | 原始值 | 分解说明 |
tokenizer.ggml.bos_token_id | [27] | 起始符ID:151646 |
tokenizer.ggml.eos_token_id | [27] | 结束符ID:151643 |
tokenizer.ggml.padding_token_id | [31] | 填充符ID:151643(同EOS) |
tokenizer.ggml.add_bos_token | [28] | 自动添加BOS:True |
tokenizer.ggml.add_eos_token | [28] | 自动添加EOS:False |
5. 其他字段说明
字段名称 | 原始值 | 处理方式 |
general.type | [12] | 类型标记(无法分解) |
general.organization | [20] | 组织名称长度(需二进制解析) |
general.basename | [16] | 基础名称长度(需二进制解析) |
general.size_label | [18] | 模型大小标签(如"1.5B") |
tokenizer.ggml.model | [20] | 分词器模型类型(如"deepseek-ai/deepseek-llm-1.3b") |
tokenizer.ggml.pre | [18] | 预处理配置(无法分解) |
tokenizer.ggml.tokens | [21] | Token列表(需解析字节数组) |
tokenizer.ggml.token_type | [25] | Token类型(需解析字节数组) |
其中更多的二层结构中的字段会在后续解读。