feat(container): add GPU (CUDA) serving image variant + build CI - #27
Conversation
Add a GPU variant of the serving container on an NVIDIA CUDA runtime base so a model's CUDA-enabled framework wheels (e.g. torch) can use the GPU at serve time. Like the CPU variant, torch is not baked; start_server.sh installs the model's requirements.txt via uv at runtime, so the CUDA torch wheel comes from the model. - container/Dockerfile.gpu: nvidia/cuda:12.8.1-cudnn-runtime-ubuntu22.04 base, same uv + mlflow-skinny orchestrator toolchain and start_server.sh entrypoint. - .github/workflows/container-image.yml: matrix build (cpu/gpu) pushing to GHCR on release (and workflow_dispatch). CPU keeps the unsuffixed tag for backward compatibility; GPU uses the -gpu suffix. - container/README.md: document both variants, tag scheme, and runtime torch note.
The serving image installs each model's requirements.txt at runtime, so the base must carry the system deps that common ML wheels need. Add a build toolchain (build-essential, pkg-config), git (git+https requirements), ffmpeg + libsndfile1 (audio/video), and libsm6/libxext6 to both CPU and GPU images. Document remaining gaps in container/README.md: source-compiled CUDA extensions (need the -devel- base / nvcc) and CUDA driver vs torch-wheel compatibility on GPU (the base targets CUDA 12.8; a +cu130 torch wheel fails on a 12.8 node driver).
리뷰프로덕션 GPU 서빙 이미지 + 배포 CI를 추가하는 PR이라 보안/일관성 위주로 확인했습니다. 전반적으로 CPU 이미지와의 패리티를 잘 유지했고, README에 알려진 제약사항(known gaps)까지 명시한 점이 좋습니다. 🔴 보안: - name: Resolve version
id: ver
run: |
if [ "${{ github.event_name }}" = "release" ]; then
V="${{ github.event.release.tag_name }}"
elif [ -n "${{ inputs.version }}" ]; then
V="${{ inputs.version }}"
...
릴리스 태그/워크플로 입력값은 보통 신뢰된 협업자만 생성할 수 있어 실제 악용 난이도는 낮지 않지만, GitHub 공식 가이드/CodeQL에서 권장하는 방식대로 - name: Resolve version
id: ver
env:
RELEASE_TAG: ${{ github.event.release.tag_name }}
INPUT_VERSION: ${{ inputs.version }}
run: |
if [ "${{ github.event_name }}" = "release" ]; then
V="$RELEASE_TAG"
elif [ -n "$INPUT_VERSION" ]; then
V="$INPUT_VERSION"
else
V="$(git rev-parse --short HEAD)"
fi
V="${V#v}"
echo "version=$V" >> "$GITHUB_OUTPUT"🟡 참고: PR 시점 CI 검증 미실행 (이미 known gap으로 명시됨) PR 설명에 이미 체크박스로 표시돼 있지만, 이 워크플로는 release/workflow_dispatch에서만 실행되어 PR 시점에는 빌드 실패를 잡아낼 수 없습니다. 이후에 pull_request 트리거로 push: false 빌드(스모크 테스트)를 추가하면 Dockerfile 문법 오류를 머지 전에 조기에 발견할 수 있을 것 같습니다. (블로킹 사항은 아닙니다.) 🟢 사소한 의견: Dockerfile/Dockerfile.gpu 중복 두 Dockerfile이 시스템 패키지 목록, orchestrator 설치, ENV 블록 등 대부분 내용을 그대로 복제하고 있고, 이를 "parity 유지"라는 주석으로 수동 동기화하고 있습니다. 지금 범위에서는 문제없지만, 추후 목록이 늘어나면 두 파일이 drift될 위험이 있으니 (예: 공통 apt-get install 목록을 별도 스크립트/ARG로 뽑아내는 등) 참고해두면 좋을 것 같습니다. 전체적으로 기능 자체는 잘 구현되어 있고 H100에서 실제 검증까지 마쳐서 안심이 됩니다. 위 스크립트 인젝션 항목만 반영해주시면 좋을 것 같습니다. |
Summary
Add a GPU (CUDA) variant of the serving container plus an image build CI, and broaden the base system dependencies for common ML libraries.
The mlplatform GPU inference deployment needs a CUDA-capable serving image. Like the CPU image, this variant does not bake torch —
start_server.shinstalls the model's ownrequirements.txtat runtime viauv, so the CUDA build of torch comes from the model itself. The GPU base only supplies the CUDA runtime + cuDNN and the NVIDIA container-runtime hooks so those wheels can reach the GPU.Changes
container/Dockerfile.gpu(new):nvidia/cuda:12.8.1-cudnn-runtime-ubuntu22.04base, sameuv+mlflow-skinnyorchestrator toolchain andstart_server.shentrypoint as the CPU image..github/workflows/container-image.yml(new): matrix build (cpu/gpu) pushing to GHCR on release (andworkflow_dispatch). CPU keeps the unsuffixed tag (:<version>,:latest) for backward compatibility; GPU uses the-gpusuffix (:<version>-gpu,:latest-gpu). Floating tags move only on release.build-essential/pkg-config(C/C++ ext without wheels),git(git+httpsrequirements),ffmpeg+libsndfile1(audio/video),libsm6/libxext6(opencv/X runtime libs).container/README.md: document both variants, tag scheme, runtime-torch note, and known gaps.Known gaps (documented in README)
nvcc+ CUDA headers, which are only in the-cudnn-devel-base (~2x size). The GPU image uses the-runtime-base, so prefer models that install prebuilt CUDA wheels.NVIDIA_REQUIRE_CUDA=cuda>=12.8). A model's CUDA framework wheel must match the node driver — e.g. a torch wheel built for CUDA 13.0 (+cu130) fails on a node whose driver only supports CUDA 12.8. Pin torch to acu126/cu128build.Test plan
container/Dockerfile.gpubuilds successfully (local build, ~5.6GB image)..github/workflows/container-image.ymlparses (valid YAML, cpu/gpu matrix).nvidia-smisees the MIG device, CUDA 12.8 runtime + cuDNN present, and a driver-compatible torch (torch==2.7.1+cu126) reportscuda.is_available() == Trueand runs a GPU matmul on a MIG slice.ghcr.io/nubison/nubison-model:0.0.7-gpu(public, anonymous pull HTTP 200).Additional (manual review)