MORI Shmem Guide
MORI Shmem provides OpenSHMEM-style symmetric memory APIs for GPU memory management and RDMA communication. It is the foundation layer that MORI-EP and MORI-IO build upon — shmem must be initialized before using any other MORI component.
Table of Contents
Quick Reference
import mori
# Initialize from PyTorch process group
mori.shmem.shmem_torch_process_group_init("default")
# Query
my_rank = mori.shmem.shmem_mype()
num_ranks = mori.shmem.shmem_npes()
# Allocate symmetric memory
ptr = mori.shmem.shmem_malloc(size_in_bytes)
# Register existing buffer for RDMA
mori.shmem.shmem_buffer_register(tensor.data_ptr(), tensor.nbytes)
# P2P address translation (intra-node)
remote_ptr = mori.shmem.shmem_ptr_p2p(ptr, my_pe, dest_pe)
# Synchronize
mori.shmem.shmem_barrier_all()
# Cleanup
mori.shmem.shmem_free(ptr)
mori.shmem.shmem_finalize()
Imports:
What |
Import |
|---|---|
All shmem APIs |
|
Init flags |
|
|
1. Concepts
Symmetric Memory
Symmetric memory is GPU memory that is allocated at the same virtual offset across all participating PEs (Processing Elements / ranks). This enables RDMA hardware to directly access remote GPU memory using simple address arithmetic — no address translation tables needed at runtime.
Processing Element (PE)
A PE is a participant in the symmetric memory domain. Each GPU rank maps to one PE. PEs are numbered 0 to N-1.
Symmetric Heap
The symmetric heap is a pre-allocated region of GPU memory from which shmem_malloc allocates. Its size is controlled by the MORI_SHMEM_HEAP_SIZE environment variable:
export MORI_SHMEM_HEAP_SIZE=6G # Must be set before shmem init
2. Initialization
Shmem must be initialized exactly once per process. There are three methods:
Method 1: PyTorch Process Group (Recommended)
Use this when PyTorch distributed is already initialized. The process group must be registered with a name before calling shmem_torch_process_group_init:
import torch
import torch.distributed as dist
import mori
# Standard PyTorch distributed init
torch.cuda.set_device(rank)
dist.init_process_group(
backend="cpu:gloo,cuda:nccl",
rank=rank, world_size=world_size,
device_id=torch.device("cuda", rank),
)
# Register the process group with a name
world_group = dist.group.WORLD
torch._C._distributed_c10d._register_process_group("default", world_group)
# Initialize shmem from the registered process group
mori.shmem.shmem_torch_process_group_init("default")
Note: The
_register_process_group("default", ...)step is required —shmem_torch_process_group_initlooks up the group by name to extract rank/world_size and bootstrap the shmem connections.
Method 2: Unique ID (No PyTorch Distributed)
Use this when PyTorch distributed is not available (e.g., standalone applications, custom launchers). Rank 0 generates a unique ID and broadcasts it to all ranks via any transport (file, MPI, TCP, etc.):
import mori
# Rank 0 generates a unique ID
if rank == 0:
unique_id = mori.shmem.shmem_get_unique_id() # Returns 128 bytes
# Broadcast unique_id to all ranks via your transport
# All ranks initialize with the same unique ID
mori.shmem.shmem_init_attr(
mori.shmem.MORI_SHMEM_INIT_WITH_UNIQUEID,
rank, # My rank
world_size, # Total ranks
unique_id, # Shared unique ID (bytes)
)
Example using file-based broadcast (for single-node testing):
import os, time
uid_file = "/tmp/mori_unique_id"
if rank == 0:
unique_id = mori.shmem.shmem_get_unique_id()
with open(uid_file, 'wb') as f:
f.write(unique_id)
else:
while not os.path.exists(uid_file):
time.sleep(0.1)
with open(uid_file, 'rb') as f:
unique_id = f.read()
mori.shmem.shmem_init_attr(
mori.shmem.MORI_SHMEM_INIT_WITH_UNIQUEID,
rank, world_size, unique_id,
)
Method 3: MPI Communicator (C++ / MPI environments)
Use this in MPI-based applications. Pass MORI_SHMEM_INIT_WITH_MPI_COMM as the flag:
mori.shmem.shmem_init_attr(
mori.shmem.MORI_SHMEM_INIT_WITH_MPI_COMM,
rank, world_size, mpi_comm,
)
Note: This is primarily used in C++ applications where an
MPI_Commhandle is available. In Python, Method 1 or Method 2 are preferred.
Initialization Comparison
Method |
When to use |
Dependencies |
|---|---|---|
PyTorch Process Group |
LLM inference/training frameworks (vLLM, SGLang, etc.) |
|
Unique ID |
Standalone apps, custom launchers, non-PyTorch environments |
None (any broadcast mechanism) |
MPI Communicator |
C++ MPI applications |
MPI |
Finalization
Always finalize shmem before process exit:
mori.shmem.shmem_finalize()
# Then destroy PyTorch process group if applicable
dist.destroy_process_group()
3. Query APIs
# Get my PE (rank) ID — 0 to npes-1
my_pe = mori.shmem.shmem_mype()
# Get total number of PEs
total_pes = mori.shmem.shmem_npes()
# Get number of RDMA queue pairs per PE
num_qp = mori.shmem.shmem_num_qp_per_pe()
4. Memory Management
Allocating Symmetric Memory
# Basic allocation
ptr = mori.shmem.shmem_malloc(size_in_bytes)
# Aligned allocation (alignment must be power of 2)
ptr = mori.shmem.shmem_malloc_align(alignment=256, size=size_in_bytes)
# Allocation with flags
ptr = mori.shmem.shmem_ext_malloc_with_flags(size_in_bytes, flags)
All allocation functions return an integer address (int). The allocated memory is symmetric — the same offset is reserved on every PE.
Freeing Symmetric Memory
mori.shmem.shmem_free(ptr)
Registering Existing Buffers
Register a PyTorch tensor or other existing GPU memory for RDMA operations without allocating new symmetric memory:
# Register
tensor = torch.zeros(1024, 7168, dtype=torch.bfloat16, device="cuda")
mori.shmem.shmem_buffer_register(tensor.data_ptr(), tensor.nbytes)
# ... use tensor in MORI operations ...
# Deregister when done
mori.shmem.shmem_buffer_deregister(tensor.data_ptr(), tensor.nbytes)
Both functions return a status code (0 for success).
5. P2P Address Translation
For intra-node GPU-to-GPU access, translate a local symmetric pointer to its P2P-accessible address on a remote PE:
remote_ptr = mori.shmem.shmem_ptr_p2p(local_ptr, my_pe, dest_pe)
Return Value |
Meaning |
|---|---|
Non-zero |
P2P address on |
0 |
Connection uses RDMA transport (different nodes) or pointer is invalid |
6. Synchronization
Global barrier — blocks until all PEs reach the barrier:
mori.shmem.shmem_barrier_all()
7. HIP Module Init (Triton Integration)
When using Triton-compiled kernels that access shmem device symbols, initialize the HIP module:
mori.shmem.shmem_module_init(hip_module_handle)
This copies the current GPU states to the globalGpuStates symbol in the dynamically compiled Triton kernel module.
8. Initialization Flags
Flag |
Value |
Description |
|---|---|---|
|
0 |
Initialize using MPI communicator |
|
1 |
Initialize using broadcast unique ID |
Environment Variables
Variable |
Description |
Default |
|---|---|---|
|
Symmetric heap size (e.g., |
Required |
|
Heap mode: |
|
|
Heap memory type: |
|
|
VMM mode chunk size in bytes |
Auto |
|
Network interface for shmem bootstrap TCP connections (e.g., |
Auto-detect |
|
RDMA NIC selection. Include: |
All available |
|
mori loads libibverbs dynamically at runtime ( |
|
|
Number of RDMA queue pairs per PE |
|
|
InfiniBand GID index for RDMA connections |
Auto-detect |
|
RDMA service level |
Auto |
|
RDMA traffic class |
Auto |
|
Disable P2P (XGMI) transport, force RDMA |
Not set |
|
Only create RDMA QPs to same-rail peers (same index within their node). For rail-isolated fabrics where cross-rail QPs cannot be established. See Rail-only connections. |
Not set |
|
Disable topology detection |
Not set |
|
By default the thread calling |
Not set (binding on) |
|
Bind to the whole NUMA node instead of this rank’s own slice of it. |
Not set (split on) |
|
Global log verbosity: |
|
|
Precompile all JIT kernels on import |
Not set |
|
Disable JIT compilation of device bitcode |
Not set |
Rail-only connections
By default Context builds a full mesh: every cross-node peer gets
MORI_NUM_QP_PER_PE queue pairs. On a rail-isolated fabric only NICs on
the same rail can reach each other, so the cross-rail QPs never reach RTR and
init hangs — even though no kernel would ever have used them.
MORI_ENABLE_RAIL_ONLY=1 restricts QP creation to same-rail peers. Two ranks are
on the same rail when they have the same index within their own node, which is
the same relation the EP internode kernels already encode in their proxy:
// src/ops/dispatch_combine/internode_v1.cpp
int proxyPe = i * config.gpuPerNode + (config.rank % config.gpuPerNode);
On a 2-node × 8-GPU job this takes each rank from 8 cross-node peers (32 QPs at
4 QP/PE) down to 1 (4 QPs). Skipped peers keep the same empty endpoint stubs
already used for non-RDMA peers, so rdmaEps indexing is unchanged; the
predicate is symmetric, so both ends stub each other and the handle AllToAll
stays aligned.
This is opt-in, and only safe for kernels whose cross-node traffic is same-rail.
Path |
Cross-node RDMA target |
Rail-only safe |
|---|---|---|
EP |
|
yes |
EP |
arbitrary |
no |
EP |
arbitrary |
no |
CCO with |
same rail |
yes |
CCO with |
arbitrary peer |
no |
Requirements checked at init — if any fails, mori logs an error and falls back to the full mesh rather than mis-pairing ranks:
node sizes are uniform,
ranks are node-major contiguous (
rankInNode == rank % gpuPerNode), which is what the EP proxy formula above assumes.
Same-host peers are never dropped: their RDMA loopback (used only under
MORI_DISABLE_P2P) does not traverse the fabric.
To confirm it took effect, look for this line from each rank:
rail-only: rank 3 rail 3 created QPs to 1 peers (4 QPs): 11
Source Files
File |
Description |
|---|---|
|
Python shmem API |
|
Public exports |
|
C++ shmem headers |
|
Python module entry point |
|
Shmem binding registration ( |