MORI-EP User Guide
MORI-EP provides high-performance MoE (Mixture of Experts) dispatch and combine kernels for Expert Parallelism. It supports both intra-node (XGMI) and inter-node (RDMA) communication, delivering state-of-the-art bandwidth for token routing in models like DeepSeek V3.
Table of Contents
Quick Reference
import mori
# 1. Initialize shmem (required before any EP operations)
mori.shmem.shmem_torch_process_group_init("default")
# 2. Configure
config = mori.ops.EpDispatchCombineConfig(
data_type=torch.bfloat16,
rank=rank,
world_size=world_size,
hidden_dim=7168,
scale_dim=0,
scale_type_size=torch.tensor([], dtype=torch.float8_e4m3fnuz).element_size(),
max_token_type_size=torch.tensor([], dtype=torch.float32).element_size(),
max_num_inp_token_per_rank=4096,
num_experts_per_rank=32,
num_experts_per_token=8,
kernel_type=mori.ops.EpDispatchCombineKernelType.IntraNode,
)
# 3. Create operator and run
op = mori.ops.EpDispatchCombineOp(config)
dispatch_output, dispatch_weights, dispatch_scales, dispatch_indices, recv_num_token = \
op.dispatch(input, weights, scales, indices)
# ... run expert computation on dispatched tokens ...
combine_output, combine_weights = op.combine(expert_output, weights, indices)
op.reset()
Imports:
What |
Import |
|---|---|
Config |
|
Operator |
|
Kernel types |
|
Shmem init |
|
1. Kernel Types
MORI-EP provides five kernel types optimized for different network topologies and latency requirements:
Kernel Type |
Value |
Topology |
Transport |
Use Case |
|---|---|---|---|---|
|
0 |
Single node |
XGMI (P2P) |
EP within a node (e.g., EP8 on 8-GPU node) |
|
1 |
Multi-node |
XGMI + RDMA |
EP across nodes, baseline inter-node kernel |
|
2 |
Multi-node |
XGMI + RDMA |
Optimized inter-node with higher bandwidth |
|
3 |
Multi-node |
XGMI + RDMA |
Low-latency variant of InterNodeV1 |
|
4 |
Multi-node |
XGMI + RDMA |
Async low-latency with pipelined transfers |
How to choose:
Is EP within a single node?
├─ Yes → IntraNode
└─ No (multi-node EP)
├─ Throughput priority (large batches) → InterNodeV1
├─ Latency priority (small batches) → InterNodeV1LL or AsyncLL
└─ Baseline / debugging → InterNode
Kernel naming in benchmarks:
Benchmark Name |
Kernel Type |
World Size |
Notes |
|---|---|---|---|
EP8 |
IntraNode |
8 |
Single node, 8 GPUs |
EP16-V0 |
InterNode |
16 |
2 nodes, baseline |
EP16-V1 |
InterNodeV1 |
16 |
2 nodes, optimized |
EP32-V1-LL |
InterNodeV1LL |
32 |
4 nodes, low-latency |
2. Configuration
EpDispatchCombineConfig
All configuration is specified through the EpDispatchCombineConfig dataclass:
Field |
Type |
Default |
Description |
|---|---|---|---|
|
|
(required) |
Token data type ( |
|
|
(required) |
Current process rank |
|
|
(required) |
Total number of EP ranks |
|
|
(required) |
Hidden dimension of token embeddings |
|
|
(required) |
Scale dimension for quantization (0 if no quantization scales) |
|
|
(required) |
Element size of scale data type in bytes |
|
|
(required) |
Element size of the max token data type in bytes (typically |
|
|
(required) |
Maximum number of input tokens per rank |
|
|
(required) |
Number of experts hosted on each rank |
|
|
(required) |
Top-K: number of experts selected per token |
|
|
|
Warps per GPU thread block |
|
|
|
Number of GPU thread blocks |
|
|
|
Use external input buffer for combine (vs. zero-copy registered buffer) |
|
|
|
Kernel type selection |
|
|
|
GPUs per node (for inter-node rank mapping) |
|
|
|
Thread blocks dedicated to RDMA transfers (inter-node only) |
|
|
|
RDMA queue pairs per PE |
DeepSeek V3 example (256 experts, top-8, 8 GPUs):
config = mori.ops.EpDispatchCombineConfig(
data_type=torch.bfloat16,
rank=rank,
world_size=8,
hidden_dim=7168, # DeepSeek V3 hidden dim
scale_dim=0, # No quantization scales
scale_type_size=torch.tensor([], dtype=torch.float8_e4m3fnuz).element_size(),
max_token_type_size=torch.tensor([], dtype=torch.float32).element_size(),
max_num_inp_token_per_rank=4096,
num_experts_per_rank=32, # 256 experts / 8 GPUs
num_experts_per_token=8, # Top-8 routing
kernel_type=mori.ops.EpDispatchCombineKernelType.IntraNode,
)
Inter-node example (16 GPUs across 2 nodes):
config = mori.ops.EpDispatchCombineConfig(
data_type=torch.bfloat16,
rank=rank,
world_size=16,
hidden_dim=7168,
scale_dim=0,
scale_type_size=torch.tensor([], dtype=torch.float8_e4m3fnuz).element_size(),
max_token_type_size=torch.tensor([], dtype=torch.float32).element_size(),
max_num_inp_token_per_rank=4096,
num_experts_per_rank=16, # 256 experts / 16 GPUs
num_experts_per_token=8,
kernel_type=mori.ops.EpDispatchCombineKernelType.InterNodeV1,
gpu_per_node=8,
rdma_block_num=64, # Blocks dedicated to RDMA
block_num=96, # Total blocks
warp_num_per_block=8,
)
3. Operator API
EpDispatchCombineOp
Create the operator with a config:
op = mori.ops.EpDispatchCombineOp(config)
dispatch()
Route input tokens to their assigned expert ranks.
dispatch_output, dispatch_weights, dispatch_scales, dispatch_indices, recv_num_token = \
op.dispatch(input, weights, scales, indices,
block_num=-1, rdma_block_num=-1, warp_per_block=-1)
Parameter |
Type |
Description |
|---|---|---|
|
|
Input tokens |
|
|
Expert weights |
|
|
Quantization scales |
|
|
Top-K expert indices |
|
|
Override |
|
|
Override |
|
|
Override |
Returns: Tuple of 5 tensors:
dispatch_output— Received tokens for this rank’s experts[recv_tokens, hidden_dim]dispatch_weights— Corresponding weights[recv_tokens, num_experts_per_token]dispatch_scales— Corresponding scales[recv_tokens, scale_dim]dispatch_indices— Corresponding expert indices[recv_tokens, num_experts_per_token]recv_num_token— Number of tokens received (1-element tensor)
combine()
Collect expert outputs and route them back to their original ranks.
combine_output, combine_weights = op.combine(
input, weights, indices,
block_num=-1, rdma_block_num=-1, warp_per_block=-1,
use_external_inp_buf=-1, call_reset=False)
Parameter |
Type |
Description |
|---|---|---|
|
|
Expert output tokens |
|
|
Expert weights for weighted combination |
|
|
Top-K expert indices (same as dispatch) |
|
|
Override |
|
|
Call |
Returns: Tuple of 2 tensors:
combine_output— Reconstructed tokens at original positions[num_tokens, hidden_dim]combine_weights— Reconstructed weights[num_tokens, num_experts_per_token]
Split dispatch/combine (send + recv)
For overlapping communication with computation, dispatch and combine can be split into separate send and receive phases:
# Dispatch: send phase returns the same 5 values as dispatch()
dispatch_output, dispatch_weights, dispatch_scales, dispatch_indices, recv_num_token = \
op.dispatch_send(input, weights, scales, indices)
# Receive phase (completes the receive-side processing, returns None)
op.dispatch_recv()
# Combine: send phase returns the same 2 values as combine()
combine_output, combine_weights = op.combine_send(expert_output, weights, indices)
# Receive phase (completes the receive-side processing, returns None)
op.combine_recv()
Note:
dispatch_send()delegates todispatch()internally.dispatch_recv()andcombine_recv()perform receive-side processing and returnNone.
reset()
Reset internal state. Must be called between iterations (unless call_reset=True was passed to combine()).
op.reset()
get_dispatch_src_token_pos()
Get the source position of each dispatched token (for correctness verification).
torch.cuda.synchronize()
src_token_pos = op.get_dispatch_src_token_pos()
# Encoding: src_token_pos[i] = src_rank * (world_size * max_num_inp_token_per_rank) + src_token_id
# Use op.decode_send_flat_idx(pos) -> (src_rank, src_token_id) to decode safely.
get_registered_combine_input_buffer()
Get the pre-registered combine input buffer for zero-copy mode (use_external_inp_buf=False).
combine_buf = op.get_registered_combine_input_buffer(dtype)
combine_buf[:recv_num_token, :].copy_(expert_output[:recv_num_token, :])
4. Standard MoE Compatibility (DeepEP)
MORI-EP provides DeepEP-compatible APIs for frameworks that use standard 3D MoE tensor layouts. These require building with ENABLE_STANDARD_MOE_ADAPT=ON:
ENABLE_STANDARD_MOE_ADAPT=ON pip install -e .
dispatch_standard_moe()
Combined dispatch + format conversion in a single launch:
result = op.dispatch_standard_moe(input, weights, scales, indices)
combine_standard_moe()
Combined combine with standard MoE input format:
output = op.combine_standard_moe(input, weights, indices, call_reset=False)
convert_dispatch_output()
Convert MORI’s 2D dispatch output to standard 3D MoE layout:
packed_recv_x, packed_recv_count, packed_recv_src_info, packed_recv_layout_range = \
op.convert_dispatch_output(dispatch_out_x, dispatch_out_topk_idx)
convert_combine_input()
Prepare standard MoE inputs for MORI combine:
converted = op.convert_combine_input(packed_recv_x, packed_recv_src_info, packed_recv_layout_range)
Note: If these methods raise
RuntimeError, rebuild MORI withENABLE_STANDARD_MOE_ADAPT=ON.
5. Initialization
MORI-EP requires symmetric memory (shmem) initialization before creating any EpDispatchCombineOp. There are two initialization methods:
Method 1: PyTorch Process Group (Recommended)
import os
import torch
import torch.distributed as dist
import mori
os.environ["MORI_SHMEM_HEAP_SIZE"] = "6G"
# Initialize PyTorch distributed
torch.cuda.set_device(rank)
dist.init_process_group(backend="cpu:gloo,cuda:nccl", rank=rank, world_size=world_size)
# Register process group and initialize shmem
world_group = dist.group.WORLD
torch._C._distributed_c10d._register_process_group("default", world_group)
mori.shmem.shmem_torch_process_group_init("default")
# ... use MORI-EP ...
# Cleanup
mori.shmem.shmem_finalize()
dist.destroy_process_group()
Method 2: Unique ID (No PyTorch Distributed)
import mori
# Rank 0 generates unique ID and broadcasts to all ranks
if rank == 0:
unique_id = mori.shmem.shmem_get_unique_id()
# broadcast unique_id to all ranks (e.g., via MPI, TCP, file)
# All ranks initialize with the unique ID
mori.shmem.shmem_init_attr(
mori.shmem.MORI_SHMEM_INIT_WITH_UNIQUEID,
rank, world_size, unique_id
)
6. Launch Configuration
Manual Mode (Default)
Launch parameters are taken from EpDispatchCombineConfig defaults or per-call overrides:
# Use config defaults (block_num=80, warp_num_per_block=8)
op.dispatch(input, weights, scales, indices)
# Override per call
op.dispatch(input, weights, scales, indices, block_num=128, warp_per_block=16)
Auto Mode
Set MORI_EP_LAUNCH_CONFIG_MODE=AUTO to use pre-tuned launch parameters:
export MORI_EP_LAUNCH_CONFIG_MODE=AUTO
Auto mode selects parameters based on kernel type:
Kernel Type |
block_num |
rdma_block_num |
warp_per_block |
|---|---|---|---|
InterNodeV1, InterNodeV1LL |
96 |
64 |
8 |
IntraNode, InterNode, AsyncLL |
128 |
0 |
16 |
When auto mode is active, per-call block_num/rdma_block_num/warp_per_block arguments are ignored.
7. Complete Example
import os
import time
import torch
import torch.distributed as dist
import mori
os.environ["MORI_SHMEM_HEAP_SIZE"] = "4G"
def run_ep(rank, world_size):
# Setup
torch.cuda.set_device(rank)
device = torch.device("cuda", rank)
dist.init_process_group(
backend="cpu:gloo,cuda:nccl",
rank=rank, world_size=world_size,
device_id=device,
)
world_group = dist.group.WORLD
torch._C._distributed_c10d._register_process_group("default", world_group)
mori.shmem.shmem_torch_process_group_init("default")
# Configuration
config = mori.ops.EpDispatchCombineConfig(
data_type=torch.bfloat16,
rank=rank,
world_size=world_size,
hidden_dim=7168,
scale_dim=0,
scale_type_size=torch.tensor([], dtype=torch.float8_e4m3fnuz).element_size(),
max_token_type_size=torch.tensor([], dtype=torch.float32).element_size(),
max_num_inp_token_per_rank=4096,
num_experts_per_rank=32,
num_experts_per_token=8,
use_external_inp_buf=False,
)
op = mori.ops.EpDispatchCombineOp(config)
# Generate test data
num_tokens = 128
input_data = torch.randn(num_tokens, config.hidden_dim,
dtype=torch.bfloat16, device=device)
weights = torch.rand(num_tokens, config.num_experts_per_token,
dtype=torch.float32, device=device)
scales = torch.empty(num_tokens, 0, dtype=torch.float8_e4m3fnuz, device=device)
# Random top-K expert selection
indices = torch.stack([
torch.randperm(config.num_experts_per_rank * world_size, device=device)
[:config.num_experts_per_token]
for _ in range(num_tokens)
]).to(torch.int32)
# Dispatch tokens to experts
dispatch_out, dispatch_w, dispatch_s, dispatch_idx, recv_count = \
op.dispatch(input_data, weights, scales, indices,
block_num=80, warp_per_block=16)
torch.cuda.synchronize()
total_recv = recv_count[0].item()
print(f"Rank {rank}: sent {num_tokens} tokens, received {total_recv}")
# Simulate expert computation (identity for testing)
expert_output = dispatch_out[:total_recv].to(torch.bfloat16)
# Combine results back
combine_out, combine_w = op.combine(
expert_output, dispatch_w, indices,
block_num=80, warp_per_block=8,
call_reset=True, # reset automatically
)
torch.cuda.synchronize()
# Cleanup
del op
mori.shmem.shmem_finalize()
dist.destroy_process_group()
if __name__ == "__main__":
os.environ["MASTER_ADDR"] = "localhost"
os.environ["MASTER_PORT"] = "12355"
world_size = 8
torch.multiprocessing.spawn(run_ep, args=(world_size,), nprocs=world_size, join=True)
8. Environment Variables
Variable |
Default |
Description |
|---|---|---|
|
— |
Symmetric heap size (e.g., |
|
all available |
RDMA NIC selection. Include: |
|
|
Launch config mode: |
|
|
Enable kernel profiler for JIT-compiled kernels. Set to |
|
— |
TCP interface for torch distributed (e.g., |
|
— |
Torch distributed master address |
|
— |
Torch distributed master port |
9. Benchmarking
Intra-node
cd /path/to/mori
python3 tests/python/ops/bench_dispatch_combine.py
Inter-node
Run on each node (replace node_rank and master_addr):
export GLOO_SOCKET_IFNAME=enp81s0f1
export MORI_RDMA_DEVICES=^mlx5_0,mlx5_1 # Optional: exclude specific NICs
torchrun --nnodes=2 --node_rank=0 --nproc_per_node=1 \
--master_addr="10.194.129.65" --master_port=1234 \
examples/ops/dispatch_combine/test_dispatch_combine_internode.py --bench
The benchmark output reports total tokens received, RDMA token count, and total bandwidth (XGMI + RDMA). To calculate RDMA-only bandwidth:
RDMA BW = Total BW × (RDMA tokens / Total tokens)
Reference Performance (DeepSeek V3 config)
4096 tokens, 7168 hidden, top-8, FP8 dispatch + BF16 combine:
Kernel |
CUs |
Dispatch XGMI |
Dispatch RDMA |
Combine XGMI |
Combine RDMA |
|---|---|---|---|---|---|
EP8 (IntraNode) |
80 |
307 GB/s |
— |
330 GB/s |
— |
EP16-V1 (InterNodeV1) |
80 |
208 GB/s |
63 GB/s |
161 GB/s |
49 GB/s |
EP32-V1-LL (InterNodeV1LL) |
32 |
103 GB/s |
57 GB/s |
91 GB/s |
50 GB/s |
10. Tuning
Tuning searches for the optimal GPU launch parameters (block_num, warp_per_block, rdma_block_num) for each kernel/dtype/token-count combination. Results are saved to JSON config files that the auto-mode launch configuration reads at runtime.
Overview
What gets tuned: For each combination of (kernel_type, dtype, num_tokens, hidden_dim), the tuner sweeps candidate values and picks the configuration that maximises bandwidth on the bottleneck rank.
Two-phase approach:
Calibrate — Run a small number of representative token counts (e.g. 128 and 4096) with
fullscope to explore the complete search space (~75 configs). This confirms whether thequicksearch space (~9-12 configs) covers the optimal region.Quick sweep — Run all token counts with
quickscope. This is 6× faster and usually finds the same optimum because the best configs tend to cluster around the same block/warp values.
The tuning scope is controlled by the MORI_TUNING_SCOPE environment variable (or --tuning-scope in the batch scripts).
Tuning Config Files
Generated JSON files follow this naming convention:
python/mori/ops/tuning_configs/{arch}_{model}_{kernel}_ep{n}_{phase}.json
For example: gfx942_mi308x_InterNodeV1_ep16_dispatch.json
Each file contains rules sorted by (dtype, hidden_dim, num_tokens):
{
"version": "1.0",
"gpu_arch": "gfx942",
"gpu_model": "mi308x",
"kernel_type": "InterNodeV1",
"ep_size": 16,
"phase": "dispatch",
"rules": [
{
"dtype": "fp4",
"num_tokens": 128,
"hidden_dim": 3584,
"block_num": 64,
"rdma_block_num": 32,
"warp_per_block": 8,
"bandwidth_gbps": 4.13,
"avg_rdma_bandwidth_gbps": 4.13,
"avg_xgmi_bandwidth_gbps": 13.2,
"avg_ll_bandwidth_gbps": 16.5,
"avg_latency_us": 128.4,
"bandwidth_metric": "grand_mean"
}
]
}
bandwidth_gbps— The bandwidth that matters for this kernel (RDMA BW for v1, LL BW for v1_ll/async_ll), recorded for readers. Nothing reads it at runtime: onlyblock_num/rdma_block_num/warp_per_blockare.avg_rdma_bandwidth_gbps/avg_xgmi_bandwidth_gbps/avg_ll_bandwidth_gbps— All three BW types recorded for analysis (inter-node only).avg_latency_us— Grand-mean latency, the quantity the inter-node tuner selects on. The intra-node tuner writes this aslatency_us.
New tuning results replace the matching rule unconditionally, printing the old and new config and their latency/bandwidth delta. Two tuning runs come from different hosts, ROCm versions and machine load, so no numeric rule can reliably decide whether a new result is worth keeping — the JSON’s git diff is where that call gets made. Re-run tuning if a printed delta looks wrong.
Inter-node
bandwidth_gbpsis the grand mean across ranks and rounds, matching the “Average” row of the table--cmd benchprints, so a saved rule can be checked against a bench run directly. Rules carrybandwidth_metricrecording this; rules written before it existed hold the slowest-rank mean and have no such field, and a re-tune that replaces one prints both bandwidths without a percentage rather than comparing across the two definitions.
Intra-node Tuning
Intra-node tuning runs on a single node. No SSH or multi-node setup required.
Tuning matrix (EP2 / EP4 / EP8, 4 dtype/quant combos each = 12 groups):
EP |
Dispatch dtype |
Combine dtype |
Quant type |
Zero-copy |
|---|---|---|---|---|
2, 4, 8 |
fp4 |
bf16 |
fp8_direct_cast |
yes |
2, 4, 8 |
fp4 |
bf16 |
fp8_direct_cast |
no |
2, 4, 8 |
fp8_e4m3_fnuz |
bf16 |
none |
yes |
2, 4, 8 |
fp8_e4m3_fnuz |
bf16 |
none |
no |
Step 1: Calibrate (optional but recommended on a new platform)
Run full-scope tuning on 128 and 4096 tokens to verify quick mode covers the optimum:
# Full scope on 2 representative token counts
bash tools/batch_intranode_tuning.sh \
--world-size 8 --dtype fp4 --combine-dtype bf16 \
--quant-type fp8_direct_cast \
--tokens-list "128,4096" --tuning-scope full
Compare the best config from full mode with a quick run on the same tokens. If they match (same block_num/warp), quick mode is safe for the full sweep.
Step 2: Quick sweep
# Single group
bash tools/batch_intranode_tuning.sh \
--world-size 8 --dtype fp4 --combine-dtype bf16 \
--quant-type fp8_direct_cast --tuning-scope quick
# All 12 groups at once
bash tools/run_all_intranode_tuning.sh --tuning-scope quick
Scripts:
Script |
Purpose |
|---|---|
|
Sweep one (EP, dtype, quant, zero_copy) group across all token sizes |
|
Run all 12 intra-node groups sequentially |
Inter-node Tuning
Inter-node tuning requires two nodes with passwordless SSH between them.
Prerequisites:
Passwordless SSH from driver node (rank 0) to peer node (rank 1)
Same repo path on both nodes, or specify
--remote-repo-rootNetwork interface name (
--ifname, e.g.bond0,ens50f1np1)If running inside containers:
--docker <CONTAINER>and--ssh-key <PATH>Kill any residual GPU processes before starting (
pkill -9 -f torchrun)
Tuning matrix (3 kernel types × 2 dtype combos = 6 groups):
Kernel |
Dispatch dtype |
Combine dtype |
Quant type |
BW metric for selection |
|---|---|---|---|---|
v1 |
fp4 |
bf16 |
fp8_direct_cast |
RDMA BW |
v1 |
fp8_e4m3_fnuz |
bf16 |
none |
RDMA BW |
v1_ll |
fp4 |
bf16 |
fp8_direct_cast |
LL BW |
v1_ll |
fp8_e4m3_fnuz |
bf16 |
none |
LL BW |
async_ll |
fp4 |
bf16 |
fp8_direct_cast |
LL BW |
async_ll |
fp8_e4m3_fnuz |
bf16 |
none |
LL BW |
Step 1: Calibrate (on a new platform)
bash tools/batch_internode_tuning.sh \
--master-addr <HOST0> --peer-host <USER>@<HOST1> --ifname <IFNAME> \
--kernel-type v1 --num-qp 2 --dtype fp4 --combine-dtype bf16 \
--quant-type fp8_direct_cast \
--tokens-list "128,4096" --tuning-scope full
Step 2: Sweep
# Single group
bash tools/batch_internode_tuning.sh \
--master-addr <HOST0> --peer-host <USER>@<HOST1> --ifname <IFNAME> \
--kernel-type v1 --num-qp 2 --dtype fp4 --combine-dtype bf16 \
--quant-type fp8_direct_cast
# All 6 groups at once
bash tools/run_all_internode_tuning.sh \
--master-addr <HOST0> --peer-host <USER>@<HOST1> --ifname <IFNAME>
Both default to --tuning-scope full. quick sweeps a reduced candidate
grid — 3 warp_per_block values against full’s 5, plus a narrower
rdma_block_num set — so its winner is not a result worth committing, and
combining it with a config output is rejected rather than silently accepted.
Use it for exploration only, with saving turned off:
bash tools/run_all_internode_tuning.sh \
--master-addr <HOST0> --peer-host <USER>@<HOST1> --ifname <IFNAME> \
--tuning-scope quick --config-output ''
For docker environments, add --docker <PEER_CONTAINER>. If the driver node
also runs mori inside a container, add --local-docker <RANK0_CONTAINER>
rather than invoking the script from inside that container — the peer is
reached over SSH, so a containerised driver would need its own SSH key to the
other node. --ssh-key <KEY> is only needed when the driver’s default key is
not the right one. Set the RDMA traffic class with --rdma-tc <TC> (and the
service level with --rdma-sl <SL>); these are passed through to both ranks,
which do not inherit the caller’s environment.
Step 3: Verify what was written
The sweep only times each candidate — run_bench_once never compares output,
so nothing in tuning itself confirms the winning geometry is correct. Check the
JSON before committing it:
bash tools/verify_tuned_config.sh \
--master-addr <HOST0> --peer-host <USER>@<HOST1> --ifname <IFNAME> \
--local-docker <RANK0_CONTAINER> --docker <PEER_CONTAINER> \
--kernel-type v1_ll --num-qp 1 --dtype fp8_e4m3_fnuz \
--combine-dtype bf16 --quant-type none \
--tokens-list "4,8,16,32" --hidden-dims "6144"
It re-runs each shape under MORI_EP_LAUNCH_CONFIG_MODE=AUTO with --cmd test,
so every op is built from whatever the JSON now holds and 500 rounds of
dispatch/combine output are compared against expected values. Exit status is
non-zero if any shape crashes, hangs, or produces wrong output.
Scripts:
Script |
Purpose |
|---|---|
|
Sweep one (kernel, dtype, quant) group across all token sizes |
|
Run all 6 inter-node groups sequentially |
|
Correctness-check the saved configs before committing them |
Tuning on a New GPU Platform
To tune MORI-EP on a previously unseen GPU (new arch/model):
Build and install MORI:
pip install . --no-build-isolationRun calibration (full scope, 128+4096 tokens) for one representative config per topology (intra-node and inter-node if applicable)
Verify that quick scope produces the same best config as full scope
Run the full quick sweep to generate all JSON files
The generated files appear under
python/mori/ops/tuning_configs/and are automatically loaded whenMORI_EP_LAUNCH_CONFIG_MODE=AUTO
Expected output files (for a gfx942 MI308X platform with EP8 intra + EP16 inter):
python/mori/ops/tuning_configs/
gfx942_mi308x_IntraNode_ep2_dispatch.json
gfx942_mi308x_IntraNode_ep2_combine.json
gfx942_mi308x_IntraNode_ep4_dispatch.json
gfx942_mi308x_IntraNode_ep4_combine.json
gfx942_mi308x_IntraNode_ep8_dispatch.json
gfx942_mi308x_IntraNode_ep8_combine.json
gfx942_mi308x_InterNodeV1_ep16_dispatch.json
gfx942_mi308x_InterNodeV1_ep16_combine.json
gfx942_mi308x_InterNodeV1LL_ep16_dispatch.json
gfx942_mi308x_InterNodeV1LL_ep16_combine.json
gfx942_mi308x_AsyncLL_ep16_dispatch.json
gfx942_mi308x_AsyncLL_ep16_combine.json
11. Profiling with MORI-VIZ
Build with profiling enabled:
ENABLE_PROFILER=ON pip install -e .
Capture and export a trace:
from mori.kernel_profiler import export_to_perfetto
# Run dispatch/combine, then:
if hasattr(mori.cpp, "get_debug_time_buf"):
trace_buffer = mori.cpp.get_debug_time_buf(op._handle)
export_to_perfetto(trace_buffer, "ep_trace.json")
Visualize at ui.perfetto.dev. See PROFILER.md for full profiler documentation.
12. Framework Integration
MORI-EP is integrated in several LLM inference frameworks:
Framework |
Integration Point |
Notes |
|---|---|---|
|
MoriAll2AllManager wraps dispatch/combine for FusedMoE |
|
|
Expert parallelism with FusedMoEParallelConfig |
|
MoE expert parallelism |
Dispatch/combine for distributed MoE |
|
MoE expert parallelism |
Dispatch/combine for distributed MoE |
Build Options
CMake Option |
Default |
Description |
|---|---|---|
|
|
Build MORI-EP dispatch/combine kernels |
|
|
Build symmetric memory library |
|
|
Build MORI-IO library |
|
|
Build Python bindings |
|
|
Enable MORI-VIZ kernel profiler |
|
|
Enable DeepEP-compatible standard MoE APIs |
|
|
Enable debug printf in device kernels |
|
|
Build for ROCm/HIP (vs. CUDA) |
|
auto-detected |
Broadcom Thor2 NIC support (enabled automatically if |
|
auto-detected |
AMD Pollara (AINIC) NIC support (enabled automatically if |
Source Files
File |
Description |
|---|---|
|
Python API: |
|
Public exports |
|
Shmem Python API |
|
C++ header: config, handle, kernel args |
|
Core dispatch/combine implementation |
|
InterNodeV1/V1LL kernels |
|
AsyncLL kernels |
|
Python module entry point |
|
Ops/Shmem/IO binding registration |
|
Complete Python example |
|
Inter-node example |
|
Correctness tests |
|
Performance benchmarks |