Skip to content

ErrorAggregator

本页所有地址都适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(BuildID md5 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,x86-64)。其他版本会有所不同。

摘要

Megascale MegascaleErrorAggregator 是集群范围的根节点,会把逐主机失败报告风暴转换成一个经过分类的根因裁决。当多切片 TPU 作业挂起或崩溃时,每台主机的 libtpu 都会通过 MegaScaleTransport.ReportError gRPC 向协调器进程发送一个 MegaScaleRuntimeError。聚合器将这些报告汇聚成一个 RapidEyeErrorDigestProto — 这是一条自描述记录,会命名疑似罪魁主机、将失败分类为九个 Cause 值之一,并逐字嵌入时间上最早的错误,使人工或下游批处理作业无需重新推导即可读取裁决。

最接近的常见类比,是接到屏障上的崩溃报告聚合器(Sentry/Crashpad):它是一个按参与者身份索引的集中式单实例收集器,具有“是否所有人都已报告”的提前触发和 deadline 回退机制 — 但不同于通用崩溃接收器,它是一次性的。聚合器只在单次错误风暴期间存在:它在第一个入站 ReportError 上惰性创建,持续汇入直到 size() == expected_workers 或 300 ms 空闲 deadline(每次报告都会重新设置)到期,随后发出摘要并消亡。没有环形缓冲区、没有 TTL sweep,也没有跨重启持久化 — 设计假设是一次风暴,随后一次 drain。

本页涵盖线格式RapidEyeErrorDigestProto 以及嵌入的 MegaScaleRuntimeError)、作用域(协调器上的单个集群级实例,由扁平 DCN (slice_id, host_id) 主机身份馈入)、保留模型(内存中的 linked_hash_map、可选的磁盘 RapidEye 日志、无驱逐),以及去重 / 首个错误胜出逻辑(按 (worker, task) 的 last-write-wins 合并,加上粘性的首个错误槽)。ICI 级失败检测 — 芯片故障最初如何变成 MegaScaleRuntimeError — 属于下一层的 ICI 失败恢复。聚合器如何消费 NumHosts()(slice_id, host_id) 的集群元数据视角,属于 屏障错误用法

对于重新实现,契约是:

  • 线格式RapidEyeErrorDigestProto(17 个有效字段、13 个嵌套消息、一个 9 值 Cause 枚举)以及嵌入的逐主机 MegaScaleRuntimeError(13 个字段、两个枚举)。摘要会逐字嵌入首个错误,因此记录是自描述的。
  • 作用域模型 — 协调器上的单个集群级聚合器实例,惰性分配,以扁平 DCN 主机身份为键;worker 不运行自己的聚合器。
  • 去重 / 保留规则slice<S>-task<H>/<task_id> map 键、last-write-wins 合并、粘性的首个错误槽,以及不存在任何数量上限、TTL 或持久化。
  • 提前触发 / deadline drainsize() == GetClusterStatus().expected 会立即触发;否则,300 ms 空闲 deadline(每个入站 ReportError 都会重新设置)会在 300 ms 内没有新报告后,带着“missing workers”差异 drain 一个部分摘要。
xla::megascale::runtime::MegascaleErrorAggregator
对象大小0x2f0 = 752 字节,operator new(752, 16)
所有者 / 作用域单实例,由协调器的 ErrorReporter 拥有(+0x38,惰性);集群范围
线格式xla.megascale.runtime.RapidEyeErrorDigestProto(语法 editions
描述符protodesc_cold VA 0xc169340 处的 rapideye_logging.proto FileDescriptorProto(4 460 字节)
源 TUplatforms/xla/megascale/runtime/common/megascale_error_aggregator.cc
摄入AddError 0x1ccba940;gRPC 汇入 ErrorReporter::ReportError 0x1ccb6ea0
DrainProcessAndShutdown 0x1ccbaba0(幂等,一次性)
协调器 job_name"McJax"
Map 键slice<S>-task<H>/<task_id>linked_hash_map<string, MegaScaleRuntimeError>
持久化默认无;可选 RapidEyeLogger sink(--megascale_rapideye_error_digest_log_path

作用域 — 一个集群级实例

目的

聚合器的作用域是整个多切片作业,并在单点收集。运行中的作业恰好有一个 MegascaleErrorAggregator,它位于协调器进程上。Worker 主机运行聚合器;它们只运行主机本地的 DebugManager 收集器(一个独立的进程单例),并把错误上报到协调器。该模型是两个扁平层级 — 主机收集器 → 协调器根节点 — 没有逐切片中间聚合器。一个包含八个切片的 superpod 仍然只有一个覆盖所有切片的协调器聚合器。

入口点

text
ErrorReporter::ReportError (0x1ccb6ea0)        ── coordinator-side gRPC fan-in handler
  ├─ (first hit) operator new(752, 16)         ── lazy-allocate the one aggregator
  │    └─ MegascaleErrorAggregator(_, "McJax")  ── 1-arg ctor 0x1ccba4c0 (delegates to 4-arg 0x1ccba600)
  ├─ thread::AddCancellableAt(.., this+64)     ── arm 300 ms deadline → ProcessErrorDigest
  ├─ SubstituteAndAppendArray("slice$0-task$1") ── build the (slice_id, host_id) worker key
  ├─ MegascaleErrorAggregator::AddError        ── 0x1ccba940 — upsert into the map
  └─ if size() == GetClusterStatus().expected  ── early-fire
       └─ ErrorReporter::ProcessErrorDigest    ── 0x1ccb7140 — drain + classify + log
```text

### 算法

```c
function ErrorReporter_ReportError(req, resp):       // sub_1ccb6ea0
    TracedMutexLock guard(this->mu_)                  // ErrorReporter+0x48, kind=14
    if this->aggregator_ == null:                     // +0x38, lazy
        this->aggregator_ = new(752,16)
            MegascaleErrorAggregator("McJax")         // line 47
    deadline = clock_.Now() + 300ms                   // line 55 — rearmed each call
    AddCancellableAt(threadpool_, deadline,
                     [this]{ ProcessErrorDigest(); },
                     &this->deadline_handle_)          // line 59, &(this+64)
    invoke this->unicast_callback_(slice_id, host_id, *err)  // ErrorReporter+0x10, per-error
    worker_id = SubstituteAndAppendArray(
                  "slice$0-task$1", {slice_id, host_id}) // line 92 — DCN host identity
    err = req.error  (proto field 3); if null -> global empty MegaScaleRuntimeError
    aggregator_->AddError(worker_id, *err)             // line 102
    status = coordinator_->GetClusterStatus()          // vtable+64; expected-worker count
    if status.ok() and aggregator_->size() == status.expected:  // line 117 early-fire
        ProcessErrorDigest()                           // line 120 — bypass the deadline
    resp = ReportErrorResponse{}                       // empty ack

注意 — 提前触发计数是通过 GetClusterStatus()(协调器 vtable+64)读取的 cluster-status 预期数量,而不是原始常量。按照 集群元数据 视角,该预期数量等于 MultiSliceTopologyAndLocation::NumHosts() — 也就是 跨主机屏障 等待的同一个集群级主机数量。如果拓扑尚未发现(!status.ok()),摘要会以 "Unexpected error digest before topology discovery is completed:" 丢弃,而不是针对未知总体发出。

PJRT 并行表面

还存在第三个独立作用域:PJRT C-API 将同一个类暴露给更高层 XLA/JAX 客户端,不涉及任何网络。pjrt::(anonymous) 下的八个 shim(Create 0xe6bab80Delete 0xe6bac00AddError 0xe6bad60ProcessAndShutdown 0xe6bae40LogErrorDigest 0xe6baec0Size 0xe6baf20Active 0xe6baf80,以及 ErrorDigestDelete 0xe6bace0)允许客户端用调用方提供的 job_name Create 自己的聚合器,用调用方提供的 worker_id 和序列化的 MegaScaleRuntimeErrorParseFromString)驱动 AddError,并在进程内直接取回摘要。每个 shim 都由 ActualStructSizeIsGreaterOrEqual ABI 检查保护,对照文档化的 args-struct 大小(Create 0x20AddError 0x30ProcessAndShutdown/Size/LogErrorDigest 0x18Active 0x11Delete/ErrorDigestDelete 0x10)。PJRT 路径聚合器的作用域就是客户端馈入的任何内容;没有 RPC,也没有协调器参与。

函数映射

函数VA角色
ErrorReporter::ReportError0x1ccb6ea0gRPC 汇入;惰性分配、构造键、deadline、AddError、提前触发
ErrorReporter::ProcessErrorDigest0x1ccb7140drain + missing-worker 差异 + LogErrorDigest + 可选 abort
MegascaleErrorAggregator ctor(4 参数)0x1ccba600类初始化;存储 job_name/log_dir 字符串 + 预计算的 global_id/xid;读取 FLAGS_megascale_error_aggregation_enabledshutdown_
MegascaleErrorAggregator ctor(1 参数)0x1ccba4c0job_name 的委托器:读取 FLAGS_megascale_rapideye_error_digest_log_pathNewGlobalID()getenv("XM_XID"),然后调用 4 参数 ctor
PJRT ErrorAggregatorCreate0xe6bab80进程内聚合器(operator new(752,16)
PJRT ErrorAggregatorAddError0xe6bad60ParseFromString + AddError;megascale_extension.cc:321
PJRT ErrorAggregatorProcessAndShutdown0xe6bae40返回 448 字节(0x1C0)的 ErrorDigest

摄入和去重 — AddError

目的

AddError 是唯一摄入点。它在聚合器唯一互斥锁下串行化并发调用者,在粘性槽中捕获第一个非取消错误用于分类,并把每个报告 upsert 到按主机和任务身份索引的 linked_hash_map。两个错误是否“相同”仅取决于键是否匹配;带有匹配键的后到报告会覆盖较早值(last write wins),但插入顺序会保留,因此仍然可以在没有时间戳的情况下恢复第一个报告者。

算法

c
function AddError(worker_id, err):                    // sub_1ccba940
    mu_.lock()                                         // mutex at this+744 == +0x2e8
    if shutdown_:                                       // +0x30 — already drained / poisoned
        mu_.unlock(); return                            // no-op
    if !has_first_recorded_error_:                      // +0xe0
        if err.error_type == CANCELLED (3):             // line 33 — *((DWORD*)err + 18) == 3
            shutdown_ = true                            // teardown signal, store NOTHING
            mu_.unlock(); return
        first_recorded_error_.CopyFrom(err)             // +0x70, arena=null
        has_first_recorded_error_ = true
    key = StrCat(worker_id, "/", err.task_id)           // line 81; task_id = field 9, +0x58
    slot = errors_by_worker_launch_.LazyEmplaceInternal(key)  // line 91 — +0xe8
    slot.value.CopyFrom(err)                            // line 95 — last write wins
    mu_.unlock()
```text

> **怪癖 —** 首个 `CANCELLED` 错误是*拆除信号,而不是数据*。如果第一个 `AddError` 在粘性槽仍为空时携带 `error_type == CANCELLED (3)`,`AddError` 会翻转 `shutdown_ = true` 并返回,且不存储任何内容(第 33-36 行)。这会毒化聚合器:后续 `ProcessAndShutdown` 会返回空的缓存摘要。语义是“作业正在有意拆除 — 不要构造挂起摘要”。如果重新实现把 cancellation 当成另一个普通错误,那么每次干净关闭都会发出虚假的 `UNKNOWN_CAUSE` 摘要。

### 两段式键

Map 键是 `slice<S>-task<H>/<task_id>`,在两个位置构造:

- **外层(主机身份)** — `ErrorReporter::ReportError` 根据 `ReportErrorRequest.{slice_id, host_id}` 构造 `worker_id = "slice<slice_id>-task<host_id>"`,这是扁平 DCN 主机身份([集群元数据](fleet-metadata/host-identity.md))。
- **内层(主机内任务)** — `AddError` 追加 `"/" + err.task_id`,其中 `task_id` 是 `MegaScaleRuntimeError` 字段 9(偏移 `+0x58`),即该主机内的 Borg task / 进程索引。

因为提前触发检查会将总体限制为 `expected`,而键会折叠重复项,所以 map 自然只保存每个预期报告者的一条记录;重试 `ReportError` 的主机(例如 transport 重传)会合并到现有条目,而不会膨胀 `size()`。

### 函数映射

| 函数 | VA | 角色 |
|---|---|---|
| `MegascaleErrorAggregator::AddError` | `0x1ccba940` | 互斥锁、CANCELLED 短路、构造键、upsert |
| `MegascaleErrorAggregator::size` | `0x1ccba900` | 在互斥锁(+744)下读取 `linked_hash_map.size()` |
| `MegascaleErrorAggregator::active` | `0x1ccba8c0` | 在互斥锁(+744)下读取 `!shutdown_` |

---

## Drain 和分类 — `ProcessAndShutdown`

### 目的

`ProcessAndShutdown` 是一次性 drain,会把累积的 map 转换成最终的分类 `ErrorDigest`。它遍历每个已存错误,构造五个 `btree_set` 索引(罪魁 worker、故障网络链路、sequencer tag/pc 状态、按 tag/pc 的 worker 倒排索引、程序信息),在完整跨主机集合上运行事后 `Cause` 分类器,缓存结果,设置 `shutdown_`,并按值返回摘要。它是**幂等的**:第二次调用返回缓存副本。运行后,聚合器即死亡。

### 算法

```c
function ProcessAndShutdown() -> ErrorDigest:          // sub_1ccbaba0
    mu_.lock()
    if shutdown_:                                       // line ~817 fast path
        out = cached_digest_; mu_.unlock(); return out  // idempotent / poisoned-by-CANCELLED
    LOG(INFO) "Processing $0 errors", size()            // line 1134
    digest.local = WorkerInfo(job_name, global_id, xid) // +0x00/+0x60/+0x68 — identify the coordinator
    for (key, err) in errors_by_worker_launch_:          // arrival order preserved
        st = err.runtime_state.rapid_eye_info            // optional<RapidEyeInfo>
        note chip_config_name == "default"               // 8-byte compare @ 1ccbaf56
        populate workers_set, hlo_set, faulty_link_set,
                 sequencer-state set, workers-by-(tag,pc) index
        build OffloadedSparseCoreTagPc entries by HLO
    digest.cause = ClassifyCause(...)                    // see decision order below
    cached_digest_ = digest                              // +0x128
    shutdown_ = true
    mu_.unlock()
    return digest

Cause 判定顺序

分类器恰好发出一个 Cause。在 5320 字节函数体中观察到的判定顺序如下(相邻 cause 之间的精确平局打分尚未完全完成数据流提取 — MEDIUM):

text
any error_type == UNRECOVERABLE_ERROR (2)   -> UNRECOVERABLE_ERROR
any worker chip_id == -1                     -> PROGRAM_NOT_QUEUED
FaultyNetworkLink set non-empty              -> NETWORKING_ISSUE
"default" config DMA-stage stall pattern     -> DATA_INPUT_STALL
module fingerprints differ across workers    -> DIFFERENT_MODULE
fingerprints match but tag/pc layouts differ -> FINGERPRINT_MISMATCH
TC stall pattern present                     -> BAD_TPU_CHIP
SC stall pattern present                     -> BAD_SC_CHIP
otherwise                                    -> UNKNOWN_CAUSE
```text

> **注意 —** 每个 `Cause` 都是协调器基于完整集合计算的*集群裁决*;不存在主机本地 cause。`DIFFERENT_MODULE` 和 `FINGERPRINT_MISMATCH` 是仅有的真正集群作用域 cause — 没有比较多台主机的报告就无法判定,这也正是聚合器作用域为整个作业的原因。`NETWORKING_ISSUE` 是主机对作用域(它命名一条故障 `src→dst` 链路);芯片/停滞 cause 会命名单个罪魁主机,但仍然只在完整集合到达后发出,这样才能构造“following hosts”列表。按 [ICI 失败恢复](../ici/failure-recovery.md) 提升的芯片级 ICI fatal,一旦其 `FaultyNetworkLink` 落入集合,就会在这里表现为 `NETWORKING_ISSUE`。

### 消费方侧 — `LogErrorDigest`

`LogErrorDigest`(`0x213b42c0`,位于 `.text.unlikely`)是一个基于 `digest.cause` 的跳转表:每个 cause 都有一个内置 LOG(ERROR) 模板和一个 `FormatWorkers(...)` 主机列表。二进制中已验证存在的示例:`BAD_TPU_CHIP` → "Megascale detects a hang that is likely caused by bad TPU tensor core chips on the following hosts. Please remove the hosts from the fleet and restart the workload."(`0xa05862f`),后接 "  The host that have bad TPUs are: "(`0xa273611`);`NETWORKING_ISSUE` → "…likely caused by a networking issue. Please examine the underlying networking stack for the following hosts."(`0x9ffc2f2`);`UNKNOWN_CAUSE` → "Megascale detects a hang but cannot determine the root cause. Please inspect the full digest below."(`0x9fe02ae`),外加完整文本 proto。协调器的 LOG(ERROR) 轨迹是摘要的主要*人工*消费者。

### 函数映射

| 函数 | VA | 角色 |
|---|---|---|
| `MegascaleErrorAggregator::ProcessAndShutdown` | `0x1ccbaba0` | 构造/缓存摘要、分类、设置 `shutdown_`(幂等) |
| `MegascaleErrorAggregator::LogErrorDigest` | `0x213b42c0` | 按 cause 切换的 LOG(ERROR) 发射器 + `FormatWorkers` |
| `ErrorDigest::ToRapidEyeErrorDigestProto` | `0x1ccb8560` | `ErrorDigest` → 线格式 proto |
| `(anon)::FormatWorkers` | `0x1ccba060` | 渲染 `btree_set<WorkerAndCoreInfo>` 主机列表 |
| `(anon)::ToWorkerAndCoreInfoProto` | `0x1ccc4f80` | 罪魁 worker → proto 消息 |

---

## 线格式 — `RapidEyeErrorDigestProto`

### 目的

聚合器持久化的单个序列化产物是 `xla.megascale.runtime.RapidEyeErrorDigestProto`。它是一个包含 17 个有效字段的根消息,携带 13 个嵌套消息类型和一个 9 值 `Cause` 枚举,从 `protodesc_cold` VA `0xc169340` 处的 `rapideye_logging.proto` FileDescriptorProto(4 460 字节序列化)恢复而来。其语法为 `editions`(Google 的 proto2/3 后继)。关键在于,摘要会**逐字嵌入入站 `MegaScaleRuntimeError`**(字段 11,`first_recorded_error`),因此记录是自描述的 — 下游读取者无需单独的入站错误清单即可解释裁决。

### 根消息

| 字段 # | 名称 | 标签 | 类型 |
|--------:|------|-------|------|
| 1 | `potential_cause` | optional | enum `Cause` |
| 2 | `potential_culprit_workers` | repeated | `WorkerAndCoreInfo` |
| 4 | `workers_by_tpu_states` | repeated | `WorkersByTpuState` |
| 5 | `all_workers` | repeated | `WorkerInfo` |
| 7 | `timestamp_ns` | optional | int64 |
| 11 | `first_recorded_error` | optional | `MegaScaleRuntimeError`(逐字嵌入) |
| 12 | `error_messages` | repeated | `ErrorMessage` |
| 13 | `graph_consolidater_output` | optional | `GraphConsolidaterOutput` |
| 14 | `event_id` | optional | fixed64 |
| 15 | `xid` | optional | int64(来自 `getenv("XM_XID")`) |
| 16 | `executable_by_modules` | repeated | `ExecutableByModules` |
| 17 | `app_type` | optional | string |
| 18 | `faulty_network_links` | repeated | `FaultyNetworkLink` |
| 19 | `offloaded_sparse_core_tag_pc` | repeated | `OffloadedSparseCoreTagPc` |
| 20 | `build_info` | optional | `BuildInfo` |
| 21 | `launches` | repeated | `Launch` |
| 22 | `runtime_error_ids` | repeated | fixed64 |

字段号 3、6、8、9、10 是空洞 — 已退役字段。依赖项 `megascale_status.proto` 提供 `MegaScaleRuntimeError`;`actions.proto` 提供嵌入在 `BottleneckNode.action` 中的 `MegaScaleAction`。

### `Cause` 枚举

九个值,密集 `0..8`;名称存在于 `protodesc_cold` 表中(通过 `libtpu.so` 字符串扫描验证了 `FINGERPRINT_MISMATCH`、`PROGRAM_NOT_QUEUED` 等):

| 值 | 名称 | 含义 | 证据局部性 |
|------:|------|---------|---|
| 0 | `UNKNOWN_CAUSE` | 分类器耗尽,未锁定根因 | 集群(耗尽) |
| 1 | `BAD_TPU_CHIP` | 挂起很可能来自故障 tensor-core 芯片 | 逐主机芯片(命名主机) |
| 2 | `FINGERPRINT_MISMATCH` | worker 间的 HLO 编译输出不一致 | **集群范围**(需要比较) |
| 3 | `DATA_INPUT_STALL` | 数据流水线饥饿 | 逐主机(数据流水线) |
| 4 | `UNRECOVERABLE_ERROR` | 至少一条报告是 `UNRECOVERABLE` | 逐主机(一台主机足够) |
| 5 | `DIFFERENT_MODULE` | worker 运行了不匹配的 HLO module | **集群范围**(需要比较) |
| 6 | `NETWORKING_ISSUE` | 检测到 ICI/DCN 链路失败 | 主机对(`src→dst`) |
| 7 | `BAD_SC_CHIP` | 挂起很可能来自故障 sparse-core 芯片 | 逐主机芯片(命名主机) |
| 8 | `PROGRAM_NOT_QUEUED` | 某个 worker 从未到达 launch 队列(`chip_id == -1`) | 逐主机 |

### 嵌套消息树

下面按形状而非穷举转储描述 13 个嵌套消息;其中诊断关键项如下:

```text
RapidEyeErrorDigestProto
├─ WorkerAndCoreInfo {1 worker_id, 2 host_name, 3 CoreInfo?}     ← culprit naming
│    └─ CoreInfo {1 chip_id, 2 core_idx, 3 physical_location}    ← only physical chip name in the layer
├─ WorkerInfo {1 worker_id, 2 host_name, 3 cloud_instance_id}
├─ WorkersByTpuState {1 TpuState, 2 [WorkerAndCoreInfo]}         ← sequencer-state grouping
│    └─ TpuState {1 tag, 2 pc, 3 hlo_name, 4 computation_name, 5 [ProgramCompletion]}
├─ Launch {1 launch_id, 2 [WorkersByTpuState], 3 [Executable]}
├─ Executable {1 fingerprint, 2 [Launch], 3 module_name, 4 sample_worker}
├─ ExecutableByModules {1 module_name, 2 [Executable]}
├─ FaultyNetworkLink {1 src_worker:WorkerInfo, 2 dst_worker:WorkerInfo}  ← host-granular fault
├─ GraphConsolidaterOutput {1 problematic_workers, 4 workers_with_missing_graphs,
│                            5 [BottleneckNode], 6 [FaultyNetworkLink]}
├─ ErrorMessage {1 worker:WorkerAndCoreInfo, 2 error_message}
├─ OffloadedSparseCoreTagPc {1 hlo, 2 [SparseCoreTagPcToWorkers]}
└─ BuildInfo {1 changelist, 2 baseline_changelist, 3 build_timestamp_secs,
              4 build_label, 5 build_target, 6 build_id}

注意 — WorkerAndCoreInfo 内的 CoreInfo {chip_id, core_idx, physical_location} 是跨主机层中唯一以物理方式命名芯片的位置,并且它是诊断输出,而不是启动库存的一部分。FaultyNetworkLink 命名的是主机WorkerInfo),不是 ICI 链路。因此,当 BAD_TPU_CHIP/BAD_SC_CHIP 裁决命名一台主机后,operator 必须继续调用 tpunetdGetCoreDump(CORE_DUMP_CHIP_DUMP) 来拉取芯片级寄存器状态 — 摘要知道哪台主机,tpunetd 知道哪颗芯片。参见 屏障错误用法

嵌入的 MegaScaleRuntimeError(字段 11 / ReportErrorRequest 字段 3)

逐主机报告,来源 megascale_status.proto,语法 editions

字段 #名称类型备注
1error_typeenum ErrorType驱动 CANCELLED 短路和 HANG abort
2error_messagestring
3estimated_start_time_msuint64
4runtime_stateRuntimeStateSummary携带分类器使用的 RapidEyeInfo
5triggering_launch_idint32
6hostnamestring
7userstring
8job_namestring
9task_idint32内层去重键段(+0x58
10task_uidint64
11event_idfixed64相关 ID,不是去重键
12unrecoverable_error_typeenum UnrecoverableErrorType
13runtime_error_idfixed64相关 ID → 根字段 22
text
enum ErrorType { NO_ERROR=0; HANG_DETECTED=1; UNRECOVERABLE_ERROR=2; CANCELLED=3; }
enum UnrecoverableErrorType { UNCLASSIFIED=0; HOST_TO_DEVICE_ERROR=1; DEVICE_TO_HOST_ERROR=2; }
```text

`error_type` 是*逐报告*类别;根 `Cause` 是*跨主机裁决*。它们是不同轴线 — 一组 `HANG_DETECTED` 报告可以根据逐主机 `RapidEyeInfo` 状态被分类为九个 cause 中的任何一个。`event_id` 和 `runtime_error_id` 是用于跨集群相关(OutKast / RapidEye)的每次收集唯一标识符,明确**不是**去重键。

---

## 保留

### 目的

保留策略有意保持最小:只保存错误到足以 drain 一次风暴。主存储是内存中的 `linked_hash_map`;可选的 `RapidEyeLogger` sink 会将序列化摘要写入磁盘;进程重启后没有任何内容保留。

### 模型

```text
default (no flags):   in-memory linked_hash_map  →  LOG(ERROR) on drain  →  freed
                       (NullRapidEyeLogger drops all WriteRecord calls)

--megascale_rapideye_error_digest_log_path=<p>:
                       in-memory map  →  ProcessAndShutdown  →
                       CloudRapidEyeLogger.WriteRecord(RapidEyeErrorDigestProto)  →  <p>
                       (scheme picks backend: gs:// → GCS, /cns/ → CNS, local → Posix)
方面行为
TTL无 — 一直存在到 ProcessAndShutdown
数量上限无 — linked_hash_map 增长到每个唯一键一条记录
字节上限仅逐错误,在主机层(截断,绝不丢弃)
Ring / LRU
跨重启无 — ctor 不加载;日志只写
Drain一次性;ProcessAndShutdown 清空并消亡

怪癖 — linked_hash_map按设计无界的。没有 LRU、没有环形缓冲区、没有驱逐 sweep — 以前存在的、用于限制已记录错误数量的 num_workers_to_log_errors flag 已退役(可通过 _GLOBAL__sub_I_megascale_error_aggregator.cc 0x213560a0 中的 absl::flags_internal::Retire 看到)。Map 的边界来自去重键折叠逐主机重复项,以及在每个预期 worker 都已报告的瞬间 drain 它的 size() == expected 提前触发。如果重新实现省略提前触发,只依赖 300 ms 空闲 deadline,那么风暴会一直留在内存中,直到 300 ms 内没有新报告;对于一个 1 000 芯片 pod,最坏情况是每个 worker 一个约 112 字节的 MegaScaleRuntimeError 加一个 24 字节字符串,约 150 kB 加消息载荷 — 有界,但只是因为 map 是每 worker 一条记录,而不是因为有任何驱逐。

粘性的首个错误槽

+0x70 处的 first_recorded_error_ 只设置一次,在第一个非 CANCELLED AddError 上设置,之后绝不覆盖。它会成为 RapidEyeErrorDigestProto.first_recorded_error(字段 11),并作为 abort 判定的基础(abort_on_hang 取决于 first_recorded_error.error_type == HANG_DETECTED)。因此,聚合器同时保留根因(首个错误,粘性)和完整清单(完整 map,last-write-wins)— 裁决采用 first-error-wins,取证记录则保留所有内容。

函数映射

函数VA角色
MegascaleErrorAggregator::WriteErrorDigestToStorage0x1ccb83e0空路径跳过;否则 RapidEyeLogger::Create + WriteRecord
RapidEyeLogger::Create0x20511aa0从 options 得到 StatusOr<unique_ptr<RapidEyeLogger>>
NullRapidEyeLogger::WriteRecord0x20511d40默认 sink — 丢弃所有写入
CloudRapidEyeLogger::WriteRecord0x20511e00tsl::FileSystem 支撑的写入

类布局 — MegascaleErrorAggregator(752 字节)

752 字节(0x2f0)堆对象,16 字节对齐分配。与作用域相关的偏移:

偏移类型字段
+0x00std::stringjob_name(协调器上为 "McJax"
+0x18std::stringrapideye_log_dir(FLAG 副本)
+0x30boolshutdown_active() = !shutdown_
+0x50AnyInvocable<void(int,int,MegaScaleRuntimeError const&)>逐错误 unicast callback
+0x60int64_tglobal_id_util::random::NewGlobalID()
+0x68int64_txid_getenv("XM_XID")
+0x70MegaScaleRuntimeError0x70first_recorded_error_(粘性)
+0xe0boolhas_first_recorded_error_
+0xe8linked_hash_map<string, MegaScaleRuntimeError>errors_by_worker_launch_(存储)
+0x128..+0x2e7ErrorDigest0x1c0 = 448 B)cached_digest_(drain 时填充);一直跨到 mu_。独立 ErrorDigest 已在 PJRT ProcessAndShutdown 0xe6bae40 中确认 operator new(0x1C0)
+0x1a8..+0x2b05 个 btree_set / btree_mapProcessAndShutdown 期间构造的索引;是 cached_digest_ 内部子字段(不是独立聚合器成员)
+0x2e8absl::Mutexmu_唯一锁(this+744

注意 — +0x2e8 处的一个 absl::Mutex 保护每个 public 方法 — AddErrorProcessAndShutdownsizeactive 都会锁定 this+744。没有无锁路径。协调器的 ErrorReporter 添加了一个单独的 TracedMutex(kind=14),因此惰性分配、deadline 设置、AddError 和提前触发检查相对于并发 deadline 触发是原子的;deadline fiber 会重新获取该互斥锁,而 ProcessErrorDigest0x1ccb7140)处的 if (!aggregator) return 幂等保护会处理与 drain 竞争的晚到报告。


告警和计数器

两个协调器层 flag 在摘要发出后控制进程 abort(abort 会进入正常 Borg/GKE crash-loop 重启流水线 — 两个 flag 都不会直接分页):

Flag默认值效果
--megascale_error_reporter_abort_on_hangfalse如果 first_recorded_error.error_type == HANG_DETECTED,则 LOG(FATAL)("Aborting the coordinator after collecting errors from all workers…", 0xa045d37
--megascale_error_reporter_abort_on_errorfalse摘要之后对任何已报告错误 LOG(FATAL)
--megascale_error_aggregation_enabledtrue总开关;false → ctor 设置 shutdown_,因此每个 AddError 都是 no-op
--megascale_rapideye_outkast_culprit_hostsfalse将罪魁主机列表转发给 OutKast,用于跨集群相关

streamz 计数器 /platforms/xla/megascale/runtime/error_count(也包括 /platforms/xla/megascale/error_countplatforms.xla.megascale.error_count)在每次 AddError 时递增;/platforms/xla/megascale/runtime/aggregated_error 在每次 ProcessAndShutdown 时递增一次,并按 (error_type, cause) 加键(metadata 字符串逐字带有拼写错误:"The number of error reported by the MXLA errore aggregator.")。两个不同的匿名命名空间 BSS 槽支撑逐实例 error_count,分别位于 0x2257b080(gRPC 路径)和 0x22589a40(host/PJRT 路径)。


聚合错误如何浮回 PJRT

摘要通过四条路径到达 PJRT 客户端,每条路径都是不同消费者:

  1. 协调器 LOG(ERROR) 轨迹LogErrorDigest 发出 cause 裁决和逐 cause 诊断文本;这是读取协调器日志的主要人工消费者。
  2. 进程内 unicast callback — 传给 ErrorReporter ctor 的 AnyInvocable<void(int slice, int host, MegaScaleRuntimeError const&)> 会针对每个单独错误触发(不是针对每个摘要);典型消费者是运行时错误处理器,它将失败传播回 PJRT executor(JAX/TF/XLA),因此用户代码会看到失败的计算,而不是静默挂起。
  3. MegascaleDebugService.ConsumeError gRPCConsumeErrorRequest{}(空)→ ConsumeErrorResponse{error: MegaScaleRuntimeError};调试器使用的“给我下一个错误”检查路径。由 DebugManager::ConsumeRuntimeError0x204d8f20)支撑,后者是 drain-once:读取 pending 槽会重置它。
  4. RapidEye 磁盘归档(可选)— CloudRapidEyeLogger 写入序列化 proto,供下游批处理作业使用(事故分析、sawmill 摄入、OutKast 打分)。

PJRT C-API shim(PJRT_Megascale_ErrorAggregator_*)是第五个表面:JAX/XLA 进程直接驱动 AddError/ProcessAndShutdown,并在进程内读取 448 字节 ErrorDigest,不涉及协调器。


未追踪 / 待解决

  • 当两个相邻 cause 同时触发时,ProcessAndShutdown 内的精确平局打分(例如 BAD_TPU_CHIP vs FINGERPRINT_MISMATCH)— 5320 字节函数体已恢复,但完整数据流提取仍待完成(MEDIUM)。
  • --max_rapid_eye_runtime_error_size_bytes 的数值默认值 — 这是一个 int flag,其默认值位于 .data 中且没有 generator fn;需要 .data 交叉引用(LOW)。
  • RuntimeStateSummary / RapidEyeInfo.chip_config_name schema — 分类器依赖 "default" 字符串(0x1ccbaf56 处 8 字节比较),但上游 RapidEyeInfo schema 只部分解码({1 telemetry, 2 tpu_core_hlos, 3 is_truncated, 6 slice_id, 7 task_id, 10 chip_config_name, …})。
  • megascale_xmon_gateway_address 消费者(除 LOG/RapidEye/callback 外可能的第三条 push 路径)— 该 flag 在此 TU 中注册,但其读取点尚未追踪(LOW)。
  • BottleneckNode.action 内的 MegaScaleAction 线格式(包含 7+ 个备选项的 proto oneof)— 目前只编目了顶层消息图。

交叉引用

  • Megascale 概览 — 本节开篇;聚合器在多主机/DCN runtime 中的位置。
  • ICI 失败恢复 — 下一层;芯片/链路故障在到达 AddError 前如何变成 MegaScaleRuntimeError
  • 屏障和错误聚合集群元数据用法 — 聚合器如何只消费扁平 DCN 层(NumHosts()(slice_id, host_id));主机与芯片之间的取证交接。
  • 跨主机屏障 — 兄弟集群协调器;共享 (slice_id, host_id) 身份和 NumHosts() 预期数量来源。
  • tpunetd 协议GetCoreDump(CORE_DUMP_CHIP_DUMP),即 BAD_TPU_CHIP/BAD_SC_CHIP 裁决命名主机后的芯片级后续操作。
  • 主机身份 — 成为外层去重键段的 (slice_id, host_id) DCN 身份。