读 DataParallelController:放置决策怎样落成代码#

这章解决什么问题#

前面的运行时架构已经在 3.9 DataParallelController、rank 路由与负载分发 里解释了多 DP 部署下请求怎样被送到某个 rank,但代码导读里还缺一个更具体的问题答案:

  • 如果你真的打开 DataParallelController 相关源码,应该从哪里开始读,才能看清“显式 placement、负载均衡方法和回包分发”怎样真正落成代码?

这条路径的主锚点并不多,但很关键:

  • DataParallelController
  • LoadBalanceMethod
  • DPBudget
  • dispatching_with_trace(...)

这章的目标,就是把这棵控制器树收成一条稳定的源码阅读顺序。

为什么这棵树值得单独成章#

因为多 DP 场景下,系统已经不再是:

  • 请求直接进 scheduler

而是:

  • 请求先经过一层放置控制器,再被送到具体 worker / scheduler 路径

这使它在系统里承担了一个非常独特的位置:

  • 既不是纯入口壳
  • 也不是纯执行壳
  • 更像 placement control plane

从技术书角度看,这类文件特别值得单独讲,因为它解释的是:

  • “谁去跑这个请求”本身如何成为正式的运行时逻辑

一张图:DP 控制器真正负责的不是转发,而是放置决策#

这张图解决的理解障碍是:很多读者会把 DP controller 想成“多一个 router”,但它其实更像一个带预算和显式 override 的 placement 决策器。

flowchart LR
    Req["incoming request"] --> Hint["routed_dp_rank / routing hints"]
    Req --> Budget["DPBudget / load snapshot"]
    Hint --> Decide["dispatching_with_trace(...)"]
    Budget --> Decide
    Decide --> Rank["target DP rank"]
    Rank --> Worker["per-rank worker / scheduler path"]

图比纯文字多解释的一点是:DP controller 不是只做一层“把请求转发出去”,而是先决定它应该落到哪个 rank。

第一层:为什么应该先抓 LoadBalanceMethod#

比起直接读控制器主体,更稳的入口往往是:

  • LoadBalanceMethod

因为它先告诉你这棵树会长出哪几类人格:

  • round robin
  • total requests / total tokens
  • 以及可能的显式 rank override

这能让你在读 controller 主逻辑时不至于把所有分支看成杂乱的 if,而会知道:

  • 这些分支本质上是在实现不同的放置策略人格

第二层:DPBudget 为什么是 placement 语义的状态核心#

运行时架构章节已经讲过它的重要性,放回代码导读再看一次,会更清楚:

  • 这不是一个“统计对象”
  • 而是 controller 在做决策时的即时状态来源

也就是说,controller 并不是 stateless router。它在问的是:

  • 当前哪个 rank 更有余量
  • 当前该按 token 还是按 request 估计负载

这说明 placement 决策在 SGLang 里不是临时直觉,而是有状态的。

routed_dp_rank 为什么是代码阅读里的第一个特殊分支#

如果你先从 maybe_external_dp_rank_routing(...) 或相关逻辑开始读,会非常快地看清一件事:

  • controller 并不总是自由决定目标 rank

有时更外层的系统已经明确告诉它:

  • 这个请求必须去哪个 DP rank

这使 DataParallelController 的语义非常像一本系统书应该强调的那类“半自治控制器”:

  • 平时它按预算和策略自己分流
  • 必要时它也允许更外层系统强制 override

dispatching_with_trace(...) 为什么是整棵树最值得读的函数#

如果只挑一个函数,我会优先读它。因为它把:

  • 负载快照
  • rank 选择
  • trace / debug 信息

统一压到了同一个决策入口里。

从阅读策略上说,它非常适合作为 controller 的“主函数心智锚点”。你先看清它如何决定目标 rank,再回头看预算和 hint 的来源,就不容易迷路。

active ranks / status 列表为什么也应一并理解#

这部分能帮助你避免一个常见误解:

  • 不是所有 rank 都总是同等可选

一旦某些 rank 被标成不活跃、不可用或负载异常,controller 的实际可选空间就会变化。因此 placement 决策既取决于策略,也取决于“谁当前还算活着、可接请求”。

这也说明 controller 其实和 liveness / placement signal 章节天然有回扣。

这章和 /v1/loads / placement signal 为什么自然互补#

维护层章节讲的是:

  • 你怎样从 /v1/loads
  • routed_dp_rank
  • placement signal

去观察系统当前把请求放到了哪里。

而这章讲的是:

  • 这些信号在代码层是怎样被 controller 真正消费的

也就是说:

  • 维护层回答“你看到什么”
  • 代码导读回答“系统为什么会这样做”

两者配起来,placement 这条线就真正闭环了。

这棵树对排障有什么直接价值#

如果你遇到:

  • 请求总是偏向某几个 rank
  • routed_dp_rank 看起来没生效
  • /v1/loads 和真实行为对不上

那么最稳的源码入口通常就是:

  • controller 的放置决策函数
  • 预算状态来源
  • active rank / status 过滤逻辑

而不是直接去怪 scheduler 本身。

这能显著缩小问题空间:先确认 placement 决策对不对,再问“被放过去之后有没有跑好”。

这一层最容易出现的误判#

1. 以为 DataParallelController 只是 round robin router#

它其实是带预算和显式 override 的 placement 控制器。

2. 以为 routed_dp_rank 只是观测字段#

它会直接改写放置决策。

3. 以为 /v1/loads 只是旁路诊断接口#

它和 controller 的内部状态是同一条 placement 语义的外显结果。

如果你要顺着源码读这棵 placement 树,推荐顺序是什么#

建议按下面顺序:

  1. LoadBalanceMethod
  2. DPBudget
  3. maybe_external_dp_rank_routing(...)
  4. dispatching_with_trace(...)
  5. active rank / status 相关逻辑

这样读,你先知道有哪些策略,再看状态,再看决策入口,最不容易把 controller 读成一堆平铺 if。

小结#

这一章真正要补齐的,是代码导读里关于 placement 的正式入口:

  • DataParallelController 不只是多一层 router
  • 它是“谁去跑这个请求”这件事的运行时控制器

到这里,运行时架构、请求元数据传播、/v1/loads 和 placement signal 这几条线,就终于在源码层真正汇合了。