同一套多卡方案,换了个数据一个废一个好?我的文搜CAD项目「薛定谔的多卡」实录

发布时间:2026/6/27 6:20:45
同一套多卡方案,换了个数据一个废一个好?我的文搜CAD项目「薛定谔的多卡」实录 一、写在前面我花了两周时间调多卡训练DDP AllGather 增加对比学习负样本中间经历了各种 bugAllGather backward 写错导致梯度放大、HuggingFace 多卡加载死锁、AMP 下自定义注意力溢出……这些我之前写过一篇博客了今天不重复。今天聊一个更有意思的发现同一套多卡方案在 Text2CAD数据集上效果不如单卡换到 PartABC数据集上R1直接涨了 40%16.9% → 23.7%远超单卡。代码和超参完全没变只是换了个 --caption_file参数。所以不是「多卡有没有用」的问题而是「什么情况下多卡才有用」。这篇文章就聊这个。二、项目速览Text-to-CAD 检索给一段文字描述从 CAD 库里找匹配模型。BRepFormerGraph Transformer编码 CADDeBERTa-v3 编码文本CLIP-style 对比损失对齐。训练用的卡单卡 48G最大batch128。为了让负样本更多我做了两件事1多用一张卡做 DDP AllGather让每个 batch 能看到 256 个负样本2换一个更大的数据集 PartABC31 万条替代原来的 Text2CAD15 万条然后比较了四种组合的效果。真正的结果出人意料。图1对比学习架构速览BRepFormer DeBERTa →对比损失三、两个数据集的真实差异3.1 Text2CAD人工标注质量参差Text2CAD 约 17 万条文本是人工标注的。虽然我只用了一种文本类型比如 abstract但不同 CAD 之间的标注风格仍有隐式漂移——有的标注者写得详细有的写得简略同一个几何特征在不同模型上的描述用词也不一致。单卡训 Text2CAD 时效果还行但 AllGather 多卡一上——batch 大了同时看到的「错误对应」也多了模型反而学歪了效果不如单卡。3.2 PartABC自动生成高度一致PartABC 基于 ABC 数据集学术界标准 CAD 基准31 万条。文本描述从 CAD 几何自动生成风格高度一致对应关系确定。CAD 模型本身拓扑干净。在 PartABC 上多卡方案不仅没有退化反而显著提升了。因为数据干净更大的负样本池带来的收益被充分释放了。四、核心对比多卡在 PartABC 上确实赢了以下是 PartABC 测试集7475 条上的结果指标单卡 (batch128)2卡 AllGather (batch256)变化R116.90%23.71%↑ 6.8pp (40%)R544.71%51.10%↑ 6.4ppR1057.90%61.20%↑ 3.3ppMRR0.3000.363↑ 21%MedR75↓ 更好CAD_CAD R1-82.5%嵌入质量好R1 从 16.9% 涨到 23.7%绝对值涨了 6.8 个百分点相对提升 40%。CAD→CAD 自检 R182.5%说明嵌入空间结构良好不是坍缩。多卡的正向收益是实打实的。但同样这套代码换到 Text2CAD 上就是另一种结果——多卡不如单卡。问题不是出在代码是出在数据。图2实验效果截图五、为什么 PartABC 上多卡有效对比学习的核心是正样本靠近负样本推开。负样本的数量和质量直接决定了学到的嵌入质量。AllGather 让 2 张卡的 embedding 拼在一起算对比损失单卡: 对比矩阵 [128, 128] → 127 个负样本2 卡 AllGather: 对比矩阵 [256, 256] → 255 个负样本多了一倍的负样本按理说应该更好——但前提是这些负样本的「标签」是对的。Text2CAD上标注有噪声多出来的负样本里混入了「假负样本」几何相似但文本描述用了不同词汇。大 batch 更多错误信号 →模型学歪。PartABC上标注一致多出来的负样本都是真实的区分信号。大 batch真正发挥了对比例学习的优势。六、PartABC 的另一个坑数值不稳定不过 PartABC 也不是一帆风顺。第一次训的时候直接炸了——训练 loss 正常下降测试全 NaN。[Diagnostic] 嵌入向量诊断 (n7475)...[CAD_t2c] NaNTrue, mean_normnanR1: 0.000136.1 数据分布差异导致 AMP 溢出PartABC 来自 ABC 数据集几何变化范围比 Text2CAD 更大——有些面夹角极小有些边长度差异大。BRepFormer 的自定义 RoPE 注意力用了 torch.view_as_complex 做复数运算在 AMP(fp16) 下精度不够Attention scores 直接溢出为 NaN。6.2 GradScaler 的静默跳过最坑的是你看不到 NaN——GradScaler 检测到 NaN 会静默跳过那个 steptqdm 显示上一个有效 batch 的 loss所以训练界面一切正常。但参数已经被污染等你训完一测——全废了。6.3 修复方案第一拆BRepEncoder切回 fp32既然 BRepEncoder 只占 5M 参数模型总量 189M 的 2.7%把它从 autocast 里移出来只让 DeBERTa 走 AMP# BRepEncoder Proj 跑 fp32_, graph_emb self.brep_encoder(batch) # fp32cad_emb self.brep_proj(graph_emb) # fp32with autocast(enabledself.args.amp):text_emb self.encode_text(captions) # 只 DeBERTa 走 AMP第二拆注意力分数加 nan_to_numscores torch.nan_to_num(scores, nan0.0, posinf1e9, neginf-1e9)第三拆修复 GraphEncoderLayer的双 FFN bugSwiGLU 被连续调用了两次第二层覆盖第一层数值异常。去掉多余的调用。七、四种组合的完整对比以下是完整的实验矩阵数据集训练方式文本编码器R1结论Text2CAD单卡DeBERTa~17%基线Text2CAD2 卡 AllGatherDeBERTa更低坍塌❌ 多卡不如单卡PartABC单卡DeBERTa16.9%基线PartABC2 卡 AllGatherDeBERTa23.7%✅ 多卡显著提升关键观察同样的多卡策略换了数据就从「负优化」变成了「40% 提升」。根本原因不在代码在数据的标注质量。八、重新认识几个事情这次折腾让我对几个问题有了新的认识1.多卡有没有用取决于你的数据干不干净。如果数据标注质量高PartABC 的自动生成更大的 batch 带来的负样本都是有效信号多卡 AllGather 就有正收益。如果标注有噪声人工标注的 Text2CAD大 batch 反而让噪声被放大多卡不如单卡。所以下次有人问你「多卡有用吗」你应该反问「你的数据有多干净」2.换数据不是换个 --caption_file那么简单。同样的代码换一个数据集数值稳定性可能天差地别。PartABC 的几何变化范围更大同样的 AMP 策略在 Text2CAD 上没事在 PartABC 上就溢出了。换新数据时至少做一次纯 fp32 的对照实验确认数值稳定再开 AMP。3.自定义算子 AMP的组合要小心。AMP 号称「无损加速」前提是你的算子在 fp16 下能稳定计算。view_as_complex polar 这些复数运算在 fp16 下的精度窗口非常窄。如果你有自定义注意力或者自定义激活函数留个心眼。4.对比学习的大 batch问题有更好的解法。如果你卡在 batch size 上除了堆显卡还可以试试 MoCo 的负样本队列——用 FIFO 队列存历史 embedding队列长度 4096 不受显存限制30 行代码就能实现单卡也能跑。这可能是对比学习领域性价比最高的 trick 了。九、写在最后这次最大的收获不是调通多卡而是理解了「数据质量决定工程策略的有效性」。你以为多卡是个通用优化——加上去总该有点提升吧但实际效果完全取决于你的数据能不能 hold 住更大的 batch。所以我现在做实验的流程变了先用小数据小 batch 把模型训稳定然后换大数据确认数值稳定性最后才上多卡看能不能进一步榨出收益。顺序反了的话出了问题你都不知道是代码 bug 还是数据问题还是多卡的问题。