Learn: MoonViT

What you're learning

How MoonViT encodes arbitrary-resolution images in a single global transformer pass — and why that’s non-trivial. By the end you should be able to derive the key design choices from first principles.


1. Learning map

The path to understanding MoonViT bottom-up:

graph TD
    A["Standard ViT<br/>(fixed resolution)"] --> B["High-res problem<br/>in MLLMs"]
    B --> C1["Option A: Resize<br/>→ loses detail"]
    B --> C2["Option B: Slice-Based Encoding<br/>→ breaks cross-region context"]
    B --> C3["Option C: Global Native Encoding<br/>← MoonViT's bet"]
    C3 --> D["NaViT packing<br/>(variable-length batching)"]
    C3 --> E["Dual positional embeddings<br/>(absolute + 2D RoPE)"]
    D --> F["FlashAttention varlen kernel"]
    E --> G["Resolution generalization"]
    F --> H["MoonViT"]
    G --> H
    H --> I["MLP Projector<br/>(pixel shuffle → LLM)"]

    style H fill:#4a4,color:#fff
    style C3 fill:#44a,color:#fff

2. Why native resolution matters

Common framing mistake

It’s tempting to think high-res is about “more pixels = more information.” That’s not the core issue. The issue is spatial coherence: tasks like OCR, chart reading, and dense visual QA require the model to reason about relationships across different parts of the image simultaneously. Slicing destroys that.

Standard ViTs (SigLIP, CLIP, etc.) are trained at fixed resolution — typically 224×224 or 384×384.1 Two problems arise when you scale to real images in an MLLM:

Problem 1: Resize loses structure.
A 1920×1080 → 384×384 resize throws away the pixel content. Small text, fine-grained charts, and dense diagrams become unreadable.

Problem 2: Slice-Based Encoding (SBE) breaks global context.
SBE (used by LLaVA, InternVL, etc.) tiles the image into fixed crops, encodes each independently, concatenates tokens.2 A word that spans two tiles, or a caption spatially separated from its figure, gets cut apart — the encoder never sees their relationship.


3. The solution: sequence packing (NaViT)

The core enabling idea: concatenate, don’t pad.3 MoonViT adopts this NaViT packing scheme directly.4

Standard batching (broken for variable resolution)

To batch images of different sizes in a standard ViT, you pad shorter sequences to the longest:

Image A (196 tokens): [t1 t2 ... t196] [PAD PAD ... PAD]  ← wasted compute
Image B (9800 tokens): [t1 t2 ... t9800]

All padding tokens run through the full transformer and contribute nothing. At high resolution the waste is severe.

Instead, concatenate all images into one flat sequence and track boundaries:

Packed: [img_A_t1 ... img_A_t196 | img_B_t1 ... img_B_t9800]
cu_seqlens: [0, 196, 9996]

cu_seqlens is a cumulative-sum tensor of sequence lengths. FlashAttention’s varlen kernel uses it to enforce block-diagonal attention5 — tokens from image A attend only to image A, tokens from image B attend only to image B:

No explicit masking matrix is materialized. FlashAttention handles this in the CUDA kernel itself — efficient at scale.


4. Positional embeddings: why you need both

This is the most subtle design choice in MoonViT. Neither approach alone is sufficient.

Absolute positional embeddings (from SigLIP init)

SigLIP was trained with learned absolute positional embeddings — a lookup table of shape where is the patch size.1 At inference on a different resolution, these embeddings are bicubically interpolated to the new grid4:

This preserves the pretrained SigLIP signal — crucial for a smooth initialization. But it degrades for large resolution changes because bicubic interpolation assumes the learned embeddings vary smoothly, which they don’t always do.

2D Rotary Position Embeddings (RoPE)

RoPE encodes position in the attention computation itself, not as an additive token feature.6 For 1D RoPE:

where is a block-diagonal rotation matrix. The key property: the dot product depends only on the relative position , not absolute positions. This generalizes to unseen sequence lengths.

For 2D (height × width), MoonViT factorizes: half the head dimensions carry height-RoPE, the other half carry width-RoPE4:

Why not just RoPE alone?

Starting from SigLIP with only RoPE would require extensive retraining to unlearn the absolute-embedding signal baked into the weights. Keeping both lets you use SigLIP as-is, then layer RoPE on top to handle resolution generalization. The Kimi-VL paper shows both together outperform either alone.4


5. Architecture walk-through

graph LR
    subgraph Input
        A["Image H×W×3"]
    end
    subgraph Encoder
        B["PatchEmbed\nConv2d(3→1152, k=14, s=14)\n→ (H/14·W/14) tokens"]
        C["AbsPosEmbed\nbicubic interp to grid"]
        D["×27 EncoderLayers\nLayerNorm → Attn+2DRoPE → MLP"]
        E["LayerNorm\nout: (N_tok, B, 1152)"]
    end
    subgraph Projector
        F["Pixel Shuffle 2×2\n4× token reduction\n1152 → 4608"]
        G["Linear → GELU → Linear\n4608 → d_llm"]
    end
    A --> B --> C --> D --> E --> F --> G

Key numbers:

  • Patch size: 14px → each patch is a 14×14 grid cell7
  • A 1920×1080 image: tokens before pixel shuffle, after
  • Hidden dim: 1152
  • 27 transformer layers
  • MLP: a standard transformer FFN. The SigLIP-SO-400M base uses a GELU-tanh activation (gelu_pytorch_tanh), and MoonViT’s public config does not override it8

6. Training

timeline
    title MoonViT Training Phases
    Phase 1 (2T tokens) : CoCa-style
                        : SigLIP contrastive loss
                        : Caption generation loss
                        : MoonViT + text decoder updated
                        : LLM frozen
    Phase 2 (0.1T tokens) : Alignment
                          : MoonViT + MLP projector updated
                          : LLM frozen
                          : Shift toward instruction-following

Why freeze the LLM in both phases?

MoonViT is being adapted to produce token sequences the LLM can interpret. If the LLM is also updated, it can co-adapt to bad encoder representations rather than forcing the encoder to produce good ones. Freezing the LLM creates pressure on MoonViT to produce high-quality features. (Phase 1 is a CoCa-style stage over ~2T tokens combining a SigLIP contrastive loss with a caption-generation loss; Phase 2 is a ~0.1T-token alignment stage updating only MoonViT and the MLP projector.)49


7. GNE vs SBE: the design space

quadrantChart
    title Vision Encoder Design Space
    x-axis Low Resolution Support --> High Resolution Support
    y-axis Broken Cross-Region Context --> Preserved Cross-Region Context
    quadrant-1 "Best of both (unsolved)"
    quadrant-2 "GNE: MoonViT, ViT-UHD"
    quadrant-3 "Fixed-res ViT (SigLIP base)"
    quadrant-4 "SBE: LLaVA-UHD, InternVL-2"
    MoonViT: [0.75, 0.85]
    ViT-UHD: [0.80, 0.82]
    SigLIP: [0.25, 0.90]
    LLaVA-UHD: [0.70, 0.35]
    InternVL-2: [0.65, 0.40]
SBEGNE (MoonViT)
Cross-region context✗ broken at boundaries✓ preserved
Token countO(N_crops × T_per_crop)O(H×W/p²) → same, but no redundant CLSs
Attention costO(T²) per crop, independentO(N_total²) — quadratic in full image
Very high-res (>2K)Tractable by designExpensive; needs token compression
Global reasoning tasksWeakerStronger

8. Reading order

Work through these in order:

  1. Start here (architecture): MoonViT-SO-400M on HuggingFace — read the model card and the example code; run the shape walkthrough in your head
  2. Code dive: kyegomez/open-moonvit — single-file PyTorch impl; read moonvit.py focusing on MoonViTPatchEmbed, RotaryEmbedding2D, and the cu_seqlens handling
  3. Primary source: Kimi-VL Technical Report (arXiv:2504.07491) — Section on MoonViT; focus on the ablation comparing absolute-only vs RoPE-only vs combined positional embeddings
  4. Background (if rusty): NaViT paper (arXiv:2307.06304) — the packing strategy; Section 3 is the key part
  5. Next frontier: Search “ViT-UHD token compression” — see how the field moved past pixel shuffle

9. What’s next

Three threads to pull on next, in suggested order:

  1. 2d-rope (not yet written) — 2D RoPE is used beyond MoonViT; understanding the math fully unlocks a lot of modern vision encoder designs
  2. siglip (not yet written) — MoonViT is a continued pre-training of SigLIP; understanding SigLIP’s contrastive objective clarifies what MoonViT inherits
  3. token-compression (not yet written) — pixel shuffle is just the start; progressive visual compression (PVC), ToMe, and Q-Former are the frontier for making GNE cost-efficient at 2K+ resolution

Reference page: moonvit | Filed: 2026-09-02


References

Footnotes

  1. Zhai et al., “Sigmoid Loss for Language Image Pre-Training (SigLIP),” arXiv:2303.15343 (2023). https://arxiv.org/abs/2303.15343. The SO400M (“shape-optimized,” 400M) variant (27 layers, hidden 1152, patch 14, trained at 224/384) is google/siglip-so400m-patch14-384 (https://huggingface.co/google/siglip-so400m-patch14-384); shape optimization from Alabdulmohsin et al., “Getting ViT in Shape,” arXiv:2305.13035. SigLIP uses learned fixed-size absolute positional embeddings. 2

  2. Slice-based / sub-image encoding as used by LLaVA-OneVision (Li et al., “LLaVA-OneVision,” arXiv:2408.03326, https://arxiv.org/abs/2408.03326) and InternVL (Chen et al., “InternVL,” arXiv:2312.14238, https://arxiv.org/abs/2312.14238); the Kimi-VL report contrasts MoonViT’s native-resolution encoding against this sub-image splitting/splicing approach.

  3. Dehghani et al., “Patch n’ Pack: NaViT, a Vision Transformer for any Aspect Ratio and Resolution,” arXiv:2307.06304 (2023). https://arxiv.org/abs/2307.06304 — example/sequence packing that concatenates variable-resolution images into one sequence.

  4. Kimi Team, “Kimi-VL Technical Report,” arXiv:2504.07491 (2025). https://arxiv.org/abs/2504.07491 — MoonViT is the 400M native-resolution vision encoder initialized from and continually pre-trained on SigLIP-SO-400M; it adopts NaViT sequence packing, combines interpolated absolute positional embeddings with 2D RoPE (over height and width), feeds an MLP projector with a pixel-shuffle operation, and is trained in a CoCa-style stage (~2T tokens) followed by a ~0.1T-token alignment stage. [recent] 2 3 4 5

  5. Dao et al., “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness,” arXiv:2205.14135 (2022). https://arxiv.org/abs/2205.14135 — the varlen kernel (using cu_seqlens to realize block-diagonal attention without materializing a mask) is provided by the flash-attention library: https://github.com/Dao-AILab/flash-attention.

  6. Su et al., “RoFormer: Enhanced Transformer with Rotary Position Embedding,” arXiv:2104.09864 (2021). https://arxiv.org/abs/2104.09864 — rotary position embeddings make the attention dot product depend on relative position; MoonViT uses a 2D factorization across height and width.

  7. MoonViT-SO-400M model card and config.json, Moonshot AI. https://huggingface.co/moonshotai/MoonViT-SO-400Mnum_hidden_layers 27, hidden_size 1152, patch_size 14, intermediate_size 4304, num_attention_heads 16; ~0.4B params, initialized from SigLIP-SO-400M.

  8. Corrected from an earlier draft that claimed SwiGLU. The MoonViT-SO-400M config.json (https://huggingface.co/moonshotai/MoonViT-SO-400M) does not expose an activation field, and MoonViT’s SigLIP-SO-400M base uses gelu_pytorch_tanh — so a GELU-family activation is the supported reading. Confirm against the Kimi-VL report / reference implementation if the exact FFN activation matters.

  9. Yu et al., “CoCa: Contrastive Captioners are Image-Text Foundation Models,” arXiv:2205.01917 (2022). https://arxiv.org/abs/2205.01917 — the combined contrastive + captioning training objective that MoonViT’s first training stage follows.