ICI 故障检测、降级轴回退与恢复
本页所有地址适用于
libtpu-0.0.40-cp314wheel 中的libtpu.so(build-id89edbbe81c5b328a958fe628a9f2207d,781,691,048 字节,未 strip)。其他版本会不同。.textVMA 等于文件偏移;所有地址均为 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,并通过 RunInParallel 在 ThreadPool 上并行运行。配套的 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-chipRoutingTableSet通道依赖图上运行RoutingTableAnalyzer::DetectPotentialDeadlock,于 slice 初始化时执行。 - 升级级联 — per-chip 延迟失败 →
FailDevice→SliceFailureType::CHIP_DRIVER_ERROR→ControlIciErrorReport广播 /FailSlice→ tpunetd session-failing → MegascaleNETWORKING_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 原语下方的容错层。它按从最快/最低到最慢/最高分层;每一层观察不同粒度的故障,并选择吸收、带内恢复或升级。
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 的边界检查:
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:
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 链路唯一的持续质量信号是 0–10 的 `ici_link_health` 分数:降级链路显示为 1–9 而非 0 或 10,由升高的重试率驱动。(中:未恢复精确的 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 链路事件上遵循的判定如下:
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:
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::OrientationsToTpuDegradedAxes(0x1fc57d00,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::GetDegradedAxis | 0x1c894c20 | 将 faulty bitset 归约为一个轴索引或 -1 |
xla::jellyfish::UseResilientAlgorithmTwistedTorus | 0x1c894fc0 | 门控弹性路径 |
tpu::OrientationsToTpuDegradedAxes | 0x1fc57d00 | Faulty Orientation enum → X/Y/Z degraded bytes |
| 弹性路由缓存 | (rodata blobs) | cache_ici_resiliency_{pufferfish,viperfish,6acc60406}_*.binarypb |
路由表死锁检测
目的
在 slice 初始化时验证所选片上路由表(包括为绕过故障而选择的弹性表)不包含通道依赖环。这是静态预检,不是运行时挂起检测器。
入口点
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 stRoutingTableAnalyzer::DetectPotentialDeadlock (0x1fbcd520) 接收一个 BufferId 和一个 map<pair<int,int>, int>,并在 buffer/channel 对上计算边集合(Edges),即经典的 turn/channel-dependency-graph 路由无死锁测试。它分析的路由条目是 RandomizedUnicastEgressNextHopRoutingEntry 和 CumulativeWeightsEgressRoutingEntryTI<6,12> 变体,也就是弹性 RandomizedToroidalWildFirstPaths 生成器发出的同类 egress-table 形状;这就是为什么在可能选择弹性表之后还要运行死锁检查。
注意事项
传给 analyzer 的 0x7FFFFFFF 预算实际上是无限搜索深度:该检查会穷尽 per-chip 依赖图,而不是被时间限制。因为它在 InitSlice 下的初始化阶段运行,易死锁的路由表会在任何 collective 运行之前使 slice 失败,表现为 FAILED_PRECONDITION,而不是运行中挂起。(外层 driver 和错误路径为高置信度;DetectPotentialDeadlock 内部环搜索算法为中置信度,它通过签名识别:BufferId + edge map + Edges accumulation,但未逐行追踪。)
注意 — 这不同于 XLA 层 collective 死锁验证器(
xlanamespace 中的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 验证:
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::ControlIciErrorReport0x1fc3cda0)投递,并携带IsLimitedIciRouting标志,而不只是故障描述符。因此跨主机流程有两个阶段:slice-builder 广播(ControlIciErrorReport)在 slice 内传播 ICI 故障和路由受限状态,另外 MegascaleReportError路径会分类 whole-pod 摘要。只接 Megascale RPC 的重新实现会漏掉通知每个 worker 当前 slice 已处于 limited-routing 模式的 slice-builder 广播。
升级级联
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 升级目标