huggingface/pytorch-image-models · error · ValueError

Image size ({H}, {W}) must be divisible by (patch_size * poo

Error message

Image size ({H}, {W}) must be divisible by (patch_size * pooling_kernel_size) = ({cell_h}, {cell_w}) when global_pool='soft'. Resize to multiples of ({cell_h}, {cell_w}), or use global_pool='avg'/'none'.

What it means

Gemma4ViT's forward paths assert the raw image is conformant with soft pooling: H and W must each be divisible by patch_size * pooling_kernel_size (the pooling cell). _assert_raw_img_conformant runs in forward_features/forward/forward_intermediates and raises with the exact required cell dimensions when the raw image is off-grid.

Source

Thrown at timm/models/gemma4_vit.py:816

        reversible and checkpoint-safe).
        """
        for mod in self.modules():
            if isinstance(mod, Gemma4ClippableLinear):
                mod.use_clipped = enabled

    def _assert_raw_img_conformant(self, x: torch.Tensor) -> None:
        """When using the soft-token pooler, raw-image H/W must divide
        by ``patch_size * pooling_kernel_size`` so the pool cell grid is integral.
        Pre-patchified / NaFlex inputs are assumed to be conformant already.
        """
        if x.ndim != 4 or self.global_pool != 'soft':
            return
        H, W = x.shape[-2:]
        ph, pw = self.patch_size
        k = self.pooling_kernel_size
        cell_h, cell_w = ph * k, pw * k
        if H % cell_h != 0 or W % cell_w != 0:
            raise ValueError(
                f"Image size ({H}, {W}) must be divisible by "
                f"(patch_size * pooling_kernel_size) = ({cell_h}, {cell_w}) when global_pool='soft'. "
                f"Resize to multiples of ({cell_h}, {cell_w}), or use global_pool='avg'/'none'."
            )

    def _encode(
            self,
            x: torch.Tensor,
            position_ids: torch.Tensor,
            padding_positions: torch.Tensor,
            block_callback: Optional[Callable[[int, torch.Tensor], None]] = None,
            max_block_index: Optional[int] = None,
    ) -> torch.Tensor:
        """RoPE + transformer-block pipeline over already-embedded tokens."""
        B, N = x.shape[:2]
        rope_cos, rope_sin = self.rotary_emb(x, position_ids)

        attn_mask: Optional[torch.Tensor] = None

View on GitHub (pinned to 9a5261e31b)

Solutions

  1. Resize/pad inputs to multiples of the reported cell (cell_h, cell_w) shown in the message
  2. Use global_pool='avg' or 'none' when arbitrary resolutions must be supported
  3. Validate image dims in the preprocessing step and reject or pad non-conformant sizes

Example fix

# before
img = F.interpolate(img, size=(500, 500))
out = model(img)  # 500 % 48 != 0
# after
img = F.interpolate(img, size=(480, 480))  # multiple of 48
out = model(img)
Defensive patterns

Strategy: validation

Validate before calling

ph, pw = model.patch_size
cell = (ph * model.pooling_kernel_size, pw * model.pooling_kernel_size)
H, W = img.shape[-2:]
if H % cell[0] or W % cell[1]:
    img = F.interpolate(img, size=((H // cell[0] + 1) * cell[0], (W // cell[1] + 1) * cell[1]))
out = model(img)

Try / catch

try:
    out = model(img)
except ValueError as e:
    if 'must be divisible by' in str(e):
        out = model(F.interpolate(img, size=(384, 384), mode='bilinear'))
    else:
        raise

Prevention

When it happens

Trigger: Calling any forward method with global_pool='soft' active and a raw (B,C,H,W) image whose H or W is not a multiple of patch_size*k (e.g. 512 with cell 48).

Common situations: Deploying on arbitrary user-uploaded image sizes; fine-tuning with a resolution valid for the patch size but not the pooling grid; switching from avg-pooled to soft-pooled checkpoints without resizing the data pipeline.

Related errors


AI-assisted analysis of huggingface/pytorch-image-models@9a5261e31b (2026-08-27). Data as JSON: /api/errors/e7a2beebdceea71f. Report an issue: GitHub.