Janus-Pro: Decoupled Encoders for Unified Multimodal Models
A single visual encoder can't be good at both halves of a unified model. Understanding wants semantic features, the concept-rich output vectors of SigLIP or DINOv2. Generation wants reconstruction-friendly codes, VQ tokens that compose back into crisp pixels. Janus (DeepSeek, October 2024) and Janus-Pro (DeepSeek, January 2025) stop trying to reconcile them: share the transformer body, but route understanding through SigLIP and generation through a VQ tokenizer. At 7B, Janus-Pro beats DALL-E 3 on GenEval while matching LLaVA on MMMU. This lesson covers why two encoders work where one struggles.
Learning Objectives
- Explain why a single shared encoder compromises either understanding or generation quality.
- Describe Janus-Pro's routing: SigLIP features on the input side for understanding, VQ tokens on both input and output for generation.
- Trace the data-mix scaling that makes Janus-Pro succeed where Janus did not.
- Compare decoupled (Janus-Pro), coupled-continuous (Transfusion), and coupled-discrete (Show-o) architectures.
The Problem
Unified models share one transformer body across understanding and generation. Earlier attempts (Chameleon, Show-o, Transfusion) also share one visual tokenizer across both directions, and that tokenizer is a compromise:
- Optimized for reconstruction (generation): VQ-VAE captures fine-grained pixel detail but produces tokens with weak semantic coherence.
- Optimized for semantics (understanding): SigLIP embeddings group "cat" images near "cat" tokens but do not permit good reconstruction.
Show-o and Transfusion pay for this with a visible quality tax on one direction. Janus-Pro asks why the two tasks should share a tokenizer at all when their needs differ.
The Concept
Decoupled visual encoding
Janus-Pro separates the two encoders:
- Understanding path. Input image → SigLIP-SO400m → 2-layer MLP → transformer body.
- Generation path. Input image (if conditioning on an existing image) → VQ tokenizer → token IDs → transformer body.
- Output generation. Image tokens predicted by the transformer → VQ decoder → pixels.
The transformer body is shared. Everything upstream and downstream of it is task-specific.
Inputs are disambiguated by prompt format: a <understand> tag routes through SigLIP and <generate> routes through VQ. Alternatively the routing is implicit from the task.
Why this works
The understanding loss gets SigLIP features, which CLIP-style pretraining has tuned for semantic similarity. Perception benchmarks improve over Show-o and Transfusion because the input features suit the task.
The generation loss gets VQ tokens, which the tokenizer has tuned for reconstruction. Image quality improves over Show-o because VQ codes compose back to pixels cleanly.
The shared body sees two input distributions (SigLIP and VQ) and has to learn both. The claim is that with enough data and enough parameters, the body absorbs the switching.
Data scaling: Janus vs Janus-Pro
Janus (the original, arXiv 2410.13848) introduced the decoupling at small scale: 1.3B params and limited data. Janus-Pro (arXiv 2501.17811) scaled it up:
- 7B params (vs 1.3B).
- 90M image-text pairs for stage 1 (alignment), up from 72M.
- 72M for stage 2 (unified), up from 26M.
- 200k image-gen instruction samples added for stage 3.
The result is that Janus-Pro-7B matches LLaVA on MMMU (60.3 vs ~58) and beats DALL-E 3 on GenEval (0.80 vs 0.67). One open model, competitive on both sides of the unified spectrum.
JanusFlow, the rectified flow variant
JanusFlow (arXiv 2411.07975) swaps the VQ generation path for a continuous rectified-flow path. The split becomes SigLIP for understanding and rectified flow for generation, and the quality ceiling lifts further. The architecture is still decoupled encoders around a shared body.
The shared body's job
The transformer body processes a unified sequence drawn from two input distributions. Its job is:
- For understanding: consume SigLIP features and text tokens, then emit text autoregressively.
- For generation: consume text tokens and (optionally) image VQ tokens, then emit image VQ tokens autoregressively.
The body has no modality-specific weights per block. It is the text-style transformer you'd expect inside Qwen or Llama, plus the two input adapters.
That means the body can start from a pretrained LLM, and Janus-Pro does exactly that, initializing from DeepSeek-MoE-7B. The choice matters because the LLM contributes reasoning ability that pure-from-scratch unified models struggle to reach.
Compared to InternVL-U
InternVL-U (Lesson 12.10) is the 2026 follow-up. It combines:
- Native multimodal pretraining (InternVL3 backbone).
- Decoupled-encoder routing (SigLIP in, VQ + diffusion heads out).
- Unified understanding, generation, and editing.
InternVL-U folds Janus-Pro's architectural choice into a larger framework. At scale, decoupled encoders are now the default for unified models.
Limitations
Decoupled encoders add complexity: two tokenizers to train, two input paths to maintain, two sets of failure modes. If your product doesn't need generation, Janus-Pro is over-engineered, so pick a LLaVA-family understanding model.
If your product doesn't need understanding, Janus-Pro is overqualified, so pick a Stable Diffusion 3 or Flux model.
If your product needs both, Janus-Pro is the reference open architecture.
Use It
main.py simulates Janus-Pro routing with stdlib Python:
- Two mock encoders: a SigLIP-like one (32-dim semantic vectors) and a VQ-like one (integer codes).
- A keyword router that picks the path from the prompt, and labels ties as ambiguous.
- A shared body stand-in that handles token sequences whichever encoder produced them.
- A table of the Janus to Janus-Pro scaling numbers across the three training stages.
It prints the routed path for five prompts, covering image QA, text-to-image, and a mixed prompt.
Ship It
This lesson produces outputs/skill-decoupled-encoder-picker.md. Given a product that wants unified generation and understanding at near-frontier quality, the skill picks Janus-Pro, JanusFlow, or InternVL-U and attaches a concrete data-scale recommendation.
I'd run main.py and read the five routing traces before anything else. The router is a keyword count, nothing learned. "Describe what's in this image" scores two understanding keywords and goes down the SigLIP path, while "Render a cyberpunk cityscape at night" goes down the VQ path. "Sketch a cat and then describe its breed" ties one to one, and the script labels it ambiguous and runs both encoders.
My bet is that the shared body is the part worth staring at. In the code, SharedBody.process ignores its input stream and branches only on whether the output is text or image. That's a stub, so don't read it as evidence that the real body absorbs two input distributions. It only shows where the seam sits: two encoders in, one body, two output heads. Whether the body copes is an empirical claim about data and scale, which is why the script prints a scaling table.
I'd also keep the toy in proportion. The encoders are seeded random-number stubs, and the outputs are placeholders. The image_seed comes from hash(p) on a string, so the printed tokens will differ between runs. Don't compare them across runs; compare the routes. For the real thing I'd start from the released Janus-Pro weights instead of rebuilding the encoders.
Exercises
-
Janus-Pro-7B beats DALL-E 3 on GenEval. Explain why a 7B open model can match a frontier proprietary model on generation but not on understanding.
-
Implement a router function: given prompt text, classify as
understandorgenerate. How do you handle ambiguous prompts like "describe and then sketch"? -
JanusFlow replaces the VQ path with rectified flow. What does the transformer body now output, and what changes in the loss?
-
Propose a fourth task the Janus-Pro architecture could handle with one more decoupled encoder. Examples: image segmentation (DINO-style), depth (MiDaS-style).
-
Read Janus-Pro Section 4.2 on data scaling. Which data stage contributes most to the T2I quality gain vs Janus?
Key Terms
| Term | What people say | What it actually means |
|---|---|---|
| Decoupled encoding | "Two visual encoders" | Separate tokenizer or encoder per direction: semantic for understanding, reconstruction for generation |
| Shared body | "One transformer" | Single transformer processes either encoder's output; no modality-specific weights |
| SigLIP for understanding | "Semantic features" | CLIP-family vision tower providing rich conceptual features but poor reconstruction |
| VQ for generation | "Reconstruction codes" | Vector-quantized tokens that decode cleanly back to pixels |
| JanusFlow | "Rectified-flow variant" | Janus-Pro with a continuous flow-matching generation head instead of VQ |
| Routing tag | "Task tag" | Prompt marker (<understand> / <generate>) that picks the input encoder |
Code
- main.py (python, 147 lines)