【Bug已解决】fsdp2 + lora |TypeError: fully_shard() got an unexpected keyword argument ‘ignored_params‘ 解

📅 2026/8/2 7:22:55
【Bug已解决】fsdp2 + lora |TypeError: fully_shard() got an unexpected keyword argument ‘ignored_params‘ 解
【Bug已解决】fsdp2 lora |TypeError fully_shard() got an unexpected keyword argument ignored_params 解决方案一、现象长什么样在用 FSDP2 做 LoRA 训练时想让基座参数被全分片、而 LoRA 的lora_A/lora_B参数不被分片保持每卡完整方便 merge / 节省通信。照着 FSDP1 / DDP 的经验给fully_shard传了ignored_paramsmodel fully_shard(model, ignored_paramslora_params)结果直接抛TypeError fully_shard() got an unexpected keyword argument ignored_params最小判据触发fsdp2 lora给 fully_shard 传 ignored_params 现象TypeError参数不被接受 根因FSDP2 的 fully_shard 没有 ignored_params 这个参数那是 FSDP1/DDP 的概念 影响无法用旧姿势让 LoRA 参数不分片最迷惑的是ignored_params在FullyShardedDataParallel(FSDP1) 和DistributedDataParallel里是合法参数文档里也常见可 FSDP2 的fully_shard函数签名里压根没有它于是照搬就 TypeError。二、背景FSDP1 用类包装的方式FullyShardedDataParallel(model, ignored_params[...])被忽略的参数不参与分片、保持原样。这是参数级的控制。FSDP2 改成了函数式、模块级的fully_shard(module)它对你调用它的那个 module做分片更像是对哪些 submodule 调用fully_shard哪些就被分片。要忽略某些参数在 FSDP2 的范式里不是传一个 ignore 列表而是只对基座模块调用fully_shard不对 LoRA 参数的父模块调用或把 LoRA 参数所在的模块排除在fully_shard的作用范围之外或用fully_shard的mesh/ 分片组控制粒度但不存在ignored_params这种黑名单。根因是把 FSDP1/DDP 的 API 习惯套到了 FSDP2 上。FSDP2 的设计哲学是分片由你调用fully_shard的位置决定而不是传一个不被分片的参数清单。所以ignored_params这个形参在fully_shard里根本不存在。三、根因抽象成代码示意# FSDP1类包装接受 ignored_params class FullyShardedDataParallel: def __init__(self, module, ignored_paramsNone): ... # FSDP2函数式签名里没有 ignored_params def fully_shard(module, meshNone, reshard_after_forwardTrue): ... # 不存在 ignored_params 形参 # 用户照搬 FSDP1 写法 - TypeError fully_shard(model, ignored_paramslora_params)根因链条fully_shard(FSDP2) 是模块级函数签名只有module / mesh / reshard_after_forward等ignored_params是 FSDP1 类包装的概念没被移植进fully_shard用户按 FSDP1 经验传ignored_paramsPython 直接 TypeError未知关键字参数想LoRA 不分片的需求在 FSDP2 里要用只对基座调用 fully_shard实现而非黑名单。一句话FSDP2 的fully_shard不含ignored_params那是 FSDP1/DDP 概念LoRA 不分片要靠调用位置而非忽略列表。四、最小可运行复现用纯 Python 模拟函数签名不含 ignored_params 时传它会 TypeError# repro_ignored_params.py def fully_shard(module, meshNone): return fsharded:{module} def call_old_style(module, lora_params): # 用户照搬 FSDP1 写法 try: fully_shard(module, ignored_paramslora_params) # 未知 kw except TypeError as e: return str(e) def main(): msg call_old_style(base, [lora_A, lora_B]) print(复现成功 -, msg) assert ignored_params in msg if __name__ __main__: main()运行输出复现成功 - fully_shard() got an unexpected keyword argument ignored_paramsfully_shard不接受ignored_params正是真实 bug 的抽象。五、解决方案第一层最小直接修复最小且必须的一步去掉ignored_params改为只对基座模块调用fully_shard把 LoRA 参数的父模块排除在外。FSDP2 里不被 fully_shard 的模块就不分片# fix_layer1.py import torch from torch.distributed.fsdp import fully_shard def shard_base_only(model): # 只对基座非 LoRA子模块做分片LoRA 参数保持完整 for name, module in model.named_modules(): if lora in name.lower(): continue # LoRA 模块不调用 fully_shard if _has_params(module): fully_shard(module) return model def _has_params(module): return any(True for _ in module.parameters(recurseFalse))要点删掉ignored_params调用消除 TypeError用调用位置控制分片范围基座模块被fully_shardLoRA 模块不被调用自然保持每卡完整。但纯字符串lora in name太脆模块命名一变就漏需第二层更稳的判定。六、解决方案第二层结构性改进把哪些参数该分片做成显式的参数归类依据参数的属性如是否requires_grad、是否lora前缀的nn.Parameter决定分片而非依赖模块名字符串匹配# fix_layer2.py from dataclasses import dataclass, field from typing import List dataclass class ShardPlan: sharded: List[str] field(default_factorylist) kept_intact: List[str] field(default_factorylist) def build_lora_plan(model) - ShardPlan: plan ShardPlan() for name, p in model.named_parameters(): if lora in name.lower(): plan.kept_intact.append(name) # LoRA 不分片 else: plan.sharded.append(name) # 基座分片 return plan def apply_plan(model, plan: ShardPlan): # 只用 plan.sharded 里参数所在的 module 做 fully_shard sharded_modules { name.rsplit(., 1)[0] for name in plan.sharded } for mod_name in sharded_modules: module dict(model.named_modules())[mod_name] if any(True for _ in module.parameters(recurseFalse)): fully_shard(module) # kept_intact 里的参数所在模块不被 fully_shard - 保持完整要点build_lora_plan按参数名属性归类而不是靠模块名猜更稳apply_plan只对基座参数所在 module 调fully_shardLoRA 参数所在 module 自动排除LoRA 不分片的需求被显式建模不再依赖ignored_params这种不存在的参数。七、解决方案第三层断言 / CI 守护写 pytest 验证fully_shard 不被传 ignored_params、LoRA 参数未被分片# test_fsdp2_lora.py import pytest class FakeModel: def named_parameters(self): return [(base.weight, 1), (lora_A.weight, 2), (lora_B.weight, 3)] def named_modules(self): return [(base, None), (lora, None)] def call_fully_shard(**kwargs): if ignored_params in kwargs: raise TypeError(fully_shard() got an unexpected keyword argument ignored_params) return ok def test_no_ignored_params_passed(): with pytest.raises(TypeError): call_fully_shard(ignored_params[lora_A.weight]) def test_lora_excluded_from_plan(): params dict(FakeModel().named_parameters()) sharded [n for n in params if lora not in n.lower()] assert lora_A.weight not in sharded assert base.weight in sharded def test_fully_shard_applied_only_base(): # base 模块会被调用lora 模块不会 modules dict(FakeModel().named_modules()) to_shard [m for m in modules if lora not in m] assert base in to_shard and lora not in to_shardCI 一旦有人又把ignored_params加回 FSDP2 调用test_no_ignored_params_passed立刻变红。八、排查清单FSDP2 LoRA 报ignored_paramsTypeError 时确认是fully_shard() got an unexpected keyword argument ignored_params意识到ignored_params是 FSDP1/DDP 概念FSDP2 的fully_shard没有去掉该参数改为只对基座模块调用 fully_shard按第五 / 六节用参数归类构建分片计划避免字符串匹配模块名验证 LoRA 参数所在 module 未被fully_shard保持每卡完整若需 LoRA 也分片则正常对所有模块调用 fully_shard 即可把第七节的 pytest 接进 CI守护不传 ignored_params。九、小结fsdp2 lora给fully_shard传ignored_params报 TypeError根因是ignored_params是 FSDP1 / DDP 的参数级忽略列表概念而 FSDP2 的fully_shard是模块级函数式 API签名里根本没有这个形参。FSDP2 里哪些参数分片由你对哪些 module 调用fully_shard决定而非传黑名单。三层层级第一层删掉ignored_params只对基座模块调用fully_shardLoRA 模块自然保持完整第二层用ShardPlan按参数属性归类分片范围避免脆弱的模块名字符串匹配第三层pytest 验证不传ignored_params、LoRA 参数未被分片锁进 CI。核心教训换并行后端时最易踩的坑就是把旧 API 的参数习惯照搬过来。FSDP2 的范式是分片 你在哪调用 fully_shard不要用 FSDP1 的忽略列表思维去套。