지금 구조라면 GPU node를 기존 Compute Cluster에 그냥 추가하기보다는, GPU 전용 Kubernetes Cluster를 하나 더 만들고 기존 Compute / Storage / Admin과 역할을 분리하는 안을 우선 추천합니다.
즉 최종적으로는 4-cluster architecture입니다.
┌──────────────────────────────┐
│ Admin Cluster │
│ │
│ CI/CD / GitOps │
│ Keycloak / SSO │
│ Cluster Management │
│ Registry / Helm Repo │
│ Monitoring / Logging(*) │
└──────────────┬───────────────┘
│
Management / GitOps / API
│
┌───────────────────────────┼───────────────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ Compute Cluster │ │ GPU Cluster │ │ Storage Cluster │
│ │ │ │ │ │
│ CPU workload │ │ B300 GPU nodes │ │ AIStor │
│ API / Backend │ │ vLLM / inference │ │ MinIO/S3 │
│ preprocessing │ │ training │ │ durable dataset │
│ application │ │ OCR/VLM(*) │ │ model/checkpoint │
└─────────┬──────────┘ └─────────┬──────────┘ └─────────┬──────────┘
│ │ │
└──────────────────────────┼───────────────────────────┘
│
Cilium ClusterMesh
BGP / Native Routing
│
Ethernet L3 Fabric
여기서 중요한 것은 ClusterMesh는 service/workload connectivity를 제공하는 계층이고, AIStor의 대용량 S3 data path나 GPU의 NDR/IB fabric까지 무조건 ClusterMesh에 태우자는 의미는 아닙니다.
저라면 경계를 다음처럼 잡겠습니다.
| Cluster | Node | 핵심 역할 | 넣지 않을 것 |
|---|---|---|---|
| Admin | 일반 CPU | GitOps, CI/CD, Keycloak, Registry, cluster lifecycle, observability | AI workload |
| Compute | CPU Compute | API/backend, preprocessing, orchestration, 일반 application | 대형 GPU serving |
| GPU | B300 | vLLM, LLM/VLM inference, training, GPU batch | AIStor |
| Storage | Storage node | AIStor/S3, model/dataset/checkpoint | application/GPU workload |
현재 Compute와 Storage가 이미 ClusterMesh라면:
현재
Compute Cluster
│
│ Cilium ClusterMesh
│
Storage Cluster
여기에 GPU cluster를:
Compute
/ \
/ \
/ \
GPU ───── Storage
ClusterMesh
로 참여시키는 것입니다.
Admin cluster까지 반드시 동일한 ClusterMesh에 넣을 필요는 없습니다.
오히려 저는 Admin은 management plane으로 분리하는 쪽을 선호합니다.
Admin cluster 역할을 보면:
Admin
├── Argo CD / GitOps
├── CI/CD
├── Keycloak
├── Registry
├── Helm repository
├── Cluster management
└── Monitoring
이므로 application data plane과 성격이 다릅니다.
따라서:
Admin Cluster
│
Management Network
│
┌───────────────┼──────────────┐
▼ ▼ ▼
Compute GPU Storage
로 관리하고,
ClusterMesh는:
┌──── Compute ────┐
│ │
│ ClusterMesh │
│ │
GPU ───────── Storage
정도로 제한하는 것이 좋습니다.
이렇게 하면 GPU/Compute/Storage data plane에 문제가 발생해도 관리 plane의 blast radius를 줄일 수 있습니다.
기존 Compute Cluster에 GPU node를 추가하는 것도 기술적으로 가능합니다.
Compute Cluster
CPU node
CPU node
CPU node
GPU B300
GPU B300
GPU B300
하지만 지금 정도 규모/구조라면 추천하지 않습니다.
GPU node에는 다음처럼 일반 Compute와 상당히 다른 node-level 설정이 들어가기 때문입니다.
GPU Node
RHEL 10
│
├─ NVIDIA Driver
├─ Container Toolkit
├─ GPU Operator
├─ NVIDIA Device Plugin
├─ DCGM
├─ NFD
│
├─ CPU Manager = static
├─ Topology Manager = restricted
│
├─ HugePages(optional)
│
├─ NVLink / NVSwitch
├─ NCCL
│
├─ NDR InfiniBand
├─ RDMA
│
└─ NVMe x8 local cache
운영 lifecycle 자체가 CPU compute node와 다릅니다.
특히 GPU driver/CUDA/NCCL/GPU Operator upgrade 때문에 GPU cluster의 maintenance cycle을 Compute와 독립시키는 가치가 큽니다.
저라면 GPU cluster는 최소 다음과 같이 만듭니다.
GPU Kubernetes Cluster
┌────────────────────────────┐
│ Control Plane x3 │
│ │
│ 일반 CPU server/VM │
│ GPU 없음 │
└──────────────┬─────────────┘
│
Kubernetes API
│
┌───────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
GPU Node 01 GPU Node 02 GPU Node N
B300 x8 B300 x8 B300 x8
NVMe x8 NVMe x8 NVMe x8
RAID0 RAID0 RAID0
XFS XFS XFS
/model-cache /model-cache /model-cache
Control Plane을 B300 node 위에 올리는 것은 피합니다.
여기가 특히 중요합니다.
GPU Node
│
┌──────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
Management Ethernet Data NDR IB
Network Network Fabric
K8s API Cilium/BGP NCCL
SSH/OOB ClusterMesh GPU↔GPU
monitoring AIStor/S3 RDMA
inference API
논리적으로:
GPU
│
├── Pod Network
├── Service Network
├── Cilium
├── BGP
├── ClusterMesh
│
├── Compute ↔ GPU
└── GPU ↔ AIStor
현재 사용하는 Cilium native routing + BGP + ECMP를 이쪽에 그대로 확장하는 것이 자연스럽습니다.
GPU Node 1
│
NDR
│
GPU Node 2
│
NDR
│
GPU Node N
여기는 ClusterMesh traffic용이 아니라 우선:
NCCL
MPI
GPU collective
RDMA
전용으로 봅니다.
일반 serving Pod:
vLLM Pod
│
eth0
│
Cilium
│
Compute/API
만 있으면 됩니다.
하지만 multi-node GPU workload라면:
GPU Pod
┌─────┴─────┐
│ │
eth0 ib0
│ │
Cilium NDR
│ │
API/S3 NCCL/RDMA
형태가 좋습니다.
따라서 GPU cluster에는 Multus + NVIDIA Network Operator / RDMA device plugin 계열을 검토할 가치가 있습니다.
즉:
Primary CNI
────────────
Cilium
│
└─ application/service traffic
Secondary Network
─────────────────
Multus
│
└─ IB/RDMA
│
└─ NCCL
입니다.
Cilium을 없애고 IB를 쓰는 게 아니라 용도가 다른 두 network를 Pod에 제공하는 구조입니다.
예를 들어 Compute cluster의 backend가 GPU cluster의 vLLM을 호출한다면:
Compute Cluster
Application Pod
│
│ HTTP/gRPC
▼
Cilium
│
│ ClusterMesh
▼
GPU Cluster
│
▼
Inference Service
│
▼
vLLM Pod
│
▼
B300
가 됩니다.
ClusterMesh를 이미 운영 중이라면 이 구조가 자연스럽습니다.
서비스 이름도 global service/discovery 전략을 정해서:
llm-serving.gpu...
같은 논리 endpoint로 노출할 수 있습니다.
다만 모든 GPU Pod를 Compute에서 직접 접근시키기보다 GPU cluster에 Gateway/API 계층을 두는 것을 권합니다.
Compute
│
▼
GPU Gateway / LB
│
├── DeepSeek service
├── GLM service
├── Qwen service
└── OCR service
│
▼
GPU Pod
이렇게 해야 backend가 GPU topology/Pod 배치를 몰라도 됩니다.
여기서 중요한 설계 포인트가 있습니다.
논리적으로는:
GPU Pod
│
ClusterMesh
│
AIStor Service
라고 만들 수 있습니다.
하지만 대규모 model/dataset/checkpoint S3 traffic까지 ClusterMesh service path에 의존할 필요는 없습니다.
저라면:
GPU Cluster
│
│ Ethernet L3
│
│ S3/HTTPS
▼
AIStor VIP / LB
│
Storage Cluster
형태의 직접 routed data path를 우선 검토합니다.
즉:
Small/control/service traffic
Compute ←── ClusterMesh ──→ GPU
↘ Storage
Bulk storage traffic
GPU =======================> AIStor
L3 Ethernet / S3
입니다.
Cilium/BGP를 사용하고 있으므로 underlay routing을 잘 설계하면 이 방식과 궁합이 좋습니다.
Storage cluster는:
Storage Kubernetes Cluster
Control Plane x3
AIStor Node
├ NVMe
├ NVMe
├ ...
└ high-speed Ethernet
AIStor Node
├ NVMe
└ ...
AIStor Node
└ ...
로 유지합니다.
여기에 GPU workload나 일반 application을 넣지 않습니다.
그리고 bucket 역할은:
AIStor
s3://models/
s3://datasets/
s3://checkpoints/
s3://artifacts/
정도로 분리합니다.
GPU node에는 앞에서 정한:
NVMe x8
│
RAID0
│
XFS
│
/model-cache
를 둡니다.
전체 storage hierarchy는:
AIStor
durable / source
│
│ S3
▼
GPU Local NVMe
8× RAID0 + XFS
cache/stage
│
▼
CPU RAM
│
▼
GPU HBM
│
┌────┴────┐
│ │
Weight KV
가 됩니다.
AIStor = source of truth, local NVMe는 언제든 재생성 가능한 cache라는 원칙을 유지합니다.
Air-gap이므로 이 구조가 특히 중요합니다.
Admin Cluster
│
┌─────────────────┼─────────────────┐
│ │ │
Git Repo OCI Registry Keycloak
│ │ │
└─────────────────┼─────────────────┘
│
GitOps
│
┌─────────────────┼──────────────────┐
▼ ▼ ▼
Compute GPU Storage
Private registry에는:
NVIDIA GPU Operator
NVIDIA Network Operator
NVIDIA driver container
DCGM exporter
vLLM
NCCL
Cilium
AIStor
application images
등 필요한 모든 artifact를 mirror합니다.
GPU cluster가 인터넷을 직접 볼 필요가 없도록 합니다.
각 cluster에는 exporter/agent를 두고:
Compute
Prometheus agent ─┐
GPU
DCGM exporter ────┼──> Admin Observability
Prometheus agent ─┤
Storage
metrics agent ────┘
형태로 중앙 집계하는 게 좋습니다.
GPU에서는 특히:
GPU utilization
HBM usage
GPU temperature/power
NVLink traffic
PCIe traffic
ECC
XID error
GPU throttling
CPU NUMA
local NVMe throughput
NCCL
IB/RDMA counters
model latency
TTFT
TPOT
tokens/sec
KV-cache utilization
까지 같이 봐야 합니다.
그래야 GPU utilization이 떨어졌을 때:
AIStor?
↓
Ethernet?
↓
NVMe?
↓
CPU/DataLoader?
↓
GPU?
↓
NVLink/NCCL?
중 어디가 병목인지 찾을 수 있습니다.
현재 환경을 크게 뜯어고치지 않고 GPU를 추가한다면 이 형태가 가장 자연스럽습니다.
ADMIN CLUSTER
┌──────────────────────────┐
│ CI/CD / GitOps │
│ Keycloak │
│ Registry / Helm │
│ Cluster Management │
│ Observability │
└────────────┬─────────────┘
│
Management Plane
│
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌────────────────┐ ┌───────────────┐
│ COMPUTE │ │ GPU CLUSTER │ │ STORAGE │
│ CLUSTER │ │ │ │ CLUSTER │
│ │ │ B300 x8/node │ │ │
│ CPU workload │ │ │ │ AIStor │
│ backend │ │ vLLM │ │ S3 │
│ API │ │ LLM/VLM │ │ model │
│ preprocess │ │ training │ │ dataset │
│ │ │ │ │ checkpoint │
└───────┬───────┘ └───────┬────────┘ └───────┬───────┘
│ │ │
│ Cilium ClusterMesh │
└─────────────────────┼──────────────────────┘
│
Ethernet Fabric
BGP + ECMP + L3
│
┌─────────────┴────────────┐
│ │
Service traffic S3 bulk
│
GPU ↔ AIStor
Separate NDR IB Fabric
GPU Node ───────── GPU Node
│ │
└──── NDR IB ─────┘
NCCL/RDMA
여기서 제가 특히 설계 원칙으로 고정할 것은 Admin=Management, Compute=CPU application, GPU=accelerated workload, Storage=durable object storage의 4-way separation입니다. 그리고 Cilium ClusterMesh는 Compute/GPU/Storage의 서비스 연결, Ethernet L3 fabric은 AIStor 같은 bulk data path, NDR IB는 GPU collective/RDMA라는 식으로 네트워크 역할도 분리합니다.
추가로 GPU cluster kubelet은 앞에서 이야기한 CPU Manager=static, Topology Manager=restricted, scope=pod를 baseline으로 하고, GPU Operator/NFD/DCGM 및 필요 시 Network Operator+Multus/RDMA를 얹는 구조가 좋습니다. 이 정도면 이후 B300 증설이나 GPU driver/CUDA 업그레이드가 기존 Compute/Storage cluster에 미치는 영향도 상당히 줄일 수 있습니다.
===
가능합니다. 지금 요구사항이면 GPU Cluster 안에 “Inference Platform” 계층을 하나 만드는 것이 좋습니다. 사용자는 개별 vLLM Pod를 직접 호출하지 않고, 항상 Inference Gateway를 통해 접근하게 합니다.
핵심 구조는 아래처럼 잡겠습니다.
┌──────────────────────────────┐
│ Admin Cluster │
│ │
│ Keycloak / GitOps / CI/CD │
│ Model Schedule / Policy │
│ Observability / Registry │
└──────────────┬───────────────┘
│
configuration / identity
│
▼
┌──────────────────────────────── GPU Cluster ───────────────────────────────┐
│ │
│ Users / Compute Cluster │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Inference Gateway │ │
│ │ │ │
│ │ OIDC / Keycloak │ │
│ │ API Key │ │
│ │ User/Team Quota │ │
│ │ RPM / TPM Limit │ │
│ │ Model ACL │ │
│ │ Routing │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌──────┴────────────────────────────────┐ │
│ │ Model Router │ │
│ │ │ │
│ │ model alias → active deployment │ │
│ └──────┬────────────────────────────────┘ │
│ │ │
│ ┌────────┴────────────────────────────────────────────────────────┐ │
│ │ GPU Serving Pool │ │
│ │ │ │
│ │ DeepSeek GLM Qwen gpt-oss PaddleOCR │ │
│ │ vLLM vLLM vLLM vLLM VLM │ │
│ │ │ │
│ │ GPU HBM ─── KV cache / Prefix cache │ │
│ │ │ │ │
│ │ └────────── MemKV (optional L2 KV) │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ Local NVMe x8 │ │
│ │ RAID0 + XFS │ │
│ │ │ │
│ │ model-cache │ │
│ │ staging │ │
│ │ scratch │ │
│ └─────────┬──────────┘ │
└──────────────────────────────┼─────────────────────────────────────────────┘
│ S3
▼
┌─────────────────────┐
│ Storage Cluster │
│ │
│ AIStor │
│ │
│ models/ │
│ datasets/ │
│ artifacts/ │
│ checkpoints/ │
│ Memory Bucket (*) │
└─────────────────────┘
AIStor 자체는 inference engine이 아니라 object/data tier입니다. 공식 문서도 AIStor가 모델 inference를 실행하는 것이 아니라 accelerator가 사용하는 데이터를 공급하는 계층이라고 명확히 구분합니다. MinIO AIStor Documentation
사용자가 이런 식으로 각 서비스를 직접 호출하게 만들지 않는 게 좋습니다.
deepseek-service:8000
glm-service:8000
qwen-service:8000
...
대신:
inference.company.local
│
▼
Inference Gateway
│
┌────────────────┼─────────────────┐
▼ ▼ ▼
DeepSeek GLM Qwen
로 만듭니다.
가능하면 API도 OpenAI-compatible 형태로 통일합니다.
POST /v1/chat/completions
model: deepseek-flash
model: glm-flash
model: qwen
model: gpt-oss
그러면 client는 backend가 vLLM인지 다른 inference engine인지 알 필요가 없습니다.
Keycloak은 이미 Admin cluster에 있으므로 적극 활용할 수 있습니다.
User
│
▼
Keycloak
│
│ JWT
▼
Inference Gateway
│
├─ identity
├─ group/team
├─ allowed models
├─ RPM
├─ TPM
├─ concurrency
└─ daily/monthly token quota
예를 들어 정책을:
| Group | Model | RPM | TPM | Concurrent | 일 사용량 |
|---|---|---|---|---|---|
| team-A | GLM | 60 | 500K | 10 | 20M token |
| team-A | DeepSeek | 30 | 300K | 5 | 10M |
| team-B | Qwen | 100 | 1M | 20 | 50M |
| batch | GLM | 10 | 5M | 4 | 200M |
처럼 관리합니다.
단순 request count보다 token 단위 quota가 중요합니다.
request A = 500 tokens
request B = 50,000 tokens
은 GPU 비용이 전혀 다르기 때문입니다.
따라서 최소한:
RPM requests/min
TPM tokens/min
Concurrent requests
Max input tokens
Max output tokens
Daily token quota
정도는 Gateway에서 제어하는 것을 권합니다.
사용자에게:
glm-5.3-flash-fp8-tp4-ep4-v20261001
같은 실제 deployment 이름을 노출하면 운영하기 어렵습니다.
논리 alias를 둡니다.
사용자
model="glm-flash"
│
▼
Model Router
│
└──> glm-5.3-flash-fp8-v3
그러면 모델 교체도:
glm-flash
│
├─ old → GLM 5.3 Flash v2
│
└─ new → GLM 5.3 Flash v3
처럼 backend만 바꾸면 됩니다.
이 구조가 주간/야간 모델 전환에도 핵심입니다.
예를 들어:
주간 08:00~22:00
GPU0~3 DeepSeek-V4.1-Flash
GPU4~7 GLM-5.3-Flash
+ OCR/Docling 별도 resource
야간 22:00~08:00
GPU0~3 GLM-5.3
GPU4~5 Qwen
GPU6 gpt-oss
GPU7 spare/replica
라고 합시다.
단순히 22:00에:
scale day models = 0
scale night models = 1
하면 안 됩니다.
모델 loading 시간이 있기 때문입니다.
제가 권하는 전환은:
21:30
│
├─ Night model prefetch
│ AIStor → Local NVMe
│
21:45
│
├─ GPU allocation 준비
│
├─ model load
│ NVMe → RAM → HBM
│
└─ warm-up
│
21:55
│
├─ readiness check
└─ test inference
│
22:00
│
├─ Model Router switch
│
│ day → night
│
└─ 신규 request → Night Model
│
22:00+
│
├─ Day Model request drain
│
└─ Day Model unload
즉 Blue/Green deployment와 비슷하게 생각하면 됩니다.
Admin/GitOps에는 모델 schedule을 선언합니다.
예를 들면 개념적으로:
day:
start: "08:00"
models:
- deepseek-v4.1-flash
- glm-5.3-flash
- paddleocr-vl
- docling
night:
start: "22:00"
models:
- glm-5.3
- qwen3.6-35b-a3b
- gpt-oss-20b
Model Lifecycle Controller가 이를 보고:
Schedule
│
▼
Model Lifecycle Controller
│
├── prefetch
├── cache validation
├── deployment
├── GPU allocation
├── warmup
├── readiness
├── route switch
├── drain
└── unload
을 수행하게 합니다.
그러면 단순 scheduler보다 훨씬 안전합니다.
모델 weight는:
COLD / Durable
AIStor
│
S3 GET
│
▼
Local NVMe
/model-cache
│
▼
RAM
│
▼
GPU HBM
HOT / Fast
입니다.
AIStor가 source of truth입니다.
s3://models/
deepseek/
v4.1-flash/
manifest.json
config.json
tokenizer/
weights/
glm/
5.3-flash/
5.3/
qwen/
3.6-35b-a3b/
gpt-oss/
20b/
Local NVMe는:
/model-cache/
deepseek-v4.1-flash/
glm-5.3-flash/
glm-5.3/
qwen3.6/
gpt-oss/
입니다.
Local NVMe는 cache일 뿐 authoritative storage가 아닙니다.
이게 네 환경의 큰 장점입니다.
보통 cache는:
LRU
로 관리하지만 여기서는 다음에 필요한 모델을 이미 알고 있습니다.
따라서:
19:00
/model-cache
Day models
████████████
Night models
████████
↑
미리 존재
시키는 것이 훨씬 좋습니다.
NVMe capacity가 충분하다면 주간+야간 모델을 전부 cache에 유지하는 게 최선입니다.
AIStor
│
│ 최초 1회 / version 변경 시
▼
Local NVMe
├── Day model set
└── Night model set
↓
시간 전환 시
NVMe → HBM만 수행
그러면 매일 AIStor에서 수백 GB~TB를 다시 가져올 이유가 없습니다.
예를 들어:
/model-cache/
sha256-AAAA/
weights...
와 같이 관리합니다.
AIStor에:
models/glm-5.3/manifest.json
version: 2026-10-01
sha256: AAAA...
가 있다면 controller가:
AIStor manifest
│
▼
Local cache manifest
│
├─ same hash
│ → HIT
│
└─ different
→ DOWNLOAD
하게 합니다.
Air-gap 환경에서는 특히 재현성과 artifact integrity 면에서 유리합니다.
여기서는 역할을 정확히 분리해야 합니다.
Model Weight Path
─────────────────
AIStor
↓
Local NVMe
↓
GPU HBM
KV Path
─────────────────
GPU HBM
↓
MemKV
즉 MemKV에 model weight를 저장하려는 게 아닙니다.
MemKV는 inference KV state의 external tier입니다.
현재 MinIO MemKV는 NVIDIA NIXL을 통해 inference framework와 연결하고, distributed RDMA+NVMe 구성뿐 아니라 GPU node에 co-locate하는 구성도 제공합니다. MinIO AIStor Documentation
GPU
│
┌───────▼───────┐
│ HBM KV Cache │
│ L1 │
└───────┬───────┘
│
cache pressure
│
┌───────▼───────┐
│ MemKV │
│ L2 │
│ │
│ RAM / NVMe │
│ RDMA/NIXL │
└───────────────┘
이게 특히 유리한 workload는:
긴 system prompt
긴 document/RAG context
multi-turn conversation
agent loop
동일 prefix 반복
입니다.
MemKV 공식 sizing 문서도 long-context, shared-prefix, multi-turn, HBM prefix cache보다 큰 working set을 주된 유효 영역으로 설명합니다. 반대로 prompt가 대부분 고유하거나 HBM에 전부 들어가면 external KV tier 자체가 overhead가 될 수 있습니다. MinIO AIStor Documentation
따라서 모든 모델에 무조건 MemKV를 켜지는 않겠습니다.
초기 정책은 이런 식이 좋습니다.
| Model/workload | HBM KV | MemKV | 이유 |
|---|---|---|---|
| DeepSeek long-context | O | O | KV reuse 가능성 |
| GLM agent/RAG | O | O | long context/multi-turn |
| Qwen chat | O | PoC | workload에 따라 |
| gpt-oss short chat | O | △ | HBM으로 충분할 수 있음 |
| PaddleOCR | O | X | KV externalization 이득 작음 |
| Docling | - | X | 해당 없음 |
MemKV 자체 benchmark에서도 모델과 concurrency에 따라 효과 차이가 상당히 크므로, TTFT / throughput / KV hit ratio를 측정해서 모델별 enable하는 게 맞습니다. MinIO AIStor Documentation
여기는 MemKV와 혼동하면 안 됩니다.
AIStor Memory는 LLM inference KV cache가 아닙니다.
현재 AIStor Memory는 agent의 workspace/long-term memory를 AIStor Memory Bucket에 저장하고, local NVMe를 staging/read cache로 사용해 파일 형태로 제공하는 구조입니다. MinIO AIStor Documentation
따라서:
Inference Platform
LLM
│
┌────────┴────────┐
│ │
KV Cache Agent Memory
│ │
MemKV AIMem
│ │
fast inference persistent
state state
│
▼
AIStor
로 구분하면 정확합니다.
예를 들어 사용자별 AI agent가:
사용자 A
conversation
documents
preferences
agent workspace
generated files
를 세션을 넘어 유지해야 한다면 AIMem이 유용합니다.
User A
│
Agent Pod
│
├── LLM inference ─────── GPU/vLLM
│
└── /workspace
│
AIMem
│
local NVMe cache
│
▼
AIStor Memory Bucket
AIStor Memory는 local directory를 staging + read cache로 사용하고 cache hit 시 local disk에서 FUSE passthrough로 읽습니다. MinIO AIStor Documentation
그래서 일반 inference API에 AIMem을 억지로 넣을 필요는 없습니다.
Agentic AI를 서비스할 때 추가하는 계층입니다.
결국 네 환경에서는 서로 다른 세 가지 cache가 존재합니다.
GPU HBM
┌─────────────────┐
│ Model Weight │
│ KV Cache L1 │
└───────┬─────────┘
│
┌─────────┴─────────┐
│ │
MemKV Local NVMe
KV Cache L2 Model Cache
│ │
│ │
conversation Model Weight
prefix/KV staging
│ │
└─────────┬─────────┘
│
AIStor
durable storage
AIMem은 별도로:
Agent
│
AIMem
│
Local NVMe
│
AIStor Memory Bucket
입니다.
AIStor에도 object access용 cache 기능이 있지만, 이것을 GPU node의 /model-cache와 동일시하면 안 됩니다.
AIStor cache는 object-store server 쪽 cache이고, 현재 문서상 drive별 accessed-object metadata 등을 node memory에 cache하며 memory 한도도 관리합니다. MinIO AIStor Documentation
우리가 설계한:
GPU node
/model-cache
는 GPU workload를 위해 직접 관리하는 model staging cache입니다.
따라서 두 cache는 동시에 존재할 수 있습니다.
Gateway:
requests/user
tokens/user
model/user
latency
TTFT
TPOT
errors
quota rejection
GPU/vLLM:
tokens/sec
batch size
queue
KV utilization
prefix hit
GPU utilization
HBM
MemKV:
KV hit
KV miss
read BW
write BW
RDMA BW
그리고 DCGM:
GPU utilization
HBM
power
temperature
NVLink
PCIe
XID
를 수집합니다.
Admin cluster의 Prometheus/Grafana 계열로 중앙 집계하면:
User
↓
Gateway
↓
Model
↓
GPU
↓
KV
를 하나의 request ID로 추적할 수 있습니다.
예를 들어 Night GLM warm-up이 실패했다고 해서 22:00에 Day GLM을 내려버리면 안 됩니다.
Controller state machine을:
PREFETCH
↓
VERIFY
↓
LOAD
↓
WARMUP
↓
HEALTHY?
/ \
NO YES
│ │
ABORT SWITCH
│ │
keep ↓
old DRAIN OLD
model ↓
UNLOAD
로 만드는 게 좋습니다.
즉 schedule은 전환을 시작하는 trigger일 뿐, traffic switch는 health condition에 의해 결정되어야 합니다.
전체를 합치면 저는 이 그림을 목표 architecture로 잡겠습니다.
Users / Applications
│
▼
┌──────────────────────────┐
│ Inference Gateway │
│ │
│ Keycloak/OIDC │
│ API Key │
│ Model ACL │
│ RPM / TPM │
│ Token Quota │
│ Concurrency Limit │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ Model Router │◀──────── Model Lifecycle Controller
└────────────┬─────────────┘ │
│ │ schedule
│ │ prefetch
│ │ warmup
│ │ switch/drain
▼ │
┌───────────────────────────────────────────────────────────┐
│ GPU Serving Pool │
│ │
│ DeepSeek GLM Qwen gpt-oss │
│ │
│ vLLM / inference runtime │
│ │ │
│ GPU HBM │
│ Weight + KV L1 │
│ │ │
│ MemKV L2 │
│ NIXL / RDMA │
└─────────────────────┬─────────────────────────────────────┘
│
┌────────▼─────────┐
│ Local NVMe x8 │
│ RAID0 + XFS │
│ │
│ /model-cache │
│ /scratch │
└────────┬────────┘
│
│ S3 / high-speed Ethernet
▼
┌──────────────────┐
│ AIStor │
│ │
│ Models │
│ Datasets │
│ Artifacts │
│ Checkpoints │
│ Memory Buckets │◀── AIMem / Agents
└──────────────────┘
AIStor S3 data path는 우선 기존 high-speed Ethernet을 사용하고, 실제 model staging이 병목이면 S3 over RDMA를 Phase 2로 PoC할 수 있습니다. 현재 AIStor는 Kubernetes에서 RDMA 전용 server image와 single/multi-NIC topology를 공식 지원합니다. MinIO AIStor Documentation
그리고 air-gap 환경에서는 이 모든 이미지와 Helm chart를 Admin cluster의 private registry에 mirror해서 GitOps로 배포하는 게 좋습니다. AIStor도 private registry와 air-gap 배포 절차를 공식 지원합니다. MinIO AIStor Documentation
결국 핵심은 Gateway → Router → Serving → HBM/MemKV → local NVMe → AIStor의 계층화와, 별도의 Model Lifecycle Controller가 주/야간 전환을 책임지는 구조입니다. 이 구조라면 나중에 GPU node가 늘어나도 사용자 API/쿼터 계층은 그대로 두고 serving pool만 수평 확장할 수 있습니다.
===
가능합니다. 지금 요구사항이면 GPU Cluster 안에 “Inference Platform” 계층을 하나 만드는 것이 좋습니다. 사용자는 개별 vLLM Pod를 직접 호출하지 않고, 항상 Inference Gateway를 통해 접근하게 합니다.
핵심 구조는 아래처럼 잡겠습니다.
┌──────────────────────────────┐
│ Admin Cluster │
│ │
│ Keycloak / GitOps / CI/CD │
│ Model Schedule / Policy │
│ Observability / Registry │
└──────────────┬───────────────┘
│
configuration / identity
│
▼
┌──────────────────────────────── GPU Cluster ───────────────────────────────┐
│ │
│ Users / Compute Cluster │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Inference Gateway │ │
│ │ │ │
│ │ OIDC / Keycloak │ │
│ │ API Key │ │
│ │ User/Team Quota │ │
│ │ RPM / TPM Limit │ │
│ │ Model ACL │ │
│ │ Routing │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌──────┴────────────────────────────────┐ │
│ │ Model Router │ │
│ │ │ │
│ │ model alias → active deployment │ │
│ └──────┬────────────────────────────────┘ │
│ │ │
│ ┌────────┴────────────────────────────────────────────────────────┐ │
│ │ GPU Serving Pool │ │
│ │ │ │
│ │ DeepSeek GLM Qwen gpt-oss PaddleOCR │ │
│ │ vLLM vLLM vLLM vLLM VLM │ │
│ │ │ │
│ │ GPU HBM ─── KV cache / Prefix cache │ │
│ │ │ │ │
│ │ └────────── MemKV (optional L2 KV) │ │
│ └──────────────────────────┬──────────────────────────────────────┘ │
│ │ │
│ ┌─────────▼──────────┐ │
│ │ Local NVMe x8 │ │
│ │ RAID0 + XFS │ │
│ │ │ │
│ │ model-cache │ │
│ │ staging │ │
│ │ scratch │ │
│ └─────────┬──────────┘ │
└──────────────────────────────┼─────────────────────────────────────────────┘
│ S3
▼
┌─────────────────────┐
│ Storage Cluster │
│ │
│ AIStor │
│ │
│ models/ │
│ datasets/ │
│ artifacts/ │
│ checkpoints/ │
│ Memory Bucket (*) │
└─────────────────────┘
AIStor 자체는 inference engine이 아니라 object/data tier입니다. 공식 문서도 AIStor가 모델 inference를 실행하는 것이 아니라 accelerator가 사용하는 데이터를 공급하는 계층이라고 명확히 구분합니다. MinIO AIStor Documentation
사용자가 이런 식으로 각 서비스를 직접 호출하게 만들지 않는 게 좋습니다.
deepseek-service:8000
glm-service:8000
qwen-service:8000
...
대신:
inference.company.local
│
▼
Inference Gateway
│
┌────────────────┼─────────────────┐
▼ ▼ ▼
DeepSeek GLM Qwen
로 만듭니다.
가능하면 API도 OpenAI-compatible 형태로 통일합니다.
POST /v1/chat/completions
model: deepseek-flash
model: glm-flash
model: qwen
model: gpt-oss
그러면 client는 backend가 vLLM인지 다른 inference engine인지 알 필요가 없습니다.
Keycloak은 이미 Admin cluster에 있으므로 적극 활용할 수 있습니다.
User
│
▼
Keycloak
│
│ JWT
▼
Inference Gateway
│
├─ identity
├─ group/team
├─ allowed models
├─ RPM
├─ TPM
├─ concurrency
└─ daily/monthly token quota
예를 들어 정책을:
| Group | Model | RPM | TPM | Concurrent | 일 사용량 |
|---|---|---|---|---|---|
| team-A | GLM | 60 | 500K | 10 | 20M token |
| team-A | DeepSeek | 30 | 300K | 5 | 10M |
| team-B | Qwen | 100 | 1M | 20 | 50M |
| batch | GLM | 10 | 5M | 4 | 200M |
처럼 관리합니다.
단순 request count보다 token 단위 quota가 중요합니다.
request A = 500 tokens
request B = 50,000 tokens
은 GPU 비용이 전혀 다르기 때문입니다.
따라서 최소한:
RPM requests/min
TPM tokens/min
Concurrent requests
Max input tokens
Max output tokens
Daily token quota
정도는 Gateway에서 제어하는 것을 권합니다.
사용자에게:
glm-5.3-flash-fp8-tp4-ep4-v20261001
같은 실제 deployment 이름을 노출하면 운영하기 어렵습니다.
논리 alias를 둡니다.
사용자
model="glm-flash"
│
▼
Model Router
│
└──> glm-5.3-flash-fp8-v3
그러면 모델 교체도:
glm-flash
│
├─ old → GLM 5.3 Flash v2
│
└─ new → GLM 5.3 Flash v3
처럼 backend만 바꾸면 됩니다.
이 구조가 주간/야간 모델 전환에도 핵심입니다.
예를 들어:
주간 08:00~22:00
GPU0~3 DeepSeek-V4.1-Flash
GPU4~7 GLM-5.3-Flash
+ OCR/Docling 별도 resource
야간 22:00~08:00
GPU0~3 GLM-5.3
GPU4~5 Qwen
GPU6 gpt-oss
GPU7 spare/replica
라고 합시다.
단순히 22:00에:
scale day models = 0
scale night models = 1
하면 안 됩니다.
모델 loading 시간이 있기 때문입니다.
제가 권하는 전환은:
21:30
│
├─ Night model prefetch
│ AIStor → Local NVMe
│
21:45
│
├─ GPU allocation 준비
│
├─ model load
│ NVMe → RAM → HBM
│
└─ warm-up
│
21:55
│
├─ readiness check
└─ test inference
│
22:00
│
├─ Model Router switch
│
│ day → night
│
└─ 신규 request → Night Model
│
22:00+
│
├─ Day Model request drain
│
└─ Day Model unload
즉 Blue/Green deployment와 비슷하게 생각하면 됩니다.
Admin/GitOps에는 모델 schedule을 선언합니다.
예를 들면 개념적으로:
day:
start: "08:00"
models:
- deepseek-v4.1-flash
- glm-5.3-flash
- paddleocr-vl
- docling
night:
start: "22:00"
models:
- glm-5.3
- qwen3.6-35b-a3b
- gpt-oss-20b
Model Lifecycle Controller가 이를 보고:
Schedule
│
▼
Model Lifecycle Controller
│
├── prefetch
├── cache validation
├── deployment
├── GPU allocation
├── warmup
├── readiness
├── route switch
├── drain
└── unload
을 수행하게 합니다.
그러면 단순 scheduler보다 훨씬 안전합니다.
모델 weight는:
COLD / Durable
AIStor
│
S3 GET
│
▼
Local NVMe
/model-cache
│
▼
RAM
│
▼
GPU HBM
HOT / Fast
입니다.
AIStor가 source of truth입니다.
s3://models/
deepseek/
v4.1-flash/
manifest.json
config.json
tokenizer/
weights/
glm/
5.3-flash/
5.3/
qwen/
3.6-35b-a3b/
gpt-oss/
20b/
Local NVMe는:
/model-cache/
deepseek-v4.1-flash/
glm-5.3-flash/
glm-5.3/
qwen3.6/
gpt-oss/
입니다.
Local NVMe는 cache일 뿐 authoritative storage가 아닙니다.
이게 네 환경의 큰 장점입니다.
보통 cache는:
LRU
로 관리하지만 여기서는 다음에 필요한 모델을 이미 알고 있습니다.
따라서:
19:00
/model-cache
Day models
████████████
Night models
████████
↑
미리 존재
시키는 것이 훨씬 좋습니다.
NVMe capacity가 충분하다면 주간+야간 모델을 전부 cache에 유지하는 게 최선입니다.
AIStor
│
│ 최초 1회 / version 변경 시
▼
Local NVMe
├── Day model set
└── Night model set
↓
시간 전환 시
NVMe → HBM만 수행
그러면 매일 AIStor에서 수백 GB~TB를 다시 가져올 이유가 없습니다.
예를 들어:
/model-cache/
sha256-AAAA/
weights...
와 같이 관리합니다.
AIStor에:
models/glm-5.3/manifest.json
version: 2026-10-01
sha256: AAAA...
가 있다면 controller가:
AIStor manifest
│
▼
Local cache manifest
│
├─ same hash
│ → HIT
│
└─ different
→ DOWNLOAD
하게 합니다.
Air-gap 환경에서는 특히 재현성과 artifact integrity 면에서 유리합니다.
여기서는 역할을 정확히 분리해야 합니다.
Model Weight Path
─────────────────
AIStor
↓
Local NVMe
↓
GPU HBM
KV Path
─────────────────
GPU HBM
↓
MemKV
즉 MemKV에 model weight를 저장하려는 게 아닙니다.
MemKV는 inference KV state의 external tier입니다.
현재 MinIO MemKV는 NVIDIA NIXL을 통해 inference framework와 연결하고, distributed RDMA+NVMe 구성뿐 아니라 GPU node에 co-locate하는 구성도 제공합니다. MinIO AIStor Documentation
GPU
│
┌───────▼───────┐
│ HBM KV Cache │
│ L1 │
└───────┬───────┘
│
cache pressure
│
┌───────▼───────┐
│ MemKV │
│ L2 │
│ │
│ RAM / NVMe │
│ RDMA/NIXL │
└───────────────┘
이게 특히 유리한 workload는:
긴 system prompt
긴 document/RAG context
multi-turn conversation
agent loop
동일 prefix 반복
입니다.
MemKV 공식 sizing 문서도 long-context, shared-prefix, multi-turn, HBM prefix cache보다 큰 working set을 주된 유효 영역으로 설명합니다. 반대로 prompt가 대부분 고유하거나 HBM에 전부 들어가면 external KV tier 자체가 overhead가 될 수 있습니다. MinIO AIStor Documentation
따라서 모든 모델에 무조건 MemKV를 켜지는 않겠습니다.
초기 정책은 이런 식이 좋습니다.
| Model/workload | HBM KV | MemKV | 이유 |
|---|---|---|---|
| DeepSeek long-context | O | O | KV reuse 가능성 |
| GLM agent/RAG | O | O | long context/multi-turn |
| Qwen chat | O | PoC | workload에 따라 |
| gpt-oss short chat | O | △ | HBM으로 충분할 수 있음 |
| PaddleOCR | O | X | KV externalization 이득 작음 |
| Docling | - | X | 해당 없음 |
MemKV 자체 benchmark에서도 모델과 concurrency에 따라 효과 차이가 상당히 크므로, TTFT / throughput / KV hit ratio를 측정해서 모델별 enable하는 게 맞습니다. MinIO AIStor Documentation
여기는 MemKV와 혼동하면 안 됩니다.
AIStor Memory는 LLM inference KV cache가 아닙니다.
현재 AIStor Memory는 agent의 workspace/long-term memory를 AIStor Memory Bucket에 저장하고, local NVMe를 staging/read cache로 사용해 파일 형태로 제공하는 구조입니다. MinIO AIStor Documentation
따라서:
Inference Platform
LLM
│
┌────────┴────────┐
│ │
KV Cache Agent Memory
│ │
MemKV AIMem
│ │
fast inference persistent
state state
│
▼
AIStor
로 구분하면 정확합니다.
예를 들어 사용자별 AI agent가:
사용자 A
conversation
documents
preferences
agent workspace
generated files
를 세션을 넘어 유지해야 한다면 AIMem이 유용합니다.
User A
│
Agent Pod
│
├── LLM inference ─────── GPU/vLLM
│
└── /workspace
│
AIMem
│
local NVMe cache
│
▼
AIStor Memory Bucket
AIStor Memory는 local directory를 staging + read cache로 사용하고 cache hit 시 local disk에서 FUSE passthrough로 읽습니다. MinIO AIStor Documentation
그래서 일반 inference API에 AIMem을 억지로 넣을 필요는 없습니다.
Agentic AI를 서비스할 때 추가하는 계층입니다.
결국 네 환경에서는 서로 다른 세 가지 cache가 존재합니다.
GPU HBM
┌─────────────────┐
│ Model Weight │
│ KV Cache L1 │
└───────┬─────────┘
│
┌─────────┴─────────┐
│ │
MemKV Local NVMe
KV Cache L2 Model Cache
│ │
│ │
conversation Model Weight
prefix/KV staging
│ │
└─────────┬─────────┘
│
AIStor
durable storage
AIMem은 별도로:
Agent
│
AIMem
│
Local NVMe
│
AIStor Memory Bucket
입니다.
AIStor에도 object access용 cache 기능이 있지만, 이것을 GPU node의 /model-cache와 동일시하면 안 됩니다.
AIStor cache는 object-store server 쪽 cache이고, 현재 문서상 drive별 accessed-object metadata 등을 node memory에 cache하며 memory 한도도 관리합니다. MinIO AIStor Documentation
우리가 설계한:
GPU node
/model-cache
는 GPU workload를 위해 직접 관리하는 model staging cache입니다.
따라서 두 cache는 동시에 존재할 수 있습니다.
Gateway:
requests/user
tokens/user
model/user
latency
TTFT
TPOT
errors
quota rejection
GPU/vLLM:
tokens/sec
batch size
queue
KV utilization
prefix hit
GPU utilization
HBM
MemKV:
KV hit
KV miss
read BW
write BW
RDMA BW
그리고 DCGM:
GPU utilization
HBM
power
temperature
NVLink
PCIe
XID
를 수집합니다.
Admin cluster의 Prometheus/Grafana 계열로 중앙 집계하면:
User
↓
Gateway
↓
Model
↓
GPU
↓
KV
를 하나의 request ID로 추적할 수 있습니다.
예를 들어 Night GLM warm-up이 실패했다고 해서 22:00에 Day GLM을 내려버리면 안 됩니다.
Controller state machine을:
PREFETCH
↓
VERIFY
↓
LOAD
↓
WARMUP
↓
HEALTHY?
/ \
NO YES
│ │
ABORT SWITCH
│ │
keep ↓
old DRAIN OLD
model ↓
UNLOAD
로 만드는 게 좋습니다.
즉 schedule은 전환을 시작하는 trigger일 뿐, traffic switch는 health condition에 의해 결정되어야 합니다.
전체를 합치면 저는 이 그림을 목표 architecture로 잡겠습니다.
Users / Applications
│
▼
┌──────────────────────────┐
│ Inference Gateway │
│ │
│ Keycloak/OIDC │
│ API Key │
│ Model ACL │
│ RPM / TPM │
│ Token Quota │
│ Concurrency Limit │
└────────────┬─────────────┘
│
▼
┌──────────────────────────┐
│ Model Router │◀──────── Model Lifecycle Controller
└────────────┬─────────────┘ │
│ │ schedule
│ │ prefetch
│ │ warmup
│ │ switch/drain
▼ │
┌───────────────────────────────────────────────────────────┐
│ GPU Serving Pool │
│ │
│ DeepSeek GLM Qwen gpt-oss │
│ │
│ vLLM / inference runtime │
│ │ │
│ GPU HBM │
│ Weight + KV L1 │
│ │ │
│ MemKV L2 │
│ NIXL / RDMA │
└─────────────────────┬─────────────────────────────────────┘
│
┌────────▼─────────┐
│ Local NVMe x8 │
│ RAID0 + XFS │
│ │
│ /model-cache │
│ /scratch │
└────────┬────────┘
│
│ S3 / high-speed Ethernet
▼
┌──────────────────┐
│ AIStor │
│ │
│ Models │
│ Datasets │
│ Artifacts │
│ Checkpoints │
│ Memory Buckets │◀── AIMem / Agents
└──────────────────┘
AIStor S3 data path는 우선 기존 high-speed Ethernet을 사용하고, 실제 model staging이 병목이면 S3 over RDMA를 Phase 2로 PoC할 수 있습니다. 현재 AIStor는 Kubernetes에서 RDMA 전용 server image와 single/multi-NIC topology를 공식 지원합니다. MinIO AIStor Documentation
그리고 air-gap 환경에서는 이 모든 이미지와 Helm chart를 Admin cluster의 private registry에 mirror해서 GitOps로 배포하는 게 좋습니다. AIStor도 private registry와 air-gap 배포 절차를 공식 지원합니다. MinIO AIStor Documentation
결국 핵심은 Gateway → Router → Serving → HBM/MemKV → local NVMe → AIStor의 계층화와, 별도의 Model Lifecycle Controller가 주/야간 전환을 책임지는 구조입니다. 이 구조라면 나중에 GPU node가 늘어나도 사용자 API/쿼터 계층은 그대로 두고 serving pool만 수평 확장할 수 있습니다.
===
아니요. K8s + Cilium + AIStor만으로 “돌아가게” 만들 수는 있지만, 앞에서 요구한 멀티유저, 사용자/팀별 quota, OpenAI-compatible API, 모델별 접근제어, 주/야간 모델 routing까지 직접 구현해야 해서 운영 부담이 커집니다.
현재 구조에는 LiteLLM 같은 AI Gateway를 하나 추가하는 것을 추천합니다. 다만 LiteLLM이 GPU-aware load balancing까지 전부 맡게 하기보다는 역할을 분리하는 편이 좋습니다.
Users / Applications
│
▼
┌──────────────────────┐
│ LiteLLM │
│ AI Gateway │
│ │
Keycloak ─────────>│ Auth / User / Team │
│ Virtual API Key │
│ Model ACL │
│ RPM / TPM │
│ Concurrency │
│ Usage / Quota │
│ OpenAI-compatible API│
└──────────┬───────────┘
│
logical model
│
┌─────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
deepseek glm-flash qwen
│ │ │
└─────────────────┼──────────────────┘
▼
┌─────────────────────────┐
│ K8s Inference Routing │
│ │
│ Gateway API │
│ Inference Extension │
│ / EPP │
└────────────┬────────────┘
│
GPU/KV-aware endpoint 선택
│
┌──────────────┼──────────────┐
▼ ▼ ▼
vLLM #1 vLLM #2 vLLM #N
│ │ │
└──────────────┼──────────────┘
│
B300 / NVSwitch
│
HBM / KV Cache
│
┌─────┴─────┐
│ │
MemKV Local NVMe
│
AIStor
LiteLLM은 user/team/key별 RPM, TPM, 최대 parallel request 및 budget 등을 제공하므로 앞서 말한 멀티테넌트 quota 계층을 직접 개발할 필요를 크게 줄여줍니다. 다만 budget/virtual-key 기능은 DB가 필요하므로 air-gap 내부 PostgreSQL도 stack에 포함해야 합니다. LiteLLM
이 구분이 중요합니다.
Cilium
────────────────────────────
Network / CNI
NetworkPolicy
BGP
ClusterMesh
Service connectivity
Gateway 기반 network ingress
≠
LiteLLM
────────────────────────────
AI API Gateway
User / Team
API Key
Model ACL
RPM / TPM
Token usage
Quota
Logical model routing
OpenAI-compatible API
따라서 Cilium이 있다고 LiteLLM이 불필요해지는 것은 아닙니다.
반대로 LiteLLM을 넣는다고 Cilium이 하던 일을 대체하는 것도 아닙니다.
이건 제가 지금 시점이라면 꽤 적극적으로 PoC하겠습니다.
Kubernetes에는 이제 Gateway API Inference Extension이 있고, InferencePool은 현재 stable API입니다. 일반 HTTP load balancer와 달리 KV-cache utilization, queue 상태 등의 inference 정보를 이용해 적절한 model-server Pod를 선택할 수 있습니다. Kubernetes 게이트웨이 API 확장
예를 들어 vLLM replica가 4개라면 일반 LB는:
Request
│
▼
Round Robin
vLLM1 → KV 90%
vLLM2 → KV 20%
vLLM3 → KV 70%
vLLM4 → KV 30%
상태를 모르고 보낼 수 있습니다.
Inference-aware routing은 개념적으로:
Request
│
▼
Endpoint Picker
│
┌───────────┼────────────┐
│ KV usage │
│ queue │
│ prefix cache │
▼ │
vLLM2 ◀──────────────────┘
KV 20%
같은 결정을 할 수 있습니다. Kubernetes Inference Extension 자체도 prefix-cache 상태나 LoRA availability 같은 model-serving 정보를 routing에 활용하도록 설계되어 있습니다. Kubernetes 게이트웨이 API 확장
즉 LiteLLM과 Inference Gateway도 서로 대체 관계가 아닙니다.
LiteLLM
"누가 어떤 모델을 얼마나 사용할 수 있는가?"
│
▼
Inference Gateway/EPP
"그 요청을 어느 GPU/vLLM replica가 처리하는 게 좋은가?"
로 나누면 됩니다.
| 계층 | 솔루션 | 역할 | 추천 |
|---|---|---|---|
| Container orchestration | Kubernetes | 전체 workload | 필수 |
| CNI | Cilium | Pod networking/BGP | 필수 |
| Multi-cluster | Cilium ClusterMesh | Compute↔GPU↔Storage | 필수 |
| Identity | 기존 Keycloak | OIDC/SSO | 필수 |
| AI Gateway | LiteLLM | 사용자/API key/quota/TPM/RPM | 추천 |
| Gateway DB | PostgreSQL | LiteLLM state/usage | 추천 |
| Inference routing | Gateway API Inference Extension | GPU/KV-aware routing | 추천/PoC |
| Model server | vLLM | 실제 LLM inference | 필수 |
| GPU management | NVIDIA GPU Operator | driver/device plugin/DCGM | 필수 |
| Object Storage | AIStor | model/dataset/checkpoint | 필수 |
| Model cache | NVMe RAID0+XFS | local staging | 필수 |
| KV L1 | vLLM HBM | hot KV | 필수 |
| KV L2 | MemKV | external KV | workload별 PoC |
| Agent memory | AIMem | persistent agent workspace | Agent 사용 시 |
| Observability | Prometheus/Grafana/DCGM | metrics | 필수 |
| GitOps | 현재 Admin CI/CD/GitOps | lifecycle | 기존 사용 |
| Model Lifecycle | Argo Workflow/자체 Controller 등 | 주/야간 전환 | 필요 |
vLLM 자체에서도 generation/prompt token, KV-cache usage, prefix-cache hit, external prefix-cache hit 등 production metrics를 제공하므로 이 routing/monitoring 계층과 연결하기 좋습니다. vLLM
Phase 1은 최대한 단순하게 가는 게 좋습니다.
Users
│
Keycloak
│
LiteLLM
│
▼
vLLM
│
B300
Model:
AIStor → NVMe → HBM
KV:
HBM
여기까지 먼저 안정화합니다.
이 단계만으로도 사용자별 API key/model access/RPM/TPM/concurrency/usage 관리가 가능합니다. LiteLLM의 virtual key와 team 모델은 이런 multi-tenant control을 제공하도록 되어 있습니다. LiteLLM
GPU node가 늘어나고 동일 모델 replica가 여러 개 생기면:
LiteLLM
│
▼
K8s Gateway API
Inference Extension
│
▼
InferencePool
│
┌─┼───────────┐
▼ ▼ ▼
vLLM1 vLLM2 vLLM3
를 넣습니다.
Kubernetes Inference Extension은 model-aware routing, serving priority, model rollout/traffic splitting, endpoint metrics 기반 routing을 목적으로 만들어져 있어서 네 요구사항과 상당히 잘 맞습니다. Kubernetes 게이트웨이 API 확장
실제 workload를 측정해서:
long context
shared prefix
multi-turn
agent
RAG
↓
KV cache miss/recompute 비용이 큼
이면:
vLLM
│
├─ HBM KV L1
│
└─ MemKV L2
│
RDMA
를 추가합니다.
vLLM 자체의 Automatic Prefix Caching도 있기 때문에 먼저 HBM prefix caching 효과를 측정하고 MemKV를 추가하는 게 맞습니다. vLLM의 prefix caching은 동일 prefix의 KV blocks를 재사용해 반복적인 prompt computation을 줄이는 방식입니다. vLLM
이 부분도 역할을 분리하겠습니다.
Model Lifecycle Controller
│
┌───────────────┼───────────────┐
│ │ │
AIStor Kubernetes LiteLLM
│ │ │
prefetch deploy route
│ │ │
▼ ▼ ▼
NVMe vLLM Pod model alias
예를 들어 22:00 야간 전환:
21:30 Night models
AIStor → NVMe prefetch
21:40 checksum/version verify
21:45 Night vLLM deployment
21:50 NVMe → HBM load
21:55 warm-up
health check
22:00 LiteLLM route/alias switch
22:00+ Day request drain
22:05 Day vLLM unload
이 workflow는 기존 Admin cluster의 GitOps + Argo Workflows/CronWorkflow 계열로 시작해도 충분합니다. 처음부터 custom Kubernetes Operator를 개발할 필요는 없습니다.
넣는 쪽을 추천합니다.
특히 네 요구사항 중:
여러 사용자
+
사용자/팀별 제한
+
모델별 접근권한
+
OpenAI-compatible API
+
사용량 측정
이 있기 때문에 그렇습니다.
K8s+Cilium만 가지고 이걸 구현하면 결국 별도의 API Gateway에서 JWT→사용자→모델 ACL→TPM/RPM→token accounting→quota DB를 직접 개발하게 됩니다. LiteLLM은 이 부분을 이미 제공합니다. LiteLLM
반대로 LiteLLM에게 GPU scheduling, NUMA, KV-aware replica selection, AIStor cache lifecycle까지 맡기려고 하면 안 됩니다.
최종적으로 역할을 이렇게 자르면 아주 명확합니다.
┌─────────────────────────────┐
│ User │
└──────────────┬──────────────┘
▼
Keycloak ────────────────► LiteLLM
│
│ WHO / HOW MUCH
│
▼
Inference Gateway
│
│ WHICH REPLICA
│
▼
vLLM
│
│ INFERENCE
┌─────────────┼──────────────┐
│ │ │
HBM KV MemKV NVMe
│ KV L2 model cache
│ │ │
└─────────────┼──────────────┘
│
AIStor
durable data
Kubernetes
└── scheduling / lifecycle
Cilium
└── networking / BGP / ClusterMesh
NVIDIA GPU Operator
└── GPU infrastructure
Model Lifecycle Controller
└── Day ↔ Night automation
따라서 제가 잡을 1차 production baseline은 K8s + Cilium + NVIDIA GPU Operator + vLLM + LiteLLM + PostgreSQL + Keycloak + AIStor + Local NVMe + Prometheus/Grafana입니다. Gateway API Inference Extension은 replica가 많아지는 시점에 추가하고, MemKV는 KV reuse 측정 후 추가, AIMem은 Agent 서비스를 실제로 제공할 때 추가하는 순서가 과설계를 피하면서도 확장하기 좋습니다.
===
네. 둘을 같이 쓰면 이름 때문에 둘 다 “Gateway”처럼 보여 헷갈리는데, 관심사가 완전히 다릅니다.
가장 간단히 말하면:
LiteLLM = “이 사용자가 어떤 모델을 얼마나 사용할 수 있는가?”
Gateway API Inference Extension = “허용된 이 inference 요청을 어느 vLLM/GPU replica에 보내야 가장 효율적인가?”
입니다.
Kubernetes의 일반적인 Service/LB는 inference workload의 내부 상태를 잘 모릅니다.
예를 들어 동일한 GLM 모델을 GPU node 4대에서 서비스한다고 해보겠습니다.
GLM Service
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
vLLM-A vLLM-B vLLM-C
GPU0-3 GPU0-3 GPU0-3
Queue 20 2 8
KV Cache 90% 30% 60%
Prefix X O X
일반적인 Kubernetes Service가 이런 inference 상태를 고려하지 않으면 요청을 단순하게 여러 endpoint에 분산할 수 있습니다.
문제는 LLM 요청은 동일한 비용을 갖지 않는다는 것입니다.
request A
input = 500 tokens
request B
input = 100,000 tokens
둘 다 HTTP request 하나지만 GPU가 처리해야 하는 양은 전혀 다릅니다.
더구나 vLLM-A에 사용자가 보내는 prompt의 prefix KV가 이미 존재한다면 A로 보내는 것이 훨씬 유리할 수도 있습니다.
Gateway API Inference Extension은 이런 LLM inference 특성을 고려해 endpoint를 선택하는 계층입니다.
Kubernetes Gateway API Inference Extension
InferencePool + EPP개념적으로 보면 다음과 같습니다.
Client Request
│
▼
Gateway / Route
│
▼
┌──────────────────┐
│ InferencePool │
│ │
│ GLM replicas │
└────────┬─────────┘
│
▼
┌───────────────────┐
│ EPP │
│ Endpoint Picker │
│ │
│ "어느 Pod로?" │
└─────────┬─────────┘
│
┌─────────┼──────────┐
▼ ▼ ▼
vLLM-A vLLM-B vLLM-C
│ │ │
GPU GPU GPU
여기서 특히 중요한 것이 EPP(Endpoint Picker) 입니다.
EPP가 inference workload 정보를 이용해서 적절한 endpoint를 고르는 역할을 합니다.
일반적인 LB를 아주 단순화하면:
request 1 → Pod A
request 2 → Pod B
request 3 → Pod C
request 4 → Pod A
같은 접근입니다.
Inference-aware routing은 다음과 같은 정보를 활용할 수 있는 구조입니다.
Request
│
▼
EPP
│
├── model availability
├── request queue
├── KV/cache 관련 상태
├── serving capacity
└── endpoint 상태
│
▼
best endpoint
그래서 LLM inference에서 중요한 latency, GPU utilization, KV reuse 등을 개선할 가능성이 생깁니다.
예를 들어 사용자들이 공통으로 아주 긴 system prompt를 사용한다고 합시다.
System prompt 20K tokens
+
user prompt
Pod A에 이미 이 prefix에 대한 cache가 있다면:
vLLM-A
[20K system prompt KV]
████████████████████
→ HIT
반면 Pod B에는 없다면:
vLLM-B
[empty]
→ 다시 계산
일 수 있습니다.
따라서 단순히:
GPU utilization 낮은 Pod
를 고르는 것만이 항상 최선은 아닙니다.
LLM에서는 경우에 따라:
prefix/KV locality
+
queue
+
GPU capacity
+
model availability
를 같이 고려해야 합니다.
이런 이유로 Kubernetes 쪽에서 일반적인 HTTP load balancing과 inference-aware load balancing을 분리하려는 것입니다.
우리가 지금 설계한 구조에 넣으면:
Users
│
▼
LiteLLM
│
│
model="glm"
│
▼
Gateway API / Route
│
▼
InferencePool
│
▼
EPP
│
┌──────────────┼───────────────┐
│ │ │
▼ ▼ ▼
GLM Pod-1 GLM Pod-2 GLM Pod-3
vLLM vLLM vLLM
│ │ │
GPU GPU GPU
라고 보면 됩니다.
즉 LiteLLM이 굳이:
Pod-1의 KV cache가 83%이고 Pod-2의 queue가 3이니 Pod-2로 보내자.
같은 GPU-level scheduling까지 책임질 필요가 없습니다.
그건 아래 inference routing layer가 담당하도록 분리하는 겁니다.
LiteLLM은 GPU scheduler라기보다 AI API Gateway라고 이해하는 게 좋습니다.
네 환경에서 제가 LiteLLM에게 맡기고 싶은 것은 크게 다음입니다.
| 기능 | LiteLLM |
|---|---|
| OpenAI-compatible API | O |
| API Key | O |
| 사용자/Team 관리 | O |
| 모델 접근 권한 | O |
| RPM 제한 | O |
| TPM 제한 | O |
| Budget/사용량 관리 | O |
| 모델 logical alias | O |
| 요청/비용 accounting | O |
| GPU 선택 | X |
| NUMA 선택 | X |
| GPU scheduling | X |
| NVLink/NVSwitch 관리 | X |
| Kubernetes scheduling | X |
| AIStor model cache 관리 | X |
| 주/야간 model lifecycle 전체 | X |
즉 North-South의 AI API control plane 역할에 가깝습니다.
Keycloak이 이미 있으므로 역할을 중복시키지 않습니다.
User
│
▼
Keycloak
│
Authentication
│
JWT
│
▼
LiteLLM
│
Authorization /
AI Policy
역할을 구분하면:
Keycloak
────────────────────────
"당신은 누구인가?"
user = kim
group = AI-Team-A
LiteLLM
────────────────────────
"그 사람이 AI를 어떻게 사용할 수 있는가?"
Team-A
├─ DeepSeek allowed
├─ GLM allowed
├─ Qwen denied
│
├─ RPM 100
├─ TPM 1M
└─ Budget ...
이런 식입니다.
Keycloak을 identity provider로 유지하고 AI-specific policy는 LiteLLM에서 관리하는 것이 깔끔합니다.
뒤에서는 모델별 serving 방식이 달라도 됩니다.
DeepSeek
↓
vLLM
GLM
↓
vLLM
Qwen
↓
vLLM
PaddleOCR
↓
별도 inference runtime
사용자는 가능한 한 하나의 API를 사용합니다.
LiteLLM
POST /v1/chat/completions
model="deepseek"
model="glm"
model="qwen"
따라서 나중에 backend가 바뀌어도 client application 변경을 최소화할 수 있습니다.
예를 들어 실제 Kubernetes deployment가:
glm-5.3-flash-fp8-vllm-0.15-prod-v3
라고 해도 사용자는 그냥:
model = "glm-flash"
만 사용하게 합니다.
중간 mapping을:
glm-flash
│
▼
glm-5.3-flash-v3
로 관리합니다.
새 모델을 배포하면:
glm-flash
│
├──── old → GLM v3
│
└──── new → GLM v4
처럼 backend를 교체할 수 있습니다.
예를 들어 주간에는:
08:00 ~ 22:00
deepseek-flash
glm-flash
paddleocr
야간에는:
22:00 ~ 08:00
glm
qwen
gpt-oss
라고 합시다.
사용자가 Kubernetes Deployment를 알 필요는 없습니다.
Model Lifecycle Controller가:
21:30
AIStor → NVMe
night model prefetch
↓
21:45
vLLM Deployment
↓
21:50
NVMe → HBM
↓
21:55
warm-up
↓
health OK
까지 수행하고 나서 routing/API availability를 바꿉니다.
여기서 LiteLLM은 사용자가 볼 수 있는 모델과 logical endpoint를 제어하는 front door 역할을 할 수 있습니다.
다만 실제 Deployment 생성/삭제, prefetch, HBM load 등은 LiteLLM이 아니라 Kubernetes/GitOps/Workflow/Model Lifecycle Controller가 담당해야 합니다.
사용자 kim이 GLM에 요청했다고 합시다.
kim
│
▼
LiteLLM
│
├─ kim 인증됐나?
├─ GLM 사용 권한 있나?
├─ quota 남았나?
├─ TPM 초과했나?
└─ concurrent request 초과했나?
│
│ OK
▼
여기까지는 User/AI Policy 문제입니다.
GLM request
│
▼
InferencePool
│
▼
EPP
│
┌─────┼──────┐
▼ ▼ ▼
Pod A Pod B Pod C
queue queue queue
cache cache cache
│
└──────► best endpoint
여기는 Serving optimization 문제입니다.
request
│
▼
vLLM
│
├─ batching
├─ scheduling
├─ prefix cache
├─ KV management
├─ TP / EP
│
▼
GPU
여기가 실제 inference execution입니다.
Inference Extension의 EPP와 Kubernetes Scheduler도 혼동하면 안 됩니다.
Kubernetes Scheduler
"vLLM Pod를 어느 Node에 띄울까?"
↓
GPU Node 03
반면:
Inference EPP
"이미 떠 있는 vLLM Pod 중
이 HTTP request를 어디로 보낼까?"
↓
vLLM Pod #7
입니다.
즉:
K8s Scheduler
│
│ Pod placement
▼
GPU Node
EPP
│
│ Request placement
▼
vLLM Pod
입니다.
이 차이가 굉장히 중요합니다.
네 환경에서는 Cilium을 쓰고 있으니 이것도 구분해야 합니다.
Cilium
───────────────────────
CNI
NetworkPolicy
BGP
ClusterMesh
Gateway API implementation
Network connectivity
Inference Extension
───────────────────────
InferencePool
Endpoint Picker
LLM-aware request routing
LiteLLM
───────────────────────
AI API Gateway
User / Team
API Key
Quota
RPM / TPM
Model ACL
Usage
vLLM
───────────────────────
Model execution
Batching
KV cache
TP / EP
GPU inference
그래서 이것들을 경쟁 제품처럼 볼 필요가 없습니다.
서로 다른 layer를 담당합니다.
제가 생각하는 목표 구조는 이겁니다.
┌──────────────┐
│ Keycloak │
│ Identity │
└──────┬───────┘
│
▼
Users ─────────────────────► LiteLLM
┌───────────────┐
│ Auth / Team │
│ Model ACL │
│ RPM / TPM │
│ Token Quota │
│ Usage │
│ Model Alias │
└───────┬───────┘
│
▼
Gateway API / HTTPRoute
│
▼
InferencePool
│
EPP
│
┌──────────────┼──────────────┐
│ │ │
▼ ▼ ▼
vLLM-1 vLLM-2 vLLM-3
│ │ │
└──────────────┼──────────────┘
│
B300 GPU Pool
│
┌────────────┴────────────┐
│ │
HBM KV MemKV
L1 L2
│ │
└────────────┬────────────┘
Model Weight Path
AIStor
│
S3 GET
▼
Local NVMe
/model-cache
│
▼
RAM
│
▼
HBM
그리고 이 옆에 별도의:
Model Lifecycle Controller
│
┌──────────┼───────────┐
│ │ │
AIStor K8s LiteLLM
│ │ │
prefetch deploy route/
verify warmup policy
│ │ │
└──────────┼───────────┘
│
Day ↔ Night
가 있는 형태가 가장 명확합니다.
처음 구축할 때 Gateway API Inference Extension을 반드시 넣어야 하는 것은 아닙니다.
예를 들어 초기 환경이:
GLM → vLLM replica 1개
Qwen → vLLM replica 1개
DeepSeek → vLLM replica 1개
라면 EPP가 고를 endpoint 자체가 없습니다. 이 단계에서는:
Keycloak
↓
LiteLLM
↓
vLLM
이면 충분합니다.
반면 GPU node가 늘어서:
GLM
vLLM #1
vLLM #2
vLLM #3
vLLM #4
처럼 동일 모델 replica가 여러 개 생기는 시점부터 Inference Extension의 가치가 커집니다.
따라서 지금 프로젝트라면 1차 구축은 Keycloak → LiteLLM → vLLM로 단순하게 만들고, API/metric/interface는 Inference Extension을 나중에 끼울 수 있게 설계한 뒤, multi-node/multi-replica 확장 시 InferencePool + EPP를 추가하는 단계적 접근을 권합니다.