【LLM推理与Serving①】LLM推理基础从Prefill/Decode到KV Cache与性能指标全解析本文是「LLM推理与Serving」系列的第一篇带你从零理解大语言模型推理的完整流程、核心概念与性能指标。无论你是AI初学者还是有一定经验的工程师都能从中收获干货。一、开篇引入LLM推理是什么你每天都在和ChatGPT、文心一言、通义千问这些大模型聊天但你有没有想过——当你按下发送按钮后模型到底做了什么才能生成一段流畅的回答这就是**LLM推理Inference**要回答的问题。1.1 推理 vs 训练有什么区别很多初学者容易把训练和推理搞混其实它们是两个完全不同的阶段阶段目的更新权重吗计算特点主要消耗训练让模型学会知识是前向反向传播batch大大量计算、显存、数据推理用训练好的模型答题否仅前向传播自回归生成显存、访存带宽、解码延迟打个比方训练就像学生寒窗苦读不断刷题改错推理就像上了考场用学到的知识做题不能再翻书改答案了。1.2 推理的两个阶段Prefill DecodeLLM推理不是一步到位的它分为两个截然不同的阶段Prefill预填充阶段把用户输入的prompt提示词一次性喂给模型计算出所有输入token的KV Cache并输出最后一个token的概率分布。Decode解码阶段自回归地、一个一个地生成新token。每生成一个就把它拼到序列后面继续生成下一个。用户输入今天天气 Prefill阶段今 → 天 → 天 → 气 一次性处理完4个token Decode阶段 很 → 好 → 。 一个一个蹦出来 最终输出 今天天气很好。这就是为什么你用ChatGPT时回答是打字机效果一个字一个字蹦出来的——因为模型在Decode阶段逐token生成。二、LLM推理完整链路下面我们沿着数据流动的方向一步步拆解LLM推理的完整流程。2.1 输入处理用户输入的是原始字符串比如请用一句话解释什么是机器学习。这串文字模型可看不懂需要先做翻译。2.2 分词TokenizationTokenizer分词器把字符串切分成一个个token并映射为token ID整数。fromtransformersimportAutoTokenizer tokenizerAutoTokenizer.from_pretrained(Qwen/Qwen2.5-7B-Instruct)text请用一句话解释什么是机器学习。inputstokenizer(text,return_tensorspt)print(Token IDs:,inputs[input_ids])print(Token数量:,inputs[input_ids].shape[1])输出类似Token IDs: tensor([[151644, 8948, 198, ..., 151645]]) Token数量: 12每个整数对应词表中的一个token。不同模型的分词粒度不同有的按字分有的按词分有的按子词分。2.3 嵌入层EmbeddingToken ID通过Embedding层变成连续的向量表示。每个token变成一个d_model维的向量这样模型才能进行数学计算。Token ID序列[t1, t2, t3, ..., tn] ↓ Embedding 向量序列 [v1, v2, v3, ..., vn] 每个vi是d_model维向量2.4 Transformer前向传播经过多层的Self-Attention自注意力和FFN前馈网络计算模型输出每个位置的logits对数概率。# 伪代码示意logitsmodel(input_ids)# 形状: [batch_size, seq_len, vocab_size]logits的最后一维是词表大小表示每个位置上每个词的得分。2.5 解码生成取最后一个位置的logits通过softmax转成概率分布然后采样或贪心选择出下一个token。把这个新token拼到序列末尾再次输入模型……循环往复直到生成结束标记或达到最大长度。三、自回归生成与KV Cache3.1 自回归是什么意思自回归听起来很高大上其实意思很简单每一步的输出都依赖于之前所有的输出。用公式表示就是P(y1,y2,…,yT∣x)∏t1TP(yt∣x,y1,…,yt−1)P(y_1, y_2, \ldots, y_T | x) \prod_{t1}^{T} P(y_t | x, y_1, \ldots, y_{t-1})P(y1​,y2​,…,yT​∣x)t1∏T​P(yt​∣x,y1​,…,yt−1​)翻译成大白话生成第1个词只看输入生成第2个词要看输入第1个词生成第3个词要看输入第1第2个词……以此类推。3.2 为什么需要KV Cache问题来了每生成一个新token都要重新计算前面所有token的Key和Value吗那也太浪费了比如生成第100个token时前面99个token的K、V其实和上一步一模一样完全不需要重新算。KV Cache就是把已经计算好的K、V缓存起来下次直接用避免重复计算。这就像你做数学题每一步都把中间结果记在草稿纸上下一步直接用不用从头再算一遍。3.3 带KV Cache的生成流程用Python伪代码表示就是kv_cacheNone# 初始为空input_idsprompt_ids# 初始输入是promptforiinrange(max_new_tokens):# 前向传播传入历史KV Cacheoutputsmodel(input_ids,past_key_valueskv_cache)logitsoutputs.logits[:,-1,:]# 取最后一个位置# 采样下一个tokennext_tokensample(logits)# shape: [batch_size, 1]# 更新输入下一步只需要输入新tokeninput_idsnext_token# 更新KV Cache追加新token的K、Vkv_cacheoutputs.past_key_valuesifnext_tokeneos_token_id:break看到没从第二步开始input_ids就只有1个token了因为前面所有token的K、V都已经存在kv_cache里了。四、KV Cache详解KV Cache是LLM推理中最重要的概念之一也是显存占用的大头。下面我们深入聊聊。4.1 KV Cache是什么对于每一层Transformer每个token都会生成一个Key向量和一个Value向量Key的形状[batch_size, num_kv_heads, seq_len, head_dim]Value的形状[batch_size, num_kv_heads, seq_len, head_dim]在Decode阶段每生成一个新token就把它的K、V追加到Cache末尾。这样Self-Attention只需要计算新token的Query然后和所有历史K做内积就行。4.2 显存占用估算公式KV Cache占多少显存公式非常重要建议记下来KV Cache (字节) 2 × num_layers × num_kv_heads × seq_len × head_dim × bytes_per_element × batch_size 2 × L × H_kv × S × D × sizeof(dtype) × B系数2是因为有K和V两份num_layersTransformer层数num_kv_headsKV注意力头数GQA模型中比Q头数少seq_len序列长度head_dim每个头的维度bytes_per_element每个元素占多少字节FP16是2FP8是1INT8是1举个实际的例子Qwen2.5-7B-Instructnum_layers 28num_kv_heads 4GQA不是MHA的28head_dim 128seq_len 8192dtype FP162字节batch_size 1KV Cache 2 × 28 × 4 × 8192 × 128 × 2 × 1 469,762,048 字节 ≈ 448 MB如果batch_size8那就是约3.6GB这个数字在长上下文、高并发场景下会非常恐怖。4.3 KV Cache的增长特点随batch_size线性增长并发越多显存占得越多随seq_len线性增长上下文越长显存占得越多与模型大小正相关模型越大层数和头数越多4.4 KV Cache的局限性显存占用大长上下文大batch时KV Cache可能超过模型权重本身内存碎片化变长序列导致Cache分配不均匀产生碎片无法共享传统方式下不同请求之间不能共享公共前缀如system prompt这些问题正是后续PagedAttention等技术要解决的我们下一篇再聊。五、解码策略模型输出的是概率分布怎么从概率分布中选出下一个token这就是解码策略要解决的问题。5.1 Greedy贪心解码最简单的策略每次选概率最大的那个token。next_tokentorch.argmax(logits,dim-1)优点确定性强、速度快、结果可复现缺点容易生成重复、单调的文本缺乏多样性甚至会出现车轱辘话适合场景代码生成、数学推理等需要确定性的任务。5.2 Beam Search束搜索同时维护k个候选序列beam每一步都扩展所有候选然后保留概率乘积最高的k个。outputsmodel.generate(**inputs,num_beams4,early_stoppingTrue,)优点在翻译、摘要等任务上质量通常更高缺点显存占用增加k倍不适合开放域聊天生成5.3 Sampling采样解码从概率分布中随机采样引入随机性和多样性。outputsmodel.generate(**inputs,do_sampleTrue,temperature0.7,top_k50,top_p0.9,repetition_penalty1.1,)下面分别介绍几个关键参数Temperature温度控制概率分布的尖锐程度temperature 1分布更尖锐高概率token更受青睐输出更保守temperature 1保持原始分布temperature 1分布更平坦低概率token也有机会被选中输出更随机、更多样打个比方temperature就像调音量旋钮调低了主旋律更突出调高了杂音也进来了。Top-k 采样只从概率最高的k个token里采样把概率极低的长尾token全部排除。比如top_k50就是从概率最高的50个token中按概率采样。Top-p 采样Nucleus Sampling从累计概率达到p的最小token集合中采样。比如top_p0.9就是从概率最高的一批token中挑选它们的累计概率刚好好达到90%然后在这个集合里采样。Top-p比Top-k更灵活如果分布很集中高概率token很少候选集就小如果分布很分散候选集就大。Repetition Penalty重复惩罚对已经出现过的token施加惩罚降低它们被再次选中的概率缓解重复生成问题。六、推理性能指标做LLM Serving怎么衡量系统好不好这几个指标你必须懂。6.1 核心指标一览指标英文全称含义受什么影响最大首token延迟TTFT (Time To First Token)从请求到第一个token输出的时间Prefill阶段每token延迟TPOT (Time Per Output Token)生成每个后续token的平均时间Decode阶段总延迟Latency完整生成所有token的时间TTFT TPOT × (N-1)吞吐量Throughput单位时间生成的token数或处理的请求数整体系统每秒token数TPS (Tokens Per Second)生成阶段每秒生成多少tokenDecode效率并发数Concurrency同时处理的请求数量显存和计算限制6.2 一个计算例子假设Prefill耗时200msDecode每token耗时40ms生成200个新token那么TTFT 200ms用户等了0.2秒才看到第一个字TPOT 40ms每个字间隔40毫秒体感很流畅Latency 200 40 × 199 8160ms ≈ 8.2秒TPS 1000 / 40 25 tok/s6.3 指标之间的关系Latency和Throughput是trade-off关系想让每个请求都很快就得少并发总吞吐就上不去想最大化吞吐就得堆高并发每个请求就会变慢TTFT影响等待感用户点了发送后多久能看到第一个字直接影响体验TPOT影响流畅感流式输出时打字的速度决定了用户会不会觉得卡长上下文两头都伤prompt越长Prefill越慢TTFT高KV Cache越大Decode也越慢TPOT高七、LLM推理显存分析GPU显存是LLM推理最宝贵的资源。它都花在哪儿了7.1 显存四大组成总显存 ≈ 模型权重 KV Cache 激活值 框架开销组成部分影响因素说明模型权重参数量、精度FP16≈2字节/参数INT8≈1字节INT4≈0.5字节KV Cachebatch、seq_len、层数、KV头数长上下文大batch时占主导激活值batch、seq_len、hidden_size前向传播中的中间张量框架开销CUDA context、内存分配器等通常几百MB到几GB7.2 实际估算Qwen2.5-7B以Qwen2.5-7B-Instruct、FP16推理为例模型权重7B × 2字节 14GBKV Cachebatch1, seq_len4096≈ 224MB激活值几十到几百MB总显存约15GB左右如果batch8seq_len8192呢KV Cache224MB × 8 × 2 ≈ 3.6GB总显存约18GB可以看到batch越大、序列越长KV Cache的占比就越高。7.3 对症下药权重占主导 → 考虑权重量化INT4、AWQ、GPTQ等KV Cache占主导 → 考虑KV Cache量化、PagedAttention、减少batch激活占主导 → 考虑FlashAttention等高效注意力实现八、代码实践示例光说不练假把式我们用transformers库写一段完整的推理代码并测量性能指标。8.1 基础推理代码importtimeimporttorchfromtransformersimportAutoModelForCausalLM,AutoTokenizer# 加载模型和tokenizermodel_nameQwen/Qwen2.5-7B-InstructtokenizerAutoTokenizer.from_pretrained(model_name,trust_remote_codeTrue)modelAutoModelForCausalLM.from_pretrained(model_name,trust_remote_codeTrue,torch_dtypetorch.float16,device_mapauto,)# 准备输入prompt请用通俗易懂的语言解释什么是机器学习。inputstokenizer(prompt,return_tensorspt).to(model.device)num_input_tokensinputs[input_ids].shape[1]print(f输入token数:{num_input_tokens})# 生成文本并测量性能start_timetime.time()withtorch.no_grad():outputsmodel.generate(**inputs,max_new_tokens128,do_sampleTrue,temperature0.7,top_p0.9,repetition_penalty1.1,)end_timetime.time()# 计算指标num_output_tokensoutputs.shape[1]-num_input_tokens latencyend_time-start_time tpsnum_output_tokens/latency# 输出结果responsetokenizer.decode(outputs[0][num_input_tokens:],skip_special_tokensTrue)print(f\n生成结果:{response})print(f\n生成token数:{num_output_tokens})print(f总延迟:{latency:.3f}s)print(fTPS:{tps:.2f}tok/s)8.2 测量TTFT和TPOTimporttime# 测量 TTFT # Prefill 生成第一个tokenstarttime.time()withtorch.no_grad():first_outputmodel.generate(**inputs,max_new_tokens1,do_sampleTrue,temperature0.7,)ttfttime.time()-startprint(fTTFT:{ttft*1000:.2f}ms)# 测量 TPOT # 继续生成剩余tokenstarttime.time()withtorch.no_grad():full_outputmodel.generate(first_output,max_new_tokens127,do_sampleTrue,temperature0.7,)decode_timetime.time()-start num_decode_tokensfull_output.shape[1]-first_output.shape[1]tpotdecode_time/num_decode_tokens*1000print(fTPOT:{tpot:.2f}ms/token)print(fDecode TPS:{1000/tpot:.2f}tok/s)print(f总Latency ≈ TTFT TPOT × (N-1) {ttfttpot/1000*(num_decode_tokens-1):.3f}s)提示更精确的TTFT测量可以直接调用一次model(**inputs)前向传播来完成。运行上面的代码你就能亲手测出模型在你机器上的推理性能了。九、小结与思考9.1 全文小结LLM推理分为Prefill和Decode两个阶段Prefill一次性处理prompt并构建KV CacheDecode逐token自回归生成。KV Cache是避免重复计算、提升Decode效率的关键技术但显存占用随batch和序列长度线性增长。解码策略有Greedy、Beam Search、Sampling三大类Temperature、Top-k、Top-p是采样的关键参数。核心性能指标包括TTFT、TPOT、Latency、Throughput、TPS它们之间存在权衡关系。显存主要由模型权重、KV Cache、激活值三部分组成不同瓶颈对应不同优化方向。transformers提供了开箱即用的推理接口但生产环境需要更专业的Serving框架。9.2 思考问题为什么说LLM推理通常是memory-bound访存瓶颈而不是compute-bound计算瓶颈什么情况下会变成compute-bound如果你的服务TTFT很高用户等很久才看到第一个字但TPOT很低打字速度很快应该优先优化什么有哪些手段假设KV Cache已经占用了80%的显存你能想出至少三种具体的优化方法吗Greedy Decoding和Sampling分别适合什么样的业务场景各举一个例子。设计一个实验如何测量不同batch size下LLM的Latency和Throughput变化并画出变化曲线下一篇预告我们将深入讲解PagedAttention——vLLM的核心技术看看它是如何借鉴操作系统虚拟内存的思想彻底解决KV Cache的显存浪费和碎片化问题的。