ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

一起理解下LLM的推理流程:从输入到输出的完整链路拆解

一起理解下LLM的推理流程:从输入到输出的完整链路拆解 1. 一次对话请求模型到底在忙什么你敲下一句「帮我写个 Python 快速排序」几百毫秒后屏幕上开始一个字一个字往外蹦。这中间 LLM 推理流程到底发生了什么很多刚接触大模型的开发者会把它当成一个黑盒输入文本输出文本中间是「模型在算」。但只要你打算做本地推理、调 API、或者排查「为什么首字这么慢」「为什么长上下文这么吃显存」就必须把这条链路拆开看。LLM 推理流程可以粗略理解成一条流水线文本先被分词器切成 tokentoken 查表变成 embedding 向量向量进入 Transformer 层做注意力计算最后一步从概率分布里采样出下一个 token再把这个 token 拼回输入循环往复直到遇到结束符。整条链路里有两个阶段特别关键——prefill 和 decode。前者一次性并行处理你输入的整段 prompt后者一个 token 一个 token 地自回归生成。理解了这两个阶段的差异你就能解释大部分推理性能问题。这篇面向刚上手大模型的开发者以一次完整对话请求为主线把分词、嵌入、注意力、KV Cache、采样解码这些环节串起来并给出一套可以本地复制运行的推理调用配置和逐步验证动作。读完你不仅能画出推理链路图还能自己动手跑一遍看到每个阶段的真实耗时和显存占用。2. 前置准备用 TaoToken 打通模型调用入口要观察推理流程最直接的方式是发一次真实请求把返回的 token 流和耗时打出来。自己从零搭一套推理环境对新手偏重比较省事的做法是先用一个兼容 OpenAI 接口的服务把链路跑通再逐步深入底层。TaoToken 提供的就是这样一个统一入口官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它的接口地址是 https://taotoken.net/api 兼容常见的 chat completions 调用方式。你不需要改太多代码把 base_url 和 api_key 换掉就能用。对于想专注理解推理流程而不是折腾环境的人来说这一步能省下大量时间。具体操作上先到控制台创建一个 API Key。控制台入口在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite API Keys 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建好之后把 key 复制出来注意它只显示一次丢了就得重建。注意API Key 属于敏感凭证不要硬编码进提交到 Git 的代码里建议用环境变量读取。如果你更想先在网页上直观感受一下模型对话的输入输出可以打开模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 随便问一句观察它是怎么逐字返回的。这个「逐字返回」正是 decode 阶段自回归生成在界面上的体现。接入相关的完整说明可以看文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 。3. 可复制配置把一次请求的推理链路跑起来下面这段代码用 Python 发一次流式请求并把每个 token 到达的时间戳打出来。这样你就能亲眼看到 TTFT首 token 时间和后续 token 之间的间隔也就是 ITLInter-token Latency。先装依赖pip install openai然后写调用脚本。把 key 放进环境变量避免泄露export TAOTOKEN_API_KEY你的keyimport os import time from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.environ[TAOTOKEN_API_KEY], ) prompt 用三句话解释什么是 KV Cache start time.time() first_token_time None token_count 0 stream client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], streamTrue, ) for chunk in stream: delta chunk.choices[0].delta.content if delta: now time.time() if first_token_time is None: first_token_time now print(f\n[TTFT] 首 token 耗时: {first_token_time - start:.3f}s) token_count 1 print(delta, end, flushTrue) total time.time() - start print(f\n\n[统计] 共生成 {token_count} 个 chunk) print(f[统计] 总耗时: {total:.3f}s) if token_count 1: itl (total - (first_token_time - start)) / (token_count - 1) print(f[统计] 平均 ITL: {itl*1000:.1f}ms)这段代码里streamTrue是关键。非流式请求要等所有 token 生成完才返回你只能看到总时间流式请求则能让你观察到推理过程的时间分布。跑一次你会看到 TTFT 通常在几百毫秒量级而后续每个 token 的间隔明显更短——这正是 prefill 和 decode 两个阶段特性不同的直接证据。如果你想更贴近底层也可以直接用 transformers 在本地加载一个小模型观察分词和 embedding 的形状from transformers import AutoTokenizer, AutoModel import torch model_name gpt2 # 小模型方便本地跑 tokenizer AutoTokenizer.from_pretrained(model_name) model AutoModel.from_pretrained(model_name) text LLM inference is fun tokens tokenizer(text, return_tensorspt) print(token ids:, tokens[input_ids]) print(token 数量:, tokens[input_ids].shape[1]) with torch.no_grad(): outputs model(**tokens) print(最后一层隐藏状态形状:, outputs.last_hidden_state.shape)输出里input_ids的形状是[1, N]N 就是分词后的 token 数last_hidden_state的形状是[1, N, hidden_size]这就是 embedding 经过 Transformer 层之后的向量表示。看到这两个形状你对「文本变矩阵」这件事就有了具体的感知。4. 逐步验证从分词到采样的每一环4.1 分词文本怎么变成 token分词器是推理链路的第一站。它把原始字符串按词表切分成 token再映射成整数 ID。这里有个容易踩的坑看起来相似的词未必共享 token。比如 smart 和 smarter分词器可能给出完全不同的 ID因为词表是按子词subword粒度构建的不是按语义相似度。你可以用上面的 tokenizer 打印一下for w in [smart, smarter, token, tokenization]: ids tokenizer.encode(w) print(w, -, ids, -, tokenizer.convert_ids_to_tokens(ids))跑完你会发现长词往往被拆成多个子词 token。这解释了一个现象同样一句话不同分词器切出来的 token 数可能差不少而 token 数直接决定了 prefill 的计算量和显存占用。输入 prompt 的 token 越多模型要处理的矩阵越大反应自然越慢。4.2 嵌入与注意力token 变成向量之后token ID 本身没有语义需要查 embedding 矩阵变成连续向量。这个 embedding 矩阵是模型权重的一部分形状是[vocab_size, hidden_size]。查完之后向量进入 Transformer 层核心是自注意力机制。自注意力做的事情通俗说就是让每个 token 去「看」序列里其他 token决定该关注谁。计算时会用三组投影权重把输入映射成 Query、Key、Value 三个矩阵然后算注意力分数。这里就是显存大户登场的地方QKV 投影权重、注意力中间结果、以及 KV Cache都是吃显存的主力。KV Cache 的机制值得单独说。如果没有它每生成一个新 token都要把当前序列所有 token 的 Key-Value 重新算一遍计算量和显存都会随序列长度快速增长。有了 KV Cacheprefill 阶段算好的 Key-Value 被缓存下来decode 阶段直接复用显存需求从近似二次增长变成线性增长。这就是为什么长上下文对话能跑起来但依然很吃显存。4.3 Prefill 与 Decode两个阶段的不同脾气一次请求进来执行顺序是 prefill 然后 decode。prefill 并行处理整段输入 promptGPU 利用率容易打满计算开销大但对 batch size 不敏感。decode 阶段每次只生成一个 token是自回归的GPU 利用率通常偏低而且需要频繁读取 KV Cache属于 IO 密集型。这解释了两个常见现象。第一首 token 慢因为要等 prefill 把整段 prompt 处理完。第二后续 token 虽然快但如果并发请求多decode 阶段会互相抢资源。扩大 batch size 能分摊 decode 的固定 IO 成本但受限于 KV Cache 的读写瓶颈很难完全打满 GPU。KV Cache 的显存占用可以用一个公式估算KV Cache 大小(字节) batch_size × sequence_length × 2 × num_layers × hidden_size × sizeof(FP16)以 FP16 的 Llama2-7B 为例batch size 为 1、序列长度 4096 时KV Cache 大约是 2GB。这还没算模型权重本身——8B 模型在 FP16 下权重就要 16GB 左右。所以本地部署时显存规划必须把权重和 KV Cache 分开算。4.4 采样解码概率分布怎么变成字最后一环是采样。模型在每个位置输出一个覆盖整个词表的概率分布解码策略决定怎么从这个分布里挑 token。常见的有贪心每次选概率最高的、温度采样温度越高越随机、top-k 和 top-p只在候选集合里采样。你可以通过调整请求参数直观感受resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: 给我一个随机数字}], temperature1.2, top_p0.9, ) print(resp.choices[0].message.content)把 temperature 调到 0输出会变得确定调到 1.2同样的问题多次问会得到不同答案。这就是采样策略在起作用。理解这一点你就能解释为什么同一个 prompt 有时输出稳定、有时发散。5. 本篇常见错排查报错一401 Unauthorized。多半是 API Key 没设对。检查环境变量名是否和代码里一致key 有没有多余空格。如果刚在控制台重建过 key旧 key 会立即失效。报错二连接超时或 base_url 写错。确认 base_url 是https://taotoken.net/api不要多加斜杠或路径。有些库会自动拼接/v1如果报 404检查一下实际请求路径。报错三本地加载模型时显存不足。这是新手最常见的问题。先确认模型权重大小FP16 下参数量乘以 2 就是权重占用。如果显存不够可以换更小的模型或者用torch_dtypetorch.float16加载别默认用 FP32。报错四流式输出里 chunk 为 None。有些 chunk 的delta.content是空的比如角色标记或结束标记。代码里加个if delta:判断就能跳过不影响统计。现象五TTFT 正常但后续 token 很慢。这通常是 decode 阶段的问题可能和并发、KV Cache 读写有关。可以对比不同 batch size 下的表现观察 ITL 的变化。注意 ITL 和 TPOT 不是一回事ITL 反映每个 token 间隔的波动TPOT 是平均值。生成速度不均匀时两者会明显不一致排查性能问题建议两个都看。6. 继续深入的方向把上面这套跑通之后你对 LLM 推理流程就有了从输入到输出的完整认知分词切文本、嵌入变向量、注意力算关联、KV Cache 省重复计算、采样定输出。接下来可以往两个方向走。一是研究 Chunked Prefill 这类调度技术它把长 prompt 拆成多个 chunk在 chunk 间隙插入 decode 请求提升整体吞吐。二是动手做性能评测把 TTFT、ITL、总耗时都记录下来对比不同参数和并发下的表现。如果你打算长期做编码类或 Agent 类应用调用量会比较大可以了解一下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite 它在持续调用场景下更合适。想继续在网页上验证不同模型的推理表现模型对话入口 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite 随时可用。接入细节和参数说明都在文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里遇到问题先翻文档再对照上面的排查清单基本能定位到原因。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进