Skip to content

Commit d374605

Browse files
committed
fix: dispatch agentic SWE-bench eval rows via a dedicated bucket and sweep job
Since #1947, generate_sweep_configs --evals-only marks the top-concurrency agentic arm of each config as a SWE-bench eval row, but two consumers were never taught about agentic eval rows, so any PR touching a single-node agentic-coding config failed its sweep: 1. Schema: ChangelogMatrixEntry.evals only accepted SingleNodeMatrixEntry (fixed-seq-len), so process_changelog.py's final model_validate rejected the agentic row and check-changelog failed before any GPU work. 2. Dispatch: even with the schema widened, run-sweep.yml's sweep-evals job dispatches every eval row with fixed-seq-len inputs; an agentic row gets empty ISL/OSL/MAX_MODEL_LEN and scenario-type defaults to fixed-seq-len, so the launcher runs the fixed_seq_len benchmark script, which exits on check_env_vars. Fix: route agentic eval rows into a new agentic_evals bucket (mirroring the single_node['agentic'] design) dispatched by a new sweep-agentic-evals job cloned from sweep-agentic's input block plus run-eval/eval-only, so an agentic row can never reach the fixed-seq-len eval dispatch. The agentic model accepts optional run-eval/eval-only carried by eval rows; exclude_none keeps benchmark row output byte-identical. 中文:自 #1947 起,generate_sweep_configs --evals-only 会将每个配置中并发度最高的 agentic 搜索空间条目标记为 SWE-bench 评估行,但下游两个消费方并不认识 agentic 评估行,导致任何涉及单节点 agentic-coding 配置的 PR 扫描失败:其一,schema 层 ChangelogMatrixEntry.evals 仅接受固定序列长度条目,check-changelog 直接报错; 其二,调度层 sweep-evals 以固定序列长度的输入派发评估行,ISL/OSL/MAX_MODEL_LEN 为空且 scenario-type 回落为 fixed-seq-len,启动器因此运行错误的基准测试脚本并 在 check_env_vars 处退出。本修复新增 agentic_evals 独立分桶(与 single_node['agentic'] 设计一致),由新的 sweep-agentic-evals 任务按 sweep-agentic 的输入派发并附加 run-eval/eval-only,使 agentic 评估行不可能进入 固定序列长度的评估调度路径;基准测试行的输出保持逐字节不变。
1 parent 0094291 commit d374605

6 files changed

Lines changed: 291 additions & 3 deletions

File tree

.github/workflows/run-sweep.yml

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,58 @@ jobs:
628628
run-eval: true
629629
eval-only: true
630630

631+
# Agentic (SWE-bench) eval rows carry the agentic input shape, so they are
632+
# dispatched with sweep-agentic's inputs rather than sweep-evals' fixed-seq-len
633+
# inputs (isl/osl/max-model-len, which agentic rows don't have).
634+
sweep-agentic-evals:
635+
needs: [setup, canary-select, canary-sweep]
636+
if: >-
637+
${{
638+
!cancelled() &&
639+
needs.setup.result == 'success' &&
640+
needs.setup.outputs.reuse-enabled != 'true' &&
641+
(needs.canary-sweep.result == 'success' || needs.canary-sweep.result == 'skipped') &&
642+
toJson(fromJson(needs.setup.outputs.search-space-config).agentic_evals) != '[]' &&
643+
toJson(fromJson(needs.setup.outputs.search-space-config).agentic_evals) != 'null'
644+
}}
645+
uses: ./.github/workflows/benchmark-tmpl.yml
646+
name: agentic eval /
647+
strategy:
648+
fail-fast: ${{ contains(github.event.pull_request.labels.*.name, 'full-sweep-fail-fast') || contains(github.event.pull_request.labels.*.name, 'full-sweep-fail-fast-no-canary') }}
649+
matrix:
650+
config: ${{ fromJson(needs.setup.outputs.search-space-config).agentic_evals }}
651+
secrets: inherit
652+
with:
653+
exp-name: ${{ matrix.config.exp-name }}
654+
runner: ${{ matrix.config.runner }}
655+
image: ${{ matrix.config.image }}
656+
model: ${{ matrix.config.model }}
657+
model-prefix: ${{ matrix.config.model-prefix }}
658+
framework: ${{ matrix.config.framework }}
659+
precision: ${{ matrix.config.precision }}
660+
router: ${{ matrix.config.router && toJson(matrix.config.router) || '' }}
661+
kv-p2p-transfer: ${{ matrix.config['kv-p2p-transfer'] || '' }}
662+
tp: ${{ matrix.config.tp }}
663+
pp: ${{ matrix.config.pp }}
664+
dcp-size: ${{ matrix.config.dcp-size }}
665+
pcp-size: ${{ matrix.config.pcp-size }}
666+
ep: ${{ matrix.config.ep }}
667+
dp-attn: ${{ matrix.config.dp-attn }}
668+
conc: ${{ matrix.config.conc }}
669+
kv-offloading: ${{ matrix.config.kv-offloading }}
670+
kv-offload-backend: ${{ matrix.config['kv-offload-backend'].name }}
671+
kv-offload-backend-metadata: ${{ matrix.config['kv-offload-backend'] && toJson(matrix.config['kv-offload-backend']) || '' }}
672+
total-cpu-dram-gb: ${{ matrix.config.total-cpu-dram-gb }}
673+
duration: ${{ matrix.config.duration }}
674+
isl: '0'
675+
osl: '0'
676+
max-model-len: '0'
677+
spec-decoding: ${{ matrix.config.spec-decoding }}
678+
disagg: ${{ 'false' }}
679+
run-eval: true
680+
eval-only: true
681+
scenario-type: agentic-coding
682+
631683
sweep-multi-node-evals:
632684
needs: [setup, canary-select, canary-sweep]
633685
if: >-
@@ -714,8 +766,8 @@ jobs:
714766
result-prefix: "bmk"
715767

716768
collect-evals:
717-
needs: [sweep-evals, sweep-multi-node-evals, setup]
718-
if: ${{ always() && needs.setup.result != 'skipped' && (needs.sweep-evals.result != 'skipped' || needs.sweep-multi-node-evals.result != 'skipped') }}
769+
needs: [sweep-evals, sweep-agentic-evals, sweep-multi-node-evals, setup]
770+
if: ${{ always() && needs.setup.result != 'skipped' && (needs.sweep-evals.result != 'skipped' || needs.sweep-agentic-evals.result != 'skipped' || needs.sweep-multi-node-evals.result != 'skipped') }}
719771
uses: ./.github/workflows/collect-evals.yml
720772
secrets: inherit
721773

utils/matrix_logic/test_validation.py

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
SingleNodeMasterConfigEntry,
1919
MultiNodeMasterConfigEntry,
2020
ChangelogEntry,
21+
ChangelogMatrixEntry,
2122
validate_matrix_entry,
2223
validate_master_config,
2324
validate_runner_config,
@@ -1400,6 +1401,113 @@ def test_scenario_type_must_be_nonempty_and_supported(self, scenario_type):
14001401
})
14011402

14021403

1404+
# =============================================================================
1405+
# Test ChangelogMatrixEntry
1406+
# =============================================================================
1407+
1408+
@pytest.fixture
1409+
def valid_agentic_eval_row():
1410+
"""Agentic SWE-bench eval row as emitted by generate_sweep_configs --evals-only."""
1411+
return {
1412+
"image": "vllm/vllm-openai:nightly-dev-x86_64-cu13.0.1-904e4ec",
1413+
"model": "deepseek-ai/DeepSeek-V4-Pro",
1414+
"model-prefix": "dsv4",
1415+
"precision": "fp4",
1416+
"framework": "vllm",
1417+
"runner": "cluster:b300-nv",
1418+
"tp": 8,
1419+
"pp": 1,
1420+
"dcp-size": 1,
1421+
"pcp-size": 1,
1422+
"ep": 8,
1423+
"dp-attn": True,
1424+
"spec-decoding": "mtp",
1425+
"conc": 224,
1426+
"kv-offloading": "none",
1427+
"router": {"name": "vllm-router", "version": "0.1.14"},
1428+
"total-cpu-dram-gb": 0,
1429+
"duration": 3600,
1430+
"exp-name": "dsv4_tp8_conc224_kvnone_spec-mtp",
1431+
"scenario-type": "agentic-coding",
1432+
"run-eval": True,
1433+
"eval-only": True,
1434+
}
1435+
1436+
1437+
@pytest.fixture
1438+
def valid_changelog_metadata():
1439+
return {
1440+
"base_ref": "base",
1441+
"head_ref": "head",
1442+
"entries": [{
1443+
"config-keys": ["test-config"],
1444+
"description": ["Test entry"],
1445+
"pr-link": "https://github.com/SemiAnalysisAI/InferenceX/pull/1",
1446+
}],
1447+
}
1448+
1449+
1450+
class TestChangelogMatrixEntry:
1451+
"""Tests for the final search-space contract consumed by run-sweep.yml."""
1452+
1453+
def test_agentic_eval_rows_validate_in_agentic_evals(
1454+
self, valid_agentic_eval_row, valid_changelog_metadata,
1455+
):
1456+
entry = ChangelogMatrixEntry.model_validate({
1457+
"agentic_evals": [valid_agentic_eval_row],
1458+
"changelog_metadata": valid_changelog_metadata,
1459+
})
1460+
1461+
assert entry.agentic_evals[0].scenario_type == "agentic-coding"
1462+
assert entry.agentic_evals[0].run_eval is True
1463+
assert entry.agentic_evals[0].eval_only is True
1464+
1465+
def test_evals_bucket_rejects_agentic_rows(
1466+
self, valid_agentic_eval_row, valid_changelog_metadata,
1467+
):
1468+
"""The `evals` bucket is dispatched with fixed-seq-len inputs
1469+
(isl/osl/max-model-len), so agentic rows must never validate there."""
1470+
with pytest.raises(ValueError):
1471+
ChangelogMatrixEntry.model_validate({
1472+
"evals": [valid_agentic_eval_row],
1473+
"changelog_metadata": valid_changelog_metadata,
1474+
})
1475+
1476+
def test_evals_bucket_accepts_fixed_seq_len_rows(
1477+
self, valid_single_node_matrix_entry, valid_changelog_metadata,
1478+
):
1479+
eval_row = {
1480+
**valid_single_node_matrix_entry,
1481+
"run-eval": True,
1482+
"eval-only": True,
1483+
}
1484+
entry = ChangelogMatrixEntry.model_validate({
1485+
"evals": [eval_row],
1486+
"changelog_metadata": valid_changelog_metadata,
1487+
})
1488+
1489+
assert entry.evals[0].run_eval is True
1490+
assert entry.evals[0].eval_only is True
1491+
1492+
def test_agentic_benchmark_rows_dump_without_eval_flags(
1493+
self, valid_agentic_eval_row, valid_changelog_metadata,
1494+
):
1495+
"""Benchmark rows omit run-eval/eval-only, and exclude_none must keep
1496+
them out of the dump so the dispatched row shape is unchanged."""
1497+
benchmark_row = {
1498+
k: v for k, v in valid_agentic_eval_row.items()
1499+
if k not in ("run-eval", "eval-only")
1500+
}
1501+
entry = ChangelogMatrixEntry.model_validate({
1502+
"single_node": {"agentic": [benchmark_row]},
1503+
"changelog_metadata": valid_changelog_metadata,
1504+
})
1505+
dumped = entry.model_dump(by_alias=True, exclude_none=True)
1506+
1507+
assert "run-eval" not in dumped["single_node"]["agentic"][0]
1508+
assert "eval-only" not in dumped["single_node"]["agentic"][0]
1509+
1510+
14031511
# =============================================================================
14041512
# Test load_config_files
14051513
# =============================================================================

utils/matrix_logic/validation.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,10 @@ class SingleNodeAgenticMatrixEntry(BaseModel):
291291
duration: int = Field(alias=Fields.DURATION.value)
292292
exp_name: str = Field(alias=Fields.EXP_NAME.value)
293293
scenario_type: str = Field(alias=Fields.SCENARIO_TYPE.value)
294+
# Agentic eval rows (SWE-bench) carry run-eval/eval-only; benchmark rows
295+
# omit them, and exclude_none keeps them out of dumped benchmark output.
296+
run_eval: Optional[bool] = Field(default=None, alias=Fields.RUN_EVAL.value)
297+
eval_only: Optional[bool] = Field(default=None, alias=Fields.EVAL_ONLY.value)
294298

295299
@model_validator(mode='after')
296300
def validate_kv_offload_fields(self):
@@ -890,6 +894,13 @@ class ChangelogMatrixEntry(BaseModel):
890894
multi_node: dict[str, list[Union[MultiNodeMatrixEntry, MultiNodeAgenticMatrixEntry]]
891895
] = Field(default_factory=dict)
892896
evals: list[SingleNodeMatrixEntry] = Field(default_factory=list)
897+
# Agentic (SWE-bench) eval rows live in their own bucket rather than a
898+
# union inside `evals`: each bucket maps 1:1 to a run-sweep.yml job with a
899+
# static input block, so an agentic row can never reach the fixed-seq-len
900+
# eval dispatch (which reads isl/osl/max-model-len and would launch the
901+
# wrong benchmark script).
902+
agentic_evals: list[SingleNodeAgenticMatrixEntry] = Field(
903+
default_factory=list)
893904
multinode_evals: list[MultiNodeMatrixEntry] = Field(default_factory=list)
894905
changelog_metadata: ChangelogMetadata
895906

utils/process_changelog.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ def main():
137137
"single_node": defaultdict(list),
138138
"multi_node": defaultdict(list),
139139
"evals": [],
140+
"agentic_evals": [],
140141
"multinode_evals": [],
141142
"changelog_metadata": {
142143
"base_ref": args.base_ref,
@@ -271,7 +272,18 @@ def main():
271272
seq_len_str = seq_len_to_str(result["isl"], result["osl"])
272273
final_results["single_node"][seq_len_str].append(result)
273274

274-
final_results["evals"] = [e for e in all_eval_results if e.get("prefill") is None]
275+
# Agentic eval rows go to their own bucket so run-sweep.yml can dispatch
276+
# them with agentic inputs (scenario-type, kv-offloading, ...) instead of
277+
# the fixed-seq-len inputs (isl/osl/max-model-len) they don't have.
278+
single_node_evals = [e for e in all_eval_results if e.get("prefill") is None]
279+
final_results["evals"] = [
280+
e for e in single_node_evals
281+
if e.get("scenario-type") != "agentic-coding"
282+
]
283+
final_results["agentic_evals"] = [
284+
e for e in single_node_evals
285+
if e.get("scenario-type") == "agentic-coding"
286+
]
275287
final_results["multinode_evals"] = [e for e in all_eval_results if e.get("prefill") is not None]
276288

277289
# Validate final results structure

utils/recover_failed_ingest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,7 @@ def build_config(
492492
)
493493
+ len(config.get("multi_node", {}).get("agentic", []) or []),
494494
"eval_jobs": len(config.get("evals", []) or [])
495+
+ len(config.get("agentic_evals", []) or [])
495496
+ len(config.get("multinode_evals", []) or []),
496497
}
497498

utils/test_process_changelog.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,3 +583,107 @@ def fake_run(command, **kwargs):
583583
assert "--all-evals" not in commands[2]
584584
assert _scenario_values(commands[2]) == ["fixed-seq-len"]
585585
json.loads(capsys.readouterr().out)
586+
587+
588+
def test_eval_rows_split_into_fixed_and_agentic_buckets(
589+
monkeypatch,
590+
capsys,
591+
):
592+
"""Realistic eval rows must pass final validation and land in the bucket
593+
matching their dispatch job: fixed-seq-len rows in `evals`, agentic
594+
(SWE-bench) rows in `agentic_evals`."""
595+
added_yaml = """
596+
- config-keys:
597+
- test-config
598+
description:
599+
- Mixed fixed-seq-len and agentic eval selection
600+
pr-link: https://github.com/SemiAnalysisAI/InferenceX/pull/1
601+
"""
602+
603+
fixed_eval_row = {
604+
"image": "vllm/vllm-openai:v0.11.0",
605+
"model": "deepseek-ai/DeepSeek-V4-Pro",
606+
"model-prefix": "dsv4",
607+
"precision": "fp4",
608+
"framework": "vllm",
609+
"spec-decoding": "mtp",
610+
"runner": "cluster:b300-nv",
611+
"isl": 8192,
612+
"osl": 1024,
613+
"tp": 8,
614+
"pp": 1,
615+
"dcp-size": 1,
616+
"pcp-size": 1,
617+
"ep": 8,
618+
"dp-attn": True,
619+
"conc": 224,
620+
"max-model-len": 10240,
621+
"exp-name": "dsv4_8k1k_fixed_eval",
622+
"disagg": False,
623+
"run-eval": True,
624+
"eval-only": True,
625+
}
626+
agentic_eval_row = {
627+
"image": "vllm/vllm-openai:v0.11.0",
628+
"model": "deepseek-ai/DeepSeek-V4-Pro",
629+
"model-prefix": "dsv4",
630+
"precision": "fp4",
631+
"framework": "vllm",
632+
"runner": "cluster:b300-nv",
633+
"tp": 8,
634+
"pp": 1,
635+
"dcp-size": 1,
636+
"pcp-size": 1,
637+
"ep": 8,
638+
"dp-attn": True,
639+
"spec-decoding": "mtp",
640+
"conc": 224,
641+
"kv-offloading": "none",
642+
"router": {"name": "vllm-router", "version": "0.1.14"},
643+
"total-cpu-dram-gb": 0,
644+
"duration": 3600,
645+
"exp-name": "dsv4_agentic_eval",
646+
"scenario-type": "agentic-coding",
647+
"run-eval": True,
648+
"eval-only": True,
649+
}
650+
651+
monkeypatch.setattr(
652+
process_changelog,
653+
"get_added_lines",
654+
lambda *_: added_yaml,
655+
)
656+
monkeypatch.setattr(
657+
process_changelog,
658+
"load_config_files",
659+
lambda _: {"test-config": {}},
660+
)
661+
662+
def fake_run(command, **kwargs):
663+
if "--evals-only" in command:
664+
stdout = json.dumps([fixed_eval_row, agentic_eval_row])
665+
else:
666+
stdout = "[]"
667+
return SimpleNamespace(stdout=stdout)
668+
669+
monkeypatch.setattr(subprocess, "run", fake_run)
670+
monkeypatch.setattr(sys, "argv", [
671+
"process_changelog.py",
672+
"--base-ref", "base",
673+
"--head-ref", "head",
674+
"--changelog-file", "perf-changelog.yaml",
675+
])
676+
677+
process_changelog.main()
678+
679+
output = json.loads(capsys.readouterr().out)
680+
assert [row["exp-name"] for row in output["evals"]] == [
681+
"dsv4_8k1k_fixed_eval"
682+
]
683+
assert [row["exp-name"] for row in output["agentic_evals"]] == [
684+
"dsv4_agentic_eval"
685+
]
686+
assert output["multinode_evals"] == []
687+
assert output["agentic_evals"][0]["scenario-type"] == "agentic-coding"
688+
assert output["agentic_evals"][0]["run-eval"] is True
689+
assert output["agentic_evals"][0]["eval-only"] is True

0 commit comments

Comments
 (0)