Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6c0cd7e
Add HybridEP support for GB200 NVL72
seonjinn Apr 13, 2026
0349e83
Merge latest main into sj/hybridep-support
seonjinn Apr 13, 2026
ec53cb6
Revert 3rdparty to match main, no submodule changes
seonjinn Apr 13, 2026
2e3b144
Update 3rdparty submodules to match origin/main
seonjinn Apr 13, 2026
0841a8e
Remove unnecessary vllm tensor_parallel_size override from deepseek c…
seonjinn Apr 13, 2026
c8167ab
Update 30BA3B config to match Bridge recipe: EP=8, num_sms=16, router…
seonjinn Apr 13, 2026
6c1a93e
Add router_force_load_balancing and bias_activation_fusion to match B…
seonjinn Apr 13, 2026
7332214
Update aarch64 deep_ep to 34152ae2 (HEAD of hybrid-ep branch)
seonjinn Apr 13, 2026
65fbdc2
Remove moe_router_fusion, force_load_balancing, bias_activation_fusio…
seonjinn Apr 13, 2026
cc17589
Set make_sequence_length_divisible_by=128 for HybridEP JIT alignment
seonjinn Apr 13, 2026
9cc82f9
Revert deep_ep to 7febc6e2 and restore original make_sequence_length_…
seonjinn Apr 13, 2026
c6e3c71
Update tests/test_suites/llm/performance/grpo-qwen3-235b-16n4g.sh
seonjinn Apr 14, 2026
b4a5688
Revert 30BA3B config to main: remove HybridEP settings, restore EP=16
seonjinn Apr 14, 2026
e24b627
Add uv.lock
seonjinn Apr 15, 2026
f0c2548
remove flag
seonjinn Apr 15, 2026
6cd4927
Merge branch 'main' into sj/hybridep-support
seonjinn Apr 15, 2026
5bb665f
Pad packed seq to 128 for HybridEP JIT kernel alignment
seonjinn Apr 15, 2026
3c5856a
Merge branch 'main' into sj/hybridep-support
seonjinn Apr 16, 2026
25a1561
Add HybridEP monkey-patches for fused_a2a.py bugs
seonjinn Apr 18, 2026
afc44c7
Revert "Add HybridEP monkey-patches for fused_a2a.py bugs"
seonjinn Apr 18, 2026
13bfb05
Update aarch64 deep_ep to a48493: no buffer patches needed
seonjinn Apr 18, 2026
b584c95
Merge latest main into sj/hybridep-support
seonjinn Apr 19, 2026
bf810dd
Update uv.lock
seonjinn Apr 19, 2026
72a7b7a
Merge branch 'main' into sj/hybridep-support
guyueh1 Apr 22, 2026
0bf98ab
Merge branch 'main' into sj/hybridep-support
guyueh1 May 1, 2026
e22f6aa
Merge branch 'main' into sj/hybridep-support
seonjinn May 7, 2026
edaed4b
test: cover HybridEP _apply_moe_config + sequence pad divisor
seonjinn May 7, 2026
2f05960
Merge branch 'main' into sj/hybridep-support
guyueh1 May 11, 2026
2d9f3dd
Merge remote-tracking branch 'origin/main' into sj/hybridep-support
seonjinn May 14, 2026
cd0f301
fix: disambiguate logprobs cross-references in docstrings
seonjinn May 14, 2026
f8fa0b0
Merge branch 'main' into sj/hybridep-support
seonjinn May 14, 2026
f438d1e
fix: backtick logprobs in Returns first-line tuple description
seonjinn May 14, 2026
5009b0d
Merge branch 'main' into sj/hybridep-support
seonjinn May 15, 2026
5987018
fix: pin NeMo Gym docs URL to v0.2.1 (latest 404)
seonjinn May 15, 2026
b9517fa
Merge remote-tracking branch 'origin/main' into sj/hybridep-support
seonjinn May 15, 2026
a4f0ead
ci: retry to clear L0_Policy fp8 numeric flake
seonjinn May 16, 2026
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
3 changes: 3 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ ENV UV_LINK_MODE=copy
# Ensure DeepEP is built for H100 and B200 (also mcore inference unified memory API now invokes a torch API that requires these to be set)
ENV TORCH_CUDA_ARCH_LIST="9.0 10.0"

# Ensure HybridEP/DeepEP JIT compilation can find nvcc at runtime
ENV CUDA_HOME=/usr/local/cuda

## CUDA 13 moved standard library headers under include/cccl; expose that path so
## builds that include <cuda/std/...> (e.g., deep_gemm) can find them.
ENV CPLUS_INCLUDE_PATH=/usr/local/cuda/include/cccl
Expand Down
4 changes: 4 additions & 0 deletions docker/Dockerfile.ngc_pytorch
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ ENV PATH="/opt/nemo_rl_venv/bin:$PATH"
# Ensure DeepEP is built for H100 and B200
ENV TORCH_CUDA_ARCH_LIST="9.0 10.0"

# Ensure HybridEP/DeepEP JIT compilation can find nvcc at runtime
ENV CUDA_HOME=/usr/local/cuda


# First copy only the dependency files
COPY --from=nemo-rl pyproject.toml uv.lock ./
COPY --from=nemo-rl --link 3rdparty/ ./3rdparty/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ policy:
pipeline_model_parallel_size: 8
num_layers_in_first_pipeline_stage: 7
num_layers_in_last_pipeline_stage: 6
# HybridEP settings
moe_token_dispatcher_type: flex
moe_flex_dispatcher_backend: hybridep
moe_hybridep_num_sms: 32
generation:
vllm_cfg:
gpu_memory_utilization: 0.3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ policy:
pipeline_model_parallel_size: 4
num_layers_in_first_pipeline_stage: 23
num_layers_in_last_pipeline_stage: 23
# HybridEP settings
moe_token_dispatcher_type: flex
moe_flex_dispatcher_backend: hybridep
moe_hybridep_num_sms: 32
generation:
vllm_cfg:
tensor_parallel_size: 8
Expand Down
14 changes: 7 additions & 7 deletions nemo_rl/models/generation/sglang/sglang_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,9 +428,9 @@ async def _generate_single_sample(
stop_string: Optional stop string for this sample

Returns:
Tuple of (generated_tokens, logprobs):
- generated_tokens: List of generated token IDs
- logprobs: List of log probabilities for generated tokens
Tuple of (``generated_tokens``, ``logprobs``):
- ``generated_tokens``: List of generated token IDs
- ``logprobs``: List of log probabilities for generated tokens
"""
# Prepare payload for SGLang API
# Note: stop should be in sampling_params, not in payload top level
Expand Down Expand Up @@ -562,10 +562,10 @@ def generate(

Returns:
BatchedDataDict conforming to GenerationOutputSpec:
- output_ids: input + generated token IDs with proper padding
- logprobs: Log probabilities for tokens
- generation_lengths: Lengths of each response
- unpadded_sequence_lengths: Lengths of each input + generated sequence
- ``output_ids``: input + generated token IDs with proper padding
- ``logprobs``: Log probabilities for tokens
- ``generation_lengths``: Lengths of each response
- ``unpadded_sequence_lengths``: Lengths of each input + generated sequence
"""
# Handle empty input case
if len(data["input_ids"]) == 0:
Expand Down
8 changes: 4 additions & 4 deletions nemo_rl/models/generation/vllm/vllm_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,10 +703,10 @@ def generate(

Returns:
BatchedDataDict conforming to GenerationOutputSpec:
- output_ids: input + generated token IDs with proper padding
- logprobs: Log probabilities for tokens
- generation_lengths: Lengths of each response
- unpadded_sequence_lengths: Lengths of each input + generated sequence
- ``output_ids``: input + generated token IDs with proper padding
- ``logprobs``: Log probabilities for tokens
- ``generation_lengths``: Lengths of each response
- ``unpadded_sequence_lengths``: Lengths of each input + generated sequence
"""
# Handle empty input case
if len(data["input_ids"]) == 0:
Expand Down
23 changes: 19 additions & 4 deletions nemo_rl/models/megatron/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,13 +567,28 @@ def _get_pack_sequence_parameters_for_megatron(
f" - If both are enabled, the minimum pad factor is `cp_size * 2 * tp_size`."
)

# packed sequence length, after splitted to TP and CP domains, needs to be divisible by 128 if using blockwise FP8, and divisible by 16 if using other FP8 recipes.
# packed sequence length, after sharding to TP and CP domains, needs to be divisible
# by a recipe-dependent divisor:
# blockwise FP8 : 128 (cublas block size)
# MXFP8 : 32 (MXFP8 block size)
# other FP8 : 16
# HybridEP+flex : 128 (MAX_NUM_OF_TOKENS_PER_RANK must be divisible by
# NUM_OF_TOKENS_PER_CHUNK=128 in deep_ep JIT kernels)
# When multiple constraints apply, take the max (128 is a multiple of 32/16).
divisor = 1
if use_fp8:
divisor = 16
if fp8_cfg["fp8_recipe"] == "blockwise":
divisor = 128
divisor = max(divisor, 128)
elif fp8_cfg["fp8_recipe"] == "mxfp8":
divisor = 32
divisor = max(divisor, 32)
else:
divisor = max(divisor, 16)
if (
megatron_cfg.get("moe_token_dispatcher_type") == "flex"
and megatron_cfg.get("moe_flex_dispatcher_backend") == "hybridep"
):
divisor = max(divisor, 128)
if divisor > 1:
pad_packed_seq_to_multiple_of = divisor
if cp_size > 1:
pad_packed_seq_to_multiple_of *= cp_size * 2
Expand Down
43 changes: 43 additions & 0 deletions nemo_rl/models/megatron/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,49 @@ def _apply_moe_config(model_cfg: Any, config: PolicyConfig) -> None:
"moe_shared_expert_overlap"
]

# HybridEP settings for MoE expert parallelism
# See: https://github.com/deepseek-ai/DeepEP/tree/hybrid-ep
if "moe_flex_dispatcher_backend" in config["megatron_cfg"]:
model_cfg.moe_flex_dispatcher_backend = config["megatron_cfg"][
"moe_flex_dispatcher_backend"
]
if "moe_hybridep_num_sms" in config["megatron_cfg"]:
model_cfg.moe_hybridep_num_sms = config["megatron_cfg"]["moe_hybridep_num_sms"]
Comment thread
terrykong marked this conversation as resolved.

# HybridEP environment variables
# These are required by DeepEP's hybrid-ep branch for NVLink domain configuration.
# Users can set them explicitly via config, or they will be auto-computed with a warning.
if config["megatron_cfg"].get("moe_flex_dispatcher_backend") == "hybridep":
ep_size = model_cfg.expert_model_parallel_size

# NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN
if "hybridep_num_ranks_per_nvlink_domain" in config["megatron_cfg"]:
val = config["megatron_cfg"]["hybridep_num_ranks_per_nvlink_domain"]
os.environ["NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN"] = str(val)
elif "NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN" not in os.environ:
default_val = min(ep_size, 64)
os.environ["NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN"] = str(default_val)
warnings.warn(
f"HybridEP: NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN not configured. "
f"Auto-setting to min(expert_model_parallel_size={ep_size}, 64) = {default_val}. "
f"Set 'hybridep_num_ranks_per_nvlink_domain' in megatron_cfg to override.",
stacklevel=2,
)

# USE_MNNVL
if "hybridep_use_mnnvl" in config["megatron_cfg"]:
val = config["megatron_cfg"]["hybridep_use_mnnvl"]
os.environ["USE_MNNVL"] = str(int(val))
elif "USE_MNNVL" not in os.environ:
default_val = int(ep_size > 4)
os.environ["USE_MNNVL"] = str(default_val)
warnings.warn(
f"HybridEP: USE_MNNVL not configured. "
f"Auto-setting to int(expert_model_parallel_size={ep_size} > 4) = {default_val}. "
f"Set 'hybridep_use_mnnvl' in megatron_cfg to override.",
stacklevel=2,
)

model_cfg.moe_permute_fusion = config["megatron_cfg"]["moe_permute_fusion"]


Expand Down
8 changes: 8 additions & 0 deletions nemo_rl/models/policy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,14 @@ class MegatronConfig(TypedDict):
moe_token_dispatcher_type: str
# Can be used only with 'alltoall' token dispatcher
moe_shared_expert_overlap: bool
# HybridEP settings for MoE expert parallelism (requires moe_token_dispatcher_type='flex')
# See: https://github.com/deepseek-ai/DeepEP/tree/hybrid-ep
moe_flex_dispatcher_backend: NotRequired[str]
moe_hybridep_num_sms: NotRequired[int]
# Number of HybridEP ranks per NVLink domain (default: min(expert_model_parallel_size, 64))
hybridep_num_ranks_per_nvlink_domain: NotRequired[int]
# Enable multi-node NVLink support (default: expert_model_parallel_size > 4)
hybridep_use_mnnvl: NotRequired[bool]
peft: NotRequired[MegatronPeftConfig | MegatronPeftConfigDisabled]
optimizer: MegatronOptimizerConfig
scheduler: MegatronSchedulerConfig
Expand Down
4 changes: 2 additions & 2 deletions nemo_rl/models/policy/interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def get_logprobs(

Returns:
BatchedDataDict containing:
- logprobs: Tensor of logprobs of actions
- ``logprobs``: Tensor of logprobs of actions
"""
pass

Expand All @@ -82,7 +82,7 @@ def get_reference_policy_logprobs(

Returns:
BatchedDataDict containing:
- logprobs: Tensor of logprobs of actions
- ``logprobs``: Tensor of logprobs of actions
"""
pass

Expand Down
6 changes: 3 additions & 3 deletions nemo_rl/models/policy/workers/megatron_policy_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,9 +765,9 @@ def generate(
data: BatchedDataDict containing input_ids and input_lengths tensors
Returns:
BatchedDataDict conforming to GenerationOutputSpec:
- output_ids: input + generated token IDs
- logprobs: Log probabilities for each token
- generation_lengths: Lengths of each response
- ``output_ids``: input + generated token IDs
- ``logprobs``: Log probabilities for each token
- ``generation_lengths``: Lengths of each response
"""
# 512 bATCH SIZE (200 tokens)
no_grad = torch.no_grad()
Expand Down
12 changes: 8 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,17 @@ automodel = [
"causal-conv1d",
"nv-grouped-gemm",
"transformer-engine[pytorch,core_cu13] @ git+https://github.com/NVIDIA/TransformerEngine.git@v2.14.1",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@bfded34800dfec415b71503f8205181de90b2480",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@bfded34800dfec415b71503f8205181de90b2480 ; platform_machine == 'x86_64'",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@a48493600c4886c1b297aaa78db0e1ebc2d8dd6c ; platform_machine == 'aarch64'",
]
vllm = [
"cuda-python",
"deep_gemm @ git+https://github.com/deepseek-ai/DeepGEMM.git@7b6b5563b9d4c1ae07ffbce7f78ad3ac9204827c",
# deep_ep also needs libibverbs-dev
# sudo apt-get update
# sudo apt-get install libibverbs-dev
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@bfded34800dfec415b71503f8205181de90b2480",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@bfded34800dfec415b71503f8205181de90b2480 ; platform_machine == 'x86_64'",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@a48493600c4886c1b297aaa78db0e1ebc2d8dd6c ; platform_machine == 'aarch64'",
# +cu130 wheels from GitHub match torch (pytorch-cu130). PyPI can resolve to +cu12
# binaries on Linux (e.g. libcudart.so.12 vs torch cu13). See v0.17.1 release assets:
# https://github.com/vllm-project/vllm/releases/tag/v0.17.1
Expand Down Expand Up @@ -130,7 +132,8 @@ mcore = [
"flash-attn @ https://github.com/Dao-AILab/flash-attention/releases/download/v2.8.1/flash_attn-2.8.1+cu13torch2.10cxx11abiTRUE-cp313-cp313-linux_x86_64.whl ; sys_platform == 'linux' and platform_machine == 'x86_64'",
"flash-attn==2.8.1 ; sys_platform != 'linux' or (platform_machine != 'aarch64' and platform_machine != 'x86_64')",
"emerging-optimizers==0.2.0",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@bfded34800dfec415b71503f8205181de90b2480",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@bfded34800dfec415b71503f8205181de90b2480 ; platform_machine == 'x86_64'",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@a48493600c4886c1b297aaa78db0e1ebc2d8dd6c ; platform_machine == 'aarch64'",
]
modelopt = ["nvidia-modelopt"]
nvrx = [
Expand Down Expand Up @@ -277,7 +280,8 @@ override-dependencies = [
"llguidance>=1.3.0,<1.4.0",
# Override setuptools range in other dependencies to address CVE GHSA-58pv-8j8x-9vj2
"setuptools>=80.10.2",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@bfded34800dfec415b71503f8205181de90b2480",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@bfded34800dfec415b71503f8205181de90b2480 ; platform_machine == 'x86_64'",
"deep_ep @ git+https://github.com/deepseek-ai/DeepEP.git@a48493600c4886c1b297aaa78db0e1ebc2d8dd6c ; platform_machine == 'aarch64'",
# Note: flashinfer versions are pinned per-extra (vllm uses 0.6.4, sglang uses 0.6.7.post2)
# since vllm and sglang extras are mutually exclusive and have different requirements.
# Override megatron-core's flashinfer~=0.5.0 constraint to allow both vllm (0.6.4) and sglang (0.6.7.post2)
Expand Down
Loading
Loading