AirLLM : faire l’expérience du modèle Kimi K3 avec seulement 4 Go de mémoire vidéo

August 3, 2026

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épendanceVersion recommandéeDescription
Python3.10+Recommandé d’isoler les dépendances via un environnement virtuel
NVIDIA DriverCompatible avec CUDA 12Doit permettre l’exécution correcte de PyTorch construit pour CUDA 12
PyTorchVersion construite pour CUDA 12La chaîne de dépendances de Kimi K3 est plus adaptée à CUDA 12
transformers4.56.xLe code de modèle distant de Kimi K3 a des exigences de version
airllmDernière versionUtilise AutoModel pour charger le modèle de manière unifiée
compressed-tensorsDernière versionLe format de poids associé à Kimi K3 sera utilisé
flash-attnCompatible avec CUDA 12Le 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énarioRecommandation
Tester si Kimi K3 peut se lancerNe pas activer la compression, valider d’abord le pipeline
L’IO disque ralentit nettementEssayer 8bit
Priorité à la vitesse et à la validation à faible coûtEssayer 4bit
Faire des évaluations sérieusesFixer 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/completions
  • v1/messages
  • v1beta/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/completions ou v1/messages pour 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.
Updated August 3, 2026