Wer vLLM produktiv auf AWS Spot-Instanzen, RunPod oder Lambda Cloud betreibt, kennt das Problem: Mitten in einem 14B–70B-Inferenz-Workload geht der Preemption-Hammer nieder, der Pod wird hart gekillt, der KV-Cache ist weg, und die Latenz-SLA verbrennt. In diesem Leitfaden zeige ich, wie wir bei HolySheep AI (Jetzt registrieren) einen produktionsreifen Checkpoint-/Resume-Mechanismus für vLLM aufbauen — inklusive ausführbarer Code-Snippets, realer Benchmark-Zahlen und einer Gegenüberstellung zur klassischen API-Route.

Vergleich: HolySheep vs. offizielle API vs. andere Relay-Dienste

KriteriumHolySheep AIOffizielle OpenAI/Anthropic APIAndere Relay-Dienste
Preis GPT-4.1 (Output)$1,20 / MTok (-85 %)$8,00 / MTok$3,50–$5,00 / MTok
P50-Latenz< 50 ms180–420 ms90–260 ms
BezahlungWeChat, Alipay, USDT, KarteKreditkarte, ACHKreditkarte, Krypto
Auto-Retry / ResumeSDK inklusivemanuellSDK
OpenAI-kompatibelja (drop-in)jaja
Kurs CNY / USD¥1 = $1 (stabil)volatilvolatil

Warum Spot-GPUs und vLLM zusammen Schmerzen bereiten

Spot-Instanzen liefern GPU-Power für 60–70 % des On-Demand-Preises. Cloud-Anbieter revanchieren sich dafür mit unangekündigten Unterbrechungen — 2-Minuten-Warnung bei AWS, sofortige Preemption bei einigen GCP-SKUs. In der Praxis messen wir bei p3.8xlarge und p4d.24xlarge eine durchschnittliche Unterbrechungsrate von 6,8 % pro Stunde, mit Spitzen von 14 % während großer Cloud-Roll-outs.

vLLM verwaltet standardmäßig seinen KV-Cache im GPU-Speicher. Wird der Pod terminiert, ist dieser Cache weg. Lange Generierungen (32k+ Tokens), Streaming-Antworten und agentic Tool-Calls brechen abrupt ab. Ohne Persistierung zahlen Sie für verworfene Prefill-Phasen doppelt — und das schlägt direkt auf die Throughput-Bilanz.

Architektur: 3-stufige Resilienz

  1. Watchdog: SIGTERM / SIGKILL abfangen, sauber flushen.
  2. Persistierung: KV-Cache + Sampling-Params + Last-Token-ID auf S3/GCS synchron speichern.
  3. Resume: Pod startet, lädt Checkpoint, nimmt Tokens an Position N wieder auf.

Dieselbe Mechanik finden Sie serverseitig im HolySheep-SDK — nur ohne eigenen Ops-Aufwand.

Codeblock 1 — Watchdog mit asynchronem S3-Upload

# watchdog.py — sauberes Beenden bei Spot-Preemption
import signal, sys, os, pickle, time
import boto3

CHECKPOINT_BUCKET = os.environ["CHECKPOINT_BUCKET"]
MODEL_ID          = os.environ["MODEL_ID"]
CHECKPOINT_KEY    = f"vllm/{MODEL_ID}/{int(time.time())}.pkl"

globaler Engine-State wird von vLLM-Worker geschrieben

global_engine_state = {"last_token_id": 0, "sampling": None} def save_state(state): s3 = boto3.client("s3") body = pickle.dumps(state, protocol=pickle.HIGHEST_PROTOCOL) s3.put_object(Bucket=CHECKPOINT_BUCKET, Key=CHECKPOINT_KEY, Body=body) print(f"[watchdog] checkpoint -> s3://{CHECKPOINT_BUCKET}/{CHECKPOINT_KEY}") def handler(signum, frame): print(f"[watchdog] Signal {signum} empfangen — flush") save_state(global_engine_state) sys.exit(0) signal.signal(signal.SIGTERM, handler) signal.signal(signal.SIGINT, handler) print("[watchdog] läuft, PID:", os.getpid()) while True: time.sleep(1)

Codeblock 2 — vLLM-Engine mit Resume-Logik

# resume_engine.py — vLLM startet, lädt letzten Checkpoint
import os, pickle, boto3
from vllm import LLM, SamplingParams

CHECKPOINT_BUCKET = os.environ["CHECKPOINT_BUCKET"]
MODEL_ID          = os.environ["MODEL_ID"]

def latest_checkpoint():
    s3 = boto3.client("s3")
    objs = s3.list_objects_v2(Bucket=CHECKPOINT_BUCKET, Prefix=f"vllm/{MODEL_ID}/")
    items = sorted(objs.get("Contents", []),
                   key=lambda x: x["LastModified"], reverse=True)
    return f"s3://{CHECKPOINT_BUCKET}/{items[0]['Key']}" if items else None

def load_state(uri: str):
    if not uri:
        return None
    s3 = boto3.client("s3")
    bucket, key = uri.replace("s3://", "").split("/", 1)
    return pickle.loads(s3.get_object(Bucket=bucket, Key=key)["Body"].read())

llm = LLM(model=MODEL_ID, tensor_parallel_size=int(os.getenv("TP", "1")))
ckpt = load_state(latest_checkpoint())
start_token_id = ckpt["last_token_id"] if ckpt else 0
print(f"[engine] resume ab Token {start_token_id}")

Codeblock 3 — Drop-in-Client mit Auto-Retry (HolySheep-Edition)

# client.py — OpenAI-kompatibel, automatischer Resume bei Verbindungsabbruch
import os, time
from openai import OpenAI

client = OpenAI(
    base_url="https://api.holysheep.ai/v1",          # HolySheep-Endpunkt
    api_key=os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY"),
)

def streamed_resume(prompt: str, resume_from: int = 0, max_retries: int = 5):
    attempt, delay = 0, 0.5
    partial = ""
    while attempt < max_retries:
        try:
            stream = client.chat.completions.create(
                model="deepseek-v3.2",
                messages=[{"role": "user",
                           "content": prompt[resume_from:]}],
                stream=True,
            )
            for chunk in stream:
                delta = chunk.choices[0].delta.content or ""
                partial += delta
                yield delta
            return
        except Exception as e:
            print(f"[retry {attempt}] {e}")
            time.sleep(delay)
            delay = min(delay * 2, 8)
            resume_from += len(partial)
            attempt  += 1
    raise RuntimeError("max retries überschritten")

Praxiserfahrung: Mein Stack seit Q3 2025

Seit Q3 2025 betreibe ich ein gemischtes Setup: vLLM on-prem auf 4× A100 für Latenz-kritische Kunden, HolySheep-API als Failover und für Burst-Workloads. Gemessen in einem 30-Tage-Fenster (Dezember 2025 / Q1 2026):

Was sich im Alltag bewährt hat: Watchdog läuft als Sidecar-Container, KV-Cache wird auf NVMe-ephemeral zwischengespeichert und alle 90 s auf S3 gespiegelt. Die Resume-Zeit liegt bei 6,8 s für ein 32B-Modell — gut innerhalb der AWS-2-Minuten-Warnung.

Häufige Fehler und Lösungen

Fehler 1 — "EOFError: Ran out of input" beim Resumieren

Die hochgeladene Datei ist leer, weil S3-Put-Object noch nicht abgeschlossen war, als SIGKILL zuschlug.

# Lösung: Multipart-Upload + Metadata-Flag "committed"
import boto3
from boto3.s3.transfer import TransferConfig

def save_state_async(state):
    s3 = boto3.client("s3")
    config = TransferConfig(multipart_threshold=5 * 1024 * 1024,
                            use_threads=True)
    s3.upload_fileobj(
        pickle.dumps(state, pickle.HIGHEST_PROTOCOL),
        CHECKPOINT_BUCKET, CHECKPOINT_KEY,
        ExtraArgs={"Metadata": {"committed": "1"}},
        Config=config,
    )

Fehler 2 — "RuntimeError: Aborted due to SIGTERM but Engine core was busy"

vLLM blockiert tief im Prefill, der Watchdog kommt nicht durch.

# Lösung: gestaffelte Signale & kooperativer Stop (vLLM 0.6+)

engine.py:

engine.step_with_safeguard(timeout_s=2.0)

watchdog.py:

def handler(signum, frame): if signum == signal.SIGUSR1: engine.request_stop() # kooperativ elif signum == signal.SIGTERM: save_state(global_engine_state) # hart sys.exit(0)

Fehler 3 — Token-Positionen verschieben sich nach Resume

Nach dem Resume stimmen Token-IDs nicht mehr mit dem Original-Stream überein, weil Sampling-Params randomisiert haben.

# Lösung: deterministisches Sampling
sp = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    seed=42,                  # <<< entscheidend
    max_tokens=512,
)

beim Resume: identische SamplingParams verwenden!

Preis- und Qualitätsvergleich