流式输出如何处理客户端背压?
简化版
客户端读取速度低于模型生成速度时,未发送数据会在服务端、网关或 TCP 缓冲区累积,这就是背压。若无限缓存,会造成内存膨胀;若发送协程阻塞推理主循环,一个慢客户端还会拖慢整批请求。
处理原则是有界缓冲、读写解耦和可取消:每个请求设置有限队列与发送超时;写满后暂停该请求、降低调度权重、合并小片段,或超过阈值后取消生成并释放 KV Cache。断连信号要贯穿网关、服务层和推理引擎,且不能让单个慢连接阻塞其他请求。
详细版
流式链路通常是“模型 Decode → Token/文本增量 → 应用队列 → SSE/WebSocket/gRPC → 代理 → 客户端”。背压可能发生在任一段,必须逐层设置缓冲上限和超时。
decode worker -> bounded per-request queue -> async writer -> network
^ |
| pause/cancel <---+ high-water mark / timeout
策略取舍:暂停保留 KV 会占显存;抢占并 Swap/重算有恢复成本;继续生成并丢弃输出浪费算力;直接取消影响用户。因此要按业务 SLO、缓冲字节、等待时间和租户配额决策,并监控队列深度、阻塞时间、断连取消延迟、废弃 Token 和慢客户端比例。
完整版教学
1. 背压发生在哪里
模型生成 Token 后,应用可能先做解码、审核和组帧,再写入网络。客户端、代理或服务端任何一段变慢,数据都会向上游累积。
GPU -> sampler -> detokenizer -> app buffer -> proxy buffer -> socket -> client
如果链路没有反馈机制,生产者只知道“生成成功”,却不知道消费者已经跟不上。
2. 为什么无限缓冲不可接受
假设每连接净积压速度为 r Byte/s,慢客户端数为 n,持续 t 秒,新增内存近似:
buffer_growth = n × r × t
积压还会延长取消后的资源释放时间。攻击者可故意建立大量慢读连接,形成 Slow Reader DoS。
3. 为什么不能阻塞推理主循环
若 Decode 线程同步写 Socket,一个客户端发送阻塞会延迟同批所有序列的下一轮生成,TPOT 集体恶化。
应让推理生产与网络消费通过每请求有界队列解耦,写操作由异步 I/O 处理。队列满时将压力反馈给该请求的调度状态,而不是阻塞全局 Worker。
4. 有界队列怎样设置
队列可按字节、Token 数或帧数限制。字节最接近内存与网络成本,Token 更方便连接推理调度;实践中可同时设置。
| 水位 | 动作 |
|---|---|
| 低水位以下 | 正常生成和发送 |
| 高水位以上 | 暂停/降权该请求 |
| 持续超时 | 取消或转异步结果 |
| 客户端断连 | 立即传播取消 |
高低水位形成迟滞,避免请求在暂停和恢复间频繁震荡。
5. 暂停生成有什么代价
暂停能停止新的积压,但该请求的 KV Cache 仍占显存。大量慢客户端都暂停时,GPU 可能没有计算负载却无法接收新请求。
因此还要设置最大暂停时间和每租户暂停 KV 配额;超过阈值可 Swap、丢弃后重算或取消,具体取决于恢复成本与业务价值。
6. 为什么不能只丢 Token
流式文本存在 UTF-8、JSON、Markdown 或工具调用边界。随意丢掉中间 Token 会破坏语义和协议,客户端也无法知道缺失位置。
可以合并多个小增量成较大帧,减少系统调用,但不能静默删除内容。若产品允许摘要式降级,必须重新生成符合协议的结果并明确标识。
7. 断连取消如何贯穿全链路
客户端关闭连接后,网关要取消上游 RPC,应用取消生成任务,调度器移出序列,KV 管理器最终释放块。
disconnect -> cancel context -> stop scheduling -> synchronize in-flight kernel -> free KV
取消应幂等,避免网络和超时同时触发导致重复释放。还需检测半开连接与代理未及时传递断连的情况。
8. SSE、WebSocket 与 gRPC 的差异
SSE 基于单向 HTTP 流,简单但受代理缓冲配置影响;WebSocket 双向,可显式发送暂停或确认;gRPC Streaming 通常有 HTTP/2 流控窗口。
底层协议有流控不代表应用无需限制。传输库可能先把大量数据写入用户态缓冲,应用看到“写成功”时数据尚未被客户端消费。
9. 代理缓冲为何会隐藏问题
反向代理可能聚合响应后再发送,使客户端看不到实时 Token,同时让背压延迟传回服务。要为流式路由关闭不合适的响应缓冲,并设置代理写超时与最大缓冲。
CDN、Ingress 和 Service Mesh 都可能有独立设置。端到端测试必须经过真实生产链路,而不是只直连模型服务。
10. 多租户如何防慢连接滥用
限制每租户流式连接数、累计缓冲字节、暂停 KV 和输出 Token;慢连接不能借“已准入”无限占用资源。
高价值请求可获得更长宽限期,普通请求超过阈值快速取消。优先级来自可信身份,不能由客户端自由声明。
11. 流式安全审核如何配合
输出审核若按较大文本窗口工作,也会形成消费速度瓶颈。不能为了低延迟先发送未经审核内容,再在发现违规后尝试撤回。
应把审核吞吐纳入链路容量,使用有界审核队列与安全分块。高风险场景可以牺牲部分流式实时性,先积累足够上下文再放行。
12. 应监控哪些指标
至少记录每请求缓冲字节、队列高水位次数、网络写阻塞时长、暂停请求数、暂停 KV、取消传播延迟、断连后废弃 Token 和慢客户端比例。
分租户、协议、区域和代理节点观察。服务端 TPOT 正常而客户端 Token 间隔很差,通常说明瓶颈在后半段链路。
13. 如何测试与验收
用网络整形模拟低带宽、高 RTT、暂停读取、突然断连和大量 Slow Reader;同时运行正常客户端,验证它们的 TTFT/TPOT 不受显著影响。
检查内存与 KV 是否有界、取消后资源能及时回收、输出帧没有乱码或丢失。灰度时为废弃 Token、暂停时间和全局尾延迟设置回滚阈值。
背压处理的核心不是让慢客户端继续无限等待,而是把它的影响限制在自己的资源预算内。
14. 常见误区与追问
- 误区:TCP 自带流控,所以应用不用管。 应用缓冲和 GPU KV 仍可能无限积压。
- 误区:发送函数返回就代表客户端已消费。 数据可能只进入本地或代理缓冲。
- 误区:队列满时丢几个 Token 即可。 会破坏文本与协议语义。
- 误区:暂停生成不再消耗资源。 KV Cache 仍然占显存。
- 误区:客户端断开后推理会自动停止。 取消必须显式传播到调度器和 KV 管理。
- 追问:为何使用高低水位? 迟滞可避免暂停/恢复频繁抖动。
- 追问:一个慢客户端为何能拖累全批? 同步网络写若占住推理循环,会阻塞其他序列调度。
15. 加强记忆
- 先找链路:GPU、应用队列、代理、Socket、客户端。
- 再守边界:每请求队列和等待时间必须有上限。
- 再做解耦:异步写,不能阻塞推理主循环。
- 再做反馈:高水位暂停或降权,超时取消。
- 再管资源:暂停仍占 KV,要有暂停配额。
- 再传取消:断连一路到调度器并安全释放缓存。
- 最后验收:慢客户端不能拖垮正常客户端,内存和 KV 始终有界。