MoonViT-SO-400M β€” ONNX

An ONNX export of moonshotai/MoonViT-SO-400M, the native-resolution vision encoder from Kimi-VL (initialized from and continually pre-trained on SigLIP-SO-400M). Built with the standalone MoonVit.py script (build / eval / inference).

Parity vs the original PyTorch model: cosine = 1.000000 (see eval below).

What's in this folder

File Description
moonvit.onnx The exported graph. Legacy export = single file (~1.7 GB); dynamo export = graph + moonvit.onnx.data (external weights).
manifest.json Export metadata β€” grid, I/O shapes, normalization, which exporter was used.
README.md This file.

Important: fixed-grid export

MoonViT packs patches NaViT-style and its internals branch on grid_hws.tolist() (per-image Python loops for the interpolated position embedding, the 2D RoPE, and the 2Γ—2 patch merger), so the graph is data-dependent on the image grid. This export bakes one fixed grid as a constant. The default is 28Γ—28 patches = 392Γ—392 px; check manifest.json for the exact grid this file was built with. For other input resolutions, export another variant (--grid-h/--grid-w).

  • Input pixel_values: float32 [L, 3, 14, 14] β€” L = grid_h Γ— grid_w packed 14Γ—14 patches, raster order, normalized with mean/std = 0.5.
  • Output image_features: float32 [L/4, 4, 1152] β€” merged tokens after the 2Γ—2 patch merger (e.g. 28Γ—28 β†’ [196, 4, 1152]).

Usage (onnxruntime)

import json, numpy as np, onnxruntime as ort
from PIL import Image

d = "."                                    # this folder
man = json.load(open(f"{d}/manifest.json"))
gh, gw, P = man["grid_h"], man["grid_w"], man["patch_size"]

# preprocess an image to the export grid (raster-order packed patches, mean/std 0.5)
img = Image.open("figures/demo.png").convert("RGB").resize((gw * P, gh * P), Image.BICUBIC)
x = (np.asarray(img, np.float32) / 255.0 - 0.5) / 0.5           # [H,W,3]
x = x.transpose(2, 0, 1).reshape(3, gh, P, gw, P).transpose(1, 3, 0, 2, 4)
pixel_values = np.ascontiguousarray(x.reshape(gh * gw, 3, P, P))

sess = ort.InferenceSession(f"{d}/{man['file']}", providers=["CPUExecutionProvider"])
feats = sess.run(None, {"pixel_values": pixel_values})[0]       # [gh*gw/4, 4, 1152]
print(feats.shape)

Or via the script: uv run MoonVit.py inference --onnx-model-path <this_dir> --image img.png.

How it was built (MoonVit.py)

uv run MoonVit.py build     --output MoonVitOnnx                 # legacy exporter (default)
uv run MoonVit.py build     --output MoonVitOnnx --dynamo        # new torch.onnx dynamo exporter
uv run MoonVit.py eval      --onnx-model-path MoonVitOnnx        # original PyTorch vs ONNX
uv run MoonVit.py inference --onnx-model-path MoonVitOnnx --image img.png

Both exporters are supported and produce identical accuracy (cos 1.000000):

Exporter Graph Weights Notes
legacy (default) ~5,050 nodes inline (~1.7 GB single file) unrolls the data-dependent loops against the baked grid
dynamo (--dynamo) ~1,794 nodes external .onnx.data cleaner/smaller graph; requires onnxscript

Export notes (handled automatically by the script; both verified bit-faithful before export):

  1. Complex RoPE β†’ real cos/sin. ONNX has no complex dtype; the 2D-RoPE freqs_cis is precomputed for the baked grid and applied as real-valued rotation.
  2. PytorchGELUTanh shim β€” the upstream remote code imports it from transformers.activations (removed in newer transformers); it's exactly nn.GELU(approximate="tanh").
  3. Dynamo only: full-attention SDPA (single baked image β‡’ the block-diagonal mask is all-True) and concrete-int patch-merger / pos-emb interpolation, to avoid unbacked-symint ops.
  4. Legacy only: int32β†’int64 normalization of Slice/Gather index tensors.

Eval result

grid=28x28   original PyTorch vs ONNX
  sample 0: cos=1.000000  max|Ξ”|=9.995e-02
  sample 1: cos=1.000000  max|Ξ”|=6.195e-03
  sample 2: cos=1.000000  max|Ξ”|=2.052e-03
=== PASS (worst cosine 1.000000, tol 0.999) ===

(max|Ξ”| is on large-magnitude features, feature std β‰ˆ 4.2 β€” cosine 1.0 is the real signal.)

Original model reference (PyTorch)

from PIL import Image
from transformers import AutoModel, AutoImageProcessor

model_path = "moonshotai/MoonViT-SO-400M"
model = AutoModel.from_pretrained(model_path, torch_dtype="auto", device_map="auto",
                                  trust_remote_code=True)
processor = AutoImageProcessor.from_pretrained(model_path, trust_remote_code=True)

image = Image.open("./figures/demo.png")
proc = processor(image, return_tensors="pt").to(dtype=model.dtype, device=model.device)
image_features: list = model(proc.pixel_values, proc.image_grid_hws)
print(image_features[0].dtype, image_features[0].shape)   # e.g. bf16, [N, 4, 1152]

See the Kimi-VL Technical Report for training details.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for Prince-1/MoonViT-SO-400M

Quantized
(2)
this model

Paper for Prince-1/MoonViT-SO-400M