news 2026/10/20 16:18:36

大模型推理端到端流程详解:从请求接入到流式返回的工程实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
大模型推理端到端流程详解:从请求接入到流式返回的工程实践

1. 大模型推理到底在做什么

很多人第一次接触大模型推理,脑子里浮现的画面是“输入一句话,模型吐出一段话”,看起来跟搜索引擎差不多。但真把模型部署到线上跑起来,你会发现事情远没有这么简单。大模型推理端到端流程,说白了就是从用户敲下回车那一刻起,到屏幕上逐字蹦出回答为止,中间经过的完整链路。这条链路上有请求接入、分词、前向计算、采样、流式返回、资源调度等一大堆环节,任何一个环节卡住,用户感受到的就是“转圈圈”或者“答非所问”。

我做过好几个不同规模的推理服务,从单卡跑7B小模型到多机多卡跑百B级模型都趟过一遍。踩过的坑包括但不限于:显存碎片导致服务跑几小时就崩、批处理没做好导致GPU利用率只有20%、流式输出被反向代理缓冲住变成“一次性吐出来”。这些问题在论文里基本不会讲,但线上环境天天遇到。

这篇文章适合谁看?如果你是刚接触大模型部署的工程师,想搞清楚从请求到响应到底发生了什么;或者你已经能跑通demo,但想把服务做得更稳、更快、更省资源;再或者你是后端/运维同学,需要理解推理服务的资源特征来做好容量规划——那这篇内容应该能帮到你。我会按端到端的顺序,把每个环节拆开讲清楚,包括为什么这么设计、参数怎么算、坑在哪里。

2. 端到端流程的整体拆解与设计思路

2.1 从请求到响应的完整链路

先给一个全局视角。一个典型的大模型推理请求,走完整个流程大致经历这些阶段:

  1. 请求接入与鉴权:网关收到HTTP请求,校验token、限流、记录日志。
  2. 请求排队与批处理调度:请求进入队列,调度器决定和哪些请求凑成一批。
  3. 输入预处理(分词):把文本转成token id序列,处理padding和attention mask。
  4. 前向计算(Prefill阶段):对输入的所有token做一次完整的前向传播,生成KV Cache。
  5. 自回归解码(Decode阶段):逐个token生成,每步只算一个新token,复用KV Cache。
  6. 采样与后处理:根据logits做采样(贪心/温度/top-p等),得到下一个token。
  7. 流式返回:把生成的token逐步解码成文本,通过SSE或WebSocket推给客户端。
  8. 资源回收:请求结束,释放KV Cache占用的显存块。

这八个阶段里,Prefill和Decode的计算特征完全不同,这是理解整个推理优化的关键。Prefill是计算密集型,矩阵乘法规模大,GPU算力吃满;Decode是访存密集型,每步只生成一个token,但要把整个KV Cache读一遍,显存带宽是瓶颈。很多优化手段就是围绕这个差异做文章。

2.2 为什么要把Prefill和Decode分开看

我刚开始做推理服务时,把Prefill和Decode混在一起调度,结果发现GPU利用率忽高忽低,延迟抖动特别大。后来才想明白:Prefill请求和Decode请求对资源的需求是冲突的。一个长输入的Prefill可能占用大量算力,把正在Decode的请求卡住,导致首token延迟(TTFT)和每token延迟(TPOT)都变差。

现在主流的做法是Prefill和Decode分离部署,或者至少在同一张卡上做分时调度。分离部署的好处是:Prefill集群用高算力卡,Decode集群用高显存带宽卡,各自优化。代价是KV Cache需要在集群间传输,网络带宽成了新瓶颈。所以小规模场景下,同卡分时调度更实际;大规模场景才值得上分离架构。

注意:分离部署不是银弹。KV Cache传输量 = 层数 × 2 × 头数 × 头维度 × 序列长度 × 精度字节数。一个70B模型、序列长度4096、FP16精度,单请求KV Cache大约几个GB,传输开销很可观。

2.3 批处理策略的选择逻辑

批处理是提升吞吐最直接的手段,但怎么批有讲究。静态批处理(Static Batching)是把固定数量的请求凑一起,等最长的那个生成完才释放。问题是短请求被长请求拖累,GPU空转。连续批处理(Continuous Batching)则是每生成一步就检查有没有请求完成,完成了就移出,空出的位置立刻填入新请求。这样GPU几乎不空转,吞吐能提升好几倍。

我实测过一个7B模型,静态批处理在混合长度请求下GPU利用率只有35%左右,换成连续批处理后拉到70%以上。代价是调度逻辑复杂很多,要维护每个请求的状态、KV Cache块的分配和回收。现在vLLM、TensorRT-LLM这些框架都内置了连续批处理,自己从零实现的话要小心内存管理。

3. 核心环节的细节与实操要点

3.1 分词与输入预处理

分词看起来简单,其实暗坑不少。不同模型用的分词器不一样,BPE、SentencePiece、WordPiece各有各的规则。最关键的一点是:推理时的分词必须和训练时完全一致,否则模型看到的就是“乱码”。我见过有人用错分词器版本,模型输出全是重复词,排查了半天。

预处理阶段还要处理几个参数:

  • max_length:输入截断长度。设太小会丢上下文,设太大浪费显存。
  • padding:批处理时对齐长度。建议用left padding,因为自回归生成是从右边续,left padding能让有效token对齐。
  • attention mask:标记哪些是真实token,哪些是padding。
# 典型的分词预处理示例 from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("model_path", padding_side="left") inputs = tokenizer( batch_texts, return_tensors="pt", padding=True, truncation=True, max_length=4096 ) # inputs.input_ids 和 inputs.attention_mask 送入模型

实操心得:如果服务要处理超长输入,建议在网关层就做长度检查,超限直接拒绝,别等到进了模型才报错。显存被一个超长请求吃满,整个批次都受影响。

3.2 KV Cache的原理与显存计算

KV Cache是自回归生成的核心优化。没有它,每生成一个token都要把前面所有token重新算一遍,复杂度是O(n²)。有了它,每步只算新token的K和V,前面缓存的直接复用,复杂度降到O(n)。

但KV Cache吃显存很凶。计算公式:

KV Cache大小 = 2 × 层数 × 头数 × 头维度 × 序列长度 × 批大小 × 精度字节数

以一个13B模型为例:40层、40头、头维度128、序列长度2048、批大小8、FP16(2字节):

2 × 40 × 40 × 128 × 2048 × 8 × 2 = 约21.5 GB

光KV Cache就20多GB,加上模型权重26GB,一张40GB的卡根本放不下。所以实际部署要么减批大小,要么用量化,要么上PagedAttention这类显存管理技术。

PagedAttention的思路借鉴了操作系统的虚拟内存分页:把KV Cache切成固定大小的块(block),不要求连续存储,用块表来索引。这样显存碎片大大减少,还能做块级别的共享(比如多个请求共享同一个system prompt的KV)。vLLM就是靠这个把吞吐提升了好几倍。

3.3 采样策略的参数选择

模型输出的是logits,怎么从logits变成token,就是采样策略的事。常见的有:

策略参数特点适用场景
贪心搜索无每步选概率最大的确定性任务,如代码生成
温度采样temperature温度高更随机创意写作
Top-kk只从前k个里采样控制多样性
Top-pp从累积概率达p的最小集合采样通用推荐
重复惩罚repetition_penalty降低已出现token概率防止复读

温度参数我一般设0.7左右做通用对话,代码生成设0.1~0.2。Top-p设0.9比较稳。重复惩罚别设太高,1.1~1.2就够了,设到1.5以上容易让输出变得不连贯。

注意:采样是在GPU上做的,每步都要把logits传到CPU再采样的话,延迟会明显增加。建议用GPU上的采样算子,或者至少把logits传输和采样异步化。

4. 完整实操流程与关键环节实现

4.1 环境准备与依赖安装

假设我们用vLLM来搭一个推理服务,这是目前比较省心的方案。环境准备大致如下:

# 创建虚拟环境 python -m venv venv source venv/bin/activate # 安装vLLM(注意版本要和CUDA匹配) pip install vllm==0.4.0 # 如果要用OpenAI兼容接口 pip install openai

CUDA版本很关键。vLLM 0.4.0需要CUDA 12.1以上,驱动版本要对应。我踩过一次坑:驱动是525,CUDA 12.0,装完vLLM跑起来报“no kernel image available”,折腾半天才发现是版本不匹配。建议先用nvidia-smi确认驱动和CUDA版本,再去vLLM文档查兼容表。

4.2 启动推理服务

vLLM提供了OpenAI兼容的API server,启动命令:

python -m vllm.entrypoints.openai.api_server \ --model /path/to/model \ --tensor-parallel-size 2 \ --max-model-len 4096 \ --gpu-memory-utilization 0.9 \ --max-num-seqs 64 \ --served-model-name my-model

几个关键参数解释:

  • tensor-parallel-size:张量并行度,等于用的GPU数量。2就是两张卡分模型。
  • max-model-len:最大序列长度,包括输入和输出。设太大会预留过多显存。
  • gpu-memory-utilization:GPU显存使用上限比例。0.9表示用90%,留10%给系统。
  • max-num-seqs:最大并发序列数。这个直接影响吞吐和延迟的平衡。

启动后,服务默认监听8000端口,可以用curl测试:

curl http://localhost:8000/v1/completions \ -H "Content-Type: application/json" \ -d '{ "model": "my-model", "prompt": "介绍一下大模型推理", "max_tokens": 128, "temperature": 0.7, "stream": true }'

4.3 流式输出的实现细节

流式输出是提升用户体验的关键。用户不用等整段生成完,首token出来就能看到。实现上一般用SSE(Server-Sent Events),服务端每生成一个token就推一次。

但这里有个常见坑:反向代理会缓冲响应。Nginx默认会等buffer满了才转发,导致流式变成“攒一批再吐”。解决办法是在Nginx配置里关掉缓冲:

location /v1/ { proxy_pass http://backend; proxy_buffering off; proxy_cache off; proxy_set_header Connection ''; proxy_http_version 1.1; chunked_transfer_encoding off; }

另外,流式输出的token要按“词”而不是“字”来推。有些token是半个词(比如中文的一个字可能由多个token组成),直接推会看到乱码。要用tokenizer的decode方法做增量解码,维护一个已输出文本的buffer,每次只推新增部分。

4.4 性能压测与参数调优

服务搭起来只是第一步,能不能扛住流量要压测才知道。我一般用locust或者自己写脚本,模拟不同并发下的请求。

压测要关注几个指标:

  • TTFT(Time To First Token):首token延迟,影响用户感知。
  • TPOT(Time Per Output Token):每token延迟,影响生成速度。
  • 吞吐(tokens/s):整体生成速度。
  • GPU利用率:算力和显存带宽的使用情况。

调优的顺序一般是:先调max-num-seqs找到吞吐和延迟的平衡点,再调gpu-memory-utilization看能不能塞更多KV Cache,最后考虑量化(FP8/INT8)进一步压显存。

我实测过一个7B模型在单张A100上的表现:max-num-seqs=32时,吞吐约1200 tokens/s,TTFT约200ms;调到64时,吞吐到1800 tokens/s,但TTFT涨到400ms。所以并发数不是越高越好,要看业务能接受的延迟。

5. 常见问题与排查技巧实录

5.1 显存溢出(OOM)的排查思路

OOM是推理服务最常见的故障。排查步骤:

  1. 看日志确认是模型加载时OOM还是运行时OOM。加载时OOM说明模型太大,要上量化或张量并行;运行时OOM说明KV Cache爆了,要减max-model-len或max-num-seqs。
  2. 用nvidia-smi看显存占用曲线。如果是一路涨到爆,说明有显存泄漏,检查KV Cache块有没有正确回收。
  3. 检查是否有超长请求。一个max_length=32768的请求,KV Cache可能是正常请求的8倍。

避坑技巧:在网关层加输入长度限制,超限直接返回错误,别让请求进到模型。另外可以给每个请求设一个生成token上限,防止模型“刹不住车”一直生成。

5.2 输出质量问题的定位

输出质量差,可能的原因很多,按概率排序:

  • 分词器不匹配:最常见。确认推理用的tokenizer和训练时一致。
  • 采样参数不当:温度太高输出发散,太低输出重复。试试temperature=0.7、top_p=0.9。
  • 精度问题:用了INT4量化但模型对精度敏感。换FP16或INT8试试。
  • 上下文截断:max_length设太小,关键上下文被截掉了。

排查时建议先用贪心搜索(temperature=0)跑一个简单prompt,如果贪心输出都不对,那基本是模型或分词器的问题;如果贪心对但采样不对,那就是采样参数的事。

5.3 延迟抖动的常见原因

延迟忽高忽低,用户体验很差。常见原因:

现象可能原因解决办法
TTFT周期性变高有长Prefill请求插队Prefill/Decode分离调度
TPOT逐渐变慢KV Cache碎片化用PagedAttention
整体延迟突然翻倍显存不足触发换页降低并发或加卡
流式输出卡顿反向代理缓冲关闭proxy_buffering

我遇到过一次TPOT从50ms慢慢涨到200ms的情况,查了半天发现是KV Cache块碎片化,连续内存分配失败,触发了显存整理。换成vLLM的PagedAttention后问题消失。

5.4 常见问题速查表

问题排查方向快速验证
服务启动失败CUDA版本、模型路径看启动日志报错
首token特别慢Prefill排队、输入太长测短输入TTFT
生成速度慢批大小、显存带宽看GPU利用率
输出乱码分词器、解码方式贪心搜索测试
服务跑一段时间崩显存泄漏、KV Cache回收监控显存曲线
并发上不去max-num-seqs、显存逐步加压测

6. 几个容易被忽略的工程细节

6.1 模型加载的冷启动优化

模型加载慢是另一个痛点。一个70B模型从磁盘加载到GPU,可能要几分钟。如果服务重启,这段时间完全不可用。优化手段:

  • 用更快的存储:NVMe SSD比普通SSD快好几倍。
  • 模型分片并行加载:多卡同时加载各自的分片。
  • 预热:服务启动后先跑几个请求,把CUDA kernel编译缓存建好。

我试过用内存文件系统缓存模型权重,加载时间从3分钟降到40秒。代价是内存占用大,适合内存充裕的机器。

6.2 多模型共存的资源隔离

一个服务要跑多个模型时,资源隔离很重要。否则一个模型的流量高峰会把另一个模型饿死。做法有:

  • 分卡部署:每个模型独占GPU,最简单但成本高。
  • MPS(Multi-Process Service):让多个进程共享GPU,但隔离性一般。
  • 时间片轮转:调度器按时间片切换模型,实现复杂。

小规模场景建议分卡,省心。大规模场景可以考虑用推理框架的多模型支持,比如Triton Inference Server,它内置了模型编排和资源管理。

6.3 监控与告警的搭建

线上服务没有监控就是裸奔。至少要监控:

  • 请求量、成功率、延迟分布(P50/P95/P99)
  • GPU利用率、显存占用、温度
  • 队列长度、批大小分布
  • KV Cache使用率

告警阈值我一般设:P99延迟超过1秒告警,显存占用超过90%告警,错误率超过1%告警。监控数据用Prometheus采集,Grafana展示,这套组合比较成熟。

实操心得:延迟监控一定要分Prefill和Decode两个阶段看。混在一起看平均值会掩盖问题。我见过平均延迟200ms但P99到5秒的情况,就是长Prefill请求导致的。

7. 关于推理优化的一些个人体会

做推理服务这两年,最大的感受是:没有银弹,只有权衡。吞吐和延迟是矛盾的,成本和体验是矛盾的,通用性和性能是矛盾的。每次优化都是在这些矛盾里找平衡点。

另一个体会是:先跑通,再优化。我见过太多人一上来就追求极致性能,结果连基本功能都没跑通。正确的顺序是:先用最朴素的方式把端到端流程跑通,确认输入输出正确,然后压测找到瓶颈,再针对性优化。优化的时候一次只改一个变量,改完压测对比,别一次改一堆,出了问题都不知道是哪个改动导致的。

最后分享一个小技巧:把推理服务的日志打全。每个请求记录输入长度、输出长度、TTFT、TPOT、批大小、KV Cache使用量。这些数据在排查问题和容量规划时非常有用。我现在的服务日志里,每个请求一行JSON,用ELK收集,查问题的时候直接搜,比翻代码快多了。

这个领域变化很快,新的框架和优化技术层出不穷。但底层的原理——Prefill和Decode的计算特征、KV Cache的显存管理、批处理的调度逻辑——这些是不变的。把原理搞清楚了,学新框架就是看文档的事。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/20 16:18:09

电气元件选型工具:规则引擎驱动的参数校验与I²t联动分析

简介:这是一款面向电气工程师、自动化设计人员及高职高专相关专业学习者的实用型选型辅助工具,聚焦电缆线径与载流量的快速计算,解决传统人工查表选型效率低、易出错的问题,适用于工业配电系统设计、建筑电气施工图深化等实际场景…

作者头像 李华
网站建设 2026/10/20 16:17:24

二叉搜索树操作题详解:递归返回值与结构修改的核心套路

开篇直入:二叉搜索树的“操作题”才是真正进入状态的地方DAY20,第六章二叉树part07。训练营走到这里,二叉树已经从“遍历模板”正式转向“结构操作”了。前面你刷过了前中后序遍历、层序遍历、翻转、对称、路径总和、最大深度这些基础题&…

作者头像 李华
网站建设 2026/10/20 16:17:03

FastAPI+LangGraph实战:多Agent网关四层架构与完整源码

说句实话,市面上的多 Agent 教程大多停在“两个 Agent 互相传一句话”的玩具阶段,真正能放进生产目录、敢让同事接手、加新 Agent 不推倒重来的工程级示例反而非常少。我最近用 FastAPI LangGraph 搭了一个完整的多 Agent 网关,把 Gateway、…

作者头像 李华
网站建设 2026/10/20 16:16:15

永磁同步电机扰动从哪来?分类、抑制与调试实战经验

做永磁同步电机控制这些年,我最大的感受是:干这行的人,十个里有八个每天都在跟“扰动”这个词打交道。现场报故障的时候,十句话里七句离不开这俩字:速度环偶发抖动是扰动,负载一加转速往下掉是扰动&#xf…

作者头像 李华
网站建设 2026/10/20 16:16:13

机器学习入门:从核心概念到模型训练与评估的完整认知框架

上周几个朋友吃饭,有人突然问我:“你说现在到处都在聊AI、聊机器学习,我要是想搞懂它到底是个啥,三分钟够不够?”我当时没直接回答,因为“三分钟”这个说法其实很有误导性——它不是让你三分钟变成专家&…

作者头像 李华
网站建设 2026/10/20 16:15:20

在Linux上安装VMware Player:内核模块与故障排查指南

说实话,我一开始也没想到自己会在 Linux 上折腾 VMware Player 这么久。前几个月我把主力机从 Windows 换到某发行版 Linux,本来觉得最难的应该是习惯终端操作,结果真正卡住我的,是几个只有 Windows 环境才能跑的工程软件和调试工…

作者头像 李华