Présentation du projet
AirLLM est un outil Python destiné à l’inférence des grands modèles de langage. Son idée centrale n’est pas de charger l’intégralité du modèle d’un seul coup dans la mémoire vidéo, mais de le découper en couches plus fines et de ne charger les parties nécessaires qu’au moment de l’inférence. Ainsi, dans des environnements à très faible mémoire vidéo, nous pouvons tenter d’exécuter 70B, 235B, 671B, voire des modèles MoE encore plus impressionnants comme Kimi K3.
Kimi K3 suscite justement beaucoup d’attention récemment, pour une raison très simple : il s’agit d’un modèle MoE de très grande ampleur. Le volume de paramètres est énorme, mais en pratique, chaque token n’est routé que vers une partie des experts. AirLLM exploite précisément cela, en proposant un chargement en flux au niveau des experts, afin d’éviter d’avoir à mettre l’intégralité du modèle en mémoire vidéo à la fois.
Dans cet article, construisons ensemble un projet minimal : d’abord faire fonctionner l’inférence de Kimi K3, puis l’englober dans une simple API HTTP. Enfin, nous verrons aussi dans quels cas il est pertinent d’effectuer une inférence locale à faible mémoire, et dans quels cas utiliser directement Defapi—des interfaces compatibles—est plus simple et plus pratique.
Difficulté : intermédiaire | Durée : 30-60 minutes | Résultat : comprendre le flux d’inférence à faible mémoire d’AirLLM, et faire tourner un service minimal d’inférence Kimi K3
Public cible
- Développeurs qui veulent expérimenter de très grands modèles sur une seule carte et des machines à faible mémoire vidéo
- Ingénieurs qui cherchent à évaluer le coût d’inférence de modèles comme Kimi K3, DeepSeek-V3 et Qwen3-235B
- Personnes qui développent des prototypes d’applications IA et souhaitent valider d’abord l’effet du modèle en local
- Développeurs qui doivent encapsuler un modèle local en API, puis l’intégrer à un Agent, à un système RAG ou à un système métier
Dépendances clés et environnement
Kimi K3 est assez exigeant en matière d’environnement. Clarifions d’abord les limites des dépendances :
| Dépendance | Version recommandée | Description |
|---|---|---|
| Python | 3.10+ | Recommandé d’isoler les dépendances via un environnement virtuel |
| NVIDIA Driver | Compatible avec CUDA 12 | Doit permettre l’exécution correcte de PyTorch construit pour CUDA 12 |
| PyTorch | Version construite pour CUDA 12 | La chaîne de dépendances de Kimi K3 est plus adaptée à CUDA 12 |
| transformers | 4.56.x | Le code de modèle distant de Kimi K3 a des exigences de version |
| airllm | Dernière version | Utilise AutoModel pour charger le modèle de manière unifiée |
| compressed-tensors | Dernière version | Le format de poids associé à Kimi K3 sera utilisé |
| flash-attn | Compatible avec CUDA 12 | Le code du modèle Kimi K3 exige généralement flash attention |
WARNING
AirLLM peut réduire l’occupation de la mémoire vidéo, mais ne rend pas les fichiers du modèle plus petits. À la première exécution, le modèle sera téléchargé puis découpé. L’espace disque et la qualité du réseau restent donc très importants. Les modèles comme Kimi K3 sont gigantesques : préparez d’abord suffisamment d’espace pour le cache.
Arborescence complète du projet
Préparons d’abord un projet minimal, en gardant une structure simple :
airllm-kimi-k3-demo/
├── .env.example
├── requirements.txt
├── run_kimi_k3.py
├── server.py
└── README.md
1. Créer un environnement Python virtuel
Commencez par créer le répertoire et l’environnement virtuel :
mkdir airllm-kimi-k3-demo
cd airllm-kimi-k3-demo
python -m venv .venv
source .venv/bin/activate
Windows PowerShell peut activer l’environnement comme suit :
mkdir airllm-kimi-k3-demo
cd airllm-kimi-k3-demo
python -m venv .venv
.venv\Scripts\Activate.ps1
Vérifiez la version de Python :
python --version
La sortie recommandée est au minimum Python 3.10.x. Si plusieurs versions de Python existent sur votre machine, vous pouvez préciser : py -3.10 -m venv .venv.
2. Installer AirLLM et les dépendances de Kimi K3
Créez requirements.txt :
airllm
accelerate
sentencepiece
safetensors
compressed-tensors
python-dotenv
fastapi
uvicorn[standard]
Installez d’abord PyTorch. En prenant CUDA 12.1 comme exemple :
pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
Puis installez les dépendances principales :
pip install -r requirements.txt
pip install "transformers>=4.56,<4.57"
Enfin, installez flash-attn :
pip install flash-attn --no-build-isolation
TIP
Si la compilation de flash-attn est très lente, commencez par vérifier que import torch fonctionne correctement dans l’environnement actuel, et que torch.version.cuda indique bien la série CUDA 12. Les dépendances de modèles comme Kimi K3 sont davantage orientées environnement de développement : évitez de l’installer directement dans un environnement Python global “en désordre”.
3. Configurer les autorisations du modèle et le répertoire de cache
Certains modèles Hugging Face nécessitent une connexion ou une autorisation. Préparons un .env.example :
HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
HF_HOME=/data/huggingface
AIRLLM_MODEL_ID=moonshotai/Kimi-K3
Lors de l’utilisation réelle, copiez-en une copie :
cp .env.example .env
Windows PowerShell :
Copy-Item .env.example .env
Ensuite, renseignez votre propre token Hugging Face. L’ID du modèle doit correspondre au nom du dépôt effectivement publié officiellement. Si vous utilisez un dépôt miroir ou une synchronisation interne, vous pouvez également le remplacer par le repo id correspondant.
WARNING
Ne committez pas .env dans le dépôt Git. Les tokens de modèle sont des informations sensibles : en cas de fuite, ils peuvent être utilisés de manière abusive pour exploiter les droits de modèles privés.
4. Écrire le script minimal d’inférence Kimi K3
Créez run_kimi_k3.py :
import os
from dotenv import load_dotenv
from airllm import AutoModel
def main() -> None:
"""Charge Kimi K3 et exécute une génération minimale de texte."""
load_dotenv()
model_id = os.getenv("AIRLLM_MODEL_ID", "moonshotai/Kimi-K3")
hf_token = os.getenv("HF_TOKEN")
# Contrôlez la longueur d’entrée : utilisez d’abord un prompt court pour valider le pipeline,
# afin d’éviter d’attendre trop longtemps lors de la première vérification.
max_length = 256
# AutoModel détecte automatiquement le type de modèle ; hf_token sert à accéder aux dépôts nécessitant une autorisation.
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=True,
)
prompt = "Explique AirLLM en trois phrases : pourquoi cela réduit la mémoire vidéo nécessaire à l’inférence d’un grand modèle."
# Le tokenizer reste au format Hugging Face : la courbe d’apprentissage est relativement faible.
input_tokens = model.tokenizer(
[prompt],
return_tensors="pt",
return_attention_mask=False,
truncation=True,
max_length=max_length,
padding=False,
)
# Pendant l’inférence, AirLLM charge les poids par couches ; fixez max_new_tokens à une valeur plus petite pour valider rapidement.
generation_output = model.generate(
input_tokens["input_ids"].cuda(),
max_new_tokens=128,
use_cache=True,
return_dict_in_generate=True,
)
output = model.tokenizer.decode(
generation_output.sequences[0],
skip_special_tokens=True,
)
print(output)
if __name__ == "__main__":
main()
Exécution :
python run_kimi_k3.py
La première exécution est généralement plus lente, car AirLLM doit télécharger le modèle, analyser le fichier de poids et le découper pour un chargement ultérieur possible par couches. Vous verrez que la deuxième exécution sera nettement plus fluide : le cache du modèle et les résultats de découpage sont déjà prêts.
5. Observer la mémoire vidéo et l’espace disque
Ouvrez un autre terminal pour surveiller la mémoire vidéo :
nvidia-smi -l 1
Vous surveillez deux indicateurs :
Memory-Usage
GPU-Util
Si tout se passe bien, la mémoire vidéo ne sera pas instantanément saturée comme avec un traditionnel transformers.from_pretrained(). L’approche d’AirLLM ressemble davantage à un « aller chercher au fur et à mesure » : vous chargez uniquement la couche, et la partie d’experts, dont vous avez besoin ; une fois utilisées, elles sont libérées.
Ensuite, observez le disque :
du -sh "$HF_HOME"
Sous Windows PowerShell, vous pouvez utiliser :
Get-ChildItem $env:HF_HOME -Recurse | Measure-Object Length -Sum
TIP
L’inférence à faible mémoire vidéo n’est pas un repas gratuit. AirLLM déplace la charge de la mémoire vidéo vers l’IO disque, la mémoire CPU et le cache du modèle : la vitesse du SSD influence directement l’expérience.
6. Activer la compression des poids
AirLLM supporte la compression via le paramètre compression pour réduire la taille chargée des poids. Nous pouvons ajouter 4bit lors de l’initialisation du modèle :
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
compression="4bit", # Compresse les poids en 4bit pour réduire la taille de chargement et améliorer la vitesse dans certains cas
profiling_mode=True,
)
Si vous voulez être plus prudent, testez d’abord 8bit :
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
compression="8bit", # 8bit est plus prudent ; meilleur équilibre entre vitesse et précision
profiling_mode=True,
)
En général, nous choisissons comme suit :
| Scénario | Recommandation |
|---|---|
| Tester si Kimi K3 peut se lancer | Ne pas activer la compression, valider d’abord le pipeline |
| L’IO disque ralentit nettement | Essayer 8bit |
| Priorité à la vitesse et à la validation à faible coût | Essayer 4bit |
| Faire des évaluations sérieuses | Fixer les paramètres et comparer sur plusieurs essais |
WARNING
La compression modifie la manière de charger les poids. Même si la plupart des tâches d’inférence sont peu impactées, si vous faites une évaluation de modèles, une inférence de longs textes ou une validation métier sensible, conservez absolument une version non compressée comme référence.
7. Encapsuler le tout dans une simple API HTTP
Une fois le script en local validé, on souhaite généralement l’intégrer à une application. Voici un service minimal avec FastAPI.
Créez server.py :
import os
from typing import List, Literal
from dotenv import load_dotenv
from fastapi import FastAPI
from pydantic import BaseModel
from airllm import AutoModel
class ChatMessage(BaseModel):
"""Structure de messages style OpenAI, pratique pour remplacer ensuite par une API compatible."""
role: Literal["system", "user", "assistant"]
content: str
class ChatRequest(BaseModel):
"""Requête de chat minimale : ne conserve que les champs nécessaires au tutoriel."""
messages: List[ChatMessage]
max_tokens: int = 256
class ChatResponse(BaseModel):
"""Résultat texte renvoyé ; reste volontairement simple pour faciliter le débogage."""
text: str
load_dotenv()
app = FastAPI(title="AirLLM Kimi K3 Demo")
model_id = os.getenv("AIRLLM_MODEL_ID", "moonshotai/Kimi-K3")
hf_token = os.getenv("HF_TOKEN")
# Charger le modèle au démarrage du service ; en production, vous pouvez ajouter une vérification de santé et un chargement paresseux.
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=False,
)
def build_prompt(messages: List[ChatMessage]) -> str:
"""Fusionne les messages multi-tours en un prompt simple ; remplaçable ensuite par le modèle officiel de chat template."""
lines: list[str] = []
for message in messages:
lines.append(f"{message.role}: {message.content}")
lines.append("assistant:")
return "\n".join(lines)
@app.post("/chat", response_model=ChatResponse)
def chat(request: ChatRequest) -> ChatResponse:
"""Exécute une génération de texte avec Kimi K3."""
prompt = build_prompt(request.messages)
input_tokens = model.tokenizer(
[prompt],
return_tensors="pt",
return_attention_mask=False,
truncation=True,
max_length=1024,
padding=False,
)
generation_output = model.generate(
input_tokens["input_ids"].cuda(),
max_new_tokens=request.max_tokens,
use_cache=True,
return_dict_in_generate=True,
)
text = model.tokenizer.decode(
generation_output.sequences[0],
skip_special_tokens=True,
)
return ChatResponse(text=text)
Démarrage du service :
uvicorn server:app --host 0.0.0.0 --port 8000
Test d’appel :
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "Explique en chinois pourquoi les modèles MoE comme Kimi K3 se prêtent au chargement en flux par experts."
}
],
"max_tokens": 200
}'
Nous avons maintenant une API minimale. Elle n’est pas encore prête pour la production, mais elle suffit pour les expériences en local, les prototypes d’Agent, le debug des prompts et la comparaison des performances des modèles.
8. Inférence locale vs interface Defapi : comment choisir
La valeur d’AirLLM est très claire : il nous permet de toucher à des modèles gigantesques avec des machines à faible mémoire vidéo, ce qui est particulièrement adapté à l’apprentissage, à la validation, à l’évaluation et aux expériences hors ligne. En production, cependant, il faut aussi tenir compte du débit, de la latence, du parallélisme, des mises à jour du modèle, de la tolérance aux pannes et des coûts d’exploitation.
Si vous voulez simplement intégrer rapidement les capacités du modèle à votre application, ou si vous avez besoin d’une API unifiée au style OpenAI/Anthropic/Gemini, Defapi est plus intéressant. Son avantage principal est que le prix est généralement environ moitié de celui de l’officiel, et que les modèles courants sont globalement compatibles avec ces protocoles :
v1/chat/completionsv1/messagesv1beta/models/
Par exemple, nous pouvons abstraire l’appel local /chat ci-dessus en un client unifié. En phase de développement, on utilise AirLLM en local ; en environnement en ligne, on bascule sur Defapi :
import os
import requests
def ask_model(prompt: str) -> str:
"""Choisit l’inférence locale ou l’interface compatible Defapi selon les variables d’environnement."""
provider = os.getenv("MODEL_PROVIDER", "local")
if provider == "defapi":
response = requests.post(
"https://api.defapi.org/api/v1/chat/completions",
headers={
"Authorization": f"Bearer {os.environ['DEFAPI_API_KEY']}",
"Content-Type": "application/json",
},
json={
"model": os.getenv("DEFAPI_MODEL", "anthropic/claude-sonnet-4.5"),
"messages": [{"role": "user", "content": prompt}],
"temperature": 0.7,
},
timeout=60,
)
response.raise_for_status()
return response.json()["choices"][0]["message"]["content"]
response = requests.post(
"http://localhost:8000/chat",
json={
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 256,
},
timeout=300,
)
response.raise_for_status()
return response.json()["text"]
Le bénéfice de ce type d’écriture est que la frontière est très claire : le modèle local se charge de l’exploration et de la validation ; Defapi se charge d’une intégration stable, unifiée et peu coûteuse du modèle en ligne. Pour OpenClaw, les services RAG et les Agents automatisés, ce double mode est très utile.
Dépannage des problèmes fréquents
1. Que faire si l’installation de flash-attn échoue ?
Vérifiez d’abord que PyTorch reconnaît bien CUDA :
python -c "import torch; print(torch.__version__); print(torch.version.cuda); print(torch.cuda.is_available())"
Si torch.cuda.is_available() est False, ne précipitez pas l’installation de flash-attn. Il faut d’abord corriger PyTorch, le driver et la version CUDA. Pour Kimi K3, il est recommandé d’utiliser une construction CUDA 12 afin d’éviter que l’absence de paquets précompilés pour CUDA 13 ne provoque un échec de compilation.
2. Que faire si transformers renvoie une erreur de version ?
Fixez la version à 4.56.x :
pip uninstall -y transformers
pip install "transformers>=4.56,<4.57"
Puis relancez :
python run_kimi_k3.py
Si vous voyez un échec de chargement du code distant du modèle, c’est généralement un problème de non-concordance entre la version de transformers et le code du modèle.
3. Que faire si le téléchargement de Hugging Face échoue ?
Vérifiez d’abord si le token fonctionne :
huggingface-cli whoami
Si vous n’êtes pas connecté :
huggingface-cli login
Vous pouvez aussi n’utiliser que le HF_TOKEN dans le fichier .env. Si le modèle nécessite une demande d’accès, il faut d’abord accepter le protocole sur la page du modèle.
4. Que faire si l’espace disque est insuffisant ?
À la première exécution, AirLLM télécharge les poids originaux et génère aussi des fichiers de modèle découpés. Vous pouvez placer le répertoire de cache sur un disque plus grand :
export HF_HOME=/mnt/models/huggingface
Ou configurez-le dans .env :
HF_HOME=/mnt/models/huggingface
Si le disque reste trop chargé, vous pouvez essayer delete_original=True d’AirLLM : ne conserver que les fichiers convertis :
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
delete_original=True, # Supprime les poids d’origine après conversion pour économiser l’espace disque
)
5. Pourquoi la première exécution est-elle particulièrement lente ?
C’est un comportement normal. La première exécution comprend le téléchargement des poids, la lecture des index, le découpage du modèle, et l’écriture du cache, etc. La deuxième exécution réutilise le cache, donc elle sera beaucoup plus rapide.
Vous pouvez activer le profiling :
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=True, # Affiche le temps de chargement et d’inférence pour aider à localiser les goulots d’étranglement
)
Si la majorité du temps est passée dans la lecture disque, remplacer par un SSD NVMe plus rapide est souvent plus efficace que d’augmenter la capacité de la mémoire vidéo.
6. La mémoire vidéo ne baisse pas comme prévu : que faire ?
Vérifiez d’abord que le code n’utilise pas un mode de chargement traditionnel en parallèle :
from airllm import AutoModel
model = AutoModel.from_pretrained(model_id)
N’utilisez pas en parallèle le mode traditionnel :
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(model_id)
Le second emprunte le chemin de chargement traditionnel, ce qui remplit facilement directement la mémoire vidéo.
7. Le service API se bloque quand on augmente le parallélisme : que faire ?
L’inférence locale à faible mémoire vidéo convient mieux aux expériences séquentielles : elle n’est pas adaptée à recevoir directement un grand volume de requêtes en parallèle. Le moyen le plus simple est de limiter le parallélisme :
uvicorn server:app --host 0.0.0.0 --port 8000 --workers 1
Si vous avez besoin d’appels plus stables en production, faites plutôt basculer les requêtes en ligne vers Defapi : gardez AirLLM local pour l’évaluation et le debug.
Lecture complémentaire / Pistes d’approfondissement
- Comparer le temps de chargement, la mémoire vidéo et la qualité de sortie de Kimi K3, DeepSeek-V3 et Qwen3-235B sous AirLLM.
- Modifier le service FastAPI pour fournir une API compatible
/v1/chat/completions. - Intégrer Defapi et utiliser
v1/chat/completionsouv1/messagespour gérer les appels aux modèles en ligne de manière unifiée. - Ajouter une file d’attente au service AirLLM pour éviter que plusieurs requêtes ne se disputent simultanément l’IO disque et le GPU.
- Dans OpenClaw ou d’autres frameworks d’Agent, configurer des modèles locaux et des modèles cloud, afin de choisir dynamiquement selon la complexité de la tâche.