读 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_formatdownload_dirmodel_loader_extra_config- remote instance loader 的 seed IP / port / backend
- transfer engine
- ModelOpt / RL 相关配置
这说明:
- 执行时的人格
- 权重加载时的人格
虽然相关,但并不是一模一样的配置问题。
优秀技术书在这里应该主动点出来:同一个系统有时会为“如何执行”和“如何加载”各自维护一层专用配置,这不是重复,而是职责分层。
get_model_loader(...) 真正决定了什么#
这段函数特别值得读,因为它把 loader 的多样性显式暴露出来。当前至少会根据 LoadFormat 选择:
DefaultModelLoaderLayeredModelLoaderRemoteInstanceModelLoader- 各类量化 / 远端 / 私有 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,而是:
LoadConfigget_model_loader(...)- 对应 loader 实现
因为很多时候,执行壳之所以行为不对,根源并不是执行本身,而是进入执行壳的权重语义就已经偏了。
这一层最容易出现的误判#
1. 以为 ModelRunner.load_model() 只是读盘#
它首先在选择 loader 家族与加载人格。
2. 以为热更新和首次加载是完全不同的体系#
它们共享 loader 链,只是入口和约束不同。
3. 以为远端实例权重加载只是网络版默认加载#
实际上它引入了额外的协同与 transfer 语义。
如果你要顺着源码读这条权重链,推荐顺序是什么#
建议按下面顺序:
ModelRunner.load_model()LoadConfigget_model_loader(...)DefaultModelLoader- 再根据实际问题进入 remote / quantized / layered 等特化 loader
这样读,你先理解“怎么选 loader”,再理解“loader 具体怎么干活”,最不容易在 loader.py 的多分支里迷路。
小结#
这一章真正要补齐的,是代码导读里“权重如何进入执行壳”这条此前仍然偏隐性的主线:
ModelRunner不是直接读权重- 它先通过
LoadConfig和get_model_loader(...)选择一条加载人格 - 然后这条 loader 链再真正把权重送进执行壳
到这里,运行时架构、执行模型和热更新控制面之间又多了一条更稳的源码闭环。
叶王 © 2013-2026 版权所有。如果本文档对你有所帮助,可以请作者喝饮料。