NVIDIA B300(Blackwell Ultra, 단일 GPU당 HBM3e 288GB 탑재) 8장으로 구성된 단일 노드는 총 VRAM만 약 2.3TB에 달하며, 5세대 NVLink(양방향 1.8TB/s per GPU)로 풀메시(Full-mesh) 연결되어 있습니다.
단일 노드 내에서 여러 모델(예: DeepSeek 계열, Qwen3-32B, 경량 임베딩/리랭커 등)을 동시에 서빙할 때 NVLink 토폴로지 유지, VRAM/KV Cache 고립, 엔진 서빙 아키텍처 및 메트릭 관측성 관점에서의 베스트 프랙티스는 다음과 같습니다.
B300 8-way 노드는 NVLink Switch를 통해 8장이 단일 패브릭으로 묶여 있지만, 여러 모델을 동시에 쪼개어 서빙할 때는 NUMA 노드와 PCIe/NVLink 도메인에 맞춰 2의 거듭제곱 단위(TP=1, 2, 4, 8)로 격리해야 All-Reduce 병목이 발생하지 않습니다.
8장 노드는 통상 듀얼 소켓 CPU(NUMA 0, NUMA 1) 구조입니다. 모델 서빙 프로세스가 GPU뿐만 아니라 대응하는 NUMA 노드의 CPU 코어/메모리에 정확히 고정되어야 인터커넥트 병목이 없습니다.
# 예: GPU 0~3번은 NUMA node 0번에 고정하여 vLLM 인스턴스 기동
numactl --cpunodebind=0 --membind=0 vllm serve /models/Qwen-70B \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.90 \
--port 8001
여러 추론 프로세스가 동일 물리 노드에 공존할 때 OOM(Out of Memory) 연쇄 장애를 막는 설정입니다.
CUDA_VISIBLE_DEVICES 하드웨어 격리:CUDA_VISIBLE_DEVICES=0,1,2,3CUDA_VISIBLE_DEVICES=4,5--gpu-memory-utilization 고정 및 KV Cache 버퍼 제어:0.85 ~ 0.90 수준으로 설정해 메모리 스파이크를 방지합니다.TP=1, 2, 4)로 물리 카드를 분배하는 것이 좋습니다.노드 1대 내부에서 모델별로 각기 다른 포트(8001, 8002, 8003...)로 엔진이 뜨게 되므로, 앞단에 경량 AI 게이트웨이를 배치해야 클라이언트 관리가 단순해집니다.
model 필드에 따라 로컬 포트로 라우팅:"model": "qwen3-32b" localhost:8001"model": "deepseek-r1-distill-70b" localhost:8002"model": "bge-reranker" localhost:8003--enable-prefix-caching을 켜서 중복 토큰 연산과 KV Cache 소비를 40~60% 절감합니다.단일 노드 멀티 모델 환경에서는 "어느 모델이 어느 GPU 대역폭을 잠식하고 있는지"를 즉각 분리해 관측해야 합니다.
[B300 GPUs] ──> DCGM Exporter (포트 9400) ──┐
[vLLM #1] ──> Prometheus 메트릭 (8001) ──┼──> Prometheus Server ──> Grafana Dashboard
[vLLM #2] ──> Prometheus 메트릭 (8002) ──┘
DCGM_FI_DEV_GPU_UTIL: GPU 연산 코어 가동률DCGM_FI_DEV_MEM_COPY_UTIL: HBM3e 메모리 대역폭 포화 여부DCGM_FI_DEV_NVLINK_BANDWIDTH_TOTAL: GPU 간 All-Reduce 교환량 (TP 분할 모델 정상 동작 검증)DCGM_FI_DEV_POWER_USAGE / DCGM_FI_DEV_GPU_TEMP: B300 노드 고전력/열 부하 감시각 vLLM 인스턴스는 자체 포트(예: :8001/metrics, :8002/metrics)로 상세 메트릭을 노출합니다.
vllm:gpu_cache_usage_factor: 가장 중요. 0.85 이상 지속 시 새 요청이 큐에 쌓이며 TTFT(Time to First Token) 지연 급증.vllm:num_requests_waiting: 요청이 처리되지 못하고 큐에 머무는 수. 0보다 크면 동시 요청 처리 한계 도달을 의미.vllm:num_requests_running: 현재 PagedAttention으로 동시 디코딩 중인 배치 크기.vllm:time_to_first_token_seconds: 초기 Prefill 지연 시간.vllm:time_per_output_token_seconds: 디코딩 토큰 생성 간격 (사용자 체감 속도).| 점검 영역 | 권장 설정값 / 액션 | 목적 |
|---|---|---|
| NUMA 바인딩 | numactl --cpunodebind=X --membind=X | 소켓 간 PCIe 크로스오버 통신 억제 |
| GPU 격리 | 컨테이너별 CUDA_VISIBLE_DEVICES 명시 | 프로세스 간 VRAM 침범 및 충돌 방지 |
| KV Cache | --gpu-memory-utilization 0.85~0.90 | PagedAttention 할당 버퍼 확보로 OOM 방지 |
| 멀티 모델 분할 | TP 크기는 2의 제곱수(1, 2, 4, 8) 유지 | NVLink 전송 효율 최적화 및 비대칭 링 방지 |
| 로그 수집 | vLLM Engine 에러 로그와 DCGM XID 수집 | 하드웨어 ECC 에러 및 엔진 Crash 빠른 격리 |
LLM 서빙 환경에서 사용자/팀(Tenant)별 인증·인가 및 사용량 제한(Rate Limiting & Quota)을 vLLM 같은 백엔드 추론 엔진에 직접 맡기는 것은 불가능합니다(대부분의 오픈소스 추론 엔진은 OpenAI 규격의 API 서빙 기능만 제공하며 Auth/Accounting 레이어가 없음).
따라서 추론 엔진 앞단에 LLM 전용 AI Gateway를 배치하고, 백엔드로 Redis 및 저장소를 연동하는 아키텍처가 표준 베스트 프랙티스입니다. 가장 널리 쓰이는 표준 조합은 LiteLLM Proxy, Envoy / Kong, 또는 Portkey 기반 아키텍처입니다.
[Client / Tenant A] ──(API Key A)──┐
▼
[Client / Tenant B] ──(API Key B)──▶ [ AI Gateway (LiteLLM Proxy) ] ──▶ [ Redis ] (토큰 버킷/사용량 계량)
│
├── /v1/chat/completions (model: qwen3-32b) ──▶ vLLM #1 (GPU 0~3, :8001)
└── /v1/chat/completions (model: deepseek) ──▶ vLLM #2 (GPU 4~7, :8002)
게이트웨이가 고유한 가상 API Key(sk-team-a-..., sk-team-b-...)를 발급하고, 이 키를 특정 팀/유저 메타데이터와 매핑합니다.
Authorization: Bearer sk-... 헤더 검증qwen3-32b, bge-m3 모델만 호출 가능deepseek-r1까지 호출 가능하도록 라우팅 제어사내 Keycloak, Okta, 또는 LDAP/Active Directory와 연동하는 경우:
groups, sub, roles)을 파싱하여 테넌트를 식별하고 인가를 수행합니다.LLM의 부하 제어는 일반 웹 API(RPM 중심)와 완전히 다릅니다. RPM(Requests Per Minute), TPM(Tokens Per Minute), TPD(Tokens Per Day) / 비용 한도를 계층적으로 통제해야 합니다.
| 제어 항목 | 목적 | 기준 |
|---|---|---|
| RPM (Requests Per Min) | 추론 엔진의 큐 폭주(Queue Starvation) 방지 | 동시 처리 요청 수 제어 |
| TPM (Tokens Per Min) | KV Cache 고갈 및 순간 연산량 폭증 방지 | 입력 프롬프트 + 출력 생성 토큰 합산 |
| TPD / Budget ($/일, $/월) | 특정 팀의 일방적인 GPU 자원 독점 방지 | 일간/월간 누적 토큰 또는 환산 비용 |
오픈소스 기반으로 가장 빠르고 안정적으로 멀티테넌시를 구축할 수 있는 LiteLLM Proxy + Redis 기준 설정 예시입니다.
config.yaml 정의model_list:
# 백엔드 vLLM #1 (Qwen3-32B)
- model_name: qwen3-32b
litellm_params:
model: openai/qwen3-32b
api_base: http://localhost:8001/v1
api_key: "none"
# 백엔드 vLLM #2 (DeepSeek-R1)
- model_name: deepseek-r1
litellm_params:
model: openai/deepseek-r1
api_base: http://localhost:8002/v1
api_key: "none"
general_settings:
master_key: "sk-admin-master-key-here" # 게이트웨이 관리자 키
database_url: "postgresql://user:pass@localhost:5432/litellm" # 키/유저 영구 저장소
redis_url: "redis://localhost:6379" # 분산 Rate Limiting용 캐시
docker run -d \
-v $(pwd)/config.yaml:/app/config.yaml \
-p 8000:4000 \
ghcr.io/berriai/litellm:main-latest \
--config /app/config.yaml
curl -X POST 'http://localhost:8000/key/generate' \
-H 'Authorization: Bearer sk-admin-master-key-here' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "service_team_a",
"models": ["qwen3-32b"],
"max_budget": 100, # 예산 한도 ($ 또는 환산 단위)
"rpm_limit": 60, # 분당 60회 요청 제한
"tpm_limit": 100000, # 분당 10만 토큰 제한
"duration": "30d"
}'
# 반환된 가상 키(sk-...)를 팀 A에 배포
curl -X POST 'http://localhost:8000/key/generate' \
-H 'Authorization: Bearer sk-admin-master-key-here' \
-H 'Content-Type: application/json' \
-d '{
"user_id": "research_team_b",
"models": ["qwen3-32b", "deepseek-r1"],
"rpm_limit": 300,
"tpm_limit": 500000
}'
게이트웨이의 계량 기능 외에, 특정 테넌트가 B300 노드의 VRAM 전체를 고갈시키는 것을 막기 위해 추론 엔진 레벨에서도 방어벽을 세워야 합니다.
--max-model-len) 강제:--max-model-len 32768 등으로 현실적인 한도를 강제합니다.--max-num-seqs):429 Too Many Requests와 함께 Retry-After: 3 헤더를 내려주어 클라이언트 SDK가 지수 백오프(Exponential Backoff)를 수행하도록 유도합니다.네, 가능합니다. 엔터프라이즈 환경에서는 개발자/서비스팀마다 수동으로 API Key를 발급하기보다 사내 IdP(Keycloak, Microsoft Entra ID / Active Directory, Okta 등)와 연동하여 OAuth2/OIDC JWT Bearer 토큰으로 인증 및 인가를 처리하는 구조가 표준입니다.
구현 방식은 크게 AI Gateway 내장 OIDC 기능 활용과 앞단 Reverse Proxy(Envoy/Kong) 계층 분리의 두 가지 패턴으로 나뉩니다.
[클라이언트/사내 유저]
│
│ 1. 인증 요청 (Client Credentials 또는 사내 계정 SSO)
▼
[Keycloak / AD (IdP)] ──(유저 그룹/권한/부서 클레임 포함)──▶ Access Token (JWT) 발급
│
│ 2. API 호출 (Authorization: Bearer <JWT>)
▼
[AI Gateway (LiteLLM Proxy 등)]
│
├─▶ 3. JWKS 검증 (서명 검증, 만료 확인)
├─▶ 4. JWT Claim 파싱 (sub=팀ID, groups=부서명, scope 등)
├─▶ 5. Redis 기반 Dynamic Quota 체크 (해당 부서/팀의 TPM/RPM 잔여량)
│
▼ (정상 통과 시)
[vLLM 추론 인스턴스 (B300)]
사내 Keycloak(또는 Azure AD / On-Prem AD FS)에서 다음을 구성합니다.
Client ID: ai-inference-platformClient Authentication: On (Service Account 기반 머신-투-머신 통신 시)Standard Flow (유저 인터랙티브 로그인용) 또는 Service Accounts Roles (서버-투-서버 연동용 client_credentials)groups: ["data-platform-team", "ai-research"] 또는 department: "sre"LiteLLM은 수신된 Authorization: Bearer <JWT> 헤더를 사내 IdP의 JWKS(Public Key) 엔드포인트로 직접 검증할 수 있습니다.
config.yaml 설정 예시model_list:
- model_name: qwen3-32b
litellm_params:
model: openai/qwen3-32b
api_base: http://localhost:8001/v1
- model_name: deepseek-r1
litellm_params:
model: openai/deepseek-r1
api_base: http://localhost:8002/v1
general_settings:
master_key: "sk-admin-master-key"
redis_url: "redis://localhost:6379"
router_settings:
# Keycloak / AD의 OIDC 설정
jwt_auth:
# Keycloak의 JWKS URL (서명 공개키 자동 갱신)
jwks_url: "https://keycloak.company.com/realms/corp-realm/protocol/openid-connect/certs"
# 토큰의 Issuer 검증
expected_issuer: "https://keycloak.company.com/realms/corp-realm"
# 토큰의 Audience 검증
expected_audience: "ai-inference-platform"
# 테넌트 식별자로 사용할 JWT Claim 필드 (예: client_id, sub, 또는 커스텀 클레임)
user_id_jwt_field: "preferred_username"
# 팀/부서 식별용 Claim (그룹 매핑)
team_id_jwt_field: "department"
JWT의 특정 Claim(예: department)에 따라 허용 모델과 쿼터를 Gateway 데이터베이스에 사전 정의해 둘 수 있습니다.
# 'data-sre' 부서에 대한 기본 쿼터 및 접근 모델 정의
curl -X POST 'http://localhost:8000/team/new' \
-H 'Authorization: Bearer sk-admin-master-key' \
-H 'Content-Type: application/json' \
-d '{
"team_id": "data-sre",
"models": ["qwen3-32b", "deepseek-r1"],
"tpm_limit": 500000,
"rpm_limit": 200
}'
"department": "data-sre"가 들어있으면, 별도의 API Key 생성 없이도 자동으로 해당 팀의 정책과 쿼터가 적용됩니다.사내 인프라 표준이 Kubernetes 환경이거나 엔터프라이즈 API Gateway(Envoy, Kong, APISIX)를 이미 사용 중인 경우, 인증(AuthN)과 인가(AuthZ)를 앞단 Gateway에서 끝내고, AI Gateway에는 메타데이터만 헤더로 주입하는 방식이 유지보수에 가장 깔끔합니다.
[Client] ──(Bearer JWT)──▶ [ Envoy / Kong Gateway ]
│
├─ 1. Keycloak OIDC 플러그인 (JWT 서명/만료 검증)
├─ 2. JWT 내부 Claim 파싱
│ - X-User-Id: "a10961"
│ - X-Tenant-Group: "platform-infra"
│
▼ (헤더 주입 후 프록시)
[ LiteLLM Proxy / 추론 앞단 ]
│
├─ X-Tenant-Group 기준 Rate Limit (Redis 토큰 버킷)
└─ vLLM으로 요청 전송
envoy.filters.http.jwt_authn 필터를 사용하여 Keycloak JWKS를 캐싱해두고 암호학적 서명을 검증한 후, payload_in_metadata를 통해 하위 서비스로 전달합니다.사용자(클라이언트 개발자)는 Keycloak에서 토큰을 받아 기존 OpenAI SDK에 전달하기만 하면 됩니다.
import os
import requests
from openai import OpenAI
# 1. Keycloak에서 Client Credentials로 토큰 획득
def get_keycloak_token():
url = "https://keycloak.company.com/realms/corp-realm/protocol/openid-connect/token"
payload = {
"grant_type": "client_credentials",
"client_id": "my-team-client-id",
"client_secret": os.environ["KEYCLOAK_CLIENT_SECRET"]
}
res = requests.post(url, data=payload)
return res.json()["access_token"]
jwt_token = get_keycloak_token()
# 2. OpenAI 표준 클라이언트로 B300 노드 서빙 게이트웨이 호출
client = OpenAI(
api_key=jwt_token, # API Key 대신 JWT Bearer 토큰 주입
base_url="http://ai-gateway.company.internal/v1"
)
response = client.chat.completions.create(
model="deepseek-r1",
messages=[{"role": "user", "content": "Ping"}]
)
print(response.choices[0].message.content)
이 방식을 도입하면 사용자가 퇴사하거나 팀을 이동했을 때 Active Directory/Keycloak에서 계정 및 그룹만 변경하면, 별도의 API Key 회수 작업 없이도 LLM 인스턴스 접근 권한과 사용량 제한이 실시간으로 동기화됩니다.
네. 이번 파일은 앞서 논의한 B300 단일 노드 멀티모델 서빙 + Gateway/Auth/Quota 설계 문서로 보이고, 앞서 평가한 K8s validation 관점에서 보면 좋은 내용과 수정해야 할 내용이 상당히 명확하게 나뉩니다. 파일의 전체 내용을 기준으로 평가하겠습니다. :chatgpt-content-reference{index="0"}
현재 문서는 아키텍처 방향은 좋지만, 일부 수치·표현은 B300/K8s 운영환경에 그대로 적용하기 전에 검증이 필요합니다.
| 영역 | 평가 | 판단 |
|---|---|---|
| GPU partitioning | ★★★★☆ | 방향 좋음, topology 표현 수정 |
| CPU/NUMA | ★★★★★ | 반드시 유지 |
| GPU isolation | ★★★★★ | K8s에서는 더 중요 |
| KV Cache isolation | ★★★★☆ | 기본 방향 좋음 |
| MIG | ★★★☆☆ | 너무 단정적 |
| Gateway | ★★★★☆ | K8s에서는 Envoy/Cilium Gateway와 역할 분리 필요 |
| Prefix caching | ★★★☆☆ | 40~60% 절감 표현은 삭제 권장 |
| Observability | ★★★★★ | 매우 좋음 |
| Auth/Quota | ★★★★☆ | 방향 좋으나 일부 구현 예시는 검증 필요 |
| K8s 적용 | ★★★☆☆ | K8s 전용 내용이 아직 부족 |
특히 이 문서는 지금 상태로는 "Bare metal 멀티모델 서빙 가이드"에 더 가깝고, 사용자가 앞서 만든 K8s validation plan과 합치려면 몇 가지를 추가해야 합니다.
문서의:
TP=1,2,4,8로 격리해야 All-Reduce 병목이 발생하지 않는다.
이 표현은 너무 강합니다. :chatgpt-content-reference{index="1"}
TP 크기를 2의 거듭제곱으로 하는 것 자체가 B300의 일반적인 필수 조건은 아닙니다.
오히려 중요한 것은:
TP size
+
실제 GPU topology
+
NVLink/NVSwitch topology
+
NUMA/PCIe locality
+
model architecture
입니다.
따라서:
TP는
1/2/4/8을 우선 benchmark 대상으로 사용하되, 최적 TP가 반드시 2의 거듭제곱이어야 하는 것은 아니며 실제 NCCL topology와 모델 특성에 따라 결정한다.
가 더 정확합니다.
특히 사용자가 이미 만들고 있는 Stage 3a의 TP=1/2/4/8 benchmark matrix와 연결하면 아주 좋습니다.
현재:
GPU 0~3 TP=4
GPU 4~5 TP=2
GPU 6 TP=1
GPU 7 TP=1
이라는 배치는 상당히 현실적인 예시입니다. :chatgpt-content-reference{index="2"}
하지만 B300에서 실제 운영 배치로 바로 "권장"하기보다는:
Candidate allocation / benchmark scenario
로 두는 것이 좋습니다.
왜냐하면 실제로는:
Model A
TP=4
Model B
TP=2
Model C
TP=1
뿐 아니라 CPU/NIC/AIStor traffic까지 같은 노드에서 경쟁하기 때문입니다.
따라서 최종적으로는:
GPU allocation
+
CPU allocation
+
NUMA allocation
+
network bandwidth
+
AIStor traffic
을 함께 검증해야 합니다.
이 부분은 오히려 K8s 버전에서 반드시 확대해야 합니다.
현재 문서의:
numactl --cpunodebind=0 --membind=0 ...
방식은 bare metal에서는 좋은 baseline입니다. :chatgpt-content-reference{index="3"}
하지만 K8s에서는:
numactl
만으로 해결하면 안 됩니다.
K8s에서는 최소한:
CPU Manager
Topology Manager
Device Plugin
NFD
GPU Operator
가 서로 맞아야 합니다.
즉:
K8s scheduler
↓
CPU Manager
↓
Topology Manager
↓
GPU device plugin
↓
NUMA
↓
GPU
관계를 확인해야 합니다.
K-J1 CPU request/limit
K-J2 CPU Manager policy
K-J3 Topology Manager policy
K-J4 NUMA locality
K-J5 GPU ↔ CPU locality
K-J6 NIC ↔ CPU locality
이것은 앞서 말씀드린 J. Kubernetes resource / NUMA / CPU overhead와 정확히 연결됩니다.
gpu-memory-utilization 0.85~0.90은 고정 권장값으로 쓰지 않는 것이 좋음문서에서는:
0.85~0.90수준으로 설정
이라고 되어 있습니다. :chatgpt-content-reference{index="4"}
이것은 초기 운영값으로 시험해 볼 수 있는 범위이지 일반적인 best practice로 고정하면 안 됩니다.
특히 현재 사용자는:
을 benchmark하려고 합니다.
그러면 GPU memory usage는 크게 달라집니다.
따라서 validation에서는:
0.80
0.85
0.90
0.92
등을 시험하기보다는, 우선:
모델/TP/context/concurrency별 실제 KV cache capacity와 OOM margin을 측정하여 결정
하는 것이 더 좋습니다.
문서:
B300에서는 가급적 MIG 대신 vLLM 단일 컨테이너 단위 분할 권장
은 운영 정책으로는 가능하지만 너무 일반화되어 있습니다. :chatgpt-content-reference{index="5"}
MIG를 쓰면 GPU를 partition할 수 있지만, 실제로 필요한지 여부는:
GPU isolation 요구
vs
GPU utilization
vs
TP 필요성
vs
QoS
vs
model size
에 따라 결정해야 합니다.
현재 사용자의 목적이 TP 기반 LLM inference benchmark이므로:
이번 benchmark에서는 MIG OFF를 baseline으로 고정하고, MIG는 별도의 resource-isolation 실험으로 분리
하는 것이 가장 깔끔합니다.
이렇게 하면 benchmark 결과가 훨씬 해석하기 쉽습니다.
이 문장의:
중복 토큰 연산과 KV Cache 소비를 40~60% 절감
은 일반적인 보장값처럼 보입니다. :chatgpt-content-reference{index="6"}
이건 workload-dependent입니다.
특히 사용자가 이미 Stage 3a-8에서 동일한 32K prefix를 ON/OFF로 정확하게 A/B 비교하려고 했던 목적과 충돌합니다.
오히려 이렇게 쓰는 것이 좋습니다.
Prefix caching은 동일 prefix가 반복되는 workload에서 prefill compute와 일부 KV-cache allocation pressure를 줄일 수 있으므로, 실제 효과는 동일 prefix 길이와 cache reuse ratio에 따라 측정한다.
그리고 benchmark:
Prefix OFF
vs
Prefix ON
에서:
를 비교하면 됩니다.
이 부분은 문서에서 가장 좋은 부분 중 하나입니다. :chatgpt-content-reference{index="7"}
특히:
DCGM
+
vLLM
+
Prometheus
+
Grafana
구조는 유지하는 게 좋습니다.
다만 K8s에서는 여기에 반드시:
Cilium metrics
Hubble
Node Exporter
kube-state-metrics
Pod metrics
를 추가해야 합니다.
결국 사용자가 원하는 것은 단순히:
GPU utilization = 90%
이 아니라:
TTFT ↑
│
├── vLLM queue ↑
├── KV cache ↑
├── GPU util ↑
├── AIStor latency ↑
├── NIC utilization ↑
├── Cilium drops ?
└── TCP retransmission ?
이라는 원인 상관관계이기 때문입니다.
파일에는:
vllm:gpu_cache_usage_factor
vllm:num_requests_waiting
vllm:num_requests_running
vllm:time_to_first_token_seconds
vllm:time_per_output_token_seconds
등이 적혀 있습니다. :chatgpt-content-reference{index="8"}
그런데 사용자가 최근 vLLM 0.29.0을 사용하면서 metric/CLI 옵션 차이를 이미 경험했기 때문에, 이 문서의 metric 이름도 실제 0.29.0 /metrics 결과를 기준으로 확정하는 것을 추천합니다.
즉 문서에:
vLLM metrics — 실제 배포 버전의
/metricsendpoint에서 제공되는 metric name을 기준으로 검증
이라고 명시하는 것이 좋습니다.
특히 최근 kv-cache-metrics 관련 옵션이 없는 상황과 같은 문제가 다시 발생할 수 있습니다.
파일에서는:
Client
↓
LiteLLM
↓
vLLM
구조입니다. :chatgpt-content-reference{index="9"}
하지만 지금 사용자의 실제 목표는:
Client
↓
Cilium Gateway API / Envoy Gateway
↓
LLM Router
↓
vLLM
에 가깝습니다.
따라서 저는 역할을 이렇게 나누는 것을 권합니다.
Keycloak / AD
│
▼
Client ── JWT ──> Envoy Gateway
│
│ AuthN/AuthZ
▼
LLM Router
│
┌──────────┼──────────┐
▼ ▼ ▼
Qwen DeepSeek Embedding
│ │ │
▼ ▼ ▼
vLLM vLLM vLLM
여기서:
으로 역할을 분리하는 것이 훨씬 깔끔합니다.
문서의 API Key 설계 자체는 충분히 현실적입니다. :chatgpt-content-reference{index="10"}
하지만 사용자의 실제 환경에는 이미 Keycloak + AD가 있으므로, 운영 아키텍처의 기본값은:
Human
↓
Keycloak/AD
↓
OIDC
↓
Envoy Gateway
↓
LLM Router
가 더 적합합니다.
반면:
Machine-to-machine
은:
Service Account / client credentials
를 사용하는 것이 좋습니다.
API Key는:
등을 위한 compatibility layer로 두는 것이 좋습니다.
이 파일의 가장 큰 개선 포인트입니다.
현재 문서는:
GPU
NUMA
vLLM
Gateway
Redis
Keycloak
는 잘 설명되어 있지만 Kubernetes 운영 모델이 빠져 있습니다.
실제 사용자 환경에서는 다음이 핵심입니다.
GPU Operator
NFD
Device Plugin
DCGM
Topology Manager
CPU Manager
Cilium
Native Routing
BGP
ECMP
ClusterMesh
Hubble
NetworkPolicy
nodeSelector
nodeAffinity
podAffinity/anti-affinity
topologySpreadConstraints
taints/tolerations
startupProbe
readinessProbe
livenessProbe
PDB
drain
rolling update
preStop
terminationGracePeriod
AIStor
PVC
model cache
local cache
cross-cluster access
가 들어가야 합니다.
현재 문서:
CUDA_VISIBLE_DEVICES=0,1,2,3
는 bare-metal/process 관점에서는 이해하기 쉽습니다. :chatgpt-content-reference{index="11"}
하지만 K8s에서는 일반적으로 애플리케이션이 직접:
CUDA_VISIBLE_DEVICES=0,1,2,3
를 관리하게 하는 것보다 NVIDIA device plugin을 통해 GPU resource를 요청하게 하는 것이 기본입니다.
예:
resources:
limits:
nvidia.com/gpu: 4
그 다음 컨테이너 안에서는 runtime/device plugin이 할당된 GPU만 보이게 합니다.
따라서 문서에서는:
Bare metal:
CUDA_VISIBLE_DEVICES
Kubernetes: NVIDIA Device Plugin resource allocation
으로 분리하는 게 좋습니다.
현재 문서 뒤에 다음을 붙이면 상당히 완성도가 올라갑니다.
| ID | 항목 | 핵심 검증 |
|---|---|---|
| K1 | GPU allocation | Pod가 요청한 GPU만 실제 노출되는가 |
| K2 | GPU topology | TP group의 실제 NVLink/NVSwitch topology |
| K3 | NUMA | GPU/CPU/NIC locality |
| K4 | CPU Manager | CPU throttling/steal 여부 |
| K5 | Cilium datapath | Pod → node → NIC 경로 |
| K6 | NCCL Pod test | bare-metal 대비 AllReduce |
| K7 | NCCL hostNetwork | Cilium overhead 분리 |
| K8 | same-node network | Pod-to-Pod |
| K9 | cross-node network | Pod-to-Pod |
| K10 | ClusterMesh | cross-cluster Pod-to-Pod |
| K11 | BGP convergence | route withdrawal/recovery |
| K12 | ECMP | path distribution |
| K13 | AIStor Global Service | local/remote endpoint |
| K14 | AIStor failure | local → remote failover |
| K15 | startup/readiness | model loading protection |
| K16 | node drain | inference graceful termination |
| K17 | NetworkPolicy | workload identity authorization |
| K18 | noisy neighbor | Spark/AIStor/LLM simultaneous load |
| K19 | observability | GPU+Cilium+vLLM correlation |
| K20 | Pod restart | GPU/NCCL/model recovery |
현재 파일은 사실 두 가지 목적이 섞여 있습니다.
GPU partition
NUMA
vLLM
KV cache
Gateway
Auth
Quota
Observability
Bare Metal
↓
K8s hostNetwork
↓
K8s Cilium
↓
same-cluster
↓
ClusterMesh
↓
AIStor
↓
failure/recovery
이렇게 분리하면 훨씬 명확합니다.
이 파일의 기본 아키텍처 방향은 유지하는 것이 좋습니다. 특히 NUMA, GPU isolation, DCGM/vLLM observability, Gateway/Auth/Quota 부분은 좋은 기반입니다. :chatgpt-content-reference{index="12"} :chatgpt-content-reference{index="13"}
다만 현재 사용자가 만들고 있는 B300 + Kubernetes + Cilium Native/BGP/ECMP + ClusterMesh + AIStor 환경에 적용하려면 다음 네 가지를 반드시 수정하는 것을 권합니다.
① TP=2의 제곱이 필수라는 표현 → benchmark/실측 기반으로 변경
② gpu-memory-utilization 0.85~0.90 → 고정 Best Practice가 아니라 workload별 튜닝값으로 변경
③ Prefix caching 40~60% → 삭제하고 실제 A/B 측정으로 대체
④ Kubernetes 전용 항목(GPU Operator/Device Plugin/NUMA/Cilium/ClusterMesh/AIStor/Probe/Drain/NetworkPolicy/Noisy Neighbor)을 별도 섹션으로 추가
그리고 이 파일에서 LiteLLM 설정 예시와 OIDC 설정 예시는 실제 사용 버전의 공식 문서/설정 schema를 기준으로 한 번 검증하는 게 좋습니다. 특히 사용자가 최근 vLLM 0.29.0에서도 CLI 옵션 차이를 경험했기 때문에, 이 부분은 문서에 그대로 "표준 설정"으로 박아두기보다 버전 검증된 예시로 관리하는 것이 안전합니다.