Inside the Nebius + PyTorch DeepSeek V3 recipe: NVSHMEM and DeepEP for wide expert parallelism

In 2026, nearly all frontier models are mixture-of-experts (MoE). Expert parallel (EP) is the most natural and efficient scheme to split the model up during training. However, enabling EP in training quickly reveals a key bottleneck: all-to-all communication that shuffles tokens between experts on every layer quickly dominates runtime. The irregular traffic pattern proves to be poorly suited to standard NCCL collectives, and most of the efficiency that makes MoE attractive disappears into communication overhead.

The alternative is GPU-initiated RDMA. NVSHMEM and DeepEP let GPUs read and write each other’s memory directly: across nodes, from inside CUDA kernels, with minimal CPU involvement.

On Nebius, adding DeepEP to a DeepSeek V3 training run on 256 B200 GPUs lifted model FLOPs utilization (MFU) by up to ~65% with no meaningful memory cost. This guide covers why MoE breaks collectives, the libraries that fix it, what we measured, and how to set it up on Nebius.

Why ML labs are switching to RDMA

Collective communication, where multiple GPUs participate in a single coordinated data exchange, is the default for distributed ML. NCCL operations like all-reduce and all-gather deliver high throughput for predictable workloads, which makes them a natural fit for dense LLMs using data, tensor, and pipeline parallelism.

MoE breaks that assumption: the router decides at each step how much data goes where, so transfer sizes and destinations change from step to step. Forcing that into strict collectives is inefficient, and ML labs are increasingly turning to RDMA point-to-point instead.

Mixture-of-experts and wide expert parallelism

In an MoE model, the dense feed-forward network (FFN) at the end of each Transformer block is replaced with a sparse block split into experts. On each token, only a small subset of experts (top-K) activates, which keeps compute cost low while preserving quality.

Dense LLM architecture beside an MoE architecture that replaces the feed-forward network with routed expertsLeft: dense LLM. Right: MoE, where the FFN becomes a set of routed experts

That sparsity makes MoEs cheap to serve but heavy on parameters, and it maps naturally onto expert parallelism (EP): partition the experts across GPUs so each holds only a subset. Modern MoEs are sparse enough that it’s often most efficient to scale EP across many nodes at a high EP degree — a technique known as Wide-EP.

DeepSeek V3 is a good example: 671B total parameters, but only 37B activate per token across 256 experts. With EP32 and DP32:

  1. Each GPU holds just 8 of the 256 experts, using far less HBM.
  2. With a high DP value, more ranks send tokens to fewer experts per GPU, so each expert processes more tokens per step, raising arithmetic intensity.
  3. Each GPU loads weights simultaneously, increasing aggregate HBM bandwidth 4x versus a single-node deployment and easing the memory bottleneck proportionally.

The catch: the higher the EP degree, the more cross-node communication dominates runtime. Every MoE layer needs two all-to-all exchanges: a dispatch phase that sends tokens to their assigned experts, and a combine phase that returns expert outputs to their original positions.

Because each rank must talk to every other rank, message count grows quadratically with the number of workers. At a high EP degree, all-to-all becomes the bottleneck and the gains above go to waste, which is exactly why any communication improvement pays off end to end. NVSHMEM and DeepEP are how we capture it.

The building blocks: NVSHMEM, RDMA, GPUDirect, IBGDA, GDRCopy

RDMA-based point-to-point lets GPUs read from and write to remote GPU memory directly, with minimal CPU involvement. Because it skips collective setup and synchronization, it can deliver much lower latency for the small, dynamic GPU-to-GPU transfers MoE generates.

NVSHMEM is NVIDIA’s library for GPU-initiated RDMA. It exposes a Partitioned Global Address Space (PGAS): each GPU maps a symmetric memory region that every other GPU can address directly, reading or writing any peer’s memory — within or across nodes — from inside CUDA kernels, without CPU involvement.

It builds on two primitives. RDMA lets a NIC access DRAM directly without CPU orchestration; GPUDirect RDMA extends that to read and write GPU HBM directly. NVSHMEM picks the fastest available fabric automatically: NVLink within a node, InfiniBand between nodes.

For MoE, where many small messages cross InfiniBand, latency is far higher than for the same operations over NVLink. NVSHMEM narrows the gap with InfiniBand GPUDirect Async (IBGDA). Standard GPUDirect RDMA still leaves the CPU in the loop to tell the NIC what to do; IBGDA removes that dependency and lets the GPU drive the NIC directly.

Both NVSHMEM and DeepEP also use GDRCopy, which gives the CPU RDMA access to GPU memory to speed up small CPU↔GPU transfers.

DeepEP

DeepEP attacks the communication bottleneck directly, with purpose-built GPU-initiated kernels for dispatch and combine, built on NVSHMEM’s symmetric memory and RDMA. It ships two kernel families for different regimes: high-throughput kernels for training and prefill, and low-latency pure-RDMA kernels for decode. That low-latency path is especially useful for inference that combines Wide-EP with prefill/decode disaggregation. DeepEP also supports low-precision operations, including FP8. It was the first library of its kind, and it’s still the one others are measured against.

Benchmarking DeepEP on Nebius

We worked with PyTorch to build a high-performance DeepSeek V3 training recipe on TorchTitan, running on 256 B200 GPUs managed by Soperator.

The early numbers, for DeepSeek-16B (64 experts) and DeepSeek V3-671B (256 experts):

Model MFU without DeepEP MFU with DeepEP Throughput gain
DeepSeek-16B (64 experts) 8.29% 13.64% ~65%
DeepSeek V3-671B (256 experts) 11.22% 13.99% ~25%

That’s roughly 65% and 25% throughput gains, with no meaningful increase in memory. For frontier-scale MoE training, gains that size change what a run costs. Read the full recipe to reproduce these results.

We’ve also built a high-throughput DeepSeek inference solution with vLLM, using llm-d, Wide-EP, and prefill/decode disaggregation, all enabled by DeepEP. Benchmarks for that setup are coming in a follow-up.

How to set it up on Nebius

The steps below assume Soperator or Managed Kubernetes nodes; VM users follow the same flow. Full instructions are available on GitHub.

1. Install the IBGDA and GDRCopy kernels

First, check if your cluster already has them enabled:

# Check IBGDA
cat /proc/driver/nvidia/params | grep -E "EnableStreamMemOPs|PeerMappingOverride"
# Should output:
# EnableStreamMemOPs: 1
# RegistryDwords: "PeerMappingOverride=1;"

# Check GDRCopy (wait a few minutes, should output test results)
gdrcopy_sanity

If either of these fail:

  • Managed Kubernetes or Soperator: contact support to assist with installation
  • Virtual Machine: follow the steps below.

Update NVIDIA Kernel to enable IBGDA

echo 'NVreg_EnableStreamMemOPs=1 NVreg_RegistryDwords="PeerMappingOverride=1;"' | sudo tee /etc/modprobe.d/nvidia.conf
sudo update-initramfs -u
# Then go to the Nebius console and stop/start the VM

Install GDRCopy kernel module

sudo apt install build-essential devscripts debhelper fakeroot pkg-config dkms
git clone https://github.com/NVIDIA/gdrcopy.git
cd gdrcopy
git checkout v2.5.1
cd packages
CUDA=/usr/local/cuda ./build-deb-packages.sh
sudo dpkg -i gdrdrv-dkms_*.deb
sudo dpkg -i libgdrapi_*.deb
sudo dpkg -i gdrcopy-tests_*.deb
sudo dpkg -i gdrcopy_*.deb
# /dev/gdrdrv should now exist, rerun gdrcopy_sanity

2. Install NVSHMEM

pip install nvidia-nvshmem-cu12   # or -cu13

3. Install DeepEP

Set NVSHMEM_DIR to your NVSHMEM install path, and TORCH_CUDA_ARCH_LIST to your GPU architecture (10.0 for Blackwell, 9.0 for Hopper).

Note: During the PyTorch collaboration we utilized DeepEP’s V1 release, reproducible in the section below with branch 29d31c0. There has since been a V2 release of the framework with separate installation instructions.

export NVSHMEM_DIR=$(python3 -c "import nvidia.nvshmem; print(nvidia.nvshmem.__path__[0])")
export TORCH_CUDA_ARCH_LIST="10.0"  # Use 9.0 for Hopper (H100/H200)
ln -sf libnvshmem_host.so.3 "$NVSHMEM_DIR/lib/libnvshmem_host.so"  # Symlink

# Build and install
git clone https://github.com/deepseek-ai/DeepEP.git
cd DeepEP && git checkout 29d31c0
python3 setup.py build && python3 setup.py install

Enable IBGDA. Recommended environment settings:

export NVSHMEM_REMOTE_TRANSPORT=ibrc
export NVSHMEM_IB_ENABLE_IBGDA=1
export NVSHMEM_IBGDA_NIC_HANDLER=gpu

Raise the memlock limit

RDMA transfers require pinned memory, so memlock limits must be high or transfers will fail.

ulimit -l unlimited

On Kubernetes, set LimitMEMLOCK=infinity with a containerd override:

# Drain the node first
kubectl drain $NODE --ignore-daemonsets --delete-emptydir-data

# Exec into the node
kubectl debug node/$NODE -it --image=ubuntu -- bash -c "chroot /host"

# On the node
mkdir -p /etc/systemd/system/containerd.service.d/
cat > /etc/systemd/system/containerd.service.d/override.conf <<EOF
[Service]
LimitMEMLOCK=infinity
EOF
systemctl daemon-reload
systemctl restart containerd

# Uncordon and verify
kubectl uncordon $NODE
kubectl debug node/$NODE -it --image=ubuntu -- /bin/bash
ulimit -l

Whitelist the right InfiniBand NICs

mlx5_12 is a virtualized NIC for VPC offloading and is sometimes picked up for RDMA transport; exclude it. Point UCX_NET_DEVICES and NVSHMEM_HCA_LIST at the real IB ports for your GPU type — at most 8 ports on x86 hosts, 4 on Arm (GB) hosts.

For H100 and H200 (mlx5_0–mlx5_7):

export UCX_NET_DEVICES=mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1,mlx5_4:1,mlx5_5:1,mlx5_6:1,mlx5_7:1
export NVSHMEM_HCA_LIST=mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1,mlx5_4:1,mlx5_5:1,mlx5_6:1,mlx5_7:1

For B200 and B300 (mlx5_4–mlx5_11, excluding the node-local NVSwitch bridges `mlx5_0–mlx5_3):

export UCX_NET_DEVICES=mlx5_4:1,mlx5_5:1,mlx5_6:1,mlx5_7:1,mlx5_8:1,mlx5_9:1,mlx5_10:1,mlx5_11:1
export NVSHMEM_HCA_LIST=mlx5_4:1,mlx5_5:1,mlx5_6:1,mlx5_7:1,mlx5_8:1,mlx5_9:1,mlx5_10:1,mlx5_11:1

For GB200 and GB300 (mlx5_0–mlx5_3):

export UCX_NET_DEVICES=mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1
export NVSHMEM_HCA_LIST=mlx5_0:1,mlx5_1:1,mlx5_2:1,mlx5_3:1

Run it on your own MoE workload

MoE moved the bottleneck in distributed training from compute to communication, and the all-to-all between experts is where a wide expert-parallel run either pays off or stalls. GPU-initiated RDMA through NVSHMEM and DeepEP turns that bottleneck back into headroom: on a DeepSeek V3 run on 256 B200 GPUs, it bought up to ~65% more throughput at no real memory cost. On Nebius, capturing that comes down to installing the right kernels and pointing NVSHMEM at the correct InfiniBand ports.

Spin up a Soperator cluster, enable IBGDA, and run DeepEP against your own MoE workload. Run it from this GitHub cookbook, or try it on Nebius AI Cloud.

Explore Nebius AI Cloud

Explore Nebius Token Factory

Sign in to save this post