路由模块设计规格
版本:1.3.2 状态:Accepted 创建日期:2026-04-26 最后更新:2026-05-23 作者:xiang.li
版本号规则 (SemVer):
- major (X.0.0):架构变更、接口不兼容、公式体系重构
- minor (1.X.0):新增模型/算法、新增章节、扩展接口(向后兼容)
- patch (1.0.X):修正笔误、补充说明、更新 Implementation Notes
变更历史
| 版本 | 日期 | 变更说明 |
|---|---|---|
| 1.3.2 | 2026-05-23 | 支持 per-flowlet Adaptive Routing 算法实现(fat-tree 等价多路径间的 flowlet 级负载均衡,LetFlow style 纯 hash 切换,无拥塞反馈):(1) Config attrs schema 新增 flowlet_gap_ns: float(同一 flow 两 packet 间隔超过该阈值视为新 flowlet,触发重新选路);(2) 新增第 6 类 Layer 2 Policy PerFlowletAdaptivePolicy——cache key (flow_id, flowlet_id) → path_index,flowlet_id 由 now - last_pkt_ts > flowlet_gap_ns 单调递增;(3) @tbl-22 Strategy↔Policy 映射新增 (first_hop, multi) → PerFlowletAdaptivePolicy 行(与既有 (first_hop, distribution) → FirstHopAdaptivePolicy 区分);(4) @tbl-23b descriptor schema 新增 per_flowlet_adaptive 行(含 flowlet_gap_ns 必填字段);(5) 新增 L2-C7 约束——flowlet 缓存语义(同 flowlet 内严格粘滞 / flowlet 边界处允许换路径),明确与 L2-C1 "FirstHopAdaptive 全 flow 粘滞"的区分;(6) @tbl-13a 决策频次 first_hop 行典型算法补 per-flowlet AR + 表注澄清 "首跳期或 flowlet 边界期";(7) §业界参考补 NVIDIA Quantum ar_ftree / LetFlow / CONGA 引用。SemVer 决策:本次新增 1 类 Layer 2 Policy + 1 行 descriptor schema + 1 条 L2 约束,按 §风险与限制 "5 类 Policy 覆盖不够 → 新增 Policy 类需 bump spec minor 版本" 严格判定本应 minor v1.4.0;用户裁定 patch v1.3.2,理由:既有 first_hop decision_frequency 类目下加变体(hash 触发 vs queue-aware 触发),不引入新核心抽象、不修改 Goals、不破坏 L2-C1 / L2-C3 既有约束。变更历史记账保留 SemVer 决策痕迹 |
| 1.3.1 | 2026-05-23 | 支持 D-mod-k 算法实现(Fat-Tree 专用,static + single,复用 StaticTablePolicy):(1) Edge attrs schema 新增 level: int + port_idx: int(D-mod-k 消费,标识 fat-tree 边层级 + 显式物理端口号,决策 1 选 EdgeSpec attrs 而非字典序隐式契约);(2) Config attrs schema 新增 w_levels: list[int] + p_levels: list[int](PGFT 通用参数,标准 k-ary n-tree 是特例 w_l=k, p_l=1);(3) §Non-Goals 新增 NG9 "不处理 fat-tree 故障 / 降级拓扑"——D-mod-k 均匀性依赖完整对称性(RLFT 等分带宽),降级下 Lemma 3 整除性失效;(4) §业界参考补 D-mod-k 闭式公式选路特例段;(5) §未来方向加 Dmodc(降级 fat-tree 改进);(6) §风险与限制加"邻居命名约束已通过 port_idx 显式化"决策痕迹。SemVer 决策:本次升 patch v1.3.1 而非 minor,理由是 NG9 仅明确既有限制(D-mod-k 算法本身依赖 fat-tree 对称性是业界常识),不引入新范围契约;与 v1.3.0 加 NG8 配合新算法 + spec 大改升 minor 不同质 |
| 1.3.0 | 2026-05-23 | (1) Config attrs schema 新增 congestion_aware: bool key(PacketSpray 必填),用于支持 Layer 1 PacketSprayStrategy 实现(per_packet + multi);(2) §Non-Goals 新增 NG8"不建模报文乱序 / 重排序"——乱序是 packet 级路径分散的直接语义后果,单独成项以明确范围契约(spec review F001 决策);(3) §业界参考补 Packet Spraying 选路权重特例段;(4) §未来方向"Packet Spraying / DQPLB"行标 Packet Spraying 接口已就绪;(5) §验收标准 加 congestion_aware schema 校验单测一行;(6) 复用 @tbl-22 既有 per_packet → PacketSprayPolicy 映射,不新增行。属新增算法接口 + 范围扩展,minor bump |
| 1.2.1 | 2026-05-22 | 修复 v1.2.0 的 5 处内部矛盾 / 漏洞:(1) 修正 §decision_frequency 枚举语义表中 ECMP 描述(归 static 与 §Strategy↔Policy 映射表对齐);(2) minimal_count 改为 Config attrs schema 配置项(类属性降级为缺省值),消解"类属性硬编码 = 1 但 UGAL 变体需不同值"矛盾;(3) 新增 PolicyDispatcher 抽象(Layer 2 包装层)承载 L2-C2 "NetworkState 未声明字段保护"约束,trait 层无法 enforce 的 issue 在 dispatcher 层解决;(4) 新增 §Policy Descriptor Schema 表,固定 Layer 1 → Layer 2 启动期桥接协议字典,消除 v1.2.0 启发式推断;(5) §NetworkState 字段表加"最低精度要求"列,禁止 link_load / flow_count_on_link 常返 0 |
| 1.2.0 | 2026-05-20 | 引入双层架构:Layer 1 RoutingStrategy(Python,全局路径计算)+ Layer 2 ForwardingPolicy(Rust,G5 仿真期每跳决策);新增 NetworkState 观测接口契约(adaptive 算法消费运行时状态);RoutingStrategy 增 2 类属性 decision_frequency / requires_runtime_state;明确 PathSet 在不同 output_form 下的语义(等代价 / minimal-valiant 分段);spec 范围扩展到 Rust 侧仿真器集成 |
| 1.1.0 | 2026-04-27 | 选路权重改为带宽倒数(对齐 OSPF RFC 2328);路径延迟与选路权重解耦;删除 tier 分解抽象(边 type 即层级标识) |
| 1.0.0 | 2026-04-26 | 初版:定义路由模块的概念模型、四个核心抽象的接口契约,以及"边可选属性 + 算法显式声明消费"的族特征载体设计 |
@tbl-spec-route-01 文档变更历史
背景与目标
概述
路由模块为 G5 事件驱动仿真提供 (通信请求 → 物理路径) 的服务。模块以族无关的通用图为输入;族特定信息(维度归属、链路角色等)作为边的可选属性表达;路由算法显式声明它消费哪些属性,从而支持任意拓扑族而不必引入族抽象。模块内部由四个稳定抽象组成:NetworkGraph(图)、RoutingRequest(请求)、RoutingStrategy(算法)、RoutingResult(结果)。
G5 仿真对路由模块的能力需求
G5 是事件驱动仿真器,每个通信事件需要决定"走哪条/哪些物理路径",并基于路径推进后续 switch 队列、链路争用等事件。路由模块要回答四类问题:
| 问题 | 用途 |
|---|---|
| 给定 (src, dst,流量元信息),使用哪些物理路径? | 报文级转发决策 |
| 路径上每条物理边承担多少流量? | 链路争用建模 |
| 通信的累计延迟和瓶颈带宽是多少? | 时间推进 |
| 当算法是逐报文随机或动态决策时,决策结果是分布而非单条路径 | 表达 Packet Spraying / UGAL 等算法 |
@tbl-spec-route-02 G5 仿真对路由模块的能力需求:问题,用途
多拓扑族的差异化路由需求
13 个拓扑族(ring / fat-tree / dragonfly / dragonfly+ / torus-1d/2d/3d / mesh / hyperx / hypercube / slimfly / polarfly / single-switch / full-mesh)的最优路由算法不同:fat-tree 用 ECMP,torus 用 DOR(维度顺序),dragonfly 用 UGAL(最短路与 Valiant 路混合),spraying 类需要逐报文随机化。模块必须支持算法插拔,且新增算法不应改动图模型或现有算法的代码。
通用图描述与族特征的张力
项目的拓扑 YAML 已设计为族无关的通用图描述(network.links 段是通用边列表,无 topo_kind 字段)。这一设计哲学的好处是拓扑生成器、校验器、可视化对所有族同构。但 DOR/UGAL 等族特定算法需要"维度归属""组身份"等族特征 — 这些特征必须以不破坏族无关图描述的方式进入算法。
需要的能力可以归结为:图模型族无关 + 算法对族特征显式声明 + 多种路径输出形态统一表达。
名词
| 名词 | 定义 | 区分 |
|---|---|---|
| NetworkGraph | 节点 + 边 + 节点物理属性 + 边物理属性 + 边可选 attrs | 仅描述图结构与物理属性,不含族标签 |
| RoutingRequest | 单次通信请求的输入对象(src、dst、流量元信息) | G5 视角下"一次通信"的边界 |
| RoutingStrategy | 路由算法实现 | 通过 consumes_edge_attrs 声明信息依赖 |
| RoutingResult | 路径决策的结果对象 | 提供路径、边占用、属性查询接口 |
| Path | 单条物理路径(节点序列 + 边序列) | RoutingResult 的内部基本元素 |
| attrs | 边上的可选字典属性 | 族特征的载体;合法 key 由模块 schema 集中维护 |
| 输出形态 | 路径决策的形式:single(单路径) / multi(路径集合) / distribution(路径分布) | 由 Strategy 声明,与算法的"路径多样性"标签一致 |
| 算法注册中心 | 已知 RoutingStrategy 的命名表,工厂据此实例化 | 与 attrs schema 是两个不同的注册表 |
| ForwardingPolicy | Layer 2 Rust 侧 trait,承接 Layer 1 PathSet 做仿真期每跳决策 | 与 Strategy 的生命周期不同:Strategy 启动期算路径、Policy 运行期选下一跳 |
| NetworkState | Layer 2 只读 trait,仿真器暴露给 Policy 的运行时观测接口(链路负载、队列深度等) | 只读;按 requires_runtime_state 声明字段访问 |
| PathSet | RoutingResult 中的路径集合(同 paths() 迭代器内容) | single / multi / distribution 三种形态有不同语义(见 §PathSet 语义) |
| minimal_count | distribution 形态算法的额外类属性,标记 PathSet 中前 N 条为 minimal 路径 | 仅 distribution 必填,其他形态算法为 None |
| decision_frequency | RoutingStrategy 类属性,标记算法决策时机(static / first_hop / per_hop / per_packet) | 决定 G5 加载期挑选哪种 ForwardingPolicy |
| PolicyDispatcher | Layer 2 包装层,持有 ForwardingPolicy + Strategy 声明的 requires_runtime_state,在每次 next_hop 调用前后审计 Policy 的 NetworkState 字段访问 | 在 trait 层无法 enforce 的"未声明字段保护"(L2-C2)由该层运行时拒绝;不参与算法语义,仅做契约校验 |
| flow 首跳 | flow 第一个 packet 经过的首个 switch hop——即 FirstHopAdaptive 与 EcmpHash 类算法的决策时机 | 与 flowlet 首跳 区分(前者粒度 = flow,后者粒度 = flowlet) |
| flowlet 首跳 | flowlet 起始 packet 经过的首个 switch hop——即 PerFlowletAdaptive 算法的决策时机;同一 flow 内每个 flowlet 各有一次"首跳" | 当 flow 内仅 1 个 flowlet 时,flowlet 首跳 == flow 首跳 |
| flowlet | 同一条 flow 中的一段连续突发,由两 packet 间隔超过 flowlet_gap_ns 阈值切分;同一 flowlet 内 packet 必须沿用同一路径(L2-C7.c) | flow 是路由模块抽象的最小流标识(src/dst/qp_id 等),flowlet 是 flow 的子粒度划分 |
@tbl-spec-route-03 名词
目标
Goals:
- G1:以单一统一接口为 G5 提供
(请求 → 路径)服务,调用方面对RoutingResult一种契约消费三种输出形态 - G2:图模型族无关 — 任何拓扑族都用同一份 NodeSpec / EdgeSpec schema 表达;族特征通过边的
attrs字段流入 - G3:算法插拔 — 新增算法只需实现 RoutingStrategy 接口、注册到工厂、声明属性消费;不需要改图模型或既有算法
- G4:算法显式声明 attrs 消费 —
consumes_edge_attrs/consumes_config_attrs列出 key 集合;加载期对 graph 与 routing.attrs 双层校验;零代码默认值(遵守.claude/rules/config-loading.md) - G5:三种输出形态(single / multi / distribution)统一在 RoutingResult 接口下表达,避免 isinstance 分支
- G6:模块边界严格 — 路由模块的输出止于 RoutingResult;如何被通信延迟模型 / 争用模型 / 仿真器消费不在本 spec 范围
- G7:在不引入 per-hop Python ↔ Rust 回调成本的前提下,把 G5 仿真期每跳决策、运行时状态消费、5 类常见路由模式的语义统一约束在 spec 内(v1.2.0 引入:Layer 1 RoutingStrategy + Layer 2 ForwardingPolicy + NetworkState)
不涉及范围
- NG1:不实现完整闭环 SDN 反馈控制——Layer 2 NetworkState 仅暴露只读观测,policy 不能修改仿真状态;仿真器内部并发控制 / 跨进程协议不在本 spec 范围。运行时反馈(出口队列深度、链路负载等)通过 NetworkState 暴露
- NG2:不为 Math 代数模型做专属优化(接口以 G5 需求为主;Math 接入沿用相同接口即可)
- NG3:不引入 SDN / SRv6 / 中央控制器
- NG4:不解决跨进程的路由决策(路由模块仍是进程内库)
- NG5:不做拓扑发现 / 故障恢复 / 链路抖动建模
- NG6:不引入族抽象(TopologyDescriptor / 子类化 EdgeSpec);族信息只以边 attrs 形式存在
- NG7:不基于图特征隐式推断算法;
routing.algorithm必须由用户在 YAML 显式声明。算法选择是实验意图,由模块自动选会破坏对比实验、损害可重复性、引入图特征歧义 - NG8:不建模报文乱序 / 重排序。Packet Spraying 等 per-packet 算法在物理上必然产生乱序到达,上层协议(RDMA / TCP)需重排序缓冲恢复有序——本模块仿真侧仅产出"哪些报文走哪条路径 / 何时到达",不建模重排序延迟、缓冲占用、丢包重传等上层语义。需要时另立 spec 处理
- NG9:不处理 fat-tree 故障 / 降级拓扑。D-mod-k 算法的无阻塞性质依赖完整 fat-tree 对称性(RLFT 等分带宽约束 $m_l \cdot p_l = w_{l+1} \cdot p_{l+1}$),任一链路 / switch 故障破坏 Lemma 3 整除性后,D-mod-k 在 Shift 流量下的均匀性失效。本模块不建模故障 / 降级;需要降级场景适配时另立 spec 引入 Dmodc(Gliksberg et al., IEEE MICRO 2023)等改进算法。与 NG5 的区分:NG5 排除"建模故障事件本身"(仿真侧能力边界——不模拟链路抖动 / 拓扑发现),NG9 进一步排除"对降级拓扑下算法性质失效的补偿"(算法正确性前提——D-mod-k 需要对称性);两者语义不冲突且互补。NG9 作为范围契约:未来引入 Dmodc 取消 NG9 时是 minor bump 触发条件(与新算法接口绑定)
业界参考
业界路由建模的"族 vs 通用图"张力
| 工具 | 族信息载体 | 算法切换方式 | 抽象代价 | 链接 |
|---|---|---|---|---|
| BookSim 2 | 按族分代码文件(fattree.cpp / dragonfly.cpp / torus.cpp) | 编译期符号绑定 | 13 族 ≈ 13 套独立路由代码 | https://github.com/booksim/booksim2 |
| SST/macro | 拓扑工厂对象(class FatTree、class Dragonfly) | 拓扑工厂驱动算法 | 拓扑与算法绑死,复用性低 | https://github.com/sstsimulator/sst-macro |
| ASTRA-sim 2.0 | network backend 内部隐藏 | backend 选择(NS-3 / Garnet / Analytical) | 路由不是一等公民 | https://github.com/astra-sim/astra-sim |
| SimGrid | XML 平台描述 + 算法属性(Floyd / Dijkstra / Cluster) | XML 声明 | 表达力有限,复杂算法需 C++ 扩展 | https://simgrid.org |
| HTSim | 路径选择嵌在 Pipe / Queue 层 | C++ PathChooser | 不支持异构拓扑混合 | https://github.com/Broadcom/csg-htsim |
@tbl-spec-route-04 业界路由建模的"族 vs 通用图"张力:工具,族信息载体
共同模式:图 + 算法 + 路径结果的三层结构是普遍设计;本模块继承这一模式但额外增加 RoutingRequest 抽象以承载流量元信息(QP id、消息类型)。
本模块的差异化设计:族特征以"边的可选属性 + 算法显式声明消费"形式融入通用图,避免:
- 子类化方案的类爆炸(13 族 × 多算法)
- 族抽象渗透到接口(违反 YAML 的"通用图描述"哲学)
- 算法图反推的歧义(部分图特征不足以唯一确定族)
选路权重的业界实践
路由算法的"权重"(决定路径选择的代价度量)在不同算法中有截然不同的定义:
| 算法 | 选路权重 | 来源 |
|---|---|---|
| OSPF/IS-IS → ECMP | 带宽倒数(Cost = 参考带宽 / 链路带宽) | RFC 2328 (OSPF Version 2) |
| DOR | 无权重,坐标确定性转发 | Dally & Seitz, 1987 |
| UGAL | 动态代价(队列深度 + 跳数 × w_hop) | Kim, Dally, Abts, ISCA 2008 |
| BookSim / KSP | 跳数 | — |
@tbl-spec-route-05 选路权重的业界实践:算法,选路权重
关键结论:业界主流的静态选路权重是带宽倒数(OSPF),不是延迟或跳数。带宽倒数的语义是"优先走高带宽链路",对 AI 集群的大块集合通信场景尤为合适。本模块 shortest_path / ECMP 采用带宽倒数作为选路权重,与 OSPF 对齐。
报文级散布的特例:Packet Spraying / DRB 不采用"权重"概念,而是以报文 ID 或 EV(Entropy Value)字段做哈希散布到等代价多路径,散布的均匀性来自统计分布而非权重选优;详见 3.7 Packet Spraying。
闭式公式选路的特例:D-mod-k(Zahavi, CCIT #776, 2010)以目的节点编号的位段做模运算选择上行端口,PGFT 通用形式:
$$\begin{equation} q_l(j) = \left\lfloor \frac{j}{\prod_{k=1}^{l} w_k} \right\rfloor \bmod (w_{l+1} \cdot p_{l+1}) \label{eq:spec-route-dmod-k-quotient} \end{equation}$$标准 k-ary n-tree 是特例($w_l = k, p_l = 1$)退化为 port = floor(d/k^l) mod k。给定 PGFT 参数(@tbl-spec-route-11 的 w_levels / p_levels)即可闭式计算路径,不需要 Dijkstra / 权重;OpenSM ftree 路由引擎是其工程实现。与权重路由不同,D-mod-k 的"最优性"来自代数结构(Lemma 1-3 整除性 + RLFT 等分带宽)而非启发式优化,需要拓扑严格对称(NG9)。详见 3.3 D-mod-k。
选路权重与路径延迟的分离:选路权重决定"走哪条路",路径延迟是"走完这条路要多久"。两者是独立关注点 — 带宽倒数用于路径选择(公式 $\eqref{eq:rt-edge-weight}$),延迟用于结果计算(公式 $\eqref{eq:rt-path-latency}$)。不同算法可能使用不同权重(DOR 无权重、UGAL 用动态代价),但路径延迟计算公式对所有算法通用。
路由算法选型空间
../interconnect/03-路由算法/ 已 survey 7 种路由算法(ECMP、Adaptive Routing、DOR、UGAL、Packet Spraying、KSP、选型指南)。本 spec 引用其结论作为算法能力空间的描述背景,不重复算法细节。每个算法所需的图特征(坐标、组身份、链路角色等)映射为 attrs 中的若干 key。
项目内引用
- 算法 survey:../interconnect/03-路由算法/
- 拓扑 YAML 通用化设计:项目
configs/topologies/_template.yaml与.claude/rules/sync-templates.md
使用示例
G5 仿真器一次典型路由调用:
# 1. 模块加载期
yaml_data = load_yaml("configs/topologies/torus-3d-C64.yaml")
graph = NetworkGraph.from_yaml(yaml_data)
# 工厂构造期校验 routing.attrs 是否齐全(模块级 + dor 的 consumes_config_attrs)
# 缺失 raise MissingConfigAttr,类型错 raise InvalidConfigAttrType
strategy = create_routing_strategy(
name=yaml_data["network"]["routing"]["algorithm"], # "dor"
attrs=yaml_data["network"]["routing"]["attrs"], # 直接透传,不做默认值填充
)
# bind 期校验:strategy.consumes_edge_attrs = {"dimension", "wraparound"}
# 检查 graph 所有边的 attrs 是否包含这两个 key 且类型正确;缺失 raise IncompatibleStrategyError
strategy.bind_graph(graph)
# 2. 仿真运行期,每次通信
request = RoutingRequest(src="c0", dst="c63", bytes=1024, kind="all-reduce")
result = strategy.compute(request)
# 3. G5 消费 result(接口统一)
for path in result.paths(): # iter[Path],与 output_form 无关
for edge_id in path.edges:
...
result.edge_load("e_42") # 用于争用建模
result.bottleneck_bandwidth() # 用于带宽估计
result.latency()
YAML 描述对所有族同构,族特征通过边的 attrs 字段表达:
network:
link_types:
c2c: { bandwidth_gb_per_s: 400, latency_us: 0.2 }
links:
# 通用边:通用算法看 from/to/type 即可工作
- {from: c0, to: c1, type: c2c}
# 带族特征的边:DOR / UGAL 等算法读 attrs
- {from: c2, to: c3, type: c2c, attrs: {dimension: x, wraparound: false}}
- {from: r0, to: r4, type: c2c, attrs: {role: global}}
switches: [...]
routing:
algorithm: dor # 必填
attrs: # 必填字典;模块级 + 算法级 key 都必填
bandwidth_utilization: 0.9 # 模块级
dimension_order: [x, y, z] # DOR 算法级
dateline_vc: true # DOR 算法级
通用算法(shortest_path / ECMP)忽略 attrs 段;族特定算法(DOR / UGAL)按声明读取。
设计详述
概念模型
模块由四个稳定抽象组成:
┌────────────────┐ ┌─────────────────┐ ┌──────────────┐
│ NetworkGraph │ │ RoutingRequest │ │ RoutingResult│
│ (图与物理属性) │ │ (一次通信请求) │ │ (决策结果) │
└────────┬───────┘ └────────┬────────┘ └──────▲───────┘
│ │ │
│ bind_graph() │ compute(request) │
│ │ │
└────────┐ ┌───────┘ │
▼ ▼ │
┌─────────────────┐ │
│ RoutingStrategy │────────────────────┘
│ (算法实现) │
└─────────────────┘
▲
│ create_routing_strategy(name, config)
│
┌──────┴──────┐
│ 算法注册中心 │ + attrs schema(独立注册)
└─────────────┘
责任边界
| 抽象 | 层级 | 职责 | 不负责 |
|---|---|---|---|
| NetworkGraph | Layer 1 (Python) | 节点和边的图论表示,节点物理属性、边物理属性、边可选 attrs | 不感知拓扑族;不缓存路由结果;不感知算法配置 |
| RoutingRequest | Layer 1 (Python) | 描述单次通信请求的输入:源、目、字节数、通信类型、QP id 等 | 不携带运行时网络状态;不与具体算法耦合 |
| RoutingStrategy | Layer 1 (Python) | 实现具体路由算法的全局路径计算;声明属性消费 + 决策频率 + 运行时状态需求;提供 bind 与 compute 两阶段接口 | 不做 per-hop 决策;不感知运行时网络状态;不持久化结果 |
| RoutingResult | Layer 1 (Python) | 路径决策结果的统一查询接口(PathSet + 派生指标) | 不重新执行算法;不修改自身内容 |
| ForwardingPolicy | Layer 2 (Rust) | G5 仿真期的每跳决策:从 Layer 1 计算出的 PathSet 中选择下一跳;可选消费 NetworkState | 不计算路径(路径来源于 Layer 1 的 RoutingResult);不修改图与算法配置 |
| NetworkState | Layer 2 (Rust) | 暴露仿真期可观测的网络状态(link 负载、队列深度等),供 adaptive 类 Policy 消费 | 只读;不写仿真状态;不感知算法 |
| PolicyDispatcher | Layer 2 (Rust) | 包装 ForwardingPolicy + Strategy 声明的 requires_runtime_state,向 Policy 暴露受限 NetworkState 视图;运行时审计字段访问 (L2-C2) | 不实现算法语义;不修改 state;不缓存路径 |
@tbl-spec-route-06 责任边界:抽象,职责
设计原理
为什么族特征用边的可选属性?
族特征(维度归属、组身份、链路角色)本质是图上的"标签",不是节点边的物理本质。将其作为边的 attrs:
- graph 模型保持族无关 — 与项目通用 YAML 描述哲学一致
- 通用算法不读这些字段也能工作 — shortest_path 不感知 attrs
- 族特定算法显式声明消费 → 类型 / 拼写错误在加载期暴露,不在运行时沉默
否决的替代方案:
- TopologyDescriptor / 族子类化:把族抽象升级为接口一等公民,违反 YAML 设计哲学,且导致 13 族类爆炸
- 算法从图反推族特征:部分图特征不足以唯一确定族(4×4×4 vs 8×8×4 Torus 边数差异微妙),实现复杂且歧义
为什么 Strategy 显式声明 consumes_attrs?
如果不声明,attrs 字典会成为"什么都能塞"的杂物间,长期会出现:拼写错误(dim vs dimension)在运行时沉默忽略;不同算法对同一 key 的语义假设不一致;attrs schema 漂移无人维护。
显式声明 + 加载期校验带来三个好处:
- 错误前置:拼错 key 在
bind_graph时立即 raise,不到compute才发现 - schema 可枚举:模块用到的所有 attrs key 是有限集合,可在文档与单测中穷举
- 算法-图兼容性显式:用户看到 Strategy 的
consumes_edge_attrs就知道它对图的要求
为什么 RoutingResult 不强制全网预计算?
不同算法的"路径"语义截然不同:
- DOR:闭式公式(给定 src, dst 立即计算),全表存储是浪费
- ECMP:路径集合(适合预计算缓存)
- UGAL:流量分布(每次决策动态加权,预计算意义不大)
- Packet Spraying:逐报文随机(路径不是确定值,是分布)
强制 N×N 预计算表会让闭式算法浪费内存、动态算法失去精度。RoutingResult 是接口,实现可选 lazy / 闭式 / 全表,由具体 Strategy 内部决定。
为什么 Strategy 用 bind_graph + compute 两阶段?
- bind_graph 是绑定时机:算法在此期校验 graph 是否提供它声明的 attrs,并完成预计算(如 ECMP 全网最短路)
- compute 是查询时机:每次仅需 request,graph 已绑定
分离两者:避免每次 compute 重复校验或预计算;让"算法 - 图"绑定关系显式(一个 Strategy 实例对应一个 graph 绑定)。
为什么 RoutingResult 既提供 paths() 也提供 edge_load()?
paths() 服务于"我要看具体走哪条路"的用例(G5 报文级转发);edge_load() 服务于"这条边承担多少流量"的用例(争用建模)。两者是同一决策的不同视图:
- 单路径算法:edge_load 退化为 0/1
- 多路径算法:edge_load 是流量在路径上的均分比例
- 分布算法:edge_load 是路径分布在该边上的边缘概率
调用方按用途选择视图,不需关心算法形态。
为什么把路由拆成 Layer 1(Strategy)+ Layer 2(ForwardingPolicy)?
事件驱动仿真有两种根本不同的决策时机:
- 全局路径计算(static):给定 (src, dst),"可能走哪些路径"——一次性回答,启动期完成
- 每跳前进决策(runtime):给定 current_node 和实时状态,"下一跳去哪"——仿真期高频调用
单层方案(让 Strategy 同时承担两者)的问题:
- 性能:Python ↔ Rust 跨语言调用成本高;如果每跳决策都回调 Python,per-packet 仿真性能崩塌
- 职责混杂:static 算法的"全局视图"和 adaptive 算法的"运行时视图"塞进同一接口,复杂度向 max 对齐
- 不可扩展:未来加入 Q-routing / TE-CCL 等真正动态算法时,必须改 Strategy 接口
双层方案的取舍:
- Layer 1 留在 Python:算法设计灵活、生态成熟、与 Math 评估器复用
- Layer 2 在 Rust:per-hop 决策零跨语言开销;适合 trait 化承接 5 类算法
- 桥接面薄:仅启动期一次传 PathSet + Policy descriptor,运行期 Layer 2 自洽
代价:spec 跨语言;新增算法需同时在 Layer 1 实现路径计算和 Layer 2 实现选路逻辑(但 5 类 Policy 已覆盖常见模式,多数新算法可复用现有 Policy)。
为什么 spec 必须覆盖 Rust 侧契约?
历史上 spec 只描述 Python 侧;但 Layer 2 引入了算法语义跨语言的强约束——例如"FirstHopAdaptivePolicy 的后续跳必须沿用首跳决策"是算法正确性约束,不是实现细节。若 spec 不覆盖 Rust 侧,则 Layer 2 实现的算法语义无外部参考,容易与 Layer 1 漂移。
spec 跨 Python / Rust 边界后:
- 接口契约:在 spec(语义层描述 trait、字段、约束)
- 具体实现:在 plan + code(Rust 文件路径、struct 字段、错误处理)
边界保持清晰:spec 用伪代码 + 语义描述,不写具体 Rust 语法 / 文件路径 / 函数实现。
数据契约
NetworkGraph
| 字段 | 类型 | 必选 | 含义 |
|---|---|---|---|
| nodes | dict[NodeId, NodeSpec] | ✅ | 节点字典 |
| edges | dict[EdgeId, EdgeSpec] | ✅ | 边字典 |
| adjacency | dict[NodeId, set[EdgeId]] | ✅ | 邻接索引(构造期生成) |
@tbl-spec-route-07 NetworkGraph:字段,类型
NodeSpec(纯物理属性):
| 字段 | 类型 | 必选 | 含义 |
|---|---|---|---|
| id | NodeId | ✅ | 唯一标识 |
| kind | "chip" | "switch" | ✅ | 节点类型 |
| port_count | int | None | switch 必选 | 端口数 |
| forwarding_latency_ns | float | switch 必选 | 转发延迟(纳秒,与 YAML schema 及 Layer 2 NetworkState 的 _ns 命名统一) |
@tbl-spec-route-08 NetworkGraph:字段,类型
EdgeSpec(物理属性 + 可选 attrs):
| 字段 | 类型 | 必选 | 含义 |
|---|---|---|---|
| id | EdgeId | ✅ | 唯一标识 |
| src | NodeId | ✅ | 源端点 |
| dst | NodeId | ✅ | 目标端点 |
| bandwidth_gb_per_s | float | ✅ | 物理带宽(单方向) |
| latency_us | float | ✅ | 物理延迟 |
| type | str | ✅ | 链路类型(如 c2c / b2b / r2r / p2p) |
| attrs | dict[str, Any] | 可选 | 族特征载体;合法 key 见 edge attrs schema;缺省视为该边无族特征(不参与族算法消费) |
@tbl-spec-route-09 NetworkGraph:字段,类型
attrs Schema(双层中央维护)
模块维护两份 attrs schema,分别约束图层与配置层的字典。每个 key 声明类型与含义。
Edge attrs schema(约束 EdgeSpec.attrs,描述图上的族特征):
| key | 类型 | 含义 | 典型消费算法 |
|---|---|---|---|
| dimension | str | 边所属逻辑维度(如 "x" / "y" / "z") | DOR |
| wraparound | bool | 是否环绕链路(用于 Torus dateline VC 标记) | DOR (Torus) |
| role | "local" | "global" | "intra" | 链路在族结构中的角色 | UGAL |
| level | int ∈ [0, h-1] | fat-tree 边层级(0=chip→leaf-switch、1=leaf→spine、2=spine→core 等);h = len(w_levels) 树层数;图中所有 fat-tree 边的 level 取值范围与 w_levels 长度对齐(约束 C7) | D-mod-k |
| port_idx | int ≥ 0 | 边在 src 节点的物理端口编号(与 D-mod-k 公式产出的端口编号 q 对齐;Strategy 直接读 attrs 找邻居 switch,避免依赖邻居名字典序的隐式契约) | D-mod-k |
@tbl-spec-route-10 attrs Schema(双层中央维护):key,类型
Config attrs schema(约束 routing.attrs,描述路由运行参数):
| key | 类型 | 含义 | 消费方 |
|---|---|---|---|
| bandwidth_utilization | float ∈ (0, 1] | 多路径聚合带宽折扣,作用于公式 \eqref{eq:rt-aggregate-bw} | 模块层(所有 Strategy 共用) |
| dimension_order | list[str] | 维度路由顺序(如 ["x", "y", "z"]) | DOR |
| dateline_vc | bool | 是否启用 Torus dateline 虚通道 | DOR (Torus) |
| max_paths | int | 多路径算法最多返回的路径数 | ECMP / KSP / PacketSpray |
| bias | float | UGAL 最短路与 Valiant 路决策偏置 | UGAL |
| valiant_k | int | Valiant 中间组候选数 | UGAL |
| minimal_count | int ≥ 1 | distribution 形态 PathSet 中前 N 条为 minimal 路径、其余为 valiant 候选 | UGAL 及其他 distribution 算法 |
| congestion_aware | bool | Packet Spraying 拥塞感知模式开关:false=纯哈希散布(path_idx = hash(packet_id) mod N),true=对每个候选路径查首跳 link_load 后选最低者(argmin) | PacketSpray (必填) |
| w_levels | list[int ≥ 1] | PGFT 各层上行链路数 $w_l$(标准 k-ary n-tree 特例:$w_l = k$ 各层常数);长度 = fat-tree 层数 h;与 p_levels 等长;与图中 max(edge.level) + 1 一致(约束 C7) | D-mod-k (必填) |
| p_levels | list[int ≥ 1] | PGFT 各层 parent group 大小 $p_l$(标准 k-ary n-tree 特例:$p_l = 1$ 各层常数);长度同 w_levels(约束 C7) | D-mod-k (必填) |
| flowlet_gap_ns | float > 0 | 同一 flow 两 packet 间隔阈值(仿真时刻差 now - last_pkt_ts),超过该值视为新 flowlet 起点,触发重新选路;业界典型 50000–2000000 ns(即 50 μs - 2 ms),分负载场景:轻负载 50000–100000 ns / 中等 100000–500000 ns / 重负载(AI AllReduce 突发)500000–2000000 ns;详见 ../interconnect/03-路由算法/04-自适应路由.md @tbl-route-ar-flowlet-gap-values | PerFlowletAdaptive (必填) |
@tbl-spec-route-11 attrs Schema(双层中央维护):key,类型
Schema 维护规则:
- 新算法引入新 attrs key → 扩展对应 schema 表(增加一行)
- 边 attrs 含未注册的 key → 加载期 warning(约束 W1)
- routing.attrs 含未注册的 key → 加载期 warning(同 W1)
- routing.attrs 缺失被消费的 key → raise(见约束 C2、C6)
每个 RoutingStrategy 通过 consumes_edge_attrs / consumes_config_attrs 显式声明它消费哪些 key;模块层固定消费 config attrs 中的 bandwidth_utilization(所有算法共用)。
RoutingRequest
| 字段 | 类型 | 必选 | 含义 |
|---|---|---|---|
| src | NodeId | ✅ | 源节点 |
| dst | NodeId | ✅ | 目标节点 |
| bytes | int | None | 可选 | 消息字节数(用于报文级算法) |
| kind | "p2p" | "all-reduce" | "all-to-all" | "all-gather" | 可选 | 通信类型 hint |
| qp_id | int | None | 可选 | QP 标识(E-ECMP 类算法的哈希维度) |
@tbl-spec-route-12 RoutingRequest:字段,类型
RoutingStrategy
类属性(声明):
| 属性 | 类型 | 必选 | 含义 |
|---|---|---|---|
| name | str | ✅ | 算法名(工厂注册键) |
| output_form | "single" | "multi" | "distribution" | ✅ | 输出形态 |
| consumes_edge_attrs | set[str] | ✅ | 算法读取的边 attrs key 集合(通用算法声明为空集 set()) |
| consumes_node_attrs | set[str] | ✅ | 算法读取的节点 attrs key 集合(通用算法声明为空集 set(),保留扩展) |
| consumes_config_attrs | set[str] | ✅ | 算法读取的 routing.attrs key 集合(通用算法声明为空集 set()) |
| decision_frequency | "static" | "first_hop" | "per_hop" | "per_packet" | ✅ | 决策时机分类——决定 G5 加载期挑选哪种 ForwardingPolicy(见 §Strategy ↔ Policy 映射) |
| requires_runtime_state | set[str] | ✅ | 运行时状态需求 key 集合,G5 加载期据此校验仿真器已暴露对应 NetworkState 字段(静态算法声明为空集 set()) |
| minimal_count | int | None | distribution 必填 | 缺省值:当用户未在 routing.attrs.minimal_count 显式提供时,Strategy 使用此类属性值;显式提供时以 attrs 为准。仅 output_form="distribution" 算法必填类属性(≥ 1 的 int);其他形态算法声明为 None。运行时真实值经 RoutingStrategy.minimal_count 实例属性暴露(构造期由工厂从 attrs.minimal_count 或类属性缺省取值后赋值) |
@tbl-spec-route-13 RoutingStrategy:属性,类型
decision_frequency 枚举语义(命名易混淆,单列澄清):
| 值 | 何时做决策 | 是否查 NetworkState | 后续 hop | 典型算法 |
|---|---|---|---|---|
static | 启动期一次性确定路径(含 hash 选路这种"对 flow_id 的纯函数"决策) | 否 | 沿用启动期决策 | DOR / D-mod-k / shortest_path / ECMP |
first_hop | 首跳期或 flowlet 边界期查 NetworkState 或 sim_time 选路 | 是(NetworkState 字段)或仅查 sim_time | 沿用当前 flow / flowlet 的首跳决策(缓存语义见 L2-C1 / L2-C7) | UGAL / Valiant + queue-aware / per-flowlet AR |
per_hop | 每跳查 NetworkState 重新决策 | 是 | 不缓存 | Q-routing / Adaptive bubble |
per_packet | 每报文决策 | 可选 | 不缓存 | Packet Spraying / DRB |
ECMP 归 static 而非 first_hop 的理由:ECMP 的路径选择是 f(flow_id) → path_index 的纯函数(hash + 模运算),不读 NetworkState 任何字段。first_hop 的定义性特征是"首跳期消费运行时状态",ECMP 不符合。本表与下方 §Strategy ↔ Policy 映射表(static + multi → EcmpHashPolicy)严格对齐。
注:per-flowlet 自适应(业界 AR 常见粒度)归 first_hop——flowlet 起始期等价于"该 flowlet 的首跳"(见 §名词 "flowlet 首跳")。本 spec v1.3.2 内置的 per-flowlet AR 采用 LetFlow style 纯 hash 切换(不查 NetworkState 字段,仅消费 flow.sim_time_ns 判断 flowlet 边界 + hash 选路),requires_runtime_state = set();将来可扩展拥塞感知 flowlet AR 变体(如 CONGA / NVIDIA Quantum IB AR)补 link_load 消费,无需再改决策频次归属。
实例方法:
| 方法 | 入参 | 返回 | 含义 |
|---|---|---|---|
| bind_graph | graph: NetworkGraph | None | 绑定 graph,校验属性消费,完成预计算 |
| compute | request: RoutingRequest | RoutingResult | 单次路由决策 |
@tbl-spec-route-14 RoutingStrategy:方法,入参
通用算法(如 shortest_path)的 consumes_edge_attrs / consumes_node_attrs / consumes_config_attrs 均为空集合 → 任意图上工作。族特定算法(如 DOR、UGAL)consumes_edge_attrs 非空 → 加载期对 graph 校验;含运行参数的算法(如 ECMP / DOR / UGAL)consumes_config_attrs 非空 → 加载期对 routing.attrs 校验。
RoutingResult
接口(实现可 lazy):
| 方法 | 入参 | 返回 | 含义 |
|---|---|---|---|
| paths | — | iter[Path] | 路径迭代器。distribution 形态下,路径权重由 RoutingResult.path_weight(i) 单独查询;前 minimal_count 条为 minimal、其余为 valiant(见 §PathSet 语义) |
| edge_load | edge_id: EdgeId | float ∈ [0, 1] | 该边在本次结果上承载的流量比例 |
| bottleneck_bandwidth | — | float | 整体瓶颈带宽(公式 \eqref{eq:rt-aggregate-bw}) |
| latency | — | float | 主路径累计延迟(公式 \eqref{eq:rt-path-latency}) |
| hop_count | — | int | 主路径跳数 |
@tbl-spec-route-15 RoutingResult:方法,入参
Path 内部:
| 字段 | 类型 | 含义 |
|---|---|---|
| nodes | list[NodeId] | 节点序列(含 src 与 dst) |
| edges | list[EdgeId] | 边序列 |
@tbl-spec-route-16 RoutingResult:字段,类型
edge_load 的语义随 output_form 变化:
| output_form | edge_load 含义 |
|---|---|
| single | 0 或 1 |
| multi | 1 / 路径数(同一边出现在多条路径时累加) |
| distribution | 边上路径分布的边缘概率(含报文级散布的统计平均) |
@tbl-spec-route-17 RoutingResult: output_form, edge_load 含义
PathSet 语义(paths() 返回的路径集合在不同 output_form 下的解读):
| output_form | PathSet 解读 | Layer 2 消费方式 |
|---|---|---|
| single | 唯一确定路径 | 直接 lookup 节点序列 |
| multi | K 条等代价并列路径,无主次 | 按 flow 哈希 / 报文 ID 选其中一条 |
| distribution | 路径带权重;前 minimal_count 条为 minimal,其余为 valiant | adaptive policy 根据 NetworkState 在 minimal/valiant 间动态选择 |
@tbl-spec-route-18 PathSet 语义:output_form,解读
distribution 形态的 PathSet 必须保证:路径列表前 N 条是 minimal(N = RoutingStrategy.minimal_count,已在属性表声明),其余为 valiant 候选。该约定是 Layer 1 与 Layer 2 的硬契约——Layer 2 直接按 index 划分 minimal/valiant,不再额外标注。
Strategy 工厂
入口(语义层):
create_routing_strategy(name: str, attrs: dict[str, Any]) → RoutingStrategy
- name 必须在算法注册中心内,否则 raise
UnknownRoutingAlgorithm - attrs 由 YAML
routing.attrs段直接透传(不经过任何代码层默认填充) - 工厂构造期校验 attrs 中是否含有 Strategy 声明的
consumes_config_attrs全部 key;缺失 raiseMissingConfigAttr(含字段名) - 对
output_form="distribution"的 Strategy,工厂构造期把minimal_count写入实例属性strategy.minimal_count:若attrs含minimal_count则取该值,否则回落到类属性缺省值(@tbl-spec-route-13 声明的整数)。规则与.claude/rules/config-loading.md一致——"类属性缺省"是 Strategy 类型自身的语义声明(与dataclass字段缺省同质),不是配置层 fallback;attrs 显式提供时以 attrs 为准
ForwardingPolicy(Layer 2 / Rust 侧)
G5 仿真器内 trait,承接 Layer 1 计算出的 PathSet,做仿真期每跳决策。
Trait 接口(伪代码,Rust-style 仅为可读性;具体类型 / 错误处理在 plan/code 阶段细化):
trait ForwardingPolicy {
fn next_hop(
ctx: RoutingContext, // src_chip, dst_chip, current_node
flow: FlowMeta, // flow_id, packet_id, sim_time_ns
state: &dyn NetworkState, // 仿真期可观测的网络状态
) -> Option<NodeId>;
}
返回 "NodeId 或 None"(None 表示在当前 PathSet 中无可行下一跳)。
字段语义:
| 入参 | 含义 |
|---|---|
| ctx.src_chip / dst_chip | 通信端点的 chip 标识(u32) |
| ctx.current_node | 当前所处节点的 NodeId(chip 或 switch) |
| flow.flow_id | 流标识,static multipath 哈希维度(如 ECMP) |
| flow.packet_id | 报文标识,per-packet 算法(如 Packet Spraying)的决策维度 |
| flow.sim_time_ns | 当前仿真时刻(adaptive 算法可用于时间窗口判断) |
| state | 只读 NetworkState 引用,adaptive 类 policy 通过它读队列深度 / 链路负载 |
返回 None 表示 (ctx, flow) 在当前 PathSet 中无可行下一跳(src 与 dst 不可达 / 路径耗尽)。
@tbl-spec-route-19 ForwardingPolicy.next_hop:入参,含义
内置 6 类 Policy(与 decision_frequency 组合):
| Policy | 服务的 (decision_frequency, output_form) | 关键逻辑 |
|---|---|---|
StaticTablePolicy | (static, single) | 启动期一次性把 PathSet 折叠为查找表,per-hop 仅查表 |
EcmpHashPolicy | (static, multi) | 按 hash(flow_id) % K 选 K 条等代价路径之一;首跳决策、后续 hop 沿用 |
FirstHopAdaptivePolicy | (first_hop, distribution) | 首跳期查 NetworkState(如 minimal vs valiant 队列深度比较);缓存 (flow_id) → path_index;后续 hop 及该 flow 后续 packet 全部沿用首跳决策(L2-C1) |
PerFlowletAdaptivePolicy | (first_hop, multi) | flowlet 边界(now - last_pkt_ts > flowlet_gap_ns)触发新一轮选路;缓存 (flow_id, flowlet_id) → path_index;同一 flowlet 内严格粘滞(L2-C7);选路方式 hash((flow_id, flowlet_id)) % K(LetFlow style,不查 NetworkState) |
PerHopAdaptivePolicy | (per_hop, single | distribution) | 每跳查 NetworkState 选下一跳;不缓存 |
PacketSprayPolicy | (per_packet, multi | distribution) | 按 packet_id 决策(伪随机 / 加权采样 / 拥塞感知散布) |
@tbl-spec-route-20 ForwardingPolicy 内置类型
注:source-routed 算法(SRv6 等)路径在报文头部携带,等价于 StaticTablePolicy 把路径"内联"到 packet。
NetworkState(Layer 2 / Rust 侧)
只读 trait,G5 仿真器实现并向 ForwardingPolicy 暴露的运行时网络状态。adaptive 类算法通过它消费仿真期实时观测量。
Trait 接口(伪代码,Rust-style 仅为可读性;具体类型 / 错误处理在 plan/code 阶段细化):
trait NetworkState {
fn link_busy_until_ns(src: NodeId, dst: NodeId) -> f64;
fn link_load(src: NodeId, dst: NodeId) -> f64;
fn switch_queue_depth(node: NodeId, out_port: NodeId) -> u32;
fn flow_count_on_link(src: NodeId, dst: NodeId) -> u32;
}
字段单位与语义契约:
| 字段 | 单位 / 类型 | 语义 | 最低精度要求 | 实现选择 |
|---|---|---|---|---|
| link_busy_until_ns | 绝对仿真时刻 (ns) | 该链路当前队列结束时刻;当 < 当前仿真时刻表示链路空闲 | 必须精确(直接来自 PhyLink.busy_until_ns) | 无可选实现 |
| link_load | 归一化 [0.0, 1.0] | 0 = 空闲、1.0 = 已饱和 | 必须返回有信号的非常量值:禁止常返 0 或常返固定值。允许实现为"过去 W ns 链路忙时间占比"(滑动平均)或"瞬时 = 当前是否在传输";选定方案必须在 §实现备注 声明窗口长度 W 或瞬时定义 | 滑动平均(推荐 W=link_latency_us × 10)/ 瞬时 / 累计 EWMA 三选一 |
| switch_queue_depth | 字节数 | 指定 switch 节点的指定出端口当前排队字节数;与 UGAL queue-aware 公式对齐 | 必须精确(来自 switch 内 VOQ 状态) | 无可选实现 |
| flow_count_on_link | 流数 | 链路上当前 active flow 数;用于多流竞争因子估算 | 必须维护 active flow 计数器,禁止常返 0。允许精确计数(HashMap<flow_id, refcount>)或哈希估计(Count-Min Sketch);估计方式与误差范围必须在 §实现备注 声明 | 精确 / Count-Min Sketch / HyperLogLog 三选一 |
@tbl-spec-route-21 NetworkState 观测字段
精度违规判定:返回常量(特别是 0)即视为违反"最低精度要求"。理由:adaptive 算法的决策质量直接取决于 state 信号强度;常返 0 让 UGAL queue-aware 模式退化为纯 cost-based、PacketSpray 拥塞感知模式退化为均匀散布——算法表现上可能"看起来正常"但语义已完全失效,调试时极难定位。
核心约束:
- 只读:Policy 不能修改 state,只观测
- 按需声明:
RoutingStrategy.requires_runtime_state列举该算法消费的 state 字段名,G5 加载期校验仿真器已实现这些字段;未声明的字段算法不得查询(防止隐式依赖) - 粒度可换:state 各字段可有精确实现 / 近似实现(如移动平均),由仿真器选择
PolicyDispatcher(Layer 2 / Rust 侧)
trait 层无法 enforce "Policy 只能查询 requires_runtime_state 声明的 NetworkState 字段"(L2-C2)——Rust trait object 无法在方法分发前感知"调用方声明了什么"。PolicyDispatcher 在 Policy 与 NetworkState 之间插入一层包装,承载该约束。
Trait 接口(伪代码,Rust-style 仅为可读性):
struct PolicyDispatcher {
policy: Box<dyn ForwardingPolicy>,
declared_state_fields: HashSet<String>, // 来自 Strategy.requires_runtime_state
}
impl PolicyDispatcher {
fn next_hop(
ctx: RoutingContext,
flow: FlowMeta,
state: &dyn NetworkState,
) -> Option<NodeId> {
let guarded = GuardedState::new(state, &self.declared_state_fields);
let result = self.policy.next_hop(ctx, flow, &guarded);
// guarded 在 drop 时报告 query 集合;若 query ⊄ declared → raise UndeclaredStateAccess
result
}
}
GuardedState 是 NetworkState 的包装实现,每个 trait 方法被调用时记录字段名;若调用未在 declared_state_fields 内的字段 → 立即 raise UndeclaredStateAccess(fail-fast,不等 drop)。
内部可变性约束:NetworkState trait 方法签名是 &self,但 GuardedState 需在调用过程中累积 query 集合并触发 fail-fast 校验——这要求内部可变性(Rust RefCell / Cell / AtomicUsize 等)。GuardedState 实现依赖 L2-C5 的单线程假设:仿真期不会有多个线程同时通过同一 GuardedState 查 state,因此非线程安全的 RefCell 是合法选择。若未来多线程化仿真器,GuardedState 需切换到原子计数或线程局部存储,与 L2-C5 一同 bump 升级。
职责边界:
| 包含 | 不包含 |
|---|---|
| 校验字段访问合法性(L2-C2 enforce) | 不修改 Policy 行为;不改 state 内容 |
| 记录访问统计(可选 debug 模式) | 不参与算法决策 |
| 错误信息含字段名 + Policy 类型 | 不感知具体算法语义 |
性能开销:每次 NetworkState 方法调用增加一次 HashSet lookup。生产模式下可通过条件编译(feature flag)跳过审计,仅在调试 / CI 模式启用。
Strategy ↔ Policy 映射
Layer 1 与 Layer 2 在 G5 加载期通过 decision_frequency 类属性自动匹配:
| RoutingStrategy.decision_frequency | output_form | 选定的 ForwardingPolicy |
|---|---|---|
| static | single | StaticTablePolicy |
| static | multi | EcmpHashPolicy |
| first_hop | distribution | FirstHopAdaptivePolicy |
| first_hop | multi | PerFlowletAdaptivePolicy |
| per_hop | single / distribution | PerHopAdaptivePolicy |
| per_packet | multi / distribution | PacketSprayPolicy |
@tbl-spec-route-22 Strategy ↔ Policy 映射
非法组合(如 static + distribution)在 G5 加载期 raise IncompatibleStrategyError。
first_hop 下 multi vs distribution 的语义区分:
(first_hop, distribution)→ FirstHopAdaptivePolicy:PathSet 前 N 条为 minimal、其余 valiant;Policy 查 NetworkState 在 minimal/valiant 间做有偏选择(UGAL 语义);同一 flow 后续 packet 全部粘滞(L2-C1)(first_hop, multi)→ PerFlowletAdaptivePolicy:PathSet K 条等代价无主次;Policy 按 flowlet 边界(sim_time gap)切分 flow 为多个 flowlet,每 flowlet 内粘滞、flowlet 间允许换路径(L2-C7)
两者通过 output_form 严格区分,避免歧义。
同 Policy 不同算法的区分:多个 Strategy 可归同一 (decision_frequency, output_form) 组合复用同一 Policy,差异由 consumes_edge_attrs / consumes_config_attrs 反映。例如 static + single → StaticTablePolicy 同时被 ShortestPath / DOR / D-mod-k 复用:ShortestPath 不读 edge attrs(族无关);DOR 读 {dimension, wraparound}(Torus 维度);D-mod-k 读 {level, port_idx} + {w_levels, p_levels}(fat-tree 层级与端口)。同一 Policy 处理已计算好的 PathSet,算法语义差异在 Layer 1 Strategy 层级体现。
算法 / 公式
选路权重(路径选择)
边权重(shortest_path / ECMP 的 Dijkstra 权重,对齐 OSPF RFC 2328):
$$\begin{equation} w(e) = \frac{1}{\text{bandwidth\_gb\_per\_s}(e)} \label{eq:rt-edge-weight} \end{equation}$$路径代价(Dijkstra 最小化目标):
$$\begin{equation} C_{\text{path}} = \sum_{e \in \mathcal{E}} w(e) \label{eq:rt-path-cost} \end{equation}$$- 语义:优先选择高带宽路径。两条 400 Gbps 边的代价 = 一条 200 Gbps 边的代价,自然平衡跳数与带宽
- ECMP 的"等代价"定义:$C_{\text{path}}$ 相等的多条路径(在浮点容差 $\epsilon = 10^{-9}$ 内)
- 交换机转发延迟不参与选路权重,仅参与路径延迟计算(下方公式 $\eqref{eq:rt-path-latency}$)
- 此权重仅适用于 shortest_path / ECMP;DOR 无权重(坐标确定性);UGAL 使用动态代价(队列深度 + 跳数)
路径指标(结果计算)
路径累计延迟(路径选出后的结果计算,所有算法通用):
$$\begin{equation} T_{\text{path}} = \sum_{e \in \mathcal{E}} \text{latency}(e) + \sum_{n \in \mathcal{N}_{\text{interior}}} \text{forwarding\_latency}(n) \label{eq:rt-path-latency} \end{equation}$$- $\mathcal{E}$:路径上的边序列(含 chip-to-chip 直连边和 switch-relay 边)
- $\mathcal{N}_{\text{interior}}$:路径上除源、目以外的中间节点(仅 switch 贡献 forwarding_latency)
路径瓶颈带宽:
$$\begin{equation} B_{\text{bottleneck}} = \min_{e \in \mathcal{E}} \text{bandwidth}(e) \label{eq:rt-bottleneck-bw} \end{equation}$$多路径聚合带宽(适用于 ECMP / KSP / Packet Spraying 等独立等代价路径):
$$\begin{equation} B_{\text{aggregate}} = u_{\text{routing}} \cdot \sum_{i=1}^{N} B_{\text{bottleneck}}^{(i)} \label{eq:rt-aggregate-bw} \end{equation}$$- $N$:等代价路径数
- $u_{\text{routing}} \in (0, 1]$:路由侧带宽利用率折扣(YAML
routing.bandwidth_utilization) - 严格成立条件:$N$ 条路径独立、等代价、流量均匀分布
- 非独立路径(共享瓶颈边)应通过 RoutingResult.edge_load 表达,不直接套用此公式
链路层级标识
边的 type 字段(c2c / b2b / r2r / p2p 或自定义)即链路的层级标识。调用方(如通信评估器)可从 RoutingResult 的路径边序列中读取 EdgeSpec.type 以区分链路层级,无需独立的"tier 分解"抽象。
路由模块的输出止于 RoutingResult(G6 边界),链路层级的业务语义由调用方自行解释。例如:通信评估器可从路径的瓶颈边 type 判断通信走的是 c2c(板内)还是 r2r(跨 rack),据此选择对应的带宽参数。
约束与规则
正确性约束(违反 → 抛 RoutingError 子类,硬拒绝):
- C1:路径必须连通;不可达时
compute返回空路径的 RoutingResult,由调用方决定如何处理 - C2:
bind_graph期间,consumes_edge_attrs中每个 key 必须在 graph 所有边的 attrs 上存在且类型与 edge attrs schema 一致 — 缺失或类型错 raiseIncompatibleStrategyError - C3:
src == dst视为零跳特殊情况(合法),返回零代价 RoutingResult(hop=0, latency=0, bw=∞) - C4:图非连通且 (src, dst) 跨连通分量 →
bind_graph期 raiseDisconnectedGraphError - C5:request 中的 src 或 dst 不在 graph →
computeraiseNodeNotFound - C6:
create_routing_strategy工厂期,consumes_config_attrs中每个 key + 模块级 key(bandwidth_utilization)必须在routing.attrs中存在且类型与 config attrs schema 一致 — 缺失 raiseMissingConfigAttr,类型错 raiseInvalidConfigAttrType - C7(D-mod-k attrs 跨表一致性):
bind_graph期 D-mod-k Strategy 必须校验:(a)len(w_levels) == len(p_levels)(PGFT 两参数等长);(b)len(w_levels) == max(edge.level for edge in fat-tree边) + 1(attrs 列长度 = 树层数 h,与图中边层级取值范围对齐);(c)w_l, p_l ≥ 1各层正整数。任一违反 raiseIncompatibleStrategyError,错误消息含具体不一致字段。该约束保证 PGFT 公式q_l(j) = floor(j/Π w_k) mod (w_{l+1}·p_{l+1})在所有层级上都有定义
可用性约束(违反 → warning,不阻塞):
- W1:边 attrs 或 routing.attrs 含未在对应 schema 注册的 key → warning(疑似拼写错误),保留 key 不丢弃
- W2:算法配置(如
max_paths=N)但实际可用路径 < N → warning,截断 - W3:
routing.attrs含已注册但当前 algorithm 不消费的 key(如 algorithm=shortest_path 但 attrs 含max_paths)→ warning,该 key 不影响行为
边界条件:
- B1:自反请求 (src == dst) → 返回单元素零代价路径(同 C3)
- B2:算法不兼容当前图(consumes 校验失败) →
bind_graph期 raiseIncompatibleStrategyError,错误消息含缺失 key 名 - B3:YAML 必须显式指定
routing.algorithm字段;缺失 → 加载期 raiseMissingAlgorithmField(错误消息含 YAML 文件路径)。模块不做任何默认推断或 fallback:算法选择是实验意图,必须由用户显式声明;推断会破坏对比实验、损害可重复性、引入图特征歧义 - B4:YAML 显式指定的
algorithm不在工厂注册中心 →create_routing_strategyraiseUnknownRoutingAlgorithm - B5:attrs 值为 None 或缺省 → 视为该边未提供该属性,C2 校验生效
Layer 2(G5 仿真侧)约束:
-
L2-C1:
FirstHopAdaptivePolicy在首跳决策后必须缓存(flow_id) → path_index,后续跳沿用首跳决策。这是 first-hop adaptive 算法的语义不变性,违反将导致同一流报文乱序到达 -
L2-C2:Policy 只能查询
RoutingStrategy.requires_runtime_state已声明的 NetworkState 字段;查询未声明字段 → PolicyDispatcher 拦截并 raiseUndeclaredStateAccess。该约束防止 adaptive 算法隐式依赖未声明状态,导致 spec 与实现漂移。承载层:见 §PolicyDispatcher 小节——trait 层本身无法 enforce,必须由 dispatcher 通过包装 NetworkState 实现 -
L2-C3:G5 加载期按
Strategy ↔ Policy 映射表匹配;非法组合(如 static + distribution)raiseIncompatibleStrategyError -
L2-C4:
PerHopAdaptivePolicy不得缓存路径选择——其语义就是每跳重新决策;缓存即等价于退化为 FirstHopAdaptive -
L2-C5:
NetworkState实现必须保证 trait 方法的查询语义在单次 next_hop 调用内一致(不允许仿真器在 policy 查询过程中并发更新 state);G5 单线程仿真天然满足,多线程化时需引入快照 -
L2-C6(dispatcher 调用必经性):G5 仿真器对 Policy 的唯一调用入口是
PolicyDispatcher.next_hop;C2CNetwork / event handler / 任何调用方必须持有PolicyDispatcher,不得持有裸Box<dyn ForwardingPolicy>。该约束保证 L2-C2 字段访问保护无法被绕过——若调用方直接拿裸 Policy + 真实 NetworkState 调next_hop,GuardedState 不被构造,未声明字段访问就不被拦截。架构层面通过"裸 Policy 不出现在 dispatcher 外部 API"实现:Policy 工厂只返回 PolicyDispatcher,不暴露内部policy字段 -
L2-C7(PerFlowletAdaptive flowlet 缓存语义):
PerFlowletAdaptivePolicy必须按(flow_id, flowlet_id)缓存 path_index,遵守以下三条规则:- L2-C7.a 初始化:flow 第一次出现时
flowlet_id = 0,按hash((flow_id, 0)) mod K选 path_index 并写入缓存 - L2-C7.b 递增条件:后续 packet 到达时若
now - last_pkt_ts > flowlet_gap_ns,则flowlet_id += 1并按新 hash key 重新选路;否则沿用当前 (flow_id, flowlet_id) 缓存的 path_index - L2-C7.c 单调性:
flowlet_id仅递增、不回退、不在 flow 生命周期内重置;同一(flow_id, flowlet_id)内任意后续 packet 与中间跳必须严格粘滞同一 path_index
与 L2-C1 的区分:L2-C1 是 "FirstHopAdaptive 全 flow 单一路径"(同 flow_id 永不换),L2-C7 是 "PerFlowletAdaptive flowlet 内粘滞、flowlet 间可换"。两者并存,按 Policy 类型分别 enforce。违反 L2-C7.c(同 flowlet 内换路径)会导致同 flowlet 内 packet 乱序到达(与 LetFlow 论文的 flowlet gap 无乱序条件 $\tau_{\text{gap}} \ge d_{\max} - d_{\min}$ 一致)。
- L2-C7.a 初始化:flow 第一次出现时
集成点
| 边界 | 输入 | 输出 |
|---|---|---|
| 模块入口 | YAML 配置 + 节点 ID 集合 | NetworkGraph 实例 + bind 完成的 RoutingStrategy 实例 |
| 模块出口(Layer 1,Python) | RoutingRequest | RoutingResult |
| G5 加载期出口(Layer 1 → Layer 2 桥接) | RoutingStrategy + NetworkGraph | (a) 全对 PathSet(dict[(src,dst), list[Path]])+ (b) ForwardingPolicy 实例(按 decision_frequency 选定)+ (c) 算法附加参数(如 UGAL bias / minimal_count) |
| 仿真期出口(Layer 2,Rust) | RoutingContext + FlowMeta + NetworkState | 下一跳 NodeId |
@tbl-spec-route-23 集成点:边界,输入
Layer 1 ↔ Layer 2 数据桥接契约:
- G5 加载期遍历所有 (src, dst) 对,调
RoutingStrategy.compute(RoutingRequest)拿 RoutingResult,提取paths()转为 PathSet(节点序列 + 边序列)传给 Rust decision_frequency+output_form组合决定 Rust 端实例化哪种 ForwardingPolicy;requires_runtime_state用于校验仿真器已暴露对应 NetworkState 字段,并传给 PolicyDispatcher 作为字段访问白名单- 算法附加参数(如 UGAL
bias/minimal_count)通过 RoutingStrategy 的额外属性暴露,桥接层按 §Policy Descriptor Schema 表打包
Policy Descriptor Schema
桥接层(Python 侧)按 (decision_frequency, output_form) 组合显式产出 descriptor 字典,Rust 侧根据 type 字段实例化对应 Policy。禁止启发式推断(如根据"是否有 multi-path"或"是否传 ugal_config"猜测 Policy 类型)——启发式无法覆盖 5 类完整组合,且与 L2-C3 "非法组合 raise" 约束冲突。
| (decision_frequency, output_form) | descriptor 字典 schema | 必填字段含义 |
|---|---|---|
| (static, single) | {"type": "static_table"} | 无附加字段;Policy 从 PathSet 单路径建查找表 |
| (static, multi) | {"type": "ecmp_hash", "max_paths": int} | max_paths:K 条等代价路径上限,与 Strategy consumes_config_attrs.max_paths 一致 |
| (first_hop, distribution) | {"type": "first_hop_adaptive", "minimal_count": int ≥ 1, "bias": float} | minimal_count:PathSet 前 N 条为 minimal;bias:minimal vs valiant 决策偏置(参见 UGAL 算法定义) |
| (first_hop, multi) | {"type": "per_flowlet_adaptive", "flowlet_gap_ns": float > 0} | flowlet_gap_ns:flowlet 边界阈值(仿真时刻差);Policy 在 PathSet K 条等代价路径间按 hash((flow_id, flowlet_id)) mod K 选路,K = PathSet 长度(runtime 推断,无需 descriptor 显式传递);flowlet_id 由 sim_time gap 单调递增(L2-C7) |
| (per_hop, single | distribution) | {"type": "per_hop_adaptive"} | 无附加字段;Policy 每跳查 state 重选下一跳。single 形态:Policy 持当前节点邻接表,每跳对所有出向邻居逐一查 state cost 后选最优(不依赖 PathSet 候选);distribution 形态:Policy 在 PathSet 候选路径的下一跳集合内做 state-driven 决策,等价于"被 Layer 1 约束在 minimal/valiant 候选内的 per-hop 选择" |
| (per_packet, multi | distribution) | {"type": "packet_spray", "congestion_aware": bool} | congestion_aware:true 时对每个候选路径查首跳 link_load,选最低者(argmin);false 时按 hash(packet_id) mod N 散布。两种模式都不在多包间缓存(per_packet 语义) |
@tbl-spec-route-23b Policy Descriptor Schema
Schema 维护规则:
- 新增 Policy 类型必须扩展本表一行 + bump spec minor 版本
- descriptor 字段值的合法性由 Rust 侧
parse_topology.rs在加载期校验;缺失必填字段 raiseMissingPolicyDescriptorField;type 不在表内 raiseUnknownPolicyType - 任何不在本表内的
(decision_frequency, output_form)组合均为非法(L2-C3),桥接层 raiseIncompatibleStrategyError
模块内部不感知下游消费方对 RoutingResult 的具体使用(通信延迟模型、争用建模、Math 评估器公式选择 不在本 spec 范围);但 G5 仿真侧 Layer 2 契约在本 spec 范围内。
YAML schema
network:
link_types: # 链路规格表
<type_name>:
bandwidth_gb_per_s: <float>
latency_us: <float>
links: # 边列表(写法 3:物理字段平级 + attrs 集中)
- from: <NodeId | list[NodeId]>
to: <NodeId | list[NodeId]>
type: <link_type_name>
attrs: # 可选;key 必须在 attrs schema 内
<attr_key>: <attr_value>
switches: # 交换机列表(可选)
- id: <NodeId>
type: <"tor" | "spine" | "core">
port_count: <int>
forwarding_latency_ns: <int>
...
routing:
algorithm: <string> # 必填,枚举:shortest_path | ecmp | dor | ugal | ...(已注册算法名)
attrs: # 必填字典;模块级 key + 当前 algorithm 的 consumes_config_attrs 全部必须存在
bandwidth_utilization: <float> # 模块级(所有算法共用),公式 \eqref{eq:rt-aggregate-bw} 使用
# 算法级 key 由 algorithm 决定,例如:
# algorithm=dor 时:dimension_order, dateline_vc
# algorithm=ecmp 时:max_paths
# algorithm=ugal 时:bias, valiant_k, minimal_count
# algorithm=packet_spray 时:max_paths, congestion_aware
# algorithm=dmodk 时:w_levels, p_levels (PGFT 通用;k-ary n-tree 写 [k, k, ...] + [1, 1, ...])
# algorithm=flowlet_ar 时:flowlet_gap_ns (业界 IB AR 典型 50000-500000 即 50-500 μs)
字段透传规则:模块加载期把 routing.attrs 段作为 dict 直接透传给 create_routing_strategy(name, attrs),不做任何代码层默认值填充(遵守 .claude/rules/config-loading.md)。Strategy 在工厂构造期校验消费 key 的存在性与类型;缺失 raise MissingConfigAttr,类型错 raise InvalidConfigAttrType。
_template.yaml 与本 schema 同步,按 .claude/rules/sync-templates.md 维护。
引用
外部:
- BookSim 2: https://github.com/booksim/booksim2
- SST/macro: https://github.com/sstsimulator/sst-macro
- ASTRA-sim 2.0: https://github.com/astra-sim/astra-sim
- SimGrid: https://simgrid.org
- HTSim: https://github.com/Broadcom/csg-htsim
- Dally & Towles, Principles and Practices of Interconnection Networks, Morgan Kaufmann (2003) — 路由算法多维分类的经典参考
- Zahavi, "D-Mod-K Routing Providing Non-Blocking Traffic for Shift Permutations on Real Life Fat Trees", CCIT Report #776 (2010): https://ece.technion.ac.il/wp-content/uploads/2021/01/publication_776.pdf — D-mod-k 原始定义、PGFT 公式 (3)、Lemma 1-3 无阻塞证明
- Gliksberg et al., "Dmodc: An Oblivious Routing Algorithm for Degraded Fat-Trees", IEEE MICRO (2023): https://arxiv.org/abs/2211.13101 — D-mod-k 在降级 fat-tree 上的算术扩展(NG9 取消时的接续算法)
- Vanini et al., "Let It Flow: Resilient Asymmetric Load Balancing with Flowlet Switching", NSDI (2017): https://www.usenix.org/conference/nsdi17/technical-sessions/presentation/vanini — LetFlow 最小化设计:flowlet 边界随机选路,无拥塞反馈;本 spec v1.3.2 PerFlowletAdaptivePolicy 直接对齐该设计(hash 替代论文的"随机"以保证仿真可复现)
- Alizadeh et al., "CONGA: Distributed Congestion-Aware Load Balancing for Datacenters", SIGCOMM (2014): https://dl.acm.org/doi/10.1145/2619239.2626316 — flowlet 级带内拥塞反馈机制(未来方向:本 spec PerFlowletAdaptive 拥塞感知变体的参考设计,对应
requires_runtime_state扩展link_load后的形态) - NVIDIA Quantum InfiniBand Adaptive Routing (
ar_ftreemode): https://docs.nvidia.com/networking/display/mlnxosv375140/adaptive+routing — 业界 fat-tree 上的 flowlet AR 部署形态,Subnet Manager 统一配置 flowlet_gap 参数
项目内:
- 算法 survey:../interconnect/03-路由算法/
- 拓扑生成器设计:拓扑生成模块设计规格
- 配置加载规则:
.claude/rules/config-loading.md(强制约束:禁止默认值) - 模板同步规则:
.claude/rules/sync-templates.md(attrs schema 修改时同步 _template.yaml)
备选方案
元数据载体形式
| 维度 | 方案 A:散落具名字段 | 方案 B:单 dict 无约束 | 方案 C(选定):dict + Strategy 声明 | 方案 D:Tagged 子类 | 方案 E:图算法反推 |
|---|---|---|---|---|---|
| schema 稳定性 | 差(每加算法改类) | 好 | 好 | 差(族子类爆炸) | 好(图模型不变) |
| 类型安全 | 强(dataclass 字段) | 弱(Any) | 中(注册期校验) | 最强 | 弱 |
| 扩展成本 | 高(改 frozen dataclass) | 极低 | 低(schema 加一行) | 高 | 高(实现复杂) |
| 错误前置 | 编译期 | 运行期沉默 | 加载期 raise | 编译期 | 运行期 |
| "杂物间"风险 | 无 | 高 | 低(声明约束) | 无 | 无 |
| YAML 友好 | 中(字段堆叠视觉乱) | 好 | 好 | 中 | 最好(YAML 极简) |
| 与现有 YAML 哲学对齐 | 破坏(族字段污染节点) | 对齐 | 对齐(沿用 attrs 风格) | 严重破坏 | 完全对齐 |
| 13 族 × 多算法适配 | 字段爆炸 | 灵活 | 灵活 | 类爆炸 | 反推歧义 |
@tbl-spec-route-24 元数据载体形式
选择理由:方案 C 兼具方案 B 的扩展灵活性与方案 A 的错误前置;声明机制把"杂物间"负面效应控制住;与项目通用 YAML 描述哲学对齐;不破坏 graph 族无关性。方案 E 看似最纯净,但 Slimfly / Dragonfly+ / 部分 HyperX 变体的图特征反推歧义难解决,且实现复杂度抵消了简洁性收益。
路由配置 YAML 组织
| 维度 | 方案 α:顶层散字段 + 算法子段嵌套 | 方案 β(选定):routing.attrs 集中字典 |
|---|---|---|
| 形式 | routing.bandwidth_utilization + routing.<algorithm>.dimension_order | routing.attrs.bandwidth_utilization + routing.attrs.dimension_order |
| 与 edge.attrs 对称性 | 否(边用 attrs,路由不用) | 是(双层都用 attrs) |
| 校验机制复用 | 否(顶层散字段需独立处理;算法子段需算法名命中) | 是(双层共用 schema + Strategy 声明 consumes 模式) |
| 杂物间防护 | 弱(顶层任意字段难校验) | 强(schema 集中维护) |
| 算法切换时配置位置 | 改 routing.<old_algo> → routing.<new_algo> 段名 | 切 algorithm 字段,attrs 内容微调 |
| 默认值合规性 | 顶层 bandwidth_utilization 易诱导写"选填默认 0.90",违反 config-loading | attrs 整体必填,零默认值天然合规 |
@tbl-spec-route-25 路由配置 YAML 组织
选择理由:方案 β 与边 attrs 设计完全对称,校验机制(schema + consumes)共用一套实现;天然遵守 .claude/rules/config-loading.md(无字段适合"选填默认"陷阱);新增算法时不需要在 YAML 引入新嵌套子段(attrs 字典扁平加 key 即可)。
Strategy 接口形式
| 维度 | 方案 a(选定):单一 compute → RoutingResult | 方案 b:三个接口(SinglePath / MultiPath / Distribution Protocol) |
|---|---|---|
| 调用方便利性 | 一个接口 | isinstance 分支 |
| 类型表达力 | output_form 作为元数据 | Protocol 类型直接区分 |
| 新形态扩展成本 | 低(RoutingResult 内部状态) | 高(新增 Protocol) |
| 13 族 × 多形态 | 简单 | 类型组合膨胀 |
| 调用方代码复杂度 | 低 | 含分支 |
@tbl-spec-route-26 Strategy 接口形式
选择理由:调用方(G5 仿真器)的契约是 RoutingResult,不应感知算法形态;统一接口避免每次新增形态都改调用方;output_form 作为声明性元数据已足够告知差异。
族信息载体宏观选型
| 维度 | 方案 i:TopologyDescriptor | 方案 ii(选定):边 attrs | 方案 iii:算法图反推 |
|---|---|---|---|
| 族抽象渗透 | 高(族成接口一等公民) | 无(图族无关) | 无 |
| YAML 表达 | 需 family_kind + family_params | 沿用 links 风格 | YAML 完全通用 |
| 算法实现成本 | 中(每族一 Descriptor 类) | 低(读 attrs key) | 高(启发反推有歧义) |
| 与现有 YAML 设计对齐 | 引入新概念 | 沿用 attrs 习惯 | 完全对齐 |
| 通用算法成本 | 需理解 GenericDescriptor | 0(不读 attrs) | 0 |
@tbl-spec-route-27 族信息载体宏观选型
选择理由:项目 YAML 已是族无关通用图描述,引入 Descriptor 等于把族概念找回,违反原设计哲学。算法图反推存在歧义且实现复杂。边 attrs 是最低侵入的设计 — 利用 YAML 既有"边贴标签"风格的自然延展。
路由分层架构(v1.2.0 新增)
是否把 Layer 2(per-hop 决策)做成独立抽象?语言放置策略对比 5 个方案:
| 维度 | 方案 α:单层 Python(Strategy 含每跳) | 方案 δ:单层全 Rust(连 Strategy 也用 Rust 实现) | 方案 ε:双层但 Layer 2 也 Python(per-hop 走 PyO3 callback) | 方案 β(选定):双层 Python + Rust | 方案 γ:三层(再加 Selector 管 multi-path) |
|---|---|---|---|---|---|
| Python ↔ Rust 跨语言开销 | 高(每跳回调) | 0 | 高(每跳 PyO3 callback) | 低(启动期一次桥接,运行期 Rust 自洽) | 低(同 β) |
| 静态算法开销 | 高 | 0 | 高 | 0 | 0 |
| Adaptive 算法扩展 | 改 Strategy 接口 | 改 Rust,Python 端不可见 | 加一种 Python policy | 加一种 Rust Policy | 加 Policy + Selector |
| Math 评估器复用 | 接口臃肿 | 失去(Math 在 Python,不能直接调 Rust 算法) | 直接复用 | Layer 1 直接复用 | Layer 1 直接复用 |
| 算法迭代速度 | 中(Python 灵活) | 慢(Rust 改动笨重) | 快 | 中(Python 改 Strategy 快 / 新 Policy 改 Rust 慢) | 中 |
| 抽象数 | 1 | 1 | 2 | 2 | 3 |
@tbl-spec-route-28 路由分层架构选型
选择理由(β):
- 双层把"全局视图"(Layer 1)和"运行时视图"(Layer 2)分到不同生命周期,复杂度对症
- per-hop 决策完全在 Rust 内,per-packet 仿真性能不受 Python 影响
- Layer 1 保留在 Python 让算法迭代快、与 Math 评估器复用零代价(否决 δ)
- Layer 2 必须在 Rust 才能保证 per-hop 零跨语言开销(否决 α、ε)
- 5 类内置 Policy 已覆盖业界主流算法模式(static / multipath / first-hop adaptive / per-hop adaptive / packet spray),新算法多数可复用现有 Policy
- 方案 γ 多出的 Selector 层在 ECMP 选路只是 5 行 Rust 代码,独立成层不值得
D-mod-k 参数表达形式(v1.3.1 新增)
| 维度 | 方案 a:k: int 简写 | 方案 b(选定):w_levels + p_levels PGFT 通用 | 方案 c:两者并存(别名) |
|---|---|---|---|
| 表达能力 | 仅标准 k-ary n-tree | PGFT / RLFT / slimmed fat-tree 通用 | 通用 |
| 用户书写量 | 1 字段 | 2 列表([k,k,k] + [1,1,1]) | 1 字段或 2 列表 |
| Strategy 实现复杂度 | 简单 | 简单(公式直接消费) | 中(attrs 校验分支:k 在则推导,否则读 list) |
| Schema 校验 | 简单(int) | 简单(list 长度 + 元素正数) | 复杂(一致性互斥校验) |
| Zahavi CCIT #776 公式对齐 | k-ary 特例 | PGFT 通用形式(公式 (3))直接消费 | 通用形式经简写转换 |
| 与 §业界参考 D-mod-k 段公式一致 | 否 | 是 | 是(间接) |
@tbl-spec-route-32 D-mod-k 参数表达形式
选择理由(b):PGFT 通用形式直接对齐 Zahavi 原始公式,零额外转换;标准 k-ary n-tree 用户写 w_levels: [4, 4, 4] + p_levels: [1, 1, 1] 仅多 ~30 字符,代价可接受;引入 k 简写别名(方案 c)会让 attrs 校验出现互斥分支(k 与 w_levels/p_levels 不能同时给),与 spec §G4 "attrs 显式声明" 简单清晰原则冲突。
per-flowlet AR 决策模型选型(v1.3.2 新增)
per-flowlet AR 有两个独立维度:选路触发条件(flowlet 边界判定)和选路决策方式(hash vs 拥塞感知)。前者业界基本一致(now - last_pkt_ts > flowlet_gap_ns),后者三种主流方案对比:
| 维度 | 方案 A1:纯 hash 切换(LetFlow style) | 方案 A2:拥塞感知 argmin(IB AR / CONGA style) | 方案 A3:双 mode 配置项 |
|---|---|---|---|
| 选路方式 | hash((flow_id, flowlet_id)) mod K | flowlet 边界查 link_load 选 argmin(或带内拥塞反馈) | 由 attrs flowlet_congestion_aware: bool 决定 |
requires_runtime_state | set()(不查 NetworkState) | {"link_load"} 或更多字段 | 两种均支持,按 mode 分支 |
| Layer 2 复杂度 | 低(与 EcmpHash 同复杂度,仅 cache key 多一维) | 中(增加 state-driven argmin 逻辑) | 高(两条路径都需实现 + 测试覆盖) |
| 仿真可复现性 | 高(hash 纯函数) | 低(依赖仿真期 state 演化) | 中 |
| 业界对应 | LetFlow (NSDI 2017) | NVIDIA Quantum InfiniBand AR / CONGA (SIGCOMM 2014) | — |
| spec patch 范围适配 | ✓ 既有 first_hop 类目下加变体 | ✗ 触发 NetworkState 精度调优、加大 PolicyDispatcher 测试矩阵 | ✗ 引入新 attrs schema 字段 + 多 mode 行为契约 |
| 用户配置门槛 | 低(仅配 flowlet_gap_ns) | 高(必须确保仿真器 link_load 精度满足 spec @tbl-21 要求) | 中 |
@tbl-spec-route-33 per-flowlet AR 决策模型选型
选择理由(A1):
- 与 spec v1.3.2 patch 范围最匹配——既有
first_hopdecision_frequency 类目下加 LetFlow style 变体,不引入新 NetworkState 字段消费、不改 PolicyDispatcher 校验逻辑 - 仿真可复现性是测试可靠性的硬要求;A2 的拥塞感知依赖仿真期 state 演化轨迹,相同 YAML 不同跑可能给出不同 path_index 分布,与 G5 既有"同 YAML 同结果"契约有张力
- LetFlow 论文(Vanini NSDI 2017)证明:在等价多路径拓扑下,纯 hash 切换在 flowlet 粒度上已能达到接近 CONGA 的负载均衡效果,复杂度远低
- A2 / A3 留作未来方向(见 §未来方向 "拥塞感知 flowlet AR"行)——CONGA / NVIDIA Quantum IB AR 形态在 future 通过扩展
requires_runtime_state实现,不必先期引入
A2 否决理由:本 spec patch 不应增加 NetworkState 字段消费契约(与既有 v1.2.1 link_load 精度承诺联动),范围超出 v1.3.2 patch 边界。
A3 否决理由:引入第 2 个布尔开关(同 PacketSpray congestion_aware)让 schema 与测试矩阵翻倍;当 A1 与 A2 的语义差异显著(state 消费 / 仿真可复现性),应独立成 Policy 类而非合并为同 Policy 的两 mode。
风险与限制
| 风险 | 影响 | 缓解措施 |
|---|---|---|
| attrs 字典缺乏强类型(dict[str, Any]) | 拼写或类型错误可能未被静态工具发现 | Strategy 显式声明 + 加载期 schema 校验(约束 C2、C6、可用性 W1)把错误提前到 bind_graph 或工厂构造期 |
| attrs schema 分两层(edge + config) | 文档与代码维护成本翻倍 | 单一 schema 模块(如 attrs_schema.py)同时定义两份;模板文件按 .claude/rules/sync-templates.md 同步 |
| YAML 必填字段过多导致用户写起来啰嗦 | 每个拓扑预设都要列出 attrs 段 | 拓扑生成器为常用族产出完整 YAML(含 attrs);用户手写 YAML 时按 schema 表照填 |
| 多算法并存时 attrs schema 膨胀 | schema 表变长 | schema 是静态注册,加载期校验复杂度 $O(\|edges\| \times \|attrs\|)$,线性可接受;超大规模可优化为 set 查找 |
| 闭式算法 vs 全表存储的实现差异 | 不同 Strategy 内部存储形式不一 | RoutingResult 接口屏蔽差异;调用方面对统一查询接口(G6 模块边界保证) |
| edge_load 的语义随 output_form 变化 | 调用方需理解三种语义 | RoutingResult 在文档中明确每种 output_form 下 edge_load 的含义;提供 helper(如 result.is_distribution()) |
| YAML 写法 3(attrs 嵌套)增加用户书写量 | 简单边写起来啰嗦 | attrs 字段可省略;通用算法不需要任何 attrs,YAML 与现状一致 |
| Layer 1 / Layer 2 双向漂移(spec 跨语言后,Python 改 Strategy 接口、Rust 改 Policy trait 可能不同步) | 算法语义不一致 | 接口契约写在本 spec;Layer 1 / Layer 2 任何接口变更都需 bump spec 版本 + 同步审核两侧实现 |
| adaptive 算法的运行时状态依赖 | Policy 隐式查询未声明状态字段 → 难调试 | PolicyDispatcher 包装层强制 L2-C2 约束:Policy 只查 requires_runtime_state 声明的字段,未声明字段查询由 GuardedState 拦截并 raise UndeclaredStateAccess(见 §PolicyDispatcher 小节) |
| Policy 类型覆盖不够 | 新算法需要新 Policy 类(如 in-network aggregation routing) | v1.2.0 内置 5 类 + v1.3.2 加 PerFlowletAdaptivePolicy → 6 类;后续新增 Policy 类需 bump spec 版本 + 更新 @tbl-22 映射表 + 更新 @tbl-23b descriptor schema |
| PerFlowletAdaptive 乱序风险 | flowlet 边界换路径后,若 flowlet_gap_ns < d_max - d_min(候选路径端到端延迟差),同 flow 跨 flowlet 仍可能乱序——LetFlow 论文给出的无乱序条件是 $\tau_{\text{gap}} \ge d_{\max} - d_{\min}$(详见 04-自适应路由.md §Flowlet Gap 的无乱序条件 公式 eq:route-ar-flowlet-gap) | spec 不在仿真侧校验 flowlet_gap_ns vs 路径延迟差关系——用户配置责任(业界 IB AR 由 Subnet Manager 经验估配);NG8 已声明不建模报文乱序 / 重排序 → 仿真结果不反映乱序代价 |
| PathSet 在 distribution 形态的 minimal/valiant 分段约定可能歧义 | UGAL 变体(如 PAR)的 minimal 集合定义不同 | 由 RoutingStrategy.minimal_count 类属性强制声明;非 distribution 算法无此属性 |
| Packet Spraying 报文乱序对上层协议影响 | 仿真结果可能高估 Packet Spraying 性能(实际重排序缓冲会引入延迟,对小消息更敏感) | 仿真侧仅测吞吐 / 链路利用率指标,不预测端到端 RDMA 延迟;NG8 已显式声明"不建模报文乱序 / 重排序",需要时另立 spec 处理 |
| D-mod-k 邻居命名约束 | 公式 port = floor(d/k^l) mod k 产出端口编号 $q$,若 Strategy 通过邻居名字典序反推 $q$,则拓扑生成器命名规律改变会让 D-mod-k 失去无阻塞性质(业界 OpenSM 走 LID 等价物,本模块图抽象无 LID 概念) | EdgeSpec.attrs 新增 port_idx: int 字段显式标注每条边的物理端口编号,Strategy 直接读 attrs 找邻居。port_idx 替代 LID 的范围:OpenSM LID 同时承载"节点全局标识"+"端口编号"双重职能;本模块 NodeId 已覆盖节点标识,port_idx 仅替代 LID 的"端口编号"维度(即 "src 节点选 q 号端口对应哪条出边" 的映射),不引入全局编号空间。拓扑生成器在 fat-tree edge 输出时必须标注 port_idx(属 plan 范围) |
@tbl-spec-route-29 风险与缓解措施
已知限制(设计选择的代价):
- L1:
模块不感知运行时网络状态(v1.2.0 已通过 Layer 2 NetworkState 接口解决) - L2:单图绑定假设(一个 Strategy 实例对应一个 graph 绑定);多 graph 跨域路由需多实例
- L3:attrs schema 是模块内中央维护,跨项目复用时需协调命名(如不同项目都用
dimension但语义不一致) - L4:模块只产出"路径 + 路径属性",不产出"流量到路径的分配概率超出 RoutingResult 形态范围"(如 SDN 全局规划留待未来)
验收标准
| 场景 | 指标 | 目标值 | 测试方法 |
|---|---|---|---|
| 通用算法在族无关图上工作 | shortest_path / ECMP 在示例 YAML 上均能产出非空 RoutingResult | 100% 覆盖 | 端到端单测 |
| 选路权重为带宽倒数 | 异构带宽图中,shortest_path 选出的路径优先走高带宽链路(而非低延迟路径) | 100% | 单测:构造 c2c(400Gbps,0.2us) + r2r(200Gbps,2us) 异构图,验证选路结果偏向 c2c |
| 选路权重与延迟解耦 | 路径延迟仍按 $\eqref{eq:rt-path-latency}$ 计算(含 switch 转发延迟),不受选路权重影响 | 路径延迟 = 边延迟之和 + switch 转发延迟之和 | 单测对比手算 |
| 族特定算法属性校验前置 | DOR 在缺 dimension 属性的图上 bind_graph 期 raise | raise IncompatibleStrategyError | 单测 |
| attrs schema 防杂物间 | YAML 含未注册 key(如 dim 而非 dimension) → 加载期 warning 含 key 名 | warning 命中 | 单测 |
| 算法注册扩展性 | 新增算法仅需实现 Strategy 子类、注册到工厂、可选扩展 schema | < 100 行新增;0 行修改既有 Strategy 代码 | code review |
| RoutingResult 三种形态统一接口 | 调用方代码消费 single / multi / distribution 三种结果时无 isinstance 分支 | 0 处 isinstance | 静态扫描 |
| 实验意图显式 | YAML 缺 routing.algorithm 字段时加载期 raise(不 fallback) | 100% | 单测:构造缺字段 YAML → raise MissingAlgorithmField |
| 同拓扑切算法 | 同一份 graph YAML 切换 algorithm 字段对应不同 Strategy 实例 | algorithm 是唯一影响算法的输入 | 单测:同 YAML 用三种 algorithm 加载,得到三种 Strategy |
配置加载零默认值(合规 config-loading.md) | 代码中无任何配置参数的默认值(不含 dataclass 零值) | grep dict.get(.*,.*) / getattr(.*,.*,.*) 在路由模块代码中无命中 | 静态扫描 + 单测:缺任意 attrs key 时 raise MissingConfigAttr |
| attrs 必填校验 | YAML 缺 routing.attrs 段或 attrs 缺被消费 key 时加载期 raise | 100% | 单测:分别构造缺 attrs 段、缺模块级 key、缺算法级 key 三种 YAML → raise |
| 公式 \eqref{eq:rt-aggregate-bw} 严格成立条件 | 多路径独立、等代价时聚合带宽误差 < 1% | 1% | 单测对比手算 |
| 模块边界(G6) | RoutingResult 不依赖通信延迟模型 / 争用模型代码 | 0 import 反向依赖 | 静态扫描 import |
| Strategy 双类属性声明(v1.2.0) | 所有 Strategy 子类必须声明 decision_frequency + requires_runtime_state | 100% 子类符合 | grep 检查;缺失声明 → 工厂期 raise |
| Strategy ↔ Policy 映射正确(v1.2.0) | 5 类 decision_frequency 都能匹配到对应 ForwardingPolicy;非法组合 raise | 100% 覆盖 | 单测:每种组合都跑一遍 |
| 静态算法零 NetworkState 开销(v1.2.0) | StaticTablePolicy / EcmpHashPolicy 的 next_hop 不查 NetworkState 任何字段 | 0 次 state 查询 | 单测:mock NetworkState 计数查询 |
| FirstHopAdaptive 路径缓存(v1.2.0) | 同一 flow_id 在后续 hop 必须复用首跳决策 | 100% 一致 | 单测:构造 UGAL 多跳路径 + 故意改变 NetworkState,验证 path_index 不变 |
| PerHopAdaptive 无缓存(v1.2.0) | 同一 flow 不同 hop 的 NetworkState 不同时,决策应不同 | 100% 响应变化 | 单测:构造队列变化场景,验证 next_hop 输出变化 |
| NetworkState 未声明字段保护(v1.2.0) | Policy 查询未在 requires_runtime_state 声明的字段 → raise | raise UndeclaredStateAccess | 单测:恶意 Policy 查询未声明字段 |
| PathSet distribution 分段约定(v1.2.0) | 输出 distribution 的 Strategy 必须额外声明 minimal_count;前 N 条路径为 minimal | 100% 声明覆盖 | 单测:UGAL 构造 distribution 结果,验证前 N 条为 minimal |
| Policy Descriptor Schema 严格产出(v1.2.1) | 桥接层按 §Policy Descriptor Schema 表的 5 行精确映射产出 descriptor 字典;禁止启发式推断 | 5 类组合各有 descriptor 单测;非法组合 raise;缺字段 raise | 单测:(1) 5 种合法组合断言 descriptor 字段集合精确匹配;(2) 模拟用户写 decision_frequency=static + output_form=distribution 应 raise IncompatibleStrategyError;(3) UGAL strategy 缺 bias attrs 应在 Strategy 构造期 raise MissingConfigAttr(不到 descriptor 阶段) |
| PolicyDispatcher 字段访问保护(v1.2.1,承载 L2-C2) | Policy 查询未在 requires_runtime_state 声明的 NetworkState 字段 → dispatcher 拦截 raise UndeclaredStateAccess | 100% 拦截命中 | 单测:构造 mock Policy 查询未声明字段(如 UGAL 的 requires={link_busy_until_ns, switch_queue_depth},让 mock 查 link_load)→ assert raise |
PacketSpray congestion_aware schema 校验(v1.3.0) | YAML 含 algorithm: packet_spray 但 attrs 缺 congestion_aware → 工厂期 raise MissingConfigAttr;attrs 含且类型为 bool 时按 §Policy Descriptor Schema 表 packet_spray 行透传 | 100% | 两个单测:(1) 缺字段 → raise MissingConfigAttr 含字段名;(2) congestion_aware=true 时 descriptor 含 "congestion_aware": true |
| D-mod-k attrs schema + 跨表一致性 (C7) 校验(v1.3.1) | YAML 含 algorithm: dmodk 但 attrs 缺 w_levels 或 p_levels → 工厂期 raise MissingConfigAttr;len(w_levels) != len(p_levels) 或与图中 max(edge.level)+1 不一致 → bind_graph 期 raise IncompatibleStrategyError,错误消息含具体不一致字段 | 100% | 三个单测:(1) 缺 w_levels → raise MissingConfigAttr 含字段名;(2) w_levels=[4,4] + p_levels=[1,1,1] (长度不等) → raise IncompatibleStrategyError;(3) w_levels=[4,4,4] + graph 含 level=3 边 → raise IncompatibleStrategyError 含"max(level)+1 != len(w_levels)" |
| PerFlowletAdaptive flowlet 缓存语义 (L2-C7)(v1.3.2) | (a) 同 (flow_id, flowlet_id) 多 packet 必须沿用同一 path_index;(b) now - last_pkt_ts > flowlet_gap_ns 时 flowlet_id+1 并触发新一轮 hash 选路(同 hash key 对不同 flowlet_id 给出不同 path_index 的概率 ≈ 1 - 1/K);(c) 同 (flow_id, flowlet_id) 中间跳全部沿用首跳决策 | 100% 拦截命中 | 三个单测:(1) 构造同 flow 连续 2 packet 时间差 < flowlet_gap_ns,assert path_index 相同;(2) 构造同 flow 两 packet 时间差 > flowlet_gap_ns,assert flowlet_id 单调 +1 且 cache key 必须区分(不强求 path_index 必然变化);(3) 构造单 packet 多跳路径,assert (flow_id, 0) 缓存生成 + 该 packet 沿路径所有中间跳的 next_hop 决策都使用同一 path_index |
PerFlowletAdaptive flowlet_gap_ns schema 校验(v1.3.2) | YAML 含 algorithm: flowlet_ar 但 attrs 缺 flowlet_gap_ns → 工厂期 raise MissingConfigAttr;attrs 含但类型非 float 或 ≤ 0 → raise InvalidConfigAttrType 含字段名 | 100% | 两个单测:(1) 缺字段 → raise MissingConfigAttr 含 "flowlet_gap_ns";(2) flowlet_gap_ns=-1.0 → raise InvalidConfigAttrType |
| PerFlowletAdaptive hash 均匀性 + key 区分(v1.3.2) | 构造 N=100 个不同 flow_id,每个 flow 跨 flowlet 边界(生成 flowlet_id=0 与 flowlet_id=1 两次决策)。期望 flowlet_id+1 后 path_index 发生变化的 flow 占比应满足 hash 均匀性下的伯努利分布:变化概率 p = (K-1)/K,N 次独立采样的变化数 X ~ Binomial(N, p),验证 ` | X - N·p | ≤ 3·sqrt(N·p·(1-p))`(3σ 区间) |
@tbl-spec-route-30 验收标准
关键单测场景:
- 通用图通用算法:32-chip ring(无 attrs) + shortest_path → 路径长度符合环距离
- 多路径聚合:32-chip fat-tree + ECMP,max_paths=4 → RoutingResult.paths() 返回 4 条等代价路径
- 族特定算法:4×4×4 torus(边 attrs 含 dimension/wraparound) + DOR → 路径长度 = Manhattan 距离
- 分布算法:dragonfly(边 attrs 含 role) + UGAL → RoutingResult.output_form == "distribution",含 minimal + valiant 两种
- 属性消费校验:构造 DorStrategy 时绑定无 dimension 的 graph →
bind_graphraise,错误 message 含"dimension" - YAML 拼写错误:YAML 写
attrs: {dim: x}(应为dimension) → 加载期 warning,DorStrategy bind 期 raise - YAML 缺 algorithm:YAML 不写
routing.algorithm→ 加载期 raiseMissingAlgorithmField,错误 message 含 YAML 路径 - YAML algorithm 不存在:YAML 写
algorithm: dor_xy(未注册算法名)→ raiseUnknownRoutingAlgorithm - 同拓扑切算法对比:同一份 fat-tree YAML 分别用
algorithm: shortest_path/ecmp/e_ecmp加载 → 得到三种不同 Strategy 实例,graph完全相同 - 跨连通分量:构造非连通图 + 跨分量 src/dst → bind_graph raise DisconnectedGraphError
- routing.attrs 缺模块级 key:YAML
routing.attrs不含bandwidth_utilization→ raiseMissingConfigAttr,错误 message 含"bandwidth_utilization" - routing.attrs 缺算法级 key:algorithm=dor 但 attrs 不含
dimension_order→ raiseMissingConfigAttr,错误 message 含"dimension_order" - routing.attrs 含已注册但未消费的 key:algorithm=shortest_path 但 attrs 含
max_paths→ warning,不影响行为 - edge attrs 拼写错误:YAML 边写
attrs: {dim: x}(应为dimension)→ 加载期 warning(W1),DorStrategy bind_graph raiseIncompatibleStrategyError - 带宽倒数选路:构造异构图(c0-c1: 400Gbps/0.2us, c0-s0: 200Gbps/0.1us, s0-c1: 200Gbps/0.1us),shortest_path 应选 c0→c1 直连(权重 1/400=0.0025)而非 c0→s0→c1(权重 1/200+1/200=0.01),尽管后者延迟更低(0.2us vs 0.2us 相当,但权重差 4 倍)
- 边 type 即层级标识:从 RoutingResult 路径边序列读取 EdgeSpec.type,验证 c2c/b2b/r2r 标识正确且与 link_types 定义一致
未来方向
| 方向 | 优先级 | 前置条件 |
|---|---|---|
| 自适应路由(per-flowlet AR / Q-routing / TE-CCL) | 中 | per-flowlet AR (LetFlow style) 接口已在 v1.3.2 spec 就绪(新增 PerFlowletAdaptivePolicy + L2-C7 + flowlet_gap_ns attrs);Q-routing / TE-CCL 待补对应 Strategy + 复用既有 Policy 类 |
| 拥塞感知 flowlet AR(CONGA / NVIDIA Quantum IB AR) | 中 | v1.3.2 PerFlowletAdaptivePolicy 已支持 hash 切换 LetFlow 模式;拥塞感知变体需扩展 requires_runtime_state 加入 link_load,在 flowlet 边界查 argmin 替代纯 hash;可通过 attrs flowlet_congestion_aware: bool 复用既有 Policy 框架(同 PacketSpray 的 congestion_aware 设计),不必新增 Policy 类 |
| 加权 ECMP(WCMP)对非对称 / 故障拓扑 | 低 | EdgeSpec 加 weight 字段;与故障注入 spec 合并设计 |
| KSP 用于非结构化拓扑(Jellyfish / Xpander) | 低 | 拓扑生成器先支持随机正则图 |
| DQPLB(DRB 变体 / multi-plane) | 中 | Packet Spraying 算法接口(Strategy + Policy + descriptor schema)已在 v1.3.0 spec 就绪,对应 Layer 1 / Layer 2 实现见 plan;DQPLB 在此基础上扩展多平面路径生成器(v1.2.1 已让 NetworkState 暴露 link_load 满足拥塞感知部分) |
| Dmodc(降级 fat-tree 适配) | 低 | D-mod-k 算法接口已在 v1.3.1 spec 就绪;Dmodc(Gliksberg et al., IEEE MICRO 2023)保持闭式算术公式但将固定 $k$ 替换为基于代价的动态分隔数,适应链路 / switch 故障破坏对称性的拓扑——需新 Strategy + 取消 NG9 限制(或单独 spec 覆盖降级场景) |
| 路径稀疏 / 闭式存储优化 | 低 | 集群规模 > 1024 chip 后再考虑 |
| 路由可视化(前端展示路径热度) | 中 | RoutingResult 接口稳定后即可 |
| 多 graph 路由(异构集群跨域) | 中 | 解除单图绑定假设;引入图域抽象 |
| 路由策略评分自检("在该图上算法选得对吗") | 低 | 给定一组 traffic,离线对比多策略产生的瓶颈分布 |
| 第 6 类 Policy(in-network aggregation routing) | 低 | NCCL SHARP / 在网计算等需要 switch 内 reduce 的算法;要扩展 Layer 2 接口语义 |
| Layer 1 / Layer 2 自动一致性检查 | 中 | 加载期工具:对每个注册 Strategy 校验 decision_frequency 与 output_form 组合合法 |
| PolicyDispatcher 字段访问统计 / 调优 | 低 | dispatcher 已建好审计能力(v1.2.1);扩展为可导出 per-Policy 的 state query 频次报告,辅助 NetworkState 精度调优 |
@tbl-spec-route-31 未来方向
实现备注
本节在实现完成后补充,记录 spec 与实际实现的偏差。
v1.2.0 实现偏差(已在 v1.2.1 实现中全部消除)
v1.2.0 时段存在的 5 项 spec ↔ 实现偏差,由 v1.2.1 实现(
docs/plans/2026-05-22-routing-v1.2.1-spec-gaps.md)逐项修复。本表保留历史记录,正文以 v1.2.1 实现为准。
| 偏差点(v1.2.0 时段) | 已落地修复 |
|---|---|
| Python ↔ Rust 桥接协议启发式推断,PerHopAdaptive / PacketSpray 路径不可达 | 桥接层改为 _build_policy_descriptor 按 spec @tbl-spec-route-23b 5 行严格映射;Rust 端按 descriptor["type"] 分发;5 类 Policy 实例化全部解锁 |
NetworkState.link_load 常返 0.0,违反精度承诺 | PhyLink 加滑动平均字段 recent_busy_ns + window_end_ns,窗口 W = base_latency_ns × 10;REQ + RSP 两 VC 都计入 |
NetworkState.flow_count_on_link 常返 0,违反精度承诺 | C2CNetwork 加 in_flight_packets_on_link RefCell HashMap;transmit +1 / transmit_ack -1;ObservableView 委托返此值(packet 级是 spec flow 数的合理 proxy) |
| L2-C2 NetworkState 未声明字段保护未实现 | 引入 PolicyDispatcher + GuardedState:dispatcher 持 declared_state_fields 白名单;feature audit_state_access 开启时通过 GuardedState 拦截未声明字段访问并 panic 携带字段名(默认 off 零开销) |
UGAL minimal_count 类属性硬编码 = 1 | CONFIG_ATTRS_SCHEMA 加 minimal_count: int;registry 工厂期对 distribution 形态 Strategy 用显式 if 分支写入实例属性(避免 dict.get(key, default) 字面违反 config-loading.md) |
v1.2.1 实现偏差
| 偏差点 | spec 描述 | 实际实现 | 修复时机 |
|---|---|---|---|
NetworkState.flow_count_on_link 多跳语义 | spec @tbl-21 要求 "active flow 数" 精确语义 | 实现为 "REQ packet 在每跳 +1,ACK 仅最终一跳 -1" 的 in-flight upstream 计数 proxy。多跳路径中间链路 counter 趋于累积偏置。 | v1.2.1 接受此偏置:当前 adaptive policy(UGAL)只消费 switch_queue_depth + link_busy_until_ns,不读 flow_count_on_link,偏置不影响决策正确性。未来引入消费 flow_count_on_link 的新 policy 时,补每跳 ACK decrement hook(switch 转发期同步减少上游 link 的 counter)。 |
link_load 衰减实现 | spec @tbl-21 给三选项(滑动平均 / 瞬时 / 累计 EWMA) | v1.2.1 选定线性衰减式滑动平均:每次 transmit 时 decay = (now - window_end) / W,recent_busy *= (1 - decay),再 += duration;W = base_latency_ns × 10。 | v1.2.1 实现选定参数已登记。若实测 adaptive policy 决策抖动,调到 W = × 20 / × 50 并更新本表。 |
所有 v1.2.1 验收标准对应单测全 PASS(
pytest tests/topology_routing/test_policy_descriptor.py10/10 +pytest tests/g5/test_multi_chip_regression.py3/3 +cargo test --features audit_state_access forwarding25/25 +cargo test forwarding(default off)23/23)。