@@ -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