大模型处理文本的全流程解析(四))
大模型基础扫盲------从文本到 Token、Embedding、QKV 与 Transformer 大模型处理文本的全流程解析一-CSDN博客大模型基础扫盲------从文本到 Token、Embedding、QKV 与 Transformer不定期优化修改大模型处理文本的全流程解析二-CSDN博客大模型基础扫盲------从文本到 Token、Embedding、QKV 与 Transformer不定期优化修改大模型处理文本的全流程解析三-CSDN博客17. 推理优化KV Cache、Prefill、Decode17.1 推理的两个阶段自回归推理通常分为两个阶段。阶段做什么特点关键指标Prefill用户 prompt 第一次进入模型一次性处理所有输入 token计算密集可并行生成整段 KV CacheTTFTTime To First TokenDecode逐 token 生成每步只处理 1 个新 token串行依赖显存带宽压力大KV Cache 越来越长TPOTTime Per Output Token或 tokens/sMLOps 视角优化 Prefill 主要靠GPU 算力FLOPS优化 Decode 主要靠显存带宽memory bandwidth这也是为什么 Decode 阶段经常是推理服务瓶颈。17.2 KV Cache 的原理没有 KV Cache 时生成第 1000 个 token 时 需要重新计算 token 1~999 的 K、V。有 KV Cache 时把前面 token 的 K、V 缓存起来。 生成第 1000 个 token 时 只计算 token 1000 的 Q、K、V 然后让新的 Q 去和缓存中的 K、V 做注意力。收益大幅减少重复计算提高生成速度。代价占用额外显存。17.3 KV Cache 显存估算通用公式KV Cache 显存 2 × num_layers × num_kv_heads × head_dim × seq_len × bytes_per_param × batch_size其中2 代表 K 和 V 两份。 num_kv_heads 是 KV 头数不是 Q 头数。 bytes_per_param 通常是 2对应 FP16/BF16。示例Mistral-7B 风格配置假设num_layers 32 num_kv_heads 8 head_dim 128 seq_len 4096 dtype BF16每参数 2 bytes单条序列2 × 32 × 8 × 128 × 4096 × 2 536,870,912 bytes ≈ 0.5 GB如果batch_size 32则0.5 × 32 16 GB如果某个模型是 4 个 KV heads那么同样条件下大约是0.25 GB / 单条序列注意不同模型的 KV 头数差异很大。做容量规划时务必以目标模型config.json中的num_key_value_heads为准不要凭记忆估算。17.4 vLLM 的 PagedAttention传统方式为每个请求预分配最大长度的连续 KV Cache 显存。问题大量显存被浪费。PagedAttention把 KV Cache 切成固定大小的“页”按需分配。类似操作系统的虚拟内存分页。你做过容器对“分页”概念不会陌生。PagedAttention 就是把 OS 虚拟内存思想用到 GPU 显存管理上。17.5 连续批处理Continuous Batching传统批处理等一批请求全到齐。 一起推理。 一起返回。问题短请求要等长请求浪费吞吐。连续批处理某个请求生成完毕就立刻移出。 新请求立刻插入。好处GPU 利用率更高。 在线服务吞吐更好。17.6 KV Cache 的更精确估算方式KV Cache 的通用估算公式是KV Cache 显存 2 × num_layers × num_kv_heads × head_dim × cached_tokens × bytes_per_element其中2 表示 K 和 V 两份。 num_layers 是 Transformer 层数。 num_kv_heads 是 KV 头数不是 Q 头数。 head_dim 是每个注意力头的维度。 cached_tokens 是当前已经缓存的 token 数量。 bytes_per_element 是每个元素占用的字节数。常见精度FP16 / BF162 bytes FP81 byte INT81 byte具体取决于实现如果是 batch 推理cached_tokens不应该简单理解成batch_size × max_seq_len更准确地说应该是所有请求当前已缓存 token 数的总和cached_tokens sum( prompt_len_i generated_len_i )也就是KV Cache 显存 2 × num_layers × num_kv_heads × head_dim × sum(prompt_len_i generated_len_i) × bytes_per_element举例假设num_layers 32 num_kv_heads 8 head_dim 128 dtype BF16每个元素 2 bytes一条请求缓存 4096 个 token2 × 32 × 8 × 128 × 4096 × 2 536,870,912 bytes ≈ 0.5 GB如果有 32 条请求每条都缓存 4096 个 token0.5 GB × 32 16 GB但如果这些请求共享相同 system prompt并且推理框架支持 prefix caching那么公共前缀的 KV Cache 可以共享。例如 32 条请求都有 2048 个 token 的相同 system prompt。这部分理论上可以只存一份或者通过页表共享。MLOps 视角KV Cache 是推理显存的大头之一。尤其是在长上下文和高并发场景下KV Cache 往往比模型权重更容易成为瓶颈。所以部署时要重点关注num_key_value_heads max_model_len batch size prompt 长度 输出长度 是否开启 prefix caching 是否使用 FP8 KV Cache 是否使用 PagedAttention17.7 PagedAttention 和操作系统分页的关系你做过容器和运维对虚拟内存、分页、按需分配这些概念不会陌生。传统 KV Cache 分配方式类似给每个请求预分配一段连续显存。 即使这个请求最后只用了一半长度 剩下的显存也可能被浪费。这很像早期连续内存分配的问题内存碎片严重 利用率低 无法灵活扩容PagedAttention 的做法类似操作系统虚拟内存分页把 KV Cache 切成固定大小的 block。 每个 block 可以存固定数量 token 的 K、V。 请求需要多少 block就分配多少 block。 不要求物理显存连续。 通过页表或 block table 管理逻辑块和物理块的映射。好处减少显存碎片。 提高 batch size。 提升 GPU 吞吐。 更容易实现 prefix caching。在 vLLM 中你会经常看到类似概念block_size block table physical block logical block copy-on-write prefix caching这些本质上都是显存管理优化。MLOps 视角如果你要部署高并发 LLM 服务PagedAttention 几乎是必学内容。因为它直接影响吞吐 显存利用率 首 token 延迟 长文本支持能力18. 显存估算从原理到工程18.1 推理显存估算公式推理时的显存消耗通常包括四部分1. 模型权重 2. KV Cache 3. 激活值 4. 临时缓冲和碎片粗略公式推理显存 ≈ 模型权重 KV Cache 激活值 临时缓冲模型权重估算weights_memory ≈ param_count × bytes_per_param例如7B 模型BF16 7,000,000,000 × 2 bytes ≈ 14 GBKV Cache 估算kv_memory 2 × num_layers × num_kv_heads × head_dim × cached_tokens × bytes_per_element激活值激活值和以下因素强相关batch_size seq_len hidden_size 推理框架的算子实现 是否使用 CUDA Graph 是否使用算子融合临时缓冲通常建议预留 10%~20% 的额外空间。所以在生产环境中不能只算模型权重。例如一张 24GB 显卡能装下 14GB 的 7B BF16 权重 不代表能稳定跑 7B 长上下文服务。因为 KV Cache 和激活值还会继续吃显存。18.2 训练显存估算训练显存比推理复杂得多。全参数训练时通常要考虑1. 模型权重 2. 梯度 3. 优化器状态 4. 激活值 5. 通信缓冲区 6. 临时 buffer以常见的 BF16 训练 AdamW 为例模型权重BF16约 2 bytes/param 梯度BF16约 2 bytes/param AdamW 优化器状态 FP32 master weights4 bytes/param FP32 first moment4 bytes/param FP32 second moment4 bytes/param合计2 2 4 4 4 16 bytes/param所以全参数训练显存粗略估算 ≈ param_count × 16 bytes 激活值 通信缓冲区 临时 buffer例如7B 模型 7,000,000,000 × 16 bytes ≈ 112 GB这还没有算激活值。所以朴素单卡全参数训练 7B经常会超过 100GB 显存。这就是为什么实际工程中经常使用ZeRO FSDP DeepSpeed activation checkpointing 8-bit Adam LoRA QLoRA 参数高效微调MLOps 视角如果你要做微调平台不能只问用户“模型多大”。还要问是否全参数训练 是否使用 LoRA 序列长度多少 batch size 多少 是否开启 gradient checkpointing 优化器是什么 精度是什么 是否多机多卡 通信后端是 NCCL 还是其他这些都会直接影响显存和集群资源规划。另外16 bytes/param是常见混合精度 AdamW 的粗略估算具体框架和实现可能略有差异。19. Padding Mask 与特殊 Token19.1 Padding Mask训练时多个句子组成 batch长度可能不一样。为了组成矩阵需要补齐。例如seq1: 我 吃 鱼 seq2: 你 看 [PAD]对应 attention mask 可能写成[ [1, 1, 1], [1, 1, 0] ]其中1 表示可以关注 0 表示屏蔽[PAD]位置不应被关注所以需要 padding mask 屏蔽。19.2 特殊 Token常见特殊 tokentoken作用[PAD]填充短序列[BOS]句子开始[EOS]句子结束生成时遇到即停止[CLS]常用于分类任务BERT 常见[SEP]分隔两个句子BERT 常见unk未知 token现代字节级 tokenizer 中可能很少触发chat 特殊 token用于系统、用户、助手消息模板注意采用字节级分词的模型通常能表示任意 UTF-8 文本 所以通常不会因为罕见字符直接失败。 但具体是否保留 unk以及有哪些 special token 要以模型 tokenizer 配置为准。19.3 chat template 必须正确现在大量开源模型都是 chat 模型。chat 模型通常有自己的对话模板。例如system user assistant不同模型的模板可能完全不同。正确做法是tokenizer.apply_chat_template()而不是自己手拼字符串。例如伪代码messages [ {role: system, content: 你是一个运维专家。}, {role: user, content: 请解释 KV Cache。} ] prompt tokenizer.apply_chat_template( messages, tokenizeFalse, add_generation_promptTrue )MLOps 视角chat template 错误是非常常见的生产事故。可能导致模型角色混乱 输出格式错误 无法正常停止 重复输出 user prompt 工具调用失败 多轮对话上下文错乱部署前一定要检查tokenizer_config.json重点关注chat_template eos_token bos_token pad_token additional_special_tokens如果模型支持 tool calling还要检查 tool 相关 token 和格式说明。20. 常见误解#误解纠正1token id 有语义id 只是编号语义在 embedding 里2token 一定是一个词可以是字、子词、标点、字节、特殊符号3embedding 是简单格式转换是训练出来的语义表示4模型天然知道 token 顺序注意力本身对顺序不敏感需要位置编码5自回归模型可以看到整个句子GPT 类模型只能看到当前位置及之前6Transformer 只有注意力每层还包括 FFN、残差、归一化7模型真的懂主谓宾模型从海量文本学到语言模式不是显式语法分析8Q、K、V 是唯一可能的结构是 Transformer 的设计选择也有 Mamba、线性注意力等替代方案9所有模型都用 LayerNorm许多现代大模型用 RMSNorm但非绝对10FFN 只有两个矩阵SwiGLU 风格 FFN 常见有三个矩阵11FFN 只占模型参数的 1/3单个 block 中 FFN 常占约 2/3整模型占比视词表、embedding、MoE 而定12Embedding 向量就是词义的最终表示输入层 embedding 只是初始表示。真正携带上下文语义的是 Transformer 各层输出的 hidden state。13一个多义词在 embedding table 中一定有多个向量通常不是。一个 token 通常只有一个初始 embedding。多义区分主要靠后续 Attention 和上下文表示。14词表越大越好词表大可以让中文、代码等文本更短减少 token 数但也会增加 embedding 和 LM Head 参数量。需要综合权衡。15max_position_embeddings 就是模型一定稳定支持的长度不一定。还要看训练数据长度分布、RoPE scaling、推理框架配置和长文本实际评测结果。16KV Cache 只和 batch_size 有关KV Cache 和层数、KV 头数、head_dim、缓存 token 总数、精度都有关。长上下文场景下尤其明显。17推理显存主要就是模型权重长上下文和高并发场景下KV Cache、激活值、临时缓冲、显存碎片都可能成为主要瓶颈。21. 全文总结21.1 核心概念速查表概念通俗解释文本人类可读的字符串分词器把文本切成 token 的工具token模型处理文本的基本单位token idtoken 在词表中的编号本身无语义embedding把 token id 变成语义向量位置编码告诉模型 token 的顺序Q查询向量我在找什么K键向量我能被什么找到V值向量找到我后我提供什么Q K^T / sqrt(d_k)计算 token 之间的相关性分数softmax把分数变成权重因果掩码防止自回归模型偷看后文多头注意力从多个子空间做注意力GQA共享 KV 头减少 KV CacheW_O把多头结果投影回模型主维度LayerNorm / RMSNorm归一化让训练更稳定FFN对每个 token 做非线性加工SwiGLU常见现代 FFN 结构通常有三个矩阵残差连接帮助深层网络训练多层 Transformer逐层提取更丰富的表示Weight Tying输入 embedding 和输出 LM Head 可能共享权重交叉熵损失训练模型预测正确 tokenLM Head把最后一层向量映射到词表概率Prefill推理时并行处理 promptDecode推理时逐步生成 tokenKV Cache推理时缓存 K、V提高生成速度21.2 最简流程图原始文本 ↓ Tokenizer ↓ tokens ↓ token ids ↓ Embedding ↓ 位置信息注入 ├── 绝对位置编码加到 embedding └── RoPE在 Attention 内旋转 Q/K ↓ Transformer Block × N ├── RMSNorm ├── Attention │ ├── Q/K/V 投影 │ ├── QK^T / sqrt(d_k) │ ├── causal mask │ ├── softmax │ └── 加权 V ├── 残差连接 ├── RMSNorm ├── FFN / SwiGLU └── 残差连接 ↓ 最后一个位置的 hidden state ↓ LM Head ↓ logits ↓ softmax ↓ 下一个 token 概率分布 ↓ 采样 / greedy / top-k / top-p ↓ 生成下一个 token21.3 一句话总结增强版如果用一句话概括现代 LLMTokenizer 把文本变成 token Embedding 给 token 一个初始语义坐标 位置编码告诉模型 token 的顺序 Transformer 通过 Attention 和 FFN 逐层构造上下文语义 LM Head 最终输出下一个 token 的概率分布。也就是说模型不是简单查表。 模型是在海量文本上训练出来的、 能够根据上下文预测下一个 token 的概率函数。22. 完整例子从“我吃鱼”到模型输出假设输入我吃鱼第一步分词器切分可能切成我 / 吃 / 鱼第二步查 token idtokentoken id我2513吃1892鱼3765得到[2513, 1892, 3765]注意这些 id 只是示例。第三步查 embeddingtokenembedding我向量 A吃向量 B鱼向量 C得到X [A, B, C]第四步位置信息如果是绝对位置编码我A position_1 吃B position_2 鱼C position_3如果是 RoPE此时不直接加位置向量。 后面 Attention 中会对 Q/K 做旋转。第五步生成 Q、K、V每个 token 向量都会经过三副“滤镜”Q X W_Q K X W_K V X W_V得到tokenQKV我q^(1)k^(1)v^(1)吃q^(2)k^(2)v^(2)鱼q^(3)k^(3)v^(3)第六步计算注意力分数如果是自回归模型需要加因果掩码。处理“吃”时它只能看到我、吃不能看到后面的鱼所以“吃”的 Queryq^(2)只能和k^(1), k^(2)计算注意力不能和k^(3)计算。这一点很重要在自回归模型中 “吃”在这个位置还不知道后面是“鱼”。 它只知道前面是“我”。第七步softmax 变权重假设在自回归模型中处理“吃”时得到目标 token权重我0.60吃0.40鱼0.00因为被 mask第八步加权 V“吃”的新向量output^(2) 0.60 · v^(1) 0.40 · v^(2) 0.00 · v^(3)这样“吃”融合了前文信息谁在吃我但此时它还不知道吃的是什么因为在自回归模型中它不能看到后面的“鱼”。第九步多头输出经过 W_O如果有多个头每个头都会得到一个输出。这些输出会被拼接然后经过输出投影矩阵W_O映射回模型主维度。例如[head_1, head_2, ..., head_32] → concat → W_O → 4096 维第十步残差连接和归一化注意力输出通常会和原始输入做残差连接再进行归一化。以 Pre-LN 风格为例X_1 X Attention(RMSNorm(X))以 Post-LN 风格为例X_1 LayerNorm(X Attention(X))具体顺序因模型而异。第十一步进入 FFN然后进入 FFNX_2 X_1 FFN(RMSNorm(X_1))FFN 会对每个 token 的表示做进一步非线性加工。可以理解为Attention 像查资料。 FFN 像自己消化总结。第十二步经过多层 Transformer注意力、FFN、残差、归一化组成一个 Transformer block。实际模型会堆叠很多层比如32 层 40 层 80 层经过多层之后每个位置的向量都会包含更丰富的上下文信息。第十三步模型预测下一个 token最后一层输出的向量经过 LM Headlogits h_last · W_LM_Head再经过 softmaxprobs softmax(logits)如果输入是我 吃模型需要预测下一个 token。它可能输出token概率鱼0.42饭0.25面0.12苹果0.03......如果生成策略选择概率最高的 token就会输出鱼最终得到我吃鱼23. 给 MLOps 学习者的下一步23.1 主流推理框架选型指南学习原理之后最终要落到部署和优化。常见主流推理框架包括vLLM TGIText Generation Inference SGLang TensorRT-LLM它们的侧重点不同。vLLM核心优势PagedAttention 高吞吐 显存利用率高 适合通用在线推理服务适合场景高并发 chat 服务 通用文本生成 对吞吐要求高的场景TGI核心优势生产功能完整 监控和部署生态成熟 支持多种量化和 LoRA 适合企业级部署适合场景企业生产环境 需要较完整服务能力 需要 HuggingFace 生态集成SGLang核心优势结构化输出强 适合 Agent、tool calling、JSON schema RadixAttention 对共享前缀和多轮对话友好适合场景Agent 系统 工具调用 结构化生成 复杂 prompt 复用TensorRT-LLM核心优势NVIDIA GPU 上深度优化 kernel 级别优化多 适合追求极致性能适合场景对延迟和吞吐要求非常高 团队具备较强 CUDA / 推理引擎能力MLOps 视角不要只问“哪个框架最快”。要结合业务场景看是否需要长上下文 是否需要高并发 是否需要结构化输出 是否需要 tool calling 是否需要多 LoRA 热切换 是否需要 prefix caching GPU 型号是什么 显存大小是多少 延迟指标是 TTFT 还是 TPOT 运维团队是否能维护复杂引擎选型不是技术炫技而是业务约束下的工程折中。另外推理框架生态更新很快最终以当前版本功能和实测 benchmark 为准。23.2 实战实验室从原理到工程建议你按顺序做下面几个实验。实验 1读取模型配置理解模型结构目标学会看 config.json。任务找一个开源模型例如 Qwen、LLaMA、Mistral 系列。找出以下字段hidden_size num_hidden_layers num_attention_heads num_key_value_heads intermediate_size vocab_size max_position_embeddings rope_theta rope_scaling tie_word_embeddings torch_dtype然后回答这个模型有多少层 主维度是多少 每个 head 维度是多少 KV Cache 用几个 head FFN 中间维度是多少 词表大小是多少 是否绑定 embedding 和 LM Head实验 2手工估算 KV Cache目标掌握推理显存大头。任务假设num_layers 32 num_kv_heads 8 head_dim 128 seq_len 8192 dtype BF16计算单条请求的 KV Cache 显存。公式KV Cache 2 × num_layers × num_kv_heads × head_dim × seq_len × bytes然后把seq_len改成4096 8192 32768 131072观察显存变化。参考结果4096 ≈ 0.5 GB 8192 ≈ 1 GB 32768 ≈ 4 GB 131072 ≈ 16 GB实验 3用 vLLM 部署一个模型目标理解推理服务启动参数。重点观察max_model_len tensor_parallel_size gpu_memory_utilization max_num_seqs enable_prefix_caching dtype quantization记录启动显存占用 首 token 延迟 TTFT 生成速度 tokens/s 并发增加时吞吐变化实验 4对比 Prefill 和 Decode目标理解推理两阶段瓶颈。方法构造不同 prompt 长度短 prompt长输出 长 prompt短输出 长 prompt长输出观察TTFT 是否明显变长 TPOT 是否稳定 GPU 利用率如何 显存占用如何变化实验 5LoRA 微调显存对比目标理解参数高效微调。任务用同一模型分别做全参数训练 LoRA QLoRA对比显存占用 训练速度 可训练参数量 checkpoint 大小 部署复杂度这些实验做完后你会从“知道原理”进入“能做工程落地”。24. MLOps 部署前检查清单24.1 模型配置检查hidden_size num_hidden_layers num_attention_heads num_key_value_heads head_dim intermediate_size vocab_size max_position_embeddings rope_scaling tie_word_embeddings24.2 显存检查模型权重显存 KV Cache 显存 激活值显存 临时缓冲 并发请求下的峰值显存 长上下文下的峰值显存24.3 推理服务检查TTFT TPOT tokens/s max_batch_size max_model_len gpu_memory_utilization 是否开启 prefix caching 是否开启 continuous batching 是否使用 PagedAttention 是否使用量化24.4 Tokenizer 检查vocab_size special tokens chat_template stop tokens 是否支持 tool calling 是否支持多语言 是否支持代码24.5 长文本检查训练长度是多少 推理长度是多少 是否配置 rope_scaling 是否做过长文本评测 是否存在注意力退化24.6 训练 / 微调检查是否全参数训练 是否 LoRA 是否 QLoRA 优化器状态显存 激活值显存 梯度累积 序列长度 batch size 是否 gradient checkpointing只要每次部署模型前过一遍这个清单很多显存 OOM、效果异常、无法停止、长文本退化问题都可以提前发现。最后一句话大模型就是一个“预测下一个 token”的函数把文本变成向量 经过几十层注意力和前馈网络加工 输出词表上的概率分布 再选出或采样出最可能的下一个 token。所有参数包括embedding Q/K/V W_O FFN 归一化参数 LM Head都是通过海量文本上的“预测下一个 token”任务训练出来的。