콘텐츠로 이동

실측 결과 요약

AWS 문서만으로 확인하기 어려웠던 동작을 SageMaker endpoint와 로컬 Docker 환경에서 측정한 결과입니다.

TL;DR

아홉 가지 결과를 요약합니다. 각 항목은 아래의 같은 번호 절에서 자세히 설명합니다.

발견 핵심 수치
1 CPU에서도 ONNX Runtime으로 latency를 줄일 수 있음 PyTorch 1,810 ms → 58 ms, 판정 결과 동일
2 HF DLC 배칭에는 batch_size 지정이 필요함 510 ms → 25 ms(b=32)
3 GPU co-host 밀도는 방식에 따라 다름 LMI는 GPU 1장에 3개, IC는 모델당 1장
4 멀티컨테이너 Direct는 GPU 인스턴스를 지원하지 않음 API에서 거부
5 vLLM의 인코더 batch throughput 이점이 제한적임 b=32에서 HF DLC의 0.77배
6 워커 수가 동시 요청 throughput에 영향을 줌 60 → 183 req/s(워커 4개)
7 GPU 인스턴스도 0까지 축소 가능 콜드 스타트 471초, 축소까지 27분
8 모델별 속도와 정확도 차이가 있음 MiniLM이 4.5배 빠르지만 샘플 정확도는 낮음
9 실행 엔진 변경으로 latency를 줄일 수 있음 TensorRT FP16으로 p50 26.8 → 15.4 ms

핵심 결론은 세 가지입니다. 경량 인코더에서는 CPU도 유효한 선택지가 될 수 있고, co-host가 항상 비용을 낮추는 것은 아니며, 유휴 비용 절감에는 콜드 스타트가 수반됩니다.

SageMaker 측정 리전은 us-east-1이며, 로컬 측정에는 NVIDIA L40S 46 GB와 Docker를 사용했습니다. 오류와 해결 방법은 트러블슈팅에 정리되어 있습니다.

측정값의 범위

latency는 p50과 p99를 함께 표시합니다. p50은 중앙값이고 p99는 상위 1% 요청의 latency를 보여 줍니다. co-host 비교는 입력 길이를 512로 고정했습니다. 측정값은 인스턴스 유형, 리전, 모델, 입력 데이터에 따라 달라질 수 있으므로 절대값보다 구성 간 상대적인 차이를 기준으로 해석해야 합니다.

측정 데이터(results/*.json)에는 p50, p90, p95, p99가 모두 포함되어 있습니다.

문서와 실측 결과의 차이

이 저장소는 경량 인코더의 co-host과 CPU 배포처럼 여러 기능을 조합한 구성을 다룹니다. 일부 제약은 API 응답이나 컨테이너 로그에서만 확인할 수 있었습니다. 각 항목에는 결과를 다시 확인할 수 있는 재현 명령을 제공합니다.


1. CPU + ONNX 런타임

같은 인스턴스(ml.c6i.2xlarge), 같은 모델인데 런타임만 바꾸면 이만큼 달라집니다.

런타임 p50 p90 p95 p99 정확도
PyTorch 1810 ms - - - 4/4
ONNX Runtime fp32 58.4 ms 70.2 ms 71.2 ms 78.1 ms 4/4

ONNX Runtime의 p50은 PyTorch보다 약 31배 낮았으며, 검증한 샘플에서는 ONNX와 PyTorch 출력이 소수점 여섯째 자리까지 일치했습니다(maxdiff=0.000000).

같은 CPU 인스턴스에서 PyTorch의 p50은 약 1.8초였고 ONNX Runtime은 58 ms였습니다. GPU 구성의 28 ms보다 약 2배 높지만, 경량 인코더에서는 인스턴스 비용과 latency 요구를 함께 비교할 수 있습니다.

수십 밀리초 수준의 응답 시간이 허용되는 워크로드에서는 CPU + ONNX를 비용과 함께 비교할 수 있습니다.

이 변환 경로에서는 FP32만 검증됐습니다

정밀도 결과
FP32 검증 성공
INT8 예측이 모두 neutral로 붕괴 (정확도 1/4)
fp16 변환 후 로드 실패 (Cast(to=FLOAT) 노드 128개와 타입 충돌)
bf16 ORT CPU EP가 네이티브 실행하지 않음

이 결과는 해당 DeBERTa 모델과 변환 경로에서 측정한 값이며, 다른 양자화 방식 전체에 일반화할 수 없습니다.

변환과 서빙을 분리합니다

optimum-onnxtransformers<4.58을 요구해 이 저장소의 Transformers 5.14.1 환경과 함께 설치할 수 없었습니다. 모델 변환과 달리 서빙에는 Optimum이 필요하지 않습니다.

단계 필요한 패키지 transformers 5.x
변환 (1회성) optimum[onnxruntime] 별도 venv
서빙 onnxruntime 제약 없음

scripts/export_onnx.py는 별도의 가상 환경을 만들고, 변환 후 PyTorch와 ONNX 출력을 비교합니다.


2. HF DLC의 배칭 조건

inputs에 리스트를 넣기만 하면 배칭되지 않습니다. transformers pipeline이 batch_size 미지정 시 한 건씩 순차 처리합니다.

batch 32, mDeBERTa, L40S latency
parameters 없음 510 ms
parameters.batch_size=32 25 ms
직접 forward (참고) 13 ms

batch 1 대비 throughput 증가율은 다음과 같습니다.

batch 수정 전 수정 후
8 1.11x 7.40x
32 1.14x 23.21x

HF DLC 핸들러는 parameters를 Pipeline에 전달하므로 요청 payload에 batch_size를 포함해야 합니다.

{"inputs": [{"text": "...", "text_pair": "..."}, ...],
 "parameters": {"batch_size": 32}}

batch_size 없이 측정하면 배칭 효과가 없는 것으로 나옵니다.


3. GPU co-host 방식별 밀도

여러 모델을 하나의 endpoint에 배포하고 각각 호출하는 방식을 co-host가라고 합니다. Inference Component와 LMI는 GPU 자원을 할당하는 방식이 다릅니다.

  • Inference Component: SageMaker가 컴포넌트 사본마다 GPU를 할당합니다.
  • LMI(DJL Serving 컨테이너): 하나의 컨테이너가 여러 모델을 로드하고 GPU를 공유합니다.

Inference Component는 사본마다 GPU를 최소 한 장 할당합니다

컴포넌트를 생성할 때 NumberOfAcceleratorDevicesRequired로 필요한 GPU 수를 지정합니다. GPU 인스턴스에서는 필수이며 최솟값은 1입니다. CPU 코어는 NumberOfCpuCoresRequired로 0.25코어 단위까지 지정할 수 있습니다.

따라서 이 구성에서는 GPU 수가 배치할 수 있는 컴포넌트 사본 수의 상한이 됩니다. 모델 3개의 파라미터 메모리를 합쳐도 약 1.9 GB이지만, GPU 1장 인스턴스에는 컴포넌트 사본 하나만 배치할 수 있었습니다.

ml.g6.12xlarge(GPU 4장)로 확인한 것:

시도 결과
모델 3개 배포 성공(GPU 3장 사용)
모델 하나의 CopyCount를 2로 설정 성공(GPU 4장 사용)
사본을 하나 더 추가(총 5개) 실패: deployed 1 out of 2 requested copies

다섯 번째 사본이 배치되지 않은 결과로 GPU 수와 사본 수의 관계를 확인했습니다.

LMI는 GPU 한 장을 여러 모델이 공유합니다

컨테이너 하나가 여러 모델을 로드하고 GPU 메모리를 공유합니다. 로컬 L40S에서 모델 3개(총 945M 파라미터)는 GPU 메모리 5.7 GB를 사용했고, SageMaker ml.g5.xlarge에서도 세 모델이 모두 응답했습니다.

Inference Component LMI
모델 3개를 올리려면 GPU 4장 인스턴스 (ml.g6.12xlarge) GPU 1장 (ml.g5.xlarge)
GPU 메모리 사용 사본당 GPU 1장 할당 3개 합계 5.7 GB
호출할 때 대상 지정 InferenceComponentName TargetModel
모델 추가 컴포넌트 생성 S3에 업로드만

co-host 비용은 배치 방식에 따라 달라집니다

구성 인스턴스 시간당 요금
CPU 멀티컨테이너 (ONNX) ml.c6i.2xlarge × 1 $0.408
LMI co-host ml.g5.xlarge × 1 $1.408
모델별 endpoint 3개 ml.g5.xlarge × 3 $4.224
Inference Component co-host ml.g6.12xlarge × 1 $5.752

2026년 8월 7일에 AWS Pricing API로 조회한 us-east-1 온디맨드 호스팅 요금입니다.

이 측정에서 Inference Component 구성은 GPU 1장 인스턴스 3대보다 비용이 높았습니다. 사본마다 GPU를 최소 한 장 할당했기 때문입니다.

Inference Component는 다음 기능이 필요할 때 검토할 수 있습니다.

  • 모델이 커서 GPU 1장을 실제로 다 쓸 때
  • 모델마다 사본 수를 따로 조절해야 할 때 (CopyCount)
  • 부하가 적은 사본으로 요청을 보내야 할 때 (LEAST_OUTSTANDING_REQUESTS)
  • 모델마다 다른 컨테이너 이미지를 써야 할 때
  • 낮은 단건 latency가 필요할 때(실측 p50 25.8 ms, LMI 49.7 ms)

전체 벤치마크는 벤치마크 결과에 있습니다.


4. 멀티컨테이너의 GPU 제약

ValidationException: Instance type ml.g5.xlarge is not supported
                     for a Model using Direct InferenceExecutionMode.

확인한 AWS 문서에서는 이 제약을 찾지 못했습니다. ml.g5.xlarge, ml.g5.2xlarge, ml.g4dn.xlarge는 모두 거부됐고 ml.c6i.2xlarge는 통과했습니다.

거부 조건은 멀티컨테이너 자체가 아니라 Direct 실행 모드였습니다. 같은 GPU 이미지로 Serial 모드를 구성하면 ml.g5.xlarge가 API 검증을 통과했습니다.

모드 GPU 컨테이너 개별 호출 co-host 용도
Direct 미지원 TargetContainerHostname으로 개별 호출 CPU co-host
Serial 지원 컨테이너 체인을 순차 통과 추론 파이프라인

create_model은 GPU 이미지로도 통과했으며 create_endpoint_config에서 거부됐습니다. endpoint를 생성하지 않으므로 인스턴스 요금 없이 확인할 수 있습니다.

uv run python 02_cpu_cohost_multicontainer/deploy.py --prove-gpu-fails

5. vLLM과 인코더

DeBERTaV2 아키텍처는 지원되지 않았습니다

Model architectures ['DebertaV2ForSequenceClassification'] are not supported for now.

확인한 세 경로에서는 모두 지원되지 않았습니다.

  1. vLLM 레지스트리에 없음: 확인 당시 등록된 *ForSequenceClassification 목록에 DeBERTaV2가 포함되지 않았습니다. 현재 목록은 vLLM 지원 모델 문서에서 확인할 수 있습니다.
  2. Transformers backend 폴백 불가: _supports_attention_backend = TrueALL_ATTENTION_FUNCTIONS 사용이 modeling_deberta_v2.py에 둘 다 없습니다. disentangled attention을 torch.bmm으로 직접 계산하는 구조라 표준 attention 인터페이스에 맞지 않습니다.
  3. TEI 지원 목록에 없음: TEI 지원 모델 문서의 Sequence Classification 목록에 DeBERTa가 포함되지 않았습니다.

배포 전 확인: uv run python 08_engine_comparison/check_vllm_support.py

지원 모델의 배치 throughput 비교

같은 MiniLM 모델에서 실행 엔진만 변경한 결과입니다. 네 개의 검증 샘플 가운데 세 개를 두 구성에서 동일하게 판정했습니다.

batch HF DLC(samples/s) vLLM(samples/s) HF DLC 대비
1 140.9 213.5 1.52x
8 926.5 906.4 0.98x
32 2374.6 1835.0 0.77x

이유는 구조적입니다.

디코더 LLM 인코더
요청 1건 prompt + N decode step forward pass 1회
KV cache 필수 없음
출력 가변 길이 토큰 고정 크기 logits

vLLM의 Paged KV cache와 자동 회귀 디코딩용 Continuous Batching은 인코더에 직접 적용되지 않습니다. batch 1에서 vLLM이 앞선 원인은 더 단순한 HTTP 처리 경로로 추정됩니다.

LMI는 다릅니다. vLLM은 지원하지 않는 아키텍처를 거부하고 종료하지만, LMI는 인코더를 인식해 rolling_batch: disable을 자동 선택하고 HuggingFace Accelerate로 폴백합니다.


6. 워커 수와 concurrency

SAGEMAKER_MODEL_SERVER_WORKERS로 컨테이너 내부의 워커 프로세스 수를 설정합니다.

mDeBERTa, L40S, batch 1:

concurrency 워커 1개 워커 4개
1 57 req/s 53 req/s
4 60 req/s 168 req/s
8 60 req/s 183 req/s
16 - 183 req/s

워커 1개 구성은 concurrency을 높여도 약 60 req/s에서 throughput이 증가하지 않았고 latency만 늘어났습니다. 워커 4개 구성은 concurrency 4까지 throughput이 증가한 뒤 약 183 req/s에서 포화됐습니다.

ONNX Runtime의 워커 수

ONNX 세션은 워커마다 모델을 별도로 로드하므로 메모리 사용량이 증가합니다. 이 저장소의 ONNX 구성에서는 워커 수를 1개로 제한했습니다. 자세한 내용은 트러블슈팅을 참고하세요.


7. GPU Scale to Zero

Serverless Inference와 ManagedInstanceScaling의 차이는 다음과 같습니다.

GPU 0까지 축소 상한
Serverless Inference 미지원 지원 메모리 6,144 MB
ManagedInstanceScaling 지원 MinInstanceCount: 0 인스턴스 한도

ServerlessConfigMemorySizeInMB, MaxConcurrency, ProvisionedConcurrency를 제공하지만 GPU 설정은 제공하지 않습니다.

ManagedInstanceScaling.MinInstanceCount에는 0을 지정할 수 있으며, GPU 인스턴스 구성도 create_endpoint_config 검증을 통과했습니다.

MinInstanceCount=0만으로는 축소되지 않습니다

API 검증을 통과해도 이 설정만으로 컴포넌트 사본 수가 0으로 줄어들지는 않습니다. Application Auto Scaling이 Inference Component의 사본 수를 먼저 조절해야 합니다.

설정 없으면
ManagedInstanceScaling MinInstanceCount: 0 인스턴스가 최소 1대 유지
register_scalable_target MinCapacity: 0 축소가 시작되지 않음
StepScaling + NoCapacityInvocationFailures 경보 0에서 자동 복구되지 않음

Step Scaling 경보가 없으면 사본 수가 0인 endpoint가 자동으로 복구되지 않습니다. 실행 대상이 없는 상태에서 들어온 요청은 NoCapacityInvocationFailures 지표를 발생시키며, 이 경보가 사본 수 확장을 시작합니다.

이 구성에서 Target Tracking이 생성한 축소 경보는 Period=10, EvaluationPeriods=90이었습니다. 지표가 15분 동안 임계값보다 낮아야 축소가 시작됩니다.

측정 결과(ml.g5.xlarge, mDeBERTa)

구간 실측
정상 상태 latency(p50) 30.5 ms
트래픽 중단 → 인스턴스 0 1,595초(약 27분), 이 구간은 계속 과금
콜드 스타트 471초(약 8분), 95회 재시도 후 성공
정상 상태 대비 콜드 스타트 15,422배

축소는 두 단계로 진행됐습니다. Inference Component 사본 수가 먼저 0이 되고 약 13분 뒤 인스턴스가 회수됐습니다. 사본 수가 0이어도 인스턴스가 회수되기 전까지는 요금이 발생합니다.

복구 중 요청은 대기하지 않고 즉시 실패합니다.

ValidationError: Inference Component has no capacity to process this request

이 오류가 NoCapacityInvocationFailures 지표를 발생시켜 복구를 시작하므로 클라이언트에는 재시도 로직이 필요합니다.

이 측정 조건에서는 유휴 구간이 30분보다 짧게 반복되면 Scale to Zero의 비용 절감 효과가 제한적입니다. 자세한 내용과 코드는 05 Scale to Zero에 있습니다.


8. 모델 선택의 절충

같은 XNLI 3-way 태스크와 샘플을 L40S에서 batch 1로 측정했습니다.

모델 파라미터 p50 NLI 정확도 vLLM
mDeBERTa 278M 15.1 ms 4/4 미지원
MiniLM 107M 3.3 ms 3/4 지원
XLM-R Large 560M 10.9 ms 3/4 지원

MiniLM은 mDeBERTa보다 4.5배 빨랐지만 검증 샘플의 정확도는 낮았습니다. 모델 선택 시 latency과 대상 데이터셋에서의 정확도를 함께 측정해야 합니다.

정확도 결과의 범위

샘플 4건은 모델 정확도를 평가하기에 충분하지 않습니다. 여기서는 배포 방식이 기존 판정 결과를 바꾸지 않는지 확인하는 용도로만 사용했습니다. 실제 도입 전에는 별도의 평가 데이터셋으로 측정해야 합니다.

id2label 순서는 모델마다 다릅니다. XLM-R Large는 contradiction이 0이고 나머지 두 모델은 entailment가 0입니다. 레이블 인덱스를 하드코딩하지 말고 model.config.id2label을 사용해야 합니다.


9. 실행 엔진 변경 효과

01~06은 컨테이너와 배포 방식을 비교하고, 07은 하드웨어와 모델을 고정한 상태에서 실행 엔진을 비교합니다. 측정 조건은 ml.g5.xlarge, mDeBERTa ONNX, max_len=512, batch 1, concurrency 1입니다.

구성 p50 p90 p95 p99 b=8 samples/s 정확도
01 HF DLC + PyTorch 26.8 ms 29.8 ms 32.7 ms 45.4 ms 113/s 4/4
Triton + ORT GPU 27.3 ms 28.4 ms 28.5 ms 28.6 ms 57/s 4/4
Triton + TensorRT FP16 15.4 ms 16.4 ms 16.8 ms 17.4 ms 185/s 4/4
Triton Ensemble + TensorRT 16.6 ms 17.1 ms 17.4 ms 17.9 ms 179/s 4/4

컨테이너와 실행 엔진의 영향을 구분하기 위해 기준 구성을 별도로 측정했습니다.

바뀐 것 p50 p99
컨테이너만 (HF DLC → Triton, 엔진 동일) 26.8 → 27.3 ms (변화 없음) 45.4 → 28.6 ms
엔진 (ORT GPU → TensorRT FP16) 27.3 → 15.4 ms (1.8배) 28.6 → 17.4 ms

HF DLC에서 Triton + ONNX Runtime GPU로 변경하면 p50은 거의 같았고 p99는 45.4 ms에서 28.6 ms로 낮아졌습니다. TensorRT FP16을 적용한 뒤 p50은 27.3 ms에서 15.4 ms로 낮아졌습니다.

02의 양자화 결과는 해당 변환 경로에 한정됩니다

02의 ORT CPU 경로에서 INT8 모델은 모든 샘플을 neutral로 판정했고 FP16 모델은 로드에 실패했습니다. 같은 모델의 TensorRT GPU FP16 경로는 검증 샘플 4건을 기존 모델과 동일하게 판정했습니다.

02 (ORT CPU) 07 (TensorRT GPU)
FP16 Cast(to=FLOAT) 노드와 충돌해 로드 실패 검증 샘플 4/4 일치

따라서 이 결과는 DeBERTa 전체의 양자화 특성이 아니라 이 모델을 ORT CPU 동적 양자화로 변환한 경로의 결과로 한정해야 합니다.

적용 시 고려 사항

새로운 배치 형태의 첫 요청에서 컴파일 시간이 발생합니다. 첫 단건 요청은 47.7초였고, batch 8의 첫 요청은 60초 제한을 초과했습니다. 컴파일 캐시는 /tmp에 저장되어 컨테이너 재시작 시 사라지므로 05 Scale to Zero와 함께 사용할 때 콜드 스타트 시간을 확인해야 합니다.

Triton 이미지와 추론 AMI의 CUDA 호환성을 확인해야 합니다. 기본 추론 AMI에서 error 803이 발생했으며, ProductionVariant.InferenceAmiVersional2023-ami-sagemaker-inference-gpu-4-1을 지정한 뒤 배포에 성공했습니다.

payload 형식이 달라집니다. Triton은 텐서를 입력받고 반환하므로 기본 구성에서는 tokenization와 softmax를 클라이언트에서 처리합니다. Python 백엔드의 전처리와 후처리를 Ensemble로 묶은 구성에서는 batch 1의 latency가 1.2 ms(8%) 증가했습니다.

개념과 백엔드별 차이는 Triton Inference Server, 배포 절차는 07 Triton + TensorRT를 참고하세요.

재현

과금 없이 확인할 수 있는 것:

uv run python -m common.local_test --all --bench              # 로컬 GPU 추론과 벤치마크
uv run python 08_engine_comparison/check_vllm_support.py      # vLLM 지원 판정
uv run python 02_cpu_cohost_multicontainer/deploy.py --prove-gpu-fails
uv run python 01_single_endpoint/deploy.py --dry-run          # 무료 API까지만
bash 01_single_endpoint/scripts/serve_local.sh                # HF DLC 로컬
bash 04_gpu_cohost_lmi/scripts/serve_local_lmi.sh             # LMI 로컬 (GPU 1장에 3개)
bash 08_engine_comparison/scripts/serve_local_vllm.sh         # vLLM 로컬
uv run python 07_gpu_triton/scripts/build_triton_repo.py       # Triton repo 만들기 (S3 업로드 없음)

과금이 발생하는 것:

uv run python 01_single_endpoint/deploy.py --gpu --workers 4
uv run python 04_gpu_cohost_lmi/deploy.py
uv run python -m common.cleanup --delete-all

측정 시 주의 사항

DLC 이미지 태그, 서비스 제약, 리전 지원, vLLM 지원 아키텍처는 변경될 수 있습니다. 배포 전에 공식 문서를 다시 확인하고 check_vllm_support.py로 현재 레지스트리를 조회하세요.