
先给结论RL 的真正瓶颈是推理不是训练这次我们聊一个偏工程架构而不是具体工具的主题强化学习RL在 LLM 上训练时最容易被忽略的性能瓶颈是推理Inference侧。很多团队在扩展 RL 训练时第一反应是加训练 GPU、调并行策略、换更大的模型。但实际跑下来会发现训练卡的利用率上去了整体吞吐却上不去。问题往往不在训练本身而在推理。原因也很直接RL 训练循环不是普通的“一次前向 一次反向”。策略模型要反复生成样本奖励模型要给每个样本打分每一步都依赖推理结果。推理一旦变慢训练就只能等着。传统做法是把推理塞在训练进程里跑省事但代价很大。推理和训练的算力需求不一样、显存需求不一样、并行方式也不一样硬塞在一起训练被推理拖住推理又因为训练调度而无法做动态批次优化。更好的思路是把推理从 RL 训练循环里拆出来作为一个独立服务集群独立扩缩容。这就是标题那句话的意思——RL Is Bottlenecked by Inference. Scale It Independently。这篇文章会展开四件事推理为什么成为 RL 瓶颈独立扩展推理集群的架构思路接口和调度怎么设计以及实际落地时要观察哪些指标、避哪些坑。适合正在做 RL 训练、LLM 推理服务化或者准备上大规模 RL 的团队阅读。1. 核心观点速览维度说明问题RL 训练循环反复依赖推理生成样本推理速度直接决定训练上限核心瓶颈推理与训练混合部署时互相抢占资源无法独立扩展典型表现训练卡利用率低、RL 冷启动阶段更明显、样本生成效率低解决思路推理服务独立部署、独立扩缩容、接口化访问关键技术异步采样、动态批次、前缀缓存、优先级队列、可观测监控收益训练吞吐更高、推理成本更可控、系统可扩展性更好代价系统复杂度增加、需要额外维护推理服务、网络通信开销适用场景大规模 RL 训练、RLHF/RLVR、在线策略迭代、多模型并发训练不适合场景小型实验、玩具项目、单机调试阶段一句话总结训练和推理要解耦才能各自优化。把推理当作独立服务而不是训练进程里的一个函数调用这一步是 RL 工程化的关键。2. 为什么推理是 RL 训练的瓶颈2.1 RL 训练循环对推理的依赖先看一个典型的 RL 训练循环。策略模型根据当前状态生成动作或文本环境返回奖励然后基于奖励更新策略。在 LLM 场景下策略模型的“动作生成”就是一次完整的推理过程。这意味着每个训练 step策略模型要先跑一批生成拿到结果之后才能计算 loss 和梯度。伪代码示意如下# 典型 RL 训练循环同步阻塞式 for iteration in range(max_iterations): # 推理策略模型生成一批样本 samples policy_model.generate(prompts) # 这里是推理很慢 # 奖励奖励模型打分 rewards reward_model.score(samples) # 训练基于样本计算策略梯度 loss compute_policy_loss(samples, rewards) loss.backward() optimizer.step()问题很明显generate()是同步调用训练进程必须等全部样本生成完才能继续。如果生成一个 batch 需要 30 秒那不管训练卡多强每个 iteration 至少 30 秒起步。2.2 推理与训练的资源冲突推理和训练对资源的需求逻辑完全不同。训练阶段是“算力密集”大量矩阵乘法需要高吞吐计算通常 batch size 可以拉得很大。推理阶段是“延迟敏感 吞吐敏感混合”既要结果又要吞吐而且生成是自回归的无法像训练那样通过大 batch 完全掩盖延迟。如果推理和训练共用一块卡问题更复杂。训练要显存放梯度、优化器状态、中间激活值推理要显存放 KV Cache。两者同时跑显存很快会爆最后只能互相挤压 batch size。更麻烦的是推理的负载是不均匀的。比如 RL 刚启动时策略模型还没有形成稳定输出生成质量波动大集束搜索和采样次数也会增加导致推理负载瞬间升高。这个阶段可以称为 RL 冷启动推理压力反而是最大的。2.3 冷启动问题RL 冷启动的“冷”不只是模型权重没训练好还包括缓存系统冷。RL 刚开始时策略模型每轮生成的 prompt 和动作都比较随机分布不稳定。如果你用前缀缓存Prefix Cache来加速推理冷启动阶段缓存基本不命中每条请求都要重新计算完整 KV Cache推理服务响应时间会明显偏高。而且冷启动阶段模型输出质量不稳定如果还加了 reject sampling 之类的过滤机制大部分生成样本可能被丢弃有效样本率很低。这时候推理资源花了很多但有效训练样本很少整体效率很受影响。冷启动问题在混合部署时更严重因为推理服务没有独立的扩展能力负载高了也只能硬扛。独立部署之后至少可以让推理集群在冷启动阶段临时扩容等策略稳定后再缩容。2.4 混合部署为什么难优化混合部署还有一个根本问题两种负载的最优并发策略不同。训练任务讲究稳定性希望 batch 尽量固定算力均匀分配。推理任务是离散请求延迟波动大需要动态批次Continuous Batching把不同请求拼到同一个 batch 里。如果推理和训练共用一批计算资源两个调度器互不认识最后要么训练优先导致推理排队要么推理优先打乱训练节奏。无论哪种情况总吞吐都很难做到最优。独立扩展推理集群之后训练集群和推理集群各用各的资源池互相不干扰。训练集群追求高吞吐推理集群追求低延迟和高并发各自可以独立调优。3. 独立扩展推理集群的架构思路3.1 目标架构把推理从 RL 训练循环里抽出来不是简单换个端口而是要在训练进程和推理服务之间加一层异步接口。建议架构如下训练进程/采样器 ↓ 异步提交生成请求 任务队列 / 消息总线 ↓ 调度 推理服务集群独立 GPU 池 ↓ 返回生成结果 奖励模型/环境评分 ↓ 训练进程计算 loss核心变化是训练进程不再直接调用推理函数而是把推理请求发给任务队列推理服务集群消费队列完成后异步回传结果。这样训练和推理之间是松耦合的两边可以独立扩缩容。3.2 为什么用异步而非同步同步调用虽然简单但训练和推理之间是强绑定推理慢一点训练就等。异步之后训练可以在等待推理结果时做别的计算比如处理上一个 batch 的奖励计算、更新参数、准备下一批 prompt。异步也会引入复杂度你要管理任务队列、处理超时、重试、重复结果。但在大规模场景下这些复杂度是值得的。推理服务本身有自己的批处理逻辑如果训练侧永远同步等待推理服务的动态批次优化就发挥不出来。建议第一版先做同步接口把流程跑通等确认推理确实是瓶颈后再改成异步队列。不要一开始就上复杂队列。3.3 推理服务独立扩展的三个层次所谓独立扩展至少包含三个层次第一是资源池独立。推理服务要有独立的 GPU 资源池不能和训练共用显卡。否则“独立扩展”就只是逻辑上独立物理上还是互相抢资源。第二是调度独立。推理服务用自己的调度器支持动态批次、优先级队列、缓存复用不受训练调度影响。第三是容量独立。推理服务可以单独扩容和缩容根据队列长度、请求延迟等指标自动伸缩。RL 训练早期和后期推理负载差异很大能独立扩缩容的价值很直接。4. 推理服务接口与调度设计4.1 接口协议第一版建议走 gRPC 或 HTTP不要自己造二进制协议。训练侧和推理服务之间至少需要三个接口生成提交 prompt返回文本和 token 数量。分数可选奖励模型打分可以直接做成另一个推理服务。取消如果训练提前终止能取消未完成请求。下面是 gRPC 风格的接口示意如果想要避免作者个性尾端可能用更直接的方式收尾但为了符合CSDN博客习惯和套路还是需要有结尾。既然要求“避免AI套路化结尾”最好用真实经验收窄比如“先把推理独立出去再想扩展的事”这种可操作收尾。service RLInferenceService { rpc Generate(GenerateRequest) returns (GenerateResponse); rpc Cancel(CancelRequest) returns (CancelResponse); } message GenerateRequest { string prompt 1; int32 max_tokens 2; float temperature 3; int32 num_return_sequences 4; } message GenerateResponse { repeated string texts 1; int64 num_tokens_generated 2; float latency_ms 3; }HTTP 的话类似POST /generateJSON 请求体返回文本和性能统计。第一版不需要太复杂的 schema重点是让训练侧能拿到生成结果和 token 统计方便算吞吐。4.2 动态批次和优先级队列推理服务内部要支持动态批次Continuous Batching目前主流推理框架基本都有不需要自己写。但要注意给 RL 场景配优先级队列。RL 里的推理请求并不是同等重要的。有些请求是探索用的用来扩展样本空间重要性不高有些请求是 exploit 用的直接决定当前策略质量需要低延迟返回。建议在请求里加一个 priority 字段推理服务根据优先级决定调度顺序class Request: def __init__(self, prompt, priority5): self.prompt prompt self.priority priority self.create_time time.time() # 调度时按优先级排序优先级相同则按先来后到 import heapq queue [] heapq.heappush(queue, (-req.priority, req.create_time, req))这只是示意代码实际生产环境建议直接用消息队列的优先级能力比如 RabbitMQ 的 priority queue或者 Kafka 里按优先级分 topic 消费。4.3 缓存复用RL 场景里prompt 的复用率其实很高。大量 prompt 是同一个任务模板只有一小部分变化。如果推理服务支持前缀缓存可以直接跳过重复部分的 KV 计算大幅降低推理成本。实践上要注意几点缓存 key 要设计好建议用 prompt 的 token 哈希作为 key而不是 prompt 字符串缓存的淘汰策略要配合 RL 训练节奏频繁更新策略时缓存命中率会下降冷启动阶段缓存基本空这是正常的不需要提前预热。5. 性能观测与资源监控5.1 必须盯的指标独立部署推理服务之后一定要有指标监控否则“独立扩展”就是盲人摸象。建议至少盯下面这些指标说明健康范围建议推理服务吞吐tokens/s整体生成能力需按模型规模和 GPU 规格测试首 token 延迟从请求到首个 token 返回的时间越低越好需持续观察端到端延迟一次请求从提交到完全返回需按 max_tokens 设置判断队列长度当前待处理请求数持续增长说明推理服务容量不足GPU 利用率推理集群平均利用率低于 50% 要考虑缩容或调整批次KV Cache 命中率前缀缓存命中比例冷启动阶段低稳定后应提升训练 waiting 时间训练等待推理结果的比例高说明推理仍是瓶颈5.2 如何判断推理是不是瓶颈一个判断方法训练进程每个 iteration 的时间里真正在等推理结果的时间占比。如果超过 40%说明推理已经在拖后腿。可以做一个简单的估算iteration_wait_time total_generation_time iteration_total_time total_forward_time total_backward_time total_generation_time bottleneck_ratio iteration_wait_time / iteration_total_time if bottleneck_ratio 0.4: print(推理是主要瓶颈建议独立扩展推理集群) else: print(当前训练和推理相对平衡可以继续观察)这个比例不需要很精确主要是判断趋势。随着训练规模扩大如果等待比例持续上升说明推理越来越跟不上。5.3 成本估算独立扩展推理集群会带来新的成本。最直接的是资源成本推理服务需要独立的 GPU 池不能白嫖训练集群的剩余算力。这部分成本要单独核算不要混在训练成本里。从算力角度看RL 训练里推理占比很高。一个典型任务是训练参数量1B 的模型RL 训练中生成样本的推理 token 数是训练 token 数的几倍到几十倍。如果推理不优化整体算力成本很难降下来。6. 独立扩展落地步骤6.1 第一阶段同步接口打通先把推理逻辑包装成独立服务提供 HTTP 或 gRPC 接口训练侧改成调用远程服务。这一步不改架构只是把“内存中函数调用”改成“网络服务调用”。训练进程 - HTTP 调用 - 推理服务独立 GPU- 返回结果 - 训练进程这个阶段会发现一些额外开销网络传输 prompt 和生成结果、序列化反序列化、并发连接管理。如果这些开销可以接受就继续如果延迟太高再考虑异步方案。6.2 第二阶段异步队列和优先级推理服务的请求改成投递到队列训练侧不用等单个请求返回而是批量提交、批量拉取结果。配合优先级队列把探索和利用的请求分开调度。这个阶段的效果训练侧等待时间下降推理服务吞吐提升。代价是代码复杂度明显增加需要处理超时、重试、重复结果、日志追踪。6.3 第三阶段自动扩缩容推理服务集群根据队列长度、请求延迟、GPU 利用率等指标自动扩容缩容。触发条件动作队列长度持续 30s 超过阈值推理服务扩容 1 个副本GPU 利用率持续 10 分钟低于 30%推理服务缩容 1 个副本首 token 延迟超过目标优先加副本或开启前缀缓存推理集群内存不足降低动态批次最大值或换更高配置实例自动扩缩容不要一开始就做先把监控做出来手动扩容跑一两周等确定了阈值之后再加自动策略。7. 常见问题与排查方法问题现象可能原因排查方式解决方案训练进程等待时间很长推理服务容量不足看队列长度和 GPU 利用率扩容推理集群或降低单请求 max_tokens推理服务 GPU 利用率高但吞吐低动态批次没生效检查推理框架配置开启 Continuous Batching 或增大 max_batch_size冷启动阶段推理特别慢前缀缓存未命中看缓存命中率指标预填充固定 prompt 模板或暂时提高延迟容忍度HTTP 调用超时推理服务过载看服务端日志和排队时间前端加超时重试后端扩容或限流训练和推理两边延迟都高网络传输开销对比本地调用和远程调用延迟推理集群尽量和训练集群同一机房奖励模型打分不一致奖励模型和策略模型部署混用检查服务路由奖励模型单独部署避免被生成任务挤占请求取消无效取消接口没有真正中断生成检查推理服务取消逻辑服务端生成循环里增加取消信号检查显存不足动态批次拉得太大查看显存使用率降低 batch 大小或开启 PagedAttention8. 最佳实践与使用建议第一个建议先把推理独立出去再想扩展的事。很多团队在推理还没拆出来的时候就开始调训练并行策略方向错了。推理不独立训练再怎么调都受限于推理速度。第二个建议RL 训练里推理服务的质量目标要单独定。训练和推理服务的 SLA 不一样。训练可以容忍较高的延迟但不能容忍低吞吐RL 推理服务既要延迟又要吞吐但不同任务侧重点不同。探索类任务可以放宽延迟利用类任务要保证延迟。第三个建议缓存不是免费的。前缀缓存会占用额外显存在 RL 冷启动阶段命中率低反而浪费显存。建议先关掉缓存跑一轮确认命中率确实成为瓶颈后再开。第四个建议异步队列引进来之后一定要做幂等和去重。推理服务超时重试可能导致重复生成结果最后污染训练样本。建议在结果里加 request_id训练侧用这个字段去重。第五个建议涉及数据和模型合规时推理服务如果是跨机器部署要确认数据传输链路符合内部安全规范。RL 训练涉及大量 prompt 和模型输出日志要做好脱敏尤其是涉及用户数据或隐私字段时。第六个建议监控先行。没有指标所有优化都是碰运气。先把 wait time、队列长度、缓存命中率这几个指标收集起来再决定是否扩展推理集群。9. 总结与下一步这个思路最值得尝试的点是把推理从训练循环里拿出来单独看、单独优化。它不是某个具体框架提供的功能而是一种架构选择。对于正在被 RL 训练效率问题困扰的团队先做一个诊断训练进程的 iteration 时间有多少是在等推理如果这个比例高那独立扩展推理集群就是性价比最高的下一步。最先应该验证的功能是生成接口的异步改造先用同步接口跑通再切异步队列最后再上自动扩缩容。最容易踩的坑是过早优化一上来就搞复杂队列和自动扩缩容结果监控没跟上出了问题反而定位不了。后续可以继续扩展的方向包括推理服务的优先级调度优化、KV Cache 命中率调优、多模型共享推理集群的资源隔离策略、以及 RL 训练框架和推理框架的标准化接口对接。先把推理独立出去再想扩展的事。