<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>LEEcDiang</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <icon>https://leecdiang.github.io/myblog-source/images/avatar.png</icon>
  <id>https://leecdiang.github.io/myblog-source/</id>
  <link href="https://leecdiang.github.io/myblog-source/" rel="alternate"/>
  <link href="https://leecdiang.github.io/myblog-source/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, LEEcDiang</rights>
  <subtitle>Tracing systems inside and beyond the clock.</subtitle>
  <title>Beyond the Clock</title>
  <updated>2026-07-01T05:07:01.768Z</updated>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Silicon &amp; Circuits" scheme="https://leecdiang.github.io/myblog-source/categories/Silicon-Circuits/"/>
    <category term="分布式训练" scheme="https://leecdiang.github.io/myblog-source/tags/%E5%88%86%E5%B8%83%E5%BC%8F%E8%AE%AD%E7%BB%83/"/>
    <category term="集合通信" scheme="https://leecdiang.github.io/myblog-source/tags/%E9%9B%86%E5%90%88%E9%80%9A%E4%BF%A1/"/>
    <category term="网络拓扑" scheme="https://leecdiang.github.io/myblog-source/tags/%E7%BD%91%E7%BB%9C%E6%8B%93%E6%89%91/"/>
    <category term="MFU" scheme="https://leecdiang.github.io/myblog-source/tags/MFU/"/>
    <category term="AI集群" scheme="https://leecdiang.github.io/myblog-source/tags/AI%E9%9B%86%E7%BE%A4/"/>
    <category term="智算网络" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%99%BA%E7%AE%97%E7%BD%91%E7%BB%9C/"/>
    <category term="AI-Infrastructure" scheme="https://leecdiang.github.io/myblog-source/tags/AI-Infrastructure/"/>
    <category term="Distributed-Training" scheme="https://leecdiang.github.io/myblog-source/tags/Distributed-Training/"/>
    <category term="GPU-Cluster" scheme="https://leecdiang.github.io/myblog-source/tags/GPU-Cluster/"/>
    <category term="NCCL" scheme="https://leecdiang.github.io/myblog-source/tags/NCCL/"/>
    <category term="Scale-Up" scheme="https://leecdiang.github.io/myblog-source/tags/Scale-Up/"/>
    <category term="Scale-Out" scheme="https://leecdiang.github.io/myblog-source/tags/Scale-Out/"/>
    <content>
      <![CDATA[<blockquote><p><strong>Abstract - Part 1</strong><br>As AI clusters scale from hundreds to tens of thousands of accelerators, peak compute no longer translates directly into usable throughput. This article examines how distributed parallelism, collective communication, network topology, synchronization, and tail latency reshape training efficiency, and explains why moving data has become one of the central constraints of large-scale AI systems.</p></blockquote><div class="note info"><p>本文是「智算互联」系列上篇。<a href="/myblog-source/series/ai-interconnect/">查看系列目录 →</a></p></div><div style="display: flex; justify-content: center; gap: 16px; margin: 32px 0;">  <a class="glass-pill" href="../ai-compute-interconnect-part2-reliability-rethink/">    中篇：关于可靠性 →  </a>  <a class="glass-pill" href="../ai-compute-interconnect-part3-smart-nic-chip/">    下篇：剖析智算网卡 →  </a></div><hr><h2 id="一、峰值算力不等于有效吞吐">一、峰值算力不等于有效吞吐</h2><h3 id="1-从-FLOPS-到-MFU">1. 从 FLOPS 到 MFU</h3><p>芯片规格表通常给出峰值浮点算力（Peak FLOPS），但训练系统真正关心的是：这些理论算力中，有多少被模型的有效计算使用。</p><p>模型 FLOPs 利用率（Model FLOPs Utilization, MFU）可以写成：</p><p>$$<br>\mathrm{MFU}=<br>\frac{\text{模型每步所需 FLOPs}}{\text{单步时间}\times\text{加速器数量}\times\text{单卡峰值 FLOPS}}<br>$$</p><p>PaLM 论文将 MFU 定义为模型有效计算吞吐与硬件理论峰值吞吐之比。这个指标刻意不把通信、内存访问、重计算和系统等待算作“有用模型 FLOPs”，因此它能够直观暴露集群中的非计算开销。<a href="https://arxiv.org/abs/2204.02311">PaLM: Scaling Language Modeling with Pathways</a></p><p>但 MFU 下降可能来自多种原因：</p><ul><li>GPU 内核没有充分利用 Tensor Core</li><li>HBM 带宽或访存模式成为瓶颈</li><li>计算与通信没有重叠</li><li>流水线出现 Bubble</li><li>MoE 专家负载不均衡</li><li>数据加载、Checkpoint 或故障恢复打断训练</li><li>某个 Rank 变成 Straggler，迫使其余设备等待</li></ul><p>网络只是其中一项。</p><blockquote><p>随着加速器变快、集群变大，通信成为越来越难以被计算隐藏的主要开销之一。</p></blockquote><h3 id="2-单步时间应该怎样拆解">2. 单步时间应该怎样拆解</h3><p>一个训练 Step 可以近似拆成：</p><p>$$<br>T_{\mathrm{step}} = T_{\mathrm{compute}} +<br>T_{\mathrm{exposed\ comm}} +<br>T_{\mathrm{bubble}} +<br>T_{\mathrm{sync}} +<br>T_{\mathrm{I/O}}<br>$$</p><p>$T_{\mathrm{exposed\ comm}}$ 是<strong>没有被计算覆盖、暴露在关键路径上的通信时间</strong>，不同于总通信时间。</p><p>例如，梯度 AllReduce 可能在反向传播过程中分桶并提前启动。如果网络足够快，通信可以与后续层的反向计算重叠；如果网络稍慢，最后一个 Bucket 仍未完成，优化器就必须等待。此时的性能往往不是平滑下降，而是在某个临界点后突然恶化。</p><p>只看“总通信量”不够实用。实际还要问：</p><ul><li>通信发生在 Step 的什么位置？</li><li>是否位于依赖链关键路径？</li><li>消息能否拆分并流水化？</li><li>通信库是否理解实际拓扑？</li><li>发送端、交换网络和接收端是否同时可用？</li></ul><h3 id="3-Amdahl-定律在-AI-集群中的变体">3. Amdahl 定律在 AI 集群中的变体</h3><p>经典 Amdahl 定律说明，无法并行化的串行部分会限制整体加速比：</p><p>$$<br>S(N)=\frac{1}{(1-P)+\frac{P}{N}}<br>$$</p><p>但大模型训练中的“串行部分”并不是固定常数。卡数增加后，通信范围、同步参与者和拓扑跨度也在变化。更接近现实的表达是：</p><p>$$<br>S(N)=\frac{T_1}{\frac{T_{\mathrm{compute}}}{N}+T_{\mathrm{comm}}(N)+T_{\mathrm{sync}}(N)+T_{\mathrm{imbalance}}(N)}<br>$$</p><p>这里的 $T_{\mathrm{comm}}(N)$ 和 $T_{\mathrm{sync}}(N)$ 可能随规模上升。于是，新增 GPU 一方面降低每张卡承担的计算量，另一方面也可能增加集体通信和等待成本。</p><p>从系统视角看，真正应该观测的是<strong>扩展效率（Scaling Efficiency）</strong>：</p><p>$$<br>\eta(N)=\frac{\text{使用 }N\text{ 张卡的实际加速比}}{N}<br>$$</p><p>如果 1024 张卡只获得相对单卡 600 倍的速度，扩展效率约为 58.6%。剩余部分并不一定全部被“网络吃掉”，但它提醒我们：卡数不是有效算力的等价单位。</p><h2 id="二、并行策略决定流量，而不是“AI”两个字">二、并行策略决定流量，而不是“AI”两个字</h2><p>“AI 流量”并不是一种统一的网络负载。数据并行、张量并行、流水线并行、专家并行和上下文并行，会产生完全不同的通信指纹。</p><p>NVIDIA Megatron Core 也把这些策略视为可以组合的不同维度：数据并行（DP）、张量并行（TP）、流水线并行（PP）、上下文并行（CP）与专家并行（EP）。<a href="https://docs.nvidia.com/megatron-core/developer-guide/latest/user-guide/parallelism-guide.html">Megatron Core Parallelism Strategies Guide</a></p><h3 id="1-Data-Parallelism：周期性的大规模归约">1. Data Parallelism：周期性的大规模归约</h3><p>数据并行（Data Parallelism, DP）让不同设备持有相同模型副本，各自处理不同 Mini-batch，然后同步梯度。</p><p>典型通信原语是：</p><ul><li>AllReduce；</li><li>ReduceScatter + AllGather；</li><li>参数或优化器状态同步。</li></ul><p>它的特点通常是：</p><ul><li>消息尺寸较大；</li><li>具有明显的周期性；</li><li>对总带宽和二分带宽敏感；</li><li>最慢 Rank 会拖住整个同步点；</li><li>梯度分桶后可以与反向计算重叠。</li></ul><p>DP 的流量不是单一的大尺寸 AllReduce。训练框架通常把梯度拆成多个 Bucket，形成一串具有依赖关系的中等或大消息。网络既要有持续吞吐，也要控制最后几个 Bucket 的尾延迟。</p><h3 id="2-Tensor-Parallelism：每层都可能等待网络">2. Tensor Parallelism：每层都可能等待网络</h3><p>张量并行（Tensor Parallelism, TP）把同一层的矩阵或张量切分到多张 GPU。它解决单卡显存与单卡计算容量不足的问题，但代价是层内频繁同步。</p><p>常见通信包括：</p><ul><li>AllReduce；</li><li>AllGather；</li><li>ReduceScatter；</li><li>点对点交换局部结果。</li></ul><p>TP 的通信频率通常高于 DP，而且往往出现在每个 Transformer Layer 内部。即使单次消息没有 DP 梯度那么大，通信也更难隐藏，因为下一步矩阵计算可能依赖刚刚收到的结果。</p><p>这使 TP 对以下指标格外敏感：</p><ul><li>单跳与端到端时延；</li><li>小到中等消息效率；</li><li>拥塞下的 P99/P999 延迟；</li><li>节点内与跨节点拓扑边界。</li></ul><p>工程上通常优先把 TP 放在高带宽、低时延的 Scale-up 域内，而不是轻易跨越普通数据中心网络。</p><h3 id="3-Pipeline-Parallelism：带宽之外还有-Bubble">3. Pipeline Parallelism：带宽之外还有 Bubble</h3><p>流水线并行（Pipeline Parallelism, PP）把模型的不同层分配到不同 Stage，并将 Mini-batch 切成多个 Micro-batch，在 Stage 之间流水执行。</p><p>它的网络流量主要是：</p><ul><li>Forward 阶段传递 Activation；</li><li>Backward 阶段传递 Activation Gradient；</li><li>相邻 Stage 间的点对点通信。</li></ul><p>PP 的核心问题不只是链路带宽，而是流水线 Bubble。Stage 计算量不均、通信延迟波动或 Micro-batch 数量不足，都会让部分设备空等。Megatron-LM 的交错流水线调度正是为了减少这种 Bubble。<a href="https://arxiv.org/abs/2104.04473">Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM</a></p><h3 id="4-Expert-Parallelism：All-to-All-与-Incast">4. Expert Parallelism：All-to-All 与 Incast</h3><p>Mixture-of-Experts（MoE）模型只激活部分专家，但不同 Token 会被路由到不同设备上的专家。专家并行（Expert Parallelism, EP）通常需要：</p><ol><li>将 Token 分发到对应专家；</li><li>完成专家计算；</li><li>把结果送回原设备。</li></ol><p>这往往形成两次 All-to-All。Switch Transformer 的实现也明确使用 All-to-All 在设备间重新分片 Token。<a href="https://arxiv.org/abs/2101.03961">Switch Transformers</a></p><p>EP 的网络难点包括：</p><ul><li>多对多通信；</li><li>瞬时 Incast；</li><li>Token 路由不均衡；</li><li>热门专家造成局部热点；</li><li>小消息与大消息混杂；</li><li>尾延迟直接传导到下一层。</li></ul><p>相比规则、对称的 Ring AllReduce，MoE 流量更难仅靠静态 ECMP 哈希均衡。</p><h3 id="5-Context-Parallelism：长上下文把序列维度变成互联问题">5. Context Parallelism：长上下文把序列维度变成互联问题</h3><p>上下文并行（Context Parallelism, CP）沿序列长度切分输入。它主要服务于长上下文训练，减轻单卡 Activation 与 Attention 计算的内存压力。</p><p>随着上下文窗口从数千扩展到数十万甚至更长，Attention 相关的 KV、Query 或中间状态需要跨设备交换。Megatron Core 支持 CP 与 TP、PP、DP 组合，这意味着长上下文的通信并不是一个孤立问题，而会叠加在原有并行维度之上。<a href="https://docs.nvidia.com/megatron-core/developer-guide/latest/user-guide/features/context_parallel.html">Megatron Core Context Parallelism</a></p><h3 id="6-一张流量指纹表">6. 一张流量指纹表</h3><table><thead><tr><th style="text-align:left">并行策略</th><th style="text-align:left">常见通信原语</th><th style="text-align:left">典型位置</th><th style="text-align:left">更敏感的网络指标</th><th style="text-align:left">主要风险</th></tr></thead><tbody><tr><td style="text-align:left">DP / FSDP</td><td style="text-align:left">AllReduce、ReduceScatter、AllGather</td><td style="text-align:left">每个训练 Step</td><td style="text-align:left">持续吞吐、二分带宽、尾延迟</td><td style="text-align:left">最慢 Rank、跨机架拥塞</td></tr><tr><td style="text-align:left">TP</td><td style="text-align:left">AllReduce、AllGather、ReduceScatter</td><td style="text-align:left">每层或每组算子</td><td style="text-align:left">低时延、中小消息效率</td><td style="text-align:left">通信暴露在关键路径</td></tr><tr><td style="text-align:left">PP</td><td style="text-align:left">P2P Send/Recv</td><td style="text-align:left">相邻 Stage</td><td style="text-align:left">稳定时延、链路局部性</td><td style="text-align:left">Pipeline Bubble</td></tr><tr><td style="text-align:left">EP / MoE</td><td style="text-align:left">All-to-All</td><td style="text-align:left">每个 MoE Layer</td><td style="text-align:left">Incast 控制、负载均衡</td><td style="text-align:left">热点专家、长尾</td></tr><tr><td style="text-align:left">CP / SP</td><td style="text-align:left">AllGather、ReduceScatter、P2P</td><td style="text-align:left">Attention 与序列切分</td><td style="text-align:left">带宽与时延兼顾</td><td style="text-align:left">长序列状态交换</td></tr><tr><td style="text-align:left">Checkpoint</td><td style="text-align:left">Gather、Storage Write</td><td style="text-align:left">周期性</td><td style="text-align:left">存储网络吞吐</td><td style="text-align:left">与训练 Fabric 争用</td></tr><tr><td style="text-align:left">推理 Decode</td><td style="text-align:left">KV 访问、P2P、小批量 Collective</td><td style="text-align:left">每个 Token 或若干 Token</td><td style="text-align:left">P99 时延、抖动</td><td style="text-align:left">小消息效率与排队</td></tr></tbody></table><h2 id="三、集合通信不是“发一个大包”">三、集合通信不是“发一个大包”</h2><p>NVIDIA Collective Communications Library（NCCL）提供 AllReduce、Broadcast、Reduce、AllGather、ReduceScatter 等 GPU 集合通信原语，并根据拓扑选择算法和路径。<a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/collectives.html">NCCL Collective Operations</a></p><h3 id="1-Ring-AllReduce：带宽效率高，但步数多">1. Ring AllReduce：带宽效率高，但步数多</h3><p>Ring AllReduce 通常由 ReduceScatter 和 AllGather 两阶段组成。每个 Rank 只与相邻 Rank 通信，能够较好利用链路带宽，并避免中心节点成为瓶颈。</p><p>对于 $N$ 个 Rank、每个 Rank 数据量为 $M$ 的情形，每个 Rank 传输的数据量近似为：</p><p>$$<br>2\frac{N-1}{N}M<br>$$</p><p>当消息较大时，Ring 的带宽利用率通常很好；但它需要较多通信步，延迟项会随 Rank 数量增加。因此 Ring 更适合大消息，不一定适合延迟主导的小消息。</p><h3 id="2-Tree：减少步数，但更依赖拓扑与上层链路">2. Tree：减少步数，但更依赖拓扑与上层链路</h3><p>Tree 或 Double Binary Tree 可以把通信步数降低到对数级，更适合小消息或延迟敏感场景。但树的上层节点和链路承担更多聚合流量，若物理拓扑与逻辑树不匹配，可能形成热点。</p><h3 id="3-Hierarchical-Collective：先节点内，再节点间">3. Hierarchical Collective：先节点内，再节点间</h3><p>现代 GPU 服务器通常同时存在多层互联：</p><ul><li>GPU 封装或板内互联；</li><li>节点内 NVLink、UALink 类 Scale-up Fabric；</li><li>PCIe 与 CPU/网卡路径；</li><li>跨节点 Ethernet 或 InfiniBand Scale-out Fabric。</li></ul><p>因此，集合通信往往采用分层算法：先在节点内归约，再由每个节点的代表参与跨节点归约，最后把结果广播回节点内。这一策略的本质是尽量减少穿过较慢、较拥塞层级的数据量。</p><h2 id="四、必须先区分-Scale-up-与-Scale-out">四、必须先区分 Scale-up 与 Scale-out</h2><p>很多智算互联讨论混淆了 NVLink、UALink、PCIe、RoCE 和 UET，因为它们都在“连接加速器”。但它们解决的层级并不相同。</p><h3 id="1-Scale-up：把一个-Pod-看成更大的计算节点">1. Scale-up：把一个 Pod 看成更大的计算节点</h3><p>Scale-up 面向服务器、机架或 Pod 内的紧耦合加速器，追求：</p><ul><li>极低时延；</li><li>高带宽密度；</li><li>直接 Load、Store 与 Atomic 语义；</li><li>更强的确定性；</li><li>较小的故障与管理域。</li></ul><p>UALink 200G 1.0 就是一个开放的 Scale-up 互联规范，定义加速器与交换机之间的低时延、高带宽连接，并支持 Read、Write 和 Atomic Transaction。<a href="https://ualinkconsortium.org/blog/ualink-200g-1-0-specification-overview-802/">UALink 200G 1.0 Specification Overview</a></p><h3 id="2-Scale-out：把大量节点组成可路由集群">2. Scale-out：把大量节点组成可路由集群</h3><p>Scale-out 面向跨服务器、跨机架甚至跨园区的集群，优先考虑：</p><ul><li>可路由与可扩展；</li><li>多路径；</li><li>故障隔离；</li><li>集合通信；</li><li>多租户与运维；</li><li>成本和生态兼容。</li></ul><p>RoCEv2、InfiniBand 与 Ultra Ethernet Transport（UET）主要处于这一层。Scale-out 不会被 Scale-up 完全取代：即使一个 Pod 内部拥有高速内存语义互联，多个 Pod 之间仍需要可扩展网络。</p><h3 id="3-PCIe-与-CXL：主机-I-O-和内存扩展层">3. PCIe 与 CXL：主机 I/O 和内存扩展层</h3><p>PCI Express（PCIe）首先是主机与设备之间的 I/O 互联。CXL 建立在 PCIe 物理层之上，为缓存与内存扩展提供更丰富语义。它们与 Scale-out 网络之间存在交汇，但不能简单视作同一种 Fabric。</p><pre><code class="mermaid">flowchart LR    A[GPU &#x2F; AI Accelerator] --&gt; B[封装内或板内互联]    B --&gt; C[Scale-up Fabric&lt;br&#x2F;&gt;NVLink &#x2F; UALink 类]    C --&gt; D[主机与 I&#x2F;O&lt;br&#x2F;&gt;PCIe &#x2F; CXL]    D --&gt; E[SmartNIC &#x2F; RNIC]    E --&gt; F[Scale-out Fabric&lt;br&#x2F;&gt;RoCE &#x2F; UET &#x2F; InfiniBand]    F --&gt; G[其他服务器或 Pod]    F --&gt; H[存储与 Checkpoint 网络]</code></pre><p>这张图画的是工程边界，不是协议分层：<strong>每一层的延迟预算、可靠性语义和可扩展范围不同。</strong></p><h2 id="五、拓扑决定了“400G”能不能变成-400G">五、拓扑决定了“400G”能不能变成 400G</h2><h3 id="1-Clos-Fat-tree：用多级交换换取规模">1. Clos / Fat-tree：用多级交换换取规模</h3><p>大型数据中心常采用 Clos 或 Fat-tree 类拓扑。服务器通过 Leaf 交换机接入，再经 Spine 层互联。它提供多条等价路径和较好的横向扩展能力，但也引入了几个关键问题：</p><ul><li>上行带宽是否与下行带宽匹配；</li><li>Leaf-to-Spine 是否过订阅；</li><li>ECMP 是否把多条大流哈希到同一路径；</li><li>跨 Rail 通信是否穿越额外层级；</li><li>故障后剩余路径是否出现新的热点。</li></ul><h3 id="2-Oversubscription：端口速率没有消失，二分带宽才是上限">2. Oversubscription：端口速率没有消失，二分带宽才是上限</h3><p>假设一个 Leaf 交换机有 32 个 400G 下行端口，却只有 16 个 400G 上行端口，那么下行总带宽为 12.8 Tbps，上行只有 6.4 Tbps，过订阅比为 2:1。</p><p>单台服务器做点对点测试时可以跑满 400G，但当大量服务器同时跨 Leaf 通信时，每台服务器可获得的平均带宽可能远低于端口标称值。</p><p>因此，集群网络不能只看：</p><ul><li>单端口速率；</li><li>交换芯片总容量；</li><li>单条链路延迟。</li></ul><p>还应关注：</p><ul><li>Bisection Bandwidth（二分带宽）；</li><li>Oversubscription Ratio（过订阅比）；</li><li>多路径利用率；</li><li>Job Placement 与拓扑亲和性；</li><li>故障降级后的剩余容量。</li></ul><h3 id="3-Rail-optimized：让并行维度与物理网络对齐">3. Rail-optimized：让并行维度与物理网络对齐</h3><p>AI 集群常把每台服务器内相同编号的 GPU 或网卡连接到同一条 Rail。这样，跨节点 Collective 可以尽量沿固定 Rail 进行，减少不必要的横向跳转。</p><p>但 Rail 优化并非自动生效。训练框架、NCCL 拓扑发现、容器调度、GPU/NIC NUMA 亲和性和交换网络布线必须一致。否则硬件拥有多条 Rail，软件却可能把流量集中到少数路径。</p><pre><code class="mermaid">flowchart TB    subgraph S1[Server 1]      G11[GPU 0] --- N11[NIC 0]      G12[GPU 1] --- N12[NIC 1]    end    subgraph S2[Server 2]      G21[GPU 0] --- N21[NIC 0]      G22[GPU 1] --- N22[NIC 1]    end    subgraph S3[Server 3]      G31[GPU 0] --- N31[NIC 0]      G32[GPU 1] --- N32[NIC 1]    end    N11 --&gt; R0[Rail 0 Fabric]    N21 --&gt; R0    N31 --&gt; R0    N12 --&gt; R1[Rail 1 Fabric]    N22 --&gt; R1    N32 --&gt; R1</code></pre><h2 id="六、为什么-400G-800G-仍然会遇到通信墙">六、为什么 400G / 800G 仍然会遇到通信墙</h2><h3 id="1-序列化时间只是最低下限">1. 序列化时间只是最低下限</h3><p>在理想情况下，400 Gbit/s 链路发送 1 MiB 数据的纯序列化时间约为 21 微秒，发送 4 MiB 约为 84 微秒。现实中还要加入：</p><ul><li>Ethernet、IP、UDP、RoCE 等头部开销；</li><li>PCIe DMA 与内存访问；</li><li>队列调度；</li><li>交换芯片转发；</li><li>FEC 与 PHY Pipeline；</li><li>路径竞争；</li><li>接收端处理与完成队列更新。</li></ul><p>当 Collective 包含多个阶段、多个跳数和全局同步时，一次通信的有效延迟远高于单链路序列化下限。</p><h3 id="2-小消息看时延，大消息看带宽，混合消息看调度">2. 小消息看时延，大消息看带宽，混合消息看调度</h3><p>用经典的 $\alpha$-$\beta$ 模型表示一次消息传输：</p><p>$$<br>T(M)=\alpha+\beta M<br>$$</p><p>其中：</p><ul><li>$\alpha$ 是固定启动延迟；</li><li>$\beta$ 是每字节传输时间；</li><li>$M$ 是消息大小。</li></ul><p>小消息由 $\alpha$ 主导，提高链路速率的收益有限；大消息由 $\beta M$ 主导，更快的 SerDes 和更多 Lane 更有效。AI 集群的难点在于两者同时存在：控制消息、Doorbell、Completion、小 Bucket 与大规模 Activation/Gradient 可能共享同一 Fabric。</p><h3 id="3-尾延迟比平均带宽更危险">3. 尾延迟比平均带宽更危险</h3><p>同步训练遵循“木桶效应”。999 个 Rank 按时完成，而第 1000 个 Rank 因拥塞晚了 200 微秒，整个 Collective 仍要等它。</p><p>因此，AI 网络不仅追求平均吞吐，还要追求：</p><ul><li>低抖动；</li><li>低 P99/P999；</li><li>快速故障检测；</li><li>稳定的多路径负载均衡；</li><li>Incast 下的队列可控；</li><li>避免无关流量污染训练关键路径。</li></ul><h3 id="4-Goodput-比-Line-Rate-更诚实">4. Goodput 比 Line Rate 更诚实</h3><p>Line Rate 表示端口能够处理的物理线速；Goodput 表示应用最终获得的有效数据吞吐。两者之间可能损失在：</p><ul><li>协议头与 FEC；</li><li>重传；</li><li>空闲与流控；</li><li>PCIe、DMA 和内存系统；</li><li>软件栈；</li><li>拥塞与路径不均；</li><li>Collective 算法本身。</li></ul><p>所以一张“400G 网卡”只说明它具备进入高性能网络的必要条件，并不能单独证明它能在千卡或万卡训练中维持高扩展效率。</p><h2 id="七、从工作负载到网络需求">七、从工作负载到网络需求</h2><p>把以上内容压缩成一条因果链：</p><pre><code class="mermaid">flowchart LR    A[模型规模与上下文增长] --&gt; B[多维并行]    B --&gt; C[Collective 更频繁]    C --&gt; D[同步与 Incast 增加]    D --&gt; E[尾延迟和路径热点]    E --&gt; F[暴露通信时间上升]    F --&gt; G[MFU 与扩展效率下降]</code></pre><p>这条链条说明，通信墙不是“网速不够快”这么简单。它同时涉及：</p><ul><li>模型切分；</li><li>Collective 算法；</li><li>GPU 与 NIC 的亲和性；</li><li>网络拓扑；</li><li>拥塞控制；</li><li>传输可靠性；</li><li>网卡和交换机实现；</li><li>光电物理层。</li></ul><h2 id="八、评价一套智算网络，应该看什么">八、评价一套智算网络，应该看什么</h2><table><thead><tr><th style="text-align:left">层级</th><th style="text-align:left">不应只看</th><th style="text-align:left">更应该看</th></tr></thead><tbody><tr><td style="text-align:left">加速器</td><td style="text-align:left">峰值 FLOPS</td><td style="text-align:left">MFU、Scaling Efficiency、Step Time</td></tr><tr><td style="text-align:left">网卡</td><td style="text-align:left">端口标称速率</td><td style="text-align:left">Goodput、小包 Mpps、DMA 效率、P99 延迟</td></tr><tr><td style="text-align:left">交换机</td><td style="text-align:left">总交换容量</td><td style="text-align:left">二分带宽、缓冲、调度、故障降级</td></tr><tr><td style="text-align:left">Fabric</td><td style="text-align:left">是否“无损”</td><td style="text-align:left">Incast、热点、重传、尾延迟、稳定性</td></tr><tr><td style="text-align:left">Collective</td><td style="text-align:left">单次带宽测试</td><td style="text-align:left">真实模型下 AllReduce / All-to-All JCT</td></tr><tr><td style="text-align:left">集群</td><td style="text-align:left">GPU 数量</td><td style="text-align:left">作业完成时间、利用率、故障恢复时间</td></tr></tbody></table><h2 id="结语：真正稀缺的是“按时抵达的数据”">结语：真正稀缺的是“按时抵达的数据”</h2><p>AI 集群的演进正在改变基础设施的价值排序。过去我们习惯把 GPU 看作计算主体，把网络看作外设；但在多维并行和大规模同步下，网络已经参与决定每一步计算能否按时开始。</p><p>更多 GPU 仍然能够提供更高的理论算力，但只有当通信、拓扑和调度共同跟上，理论算力才会转化为训练吞吐。</p><p>上篇回答了通信压力从哪里来。接下来更尖锐的问题是：传统 RoCE 网络为什么如此依赖 PFC？当“绝不丢包”的代价越来越高，可靠性应该由交换机、网卡还是端点承担？</p><link rel="stylesheet" href="/myblog-source/css/series-page.css"><div class="series-nav series-nav--single">  <a href="/myblog-source/2026/06/28/ai-compute-interconnect-part2-reliability-rethink/" class="series-nav__link">下一篇：重构可靠性 →</a></div><h2 id="参考资料">参考资料</h2><ol><li><a href="https://arxiv.org/abs/2204.02311">PaLM: Scaling Language Modeling with Pathways</a></li><li><a href="https://docs.nvidia.com/megatron-core/developer-guide/latest/user-guide/parallelism-guide.html">NVIDIA Megatron Core: Parallelism Strategies Guide</a></li><li><a href="https://arxiv.org/abs/2104.04473">Efficient Large-Scale Language Model Training on GPU Clusters Using Megatron-LM</a></li><li><a href="https://arxiv.org/abs/2101.03961">Switch Transformers: Scaling to Trillion Parameter Models with Simple and Efficient Sparsity</a></li><li><a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/usage/collectives.html">NVIDIA NCCL User Guide: Collective Operations</a></li><li><a href="https://docs.nvidia.com/megatron-core/developer-guide/latest/user-guide/features/context_parallel.html">NVIDIA Megatron Core: Context Parallelism</a></li><li><a href="https://ualinkconsortium.org/blog/ualink-200g-1-0-specification-overview-802/">UALink 200G 1.0 Specification Overview</a></li></ol><!-- 上篇：通信墙——为什么更多 GPU 不等于更高吞吐 -->]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/06/28/ai-compute-interconnect-part1-communication-wall/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/06/28/ai-compute-interconnect-part1-communication-wall/"/>
    <published>2026-06-28T14:39:30.000Z</published>
    <summary>从 MFU、并行策略、集合通信与网络拓扑出发，解释 AI 集群为什么会从计算密集型系统走向通信受限系统，以及为什么 400G、800G 链路并不自动等于更高的有效吞吐。</summary>
    <title>智算互联（上）：通信墙——为什么更多 GPU 不等于更高吞吐</title>
    <updated>2026-07-01T05:07:01.768Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Silicon &amp; Circuits" scheme="https://leecdiang.github.io/myblog-source/categories/Silicon-Circuits/"/>
    <category term="智算网络" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%99%BA%E7%AE%97%E7%BD%91%E7%BB%9C/"/>
    <category term="RoCE" scheme="https://leecdiang.github.io/myblog-source/tags/RoCE/"/>
    <category term="RDMA" scheme="https://leecdiang.github.io/myblog-source/tags/RDMA/"/>
    <category term="拥塞控制" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%8B%A5%E5%A1%9E%E6%8E%A7%E5%88%B6/"/>
    <category term="数据中心网络" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%BF%83%E7%BD%91%E7%BB%9C/"/>
    <category term="PFC" scheme="https://leecdiang.github.io/myblog-source/tags/PFC/"/>
    <category term="ECN" scheme="https://leecdiang.github.io/myblog-source/tags/ECN/"/>
    <category term="Ultra-Ethernet" scheme="https://leecdiang.github.io/myblog-source/tags/Ultra-Ethernet/"/>
    <category term="TTPoE" scheme="https://leecdiang.github.io/myblog-source/tags/TTPoE/"/>
    <category term="In-Network-Computing" scheme="https://leecdiang.github.io/myblog-source/tags/In-Network-Computing/"/>
    <content>
      <![CDATA[<blockquote><p><strong>Abstract - Part 2</strong><br>Modern AI fabrics are challenging the traditional assumption that high-performance RDMA must rely on a strictly lossless network. This article explores the limits of PFC-based Ethernet, distinguishes fabric loss from end-to-end reliability, and compares emerging approaches such as RoCE, TTPoE, and Ultra Ethernet to show how transport, congestion control, and recovery are being redistributed across endpoints, switches, and the control plane.</p></blockquote><div class="note info"><p>本文是「智算互联」系列中篇。<a href="/myblog-source/series/ai-interconnect/">查看系列目录 →</a><br><br>上篇讨论通信墙与流量指纹；本文讨论传输与 Fabric；下篇将讨论网卡芯片、SerDes、PCIe 与光互联。</p></div><hr><p>在传统数据中心里,丢一个包通常只意味着某条流稍后重传;在大规模同步训练里,丢包、乱序和排队抖动可能让整个 Collective 等待最慢的 Rank。因此,智算网络长期追求&quot;无损&quot;:尽可能不让交换机丢包,让远程直接内存访问(Remote Direct Memory Access, RDMA)维持低时延和高吞吐。</p><p>问题在于,<strong>无损不是免费的</strong>。</p><p>Priority-based Flow Control(PFC)可以在队列接近溢出时向上游发送 Pause,但暂停会传播,拥塞会扩散,互不相关的流可能被同一个优先级队列阻塞。在足够复杂的依赖关系下,还可能形成死锁。</p><p>下一代智算网络的变化不是放弃可靠性,而是换了一种实现方式:</p><blockquote><p><strong>从逐跳强制刹车,变为端到端的容损、恢复与协同调度。</strong></p></blockquote><hr><h2 id="一、为什么-AI-集群偏爱-RDMA">一、为什么 AI 集群偏爱 RDMA</h2><h3 id="1-传统网络栈的路径太长">1. 传统网络栈的路径太长</h3><p>普通 Socket 通信可能经历:</p><ol><li>应用缓冲区;</li><li>用户态与内核态切换;</li><li>内核 TCP/IP 协议栈;</li><li>内核网络缓冲区;</li><li>网卡 DMA;</li><li>接收端反向执行类似路径。</li></ol><p>现代内核、零拷贝接口和卸载技术已经能显著降低这些成本,但对微秒级、高吞吐的 GPU 集群而言,CPU 介入、内存复制和上下文切换仍可能成为负担。</p><p>RDMA 的目标是让网卡直接访问已经注册的内存区域,在较少 CPU 参与的情况下完成远端读、写或 Send/Recv。对于 GPU 集群,GPUDirect RDMA 进一步允许网卡与 GPU 显存建立更直接的数据路径,减少经主机内存中转的成本。</p><h3 id="2-RoCEv2-把-RDMA-放到可路由以太网上">2. RoCEv2 把 RDMA 放到可路由以太网上</h3><p>RDMA over Converged Ethernet version 2(RoCEv2)通常封装在 UDP/IP 之上,因此可以跨三层网络路由。它保留 RDMA 的队列对(Queue Pair, QP)、完成队列(Completion Queue, CQ)、内存注册和可靠连接等语义,同时利用成熟的 Ethernet 交换生态。</p><p>这使 RoCEv2 成为智算网络的重要路线:</p><ul><li>Ethernet 设备和运维体系成熟;</li><li>可以与 IP 路由和 Clos Fabric 结合;</li><li>服务器、网卡和交换机供应商众多;</li><li>与 MPI、NCCL、存储和云平台生态衔接。</li></ul><p>但传统 RoCE 的可靠连接实现对丢包和乱序较为敏感。为了避免频繁触发重传,工程部署常通过 PFC、ECN 与端到端拥塞控制,把 Fabric 尽量维持在低丢包状态。</p><h2 id="二、PFC、ECN-与-DCQCN-各自做什么">二、PFC、ECN 与 DCQCN 各自做什么</h2><p>三者经常被一起提及,但作用并不相同。</p><h3 id="1-PFC-逐跳、按优先级暂停">1. PFC:逐跳、按优先级暂停</h3><p>Priority-based Flow Control(PFC)由 IEEE 802.1Qbb 定义。与暂停整个端口的传统 Pause Frame 不同,PFC 可以暂停某个优先级类别。</p><p>当下游交换机某个优先级队列达到阈值时,它向上游发送 PFC Pause。上游停止发送该优先级流量,为下游释放缓冲区争取时间。</p><p>PFC 的优点非常直接:</p><ul><li>响应发生在相邻链路之间;</li><li>不必等待端到端控制环路;</li><li>能防止队列立即溢出;</li><li>对不擅长处理丢包的旧式 RoCE 端点友好。</li></ul><p>它的代价也来自同一个机制:<strong>上游被暂停后,数据不会消失,只会在更上游积压。</strong></p><h3 id="2-ECN-交换机标记拥塞-而不是立刻丢包">2. ECN:交换机标记拥塞,而不是立刻丢包</h3><p>Explicit Congestion Notification(ECN)允许交换机在队列达到标记阈值时,为报文设置拥塞标志。接收端把拥塞信息反馈给发送端,发送端再降低速率。</p><p>ECN 的核心价值是提前反馈:在队列真正溢出之前,让源端看到拥塞趋势。</p><p>但 ECN 是端到端闭环。反馈需要经过:</p><ul><li>报文抵达接收端;</li><li>接收端生成反馈;</li><li>反馈返回发送端;</li><li>发送端调整发送速率。</li></ul><p>在 400G、800G 链路上,一个往返时延内可以注入大量数据。若阈值和控制参数不合适,反馈到达前队列可能已经迅速增长。</p><h3 id="3-DCQCN-为-RoCEv2-设计的端到端速率控制">3. DCQCN:为 RoCEv2 设计的端到端速率控制</h3><p>Data Center Quantized Congestion Notification(DCQCN)由 Microsoft 等提出,用于大规模 RoCEv2 部署。交换机使用 ECN 标记,接收端向发送端发送 Congestion Notification Packet(CNP),发送端根据反馈调整速率。<a href="https://www.microsoft.com/en-us/research/publication/congestion-control-for-large-scale-rdma-deployments/">Congestion Control for Large-Scale RDMA Deployments</a></p><p>DCQCN 试图让大多数流在队列触发 PFC 之前主动减速,因此 PFC 更像最后一道保险,而不是日常调速器。</p><pre><code class="mermaid">sequenceDiagram    participant S as Sender RNIC    participant SW as Congested Switch    participant R as Receiver RNIC    S-&gt;&gt;SW: RoCE Data    SW-&gt;&gt;R: ECN-marked Data    R--&gt;&gt;S: CNP &#x2F; Congestion Feedback    S-&gt;&gt;S: Reduce sending rate    Note over SW,S: If queue rises too fast, PFC may pause upstream before end-to-end loop converges</code></pre><h3 id="4-三者作用在不同时间尺度">4. 三者作用在不同时间尺度</h3><table><thead><tr><th style="text-align:left">机制</th><th style="text-align:left">作用位置</th><th style="text-align:left">主要动作</th><th style="text-align:left">优势</th><th style="text-align:left">主要代价</th></tr></thead><tbody><tr><td style="text-align:left">PFC</td><td style="text-align:left">相邻链路</td><td style="text-align:left">暂停某优先级</td><td style="text-align:left">反应快,避免瞬时丢包</td><td style="text-align:left">Pause 传播、HoL Blocking、死锁风险</td></tr><tr><td style="text-align:left">ECN</td><td style="text-align:left">交换机到端点</td><td style="text-align:left">标记拥塞</td><td style="text-align:left">不必立刻丢包,可提前反馈</td><td style="text-align:left">依赖端到端控制环路</td></tr><tr><td style="text-align:left">DCQCN</td><td style="text-align:left">发送端</td><td style="text-align:left">调整发送速率</td><td style="text-align:left">改善吞吐、公平性和队列</td><td style="text-align:left">参数复杂,对突发流量反应有限</td></tr></tbody></table><p>现实中的 RoCE 网络往往同时使用三者。困难不在于某个机制有根本缺陷,而在于规模、速率和流量模式变化后,靠它们维持强无损的工程成本快速上升。</p><h2 id="三、PFC-的诅咒到底是什么">三、PFC 的诅咒到底是什么</h2><h3 id="1-Head-of-Line-Blocking-无辜流量一起停车">1. Head-of-Line Blocking:无辜流量一起停车</h3><p>队头阻塞(Head-of-Line Blocking, HoL Blocking)指的是:队列前部的报文被阻塞,后面的报文即使目的地和路径并不拥塞,也无法前进。</p><p>PFC 按优先级暂停,而不是按单条流暂停。如果多条流共享同一优先级和队列,一条热点流触发 Pause,其他流也可能被迫等待。</p><h3 id="2-Congestion-Spreading-拥塞从一个端口长成一棵树">2. Congestion Spreading:拥塞从一个端口长成一棵树</h3><p>假设交换机 A 的某个出口拥塞,它暂停上游交换机 B;B 的队列随之增长,又暂停更上游的 C。最终,最初局限在一个出口的拥塞沿反向路径传播,形成拥塞树(Congestion Tree)。</p><p>这会产生两个后果:</p><ul><li>原本不经过热点出口的流也可能受影响;</li><li>故障定位变得困难,因为看到 Pause 的设备未必是拥塞根源。</li></ul><h3 id="3-Deadlock-循环等待不是-PFC-的必然-但确实可能发生">3. Deadlock:循环等待不是 PFC 的必然,但确实可能发生</h3><p>如果多个队列和路径形成循环依赖,每个节点都在等待下游释放缓冲区,就可能发生 PFC Deadlock。</p><p>这里需要避免两个极端说法:</p><ul><li>说&quot;PFC 一定导致死锁&quot;是不准确的;</li><li>说&quot;只要配置正确就没有问题&quot;也低估了大规模网络的复杂度。</li></ul><p>死锁与拓扑、路由、虚拟通道、优先级映射、缓冲区和故障状态有关。网络越大、路径越动态、业务越混杂,证明所有依赖关系始终安全越困难。</p><p>Microsoft 的研究和后续 IRN 工作都把 Head-of-Line Blocking、Congestion Spreading 与偶发死锁列为 PFC 的核心问题。<a href="https://dl.acm.org/doi/10.1145/3230543.3230557">Revisiting Network Support for RDMA</a></p><h3 id="4-Buffer-Threshold-是一场精密预算">4. Buffer Threshold 是一场精密预算</h3><p>PFC 阈值不能随意设置。它必须为&quot;Pause Frame 发出后仍在路上的数据&quot;预留 Headroom,通常需要考虑:</p><ul><li>链路速率;</li><li>电缆与光纤传播时延;</li><li>交换机处理延迟;</li><li>对端响应时间;</li><li>最大帧长;</li><li>多个上游端口同时注入的情况。</li></ul><p>阈值过低,会频繁暂停并损失利用率;阈值过高,又可能来不及阻止溢出。端口从 100G 升到 400G、800G 后,同样的时间窗口内会飞入更多字节,Headroom 压力随之增加。</p><h2 id="四、先拆开-有损、可靠、有序-三个概念">四、先拆开&quot;有损、可靠、有序&quot;三个概念</h2><p>很多讨论把有损网络等同于不可靠传输,这是一个关键误区。</p><h3 id="1-Fabric-是否允许丢包">1. Fabric 是否允许丢包</h3><p>这是交换网络层面的问题:队列满时,交换机是否可以 Drop 或 Trim 报文。</p><h3 id="2-端到端传输是否可靠">2. 端到端传输是否可靠</h3><p>这是 Transport 的问题:如果报文丢失,端点是否检测、确认并重传,最终保证完整数据抵达。</p><h3 id="3-数据是否必须严格有序">3. 数据是否必须严格有序</h3><p>这是 Ordering 的问题:应用是否要求所有报文严格按发送顺序交付,还是允许报文乱序到达后直接放入最终内存位置。</p><p>三者可以形成不同组合:</p><table><thead><tr><th style="text-align:left">Fabric</th><th style="text-align:left">Transport</th><th style="text-align:left">Ordering</th><th style="text-align:left">含义</th></tr></thead><tbody><tr><td style="text-align:left">尽量无损</td><td style="text-align:left">可靠</td><td style="text-align:left">严格有序</td><td style="text-align:left">传统 RoCE RC 的典型部署思路</td></tr><tr><td style="text-align:left">允许丢包</td><td style="text-align:left">可靠</td><td style="text-align:left">严格有序</td><td style="text-align:left">端点承担重传,但可能需要重排缓存</td></tr><tr><td style="text-align:left">允许丢包</td><td style="text-align:left">可靠</td><td style="text-align:left">灵活有序</td><td style="text-align:left">可使用包级多路径和直接数据放置</td></tr><tr><td style="text-align:left">允许丢包</td><td style="text-align:left">不可靠</td><td style="text-align:left">不保证有序</td><td style="text-align:left">适合能自行容错的特殊应用,不适合普通 RDMA 语义</td></tr></tbody></table><p>下一代 AI Transport 的方向通常是第二或第三种:Fabric 可以在极端拥塞时丢包,但端点仍保证最终可靠交付。</p><h2 id="五、为什么选择性重传改变了问题">五、为什么选择性重传改变了问题</h2><p>传统可靠连接如果采用 Go-Back-N,一旦检测到一个报文丢失,可能从缺失位置开始重传之后的一串报文。大量已成功到达的数据也被重复发送,浪费带宽。</p><p>选择性确认与选择性重传(Selective Acknowledgment / Selective Retransmission)只恢复缺失部分,更适合高速、有损 Fabric。</p><p>这背后需要端点增加能力:</p><ul><li>更精确的序列号和丢包检测;</li><li>更复杂的 Outstanding Packet 状态;</li><li>每包直接数据放置;</li><li>乱序完成管理;</li><li>更细粒度的计时器与重传队列;</li><li>防止重复写入或状态失配。</li></ul><p>&quot;允许网络丢包&quot;不是省掉硬件,而是把部分复杂性从交换机缓冲和 PFC 运维转移到 RNIC Transport Engine。</p><h2 id="六、IRN-PFC-不是-RDMA-的物理定律">六、IRN:PFC 不是 RDMA 的物理定律</h2><p>SIGCOMM 2018 的 IRN(Improved RoCE NIC)研究提出一个重要观点:RoCE 对 PFC 的依赖,很大程度上来自当时 NIC 的设计,而不是 RDMA 本身的必然要求。</p><p>IRN 通过有限的接收窗口、选择性重传和更好的丢包处理,使 RoCE 可以在不依赖 PFC 的情况下运行。论文报告,在其典型网络场景中,IRN 无 PFC 方案相对传统 RoCE + PFC 获得 6% 到 83% 的性能提升,NIC 资源开销约为 3% 到 10%。这些数字属于论文特定实现与实验条件,不能直接外推到所有商用网络,但它证明了一个架构命题:</p><blockquote><p>&quot;RDMA 必须依靠全网强无损&quot;不是不可改变的规则。</p></blockquote><p><a href="https://dl.acm.org/doi/10.1145/3230543.3230557">Revisiting Network Support for RDMA</a></p><h2 id="七、TTPoE-垂直整合系统的极端样本">七、TTPoE:垂直整合系统的极端样本</h2><p>Tesla Transport Protocol over Ethernet(TTPoE)常被误写成 Tensor over Ethernet。Tesla 在 Hot Chips 2024 后公开了相关代码与说明。<a href="https://github.com/teslamotors/ttpoe">Tesla TTPoE GitHub Repository</a></p><p>Tesla 的官方 README 给出了几个关键设计取向:</p><ul><li>丢包和重放被视为可接受的默认情况,但完整传输仍得到保证;</li><li>初始部署用于 Dojo v1;</li><li>协议在硬件中执行;</li><li>数据链路不需要 CPU 或操作系统参与;</li><li>拥塞管理强调端点自治,而不是集中式控制。</li></ul><h3 id="1-TTPoE-的意义不在于-替代所有协议">1. TTPoE 的意义不在于&quot;替代所有协议&quot;</h3><p>TTPoE 的价值在于它展示了垂直整合的力量:当厂商同时控制加速器、网络接口、拓扑和软件栈时,可以放弃大量通用性,围绕特定系统目标设计更简单的 Transport。</p><p>但这也限制了它作为行业通用路线的代表性:</p><ul><li>公开实现和生态远小于 RoCE;</li><li>部署条件高度特定;</li><li>与通用 Verbs、libfabric 和多厂商交换生态的兼容性有限;</li><li>公开材料不足以完整评估大规模故障、隔离和安全语义。</li></ul><p>因此,TTPoE 更适合作为&quot;垂直整合系统如何重新定义可靠性&quot;的案例,而不是简单写成 RoCE 的继任者。</p><h2 id="八、Ultra-Ethernet-不是给-Ethernet-换一个名字">八、Ultra Ethernet:不是给 Ethernet 换一个名字</h2><p>Ultra Ethernet Consortium(UEC)在 2025 年发布 Ultra Ethernet Specification 1.0,目标是为 AI 与 HPC 的 Scale-out 网络提供开放、多厂商的完整通信架构。<a href="https://ultraethernet.org/ultra-ethernet-consortium-uec-launches-specification-1-0-transforming-ethernet-for-ai-and-hpc-at-scale/">Ultra Ethernet Specification 1.0 Announcement</a></p><p>Ultra Ethernet Transport(UET)不是在 RoCE 上加一个拥塞算法,而是在 Transport、API、交换机能力、安全与物理层之间重新协同。</p><h3 id="1-Packet-Spraying-从-每条流选一条路-到-每个包利用多条路">1. Packet Spraying:从&quot;每条流选一条路&quot;到&quot;每个包利用多条路&quot;</h3><p>传统 ECMP 通常对五元组做哈希,同一条流固定走一条路径。这能避免乱序,但当少数大流碰巧哈希到同一路径时,其他路径可能空闲,热点路径却拥塞。</p><p>UET 支持多路径 Packet Spraying,让同一传输的不同包使用多条可用路径,并结合端点与网络的实时拥塞信息进行更细粒度负载均衡。<a href="https://ultraethernet.org/uec-progresses-towards-v1-0-set-of-specifications/">UEC Progresses Towards v1.0 Set of Specifications</a></p><p>它的收益是提高路径利用率、降低 Flow Collision 和尾延迟;代价是接收端必须面对乱序。</p><h3 id="2-Flexible-Ordering-不为应用不需要的顺序付费">2. Flexible Ordering:不为应用不需要的顺序付费</h3><p>有些应用只关心整个消息何时完成,并不要求每个包按顺序交给软件。UET 允许应用通过 libfabric 表达不同的 Ordering 需求。</p><p>如果报文携带目标位置等元数据,接收端可以将乱序到达的包直接放入最终内存位置,而不是先写入巨大 Reorder Buffer,再按顺序复制。</p><h3 id="3-Sender-based-CC-与-Receiver-Credit">3. Sender-based CC 与 Receiver Credit</h3><p>UEC 公开资料描述的基础路径是可扩展的发送端拥塞控制,并可选用接收端基于 Credit 的控制来缓解 All-to-All Incast。</p><p>这体现了两类拥塞的差异:</p><ul><li>Fabric 中间链路拥塞,适合由路径遥测和发送端调速处理;</li><li>接收端口或最终 ToR 出口 Incast,接收端更清楚自己能够接收多少数据。</li></ul><h3 id="4-Packet-Trimming-丢-Payload-但保留快速反馈">4. Packet Trimming:丢 Payload,但保留快速反馈</h3><p>在极端拥塞时,交换机可以截断报文 Payload,只把 Header 和拥塞信息送到接收端。这样接收端更快知道哪个包受损,而不必等待超时。</p><p>Packet Trimming 保留 Header 丢弃 Payload,把&quot;无声 Drop&quot;变成快速、明确的控制信号。</p><h3 id="5-Security-是一等公民">5. Security 是一等公民</h3><p>UEC 把安全与 Transport 协同设计,而不是把加密视为后挂功能。对多租户 AI 云而言,这一点很重要:训练数据、模型参数、梯度和 KV Cache 都可能跨越主机和交换网络边界。</p><p>不过,规范发布不等于所有特性已经在多厂商设备中成熟部署。UET 的真实价值最终要通过:</p><ul><li>RNIC 实现;</li><li>Switch 支持;</li><li>libfabric 与 Collective Library 集成;</li><li>跨厂商互操作;</li><li>大规模故障和性能测试;</li><li>与现有 RoCE 网络的迁移成本。</li></ul><h2 id="九、RoCE、TTPoE-与-UET-应该怎样比较">九、RoCE、TTPoE 与 UET 应该怎样比较</h2><table><thead><tr><th style="text-align:left">维度</th><th style="text-align:left">RoCEv2</th><th style="text-align:left">TTPoE</th><th style="text-align:left">UET</th></tr></thead><tbody><tr><td style="text-align:left">主要定位</td><td style="text-align:left">通用 Ethernet RDMA</td><td style="text-align:left">Tesla 垂直整合 Fabric</td><td style="text-align:left">开放 AI/HPC Scale-out Transport</td></tr><tr><td style="text-align:left">生态成熟度</td><td style="text-align:left">高</td><td style="text-align:left">有限、特定部署</td><td style="text-align:left">规范与产品生态建设中</td></tr><tr><td style="text-align:left">典型可靠性思路</td><td style="text-align:left">强无损部署 + 端到端 RC</td><td style="text-align:left">允许丢包并由端点保证传输</td><td style="text-align:left">容损、选择性恢复、灵活 Ordering</td></tr><tr><td style="text-align:left">多路径</td><td style="text-align:left">常依赖 Flow-level ECMP</td><td style="text-align:left">特定系统设计</td><td style="text-align:left">Packet Spraying 是核心能力之一</td></tr><tr><td style="text-align:left">乱序处理</td><td style="text-align:left">传统 RC 较敏感</td><td style="text-align:left">自定义</td><td style="text-align:left">Direct Data Placement + Flexible Ordering</td></tr><tr><td style="text-align:left">拥塞控制</td><td style="text-align:left">PFC、ECN、DCQCN 等</td><td style="text-align:left">分布式端点自治</td><td style="text-align:left">Sender CC、可选 Receiver Credit、Telemetry</td></tr><tr><td style="text-align:left">通用性</td><td style="text-align:left">面向广泛服务器与存储生态</td><td style="text-align:left">为特定系统简化</td><td style="text-align:left">追求多厂商互操作</td></tr></tbody></table><p>这张表不是胜负表。RoCE 的部署基础和软件生态是实实在在的优势;TTPoE 展示了定制系统可以多激进;UET 则试图在开放性与 AI 原生能力之间找到新的平衡。</p><h2 id="十、端点、交换机与-Fabric-Controller-的闭环">十、端点、交换机与 Fabric Controller 的闭环</h2><p>下一代智算网络不能被写成&quot;NIC 进化成 Switch,再进化成 Controller&quot;。三者是并列的责任域。</p><h3 id="1-Endpoint-RNIC">1. Endpoint / RNIC</h3><p>端点最适合承担:</p><ul><li>Pacing 与发送速率控制;</li><li>Packet Spraying;</li><li>选择性重传;</li><li>乱序接收与直接数据放置;</li><li>QP、CQ、内存注册和 DMA;</li><li>应用语义与 Transport 的映射;</li><li>多租户隔离和安全上下文。</li></ul><h3 id="2-Switch">2. Switch</h3><p>交换机最适合承担:</p><ul><li>高速转发;</li><li>队列管理与调度;</li><li>ECN 标记;</li><li>遥测与路径状态;</li><li>Packet Trimming;</li><li>Congestion Isolation;</li><li>特定 Collective 的在网卸载。</li></ul><h3 id="3-Fabric-Controller">3. Fabric Controller</h3><p>控制平面最适合处理较慢时间尺度的全局问题:</p><ul><li>拓扑与路径规划;</li><li>作业放置;</li><li>Rail 与租户分配;</li><li>故障隔离;</li><li>流量工程;</li><li>策略下发;</li><li>网络遥测分析。</li></ul><pre><code class="mermaid">flowchart TB    A[Training Framework &#x2F; NCCL &#x2F; MPI] --&gt; B[Endpoint RNIC]    B --&gt;|Data, pacing, retransmission| C[Switch Fabric]    C --&gt;|ECN, telemetry, trim| B    D[Fabric Controller] --&gt;|Path and policy| B    D --&gt;|Routing and queue policy| C    C --&gt;|Telemetry| D    B --&gt;|Endpoint statistics| D</code></pre><p>端点看到连接状态和应用语义,交换机看到局部队列和链路状态,Controller 看到全局拓扑。任何一方都缺少完整信息,因此真正有效的架构往往是多时间尺度协同,而不是让某一层&quot;包办一切&quot;。</p><h2 id="十一、In-Network-Computing-交换机可以计算什么">十一、In-Network Computing:交换机可以计算什么</h2><h3 id="1-SHARP-的正确位置">1. SHARP 的正确位置</h3><p>NVIDIA Scalable Hierarchical Aggregation and Reduction Protocol(SHARP)把 AllReduce、Reduce 和 Broadcast 等 Collective 的部分操作卸载到交换 Fabric 中。交换 ASIC 在数据经过时完成归约,减少端点之间重复传输的数据量。<a href="https://developer.nvidia.com/blog/advancing-performance-with-nvidia-sharp-in-network-computing/">NVIDIA SHARP In-Network Computing</a></p><p>SHARP 是一种在网集合通信能力,但不能据此推断交换芯片内部一定采用 CGRA。公开资料说明它集成于交换 ASIC,却没有把其底层实现定义为通用粗粒度可重构阵列。</p><h3 id="2-在网计算的优势">2. 在网计算的优势</h3><p>适合放进网络的数据面任务通常具有以下特征:</p><ul><li>操作简单;</li><li>状态有限;</li><li>数据流式经过;</li><li>需要线速;</li><li>能显著减少后续流量;</li><li>对确定性和延迟敏感。</li></ul><p>例如:</p><ul><li>Sum、Min、Max 等归约;</li><li>Telemetry;</li><li>Sketch 和计数;</li><li>报文裁剪与拥塞信号;</li><li>某些安全、压缩或校验操作。</li></ul><h3 id="3-为什么不能把整个训练算子塞进交换机">3. 为什么不能把整个训练算子塞进交换机</h3><p>交换芯片受到严格约束:</p><ul><li>每包处理时间固定;</li><li>片上 SRAM 有限;</li><li>外部 HBM 会增加成本和功耗;</li><li>浮点精度、状态一致性和多租户隔离复杂;</li><li>可编程性越强,PPA 与验证代价越高;</li><li>交换机故障会影响整个 Fabric,不能随意运行复杂代码。</li></ul><p>因此,In-Network Computing 更像是<strong>在正确的位置执行少量高价值操作</strong>,而不是把交换机变成另一种 GPU。</p><h2 id="十二、从-FPGA-到可编程微引擎-不要把-可编程-混成一个词">十二、从 FPGA 到可编程微引擎:不要把&quot;可编程&quot;混成一个词</h2><p>网络数据面可以采用多种实现:</p><table><thead><tr><th style="text-align:left">架构</th><th style="text-align:left">优点</th><th style="text-align:left">局限</th><th style="text-align:left">适合任务</th></tr></thead><tbody><tr><td style="text-align:left">固定功能 ASIC</td><td style="text-align:left">最高吞吐和能效、确定性强</td><td style="text-align:left">灵活性低</td><td style="text-align:left">稳定协议、核心转发路径</td></tr><tr><td style="text-align:left">Match-Action Pipeline</td><td style="text-align:left">线速、规则可编程</td><td style="text-align:left">算术与状态能力有限</td><td style="text-align:left">Parser、分类、转发、遥测</td></tr><tr><td style="text-align:left">多核网络处理器</td><td style="text-align:left">软件灵活、生态友好</td><td style="text-align:left">单核无法承担全线速数据面</td><td style="text-align:left">控制面、异常路径、复杂协议</td></tr><tr><td style="text-align:left">专用微引擎</td><td style="text-align:left">吞吐与灵活性折中</td><td style="text-align:left">编程模型专有</td><td style="text-align:left">隧道、协议处理、队列任务</td></tr><tr><td style="text-align:left">FPGA</td><td style="text-align:left">开发快、位级可重构</td><td style="text-align:left">面积、功耗和频率劣于 ASIC</td><td style="text-align:left">原型、小批量、快速变化功能</td></tr><tr><td style="text-align:left">CGRA</td><td style="text-align:left">算术密集数据流效率较高</td><td style="text-align:left">工具链和映射复杂</td><td style="text-align:left">规则计算图、特定流式加速</td></tr></tbody></table><p>“FPGA 能效低”&quot;CGRA 一定更好&quot;都过于绝对。真正的问题是:任务是否需要位级灵活性、控制流是否复杂、状态是否规则、吞吐是否可并行,以及产品量是否足以摊薄 ASIC 成本。</p><h2 id="十三、怎样判断一种-无-PFC-方案是否真的更好">十三、怎样判断一种&quot;无 PFC&quot;方案是否真的更好</h2><p>厂商很容易宣称支持有损网络、包喷洒、乱序接收和选择性重传,但工程评价不能停留在功能列表。</p><p>至少需要测试:</p><h3 id="1-性能">1. 性能</h3><ul><li>AllReduce Bus Bandwidth;</li><li>All-to-All Job Completion Time;</li><li>64B 小包 Mpps;</li><li>大消息 Goodput;</li><li>P50、P99、P999 延迟;</li><li>多跳与跨机架性能。</li></ul><h3 id="2-拥塞">2. 拥塞</h3><ul><li>Many-to-One Incast;</li><li>多个 Collective 叠加;</li><li>Elephant 与 Mice Flow 混合;</li><li>链路故障后的路径收敛;</li><li>Packet Spraying 下的乱序程度;</li><li>Credit 耗尽与接收端过载。</li></ul><h3 id="3-可靠性">3. 可靠性</h3><ul><li>单包和突发丢包;</li><li>Reorder;</li><li>Bit Error 与 FEC 后残余错误;</li><li>QP 数量扩大后的状态压力;</li><li>网卡重置、进程退出和节点故障;</li><li>重传风暴与超时恢复。</li></ul><h3 id="4-生态">4. 生态</h3><ul><li>NCCL、MPI、libfabric、Verbs 支持;</li><li>GPU Direct 与国产加速器适配;</li><li>Switch 多厂商互操作;</li><li>遥测、调试与故障定位;</li><li>与现有 RoCE Fabric 的迁移方式。</li></ul><h2 id="结语-无损没有消失-可靠性的位置正在改变">结语:无损没有消失,可靠性的位置正在改变</h2><p>PFC 解决了一个真实问题:当端点不擅长处理丢包时,用逐跳流控保护有限的交换机缓冲。它帮助 RoCE 走进大规模数据中心,也不会在短期内突然消失。</p><p>但随着链路进入 400G、800G,AI 流量出现更强同步、更剧烈 Incast 和更多多路径需求,单纯依靠&quot;不要让任何包丢失&quot;来维持性能,正在变得越来越昂贵。</p><p>IRN 证明了 RDMA 可以重新设计丢包恢复;TTPoE 展示了垂直整合系统可以让端点自治;UET 则试图把 Packet Spraying、Flexible Ordering、Selective Retransmission、Telemetry 与 Security 组合成开放的 Scale-out 架构。</p><p>真正的变化不是从可靠走向不可靠:</p><blockquote><p><strong>从网络内部用 Pause 掩盖拥塞,转向由端点、交换机和控制平面共同感知、承受并恢复拥塞。</strong></p></blockquote><p>下一篇将回到硅片:一张 400G 智算网卡内部到底有哪些模块?112G / 224G SerDes 为什么困难?PCIe、DMA、NoC、RDMA Engine 和可编程微引擎如何维持线速?沐创从密码安全芯片走向 N20 与 N30,又选择了怎样的技术路线?</p><link rel="stylesheet" href="/myblog-source/css/series-page.css"><div class="series-nav series-nav--both">  <a href="/myblog-source/2026/06/28/ai-compute-interconnect-part1-communication-wall/" class="series-nav__link">← 上一篇：通信墙</a>  <a href="/myblog-source/2026/06/28/ai-compute-interconnect-part3-smart-nic-chip/" class="series-nav__link">下一篇：智算网卡芯片 →</a></div><h2 id="参考资料">参考资料</h2><ol><li><a href="https://www.microsoft.com/en-us/research/publication/congestion-control-for-large-scale-rdma-deployments/">Microsoft Research: Congestion Control for Large-Scale RDMA Deployments</a></li><li><a href="https://dl.acm.org/doi/10.1145/3230543.3230557">Revisiting Network Support for RDMA, SIGCOMM 2018</a></li><li><a href="https://github.com/teslamotors/ttpoe">Tesla Transport Protocol over Ethernet, Official GitHub Repository</a></li><li><a href="https://ultraethernet.org/ultra-ethernet-consortium-uec-launches-specification-1-0-transforming-ethernet-for-ai-and-hpc-at-scale/">Ultra Ethernet Consortium: Specification 1.0 Announcement</a></li><li><a href="https://ultraethernet.org/uec-progresses-towards-v1-0-set-of-specifications/">UEC Progresses Towards v1.0 Set of Specifications</a></li><li><a href="https://ultraethernet.org/wp-content/uploads/sites/20/2025/06/UE-Specification-6.11.25.pdf">Ultra Ethernet Specification 1.0</a></li><li><a href="https://developer.nvidia.com/blog/advancing-performance-with-nvidia-sharp-in-network-computing/">NVIDIA SHARP In-Network Computing</a></li><li><a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/">NVIDIA NCCL User Guide</a></li></ol><!-- 中篇:重构可靠性--从强制无损到端网协同 -->]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/06/28/ai-compute-interconnect-part2-reliability-rethink/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/06/28/ai-compute-interconnect-part2-reliability-rethink/"/>
    <published>2026-06-28T14:39:00.000Z</published>
    <summary>从 RoCE、PFC、ECN 与 DCQCN 出发,拆解无损以太网的收益和代价,并比较 IRN、TTPoE 与 Ultra Ethernet 如何把可靠性、拥塞控制和多路径能力重新分配给端点、交换机与控制平面。</summary>
    <title>智算互联(中):重构可靠性--从强制无损到端网协同</title>
    <updated>2026-07-01T05:07:06.443Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Silicon &amp; Circuits" scheme="https://leecdiang.github.io/myblog-source/categories/Silicon-Circuits/"/>
    <category term="RDMA" scheme="https://leecdiang.github.io/myblog-source/tags/RDMA/"/>
    <category term="智能网卡" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%99%BA%E8%83%BD%E7%BD%91%E5%8D%A1/"/>
    <category term="SmartNIC" scheme="https://leecdiang.github.io/myblog-source/tags/SmartNIC/"/>
    <category term="SerDes" scheme="https://leecdiang.github.io/myblog-source/tags/SerDes/"/>
    <category term="芯片设计" scheme="https://leecdiang.github.io/myblog-source/tags/%E8%8A%AF%E7%89%87%E8%AE%BE%E8%AE%A1/"/>
    <category term="沐创" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%B2%90%E5%88%9B/"/>
    <category term="国产芯片" scheme="https://leecdiang.github.io/myblog-source/tags/%E5%9B%BD%E4%BA%A7%E8%8A%AF%E7%89%87/"/>
    <category term="可编程网络" scheme="https://leecdiang.github.io/myblog-source/tags/%E5%8F%AF%E7%BC%96%E7%A8%8B%E7%BD%91%E7%BB%9C/"/>
    <category term="PCIe" scheme="https://leecdiang.github.io/myblog-source/tags/PCIe/"/>
    <category term="Network-on-Chip" scheme="https://leecdiang.github.io/myblog-source/tags/Network-on-Chip/"/>
    <category term="CPO" scheme="https://leecdiang.github.io/myblog-source/tags/CPO/"/>
    <content>
      <![CDATA[<blockquote><p><strong>Abstract - Part 3</strong><br>A high-performance AI network interface card is far more than a fast Ethernet controller. This article traces the complete data path from PCIe, DMA, and RDMA transport to packet processing, on-chip memory, SerDes, FEC, firmware, and optical interconnects, revealing the architectural trade-offs behind building network silicon capable of sustaining hundreds of gigabits per second.</p></blockquote><hr><div class="note info"><p>本文是「智算互联」系列下篇。<a href="/myblog-source/series/ai-interconnect/">查看系列目录 →</a></p></div><p>前两篇分别讨论了通信墙与可靠性范式的变化。但无论是 RoCE、包级多路径、选择性重传，还是端网协同，最终都必须落到芯片、板卡和软件栈上。</p><p>“支持 400G”“支持 RoCEv2”“支持 GPUDirect RDMA”只是产品规格的最外层描述。真正决定一张智算网卡能否持续线速运行的，是一条横跨主机接口、地址转换、DMA、队列状态、传输协议、片上存储、以太网 PHY、驱动和 Collective 通信库的完整数据路径。</p><p>这一篇不针对某一家厂商做产品拆解，而是回答三个更基础的问题：</p><ol><li>一颗高性能 RNIC / SmartNIC 内部到底有什么；</li><li>PCIe、DMA、片上 NoC、112G / 224G SerDes 为什么会共同决定最终吞吐；</li><li>一张“端口很快”的网卡，怎样才能真正成为 AI 集群中的高效互联端点。</li></ol><div class="note warning"><p>本文中的芯片框图均为基于公开标准与通用工程原理绘制的<strong>典型架构示意</strong>，不代表任何特定厂商产品的实际内部实现。</p><p>文中涉及具体产品时，只用于说明公开可见的产业路线；厂商规格、工程推导与第三方实测会被明确区分。</p></div><h2 id="一、从系统需求倒推一颗智算网卡">一、从系统需求倒推一颗智算网卡</h2><h3 id="1-AI-网卡不是“更快的以太网卡”">1. AI 网卡不是“更快的以太网卡”</h3><p>传统网卡负责稳定收发报文，把部分协议处理从 CPU 卸载到硬件。智算网卡面对的约束更苛刻：</p><ul><li>GPU 或加速器持续产生大规模 DMA 流量；</li><li>集合通信要求多个 Rank 在相近时间到达同步点；</li><li>Tensor Parallel 和 Expert Parallel 对尾延迟高度敏感；</li><li>一个丢包、慢流或异常队列可能拖慢整次 Collective；</li><li>数万甚至更多 Queue Pair 带来庞大的连接状态；</li><li>多租户环境还要求虚拟化、隔离与安全；</li><li>驱动、Firmware、RDMA Core 和通信库必须共同工作。</li></ul><p>因此，智算网卡优化的目标不是峰值吞吐，而是：</p><blockquote><p>在有限的 PCIe、片上 SRAM、封装功耗和网络带宽下，让尽可能多的数据按时抵达正确的 GPU 内存，并把拥塞、丢包与软件开销对训练步骤的影响降到最低。</p></blockquote><h3 id="2-网卡同时包含三个平面">2. 网卡同时包含三个平面</h3><p>可以把一颗高性能网卡拆成三个相互依赖的平面：</p><table><thead><tr><th style="text-align:left">平面</th><th style="text-align:left">主要职责</th><th style="text-align:left">典型模块</th></tr></thead><tbody><tr><td style="text-align:left">数据平面</td><td style="text-align:left">线速搬运和处理报文</td><td style="text-align:left">DMA、Parser、RDMA Engine、Scheduler、MAC、FEC</td></tr><tr><td style="text-align:left">控制平面</td><td style="text-align:left">初始化、配置与异常处理</td><td style="text-align:left">嵌入式 CPU、Firmware、管理队列、Telemetry</td></tr><tr><td style="text-align:left">管理与安全平面</td><td style="text-align:left">生命周期、隔离和可信性</td><td style="text-align:left">Secure Boot、密钥、SR-IOV、设备管理、升级</td></tr></tbody></table><p>最热的数据路径必须尽量硬化；复杂但低频的控制逻辑适合交给嵌入式处理器；需要随协议演进的部分，则可能使用可编程微引擎或受限的数据面指令集。</p><h3 id="3-典型数据路径">3. 典型数据路径</h3><pre><code class="mermaid">flowchart LR    A[Host CPU &#x2F; GPU Memory] --&gt; B[PCIe Controller&lt;br&#x2F;&gt;ATS &#x2F; PASID &#x2F; SR-IOV]    B --&gt; C[DMA + Address Translation]    C --&gt; D[Queue &#x2F; RDMA Transport Engine]    D --&gt; E[Packet Processing&lt;br&#x2F;&gt;Parser &#x2F; Scheduler &#x2F; Security]    E --&gt; F[MAC &#x2F; PCS &#x2F; FEC]    F --&gt; G[SerDes &#x2F; PMA]    G --&gt; H[DAC &#x2F; AEC &#x2F; Optical Module]    I[Embedded CPU &#x2F; Firmware] -.control.-&gt; C    I -.control.-&gt; D    I -.control.-&gt; E    J[On-chip SRAM &#x2F; NoC] --- C    J --- D    J --- E</code></pre><p>这张图画得像顺序流水线，但真实芯片的数据路径不是直线。描述符、队列上下文、重传状态、地址映射与完成事件会在多个模块之间反复交互；任何一处带宽不足或状态访问失衡，都可能让 400G 端口无法转化成 400G 有效吞吐。</p><h2 id="二、PCIe：网络端口背后的主机侧天花板">二、PCIe：网络端口背后的主机侧天花板</h2><h3 id="1-PCIe-是主数据通道，不是管理接口">1. PCIe 是主数据通道，不是管理接口</h3><p>PCIe Controller 通常负责：</p><ul><li>Link Training 与链路状态管理；</li><li>Transaction Layer Packet（TLP）收发；</li><li>BAR 与配置空间；</li><li>MSI-X 中断；</li><li>SR-IOV PF / VF；</li><li>Advanced Error Reporting；</li><li>ATS、PASID 等地址与进程隔离能力；</li><li>与 DMA Engine 交互。</li></ul><p>当网卡连接 GPU 或主机内存时，大部分业务数据都要经过 PCIe。端口带宽能否被利用，不只取决于 PCIe 的标称速率，还取决于 Max Payload Size、Read Request、Completion、NUMA 路径、Root Complex、PCIe Switch 和内存控制器。</p><h3 id="2-PCIe-4-0-x16-与-200G-级网卡">2. PCIe 4.0 x16 与 200G 级网卡</h3><p>PCIe 4.0 每 Lane 为 16 GT/s，采用 128b/130b 编码。x16 单方向编码后的原始有效带宽约为 31.5 GB/s，即约 252 Gbit/s。</p><p>对于网络端总带宽为 200 Gbit/s 的网卡，PCIe 4.0 x16 在数值上能够覆盖单方向线速，但实际余量还要扣除：</p><ul><li>TLP 与 Data Link Layer 开销；</li><li>DMA 地址不连续；</li><li>Doorbell、CQE 和元数据访问；</li><li>小包造成的事务效率下降；</li><li>IOMMU 与地址转换；</li><li>多设备共享 Root Complex；</li><li>Host Memory 或 GPU Memory 的实际服务能力。</li></ul><p>因此，“PCIe 带宽大于网络端口带宽”只是必要条件，不是线速保证。</p><h3 id="3-PCIe-5-0-x16-与-400G-网卡">3. PCIe 5.0 x16 与 400G 网卡</h3><p>PCIe 5.0 将速率提高到 32 GT/s。PCI-SIG 给出的 x16 单方向编码后原始有效带宽约为 504 Gbit/s。<a href="https://pcisig.com/what-bit-rates-does-pcie-50-specification-support-and-how-does-it-compare-prior-pcie-generations">PCI-SIG：PCIe 5.0 带宽</a></p><p>这使 PCIe 5.0 x16 成为 400G 网卡常见的主机接口组合。但 504 Gbit/s 与 400 Gbit/s 之间的差额不能直接视为完整设计余量，因为网络报文、PCIe 事务、地址转换和完成队列都会产生额外流量。</p><p>PCIe 与以太网都是全双工链路，分析时也必须分别考察：</p><ul><li>Host-to-Network；</li><li>Network-to-Host；</li><li>双向同时满载；</li><li>RDMA Read 与 RDMA Write 的非对称事务行为。</li></ul><h3 id="4-800G-为什么会再次逼近接口极限">4. 800G 为什么会再次逼近接口极限</h3><p>800G 网络端已经超过 PCIe 5.0 x16 的单方向原始有效能力。下一代高端网卡可能采用：</p><ul><li>PCIe 6.0 x16；</li><li>多个 PCIe Endpoint；</li><li>多 Host 或多 Accelerator 直连；</li><li>板载 PCIe Switch；</li><li>更紧密的 Scale-up 或芯粒级接口。</li></ul><p>PCIe 6.0 采用 PAM4、FLIT、轻量 FEC 和 CRC，在 64 GT/s 下进一步翻倍带宽。<a href="https://pcisig.com/pci-express-6.0-specification">PCI-SIG：PCI Express 6.0</a></p><p>但接口升级会把困难从“带宽不够”转移到信号完整性、FEC、延迟、封装和系统验证上。</p><h2 id="三、DMA-与地址转换：零拷贝并不等于零工作">三、DMA 与地址转换：零拷贝并不等于零工作</h2><h3 id="1-RDMA-的数据仍然必须移动">1. RDMA 的数据仍然必须移动</h3><p>RDMA 所谓“零拷贝”，主要指减少 CPU 介入和中间缓冲区复制。网卡仍然要完成：</p><ul><li>读取 Work Queue Entry（WQE）；</li><li>查询 Memory Region；</li><li>校验 Local Key / Remote Key；</li><li>完成虚拟地址到物理地址转换；</li><li>发起 PCIe Read / Write；</li><li>处理 Scatter-Gather List；</li><li>更新 Completion Queue Entry（CQE）；</li><li>维护 Doorbell 与 Producer / Consumer Index。</li></ul><p>这些动作不会出现在“400G”三个字符中，却直接决定小消息延迟和大规模队列下的持续吞吐。</p><h3 id="2-地址转换可能成为隐藏瓶颈">2. 地址转换可能成为隐藏瓶颈</h3><p>在虚拟化或 GPU Direct 场景中，网卡需要理解进程、虚拟机与设备地址空间。常见机制包括：</p><ul><li>IOMMU；</li><li>Address Translation Services（ATS）；</li><li>Process Address Space ID（PASID）；</li><li>Page Request Interface（PRI）；</li><li>网卡内部 Translation Cache。</li></ul><p>如果 Translation Cache 命中率过低，或页粒度与访问模式不匹配，DMA 会被地址转换和 PCIe 往返拖慢。</p><h3 id="3-大规模队列意味着上下文分层存储">3. 大规模队列意味着上下文分层存储</h3><p>当 QP、CQ、Memory Region 和 Flow 数量达到数万甚至更多时，所有状态不可能同时驻留在最快的片上 SRAM。芯片通常需要建立类似缓存层级：</p><pre><code class="mermaid">flowchart LR    A[Hot Context&lt;br&#x2F;&gt;On-chip SRAM] --&gt; B[Warm Context&lt;br&#x2F;&gt;External Memory &#x2F; On-board DRAM]    B --&gt; C[Cold Context&lt;br&#x2F;&gt;Host Memory]    D[Queue &#x2F; Transport Engine] --&gt; A    A -.miss.-&gt; B    B -.miss.-&gt; C</code></pre><p>真正困难的不是“能否支持 100 万 QP”，而是：在目标业务的活跃集规模下，Context Cache Miss 是否会把尾延迟和 PCIe 流量推高。</p><h2 id="四、RDMA-Transport-Engine：最复杂的数字状态机之一">四、RDMA Transport Engine：最复杂的数字状态机之一</h2><h3 id="1-可靠连接需要维护什么">1. 可靠连接需要维护什么</h3><p>以 Reliable Connection（RC）为例，Transport Engine 需要维护：</p><ul><li>Packet Sequence Number；</li><li>ACK / NAK；</li><li>Retransmission Timer；</li><li>RDMA Read / Write / Send / Atomic 语义；</li><li>Outstanding Request；</li><li>QP 状态；</li><li>Completion Ordering；</li><li>Congestion Notification；</li><li>Error Recovery。</li></ul><p>它既像网络协议处理器，又像远程内存访问控制器。错误不仅可能造成丢包，还可能破坏内存语义、完成顺序和应用可见状态。</p><h3 id="2-包级多路径会扩大状态空间">2. 包级多路径会扩大状态空间</h3><p>如果网卡支持 Packet Spraying、乱序接收与选择性重传，通常还需要：</p><ul><li>每包级路径选择；</li><li>Out-of-order Tracking；</li><li>Selective ACK / NACK；</li><li>直接数据放置；</li><li>Duplicate Suppression；</li><li>更细粒度的重传 Buffer；</li><li>更复杂的完成条件判断。</li></ul><p>这些能力可以提升多路径利用率，并减少单个丢包导致的大范围重传，但代价是更多 SRAM、更复杂的状态机以及更大的验证空间。</p><h3 id="3-协议灵活性首先是验证问题">3. 协议灵活性首先是验证问题</h3><p>网络芯片中的新 Transport 机制并不只需要“写出 RTL”。它还必须覆盖：</p><ul><li>包重复、乱序与丢失同时发生；</li><li>ACK 或 Credit 本身丢失；</li><li>QP Reset 与重传并发；</li><li>链路故障与路径切换；</li><li>多租户资源耗尽；</li><li>Firmware 升级与状态迁移；</li><li>交换机行为不完全符合预期。</li></ul><p>协议越灵活，Corner Case 越多。很多纸面上合理的算法，真正困难的部分是能否在硅片上以有限状态、有限 Buffer 和可验证的时序实现。</p><h2 id="五、Packet-Processing：固定功能与可编程性的分界">五、Packet Processing：固定功能与可编程性的分界</h2><h3 id="1-数据面通常要处理什么">1. 数据面通常要处理什么</h3><p>报文处理模块可能包含：</p><ul><li>Parser；</li><li>Flow Classification；</li><li>RSS / Hash；</li><li>ACL 与五元组过滤；</li><li>VXLAN、Geneve 等隧道；</li><li>Checksum、TSO / LRO；</li><li>Queue Selection；</li><li>QoS 与 Scheduler；</li><li>Telemetry；</li><li>IPsec / MACsec；</li><li>厂商扩展 Header 与拥塞控制。</li></ul><p>最常用、最稳定、对延迟敏感的逻辑适合硬化；变化快或客户差异大的部分，则更适合放进微引擎或可编程处理器。</p><h3 id="2-固定功能-ASIC-的优势与边界">2. 固定功能 ASIC 的优势与边界</h3><p>固定功能逻辑通常具有最高的能效和最确定的延迟，适合：</p><ul><li>Ethernet MAC / PCS；</li><li>CRC / Checksum；</li><li>常见 Parser；</li><li>DMA Fast Path；</li><li>成熟 RDMA 操作；</li><li>Queue Scheduler；</li><li>AES、SM4 等密码流水线。</li></ul><p>缺点是协议一旦变化，硬件修改往往需要重新流片。</p><h3 id="3-嵌入式-CPU-适合控制面与慢路径">3. 嵌入式 CPU 适合控制面与慢路径</h3><p>RISC-V、Arm 或其他嵌入式核常用于：</p><ul><li>初始化硬件；</li><li>管理 QP 与异常；</li><li>运行 Firmware；</li><li>处理 Control Packet；</li><li>更新 Table；</li><li>设备管理与 Telemetry；</li><li>实现低频但复杂的协议逻辑。</li></ul><p>它们的价值不在于逐包处理全部 400G 流量。以 64B 最小包计算，400G Ethernet 的包率可达到数亿 PPS，通用核无法承担所有热路径。</p><h3 id="4-专用微引擎填补中间地带">4. 专用微引擎填补中间地带</h3><p>专用微引擎常具有：</p><ul><li>多线程隐藏访存延迟；</li><li>受限但高效的指令集；</li><li>本地 Scratchpad；</li><li>硬件队列；</li><li>与 Parser、DMA、Crypto 紧耦合。</li></ul><p>它适合处理可变协议、隧道、客户定制逻辑、Telemetry 和拥塞算法，在固定 ASIC 与通用 CPU 之间取得折中。</p><h3 id="5-CGRA-不是所有“可重构引擎”的统称">5. CGRA 不是所有“可重构引擎”的统称</h3><p>Coarse-Grained Reconfigurable Array（CGRA）通常由多个可编程处理单元与可配置互联构成，擅长映射规则数据流和算术计算图。</p><p>只有公开了 Processing Element、互联结构、调度或映射模型，才能较有把握地判断某个模块是否属于 CGRA。厂商使用“可编程引擎”或“可重构微引擎”，并不自动意味着其内部采用 CGRA。</p><h2 id="六、片上互联：AXI-不是一条无限宽的高速公路">六、片上互联：AXI 不是一条无限宽的高速公路</h2><h3 id="1-AXI-是接口协议，不是完整-NoC">1. AXI 是接口协议，不是完整 NoC</h3><p>AMBA AXI 定义事务与通道语义，适合连接 Master 与 Slave；大型网络 SoC 还需要片上网络（Network-on-Chip, NoC）、Crossbar、Arbiter、Bridge 和多组 SRAM Bank。</p><p>直接比较“PCIe 和 AXI 谁更快”没有意义：</p><ul><li>PCIe 是芯片外部串行 I/O；</li><li>AXI 是芯片内部事务接口；</li><li>NoC 决定多个模块如何共享片上带宽；</li><li>实际吞吐取决于位宽、频率、并发、路由和仲裁。</li></ul><h3 id="2-1-Tbps-数据通路有多宽">2. 1 Tbps 数据通路有多宽</h3><p>一个 512-bit、1 GHz 的理想单向数据通路，原始带宽为：</p><p>$$<br>512\ \mathrm{bit} \times 1\ \mathrm{GHz}=512\ \mathrm{Gbit/s}<br>$$</p><p>要承载 1 Tbps，可以采用：</p><ul><li>1024-bit × 1 GHz；</li><li>512-bit × 2 GHz；</li><li>多条并行 256 / 512-bit Fabric；</li><li>分片 Packet Pipeline；</li><li>多 Bank SRAM 与多 DMA Channel。</li></ul><p>现实中还会损失在仲裁、空泡、Header、跨时钟域、Credit 和读写冲突中，因此设计目标必须显著高于端口速率的算术下限。</p><h3 id="3-On-chip-SRAM-是隐形核心">3. On-chip SRAM 是隐形核心</h3><p>高性能网卡需要 SRAM 存放：</p><ul><li>Packet Buffer；</li><li>QP / CQ Context Cache；</li><li>Flow Table；</li><li>Retransmission State；</li><li>Descriptor；</li><li>Scheduler Queue；</li><li>Security Association；</li><li>Telemetry Counter。</li></ul><p>SRAM 面积大、静态功耗高，又往往要求多端口或多 Bank。Buffer 太小会增加丢包和外部访问；Buffer 太大则显著推高 Die Area、功耗与访问延迟。</p><h3 id="4-数据通路与控制通路必须隔离">4. 数据通路与控制通路必须隔离</h3><p>如果 Firmware、Telemetry 或异常处理可以长期占用与热路径相同的 NoC 和 SRAM 端口，就可能在最不应该抖动的时候制造抖动。</p><p>因此，高性能设计通常会考虑：</p><ul><li>独立管理网络；</li><li>数据面保留带宽；</li><li>QoS 与优先级；</li><li>分离的 SRAM Bank；</li><li>控制面限速；</li><li>故障风暴下的资源保护。</li></ul><h2 id="七、112G-224G-SerDes-为什么像“黑魔法”">七、112G / 224G SerDes 为什么像“黑魔法”</h2><h3 id="1-速率提升不再靠简单提高时钟">1. 速率提升不再靠简单提高时钟</h3><p>现代高速以太网广泛采用 PAM4（四电平脉冲幅度调制）。PAM4 每个 Symbol 携带 2 bit，因此能够在相近 Baud Rate 下提高数据率。</p><p>代价是原本一个大眼图变成三个更小的眼图。相邻电平间距缩小，系统对以下因素更敏感：</p><ul><li>热噪声；</li><li>串扰；</li><li>电源噪声；</li><li>抖动；</li><li>封装损耗；</li><li>PCB 插入损耗与反射；</li><li>连接器；</li><li>光电器件线性度。</li></ul><h3 id="2-Tx-与-Rx-要共同恢复眼图">2. Tx 与 Rx 要共同恢复眼图</h3><p>发送端常使用 Feed-Forward Equalization（FFE）预加重，接收端可能使用：</p><ul><li>Continuous-Time Linear Equalizer（CTLE）；</li><li>Variable Gain Amplifier；</li><li>Decision Feedback Equalizer（DFE）；</li><li>Clock and Data Recovery（CDR）；</li><li>ADC / DSP 或模拟判决电路。</li></ul><p>均衡越强，功耗和复杂度越高；DFE 还可能产生误判传播。设计目标不是单点追求最高 SNR，而是在通道损耗、功耗、面积、抖动容限与误码率之间寻找可量产的平衡。</p><h3 id="3-Pre-FEC-BER-与-Post-FEC-BER">3. Pre-FEC BER 与 Post-FEC BER</h3><p>高速链路通常允许 FEC 前存在一定误码，再由 Forward Error Correction（FEC）把最终 BER 降到系统目标。</p><p>$$<br>\text{Channel Loss / Noise}<br>\rightarrow \text{Equalization}<br>\rightarrow \text{Pre-FEC BER}<br>\rightarrow \text{FEC Coding Gain}<br>\rightarrow \text{Post-FEC BER}<br>$$</p><p>因此，不能简单写成“SNR 决定是否开启 FEC”。在许多 112G / 224G PAM4 标准中，FEC 本身就是链路预算的一部分。</p><p>FEC 延迟也不存在统一的“几百纳秒”，它取决于：</p><ul><li>码型与码长；</li><li>Decoder 架构；</li><li>并行度；</li><li>Interleaving；</li><li>时钟频率；</li><li>PHY Pipeline。</li></ul><h3 id="4-224G-不只是“112G-翻倍”">4. 224G 不只是“112G 翻倍”</h3><p>OIF 的 CEI-224G 工作覆盖 Very Short Reach、Medium Reach、Long Reach 和 Linear 等不同信道目标，面向 800G、1.6T 及更高容量系统。<a href="https://www.oiforum.com/technical-work/hot-topics/common-electrical-i-o-cei-224g/">OIF：CEI-224G</a></p><p>不同 Reach 对均衡、功耗、封装和信道材料的要求不同。一颗适用于封装附近短距离连接的 224G SerDes，不等于能够穿越长 PCB、连接器和铜缆完成更长距离传输。</p><h2 id="八、自研还是采购：高速-IP-的工程经济学">八、自研还是采购：高速 IP 的工程经济学</h2><h3 id="1-SerDes-是工艺绑定的模拟系统">1. SerDes 是工艺绑定的模拟系统</h3><p>数字逻辑可以通过综合与布局布线迁移，但高速 SerDes 深度依赖：</p><ul><li>晶体管模型；</li><li>PLL / CDR；</li><li>I/O Device；</li><li>Package Model；</li><li>ESD；</li><li>PVT Corner；</li><li>模拟版图与寄生参数；</li><li>测试芯片和实验室设备。</li></ul><p>从一个工艺节点迁移到另一个节点，远不是重新综合 RTL。</p><h3 id="2-一次成功流片的价值极高">2. 一次成功流片的价值极高</h3><p>数字逻辑问题有时可以通过 Firmware、旁路或后续版本修复；SerDes 如果在关键通道上达不到眼图和 BER，整颗芯片可能无法满足目标端口速率。</p><p>成熟 IP 的价值通常包括：</p><ul><li>Silicon Proven 记录；</li><li>标准 Compliance 经验；</li><li>信道、封装和测试模型；</li><li>缩短上市周期；</li><li>降低首次流片失败风险。</li></ul><p>代价则是授权费、Royalty、供应商锁定、工艺绑定与定制受限。</p><h3 id="3-采购-IP-不等于放弃核心能力">3. 采购 IP 不等于放弃核心能力</h3><p>即使购买 SerDes、PCIe 或 DDR IP，芯片公司仍要完成：</p><ul><li>IP 评估与 PPA 验证；</li><li>SoC 集成；</li><li>Clock、Power 与 Reset；</li><li>DFT；</li><li>Package / Board SI；</li><li>Firmware；</li><li>Compliance；</li><li>Yield 与量产测试。</li></ul><p>真正的决策不是“自研高尚、采购简单”，而是哪些模块决定产品差异化，哪些模块的失败风险又足以拖垮整个项目。</p><h3 id="4-差异化通常落在哪里">4. 差异化通常落在哪里</h3><p>对资源有限的网络芯片公司，产品差异常来自：</p><ul><li>RDMA Transport；</li><li>Congestion Control；</li><li>Packet Spraying 与乱序恢复；</li><li>Queue / Context 管理；</li><li>安全卸载；</li><li>可编程数据面；</li><li>驱动、Firmware 与生态适配。</li></ul><p>把有限的工程资源集中在这些系统级能力上，往往比重复实现所有成熟 PHY IP 更有价值。</p><h2 id="九、安全为什么可能成为智算网卡的原生能力">九、安全为什么可能成为智算网卡的原生能力</h2><h3 id="1-AI-集群不再总是单租户封闭机器">1. AI 集群不再总是单租户封闭机器</h3><p>云端 GPU 集群可能同时承载不同客户与不同安全等级的任务。需要保护的数据包括：</p><ul><li>训练数据；</li><li>模型参数；</li><li>梯度；</li><li>Checkpoint；</li><li>推理请求；</li><li>KV Cache；</li><li>Agent 工具调用与中间状态。</li></ul><p>当数据跨越服务器、机架或数据中心边界，网络加密、设备身份和租户隔离会逐渐成为数据路径的一部分。</p><h3 id="2-Inline-Security-的价值">2. Inline Security 的价值</h3><p>如果加密完全由 CPU 或 GPU 软件完成，可能带来：</p><ul><li>额外内存访问；</li><li>CPU 占用；</li><li>GPU Kernel 干扰；</li><li>更长关键路径；</li><li>难以维持线速。</li></ul><p>网卡中的 Inline IPsec / MACsec 可以在报文数据路径中完成加解密和认证，尽量减少对 RDMA 与零拷贝路径的破坏。</p><div class="note info"><p>国内厂商已经出现把高速网络、RDMA 与安全卸载结合的公开产品路线。例如，沐创公开的 N20 同时强调双 100G、RoCEv2、可编程处理与 Inline Security。这里仅把它视为一种产业路线样本，不讨论其未公开的内部架构。<a href="https://mucse.com/about/detail.aspx?Id=280&amp;mtt=1">沐创 N20 公开信息</a></p></div><h3 id="3-真正困难的是状态和密钥生命周期">3. 真正困难的是状态和密钥生命周期</h3><p>安全网卡不仅要计算 AES、SM4 或哈希，还要处理：</p><ul><li>Security Association 数量；</li><li>Key Provisioning；</li><li>Rekey；</li><li>Anti-Replay Window；</li><li>多租户密钥隔离；</li><li>Secure Boot；</li><li>Firmware 签名；</li><li>Device Attestation；</li><li>Debug Port 与生产测试安全。</li></ul><p>如果产品只说明“支持某算法”，却没有密钥生命周期、租户隔离与控制面设计，就不能完整说明其安全能力。</p><h3 id="4-安全也会消耗-PPA-与验证资源">4. 安全也会消耗 PPA 与验证资源</h3><p>安全模块占用面积、功耗、Buffer 和验证周期。对于追求极低时延的封闭训练 Fabric，用户可能并不需要所有安全功能。</p><p>合理的产品设计可能需要回答：</p><ul><li>是否采用不同 SKU；</li><li>安全模块关闭后能否完全旁路；</li><li>Inline 加密对 RDMA 延迟与吞吐影响多大；</li><li>能否接入云平台密钥管理和可信计算体系；</li><li>安全能力是否真正对应用户威胁模型。</li></ul><h2 id="十、驱动与软件栈不是芯片的附件">十、驱动与软件栈不是芯片的附件</h2><p>一张 RNIC 从应用到物理网络至少经过：</p><pre><code class="mermaid">flowchart TB    A[PyTorch &#x2F; Training Framework] --&gt; B[NCCL &#x2F; MPI &#x2F; Collective Library]    B --&gt; C[libfabric &#x2F; Verbs &#x2F; Communication Runtime]    C --&gt; D[Kernel Driver &#x2F; User-space Driver]    D --&gt; E[Firmware]    E --&gt; F[Queue + RDMA + DMA Hardware]    F --&gt; G[Ethernet Fabric]</code></pre><h3 id="1-驱动决定硬件能否被稳定使用">1. 驱动决定硬件能否被稳定使用</h3><p>驱动需要处理：</p><ul><li>Device Initialization；</li><li>Queue Allocation；</li><li>Memory Registration；</li><li>Interrupt 与 Polling；</li><li>SR-IOV；</li><li>Error Recovery；</li><li>Firmware Upgrade；</li><li>ethtool / devlink / RDMA Core；</li><li>NUMA 与 CPU Affinity。</li></ul><p>这些工作不会提高峰值端口数字，却直接决定设备能否在真实服务器中长期运行。</p><h3 id="2-Collective-Library-决定实际流量形态">2. Collective Library 决定实际流量形态</h3><p>即使网卡支持 400G，如果通信库没有正确发现拓扑、GPU Direct 路径未启用，或 Collective Algorithm 选择不匹配，实际训练仍可能走 Host Staging 或低效路径。</p><p>NCCL、MPI 或其他通信运行时会决定：</p><ul><li>Ring、Tree 或分层算法；</li><li>通道数量；</li><li>Chunk Size；</li><li>GPU、NIC 与 NUMA 的绑定；</li><li>多 Rail 使用方式；</li><li>是否启用特定网络插件。</li></ul><h3 id="3-Firmware-是硬件与协议之间的长期契约">3. Firmware 是硬件与协议之间的长期契约</h3><p>Firmware 负责初始化、错误恢复、配置与设备生命周期。新协议和新平台适配往往需要 Firmware 更新，而不是重新流片。</p><p>这也意味着 Firmware 必须处理：</p><ul><li>版本兼容；</li><li>安全更新；</li><li>回滚；</li><li>现场升级失败；</li><li>日志与故障诊断；</li><li>不同硬件 Revision。</li></ul><h3 id="4-生态是最慢形成的护城河">4. 生态是最慢形成的护城河</h3><p>网络控制器的长期壁垒往往来自：</p><ul><li>多年驱动维护；</li><li>大量服务器兼容性；</li><li>BIOS / BMC / PXE / NC-SI；</li><li>Linux Kernel Version；</li><li>DPDK；</li><li>RDMA Core；</li><li>GPU Runtime；</li><li>故障日志与现场支持。</li></ul><p>芯片可以在一次流片后出现，生态却需要通过长期部署逐渐积累。</p><h2 id="十一、从铜到光：CPO-是关键路线，但不是唯一终点">十一、从铜到光：CPO 是关键路线，但不是唯一终点</h2><h3 id="1-高速电信号为什么越来越难走到前面板">1. 高速电信号为什么越来越难走到前面板</h3><p>交换 ASIC 或网络芯片与前面板光模块之间，需要高速 SerDes 穿越封装、PCB 和连接器。端口密度和 Lane 速率提升后：</p><ul><li>插入损耗增加；</li><li>Retimer / DSP 功耗上升；</li><li>PCB 材料更昂贵；</li><li>前面板散热困难；</li><li>信号完整性 Margin 缩小。</li></ul><p>Co-Packaged Optics（CPO）把光引擎放到交换 ASIC 附近，缩短高损耗电通道，再通过光纤离开封装。</p><h3 id="2-高速互联是一组连续方案">2. 高速互联是一组连续方案</h3><table><thead><tr><th style="text-align:left">方案</th><th style="text-align:left">电通道长度</th><th style="text-align:left">模块可维护性</th><th style="text-align:left">功耗趋势</th><th style="text-align:left">主要特点</th></tr></thead><tbody><tr><td style="text-align:left">DAC</td><td style="text-align:left">较长铜缆</td><td style="text-align:left">高</td><td style="text-align:left">较低</td><td style="text-align:left">距离短、成本低</td></tr><tr><td style="text-align:left">AEC</td><td style="text-align:left">铜缆 + 有源均衡</td><td style="text-align:left">高</td><td style="text-align:left">中</td><td style="text-align:left">比 DAC 更长，包含有源器件</td></tr><tr><td style="text-align:left">Retimed Pluggable Optics</td><td style="text-align:left">ASIC 到前面板</td><td style="text-align:left">很高</td><td style="text-align:left">较高</td><td style="text-align:left">生态成熟、易更换</td></tr><tr><td style="text-align:left">LPO / LRO</td><td style="text-align:left">缩减模块侧 DSP</td><td style="text-align:left">高</td><td style="text-align:left">较低</td><td style="text-align:left">功耗下降，但链路预算更敏感</td></tr><tr><td style="text-align:left">NPO</td><td style="text-align:left">光引擎靠近 ASIC</td><td style="text-align:left">中</td><td style="text-align:left">较低</td><td style="text-align:left">缩短电通道，保持部分模块化</td></tr><tr><td style="text-align:left">CPO</td><td style="text-align:left">光引擎与 ASIC 共封装</td><td style="text-align:left">较低</td><td style="text-align:left">最低潜力</td><td style="text-align:left">高带宽密度，维护和良率复杂</td></tr></tbody></table><h3 id="3-CPO-已经从概念走向产品">3. CPO 已经从概念走向产品</h3><p>Broadcom 已公开多代高容量 CPO 交换平台，强调系统功耗与带宽密度收益。<a href="https://investors.broadcom.com/news-releases/news-release-details/broadcom-announces-third-generation-co-packaged-optics-cpo">Broadcom：第三代 CPO</a></p><p>NVIDIA 也公开了 Spectrum-X Ethernet Photonics，将硅光引擎放到交换 ASIC 封装附近。<a href="https://www.nvidia.com/en-sg/networking/products/silicon-photonics/">NVIDIA Silicon Photonics Networking</a></p><p>这些资料能够证明产业方向，但厂商给出的功耗和可靠性数字仍需要结合测试条件理解，不能直接视为跨平台 Benchmark。</p><h3 id="4-CPO-的代价">4. CPO 的代价</h3><ul><li>光引擎与高功耗 ASIC 热耦合；</li><li>封装良率与测试复杂；</li><li>单个光引擎故障可能影响整机维修；</li><li>外置激光源、光纤连接器和自动装配要求高；</li><li>供应链从模块级转向封装级协作；</li><li>现场更换不如可插拔模块简单。</li></ul><p>因此，更现实的判断是：</p><blockquote><p>CPO 会优先进入高容量交换芯片和极高带宽密度场景，并与可插拔光模块、LPO、LRO、AEC 和 NPO 长期共存。</p></blockquote><h2 id="十二、如何严肃评价一张智算网卡">十二、如何严肃评价一张智算网卡</h2><p>判断一张网卡是否真正适合智算集群，不能只看“400G + RoCEv2”。至少需要以下证据。</p><h3 id="1-基础数据面">1. 基础数据面</h3><ul><li>单端口与多端口同时吞吐；</li><li>64B、128B、256B、1KB、4KB、1MB 消息性能；</li><li>单向与双向吞吐；</li><li>PCIe Read / Write Efficiency；</li><li>功耗与每 Gbit/s 能效；</li><li>不同 NUMA 与 PCIe Topology 下的性能。</li></ul><h3 id="2-RDMA">2. RDMA</h3><ul><li>最大 QP / CQ / MR 数量；</li><li>活跃连接规模下的 Context Cache Miss；</li><li>RDMA Read、Write、Send 延迟；</li><li>RC 丢包恢复；</li><li>Selective Retransmission；</li><li>Out-of-order 深度；</li><li>Memory Registration 开销；</li><li>GPUDirect RDMA 的真实路径。</li></ul><h3 id="3-AI-Collective">3. AI Collective</h3><ul><li>NCCL Tests AllReduce / AllGather / ReduceScatter / All-to-All；</li><li>8、16、64、256 节点扩展；</li><li>Ring、Tree 与 Hierarchical Algorithm；</li><li>多 Job 并发；</li><li>MoE Incast；</li><li>P50、P99、P999；</li><li>故障后的恢复时间。</li></ul><h3 id="4-端网协同">4. 端网协同</h3><ul><li>配套交换机和 ECN 配置；</li><li>Packet Spraying 的路径选择；</li><li>Credit 或其他反馈机制；</li><li>Telemetry；</li><li>是否依赖私有扩展；</li><li>与第三方交换机的兼容性；</li><li>PFC 开启、关闭和混合场景。</li></ul><h3 id="5-安全与虚拟化">5. 安全与虚拟化</h3><ul><li>Inline IPsec / MACsec 吞吐和延迟；</li><li>加密开启后的 RDMA 性能；</li><li>VF 数量与隔离；</li><li>密钥管理；</li><li>Firmware 安全；</li><li>恶意租户压力下的资源保护。</li></ul><div class="note primary"><p>最有说服力的材料不是再增加一页功能列表，而是公开可复现的测试条件：服务器、GPU、交换机、线缆、拓扑、驱动、Firmware、通信库版本、消息大小和完整命令。</p></div><h2 id="十三、国产智算网卡真正面对的机会与边界">十三、国产智算网卡真正面对的机会与边界</h2><h3 id="1-机会不只来自“国产替代”">1. 机会不只来自“国产替代”</h3><p>国内 CPU、GPU、服务器和云平台需要可控的高速网络接口、驱动与运维体系。但供应链窗口只能提供入场机会，无法代替性能、稳定性和生态。</p><p>更长期的机会来自：</p><ul><li>国内异构计算平台需要深度适配；</li><li>AI 网络协议仍在快速演进；</li><li>安全、虚拟化与多租户需求具有本地差异；</li><li>客户需要能够快速响应的 Firmware 和驱动团队；</li><li>网络、存储和加速器之间仍存在大量系统优化空间。</li></ul><h3 id="2-端口速率很快会商品化">2. 端口速率很快会商品化</h3><p>400G、800G 会逐渐成为产品入场券。真正难复制的能力是：</p><ul><li>大规模 RDMA 状态与故障恢复；</li><li>Collective 下的尾延迟；</li><li>交换机与端点协同；</li><li>驱动和 Firmware 稳定性；</li><li>多平台兼容；</li><li>可复现的系统级性能。</li></ul><h3 id="3-Scale-out-与-Scale-up-不是自然升级关系">3. Scale-out 与 Scale-up 不是自然升级关系</h3><p>拥有高速 Scale-out RNIC，并不意味着自然获得 Scale-up 互联能力。Scale-up 往往还需要：</p><ul><li>更低的端到端延迟；</li><li>Load / Store / Atomic 等内存语义；</li><li>加速器一致的软件模型；</li><li>专用交换芯片；</li><li>Fabric Manager；</li><li>故障域和拓扑管理。</li></ul><p>两者可以共享 SerDes、封装、DMA 或部分协议能力，但系统目标和验证方法并不相同。</p><h3 id="4-软件维护周期远长于一次流片">4. 软件维护周期远长于一次流片</h3><p>Linux Kernel、DPDK、RDMA Core、GPU Runtime、服务器固件与云平台会持续变化。一颗网卡从 Tape-out 到成为稳定基础设施，中间往往隔着多年的软件维护、兼容性测试和现场故障处理。</p><h2 id="结语：网卡设计师正在变成互联系统架构师">结语：网卡设计师正在变成互联系统架构师</h2><p>一颗智算网卡同时面对三个世界：</p><ul><li>模拟与物理世界：SerDes、抖动、信道和 FEC；</li><li>数字芯片世界：DMA、NoC、SRAM、队列、状态机和 PPA；</li><li>分布式系统世界：Collective、拥塞、故障、软件栈和多租户。</li></ul><p>只懂其中一层，很难定义下一代互联。</p><p>端口速率决定了一张网卡理论上能够搬运多少数据；Transport、片上存储与软件栈则决定这些带宽能否被稳定地交付给应用；交换网络和 Collective Runtime 最终决定它能否转化成训练吞吐。</p><blockquote><p>下一代互联架构的竞争，不再发生在单一 MAC、SerDes 或协议模块内部，而发生在工作负载、软件、传输语义、交换结构和物理信道之间。</p></blockquote><p>对于 IC 设计师而言，真正值得培养的能力也不只是把一个 RTL 模块写对，而是理解数据为什么移动、状态应该放在哪里，以及每一层的优化会把代价转移到哪一层。</p><hr><h2 id="建议配图">建议配图</h2><p>全文以 Mermaid 自绘架构图为主，不需要加入具体厂商产品图。若希望增加一张视觉主图，建议自行绘制以下内容：</p><ol><li><p><strong>智算网卡从 GPU 内存到光口的端到端数据路径</strong><br>文件名建议：<code>ai-nic-end-to-end-datapath.png</code></p></li><li><p><strong>固定功能 ASIC、微引擎与嵌入式 CPU 的职责分层</strong><br>文件名建议：<code>ai-nic-programmability-layers.png</code></p></li><li><p><strong>PCIe、片上 NoC、SerDes 三个带宽域的对应关系</strong><br>文件名建议：<code>ai-nic-bandwidth-domains.png</code></p></li></ol><p>配图应明确标注“通用架构示意”，避免让读者误认为是任何特定芯片的内部框图。</p><link rel="stylesheet" href="/myblog-source/css/series-page.css"><div class="series-nav series-nav--single">  <a href="/myblog-source/2026/06/28/ai-compute-interconnect-part2-reliability-rethink/" class="series-nav__link">← 上一篇：重构可靠性</a></div><h2 id="参考资料">参考资料</h2><ol><li><a href="https://pcisig.com/what-bit-rates-does-pcie-50-specification-support-and-how-does-it-compare-prior-pcie-generations">PCI-SIG：PCIe 5.0 Bit Rates and Bandwidth</a></li><li><a href="https://pcisig.com/pci-express-6.0-specification">PCI-SIG：PCI Express 6.0 Specification</a></li><li><a href="https://www.oiforum.com/technical-work/hot-topics/common-electrical-i-o-cei-224g/">OIF：Common Electrical I/O CEI-224G</a></li><li><a href="https://docs.nvidia.com/cuda/gpudirect-rdma/">NVIDIA：GPUDirect RDMA Documentation</a></li><li><a href="https://docs.kernel.org/infiniband/user_verbs.html">Linux Kernel：Userspace Verbs Access</a></li><li><a href="https://ualinkconsortium.org/blog/ualink-200g-1-0-specification-overview-802/">UALink 200G 1.0 Specification Overview</a></li><li><a href="https://investors.broadcom.com/news-releases/news-release-details/broadcom-announces-third-generation-co-packaged-optics-cpo">Broadcom：Third-Generation Co-Packaged Optics</a></li><li><a href="https://www.broadcom.com/topics/what-is-co-packaged-optics">Broadcom：What Is Co-Packaged Optics</a></li><li><a href="https://www.nvidia.com/en-sg/networking/products/silicon-photonics/">NVIDIA：Silicon Photonics Networking</a></li><li><a href="https://mucse.com/about/detail.aspx?Id=280&amp;mtt=1">沐创 N20 公开产品信息</a></li></ol>]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/06/28/ai-compute-interconnect-part3-smart-nic-chip/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/06/28/ai-compute-interconnect-part3-smart-nic-chip/"/>
    <published>2026-06-28T14:38:30.000Z</published>
    <summary>从一颗智算网卡的内部数据通路出发，分析 PCIe、DMA、RDMA Transport、片上 NoC、SRAM、112G/224G SerDes、FEC、可编程处理器与光互联，讨论一张高速网卡如何真正成为 AI 集群中的互联端点。</summary>
    <title>智算互联（下）：一颗智算网卡如何被造出来——高速接口、数据通路与光互联</title>
    <updated>2026-07-01T05:07:10.327Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Digital Anatomy" scheme="https://leecdiang.github.io/myblog-source/categories/Digital-Anatomy/"/>
    <category term="OpenClaw" scheme="https://leecdiang.github.io/myblog-source/tags/OpenClaw/"/>
    <category term="Agent" scheme="https://leecdiang.github.io/myblog-source/tags/Agent/"/>
    <category term="架构" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%9E%B6%E6%9E%84/"/>
    <category term="可靠性" scheme="https://leecdiang.github.io/myblog-source/tags/%E5%8F%AF%E9%9D%A0%E6%80%A7/"/>
    <category term="容错" scheme="https://leecdiang.github.io/myblog-source/tags/%E5%AE%B9%E9%94%99/"/>
    <category term="工程实践" scheme="https://leecdiang.github.io/myblog-source/tags/%E5%B7%A5%E7%A8%8B%E5%AE%9E%E8%B7%B5/"/>
    <content>
      <![CDATA[<p><img data-src="openclaw-reliable-search-20260619-115308.png" alt=""></p><blockquote><p><strong>Abstract</strong></p><p>This article traces the evolution of a reliability layer for web search in AI agents. The work began as an attempt to stabilize OpenClaw’s Gemini-backed search, but quickly exposed deeper problems: provider-specific parameters, ambiguous failure states, uncontrolled retries, proxy failures, response-envelope mismatches, tool-policy bypasses, and missing execution records. I first built an OpenClaw runtime gate that governed search calls, classified failures, bounded retries, normalized responses, and wrote auditable JSONL records. The design was then generalized into <code>reliable-web-search</code>, a policy-driven multi-provider search runtime with provider priorities, structured errors, real request cancellation, circuit breaking, fallback/race/aggregate strategies, multi-credential routing, a unified CLI, and a stdio MCP server. The central lesson is simple: reliable agent systems are not defined by never failing, but by knowing precisely what failed, what evidence remains usable, what action is permitted next, and when execution must stop.</p></blockquote><blockquote><p><strong>2026-06-26 更新</strong></p><p>本文最初记录的是一个运行在 OpenClaw 内部的本地插件原型。随后,我将其中可复用的参数治理、搜索路由、错误语义、取消、熔断和诊断能力抽象为独立开源项目 <a href="https://github.com/leecdiang/reliable-web-search"><code>reliable-web-search</code></a>,并发布到 npm。当前公开版本为 <code>v0.4.0</code>,已支持统一 CLI、stdio MCP、多 Provider、多凭据 profile、环境变量临时路由,以及 OpenClaw 等 Agent Host 的自动接入。文中的本地插件与当前 npm 包不是同一套代码:前者解释项目为什么出现,后者则是这套思路经过重构后的通用实现。</p><p><a href="https://github.com/leecdiang/reliable-web-search/tree/main" target="_blank" class="glass-pill"><i class="fab fa-github"></i> leecdiang/reliable-web-search</a></p></blockquote><p>这次改造的起因很简单:OpenClaw 的联网搜索不稳定。</p><p>最开始我把它当成普通配置问题–也许 API Key 没填对,也许本地代理偶尔掉线,也许重启一下 Gateway 就恢复了。结果排查下来发现,整条 Tool Calling 链路缺的是<strong>状态语义</strong>。</p><p>搜索失败时,Agent 知道失败发生在哪一层吗?</p><p>网络没连上、Provider 服务过载、配额耗尽、鉴权失败、响应解析错误和真正没有结果,最后可能都被压缩成一句&quot;搜索失败&quot;。Agent 也可能进一步把&quot;调用失败&quot;误解成&quot;网上没有相关资料&quot;。一次请求该重试几次?超时后底层请求还在消耗额度吗?多个 Provider 同时可用时,按什么顺序调用?哪个结果才算真正可用?整个过程能不能回溯?</p><p>这些问题不断冒出来,任务也从&quot;修好搜索&quot;变成了:</p><blockquote><p>为 Agent 建立一个受控、可测试、可取消、可审计,并且能够正确失败的搜索运行时。</p></blockquote><p>下面是一条从故障排查、运行时治理到通用搜索基础设施的演化路径。</p><hr><h2 id="1-搜索失败不是一个状态-而是一组完全不同的故障">1. 搜索失败不是一个状态,而是一组完全不同的故障</h2><p>OpenClaw 原本已经有 <code>web_search</code> 工具,通过 Gemini 获取联网结果。调用链看起来是这样的:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Agent</span><br><span class="line">→ web_search</span><br><span class="line">→ Gemini</span><br><span class="line">→ 搜索结果</span><br></pre></td></tr></table></figure><p>这条链路藏了一个问题:Agent 看到的是工具调用结果,但故障到底发生在哪个层级,它不知道。</p><p>排查时我遇到过这些状态:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">unsupported_language</span><br><span class="line">503 UNAVAILABLE / high demand</span><br><span class="line">429 rate limited</span><br><span class="line">fetch failed</span><br><span class="line">ECONNREFUSED</span><br><span class="line">no_results</span><br><span class="line">response_parse_failure</span><br><span class="line">401 authentication failure</span><br></pre></td></tr></table></figure><p>它们不是同一类失败。</p><h3 id="1-1-参数错误、服务过载、网络故障和空结果不是一回事">1.1 参数错误、服务过载、网络故障和空结果不是一回事</h3><p>统一接口容易让人以为所有搜索 Provider 接受同一组参数。实际上 Gemini、Brave、Tavily、SearXNG 这些服务,在语言、国家、时间范围、结果数量和网页索引能力上各有差异。</p><p>早期 OpenClaw 调用中,Gemini 不接受部分通用字段:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">language</span><br><span class="line">search_lang</span><br><span class="line">country</span><br><span class="line">ui_lang</span><br></pre></td></tr></table></figure><p>Agent 按统一接口传这些字段,Provider 直接拒绝请求。问题不是网络,也不是&quot;没有结果&quot;,是:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">调用参数</span><br><span class="line">≠</span><br><span class="line">Provider 能力</span><br></pre></td></tr></table></figure><p>其他故障也要分开处理:</p><table><thead><tr><th style="text-align:left">状态</th><th style="text-align:left">实际含义</th><th style="text-align:left">合理动作</th></tr></thead><tbody><tr><td style="text-align:left"><code>provider_overloaded</code></td><td style="text-align:left">Provider 暂时过载</td><td style="text-align:left">有限重试,或切换 Provider</td></tr><tr><td style="text-align:left"><code>rate_limited</code></td><td style="text-align:left">触发速率或配额限制</td><td style="text-align:left">尊重 <code>Retry-After</code>,或切换凭据/Provider</td></tr><tr><td style="text-align:left"><code>transport_failure</code></td><td style="text-align:left">DNS、代理、连接或网络路径失败</td><td style="text-align:left">短重试,之后切换故障域</td></tr><tr><td style="text-align:left"><code>authentication_failure</code></td><td style="text-align:left">Key 缺失、失效或权限不足</td><td style="text-align:left">不原样重试;可换凭据</td></tr><tr><td style="text-align:left"><code>response_parse_failure</code></td><td style="text-align:left">Provider 有响应,但适配器无法理解</td><td style="text-align:left">记录兼容性错误并 fallback</td></tr><tr><td style="text-align:left"><code>no_results</code></td><td style="text-align:left">Provider 成功响应,但没有可用结果</td><td style="text-align:left">不原样重试;可换索引或 Provider</td></tr></tbody></table><p>只有两个条件都满足才算 <code>no_results</code>:Provider 成功响应,响应结构正确解析,且没有可用内容。</p><p>请求没发出去、响应解析不了、Provider 确实返回空结果–这是三种状态。</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">不应原样重试同一个 Provider</span><br><span class="line">≠</span><br><span class="line">不应 fallback 到其他 Provider</span><br></pre></td></tr></table></figure><p>某个 Provider 返回空结果,可能只是索引覆盖范围不同、查询理解方式不同或者服务类型不同。可靠搜索不能把&quot;当前 Provider 没找到&quot;解释成&quot;互联网中不存在&quot;。</p><h3 id="1-2-可靠性的关键是让失败决定下一步-不光是避免它">1.2 可靠性的关键是让失败决定下一步,不光是避免它</h3><p>需要回答的问题就那么几个:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">这次请求发出去了没有?</span><br><span class="line">发给了哪个 Provider?</span><br><span class="line">系统读懂响应了吗?</span><br><span class="line">错误值得重试吗?</span><br><span class="line">应该换凭据,还是换 Provider?</span><br><span class="line">超时后底层请求真的停了吗?</span><br><span class="line">当前结果能进入证据审查吗?</span><br><span class="line">什么时候必须停止?</span><br></pre></td></tr></table></figure><p>这些没有稳定答案,Agent 就只能自己猜。</p><p>可靠性设计的核心也变了:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;让搜索成功&quot;</span><br><span class="line">→</span><br><span class="line">&quot;让每一种失败都有明确的类型、边界和后续动作&quot;</span><br></pre></td></tr></table></figure><hr><h2 id="2-先在-OpenClaw-里造一道-Agent-绕不过去的-Runtime-Gate">2. 先在 OpenClaw 里造一道 Agent 绕不过去的 Runtime Gate</h2><p>最直接的做法,是在 Prompt 或 Skill 中加规则:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">如果搜索失败,不要编造答案。</span><br><span class="line">遇到 503 时有限重试。</span><br><span class="line">网络错误不能解释成没有结果。</span><br></pre></td></tr></table></figure><p>这些规则当然有用,但本质上仍然只是行为建议。</p><p>模型可能遵守,也可能随着上下文增长、工具变多、任务变复杂,把规则搁在一边。更关键的是,Prompt 管不住 Agent 绕过新流程。如果 Runtime 同时暴露 <code>web_search</code> 和 <code>reliable_web_search</code>,Skill 写着&quot;优先用后者&quot;,模型仍然可能直接调用原始工具。</p><p>所以关键不在继续扩写 Prompt,而是在工具层建立一道 Gate:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">Agent</span><br><span class="line">→ 受控搜索入口</span><br><span class="line">→ 参数治理</span><br><span class="line">→ Provider 路由</span><br><span class="line">→ 错误分类</span><br><span class="line">→ 有限重试</span><br><span class="line">→ 结果标准化</span><br><span class="line">→ 状态判断</span><br><span class="line">→ 审计记录</span><br></pre></td></tr></table></figure><p>同时在权限层封死原始入口:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">web_search = denied</span><br></pre></td></tr></table></figure><p>可靠性不能依赖模型&quot;记得遵守规则&quot;。应该让未经治理的路径根本不存在。</p><h3 id="2-1-先建立隔离的-Test-Agent">2.1 先建立隔离的 Test Agent</h3><p>我没有直接修改 Main Agent 或 Code Lab,而是单独建立了一个临时 <code>test</code> Agent,验证三件事:</p><ol><li>插件是否被 OpenClaw Runtime 正确加载;</li><li>新工具是否真的可调用;</li><li>被禁止的工具是否在 Runtime 层不可调用。</li></ol><p>当时的工具策略大致如下:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;test&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;tools&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;profile&quot;</span><span class="punctuation">:</span> <span class="string">&quot;full&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;allow&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;reliable_web_search&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;read&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;session_status&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;memory_get&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="string">&quot;memory_search&quot;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;deny&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;web_search&quot;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>使用 <code>full</code> profile 是因为 OpenClaw 的处理顺序是：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">profile</span><br><span class="line">→ allow</span><br><span class="line">→ deny</span><br></pre></td></tr></table></figure><p>之前用 <code>minimal</code> 时，插件工具在 profile 阶段就被过滤掉，allowlist 无法把它加回来。最后采用：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">full profile</span><br><span class="line">→ 让插件工具进入候选集合</span><br><span class="line">→ allowlist 严格收窄</span><br><span class="line">→ deny 明确移除原始 web_search</span><br></pre></td></tr></table></figure><p>最终 Runtime 中确认:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">reliable_web_search = present</span><br><span class="line">web_search = absent</span><br><span class="line">write = absent</span><br><span class="line">apply_patch = absent</span><br></pre></td></tr></table></figure><p>我还让 Agent 实际尝试调用 <code>write</code>。Runtime 返回&quot;工具未注册&quot;,而不是由 Agent 自己说&quot;我不会使用&quot;。</p><p>这一步验证了一条很重要的原则:</p><blockquote><p>Tool Policy 必须是运行时能力边界,不是写在 Prompt 里的行为建议。</p></blockquote><h3 id="2-2-OpenClaw-原型只治理调用-不重写整套基础设施">2.2 OpenClaw 原型只治理调用,不重写整套基础设施</h3><p>第一版插件注册了一个新的工具:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">reliable_web_search</span><br></pre></td></tr></table></figure><p>它没有重新实现 Gemini API,而是调用 OpenClaw 自己的搜索 Runtime:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">api.<span class="property">runtime</span>.<span class="property">webSearch</span>.<span class="title function_">search</span>(&#123;</span><br><span class="line">  <span class="attr">config</span>: api.<span class="property">config</span>,</span><br><span class="line">  args</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>这样做是因为 OpenClaw 已经负责:</p><ul><li>API Key 管理;</li><li>代理设置;</li><li>Provider 适配;</li><li>网络与安全约束;</li><li>引用包装;</li><li>Runtime 返回结构;</li><li>基础日志。</li></ul><p>如果插件直接对 Gemini 发起 <code>fetch</code>,就等于绕过了现有的运行时管理能力。这个 wrapper 只负责搜索调用的治理,而不是复制一套搜索基础设施。</p><p>原型被拆成几个部分:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">src/index.ts</span><br><span class="line">    插件入口、调用流程和路由</span><br><span class="line"></span><br><span class="line">src/pure.ts</span><br><span class="line">    参数清洗、响应标准化和错误分类</span><br><span class="line"></span><br><span class="line">src/retry.ts</span><br><span class="line">    重试计划、退避和 jitter</span><br><span class="line"></span><br><span class="line">src/search-run-log.ts</span><br><span class="line">    JSONL 审计记录</span><br><span class="line"></span><br><span class="line">src/index.test.ts</span><br><span class="line">    回归测试</span><br></pre></td></tr></table></figure><p>标准输出被设计为:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;ok&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;success&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;provider&quot;</span><span class="punctuation">:</span> <span class="string">&quot;gemini&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;results&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;removed_parameters&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;retrieval_succeeded&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;usable_for_review&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这里刻意没有设计 <code>usable_for_claim</code>。</p><p>因为:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">retrieval_succeeded</span><br><span class="line">= 检索成功并得到可解析内容</span><br><span class="line"></span><br><span class="line">usable_for_review</span><br><span class="line">= 内容可以进入后续来源审查</span><br><span class="line"></span><br><span class="line">usable_for_claim</span><br><span class="line">= 不应由搜索层决定,而应由 Evidence Layer 判断</span><br></pre></td></tr></table></figure><p>搜索层负责取回数据和描述状态,不替 Agent 宣布事实成立。</p><h3 id="2-3-参数治理-统一请求经过-Provider-capability-check">2.3 参数治理:统一请求经过 Provider capability check</h3><p>OpenClaw 原型中,调用前先经过:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">sanitizeSearchArgs</span>(provider, args)</span><br></pre></td></tr></table></figure><p>输入:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ASIC design flow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">10</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;country&quot;</span><span class="punctuation">:</span> <span class="string">&quot;US&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;language&quot;</span><span class="punctuation">:</span> <span class="string">&quot;zh&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>如果当前 Provider 不支持 <code>country</code> 和 <code>language</code>,发送前变成:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;ASIC design flow&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;count&quot;</span><span class="punctuation">:</span> <span class="number">10</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>同时记录:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;removed_parameters&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;country&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;language&quot;</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这不只是删字段,而是建立一条显式转换链:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">用户意图</span><br><span class="line">→ 统一搜索请求</span><br><span class="line">→ Provider capability check</span><br><span class="line">→ Provider-specific request</span><br></pre></td></tr></table></figure><p>这个思路后来保留到 <code>reliable-web-search</code>。每个 Provider 都有显式 <code>capabilities</code>,系统可以判断它属于完整网页搜索、AI Search 还是 Instant Answer,是否支持 freshness,以及最大结果数量。</p><h3 id="2-4-失败语义、有限重试和停止条件">2.4 失败语义、有限重试和停止条件</h3><p>原型阶段,我将错误归纳为:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">provider_overloaded</span><br><span class="line">rate_limited</span><br><span class="line">transport_failure</span><br><span class="line">authentication_failure</span><br><span class="line">no_results</span><br><span class="line">response_parse_failure</span><br></pre></td></tr></table></figure><p>随后明确区分两个经常被混在一起的动作:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">retry</span><br><span class="line">= 再次调用同一个 Provider</span><br><span class="line"></span><br><span class="line">fallback</span><br><span class="line">= 换一个 Provider 继续执行</span><br></pre></td></tr></table></figure><p>合理策略大致如下:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">provider_overloaded</span><br><span class="line">→ 2s → 5s → 12s</span><br><span class="line">→ 有限尝试</span><br><span class="line"></span><br><span class="line">rate_limited</span><br><span class="line">→ 优先读取 Retry-After</span><br><span class="line">→ 否则最多短暂重试一次</span><br><span class="line"></span><br><span class="line">transport_failure</span><br><span class="line">→ 短等待</span><br><span class="line">→ 最多重试一次</span><br><span class="line"></span><br><span class="line">authentication_failure</span><br><span class="line">→ 不重试当前凭据</span><br><span class="line"></span><br><span class="line">no_results</span><br><span class="line">→ 不对同一 Provider 原样重试</span><br><span class="line">→ 可以换 Provider</span><br></pre></td></tr></table></figure><p>期间还修复过一个 <code>attempts</code> 计算问题,最终明确:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">attempts</span><br><span class="line">= 包含首次调用在内的总调用次数</span><br><span class="line"></span><br><span class="line">retryIndex</span><br><span class="line">= 已完成的重试次数</span><br></pre></td></tr></table></figure><p>真实网络失败时,结果类似:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;ok&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;transport_failure&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;provider&quot;</span><span class="punctuation">:</span> <span class="string">&quot;gemini&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;results&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;error&quot;</span><span class="punctuation">:</span> <span class="string">&quot;fetch failed&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;retrieval_succeeded&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;usable_for_review&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>它表达的是:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">首次调用失败</span><br><span class="line">→ 有限等待</span><br><span class="line">→ 重试一次</span><br><span class="line">→ 仍然失败</span><br><span class="line">→ 明确停止</span><br></pre></td></tr></table></figure><p>可靠性不是死磕到成功,而是知道什么时候继续、什么时候切换、什么时候结束。</p><h3 id="2-5-最难判断的不是失败-而是失败发生在哪一层">2.5 最难判断的不是失败,而是失败发生在哪一层</h3><p>排查时最容易混淆的是:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">网络失败</span><br><span class="line">Provider 返回空结果</span><br><span class="line">wrapper 解析失败</span><br></pre></td></tr></table></figure><p>一开始 Gemini Runtime 返回:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;provider&quot;</span><span class="punctuation">:</span> <span class="string">&quot;gemini&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;result&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;citations&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>wrapper 检查的却是:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rawRecord.<span class="property">content</span></span><br></pre></td></tr></table></figure><p>真正内容在:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rawRecord.<span class="property">result</span>.<span class="property">content</span></span><br></pre></td></tr></table></figure><p>一个包含正文的成功响应,被误判成 <code>no_results</code>。</p><p>正确做法是先解开 Runtime envelope:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> rawData =</span><br><span class="line">  rawRecord &amp;&amp;</span><br><span class="line">  <span class="keyword">typeof</span> rawRecord === <span class="string">&quot;object&quot;</span> &amp;&amp;</span><br><span class="line">  <span class="string">&quot;result&quot;</span> <span class="keyword">in</span> rawRecord &amp;&amp;</span><br><span class="line">  <span class="keyword">typeof</span> rawRecord.<span class="property">result</span> === <span class="string">&quot;object&quot;</span></span><br><span class="line">    ? rawRecord.<span class="property">result</span></span><br><span class="line">    : rawRecord;</span><br></pre></td></tr></table></figure><p>之后所有内容检测、引用读取和标准化都基于 <code>rawData</code>。</p><p>教训:响应结构本身也是运行时契约。适配器看不懂响应,就该返回 parse failure,而不是解释成空结果。</p><p>期间还出过一次事故–用宽泛正则清理调试代码时,顺手删掉了刚修好的 envelope 解包逻辑。之后我给自己加了一条规矩:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">不使用宽泛正则批量改源码;</span><br><span class="line">修复必须落在 src;</span><br><span class="line">每次修改后重新 build 并跑回归测试。</span><br></pre></td></tr></table></figure><p>最终,真实调用链闭环为:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Agent</span><br><span class="line">→ reliable_web_search</span><br><span class="line">→ OpenClaw Web Search Runtime</span><br><span class="line">→ Gemini</span><br><span class="line">→ Runtime envelope</span><br><span class="line">→ normalize</span><br><span class="line">→ Agent</span><br></pre></td></tr></table></figure><h3 id="2-6-基础设施故障不会因为代码正确而消失">2.6 基础设施故障不会因为代码正确而消失</h3><p>当时另一个反复出现的变量,是本地代理:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">127.0.0.1:17891</span><br></pre></td></tr></table></figure><p>只要代理进程退出,Gateway 仍然尝试连接原端口,最终得到:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ECONNREFUSED</span><br><span class="line">fetch failed</span><br><span class="line">transport_failure</span><br></pre></td></tr></table></figure><p>这解释了为什么同一套代码可能几分钟前成功、几分钟后又失败。</p><p>最终确认的代理配置是:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">代理模式:规则</span><br><span class="line">HTTP/SOCKS 端口:17891</span><br><span class="line"></span><br><span class="line">服务模式 TUN:关闭</span><br><span class="line">DNS 防污染:关闭</span><br><span class="line">局域网代理:关闭</span><br><span class="line"></span><br><span class="line">自启动:开启</span><br><span class="line">关闭时询问:关闭</span><br><span class="line">退出时最小化:开启</span><br></pre></td></tr></table></figure><p>最需要区分的是:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">关闭窗口</span><br><span class="line">≠</span><br><span class="line">退出代理进程</span><br></pre></td></tr></table></figure><p><code>reliable_web_search</code> 可以正确识别代理故障,却不能自动恢复一个已经退出的代理服务。</p><p>这也是可靠性工程的边界:软件可以描述基础设施失败、隔离影响并选择后续策略,但不能假装依赖永远可靠。</p><h3 id="2-7-JSONL-让搜索离开不可追踪的对话">2.7 JSONL:让搜索离开不可追踪的对话</h3><p>原型会把每次搜索追加到:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">~/.openclaw/workspace/90_system/search/search_runs.jsonl</span><br></pre></td></tr></table></figure><p>成功记录类似:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;search_run_id&quot;</span><span class="punctuation">:</span> <span class="string">&quot;SEARCH-2bb117-372755&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;latest OpenClaw version 2026&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;provider&quot;</span><span class="punctuation">:</span> <span class="string">&quot;gemini&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;success&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;error_code&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">null</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;latency_ms&quot;</span><span class="punctuation">:</span> <span class="number">7758</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;result_count&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;removed_parameters&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;retrieval_succeeded&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;usable_for_review&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;started_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-06-18T13:42:26.336Z&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;finished_at&quot;</span><span class="punctuation">:</span> <span class="string">&quot;2026-06-18T13:42:34.094Z&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>网络失败也会留下记录:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;query&quot;</span><span class="punctuation">:</span> <span class="string">&quot;OpenClaw official documentation&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;provider&quot;</span><span class="punctuation">:</span> <span class="string">&quot;gemini&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="number">2</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;transport_failure&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;error_code&quot;</span><span class="punctuation">:</span> <span class="string">&quot;fetch failed&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;result_count&quot;</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;retrieval_succeeded&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;usable_for_review&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>过去 Agent 只丢一句&quot;搜索失败&quot;,想追问都不知道从哪里下手:调用了哪个 Provider?实际调用了几次?是代理问题、限流还是解析失败?哪些参数被删除?有没有获得可用内容?总耗时多少?</p><p>有了结构化记录,这些问题都有答案。</p><p>日志中刻意不保存 API Key、Authorization Header、完整 Provider 原始响应和完整搜索正文。</p><p>不过 query 本身也可能包含姓名、文件路径、项目代号等敏感信息。正式部署时,日志系统仍应支持关闭 query 日志、对 query 脱敏、只保存 query hash、设置日志保留期限。</p><p>可审计不意味着无限记录。观测能力同样要服从隐私边界。</p><hr><h2 id="3-从-OpenClaw-插件到通用搜索运行时">3. 从 OpenClaw 插件到通用搜索运行时</h2><p>OpenClaw 原型已经解决了当时的问题,但它仍然和本地环境高度绑定:</p><ul><li>依赖 OpenClaw Runtime;</li><li>Provider 路由写在插件内部;</li><li>JSONL 路径固定在 workspace;</li><li>错误类型和返回结构服务于特定 Agent;</li><li>其他 Node.js 应用无法直接复用。</li></ul><p>真正值得抽取的问题其实是:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">如何选择 Provider?</span><br><span class="line">什么状态触发 retry?</span><br><span class="line">什么状态触发 fallback?</span><br><span class="line">超时后请求是否真的停止?</span><br><span class="line">多个 Provider 并发时如何结束?</span><br><span class="line">Provider 连续故障时如何隔离?</span><br><span class="line">如何判断结果是否满足最低质量门?</span><br><span class="line">如何记录每一次尝试?</span><br></pre></td></tr></table></figure><p>于是这部分能力被抽象成独立项目:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">reliable-web-search</span><br></pre></td></tr></table></figure><p>它的定位不再是&quot;OpenClaw 搜索插件&quot;,而是应用与多个搜索 API 之间的策略运行时:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">Application / Agent</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">reliable-web-search</span><br><span class="line">        │</span><br><span class="line">        ├─ Provider registry</span><br><span class="line">        ├─ Priority routing</span><br><span class="line">        ├─ Capability and configuration checks</span><br><span class="line">        ├─ Retry and fallback policy</span><br><span class="line">        ├─ Timeout cancellation</span><br><span class="line">        ├─ Circuit breaker</span><br><span class="line">        ├─ Result status and quality gate</span><br><span class="line">        └─ Attempt diagnostics</span><br><span class="line">        │</span><br><span class="line">        ▼</span><br><span class="line">Search Providers</span><br></pre></td></tr></table></figure><h3 id="3-1-Provider-registry-显式优先级、能力和配置门">3.1 Provider registry:显式优先级、能力和配置门</h3><p>第一版按注册顺序排列,结果有时让无需密钥但能力有限的 Provider 排到前面。</p><p>现在每个 Provider 都具有:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">priority</span><br><span class="line">capabilities</span><br><span class="line"><span class="title function_">isConfigured</span>()</span><br></pre></td></tr></table></figure><p>registry 只加入真实配置完成的 Provider,并按优先级路由。SearXNG 只有显式配置 Base URL 才算可用,不再默认向本机 <code>localhost</code> 发请求。</p><p>如果用户写错 Provider 名称:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">providers</span>: [<span class="string">&quot;brvae&quot;</span>, <span class="string">&quot;tavily&quot;</span>]</span><br></pre></td></tr></table></figure><p>系统会给出接近的拼写建议,而不是静默忽略。</p><p>当前内置八个 Provider:</p><table><thead><tr><th style="text-align:left">Provider</th><th style="text-align:left">类型</th><th style="text-align:left">当前定位</th></tr></thead><tbody><tr><td style="text-align:left">Brave</td><td style="text-align:left">完整网页搜索</td><td style="text-align:left">Verified</td></tr><tr><td style="text-align:left">Tavily</td><td style="text-align:left">面向 LLM 的网页搜索</td><td style="text-align:left">Verified</td></tr><tr><td style="text-align:left">Bocha</td><td style="text-align:left">完整网页搜索</td><td style="text-align:left">Experimental</td></tr><tr><td style="text-align:left">Metaso</td><td style="text-align:left">AI Search</td><td style="text-align:left">Experimental</td></tr><tr><td style="text-align:left">Gemini</td><td style="text-align:left">AI Grounding</td><td style="text-align:left">Verified</td></tr><tr><td style="text-align:left">SerpAPI</td><td style="text-align:left">多搜索引擎适配</td><td style="text-align:left">Verified</td></tr><tr><td style="text-align:left">SearXNG</td><td style="text-align:left">自托管聚合搜索</td><td style="text-align:left">Verified</td></tr><tr><td style="text-align:left">DuckDuckGo</td><td style="text-align:left">Instant Answer</td><td style="text-align:left">零配置兜底</td></tr></tbody></table><p>DuckDuckGo 使用 Instant Answer API,不是完整网页搜索,更适合百科式主题摘要,因此被放在低优先级兜底位置。</p><h3 id="3-2-三种执行策略-fallback、race、aggregate">3.2 三种执行策略:fallback、race、aggregate</h3><p>三种模式:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">fallback</span><br><span class="line">race</span><br><span class="line">aggregate</span><br></pre></td></tr></table></figure><h4 id="fallback"><code>fallback</code></h4><p>按优先级顺序调用 Provider:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">Provider A</span><br><span class="line">→ 失败或空结果</span><br><span class="line">→ Provider B</span><br><span class="line">→ 成功后停止</span><br></pre></td></tr></table></figure><h4 id="race"><code>race</code></h4><p>同时启动多个 Provider,第一个获得可用结果的请求成为 winner:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">A ───────────────</span><br><span class="line">B ───── success</span><br><span class="line">C ─────────</span><br><span class="line"></span><br><span class="line">B wins</span><br><span class="line">→ abort A</span><br><span class="line">→ abort C</span><br></pre></td></tr></table></figure><p>这不是简单调用 <code>Promise.all()</code>。winner 一旦确认,其他 Provider 的独立 <code>AbortController</code> 会立即触发,避免落败请求继续占用连接和额度。</p><h4 id="aggregate"><code>aggregate</code></h4><p>并发调用多个 Provider,并汇总成功结果。当前版本已经完成统一执行和状态诊断,但复杂的 URL canonicalization、跨 Provider 去重、融合排序和域名多样性控制仍属于后续工作。</p><h3 id="3-3-超时现在真的会取消请求">3.3 超时现在真的会取消请求</h3><p>早期实现使用:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Promise</span>.<span class="title function_">race</span>([providerPromise, timeoutPromise])</span><br></pre></td></tr></table></figure><p>这只会让上层停止等待,底层 <code>fetch</code> 仍可能继续运行。</p><p>当前实现通过 <code>AbortController</code> 将 timeout 传入实际请求:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">达到超时</span><br><span class="line">→ signal.abort()</span><br><span class="line">→ Provider fetch 终止</span><br><span class="line">→ 记录 timeout</span><br><span class="line">→ 根据策略 retry 或 fallback</span><br></pre></td></tr></table></figure><p>测试会直接验证 Provider 能观察到:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">signal.<span class="property">aborted</span> === <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>这解决了 Agent 系统里很隐蔽的一类成本问题:</p><blockquote><p>表面已经超时,后台请求还在继续消耗额度。</p></blockquote><h3 id="3-4-结构化-ProviderError-与-Circuit-Breaker">3.4 结构化 <code>ProviderError</code> 与 Circuit Breaker</h3><p>Provider 不再只抛一个字符串,而是返回结构化错误:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">providerId</span><br><span class="line">error code</span><br><span class="line">HTTP status</span><br><span class="line">retryable</span><br><span class="line">shouldBreakerTrip</span><br><span class="line">Retry-After</span><br></pre></td></tr></table></figure><p>系统据此区分:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">401 auth_failed</span><br><span class="line">429 rate_limited</span><br><span class="line">500 server_error</span><br><span class="line">timeout</span><br><span class="line">aborted</span><br><span class="line">no_results</span><br><span class="line">parse_error</span><br></pre></td></tr></table></figure><p>并决定要不要重试、要不要 fallback、要不要切换凭据、要不要计入熔断器、是否需要等待、最终返回什么状态。</p><p>鉴权错误不应该当成瞬态服务故障记入熔断器。网络错误、超时和 5xx 才真正反映 Provider 健康状态。</p><p>熔断器有 closed、open、half-open 三种状态。失败达到阈值后:closed → open → 暂时跳过该 Provider → recovery timeout → half-open 试探 → 成功后 closed。</p><p>当前实现验证了调用级配置能真正进入 breaker–比如 <code>failureThreshold = 1</code>、<code>recoveryTimeout = 100ms</code>–不只是类型定义里有。</p><h3 id="3-5-结果契约与-AttemptRecord">3.5 结果契约与 AttemptRecord</h3><p>最终返回值不只包含结果,还包含完整的执行元数据:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">ReliableSearchResult</span> &#123;</span><br><span class="line">  <span class="attr">results</span>: <span class="title class_">UnifiedSearchResult</span>[];</span><br><span class="line">  <span class="attr">provider</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">providerPath</span>: <span class="built_in">string</span>[];</span><br><span class="line">  <span class="attr">fallbackReason</span>?: <span class="built_in">string</span>;</span><br><span class="line">  <span class="attr">attempts</span>: <span class="title class_">AttemptRecord</span>[];</span><br><span class="line">  <span class="attr">elapsedMs</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">retrievalSucceeded</span>: <span class="built_in">boolean</span>;</span><br><span class="line">  <span class="attr">usableForReview</span>: <span class="built_in">boolean</span>;</span><br><span class="line">  <span class="attr">resultStatus</span>:</span><br><span class="line">    | <span class="string">&quot;success&quot;</span></span><br><span class="line">    | <span class="string">&quot;partial&quot;</span></span><br><span class="line">    | <span class="string">&quot;no_results&quot;</span></span><br><span class="line">    | <span class="string">&quot;failed&quot;</span></span><br><span class="line">    | <span class="string">&quot;aborted&quot;</span>;</span><br><span class="line">  <span class="attr">cacheHit</span>: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每次尝试都会记录:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Provider</span><br><span class="line">Route</span><br><span class="line">凭据 profile</span><br><span class="line">状态</span><br><span class="line">耗时</span><br><span class="line">错误分类</span><br><span class="line">HTTP 状态</span><br><span class="line">结果数量</span><br></pre></td></tr></table></figure><p>例如:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;providerPath&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;tavily.default&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;providerId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tavily&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;routeId&quot;</span><span class="punctuation">:</span> <span class="string">&quot;tavily.default&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;credentialProfile&quot;</span><span class="punctuation">:</span> <span class="string">&quot;default&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;success&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;resultCount&quot;</span><span class="punctuation">:</span> <span class="number">5</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;resultStatus&quot;</span><span class="punctuation">:</span> <span class="string">&quot;success&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;retrievalSucceeded&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;usableForReview&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>这样一次搜索不只告诉调用者&quot;最后谁成功了&quot;,还还原了整条执行路径。</p><p>缓存命中也会明确返回:</p><figure class="highlight ts"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">cacheHit</span>: <span class="literal">true</span></span><br></pre></td></tr></table></figure><p>并保持和实时调用一致的主要字段,避免调用者因为结果来自缓存而拿到另一套残缺结构。</p><h3 id="3-6-发布包不只验证源码-而是验证真实安装路径">3.6 发布包不只验证源码,而是验证真实安装路径</h3><p>项目通过 <code>tsup</code> 同时输出:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ESM</span><br><span class="line">CommonJS</span><br><span class="line">TypeScript declarations</span><br></pre></td></tr></table></figure><p>发布前不仅执行源码测试,还验证:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">ESM import</span><br><span class="line">CJS require</span><br><span class="line">typecheck</span><br><span class="line">build</span><br><span class="line">npm pack</span><br><span class="line">packaged CLI</span><br><span class="line">MCP handshake</span><br></pre></td></tr></table></figure><p>v0.4.0 发布前完成:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">196 tests</span><br><span class="line">8 packaged smoke tests</span><br><span class="line">0 failures</span><br></pre></td></tr></table></figure><p>这些测试覆盖了真实行为：</p><ul><li>空结果会继续 fallback；</li><li>timeout 会真正 abort；</li><li>race winner 会取消 loser；</li><li>attempts 顺序稳定；</li><li>breaker 自定义配置真实生效；</li><li>cache hit 返回完整结构；</li><li>ESM/CJS 包可以真实加载；</li><li>打包后的 CLI 和 MCP Server 可以启动；</li><li>多凭据 route 能被 MCP 正确读取。</li></ul><hr><h2 id="4-v0-4-0-从-多个-Provider-走向-多个凭据、统一-CLI-与-MCP">4. v0.4.0:从&quot;多个 Provider&quot;走向&quot;多个凭据、统一 CLI 与 MCP&quot;</h2><p>到了 v0.4.0，问题从&quot;Provider A 挂了切换 Provider B&quot;变成了更贴近实际使用的场景：</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">同一个 Provider 可能有多个合法凭据；</span><br><span class="line">多个 Agent Host 需要共享同一个搜索入口；</span><br><span class="line">用户不应该手工修改 JSON 才能配置；</span><br><span class="line">环境变量、文件凭据和无 Key Provider 应该共存。</span><br></pre></td></tr></table></figure><p>因此,配置模型从:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Provider</span><br></pre></td></tr></table></figure><p>升级为:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Provider Definition</span><br><span class="line">→ Credential Profile</span><br><span class="line">→ Provider Route</span><br></pre></td></tr></table></figure><p>例如:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">tavily.default</span><br><span class="line">tavily.backup-2</span><br><span class="line">gemini.default</span><br><span class="line">duckduckgo</span><br></pre></td></tr></table></figure><h3 id="4-1-两层故障决策-credential-failover-与-provider-fallback">4.1 两层故障决策:credential failover 与 provider fallback</h3><p>v0.4.0 明确区分两层切换。</p><p>第一层是同一 Provider 内的凭据切换:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">tavily.default</span><br><span class="line">→ tavily.backup-2</span><br></pre></td></tr></table></figure><p>第二层是 Provider 级 fallback:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">tavily.*</span><br><span class="line">→ gemini.*</span><br><span class="line">→ duckduckgo</span><br></pre></td></tr></table></figure><p>规则如下:</p><table><thead><tr><th style="text-align:left">错误类型</th><th style="text-align:left">后续动作</th></tr></thead><tbody><tr><td style="text-align:left"><code>rate_limited</code></td><td style="text-align:left">尝试同 Provider 的下一个 credential</td></tr><tr><td style="text-align:left"><code>quota_exhausted</code></td><td style="text-align:left">尝试同 Provider 的下一个 credential</td></tr><tr><td style="text-align:left"><code>authentication_failure</code></td><td style="text-align:left">当前 credential 标记失败,尝试同 Provider 其他凭据</td></tr><tr><td style="text-align:left"><code>network_error</code></td><td style="text-align:left">按 retry/breaker 处理后切换 Provider</td></tr><tr><td style="text-align:left"><code>timeout</code></td><td style="text-align:left">按 retry/breaker 处理后切换 Provider</td></tr><tr><td style="text-align:left"><code>server_error</code></td><td style="text-align:left">按 retry/breaker 处理后切换 Provider</td></tr><tr><td style="text-align:left"><code>provider_overloaded</code></td><td style="text-align:left">按 retry/breaker 处理后切换 Provider</td></tr><tr><td style="text-align:left"><code>no_results</code></td><td style="text-align:left">直接切换 Provider</td></tr><tr><td style="text-align:left"><code>unusable_results</code></td><td style="text-align:left">直接切换 Provider</td></tr><tr><td style="text-align:left">用户取消</td><td style="text-align:left">立即停止</td></tr></tbody></table><p>这里有一个刻意的限制:</p><blockquote><p>默认实现 failover,不做 round-robin。</p></blockquote><p>多个凭据的目的是备用、合法的团队 workspace、Key rotation 和故障隔离,不是让系统无条件在不同账号之间轮询消耗额度。</p><h3 id="4-2-循环式-Setup-Wizard">4.2 循环式 Setup Wizard</h3><p>v0.3.0 的 <code>rws setup</code> 只能选择一个 Provider、输入一个 Key,然后结束。v0.4.0 改成循环式流程:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">添加 Provider</span><br><span class="line">→ 添加凭据</span><br><span class="line">→ 继续添加同 Provider 凭据</span><br><span class="line">→ 添加其他 Provider</span><br><span class="line">→ 查看和调整路由</span><br><span class="line">→ 完成配置</span><br><span class="line">→ 检测 Agent Host</span><br></pre></td></tr></table></figure><p>使用方式:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">npm install --global reliable-web-search</span><br><span class="line">rws setup</span><br></pre></td></tr></table></figure><p>在 macOS 或 Linux 上,如果系统全局目录无写权限,可以安装到用户目录:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm install --global \</span><br><span class="line">  --prefix <span class="string">&quot;<span class="variable">$HOME</span>/.local&quot;</span> \</span><br><span class="line">  reliable-web-search</span><br></pre></td></tr></table></figure><p>再把下面路径加入 <code>PATH</code>:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> PATH=<span class="string">&quot;<span class="variable">$HOME</span>/.local/bin:<span class="variable">$PATH</span>&quot;</span></span><br></pre></td></tr></table></figure><p>不需要使用:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo npm install -g</span><br></pre></td></tr></table></figure><p>向导能够连续添加:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">tavily.default</span><br><span class="line">tavily.backup-2</span><br><span class="line">gemini.default</span><br><span class="line">duckduckgo</span><br></pre></td></tr></table></figure><p>并在最终确认路由后,检测 OpenClaw、Codex、Claude Code 或 Generic MCP 环境。</p><h3 id="4-3-CLI-管理能力">4.3 CLI 管理能力</h3><p>除了 setup,v0.4.0 还提供独立管理命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">rws credentials list</span><br><span class="line">rws credentials add tavily --label backup</span><br><span class="line">rws credentials remove tavily.backup</span><br><span class="line">rws credentials <span class="built_in">enable</span> tavily.backup</span><br><span class="line">rws credentials <span class="built_in">disable</span> tavily.backup</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">rws routes list</span><br><span class="line">rws routes move tavily.backup --before gemini.default</span><br><span class="line">rws routes <span class="built_in">enable</span> tavily.backup</span><br><span class="line">rws routes <span class="built_in">disable</span> tavily.backup</span><br></pre></td></tr></table></figure><p>诊断命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">rws doctor</span><br><span class="line">rws doctor --live</span><br><span class="line">rws doctor --live --all-credentials</span><br></pre></td></tr></table></figure><p>其中:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">doctor</span><br><span class="line">= 检查配置、凭据引用、路由完整性和宿主环境</span><br><span class="line"></span><br><span class="line">doctor --live</span><br><span class="line">= 验证每个 Provider 的首选 credential</span><br><span class="line"></span><br><span class="line">doctor --live --all-credentials</span><br><span class="line">= 明确警告后逐个发起真实请求</span><br></pre></td></tr></table></figure><p>最后一个命令会消耗每个凭据的真实额度,因此不应作为日常检查默认执行。</p><h3 id="4-4-环境变量成为不落盘的临时路由">4.4 环境变量成为不落盘的临时路由</h3><p>如果环境中存在:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">export</span> TAVILY_API_KEY=<span class="string">&quot;...&quot;</span></span><br></pre></td></tr></table></figure><p>系统自动产生一个高优先级路由 <code>tavily.env</code>。它有这些特征:不写入 <code>config.json</code>,不写入 <code>credentials.json</code>,删除环境变量后自动消失,与本地相同 Key 去重,<code>routes list</code> 中标记为 <code>[env]</code>,不能通过 <code>credentials remove</code> 删除。</p><p>CI、服务器和临时开发环境可以直接注入 Key,无需改写本地凭据文件。</p><h3 id="4-5-凭据与配置分离">4.5 凭据与配置分离</h3><p>v0.4.0 将数据分开保存:<code>config.json</code> 放 routes、优先级、启停状态等非敏感配置;<code>credentials.json</code> 放 credential profiles 和 API Key。</p><p>安全约束包括:Unix 下 <code>credentials.json</code> 权限为 <code>0600</code>;API Key 不进入 Host MCP 配置、<code>AttemptRecord</code>、stdout、日志或诊断输出;setup 和 doctor 只显示 label 或掩码;v1 → v2 迁移会创建备份;损坏文件不会被自动覆盖。</p><h3 id="4-6-一个工具接入多个-Agent-Host">4.6 一个工具接入多个 Agent Host</h3><p>v0.3.0 开始,项目提供 stdio MCP Server:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">rws mcp</span><br></pre></td></tr></table></figure><p>Agent Host 只需要注册:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;command&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/path/to/rws&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;args&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;mcp&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>当前支持状态为:</p><table><thead><tr><th style="text-align:left">Host</th><th style="text-align:left">状态</th></tr></thead><tbody><tr><td style="text-align:left">OpenClaw</td><td style="text-align:left">Verified</td></tr><tr><td style="text-align:left">Generic MCP</td><td style="text-align:left">Standard MCP</td></tr><tr><td style="text-align:left">Codex</td><td style="text-align:left">Beta</td></tr><tr><td style="text-align:left">Claude Code</td><td style="text-align:left">Beta</td></tr></tbody></table><p>对 Agent 来说,始终只看到一个搜索工具:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">reliable_web_search</span><br></pre></td></tr></table></figure><p>底下配置了多少 Provider、多少凭据、是否发生 fallback,Agent 不需要关心具体接入细节,只需要读取结构化结果。</p><p>在 OpenClaw 实机验证中,新版 MCP 工具最终返回:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">providerPath: [&quot;tavily.default&quot;]</span><br><span class="line">routeId: tavily.default</span><br><span class="line">status: success</span><br><span class="line">credentialProfile: default</span><br></pre></td></tr></table></figure><p>这也暴露过一个迁移问题:旧版 OpenClaw 本地插件仍然注册了同名工具,导致模型先调用旧 Gemini 路由,再调用新版 MCP。最终通过禁用旧 <code>0.1.0</code> 插件、保留 <code>rws mcp</code> 注册,才让 OpenClaw 只剩新版工具。</p><p>这件事再次证明:</p><blockquote><p>工具发现、工具命名和运行时所有权,同样属于可靠性边界。</p></blockquote><hr><h2 id="5-这套系统解决了什么-又仍然没有解决什么">5. 这套系统解决了什么,又仍然没有解决什么</h2><p>从原型到 SDK,项目经历了三次明显变化。</p><h3 id="原型之前">原型之前</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">搜索成功</span><br><span class="line">→ 返回结果</span><br><span class="line"></span><br><span class="line">搜索失败</span><br><span class="line">→ 原因不清楚</span><br><span class="line">→ Agent 自己推断</span><br><span class="line">→ 可能重试</span><br><span class="line">→ 可能直接回答</span><br></pre></td></tr></table></figure><h3 id="OpenClaw-Runtime-Gate">OpenClaw Runtime Gate</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">Agent</span><br><span class="line">→ 参数清洗</span><br><span class="line">→ Gemini / Tavily</span><br><span class="line">→ 错误分类</span><br><span class="line">→ 有限重试与 fallback</span><br><span class="line">→ 标准化输出</span><br><span class="line">→ JSONL</span><br><span class="line">→ Evidence Review</span><br></pre></td></tr></table></figure><h3 id="当前通用运行时">当前通用运行时</h3><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Application / Agent</span><br><span class="line">→ Provider detection and priority</span><br><span class="line">→ Credential-aware routes</span><br><span class="line">→ Abortable timeout</span><br><span class="line">→ Retry / credential failover / provider fallback</span><br><span class="line">→ Race / aggregate</span><br><span class="line">→ Circuit breaker</span><br><span class="line">→ Result quality gate</span><br><span class="line">→ Stable attempt diagnostics</span><br><span class="line">→ CLI / MCP / SDK</span><br></pre></td></tr></table></figure><p>可以将这次演化总结为:</p><table><thead><tr><th style="text-align:left">第一阶段</th><th style="text-align:left">当前阶段</th></tr></thead><tbody><tr><td style="text-align:left">搜索工具 wrapper</td><td style="text-align:left">搜索可靠性运行时</td></tr><tr><td style="text-align:left">单一 OpenClaw 环境</td><td style="text-align:left">通用 Node.js SDK + CLI + MCP</td></tr><tr><td style="text-align:left">Gemini 为主、Tavily 兜底</td><td style="text-align:left">八个 Provider 的统一 registry</td></tr><tr><td style="text-align:left">单 Provider 单 Key</td><td style="text-align:left">多 Provider、多 credential profile</td></tr><tr><td style="text-align:left">普通错误分类</td><td style="text-align:left">结构化 <code>ProviderError</code></td></tr><tr><td style="text-align:left">上层 timeout</td><td style="text-align:left">底层真实取消</td></tr><tr><td style="text-align:left">简单 fallback</td><td style="text-align:left">fallback / race / aggregate</td></tr><tr><td style="text-align:left">JSONL 本地审计</td><td style="text-align:left">标准化 <code>AttemptRecord[]</code></td></tr><tr><td style="text-align:left">工具权限治理</td><td style="text-align:left">Provider、凭据与 Host 三层治理</td></tr><tr><td style="text-align:left">能搜索吗</td><td style="text-align:left">是否值得继续、切换或停止</td></tr></tbody></table><p>它仍然没有解决所有搜索问题。</p><p>当前版本还没有跨 Provider URL canonicalization、复杂重复结果融合、相关性重排序、域名多样性控制、round-robin 或健康权重、完整用量统计与成本预算、云端凭据管理、Web UI、通用 telemetry hooks。</p><p>这些能力都有价值,但不需要一次性全部实现。更合理的顺序是发布、实际使用、观察真实故障、根据数据决定下一步。</p><p>出现重复链接再做融合。多个 Agent 互相污染再引入更强实例隔离。费用失控再加预算治理。真实用户确实需要 GUI 再考虑 Web UI。</p><p>可靠系统不等于功能最多的系统,而是承诺过的行为都能被测试证明。</p><h3 id="最后真正学到的东西">最后真正学到的东西</h3><p>最消耗精力的不是写代码,是判断问题出在哪一层:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">代码错误?</span><br><span class="line">配置错误?</span><br><span class="line">Provider API 变化?</span><br><span class="line">Runtime 没有加载新构建?</span><br><span class="line">本地代理退出?</span><br><span class="line">请求被限流?</span><br><span class="line">响应 envelope 改变?</span><br><span class="line">旧插件和新 MCP 同时存在?</span><br><span class="line">旧日志和新日志混在一起?</span><br></pre></td></tr></table></figure><p>有几次看起来像&quot;刚修好又坏了&quot;。</p><p>一次是旧的 <code>transport_failure</code> 和新成功记录同时出现在 <code>tail</code> 里。一次是 wrapper 检查错了对象层级。一次是关代理窗口时顺手退出了后台进程。还有一次,新版 MCP 已经正常工作,但旧 OpenClaw 插件仍在抢占同名工具。</p><p>这些经历让我越来越确信:Agent 工程的核心不只是模型能不能完成任务,而是系统能不能准确描述自己当前的状态。</p><p>一个可靠的 Agent 不能只有&quot;成功&quot;和&quot;失败&quot;两种答案。</p><p>它至少应该知道这些:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">请求到底发出去了没有?</span><br><span class="line">发给了谁?</span><br><span class="line">使用了哪个 route 和 credential profile?</span><br><span class="line">响应结构是什么?</span><br><span class="line">系统是否读懂了响应?</span><br><span class="line">失败发生在哪一层?</span><br><span class="line">这个错误是否值得重试?</span><br><span class="line">要不要换凭据?</span><br><span class="line">要不要换 Provider?</span><br><span class="line">底层请求到底停了没有?</span><br><span class="line">当前结果能不能进入证据审查?</span><br><span class="line">整个过程是否留下了可追踪记录?</span><br></pre></td></tr></table></figure><p>这些问题都有明确答案,Agent 才开始从一个聊天界面变成一个可被信任的系统。</p><hr><h2 id="结语">结语</h2><p>开头只是想修好 OpenClaw 的联网搜索。</p><p>过程中绕过了代理、Tool Policy、插件加载、Runtime envelope、Provider 参数、重试循环、fallback、日志路径、Agent 权限、CLI 安装、MCP 注册和旧工具冲突。后来又把这些抽象成 registry、结构化错误、真实取消、race、熔断器、多凭据路由、稳定诊断和统一 Host 接入,最终发布成独立的 <code>reliable-web-search</code>。</p><p>回头看,真正建起来的不是&quot;永远不会失败的搜索工具&quot;:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">一个 Agent 绕不过去的治理入口</span><br><span class="line">一套能准确描述故障的状态语义</span><br><span class="line">一个超时后不会偷跑资源的执行层</span><br><span class="line">一条能根据错误选择 retry、credential failover、provider fallback 或停止的路由</span><br><span class="line">一份能回溯每次 Provider 尝试的诊断记录</span><br><span class="line">一个可以通过 CLI、MCP 和 SDK 复用的统一接口</span><br></pre></td></tr></table></figure><p>可靠性从来不是消灭失败。</p><p>可靠性是让失败拥有明确的类型、边界、证据和后续动作;让系统知道自己没拿到什么,也知道什么时候不该继续假装成功。</p><p>至少现在,某一个 Provider、某一把 Key,甚至某一条网络路径挂掉,都不再意味着整条搜索链路走到终点。</p><hr><p><a href="https://github.com/leecdiang/reliable-web-search/tree/main" target="_blank" class="glass-pill"><i class="fab fa-github"></i> leecdiang/reliable-web-search</a></p>]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/06/19/openclaw-reliable-search/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/06/19/openclaw-reliable-search/"/>
    <published>2026-06-18T16:05:00.000Z</published>
    <summary>从一次 OpenClaw 搜索故障出发,记录如何把参数治理、错误分类、有限重试、权限隔离和审计日志,逐步抽象为一个支持多 Provider、多凭据、真实取消、熔断、CLI 与 MCP 接入的搜索可靠性运行时。</summary>
    <title>让 Agent 正确地失败：从 OpenClaw 搜索治理到 reliable-web-search</title>
    <updated>2026-06-25T16:00:00.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Digital Anatomy" scheme="https://leecdiang.github.io/myblog-source/categories/Digital-Anatomy/"/>
    <category term="AI" scheme="https://leecdiang.github.io/myblog-source/tags/AI/"/>
    <category term="OpenClaw" scheme="https://leecdiang.github.io/myblog-source/tags/OpenClaw/"/>
    <category term="Agent" scheme="https://leecdiang.github.io/myblog-source/tags/Agent/"/>
    <category term="工作流" scheme="https://leecdiang.github.io/myblog-source/tags/%E5%B7%A5%E4%BD%9C%E6%B5%81/"/>
    <category term="思考" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%80%9D%E8%80%83/"/>
    <category term="DeepSeek" scheme="https://leecdiang.github.io/myblog-source/tags/DeepSeek/"/>
    <content>
      <![CDATA[<blockquote><p><strong>Abstract</strong><br>This post is not a tutorial on building an agent. It is a reflection on turning AI from a chat interface into a personal workflow system. Through the deployment of Main Agent, Code Lab, evidence tracking, approval queues, validators, and Obsidian dashboards, I gradually realized that the real value of an agent is not full automation, but structured collaboration. A useful agent does not replace human judgment; it participates in a workflow where facts are traceable, decisions are explicit, and failures can be detected.</p></blockquote><hr><h2 id="这不是一篇教程。">这不是一篇教程。</h2><p>我不想把这次部署包装成某种一键复刻的效率神话。如果只看最后的成品，它看起来很像一个&quot;AI 留学助手&quot; : 它会晨检，会查学校，会整理 deadline，会同步 Obsidian，还会把待审批事项写进 Mac 备忘录等等等。</p><p>但这次部署真正让我开心的，是我第一次比较完整地把一个 AI 工具放进了自己的长期工作流。它更像一个被约束、拆分、审计、校验、可视化之后的工作台，而不是一个能检索的聊天窗口。</p><p>回头看，这次我做的事情可以概括成一句话:</p><blockquote><p>我搭的不是 Agent，而是一套工作方式</p></blockquote><p>这套工作方式的核心是让 AI 参与我的长期任务: 它搜索、整理、生成候选项、提醒我、维护文件。但必须留下证据，接受校验，走审批流，也得承认失败——不能因为&quot;看起来完成了&quot;就被我信任。</p><p>这篇文章就是这次部署的复盘。</p><hr><h2 id="1-我不是在搭一个留学-Agent，而是在搭一个个人工作台">1. 我不是在搭一个留学 Agent，而是在搭一个个人工作台</h2><p>我确实有一个很具体的需求: 我正在准备未来的留学申请，需要管理学校列表、项目要求、APS、IELTS、GRE、推荐人、材料时间线、CV 和 SOP 等一堆东西。</p><p>这类任务麻烦在它不是一次性问答。</p><p>问&quot;某个学校的 GRE 要不要交&quot;，AI 很容易给出一个看起来有用的回答。但留学申请真正麻烦的地方在于这些答案会不断变化、互相关联，还会影响后续决策。</p><p>deadline 可能变，语言要求可能和学院要求不一样，官方页面可能更新，APS 审核政策可能有新程序。你的背景也会随着实习、项目、成绩、竞赛而变化。这些信息最终要进入一个长期决策系统: 哪些学校是 Reach，哪些是 Target，哪些是 Safety; 哪些信息已经核验，哪些只是低置信度; 哪些需要马上行动，哪些只是以后再看。</p><p>所以我慢慢意识到，除了尽早摆脱 Gemini 网页版等对话聊天助手，我需要一个能承载长期任务的个人工作台，而不是一个能回答留学问题的 Agent。选择转向 agent 的一大原因是最近被 Gemini 气的不行，觉得它的能力和效率满足不了我的需求。</p><p>这个工作台后来长成了这样的结构:</p><pre><code class="mermaid">graph TD    A[Personal Workbench]    A --&gt; B[Agent Layer]    B --&gt; B1[Main Agent]    B1 --&gt; B1a[长期规划 &#x2F; 晨检 &#x2F; 审批]    B1 --&gt; B1b[策略 &#x2F; 资料库维护]    B --&gt; B2[Code Lab]    B2 --&gt; B2a[代码 &#x2F; 脚本 &#x2F; Validator]    B2 --&gt; B2b[Obsidian 生成逻辑]    A --&gt; C[Evidence Layer]    C --&gt; C1[source]    C --&gt; C2[run]    C --&gt; C3[claim]    C --&gt; C4[approval]    A --&gt; D[Control Layer]    D --&gt; D1[Validator]    D1 --&gt; D1a[检查格式 &#x2F; 语义]    D1 --&gt; D1b[生成文件健康状态]    A --&gt; E[View Layer]    E --&gt; E1[Obsidian &#x2F; 可视化看板]    E --&gt; E2[Mac 备忘录 &#x2F; 轻提醒副本]    A --&gt; F[Source of Truth]    F --&gt; F1[OpenClaw Workspace]    F1 --&gt; F1a[事实源 &#x2F; 正式记录]</code></pre><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">System Structure</span><br><span class="line"></span><br><span class="line">Main Agent:长期规划、晨检、审批、策略、资料库维护</span><br><span class="line">Code Lab:代码项目、脚本、validator、Obsidian 生成逻辑</span><br><span class="line">Evidence Layer:source → run → claim → approval</span><br><span class="line">Validator:检查格式、语义和生成文件健康状态</span><br><span class="line">Obsidian:可视化看板</span><br><span class="line">Mac 备忘录:轻提醒副本</span><br><span class="line">OpenClaw workspace:事实源</span><br></pre></td></tr></table></figure><p>留学只是第一个被跑通的场景。更大的目标是: 把 AI 放进我的工程、学习、毕业设计、博客、申请这些长期任务之间，让它成为一个稳定的工作流节点，而不是一个随时失忆、随时飘走的聊天框。</p><hr><h2 id="2-为什么一个-Agent-不够-Main-Agent-与-Code-Lab-的分工">2. 为什么一个 Agent 不够:Main Agent 与 Code Lab 的分工</h2><p>为了在一开始确立正确的系统架构，我参考了两篇关于 single-agent 与 multi-agent 的论文，它们直接影响了我对这套系统的设计判断。</p><blockquote><p><strong>论文一:When Single-Agent with Skills Replace Multi-Agent Systems and When They Fail</strong><br>arXiv: <a href="https://arxiv.org/abs/2601.04748">https://arxiv.org/abs/2601.04748</a></p><p>这篇论文的核心是如果一个 multi-agent system 里的多个 agent 只是承担不同角色，并通过显式通信协作，那么这些角色可以被编译成一个 single-agent 内部可调用的多个 skills。论文的结论是: 在共享上下文、同一模型 backbone、任务流程可以序列化的情况下，single-agent with skills 往往可以用更低的 token、延迟和 API 调用成本，达到接近甚至相当的效果。但它也指出，skill library 不能无限扩张; 当 skills 数量过多，尤其是语义相似时，模型的 skill selection 会出现明显退化。论文中这个分界点大概在 20 个 skills，但具体分界点与不同 skill 易混淆程度和是否采用了正确的 skills  路由器有关。</p></blockquote><blockquote><p><strong>论文二:Single-Agent LLMs Outperform Multi-Agent Systems on Multi-Hop Reasoning Under Equal Thinking Token Budgets</strong><br>arXiv: <a href="https://arxiv.org/abs/2604.02460">https://arxiv.org/abs/2604.02460</a></p><p>这篇论文的核心观点是: 很多 multi-agent 系统看起来更强，可能只是因为它用了更多 test-time compute。多个 agent 轮流发言、互相总结、互相修正，本质上消耗了更多 thinking tokens、更多上下文传递和更多中间推理。当把 reasoning-token budget 控制到相同水平时，single-agent 在多跳推理任务中经常可以匹配甚至超过 multi-agent。这说明，multi-agent 的优势不应被默认归因于&quot;架构更聪明&quot;，而要先排除计算预算、上下文组织和通信开销带来的影响。</p></blockquote><p>这两篇论文放在一起，给了我一个部署原则:<strong>不要因为多 agent 看起来很厉害，就把系统拆成很多 agent。</strong></p><p>一开始我想的是：做一个留学 Agent、一个芯片 Agent、一个代码 Agent、一个博客 Agent。后面再按需部署更多。这种拆法按主题划分: 留学一类，数字 IC 一类，代码一类，博客一类。</p><p>但真正开始设计后我发现，这样切得太碎。</p><blockquote><p>主题不是最重要的边界，<strong>职责才是</strong>。</p></blockquote><p>按主题拆的话，&quot;博客 Agent&quot;既要写作、改 Hexo 主题、还要管理 Git; “留学 Agent” 既要做长期规划、搜索资料、整理文档、甚至改本地文件; “芯片 Agent” 既要讲知识，又要参与毕业设计文档管理。每个 agent 都要参与太多杂事，边界反而更模糊。</p><p>论文里关于 single-agent with skills 的观点解释了这个问题: 如果多个 agent 之间没有真正独立的状态、权限、工具链或决策目标，只是换几个角色说同一套话，那它们未必需要被拆开。很多时候让一个 agent 拥有少量边界清晰的 skills，比让一组 agent 互相转述上下文更合理。</p><p>但另一方面，我也没有让一个 Agent 什么都做。</p><p>因为在真实部署中，问题不只是推理能力，还有权限、风险和执行边界。一个负责长期规划和记忆的 agent，不应该同时拥有随手改代码、执行 shell、重写配置文件的能力。否则它一旦理解错上下文，就可能把策略判断和工程执行混在一起，造成很难追踪的副作用。</p><p>所以我最后没有按照主题拆分，而是按照职责和风险拆分。系统收敛成两个常驻角色:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Main Agent = 长期规划 / 策略 / 资料库 / 记忆 / 决策支持</span><br><span class="line">Code Lab = 工程执行 / 脚本 / 测试 / 本地工具维护</span><br></pre></td></tr></table></figure><p>Main Agent 是长期工作流中枢。它负责帮我理解目标、拆分路径、维护资料库、生成待办、管理审批。它可以处理留学规划、数字 IC 学习、毕业设计、实习项目分析、长期资料归档。它的价值在于&quot;想清楚&quot;和&quot;记下来&quot;。</p><p>更准确地说，Main Agent 承担的是 <strong>规划层、解释层和记忆层</strong> 的工作。它应该帮我回答:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">这个目标应该怎么拆？</span><br><span class="line">哪些信息值得长期保存？</span><br><span class="line">哪些任务应该交给 Code Lab 执行？</span><br><span class="line">哪些决策需要我本人确认？</span><br><span class="line">哪些资料还只是草稿，不能写入正式记录？</span><br></pre></td></tr></table></figure><p>但 Main Agent 不应该直接动工程。它不应该递归扫描代码仓库，不应该重写脚本，不应该随手改项目文件，也不应该把未经审批的信息写进正式资料库。它更像一个 PM、研究助理和长期笔记管理员的结合体。它可以提出方案、生成任务单、记录决策，但不应该越过边界直接执行危险操作。</p><hr><p>Code Lab 则是工程执行器。它负责 Apple-Health-Pro、Hexo 博客、Obsidian 同步脚本、validator、CSS、Git、测试和本地 shell。它可以改文件，可以跑命令，可以调试，可以修脚本。它的价值在于能够把明确的工程任务做完，并留下可验证的证据。</p><p>Code Lab 应该回答的是另一组问题:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">当前 Git 状态是什么？</span><br><span class="line">测试是否通过？</span><br><span class="line">哪个脚本失败了？</span><br><span class="line">改了哪些文件？</span><br><span class="line">diff 是否最小？</span><br><span class="line">能否复现问题？</span><br><span class="line">是否需要回滚？</span><br></pre></td></tr></table></figure><p>但 Code Lab 不应该替我做留学策略判断，也不应该把&quot;信息判断&quot;直接写成&quot;正式事实&quot;。它可以执行 Main Agent 派发的工程任务，也可以把执行结果反馈回来，但它不应该直接决定我的 APS 路线、学校优先级、申请策略或长期学习方向。</p><p>这层分工听起来有点形式主义，但它其实非常关键。</p><p>如果 Main Agent 同时负责策略判断和脚本维护，它会变成一个不受控的&quot;万能助手&quot;。它可能在晨检失败时顺手重写脚本，也可能在理解错上下文时污染正式文件。如果 Code Lab 也开始替我判断 APS 路线、学校优先级、申请策略，那代码执行和人生决策就混在一起了。</p><p>从 agent 架构角度看，这里需要隔离的是权限和风险，而不是&quot;知识领域&quot;:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">上下文隔离：长期规划和工程执行的上下文不要互相污染。</span><br><span class="line">权限隔离：能做策略判断的 agent，不必有本地执行权限。</span><br><span class="line">风险隔离：低风险的信息整理和高风险的文件修改，应该在不同边界里。</span><br></pre></td></tr></table></figure><p>这也是我最后没继续拆出&quot;留学 Agent / 芯片 Agent / 博客 Agent&quot;的原因。留学、芯片、博客这些主题，完全可以作为 Main Agent 内部的长期 skill 或资料库; 代码修改、脚本调试、Git 操作、本地测试这些，交给 Code Lab。前者是认知和记忆问题，后者是工程和执行问题。</p><p>最后我的结论是:</p><blockquote><p>一个 Agent 不够，是因为不同职责需要边界;<br>太多 Agent 也不好，因为过度拆分制造通信成本、上下文损耗和选择混乱。</p><p>Agent 越强，越需要边界。<br>分工不是为了复杂，而是为了降低混乱。</p></blockquote><p>对我来说，Main Agent 和 Code Lab 的分工刚好卡在中间。Main Agent 负责长期记忆、策略判断和任务分解;Code Lab 负责本地工程、代码修改和测试验证。按权限、风险和职责隔离，而不是按主题分裂。</p><hr><h2 id="3-为什么光有记忆不够-事实源、证据链和审批流">3. 为什么光有记忆不够: 事实源、证据链和审批流</h2><p>AI 的&quot;记忆&quot;很诱人——它似乎可以记住背景、偏好、项目、学校列表和长期目标。但在长期任务里，记忆不能当事实源。</p><p>它能提供上下文，但不能直接作为依据。它记得我说过&quot;Purdue GRE 可能 optional&quot;，但不能因为记得，就把这条写进正式 tracker。事实必须有来源、有时间、有置信度、有核验日期、有变更记录。</p><p>所以我们建立了一层 evidence layer，由四个 JSONL 文件构成:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">source_registry.jsonl</span><br><span class="line">retrieval_runs.jsonl</span><br><span class="line">fact_claims.jsonl</span><br><span class="line">approval_queue.jsonl</span><br></pre></td></tr></table></figure><p><code>source_registry.jsonl</code> 记录来源本身:URL、标题、来源类型、检索时间、置信度。一个来源是官方页面、论坛帖子、项目手册还是非正式总结，意义完全不同。</p><p><code>retrieval_runs.jsonl</code> 记录每次查了什么:query 是什么，用了什么工具，目的是什么，结论是什么。这解决的是&quot;我到底有没有查过&quot;这个问题。</p><p><code>fact_claims.jsonl</code> 记录具体事实声明。比如某个项目 GRE optional、某个项目 IELTS writing 要求、某个 deadline 是估算还是官方确认。它把&quot;资料&quot;拆成可追溯的 claim。</p><p><code>approval_queue.jsonl</code> 记录这些信息是否需要我介入。确认某条信息无变化时用 <code>no_action_required</code>。需要我行动是 <code>pending</code>。信息不足是 <code>needs_more_research</code>。路线选择——比如 APS 走 TestAS 还是标准面谈——是 <code>decision_item</code>，必须等我明确选。</p><hr><p>第一次测试 Purdue GRE 政策时，Main Agent 口头说已经记录了 retrieval run、注册了 source、生成了 claim、写入了 approval queue。但 validator 一跑，四个 JSONL 文件全是空的。</p><p>如果没有 evidence layer，我可能以为晨检成功了。实际上它只更新了 human-readable summary，没形成机器可读的审计记录。这就是为什么后来所有晨检必须写入 JSONL。</p><p>还有一次，<code>approval_queue</code> 里本来要写&quot;核验无变化，无需审批&quot;，但 validator 不接受 <code>no_action_required</code>。Agent 为了让 validator PASS，改成了 <code>rejected</code>。</p><p>技术上通过了，语义上错了。</p><p>这说明 validator 不能只检查格式，还要保护语义。<code>rejected</code> 只能表示我明确拒绝，不能代替&quot;无需改动&quot;。否则以后复盘，系统会以为我拒绝过某个事实更新。</p><p>于是我们补齐了 status 语义:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">pending - 等待我审批</span><br><span class="line">approved - 我已批准,但尚未应用</span><br><span class="line">applied - 已写入正式 tracker / checklist / timeline</span><br><span class="line">rejected - 只有我明确拒绝时使用</span><br><span class="line">needs_more_research - 信息不足,不能写入正式 tracker</span><br><span class="line">no_action_required - 核验无变化,无需人工审批</span><br></pre></td></tr></table></figure><blockquote><p>AI 可以发现问题，但不能替我承担选择。<br>它可以生成 action item，但 decision item 必须留给我。<br>它可以干活，但必须留下证据。</p></blockquote><hr><h2 id="4-为什么光有-UI-不够-Obsidian-只是视图，不是系统本体">4. 为什么光有 UI 不够:Obsidian 只是视图，不是系统本体</h2><p>这次部署里，Obsidian 是很重要的一环。它让整个系统终于&quot;看得见&quot;。</p><p>我最终有了这些页面:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">Dashboard - Overview</span><br><span class="line">Dashboard - Timeline</span><br><span class="line">Dashboard - By Tier</span><br><span class="line">Dashboard - Pending Approval</span><br><span class="line">Board - Application Status</span><br><span class="line">Schools/</span><br></pre></td></tr></table></figure><p><img data-src="%E6%88%91%E6%90%AD%E7%9A%84%E4%B8%8D%E6%98%AFAgent%E8%80%8C%E6%98%AF%E4%B8%80%E5%A5%97%E5%B7%A5%E4%BD%9C%E6%96%B9%E5%BC%8F-20260612-002150.png" alt=""></p><p>Overview 是总控台，告诉我当前阶段、待审批、时间压力和下一步重点。Timeline 看 deadline 和时间顺序。By Tier 看选校梯度。Pending Approval 看待我确认的事项。Kanban 看申请流程状态:Research、Needs Verification、Shortlist、Preparing、Applying、Submitted、Archived。Schools 下面则是每个项目的单独页面。</p><p>现在我的理解:</p><ul><li>Timeline 看时间压力</li><li>Kanban 看流程状态</li><li>By Tier 看选校结构</li><li>Overview 看今天该做什么</li><li>Pending Approval 看等待我决定的事</li></ul><p>别把所有信息塞进一个页面，也别幻想一个 Dashboard 解决所有问题。</p><p>更重要的是，Obsidian 不是事实源。</p><p>这点非常关键。Obsidian 再漂亮，也只是生成视图。Mac 备忘录再方便，也只是提醒副本。真正的事实源在 OpenClaw workspace 里，在 CSV、Markdown、JSONL、approval log、change log 里。</p><p>这层分离很重要，因为 UI 很容易让人产生错觉。一个页面看起来很完整，不代表系统可靠。一个看板颜色很漂亮，不代表背后的数据经过核验。一个学校卡片里有 deadline，不代表这个 deadline 一定来自官方来源。</p><p>所以我的原则变成:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">OpenClaw workspace = 事实源</span><br><span class="line">Obsidian = 可视化看板 / 阅读副本</span><br><span class="line">Mac Notes = 轻提醒副本</span><br><span class="line">Excel/CSV = 结构化数据和快照</span><br></pre></td></tr></table></figure><p>我可以在 Obsidian 里理解系统，但不应该绕过系统直接改事实。下一次同步时，这些手动修改也可能被覆盖。</p><blockquote><p>一个好看的 Dashboard 不等于一个可靠的系统。<br>Dashboard 只是仪表，事实源才是本体。</p></blockquote><hr><h2 id="5-为什么模型不够聪明也能用-用-validator、脚本和流程约束模型">5. 为什么模型不够聪明也能用: 用 validator、脚本和流程约束模型</h2><p>这次部署让我对模型能力有了更真实的感受。先对比下各家当前的 api 价格。</p><p><img data-src="%E6%88%91%E6%90%AD%E7%9A%84%E4%B8%8D%E6%98%AFAgent%E8%80%8C%E6%98%AF%E4%B8%80%E5%A5%97%E5%B7%A5%E4%BD%9C%E6%96%B9%E5%BC%8F-20260612-002215.png" alt=""></p><p>DeepSeek V4 Flash 便宜、快、上下文长、日常可用，但在复杂 agent 工作流里并不省心，经常要多改几次。没有多模态能力，所以识图、记忆检索、联网搜索等场景我接入了 Gemini 的 api，免费额度在这些场景基本用不完。</p><p>对于 DeepSeek，尤其是当任务变成多步骤执行时，它会犯一些经典错误:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">口头说完成，但实际没写 JSONL</span><br><span class="line">工具报错后仍然汇报&quot;写入完成&quot;</span><br><span class="line">把 rejected 当成 no_action_required</span><br><span class="line">忘记脚本路径</span><br><span class="line">用 fragile exact oldText 替换文件，结果失败</span><br><span class="line">同步看板时写出 \n 或 \u000 这类转义污染</span><br></pre></td></tr></table></figure><p>模型能力不够强，所以系统必须足够完善来包容这些不完美。</p><p>以 <code>DAILY_BRIEF.md</code> 的更新逻辑为例：</p><p>开始时，Main Agent 用精确文本替换更新每日简报:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">edit(oldText=..., newText=...)</span><br></pre></td></tr></table></figure><p>这个方式鲁棒性很弱：日期、空格、emoji、换行，有一点点不同就失败。实际也确实失败了——它以为文件里是 2026-06-11 的版本，真实文件还是 2026-06-09 的，oldText 匹配不上。更糟的是，工具已经报错，它还是口头说&quot;写入完成&quot;。</p><p>后来我们把它改成 marker-based update:</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&lt;!-- DAILY<span class="emphasis">_BRIEF:BEGIN --&gt;</span></span><br><span class="line"><span class="emphasis">这里是最新晨检摘要</span></span><br><span class="line"><span class="emphasis">&lt;!-- DAILY_</span>BRIEF:END --&gt;</span><br></pre></td></tr></table></figure><p>然后写了 <code>update_daily_brief.py</code>，以后只替换 marker 之间的内容。更新后必须读取前 80 行确认，确认成功后才能汇报完成。</p><p>类似的防线还有 validator。它检查 JSONL 格式、字段是否完整、status 是否符合语义、Obsidian 生成文件是否有坏转义、Kanban 是否出现错误列、学校页面 frontmatter 是否包含必要字段。不是为了显得高级，而是防止 Agent 口头完成、实际失败。</p><blockquote><p>当模型不够可靠时，不要要求它&quot;更自觉&quot;。<br>要把它放进流程里，让它必须留下证据，必须通过校验，必须承认失败。</p></blockquote><hr><h2 id="6-留学申请只是第一个被跑通的场景">6. 留学申请只是第一个被跑通的场景</h2><p>虽然这次最完整跑通的是留学申请，但我不想把这套系统定义成&quot;留学 Agent&quot;。</p><p>留学只是一个足够复杂、真实、容易出错的验证场景。有信息检索、官方来源、时间线、材料清单、个人决策、审批、可视化、长期维护。用它来测试这套工作流，很合适。</p><p>最后我们把&quot;执行留学晨检&quot;固化成了一个 skill。它不再是一个模糊指令，而是一套明确的 11 步工作流:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">写入 retrieval_runs.jsonl</span><br><span class="line">写入 source_registry.jsonl</span><br><span class="line">写入 fact_claims.jsonl</span><br><span class="line">写入 approval_queue.jsonl</span><br><span class="line">用 update_daily_brief.py 更新 DAILY_BRIEF.md</span><br><span class="line">读取前 80 行确认</span><br><span class="line">更新 PENDING_APPROVAL.md</span><br><span class="line">跑 validator</span><br><span class="line">同步 Mac 备忘录</span><br><span class="line">同步 Obsidian</span><br><span class="line">汇报全部结果</span><br></pre></td></tr></table></figure><p>而且我们明确规定:SCHOOL_LIST 只读不改，除非我专门审批通过。validator FAIL 必须修复，不能跳过。脚本路径固定，Main Agent 只调用，不重写。<code>rejected</code> 只能表示我明确拒绝，<code>no_action_required</code> 才表示核验无变化。</p><p>这套流程后来跑通了几个真实测试:</p><ul><li><p>Purdue GRE policy: 核验无变化，进入 <code>no_action_required</code>。</p></li><li><p>Purdue IELTS 小分: 确认 W6.0 / S6.0 满足要求，写入 evidence。</p></li><li><p>APS 审核: 生成了 action_item 和 decision_item。比如材料清单需要补充，TestAS 060 vs 标准面谈则需要我自己决策。</p></li></ul><p>这证明系统能处理&quot;无变化的事实核验&quot;，也能处理&quot;需要行动&quot;和&quot;需要决策&quot;的事项。</p><p>但它真正的意义不在于 Purdue，也不在于 APS。意义在于: 这套工作流可以迁移。</p><p>它以后可以服务我的工程项目，可以服务毕业设计，可以服务数字 IC 学习路径，可以服务博客内容管理，也可以服务实习项目分析。留学申请只是第一块被跑通的样板间。</p><hr><h2 id="7-接下来它会服务工程、博客、毕业设计和长期学习">7. 接下来它会服务工程、博客、毕业设计和长期学习</h2><p>现在系统基本成型，我反而应该少折腾、多使用。</p><p>这也是这次部署最后一个重要结论。</p><p>系统搭到一定程度后容易陷入另一种陷阱: 继续加功能，继续调 UI，想接飞书、Telegram、iMessage、日历插件、全自动监控、更多 Agent、更多 Dashboard。每个功能看起来都合理，但也会把精力从真正的工作上带走。</p><p>现在我更愿意把这套系统看成一个阶段性可用的工作台。接下来它要服务具体产出。</p><p>在工程上，Code Lab 会继续维护 Apple-Health-Pro、Hexo 博客和各种同步脚本。规则也很明确: 先 <code>git status</code>，不要递归扫描，每轮最多读几个文件、跑几条命令，小步修改，小步提交。Code Lab 是工程执行器，不是人生规划师。</p><p>在留学上，Main Agent 会继续按 evidence-first workflow 执行晨检。它要关注 APS、推荐人、High priority 项目、IELTS/GRE/deadline 核验、CV_FACTS、SOP 主线等。但它不能越过审批流。</p><p>在毕业设计和学习上，Main Agent 更适合做长期路径规划和资料整理。比如数字 IC 学习路线、实习项目理解、毕业设计拆解，这些应该变成长期资料库和阶段性决策，而不是一次性问答。</p><p>在博客上，这套系统也会继续有用。Code Lab 可以维护 Hexo 和样式，Main Agent 可以帮我梳理文章结构、复盘经验、保存主题线索。但最终表达仍然是我的。</p><p>我现在对这套系统的理想使用方式是:</p><figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Main Agent：帮我想清楚、记下来、形成决策流</span><br><span class="line">Code Lab：帮我做出来、跑起来、修到稳定</span><br><span class="line">Obsidian：让我看见</span><br><span class="line">Evidence Layer：让一切有迹可循</span><br><span class="line">Validator：让错误尽早暴露</span><br></pre></td></tr></table></figure><p>这不是什么&quot;全自动 AI 人生操作系统&quot;。它更像一套被我慢慢磨出来的个人工作方式。</p><hr><h2 id="结尾-把-AI-放回工作流里">结尾: 把 AI 放回工作流里</h2><p>回头看，这次部署让我最开心的是：我终于把 AI 放回了工作流里，而不是只&quot;拥有了一个 Agent&quot;。</p><p>以前我对 AI 工具的使用，更多是对话式的: 我问，它答; 我再问，它再答。这个模式很轻，很快，也很有用。但它不适合承载长期任务。长期任务需要状态，需要边界，需要历史，需要审计，需要可视化，需要失败处理，也需要人的决策位置。</p><p>现在这套系统仍然不完美。模型会犯错，脚本可能出 bug，Obsidian 看板以后也许还会坏，Main Agent 也可能偶尔忘记规则。但不同的是，错误不再完全漂浮在聊天里——它们会被 validator 发现，被 approval queue 拦住，被 evidence layer 留痕，被 Code Lab 修复。</p><p>我对 Agent 的理解也变得更具体了。</p><p>Agent 是我能组织进流程的执行者，不是一个替我生活的东西或永远正确的答案机器。它很有用，但我得知道什么该交给它、什么必须留给我。</p><blockquote><p>让 AI 参与我的长期任务，但不让它越过边界;<br>让它替我处理重复劳动，但不替我承担判断;<br>让它生成信息，但必须留下证据;<br>让它执行流程，但必须接受校验。</p></blockquote><p>如果你的部署遇到什么问题，可以写邮件给我，我们一起解决。</p><hr><h2 id="TL-DR">TL;DR</h2><p>I was building a workflow, not a single agent.</p><p>The key: keep AI accountable rather than just autonomous.<br>Separate planning from execution, keep evidence trails, add approval gates, validate outputs, treat dashboards as views—not sources of truth.</p><p>A useful agent is one that can safely participate in my long-term work, not one that does everything for me.</p>]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/06/11/not-an-agent-but-a-workflow/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/06/11/not-an-agent-but-a-workflow/"/>
    <published>2026-06-11T05:33:00.000Z</published>
    <summary>关于 Main Agent、Code Lab、证据链、审批流,以及如何让一个不完美的模型参与长期任务。</summary>
    <title>我搭的不是 Agent,而是一套工作方式</title>
    <updated>2026-06-14T16:22:59.694Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Visual Narrative" scheme="https://leecdiang.github.io/myblog-source/categories/Visual-Narrative/"/>
    <category term="胶片" scheme="https://leecdiang.github.io/myblog-source/tags/%E8%83%B6%E7%89%87/"/>
    <category term="摄影" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%91%84%E5%BD%B1/"/>
    <category term="旅行" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%97%85%E8%A1%8C/"/>
    <category term="西北" scheme="https://leecdiang.github.io/myblog-source/tags/%E8%A5%BF%E5%8C%97/"/>
    <category term="青甘大环线" scheme="https://leecdiang.github.io/myblog-source/tags/%E9%9D%92%E7%94%98%E5%A4%A7%E7%8E%AF%E7%BA%BF/"/>
    <content>
      <![CDATA[<blockquote><p>青甘之所以吸引我，是这一路集齐了雪山、古迹、沙漠、戈壁、丹霞、草原、石窟。</p><p>这不是一条单纯看风景的路线。它更像一次穿越：从湿润的盆地走向高原，从湖水走向荒漠，从宗教、边塞、丝路与工业遗迹之间穿过。一路上，山河并不温顺，历史也并不遥远。它们共同构成了青甘大环线最迷人的地方——辽阔、粗粝、孤独，却又壮丽得近乎慷慨。</p></blockquote><p><img data-src="000008-2.jpg" alt=""></p><p>路线：</p><p>成都-兰州–西宁-塔尔寺-拉脊山-二郎剑-德令哈-格尔木-可可西里-格尔木-敦煌-嘉峪关-张掖-西宁-兰州</p><p><img data-src="eight-thousand-miles-route.png" alt=""></p><hr><h2 id="出发">出发</h2><p>成都出发，在硬卧上摇了一夜，不知道是车子年久失修还是铁路老化，这一趟绿皮很颠很吵，第二天早上七点到了兰州。</p><p><img data-src="eight-thousand-miles-20260606-1.jpg" alt=""></p><p>在兰州站拿到车，我们去吃了一碗牛肉面，选了最妥当的二细。</p><p><img data-src="eight-thousand-miles-20260606-2.jpg" alt=""></p><hr><h2 id="西宁">西宁</h2><p>吃饱后一路开，中午就到了西宁。吃了羊肉和抓饭，下午去看了塔尔寺的香火、白塔、转经筒…</p><p><img data-src="eight-thousand-miles-20260606.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">塔尔寺</p><hr><h2 id="青海湖">青海湖</h2><p>开到湖边的时候正好是日落。吃了一盆很香的羊肉炕锅。</p><p>第二天起的很早，但也遗憾没有看到日出，于是就沿着湖一路往下开。</p><p><img data-src="eight-thousand-miles-20260606-3.jpg" alt=""></p><p><img data-src="eight-thousand-miles-20260606-4.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">青海湖边的大羊小羊</p><hr><h2 id="德令哈">德令哈</h2><p>开到德令哈的时候已经是下午两三点，吃了特别满足的一顿牦牛退骨肉，用小刀剃下牛骨上的嫩肉，蘸辣子吃。退骨的意思是：吃之前称骨头带肉重量，你啃干净后再称一次，用重量差乘以肉价算钱</p><p><img data-src="eight-thousand-miles-20260606-6.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">超香的退骨牦牛肉</p><p>趁着天还没黑，去看了德令哈市区的”光棱塔“，全名是塔式熔盐储能光热项目，肉眼看很震撼。</p><p><img data-src="eight-thousand-miles-20260606-7.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">光棱塔</p><p>在西北玩最大的红利是天黑的晚，八九点天都亮着，很方便赶路和多去几个景点，我们一路开上了柏树山垭口，再往下开就是去哈拉湖的穿越线路了，我们的车开不过去。</p><p><img data-src="eight-thousand-miles-20260606-8.jpg" alt=""></p><p><img data-src="eight-thousand-miles-20260606-10.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">沿着一直开就是哈拉湖</p><p>柏树山，顾名思义就是一座长满柏树的山。</p><p><img data-src="eight-thousand-miles-20260609.jpg" alt=""></p><p>在垭口上待到了日落</p><p><img data-src="eight-thousand-miles-20260606-11.jpg" alt=""></p><p>下山后，再往前就是德令哈的黑石山水库，可惜去水库边的路被封死了。</p><p><img data-src="eight-thousand-miles-20260606-12.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">黑石山水库和远处的山脉</p><hr><h2 id="托素湖">托素湖</h2><p>过了德令哈，绿色消失了，本该直接朝格尔木出发，但刷到说托素湖自然保护区开放进入了，于是离开德令哈的早晨我们先朝托素湖方向驶去，保护站登记了身份证和车牌就可以直接开到湖边。</p><p><img data-src="eight-thousand-miles-20260606-13.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">托素湖边</p><p>据说托素湖最美的地方还要离开主路，沿湖走四公里才能到达，我们头铁了一把，就这样把岚图当越野车开，往沙地里钻。</p><p>实在是低估了新能源的车重，我们的车在越野路线开头的沙地就托底陷车了😭 被困了一个多小时，我就这样在海拔三千多的湖边趴在地上刨土，把车底托住的地方刨开，在一辆从上海来的坦克 300 的帮助下，我们把车子救了出来。</p><p><img data-src="eight-thousand-miles-20260606-15.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">救援中</p><p>把车子开回堤上，我们实在不敢再做尝试，于是搭了当地人的普拉多深入湖岸</p><p><img data-src="eight-thousand-miles-20260606-16.jpg" alt=""></p><hr><h2 id="察尔汗盐湖">察尔汗盐湖</h2><p>我们对人多的景区感到乏味，一路上跳过了青海湖的所有景区、茶卡盐湖、大小柴旦和翡翠湖，想着就挑一个湖去，选择了察尔汗盐湖，但却很失望：天气不好，但更多是被景区的各种人造景观恶心到了，各种土味打卡点层出不穷，每走几步就会有人上来问你要不要旅拍，要不要无人机航拍🤦‍♂️</p><p><img data-src="eight-thousand-miles-20260606-17.jpg" alt=""></p><p>盐湖是死去的海，景区体验虽然糟糕，但很难掩住湖水本身的颜色，也很难掩住这片盐湖孕育出的工业，这属于自然，矿车、盐田和人的美。</p><p><img data-src="eight-thousand-miles-20260607-5.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">察尔汗盐湖的工业区</p><hr><h2 id="可可西里">可可西里</h2><p>在格尔木住两晚，其中一天从 109 国道出发，前往可可西里，由于当日往返，我们只走到了索南达杰保护站。一路上看到了藏野驴、藏羚羊，但可惜没看到狼和岩羊。</p><p><img data-src="eight-thousand-miles-20260607.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">青藏铁路和昆仑山脉</p><p><img data-src="eight-thousand-miles-20260609.png" alt=""></p><p><img data-src="eight-thousand-miles-20260607-7.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">无极龙凤宫，传说中姜子牙飞升的道场</p><p><img data-src="eight-thousand-miles-wx-20260609.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">藏野驴</p><p><img data-src="eight-thousand-miles-20260609-1.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">藏羚羊</p><hr><h2 id="格尔木-敦煌">格尔木-敦煌</h2><p>路程最远的一天，开了将近八百公里。途经冷湖石油小镇、黑独山。本来有计划往火星一号公路里开，但前一天在 109 国道上颠都够呛，就不想再走烂路了。</p><p><img data-src="eight-thousand-miles-20260607-3.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">石油小镇，随着石油枯竭而荒废</p><p><img data-src="eight-thousand-miles-wx-20260609-1.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">黑独山</p><hr><h2 id="敦煌">敦煌</h2><p>没有抢到莫高窟的普票，早上吃了一碗羊肉合汁后，选择去了敦煌博物馆，看到了莫高窟的复制窟和不少文物如各种镇墓兽、画像砖以及莫高窟藏经洞中出土的经书典籍。中午吃了一份胡羊焖饼，配上沙葱炒蛋和杏皮茶，绝了。</p><p><img data-src="eight-thousand-miles-20260607-9.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">玉门关的汉长城遗址</p><p><img data-src="eight-thousand-miles-20260609-2.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">河仓城，西汉粮仓</p><p><img data-src="eight-thousand-miles-wx-20260609-2.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">河仓城外的沼泽滩</p><p>从玉门关回来，晚上去了敦煌夜市/沙州夜市，继续大快朵颐。</p><hr><h2 id="敦煌-嘉峪关">敦煌-嘉峪关</h2><p>从敦煌出发，我们去了榆林窟。从洞窟形式、表现内容和艺术风格看，榆林窟与莫高窟相似度高，是莫高窟艺术系统的一个分支，但可惜大部分的塑像都是清代重塑的，技艺水平很低，甚至有点辣眼睛，但好在壁画保存完整，我们进了特窟 3 号窟，有两幅西夏大幅水墨山水画的《文殊变》，《普贤变》，真是大饱眼福。</p><p><img data-src="eight-thousand-miles-20260607-1.png" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">图源：数字敦煌</p><p>参观完榆林窟已经是下午，我们在瓜州尝了骆驼肉，便继续往嘉峪关开。</p><p><img data-src="000022.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">嘉峪关上远眺祁连山脉</p><p><img data-src="000023.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">骆驼们在午休，远处有人 cos 大将军</p><hr><h2 id="嘉峪关-西宁">嘉峪关-西宁</h2><p><img data-src="eight-thousand-miles-20260607-13.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">张掖·七彩丹霞</p><p><img data-src="000015.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">路上</p><p><img data-src="000027.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">祁连草原上的小羊</p><p>到了西宁后，马不停蹄往西宁野生动物园，赶在闭园前入园了。当天西宁在下小雨，但我们运气还不错，看到了雪豹，猞猁还有荒漠猫，但可惜兔狲正在疗养期间，没有上班。</p><p><img data-src="IMG_8968.png" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">雪豹</p><p><img data-src="eight-thousand-miles-20260607-3.png" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">猞猁</p><p><img data-src="000029.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">南山公园·凤凰台</p><p><img data-src="000028.jpg" alt=""></p><hr><h2 id="兰州">兰州</h2><p>四千公里的舟车劳顿，终于回到兰州时候已经精疲力尽了，在兰州的一天又吃了两顿牛肉面，试了下韭叶、二柱子。整个下午我们都坐在黄河边上，喝着三炮台吹风聊天。</p><p>兰州是一个可以多待几天好好走一走的城市，下次要把所有粗细的牛肉面都吃满。</p><p><img data-src="eight-thousand-miles-20260607-18.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">黄河滩</p><p><img data-src="eight-thousand-miles-20260607-19.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">中山桥上</p><p><img data-src="eight-thousand-miles-20260607-21.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">喝茶中</p><p><img data-src="eight-thousand-miles-20260607-22.jpg" alt=""></p><p align="center" style="font-size: 0.85em; color: #888;">黄河边的茶摊</p><hr><p><em>2026.06 · 青甘大环线 · 胶片记录</em></p>]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/06/06/eight-thousand-miles/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/06/06/eight-thousand-miles/"/>
    <published>2026-06-05T17:00:00.000Z</published>
    <summary>两卷胶片走完青甘大环线。</summary>
    <title>八千里路</title>
    <updated>2026-06-12T07:17:30.947Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Visual Narrative" scheme="https://leecdiang.github.io/myblog-source/categories/Visual-Narrative/"/>
    <category term="胶片" scheme="https://leecdiang.github.io/myblog-source/tags/%E8%83%B6%E7%89%87/"/>
    <category term="摄影" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%91%84%E5%BD%B1/"/>
    <category term="旅行" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%97%85%E8%A1%8C/"/>
    <category term="西北" scheme="https://leecdiang.github.io/myblog-source/tags/%E8%A5%BF%E5%8C%97/"/>
    <category term="宝丽来" scheme="https://leecdiang.github.io/myblog-source/tags/%E5%AE%9D%E4%B8%BD%E6%9D%A5/"/>
    <content>
      <![CDATA[<p><img data-src="polaroid-636-northwest-20260604.png" alt=""><br><img data-src="polaroid-636-northwest-20260604-1.png" alt=""><br>《白塔》  西宁，塔尔寺</p><p><img data-src="polaroid-636-northwest-20260604-2.png" alt=""><br>青海湖二郎剑附近的湖滩，可惜相纸漏液了</p><p><img data-src="polaroid-636-northwest-20260604-3.png" alt=""><br>德令哈的光棱塔，学名是太阳能熔盐塔式光热电站</p><p><img data-src="polaroid-636-northwest-20260604-4.png" alt=""><br>托素湖，我们的岚图 free 在湖边陷车，困了一个多小时</p><p><img data-src="polaroid-636-northwest-20260604-5.png" alt=""><br>柏树山，因长满柏树得名，山清水秀</p><p><img data-src="polaroid-636-northwest-20260604-6.png" alt=""><br>冷湖石油小镇，因曾经发现石油兴建，枯竭后又迅速消失在荒漠</p><p><img data-src="polaroid-636-northwest-20260604-7.png" alt=""><br>黑独山，确实是独特的风景，风沙很大</p><p><img data-src="polaroid-636-northwest-20260604-9.png" alt=""><br>无极龙凤宫，背靠昆仑山，传说中姜子牙飞升的地方</p><p><img data-src="polaroid-636-northwest-20260604-8.png" alt=""><br>汉长城的烽火燧，于玉门关</p><p><img data-src="polaroid-636-northwest-20260604-10.png" alt=""><br>张掖，七彩丹霞景区，没能拍出丰富的颜色</p><p><img data-src="polaroid-636-northwest-20260604-11.png" alt=""><br>西宁，凤凰台。出发时路过了图中的电视塔，于是决定大环线回来一定要拍拍</p><p><img data-src="polaroid-636-northwest-20260604-12.png" alt=""></p><p><img data-src="polaroid-636-northwest-20260604-13.png" alt=""><br>西宁野生动物园的雪豹和猞猁，两只大猫</p><p><img data-src="polaroid-636-northwest-20260604-14.png" alt=""><br>兰州，黄河滩。看到在黄河边长大的孩子们，想到海边长大的自己。</p><p>这次旅行因为每天要开车，虽然带了数码相机但实在没有气力拍摄、选片、修图，感谢宝丽来能帮助我留下即时的影像回忆。</p>]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/06/04/polaroid-636-northwest/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/06/04/polaroid-636-northwest/"/>
    <published>2026-06-04T07:02:00.000Z</published>
    <summary>一趟自驾青甘大环线的宝丽来胶片记录，使用我从第比利斯旱桥集市上带回来的宝丽来 636。</summary>
    <title>带着宝丽来 636 去西北看了看</title>
    <updated>2026-06-12T07:10:34.147Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Digital Anatomy" scheme="https://leecdiang.github.io/myblog-source/categories/Digital-Anatomy/"/>
    <category term="AI" scheme="https://leecdiang.github.io/myblog-source/tags/AI/"/>
    <category term="Productivity" scheme="https://leecdiang.github.io/myblog-source/tags/Productivity/"/>
    <category term="数据处理" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%95%B0%E6%8D%AE%E5%A4%84%E7%90%86/"/>
    <category term="DataScience" scheme="https://leecdiang.github.io/myblog-source/tags/DataScience/"/>
    <category term="苹果健康" scheme="https://leecdiang.github.io/myblog-source/tags/%E8%8B%B9%E6%9E%9C%E5%81%A5%E5%BA%B7/"/>
    <category term="Apple" scheme="https://leecdiang.github.io/myblog-source/tags/Apple/"/>
    <category term="Health" scheme="https://leecdiang.github.io/myblog-source/tags/Health/"/>
    <category term="XML" scheme="https://leecdiang.github.io/myblog-source/tags/XML/"/>
    <category term="CSV" scheme="https://leecdiang.github.io/myblog-source/tags/CSV/"/>
    <content>
      <![CDATA[<h2 id="01-前言：Apple-Health-的数据围城">01. 前言：Apple Health 的数据围城</h2><p>导出过 Apple 健康数据的朋友都深有体会：苹果给我们的不仅是数据，更是一道技术门槛。</p><p><strong>1、文件巨大：</strong> 数 GB 的 <code>导出.xml</code>。</p><p><strong>2、格式混乱：</strong> 记事本打不开，Excel 内存溢出。</p><p><strong>3、AI 难读：</strong> 原始 XML 的层级结构让 LLM在处理长周期趋势时产生幻觉。</p><p>为打破这道门槛，我开发了<strong>Apple Health Pro🍎</strong>。<a href="https://github.com/leecdiang/Apple-Health-Pro/releases" target="_blank" class="glass-pill download-btn"><i class="fab fa-github"></i> 前往 GitHub 下载安装包</a></p><hr><h2 id="02-用户指南：3-步实现“健康数据自由”">02. 用户指南：3 步实现“健康数据自由”</h2><h3 id="第一步：iPhone-端的原始导出">第一步：iPhone 端的原始导出</h3><p>在健康 App 中点击头像 -&gt; <strong>“导出所有健康数据”</strong>。这一步你将得到一个 <code>导出.zip</code>。</p><p><img data-src="Apple-Health-Pro-20260405-2.jpg" alt=""></p><h4 id="第二步：Apple-Health-Pro-精准洗数">第二步：Apple Health Pro 精准洗数</h4><p>启动应用（支持 macOS ， windows 以及 Linux 三平台）</p><p><strong>1、加载：</strong> 点击 <code>SELECT DATA ARCHIVE</code>并选择你在上一步导出的 ZIP 压缩包。</p><p><strong>2、过滤：</strong> 在 <code>IDENTIFIED SOURCES</code> 列表中勾选你信任的来源，剔除冗余的第三方 App 记录。</p><p><strong>3、隐私和时间：</strong> 在 <code>Date range</code> 输入你期望的起止日期，同时选择是否隐藏设备名（这一步会将初始数据的设备名覆盖为 AppleHealthPro_device</p><p><strong>4、执行：</strong> 点击 <code>EXECUTE EXPORT</code>。</p><p><img data-src="Apple-Health-Pro-20260503.png" alt=""></p><h4 id="第三步：让-AI-成为你的“数字私人医生”">第三步：让 AI 成为你的“数字私人医生”</h4><p>导出的 CSV 已经过专业的维度归纳，**在最新的 v8.6.0 版本中，已全面升级为 15 大医疗与运动维度。**如下方表格所示，如果未提取到相关数据则不会生成对应文件。</p><table><thead><tr><th style="text-align:center">分类序号</th><th style="text-align:left">维度名称 (Category)</th><th style="text-align:left">导出文件名 (Exported CSV)</th><th style="text-align:left">核心指标涵盖 (Key Metrics Included)</th></tr></thead><tbody><tr><td style="text-align:center"><strong>01</strong></td><td style="text-align:left"><strong>核心心血管</strong> (Heart &amp; Cardio)</td><td style="text-align:left"><code>1_Heart_Cardio.csv</code></td><td style="text-align:left">心率、静息心率、HRV (心率变异性)、步行平均心率</td></tr><tr><td style="text-align:center"><strong>02</strong></td><td style="text-align:left"><strong>身体成分</strong> (Body Metrics)</td><td style="text-align:left"><code>2_Body_Metrics.csv</code></td><td style="text-align:left">体重、BMI、体脂率、瘦体重、身体水分</td></tr><tr><td style="text-align:center"><strong>03</strong></td><td style="text-align:left"><strong>日常基础消耗</strong> (Daily Activity)</td><td style="text-align:left"><code>3_Daily_Activity.csv</code></td><td style="text-align:left">步数、活动能量消耗、静息能量消耗、步行距离、爬楼层数</td></tr><tr><td style="text-align:center"><strong>04</strong></td><td style="text-align:left"><strong>睡眠与恢复</strong> (Sleep Recovery)</td><td style="text-align:left"><code>4_Sleep_Recovery.csv</code></td><td style="text-align:left">睡眠分析 (核心、深度、快速动眼、清醒等状态)</td></tr><tr><td style="text-align:center"><strong>05</strong></td><td style="text-align:left"><strong>步态与行动力</strong> (Mobility &amp; Gait)</td><td style="text-align:left"><code>5_Mobility_Gait.csv</code></td><td style="text-align:left">步行速度、步长、不对称性、双足支撑时间、步态稳定性</td></tr><tr><td style="text-align:center"><strong>06</strong></td><td style="text-align:left"><strong>生殖与生理健康</strong> (Reproductive)</td><td style="text-align:left"><code>6_Reproductive.csv</code></td><td style="text-align:left">经期记录、排卵测试结果、宫颈粘液质量</td></tr><tr><td style="text-align:center"><strong>07</strong></td><td style="text-align:left"><strong>生命体征</strong> (Vitals &amp; Respiratory)</td><td style="text-align:left"><code>7_Vitals_Respiratory.csv</code></td><td style="text-align:left">血氧饱和度、呼吸率、体温、血压</td></tr><tr><td style="text-align:center"><strong>08</strong></td><td style="text-align:left"><strong>跑步硬核动态</strong> (Running Dynamics)</td><td style="text-align:left"><code>8_Running_Dynamics.csv</code></td><td style="text-align:left">跑步功率、垂直振幅、触地时间、跑步步幅、跑步速度</td></tr><tr><td style="text-align:center"><strong>09</strong></td><td style="text-align:left"><strong>骑行表现</strong> (Cycling Stats)</td><td style="text-align:left"><code>9_Cycling_Stats.csv</code></td><td style="text-align:left">骑行功率、踏频、骑行速度、功能性阈值功率 (FTP)</td></tr><tr><td style="text-align:center"><strong>10</strong></td><td style="text-align:left"><strong>游泳与水域</strong> (Swimming &amp; Water)</td><td style="text-align:left"><code>10_Swimming_Water.csv</code></td><td style="text-align:left">游泳距离、划水次数、水下深度、水温</td></tr><tr><td style="text-align:center"><strong>11</strong></td><td style="text-align:left"><strong>通用体能训练</strong> (Workouts &amp; Training)</td><td style="text-align:left"><code>11_Workouts_Training.csv</code></td><td style="text-align:left">力量训练、瑜伽、HIIT、传统跑/骑/游等所有手动开启的运动记录</td></tr><tr><td style="text-align:center"><strong>12</strong></td><td style="text-align:left"><strong>环境与感官</strong> (Environment &amp; Senses)</td><td style="text-align:left"><code>12_Environment_Senses.csv</code></td><td style="text-align:left">日照时间、环境音量暴露、耳机音量暴露</td></tr><tr><td style="text-align:center"><strong>13</strong></td><td style="text-align:left"><strong>营养与摄入</strong> (Nutrition &amp; Hydration)</td><td style="text-align:left"><code>13_Nutrition_Hydration.csv</code></td><td style="text-align:left">膳食能量、碳水、蛋白质、饮水量、咖啡因摄入</td></tr><tr><td style="text-align:center"><strong>14</strong></td><td style="text-align:left"><strong>心理状态与正念</strong> (Mindfulness &amp; Mental)</td><td style="text-align:left"><code>14_Mindfulness_Mental.csv</code></td><td style="text-align:left">心理状态打卡、情绪追踪、正念冥想时间</td></tr><tr><td style="text-align:center"><strong>15</strong></td><td style="text-align:left"><strong>症状与病史</strong> (Symptoms &amp; Illness)</td><td style="text-align:left"><code>15_Symptoms_Illness.csv</code></td><td style="text-align:left">头痛、咳嗽、疲劳等手动打卡的各类症状记录</td></tr></tbody></table><p>你可以直接将这些文件喂给 ChatGPT 或 Gemini 等大模型，配合我推荐的提示词：</p><blockquote><p><strong>[你是一名具备运动生理学、 心血管医学 和健康数据建模能力的专业分析师。我将提供Apple Health原始数据（CSV），请基于数据进行接近专业体检级别的分析，并严格按照以下结构输出：首先给出【一句话结论】，直接判断整体健康状态（健康 / 亚健康 / 风险状态），不得模糊；然后进行【生理系统拆解分析】，从心血管系统（心率、HRV、静息心率）、神经系统（基于HRV分析交感/副交感平衡）、睡眠恢复系统、代谢与活动水平四个层面分析，必须解释背后的生理机制而非表象；接着进行【趋势建模】，基于时间序列判断是否存在周期性波动、长期改善或恶化趋势]</strong></p></blockquote><p><img data-src="Apple-Health-Pro-20260405-1.png" alt=""></p><hr><h2 id="03-技术深挖：我是如何驯服-15GB-“数据巨兽”的？">03. 技术深挖：我是如何驯服 15GB+ “数据巨兽”的？</h2><p>面对极端的全量 Apple Health 数据（部分极客用户的 <code>导出.xml</code> 高达 13GB 以上），传统的读取方式会让内存瞬间红温宕机。在 v8.4.0 版本中，我重构了底层的解析引擎：</p><p><img data-src="Apple-Health-Pro-20260407.png" alt="697"></p><p><img data-src="Apple-Health-Pro-20260407-1.png" alt=""></p><p><strong>1、 O(1) 流式清洗管道 (CleanStream)</strong> 苹果的 XML 中偶发带有 <code>\x0b</code> 等乱码字符，常规的 <code>f.read().replace()</code> 会将十几个 G 的文件瞬间载入物理内存。我写了一个流式清洗代理，将内存消耗从 O(N) 全量读取降维至 O(1) 按块读取，彻底消灭了闪退的元凶。</p><p><strong>2、C 语言级内存池去重 (String Interning)</strong> 在 3000 万条健康记录中，<code>type</code>和 <code>unit</code>存在海量重复。我调用了 Python 底层的 <code>sys.intern()</code> 机制，让几百万个相同的字符串在内存中共享同一个物理地址，再辅以 <code>gc.collect()</code> 强制垃圾回收，打破了 Python 字典对象的物理内存瓶颈。</p><p><strong>3、智能安全分割 (Smart Chunking)</strong> 为了防止导出的巨型 CSV 撑爆用户的 Excel，系统内置了自动分卷算法。经过压力测试，我将分卷阈值精准踩在 <strong>88 万行</strong>（单文件约 90MB），这是兼顾数据连续性与软件加载稳定性的绝佳甜点。</p><hr><h2 id="04-开源与未来">04. 开源与未来</h2><p>目前项目已在 GitHub 开源，欢迎各位 Star 指教。</p><p><strong>GitHub 地址</strong>：<a href="https://github.com/leecdiang/Apple-Health-Pro" target="_blank" class="glass-pill"><i class="fab fa-github"></i> leecdiang/Apple-Health-Pro</a><br><strong>未来计划：</strong> 支持更多运动指标提取，图标显示数据 …</p>]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/04/05/Apple-Health-Pro/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/04/05/Apple-Health-Pro/"/>
    <published>2026-04-05T12:00:00.000Z</published>
    <summary>Apple Health 导出的 XML 文件庞大且难以直接读取。本文介绍如何利用我开发的转换工具，将复杂的 ZIP/XML 数据瞬间转换为 AI 可读的清爽 CSV 格式，助力你的个人健康数据分析。</summary>
    <title>打破围城：将 Apple Health 导出的 XML数据转换为友好的 CSV 格式</title>
    <updated>2026-06-12T07:11:20.761Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Silicon &amp; Circuits" scheme="https://leecdiang.github.io/myblog-source/categories/Silicon-Circuits/"/>
    <category term="Silicon" scheme="https://leecdiang.github.io/myblog-source/tags/Silicon/"/>
    <category term="Intel" scheme="https://leecdiang.github.io/myblog-source/tags/Intel/"/>
    <category term="IBOT" scheme="https://leecdiang.github.io/myblog-source/tags/IBOT/"/>
    <category term="CPU" scheme="https://leecdiang.github.io/myblog-source/tags/CPU/"/>
    <category term="ArrowLake" scheme="https://leecdiang.github.io/myblog-source/tags/ArrowLake/"/>
    <content>
      <![CDATA[<h2 id="Arrow-Lake-的“二次进化”">Arrow Lake 的“二次进化”</h2><p>intel 发布了Arrow Lake Refresh 的<a href="https://overclock3d.net/reviews/cpu_mainboard/intel-delivers-a-value-boost-with-its-core-ultra-200s-plus-cpus/"><strong>Core Ultra 200S PLUS CPUs</strong></a>，核心架构不变，频率微调，通过优化总线连接在 D2D 频率上提升了最多 900 MHz， 同时内存频率支持到 DDR5-7200，除此之外，Intel 在这代 CPU 上搭载了新的二进制优化工具，也就是<strong>Intel Binary Optimisation Tool （IBOT)。</strong></p><p>IBOT旨在优化新 CPU 架构的应用，提高负载 IPC。本文将聚焦这一技术展开讨论。<br><img data-src="silicon-roadmap-20260318.png" alt=""></p><hr><h2 id="什么是IBOT？">什么是IBOT？</h2><p>在高性能计算的发展历程中，半导体行业长期通过微缩制程与增加物理核心来获取性能增长。但物理极限的逼近以及异构架构复杂性的提升使得单纯堆料难以应对现代复杂工作负载。同时，AI 浪潮下的能源危机和 AI 终端的个人普及使企业开始重视能效比，在这个大背景下，计算终端<strong>软硬件协同设计</strong>变得尤为重要。</p><p><strong>IBOT</strong>正是这种软硬件协同理念下的产物。简单来说，它是一个动态二进制优化层。</p><p>与传统的编译器优化是在程序运行前完成不同，IBOT 运行在系统底层，它实时监控应用程序的指令流，识别出那些执行效率低下的代码段，并针对 <strong>Arrow Lake</strong> 的微架构特性进行动态重写。</p><h3 id="表-1：编译器优化与-Intel-iBot-技术的深度对比">表 1：编译器优化与 Intel iBot 技术的深度对比</h3><table><thead><tr><th style="text-align:left">特性</th><th style="text-align:left">编译器优化</th><th style="text-align:left">iBot 底层优化</th></tr></thead><tbody><tr><td style="text-align:left"><strong>作用对象</strong></td><td style="text-align:left">源代码（C++/C# ）</td><td style="text-align:left">编译后的二进制文件（.exe / .dll）</td></tr><tr><td style="text-align:left"><strong>获取信息</strong></td><td style="text-align:left">静态语法树、数据流分析</td><td style="text-align:left">实际运行时的分析数据 (Profiling Data)</td></tr><tr><td style="text-align:left"><strong>针对性</strong></td><td style="text-align:left">针对指令集架构（如 x86-64）</td><td style="text-align:left">针对<strong>特定微架构</strong>（如流水线深度解码宽度等）</td></tr><tr><td style="text-align:left"><strong>典型手段</strong></td><td style="text-align:left">循环展开、函数内联、寄存器分配</td><td style="text-align:left">代码块重排、去除无效指令、优化指令对齐</td></tr><tr><td style="text-align:left"><strong>灵活性</strong></td><td style="text-align:left">开发者控制，发布后即固定</td><td style="text-align:left">用户侧控制，可针对新发布的 CPU 进行二次优化</td></tr></tbody></table><p>这种技术的核心目的并非单纯提高频率，而是通过<strong>减少指令周期的浪费（Stalls）</strong>，让 CPU 在每个周期中都能完成更多的实质性工作。</p><hr><h2 id="IBOT-的核心运作机制">IBOT 的核心运作机制</h2><p>为了实现 IPC 的提升，IBOT 主要在以下三个环节发挥作用：</p><ol><li><p><strong>Profiling</strong>： 利用 CPU 内部集成的性能监控单元（PMU），IBOT 能够精准捕捉程序运行时的瓶颈：是分支预测失败？还是频繁的Cache Miss？它能识别出哪些代码导致了架构内部的交通拥堵。</p></li><li><p><strong>Dynamic Transformation</strong>： 一旦锁定目标，IBOT 会在内存中对二进制指令进行调整。例如，将长串的低效指令序列替换为针对当前架构优化的专用指令集，或者重新排列数据读取顺序以匹配更快的总线带宽。</p></li><li><p><strong>Contention Reduction</strong>： 在 Arrow Lake 的分 Tile 设计中，数据在不同逻辑单元间的往返往往产生延迟。IBOT 通过优化指令流，最大限度地减少了对架构资源的争用，确保核心始终处于高效负载的状态。</p></li></ol><hr><h2 id="D2D-提频是-IBOT-的先决条件">D2D 提频是 IBOT 的先决条件</h2><p><strong>Core Ultra 200S</strong> 由于采用了类似 AMD 的 Cheplet 设计，通过 Foveros 3D 封装将不同的 Tiles 组合在一起：<strong>Compute Tile</strong>负责算力，而<strong>SoC Tile</strong>负责内存控制。这导致了CPU 核心每读取一次内存数据，指令都必须跨越计算模块与 SoC 模块之间的物理边界。</p><p>这个边界的通信效率由 D2D 频率决定。由于 D2D 频率较低，数据在模块间传输的时间大幅拉跨整体延迟。因此初代 200S 的内存延迟普遍在 <strong>80ns-100ns</strong>，远高于上一代的 <strong>60ns</strong> 级别。</p><p>同时在 Intel 的设计中，Compute Tile内部的 Ring Bus 频率与 D2D 互连频率往往存在同步或比例关系，因此当 D2D 频率被限制在较低水平时，为了保证数据传输的稳定性，计算模块内部的环形总线频率也会受到牵制，这直接导致了 L3 缓存的带宽和响应速度下降。</p><p>这种环境下，运行任何实时指令优化工具都会产生不可忽视的开销：因为优化器本身也需要占用宝贵的互连带宽去监测和重写指令。</p><p><strong>900 MHz 的 D2D 提频，本质上是 Intel 为 IBOT 预付的“性能税”：</strong></p><ul><li><p><strong>释放带宽</strong>：得益于 D2D 频率的激增，模块间通信大大加速。这降低了基础延迟，也为 IBOT 腾出了足够的运行空间。现在，IBOT 在后台实时分析并重构二进制指令时，其产生的数据交换可以白嫖这部分多出来的带宽，而不会拖累正常的计算任务。</p></li><li><p><strong>消除架构碰撞</strong>：IBOT 的核心任务是减少 Architectural Contention。在分 Tile 设计中，程序往往会因为不适应“跨片取数”而产生大量的指令气泡。IBOT 通过实时重写指令流，提前排列好数据的进出顺序，确保在高频 D2D 链路的配合下，数据能以最完美的节奏填满每一个 CPU 周期。</p></li></ul><hr><h2 id="性能预期-IPC的纯粹提升">性能预期: IPC的纯粹提升</h2><p>在实际应用中，IBOT 的加入意味着：</p><h3 id="游戏表现："><strong>游戏表现</strong>：</h3><p><a href="https://media.overclock3d.net/2026/03/Intel-Binary-Optimisation-IBOT-Gains.jpg"><img data-src="https://media.overclock3d.net/2026/03/Intel-Binary-Optimisation-IBOT-Gains.jpg" alt=""></a><br>对于那些针对旧架构编写、对单核效率敏感的老旧游戏，或是从 Switch/PS5 上移植来的非原生游戏，IBOT 可以通过实时优化指令流，显著改善 1% Low FPS。</p><p>这几年，我们已经习惯了各种<strong>Frame Generation</strong>或是<strong>Upscaling</strong>技术。尽管 DLSS 或 FSR 能够带来震撼的平均帧数，但它们本质上是基于算法的视觉补偿，在技术力上对我没什么吸引力。</p><p><strong>IBOT 的逻辑则完全不同</strong>：它不生成任何虚假的像素，也不跳过任何必要的计算步骤。</p><p>它的核心价值在于解决“指令卡顿”。在异构架构中，很多移植游戏或旧作并不理解如何高效地在核心间分配任务，导致 CPU 频繁出现无效周期。IBOT 在二进制层面实时重排这些指令，确保每一个时钟周期都在处理有意义的代码。</p><p>其结果是显而易见的：传统的帧生成技术往往无法根治掉帧，因为底层逻辑卡顿依然存在。而 IBOT 从指令源头消除了气泡，让帧生成时间变得丝滑平均。同时，与 AI 补帧带来的输入延迟不同，IBOT 是原生的计算提速，你获得的每一帧都是真实的物理帧。</p><hr><h3 id="专业负载：">专业负载：</h3><p>在复杂的生产力工具中，IBOT 的核心价值在于平滑 P-Core 与 E-Core 切换时的执行抖动。</p><ul><li><p><strong>优化上下文切换</strong>： 生产力任务如代码编译、大型工程文件加载常涉及频繁的核心迁移。由于 P/E 核微架构不同，切换时易引发流水线清空。IBOT 在二进制层面实时预对齐指令流，将这种切换开销降至最低。</p></li><li><p><strong>填补跨 Tile 指令气泡</strong>： 针对 Arrow Lake 的分 tile 设计，IBOT 能识别并重排那些可能导致跨片取数延迟的指令。它通过预取和指令重排序，填补了由于 D2D 通信产生的指令气泡，让 CPU 在处理非线性生产力任务时，单位周期内的吞吐量更稳定。</p></li><li><p><strong>老旧工具的“实时翻译”</strong>： 大量未针对异构架构优化的专业软件，在 IBOT 的干预下，可能以更契合当前 Tile 架构的逻辑运行。它本质上是让老软件在新芯片上跑出了原生优化的高效感。</p></li></ul><hr><h2 id="总结：Intel-的“软硬协同”进化论">总结：Intel 的“软硬协同”进化论</h2><p><strong>Intel Binary Optimisation Tool (IBOT)</strong> 的出现，标志着半导体竞争进入了一个全新的维度。</p><p>在摩尔定律放缓、异构架构日益复杂的今天，单纯堆砌晶体管和拉升频率已经难以满足玩家和专业用户的胃口。<strong>Core Ultra 200S PLUS</strong> 给出了一套新颖的组合拳：</p><ol><li><p><strong>硬件层面</strong>：通过物理层面的 <strong>+900 MHz D2D 提频</strong>，Intel 终于化解了分离式 Tile 架构的延迟沉疴，为后续的优化留出了重要的带宽预算。</p></li><li><p><strong>软件层面</strong>：<strong>IBOT</strong> 拒绝了当下流行的视觉补偿套路。它不跳过任何计算步骤，也不生成虚假的像素，而是通过二进制层面的动态重构，让每一行指令都能在最合适的时间、以最合适的顺序进入最合适的核心。</p></li></ol><p>对于追求极致体感的玩家，这意味着实打实的 <strong>1% Low FPS</strong> 提升和零延迟的响应；对于专业创作者，这代表着在繁重的生产力洪流中，获得了前所未有的执行确定性。</p><p><strong>当物理瓶颈被打破，真正的 IPC 潜力才被彻底释放。</strong></p><p><img data-src="silicon-roadmap-20260318-2.png" alt=""></p>]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2026/03/18/silicon-roadmap/</id>
    <link href="https://leecdiang.github.io/myblog-source/2026/03/18/silicon-roadmap/"/>
    <published>2026-03-18T04:00:00.000Z</published>
    <summary>本文深度解析 Intel 最新的 iBot (Binary Optimisation Tool) 技术，探讨其在 Arrow Lake Refresh 架构中如何通过二进制层面的实时重排，并从此分析 SOC 软硬件协同设计的未来。</summary>
    <title>从Intel Binary Optimisation Tool (IBOT) 看高性能计算发展方向</title>
    <updated>2026-06-04T06:49:52.789Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Digital Anatomy" scheme="https://leecdiang.github.io/myblog-source/categories/Digital-Anatomy/"/>
    <category term="Apple" scheme="https://leecdiang.github.io/myblog-source/tags/Apple/"/>
    <category term="MacBook" scheme="https://leecdiang.github.io/myblog-source/tags/MacBook/"/>
    <category term="硬盘扩容" scheme="https://leecdiang.github.io/myblog-source/tags/%E7%A1%AC%E7%9B%98%E6%89%A9%E5%AE%B9/"/>
    <category term="扩容" scheme="https://leecdiang.github.io/myblog-source/tags/%E6%89%A9%E5%AE%B9/"/>
    <category term="MacBookAir" scheme="https://leecdiang.github.io/myblog-source/tags/MacBookAir/"/>
    <category term="Silicon" scheme="https://leecdiang.github.io/myblog-source/tags/Silicon/"/>
    <content>
      <![CDATA[<h2 id="前言">前言</h2><p>在购入这台macbook air m4的时候选择了15寸 + 24g RAM + 256g SSD的配置，国补+教育优惠叠加后的价格是8700左右，本以为256的存储用到过保再扩容绰绰有余，甚至也许可以外接硬盘一直用下去；但仅过了半年就容量告紧，遂决定扩容。</p><hr><h2 id="Part-1-选择第三方扩容？">Part 1: 选择第三方扩容？</h2><p>由于苹果的高昂存储价格，第三方扩容性价比高的多，但25年AI采购导致内存、存储芯片涨价严重，这次第三方扩容花费1300元.</p><p>这次选择在【万维创芯】这家工作室扩容，因为这是我在b站看到的唯一一家扩容后使用BGA专用基底填充胶水的商家；</p><p>Underfill底部填充胶的材料通常使用环氧树脂(Epoxy)，它利用毛细作用原理把Epoxy涂抹在晶片的边缘让其渗透到覆晶晶片或BGA的底部，然后加热予以固化(cured)，它填充封装和印刷电路板 (PCB) 之间的间隙，提供机械支撑和应力释放，以防止热膨胀和收缩损坏。 底部填充环氧树脂还可以通过降低寄生电感和电容来提高封装的电气性能。</p><img data-src="/myblog-source/2025/10/13/macbook-air-m4-ssd-upgrade/2.png" class="" title="BGA底部填充胶示意图"><p>在【万维创芯】的早期b站视频中可见，使用的并非Underfill胶水而是光固化UV胶，仅能为存储芯片四周封边，无法渗透进入焊盘，因此抗跌落和热膨胀性能差，这也是我之前未选择扩容的原因。</p><img data-src="/myblog-source/2025/10/13/macbook-air-m4-ssd-upgrade/3.png" class="" title="UV胶封边示意图"><hr><h2 id="Part-2-扩容过程记录">Part 2: 扩容过程记录</h2><ol><li><p><strong>数据备份</strong>：使用“时间机器”(Time Machine) 将整个系统完整备份到了我的移动硬盘上。（需要格式硬盘为APFS格式）</p></li><li><p><strong>拆机与操作</strong>：全程大概三小时，过程为拆下主板-热风枪吹下旧硬盘-清理焊盘-植中温锡-焊接新硬盘-测试-封胶-装机</p></li></ol><img data-src="/myblog-source/2025/10/13/macbook-air-m4-ssd-upgrade/4.png" class="" title="扩容操作过程"><ol start="3"><li><strong>数据恢复</strong>：扩容完成后，从“时间机器”备份中恢复数据。开机后，一切都和送修前一样，只是存储空间变成了 1TB。</li></ol><img data-src="/myblog-source/2025/10/13/macbook-air-m4-ssd-upgrade/5.png" class="" title="扩容后系统显示1TB容量"><hr><h2 id="Part-3-性能实测">Part 3: 性能实测</h2><p>扩容后，除了容量变大，硬盘的读写速度也是我非常关心的一点。我使用了 Blackmagic Disk Speed Test 这款软件，对扩容后的硬盘进行了测速。</p><img data-src="/myblog-source/2025/10/13/macbook-air-m4-ssd-upgrade/6.png" class="" title="Blackmagic 测速结果"><p>对比扩容前稍高，但算不上显著（扩容前在读写3000左右）</p><p>通常来说，大容量的 SSD 因为有更多的闪存通道，读写速度可能会比小容量的更快。</p><hr><h2 id="总结：值吗？">总结：值吗？</h2><p>我认为是值得的。</p><p>现在，我可以毫无顾忌地导入我的摄影原片、安装大型软件、创建虚拟机。</p><p>采用了正确的扩容工艺也让我更加放心地使用</p><p><strong>优点：</strong></p><ul><li><p><strong>性价比高</strong>：用远低于官方的价格，获得了四倍的存储空间。</p></li><li><p><strong>体验无缝</strong>：通过时间机器恢复后，系统和数据都和原来一样，没有任何不适感。</p></li><li><p><strong>性能提升</strong>：获得了更快的读写速度。</p></li></ul><p><strong>潜在风险：</strong></p><ul><li><strong>保修</strong>：影响官方保修，二手不好卖，可能存在的虚焊掉盘。</li></ul><p>如果你也像我一样，正在被 256GB 的存储空间所困扰，并且愿意承担一定的保修风险，那么正确的第三方扩容是一个值得考虑的选择。</p><p>希望我的这次经历能给你带来一些参考。</p>]]>
    </content>
    <id>https://leecdiang.github.io/myblog-source/2025/10/13/macbook-air-m4-ssd-upgrade/</id>
    <link href="https://leecdiang.github.io/myblog-source/2025/10/13/macbook-air-m4-ssd-upgrade/"/>
    <published>2025-10-13T01:15:00.000Z</published>
    <summary>MacBook Air M4 15寸 扩容1tb记录以及建议</summary>
    <title>MacBook Air M4 15寸扩容记录</title>
    <updated>2026-06-12T07:11:03.667Z</updated>
  </entry>
  <entry>
    <author>
      <name>LEEcDiang</name>
    </author>
    <category term="Visual Narrative" scheme="https://leecdiang.github.io/myblog-source/categories/Visual-Narrative/"/>
    <category term="Hexo" scheme="https://leecdiang.github.io/myblog-source/tags/Hexo/"/>
    <category term="个人博客" scheme="https://leecdiang.github.io/myblog-source/tags/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"/>
    <id>https://leecdiang.github.io/myblog-source/2025/10/01/hello-world/</id>
    <link href="https://leecdiang.github.io/myblog-source/2025/10/01/hello-world/"/>
    <published>2025-10-01T01:15:00.000Z</published>
    <summary>这是我的第一篇博文，用于测试 hexo + next 部署的相关功能</summary>
    <title>你好！欢迎👏</title>
    <updated>2026-03-18T05:25:48.896Z</updated>
  </entry>
</feed>
