Projektvorstellung
AirLLM ist ein Python-Tool für das Inferenz-Tuning großer Sprachmodelle. Die Kernidee lautet: Man lädt nicht das komplette Modell auf einmal in den VRAM, sondern zerlegt es in feinere Ebenen und lädt es beim Inferenzbedarf nach Bedarf nach. So können wir 70B, 235B, 671B – sogar noch exzentrischere MoE-Modelle wie Kimi K3 – auch in sehr knappen VRAM-Umgebungen ausprobieren.
Kimi K3 steht derzeit besonders im Fokus, und zwar aus einem sehr einfachen Grund: Es handelt sich um ein extrem großes MoE-Modell. Die Parametermenge ist enorm, aber tatsächlich wird jeder Token nur an einen Teil der Experten geroutet. Genau diese Eigenschaft macht AirLLM sich zunutze: Durch ein expertenbasiertes, gestreamtes (schrittweises) Laden wird vermieden, dass das vollständige Modell auf einmal in den VRAM muss.
In diesem Artikel bauen wir gemeinsam ein kleines Minimalprojekt: Erst führen wir die Inferenz von Kimi K3 einmal durch, danach verpacken wir es in eine einfache HTTP-API. Zum Schluss besprechen wir noch, wann sich lokale Inferenz mit wenig VRAM eignet und wann es einfacher ist, direkt Defapi – also eine kompatible Schnittstelle – zu verwenden.
Schwierigkeit: Mittel | Dauer: 30–60 Minuten | Ergebnis: Den Inferenz-Workflow von AirLLM mit wenig VRAM verstehen und einen minimalen Kimi-K3-Inferenzdienst zum Laufen bringen
Zielgruppe
- Entwickler, die auf einer einzelnen Grafikkarte bzw. auf einem System mit wenig VRAM ein extremes Modell erleben möchten
- Ingenieure, die die Inferenzkosten von Modellen wie Kimi K3, DeepSeek-V3 oder Qwen3-235B einschätzen wollen
- Teams, die an Prototypen für KI-Anwendungen arbeiten und zuerst lokal prüfen möchten, wie gut das Modell funktioniert
- Entwickler, die lokale Modelle als API verpacken müssen und sie anschließend an Agenten, RAG oder Business-Systeme anbinden wollen
Wichtige Abhängigkeiten und Umgebung
Kimi K3 stellt recht konkrete Anforderungen an die Umgebung. Wir ziehen zunächst die Abgrenzung der Abhängigkeiten sauber:
| Abhängigkeit | Empfohlene Version | Hinweis |
|---|---|---|
| Python | 3.10+ | Empfehlung: Abhängigkeiten in einer virtuellen Umgebung isolieren |
| NVIDIA-Treiber | unterstützt CUDA 12 | Muss einen PyTorch-Stand ermöglichen, der CUDA 12 korrekt unterstützt |
| PyTorch | CUDA-12-Build | Die Abhängigkeitskette von Kimi K3 passt besonders gut zu CUDA 12 |
| transformers | 4.56.x | Für den Remote-Modellcode von Kimi K3 gibt es Versionsanforderungen |
| airllm | neueste Version | Nutzt AutoModel, um das Modell einheitlich zu laden |
| compressed-tensors | neueste Version | Benötigt für Kimi-K3-relevante Gewichtsformate |
| flash-attn | kompatibel mit CUDA 12 | Der Code des Kimi-K3-Modells verlangt in der Regel flash attention |
WARNING
AirLLM kann den VRAM-Verbrauch reduzieren, aber es macht die Modell-Dateien nicht kleiner. Beim ersten Start werden das Modell heruntergeladen und aufgeteilt; deshalb sind Speicherplatz auf der Platte und Netzwerkqualität weiterhin entscheidend. Kimi-K3-Modelle dieser Größenordnung sind riesig – bereite daher unbedingt ausreichend Speicherplatz für den Modell-Cache vor.
Vollständige Projektstruktur
Wir starten mit einem Minimalprojekt, bei dem die Struktur bewusst überschaubar bleibt:
airllm-kimi-k3-demo/
├── .env.example
├── requirements.txt
├── run_kimi_k3.py
├── server.py
└── README.md
1. Virtuelle Python-Umgebung erstellen
Zuerst legen wir das Verzeichnis und die virtuelle Umgebung an:
mkdir airllm-kimi-k3-demo
cd airllm-kimi-k3-demo
python -m venv .venv
source .venv/bin/activate
Windows PowerShell aktiviert man so:
mkdir airllm-kimi-k3-demo
cd airllm-kimi-k3-demo
python -m venv .venv
.venv\Scripts\Activate.ps1
Python-Version prüfen:
python --version
Empfohlen ist mindestens Python 3.10.x. Falls mehrere Python-Versionen auf der Maschine vorhanden sind, kann man mit py -3.10 -m venv .venv explizit festlegen.
2. AirLLM und Kimi-K3-Abhängigkeiten installieren
Neue Datei requirements.txt anlegen:
airllm
accelerate
sentencepiece
safetensors
compressed-tensors
python-dotenv
fastapi
uvicorn[standard]
Zuerst PyTorch installieren. Beispiel für CUDA 12.1:
pip install --upgrade pip
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
Dann die Kernabhängigkeiten:
pip install -r requirements.txt
pip install "transformers>=4.56,<4.57"
Zum Schluss flash-attn installieren:
pip install flash-attn --no-build-isolation
TIP
Wenn flash-attn extrem langsam kompiliert, prüfe zuerst, dass import torch im aktuellen Environment korrekt funktioniert – und dass torch.version.cuda zur CUDA-12-Serie gehört. Abhängigkeiten wie bei Kimi K3 sind eher ein Engineer-Umfeld: Installiere es nicht einfach direkt in einer chaotischen globalen Python-Umgebung.
3. Modellberechtigungen und Cache-Verzeichnis konfigurieren
Manche Hugging-Face-Modelle benötigen Login oder Freigaben. Wir bereiten ein .env.example vor:
HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
HF_HOME=/data/huggingface
AIRLLM_MODEL_ID=moonshotai/Kimi-K3
Beim eigentlichen Einsatz kopieren wir eine Kopie:
cp .env.example .env
Windows PowerShell:
Copy-Item .env.example .env
Dann trägst du dein eigenes Hugging-Face-Token ein. Die Modell-ID sollte dem offiziellen, tatsächlich veröffentlichten Repository-Namen entsprechen. Wenn du statt des offiziellen Repos ein Mirror-Repo oder ein intern synchronisiertes Repo verwendest, kannst du es entsprechend in die passende Repo-ID ändern.
WARNING
.env nicht in ein Git-Repository einchecken. Modell-Tokens sind sensible Informationen; bei einem Leak können private Modellberechtigungen missbraucht werden.
4. Minimalen Kimi-K3-Inferenz-Wrapper schreiben
Neue Datei run_kimi_k3.py:
import os
from dotenv import load_dotenv
from airllm import AutoModel
def main() -> None:
"""Kimi K3 laden und einmal eine minimale Textgenerierung ausführen."""
load_dotenv()
model_id = os.getenv("AIRLLM_MODEL_ID", "moonshotai/Kimi-K3")
hf_token = os.getenv("HF_TOKEN")
# Steuere die Eingabelänge: erst mit kurzem Prompt testen, damit du bei der ersten Validierung nicht zu lange warten musst.
max_length = 256
# AutoModel erkennt den Modelltyp automatisch; hf_token wird für den Zugriff auf benötigte, autorisierte Modell-Repositories verwendet.
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=True,
)
prompt = "Erkläre in drei Sätzen, warum AirLLM den VRAM für die Inferenz großer Modelle reduzieren kann."
# Der Tokenizer bleibt im Hugging-Face-Stil; die Umstellung ist relativ gering.
input_tokens = model.tokenizer(
[prompt],
return_tensors="pt",
return_attention_mask=False,
truncation=True,
max_length=max_length,
padding=False,
)
# Bei AirLLM werden die Gewichte während der Inferenz schichtweise geladen; max_new_tokens setzen wir zuerst kleiner, um die Validierung zu beschleunigen.
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()
Ausführen:
python run_kimi_k3.py
Beim ersten Lauf dauert es in der Regel länger, weil AirLLM das Modell herunterladen, die Gewichtungsdateien analysieren und das Modell in ein Format zerlegen muss, das danach schichtweise geladen werden kann. Beim zweiten Lauf läuft es deutlich flüssiger: Modell-Cache und Aufspaltung sind dann bereits vorbereitet.
5. VRAM- und Plattenverbrauch beobachten
Öffne ein weiteres Terminal, um den VRAM zu beobachten:
nvidia-smi -l 1
Achte auf zwei Kennzahlen:
Memory-Usage
GPU-Util
Wenn alles korrekt läuft, wird der VRAM nicht so abrupt „explodieren“ wie bei dem klassischen transformers.from_pretrained(). AirLLM macht eher „hol dir was du brauchst, während du gehst“: Welche Schicht bzw. welcher Teil der Experten benötigt wird, die entsprechenden Gewichte werden geladen – danach wieder freigegeben.
Dann beobachte die Platte:
du -sh "$HF_HOME"
Windows PowerShell kann so aussehen:
Get-ChildItem $env:HF_HOME -Recurse | Measure-Object Length -Sum
TIP
Inferenz mit wenig VRAM ist kein kostenloses Mittagessen. AirLLM verlagert die Last von VRAM auf Platten-IO, CPU-RAM und Modell-Cache – daher wirkt sich die SSD-Geschwindigkeit direkt auf dein Erlebnis aus.
6. Komprimierte Parameter aktivieren
AirLLM unterstützt das Reduzieren des geladenen Gewichtsvolumens über den Parameter compression. Wir können beim Initialisieren 4bit hinzufügen:
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
compression="4bit", # 4bit-Gewichtskompression nutzen: reduziert das Ladevolumen und verbessert in manchen Szenarien die Geschwindigkeit
profiling_mode=True,
)
Wenn du es konservativer willst, probiere zuerst 8bit:
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
compression="8bit", # 8bit ist konservativer: ein besseres Gleichgewicht zwischen Geschwindigkeit und Genauigkeit
profiling_mode=True,
)
Typischerweise wählen wir so:
| Szenario | Empfehlung |
|---|---|
| Nur testen, ob Kimi K3 überhaupt läuft | Keine Kompression einschalten, erst den Ablauf verifizieren |
| Platten-IO bremst deutlich | 8bit testen |
| Fokus auf Geschwindigkeit und Low-Cost-Validierung | 4bit testen |
| Für ernsthafte Evaluierung | Parameter fixieren und Ausgaben über mehrere Runden vergleichen |
WARNING
Kompression verändert die Art und Weise, wie Gewichte geladen werden. In vielen Inferenzaufgaben hat das nur wenig Einfluss – aber wenn du Modelle evaluierst, lange Textinferenz machst oder kritische Business-Validierungen durchführst, solltest du unbedingt eine unkomprimierte Version als Referenz behalten.
7. In eine einfache HTTP-API verpacken
Nachdem das lokale Skript einmal durchläuft, möchten wir es normalerweise in eine Anwendung integrieren. Unten bauen wir mit FastAPI einen Minimalservice.
Neue Datei 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):
"""Nach dem OpenAI-Nachrichtenformat, damit es später leicht durch eine kompatible API ersetzt werden kann."""
role: Literal["system", "user", "assistant"]
content: str
class ChatRequest(BaseModel):
"""Minimaler Chat-Request: nur die Felder beibehalten, die das Tutorial benötigt."""
messages: List[ChatMessage]
max_tokens: int = 256
class ChatResponse(BaseModel):
"""Antwort mit Text: zuerst bewusst einfach halten, damit das Debuggen leichter ist."""
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")
# Beim Start des Services wird das Modell geladen; in einem echten Production-Setup kann man Health-Checks und Lazy Loading ergänzen.
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=False,
)
def build_prompt(messages: List[ChatMessage]) -> str:
"""Mehrere Nachrichten zu einem einfachen Prompt zusammenbauen; später austauschbar gegen das offizielle Chat-Template des Modells."""
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:
"""Führt einmal eine Kimi-K3-Textgenerierung aus."""
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)
Service starten:
uvicorn server:app --host 0.0.0.0 --port 8000
Test-Aufruf:
curl -X POST http://localhost:8000/chat \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "user",
"content": "Erkläre auf Chinesisch, warum MoE-Modelle wie Kimi K3 sich für expertenbasiertes, gestreamtes Laden eignen."
}
],
"max_tokens": 200
}'
Jetzt haben wir eine minimale API. Das ist noch kein Production-Level-Service, aber für lokale Experimente, Agent-Prototypen, Prompt-Debugging und den Vergleich der Modellwirkung reicht es.
8. Lokale Inferenz vs. Defapi: Wie trifft man die Entscheidung?
Der Mehrwert von AirLLM ist sehr klar: Es ermöglicht uns, mit einem Modell mit wenig VRAM an sehr große Modelle heranzukommen – besonders geeignet zum Lernen, Validieren, Evaluieren und für Offline-Experimente. Für ein Production-Setup muss man jedoch zusätzlich Faktoren wie Durchsatz, Latenz, Parallelität, Modell-Updates, Ausfallsicherheit und Betriebskosten berücksichtigen.
Wenn du das Modell nur schnell in eine Anwendung integrieren willst oder eine einheitliche API im Stil von OpenAI/Anthropic/Gemini benötigst, ist Defapi die einfachere Wahl. Der Vorteil liegt darin, dass die Preise häufig nur etwa halb so hoch sind wie bei den offiziellen Anbietern – und dass viele gängige Modelle diese Protokolle grundsätzlich kompatibel abdecken:
v1/chat/completionsv1/messagesv1beta/models/
Zum Beispiel können wir den lokalen /chat-Aufruf oben in einen einheitlichen Client abstrahieren. In der Entwicklungsphase läuft es lokal über AirLLM, in der Produktion schaltet man auf Defapi um:
import os
import requests
def ask_model(prompt: str) -> str:
"""Wählt je nach Umgebungsvariablen lokale Inferenz oder eine Defapi-kompatible Schnittstelle."""
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"]
Der Vorteil dieser Art von Implementierung ist die klare Aufgabenverteilung: Das lokale Modell kümmert sich um Exploration und Validierung, Defapi liefert stabil, einheitlich und zu niedrigen Kosten den Zugang zu Online-Modellen. Für OpenClaw, RAG-Services und automatisierte Agenten ist dieses Zweimodus-Setup sehr praktisch.
Häufige Probleme beheben
1. Was tun, wenn flash-attn sich nicht installieren lässt?
Prüfe zuerst, ob PyTorch CUDA korrekt erkennt:
python -c "import torch; print(torch.__version__); print(torch.version.cuda); print(torch.cuda.is_available())"
Wenn torch.cuda.is_available() False zurückgibt, dann ist es zu früh, flash-attn zu installieren. Zuerst sollten PyTorch, Treiber und die CUDA-Version repariert bzw. abgestimmt werden. Für Kimi-K3-Modelle wird empfohlen, einen Build für CUDA 12 zu nutzen, um Probleme durch fehlende passende Prebuild-Pakete bei CUDA 13 zu vermeiden.
2. Wie behebt man Versionsfehler bei transformers?
Fixiere die Version auf 4.56.x:
pip uninstall -y transformers
pip install "transformers>=4.56,<4.57"
Danach erneut ausführen:
python run_kimi_k3.py
Wenn das Laden des Remote-Modellcodes fehlschlägt, liegt das in der Regel daran, dass die transformers-Version nicht zum Modellcode passt.
3. Was tun, wenn das Herunterladen von Hugging Face fehlschlägt?
Prüfe zuerst, ob das Token nutzbar ist:
huggingface-cli whoami
Falls nicht eingeloggt:
huggingface-cli login
Man kann auch nur das HF_TOKEN aus der .env verwenden. Wenn das Modell Zugriffsrechte benötigt, musst du zuerst auf der Modellseite die Bedingungen akzeptieren.
4. Was tun bei zu wenig Speicherplatz?
Beim ersten Start lädt AirLLM die ursprünglichen Gewichte herunter und erstellt außerdem die aufgesplitteten Modell-Dateien. Du kannst den Cache-Ordner auf ein größeres Laufwerk legen:
export HF_HOME=/mnt/models/huggingface
Oder in der .env konfigurieren:
HF_HOME=/mnt/models/huggingface
Wenn der Speicher weiterhin eng wird, kannst du versuchen, AirLLM mit delete_original=True zu nutzen und nur die konvertierten Dateien zu behalten:
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
delete_original=True, # Nach der Konvertierung die Originalgewichte löschen, um Speicherplatz zu sparen
)
5. Warum ist der erste Lauf besonders langsam?
Das ist normal. Beim ersten Start gehören dazu: Herunterladen der Gewichte, Indizes lesen, Modell aufteilen, Cache schreiben usw. Beim zweiten Start wird der Cache wiederverwendet, wodurch es deutlich schneller wird.
Profiling aktivieren:
model = AutoModel.from_pretrained(
model_id,
hf_token=hf_token,
profiling_mode=True, # Ausgabe von Lade- und Inferenzzeiten, um Engpässe zu lokalisieren
)
Wenn der Großteil der Zeit im Platten-Lesezugriff steckt, ist oft ein schnelleres NVMe-SSD effektiver als einfach mehr VRAM zu kaufen.
6. Warum sinkt der VRAM nicht auf die erwarteten Werte?
Zuerst prüfen, ob dein Code keine klassischen Ladewege vermischt:
from airllm import AutoModel
model = AutoModel.from_pretrained(model_id)
Nicht mischen mit dem klassischen Ladeweg:
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(model_id)
Der zweite Pfad nutzt den traditionellen Ladeprozess und führt sehr leicht dazu, dass der VRAM sofort komplett gefüllt wird.
7. Was tun, wenn der API-Service bei mehr Parallelität „hängt“?
Lokale Inferenz mit wenig VRAM eignet sich eher für serielle Experimente und nicht dafür, direkt hohe Parallelität zu bedienen. Die simpelste Methode ist, zuerst die Parallelität zu begrenzen:
uvicorn server:app --host 0.0.0.0 --port 8000 --workers 1
Wenn du stabilere Produktionsaufrufe brauchst, leite die Anfragen stattdessen auf Defapi um. Lokal bleibt AirLLM dann für Evaluierung und Debugging.
Weiterführende Lektüre / Fortgeschrittene Richtungen
- Vergleiche Ladezeit, VRAM-Verbrauch und Ausgabequalität von Kimi K3, DeepSeek-V3 und Qwen3-235B unter AirLLM.
- Wandle den FastAPI-Service in eine OpenAI-kompatible
/v1/chat/completions-Schnittstelle um. - Integriere Defapi und nutze
v1/chat/completionsoderv1/messages, um Online-Modelle einheitlich zu verwalten. - Ergänze für den AirLLM-Service eine Warteschlange, um zu vermeiden, dass mehrere Requests gleichzeitig um Platten-IO und GPU konkurrieren.
- Konfiguriere in OpenClaw oder anderen Agent-Frameworks sowohl lokale Modelle als auch Cloud-Modelle und wähle je nach Aufgabenkomplexität dynamisch aus.