DefaultModelLoader : du HF Hub vers la VRAM du GPU
Responsabilités
Le chargeur de modèle (model loader) est, dans le démarrage de vLLM, l'étape qui transporte les poids du modèle depuis le disque ou Hugging Face Hub vers la VRAM du GPU. DefaultModelLoader en est l'implémentation la plus courante : il gère plusieurs formats sur disque (safetensors / .pt / format maison Mistral / npcache), assemble les poids multi-shards en un itérateur de (name, tensor), puis laisse la méthode load_weights du modèle les déposer un à un dans le nn.Module. Il s'appuie sur BaseModelLoader pour enchaîner « télécharger, filtrer, parser, itérer » ; il ne se préoccupe ni du sharding des tensors ni de la quantization — ces deux tâches sont laissées aux couches comme ColumnParallelLinear et à QuantizationConfig.
Une passe de chargement typique : __init__ valide model_loader_extra_config du LoadConfig(default_loader.py:74-126) en n'acceptant que les clés enable_multithread_load, num_threads, enable_weights_track. download_model passe par _prepare_weights, qui choisit les allow_patterns selon load_format, glob directement pour un dossier local, ou utilise download_weights_from_hf pour du distant(default_loader.py:194-207). load_weights est l'entrée qui travaille réellement : initialisation du filtre EP(default_loader.py:425-426), passage de l'itérateur get_all_weights à model.load_weights, puis vérification via track_weights_loading qu'aucun paramètre n'a été oublié(default_loader.py:444-445).
Motivation de conception
Pourquoi séparer le loader en deux couches Loader + Source ?
- Poids multi-sources : certains modèles ont, en plus du corps principal, des
secondary_weights(par ex. l'encoder visuel d'un modèle multimodal, avec son propre checkpoint) ; le dataclassSourcedonne à chaque source sonprefix,fall_back_to_pt,allow_patterns_overrides(default_loader.py:49-69) ;get_all_weightsles concatène en un seul itérateur(default_loader.py:321-340). - Embranchement par format :
load_formatdécide du chemin à suivre ;autodétecte d'abordconsolidated*.safetensorset bascule enmistral(default_loader.py:151-163) ;safetensors/fastsafetensors/instanttensorforce l'usage exclusif de.safetensors,pt/npcacheemprunte l'ancien chemin. - Accélération multi-thread :
enable_multithread_loadbascule versmulti_thread_safetensors_weights_iteratoroumulti_thread_pt_weights_iterator, avec 8 threads par défaut(default_loader.py:278-308) ; mais le mode multi-thread ne supporte que la stratégie lazy par défaut, et est incompatible avec les autressafetensors_load_strategy(default_loader.py:116-126). - Filtrage EP des poids : en MoE + expert parallelism, on ne charge que les experts gérés par ce rank ;
_init_ep_weight_filtercalcule leslocal_expert_ids(default_loader.py:351-412) ; l'itérateur safetensors saute ensuite les tensors d'experts non locaux, économisant des I/O disque. - Déduplication de l'index safetensors :
Mistral-7B-Instruct-v0.3possède à la fois des versions sharded et consolidated des safetensors ; un glob brut lirait les deux et causerait des conflits,filter_duplicate_safetensors_filesutilisemodel.safetensors.index.jsonpour filtrer(default_loader.py:217-235). - Tracking du chargement : pour les modèles non quantifiés,
enable_weights_track=Truepar défaut ; après chargement, on compare auxnamed_parameterspour repérer les poids oubliés, évitant de lancer une inference en silence avec des poids aléatoires(default_loader.py:434-445).
Fichiers clés
DefaultModelLoader class:43-47— définition de classe et docstring,DEFAULT_NUM_THREADS = 8.Source dataclass:49-69— dataclass interneSource, décrit une source de poids._prepare_weights:128-242— parseload_format, télécharge, glob, déduplique, renvoie(hf_folder, hf_weights_files, use_safetensors)._get_weights_iterator:244-319— selonload_formatet le toggle multi-thread, choisit le weights iterator, puis préfixe le nom des tensors avecprefix.get_all_weights:321-340— concatène primary source etsecondary_weightsen un seul générateur._init_ep_weight_filter:351-412— en EP, calcule leslocal_expert_idspour sauter les tensors d'experts non locaux.load_weights:414-445— entrée du chargement : stratégie torchao → filtre EP →model.load_weights→ tracking.track_weights_loading:447-475— comparenamed_parametersetloaded_weights, signale les poids manquants ou inattendus.weight_utils.py:1-1— fournitsafetensors_weights_iterator,download_weights_from_hf,filter_duplicate_safetensors_files, etc.base_loader.py:1-1— classe abstraiteBaseModelLoader, définit l'interfaceload_weights.
Flux de données
L'appelant (typiquement le load_model du worker) instancie d'abord un DefaultModelLoader, puis appelle load_weights(model, model_config). Dans le corps, on ajuste d'abord la stratégie safetensors si quantization == "torchao", puis _init_ep_weight_filter calcule le filtre EP, puis get_all_weights nourrit chaque Source à _get_weights_iterator pour produire le générateur, et enfin model.load_weights le consomme :
@instrument(span_name="Load weights")
def load_weights(self, model: nn.Module, model_config: ModelConfig) -> None:
if model_config.quantization == "torchao":
quant_config = get_quant_config(model_config, self.load_config)
if (
hasattr(quant_config, "is_checkpoint_torchao_serialized")
and quant_config.is_checkpoint_torchao_serialized
and torchao_version_at_least("0.15.0")
):
self.load_config.safetensors_load_strategy = "torchao"
self._init_ep_weight_filter(model_config)
loaded_weights = model.load_weights(self.get_all_weights(model_config, model))
self.counter_after_loading_weights = time.perf_counter()
logger.info_once(
"Loading weights took %.2f seconds",
self.counter_after_loading_weights - self.counter_before_loading_weights,
)
# We only enable strict check for non-quantized models
# that have loaded weights tracking by default.
default_enable_weights_track = (
model_config.quantization is None and loaded_weights is not None
)(default_loader.py:414-438) Notez que c'est model.load_weights(...) qui copie réellement les tensors dans le nn.Module ; chaque implémentation de modèle dans vLLM redéfinit cette méthode, et y associe chaque nom de tensor au bon weight_loader (par ex. ColumnParallelLinear.weight_loader_v2) — sharding et quantization sont faits à ce niveau. Voir couches linéaires parallèles par tensor et chargement des couches quantifiées.
Limites et échecs
- load_format inconnu : toute valeur hors de
hf / safetensors / fastsafetensors / instanttensor / mistral / pt / npcachelèveValueError(default_loader.py:183-184). - Fichiers de poids introuvables : si le glob ne retourne rien, on lève
Cannot find any model weights(default_loader.py:237-240). - Multi-thread + safetensors_load_strategy incompatibles :
enable_multithread_load=Truene supporte que la stratégie lazy par défaut ; les combinaisons avec d'autres stratégies lèventValueError(default_loader.py:118-126). - Type d'extra_config erroné : si
model_loader_extra_confign'est pas un dict, qu'une clé n'est pas dans la whitelist, ou quenum_threadsn'est pas un entier positif, on lève immédiatement(default_loader.py:79-110). - Saut du filtre EP en EPLB : quand
enable_eplbest activé, les slots d'experts redondants pointent vers des experts logiques d'autres ranks, et le filtrage les laisserait vides ; onreturndonc directement(default_loader.py:370-375). - Modèles quantifiés hors tracking : si la méthode de quantization a
process_weights_after_loadingouuses_meta_device, le checkpoint peut ne pas contenir les scales correspondants ; ces paramètres sont retirés de l'ensemble de tracking(default_loader.py:453-462).
Résumé
DefaultModelLoader fait correctement les trois choses « trouver les fichiers, les télécharger, itérer les tensors » ; le sharding / quantization / déquantization proprement dit est laissé aux couches et aux méthodes de quantization. La jointure avec les couches linéaires parallèles par tensor se fait à l'intérieur de model.load_weights, où chaque nom de tensor est associé à un weight_loader ; avec le chargement des couches quantifiées, elle se fait via quant_config.get_quant_method qui équipe chaque couche d'un LinearMethodBase — le loader lui-même reste ignorant.
Voir la documentation officielle : Documentation vLLM · README