多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

NVIDIA Dynamo:让分布式AI推理像一台超大GPU一样调度

NVIDIA Dynamo:让分布式AI推理像一台超大GPU一样调度 我先从一个很现实的问题说起手头有 8 张 H100线上大模型的并发推理性能就是上不去GPU 利用率忽高忽低单卡显存动不动就爆。这已经不是“模型训练完就能上线”的时代了。推理侧正在变成真正的战场而 NVIDIA Dynamo 这个分布式 AI 推理中间层恰好就是冲着这个痛点来的。它的目标很直接让分布在大规模 GPU 集群上的推理服务像一台逻辑上的“超大号 GPU”那样被调度、被管理把分布式推理的效率往上拉一大截。这篇文章不打算做成官方文档的复读机而是从一个实际做推理系统的人的角度聊聊 Dynamo 到底解决了什么问题、核心机制有哪些、以及如果你想上手应该从哪些地方切入。1. 项目概述NVIDIA Dynamo 到底是个什么角色1.1 先理解“推理系统”和“训练系统”为什么不一样很多人对分布式系统的第一印象来自大模型训练比如 Megatron、DeepSpeed、FSDP 这些框架。训练的特点是任务确定、并行策略明确、可以花很长时间做流水线调度和梯度同步。但推理完全不同。用户的请求是实时到达的每个请求的输入输出长度不一样有的问一句“你好”有的直接丢一篇几万字的长文进来算力消耗相差几十倍。又因为大模型是自回归生成每个 token 的生成都依赖于前面的 token这种串行依赖让推理对延迟的要求非常苛刻。更重要的是显存。推理时的 KV Cache 会随着并发请求数量和序列长度动态增长同一个 batch 里不同请求的长度差异会造成严重的显存碎片和算力浪费。你如果只拿训练时的“数据并行 / 张量并行”思维去做推理很快就会遇到一个尴尬局面卡多了以后通信开销甚至盖过了算力收益。所以分布式 AI 推理不是一个“把模型切几块”的问题而是一个“如何动态管理海量请求、KV Cache 和异构算力”的调度问题。1.2 NVIDIA Dynamo 的定位推理时代的“分布式操作系统”NVIDIA Dynamo 不是又一个推理引擎它不负责具体的算子执行也不直接实现 Transformer 的前向计算。它的定位更像是一个分布式推理的编排与控制平面。类比一下TensorRT-LLM 或 vLLM 这类框架是“发动机”负责把模型跑起来Kubernetes 是“机房管理员”负责管容器和节点而 Dynamo 是“车队调度中枢”负责让每一辆发动机驱动的车在正确的时间跑到正确的位置并提前规划好所有路线的资源占用。在官方介绍里Dynamo 通常被描述为“用于 AI 推理的分布式中间层”它允许你描述整个推理服务的逻辑拓扑再由它映射到物理 GPU 集群上。它能够管理多节点下的预处理、上下文处理Context 阶段和生成阶段Generation 阶段也可以在 GPU 之间自动复制和放置模型同时还负责收集 KV Cache 等运行时遥测数据用于智能调度。我最早看到这个项目时的感觉是这其实是在把 Google 在大型系统里常用的一些思路比如分阶段调度、全局资源池、状态感知调度真正下沉到 GPU 推理这个具体场景里。对于线上业务来说这意味着可以从“手工编排脚本 拍脑袋定并发上限”走向更精细的资源管控。1.3 这个项目适合谁看、能帮你解决什么问题如果你手头只是部署一个很小的模型单张消费级显卡就能跑那 Dynamo 的复杂度可能超出了你的需求。但如果你符合下面任意一条这个项目就值得认真研究你负责的大模型服务需要部署在 4 张以上 GPU 上并且经常为“某几张卡算力打满、另外几张卡闲着”而头疼。你的在线服务需要同时支持多用户并发请求变化剧烈比如白天业务高峰、深夜低峰。你的模型规模太大单卡显存放不下需要做张量并行或流水线并行推理。你需要把 Prefill预填充即处理输入 prompt 的阶段和 Decode逐 token 生成的阶段拆到不同的 GPU 或不同的 Node 上以提升整体吞吐。你希望用更少的 GPU 支撑更大的并发而不是不停扩机器。一句话总结如果你已经在跟“分布式推理”死磕Dynamo 就是那个能让你少写很多调度代码、少踩很多显存坑的框架层工具。2. 整体设计与架构拆解Dynamo 是怎么把“一群 GPU”变成“一个 GPU”的2.1 分布式推理的三大并行策略DP、PP、EP要理解 Dynamo 的设计必须先理清并行策略。Dynamo 支持数据并行DP、流水线并行PP和专家并行EP而且允许你在同一个推理拓扑里混合使用这些策略。数据并行DP是最直观的同一份模型放在多张卡上每张卡处理不同的请求。这种方式扩容简单但每张卡都要有完整的模型权重显存开销大而且如果每个请求占用的 KV Cache 不均衡卡之间可能出现严重的“偏科”。流水线并行PP是把模型按层切成多段每张卡负责其中一段。请求像流水线一样依次经过各段。这种方式能放下单卡放不下的超大模型但因为请求是串行通过各阶段的容易产生气泡利用率不高。专家并行EP是 MoE混合专家模型的常用策略。把不同的专家Expert分布到不同 GPU 上每个 token 只激活其中一部分专家从而用更少的算力实现更大的模型容量。EP 的问题在于通信量巨大对互连带宽要求极高。Dynamo 的做法是不强迫你选一种而是把整个推理服务拆成多个可独立并行的逻辑单元。比如一个模型可以被切成两份流水线同时每一份又做 2 路张量并行整体还可以放两个副本来扛并发。这种多维度的组合能力是它区别于早期简单推理框架的关键点。2.2 核心架构Prefill 与 Decode 的物理分离你可能在不少文章里见过“PD 分离”这个词PD 就是 Prefill 和 Decode。传统方式里一个请求进入 GPU 后先做 Prefill把用户的 prompt 处理成 KV Cache然后进入 Decode 阶段逐 token 生成回答。两个阶段的计算特征完全不同Prefill 是计算密集尤其是长 prompt 场景需要大量矩阵乘法GPU 算力能跑得很满。Decode 是访存密集每一步只生成一个 token但要把整个 KV Cache 读一遍瓶颈在显存带宽。如果把两者混在同一批 GPU 上互相干扰非常大。Preffill 想把算力吃满Decode 又需要低延迟和带宽最终结果就是两个阶段都跑得不舒服。Dynamo 的思路是把推理拓扑显式地拆成Context 阶段Prefill和Generation 阶段Decode不同阶段使用不同的 GPU 集合。你可以在一个拓扑文件里定义用 4 张卡跑 Prefill、8 张卡跑 Decode中间通过高效的张量传输把 KV Cache 从 Prefill 卡搬到 Decode 卡。这样 Prefill 卡可以专心冲刺计算Decode 卡可以批量处理更多的生成请求整体吞吐自然就上来了。这种设计不是 Dynamo 凭空发明的行业内很多大厂已经在实践但 Dynamo 把它变成了一个相对通用的开源框架能力这大大降低了实现门槛。2.3 全局调度、KV Cache 管理与分布式内存池Dynamo 最出彩的部分我个人认为是对 KV Cache 的管理。KV Cache 是推理过程中“最贵”的动态资源它不像模型权重那样固定不变而是随着每个请求的生存周期被分配和释放。传统框架里KV Cache 被限制在某一块 GPU 上请求一旦被调度到某张卡它的 KV Cache 就必须一直在那张卡上待着这会直接影响负载均衡。Dynamo 引入了一种更灵活的模型把 KV Cache 看作一个可寻址的分布式内存池并通过KV Cache Manager来统一管理。调度器可以基于 KV Cache 的具体位置来决定请求去哪个 GPU也可以为了负载均衡而主动搬移或复制缓存。这与传统“局部性优先”的调度思路很不一样等于是在全局视角上重新分配内存资源。这里有两点非常重要。第一KV Cache Manager 需要和推理引擎强绑定Dynamo 提供了对应的接口TensorRT-LLM 等引擎可以暴露 KV Cache 的实时索引第二调度决策必须足够快因为请求的到达是毫秒级的如果调度器本身需要几百毫秒才能算出结果那一切都白搭。Dynamo 的做法是让调度尽量轻量化并利用异步通信来完成 KV Cache 状态同步。2.4 为什么选择“可编程的拓扑描述”而不是“写死一套规则”Dynamo 有一个比较特别的设计它让你用 Python 类来描述一个推理服务的拓扑比如继承LLM或LLMNode基类定义 Prefill 和 Decode 的关系、权重如何加载、KV Cache 如何传输。这相当于把“分布式架构”本身变成了可以版本管理、动态修改的代码。这么做的好处很明显不同模型的推理模式差异巨大。一个 7B 的稠密模型和一个 405B 的 MoE 模型它们的显存占用、通信热点、最适并行方式完全不同。如果框架把架构写死要么是某种模型表现好其他模型很别扭要么就是框架复杂度爆炸。可编程拓扑让用户自己掌握架构决策权Dynamo 负责把描述转换为真实执行。我在实际做系统时深有体会很多框架功能强大但你很难“告诉它”你的特别需求。Dynamo 这种让用户参与编排的方式对于有专门推理优化团队的公司来说是真正可落地的而不是只能照着默认模板跑。3. 核心细节解析与实操要点我该从哪里下手3.1 看懂的标志能说清 LLM、LLMNode、KV Cache Manager、Router 各干什么如果你去翻 Dynamo 的代码仓库或文档会看到几个核心抽象。先别急着写代码把这几个概念搞明白后面就顺了。LLM最上层的推理服务抽象。你配置它表示“我要部署一个完整的模型服务”。它内部会管理副本、并行方式、KV Cache 分配等。LLMNode核心的可编排节点抽象。一个 LLM 可以拆成多个 LLMNode典型场景就是PrefillNode和DecodeNode。每个节点可以独立设置并行策略和 GPU 数量。KV Cache Manager负责 KV Cache 的分配、跟踪、传输和释放。Dynamo 通过它与推理引擎交互获取 KV Cache 的索引并根据调度决策传递缓存数据。Router / Scheduler接收外部请求根据后端实例的负载、KV Cache 位置等动态将请求分发到合适的 GPU 或节点。这里要注意具体代码 API 可能会随版本更新而变但核心概念是稳定的。你先理解“节点 全局资源管理 路由”这个三角关系再去看代码完全不会迷路。3.2 KV Cache 的分布式传输核心难点与优化思路PD 分离后一个请求的 KV Cache 需要从 Prefill 节点搬到 Decode 节点。这听起来简单真做起来却很难因为 KV Cache 动辄几百 MB 甚至上 GB。如果传输效率低网络会变成瓶颈甚至比不分离开还慢。Dynamo 的解决方案我在分析源码时看到几个关键点。一是尽量复用显存到显存的直接传输避免“显存 - 内存 - 网卡 - 远端内存 - 远端显存”这种绕路二是采用分块或流水线传输让计算和通信重叠而不是等整个 KV Cache 全部传完再开始生成三是尽可能让请求路由到“KV Cache 已经在附近”的节点以此减少跨机传输。这一点也引出一个实操经验不要把 KV Cache 传输参数当作默认值不管。如果你们的网络是 100Gbps 和 400Gbps表现会差很多。你至少要为集群的带宽特性做一次针对性压测找出当前 PD 分离配置下 KV Cache 传输是否成了瓶颈。3.3 推理引擎的适配TensorRT-LLM、vLLM 如何接进来Dynamo 不是一个独立的“全栈推理方案”它需要配合实际的推理引擎。目前最典型的组合是 Dynamo TensorRT-LLM因为两者都是 NVIDIA 生态接口配合最顺。但这不代表 vLLM 或其他框架不能用只是适配工作量取决于引擎是否暴露了 Dynamo 所需要的控制点。要理解适配需要什么可以从推理引擎的角度想引擎需要允许外部模块创建和管理 KV Cache而不是自己内部封闭管理。引擎需要能接收来自 Dynamo 的调度指令并把结果返回给调度器。引擎需要能和 Dynamo 共享遥测数据比如每块 GPU 的显存余量、当前并发数、KV Cache 命中情况。我在实际使用中建议不要一上来就试图在自定义引擎上对接 Dynamo。先用官方示例跑通理解接口语义再做深度集成。通常官方会提供一个比较完整的参考实现你对照着改是最靠谱的路子。3.4 从 API 到实际部署需要留意的基础环境在动手部署之前有几个基础环境要心里有数NVIDIA Driver 和 CUDA保持较新版本Dynamo 依赖较新的 GPU 驱动能力尤其是显存直接访问和多租户管理相关特性。NVIDIA 容器工具通常建议在容器里跑官方镜像已经预装了大量依赖省去自己配环境的痛苦。网络互连如果做多节点部署建议使用 RDMA 或 InfiniBand 网络。RoCE 也能跑但延迟和带宽稳定性会略差。GPU 型号与显存不同 GPU 的显存带宽差异直接决定 Decode 节点的吞吐上限选型时别只盯算力。这里没有特别复杂的步骤但很多人会因为环境版本不一致而翻车。我的建议是直接用官方提供的 Docker 镜像开启一版确认跑通再迁移到自己的镜像。这样做排查问题时至少能分清是环境问题还是代码问题。4. 实操过程与核心环节实现从零搭一个 PD 分离的推理服务4.1 第一步定义推理拓扑把 Prefill 和 Decode 拆开我会用一个非常简化的伪代码来说明如何定义拓扑。假设你只有两个逻辑阶段一个处理 Prefill一个处理 Decode。在 Dynamo 的框架体系里大致会长这样class MyPrefillNode(LLMNode): def exec(self, inputs, kv_cache_manager): # 使用 TensorRT-LLM 等引擎执行 prefill # 将生成的 KV Cache 注册到 kv_cache_manager ... class MyDecodeNode(LLMNode): def exec(self, inputs, kv_cache_manager): # 根据 kv_cache 索引读取缓存 # 执行自回归生成 ... llm LLM() prefill_node MyPrefillNode( modelllama3-70b, enginetensorrt_llm, placementnode1, # 例如放到 node1 的 4 张 GPU parallel_strategy{tp: 4} ) decode_node MyDecodeNode( modelllama3-70b, enginetensorrt_llm, placementnode2, # 例如放到 node2 的 8 张 GPU parallel_strategy{tp: 8} ) llm.add_node(prefill_node) llm.add_node(decode_node) llm.start()这是为了让你理解思路真实 API 比这复杂但基本逻辑如此。你把模型切分和放置逻辑写清楚Dynamo 会根据这些信息去拉起进程、分配 GPU、建立连接。这里的一个关键决策点是tp 和 pp 的选择。如果模型权重大于单卡显存你至少需要 tp2 或 tp4但如果 tp 太大AllReduce 通信会成为瓶颈尤其是在单机内部 NVLink 可用时 tp8 是常见选择跨机则要慎重。如果你拿不准可以先从单机 tp 全部打满开始再逐步增加数据并行副本。4.2 第二步把 KV Cache 的管理权限交给 Dynamo这个环节特别容易踩坑。默认情况下TensorRT-LLM 或 vLLM 都有自己的 KV Cache 管理方式它们会自己规划显存池。但 Dynamo 需要获取 KV Cache 的状态来做全局调度所以你必须显式地启用“外部 KV Cache 管理”模式。以 TensorRT-LLM 为例大致要经历这么几步在构建引擎时开启 KV Cache 相关配置并确保运行时以可外部访问的方式暴露 KV Cache 索引。在 Dynamo 端配置 KV Cache Manager指定缓存存储的位置和传输方式。查询或预先分配 KV Cache 的显存池大小避免因为缓存块不足导致请求排队。我见过最多的问题就是KV Cache 管理器显示的容量和实际引擎可用容量不一致结果部分请求直接被拒绝。原因通常是对齐参数没配置好或者显存池预留小了。稳妥做法是先设置一个偏大的缓存池跑一段时间性能测试后再根据显存余量调小。4.3 第三步启动服务看监控指标而不是只看日志服务启动以后很多人习惯只看日志里有没有报错。但在分布式推理场景下日志只能告诉你“进程活着”无法告诉你“系统是否健康”。你需要看几类关键指标GPU 利用率区分 Prefill 和 Decode 节点。如果 Prefill 节点利用率很低说明请求压力不足如果长期打满但请求还是排队说明 Prefill 的并行度不够。KV Cache 利用率缓存池是否经常不够用、是否有大量淘汰发生。如果频繁淘汰会极大增加 Prefill 重复计算。跨节点传输量如果 PD 分离后网络传输量非常大优先看是否路由到了错误的节点。首 token 延迟和生成延迟这两个指标比总体延迟更值得盯。首 token 延迟高问题大多在 Prefill生成延迟高往往在 Decode 或显存带宽瓶颈。Dynamo 通常会暴露一些度量接口你也完全可以把这些数据接进 Prometheus 或 Grafana。但作为最初的评估nvidia-smi 配合自定义日志就够了先把数据拿到手再谈优化。4.4 第四步压测与容量规划确定“要不要调大 Decode 节点”部署完成后一定要做系统性的压测而不是只发两个请求试试。我的建议是准备一组比较有代表性的请求数据短问题、长问题、短答案、长答案混合并发压测。记录四个数QPS、首 token 延迟、生成吞吐、平均生成速度。如果你发现 Prefill 节点 GPU 利用率高Decode 节点显存带宽基本跑满那说明资源分配合理。如果 Decode 节点大量空闲而 Prefill 节点已经排队多半是 Prefill 节点算力不够需要增加 Prefill 节点数量。反过来如果 Decode 节点排队严重而 Prefill 节点很闲就该加 Decode 节点。这种容量规划思路比“无脑加卡”要科学得多。Dynamo 的价值也正在这里拓扑可以用代码描述扩容时只需要调整节点数量重新部署而不需要重写推理逻辑。5. 常见问题与排查技巧实录我在踩坑中总结的经验5.1 问题一PD 分离之后整体延迟反而更高了这是最容易让人打退堂鼓的问题。很多人拆了 Prefill 和 Decode 后发现首 token 延迟或总延迟不降反升。第一反应是“分离方案不行”但绝大多数情况下其实是网络传输瓶颈。排查思路很简单看 PD 之间 KV Cache 传输时间占比。如果是传输导致的延迟增加办法有几个优先把 Prefill 和 Decode 节点放在同一台物理机内利用 NVLink 传输而不是走以太网。开启更高效的传输模式比如显存到显存的同步传输。让 Router 将请求优先调度到 “KV Cache 已经存在” 的节点减少传输概率。另外PD 分离适合并发压力大的场景。如果并发很低比如每秒只有几个请求PD 分离带来的收益远远盖不过传输损耗。别为了追新技术而强行拆分。5.2 问题二GPU 之间的负载严重不均分布式推理系统最常见的问题是某一张卡跑满其他卡在摸鱼。我排查时通常按顺序看是不是路由策略导致的。请求被固定打到某些节点而另外节点因为 KV Cache 位置原因长期空闲。是不是并行策略不合理。比如 tp8 时如果通信期间阻塞了计算卡间利用率必然不均。是不是 KV Cache 碎片化导致部分请求无法分配只能挤在少数节点上。解决时一是调整 Router 的负载均衡策略二是重新规划 TP/PP 配置三是缩短 KV Cache 的“黏性”允许更多请求被路由到缓存未命中的节点用一定的重复计算换取全局均衡。5.3 问题三并发一上来显存直接爆掉显存爆掉通常是 KV Cache 池和模型权重之间的比例设置有问题。你要意识到显存是有限的模型权重占一部分激活值占一部分KV Cache 占一部分。推理时哪部分膨胀都会导致 OOM。建议给 KV Cache 池设置合理上限同时确保推理引擎有显存保护机制。另外不要忽略“请求长度”这个变量。如果业务中经常出现超长输入会导致单个请求占用的 KV Cache 异常巨大。你可以在网关层做请求长度限制也可以在 Dynamo 层设置最大 KV Cache 分配量。5.4 问题四新版本接口变了老代码跑不起来Dynamo 还处于快速迭代期API 变动是比较常见的。我的经验是锁定版本不要盲目升级。生产环境用固定的镜像 tag。阅读官方示例时注意版本分支不要直接拿 main 分支的代码套到稳定版上。升级前先本地重新跑官方示例确认没有基础 API 变化再动自己的代码。听起来是老生常谈但我在实际项目中见过太多次因为 API 变动导致整个拓扑全部重写的案例。6. 一些真实体会和进阶扩展建议这套架构接近成型后我最大的体会是分布式推理优化的天花板往往不在“模型算子”里而在“资源调度”和“数据流”上。Dynamo 能火起来是因为它把大家过去手工做的那些破事比如自己写 KV Cache 管理、自己写 PD 分离、自己写请求路由通通抽象成了框架能力。你不需要跟每一行通信代码较劲而是可以把精力放在业务量预估和拓扑调优上。最后再分享一个小技巧如果你们的场景是典型的“大并发、长短请求混合”可以先不做太复杂的 EP 和 PP而是从 DP PD 分离开始。先把最直观的负载不均问题解决掉再根据压测数据考虑引入更精细的并行策略。分布式推理的优化是一步一步“喂”出来的不存在一个万能配置能适配所有模型和所有业务。把 Dynamo 提供的控制能力用起来然后建立“假设 - 压测 - 调参 - 再压测”的循环这才是最稳的路。
返回列表