Skip to content

Commit e4cb868

Browse files
authored
fix(recipe): verify GLM-5.2 RP2 pretraining and packed SFT (#5344)
Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
1 parent 7f239ca commit e4cb868

14 files changed

Lines changed: 568 additions & 186 deletions

File tree

‎examples/model_verification_cards/glm5-2/card.yaml‎

Lines changed: 111 additions & 77 deletions
Large diffs are not rendered by default.

‎src/megatron/bridge/data/builders/gpt_sft.py‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ def _packing_fingerprint(config: "GPTSFTDatasetConfig", dataset_kwargs: dict[str
9191
"preprocessing": asdict(preprocessing),
9292
"dataset_kwargs": dataset_kwargs,
9393
}
94+
if (
95+
config.offline_packing_specs is not None
96+
and config.offline_packing_specs.max_single_sequence_length is not None
97+
):
98+
packing_identity["max_single_sequence_length"] = config.offline_packing_specs.max_single_sequence_length
9499
return hashlib.sha256(
95100
json.dumps(packing_identity, sort_keys=True, separators=(",", ":"), default=str).encode("utf-8")
96101
).hexdigest()[:12]
@@ -560,6 +565,9 @@ def __init__(
560565
self.packed_sequence_size = (
561566
-1 if config.offline_packing_specs is None else config.offline_packing_specs.packed_sequence_size
562567
)
568+
self._max_single_sequence_length = (
569+
None if config.offline_packing_specs is None else config.offline_packing_specs.max_single_sequence_length
570+
)
563571
self.dataset_kwargs = normalize_gpt_sft_dataset_kwargs(config)
564572
self._pad_cu_seqlens = (
565573
False if config.offline_packing_specs is None else config.offline_packing_specs.pad_cu_seqlens
@@ -662,7 +670,7 @@ def _prepare_packed_split(
662670
output_metadata_path=self.pack_metadata,
663671
packed_sequence_size=self.packed_sequence_size,
664672
tokenizer=self.tokenizer,
665-
max_seq_length=self.seq_length,
673+
max_seq_length=self._max_single_sequence_length or self.seq_length,
666674
seed=self.seed,
667675
dataset_kwargs=self.dataset_kwargs,
668676
pad_seq_to_mult=self._pad_seq_to_mult,

‎src/megatron/bridge/data/packing/config.py‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ class PackedSequenceSpecs:
2828
"""Settings and optional artifact paths for offline sequence packing."""
2929

3030
packed_sequence_size: int = -1
31+
max_single_sequence_length: int | None = None
3132
tokenizer_model_name: str | None = None
3233
num_tokenizer_workers: int = -1
3334
packed_train_data_path: str | Path | None = None
3435
packed_val_data_path: str | Path | None = None
3536
packed_metadata_path: str | Path | None = None
3637
pad_cu_seqlens: bool = False
38+
"""Pad cumulative sequence boundaries for full-iteration, whole-layer, or attention-scoped CUDA graphs."""
3739
pad_seq_to_mult: int | None = 1
3840

3941
def __post_init__(self) -> None:
@@ -44,6 +46,11 @@ def __post_init__(self) -> None:
4446
self._validate_packed_path("packed_val_data_path", self.packed_val_data_path)
4547
if self.pad_seq_to_mult is not None and self.pad_seq_to_mult <= 0:
4648
raise ValueError("pad_seq_to_mult must be a positive integer when provided.")
49+
if self.max_single_sequence_length is not None:
50+
if self.max_single_sequence_length <= 0:
51+
raise ValueError("max_single_sequence_length must be a positive integer when provided.")
52+
if self.packed_sequence_size > 0 and self.max_single_sequence_length > self.packed_sequence_size:
53+
raise ValueError("max_single_sequence_length cannot exceed packed_sequence_size.")
4754

4855
def _validate_packed_path(self, attr_name: str, path_value: str | Path) -> None:
4956
"""Validate an explicitly supplied packed artifact path."""

‎src/megatron/bridge/data/packing/gpt_sft.py‎

Lines changed: 48 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ def __init__(
6161
return_cu_seqlen: Whether to return `cu_seqlen` to pass to the model. Having `cu_seqlen` in the model input
6262
enables THD attention kernel, which is the correct format for training with packed sequence to prevent
6363
cross-sequence attention. This flag should be True unless you have a specific use case.
64-
pad_seq_to_mult: The multiple used for padding sequences during packing. When > 1, cu_seqlens_unpadded
65-
will be computed to support THD CP. When == 1 (no padding), cu_seqlens_unpadded is not computed.
64+
pad_seq_to_mult: The multiple used for padding sequences during packing. When > 1, both logical and
65+
physically padded cumulative sequence offsets are returned for THD CP.
66+
pack_metadata_file_path: Path to the metadata JSON file used to stabilize cu_seqlens shapes.
6667
"""
6768
np.random.seed(kwargs.get("seed", 1234))
6869
super().__init__(file_path, tokenizer, **kwargs)
@@ -161,8 +162,8 @@ def collate_fn(self, batch):
161162
162163
Returns:
163164
dict: A dictionary of batched tensors, including 'tokens', 'labels',
164-
'loss_mask', 'position_ids', and potentially 'cu_seqlens',
165-
'cu_seqlens_argmin', 'max_seqlen' if `return_cu_seqlen` is True.
165+
'loss_mask', 'position_ids', and the MCore/TE packed-sequence
166+
fields if `return_cu_seqlen` is True.
166167
"""
167168
input_ids = [
168169
np.concatenate(
@@ -198,49 +199,60 @@ def collate_fn(self, batch):
198199
assert max_length <= self.max_seq_length
199200

200201
position_ids: list[list[int]] = []
202+
# TE naming: cu_seqlens contains logical offsets, while cu_seqlens_padded
203+
# contains physical offsets including per-sequence alignment padding.
201204
cu_seqlens: list[list[int]] = []
202-
# Only compute cu_seqlens_unpadded when pad_seq_to_mult > 1 (actual padding for CP)
203-
cu_seqlens_unpadded: list[list[int]] | None = [] if self._pad_seq_to_mult > 1 else None
205+
cu_seqlens_padded: list[list[int]] | None = [] if self._pad_seq_to_mult > 1 else None
204206
for item in batch:
205207
position_ids.append([])
206208
cu_seqlens.append([0])
207-
if cu_seqlens_unpadded is not None:
208-
cu_seqlens_unpadded.append([0])
209+
if cu_seqlens_padded is not None:
210+
cu_seqlens_padded.append([0])
209211
seqlens = np.array(item["seq_boundaries"][1:]) - np.array(item["seq_boundaries"][:-1])
210212
for length in seqlens:
211213
# length minus 1 because input_ids is truncated by 1 for labels
212214
position_ids[-1].extend(list(range(length - 1)))
213-
cu_seqlens[-1].append(cu_seqlens[-1][-1] + length - 1)
215+
if cu_seqlens_padded is not None:
216+
cu_seqlens_padded[-1].append(cu_seqlens_padded[-1][-1] + length - 1)
217+
else:
218+
cu_seqlens[-1].append(cu_seqlens[-1][-1] + length - 1)
214219

215220
# the last seq needs to be the max seq len because rope and attn kernels expect no padding
216-
assert cu_seqlens[-1][-1] <= max_length
217-
218-
# since data is prepadded when cp_size > 1, there may be some extra padding at the end
219-
# of the packed sequence. In this case, we need to add the max seq len to the end.
220-
if cu_seqlens[-1][-1] != max_length:
221-
cu_seqlens[-1].append(max_length)
222-
223-
if cu_seqlens_unpadded is not None:
221+
if cu_seqlens_padded is not None:
222+
assert cu_seqlens_padded[-1][-1] <= max_length
223+
if cu_seqlens_padded[-1][-1] != max_length:
224+
cu_seqlens_padded[-1].append(max_length)
224225
for i in range(len(item["seq_boundaries"]) - 1):
225226
current_seq = item["input_ids"][item["seq_boundaries"][i] : item["seq_boundaries"][i + 1] - 1]
226227

227-
# Stop unpadded lengths at the last non-eos token so padding eos are excluded.
228+
# Stop logical lengths at the last non-EOS token so alignment padding is excluded.
228229
current_seq_arr = np.array(current_seq)
229230
non_eos_positions = np.where(current_seq_arr != self.tokenizer.eos_id)[0]
230-
seqlen_unpadded = non_eos_positions[-1] + 1 if non_eos_positions.size > 0 else 0
231-
cu_seqlens_unpadded[-1].append(cu_seqlens_unpadded[-1][-1] + seqlen_unpadded)
231+
logical_seqlen = non_eos_positions[-1] + 1 if non_eos_positions.size > 0 else 0
232+
cu_seqlens[-1].append(cu_seqlens[-1][-1] + logical_seqlen)
232233

233234
# if extra paddings are added in the packed sequence, they can't be counted as
234235
# actual tokens for training
235-
if len(cu_seqlens[-1]) > len(cu_seqlens_unpadded[-1]):
236-
cu_seqlens_unpadded[-1].append(cu_seqlens_unpadded[-1][-1])
236+
if len(cu_seqlens_padded[-1]) > len(cu_seqlens[-1]):
237+
cu_seqlens[-1].append(cu_seqlens[-1][-1])
238+
else:
239+
assert cu_seqlens[-1][-1] <= max_length
240+
# Prepadded data may leave padding at the end of the packed sequence.
241+
if cu_seqlens[-1][-1] != max_length:
242+
cu_seqlens[-1].append(max_length)
237243

238244
if self.pad_cu_seqlens:
239-
# pad cu_seqlens to a constant shape with zero length sequences
245+
# Pad both boundary representations to the same static shape with zero-length
246+
# sequences. TE expects corresponding logical and physical boundary arrays.
240247
max_samples_per_bin = max(p["max_samples_per_bin"] for p in self.pack_metadata)
241248
# plus 2 since cu_seqlens additionally contains 0 and may append max_length
242-
pad_num = max_samples_per_bin - len(cu_seqlens[-1]) + 2
243-
cu_seqlens[-1].extend([max_length] * pad_num)
249+
if cu_seqlens_padded is not None:
250+
pad_num = max_samples_per_bin - len(cu_seqlens_padded[-1]) + 2
251+
cu_seqlens_padded[-1].extend([max_length] * pad_num)
252+
cu_seqlens[-1].extend([cu_seqlens[-1][-1]] * pad_num)
253+
else:
254+
pad_num = max_samples_per_bin - len(cu_seqlens[-1]) + 2
255+
cu_seqlens[-1].extend([max_length] * pad_num)
244256

245257
assert len(input_ids[0]) == len(position_ids[0]), (
246258
"Dataset problem: input_ids and position_ids lengths don't match"
@@ -263,13 +275,10 @@ def collate_fn(self, batch):
263275
}
264276

265277
if self.return_cu_seqlen:
266-
cu_seqlens = self._collate_item(
267-
cu_seqlens, max_length=max(len(length) for length in cu_seqlens) + 1, pad_id=-1
268-
)
269-
# Pre-generate `cu_seqlens_argmin` and `max_seqlen` as CPU tensor to avoid device-to-host copies.
270-
cu_seqlens = torch.IntTensor(cu_seqlens)
271-
cu_seqlens_argmin = torch.argmin(cu_seqlens, dim=1, keepdim=True)
272-
seqlens = cu_seqlens[:, 1:] - cu_seqlens[:, :-1]
278+
# Offline packed SFT requires micro-batch size 1, so no sentinel padding is
279+
# needed to batch variable-length metadata. Emit MCore/TE field names directly.
280+
seqlens = torch.IntTensor(cu_seqlens_padded if cu_seqlens_padded is not None else cu_seqlens)
281+
seqlens = seqlens[:, 1:] - seqlens[:, :-1]
273282
max_seqlen, _ = seqlens.max(dim=1, keepdim=True)
274283

275284
if self.pad_cu_seqlens:
@@ -284,25 +293,19 @@ def collate_fn(self, batch):
284293
safe_max_seqlen = max(dataset_max_seqlen, padding_gap)
285294
max_seqlen = torch.IntTensor([safe_max_seqlen] * len(cu_seqlens))
286295
else:
287-
seqlens = cu_seqlens[:, 1:] - cu_seqlens[:, :-1]
288296
max_seqlen, _ = seqlens.max(dim=1, keepdim=True)
289297

290298
cu_seqlens_batch = {
291299
"attention_mask": None, # no attention mask is needed for packed seq
292-
"cu_seqlens": torch.IntTensor(cu_seqlens), # cu_seqlens_q must be in dtype torch.int32
293-
"cu_seqlens_argmin": cu_seqlens_argmin, # only required for perf
294-
"max_seqlen": max_seqlen, # only required for perf
300+
"cu_seqlens_q": torch.IntTensor(cu_seqlens),
301+
"max_seqlen_q": max_seqlen,
302+
"max_seqlen_kv": max_seqlen,
295303
}
304+
cu_seqlens_batch["cu_seqlens_kv"] = cu_seqlens_batch["cu_seqlens_q"]
296305

297-
# Only include cu_seqlens_unpadded when pad_seq_to_mult > 1 (actual CP padding)
298-
if cu_seqlens_unpadded is not None:
299-
cu_seqlens_unpadded = self._collate_item(
300-
cu_seqlens_unpadded, max_length=max(len(length) for length in cu_seqlens_unpadded) + 1, pad_id=-1
301-
)
302-
cu_seqlens_unpadded = torch.IntTensor(cu_seqlens_unpadded)
303-
cu_seqlens_unpadded_argmin = torch.argmin(cu_seqlens_unpadded, dim=1, keepdim=True)
304-
cu_seqlens_batch["cu_seqlens_unpadded"] = cu_seqlens_unpadded
305-
cu_seqlens_batch["cu_seqlens_unpadded_argmin"] = cu_seqlens_unpadded_argmin
306+
if cu_seqlens_padded is not None:
307+
cu_seqlens_batch["cu_seqlens_q_padded"] = torch.IntTensor(cu_seqlens_padded)
308+
cu_seqlens_batch["cu_seqlens_kv_padded"] = cu_seqlens_batch["cu_seqlens_q_padded"]
306309

307310
processed_batch.update(cu_seqlens_batch)
308311
else:

0 commit comments

Comments
 (0)