diff --git a/README.md b/README.md index 6fc2a21..ea0f4a1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # sskj — 多平台大模型推理性能基准测试项目 +> **更新(2026-07-30 18:40:53 CST)** +> +> 为 DeepSeek-V4-Pro 双机 TP16 quick-map 的唯一入口新增 `CASE_IDS` 场景过滤和未知 Case 预检,可在完整九点实验前先跑 1K/32K Prefill 与 C1/C32 Decode 四点 Sanity;运行清单会记录实际过滤条件。同步精简阶段档案:正文只保留最终成功 Run 与有效结论,历史失败压缩到末尾经验教训。 +> > **更新(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 重跑尚未执行。 diff --git a/docs/dsv4pro_pro6000d_2node_sglang/phase1_dsv4pro_pro6000d_2node_sglang_quick_map.html b/docs/dsv4pro_pro6000d_2node_sglang/phase1_dsv4pro_pro6000d_2node_sglang_quick_map.html index 7c6b70f..1d3f41c 100644 --- a/docs/dsv4pro_pro6000d_2node_sglang/phase1_dsv4pro_pro6000d_2node_sglang_quick_map.html +++ b/docs/dsv4pro_pro6000d_2node_sglang/phase1_dsv4pro_pro6000d_2node_sglang_quick_map.html @@ -237,21 +237,9 @@ 返回推理优化主计划

- 阶段状态:网络错误已定位,RDMA fail-closed 代码已完成,等待真机验证和重跑。 - 第一次真机 Run - dsv4pro-pro6000d-2node-sglang-quick-20260730-140026 - 已验证双机服务可用,但因发现请求量和 Prefix Cache 口径问题而主动停止。 - 精简后的第二次 Run - dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625 - 完成 3 个 Prefill 固定点后曾观察到约 65 token/s。 - 后续控制组确认 quick-map 容器看不到 RDMA,NCCL 回退 - NET/Socket,同时脚本把 Socket 接口设成了低速非计算网。 - 因此这 3 个结果不能作为模型、算子或 TP16 的性能基线,Phase 2 暂停, - 当前入口已固定计算网 eth0/eth3 与 - mlx5_0/mlx5_3,完成设备透传、日志强校验和 dry-run; - 只有真机日志证明 NET/IB 后才会重跑 Phase 1。 - Manifest 终态为 ABORTED_EARLY_FOR_PHASE2; - 两节点容器和 16 张 GPU 已清理。 + 阶段状态:等待真实双 Rail RDMA 重跑。 + 启动器与静态验证已完成;历史无效 Run 不进入最终分析。下一次仅在 + NCCL 日志通过 NET/IB 门禁后执行 Sanity 和完整 quick map。

1. 目标与边界

@@ -265,7 +253,7 @@
  • 只测试 SGLang,不测试 vLLM。
  • 使用双机 16 卡完整实例,不做 PD 分离。
  • 不启用 MTP、EAGLE、DSpark 或其他投机解码。
  • -
  • 本轮不启用 Profiler;受错误 Socket 网络影响的三个旧 Case 仅保留为事故证据,不再作为端到端基线。
  • +
  • 本轮不启用 Profiler;正文只记录最终有效 Run,失败尝试仅在末尾总结经验。
  • 不修改或调用旧的全天全量 Benchmark 脚本。
  • @@ -542,211 +530,41 @@ wait "${background_pid}" 完整 Dry-run通过服务、九个固定点、混合 A/B、清理均展开成功 真实旧 Bench JSON 解析通过成功解析 P50/P95/P99 与吞吐字段 项目精简通过实验目录顶层仅保留一个 Shell 入口 - 双机容器启动通过第二次 Run 于 14:42:04 通过 Health Check,启动约 5 分 30 秒 - Prefix Cache 隔离通过Warm-up 后 POST /flush_cache 返回 200,正式请求仍为 #cached-token: 0 - 中止清理通过头、Worker 节点均无相关容器和 Bench 进程,16 张 GPU 显存回到 0 MiB + RDMA fail-closed静态验证通过非法网卡、缺失设备或未出现 NET/IB 时禁止 benchmark -

    9. 真机结果

    -

    - 最终采用 Run - dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625。 - 以下三条均为一条请求、C=1OSL=1,且正式测量前 - Prefix Cache 已清空。 +

    9. 最终真机结果

    +

    + 正确双 Rail RDMA 下的正式结果尚未生成。本节只接受通过传输门禁、 + 冷缓存口径和结果校验的最终 Run;完成后将写入四点 Sanity、九个固定点、 + 混合 A/B 与阶段结论。

    - - - - - - - - + - - - - + + + +
    CaseISL输入 TPSTTFTE2E状态
    门禁或实验状态最终证据
    short_prefill_latency_1k_c11K64.44 tok/s15.88 s15.88 sCOMPLETED
    mid_prefill_latency_32k_c132K64.96 tok/s504.44 s504.44 sCOMPLETED
    long_prefill_latency_128k_c1128K65.20 tok/s2010.38 s2010.38 sCOMPLETED
    mid_prefill_throughput_32k_c1632K × 16未形成最终结果未形成最终结果未形成最终结果ABORTED
    双 Rail NET/IB待真机验证头、Worker 日志均需识别 mlx5_0/mlx5_3
    四点 Sanity待执行1K/32K Prefill 与 C1/C32 Decode
    九个固定点待执行最终 summary.csvreport.md
    混合 A/B待执行Control 与 Treatment 的 Decode 指标变化
    -

    - 这些数值已被降级为“错误网络路径复现”。 - 它们可以证明低速 Socket 路径近似随输入长度线性增长,但不能用于判断 - DeepSeek-V4-Pro、SGLang Kernel、GPU 算力或正确 TP16 数据面的性能。 -

    - -

    9.1 对错误网络 Run 可以下的结论

    +

    10. 经验教训

    - -

    9.2 现在还不能下的结论

    - - -

    9.3 为什么提前结束

    - 当时根据约 65 token/s 的稳定信号提前停止第 4 个 Case,并写入 - EARLY_STOP_FOR_PHASE2。后续网络控制组表明这个停止动作仍然避免了 - 无效算力消耗,但调查方向需要修正:不是立即进入 Kernel/硬件归因,而是先校正 - NCCL 数据面并重跑快速地图。 -

    - -

    9.4 为什么旧脚本的 TTFT 短很多:缓存与网络两个因素

    -

    - 2026-07-30 对旧目录 - /data/qqt/sskj/experiments/pro6000/dsv4_pro6000_sglang_tp16 - 做了逐项审计,并追加原网络配置冷请求控制组。最终结论是: - 旧结果与 quick-map 既不是同一缓存口径,也不是同一 Socket 网络路径。 -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    审计项旧脚本quick-map判断
    服务端配置同一镜像,TP16 / EP2,8K Chunk,FlashInfer MXFP4 MoE模型参数相同,但 NCCL/IP 接口不同不能排除网络启动参数回归
    正式请求数C=1 仍强制至少 10 条延迟点只发 1 条旧均值混合了多条请求的缓存状态
    Warm-up每个 Shape 固定 16 条同 Prompt Warm-upWarm-up 后清 Prefix Cache;32K/128K 延迟点不做额外 Warm-up旧正式测量会继承 Warm-up 的 Prompt 前缀
    Cache 清理从不传 --flush-cache正式测量前传 --flush-cache旧脚本跨 Case、跨长度保留 Radix/Prefix Cache
    Shape 顺序固定 Seed=42,按 1K→4K→8K→16K→32K→64K→128K 递增每个延迟点按冷缓存解释旧请求会复用上一档相同 Prompt 的短前缀
    -

    - 旧时间线还有一条直接证据:17:40 的失败 Run 已完整执行过 - 1K / 128 / C=1,18:01 的正式 Run 没有重启服务便再次执行同一批 - Seed=42 请求。因此旧文件中的 1K TTFT 约 0.455 秒,本身就是热缓存结果。 -

    - - - - - - - - - - - -
    最小复现Mean TTFTP95 TTFT解释
    quick-map 错误网络,1K→1 冷缓存15.88–16.04 s15.88–16.04 s低速非计算网 Socket 路径
    旧原始网络,1K→1 冷缓存1.458 s1.458 seth0 400G 物理端口上的 Socket 路径
    旧原始网络,32K→1 冷缓存38.062 s38.062 s比 quick-map 的 504.44 s 快约 13.25 倍
    quick-map,1K→128 冷缓存15.79 s15.79 s排除 OSL=1 特殊慢路径,但仍受错误网络影响
    2026-07-28 旧产物0.455 s0.513 s服务已被前一次 Run 和后续递增长度预热
    -

    - 旧 32K 文件的第一条请求约 0.67 秒,其余 9 条平均约 35.51 秒; - 旧 128K 文件的第一条约 0.96 秒,其余 9 条平均约 144.83 秒。 - 后 9 条也已经分别继承上一档 16K、64K 前缀。旧报告仍按完整 ISL 统计 - Input TPS,因此会把只计算新增后缀的耗时除进完整 token 数,进一步放大吞吐。 -

    -

    - 缓存审计原始产物保存在: -

    -
    /data/hzy/dsv4_script_audit_20260730/
    -

    - 本地归档: + 历史排查细节保存在 TTFT 脚本口径审计报告 - 及同目录原始 JSON/log。 -

    - -

    9.5 NCCL 数据面控制组

    -

    - 原脚本控制组使用其默认 NCCL_SOCKET_IFNAME=eth0,不注入 - NCCL_IB_HCANCCL_CROSS_NIC。NCCL INFO 和容器设备 - 检查给出了直接证据: -

    -
    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
    -

    - 宿主机本身存在 uverbs0/uverbs3/rdma_cm,且 - mlx5_0→eth0mlx5_3→eth3 均 Up。这说明问题位于 - 容器设备透传和脚本网卡选择,不是物理计算网缺失。quick-map 与原脚本使用相同 - Docker 设备边界,却把 Socket 接口设成了低速非计算网,造成 10–13 倍退化。 - 在 NET/Socket 路径上,NCCL_CROSS_NIC=1 不参与选择。 -

    -

    - 修正后的唯一入口已加入三层门禁:两端字符设备预检、Docker 精确 - --device 透传、健康后 NCCL INFO 传输后端验证。静态检查、 - 结果解析器单测和完整 dry-run 已通过;非法 eth1 或 - mlx5_1 会在加载模型前被拒绝。该结论仍属于代码验证, - 真机 NET/IB 成功证据要等下一次启动后补入。 -

    -

    - 网络控制组原始产物: -

    -
    /data/hzy/dsv4_oldscript_cold_control_20260730/
    -

    - 本地归档: - TP16 网络路径审计报告 - 及同目录 NCCL/benchmark 原始日志。 -

    - - - - - - - - - - - - - - - - - - -
    检查点状态结果或结论
    服务健康通过端口可用且无 OOM/Engine 异常,但 NCCL 数据面未按预期进入 RDMA
    第一次固定点 Run主动停止发现 32K 单请求约需十余分钟;原协议的 4 次同形状请求会使整轮再次接近半天
    RDMA fail-closed 启动器静态验证通过仅允许两条计算网 Rail;设备透传、日志强校验与运行清单已加入
    精简后九个固定点旧 Run 作废,待重跑先完成真机 NET/IB 验证,再生成新的性能基线
    混合干扰 A/B未执行待 Prefill 根因明确后再决定是否重放
    阶段耗时约 65 分钟14:36:26 启动,15:41:52 完成进程与容器清理
    是否进入下一阶段Phase 2 暂停;先证明双 Rail NET/IB 并重跑 Phase 1
    - -

    10. 结果位置

    -

    服务器原始结果:

    -
    /data/hzy/sskj/experiments/pro6000/
    -dsv4pro_pro6000d_2node_sglang_tp16_quick_map/results/
    -dsv4pro-pro6000d-2node-sglang-quick-v2-20260730-143625/
    -

    - 本地已归档 - report.md、 - aggregate.csv - 和同目录下的 Manifest、Summary、主日志。 + 与 + TP16 网络路径审计报告, + 不作为本阶段最终结果。

    11. 运行命令

    diff --git a/docs/dsv4pro_pro6000d_2node_sglang/推理优化计划.html b/docs/dsv4pro_pro6000d_2node_sglang/推理优化计划.html index af9af15..f661f8f 100644 --- a/docs/dsv4pro_pro6000d_2node_sglang/推理优化计划.html +++ b/docs/dsv4pro_pro6000d_2node_sglang/推理优化计划.html @@ -415,7 +415,7 @@

    6000D 双机 DeepSeek-V4-Pro 推理优化计划

    -

    适用环境:174.1.51.5 + 174.1.51.7,每台 8 张 RTX PRO 6000 Blackwell Server Edition
    当前部署:DeepSeek-V4-Pro,16 张 GPU 组成一个完整实例
    当前约束:模型暂时只能使用全部 16 张 GPU,无法额外复制一套模型进行 PD 分离
    计划版本:2026-07-30 17:54 CST

    +

    适用环境:174.1.51.5 + 174.1.51.7,每台 8 张 RTX PRO 6000 Blackwell Server Edition
    当前部署:DeepSeek-V4-Pro,16 张 GPU 组成一个完整实例
    当前约束:模型暂时只能使用全部 16 张 GPU,无法额外复制一套模型进行 PD 分离
    计划版本:2026-07-30 18:40 CST

    当前执行状态与阶段档案

    @@ -443,6 +443,10 @@
    打开 Phase 2 档案
    +

    +阶段档案规范:正文只保留最终成功实验、有效结果和结论; +失败尝试压缩到末尾的经验教训。阶段完成时删除“暂不能下结论”等过渡内容。 +

    0. 先看懂双机通信

    在分析 TP16 性能前,先区分设备、传输后端与 NCCL 参数。 @@ -471,14 +475,6 @@ 400 Gbit/s ≈ 50 GB/s 只是单向理论上限,NCCL 的 algbw/busbw 与端到端模型吞吐都不能直接等同于该数字。

    -
    -

    -2026-07-30 已确认:当时容器内没有 /dev/infiniband,NCCL 日志显示 -NET/IB : No device found 并回退 NET/Socket。quick-map 又误选 -低速非计算网,因此 1K/32K 冷 Prefill 比原脚本的 eth0 Socket 路径慢约 -10.9×/13.25×。这不是 NCCL_CROSS_NIC=1 导致的。 -

    -

    1. 目标与原则

    1.1 最终目标

    在不做 PD 分离的前提下,定位 DeepSeek-V4-Pro 在双机 6000D 上的端到端瓶颈,并提高:

    diff --git a/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/README.md b/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/README.md index 2417aac..191d74a 100644 --- a/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/README.md +++ b/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/README.md @@ -68,6 +68,17 @@ DRY_RUN=1 bash run_quick_map.sh all The launcher refuses to start while either node has an active GPU compute process. `ALLOW_BUSY_GPU=1` exists only for deliberate operator override. +Before the complete run, use the same entry for a four-point Sanity pass: + +```bash +CASE_IDS="short_prefill_latency_1k_c1,mid_prefill_latency_32k_c1,decode_latency_1k_to_1k_c1,decode_throughput_1k_to_1k_c32" \ + DRY_RUN=1 bash run_quick_map.sh fixed +``` + +Remove `DRY_RUN=1` only after the TP16 service has passed the `NET/IB` startup +gate. An unknown `CASE_IDS` value fails before any benchmark request is sent. +Leave `CASE_IDS` empty to run all nine fixed points. + ## GPU run Run the complete quick map in `tmux` after both nodes are free: diff --git a/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/config.env b/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/config.env index a7a568d..8370ab0 100644 --- a/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/config.env +++ b/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/config.env @@ -70,5 +70,9 @@ SCENARIO_FILE="${SCENARIO_FILE:-${SCRIPT_DIR}/quick_map_scenarios.tsv}" RESULT_BASE="${RESULT_BASE:-${SCRIPT_DIR}/results}" RUNTIME_BASE="${RUNTIME_BASE:-${SCRIPT_DIR}/runtime}" +# Optional comma- or space-separated subset of quick_map_scenarios.tsv. +# Leave empty for the complete nine-point fixed suite. +CASE_IDS="${CASE_IDS:-}" + DRY_RUN="${DRY_RUN:-0}" RESUME="${RESUME:-1}" diff --git a/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/quick_map_results.py b/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/quick_map_results.py index 27bb7d8..6e1878c 100755 --- a/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/quick_map_results.py +++ b/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/quick_map_results.py @@ -369,6 +369,7 @@ def write_manifest(args: argparse.Namespace) -> None: "git_commit": args.git_commit, "git_dirty": bool(args.git_dirty), "scenario_file": args.scenario_file, + "case_ids": args.case_ids, "notes": [ "The fixed quick map does not stop on SLO.", "Profiler is disabled; these results are eligible for performance comparison.", @@ -649,6 +650,7 @@ def add_manifest_arguments(parser: argparse.ArgumentParser) -> None: parser.add_argument("--git-commit", required=True) parser.add_argument("--git-dirty", type=int, required=True) parser.add_argument("--scenario-file", required=True) + parser.add_argument("--case-ids", default="") def main() -> None: diff --git a/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/run_quick_map.sh b/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/run_quick_map.sh index 5da247a..12c1284 100755 --- a/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/run_quick_map.sh +++ b/experiments/pro6000/dsv4pro_pro6000d_2node_sglang_tp16_quick_map/run_quick_map.sh @@ -55,6 +55,33 @@ run_on_node() { fi } +case_selected() { + local candidate="$1" + local normalized="${CASE_IDS//,/ }" + local selected + [[ -z "${normalized//[[:space:]]/}" ]] && return 0 + for selected in ${normalized}; do + [[ "${selected}" == "${candidate}" ]] && return 0 + done + return 1 +} + +validate_case_filter() { + local normalized="${CASE_IDS//,/ }" + local selected + [[ -z "${normalized//[[:space:]]/}" ]] && return 0 + + for selected in ${normalized}; do + if ! awk -F $'\t' -v case_id="${selected}" \ + '$1 == case_id { found = 1 } END { exit !found }' \ + "${SCENARIO_FILE}"; then + log "ERROR: CASE_IDS contains an unknown case: ${selected}" + return 1 + fi + done + log "Selected fixed cases: ${normalized}" +} + validate_network_config() { case "${ENABLE_RDMA}" in 0|1) ;; @@ -644,7 +671,8 @@ write_run_manifest() { --rdma-device-paths "${RDMA_DEVICE_PATHS}" \ --git-commit "${git_commit}" \ --git-dirty "${git_dirty}" \ - --scenario-file "${SCENARIO_FILE}" + --scenario-file "${SCENARIO_FILE}" \ + --case-ids "${CASE_IDS}" } summarize_results() { @@ -667,12 +695,14 @@ enable_result_logging() { run_fixed_suite() { log "Validating scenario file: ${SCENARIO_FILE}" python3 "${RESULT_TOOL}" validate-scenarios "${SCENARIO_FILE}" + validate_case_filter local completed=0 total=0 local rep case_id stage isl osl concurrency multiplier minimum warmup note for (( rep=1; rep<=NUM_REPETITIONS; rep++ )); do while IFS=$'\t' read -r case_id stage isl osl concurrency multiplier minimum warmup note; do [[ -z "${case_id}" || "${case_id}" == \#* ]] && continue + case_selected "${case_id}" || continue [[ -n "${PROMPT_MULTIPLIER_OVERRIDE}" ]] && multiplier="${PROMPT_MULTIPLIER_OVERRIDE}" [[ -n "${MIN_NUM_PROMPTS_OVERRIDE}" ]] && minimum="${MIN_NUM_PROMPTS_OVERRIDE}" @@ -698,6 +728,10 @@ run_fixed_suite() { [[ "${DRY_RUN}" == "1" ]] || sleep "${CASE_COOLDOWN_S}" done < "${SCENARIO_FILE}" done + if (( total == 0 )); then + log "ERROR: fixed suite selected zero cases" + return 1 + fi log "Fixed quick map complete: completed=${completed}/${total} failed=${FIXED_FAILURES}" } @@ -848,6 +882,10 @@ run_standalone_suite() { run_all() { SERVER_ARTIFACT_DIR="${RESULT_DIR}/server" + log "Validating scenario file before service startup: ${SCENARIO_FILE}" + python3 "${RESULT_TOOL}" validate-scenarios "${SCENARIO_FILE}" + validate_case_filter + if [[ "${DRY_RUN}" != "1" ]]; then preflight_bench_client enable_result_logging