Skip to content

ToroidalRouteCache

地址适用于 libtpu-0.0.40-cp314 wheel 中的 libtpu.so。其他版本会不同。二进制: extracted/libtpu-0.0.40-cp314-cp314-manylinux_2_31_x86_64/libtpu/libtpu.so(build-id 89edbbe81c5b328a958fe628a9f2207d,build libtpu_lts_20260413_b_RC00.text VMA == 文件偏移,基址 0xe63c000)。已将反混淆名称、成员偏移和 .rodata 字符串与 IDA 反编译结果交叉核对(ToroidalRouteCache::{Create, GetRouteCacheData, GetRoutePath, GetRoutePaths, GetRouteDistance})。

摘要

ToroidalRouteCache预计算的 (src chip, dst chip) → 路由表的内存容器,在 slice 启动时由一个烘焙好的 .binarypb.compressed 资源物化而来。它是 ResilientToroidalTopology::InitRouteSolution 中“使用缓存还是现场生成”决策的快速路径分支:当发现的故障模式匹配某个烘焙形状时,Create 会加载匹配的 blob,并将其降级为四个 absl::flat_hash_map<pair<int,int>, V> 槽位;只有未命中时才运行实时的 *ToroidalWildFirstPaths 生成器。构建完成后,片上路由表发射器会在为任意新路由计算前,按每个 (src,dst) 对查询它。

这个容器有意只围绕一个布局判别值做多态。[obj+0] 处的单个 int_cache_type_)记录烘焙方案携带的是四种表示中的哪一种:有符号距离向量、单条静态路径位编码路径随机跳路径集合;该 int 会选择四个 map 中哪个保存每对芯片的值。三个读侧访问器(GetRouteDistanceGetRoutePathGetRoutePaths)都会先验证请求的表示与 [obj+0] 匹配,然后查找 pair<int,int> 芯片 ID 键。表示不匹配和键未命中是两个不同的外显错误,而不是一个;把它们合并的重实现会误诊缓存。

本页负责说明容器对象及其四 map 布局Create 的编排(load → decompress → construct → move-into-StatusOr 链)、决定给定硅片代际加载哪个烘焙 blob 的按 codename / twist 划分的缓存集合,以及读侧查找分派。proto→map 展开循环(CacheRead)和 Decompress Brotli 膨胀见 route-cache-decompress.md;选择 blob 的去重缓存route-cache-dedup.md;type-2 位编解码见 route-cache-codec.md

对于重实现,契约是:

  • 对象布局 — 前置的 int 布局判别值 {0,1,2,3},后接四个 flat_hash_map<pair<int,int>, V> 槽位;每个缓存只会填充其中一个。
  • Create 的编排GetRouteCacheData(path → ReadBinaryProtoDecompress)以 StatusOr OK 为门控,然后 ctor + CacheRead,再把四个 map 移入 StatusOr<ToroidalRouteCache> 结果;加载失败会作为 non-OK Status(源代码行 120)传播,而不是中止。
  • 缓存集合划分 — 三个静态 flat_set<string_view> 形状数组(kRouteCacheSet 12、kViperfishRouteCacheSet 4、TwistedTorusTopology::kRouteCacheSet 8)限定每个 codename 家族可加载哪些形状,以及按 codename 选择哪个集合的规则。
  • 查找 — 三个访问器共享的、由 [obj+0] 门控的分派:类型保护探测 → 键查找 → 返回值或两类错误之一。
容器accel_ssw::deepsea::slice_builder::ToroidalRouteCache_cache_type_ 位于 [obj+0]
CreateToroidalRouteCache::Create(Span<const int> dims, bool twisted, optional<Orientation>, const proto::ToroidalRouteCacheType&, vector<Orientation>) @0x20b5d6e0(490 B)
加载辅助函数GetRouteCacheData @0x20b5c420(368 B)→ GetRouteCacheDataPath @0x20bf2080(765 B)
构造函数ToroidalRouteCache::ToroidalRouteCache(const proto::ToroidalRouteCache&, const vector<Orientation>&) @0x20b5d8e0(310 B)
展开循环CacheRead @0x20b5da20(15157 B)→ route-cache-decompress.md
读访问器GetRouteDistance @0x20b615c0 · GetRoutePath @0x20b61720 · GetRoutePaths @0x20b61880
布局判别值[obj+0] int {0=distance, 1=static_path, 2=bit_encoded, 3=random_hop}
四个 map 槽位+0x8 distance · +0x28 path · +0x48 random-hop paths · +0x68 random-hop weights
Create 的调用方ResilientToroidalTopology::InitRouteSolution @0x1fbdf8a0 · TwistedTorusTopology::InitRouteSolution @0x20b3f7c0
源码platforms/accel_ssw/deepsea/slice_builder/internal/toroidal_route_cache.cc

1. 容器对象

ToroidalRouteCache 是一个小型固定头结构:一个 int 判别值和四个哈希 map,全部以同一个 pair<int,int> 芯片 ID 对 (src_chip_id, dst_chip_id) 为键。下面的偏移直接读自 Create(它会在 StatusOr payload 的固定偏移处构造四个 map)和三个访问器(它们基于 [obj+0] 分派并索引 map)。

说明 — 两套偏移基准。 CreateStatusOr<ToroidalRouteCache> payload a1 内部构建对象,其中 [a1+0]StatusOr OK 判别值(1),真正对象从 [a1+8] 开始。因此在 Create 中,布局 int 是 [a1+8],map 位于 [a1+16/+48/+80/+112]。在读访问器中,this 指针是裸对象:[obj+0] 是布局 int,map 位于 [obj+0x8/+0x28/+0x48/+0x68]。两套基准描述的是同一个结构;本页以裸对象基准(+0x8+0x28、…)为规范,并在出现时注明相对 StatusOr 的偏移。

对象布局

字段偏移(裸 obj)类型含义
_cache_type_+0x0int {0,1,2,3}布局判别值;由 ctor 根据第一个 RouteSchemeroute oneof 设置(见 route-cache-decompress.md
distance map+0x8flat_hash_map<pair<int,int>, superpod::routing::Coordinates>仅当 _cache_type_ == 0 时填充;每对的有符号逐轴距离向量
path map+0x28flat_hash_map<pair<int,int>, vector<proto::Direction>>仅当 _cache_type_ ∈ {1,2} 时填充;每对的单条跳序列
random-hop paths+0x48flat_hash_map<pair<int,int>, vector<vector<proto::Direction>>>仅当 _cache_type_ == 3 时填充;每对的等成本路径集合
random-hop weights+0x68flat_hash_map<pair<int,int>, vector<signed char>>仅当 _cache_type_ == 3 时填充;逐路径 int8 概率权重,与 +0x48 按索引对齐

确切的值类型由 Create 调用的四个 raw_hash_set<FlatHashMapPolicy<…>> 移动构造函数(@0x1fbe41e0 / 4220 / 4260 / 42a0)以及三个访问器中的模板化 find<pair<int,int>> 实例确认。

特性 — 四个槽位,一个有效。 只有与 _cache_type_ 匹配的 map 会被填充;另外三个会被构造为空,并在缓存生命周期内保持为空。该结构携带全部四个槽位,是因为缓存生产者可能烘焙任意一种表示,而布局在编译时固定,不按对象大小选择。重实现可以把未使用槽位折叠为四种 map 的 std::variant;二进制则为每个空 raw_hash_set 支付代价(各一个空的 backing-array 指针)。

说明 — type 1 和 2 共享一个 map。 static_path_cache_type_ == 1)和 bit_encoded_path_cache_type_ == 2)都会降级为 vector<proto::Direction>,并且都填充同一个 +0x28 map;它们只是相同内存值的两种磁盘编码。此 build 中发布的每张表都是 static_path(type 1);bit_encoded_pathroute-cache-codec.md 上的 DecodePathFromBits 解码。

判别值如何设置

ctor(@0x20b5d8e0)会将四个 map 零初始化,然后从第一个 scheme 的 route oneof tag 设置 [obj+0]3→05→24→1),如果任意 scheme 携带 random_first_hop,则把 1→3。完整说明见 route-cache-decompress.md;本页唯一需要的事实是判别值是每个 blob 同质的:缓存生产者必须在一个文件内为所有 scheme 发出单一 route 表示,而 CacheRead 的逐 scheme RetCheck 会把异质文件变成 non-OK Status,而不是静默误插入。


2. Create @0x20b5d6e0 — 加载、解压、构造、打包

Create 是物化预计算缓存的唯一入口。它大小为 490 字节,读起来像一条 StatusOr 成功路径,并且在加载失败时只有一个提前退出。它同时被 ResilientToroidalTopology::InitRouteSolution@0x1fbdf8a0,resilient 家族)和 TwistedTorusTopology::InitRouteSolution@0x20b3f7c0,twisted 家族)调用;同一个容器服务两者,codename/twist 划分位于 §3 的缓存集合数组中,并由选择 blob 的 ToroidalRouteCacheType 参数承载。

入口点

text
ResilientToroidalTopology::InitRouteSolution  0x1fbdf8a0   ── resilient family (per-codename)
TwistedTorusTopology::InitRouteSolution       0x20b3f7c0   ── twisted family
  └─ ToroidalRouteCache::Create               0x20b5d6e0   ── *** THIS PAGE ***
       ├─ GetRouteCacheData                   0x20b5c420   ── path → ReadBinaryProto → Decompress
       │    ├─ GetRouteCacheDataPath          0x20bf2080   ── "embed://<lower(Type)>_data/<shape>.binarypb.compressed"
       │    ├─ tsl::ReadBinaryProto           (Env::Default) ── read CompressedToroidalRouteCache off the baked resource
       │    └─ Decompress                     0x20b63320   ── Brotli → proto::ToroidalRouteCache  → route-cache-decompress.md
       ├─ ToroidalRouteCache::ctor            0x20b5d8e0   ── set [obj+0]; then CacheRead → 4 maps  → route-cache-decompress.md
       └─ move 4 maps into StatusOr<ToroidalRouteCache>
```text

### 算法

```c
// 0x20b5d6e0 — Create(a1 <- StatusOr storage, dims, twisted, orient, ToroidalRouteCacheType, dedup_orientations)
StatusOr<ToroidalRouteCache> Create(...) {
    StatusOr<proto::ToroidalRouteCache> data = GetRouteCacheData(...);   // 0x20b5c420

    if (!data.ok()) {                                       // v25 != OK sentinel (&dword_0+1)
        a1.status = AddSourceLocationImpl(data.status,      // toroidal_route_cache.cc:120
                       "…/toroidal_route_cache.cc");        // propagate, DO NOT abort
        return a1;
    }

    proto::ToroidalRouteCache pb;                           // ctor(&pb, 0)
    // arena-aware adopt: InternalSwap if same arena, else CopyFrom  (0x20c040e0 / 0x20c040a0)
    if (same_arena(pb, data)) pb.InternalSwap(&data.value);
    else                      pb.CopyFrom(&data.value);

    ToroidalRouteCache obj(pb, dedup_orientations);         // 0x20b5d8e0 — sets obj._cache_type_, runs CacheRead
    a1[8] = obj._cache_type_;                               // copy the layout int into the StatusOr payload

    // move-construct the four maps into the StatusOr payload at +16/+48/+80/+112
    move(a1+16,  obj.distance_map);        // FlatHashMapPolicy<pair<int,int>, Coordinates>          0x1fbe41e0
    move(a1+48,  obj.path_map);            // FlatHashMapPolicy<pair<int,int>, vector<Direction>>    0x1fbe4220
    move(a1+80,  obj.random_paths_map);    // FlatHashMapPolicy<pair<int,int>, vector<vector<Dir>>>  0x1fbe4260
    move(a1+112, obj.random_weights_map);  // FlatHashMapPolicy<pair<int,int>, vector<int8>>         0x1fbe42a0

    a1.discriminant = 1;                                    // StatusOr now holds a value (*a1 = 1)
    return a1;
}

字节锚定的事实:

  • OK 是哨兵指针测试,而不是读取 tag。 GetRouteCacheData 返回到一个 StatusOr 中,当 OK 时其 payload 指针等于 (&dword_0 + 1)(absl 内联 OK 哨兵)。0x20b5d6e0+offset 处的比较正是 v25 == &dword_0+1;任何其他值都会走错误路径。
  • 加载失败是结构化的。!data.ok() 时唯一的工作是 AddSourceLocationImpl(status, 120, "…/toroidal_route_cache.cc"):错误被标注源代码行并传播给 InitRouteSolution,随后后者回落到实时生成器或使 slice 失败。对比构造函数,它会在 proto 畸形时 CHECK 失败(烘焙资源 bug)。两套失败纪律:缺失/损坏资源可恢复;解析后的 proto 畸形是 build bug,会中止。
  • 采纳过程感知 arena。 解析出的 proto 是被移动(InternalSwap)还是复制(CopyFrom)到本地 pb,取决于两个消息是否共享 protobuf arena((v24 & 1) / (v27 & 1) arena 指针测试)。功能上相同;重实现可以总是复制。
  • Create 不触碰 map 内容。 所有逐 scheme 解码都发生在 ctor 对 CacheRead 的调用中;Create 只移动完成的 map。四个移动构造函数以及匹配的 destructor_impl 调用(@0x1fbe42e0/43c0/4660)包围移动,使临时 obj 留为空。

说明 — 资源路径在 GetRouteCacheDataPath 中构建。 ToroidalRouteCacheType enum(转小写)和 dimension/twist/orientation 后缀会在读取之前StrFormat 格式化进 "embed://%s_data/%s.binarypb.compressed"。这个路径构建处就是 codename 绑定到 blob 的地方;说明见 route-cache-decompress.mdCreate 自身从不看到 codename 字符串,只看到已经解析好的 ToroidalRouteCacheType 参数。

函数映射

函数地址大小角色
ToroidalRouteCache::Create0x20b5d6e0490加载 → 解压 → 构造 → 移入 StatusOr
GetRouteCacheData0x20b5c420368构建路径、ReadBinaryProtoDecompress
GetRouteCacheDataPath0x20bf2080765ToroidalRouteCacheType + shape → embed://… 路径
ToroidalRouteCache::ctor0x20b5d8e0310设置 [obj+0],调用 CacheRead
CacheRead0x20b5da2015157逐 scheme 解码到四个 map

3. 按 codename / twist 划分的缓存集合

Create 只加载一个 blob;某个 codename 家族允许加载哪些形状,事先由三个静态 flat_set<string_view> 形状名数组限定。这些是 .data 常量,其成员数量由 array<string_view, N> 模板参数固定,因此清单在字节层面精确。

三个缓存集合

缓存集合符号(.dataN形状
kRouteCacheSetResilientToroidalTopology::kRouteCacheSet @0x21f57380124x4x4, 4x4x8, 4x4x8_twisted, 4x4x12, 4x4x16, 4x8x8, 4x8x8_twisted, 8x8x8, 8x8x16_twisted, 8x16x16_twisted, 12x12x12, 12x12x24_twisted
kViperfishRouteCacheSetResilientToroidalTopology::kViperfishRouteCacheSet @0x21f57440416x16x16, 20x20x20, 12x24x24_twisted, 16x16x32_twisted(大型 pod)
TwistedTorusTopology::kRouteCacheSet@0x22011f8884x8_twisted, 4x4x8_twisted, 4x8x8_twisted, 8x8x16_twisted, 8x16x16_twisted, 12x12x24_twisted, 12x24x24_twisted, 16x16x32_twisted

(形状按字母序 flat_set 顺序列出;_twisted 后缀是几何标签,而不是一条单独的轴。)

按 codename 选择集合的规则

选择器不是 TPU 版本。InitRouteSolution 基于 HIDWORD(v90) 切换,这是 TopologyFaults::GetSymmetryProperty 返回的 ocs_fault_links_symmetry 判别值,其唯一有效值是 {1, 2, 4}(任何其他值都会在源代码行 266 生成 MakeErrorImpl<12>("Invalid ocs_fault_links_symmetry: …"))。每个 case 都会在 GetCacheDeduplicator(int) 中惰性构造三个函数局部 RouteCacheDeduplicator 单例之一:pf_deduplicatorvf_deduplicatorgf_deduplicator,然后用边界 flat_set 更新它。默认集合由 RouteCacheDeduplicator::Create(lambda @0x21f574c0)构建;viperfish 会在默认集合之后通过 CreateResilientViperfish(经 0x1fbe3e60 到达)添加大型 pod 集合,因此覆盖 12 + 4 = 16 个形状。symmetry→deduplicator→sets 映射如下(每个 UpdateDeduplicator 都被 CHECK 断言为 OK):

ocs_fault_links_symmetryDeduplicator加载的集合(由 CHECK 失败字符串断言)Codename 家族
1pf_deduplicatorkRouteCacheSet(12)pufferfish(v4)
2vf_deduplicatorkRouteCacheSet(12)+ kViperfishRouteCacheSet(4)= 16,经 CreateResilientViperfishviperfish(v5)
4gf_deduplicatorkRouteCacheSet(12)+ k6acc60406RouteCacheSet(4)= 166acc60406 / TPU7x(gen 6)
any other无 — MakeErrorImpl<12>("Invalid ocs_fault_links_symmetry: …"),行 266无效

codename 字符串单独绑定:一个次级 switch(HIDWORD(v90))(行 429/432/435)把 1→22→34→4 映射到 proto::ToroidalRouteCacheType 值,随后 GetRouteCacheDataPath 将其小写化到 embed://<codename>_data/… 路径中。因此 symmetry-1 加载 pufferfish_*,symmetry-2 加载 viperfish_*,symmetry-4 加载 6acc60406_*。从未到达这个 switch 的 codename(jellyfish v2、dragonfish v3、ghostlite v6e)在这里没有 resilient 缓存去重器;这些部件上的故障会在 symmetry gate 失败,slice 会重塑(见 route-table-generation.md)。

特性 — 划分的是覆盖范围,不是算法。 所有烘焙表都是相同 *ToroidalWildFirstPaths 输出;vf/gfpf 的差别只在于 (a) 哪些形状有烘焙表:vfgf 各自在共享的 12 个形状上叠加自己的 4 形状大型 pod 集合;以及 (b) 每个形状的表可容忍多少故障链路(6acc60406_fault_dim_* 故障目录显著大于 viperfish,而 viperfish 又大于 pufferfish;这里不枚举精确 FileToc 大小)。把 pufferfish 的 12 形状集合复制到 vf/gf 硅片上的重实现,会静默丢失四个大型 pod 的保护,并在大型 pod 故障时回落到实时生成器。

规范 — 6acc60406 是 TPU7x,不是 “ghostfish”。 去重器变量名是 gf_deduplicator,但该缩写不是二进制证实的 codename;唯一证实的 token 是哈希 6acc60406,即 TPU7x 代际(gen 6,不同于 ghostlite/v6e)。不要把 gf_ 前缀解释为 “ghostfish”——二进制中不存在这个字符串。

说明 — 边界数组。 导出的 resilient 形状名数组是两个 flat_set<string_view>ResilientToroidalTopology::kRouteCacheSet(12)和 ResilientToroidalTopology::kViperfishRouteCacheSet(4);twisted topology 带有其自己的第三个(TwistedTorusTopology::kRouteCacheSet,8)。第四个 k6acc60406RouteCacheSet(4)只通过 gf_deduplicator UpdateDeduplicator CHECK 失败字符串和 off_21F57480 局部引用;它是 symmetry-4(6acc60406/TPU7x)大型 pod 集合,在结构上对应 kViperfishRouteCacheSetgf 版本。因此三个去重器(pf/vf/gf)从四个边界数组中取用:pf→{12},vf→{12,4},gf→{12,4}。flat_set 数组限定哪些形状字符串可以命中;去重器则是哪个已填充查找表作答。置信度:CERTAIN — 每个 UpdateDeduplicator 目标都在 CHECK 断言字符串中逐字命名。

嵌入资源

这些形状会解析到少量 tsl::memfile 注册的 blob(静态初始化时通过 GlobalRegisterFiles(name, FileToc*) @0x20c04960 注册),不是每个形状一个文件。按 .rodata 中的 cache_ici_resiliency_* / cache_twisted_torus_* 名称前缀,嵌入的 route-cache 资源是:

  • pufferfishviperfish6acc60406 各自的一个 _config 以及逐轴 _fault_dim_x / _fault_dim_y / _fault_dim_z 故障目录(6acc60406 的故障目录最大);
  • pufferfishviperfish 以及 6acc60406 各自逐轴的 _x_data / _y_data / _z_data Brotli .binarypb.compressed 路由表;也就是说 6acc60406/TPU7x 确实发布了烘焙路由表,而不只是故障配置;
  • twisted 组合 cache_twisted_torus_config + cache_twisted_torus_data(外加一个 cache_twisted_torus_all 形状目录)。

FileToc 结构是 {+0x00 name*, +0x08 data*, +0x10 size:u64, +0x18 hash[16]}。压缩资源解码由 route-cache-decompress.md 负责;dedup-key→shape-name 选择由 route-cache-dedup.md 负责。


4. 读侧查找

构建完成后,三个访问器会按每个 (src,dst) 对查询缓存。它们是从 DMA descriptor 的路由表索引(也就是 (src_chip_id, dst_chip_id) 对,经 net_util::MapSrcDstCoreToRoutingTableIndex @0x1c6aea80)到具体 vector<proto::Direction> 或距离向量之间的桥梁。路由生成会在计算新路由之前查询这个 map;命中正是预计算缓存的意义。

三个访问器

访问器地址读取 map[obj+0] 为以下值时有效返回
GetRouteDistance(src,dst)0x20b615c0+0x8 distance0superpod::routing::Coordinates(有符号逐轴向量)
GetRoutePath(src,dst)0x20b61720+0x28 path12vector<proto::Direction>(单条跳序列)
GetRoutePaths(src,dst)0x20b61880+0x48 random-hop paths3vector<vector<proto::Direction>>(等成本路径集合)

分派骨架

三者的形态在字节上完全相同(find 实例化和源代码行标签不同)。逻辑是类型保护后接键查找,有两个不同的错误出口:

c
// 0x20b61720 — GetRoutePath(this <- StatusOr<...> storage, obj, src, dst)
StatusOr<...> GetRoutePath(const ToroidalRouteCache* obj, int src, int dst) {
    pair<int,int> key{src, dst};

    // --- STAGE 1: type guard. Probe whether THIS object's layout can answer the
    //     requested representation at all. The guard is keyed on [obj+0]. ---
    if (obj->_cache_type_ == 3) {                          // object holds random-hop sets …
        if (random_paths_map.find(key)) goto found;        //   +0x48
        goto type_mismatch;
    }
    if (obj->_cache_type_ != 0) {                          // type 1 or 2: holds vector<Direction>
        if (path_map.find(key)) goto found;                //   +0x28
type_mismatch:
        // requested a path, but layout is distance (type 0) → wrong representation
        return MakeErrorImpl<9>(                           // toroidal_route_cache.cc:393
            "ICI route cache is not stored in full path format.");
    }
    // _cache_type_ == 0 (distance) but caller wants a path → type mismatch
    if (!distance_map.find(key)) goto type_mismatch;       //   +0x8

    // --- STAGE 2: key lookup in the CORRECT map for this accessor ---
found:
    if (path_map.find(key)) {                              //   +0x28 again, return the value
        this->value = &found_value;                        //   16-byte vector header copy
        this->discriminant = 1;
        return this;
    }
    // key absent → distinct from a type mismatch
    return MakeErrorImpl<3>(FormatPack(                    // toroidal_route_cache.cc:401
        "ICI routing scheme between src %d and dest %d does not exist in cache.",
        src, dst));
}
```text

另外两个访问器相同,只是 stage-2 map 替换了:`GetRoutePaths` 查找 `+0x48` random-hop map(行 413/419),`GetRouteDistance` 查找 `+0x8` distance map(行 373/381),并且距离访问器的类型保护消息改为 `"… not stored in distance vector format."`。

> **注意 — 两种错误,不是一种。两者都会外显,且都不中止。** *类型不匹配*(`MakeErrorImpl<9>`,"not stored in … format")表示缓存*存在*,但持有的表示与访问器请求的不同——调用方 bug,例如在 distance-only 缓存上调用 `GetRoutePath`。*键未命中*(`MakeErrorImpl<3>`,"ICI routing scheme between src %d and dest %d does not exist in cache.")表示缓存类型正确,但没有该 pair 的条目——覆盖缺口。把二者合并为一种错误的重实现会误导诊断:前者表示“你用了错误的访问器”,后者表示“这个 (src,dst) 从未被烘焙”。状态码有意不同(9 = `FailedPrecondition`,3 = `InvalidArgument`)。
>
> **说明 — stage-1 探测的 map 顺序与 stage-2 *不同*** 在反编译中,每个访问器的 stage-1 guard 使用临时键 `__PAIR64__(dst,src)`,而 stage-2 使用 `{src,dst}`。guard 的目的只是先把布局/表示不匹配转换为类型不匹配错误,再执行真正查找;值查找的规范 `(src,dst)` 键是 stage-2 的键。重实现应将 stage 1 实现为针对访问器期望类型的纯 `_cache_type_` 检查;探测式 `find` 是内联 guard 的产物,不影响正确性。

### 命中会产生什么

`GetRoutePath` 命中会返回一个 `vector<proto::Direction>`:对于最小距离 pair,它是严格按维度排序的跳列表(所有 X 跳,然后 Y,再 Z);对于最小路径会穿过故障链路的 pair,它是 wild-first 绕行(一跳非最小跳出,剩余按维度排序,一跳跳回)。下游会把每个 `Direction` 降级为 `output_link_index`(0..3 SerDes 端口)以及 `PerLinksRoutingTable` 中的一行 `next_hop_chip_id`,片上路由引擎会索引该表。`Direction` 打包(`orientation` + `polarity`)与实时生成器共享,见 [`get-static-path.md`](get-static-path.md);逐跳位编解码见 [`route-cache-codec.md`](route-cache-codec.md)。`GetRouteDistance` 命中返回该 pair 的有符号 torus 归约距离向量,也就是 [`get-distances.md`](get-distances.md) 实时计算的同一量。