Megatron Timer Predictor——训练 Straggler 在线检测与定位系统

本文记录我设计并落地的 Megatron Timer Predictor(mt-detector):一个针对 Megatron-LM 大规模分布式训练中性能落后节点(Straggler)问题的检测与定位系统。它从 ClickHouse 读 Megatron Timer 已落盘的全 rank 时序指标,经"6 种聚类算法 + 跨算法投票"识别异常 rank,再针对流水线并行(PP)场景做通信异常检测 + 交叉验证定位网络拥塞节点,多进程并发把分析压到 30 秒内。

说明:项目名里 “Predictor/预测” 是个 misnomer——系统做的是当前态检测/分类(哪些 rank 现在慢),不做时序外推或"未来哪步会慢"的预测。下文"预测"一律按"检测/标记当前异常 rank"理解。

背景知识:Megatron Timer 怎么采时序、为什么跨 rank all-gather 取 max,见本人《Megatron Timers 源码精读》篇。本篇讲的是这些时序数据被采集层写入 ClickHouse 之后,怎么自动找出谁是 straggler、为什么慢、慢在哪一段


一、问题:Straggler 是分布式训练的隐形杀手

大模型训练动辄几百到几千张 GPU,靠 NCCL/Megatron 的集合通信把各卡同步起来。木桶效应:训练每个 step 的推进速度由最慢的那张卡决定——它没算完,all-reduce 等它;它没把梯度同步完,下一步卡住。这张慢卡就是 straggler

straggler 的危害:

  • 吞吐塌方:1000 张卡里 1 张慢 20%,整个集群吞吐掉 20%——上千卡折算成钱和算力是天文数字。
  • 难发现:straggler 不是死机(死机 NCCL 会直接 hang/报错),而是"还能跑但比别卡慢",日志里不报错、metrics 平均值看着正常,被均值掩盖。
  • 难定位:慢的原因五花八门——GPU 硬件降频/故障、显存 ECC 错误触发重算、NVLink 拓扑错位跨 NUMA、网卡拥塞丢包、某个 op 的 kernel 异常、数据加载抖动……要能区分"计算慢"还是"通信慢"、定位到具体卡和具体段。

传统运维手段不够:

  • nvidia-smi 看 util/温度:粗粒度,util 高也可能正卡在访存,看不出谁落后。
  • 看 loss/吞吐:是结果不是原因,发现时已经亏了几个小时。
  • 看 NCCL 报错:只覆盖死链路,慢但不死的 straggler 不报。

所以需要一个基于训练运行时真实时序、自动、准、快的检测定位系统。这就是 Megatron Timer Predictor 的目标:

  1. 检测:哪个 rank 是 straggler(异常检测)。
  2. 归因:它慢在计算还是通信、慢在哪个阶段、是不是网络拥塞节点。
  3. :30 秒内出分析结果,能接进运维告警闭环。
  4. :以运维后确认的故障/换卡样本为评估基准,目标准确率 >90%(少误报少漏报)。

二、数据来源:ClickHouse 里的 Megatron Timer 全 rank 时序

系统的"信号源"不是直接调 megatron/core/timers.py,而是已由采集层写入 ClickHouse 的 megatron_metrics。Megatron Timer 每步跨 rank all-gather 出一张 [world_size, num_timers] 矩阵(每行一个 rank,每列一个 timer),采集层把它按行展开写进 ClickHouse,本项目再去查。

2.1 表与字段

1
2
3
4
5
6
CREATE TABLE dev.megatron_metrics (
`timestamp` DateTime, `job` String, `metric_name` String, `subname` String,
`rank` Int32, `iteration` Int32, `node` String, `cluster` String,
`metric_values` Array(Float64), `attributes` Object('json'), `rerun_uuid` String
) ENGINE = MergeTree PARTITION BY timestamp
ORDER BY (timestamp, job, metric_name, subname, rank, iteration, node, cluster)

本项目读三类 metric_name:

metric_name 含义 典型 subname
megatron_timers 各 timer 的时长(数组) interval-time(整步)、forward-computebackward-computeforward-sendforward-recvbackward-sendbackward-recvforward-send-backward-recvbackward-send-forward-recvself-attention-linear-qkv
megatron_timers_trace trace 事件(start/end 配对) 各通信段 phase
megatron_parallel_group_rank 并行组 rank pp(自动探测 PP size)

每个 rank 一个 timer 的耗时取 metric_values 数组里的元素(如 interval-timemetric_values[1] 即整步秒数)。

2.2 为什么这套数据适合做异常检测

  1. 粒度对:timer 切到 forward-compute/backward-compute/通信段,能直接看出"慢在哪一段"——不像 nvidia-smi 只给一个 util。
  2. 跨 rank 可比:all-gather 后每个 rank 都有完整矩阵,天然带 rank 维度做对比。
  3. 已 sync 过滤:Timer 内部 cuda.synchronize 保证数据可信(详见 Timer 篇),不会被异步 launch 污染。
  4. 落盘可查:写入 ClickHouse 后,检测服务与训练进程解耦,可离线/在线反复分析。

2.3 特征构建:per-(subname, rank) 四元组

聚类输入不是单步的原始耗时,而是对每个 (subname, rank) 跨一个 iteration 区间聚合出的四元组:

1
[Min, Max, Range, Mean]   # 该 rank 在该 subname 上、跨区间所有 iter 的 最小/最大/极差/均值

额外预处理(predicter/Cluster/fastslow_common.py):

  • 丢弃前 10 个 iteration:起步阶段抖动大,不参与。
  • 过滤覆盖不全的 subname:不是所有 rank 都打了某 timer(值为 0)的 subname 剔除。
  • 过滤相对 spread 太小的 subname:min_subname_rel_range=0.03,极差/均值 < 3% 的 subname 区分度不够,跳过。
  • robust scale 归一化:用 median/MAD 或 IQR 做 robust 缩放(不同 subname 量级差大:forward-compute 几百 ms、send 几 ms),让各维度可比。不是 z-score——z-score 受异常值拉动,robust 更稳。
  • PP≥2 时限定 compute 白名单:有流水线并行时,聚类只用计算类 subname,通信类另走通信检测。

注:滑动中位数这一步不在聚类预处理里,而在在线触发那一层——用来判定"某 iter 是否算慢"(见第三节),两者别混淆。

2.4 标签:什么是"异常"

无监督为主(真实生产里 straggler 标签稀缺),但有"事后确认真故障"的样本作为评估基准(运维记录的 GPU 故障卡/换卡事件)。准确率口径 = 检出的异常 rank 与运维确认的故障/落后节点一致的比例(目标 >90%,属评估口径而非代码内置数值)。


三、在线触发:从轮询到启动分析

检测不是每个 step 都跑一遍重聚类,而是在线轮询 + 条件触发 batch 分析两段式(monitor/)。

3.1 轮询与慢迭代判定

DataMonitorcheck_interval(生产 30s / 模拟 5s)轮询 ClickHouse 的 get_latest_iterations,发现新 iteration 就分析上一iter:

  • 读该 iter 的 interval-time 时长(秒)。
  • 慢判定:growth_rate = (current - median)/median,当 growth_rate > slowdown_threshold*100(默认 slowdown_threshold=0.25,即 25%)判为慢。
  • 滚动中位数:baseline 只纳入"正常"iter(时长比当前中位数 ratio < 1.1 的才进 baseline),保留最近 history_size=5 个,避免慢 iter 自身污染中位数。
  • hang 判定:hang_threshold=60s 内无新 iter → 状态 hanging

3.2 触发分析

连续 5 个慢 iter(或慢历史 ≥5 且上次结果为空)时,状态置 predicting,起一个线程跑 _run_straggler_analysis:

  • 用慢 iter 区间拼 --iterations start-end
  • 用 job 注册时间到现在拼 --start/--end 时间界(这条是 commit 014d164 "修复在线检测中的问题" 加的,防止 batch 分析拉到 job 注册前的旧数据)。
  • 子进程执行 python3 predicter/prediction.py --job-id … --algorithm all --iterations … --start … --end …
  • 分析完读最新的 predicter/prediction_results/prediction_<ts>/summary.txt,正则取 anomaly_ranks:/anomaly_nodes:,状态置 detected

所以"30 秒"是分析阶段的耗时,触发条件是"连续 5 个慢 iter",不是每 step 都 30 秒。


四、检测核心:6 种聚类算法 + 跨算法投票

4.1 为什么用聚类做异常检测

正常 rank 耗时聚集在"正常簇"里,straggler 是偏离的 rank。这本质是个无监督异常检测问题。但没有单一算法通吃所有形态:基于距离的(KMeans)、基于密度的(DBSCAN)、基于分布/隶属度的(GMM/Fuzzy)、层次结构(Agglomerative)、密度峰值(Mean Shift)各有盲区。所以上多种互补算法,各自给每个 rank 一个 fast/slow 标签,再投票融合——单算法盲区被其它补。

4.2 集成的 6 种算法(实际实现)

算法 模型 检测准则 文件
KMeans sklearn KMeans(n_clusters=2) 聚成 2 簇,均值最高的簇=slow;>2 簇时二级 KMeans 收敛 Cluster/kmeans.py
DBSCAN sklearn DBSCAN,eps 自适应(k-distance 膝点 × 多尺度) noise 与非 noise 归并后,均值最高簇=slow Cluster/dbscan.py
GMM GaussianMixture(n_components=2),协方差类型网格搜索 argmax 隶属度分簇,均值最高簇=slow Cluster/gmm.py
Agglomerative(层次) AgglomerativeClustering(n_clusters=2),linkage/metric 网格 均值最高簇=slow Cluster/hierarchical.py
Mean Shift sklearn MeanShift,bandwidth=分位数估计×倍数 自动簇数,二级 KMeans 收敛后均值最高簇=slow Cluster/mean_shift.py
Fuzzy C-Means(模糊) 手写 FCM,隶属度矩阵 U argmax 隶属度分簇,均值最高簇=slow;网格搜 m/seed Cluster/fuzzy.py

关键认知:六个算法的判定准则统一是"落入慢簇(各 timer 均值最高的那个簇)",不是"离质心距离大"、不是"概率低于阈值"、不是"离群点 label=-1"。这样把异构算法的输出规约成同一语义的 fast/slow 二值标签,才能跨算法投票。每个算法内部都做超参"调优"(silhouette / Calinski-Harabasz / Davies-Bouldin 选最优)。

4.3 三层投票融合(实际机制)

第一层——算法内、跨 subname 加权多数(fastslow_common.py:vote_rank_labels):

每个算法对每个 subname 都把 rank 聚成 fast/slow,然后跨该 rank 参与的所有 subname 投票:

1
label = "slow" if slow_votes * 2 >= total else "fast"   # 加权多数

其中 self-attention-linear-qkv 权重为 3(它是计算耗时的强信号),其余 subname 权重 1。再加一道 original_total_subnames 门槛:slow 票数需 ≥ 该 rank 覆盖的原始 subname 数的一半,否则强制 fast,防"只参与了少数 subname 就被判慢"。PP≥2 时还用 top-10 慢 iter 做 1.3× 收窄(某 rank 在慢 iter 里均值 < 最快 rank 的 1.3× 就回退 fast)。

每个算法最终输出一张 fast_slow_summary.csv(kind=rank, key=rankN, label=slow/fast, slow_votes, total_subnames)。

第二层——跨算法、取最高票(prediction.py:generate_summary_file 活性版本):

6 个算法各跑一遍(各自一个进程),汇总每个 rank 被多少个算法标 slow:

1
2
rank_counter = Counter(slow_ranks_from_each_algo)
cluster_anomaly_ranks = most_common_ranks # 取被标 slow 次数最多的 rank(s)

最终 cluster 异常 rank = 跨 6 算法得票最多的 rank。这是计数取 max,不是固定阈值(如"≥4/6")——固定阈值那条规则只存在于被覆盖的死代码里,线上不生效。

第三层——cluster 与 communication 的节点并集(见第五节):

1
2
all_anomaly_ranks  = cluster_anomaly_ranks                        # 只来自聚类
all_anomaly_nodes = cluster_anomaly_nodes ∪ comm_anomaly_nodes # 节点取并集

写入 summary.txtanomaly_ranks: / anomaly_nodes:

这样设计的好处:算法内 subname 多数投票抗"单 subname 抖动";跨算法取最高票抗"单算法系统性偏差"(如 DBSCAN 对 eps 敏感、KMeans 对初始化敏感时,其它算法的票能纠偏)。没有"加权打分 + 权重学习 + 标签回填重训"那套——线上是固定的计数取 max,简单可靠。

4.4 为什么是这 6 个的组合

选算法刻意覆盖多类原理:基于距离(KMeans)、基于密度(DBSCAN)、基于分布/隶属度(GMM/Fuzzy)、层次结构(Agglomerative)、密度峰值(Mean Shift)。原理越分散,盲区越不重叠,ensemble 增益越大。纯堆同类算法(比如 6 个都是 KMeans 变种)投票增益小。


五、归因:通信异常检测 + 交叉验证定位网络拥塞

光说"rank 53 是 straggler"还不够,运维要知道慢在计算还是通信、是不是某段网络拥塞。这里针对 Megatron 流水线并行(PP)场景设计了通信异常检测模块交叉验证(predicter/Communication/)。PP=1 时跳过整个通信检测;PP≥2 才跑。

5.1 流水线并行的通信特征

PP 把模型按层切成 P 段,段间用 P2P send/recv 传激活。Megatron 的通信段被打成 megatron_timers_trace(start/end 配对算时长)和 megatron_timersforward-send/forward-recv/backward-send/backward-recv/forward-send-backward-recv/backward-send-forward-recv 等 subname。

5.2 通信异常检测(communication_predict.py / IBWDetector)

区间内每个 iteration跑一遍(≤4 并行),5 个 trace 子检测器覆盖 5 条通信段:

  • 把 trace 行转成 start/end 事件,按 PP group 分组,比较每组首个 timer 值;
  • 某 group 首值 > min(各 group 首值) × threshold 即判该 group 异常(文件默认 1.5,CLI 默认 1.3);
  • detect_sudden_increases 标连续值跳变 > 2.0×;
  • baseline 检测:baseline = min{ 非零 group 最小值, ½·PP0 的 fsbr, ½·末 PP 的 bsfr },worker 异常 = avg_last_value > 2.0 × baseline

5.3 交叉验证定位拥塞节点(apply_cross_validation_rules)

通信段慢有二义性:send 端发得慢?recv 端收得慢?两端之间的网络拥塞? 用多信号互证消解:

  1. 计算 vs 通信分离:把 rank 的 timer 拆成计算段(forward-compute/backward-compute)和通信段(send/recv)。
    • 某 rank 计算段正常、通信段异常 → 倾向网络/通信问题。
    • 某 rank 计算段也异常 → 倾向该卡硬件/算力问题(降频、ECC、kernel 异常)。
  2. send/recv 端点互证:
    • forward-send 异常 ⇒ 该 worker 发送拥塞下游 worker 接收拥塞;
    • forward-recv 异常 ⇒ 该 worker 接收拥塞或上游发送拥塞;
    • 结合 PP group 归属判定两端点。
  3. PP group 一致性:某 worker 涉及的通信段都慢 → 该节点网络问题;只单段慢 → 那段链路问题。
  4. 跨 iteration 并集:每个 iter 各自跑通信检测,结果在 generate_summary_file 里 union 成 all_anomalous_workers(send 拥塞 ∪ recv 拥塞),覆盖整个慢 iter 区间。

注:代码里的归因粒度到 worker / node(rank→node 靠查 ClickHouse 的 node 字段映射),不到 NVSwitch 域/IB 口的物理拓扑粒度。把异常 rank 映射到 node 是有的,再往下到具体交换机/网口要靠外部拓扑系统,本项目不内置。

交叉验证后输出结构化归因:

1
2
3
4
5
straggler: rank 5 (node gpu-07)
计算段: forward-compute 正常(均值接近中位数)
通信段: forward-send 异常(2.1× baseline)
交叉验证: rank 5 的 send 拥塞,下游 recv 正常 → 该节点发送侧网络问题
来源: 跨 5 个慢 iter 的通信检测并集

这把"谁慢"和"为什么慢、慢在哪"都给运维了。

5.4 PP=2 的兜底检查

PP=2 是最小 PP,通信段特征少,有专门的 run_pp2_fallback_check:随机抽 3 个 (fast, slow) iter 对,比 5 条通信 subname 的 per-worker 均值,slow iter 某 worker 均值 > 其 fast iter 均值 × 1.3 → “通信可能有异常”。给小 PP 场景兜个底。


六、性能优化:多进程并发,分析阶段 30 秒内

检测要接进运维告警闭环,必须快——触发后训练还在跑、运维在等。优化手段:

6.1 多进程并发

sklearn 底层有 GIL,且要避开 BLAS 多线程争抢,用进程不用线程:

  • 算法并行:run_cluster()ProcessPoolExecutor,6 种算法各一个进程并行算(prediction.py)。
  • 通信并行:run_communication() 对每个 iter 跑通信检测,≤4 个进程并行。
  • 进程间只传小的 timer 聚合结果(四元组)和 fast/slow 标签,不传大对象,开销低。

6.2 计算层面的优化

  • 向量化:归一化、距离计算全用 numpy/sklearn 向量化,避免 Python 循环。
  • 早筛:min_subname_rel_range 等过滤跳过区分度不够的 subname,减少聚类样本。
  • 超参调优有界:每个算法的网格搜索范围限定,避免在无效参数空间耗时间。
  • 只对慢 iter 区间分析:不是全量 iter 重算,只分析触发时圈定的慢 iter 范围 + 时间界。

6.3 结果

  • 分析阶段(6 算法并行 + 通信 per-iter 并行 + 跨算法投票 + 归因)压在 30 秒内
  • 触发前是 30s 轮询 + 连续 5 慢 iter 的条件,所以从"慢出现"到"出结果"约几分钟(取决于多快凑够 5 个慢 iter),分析本身 30 秒级。

七、系统形态与 API

7.1 形态

  • 采集层:Megatron Timer 采集 → 写入 ClickHouse megatron_metrics(生产用 --log-level 控制 Timer 开销)。
  • 检测层:一个常驻 Flask 服务(main.py 起),DataMonitor 轮询 + 触发 predicter/prediction.py 子进程做聚类+通信分析,结果写 summary.txt
  • API 层:Flask 暴露状态查询/注册接口(见下)。
  • 部署:Helm chart(megatron-timer-predichter v0.1.0),1 replica,镜像 cr.infini-ai.com/tsinghua-lenovo/megatron-timer-predicter:v0.1.0,ClickHouse 配置经 env 注入。
  • 模拟器:simulator/ 不是凭空合成,而是回放真实 source job(默认 jo-dbyrjz4l337e3y6j)到新 sim-<uuid> job id,按 write_speed 节奏重放,便于本地端到端测试(8 rank 来自 source)。

说明:本项目不依赖 Prometheus/Grafana/Kafka。代码里有个 GET /metrics 路由但只是占位符,没有真实 Prometheus 指标导出;Timer 数据直接写 ClickHouse 满足吞吐,没引入 Kafka 链路。

7.2 Flask API(api/flask_server_routes.py)

方法 路径 作用
GET / 健康/版本
GET /metrics 占位(无真实 Prometheus)
GET /jobs 列出监控中的 job
GET /detect/<job_id> 注册 job 进入监控
GET /undetect/<job_id> 取消监控
GET /<job_id> 主状态/检测结果

GET /<job_id> 返回结构:

1
2
3
4
5
6
7
8
9
10
{
"job_id": "...",
"status": "active | slowdown | hanging | predicting | detected",
"hang_check": { "is_hanging": bool, "last_hang_ts": ..., "rca": ... },
"slowdown_check": {
"is_slowing_down": bool, "last_slowdown_iter": ..., "last_slowdown_ts": ...,
"straggler_prediction": { "straggler_rank": [...], "straggler_node": [...] }
},
"metrics": { "avg/max/min/last_iteration_duration": ..., "iteration_count": ..., "last_iteration": ... }
}

straggler_rank / straggler_node 即从 summary.txt 解析出的异常 rank/节点列表。

7.3 关键参数(main.py)

参数 默认 含义
--slowdown-threshold 0.25 慢 iter 判定:比中位数增长 >25%
--hang-threshold 60s 无新 iter 多久判 hang
--history-size 5 慢 iter 历史/触发所需连续慢 iter 数
--check-interval 30s 轮询间隔(模拟 5s)
--simulate / --write-speed 回放模式

八、落地效果与踩的坑

8.1 效果(评估口径)

  • 异常检测准确率目标 >90%:以运维后确认的故障/换卡样本为基准,检出异常 rank 与之一致的比例(评估口径,非代码内置数值)。
  • 覆盖多形态异常:温和偏移(慢簇均值高)、突刺(通信段 2× baseline)都能检出。
  • 30 秒级分析:触发后准实时出结果。
  • 真实价值:帮运维团队快速定位 GPU 故障/网络拥塞节点,把"靠人肉看日志找慢卡"从数小时压到分钟级。

8.2 落地中踩的坑(面试可讲)

  1. timer 0 值/覆盖不全:不是所有 rank 都打了某 subname(值 0 或缺失),早期没过滤被当"特别快"误判,后来加 subname 覆盖率检查 + original_total_subnames 门槛。
  2. 起步抖动:前 10 个 iter 训练 warmup 抖动大,直接聚类噪声高,改成丢弃前 10 iter。
  3. 算法参数敏感:DBSCAN 的 eps、KMeans 的 k/seed 对结果影响大,线上用基于 k-distance 膝点/分位数的自适应参数 + 网格调优选最优 silhouette,而非固定值。
  4. 归一化方式:z-score 受异常值拉动,改用 robust scale(median/MAD、IQR);判定"慢簇"用"均值最高"而非"离质心远",更贴合"相对落后"语义。
  5. PP 段 P2P 二义性:单看一段 send/recv 分不清 send/recv/网络,交叉验证规则 + PP group 一致性才定位准。
  6. 在线检测拉旧数据:batch 分析若不卡时间界会拉到 job 注册前的旧数据,commit 014d164--start/--end 修复。
  7. 代码重复定义:prediction.py 有方法重复定义两份(Python 取最后一份活性),改的时候要改活性那份,曾踩过"改了不生效"的坑。

九、系统架构总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
             Megatron 训练进程(每 step)
│ Timer 跨 rank all-gather → 采集层写 ClickHouse

┌──── ClickHouse megatron_metrics ────┐
│ metric_name=megatron_timers(_trace) │
│ subname=interval-time/forward-compute/…
└────────────────┬─────────────────────┘

┌──── 在线触发(monitor, 轮询 30s)────┐
│ 新 iter → interval-time 时长 │
│ 慢判定: vs 滚动中位数 >25% │
│ 连续 5 慢 iter → 启动分析子进程 │
└────────────────┬─────────────────────┘
│ --iterations 慢iter范围 --start/--end 时间界

┌──── 分析层(prediction.py, 多进程)─────┐
│ ProcessPoolExecutor: │
│ ┌─proc1─ KMeans ─────────────────┐ │
│ ├─proc2─ DBSCAN ────────────────┤ │
│ ├─proc3─ GMM ───────────────────┤ 各算法: per-(subname,rank) 四元组
│ ├─proc4─ Agglomerative ─────────┤ → robust scale → 2 簇(慢簇=均值最高)
│ ├─proc5─ Mean Shift ───────────┤ → 算法内跨 subname 加权多数
│ └─proc6─ Fuzzy C-Means ─────────┘ (self-attn-qkv 权重3)
│ ▼ │
│ 跨算法: 取被标 slow 次数最多的 rank │
│ PP≥2: 通信检测(≤4并行/iter) │
│ trace+baseline → send/recv拥塞│
│ apply_cross_validation_rules │
└────────────────┬──────────────────────┘

summary.txt: anomaly_ranks(聚类) + anomaly_nodes(聚类∪通信)


Flask API GET /<job_id> → straggler_rank / straggler_node

十、面试速答清单

Q1:这个项目解决什么问题?为什么重要?

大规模分布式训练的 straggler(慢卡)问题。straggler 不是死机(NCCL 不报错)但拖慢整个集群——木桶效应下 1 张慢 20% 的卡让上千卡吞吐掉 20%。它难发现(均值掩盖、不报错)、难定位(原因多样:硬件降频/ECC/拓扑/网卡/通信)。系统目标就是自动、准、快地检测+归因。

Q2:数据从哪来?为什么用它?

从 ClickHouse 的 megatron_metrics 表读 Megatron Timer 已落盘的指标(metric_name=megatron_timers,subname=interval-time/forward-compute/forward-send…,metric_values 是时长数组)。好处:粒度切到 forward-compute/backward-compute/通信段能直接看慢在哪段;跨 rank 可比;Timer 内部 cuda.synchronize 保证数据可信;落盘后检测与训练解耦,可在线/离线反复分析。

Q3:为什么用 6 种聚类 + 投票而不是单一算法?

没有单一算法通吃所有异常形态:KMeans/Agglomerative 抓质心偏离、DBSCAN/MeanShift 抓密度异常、GMM/Fuzzy 抓分布/隶属度偏离。选 6 个刻意覆盖距离/密度/分布/层次/峰值多类原理、盲区不重叠,ensemble 增益大。关键是六个算法判定准则统一规约为"均值最高的簇=slow",才能跨算法投票。

Q4:投票融合具体怎么做?有权重学习吗?

三层:①算法内跨 subname 加权多数(slow 票×2≥total,self-attention-qkv 权重3)+ original_total_subnames 门槛;②跨 6 算法取被标 slow 次数最多的 rank 为最终异常 rank;③cluster 异常 rank 与通信异常 worker 的节点取并集输出。没有加权打分、没有权重学习、没有标签回填重训——线上是固定计数取 max,简单可靠。(固定 ≥4/6 阈值只在被覆盖的死代码里,不生效。)

Q5:通信异常怎么定位到网络拥塞节点?

PP≥2 时对每个慢 iter 跑通信检测:基于 megatron_timers_trace(start/end 算时长)+ megatron_timers 的 send/recv baseline,worker 均值 > 2×baseline 判异常;再用 apply_cross_validation_rules 消解 send/recv 二义——send 异常⇒该 worker 发送拥塞或下游接收拥塞,结合 PP group 归属定端点;某 worker 所有通信段都慢→节点网络问题,只单段慢→那段链路问题;跨慢 iter 取并集。归因粒度到 worker/node,不到 NVSwitch/IB 口(那要外部拓扑系统)。

Q6:30 秒怎么做到的?

多进程并发——6 种聚类算法各一进程并行(ProcessPoolExecutor),通信检测每 iter ≤4 并行;sklearn 有 GIL 用进程不用线程。加向量化、subname 早筛、只分析慢 iter 区间不全量重算。瓶颈从"串行+全量"降到"算法并行+区间限定",分析阶段压到 30 秒内。触发靠 30s 轮询 + 连续 5 慢 iter 条件。

Q7:准确率 >90% 怎么算的?怎么保证不误报?

以运维后确认的故障/换卡样本为评估基准,检出异常 rank 与之一致的比例(目标 >90%,评估口径)。控误报靠:算法内 subname 加权多数 + original_total_subnames 门槛、跨算法取最高票、robust scale 抗异常值拉动、慢 iter 区间限定。落地踩过 0 值/覆盖不全误判、起步抖动、参数敏感等坑,分别靠 subname 覆盖检查、丢前 10 iter、自适应参数解决。

Q8:落地产出是什么?怎么用?

Flask 服务:GET /detect/<job_id> 注册监控,GET /<job_id> 查状态(status/slowdown_check/hang_check),返回 straggler_rank/straggler_node。运维拿这个去查那张卡/那根线。Helm chart 部署,ClickHouse 配置 env 注入。模拟器回放真实 job 供本地测试。


十一、一句话总结

Megatron Timer Predictor = 从 ClickHouse 读 Megatron Timer 全 rank 时序(已落盘)→ 在线轮询(30s)发现连续 5 个慢 iter(>25%)触发 batch 分析 → 6 种互补聚类算法(KMeans/DBSCAN/GMM/Agglomerative/MeanShift/Fuzzy)各把 rank 分 fast/slow(慢簇=均值最高)、算法内跨 subname 加权多数 + 跨算法取最高票 → PP≥2 时通信检测(trace+baseline 2×)+ send/recv 交叉验证定位网络拥塞 → 多进程并发把分析压到 30 秒 → Flask API 输出 straggler_rank/node。 它把"分布式训练谁慢、为什么慢"这件原本靠人肉看日志的事,变成了在线触发、自动、可归因的运维闭环。


参考资料

  • 项目:mt-detector(Megatron Timer Predicter)
  • Megatron-Core timers.py / p2p_communication.py(数据源与 PP 通信打点,背景见《Megatron Timers 源码精读》)
  • sklearn: KMeans / DBSCAN / GaussianMixture / AgglomerativeClustering / MeanShift;手写 Fuzzy C-Means
  • ClickHouse MergeTree + Array/Object 列式存储