De l'API au GPU, semaine 2 : ce qui se passe réellement derrière l'API

DEV - 19/07
Phase 1 sur 8 : Modèles locaux fonctionnant confortablement. Semaine 2 sur 32. Au cours de la semaine 1, je suis passé par la machine...

Phase 1 sur 8 : Modèles locaux fonctionnant confortablement. Semaine 2 sur 32.

Au cours de la première semaine, j'ai parcouru la machine et prouvé que le GPU fonctionnait. Cette semaine, j'exécute un véritable modèle de langage dessus et, pour la première fois, je regarde ce qui se cache derrière "l'API AI" que j'appelle depuis des années. La version courte : l’API n’est pas magique. Il s'agit d'une couche HTTP sur un processus local qui charge un fichier de nombres en mémoire et effectue les calculs matriciels à partir de la semaine 1. À la fin de cet article, cette phrase sera claire, soutenue par de vraies commandes et une sortie réelle que vous pourrez reproduire.

J'utilise Ollama, un runtime qui rend l'exécution d'un modèle local aussi simple que l'exécution d'un conteneur. Les autres noms que vous verrez dans cet espace sontlama.cpppour une inférence locale légère, vLLM pour un service à haut débit et NVIDIA TensorRT-LLM pour une inférence NVIDIA optimisée. Hugging Face Transformers est également courant, mais il s'agit d'un cadre Python plus large pour l'exécution et la formation de modèles, et non d'un service de modèle local prêt à l'emploi. Ces outils se chevauchent, mais ils ne constituent pas des remplacements exacts. J'ai choisi Ollama car il me donne une CLI et une API HTTP locale avec très peu de configuration.1

Tout ce qui suit fonctionne sur le DGX Spark et est accessible via SSH commeétincelle, la même configuration que la semaine 1.

Concept 1 : un modèle n’est pas un runtime

La première idée à retenir, car elle vaut pour le reste de la série, est la séparation entre le modèle et le runtime.

  • Le modèle est constitué de données entraînées : des poids plus les métadonnées nécessaires pour les utiliser. A lui seul, il ne fait rien. Ce sont des données sur disque.
  • Le moteur d'exécution est le programme qui charge ces poids en mémoire et exécute les calculs pour transformer votre invite en texte. Ollama est le moteur d'exécution ici.

Si vous connaissez Docker, il existe une analogie utile, mais elle n'est pas exacte. Un package de modèle est comme une image composée de couches versionnées et adressées par contenu. Ollama est comme le moteur qui extrait ces couches et démarre la charge de travail. Contrairement à une image de conteneur, la couche de modèle principale est constituée de données numériques entraînées, et non d'une application et de ses fichiers de système d'exploitation.

Ollama appelle chaque fichier de package stocké un blob. Here, a blob is just a file kept under a name derived from its content digest. The inspection below follows the package index to the model blob and checks that it is the expected file.

Plutôt que d'exécuter un script wrapper, j'ai inspecté le modèle avec quelques commandes directes, une à la fois. Chaque commande répond à une seule question, vous pouvez donc la coller, lire le résultat, puis passer à la suivante.

Tout d’abord, listez ce qu’Ollama a téléchargé localement :

ssh spark 'liste ollama'
Entrer en mode plein écran Quitter le mode plein écran
NOM ID TAILLE MODIFIÉ nomic-embed-text:latest 0a109f422b47 274 Mo il y a 2 jours lama3.2:3b a80c4f17acd5 2,0 Go il y a 3 jours phi4:latest ac896e5b8b34 9,1 Go il y a 7 jours qwen3.6:35b-a3b-bf16 94061ddd23a7 71 Go il y a 8 jours
Entrer en mode plein écran Quitter le mode plein écran

Ensuite, demandez à Ollama des faits ciblés sur Phi-4. Le serveur Ollama écoutehôte local : 11434sur le Spark, alors exécutez ceci dans un shell sur le Spark (chut étincelle) :

curl -s http://localhost:11434/api/show -d '{"model": "phi4"}' | jq '{format : .details.format, architecture : .details.family, paramètres : .details.parameter_size, context_length : (.model_info | to_entries | map(select(.key | endswith(".context_length"))) | first.value), embedding_length : (.model_info | to_entries | map(select(.key | endswith(".embedding_length"))) | first.value), quantification : .details.quantization_level, capacités, runtime_parameters : (.parameters | split("\n") | map(select(length > 0) | gsub(" +"; " "))) }'
Entrer en mode plein écran Quitter le mode plein écran
{ "format": "gguf", "architecture": "phi3", "parameters": "14.7B", "context_length": 16384, "embedding_length": 5120, "quantization": "Q4_K_M", "capabilities": [ "achèvement" ], "runtime_parameters": [ "stop \"<|im_start|>\"", "stop \"<|im_end|>\"", "stop \"<|im_sep|>\"" ] }
Entrer en mode plein écran Quitter le mode plein écran

Cet appel donne des faits modèles lisibles, notamment"format": "gguf". Pour un contrôle normal, c'est ainsi que je sais qu'Ollama identifie le modèle comme GGUF. Je n'ai pas besoin de localiser le fichier brut et d'inspecter ses octets juste pour répondre à cette question.

Pour voir les fichiers exacts qui composent le package, lisez le manifeste d'Ollama. Il s'agit d'un petit fichier JSON sur Spark, doncjqpeut le lire directement. Je sélectionne uniquement la version du package et la liste des fichiers :

jq '{schemaVersion, couches : [.layers[] | {mediaType, digest, size}]}' \ /usr/share/ollama/.ollama/models/manifests/registry.ollama.ai/library/phi4/latest
Entrer en mode plein écran Quitter le mode plein écran
{ "schemaVersion": 2, "layers": [ { "mediaType": "application/vnd.ollama.image.model", "digest": "sha256:fd7b6731c33c57f61767612f56517460ec2d1e2e5a3f0163e0eb3d8d8cb5df20", "size": 9053114464 }, { "mediaType": "application/vnd.ollama.image.template", "digest": "sha256:32695b892af87ef8fca6e13a1a31c67c1441d7398be037e366e2fc763857c06a", "size": 275 }, { "mediaType": "application/vnd.ollama.image.license", "digest": "sha256:fa8235e5b48faca34e3ca98cf4f694ef08bd216d28b58071a1f85b1d50cb814d", "size": 1084 }, { "mediaType": "application/vnd.ollama.image.params", "digest": "sha256:45a1c652dddc9efdcefa977ab81cfbe26b6e52bc8e78f2f4c698538783e0ac80", "taille": 82 } ] }
Entrer en mode plein écran Quitter le mode plein écran

Le chemin du registre se termine parbibliothèque/phi4/dernière, donc ce manifeste est l'index du nom du modèlephi4et étiquettedernier. Il ne contient pas les 9,1 Go de poids. Il répertorie les fichiers qui composent le package Ollama, un peu comme un fichier de verrouillage mappe les noms de package à des artefacts exacts.

Chaque entrée souscouchesest un fichier dans le package (un blob). Il comporte trois champs utiles :

  • type de médiaindique quel est le rôle du fichier. Ce package contient un modèle, un modèle d'invite, une licence et des paramètres par défaut.
  • digérerest l'identité du contenu du fichier. Lesha256le préfixe nomme l'algorithme de hachage et les caractères après les deux points sont le hachage des octets du fichier.
  • tailleindique exactement combien d'octets ce fichier doit contenir.

La couche modèledigérerC'est aussi ainsi qu'Ollama nomme le fichier sur le disque. Il stocke chaque blob sous unblobsrépertoire utilisant le résumé comme nom de fichier, avec les deux points remplacés par un trait d'union, doncsha256:fd7b...df20devientsha256-fd7b...df20. Ce fichier est9 053 114 464octets, letaillemontré ci-dessus, et son SHA-256 correspond au résumé. L'enquête liée vérifie indépendamment l'en-tête du fichier avecxxd: octets47 47 55 46épelerGGUFen ASCII. Cette vérification plus approfondie est utile lors de la réutilisation du fichier brut dans un autre environnement d'exécution, mais.détails.formatest la réponse simple de l'API Ollama.2

Doncspectacle ollamaet le manifeste répond à différentes questions. L'appel de métadonnées donne des informations lisibles sur le modèle telles que l'architecture et la longueur du contexte. Le manifeste indique à Ollama quels fichiers exacts forment le package exécutable et où les trouver par ID de contenu.

Vous pourriez maintenant vous demander si un autre runtime peut utiliser le même fichier GGUF. C’est utile, mais cela ne fait pas partie de la leçon principale d’Ollama. J'y réponds vers la fin dans Can llama.cpp réutiliser ce modèle Ollama ?.

j'utilisephi4, le modèle à 14,7 B paramètres de Microsoft. À 16 bits par poids, 14,7 milliards de poids nécessiteraient à eux seuls environ 29,4 Go. Le forfait local ne fait que 9,1 Go, ce qui entraîne une première surprise.

Petit tour d'horizon de la signification de chaque ligne, puisque ces termes reviennent constamment :

ChampValeurSignification simple
formatggufformat de fichier modèle rapporté par Ollama
architecturephi3la conception du réseau neuronal (phi4 réutilise la famille phi3)
paramètres14,7Bcombien de poids entraînés le modèle possède
longueur du contexte16384le plus de jetons (invite + réponse) qu'il peut considérer en même temps
longueur d'intégration5120largeur de chaque vecteur de jeton ; couvert plus tard
quantificationQ4_K_Mles poids sont stockés sur environ 4 bits chacun, et non 16

Cette dernière ligne répond à la surprise de 9,1 Go. Cette construction est quantifiée : ses poids utilisent un bit faible mixteQ4_K_Mreprésentation au lieu de valeurs 16 bits. Quatre bits par poids représenteraient environ 7,35 Go avant les métadonnées et la surcharge de quantification, donc une couche de modèle de 9,1 Go est raisonnable. La quantification est une phase entière plus tard dans cette série (semaines 14 à 16). Pour l'instant, la seule chose à retenir est qu'Ollama utilise des poids de moindre précision, et c'est pourquoi ce modèle 14,7B prend beaucoup moins de place que son modèle source 16 bits.

Les troisarrêtentrées (<|im_start|>,<|im_end|>,<|im_sep|>) sont des jetons spéciaux : des marqueurs que le modèle a été entraîné à utiliser comme limites de tour dans une conversation. Le runtime les surveille pour savoir quand le modèle a terminé sa réponse. Ils réapparaissent dans la section suivante, alors gardez-les à l’esprit.

Concept 2 : l'API est simplement du HTTP sur un processus local

Ollama expose une API HTTP locale sur le port 11434,3 de la même forme que n'importe que...
[Courte citation de 8% de l'article original]

Loading...