Skip to content

Commit da54b42

Browse files
authored
[codex] support optional heterogeneous hardware metadata (#2100)
* feat: add heterogeneous disaggregated hardware metadata * fix: make heterogeneous hardware metadata optional * fix: restrict disaggregation to multinode configs * refactor: colocate hardware with worker configs * fix: surface heterogeneous hardware in agentic results
1 parent 4699ab8 commit da54b42

11 files changed

Lines changed: 271 additions & 8 deletions

.github/workflows/benchmark-multinode-tmpl.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ on:
3939
disagg:
4040
required: true
4141
type: string
42+
prefill-hardware:
43+
description: "GPU SKU used by prefill workers"
44+
required: false
45+
type: string
46+
default: ""
47+
decode-hardware:
48+
description: "GPU SKU used by decode workers"
49+
required: false
50+
type: string
51+
default: ""
4252

4353
max-model-len:
4454
required: true
@@ -144,6 +154,8 @@ env:
144154
CONC_LIST: ${{ join(fromJson(inputs.conc-list), ' ') }}
145155
SPEC_DECODING: ${{ inputs.spec-decoding }}
146156
DISAGG: ${{ inputs.disagg }}
157+
PREFILL_HARDWARE: ${{ inputs.prefill-hardware }}
158+
DECODE_HARDWARE: ${{ inputs.decode-hardware }}
147159
RUN_EVAL: ${{ inputs.run-eval }}
148160
EVAL_ONLY: ${{ inputs.eval-only }}
149161
EVAL_CONC: ${{ inputs.eval-conc }}

.github/workflows/e2e-tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ jobs:
106106
conc-list: ${{ toJson(matrix.config.conc) }}
107107
spec-decoding: ${{ matrix.config.spec-decoding }}
108108
disagg: ${{ matrix.config.disagg }}
109+
prefill-hardware: ${{ matrix.config.prefill.hardware }}
110+
decode-hardware: ${{ matrix.config.decode.hardware }}
109111

110112
prefill-num-worker: ${{ matrix.config.prefill.num-worker }}
111113
prefill-tp: ${{ matrix.config.prefill.tp }}
@@ -145,6 +147,8 @@ jobs:
145147
conc-list: ${{ toJson(matrix.config.conc) }}
146148
spec-decoding: ${{ matrix.config.spec-decoding }}
147149
disagg: ${{ matrix.config.disagg }}
150+
prefill-hardware: ${{ matrix.config.prefill.hardware }}
151+
decode-hardware: ${{ matrix.config.decode.hardware }}
148152

149153
prefill-num-worker: ${{ matrix.config.prefill.num-worker }}
150154
prefill-tp: ${{ matrix.config.prefill.tp }}
@@ -221,6 +225,8 @@ jobs:
221225
conc-list: ${{ toJson(matrix.config.conc) }}
222226
spec-decoding: ${{ matrix.config.spec-decoding }}
223227
disagg: ${{ matrix.config.disagg }}
228+
prefill-hardware: ${{ matrix.config.prefill.hardware }}
229+
decode-hardware: ${{ matrix.config.decode.hardware }}
224230
prefill-num-worker: ${{ matrix.config.prefill.num-worker }}
225231
prefill-tp: ${{ matrix.config.prefill.tp }}
226232
prefill-ep: ${{ matrix.config.prefill.ep }}

.github/workflows/run-sweep.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,8 @@ jobs:
369369
conc-list: ${{ toJson(matrix.config.conc) }}
370370
spec-decoding: ${{ matrix.config.spec-decoding }}
371371
disagg: ${{ matrix.config.disagg }}
372+
prefill-hardware: ${{ matrix.config.prefill.hardware }}
373+
decode-hardware: ${{ matrix.config.decode.hardware }}
372374

373375
prefill-num-worker: ${{ matrix.config.prefill.num-worker }}
374376
prefill-tp: ${{ matrix.config.prefill.tp }}
@@ -531,6 +533,8 @@ jobs:
531533
conc-list: '[${{ matrix.config.conc }}]'
532534
spec-decoding: ${{ matrix.config.spec-decoding }}
533535
disagg: ${{ matrix.config.disagg }}
536+
prefill-hardware: ${{ matrix.config.prefill.hardware }}
537+
decode-hardware: ${{ matrix.config.decode.hardware }}
534538
prefill-num-worker: ${{ matrix.config.prefill.num-worker }}
535539
prefill-tp: ${{ matrix.config.prefill.tp }}
536540
prefill-ep: ${{ matrix.config.prefill.ep }}
@@ -618,6 +622,8 @@ jobs:
618622
conc-list: ${{ toJson(matrix.config.conc) }}
619623
spec-decoding: ${{ matrix.config.spec-decoding }}
620624
disagg: ${{ matrix.config.disagg }}
625+
prefill-hardware: ${{ matrix.config.prefill.hardware }}
626+
decode-hardware: ${{ matrix.config.decode.hardware }}
621627
prefill-num-worker: ${{ matrix.config.prefill.num-worker }}
622628
prefill-tp: ${{ matrix.config.prefill.tp }}
623629
prefill-ep: ${{ matrix.config.prefill.ep }}

configs/CONFIGS.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,33 @@ entry-name:
2828
- { tp: int, conc-start: int, conc-end: int }
2929
- ...
3030
```
31+
32+
Heterogeneous disaggregated search-space entries declare hardware on each
33+
worker pool. Omit both `hardware` fields for homogeneous hardware:
34+
35+
```yaml
36+
multinode: true
37+
disagg: true
38+
scenarios:
39+
fixed-seq-len:
40+
- isl: 1024
41+
osl: 1024
42+
search-space:
43+
- conc-list: [64]
44+
prefill:
45+
hardware: b200
46+
num-worker: 1
47+
tp: 8
48+
ep: 8
49+
dp-attn: false
50+
decode:
51+
hardware: h100
52+
num-worker: 2
53+
tp: 8
54+
ep: 8
55+
dp-attn: false
56+
```
57+
3158
Note: while not required, `entry-name` typically takes the format `<INFMAX_MODEL_PREFIX>-<PRECISION>-<GPU>-<FRAMEWORK>`.
3259

3360
The below list describes what each field is:
@@ -41,6 +68,13 @@ The below list describes what each field is:
4168
fleet.
4269
- `precision`: The precision to run the benchmark. Again, this is used to find which script to run in `benchmarks/`.
4370
- `framework`: The framework (serving runtime) to serve the benchmark, e.g., `vllm`, `sglang`, `trt`.
71+
- `disagg`: Enables disaggregated serving and may only be `true` when
72+
`multinode` is also `true`.
73+
- `hardware`: Optional metadata within each `prefill` and `decode` worker block
74+
for heterogeneous disaggregated deployments. If one worker declares a GPU
75+
SKU, the other must also declare one. Omit both fields for homogeneous
76+
hardware. These values flow into aggregate results but do not affect runner
77+
scheduling.
4478
- `scenarios`: A dictionary of benchmark scenario types. At least one must be specified. Currently supported:
4579
- `fixed-seq-len`: Fixed input/output sequence length benchmarks. Each entry must have:
4680
- `isl`: An integer representing the input sequence length, e.g., `1024`

utils/agentic/aggregation/process_agentic_result.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ def _gpu_shape() -> tuple[dict[str, Any], int, int, int, str]:
6767
decode_tp = env_int("DECODE_TP")
6868
decode_ep = env_int("DECODE_EP", 1)
6969
decode_dp_attention = os.environ.get("DECODE_DP_ATTN", "false")
70+
prefill_hardware = os.environ.get("PREFILL_HARDWARE", "")
71+
decode_hardware = os.environ.get("DECODE_HARDWARE", "")
72+
if bool(prefill_hardware) != bool(decode_hardware):
73+
raise SystemExit(
74+
"PREFILL_HARDWARE and DECODE_HARDWARE must be specified together."
75+
)
7076
num_prefill_gpu = prefill_num_workers * prefill_tp
7177
num_decode_gpu = decode_num_workers * decode_tp
7278
num_gpus = num_prefill_gpu + num_decode_gpu
@@ -91,6 +97,9 @@ def _gpu_shape() -> tuple[dict[str, Any], int, int, int, str]:
9197
"num_decode_gpu": num_decode_gpu,
9298
}
9399
)
100+
if prefill_hardware:
101+
fields["prefill_hw"] = prefill_hardware
102+
fields["decode_hw"] = decode_hardware
94103
return fields, num_gpus, tp, ep, dp_attention
95104

96105

utils/agentic/aggregation/test_process_agentic_result.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
load_aggregate,
2727
load_records,
2828
)
29+
from utils.agentic.aggregation.process_agentic_result import _gpu_shape
2930
from utils.agentic.aggregation.server_metrics import (
3031
compute_server_metrics,
3132
load_server_metrics,
@@ -308,6 +309,8 @@ def _run_processor(
308309
env_overrides: dict[str, str] | None = None,
309310
) -> dict:
310311
env = os.environ.copy()
312+
env.pop("PREFILL_HARDWARE", None)
313+
env.pop("DECODE_HARDWARE", None)
311314
env.update(
312315
{
313316
"RESULT_DIR": str(result_dir),
@@ -453,6 +456,70 @@ def test_processor_surfaces_allocated_cpu_dram(tmp_path: Path):
453456
assert agg["allocated_cpu_dram_gb"] == 2400
454457

455458

459+
def test_multinode_processor_surfaces_heterogeneous_hardware(tmp_path: Path):
460+
result_dir = _write_fixture(tmp_path)
461+
agg = _run_processor(
462+
result_dir,
463+
tmp_path / "out",
464+
env_overrides={
465+
"IS_MULTINODE": "true",
466+
"DISAGG": "true",
467+
"PREFILL_NUM_WORKERS": "1",
468+
"PREFILL_TP": "8",
469+
"PREFILL_EP": "8",
470+
"PREFILL_DP_ATTN": "false",
471+
"PREFILL_HARDWARE": "b200",
472+
"DECODE_NUM_WORKERS": "2",
473+
"DECODE_TP": "8",
474+
"DECODE_EP": "8",
475+
"DECODE_DP_ATTN": "false",
476+
"DECODE_HARDWARE": "h100",
477+
},
478+
)
479+
480+
assert agg["prefill_hw"] == "b200"
481+
assert agg["decode_hw"] == "h100"
482+
483+
484+
def test_multinode_processor_omits_homogeneous_hardware(tmp_path: Path):
485+
result_dir = _write_fixture(tmp_path)
486+
agg = _run_processor(
487+
result_dir,
488+
tmp_path / "out",
489+
env_overrides={
490+
"IS_MULTINODE": "true",
491+
"DISAGG": "true",
492+
"PREFILL_NUM_WORKERS": "1",
493+
"PREFILL_TP": "8",
494+
"DECODE_NUM_WORKERS": "2",
495+
"DECODE_TP": "8",
496+
},
497+
)
498+
499+
assert "prefill_hw" not in agg
500+
assert "decode_hw" not in agg
501+
502+
503+
@pytest.mark.parametrize(
504+
("present_var", "missing_var"),
505+
[
506+
("PREFILL_HARDWARE", "DECODE_HARDWARE"),
507+
("DECODE_HARDWARE", "PREFILL_HARDWARE"),
508+
],
509+
)
510+
def test_multinode_processor_rejects_one_sided_hardware(
511+
monkeypatch: pytest.MonkeyPatch,
512+
present_var: str,
513+
missing_var: str,
514+
):
515+
monkeypatch.setenv("IS_MULTINODE", "true")
516+
monkeypatch.setenv(present_var, "b200")
517+
monkeypatch.delenv(missing_var, raising=False)
518+
519+
with pytest.raises(SystemExit, match="must be specified together"):
520+
_gpu_shape()
521+
522+
456523
def test_processor_surfaces_request_accounting(tmp_path: Path):
457524
result_dir = tmp_path / "results"
458525
artifact = result_dir / "aiperf_artifacts"

utils/matrix_logic/test_generate_sweep_configs.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def sample_multinode_config():
7878
{
7979
"conc-list": [2150],
8080
"prefill": {
81+
"hardware": "gb200",
8182
"num-worker": 5,
8283
"tp": 4,
8384
"ep": 4,
@@ -88,6 +89,7 @@ def sample_multinode_config():
8889
],
8990
},
9091
"decode": {
92+
"hardware": "h100",
9193
"num-worker": 1,
9294
"tp": 8,
9395
"ep": 8,
@@ -981,6 +983,8 @@ def test_multinode_entry_structure(self, sample_multinode_config, sample_runner_
981983
assert entry["prefill"]["num-worker"] == 5
982984
assert entry["decode"]["num-worker"] == 1
983985
assert entry["disagg"] is True
986+
assert entry["prefill"]["hardware"] == "gb200"
987+
assert entry["decode"]["hardware"] == "h100"
984988

985989
def test_multinode_conc_as_list(self, sample_multinode_config, sample_runner_config, full_sweep_args_multi_node):
986990
"""Multinode conc should be passed as list."""
@@ -2004,8 +2008,8 @@ def test_multinode_agentic_groups_concurrencies_per_search_entry(self):
20042008
"search-space": [
20052009
{
20062010
"conc-list": [16, 32, 64, 128, 256],
2007-
"prefill": {"num-worker": 2, "tp": 8, "ep": 8, "dp-attn": False},
2008-
"decode": {"num-worker": 1, "tp": 8, "ep": 1, "dp-attn": False},
2011+
"prefill": {"hardware": "gb200", "num-worker": 2, "tp": 8, "ep": 8, "dp-attn": False},
2012+
"decode": {"hardware": "h100", "num-worker": 1, "tp": 8, "ep": 1, "dp-attn": False},
20092013
}
20102014
],
20112015
}
@@ -2151,8 +2155,8 @@ def test_node_type_filters_apply_to_agentic_configs(
21512155
"search-space": [
21522156
{
21532157
"conc-list": [16, 32],
2154-
"prefill": {"num-worker": 2, "tp": 8, "ep": 8, "dp-attn": False},
2155-
"decode": {"num-worker": 1, "tp": 8, "ep": 1, "dp-attn": False},
2158+
"prefill": {"hardware": "gb200", "num-worker": 2, "tp": 8, "ep": 8, "dp-attn": False},
2159+
"decode": {"hardware": "h100", "num-worker": 1, "tp": 8, "ep": 1, "dp-attn": False},
21562160
},
21572161
],
21582162
}],

0 commit comments

Comments
 (0)