Module 10 · Leçon 4

Faire tourner un modèle sur votre ordinateur : Ollama et quantification

Exécuter un modèle open source sur son propre ordinateur. Estimer d'abord la mémoire qu'occupe un modèle, écrire la quantification à la main, voir de combien les poids rétrécissent et se dégradent en 8, 4 et 2 bits, et enfin le faire tourner avec Ollama.

  • Environ 40 minutes
  • Niveau : Intermédiaire
  • Testé : 2026-09-15 torch 2.14, CPU Apple M4 ; utilisation d'Ollama d'après sa documentation officielle

Le code et les sorties des programmes sont reproduits tels qu’ils ont tourné : commentaires et sorties sont donc en chinois.

Toute la première partie appelait l'API de DeepSeek. C'est très pratique, mais dans certains cas, vous voudrez que le modèle tourne sur votre propre machine : des données qui ne doivent pas quitter l'entreprise, pas de réseau, un volume d'appels tel que l'API revient trop cher, ou simplement l'envie d'essayer.

Cette leçon explique comment exécuter un modèle open source sur son ordinateur. La question clé : quelle taille de modèle mon ordinateur peut-il accueillir ? La réponse dépend de deux choses : le nombre de paramètres du modèle, et le nombre d'octets utilisés pour stocker chaque paramètre.

python memory_estimate.py
python quantize.py

Estimer la mémoire

Les poids d'un modèle sont un grand tas de nombres. Stocker chaque nombre en décimal 32 bits (FP32) demande 4 octets, en 16 bits (BF16 ou FP16) 2 octets. La mémoire occupée par les poids vaut donc à peu près :

参数量 × 每个参数的字节数

Prenons le Qwen2.5-0.5B-Instruct de la leçon 3 (494 millions de paramètres) :

== 1. Qwen2.5-0.5B-Instruct 的权重,在不同精度下占多少
  FP32        1.84 GB
  BF16/FP16   0.92 GB
  INT8        0.46 GB
  4 比特        0.23 GB

Le fichier téléchargé model.safetensors fait 988 Mo, soit exactement la taille en BF16 (988 Mo valent environ 0,92 Go ; l'écart vient de la conversion entre 1000 et 1024).

À l'exécution, aux poids s'ajoute le cache KV de la leçon 6 du module 09. Sa taille se calcule d'après la configuration du modèle : pour chaque token et chaque couche, on stocke un K et un V :

def kv_cache_gb(n_layers, n_kv_heads, head_dim, n_tokens, bytes_per_value=2):
    # 每个词元、每一层,要存一个 K 和一个 V,各是 n_kv_heads × head_dim 个数
    return 2 * n_layers * n_kv_heads * head_dim * n_tokens * bytes_per_value / 1024**3
== 2. 它的 KV 缓存(每层 2 组 K/V,每组 64 维,BF16)
  每个词元 12 KB
    1000 个词元:0.01 GB
   32000 个词元:0.37 GB
  如果不用分组查询注意力(14 个头各存一份 K/V),32000 个词元要 2.56 GB,是现在的 7 倍

La dernière ligne montre l'effet de l'attention à requêtes groupées : Qwen2.5-0.5B a 14 têtes d'attention, mais seulement 2 groupes de K et V, ce qui divise le cache par 7. Avec un long contexte, cet écart est décisif.

Pour une estimation pratique, une règle simple : nombre de paramètres × octets par paramètre, plus vingt pour cent de marge pour le cache et les autres frais :

== 3. 粗略估算:参数量 × 每个参数的字节数,再留两成余量给缓存和其他开销
   0.5 B 参数:FP32    2.2 GB  BF16/FP16    1.1 GB  INT8    0.6 GB  4 比特    0.3 GB
     7 B 参数:FP32   31.3 GB  BF16/FP16   15.6 GB  INT8    7.8 GB  4 比特    3.9 GB
    14 B 参数:FP32   62.6 GB  BF16/FP16   31.3 GB  INT8   15.6 GB  4 比特    7.8 GB
    32 B 参数:FP32  143.1 GB  BF16/FP16   71.5 GB  INT8   35.8 GB  4 比特   17.9 GB
    70 B 参数:FP32  312.9 GB  BF16/FP16  156.5 GB  INT8   78.2 GB  4 比特   39.1 GB

Ce tableau s'utilise tel quel. Un portable de 16 Go de mémoire fait tout juste tourner un modèle 7B en BF16 ; en 4 bits, un modèle 14B passe aussi. Pour un long contexte, il faut ajouter le cache KV.

Le tableau montre aussi pourquoi tout le monde parle de « quantification » : pour un même modèle, 4 bits ne demandent qu'un quart de BF16.

Écrire la quantification à la main

La quantification consiste à stocker chaque paramètre avec moins de bits. La méthode la plus simple s'appelle quantification symétrique : dans un groupe de nombres, on repère celui dont la valeur absolue est la plus grande, on divise l'intervalle [-maximum, maximum] en cases égales, chaque nombre tombe dans la case la plus proche, et l'on ne stocke que le numéro de la case (un petit entier) et le facteur d'échelle du groupe.

def quantize(w, bits, group=None):
    """对称量化:每组数用一个缩放系数,把 [-最大绝对值, 最大绝对值] 映射到整数 [-qmax, qmax]。
    返回"量化后再还原"的权重,以及实际要存的整数和缩放系数。"""
    qmax = 2 ** (bits - 1) - 1  # 8 比特是 127,4 比特是 7
    shape = w.shape
    w = w.reshape(-1, group) if group else w.reshape(shape[0], -1)  # 按组,或者按行
    scale = w.abs().amax(dim=1, keepdim=True) / qmax
    q = torch.round(w / scale).clamp(-qmax, qmax)  # 这就是要存下来的整数
    return (q * scale).reshape(shape), q, scale

Un exemple : 8 nombres quantifiés en 4 bits (entiers de -7 à 7) :

== 1. 一个例子:把 8 个小数量化成 4 比特整数
  原来:   [0.077, -0.0147, -0.1089, 0.0284, -0.0542, -0.0699, 0.0202, 0.0419]
  整数:   [5, -1, -7, 2, -3, -4, 1, 3](缩放系数 0.01556)
  还原后: [0.0778, -0.0156, -0.1089, 0.0311, -0.0467, -0.0623, 0.0156, 0.0467]

Le plus grand, -0,1089, correspond à -7, et le facteur d'échelle vaut donc 0,1089 / 7 = 0,01556. Les autres nombres sont divisés par lui puis arrondis. En rétablissant, on multiplie de nouveau ; l'erreur est d'environ 0,003 pour la plupart des nombres, et -0,0542 devient -0,0467, avec une erreur un peu plus grande.

Chaque nombre passe de 32 à 4 bits, avec en plus un seul facteur d'échelle stocké.

Quantifier notre petit GPT

Quantifions toutes les matrices du petit GPT entraîné au module 09, pour voir de combien le modèle rétrécit et se dégrade (LayerNorm et biais sont petits et restent tels quels) :

== 2. 整个模型量化之后
                                大小      验证损失
  32 位小数(原模型)             6.16 MB     4.476   白露起春光,知君得舞衣。雪时疑是静,山意势悠扬。
  8 比特,每行一个系数             1.58 MB     4.476   白露起春光,知君得舞衣。雪时疑是静,山意势悠扬。
  4 比特,每行一个系数             0.81 MB     4.542   云泉四百古,一树两三五。雪路之山在,山闾势悠扬。
  4 比特,每 32 个数一个系数        0.89 MB     4.512   白露起春光,知君得舞衣。雪时疑报晓,山下势悠扬。
  2 比特,每 32 个数一个系数        0.51 MB     6.933   百日起春光津倚郭对舞儿年,一之州在子。闾里里里里

Chaque ligne écrit un poème avec la même graine aléatoire, pour faciliter la comparaison.

8 bits : la taille tombe au quart, la perte de validation vaut 4,476, exactement celle du modèle d'origine, et même le poème écrit est identique caractère pour caractère. La quantification en 8 bits est presque « gratuite ».

4 bits, un facteur par ligne : la taille est encore divisée par deux, la perte passe de 4,476 à 4,542, et le poème change. Une ligne contient quelques centaines de nombres ; il suffit que l'un d'eux soit particulièrement grand pour gonfler le facteur d'échelle, et tous les autres se retrouvent tassés dans quelques cases, avec une grande erreur.

4 bits, un facteur par groupe de 32 : on stocke un peu plus de facteurs d'échelle (la taille passe de 0,81 à 0,89 Mo), la perte redescend à 4,512, et le poème redevient presque celui d'origine, à deux endroits près. Plus les groupes sont petits, plus le facteur d'échelle colle à ses nombres, et plus l'erreur est faible. La quantification 4 bits utilisée en pratique est presque toujours par groupes.

2 bits : seules trois valeurs, -1, 0 et 1, sont disponibles ; le modèle est complètement cassé, perte 6,93, le format de ce qu'il écrit part en morceaux, et il finit par répéter « 里里里里 ».

La conclusion rejoint l'expérience pratique : 8 bits est presque sans perte ; 4 bits perd un peu, mais en échange d'un quart de la taille, et c'est le choix le plus courant pour faire tourner un modèle sur son ordinateur ; en dessous, la qualité s'effondre vite.

Les vraies méthodes de quantification sont plus complexes que celle-ci : elles traitent les « valeurs aberrantes » particulièrement grandes, décident d'après des données comment quantifier avec le moins d'erreur, et il existe des formats de données conçus exprès pour 4 bits (le QLoRA évoqué à la leçon 2 utilise un format appelé NF4). Mais le principe de base, ce sont les quelques lignes de code de cette section.

Exécuter un modèle avec Ollama

Écrire soi-même le code de quantification et d'exécution d'un grand modèle est fastidieux. Ollama est un outil qui empaquette tout cela : une commande télécharge un modèle déjà quantifié et l'exécute.

Installation (en septembre 2026 ; référez-vous aux instructions officielles) :

# macOS 和 Linux
curl -fsSL https://ollama.com/install.sh | sh

# Windows(PowerShell)
irm https://ollama.com/install.ps1 | iex

Sous macOS et Windows, on peut aussi télécharger un installeur depuis ollama.com.

Exécutons le Qwen2.5-0.5B de la leçon 3 :

ollama run qwen2.5:0.5b

Au premier lancement, le modèle est téléchargé automatiquement, puis la conversation commence. En septembre 2026, le qwen2.5:0.5b de la bibliothèque de modèles d'Ollama est par défaut la version quantifiée Q4_K_M, de 398 Mo, moins de la moitié des 988 Mo en BF16. Q4_K_M est un format de quantification 4 bits par groupes défini par llama.cpp, la même idée que le « 4 bits, par groupes » écrit plus haut, en plus raffiné.

Un même modèle propose généralement plusieurs versions quantifiées, par exemple qwen2.5:0.5b-instruct-q8_0 (8 bits) ou qwen2.5:0.5b-instruct-fp16 (16 bits, non quantifié). Choisissez la version adaptée d'après l'estimation de mémoire ci-dessus.

Appeler le modèle local par API

Une fois lancé, Ollama fournit sur le port 11434 de votre machine une interface compatible OpenAI. Autrement dit, tout le code écrit dans la première partie passe au modèle local en changeant trois variables d'environnement :

export LLM_BASE_URL=http://localhost:11434/v1
export LLM_API_KEY=ollama        # 本地服务不检查密钥,但 openai 库要求有一个值
export LLM_MODEL=qwen2.5:0.5b

Si la leçon 2 du module 00 a mis adresse, clé et nom du modèle dans des variables d'environnement, c'était pour ce moment.

Gardez toutefois des attentes raisonnables : nous avons vu le modèle 0,5B à la leçon 3, il se trompe sur la géographie de Pékin. Les petits modèles qui tournent confortablement en local sont loin des grands modèles comme DeepSeek sur les tâches complexes. Le jeu d'évaluation du module 06 est de nouveau utile ici : faites-le passer après le changement de modèle, pour voir quelles tâches il assure encore.

Utiliser votre propre modèle fine-tuné

Ollama peut aussi exécuter vos propres modèles. Il importe les modèles au format Hugging Face (safetensors) et au format GGUF, au moyen d'un fichier de configuration appelé Modelfile qui pointe vers le fichier du modèle avec FROM, suivi d'un ollama create.

Pour le RepoBot fine-tuné à la leçon 3, on peut d'abord fusionner LoRA dans le modèle d'origine (merge_and_unload), enregistrer, puis importer selon la documentation officielle. Celle-ci précise expressément qu'à l'import d'un modèle GGUF, Ollama ne quantifie pas pour vous : il faut avoir quantifié au préalable avec les outils de llama.cpp. Pour les étapes exactes, référez-vous à la documentation d'import d'Ollama ; cette partie évolue assez vite.

Exercices

  1. Vérifiez la mémoire de votre ordinateur (ou la mémoire de votre carte graphique) : d'après le tableau d'estimation de cette leçon, quel est le plus gros modèle que vous pouvez faire tourner ? Combien en 4 bits et en 8 bits ?
  2. Dans quantize.py, essayez 3 bits avec des groupes de 32, ainsi que 4 bits avec des groupes de 8 et de 128, et dressez un tableau des tailles et des pertes de validation.
  3. Installez Ollama, exécutez un petit modèle, et faites appeler celui-ci par RepoBot v1 du module 03 (en ne changeant que les variables d'environnement) pour voir s'il fonctionne normalement.

Auto-test

1. Comment estimer la mémoire occupée par un modèle de 7B paramètres en BF16 ?

Nombre de paramètres multiplié par les octets par paramètre : 7 milliards × 2 octets = 14 milliards d'octets, soit environ 13 Go. En ajoutant le cache KV et les autres frais, avec vingt pour cent de marge, environ 16 Go.

2. En quantification 4 bits, pourquoi « un facteur d'échelle par groupe de 32 » fait-il mieux qu'« un facteur par ligne » ?

Le facteur d'échelle est déterminé par le nombre de plus grande valeur absolue du groupe. Une ligne contient quelques centaines de nombres ; il suffit que l'un soit particulièrement grand pour gonfler le facteur de toute la ligne, et les autres nombres se tassent sur très peu d'entiers, avec une grosse erreur. En petits groupes, chaque facteur colle mieux à ses nombres, l'erreur est bien plus faible, et le prix n'est que le stockage de quelques facteurs d'échelle de plus.

3. Pourquoi le code de la première partie ne demande-t-il presque aucune modification pour utiliser le modèle local d'Ollama ?

Ollama fournit une interface compatible OpenAI, et le code de la première partie utilise la bibliothèque openai en plaçant adresse, clé et nom du modèle dans des variables d'environnement. Il suffit de remplacer ces trois variables par l'adresse et le nom de modèle d'Ollama pour que le code appelle directement le modèle local.

Questions et discussion

Bloqué sur cette leçon ? Posez votre question ici. Et si vous pouvez répondre à quelqu'un, n'hésitez pas.

Une question rapporte 3 points, une réponse 6. Les messages paraissent après vérification.

Chargement de la discussion…