vLLM Explained

"model_executor/models/llama.py: the Llama model"

AdvancedCode mapabout 9 min read + 3 min deep dives

Code version: vLLM @ 3e44c0d · 2026-10-22

Purpose#

vllm/model_executor/models/llama.py implements the Llama architecture with vLLM layers, so that the worker can build it, load a Hugging Face checkpoint into it, and run it in the forward pass of each step.

Filevllm/model_executor/models/llama.py, 585 lines of Python. The header says it is adapted from the Transformers Llama code, v4.28.0 (vllm/model_executor/models/llama.py:4-5).
Main classLlamaForCausalLM (vllm/model_executor/models/llama.py:464-561).
Runs inEach worker (one per GPU), or the engine core process with the uni executor. Built once in load_model; forward runs on the GPU in every step, usually inside a compiled graph or a CUDA graph.
Called byThe model loader through initialize_model() after the registry resolves the name (vllm/model_executor/model_loader/utils.py:96-103); Model Runner V2 for forward and compute_logits (vllm/v1/worker/gpu/model_runner.py:2070, vllm/v1/worker/gpu/model_runner.py:1638); 21 other files that import it.
CallsThe TP layers of linear.py and vocab_parallel_embedding.py, Attention, RMSNorm, get_rope, SiluAndMul, LogitsProcessor, and the helpers make_layers, AutoWeightsLoader and WeightsMapper (vllm/model_executor/models/llama.py:35-79).
Not in this fileHow the layers split and communicate (The tensor-parallel layers (linear.py and vocab_parallel_embedding.py)), the attention kernels (Attention backends: backend, metadata builder and implementation), compilation (torch.compile and CUDA graphs in vLLM), the registry lookup (model_executor/models/registry.py: the model registry).

The page How a vLLM model file is written (llama.py) explains the patterns of this file. This page reads the file class by class and follows the load_weights path.

Where it sits#

Figure: the objects that this file builds, with their prefixes and per-rank weight shapes (Llama-3-8B, TP = 2).
Open full size ↗

Who calls it#

What it calls#

Key classes and functions#

NameLinesRole
LlamaMLPvllm/model_executor/models/llama.py:82-122gate_up (column), SiLU × up, down (row).
LlamaAttentionvllm/model_executor/models/llama.py:125-248Per-rank heads, QKV, RoPE, Attention, o_proj.
LlamaDecoderLayervllm/model_executor/models/llama.py:251-334Reads config knobs; norm, attention, norm, MLP with a separate residual.
LlamaModelvllm/model_executor/models/llama.py:337-461Compiled inner model: embedding, layers of this PP stage, final norm, the mapper and load_weights.
LlamaForCausalLMvllm/model_executor/models/llama.py:464-561Outer class: mixins, LoRA attributes, LM head, compute_logits, load_weights.
bidirectional basesvllm/model_executor/models/llama.py:564-571as_seq_cls_model and as_embedding_model of LlamaForCausalLM.
LlamaBidirectionalForSequenceClassification, LlamaBidirectionalModelvllm/model_executor/models/llama.py:574-585Empty subclasses; the config sets the attention and pooling types.

Walk through the main methods#

LlamaAttention.__init__: sliding windows and the attention class#

The head arithmetic and the two linear layers are on How a vLLM model file is written (llama.py) (lines 143-181). The second half of the constructor:

        self._init_rotary_emb(config, quant_config=quant_config)

        sliding_window = None
        if layer_types := getattr(config, "layer_types", None):
            # Fix for Eagle3 compatibility:
            # for draft models, subtract target layer count
            # to get draft-relative layer index starting from 0
            if hasattr(config, "target_layer_count"):
                # This is a draft model,
                # adjust layer_idx to be relative to draft layers
                effective_layer_idx = layer_idx - config.target_layer_count
            else:
                # This is a target model, use layer_idx directly
                effective_layer_idx = layer_idx
            assert effective_layer_idx < len(layer_types), (
                f"effective_layer_idx: {effective_layer_idx} "
                f"is out of bounds for layer_types: {layer_types}"
            )

            is_sliding = layer_types[effective_layer_idx] == "sliding_attention"
            if is_sliding:
                sliding_window = config.sliding_window

        attn_cls = (
            EncoderOnlyAttention
            if attn_type == AttentionType.ENCODER_ONLY
            else Attention
        )

        self.attn = attn_cls(
            self.num_heads,
            self.head_dim,
            self.scaling,
            num_kv_heads=self.num_kv_heads,
            cache_config=cache_config,
            quant_config=quant_config,
            per_layer_sliding_window=sliding_window,
            attn_type=attn_type,
            prefix=f"{prefix}.attn",
        )
  1. Line 183. _init_rotary_emb calls get_rope with the head size, max_position_embeddings and the config's rope_parameters, NeoX style (lines 236-248).
  2. Lines 186-204. If the config has layer_types, a layer marked sliding_attention gets config.sliding_window. The layer index comes from the prefix (line 141). For an EAGLE-3 draft, the index is shifted by target_layer_count.
  3. Lines 206-210. ENCODER_ONLY selects EncoderOnlyAttention; everything else uses Attention.
  4. Lines 212-222. The attention layer gets per-rank heads (self.num_heads, self.num_kv_heads), the scale head_dim ** -0.5 (line 162) and its prefix.

LlamaDecoderLayer: config knobs and the residual#

        config = config or vllm_config.model_config.hf_config
        cache_config = vllm_config.cache_config
        quant_config = self.get_quant_config(vllm_config)

        self.hidden_size = config.hidden_size
        max_position_embeddings = getattr(config, "max_position_embeddings", 8192)
        # Support abacusai/Smaug-72B-v0.1 with attention_bias
        attention_bias = getattr(config, "attention_bias", False) or getattr(
            config, "bias", False
        )
        bias_o_proj = attention_bias
        # support internlm/internlm3-8b with qkv_bias
        if hasattr(config, "qkv_bias"):
            attention_bias = config.qkv_bias

        # By default, Llama uses causal attention as it is a decoder-only model.
        # You can override the HF config with `is_causal=False` to enable
        # bidirectional attention, which is used in some embedding models
        # (e.g. nvidia/llama-nemotron-embed-1b-v2)
        if getattr(config, "is_causal", True):
            attn_type = AttentionType.DECODER
        else:
            attn_type = AttentionType.ENCODER_ONLY
  1. Line 263. The quant config comes from get_quant_config(), which subclasses can override (lines 332-334).
  2. Lines 268-274. attention_bias or bias turns on biases in QKV and o_proj; qkv_bias sets the QKV bias alone. The comments name the models that need this.
  3. Lines 280-283. is_causal=False in the config makes the layer bidirectional, for embedding models.
    def forward(
        self,
        positions: torch.Tensor,
        hidden_states: torch.Tensor,
        residual: torch.Tensor | None,
    ) -> tuple[torch.Tensor, torch.Tensor]:
        # Self Attention
        if residual is None:
            residual = hidden_states
            hidden_states = self.input_layernorm(hidden_states)
        else:
            hidden_states, residual = self.input_layernorm(hidden_states, residual)
        hidden_states = self.self_attn(positions=positions, hidden_states=hidden_states)

        # Fully Connected
        hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
        hidden_states = self.mlp(hidden_states)
        return hidden_states, residual
  1. Lines 320-322. The first layer has no residual yet: it keeps the input as the residual and normalizes.
  2. Line 324. Later layers call the norm with the residual. RMSNorm adds it and normalizes in one op, and returns the new residual too.
  3. Line 328. The same fused add-and-norm before the MLP. The layer returns (hidden_states, residual); the final norm of LlamaModel adds the last residual (line 452).

LlamaModel.load_weights and LlamaForCausalLM.load_weights: the load path#

    def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
        loader = AutoWeightsLoader(self)
        return loader.load_weights(weights, mapper=self.hf_to_vllm_mapper)
    def load_weights(self, weights: Iterable[tuple[str, torch.Tensor]]) -> set[str]:
        loader = AutoWeightsLoader(self)
        return loader.load_weights(weights)

Follow one tensor, model.layers.0.mlp.up_proj.weight, in a TP = 2 worker:

  1. The default loader calls LlamaForCausalLM.load_weights(iterator) (vllm/model_executor/model_loader/default_loader.py:503).
  2. The outer AutoWeightsLoader has no model mapper. It adds the quant config mappers, if any, and the rule that drops old rotary tables (vllm/model_executor/models/utils.py:460-473).
  3. The walk groups by model and finds self.model, a LlamaModel with its own load_weights. It passes the rest of the name: layers.0.mlp.up_proj.weight (vllm/model_executor/models/utils.py:387-400).
  4. LlamaModel.load_weights makes a second AutoWeightsLoader with hf_to_vllm_mapper. .up_proj becomes .gate_up_proj with shard id 1 (vllm/model_executor/models/llama.py:354-355).
  5. The walk goes layers → 0 → mlp → gate_up_proj. MergedColumnParallelLinear has a load_weights; it reads shard_id = 1 from the tensor and calls the parameter's loader (vllm/model_executor/layers/linear.py:985-1000).
  6. The v2 loader computes offset 14336 / 2 = 7168 and size 7168 in the parameter. It copies checkpoint rows rank × 7168 to rank × 7168 + 7167 (vllm/model_executor/layers/linear.py:965-983, vllm/model_executor/parameter.py:153-173).
  7. The loaded name goes back up the tree with its prefixes: model.layers.0.mlp.gate_up_proj.weight. The default loader uses the set of these names for its completeness check (vllm/model_executor/model_loader/default_loader.py:510-523).

The tensor lm_head.weight takes another path. In the outer walk, lm_head is a ParallelLMHead without load_weights, so its weight parameter uses VocabParallelEmbedding.weight_loader (vllm/model_executor/models/utils.py:416-424). If tie_word_embeddings is set, lm_head.weight is the same object as model.embed_tokens.weight (lines 509-510), and the loader skips the second name (vllm/model_executor/models/utils.py:255-259). One exception: if the config says tied but the checkpoint has its own lm_head, VllmConfig turns the flag off, so the head loads (vllm/config/vllm.py:1594-1599). After loading, vLLM ties the two again if they are equal (vllm/config/model.py:1255-1281, vllm/model_executor/model_loader/utils.py:155).

LlamaForCausalLM: class attributes#

class LlamaForCausalLM(
    LocalArgmaxMixin,
    nn.Module,
    SupportsLoRA,
    SupportsPP,
    SupportsEagle,
    SupportsEagle3,
    SupportsQuant,
):
    hf_to_vllm_mapper = LlamaModel.hf_to_vllm_mapper
    # LoRA specific attributes
    packed_modules_mapping = {
        "qkv_proj": ["q_proj", "k_proj", "v_proj"],
        "gate_up_proj": ["gate_proj", "up_proj"],
    }
    embedding_modules = {
        "embed_tokens": "input_embeddings",
        "lm_head": "output_embeddings",
    }
  1. Lines 464-472. The mixins; How a vLLM model file is written (llama.py) has a table of what each one adds.
  2. Line 473. The outer class shares the mapper of LlamaModel. SupportsQuant.__new__ gives its rename-only part to the quant config, so names in ignored_layers can match vLLM names (vllm/model_executor/models/interfaces.py:1292-1299). For Llama this part is empty: get_rename_mapper() drops the stacked rules, which are the only rules of this mapper (vllm/model_executor/models/utils.py:180-199).
  3. Lines 475-478. packed_modules_mapping tells LoRA and the quant config which checkpoint layers form each stacked layer.
  4. Lines 479-482. embedding_modules lists the layers that get LoRA embedding buffers; Mistral clears it (vllm/model_executor/models/mistral.py:237-238).

Deep dive#

The sections below give the full detail. Open them one at a time, or use Open all deep dives at the top of the page.

Deep dive

Data flow: types and shapes

ValueType, shape (per rank)Where
input_idsint tensor [T], or None when inputs_embeds is given or on a later PP stagevllm/v1/worker/gpu/model_runner.py:1981-1997
positions[T]vllm/v1/worker/gpu/model_runner.py:1986
dynamic dimensions for torch.compiledimension 0 of all four inputsvllm/model_executor/models/llama.py:340-345
qkv[T, q_size + 2 × kv_size]vllm/model_executor/models/llama.py:229-230
decoder layer output(hidden_states, residual), both [T, hidden_size]vllm/model_executor/models/llama.py:313-330
LlamaModel.forward result[T, hidden_size]; IntermediateTensors on non-last PP stages; (hidden, aux list) with EAGLE-3vllm/model_executor/models/llama.py:443-457
compute_logits result[R, vocab_size] float logits after the TP gathervllm/model_executor/models/llama.py:546-551
load_weights resultset[str] of loaded parameter namesvllm/model_executor/models/llama.py:559-561

T is the number of scheduled tokens of the step after padding; R is the number of rows that sample.

Deep dive

Interaction with other subsystems

Deep dive

Worked example: which config keys change the module tree

Config key (Hugging Face)EffectLine
num_key_value_heads (missing)defaults to num_attention_heads (no GQA)vllm/model_executor/models/llama.py:289-291
head_dimoverrides hidden_size / num_attention_headsvllm/model_executor/models/llama.py:158-159
attention_bias / bias / qkv_biasbiases in QKV and o_projvllm/model_executor/models/llama.py:268-274
mlp_biasbiases in gate_up and downvllm/model_executor/models/llama.py:305
hidden_actmust be siluvllm/model_executor/models/llama.py:112-115
is_causalfalse → EncoderOnlyAttentionvllm/model_executor/models/llama.py:280-283
layer_types, sliding_windowper-layer sliding windowvllm/model_executor/models/llama.py:186-204
tie_word_embeddingsLM head shares the embedding weight; embedding also on the last PP stagevllm/model_executor/models/llama.py:379, vllm/model_executor/models/llama.py:509-510
logit_scalemultiplies the logits (default 1.0)vllm/model_executor/models/llama.py:512-515

Example: internlm/internlm3-8b maps to LlamaForCausalLM (vllm/model_executor/models/registry.py:140), and the comment at line 272 names it as the reason for qkv_bias.

Deep dive

Error paths

SituationResultWhere
hidden_act is not siluValueErrorvllm/model_executor/models/llama.py:112-115
query heads do not divide by TPAssertionError (start-up normally stops earlier with a ValueError)vllm/model_executor/models/llama.py:145, vllm/config/model.py:1559-1564
KV heads and TP do not divide either wayAssertionErrorvllm/model_executor/models/llama.py:148-155
layer index outside layer_typesAssertionError with both valuesvllm/model_executor/models/llama.py:197-200
a non-first PP stage gets no intermediate tensorsAssertionErrorvllm/model_executor/models/llama.py:421
a checkpoint name with no module or parameterValueError from the loadervllm/model_executor/models/utils.py:442-448
Deep dive

Environment variables

The file reads no environment variable. Two variables help when you study it:

VariableDefaultRead atEffect
VLLM_LOG_MODEL_INSPECTION0vllm/envs.py:2153-2155Logs the built module tree after create_model (vllm/model_executor/model_loader/base_loader.py:118-125).
VLLM_LOGGING_LEVEL=DEBUGINFOvllm/envs.py:860Shows one "Loaded weight ... with shape ..." line per parameter (vllm/model_executor/models/utils.py:343).

Check yourself#

  1. 1
    Which class of llama.py carries @support_torch_compile?
    easy
    Show answer

    LlamaModel (vllm/model_executor/models/llama.py:337-347). LlamaForCausalLM.forward only calls it.

  2. 2
    Why does LlamaForCausalLM.load_weights pass no mapper, although the class has hf_to_vllm_mapper?
    medium
    Show answer

    The outer loader hands every model.* tensor to LlamaModel.load_weights, which applies the mapper itself (vllm/model_executor/models/utils.py:387-400, vllm/model_executor/models/llama.py:459-461). The class attribute on the outer class is for others: SupportsQuant gives its rename part to the quant config (vllm/model_executor/models/interfaces.py:1296-1297). For Llama that part is empty, because the mapper has only stacked rules (vllm/model_executor/models/utils.py:186-196).

  3. 3
    A config has "qkv_bias": true and no attention_bias. Which layers get a bias?
    medium
    Show answer

    Only qkv_proj. bias_o_proj takes the value of attention_bias before qkv_bias overrides attention_bias (vllm/model_executor/models/llama.py:268-274).

  4. 4
    Follow model.layers.0.mlp.gate_proj.weight on TP rank 1 of a Llama-3-8B with TP = 2. Which rows of the checkpoint tensor end up where?
    hard
    Show answer

    The mapper renames it to layers.0.mlp.gate_up_proj.weight with shard id 0 (vllm/model_executor/models/llama.py:354). The v2 loader uses offset 0 and size 14336 / 2 = 7168 in the parameter (vllm/model_executor/layers/linear.py:967-970). The parameter copies checkpoint rows 7168-14335 (rank 1 × 7168) into its rows 0-7167 (vllm/model_executor/parameter.py:168-173).