ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • DGX Spark에서 Immich로 가족앨범 GPU 가속 관리하기
    IT 2026. 3. 16. 22:00

    들어가며

    가족앨범을 AI로 관리하고 싶었다. "레고 블록을 쌓는 아이"로 검색하면 해당 사진이 나오고, 얼굴 인식으로 인물별 앨범이 자동으로 만들어지는 것. 구글 포토가 해주는 것을 내 서버에서 직접 돌리고 싶었다.

    Immich는 이 모든 것을 제공하는 셀프호스팅 사진 관리 솔루션이다. 하지만 DGX Spark(ARM64 + Blackwell GPU)에서 돌리려니 생각보다 많은 장벽이 있었다. 이것은 그 구축 과정의 기록이다.


    Immich란?

    구글 포토의 셀프호스팅 대안이다. GitHub 60,000+ stars.

    diagram

    항목 구글 포토 Immich
    데이터 위치 구글 서버 내 서버
    원본 보존 15GB 이후 압축/유료 압축 없음, 무제한
    얼굴 인식
    자연어 검색 ✅ (CLIP)
    비용 15GB 초과 유료 무료
    프라이버시

    환경과 도전 과제

    항목
    서버 NVIDIA DGX Spark
    CPU NVIDIA Grace (ARM64)
    GPU NVIDIA GB10 (Blackwell)
    저장소 Samsung T7 외장 SSD (가족앨범)

    DGX Spark에서의 도전 과제

    DGX Spark는 일반 x86 서버가 아니다. ARM64(aarch64) CPU에 Blackwell GPU라는 최신 조합이다. 이로 인해 몇 가지 문제가 발생한다:

    diagram


    Step 1: Immich 기본 설치

    Docker Compose 배포

    Immich는 4개의 컨테이너로 구성된다. 왜 하나가 아니라 4개일까? 각 컴포넌트가 하는 일이 다르고, 독립적으로 스케일링하거나 재시작할 수 있어야 하기 때문이다:

    diagram

    이렇게 분리하면 ML 컨테이너만 GPU 이미지로 교체하거나, DB만 별도로 백업하는 것이 가능해진다.

    mkdir ~/immich-app && cd ~/immich-app
    wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
    wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
    

    ARM64 호환 문제: Docker 이미지 SHA 해시

    공식 docker-compose.yml에는 일부 이미지에 SHA256 해시가 하드코딩되어 있었다.

    image: valkey/valkey:9@sha256:abc123...
    

    이 해시의 원래 의도는 보안과 재현성이다. 태그(9)는 언제든 다른 이미지를 가리킬 수 있지만, SHA256 해시는 정확히 그 바이너리를 지정한다. 누군가 악의적으로 태그를 바꿔치기해도 해시가 다르면 pull을 거부하므로, 운영 환경에서 "어제까지 되던 게 오늘 안 된다" 같은 사고를 방지할 수 있다.

    문제는 이 해시가 특정 플랫폼(amd64)의 이미지를 고정하고 있었다는 것이다:

    diagram

    [해결]
    image: valkey/valkey:9@sha256:abc123...  → image: valkey/valkey:9
    

    해시를 제거하면 Docker 데몬이 현재 호스트의 CPU 아키텍처를 확인하고, 해당 플랫폼용 이미지를 자동으로 선택한다. 보안성은 약간 떨어지지만, ARM64에서 실행하려면 필요한 조치다.

    ML 컨테이너: GPU 가속 이미지 비호환

    Immich는 GPU 가속을 위해 release-cuda 태그를 제공한다. 이 이미지에는 CUDA 런타임과 onnxruntime-gpu가 미리 설치되어 있어서 x86 서버에서는 바로 GPU 가속이 작동한다. 하지만 ARM64용 release-cuda 이미지는 Immich 팀이 빌드하지 않는다:

    diagram

    일단 CPU 모드(release)로 시작했다.


    Step 2: 외장 드라이브 연결 (External Library)

    가족앨범이 Samsung T7 외장 SSD에 있다. Immich로 복사 없이 기존 폴더 구조 그대로 접근하고 싶었다.

    마운트 구성

    diagram

    docker-compose.ymlimmich-server 서비스에 볼륨을 추가:

    volumes:
      - ${UPLOAD_LOCATION}:/data
      - /path/to/가족앨범:/mnt/photos:ro
    

    :ro(read-only)로 마운트하여 Immich가 원본 파일을 수정하지 못하게 했다. 가족 사진 원본은 절대 건드리면 안 되니까.

    External Library 등록

    Immich 웹 UI(http://localhost:2283)에서:

    1. 관리자 설정 → External Libraries → 라이브러리 추가
    2. Import Path: /mnt/photos
    3. 스캔 시작

    가족앨범의 사진이 인식되기 시작했다.


    Step 3: GPU 가속 — 커스텀 ML 이미지 제작

    CPU 모드에서 가족앨범 전체의 AI 분석은 며칠이 걸릴 것으로 예상되었다. GPU 가속이 필수였다.

    문제 구조

    diagram

    ONNX Runtime GPU wheel 빌드 과정은 별도의 글로 다루었다. 여기서는 빌드된 wheel을 Immich에 통합하는 과정을 다룬다.

    Multi-stage Dockerfile

    빌드된 onnxruntime_gpu wheel을 Immich ML 컨테이너에 넣어야 한다. 단순히 pip install하면 될 것 같지만, 세 가지 함정이 있었다.

    함정 1: CUDA 런타임 라이브러리 부재

    onnxruntime-gpu는 이름 그대로 GPU용 ONNX Runtime이다. 하지만 이 패키지 혼자서는 GPU를 사용할 수 없다. GPU 연산을 실제로 수행하는 것은 NVIDIA의 CUDA 런타임 라이브러리들(libcudart, libcublas, libcudnn 등)이고, onnxruntime-gpu는 이 라이브러리들을 호출하는 쪽이다.

    diagram

    그런데 Immich의 release 이미지는 CPU 전용이라 CUDA 라이브러리가 전혀 없다:

    diagram

    해결: NVIDIA 공식 CUDA 이미지에서 라이브러리만 복사하는 Multi-stage 빌드를 사용했다. NVIDIA가 제공하는 nvidia/cuda:13.0.0-cudnn-runtime 이미지에는 필요한 라이브러리가 모두 들어있다. 이 이미지를 통째로 쓰는 게 아니라, 라이브러리 파일(.so)만 꺼내서 Immich 이미지에 복사한다:

    # Stage 1: CUDA 라이브러리가 들어있는 이미지 (복사 소스)
    FROM nvidia/cuda:13.0.0-cudnn-runtime-ubuntu22.04 AS cuda-libs
    
    # Stage 2: Immich ML 이미지에 라이브러리 + GPU wheel 추가
    FROM ghcr.io/immich-app/immich-machine-learning:release
    
    COPY --from=cuda-libs /usr/local/cuda/lib64/lib*.so* /usr/local/cuda/lib64/
    COPY --from=cuda-libs /usr/lib/aarch64-linux-gnu/libcudnn* /usr/lib/aarch64-linux-gnu/
    

    diagram

    함정 2: pip 설치 경로

    CUDA 라이브러리를 넣었으니, 이제 Immich에 들어있는 CPU용 onnxruntime을 호스트에서 빌드한 GPU용 커스텀 wheel로 교체해야 한다. Dockerfile에서 pip3 install로 이 wheel을 설치했다. 설치 자체는 성공했다. 하지만 Immich를 실행하면 여전히 CUDAExecutionProvider가 나타나지 않았다. GPU용 라이브러리(libonnxruntime_providers_cuda.so)가 분명히 설치되었는데, Immich의 Python이 그것을 찾지 못하는 것이다.

    원인은 Immich ML 컨테이너 내부에 Python 가상 환경(venv)이 있기 때문이다. pip3 install의 기본 설치 경로와 Immich가 실제로 참조하는 경로가 달랐다:

    diagram

    해결: 기존 CPU 버전을 삭제한 뒤, --target 옵션으로 venv 안의 정확한 경로에 직접 설치:

    RUN rm -rf /opt/venv/lib/python3.11/site-packages/onnxruntime* \
        && pip3 install --no-deps \
           --target=/opt/venv/lib/python3.11/site-packages \
           /tmp/onnxruntime_gpu-1.24.3-*.whl
    

    함정 3: GPU 디바이스 연결

    이미지 안에 CUDA 라이브러리와 onnxruntime-gpu가 모두 들어갔다. 하지만 Docker 컨테이너는 기본적으로 호스트의 GPU에 접근할 수 없다. Docker는 보안을 위해 컨테이너를 격리하므로, GPU 같은 하드웨어 디바이스는 명시적으로 연결해줘야 한다.

    diagram

    docker-compose.yml에 GPU 디바이스 예약을 추가해야 한다. 이것은 Docker에게 "이 컨테이너는 NVIDIA GPU를 사용할 것이니 디바이스를 연결해줘"라고 알려주는 것이다:

    immich-machine-learning:
      image: immich-ml-gpu:local    # 커스텀 이미지
      deploy:
        resources:
          reservations:
            devices:
              - driver: nvidia
                count: 1
                capabilities: [gpu]
    

    이 설정이 있으면 NVIDIA Container Toolkit이 호스트의 GPU 디바이스(/dev/nvidia0 등)를 컨테이너 안으로 패스스루해준다.


    Step 4: GPU 가속 검증

    커스텀 이미지를 빌드하고 배포한 후 검증:

    $ docker exec immich_machine_learning python3 -c \
        "import onnxruntime; print(onnxruntime.get_available_providers())"
    
    ['CUDAExecutionProvider', 'CPUExecutionProvider']
    

    CUDAExecutionProvider가 나타났다!

    성능 측정

    Smart Search(CLIP), Face Detection(InsightFace), Facial Recognition을 순서대로 실행:

    [GPU 가속 결과]
    
    Smart Search (CLIP 인덱싱):
      CPU: 수일 예상
      GPU: 4분 완료!
    
    Face Detection (InsightFace):
      CPU: 수일 예상
      GPU: 8분 완료!
    
    Facial Recognition (클러스터링):
      CPU: 수일 예상
      GPU: 자동 실행 → 10분 완료!
    
    총 소요: 약 22분 (CPU였다면 수일)
    

    결과 확인

    Immich 웹 UI에서: - 검색: "레고 블록을 쌓는 아이" → 실제 해당 사진이 검색됨 - People: 자동으로 인물별 클러스터링 완료 - 지도: GPS 데이터가 있는 사진은 지도에 표시


    Step 5: 썸네일 미생성 문제

    GPU 가속으로 AI 분석은 빠르게 끝났지만, 웹 UI에서 일부 사진이 로딩되지 않았다.

    [증상]
      웹 UI에서 일부 사진 → "이미지 로딩 오류" 표시
    
    [조사]
      전체 사진 중 약 39%의 썸네일이 미생성!
    

    External Library로 등록된 사진은 AI 분석과 별도로 썸네일 생성 잡을 실행해야 했다:

    # Immich API로 썸네일 생성 잡 강제 실행
    curl -X PUT -H "x-api-key: $API_KEY" \
      "http://localhost:2283/api/jobs/thumbnailGeneration" \
      -d '{"command": "start", "force": true}'
    

    약 15분 후 전체 썸네일 생성 완료. 이후 모든 사진이 정상 표시되었다.


    최종 아키텍처

    diagram


    교훈 정리

    1. ARM64에서 Immich는 CPU 모드로는 바로 작동한다

    공식 release 이미지는 ARM64를 지원한다. GPU 가속이 필요 없다면 별도 작업 없이 바로 사용 가능하다.

    2. GPU 가속은 커스텀 이미지가 필요하다

    Immich의 release-cuda 이미지(GPU 가속용)가 ARM64를 지원하지 않으므로, CUDA 런타임 + onnxruntime-gpu를 직접 넣은 커스텀 이미지를 만들어야 한다. Multi-stage Docker 빌드가 핵심이다.

    3. External Library 사용 시 썸네일 잡을 확인할 것

    사진을 복사하지 않고 외장 드라이브를 마운트하면, AI 분석은 자동이지만 썸네일 생성은 별도로 트리거해야 할 수 있다.

    4. Docker 이미지의 SHA 해시에 주의

    공식 compose 파일의 이미지 해시가 특정 플랫폼(amd64) 전용일 수 있다. ARM64에서는 해시를 제거하고 태그만 사용하면 Docker가 플랫폼에 맞는 이미지를 자동 선택한다.

    5. 컨테이너 내부 경로를 확인할 것

    venv가 있는 컨테이너에서는 pip의 기본 설치 경로와 실제 사용 경로가 다를 수 있다. --target 옵션으로 정확한 경로에 설치해야 한다.


    GPU 가속의 가치

    작업 CPU GPU 비고
    CLIP 검색 인덱싱 수일 4분 "레고 블록을 쌓는 아이" 검색 가능
    얼굴 검출 수일 8분 가족앨범에서 얼굴 추출
    얼굴 클러스터링 수일 10분 인물별 자동 분류
    합계 수일 22분  

    가족앨범이 22분 만에 검색 가능하고, 인물별로 정리되고, 지도 위에 표시된다. 구글 포토에 의존하지 않고, 내 서버에서, 내 데이터로, 완전한 프라이버시를 지키면서.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.