Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions tensorrt_llm/_torch/modules/fused_moe/configurable_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,23 @@ def __init__(
# ConfigurableMoE's super().__init__() was called with real layer_idx and initialized load balancer.
# Backend was created with init_load_balancer=False and without_comm=True to avoid
# duplicate initialization. Now sync all attributes from ConfigurableMoE to backend.
self.backend.aux_stream_dict = self.aux_stream_dict
self.backend.layer_idx = self.layer_idx
self.backend.layer_idx_str = self.layer_idx_str
self.backend.num_slots = self.num_slots
self.backend.layer_load_balancer = self.layer_load_balancer
self.backend.repeat_count = self.repeat_count
self.backend.repeat_idx = self.repeat_idx
self.backend.initial_local_expert_ids = self.initial_local_expert_ids
self.backend.initial_global_assignments = self.initial_global_assignments
self.backend.slot_start = self.slot_start
self.backend.slot_end = self.slot_end
self.backend.expert_size_per_partition = self.expert_size_per_partition
if self.backend is not None:
# Add a check to WAR the issue that the backend is none during torch.compile
assert not torch.compiler.is_compiling(), (
"Backend should not be none if not in torch.compile"
)
self.backend.aux_stream_dict = self.aux_stream_dict
self.backend.layer_idx = self.layer_idx
self.backend.layer_idx_str = self.layer_idx_str
self.backend.num_slots = self.num_slots
self.backend.layer_load_balancer = self.layer_load_balancer
self.backend.repeat_count = self.repeat_count
self.backend.repeat_idx = self.repeat_idx
self.backend.initial_local_expert_ids = self.initial_local_expert_ids
self.backend.initial_global_assignments = self.initial_global_assignments
self.backend.slot_start = self.slot_start
self.backend.slot_end = self.slot_end
self.backend.expert_size_per_partition = self.expert_size_per_partition

# Create weights here, because the backend needs the layer_load_balancer info to create weights
model_config._frozen = False
Expand Down
94 changes: 87 additions & 7 deletions tests/integration/defs/accuracy/test_llm_api_pytorch.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,37 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys

import pytest
import torch
from mpi4py.futures import MPIPoolExecutor


def patch_mpi_pool_session_for_env(mocker, env_vars: dict):
"""
Patch MpiPoolSession._start_mpi_pool to propagate environment variables to MPI child processes.

Uses MPIPoolExecutor's built-in `env` parameter instead of `initializer` to avoid
segfault issues during process cleanup (UCX memory cache conflicts with PyTorch
tensor cleanup during Py_FinalizeEx).

Args:
mocker: pytest-mock mocker fixture
env_vars: Dictionary of environment variable name -> value to propagate
"""
from tensorrt_llm.llmapi.mpi_session import MpiPoolSession

def patched_start_mpi_pool(self):
assert not self.mpi_pool, 'MPI session already started'
self.mpi_pool = MPIPoolExecutor(max_workers=self.n_workers,
path=sys.path,
env=env_vars)

mocker.patch.object(MpiPoolSession, '_start_mpi_pool',
patched_start_mpi_pool)


from defs.conftest import get_sm_version, is_sm_100f

from tensorrt_llm import LLM
Expand Down Expand Up @@ -1830,9 +1858,24 @@ def test_nvfp4_batch_waiting(self, torch_compile, fp8kv, cuda_graph,
ids=["tp4", "ep4", "tp2pp2", "pp4"])
@parametrize_with_ids("mtp_nextn", [0, 2])
@parametrize_with_ids("moe_backend", ["CUTLASS", "TRTLLM", "CUTEDSL"])
@pytest.mark.parametrize("enable_configurable_moe", [0, 1],
ids=lambda x: ""
if x == 0 else "enable_configurable_moe")
def test_nvfp4_4gpus(self, fp8kv, attention_dp, cuda_graph,
overlap_scheduler, tp_size, pp_size, ep_size,
torch_compile, mtp_nextn, moe_backend):
torch_compile, mtp_nextn, moe_backend,
enable_configurable_moe, mocker):
# Handle ENABLE_CONFIGURABLE_MOE environment variable
if enable_configurable_moe == 1 and moe_backend != "TRTLLM":
pytest.skip(
f"ENABLE_CONFIGURABLE_MOE=1 is only supported with TRTLLM backend, "
f"current backend is {moe_backend}")

# Patch MpiPoolSession to propagate env vars to MPI worker processes
env_value = "1" if enable_configurable_moe == 1 and moe_backend == "TRTLLM" else "0"
patch_mpi_pool_session_for_env(mocker,
{"ENABLE_CONFIGURABLE_MOE": env_value})

if moe_backend == "TRTLLM" and (get_sm_version() == 120
or get_sm_version() == 121):
pytest.skip(
Expand Down Expand Up @@ -3452,9 +3495,23 @@ def test_nvfp4(
ids=["latency", "ep2", "ep4"])
@pytest.mark.parametrize("activation_dtype", ["static_fp8", "mxfp8"],
ids=["fp8", "mxfp8"])
@pytest.mark.parametrize("enable_configurable_moe", [0, 1],
ids=lambda x: ""
if x == 0 else "enable_configurable_moe")
def test_w4a8_mxfp4(self, moe_backend, tp_size, pp_size, ep_size,
attention_dp, cuda_graph, overlap_scheduler,
activation_dtype):
activation_dtype, enable_configurable_moe, mocker):
# Handle ENABLE_CONFIGURABLE_MOE environment variable
if enable_configurable_moe == 1 and moe_backend != "TRTLLM":
pytest.skip(
f"ENABLE_CONFIGURABLE_MOE=1 is only supported with TRTLLM backend, "
f"current backend is {moe_backend}")

# Patch MpiPoolSession to propagate env vars to MPI worker processes
env_value = "1" if enable_configurable_moe == 1 and moe_backend == "TRTLLM" else "0"
patch_mpi_pool_session_for_env(mocker,
{"ENABLE_CONFIGURABLE_MOE": env_value})

if moe_backend == "TRITON":
if not IS_TRITON_KERNELS_AVAILABLE:
pytest.skip("TRITON moe backend is not available.")
Expand Down Expand Up @@ -3906,9 +3963,23 @@ def test_dummy_load_format(self):
(4, 1, 4, True, True, True),
],
ids=["tp4", "ep4", "dp4"])
@pytest.mark.parametrize("enable_configurable_moe", [0, 1],
ids=lambda x: ""
if x == 0 else "enable_configurable_moe")
def test_w4_4gpus(self, kv_cache_dtype, moe_backend, tp_size, pp_size,
ep_size, attention_dp, cuda_graph, overlap_scheduler,
mocker):
enable_configurable_moe, mocker):
# Handle ENABLE_CONFIGURABLE_MOE environment variable
if enable_configurable_moe == 1 and moe_backend != "TRTLLM":
pytest.skip(
f"ENABLE_CONFIGURABLE_MOE=1 is only supported with TRTLLM backend, "
f"current backend is {moe_backend}")

# Patch MpiPoolSession to propagate env vars to MPI worker processes
env_value = "1" if enable_configurable_moe == 1 and moe_backend == "TRTLLM" else "0"
patch_mpi_pool_session_for_env(mocker,
{"ENABLE_CONFIGURABLE_MOE": env_value})

if moe_backend == "TRITON":
if not IS_TRITON_KERNELS_AVAILABLE:
pytest.skip("Triton kernels are not available")
Expand All @@ -3925,7 +3996,8 @@ def test_w4_4gpus(self, kv_cache_dtype, moe_backend, tp_size, pp_size,

pytorch_config = dict(
disable_overlap_scheduler=not overlap_scheduler,
cuda_graph_config=CudaGraphConfig() if cuda_graph else None)
cuda_graph_config=CudaGraphConfig() if cuda_graph else None,
moe_config=MoeConfig(backend=moe_backend))

kv_cache_config = KvCacheConfig(free_gpu_memory_fraction=0.7,
dtype=kv_cache_dtype)
Expand All @@ -3939,8 +4011,7 @@ def test_w4_4gpus(self, kv_cache_dtype, moe_backend, tp_size, pp_size,
max_seq_len=max_seq_len,
max_batch_size=720,
**pytorch_config,
enable_attention_dp=attention_dp,
moe_config=MoeConfig(backend=moe_backend))
enable_attention_dp=attention_dp)

with llm:
model_name = "GPT-OSS/120B-MXFP4"
Expand Down Expand Up @@ -4252,8 +4323,17 @@ def test_eagle3(self, moe_backend, one_model, overlap_scheduler, mocker):
@pytest.mark.parametrize(
"kv_cache_dtype",
["auto", pytest.param("fp8", marks=skip_pre_blackwell)])
def test_w4_4gpus_online_eplb(self, kv_cache_dtype, mocker):
@pytest.mark.parametrize("enable_configurable_moe", [0, 1],
ids=lambda x: ""
if x == 0 else "enable_configurable_moe")
def test_w4_4gpus_online_eplb(self, kv_cache_dtype, enable_configurable_moe,
mocker):
"""Test GPTOSS with online expert parallel load balancer using TRTLLM backend and attention DP."""
# Patch MpiPoolSession to propagate env vars to MPI worker processes
env_value = "1" if enable_configurable_moe == 1 else "0"
patch_mpi_pool_session_for_env(mocker,
{"ENABLE_CONFIGURABLE_MOE": env_value})

mocker.patch.object(GSM8K, "MAX_OUTPUT_LEN", 8192)
mocker.patch.dict(GSM8K.EVALUATE_KWARGS,
{"scores_filter": "exact_match,flexible-extract"})
Expand Down
92 changes: 92 additions & 0 deletions tests/integration/defs/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2209,6 +2209,94 @@ def pytest_generate_tests(metafunc: pytest.Metafunc):
metafunc.parametrize("case", uts, ids=lambda x: x)


# Test cases that use enable_configurable_moe parameter and need ID conversion
TESTS_WITH_CONFIGURABLE_MOE = [
"TestDeepSeekV3Lite::test_nvfp4_4gpus",
"TestGPTOSS::test_w4_4gpus",
"TestGPTOSS::test_w4_4gpus_online_eplb",
"TestQwen3_30B_A3B::test_w4a8_mxfp4",
]


def _convert_clean_to_original_moe_test_id(test_id):
"""Convert clean MoE test ID back to original format for pytest collection.

Example: "test_llm_api_pytorch.py::test_foo[param]" -> "test_llm_api_pytorch.py::test_foo[-param]"

This is needed because the `enable_configurable_moe` parameter uses empty string
as ID when value is 0, resulting in test IDs like "test_foo[-param]".
We clean these up in pytest_collection_modifyitems, but pytest filters tests
during collection using the original IDs. So when user runs with clean test name,
we need to convert it back to match the original.
"""
if "test_llm_api_pytorch.py" not in test_id:
return test_id

# Match pattern like "test_name[params]" and add leading dash after "["
# But only if params don't already start with "-" or "enable_configurable_moe"
match = re.search(r"\[([^\]]+)\]", test_id)
if match:
params = match.group(1)
# Skip if already has leading dash or starts with enable_configurable_moe
if not params.startswith("-") and not params.startswith(
"enable_configurable_moe"):
# Add leading dash to params
new_params = "-" + params
test_id = test_id.replace(f"[{params}]", f"[{new_params}]")

return test_id


def pytest_sessionstart(session):
"""Convert clean MoE test IDs in config.args to original format for collection.

This is needed because pytest filters tests during collection using original IDs.
When user runs with clean test name, we convert it back to match the original.
"""
args = session.config.args
for i, arg in enumerate(args):
if "test_llm_api_pytorch.py" in arg and "[" in arg:
# Only apply conversion to specific tests that use enable_configurable_moe
should_convert = any(test_name in arg
for test_name in TESTS_WITH_CONFIGURABLE_MOE)
if should_convert:
args[i] = _convert_clean_to_original_moe_test_id(arg)


def _clean_moe_test_ids(items):
"""Clean up test IDs by removing leading/trailing dashes from parameter IDs.

This is needed because `enable_configurable_moe` parameter can be empty,
resulting in ugly test IDs like "test_foo[-True]" or "test_foo[--abc]".
We clean these up to "test_foo[True]" or "test_foo[abc]" so that:
1. Test names in waive files and test lists remain unchanged
2. Test reports look cleaner
"""
for item in items:
if "test_llm_api_pytorch.py" in item.nodeid and "[" in item.nodeid:
# Only apply cleanup to specific tests that use enable_configurable_moe
should_cleanup = any(test_name in item.nodeid
for test_name in TESTS_WITH_CONFIGURABLE_MOE)
if should_cleanup:
original_nodeid = item.nodeid
original_name = item.name
nodeid = item.nodeid
name = item.name

# Clean up leading/trailing dashes in nodeid
nodeid = nodeid.replace("[-", "[")
nodeid = nodeid.replace("-]", "]")

# Clean up leading/trailing dashes in name
name = name.replace("[-", "[")
name = name.replace("-]", "]")

if nodeid != original_nodeid:
item._nodeid = nodeid
if name != original_name:
item.name = name


@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_collection_modifyitems(session, config, items):
testlist_path = config.getoption("--test-list")
Expand All @@ -2217,6 +2305,10 @@ def pytest_collection_modifyitems(session, config, items):
perf_test = config.getoption("--perf")
test_model_suites = config.getoption("--test-model-suites")

# TODO Once the MoE refactor is complete, this should be removed.
# This is a temporary WAR to minimize the impact of the MoE refactor on the existing test lists.
_clean_moe_test_ids(items)

if perf_test:
global ALL_PYTEST_ITEMS
ALL_PYTEST_ITEMS = None
Expand Down
10 changes: 10 additions & 0 deletions tests/integration/test_lists/test-db/l0_dgx_b200.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ l0_dgx_b200:
tests:
- unittest/_torch/modules/test_fused_moe.py::test_fused_moe_alltoall_fp4[DeepEPLowLatency]
- unittest/_torch/modules/test_fused_moe.py::test_fused_moe_alltoall_fp4[MNNVL]
- unittest/_torch/modules/test_fused_moe.py::test_fused_moe_nvfp4[enable_configurable_moe-TRTLLM-dtype1]
- unittest/_torch/modules/test_fused_moe.py::test_fused_moe_w4a8_nvfp4_fp8[enable_configurable_moe-TRTLLM]
- unittest/_torch/modules/test_fused_moe.py::test_fused_moe_mxfp4_mxfp8[enable_configurable_moe-True-8-64-TRTLLM]
- unittest/_torch/modules/test_fused_moe.py::test_fused_moe_wfp4a16[enable_configurable_moe-TRTLLM-2880-dtype0]
- accuracy/test_llm_api_pytorch.py::TestLlama3_1_8BInstruct::test_bfloat16_4gpus[pp4-attn_backend=TRTLLM-torch_compile=False]
- accuracy/test_llm_api_pytorch.py::TestLlama3_1_8BInstruct::test_fp8_4gpus[tp4-fp8kv=True-attn_backend=TRTLLM-torch_compile=False]
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_bfloat16_4gpus[tp4-mtp_nextn=2-attention_dp=True-cuda_graph=True-overlap_scheduler=True-torch_compile=True]
Expand Down Expand Up @@ -158,6 +162,8 @@ l0_dgx_b200:
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_bfloat16_4gpus[pp4-mtp_nextn=2-attention_dp=True-cuda_graph=True-overlap_scheduler=True-torch_compile=True]
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_bfloat16_4gpus[pp4-mtp_nextn=0-attention_dp=False-cuda_graph=False-overlap_scheduler=False-torch_compile=False]
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_bfloat16_4gpus[pp4-mtp_nextn=2-attention_dp=True-cuda_graph=True-overlap_scheduler=True-torch_compile=False]
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_nvfp4_4gpus[enable_configurable_moe-moe_backend=TRTLLM-mtp_nextn=0-tp4-fp8kv=False-attention_dp=False-cuda_graph=False-overlap_scheduler=False-torch_compile=False]
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_nvfp4_4gpus[enable_configurable_moe-moe_backend=TRTLLM-mtp_nextn=0-ep4-fp8kv=False-attention_dp=False-cuda_graph=False-overlap_scheduler=False-torch_compile=True]
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_nvfp4_4gpus[moe_backend=CUTLASS-mtp_nextn=0-tp4-fp8kv=False-attention_dp=False-cuda_graph=False-overlap_scheduler=False-torch_compile=True]
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_nvfp4_4gpus[moe_backend=CUTLASS-mtp_nextn=0-tp4-fp8kv=True-attention_dp=True-cuda_graph=True-overlap_scheduler=True-torch_compile=True]
- accuracy/test_llm_api_pytorch.py::TestDeepSeekV3Lite::test_nvfp4_4gpus[moe_backend=CUTLASS-mtp_nextn=0-tp2pp2-fp8kv=False-attention_dp=False-cuda_graph=False-overlap_scheduler=False-torch_compile=True]
Expand Down Expand Up @@ -191,12 +197,16 @@ l0_dgx_b200:
- accuracy/test_llm_api_pytorch.py::TestLlama4ScoutInstruct::test_fp8_chunked_prefill[tp4ep4-cuda_graph=True]
- accuracy/test_disaggregated_serving.py::TestDeepSeekV3Lite::test_guided_decoding[xgrammar-mtp_nextn=0]
- accuracy/test_disaggregated_serving.py::TestQwen3_30B_A3B::test_mixed_ctx_gen_model[ctxpp2gentp2]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[enable_configurable_moe-tp4-trtllm-fp8]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[enable_configurable_moe-ep4-trtllm-fp8]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[enable_configurable_moe-dp4-trtllm-fp8]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[tp4-cutlass-auto]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[tp4-triton-auto]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[ep4-trtllm-auto]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[ep4-trtllm-fp8]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[dp4-cutlass-auto]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus[dp4-triton-auto]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_w4_4gpus_online_eplb[enable_configurable_moe-fp8]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_eagle3[trtllm-one_model-overlap_scheduler]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_eagle3[trtllm-one_model-no_overlap_scheduler]
- accuracy/test_llm_api_pytorch.py::TestGPTOSS::test_eagle3[trtllm-two_model-no_overlap_scheduler]
Expand Down
Loading