Feat: add named parameters - #194
Conversation
15630b4 to
8a20374
Compare
8a20374 to
6efc27e
Compare
6efc27e to
5a75ab8
Compare
5a75ab8 to
9dd31c7
Compare
| std::unordered_set<const Tensor *> visited; | ||
|
|
||
| std::function<void(const Module &, const std::string &)> collect | ||
| = [&](const Module &module, const std::string &module_prefix) { |
There was a problem hiding this comment.
parameters_ 和 modules_ 都是 unordered_map,不保序,同一个共享参数保存成哪个的 key 是不稳定的,现有 NamedModules() 是按名称排序 child 后遍历,这里能不能直接用NamedModules() 方法获取 modules_ 再保序遍历 parameters_ (parameters_ 数量太大的话排序不知道有没有性能问题)
There was a problem hiding this comment.
这里保证一下顺序,先调用NamedModules保证 module 顺序,再在遍历 parameters_后进行排序,保证整体参数顺序稳定
There was a problem hiding this comment.
在头文件里补充注释说明下:
InfiniTrain 的 NamedParameters 按 full parameter name 字典序返回,而不是 PyTorch registration order,且共享参数情况下保留名称字典序靠前的参数。
之后再看是否有必要与 PyTorch 语义完全对齐。
0b8f151 to
9d6aa81
Compare
| // NamedModules only reads the hierarchy and provides its stable, name-sorted traversal order. Keep all module | ||
| // aliases here so parameter-level deduplication deterministically selects the first full parameter name. | ||
| named_modules | ||
| = const_cast<Module *>(this)->NamedModules(/*memory=*/nullptr, prefix, /*remove_duplicate=*/false); |
There was a problem hiding this comment.
之前没注意,这里 NamedModules() 非 const,返回 shared_ptr,要调用的话引入了 const_cast、const_pointer_cast 和 shared_from_this(),感觉有点危险。而且如果后面param排序的话,module就不需要保序了?要不还是恢复局部递归 collect吧
There was a problem hiding this comment.
收集完成后按照完整参数名排序,再进行共享参数去重,保证保留的参数名稳定
| } | ||
|
|
||
| std::vector<std::pair<std::string, std::shared_ptr<Tensor>>> | ||
| Module::NamedParameters(const std::string &prefix, bool recurse, bool remove_duplicate) const { |
There was a problem hiding this comment.
Parameters 改成直接调用 NamedParameters 函数。参考:
https://github.com/pytorch/pytorch/blob/0d62256a2b23365f8e1604297eb23a6545102aa8/torch/nn/modules/module.py#L2665
| std::sort(named_parameters.begin(), named_parameters.end(), | ||
| [](const auto &lhs, const auto &rhs) { return lhs.first < rhs.first; }); | ||
|
|
||
| if (remove_duplicate) { |
There was a problem hiding this comment.
| std::unordered_set<const Tensor *> visited; | ||
|
|
||
| std::function<void(const Module &, const std::string &)> collect | ||
| = [&](const Module &module, const std::string &module_prefix) { |
There was a problem hiding this comment.
在头文件里补充注释说明下:
InfiniTrain 的 NamedParameters 按 full parameter name 字典序返回,而不是 PyTorch registration order,且共享参数情况下保留名称字典序靠前的参数。
之后再看是否有必要与 PyTorch 语义完全对齐。
| if (!recurse) { | ||
| return; | ||
| } | ||
| for (const auto &[name, child] : module.modules_) { |
There was a problem hiding this comment.
这里是不是应该调用 modules() 方法,里面对返回的 module 做了去重和保序操作。
| class SGD : public Optimizer { | ||
| public: | ||
| SGD(const std::vector<std::shared_ptr<Tensor>> ¶ms, float learning_rate); | ||
| SGD(const std::vector<std::shared_ptr<Tensor>> ¶ms, float learning_rate, |
There was a problem hiding this comment.
建议拆成两个接口,实现上用 delegating constructor:
SGD::SGD(const std::vector<std::shared_ptr<Tensor>> ¶ms,
float learning_rate)
: SGD(params, learning_rate, {}) {}
SGD::SGD(const NamedParameterList &named_parameters,
float learning_rate,)
: Optimizer(params, named_parameters),
learning_rate_(learning_rate) {
}
Adam、Parameter 基类和 DistributedOptimizer 同理。
而且这里同时传 params 和 named_parameters 有点冗余,torch 里 params/named_parameters 构造对应的也是同一个参数:
https://github.com/pytorch/pytorch/blob/c93b35450f3c49330e4b50bb6daf110dc3508366/torch/optim/optimizer.py#L348
https://github.com/pytorch/pytorch/blob/cf30153c4c131c8164ee7798e5022d810682e2cb/torch/optim/adam.py#L37
| size_t ddp_rank_; | ||
|
|
||
| // shard params | ||
| std::vector<std::shared_ptr<Tensor>> shard_params_; |
There was a problem hiding this comment.
当时加这个成员变量时好像就讨论过,麻烦 @Chamberlain0w0 确认下这样修改是否合适。
我看目前是将 shard_params 作为了 BuildShardParamsAndBindGrads 参数传入,就不需要 DistributedOptimizer 维护了,似乎也更合理,因为 base_optimizer_ 本身已经维护了分片参数,没必要在 DistributedOptimizer 里额外维护一份。
| shard_params_.clear(); | ||
| void DistributedOptimizer::BuildShardParamsAndBindGrads(const NamedParameterList &named_parameters, | ||
| std::vector<std::shared_ptr<Tensor>> &shard_params, | ||
| NamedParameterList &shard_named_parameters) { |
There was a problem hiding this comment.
- shard_named_parameters 本身已经包含了 shard_params,这里是否还有必要单独传 shard_params?
- 如果上面的 Optimizer 接口采用重载方式,建议这里也保持一致:分别支持普通 shard 参数和 named shard parameters 的初始化路径,并复用公共逻辑,避免为了 named parameters 支持引入重复的数据维护。
- 确认下这个函数是否需要用到 named_parameters 作为参数。
| namespace infini_train::nn::parallel { | ||
| DistributedOptimizer::DistributedOptimizer(OptimizerCreator creator, | ||
| const std::vector<std::shared_ptr<Tensor>> &full_params, | ||
| const NamedParameterList &named_parameters, |
There was a problem hiding this comment.
同上,建议同时保留仅接收 full_params 和仅接收 named_parameters 的构造函数,避免所有 DistributedOptimizer 场景都强制依赖 named_parameters。
| EXPECT_EQ(deduplicated[1].first, "model.0.weight"); | ||
| std::unordered_set<const Tensor *> tensors; | ||
| for (const auto &[name, parameter] : deduplicated) { | ||
| EXPECT_TRUE(name == "model.0.weight" || name == "model.0.bias" || name == "model.1.weight" |
There was a problem hiding this comment.
上面已经判断了
ASSERT_EQ(deduplicated.size(), 2);
EXPECT_EQ(deduplicated[0].first, "model.0.bias");
EXPECT_EQ(deduplicated[1].first, "model.0.weight");
这里不需要判断吧
| EXPECT_EQ(parameters[2].first, "1.1.weight"); | ||
| EXPECT_TRUE(by_name.contains("0.weight")); | ||
| EXPECT_TRUE(by_name.contains("1.0.weight")); | ||
| EXPECT_TRUE(by_name.contains("1.1.weight")); |
There was a problem hiding this comment.
这里为什么要同时检查 parameters 和 by_name,这俩是一样的吧


1. 主要修改