<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>BiumBiu</title><description>关于 AI Infrastructure、推理优化、Agent 与工程实践的技术笔记与项目档案。</description><link>https://biumbiu.com/</link><item><title>把 FlashAttention 搬上昇腾：Triton-Ascend CV Pipeline 优化实战</title><link>https://biumbiu.com/notes/flashattention-kernel-optimize/</link><guid isPermaLink="true">https://biumbiu.com/notes/flashattention-kernel-optimize/</guid><description>基于 Triton-Ascend 构建昇腾亲和算子，针对FlashAttention fwd&amp;bwd算子展开优化，通过细粒度 CV 流水编排等优化方式，将 Cube 利用率提升到 90%+。</description><pubDate>Sun, 30 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;2026 年，我们参加了全国大学生计算机系统能力大赛编译系统设计赛（华为毕昇杯）。
赛题围绕 FlashAttention 算子展开：初赛主要优化 Forward，决赛主要优化 Backward。&lt;/p&gt;
&lt;p&gt;我们基于 Triton-Ascend 完成算子实现，并针对昇腾 Cube 与 Vector 分离的硬件结构设计
细粒度 CV 流水，使 Cube 利用率达到 90%+。最终，我们获得全国一等奖。这个赛道共有
115 支队伍报名，最终只有 5 支队伍获得一等奖。&lt;/p&gt;
&lt;p&gt;下面按我们实际做优化的顺序整理。&lt;/p&gt;
&lt;h2&gt;一、先从 FlashAttention Forward 说起&lt;/h2&gt;
&lt;p&gt;标准 Attention 的前向计算是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;S = Q @ K^T * scale
P = softmax(S)
O = P @ V
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接实现会生成完整的 &lt;code&gt;N × N&lt;/code&gt; 分数矩阵和概率矩阵。序列一长，中间矩阵本身就很大，
而且还要在计算单元和 Global Memory 之间来回搬。&lt;/p&gt;
&lt;p&gt;FlashAttention 的处理方式是把 Q、K、V 切成 tile。一个 Q tile 留在片上，依次遍历
K/V tile，只计算当前小块的 score。Softmax 也不必等完整一行算完再做，而是在线维护：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;m_i&lt;/code&gt;：已经遍历部分的逐行最大值；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;l_i&lt;/code&gt;：当前 Softmax 分母；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;acc&lt;/code&gt;：尚未归一化的 &lt;code&gt;P @ V&lt;/code&gt; 累加结果。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;新 tile 到来时，如果行最大值发生变化，就缩放旧的 &lt;code&gt;l_i&lt;/code&gt; 和 &lt;code&gt;acc&lt;/code&gt;，再合并当前 tile。
这样整个过程中只保留一个 &lt;code&gt;BLOCK_M × BLOCK_N&lt;/code&gt; 的 score，完整的 &lt;code&gt;S&lt;/code&gt; 和 &lt;code&gt;P&lt;/code&gt; 都不会
写回 Global Memory。&lt;/p&gt;
&lt;p&gt;初赛的 Forward 版本还做了几项常规但有效的处理：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;exp2&lt;/code&gt;，把 scale 乘上 &lt;code&gt;log2(e)&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;m_i&lt;/code&gt;、&lt;code&gt;l_i&lt;/code&gt; 和输出 accumulator 使用 FP32，送进 Cube 的 &lt;code&gt;P&lt;/code&gt; 转成 FP16；&lt;/li&gt;
&lt;li&gt;causal 模式直接缩短 key tile 的循环上界，只在对角 tile 内做精确 mask；&lt;/li&gt;
&lt;li&gt;D256 拆成两个 D128 分片，避免一次保留过宽的 accumulator。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Forward 的实现相对清楚：一个 Q tile 扫过 K/V，最后只写一次 O。到了 Backward，
数据的归属关系就复杂多了。&lt;/p&gt;
&lt;h2&gt;二、Backward 难在三个梯度的归约方式不同&lt;/h2&gt;
&lt;p&gt;FlashAttention Backward 可以写成：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Delta = rowsum(O * dO)
P     = recompute_softmax(Q @ K^T * scale)
dP    = dO @ V^T
dS    = scale * P * (dP - Delta)
dQ   += dS @ K
dK   += dS^T @ Q
dV   += P^T @ dO
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们仍然不保存前向的完整概率矩阵，而是在反向里重新计算 &lt;code&gt;P&lt;/code&gt;。额外计算是可以接受的，
因为省掉了巨大的中间矩阵读写。&lt;/p&gt;
&lt;p&gt;麻烦主要出在 &lt;code&gt;dQ/dK/dV&lt;/code&gt; 的累加方向不同。&lt;code&gt;dQ&lt;/code&gt; 可以由当前 query tile 自己完成；
一个 &lt;code&gt;dK&lt;/code&gt; 或 &lt;code&gt;dV&lt;/code&gt; tile 却要收集多个 query tile 的贡献。如果直接把小块分给很多 program，
最后就要用 atomic、跨 program reduction，或者分配很大的 partial workspace。&lt;/p&gt;
&lt;p&gt;我们早期试过保存全量 &lt;code&gt;P/dS&lt;/code&gt;。大配置中，单个 &lt;code&gt;B × H × N²&lt;/code&gt; scratch 就接近 2 GiB，
同时保存两份约 4 GiB，D256 很快就 OOM 了。这条路也会产生大量 MTE2 搬运，所以后来
完全放弃。&lt;/p&gt;
&lt;p&gt;后面的实现遵守三个原则：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;P&lt;/code&gt; 在反向过程中重算，不保存完整注意力矩阵；&lt;/li&gt;
&lt;li&gt;每个梯度 tile 尽量只有一个 program 负责写回；&lt;/li&gt;
&lt;li&gt;scratch 按物理 core 分配和复用，不随全部 head 的 &lt;code&gt;N²&lt;/code&gt; 一起增长。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;三、昇腾上的 Cube 和 Vector 需要分别安排&lt;/h2&gt;
&lt;p&gt;昇腾的 Cube 和 Vector 是分开的。Cube 适合做 QK、dP 和梯度 GEMM，Vector 更适合
Softmax、mask、Delta 修正与 reduction。比赛机器上的 Cube、Vector 资源配比约为
&lt;code&gt;2:1&lt;/code&gt;，两边的工作量如果没有安排好，就会有一侧提前做完，等另一侧追上来。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;图片：昇腾 Cube、Vector 与片上存储结构&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;最初的执行顺序基本是串行的：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cube   计算 QK 和 dP
Vector 生成 P 和 dS
Cube   计算 dQ、dK、dV
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每一段单独看都没有明显问题，但 Profile 里能看到不少等待。Cube 算完 score 后停下来等
Vector，Vector 做完 &lt;code&gt;P/dS&lt;/code&gt;，Cube 才能继续。我们后面大部分优化都在处理这段等待时间。&lt;/p&gt;
&lt;h2&gt;四、CV Pipeline：让 Cube 和 Vector 同时工作&lt;/h2&gt;
&lt;p&gt;最后使用的是跨 head 的 producer-consumer pipeline。Cube 为当前 head 计算 score 时，
Vector 可以处理前一个 head 的 &lt;code&gt;P/dS&lt;/code&gt;；等 Vector 处理完，Cube 再接着计算这个 head 的
梯度 GEMM。&lt;/p&gt;
&lt;p&gt;为了防止前后两批数据互相覆盖，我们准备了两个 scratch slot，交替存放中间结果：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cube   : score(head n, slot 0) -&amp;gt; score(head n+20, slot 1)
Vector :                        -&amp;gt; P/dS(head n, slot 0)
Cube   :                                               -&amp;gt; grad(head n, slot 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;core_mode&lt;/code&gt; 用来区分 Cube 和 Vector 的工作域，不同 &lt;code&gt;EventID&lt;/code&gt; 的 &lt;code&gt;Set/Wait&lt;/code&gt; 负责同步：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cube 写完 score，通知 Vector 可以读取；&lt;/li&gt;
&lt;li&gt;Vector 写完 &lt;code&gt;P/dS&lt;/code&gt;，通知 Cube 可以开始梯度计算；&lt;/li&gt;
&lt;li&gt;Cube 消费完成，这个 slot 才能在下一轮重新使用。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这部分最容易出现的是偶发错误。少一个等待，slot 可能在消费完成前被覆盖；多一个等待，
流水又退化成串行。我们用事件把依赖拆开后，Softmax 和 reduction 的时间可以藏在相邻
head 的 Cube 计算后面。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;图片：答辩 PPT 中的 CV Pipeline 双槽双流水&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;五、Persistent Program：20 个 program 处理 1024 个 head&lt;/h2&gt;
&lt;p&gt;如果按照一个 head 对应一个 program 的方式执行，&lt;code&gt;Z=128、H=8&lt;/code&gt; 时会启动 1024 个
program。调度次数很多，每个 program 还要准备自己的 scratch，core 之间的负载也不够
稳定。&lt;/p&gt;
&lt;p&gt;比赛使用的 Ascend 910B3 上有 20 个物理 Cube core，所以我们只启动 20 个 program。
每个 program 以固定步长领取 head，最终处理 51 或 52 个。处理完一个 head 后不用退出，
而是继续下一个，scratch 也可以直接复用。&lt;/p&gt;
&lt;p&gt;这种分配方式还有一个好处：一个 program 可以负责一个 head 内完整的 &lt;code&gt;dQ/dK/dV&lt;/code&gt;
生成过程，每个梯度 tile 只写一次，不需要再让多个 program 对同一输出做 atomic 或额外
归约。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;图片：答辩 PPT 中的 Persistent Program HEAD 分配&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;六、Compact Group：只保存眼前要用的中间结果&lt;/h2&gt;
&lt;p&gt;Persistent Program 解决了 program 数量和全量 workspace 的问题，但 D64/D128 的
non-causal 配置仍然很吃搬运。它们的计算强度不高，QK score、&lt;code&gt;P/dS&lt;/code&gt; 和 partial 在
Global Memory 与片上缓存之间多走一遍，MTE2 占比就会明显上升。&lt;/p&gt;
&lt;p&gt;一开始，我们为一个 head 保留较大范围的 QK score。后来改成两个 query tile 组成一个
group，只让当前 group 的 score 和 &lt;code&gt;dK/dV partial&lt;/code&gt; 保持活跃。一个 group 算完并归约后，
对应空间马上复用给下一组。&lt;/p&gt;
&lt;p&gt;Compact Group 减少了 scratch 容量和中间结果的存活时间，代价是 Vector 和 Cube 要
多处理一层 group 内的 partial。对 D64 这类带宽更紧张的配置，这个交换是划算的，
Config 1/2 的线上总分因此又提高了约 0.5 分。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;图片：答辩 PPT 中的 Compact Group 优化&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;七、D64、D128 和 D256 分开处理&lt;/h2&gt;
&lt;p&gt;我们一度希望用一个 kernel 覆盖所有 &lt;code&gt;HEAD_DIM&lt;/code&gt;，实际效果并不好。维度变化后，Cube
计算量、accumulator 大小、UB/L0C 压力和 Vector reduction 成本都会一起变化。&lt;/p&gt;
&lt;h3&gt;D64&lt;/h3&gt;
&lt;p&gt;D64 的计算/搬运比最低，non-causal 还要处理完整 &lt;code&gt;N²&lt;/code&gt; 区域。它的主要问题不是 Cube
算得慢，而是数据搬得太多。Compact Group、Delta 融合、K tile 复用和 scratch alias
在这条路径上更有效。&lt;/p&gt;
&lt;h3&gt;D128&lt;/h3&gt;
&lt;p&gt;D128 更容易让 Cube 忙起来，但 &lt;code&gt;dK/dV&lt;/code&gt; accumulator 也更大。部分 non-causal 配置把
dK 和 dV 合到同一次 query-tile 扫描里，少读一次 &lt;code&gt;P/dS/Q/dO&lt;/code&gt;；资源更紧张的 causal
配置仍然分两遍做。&lt;/p&gt;
&lt;h3&gt;D256&lt;/h3&gt;
&lt;p&gt;D256 直接使用 256 宽的大 accumulator 很容易碰到 UB/L0C 和编译器展开限制。我们的
处理方式是把 Q/K/V 和 preprocess reduction 拆成两个 D128 half，QK 阶段合并两段
点积，后续再完成宽维度累加。&lt;/p&gt;
&lt;p&gt;D256 的 score 对精度更敏感，所以 QK scratch 保留 FP32，&lt;code&gt;P/dS&lt;/code&gt; 使用 FP16 控制容量。
它也没有照搬 D64/D128 的跨 head 双大槽，而是使用单 head slot 和 query-tile 级事件
交替，避免同时占用多个宽 accumulator。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;图片：答辩 PPT 中的 D256、causal 与局部缓存优化&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;八、Causal 不是只加一个 Mask&lt;/h2&gt;
&lt;p&gt;causal Attention 的上三角区域不参与计算。假设 score 被分成 8 × 8 个 tile，
non-causal 需要处理 64 个，而 causal 只需要：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;1 + 2 + ... + 8 = 36
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;循环只走下三角后，可以直接少算 &lt;code&gt;43.75%&lt;/code&gt; 的 tile。QK、dP、dQ、dK 和 dV 都会一起
减少。如果只是算完整矩阵后再 mask，省不到这些 GEMM。&lt;/p&gt;
&lt;p&gt;我们还让 &lt;code&gt;qk -&amp;gt; P&lt;/code&gt;、&lt;code&gt;dP -&amp;gt; dS&lt;/code&gt; 复用同一块 scratch，因为前一个值被消费后就不再需要。
部分路径使用 tile-local C/V ping-pong，让 &lt;code&gt;P/dS&lt;/code&gt; 尽量留在较近的 buffer，少绕一次
global ring。&lt;/p&gt;
&lt;h2&gt;九、哪些尝试最后没有留下&lt;/h2&gt;
&lt;p&gt;优化过程中做过不少看起来合理、实际没有收益的实验：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全量 &lt;code&gt;P/dS workspace&lt;/code&gt; 在 D256 上直接 OOM；&lt;/li&gt;
&lt;li&gt;10 或 16 个 core 都比 20 个 core 慢；&lt;/li&gt;
&lt;li&gt;C1 使用 BN256、BM512 时出现 UB/Cbuf 溢出、编译失败或性能下降；&lt;/li&gt;
&lt;li&gt;更激进的 FP16 accumulator 没有稳定变快，反而增加精度风险；&lt;/li&gt;
&lt;li&gt;一些 compiler flag 能让单个 kernel 少几十微秒，但端到端时间没有变化；&lt;/li&gt;
&lt;li&gt;单纯继续放大 tile，在 MTE2 已经成为瓶颈时基本没有帮助。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;所以每个候选版本都要先检查 FP16/BF16、causal/non-causal 的正确性，再看 Cube MAC、
CV 工作比例、同步等待、MTE2 和端到端耗时。只看某一次 kernel 时间，很容易把测量
波动当成优化。&lt;/p&gt;
&lt;h2&gt;十、最后&lt;/h2&gt;
&lt;p&gt;回头看，提升最大的一步不是换了某组 &lt;code&gt;BLOCK_M/BLOCK_N&lt;/code&gt;，而是把执行方式改成了适合
昇腾的样子：20 个 program 持续处理 head，Cube 和 Vector 用双槽事件流水衔接，
Compact Group 控制中间结果的存活范围，causal 场景则直接跳过不需要计算的 tile。&lt;/p&gt;
&lt;h2&gt;参考资料&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Tri Dao 等，&lt;em&gt;FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness&lt;/em&gt;，NeurIPS 2022。&lt;/li&gt;
&lt;li&gt;Tri Dao，&lt;em&gt;FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning&lt;/em&gt;，ICLR 2024。&lt;/li&gt;
&lt;li&gt;OpenAI Triton fused attention tutorial。&lt;/li&gt;
&lt;li&gt;Triton-Ascend 教程、AscendNPU IR 文档与 Triton-Ascend 仓库。&lt;/li&gt;
&lt;li&gt;项目中的 Forward、Backward 源码、技术报告、Profile 和消融记录。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>笔记</category><category>FlashAttention</category><category>Triton-Ascend</category><category>昇腾</category><category>算子优化</category></item><item><title>一次完整部署大模型：在 4 张 A100 上用 SGLang 运行 DeepSeek-V4-Flash-0731</title><link>https://biumbiu.com/notes/deepseek-v4-flash-sglang-a100-deploy/</link><guid isPermaLink="true">https://biumbiu.com/notes/deepseek-v4-flash-sglang-a100-deploy/</guid><description>记录从官方 checkpoint 出发，在 4 张 A100 上完成 DeepSeek-V4-Flash-0731 的模型转换、A100 适配、TP=4 部署、DSpark 加速与端到端验证的完整过程。</description><pubDate>Sun, 02 Aug 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;时间：2026 年 8 月&lt;/li&gt;
&lt;li&gt;模型：DeepSeek-V4-Flash-0731&lt;/li&gt;
&lt;li&gt;推理框架：SGLang v0.5.16&lt;/li&gt;
&lt;li&gt;硬件：4 x NVIDIA A100-SXM4-80GB&lt;/li&gt;
&lt;li&gt;目标：从官方 checkpoint 出发，完成模型转换、A100 适配、TP=4 部署、API
鉴权、DSpark 加速和端到端验证&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;这是一个从头走完一个大模型的完整部署流程。&lt;/p&gt;
&lt;p&gt;这里的“完整”不只是把服务进程启动起来，而是包含下载和校验官方模型、理解权重
格式、做离线转换、补齐 A100 不支持的计算路径、接入 SGLang、启动多卡推理、验证
API 协议、测试长上下文和并发，并确认 speculative decoding 真的产生了加速。&lt;/p&gt;
&lt;p&gt;最终，我在 4 张 A100-SXM4-80GB 上成功运行了 DeepSeek-V4-Flash-0731，服务由
SGLang v0.5.16 提供 OpenAI Chat Completions、Responses 和 Anthropic Messages
兼容接口。基础生成、流式输出、多轮对话、函数工具调用、8K/32K 上下文以及并发
1/4/8 均完成验证，另外用官方 ShareGPT 数据做了两轮在线 request-rate sweep。&lt;/p&gt;
&lt;p&gt;这篇笔记记录完整过程，也记录为了让 DeepSeek V4 在 A100 SM80 上运行，我所采用
和适配的运行时改动。&lt;/p&gt;
&lt;p&gt;本文对应的完整代码已经整理到
&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516&quot;&gt;yaleyoou/deepseek-v4-a100-sglang-v0516&lt;/a&gt;。
其中 A100 kernel 和早期 patch 设计参考了
&lt;a href=&quot;https://github.com/Qeeweew/deepseek-v4-a100-sglang&quot;&gt;Qeeweew/deepseek-v4-a100-sglang&lt;/a&gt;，
我在此基础上完成了 DeepSeek-V4-Flash-0731、SGLang v0.5.16、Responses API 和
DSpark 部署链路的迁移与回归。&lt;/p&gt;
&lt;h2&gt;一、最终结果&lt;/h2&gt;
&lt;p&gt;最终验证环境如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;项目&lt;/th&gt;
&lt;th&gt;配置&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;4 x NVIDIA A100-SXM4-80GB，SM80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CUDA runtime&lt;/td&gt;
&lt;td&gt;12.9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PyTorch&lt;/td&gt;
&lt;td&gt;2.11.0+cu129&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SGLang&lt;/td&gt;
&lt;td&gt;0.5.16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SGLang commit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fdebc938f7f4d16fe6b9f55dcd9a767cf0899ea1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Triton&lt;/td&gt;
&lt;td&gt;3.6.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sglang-kernel&lt;/td&gt;
&lt;td&gt;0.4.5+cu129&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flashinfer-python&lt;/td&gt;
&lt;td&gt;0.6.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nvidia-cutlass-dsl&lt;/td&gt;
&lt;td&gt;4.6.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tensor Parallel&lt;/td&gt;
&lt;td&gt;TP=4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;主权重 dtype&lt;/td&gt;
&lt;td&gt;BF16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routed experts&lt;/td&gt;
&lt;td&gt;packed MXFP4，运行时转为 MXFP4/INT8 路径&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;KV cache&lt;/td&gt;
&lt;td&gt;BF16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexer cache&lt;/td&gt;
&lt;td&gt;INT8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speculative decoding&lt;/td&gt;
&lt;td&gt;DSpark&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;真实部署结果：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;验证项目&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Patch、kernel、validator tests&lt;/td&gt;
&lt;td&gt;68 passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SGLang API/protocol unit tests&lt;/td&gt;
&lt;td&gt;88 passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原始 checkpoint 校验&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;转换后 checkpoint 校验&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;基础模式 API 回归&lt;/td&gt;
&lt;td&gt;12/12 passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSpark API 回归&lt;/td&gt;
&lt;td&gt;12/12 passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;外部 HTTPS 代理&lt;/td&gt;
&lt;td&gt;12/12 passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;长上下文 Chat API&lt;/td&gt;
&lt;td&gt;8,214 和 32,790 prompt tokens passed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;官方 server benchmark&lt;/td&gt;
&lt;td&gt;8K/32K 精确输入，50/50 请求成功&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ShareGPT 在线 benchmark&lt;/td&gt;
&lt;td&gt;0.5/1/2/3 req/s，两轮 800/800 请求成功&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;并发性能&lt;/td&gt;
&lt;td&gt;concurrency 1/4/8，五轮固定长度测试完成&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;DSpark 模式启动后，每张卡约剩余 27.43GB，SGLang 计算出的
&lt;code&gt;max_total_num_tokens=412416&lt;/code&gt;，最大运行请求数为 48。&lt;/p&gt;
&lt;p&gt;使用 SGLang 官方 serving benchmark，固定每个请求输入 1,024 tokens、生成 128
tokens，预热并清空 Radix Cache 后做五轮测试，聚合输出吞吐的中位数为：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;并发&lt;/th&gt;
&lt;th&gt;五轮中位输出吞吐&lt;/th&gt;
&lt;th&gt;五轮范围&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;107.01 tok/s&lt;/td&gt;
&lt;td&gt;106.98-115.17 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;237.01 tok/s&lt;/td&gt;
&lt;td&gt;208.11-245.05 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;314.54 tok/s&lt;/td&gt;
&lt;td&gt;306.01-328.41 tok/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这些是固定合成输入下的模型服务核心性能，不包含 Chat 模板和客户端 tokenization，
也不等同于真实业务流量。详细口径和每轮数据见第十三节。&lt;/p&gt;
&lt;h2&gt;二、这次部署真正困难在哪里&lt;/h2&gt;
&lt;p&gt;DeepSeek-V4-Flash-0731 的官方 checkpoint 不是简单的 BF16 模型，而是混合格式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通矩阵主要使用 block FP8。&lt;/li&gt;
&lt;li&gt;Routed expert 权重使用 packed MXFP4 E2M1。&lt;/li&gt;
&lt;li&gt;MXFP4 每个 block 还带 UE8M0 scale。&lt;/li&gt;
&lt;li&gt;模型包含 DeepSeek V4 特有的稀疏注意力、compressor 和 indexer。&lt;/li&gt;
&lt;li&gt;0731 checkpoint 还包含 MTP/DSpark 所需权重。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A100 是 SM80。它有很强的 BF16 和 INT8 Tensor Core，但没有 Hopper/Blackwell
上的原生 FP8/FP4 Tensor Core 路径。SGLang 原始 MXFP4 Marlin 实现也会明确拒绝
SM80，只允许 SM90 或 SM120。&lt;/p&gt;
&lt;p&gt;所以问题不是“给启动命令加一个 A100 参数”，而是需要重新规划数据路径：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;官方 checkpoint
        |
        | 离线转换
        v
普通 FP8 权重 -&amp;gt; BF16
Routed experts -&amp;gt; 保留 packed MXFP4 + UE8M0 scale
        |
        | SGLang 加载时注入 A100 patch
        v
普通线性层 -&amp;gt; BF16
MoE experts -&amp;gt; MXFP4 重排 + INT8 Tensor Core
KV cache -&amp;gt; BF16
Indexer cache -&amp;gt; INT8
Sparse attention -&amp;gt; SM80 Triton kernel
        |
        v
TP=4 SGLang API 服务
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;三、目录与存储规划&lt;/h2&gt;
&lt;p&gt;为了让命令可以在其他机器复用，我先把工作盘和模型盘抽象成环境变量：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export WORKSPACE=/path/to/workspace
export MODEL_ROOT=/path/to/persistent/models

export SGLANG_ROOT=&quot;${WORKSPACE}/sglang&quot;
export PATCH_ROOT=&quot;${WORKSPACE}/deepseek-v4-a100-sglang-v0516&quot;
export ORIGINAL_MODEL=&quot;${MODEL_ROOT}/original/DeepSeek-V4-Flash-0731&quot;
export MODEL_PATH=&quot;${MODEL_ROOT}/converted/DeepSeek-V4-Flash-0731-MoE-MXFP4-BF16&quot;

mkdir -p &quot;$WORKSPACE&quot; &quot;$MODEL_ROOT/original&quot; &quot;$MODEL_ROOT/converted&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我实际部署时把源码、编译缓存和日志放在普通工作盘，只把大模型放在持久化盘。
读者只需要修改上面两个根目录，后续命令不依赖我的机器路径。&lt;/p&gt;
&lt;p&gt;原模型需要读取约 167GB，转换结果约 173GB。为了同时保留原始模型和转换结果，
模型盘最好至少准备 400GB 可用空间，还要为临时文件留出余量。&lt;/p&gt;
&lt;p&gt;转换器逐分片处理，不会把整个模型同时装入 CPU 内存。这个设计对大模型转换非常
重要：磁盘空间和顺序读写速度往往比 CPU 算力更容易成为瓶颈。&lt;/p&gt;
&lt;h2&gt;四、固定 SGLang 版本&lt;/h2&gt;
&lt;p&gt;这个项目不是 SGLang fork，而是外部 monkeypatch。它会替换 SGLang 的模型、KV
pool、indexer、attention backend 和 JIT 等内部接口，因此必须固定到经过验证的
commit，而不能只写一个宽松的版本范围。&lt;/p&gt;
&lt;p&gt;先获取本文对应的 patch 仓库和 SGLang：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516.git &quot;$PATCH_ROOT&quot;
git clone https://github.com/sgl-project/sglang.git &quot;$SGLANG_ROOT&quot;
git -C &quot;$SGLANG_ROOT&quot; checkout fdebc938f7f4d16fe6b9f55dcd9a767cf0899ea1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目标版本是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;SGLang version: 0.5.16
SGLang commit:  fdebc938f7f4d16fe6b9f55dcd9a767cf0899ea1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装 patch package：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd &quot;$PATCH_ROOT&quot;
python -m pip install -e . --no-deps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里使用 editable install，启动脚本还会显式把项目根目录和
&lt;code&gt;${SGLANG_ROOT}/python&lt;/code&gt; 放到 &lt;code&gt;PYTHONPATH&lt;/code&gt;。这样可以保证 Python 启动时找到本项目
的 &lt;code&gt;sitecustomize.py&lt;/code&gt;，同时使用指定 checkout 中的 SGLang，而不是环境里另一个
碰巧同名的安装包。&lt;/p&gt;
&lt;h2&gt;五、下载并校验官方模型&lt;/h2&gt;
&lt;p&gt;我固定了 Hugging Face revision，避免部署过程中上游模型内容变化：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export HF_HOME=&quot;${PATCH_ROOT}/cache/huggingface&quot;
export HF_XET_CACHE=$HF_HOME/xet
export HF_XET_HIGH_PERFORMANCE=1
export HF_XET_NUM_CONCURRENT_RANGE_GETS=32
export HF_HUB_DOWNLOAD_TIMEOUT=1800

hf download deepseek-ai/DeepSeek-V4-Flash-0731 \
  --revision 7872f01b1d1fe23eabc4c98b48bffcef5a386062 \
  --local-dir &quot;$ORIGINAL_MODEL&quot; \
  --max-workers 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;下载完成后，我没有立刻开始转换，而是先严格校验原始 checkpoint：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd &quot;$PATCH_ROOT&quot;
python scripts/validate_dsv4_checkpoint.py original \
  &quot;$ORIGINAL_MODEL&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;校验器检查的内容包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;48 个 safetensors 分片及其规范命名。&lt;/li&gt;
&lt;li&gt;index 中的 tensor 到 shard 映射。&lt;/li&gt;
&lt;li&gt;safetensors header、重复 tensor 和缺失 tensor。&lt;/li&gt;
&lt;li&gt;MXFP4 expert weight 与 scale 是否成对出现。&lt;/li&gt;
&lt;li&gt;tokenizer、config、MTP 和 DSpark 权重。&lt;/li&gt;
&lt;li&gt;0731 的四个 DSpark 字段。&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;dspark_block_size       = 5
dspark_noise_token_id   = 128799
dspark_target_layer_ids = [40, 41, 42]
dspark_markov_rank      = 256
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一步让我认识到，模型文件“已经下载完”不等于 checkpoint 完整。对于几十个分片
的大模型，少一个 shard 或 index 写错一项，都可能等到加载数分钟后才暴露。&lt;/p&gt;
&lt;h2&gt;六、离线转换模型&lt;/h2&gt;
&lt;p&gt;正式转换命令如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python scripts/convert_deepseek_v4_flash_moe_mxfp4_bf16.py \
  --input &quot;$ORIGINAL_MODEL&quot; \
  --output &quot;$MODEL_PATH&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;转换器对每个 tensor 做分类处理。&lt;/p&gt;
&lt;h3&gt;普通 FP8 权重&lt;/h3&gt;
&lt;p&gt;对于带 block scale 的普通 FP8 weight，转换器执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;BF16 weight = BF16(FP8 code) * BF16(block scale)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后将结果写成 BF16。这个过程是解量化，不会恢复官方 checkpoint 量化前已经
损失的精度，但不会再增加一次新的低比特重量化。&lt;/p&gt;
&lt;h3&gt;Routed experts&lt;/h3&gt;
&lt;p&gt;对于形如以下路径的 expert 权重：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;layers.&amp;lt;layer&amp;gt;.ffn.experts.&amp;lt;expert&amp;gt;.w1.weight
layers.&amp;lt;layer&amp;gt;.ffn.experts.&amp;lt;expert&amp;gt;.w2.weight
layers.&amp;lt;layer&amp;gt;.ffn.experts.&amp;lt;expert&amp;gt;.w3.weight
mtp.&amp;lt;layer&amp;gt;.ffn.experts.&amp;lt;expert&amp;gt;.*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;转换器保留 packed MXFP4 weight 以及对应 &lt;code&gt;.scale&lt;/code&gt;。它们会在 SGLang 加载权重时
被重新编码成 A100 kernel 使用的紧凑格式。&lt;/p&gt;
&lt;h3&gt;为什么配置仍然写 fp8&lt;/h3&gt;
&lt;p&gt;转换后的 &lt;code&gt;config.json&lt;/code&gt; 仍保留：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;quantization_config&quot;: {
    &quot;quant_method&quot;: &quot;fp8&quot;,
    &quot;activation_scheme&quot;: &quot;dynamic&quot;,
    &quot;fmt&quot;: &quot;e4m3&quot;,
    &quot;scale_fmt&quot;: &quot;ue8m0&quot;
  },
  &quot;torch_dtype&quot;: &quot;bfloat16&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这看起来有些反直觉，但这里的 &lt;code&gt;fp8&lt;/code&gt; 主要承担 loader 路由作用。SGLang 通过 FP8
quantization config 识别 DeepSeek V4 的 MXFP4 experts；普通模块则加入
&lt;code&gt;ignored_layers&lt;/code&gt;，按转换后的 BF16 tensor 加载。&lt;/p&gt;
&lt;p&gt;正式转换完成后，我再次对照原模型做严格校验：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python scripts/validate_dsv4_checkpoint.py converted \
  &quot;$MODEL_PATH&quot; \
  --reference &quot;$ORIGINAL_MODEL&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最终输出是 48 个 shards、71,927 个 tensors、173,182,190,584 data bytes；35,328
个 expert tensors 和 4,680 个 MTP tensors 都被完整映射。&lt;/p&gt;
&lt;h2&gt;七、A100 支持改动&lt;/h2&gt;
&lt;p&gt;运行时适配采用 &lt;code&gt;sitecustomize.py&lt;/code&gt; monkeypatch，而不是直接修改 SGLang 源码。&lt;/p&gt;
&lt;p&gt;启动脚本设置：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export PYTHONPATH=&quot;${PROJECT_ROOT}:${SGLANG_ROOT}/python:${PYTHONPATH}&quot;
export ENABLE_SGLANG_DSV4_A100_PATCH=1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Python 启动时自动导入 &lt;code&gt;sitecustomize.py&lt;/code&gt;，然后执行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;from dsv4_a100_patch import apply_patch

apply_patch()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样做有两个好处：补丁和 SGLang 源码边界清晰；去掉环境变量或 &lt;code&gt;PYTHONPATH&lt;/code&gt;
就能快速回到原生 SGLang，便于做对照和定位问题。&lt;/p&gt;
&lt;h3&gt;7.1 DeepSeek V4 默认参数&lt;/h3&gt;
&lt;p&gt;补丁强制使用 DeepSeek V4 attention backend、page size 256 和 BF16 KV cache。
基础模式最大 running requests 默认为 256；DSpark 模式让 SGLang speculative hook
选择更小的 48，避免启动时捕获大量无用 verify CUDA Graph。&lt;/p&gt;
&lt;h3&gt;7.2 MXFP4 expert 的 A100 计算路径&lt;/h3&gt;
&lt;p&gt;A100 没有原生 FP4 MMA，所以我采用的计算路径是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;INT8 activation x decoded INT8 weight
    -&amp;gt; INT32 accumulate
    -&amp;gt; activation scale * channel scale
    -&amp;gt; BF16 output
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加载时，原始 MXFP4 E2M1 code 和 UE8M0 block scale 被重排为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;remapped packed MXFP4 code
+ packed 2-bit shift
+ FP32 per-channel scale
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;运行时整数解码近似为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;weight_i8 = e2m1_lut_x2[fp4_code] &amp;lt;&amp;lt; shift2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最大的值是 &lt;code&gt;12 &amp;lt;&amp;lt; 3 = 96&lt;/code&gt;，能够放入有符号 INT8。activation 则按 token 动态
量化：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;scale = max(abs(row)) / 127
q_i8 = round(row / scale), clamp to [-127, 127]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MoE 前向流程为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;hidden states
  -&amp;gt; per-token INT8 quantization
  -&amp;gt; 按 expert 对 routed token 对齐
  -&amp;gt; W13 grouped GEMM
  -&amp;gt; SwiGLU
  -&amp;gt; 再次 per-route INT8 quantization
  -&amp;gt; W2 grouped GEMM
  -&amp;gt; 按 top-6 router weight 归并
  -&amp;gt; BF16 hidden states
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MXFP4 重排不是数学上的无损变换。一些原 block scale 无法被 2-bit shift 精确表达，
因此预处理会选择最接近的 E2M1 code。这是为了在 A100 上保留低显存权重表示并使用
INT8 Tensor Core 所做的明确精度折中。&lt;/p&gt;
&lt;p&gt;CUTLASS kernel 通过 SGLang JIT/TVM FFI 编译。第一次启动会为不同 &lt;code&gt;block_m&lt;/code&gt;、
&lt;code&gt;block_n&lt;/code&gt;、W13/W2 组合生成 SM80 模块，并在 CUDA Graph capture 前初始化动态共享
内存属性。&lt;/p&gt;
&lt;h3&gt;7.3 BF16 KV cache&lt;/h3&gt;
&lt;p&gt;原始 DeepSeek V4 路径包含 A100 不适合直接使用的 packed FP8 cache。补丁替换了
SWA、C4 和 C128 KV pool，使 attention cache 按 BF16 存储。&lt;/p&gt;
&lt;p&gt;同时修复了一个 v0.5.16 迁移细节：SGLang &lt;code&gt;_make_kv_pool&lt;/code&gt; 通过默认参数捕获原来的
FP8 pool class，仅替换模块变量并不会影响已经捕获的默认值。因此补丁必须显式
覆盖 factory，确保所有 pool 都使用 BF16 layout。&lt;/p&gt;
&lt;p&gt;内存 configurator 也同步改写，否则 SGLang 会按错误的 cache 字节数估算
&lt;code&gt;max_total_num_tokens&lt;/code&gt;，轻则浪费显存，重则启动后 OOM。&lt;/p&gt;
&lt;h3&gt;7.4 INT8 indexer&lt;/h3&gt;
&lt;p&gt;DeepSeek V4 的 C4 indexer 为 query 选择稀疏 attention page。补丁使用 INT8
indexer cache，每行额外保存 FP32 scale，并用 Triton kernel 完成 query 投影、
RoPE、Hadamard、量化和 paged MQA logits。&lt;/p&gt;
&lt;p&gt;长 prefill 时，每个 TP rank 不再重复处理全部 query token，而是按 token 维度分块：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;T = total query tokens
W = attention TP size
chunk = ceil(T / W)

rank r 处理 [r * chunk, (r + 1) * chunk)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;每个 rank 仍读取完整历史 KV，因此本地 top-k 是针对完整上下文计算的。完成后使用
attention TP group all-gather，恢复全局 token 顺序。&lt;/p&gt;
&lt;h3&gt;7.5 A100 稀疏注意力&lt;/h3&gt;
&lt;p&gt;补丁实现了 SM80 Triton direct sparse attention，将以下操作融合到同一个 kernel：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;sparse page gather
-&amp;gt; QK
-&amp;gt; online softmax
-&amp;gt; PV
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;单 source 处理 SWA；双 source 将 SWA 与 C4/C128 作为同一个逻辑 sparse set，在
统一 softmax 分母中归一化。&lt;/p&gt;
&lt;p&gt;Prefill 和 decode 可以共用 kernel，因为 causal 约束已经由 metadata 展开成每个
query row 的 &lt;code&gt;page_indices&lt;/code&gt; 和 &lt;code&gt;lengths&lt;/code&gt;。kernel 只访问：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;indices[token, :lengths[token]]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7.6 TP=4 的 Q-head padding 修复&lt;/h3&gt;
&lt;p&gt;迁移到 SGLang v0.5.16 时遇到一个非常危险的问题：服务能够启动，也能返回 HTTP
200，但生成内容错误。&lt;/p&gt;
&lt;p&gt;原因是 TP=4 下的 Q tensor 布局是“有效 local heads 位于零前缀，后面是 padding”，
而不是每个 TP rank 从全局 head tensor 中取自己的连续 rank 分片。旧逻辑在 rank
1 到 rank 3 上选择到了 padding。&lt;/p&gt;
&lt;p&gt;正确逻辑是所有 rank 都取前 &lt;code&gt;local_heads&lt;/code&gt;，并把输出写回同样的零前缀位置。这个
问题让我意识到，服务 ready 和 HTTP 200 都不能证明模型部署正确，必须加入确定性
短生成和内容断言。&lt;/p&gt;
&lt;h3&gt;7.7 DSpark 和 MTP&lt;/h3&gt;
&lt;p&gt;0731 checkpoint 自带 &lt;code&gt;mtp.*&lt;/code&gt; 和 DSpark 权重，不需要第二个 draft model 目录。&lt;/p&gt;
&lt;p&gt;补丁分别处理 &lt;code&gt;DeepseekV4ForCausalLMNextN&lt;/code&gt; 和
&lt;code&gt;DeepseekV4ForCausalLMDSpark&lt;/code&gt;，让 draft model 的 routed experts 继续进入 A100
MXFP4/INT8 路径。&lt;/p&gt;
&lt;p&gt;DSpark 还有一个关键点：转换后的 &lt;code&gt;mtp.0.main_proj&lt;/code&gt; 必须保持 BF16。如果它被 FP8
loader 再量化成 Marlin FP8，target 最终输出可能仍然正确，但 draft acceptance
会接近 0，表面上“服务正常”，实际上 speculative decoding 完全没有加速。&lt;/p&gt;
&lt;p&gt;修复前观察值约为：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;accept len ~= 1
accept rate ~= 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;修复后观察值：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;单请求：accept len=4.38, accept rate=0.68
并发 8：accept len=2.71, accept rate=0.34
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这也是为什么部署 speculative decoding 时不能只检查输出正确性，还必须检查
accept length、accept rate 和实际吞吐。&lt;/p&gt;
&lt;h3&gt;7.8 SGLang v0.5.16 API 漂移适配&lt;/h3&gt;
&lt;p&gt;从旧 patch 迁移到 v0.5.16 时，还适配了以下内部接口变化：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JIT compile context 和 dependency registry 的模块位置变化。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_compute_kv_to_cache&lt;/code&gt; 新增 &lt;code&gt;attn_backend&lt;/code&gt; 参数。&lt;/li&gt;
&lt;li&gt;SWA cache location 改为通过 backend 获取。&lt;/li&gt;
&lt;li&gt;fused norm/rope 和 compressor rope 模块位置变化。&lt;/li&gt;
&lt;li&gt;Attention TP all-gather helper 改名。&lt;/li&gt;
&lt;li&gt;Indexer prepare 函数的参数、返回值和 metadata 生命周期变化。&lt;/li&gt;
&lt;li&gt;已删除的 unified-attention fallback 不再保留，A100 路径要求 direct attention。&lt;/li&gt;
&lt;li&gt;DSpark 直接使用 &lt;code&gt;DSPARK&lt;/code&gt; algorithm，不再依赖旧环境变量。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这种 monkeypatch 会直接触碰内部 API，因此每次升级 SGLang 都必须重新做完整回归，
不能假设小版本升级天然兼容。&lt;/p&gt;
&lt;h2&gt;八、安全准备&lt;/h2&gt;
&lt;p&gt;服务监听 &lt;code&gt;0.0.0.0&lt;/code&gt;，因此必须开启 API key。我没有把 key 写进启动命令，而是保存
在权限为 &lt;code&gt;0600&lt;/code&gt; 的文件中：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd &quot;$PATCH_ROOT&quot;
mkdir -p secrets
umask 077
openssl rand -hex 32 &amp;gt; secrets/sglang_api_key
chmod 600 secrets/sglang_api_key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;启动 wrapper 在 Python 进程内部读取 key，再构造 SGLang &lt;code&gt;ServerArgs&lt;/code&gt;。补丁还会
对 &lt;code&gt;ServerArgs.__repr__&lt;/code&gt; 和 &lt;code&gt;/server_info&lt;/code&gt; 返回值中的 &lt;code&gt;api_key&lt;/code&gt;、
&lt;code&gt;admin_api_key&lt;/code&gt; 做脱敏。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;secrets/&lt;/code&gt;、&lt;code&gt;logs/&lt;/code&gt;、&lt;code&gt;cache/&lt;/code&gt; 和 &lt;code&gt;test-results/&lt;/code&gt; 都加入 &lt;code&gt;.gitignore&lt;/code&gt;。博客、issue、
截图和 shell history 中不应出现真实 key 或内部代理地址。&lt;/p&gt;
&lt;h2&gt;九、启动前 fail-fast 检查&lt;/h2&gt;
&lt;p&gt;正式启动脚本会先运行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python scripts/check_compatibility.py \
  --sglang-root &quot;$SGLANG_ROOT&quot; \
  --model-path &quot;$MODEL_PATH&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;它会拒绝以下情况：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SGLang commit 或版本不匹配。&lt;/li&gt;
&lt;li&gt;可见 GPU 不是正好四张。&lt;/li&gt;
&lt;li&gt;任意 GPU 不是 compute capability 8.0。&lt;/li&gt;
&lt;li&gt;模型 architecture 不是 &lt;code&gt;DeepseekV4ForCausalLM&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;DSpark 配置字段不匹配 0731。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sitecustomize&lt;/code&gt; patch 没有生效。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这个检查放在模型加载之前，可以避免等权重加载和 JIT 数分钟之后才发现环境根本
不受支持。&lt;/p&gt;
&lt;h2&gt;十、启动基础模式&lt;/h2&gt;
&lt;p&gt;基础模式禁用 speculative decoding，便于先确认 target model 本身正确：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd &quot;$PATCH_ROOT&quot;

CUDA_VISIBLE_DEVICES=0,1,2,3 \
SGLANG_ROOT=&quot;$SGLANG_ROOT&quot; \
MODEL_PATH=&quot;$MODEL_PATH&quot; \
API_KEY_FILE=$PWD/secrets/sglang_api_key \
bash scripts/launch_dsv4_flash_0731_tp4.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;关键 SGLang 参数是：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;--dtype bfloat16
--quantization fp8
--moe-runner-backend marlin
--tp-size 4
--mem-fraction-static 0.70
--reasoning-parser deepseek-v4
--tool-call-parser deepseekv4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再强调一次：&lt;code&gt;--quantization fp8&lt;/code&gt; 是进入 DeepSeek V4/MXFP4 loader 的入口，不代表
普通矩阵最终仍使用 FP8。&lt;code&gt;Mxfp4MarlinMoEMethod&lt;/code&gt; 也已经被 patch 替换，真正运行
的是 A100 MXFP4/INT8 backend。&lt;/p&gt;
&lt;p&gt;基础模式加入 &lt;code&gt;--skip-server-warmup&lt;/code&gt;。日志默认写入：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;logs/server-tp4-&amp;lt;UTC timestamp&amp;gt;.log
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;十一、启动 DSpark 模式&lt;/h2&gt;
&lt;p&gt;确认基础模式正确后，再启动 DSpark：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd &quot;$PATCH_ROOT&quot;
API_KEY_FILE=$PWD/secrets/sglang_api_key \
bash scripts/launch_dsv4_flash_0731_tp4_dspark.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DSpark wrapper 设置 &lt;code&gt;ENABLE_DSPARK=1&lt;/code&gt;，主脚本最终增加：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;--speculative-algorithm DSPARK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DSpark 模式不能添加 &lt;code&gt;--skip-server-warmup&lt;/code&gt;。第一次启动需要编译
gptq-marlin/TVM FFI 模块、预热 target/draft verify CUDA Graph。必须等日志出现：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;The server is fired up and ready to roll!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再接入外部流量。&lt;/p&gt;
&lt;p&gt;我使用 tmux 管理服务：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;tmux new -s dsv4
cd &quot;$PATCH_ROOT&quot;
bash scripts/launch_dsv4_flash_0731_tp4_dspark.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;按 &lt;code&gt;Ctrl-b d&lt;/code&gt; 离开，通过 &lt;code&gt;tmux attach -t dsv4&lt;/code&gt; 返回。启动脚本已经使用 &lt;code&gt;tee&lt;/code&gt;
记录完整日志，不需要再叠加 &lt;code&gt;nohup&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;十二、API 验证&lt;/h2&gt;
&lt;p&gt;先测试模型列表和鉴权。以下 key 只是占位符：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export BASE_URL=http://127.0.0.1:30000/v1
export SGLANG_API_KEY=&apos;&amp;lt;从安全配置读取&amp;gt;&apos;

curl -sS &quot;$BASE_URL/models&quot; \
  -H &quot;Authorization: Bearer $SGLANG_API_KEY&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chat Completions：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sS &quot;$BASE_URL/chat/completions&quot; \
  -H &quot;Authorization: Bearer $SGLANG_API_KEY&quot; \
  -H &apos;Content-Type: application/json&apos; \
  -d &apos;{
    &quot;model&quot;:&quot;deepseek-v4-flash-0731&quot;,
    &quot;messages&quot;:[{&quot;role&quot;:&quot;user&quot;,&quot;content&quot;:&quot;Reply with exactly: CHAT_OK&quot;}],
    &quot;temperature&quot;:0,
    &quot;max_tokens&quot;:32
  }&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Responses API：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -sS &quot;$BASE_URL/responses&quot; \
  -H &quot;Authorization: Bearer $SGLANG_API_KEY&quot; \
  -H &apos;Content-Type: application/json&apos; \
  -d &apos;{
    &quot;model&quot;:&quot;deepseek-v4-flash-0731&quot;,
    &quot;input&quot;:&quot;Reply with exactly: RESPONSES_OK&quot;,
    &quot;max_output_tokens&quot;:32,
    &quot;store&quot;:false
  }&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自动回归：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python scripts/smoke_test_api.py \
  --base-url http://127.0.0.1:30000 \
  --api-key-file secrets/sglang_api_key \
  --suite full
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DSpark 模式使用：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python scripts/smoke_test_api.py \
  --base-url http://127.0.0.1:30000 \
  --api-key-file secrets/sglang_api_key \
  --suite full \
  --tool-choice-mode auto
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;SGLang v0.5.16 的 DFlash validator 不允许 speculative decoding 与 grammar
constraint 同时使用。因此 DSpark 支持普通文本、stream、多轮和
&lt;code&gt;tool_choice=auto&lt;/code&gt;，不支持强制 JSON grammar 或 &lt;code&gt;tool_choice=required/any&lt;/code&gt;。
需要强制工具调用时，应停止 DSpark 并使用基础模式。&lt;/p&gt;
&lt;p&gt;另外，当前 Responses request schema 的 &lt;code&gt;tool_choice&lt;/code&gt; 仅接受字符串
&lt;code&gt;auto/required/none&lt;/code&gt;，不接受指定函数的对象。需要 named function choice 时，可以
改用 Chat Completions。&lt;/p&gt;
&lt;h2&gt;十三、长上下文和并发性能实测&lt;/h2&gt;
&lt;h3&gt;13.1 先区分功能验证和性能压测&lt;/h3&gt;
&lt;p&gt;仓库中的 &lt;code&gt;long_context_api.py&lt;/code&gt; 走带鉴权的 OpenAI Chat Completions，用来确认长
上下文请求能完成、usage 计数正确且模型返回指定文本：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;python scripts/long_context_api.py \
  --base-url http://127.0.0.1:30000 \
  --api-key-file secrets/sglang_api_key \
  --target-prompt-tokens 8192,32768 \
  --max-tokens 32 \
  --output test-results/long-context-chat-api.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;本次重新实测结果如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;目标 prompt tokens&lt;/th&gt;
&lt;th&gt;Chat 实际 tokens&lt;/th&gt;
&lt;th&gt;completion tokens&lt;/th&gt;
&lt;th&gt;单次耗时&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8,192&lt;/td&gt;
&lt;td&gt;8,214&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1.533s&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;td&gt;32,790&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;2.994s&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这里多出的 22 tokens 来自 system/user 消息包装。这个脚本使用重复文本构造输入，
两个请求顺序执行，第二个请求还可能命中第一个请求留下的公共前缀；同时模型只生成
6 tokens 就遇到 EOS。因此这张表只说明 Chat API 功能通过，单次耗时不能作为长
上下文性能结论。&lt;/p&gt;
&lt;h3&gt;13.2 使用 SGLang 官方 serving benchmark&lt;/h3&gt;
&lt;p&gt;性能测试改用 SGLang v0.5.16 自带的
&lt;a href=&quot;https://github.com/sgl-project/sglang/blob/fdebc938f7f4d16fe6b9f55dcd9a767cf0899ea1/python/sglang/benchmark/serving.py&quot;&gt;&lt;code&gt;sglang.benchmark.serving&lt;/code&gt;&lt;/a&gt;。
它直接请求 SGLang native &lt;code&gt;/generate&lt;/code&gt; 接口，并同时给出输出吞吐、E2E latency、TTFT、
TPOT 和 speculative accept length。旧入口 &lt;code&gt;python -m sglang.bench_serving&lt;/code&gt; 在这个
版本中已经提示 deprecated。&lt;/p&gt;
&lt;p&gt;正式测量前我做了以下控制：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;scripts/launch_dsv4_flash_0731_tp4_dspark.sh&lt;/code&gt; 启动，确认日志出现
&lt;code&gt;Initialized DSpark draft runner&lt;/code&gt;，并等待 target/draft CUDA Graph 和服务内建
warmup 全部完成。&lt;/li&gt;
&lt;li&gt;预热完成后确认四张 A100 利用率回到 0%，温度约 30-37 摄氏度，再开始记录。&lt;/li&gt;
&lt;li&gt;本次调度容器有 cpuset 限制，启动时额外设置了 &lt;code&gt;SGLANG_SET_CPU_AFFINITY=0&lt;/code&gt;；
它只关闭 SGLang 自动 CPU 绑核，不改变模型、GPU 或 DSpark 参数。没有此限制的
完整主机可以保留启动脚本默认值。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;random-ids&lt;/code&gt;、&lt;code&gt;--tokenize-prompt&lt;/code&gt; 和 &lt;code&gt;--random-range-ratio 1.0&lt;/code&gt; 精确控制
输入/输出长度；工具默认发送 &lt;code&gt;ignore_eos=true&lt;/code&gt;，保证每个请求生成满 128 tokens。&lt;/li&gt;
&lt;li&gt;每轮先按目标并发预热，再用 &lt;code&gt;--flush-cache&lt;/code&gt; 清空 Radix Cache；固定 seed，并交错
concurrency 1/4/8 的测试顺序，减少缓存、温升和先后顺序偏差。&lt;/li&gt;
&lt;li&gt;concurrency 1 使用 16 请求/轮，concurrency 4 和 8 分别使用 32 和 64 请求/轮，
保证每个档位都有多个满批次；每个档位正式测五轮。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;单个并发档位的复现命令如下，修改 &lt;code&gt;CONCURRENCY&lt;/code&gt; 和 &lt;code&gt;NUM_PROMPTS&lt;/code&gt; 后重复五轮：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export OPENAI_API_KEY=&quot;$(&amp;lt; secrets/sglang_api_key)&quot;
MODEL_PATH=/path/to/DeepSeek-V4-Flash-0731-MoE-MXFP4-BF16
CONCURRENCY=8
NUM_PROMPTS=64

PYTHONPATH=/path/to/sglang/python \
python -m sglang.benchmark.serving \
  --backend sglang \
  --base-url http://127.0.0.1:30000 \
  --model &quot;$MODEL_PATH&quot; \
  --served-model-name deepseek-v4-flash-0731 \
  --tokenizer &quot;$MODEL_PATH&quot; \
  --dataset-name random-ids \
  --num-prompts &quot;$NUM_PROMPTS&quot; \
  --random-input-len 1024 \
  --random-output-len 128 \
  --random-range-ratio 1.0 \
  --tokenize-prompt \
  --request-rate inf \
  --max-concurrency &quot;$CONCURRENCY&quot; \
  --temperature 0 \
  --warmup-requests &quot;$CONCURRENCY&quot; \
  --flush-cache \
  --disable-tqdm \
  --seed 20260803 \
  --output-file test-results/bench-serving-dspark.jsonl
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;固定 1,024 input tokens + 128 output tokens 的五轮结果为：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;并发&lt;/th&gt;
&lt;th&gt;请求/轮&lt;/th&gt;
&lt;th&gt;五轮输出吞吐（tok/s）&lt;/th&gt;
&lt;th&gt;中位数&lt;/th&gt;
&lt;th&gt;范围&lt;/th&gt;
&lt;th&gt;CV（变异系数）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;106.99 / 106.98 / 107.01 / 115.17 / 112.90&lt;/td&gt;
&lt;td&gt;107.01&lt;/td&gt;
&lt;td&gt;106.98-115.17&lt;/td&gt;
&lt;td&gt;3.59%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;208.11 / 237.01 / 245.05 / 221.40 / 242.39&lt;/td&gt;
&lt;td&gt;237.01&lt;/td&gt;
&lt;td&gt;208.11-245.05&lt;/td&gt;
&lt;td&gt;6.78%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;319.90 / 308.06 / 328.41 / 306.01 / 314.54&lt;/td&gt;
&lt;td&gt;314.54&lt;/td&gt;
&lt;td&gt;306.01-328.41&lt;/td&gt;
&lt;td&gt;2.89%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;辅助指标同样取五轮中位数：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;并发&lt;/th&gt;
&lt;th&gt;median TTFT&lt;/th&gt;
&lt;th&gt;median TPOT&lt;/th&gt;
&lt;th&gt;DSpark average accept length&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;239.16ms&lt;/td&gt;
&lt;td&gt;7.48ms&lt;/td&gt;
&lt;td&gt;2.52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;252.61ms&lt;/td&gt;
&lt;td&gt;14.19ms&lt;/td&gt;
&lt;td&gt;2.51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;267.08ms&lt;/td&gt;
&lt;td&gt;23.40ms&lt;/td&gt;
&lt;td&gt;2.52&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;15 个正式轮次共完成 560 个请求、573,440 input tokens 和 71,680 output tokens；
全部请求成功，并且每个请求都准确生成 128 tokens。&lt;/p&gt;
&lt;p&gt;这是一组固定 shape、无限到达率下的合成饱和吞吐测试，回答的是“把请求持续灌满
时能处理多少 token”。它不能单独回答真实流量下的用户延迟，因此还需要下面的
ShareGPT open-loop 测试。&lt;/p&gt;
&lt;h3&gt;13.3 ShareGPT 在线 open-loop benchmark&lt;/h3&gt;
&lt;p&gt;在线延迟测试继续使用同一个 SGLang 官方工具，数据改为其默认的
&lt;a href=&quot;https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered&quot;&gt;ShareGPT_V3_unfiltered_cleaned_split.json&lt;/a&gt;。
本文锁定的 v0.5.16 实现会保留每条样本最前面的 user/assistant 两个 turn，将 user
内容作为 prompt，并以 assistant 内容的 token 数作为目标输出长度。有限
&lt;code&gt;--request-rate&lt;/code&gt; 使用指数分布生成请求
间隔，即 Poisson 到达，而不是“上一批完成后再补请求”的固定并发闭环。&lt;/p&gt;
&lt;p&gt;SGLang v0.5.16 自己的
&lt;a href=&quot;https://github.com/sgl-project/sglang/blob/fdebc938f7f4d16fe6b9f55dcd9a767cf0899ea1/test/registered/perf/test_bench_serving_1gpu_part1.py#L124-L144&quot;&gt;&lt;code&gt;test_online_latency_default&lt;/code&gt;&lt;/a&gt;
也是 100 个请求、&lt;code&gt;request_rate=1&lt;/code&gt;。本文保留这个官方在线延迟点，再向两侧扩展为
0.5/1/2/3 req/s 的 rate sweep，以观察吞吐和排队延迟的拐点。&lt;/p&gt;
&lt;p&gt;本次从数据集中固定抽取 seed=42 的 100 条请求，限制 prompt + output 不超过 32K，
不覆盖 ShareGPT 原生输出长度。每个 QPS 档位预热 8 条请求、清空 Radix Cache 后
正式跑 100 条，完整 sweep 重复两轮。未设置 &lt;code&gt;--max-concurrency&lt;/code&gt;，避免客户端限流
掩盖服务过载；流式输出和默认 &lt;code&gt;ignore_eos=true&lt;/code&gt; 保持开启，使每条请求生成到数据集
给定长度。&lt;/p&gt;
&lt;p&gt;这 100 条请求的 token 分布为：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;长度&lt;/th&gt;
&lt;th&gt;mean&lt;/th&gt;
&lt;th&gt;P50&lt;/th&gt;
&lt;th&gt;P95&lt;/th&gt;
&lt;th&gt;max&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;prompt tokens&lt;/td&gt;
&lt;td&gt;350.84&lt;/td&gt;
&lt;td&gt;158&lt;/td&gt;
&lt;td&gt;1,329.10&lt;/td&gt;
&lt;td&gt;3,588&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;output tokens&lt;/td&gt;
&lt;td&gt;236.76&lt;/td&gt;
&lt;td&gt;173&lt;/td&gt;
&lt;td&gt;688.70&lt;/td&gt;
&lt;td&gt;821&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prompt + output&lt;/td&gt;
&lt;td&gt;587.60&lt;/td&gt;
&lt;td&gt;415.50&lt;/td&gt;
&lt;td&gt;1,855.95&lt;/td&gt;
&lt;td&gt;3,681&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;官方脚本可以自动下载数据集，因此复现时不需要把本机 Hugging Face cache 路径写进
命令。单轮 sweep 如下；再执行一遍即可得到本文的两轮结果：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export OPENAI_API_KEY=&quot;$(&amp;lt; secrets/sglang_api_key)&quot;
export HF_HOME=/path/to/huggingface-cache
export PYTHONPATH=/path/to/sglang/python
MODEL_PATH=/path/to/DeepSeek-V4-Flash-0731-MoE-MXFP4-BF16

for RATE in 0.5 1 2 3; do
  python -m sglang.benchmark.serving \
    --backend sglang \
    --base-url http://127.0.0.1:30000 \
    --model &quot;$MODEL_PATH&quot; \
    --served-model-name deepseek-v4-flash-0731 \
    --tokenizer &quot;$MODEL_PATH&quot; \
    --dataset-name sharegpt \
    --sharegpt-context-len 32768 \
    --num-prompts 100 \
    --request-rate &quot;$RATE&quot; \
    --temperature 0 \
    --warmup-requests 8 \
    --flush-cache \
    --disable-tqdm \
    --seed 42 \
    --output-details \
    --output-file test-results/bench-serving-sharegpt-online-dspark.jsonl
done
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;两轮在每个档位共完成 200 条请求。吞吐按两轮总 token / 总测试时间聚合，平均并发
也按测试时间加权；两轮输出吞吐范围单独列出，以免隐藏运行波动：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;到达率&lt;/th&gt;
&lt;th&gt;完成速率&lt;/th&gt;
&lt;th&gt;输出吞吐&lt;/th&gt;
&lt;th&gt;两轮输出吞吐范围&lt;/th&gt;
&lt;th&gt;平均并发&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.5 req/s&lt;/td&gt;
&lt;td&gt;0.538 req/s&lt;/td&gt;
&lt;td&gt;127.46 tok/s&lt;/td&gt;
&lt;td&gt;127.45-127.47 tok/s&lt;/td&gt;
&lt;td&gt;1.62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 req/s&lt;/td&gt;
&lt;td&gt;1.040 req/s&lt;/td&gt;
&lt;td&gt;246.17 tok/s&lt;/td&gt;
&lt;td&gt;246.00-246.35 tok/s&lt;/td&gt;
&lt;td&gt;3.45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 req/s&lt;/td&gt;
&lt;td&gt;1.948 req/s&lt;/td&gt;
&lt;td&gt;461.12 tok/s&lt;/td&gt;
&lt;td&gt;458.09-464.20 tok/s&lt;/td&gt;
&lt;td&gt;12.59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 req/s&lt;/td&gt;
&lt;td&gt;2.438 req/s&lt;/td&gt;
&lt;td&gt;577.14 tok/s&lt;/td&gt;
&lt;td&gt;556.34-599.55 tok/s&lt;/td&gt;
&lt;td&gt;27.28&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这里的到达率是 Poisson 分布参数，完成速率是有限样本下的完成请求数 / 整段测试
时间；两者不会严格相等。尤其 3 req/s 档还包含最后一批积压请求的排空时间。&lt;/p&gt;
&lt;p&gt;下面的百分位不是“两个 P95/P99 再平均”，而是从 &lt;code&gt;--output-details&lt;/code&gt; 保存的逐请求
TTFT 和逐 token ITL 重建 E2E/TPOT，并合并两轮样本后重新计算。重建值与脚本的
单轮 E2E 百分位差异小于 0.1ms。每个档位包含 200 个请求和 47,352 个输出 tokens：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;到达率&lt;/th&gt;
&lt;th&gt;P50 E2E&lt;/th&gt;
&lt;th&gt;P50 TTFT&lt;/th&gt;
&lt;th&gt;P50 TPOT&lt;/th&gt;
&lt;th&gt;P50 ITL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.5 req/s&lt;/td&gt;
&lt;td&gt;2,671.32ms&lt;/td&gt;
&lt;td&gt;437.60ms&lt;/td&gt;
&lt;td&gt;8.87ms&lt;/td&gt;
&lt;td&gt;4.67ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 req/s&lt;/td&gt;
&lt;td&gt;2,626.32ms&lt;/td&gt;
&lt;td&gt;300.93ms&lt;/td&gt;
&lt;td&gt;11.78ms&lt;/td&gt;
&lt;td&gt;5.64ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 req/s&lt;/td&gt;
&lt;td&gt;5,160.67ms&lt;/td&gt;
&lt;td&gt;425.06ms&lt;/td&gt;
&lt;td&gt;24.59ms&lt;/td&gt;
&lt;td&gt;8.95ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 req/s&lt;/td&gt;
&lt;td&gt;9,407.33ms&lt;/td&gt;
&lt;td&gt;1,329.49ms&lt;/td&gt;
&lt;td&gt;43.75ms&lt;/td&gt;
&lt;td&gt;13.28ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;到达率&lt;/th&gt;
&lt;th&gt;P95 / P99 E2E&lt;/th&gt;
&lt;th&gt;P95 / P99 TTFT&lt;/th&gt;
&lt;th&gt;P95 / P99 TPOT&lt;/th&gt;
&lt;th&gt;P95 / P99 ITL&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.5 req/s&lt;/td&gt;
&lt;td&gt;6,999.13 / 10,491.64ms&lt;/td&gt;
&lt;td&gt;2,191.47 / 3,124.93ms&lt;/td&gt;
&lt;td&gt;31.92 / 131.09ms&lt;/td&gt;
&lt;td&gt;22.51 / 51.28ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 req/s&lt;/td&gt;
&lt;td&gt;8,451.42 / 12,659.81ms&lt;/td&gt;
&lt;td&gt;2,241.13 / 3,049.49ms&lt;/td&gt;
&lt;td&gt;30.27 / 114.16ms&lt;/td&gt;
&lt;td&gt;30.33 / 73.47ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 req/s&lt;/td&gt;
&lt;td&gt;16,636.36 / 23,882.79ms&lt;/td&gt;
&lt;td&gt;2,684.88 / 2,920.10ms&lt;/td&gt;
&lt;td&gt;112.51 / 233.09ms&lt;/td&gt;
&lt;td&gt;54.59 / 236.72ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3 req/s&lt;/td&gt;
&lt;td&gt;28,479.37 / 34,387.38ms&lt;/td&gt;
&lt;td&gt;3,207.61 / 3,462.48ms&lt;/td&gt;
&lt;td&gt;216.19 / 387.90ms&lt;/td&gt;
&lt;td&gt;130.52 / 559.35ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;指标口径如下：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TTFT 是从客户端发出请求到收到第一个非空流式 token 的时间，包含排队和
prefill。如果把首个有效流式数据包称为 TTFP，那么它在这里对应同一个观测点；
SGLang 官方输出的指标名仍是 TTFT。&lt;/li&gt;
&lt;li&gt;TPOT 是单个请求扣除首 token 后，每个输出 token 的平均耗时。&lt;/li&gt;
&lt;li&gt;ITL 是相邻输出 token 的流式到达间隔。DSpark 一次 stream chunk 可能接受多个
token，SGLang 官方 native benchmark 会用该 chunk 的时间间隔除以新增 token 数，
再展开成逐 token ITL。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果显示，增加请求率会提高连续批处理利用率，所以输出吞吐从 127.46 tok/s 提升到
577.14 tok/s；但这不代表单个用户更快。到达率从 1 增加到 2 req/s 后，P50 TPOT
从 11.78ms 增至 24.59ms，P95 E2E 从 8.45s 增至 16.64s。3 req/s 时到达率已经
高于服务对这组 ShareGPT 长度分布的持续完成能力，完成速率只有 2.438 req/s，平均
并发升到 27.28，P95 E2E 达 28.48s。若以延迟而不是峰值吞吐为目标，这套硬件和
模型配置更适合把持续流量控制在 1 req/s 左右，并结合业务 SLO 再做更细的 1-2
req/s 区间扫描。&lt;/p&gt;
&lt;p&gt;两轮间也存在明显波动。例如 0.5 req/s 的单轮 median TTFT 分别为 1,041.09ms 和
238.54ms，3 req/s 的单轮输出吞吐分别为 556.34 和 599.55 tok/s。因此本文保留
轮间范围并使用 pooled 百分位，不把某一轮最好成绩当作稳定性能。以上在线指标针对
SGLang native &lt;code&gt;/generate&lt;/code&gt;；Chat/Responses 的协议功能由前面的 API 回归覆盖，若要
评估生产网关，还应在真实 HTTPS 路径上重复相同 workload。&lt;/p&gt;
&lt;h3&gt;13.4 8K/32K 长上下文 benchmark&lt;/h3&gt;
&lt;p&gt;长上下文继续使用相同官方工具，固定 concurrency 1、每轮 5 个请求、每个请求输出
128 tokens，各做五轮。以 32K 为例：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;export OPENAI_API_KEY=&quot;$(&amp;lt; secrets/sglang_api_key)&quot;
MODEL_PATH=/path/to/DeepSeek-V4-Flash-0731-MoE-MXFP4-BF16
INPUT_LEN=32768

PYTHONPATH=/path/to/sglang/python \
python -m sglang.benchmark.serving \
  --backend sglang \
  --base-url http://127.0.0.1:30000 \
  --model &quot;$MODEL_PATH&quot; \
  --served-model-name deepseek-v4-flash-0731 \
  --tokenizer &quot;$MODEL_PATH&quot; \
  --dataset-name random-ids \
  --num-prompts 5 \
  --random-input-len &quot;$INPUT_LEN&quot; \
  --random-output-len 128 \
  --random-range-ratio 1.0 \
  --tokenize-prompt \
  --request-rate inf \
  --max-concurrency 1 \
  --temperature 0 \
  --warmup-requests 1 \
  --flush-cache \
  --disable-tqdm \
  --seed 20260803 \
  --output-file test-results/bench-serving-long-context-dspark.jsonl
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;目标 input tokens&lt;/th&gt;
&lt;th&gt;实际 input tokens&lt;/th&gt;
&lt;th&gt;正式请求&lt;/th&gt;
&lt;th&gt;五轮 median E2E&lt;/th&gt;
&lt;th&gt;五轮范围&lt;/th&gt;
&lt;th&gt;median TTFT&lt;/th&gt;
&lt;th&gt;结果&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8,192&lt;/td&gt;
&lt;td&gt;8,192&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;1.727s&lt;/td&gt;
&lt;td&gt;1.220-1.965s&lt;/td&gt;
&lt;td&gt;744.20ms&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;td&gt;32,768&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;3.606s&lt;/td&gt;
&lt;td&gt;3.490-4.047s&lt;/td&gt;
&lt;td&gt;2,978.69ms&lt;/td&gt;
&lt;td&gt;PASS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;每轮的请求中位 E2E 分别为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;8K：1.879 / 1.965 / 1.220 / 1.727 / 1.549 秒。&lt;/li&gt;
&lt;li&gt;32K：4.047 / 3.731 / 3.606 / 3.490 / 3.502 秒。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;10 个正式轮次共处理 1,024,000 input tokens 和 6,400 output tokens，50 个请求的
输入长度和输出长度全部与设定一致。8K 结果的轮间波动较明显，因此这里报告中位数
和范围，而不是挑最好的一轮；32K 的 median E2E 轮间 CV 为 6.24%。&lt;/p&gt;
&lt;h3&gt;13.5 如何看待旧数据&lt;/h3&gt;
&lt;p&gt;旧并发脚本使用很短的 Chat prompt，默认每个并发档位只发送一批请求，整个测试只做
一次公共 warmup，也没有每轮清缓存。因此原来的 119.99 / 152.73 / 358.89 tok/s
可以视为当时那一次请求的观测值，但不足以作为稳定 benchmark；尤其 concurrency 4
明显低于新测试的五轮范围。新旧测试的 input 长度也不同，不能直接计算性能涨跌。&lt;/p&gt;
&lt;p&gt;同理，旧长上下文表中的 4.734s / 7.285s 不是“错误结果”，而是 Chat API、重复
前缀、可提前 EOS 和单次计时共同得到的结果。本次同一功能脚本重跑为 1.533s /
2.994s，进一步说明这种单次耗时不适合作为性能数字。博客中的正式性能结论以固定
token shape、预热、清缓存和五轮中位数为准。&lt;/p&gt;
&lt;p&gt;性能之外，我也没有只检查请求是否返回 200，而是同时检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;输出 token 数是否达到预期。&lt;/li&gt;
&lt;li&gt;返回文本是否符合确定性断言。&lt;/li&gt;
&lt;li&gt;streaming 是否完整结束。&lt;/li&gt;
&lt;li&gt;多轮消息是否保持上下文。&lt;/li&gt;
&lt;li&gt;function tool loop 是否真的返回 tool call 并继续生成。&lt;/li&gt;
&lt;li&gt;未带 key 的请求是否返回 401。&lt;/li&gt;
&lt;li&gt;日志是否出现 CUDA、NCCL、missing weight 或 unexpected weight 错误。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;十四、我遇到和重点防范的问题&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;现象&lt;/th&gt;
&lt;th&gt;根因或处理方式&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SGLang commit mismatch&lt;/td&gt;
&lt;td&gt;Patch 依赖内部 API，切回固定 commit，不跳过正式检查&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;模型缺 shard 或权重&lt;/td&gt;
&lt;td&gt;先运行 original/converted 严格 validator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;原生 MXFP4 报 SM90/SM120&lt;/td&gt;
&lt;td&gt;A100 patch 未加载，检查&lt;code&gt;PYTHONPATH&lt;/code&gt; 和环境变量&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务 ready 但输出错误&lt;/td&gt;
&lt;td&gt;检查 TP=4 Q-head padding/local-head 选择逻辑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSpark accept rate 接近 0&lt;/td&gt;
&lt;td&gt;确认&lt;code&gt;mtp.0.main_proj&lt;/code&gt; 保持 BF16，没有进入 FP8 Marlin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;首个请求像卡死&lt;/td&gt;
&lt;td&gt;DSpark 必须执行内建 warmup，并等待 JIT/CUDA Graph 完成&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OOM&lt;/td&gt;
&lt;td&gt;确认无残留进程，从&lt;code&gt;mem-fraction-static=0.70&lt;/code&gt; 开始向下调&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NCCL/Bus error&lt;/td&gt;
&lt;td&gt;检查四卡可见性和&lt;code&gt;/dev/shm&lt;/code&gt;，必要时提高到 64GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Responses 返回 400&lt;/td&gt;
&lt;td&gt;检查路径&lt;code&gt;/v1/responses&lt;/code&gt; 和当前 &lt;code&gt;tool_choice&lt;/code&gt; schema&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DSpark 强制工具调用报 400&lt;/td&gt;
&lt;td&gt;Grammar constraint 与 speculative decoding 不兼容，切基础模式&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anthropic system 报 400&lt;/td&gt;
&lt;td&gt;&lt;code&gt;system&lt;/code&gt; 应放请求顶层，不能放进 messages 数组&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;当前环境 &lt;code&gt;/dev/shm=10GB&lt;/code&gt; 已通过 TP=4 实测，但换到其他容器或调度系统后，如果
出现 NCCL shared-memory 错误，仍建议将共享内存提高到至少 64GB。&lt;/p&gt;
&lt;h2&gt;十五、参考项目、迁移动机与工作边界&lt;/h2&gt;
&lt;h3&gt;15.1 我从哪里开始&lt;/h3&gt;
&lt;p&gt;这次工作不是从零发明 A100 kernel。最初让我能够系统理解这条路线的项目是
&lt;a href=&quot;https://github.com/Qeeweew/deepseek-v4-a100-sglang&quot;&gt;Qeeweew/deepseek-v4-a100-sglang&lt;/a&gt;。
它已经完成了几项最困难的基础工作：MXFP4 routed expert 重排、A100 INT8 Tensor
Core MoE、BF16 KV cache、indexer/attention fallback，以及通过 &lt;code&gt;sitecustomize.py&lt;/code&gt;
向 SGLang 注入运行时补丁的整体设计。&lt;/p&gt;
&lt;p&gt;参考项目锁定的 SGLang commit 是
&lt;a href=&quot;https://github.com/sgl-project/sglang/commit/1c0019da7579db73223195f25b0eed3882dff24e&quot;&gt;&lt;code&gt;1c0019da7579db73223195f25b0eed3882dff24e&lt;/code&gt;&lt;/a&gt;。
我以该项目的 &lt;code&gt;d3987e718f0f&lt;/code&gt; 为迁移基线，保留其 Apache-2.0 许可证和原作者信息。&lt;/p&gt;
&lt;h3&gt;15.2 为什么要迁移到新的 SGLang&lt;/h3&gt;
&lt;p&gt;严格来说，参考项目锁定的旧 SGLang 已经有一个早期 &lt;code&gt;/v1/responses&lt;/code&gt; 路由，但
&lt;a href=&quot;https://github.com/sgl-project/sglang/blob/1c0019da7579db73223195f25b0eed3882dff24e/python/sglang/srt/entrypoints/openai/protocol.py#L1276-L1322&quot;&gt;当时的 ResponseTool schema&lt;/a&gt;
只接受 &lt;code&gt;web_search_preview&lt;/code&gt; 和 &lt;code&gt;code_interpreter&lt;/code&gt;，不能接收
&lt;code&gt;{&quot;type&quot;:&quot;function&quot;, ...}&lt;/code&gt; 自定义函数工具。因此它无法支撑 0731 正式版所需的
真实 function tool loop。迁移目标不是简单增加一个同名路由，而是补齐协议、模型
parser、工具调用和多轮回放的完整兼容链路。&lt;/p&gt;
&lt;p&gt;与此同时，我部署的
&lt;a href=&quot;https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-0731&quot;&gt;DeepSeek-V4-Flash-0731&lt;/a&gt;
是取代 preview 的正式版本。官方模型卡明确说明它显著增强了 agentic capabilities，
并且 checkpoint 自带 DSpark speculative decoding 模块。&lt;/p&gt;
&lt;p&gt;我的目标因此不再只是“让模型在 A100 上生成文本”，而是让它能够作为 Agent 模型
接入上层应用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;使用 &lt;code&gt;/v1/chat/completions&lt;/code&gt; 兼容传统 OpenAI 客户端。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;/v1/responses&lt;/code&gt; 承载 reasoning、tool call、tool output 和多轮 Agent loop。&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;/v1/messages&lt;/code&gt; 兼容 Anthropic Messages 客户端。&lt;/li&gt;
&lt;li&gt;使用 0731 自带的 DSpark 权重降低 decode 成本。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这里要区分两个层次：DeepSeek-V4-Flash-0731 提供推理、工具调用和 Agent 能力；
SGLang 则负责把这些能力解析并暴露成 Chat、Responses 或 Anthropic HTTP 协议。
只有模型和服务框架两边都支持，Agent 链路才真正完整。&lt;/p&gt;
&lt;p&gt;因此我将目标 SGLang 升级并固定到 v0.5.16 commit
&lt;a href=&quot;https://github.com/sgl-project/sglang/commit/fdebc938f7f4d16fe6b9f55dcd9a767cf0899ea1&quot;&gt;&lt;code&gt;fdebc938f7f4d16fe6b9f55dcd9a767cf0899ea1&lt;/code&gt;&lt;/a&gt;。
升级不是简单地替换版本号，因为 monkeypatch 依赖的都是 SGLang 内部接口。&lt;/p&gt;
&lt;h3&gt;15.3 这次迁移具体做了什么&lt;/h3&gt;
&lt;p&gt;在参考项目的 A100 计算基础上，这次迁移和补充的主要工作包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;适配 SGLang v0.5.16 的 JIT、KV pool factory、attention backend 和 indexer API。&lt;/li&gt;
&lt;li&gt;修复 TP=4 下 Q tensor 零前缀有效 head 布局，否则 rank 1 到 rank 3 会读取 padding。&lt;/li&gt;
&lt;li&gt;为 DeepSeek-V4-Flash-0731 增加严格 checkpoint validator 和转换后索引校验。&lt;/li&gt;
&lt;li&gt;保留 0731 的 MTP/DSpark 字段与权重，并让 draft experts 进入 A100 MXFP4/INT8 路径。&lt;/li&gt;
&lt;li&gt;保持 DSpark &lt;code&gt;main_proj&lt;/code&gt; 为 BF16，避免输出正确但 acceptance 接近零的性能失效。&lt;/li&gt;
&lt;li&gt;验证 Chat Completions、Responses、Anthropic、stream、多轮和真实 tool loop。&lt;/li&gt;
&lt;li&gt;增加 API key 文件加载、日志脱敏、长上下文、并发和外部代理回归。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;迁移后的完整实现发布在
&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516&quot;&gt;yaleyoou/deepseek-v4-a100-sglang-v0516&lt;/a&gt;。
这个仓库是本文所有转换脚本、A100 patch、启动脚本、测试和实现文档的公开来源。&lt;/p&gt;
&lt;h3&gt;15.4 我如何描述这项工作的边界&lt;/h3&gt;
&lt;p&gt;更准确地说，这次工作的价值不是“从零写出了 DeepSeek V4 A100 kernel”，而是学习
并复用了已有 A100 方案，再把它迁移到 DeepSeek-V4-Flash-0731 和 SGLang v0.5.16，
补齐 Responses/Anthropic/DSpark/安全启动和端到端验证，使它成为一条可以复现、
可以维护、也能够接入 Agent 应用的完整部署链路。&lt;/p&gt;
&lt;h2&gt;十六、这次部署让我学到什么&lt;/h2&gt;
&lt;h3&gt;1. Server ready 不等于模型正确&lt;/h3&gt;
&lt;p&gt;多卡 head 布局错误时，服务可以正常启动并返回 HTTP 200，但内容是错的。部署验收
必须包含确定性 prompt、内容断言和多种 batch/context shape。&lt;/p&gt;
&lt;h3&gt;2. 量化配置也是 loader 协议&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;--quantization fp8&lt;/code&gt; 不一定意味着最终所有算子都执行 FP8。它还决定 SGLang 如何
创建层、如何注册参数、如何选择 MoE method。理解 loader 路由比只看命令行名称
更重要。&lt;/p&gt;
&lt;h3&gt;3. 性能错误可能不会影响正确性&lt;/h3&gt;
&lt;p&gt;DSpark &lt;code&gt;main_proj&lt;/code&gt; 被错误量化时，target 输出仍然正确，但 acceptance 接近 0。
性能功能必须用自己的指标验证，不能用“输出没错”代替。&lt;/p&gt;
&lt;h3&gt;4. 大模型部署是完整系统工程&lt;/h3&gt;
&lt;p&gt;真正的部署范围包括 checkpoint、存储、loader、kernel、cache layout、分布式通信、
CUDA Graph、HTTP 协议、鉴权、日志、代理和回归测试。任何一层都可能让最终服务
失败或悄悄退化。&lt;/p&gt;
&lt;h3&gt;5. 对内部 API 的 patch 必须严格锁版本&lt;/h3&gt;
&lt;p&gt;Monkeypatch 的优点是改动边界清晰，不污染 SGLang 源码；代价是内部接口一旦变化
就必须重新迁移。版本锁定和 fail-fast 是这种方案的一部分，不是额外负担。&lt;/p&gt;
&lt;h2&gt;十七、最终启动清单&lt;/h2&gt;
&lt;p&gt;以后重新部署时，我会按下面的顺序检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[ ]  GPU 是 4 x A100 SM80，&lt;code&gt;CUDA_VISIBLE_DEVICES=0,1,2,3&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;[ ]  SGLang commit 和 version 精确匹配。&lt;/li&gt;
&lt;li&gt;[ ]  原始 checkpoint validator 通过。&lt;/li&gt;
&lt;li&gt;[ ]  转换后 checkpoint validator 通过。&lt;/li&gt;
&lt;li&gt;[ ]  API key 文件权限为 &lt;code&gt;0600&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;[ ]  &lt;code&gt;ENABLE_SGLANG_DSV4_A100_PATCH=1&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;[ ]  Compatibility check 返回 PASS。&lt;/li&gt;
&lt;li&gt;[ ]  四个 TP rank 都加载了 &lt;code&gt;mxfp4_int8&lt;/code&gt; experts。&lt;/li&gt;
&lt;li&gt;[ ]  日志显示 BF16 attention cache + INT8 indexer cache。&lt;/li&gt;
&lt;li&gt;[ ]  DSpark 模式完成 JIT 和 CUDA Graph warmup。&lt;/li&gt;
&lt;li&gt;[ ]  确定性短生成内容正确。&lt;/li&gt;
&lt;li&gt;[ ]  Chat、Responses、Anthropic、stream 和 tool loop 通过。&lt;/li&gt;
&lt;li&gt;[ ]  未鉴权请求返回 401。&lt;/li&gt;
&lt;li&gt;[ ]  8K/32K context 和并发 1/4/8 通过。&lt;/li&gt;
&lt;li&gt;[ ]  ShareGPT open-loop rate sweep 的成功率和 TTFT/TPOT/ITL 符合目标 SLO。&lt;/li&gt;
&lt;li&gt;[ ]  DSpark accept rate 明显大于 0。&lt;/li&gt;
&lt;li&gt;[ ]  日志没有明文 key、missing weight、CUDA 或 NCCL traceback。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;这次部署最有价值的地方，不是最终看到端口监听成功，而是第一次完整理解了一个
模型如何从磁盘上的低精度 checkpoint，经过 loader、运行时权重重排、分布式调度、
GPU kernel、KV cache 和 API 层，最后变成一个可以稳定调用的服务。&lt;/p&gt;
&lt;p&gt;DeepSeek-V4-Flash-0731 并不是天然适配 A100。最终能够运行，是因为把不适合 SM80
的 FP8/FP4 路径拆开处理：普通权重落到 BF16，routed experts 映射到 INT8 Tensor
Core，KV/indexer 和稀疏注意力分别使用 A100 兼容实现，再通过严格版本约束和完整
回归把它们重新组合到 SGLang 服务链路中。&lt;/p&gt;
&lt;p&gt;对我来说，这才算第一次真正“完整部署了一个模型”。&lt;/p&gt;
&lt;h2&gt;参考资料与项目链接&lt;/h2&gt;
&lt;h3&gt;本文实现&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516&quot;&gt;完整代码仓库：yaleyoou/deepseek-v4-a100-sglang-v0516&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/README.md&quot;&gt;项目 README 与快速开始&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/docs/deployment_zh.md&quot;&gt;完整中文部署手册&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/docs/implementation_v0516.md&quot;&gt;SGLang v0.5.16 迁移实现说明&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/docs/model_conversion.md&quot;&gt;模型转换说明&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/docs/mxfp4_int8_moe.md&quot;&gt;MXFP4/INT8 MoE 设计&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/docs/deepseek_v4_sparse_attention.md&quot;&gt;A100 稀疏注意力设计&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/docs/indexer_query_cp.md&quot;&gt;Indexer query-token CP 设计&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/dsv4_a100_patch/patch.py&quot;&gt;运行时 monkeypatch 主入口&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/scripts/launch_dsv4_flash_0731_tp4.sh&quot;&gt;TP=4 基础启动脚本&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yaleyoou/deepseek-v4-a100-sglang-v0516/blob/main/scripts/launch_dsv4_flash_0731_tp4_dspark.sh&quot;&gt;TP=4 DSpark 启动脚本&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;参考项目与上游资料&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/Qeeweew/deepseek-v4-a100-sglang&quot;&gt;A100 patch 参考项目：Qeeweew/deepseek-v4-a100-sglang&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/deepseek-ai/DeepSeek-V4-Flash-0731&quot;&gt;DeepSeek-V4-Flash-0731 官方模型卡&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sgl-project/sglang&quot;&gt;SGLang 官方仓库&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://huggingface.co/datasets/anon8231489123/ShareGPT_Vicuna_unfiltered&quot;&gt;SGLang 官方 ShareGPT benchmark 数据&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sgl-project/sglang/commit/fdebc938f7f4d16fe6b9f55dcd9a767cf0899ea1&quot;&gt;本文锁定的 SGLang v0.5.16 commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sgl-project/sglang/commit/1c0019da7579db73223195f25b0eed3882dff24e&quot;&gt;参考项目锁定的旧 SGLang commit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>笔记</category><category>DeepSeek</category><category>SGLang</category><category>A100</category><category>LLM 部署</category><category>大模型推理</category></item><item><title>从域名和 VPS 到可用节点：一次 3x-ui 部署记录</title><link>https://biumbiu.com/notes/personal-infrastructure/</link><guid isPermaLink="true">https://biumbiu.com/notes/personal-infrastructure/</guid><description>复盘从 Cloudflare DNS、美国 VPS、3x-ui 到 VLESS REALITY 节点、订阅和性能调优的完整过程。</description><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这次的目标并不只是让客户端第一次显示“已连接”，而是搭出一个职责清楚、能够排错、以后也敢继续维护的个人节点。日常交流里经常把它叫作 VPN，但更准确地说，它是由 Xray 提供的代理节点。本文只记录本人设备的合法远程访问与网络技术学习，请遵守所在地法律和服务商条款。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;客户端 → 节点域名 → 美国 VPS → Xray Core → 美国出口 IP&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;本文公开的是架构、配置原则和排错方法。真实 VPS IP、面板路径、用户 UUID、订阅 ID、REALITY 私钥和 Short ID 均不展示；示例统一使用 &lt;code&gt;sub.example.com&lt;/code&gt; 和占位符。&lt;/p&gt;
&lt;h2&gt;先把每个入口的职责分开&lt;/h2&gt;
&lt;p&gt;最终我把根域名和 &lt;code&gt;www&lt;/code&gt; 留给个人网站，节点只使用一个独立子域名。客户端负责使用配置，3x-ui 只负责管理配置，真正处理连接的是 Xray Core。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;个人网站：根域名与 &lt;code&gt;www&lt;/code&gt;，部署在 Cloudflare Pages；&lt;/li&gt;
&lt;li&gt;节点域名：&lt;code&gt;sub.example.com&lt;/code&gt;，直接解析到 VPS，保持 DNS only；&lt;/li&gt;
&lt;li&gt;管理面板：只监听 &lt;code&gt;127.0.0.1&lt;/code&gt;，通过 SSH 隧道访问；&lt;/li&gt;
&lt;li&gt;数据入口：Xray 在公网 &lt;code&gt;443/tcp&lt;/code&gt; 接收连接；&lt;/li&gt;
&lt;li&gt;订阅服务：作为独立的 HTTPS 服务处理，不和节点数据通道混为一谈。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;关于 DNS 的一个误判&lt;/h2&gt;
&lt;p&gt;我先在 Cloudflare 创建节点子域名的 A 记录，让它指向美国 VPS。这里必须使用 DNS only。Cloudflare 的普通代理面向 HTTP/HTTPS，不能直接代理这条原生 REALITY TCP 连接。&lt;/p&gt;
&lt;p&gt;第一次执行 DNS 查询时，结果却是 &lt;code&gt;198.18.2.16&lt;/code&gt;。这并不是域名绑定错误，而是本地代理的 Fake-IP 机制返回了保留地址。关闭代理后，再用公共解析器查询，域名才显示真实 VPS IP。&lt;/p&gt;
&lt;p&gt;排查 DNS 时要区分“系统实际使用的代理 DNS”和“公网权威解析结果”。看到 &lt;code&gt;198.18.0.0/15&lt;/code&gt; 一类地址时，应先想到 Fake-IP，而不是立刻修改 Cloudflare 记录。&lt;/p&gt;
&lt;h2&gt;3x-ui 面板不直接暴露公网&lt;/h2&gt;
&lt;p&gt;安装 3x-ui 后，我修改了面板端口、用户名、密码和 Base URI，并把面板绑定到 &lt;code&gt;127.0.0.1&lt;/code&gt;。这意味着面板本身虽然提供 HTTP，公网却无法直接访问它。&lt;/p&gt;
&lt;p&gt;Mac 通过 SSH 本地端口转发进入面板：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ssh -L LOCAL_PORT:127.0.0.1:PANEL_PORT root@VPS_PUBLIC_IP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;浏览器访问的是本机 &lt;code&gt;127.0.0.1&lt;/code&gt;，真正跨越公网的部分由 SSH 加密。安装完成后，使用 &lt;code&gt;x-ui status&lt;/code&gt; 和 &lt;code&gt;systemctl status x-ui --no-pager&lt;/code&gt; 确认 3x-ui 与 Xray 都处于运行状态。&lt;/p&gt;
&lt;h2&gt;建立 VLESS REALITY 入站&lt;/h2&gt;
&lt;p&gt;数据入口最终使用 VLESS、TCP/RAW、REALITY 与 Vision。关键配置可以概括为：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;协议为 VLESS，解密和加密均为 &lt;code&gt;none&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;监听地址留空，使 Xray 监听公网；端口使用 &lt;code&gt;443&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;传输使用 TCP/RAW，Header 为 &lt;code&gt;none&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;安全选择 REALITY，客户端指纹使用 &lt;code&gt;chrome&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;目标地址与 SNI 保持一致；&lt;/li&gt;
&lt;li&gt;生成 X25519 密钥对，私钥只留在服务器；&lt;/li&gt;
&lt;li&gt;客户端 flow 使用 &lt;code&gt;xtls-rprx-vision&lt;/code&gt;，并填写匹配的 Short ID。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;第一次导出分享链接为空，是因为入站的 &lt;code&gt;clients&lt;/code&gt; 数组还是空的。入站协议配置完成不等于已经有可用用户；还必须新增客户端、生成 UUID，并保存入站。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;REALITY 私钥、UUID、订阅 ID 和面板路径都属于凭据。公开截图或求助后，应立即轮换已经暴露的值。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;端口可达不代表协议正确&lt;/h2&gt;
&lt;p&gt;使用 &lt;code&gt;nc -vz -G 5 sub.example.com 443&lt;/code&gt; 可以确认 TCP 三次握手成功。这个结果证明 DNS、路由和端口基本可达，但不能证明 REALITY 的公钥、SNI、Short ID、UUID 与 flow 全部匹配。&lt;/p&gt;
&lt;p&gt;实际排查顺序应当是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;确认公网 DNS 指向正确 VPS，并排除 Fake-IP 干扰；&lt;/li&gt;
&lt;li&gt;确认服务器确实监听 &lt;code&gt;443/tcp&lt;/code&gt;，外部端口可以建立连接；&lt;/li&gt;
&lt;li&gt;逐项核对 UUID、公钥、SNI、Short ID、Vision flow 和客户端指纹；&lt;/li&gt;
&lt;li&gt;检查 Xray 与 3x-ui 日志，观察连接在哪一步失败；&lt;/li&gt;
&lt;li&gt;最后用真实网页、下载和 DNS 查询验证，而不只看客户端延迟数字。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;节点链接和订阅不是一回事&lt;/h2&gt;
&lt;p&gt;单条 VLESS 链接可以直接导入支持它的客户端。客户端显示“本地节点”，通常只表示配置由用户在本机导入，并不代表服务器位于本机。面板最初显示的订阅地址是 &lt;code&gt;http://127.0.0.1:SUB_PORT/sub/TOKEN&lt;/code&gt;，这个地址只对服务器本机有效，外部客户端当然无法访问。&lt;/p&gt;
&lt;p&gt;要提供订阅，就要单独准备公网 HTTPS 地址、证书和正确的响应格式。完成测试后，应删除失败的测试客户端，并重新生成曾经暴露过的订阅 ID。&lt;/p&gt;
&lt;h2&gt;SSH 免密是管理入口的一部分&lt;/h2&gt;
&lt;p&gt;将本机 SSH 公钥加入 VPS 的 &lt;code&gt;authorized_keys&lt;/code&gt; 后，应保留原会话，再开一个新终端验证密钥登录；只有确认新入口可靠，才考虑关闭密码登录。这样即使面板不可用，服务器仍然有一条独立、可审计的管理路径。&lt;/p&gt;
&lt;h2&gt;真正完成的是运维闭环&lt;/h2&gt;
&lt;p&gt;节点能用只是起点。最终我把以下事项作为完成标准：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;面板仅监听回环地址，通过 SSH 密钥和本地隧道管理；&lt;/li&gt;
&lt;li&gt;公网只开放必要端口，节点域名保持 DNS only；&lt;/li&gt;
&lt;li&gt;定期更新 3x-ui 与 Xray，并在变更前备份配置数据库；&lt;/li&gt;
&lt;li&gt;证书能够自动续期，订阅凭据和客户端 UUID 可以随时轮换；&lt;/li&gt;
&lt;li&gt;保留服务状态、监听端口、客户端实测和日志四层检查方法；&lt;/li&gt;
&lt;li&gt;根域名与节点子域名职责独立，网站部署不会影响代理入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这次部署最大的收获不是记住某几个表单值，而是建立了一个可以复述的模型：DNS 决定去哪里，防火墙决定能不能到，REALITY 参数决定能不能握手，客户端与订阅决定配置如何分发，日志和真实流量则决定它是否真的可用。&lt;/p&gt;
</content:encoded><category>笔记</category><category>Cloudflare</category><category>VPS</category><category>3x-ui</category><category>Xray</category></item><item><title>从本地预览到正式域名：BiumBiu 个人网站上线记录</title><link>https://biumbiu.com/notes/website-launch/</link><guid isPermaLink="true">https://biumbiu.com/notes/website-launch/</guid><description>记录 BiumBiu 从 Astro 静态站点到 Cloudflare Pages、Three.js 首页、站内搜索与 D1 访问量统计的搭建和维护过程。</description><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;有了自己的域名以后，我不想只放一个临时首页，而是希望得到一个能长期更新的公开空间：它可以介绍我正在做什么，展示项目，也能沉淀每一次真正解决过的问题。网站最初以 Astro 静态页面上线，后来逐步补齐了 Content Collections、站内搜索、Three.js 首页模型、图片检查和基于 Cloudflare D1 的访问量统计。&lt;/p&gt;
&lt;p&gt;现在的发布链路可以概括为：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Markdown 内容 → Astro 静态构建 → Git 版本记录 → Cloudflare Pages → biumbiu.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;首屏交互由浏览器端 TypeScript 和 Three.js 负责；访问量则由 Pages Function 写入 D1。静态内容、客户端交互和服务端统计各自处在清楚的边界内。&lt;/p&gt;
&lt;h2&gt;先确定网站要承担什么&lt;/h2&gt;
&lt;p&gt;这个站点不需要登录和复杂后台，核心仍然是内容与阅读体验。首页集中展示关于、近况、项目和笔记，独立页面提供个人时间线、项目归档、笔记归档、标签与搜索。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;项目页保存做过的系统、工具和网站，而不是只罗列技术名词；&lt;/li&gt;
&lt;li&gt;笔记页记录部署、排错和取舍，让经验以后仍然可以检索；&lt;/li&gt;
&lt;li&gt;标签和搜索负责跨内容查找，避免文章增多后只能按时间翻阅；&lt;/li&gt;
&lt;li&gt;首页保留个人信息与精选内容，把完整档案交给独立页面。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;内容展示为主的网站很适合静态生成。Astro 在构建阶段输出普通 HTML，线上阅读不依赖常驻应用服务器；只有访问量计数通过一个很小的服务端接口处理。&lt;/p&gt;
&lt;h2&gt;当前项目如何组织&lt;/h2&gt;
&lt;p&gt;项目要求 Node.js 22 或更高版本。核心目录按照运行环境和职责拆分：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;biumbiu-site/
├── functions/                 # Cloudflare Pages Functions
├── migrations/                # D1 数据库迁移
├── public/                    # 图片、3D 模型和其他公开资源
├── scripts/                   # 内容、图片与模型处理脚本
├── src/
│   ├── client/                # 浏览器端搜索和首页交互
│   ├── components/            # Astro UI 组件
│   ├── content/               # 项目与笔记 Markdown
│   ├── layouts/               # 页面骨架与 SEO
│   ├── lib/                   # 内容和 Markdown 工具
│   ├── pages/                 # 文件路由与静态 API
│   └── styles/                # 全局视觉系统
├── astro.config.mjs
└── package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里最重要的边界有两组。&lt;code&gt;scripts/&lt;/code&gt; 由 Node.js 在终端运行，不能依赖 DOM 或 &lt;code&gt;window&lt;/code&gt;；&lt;code&gt;src/client/&lt;/code&gt; 会被 Vite 打包到浏览器，用于搜索和 3D 交互。&lt;code&gt;public/&lt;/code&gt; 中的文件保持原样发布，3D 模型以 &lt;code&gt;/models/&lt;/code&gt; 为公开路径前缀；&lt;code&gt;src/&lt;/code&gt; 中的文件则必须经过 Astro 和 Vite 处理，其中 &lt;code&gt;src/assets/images/&lt;/code&gt; 的图片会由 &lt;code&gt;astro:assets&lt;/code&gt; 构建期压缩，组件封面生成 AVIF/WebP，Markdown 正文图片生成响应式 &lt;code&gt;srcset&lt;/code&gt;。&lt;/p&gt;
&lt;h2&gt;本地开发与预览&lt;/h2&gt;
&lt;p&gt;安装依赖并启动开发服务器：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm ci
npm run dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;默认地址是 &lt;code&gt;http://localhost:4321/&lt;/code&gt;。需要用手机等局域网设备检查时，可以运行：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run dev -- --host 0.0.0.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;桌面端首页使用固定信息栏与滚动内容区，移动端收敛成单栏。首屏的 3D 形象可以拖动或用键盘旋转，模型无法加载时会回退到静态海报；减少动态效果的系统偏好也会得到尊重。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm run dev&lt;/code&gt; 启动前会自动检查首屏 GLB 是否变化，并在需要时重新生成海报，因此模型和回退图片不会依赖两套手工维护流程。&lt;/p&gt;
&lt;h2&gt;内容改用 Content Collections&lt;/h2&gt;
&lt;p&gt;项目和笔记现在都由 Astro Content Collections 管理。新增内容时先运行脚手架：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run new:note -- my-note
npm run new:project -- my-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成的 Markdown 默认带有 &lt;code&gt;draft: true&lt;/code&gt;。草稿可以在开发环境预览，但不会进入生产构建；确认内容完成后，再将它改为 &lt;code&gt;false&lt;/code&gt; 或删除该字段。&lt;/p&gt;
&lt;p&gt;文件名同时是 URL slug。例如 &lt;code&gt;src/content/notes/example.md&lt;/code&gt; 会生成 &lt;code&gt;/notes/example/&lt;/code&gt;。slug 只使用小写字母、数字和连字符，公开后尽量不修改，以免已经分享的链接失效。&lt;/p&gt;
&lt;p&gt;标题、摘要、日期、封面和替代文本写在 frontmatter 中。首页精选、归档列表、标签页、详情页和搜索索引都从同一份内容生成，不再分别维护标题与链接。正文从二级标题开始，因为详情页的主标题由布局自动生成。&lt;/p&gt;
&lt;h2&gt;图片与 3D 模型进入发布流程&lt;/h2&gt;
&lt;p&gt;公开图片统一放在 &lt;code&gt;src/assets/images/&lt;/code&gt;，由 &lt;code&gt;astro:assets&lt;/code&gt; 在构建期处理。frontmatter 和 Markdown 正文都用相对路径引用（例如 &lt;code&gt;../../assets/images/cover.webp&lt;/code&gt;），组件里则用 &lt;code&gt;&amp;lt;Image&amp;gt;&lt;/code&gt;、&lt;code&gt;&amp;lt;Picture&amp;gt;&lt;/code&gt; 渲染。Frontmatter 封面会生成多宽度 AVIF/WebP，Markdown 正文图片按源格式生成响应式 &lt;code&gt;srcset&lt;/code&gt;；两者都会写入真实 &lt;code&gt;width&lt;/code&gt;/&lt;code&gt;height&lt;/code&gt;，移动端不再下载全尺寸大图，也避免了布局偏移。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-astro&quot;&gt;&amp;lt;Picture src={cover} alt={coverAlt} layout=&quot;none&quot; widths={[480, 768, 1200]} formats={[&quot;avif&quot;, &quot;webp&quot;]} fallbackFormat=&quot;webp&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;相对路径写错或文件缺失会在 &lt;code&gt;astro check&lt;/code&gt; 阶段直接报错，因此文章在本地能显示但部署后丢图的问题可以更早暴露。&lt;/p&gt;
&lt;p&gt;首页模型固定使用 &lt;code&gt;public/models/myself.glb&lt;/code&gt;。开发和构建前执行的 &lt;code&gt;model:prepare&lt;/code&gt; 会在模型变化时更新 &lt;code&gt;public/images/myself-poster.webp&lt;/code&gt;，并同步当前 Three.js 版本需要的 Draco 解码器。浏览器加载真实模型，静态海报负责首屏预载和失败回退。&lt;/p&gt;
&lt;h2&gt;构建不只是生成 dist&lt;/h2&gt;
&lt;p&gt;页面在开发环境能打开，不代表生产构建一定成功。当前构建命令会依次准备模型资源、执行 Astro 类型检查（含图片引用校验），再输出静态站点：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run build
npm run preview
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;生成结果位于 &lt;code&gt;dist/&lt;/code&gt;。桌面和手机尺寸都需要分别检查，确认文字没有溢出、图片和模型能够加载、导航与主题切换可操作，站内链接不会跳到不存在的页面。&lt;/p&gt;
&lt;p&gt;Pages Function 不由 Astro 本地服务器提供，需要单独验证它能否编译：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run check:functions
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;搜索和访问量如何接入&lt;/h2&gt;
&lt;p&gt;站内搜索没有依赖外部服务。构建时，Astro 会从已经发布的项目与笔记生成 &lt;code&gt;/search-index.json&lt;/code&gt;，其中包含标题、摘要、标签和正文；浏览器端再加载这份索引完成匹配。草稿不会进入索引。&lt;/p&gt;
&lt;p&gt;访问量接口位于 &lt;code&gt;functions/api/views.ts&lt;/code&gt;，使用 D1 同时记录当前路径和全站总访问量。Cloudflare Pages 中需要创建名为 &lt;code&gt;DB&lt;/code&gt; 的 D1 绑定，并执行仓库里的数据库迁移。Preview 环境最好使用独立数据库，避免测试访问写入正式统计。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;npm run dev&lt;/code&gt; 只启动 Astro，不提供 Pages Function 和 D1，因此本地访问量组件会自动隐藏。真正的端到端计数需要在 Cloudflare Preview 或正式环境验证。&lt;/p&gt;
&lt;h2&gt;Cloudflare Pages 的构建设置&lt;/h2&gt;
&lt;p&gt;Pages 连接 Git 仓库后，核心参数如下：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;配置&lt;/th&gt;
&lt;th&gt;值&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Production branch&lt;/td&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Framework preset&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Astro&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build command&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npm run build&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build output directory&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dist&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root directory&lt;/td&gt;
&lt;td&gt;仓库根目录&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;首次构建成功后，Pages 会提供独立的 &lt;code&gt;*.pages.dev&lt;/code&gt; 预览地址。以后生产分支出现新提交，Cloudflare 会重新安装依赖、构建并发布，不需要手动上传压缩包。&lt;/p&gt;
&lt;h2&gt;第一次部署走错了入口&lt;/h2&gt;
&lt;p&gt;Cloudflare 把 Workers 与 Pages 放在相近的管理入口里，我第一次创建成了 Worker + Static Assets。构建日志出现 &lt;code&gt;npx wrangler deploy&lt;/code&gt;，最终地址也是 &lt;code&gt;*.workers.dev&lt;/code&gt;。它同样可以发布静态资源，但不是原先计划的 Pages Git 集成路径。&lt;/p&gt;
&lt;p&gt;确认问题后，我重新选择 Pages。正确部署完成后得到 &lt;code&gt;*.pages.dev&lt;/code&gt; 地址。先在临时域名验证网站，再绑定正式域名；旧 Worker 也等到 Pages 可用后再删除，避免网站中途没有可用版本。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;workers.dev&lt;/code&gt; 和 &lt;code&gt;pages.dev&lt;/code&gt; 都可能显示网页，但它们代表不同的 Cloudflare 产品与部署流程。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;绑定正式域名&lt;/h2&gt;
&lt;p&gt;在 Pages 项目的自定义域名中，分别添加 &lt;code&gt;biumbiu.com&lt;/code&gt; 和 &lt;code&gt;www.biumbiu.com&lt;/code&gt;。按照向导完成 DNS 校验后，Cloudflare 会提供 HTTPS 证书。证书签发与 DNS 生效可能需要一点时间，因此绑定后应分别访问两个地址，而不是只看后台状态。&lt;/p&gt;
&lt;p&gt;根域名和 &lt;code&gt;www&lt;/code&gt; 指向 Pages，其他连接独立服务的子域名继续保持自己的解析方式。两个网站地址都能打开以后，再使用 Redirect Rules 将 &lt;code&gt;https://www.biumbiu.com/*&lt;/code&gt; 以 301 跳转到根域名，并保留路径和查询字符串。&lt;/p&gt;
&lt;h2&gt;每次更新的发布流程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;运行 &lt;code&gt;npm run dev&lt;/code&gt;，在桌面和手机尺寸下预览；&lt;/li&gt;
&lt;li&gt;使用 &lt;code&gt;git status&lt;/code&gt; 和 &lt;code&gt;git diff&lt;/code&gt; 确认本次实际改动；&lt;/li&gt;
&lt;li&gt;运行 &lt;code&gt;npm run build&lt;/code&gt;，完成模型、图片、类型和静态构建检查；&lt;/li&gt;
&lt;li&gt;运行 &lt;code&gt;npm run check:functions&lt;/code&gt;，确认访问量函数能够编译；&lt;/li&gt;
&lt;li&gt;只提交准备发布的文件，并推送到生产分支；&lt;/li&gt;
&lt;li&gt;等待 Cloudflare Pages 构建成功，再检查正式域名、新页面和主要链接。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;现在的 BiumBiu 已经不只是“能打开”的个人首页，而是一套边界清楚的内容发布流程：Markdown 保存内容，Astro 负责静态页面与索引，浏览器端代码提供必要交互，Pages Function 与 D1 承担小范围动态能力，Cloudflare Pages 负责构建、HTTPS 和分发。新增一篇笔记、替换一张图片或更新首页模型，都能沿着同一套检查和发布路径完成。&lt;/p&gt;
</content:encoded><category>笔记</category><category>Astro</category><category>Cloudflare Pages</category><category>Three.js</category><category>D1</category></item><item><title>Kimi K3 Inference Stack</title><link>https://biumbiu.com/projects/kimi-k3/</link><guid isPermaLink="true">https://biumbiu.com/projects/kimi-k3/</guid><description>将 Kimi K3 部署到 NVIDIA B300，围绕推理效率持续测量与优化，并通过 Token 中转服务提供稳定的外部调用入口。</description><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这个项目的目标不是完成一次性的部署演示，而是把算力环境、推理引擎、性能测量、优化实验和外部访问入口连接成一套可以持续迭代的系统。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NVIDIA B300 → SGLang → 数据采集与分析 → 推理优化 → Token 中转服务&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;目前，Kimi K3 已经通过 SGLang 部署到 NVIDIA B300 平台，基础请求链路可以工作；首轮运行与请求数据正在持续收集和分析；面向外部调用的 Token 中转站也已经搭建完成。&lt;/p&gt;
&lt;h2&gt;系统链路&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;当前实现&lt;/th&gt;
&lt;th&gt;关注点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compute&lt;/td&gt;
&lt;td&gt;NVIDIA B300&lt;/td&gt;
&lt;td&gt;显存占用、计算利用率与多请求并发&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime&lt;/td&gt;
&lt;td&gt;SGLang&lt;/td&gt;
&lt;td&gt;模型加载、调度策略与推理稳定性&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Measurement&lt;/td&gt;
&lt;td&gt;Data &amp;amp; Analysis&lt;/td&gt;
&lt;td&gt;不同负载、并发和上下文下的实际表现&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Access&lt;/td&gt;
&lt;td&gt;Token Relay&lt;/td&gt;
&lt;td&gt;统一入口、鉴权、限流与可观测性&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;已完成&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;完成 Kimi K3 在 NVIDIA B300 平台上的基础推理环境搭建；&lt;/li&gt;
&lt;li&gt;使用 SGLang 部署模型并打通完整请求链路；&lt;/li&gt;
&lt;li&gt;开始收集运行与请求数据，进入整理和分析阶段；&lt;/li&gt;
&lt;li&gt;完成 Token 中转站搭建，对外提供统一调用入口。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;正在推进&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;建立可复现的基准测试条件，减少单次测量带来的偶然性；&lt;/li&gt;
&lt;li&gt;对比批处理、并发、上下文长度与运行参数对推理表现的影响；&lt;/li&gt;
&lt;li&gt;定位吞吐、时延、显存利用率和稳定性之间的约束关系；&lt;/li&gt;
&lt;li&gt;补齐中转层的访问控制、限流、日志和故障观测能力。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;用数据决定优化方向&lt;/h2&gt;
&lt;p&gt;我不会只使用一个“每秒 Token 数”概括系统表现。当前测量主要围绕四组指标展开：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;指标&lt;/th&gt;
&lt;th&gt;含义&lt;/th&gt;
&lt;th&gt;用途&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TTFT&lt;/td&gt;
&lt;td&gt;首 Token 时延&lt;/td&gt;
&lt;td&gt;判断排队、预填充与调度开销&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPOT&lt;/td&gt;
&lt;td&gt;单 Token 生成时延&lt;/td&gt;
&lt;td&gt;拆解持续解码阶段的生成效率&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPS&lt;/td&gt;
&lt;td&gt;吞吐与并发&lt;/td&gt;
&lt;td&gt;寻找吞吐和单请求体验之间的平衡点&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPU&lt;/td&gt;
&lt;td&gt;显存与稳定性&lt;/td&gt;
&lt;td&gt;跟踪利用率、失败请求和长时间运行状态&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;项目仍在进行中。具体性能数字会在测试条件、样本规模和对照方案稳定后再公开，避免把偶然结果当作优化结论。&lt;/p&gt;
&lt;h2&gt;下一阶段&lt;/h2&gt;
&lt;p&gt;下一步先形成一份可复现的基线报告，固定模型版本、引擎版本、请求分布、并发配置和统计口径，再进入有针对性的参数优化与压力测试。每次优化都需要保留对照组，并能解释它改善的是哪一个阶段、付出了什么代价。&lt;/p&gt;
</content:encoded><category>项目</category><category>Kimi K3</category><category>NVIDIA B300</category><category>SGLang</category><category>Inference</category></item><item><title>BiumBiu.com</title><link>https://biumbiu.com/projects/biumbiu-site/</link><guid isPermaLink="true">https://biumbiu.com/projects/biumbiu-site/</guid><description>一个以内容为中心的个人网站：使用 Astro 管理项目与笔记，以 Three.js 提供首页交互，并由 Cloudflare Pages 和 D1 承担发布与访问量统计。</description><pubDate>Thu, 30 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;BiumBiu.com 是我用来保存项目、技术笔记和阶段性思考的公开空间。它不需要登录或复杂后台，核心要求是快速、清楚，并且多年以后仍然容易维护。&lt;/p&gt;
&lt;h2&gt;项目目标&lt;/h2&gt;
&lt;p&gt;首页需要在一个视图中交代我是谁、最近关注什么、做过哪些项目以及正在写什么；个人时间线、项目、笔记和标签则拥有独立页面，方便继续浏览和检索。内容应该用普通 Markdown 长期保存，视觉与交互可以迭代，但不应该反过来绑架写作流程。&lt;/p&gt;
&lt;h2&gt;三层职责&lt;/h2&gt;
&lt;p&gt;站点把不同能力拆在各自合适的运行环境里：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;层级&lt;/th&gt;
&lt;th&gt;职责&lt;/th&gt;
&lt;th&gt;实现&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;静态内容&lt;/td&gt;
&lt;td&gt;页面、项目、笔记、标签与搜索索引&lt;/td&gt;
&lt;td&gt;Astro、Content Collections&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;浏览器交互&lt;/td&gt;
&lt;td&gt;搜索、主题切换、首页信号场与 3D 模型&lt;/td&gt;
&lt;td&gt;TypeScript、Three.js&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;服务端能力&lt;/td&gt;
&lt;td&gt;页面与全站访问量计数&lt;/td&gt;
&lt;td&gt;Pages Functions、D1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;大部分页面在构建阶段生成普通 HTML，线上不依赖常驻应用服务器。只有确实需要写入状态的访问量接口运行在 Cloudflare 边缘环境中。&lt;/p&gt;
&lt;h2&gt;内容优先&lt;/h2&gt;
&lt;p&gt;站点最初直接用 Astro 页面保存文章。这样可以精确控制每个页面，但新增内容时还要手动维护首页和归档页，长期使用的成本很高。&lt;/p&gt;
&lt;p&gt;现在项目与笔记统一由 Astro Content Collections 管理。每篇内容都是一个普通 Markdown 文件，标题、日期、封面、标签和草稿状态写在 frontmatter 中；详情页、首页精选、归档列表、标签页和搜索索引都从同一份数据自动生成。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/content/projects/ -&amp;gt; 项目 Markdown
src/content/notes/    -&amp;gt; 笔记 Markdown
src/pages/            -&amp;gt; 列表、详情、标签、搜索与 RSS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;内容脚手架会默认创建草稿，开发环境可以预览，生产构建自动排除。文件名直接成为稳定的 URL slug，不需要额外维护路由表。&lt;/p&gt;
&lt;h2&gt;首页视觉与交互&lt;/h2&gt;
&lt;p&gt;桌面首页采用固定个人信息栏和滚动内容区，移动端收敛成单栏。首屏使用 Three.js 渲染可拖动、可用键盘操作的 GLB 个人形象，背景信号场和轻量视差用于建立层次，但不会遮挡主要内容。&lt;/p&gt;
&lt;p&gt;3D 模型同时提供静态 WebP 海报作为预载和加载失败回退。开发与构建前的资源脚本会根据模型内容自动更新海报并同步 Draco 解码器；用户偏好减少动态效果时，页面也会降低非必要动画。&lt;/p&gt;
&lt;h2&gt;内容发现&lt;/h2&gt;
&lt;p&gt;项目和笔记除了归档页，还可以通过标签与全文搜索互相连接。构建阶段生成的 &lt;code&gt;/search-index.json&lt;/code&gt; 收录已发布内容的标题、摘要、标签和正文，浏览器端直接完成匹配，不依赖第三方搜索服务，也不会把草稿暴露到生产环境。&lt;/p&gt;
&lt;p&gt;站点同时生成 RSS 与 sitemap，让内容既能在站内被找到，也能被订阅工具和搜索引擎稳定发现。&lt;/p&gt;
&lt;h2&gt;发布链路&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Markdown 内容 → Astro 静态构建 → Git 版本记录 → Cloudflare Pages → biumbiu.com&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cloudflare Pages 连接生产分支，每次提交后自动安装依赖、检查图片引用、执行 Astro 类型检查并发布 &lt;code&gt;dist/&lt;/code&gt;。图片导入工具会统一方向、尺寸与 WebP 输出；缺失、损坏或仍需优化的资源能够在发布前被发现。&lt;/p&gt;
&lt;p&gt;页面访问量由 Pages Function 接收并写入 D1，同时记录当前路径和全站总量。Astro 本地开发不模拟这部分服务端环境，因此组件会在本地自动隐藏，完整链路放到 Cloudflare Preview 和正式环境验证。&lt;/p&gt;
&lt;h2&gt;设计取舍&lt;/h2&gt;
&lt;p&gt;视觉系统使用深绿背景、荧光绿重点色和少量珊瑚红状态色，保持工程感，同时避免纯黑界面过于生硬。浅色与深色主题共享同一套信息层级，代码、表格、引用和正文宽度则围绕长时间阅读调整。&lt;/p&gt;
&lt;p&gt;交互只服务于信息层级：导航显示当前栏目，项目缩略图帮助快速识别内容，文章页提供目录和内容导航，首页模型有键盘入口与静态回退。站点不会为了视觉效果牺牲移动端布局、可读性或基本可访问性。&lt;/p&gt;
&lt;h2&gt;维护原则&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;内容只有一个来源，列表页不重复填写标题与链接；&lt;/li&gt;
&lt;li&gt;草稿通过 frontmatter 控制，不会进入生产构建；&lt;/li&gt;
&lt;li&gt;&lt;code&gt;scripts/&lt;/code&gt; 与浏览器端 &lt;code&gt;src/client/&lt;/code&gt; 保持明确的运行环境边界；&lt;/li&gt;
&lt;li&gt;图片和模型放在 &lt;code&gt;public/&lt;/code&gt;，并由检查或生成脚本维护关联资源；&lt;/li&gt;
&lt;li&gt;发布前执行图片检查、类型检查、静态构建与 Pages Function 编译；&lt;/li&gt;
&lt;li&gt;保留旧 URL，内容架构调整不破坏已经分享出去的链接。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;当前结果&lt;/h2&gt;
&lt;p&gt;网站已经形成一条可重复的维护路径：用 Markdown 新增内容，用脚本处理图片和模型，用 Astro 生成页面、索引、RSS 与 sitemap，用少量浏览器代码增强交互，再由 Cloudflare Pages、Functions 和 D1 完成发布与统计。&lt;/p&gt;
&lt;p&gt;这个项目不会以不断增加页面为目标。更重要的是让记录一项工作、修改一篇文章和回看过去都足够轻松，同时让每次更新仍然能够被构建和检查流程验证。&lt;/p&gt;
</content:encoded><category>项目</category><category>Astro</category><category>TypeScript</category><category>Three.js</category><category>Cloudflare Pages</category><category>D1</category><category>Content Collections</category></item><item><title>从 Research Seed 到实验报告：自动化科研 Agent 实践</title><link>https://biumbiu.com/projects/auto-sci-find-multiagent/</link><guid isPermaLink="true">https://biumbiu.com/projects/auto-sci-find-multiagent/</guid><description>一套覆盖文献调研、代码实验、质量检查和反思迭代的自动化科研 Multi-Agent 框架，以及持续优化的 SurveyAgent 文献调研谱系系统。</description><pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这是一套持续演化的系统：它覆盖文献调研、代码实验和反思迭代；我主要负责文献调研部分，该部分单独深化成了一套面向“领域谱系地图”的 SurveyAgent 系统。&lt;/p&gt;
&lt;p&gt;github仓库：https://github.com/yaleyoou/Auto-Sci-Find-MutiAgent&lt;/p&gt;
&lt;h2&gt;先说明两个版本&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;版本&lt;/th&gt;
&lt;th&gt;目标&lt;/th&gt;
&lt;th&gt;主要 Agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;科研闭环v1&lt;/td&gt;
&lt;td&gt;从研究问题推进到可验证的实验报告&lt;/td&gt;
&lt;td&gt;ResearchAgent、SurveyAgent、ReflectionAgent、CoderAgent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;调研系统v2&lt;/td&gt;
&lt;td&gt;从研究方向或种子论文构建领域谱系地图&lt;/td&gt;
&lt;td&gt;SurveyAgent、LineageAgent、PaperReaderAgent、SurveyCriticAgent&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;两版共用 BaseAgent、工具注册、Skill 加载、上下文管理和文件化产物等基础设施。比赛名次和完整 Multi-Agent 系统是团队成果；我主要负责文献调研链路及其后续演化，包括查询规划、检索筛选、引用扩展、论文解析与分析、文献矩阵和调研质量闭环。后续已将自己负责的文献调研部分整合到nanobot形成产品。&lt;/p&gt;
&lt;h2&gt;为什么要做这套系统&lt;/h2&gt;
&lt;p&gt;大模型可以帮助搜索论文、读代码和写实验脚本，但真正的科研任务很少能在一个 prompt 里结束。&lt;/p&gt;
&lt;p&gt;一个研究问题通常要经历这样的过程：先拆清楚问题，再查已有工作；从文献中找出可复现的 baseline 和可能的 gap；搭环境、跑实验、读取结果；如果结果不支持原来的判断，还要回到调研或实验设计阶段重新迭代。任何一环的信息不完整，后面的工作都可能建立在错误假设上。&lt;/p&gt;
&lt;p&gt;我们探索的核心问题因此不是“怎样让一个 Agent 更聪明”，而是：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;能否把任务拆解、策略规划、调研分析、代码实验、结果校验和反思迭代串成一条可执行、可追踪、可回退的自动化科研闭环？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这套框架后来被用于 AI4S 智能体 CNS 挑战赛中的蛋白质构象系综生成任务。与输出一个静态结构不同，构象系综生成关心的是一组可能构象及其分布，方法选择、实验设置和评价方式之间有很强的依赖关系。Agent 不仅要执行预设流程，还要根据实验反馈重新判断策略。据团队参赛记录，我们以初赛第一名进入复赛。&lt;/p&gt;
&lt;h2&gt;整体架构：交接比角色数量重要&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;图片：参赛阶段的自动化科研 Multi-Agent 工作流&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Research Seed
  -&amp;gt; SurveyAgent
  -&amp;gt; ReflectionAgent
  -&amp;gt; CoderAgent
  -&amp;gt; ReflectionAgent
  -&amp;gt; Final Experiment Report
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最上层的 ResearchAgent 是调度者。它不亲自完成所有工作，而是负责拆解任务、选择下一步策略、把上游产物交给合适的子 Agent，并根据质量检查结果决定继续、回退还是结束。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;主要职责&lt;/th&gt;
&lt;th&gt;关键产物&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SurveyAgent&lt;/td&gt;
&lt;td&gt;检索、筛选、解析和精读论文，提炼谱系、baseline、数据集、指标与 gap&lt;/td&gt;
&lt;td&gt;survey report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ReflectionAgent&lt;/td&gt;
&lt;td&gt;检查调研覆盖、实验充分性和创新性，决定通过或打回&lt;/td&gt;
&lt;td&gt;quality state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CoderAgent&lt;/td&gt;
&lt;td&gt;搭建环境、复现 baseline、执行优化与对照实验、记录结果&lt;/td&gt;
&lt;td&gt;experiment report&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ResearchAgent&lt;/td&gt;
&lt;td&gt;维护全局任务状态，完成跨 Agent 调度和最终汇总&lt;/td&gt;
&lt;td&gt;state / handoff&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;系统要求子 Agent 通过文件交接证据和状态，而不是只返回一段自然语言摘要。ResearchAgent 在做关键判断时，也要重新读取报告、日志和结果文件。&lt;/p&gt;
&lt;p&gt;这解决了一个很实际的问题：对话会变长、上下文会被压缩、子 Agent 的上下文彼此隔离，但文件不会因为一次对话结束而消失。Markdown、JSON、CSV、实验日志和模型输出不是附属品，而是系统的长期记忆。&lt;/p&gt;
&lt;h2&gt;一次任务怎样从头跑到尾&lt;/h2&gt;
&lt;h3&gt;1. 定义 Research Seed&lt;/h3&gt;
&lt;p&gt;系统先把用户的想法整理成 Research Seed，也就是准备继续验证的初始研究问题、使用场景、约束和成功标准。输入可以是一个宽泛方向、一篇种子论文，也可以是竞赛给定的任务。&lt;/p&gt;
&lt;p&gt;如果输入仍然模糊，ResearchAgent 只提出必要的澄清问题，而不是立刻开始大规模搜索或训练。&lt;/p&gt;
&lt;h3&gt;2. SurveyAgent 建立证据底座&lt;/h3&gt;
&lt;p&gt;SurveyAgent 从研究问题出发，完成检索规划、论文搜索、筛选、PDF 下载与解析、关键论文精读和综合报告。报告不仅回答“这个方向有哪些论文”，还要给出后续实验真正需要的信息：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;推荐复现的 baseline；&lt;/li&gt;
&lt;li&gt;数据集、指标和实验协议；&lt;/li&gt;
&lt;li&gt;论文方法的关键差异；&lt;/li&gt;
&lt;li&gt;已知失败场景与可能的优化方向；&lt;/li&gt;
&lt;li&gt;哪些判断来自论文证据，哪些只是待验证推断。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 第一次 Reflection：调研够不够支撑实验&lt;/h3&gt;
&lt;p&gt;ReflectionAgent 作为质量门检查：关键词是否只覆盖了单一叫法，是否遗漏关键 benchmark，指标是否明确，推荐的 baseline 是否真的可运行。&lt;/p&gt;
&lt;p&gt;如果返回 &lt;code&gt;needs_survey&lt;/code&gt;，ResearchAgent 会把具体补搜 query、缺失数据集或待核查指标交回 SurveyAgent。只有调研报告能够形成清晰的实验交接，流程才进入下一阶段。&lt;/p&gt;
&lt;h3&gt;4. 收敛 Hypothesis 和最小实验协议&lt;/h3&gt;
&lt;p&gt;ResearchAgent 基于调研材料提炼当前 hypothesis，明确要验证什么、选哪个 baseline、使用什么指标，以及怎样用最小成本判断路线是否成立。实验目标必须能追溯到上游证据，实验结果也必须有可能支持或推翻当前假设。&lt;/p&gt;
&lt;h3&gt;5. CoderAgent 复现和优化&lt;/h3&gt;
&lt;p&gt;CoderAgent 读取 survey report 和 handoff 后，依次完成环境搭建、代码理解、baseline 复现、profiling、单变量优化和 A/B 对照。训练命令、依赖版本、随机种子、日志、结果和失败原因都会写入实验目录。&lt;/p&gt;
&lt;p&gt;长时间训练使用后台任务执行。主 Agent 可以继续读代码、检查日志或安排其他工作，任务完成后再把结果注入后续推理，避免一次同步调用超时就让整个闭环中断。&lt;/p&gt;
&lt;h3&gt;6. 第二次 Reflection：结果是否站得住&lt;/h3&gt;
&lt;p&gt;实验后的 ReflectionAgent 检查 baseline 是否复现、对照是否公平、数据量是否足够、优化是否只是偶然波动，以及当前结果能否支撑所谓“创新点”。&lt;/p&gt;
&lt;p&gt;如果返回 &lt;code&gt;needs_coder&lt;/code&gt;，系统会补随机种子、补消融、做错误分析或重新跑对照；如果实验从根本上不支持 hypothesis，则回到调研或问题定义阶段，而不是只挑好看的结果写进报告。&lt;/p&gt;
&lt;h3&gt;7. 形成 Final Experiment Report&lt;/h3&gt;
&lt;p&gt;最终产物不是自动生成的论文，而是一份可审计的实验报告。它包含研究问题、上游证据、实现环境、实验设置、结果表格、失败案例、结论边界和下一步工作。&lt;/p&gt;
&lt;h2&gt;我负责的文献调研链路&lt;/h2&gt;
&lt;p&gt;文献调研看起来像“搜索加总结”，但如果希望它能驱动实验，至少要解决三个问题：召回是否足够、筛选是否可靠、结论是否可追溯。&lt;/p&gt;
&lt;h3&gt;查询规划&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;plan_queries&lt;/code&gt; 不直接搜索，而是把研究主题拆成多组 query。除了核心方法名，还会覆盖任务定义、数据集、benchmark、系统或模型名、作者群、相邻术语和年份窗口。&lt;/p&gt;
&lt;p&gt;这样做是为了避免只围绕用户最初使用的一个词打转。前沿论文的标题未必包含表层关键词，真正重要的工作可能藏在某个系统名、benchmark 或研究团队的后续论文里。&lt;/p&gt;
&lt;h3&gt;多源搜索&lt;/h3&gt;
&lt;p&gt;搜索层可以按配置组合多个学术与 Web 来源，统一论文元数据并去重。每条结果保留来源 query 和 source status，使后续能够回答“这篇论文为什么会进入候选池”。&lt;/p&gt;
&lt;p&gt;不同来源对预印本、引用关系、元数据和网页线索的覆盖不同。只有保留来源信息，后面才能判断缺口是领域本身没有结果，还是某个接口没有召回。&lt;/p&gt;
&lt;h3&gt;两阶段筛选&lt;/h3&gt;
&lt;p&gt;候选池先经过一轮可解释的启发式粗筛，综合题名和摘要词汇重合、年份、引用、venue、来源共识和 PDF 可用性；随后再由 LLM 对小批论文做语义相关性判断。&lt;/p&gt;
&lt;p&gt;这种组合减少了把所有论文都交给 LLM 打分的调用成本，也降低了经典论文仅因年份较早被过滤，或新工作因引用尚少被忽略的风险。&lt;/p&gt;
&lt;h3&gt;从关键词搜索走向谱系追踪&lt;/h3&gt;
&lt;p&gt;关键词检索可以找到“名字像”的论文，却不一定找得到真正定义问题、改变路线或被后来工作反复继承的节点。&lt;/p&gt;
&lt;p&gt;因此系统从筛选后的高相关论文出发，沿参考文献向后追溯，再沿引用关系追踪后续工作，输出时间线、方法分支以及 &lt;code&gt;introduces / extends / critiques / benchmarks / applies&lt;/code&gt; 等论文关系。&lt;/p&gt;
&lt;p&gt;调研目标随之从“搜到一批相关论文”变成了“建立一张核心谱系地图”。系统会区分：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;origin / concept / survey / method / benchmark
application / recent_variant / side_branch / noise
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;PDF 解析与关键论文精读&lt;/h3&gt;
&lt;p&gt;下载预算优先分配给高优先级论文，并把下载成功、跳过和失败记录在 manifest 中。PDF 解析优先转成 Markdown；解析全文和论文精读是两个不同阶段，不能把“已经转成文本”当成“已经理解”。&lt;/p&gt;
&lt;p&gt;PaperReaderAgent 聚焦主干节点。每篇笔记不仅总结方法，还要回答它在谱系中的角色、相比前作的关键变化、数据集和指标、主要证据、局限、复现线索，以及是否暴露了新的补搜目标。&lt;/p&gt;
&lt;h3&gt;文献矩阵&lt;/h3&gt;
&lt;p&gt;整个调研系统的中心数据结构是 &lt;code&gt;literature_matrix.csv&lt;/code&gt;。一行对应一篇论文，包含题名、作者、年份、标识符、引用数、谱系角色、方法分支、贡献、相对前作的变化、数据集、指标、复现性、阅读优先级、用途和证据状态。&lt;/p&gt;
&lt;p&gt;它既是给人看的领域地图，也是后续 Agent 的结构化输入。SurveyAgent 可以据此选择必须精读的论文，Critic 可以检查高优先级论文是否缺笔记，实验 Agent 也可以从中提取 baseline 和指标。&lt;/p&gt;
&lt;h3&gt;质量闸门&lt;/h3&gt;
&lt;p&gt;当前版本使用两层检查：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;evidence-verify&lt;/code&gt; 检查文件、标识符、年份覆盖、精读产物和生成时间；&lt;/li&gt;
&lt;li&gt;SurveyCriticAgent 检查主线是否完整、分支是否平衡，以及 gap 和 idea 是否真的有论文证据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;状态可能是 &lt;code&gt;pass&lt;/code&gt;、&lt;code&gt;needs_search&lt;/code&gt;、&lt;code&gt;needs_reading&lt;/code&gt;、&lt;code&gt;needs_metadata&lt;/code&gt; 或 &lt;code&gt;preliminary&lt;/code&gt;。只要仍有会改变主线的 blocking gap 且循环预算未用完，SurveyAgent 就要把 Critic 的建议转换成下一轮可执行任务。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;反思不是报告末尾的一段自我批评，而是会改变控制流的程序状态。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;从科研全闭环到文献调研专精版&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;图片：当前 SurveyAgent 文献谱系工作流&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;项目的演化大致经历了以下阶段：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;时间&lt;/th&gt;
&lt;th&gt;主要变化&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2026.06.06&lt;/td&gt;
&lt;td&gt;完成基础 LLM tool-use loop、工具抽象和 Skill 加载机制&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026.06.07&lt;/td&gt;
&lt;td&gt;打通检索、筛选、下载、解析、精读、综合流程，并加入 CoderAgent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026.06.08&lt;/td&gt;
&lt;td&gt;将专职 Agent 暴露成主控工具，加入 ReflectionAgent 和后台任务&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026.06.09-10&lt;/td&gt;
&lt;td&gt;增加持久任务图、handoff、上下文压缩、大结果落盘和分页读文件&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026.06.11&lt;/td&gt;
&lt;td&gt;修复后台任务提前结束和空返回，加入引用扩展与二次筛选&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2026.06.30+&lt;/td&gt;
&lt;td&gt;收敛为文献调研系统，加入谱系、精读、文献矩阵和证据闸门&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;参赛版本强调 &lt;code&gt;Survey -&amp;gt; Reflection -&amp;gt; Coder -&amp;gt; Reflection&lt;/code&gt; 的完整实验闭环；赛后版本则刻意移除实验 Agent，把范围收窄为“从研究方向或种子论文构建可复用的领域地图”。&lt;/p&gt;
&lt;h2&gt;支撑长任务的工程设计&lt;/h2&gt;
&lt;h3&gt;共享 BaseAgent&lt;/h3&gt;
&lt;p&gt;所有 Agent 复用同一个 BaseAgent：维护消息历史，调用 LLM，执行 tool use，把 tool result 写回上下文，直到模型给出最终答案。专职 Agent 主要通过 system prompt、可用 Skills 和产物约束定义职责。&lt;/p&gt;
&lt;h3&gt;Skill 按需加载&lt;/h3&gt;
&lt;p&gt;工具注册表只把 Skill 的名称和简介放入 system prompt。Agent 真正需要某项能力时，再读取完整说明。这样可以在保持扩展性的同时控制 prompt 体积。&lt;/p&gt;
&lt;h3&gt;持久状态比对话记忆可靠&lt;/h3&gt;
&lt;p&gt;短期计划由 Todo 管理，跨阶段任务写入持久状态。每个任务记录执行 Agent、状态、输入、输出、依赖、阻塞原因和下一步动作。handoff 文件保存当前研究种子、hypothesis、baseline、最新反思结论和实验结果。&lt;/p&gt;
&lt;h3&gt;上下文压缩必须可审计&lt;/h3&gt;
&lt;p&gt;搜索结果、论文全文和实验日志很容易撑爆上下文。运行时会把完整 transcript 与超大工具输出落盘，再用连续性摘要和文件路径替换历史。这样既控制上下文长度，也没有直接丢掉原始信息。&lt;/p&gt;
&lt;h3&gt;失败状态必须是一等公民&lt;/h3&gt;
&lt;p&gt;开发过程中遇到过几类典型问题：后台任务还没结束，Agent 却已经返回最终答案；子 Agent 没有文本；工具输出过长；质量检查指出缺口后，主控只把它写成建议，却没有继续执行。&lt;/p&gt;
&lt;p&gt;对应修复逐渐形成了运行时规则：只要还有后台任务，主循环就不能结束；空返回会触发恢复提示；大结果自动落盘；Critic 的 blocking gap 会被解析成下一轮任务队列。&lt;/p&gt;
&lt;h2&gt;怎样判断调研真的完成了&lt;/h2&gt;
&lt;p&gt;生成了 report 并不等于任务完成。我更愿意用下面几组问题判断调研是否收敛：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;起源论文、关键概念、主要方法分支和近期代表作是否连成主线？&lt;/li&gt;
&lt;li&gt;新增关键词和引用是否还会不断改变主线，还是只增加旁支？&lt;/li&gt;
&lt;li&gt;高优先级论文是否真的有全文精读笔记？&lt;/li&gt;
&lt;li&gt;gap 和 idea 能否回指到论文、Web 或代码证据？&lt;/li&gt;
&lt;li&gt;对“最新”“SOTA”的判断是否实际检索了当前年份？&lt;/li&gt;
&lt;li&gt;元数据、URL 和实验指标是否可追溯？&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;只有这些问题通过，系统才有资格把产物称为 final。&lt;/p&gt;
&lt;h2&gt;这次实践带给我的认识&lt;/h2&gt;
&lt;h3&gt;Multi-Agent 的价值来自约束&lt;/h3&gt;
&lt;p&gt;如果多个 Agent 只是轮流输出文字，系统只会变得更慢、更贵。角色划分必须对应不同的工具权限、输入产物、输出格式和通过条件。ReflectionAgent 尤其不能只做“总结与建议”，它必须拥有打回流程的能力。&lt;/p&gt;
&lt;h3&gt;科研自动化的核心是证据流&lt;/h3&gt;
&lt;p&gt;从论文到 hypothesis，从 hypothesis 到实验，从实验到结论，每一步都要保留来源。自然语言负责解释，JSON、CSV、日志和报告负责承载状态。没有这条证据流，自动化越快，放大错误的速度也越快。&lt;/p&gt;
&lt;h3&gt;Agent 要能诚实地停在 preliminary&lt;/h3&gt;
&lt;p&gt;自动化系统最危险的不是报错，而是证据不足时仍然生成一个完整、肯定的答案。把 &lt;code&gt;needs_search&lt;/code&gt;、&lt;code&gt;needs_reading&lt;/code&gt; 和 &lt;code&gt;preliminary&lt;/code&gt; 设计成明确状态，往往比继续优化 prompt 更重要。&lt;/p&gt;
&lt;h2&gt;当前边界与下一步&lt;/h2&gt;
&lt;p&gt;文献召回受搜索接口覆盖和元数据质量影响，PDF 解析可能丢失公式或表格，LLM 对论文关系的判断也必须由引用或正文证据约束。当前调研版本不再执行代码实验，因此完整竞赛闭环需要重新接入实验 Agent。&lt;/p&gt;
&lt;p&gt;下一步最值得继续做三件事：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;建立标准化评测集，量化搜索召回、谱系完整性、精读准确性和 gap 可追溯性；&lt;/li&gt;
&lt;li&gt;加强论文实体消歧与引用图构建，减少标题变体、预印本和正式版本造成的重复与断链；&lt;/li&gt;
&lt;li&gt;重新打通“文献证据 → 实验协议 → 结果 → 反向补搜”，同时保留证据闸门和 preliminary 机制。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;回看这个项目，我最看重的不是“做了几个 Agent”，而是逐渐把一个模糊的科研过程变成了可执行的状态机：每个阶段有输入、有产物、有检查条件，也有失败后的回退路径。&lt;/p&gt;
</content:encoded><category>项目</category><category>AI4S</category><category>Multi-Agent</category><category>SurveyAgent</category><category>Research Automation</category></item><item><title>从算子到 Serving：昇腾推理评测链路实践</title><link>https://biumbiu.com/projects/ascend-kernel-to-serving/</link><guid isPermaLink="true">https://biumbiu.com/projects/ascend-kernel-to-serving/</guid><description>从 MatMul、Online Softmax、Paged Attention 和 Quest Sparse Attention，一直到 vLLM-Ascend 集成与端到端评测。</description><pubDate>Thu, 30 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;从 1 月到 4 月，我作为助教参与了中国科学技术大学首届算子开发创新大赛的技术支持、赛题设计和评测体系建设。项目以 Ascend 910B3 为硬件平台，以 Triton-Ascend 为主要开发工具，目标不是完成几个孤立的算子，而是走通一条从基础计算到大模型推理集成的完整链路：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;MatMul / Online Softmax → Paged Attention / Quest Sparse Attention → vLLM-Ascend → Correctness / Throughput / Serving&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;我完整实现了各阶段的 baseline，并把 Paged Attention 与 Quest Sparse Attention 接入 vLLM-Ascend。主要职责集中在集成阶段的题目设计、代码框架和测试脚本建设，同时也参与了前面各阶段的测评、复查和服务器环境支持。&lt;/p&gt;
&lt;p&gt;&lt;em&gt;图片：首届算子开发创新大赛项目海报&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;为什么要从算子一直做到推理系统&lt;/h2&gt;
&lt;p&gt;大模型最终需要落到硬件上执行，而算子正是模型与芯片之间的执行单元。项目采用逐级递进的设计：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;环境与基础知识培训；&lt;/li&gt;
&lt;li&gt;Softmax、MatMul 基础算子开发；&lt;/li&gt;
&lt;li&gt;Paged Attention、带 Quest 的 Sparse Attention 综合算子开发；&lt;/li&gt;
&lt;li&gt;将算子接入 vLLM-Ascend，进行端到端正确性与性能评测。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;前两个阶段主要验证选手能否正确使用 Triton-Ascend，并理解并行划分、访存和数值稳定性。综合算子阶段进一步引入分页 KV Cache、GQA、Online Softmax 和动态稀疏选择。&lt;/p&gt;
&lt;p&gt;到了集成阶段，问题发生了变化：算子在独立测试中输出正确，并不意味着它能直接进入大模型推理系统。真实框架会带来变长序列、分页映射、动态 batch、模型配置、KV Cache 生命周期、图模式、服务并发和显存预分配等约束。集成阶段的核心，就是补齐从“功能正确”到“可用于真实推理”的最后一段距离。&lt;/p&gt;
&lt;h2&gt;我的工作范围&lt;/h2&gt;
&lt;h3&gt;全流程 baseline 实现&lt;/h3&gt;
&lt;p&gt;我完整实现了从 MatMul、Online Softmax，到 Paged Attention、Quest Sparse Attention，再到 vLLM-Ascend 集成的 baseline。&lt;/p&gt;
&lt;p&gt;baseline 不只是给出一份答案，它至少承担四项职责：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;验证题目接口和输入约束确实可实现；&lt;/li&gt;
&lt;li&gt;为正确性测试提供可信参照；&lt;/li&gt;
&lt;li&gt;帮助定位问题来自 kernel、封装层还是框架集成；&lt;/li&gt;
&lt;li&gt;为性能测试建立可复现的起点。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;尤其在 Paged Attention 和 Sparse Attention 阶段，很多错误不会直接崩溃，而会隐藏在最后一个不完整 block、GQA 头映射、逻辑块到物理块的转换、无效 token mask 或 Softmax 累加顺序中。只有先把全流程跑通，才能设计出真正能区分实现质量的测试。&lt;/p&gt;
&lt;h3&gt;各阶段测评与题目复查&lt;/h3&gt;
&lt;p&gt;在基础算子阶段，我负责 Softmax 的测评，并参与部分 MatMul 测评。除了结果是否正确，还需要处理不同 shape、非对齐维度、数据类型与性能波动等问题。&lt;/p&gt;
&lt;p&gt;在综合算子阶段，我复查了 Paged Attention 和 Quest Sparse Attention 的题面，并亲自试做题目。亲自走过数据布局、边界条件和运行脚本后，才能判断题面是否给出足够信息，测试是否覆盖关键路径，以及性能目标是否合理。&lt;/p&gt;
&lt;h3&gt;独立评测服务器与运行环境&lt;/h3&gt;
&lt;p&gt;我协助准备了独立机房中用于测评的服务器和运行环境。评测环境需要固定软件版本、共享文件布局和运行入口，使不同提交能够在尽量一致的条件下执行。&lt;/p&gt;
&lt;p&gt;高性能算子的结果很容易受到后台负载、首次编译、缓存状态、模型初始化和测试顺序影响。只有把环境、脚本和基线固定下来，最终结果才具有可比性。&lt;/p&gt;
&lt;h3&gt;集成阶段题目、框架与评测体系&lt;/h3&gt;
&lt;p&gt;集成阶段是我投入最多的部分，工作包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;集成阶段总题面的设计与编写；&lt;/li&gt;
&lt;li&gt;Paged Attention、ReshapeAndCache、Sparse Attention 的算子接入框架；&lt;/li&gt;
&lt;li&gt;vLLM-Ascend 评测框架；&lt;/li&gt;
&lt;li&gt;正确性、离线吞吐与 Serving Benchmark 脚本；&lt;/li&gt;
&lt;li&gt;测评数据集与两种运行入口；&lt;/li&gt;
&lt;li&gt;NPU Graph 入图兼容脚本；&lt;/li&gt;
&lt;li&gt;答辩重新提交阶段的新增长度数据集、极限长度和兼容性复测；&lt;/li&gt;
&lt;li&gt;对选手提交进行超长序列手动测试。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;三个算子，一条推理链路&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;阶段&lt;/th&gt;
&lt;th&gt;核心问题&lt;/th&gt;
&lt;th&gt;系统约束&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ReshapeAndCache&lt;/td&gt;
&lt;td&gt;正确写入分页 KV Cache&lt;/td&gt;
&lt;td&gt;slot mapping、跳过无效 token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GQA Paged Attention&lt;/td&gt;
&lt;td&gt;在非连续缓存上稳定计算&lt;/td&gt;
&lt;td&gt;GQA、block table、变长序列、Online Softmax&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quest Sparse Attention&lt;/td&gt;
&lt;td&gt;先选块，再做精确注意力&lt;/td&gt;
&lt;td&gt;Top-K、分页映射、动态上下文与额外调度开销&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;ReshapeAndCache：正确写入分页 KV Cache&lt;/h3&gt;
&lt;p&gt;在解码过程中，新 token 产生的 Key 和 Value 最初是连续排列的，而 Paged Attention 使用按物理块组织的 KV Cache。ReshapeAndCache 需要根据 &lt;code&gt;slot_mapping&lt;/code&gt;，将每个 token 的 Key/Value 写入正确的物理块和块内偏移：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;block_idx    = slot_idx // block_size
block_offset = slot_idx % block_size
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;若 &lt;code&gt;slot_mapping[i] &amp;lt; 0&lt;/code&gt;，则应跳过对应 token。这个算子的计算并不复杂，但它处于 KV Cache 写入路径上，一旦地址映射错误，后续 Attention 读取到的就是错误上下文。测试因此不能只看算子是否执行成功，而必须分别比对 Key Cache 和 Value Cache 的写入结果。&lt;/p&gt;
&lt;h3&gt;GQA Paged Attention：非连续缓存上的稳定计算&lt;/h3&gt;
&lt;p&gt;集成版 Paged Attention 需要支持 GQA。Query 头数可以是 KV 头数的整数倍，多个 Query Head 共享同一组 KV Head。算子还需要读取 &lt;code&gt;block_tables&lt;/code&gt; 完成逻辑块到物理块的映射，使用 &lt;code&gt;context_lens&lt;/code&gt; 处理不同序列的实际长度，并对最后一个 block 中的无效 token 做 mask。&lt;/p&gt;
&lt;p&gt;在数值计算上，我们引入 Online Softmax，避免显式保存完整 attention score。在线更新需要维护当前最大值、归一化系数和累积输出；当新的分块改变最大值时，旧的累积结果必须按新尺度重标定。&lt;/p&gt;
&lt;h3&gt;Quest Sparse Attention：先选块，再做精确注意力&lt;/h3&gt;
&lt;p&gt;Quest Sparse Attention 面向长上下文。标准注意力需要让 Query 与全部历史 Key 交互，计算和访存成本随上下文增长。Quest 的思路是先以较低成本为逻辑块打分，选择最相关的 Top-K 块，再只对这些块执行精确注意力。&lt;/p&gt;
&lt;p&gt;集成版本需要同时满足：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支持 GQA；&lt;/li&gt;
&lt;li&gt;支持 batch 内不同的 &lt;code&gt;context_lens&lt;/code&gt;；&lt;/li&gt;
&lt;li&gt;通过 &lt;code&gt;block_table&lt;/code&gt; 访问分页 KV Cache；&lt;/li&gt;
&lt;li&gt;跳过超出实际上下文的逻辑块与 token；&lt;/li&gt;
&lt;li&gt;只对 &lt;code&gt;topk_blocks&lt;/code&gt; 个候选块执行完整注意力；&lt;/li&gt;
&lt;li&gt;保持对外接口稳定，使内部可以继续做融合和访存优化。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它不再是一个孤立的 Sparse kernel，而是生产推理路径中的 Sparse Paged Attention。评分、Top-K、物理块读取和精确注意力之间任何一处不一致，都会在最终模型输出或吞吐表现上放大。&lt;/p&gt;
&lt;h2&gt;分层评测体系&lt;/h2&gt;
&lt;p&gt;集成阶段采用“单算子正确性 → 组合正确性 → 离线吞吐 → 在线服务”的结构。&lt;/p&gt;
&lt;h3&gt;单算子正确性&lt;/h3&gt;
&lt;p&gt;三个算子分别构建独立测试：比较 Key/Value 写入后的缓存、Paged Attention 输出，以及以 PyTorch 版 Quest 流程为参考的 Sparse Attention 输出。&lt;/p&gt;
&lt;p&gt;测试输出最大误差，并给出明确状态。评测不能只检查进程退出码，因为“程序没有崩溃”与“数值正确”是两件不同的事。&lt;/p&gt;
&lt;h3&gt;可切换的端到端验证&lt;/h3&gt;
&lt;p&gt;框架通过环境变量切换实现：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;配置&lt;/th&gt;
&lt;th&gt;作用&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;VLLM_KVCACHE_MODE=npu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用官方 ReshapeAndCache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;VLLM_KVCACHE_MODE=triton&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用 Triton ReshapeAndCache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAGED_ATTN_MODE=npu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用官方 NPU Paged Attention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAGED_ATTN_MODE=triton&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用 Triton Paged Attention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PAGED_ATTN_MODE=sparse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;使用 Quest Sparse Attention&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这种设计可以只替换一个环节，也可以同时启用多个 Triton 算子进行组合测试。相比一次性替换整条路径，它大幅降低了定位错误的成本。&lt;/p&gt;
&lt;h3&gt;Offline Throughput&lt;/h3&gt;
&lt;p&gt;离线吞吐测试使用 vLLM CLI 直接运行数据集，关注总 token 吞吐、输出 token 吞吐和请求处理速度。它适合稳定比较 kernel 集成前后的总体计算效率，也便于固定 prompt 数、输入输出长度和执行参数。&lt;/p&gt;
&lt;h3&gt;Serving Benchmark&lt;/h3&gt;
&lt;p&gt;真实服务还会受到调度、并发和请求到达方式影响。因此我同时实现了 server + client 的 Serving Benchmark，记录 request throughput、token throughput、TTFT、TPOT、ITL、成功与失败请求数以及峰值并发。&lt;/p&gt;
&lt;p&gt;Offline 与 Serving 是两个不同口径。前者更接近受控条件下的吞吐能力，后者反映在线调度与并发服务能力，二者不能混成一个排名指标。&lt;/p&gt;
&lt;h2&gt;长序列 Sparse 测试&lt;/h2&gt;
&lt;p&gt;为了让 Sparse Attention 与 Paged Attention 在更有代表性的长上下文上比较，我新增了一组 workload 对齐的长序列测试。&lt;/p&gt;
&lt;p&gt;数据来自 LongBench 的三个子集：&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;子集&lt;/th&gt;
&lt;th&gt;样本数&lt;/th&gt;
&lt;th&gt;特点&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Qasper&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;长文档问答&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NarrativeQA&lt;/td&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;叙事文本理解&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GovReport&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;长篇政府报告&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;共 100 条 prompt，平均长度约 8K tokens。数据被重新整理为评测脚本可消费的格式，并尽量让 Sparse 与 Paged Attention 使用一致的请求集合和生成参数。只有 workload 一致，性能对比才有意义。&lt;/p&gt;
&lt;h3&gt;测试结果与解释&lt;/h3&gt;
&lt;p&gt;在这组约 8K 平均输入长度的数据上，Quest Sparse Attention 的端到端性能约为 Triton Paged Attention 的 2 倍。与此同时，它仍未追平基于 Ascend C 的官方实现，实测大致达到后者的 70%；Triton Paged Attention 则约为官方实现的 30%。&lt;/p&gt;
&lt;p&gt;这些比例是端到端测试中的近似值，不同批次、请求调度和统计口径会带来波动。更重要的结论是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;长序列上，动态稀疏开始体现减少 KV 读取和注意力计算的收益；&lt;/li&gt;
&lt;li&gt;稀疏算法收益足以显著超过当前 Triton 稠密实现；&lt;/li&gt;
&lt;li&gt;官方底层 kernel 的优化仍然很强，减少计算量不等于自动获得最高端到端性能；&lt;/li&gt;
&lt;li&gt;Sparse 路径仍有评分、Top-K、索引和多 kernel 调度开销。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;极限长度、精度与显存&lt;/h2&gt;
&lt;h3&gt;极限序列长度&lt;/h3&gt;
&lt;p&gt;我增加了 Sparse Attention 极限序列长度测试，并在当前模型与环境配置下验证到 &lt;code&gt;40960&lt;/code&gt; tokens，也就是该模型配置支持的最长上下文。&lt;/p&gt;
&lt;p&gt;一次 40960 tokens 测试成功，只能说明当前整条推理链路到达模型上限；要继续寻找 kernel 的理论或实现上限，还需要更长上下文模型、足够的 KV Cache，以及对 block table、grid 数量和索引类型的单独压力测试。&lt;/p&gt;
&lt;h3&gt;Sparse 精度&lt;/h3&gt;
&lt;p&gt;Sparse Attention 的正确性不能直接和完整稠密注意力逐元素相等，因为它只计算选出的 Top-K 块。参考语义是“同样的 Quest 评分 + 同样的 Top-K 选择 + 对选中 token 做精确注意力”。&lt;/p&gt;
&lt;p&gt;算法近似误差和 kernel 实现误差是两类问题。评测 kernel 正确性时，必须先固定稀疏选择结果，否则无法判断差异来自近似策略还是算子本身。&lt;/p&gt;
&lt;h3&gt;显存优化&lt;/h3&gt;
&lt;p&gt;vLLM 启动时会先进行 profiling run。它根据显存利用率配置，在扣除模型权重、框架开销和临时显存后，将剩余显存尽可能预分配给 KV Cache。因此，在同一配置下运行不同长度的数据集，最终看到的 KV Cache 显存占用通常都差不多。&lt;/p&gt;
&lt;p&gt;这不代表 Sparse Attention 没有减少计算或访存，也不能直接证明它节省了 KV Cache 容量。Quest 仍需保留完整 KV Cache，只是在每一步只读取和计算其中一部分。&lt;/p&gt;
&lt;p&gt;更合理的显存评测应区分：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;模型权重与框架常驻显存；&lt;/li&gt;
&lt;li&gt;预分配的 KV Cache 容量或 GPU block 数；&lt;/li&gt;
&lt;li&gt;kernel 执行期间的临时峰值显存；&lt;/li&gt;
&lt;li&gt;固定显存预算下可支持的最大并发和上下文长度；&lt;/li&gt;
&lt;li&gt;稀疏评分与 Top-K 带来的额外工作区。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;NPU Graph 与两种运行入口&lt;/h2&gt;
&lt;p&gt;常规 eager 模式通过，并不意味着算子可以进入图模式。图捕获会对动态 shape、Python 侧控制流、张量生命周期和编译时行为提出额外要求。为此，我补充了算子入图兼容脚本，并把它纳入答辩重新提交阶段的检查。&lt;/p&gt;
&lt;p&gt;同时，我分别实现 CLI 与 server 两种测评入口：CLI 用于受控的离线吞吐测试，server + client 用于服务化并发测试。两条路径覆盖了算子在 vLLM-Ascend 中最常见的使用方式，也让我们能够判断性能变化来自 kernel 本身，还是来自 scheduler、请求组织和服务配置。&lt;/p&gt;
&lt;h2&gt;项目结果&lt;/h2&gt;
&lt;p&gt;从项目最终统计看，环境配置阶段有 46 人参与，基础算子阶段各有约 40 人，综合算子阶段累计收到 65 人次提交，最终有 15 人完成集成测试。&lt;/p&gt;
&lt;p&gt;最终汇总中，Paged Attention 与 Sparse Attention 都出现了相对初始 baseline 的显著加速，集成阶段也产出了 Serving 与 Offline Sparse 两类端到端结果。这些数字是参赛选手和整个项目团队的共同成果，并非我的个人性能成绩；我的工作重点是让这些实现能够在统一环境、统一数据和统一口径下被正确比较。&lt;/p&gt;
&lt;p&gt;对评测工作来说，最终能得到一个排名并不是全部。更重要的是，这套流程能够回答：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;算子在独立输入上是否正确？&lt;/li&gt;
&lt;li&gt;接入真实 KV Cache 后是否仍然正确？&lt;/li&gt;
&lt;li&gt;稠密和稀疏路径是否使用同一 workload？&lt;/li&gt;
&lt;li&gt;性能提升来自 kernel，还是来自测试数据差异？&lt;/li&gt;
&lt;li&gt;长序列、变长 batch 和模型上限附近是否稳定？&lt;/li&gt;
&lt;li&gt;eager 模式和图模式是否都能运行？&lt;/li&gt;
&lt;li&gt;离线吞吐提升能否延续到在线 Serving？&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;这段经历带给我的认识&lt;/h2&gt;
&lt;h3&gt;出题本身也是系统设计&lt;/h3&gt;
&lt;p&gt;一份好的算子题面必须同时定义接口、数据布局、参考语义、容差、边界条件和性能口径。任何部分含糊，都会在提交阶段变成重复沟通，甚至造成不公平比较。亲自实现 baseline 和试做题目，是降低这种风险最有效的方式。&lt;/p&gt;
&lt;h3&gt;正确性测试要能定位&lt;/h3&gt;
&lt;p&gt;单个总误差只能告诉我们“错了”，分层切换官方实现与 Triton 实现，才能判断错误发生在 KV Cache 写入、分页读取、稀疏选择还是框架调用。可诊断性应当从一开始就写进测试架构。&lt;/p&gt;
&lt;h3&gt;性能测试首先要统一语义&lt;/h3&gt;
&lt;p&gt;比较两个实现之前，必须确认输入集合、prompt 长度、生成长度、并发、预热、缓存状态和统计指标一致。特别是 Sparse 与 Dense 的比较，如果 workload 不一致，结果再漂亮也没有解释力。&lt;/p&gt;
&lt;h3&gt;框架行为会改变指标含义&lt;/h3&gt;
&lt;p&gt;vLLM 的 KV Cache 预分配就是一个例子。只看监控工具中的“显存占用”很容易得到错误结论。理解框架何时分配内存、怎样调度请求、哪些阶段进入图捕获，与理解 kernel 本身同样重要。&lt;/p&gt;
&lt;h3&gt;从 kernel 到 serving，中间没有自动成立&lt;/h3&gt;
&lt;p&gt;一个 kernel 的 microbenchmark 很快，并不保证端到端吞吐一定提高。数据准备、索引、Top-K、kernel launch、图模式兼容和 scheduler 都可能抵消局部收益。也正因如此，最终阶段放在 vLLM-Ascend，而不是停留在单算子计时。&lt;/p&gt;
&lt;p&gt;这四个月的工作横跨算子实现、题目设计、测试框架、服务器环境、数据集和端到端推理。我做的不只是几道算子题，而是为一条从算子到大模型服务的完整链路建立了可实现、可测试、可比较的工程基线。&lt;/p&gt;
</content:encoded><category>项目</category><category>Triton-Ascend</category><category>Ascend NPU</category><category>vLLM-Ascend</category><category>Sparse Attention</category></item></channel></rss>