4.2 KiB
4.2 KiB
Lesson 19 笔记:named_children vs named_modules 详解
目录
核心区别
| 方法 | 遍历范围 | 适合场景 |
|---|---|---|
named_modules() |
递归所有后代模块 | 读、统计、原地修改 |
named_children() |
只遍历直接子模块 | 替换子模块 |
named_modules() 的特点
named_modules() 会递归遍历整棵模块树,返回所有后代模块:
for name, module in model.named_modules():
print(name, module)
输出类似:
"" -> 整个 model 自己
"layers" -> ModuleList
"layers.0" -> 第 0 个 block
"layers.0.self_attn.q_proj" -> q_proj Linear
"layers.0.self_attn.k_proj" -> k_proj Linear
...
特点:
- 给你的是完整路径和模块对象本身
- 没有直接给你父模块
- 适合"查看、统计、收集参数"
named_children() 的特点
named_children() 只遍历直接子模块:
for name, child in model.named_children():
print(name, child)
输出类似:
"embed_tokens" -> Embedding
"layers" -> ModuleList
"norm" -> RMSNorm
特点:
- 每一层递归时,你都站在父模块上
- 可以直接
setattr(model, name, new_child)替换子模块 - 需要自己递归进入子模块
原项目为什么用 named_modules()
原项目 apply_lora 的代码:
def apply_lora(model, rank=16):
for name, module in model.named_modules():
if isinstance(module, nn.Linear) and module.in_features == module.out_features:
lora = LoRA(module.in_features, module.out_features, rank=rank).to(model.device)
setattr(module, "lora", lora)
original_forward = module.forward
def forward_with_lora(x, layer1=original_forward, layer2=lora):
return layer1(x) + layer2(x)
module.forward = forward_with_lora
关键:这里没有替换模块,而是修改现有模块对象本身。
原始:q_proj: nn.Linear
改后:q_proj: nn.Linear (被 monkey-patch 了 forward)
└── 多了 .lora 属性
module.forward = new_forward直接改了 module 自己的方法setattr(module, "lora", lora)直接在 module 上挂属性- 不需要父模块参与,只需要拿到 module 对象
所以 named_modules() 完全够用。
如果要做模块替换用什么
如果想替换一个模块,而不是修改它:
# 这样写是无效的!
for name, module in model.named_modules():
if isinstance(module, nn.Linear):
module = LoRALinear(module) # 只改了局部变量,模型树没变
模型里的 q_proj 还是原来的 Linear。
必须改父模块:
# 解析路径
parent_path, child_name = name.rsplit(".", 1)
parent = model.get_submodule(parent_path)
setattr(parent, child_name, LoRALinear(module))
这比 named_children() 递归麻烦多了。
用 named_children() 递归更自然:
def apply_lora_to_linear_layers(model):
for name, child in model.named_children():
if isinstance(child, nn.Linear):
setattr(model, name, LoRALinear(child)) # 站在父模块上替换
else:
apply_lora_to_linear_layers(child)
每一层递归时,model 就是当前父模块,name 是直接子模块名,可以直接 setattr 替换。
对比总结
| 场景 | 用哪个 | 为什么 |
|---|---|---|
| monkey-patch forward / 挂属性 | named_modules() |
只需要对象本身,不需要父模块 |
| 替换模块 | named_children() |
需要父模块才能执行 setattr(parent, child_name, new_module) |
| 统计参数 / 收集信息 | named_modules() |
要遍历所有模块 |
一句话总结:
named_modules()是"我看到的所有节点"named_children()是"我直接管理的子节点 + 我可以替换它们"