llama.cpp 模型转换实战:把 Karpathy llama2.c 权重导入 GGUF 格式完整指南
2026/9/7 8:40:27 网站建设 项目流程

llama.cpp 模型转换实战:把 Karpathy llama2.c 权重导入 GGUF 格式完整指南

【免费下载链接】llama.cppLLM inference in C/C++项目地址: https://gitcode.com/GitHub_Trending/ll/llama.cpp

本篇指南围绕 llama.cpp 仓库中的examples/convert-llama2c-to-ggml示例展开,讲解如何把 Karpathy llama2.c 训练出的模型二进制文件(如 tinyllamas 系列小模型)转换为 llama.cpp 可加载的 GGUF 格式:包括完整的命令行参数说明、llama2.c 权重文件的二进制布局、张量重命名映射、词表(vocabulary)的双来源加载机制,以及转换后使用llama-cli直接推理的实操步骤。读完后你可以独立完成“llama2.c 小模型 → GGUF → llama.cpp 推理”这条链路,并理解转换工具底层的文件解析与元数据写入逻辑。

一、这个工具解决什么问题

llama2.c 是 Karpathy 的一个轻量级 Llama 实现,其产出的模型是纯二进制文件(.bin),头部是一段Config结构体,后面紧跟按固定顺序排布的 F32 权重。llama.cpp 无法直接加载这种格式,llama-convert-llama2c-to-ggml工具的作用就是:

  1. 解析 llama2.c 模型文件的头部配置与全部权重;
  2. 从指定的 GGUF 模型或 llama2.c 词表文件中复制词表;
  3. 将权重逐一写入 ggml 张量,并生成符合 llama.cpp 约定的张量名与 GGUF 元数据,最终保存为标准 GGUF 文件。

该示例的说明文档位于 examples/convert-llama2c-to-ggml/README.md,实现代码为 examples/convert-llama2c-to-ggml/convert-llama2c-to-ggml.cpp。

需要注意一点:README 中提到的默认词表文件models/ggml-vocab.bin在源码中实际的默认值是models/7B/ggml-model-f16.gguf(见get_default_train_params()中的params.fn_vocab_model,约第 770 行)。由于models/目录下并不包含 7B 模型文件,实际使用时几乎总是需要显式传入--copy-vocab-from-model,这一点在 examples/convert-llama2c-to-ggml/README.md 给出的示例命令中也体现了。

二、构建工具与产物名称

该示例通过 CMake 构建,目标名在 examples/convert-llama2c-to-ggml/CMakeLists.txt 中定义:

set(TARGET llama-convert-llama2c-to-ggml) add_executable(${TARGET} convert-llama2c-to-ggml.cpp) install(TARGETS ${TARGET} RUNTIME) target_link_libraries(${TARGET} PRIVATE llama-common llama ${CMAKE_THREAD_LIBS_INIT}) target_compile_features(${TARGET} PRIVATE cxx_std_17)

两点值得注意:

  • 可执行产物名为llama-convert-llama2c-to-ggml。这是 2024 年 6 月二进制统一加llama-前缀改名之后的名称,旧名convert-llama2c-to-ggml的对照关系可参见 examples/deprecation-warning/README.md。
  • 从 examples/CMakeLists.txt 第 37-38 行可以看到,该示例仅在NOT GGML_BACKEND_DL(未启用后端动态加载)时才加入编译,因为它会直接使用 ggml 内部接口(如ggml_new_tensor_2dggml_set_f32_nd等)。因此默认构建配置下即可编译出该工具,配置方式参考仓库根目录 CMakeLists.txt。

三、命令行参数详解

工具的 usage 信息(由源码print_usage()输出)如下:

usage: ./llama-convert-llama2c-to-ggml [options] options: -h, --help show this help message and exit --copy-vocab-from-model FNAME path of gguf llama model or llama2.c vocabulary from which to copy vocab (default 'models/7B/ggml-model-f16.gguf') --llama2c-model FNAME [REQUIRED] model path from which to load Karpathy's llama2.c model --llama2c-output-model FNAME model path to save the converted llama2.c model (default ak_llama_model.bin')

结合参数解析代码params_parse()(第 829-882 行)整理成表:

参数是否必填默认值说明
--llama2c-model FNAME必填Karpathy llama2.c 格式模型文件路径,未提供会直接报错退出
--copy-vocab-from-model FNAME建议提供models/7B/ggml-model-f16.gguf词表来源,可以是任意 GGUF llama 模型,也可以是 llama2.c 词表二进制文件;工具会按文件魔数自动识别格式
--llama2c-output-model FNAME可选ak_llama_model.bin输出的 GGUF 文件路径(默认名虽为.bin,内容实为 GGUF)
-h, --help可选-打印帮助信息并退出

参数解析有一个细节:所有参数中的下划线会被统一替换为连字符(第 838-840 行的std::replace(arg.begin(), arg.end(), '_', '-')),因此--copy_vocab_from_model--copy-vocab-from-model等价。未知参数会报错并打印 usage。

四、llama2.c 模型文件的二进制布局

工具对输入文件的解析逻辑完全对应 llama2.c 的保存格式。头部是一个 64 字节的Config结构(第 75-83 行):

typedef struct { int dim; // transformer dimension int hidden_dim; // for ffn layers int n_layers; // number of layers int n_heads; // number of query heads int n_kv_heads; // number of key/value heads (can be < query heads because of multiquery) int vocab_size; // vocabulary size, usually 256 (byte-level) int seq_len; // max sequence length } Config;

main()中有一个关键判断(第 916-917 行):

auto shared_weights = config.vocab_size > 0; config.vocab_size = abs(config.vocab_size);

即 llama2.c 用vocab_size符号位表示是否共享权重(tied weights):为正表示输出层与嵌入层共享词表,此时文件末尾不保存独立的wcls分类器权重。

头部之后,checkpoint_init_weights()(第 157-186 行)按固定顺序依次fread以下权重块:

顺序权重形状(按层展开)
1token_embedding_table(vocab_size, dim)
2rms_att_weight(n_layers, dim)
3wq(n_layers, dim, dim)
4wk(n_layers, dim, dim / n_multiqueries)
5wv(n_layers, dim, dim / n_multiqueries)
6wo(n_layers, dim, dim)
7rms_ffn_weight(n_layers, dim)
8w1/w2/w3(FFN 的 gate / down / up)(n_layers, hidden_dim, dim) 等
9rms_final_weight(dim,)
10freq_cis_real+freq_cis_imag跳过(第 170-172 行用fseek越过seq_len × head_size × sizeof(float)字节)
11wcls(可选)(vocab_size, dim),仅在非共享权重时存在

读取完成后工具会校验文件指针是否恰好到达文件末尾(第 176-183 行),任何遗漏或多余数据都会报错退出——这是一种防御性的完整性检查。RoPE 的频率表freq_cis被跳过是合理的:它在 llama.cpp 中是在运行时由rope.dimension_count元数据动态重建的,无需随权重一起保存。

五、转换流程:从 float 数组到 GGUF 张量

转换分为三步:分配 ggml 张量 → 逐值拷贝权重 → 写入元数据与张量并落盘

5.1 超参数映射

第 931-940 行将 llama2.c 的Config映射到内部超参数结构:

model.hparams.n_vocab = config.vocab_size; model.hparams.n_ctx = params.n_ctx; // 默认 128,见 get_default_train_params() model.hparams.n_embd = config.dim; model.hparams.n_ff = config.hidden_dim; model.hparams.n_mult = 32; // 硬编码 model.hparams.n_head = config.n_heads; model.hparams.n_head_kv = config.n_kv_heads; model.hparams.n_layer = config.n_layers; model.hparams.n_rot = std::min((uint32_t)params.n_rotmax, model.hparams.n_embd / model.hparams.n_head);

这里值得注意两点:

  • n_ctx(上下文长度元数据)取默认值128,并非来自模型文件的seq_len;tinyllamas 这类小模型推理时通常够用,但若你的场景需要更长上下文,输出 GGUF 里写的是 128。
  • n_rot(RoPE 维度)取min(64, dim / n_head),与 llama.cpp 对 RoPE 维度的约定一致。

5.2 张量名映射

llama2.c 的扁平命名与 llama.cpp 的张量命名约定不同,save_as_llama_model()(第 651-766 行)在写入前会用ggml_format_name统一改名为 llama 架构的标准张量名:

llama2.c 权重GGUF 张量名
token_embedding_tabletoken_embd.weight
rms_final_weightoutput_norm.weight
wcls(或共享时的嵌入表)output.weight
第 i 层wq/wk/wv/woblk.{i}.attn_q.weight/blk.{i}.attn_k.weight/blk.{i}.attn_v.weight/blk.{i}.attn_output.weight
第 i 层rms_att_weight/rms_ffn_weightblk.{i}.attn_norm.weight/blk.{i}.ffn_norm.weight
第 i 层w1/w2/w3blk.{i}.ffn_gate.weight/blk.{i}.ffn_down.weight/blk.{i}.ffn_up.weight

权重拷贝本身由convert_weights_ak_to_gg()(第 638-649 行)完成:用ggml_unravel_index把一维索引展开为多维索引后逐个ggml_set_f32_nd写入,本质是一个朴素的逐元素拷贝。输出张量output.weight的处理遵循 tied weights 语义——若原模型共享权重,则直接复用token_embedding_table的数据(第 658 行)。

5.3 写入的 GGUF 元数据

落盘前(第 687-721 行)写入的元数据包括:

  • general.architecture = "llama"general.name = "llama"
  • 全部词表:tokenizer.ggml.tokens(字符串数组)、tokenizer.ggml.scores(F32 数组)、tokenizer.ggml.token_type(I32 数组);
  • 特殊 token id:<unk>= 0、<s>(BOS) = 1、</s>(EOS) = 2,seperator/padding置为 NULL;
  • 结构参数:llama.context_lengthllama.embedding_lengthllama.feed_forward_lengthllama.attention.head_count(_kv)llama.block_countllama.rope.dimension_countllama.attention.layer_norm_rms_epsilon = 1e-5

最终由gguf_write_to_file(ctx, filename, false)写盘,参数为 false 表示不量化、以 F32 保存——所以转换产物是未量化的 F32 GGUF,之后可以用llama-quantize自行量化。

六、词表加载:GGUF 模型与 llama2.c 词表两种来源

load_vocab()(第 532-636 行)先调用is_ggml_file()读取文件前 4 字节魔数判断是否为 GGUF,然后走两条分支:

分支一:传入 GGUF llama 模型(推荐)。要求该模型的tokenizer.ggml.model必须是"llama",否则断言失败;随后读取 token 列表、score 与 type 三个数组,并强制校验词表大小与 llama2.c 模型的vocab_size一致:

if (n_vocab != static_cast<uint32_t>(config->vocab_size)) { die_fmt("vocab size mismatch: (gguf) %u != (llama2c) %d", n_vocab, config->vocab_size); }

这就是为什么转换 7B 量级的 llama2.c 模型时,README 示例用llama-2-7b-chat.gguf.q2_K.bin这类 32000 词表的模型作为词表来源——词表大小必须严格匹配。

分支二:传入 llama2.c 原生词表文件(如 tinyllamas 附带的tok512.bin)。文件格式为先读一个未使用的max_token_length(u32),然后每个 token 依次是score(f32) +len(u32) + 文本。代码会为前三个 id 赋予特殊语义:id 0 →<unk>(UNKNOWN 类型)、id 1 →<s>、id 2 →</s>(CONTROL 类型);形如<0x%02hhX>的字节级 token 标记为 BYTE 类型。此外所有词表文本中的空格都会被转义为 U+2581(▁)(llama_escape_whitespaces(),第 523-530 行),这是 llama BPE 分词器的约定,用于区分词内空格与词间空格。

examples/convert-llama2c-to-ggml/README.md 中特别提醒:stories260K.bin的词表必须用它自己目录下的tok512.bin,而不是 stories42M 的词表,否则词表大小不匹配会直接触发上面的die_fmt报错。

七、完整实操流程

以 Karpathy 的 tinyllamas 数据集(HuggingFace 上的karpathy/tinyllamas,包含 stories42M.bin、stories110M.bin、stories260K.bin 等)为例,完整流程如下:

第 1 步:从 tinyllamas 数据集下载目标模型,例如stories42M.bin

第 2 步:准备一个词表来源。stories42M 使用与 LLaMA 相同的 tokenizer,任选一个词表为 32000 的 llama GGUF 模型(任意量化版本即可,因为只读其词表元数据)。

第 3 步:执行转换(README 中的原始示例命令):

./llama-convert-llama2c-to-ggml \ --copy-vocab-from-model llama-2-7b-chat.gguf.q2_K.bin \ --llama2c-model stories42M.bin \ --llama2c-output-model stories42M.gguf.bin

第 4 步:用llama-cli加载转换后的模型直接推理(README 中的示例):

./llama-cli -m stories42M.gguf.bin -p "One day, Lily met a Shoggoth" -n 500 -c 256

-n 500生成 500 个 token,-c 256指定 256 的上下文窗口(注意这超过了转换时写入的 128 元数据值,llama.cpp 允许在加载时通过-c覆盖)。

stories260K 的例外:它是 260K 词表的小模型,必须使用自带的tok512.bin词表:

./llama-convert-llama2c-to-ggml \ --copy-vocab-from-model stories260K/tok512.bin \ --llama2c-model stories260K.bin \ --llama2c-output-model stories260K.gguf.bin

转换成功后,产物是一个 F32 GGUF 文件,除本示例外,llama.cpp 仓库自身的测试也围绕这类 tinyllamas 小模型展开——例如 tools/server/tests/unit/test_router.py 中使用了ggml-org/test-model-stories260K作为服务端测试模型,可以佐证 stories260K 这类模型是 llama.cpp 生态中常见的轻量验证模型。

八、使用限制与注意事项

结合源码实现,使用这个转换器时需注意以下边界:

  1. 词表大小必须严格一致:GGUF 词表分支会做n_vocab比对,不匹配直接终止。为不同词表的模型(7B 系 vs 260K 系)选错词表来源是最常见的报错来源。
  2. 输出为 F32 未量化格式gguf_write_to_file(ctx, filename, false)固定第三参数为 false;如需更小体积需另行量化。
  3. 上下文长度元数据固定为 128n_ctxtrain_params默认值而非模型文件的seq_len,推理时如需更大上下文请用-c参数显式指定。
  4. RoPE 频率表不随文件保存:被fseek跳过,由 llama.cpp 运行时按llama.rope.dimension_count重建。
  5. 构建前提:仅在未启用GGML_BACKEND_DL时可编译(见 examples/CMakeLists.txt 第 37-38 行),因为它直接依赖 ggml 内部 API。
  6. 默认词表路径可能不存在:默认值models/7B/ggml-model-f16.gguf并非仓库自带文件(仓库models/目录只提供各架构的词表 GGUF 样例,如 models/ggml-vocab-llama-bpe.gguf.inp),实际使用请显式指定词表来源。

九、小结

llama-convert-llama2c-to-ggml是理解 llama.cpp 模型格式的绝佳切入口:它把 llama2.c 那种“头部配置 + 顺序 F32 块”的朴素布局,逐张量翻译成了 llama.cpp 的blk.{i}.*命名约定与完整的tokenizer.ggml.*/llama.*元数据集合。整个转换链路只有三个参数,但涉及词表大小校验、tied weights 判断、特殊 token 语义分配与空格转义等细节,源码约 960 行、全部集中在 examples/convert-llama2c-to-ggml/convert-llama2c-to-ggml.cpp 一个文件内,适合作为学习 GGUF 张量命名与元数据规范的入门范例。

【免费下载链接】llama.cppLLM inference in C/C++项目地址: https://gitcode.com/GitHub_Trending/ll/llama.cpp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询