|
5 | 5 | from torch.utils.data import Dataset |
6 | 6 |
|
7 | 7 | from megatron.bridge.data.samplers import ( |
| 8 | + MegatronPretrainingRandomSampler, |
8 | 9 | MegatronPretrainingSampler, |
9 | 10 | RandomSeedDataset, |
10 | 11 | build_pretraining_data_loader, |
@@ -108,3 +109,30 @@ def test_cyclic_sampler_resume_seeds_worker_dataset_for_current_epoch( |
108 | 109 | expected_value = torch.randint(0, 1_000_000, (1,), generator=generator).item() |
109 | 110 |
|
110 | 111 | assert actual_value.item() == expected_value |
| 112 | + |
| 113 | + |
| 114 | +def test_cyclic_sampler_non_sharded_tail_keeps_data_parallel_epochs_aligned() -> None: |
| 115 | + """Every DP rank must cross a truncated cyclic epoch on the same step.""" |
| 116 | + |
| 117 | + def make_sampler(rank: int, consumed_samples: int = 0) -> MegatronPretrainingRandomSampler: |
| 118 | + return MegatronPretrainingRandomSampler( |
| 119 | + list(range(10)), |
| 120 | + total_samples=10, |
| 121 | + consumed_samples=consumed_samples, |
| 122 | + micro_batch_size=2, |
| 123 | + data_parallel_rank=rank, |
| 124 | + data_parallel_size=3, |
| 125 | + data_sharding=False, |
| 126 | + ) |
| 127 | + |
| 128 | + def cyclic_batches(sampler: MegatronPretrainingRandomSampler): |
| 129 | + while True: |
| 130 | + yield from sampler |
| 131 | + |
| 132 | + uninterrupted = [cyclic_batches(make_sampler(rank)) for rank in range(3)] |
| 133 | + _ = [next(rank_batches) for rank_batches in uninterrupted] |
| 134 | + second_step = [next(rank_batches) for rank_batches in uninterrupted] |
| 135 | + resumed_step = [next(iter(make_sampler(rank, consumed_samples=6))) for rank in range(3)] |
| 136 | + |
| 137 | + assert len({index for batch in second_step for index in batch}) == 6 |
| 138 | + assert second_step == resumed_step |
0 commit comments