[BugFix] enforce RDMA transport for DSV4-Pro TP16 quick map

This commit is contained in:
Zhiyi Hong 2026-07-30 18:17:33 +08:00
parent 595bdde5d7
commit 0d3dd86519
10 changed files with 1160 additions and 75 deletions

View File

@ -1,5 +1,13 @@
# sskj — 多平台大模型推理性能基准测试项目 # sskj — 多平台大模型推理性能基准测试项目
> **更新2026-07-30 17:54:30 CST**
>
> 为 DeepSeek-V4-Pro 双机 TP16 quick-map 加入 RDMA fail-closed 启动保护。唯一 Shell 入口现在只允许计算网 `eth0/eth3` 与其 RDMA HCA `mlx5_0/mlx5_3`,在两端预检并透传 `rdma_cm/uverbs0/uverbs3`,服务健康后必须从两端 NCCL INFO 日志证明 `NET/IB` 和两条 HCA 均已启用,否则 benchmark 不会开始。运行清单新增 RDMA 开关、强制校验和设备路径;语法、结果解析器单测、完整 dry-run 及非法网卡/HCA 负例均已通过,真机 NET/IB 验证与 Phase 1 重跑尚未执行。
>
> **更新2026-07-30 17:45:18 CST**
>
> 修正 DeepSeek-V4-Pro 双机 TP16 quick-map 的 NCCL Socket 网卡错误。控制组确认服务容器未暴露 `/dev/infiniband`NCCL 实际回退 `NET/Socket`;旧 quick-map 又误选低速非计算网,导致冷 1K/32K Prefill 比 `eth0` 计算网 Socket 控制组慢约 10.9 倍/13.25 倍。默认 `NCCL_SOCKET_IFNAME` 已改为 `eth0`;旧约 65 token/s 结果降级为事故证据Phase 2 暂停并等待修正后的 Phase 1。新增双机通信/NCCL 术语 HTML、网络审计报告并保留原 `/data/qqt/sskj` TP16 脚本不变。
>
> **更新2026-07-30 16:38:41 CST** > **更新2026-07-30 16:38:41 CST**
> >
> 完成 DeepSeek-V4-Pro 双机 TP16 新旧脚本 TTFT 口径审计。确认旧产物受到 16 条 Warm-up、跨 Case 固定 Seed 递增长度、未清 Prefix Cache 及前一轮残留服务状态影响;同配置冷请求稳定复现约 16 秒/1K。新增 Phase 1 结果、脚本审计和 Phase 2 设计 HTML 档案,后续 Cold/Warm Prefix 指标分开报告。 > 完成 DeepSeek-V4-Pro 双机 TP16 新旧脚本 TTFT 口径审计。确认旧产物受到 16 条 Warm-up、跨 Case 固定 Seed 递增长度、未清 Prefix Cache 及前一轮残留服务状态影响;同配置冷请求稳定复现约 16 秒/1K。新增 Phase 1 结果、脚本审计和 Phase 2 设计 HTML 档案,后续 Cold/Warm Prefix 指标分开报告。

View File

@ -0,0 +1,696 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>6000D 双机通信与 NCCL 术语入门</title>
<style>
:root {
color-scheme: light;
--ink: #17202a;
--muted: #5f6b76;
--line: #d9dee3;
--panel: #f5f7f8;
--cyan: #087b83;
--cyan-soft: #e5f4f3;
--orange: #a64b17;
--orange-soft: #fff0e5;
--green: #287a45;
--green-soft: #eaf6ee;
--red: #a73535;
--red-soft: #fdecec;
--code: #f0f2f4;
--max: 1160px;
}
* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
margin: 0;
color: var(--ink);
background: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
font-size: 16px;
line-height: 1.72;
}
header {
color: #fff;
background: #172f35;
border-bottom: 5px solid #e57932;
}
.header-inner {
max-width: var(--max);
margin: 0 auto;
padding: 46px 28px 40px;
}
.eyebrow {
margin: 0 0 8px;
color: #9ed8d5;
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
}
h1, h2, h3 { letter-spacing: 0; }
header h1 {
max-width: 850px;
margin: 0;
font-size: clamp(32px, 5vw, 54px);
line-height: 1.12;
}
.header-meta {
display: flex;
flex-wrap: wrap;
gap: 10px 22px;
margin-top: 22px;
color: #d4e4e5;
font-size: 14px;
}
.layout {
display: grid;
grid-template-columns: 240px minmax(0, 1fr);
gap: 38px;
max-width: var(--max);
margin: 0 auto;
padding: 34px 28px 70px;
}
nav {
position: sticky;
top: 20px;
align-self: start;
padding-right: 20px;
border-right: 1px solid var(--line);
}
nav strong {
display: block;
margin-bottom: 10px;
font-size: 13px;
color: var(--muted);
}
nav a {
display: block;
padding: 6px 0;
color: #40515a;
font-size: 14px;
text-decoration: none;
}
nav a:hover { color: var(--cyan); }
main { min-width: 0; }
h2 {
margin: 48px 0 16px;
padding-top: 8px;
font-size: 28px;
line-height: 1.25;
border-top: 2px solid var(--ink);
}
h2:first-child { margin-top: 0; }
h3 {
margin: 30px 0 10px;
font-size: 20px;
line-height: 1.35;
}
p { margin: 10px 0; }
a { color: var(--cyan); }
code {
padding: 2px 5px;
border-radius: 3px;
background: var(--code);
font-family: "SFMono-Regular", Consolas, monospace;
font-size: .92em;
}
pre {
overflow-x: auto;
margin: 14px 0;
padding: 16px 18px;
color: #e8f1f1;
background: #1c292d;
border-left: 4px solid #4cb3ae;
border-radius: 4px;
line-height: 1.55;
}
pre code {
padding: 0;
color: inherit;
background: transparent;
}
table {
width: 100%;
margin: 16px 0 24px;
border-collapse: collapse;
font-size: 14px;
}
th, td {
padding: 11px 12px;
text-align: left;
vertical-align: top;
border: 1px solid var(--line);
}
th {
color: #26363e;
background: #eef1f3;
}
ul, ol { padding-left: 24px; }
.note, .warning, .finding, .good {
margin: 18px 0;
padding: 15px 18px;
border-left: 4px solid;
border-radius: 4px;
}
.note { background: var(--cyan-soft); border-color: var(--cyan); }
.warning { background: var(--orange-soft); border-color: var(--orange); }
.finding { background: var(--red-soft); border-color: var(--red); }
.good { background: var(--green-soft); border-color: var(--green); }
.path {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 8px;
margin: 18px 0 24px;
}
.path div {
min-height: 108px;
padding: 12px;
border: 1px solid var(--line);
border-top: 4px solid var(--cyan);
background: var(--panel);
}
.path b { display: block; margin-bottom: 5px; }
.split {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18px;
margin: 18px 0;
}
.split section {
padding: 16px 18px;
border: 1px solid var(--line);
border-radius: 4px;
}
.split h3 { margin-top: 0; }
.metric {
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
footer {
padding: 26px 28px 40px;
color: var(--muted);
text-align: center;
border-top: 1px solid var(--line);
}
@media (max-width: 900px) {
.layout { grid-template-columns: 1fr; }
nav {
position: static;
padding: 0 0 18px;
border-right: 0;
border-bottom: 1px solid var(--line);
}
nav a { display: inline-block; margin-right: 14px; }
.path { grid-template-columns: 1fr; }
.split { grid-template-columns: 1fr; }
}
@media print {
nav { display: none; }
.layout { display: block; max-width: none; }
header { color: #000; background: #fff; border-bottom-color: #000; }
.header-meta, .eyebrow { color: #333; }
pre { color: #000; background: #f4f4f4; }
}
</style>
</head>
<body>
<header>
<div class="header-inner">
<p class="eyebrow">Two-node communication primer</p>
<h1>6000D 双机通信与 NCCL 术语入门</h1>
<div class="header-meta">
<span>节点174.1.51.5 + 174.1.51.7</span>
<span>规模16 GPU / TP16</span>
<span>版本2026-07-30 17:54 CST</span>
</div>
</div>
</header>
<div class="layout">
<nav aria-label="目录">
<strong>阅读顺序</strong>
<a href="#mental-model">1. 一张总图</a>
<a href="#names">2. 设备名字</a>
<a href="#glossary">3. 核心术语</a>
<a href="#bootstrap">4. Bootstrap</a>
<a href="#parameters">5. NCCL 参数</a>
<a href="#logs">6. 日志怎么读</a>
<a href="#incident">7. 本次事故</a>
<a href="#checklist">8. 排查清单</a>
<a href="#sources">9. 官方资料</a>
</nav>
<main>
<h2 id="mental-model">1. 先建立一张总图</h2>
<p>
SGLang 不会自己搬运 16 张 GPU 之间的 Tensor。模型代码发起 TP/MoE 通信,
NCCL 决定用什么算法、经过哪条链路把数据送到其他 rank。
</p>
<div class="path" aria-label="通信路径">
<div><b>SGLang</b>执行模型层、TP16 和 EP2</div>
<div><b>Collective</b>AllReduce、AllGather、ReduceScatter、AllToAll</div>
<div><b>NCCL</b>构造 rank、ring/tree 和 channel</div>
<div><b>Transport</b>机内 P2P/IPC跨机 NET/IB 或 NET/Socket</div>
<div><b>硬件</b>GPU、PCIe、HCA、网卡、光模块、交换机</div>
</div>
<div class="note">
<strong>最重要的区分:</strong>
<code>NCCL bootstrap</code> 是“启动时要完成的一件事”;
<code>NCCL_SOCKET_IFNAME</code> 是“选择 IP 网卡的一个参数”;
<code>NET/IB</code><code>NET/Socket</code> 才是 NCCL 实际搬运数据的传输后端。
</div>
<h3>两类跨机路径</h3>
<div class="split">
<section>
<h3>理想路径RDMA</h3>
<p><code>GPU → HCA → RoCE 网络 → HCA → GPU</code></p>
<p>日志应出现 <code>NET/IB</code>,支持时还会出现 <code>GDRDMA</code></p>
</section>
<section>
<h3>回退路径TCP Socket</h3>
<p><code>GPU/CPU → Linux Socket → ethX → TCP/IP → ethX</code></p>
<p>日志会出现 <code>Using network Socket</code>。这不是报错,但性能通常低得多。</p>
</section>
</div>
<h2 id="names">2. eth0 和 mlx5_0 不是同一个设备</h2>
<div class="note">
<strong>400G 是物理 Ethernet 端口的标称链路速率。</strong>
<code>eth0</code> 是该端口的 Linux netdev/IP 入口;
<code>mlx5_0</code> 是映射到该端口的 RDMA Verbs/HCA 入口。
二者相关联,但不相等,也不代表 TCP 或 RDMA 应用一定能跑到 400G。
</div>
<table>
<thead>
<tr><th>名字</th><th>属于哪一层</th><th>负责什么</th><th>本机实例</th></tr>
</thead>
<tbody>
<tr>
<td><code>eth0</code></td>
<td>Linux IP 网卡接口</td>
<td>配置 IP、TCP/UDP、路由<code>NCCL_SOCKET_IFNAME</code> 选择</td>
<td>400 Gbit/s 计算网</td>
</tr>
<tr>
<td><code>eth3</code></td>
<td>Linux IP 网卡接口</td>
<td>第二条计算网 Rail</td>
<td>400 Gbit/s 计算网</td>
</tr>
<tr>
<td><code>mlx5_0</code></td>
<td>RDMA HCA / Verbs 设备</td>
<td><code>NET/IB</code> 使用;由 <code>NCCL_IB_HCA</code> 选择</td>
<td>对应 <code>eth0</code>,挂 switch 1</td>
</tr>
<tr>
<td><code>mlx5_3</code></td>
<td>RDMA HCA / Verbs 设备</td>
<td>第二条 RDMA Rail</td>
<td>对应 <code>eth3</code>,挂 switch 2</td>
</tr>
<tr>
<td><code>/dev/infiniband/uverbs0</code></td>
<td>Linux 字符设备</td>
<td>容器进程访问 RDMA Verbs 的入口</td>
<td>对应 <code>mlx5_0</code></td>
</tr>
</tbody>
</table>
<pre><code>mlx5_0 port 1 ==> eth0 (Up)
mlx5_3 port 1 ==> eth3 (Up)</code></pre>
<p>
同一条物理端口可以同时暴露 Linux IP 接口和 RDMA HCA。
<code>eth0</code> 是 IP/Socket 世界的入口,<code>mlx5_0</code> 是 RDMA
Verbs 世界的入口。<code>ibdev2netdev</code> 输出的是映射关系,不是等号。
本项目部署时只把 <code>eth0/eth3</code> 作为节点间计算网。
</p>
<pre><code>同一条 400G 物理 Ethernet 端口
├── eth0 -> Linux netdev -> IP / TCP Socket
└── mlx5_0 -> RDMA HCA -> RoCE / Verbs / GDRDMA</code></pre>
<p>
<code>400 Gbit/s = 50 GB/s</code> 只是单方向理论线速。协议开销、PCIe、
CPU、Socket 线程、消息大小和 collective 算法都会让实际
<code>algbw/busbw</code> 低于或采用不同统计口径。
</p>
<h2 id="glossary">3. 核心术语字典</h2>
<table>
<thead>
<tr><th>术语</th><th>通俗解释</th><th>在本项目中的意义</th></tr>
</thead>
<tbody>
<tr>
<td><strong>NCCL</strong></td>
<td>NVIDIA 的多 GPU 通信库,负责高效实现 collective 和点对点通信。</td>
<td>SGLang TP16 每层跨 GPU 通信最终大量落到 NCCL。</td>
</tr>
<tr>
<td><strong>rank</strong></td>
<td>一个通信参与者的编号。TP16 communicator 有 rank 015。</td>
<td>两台机器各 8 个 GPU rank共 16 个。</td>
</tr>
<tr>
<td><strong>collective</strong></td>
<td>一组 rank 共同参与的通信操作。</td>
<td>TP 常见 AllReduce、AllGather、ReduceScatterMoE 还可能有 AllToAll。</td>
</tr>
<tr>
<td><strong>RDMA</strong></td>
<td>远端直接内存访问。网卡可直接读写远端内存,减少 CPU 和内核数据拷贝。</td>
<td>双机 TP16 希望使用的高速数据路径。</td>
</tr>
<tr>
<td><strong>IB</strong></td>
<td>InfiniBand。既是一套高速网络体系也常被 NCCL 用作 Verbs/RDMA 后端的统称。</td>
<td>NCCL 日志里的 <code>NET/IB</code> 也可承载 RoCE不代表交换机一定是原生 IB。</td>
</tr>
<tr>
<td><strong>RoCE</strong></td>
<td>RDMA over Converged Ethernet在以太网上承载 RDMA。</td>
<td>本项目的 400G 计算网类型。</td>
</tr>
<tr>
<td><strong>HCA</strong></td>
<td>Host Channel Adapter提供 RDMA 能力的适配器。</td>
<td><code>mlx5_0</code><code>mlx5_3</code></td>
</tr>
<tr>
<td><strong>GDRDMA</strong></td>
<td>GPUDirect RDMA让 HCA 直接访问 GPU 显存,减少经 CPU 内存中转。</td>
<td>跨机 GPU 通信的理想路径,日志可见 <code>via NET/IB/.../GDRDMA</code></td>
</tr>
<tr>
<td><strong>Socket / TCP</strong></td>
<td>普通 IP 网络编程路径。NCCL 找不到 RDMA 时会使用。</td>
<td>本次脚本实际发生的回退路径。</td>
</tr>
<tr>
<td><strong>Rail</strong></td>
<td>一条相对独立的网络通道,通常由一张 HCA 和一套交换路径组成。</td>
<td><code>mlx5_0/switch 1</code><code>mlx5_3/switch 2</code> 是双 Rail。</td>
</tr>
<tr>
<td><strong>ring / tree</strong></td>
<td>NCCL 对 collective 的通信拓扑组织方式。</td>
<td><code>NCCL_CROSS_NIC</code> 决定同一 ring/tree 能否跨不同 NIC。</td>
</tr>
<tr>
<td><strong>PFC / ECN</strong></td>
<td>RoCE 网络控制拥塞和丢包的机制。</td>
<td>RDMA 出现 retry、pause 或吞吐抖动时由运维检查。</td>
</tr>
</tbody>
</table>
<h2 id="bootstrap">4. NCCL bootstrap 到底是什么</h2>
<p>
NCCL 本身不是进程启动器。SGLang 先启动各个 workerNCCL communicator 初始化时,
rank 之间需要交换地址、唯一 ID、拓扑和连接信息这段“先认识彼此”的过程就是 bootstrap。
</p>
<ol>
<li>每个 rank 启动并获得自己的 rank ID。</li>
<li>通过 IP Socket 交换 NCCL unique ID 和连接信息。</li>
<li>NCCL 探测 GPU、PCIe、HCA 和节点拓扑。</li>
<li>构造 ring/tree/channel。</li>
<li>选择真正的数据传输后端P2P、SHM、NET/IB 或 NET/Socket。</li>
</ol>
<div class="warning">
<strong>容易误解的地方:</strong>
<code>NCCL_SOCKET_IFNAME</code> 不保证“只用于 bootstrap”。
RDMA 正常时它主要承担 bootstrapRDMA 失败并回退 Socket 后,它也会决定大块 Tensor
数据走哪张 IP 网卡。
</div>
<h2 id="parameters">5. 常见 NCCL 参数</h2>
<h3>NCCL_SOCKET_IFNAME</h3>
<p>筛选 NCCL 可使用的 Linux IP 接口。精确指定接口时可写:</p>
<pre><code>NCCL_SOCKET_IFNAME="=eth0"</code></pre>
<ul>
<li>RDMA 正常:主要影响 bootstrap/OOB IP 连接。</li>
<li>RDMA 不可用:决定 <code>NET/Socket</code> 的数据网卡。</li>
<li>部署时只允许使用计算网 <code>eth0/eth3</code>Socket 回退时不能落到其他接口。</li>
</ul>
<h3>NCCL_IB_HCA</h3>
<p>筛选 NCCL 的 RDMA HCA。推荐使用精确匹配</p>
<pre><code>NCCL_IB_HCA="=mlx5_0:1,mlx5_3:1"</code></pre>
<p>
这个变量只是“允许选择谁”,不会自动把宿主机 RDMA 设备送进容器。
容器还必须看到 <code>/dev/infiniband/rdma_cm</code>
<code>uverbs0</code><code>uverbs3</code>
</p>
<h3>NCCL_CROSS_NIC</h3>
<table>
<thead>
<tr><th></th><th>行为</th><th>适用直觉</th></tr>
</thead>
<tbody>
<tr>
<td><code>0</code></td>
<td>尽量让同一 ring/tree 在不同节点使用对应的同一条 Rail。</td>
<td>每张 NIC 接不同交换机、跨 Rail 代价高的 rail-optimized 网络。</td>
</tr>
<tr>
<td><code>1</code></td>
<td>允许同一 ring/tree 在不同节点使用不同 NIC。</td>
<td>所有 NIC 进入同一网络 Fabric跨 NIC 不构成额外问题。</td>
</tr>
<tr>
<td><code>2</code></td>
<td>优先对应同一 NIC但必要时允许跨 NIC。</td>
<td>NCCL 默认的折中策略。</td>
</tr>
</tbody>
</table>
<div class="note">
本项目的 <code>mlx5_0</code><code>mlx5_3</code> 分挂 switch 1/2
拓扑直觉上更偏向 <code>0</code> 或默认 <code>2</code>。最终值必须在
<strong>真正启用 NET/IB 后</strong>用 all_reduce 和 SGLang 端到端 A/B 决定。
当 NCCL 使用 NET/Socket 时,这个参数不参与路径选择。
</div>
<h3>NCCL_DEBUG 与 NCCL_DEBUG_SUBSYS</h3>
<pre><code>NCCL_DEBUG=INFO
NCCL_DEBUG_SUBSYS=INIT,NET,GRAPH,TUNING</code></pre>
<p>用于确认实际路径,诊断完成后应关闭,正式性能数据不要长期带 INFO 日志。</p>
<h2 id="logs">6. NCCL 日志速查</h2>
<table>
<thead>
<tr><th>日志</th><th>含义</th><th>判断</th></tr>
</thead>
<tbody>
<tr>
<td><code>Bootstrap: Using eth0:...</code></td>
<td>初始化控制连接选择 eth0。</td>
<td>只说明 bootstrap尚不能证明数据走 RDMA。</td>
</tr>
<tr>
<td><code>NET/IB : Using ... mlx5_0 ...</code></td>
<td>NCCL 已识别 RDMA HCA。</td>
<td>RDMA 数据后端可用。</td>
</tr>
<tr>
<td><code>via NET/IB/.../GDRDMA</code></td>
<td>跨机边通过 GPUDirect RDMA。</td>
<td>理想证据。</td>
</tr>
<tr>
<td><code>NET/IB : No device found</code></td>
<td>容器没有可用 RDMA 设备或驱动/权限不完整。</td>
<td>继续看是否回退 Socket。</td>
</tr>
<tr>
<td><code>NET/Socket : Using 非计算网...</code></td>
<td>跨机数据由普通 TCP Socket 传输。</td>
<td>若误入低速非计算网,性能会严重受限。</td>
</tr>
<tr>
<td><code>via P2P/IPC</code></td>
<td>同机 GPU 通过 CUDA P2P/IPC。</td>
<td>机内路径,不代表跨机路径。</td>
</tr>
</tbody>
</table>
<h2 id="incident">7. 2026-07-30 Prefill 变慢事故复盘</h2>
<h3>已观测事实</h3>
<ul>
<li>宿主机存在 <code>/dev/infiniband</code>,两条 400G Rail 均 Up。</li>
<li>原脚本容器内不存在 <code>/dev/infiniband</code></li>
<li>NCCL INFO 明确打印 <code>NET/IB : No device found</code><code>Using network Socket</code></li>
<li>原脚本选择 400G 计算网 <code>eth0</code>quick-map 曾误选低速非计算网。</li>
<li>部署规定只有 <code>eth0/eth3</code> 用于节点间通信,两者均为 400G。</li>
</ul>
<table>
<thead>
<tr><th>冷缓存 Shape</th><th>原脚本网络eth0 Socket</th><th>quick-map错误的非计算网</th><th>差异</th></tr>
</thead>
<tbody>
<tr>
<td>1K → 1, C=1</td>
<td class="metric">TTFT 1.458s / 693.1 input tok/s</td>
<td class="metric">TTFT 15.8816.04s / 约 64 tok/s</td>
<td>约 10.9×</td>
</tr>
<tr>
<td>32K → 1, C=1</td>
<td class="metric">TTFT 38.062s / 860.5 input tok/s</td>
<td class="metric">TTFT 504.44s / 64.96 tok/s</td>
<td>约 13.25×</td>
</tr>
</tbody>
</table>
<div class="finding">
<strong>根因判断:</strong>
quick-map 没有把 RDMA 设备透传进容器,却把 <code>NCCL_SOCKET_IFNAME</code>
设成低速非计算网。NCCL 回退 NET/Socket 后TP16 跨机数据没有进入规定的
<code>eth0/eth3</code> 计算网。<code>NCCL_CROSS_NIC=1</code> 在没有 NET/IB
的情况下不是致因。
</div>
<h3>为什么旧日志还会比 38 秒更短</h3>
<p>
旧矩阵脚本还有第二个独立因素:<code>warmup_requests=16</code>、固定
<code>seed=42</code>、ISL/OSL/C 升序运行,而且从不 flush Prefix Cache。
因此旧日志混入缓存命中,不能直接与冷 Prefill 比较。
</p>
<div class="good">
quick-map 现在只允许 <code>eth0/eth3</code>
<code>mlx5_0/mlx5_3</code>,并会透传精确 RDMA 设备、强制检查两端
<code>NET/IB</code> 日志。代码与 dry-run 已通过;下一步是真机启动验证,
在拿到运行时证据前不进入 Kernel 归因。
</div>
<h2 id="checklist">8. 从宿主机到 NCCL 的排查清单</h2>
<ol>
<li>
<strong>宿主机链路:</strong>
<code>ethtool eth0</code><code>ethtool eth3</code>
</li>
<li>
<strong>HCA 映射:</strong>
<code>ibdev2netdev</code>,确认 <code>mlx5_0→eth0</code>
<code>mlx5_3→eth3</code>
</li>
<li>
<strong>宿主机设备:</strong>
<code>ls -l /dev/infiniband</code>
</li>
<li>
<strong>容器设备:</strong>
<code>docker exec CONTAINER ls -l /dev/infiniband</code>
宿主机有、容器没有NCCL 仍然用不了 RDMA。
</li>
<li>
<strong>运行时证据:</strong>
用一次 <code>NCCL_DEBUG=INFO</code> 启动,搜索
<code>NET/IB</code><code>NET/Socket</code><code>GDRDMA</code>
</li>
<li>
<strong>硬件计数器:</strong>
同时观察 eth0/eth3 流量和 RDMA 端口计数;不能只看环境变量。
</li>
<li>
<strong>端到端 A/B</strong>
冷缓存、同一 prompt、同一模型参数仅改变一个网络变量。
</li>
</ol>
<h3>最小 RDMA 设备透传验证</h3>
<pre><code>docker run --rm \
--device=/dev/infiniband/rdma_cm \
--device=/dev/infiniband/uverbs0 \
--device=/dev/infiniband/uverbs3 \
IMAGE \
ls -l /dev/infiniband</code></pre>
<p>
能看到设备只是第一关。最终仍必须从 NCCL INFO 中看到 <code>NET/IB</code>
并通过通信基准与 SGLang 结果确认。
</p>
<h2 id="sources">9. 官方资料</h2>
<ul>
<li>
<a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/setup.html">
NVIDIA NCCL Setupbootstrap 与通信安全边界
</a>
</li>
<li>
<a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/env.html">
NVIDIA NCCL Environment VariablesSOCKET_IFNAME、IB_HCA、CROSS_NIC
</a>
</li>
<li>
<a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/troubleshooting/networking_troubleshooting.html">
NVIDIA NCCL Networking Troubleshooting
</a>
</li>
</ul>
<p>
<a href="./推理优化计划.html">返回推理优化主计划</a>
</p>
</main>
</div>
<footer>
本页以两台 6000D 的真实设备映射和 2026-07-30 实测日志为例。
</footer>
</body>
</html>

View File

@ -228,7 +228,7 @@
<div class="meta"> <div class="meta">
<span>节点174.1.51.5 + 174.1.51.7</span> <span>节点174.1.51.5 + 174.1.51.7</span>
<span>拓扑SGLang TP16 / EP2</span> <span>拓扑SGLang TP16 / EP2</span>
<span>更新2026-07-30 15:46 CST</span> <span>更新2026-07-30 17:54 CST</span>
</div> </div>
</div> </div>
</header> </header>
@ -237,15 +237,19 @@
<a class="back" href="./推理优化计划.html">返回推理优化主计划</a> <a class="back" href="./推理优化计划.html">返回推理优化主计划</a>
<p class="status"> <p class="status">
<strong>阶段状态:提前结束,已进入硬件归因</strong> <strong>阶段状态:网络错误已定位RDMA fail-closed 代码已完成,等待真机验证和重跑</strong>
第一次真机 Run 第一次真机 Run
<code>dsv4pro-pro6000d-2node-sglang-quick-20260730-140026</code> <code>dsv4pro-pro6000d-2node-sglang-quick-20260730-140026</code>
已验证双机服务可用,但因发现请求量和 Prefix Cache 口径问题而主动停止。 已验证双机服务可用,但因发现请求量和 Prefix Cache 口径问题而主动停止。
精简后的第二次 Run 精简后的第二次 Run
<code>dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625</code> <code>dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625</code>
完成 3 个 Prefill 固定点后,发现输入吞吐稳定锁定在约 完成 3 个 Prefill 固定点后曾观察到约 <code>65 token/s</code>
<code>65 token/s</code>。继续扫描 Decode 和混合流量不能解释该异常, 后续控制组确认 quick-map 容器看不到 RDMANCCL 回退
因此用户决定中止第 4 个 Case直接进入 Phase 2。 <code>NET/Socket</code>,同时脚本把 Socket 接口设成了低速非计算网。
因此这 3 个结果不能作为模型、算子或 TP16 的性能基线Phase 2 暂停,
当前入口已固定计算网 <code>eth0/eth3</code>
<code>mlx5_0/mlx5_3</code>,完成设备透传、日志强校验和 dry-run
只有真机日志证明 <code>NET/IB</code> 后才会重跑 Phase 1。
Manifest 终态为 <code>ABORTED_EARLY_FOR_PHASE2</code> Manifest 终态为 <code>ABORTED_EARLY_FOR_PHASE2</code>
两节点容器和 16 张 GPU 已清理。 两节点容器和 16 张 GPU 已清理。
</p> </p>
@ -261,7 +265,7 @@
<li>只测试 SGLang不测试 vLLM。</li> <li>只测试 SGLang不测试 vLLM。</li>
<li>使用双机 16 卡完整实例,不做 PD 分离。</li> <li>使用双机 16 卡完整实例,不做 PD 分离。</li>
<li>不启用 MTP、EAGLE、DSpark 或其他投机解码。</li> <li>不启用 MTP、EAGLE、DSpark 或其他投机解码。</li>
<li>本轮不启用 Profiler三个已完成 Case 可作为对应 Shape 的端到端基线。</li> <li>本轮不启用 Profiler受错误 Socket 网络影响的三个旧 Case 仅保留为事故证据,不再作为端到端基线。</li>
<li>不修改或调用旧的全天全量 Benchmark 脚本。</li> <li>不修改或调用旧的全天全量 Benchmark 脚本。</li>
</ul> </ul>
@ -351,14 +355,19 @@ bash run_quick_map.sh stop</code></pre>
<td>覆盖固定矩阵中的 Decode C64</td> <td>覆盖固定矩阵中的 Decode C64</td>
</tr> </tr>
<tr> <tr>
<td>NCCL bootstrap</td> <td>NCCL Socket 接口</td>
<td><code>eth1</code></td> <td><code>eth0</code></td>
<td>普通 TCP 建连接口</td> <td>RDMA 失败回退时也承载跨机 Tensor不只是 bootstrap</td>
</tr> </tr>
<tr> <tr>
<td>RoCE HCA</td> <td>RoCE HCA</td>
<td><code>mlx5_0,mlx5_3</code></td> <td><code>mlx5_0,mlx5_3</code></td>
<td>双 Rail 数据面,<code>NCCL_CROSS_NIC=1</code></td> <td>启动器只透传对应的 <code>uverbs0/uverbs3</code><code>rdma_cm</code></td>
</tr>
<tr>
<td>传输后端门禁</td>
<td><code>REQUIRE_NCCL_IB=1</code></td>
<td>两端日志未证明 <code>NET/IB + mlx5_0 + mlx5_3</code> 时禁止开始 benchmark</td>
</tr> </tr>
<tr> <tr>
<td>代码分支</td> <td>代码分支</td>
@ -565,38 +574,43 @@ wait "${background_pid}"</code></pre>
</tbody> </tbody>
</table> </table>
<h3>9.1 可以下的结论</h3> <p class="status">
<strong>这些数值已被降级为“错误网络路径复现”。</strong>
它们可以证明低速 Socket 路径近似随输入长度线性增长,但不能用于判断
DeepSeek-V4-Pro、SGLang Kernel、GPU 算力或正确 TP16 数据面的性能。
</p>
<h3>9.1 对错误网络 Run 可以下的结论</h3>
<ul> <ul>
<li>三个长度的输入吞吐只相差约 1.2%,稳定在 <code>64.4465.20 token/s</code></li> <li>三个长度的输入吞吐只相差约 1.2%,稳定在 <code>64.4465.20 token/s</code></li>
<li>32K TTFT 约为 1K 的 31.76 倍128K TTFT 约为 32K 的 3.99 倍,几乎按 token 数线性增长。</li> <li>32K TTFT 约为 1K 的 31.76 倍128K TTFT 约为 32K 的 3.99 倍,几乎按 token 数线性增长。</li>
<li>这不是偶发卡顿:服务端每约 125 秒完成一个 8192-token ChunkGPU 在该期间持续忙碌。</li> <li>这不是偶发卡顿:服务端每约 125 秒完成一个 8192-token ChunkGPU 在该期间持续忙碌。</li>
<li><code>32K, C=16</code> 的观察窗口内,日志持续显示 <code>#new-seq: 1</code>,队列从 15 降到 14至少当前路径没有立即把 16 条 Prefill 合成大批次。</li> <li><code>32K, C=16</code> 的观察窗口内,日志持续显示 <code>#new-seq: 1</code>,队列从 15 降到 14至少当前路径没有立即把 16 条 Prefill 合成大批次。</li>
<li>没有出现 OOM、NCCL、CUDA、EngineDead 或请求失败,所以“服务崩溃”不能解释低吞吐。</li> <li>没有出现 OOM、CUDA、EngineDead 或请求失败,但 NCCL 没有使用预期 RDMA 数据面</li>
</ul> </ul>
<h3>9.2 现在还不能下的结论</h3> <h3>9.2 现在还不能下的结论</h3>
<ul> <ul>
<li>仅凭 GPU Utilization=100% 不能判断是算力、显存带宽还是通信瓶颈。</li> <li>仅凭 GPU Utilization=100% 不能判断是算力、显存带宽还是通信瓶颈。</li>
<li>尚不能断言是 SGLang Bug、DSV4/NSA Kernel、TP16 NCCL、MoE Backend 或 Scheduler 中的哪一项</li> <li>不能用本轮断言 SGLang Bug、DSV4/NSA Kernel、MoE Backend 或 Scheduler 存在性能问题</li>
<li>Decode、Balanced 和混合 A/B 未执行Phase 1 不提供这些场景的基线。</li> <li>Decode、Balanced 和混合 A/B 未执行Phase 1 不提供这些场景的基线。</li>
<li>每个 Shape 只有一次重复,不能用于稳定性或 CV 结论。</li> <li>每个 Shape 只有一次重复,不能用于稳定性或 CV 结论。</li>
</ul> </ul>
<h3>9.3 为什么提前结束</h3> <h3>9.3 为什么提前结束</h3>
<p> <p>
Phase 1 的目标是发现值得归因的关键异常,而不是机械完成九个格子。 当时根据约 65 token/s 的稳定信号提前停止第 4 个 Case并写入
三个独立长度已经给出同一个稳定信号;第 4 个并发 Prefill 在 922 秒后仍表现为 <code>EARLY_STOP_FOR_PHASE2</code>。后续网络控制组表明这个停止动作仍然避免了
单序列 Chunk 推进。继续执行剩余矩阵预计还需数小时,却不能回答 无效算力消耗,但调查方向需要修正:不是立即进入 Kernel/硬件归因,而是先校正
“这 65 token/s 到底卡在哪里”。因此第 4 个 Case 被写入 NCCL 数据面并重跑快速地图。
<code>EARLY_STOP_FOR_PHASE2</code>,其余固定点和混合 A/B 保留为未执行。
</p> </p>
<h3>9.4 为什么旧脚本的 TTFT 短很多</h3> <h3>9.4 为什么旧脚本的 TTFT 短很多:缓存与网络两个因素</h3>
<p> <p>
2026-07-30 对旧目录 2026-07-30 对旧目录
<code>/data/qqt/sskj/experiments/pro6000/dsv4_pro6000_sglang_tp16</code> <code>/data/qqt/sskj/experiments/pro6000/dsv4_pro6000_sglang_tp16</code>
做了逐项审计。结论是:<strong>旧结果与本轮冷 Prefill 不是同一缓存口径</strong> 做了逐项审计,并追加原网络配置冷请求控制组最终结论是:
不是 quick-map 把相同请求跑慢了。 <strong>旧结果与 quick-map 既不是同一缓存口径,也不是同一 Socket 网络路径。</strong>
</p> </p>
<table> <table>
<thead> <thead>
@ -606,8 +620,8 @@ wait "${background_pid}"</code></pre>
<tr> <tr>
<td>服务端配置</td> <td>服务端配置</td>
<td>同一镜像TP16 / EP28K ChunkFlashInfer MXFP4 MoE</td> <td>同一镜像TP16 / EP28K ChunkFlashInfer MXFP4 MoE</td>
<td>相同</td> <td>模型参数同,但 NCCL/IP 接口不</td>
<td class="pass">排除明显的启动参数回归</td> <td>不能排除网络启动参数回归</td>
</tr> </tr>
<tr> <tr>
<td>正式请求数</td> <td>正式请求数</td>
@ -645,10 +659,10 @@ wait "${background_pid}"</code></pre>
<tr><th>最小复现</th><th>Mean TTFT</th><th>P95 TTFT</th><th>解释</th></tr> <tr><th>最小复现</th><th>Mean TTFT</th><th>P95 TTFT</th><th>解释</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>1K→1首次冷缓存</td><td>16.04 s</td><td>16.04 s</td><td>清 Prefix Cache1 条正式请求</td></tr> <tr><td>quick-map 错误网络1K→1 冷缓存</td><td>15.8816.04 s</td><td>15.8816.04 s</td><td>低速非计算网 Socket 路径</td></tr>
<tr><td>1K→1原样再次冷缓存</td><td>15.90 s</td><td>15.90 s</td><td>再次清 Cache排除一次性 JIT 主导</td></tr> <tr><td>旧原始网络1K→1 冷缓存</td><td>1.458 s</td><td>1.458 s</td><td><code>eth0</code> 400G 物理端口上的 Socket 路径</td></tr>
<tr><td>1K→128冷缓存</td><td>15.79 s</td><td>15.79 s</td><td>排除 OSL=1 特殊慢路径</td></tr> <tr><td>旧原始网络32K→1 冷缓存</td><td>38.062 s</td><td>38.062 s</td><td>比 quick-map 的 504.44 s 快约 13.25 倍</td></tr>
<tr><td>旧命令语义重新复现</td><td>14.60 s</td><td>15.97 s</td><td>10 条正式请求、16 条 Warm-up、不清 Cache</td></tr> <tr><td>quick-map1K→128 冷缓存</td><td>15.79 s</td><td>15.79 s</td><td>排除 OSL=1 特殊慢路径,但仍受错误网络影响</td></tr>
<tr><td>2026-07-28 旧产物</td><td>0.455 s</td><td>0.513 s</td><td>服务已被前一次 Run 和后续递增长度预热</td></tr> <tr><td>2026-07-28 旧产物</td><td>0.455 s</td><td>0.513 s</td><td>服务已被前一次 Run 和后续递增长度预热</td></tr>
</tbody> </tbody>
</table> </table>
@ -659,9 +673,7 @@ wait "${background_pid}"</code></pre>
Input TPS因此会把只计算新增后缀的耗时除进完整 token 数,进一步放大吞吐。 Input TPS因此会把只计算新增后缀的耗时除进完整 token 数,进一步放大吞吐。
</p> </p>
<p> <p>
审计结论Phase 1 的约 65 token/s 是<strong>冷 Prefix Cache 的完整 Prompt 路径</strong> 缓存审计原始产物保存在:
旧结果是热缓存/递增前缀路径。两者都可以测,但必须分成 Cold 与 Warm 两套实验,
不能放在同一列直接比较。审计原始产物保存在:
</p> </p>
<pre><code>/data/hzy/dsv4_script_audit_20260730/</code></pre> <pre><code>/data/hzy/dsv4_script_audit_20260730/</code></pre>
<p> <p>
@ -670,6 +682,42 @@ wait "${background_pid}"</code></pre>
及同目录原始 JSON/log。 及同目录原始 JSON/log。
</p> </p>
<h3>9.5 NCCL 数据面控制组</h3>
<p>
原脚本控制组使用其默认 <code>NCCL_SOCKET_IFNAME=eth0</code>,不注入
<code>NCCL_IB_HCA</code><code>NCCL_CROSS_NIC</code>。NCCL INFO 和容器设备
检查给出了直接证据:
</p>
<pre><code>NET/IB : No device found
NET/Socket : Using [0]eth0:10.101.0.11
Using network Socket
docker exec ... ls /dev/infiniband
# No such file or directory</code></pre>
<p>
宿主机本身存在 <code>uverbs0/uverbs3/rdma_cm</code>,且
<code>mlx5_0→eth0</code><code>mlx5_3→eth3</code> 均 Up。这说明问题位于
容器设备透传和脚本网卡选择不是物理计算网缺失。quick-map 与原脚本使用相同
Docker 设备边界,却把 Socket 接口设成了低速非计算网,造成 1013 倍退化。
<code>NET/Socket</code> 路径上,<code>NCCL_CROSS_NIC=1</code> 不参与选择。
</p>
<p>
修正后的唯一入口已加入三层门禁两端字符设备预检、Docker 精确
<code>--device</code> 透传、健康后 NCCL INFO 传输后端验证。静态检查、
结果解析器单测和完整 dry-run 已通过;非法 <code>eth1</code>
<code>mlx5_1</code> 会在加载模型前被拒绝。该结论仍属于代码验证,
真机 <code>NET/IB</code> 成功证据要等下一次启动后补入。
</p>
<p>
网络控制组原始产物:
</p>
<pre><code>/data/hzy/dsv4_oldscript_cold_control_20260730/</code></pre>
<p>
本地归档:
<a href="./results/network-path-audit-20260730/report.md">TP16 网络路径审计报告</a>
及同目录 NCCL/benchmark 原始日志。
</p>
<table> <table>
<thead> <thead>
<tr> <tr>
@ -679,12 +727,13 @@ wait "${background_pid}"</code></pre>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>服务健康</td><td class="pass">通过</td><td>端口 <code>30002</code> 已就绪,无 OOM、NCCL 或 Engine 异常</td></tr> <tr><td>服务健康</td><td class="pass">通过</td><td>端口可用且无 OOM/Engine 异常,但 NCCL 数据面未按预期进入 RDMA</td></tr>
<tr><td>第一次固定点 Run</td><td class="pending">主动停止</td><td>发现 32K 单请求约需十余分钟;原协议的 4 次同形状请求会使整轮再次接近半天</td></tr> <tr><td>第一次固定点 Run</td><td class="pending">主动停止</td><td>发现 32K 单请求约需十余分钟;原协议的 4 次同形状请求会使整轮再次接近半天</td></tr>
<tr><td>精简后九个固定点</td><td class="pending">3 完成 / 1 中止 / 5 未执行</td><td>Prefill 异常信号已足够清晰,停止继续消耗算力</td></tr> <tr><td>RDMA fail-closed 启动器</td><td class="pass">静态验证通过</td><td>仅允许两条计算网 Rail设备透传、日志强校验与运行清单已加入</td></tr>
<tr><td>精简后九个固定点</td><td class="pending">旧 Run 作废,待重跑</td><td>先完成真机 NET/IB 验证,再生成新的性能基线</td></tr>
<tr><td>混合干扰 A/B</td><td class="pending">未执行</td><td>待 Prefill 根因明确后再决定是否重放</td></tr> <tr><td>混合干扰 A/B</td><td class="pending">未执行</td><td>待 Prefill 根因明确后再决定是否重放</td></tr>
<tr><td>阶段耗时</td><td class="pass">约 65 分钟</td><td>14:36:26 启动15:41:52 完成进程与容器清理</td></tr> <tr><td>阶段耗时</td><td class="pass">约 65 分钟</td><td>14:36:26 启动15:41:52 完成进程与容器清理</td></tr>
<tr><td>是否进入下一阶段</td><td class="pass"></td><td>用户决定立即进入 Phase 2 硬件指标归因</td></tr> <tr><td>是否进入下一阶段</td><td class="pending"></td><td>Phase 2 暂停;先证明双 Rail NET/IB 并重跑 Phase 1</td></tr>
</tbody> </tbody>
</table> </table>

View File

@ -135,7 +135,7 @@
<div class="meta"> <div class="meta">
<span>节点174.1.51.5 + 174.1.51.7</span> <span>节点174.1.51.5 + 174.1.51.7</span>
<span>拓扑SGLang TP16 / EP2</span> <span>拓扑SGLang TP16 / EP2</span>
<span>更新2026-07-30 16:35 CST</span> <span>更新2026-07-30 17:54 CST</span>
</div> </div>
</div> </div>
</header> </header>
@ -144,53 +144,55 @@
<a class="back" href="./推理优化计划.html">返回推理优化主计划</a> <a class="back" href="./推理优化计划.html">返回推理优化主计划</a>
<p class="status"> <p class="status">
<strong>当前状态:旧脚本口径审计完成Phase 2 代码尚未开始。</strong> <strong>当前状态:网络前置条件未满足Phase 2 暂停,代码尚未开始。</strong>
本页从第一行 Phase 2 代码开始同步维护。每次代码改动、静态验证、真机运行和 本页从第一行 Phase 2 代码开始同步维护。每次代码改动、静态验证、真机运行和
结果判断都会在对应小节留下文件路径、命令和证据,不在阶段结束后凭记忆补写。 结果判断都会在对应小节留下文件路径、命令和证据,不在阶段结束后凭记忆补写。
</p> </p>
<h2>1. 为什么立即进入 Phase 2</h2> <h2>1. 为什么现在不能直接进入 Phase 2</h2>
<p> <p>
Phase 1 在没有 Profiler、没有 Prefix Cache 命中的条件下得到以下结果: Phase 1 在没有 Profiler、没有 Prefix Cache 命中的条件下得到以下结果,
但这些数值后来确认受错误 Socket 网络路径污染:
</p> </p>
<table> <table>
<thead> <thead>
<tr><th>ISL / OSL / C</th><th>输入 TPS</th><th>TTFT</th><th>结果</th></tr> <tr><th>ISL / OSL / C</th><th>输入 TPS</th><th>TTFT</th><th>结果</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr><td>1K / 1 / 1</td><td>64.44 tok/s</td><td>15.88 s</td><td>完成</td></tr> <tr><td>1K / 1 / 1</td><td>64.44 tok/s</td><td>15.88 s</td><td>错误网络证据,不作为基线</td></tr>
<tr><td>32K / 1 / 1</td><td>64.96 tok/s</td><td>504.44 s</td><td>完成</td></tr> <tr><td>32K / 1 / 1</td><td>64.96 tok/s</td><td>504.44 s</td><td>错误网络证据,不作为基线</td></tr>
<tr><td>128K / 1 / 1</td><td>65.20 tok/s</td><td>2010.38 s</td><td>完成</td></tr> <tr><td>128K / 1 / 1</td><td>65.20 tok/s</td><td>2010.38 s</td><td>错误网络证据,不作为基线</td></tr>
</tbody> </tbody>
</table> </table>
<p> <p>
三个长度的输入吞吐几乎相同TTFT 近似按 token 数线性增加。 quick-map 容器没有 <code>/dev/infiniband</code>NCCL 回退
这已经不是“继续扩充 Shape”能回答的问题。Phase 2 要回答: <code>NET/Socket</code>;脚本又误选低速非计算网。原网络配置冷请求控制组中,
<strong>稳定的约 65 token/s 到底受 GPU 计算、显存、CPU 调度还是双机通信中的哪一项限制。</strong> 1K/32K TTFT 分别只有 1.458s/38.062s,比 quick-map 快约 10.9×/13.25×。
因此约 65 token/s 不是待 profile 的模型现象,而是已定位的部署配置错误。
</p> </p>
<h3>1.1 Phase 2 前置审计</h3> <h3>1.1 Phase 2 前置审计</h3>
<p> <p>
旧脚本较短的 TTFT 已确认不是同口径反例。旧脚本固定执行 16 条同 Prompt 旧脚本较短的 TTFT 包含两个因素。其一,旧脚本固定执行 16 条同 Prompt
Warm-up从不清 Prefix Cache并按固定 Seed 递增长度17:40 的失败 Run Warm-up从不清 Prefix Cache并按固定 Seed 递增长度17:40 的失败 Run
还在 18:01 正式 Run 前预热了同一批 1K 请求。全字段比较显示新旧 还在 18:01 正式 Run 前预热了同一批 1K 请求。其二,新旧模型参数虽相同,
<code>server_info</code> 的关键运行参数相同 NCCL Socket 接口不同,而二者容器都没有形成真实 RDMA 数据面
</p> </p>
<p> <p>
同一服务上的最小复现得到:两次独立清 Cache 的 1K→1 TTFT 分别为 网络控制组明确打印 <code>NET/IB : No device found</code>
16.04 秒和 15.90 秒;把 OSL 改为 128 后是 15.79 秒;按旧命令语义重新执行 <code>Using network Socket</code>。Phase 1 入口现已加入 RDMA 设备透传与
仍为 14.60 秒,而不是旧产物的 0.455 秒。因此 Phase 2 将继续 profile <code>NET/IB</code> fail-closed 校验但真机验证尚未运行。Phase 2 只有在
<strong>清 Prefix Cache 后的完整冷 Prefill</strong>。Warm Prefix/Prefix Cache 双 Rail RDMA 得到运行时证据并重跑 Phase 1 后才会开始。Warm Prefix/Prefix Cache
收益另立 A/B不与本阶段混算。 收益仍另立 A/B不与冷 Prefill 混算。
</p> </p>
<h2>2. 本阶段的边界</h2> <h2>2. 本阶段的边界</h2>
<ul> <ul>
<li>只测试 SGLang不测试 vLLM。</li> <li>只测试 SGLang不测试 vLLM。</li>
<li>保留 Phase 1 的模型、镜像、TP16、EP2、显存比例和 NCCL 参数</li> <li>保留模型、镜像、TP16、EP2 和显存比例NCCL 参数必须使用修正并验证后的版本</li>
<li>不启用 Nsight Systems、PyTorch Profiler、NCCL DEBUG 或投机解码。</li> <li>不启用 Nsight Systems、PyTorch Profiler、NCCL DEBUG 或投机解码。</li>
<li>不调参,不尝试优化;先获得足以区分瓶颈类别的硬件证据。</li> <li>不调参,不尝试优化;先获得足以区分瓶颈类别的硬件证据。</li>
<li>第一轮只重放 <code>32K → 1, C=1</code>,与 Phase 1 结果直接对齐。</li> <li>第一轮只重放 <code>32K → 1, C=1</code>但必须修正后的 Phase 1 结果对齐。</li>
<li>采集器从请求开始前启动,到请求结束后停止,不能中途补采后声称完整。</li> <li>采集器从请求开始前启动,到请求结束后停止,不能中途补采后声称完整。</li>
</ul> </ul>
@ -311,13 +313,15 @@ dsv4pro_pro6000d_2node_sglang_prefill_hardware_attribution/</code></pre>
<tbody> <tbody>
<tr><td>2026-07-30 15:46 CST</td><td>创建 Phase 2 设计与档案</td><td>代码尚未开始,等待按本页设计实现</td></tr> <tr><td>2026-07-30 15:46 CST</td><td>创建 Phase 2 设计与档案</td><td>代码尚未开始,等待按本页设计实现</td></tr>
<tr><td>2026-07-30 16:35 CST</td><td>完成旧脚本与 quick-map 同口径审计</td><td>排除服务参数、OSL=1 和一次性 JIT确认旧产物被 Warm-up、跨 Case 与前一轮 Prefix Cache 污染</td></tr> <tr><td>2026-07-30 16:35 CST</td><td>完成旧脚本与 quick-map 同口径审计</td><td>排除服务参数、OSL=1 和一次性 JIT确认旧产物被 Warm-up、跨 Case 与前一轮 Prefix Cache 污染</td></tr>
<tr><td>2026-07-30 17:30 CST</td><td>完成原网络配置冷请求控制组</td><td>确认 quick-map 误入低速非计算网 SocketPhase 2 暂停,先修正并重跑 Phase 1</td></tr>
<tr><td>2026-07-30 17:54 CST</td><td>完成 Phase 1 RDMA fail-closed 代码与 dry-run</td><td>只允许 eth0/eth3 与 mlx5_0/mlx5_3等待真机 NET/IB 证据</td></tr>
</tbody> </tbody>
</table> </table>
<h2>10. 真机结果</h2> <h2>10. 真机结果</h2>
<p class="pending"> <p class="pending">
尚未运行。代码实现、静态验证和 Dry-run 完成后,将先向用户说明具体代码改动, 尚未运行,也不应立即运行。先获得修正网络后的 Phase 1 冷 32K 基线;
再启动真机诊断 Phase 2 代码实现、静态验证和 Dry-run 完成后,再向用户说明具体改动并等待阶段门
</p> </p>
<p><a class="back" href="./phase1_dsv4pro_pro6000d_2node_sglang_quick_map.html">返回 Phase 1 实施记录</a></p> <p><a class="back" href="./phase1_dsv4pro_pro6000d_2node_sglang_quick_map.html">返回 Phase 1 实施记录</a></p>

View File

@ -0,0 +1,104 @@
# DeepSeek-V4-Pro TP16 网络路径审计
- 时间2026-07-30
- 节点:`174.1.51.5 + 174.1.51.7`
- 模型DeepSeek-V4-Pro
- 引擎SGLang nightly
- 拓扑TP16 / EP2 / 2 nodes
## 结论
Phase 1 quick-map 的约 65 input token/s 不是可直接归因给模型、Kernel 或 GPU
的性能基线。它同时受两个独立因素影响:
1. quick-map 容器没有 RDMA 设备NCCL 回退 `NET/Socket`
2. quick-map 的 `NCCL_SOCKET_IFNAME` 误选低速非计算网Socket 数据没有进入
部署规定的 `eth0/eth3` 计算网。
`NCCL_CROSS_NIC=1` 不是本次 10 倍以上退化的原因。该参数只有在 NCCL
真正使用多个 RDMA HCA 时才影响 ring/tree 的 NIC 选择;本次实际后端为 Socket。
## 设备关系
部署时只使用两条节点间计算网:
| 物理端口 | Linux netdev/IP 入口 | RDMA Verbs/HCA 入口 | 状态 |
|---|---|---|---|
| 400G Rail 1 | `eth0` | `mlx5_0` | Up |
| 400G Rail 2 | `eth3` | `mlx5_3` | Up |
`eth0``mlx5_0` 不是同一个软件设备。它们是同一条 400G 物理 Ethernet
端口的两种入口:前者服务 IP/TCP Socket后者服务 RoCE/RDMA Verbs。
## 运行时证据
宿主机存在:
```text
/dev/infiniband/rdma_cm
/dev/infiniband/uverbs0
/dev/infiniband/uverbs3
```
按原脚本启动的容器内:
```text
ls: cannot access '/dev/infiniband': No such file or directory
```
NCCL INFO
```text
NCCL_SOCKET_IFNAME set by environment to eth0
Bootstrap: Using eth0:10.101.0.11
NET/IB : No device found.
Failed to initialize NET plugin IB
NET/Socket : Using [0]eth0:10.101.0.11
Using network Socket
```
## 冷缓存对照
共同条件:
- 同一模型、镜像、TP16/EP2 和 serving 参数。
- `random` 数据集,`seed=42`
- `warmup_requests=0`
- 测量前 `--flush-cache`
- `num_prompts=1``max_concurrency=1`
| Shape | 原脚本网络:`eth0` Socket | quick-map 错误网络 | 退化 |
|---|---:|---:|---:|
| 1K -> 1 | TTFT 1.458s693.1 input tok/s | TTFT 15.88-16.04s;约 64 tok/s | 约 10.9x |
| 32K -> 1 | TTFT 38.062s860.5 input tok/s | TTFT 504.44s64.96 tok/s | 约 13.25x |
## 旧矩阵为什么还能更快
旧矩阵结果还有 Prefix Cache 污染:
- 每个 case 固定 16 条相同首个 prompt 的 warm-up。
- benchmark 默认 `seed=42`,每次使用相同 ShareGPT shuffle 顺序。
- ISL、OSL 和并发升序运行。
- 从不传 `--flush-cache`
- 17:40 的失败 Run 已执行过首个 1K case18:01 正式 Run 复用同一服务。
所以旧 1K 的 0.455s、32K 的 32.03s 和 128K 的 130.44s 不是完整冷
Prefill不能与任一冷缓存结果直接比较。
## 修复顺序
1. 当前 Socket baseline 默认改为 `NCCL_SOCKET_IFNAME=eth0`
2. 重跑 Phase 1 的冷 1K/32K/128K 代表点。
3. 单独给容器透传 `rdma_cm``uverbs0``uverbs3`
4. 用 `NCCL_DEBUG=INFO` 确认出现 `NET/IB`,不能只看环境变量。
5. 真正启用双 Rail RDMA 后,再比较 `NCCL_CROSS_NIC=0/1/2`
6. 以修正后的端到端结果决定是否进入 Phase 2 硬件归因。
## 产物
- `head_server.log`
- `worker_server.log`
- `oldscript_network_cold_1k_o1.log`
- `oldscript_network_cold_1k_o1.jsonl`
- `oldscript_network_cold_32k_o1.log`
- `oldscript_network_cold_32k_o1.jsonl`

View File

@ -400,7 +400,7 @@
<div class="document-header__meta"> <div class="document-header__meta">
<span>节点174.1.51.5 + 174.1.51.7</span> <span>节点174.1.51.5 + 174.1.51.7</span>
<span>资源16 × RTX PRO 6000 Blackwell</span> <span>资源16 × RTX PRO 6000 Blackwell</span>
<span>版本2026-07-30 15:52 CST</span> <span>版本2026-07-30 17:54 CST</span>
</div> </div>
</div> </div>
</header> </header>
@ -415,7 +415,7 @@
<article id="document-content"> <article id="document-content">
<h1>6000D 双机 DeepSeek-V4-Pro 推理优化计划</h1> <h1>6000D 双机 DeepSeek-V4-Pro 推理优化计划</h1>
<blockquote> <blockquote>
<p>适用环境:<code>174.1.51.5 + 174.1.51.7</code>,每台 8 张 RTX PRO 6000 Blackwell Server Edition<br>当前部署DeepSeek-V4-Pro16 张 GPU 组成一个完整实例<br>当前约束:模型暂时只能使用全部 16 张 GPU无法额外复制一套模型进行 PD 分离<br>计划版本2026-07-30 15:52 CST</p> <p>适用环境:<code>174.1.51.5 + 174.1.51.7</code>,每台 8 张 RTX PRO 6000 Blackwell Server Edition<br>当前部署DeepSeek-V4-Pro16 张 GPU 组成一个完整实例<br>当前约束:模型暂时只能使用全部 16 张 GPU无法额外复制一套模型进行 PD 分离<br>计划版本2026-07-30 17:54 CST</p>
</blockquote> </blockquote>
<h2>当前执行状态与阶段档案</h2> <h2>当前执行状态与阶段档案</h2>
<table> <table>
@ -428,16 +428,57 @@
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td>6000D 双机通信与 NCCL 基础</td>
<td>已建立覆盖计算网、RDMA、Bootstrap、NCCL 参数和日志判读</td>
<td><a href="./6000D双机通信与NCCL术语入门.html">打开通信术语入门</a></td>
</tr>
<tr>
<td>DeepSeek-V4-Pro / 双机 Pro6000D / SGLang TP16 快速性能地图</td> <td>DeepSeek-V4-Pro / 双机 Pro6000D / SGLang TP16 快速性能地图</td>
<td>提前结束3 个冷 Prefill 点完成,输入吞吐稳定约 65 token/s旧脚本缓存口径审计已完成</td> <td>网络错误已定位RDMA fail-closed 代码与 dry-run 已完成,待真机 NET/IB 验证后重跑</td>
<td><a href="./phase1_dsv4pro_pro6000d_2node_sglang_quick_map.html">打开实施记录</a></td> <td><a href="./phase1_dsv4pro_pro6000d_2node_sglang_quick_map.html">打开实施记录</a></td>
</tr> </tr>
<tr> <tr>
<td>DeepSeek-V4-Pro / 双机 Pro6000D / SGLang Prefill 硬件指标归因</td> <td>DeepSeek-V4-Pro / 双机 Pro6000D / SGLang Prefill 硬件指标归因</td>
<td>设计已固化;代码尚未开始</td> <td>设计已固化、代码尚未开始;等待修正后的 Phase 1 基线</td>
<td><a href="./phase2_dsv4pro_pro6000d_2node_sglang_prefill_hardware_attribution.html">打开 Phase 2 档案</a></td> <td><a href="./phase2_dsv4pro_pro6000d_2node_sglang_prefill_hardware_attribution.html">打开 Phase 2 档案</a></td>
</tr> </tr>
</tbody></table> </tbody></table>
<h2>0. 先看懂双机通信</h2>
<p>
在分析 TP16 性能前,先区分设备、传输后端与 NCCL 参数。
完整解释和本次网络误配置复盘见
<a href="./6000D双机通信与NCCL术语入门.html">《6000D 双机通信与 NCCL 术语入门》</a>
</p>
<table>
<thead>
<tr><th>术语</th><th>一句话解释</th><th>本项目实例</th></tr>
</thead>
<tbody>
<tr><td>NCCL</td><td>NVIDIA 多 GPU 通信库,执行 collective 和点对点通信</td><td>SGLang TP16 的跨 GPU 通信层</td></tr>
<tr><td>RDMA</td><td>网卡绕过常规 TCP/内核拷贝直接访问远端内存</td><td>双机高速数据面的目标路径</td></tr>
<tr><td>IB / RoCE</td><td>IB 是高速网络/Verbs 体系RoCE 在以太网上承载 RDMA</td><td>NCCL 日志统一显示为 <code>NET/IB</code></td></tr>
<tr><td><code>eth0/eth3</code></td><td>400G 物理端口的 Linux netdev/IP 入口</td><td>仅这两个接口用于节点间部署通信</td></tr>
<tr><td><code>mlx5_0/mlx5_3</code></td><td>映射到上述物理端口的 RDMA Verbs/HCA 入口</td><td><code>eth0/eth3</code> 有映射关系,但不是同一个软件设备</td></tr>
<tr><td>NCCL bootstrap</td><td>rank 启动时交换身份、地址、拓扑和连接信息的阶段</td><td>先建连,再选择真正的数据后端</td></tr>
<tr><td><code>NCCL_SOCKET_IFNAME</code></td><td>选择 NCCL 可用的 IP 接口</td><td>RDMA 失败时也决定 Socket 数据走哪张网卡</td></tr>
<tr><td><code>NCCL_IB_HCA</code></td><td>选择 NCCL 可用的 RDMA HCA</td><td><code>mlx5_0,mlx5_3</code></td></tr>
<tr><td><code>NCCL_CROSS_NIC</code></td><td>控制同一 ring/tree 能否在节点间跨不同 HCA</td><td>只有真正使用多 HCA <code>NET/IB</code> 时才有意义</td></tr>
</tbody>
</table>
<p>
<strong>400G 是每条物理 Ethernet 链路的标称线速不是“TCP 速度”或“RDMA
速度”。</strong>同一条链路可以承载 TCP也可以承载 RoCE/RDMA
<code>400 Gbit/s ≈ 50 GB/s</code> 只是单向理论上限NCCL 的
<code>algbw/busbw</code> 与端到端模型吞吐都不能直接等同于该数字。
</p>
<blockquote>
<p>
2026-07-30 已确认:当时容器内没有 <code>/dev/infiniband</code>NCCL 日志显示
<code>NET/IB : No device found</code> 并回退 <code>NET/Socket</code>。quick-map 又误选
低速非计算网,因此 1K/32K 冷 Prefill 比原脚本的 <code>eth0</code> Socket 路径慢约
10.9×/13.25×。这不是 <code>NCCL_CROSS_NIC=1</code> 导致的。
</p>
</blockquote>
<h2>1. 目标与原则</h2> <h2>1. 目标与原则</h2>
<h3>1.1 最终目标</h3> <h3>1.1 最终目标</h3>
<p>在不做 PD 分离的前提下,定位 DeepSeek-V4-Pro 在双机 6000D 上的端到端瓶颈,并提高:</p> <p>在不做 PD 分离的前提下,定位 DeepSeek-V4-Pro 在双机 6000D 上的端到端瓶颈,并提高:</p>
@ -469,7 +510,7 @@
<tbody><tr> <tbody><tr>
<td>H1</td> <td>H1</td>
<td>TP16 每层跨机通信暴露过多</td> <td>TP16 每层跨机通信暴露过多</td>
<td>两台机器没有跨机 NVLinkTP Collective 需要经过 RoCE</td> <td>两台机器没有跨机 NVLink;应先确保 Collective 真正经过计算网/RDMA而不是 Socket 回退</td>
</tr> </tr>
<tr> <tr>
<td>H2</td> <td>H2</td>
@ -725,6 +766,20 @@ numastat -p &lt;PID&gt;
<li>CPU 空洞是否对应 GPU 空洞。</li> <li>CPU 空洞是否对应 GPU 空洞。</li>
</ul> </ul>
<h3>6.3 网络</h3> <h3>6.3 网络</h3>
<blockquote>
<p>
2026-07-30 控制组已确认:宿主机具备 <code>mlx5_0/eth0</code>
<code>mlx5_3/eth3</code> 两条 400G Rail但原服务容器没有
<code>/dev/infiniband</code>NCCL 实际使用 <code>NET/Socket</code>
当前第一优先级是校正容器数据面并重做 Phase 1不再把约 65 token/s 当作模型或算子瓶颈。
</p>
</blockquote>
<p>
唯一启动入口现已在两端预检并透传
<code>rdma_cm/uverbs0/uverbs3</code>,且服务健康后强制从两端 NCCL INFO
日志确认 <code>NET/IB</code> 同时识别 <code>mlx5_0/mlx5_3</code>。代码、
单测与 dry-run 已通过;真机服务尚未启动,因此这里仍不宣称 RDMA 已验证成功。
</p>
<p>当前拓扑中需要分别观察两条 Compute Rail确认</p> <p>当前拓扑中需要分别观察两条 Compute Rail确认</p>
<ul> <ul>
<li>两条 Rail 是否同时有流量。</li> <li>两条 Rail 是否同时有流量。</li>
@ -744,8 +799,10 @@ NCCL_DEBUG_SUBSYS=INIT,NET,GRAPH,TUNING
<p>该日志开销较高,不应在正式性能结果中长期启用。</p> <p>该日志开销较高,不应在正式性能结果中长期启用。</p>
<h3>6.4 NCCL_CROSS_NIC 快速 A/B</h3> <h3>6.4 NCCL_CROSS_NIC 快速 A/B</h3>
<p> <p>
Phase 1 固定使用 <code>NCCL_CROSS_NIC=1</code>。完成首轮 Prefill 硬件归因后, 旧 Phase 1 虽然设置了 <code>NCCL_CROSS_NIC=1</code>,但由于 NCCL 回退
固定其余环境,快速比较 <code>0/1/2</code>,不能仅凭双 Rail 拓扑判断最优值。 <code>NET/Socket</code>,该参数没有参与实际路径选择。先让容器真正使用
<code>mlx5_0/mlx5_3</code><code>NET/IB</code>,再固定其余环境比较
<code>0/1/2</code>,不能仅凭双 Rail 拓扑判断最优值。
</p> </p>
<ol> <ol>
<li>分别执行相同消息范围的 <code>all_reduce_perf</code>,每个值至少重复 3 次检查错误、algbw 和 busbw。</li> <li>分别执行相同消息范围的 <code>all_reduce_perf</code>,每个值至少重复 3 次检查错误、algbw 和 busbw。</li>

View File

@ -4,6 +4,20 @@ This directory contains the short, repeatable performance-map suite for
DeepSeek-V4-Pro on two RTX PRO 6000 Blackwell nodes. It does not modify or call the existing DeepSeek-V4-Pro on two RTX PRO 6000 Blackwell nodes. It does not modify or call the existing
`dsv4_pro6000_sglang_tp16/run_batch.sh`. `dsv4_pro6000_sglang_tp16/run_batch.sh`.
## Network precondition
The 2026-07-30 audit found that the current service container does not expose
`/dev/infiniband`, so NCCL falls back to `NET/Socket`. The quick-map previously
selected a low-speed non-compute interface and produced invalid 1K/32K cold
Prefill baselines. The Socket default is now `eth0`, one of the two deployment
compute interfaces (`eth0/eth3`).
Declaring `NCCL_IB_HCA=mlx5_0,mlx5_3` does not by itself enable RDMA. The
launcher now exposes only the three device nodes needed by the two deployment
rails (`rdma_cm`, `uverbs0`, `uverbs3`) and fails before benchmarking unless
both nodes' NCCL INFO logs prove `NET/IB` is using `mlx5_0` and `mlx5_3`.
`NCCL_CROSS_NIC` has no effect while the active transport is `NET/Socket`.
## Scope ## Scope
The fixed suite covers nine points: The fixed suite covers nine points:

View File

@ -21,11 +21,18 @@ DOCKER_IMAGE="${DOCKER_IMAGE:-lmsysorg/sglang:nightly-dev-cu13-20260720-b3570a45
DOCKER_CLIENT_IMAGE="${DOCKER_CLIENT_IMAGE:-$DOCKER_IMAGE}" DOCKER_CLIENT_IMAGE="${DOCKER_CLIENT_IMAGE:-$DOCKER_IMAGE}"
SGLANG_CACHE_DIR="${SGLANG_CACHE_DIR:-/data/hzy/sglang_cache/dsv4_pro_tp16}" SGLANG_CACHE_DIR="${SGLANG_CACHE_DIR:-/data/hzy/sglang_cache/dsv4_pro_tp16}"
# eth1 is the TCP bootstrap interface. mlx5_0/mlx5_3 are the two RoCE rails. # Only eth0/eth3 are deployment compute interfaces. eth0 is the bootstrap and
NCCL_SOCKET_IFNAME="${NCCL_SOCKET_IFNAME:-eth1}" # Socket-fallback interface; mlx5_0/mlx5_3 are the two RoCE HCA rails.
NCCL_IB_HCA="${NCCL_IB_HCA:-mlx5_0,mlx5_3}" NCCL_SOCKET_IFNAME="${NCCL_SOCKET_IFNAME:-eth0}"
NCCL_IB_HCA="${NCCL_IB_HCA:-=mlx5_0:1,mlx5_3:1}"
NCCL_CROSS_NIC="${NCCL_CROSS_NIC:-1}" NCCL_CROSS_NIC="${NCCL_CROSS_NIC:-1}"
NCCL_DEBUG="${NCCL_DEBUG:-WARN}" NCCL_DEBUG="${NCCL_DEBUG:-INFO}"
# Fail closed: a run labeled as RDMA must expose the exact HCA device nodes on
# both nodes and show NET/IB in NCCL startup logs.
ENABLE_RDMA="${ENABLE_RDMA:-1}"
REQUIRE_NCCL_IB="${REQUIRE_NCCL_IB:-1}"
RDMA_DEVICE_PATHS="${RDMA_DEVICE_PATHS:-/dev/infiniband/rdma_cm,/dev/infiniband/uverbs0,/dev/infiniband/uverbs3}"
# Keep the known working TP16 baseline. The quick map changes workload, not serving knobs. # Keep the known working TP16 baseline. The quick map changes workload, not serving knobs.
MEM_FRACTION_STATIC="${MEM_FRACTION_STATIC:-0.9}" MEM_FRACTION_STATIC="${MEM_FRACTION_STATIC:-0.9}"

View File

@ -363,6 +363,9 @@ def write_manifest(args: argparse.Namespace) -> None:
"nccl_socket_ifname": args.nccl_socket_ifname, "nccl_socket_ifname": args.nccl_socket_ifname,
"nccl_ib_hca": args.nccl_ib_hca, "nccl_ib_hca": args.nccl_ib_hca,
"nccl_cross_nic": args.nccl_cross_nic, "nccl_cross_nic": args.nccl_cross_nic,
"enable_rdma": bool(args.enable_rdma),
"require_nccl_ib": bool(args.require_nccl_ib),
"rdma_device_paths": args.rdma_device_paths,
"git_commit": args.git_commit, "git_commit": args.git_commit,
"git_dirty": bool(args.git_dirty), "git_dirty": bool(args.git_dirty),
"scenario_file": args.scenario_file, "scenario_file": args.scenario_file,
@ -640,6 +643,9 @@ def add_manifest_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument("--nccl-socket-ifname", required=True) parser.add_argument("--nccl-socket-ifname", required=True)
parser.add_argument("--nccl-ib-hca", required=True) parser.add_argument("--nccl-ib-hca", required=True)
parser.add_argument("--nccl-cross-nic", required=True) parser.add_argument("--nccl-cross-nic", required=True)
parser.add_argument("--enable-rdma", type=int, required=True)
parser.add_argument("--require-nccl-ib", type=int, required=True)
parser.add_argument("--rdma-device-paths", required=True)
parser.add_argument("--git-commit", required=True) parser.add_argument("--git-commit", required=True)
parser.add_argument("--git-dirty", type=int, required=True) parser.add_argument("--git-dirty", type=int, required=True)
parser.add_argument("--scenario-file", required=True) parser.add_argument("--scenario-file", required=True)

View File

@ -55,6 +55,78 @@ run_on_node() {
fi fi
} }
validate_network_config() {
case "${ENABLE_RDMA}" in
0|1) ;;
*)
log "ERROR: ENABLE_RDMA must be 0 or 1, got: ${ENABLE_RDMA}"
return 1
;;
esac
case "${REQUIRE_NCCL_IB}" in
0|1) ;;
*)
log "ERROR: REQUIRE_NCCL_IB must be 0 or 1, got: ${REQUIRE_NCCL_IB}"
return 1
;;
esac
if [[ "${REQUIRE_NCCL_IB}" == "1" && "${ENABLE_RDMA}" != "1" ]]; then
log "ERROR: REQUIRE_NCCL_IB=1 requires ENABLE_RDMA=1"
return 1
fi
local interface_spec="${NCCL_SOCKET_IFNAME#=}"
local -a interfaces=()
local interface
IFS=',' read -r -a interfaces <<< "${interface_spec}"
for interface in "${interfaces[@]}"; do
case "${interface#=}" in
eth0|eth3) ;;
*)
log "ERROR: only deployment compute interfaces eth0/eth3 are allowed; got: ${interface}"
return 1
;;
esac
done
local hca_spec="${NCCL_IB_HCA#=}"
local -a hca_entries=()
local hca_entry hca saw_mlx5_0=0 saw_mlx5_3=0
IFS=',' read -r -a hca_entries <<< "${hca_spec}"
for hca_entry in "${hca_entries[@]}"; do
hca="${hca_entry%%:*}"
case "${hca}" in
mlx5_0) saw_mlx5_0=1 ;;
mlx5_3) saw_mlx5_3=1 ;;
*)
log "ERROR: only deployment RDMA HCAs mlx5_0/mlx5_3 are allowed; got: ${hca}"
return 1
;;
esac
done
if [[ "${ENABLE_RDMA}" == "1" ]] \
&& (( saw_mlx5_0 == 0 || saw_mlx5_3 == 0 )); then
log "ERROR: RDMA mode requires both mlx5_0 and mlx5_3"
return 1
fi
}
preflight_rdma_devices_on_node() {
local node="$1"
[[ "${ENABLE_RDMA}" == "1" ]] || return 0
local -a device_paths=()
local device_path quoted_path
IFS=',' read -r -a device_paths <<< "${RDMA_DEVICE_PATHS}"
for device_path in "${device_paths[@]}"; do
printf -v quoted_path '%q' "${device_path}"
if ! run_on_node "${node}" "test -c ${quoted_path}"; then
log "ERROR: ${node} is missing RDMA character device: ${device_path}"
return 1
fi
done
}
service_is_healthy() { service_is_healthy() {
curl --fail --silent --show-error --max-time 5 \ curl --fail --silent --show-error --max-time 5 \
"http://${HEAD_IP}:${SGLANG_PORT}/health" >/dev/null 2>&1 "http://${HEAD_IP}:${SGLANG_PORT}/health" >/dev/null 2>&1
@ -78,8 +150,12 @@ remote_has_gpu_processes() {
preflight_service_node() { preflight_service_node() {
local node="$1" local node="$1"
run_on_node "${node}" \ if ! run_on_node "${node}" \
"test -d '${MODEL_PATH}' && command -v docker >/dev/null && command -v nvidia-smi >/dev/null && docker image inspect '${DOCKER_IMAGE}' >/dev/null" "test -d '${MODEL_PATH}' && command -v docker >/dev/null && command -v nvidia-smi >/dev/null && docker image inspect '${DOCKER_IMAGE}' >/dev/null"; then
log "ERROR: ${node} failed model, Docker, GPU, or image preflight"
return 1
fi
preflight_rdma_devices_on_node "${node}" || return 1
if [[ "${ALLOW_BUSY_GPU}" != "1" ]] && remote_has_gpu_processes "${node}"; then if [[ "${ALLOW_BUSY_GPU}" != "1" ]] && remote_has_gpu_processes "${node}"; then
log "ERROR: ${node} has active GPU compute processes" log "ERROR: ${node} has active GPU compute processes"
log "This experiment will not evict another workload." log "This experiment will not evict another workload."
@ -130,6 +206,16 @@ build_server_command() {
-e "NCCL_CROSS_NIC=${NCCL_CROSS_NIC}" -e "NCCL_CROSS_NIC=${NCCL_CROSS_NIC}"
-e "NCCL_DEBUG=${NCCL_DEBUG}" -e "NCCL_DEBUG=${NCCL_DEBUG}"
-e SGLANG_SHARED_EXPERT_TP1=1 -e SGLANG_SHARED_EXPERT_TP1=1
)
if [[ "${ENABLE_RDMA}" == "1" ]]; then
local -a device_paths=()
local device_path
IFS=',' read -r -a device_paths <<< "${RDMA_DEVICE_PATHS}"
for device_path in "${device_paths[@]}"; do
DOCKER_CMD+=(--device "${device_path}")
done
fi
DOCKER_CMD+=(
--entrypoint python3 --entrypoint python3
"${DOCKER_IMAGE}" "${DOCKER_IMAGE}"
-m sglang.launch_server -m sglang.launch_server
@ -162,16 +248,64 @@ start_service_node() {
build_server_command "${node_rank}" "${container_name}" build_server_command "${node_rank}" "${container_name}"
print_command "${DOCKER_CMD[@]}" > "${SERVER_ARTIFACT_DIR}/${role}_server_cmd.txt" print_command "${DOCKER_CMD[@]}" > "${SERVER_ARTIFACT_DIR}/${role}_server_cmd.txt"
log "Starting ${role} node=${node} rank=${node_rank} container=${container_name}" log "Starting ${role} node=${node} rank=${node_rank} container=${container_name}"
run_on_node "${node}" "mkdir -p '${SGLANG_CACHE_DIR}'" if ! run_on_node "${node}" "mkdir -p '${SGLANG_CACHE_DIR}'"; then
log "ERROR: failed to create SGLang cache directory on ${node}"
return 1
fi
run_on_node "${node}" "docker rm -f '${container_name}' >/dev/null 2>&1 || true" run_on_node "${node}" "docker rm -f '${container_name}' >/dev/null 2>&1 || true"
local command local command
command="$(print_command "${DOCKER_CMD[@]}")" command="$(print_command "${DOCKER_CMD[@]}")"
run_on_node "${node}" "${command}" \ if ! run_on_node "${node}" "${command}" \
> "${SERVER_ARTIFACT_DIR}/${role}_container_id.txt" > "${SERVER_ARTIFACT_DIR}/${role}_container_id.txt"; then
log "ERROR: failed to launch ${role} container on ${node}"
return 1
fi
}
verify_nccl_transport_node() {
local node="$1"
local container_name="$2"
local role="$3"
local transport_log="${SERVER_ARTIFACT_DIR}/${role}_nccl_transport.log"
run_on_node "${node}" "docker logs '${container_name}' 2>&1" \
> "${transport_log}" 2>&1
if grep -Fq "NET/IB : No device found" "${transport_log}"; then
log "ERROR: ${role} NCCL could not find an RDMA device"
return 1
fi
if ! grep -Eq 'NET/IB.*Using|Using network IB|via NET/IB' "${transport_log}"; then
log "ERROR: ${role} NCCL log does not prove NET/IB is active"
return 1
fi
local hca_spec="${NCCL_IB_HCA#=}"
local -a hca_entries=()
local hca_entry hca
IFS=',' read -r -a hca_entries <<< "${hca_spec}"
for hca_entry in "${hca_entries[@]}"; do
hca="${hca_entry%%:*}"
if ! grep -E "NET/IB.*${hca}|${hca}.*NET/IB" "${transport_log}" >/dev/null; then
log "ERROR: ${role} NCCL log does not show configured HCA ${hca}"
return 1
fi
done
log "Verified ${role} NCCL transport: NET/IB with mlx5_0 and mlx5_3"
}
verify_nccl_transport() {
[[ "${REQUIRE_NCCL_IB}" == "1" ]] || return 0
verify_nccl_transport_node \
"${WORKER_NODE}" "${WORKER_CONTAINER}" "worker" || return 1
verify_nccl_transport_node \
"${HEAD_NODE}" "${HEAD_CONTAINER}" "head" || return 1
} }
start_service() { start_service() {
validate_network_config || return 1
build_server_command 1 "${WORKER_CONTAINER}" build_server_command 1 "${WORKER_CONTAINER}"
if [[ "${DRY_RUN}" == "1" ]]; then if [[ "${DRY_RUN}" == "1" ]]; then
printf '[DRY] worker (%s): ' "${WORKER_NODE}" printf '[DRY] worker (%s): ' "${WORKER_NODE}"
@ -183,18 +317,21 @@ start_service() {
fi fi
mkdir -p "${SERVER_ARTIFACT_DIR}" mkdir -p "${SERVER_ARTIFACT_DIR}"
preflight_service_node "${HEAD_NODE}" preflight_service_node "${HEAD_NODE}" || return 1
preflight_service_node "${WORKER_NODE}" preflight_service_node "${WORKER_NODE}" || return 1
start_service_node "${WORKER_NODE}" 1 "${WORKER_CONTAINER}" "worker" start_service_node \
"${WORKER_NODE}" 1 "${WORKER_CONTAINER}" "worker" || return 1
sleep 5 sleep 5
start_service_node "${HEAD_NODE}" 0 "${HEAD_CONTAINER}" "head" start_service_node \
"${HEAD_NODE}" 0 "${HEAD_CONTAINER}" "head" || return 1
log "Waiting for SGLang health at ${HEAD_IP}:${SGLANG_PORT}" log "Waiting for SGLang health at ${HEAD_IP}:${SGLANG_PORT}"
local attempt local attempt
for (( attempt=1; attempt<=HEALTH_CHECK_RETRIES; attempt++ )); do for (( attempt=1; attempt<=HEALTH_CHECK_RETRIES; attempt++ )); do
if service_is_healthy; then if service_is_healthy; then
log "SGLang is healthy after ${attempt} checks" log "SGLang is healthy after ${attempt} checks"
verify_nccl_transport || return 1
return 0 return 0
fi fi
if ! run_on_node "${HEAD_NODE}" \ if ! run_on_node "${HEAD_NODE}" \
@ -502,6 +639,9 @@ write_run_manifest() {
--nccl-socket-ifname "${NCCL_SOCKET_IFNAME}" \ --nccl-socket-ifname "${NCCL_SOCKET_IFNAME}" \
--nccl-ib-hca "${NCCL_IB_HCA}" \ --nccl-ib-hca "${NCCL_IB_HCA}" \
--nccl-cross-nic "${NCCL_CROSS_NIC}" \ --nccl-cross-nic "${NCCL_CROSS_NIC}" \
--enable-rdma "${ENABLE_RDMA}" \
--require-nccl-ib "${REQUIRE_NCCL_IB}" \
--rdma-device-paths "${RDMA_DEVICE_PATHS}" \
--git-commit "${git_commit}" \ --git-commit "${git_commit}" \
--git-dirty "${git_dirty}" \ --git-dirty "${git_dirty}" \
--scenario-file "${SCENARIO_FILE}" --scenario-file "${SCENARIO_FILE}"