Skip to content

Qwen3-VL-MoE

Weights: pretrained Keras weights live on Hugging Face under kerasformers/<variant> (each repo carries kf_config.json and kf_preprocessor.json plus a sharded model.weights.json + shards and a tokenizer.json). Load the model and processor with from_weights("kerasformers/<variant>").

The Mixture-of-Experts variant of Qwen3-VL, ported to pure Keras 3. It is exactly Qwen3-VL, the same DeepStack vision tower, interleaved M-RoPE, and per-head QK-norm GQA attention, except each text decoder block's MLP is a sparse Mixture-of-Experts block: a float32-softmax top-k router over fused SwiGLU experts, with no shared expert (the Qwen3-MoE recipe). Layers off the decoder_sparse_step cadence (or in mlp_only_layers) stay dense.

Memory is governed by total parameters, not active ones.

Links:

See also qwen3_vl.md (the dense Qwen3-VL), qwen3_moe.md (the text-only Qwen3-MoE recipe reused here), qwen3_5_moe.md (the Qwen3.5 MoE VLM, a different hybrid backbone).

Variants

Preconverted, bf16 weights are hosted under kerasformers/. Load the model and processor with from_weights("kerasformers/<variant>"); the -instruct sizes are the instruction-tuned checkpoints and -thinking the reasoning checkpoints. Qwen3-VL-MoE is Apache 2.0.

Variant Hub
qwen3-vl-30b-a3b-instruct kerasformers/qwen3-vl-30b-a3b-instruct
qwen3-vl-30b-a3b-thinking kerasformers/qwen3-vl-30b-a3b-thinking
qwen3-vl-235b-a22b-instruct kerasformers/qwen3-vl-235b-a22b-instruct
qwen3-vl-235b-a22b-thinking kerasformers/qwen3-vl-235b-a22b-thinking

Upstream Qwen safetensors also load directly via the hf: prefix, e.g. from_weights("hf:Qwen/Qwen3-VL-30B-A3B-Instruct"), which converts them in process (pass cache_converted=True to keep the result). See Loading Weights.

API

Qwen3VLMoeModel

The multimodal backbone (vision tower + DeepStack + Qwen3-VL MoE decoder), no LM head. Returns {"last_hidden_state": (batch, seq, embed_dim)}.

Arg Default Meaning
vocab_size 151936 token vocabulary size
embed_dim 2048 text model width
mlp_dim 5632 dense-MLP width (non-MoE layers)
num_layers 24 decoder blocks
num_heads 16 query heads
num_kv_heads 16 key/value heads (GQA)
head_dim 128 per-head width
mrope_section (24, 20, 20) interleaved M-RoPE split (time/height/width)
num_experts 60 routed experts
num_experts_per_tok 4 experts routed per token
moe_mlp_dim 1408 per-expert inner width
norm_topk_prob True renormalize the top-k router weights
decoder_sparse_step 1 MoE every Nth layer
mlp_only_layers () layer indices forced dense
vision_depth 27 vision blocks
vision_out_dim embed_dim merger output width
deepstack_visual_indexes (8, 16, 24) vision blocks feeding DeepStack
patch_size 16 vision patch size
image_token_id 151655 placeholder token expanded per image
video_token_id 151656 placeholder token expanded per video

Qwen3VLMoeGenerate

Qwen3VLMoeModel plus a (tied) LM head and fast .generate() (image+text -> text). Returns {"logits": (batch, seq, vocab_size)}. Same fast multimodal generation as Qwen3-VL: vision encoder + M-RoPE prefill into a fixed KV cache (DeepStack threaded through prefill), then text-only decode. The MoE MLP does not change the cache structure.

Qwen3VLMoeProcessor

Image/video + text processor (ChatML + image-pad expansion), a 16px-patch Qwen-VL processor with the Qwen3-VL-MoE tokenizer and video processor.

End-to-end example

import os

os.environ["KERAS_BACKEND"] = "torch"  # or "jax" / "tensorflow"

from PIL import Image
from kerasformers.models.qwen3_vl_moe import Qwen3VLMoeGenerate, Qwen3VLMoeProcessor

model = Qwen3VLMoeGenerate.from_weights("kerasformers/qwen3-vl-30b-a3b-instruct")
processor = Qwen3VLMoeProcessor.from_weights("kerasformers/qwen3-vl-30b-a3b-instruct")

inputs = processor(
    conversation=[
        {
            "role": "user",
            "content": [
                {"type": "image", "image": Image.open("photo.jpg")},
                {"type": "text", "text": "Describe this image in one sentence."},
            ],
        }
    ]
)
outputs = model.generate(**inputs, max_new_tokens=64)

print(processor.decode(outputs[0]))

Lower memory

Larger checkpoints load in bf16 or weight-only quantized. See quantization.md:

model = Qwen3VLMoeGenerate.from_weights(
    "kerasformers/qwen3-vl-30b-a3b-instruct", quantization="int8", load_dtype="bfloat16"
)