AWS 위 CUDA가 없는 환경에서
vLLM을 띄운다.
이번 주는 책이 아니라 손입니다. NVIDIA GPU가 한 장도 없는 EKS 클러스터에 AWS가 직접 설계한 칩을 꽂고, 그 위에 vLLM을 올려 OpenAI 호환 API까지 뽑아냅니다. 같은 vLLM인데 밑바닥이 통째로 다릅니다.
들어가며
여기까지 다섯 주 동안 다룬 것은 전부 NVIDIA GPU 위의 이야기였습니다. KV 캐시도, PagedAttention도, 텐서 병렬도, 프로파일링도 CUDA라는 바닥을 깔고 서 있었습니다. 이번 워크샵은 그 바닥을 걷어냅니다.
AWS가 자체 설계한 Trainium 칩을 꽂은 EKS 노드 한 대에
TinyLlama-1.1B-Chat-v1.0을 올리고, Ingress·모니터링·부하 테스트·오토스케일링까지
엔드투엔드로 돌려봅니다. 위층은 우리가 아는 vLLM 그대로인데,
바닥에서 무슨 일이 벌어지는지가 완전히 다릅니다.
- vLLM은 그대로입니다.
--tensor-parallel-size 2도, OpenAI 호환 API도, continuous batching도 똑같이 씁니다. 바뀌는 건--device=neuron한 줄과 그 아래 스택 전체입니다. - Neuron은 컨테이너 런타임에 손대지 않습니다. NVIDIA가 전용 런타임으로 드라이버 라이브러리를 매번 주입해야 하는 것과 달리, Neuron은 표준
runc가 디바이스 노드만 마운트하면 끝입니다. 4장이 이 차이를 다룹니다. - 제일 오래 걸리는 건 추론이 아니라 컴파일입니다. 첫 기동에 8분, 그중 4분이 모델 컴파일입니다. 이 결과를 S3에 캐시해 두면 다음 기동은 20초입니다.
접는 상자(▸)는 안 열어도 됩니다. 안에는 실제 명령어, 매니페스트, 원문 출력이 들어 있습니다. 본문만 읽어도 흐름은 끊기지 않습니다.
그림은 만져보라고 넣었습니다. 슬라이더를 극단까지 밀면 각 구성이 언제 무너지는지가 보입니다. 노란 상자에 "해볼 것"이 적혀 있습니다.
이 페이지의 장 번호는 워크샵의 Lab 번호와 다릅니다. Lab을 가리킬 때는 "Lab 2"처럼 그대로 적었습니다.
"실습 결과"라고 붙은 화면은 실제로 돌려서 찍은 것입니다. 그림(모형)이 말한 것이 진짜 그렇게 나오는지 눈으로 확인하는 자리입니다. 계정 ID·세션 ID·엔드포인트 주소는 가렸습니다.
다루는 것
Neuron이라는 소프트웨어 스택이 CUDA 자리에 무엇을 놓는지(1장), 워크샵이 어떤 조각들로 이루어져 있는지(2장), EKS 클러스터에 Trainium을 붙이는 과정(3장), 그리고 왜 Neuron에는 전용 컨테이너 런타임이 없는지(4장)를 봅니다. 이어서 vLLM 배포(5장) · Ingress(6장) · 관측(7장) · 성능 측정(8장) · 오토스케일링(9장) · 정리(10장) 순서로 워크샵을 따라갑니다.
다루지 않는 것
Neuron 컴파일러 내부와 NKI 커널 작성법은 범위 밖입니다. Trainium2·Trn2 UltraServer는 이름만 언급합니다 — 실습 환경이 1세대 Trn1이기 때문입니다. 학습(training) 워크로드도 다루지 않습니다. 이름은 Trainium이지만 여기서는 추론에만 씁니다.
스터디 원문의 사전 준비 항목에 workshop/.env의 내용이 실제 Hugging Face 토큰 값 그대로 붙어 있습니다.
이 페이지에는 옮기지 않았고, 전부 hf_****로 가렸습니다.
원문을 공개된 곳에 올릴 계획이라면 해당 토큰을 먼저 폐기(revoke)하시길 권합니다.
Grafana·MinIO 비밀번호도 같은 이유로 이 페이지에서는 값을 적지 않았습니다.
모든 장에서 같은 질문을 던져 보세요. "이 일을 누가 하는가?"
모델을 컴파일하는 건 init 컨테이너, 어떤 코어를 줄지 정하는 건 device plugin,
실제로 디바이스를 마운트하는 건 runc, 어느 노드에 앉힐지 정하는 건 커스텀 스케줄러입니다.
NVIDIA 환경과 비교하면 같은 일을 다른 계층이 맡고 있다는 게 계속 보입니다.
먼저 이름부터. Neuron, Inferentia, Trainium, NeuronCore가 각각 무엇인지 정리하고 시작합니다.
01AWS Neuron — CUDA 자리에 무엇이 들어가나
한 줄 요약: Neuron은 SDK(소프트웨어)고, Inferentia·Trainium은 칩이고, NeuronCore는 칩 안의 연산 코어입니다.
이름이 헷갈리게 지어져 있습니다. Inferentia는 추론용, Trainium은 학습용으로 설계 목적이 나뉘어 있지만, Trainium 칩으로 추론을 돌리는 것도 얼마든지 가능합니다. 실제로 이 워크샵이 그렇게 합니다.
더 중요한 건 계층을 헷갈리면 문서를 못 읽는다는 점입니다.
"Neuron이 지원한다"는 말이 SDK 이야기인지 칩 이야기인지에 따라 뜻이 달라집니다.
파드가 요청하는 자원 이름도 aws.amazon.com/neuron(칩)과 neuroncore(코어)가 다릅니다.
- AWS Neuron
- AWS 자체 설계 가속기를 위한 소프트웨어 스택 전체. 컴파일러(
neuronx-cc), 런타임, 프레임워크 통합(torch-neuronx), 프로파일러를 포함합니다. CUDA에 해당하는 자리입니다. - Inferentia / Trainium
- AWS가 설계한 가속기 칩. Inferentia는 추론용(Inf1/Inf2), Trainium은 학습용(Trn1/Trn2)으로 설계됐지만 경계는 고정적이지 않습니다.
- NeuronCore
- 칩 안의 실제 연산 코어 단위. NVIDIA GPU의 SM(Streaming Multiprocessor)에 해당하는 개념입니다. Trainium1 칩 하나에 NeuronCore-v2가 2개 들어 있습니다.
- NeuronLink
- 칩과 칩 사이를 잇는 전용 인터커넥트. AllReduce·AllGather 같은 집합 통신을 빠르게 만들어 텐서 병렬을 가능하게 합니다. NVLink에 해당합니다.
- NxD Inference
- NeuronX Distributed Inference. 모델을 여러 NeuronCore에 쪼개서 올리는 라이브러리. vLLM이 이 라이브러리를 통해 Neuron 위에서 돕니다.
- NKI
- Neuron Kernel Interface. Neuron 명령어 집합에 직접 접근하는 저수준 API. CUDA 커널을 직접 짜는 것에 해당합니다.
- DLAMI / DLC
- Deep Learning AMI / Deep Learning Container. Neuron SDK가 미리 깔려 있는 EC2 이미지와 컨테이너 이미지입니다.
비유 — 공장, 기계, 작업대
제조 공장으로 바꿔 보면 계층이 분명해집니다.
Neuron SDK는 공장의 운영 체계입니다. 설계도(모델)를 받아 기계가 이해하는 작업 지시서로 바꾸고(컴파일러), 작업을 배분하고(런타임), 어디서 시간이 새는지 봅니다(프로파일러).
Trainium 칩은 기계 한 대입니다. 기계 안에는 작업대(NeuronCore)가 두 개 있고, 각 작업대에는 용도가 다른 도구(텐서·벡터·스칼라·GPSIMD 엔진)와 손 닿는 곳의 부품함(온칩 SRAM)이 있습니다.
공장 하나에 기계를 여러 대 놓을 수 있습니다. trn1.2xlarge는 기계 한 대(작업대 2개),
trn1.32xlarge는 기계 열여섯 대(작업대 32개)입니다.
기계끼리 반제품을 주고받는 컨베이어가 NeuronLink입니다.
작은 숫자로 한 번
380 TOPS · 190 TFLOPS는 칩 하나(NeuronCore 2개 합계) 기준입니다. 코어 하나당이 아닙니다. HBM 32 GiB도 칩 단위로 공유됩니다 — 코어마다 16 GiB씩 따로 있는 게 아니라, 두 코어가 같은 32 GiB를 나눠 씁니다.
neuron-top으로 직접 확인한 화면
--tensor-parallel-size=2라는 숫자가 어디서 나왔는지 이 화면이 답입니다.
그리고 Device Used Memory가 0.0B입니다.
5장에서 모델을 올리고 나면 이 자리가 3.6GB로 바뀌고, 그것이 NC0·NC1에 반씩 나뉩니다.
비어 있는 지금 화면을 기억해 두면 그때 비교가 쉽습니다.
(계정·세션 정보는 가렸습니다.)도식 1-1. Neuron SDK · 칩 · NeuronCore의 관계
원문의 텍스트 트리를 그림으로 옮긴 것입니다. 핵심은 SDK 하나가 두 종류의 칩을 모두 상대한다는 점,
그리고 칩과 코어는 다른 층위라는 점입니다. 파드가 자원을 요청할 때 aws.amazon.com/neuron(칩 단위)과
aws.amazon.com/neuroncore(코어 단위) 중 무엇을 쓰느냐가 여기서 갈립니다.
인스턴스를 키우면 무엇이 늘어나나
↔ 슬라이더trn1.2xlarge(이번 실습 환경)입니다. 코어가 2개뿐인 게 보입니다. ② 칩 수를 16까지 밀면 코어가 32개가 되고 HBM이 512 GiB까지 늘어납니다. ③ 칩이 2개 이상일 때만 NeuronLink 선이 그려집니다 — 칩을 넘어가는 텐서 병렬은 이 선을 타야 하고, 그래서 같은 칩 안의 2코어 병렬보다 느립니다.칩 1개 = trn1.2xlarge, 16개 = trn1.32xlarge입니다.
중간값들은 실제로 판매되는 인스턴스가 아니라 비례 관계를 보기 위한 값입니다.
도식 1-2. NeuronCore-v2 하나의 내부
NeuronCore-v2는 서로 다른 엔진 네 개가 붙어 있는 이기종 유닛입니다. 행렬 연산은 텐서 엔진이, 요소별 연산은 벡터 엔진이, 제어 흐름은 스칼라 엔진이 맡습니다. GPU에서 SM 하나가 모든 일을 하는 것과 대비되는 설계입니다.
그리고 온칩 SRAM이 컴파일러 관리라는 점이 중요합니다. GPU의 L1/L2 캐시는 하드웨어가 알아서 채우지만, 여기서는 컴파일 시점에 무엇을 언제 올릴지 정해집니다. 5장에서 "왜 모델을 미리 컴파일해야 하는가"의 답이 여기 있습니다.
모델을 올리는 두 갈래 길
Neuron 위에서 추론을 돌리는 방법은 두 가지입니다. 갈림길은 "이 모델을 vLLM이 지원하는가" 하나뿐입니다.
도식 1-3. 추론 배포 방법 고르기
이번 워크샵은 왼쪽 길입니다. NxD Inference + vLLM 플러그인 0.5.x가 Inf2·Trn1·Trn2를 모두 지원합니다. 오른쪽의 vLLM Neuron 플러그인(베타)은 NxD에 의존하지 않고 모델 구현을 플러그인 안에 직접 갖고 있는데, 아직 Trn2/Trn3 전용이라 Trn1인 이번 실습에서는 쓸 수 없습니다.
두 경로의 설치 명령과 지원 범위
| 항목 | NxD Inference + vLLM (권장) | vLLM Neuron 플러그인 (베타) |
|---|---|---|
| 지원 인스턴스 | Inf2, Trn1, Trn2 | Trn2, Trn3만 |
| 플러그인 버전 | 0.5.x (vLLM 0.16) | release-0.24.0.x (vLLM 0.24.0) |
| 모델 실행 | neuronx-distributed-inference 라이브러리에 위임 | 플러그인이 모델 구현을 직접 보유 |
| 고급 기능 | 연속 배칭, 접두사 캐싱, 투기적 디코딩(Eagle V1), INT8/FP8 양자화, 멀티모달 | 분산 추론, 분할 프리필, EAGLE3 투기적 디코딩 |
경로 1 — NxD Inference + vLLM (이번 워크샵)
경로 2 — vLLM Neuron 플러그인 (Trn2/Trn3 전용, 베타)
NxD Inference를 직접 설치할 때
설치 확인
- 정의 — Neuron은 소프트웨어 스택(CUDA 자리), Inferentia·Trainium은 칩, NeuronCore는 칩 안의 코어(SM 자리)입니다. 세 층위를 섞어 읽으면 문서가 안 읽힙니다.
- 성질 — Trainium1 칩 1개 = NeuronCore-v2 2개 + HBM 32 GiB이고, 이 2가 실습의
--tensor-parallel-size 2로 그대로 이어집니다. 온칩 SRAM이 컴파일러 관리라는 점이 5장의 "미리 컴파일" 요구로 연결됩니다. - 쓰임 — vLLM이 지원하는 모델이면 NxD Inference + vLLM 플러그인 0.5.x가 정답입니다. 베타 플러그인은 Trn2 이상 전용이라 Trn1에서는 선택지가 아닙니다.
스택을 알았으니 이제 워크샵 전체를 한 장에 펼칩니다. 무엇이 어디에 올라가는지.
02워크샵의 지도 — 무엇을 어디에 올리나
한 줄 요약: 개발용 EC2 한 대에서 EKS를 조작하고, Trainium 노드 한 대 위에 vLLM 파드 하나를 띄웁니다.
워크샵 실습의 함정이 이겁니다. kubectl apply를 스무 번쯤 하고 나면
지금 조작하는 터미널이 어디에 붙어 있는지부터 헷갈립니다.
이 워크샵에는 컴퓨터가 세 층 있습니다.
내 노트북(SSH 클라이언트), 개발용 EC2(t3.2xlarge, 여기서 kubectl을 침),
Trainium 워커 노드(trn1.2xlarge, 실제로 모델이 도는 곳)입니다.
이 셋을 구분해 두면 뒤에 나오는 명령어들이 "어디서 실행하는 것인지" 헷갈리지 않습니다.
- EKS
- Amazon Elastic Kubernetes Service. 컨트롤 플레인을 AWS가 대신 운영해 주는 관리형 쿠버네티스입니다.
- managed node group
- EKS가 수명주기를 관리해 주는 워커 노드 묶음. 이 워크샵의 이름은
neuron-trn1-2x입니다. - device plugin
- 쿠버네티스가 모르는 하드웨어를 "이 노드에 몇 개 있다"고 대신 알려주는 데몬셋. 4장에서 자세히 봅니다.
- scheduler extension
- 기본 스케줄러가 못 하는 판단을 대신 해주는 보조 스케줄러. 워크샵에서는
my-scheduler라는 이름으로 뜹니다. - CSI driver
- Container Storage Interface 구현체. 여기서는 S3 버킷을 파일시스템처럼 마운트해 주는 Mountpoint 드라이버입니다.
- OIDC provider
- 파드에 IAM 역할을 직접 붙일 수 있게 해주는 신원 연동 설정. EKS 클러스터에 켜 둡니다.
비유 — 작업장, 창고, 그리고 접수대
공방을 차린다고 생각해 보세요.
개발용 EC2는 사무실입니다. 여기서 도면을 그리고 지시를 내리지만, 정작 물건은 여기서 안 만듭니다. Trainium 노드가 작업장입니다. 기계가 있고 실제 생산이 일어나는 곳입니다. S3 버킷은 창고입니다. 한 번 만든 반제품(컴파일된 모델)을 보관해 뒀다가 다음에 꺼내 씁니다.
NGINX Ingress는 접수대입니다. 바깥에서 온 손님을 받아 작업장 안쪽으로 안내합니다. 손님은 작업장 위치를 몰라도 됩니다.
작은 숫자로 한 번
도식 2-1. 워크샵 전체 아키텍처
화살표 방향을 따라가 보면 제어 경로와 데이터 경로가 다르다는 게 보입니다.
내가 치는 kubectl은 개발용 EC2 → EKS 컨트롤 플레인으로 가고,
실제 추론 요청은 인터넷 → CLB → Ingress → Service → 파드로 갑니다.
모델 아티팩트는 세 번째 경로 — 파드 ↔ S3 — 로 따로 움직입니다.
노드가 한 대뿐이라는 점도 그림에 그대로 나옵니다. 9장의 HPA 실습이 막히는 이유입니다.
사전 준비 — 접속까지
워크샵에 참가하면 AWS 콘솔 접근 권한과 SSH 키를 받습니다. 리전은 us-west-2(오레곤) 또는 us-east-1(버지니아)입니다.
내려받은 키의 권한을 좁힌다 — 안 하면 ssh가 거부한다
개발용 EC2 접속과 환경 확인
원문에는 cat workshop/.env 출력이 실제 값 그대로 남아 있습니다.
이 페이지에서는 hf_****로 가렸습니다.
Hugging Face 토큰은 발급받은 본인 것을 쓰고, 공개 저장소·블로그에 옮길 때는 반드시 가립니다.
- 정의 — 워크샵은 네 계층입니다. 인프라(EC2·VPC) → EKS(1.33 + Neuron AMI 노드그룹) → Trainium 통합(device plugin + 스케줄러 확장) → vLLM(이미지 + 모델 + TP=2).
- 성질 — 컴퓨터가 세 대라는 걸 계속 의식해야 합니다. 내 노트북(SSH) · 개발용 EC2(kubectl을 치는 곳) · Trainium 노드(모델이 도는 곳)는 서로 다른 기계입니다.
- 쓰임 — 제어 경로(kubectl)·데이터 경로(추론 요청)·아티팩트 경로(S3)가 각각 다릅니다. 문제가 생겼을 때 어느 경로가 끊겼는지부터 나누면 디버깅이 빨라집니다.
지도를 봤으니 이제 세웁니다. 컨트롤 플레인은 이미 있고, 우리가 할 일은 Trainium 노드를 붙이는 것입니다.
03클러스터 세우기 — Lab 1
한 줄 요약: 노드그룹을 붙이고, 이미 깔린 device plugin을 지운 뒤 Helm으로 다시 깔고, 스케줄러 확장과 S3 CSI를 얹습니다.
워크샵에서 가장 이해가 안 가는 단계가 이겁니다.
Neuron 최적화 AMI를 쓰면 노드가 부팅할 때 neuron-device-plugin이 자동으로 배포됩니다.
그런데 다음 단계에서 Helm으로 같은 이름의 데몬셋·클러스터롤·서비스어카운트를 또 설치하라고 합니다.
이건 Helm의 소유권 문제입니다.
자동 설치된 리소스에는 meta.helm.sh/release-name 같은 소유권 어노테이션이 없습니다.
Helm은 "이미 있는데 내가 관리해도 되는 건가?"를 판단하지 못하고 AlreadyExists로 실패합니다.
게다가 이후에 scheduler.enabled=true 같은 옵션을 붙여 업그레이드하려면 구성이 달라 어차피 새로 깔아야 합니다.
지우고 다시 까는 건 우회가 아니라 정리입니다.
- eksctl
- EKS 클러스터·노드그룹을 YAML 한 장으로 만들어 주는 CLI. 내부적으로는 CloudFormation 스택을 만듭니다.
- Neuron optimized AMI
- Neuron 드라이버와 device plugin이 미리 들어 있는 EKS 워커 노드 이미지. SSM 파라미터로 최신 ID를 조회합니다.
- taint / label
- 노드에 붙이는 표식. label은 "이런 노드다"라는 정보, taint는 "특별히 허락받은 파드만 오라"는 제한입니다.
- Helm 소유권 어노테이션
meta.helm.sh/release-name등. Helm이 "이건 내가 만든 것"이라고 표시해 두는 메타데이터입니다.- Mountpoint for S3
- S3 버킷을 파일시스템처럼 보이게 해주는 AWS 공식 FUSE 클라이언트. Rust로 작성돼 있습니다.
- neuron-ls / neuron-top
- 노드에서 Neuron 디바이스 목록과 사용률을 보는 명령어.
nvidia-smi·nvtop에 해당합니다.
비유 — 이사 온 집의 붙박이 가구
새 집에 들어갔더니 붙박이장이 이미 달려 있습니다. 편해 보이지만, 내가 산 시스템 가구를 넣으려니 같은 자리를 차지하고 있어서 들어가지 않습니다. 기존 것을 떼어내고 내 것을 다시 다는 게 빠릅니다.
그리고 나중에 선반을 하나 더 달고 싶어질 때(scheduler.enabled=true),
붙박이는 손댈 수 없지만 내가 단 것은 마음대로 바꿀 수 있습니다.
Helm으로 다시 까는 이유가 정확히 이겁니다 — 지금 편해지자고 하는 게 아니라 나중에 바꿀 수 있게 하려는 것입니다.
작은 숫자로 한 번
모델이 코어에 들어가는가 — 텐서 병렬이 필요한 지점
↔ 슬라이더trn1.2xlarge의 HBM을 거의 다 먹습니다. KV 캐시 자리가 안 남습니다. ③ 그 상태에서 칩 수를 2로 올리면 들어갑니다 — 다만 이제 NeuronLink를 타는 통신이 생깁니다.가중치만 셈한 값입니다. 실제로는 KV 캐시·활성화·런타임 오버헤드가 더 붙어서 HBM의 70~80%쯤에서 이미 빡빡해집니다. 그림의 주황 경고선이 그 지점입니다.
스케줄러가 하나 더 필요한 이유
노드에 코어가 여러 개 있을 때, 기본 스케줄러는 "코어 2개 남았네"까지만 압니다. 그런데 텐서 병렬은 같은 칩 안의 이웃한 코어여야 NeuronLink 없이 빠르게 돕니다. 칩 A의 코어 하나와 칩 B의 코어 하나가 남았다면, 숫자로는 2개지만 TP=2 파드를 올리기엔 나쁜 조합입니다.
이 판단을 대신 해주는 게 k8s-neuron-scheduler이고,
그걸 확장으로 물고 있는 두 번째 스케줄러가 my-scheduler입니다.
파드는 schedulerName: my-scheduler로 이 스케줄러를 지정합니다.
코어가 흩어져 있으면 생기는 일
클릭 + 슬라이더실제 Neuron 스케줄러 확장은 이보다 세밀한 규칙을 씁니다. 여기서는 "연속한 코어여야 한다"는 한 가지 조건만 보여줍니다. 이번 실습 환경은 칩이 1개뿐이라 이 상황 자체가 생기지 않습니다.
0-1)
Lab 1의 실제 순서 — 명령어 9단계
- 도구 설치AWS CLI v2, kubectl, eksctl, helm, jq. 개발용 EC2에서 한 번만.
- 환경 변수와 AMI 조회리전·클러스터명·EKS 버전·인스턴스 타입을 잡고, SSM에서 Neuron 최적화 AMI ID를 조회합니다.
- 클러스터 접근 설정
aws eks update-kubeconfig로 kubeconfig를 받고kubectl cluster-info로 확인합니다. - (옵션) 노드 접속용 SSH 키 생성
ssh-keygen으로 만든 공개키를 노드그룹 설정에 넣습니다. - 네트워킹 확인VPC ID와 퍼블릭 라우트 테이블을 찾고,
trn1.2xlarge를 지원하는 AZ의 퍼블릭 서브넷 두 개를 고릅니다. - 노드그룹 생성
eks_nodegroup.yaml을 만들고eksctl create nodegroup. 3~4분 걸립니다. - S3 버킷 생성컴파일 캐시용 버킷
ai-infra-summit-vllm-models-cache-{ACCOUNT_ID}. - device plugin 재설치 + 스케줄러 확장기존 리소스 4종을 지운 뒤 Helm으로 재설치합니다.
- S3 CSI 드라이버 설치Mountpoint for S3 CSI Driver를 Helm으로.
1단계 — 도구 설치
2단계 — 환경 변수와 Neuron 최적화 AMI 조회
3단계 — 클러스터 접근 설정
k9s도 깔아 두면 이후가 편하다
4단계 — (옵션) 노드 접속용 SSH 키
5단계 — VPC · 서브넷 확인
6단계 — 노드그룹 정의
노드그룹 생성 (3~4분)
생성 확인 — aws.amazon.com/neuron 이 보이면 성공
7단계 — S3 버킷 생성
8단계 — device plugin을 지우고 Helm으로 재설치, 그리고 스케줄러 확장
9단계 — S3 CSI 드라이버
최종 확인
npd.enabled=false는 Neuron Node Problem Detector를 끄는 옵션입니다.
실습 규모에서는 필요 없고, 켜면 파드가 더 늘어납니다.
워커 노드에 직접 들어가서 보기 — neuron-ls / neuron-top
Session Manager로 neuron-trn1-2x 노드에 접속하면 호스트 레벨에서 Neuron 상태를 직접 볼 수 있습니다.
nvidia-smi 대신 neuron-ls(디바이스 목록)와 neuron-top(실시간 사용률)을 씁니다.
여기서 확인할 것 두 가지입니다. 첫째, /dev/neuron0과 그 아래 ng0n1 형태의
코어 단위 디바이스 노드가 커널에서 이미 나뉘어 있다는 것. 둘째, 나중에 vLLM 파드가 뜨면
neuron-top에서 코어 2개가 동시에 올라가는 모습입니다 — TP=2가 실제로 동작하는 증거입니다.
- 정의 — Lab 1은 이미 있는 EKS 컨트롤 플레인에 Trainium 노드와 그 부속품을 붙이는 작업입니다. 노드그룹 · device plugin · 스케줄러 확장 · S3 CSI 네 가지가 결과물입니다.
- 성질 — device plugin을 지우고 다시 까는 이유는 Helm 소유권입니다. AMI가 자동 배포한 리소스에는 Helm 어노테이션이 없어 충돌하고, 나중에 옵션을 바꿀 수도 없습니다.
- 쓰임 — 노드에
aws.amazon.com/neuron: 1이 뜨면 성공입니다. 텐서 병렬을 쓸 때는 코어가 몇 개 남았느냐보다 어디에 남았느냐가 중요해서, 그 판단을my-scheduler가 맡습니다.
여기서 잠깐 멈춥니다. 이 워크샵에서 가장 흥미로운 질문이 하나 남아 있습니다 — 왜 Neuron에는 전용 컨테이너 런타임이 없을까?
04NVIDIA와 Neuron은 왜 다르게 끼어드나
한 줄 요약: NVIDIA는 컨테이너 런타임 계층에서, Neuron은 순수 디바이스 플러그인 계층에서만 개입합니다.
NVIDIA GPU 노드에서 containerd 설정을 열어 보면 nvidia라는 런타임 항목이 있고
BinaryName = "/usr/bin/nvidia-container-runtime"이 잡혀 있습니다.
파드도 runtimeClassName: nvidia를 지정해야 GPU를 씁니다.
그런데 Trainium 노드에는 그런 게 없습니다.
default_runtime_name = "runc" 그대로고, vLLM 파드도 runtimeClassName을 지정하지 않습니다.
같은 "가속기를 컨테이너에 꽂는 일"인데 왜 한쪽만 특별 취급이 필요한가 — 이 질문의 답이
두 하드웨어 생태계의 설계 차이를 그대로 보여줍니다.
- OCI runtime / runc
- 컨테이너를 실제로 만드는 가장 아래층 프로그램. 네임스페이스·cgroup을 설정하고 프로세스를 띄웁니다.
- prestart hook
- runc가 컨테이너 프로세스를 시작하기 직전에 끼워 실행하는 외부 프로그램. NVIDIA 툴킷이 여기에 붙습니다.
- bind-mount
- 호스트의 파일·디렉터리를 컨테이너 안의 경로에 그대로 비춰 주는 마운트 방식.
- device cgroup
- 커널이 "이 프로세스는 어떤 디바이스에 접근해도 되는지"를 관리하는 규칙. 마운트만으로는 부족하고 이 허가가 따로 필요합니다.
- CDI
- Container Device Interface. "이 디바이스를 컨테이너에 넣으려면 무엇이 필요한지"를 YAML로 적어 둔 표준. CNI(네트워크)·CSI(스토리지)·CRI(런타임)와 같은 계열의 이름입니다.
- ioctl ABI
- 유저 공간 프로그램이 커널 드라이버에 명령을 보내는 규약. 이 규약이 안정적이면 버전이 달라도 통신이 됩니다.
비유 — 번역기가 필요한 손님과 필요 없는 손님
회사에 외국 손님이 옵니다. 두 경우가 있습니다.
손님 A(NVIDIA)는 통역이 필요합니다. 회의실에 들여보내기 전에 매번 통역사를 붙여야 하고,
통역사는 그 손님이 쓰는 방언 버전과 정확히 맞아야 합니다.
회사 안내데스크에는 "이 손님에게는 통역사 배정"이라는 절차가 등록돼 있습니다.
통역사가 하는 일은 문서를 회의실 안으로 옮겨 놓는 것(libcuda.so 주입)입니다.
손님 B(Neuron)는 이미 회사 언어를 할 줄 압니다. 필요한 자료도 본인이 들고 왔습니다 (Neuron SDK는 pip 패키지로 컨테이너 이미지 안에 이미 들어 있습니다). 안내데스크는 회의실 문만 열어 주면 끝입니다. 특별 절차가 필요 없습니다.
차이의 뿌리는 "자료를 누가 들고 있느냐"입니다.
NVIDIA는 유저 공간 라이브러리가 커널 드라이버 버전과 강하게 묶여 있어 호스트 것을 써야 하고,
Neuron은 안정적인 ioctl 규약으로 통신해서 컨테이너 안의 SDK가 그대로 먹힙니다.
작은 숫자로 한 번
도식 4-1. NVIDIA GPU 파드가 뜨는 과정
device plugin이 Allocate() 응답에 환경변수(NVIDIA_VISIBLE_DEVICES)를 담아 보내면,
그걸 가로채는 별도 런타임이 필요합니다. 이 런타임이 디바이스 노드를 만들고,
호스트의 드라이버 라이브러리를 컨테이너 안으로 bind-mount하고, ldconfig를 다시 돌립니다.
최근에는 이 과정이 CDI 스펙(/var/run/cdi/nvidia.yaml)으로 표준화되고 있지만,
"라이브러리를 주입한다"는 무거운 작업 자체는 그대로입니다.
커널 드라이버 버전과 유저 공간 라이브러리 버전이 엄격하게 맞아야 하기 때문입니다.
도식 4-2. AWS Neuron 파드가 뜨는 과정
같은 그림을 그렸는데 상자 하나가 통째로 빠져 있습니다.
device plugin이 Allocate() 응답에 호스트 디바이스 경로 목록을 그냥 담아 반환하면,
그것은 CRI 표준 device-mount 메커니즘(docker run --device와 같은 개념 —
cgroup 디바이스 허용 규칙 + bind-mount)으로 처리되고 표준 runc가 그대로 끝냅니다.
대신 빠진 자리를 스케줄러 확장이 메웁니다. 런타임이 개입하지 않으니 "어느 코어를 줄지"를 더 위층에서 정교하게 정해야 하기 때문입니다(3장).
왜 이 차이가 생기나 — 네 가지 축
| 항목 | NVIDIA | AWS Neuron |
|---|---|---|
| 유저 공간 라이브러리 | 호스트 드라이버와 강하게 묶임. libcuda.so가 커널 모듈과 정확히 맞아야 함 → 런타임이 매번 주입 |
Neuron SDK(torch-neuronx, neuronx-cc)가 pip 패키지로 이미지 안에 포함. 커널 드라이버와는 안정적인 ioctl ABI로 통신 → 주입 불필요 |
| 디바이스 제어 | 커널 드라이버 하나가 GPU 전체를 통제. 나누려면 MPS·MIG 같은 별도 분할 로직이 필요 | /dev/neuronN(칩) + /dev/ngXnY(코어)가 커널 레벨에서 이미 분리돼 있음. plugin은 "어느 노드를 마운트할지"만 결정 |
| 런타임 개입 | 필요 — nvidia-container-runtime 또는 CDI 훅 |
불필요 — 표준 runc + device cgroup 규칙 |
| 코어 단위 격리 | MIG(하드웨어 파티셔닝) 또는 time-slicing(소프트웨어) | NEURON_RT_VISIBLE_CORES 환경변수 + ngXnY 노드를 파드별로 선택 마운트 |
NVIDIA는 무거운 통합입니다 — 전용 런타임이 드라이버 라이브러리까지 매번 주입해야 해서 containerd 설정에 흔적이 남습니다.
Neuron은 가벼운 통합입니다 — 커널이 이미 디바이스를 세분화해 노출하고 SDK는 이미지 안에 있으니,
표준 쿠버네티스 device-allocation API만으로 충분합니다.
그 대신 세밀한 스케줄링을 전용 스케줄러 확장이 보완합니다.
CDI로 옮겨 가는 중 — CNI · CSI · CRI · CDI
쿠버네티스에는 "이 일은 플러그인이 알아서 하라"고 넘기는 표준 인터페이스가 여럿 있습니다. CNI는 네트워크, CSI는 스토리지, CRI는 컨테이너 런타임, 그리고 최근 붙은 CDI가 디바이스입니다.
NVIDIA 노드의 containerd 설정에는 전용 런타임이 잡혀 있다
CDI 설정과 실제 스펙 파일 전문 — 주입할 디바이스 노드와 라이브러리 목록이 그대로 적혀 있다
Neuron device plugin도 최근 버전부터 CDI를 지원하기 시작했습니다. 다만 스펙이 훨씬 단순합니다 — 라이브러리 마운트 목록이 없기 때문입니다. 같은 표준을 쓰더라도 채워 넣을 내용의 양이 다릅니다.
- 정의 — NVIDIA는 컨테이너 런타임 계층에서 개입하고(전용 런타임 + prestart 훅), Neuron은 디바이스 플러그인 계층에서만 개입합니다(표준 runc).
- 성질 — 차이의 뿌리는 유저 공간 라이브러리를 누가 들고 있느냐입니다.
libcuda.so는 커널 모듈과 버전이 묶여 호스트 것을 주입해야 하고, Neuron SDK는 이미지 안에 pip로 들어 있어 그럴 필요가 없습니다. 커널이/dev/ngXnY로 코어를 이미 나눠 둔 것도 큽니다. - 쓰임 — 그래서 Trainium 노드의 containerd 설정은 순정 그대로고, 파드도
runtimeClassName을 안 씁니다. 대신 빠진 정교함을my-scheduler가 메웁니다. 6W-2에서 NVIDIA 쪽 훅 네 단계를 직접 보면 이 대비가 더 선명해집니다.
바닥 이야기는 끝났습니다. 이제 실제로 모델을 올립니다 — 그런데 그전에 컴파일을 먼저 해야 합니다.
05vLLM 올리기 — init 컨테이너와 S3 캐시
한 줄 요약: 모델을 미리 컴파일해 S3에 넣어 두면, 다음 기동은 8분에서 20초로 줄어듭니다.
vllm serve 한 줄이면 떴는데, 왜 여기는 init 컨테이너가 필요하지?"1장에서 본 것 하나가 여기서 값을 치릅니다 — NeuronCore의 온칩 SRAM이 컴파일러 관리라는 사실입니다. 무엇을 언제 어느 메모리에 올릴지를 실행 전에 정해야 하고, 그래서 모델은 Neuron 전용 형식으로 컴파일되어야 합니다. 이게 4분 걸립니다.
파드가 재시작될 때마다 4분씩 태울 수는 없습니다.
그래서 워크샵은 컴파일 결과물을 S3에 저장해 두고, 다음부터는 내려받아 쓰는 구조를 만듭니다.
이 패턴은 Neuron만의 이야기가 아닙니다 — GPU에서도 torch.compile 캐시에 같은 문제가 있습니다(6W-2 8장).
- init container
- 메인 컨테이너가 뜨기 전에 먼저 실행되고 끝나야 하는 컨테이너. 준비 작업을 맡습니다.
- ConfigMap / Secret
- 설정값과 비밀값을 파드 밖으로 빼두는 쿠버네티스 오브젝트. Secret은 base64로 저장될 뿐 암호화는 아닙니다.
- PV / PVC
- PersistentVolume(실제 저장소) / PersistentVolumeClaim(그걸 쓰겠다는 신청서). 파드는 신청서를 통해 저장소에 붙습니다.
- RWX (ReadWriteMany)
- 여러 노드의 여러 파드가 동시에 읽고 쓸 수 있는 접근 모드.
- FUSE
- Filesystem in Userspace. 파일시스템을 커널이 아니라 일반 프로그램으로 구현하는 방식입니다.
- strong read-after-write consistency
- 쓰기가 끝난 직후 다른 프로세스가 읽어도 최신 데이터가 보장되는 성질. S3는 2020년부터 이 성질을 가집니다.
비유 — 매번 조립하는 가구와 미리 조립해 둔 가구
이케아 가구를 쓸 때마다 조립하고 쓰고 나면 분해한다고 생각해 보세요. 말이 안 됩니다. 한 번 조립해서 창고에 넣어 두고, 필요할 때 꺼내 오는 게 당연합니다.
init 컨테이너가 하는 일이 그겁니다. 창고(S3)를 먼저 뒤져서 이미 조립된 게 있으면 그냥 가져오고, 없으면 부품을 사 와서(Hugging Face 다운로드) 조립하고(Neuron 컴파일) 창고에 넣어 둡니다(S3 업로드).
메인 컨테이너는 조립을 아예 모릅니다. 창고에서 완성품을 꺼내 쓰기만 합니다. 역할을 나눠 놓으면 메인 컨테이너의 기동이 빨라집니다.
작은 숫자로 한 번
도식 5-1. Lab 2가 만드는 것들과 그 관계
일곱 개의 오브젝트가 만들어지는데, 실제 의존 관계는 그림의 화살표 방향입니다. Secret과 ConfigMap은 init 컨테이너가, PVC는 두 컨테이너가 공유하고, Service는 메인 컨테이너만 바라봅니다.
그래서 init 컨테이너가 실패하면 Service의 엔드포인트가 아예 안 생깁니다 —
kubectl get endpoints vllm-service가 비어 있으면 메인이 아니라 init을 먼저 봐야 합니다.
캐시가 있고 없고에 따른 기동 시간
클릭 + 슬라이더이미지 4분 · 컴파일 4분 · 서버 20초는 워크샵에서 실제로 측정된 값입니다. 모델 크기를 키웠을 때의 증가분은 비례식으로 추정한 모형입니다.
model-prep init 컨테이너의 로그를 따라간 화면입니다. 아래에서 위로 읽으면 순서가 보입니다 —
torch.float32 → torch.bfloat16 변환 경고가 층마다 반복되고(체크포인트가 fp32로 저장돼 있어서),
Done Sharding weights in 4.68초(TP=2로 가중치를 반씩 쪼갠 단계),
Finished weights loading in 10.76초,
# neuron blocks: 4(= max-num-seqs=4),
Warmup completed in 0.40초, 그리고 Model compiled successfully!.
마지막 두 줄의 nrtucode: 52 object(s) leaked, improper teardown과
stream closed: EOF는 실패가 아닙니다.
init 컨테이너가 할 일을 끝내고 스스로 종료하면서 나는 정리 메시지입니다 —
Model compiled successfully!가 그 위에 찍혀 있으면 정상입니다.
libnccom-net.so load failed 경고도 마찬가지로, 여러 노드를 묶을 때 쓰는 EFA 라이브러리가 없다는 뜻이라
노드 한 대짜리 이번 실습에서는 상관없습니다.--tensor-parallel-size=2가 메모리에 남긴 흔적
--tensor-parallel-size=2가 한 일입니다.
TP를 안 걸었다면 한쪽에 3.6GB가 통째로 올라가고 다른 쪽은 0B였을 겁니다.
내역도 읽어 둘 만합니다 — Tensors 2.1GB(가중치),
Model Code 222MB(컴파일된 NEFF 실행 코드),
Constants 285MB. 위의 Model compiled successfully!가 만들어 낸 게 이 222MB입니다.S3를 파일시스템처럼 쓴다는 것
PV에 capacity: 100Gi라고 적혀 있지만, 이 숫자는 아무 의미가 없습니다.
Mountpoint는 실제 쿼터를 강제하지 않고 S3는 사실상 무제한이라,
이건 쿠버네티스 스케줄링을 위한 논리값일 뿐입니다.
더 중요한 건 완전한 POSIX 파일시스템이 아니라는 점입니다.
init 컨테이너가 하는 cp -r 같은 통짜 복사는 문제없지만,
파일을 열어 놓고 이어쓰기(append) 하는 패턴은 지원되지 않을 수 있습니다.
로그 파일을 여기에 두면 안 되는 이유입니다.
Mountpoint FUSE는 어디에 끼어드나
클릭PutObject 한 번으로 끝나고, 읽기는 GetObject로 내려갑니다.S3 CSI 드라이버는 한 번 stage해 두고 bind-mount로 재사용하는 블록 스토리지 방식이 아닙니다.
마운트 지점마다 mount-s3 프로세스가 새로 뜹니다.
그래서 RWX로 여러 파드가 같은 버킷을 봐도 서로 독립적이라 안전합니다.
Lab 2의 실제 순서와 확인 명령
- Hugging Face 토큰을 Secret으로값은 셸 변수로 넘겨 히스토리에 남기지 않습니다.
- ConfigMap 생성모델명·TP 크기·포트 등 14개 키. 두 컨테이너가 공유합니다.
- PV / PVC 생성S3 버킷을 100Gi RWX Retain으로.
Bound가 되면 성공입니다. - Deployment 배포init 컨테이너(
model-prep) + 메인 컨테이너(vllm-server). - Service 생성LoadBalancer 타입 8080. AWS CLB가 하나 만들어집니다.
1단계 — HF 토큰 Secret
2단계 — ConfigMap 전문
3단계 — S3 기반 PV / PVC 전문
4단계 — vLLM Deployment 전문 (init 컨테이너 + 메인 컨테이너)
5단계 — Service(LoadBalancer) 생성과 확인
배포 완료 확인 — init 컨테이너 로그가 제일 중요하다
컨트롤러 파드 로그로 4장의 내용이 실제로 도는지 확인
워커 노드에서 직접 확인 — vLLM 프로세스와 S3 FUSE 마운트
대화형 채팅 테스트 클라이언트
(참고) init 컨테이너가 만들어 내는 neuron_config.json — 214줄
이건 우리가 쓰는 파일이 아니라 컴파일러가 뱉는 산출물입니다.
tensor_parallel_size, max_length, batch_size 같은 값이
ConfigMap에서 넘어와 여기 박히고, 이 조합이 곧 S3 캐시 키가 됩니다 —
하나라도 바뀌면 캐시가 안 맞아 4분을 다시 씁니다.
- 정의 — Lab 2는 init 컨테이너 패턴입니다. init이 S3 캐시 확인 → (없으면) 다운로드·컴파일·업로드까지 하고, 메인 컨테이너는 준비된 아티팩트로 바로 시작합니다.
- 성질 — Neuron은 온칩 SRAM이 컴파일러 관리라 실행 전 컴파일이 필수고, 그게 4분입니다. 캐시가 있으면 전체 기동이 8분 → 20초로 줄어듭니다. 모델이 클수록 이 격차가 커집니다.
- 쓰임 — S3는 FUSE로 붙은 유사 파일시스템입니다. 통짜 복사는 되지만 이어쓰기·하드링크는 기대하면 안 됩니다. PV의
100Gi는 쿼터가 아니라 스케줄링용 숫자입니다.
모델이 돕니다. 이제 바깥에서 부를 수 있게 문을 냅니다.
06바깥에서 부르기 — Ingress
한 줄 요약: 서비스마다 로드밸런서를 하나씩 만드는 대신, Ingress 하나가 경로로 나눠 보냅니다.
Lab 2의 vllm-service는 type: LoadBalancer라 AWS CLB가 하나 붙었습니다.
이미 바깥에서 접근이 됩니다. 그런데 Lab 4에서 Prometheus와 Grafana를 올리면,
같은 방식으로는 로드밸런서가 세 개가 됩니다. 주소도 세 개, 비용도 세 배입니다.
Ingress는 로드밸런서 하나 뒤에서 경로로 나눠 보냅니다.
/는 vLLM, /p8s는 Prometheus, /grafana는 Grafana로.
Lab 4에서 서브패스 때문에 한 번 걸려 넘어지는데, 그 이유가 여기 있습니다.
- Service (LoadBalancer)
- 서비스 하나마다 클라우드 로드밸런서를 하나씩 붙이는 방식. 간단하지만 개수만큼 돈이 듭니다.
- Ingress / Ingress Controller
- Ingress는 "이 경로는 저 서비스로"라는 규칙표, Ingress Controller는 그 규칙을 실제로 집행하는 프록시(여기서는 NGINX)입니다.
- IngressClass
- 여러 컨트롤러가 있을 때 어느 컨트롤러가 이 규칙을 맡을지 지정하는 이름. 여기서는
nginx. - rewrite-target
- 들어온 경로를 백엔드로 넘길 때 바꿔 주는 설정.
/p8s/targets를/targets로 바꾸는 식입니다. - CLB
- Classic Load Balancer. 이 워크샵의 서비스와 Ingress 컨트롤러가 각각 하나씩 만듭니다.
비유 — 건물 하나에 입주사 셋
입주사마다 따로 건물을 짓는 것이 LoadBalancer 서비스입니다. 확실하지만 낭비입니다.
Ingress는 건물 하나에 안내데스크를 두는 것입니다. 손님이 "3층 회계팀요" 하면 안내데스크가 올려 보냅니다. 주소는 하나, 건물도 하나입니다.
다만 함정이 있습니다. 3층 회계팀은 자기가 3층에 있다는 걸 모릅니다.
자기 방 번호를 "101호"로 알고 있어서, 손님이 "3층 101호"라고 말하면 못 알아듣습니다.
Grafana가 Prometheus를 /p8s 없이 찾다가 실패하는 게 정확히 이 상황입니다.
작은 숫자로 한 번
도식 6-1. 요청이 파드에 닿기까지
이 워크샵에는 CLB가 두 개 생깁니다. 하나는 Lab 2의 vllm-service가,
다른 하나는 Lab 3의 NGINX Ingress Controller가 만든 것입니다.
두 경로 모두 결국 같은 파드로 가지만, 포트와 주소가 다릅니다.
Lab 5의 부하 테스트가 :8080 주소를 쓰는 이유가 여기 있습니다 —
Ingress를 거치지 않고 서비스 CLB로 직접 때려서 프록시 계층의 영향을 뺀 것입니다.
Ingress 설치와 테스트
NGINX Ingress Controller 설치와 확인
vLLM용 Ingress 리소스 전문
엔드포인트를 잡아서 바로 호출해 보기
대화형 클라이언트로도 확인
문제가 생겼을 때 볼 곳 — 규칙 · 컨트롤러 · 백엔드 엔드포인트
셋째(kubectl get endpoints vllm-service)가 비어 있으면 5장의 init 컨테이너를 봐야 합니다.
/v1/models가 열린다
:8080/v1/models를 연 화면입니다.
"owned_by": "vllm"이니 응답을 만든 건 vLLM이고,
"max_model_len": 1024는 5장에서 준 --max-model-len=1024가
그대로 살아 있다는 확인입니다.
이 한 화면으로 Deployment → Service → LoadBalancer 세 단계가 전부 이어졌다는 걸 알 수 있습니다.
(엔드포인트 주소는 가렸습니다.)- 정의 — Ingress는 로드밸런서 하나 뒤에서 경로로 나눠 보내는 규칙표고, NGINX Ingress Controller가 그 규칙을 집행합니다.
- 성질 — 서비스마다 LoadBalancer를 붙이면 개수만큼 CLB가 생깁니다. Ingress를 쓰면 하나로 줄지만, 대신 서브패스를 백엔드가 알아야 하는 문제가 따라옵니다.
- 쓰임 — 이 워크샵에는 CLB가 결국 두 개 생깁니다(서비스용 + Ingress용). Lab 5의 부하 테스트는 프록시 영향을 빼려고 서비스 쪽
:8080을 직접 씁니다.
돌아가는 건 확인했습니다. 그런데 안에서 무슨 일이 벌어지는지는 아직 안 보입니다.
07보이게 만들기 — Prometheus와 Grafana
한 줄 요약: vLLM은 이미 /metrics로 지표를 내보내고 있습니다. 남은 일은 긁어 오고 그리는 것뿐입니다.
바깥에서 재는 지연시간 하나로는 원인을 못 가립니다. 요청이 큐에서 기다린 것인지, 첫 토큰이 늦게 나온 것인지, KV 캐시가 꽉 차서 새 요청을 못 받은 것인지 — 셋은 처방이 다릅니다.
다행히 vLLM은 이 구분을 이미 지표로 내보내고 있습니다.
vllm:num_requests_waiting과 vllm:num_requests_running을 나란히 놓으면
첫 번째 질문에 바로 답이 나옵니다. 우리가 할 일은 새로 만드는 게 아니라 연결하는 것입니다.
- scrape
- Prometheus가 대상의
/metrics를 주기적으로 긁어 가는 동작. 대상이 밀어 보내는 게 아닙니다. - service discovery
- 긁을 대상을 수동 목록이 아니라 쿠버네티스 API로 찾아내는 방식.
prometheus.io/scrape=true어노테이션이 신호가 됩니다. - counter / gauge
- counter는 늘기만 하는 누적값(
..._total), gauge는 오르내리는 현재값입니다. - TTFT
- Time To First Token — 요청 후 첫 토큰이 나올 때까지의 시간.
- ITL
- Inter-Token Latency — 토큰과 토큰 사이의 간격. 체감 속도를 좌우합니다.
- KV cache usage
- KV 캐시가 얼마나 찼는지의 비율. 100%에 닿으면 새 요청이 대기열로 밀립니다.
비유 — 병원 대기실의 두 숫자
병원 대기실에 두 개의 숫자가 떠 있다고 해봅시다. 진료 중 3명, 대기 12명.
대기가 12명인데 진료 중이 3명이면 의사가 부족한 것입니다(파드를 늘려야 함).
진료 중이 3명인데 대기가 0명이면 여유가 있는 것입니다.
진료 중이 3명에서 안 늘어나고 대기만 쌓이면, 진료실 자체가 3개뿐인 겁니다
(max-num-seqs=4가 그 한계선입니다).
여기에 대기실 의자가 몇 개 남았는지(KV 캐시 사용률)를 더하면 그림이 완성됩니다. 의자가 꽉 차면 대기조차 못 받습니다.
작은 숫자로 한 번
첫째 — prometheus-values.yaml에 job_name: kubernetes-pods 블록이
중복으로 들어 있습니다. 그대로 쓰면 Prometheus가 뜨지 않습니다. 하나를 지우고 설치해야 합니다.
둘째 — /p8s 서브패스 노출은 강의자료에 적힌 방식으로는 되지 않습니다.
강의자료는 Prometheus를 먼저 설치한 뒤 helm upgrade --reuse-values --set --web.route-prefix=/p8s로
플래그를 나중에 붙이라고 하는데, 이 명령은 적용되지 않습니다.
설치할 때부터 values 파일에 넣어야 하고, 프로브 설정도 같이 바꿔야 합니다.
아래 〈실습 정리본 — 동작하는 /p8s 노출 설정〉 상자의 소스를 쓰십시오.
셋째 — Prometheus를 /p8s 서브패스로 노출했으면
Grafana의 데이터소스 URL에도 /p8s를 붙여야 합니다.
이걸 빼면 Grafana는 뜨는데 패널이 전부 비어 있습니다(6장의 "3층 101호" 문제).
실습 정리본 — 동작하는 /p8s 노출 설정
강의자료 방식이 왜 안 되나 — 두 가지가 겹쳐 있습니다.
| 문제 | 강의자료 | 실습 정리본 |
|---|---|---|
1. --set 키가 값 경로가 아니다 |
--set --web.route-prefix=/p8sHelm --set은 차트값경로=값 형식을 받습니다.
--web.route-prefix는 Prometheus 바이너리의 명령줄 플래그이지 차트 값 경로가 아닙니다.
그래서 이 인자는 서버 인자로 전달되지 않습니다. |
values 파일의 server.extraArgs에 넣습니다.
명령줄로 넣어야 한다면 점을 이스케이프해야 합니다 →
--set 'server.extraArgs.web\.route-prefix=/p8s' |
| 2. 프로브가 루트 경로를 본다 | 없음. 그래서 route-prefix가 걸리면 차트 기본 HTTP 프로브가
/-/ready를 찌르는데 서버는 이제 /p8s/-/ready에서만 답합니다.
404 → 파드가 Ready로 안 넘어감(1/2 Running에서 멈춤). |
tcpSocketProbeEnabled: true를 켭니다.
프로브를 HTTP 대신 TCP 연결 확인으로 바꿔서 경로와 무관하게 통과시킵니다. |
동작하는 prometheus-values.yaml — 설치할 때 한 번에 넣습니다.
굵게 칠한 네 줄이 강의자료에 없는 부분입니다.
<ingress-주소>는 Ingress Controller의 LoadBalancer 주소입니다.
Ingress를 먼저 만들어 주소를 받은 뒤 값을 채우거나, 아래처럼 이스케이프한 --set으로 한 번 더 올립니다.
kubectl get pod -n monitoring에서 prometheus-server가
2/2 Running이면 프로브가 통과한 것이고,
1/2에서 멈춰 있으면 tcpSocketProbeEnabled를 빠뜨린 것입니다.
브라우저에서 /p8s/targets가 열리고 화면 모양이 깨지지 않으면
web.external-url·web.route-prefix가 제대로 들어간 것입니다.
/p8s 아래에서 열리고, vLLM 지표가 잡힌다
/p8s/query입니다 — 서브패스가 실제로 걸렸다는 뜻입니다.
쿼리창에 vllm만 쳐도 vllm:num_requests_running,
vllm:gpu_cache_usage_perc, vllm:e2e_request_latency_seconds_*가 줄줄이 뜹니다.
앞 장에서 붙인 스크레이프 어노테이션이 먹었다는 확인입니다.
(계정·엔드포인트 주소는 가렸습니다.)vllm:generation_tokens_total을 그래프로 그린 것입니다.
화면 모양이 깨지지 않고 그래프 탭이 정상 동작한다는 건
web.external-url이 들어가서 정적 자산(JS/CSS) 경로가 살아 있다는 뜻입니다.
이 설정을 빼면 페이지는 열리는데 스타일이 전부 깨진 채로 뜹니다.Grafana 대시보드 패널과 쿼리
Prometheus 설치 — 워크샵 values의 job_name: kubernetes-pods 중복을 먼저 지워야 뜬다
Prometheus Ingress를 /p8s 서브패스로 추가
Ingress 부분은 맞습니다. 하지만 그 아래
helm upgrade --reuse-values --set --web.route-prefix=... 두 줄은
적용되지 않습니다(--set의 키가 차트 값 경로가 아니라 바이너리 플래그 이름이라서).
설령 키를 고쳐 넣더라도 프로브가 /-/ready에서 404를 맞아
파드가 1/2 Running에서 멈춥니다.
위의 〈실습 정리본 — 동작하는 /p8s 노출 설정〉을 쓰십시오.
여기 원문을 남겨 두는 건 무엇이 달라졌는지 대조해 보기 위해서입니다.
Grafana 설치
데이터소스 URL에 /p8s를 붙인다 — 이걸 빼면 패널이 전부 빈다
Grafana Ingress 추가
대시보드는 JSON으로 만들어 ConfigMap으로 넣고, Grafana 파드에 볼륨으로 마운트합니다.
vLLM 대시보드 JSON 전문
ConfigMap으로 가져오기
Grafana 파드에 마운트
vLLM Deployment에 스크레이프 어노테이션 — 이게 없으면 Prometheus가 안 긁는다
| 패널 | 쿼리 | 무엇을 보는가 |
|---|---|---|
| Total Successful Requests | vllm:request_success_total | 누적 성공 건수 |
| Running Requests | vllm:num_requests_running | 동시 처리 중 — max-num-seqs에 닿았는지 |
| Waiting Requests | vllm:num_requests_waiting | 대기열 — 여기가 쌓이면 용량 부족 |
| KV Cache Usage | vllm:gpu_cache_usage_perc | 캐시 포화 — 100%면 새 요청을 못 받음 |
| Total Prompt Tokens | vllm:prompt_tokens_total | 프리필 쪽 부하 |
| Total Generated Tokens | vllm:generation_tokens_total | 디코드 쪽 부하 |
| Request Success Over Time | vllm:request_success_total (시계열) | 처리율 추이 |
| Token Generation Over Time | prompt / generation tokens (시계열) | 토큰 처리량 추이 |
지표 이름에 gpu가 들어 있는 게 눈에 걸릴 수 있습니다 —
vllm:gpu_cache_usage_perc는 Trainium에서도 그대로 이 이름을 씁니다.
vLLM 입장에서는 "가속기 메모리의 캐시"라는 뜻이고, 백엔드가 무엇이든 이름은 바뀌지 않습니다.
curl로 한 번 찔러 본 그 요청입니다.
이 화면이 다음 장의 기준선입니다 — 부하를 걸면 이 여섯 숫자가 어떻게 움직이는지 보겠습니다.
(여기서 KV 캐시가 0%인 건 처리 중인 요청이 없어서이지, 캐시가 없어서가 아닙니다.)- 정의 — vLLM은
/metrics로 이미 지표를 내보내고 있습니다. Lab 4가 하는 일은 Prometheus가 긁어 가게 어노테이션을 붙이고, Grafana로 그리는 것뿐입니다. - 성질 — running과 waiting을 나란히 보면 원인이 갈립니다. running이
max-num-seqs에 붙어 있고 waiting만 쌓이면 용량 문제, KV 캐시가 100%면 메모리 문제입니다. - 쓰임 — 워크샵 문서에 함정이 셋 있습니다.
job_name중복은 지우고 설치,/p8s노출은 values의server.extraArgs+tcpSocketProbeEnabled로(강의자료의--set --web.route-prefix는 안 먹습니다), Grafana 데이터소스 URL에는/p8s를 붙여야 합니다.
보이게 만들었으니 이제 밀어붙입니다. 어디까지 버티는지.
08성능 재기 — 부하를 걸어 보기
한 줄 요약: 평균 하나로는 아무것도 모릅니다. TTFT·ITL·처리량을 나눠서 봐야 병목이 보입니다.
이 숫자 하나로는 판단할 수 없습니다. 같은 53 tok/s라도 한 요청이 혼자 53인지 다섯 요청이 각각 10씩인지에 따라 완전히 다른 상황입니다. 앞은 모델이 느린 것이고, 뒤는 배칭이 잘 돌고 있는 것입니다.
그래서 세 숫자를 같이 봅니다. TTFT(첫 응답까지) · ITL(토큰 사이 간격) · 처리량(전체 초당 토큰). 동시 요청을 늘렸을 때 이 셋이 각각 어떻게 움직이는지가 시스템의 성격을 말해 줍니다.
- TTFT
- Time To First Token — 요청부터 첫 토큰까지. 프리필 단계의 비용입니다.
- ITL
- Inter-Token Latency — 토큰과 토큰 사이 간격. 디코드 단계의 비용입니다.
- E2E latency
- 요청부터 마지막 토큰까지의 전체 시간. 대략 TTFT + ITL × 출력 토큰 수입니다.
- P50 / P95 / P99
- 느린 순으로 줄 세웠을 때 중간값 · 상위 5% · 상위 1%. 평균보다 이쪽이 사용자 체감에 가깝습니다.
- llmperf
- Ray 팀이 만든 LLM 전용 부하 테스트 도구. 입력 토큰 수를 분포로 지정할 수 있습니다.
- continuous batching
- 요청이 도착하는 대로 돌아가는 배치에 끼워 넣는 방식. 이게 있어서 동시 요청이 늘어도 처리량이 따라 올라갑니다.
비유 — 카페의 세 가지 시간
카페에서 커피를 시킵니다. 세 가지 시간이 있습니다.
주문받고 첫 잔이 나오기까지(TTFT)는 바리스타가 얼마나 밀려 있느냐에 달렸습니다. 잔과 잔 사이 간격(ITL)은 기계 속도입니다. 시간당 총 잔 수(처리량)는 앞의 둘과 손님 수의 조합입니다.
손님이 늘면 TTFT는 길어집니다(줄이 생기니까). 하지만 ITL은 거의 그대로입니다(기계는 같은 속도로 뽑으니까). 그리고 총 잔 수는 올라갑니다 — 기계가 놀지 않게 되니까. 어느 지점에서 이 관계가 깨지는지가 시스템의 한계입니다.
작은 숫자로 한 번
동시 요청을 늘리면 무엇이 먼저 무너지나
↔ 슬라이더max-num-seqs=4가 상한이라 초과분은 대기열로 밀리고, TTFT가 꺾여 올라갑니다. ③ 처리량은 그 뒤로 거의 평평합니다 — 더 밀어넣어도 총량은 안 늘고 지연만 늘어납니다.기준점(TTFT 166 ms, ITL 11 ms, 53.4 tok/s)은 워크샵 실측값이지만, 동시 요청을 늘렸을 때의 곡선은 대기 이론으로 그린 모형입니다. 실제로는 KV 캐시 포화가 겹치면서 더 급하게 꺾입니다.
그림 6이 맞는지 실제로 확인해 보기
위 그림은 모형입니다. 그런데 이번 실습에서는 부하를 걸기 전 · 파이썬 부하 테스트 중 · llmperf 벤치마크 중의 대시보드를 차례로 찍어 두었습니다. 7장 끝의 기준선 화면과 나란히 놓고 보면, 모형이 말한 세 가지가 그대로 나옵니다.
--max-num-seqs=4가 상한이기 때문입니다.
그리고 KV 캐시가 먼저 100%에 닿습니다. 동시 처리 수보다 캐시가 먼저 포화한다는 뜻입니다.Request Success Over Time·Token Generation Over Time의
계단 모양도 같은 이야기입니다. 토큰이 쏟아지는 구간과 요청 수가 오르는 구간이 겹칩니다.neuron-top — 두 코어가 나란히 48%
--tensor-parallel-size=2로 모델을 반씩 쪼개 놓았으니, 한 요청이 두 코어를 동시에 씁니다.
한쪽만 100%이고 다른 쪽이 0%라면 TP가 안 걸린 것입니다.
48%라는 값도 읽을 거리가 있습니다 —
KV 캐시는 100%인데 코어는 절반만 쓰고 있습니다.
즉 이 시점의 병목은 연산이 아니라 메모리입니다.
코어를 더 굴리고 싶으면 max-model-len을 줄이거나 KV 캐시를 키워야지,
요청을 더 밀어넣는다고 되지 않습니다.강의자료의 llmperf 결과는 분당 204.98건이었고, 이번 실습에서는 같은 조건에서 분당 207.35건 · 출력 처리량 343.71 tok/s가 나왔습니다(50건, 오류 0건). 1% 남짓 차이인데, 이 정도는 같은 인스턴스 타입에서도 그때그때 생깁니다. 벤치마크 숫자를 볼 때는 한 번 돌린 값이 아니라 여러 번의 분포를 봐야 한다는 뜻입니다. 이 페이지의 P50/P95/P99를 강조하는 이유도 같습니다.
부하 테스트 환경 만들기
테스트 도구는 클러스터 안에 별도 네임스페이스로 띄웁니다. 바깥에서 때리면 네트워크 왕복이 섞여 들어가기 때문입니다.
환경 변수 — 워크샵 문서의 클러스터명 오타에 주의
테스트 러너 파드 전문
필요한 파이썬 패키지 설치
basic_load_test.py 전문
스크립트를 파드로 복사
기본 부하 테스트 실행 — 결과 53.4 tok/s
llmperf 설치
llmperf 토큰 벤치마크 — 결과 204.98 req/min
개별 요청 기록 확인 — TTFT · ITL · E2E
끝나면 네임스페이스째 지운다
- 정의 — 성능은 숫자 하나가 아니라 TTFT · ITL · 처리량 세 개입니다. 각각 프리필 비용 · 디코드 비용 · 전체 총량에 대응합니다.
- 성질 — 동시 요청을 늘리면 처리량은 오르고 TTFT는 거의 그대로입니다 —
max-num-seqs에 닿을 때까지만. 넘어서면 초과분이 대기열로 밀려 TTFT만 꺾여 올라갑니다. - 쓰임 — 이번 환경의 실측은 53.4 tok/s · 204.98 req/min · TTFT 166 ms · ITL 11 ms입니다. 부하 테스트는 클러스터 안에서 돌리고, 끝나면 네임스페이스째 지웁니다.
한 대로 부족하면 늘려야 합니다. 그런데 여기서 워크샵이 막힙니다.
09늘리기 — HPA와 GPU 오토스케일링
한 줄 요약: CPU 사용률로 GPU 워크로드를 늘리는 건 원리적으로 맞지 않습니다.
이 워크샵의 Lab 6은 실행이 안 됩니다.
계정의 vCPU 쿼터가 8이라 trn1.2xlarge를 한 대밖에 못 띄웁니다.
노드그룹을 하나 더 만들려고 하면 Quota 제약으로 실패합니다.
그런데 실행이 막힌 덕분에 더 중요한 질문이 남습니다. 설령 노드가 여러 대 있었다 해도, CPU 사용률 기준 HPA는 이 워크로드에 맞지 않습니다. GPU 노드는 CPU를 10%밖에 안 쓰면서 가속기는 100% 포화돼 있고 큐에 200건이 밀려 있을 수 있습니다. HPA는 그 상황을 한가하다고 읽습니다.
- HPA
- Horizontal Pod Autoscaler. 지표를 보고 파드 개수를 늘리고 줄이는 컨트롤러입니다.
- metrics-server
- 노드·파드의 CPU/메모리 사용량을 모아 HPA에 공급하는 컴포넌트. 없으면 HPA가 아예 동작하지 않습니다.
- KEDA
- Kubernetes Event Driven Autoscaling. 임의의 외부 지표로 HPA를 굴릴 수 있게 해주는 프로젝트입니다.
- external scaler
- KEDA가 gRPC로 물어보는 외부 지표 공급자. 직접 만들어 붙일 수 있습니다.
- scale to zero
- 트래픽이 없을 때 파드를 0개까지 줄이는 것. 비싼 GPU 노드에서는 절약폭이 큽니다.
- NVML
- NVIDIA Management Library. GPU 상태를 직접 읽는 라이브러리(
libnvidia-ml.so).nvidia-smi도 이걸 씁니다.
비유 — 주방은 불타는데 계산대만 보는 매니저
식당 매니저가 계산대 앞 줄 길이만 보고 인력을 배치한다고 해봅시다. 계산은 금방 끝나니 줄은 늘 짧습니다. 매니저는 "한가하네" 하고 넘어갑니다.
그동안 주방은 주문 200건이 밀려 있습니다. 요리사들은 쉬지 못하고 있습니다. 하지만 매니저의 지표에는 이게 안 잡힙니다.
CPU 기준 HPA가 정확히 이 매니저입니다. 고쳐야 할 건 임계값이 아니라 보는 지표 자체입니다.
CPU를 보면 왜 놓치나
↔ 슬라이더"GPU 노드는 CPU 10%인데 가속기는 100% 포화"라는 관계 자체는 실제 관찰이지만, 여기 곡선은 그 관계를 보여주기 위한 모형입니다. CPU도 완전히 0은 아닙니다 — 토크나이즈·직렬화·HTTP 처리에 얼마간 씁니다.
대안 — 지표를 하드웨어에서 직접 읽기
표준 해법은 DCGM Exporter → Prometheus → PromQL → KEDA → HPA입니다. 동작하지만 구성요소가 다섯 개고, 스크레이프 지연이 15~30초 붙고, PromQL 쿼리가 업그레이드 때 깨집니다.
keda-gpu-scaler는 이 파이프라인을 걷어냅니다. GPU 노드마다 데몬셋으로 뜬 뒤 NVML C 바인딩에서 직접 GPU 상태를 읽어 gRPC로 KEDA에 넘깁니다. 구성요소 두 개, 지연은 1초 미만입니다.
keda-gpu-scaler의 구조와 설정
먼저 HPA가 동작하려면 metrics-server가 있어야 합니다.
metrics-server 설치
왜 KEDA 코어에 GPU 지원을 직접 넣지 않았는가 — 세 가지 이유가 있습니다.
- CGO 제약NVIDIA의 Go 바인딩(
go-nvml)은 CGO가 필요한데, KEDA 빌드는 CGO 없이 이뤄집니다. - 노드 수준 하드웨어 접근KEDA 오퍼레이터는 중앙 파드로 뜹니다. NVML은
libnvidia-ml.so를 통한 로컬 GPU 접근이 필요해서, GPU 노드의 데몬셋만 제공할 수 있습니다. - 독립 출시 주기KEDA 릴리스를 기다리지 않고 GPU 쪽 개선을 내보낼 수 있습니다.
30초 요약
파이프라인 비교
아키텍처
| 프로필 | 주 지표 | 목표 | 활성화 임계 | 용도 |
|---|---|---|---|---|
vllm-inference | Memory % | 80 | 5 | vLLM / LLM 서빙 (scale-to-zero 포함) |
triton-inference | GPU Util | 75 | 10 | NVIDIA Triton |
training | GPU Util | 90 | 0 | 학습 잡 (scale-to-zero 없음) |
batch | Memory % | 70 | 1 | 배치 추론 (공격적 축소) |
스케일러 배포
ScaledObject로 워크로드에 붙이기
프로필 대신 원시 지표를 직접 쓸 때
keda-gpu-scaler는 NVML을 읽으므로 Trainium에는 그대로 쓸 수 없습니다.
Neuron 쪽에서 같은 걸 하려면 neuron-monitor가 내보내는 지표를 Prometheus로 받아
KEDA Prometheus 스케일러를 쓰는, 위 그림의 "이전" 경로를 타야 합니다.
이 워크샵의 도전 과제가 그 방향입니다.
참고로 HAMi는 v2.7.0부터 AWS Neuron을 정식 지원하지만, 이번 실습은 vLLM이 TP=2 구성이라 GPU 분할이 의미가 없어 건너뜁니다.
- 정의 — Lab 6은 vCPU 쿼터 8 제약으로 실행이 막힙니다. Trainium 노드를 한 대밖에 못 띄우기 때문입니다. 대신 왜 CPU 기준 HPA가 부적절한가라는 질문이 남습니다.
- 성질 — GPU 노드는 CPU 10%인데 가속기는 100% 포화일 수 있습니다. CPU 임계값은 그 상황에서 절대 발동하지 않습니다. 고칠 것은 임계값이 아니라 보는 지표입니다.
- 쓰임 — 표준 경로는 DCGM → Prometheus → PromQL → KEDA(구성요소 5개, 지연 15~30초),
keda-gpu-scaler는 NVML 직독 → KEDA(2개, 1초 미만)입니다. 단 후자는 NVIDIA 전용이라 Neuron에는 표준 경로를 써야 합니다.
마지막입니다. 만든 순서의 역순으로, 하지만 그냥 역순은 아닙니다.
10치우기 — 지우는 순서도 설계다
한 줄 요약: 의존 관계를 거꾸로 타지 않으면 리소스가 삭제 중 상태로 걸립니다.
클라우드 실습에서 정리를 못 하면 비용이 계속 나갑니다.
trn1.2xlarge는 놀고 있어도 시간당 요금이 붙고, CLB도 두 개가 떠 있습니다.
그런데 그냥 지우면 안 됩니다. PVC가 남은 채로 PV를 지우면 매달려서 안 지워지고, 서비스가 남은 채로 노드그룹을 지우면 CLB가 고아가 됩니다. S3 버킷은 비어 있지 않으면 삭제 자체가 거부됩니다. 그리고 마지막에 CloudFormation의 종료 방지가 한 번 더 막습니다.
- finalizer
- 리소스가 지워지기 전에 반드시 끝나야 하는 뒷정리 작업 표시. 이게 안 끝나면
Terminating에서 멈춥니다. - Reclaim Policy
- PVC가 사라졌을 때 PV를 어떻게 할지.
Retain이면 남겨 두므로 직접 지워야 합니다. - orphan resource
- 쿠버네티스에서는 지워졌는데 클라우드 쪽에 남아 계속 과금되는 리소스. CLB가 대표적입니다.
- 종료 방지 (termination protection)
- CloudFormation 스택이 실수로 지워지지 않게 걸어 두는 잠금. 풀어야 삭제됩니다.
지우는 순서 — 거꾸로 하면 어디서 걸리나
클릭"나를 참조하는 것이 아직 있는가"를 먼저 묻습니다. 트래픽 입구(Ingress) → 애플리케이션 → 스토리지 클레임 → 스토리지 → 클러스터 부속 → 노드그룹 순서면 매달림이 생기지 않습니다.
실제 삭제 명령 — 순서대로
1. Ingress 먼저 — 트래픽 입구를 닫는다
2. vLLM 애플리케이션 — 서비스 → 디플로이먼트 → 설정 → PVC → PV
3. 모니터링 스택
4. S3 — 비우고 나서 지운다 (순서 바꾸면 거부당한다)
5~6. 클러스터 부속과 노드그룹
AWS CloudFormation 콘솔에서 해당 노드그룹 스택의 종료 방지(termination protection)를 먼저 해제한 뒤 삭제를 다시 실행합니다. 워크샵 환경에는 이 잠금이 걸려 있습니다.
삭제가 끝난 뒤 EC2 콘솔에서 Classic Load Balancer가 0개인지 한 번 더 확인하세요.
- 정의 — 정리는 의존 관계의 역순입니다. Ingress → 애플리케이션 → PVC → PV → 모니터링 → S3 → 클러스터 부속 → 노드그룹.
- 성질 — 순서를 어기면
Terminating에서 멈추거나 클라우드 쪽에 고아 리소스가 남습니다. S3 버킷은 비워야 지워지고, CloudFormation은 종료 방지를 풀어야 지워집니다. - 쓰임 — 실습이 끝나면 CLB 개수를 눈으로 확인하세요. 쿠버네티스 리소스는 지워졌는데 로드밸런서만 남아 과금되는 일이 흔합니다.
마치며
이번 실습에서는, AWS Trainium을 활용한 vLLM 기반 LLM 추론 환경을 구축하고, 다양한 컴포넌트의 설치와 설정, 그리고 클러스터 자원의 정리까지 진행했습니다. 이를 통해 AWS Trainium 환경에서의 LLM 추론 워크플로우를 이해하고, 서빙을 하며 모니터링 할 수 있는 기본 루틴을 익힐 수 있었습니다. 이번 워크샵을 통해 얻은 경험과 지식을 바탕으로, 실제 운영 환경에서도 AWS Trainium을 활용한 LLM 추론 시스템을 보다 효율적으로 구축하고 관리할 수 있을 것 같습니다.
제보 부탁드립니다
이 페이지는 [6W-1] Scaling LLM Inference with vLLM and AWS Trainium Workshop의 스터디 정리본입니다. 참고한 것은 워크샵 원문, AWS Neuron 문서, NeuronCore-v2 아키텍처, NxD Inference, vLLM Neuron 플러그인, llmperf, 그리고 keda-gpu-scaler입니다.
설명이 틀렸거나 실제 운영에서 다르게 동작하는 부분이 있다면 알려주세요. 특히 Trainium에서 오토스케일링을 실제로 붙여 본 사례가 있다면 그게 가장 궁금합니다 — 이 워크샵에서 유일하게 실행하지 못한 부분입니다.
'AI > [LLMSO] Hands-On LLM 서빙과 최적화 스터디' 카테고리의 다른 글
| [7주차-LLMSO] llm-d 란 (2026.09.13) (0) | 2026.09.20 |
|---|---|
| [7주차-LLMSO] Envoy: Agent Router (2026.09.13) (0) | 2026.09.20 |
| [5주차-LLMSO]CH10. Advancements in LLM Serving (2026.08.30) (1) | 2026.09.05 |
| [5주차-LLMSO]CH9. LLM Optimization in Practice (2026.08.30) (0) | 2026.09.05 |
| [4주차-LLMSO]CH8. LLM Serving Frameworks (2026.08.23) (0) | 2026.08.30 |