Skip to content

Commit acb1752

Browse files
committed
add t2i task
1 parent 20e9017 commit acb1752

5 files changed

Lines changed: 168 additions & 141 deletions

File tree

flagscale/train/datasets/energon/energon_bagel_task_encoder.py

Lines changed: 124 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ def __init__(
5050
max_latent_size=64,
5151
vit_patch_size=14,
5252
max_num_patch_per_side=70,
53-
max_num_tokens=32768,
54-
expected_num_tokens=31000,
53+
max_num_tokens=36864,
54+
expected_num_tokens=32768,
5555
max_num_tokens_per_sample=16384,
56+
max_buffer_size=50,
5657
):
5758
self.text_cond_dropout_prob = text_cond_dropout_prob
5859
self.vit_cond_dropout_prob = vit_cond_dropout_prob
@@ -64,6 +65,7 @@ def __init__(
6465
self.max_num_tokens = max_num_tokens
6566
self.expected_num_tokens = expected_num_tokens
6667
self.max_num_tokens_per_sample = max_num_tokens_per_sample
68+
self.max_buffer_size = max_buffer_size
6769

6870

6971
@lru_cache(maxsize=16)
@@ -123,14 +125,12 @@ def __init__(
123125
self,
124126
data_config: BagelDataConfig,
125127
interpolate_pos: bool = False,
126-
max_seq_length: Optional[int] = None,
127128
):
128129
super().__init__()
129130
self.tokenizer = get_tokenizer()
130131
self.special_tokens = self.tokenizer.new_special_token_ids
131132
print(f"{self.special_tokens=}")
132133
self.data_config = data_config
133-
self.group_size = max_seq_length if max_seq_length is not None else 4096
134134

135135
self.handlers = {
136136
name: cls(self.tokenizer, self.special_tokens, self.data_config)
@@ -148,6 +148,11 @@ def __init__(
148148
self._tp_size = getattr(_args, 'tensor_model_parallel_size', 1)
149149
self._sequence_parallel = getattr(_args, 'sequence_parallel', False)
150150

151+
# Overflow buffer: samples that didn't fit in the previous pack are held
152+
# here and prepended to the next select_samples_to_pack call so that no
153+
# sample is wasted and every yielded pack meets expected_num_tokens.
154+
self._overflow_buffer: List = []
155+
151156
def _build_transforms(self, subflavors):
152157
transforms_item = {}
153158
image_args = subflavors.get('image_transform_args')
@@ -176,76 +181,133 @@ def _build_transforms(self, subflavors):
176181

177182
return transforms_item
178183

179-
def select_samples_to_pack(self, samples: List[BagelSample]) -> List[List[Dict[str, torch.Tensor]]]:
180-
"""Select samples from buffer to form packs.
184+
def select_samples_to_pack(self, samples: List[BagelSample]) -> List[List[BagelSample]]:
185+
"""Select samples from buffer to form packs with overflow management.
181186
182-
Implements Bagel's packing strategy (mirrors dataset_base.py __iter__):
183-
- Each pack starts by consuming all mandatory samples first
184-
- Then greedily fills with non-mandatory samples
185-
- Uses expected_num_tokens as the "pack is ready" threshold
186-
- Uses max_num_tokens as the hard upper limit
187+
Mirrors the original Bagel PackedDataset.__iter__ packing strategy:
188+
- Prepend overflow samples from the previous call (like the original buffer)
189+
- Uses expected_num_tokens as the soft threshold to yield a pack
190+
- Uses max_num_tokens as the hard upper limit per pack
187191
- Samples exceeding max_num_tokens_per_sample are skipped
188-
- Overflow samples go into a buffer for priority use in next pack
189-
190-
Selects which samples will be packed together.
191-
192-
This function receives a list of samples (size according to the selected packing_buffer_size),
193-
and partitions those samples into groups that shall be packed together.
192+
- If the last pack doesn't reach expected_num_tokens, its samples are
193+
held in the overflow buffer for the next call (no short batches)
194194
195195
Args:
196-
samples (List[Dict[str, torch.Tensor]]): List of samples from the buffer, each containing
197-
tokenized data with keys like 'input_ids', 'labels', 'loss_mask', etc.
196+
samples: List of samples from Energon's reading buffer.
198197
199198
Returns:
200-
List[List[Dict[str, torch.Tensor]]]: List of groups, where each group is a list of samples
201-
that should be packed together. Each group's total length will not exceed group_size.
199+
List of groups where each group is a list of samples to pack together.
200+
Every returned pack is guaranteed to have >= expected_num_tokens
201+
(except when the data source is exhausted).
202202
203203
NOTE: Energon dataloader calls this method internally if packing is used.
204204
Please see https://nvidia.github.io/Megatron-Energon/advanced/packing.html
205205
"""
206+
# --- Step 1: Load packing configuration ---
207+
# max_tokens: hard ceiling for a single pack (prevents OOM)
208+
# max_per_sample: discard any sample longer than this
209+
# expected: soft target — once a pack reaches this, emit it
210+
# max_buffer_size: cap on how many "doesn't fit" samples we hold locally
206211
max_tokens = self.data_config.max_num_tokens
207212
max_per_sample = self.data_config.max_num_tokens_per_sample
208213
expected = self.data_config.expected_num_tokens
214+
max_buffer_size = self.data_config.max_buffer_size
215+
216+
# --- Step 2: Merge overflow from previous call with new samples ---
217+
# Overflow samples are placed first so they get priority (equivalent to
218+
# the original code's "prefer_buffer_before" behavior where buffered
219+
# samples are consumed before drawing new ones from the data stream).
220+
all_samples = self._overflow_buffer + list(samples)
221+
self._overflow_buffer = []
222+
223+
# --- Step 3: Filter out oversized samples ---
224+
# Samples exceeding max_num_tokens_per_sample are permanently discarded,
225+
# matching the original "skip a sample with length ..." behavior.
226+
# Token count includes +2 per segment in sequence_plan (bos/eos overhead).
227+
valid_samples = []
228+
for s in all_samples:
229+
token_count = s.num_tokens + 2 * len(s.sequence_plan)
230+
if token_count <= max_per_sample:
231+
valid_samples.append((s, token_count))
209232

210-
# Filter out oversized samples
211-
valid_samples = [s for s in samples if s.num_tokens <= max_per_sample]
212-
print(f"{len(valid_samples)=}, {valid_samples=}")
213233
if not valid_samples:
214234
return []
215235

216-
# # Separate mandatory and non-mandatory
217-
# mandatory = [s for s in valid_samples if s.is_mandatory]
218-
# non_mandatory = [s for s in valid_samples if not s.is_mandatory]
219-
236+
# --- Step 4: Greedy bin-packing with overflow buffer ---
237+
# Walk through valid_samples one by one, trying to fit each into
238+
# current_pack. Three outcomes per sample:
239+
# (a) Fits and pack not yet full → append to current_pack
240+
# (b) Fits and pack reaches expected → emit pack, start fresh
241+
# (c) Doesn't fit → stash in pending_candidates for later
220242
packs = []
221243
current_pack = []
222244
current_tokens = 0
245+
pending_candidates = []
223246

224-
# # Start each pack with a mandatory sample if available
225-
# if mandatory:
226-
# m_sample = mandatory.pop(0)
227-
# current_pack.append(m_sample)
228-
# current_tokens = m_sample.num_tokens + 2 * len(m_sample.sequence_plan)
229-
230-
# # Fill with remaining samples (sorted by size for better packing)
231-
# remaining = mandatory + non_mandatory
232-
# random.shuffle(remaining)
233-
234-
for sample in samples:
235-
sample_tokens = sample.num_tokens + 2 * len(sample.sequence_plan)
236-
if current_tokens + sample_tokens <= max_tokens:
247+
for sample, token_count in valid_samples:
248+
if current_tokens + token_count <= max_tokens:
249+
# Case (a)/(b): sample fits within the hard limit
237250
current_pack.append(sample)
238-
current_tokens += sample_tokens
239-
elif current_pack:
240-
# Current pack is full, start a new one
241-
packs.append(current_pack)
242-
current_pack = [sample]
243-
current_tokens = sample_tokens
244-
245-
if current_pack:
246-
packs.append(current_pack)
251+
current_tokens += token_count
252+
253+
if current_tokens >= expected:
254+
# Pack reached the soft target — emit it
255+
packs.append(current_pack)
256+
current_pack = []
257+
current_tokens = 0
258+
259+
# Drain pending_candidates into the fresh pack immediately.
260+
# This gives previously-buffered (typically large) samples a
261+
# chance to be placed while the new pack is still empty.
262+
for pend_sample, pend_tokens in pending_candidates:
263+
if current_tokens + pend_tokens <= max_tokens:
264+
current_pack.append(pend_sample)
265+
current_tokens += pend_tokens
266+
if current_tokens >= expected:
267+
packs.append(current_pack)
268+
current_pack = []
269+
current_tokens = 0
270+
else:
271+
# Still doesn't fit — persist to cross-call overflow
272+
self._overflow_buffer.append(pend_sample)
273+
pending_candidates = []
274+
else:
275+
# Case (c): sample would exceed the hard limit for current pack
276+
if len(pending_candidates) < max_buffer_size:
277+
# Stash it; we'll try again after the current pack is emitted
278+
pending_candidates.append((sample, token_count))
279+
else:
280+
# Overflow buffer is full — force-emit the current pack even
281+
# though it may not have reached `expected`. This matches the
282+
# original behavior: "buffer full + can't fit → yield batch".
283+
if current_pack:
284+
packs.append(current_pack)
285+
# Start a new pack with the sample that triggered the flush
286+
current_pack = [sample]
287+
current_tokens = token_count
288+
# Try to fit pending_candidates into the new pack
289+
for pend_sample, pend_tokens in pending_candidates:
290+
if current_tokens + pend_tokens <= max_tokens:
291+
current_pack.append(pend_sample)
292+
current_tokens += pend_tokens
293+
else:
294+
self._overflow_buffer.append(pend_sample)
295+
pending_candidates = []
296+
297+
# --- Step 5: Handle leftover samples at the end of this call ---
298+
# current_pack is within max_tokens, but pending_candidates may push
299+
# the total over. Only emit if both conditions are met: total tokens
300+
# >= expected AND <= max_tokens. Otherwise, hold everything for next call.
301+
remaining = current_pack + [s for s, _ in pending_candidates]
302+
if remaining:
303+
remaining_tokens = sum(
304+
s.num_tokens + 2 * len(s.sequence_plan) for s in remaining
305+
)
306+
if remaining_tokens >= expected and remaining_tokens <= max_tokens:
307+
packs.append(remaining)
308+
else:
309+
self._overflow_buffer.extend(remaining)
247310

248-
print(f"{len(packs)=}, {packs=}")
249311
return packs
250312

251313
@stateless
@@ -275,10 +337,10 @@ def encode_sample(self, sample: Dict[str, Any]):
275337
- 'vlm': VLM SFT data (jsonl conversations + images)
276338
- 't2i': Text-to-image data (parquet image + caption)
277339
"""
278-
print(f"{sample=}")
340+
# print(f"{sample=}")
279341
subflavors = sample.get('__subflavors__', {})
280342
task = subflavors.get('task', None)
281-
print(f"{subflavors=}")
343+
# print(f"{subflavors=}")
282344

283345
kwargs = self._build_transforms(subflavors)
284346
handler = self.handlers.get(task)
@@ -304,7 +366,7 @@ def encode_batch(self, batch: BagelPackedBatch) -> BagelPackedBatch:
304366
return batch
305367

306368

307-
def bagel_vlm_dataloader_provider(train_val_test_num_samples, max_seq_length: Optional[int] = None):
369+
def bagel_vlm_dataloader_provider(train_val_test_num_samples):
308370
args = get_args()
309371

310372
bagel_config = BagelDataConfig(
@@ -315,17 +377,17 @@ def bagel_vlm_dataloader_provider(train_val_test_num_samples, max_seq_length: Op
315377
vit_patch_size=getattr(args, 'vit_patch_size', 14),
316378
max_latent_size=getattr(args, 'max_latent_size', 64),
317379
max_num_patch_per_side=getattr(args, 'max_num_patch_per_side', 70),
318-
max_num_tokens=getattr(args, 'max_num_tokens', 16384), # 36864
319-
expected_num_tokens=getattr(args, 'expected_num_tokens', 16384), # 32768
380+
max_num_tokens=getattr(args, 'max_num_tokens', 36864),
381+
expected_num_tokens=getattr(args, 'expected_num_tokens', 32768),
320382
max_num_tokens_per_sample=getattr(args, 'max_num_tokens_per_sample', 16384),
383+
max_buffer_size=getattr(args, 'max_buffer_size', 50),
321384
)
322385

323386
return train_valid_test_dataloaders_provider(
324387
train_val_test_num_samples,
325388
task_encoder=BagelTaskEncoder(
326389
data_config=bagel_config,
327390
interpolate_pos=args.interpolate_pos,
328-
max_seq_length=max_seq_length,
329391
)
330392
)
331393

@@ -384,15 +446,14 @@ def save_state(self):
384446
return self._dataloader.save_state_rank()
385447

386448

387-
def train_valid_test_dataloaders_provider(train_val_test_num_samples, task_encoder=None):
449+
def train_valid_test_dataloaders_provider(train_val_test_num_samples, task_encoder):
388450

389451
args = get_args()
390452

391453
# Dataloader is only on specific ranks.
392454
if not is_dataloader_rank():
393455
return None, None, None
394456

395-
tokenizer = get_tokenizer()
396457
worker_debug_path = None
397458
worker_log_level = 0
398459

@@ -409,25 +470,12 @@ def train_valid_test_dataloaders_provider(train_val_test_num_samples, task_encod
409470
worker_log_level=worker_log_level,
410471
)
411472

412-
# task_encoder = BagelTaskEncoder(
413-
# tokenizer=tokenizer,
414-
# special_tokens=tokenizer.new_special_token_ids,
415-
# data_config=bagel_config,
416-
# )
417-
# task_encoder=BagelTaskEncoder(
418-
# data_config=bagel_config,
419-
# interpolate_pos=args.interpolate_pos,
420-
# max_seq_length=max_seq_length,
421-
# )
422-
423-
# # Build dataset paths with weights
424-
# dataset_configs = getattr(args, 'datasets', [])
425-
# if not dataset_configs:
426-
# raise ValueError("data_config must contain 'datasets' list")
427-
dname = args.data_path[0] if type(args.data_path) is list else args.data_path
473+
# Build dataset paths with weights
474+
assert (isinstance(args.data_path, list) and len(args.data_path) == 1) or \
475+
isinstance(args.data_path, str)
476+
dname = args.data_path[0] if isinstance(args.data_path, list) else args.data_path
428477

429478
# For single dataset
430-
# if len(dataset_configs) == 1:
431479
dataset = get_train_dataset(
432480
dname,
433481
batch_size=args.micro_batch_size,
@@ -440,23 +488,6 @@ def train_valid_test_dataloaders_provider(train_val_test_num_samples, task_encod
440488
handler=print_error_handler,
441489
image_decode="pil",
442490
)
443-
# else:
444-
# # For blended datasets
445-
# blend_config = []
446-
# for ds_cfg in dataset_configs:
447-
# blend_config.append({
448-
# 'path': ds_cfg['path'],
449-
# 'weight': ds_cfg.get('weight', 1.0),
450-
# 'subflavors': ds_cfg.get('subflavors', {}),
451-
# })
452-
# dataset = get_train_dataset(
453-
# blend_config,
454-
# worker_config=worker_config,
455-
# batch_size=args.micro_batch_size,
456-
# task_encoder=task_encoder,
457-
# max_samples_per_sequence=getattr(args, 'max_samples_per_sequence', None),
458-
# shuffle_buffer_size=getattr(args, 'shuffle_buffer_size', 1000),
459-
# )
460491

461492
# Build savable dataloader
462493
dataloader = get_savable_loader(

flagscale/train/datasets/energon/sample_types.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -82,16 +82,3 @@ def to_dict(self):
8282
data['ce_loss_indexes'] = self.ce_loss_indexes
8383
data['ce_loss_weights'] = self.ce_loss_weights
8484
return data
85-
86-
87-
@dataclass
88-
class T2ISample:
89-
#: The input image tensor in the shape (C, H, W)
90-
image: torch.Tensor
91-
#: The captions of the image
92-
captions: str
93-
94-
95-
class VLMSample:
96-
#: The input image tensor in the shape (C, H, W)
97-
image: torch.Tensor

flagscale/train/datasets/energon/task_handlers/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from .base import BaseTaskHandler
55
from .vlm import VLMHandler
6-
# from .t2i import T2IHandler
6+
from .t2i import T2IHandler
77

88

99
TASK_REGISTRY: Dict[str, "BaseTaskHandler"] = {}
@@ -14,3 +14,4 @@ def register_task(name, cls):
1414

1515

1616
register_task("vlm_sft", VLMHandler)
17+
register_task("t2i", T2IHandler)

0 commit comments

Comments
 (0)