Skip to content

Commit aec3f15

Browse files
committed
refactor padding
1 parent a1853a7 commit aec3f15

12 files changed

Lines changed: 315 additions & 128 deletions

File tree

‎megatron/core/datasets/data_schedule.py‎

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
next_hdp_group,
2121
reroute_samples_to_dcp_ranks,
2222
)
23-
from megatron.core.packed_seq_params import PackedSeqParams
23+
from megatron.core.packed_seq_params import PackedSeqParams, pad_sequence_for_thd
2424
from megatron.core.pipeline_parallel.hybrid_cp_schedule import BalancedCPScheduler
2525
from megatron.core.process_groups_config import ProcessGroupCollection
2626

@@ -486,8 +486,8 @@ def get_batch_on_this_rank_for_sequence_packing(
486486
data_iterator (Iterator): The data iterator to get the batch from.
487487
mtp_on_this_rank (bool): Whether to use multi-token prediction.
488488
vp_stage (Optional[int]): The stage of the pipeline.
489-
config: Model parallel config used for THD CUDA Graph padding. When None
490-
or config.max_seqlen_per_dp_cp_rank is None, no padding is applied.
489+
config: Model parallel config used for optional THD packed-sequence padding.
490+
When None or config.pad_packed_seq_alignment is None, no padding is applied.
491491
Returns:
492492
tuple of (tokens, labels, loss_mask, attention_mask, position_ids,
493493
packed_seq_params, padding_mask)
@@ -660,9 +660,8 @@ def get_batch_on_this_rank_for_sequence_packing(
660660
else None
661661
)
662662

663-
# Transformer Engine has a bug of cu_seqlens, we must treat cu_seqlens_padded as cu_seqlens to
664-
# get the correct result.
665-
# TODO: Revert this workaround once TE fixes the issue.
663+
# Use padded cumulative lengths for THD partitioning so token slices follow
664+
# the padded sequence boundaries consumed by attention kernels.
666665
packed_seq_params = PackedSeqParams(
667666
qkv_format="thd",
668667
cu_seqlens_q=cu_seqlens_padded,
@@ -675,24 +674,35 @@ def get_batch_on_this_rank_for_sequence_packing(
675674
cp_group=cp_group,
676675
)
677676

678-
# Pad to static shapes for THD + CUDA Graph when requested.
677+
# Pad the already-packed THD tensors at the end when requested. CUDA Graph
678+
# additionally pads cu_seqlens tensors to thd_max_num_seqs + 1 entries.
679679
padding_mask = None
680-
if (
681-
config is not None
682-
and getattr(config, 'max_seqlen_per_dp_cp_rank', None) is not None
683-
and packed_seq_params is not None
684-
):
685-
from megatron.core.packed_seq_params import pad_thd_for_cuda_graph
686-
680+
pad_alignment = (
681+
getattr(config, 'pad_packed_seq_alignment', None) if config is not None else None
682+
)
683+
if pad_alignment is not None and packed_seq_params is not None:
684+
cuda_graph_static = getattr(config, 'cuda_graph_impl', 'none') != 'none'
685+
static_target = (
686+
pad_alignment == 0 and getattr(config, 'max_seqlen_per_dp_cp_rank', None) is not None
687+
)
688+
if cuda_graph_static or static_target:
689+
target_len = config.max_seqlen_per_dp_cp_rank
690+
max_num_seqs = config.thd_max_num_seqs
691+
alignment = None
692+
else:
693+
target_len = None
694+
max_num_seqs = None
695+
alignment = max_seqlen if pad_alignment == 0 else pad_alignment
687696
tokens, labels, loss_mask, position_ids, packed_seq_params, padding_mask = (
688-
pad_thd_for_cuda_graph(
697+
pad_sequence_for_thd(
689698
tokens,
690699
labels,
691700
loss_mask,
692701
position_ids,
693702
packed_seq_params,
694-
max_seqlen=config.max_seqlen_per_dp_cp_rank,
695-
max_num_seqs=config.thd_max_num_seqs,
703+
alignment=alignment,
704+
target_len=target_len,
705+
max_num_seqs=max_num_seqs,
696706
)
697707
)
698708

‎megatron/core/datasets/data_schedule_utils.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,8 @@ def get_cp_slice_for_thd(batch, cp_group):
2626
if cp_size <= 1:
2727
return
2828
cp_rank = cp_group.rank()
29-
# Transformer Engine has a bug of cu_seqlens, we must treat cu_seqlens_padded as
30-
# cu_seqlens to get the correct result.
31-
# TODO: Revert this workaround once TE fixes the issue.
29+
# Partition with padded cumulative lengths so CP slices match the THD
30+
# sequence boundaries consumed by attention kernels.
3231
cu_seqlens = batch["cu_seqlens_padded"]
3332
# Use cu_seqlens_padded[-1] for total_tokens instead of batch['tokens'].size(0):
3433
# under VPP, the last PP stage has labels/loss_mask but no tokens, so

‎megatron/core/model_parallel_config.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,25 @@ class ModelParallelConfig:
8787
default_dynamic_cp: Dynamic-CP scheduler for packed sequence balancing.
8888
"""
8989

90+
pad_packed_seq_alignment: Optional[int] = field(
91+
default=None,
92+
metadata={
93+
"argparse_meta": {
94+
"arg_names": ["--pad-packed-seq-alignment"],
95+
"nargs": "?",
96+
"const": 0,
97+
"type": int,
98+
}
99+
},
100+
)
101+
"""Pad THD packed sequence tensors after packing.
102+
103+
If set without a value, the caller chooses a static target; the standard THD
104+
training paths use max_seqlen_per_dp_cp_rank and pad cu_seqlens tensors to
105+
thd_max_num_seqs + 1 entries. If set to a positive integer N, token-like
106+
tensors are padded to a multiple of N and cu_seqlens metadata is preserved.
107+
"""
108+
90109
expert_model_parallel_size: int = 1
91110
"""Distributes Moe Experts across sub data parallel dimension."""
92111

@@ -463,6 +482,14 @@ def __post_init__(self):
463482
f"got {self.min_dynamic_context_parallel_size}"
464483
)
465484

485+
if self.pad_packed_seq_alignment is not None:
486+
if self.pad_packed_seq_alignment < 0:
487+
raise ValueError(
488+
"pad_packed_seq_alignment must be >= 0. Use the flag without a value "
489+
"to let the caller choose a static target, or pass a positive integer "
490+
"alignment."
491+
)
492+
466493
if self.sequence_parallel:
467494
if self.tensor_model_parallel_size <= 1:
468495
raise ValueError("Cannot use sequence parallelism without tensor parallelism")

‎megatron/core/models/common/embeddings/rope_utils.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,10 +293,8 @@ def _apply_rotary_pos_emb_thd(
293293
# sequence, no shift is needed.
294294
freq_pos = freq_pos + global_seq_start
295295

296-
# Same rationale as the seq_idx clamp above: padded positions can index
297-
# past `freqs`; they receive a known wrong-but-harmless freq that gets
298-
# masked away. If you suspect a real out-of-range bug, swap clamp for an
299-
# assert during development.
296+
# Padded positions can sit outside the frequency table. Clamp them into
297+
# range; downstream padding masks exclude those positions from the result.
300298
freq_pos = freq_pos.clamp(min=0, max=freqs.shape[0] - 1)
301299
freqs_packed = freqs[freq_pos]
302300

‎megatron/core/packed_seq_params.py‎

Lines changed: 135 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,9 @@ def _pad_seq_tensor(t: Optional[Tensor], target_len: int) -> Optional[Tensor]:
9090
def _pad_cu_seqlens(cu_seqlens: Optional[Tensor], target_entries: int) -> Optional[Tensor]:
9191
"""Pad a cu_seqlens tensor to exactly ``target_entries`` entries.
9292
93-
Asserts the actual entry count does not exceed ``target_entries``: this is
94-
the reviewer-flagged overflow case and corresponds to "too many packed
95-
sequences in this microbatch for thd_max_num_seqs". Failing fast prevents
96-
a silent CUDA-graph shape mismatch at replay.
93+
Asserts the actual entry count does not exceed ``target_entries``. An
94+
oversized pack cannot be represented by the configured static cu_seqlens
95+
buffer and would not match captured CUDA Graph replay shapes.
9796
"""
9897
if cu_seqlens is None:
9998
return None
@@ -114,33 +113,21 @@ def _pad_cu_seqlens(cu_seqlens: Optional[Tensor], target_entries: int) -> Option
114113
return padded
115114

116115

117-
def pad_thd_for_cuda_graph(
116+
def _round_up_to_alignment(value: int, alignment: int) -> int:
117+
assert alignment > 0, f"Packed sequence padding alignment must be > 0, got {alignment}."
118+
return ((value + alignment - 1) // alignment) * alignment
119+
120+
121+
def _resolve_thd_padding_lengths(
118122
tokens: Optional[Tensor],
119123
labels: Optional[Tensor],
120124
loss_mask: Optional[Tensor],
121125
position_ids: Optional[Tensor],
122126
packed_seq_params: PackedSeqParams,
123-
max_seqlen: int,
124-
max_num_seqs: int,
125-
) -> Tuple[
126-
Optional[Tensor],
127-
Optional[Tensor],
128-
Optional[Tensor],
129-
Optional[Tensor],
130-
PackedSeqParams,
131-
Optional[Tensor],
132-
]:
133-
"""Pad THD batch data to fixed sizes for CUDA Graph compatibility.
134-
135-
CUDA Graph requires static tensor shapes. This function pads:
136-
- tokens, labels, loss_mask, position_ids along dim=-1 to max_seqlen
137-
- cu_seqlens tensors to (max_num_seqs + 1) entries, filled with actual_T
138-
- Generates padding_mask for MoE aux loss exclusion
139-
140-
Returns:
141-
Padded (tokens, labels, loss_mask, position_ids, packed_seq_params, padding_mask)
142-
padding_mask: [1, max_seqlen] bool tensor, True at padding positions.
143-
"""
127+
target_len: Optional[int],
128+
alignment: Optional[int],
129+
) -> Tuple[int, int, int, int, torch.device, bool]:
130+
"""Resolve local/global THD padding lengths without changing tensors."""
144131

145132
actual_T = None
146133
mask_device = None
@@ -166,48 +153,23 @@ def pad_thd_for_cuda_graph(
166153
else parallel_state.get_context_parallel_world_size()
167154
)
168155
cp_rank = parallel_state.get_context_parallel_rank() if cp_size > 1 else 0
169-
max_seqlen_global = max_seqlen * cp_size
170-
171-
if actual_T is not None and packed_seq_params.cu_seqlens_q is not None:
172-
_cu = packed_seq_params.cu_seqlens_q
173-
_individual_lens = _cu[1:] - _cu[:-1]
174-
_max_individual = int(_individual_lens.max().item()) if _individual_lens.numel() > 0 else 0
175-
assert _max_individual <= max_seqlen_global, (
176-
f"Individual request length ({_max_individual}) exceeds the global max sequence length "
177-
f"({max_seqlen_global} = max_seqlen_per_dp_cp_rank {max_seqlen} * cp_size {cp_size}). "
178-
f"Each request must fit within the CUDA Graph static buffer after CP partitioning. "
179-
f"Increase --max-seqlen-per-dp-cp-rank or --seq-length, or filter out overlong "
180-
f"requests."
181-
)
182-
183-
tokens = _pad_seq_tensor(tokens, max_seqlen)
184-
labels = _pad_seq_tensor(labels, max_seqlen)
185-
loss_mask = _pad_seq_tensor(loss_mask, max_seqlen)
186-
position_ids = _pad_seq_tensor(position_ids, max_seqlen)
187156

188-
target_cu_entries = max_num_seqs + 1
189-
padded_params = PackedSeqParams(
190-
qkv_format=packed_seq_params.qkv_format,
191-
cu_seqlens_q=_pad_cu_seqlens(packed_seq_params.cu_seqlens_q, target_cu_entries),
192-
cu_seqlens_kv=_pad_cu_seqlens(packed_seq_params.cu_seqlens_kv, target_cu_entries),
193-
cu_seqlens_q_padded=_pad_cu_seqlens(
194-
packed_seq_params.cu_seqlens_q_padded, target_cu_entries
195-
),
196-
cu_seqlens_kv_padded=_pad_cu_seqlens(
197-
packed_seq_params.cu_seqlens_kv_padded, target_cu_entries
198-
),
199-
max_seqlen_q=max_seqlen_global,
200-
max_seqlen_kv=max_seqlen_global,
201-
local_cp_size=packed_seq_params.local_cp_size,
202-
cp_group=packed_seq_params.cp_group,
203-
)
157+
if target_len is None:
158+
assert alignment is not None, "Either target_len or alignment must be provided."
159+
global_target_len = _round_up_to_alignment(int(actual_T), alignment)
160+
else:
161+
global_target_len = int(target_len) * cp_size
204162

205163
if cp_size > 1:
206164
from megatron.core.extensions.transformer_engine import get_thd_partitioned_indices
207165

208166
if actual_T_is_local:
209167
local_actual_T = int(actual_T)
210-
local_max_seqlen = int(max_seqlen)
168+
local_target_len = (
169+
int(target_len)
170+
if target_len is not None
171+
else _round_up_to_alignment(local_actual_T, alignment)
172+
)
211173
else:
212174
local_actual_T = int(
213175
get_thd_partitioned_indices(
@@ -221,22 +183,125 @@ def pad_thd_for_cuda_graph(
221183
cp_rank,
222184
).numel()
223185
)
224-
local_max_seqlen = int(
186+
local_target_len = int(
225187
get_thd_partitioned_indices(
226188
(
227-
padded_params.cu_seqlens_q_padded
228-
if padded_params.cu_seqlens_q_padded is not None
229-
else padded_params.cu_seqlens_q
189+
packed_seq_params.cu_seqlens_q_padded
190+
if packed_seq_params.cu_seqlens_q_padded is not None
191+
else packed_seq_params.cu_seqlens_q
230192
),
231-
max_seqlen_global,
193+
global_target_len,
232194
cp_size,
233195
cp_rank,
234196
).numel()
235197
)
236-
padding_mask = (
237-
torch.arange(local_max_seqlen, device=mask_device).unsqueeze(0) >= local_actual_T
238-
)
239198
else:
240-
padding_mask = torch.arange(max_seqlen, device=mask_device).unsqueeze(0) >= actual_T
199+
local_actual_T = int(actual_T)
200+
local_target_len = global_target_len
201+
202+
return (
203+
int(actual_T),
204+
local_actual_T,
205+
local_target_len,
206+
global_target_len,
207+
mask_device,
208+
actual_T_is_local,
209+
)
210+
211+
212+
def pad_sequence_for_thd(
213+
tokens: Optional[Tensor],
214+
labels: Optional[Tensor],
215+
loss_mask: Optional[Tensor],
216+
position_ids: Optional[Tensor],
217+
packed_seq_params: PackedSeqParams,
218+
alignment: Optional[int] = None,
219+
target_len: Optional[int] = None,
220+
max_num_seqs: Optional[int] = None,
221+
) -> Tuple[
222+
Optional[Tensor],
223+
Optional[Tensor],
224+
Optional[Tensor],
225+
Optional[Tensor],
226+
PackedSeqParams,
227+
Optional[Tensor],
228+
]:
229+
"""Pad packed THD tensors after packing.
230+
231+
This appends padding tokens to token-like tensors and returns a padding mask
232+
for MoE auxiliary-loss/routing paths. When ``max_num_seqs`` is provided, the
233+
four cu_seqlens tensors are also padded to ``max_num_seqs + 1`` entries;
234+
this is required by CUDA Graph replay because those tensors are graph inputs.
235+
236+
Returns:
237+
Padded (tokens, labels, loss_mask, position_ids, packed_seq_params, padding_mask)
238+
padding_mask: [1, target] bool tensor, True at padding positions.
239+
"""
240+
assert (alignment is None) != (target_len is None), (
241+
"Exactly one of alignment or target_len must be provided for THD padding."
242+
)
243+
244+
actual_T, local_actual_T, local_target_len, global_target_len, mask_device, _ = (
245+
_resolve_thd_padding_lengths(
246+
tokens,
247+
labels,
248+
loss_mask,
249+
position_ids,
250+
packed_seq_params,
251+
target_len=target_len,
252+
alignment=alignment,
253+
)
254+
)
255+
256+
if actual_T is not None and packed_seq_params.cu_seqlens_q is not None:
257+
_cu = packed_seq_params.cu_seqlens_q
258+
_individual_lens = _cu[1:] - _cu[:-1]
259+
_max_individual = int(_individual_lens.max().item()) if _individual_lens.numel() > 0 else 0
260+
assert _max_individual <= global_target_len, (
261+
f"Individual request length ({_max_individual}) exceeds the global max sequence length "
262+
f"({global_target_len}). Increase --max-seqlen-per-dp-cp-rank / alignment, "
263+
f"or filter out overlong requests."
264+
)
265+
266+
tokens = _pad_seq_tensor(tokens, local_target_len)
267+
labels = _pad_seq_tensor(labels, local_target_len)
268+
loss_mask = _pad_seq_tensor(loss_mask, local_target_len)
269+
position_ids = _pad_seq_tensor(position_ids, local_target_len)
270+
271+
target_cu_entries = None if max_num_seqs is None else max_num_seqs + 1
272+
padded_params = PackedSeqParams(
273+
qkv_format=packed_seq_params.qkv_format,
274+
cu_seqlens_q=(
275+
packed_seq_params.cu_seqlens_q
276+
if target_cu_entries is None
277+
else _pad_cu_seqlens(packed_seq_params.cu_seqlens_q, target_cu_entries)
278+
),
279+
cu_seqlens_kv=(
280+
packed_seq_params.cu_seqlens_kv
281+
if target_cu_entries is None
282+
else _pad_cu_seqlens(packed_seq_params.cu_seqlens_kv, target_cu_entries)
283+
),
284+
cu_seqlens_q_padded=(
285+
packed_seq_params.cu_seqlens_q_padded
286+
if target_cu_entries is None
287+
else _pad_cu_seqlens(packed_seq_params.cu_seqlens_q_padded, target_cu_entries)
288+
),
289+
cu_seqlens_kv_padded=(
290+
packed_seq_params.cu_seqlens_kv_padded
291+
if target_cu_entries is None
292+
else _pad_cu_seqlens(packed_seq_params.cu_seqlens_kv_padded, target_cu_entries)
293+
),
294+
max_seqlen_q=(
295+
global_target_len if target_cu_entries is not None else packed_seq_params.max_seqlen_q
296+
),
297+
max_seqlen_kv=(
298+
global_target_len if target_cu_entries is not None else packed_seq_params.max_seqlen_kv
299+
),
300+
local_cp_size=packed_seq_params.local_cp_size,
301+
cp_group=packed_seq_params.cp_group,
302+
total_tokens=local_target_len if target_cu_entries is None else None,
303+
)
304+
305+
padding_mask = torch.arange(local_target_len, device=mask_device).unsqueeze(0) >= local_actual_T
241306

242307
return tokens, labels, loss_mask, position_ids, padded_params, padding_mask

0 commit comments

Comments
 (0)