26O01c

QK·약 20시간 전

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 고립, 엔진 서빙 아키텍처 및 메트릭 관측성 관점에서의 베스트 프랙티스는 다음과 같습니다.


1. GPU 토폴로지 기반 자원 파티셔닝 (Tensor Parallelism 분할)

B300 8-way 노드는 NVLink Switch를 통해 8장이 단일 패브릭으로 묶여 있지만, 여러 모델을 동시에 쪼개어 서빙할 때는 NUMA 노드와 PCIe/NVLink 도메인에 맞춰 2의 거듭제곱 단위(TP=1, 2, 4, 8)로 격리해야 All-Reduce 병목이 발생하지 않습니다.

권장 모델 배치 시나리오

  • 케이스 A: 초대형 MoE 1개 + 중소형 모델 병행 (가장 흔한 멀티모델 패턴)
  • GPU 0~3 (TP=4): DeepSeek-R1-Distill-70B 또는 중대형 MoE 모델 (4장 × 288GB = 1.15TB VRAM 풀)
  • GPU 4~5 (TP=2): Qwen3-32B / Llama 계열 메인 LLM 추론 (2장 × 288GB = 576GB VRAM)
  • GPU 6 (TP=1): 경량 7B~14B 모델 또는 코드 전용 LLM
  • GPU 7 (TP=1): 임베딩(Embedding) + 리랭커(Reranker) 전용 인스턴스 (vLLM 또는 TEI)
  • 케이스 B: DeepSeek-R1 Full (FP8/FP4) 전용 서빙
  • DeepSeek 671B MoE 가중치(FP8 기준 약 700GB)는 단일 노드 8장(TP=8, EP=8)으로 VRAM에 완전히 올릴 수 있으므로, 멀티 테넌트보다는 단일 노드 전체를 전용으로 할당하는 것이 성능상 유리합니다.

CPU & NUMA Affinity 바인딩 (필수)

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

2. 추론 엔진(Engine) 및 메모리 격리 전략

여러 추론 프로세스가 동일 물리 노드에 공존할 때 OOM(Out of Memory) 연쇄 장애를 막는 설정입니다.

  1. CUDA_VISIBLE_DEVICES 하드웨어 격리:
  • 각 서빙 컨테이너(또는 프로세스)에는 사용할 GPU 인덱스만 노출시킵니다.
  • Container A: CUDA_VISIBLE_DEVICES=0,1,2,3
  • Container B: CUDA_VISIBLE_DEVICES=4,5
  1. --gpu-memory-utilization 고정 및 KV Cache 버퍼 제어:
  • vLLM/SGLang은 기본적으로 GPU 메모리의 90% 이상을 가중치 + KV Cache 블록으로 사전 선점(Pre-allocate)합니다.
  • 멀티 프로세스 환경에서는 프레임워크 오버헤드, PyTorch CUDA 컨텍스트, PagedAttention 관리 버퍼를 고려하여 0.85 ~ 0.90 수준으로 설정해 메모리 스파이크를 방지합니다.
  1. MIG(Multi-Instance GPU) vs 프로세스 기반 분할:
  • B300에서는 가급적 MIG 대신 vLLM 단일 컨테이너 단위 분할 권장: MIG를 활성화하면 NVLink 대역폭 활용이 제한되고 Tensor Parallelism 제약이 생깁니다. LLM 추론은 NVLink를 온전히 활용할 수 있도록 베어메탈/컨테이너 레벨에서 GPU 카운트 단위(TP=1, 2, 4)로 물리 카드를 분배하는 것이 좋습니다.

3. 라우팅 및 동적 부하 분산 (Front Gateway)

노드 1대 내부에서 모델별로 각기 다른 포트(8001, 8002, 8003...)로 엔진이 뜨게 되므로, 앞단에 경량 AI 게이트웨이를 배치해야 클라이언트 관리가 단순해집니다.

  • Reverse Proxy / Gateway: Litellm, Envoy, 또는 Traefik
  • 포트 8000 단일 엔드포인트 노출 후 요청 헤더/바디의 model 필드에 따라 로컬 포트로 라우팅:
  • "model": "qwen3-32b" →\rightarrow localhost:8001
  • "model": "deepseek-r1-distill-70b" →\rightarrow localhost:8002
  • "model": "bge-reranker" →\rightarrow localhost:8003
  • Prefix Caching 활성화:
  • 동일 모델에 반복되는 시스템 프롬프트(System Prompt)가 많다면 --enable-prefix-caching을 켜서 중복 토큰 연산과 KV Cache 소비를 40~60% 절감합니다.

4. 모니터링 및 관측성(Observability) 파이프라인

단일 노드 멀티 모델 환경에서는 "어느 모델이 어느 GPU 대역폭을 잠식하고 있는지"를 즉각 분리해 관측해야 합니다.

[B300 GPUs] ──> DCGM Exporter (포트 9400) ──┐
[vLLM #1]   ──> Prometheus 메트릭 (8001)   ──┼──> Prometheus Server ──> Grafana Dashboard
[vLLM #2]   ──> Prometheus 메트릭 (8002)   ──┘

A. 하드웨어 레벨: NVIDIA DCGM Exporter (Blackwell 지원 버전)

  • 포트: 기본 9400
  • 필수 감시 메트릭:
  • 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 노드 고전력/열 부하 감시

B. 서빙 엔진 레벨: vLLM / SGLang 내장 Prometheus 메트릭

각 vLLM 인스턴스는 자체 포트(예: :8001/metrics, :8002/metrics)로 상세 메트릭을 노출합니다.

  • KV Cache 포화도:
  • vllm:gpu_cache_usage_factor: 가장 중요. 0.85 이상 지속 시 새 요청이 큐에 쌓이며 TTFT(Time to First Token) 지연 급증.
  • 대기열 병목:
  • vllm:num_requests_waiting: 요청이 처리되지 못하고 큐에 머무는 수. 0보다 크면 동시 요청 처리 한계 도달을 의미.
  • vllm:num_requests_running: 현재 PagedAttention으로 동시 디코딩 중인 배치 크기.
  • 처리 성능(Latency & Throughput):
  • vllm:time_to_first_token_seconds: 초기 Prefill 지연 시간.
  • vllm:time_per_output_token_seconds: 디코딩 토큰 생성 간격 (사용자 체감 속도).

5. 핵심 운용 체크리스트

점검 영역권장 설정값 / 액션목적
NUMA 바인딩numactl --cpunodebind=X --membind=X소켓 간 PCIe 크로스오버 통신 억제
GPU 격리컨테이너별 CUDA_VISIBLE_DEVICES 명시프로세스 간 VRAM 침범 및 충돌 방지
KV Cache--gpu-memory-utilization 0.85~0.90PagedAttention 할당 버퍼 확보로 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 기반 아키텍처입니다.


1. 표준 아키텍처 구성

[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)

2. 컴포넌트별 구현 방안: 인증 및 인가 (AuthN / AuthZ)

① API Key 기반 테넌트 인증 (가장 일반적)

게이트웨이가 고유한 가상 API Key(sk-team-a-..., sk-team-b-...)를 발급하고, 이 키를 특정 팀/유저 메타데이터와 매핑합니다.

  • 인증(AuthN): Authorization: Bearer sk-... 헤더 검증
  • 인가(AuthZ - 모델 접근 제어):
  • 테넌트 A는 qwen3-32b, bge-m3 모델만 호출 가능
  • 테넌트 B(연구팀)는 고비용의 deepseek-r1까지 호출 가능하도록 라우팅 제어

② 사내 IdP 연동 (SSO / OAuth2 / JWT)

사내 Keycloak, Okta, 또는 LDAP/Active Directory와 연동하는 경우:

  • 클라이언트는 Bearer JWT 토큰을 전달하고, Gateway가 JWT의 Claim(예: groups, sub, roles)을 파싱하여 테넌트를 식별하고 인가를 수행합니다.

3. 사용량 제한 (Rate Limiting & Quota) 설계

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 자원 독점 방지일간/월간 누적 토큰 또는 환산 비용

4. 구현 실전 예시: LiteLLM Proxy 설정

오픈소스 기반으로 가장 빠르고 안정적으로 멀티테넌시를 구축할 수 있는 LiteLLM Proxy + Redis 기준 설정 예시입니다.

1) 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용 캐시

2) 게이트웨이 기동

docker run -d \
  -v $(pwd)/config.yaml:/app/config.yaml \
  -p 8000:4000 \
  ghcr.io/berriai/litellm:main-latest \
  --config /app/config.yaml

3) 관리자 API로 테넌트별 API Key 및 쿼터 생성

  • 팀 A (일반 서비스팀 - Qwen만 사용 가능, 엄격한 쿼터 적용):
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에 배포
  • 팀 B (연구/코어팀 - 전 모델 허용, 높은 쿼터):
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
  }'

5. 선점 방지(Noisy Neighbor)와 백엔드 보호 기법

게이트웨이의 계량 기능 외에, 특정 테넌트가 B300 노드의 VRAM 전체를 고갈시키는 것을 막기 위해 추론 엔진 레벨에서도 방어벽을 세워야 합니다.

  1. 최대 컨텍스트 길이(--max-model-len) 강제:
  • 아무리 쿼터를 걸어도 단일 요청에서 수십만 토큰짜리 프롬프트를 밀어 넣으면 엔진의 KV Cache가 즉시 고갈되어 타 테넌트 요청이 큐에 대기(Waiting)됩니다.
  • vLLM 기동 시 --max-model-len 32768 등으로 현실적인 한도를 강제합니다.
  1. 동시 디코딩 시퀀스 상한(--max-num-seqs):
  • 한 번에 처리할 수 있는 최대 배치 크기를 128~256 수준으로 제한하여 스케줄러 큐의 공평성(Fairness)을 유지합니다.
  1. HTTP 429 Retry-After 헤더 활용:
  • 게이트웨이에서 특정 테넌트가 TPM/RPM 한도에 도달하면 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) 계층 분리의 두 가지 패턴으로 나뉩니다.


아키텍처 흐름 (OIDC / OAuth2 Bearer Flow)

[클라이언트/사내 유저]
       │
       │ 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)]

1. Keycloak / AD 사전 세팅 (IdP 설정)

사내 Keycloak(또는 Azure AD / On-Prem AD FS)에서 다음을 구성합니다.

  1. Client 생성:
  • Client ID: ai-inference-platform
  • Client Authentication: On (Service Account 기반 머신-투-머신 통신 시)
  • Standard Flow (유저 인터랙티브 로그인용) 또는 Service Accounts Roles (서버-투-서버 연동용 client_credentials)
  1. Token Mapper (Claim) 추가:
  • JWT 페이로드에 팀명, 부서 코드, 또는 그룹 정보가 들어가도록 설정합니다.
  • 예: groups: ["data-platform-team", "ai-research"] 또는 department: "sre"

2. 구현 방식 A: LiteLLM Proxy 자체 OIDC/JWT 검증 연동

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"

동적 인가 및 사용량 정책 (Tier 매핑)

JWT의 특정 Claim(예: department)에 따라 허용 모델과 쿼터를 Gateway 데이터베이스에 사전 정의해 둘 수 있습니다.

  • 사전 등록 정책 예시 (API로 등록):
# '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
  }'
  • 이제 클라이언트가 Keycloak에서 발급받은 JWT에 "department": "data-sre"가 들어있으면, 별도의 API Key 생성 없이도 자동으로 해당 팀의 정책과 쿼터가 적용됩니다.

3. 구현 방식 B: Envoy / Kong 기반 인프라 계층 분리 (엔터프라이즈 권장)

사내 인프라 표준이 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 적용 시: envoy.filters.http.jwt_authn 필터를 사용하여 Keycloak JWKS를 캐싱해두고 암호학적 서명을 검증한 후, payload_in_metadata를 통해 하위 서비스로 전달합니다.
  • 장점: 추론 엔진이나 AI 프록시가 IdP와 직접 통신할 필요가 없고, 비인가 트래픽이 내부 네트워크로 유입되기 전에 L7 인프라 레벨에서 차단됩니다.

4. 클라이언트 실무 호출 예시 (Python)

사용자(클라이언트 개발자)는 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"}

1. 전체 평가

현재 문서는 아키텍처 방향은 좋지만, 일부 수치·표현은 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과 합치려면 몇 가지를 추가해야 합니다.


2. 가장 먼저 수정할 부분 — GPU topology

문서의:

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와 연결하면 아주 좋습니다.


3. 모델 배치 예시는 "권장"보다 "시험 시나리오"로 바꾸는 것을 추천

현재:

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

을 함께 검증해야 합니다.


4. CPU/NUMA binding은 아주 좋은 내용

이 부분은 오히려 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

관계를 확인해야 합니다.

따라서 K8s validation에 추가

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와 정확히 연결됩니다.


5. gpu-memory-utilization 0.85~0.90은 고정 권장값으로 쓰지 않는 것이 좋음

문서에서는:

0.85~0.90 수준으로 설정

이라고 되어 있습니다. :chatgpt-content-reference{index="4"}

이것은 초기 운영값으로 시험해 볼 수 있는 범위이지 일반적인 best practice로 고정하면 안 됩니다.

특히 현재 사용자는:

  • TP=1/2/4/8
  • BF16/FP8/FP4
  • context length
  • concurrency
  • prefix caching

을 benchmark하려고 합니다.

그러면 GPU memory usage는 크게 달라집니다.

따라서 validation에서는:

0.80
0.85
0.90
0.92

등을 시험하기보다는, 우선:

모델/TP/context/concurrency별 실제 KV cache capacity와 OOM margin을 측정하여 결정

하는 것이 더 좋습니다.


6. MIG 부분은 수정 필요

문서:

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 결과가 훨씬 해석하기 쉽습니다.


7. Prefix Caching의 "40~60%"는 삭제하는 것을 권장

이 문장의:

중복 토큰 연산과 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

에서:

  • TTFT
  • prefill throughput
  • total throughput
  • GPU utilization
  • KV cache usage
  • request latency

를 비교하면 됩니다.


8. Observability 부분은 매우 좋음

이 부분은 문서에서 가장 좋은 부분 중 하나입니다. :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 ?

이라는 원인 상관관계이기 때문입니다.


9. vLLM metric 이름은 현재 사용 버전 기준으로 검증해야 함

파일에는:

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 — 실제 배포 버전의 /metrics endpoint에서 제공되는 metric name을 기준으로 검증

이라고 명시하는 것이 좋습니다.

특히 최근 kv-cache-metrics 관련 옵션이 없는 상황과 같은 문제가 다시 발생할 수 있습니다.


10. Gateway 구조는 K8s에서는 한 단계 더 분리해야 함

파일에서는:

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

여기서:

Envoy/Cilium Gateway

  • TLS
  • JWT/OIDC
  • Network policy
  • routing
  • basic rate limit
  • ingress

LLM Router/LiteLLM

  • model routing
  • tenant policy
  • token quota
  • concurrency
  • model-specific routing
  • backend health

vLLM

  • inference
  • batching
  • KV cache
  • GPU execution

으로 역할을 분리하는 것이 훨씬 깔끔합니다.


11. Auth 부분은 방향이 좋지만 API Key → OIDC를 좀 더 명확히

문서의 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는:

  • legacy client
  • 외부 application
  • 단순 service integration

등을 위한 compatibility layer로 두는 것이 좋습니다.


12. 문서의 가장 큰 문제: Kubernetes가 거의 없음

이 파일의 가장 큰 개선 포인트입니다.

현재 문서는:

GPU
NUMA
vLLM
Gateway
Redis
Keycloak

는 잘 설명되어 있지만 Kubernetes 운영 모델이 빠져 있습니다.

실제 사용자 환경에서는 다음이 핵심입니다.

Kubernetes GPU

GPU Operator
NFD
Device Plugin
DCGM
Topology Manager
CPU Manager

Networking

Cilium
Native Routing
BGP
ECMP
ClusterMesh
Hubble
NetworkPolicy

Scheduling

nodeSelector
nodeAffinity
podAffinity/anti-affinity
topologySpreadConstraints
taints/tolerations

Lifecycle

startupProbe
readinessProbe
livenessProbe
PDB
drain
rolling update
preStop
terminationGracePeriod

Storage

AIStor
PVC
model cache
local cache
cross-cluster access

가 들어가야 합니다.


13. 특히 "GPU isolation"을 K8s 방식으로 바꿔야 함

현재 문서:

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

으로 분리하는 게 좋습니다.


14. 제가 이 문서에 반드시 추가할 K8s 항목

현재 문서 뒤에 다음을 붙이면 상당히 완성도가 올라갑니다.

Kubernetes-specific validation

ID항목핵심 검증
K1GPU allocationPod가 요청한 GPU만 실제 노출되는가
K2GPU topologyTP group의 실제 NVLink/NVSwitch topology
K3NUMAGPU/CPU/NIC locality
K4CPU ManagerCPU throttling/steal 여부
K5Cilium datapathPod → node → NIC 경로
K6NCCL Pod testbare-metal 대비 AllReduce
K7NCCL hostNetworkCilium overhead 분리
K8same-node networkPod-to-Pod
K9cross-node networkPod-to-Pod
K10ClusterMeshcross-cluster Pod-to-Pod
K11BGP convergenceroute withdrawal/recovery
K12ECMPpath distribution
K13AIStor Global Servicelocal/remote endpoint
K14AIStor failurelocal → remote failover
K15startup/readinessmodel loading protection
K16node draininference graceful termination
K17NetworkPolicyworkload identity authorization
K18noisy neighborSpark/AIStor/LLM simultaneous load
K19observabilityGPU+Cilium+vLLM correlation
K20Pod restartGPU/NCCL/model recovery

15. 최종적으로 이 문서는 두 문서로 나누는 게 좋습니다

현재 파일은 사실 두 가지 목적이 섞여 있습니다.

문서 A — B300 Multi-Model Serving Architecture

GPU partition
NUMA
vLLM
KV cache
Gateway
Auth
Quota
Observability

문서 B — Kubernetes Validation Plan

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 옵션 차이를 경험했기 때문에, 이 부분은 문서에 그대로 "표준 설정"으로 박아두기보다 버전 검증된 예시로 관리하는 것이 안전합니다.

profile
engineer

0개의 댓글