Skip to content

Commit 88eb946

Browse files
[ROCm][CI] Stabilize MI355 FusedMoE test group (#53025)
Signed-off-by: Andreas Karatzas <akaratza@amd.com>
1 parent c0ff334 commit 88eb946

2 files changed

Lines changed: 174 additions & 70 deletions

File tree

.buildkite/test_areas/kernels.yaml

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -468,56 +468,3 @@ steps:
468468
commands:
469469
- pytest -v -s kernels/moe/test_moe_layer.py
470470
- pytest -v -s kernels/moe/test_deepep_v2_moe.py
471-
472-
- label: Kernels FusedMoE Layer Test (2xMI355)
473-
key: kernels-fusedmoe-layer-test-2-mi355
474-
depends_on:
475-
- image-build-amd
476-
timeout_in_minutes: 180
477-
dind: false
478-
device: mi355_2
479-
soft_fail: true
480-
optional: true
481-
working_dir: "/vllm-workspace/tests"
482-
source_file_dependencies:
483-
- csrc/moe/
484-
- csrc/rocm/
485-
- tests/kernels/moe
486-
- vllm/model_executor/layers/fused_moe/
487-
- vllm/model_executor/layers/quantization/
488-
- vllm/distributed/
489-
- vllm/config/
490-
- vllm/forward_context.py
491-
- vllm/v1/worker/workspace.py
492-
- vllm/utils/import_utils.py
493-
- vllm/utils/math_utils.py
494-
- vllm/utils/torch_utils.py
495-
- vllm/platforms/
496-
- vllm/_aiter_ops.py
497-
commands:
498-
- pytest -v -s kernels/moe/test_moe_layer.py
499-
500-
- label: Kernels FP8 MoE Test (MI355)
501-
key: kernels-fp8-moe-test-mi355
502-
depends_on:
503-
- image-build-amd
504-
timeout_in_minutes: 180
505-
dind: false
506-
device: mi355_1
507-
soft_fail: true
508-
optional: true
509-
working_dir: "/vllm-workspace/tests"
510-
source_file_dependencies:
511-
- csrc/moe/
512-
- vllm/model_executor/layers/fused_moe/
513-
- tests/kernels/moe/test_deepep_moe.py
514-
- vllm/_aiter_ops.py
515-
- vllm/platforms/rocm.py
516-
- vllm/envs.py
517-
commands:
518-
- pytest -v -s kernels/moe/test_gpt_oss_triton_kernels.py
519-
- pytest -v -s kernels/moe/test_modular_oai_triton_moe.py
520-
- pytest -v -s kernels/moe/test_moe.py
521-
- pytest -v -s kernels/moe/test_block_int8.py
522-
- pytest -v -s kernels/moe/test_triton_moe_no_act_mul.py
523-
- pytest -v -s kernels/moe/test_triton_moe_ptpc_fp8.py

tests/kernels/moe/test_moe_layer.py

Lines changed: 174 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77

88
import functools
99
import os
10+
import sys
1011
import tempfile
1112
import traceback
1213
import types
1314
from collections.abc import Callable
1415
from contextlib import suppress
15-
from dataclasses import astuple, dataclass, fields
16+
from dataclasses import astuple, dataclass, fields, replace
1617
from itertools import product
1718
from typing import get_args
1819

@@ -28,6 +29,7 @@
2829
from tests.kernels.moe.utils import TestMLP, make_test_weights, moe_quantize_weights
2930
from vllm.config import (
3031
CompilationConfig,
32+
EPLBConfig,
3133
ParallelConfig,
3234
SchedulerConfig,
3335
VllmConfig,
@@ -383,6 +385,56 @@ def convert(v: str, ty):
383385
return MoETestConfig(*values)
384386

385387

388+
def _group_deepep_ll_configs(
389+
test_configs: list[MoETestConfig],
390+
) -> list[list[MoETestConfig]]:
391+
"""Group configs that can share one DeepEP low-latency buffer."""
392+
groups: dict[tuple[int, int, int], list[MoETestConfig]] = {}
393+
for test_config in test_configs:
394+
# max_num_tokens and the remaining buffer arguments are fixed for one
395+
# outer pytest item. These are the arguments that can vary here.
396+
buffer_key = (
397+
test_config.k,
398+
test_config.ep_size,
399+
test_config.num_experts,
400+
)
401+
groups.setdefault(buffer_key, []).append(test_config)
402+
return list(groups.values())
403+
404+
405+
@pytest.mark.cpu_test
406+
def test_group_deepep_ll_configs_by_buffer_requirements():
407+
config_a = MoETestConfig(
408+
1,
409+
128,
410+
2048,
411+
8,
412+
2,
413+
torch.bfloat16,
414+
None,
415+
False,
416+
False,
417+
False,
418+
backend="deepep_low_latency",
419+
ep_size=2,
420+
dp_size=2,
421+
)
422+
config_b = replace(config_a, num_experts=64)
423+
config_a_later = replace(config_a, m=32)
424+
config_c = replace(config_a, k=4096)
425+
config_d = replace(config_a, ep_size=4, dp_size=4)
426+
427+
assert _group_deepep_ll_configs(
428+
[config_a, config_b, config_a_later, config_c, config_d]
429+
) == [
430+
[config_a, config_a_later],
431+
[config_b],
432+
[config_c],
433+
[config_d],
434+
]
435+
assert _group_deepep_ll_configs([]) == []
436+
437+
386438
def generate_valid_test_configs(
387439
backend: str,
388440
ep_size: int,
@@ -520,6 +572,17 @@ def is_valid_config(config: MoETestConfig) -> tuple[bool, str | None]:
520572
f"{config.backend} does not support quantization={config.quantization}",
521573
)
522574

575+
if (
576+
on_gfx950()
577+
and config.backend == "deepep_low_latency"
578+
and config.quantization == "modelopt_fp4"
579+
):
580+
return (
581+
False,
582+
"DeepEP low latency requires a batched NVFP4 MoE backend, "
583+
"which is not available on gfx950.",
584+
)
585+
523586
if config.backend in MORI_BACKENDS:
524587
if os.environ.get("VLLM_TEST_ENABLE_MORI_MOE_LAYER") != "1":
525588
return False, "mori MoE layer matrix is opt-in"
@@ -1719,6 +1782,7 @@ def _parallel_worker(
17191782
test_configs: list[MoETestConfig],
17201783
verbosity: int,
17211784
failure_report_path: str | None = None,
1785+
deep_ep_handle_keepalive: list[object] | None = None,
17221786
**kwargs,
17231787
) -> None:
17241788
set_random_seed(7)
@@ -1776,15 +1840,30 @@ def _parallel_worker(
17761840
finally:
17771841
# DeepEP managers are not reliably reusable across many subtests in
17781842
# a single worker process. Tear them down after each DeepEP case so
1779-
# later subtests do not inherit stale communication state.
1780-
if test_config.backend in {
1781-
"deepep_low_latency",
1782-
"deepep_high_throughput",
1783-
}:
1843+
# later subtests do not inherit stale communication state. Skip this
1844+
# on ROCm: rocSHMEM cannot reinitialize the allocator after a DeepEP
1845+
# buffer is destroyed in the same process.
1846+
if current_platform.is_cuda() and test_config.backend in DEEPEP_BACKENDS:
17841847
torch.accelerator.synchronize()
17851848
all2all_manager = get_ep_group().device_communicator.all2all_manager
17861849
if all2all_manager is not None:
17871850
all2all_manager.destroy()
1851+
elif (
1852+
deep_ep_handle_keepalive is not None
1853+
and current_platform.is_rocm()
1854+
and test_config.backend in DEEPEP_BACKENDS
1855+
):
1856+
# The manager cache is weak. Keep its handle alive until the
1857+
# launcher hard-exits this ROCm worker, otherwise each subtest
1858+
# implicitly destroys and recreates the DeepEP buffer.
1859+
all2all_manager = get_ep_group().device_communicator.all2all_manager
1860+
if all2all_manager is not None:
1861+
handle_cache = getattr(all2all_manager, "handle_cache", None)
1862+
if handle_cache is not None and not deep_ep_handle_keepalive:
1863+
with handle_cache._lock:
1864+
cached_handles = list(handle_cache._cache.values())
1865+
if cached_handles:
1866+
deep_ep_handle_keepalive.extend(cached_handles)
17881867
total = total + 1
17891868
torch.distributed.barrier()
17901869

@@ -1845,12 +1924,60 @@ def _parallel_worker(
18451924
f"{failure_details_str}\n{report}"
18461925
)
18471926
if is_logging_rank and failure_report_path is not None:
1848-
with open(failure_report_path, "w", encoding="utf-8") as report_file:
1927+
with open(failure_report_path, "a", encoding="utf-8") as report_file:
18491928
report_file.write(failure_report)
18501929
if is_logging_rank:
18511930
raise RuntimeError(failure_report)
18521931

18531932

1933+
def _parallel_worker_rocm_deepep(
1934+
pgi: ProcessGroupInfo,
1935+
vllm_config: VllmConfig,
1936+
cpu_group,
1937+
test_configs: list[MoETestConfig],
1938+
verbosity: int,
1939+
failure_report_path: str | None = None,
1940+
deep_ep_handle_keepalive: list[object] | None = None,
1941+
**kwargs,
1942+
) -> None:
1943+
"""Run a ROCm DeepEP batch without unsafe Python/HIP teardown."""
1944+
assert current_platform.is_rocm()
1945+
assert deep_ep_handle_keepalive is not None
1946+
1947+
exit_code = 1
1948+
try:
1949+
_parallel_worker(
1950+
pgi,
1951+
vllm_config,
1952+
cpu_group,
1953+
test_configs,
1954+
verbosity,
1955+
failure_report_path=failure_report_path,
1956+
deep_ep_handle_keepalive=deep_ep_handle_keepalive,
1957+
**kwargs,
1958+
)
1959+
exit_code = 0
1960+
except BaseException as ex:
1961+
print(ex)
1962+
traceback.print_exc()
1963+
finally:
1964+
try:
1965+
torch.accelerator.synchronize()
1966+
# Do not run vLLM cleanup: it explicitly destroys the DeepEP
1967+
# buffer. Destroy only the default group, matching the accepted
1968+
# ROCm workaround in tests/kernels/moe/parallel_utils.py.
1969+
torch.distributed.destroy_process_group()
1970+
except BaseException:
1971+
traceback.print_exc()
1972+
exit_code = 1
1973+
finally:
1974+
# Bypass the HIP atexit use-after-free fixed upstream by
1975+
# https://github.com/ROCm/rocm-systems/pull/6942.
1976+
sys.stdout.flush()
1977+
sys.stderr.flush()
1978+
os._exit(exit_code)
1979+
1980+
18541981
# TODO: add cudagraphs/torch.compile tests
18551982
@pytest.mark.parametrize("dp_size, tp_size, use_ep", PARALLEL_COMBOS)
18561983
@pytest.mark.parametrize("backend", BACKENDS)
@@ -1909,13 +2036,20 @@ def test_moe_layer(
19092036
# moe_backend=flashinfer_trtllm / flashinfer_cutlass / flashinfer_cutedsl
19102037
# (BF16, FP8 and NVFP4 paths), and VLLM_USE_FLASHINFER_MOE_INT4=1.
19112038

2039+
# Repeated NIXL memory registration in this broad layer matrix fails on
2040+
# gfx950. NIXL EPLB is covered by dedicated tests, so use Gloo here to
2041+
# preserve the layer/EPLB coverage.
2042+
eplb_config = EPLBConfig(
2043+
communicator="torch_gloo" if enable_eplb and on_gfx950() else None
2044+
)
19122045
parallel_config = ParallelConfig(
19132046
pipeline_parallel_size=1,
19142047
data_parallel_size=dp_size,
19152048
tensor_parallel_size=tp_size,
19162049
enable_expert_parallel=use_ep,
19172050
all2all_backend=backend,
19182051
enable_eplb=enable_eplb,
2052+
eplb_config=eplb_config,
19192053
)
19202054

19212055
compilation_config = CompilationConfig()
@@ -1955,19 +2089,42 @@ def test_moe_layer(
19552089
) as failure_report_file:
19562090
failure_report_path = failure_report_file.name
19572091

2092+
test_config_batches = [test_configs]
2093+
if current_platform.is_rocm() and backend == "deepep_low_latency":
2094+
# rocSHMEM cannot destroy one low-latency buffer and initialize a
2095+
# differently sized one in the same process. Use one worker lifetime
2096+
# per compatible buffer shape while preserving every matrix case.
2097+
test_config_batches = _group_deepep_ll_configs(test_configs)
2098+
2099+
rocm_deepep = current_platform.is_rocm() and backend in DEEPEP_BACKENDS
2100+
parallel_worker = _parallel_worker_rocm_deepep if rocm_deepep else _parallel_worker
2101+
launch_failures: list[str] = []
2102+
19582103
try:
1959-
parallel_launch_with_config(
1960-
world_size,
1961-
_parallel_worker,
1962-
vllm_config,
1963-
None,
1964-
test_configs,
1965-
verbosity,
1966-
failure_report_path=failure_report_path,
1967-
)
2104+
for test_config_batch in test_config_batches:
2105+
report_size_before = os.path.getsize(failure_report_path)
2106+
try:
2107+
parallel_launch_with_config(
2108+
world_size,
2109+
parallel_worker,
2110+
vllm_config,
2111+
None,
2112+
test_config_batch,
2113+
verbosity,
2114+
failure_report_path=failure_report_path,
2115+
deep_ep_handle_keepalive=[] if rocm_deepep else None,
2116+
)
2117+
except Exception as ex:
2118+
# Normal subtest failures are already in the shared report.
2119+
# Preserve launcher/setup errors that occur before that write.
2120+
if os.path.getsize(failure_report_path) == report_size_before:
2121+
launch_failures.append(str(ex))
2122+
19682123
if os.path.getsize(failure_report_path) > 0:
19692124
with open(failure_report_path, encoding="utf-8") as report_file:
1970-
pytest.fail(report_file.read())
2125+
launch_failures.insert(0, report_file.read())
2126+
if launch_failures:
2127+
pytest.fail("\n\n".join(launch_failures))
19712128
finally:
19722129
with suppress(FileNotFoundError):
19732130
os.remove(failure_report_path)

0 commit comments

Comments
 (0)