Skip to content

feat(vlm): carry a media-token validity mask through sequence packing - #3631

Merged
yfw merged 5 commits into
mainfrom
smohsenitahe/media-token-validity-mask
Aug 18, 2026
Merged

feat(vlm): carry a media-token validity mask through sequence packing#3631
yfw merged 5 commits into
mainfrom
smohsenitahe/media-token-validity-mask

Conversation

@DanialTaheri

Copy link
Copy Markdown
Contributor

What

Lets a row whose text legitimately contains the media placeholder train, instead
of being dropped at data-prep time.

Stacked on #3494 — based on smohsenitahe/super-omni-mtp-cp, so this diff is
only the two commits. GitHub will retarget the base to main once #3494 merges.

Why this is separate

#3494 removes placeholder sanitization, and the VLM blend trains fine without
this: every row there either carries no literal placeholder, or carries exactly
as many as it has images. A 50-step Super Omni run with sanitization off and the
mask entirely unwired completed 50/50 with zero media-alignment failures.

The mask matters only for rows with placeholders > 0, images == 0 — text that
spells the placeholder with no image attached. There are zero such rows in
the filtered VLM blend, so #3494 does not need this. Text blends do: in a 100k
sample of a Nemotron post-training blend, 2,539 rows (2.5%) contain a literal
<image> with no image — competitive-programming statements where it replaced
inline math.

Why the model cannot answer this itself

_merge_projected_media enforces one projected feature per valid placeholder.
The mask it derives comes from the padding or attention mask, and both answer
"is this a real token", which is a different question from "is this a media
anchor"
. They coincide only while every media token in a valid position anchors
an image.

The caller knows how many media items each row carries, so it can answer. This
carries that answer to the model.

The packing constraint

The mask is built in sample space, where a row is a sample. Sequence packing
then concatenates samples into one THD sequence and CP-shards it, after which
no per-row question can be asked — so the mask travels through the same
packing transform as input_ids rather than being derived downstream, exactly
as mtp_loss_mask already does.

A mask that is merely misaligned does not raise; it attaches image features to
the wrong positions. The packing tests are the load-bearing ones.

Dependency

Needs NVIDIA-NeMo/Megatron-Bridge#5573, which adds the media_token_validity_mask
argument to NemotronOmniModel.forward. Until that merges and the pin is bumped,
_model_accepts_media_token_validity_mask finds no such parameter and no mask is
ever attached — inert, not broken.

Tests

  • 6 unit tests for mask construction
  • 2 mcore-marked tests pinning the post-packing layout
  • Cluster: 256-row mixed batch (128 real-image rows + 128 text-only rows carrying
    real <image> statements) reached Step 1/1, rollouts 100%, zero alignment failures

@copy-pr-bot

copy-pr-bot Bot commented Aug 13, 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.

@github-actions github-actions Bot added the Documentation Improvements or additions to documentation label Aug 13, 2026
Base automatically changed from smohsenitahe/super-omni-mtp-cp to main August 14, 2026 07:15
@DanialTaheri
DanialTaheri force-pushed the smohsenitahe/media-token-validity-mask branch 2 times, most recently from ae769e0 to 724412b Compare August 14, 2026 20:22
@DanialTaheri

Copy link
Copy Markdown
Contributor Author

/ok to test c3559f0

@copy-pr-bot

copy-pr-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

/ok to test c3559f0

@DanialTaheri, there was an error processing your request: E2

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

@yfw yfw added the CI:Lfast Runs a fast test suite and re-use nightly `main` container (but sync dependencies to PRs version) label Aug 15, 2026
@yfw

yfw commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

/ok to test 724412b

@github-actions

Copy link
Copy Markdown

✅ Submodule Fast-Forward Check Results

Check based on commit: 87a4c05 (PR #3631 from smohsenitahe/media-token-validity-mask)

✅ Submodules that are properly updated:

Megatron-Bridge: ✅ PR branch is ahead of main branch (fast-forward)

All submodule changes look good! ✨

@DanialTaheri
DanialTaheri marked this pull request as ready for review August 17, 2026 16:54
@DanialTaheri
DanialTaheri requested review from a team as code owners August 17, 2026 16:54
@DanialTaheri

Copy link
Copy Markdown
Contributor Author

/ok to test 87a4c05

@DanialTaheri
DanialTaheri force-pushed the smohsenitahe/media-token-validity-mask branch from 87a4c05 to 087a985 Compare August 17, 2026 18:19
@DanialTaheri
DanialTaheri requested a review from a team as a code owner August 17, 2026 18:19
@DanialTaheri
DanialTaheri changed the base branch from main to yifu/bump_mbridge_20260814 August 17, 2026 18:19
@github-actions

Copy link
Copy Markdown

✅ Submodule Fast-Forward Check Results

Check based on commit: 087a985 (PR #3631 from smohsenitahe/media-token-validity-mask)

✅ Submodules that are properly updated:

Megatron-Bridge: ✅ PR branch is ahead of main branch (fast-forward)

All submodule changes look good! ✨

1 similar comment
@github-actions

Copy link
Copy Markdown

✅ Submodule Fast-Forward Check Results

Check based on commit: 087a985 (PR #3631 from smohsenitahe/media-token-validity-mask)

✅ Submodules that are properly updated:

Megatron-Bridge: ✅ PR branch is ahead of main branch (fast-forward)

All submodule changes look good! ✨

@DanialTaheri

Copy link
Copy Markdown
Contributor Author

/ok to test 087a985

@DanialTaheri
DanialTaheri force-pushed the smohsenitahe/media-token-validity-mask branch from 087a985 to 93f16af Compare August 17, 2026 20:19
@DanialTaheri

Copy link
Copy Markdown
Contributor Author

/ok to test 93f16af

Comment thread docs/design-docs/media-token-validity-mask.md Outdated
Base automatically changed from yifu/bump_mbridge_20260814 to main August 18, 2026 03:52
@yfw

yfw commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

I think we don't need the Megatron-Bridge bump in this PR anymore because of 04a687d. Also I don't think anything needs to change in uv.lock anymore?

DanialTaheri and others added 3 commits August 17, 2026 23:22
A media placeholder is an ordinary vocabulary entry, so text can legitimately
contain it -- competitive-programming statements, for instance, where <image>
replaced inline math. The model derives which placeholder positions anchor a
projected feature from the padding or attention mask, but both answer "is this
a real token", which is a different question from "is this a media anchor".
They coincide only while every media token in a valid position anchors an
image, so a text row that spells the placeholder makes the model demand a
feature that was never meant to exist.

Build the answer where it is known. The caller knows how many media items each
row carries, so build_media_token_validity_mask marks the placeholder
positions of media-less rows. Rows that do carry media keep every position
valid, so a genuine placeholder/feature disagreement is still reported rather
than masked away.

The mask is built in sample space, where a row is a sample. Sequence packing
then concatenates samples into one THD sequence and CP-shards it, after which
no per-row question can be asked -- so the mask travels through the same
packing transform as input_ids rather than being derived downstream, exactly
as mtp_loss_mask already does. It is packed in the token dtype because packing
pads with 0, which is a valid token id but not a valid bool, and a model that
slices CP itself receives the unsharded row so the mask lines up after media
insertion.

Only models whose forward declares the argument receive it. The check is on
the signature rather than a class flag because a model that does not know
about the mask would absorb it into **kwargs and ignore it, which looks
identical to the mask having been applied.

Attached at all three forward sites: logprobs run the same forward as
training, so computing them without the mask would score a different media
alignment than the one trained on.

Models that pack internally raise rather than silently dropping the mask: a
misaligned media mask attaches features to the wrong positions without
erroring.

Signed-off-by: DanialTaheri <smohsenitahe@nvidia.com>
Records why placeholder sanitization was removed and what replaced it: the
mask the model derives answers "is this a real token", the merge needs "is
this a media anchor", and those only coincide while every media token anchors
an image. Covers the packing constraint that makes the mask meaningful, and
notes that filtering the blend is now required rather than optional.

Signed-off-by: DanialTaheri <smohsenitahe@nvidia.com>
Co-authored-by: Yi-Fu Wu <yifu.wu@gmail.com>
Signed-off-by: Danial Mohseni Taheri <49656670+DanialTaheri@users.noreply.github.com>
@DanialTaheri
DanialTaheri force-pushed the smohsenitahe/media-token-validity-mask branch from 9298ca1 to 525873d Compare August 18, 2026 06:23
@DanialTaheri

Copy link
Copy Markdown
Contributor Author

/ok to test 525873d

Building the mask needs only input_ids, the placeholder id and the per-row
image counts -- none of which are Megatron concerns. Sitting in the policy
worker put that logic behind a module that cannot be imported without
megatron, so it could not be unit tested and the plumbing had to be trusted.

Move the four pure pieces next to build_media_token_validity_mask, where
they have no such dependency, and leave the worker with the unwrapping glue
that genuinely does. The batch is duck-typed there because
batched_data_dict imports this module, so naming its type would be circular.

Adds twelve tests over the moved pieces, including the two that decide
whether the feature engages at all: the capability probe reads the forward
signature rather than a flag, and a batch with no pixel_values counts as
text-only rather than unreadable.

Signed-off-by: Danial Mohseni Taheri <smohsenitahe@nvidia.com>
@DanialTaheri

Copy link
Copy Markdown
Contributor Author

/ok to test c4a7d97

@yfw yfw removed the CI:L0 Run doctests and unit tests label Aug 18, 2026
@yfw

yfw commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

/ok to test 3cd5cbc

@yfw
yfw merged commit 93bfc06 into main Aug 18, 2026
83 of 84 checks passed
@yfw
yfw deleted the smohsenitahe/media-token-validity-mask branch August 18, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI:Lfast Runs a fast test suite and re-use nightly `main` container (but sync dependencies to PRs version) Documentation Improvements or additions to documentation Feature multimodal t-mcore

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants