nemo_rl.data.multimodal_utils#
Module Contents#
Classes#
A logical batch of rows backed by packable tensor segments. |
Functions#
Return whether a processor requires explicit image placeholders. |
|
Companion tag key carrying per-row shapes for |
|
Multimodal wire fields the rollout actually wrote for this batch. |
|
Per-sample tag rows carrying what |
|
In place: rebuild |
|
Cut pixel segments into vision patches and pack them into one sequence. |
|
Return the preprocessing setting shared by every input. |
|
The wire value for one multimodal field. Dispatched by registry membership. |
|
Get keys of the multimodal data that can be used as model inputs. |
|
Special considerations for packing certain keys from certain processors. |
|
Return materialization preprocessing for one processor input. |
|
Extract packed media inputs and sequence-aligned auxiliary tensors. |
|
Resolve the image path to a PIL.Image object. |
|
Return |
|
Encode a PIL Image as a base64 |
|
Return a string media source from a Responses/Chat content part. |
|
Extract tagged image and video sources in encounter order. |
|
Compare tagged media by string value or object identity. |
|
Preserve a ragged per-image |
|
Derive image sizes and preserve native-shape tiles for patchification. |
|
Convert a processor’s list outputs back to tensors, in place. |
|
Attach image tensors while preserving rollout-token/media parity. |
|
Record which rollout tokens are processor-owned media placeholders. |
|
Inline a local or |
|
Get all media from a message log item. |
|
Mark media-token positions in rows that carry no media of that modality. |
|
The vocabulary id these model chunks treat as a media placeholder, if any. |
|
Whether a model chunk’s forward takes an explicit media-token validity mask. |
|
How many images each row of the batch actually carries. |
|
Attach a |
Data#
API#
- nemo_rl.data.multimodal_utils.VLLM_CONTENT_KEY#
‘vllm_content’
- nemo_rl.data.multimodal_utils.VLLM_MULTI_MODAL_DATA_KEY#
‘vllm_multi_modal_data’
- nemo_rl.data.multimodal_utils.VLLM_PROMPT_KEYS#
‘frozenset(…)’
- nemo_rl.data.multimodal_utils.IMAGE_CONTENT_TYPES#
‘frozenset(…)’
- nemo_rl.data.multimodal_utils.VIDEO_CONTENT_TYPES#
‘frozenset(…)’
- nemo_rl.data.multimodal_utils.AUDIO_CONTENT_TYPES#
‘frozenset(…)’
- nemo_rl.data.multimodal_utils.MULTIMODAL_CONTENT_TYPES#
‘frozenset(…)’
- nemo_rl.data.multimodal_utils.ROLLOUT_MATCHED_MEDIA_KEY#
‘_rollout_matched_media’
- nemo_rl.data.multimodal_utils.MEDIA_TAGS#
None
- nemo_rl.data.multimodal_utils.MEDIA_TAGS_REVERSED#
None
- nemo_rl.data.multimodal_utils.CACHED_VIDEO_FRAME_MANIFEST_MAGIC#
b’NEMO_RL_CACHED_VIDEO_FRAMES_V1\n’
- nemo_rl.data.multimodal_utils.CACHED_VIDEO_FRAME_MANIFEST_MIME#
‘video/x-nemo-rl-cached-frames’
- nemo_rl.data.multimodal_utils.DEFAULT_MEDIA_EXTENSIONS#
None
- nemo_rl.data.multimodal_utils._PLACEHOLDER_STYLE_PROCESSOR_NAMES#
‘frozenset(…)’
- nemo_rl.data.multimodal_utils.MEDIA_TAGS_TO_ALLOWED#
None
- nemo_rl.data.multimodal_utils.MEDIA_TAG_PATTERN#
‘compile(…)’
- nemo_rl.data.multimodal_utils.logger#
‘getLogger(…)’
- nemo_rl.data.multimodal_utils.uses_image_placeholder(processor: Any) bool#
Return whether a processor requires explicit image placeholders.
- Parameters:
processor – Multimodal processor to classify.
- Returns:
Whether the processor expands image placeholders through
__call__rather than tokenizedapply_chat_template.
- nemo_rl.data.multimodal_utils.PER_TOKEN_MULTIMODAL_FIELDS#
‘frozenset(…)’
- nemo_rl.data.multimodal_utils.PACKED_MULTIMODAL_FIELDS#
‘frozenset(…)’
- nemo_rl.data.multimodal_utils.ROW_SHAPES_SUFFIX#
‘__row_shapes’
- nemo_rl.data.multimodal_utils.row_shapes_key(field: str) str#
Companion tag key carrying per-row shapes for
field.
- nemo_rl.data.multimodal_utils.ROW_GEOMETRY_SHAPES#
‘shapes’
- nemo_rl.data.multimodal_utils.ROW_GEOMETRY_PREPROCESS_MODE#
‘preprocess_mode’
- nemo_rl.data.multimodal_utils.ROW_GEOMETRY_PREPROCESS_KWARGS#
‘preprocess_kwargs’
- nemo_rl.data.multimodal_utils.WIRE_MULTIMODAL_FIELDS#
None
- nemo_rl.data.multimodal_utils.present_multimodal_fields( ) list[str]#
Multimodal wire fields the rollout actually wrote for this batch.
Intersecting with
meta.fieldsis required, not defensive: the noop adapter and the TQ contract both raise on a fetch for a field that was never written, and text-only runs write none of these. Sorted for a deterministic field list across ranks.
- nemo_rl.data.multimodal_utils.multimodal_row_tags(
- multimodal: dict,
- sample_count: int,
Per-sample tag rows carrying what
to_wire’s flattening removes.KVBatchMeta.tagsis the transport’s channel for per-sample primitives: it is aligned 1:1 withsample_idsand projected automatically bysubset/slice/concat, so a worker holding a shard gets its own rows without anyone re-keying them. The data plane never interprets the contents.Carries
shapes(per-row, and unrecoverable onceto_wireflattens) and the field’s preprocessing settings. Deliberately not a pad target: the width padding lands at is scratch that the model discards. Mcore consumes pre-patchified pixels without spatial dimensions, while AutoModel pads at materialization, so nothing batch-wide has to be agreed across shards.
- nemo_rl.data.multimodal_utils.reassemble_packed_multimodal(
- fields: dict,
- tags: list[dict[str, Any]] | None = None,
In place: rebuild
PackedTensorfor packed fields, consuming companions.Called by the read path once its columns are materialized. Leaves anything that is not a wire-form packed field untouched, so it is safe to call on any column dict.
- Raises:
ValueError – A packed field arrived without its shapes companion. Every producer mints the two together (
multimodal_row_tagsbesideencode_multimodal_for_wire) and everyKVBatchMetatransform projectstagsalongsidesample_ids, so a missing companion means the transport dropped it. Reconstructing anyway would hand the model 1-D pixels and train image-blind without an error.
- nemo_rl.data.multimodal_utils._patchify_segments(
- segments: list[torch.Tensor],
- *,
- patch_dim: int,
Cut pixel segments into vision patches and pack them into one sequence.
Each
[N, channels, H, W]segment is processed at its native resolution into a[C_i, P²]block, whereC_iisN * rows * columnsandP²is the flattened patch width (channels * patch_dim**2). Blocks are packed along dimension zero, then a batch dimension is added to produce[1, total_C, P²]. Already-patchified segments in that final layout are accepted so repeated materialization is safe.
- from_packed_tensors: list[PackedTensor],
Return the preprocessing setting shared by every input.
- class nemo_rl.data.multimodal_utils.PackedTensor(
- tensors: Union[torch.Tensor, list[Optional[torch.Tensor]], list[None]],
- dim_to_pack: int,
- *,
- preprocess_mode: Optional[str] = None,
- preprocess_kwargs: Optional[dict[str, Any]] = None,
- _row_offsets: Optional[list[int]] = None,
- _segment_indices: Optional[list[int]] = None,
- _segment_provenance: Optional[list[bytes]] = None,
A logical batch of rows backed by packable tensor segments.
The default representation is intentionally the legacy one: every entry in
tensorsis one logical row and no deduplication metadata is allocated.enable_deduplicationadds stable provenance to the physical segments. Operations that combine or slice dedup-enabled values then use a CSR-like logical-row mapping:_row_offsetspartitions the flattened logical segment references._segment_indicesmaps each logical segment reference totensors._segment_provenanceis stable across deepcopy/pickle and is the only evidence used to re-intern physical segments.
Prompt identity is deliberately absent: belonging to the same prompt group makes media a candidate for sharing, but never proves media equality.
Worked example. Prompt A has one image and prompt B has two, each already merged by :meth:
merge_segmentsinto a single logical row::A: tensors = [tA] _row_offsets = [0, 1] _segment_indices = [0] B: tensors = [tB0, tB1] _row_offsets = [0, 2] _segment_indices = [0, 1]
GRPO then expands each prompt into G generations. The rows are deep-copied while
_prepare_multimodal_sharingaliases the media leaves, so- Meth:
concatre-interns them by provenance. For G=3::tensors = [tA, tB0, tB1] # 3 physical segments _row_offsets = [0, 1, 2, 3, 5, 7, 9] # 6 logical rows _segment_indices = [0, 0, 0, 1, 2, 1, 2, 1, 2] len() = 6
Six logical rows over nine segment references and three physical tensors, so physical memory is flat in G. Row 4 reads as
_segment_indices[_row_offsets[4]:_row_offsets[5]] == [1, 2], i.e. both of prompt B’s images, without copying either.Initialization
Wrap per-item tensors for concatenation along
dim_to_pack.- Parameters:
tensors – A tensor or list of per-item tensors. List entries may be
Nonefor items without this modality.dim_to_pack – Dimension along which
as_tensorconcatenates.preprocess_mode – Optional preprocessing applied by
as_tensor. Supported values arepad_to_max_shapeandpatchify. Patchify requiresdim_to_pack=0and changes 4-D inputs into a 3-D[1, total_patches, P²]tensor.preprocess_kwargs – Extra arguments for
preprocess_mode. Patchify requirespatch_dim.
- __setstate__(state: dict[str, Any]) None#
Restore both current and pre-deduplication pickled instances.
- broadcast_parts() tuple[nemo_rl.data.multimodal_utils.PackedTensor, list[Optional[tuple[int, ...]]], str, str, list[torch.Tensor]]#
Split semantic state from physical data for replica broadcast.
The tensor-free header retains every other instance attribute, so new semantic state does not require a parallel broadcast schema update.
- rebuild_from_broadcast_parts(
- shapes: list[Optional[tuple[int, ...]]],
- payload: torch.Tensor,
Attach received physical data to a header from
broadcast_parts.
- property _preprocess_spec: dict[str, Any]#
Return keyword arguments that preserve preprocessing in a copy.
- property deduplication_enabled: bool#
Whether this value carries stable physical-segment provenance.
- logical_segment_counts_by_row() list[int]#
Return the number of non-empty media segments in each logical row.
- iter_logical_segments()#
Yield physical tensor segments in logical row/segment order.
- enable_deduplication() nemo_rl.data.multimodal_utils.PackedTensor#
Assign stable provenance lazily without changing logical contents.
- _row_segment_indices(row: int) list[int]#
- __deepcopy__(
- memo: dict[int, Any],
Share immutable media segments only for an explicitly enabled value.
- as_tensor(
- device: torch.device | None = None,
- mode: str | None = None,
- __len__() int#
- to(
- device: str | torch.device,
Move physical segments in place, retaining provenance.
A device move is value-preserving, so provenance stays valid and two copies of the same segment still re-intern. This is the opposite of
- Meth:
to_dtype, which changes values and therefore mints fresh provenance. The asymmetry is deliberate: :meth:concatre-interns on provenance alone, so a value-changing operation must not keep it.
The corollary is that two values sharing provenance on different devices would merge to whichever appears first in
concat. That is currently unreachable –BatchedDataDict.toandget_multimodal_dictonly touch top-level values, while message-level segments are nested insidemessage_logand worker-side device moves happen post-serialization on independent copies – and it would surface as a loud mixed-devicetorch.caterror rather than silent corruption. Keep it that way: do not move a subset of segments.
- to_dtype(
- dtype: torch.dtype,
Return an independent wrapper without expanding logical segments.
Dtype conversion creates new physical tensor values, so deduplicated inputs receive new provenance. When the dtype already matches, immutable tensor segments and their provenance remain shared, but mutable wrapper state is copied. The logical row-to-segment mapping is preserved exactly.
Non-floating-point segments are returned unchanged. Integer media metadata (grid sizes, frame counts) is index data, so casting it to a float dtype would silently corrupt it.
- slice(
- indices: Union[list[int], torch.Tensor],
- classmethod empty_like( ) nemo_rl.data.multimodal_utils.PackedTensor#
Return empty logical rows matching
other.
- classmethod empty_rows_like(
- other: nemo_rl.data.multimodal_utils.PackedTensor,
- num_rows: int,
Return
num_rowslogical rows containing no media segments.
- classmethod concat(
- from_packed_tensors: list[nemo_rl.data.multimodal_utils.PackedTensor],
Concatenate a list of PackedTensor objects into a single PackedTensor.
The underlying tensors from the PackedTensors are combined into a single list of tensors and used to create a new PackedTensor.
Each batch must have the same dim_to_pack.
Example:
>>> import torch >>> from nemo_rl.data.multimodal_utils import PackedTensor >>> p1 = PackedTensor([torch.tensor([1, 2, 3]), torch.tensor([4, 5, 6])], dim_to_pack=0) >>> p2 = PackedTensor([torch.tensor([7, 8, 9])], dim_to_pack=0) >>> p3 = PackedTensor.concat([p1, p2]) >>> p3.tensors [tensor([1, 2, 3]), tensor([4, 5, 6]), tensor([7, 8, 9])] >>> p3.as_tensor() tensor([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>>
- classmethod merge_segments(
- from_packed_tensors: list[nemo_rl.data.multimodal_utils.PackedTensor],
Merge message-turn values, collapsing compact inputs to one row.
The legacy path retains one logical row per physical segment; its caller materializes those segments into one conversation row later.
- classmethod flattened_concat(
- from_packed_tensors: list[nemo_rl.data.multimodal_utils.PackedTensor],
Given a list of PackedTensor objects, flattens each PackedTensor and then concatenates them into a single PackedTensor.
Each PackedTensor is first flattened by packing along the PackedTensor’s
dim_to_packdimension. Then, the resulting flattened tensors are used to create a new PackedTensor.This is different from
PackedTensor.concatwhich simply extends the underlying list of tensors. This is important because thesliceand__len__methods operate on the underlying list of tensors. Note, however, that callingas_tensoron the resulting PackedTensor will result in the same tensor asconcat.Each batch must have the same dim_to_pack.
Example:
>>> import torch >>> from nemo_rl.data.multimodal_utils import PackedTensor >>> p1 = PackedTensor([torch.tensor([1, 2, 3]), torch.tensor([4, 5, 6])], dim_to_pack=0) >>> p2 = PackedTensor([torch.tensor([7, 8, 9])], dim_to_pack=0) >>> p3 = PackedTensor.flattened_concat([p1, p2]) >>> p3.tensors [tensor([1, 2, 3, 4, 5, 6]), tensor([7, 8, 9])] >>> p3.as_tensor() tensor([1, 2, 3, 4, 5, 6, 7, 8, 9]) >>>
- _row_segments() list[list[torch.Tensor]]#
Per logical row, its non-empty physical segments.
One entry per logical row. Under deduplication a logical row maps to several shared physical segments (
_row_offsets/_segment_indices), so iteratingself.tensorsdirectly would yield the physical segment count instead of the batch size._row_segment_indicesdegrades to[row]for the legacy one-tensor-per-row layout.
- static _shapes_of(
- row_segments: list[list[torch.Tensor]],
shapes[row][segment]for an already-walked row/segment list.Shared by :meth:
row_shapesand :meth:to_wireso the shape encoding cannot drift between the tag minter and the payload encoder.
- row_shapes() list[list[list[int]]]#
The
shapeshalf of :meth:to_wire, without encoding the payload.Callers that only need the geometry – :func:
multimodal_row_tags– use this so they do not payto_wire’storch.cata second time.
- to_wire() tuple[Optional[torch.Tensor], list[list[list[int]]]]#
Encode as a flattened
torch.jaggedtensor plus its row shapes.Returns
(None, [])when every logical row is empty so the caller can skip the field entirely. Otherwise:nested— one row per logical row, each row the 1-D concat of that row’s segments. Flattening is what makestorch.jaggedtotal: rows vary only in dim 0, so ragged trailing dims and mixed rank both encode, and nothing is padded to make a container accept them. Empty rows become zero-length placeholders.shapes—shapes[row][segment]is that segment’s true shape. Required because TQ derivesper_sample_shapesfrom the value it is handed, so flat rows make it record flat lengths. The caller ships this onKVBatchMeta.tags; see- func:
nemo_rl.data.multimodal_utils.multimodal_row_tags.
Only
dim_to_pack=0is supported today; other values would needragged_idxon the nested tensor and a matching transpose in :meth:from_wire.
- classmethod from_wire(
- nested: torch.Tensor,
- shapes: list[list[list[int]]],
- *,
- preprocess_mode: Optional[str] = None,
- preprocess_kwargs: Optional[dict[str, Any]] = None,
Reconstruct from the value produced by :meth:
to_wire.Returns
Nonefor an empty batch (no rows); the caller should skip the field entirely rather than instantiate an emptyPackedTensor.shapesis the companion returned by :meth:to_wire, carried onKVBatchMeta.tags(see :func:multimodal_row_tags). Each flat row is split by segmentnumeland reshaped back to its true shape. It is required, not optional: reconstructing without it yields 1-D segments, which train image-blind without erroring.A zero-length row means the sample had no media – it becomes
None(not a(0, ...)tensor) so an image-free shard reconstructs as legacy does:as_tensorreturnsNoneandlogical_segment_counts_by_rowreports 0 rather than 1.The preprocessing settings are restored onto the rebuilt value, not materialized. Segments come back at their true shapes and stay separate via the CSR row map; :meth:
as_tensorpreprocesses at use time.Mirrors :meth:
to_wire; both assumedim_to_pack=0.
- nemo_rl.data.multimodal_utils.encode_multimodal_for_wire(
- k: str,
- v: Union[nemo_rl.data.multimodal_utils.PackedTensor, torch.Tensor],
The wire value for one multimodal field. Dispatched by registry membership.
Returns
Nonewhen the field has nothing to ship (an all-empty packed batch), in which case the caller omits the column entirely. The wire key is alwaysk: one wire field per logical field.Payload only. Per-token fields ride rectangular; packed fields ride as one flattened
torch.jaggedvalue. The geometry :meth:PackedTensor.from_wireneeds to undo that flattening – per-row segment shapes plus the preprocessing settings – is minted separately by- Func:
multimodal_row_tagsand shipped onKVBatchMeta.tags. TQ cannot derive it, because it readsper_sample_shapesoff the flattened rows it is handed.
- nemo_rl.data.multimodal_utils.UNDECLARED_MULTIMODAL_MODEL_INPUTS#
(‘imgs_sizes’, ‘num_frames’, ‘pixel_values_flat’, ‘image_num_patches’)
- nemo_rl.data.multimodal_utils.get_multimodal_keys_from_processor(processor) list[str]#
Get keys of the multimodal data that can be used as model inputs.
This will be used in the data_processor function to determine which keys to use as model inputs.
- nemo_rl.data.multimodal_utils.get_multimodal_default_settings_from_processor(
- processor,
- nemo_rl.data.multimodal_utils.get_dim_to_pack_along(processor, key: str) int#
Special considerations for packing certain keys from certain processors.
In most cases, the packed items are along dim 0
- nemo_rl.data.multimodal_utils.get_preprocess(
- processor: Any,
- key: str,
Return materialization preprocessing for one processor input.
- nemo_rl.data.multimodal_utils.extract_multimodal_model_inputs(
- processor: Any,
- processed: dict[str, Any],
Extract packed media inputs and sequence-aligned auxiliary tensors.
- nemo_rl.data.multimodal_utils.resolve_to_image(
- image_path_or_image: str | PIL.Image.Image,
Resolve the image path to a PIL.Image object.
image_path can be either:
path to local file
url to image
base64 encoded image
- nemo_rl.data.multimodal_utils.image_size_from_source(
- source: str | PIL.Image.Image,
Return
(width, height)for an image source.
- nemo_rl.data.multimodal_utils.image_to_data_url(image: PIL.Image.Image, fmt: str = 'PNG') str#
Encode a PIL Image as a base64
data:URL.- Parameters:
image – PIL image to encode.
fmt – PIL image format used for serialization (e.g.
"PNG","JPEG"). The value is also lowercased and embedded in the MIME type of the returned URL.
- Returns:
A
data:image/<fmt>;base64,<payload>URL suitable for embedding in an OpenAI Responsesinput_imagecontent part.
- nemo_rl.data.multimodal_utils.get_responses_content_part_url(
- part: dict[str, Any],
- *keys: str,
Return a string media source from a Responses/Chat content part.
- nemo_rl.data.multimodal_utils.extract_input_media_sources_from_responses_messages(
- messages: Any,
Extract tagged image and video sources in encounter order.
- nemo_rl.data.multimodal_utils.media_sources_equal(
- left: tuple[str, Any],
- right: tuple[str, Any],
Compare tagged media by string value or object identity.
- nemo_rl.data.multimodal_utils._materialize_ragged_pixel_values(
- processed: dict[str, Any],
- processor: Any,
Preserve a ragged per-image
pixel_valueslist for materialization.Tiles remain separate until
PackedTensor.as_tensormaterializes them.
- nemo_rl.data.multimodal_utils._stack_ragged_pixel_values(
- processed: dict[str, Any],
- tiles: list[torch.Tensor],
- processor: Any,
Derive image sizes and preserve native-shape tiles for patchification.
- nemo_rl.data.multimodal_utils._restore_tensors(processed: dict[str, Any]) None#
Convert a processor’s list outputs back to tensors, in place.
return_tensors=Nonemakes the processor hand back every output as plain Python lists, not only the raggedpixel_valuesthat mode was requested for. Downstream expects tensors –input_idsin particular is rank-checked – so restore the rest of the batch to whatreturn_tensors="pt"would have produced. Values that resist conversion (genuinely ragged per-image metadata) are left for their own handling.
- nemo_rl.data.multimodal_utils._image_num_tokens_from_processed(
- processed: dict[str, Any],
- nemo_rl.data.multimodal_utils.attach_image_model_inputs_to_message(
- message: dict[str, Any],
- *,
- images: list[PIL.Image.Image],
- processor: Any,
- pad_dynamic_image_shapes: bool = False,
- expected_num_tokens_per_image: collections.abc.Sequence[int] | None = None,
Attach image tensors while preserving rollout-token/media parity.
- nemo_rl.data.multimodal_utils.attach_processor_media_token_validity_mask(
- message: dict[str, Any],
- processor: Any,
Record which rollout tokens are processor-owned media placeholders.
- nemo_rl.data.multimodal_utils._VIDEO_EXT_TO_MIME#
None
- nemo_rl.data.multimodal_utils.video_path_to_data_url(video_path: str) str#
Inline a local or
file://video as a base64 data URL.
- nemo_rl.data.multimodal_utils.get_media_from_message(
- message: dict[str, Any],
Get all media from a message log item.
- nemo_rl.data.multimodal_utils.load_media_from_message(
- message: dict[str, Any],
- processor=None,
- multimodal_load_kwargs: Optional[dict[str, dict[str, Any]]] = None,
- nemo_rl.data.multimodal_utils.build_media_token_validity_mask(
- input_ids: torch.Tensor,
- media_token_id: int,
- media_counts_by_row: collections.abc.Sequence[int],
- base_mask: Optional[torch.Tensor] = None,
Mark media-token positions in rows that carry no media of that modality.
A media token is an ordinary vocabulary entry with its own embedding row. It only means “a projected feature belongs here” when media is attached; in a text-only row the same id is whatever the author wrote, and counting it as a placeholder makes the model demand a feature that does not exist.
Rows that do carry media keep every position valid, so a real placeholder/feature disagreement there is still reported rather than silently masked away.
- Parameters:
input_ids –
[B, S]token ids, one row per sample.media_token_id – Vocabulary id the model treats as a media placeholder.
media_counts_by_row –
Attached media items per row, e.g. from
- meth:
PackedTensor.logical_segment_counts_by_row.
base_mask – Optional
[B, S]validity mask to refine, so masks for several modalities can be combined.
- Returns:
A
[B, S]bool mask, orNonewhen nothing needs masking and the caller should leave the model’s own derivation untouched.
- nemo_rl.data.multimodal_utils.media_placeholder_token_id_from_chunks(
- chunks: collections.abc.Sequence[Any],
The vocabulary id these model chunks treat as a media placeholder, if any.
- nemo_rl.data.multimodal_utils.chunks_accept_media_token_validity_mask(
- chunks: collections.abc.Sequence[Any],
Whether a model chunk’s forward takes an explicit media-token validity mask.
Checked against the signature rather than a class flag so a model that does not know about the mask never receives it: such a forward would absorb it into
**kwargsand silently ignore it, which looks identical to the mask having been applied. Failing to send it is visible; sending it into a void is not.
- nemo_rl.data.multimodal_utils.image_counts_by_row(
- batch: Any,
- num_rows: int,
How many images each row of the batch actually carries.
Returns None when the batch describes its images in a way this cannot read, so the caller leaves the model’s own derivation alone rather than guessing a count and masking against it.
- nemo_rl.data.multimodal_utils.attach_media_token_validity_mask(
- batch: Any,
- media_token_id: Optional[int],
Attach a
[B, S]mask of media-token positions that anchor media features.Builds the mask while rows still are samples. Sequence packing later concatenates those rows into one THD sequence, after which no per-row information can be recovered, so the packing step carries this through the same transform as
input_idsrather than deriving it downstream.- Parameters:
batch – Batch with
[B, S]input_ids.media_token_id – Media placeholder token ID, or None if the model does not accept the mask, in which case any existing mask is removed. The Megatron policy worker passes None when
_model_accepts_media_token_validity_maskis False.