Skip to content

Commit f54f47b

Browse files
gaohongkuiyaoyu-33
andauthored
[model] fix: clamp the first CP chunk slice in preprocess_packed_seqs (#5614)
Signed-off-by: gaohongkui <gaohongkui1021@163.com> Co-authored-by: Yu Yao <54727607+yaoyu-33@users.noreply.github.com>
1 parent bc09410 commit f54f47b

3 files changed

Lines changed: 63 additions & 8 deletions

File tree

  • src/megatron/bridge/models
  • tests/unit_tests/models/qwen_vl/modelling_qwen3_vl

‎src/megatron/bridge/models/exaone/exaone45/modelling_exaone45/utils.py‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -633,14 +633,24 @@ def preprocess_packed_seqs(
633633
start_idx = cu_seqlens_padded_cpu[i] // cp_size
634634
# split to 2 chunks
635635
d = input_ids[i, attention_mask[i]]
636-
input_ids_rmpad[start_idx : start_idx + half_seqlen] = d[
637-
half_seqlen * cp_rank : half_seqlen * (cp_rank + 1)
638-
]
636+
# Chunk indices are computed in the *padded* coordinate space
637+
# (seqlen_padded_i), while d holds only the *valid* tokens. When a row's
638+
# valid length is shorter than the alignment padding, the first-chunk
639+
# slice can start past the end of d and yield an empty tensor, which
640+
# raises on assignment:
641+
# RuntimeError: The expanded size of the tensor (2) must match the
642+
# existing size (0) at non-singleton dimension 0
643+
# Clamp it the same way the remaining chunk below already is.
644+
first_start = half_seqlen * cp_rank
645+
first_end = min(half_seqlen * (cp_rank + 1), d.shape[0])
646+
first_len = max(first_end - first_start, 0)
647+
if first_len > 0:
648+
input_ids_rmpad[start_idx : start_idx + first_len] = d[first_start:first_end]
639649

640650
remain_start = seqlen_padded_i - half_seqlen * (cp_rank + 1)
641651
remain_end = seqlen_padded_i - half_seqlen * cp_rank
642652
remain_end = min(remain_end, d.shape[0])
643-
remain_len = remain_end - remain_start
653+
remain_len = max(remain_end - remain_start, 0)
644654
if remain_len > 0:
645655
input_ids_rmpad[start_idx + half_seqlen : start_idx + half_seqlen + remain_len] = d[
646656
remain_start:remain_end

‎src/megatron/bridge/models/qwen_vl/modelling_qwen3_vl/utils.py‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -767,14 +767,24 @@ def preprocess_packed_seqs(
767767
start_idx = cu_seqlens_padded_cpu[i] // cp_size
768768
# split to 2 chunks
769769
d = input_ids[i, attention_mask[i]]
770-
input_ids_rmpad[start_idx : start_idx + half_seqlen] = d[
771-
half_seqlen * cp_rank : half_seqlen * (cp_rank + 1)
772-
]
770+
# Chunk indices are computed in the *padded* coordinate space
771+
# (seqlen_padded_i), while d holds only the *valid* tokens. When a row's
772+
# valid length is shorter than the alignment padding, the first-chunk
773+
# slice can start past the end of d and yield an empty tensor, which
774+
# raises on assignment:
775+
# RuntimeError: The expanded size of the tensor (2) must match the
776+
# existing size (0) at non-singleton dimension 0
777+
# Clamp it the same way the remaining chunk below already is.
778+
first_start = half_seqlen * cp_rank
779+
first_end = min(half_seqlen * (cp_rank + 1), d.shape[0])
780+
first_len = max(first_end - first_start, 0)
781+
if first_len > 0:
782+
input_ids_rmpad[start_idx : start_idx + first_len] = d[first_start:first_end]
773783

774784
remain_start = seqlen_padded_i - half_seqlen * (cp_rank + 1)
775785
remain_end = seqlen_padded_i - half_seqlen * cp_rank
776786
remain_end = min(remain_end, d.shape[0])
777-
remain_len = remain_end - remain_start
787+
remain_len = max(remain_end - remain_start, 0)
778788
if remain_len > 0:
779789
input_ids_rmpad[start_idx + half_seqlen : start_idx + half_seqlen + remain_len] = d[
780790
remain_start:remain_end

‎tests/unit_tests/models/qwen_vl/modelling_qwen3_vl/test_utils.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,41 @@ def test_preprocess_packed_seqs(self):
507507

508508
self.destroy_parallel_state()
509509

510+
@pytest.mark.skipif(
511+
not torch.cuda.is_available() or int(os.environ.get("WORLD_SIZE", "1")) < 2,
512+
reason="Requires at least 2 GPUs",
513+
)
514+
def test_preprocess_packed_seqs_row_shorter_than_alignment(self):
515+
"""A row whose valid length is below the alignment padding must not raise.
516+
517+
Chunk indices come from the alignment-padded length while ``d`` holds only the
518+
valid tokens, so on the higher CP ranks the first-chunk slice can start past the
519+
end of ``d``. Assigning that empty slice used to raise::
520+
521+
RuntimeError: The expanded size of the tensor (1) must match the existing
522+
size (0) at non-singleton dimension 0
523+
524+
With tp=1/cp=2 the alignment is 4, so a single-token row is the smallest case
525+
that reaches the out-of-range slice on cp_rank 1.
526+
"""
527+
tp_size = 1
528+
cp_size = 2
529+
self._setup_parallel_state(tp_size=tp_size, cp_size=cp_size)
530+
531+
batch_size, seq_len = 2, 8
532+
input_ids = torch.randint(0, 1000, (batch_size, seq_len))
533+
attention_mask = torch.zeros(batch_size, seq_len, dtype=torch.bool)
534+
attention_mask[0, :] = True # a full-length row
535+
attention_mask[1, 0] = True # a single valid token
536+
537+
ids_out, packed = preprocess_packed_seqs(input_ids, attention_mask, pre_process=True)
538+
assert packed.max_seqlen_q == packed.max_seqlen_kv
539+
assert ids_out.shape[0] == 1
540+
# 8 valid tokens need no padding; the 1-token row is padded to align_size = 4.
541+
assert ids_out.shape[1] == (8 + 4) // cp_size
542+
543+
self.destroy_parallel_state()
544+
510545
@pytest.mark.skipif(
511546
not torch.cuda.is_available() or int(os.environ.get("WORLD_SIZE", "1")) < 2,
512547
reason="Requires at least 2 GPUs",

0 commit comments

Comments
 (0)