PowerInfer 极简文本生成指南:基于 examples/simple 源码剖析最小 LLM 推理流程 人工智能大模型推理引擎本地部署【免费下载链接】PowerInferHigh-speed Large Language Model Serving for Local Deployment项目地址https://gitcode.com/gh_mirrors/po/PowerInfer点击查看免费下载PowerInfer 是一个面向本地部署的高性能大语言模型LLM推理引擎其核心思路是利用 LLM 推理中神经元激活的幂律分布特性少量热神经元被持续激活、绝大多数冷神经元随输入变化通过 GPU-CPU 混合推理实现单消费级显卡上的高速文本生成。examples/simple是仓库中最小的端到端示例它不带任何交互逻辑、采样策略或并行调度只保留加载模型 → 分词 → 解码 → 贪心采样 → 输出文本这条最纯粹的主链路。阅读本文后你将掌握 PowerInfer/llama.cpp 兼容 API 的最核心调用序列理解llama_batch、KV Cache、logits 与贪心采样之间的协作关系并能基于此最小骨架自行扩展出更多示例。一、示例定位与运行方式1.1 这个示例解决什么问题examples/simple/README.md明确指出该示例的用途是演示使用给定 prompt 生成文本的最小 llama.cpp及其兼容引擎 PowerInfer用法。它没有交互模式、没有批处理、没有 grammar 约束甚至没有温度采样——代码里只用贪心策略选取概率最高的下一个 token。对应源码位于 examples/simple/simple.cpp。从main函数开头可以看出它的接口极其简单if (argc 1 || argv[1][0] -) { printf(usage: %s MODEL_PATH [PROMPT]\n , argv[0]); return 1; }只接受两个位置参数模型路径和可选的prompt。examples/simple/README.md中的运行示例为./simple ./models/llama-7b-v2/ggml-model-f16.gguf Hello my name is1.2 构建方式与产物simple示例通过 CMake 构建。其编译配置见 examples/simple/CMakeLists.txtset(TARGET simple) add_executable(${TARGET} simple.cpp) install(TARGETS ${TARGET} RUNTIME) target_link_libraries(${TARGET} PRIVATE common llama ${CMAKE_THREAD_LIBS_INIT}) target_compile_features(${TARGET} PRIVATE cxx_std_11)依赖commoncommon/CMakeLists.txt 定义的参数解析与工具库和llama核心推理库需要 C11 标准在 examples/CMakeLists.txt 中被add_subdirectory(simple)引入随整个 examples 树一起构建。从仓库根目录构建以 NVIDIA GPU 场景为例详见 README.md 的 Setup and Installation 一节cmake -S . -B build -DLLAMA_CUBLASON cmake --build build --config Release -j构建完成后可执行文件位于build/bin/simple或构建目录的对应 bin 子目录。1.3 参数默认值simple示例没有使用gpt_params_parse那套完整的 CLI 解析那是main示例的能力而是直接读取argv[1]与argv[2]if (argc 2) { params.model argv[1]; } if (argc 3) { params.prompt argv[2]; } if (params.prompt.empty()) { params.prompt Hello my name is; }也就是说第一个参数模型文件路径必填第二个参数prompt可省略省略时使用默认 promptHello my name is生成长度硬编码为n_len 32即总序列长度含 prompt最多 32 个 token。这些字段来自gpt_params结构体common/common.h其中params.model默认指向models/7B/ggml-model-f16.ggufparams.prompt默认为空字符串params.n_threads默认取物理核心数get_num_physical_cores()。二、初始化从后端到模型与上下文2.1 后端初始化llama_backend_init(params.numa);对应 llama.h 中的声明// Initialize the llama ggml backend // If numa is true, use NUMA optimizations // Call once at the start of the program LLAMA_API void llama_backend_init(bool numa);它负责初始化 llama 与 ggml 底层后端程序开始时调用一次。numa参数用于启用 NUMA 优化对多路 CPU 服务器有帮助。程序结束时对应的清理函数是llama_backend_free()。2.2 加载模型llama_model_params model_params llama_model_default_params(); // model_params.n_gpu_layers 99; // offload all layers to the GPU llama_model * model llama_load_model_from_file(params.model.c_str(), model_params);llama_model_default_params()返回模型加载参数的默认值代码中注释掉的model_params.n_gpu_layers 99是 GPU 卸载的关键开关在 PowerInfer 的 GPU-CPU 混合推理设计里更多层卸载到 GPU 通常意味着更高吞吐但受显存限制llama_load_model_from_file()在 llama.h 中的签名为LLAMA_API struct llama_model * llama_load_model_from_file( const char * path_model, struct llama_model_params params);加载失败时返回NULL示例会打印unable to load model并退出。2.3 创建上下文Contextllama_context_params ctx_params llama_context_default_params(); ctx_params.seed 1234; ctx_params.n_ctx 2048; ctx_params.n_threads params.n_threads; ctx_params.n_threads_batch params.n_threads_batch -1 ? params.n_threads : params.n_threads_batch; llama_context * ctx llama_new_context_with_model(model, ctx_params);三个关键字段seed 1234随机数种子固定保证每次运行结果可复现由于采样是贪心的种子影响相对有限但在引入随机采样后会起作用n_ctx 2048上下文窗口大小即 KV Cache 能容纳的最大 token 数n_threads/n_threads_batch解码线程数。n_threads_batch -1时回退为n_threads即批处理线程数默认跟随生成线程数。llama_new_context_with_model()的声明同样在 llama.hLLAMA_API struct llama_context * llama_new_context_with_model( struct llama_model * model, struct llama_context_params params);创建失败同样返回NULL并报错退出。三、分词与 KV Cache 容量校验3.1 分词std::vectorllama_token tokens_list; tokens_list ::llama_tokenize(ctx, params.prompt, true);第三个参数add_bos true表示在 prompt 前自动加上句首标记BOS。llama_tokenize是 common/common.h 提供的便捷封装其头注释明确说明行为应与 Python 的tokenizer.encode类似。3.2 KV Cache 容量校验const int n_ctx llama_n_ctx(ctx); const int n_kv_req tokens_list.size() (n_len - tokens_list.size()); if (n_kv_req n_ctx) { LOG_TEE(%s: error: n_kv_req n_ctx, the required KV cache size is not big enough\n, __func__); LOG_TEE(%s: either reduce n_parallel or increase n_ctx\n, __func__); return 1; }这里n_kv_req tokens_list.size() (n_len - tokens_list.size())实际上恒等于n_len当 prompt token 数小于n_len时。它的含义是要生成到n_len长度KV Cache 总共需要容纳的 token 数。如果该需求超过了n_ctx就无法继续示例会提示要么降低并行度、要么增大上下文窗口。这正是 KV Cache 设计的基本约束prompt 与已生成 token 的 KV 状态都要驻留在缓存中直到序列结束。随后示例把 prompt 逐 token 打印到 stderrfor (auto id : tokens_list) { fprintf(stderr, %s, llama_token_to_piece(ctx, id).c_str()); }llama_token_to_piece把 token id 还原为文本片段相当于 Python 的tokenizer.id_to_piece。四、核心解码循环batch → decode → sample4.1 初始化 batchllama_batch batch llama_batch_init(512, 0, 1);llama_batch_init在 llama.h 中这样定义// Allocates a batch of tokens on the heap that can hold a maximum of n_tokens // Each token can be assigned up to n_seq_max sequence ids // The batch has to be freed with llama_batch_free() // If embd ! 0, llama_batch.embd will be allocated with size of n_tokens * embd * sizeof(float) // Otherwise, llama_batch.token will be allocated to store n_tokens llama_token LLAMA_API struct llama_batch llama_batch_init( int32_t n_tokens, int32_t embd, int32_t n_seq_max);三个参数分别表示batch 最大可容纳 token 数512、嵌入维度0 表示普通 token 模式而非 embedding 输入、每个 token 最多可关联的序列数1即单序列场景。4.2 提交 prompt 并解码for (size_t i 0; i tokens_list.size(); i) { llama_batch_add(batch, tokens_list[i], i, { 0 }, false); } // llama_decode will output logits only for the last token of the prompt batch.logits[batch.n_tokens - 1] true; if (llama_decode(ctx, batch) ! 0) { LOG_TEE(%s: llama_decode() failed\n, __func__); return 1; }llama_batch_add逐个把 prompt token 放入 batch位置pos i序列 id 为{0}logits false表示默认不输出该 token 的 logits关键优化点只有 batch 中最后一个 token 的logits被置为 true。注释解释了原因——llama_decode只为最后那个 token 输出 logits这正是预测下一个 token所必需的从而避免为整个 prompt 计算并保留 logits 的开销llama_decode的返回值语义见 llama.h// Positive return values does not mean a fatal error, but rather a warning. // 0 - success // 1 - could not find a KV slot for the batch (try reducing the size of the batch or increase the context) // 0 - error LLAMA_API int llama_decode( struct llama_context * ctx, struct llama_batch batch);返回 1 表示 KV Cache 中没有足够槽位容纳该 batch——这是减小 batch 或增大n_ctx的信号。4.3 采样与生成循环int n_cur batch.n_tokens; // 当前序列长度从 prompt 长度开始 int n_decode 0; while (n_cur n_len) { auto n_vocab llama_n_vocab(model); auto * logits llama_get_logits_ith(ctx, batch.n_tokens - 1); std::vectorllama_token_data candidates; candidates.reserve(n_vocab); for (llama_token token_id 0; token_id n_vocab; token_id) { candidates.emplace_back(llama_token_data{ token_id, logits[token_id], 0.0f }); } llama_token_data_array candidates_p { candidates.data(), candidates.size(), false }; const llama_token new_token_id llama_sample_token_greedy(ctx, candidates_p); if (new_token_id llama_token_eos(model) || n_cur n_len) { break; } LOG_TEE(%s, llama_token_to_piece(ctx, new_token_id).c_str()); llama_batch_clear(batch); llama_batch_add(batch, new_token_id, n_cur, { 0 }, true); n_decode 1; n_cur 1; if (llama_decode(ctx, batch)) { fprintf(stderr, %s : failed to eval, return code %d\n, __func__, 1); return 1; } }逐步拆解取 logitsllama_get_logits_ith(ctx, batch.n_tokens - 1)返回最近一次解码 batch 中第batch.n_tokens - 1个 token 的 logits 指针。按 llama.h 的说明它等价于llama_get_logits(ctx) i*n_vocab是一个n_vocab长度的浮点数组每个元素对应词表中的一个候选 token。构造候选数组把 logits 逐项包装成llama_token_data{ token_id, logit, 0.0f }构成llama_token_data_array。p概率字段初始为 0由采样函数内部计算。贪心采样llama_sample_token_greedy在 llama.h 中说明为选择概率最高的 token不计算 token 概率/// details Selects the token with the highest probability. /// Does not compute the token probabilities. Use llama_sample_softmax() instead. LLAMA_API llama_token llama_sample_token_greedy( struct llama_context * ctx, llama_token_data_array * candidates);终止判断若采到 EOSllama_token_eos(model)即句末标记或达到n_len长度上限退出循环。llama_token_eos在 llama.h 中与llama_token_bos句首、llama_token_nl换行并列属于特殊 token 查询接口。单 token 续接llama_batch_clear清空 batch再llama_batch_add把新 token 以位置n_cur、logits true加入随后llama_decode。这里体现了自回归生成的标准形态每个新生成的 token 都作为下一次解码的输入而 KV Cache 会保存此前所有 token 的键值状态因此无需重新计算历史。llama_batch_clear与llama_batch_add同样是 common/common.h 声明的 batch 工具函数负责内部数组的重置与追加。4.4 计时与清理llama_print_timings(ctx); ... llama_batch_free(batch); llama_free(ctx); llama_free_model(model); llama_backend_free();llama_print_timings输出分阶段的耗时统计load time、sample time、prompt eval time、eval time、total time四步清理与初始化严格对称释放 batch → 释放上下文 → 释放模型 → 释放后端。五、输出解读一次真实运行的性能指标examples/simple/README.md给出了示例输出逐行解读如下main: n_len 32, n_ctx 2048, n_parallel 1, n_kv_req 32 Hello my name is Shawn and Im a 20 year old male from the United States. Im a 20 year old main: decoded 27 tokens in 2.31 s, speed: 11.68 t/s llama_print_timings: load time 579.15 ms llama_print_timings: sample time 0.72 ms / 28 runs ( 0.03 ms per token, 38888.89 tokens per second) llama_print_timings: prompt eval time 655.63 ms / 10 tokens ( 65.56 ms per token, 15.25 tokens per second) llama_print_timings: eval time 2180.97 ms / 27 runs ( 80.78 ms per token, 12.38 tokens per second) llama_print_timings: total time 2891.13 msn_kv_req 32即前文计算的 KV Cache 需求prompt 的 10 个 token 待生成的 22 个 token 之和不超过 32实际输出 27 个新 token 后命中 EOS 或长度条件decoded 27 tokens in 2.31 s, speed: 11.68 t/s这是示例自己统计的生成吞吐不含 prompt 预填充阶段prompt eval timeprompt 预填充prefill阶段10 个 token 共 655.63 ms这是并行计算的因此单 token 65.56 ms 但整体吞吐 15.25 tokens/seval time自回归解码decode阶段27 个 token 共 2180.97 ms每 token 80.78 ms12.38 tokens/s——逐 token 串行所以单 token 耗时与整体吞吐直接对应sample time采样本身极快0.72 ms / 28 runs几乎不构成瓶颈。需要说明的是这些数字来自该文档在示例机器模型为 llama-7b 系 f16 量化上的真实输出具体数值会随硬件、模型、量化方式与线程数变化不具备跨环境可比性。读者在自己的机器上运行时会得到不同的绝对数值但prefill 并行、decode 串行、采样开销极低这一规律是稳定的。六、从最小示例到完整应用扩展路径simple之所以叫 simple是因为它刻意省略了生产级能力。对照仓库中的其他示例与公共库可以沿以下方向扩展完整 CLI 参数main示例examples/main/main.cpp通过gpt_params_parsecommon/common.h 声明支持-n生成 token 数、-c上下文大小、-t线程数、--seed、-nglGPU 层数等全套参数更丰富的采样策略本示例只用llama_sample_token_greedy。C API 还提供llama_sample_token_mirostat、llama_sample_token_mirostat_v2Mirostat 1.0/2.0目标困惑度控制、llama_sample_token按概率随机采样等见 llama.hcommon/sampling.h中的llama_sampling_context则封装了 temperature、top-k、top-p、penalty 等完整采样管线多序列批处理把llama_batch_init的第三个参数n_seq_max从 1 改为更大值、为不同 token 分配不同seq_id即可并行处理多条序列——这是 examples/batched/batched.cpp 展示的用法服务化部署以simple的模型加载与解码逻辑为骨架套上 HTTP 层就是 examples/server/server.cpp 所做的事情支持 OpenAI 兼容接口的本地服务GPU 加速PowerInfer 的 GPU-CPU 混合推理README.md 中描述的 hot/cold 神经元分工依赖 CUDA 后端构建时通过-DLLAMA_CUBLASON开启示例源码中注释掉的model_params.n_gpu_layers 99即提示了层级卸载入口。七、小结examples/simple以约 180 行 C 代码完整呈现了 PowerInfer/llama.cpp 兼容 API 的最小推理闭环llama_backend_init→ 初始化后端llama_load_model_from_filellama_new_context_with_model→ 装载模型与上下文llama_tokenize→ prompt 分词llama_batch_initllama_batch_addllama_decode→ 预填充并取得 logitsllama_get_logits_ithllama_sample_token_greedy→ 贪心采样循环单 token 解码 → 采样 → 回填 batch直到 EOS 或长度上限llama_print_timings输出分阶段耗时对称清理资源。理解这七步就等于掌握了整个 PowerInfer/llama.cpp 生态的通用推理范式batch 提交、KV Cache 管理、logits 采样与自回归续接。无论是改造成交互式聊天、接入批处理服务还是换成更复杂的采样管线都是在这条主链路上做加法。赞分享人工智能大模型推理引擎本地部署【免费下载链接】PowerInferHigh-speed Large Language Model Serving for Local Deployment项目地址https://gitcode.com/gh_mirrors/po/PowerInfer点击查看免费下载相关推荐PowerInfer smallthinker 最小示例解析用 llama-simple 跑通本地 LLM 文本生成全流程PowerInfer smallthinker 最小示例解析用 llama simple 跑通本地 LLM 文本生成全流程 llama simple 是 Po人工智能大模型推理引擎本地部署PowerInfer 中基于猜测解码Speculative Decoding的快速生成speculative-simple 示例实战与原理剖析PowerInfer 中基于猜测解码Speculative Decoding的快速生成speculative simple 示例实战与原理剖析 导读 本文人工智能大模型推理引擎本地部署LLM本地推理全流程基于LMDeploy的pipeline实战指南在大语言模型LLM应用落地过程中本地环境的高效推理部署是开发者面临的核心挑战之一。LMDeploy作为一款轻量级推理框架通过其pipeline API为大模型多模态深度学习上一篇Android-Image-Cropper终极迁移指南从旧版本升级到最新版本的完整教程下一篇Zotero Style学术文献管理的视觉化革命与智能工作流构建创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考