Megatron-LM 训练可观测性指标(Metrics)实战指南:megatron.training.* 命名空间、Prometheus 导出与自定义指标扩展
【免费下载链接】Megatron-LMOngoing research training transformer models at scale项目地址: https://gitcode.com/GitHub_Trending/me/Megatron-LM
本篇技术指南以 Megatron-LM 的训练指标(Metrics)体系为主线,完整讲解megatron.training.*指标命名空间下的 8 个训练指标、其基于 OpenTelemetry(OTel)的发射机制与导出路径,并给出 Prometheus 名称映射、跨 run 过滤查询与"指标 vs Span 属性"的常见陷阱辨析。阅读完本文后,你可以直接在 Grafana/Jaeger 等 OTLP 兼容后端中搭建训练监控面板,并能依照仓库内的现有实现模式为 Megatron 添加自定义指标。
Megatron-LM 通过nemo-lens库接入 OpenTelemetry,在训练框架边界发出 traces,同时为 loss、吞吐量、梯度范数等关键训练信号发出metrics。指标部分独立成文,统一收拢在megatron.training.*项目专属命名空间下,相关实现集中在 megatron/core/telemetry/training_metrics.py,观测性总览见 docs/user-guide/observability/index.md。
指标命名空间与发射范围
Megatron 的训练指标全部归属megatron.training.*命名空间。与 span 命名(megatron.*)保持一致的项目专属风格不同,训练指标之所以使用独立命名空间,是因为训练过程没有 OTel 官方标准语义约定(semantic convention),因此由项目自行定义一套稳定的指标名称与单位。
两个关键约束需要先明确:
- 仅在导出 rank 上发射:所有指标只由
is_exporting = True的 rank 创建并记录,非导出 rank 不会创建任何 metric instrument(指标仪器)。这在分布式训练中避免了每个 rank 各自上报一份相同数据造成的冗余与后端压力。 - 发射节奏与
--log-interval对齐:指标记录与 TensorBoard、W&B logger 的日志节奏完全一致,即每--log-interval次迭代记录一次,保证三个观测通道看到的是同一时间点的训练状态。
训练指标清单(megatron.training.*)
以下是 Megatron 当前发出的全部训练指标,逐项对应 training_metrics.py 中的 instrument 定义:
| Metric | 类型 | 单位 | 描述 |
|---|---|---|---|
megatron.training.step_duration_ms | Histogram | ms | 单个训练 step 的耗时(毫秒) |
megatron.training.loss | Gauge | — | 训练 loss(每个 log interval 的最新值) |
megatron.training.throughput_tflops | Gauge | TFLOP/s | 训练吞吐,单位 TFLOP/s/GPU |
megatron.training.tokens_per_sec | Gauge | tokens/s | 训练吞吐,单位 tokens/s |
megatron.training.grad_norm | Gauge | — | 全局梯度范数 |
megatron.training.skipped_iters | Counter | — | 被跳过的优化器 step 数(loss 为 NaN/inf 时) |
megatron.training.learning_rate | Gauge | — | 当前学习率 |
megatron.training.memory_allocated_gb | Gauge | GB | 峰值 GPU 显存分配量 |
需要特别注意的是类型语义:loss、throughput、grad norm、learning rate 全部使用Gauge(瞬时值)而非 Histogram。这保证了导出到 Prometheus 后得到的是语义正确的gauge类型——这类指标每个 log interval 都会变化,用瞬态值表达最合适;而 step 耗时这类分布型指标才用 Histogram,便于统计 P50/P95 等分位数。
发射机制与源码实现
指标的发射点位于 megatron/training/training.py,训练循环在满足iteration % args.log_interval == 0(或首个 iteration)时进入日志分支,并在末尾调用record_training_metrics():
# megatron/training/training.py(约 3808-3829 行) if _otel_telemetry_log is not None and _otel_telemetry_log.is_exporting: from megatron.core.telemetry.training_metrics import record_training_metrics _avg_loss = _otel_loss_snapshot _tokens_per_sec = ( batch_size * args.seq_length / elapsed_time_per_iteration if elapsed_time_per_iteration > 0 else None ) _mem_gb = torch.cuda.max_memory_allocated() / (1024 ** 3) if torch.cuda.is_available() else None record_training_metrics( meter=_otel_telemetry_log.meter, step_duration_ms=elapsed_time_per_iteration * 1000.0, loss=_avg_loss, throughput_tflops=throughput if args.log_throughput else None, grad_norm=grad_norm, learning_rate=learning_rate, skipped_iters=_otel_skipped_iters_snapshot, tokens_per_sec=_tokens_per_sec, memory_allocated_gb=_mem_gb, )从这段代码可以读出几个实现细节:
- 快照时序:loss 与 skipped-iters 取自上方"快照"(snapshot),即累加器重置之前的值,其余指标在调用时仍是实时值——保证记录的是完整 log interval 的聚合结果。
tokens_per_sec的推算:由batch_size * args.seq_length / elapsed_time_per_iteration直接计算,且当elapsed_time_per_iteration <= 0时安全地传None。throughput_tflops的条件性:仅当args.log_throughput开启时才上报,避免无意义的额外开销。memory_allocated_gb的平台守卫:仅在torch.cuda.is_available()时上报,非 CUDA 环境下传None。
指标模块的内部结构
training_metrics.py 是整个指标体系的"自包含副本"——它是从 nemo.lens 的指标记录逻辑复制而来,使 Megatron-LM 可以不依赖 nemo-lens 硬依赖就发出训练指标。其内部由三部分组成:
- 名称常量:文件头部定义了 8 个
MEGATRON_TRAINING_*字符串常量(与 nemo.lens 的 semconv 对齐),保证名称在代码中"单一来源"。 _get_training_instruments(meter):按需创建并缓存 instrument。使用weakref.WeakKeyDictionary以meter为 key 缓存,避免 re-init 时发生泄漏;首次调用时通过meter.create_histogram / create_gauge / create_counter创建 8 个 instrument。record_training_metrics(meter, **kwargs):公共记录入口,所有参数可选,None值被静默跳过。例如skipped_iters仅在> 0时才调用add();grad_norm会被显式float()转换。
同时,整个模块对 OTel 缺失保持优雅降级:
- 若
opentelemetry未安装,metrics = None,record_training_metrics()直接成为 no-op; - 若 instrument 创建失败,只记录一条 warning 日志后返回,不影响训练主流程;
- 在 megatron/core/telemetry/fallbacks.py 中,当 nemo-lens 未安装时还提供了
trace_fn、managed_span、span_cm等 no-op stub,保证整个 telemetry 层在缺少依赖时"一切照常"。
这种设计使得指标发射对训练性能与稳定性零侵入,可以放心在生产环境常开。
Prometheus 指标名称与单元后缀
OTel SDK 导出到 Prometheus 时,可能会根据 instrument 的 unit 追加后缀,因此实际抓取到的名称与 OTel instrument 名不完全一致。官方示例映射如下:
| OTel instrument 名称 | Prometheus 指标(示例) |
|---|---|
megatron.training.loss | megatron_training_loss(Gauge) |
megatron.training.step_duration_ms | megatron_training_step_duration_ms_milliseconds |
megatron.training.throughput_tflops | megatron_training_throughput_tflops(Gauge) |
megatron.training.tokens_per_sec | megatron_training_tokens_per_sec(Gauge) |
megatron.training.skipped_iters | megatron_training_skipped_iters_total |
注意两个转换规律:
- 命名空间中的点号(
.)替换为下划线(_); - 带 unit 的 Histogram 会追加单位后缀(如
_milliseconds),Counter 会追加_total(Prometheus 计数器命名惯例)。
正因后缀随 SDK 版本可能有差异,官方仪表板(dashboard)在写 PromQL 时普遍使用正则匹配,例如{__name__=~"megatron_training_loss.*"},这样无论 SDK 是否追加后缀都能命中。如果你在面板上看到 "No data",推荐用Explore → Prometheus → Metrics browser浏览当前 SDK 版本实际暴露的完整指标名,再据此调整查询。
跨 run 过滤与多 run 对比
每个训练 run 都会被自动分配唯一的nemo.run.id资源属性,并且该属性会附着在每一个数据点上。这意味着你可以在 Grafana 中用它做两件事:
隔离单个 run:
{nemo_run_id="<id>", __name__=~"megatron_training_.*"}对比两个 run:
{nemo_run_id=~"run-a|run-b", __name__="megatron_training_loss"}run id 的生成遵循固定的优先级顺序(详见 docs/user-guide/observability/configuration.md):NEMO_LENS_RUN_ID环境变量(显式指定,优先级最高)→SLURM_JOB_ID(SLURM 集群自动检测)→ 自动生成的 12 字符 UUID(兜底)。同一分布式任务的所有 rank 共享同一个run_id,同时每个 rank 拥有唯一的service.instance.id(格式为{run_id}-rank{rank}),因此你既能按 run 聚合整轮训练,也能下钻到具体 rank 排查单卡异常。
Metric vs Span 属性:一个反复出现的陷阱
在可观测性设计中,最容易出错的地方是把"应该是指标的量"放到 span 属性上。官方文档明确给出的原则是:
- Loss 每个 iteration 都在变化,属于时间序列数据,应当记录到
megatron.training.loss指标上。Prometheus 会保存每个值,Grafana 可将其绘制成连续曲线,用于观察训练收敛趋势; - Iteration 编号是某个特定 span 的分类上下文,应当放在
megatron.iterationspan 属性上,由 Jaeger 这类 trace 系统用于过滤和检索。
反过来做则两头受损:
- 把 loss 放到 span 属性上,Jaeger 中无法形成有用的时间序列——这只是一堆零散的调试数据;
- 把 iteration 放到 metric 的 label 上,每个 iteration 都会产生一条独立的 metric 序列——无界基数爆炸(unbounded cardinality explosion),会迅速拖垮 Prometheus 的存储与查询性能。
一句话总结:会随时间连续变化的量用 Metric,用于描述"某一次事件是什么"的分类信息用 Span/Resource 属性。(关于三者的完整辨析,可参考 nemo-lens 文档中 "Metric vs span attribute vs resource attribute" 一节。)
添加自定义指标:三步模板
如果你需要为 Megatron 增加项目专属指标,官方推荐在megatron/core/telemetry/目录下新增文件,并严格仿照 training_metrics.py 的既有模式。模板步骤如下:
- 声明
WeakKeyDictionary用于 per-Meter 的 instrument 缓存(避免重复创建与内存泄漏); - 实现
_get_<domain>_instruments(meter):创建并缓存该域的全部 instrument,首次调用时通过meter.create_gauge / create_histogram / create_counter创建; - 实现
record_<domain>_metrics(meter, **kwargs):所有参数可选,只记录非None的值(与现有record_training_metrics的 None-skipping 行为保持一致)。
命名空间方面遵循以下约定:
- 应用/项目专属指标使用
megatron.<subsystem>.<metric>命名,与训练指标同风格; - 共享的
dl.*与gen_ai.*命名空间保留给跨消费者使用或符合行业标准的指标,不要随意占用。
仓库中已有的nemo.lens.instruments.inference可作为模板参考。按此模式新增的文件会被 megatron/core/telemetry/init.py 统一组织:安装 nemo-lens 时使用真实实现,未安装时由fallbacks提供 no-op 兜底,从而保证新指标与既有体系无缝融合。
快速启用与验证
要把整套指标真正跑起来,最小配置只需三个环境变量(完整配置见 docs/user-guide/observability/configuration.md):
export MEGATRON_OTEL_ENABLED=1 export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 export MEGATRON_OTEL_SPAN_GROUPS=default # 粗粒度 span,生产安全 torchrun --nproc_per_node=8 pretrain_gpt.py ...MEGATRON_OTEL_ENABLED是总开关(默认0),必须置1才激活;它是NEMO_LENS_ENABLED的别名,二者指向同一底层配置。- 指标上报通过
MEGATRON_OTEL_METRICS_ENABLED(默认1)独立控制,可单独关闭而保留 traces。 - 默认只有最后一个 rank导出(
single_rank策略,MEGATRON_OTEL_EXPORT_RANK=-1表示最后一个 rank)。如需多 rank 上报,可切换all_ranks、sampled、first_rank_per_node等策略。 - 本地调试时可将
MEGATRON_OTEL_EXPORTER=console,span 与指标直接打印到 stdout,无需任何后端。
开启后,配合 Prometheus + Grafana 即可对 loss、吞吐(TFLOP/s/GPU 与 tokens/s)、梯度范数、学习率、显存峰值进行实时监控,并通过nemo.run.id在多次实验间做横向对比——这套指标体系与 docs/user-guide/observability/span-groups.md 中的 trace 体系共同构成了 Megatron 训练任务完整的可观测性闭环。
【免费下载链接】Megatron-LMOngoing research training transformer models at scale项目地址: https://gitcode.com/GitHub_Trending/me/Megatron-LM
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考