Compare commits

..

2 Commits

Author SHA1 Message Date
Zhiyi Hong
e9c5f85500 [Docs] document Phase 2 worker staging fix 2026-07-31 16:20:53 +08:00
Zhiyi Hong
39fc2ba565 [BugFix] stage Phase 2 communication tool on workers 2026-07-31 16:19:23 +08:00
5 changed files with 116 additions and 23 deletions

View File

@ -1,5 +1,9 @@
# sskj — 多平台大模型推理性能基准测试项目
> **更新2026-07-31 16:18:35 CST**
>
> 修复 Phase 2 双节点通信基线在 Worker 启动后立即退出的问题。唯一入口仍只在 Head `174.1.51.5` 执行;脚本现在按 `RUN_ID``communication_baseline.py` 自动暂存到 Head/Worker 的 `/tmp`,校验 SHA256 后只读挂载进通信容器并在结果目录保存当次源码与哈希。Worker `174.1.51.7` 不再依赖同路径 Git 工作树,异常退出也会清理暂存文件。新增回归测试后 Phase 2 单元测试为 9/9。
>
> **更新2026-07-31 15:59:49 CST**
>
> 在 DeepSeek-V4-Pro 双机 Pro6000D 推理优化主计划和 Phase 2 实施档案中增加 `phase2_code.html` 的直接入口,便于从阶段状态、实验命令与结果页面跳转到对应代码调用关系和逐行实现说明。

View File

@ -105,7 +105,7 @@
<p class="eyebrow">Standalone Code Walkthrough / Phase 2</p>
<h1>DSV4-Pro 双机 Pro6000D SGLang 硬件竞争归因:代码详解</h1>
<div class="meta">
行号基线:<code>30664faa41f8</code> 
行号基线:<code>39fc2ba565a3</code> 
生成时间2026-07-31 15:25:00 CST 
唯一入口:<code>run_hardware_contention_attribution.sh all</code>
</div>
@ -114,7 +114,7 @@
<main>
<div class="callout">
<strong>文档边界:</strong>本文只解释提交 <code>30664faa41f8</code> 的 Phase 2
<strong>文档边界:</strong>本文只解释提交 <code>39fc2ba565a3</code> 的 Phase 2
代码和文件调用关系。Phase 1 负责模型服务与请求Phase 2 负责通信基线、
两节点监控、精确时间切片和逐指标报告。
</div>
@ -161,10 +161,10 @@
<thead><tr><th>文件</th><th>行数</th><th>职责</th></tr></thead>
<tbody>
<tr><td class="path">config.env</td><td>61</td><td>节点、Case、分层采样周期、通信尺寸、NCCL 选择和 fail-closed 策略。</td></tr>
<tr><td class="path">run_hardware_contention_attribution.sh</td><td>968</td><td>唯一 Shell 编排器预检、通信基线、Phase 1 委托、采集器、Case 和清理。</td></tr>
<tr><td class="path">run_hardware_contention_attribution.sh</td><td>1036</td><td>唯一 Shell 编排器:预检、通信文件分发、通信基线、Phase 1 委托、采集器、Case 和清理。</td></tr>
<tr><td class="path">communication_baseline.py</td><td>227</td><td>CUDA P2P 全矩阵及 PyTorch/NCCL AllReduce 正确性、延迟和带宽测试。</td></tr>
<tr><td class="path">hardware_contention_attribution.py</td><td>1480</td><td>解析所有原始采集器,按 Case 切片,聚合通信并生成 CSV/JSON/report.md。</td></tr>
<tr><td class="path">tests/test_hardware_contention_attribution.py</td><td>308</td><td>8 项纯 Python 单元测试,覆盖精确窗口、解析器、RDMA 单位和通信聚合。</td></tr>
<tr><td class="path">tests/test_hardware_contention_attribution.py</td><td>322</td><td>9 项纯 Python 单元测试,覆盖 worker 无仓库依赖、精确窗口、解析器、RDMA 单位和通信聚合。</td></tr>
</tbody>
</table>
@ -212,19 +212,23 @@
<h2 id="communication">5. 通信微基准</h2>
<h3>5.1 Shell 如何编排</h3>
<p>
<code>run_hardware_contention_attribution.sh:L269-L447</code> 负责 Docker 命令
两节点同步和清理。所有命令先写入 <code>commands/*.txt</code>
<code>run_hardware_contention_attribution.sh:L281-L513</code> 负责源码暂存
Docker 命令、两节点同步和清理。所有命令先写入 <code>commands/*.txt</code>
</p>
<ul>
<li><code>L300-L323</code>Head/Worker 各跑一次 P2P。</li>
<li><code>L324-L353</code>Head/Worker 各跑一次 8-rank AllReduce。</li>
<li><code>L354-L416</code>:每个 CROSS_NIC 值先启动 Worker rank再运行 Head rank。</li>
<li><code>L417-L447</code>:只清理本实验前缀的通信容器。</li>
<li><code>L281-L319</code>:从 Head 将当次通信脚本暂存到两节点并校验 SHA256。</li>
<li><code>L321-L374</code>构造容器命令Head/Worker 各跑一次 P2P。</li>
<li><code>L376-L404</code>Head/Worker 各跑一次 8-rank AllReduce。</li>
<li><code>L406-L467</code>:每个 CROSS_NIC 值先启动 Worker rank再运行 Head rank。</li>
<li><code>L469-L513</code>:只清理本实验前缀的通信容器和本次 `/tmp` 暂存目录。</li>
</ul>
<p>
Docker 使用和 SGLang 一致的 CUDA 13 nightly 镜像,并显式透传
<code>rdma_cm</code><code>uverbs0</code><code>uverbs3</code>
<code>NCCL_DEBUG=INFO</code> 只在微基准中打开,用于证明 NET/IB/GDRDMA 路径。
Worker 不要求存在 Git 仓库;容器只读挂载自动分发的
<code>/tmp/.../&lt;RUN_ID&gt;/communication_baseline.py</code>。结果目录同时保存
当次源码副本和 SHA256避免两个节点 checkout 不一致造成版本漂移。
</p>
<h3>5.2 P2P 代码</h3>
@ -374,7 +378,7 @@ measurement_window_source = bench_main_marker_plus_duration</code></pre>
<tr><td>L26-L66</td><td>日志、远端执行、命令证据</td><td>基础设施。</td></tr>
<tr><td>L67-L199</td><td>配置、工具、时钟、GPU 空闲门禁</td><td>正式运行前 fail-fast。</td></tr>
<tr><td>L200-L268</td><td>Manifest、marker、Phase 1 委托</td><td>运行身份与复用边界。</td></tr>
<tr><td>L269-L447</td><td>通信基线</td><td>P2P、8/16-rank AllReduce、CROSS_NIC A/B。</td></tr>
<tr><td>L281-L513</td><td>通信基线</td><td>按 Run 分发源码、P2P、8/16-rank AllReduce、CROSS_NIC A/B 与清理</td></tr>
<tr><td>L448-L516</td><td>服务和静态快照</td><td>启停 Phase 1 双机服务并保存环境。</td></tr>
<tr><td>L517-L710</td><td>采集命令与启动</td><td>两节点分层采样。</td></tr>
<tr><td>L711-L772</td><td>采集器检查和停止</td><td>fail-closed 与残留清理。</td></tr>
@ -403,7 +407,7 @@ measurement_window_source = bench_main_marker_plus_duration</code></pre>
</table>
<footer>
本文只描述提交 <code>30664faa41f8</code>。Nsight Systems、SGLang Profiler 和
本文只描述提交 <code>39fc2ba565a3</code>。Nsight Systems、SGLang Profiler 和
Kernel Timeline 属于 Phase 3不加入 Phase 2避免重复采集和职责混淆。
</footer>
</main>

View File

@ -574,11 +574,11 @@ dsv4pro_pro6000d_2node_sglang_hardware_contention_attribution/</code></pre>
<tr><th>文件</th><th>职责</th><th>当前状态</th></tr>
</thead>
<tbody>
<tr><td><code>run_hardware_contention_attribution.sh</code></td><td>唯一 Shell 入口通信基线、服务启停、双节点采集器、Case 编排、门禁和 Trap 清理</td><td class="pass">已实现</td></tr>
<tr><td><code>run_hardware_contention_attribution.sh</code></td><td>唯一 Shell 入口;按 Run 分发通信代码、通信基线、服务启停、双节点采集器、Case 编排、门禁和 Trap 清理</td><td class="pass">已实现</td></tr>
<tr><td><code>config.env</code></td><td>Phase 1 相对路径、节点、代表 Case、分层采样周期、通信基线与 fail-closed 策略</td><td class="pass">已实现</td></tr>
<tr><td><code>communication_baseline.py</code></td><td>CUDA P2P 全矩阵与 PyTorch/NCCL 8/16-rank AllReduce 微基准</td><td class="pass">已实现</td></tr>
<tr><td><code>hardware_contention_attribution.py</code></td><td>精确窗口、全部采集器解析、逐 Case 汇总、通信聚合和逐指标报告</td><td class="pass">已实现</td></tr>
<tr><td><code>tests/test_hardware_contention_attribution.py</code></td><td>GPU/RDMA、精确窗口、DCGM/CPU 解析、通信聚合和结果生成测试</td><td class="pass">8/8 通过</td></tr>
<tr><td><code>tests/test_hardware_contention_attribution.py</code></td><td>Worker 无仓库依赖、GPU/RDMA、精确窗口、DCGM/CPU 解析、通信聚合和结果生成测试</td><td class="pass">9/9 通过</td></tr>
<tr><td><code>README.md</code></td><td>唯一入口、范围和结果目录说明</td><td class="pass">已实现</td></tr>
</tbody>
</table>
@ -602,6 +602,8 @@ dsv4pro_pro6000d_2node_sglang_hardware_contention_attribution/</code></pre>
head_server.log
worker_server.log
communication/
communication_baseline.py
communication_baseline.sha256
p2p_head.log
p2p_worker.log
allreduce_head_8gpu.log
@ -646,8 +648,8 @@ dsv4pro_pro6000d_2node_sglang_hardware_contention_attribution/</code></pre>
<h2>8. 验收结果</h2>
<ul>
<li class="pass">最终代码固定到提交 <code>30664faa41f8</code>Shell 语法检查和三个 Python 文件编译通过。</li>
<li class="pass">Phase 1 精确窗口 4/4 单元测试通过Phase 2 采集、解析与通信聚合 8/8 单元测试通过。</li>
<li class="pass">最终代码固定到提交 <code>39fc2ba565a3</code>Shell 语法检查和三个 Python 文件编译通过。</li>
<li class="pass">Phase 1 精确窗口 4/4 单元测试通过Phase 2 worker 分发、采集、解析与通信聚合 9/9 单元测试通过。</li>
<li class="pass">完整 Dry-run 不启动服务即可展开全部模型、采集和通信命令;通信子流程生成 2 个 P2P、2 个单机 AllReduce、6 个双机 rank 命令。</li>
<li class="pass">最终代码默认要求精确测量窗口、两端 DCGM 可用和全部必需采集器存活,缺失时 fail-closed。</li>
<li>首次 Run 的性能数据保留在第 10 节;最终代码尚未在 16 卡上复跑,因此新通信基线和逐指标表暂不填写虚构数值。</li>
@ -666,6 +668,7 @@ dsv4pro_pro6000d_2node_sglang_hardware_contention_attribution/</code></pre>
<tr><td>2026-07-31 13:27:51 CST</td><td>完成正式双机 Run</td><td>Run <code>dsv4pro-phase2-20260731-130125</code>8/8 benchmark 成功,总用时 26 分 26 秒,无 OOM</td></tr>
<tr><td>2026-07-31 13:40:03 CST</td><td>完成首轮结果归因</td><td>排除原始双 Rail 带宽饱和、整机 CPU 饱和和频率塌陷作为首要原因;锁定 TP16 Kernel、调度与同步时间线</td></tr>
<tr><td>2026-07-31 15:25:00 CST</td><td>完成最终 Phase 2 代码</td><td>提交 <code>30664faa41f8</code>:精确主测量窗口、双节点 DCGM 门禁、5 秒低开销 Host 采样、PCIe/NCCL 基线和逐指标自动报告均已通过本地验证</td></tr>
<tr><td>2026-07-31 16:18:35 CST</td><td>修复 Worker 通信代码路径</td><td>提交 <code>39fc2ba565a3</code>:通信脚本按 Run 自动分发并校验哈希Worker 不再要求存在同路径 Git 仓库9/9 回归测试通过</td></tr>
</tbody>
</table>

View File

@ -12,6 +12,9 @@ RUN_ID="${RUN_ID:-dsv4pro-phase2-$(date +%Y%m%d-%H%M%S)}"
RESULT_DIR="${RESULT_BASE}/${RUN_ID}"
RESULT_TOOL="${SCRIPT_DIR}/hardware_contention_attribution.py"
COMMUNICATION_TOOL="${SCRIPT_DIR}/communication_baseline.py"
COMMUNICATION_STAGE_DIR="/tmp/${EXPERIMENT}_communication/${RUN_ID}"
COMMUNICATION_STAGE_TOOL="${COMMUNICATION_STAGE_DIR}/communication_baseline.py"
COMMUNICATION_CONTAINER_TOOL="/opt/phase2/communication_baseline.py"
MARKERS_PATH="${RESULT_DIR}/markers.csv"
SERVICE_DIR="${RESULT_DIR}/service"
COMMAND_DIR="${RESULT_DIR}/commands"
@ -32,6 +35,15 @@ print_command() {
printf '\n'
}
sha256_file() {
local path="$1"
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "${path}" | awk '{print $1}'
else
shasum -a 256 "${path}" | awk '{print $1}'
fi
}
node_is_local() {
local node="$1"
local local_ips
@ -133,7 +145,7 @@ validate_config() {
preflight_node_tools() {
local node="$1"
local required="nvidia-smi docker mpstat pidstat sar numastat perf"
local required="nvidia-smi docker mpstat pidstat sar numastat perf sha256sum"
local tool
for tool in ${required}; do
if ! run_on_node "${node}" "command -v '${tool}' >/dev/null"; then
@ -266,6 +278,46 @@ run_phase1_action() {
"${command[@]}"
}
stage_communication_tool() {
local provenance_tool="${RESULT_DIR}/communication/communication_baseline.py"
local provenance_hash="${RESULT_DIR}/communication/communication_baseline.sha256"
mkdir -p "${RESULT_DIR}/communication"
install -m 0644 "${COMMUNICATION_TOOL}" "${provenance_tool}"
printf '%s %s\n' \
"$(sha256_file "${provenance_tool}")" \
"$(basename "${provenance_tool}")" \
> "${provenance_hash}"
if [[ "${DRY_RUN}" == "1" ]]; then
log "[DRY] stage communication tool on both nodes: ${COMMUNICATION_STAGE_TOOL}"
return 0
fi
local expected_hash actual_hash node
expected_hash="$(sha256_file "${COMMUNICATION_TOOL}")"
for node in "${HEAD_NODE}" "${WORKER_NODE}"; do
if node_is_local "${node}"; then
mkdir -p "${COMMUNICATION_STAGE_DIR}"
install -m 0644 "${COMMUNICATION_TOOL}" "${COMMUNICATION_STAGE_TOOL}"
else
ssh -o BatchMode=yes -o StrictHostKeyChecking=no "${node}" \
"mkdir -p '${COMMUNICATION_STAGE_DIR}' && \
cat > '${COMMUNICATION_STAGE_TOOL}' && \
chmod 0644 '${COMMUNICATION_STAGE_TOOL}'" \
< "${COMMUNICATION_TOOL}"
fi
actual_hash="$(
run_on_node "${node}" "sha256sum '${COMMUNICATION_STAGE_TOOL}'" |
cut -d' ' -f1
)"
if [[ "${actual_hash}" != "${expected_hash}" ]]; then
log "ERROR: staged communication tool hash mismatch node=${node}"
return 1
fi
log "staged communication tool node=${node} sha256=${actual_hash}"
done
}
build_communication_docker_command() {
local output_name="$1"
local container_name="$2"
@ -281,7 +333,7 @@ build_communication_docker_command() {
--shm-size 20g
--ulimit memlock=-1
--ulimit stack=67108864
-v "${REPO_ROOT}:${REPO_ROOT}:ro"
-v "${COMMUNICATION_STAGE_TOOL}:${COMMUNICATION_CONTAINER_TOOL}:ro"
-e "NCCL_SOCKET_IFNAME=${NCCL_SOCKET_IFNAME}"
-e "NCCL_IB_HCA=${NCCL_IB_HCA}"
-e "NCCL_CROSS_NIC=${cross_nic}"
@ -304,7 +356,7 @@ run_p2p_baseline() {
local command
build_communication_docker_command \
command "${container}" python3 2 \
"${COMMUNICATION_TOOL}" p2p \
"${COMMUNICATION_CONTAINER_TOOL}" p2p \
--node "${role}" \
--size "${P2P_SIZE}" \
--warmup "${P2P_WARMUP}" \
@ -333,7 +385,7 @@ run_single_node_allreduce() {
--nnodes=1 \
--nproc-per-node=8 \
--master-port "${master_port}" \
"${COMMUNICATION_TOOL}" all-reduce \
"${COMMUNICATION_CONTAINER_TOOL}" all-reduce \
--scope "${role}_8gpu" \
--sizes "${COMMUNICATION_SIZES}" \
--repetitions "${COMMUNICATION_REPETITIONS}" \
@ -367,7 +419,7 @@ run_two_node_allreduce() {
worker_command "${worker_container}" torchrun "${cross_nic}" \
"${common_args[@]}" \
--node-rank=1 \
"${COMMUNICATION_TOOL}" all-reduce \
"${COMMUNICATION_CONTAINER_TOOL}" all-reduce \
--scope two_node_16gpu \
--sizes "${COMMUNICATION_SIZES}" \
--repetitions "${COMMUNICATION_REPETITIONS}" \
@ -377,7 +429,7 @@ run_two_node_allreduce() {
head_command "${head_container}" torchrun "${cross_nic}" \
"${common_args[@]}" \
--node-rank=0 \
"${COMMUNICATION_TOOL}" all-reduce \
"${COMMUNICATION_CONTAINER_TOOL}" all-reduce \
--scope two_node_16gpu \
--sizes "${COMMUNICATION_SIZES}" \
--repetitions "${COMMUNICATION_REPETITIONS}" \
@ -424,6 +476,19 @@ cleanup_communication_containers() {
done
}
cleanup_communication_runtime() {
[[ "${DRY_RUN}" == "1" ]] && return 0
local node
for node in "${HEAD_NODE}" "${WORKER_NODE}"; do
run_on_node "${node}" "rm -rf '${COMMUNICATION_STAGE_DIR}'" || true
done
}
cleanup_communication() {
cleanup_communication_containers
cleanup_communication_runtime
}
run_communication_baseline() {
[[ "${RUN_COMMUNICATION_BASELINE}" == "1" ]] || {
log "SKIP communication baseline by configuration"
@ -434,6 +499,7 @@ run_communication_baseline() {
preflight_gpus_idle "${HEAD_NODE}"
preflight_gpus_idle "${WORKER_NODE}"
fi
stage_communication_tool
run_p2p_baseline head "${HEAD_NODE}"
run_p2p_baseline worker "${WORKER_NODE}"
run_single_node_allreduce head "${HEAD_NODE}" "${COMMUNICATION_MASTER_PORT}"
@ -443,6 +509,7 @@ run_communication_baseline() {
run_two_node_allreduce "${cross_nic}"
done
cleanup_communication_containers
cleanup_communication_runtime
}
start_service() {
@ -850,6 +917,7 @@ cleanup() {
stop_collectors || true
stop_service || true
cleanup_communication_containers || true
cleanup_communication_runtime || true
if (( rc != 0 )) && [[ -f "${RESULT_DIR}/manifest.json" ]]; then
finish_manifest ABORTED || true
fi
@ -935,7 +1003,7 @@ run_communication_only() {
preflight_node_tools "${WORKER_NODE}"
preflight_clock_sync
fi
trap cleanup_communication_containers EXIT INT TERM
trap cleanup_communication EXIT INT TERM
run_communication_baseline
python3 "${RESULT_TOOL}" summarize-communication "${RESULT_DIR}"
trap - EXIT INT TERM

View File

@ -15,6 +15,20 @@ import hardware_contention_attribution as attribution # noqa: E402
class HardwareContentionAttributionTest(unittest.TestCase):
def test_communication_tool_is_staged_without_worker_repo_dependency(
self,
) -> None:
script = (
EXPERIMENT_DIR / "run_hardware_contention_attribution.sh"
).read_text(encoding="utf-8")
self.assertIn("stage_communication_tool", script)
self.assertIn(
'-v "${COMMUNICATION_STAGE_TOOL}:${COMMUNICATION_CONTAINER_TOOL}:ro"',
script,
)
self.assertNotIn('-v "${REPO_ROOT}:${REPO_ROOT}:ro"', script)
def test_gpu_summary_ignores_na_and_computes_percentiles(self) -> None:
with tempfile.TemporaryDirectory() as temporary:
path = Path(temporary) / "gpu_samples.csv"