Skip to content

fix(data): shuffle direct HF SFT with cyclic sampler - #5048

Merged
yaoyu-33 merged 2 commits into
mainfrom
yuya/fix-direct-hf-vlm-shuffle
Jul 23, 2026
Merged

yaoyu-33 merged 2 commits into
mainfrom
yuya/fix-direct-hf-vlm-shuffle

Conversation

@yaoyu-33

@yaoyu-33 yaoyu-33 commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

Use the standard Megatron cyclic dataloader for Direct-HF SFT shuffling instead of mutating loaded examples inside the dataset builder.

Direct-HF SFT configs now default to dataloader_type="cyclic". The in-tree VLM/audio SFT and PEFT recipes that previously pinned the sequential single sampler now explicitly select cyclic, and the MegatronMIMO Qwen3.5-VL example uses the same CLI default.

Why cyclic instead of batch?

cyclic selects MegatronPretrainingRandomSampler, which provides deterministic epoch shuffling and resume-aware sample ordering while keeping DataLoader collation at microbatch granularity. That preserves the existing VLM in-batch packing behavior.

The batch sampler collates a rank-local global batch before the finetuning step splits it into microbatches. For Direct-HF VLM recipes this can change the packing scope and can be incompatible with collators that pack the input down to a batch dimension of one.

Changelog

  • Default DirectHFSFTDatasetConfig.dataloader_type to cyclic.
  • Use cyclic in Direct-HF SFT/PEFT recipes for common VLM, Nemotron Omni, Qwen2-Audio, and Qwen3-Omni.
  • Default the MegatronMIMO Qwen3.5-VL Direct-HF example to cyclic.
  • Remove the builder-level shuffle flags, dedicated seed, and list mutation introduced by the first revision of this PR.
  • Add config, recipe, serialization, and example-default assertions.

Users can still request source-order sampling explicitly with dataloader_type="single".

Validation

  • uv run --isolated --no-project --with pre-commit==4.6.0 pre-commit run --all-files — passed.
  • uv run --isolated --no-project python -m compileall -q <changed Python files> — passed.
  • git diff --check — passed.
  • Targeted pytest collection is pending Linux CI. On the local macOS ARM environment, uv run is blocked before pytest by the Linux-only nvidia-resiliency-ext wheel; the current Megatron-Core import also requires Triton.

Before your PR is Ready for review

  • Read and followed the contributor guidelines.
  • Added unit-level contract coverage.
  • No new dependency or public conversion API change.

Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@yaoyu-33 yaoyu-33 added bug Something isn't working area:data Dataset builders, preprocessing, and samplers needs-review PR is ready for code review and waiting on a reviewer labels Jul 23, 2026
@claude

claude Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Light review - LGTM

Deterministic shuffling for Direct-HF SFT splits with a clean opt-out and no source mutation. Logic is correct: list(examples) copies before shuffling so the loaded source is preserved, and random.Random(seed) keeps it deterministic and isolated from global RNG. Tests cover default-shuffle-without-mutation, the shuffle=False opt-out, and round-trip serialization of the two new config fields.

Minor observations (non-blocking):

  • All three splits (train/validation/test) share shuffle_seed. Each split is a separate list and shuffling happens before truncation to target_length, so the selected subset stays deterministic across runs, which looks intended.
  • Consider noting in the docstring/config field that shuffling also applies to validation/test, not just train.

Suggested test cases

  • tests/unit_tests/data/builders/test_direct_hf_sft.py::test_builder_shuffles_examples_by_default_without_mutating_source
  • tests/unit_tests/data/builders/test_direct_hf_sft.py::test_builder_allows_source_order_opt_out
  • tests/unit_tests/data/builders/test_direct_hf_sft.py::test_direct_hf_sft_config_round_trip_is_declarative
  • (gap) A case asserting validation/test splits are also shuffled (current tests set do_validation=False/do_test=False, so the shuffle path is only exercised for train).
  • No perf tests impacted.

Signed-off-by: yaoyu-33 <yaoyu.094@gmail.com>
@yaoyu-33 yaoyu-33 changed the title fix(data): restore direct HF dataset shuffling fix(data): shuffle direct HF SFT with cyclic sampler Jul 23, 2026
@copy-pr-bot

copy-pr-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

/ok to test 071fc4afa8444ed2f3d4542181321794329bbf6d

@yaoyu-33, there was an error processing your request: E2

See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/2/

@yaoyu-33

Copy link
Copy Markdown
Contributor Author

/ok to test 071fc4a

@yaoyu-33
yaoyu-33 merged commit 5507057 into main Jul 23, 2026
29 checks passed
@yaoyu-33
yaoyu-33 deleted the yuya/fix-direct-hf-vlm-shuffle branch July 23, 2026 22:02
going-song added a commit to going-song/Megatron-Bridge that referenced this pull request Aug 12, 2026
By default every MegatronMIMO data-loading rank reads the full global
micro-batch and forward_step slices out its module-DP shard, so per-rank
read and preprocessing cost scales with the DP degree. This adds an opt-in
dataset switch, `megatron_mimo_scalable_dp`, that shards reads at the
sampler instead and skips the forward-step slice.

Reads are sharded on a canonical grid: the LCM of the module DP sizes.
Each rank covers `grid // dp` consecutive canonical groups and
concatenates one flat Megatron sampler per group, all built with the same
`(micro_batch // grid, grid)` geometry and the global consumed_samples.
Group streams depend only on module-independent inputs, so every module
materializes the identical ordered global micro-batch under both "single"
and the default "cyclic" sampler, keeping the BridgeCommunicator's
positional batch-dim routing aligned. (Sharding by each module's own DP -
the original mechanism - only aligns under "single"; "cyclic" became the
direct-HF-SFT default in NVIDIA-NeMo#5048.) Group geometries are truncated to whole
global micro-batches so ragged dataset sizes cannot desynchronize the
groups' window counts.

Split (2/3) of NVIDIA-NeMo#4608 as agreed in NVIDIA-NeMo#4609. Original work by Chanwoo Park.

Validated: unit tests for cross-geometry alignment, resume across epoch
boundaries, and non-multiple dataset sizes; live PP=2 runs (language
tp1/pp2/dp2 + images dp1): cyclic+scalable completes where module-DP
sharding crashed on embedding-count mismatch, single scalable matches
non-scalable losses within bf16 noise, and the default path reproduces
prior losses exactly.

Signed-off-by: kayeon.song <kayeon.song@navercorp.com>
Signed-off-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Signed-off-by: Chanwoo Park <chanwoo.park98@navercorp.com>
Co-authored-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Co-authored-by: Chanwoo Park <chanwoo.park98@navercorp.com>
going-song added a commit to going-song/Megatron-Bridge that referenced this pull request Aug 12, 2026
By default every MegatronMIMO data-loading rank reads the full global
micro-batch and forward_step slices out its module-DP shard, so per-rank
read and preprocessing cost scales with the DP degree. This adds an opt-in
dataset switch, `megatron_mimo_scalable_dp`, that shards reads at the
sampler instead and skips the forward-step slice.

Reads are sharded on a canonical grid: the LCM of the module DP sizes.
Each rank covers `grid // dp` consecutive canonical groups and
concatenates one flat Megatron sampler per group, all built with the same
`(micro_batch // grid, grid)` geometry and the global consumed_samples.
Group streams depend only on module-independent inputs, so every module
materializes the identical ordered global micro-batch under both "single"
and the default "cyclic" sampler, keeping the BridgeCommunicator's
positional batch-dim routing aligned. (Sharding by each module's own DP -
the original mechanism - only aligns under "single"; "cyclic" became the
direct-HF-SFT default in NVIDIA-NeMo#5048.) Group geometries are truncated to whole
global micro-batches so ragged dataset sizes cannot desynchronize the
groups' window counts.

Split (2/3) of NVIDIA-NeMo#4608 as agreed in NVIDIA-NeMo#4609. Original work by Chanwoo Park.

Validated: unit tests for cross-geometry alignment, resume across epoch
boundaries, and non-multiple dataset sizes; live PP=2 runs (language
tp1/pp2/dp2 + images dp1): cyclic+scalable completes where module-DP
sharding crashed on embedding-count mismatch, single scalable matches
non-scalable losses within bf16 noise, and the default path matches
prior losses within run-to-run noise.

Signed-off-by: kayeon.song <kayeon.song@navercorp.com>
Signed-off-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Signed-off-by: Chanwoo Park <chanwoo.park98@navercorp.com>
Co-authored-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Co-authored-by: Chanwoo Park <chanwoo.park98@navercorp.com>
going-song added a commit to going-song/Megatron-Bridge that referenced this pull request Aug 12, 2026
By default every MegatronMIMO data-loading rank reads the full global
micro-batch and forward_step slices out its module-DP shard, so per-rank
read and preprocessing cost scales with the DP degree. This adds an opt-in
dataset switch, `megatron_mimo_scalable_dp`, that shards reads at the
sampler instead and skips the forward-step slice.

Reads are sharded on a canonical grid: the LCM of the module DP sizes.
Each rank covers `grid // dp` consecutive canonical groups and
concatenates one flat Megatron sampler per group, all built with the same
`(micro_batch // grid, grid)` geometry and the global consumed_samples.
Group streams depend only on module-independent inputs, so every module
materializes the identical ordered global micro-batch under both "single"
and the default "cyclic" sampler, keeping the BridgeCommunicator's
positional batch-dim routing aligned. (Sharding by each module's own DP -
the original mechanism - only aligns under "single"; "cyclic" became the
direct-HF-SFT default in NVIDIA-NeMo#5048.) Group geometries are truncated to whole
global micro-batches so ragged dataset sizes cannot desynchronize the
groups' window counts.

Split (2/3) of NVIDIA-NeMo#4608 as agreed in NVIDIA-NeMo#4609. Original work by Chanwoo Park.

Validated: unit tests for cross-geometry alignment, resume across epoch
boundaries, and non-multiple dataset sizes; live PP=2 runs (language
tp1/pp2/dp2 + images dp1): cyclic+scalable completes where module-DP
sharding crashed on embedding-count mismatch, single scalable matches
non-scalable losses within bf16 noise, and the default path matches
prior losses within run-to-run noise.

Signed-off-by: kayeon.song <kayeon.song@navercorp.com>
Signed-off-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Signed-off-by: Chanwoo Park <chanwoo.park98@navercorp.com>
Co-authored-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Co-authored-by: Chanwoo Park <chanwoo.park98@navercorp.com>
going-song added a commit to going-song/Megatron-Bridge that referenced this pull request Aug 18, 2026
By default every MegatronMIMO data-loading rank reads the full global
micro-batch and forward_step slices out its module-DP shard, so per-rank
read and preprocessing cost scales with the DP degree. This adds an opt-in
dataset switch, `megatron_mimo_scalable_dp`, that shards reads at the
sampler instead and skips the forward-step slice.

Reads are sharded on a canonical grid: the LCM of the module DP sizes.
Each rank covers `grid // dp` consecutive canonical groups and
concatenates one flat Megatron sampler per group, all built with the same
`(micro_batch // grid, grid)` geometry and the global consumed_samples.
Group streams depend only on module-independent inputs, so every module
materializes the identical ordered global micro-batch under both "single"
and the default "cyclic" sampler, keeping the BridgeCommunicator's
positional batch-dim routing aligned. (Sharding by each module's own DP -
the original mechanism - only aligns under "single"; "cyclic" became the
direct-HF-SFT default in NVIDIA-NeMo#5048.) Group geometries are truncated to whole
global micro-batches so ragged dataset sizes cannot desynchronize the
groups' window counts.

Split (2/3) of NVIDIA-NeMo#4608 as agreed in NVIDIA-NeMo#4609. Original work by Chanwoo Park.

Validated: unit tests for cross-geometry alignment, resume across epoch
boundaries, and non-multiple dataset sizes; live PP=2 runs (language
tp1/pp2/dp2 + images dp1): cyclic+scalable completes where module-DP
sharding crashed on embedding-count mismatch, single scalable matches
non-scalable losses within bf16 noise, and the default path matches
prior losses within run-to-run noise.

Signed-off-by: kayeon.song <kayeon.song@navercorp.com>
Signed-off-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Signed-off-by: Chanwoo Park <chanwoo.park98@navercorp.com>
Co-authored-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Co-authored-by: Chanwoo Park <chanwoo.park98@navercorp.com>
going-song added a commit to going-song/Megatron-Bridge that referenced this pull request Aug 27, 2026
By default every MegatronMIMO data-loading rank reads the full global
micro-batch and forward_step slices out its module-DP shard, so per-rank
read and preprocessing cost scales with the DP degree. This adds an opt-in
dataset switch, `megatron_mimo_scalable_dp`, that shards reads at the
sampler instead and skips the forward-step slice.

Reads are sharded on a canonical grid: the LCM of the module DP sizes.
Each rank covers `grid // dp` consecutive canonical groups and
concatenates one flat Megatron sampler per group, all built with the same
`(micro_batch // grid, grid)` geometry and the global consumed_samples.
Group streams depend only on module-independent inputs, so every module
materializes the identical ordered global micro-batch under both "single"
and the default "cyclic" sampler, keeping the BridgeCommunicator's
positional batch-dim routing aligned. (Sharding by each module's own DP -
the original mechanism - only aligns under "single"; "cyclic" became the
direct-HF-SFT default in NVIDIA-NeMo#5048.) Group geometries are truncated to whole
global micro-batches so ragged dataset sizes cannot desynchronize the
groups' window counts.

Split (2/3) of NVIDIA-NeMo#4608 as agreed in NVIDIA-NeMo#4609. Original work by Chanwoo Park.

Validated: unit tests for cross-geometry alignment, resume across epoch
boundaries, and non-multiple dataset sizes; live PP=2 runs (language
tp1/pp2/dp2 + images dp1): cyclic+scalable completes where module-DP
sharding crashed on embedding-count mismatch, single scalable matches
non-scalable losses within bf16 noise, and the default path matches
prior losses within run-to-run noise.

Signed-off-by: kayeon.song <kayeon.song@navercorp.com>
Signed-off-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Signed-off-by: Chanwoo Park <chanwoo.park98@navercorp.com>
Co-authored-by: Yoonsik Kim <yoonsik.kim90@navercorp.com>
Co-authored-by: Chanwoo Park <45866990+sailor1493@users.noreply.github.com>

This branch was previously deployed

2 inactive deployments
public — 071fc4af Deployed Jul 23, 2026 by copy-pr-bot[bot] via release / finalize / publish-docs #3244
test — 071fc4af Deployed Jul 23, 2026 by copy-pr-bot[bot] via cicd-wait-in-queue #18673
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:data Dataset builders, preprocessing, and samplers bug Something isn't working needs-review PR is ready for code review and waiting on a reviewer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant