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
62 changes: 62 additions & 0 deletions tests/kernels/moe/test_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,68 @@ def test_fused_moe_int64_overflow(workspace_init):
)


@pytest.mark.parametrize("m", [1, 2, 4])
@pytest.mark.parametrize("host_resident", [False, True])
@pytest.mark.parametrize("modular", [True, False])
def test_host_resident_weights_take_sorted_path(
m: int, host_resident: bool, modular: bool, monkeypatch, workspace_init
):
"""At small batch the naive path reads an expert once per (token, expert)
pair. Weights read from host memory in place (UVA offloading) must take
the sorted path instead, with the same output."""
from vllm.utils.platform_utils import is_uva_available
from vllm.utils.torch_utils import get_accelerator_view_from_cpu_tensor

if host_resident and not is_uva_available():
pytest.skip("needs UVA")
set_random_seed(7)
e, n, k, topk, dtype = 128, 256, 128, 8, torch.bfloat16
# Inputs scaled so that outputs sit well above the tolerance below.
a = torch.randn((m, k), device="cuda", dtype=dtype)
w1 = torch.randn((e, 2 * n, k), dtype=dtype) / 8
w2 = torch.randn((e, k, n), dtype=dtype) / 8
score = torch.randn((m, e), device="cuda", dtype=dtype)
if host_resident:
w1_dev = get_accelerator_view_from_cpu_tensor(w1.pin_memory())
w2_dev = get_accelerator_view_from_cpu_tensor(w2.pin_memory())
w1_dev._vllm_is_uva_offloaded = True
w2_dev._vllm_is_uva_offloaded = True
else:
w1_dev, w2_dev = w1.cuda(), w2.cuda()

aligned: list[int] = []
align = fused_moe_module.moe_align_block_size

def counting_align(*args, **kwargs):
aligned.append(1)
return align(*args, **kwargs)

monkeypatch.setattr(fused_moe_module, "moe_align_block_size", counting_align)
with set_current_vllm_config(vllm_config):
if modular:
fused_moe_fn = modular_triton_fused_moe(
make_dummy_moe_config(), FUSED_MOE_UNQUANTIZED_CONFIG
)
topk_weights, topk_ids, _ = fused_topk(a, score, topk, False)
out = fused_moe_fn.apply(
a,
w1_dev,
w2_dev,
topk_weights,
topk_ids,
activation=MoEActivation.SILU,
global_num_experts=e,
expert_map=None,
apply_router_weight_on_input=False,
)
else:
out = fused_moe(a, w1_dev, w2_dev, score, topk, renormalize=False)
ref = torch_moe(a, w1.cuda(), w2.cuda(), score, topk)
assert ref.abs().mean() > 0.1
torch.testing.assert_close(out, ref, atol=2e-2, rtol=2e-2)
assert bool(aligned) == host_resident


@pytest.mark.parametrize("m,n,k", FUSED_MOE_MNK_FACTORS_SMALL_M)
@pytest.mark.parametrize("e", NUM_EXPERTS_LARGE)
@pytest.mark.parametrize("topk", TOP_KS_SMALL)
Expand Down
2 changes: 2 additions & 0 deletions vllm/model_executor/layers/fused_moe/experts/triton_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
)
from vllm.model_executor.layers.fused_moe.fused_moe import (
_prepare_expert_assignment,
_weights_host_resident,
invoke_fused_moe_triton_kernel,
invoke_fused_moe_wna16_triton_kernel,
try_get_optimal_moe_config,
Expand Down Expand Up @@ -370,6 +371,7 @@ def apply(
use_int4_w4a16=self.quant_config.use_int4_w4a16,
block_shape=self.block_shape,
ignore_invalid_experts=skip_invalid,
weights_host_resident=_weights_host_resident(w1, w2),
)
)

Expand Down
13 changes: 12 additions & 1 deletion vllm/model_executor/layers/fused_moe/fused_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,12 @@ def fused_experts_op_fake(
)


def _weights_host_resident(*weights: torch.Tensor) -> bool:
"""True when any of the expert weights is read from host memory in place,
as UVA weight offloading (``--offload-backend uva``) leaves them."""
return any(getattr(w, "_vllm_is_uva_offloaded", False) for w in weights)


def _prepare_expert_assignment(
topk_ids: torch.Tensor,
config: dict[str, Any],
Expand All @@ -1550,14 +1556,18 @@ def _prepare_expert_assignment(
use_int4_w4a16: bool = False,
block_shape: list[int] | None = None,
ignore_invalid_experts: bool = False,
weights_host_resident: bool = False,
) -> tuple[torch.Tensor | None, torch.Tensor, torch.Tensor]:
"""Prepare expert assignments for the aligned and low-latency Triton paths."""
# SPARSITY_FACTOR is a heuristic margin ensuring tokens_in_chunk * top_k
# activates only a small fraction of total experts
# Skips moe_align_block_size and activates the `sorted_token_ids is None`
# path of the fused_moe_kernel kernel
# path of the fused_moe_kernel kernel, which reads an expert's weights once
# per (token, expert) pair. Not for weights in host memory: there every
# repeated read crosses PCIe, so group each expert's tokens instead.
naive_block_assignment = (
expert_map is None
and not weights_host_resident
and num_tokens * top_k_num * 4 <= global_num_experts
and not (
(use_int8_w8a16 or use_int4_w4a16)
Expand Down Expand Up @@ -1782,6 +1792,7 @@ def fused_experts_impl(
use_int4_w4a16=use_int4_w4a16,
block_shape=block_shape,
ignore_invalid_experts=True,
weights_host_resident=_weights_host_resident(w1, w2),
)

dispatch_fused_moe_kernel(
Expand Down
Loading