读 model_loader:权重如何进入执行壳#

这章解决什么问题#

前面的运行时架构已经讲了:

  • 模型定义和执行壳的边界
  • 远端 transfer 协调
  • 热更新控制面

但如果你真正想在源码里回答“模型权重到底是怎样被装进执行壳”的问题,仍然还缺一个正式入口:

  • model_runner.py::load_model()
  • model_loader/loader.py

这条链非常关键,因为很多运行时问题其实都发生在这里:

  • load format 选择不对
  • 远端实例权重加载没走通
  • 默认 loader 和量化 loader 的行为差异
  • 权重热更新为什么会回到 DefaultModelLoader

这章的目标,就是把“权重如何进入执行壳”收成一条稳定的源码阅读路径。

为什么这条链值得单独成章#

因为如果只看 ModelRunner,你会知道它会 load_model();如果只看控制面,你会知道它能 update_weights_from_*。但两者中间真正承接“怎么加载、从哪加载、用什么 loader 加载”的系统,却在:

  • model_loader/loader.py

里。

这意味着:

  • 权重加载既不是外围工具
  • 也不是 ModelRunner 自己的局部细节

它是执行壳能否真正成立的地基之一。

一张图:权重不是直接读盘给模型,而是经过一条 loader 链#

这张图解决的理解障碍是:很多读者会默认 ModelRunner.load_model() 里就是几行读权重代码,但实际系统先编译 LoadConfig,再选 loader,再进具体加载路径。

flowchart LR
    Args["ServerArgs / LoadFormat"] --> Config["LoadConfig"]
    Config --> Select["get_model_loader(...)"]
    Select --> Loader["Default / Remote / Quantized / Layered ..."]
    Loader --> Model["loader.load_model(...)"]
    Model --> Runner["ModelRunner execution shell"]

图比纯文字多解释的一点是:权重加载本身就是一条被显式建模的可插拔链路,而不是 ModelRunner 内部不可见的一步。

第一层:为什么应该先读 ModelRunner.load_model()#

因为它是这条链的总入口,而且非常清楚地告诉你:

  • 它先收集当前硬件与执行人格
  • 再构造 LoadConfig
  • get_model_loader(...)
  • 再真正 loader.load_model(...)

也就是说,这里不是“load weights now”,而是:

  • 先决定用什么方式 load weights

从系统角度看,这非常关键,因为执行壳的很多人格在这里已经进一步收缩:

  • load_format
  • remote instance loader backend
  • modelopt 相关配置
  • transfer engine 相关配置

LoadConfig 为什么是这条链的真正参数总谱#

LoadConfig 把很多本来会散落在 ServerArgs 里的加载语义重新收口成一份专门的权重加载配置,例如:

  • load_format
  • download_dir
  • model_loader_extra_config
  • remote instance loader 的 seed IP / port / backend
  • transfer engine
  • ModelOpt / RL 相关配置

这说明:

  • 执行时的人格
  • 权重加载时的人格

虽然相关,但并不是一模一样的配置问题。

优秀技术书在这里应该主动点出来:同一个系统有时会为“如何执行”和“如何加载”各自维护一层专用配置,这不是重复,而是职责分层。

get_model_loader(...) 真正决定了什么#

这段函数特别值得读,因为它把 loader 的多样性显式暴露出来。当前至少会根据 LoadFormat 选择:

  • DefaultModelLoader
  • LayeredModelLoader
  • RemoteInstanceModelLoader
  • 各类量化 / 远端 / 私有 loader

这说明“权重从哪里来”在系统里从来不是单一现实。不同格式或不同部署模式,对应的是不同 loader 家族。

从阅读策略上说,这意味着:

  • 如果你只关心默认本地权重加载,先读 DefaultModelLoader
  • 如果你关心远端或特殊量化人格,再追对应 loader 分支

DefaultModelLoader 为什么仍然是最值得先读的入口#

因为即使系统支持很多 loader,大多数路径最终还是会回到它或至少遵循它的基本结构:

  • 下载或定位权重文件
  • 迭代权重
  • postprocess
  • 必要时处理量化与 secondary weights

而且 ModelRunner.update_weights_from_disk(...) 里也明确写了:

  • 当前只支持 DefaultModelLoader

这说明默认 loader 不是“最普通的一种”,而仍然是权重语义的主要参照系。

远端实例权重加载为什么不是普通下载#

RemoteInstanceWeightLoaderBackend 和相关 utils 说明:

  • 某些场景里,权重来源不是磁盘或 HF 仓库
  • 而是远端 seed instance

这时又会进一步分出:

  • NCCL 协调组
  • transfer engine
  • 远端服务地址和组名

这说明远端权重加载不是“从网络拉一个文件”,而是一条真正的分布式协同加载路径。

也正因此,前面运行时架构里讲的 bootstrap server 和远端 transfer info,在这里会重新落回具体 loader 行为。

为什么 load_model()update_weights_from_disk() 必须一起看#

这是这条链最有书感的一处回扣。

初次加载时:

  • ModelRunner.load_model() 走完整 loader 选择链

运行中磁盘热更新时:

  • update_weights_from_disk() 又会重新构造 LoadConfig
  • 再次 get_model_loader(...)
  • 但当前只支持 DefaultModelLoader

这说明:

  • 热更新并不是完全另一套体系
  • 它会重新借用同一个 loader 家族

从系统角度看,这是非常健康的设计,因为它避免了“首次加载”和“热更新”各走完全不同的权重语义。

load_model() 为什么和远端 transfer engine 也要配对看#

在远端 loader 场景下,ModelRunner 还会:

  • 在需要时初始化 transfer engine
  • register memory region
  • 注册 bootstrap 信息

也就是说,权重加载不再只是“把模型读进来”,还包括“把当前实例的权重传输能力公布出去”。

这说明在某些部署人格里:

  • 权重加载
  • 远端协调
  • 执行壳装配

已经不是三件分开的事,而是同一条初始化链。

这条链对排障有什么直接价值#

如果你遇到:

  • 模型能启动但权重版本不对
  • 远端 seed instance 没把权重送过来
  • 热更新失败却不清楚是控制面还是 loader 失败

那么这时最稳的第一落点往往不是 scheduler,而是:

  • LoadConfig
  • get_model_loader(...)
  • 对应 loader 实现

因为很多时候,执行壳之所以行为不对,根源并不是执行本身,而是进入执行壳的权重语义就已经偏了。

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

1. 以为 ModelRunner.load_model() 只是读盘#

它首先在选择 loader 家族与加载人格。

2. 以为热更新和首次加载是完全不同的体系#

它们共享 loader 链,只是入口和约束不同。

3. 以为远端实例权重加载只是网络版默认加载#

实际上它引入了额外的协同与 transfer 语义。

如果你要顺着源码读这条权重链,推荐顺序是什么#

建议按下面顺序:

  1. ModelRunner.load_model()
  2. LoadConfig
  3. get_model_loader(...)
  4. DefaultModelLoader
  5. 再根据实际问题进入 remote / quantized / layered 等特化 loader

这样读,你先理解“怎么选 loader”,再理解“loader 具体怎么干活”,最不容易在 loader.py 的多分支里迷路。

小结#

这一章真正要补齐的,是代码导读里“权重如何进入执行壳”这条此前仍然偏隐性的主线:

  • ModelRunner 不是直接读权重
  • 它先通过 LoadConfigget_model_loader(...) 选择一条加载人格
  • 然后这条 loader 链再真正把权重送进执行壳

到这里,运行时架构、执行模型和热更新控制面之间又多了一条更稳的源码闭环。