vLLM Explained

Hugging Face checkpoints

BeginnerLearn the basicsabout 7 min read + 5 min deep dives

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

In one sentence

A model folder holds a config, a generation config, tokenizer files and weight files; each part of vLLM reads its own file.

The idea#

When you run vllm serve Qwen/Qwen3-0.6B, the name points to a folder of files on the Hugging Face Hub, or to a local folder. vLLM needs four kinds of information from it. They are the shape of the model, the default sampling values, the tokenizer with its chat template, and the weights. Each kind lives in its own files.

config.json: the shape of the model#

The Hugging Face config is a JSON object. The keys that matter most for serving:

  • architectures: the name of the model class, for example ["Qwen3ForCausalLM"]. vLLM looks this name up in its model registry to find its own implementation.
  • max_position_embeddings: the longest context that the model was trained for. It becomes the default max_model_len.
  • torch_dtype (in newer files dtype): the dtype of the weights, for example bfloat16. It becomes the default --dtype.
  • quantization_config: only in quantized checkpoints (Quantized checkpoints).
  • rope_scaling or rope_parameters: RoPE settings for long context. Some RoPE types make the usable context longer than max_position_embeddings.
  • Sizes: hidden_size, num_hidden_layers, num_attention_heads, num_key_value_heads, vocab_size. They give the shapes of all weights and of the KV cache.

generation_config.json: default sampling values#

The generation config holds the values that the model authors suggest for sampling, for example temperature, top_p and top_k, and the end-of-sequence token ids. By default, vLLM uses these values when a request does not set them. So two models can sample differently with the same request.

Tokenizer files and the chat template#

The tokenizer turns text into token ids (Tokens). Its files are tokenizer.json (the vocabulary and merge rules) and tokenizer_config.json (special tokens and settings). The chat template turns a list of chat messages into one prompt. It is a Jinja template, in the key chat_template of tokenizer_config.json, or in its own file chat_template.jinja in newer repositories. Transformers reads both places (general knowledge about Transformers, not vLLM code).

Weights: safetensors files#

The weights are in safetensors files. A file starts with a JSON header that lists each tensor: name, dtype, shape and byte offsets. The raw bytes follow. Loading a safetensors file runs no code, unlike a pickled .bin file. A small model has one model.safetensors. A large model has shards such as model-00001-of-00004.safetensors, and model.safetensors.index.json maps each tensor name to its shard.

Where the files come from#

  • A Hub name such as Qwen/Qwen3-0.6B. vLLM downloads the files with the huggingface_hub library into the Hugging Face cache. By default, this library uses ~/.cache/huggingface/hub. HF_HOME moves it to $HF_HOME/hub, and HF_HUB_CACHE sets it directly (general knowledge about huggingface_hub). A second start finds the files there.
  • A local path such as /models/qwen3. vLLM reads the folder directly and downloads nothing.
  • --revision selects a branch, a tag or a commit of the Hub repository. Pin a commit in production, so a new upload does not change your server.

An everyday comparison#

Think of a flat-pack furniture box. config.json is the label with the model name and the sizes. generation_config.json is the "recommended use" sheet. The tokenizer files are the dictionary for the instruction language. The safetensors files are the parts, in numbered bags, and the index is the parts list. vLLM is the worker who reads the label first, picks the right assembly plan (the model class), and then puts each part in its place.

The comparison stops here: vLLM does not use the assembly plan in the box (the Python code of the repository) by default. It uses its own plan for the architecture name. Only with --trust-remote-code can code from the repository run.

The picture#

Figure: each kind of file has its own reader in vLLM (panel 1). The architecture name in config.json selects the Python class through the model registry (panel 2). The file values (40960, 0.6, 20, 0.95) come from the Qwen3-0.6B repository on the Hugging Face Hub, not from the vLLM repository.
Open full size ↗

Read panel 1 row by row, from the file on the left to what vLLM gets on the right. Panel 2 follows only the first row further: from the name on the command line to the class that the workers build.

In vLLM#

This is a basics page, so this part gives only pointers. The pages How a vLLM model file is written (llama.py), "Weight loading: from checkpoint files to GPU parameters" and Renderers and chat templates: from messages to token ids explain the code in full.

  1. The default model. ModelConfig.model defaults to "Qwen/Qwen3-0.6B" (vllm/config/model.py:155), and EngineArgs copies it (vllm/engine/arg_utils.py:486). vllm serve <name> puts the positional name into args.model (vllm/entrypoints/cli/serve.py:56-57).
  2. Name → config → class. ModelConfig.__post_init__() calls get_config() (vllm/config/model.py:680-690). It then asks the registry for the architecture: registry.inspect_model_cls(architectures, self) (vllm/config/model.py:739-744). On the workers, resolve_model_cls() imports the class (vllm/model_executor/model_loader/utils.py:310-313).
  3. The registry entry. "Qwen3ForCausalLM": ("qwen3", "Qwen3ForCausalLM") maps the name to a module and a class (vllm/model_executor/models/registry.py:199).
        hf_config = get_config(
            self.hf_config_path or self.model,
            self.trust_remote_code,
            self._hf_config_revision or self.revision,
            self.code_revision,
            self.config_format,
            hf_overrides_kw=hf_overrides_kw,
            hf_overrides_fn=hf_overrides_fn,
            token=self.hf_token,
        )
        self.hf_config = hf_config
ModelConfig loads config.json with get_config(): from --hf-config-path or the model, with --trust-remote-code and the revision.

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

config.json: how vLLM reads it

  1. Find the format. With --config-format auto, get_config() checks for a Mistral repository with params.json first, then for config.json. With neither, it raises ValueError with a list of what to check (vllm/transformers_utils/config.py:883-917).
  2. Parse. A config parser builds a Transformers PretrainedConfig. It passes trust_remote_code, revision, code_revision and --hf-overrides (vllm/transformers_utils/config.py:919-931).
  3. No architectures? vLLM fills it from model_type if Transformers knows the type. Otherwise it warns and expects hf_overrides={'architectures': [...]} (vllm/transformers_utils/config.py:933-943).
  4. Quantization config. It takes quantization_config from the file, or from hf_quant_config.json for old ModelOpt checkpoints (vllm/transformers_utils/config.py:945-958).

dtype. With --dtype auto (the default, vllm/config/model.py:200), vLLM reads the config dtype (vllm/transformers_utils/model_arch_config_convertor.py:208-219). If the config has none, it reads the dtypes in the safetensors headers (vllm/transformers_utils/model_arch_config_convertor.py:221-231). FP32 models get the first dtype that the platform supports, or FP16 for pooling models when the platform supports it (vllm/config/model.py:2429-2445).

max_model_len. vLLM takes the smallest of several keys: max_position_embeddings, n_positions, max_seq_len, seq_length and others (vllm/transformers_utils/model_arch_config_convertor.py:372-399). It also caps the value with model_max_length from tokenizer_config.json (vllm/config/model.py:2570-2575). Some RoPE scaling types multiply it by their factor; for llama3, yarn and others the config value already includes the scaling (vllm/config/model.py:2603-2634). With no key at all, vLLM assumes 2,048 and warns (vllm/config/model.py:2589-2596). Example: Qwen3-0.6B has max_position_embeddings 40,960 and no RoPE scaling, so its default max_model_len is 40,960. These two values come from its config.json on the Hugging Face Hub, not from the vLLM repository.

Deep dive

generation_config.json and --generation-config

--generation-config has the default "auto" (vllm/config/model.py:358-364). The three values:

ValueDefault sampling values come from
auto (default)generation_config.json of the model folder
vllmvLLM's neutral defaults; the file is ignored for sampling
a folder pathgeneration_config.json in that folder

try_get_generation_config() loads the file with Transformers. If the folder has no such file, it makes a generation config from config.json (vllm/transformers_utils/config.py:1269-1295). get_diff_sampling_param() then keeps only these keys: repetition_penalty, presence_penalty, frequency_penalty, temperature, top_k, top_p, min_p, max_new_tokens and skip_special_tokens. It renames max_new_tokens to max_tokens (vllm/config/model.py:1877-1902). --override-generation-config is applied on top (vllm/config/model.py:1880).

When the file changes the defaults, vLLM logs: "Default vLLM sampling parameters have been overridden by the model's generation_config.json" (vllm/config/model.py:1906-1914). The EOS ids of the file become extra stop token ids (vllm/sampling_params.py:789-803). The page The OpenAI-compatible API shows how a request value, this default and the OpenAI default combine.

Deep dive

Tokenizer and chat template

The tokenizer path defaults to the model path (vllm/config/model.py:607-610). --tokenizer and --tokenizer-revision change it. cached_tokenizer_from_config() loads it with the tokenizer mode, revision and trust_remote_code of the model config (vllm/tokenizers/registry.py:298-315).

The chat template comes from the first source that has one (vllm/renderers/hf.py:264-319):

  1. the template that the server or the request gives (--chat-template);
  2. the template of the Transformers processor (AutoProcessor), but only when the request has no tools;
  3. the template of the tokenizer: Transformers reads it from tokenizer_config.json or chat_template.jinja (Transformers behaviour);
  4. a template that vLLM ships for some model types.

The page Renderers and chat templates: from messages to token ids explains how the renderer uses the template.

Deep dive

Weights: download, cache and the index file

DefaultModelLoader._prepare_weights() finds the weight files (vllm/model_executor/model_loader/default_loader.py:147-263):

  1. Format. With --load-format auto, a folder with consolidated*.safetensors uses the Mistral format. Otherwise vLLM tries *.safetensors, then *.bin (vllm/model_executor/model_loader/default_loader.py:170-186).
  2. Download. For a Hub name, download_weights_from_hf() calls snapshot_download() with the allowed patterns and --download-dir as the cache directory (vllm/model_executor/model_loader/default_loader.py:213-221). A file lock stops two processes from downloading the same model at once (vllm/model_executor/model_loader/weight_utils.py:516-527). With HF_HUB_OFFLINE=1, it uses only local files (vllm/model_executor/model_loader/weight_utils.py:466).
  3. Filter. It keeps only files that model.safetensors.index.json names. A file that the index names but that is missing raises FileNotFoundError (vllm/model_executor/model_loader/weight_utils.py:590-616). This matters for repositories that contain both shards and one consolidated file.
  4. Nothing found. RuntimeError: Cannot find any model weights (vllm/model_executor/model_loader/default_loader.py:258-261).

The cache folder. --download-dir defaults to the Hugging Face default cache (vllm/config/load.py:62-64). The cache stores each repository as models--<org>--<name>/snapshots/<commit>/ under HF_HUB_CACHE (vllm/model_executor/model_loader/weight_utils.py:903-906). With VLLM_USE_MODELSCOPE, vLLM downloads from ModelScope instead (vllm/model_executor/model_loader/weight_utils.py:161-175).

After the files are found, each worker reads the tensors and gives them to model.load_weights(). With TP, each worker keeps only its slice ("Weight loading: from checkpoint files to GPU parameters").

Deep dive

Names, revisions and remote code

FlagDefaultEffect
--revisionnone (the default branch)Branch, tag or commit of the model (vllm/config/model.py:238-240). The tokenizer revision follows it unless --tokenizer-revision is set (vllm/config/model.py:609-610).
--trust-remote-codeoffAllows Python code from the repository, for config and tokenizer classes (vllm/config/model.py:197-199).
--tokenizerthe modelAnother tokenizer name or path (vllm/config/model.py:170-172).
--served-model-namethe modelThe names that clients put in model. The first name is in responses and metrics (vllm/config/model.py:329-336). The API server uses all names, or [args.model] (vllm/entrypoints/launchers/api_server/app_state.py:49-52).
--hf-overrides{}Changes keys of config.json at load time (vllm/config/model.py:348-350).

Architecture lookup. inspect_model_cls() tries each name in architectures. If vLLM has no implementation for any of them and --model-impl is auto, it falls back to the Transformers implementation (vllm/model_executor/models/registry.py:1373-1400). The model loader then logs that some features may not work (vllm/model_executor/model_loader/utils.py:315-323). With no match at all, vLLM raises an error for an unsupported architecture (vllm/model_executor/models/registry.py:1402).

What can go wrong#

  • "Invalid repository ID or local directory". The name is wrong, the repository is private (log in, or set HF_TOKEN), or the folder has no config.json (vllm/transformers_utils/config.py:904-917).
  • Unexpected sampling. The model's generation_config.json changes the defaults, for example temperature 0.6 (Qwen3-0.6B on the Hub) instead of 1.0. Check the log line, or start with --generation-config vllm (vllm/config/model.py:1906-1914).
  • A slow model on the Transformers fallback. If vLLM has no class for the architecture, it uses the Transformers implementation and warns (vllm/model_executor/model_loader/utils.py:315-323). Check the "Resolved architecture" log line (vllm/config/model.py:744).
  • A model that changes under you. Without --revision, a new upload to the repository can change the weights or the chat template at the next start. Pin a commit.
  • Missing shards. An index file that names a shard that is not in the folder raises FileNotFoundError (vllm/model_executor/model_loader/weight_utils.py:609-613).
  • Remote code. Some repositories need --trust-remote-code. It runs Python code from the repository in every vLLM process. Read the code first, and pin the revision.

Why it matters. Most surprises at start-up come from the model folder: the wrong class, the wrong context length, other default sampling values or a missing file. When you know which file feeds which setting, you can fix them with the right flag.

Check yourself#

  1. 1
    Which key of config.json selects the Python class that vLLM builds?
    easy
    Show answer

    architectures. ModelConfig passes it to registry.inspect_model_cls() (vllm/config/model.py:741), and the registry maps the name to a module and a class (vllm/model_executor/models/registry.py:199).

  2. 2
    What model does vllm serve use when you give no model name?
    easy
    Show answer

    Qwen/Qwen3-0.6B, the default of ModelConfig.model (vllm/config/model.py:155).

  3. 3
    A request sets no temperature. The model folder has generation_config.json with "temperature": 0.6. Which temperature does the request get by default, and how can the operator change that?
    medium
    Show answer

    0.6. With the default --generation-config auto, get_diff_sampling_param() takes temperature from the file (vllm/config/model.py:1877-1902). --generation-config vllm ignores the file, and --override-generation-config '{"temperature": 0.7}' replaces the value (vllm/config/model.py:1880).

  4. 4
    A config has max_position_embeddings 131072, and tokenizer_config.json has model_max_length 32768. What is the default max_model_len, before RoPE scaling?
    hard
    Show answer

    32,768. vLLM takes the derived length from the config and then the minimum with model_max_length of the tokenizer config (vllm/config/model.py:2570-2575).

  5. 5
    A repository has both model-00001-of-00002.safetensors, model-00002-of-00002.safetensors and a consolidated.safetensors with the same weights. Why does vLLM not load the weights twice?
    hard
    Show answer

    With --load-format auto, a folder with consolidated*.safetensors uses the Mistral format and loads only those files (vllm/model_executor/model_loader/default_loader.py:170-182). In the hf format, vLLM keeps only the files that model.safetensors.index.json names (vllm/model_executor/model_loader/default_loader.py:236-254). Both rules stop it from loading both copies.