
开发者用C++和CUDA从零构建LLM推理引擎教程
来源标题:Hours of YouTube and 85 handtyped pages of notes on LLM Inference – Start here
一位开发者发布了一套从第一性原理讲解LLM推理的系列教程,通过用C++和CUDA构建小型推理引擎来演示提示词到生成token的全过程。该系列共10个部分加终章,涵盖权重加载、RMSNorm、RoPE、自注意力、KV缓存和分页注意力等环节,并配有80多页手写笔记和图表PDF,作者强调全部内容均由自己撰写、未使用AI生成。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
用 C++ 和 CUDA 从零构建一个小型推理引擎,从第一性原理讲解 LLM 推理。
这个系列沿着「从 prompt 到生成一个 token」的路径展开,涵盖从加载模型权重到在 GPU 上运行 transformer 每个部件的全过程。
阅读《Inference from Scratch》(PDF),其中包含 80 多页我的手写笔记和图表。这份 PDF 中的所有文字均由我本人撰写,没有使用任何 AI 写作。
第 0 部分 · AI 推理详解 第 1 部分 · 加载模型权重 第 2 部分 · Embedding gather 第 3 部分 · RMSNorm 第 4 部分 · RoPE 第 5 部分 · 用 cuBLAS 实现 Q/K/V 第 6 部分 · 自注意力 第 7 部分 · 前馈网络 第 8 部分 · Decode 与 KV cache 第 9 部分 · 静态批处理与连续批处理 第 10 部分 · Paged KV cache 终章 · Paged attention
每个视频的配套笔记都在 study-notes 索引中,分别提供 Markdown 和 PDF 两个文件夹。你也可以直接阅读完整的 PDF 合集。
一个 C++17 / CUDA 项目,用 CMake 构建,链接 ICU 以支持 Unicode 感知的分词。
直接加载 model.safetensors:解析元数据、校验 tensor 偏移,并将原始 BF16 权重拷贝到 GPU 显存。此外还提供了一个内存映射加载器实现,以避免在 CPU 侧多一次拷贝。
固定的 Llama 3.2 1B Instruct 权重布局,带有指向全部 16 层中 embedding、归一化、注意力和 MLP tensor 的直接指针。
本地 BPE 分词器,支持 Unicode、特殊 token、编码/解码以及 Instruct 对话 prompt 格式;其预期输出由专门的测试可执行文件覆盖。
CUDA embedding-gather kernel 和 prefill 路径,将 prompt 的 token ID 拷贝到 GPU 并分配激活值。
针对 2,048 宽隐藏状态的 CUDA RMSNorm kernel:以 FP32 做归约,并在整个前向传播中应用学习到的 BF16 权重。
稠密因果 prefill 现在会运行全部 16 层:Q/K/V 投影、RoPE、分组查询注意力、输出投影、注意力残差、注意力后 RMSNorm、SwiGLU MLP 以及 MLP 残差。
最终的 RMSNorm 和绑定的 embedding/LM-head 投影产生 FP32 logits;GPU argmax 选出每个下一个 token。
共享的 BF16 KV page pool,每个序列、每一层各有一张 block table。每个物理 page 存放 16 个 token 的 K 和 V;prefill 填充 prompt 的 page,decode 则在其绝对 RoPE 位置追加一个新 token。
带 Instruct 对话格式的贪心响应生成,支持回合结束/文本结束停止,并可在 2,048 token 上下文内配置输出长度。
持久化的 scratch buffer,以及由 prefill 和 decode 共享的一个 cuBLAS handle。Q 的存储被复用于输出/MLP 投影,归一化的存储被复用于注意力输出。
带运行时尺寸 buffer 的批处理 decode,打包的投影,以及每个序列独立的 KV page 映射和注意力长度。请求数量不会扩大 KV pool。
静态批处理,以及一个连续调度器:它根据可用的 page 预算接纳排队的 prompt,并在完成时归还其 page 和预算。
直接的 paged decode attention,采用 FP32 online softmax。每个 query head 流式读取其序列缓存的 page,并累加加权值,无需收集连续前缀,也无需存储注意力分数。
第 8 部分讲解 Prefill、Decode 和 KV Cache。第 9 部分接着讲静态批处理与连续批处理,包括当前的 paged cache 和 online-softmax decode attention。
Llama 3.2 1B 推理路径包含稠密因果 prefill、paged decode attention,以及静态或连续批处理。对于这个学习用引擎,模型维度和 2,048 token 上下文保持固定。KV pool 默认为 128 MiB,可通过 --kv-cache-mib N 设置;活跃请求容量取决于工作负载的 page 预算,而不是固定的两个请求上限。
准入会为每个请求的最大已处理前缀预留空间:prompt_tokens + allowed_new_tokens - 1,在 16 层中的每一层都向上取整到 16 token 的 page。允许的输出预算会被钳制到上下文上限,最终预测的 token 会被返回而不再处理一次。这些准入额度与物理 page 相互独立,物理 page 是惰性分配的。提前完成会同时释放已分配的 page 和剩余的额度。
当请求的预算无法与活跃请求共存时,请求按 FIFO 顺序等待。活跃请求有足够空间完成,因此未来的 page 边界增长不会耗尽 pool 或导致生成死锁。预算超过整个 pool 的请求会被拒绝,并给出清晰的错误。这种保守策略避免了抢占和重计算;即使模型最终会提前停止,较大的请求输出预算仍可能限制并发度。
--stats 会报告 pool 大小、峰值活跃请求数、最大 decode 批大小、峰值已分配 page 数、准入等待次数以及最终空闲 page 数。Prefill 在 decode 迭代之间仍然一次只处理一个 prompt。
BF16 计算在不同批大小下可能产生不同的舍入结果。当词表 logits 几乎并列时,贪心输出的 token 因此可能在单条运行和批处理运行之间不同。不同批大小之间的逐位输出一致性并不保证。
环境要求
一块支持 CUDA 的 NVIDIA GPU 和 CUDA Toolkit CMake 3.24+ 一个 C++17 编译器 ICU 开发库 nlohmann/json 头文件 模型权重位于 models/llama-3.2-1b-instruct/model.safetensors 匹配的分词器位于 models/llama-3.2-1b-instruct/tokenizer.model(从 models/Llama-3.2-1B-Instruct/original/tokenizer.model 拷贝)
mkdir -p models/llama-3.2-1b-instruct cp models/Llama-3.2-1B-Instruct/original/tokenizer.model models/llama-3.2-1b-instruct/ cmake -S . -B build cmake --build build -j ./build/samir-vllm "Explain the KV cache in two sentences." 128
Continuous batching (default): admit requests whose page budgets fit.
./build/samir-vllm "Say hello." 64 "Explain the KV cache." "What is RoPE?"
Static batching: wait for the current group before admitting more.
./build/samir-vllm --static "Say hello." 64 "Explain the KV cache." "What is RoPE?"
More than two requests share the same default 128 MiB pool.
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。