SJ_Koding

[LLM] Docker compose를 활용한 sLLM 파인튜닝 및 추론 자동화하기 上편 - Docker Image 빌드 본문

LLM

[LLM] Docker compose를 활용한 sLLM 파인튜닝 및 추론 자동화하기 上편 - Docker Image 빌드

성지코딩 2024. 11. 13. 10:46

대학생때 부터 AI만 전공해오다보니 백엔드 지식이 턱없이 부족한 것을 깨닫게 해준 프로젝트를 진행해왔습니다.

그 중 Docker를 활용하여 LLM파인튜닝 및 추론단계를 자동화 할 수 있도록 만들어야했는데 제가 삽질하면서 얻은 내용들을 여기에 정리해보고자 합니다. 

LLM특성상 GPU환경을 사용해야만 합니다. 저는 하나의 GPU환경과 모델에 필요한 라이브러리를 하나의 Image로 만들고, trainable data 생성, LLM Finetuning(LoRA), LLM Inference를 진행하는 3개의 컨테이너를 만들어 Docker compose를 활용해 순차적으로 실행되게끔 자동화를 시켜볼 예정입니다.

 

준비물: Docker엔진 Docker compose(v2), 학습용 데이터 코드(json), LLM 파인튜닝 코드 및 추론 코드, requirement.txt, CUDA버전확인

각자 환경에 맞춰 진행하시면 됩니다. 바로 적용할 수 있도록 글을 잘 작성해보겠습니다.


우선 Docker먼저 간략하게 소개하겠습니다.

Docker

Docker는 애플리케이션과 그 실행 환경을 하나의 패키지로 묶어주는 도구라고 설명합니다. 이를 통해 개발자는 동일한 환경에서 애플리케이션을 실행할 수 있어, 환경 차이로 인한 문제를 줄일 수 있습니다. 대표적인 의존성 문제, 성능 재현 문제를 한 번에 해결할 수 있는 강력한 도구입니다.

정말 쉽게 설명하면 "데스크톱PC 자체를 통째로 줄게, 그냥 마우스 까딱만 해"를 가능하게 하는 도구입니다. 예시는 예시일뿐 실제로는 '이미지 형태로 배포한다'로 이해하시면 편합니다.

Docker Image

Docker 이미지가 모든 환경을 가지고있는 아이입니다. 즉, 컴퓨터입니다. 애플리케이션을 실행하기 위한 모든 파일과 설정, 코드, 라이브러리, 설정파일등을 모두 담고있습니다. 이를 기반으로 '컨테이너' 라는 것을 생성하게 됩니다.

Docker Container

Docker 컨테이너는 이미지를 기반으로 실제로 실행되는 독립된 환경입니다. AI 경험이 있으신분들은 익숙한 conda 가상환경과 유사한 개념입니다. 각각의 컨테이너는 독립적으로 이루어져있습니다. 


Dockerfile

Docker Image를 생성할 때 쓰이는 설정 및 명령 파일입니다. Visual Studio에서 Docker 확장프로그램을 설치하면 해당 파일명을 가지는 파일의 아이콘 모양이 바뀝니다. 만약 모양이 바뀌지 않았을 경우, Extention에서 Docker를 검색해 확장프로그램을 설치해주세요. Dockerfile에서는 FROM, ENV, RUN, COPY, CMD 명령이 주로 사용됩니다.

 

  • FROM: 기반이 되는 이미지 설정으로, 새로운 이미지는 이 이미지에서 시작합니다.
  • ENV: 환경 변수를 설정하여 실행 환경을 지정합니다.
  • RUN: 이미지를 빌드할 때 실행될 명령을 지정하여, 패키지 설치나 파일 변경 등의 작업을 수행합니다.
  • COPY: 로컬 파일이나 디렉터리를 이미지 내부로 복사합니다.
  • CMD: 컨테이너가 시작될 때 실행할 기본 명령을 지정합니다.

 

어떻게 보면 Docker 시스템에 있어 가장 핵심이 되는 파일입니다. 이미지단위로 주로 움직이는 도커 시스템에서 이미지를 만들어내는 친구이기 때문입니다. 가장 먼저 LLM 학습과 추론이 가능한 환경을 구축해주는것이 우선입니다. 다음 단계들을 따릅니다.

 

1) 베이스 이미지 설정

프로젝트 파일에 Dockerfile이라는 이름으로 파일을 만들어주세요. 확장자는 필요없습니다.

LLM파인튜닝을 하기 위해서는 앞서 말씀드렸듯 GPU환경이 반드시 필요합니다. 대부분 cuda환경을 사용하므로 먼저 CUDA셋팅부터 해줄겁니다. 해보신분들은 알겠지만 GPU에 맞는 CUDA버전을 확인하고 또 이 CUDA버전에 맞는 cuDNN을 찾아 설치해줘야합니다. 조금 번거로울 수 있으나 다행히도 간단합니다.

도커 시스템은 전세계적으로 정말 많이 사용되고있습니다. 다행히도 CUDA환경이 마련된 이미지파일 또한 이미 존재합니다.

FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04

해당 구문은 NVIDIA에서 제공하는 CUDA 11.8 버전과 cuDNN 8이 포함된 Ubuntu 22.04 기반의 Docker 이미지를 사용하여 환경을 설정하는 것입니다. 즉 Ubuntu환경에서 GPU를 사용할 수 있게 마련된 환경이 준비되어있는것입니다.

(사용하시는 GPU에 맞는 cuda버전을 확인하고, 해당 이미지파일이 있는지 찾으신 후 변경바랍니다.)

 

2) 환경 변수 설정

다음은 컨테이너 환경을 설정하는 환경 변수를 셋팅해야합니다. 이미지가 빌드되는 시점에서 사용자의 개입 없이 환경 셋팅이 마무리가 되어야합니다. 예를 들어 Y/n을 묻는 질문같은 경우를 생략해줍니다. (모두 Y)

ENV DEBIAN_FRONTEND=noninteractive \

또한 추후 DB를 사용하실 분이라면 한국의 time format과 미국의 time format이 상이하고, container도 한국으로 셋팅되어있지 않을 가능성이 큽니다. 따라서 이를 한국을 기준으로 동일화해줍니다. 이참에 모두 한국환경에 맞춥니다.

    TZ=Asia/Seoul \
    LANG=ko_KR.UTF-8 \
    LANGUAGE=ko_KR:ko \
    LC_ALL=ko_KR.UTF-8 \

다음으로 사용할 파이썬과 pip버전을 셋팅합니다. 파이썬은 3.10.14버전을 사용하고, PIP버전은 24.2를 사용합니다.

    PYTHON_VERSION=3.10.14 \
    PYTHON_PIP_VERSION=24.2

한 눈에 

ENV DEBIAN_FRONTEND=noninteractive \
    TZ=Asia/Seoul \
    LANG=ko_KR.UTF-8 \
    LANGUAGE=ko_KR:ko \
    LC_ALL=ko_KR.UTF-8 \
    PYTHON_VERSION=3.10.14 \
    PYTHON_PIP_VERSION=24.2

 

3) 필수 패키지 설치 및 환경 설정

RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    wget \
    zlib1g-dev \
    libncurses5-dev \
    libgdbm-dev \
    libnss3-dev \
    libssl-dev \
    libsqlite3-dev \
    libreadline-dev \
    libffi-dev \
    libbz2-dev \
    liblzma-dev \
    tk-dev \
    uuid-dev \
    git \
    unzip \
    sudo \
    locales && \
    ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
    dpkg-reconfigure --frontend noninteractive tzdata && \
    locale-gen ko_KR.UTF-8 && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

build-essential, libncurses5-dev, libgdbm-dev, libnss3-dev, libssl-dev, libsqlite3-dev, libreadline-dev, libffi-dev, libbz2-dev, liblzma-dev, zlib1g-dev, uuid-dev, tk-dev 등은 Python과 다른 소프트웨어의 빌드 및 컴파일에 필요한 라이브러리입니다. 

이외 기타 유틸리티 라이브러리는 다음과 같습니다:
- wget: 파일 다운로드용 도구
- git: 버전 관리 시스템, 소스 코드 다운로드 및 관리
- unzip: 압축 해제 유틸리티
- sudo: 사용자 권한 관리
- locales: 로케일  설정
이외 등등 

  • ln -fs /usr/share/zoneinfo/$TZ /etc/localtime:
    $TZ로 설정된 시간대 (여기서는 Asia/Seoul)에 맞게 시스템 시간대를 설정
  • dpkg-reconfigure --frontend noninteractive tzdata:
    시간대를 재설정하여 업데이트된 시간대가 적용되도록 합니다.
  • locale-gen ko_KR.UTF-8:
    한국어 UTF-8 로케일을 생성하여 한국어 환경을 지원합니다.
  • apt-get clean && rm -rf /var/lib/apt/lists/*: 
    설치 후 불필요한 패키지 캐시를 삭제하여 이미지의 크기를 줄입니다.

4) 3.10.14 설치 및 빌드 아티팩트 정리

RUN cd /usr/src && \
    wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz && \
    tar xzf Python-${PYTHON_VERSION}.tgz && \
    cd Python-${PYTHON_VERSION} && \
    ./configure --enable-optimizations && \
    make -j"$(nproc)" && \
    make altinstall && \
    ln -s /usr/local/bin/python3.10 /usr/bin/python3 && \
    ln -s /usr/local/bin/pip3.10 /usr/bin/pip3 && \
    cd / && rm -rf /usr/src/Python-${PYTHON_VERSION}*
  • 이 구문은 Python 3.10.14를 소스에서 컴파일하여 설치하는 Dockerfile의 일부로, Python 설치 및 최적화, 그리고 설치 후의 빌드 아티팩트 정리를 수행합니다.

    cd /usr/src: 소스 파일을 다운로드할 디렉토리로 이동합니다.
  • wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz:
    Python 소스 코드 압축 파일을 Python 공식 사이트에서 다운로드합니다. PYTHON_VERSION 환경 변수에 따라 지정된 버전을 가져옵니다 (여기서는 3.10.14).
  • tar xzf Python-${PYTHON_VERSION}.tgz:
    다운로드한 파일을 압축 해제하여 소스 파일을 준비합니다.
  • cd Python-${PYTHON_VERSION}:
    압축 해제된 Python 소스 디렉토리로 이동합니다.
  • ./configure --enable-optimizations:
    Python 설치를 최적화하여 컴파일하도록 설정합니다. 이 옵션은 Python 실행 속도를 높이기 위해 여러 컴파일 최적화 기법을 사용합니다.
  • make -j"$(nproc)":
    소스 코드를 컴파일합니다. -j"$(nproc)" 옵션은 시스템의 CPU 코어 수를 활용하여 병렬로 컴파일하여 설치 속도를 높입니다.
  • make altinstall:
    Python을 설치합니다. altinstall 옵션은 기존 Python 버전과의 충돌을 방지하며, python3의 기본 버전을 바꾸지 않고 별도로 설치하게 합니다.
  • ln -s /usr/local/bin/python3.10 /usr/bin/python3 및 ln -s /usr/local/bin/pip3.10 /usr/bin/pip3:
    Python과 pip 실행 파일에 대한 심볼릭 링크를 생성하여 /usr/bin에서 접근할 수 있도록 합니다. 이를 통해 python3와 pip3 명령어를 사용할 수 있게 됩니다.
  • cd / && rm -rf /usr/src/Python-${PYTHON_VERSION}*:
    빌드 후 남은 Python 소스 디렉토리와 압축 파일을 삭제하여 Docker 이미지 크기를 줄입니다.

이 과정은 Python 3.10.14을 Docker 컨테이너에 설치하기 위한 소스 컴파일 단계로, 필요한 최적화와 링크 설정을 완료한 후, 빌드 중간 파일을 제거해 최종 이미지 크기를 줄이는 데 목적이 있습니다.

 

5) requirements.txt 및 torch 라이브러리 설치


중요! 기존 requirement파일과 소스파일들을 이미지환경에 구축시키기 위해서는 Dockerfile이 있는 경로 상에 있는것이 일반적입니다. 예를 들어 COPY명령을 실행할 때, requirement.txt와 Dockerfile이 동일 경로에 있다면

COPY requirements.txt /app/
코드로 이미지 상에 업로드가 가능해집니다. 즉, 프로젝트 폴더안에 Dockerfile이 위치해있으면 편합니다.


위 셋팅이 되어있다고 가정합니다. requirements.txt를 준비해야하는데 준비하는 방법은 아래 제 이전 포스팅을 참고해주세요.

 

requirements.txt 오류 없이 만들기 (@, file:///를 버전으로 바꾸기)

요약:문제 코드pip freeze > requirements.txt해결 코드pip list --format=freeze > requirements.txt파이썬을 사용한다면 다른 가상환경, 다른 PC, local --> server, 경진대회 검증소스코드등 환경 자체를 옮겨야하는 경

sjkoding.tistory.com


가장 중요한 점은 requirements.txt에서 torch라이브러리를 제외시키는 것입니다. 이미지에서 새롭게 설치한 CUDA 11.8환경과 기존 11.8환경의 torch 라이브러리 의존성이 다른 에러가 종종 발생합니다. requirements 속 라이브러리를 모두 설치 한 후에 torch는 별도로 설치하는걸로 합니다.

requirement환경을 실행하기 위해 아래의 명령을 수행합니다.

# pip 특정 버전으로 업그레이드
RUN pip3 install --no-cache-dir --upgrade pip==${PYTHON_PIP_VERSION}

# Docker 레이어 캐싱을 활용하기 위해 requirements.txt 복사
COPY requirements.txt /app/

그 후 requirements.txt라이브러리와 torch설치를 순차적으로 수행합니다.

# PyTorch(CUDA 11.8 포함)를 비롯한 Python 의존성 설치
RUN pip3 install --no-cache-dir -r /app/requirements.txt && \
    pip3 install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cu118

 

6) git-lfs 설치 및 정리

huggingface의 오픈소스 LLM을 다운받기 위해서는 git-lfs가 필요하며 선행작업으로 git설치도 필요합니다(위 3번 에서 이미 진행함). 

# git-lfs 설치 및 정리
RUN wget https://github.com/git-lfs/git-lfs/releases/download/v3.1.4/git-lfs-linux-amd64-v3.1.4.tar.gz && \
    tar -xzf git-lfs-linux-amd64-v3.1.4.tar.gz && \
    ./install.sh && \
    rm -rf git-lfs-linux-amd64-v3.1.4.tar.gz install.sh README.md CHANGELOG.md LICENSE.md

 

7) 필요 디렉토리 생성 및 코드 파일 및 데이터셋 복사

저만의 규칙을 세웠는데, 여러분의 구조에 맞게 설정하셔도 됩니다.

참고로 /app의 경로는 docker로 부터 생성되거나 사용될 소스코드, 모델파일 등 모든 일괄의 파일을 저장하는 경로이며 정해진 이름은 아니지만 일반적으로 자주 사용되는 경로명입니다.

또 하단의 경로들은 제 소스파일에 맞춘 경로이므로 여러분의 환경에 맞게 적절하게 수정하시기 바랍니다.

/app/outputs : trainable한 데이터셋과 파인튜닝된 모델이 저장되는 경로
/app/models: huggingface에서 다운로드 받아질 LLM모델 및 embedding(필요시) 모델 등
위 두 개의 경로는 추후 docker volume시스템에 의해 관리되며, 불필요한 중복 실행을 막을 수도 있습니다.

/app/train : train관련 코드
/app/inference: inference관련 코드

그리고 이를 복사하는 과정까지의 코드입니다.

# 필요한 디렉토리 생성
RUN mkdir -p /app/outputs /app/models /app/outputs/temp_lora /app/temp

# 코드 파일 및 데이터셋 복사
COPY train/ /app/train
COPY inference/ /app/inference

# 기본 명령어로 nvidia-smi 실행하여 GPU 확인
CMD ["nvidia-smi"]



추후 업로드될 下편에서는 docker compose와 실제 구동 원리를 다룰 예정입니다. docker-compose.yml의 명령구조를 보면 손쉽게 각자 코드구조에 맞게 학습과 추론을 진행할 수 있다는 것을 알 수 있을 것입니다.

8) 전체 Dockerfile 코드

# CUDA 11.8과 Ubuntu 22.04를 기반으로 한 베이스 이미지
FROM nvidia/cuda:11.8.0-cudnn8-devel-ubuntu22.04

# 환경 변수 설정
ENV DEBIAN_FRONTEND=noninteractive \
    TZ=Asia/Seoul \
    PYTHON_VERSION=3.10.14 \
    PYTHON_PIP_VERSION=24.2 \
    LANG=ko_KR.UTF-8 \
    LANGUAGE=ko_KR:ko \
    LC_ALL=ko_KR.UTF-8

# 의존성 설치, 시간대 설정, 로케일 생성 및 정리
RUN apt-get update && apt-get install -y --no-install-recommends \
    build-essential \
    wget \
    zlib1g-dev \
    libncurses5-dev \
    libgdbm-dev \
    libnss3-dev \
    libssl-dev \
    libsqlite3-dev \
    libreadline-dev \
    libffi-dev \
    libbz2-dev \
    liblzma-dev \
    tk-dev \
    uuid-dev \
    tzdata \
    git \
    libaio1 \
    unzip \
    sudo \
    locales && \
    ln -fs /usr/share/zoneinfo/$TZ /etc/localtime && \
    dpkg-reconfigure --frontend noninteractive tzdata && \
    locale-gen ko_KR.UTF-8 && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

# Python 3.10.14 소스에서 설치 및 빌드 아티팩트 정리
RUN cd /usr/src && \
    wget https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz && \
    tar xzf Python-${PYTHON_VERSION}.tgz && \
    cd Python-${PYTHON_VERSION} && \
    ./configure --enable-optimizations && \
    make -j"$(nproc)" && \
    make altinstall && \
    ln -s /usr/local/bin/python3.10 /usr/bin/python3 && \
    ln -s /usr/local/bin/pip3.10 /usr/bin/pip3 && \
    cd / && rm -rf /usr/src/Python-${PYTHON_VERSION} /usr/src/Python-${PYTHON_VERSION}.tgz

# pip 특정 버전으로 업그레이드
RUN pip3 install --no-cache-dir --upgrade pip==${PYTHON_PIP_VERSION}

# Docker 레이어 캐싱을 활용하기 위해 requirements.txt 복사
COPY requirements.txt /app/

# PyTorch(CUDA 11.8 포함)를 비롯한 Python 의존성 설치
RUN pip3 install --no-cache-dir -r /app/requirements.txt && \
    pip3 install --no-cache-dir torch --index-url https://download.pytorch.org/whl/cu118


# git-lfs 설치 및 정리
RUN wget https://github.com/git-lfs/git-lfs/releases/download/v3.1.4/git-lfs-linux-amd64-v3.1.4.tar.gz && \
    tar -xzf git-lfs-linux-amd64-v3.1.4.tar.gz && \
    ./install.sh && \
    rm -rf git-lfs-linux-amd64-v3.1.4.tar.gz install.sh README.md CHANGELOG.md LICENSE.md

# 필요한 디렉토리 생성
RUN mkdir -p /app/outputs /app/models /app/trian /app/inference

# 코드 파일 및 데이터셋 복사
COPY train/ /app/train
COPY inference/ /app/inference

# 기본 명령어로 nvidia-smi 실행하여 GPU 확인
CMD ["nvidia-smi"]

해당 코드에 Oracle셋팅을 포함하면 18GB의 이미지가 완성됩니다. 아직 LLM모델은 다운받지 않았으며 컨테이너 빌드시점에서 volume에 저장되도록 다운로드 할 예정입니다.


Docker image build

본 포스팅은 Docker 엔진이 설치되어있다고 가정합니다. 빌드하는 방법은 간단합니다.

Dockerfile이 있는 경로로 이동한 후

docker build -t <이미지명>:<태그명> .

위  명령을 기입하면 이미지  빌드가 진행됩니다. 설치된 이미지의 내역을 보시려면 아래 명령을 실행해주세요.

docker images

 

감사합니다.