Skip to content

Commit 81e1ddc

Browse files
committed
refactor
Signed-off-by: HaochenYuan <haocheny@nvidia.com>
1 parent 241dc40 commit 81e1ddc

4 files changed

Lines changed: 7 additions & 192 deletions

File tree

‎megatron/core/model_parallel_config.py‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,6 @@ class ModelParallelConfig:
6262
each rank when using hybrid_context_parallel.
6363
"""
6464

65-
sequence_packing_scheduler: Optional[Literal['dp_balanced', 'default_dynamic_cp']] = None
66-
"""
67-
Scheduler for sequence packing.
68-
dp_balanced: DP-balanced scheduler for sequence packing.
69-
default_dynamic_cp: Dynamic-CP scheduler for packed sequence balancing.
70-
"""
71-
7265
hybrid_context_parallel: bool = False
7366
"""
7467
If true, enables hybrid context parallel. This is used to balance the workload of

‎megatron/core/transformer/moe/token_dispatcher.py‎

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,9 +1033,8 @@ def __init__(
10331033

10341034
self.moe_expert_rank_capacity_factor = self.config.moe_expert_rank_capacity_factor
10351035
self.over_budget = torch.zeros(1, dtype=torch.bool, device='cuda')
1036-
# THD sequence packing can produce different token counts per rank.
1037-
# HybridEP dispatch expects equal per-rank input sizes, so metadata and
1038-
# hidden states are padded to the group-wide max and trimmed in combine.
1036+
# HybridEP dispatch expects equal per-rank input sizes. When requested,
1037+
# variable token counts are padded to the group-wide max and trimmed in combine.
10391038
self._original_num_tokens: Optional[int] = None
10401039
self._padded_num_tokens: Optional[int] = None
10411040

@@ -1044,11 +1043,7 @@ def setup_metadata(self, routing_map: torch.Tensor, probs: torch.Tensor):
10441043
self._original_num_tokens = num_tokens
10451044

10461045
padded_num_tokens = num_tokens
1047-
equalize_thd_token_counts = (
1048-
self.config.sequence_packing_scheduler is not None
1049-
or self.config.moe_hybridep_pad_variable_tokens
1050-
)
1051-
if equalize_thd_token_counts:
1046+
if self.config.moe_hybridep_pad_variable_tokens:
10521047
# Use the actual tp_ep max so all ranks in the MoE communication
10531048
# group pass the same token count to HybridEP.
10541049
max_num_tokens_across_ep = torch.tensor(
@@ -1063,7 +1058,7 @@ def setup_metadata(self, routing_map: torch.Tensor, probs: torch.Tensor):
10631058

10641059
routing_map = routing_map.reshape(num_tokens, self.num_experts)
10651060
probs = probs.reshape(num_tokens, self.num_experts)
1066-
if equalize_thd_token_counts and padded_num_tokens > num_tokens:
1061+
if padded_num_tokens > num_tokens:
10671062
pad_rows = padded_num_tokens - num_tokens
10681063
routing_map = torch.cat(
10691064
[routing_map, routing_map.new_zeros((pad_rows, self.num_experts))], dim=0

‎megatron/core/transformer/transformer_config.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -801,8 +801,8 @@ class TransformerConfig(ModelParallelConfig):
801801

802802
moe_hybridep_pad_variable_tokens: bool = False
803803
"""Pad uneven local token counts to the HybridEP group maximum before dispatch.
804-
This is needed when the frontend supplies locally packed THD inputs whose token counts
805-
can differ across ranks, without using Megatron Core's sequence_packing_scheduler.
804+
Enable only when HybridEP inputs can have different token counts across ranks.
805+
Leave disabled when inputs are already statically padded to equal token counts.
806806
"""
807807

808808
moe_per_layer_logging: bool = False

‎tests/unit_tests/transformer/moe/test_token_dispatcher.py‎

Lines changed: 1 addition & 174 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
# Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
22

33
import dataclasses
4-
import math
54

65
import pytest
76
import torch
87

98
from megatron.core import config, parallel_state
10-
from megatron.core.models.gpt.gpt_layer_specs import (
11-
get_gpt_layer_local_submodules,
12-
get_gpt_layer_with_transformer_engine_spec,
13-
)
14-
from megatron.core.packed_seq_params import PackedSeqParams
9+
from megatron.core.models.gpt.gpt_layer_specs import get_gpt_layer_local_submodules
1510
from megatron.core.transformer.moe.fused_a2a import reset_hybrid_ep_buffer
1611
from megatron.core.transformer.moe.moe_layer import MoELayer, MoESubmodules
1712
from megatron.core.transformer.moe.moe_utils import get_capacity
1813
from megatron.core.transformer.spec_utils import get_submodules
19-
from megatron.core.transformer.transformer_block import TransformerBlock
2014
from megatron.core.transformer.transformer_config import TransformerConfig
2115
from megatron.core.typed_torch import apply_module
2216
from megatron.core.utils import is_te_min_version
@@ -425,173 +419,6 @@ def is_hybrid_ep_available():
425419
return HAVE_HYBRIDEP
426420

427421

428-
def _round_up(value, divisor):
429-
return value if divisor <= 1 else (value + divisor - 1) // divisor * divisor
430-
431-
432-
def _get_thd_padded_seqlens(seqlens, cp_size, tp_size):
433-
# This follows the runtime packed-sequence path used by the Moonlight script:
434-
# per-sequence lengths must be CP partitionable, and the packed token count
435-
# must be even for TP/SP slicing.
436-
cp_divisor = 2 * cp_size if cp_size > 1 else 1
437-
padded_seqlens = [_round_up(seqlen, cp_divisor) for seqlen in seqlens]
438-
total_seqlen = sum(padded_seqlens)
439-
total_alignment = math.lcm(cp_divisor, tp_size)
440-
padded_seqlens[-1] += _round_up(total_seqlen, total_alignment) - total_seqlen
441-
return padded_seqlens
442-
443-
444-
def _to_cu_seqlens(seqlens):
445-
cu_seqlens = torch.empty(len(seqlens) + 1, dtype=torch.int32, device="cuda")
446-
cu_seqlens[0] = 0
447-
cu_seqlens[1:] = torch.cumsum(torch.tensor(seqlens, dtype=torch.int32, device="cuda"), dim=0)
448-
return cu_seqlens
449-
450-
451-
def _make_thd_packed_seq_params(seqlens, cp_size, tp_size):
452-
padded_seqlens = _get_thd_padded_seqlens(seqlens, cp_size, tp_size)
453-
cu_seqlens_padded = _to_cu_seqlens(padded_seqlens)
454-
max_seqlen = max(padded_seqlens)
455-
# Match get_batch_on_this_rank_for_sequence_packing(): TE consumes padded
456-
# cumulative lengths as both cu_seqlens and cu_seqlens_padded for THD.
457-
return PackedSeqParams(
458-
qkv_format="thd",
459-
cu_seqlens_q=cu_seqlens_padded,
460-
cu_seqlens_kv=cu_seqlens_padded,
461-
cu_seqlens_q_padded=cu_seqlens_padded,
462-
cu_seqlens_kv_padded=cu_seqlens_padded,
463-
max_seqlen_q=max_seqlen,
464-
max_seqlen_kv=max_seqlen,
465-
)
466-
467-
468-
def _make_sharded_thd_hidden_states(seqlens, hidden_size, cp_size, tp_size, dtype):
469-
padded_seqlens = _get_thd_padded_seqlens(seqlens, cp_size, tp_size)
470-
padded_sequences = []
471-
for seqlen, padded_seqlen in zip(seqlens, padded_seqlens):
472-
sequence = torch.randn(seqlen, hidden_size, device="cuda", dtype=dtype)
473-
if padded_seqlen > seqlen:
474-
sequence = torch.cat(
475-
[
476-
sequence,
477-
torch.zeros(padded_seqlen - seqlen, hidden_size, device="cuda", dtype=dtype),
478-
],
479-
dim=0,
480-
)
481-
padded_sequences.append(sequence)
482-
483-
hidden_states = torch.cat(padded_sequences, dim=0)
484-
if cp_size > 1:
485-
import transformer_engine_torch as tex
486-
487-
cu_seqlens_padded = _to_cu_seqlens(padded_seqlens)
488-
cp_rank = parallel_state.get_context_parallel_rank()
489-
index = tex.thd_get_partitioned_indices(
490-
cu_seqlens_padded, hidden_states.shape[0], cp_size, cp_rank
491-
)
492-
hidden_states = hidden_states.index_select(0, index)
493-
494-
tp_rank = parallel_state.get_tensor_model_parallel_rank()
495-
sequence_parallel_length = hidden_states.shape[0] // tp_size
496-
hidden_states = hidden_states[
497-
tp_rank * sequence_parallel_length : (tp_rank + 1) * sequence_parallel_length
498-
]
499-
return hidden_states.unsqueeze(1).contiguous().requires_grad_(True)
500-
501-
502-
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA not available")
503-
@pytest.mark.skipif(
504-
Utils.world_size % 8 != 0, reason="requires world size divisible by 8 for pp2/cp2/tp2/ep2/etp2"
505-
)
506-
@pytest.mark.internal
507-
@pytest.mark.parametrize("dispatcher", ["alltoall", "deepep", "hybridep"])
508-
def test_sequence_packing_thd_e2e_proxy_model(dispatcher):
509-
"""Run packed THD attention + MoE forward/backward with major parallelisms enabled."""
510-
if not is_te_min_version("2.9.0"):
511-
pytest.skip("SFT sequence packing requires Transformer Engine >= 2.9.0")
512-
if dispatcher == "deepep" and not is_deep_ep_available():
513-
pytest.skip("Deep EP is not available")
514-
if dispatcher == "hybridep" and not is_hybrid_ep_available():
515-
pytest.skip("Hybrid EP is not available")
516-
517-
tp_size, pp_size, cp_size, ep_size, etp_size = 2, 2, 2, 2, 2
518-
Utils.initialize_model_parallel(
519-
tensor_model_parallel_size=tp_size,
520-
pipeline_model_parallel_size=pp_size,
521-
context_parallel_size=cp_size,
522-
expert_model_parallel_size=ep_size,
523-
expert_tensor_parallel_size=etp_size,
524-
)
525-
_set_random_seed(seed_=123, data_parallel_random_init=False)
526-
527-
try:
528-
spec = get_gpt_layer_with_transformer_engine_spec(num_experts=4, moe_grouped_gemm=False)
529-
transformer_config = TransformerConfig(
530-
num_layers=4,
531-
hidden_size=1024,
532-
ffn_hidden_size=2048,
533-
moe_ffn_hidden_size=2048,
534-
num_attention_heads=8,
535-
tensor_model_parallel_size=tp_size,
536-
pipeline_model_parallel_size=pp_size,
537-
context_parallel_size=cp_size,
538-
expert_model_parallel_size=ep_size,
539-
expert_tensor_parallel_size=etp_size,
540-
sequence_parallel=True,
541-
sequence_packing_scheduler="dp_balanced",
542-
max_seqlen_per_dp_cp_rank=1024,
543-
cp_comm_type="p2p",
544-
num_moe_experts=4,
545-
moe_router_topk=2,
546-
moe_router_load_balancing_type="aux_loss",
547-
moe_token_dispatcher_type=(
548-
"flex" if dispatcher in ("deepep", "hybridep") else dispatcher
549-
),
550-
moe_flex_dispatcher_backend=(
551-
dispatcher if dispatcher in ("deepep", "hybridep") else "deepep"
552-
),
553-
moe_grouped_gemm=False,
554-
moe_router_dtype="fp32",
555-
params_dtype=torch.bfloat16,
556-
pipeline_dtype=torch.bfloat16,
557-
autocast_dtype=torch.bfloat16,
558-
bf16=True,
559-
add_bias_linear=False,
560-
attention_dropout=0.0,
561-
hidden_dropout=0.0,
562-
use_cpu_initialization=True,
563-
)
564-
transformer_block = TransformerBlock(transformer_config, spec).cuda().to(torch.bfloat16)
565-
566-
torch.manual_seed(1000 + torch.distributed.get_rank())
567-
seqlens = [257, 509, 1021]
568-
hidden_states = _make_sharded_thd_hidden_states(
569-
seqlens, transformer_config.hidden_size, cp_size, tp_size, torch.bfloat16
570-
)
571-
packed_seq_params = _make_thd_packed_seq_params(seqlens, cp_size, tp_size)
572-
573-
output = transformer_block(
574-
hidden_states=hidden_states, attention_mask=None, packed_seq_params=packed_seq_params
575-
)
576-
assert output.shape == hidden_states.shape
577-
assert torch.isfinite(output).all()
578-
579-
loss = output.float().square().mean()
580-
loss.backward()
581-
582-
assert hidden_states.grad is not None
583-
assert hidden_states.grad.shape == hidden_states.shape
584-
assert torch.isfinite(hidden_states.grad).all()
585-
assert any(
586-
param.grad is not None and torch.isfinite(param.grad).all()
587-
for param in transformer_block.parameters()
588-
if param.requires_grad
589-
)
590-
finally:
591-
reset_hybrid_ep_buffer()
592-
Utils.destroy_model_parallel()
593-
594-
595422
@pytest.mark.skipif(
596423
not is_deep_ep_available() and not is_hybrid_ep_available(),
597424
reason="Deep EP and Hybrid EP are not available",

0 commit comments

Comments
 (0)