Skip to content

ICI 故障检测、降级轴回退与恢复

本页所有地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,未 strip)。其他版本会不同。.text VMA 等于文件偏移;所有地址均为 VMA。

摘要

当 TPU slice 上的 Inter-Chip Interconnect (ICI) 链路行为异常时,libtpu 不会动态重路由流量。片上路由表只在 bring-up 时安装一次,并且不会因运行中的链路掉线而重写。libtpu 确实具备的是一个四层检测栈:固件状态读取、由中断驱动的状态变更处理器、带重试率预算的按需健康谓词,以及跨主机错误报告广播 RPC;这些共同给出二元的软/硬判定。软故障会在带内恢复:对受影响链路重新运行 bring-up 序列(LinksDownReset → 重新启用 → wait-for-data-link-up → GTC 重新同步)。硬故障会通过 FailDevice 级联拆除设备,并以带外方式升级到 slice master 和 Megascale 聚合器;恢复单元是整个 slice 的重启。

二进制中命名的两个 slice-builder 机制锚定了跨主机流程,本文首次以反编译深度记录它们。Master::ControlIciErrorReport (0x1fbc0d00) 是 master 的扇出广播:它打上截止时间(absl::Now() + timeout),附加 IsLimitedIciRouting 标志,并调用每个 worker 的 SliceBuilderWorkerService stub;Worker::ControlIciErrorReport (0x1fc40d80) 在共享互斥锁下遍历其 per-peer stub 列表,为每个 peer 构造一个 closure,并通过 RunInParallelThreadPool 上并行运行。配套的 Master::DetectRoutingTableDeadlock (0x1fbbed60) 是在 slice 初始化时运行的静态死锁检查:它重新生成每个芯片的 superpod::routing::RoutingTableSet,交给 RoutingTableAnalyzer,并在检测到通道依赖环时返回带有 bug 提交字符串的 FAILED_PRECONDITION;它是在 slice 运行之前验证所选(可能是弹性的)路由表无死锁,而不是检测运行时挂起。

熟悉 MPI 容错 collectives 和 credit/channel-dependency 死锁理论的读者已经具备本文框架。降级轴的 proto ingest(错误链路朝向如何变成三个布尔值,从 collective ring 中折叠掉一个 torus 轴)见 降级轴摄取弹性路由缓存弹性 Route-Cache 去重;链路 bring-up 见 链路 Bring-Up 序列。本文负责说明检测栈、软/硬分类器、降级回退选择、路由表死锁检测器,以及恢复/升级流程

对于重新实现,契约如下:

  • 检测栈IsLinkUp(固件状态读取,端口 < 5 边界)、HandleIciLinkStatusChange(中断驱动,AllLinksUp 比较 → Link error.)、IsHealthy(链路 up 且重试率在预算内),以及 UpdateAndGetRetriesPerMinute(60 秒滑动窗口 deque)。
  • 软/硬分流FatalErrorCheck 读取两个固件 fatal 位(vtable +0x20、+0x28)→ Fatal error occurred. Data links will go down.;软路径重新运行 bring-up;硬路径向 FailDevice 级联发出延迟失败信号。
  • 降级回退 — 路由是静态的;弹性是感知故障的表选择,在 super-pod 故障对称性下由 UseResilientAlgorithmTwistedTorus 门控,并用三个降级字节携带错误链路轴。
  • 死锁检测器DetectRoutingTableDeadlock → 在 per-chip RoutingTableSet 通道依赖图上运行 RoutingTableAnalyzer::DetectPotentialDeadlock,于 slice 初始化时执行。
  • 升级级联 — per-chip 延迟失败 → FailDeviceSliceFailureType::CHIP_DRIVER_ERRORControlIciErrorReport 广播 / FailSlice → tpunetd session-failing → Megascale NETWORKING_ISSUE
固件链路状态读取jxc::IciControl::IsLinkUp(int) @ 0xe7afe80(per-port 位 (state>>link)&1,端口 < 5
中断状态处理器jxc::IciControl::HandleIciLinkStatusChange(Span<int>,bool) @ 0x21381e40
所有链路 up 比较jxc::IciControl::AllLinksUp(Span<int>,bool) @ 0xe7b0200
健康谓词jxc::IciControl::IsHealthy(int) @ 0xe7af720(4 端口循环)
重试率窗口jxc::IciControl::UpdateAndGetRetriesPerMinute(RetryHistory*,long) @ 0xe7af540(60 s deque)
Fatal 门控ici::SliceConfiguration::FatalErrorCheck() @ 0x1fdb6720(fw 位 +0x20/+0x28)
芯片健康汇总ici::SliceConfiguration::GetChipHealth() @ 0x1fdb6320
链路 down 恢复ici::SliceConfiguration::LinksDownReset() @ 0x1fdb5c00(旧版 0x1fe82f20
错误屏蔽ici::SliceConfiguration::MaskIciErrorsInternal(IciErrorType,bool) @ 0x1fdb6ec0
跨主机错误报告Master::ControlIciErrorReport @ 0x1fbc0d00 / Worker::ControlIciErrorReport @ 0x1fc40d80
路由表死锁Master::DetectRoutingTableDeadlock() @ 0x1fbbed60; RoutingTableAnalyzer::DetectPotentialDeadlock @ 0x1fbcd520
降级回退门控xla::jellyfish::UseResilientAlgorithmTwistedTorus @ 0x1c894fc0; GetDegradedAxis @ 0x1c894c20
置信度高(检测、fatal 门控、错误报告广播、死锁检测器、link-down reset 均有反编译验证的函数体),除非某行/标注另有说明

所处位置

ICI 故障处理是 all-reduce / DMA 原语下方的容错层。它按从最快/最低到最慢/最高分层;每一层观察不同粒度的故障,并选择吸收、带内恢复或升级。

text
Layer 0  HARDWARE/FIRMWARE  — SerDes PHY, link-stack fw, NIU credit FSM
         absorb transient bit errors (CRC/lane retry); host sees only outcomes
              │  port_ready_state code, link-up bit, fatal bit, stall counters

Layer 1  DRIVER INTERRUPT   — Ici::HandleIciLinkInterrupt → HandleIciLinkStatusChange
         per-chip, per-event; decides recoverable (retrain) vs deferred-fatal

Layer 2  DRIVER HEALTH POLL — IciControl::IsHealthy(link)
         link-up bit AND retry rate (60 s window) → 0–10 ici_link_health

Layer 3  SLICE-WIDE CHECK   — LinkChecker / MeshVerifier; GetChipHealth rollup
         the whole chip×link set vs the expected toroidal mesh

Layer 4  CROSS-HOST REPORT  — Master::ControlIciErrorReport broadcast
         IciSessionMonitor CHECK_SESSION_HEALTH; missed_health_check watchdog

Layer 5  MEGASCALE AGGREGATION — ReportError → MegascaleErrorAggregator
         whole-pod digest → Cause::NETWORKING_ISSUE (error-aggregator.md)
```text

静态死锁预检查(`DetectRoutingTableDeadlock`)位于此栈一侧:它在 slice **初始化**时运行,而非运行时运行,用于验证 discovery 选择的路由表(包括为绕过已知坏链路而选择的弹性表)不包含通道依赖环。上游是链路 discovery 和 bring-up([链路 Bring-Up 序列](link-bringup.md)、[拓扑发现](topology-discovery.md));下游消费者是 collective picker([降级轴摄取](../collectives/degraded-axis.md))和跨 pod 聚合器([Megascale 错误聚合器](../megascale/error-aggregator.md))。整个 ICI fabric 的概览见 [ICI 概览](overview.md)。

> **注意 —** 跨主机升级并不只通过 Megascale RPC:slice-builder 层有自己专用的 ICI 错误报告广播。`Master::ControlIciErrorReport` 和 `Master::DetectRoutingTableDeadlock` 在二进制中以完整 slice-builder 符号(`accel_ssw::deepsea::slice_builder`)存在,被 `Master::InitSlice` (`0x1fbbaac0`) 引用,其函数体在下文反编译。

---

## Link-Down / Link-Error 检测

### 目的

从主机侧确认给定 ICI 链路是否存活:使用固件 mailbox、中断驱动的状态变更,以及主动 liveness probe;并将 per-link 错误活动转换为单一重试率信号,供健康判定使用。

### 入口点

```text
Ici::HandleIciLinkInterrupt (jfc 0xe7adc80 / dfc 0xe76fe80)   ── driver IRQ, under mutex
  └─ IciControl::HandleIciLinkStatusChange (0x21381e40)        ── interrupt status handler
       ├─ IciControl::AllLinksUp (0xe7b0200)                   ── expected-up compare
       └─ MakeErrorImpl<13> "Link error."                      ── on not-all-up
IciControl::IsLinkUp (0xe7afe80)                               ── firmware-state read (on demand)
IciControl::IsHealthy (0xe7af720)                             ── 4-port health poll
  └─ IciControl::UpdateAndGetRetriesPerMinute (0xe7af540)      ── 60 s retry-rate window

算法

IsLinkUp 通过 link-stack 接口读取固件 port-ready 状态,并对端口索引做 5 的边界检查:

c
function IsLinkUp(link):                 // IciControl::IsLinkUp @ 0xe7afe80
    if link >= 5:                        // cmp $0x5 — ports 0..4 valid; ≥5 fails
        return MakeErrorImpl<3>("Invalid link number %d", link)  // kInvalidArgument, line 153
    state = fw->ReadPortReadyState()     // vtable+0x40 on the firmware-comm object, line 150
    if link == 4:                        // default switch arm inside the <5 branch
        LOG(FATAL) "port_ready index is invalid. "
    return (state >> link) & 1           // per-port bit, ports 0..3; up iff bit set
```text

> **怪异点 —** 边界是 `< 5`,不是 `< 4`。主机最多建模每芯片 **5** 个 ICI 端口(4 个物理 SerDes + 1 个保留/loopback 槽位)。`< 5` 索引检查仅对 `link >= 5` 返回 `kInvalidArgument`(`"Invalid link number %d"`,第 153 行);索引 `4` 通过边界检查,但命中 `LOG(FATAL)` default switch arm(`"port_ready index is invalid. "`),所以实际只有端口 `0..3` 会解析。`IsHealthy` 只迭代这 4 个物理端口。若重新实现时将端口数组大小设为 4,会在保留索引上溢出。(更完整的 `"port_ready_state index is invalid."` 字符串属于兄弟函数 `GetLinkStackReadyState` @ `0xe7afd00`,它提取每端口 4-bit nibble,而非单个位。)

`HandleIciLinkStatusChange` 是中断驱动的反应,已在 `ici_control.cc:193/199/201/202` 验证:

```c
function HandleIciLinkStatusChange(links, links_are_enabled):   // 0x21381e40
    LOG(INFO) << "Got link status change from device, "
                 "links_are_enabled_: " << links_are_enabled    // ici_control.cc:193
    if not links_are_enabled:
        return OK                                               // disabled → nothing to check
    st = AllLinksUp(links, /*expect_up=*/true)                  // 0xe7b0200, ici_control.cc:199
    if st.ok():
        if all_up:                                              // AllLinksUp true → no action
            return OK
        LOG(INFO) << "Link status changed to down."             // ici_control.cc:201
        return MakeErrorImpl<13>("Link error.")                 // INTERNAL, ici_control.cc:202
    return StatusBuilder(st) << "while HandleIciLinkStatusChange"

MakeErrorImpl<13>absl::StatusCode::kInternal 构造器;尾部 StatusBuilder(...) << "while …" 会用上下文包装非 OK 的 AllLinksUp 结果。非 OK 返回值会被 Ici::HandleIciLinkInterrupt(调用方,在 driver mutex 下)检查,以决定是否调度延迟失败。

重试率信号是真正的滑动窗口:UpdateAndGetRetriesPerMinute 维护一个 deque 和一个 running sum:

c
function UpdateAndGetRetriesPerMinute(history, delta):   // 0xe7af540
    history.deque.push_back({absl::Now(), (int)delta, delta})
    history.sum += delta                                 // running sum at object+0x30
    cutoff = absl::Now() - Seconds(60)                   // mov $0x3c (=60) → Duration
    while history.deque.front().time < cutoff:           // evict stale entries
        history.sum -= history.deque.front().weight
        history.deque.pop_front()                        // frees a head block past 0x154 entries
    return (double) history.sum                          // retries in the trailing 60 s
```text

这是 `max_ici_retries_per_minute` 的 per-link 执行点。deque 有界:它通过 `__add_back_capacity` (`0xe7b58e0`) 增长,并在超过 0x154 个条目时释放头部 block,因此在持续重试风暴下窗口不会泄漏内存。

### 函数映射

| 函数 | 地址 | 角色 |
|---|---|---|
| `IciControl::IsLinkUp(int)` | `0xe7afe80` | 固件 port-ready 读取,端口 `< 5` 边界 |
| `IciControl::HandleIciLinkStatusChange` | `0x21381e40` | 中断状态处理器 → `Link error.` |
| `IciControl::AllLinksUp(Span<int>,bool)` | `0xe7b0200` | expected-up 集合比较 |
| `IciControl::IsHealthy(int)` | `0xe7af720` | Per-link 健康 = up 且重试在预算内 |
| `IciControl::UpdateAndGetRetriesPerMinute` | `0xe7af540` | 60 s 滑动窗口 deque 求和 |
| `Ici::HandleIciLinkInterrupt` | `0xe7adc80` (jfc) / `0xe76fe80` (dfc) | Driver IRQ 入口,在 mutex 下 |
| `LinkChecker::CheckLinks` | `0x1fc38580` | Bring-up 后主动 liveness probe |

### 注意事项

ICI 没有**主机可见的 lane 降级 / width-fallback 路径**。穷尽搜索发现,二进制中唯一的 half-width / L0p / lane-retry 文字描述的是 Intel QPI/UPI host-uncore 计数器(`UNC_IO_LINK_NUM_RETRIES`),不是 ICI。SerDes width 协商和 per-lane deskew 由固件拥有:如果某条 lane 降级,固件要么透明地重新协商(主机只会看到 `port_ready_state` 到达 "up" 前的训练延迟增加),要么声明链路不可用(link-down / fatal 路径)。主机对 degraded-but-up 链路唯一的持续质量信号是 010 的 `ici_link_health` 分数:降级链路显示为 19 而非 010,由升高的重试率驱动。(中:未恢复精确的 rate→score 曲线。)

---

## 软故障 vs 硬故障 — 分类器

### 目的

将 ICI 链路事件归约为两种结果之一:**故障,通过重新运行 bring-up 在带内恢复;或**故障,拆除设备并升级。判别器是固件 fatal 位加重试率预算。

### 算法

`FatalErrorCheck` 是门控,已在 `slice_configuration.cc:739/741/743/746` 验证:

```c
function FatalErrorCheck():                       // ici::SliceConfiguration @ 0x1fdb6720
    hw_fatal_st  = fw->ReadHardwareFatal()        // vtable+0x20 on fw object (**this+49)
    if not hw_fatal_st.ok():
        return AddSourceLocation(hw_fatal_st, line 739)
    hardware_fatal = hw_fatal_st.value
    net_fatal_st = fw->ReadNetworkFatal()         // vtable+0x28
    if not net_fatal_st.ok():
        return AddSourceLocation(net_fatal_st, line 741)
    network_fatal = net_fatal_st.value
    if hardware_fatal | network_fatal:            // line 743
        LOG(ERROR) << "!!!! FATAL ERROR !!!! for "
                   << " hardware_fatal: " << hardware_fatal
                   << " network_fatal: "  << network_fatal
        return MakeErrorImpl<13>(                  // INTERNAL, line 746
            "Fatal error occurred. Data links will go down.")
    return OK

日志行在字面前缀 "!!!! FATAL ERROR !!!! for " 下,先追加 hardware_fatal(vtable+0x20 读取,第 739 行),再追加 network_fatal(vtable+0x28,第 741 行)。driver 在 ICI 链路事件上遵循的判定如下:

text
ICI link IRQ → HandleIciLinkInterrupt → HandleIciLinkStatusChange
  AllLinksUp == true ?
    YES → transient blip, no action (counted via retry rate)
    NO  → IsHealthy(link) ?  (link-up AND retry < max_ici_retries_per_minute,
                              AND no firmware fatal bit)
            YES → SOFT: schedule LinksDownReset + re-run bring-up;
                  chip stays in slice; health score 1–9
            NO  → FatalErrorCheck(): network_fatal or hardware_fatal set,
                  OR retry budget persistently exceeded
                → HARD: SignalDeferredFailure → FailDevice cascade;
                  health score 10; core dump; CHIP_DRIVER_ERROR;
                  Megascale NETWORKING_ISSUE; host flagged for fleet removal
```text

| 软(可恢复)标记 | 硬(fatal)标记 |
|---|---|
| link-up 位短暂清零 | 记录 `!!!! FATAL ERROR !!!! for` |
| 重试率 `< max_ici_retries_per_minute` | 设置 `network_fatal` / `hardware_fatal` 位 |
| `ici_link_health` 分数 1–9 | `ici_link_health` 分数 10 |
| `LinksDownReset` + 重新启用 + wait-for-DL-up 成功 | `FailDevice` 级联触发 |
| GTC 重新同步成功 | `SliceFailureType::CHIP_DRIVER_ERROR`(值 5) |
| 无进程重启 | core dump + Megascale `NETWORKING_ISSUE` + fleet removal |

### 注意事项

软故障恢复是带内的(retrain,无进程重启)。硬故障的“恢复”是带外的:进程退出(或 coordinator 在 `megascale_error_reporter_abort_on_*` 下 log-fatal),orchestrator 重启作业,且*下一次* bring-up 要么选择排除现在已知坏链路的弹性路由表(见下一节,如果对称性允许),要么因为该链路必需且不可替代而 discovery 失败。不存在原地“隔离一个芯片并继续”;恢复单元是整个 slice。(低:vtable+0x20 / +0x28 背后的精确 CM-register 位位置,以及 `max_ici_retries_per_minute` 的默认值,均在运行时从 Options 填充,不在面向用户的 rodata 中。)

---

## 恢复 — Link-Down Reset 与 Retrain

### 目的

通过在受影响链路上重新运行 bring-up 序列来恢复软故障。没有专用的 `RetrainLink()`;“retrain”就是 `LinksDownReset` 后接正常的 enable / wait / GTC-resync 路径。

### 算法

`LinksDownReset` 将每条非 down 链路拉下,并确认 data-link 层到达 down/disabled 状态,已在 `slice_configuration.cc:570` 验证:

```c
function LinksDownReset():                       // ici::SliceConfiguration @ 0x1fdb5c00
    lock(this+200)                               // absl::Mutex
    st = CollectDataLinkState()                  // snapshot per-port DL state
    if not st.ok(): return StatusBuilder(st, line 570)
    for port in 0 .. enabled_port_count-1:       // *((this+9)) = port count
        state = data_link_state[port]            // *((this+30))[port]
        if (state - 3) >= 2:                     // states 3,4 = kDown/kDisabled → skip
            continue
        IciPortUser::SetDataLinkLayerState(port, false)   // firmware "turn link down"
        // on failure: "Failed to turn down ICI link %d during slice reset, state=%d"
    CollectDataLinkState()                       // re-confirm all reached kDown/kDisabled
    clear enabled-port list (this+0x110 = 0)
    if ports were enabled: clear bring-up status fields (this+0x120 / +0x128)

完整 retrain 随后重新运行 bring-up:

text
1. LinksDownReset           — SetDataLinkLayerState(false) per non-down port
                              umbrella: "Bringing ICI links down." /
                              "Failed to take down links and reset ICI"
2. EnableIci(span)          — re-issue enable_ici_serdes_training → fw re-trains PHY
   WaitForDataLinkUp(dur)   — poll port_ready_state (fixed 1 ms quantum, clamped to the
                              remaining budget when under 1 ms; no second tier — see link-bringup.md §2)
   MaskIciErrors during the window; UnmaskIciErrors once DL-up succeeds
3. ClearGtc → WaitForGtcReset → StartGtc   — tear and rebuild global-time-counter sync
                              failure: "Failed to restart GTC on link "
```text

在 retrain 窗口期间,错误通过 mask map 被抑制,避免正在进行的 reset 本身触发升级级联。

### 函数映射

| 函数 | 地址 | 角色 |
|---|---|---|
| `ici::SliceConfiguration::LinksDownReset` | `0x1fdb5c00` | 拉下链路,确认 DL down/disabled |
| `SliceConfiguration::LinksDownReset`(旧版) | `0x1fe82f20` | `ici` namespace 之前的变体 |
| `ici::SliceConfiguration::CollectDataLinkState` |(上方 callee)| Snapshot per-port DL state |
| `ici::SliceConfiguration::MaskIciErrorsInternal` | `0x1fdb6ec0` | `flat_hash_map<IciErrorType, vector<MaskedErrors>>` |
| `ici::SliceConfiguration::PerformReset` | `0x1fdb71a0` | Slice-wide reset(芯片 reset 路径) |
| `ici::SliceConfiguration::GenerateAndSerializeCoreDump` | `0x1fdb5fa0` | 取证 `CORE_DUMP_ICI_DUMP` artifact |
| `KernelPrivilegedInterface::PerformReset(ResetType)` |(per-gen)| 当 LinksDownReset 不足时的芯片级 hard reset |

### 注意事项

重试率预算(上一节)限制每分钟可 retrain 某条链路的次数,超过后会将其声明为 hard-failed。没有**descriptor 级 DMA 重试**:卡住的 remote DMA 永远不会在 descriptor 层重试;timeout 会升级,包围它的 collective 会失败。唯一的“重试”粒度是链路级 retrain。(中:`PerformReset` / `KernelPrivilegedInterface::PerformReset` 函数体和 `ResetType` enum(warm vs cold vs link-only)未逐一追踪。)

---

## 降级轴回退 — 静态、感知故障的路由

### 目的

通过选择预计算的弹性路由表绕过*bring-up 时已知*的错误链路,而不是在运行时重新计算路由。实时链路掉线**绝不会**被重路由;它会作为故障升级。

### 算法

路由表只在 bring-up 时生成一次并安装一次;二进制中没有 `Reroute`、`RegenerateRoutingTable` 或 `RecomputeRoutes` 符号。弹性通过*表选择*实现:

```c
function select_route_table(target, env):       // at bring-up, not at runtime
    axis = GetDegradedAxis(target, faulty_links) // 0x1c894c20 → -1, or 0/1/2 (X/Y/Z)
    if axis >= 0 and UseResilientAlgorithmTwistedTorus(target, env):  // 0x1c894fc0
        // precondition: non-empty faulty set with super-pod fault symmetry
        // "ICI resiliency only supports … super-pod fault symmetry …"
        // "The topology size must be a multiple of the fault symmetry"
        install pre-baked resilient cache:
            cache_ici_resiliency_<codename>_config.binarypb
            cache_ici_resiliency_<codename>_fault_dim_{x,y,z}.binarypb
            cache_ici_resiliency_<codename>_{x,y,z}_data.binarypb
        // routes computed by RandomizedToroidalWildFirstPaths (deadlock-free)
    else:
        install the normal generated table (or fail discovery if the link is required)

错误链路轴由 tpu::OrientationsToTpuDegradedAxes0x1fc57d00,orientation 1→X、2→Y、3→Z)归约为三个 degraded bytes,并由 collective-ring picker 消费。完整的 proto/POD ingest 和 Target[+0x3f8..+0x3fa] 字节布局记录在 降级轴摄取;弹性缓存 codec 和去重见 弹性 Route-Cache 去重。本文只链接它们,不重复说明。

易错点 — “fault-aware”不是“运行时 fault-tolerant”。执行期间掉线的链路不会被绕过:路由到死链路上的 packet 会 back-pressure 并 stall,直到 sflag/DMA watchdog 触发,然后故障升级。弹性表只帮助 slice 构建时已经已知且符合 super-pod 对称性的故障。破坏对称性的任意单链路故障不可重路由,会落到 discovery failure。

函数映射

函数地址角色
xla::jellyfish::GetDegradedAxis0x1c894c20将 faulty bitset 归约为一个轴索引或 -1
xla::jellyfish::UseResilientAlgorithmTwistedTorus0x1c894fc0门控弹性路径
tpu::OrientationsToTpuDegradedAxes0x1fc57d00Faulty Orientation enum → X/Y/Z degraded bytes
弹性路由缓存(rodata blobs)cache_ici_resiliency_{pufferfish,viperfish,6acc60406}_*.binarypb

路由表死锁检测

目的

在 slice 初始化时验证所选片上路由表(包括为绕过故障而选择的弹性表)不包含通道依赖环。这是静态预检,不是运行时挂起检测器。

入口点

text
Master::InitSlice (0x1fbbaac0)
  └─ Master::DetectRoutingTableDeadlock (0x1fbbed60)        ── per-chip, at init
       ├─ superpod::routing::RoutingTableSet (per chip)     ── regenerated routing tables
       │     err: "Failed to generate routing table set for chip … at <Coordinates>"
       ├─ RoutingTableAnalyzer (chips, 0x7FFFFFFF, map)     ── builds dependency graph
       └─ RoutingTableAnalyzer::DetectPotentialDeadlock (0x1fbcd520)
             → MakeErrorImpl<9> (FAILED_PRECONDITION) on a detected cycle
```text

### 算法

`DetectRoutingTableDeadlock`(master.cc:705–726)遍历每个芯片,重新生成其 `RoutingTableSet`,并将该 set 输入 `RoutingTableAnalyzer`,后者运行通道依赖图环搜索:

```c
function DetectRoutingTableDeadlock():           // Master @ 0x1fbbed60
    chip_tables = {}                              // flat_hash_map<int, unique_ptr<RoutingTableSet>>
    chip_views  = {}                              // flat_hash_map<int, RoutingTableSet const*>
    for chip in slice.chips:
        set_or = GenerateRoutingTableSet(chip)    // RoutingTableSet ctor per chip
        if not set_or.ok():
            return StatusBuilder(set_or)          // master.cc:55:
                << "Failed to generate routing table set for chip "
                << chip.id << " at " << chip.coords.ToString()
        chip_tables[chip.id] = move(set_or.value)
        chip_views[chip.id]  = chip_tables[chip.id].get()
    t0 = absl::Now()
    analyzer = RoutingTableAnalyzer(slice, /*budget=*/0x7FFFFFFF, chip_views, ...)
    st = analyzer.DetectPotentialDeadlock()       // 0x1fbcd520
    VLOG(1) << "Deadlock detection took " << (absl::Now() - t0)   // master.cc:724
    if st.ok() and st.value == /*deadlock found*/:
        return MakeErrorImpl<9>(                   // FAILED_PRECONDITION, master.cc:726
            "RoutingTableAnalyzer detects a potential deadlock!  "
            "File a bug against SliceBuilder (…).  "
            "Please attach core dumps retrieved from Coroner.")
    return st

RoutingTableAnalyzer::DetectPotentialDeadlock (0x1fbcd520) 接收一个 BufferId 和一个 map<pair<int,int>, int>,并在 buffer/channel 对上计算边集合(Edges),即经典的 turn/channel-dependency-graph 路由无死锁测试。它分析的路由条目是 RandomizedUnicastEgressNextHopRoutingEntryCumulativeWeightsEgressRoutingEntryTI<6,12> 变体,也就是弹性 RandomizedToroidalWildFirstPaths 生成器发出的同类 egress-table 形状;这就是为什么在可能选择弹性表之后还要运行死锁检查。

注意事项

传给 analyzer 的 0x7FFFFFFF 预算实际上是无限搜索深度:该检查会穷尽 per-chip 依赖图,而不是被时间限制。因为它在 InitSlice 下的初始化阶段运行,易死锁的路由表会在任何 collective 运行之前使 slice 失败,表现为 FAILED_PRECONDITION,而不是运行中挂起。(外层 driver 和错误路径为高置信度;DetectPotentialDeadlock 内部环搜索算法为中置信度,它通过签名识别:BufferId + edge map + Edges accumulation,但未逐行追踪。)

注意 — 这不同于 XLA 层 collective 死锁验证器(xla namespace 中的 CheckPendingSendRecvDeadlocks / VerifyNoCollectiveDeadlocksRecursive),后者在编译时检查 HLO send/recv 配对。DetectRoutingTableDeadlock 在 slice 初始化时操作物理 superpod::routing 表,比 HLO 图低一层。


跨主机错误报告与升级

目的

将芯片级 ICI 故障从失败 worker 向外传播:经过设备拆除级联、slice-builder 错误报告广播、tpunetd session monitor,最终到达 Megascale 聚合器。

ControlIciErrorReport 广播

Master::ControlIciErrorReport (0x1fbc0d00) 是 master 的扇出,已在 master.cc:1231 验证:

c
function Master::ControlIciErrorReport(worker_name, stub):   // 0x1fbc0d00
    req = ControlIciErrorReportRequest()
    req.field6 = 1                                   // request flag
    req.is_limited_ici_routing =
        RoutingTableGeneratorFactory::IsLimitedIciRouting(this+8)
    ctx = ClientContext()
    deadline = absl::Now() + this.rpc_timeout        // Now() += Duration(this+20,this+28)
    ctx.set_deadline(deadline)                       // gpr_inf_future/past clamps handled
    reply = ControlIciErrorReportReply()
    grpc_status = stub->ControlIciErrorReport(ctx, req, &reply)   // vtable+72
    st = GrpcStatusToAbslStatus(grpc_status)
    if not st.ok():
        return StatusBuilder(st, "master.cc", line 1231)
    return OK
```text

`Worker::ControlIciErrorReport`(`0x1fc40d80`,worker.cc:378)是接收侧,会将报告并行扇出给自己的 peers:

```c
function Worker::ControlIciErrorReport(req):         // 0x1fc40d80
    lock_shared(this+8)                              // absl::Mutex, shared
    closures = []                                    // vector<function<absl::Status()>>
    for peer in this.peer_stub_list:                 // *((this+9)) entries, stride 64
        // capture peer endpoint strings + req.field6 + req.byte28 into a $_0 closure
        closures.push_back(make_report_closure(peer, req))
    pool = ThreadPool(closures.size())
    status = RunInParallel(closures)                 // worker.cc $_0 → ThreadPool
    pool.JoinAll()
    unlock_shared(this+8)
    if status != OK:
        return StatusBuilder(status, "worker.cc", line 378)
    return OK

怪异点 — 错误报告以 slice-builder gRPC unary call(SliceBuilderWorkerService::ControlIciErrorReport,server stub 位于 WorkerService::ControlIciErrorReport 0x1fc3cda0)投递,并携带 IsLimitedIciRouting 标志,而不只是故障描述符。因此跨主机流程有两个阶段:slice-builder 广播(ControlIciErrorReport)在 slice 内传播 ICI 故障和路由受限状态,另外 Megascale ReportError 路径会分类 whole-pod 摘要。只接 Megascale RPC 的重新实现会漏掉通知每个 worker 当前 slice 已处于 limited-routing 模式的 slice-builder 广播。

升级级联

text
TIER 1  per-chip driver — Ici::HandleIciLinkInterrupt → HandleIciLinkStatusChange
          → Ici::SignalDeferredFailure(Status) → Ici::FailDevice(Status)
          FatalErrorCheck() gates recoverable vs fatal
TIER 2  FailDevice cascade — Ici::FailDevice
          → Driver::FailDevice / FailDeviceLocked
            → TensorNode::FailDevice → Queue::FailDevice → BarnaCore::FailDevice
          aborts all in-flight queues/transfers on the chip
TIER 3  slice-builder — SliceFailureType::CHIP_DRIVER_ERROR (value 5) in
          UpdateSessionInfoRequest; Master::FailSlice (0x1fbc1760);
          Master::ControlIciErrorReport broadcast (limited-routing propagation)
TIER 4  tpunetd — SessionMaster::CheckSessionHeartbeat /
          HandleFailingSession(SessionState); IncrementMissedHealthCheck
          watchdog; GenerateAndSerializeCoreDump → CORE_DUMP_ICI_DUMP
TIER 5  Megascale — ReportError → MegascaleErrorAggregator::ProcessAndShutdown
          → Cause::NETWORKING_ISSUE + FaultyNetworkLink proto
          "Megascale detects a hang that is likely caused by a networking issue."
          (see ../megascale/error-aggregator.md)
```text

### 函数映射

| 函数 | 地址 | 角色 |
|---|---|---|
| `Master::ControlIciErrorReport` | `0x1fbc0d00` | Master 扇出广播(deadline + IsLimitedIciRouting) |
| `Worker::ControlIciErrorReport` | `0x1fc40d80` | 在 ThreadPool 上并行 peer 重新广播 |
| `WorkerService::ControlIciErrorReport` | `0x1fc3cda0` | gRPC server 入口 |
| `Ici::SignalDeferredFailure(Status)` | `0xe7aeb20` (jfc) / `0xe770ec0` (dfc) | 发布 deferred-failure closure |
| `Ici::FailDevice(Status)` | `0xe7ae320` (jfc) / `0xe7706a0` (dfc) | 整芯片 teardown 入口 |
| `AsyncDriver::HandleFatalError(b,b,b,b)` | `0x1fe993c0` | 4 类 fatal 分类器 |
| `Master::FailSlice(SliceFailureType)` | `0x1fbc1760` | Per-slice 失败转换 |
| `ErrorHandler::InterruptFired` | `0x21382040` | 通用 error-IRQ handler |

### 注意事项

通过报告流动的 `RoutingTableGeneratorFactory::IsLimitedIciRouting` true 值会告知每个 worker 当前 slice 正运行在 degraded/limited 路由表上;这是静态弹性表选择的运行时对应物。可通过 `GenerateAndSerializeCoreDump` (`0x1fdb5fa0`) 收集 core dump 进行取证,产出 `CORE_DUMP_ICI_DUMP` artifact;`master.cc` 死锁字符串和 `worker.cc` 报告字符串都要求 operator 附上该 artifact。(低:驱动 `HandleFailingSession` 的 `SessionState` enum 值以及 per-state 恢复动作表未追踪。)

---

## Health-Check 与诊断计数器

### 目的

暴露连续的、主机可读的健康遥测,供软/硬判定和跨主机 monitor 使用。

### 算法

`GetChipHealth` (`0x1fdb6320`) 是 per-chip 汇总:它先调用 `FatalErrorCheck`,若设置了 fatal 则立即返回 fatal Status;否则在共享锁下检查 `+0x120`(counter,`cmp $0x2`)和 `+0x128`(17-bit mask,`cmp $0x20000`)处的状态字段,并在 link-down 条件下构造诊断 `"The following ICI link(s) have unexpectedly gone down: "`,追加 per-link 索引列表,以 `MakeErrorImpl<15>`(INTERNAL)返回。该链路存在于六层中(Synchronous/Async driver、`ici::IciDriver`,以及 `ici`/`jxc`/base `SliceConfiguration`)。

### Streamz 指标

| 指标 | 生产者 | 含义 |
|---|---|---|
| `ici_link_health` | telemetry harvest | 0–10 per-link scale:0 healthy,1–5 transient,6–9 persistent minor,10 unusable;`.int`/`.ext` suffix = intra-/inter-host cable |
| `missed_health_check` | `IciSessionMonitorImpl::IncrementMissedHealthCheck` (`0x1ff94a60`) | 跨主机 heartbeat watchdog,以 `TpuType` 为 key;由 `ClearMissedHealthCheck` (`0x1ff94c80`) 清除 |
| `session_health` | `RecordSessionHealth` | Session 状态转换健康 |
| `broadcast_latency` / `notification_latency` | `RecordBroadcastLatency` / `RecordNotificationLatency` | 跨主机 barrier/notify timing |

### 注意事项

`CHECK_SESSION_HEALTH` tpunetd debug RPC(`check_session_health_request/response`)是跨主机 probe,其 miss 会递增 `missed_health_check`;超过阈值后 session 通过 `HandleFailingSession` 失败。Per-gen telemetry harvest(gxc/gfc、vxc/vfc、vxc/vlc 上的 `CollectIciTelemetryCounterSet`)确认较新 generation 携带同一组 per-link MGT stall counter。(低:未恢复精确的 rate→`ici_link_health` 映射曲线。)

---

## 各代差异

恢复机制沿两个轴按芯片家族拆分:ICI 中断模型和弹性路由缓存可用性。

| 家族(driver ns) | ICI 中断模型 | 弹性路由缓存 | DMA-timeout 错误工厂 |
|---|---|---|---|
| Jellyfish (`jxc::jfc`) | 直接 MSIX `IciInterrupt` | 未观察到 | — |
| Dragonfish (`jxc::dfc`) | 直接 MSIX `IciInterrupt` | 未观察到 | — |
| Pufferfish (`pxc/plc`) | `IciInterrupt`(`pxc::plc` factory) | `cache_ici_resiliency_pufferfish_*` | — |
| Viperfish (`vxc/vfc`) | `VfIciFirmwareInterrupt`(fw-mediated) | `cache_ici_resiliency_viperfish_*` | `vxc::vfc::SparsecoreDmaTimeoutErrorFactory` |
| Viperlite (`vxc/vlc`) | `VfIciFirmwareInterrupt`(fw-mediated) | 共享 viperfish | `vxc::vlc::DmaRoutingMismatchError` |
| Ghostlite (`gxc/gfc`) | `VfIciFirmwareInterrupt`(fw-mediated) | `cache_ici_resiliency_6acc60406_*` | — |
| Ghostlite (`gxc/glc`) | `VfIciFirmwareInterrupt`(fw-mediated) | 共享 `6acc60406` | `gxc::glc::SparsecoreDmaTimeoutErrorFactory` |

关键差异:

- **中断投递。** 较旧的 `jxc`/`pxc` 将 ICI 链路中断作为直接 MSIX vector 投递;较新的家族通过 firmware-mediated `VfIciFirmwareInterrupt`(`Vf` = via-firmware)路由它,每个 IRQ 增加一个固件仲裁步骤,但将主机与原始 MSIX vector layout 解耦。
- **弹性路由。** 预制 fault-route cache 仅存在于 `pufferfish`、`viperfish` 和 `6acc60406`。Jellyfish/Dragonfish 在此构建中没有弹性 cache → 错误链路无法绕过;discovery 失败或 slice 被 reshaped。
- **DMA-timeout 类。** 离散的 `SparsecoreDmaTimeoutError` 仅在 `gxc/glc` 和 `vxc/vfc` 上有 factory;`vxc/vlc` 另外有 `DmaRoutingMismatchError`(当 DMA descriptor 的 routing 字段与已安装表不一致时抛出)。较旧 generation 仅通过 sflag-wait watchdog 暴露卡住的 DMA,而没有专用硬件错误类型。
- **芯片 reset 粒度。** `KernelPrivilegedInterface::PerformReset(ResetType)` 和 `FirmwareInterface::Reset(ResetType)` 存在于每个家族的 `KernelFirmware` 上;这是在 `LinksDownReset` 不足时使用的芯片级 hard reset(fatal 路径)。

---

## 交叉引用

- [ICI 概览](overview.md) — ICI fabric 以及故障处理位于 collective/DMA 原语下方的位置
- [链路 Bring-Up 序列](link-bringup.md) — `LinksDownReset` 为 retrain 链路而重新运行的 bring-up state machine
- [拓扑发现](topology-discovery.md) — discovery 产生弹性回退选择所依据的 faulty-link set
- [降级轴摄取](../collectives/degraded-axis.md) — faulty-link orientation 到 collective ring 的 proto/POD ingest(降级回退的 producer 侧)
- [弹性 Route-Cache 去重](../routing/route-cache-dedup.md) — 降级回退选择的 `cache_ici_resiliency_*` 弹性路由表 codec
- [Megascale 错误聚合器](../megascale/error-aggregator.md) — 将摘要分类为 `NETWORKING_ISSUE` 的跨 pod 升级目标