Add local "dry run" mode: run all GHA input/script-generation logic without triggering a run / 添加本地「试运行」模式:执行所有 GHA 输入/脚本生成逻辑而不触发实际运行
@cquil11 is already working on this.
Since Jul 1, 2026.
Assessment
This issue has not been assessed yet.
Description
Summary
Add a local "dry run" mode that reproduces everything the e2e benchmark pipeline does to turn a dispatch into runnable work — expand the matrix, fill in the per-job inputs, and generate the config files and command scripts — but stops short of executing anything: no workflow_dispatch, no runner allocation, no server launch, no benchmark/aiperf run, nothing touching the clusters.
The point is to preview exactly what a given generate-cli-command would produce (the bucketed matrices, the per-job config.yaml, the vllm_command.txt / sglang_command.txt / benchmark_command.txt / lmcache_command.txt / mooncake_config.json) before spending a real run on the GPU fleet.
Motivation
Right now the only way to see what a dispatch expands into is to actually fire e2e-tests.yml, which allocates self-hosted Slurm runners and starts jobs on the GPU clusters. That's expensive and slow for what is often just "did I get the matrix / concurrency sweep / command flags right?"
Almost all of the "fill in inputs + create scripts" logic is our own Python and bash, not native GHA steps — which is good news: a dry run can call the same code directly and stay faithful to CI without emulating GitHub at all.
What a dry run must reproduce (grounded inventory)
Mapped from the current pipeline:
| Stage | Where | What happens |
|---|---|---|
| Matrix generation | utils/matrix_logic/generate_sweep_configs.py (generate_full_sweep ~L223-575, mark_eval_entries ~L124-220) |
Reads .github/configs/{nvidia,amd}-master.yaml + runners.yaml, filters by model-prefix/precision/framework/runner/seq-lens/conc/tp/ep, expands conc-start..conc-end by step-size, chunks multi-node conc lists, marks eval entries. Emits config JSON to stdout. |
| Matrix bucketing | .github/workflows/e2e-tests.yml get-jobs (~L70-83) |
Six inline python3 -c filters split the JSON into single-node / multi-node / eval / multi-node-eval / agentic / multi-node-agentic matrices. This logic lives only in the YAML today. |
| Per-job input fill | benchmark-tmpl.yml / benchmark-multinode-tmpl.yml |
Each matrix entry is unpacked into ~93 job inputs and exported as env vars. |
| Runner + script launch | runners/launch_*.sh |
Parses runner name, allocates cluster (salloc/srun), mounts image + workspaces, invokes the scenario script under benchmarks/single_node/.... |
| Command/config generation | benchmarks/benchmark_lib.sh (build_replay_cmd ~L1210-1348, run_agentic_replay_and_write_outputs ~L1365-1400) + scenario scripts (e.g. benchmarks/single_node/agentic/*.sh) |
Builds the server launch command array and writes sglang_command.txt / vllm_command.txt; builds the full aiperf CLI ($REPLAY_CMD, 40+ flags from env) and writes benchmark_command.txt; then immediately executes the server + benchmark. |
The critical wrinkle: matrix generation is already a clean Python function (trivial to reuse), but script generation and execution are interleaved in bash — the same functions that build $SGLANG_CMD / $REPLAY_CMD also launch the server and run aiperf. So a real "generate but don't run" needs the generation split from the execution.
There is no existing dry-run/preview path in the pipeline (only unrelated --dry-run flags in utils/agentic/sample_proxy_traces.py).
Proposed shape (two layers)
Layer 1 — matrix/config preview (cheap, do first). A CLI (utils/matrix_logic/dry_run.py or make dry-run GEN_CMD="...") that takes the same generate-cli-command, calls generate_sweep_configs.py, and applies the same bucketing to print all six matrices + a summary (models, SKUs, concurrency points, job count, which entries are eval-marked). Requires factoring the six inline python3 -c filters out of e2e-tests.yml into a shared module (e.g. utils/matrix_logic/bucket_configs.py) that both the workflow and the dry run import — so they can't drift.
Layer 2 — script/command preview (the valuable part). Thread a DRY_RUN=1 through benchmark_lib.sh, the scenario scripts, and the launchers so they still build the command arrays and write config.yaml / *_command.txt / mooncake_config.json into an output dir, but skip salloc/srun, the backgrounded server launch, and the aiperf invocation. This makes the generated artifacts inspectable locally without a cluster. Guard the execution lines (server launch, build_replay_cmd → run, run_agentic_replay_and_write_outputs run/tee) behind the flag.
Net: dry-run GEN_CMD="full-sweep --config-files ... --runner-type h200 --precision fp8" writes a tree of exactly-what-would-run scripts and configs, dispatching nothing.
Library research
I surveyed the landscape for tools that could power this (full report + sources below). Conclusion up front: no off-the-shelf tool does what we want end-to-end, and the two hard pieces — matrix expansion and previewing step-generated files — are exactly the parts GitHub keeps server-side and only partial public reimplementations exist. Since our generation logic is already our own Python/bash, building a thin custom harness on top of it is both the most faithful and the least work.
Full local GHA runners
- nektos/act (~71k stars, active) — the incumbent. Important gotcha for us:
act --dryrun/-nwrites each step's shell script to disk but never executes them (issue #1347), so our Python/bash generation never runs and none of the config/command files materialize — it does not give us the preview we want.actin normal mode would run the generation, but needs Docker + Ubuntu containers and has no Slurm/self-hosted-runner fidelity. Not a fit. - wrkflw (Rust, ~3.3k stars, v0.8.0 2026-04) — the real newcomer and closest off-the-shelf fit: its emulation mode runs steps directly on the host with no container and no dispatch, so our generation actually runs and files appear. Worth keeping as a cross-check of our custom harness, not as the primary tool (younger, host-emulation quirks; still runs on your host, not the cluster — which is fine for preview).
- gflows (abandoned), Cirrus CLI (different YAML schema), Dagger (pipeline-as-code rewrite, not a preview), Forgejo/Gitea
act_runner(server-coupled act) — none fit.
Static analysis / linters (all lint-only — none expand matrices or evaluate expressions)
- actionlint (~4k stars, active) — gold standard for YAML correctness: type-checks
${{ }}, runs shellcheck onrun:shell and pyflakes onrun:python, validatesneeds:/matrix refs/runner labels/cron. It's a type-checker, not an evaluator — won't enumerate matrix combos or compute values. Worth adding as an orthogonal CI gate; doesn't power the dry run. - zizmor (security), action-validator (JSON-schema only), check-jsonschema, poutine, ghalint, octoscan — orthogonal/weaker; none help.
Workflow parsers
- @actions/workflow-parser (GitHub official, JS/TS, MIT) — parses + validates and models
workflow_dispatchinputs, but does not evaluate expressions and has no matrix/strategy converter at all. TS-only (needs a Node sidecar from Python). - Python side: nothing usable —
actionlint-pyjust wraps the Go binary;gha/gha-workflow/actions-workflow-parserdon't exist on PyPI. Realistic Python path isruamel.yaml/PyYAML+ our own model (which we already effectively have).
${{ }} expression evaluators (the piece people ask for)
- @actions/expressions (GitHub official, JS/TS, MIT, v0.3.58 2026-06) — a genuinely standalone, maintained evaluator implementing the full grammar (
fromJSON/format/contains/... ;hashFiles/success()/etc. are runner-supplied, you register them). This is the only standalone evaluator worth using — but it's JS/TS. - Python: no GHA-dialect evaluator exists (CEL libs are a different grammar;
simpleevalis Python syntax). We'd have to port or shell out — but we likely don't need it: our workflow expressions are simple and the real logic already lives in Python.
Matrix expansion (include/exclude semantics)
- No published spec-perfect standalone library; the correct implementations are embedded in act (Go, ~80 lines), austenstone/actio (TS, most faithful, unmaintained), and the
wrkflw-matrixcrate (Rust, minor deviations). Moot for us — our matrix isn't a native GHAstrategy.matrix; it's generated bygenerate_sweep_configs.py, so we already own expansion.
GitHub's own tooling
- No first-party dry-run/validate/matrix-preview anywhere:
gh workflow run/viewhave no such flag; the REST/GraphQLdispatchesendpoint fires a real run and only returns HTTP 422 on bad input (validation with the side effect we're avoiding).
Recommendation
- Build Layer 1 + Layer 2 on our own generation code — highest value, no Docker/Node/dispatch, most faithful. Extract the bucketing out of the YAML into a shared module so dry-run and CI share one implementation.
- Optionally run wrkflw (emulation mode) as a sanity cross-check that the workflow steps behave locally.
- Add actionlint in CI as an orthogonal YAML-correctness gate.
- Only reach for @actions/expressions (via a small Node sidecar) if we ever need faithful
${{ }}evaluation — currently unnecessary. - Do not rely on
act --dryrun(writes scripts, never runs them) or wait for a GitHub/ghdry-run (doesn't exist).
Acceptance criteria
- Layer 1:
dry-run GEN_CMD="..."prints all six bucketed matrices + a summary (job count, SKUs, conc points, eval-marked entries) from the samegenerate-cli-commandCI uses. - The six bucketing filters are factored out of
e2e-tests.ymlinto a shared module imported by both the workflow and the dry run (no duplication / drift). - Layer 2: a
DRY_RUNpath throughbenchmark_lib.sh+ scenario scripts + launchers writesconfig.yaml,*_command.txt, andmooncake_config.jsonto an output dir while skipping salloc/srun, server launch, and aiperf execution. - Nothing is dispatched to GitHub and nothing runs on the clusters.
- (Optional) actionlint wired into CI as a lint gate.
Research: two-pass investigation (repo pipeline map + tooling landscape survey, cross-verified July 2026). Key sources: act / act #1347, wrkflw, actionlint, @actions/expressions, @actions/workflow-parser, GHA matrix docs.
中文说明
提议添加本地「试运行」模式,在不触发 GitHub Actions 调度、不分配集群运行器、不启动推理服务或基准测试的前提下,完整复现端到端基准测试流水线的矩阵展开和脚本生成逻辑。分两层实现:第一层是矩阵/配置预览,调用 generate_sweep_configs.py 并应用相同的分桶逻辑,输出六类矩阵及摘要;第二层在 benchmark_lib.sh、场景脚本和启动器中加入 DRY_RUN=1 路径,仅生成 config.yaml、*_command.txt、mooncake_config.json 等产物到输出目录,跳过 salloc/srun、服务启动和 aiperf 执行。调研了 act、wrkflw、actionlint 等工具后,建议基于自有生成代码构建,最忠实且工作量最小。
- Dominant language
- Python
- Stars
- 1.7k
- Forks
- 303
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 284
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from SemiAnalysisAI/InferenceX
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
SemiAnalysisAI/InferenceX#2125 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
SemiAnalysisAI/InferenceX#1587 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 78/100
SemiAnalysisAI/InferenceX#1369 · 3 comments ·
-
Difficulty 1/5 1-3 hours Newbie friendliness 76/100
SemiAnalysisAI/InferenceX#1359 · 1 comment ·
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
SemiAnalysisAI/InferenceX#3122 · 3 comments ·
All issues in SemiAnalysisAI/InferenceX
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
bancolombia/sentinel#23 ·
-
test md OpenCI
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
-
integration:quickjs org:external priority:backlog topic:code-interpreter topic:middleware type:feature
Difficulty 2/5 1-3 hours Newbie friendliness 74/100
langchain-ai/deepagents#6450 ·
-
bug client
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 74/100