gemma.cpp 库集成实战:用 simplified_gemma 模板构建最小文本生成应用
2026/9/17 11:59:56 网站建设 项目流程

gemma.cpp 库集成实战:用 simplified_gemma 模板构建最小文本生成应用

【免费下载链接】gemma.cpplightweight, standalone C++ inference engine for Google's Gemma models.项目地址: https://gitcode.com/GitHub_Trending/ge/gemma.cpp

导读

simplified_gemma 是 gemma.cpp 仓库中最简洁的“把 Gemma 模型作为 C++ 库来使用”的模板项目:它不提供交互式界面,而是用几行代码完成模型加载、状态初始化,并针对一个硬编码的 prompt 生成文本。读完本文,你将掌握基于 CMake + FetchContent 的两种构建方式(拉取远程 libgemma 与使用本地源码)、--tokenizer/--weights/--model三参数运行方式,以及--reject受约束解码(constrained decoding)的用法,并能读懂SimplifiedGemma封装背后的RuntimeConfigstream_tokenaccept_token回调机制,把它改造成自己的生成程序。

一、示例定位:最小化、可复制的“库用法”模板

examples/README.md对该目录的定位说明得很清楚:这里展示的是“超越交互式gemma应用(run.cc)之外的、把 gemma.cpp 作为库使用的简单示例”,其中hello_world/被描述为“minimal/template project”(最小模板项目)。simplified_gemma/README.md 进一步明确:

  • 它是一个模板工程,用于演示将gemma.cpp作为库集成;
  • 没有交互界面,模型状态设置好后,针对单个硬编码 prompt生成文本;
  • 构建步骤与主gemma可执行程序类似,目前仅支持cmake/make构建。

与 hello_world 相比,simplified_gemma 额外演示了受约束解码(--reject),并且把GemmaThreadingContextMatMulEnvKVCache等底层组件封装成了一个更接近实战的SimplifiedGemma类,是学习 gemma.cpp 库接口的绝佳起点。

二、构建:两种模式(远程 libgemma / 本地 gemma.cpp)

gemma.cpp/examples/simplified_gemma目录下先执行 CMake 配置:

cmake -B build

这会生成gemma.cpp/examples/simplified_gemma/build构建目录。注意:默认模式会通过 FetchContent 从 git 仓库的固定 commit 拉取libgemma(源码见 CMakeLists.txt:BUILD_MODE未指定时默认为remoteFetchContent_Declare(gemma GIT_REPOSITORY https://github.com/google/gemma.cpp.git GIT_TAG a9aa63fd...),同时还会拉取 google/highway 与 google/sentencepiece 两个依赖,分别固定到各自的 commit hash)。

如果你想基于当前本地版本的 gemma.cpp构建,改用:

cmake -B build -DBUILD_MODE=local

local模式下,CMake 通过FetchContent_Declare(gemma SOURCE_DIR ../../..)(CMakeLists.txt)直接引用从examples/simplified_gemma/build/向上三层即仓库根目录的源码,不再执行网络拉取。

切换构建配置前,务必清空 build 目录内容,避免缓存了旧的 FetchContent 结果导致配置错乱。

配置完成后进入构建目录执行 make:

cd build make simplified_gemma

与顶层 gemma.cpp 项目一致,可用-j参数并行加速,例如make -j8 simplified_gemma。CMake 相关细节可从 CMakeLists.txt 确认:项目要求 C++17、未指定构建类型时默认Release,可执行文件simplified_gemmarun.cc编译而来,并链接hwy hwy_contrib sentencepiece libgemma四个库(第45-46行)。

三、运行:与 gemma.cpp 相同的三参数命令行

构建完成后,build目录下会出现simplified_gemma可执行文件。运行参数与主gemma程序完全一致,同样是三个模型参数:tokenizer 路径、压缩权重文件路径、模型类型

./simplified_gemma --tokenizer tokenizer.spm --weights 2b-it-sfp.sbs --model 2b-it

各参数含义(与顶层 README.md 的 gemma 运行参数一致):

参数说明示例值
--tokenizertokenizer 模型路径tokenizer.spm
--weights压缩后的模型权重文件(.sbs)2b-it-sfp.sbs
--model模型类型标识2b-it

权重与 tokenizer 文件从 Kaggle 的 Gemma cpp 版本页面下载(顶层 README.md 给出了完整的获取与解压流程,2b-it-sfp.sbs即 2B instruction-tuned 模型的 8-bit 切换浮点(SFP)压缩权重;2025-05-06 之后生成的单文件格式权重也可省略 tokenizer 参数)。

运行成功后会向终端输出一段问候语,例如:

"Hello, world! It's a pleasure to greet you all. May your day be filled with joy, peace, and all the things that make your heart soar.

四、源码主流程:run.cc 的三步骨架

打开 run.cc,整个程序只有三个核心步骤,正是“把 gemma.cpp 当库用”的标准骨架:

第 1 步:解析命令行参数。gcpp::LoaderArgs loader(argc, argv);从命令行读取并校验 tokenizer、weights 等参数。LoaderArgs定义在 gemma_args.h,内部通过ArgsBaseInitAndParse完成解析,支持的字段包括tokenizerweightsmap(是否启用内存映射,-1 自动/0 否/1 是)、to_bf16(是否将权重转为 bf16)、wrapping(是否启用 prompt 包装)。

第 2 步:构造模型并生成。代码注释给出了三种用法:

  • 最简用法:SimplifiedGemma gemma(loader);
  • 显式传入路径:gcpp::LoaderArgs loader("/path/to/tokenizer", "/path/to/weights", "model_identifier");
  • 完整用法:额外传入gcpp::InferenceArgs inference(argc, argv);gcpp::ThreadingArgs threading(argc, argv);,再SimplifiedGemma gemma(loader, threading, inference);——不传则使用默认值。

第 3 步:硬编码 prompt 并生成。示例固定使用:

std::string prompt = "Write a greeting to the world."; gemma.Generate(prompt, 256, 0.6);

即生成最多 256 个 token、采样温度 0.6的输出。Generate的完整签名(gemma.hpp)为:

void Generate(std::string& prompt, size_t max_generated_tokens = 1024, float temperature = 0.7, const std::set<int>& reject_tokens = {});

三个可选参数分别为最大生成 token 数(默认 1024)、采样温度(默认 0.7)、以及用于受约束解码的被拒绝 token ID 集合

五、深入 SimplifiedGemma:一个可复用的封装类

gemma.hpp 中的SimplifiedGemma类把 gemma.cpp 的底层组件组装成了可直接复用的封装。理解它的构造函数,也就理解了 gemma.cpp 作为库运行所需的最小对象集合(第35-45行):

SimplifiedGemma(const gcpp::LoaderArgs& loader, const gcpp::ThreadingArgs& threading = gcpp::ThreadingArgs(), const gcpp::InferenceArgs& inference = gcpp::InferenceArgs()) : ctx_(threading), // 线程上下文:管理线程池 env_(ctx_), // MatMul 执行环境 gemma_(loader, inference, ctx_), // 核心 Gemma 对象 kv_cache_(gemma_.Config(), inference, ctx_.allocator) { std::random_device rd; gen_.seed(rd()); // 随机数发生器,供采样使用 }

成员对象与Gemma类的对应关系(见 gemma.h 中Gemma的公开接口):gemma_.Config()返回ModelConfiggemma_.Tokenizer()返回分词器,gemma_.ChatTemplate()返回聊天模板——这些都在Generate内部被直接调用。

Generate的生成流水线分为三个阶段:

  1. 分词与包装gcpp::WrapAndTokenize(gemma_.Tokenizer(), gemma_.ChatTemplate(), gemma_.Config().wrapping, generated, prompt)将原始字符串转为 token ID 序列(使用模型配置中的 wrapping 设置决定是否套用对话模板)。
  2. 流式回调:定义stream_token回调,每当生成一个 token 就被调用一次(见 gemma.hpp)。回调中通过generated < prompt_size判断当前仍是 prompt 阶段(静默跳过),否则调用gemma_.Tokenizer().Decode({token}, &token_text)把 token 解码为文本并std::cout << token_text << std::flush实时打印——这就是输出逐字“流式”出现的原因。
  3. 调用底层生成:组装gcpp::RuntimeConfig后调用gemma_.Generate(runtime_config, tokens, 0, kv_cache_, env_, timing_info)RuntimeConfig定义于 gemma_args.h,示例中配置了max_generated_tokenstemperature、随机数发生器genverbosity = 0stream_tokenaccept_tokenGemma::Generate的签名(gemma.h)要求调用方显式传入pos(KV cache 中的位置,单轮对话传 0)、KVCacheMatMulEnvTimingInfo——这四点正是库模式与交互式 CLI 的主要差异,也是二次开发时需要理解的关键约定。

六、受约束解码:--reject 拒绝指定 token

README 专门演示了受约束解码(constrained decoding)。运行时可加--reject标志,后面跟一串token ID

./simplified_gemma [...] --reject 32338 42360 78107 106837 132832 143859 154230 190205

两个使用要点:

  • --reject必须是命令行最后一个参数,因为它会消费其后所有的参数作为 token ID 列表;
  • 参数是一串数字形式的 token ID,例如示例中列出的 8 个 ID 是“greeting”一词在不同上下文中出现的各种变体。

其底层机制对应RuntimeConfig::accept_token回调(gemma.hpp):

.accept_token = & { return !reject_tokens.contains(token); },

AcceptFunc的契约(gemma_args.h)是:对每个候选 token,返回 false 表示拒绝生成,返回 true 表示接受。因此--reject的实现就是维护一个std::set<int>,凡落在集合中的 token 一律返回 false,从候选集合中“屏蔽”掉。--reject的解析发生在LoaderArgs/InferenceArgs等通用参数解析之前,由示例自身负责收集(收集到的 ID 通过SimplifiedGemma::Generatereject_tokens形参传入),这就是它必须放在最后的原因。

值得注意的是,accept_token是 gemma.cpp 提供的通用机制——除了拒绝列表,你完全可以实现自定义约束(例如只允许特定词表子集、强制输出数字等),这也是该示例对“库化使用”最具启发性的部分。

七、从模板到自有应用:可扩展的方向

基于该模板改造为自有应用时,参考 gemma_args.h 中InferenceArgs的参数表,可在构造SimplifiedGemma时传入并控制更多生成行为:

参数默认值说明
--max_generated_tokens4096最大生成 token 数(示例中直接调用Generate(prompt, 256, 0.6)覆写为 256)
--temperature1.0top-K 采样温度
--top_k1采样时考虑的候选 token 数
--verbosity10 仅打印生成结果 / 1 标准终端 UI / 2 开发者调试信息
--seq_len8192序列长度(上限由ModelConfig.max_seq_len决定)
--deterministicfalse使 top-K 采样确定性化
--prefill_tbatch256prefill 阶段每批最大 token 数
--decode_qbatch16decode 阶段每批最大查询数

InferenceArgs::CopyTo(gemma_args.h)负责把这些值同步进RuntimeConfig,并校验 batch 尺寸不超过MMStorage::kMaxM

在此基础上,常见的改造方向包括:将硬编码 prompt 改为--prompt/--prompt_file参数驱动(非交互式一键生成,见InferenceArgs中对应字段);将Generate循环多次调用以实现多轮对话(配合--multiturn决定是否保留 KV cache);把stream_token回调里的std::cout替换为 WebSocket/文件输出,即可快速搭建一个最小生成服务——这一切都从 run.cc 这个不足 30 行的入口开始。

八、总结

simplified_gemma 用最小的代码量展示了 gemma.cpp 库模式的完整闭环:CMake 配置(本地/远程双模式)→ make 构建 → 三参数运行 → 流式输出 →--reject受约束解码。透过 gemma.hpp 与 run.cc 两处核心源码,可以清晰看到它如何把LoaderArgsThreadingContextMatMulEnvKVCacheRuntimeConfig组装成可复用的SimplifiedGemma类,而stream_token/accept_token两个回调则分别承载了输出流式化与解码约束两大扩展点。对于想要将 Gemma 能力嵌入自有 C++ 项目的开发者而言,这个模板是比直接阅读gemma交互式 CLI 更短、更聚焦的集成起点。

【免费下载链接】gemma.cpplightweight, standalone C++ inference engine for Google's Gemma models.项目地址: https://gitcode.com/GitHub_Trending/ge/gemma.cpp

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

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

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

立即咨询