dev.to

Serving Gemma 4 on an AMD MI300X: What $1.99 an Hour Buys

AMD MI300X에서 Gemma 4 서빙하기 — 시간당 1.99달러로 얻는 성능

AMD Instinct MI300X 한 장에 Gemma 4 E2B를 vLLM과 ROCm으로 배포하고, MCP 기반 운영 도구로 상태 확인과 벤치마크를 자동화한 과정을 설명합니다. 1,024 컨텍스트 단일 스트림에서 305 tok/s, 64개 스트림에서 10,284 tok/s를 기록했으며, 온디맨드 비용 기준으로 TPU v5e보다 높은 처리량 대비 비용을 측정했습니다.

AI 요약

이 글은 AMD Instinct MI300X 한 장에서 Google의 Gemma 4 E2B 모델을 배포하고, 시간당 1.99달러의 GPU 인스턴스가 실제로 어느 정도의 추론 처리량을 제공하는지 측정하는 단계별 가이드입니다. GPU는 AMD Developer Cloud를 통해 제공되지만 실제 인프라는 DigitalOcean GPU droplet이며, 배포를 제어하는 워크스테이션에는 AMD GPU가 전혀 없습니다. 따라서 로컬에서 ROCm 명령을 실행하는 대신, 모든 하드웨어 확인은 SSH 또는 DigitalOcean API를 통해 원격으로 수행합니다.

■ 배포 환경과 비용 구조

대상 모델은 `google/gemma-4-E2B-it`의 BF16 기준 릴리스이며, 하드웨어는 `gfx942`, CDNA 3 기반의 AMD Instinct MI300X입니다. 인스턴스에는 20 vCPU, 240GB RAM, 720GB 디스크가 제공되고 GPU에는 191.7GiB의 HBM3 메모리와 304개의 컴퓨트 유닛이 있습니다. 사용한 소프트웨어 이미지는 `vllm/vllm-openai-rocm:nightly-rocm100`이며, 실행 중인 컨테이너에서 확인한 실제 vLLM 버전은 `0.3.1.dev3+g0bfc7a15`입니다.

GPU droplet 생성과 삭제는 AMD Developer Cloud 콘솔에서 직접 수행합니다. DigitalOcean v2 API의 droplet ID와 토큰을 사용하지만, 시간당 과금이 발생하는 인스턴스를 에이전트가 임의로 만들거나 삭제하지 않도록 이 두 작업은 MCP 도구에서 제외했습니다. 특히 인스턴스를 중지해도 과금은 멈추지 않습니다. 리소스가 예약된 상태로 남아 시간당 1.99달러가 계속 청구되며, 실제로 요금을 멈추려면 droplet을 삭제해야 합니다.

■ GPU가 없는 워크스테이션에서 원격 운영

저자는 워크스테이션에 GPU가 없다는 제약을 전체 도구 설계의 기준으로 삼았습니다. `rocm-smi`, `amd-smi`, `rocminfo`, `hipcc`는 로컬에 설치하지 않으며, 로컬 셸에서 ROCm 명령을 실행하는 것은 정상적인 검사가 아니라 오류로 간주합니다. 워크스테이션에서 실행되는 MCP 서버는 표준 입출력(stdio)으로 21개 도구를 제공하고, SSH와 DigitalOcean API를 통해 원격 GPU를 조작합니다.

모든 서브프로세스는 `asyncio.create_subprocess_exec`를 사용하는 단일 `run_command(cmd: list[str])` 경로를 거치며 셸을 직접 호출하지 않습니다. 도구는 droplet 수명주기, 이미지 확인, 배포, 로그 조회, 벤치마크, SRE 상태 점검으로 구성됩니다. 각 droplet에는 태그를 붙이고 모든 도구가 해당 태그에 한정되도록 만들어, 태그가 없는 다른 인스턴스를 실수로 건드리지 않게 했습니다. 실행을 위해서는 SSH 키로 접근 가능한 GPU droplet, `0600` 권한의 `.env` 파일에 저장한 `DIGITALOCEAN_ACCESS_TOKEN`, GPU 장치 파일인 `/dev/kfd`와 `/dev/dri`가 연결된 Docker, Gemma 4 가중치 접근용 Hugging Face 토큰이 필요합니다.

MI300X는 가상화된 SR-IOV Virtual Function으로 `Aqua Vanjaram [Instinct MI300X VF]`라는 이름으로 표시됩니다. 하지만 이는 일부 GPU만 제공하는 파티션이 아니라 전체 304개 컴퓨트 유닛과 191.7GiB 메모리를 사용할 수 있는 구성입니다. `rocm-smi`와 `amd-smi`가 유용한 종료 코드를 반환하지 않기 때문에 GPU 상태 도구는 프로세스 종료 코드가 아니라 출력 내용을 직접 파싱합니다.

■ vLLM 이미지 확인과 Gemma 4 배포

Gemma 4의 sliding-attention 계층은 256-wide head를 사용하고 full-attention 계층은 512-wide head를 사용합니다. 이 구조를 지원하기 전의 vLLM 이미지는 모델 설정을 파싱하지 못할 수 있으므로, 이미지 태그만 믿지 않고 실행 중인 컨테이너에서 실제 버전을 확인해야 합니다. 특히 `nightly` 태그는 시간이 지나면서 다른 빌드를 가리킬 수 있어, 측정에 사용한 버전을 별도로 확인하고 새 이미지도 도입 전에 검증해야 합니다.

배포 명령은 최대 컨텍스트 길이를 32,768 토큰, GPU 메모리 사용률을 0.90으로 설정합니다. `--enable-auto-tool-choice`, `--reasoning-parser gemma4`, `--tool-call-parser gemma4`, Gemma 4용 chat template, 비동기 스케줄링도 활성화합니다. 멀티모달 입력은 이미지 최대 4개로 설정하고 오디오는 0으로 제한합니다. E2B 모델에는 conformer 오디오 인코더가 있지만 ROCm용 vLLM 이미지에는 `vllm[audio]` 추가 의존성이 포함되어 있지 않기 때문에, 오디오 제한을 0보다 크게 설정하면 사용할 수 없는 인코더 메모리만 할당하게 됩니다.

`docker run`은 약 1초 만에 반환되지만, HTTP 엔드포인트가 실제로 응답하기까지는 가중치 로딩, `torch.compile`, 그래프 캡처 때문에 약 2분 40초가 더 필요합니다. 이 배포에서는 `docker run` 반환 시점부터 `/v1/models`를 10초마다 폴링했으며, 160초 후 모델 엔드포인트가 준비되었습니다. 따라서 컨테이너가 실행 중이라는 사실과 모델이 요청을 처리할 준비가 됐다는 사실을 별도의 상태로 관리합니다. 측정 당시 컨테이너는 실행 중이었고 `127.0.0.1:8000`의 엔드포인트가 `google/gemma-4-E2B-it`을 제공하고 있었습니다.

■ 실제 엔드포인트 기능 검증

플래그가 받아들여졌다는 사실만으로 기능이 동작한다고 간주하지 않고, 각 기능에 대해 정답을 미리 알 수 있는 요청을 보냈습니다. 텍스트 응답에서는 “The AMD MI300X is based on the CDNA 3 architecture.”라는 사실을 확인했고, thinking 기능에서는 1,404자의 출력과 447개의 reasoning token을 얻었습니다. tool calling은 `get_weather{"city": "Reykjavik"}` 형태의 도구 호출을 생성하는지 검증했습니다. vision 기능은 외부 사진 대신 밝은 빨간색과 로열 블루 사각형이 번갈아 배치된 체커보드 이미지를 생성해, 이미지 캡션이 아니라 이미지 내용에 대한 알려진 답을 확인하는 방식으로 테스트했습니다.

그 결과 텍스트, thinking, tool calling, vision 네 가지 기능은 모두 검증됐습니다. 오디오는 ROCm vLLM 이미지에 필요한 추가 기능이 없어 테스트하지 않았습니다. 서버 시작 로그에는 GPU KV cache로 155.04GiB를 사용할 수 있고, 총 KV cache 용량은 9,026,017토큰이며, 32,768토큰 요청 기준 최대 동시성은 275.45배라고 표시됩니다.

■ 동시성·컨텍스트별 성능

벤치마크는 컨텍스트 길이 128, 1,024, 8,192토큰과 동시 스트림 1, 4, 16, 64개를 조합해 수행했습니다. 각 셀은 출력 128토큰을 생성하고 세 번 반복했으며, 부하는 GPU 장치에 접근하지 않는 별도 컨테이너의 vLLM 벤치마크 클라이언트가 만들었습니다. 16개 셀 중 12개가 실행 가능했고 4개는 실행 불가능한 셀로 기록했습니다. 예를 들어 최대 모델 길이가 32,768일 때 입력 32,768토큰과 출력 128토큰을 동시에 요구하는 셀은 컨텍스트 한도를 넘기므로 누락하지 않고 infeasible로 처리했습니다.

프롬프트는 `--seed`에서 파생되고 배포 환경에는 prefix caching이 활성화되어 있기 때문에, 같은 seed를 재사용하면 GPU 성능이 아니라 캐시 효과를 측정할 수 있습니다. 이 실험에서는 각 셀과 반복에 다른 seed를 사용했습니다. 세 번의 반복 중 최악의 변동계수(coefficient of variation)는 7.96%였습니다.

| 컨텍스트 | 1 스트림 | 4 스트림 | 16 스트림 | 64 스트림 | |---|---:|---:|---:|---:| | 128 | 340.9 | 1,124.1 | 3,569.1 | 10,284.0 | | 1,024 | 305.1 | 969.1 | 2,653.6 | 5,398.2 | | 8,192 | 192.8 | 445.5 | 688.2 | 702.7 |

출력 토큰 기준 최고치는 128토큰 컨텍스트에서 64개 스트림을 사용했을 때의 10,284 tok/s입니다. Prefill까지 포함하면 1,024토큰 컨텍스트와 64개 스트림에서 총 48,583 tok/s를 기록했습니다. 단일 스트림, 1,024토큰 컨텍스트에서는 첫 토큰까지 32.67ms가 걸렸고 이후 토큰 생성 간격은 3.04ms였습니다.

컨텍스트가 길어질수록 출력 토큰당 시간은 128토큰에서 2.85ms, 1,024토큰에서 3.04ms, 8,192토큰에서 3.9ms로 비교적 완만하게 증가했습니다. 반면 첫 토큰까지의 시간은 12.97ms, 32.67ms, 169.61ms로 크게 늘었습니다. 긴 컨텍스트에서 병목이 되는 것은 decode보다 prefill이며, 이 때문에 8,192토큰 행은 16개 스트림에서 688 tok/s, 64개 스트림에서 703 tok/s로 거의 증가하지 않습니다.

가장 부하가 큰 셀은 64개 스트림과 8,192토큰 컨텍스트의 조합으로, 총 524,288개의 KV 토큰을 사용합니다. 이는 엔진이 할당한 9,026,017토큰 KV 풀의 5.8%에 불과합니다. 따라서 이 192GB 카드에서 2B 모델을 서비스할 때는 KV 메모리 용량보다 긴 입력을 처리하는 prefill이 먼저 성능 한계가 됩니다.

■ 시간당 비용 대비 비교

입력 1,024토큰과 출력 128토큰 조건에서 처리량을 시간당 비용으로 나눠 비교했습니다. MI300X는 시간당 1.99달러의 온디맨드 가격으로 1, 4, 16, 64개 스트림에서 각각 153, 487, 1,333, 2,713 tokens/s per dollar-hour를 기록했습니다. TPU v5e-1은 0.5779달러의 spot 가격을 적용했을 때 208, 711, 1,572, 1,972를 기록했고, TPU v6e-1은 온디맨드 2.97달러에서 67, 233, 587, 719를 기록했습니다. NVIDIA L4는 spot 0.94달러 기준으로 1개와 4개 스트림에서 각각 49와 187을 기록했습니다.

spot 가격과 온디맨드 가격을 섞으면 하드웨어와 가격 조건을 구분하기 어렵기 때문에, 세 장치를 모두 온디맨드 가격으로 환산하면 MI300X가 1, 4, 16, 64개 스트림에서 각각 153, 487, 1,333, 2,713을 기록하고, TPU v5e-1은 100, 342, 757, 950, TPU v6e-1은 74, 257, 646, 790을 기록합니다. 이 계산에서 MI300X는 TPU v5e 대비 동시성별로 1.53배, 1.42배, 1.76배, 2.86배의 tokens-per-dollar를 제공합니다.

다만 DigitalOcean은 GPU droplet에 preemptible 등급을 제공하지 않으므로 MI300X의 1.99달러는 온디맨드 가격 기준입니다. 반면 TPU v5e의 0.5779달러 spot 가격은 실제로 구매 가능한 선택지이며, 16개 이하의 스트림에서는 spot TPU가 MI300X보다 더 많은 토큰을 달러당 처리합니다. 따라서 이 측정의 조건부 결론은 비선점 인스턴스가 필요하거나 수십 개의 스트림을 지속적으로 채울 수 있다면 MI300X가 측정된 장비 중 가장 높은 처리량 대비 비용을 보였고, 트래픽이 적고 선점을 감수할 수 있다면 spot TPU가 더 저렴하다는 것입니다.

■ 측정 결과와 운영상 주의점

최종 결과는 단일 MI300X에서 1,024토큰 컨텍스트 단일 스트림 기준 305 tok/s와 32.67ms의 첫 토큰 지연시간, 128토큰 컨텍스트 64개 스트림 기준 10,284 tok/s, prefill 포함 48,583 tok/s입니다. 엔드포인트가 준비되기까지는 `docker run` 반환 후 160초가 필요했고, KV 풀은 9,026,017토큰이었으며 가장 무거운 셀은 그중 5.8%만 사용했습니다. 측정은 하나의 droplet, atl1 리전, 셀당 세 번의 반복, 각 실행에서 고유한 프롬프트 seed를 사용해 수행했습니다.

비교 대상인 TPU와 L4 결과는 더 이른 vLLM 빌드에서 셀당 한 번만 측정됐고, v5e와 L4는 spot 용량, MI300X와 v6e는 온디맨드 용량에서 측정됐습니다. 또한 비교 저장소에서 AMD 환경으로 같은 체크포인트를 서비스한 쌍둥이 실험은 없었습니다. 따라서 수치는 동일한 조건의 완전한 하드웨어 대조라기보다, 각 보고서의 실행 조건과 가격 모델을 함께 명시한 비교로 제시됩니다. 마지막으로 `docker rm -f vllm`은 GPU 메모리를 해제하지만 droplet 자체를 삭제하지 않으므로 시간당 과금은 계속됩니다.

원문: dev.to / 번역·요약: Trawling