Skip to content

[NV] [PR3] feat(dsv4): enable synthetic-acceptance for dsv4-fp4-gb200 mtp2 sweep#2091

Open
qiching wants to merge 4 commits into
mainfrom
albecheng/enable-dsv4-synthetic-mtp2
Open

[NV] [PR3] feat(dsv4): enable synthetic-acceptance for dsv4-fp4-gb200 mtp2 sweep#2091
qiching wants to merge 4 commits into
mainfrom
albecheng/enable-dsv4-synthetic-mtp2

Conversation

@qiching

@qiching qiching commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

root added 4 commits July 5, 2026 18:36
Two changes needed to run dsv4 fp4 multinode sweeps on the watchtower
(Oracle) gb200 cluster:

- Point the dynamo-vllm dsv4 MODEL_PATH at the base DeepSeek-V4-Pro
  checkpoint instead of the -NVFP4 re-quant. The pinned v0.20.1
  container's deepseek_v4 loader doesn't define the NVFP4 export's extra
  quant params (e.g. ffn.experts.w13_input_scale) and KeyErrors at load;
  the base checkpoint loads and matches the recipe's served identity.
- Add dsv4/dynamo-vllm to uses_watchtower_shared_fs() so the srt-slurm
  workspace/outputs land on a compute-visible shared FS (the runner home
  is not cross-mounted to compute nodes, otherwise the batch step dies).
Framework-agnostic plumbing for optionally overriding a recipe's
speculative-decoding acceptance length with a synthetic value, gated on
SYNTHETIC_ACCEPTANCE=true (no-op otherwise, so this commit changes no
existing behavior).

- runners/synthetic_injectors/: backend registry (register/get_injector).
  No backends registered yet -> the generic layer alone injects nothing.
- runners/inject_synthetic_acceptance.py: generic driver. Handles flag
  gating, acceptance-length resolution (explicit env or reference-yaml
  lookup) and file I/O, then delegates the recipe rewrite to the
  framework backend resolved from FRAMEWORK. Errors clearly if the flag
  is on but no backend is registered.
- runners/launch_gb200-nv.sh: invoke the driver after the name override
  and before srtctl apply. With no backend registered this is a no-op.
Register a dynamo-vllm backend with the generic driver. When
SYNTHETIC_ACCEPTANCE=true and FRAMEWORK=dynamo-vllm, every
speculative-config JSON entry in the recipe is rewritten to add
rejection_sample_method=synthetic and synthetic_acceptance_length=<al>,
so the vLLM engine emits a controlled mean acceptance length instead of
running the real draft model. Rewrite uses the json module on the
captured JSON (no string-munging), preserving the recipe's compact
style.

- runners/synthetic_injectors/vllm.py: rewrite() + spec_tokens_from_recipe(),
  self-registers under "dynamo-vllm".
- runners/synthetic_injectors/__init__.py: import the backend so it
  registers on package import.

Still inert until a config opts in via SYNTHETIC_ACCEPTANCE (added for
dsv4 mtp2 in a follow-up).
Opt the dsv4 fp4 gb200 dynamo-vllm mtp2 config into synthetic acceptance
(length 2.27) on all four search-space cells, and add a matching
-nosynthetic baseline config (identical topology/recipes, synthetic envs
removed) so the sweep produces a before/after Pareto pair (throughput vs
interactivity) in one run.

This is the config that actually exercises the plumbing from the
preceding PRs: base-checkpoint/shared-FS multinode fix, the generic
injection driver, and the vLLM backend.
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Thanks for the contribution! Please reach out to respective companies' CODEOWNER to fill in the latest PR_REVIEW_CHECKLIST.md before pinging core maintainer on Slack for review. In order for the signoff PR check bot to trigger, you must follow the PR_REVIEW_CHECKLIST.md template correctly, including the phrase As a PR reviewer and CODEOWNER, I have reviewed this and have.

For PR verification, add the full-sweep-fail-fast label (strongly recommended) to this PR — the benchmark sweep only runs on labeled PRs. Use full-sweep-enabled only if you need matrix jobs to keep running past a failure.

PR authors are responsible for ensuring that after merging, all GitHub Action jobs fully pass. A lot of the time, failures are just flakes and simply re-running the failed jobs will fix it. See GitHub's docs on re-running failed jobs


感谢你的贡献!请联系相应公司的 CODEOWNER 填写最新的 PR_REVIEW_CHECKLIST.md,然后再在 Slack 上联系核心维护者进行审阅。为了触发 signoff PR 检查机器人,你必须正确遵循 PR_REVIEW_CHECKLIST.md 模板,包括保留英文语句 As a PR reviewer and CODEOWNER, I have reviewed this and have

如需进行 PR 验证,请为此 PR 添加 full-sweep-fail-fast 标签(强烈推荐)— 基准测试 sweep 仅在带有标签的 PR 上运行。仅当需要矩阵任务在失败后继续运行时才使用 full-sweep-enabled

PR 作者有责任确保合并后所有 GitHub Action 任务完全通过。 很多时候失败只是偶发抖动(flake),重新运行失败的任务即可解决。参见 GitHub 关于重新运行失败任务的文档

@qiching

qiching commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

/sweep test-config --config-files configs/nvidia-master.yaml --config-keys dsv4-fp4-gb200-dynamo-vllm-mtp2 --no-evals

@qiching qiching marked this pull request as ready for review July 8, 2026 17:05
@qiching qiching requested a review from a team July 8, 2026 17:05
@claude

claude Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @qiching's task in 3m 48s —— View job


Review of PR #2091

  • Gather context (diff vs base, read changed files)
  • Validate config rules (perf-changelog, image accessibility, model prefix)
  • Review new synthetic-acceptance injector code
  • Review launch script changes
  • Post findings

Summary

Found 1 blocking issue and 2 warnings (posted as inline comments):

  1. 🔴 configs/nvidia-master.yaml was modified without a corresponding perf-changelog.yaml entry — required for master-config changes; append the entry to the end of the file.
  2. 🟡 runners/launch_gb200-nv.sh:476 — the injector's exit status is ignored (no set -e), so an injection failure silently runs the benchmark with the unmodified recipe while the results get recorded under the synthetic-acceptance key.
  3. 🟡 runners/inject_synthetic_acceptance.py:146-148 — when injection is explicitly enabled but zero speculative-config entries match, the script warns and exits 0 instead of failing, producing the same mislabeled-data failure mode.

Minor note (not blocking): the auto-lookup path in inject_synthetic_acceptance.py references benchmarks/speedbench-reference-al.yaml, which does not exist in the repo yet — presumably arriving in a follow-up PR of this series. It fails loudly with a clear error and this PR always sets SYNTHETIC_ACCEPTANCE_LENGTH explicitly, so it's fine for now.

Otherwise the design is sound: the injector is a strict no-op when SYNTHETIC_ACCEPTANCE is unset, the vLLM backend edits the speculative-config JSON via the json module rather than string-munging, the container image is a public Docker Hub image, the launch script retains the enroot import docker:// pattern, and the -nosynthetic baseline block is an exact topology mirror of the mtp2 block with only the synthetic envs removed.

# MTP2 variant of dsv4-fp4-gb200-dynamo-vllm. Uses the vLLM 0.20.1 image
# and hand-picked 8k/1k Pareto points mirrored from NVIDIA/srt-slurm.
dsv4-fp4-gb200-dynamo-vllm-mtp2:
image: vllm/vllm-openai:v0.20.1-ubuntu2404

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 BLOCKING: Master config files were modified but perf-changelog.yaml was not updated. When changing configs/amd-master.yaml or configs/nvidia-master.yaml, you must add a corresponding entry to perf-changelog.yaml documenting the changes.

Why it matters: The perf changelog is how config-driven performance changes are tracked chronologically; this PR changes what dsv4-fp4-gb200-dynamo-vllm-mtp2 measures (synthetic acceptance, AL=2.27) and adds a new -nosynthetic baseline key.

Fix: Append an entry to the end of perf-changelog.yaml (the file is read chronologically, oldest at top) documenting the synthetic-acceptance enablement and the new baseline key, with a link to this PR.

# Optionally inject synthetic acceptance into the recipe's speculative-config
# when SYNTHETIC_ACCEPTANCE=true (no-op otherwise). Must run after the name
# override and before srtctl apply so the rendered job picks it up.
python3 "$GITHUB_WORKSPACE/runners/inject_synthetic_acceptance.py" "$CONFIG_PATH" "$FRAMEWORK"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 WARNING: The injector's exit status is ignored — this script runs under set -x only (no set -e), so if inject_synthetic_acceptance.py fails (sys.exit("ERROR: ...") on unregistered framework, bad SYNTHETIC_ACCEPTANCE_LENGTH, or an uncaught traceback), the sweep proceeds with the unmodified recipe.

Why it matters: The benchmark then runs real MTP acceptance but the results are recorded and published under the synthetic-acceptance config key — silently wrong data, which is worse than a failed job.

Fix:

Suggested change
python3 "$GITHUB_WORKSPACE/runners/inject_synthetic_acceptance.py" "$CONFIG_PATH" "$FRAMEWORK"
python3 "$GITHUB_WORKSPACE/runners/inject_synthetic_acceptance.py" "$CONFIG_PATH" "$FRAMEWORK" || { echo "Error: synthetic-acceptance injection failed" >&2; exit 1; }

Comment on lines +146 to +148
if count == 0:
_log("WARNING: No speculative-config entries found to modify; leaving recipe unchanged")
return 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 WARNING: When SYNTHETIC_ACCEPTANCE=true but the recipe contains no matching speculative-config: '<json>' line (e.g. the recipe uses double quotes, block style, or drops the field), this logs a warning and returns 0 — the sweep continues and publishes real-MTP numbers under the synthetic-acceptance config key.

Why it matters: The caller explicitly opted in, so "nothing injected" means the recipe and the config key have drifted apart; the resulting data is silently mislabeled rather than the job failing.

Fix: Treat this as an error when injection was explicitly requested:

Suggested change
if count == 0:
_log("WARNING: No speculative-config entries found to modify; leaving recipe unchanged")
return 0
if count == 0:
sys.exit(
"ERROR: SYNTHETIC_ACCEPTANCE=true but no speculative-config "
f"entries were found to modify in {config_file}"
)

Comment on lines +473 to +476
# Optionally inject synthetic acceptance into the recipe's speculative-config
# when SYNTHETIC_ACCEPTANCE=true (no-op otherwise). Must run after the name
# override and before srtctl apply so the rendered job picks it up.
python3 "$GITHUB_WORKSPACE/runners/inject_synthetic_acceptance.py" "$CONFIG_PATH" "$FRAMEWORK"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Two-layer silent failure in the new synthetic-acceptance injection. launch_gb200-nv.sh:476 invokes inject_synthetic_acceptance.py without || exit 1, and the script has only set -x (no set -e), so any sys.exit(1) from the injector is swallowed and srtctl apply proceeds with the unmodified recipe. Compounding this, inject_synthetic_acceptance.py:146-148 returns 0 with only a WARNING when count == 0 (e.g. the vLLM backend's single-quoted-JSON regex fails against a future block-scalar/double-quoted recipe). When SYNTHETIC_ACCEPTANCE=true is set on the mtp2 configs, either path silently produces a run without synthetic acceptance whose results are then attributed to the synthetic-acceptance side of the -nosynthetic Pareto comparison this PR is built to enable. Neither path fires for the currently-shipped recipes — fix is trivial defense-in-depth: add || exit 1 on line 476, and have the injector sys.exit non-zero on count == 0 when SYNTHETIC_ACCEPTANCE=true was explicitly requested.

Extended reasoning...

What the bug is

This PR wires a new synthetic-acceptance injection step into launch_gb200-nv.sh and adds a dsv4-fp4-gb200-dynamo-vllm-mtp2-nosynthetic baseline so the sweep can Pareto-compare synthetic-AL results against a real-draft baseline. There are two independent silent-failure paths in that new plumbing:

Layer 1 — launcher swallows non-zero exits. runners/launch_gb200-nv.sh:5 has only set -x (no set -e), and line 476 invokes the injector as:

python3 "$GITHUB_WORKSPACE/runners/inject_synthetic_acceptance.py" "$CONFIG_PATH" "$FRAMEWORK"

with no || exit 1 trailer. The injector has five sys.exit("ERROR: ...") paths (no injector registered for framework, missing reference YAML when SYNTHETIC_ACCEPTANCE_LENGTH is unset, missing model key, missing num_spec_tokens, missing THINKING_MODE). Any of these produce exit code 1 — but the launcher just continues to srtctl apply with the unmodified recipe. Every other critical step in this same file (mkdir/rsync/git clone/make setup at 448, 455, and the entire clone-branch block) uses || exit 1, so this omission is out of local convention.

Layer 2 — injector silently no-ops on zero matches. runners/inject_synthetic_acceptance.py:146-148:

if count == 0:
    _log("WARNING: No speculative-config entries found to modify; leaving recipe unchanged")
    return 0

returns success even when SYNTHETIC_ACCEPTANCE=true was explicitly requested. The vLLM backend regex speculative-config:\s*'([^']+)' in runners/synthetic_injectors/vllm.py:22 only matches single-quoted JSON on a single line — a recipe converted to double-quoted style, block scalar (speculative-config: |), or bare flow style would silently yield count=0 and the injector would return 0, hiding the mismatch from the launcher entirely (even after fixing Layer 1).

Step-by-step proof (Layer 1)

  1. A future config sets SYNTHETIC_ACCEPTANCE=true and FRAMEWORK=dynamo-sglang (or any framework not in _INJECTORS).
  2. inject_synthetic_acceptance.py's inject() hits get_injector("dynamo-sglang")None.
  3. It calls sys.exit("ERROR: SYNTHETIC_ACCEPTANCE=true but no synthetic-acceptance injector is registered for FRAMEWORK='dynamo-sglang'") — process exits 1.
  4. Bash continues, because there's no set -e and no || exit 1. set -x echoes the exit; nothing stops the pipeline.
  5. srtctl apply runs with the unmodified recipe (real draft model MTP2, not synthetic AL=2.27).
  6. The benchmark completes and the JSON is saved under the config key. Downstream perf-changelog / the -nosynthetic Pareto comparison treats these numbers as the synthetic-acceptance side of the sweep — silently poisoned data.

Step-by-step proof (Layer 2)

  1. A future maintainer reformats agg-gb200-low-latency-mtp2.yaml to use block scalars for the speculative-config JSON (or double quotes — a stylistically valid change).
  2. _SPEC_CONFIG_RE.finditer(text) yields zero matches.
  3. injector.rewrite() returns ("<unmodified>", 0).
  4. The count == 0 branch logs a WARNING and returns 0.
  5. Launcher accepts exit 0 (even after fixing Layer 1) and proceeds to srtctl apply with the unmodified recipe.
  6. Same downstream mis-attribution as above.

Why this is nit-severity for THIS PR

I verified that none of the currently-shipped recipes trigger either failure mode:

  • All four mtp2 recipes referenced by dsv4-fp4-gb200-dynamo-vllm-mtp2 (agg-gb200-low-latency-mtp2.yaml, disagg-gb200-{low-latency,mid-curve-megamoe,high-tpt-megamoe}-mtp2.yaml) use the exact single-quoted JSON form speculative-config: '{"method":"mtp","num_speculative_tokens":2}' that the regex expects.
  • The config sets SYNTHETIC_ACCEPTANCE_LENGTH=2.27 explicitly on every scenario, so the reference-YAML lookup path (which has three of the five sys.exit calls) is skipped entirely.
  • FRAMEWORK=dynamo-vllm is registered by runners/synthetic_injectors/vllm.py:69, so the "no injector for framework" path doesn't fire.

So the immediate mtp2 vs mtp2-nosynthetic Pareto comparison this PR is enabling should be correct. This is defense-in-depth for the scaffolding that's explicitly designed to be reused across future frameworks (sglang, trtllm — see runners/synthetic_injectors/__init__.py's module docstring) and models.

How to fix

Two one-liners:

-python3 "$GITHUB_WORKSPACE/runners/inject_synthetic_acceptance.py" "$CONFIG_PATH" "$FRAMEWORK"
+python3 "$GITHUB_WORKSPACE/runners/inject_synthetic_acceptance.py" "$CONFIG_PATH" "$FRAMEWORK" || exit 1

and in inject_synthetic_acceptance.py:

     if count == 0:
-        _log("WARNING: No speculative-config entries found to modify; leaving recipe unchanged")
-        return 0
+        sys.exit(
+            "ERROR: SYNTHETIC_ACCEPTANCE=true was requested but no speculative-config "
+            f"entries matched in {config_file}; refusing to run an un-injected sweep"
+        )

Both changes match the fail-fast pattern used throughout the launcher (mkdir/rsync/git-clone/make setup all use || exit 1).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

1 participant