惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Heimdal Security Blog
A
Arctic Wolf
K
Kaspersky official blog
V
Vulnerabilities – Threatpost
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Simon Willison's Weblog
Simon Willison's Weblog
L
LINUX DO - 热门话题
MongoDB | Blog
MongoDB | Blog
T
Threat Research - Cisco Blogs
D
Docker
爱范儿
爱范儿
T
Tenable Blog
C
Check Point Blog
B
Blog
C
Cisco Blogs
Vercel News
Vercel News
The Cloudflare Blog
T
Threatpost
NISL@THU
NISL@THU
T
Tor Project blog
V2EX - 技术
V2EX - 技术
P
Palo Alto Networks Blog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
T
Tailwind CSS Blog
G
GRAHAM CLULEY
P
Privacy & Cybersecurity Law Blog
SecWiki News
SecWiki News
博客园 - 司徒正美
S
Security @ Cisco Blogs
GbyAI
GbyAI
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
The Register - Security
The Register - Security
Recorded Future
Recorded Future
Cloudbric
Cloudbric
Webroot Blog
Webroot Blog
N
News and Events Feed by Topic
Y
Y Combinator Blog
博客园_首页
T
Troy Hunt's Blog
The Hacker News
The Hacker News
雷峰网
雷峰网
Google DeepMind News
Google DeepMind News
U
Unit 42
AWS News Blog
AWS News Blog
PCI Perspectives
PCI Perspectives
V
Visual Studio Blog
博客园 - 聂微东
有赞技术团队
有赞技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Running OpenAI's gpt-oss-20b with 128k Context on a Single L4 GPU
Oleksii Nizh · 2026-05-19 · via DEV Community


Alexey Nizhegolenko

DevOps Engineer, AgentOps Engineer, AI Infrastructure Engineer


This is the second article in my series on self-hosting LLMs on GKE. In the first article I covered deploying Gemma4 26B with a 28,000 token context window. This time I'll show you something more impressive: openai/gpt-oss-20b running with a 128,000 token context on the same single L4 GPU.

The setup has been running in production since November 2025, for about 6 months, with no major incidents. That's the kind of track record worth writing about.

Why gpt-oss-20b?

OpenAI released gpt-oss-20b in August 2025 as their first open-weight model since GPT-2. It's a 21B-parameter Mixture-of-Experts model (~3.6B active parameters per token) with mxfp4 quantization built-in, meaning the weights are already compressed using microscaling FP4 format, which is far more memory-efficient than standard quantization approaches like AWQ or GPTQ.

Two things make it stand out:

128k context window - this is the main reason to pick this model over alternatives. Most quantized models on a 24GB L4 GPU are limited to 20-64k tokens. gpt-oss-20b achieves 128k through the combination of mxfp4 weights (~13GB on disk).

Built-in reasoning - the model uses chain-of-thought reasoning internally. In API responses, you'll see a reasoning_content field with the model's thought process before the final answer. This is useful for complex analytical tasks where you want to understand how the model concluded.

OpenAI tool calling format - natively compatible with --tool-call-parser openai, which means it drops in as a replacement for OpenAI API clients without any prompt engineering changes.

Hardware and Cost

Same hardware as Part 1 - g2-standard-4 with one NVIDIA L4 GPU (24GB VRAM, 4 vCPU, 16GB RAM).

Instance type On-demand price Spot price
g2-standard-4 (1x L4) ~$0.70/hr ~$0.21/hr

This article uses a standard on-demand node pool to keep the setup simple and predictable. The spot-based, cost-optimised variant and the zone-aware failover architecture that makes spot safe to run in production are the subject of Part 3.

How 128k Context Fits on 24GB VRAM

The real answer is simpler than you might expect - it fits entirely in GPU RAM. From the actual startup logs:

Model weights (mxfp4):   13.72 GiB
KV cache (fp8):           4.17 GiB  →  182,336 tokens available
CUDA graphs:              0.60 GiB
Total:                   ~18.5 GiB out of 24 GiB (0.85 utilisation)

Enter fullscreen mode Exit fullscreen mode

GPU KV cache size: 182,336 tokens
Maximum concurrency for 128,000 tokens per request: 2.68x

Enter fullscreen mode Exit fullscreen mode

182k tokens of KV cache covers 128k context with room for more than two simultaneous requests. No CPU offloading needed.

Why does --swap-space 6 exist in the config then? It's a safety net, if KV cache ever overflows under unusual load patterns, vLLM can spill to CPU RAM instead of dropping requests. In practice, it hasn't been used in 6 months of production. The fp8 KV cache combined with mxfp4 weights is efficient enough that everything fits comfortably on the GPU.

The real reason this works at 128k where other models can't is mxfp4 quantization. It stores weights in microscaling FP4 format - roughly 2x more efficient than AWQ INT4. This frees up ~2GB of VRAM compared to an equivalent AWQ model, and that extra headroom goes directly into KV cache budget.

Requirements

  • GKE cluster (Standard mode) in example, it's us-central1
  • kubectl configured
  • Google Artifact Registry for Docker images

Step 1: Create the GPU Node Pool

A standard on-demand node pool, single zone, scale-to-zero, one L4 GPU at peak:

gcloud container node-pools create l4-gptoss \
  --cluster=YOUR_CLUSTER_NAME \
  --zone=us-central1-a \
  --machine-type=g2-standard-4 \
  --accelerator=type=nvidia-l4,count=1,gpu-driver-version=latest \
  --num-nodes=0 \
  --enable-autoscaling \
  --min-nodes=0 \
  --max-nodes=1 \
  --node-labels=service=gpt-oss-20b \
  --node-taints=nvidia.com/gpu=present:NoSchedule \
  --scopes=cloud-platform

Enter fullscreen mode Exit fullscreen mode

The --node-labels=service=gpt-oss-20b label is what the StatefulSet's nodeSelector targets, and the nvidia.com/gpu taint keeps non-GPU workloads off this pool.

Step 2: Prepare the vLLM Image

gpt-oss-20b requires vLLM v0.12.0 or later with mxfp4 support. Push it to your Artifact Registry:

docker pull vllm/vllm-openai:v0.12.0

docker tag vllm/vllm-openai:v0.12.0 \
  us-central1-docker.pkg.dev/YOUR_PROJECT/tools/vllm-openai:v0.12.0

docker push us-central1-docker.pkg.dev/YOUR_PROJECT/tools/vllm-openai:v0.12.0

Enter fullscreen mode Exit fullscreen mode

Step 3: Create Namespace and Secrets

openai/gpt-oss-20b is a public model, no HuggingFace token required. You only need an API key to protect your vLLM endpoint:

kubectl create namespace gptoss-multi

# API key for protecting the vLLM endpoint
kubectl create secret generic vllm-api-multi \
  --from-literal=VLLM_API_KEY=your-api-key-here \
  -n gptoss-multi

Enter fullscreen mode Exit fullscreen mode

Step 4: Deploy gpt-oss-20b

Here's the complete StatefulSet manifest. Scheduling is intentionally minimal - a simple nodeSelector targeting the service: gpt-oss-20b label, plus a toleration for the GPU taint. No node affinity rules; the zone-aware scheduling logic comes in Part 3.

apiVersion: v1
kind: Service
metadata:
  name: gptoss-multi
  namespace: gptoss-multi
  labels:
    app: gptoss-20b
spec:
  type: ClusterIP
  selector:
    app: gptoss-20b
  ports:
    - name: http
      port: 80
      targetPort: 8000
      protocol: TCP
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: gptoss-20b
  namespace: gptoss-multi
  labels:
    app: gptoss-20b
spec:
  serviceName: gptoss
  replicas: 1
  selector:
    matchLabels:
      app: gptoss-20b
  updateStrategy:
    type: RollingUpdate
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Delete
    whenScaled: Retain
  template:
    metadata:
      labels:
        app: gptoss-20b
    spec:
      terminationGracePeriodSeconds: 30
      nodeSelector:
        service: gpt-oss-20b
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
      containers:
        - name: vllm
          image: us-central1-docker.pkg.dev/YOUR_PROJECT/tools/vllm-openai:v0.12.0
          imagePullPolicy: IfNotPresent
          args:
            - --model
            - openai/gpt-oss-20b
            - --api-key
            - $(VLLM_API_KEY)
            - --gpu-memory-utilization
            - "0.85"
            - --max-model-len
            - "128000"
            - --swap-space
            - "6"
            - --tensor-parallel-size
            - "1"
            - --max-num-seqs
            - "3"
            - --max-num-partial-prefills
            - "1"
            - --max-num-batched-tokens
            - "8128"
            - --kv-cache-dtype
            - fp8
            - --enable-auto-tool-choice
            - --tool-call-parser
            - openai
            - --host
            - 0.0.0.0
            - --port
            - "8000"
          env:
            - name: HF_HOME
              value: /models
            - name: XDG_CACHE_HOME
              value: /models/.xdg-cache
            - name: TRITON_CACHE_DIR
              value: /models/.triton
            - name: VLLM_API_KEY
              valueFrom:
                secretKeyRef:
                  key: VLLM_API_KEY
                  name: vllm-api-multi
            - name: VLLM_LOGGING_LEVEL
              value: INFO
            - name: NVIDIA_VISIBLE_DEVICES
              value: all
            - name: NVIDIA_DRIVER_CAPABILITIES
              value: compute,utility
            - name: CUDA_VISIBLE_DEVICES
              value: "0"
            - name: LD_LIBRARY_PATH
              value: /home/kubernetes/bin/nvidia/lib64:/usr/local/cuda/lib64:/usr/lib/x86_64-linux-gnu:/usr/lib:/lib
            - name: TORCH_CUDA_ARCH_LIST
              value: "8.9"
          ports:
            - name: http
              containerPort: 8000
              protocol: TCP
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
              scheme: HTTP
            initialDelaySeconds: 60
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 12
          resources:
            requests:
              cpu: "1"
              memory: 2Gi
              nvidia.com/gpu: "1"
            limits:
              cpu: "3500m"
              memory: 12Gi
              nvidia.com/gpu: "1"
          volumeMounts:
            - name: model-cache
              mountPath: /models
            - name: dshm
              mountPath: /dev/shm
            - name: nvidia-lib64
              mountPath: /home/kubernetes/bin/nvidia/lib64
              readOnly: true
            - name: nvidia-bin
              mountPath: /home/kubernetes/bin/nvidia/bin
              readOnly: true
      volumes:
        - name: dshm
          emptyDir:
            medium: Memory
            sizeLimit: 6Gi
        - name: nvidia-lib64
          hostPath:
            path: /home/kubernetes/bin/nvidia/lib64
            type: Directory
        - name: nvidia-bin
          hostPath:
            path: /home/kubernetes/bin/nvidia/bin
            type: Directory
  volumeClaimTemplates:
    - apiVersion: v1
      kind: PersistentVolumeClaim
      metadata:
        name: model-cache
      spec:
        accessModes:
          - ReadWriteOnce
        storageClassName: standard-rwo
        resources:
          requests:
            storage: 60Gi

Enter fullscreen mode Exit fullscreen mode

Apply it:

kubectl apply -f gptoss-20b.yaml
kubectl logs -f statefulset/gptoss-20b -n gptoss-multi

Enter fullscreen mode Exit fullscreen mode

Here's what a healthy startup looks like:

# Architecture confirmed - custom GptOss model class
INFO [model.py] Resolved architecture: GptOssForCausalLM
INFO [model.py] Using max model len 128000

# mxfp4 quantization confirmed, Marlin kernel selected
INFO [mxfp4.py] Using Marlin backend
WARNING: Your GPU does not have native support for FP4 computation.
         Weight-only FP4 compression will be used via Marlin kernel.

# Weights loaded - note 13.72 GiB vs 15.55 GiB for Gemma4
INFO [default_loader.py] Loading weights took 73.54 seconds
INFO [gpu_model_runner.py] Model loading took 13.7193 GiB memory and 104.729963 seconds

# torch.compile from cache - 13 seconds instead of ~90
INFO [monitor.py] torch.compile takes 13.67 s in total

# KV cache - this is the key number
INFO [gpu_worker.py] Available KV cache memory: 4.17 GiB
INFO [kv_cache_utils.py] GPU KV cache size: 182,336 tokens
INFO [kv_cache_utils.py] Maximum concurrency for 128,000 tokens per request: 2.68x

# Server ready
INFO: Application startup complete.

Enter fullscreen mode Exit fullscreen mode

The WARNING about FP4 support is expected and not a problem. L4 is sm_8.9 architecture. Native FP4 requires Blackwell (sm_9.0+). The Marlin kernel handles this transparently with no quality impact.

Key Configuration Decisions Explained

Why --gpu-memory-utilization 0.85 instead of 0.96-0.97?

We need to leave headroom for the CPU swap mechanism. When the KV cache overflows from GPU to CPU RAM, vLLM needs free GPU memory for the swap buffers. Using 0.97 here will cause OOM under load with long contexts. 0.85 is the stable value we've validated over 6 months.

Why --max-num-seqs 3?

With 128k context, each sequence can occupy a huge amount of KV cache. Allowing too many parallel sequences risks exhausting both GPU and CPU swap memory simultaneously. Three concurrent sequences is the conservative limit that keeps the deployment stable under real-world load.

Why --max-num-batched-tokens 8128?

This limits how many tokens get processed per engine step. With long-context requests, an uncapped value here can cause prefill spikes that OOM the GPU. 8128 gives a good balance between throughput and stability.

Why --max-num-partial-prefills 1?

For very long prompts, vLLM splits prefill across multiple steps (chunked prefill). Setting this to 1 means only one chunk is processed at a time, which keeps memory usage predictable during long-context ingestion.

Why 60Gi PVC instead of 30Gi?

The model weights are ~13GB but the torch.compile cache, XDG cache, and Triton cache for a 128k context model are significantly larger than for a 28k model. 60Gi gives comfortable headroom.

Expose the API

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: gptoss-ingress
  namespace: gptoss-multi
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
  ingressClassName: nginx
  rules:
    - host: gptoss.yourdomain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: gptoss-multi
                port:
                  number: 80

Enter fullscreen mode Exit fullscreen mode

Note the proxy-read-timeout: 600 — with 128k context requests can take a long time for prefill. The default nginx timeout of 60 seconds will kill long-context requests mid-generation.

Test it:

curl -s http://gptoss.yourdomain.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your-api-key" \
  -d '{
    "model": "openai/gpt-oss-20b",
    "messages": [{"role": "user", "content": "Explain how Kubernetes scheduling works."}],
    "max_tokens": 500
  }'

Enter fullscreen mode Exit fullscreen mode

Performance Results

All numbers are measured from our production instance.

Test 1 - Short context (94 prompt tokens, 500 output):

time curl -s http://gptoss.yourdomain.com/v1/chat/completions \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-oss-20b",
    "messages": [{"role": "user", "content": "Write a detailed technical explanation of how Kubernetes scheduling works..."}],
    "max_tokens": 500
  }'

# real 0m9.505s  →  500 tokens / 9.5s = ~52 tokens/sec

Enter fullscreen mode Exit fullscreen mode

Test 2 - Long context (8,076 prompt tokens, 200 output):

# ~8k tokens of context
python3 -c "print('word ' * 8000)" | xargs -I{} curl -s \
  http://gptoss.yourdomain.com/v1/chat/completions \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d "{\"model\": \"openai/gpt-oss-20b\", \"messages\": [{\"role\": \"user\", \"content\": \"{} Summarize the above.\"}], \"max_tokens\": 200}"

# real 0m6.113s  →  ~53 tokens/sec generation, TTFT ~1.47 sec

Enter fullscreen mode Exit fullscreen mode

Test 3 - 3 parallel requests:

for i in {1..3}; do
  curl -s http://gptoss.yourdomain.com/v1/chat/completions \
    -H "Authorization: Bearer your-api-key" \
    -H "Content-Type: application/json" \
    -d '{"model": "openai/gpt-oss-20b", "messages": [{"role": "user", "content": "Explain distributed systems consistency models in detail."}], "max_tokens": 500}' &
done
wait
# real 0m16.1s

Enter fullscreen mode Exit fullscreen mode

Results summary:

Metric Short context (94 tok) Long context (8k tok) 3 parallel
Throughput ~52 tok/s ~53 tok/s ~32 tok/s
TTFT 237ms ~1.47 sec ~410ms
Prompt tokens 94 8,076 77
KV cache usage <1% <1% <1%

The most important finding here: throughput stays flat regardless of prompt length. 52 tok/s with 94 tokens vs 53 tok/s with 8,076 tokens. The mxfp4 quantization handles long contexts extremely efficiently. The only cost of longer context is TTFT - prefilling 8k tokens takes ~1.47 seconds vs 237ms for a short prompt, which is expected and linear.

The Reasoning Feature

One thing worth calling out the model exposes its internal reasoning process. Every response includes a reasoning_content field:

{
  "reasoning_content": "We need to explain distributed systems consistency models in detail. 
  Likely include eventual consistency, strong consistency, linearizability, sequential 
  consistency, causal consistency... We'll structure: 1. Intro. 2. CAP theorem. 
  3. ACID vs BASE...",
  "content": null
}

Enter fullscreen mode Exit fullscreen mode

This is the model's chain-of-thought before generating the final answer. For analytical tasks, debugging agent failures, or building explainable AI pipelines, this is genuinely useful - you can see exactly how the model reasoned through a problem.

Note that content is null in the response above - the reasoning model separates thinking from output. Your client needs to handle both fields.

Cost Breakdown

Resource Cost/month
g2-standard-4 on-demand ~$500
PVC 60GB standard-rwo ~$6
Total ~$506/month

At 52 tok/s running 24/7:

52 tokens/sec × 3600 × 24 × 30 = ~134 billion tokens/month theoretical

Enter fullscreen mode Exit fullscreen mode

At 20% average utilization: ~27 billion tokens/month for $506.

(Part 3 cuts this roughly 3x by moving to a spot with a failover architecture that makes the spot safe to run.)

Compared to the Gemma4 Article

If you read Part 1, here's how the two models compare side by side:

gpt-oss-20b Gemma 4 26B AWQ
Context window 128,000 tokens 28,000 tokens
Throughput ~52 tok/s ~51 tok/s
TTFT (short) 237ms 84ms
Weights size ~13GB (mxfp4) ~16GB (AWQ int4)
VRAM for weights 13.72 GiB 15.55 GiB
KV cache pool (GPU) 4.17 GiB 3.12 GiB
KV cost per token ~24.5 KB (GQA, fp8) ~112.7 KB (global head_dim=512)
Max tokens in KV 182,336 29,709
GPU util setting 0.85 0.97
Reasoning ✅ built-in ✅ built-in
Tool calling openai format gemma4 format
License Apache 2.0 Apache 2.0
HuggingFace access gated public

Why such a huge difference in context despite similar KV cache size?

This is the most interesting technical finding. The answer lies in how each model's attention is shaped.

From the vLLM startup logs:

  • gpt-oss-20b: 4.17 GiB for 182,336 tokens → ~24.5 KB per token
  • Gemma4 26B: 3.12 GiB for 29,709 tokens → ~112.7 KB per token

The standard formula for KV cache memory per token is:

Bytes per token = 2 × layers × KV_heads × head_dim × bytes_per_element

Enter fullscreen mode Exit fullscreen mode

gpt-oss-20b is a 24-layer model with 8 KV heads (GQA — 64 query heads grouped onto just 8 KV heads) and a head_dim of 64. With fp8 KV cache (1 byte per element):

2 × 24 × 8 × 64 × 1 = 24,576 bytes ≈ ~24 KB per token

Enter fullscreen mode Exit fullscreen mode

That already matches the ~24.5 KB/token we see in the logs almost exactly - and 4.17 GiB ÷ 24,576 bytes ≈ 182,336, which is precisely the headline KV pool size vLLM reports. So there is no mystery in the per-token number and no hidden reduction happening: aggressive GQA (8 KV heads instead of 64), a small head_dim of 64, and fp8 KV cache are what make each token cheap. vLLM computes the headline token capacity using exactly this uniform per-token cost.

So, where does Sliding Window Attention (SWA) actually help? Not in the per-token headline - in concurrency. gpt-oss-20b alternates layer types: roughly half of its 24 layers use full global attention, the other half use a tight 128-token sliding window. For long-context requests, the sliding-window layers do not grow with prompt length - they stay bounded at 128 tokens whether the prompt is 4k or 128k. So a real 128k request only pays the full per-token price on about half its layers; the sliding-window half is effectively free at length.

This is exactly what the log line reports:

INFO [kv_cache_utils.py] Maximum concurrency for 128,000 tokens per request: 2.68x

Enter fullscreen mode Exit fullscreen mode

A naive reading would expect 182,336 ÷ 128,000 ≈ 1.42x concurrency for 128k requests. vLLM reports 2.68x - nearly double — because its memory manager understands the hybrid SWA structure and knows a 128k sequence costs roughly half the uniform estimate (only the ~12 full-attention layers accumulate full-length KV; the ~12 sliding-window layers plateau at 128 tokens). That ~1.9x uplift over the naive ratio is the SWA payoff - it buys concurrency headroom, not a cheaper headline per-token figure.

In contrast, Gemma4 26B uses a heavy heterogeneous attention architecture: most layers are local sliding-window layers at head_dim=256, but a few global attention layers use a much larger head_dim=512 (8 query heads grouped onto 4 KV heads via GQA). It's those wide head_dim=512 global layers that dominate the KV budget. The startup logs flag the split explicitly:

INFO [config.py] Gemma4 model has heterogeneous head dimensions
     (head_dim=256, global_head_dim=512).
     Forcing TRITON_ATTN backend to prevent mixed-backend numerical divergence.

Enter fullscreen mode Exit fullscreen mode

Gemma4's global attention layers with a massive head_dim=512 cost dramatically more per token, pushing its combined average overhead to ~112.7 KB per token - roughly 4.6x heavier than gpt-oss-20b's ~24.5 KB.

This explains the gap:

  • gpt-oss-20b: 4.17 GiB ÷ ~24.5 KB/token ≈ 182k tokens of headline KV pool cheap per token thanks to aggressive GQA + small head_dim + fp8, plus ~2.68x effective concurrency at 128k because the Sliding Window layers don't grow with context
  • Gemma4: 3.12 GiB ÷ ~112.7 KB/token ≈ 29k tokens - heavy global attention dimensions for maximum recall accuracy at the cost of density

Choose gpt-oss-20b when you need long context on budget hardware or an OpenAI-compatible tool-calling drop-in. Choose Gemma 4 when you need lower TTFT or native vision input.

Honest Assessment

Same caveat as in Part 1 - this is a quantized model, not the full cloud API. The mxfp4 quantization is more aggressive than AWQ int4, which can affect quality on tasks requiring precise numerical reasoning or very long coherent outputs.

In practice we haven't noticed quality issues for the use cases we run: document analysis, structured data extraction, automation agents, and code review. For these tasks, the model performs well and the 128k context is genuinely useful - you can feed entire codebases or long documents without chunking.

Data privacy remains the core advantage. Everything runs inside your VPC.

6 Months of Production Data

The setup has been running since November 2025. A few things we learned over time:

Pod restarts are fast and self-healing - when the node is recycled (GKE node auto-upgrade, maintenance, or a manual node pool operation), the StatefulSet pod is rescheduled, picks up the PVC with cached weights and torch.compile artifacts, and is back up in ~3 minutes. No data loss, no manual intervention. (Surviving spot preemption - and eliminating even that ~3-minute gap with a multi-zone replica architecture - is exactly what Part 3 covers.)

Memory is stable - no OOM events in 6 months with the --max-num-seqs 3 limit. The CPU swap mechanism handles occasional long-context requests without instability.

The model is consistent - response quality and latency have been stable. No drift or degradation observed.

Afterwords

Running a model with 128k context on a $0.70/hr instance felt ambitious when we started. Six months later, it's just infrastructure that runs. The key insight is that mxfp4 quantization combined with aggressive GQA and Sliding Window Attention is what makes 128k context genuinely feasible on 24GB VRAM - not a hack, but an architectural decision that vLLM understands and optimizes for natively.

This deployment is deliberately simple - a single replica on a standard on-demand node, with minimal scheduling. The next article in this series builds directly on it: a zone-aware multi-node spot setup with a K8S controller and automatic failover that guarantees at least one replica is always serving - the architecture that makes this both cheap (~3x cost reduction) and truly resilient in production.

If you have questions or feedback, feel free to reach out.