Skip to main content
MiniVLLM ships two model families in myvllm/models/: Qwen3 (qwen3.py) and Llama 3.2 (llama.py). Both follow the same decoder-only transformer pattern and are fully tensor-parallel.

Architecture comparison


Qwen3 architecture

Class hierarchy

Qwen3ForCausalLM constructor

Key architectural choices

QK normalization. Qwen3 applies LayerNorm (RMSNorm) to the query and key tensors after the QKV projection but before the rotary embedding. This prevents large values from destabilizing the softmax inside attention. Value tensors are not normalized because they do not participate in the attention score computation.
Grouped-query attention (GQA). num_kv_heads < num_heads is fully supported. Each GPU holds num_heads // tp_size query heads and num_kv_heads // tp_size KV heads. MergedColumnParallelLinear for gate + up. The MLP gate and up projections are merged into a single weight tensor. This is required because model checkpoints store gate_proj.weight and up_proj.weight as separate tensors with size (intermediate_size, hidden_size). A regular ColumnParallelLinear over intermediate_size * 2 would not know where the boundary is when loading. The merged layer’s weight_loader accepts a loaded_weight_id argument (0 or 1) that specifies which sub-matrix is being loaded.
Residual connections. Each Qwen3DecoderLayer maintains a running residual that is fused into the LayerNorm calls (see Neural Network Layers — LayerNorm):

Llama 3.2 architecture

The Llama 3.2 implementation mirrors Qwen3 almost exactly. The two structural differences are:
  1. No QK normalization. LlamaAttn does not have q_norm or k_norm.
  2. NTK-scaled RoPE. The RotaryEmbedding is constructed with is_llama3=True and a much larger base (500000) plus scaling factors that adapt low-frequency dimensions for sequences beyond the training length.
Because the field names in the checkpoint are identical to the names used in the Qwen3 loader, no changes to loader.py are needed.

packed_module_mapping

Checkpoint weight names do not always match the attribute names used in the model. packed_module_mapping is a class-level dict that bridges this gap.
The loading utility in myvllm/utils/loader.py inspects this mapping to know:
  • Which checkpoint keys correspond to merged parameters (e.g. gate_up_proj maps to sub-index '0' of the merged gate_up tensor).
  • Which weight_loader ID argument to pass when calling the loader (e.g. 'q', 'k', 'v' for the QKV projection).

Adding a new model

1

Implement the model class

Create myvllm/models/mymodel.py. The class must:
  • Be a subclass of nn.Module.
  • Expose forward(input_ids) returning hidden states.
  • Expose compute_logits(hidden_states) returning logits.
  • Define packed_module_mapping as a class attribute.
  • Use the parallel layer classes from myvllm/layers/ for all weight tensors.
2

Register the model in ModelRunner

Open myvllm/engine/model_runner.py and add a case to the match block inside ModelRunner.__init__:
3

Provide a config dict

Create a config dict with the model-specific keys expected by your constructor and pass it to LLMEngine:
Study the Llama 3.2 implementation (llama.py) as a template — it was added as an exercise on top of the existing Qwen3 code and demonstrates the minimal set of changes required.