Skip to content

VLM LengthGroupedSampler logs a 1.2x pre-filter threshold but applies max_length + 512 #3798

Description

@kabirvashisht4-glitch

Describe the bug

The VLM LengthGroupedSampler pre-filter says it uses 1.2x headroom but adds a
flat 512 tokens, and the log line reports the flat threshold labelled as
1.2 * max_length. The two diverge as max_length grows, so at long context the
sampler drops far more samples than the comment describes and the log misstates
the rule that was applied.

nemo_automodel/components/datasets/vlm/samplers.py L117-131:

# Use 1.2x headroom: the estimated length is a heuristic, so only
# drop samples that are clearly overlong.  Borderline samples are
# left to PreTokenizedDatasetWrapper's precise tokenize-and-retry.
filter_threshold = max_length + 512
kept = [i for i in all_indices if self.lengths[i] <= filter_threshold]
n_dropped = len(dataset) - len(kept)
if n_dropped:
    logger.info(
        "LengthGroupedSampler: pre-filtered %d/%d samples with "
        "estimated length > %.0f tokens (1.2 * max_length %d).",
        n_dropped,
        len(dataset),
        filter_threshold,   # <- max_length + 512
        max_length,
    )

%.0f receives filter_threshold, so the message asserts that
max_length + 512 is 1.2 * max_length. At max_length=32768 it prints:

LengthGroupedSampler: pre-filtered N/M samples with estimated length > 33280 tokens (1.2 * max_length 32768).

but 1.2 * 32768 is 39322, not 33280.

How far the two diverge

max_length code (+512) stated (1.2x) effective headroom delta
2048 2560 2457 25.0% +103
4096 4608 4915 12.5% -307
8192 8704 9830 6.2% -1126
16384 16896 19660 3.1% -2764
32768 33280 39321 1.6% -6041
131072 131584 157286 0.4% -25702

A flat 512 is ~25% headroom at 2k and ~0.4% at 128k. Beyond max_length=2560
the filter is stricter than the documented rule, and the gap grows with context
length — 6k tokens' worth of samples at 32k, 25k at 128k.

Why it matters: the comment's stated design is that borderline samples are
not dropped here — they are left to PreTokenizedDatasetWrapper's precise
tokenize-and-retry, because self.lengths[i] is only an estimate. With
effectively no headroom at long context, samples the estimator merely
over-counts are silently removed from training instead of reaching that retry
path. The repo has long-context VLM recipes, so this is the regime where the
heuristic's error matters most and the headroom protecting against it has all
but vanished.

There is no user-visible signal that the rule differs from the one logged.

Steps/Code to reproduce bug

for m in (2048, 8192, 32768):
    print(m, "code:", m + 512, " logged as 1.2 *", m, "=", int(m * 1.2))
2048 code: 2560  logged as 1.2 * 2048 = 2457
8192 code: 8704  logged as 1.2 * 8192 = 9830
32768 code: 33280  logged as 1.2 * 32768 = 39321

Expected behavior

The log states the threshold actually applied, and the comment describes the
rule the code implements — whichever of the two is intended.

Environment overview

  • main at 3ddef9b. Static/arithmetic; no GPU, dataset or processor needed.

Additional context

The fix depends on which was intended, and that is a maintainer call:

  1. The flat +512 is correct — then the comment and the log string are
    simply stale and should say max_length + 512. No behaviour change.
  2. The 1.2x was correct — then filter_threshold should be
    int(max_length * 1.2), which keeps more borderline samples at long context.

I will send a PR for (1), since it is the strictly non-behaviour-changing
reading and makes the log truthful immediately. Say the word and I will switch
it to (2) instead — happy to follow whichever matches the original intent.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions