<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>MindSpeed on zzudongxiang</title><link>https://www.zzudongxiang.com/tags/mindspeed/</link><description>Recent content in MindSpeed on zzudongxiang</description><generator>Hugo</generator><language>zh-cn</language><copyright>2026 zzudongxiang.com · 豫ICP备2022012018号-1 · 豫公网安备41160202000614号</copyright><lastBuildDate>Tue, 27 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.zzudongxiang.com/tags/mindspeed/index.xml" rel="self" type="application/rss+xml"/><item><title>记录在昇腾上训练模型时踩到的一个坑</title><link>https://www.zzudongxiang.com/posts/pretrain-loss-mask-cache/</link><pubDate>Tue, 27 Oct 2026 00:00:00 +0000</pubDate><guid>https://www.zzudongxiang.com/posts/pretrain-loss-mask-cache/</guid><description>&lt;blockquote&gt;
&lt;p&gt;摘要：最近用 MindSpeed 预训练一个 MoE 模型，碰到了两件怪事：续训后周期性 NaN，以及每次重启 loss 都往上跳一截。查到最后，是数据侧一行原地写把缓存的 mask 模板慢慢写坏了。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="1-事情的起因"&gt;1. 事情的起因&lt;/h2&gt;
&lt;p&gt;最近在用 MindSpeed 预训练一个 MoE 模型，大概用了4台机器训练一个13B左右的参数，设置的是每 1000 步存一次Checkpoint&lt;/p&gt;
&lt;p&gt;然后就遇到两件怪事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;NaN&lt;/strong&gt;：续训跑一段时间，反向传播里突然报 &lt;code&gt;found NaN in local grad norm&lt;/code&gt;。奇怪的是loss还没爆炸，但是梯度先坏了，重启一下续训又好了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;loss 跳升&lt;/strong&gt;：每次重启之后的第一段日志，loss比重启前都要稍微高一点，0.35 到 0.45 之间。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;单看哪一条都不像大事。跳升嘛，换个数据窗口很正常；NaN 嘛，偶发的数值意外。但这两件事都只在&amp;quot;重启&amp;quot;之后出现，就有点意思了。&lt;/p&gt;
&lt;p&gt;&lt;img alt="image-20261027103644971" loading="lazy" src="https://www.zzudongxiang.com/posts/pretrain-loss-mask-cache/images/image-20261027103644971.jpg"&gt;&lt;/p&gt;
&lt;h2 id="2-问题排查"&gt;2. 问题排查&lt;/h2&gt;
&lt;p&gt;一开始的怀疑清单很长：硬件、通信、精度、学习率、数据…逐个排查。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;通信和硬件&lt;/strong&gt;：小整数all_reduce之类的逐位校验跑了一遍，多种dtype、多种尺寸，还加了乱序压力，全过；训练期间的通信日志也很干净。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;精度&lt;/strong&gt;：bf16换FP32残差、改路由精度，问题依旧。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;学习率&lt;/strong&gt;：跨15倍幅度、换warmup形状，问题还是依旧。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;数据内容&lt;/strong&gt;：把挂掉那一步的原始文档离线解码出来看，就是普通文本，没毒；换个seed重建整条样本流，它还是死在进程启动之后的某个固定的位置。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;排到后面就剩一条线索：&lt;strong&gt;死点跟&amp;quot;训练到第几步&amp;quot;关系不大，跟&amp;quot;这个进程启动之后吃了多少样本&amp;quot;关系很大&lt;/strong&gt;。同一份配置重启一次，计数器清零，故障就得重新攒一遍才出现。&lt;/p&gt;
&lt;p&gt;换句话说，问题不在训练动力学里，在&amp;quot;某个随进程增长的状态&amp;quot;里。范围一下小了很多。&lt;/p&gt;
&lt;h2 id="3-罪魁祸首"&gt;3. 罪魁祸首&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;GPTDataset.__getitem__&lt;/code&gt; 会缓存loss mask，但缓存的是&lt;strong&gt;引用&lt;/strong&gt;。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 屏蔽PAD，代码直接原地清零&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loss_mask[labels &lt;span style="color:#f92672"&gt;==&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_pad_token_id] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0.0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;于是每个DataLoader worker都在改同一份模板。偏偏这份数据的PAD和EOS用的是同一个token id（151643），packed之后样本里到处都是它，模板里的有效位置就这么被一点点吃掉。&lt;/p&gt;
&lt;p&gt;造成的影响：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;软污染&lt;/strong&gt;：mask的有效token变少，训练loss的计算口径慢慢偏。污染期间loss偏低，重启重建模板之后就&amp;quot;跳升&amp;quot;——其实只是回到了正确值。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;硬失败&lt;/strong&gt;：当某个局部microbatch的mask被吃到全零，token分母为0，反向直接出非有限值。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而这份缓存是&lt;strong&gt;每个worker自己的&lt;/strong&gt;，一重启就重建。所以故障严格按&amp;quot;进程内消费的样本数&amp;quot;复现，严丝合缝。&lt;/p&gt;
&lt;p&gt;&lt;img alt="缓存模板被逐样本原地清零的过程" loading="lazy" src="https://www.zzudongxiang.com/posts/pretrain-loss-mask-cache/images/fig2-root-cause.jpg"&gt;&lt;/p&gt;
&lt;h2 id="4-实锤的证据"&gt;4. 实锤的证据&lt;/h2&gt;
&lt;p&gt;用原版代码、原数据和原helper，把DP32 × 2worker的64条数据流在CPU上重放了一遍：最早出现全零mask的是rank4/worker1，相对训练步1508，而历史崩溃日志里记的计数器是1507——对上了。加clone之后，那条样本的有效token是 4091、raw PAD是5，64条流逐样本和当前标签一致。&lt;/p&gt;
&lt;p&gt;更直接的是冻结权重：同一份权重、同一批输入、同一份模型输出，只换mask，重算5120条序列的CE。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;同一权重、同一模型输出&lt;/th&gt;
&lt;th style="text-align: right"&gt;CE 均值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;重建历史污染mask&lt;/td&gt;
&lt;td style="text-align: right"&gt;1.902&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;当前样本的正确mask&lt;/td&gt;
&lt;td style="text-align: right"&gt;2.351&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;差值&lt;/td&gt;
&lt;td style="text-align: right"&gt;+0.449 nats&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;当然，这只能量化mask的测量效应，不能反推历史每一步的权重到底偏了多少。&lt;/p&gt;</description></item></channel></rss>