Skip to content

vision

vision

Vision: an image-text-to-text checkpoint's vision tower, and its blocks.

A multimodal wrapper is a text model plus a vision tower, a projector, and a step that scatters the projected features into the text stream at the image tokens. The text model keeps the standard names it has everywhere; the tower answers to model.vision, its blocks to vision.layers[i], and the last module before the scatter to model.projector. The family names them in its RENAME (keys that resolve only on the wrapper's tree) and keys these envoys on the tower's module types in its ENVOYS.

The tower's blocks are pre-norm attention + MLP blocks on transformers' shared attention interface (SigLIP, CLIP), so they are Layer, Attention and Mlp with the stream values re-annotated as Patches: layer_input, layer_output, attention_output and mlp_output mean what they mean on a text block, vision.layers[i].layer_input + attention_output + mlp_output == layer_output. The attention interior is inherited unchanged: attention_probabilities is a Pattern whose batch axis is the tower's (one row per image) and whose query/key axes are the image's patches. There is no causal mask (a patch attends to every patch of its image); a tower that masks does so for its own layout (Pixtral's block-diagonal mask between packed images, Gemma 4's padded keys). The tower's sizes are on Vision, read off its own config; the root's sizes stay the text model's.

Where the image meets the text model is the tower's too: vision.image_token_mask (which positions of the text batch hold an image token, off the root's inputs) and vision.image_features (what the wrapper scatters into the token embeddings, flat over those tokens, read at the scatter in the forward of the module the family keys ImageScatter on), so layers[0].input[vision.image_token_mask] == vision.image_features.

Patches module-attribute

Patches = Float[Tensor, f'{IMAGES} {PATCHES} {VISION_HIDDEN}']

ImageTokenMask module-attribute

ImageTokenMask = Bool[Tensor, f'{BATCH} {SEQ}']

ImageFeatures module-attribute

ImageFeatures = Float[Tensor, f'{IMAGE_TOKENS} {HIDDEN}']

PER_IMAGE module-attribute

PER_IMAGE = "the Qwen ViT's attention makes one interface call per image (attention_interface_2; one per window on Qwen2.5-VL's windowed blocks), so no one tensor is the block's pattern; read attention_queries and attention_keys, whole, and split them where the calls do: at the attention's cu_seqlens argument (self_attn.inputs[1]['cu_seqlens']), which per image is the processor's image_grid_thw"

ImageScatter

ImageScatter(*args: Any, **kwargs: Any)

Bases: Standard

The wrapper's inner model (model.model): the module whose forward scatters the image features into the token embeddings.

A family keys it on the wrapper model's type (LlavaModel: ImageScatter); Vision.image_features reads the scatter_argument of the scatter operation, the features in inputs_embeds.masked_scatter(image_mask, image_features), which is what the text model receives at the image tokens whatever the wrapper did after its projector (LLaVA-NeXT's unpadding and newline tokens). A wrapper that writes the features in elsewhere subclasses it with its own operation and argument. The forward is instrumented at build (sourced), so the scatter is served after the tower's values, which run inside the same forward. Carries no values of its own.

A wrapper that scatters in the root's own forward (Llama 4's Llama4ForConditionalGeneration) has no child to key: its family sets ROOT_SCATTER to the operation's name, the root is the host (scatter_host), and StandardizedTransformer instruments its own forward at build.

sourced class-attribute instance-attribute

sourced = True

scatter class-attribute instance-attribute

scatter = 'inputs_embeds_masked_scatter_0'

scatter_argument class-attribute instance-attribute

scatter_argument: int | str = 1

VisionLayer

VisionLayer(*args: Any, **kwargs: Any)

Bases: Layer

A vision tower's block: layer_input and layer_output are the tower's stream, Patches.

layer_input

layer_input(value: Tensor) -> Tensor

attention_norm_output

attention_norm_output(value: Tensor) -> Tensor

layer_mid

layer_mid(value: Tensor) -> Tensor

mlp_norm_output

mlp_norm_output(value: Tensor) -> Tensor

mlp_input

mlp_input(value: Tensor) -> Tensor

layer_output

layer_output(value: Tensor) -> Any

VisionAttention

VisionAttention(*args: Any, **kwargs: Any)

Bases: Attention

A vision tower block's attention: what it adds to the tower's stream is Patches; the interior is Attention's.

off_interface

off_interface() -> str | None

attention_output

attention_output(value: Tensor) -> Any

attention_premix

attention_premix(value: Tensor) -> Tensor

VisionMlp

VisionMlp(*args: Any, **kwargs: Any)

Bases: Mlp

A vision tower block's MLP: what it adds to the tower's stream is Patches.

mlp_output

mlp_output(value: Tensor) -> Any

mlp_activation

mlp_activation(value: Tensor) -> Neurons

The activation's output over the patches, in the tower's own row layout.

mlp_neurons

mlp_neurons(value: Tensor) -> Neurons

What the down projection sums over the patches, in the tower's own row layout.

Vision

Vision(*args: Any, **kwargs: Any)

Bases: Standard

model.vision: a vision tower's root. Its blocks are layers; its sizes are its own config's.

Values: patch_embeddings, the patch embedding's output one row per patch (before any position embedding, CLS token or pre-norm the tower adds), and tower_output, the last block's stream after the tower's final norm where it has one, before any pooling, CLS dropping or adapter (the tower's last_hidden_state, unless the tower returns something after those). The host reads the tower its own way: Gemma 3 pools tower_output, Llava 1.5 takes vision.layers[-2].layer_output and drops the CLS token, so what reaches the projector is projector.input, not necessarily tower_output. Where the image meets the text model: image_token_mask, read off the model's inputs, and image_features, read at the scatter (ImageScatter, keyed from the root), so layers[0].input[image_token_mask] == image_features. Both need an image to reach the model (no_images).

The sizes (num_layers, vision_hidden_size, num_heads, head_dim, intermediate_size, patch_size, image_size) are read off the tower's own config, the plain spellings SigLIP's and CLIP's use; a tower that spells one its own way overrides the property on a subclass. The tower's width is a different quantity from the text model's hidden_size and is named apart, so axis_size on image_features (text width, served here) walks past the tower to the root; num_heads, head_dim, num_layers and intermediate_size keep the plain names and shadow the root's on purpose, because the tower's values want the tower's numbers.

ATTRIBUTE DESCRIPTION
layers

The tower's blocks, each a VisionLayer.

TYPE: Sequence[VisionLayer]

patch_embed

The patch embedding (a convolution on SigLIP and CLIP).

TYPE: Sequence[VisionLayer]

norm

The final norm over the patches, where the tower has one (SigLIP's post_layernorm; CLIP's norms only the pooled CLS token and is not aliased).

TYPE: Sequence[VisionLayer]

layers instance-attribute

layers: Sequence[VisionLayer]

num_layers property

num_layers: int

vision_hidden_size property

vision_hidden_size: int

num_heads property

num_heads: int

head_dim property

head_dim: int

Width of one head: the config's head_dim when it says, else vision_hidden_size // num_heads.

intermediate_size property

intermediate_size: int

patch_size property

patch_size: int

Side of one square patch, in pixels.

image_size property

image_size: int

Side of the square image the tower is configured for, in pixels; Unavailable on a tower with no fixed resolution (variable_resolution).

image_token_mask

image_token_mask(value: Any) -> Any

patch_embeddings

patch_embeddings(value: Tensor) -> Tensor

tower_output

tower_output(value: Tensor) -> Any

image_features

image_features(value: Tensor) -> Tensor

no_images

no_images() -> str | None

Why no image reaches the model this tower belongs to, or None: the one place that decides.

A wrapper whose family names no projector (where the image enters the text model is unknown), or a load without a processor (a task="text-generation" load of the wrapper).

support

support(layer: int | None = None) -> dict[str, Any]

Which tower values this checkpoint has, the way StandardizedTransformer.support reports the text model's.

The tower's own values (image_token_mask and image_features among them) plus every block value over layers ("layer_output", "self_attn.attention_probabilities", ...): None when available on every block, else {layer: reason}. With layer, that block's values alone. Empty on a load no image reaches (no_images): the tower never runs, so the model's support has no vision rows, and a read of an image value raises Unavailable saying why.

QwenVisionAttention

QwenVisionAttention(*args: Any, **kwargs: Any)

Bases: VisionAttention

The Qwen ViT's attention: one interface call per image (or window), so the interior is read whole around them.

attention_queries, attention_keys and attention_values are the whole [1, heads, patches, head_dim] tensors (the queries and keys after the 2D rotary embedding) before the module splits them per image, and attention_head_outputs is the per-image outputs concatenated back, [1, patches, heads, head_dim], under any implementation but flash. attention_scores and attention_probabilities are Unavailable (PER_IMAGE): loading eager does not help, since the module splits under every implementation.

off_interface

off_interface() -> str | None

attention_queries

attention_queries(value: Tensor) -> Queries

Every image's queries, [1, heads, patches, head_dim], before the per-image split; assign or edit in place.

attention_keys

attention_keys(value: Tensor) -> Keys

Every image's keys, [1, heads, patches, head_dim], before the per-image split; assign or edit in place.

attention_values

attention_values(value: Tensor) -> Values

Every image's values, [1, heads, patches, head_dim], before the per-image split; assign or edit in place.

attention_head_outputs

attention_head_outputs(value: Tensor) -> HeadOutputs

Every image's per-head outputs concatenated back, [1, patches, heads, head_dim], before the output projection.

QwenVision

QwenVision(*args: Any, **kwargs: Any)

Bases: Vision

The Qwen ViT (Qwen2-VL, Qwen2.5-VL, Qwen3-VL, Qwen3.5): packed, its merger inside, at model.visual.

The tower runs on [patches, vision_hidden_size], every image's patches concatenated, so its stream values are Patches with 1 in the images axis, [1, patches, vision_hidden_size], and its blocks' attention is a QwenVisionAttention.

The processor cuts each image into patch_size squares (two frames deep), and lays them out in spatial_merge_size x spatial_merge_size blocks, each block's patches consecutive, images one after another; the merger folds each block into one image token. So patches runs over every image of the invoke, image_grid_thw ([t, h, w] per image, in patches) splits it, and an image's patches are in merge-block order, not raster order. Qwen2.5-VL's tower further permutes them into attention windows at entry: its blocks' values, tower_output and the merger's output are in window order, and the tower restores the merge-block order after the merger.

The tower has no final norm, so tower_output is the last block's stream, the merger's input. It is served at the tower's output, after the merger has run, so projector.input and projector.output are read before tower_output.

Sizes, off the vision config: vision_hidden_size is the tower's width (embed_dim on Qwen2-VL, whose config's hidden_size is the merger's output width), num_heads, intermediate_size (Qwen2-VL's is embed_dim * mlp_ratio), patch_size, spatial_merge_size and window_size (Qwen2.5-VL's, in pixels; None on the others). image_size is Unavailable: the tower takes any resolution.

vision_hidden_size property

vision_hidden_size: int

num_heads property

num_heads: int

intermediate_size property

intermediate_size: int

image_size class-attribute instance-attribute

image_size = property(variable_resolution)

spatial_merge_size property

spatial_merge_size: int

Side of the square block of patches the merger folds into one image token.

window_size property

window_size: int | None

Side of an attention window in pixels (Qwen2.5-VL), or None on a tower without windows.

PixtralVision

PixtralVision(*args: Any, **kwargs: Any)

Bases: Vision

Pixtral's tower (Mistral 3, Llava-Pixtral): packed, every image's patches in one row.

The convolution runs on the batch padded to its largest image, each image is cropped to its own grid, and the grids are flattened and concatenated into [1, all patches, vision_hidden_size] before ln_pre; the blocks attend under a block-diagonal mask, so no patch sees another image's. So Patches has a leading 1 here, and patch_embeddings is that packed row as it enters ln_pre (the convolution's own output, patch_embed.output, is still the padded grid). The processor's image_sizes split the row per image: (height // patch_size) * (width // patch_size) patches each, in order. The attention interior is whole: one interface call over the packed row, so attention_probabilities is [1, heads, all patches, all patches], zero between images. image_size is Unavailable: the config's is the largest side the processor resizes to, not one size every image has.

image_size class-attribute instance-attribute

image_size = property(variable_resolution)

patch_embeddings

patch_embeddings(value: Tensor) -> Patches

Every image's patch embeddings, [1, all patches, vision_hidden_size], image after image, each in raster order.

What enters ln_pre: the convolution's output cropped per image and concatenated. Assign a tensor of the same shape to replace it.

image_token_id

image_token_id(config: Any) -> int | None

The id of the token the processor puts where an image's features go: image_token_id (image_token_index on older configs).

variable_resolution

variable_resolution(vision: Vision) -> int

image_size on a tower with no fixed resolution: Unavailable, saying where each image's size is.

no_image_tokens

no_image_tokens(vision: Vision) -> str | None

Why vision.image_token_mask is unavailable, or None.

scatter_host

scatter_host(model: Any) -> tuple[str, Any] | None

The module whose forward scatters the image features, with its path from the root, or None.

The root's child the family keys ImageScatter on, or the root itself (path "") on a wrapper that scatters in its own forward, whose family names the operation in ROOT_SCATTER (Llama 4).

scatter_call

scatter_call(model: Any) -> tuple[str, int | str]

The scatter's path from the root ("model.source.inputs_embeds_masked_scatter_0") and which argument is the image features.

no_image_features

no_image_features(vision: Vision) -> str | None

Why vision.image_features is unavailable, or None.

image_features is read at the scatter, in the forward of the module the family keys ImageScatter on; a wrapper whose family keys none there binds the tower's names but does not serve the value.

image_scatter

image_scatter(vision: Vision) -> str

The path of the scatter's arguments, from the root: "/model.source.inputs_embeds_masked_scatter_0.inputs".

scattered_argument

scattered_argument(vision: Vision) -> int | str

Which argument of the scatter is the image features: ImageScatter.scatter_argument.

no_tower_run

no_tower_run(envoy: Any) -> str | None

Why the tower envoy belongs to never runs (Vision.no_images), or None: the gate on every tower value.

as_patches

as_patches(value: Tensor) -> Tensor

A tower's stream as Patches: a packed tower's [patches, vision_hidden_size] gets a leading images axis of 1 (a view).

as_native

as_native(current: Tensor, value: Tensor) -> Tensor

Patches back in the shape of the module's own tensor current: a packed tower's drops the leading 1.

tower_absent

tower_absent(name: str, where: str = 'block')

no_tower_run, then absent: why a tower value at the child name is unavailable.

no_concatenated_heads

no_concatenated_heads(envoy: Any) -> str | None

Why the Qwen ViT's attention_head_outputs is unavailable, or None: flash attention takes the other branch.