동기 엔드포인트의 반대편에 있는 두 가지, 배치 변환과 서버리스 추론을 깊이 판다. 먼저 배치 변환은 S3의 대량 입력을 읽어 일괄 추론하고 결과를 S3에 쓴다. 처리량을 높이는 레버는 세 가지다.
InstanceCount: 인스턴스를 늘리면 데이터가 분산되어 병렬 처리. 가장 직접적인 수평 확장.
데이터 분할(SplitType): Line(레코드 줄 단위), RecordIO, TFRecord 등으로 큰 파일을 레코드 단위로 쪼개 여러 인스턴스/배치에 분배.
미니배치(BatchStrategy, MaxPayloadInMB, MaxConcurrentTransforms): 한 번에 모델에 보내는 레코드 묶음 크기와 동시 요청 수를 조절.
배치 변환의 병렬 분산 구조 S3 입력 (여러 파일 또는 분할 가능한 포맷) part-000.csv part-001.csv part-002.csv part-003.csv │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ 인스턴스1│ │ 인스턴스2│ │ 인스턴스3│ │ 인스턴스4│ ← instance_count=4 └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │ │ └─────────────┴──────┬──────┴─────────────┘ ▼ S3 출력 (.out) │ ▼ 작업 종료 → 클러스터 전부 삭제(비용 0) ⚠ 입력이 분할 불가능한 단일 거대 파일이면 인스턴스1만 일하고 나머지는 논다.
from sagemaker.transformer import Transformertransformer = Transformer( model_name="churn-model", instance_count=8, # 8대로 병렬 처리 instance_type="ml.m5.xlarge", strategy="MultiRecord", # 여러 레코드를 한 미니배치로 max_payload=6, # 미니배치 최대 6MB max_concurrent_transforms=4, # 인스턴스당 동시 요청 4 output_path="s3://bkt/out/",)transformer.transform( data="s3://bkt/in/", content_type="text/csv", split_type="Line", # 줄 단위 분할)
파라미터별로 무엇을 조절하는지 정리하면 다음과 같다.
파라미터
조절 대상
올리면
주의
instance_count
병렬도(수평 확장)
처리 시간 단축
비용도 비례 증가. 분할 가능해야 효과
split_type
입력을 어떻게 쪼갤지
Line 등으로 분산 가능해짐
None이면 파일 단위로만 분배된다
strategy
한 요청에 담는 레코드 수
MultiRecord로 오버헤드 감소
모델이 단건만 받으면 SingleRecord
max_payload
미니배치 최대 크기(MB)
요청당 처리량 증가
너무 크면 모델 메모리 초과
max_concurrent_transforms
인스턴스당 동시 요청 수
인스턴스 활용률 상승
과하면 컨테이너가 밀린다
strategy="MultiRecord"는 여러 레코드를 한 요청으로 묶어 처리량을 높이고, SingleRecord는 한 번에 한 레코드만 보낸다(모델이 단건 입력만 받을 때).
split_type은 None(파일 통째) · Line(줄 단위) · RecordIO · TFRecord 중에서 입력 포맷에 맞춰 고른다.
배치 변환의 처리량은 결국 "병렬도 × 인스턴스당 효율"이다. 인스턴스 수를 늘리면 선형에 가깝게 빨라지지만 비용도 비례해 늘고, 데이터가 여러 파일로 나뉘어 있거나 SplitType으로 쪼갤 수 있어야 분산이 효과를 본다. 입력이 거대한 단일 파일이고 분할 불가하면 한 인스턴스가 다 떠안아 확장이 안 된다. 그래서 배치 입력은 여러 파일로 샤딩하거나 줄 단위 분할이 가능한 포맷으로 두는 것이 튜닝의 시작이다.
⚠️ 함정: 배치 변환은 입력 레코드와 출력 예측을 다시 연결(join)하는 것이 까다로울 수 있다. 입력 ID와 예측을 함께 출력하려면 input_filter/output_filter/join_source 파라미터(JSONPath)로 입력 일부를 출력에 합쳐야 한다. "예측 결과를 원본 레코드와 어떻게 매칭하느냐"는 실무·헷갈리기 쉬운 함정이다.
같은 모델로 100만 건을 처리한다고 가정한다. 실시간 엔드포인트로 100만 번 호출하면 always-on 인스턴스 비용 + 호출 오버헤드가 들고, 트래픽 스파이크 관리도 해야 한다. 배치 변환은 클러스터를 잠깐 띄워 100만 건을 분산 처리하고 끝나면 종료해 비용이 처리 시간에만 발생한다.
실시간 엔드포인트는 "요청당 즉시 응답"에 최적화되어 개별 요청 지연이 낮지만, 대량 일괄에서는 인스턴스를 계속 켜둔 채 순차/동시 호출을 받아야 해 비효율적이다. 배치는 "데이터 지역성 + 일괄 분산"으로 대량 처리량을 극대화한다. 정리하면 즉시성이 필요 없는 대량 처리 = 배치가 거의 항상 저렴하고 빠르다. 단 결과를 실시간으로 못 받으므로 즉시성이 조금이라도 필요하면 배치는 탈락이다.
MemorySizeInMB(1024~6144): 메모리를 키우면 할당되는 컴퓨팅도 함께 커진다.
MaxConcurrency: 동시에 처리할 최대 요청 수. 이를 넘는 요청은 스로틀링된다.
인스턴스 타입을 고르지 않는다 → GPU를 쓸 수 없다는 뜻이기도 하다.
트래픽이 오면 자동으로 컴퓨팅을 띄우고, 없으면 0으로 줄인다.
from sagemaker.serverless import ServerlessInferenceConfigserverless_config = ServerlessInferenceConfig( memory_size_in_mb=2048, # 1024 ~ 6144 max_concurrency=20, # 동시 처리 최대 요청 수)predictor = model.deploy(serverless_inference_config=serverless_config)
문제는 콜드 스타트다. 한동안 호출이 없다가 첫 요청이 오면 컨테이너를 띄우고 모델을 로드하는 동안 지연이 발생한다.
콜드 스타트 vs 워밍된 상태[콜드] 요청 ─▶ │컨테이너 기동│모델 다운로드·로드│추론│ ─▶ 응답 └─────────── 수 초 ────────────┘ ↑ 여기만 실제 추론[워밍] 요청 ─▶ │추론│ ─▶ 응답 (밀리초~수백 ms)Provisioned Concurrency = 지정한 수만큼을 "워밍" 상태로 붙잡아 두는 것 → 콜드 스타트 제거, 대신 그만큼 상시 비용 발생
일관된 저지연이 필요하면 Provisioned Concurrency(프로비저닝된 동시성)를 설정해 일정 수의 인스턴스를 미리 데워둘 수 있다. 단 이 경우 그만큼 상시 비용이 발생해 "완전 0 비용" 이점은 일부 사라진다.
서버리스의 비용 모델은 "처리한 요청의 컴퓨팅 시간 + (선택) Provisioned Concurrency 상시분"이다. 트래픽이 드물수록 0 스케일 덕에 실시간 대비 절감 폭이 크고, 트래픽이 꾸준히 많을수록 절감 이점이 사라지거나 역전된다. Provisioned Concurrency는 콜드 스타트를 없애지만 상시 비용을 부르므로, "간헐적 트래픽이지만 첫 응답 지연은 못 견딘다"는 절충 상황에서만 의미가 있다.
직관적으로 잡으면, 엔드포인트가 대부분 유휴라면 서버리스가 싸고, 거의 항상 바쁘면 실시간이 싸다. 둘 사이 어딘가에 손익분기가 있다.
판단 축
서버리스가 유리
실시간이 유리
트래픽 패턴
간헐적, 유휴 구간이 김
꾸준하거나 항상 높음
지연 SLA
첫 응답 지연을 감수 가능
일관된 저지연이 필수
하드웨어
CPU로 충분
GPU 필요(서버리스는 불가)
페이로드
4MB 이하
6MB 이하
운영 부담
인스턴스 관리 없음
스케일링·용량 계획 필요
비용 구조
처리 시간만 과금
가동 시간 전체 과금
⚠️ 함정: "비용 절감 = 서버리스"라는 단순 매핑은 오답을 부른다. 서버리스가 유리한 조건은 ① 트래픽이 간헐적이고 ② 콜드 스타트를 감수할 수 있을 때다. 고정적으로 높은 트래픽에 서버리스를 쓰면 컴퓨팅 시간 과금이 누적되어 실시간보다 비싸질 수 있고, 콜드 스타트로 SLA를 못 맞춘다. 시험에서 트래픽 패턴(간헐 vs 꾸준)과 지연 SLA(콜드 스타트 허용 여부)를 먼저 확인해야 한다.