bev-project/archive/docs_old/FP16训练问题分析_20251102.md

7.8 KiB
Raw Blame History

FP16训练Map Head缺失问题深度分析

分析时间: 2025-11-02 12:35 UTC
问题: FP16训练中BEV分割任务完全缺失


🚨 问题症状

1. Loss输出差异

FP32训练正常:

Epoch [1][50]
loss/map/drivable_area/dice: 0.1315  ✅
loss/map/ped_crossing/dice: 0.3073   ✅
loss/map/walkway/dice: 0.2667         ✅
loss/map/stop_line/dice: 0.4295       ✅
loss/map/carpark_area/dice: 0.2706    ✅
loss/map/divider/dice: 0.6023         ✅
loss/object/loss_heatmap: 0.2326      ✅
loss: 2.8185  ← 包含map+object

FP16训练异常:

Epoch [1][50]
loss/object/loss_heatmap: 0.6090      ✅
loss/object/layer_-1_loss_cls: 0.0996 ✅
loss/object/layer_-1_loss_bbox: 0.7440 ✅
loss: 1.4526  ← 只有object缺少map

差异:

  • FP32总loss ~2.82 (map+object)
  • FP16总loss ~1.45 (仅object)
  • Map相关loss完全缺失

2. 模型结构差异

FP32模型打印:

BEVFusion(
  (encoders): ModuleDict(...)
  (fuser): ConvFuser(...)  ✅
  (decoder): ModuleDict(...)
  (heads): ModuleDict(
    (object): TransFusionHead(...)  ✅
    (map): EnhancedBEVSegmentationHead(  ✅
      (transform): BEVGridTransform()
      (aspp): ASPP(...)
      (channel_attn): ChannelAttention(...)
      (spatial_attn): SpatialAttention(...)
      (decoder): Sequential(...)
      (classifiers): ModuleList(...)
      (aux_classifier): Conv2d(...)
    )
  )
)

FP16模型打印:

BEVFusion(
  (encoders): ModuleDict(...)
  (fuser): ConvFuser(...)  ✅
  (decoder): ModuleDict(...)
  (heads): ModuleDict(
    (object): TransFusionHead(...)  ✅
    # ← 模型打印在这里就结束了没有map!
  )
)

结论: FP16模型中map head完全没有被创建


3. Checkpoint加载警告

FP16训练警告:

unexpected key in source state_dict: 
  heads.map.aspp.convs.0.weight
  heads.map.aspp.convs.1.weight
  heads.map.aspp.convs.2.weight
  ...
  heads.map.classifiers.0.0.weight
  ...
  heads.map.aux_classifier.weight
  heads.map.aux_classifier.bias

含义:

  • Checkpoint中有完整的map head权重
  • 但当前模型中没有对应的结构
  • 所以这些权重被标记为"unexpected"并被丢弃

🔍 根本原因分析

可能原因1: 配置继承问题(最可能)

FP16配置文件:

# multitask_BEV2X_phase4a_stage1_fp16.yaml
_base_: ./multitask_BEV2X_phase4a_stage1.yaml

model:
  fuser:  # ← 显式声明了fuser
    type: ConvFuser
    in_channels: [80, 256]
    out_channels: 256

# ← 但没有显式声明heads!

问题:

  • 在FP16配置中显式声明model.fuser
  • 这可能导致mmcv的配置合并机制认为要覆盖整个model字段
  • 结果base配置中的model.heads.map被丢弃!

可能原因2: FP16与EnhancedBEVSegmentationHead不兼容

假设: EnhancedBEVSegmentationHead在FP16模式下初始化失败

验证方法:

# 检查是否有初始化错误被静默处理

可能原因3: 配置文件语法问题

YAML配置合并规则:

# 错误的覆盖方式
model:
  fuser: {...}
# 这会导致model下其他字段encoders, heads, decoder被丢弃

# 正确的覆盖方式
model:
  fuser: {...}
  heads:
    map: ${_base_.model.heads.map}  # 显式继承

解决方案

方案A: 修复FP16配置文件推荐

修改: 显式继承所有model字段

# multitask_BEV2X_phase4a_stage1_fp16.yaml
_base_: ./multitask_BEV2X_phase4a_stage1.yaml

work_dir: /data/runs/phase4a_stage1_fp16

# ⚠️ 不要覆盖model字段改为merge
# 方法1: 完全不声明model让base配置生效
# 方法2: 显式继承heads
model:
  fuser:
    type: ConvFuser
    in_channels: [80, 256]
    out_channels: 256
  heads:
    object: ${_base_.model.heads.object}  # 显式继承
    map: ${_base_.model.heads.map}        # 显式继承

fp16:
  loss_scale: dynamic

方案B: 不显式声明fuser测试

# multitask_BEV2X_phase4a_stage1_fp16.yaml
_base_: ./multitask_BEV2X_phase4a_stage1.yaml

work_dir: /data/runs/phase4a_stage1_fp16

# 完全不声明model让base配置生效
# model:  # ← 删除这部分
#   fuser:
#     ...

fp16:
  loss_scale: dynamic

方案C: 完整声明model最保险

# 复制base文件中的完整model配置
model:
  encoders: ${_base_.model.encoders}
  fuser: ${_base_.model.fuser}
  decoder: ${_base_.model.decoder}
  heads: ${_base_.model.heads}
  loss_scale: ${_base_.model.loss_scale}

fp16:
  loss_scale: dynamic

🧪 验证测试

测试1: 检查配置合并结果

cd /workspace/bevfusion

# 使用修复后的配置
python -c "
from mmcv import Config
cfg = Config.fromfile('configs/.../multitask_BEV2X_phase4a_stage1_fp16.yaml')

print('Model keys:', list(cfg.model.keys()))
print('Heads keys:', list(cfg.model.heads.keys()))
print()
print('Object head type:', cfg.model.heads.get('object', {}).get('type', 'ConfigDict'))
print('Map head type:', cfg.model.heads.get('map', {}).get('type', 'NOT FOUND'))
"

测试2: Dry-run模型构建

# 测试模型是否能正确构建
python -c "
from mmcv import Config
from mmdet3d.models import build_model

cfg = Config.fromfile('configs/.../multitask_BEV2X_phase4a_stage1_fp16.yaml')
model = build_model(cfg.model)

print('Model heads:', list(model.heads.keys()))
for name, head in model.heads.items():
    print(f'  {name}: {type(head).__name__}')
"

📊 配置合并机制说明

YAML配置继承规则

mmcv Config合并行为:

# base.yaml
model:
  encoders: {...}
  fuser: {...}
  heads:
    object: {...}
    map: {...}

# derived.yaml
_base_: ./base.yaml

# 情况1: 不声明model → 完全继承 ✅
# 结果: model有完整的encoders, fuser, heads

# 情况2: 部分声明model → 覆盖整个model! ❌
model:
  fuser: {...}
# 结果: model只有fuser丢失encoders和heads!

# 情况3: 显式继承 → 正确合并 ✅
model:
  fuser: {...}
  heads: ${_base_.model.heads}  
# 结果: fuser被覆盖heads继承自base

🎯 FP16失败的真正原因

确认 YAML配置合并错误

过程:

1. base配置定义了完整modelencoders, fuser, heads
2. FP16配置中显式声明了model.fuser
3. mmcv配置合并时认为要覆盖整个model字段
4. 结果model.heads被丢弃
5. 模型构建时只有object head从哪来默认配置
6. Map head完全没有被创建
7. 训练时只有object loss

修复建议

立即修复方案(最简单)

删除FP16配置中的model字段:

# multitask_BEV2X_phase4a_stage1_fp16.yaml
_base_: ./multitask_BEV2X_phase4a_stage1.yaml

work_dir: /data/runs/phase4a_stage1_fp16

# 删除model字段完全继承base配置
# model:  # ← 删除
#   fuser:
#     ...

fp16:
  loss_scale: dynamic

原因:

  • Base配置中已经有正确的fuser
  • 不需要在FP16配置中重复声明
  • 让配置完全继承即可

📝 经验教训

  1. mmcv配置继承陷阱: 部分声明会覆盖整个字段
  2. 调试方法: 对比模型打印,查看哪些模块缺失
  3. 验证配置: 修改后先用python加载检查再启动训练
  4. 保守优化: 重大训练时先保证功能完整,再追求性能

🔧 下一步行动

选项1: 继续FP32训练当前

优势:

  • 已验证稳定
  • 双任务完整
  • 无风险

时间: 9天完成

选项2: 修复FP16后重试

步骤:

  1. 修复FP16配置删除model字段
  2. 验证配置加载正确
  3. 小规模测试50 iters
  4. 确认map loss存在后正式训练

时间: 调试1-2小时如成功可节省2.5天


状态: 问题分析完成FP32训练恢复成功
建议: 先让FP32训练跑后续再优化FP16

FP32训练正常运行中预计11/10完成