일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- dp
- mobilenet
- depthwise convolution
- 식별자
- outer join
- 정규화
- 인접행렬
- SQLD 후기
- resnet
- BFS
- 1x1 Convolution
- 데이터모델링
- SQLD
- skip connection
- Depthwise Separable Convolution
- get_dummies()
- pytorch
- dfs
- feature map
- 연산량 감소
- 엔터티
- 인접리스트
- Two Pointer
- 그래프
- numpy
- bottleneck
- SQL
- CROSS JOIN
- Inductive Bias
- 백준
- Today
- Total
SJ_Koding
[LLM] Docker compose를 활용한 sLLM 파인튜닝 및 추론 자동화하기 下편 - Docker compose 본문
여러분의 소스코드가 담겨있는 Docker Image를 성공적으로 빌드했습니다. ipynb가 아닌 이상 학습을 실행하는 코드와 추론을 진행하는 코드가 별도로 존재하고, 특정 명령을 통해 수행될 것입니다.
[LLM] Docker compose를 활용한 sLLM 파인튜닝 및 추론 자동화하기 上편 - Docker Image 빌드
대학생때 부터 AI만 전공해오다보니 백엔드 지식이 턱없이 부족한 것을 깨닫게 해준 프로젝트를 진행해왔습니다.그 중 Docker를 활용하여 LLM파인튜닝 및 추론단계를 자동화 할 수 있도록 만들어
sjkoding.tistory.com
LLM파인튜닝 특성상 환경을 분할할 필요가 적습니다. train타입과 inference타입의 환경은 거의 동일하며 소스코드만 차이가 나기 때문에 이 때문에 이미지를 2개로 나누는 것이 어쩌면 공간적으로 비효율적일 수 있기 때문에 하나의 이미지를 활용할 예정입니다. 물론 inference때 train코드가 필요 없겠지만, 일반적으로 큰 문제가 아닙니다.
우선 Docker compose가 무엇인지 간략하게 살펴보겠습니다.
Docker compose는 여러 컨테이너 정의를 docker-compose.yml파일로 정의하며, 이 파일에 담긴 모든 컨테이너의 정의 및 명령을 일괄적으로 빌드할 수 있게 해주는 도구입니다.
저는 trainable한 데이터셋으로 변환해주는 컨테이너, fine-tuning을 진행하는 컨테이너, inference를 진행하는 컨테이너를 빌드할 예정인데, 이를 간편하게 수행할 수 있게 해줍니다.
설정 값으로는 네트워크, 볼륨, 환경 변수 등이 존재하며, 해당 파일로 어디에서나 동일한 환경을 재현할 수 있어 편리합니다.
빌드 명령은 아래 명령 으로 매우 간편하게 빌드할 수 있습니다:
docker compose up # 최신버전 (2020년 12월 이후, 권장버전)
docker-compose up # 구버전
복잡한 docker run과 다르게 이미 설정값이 docker-compose.yml에 삽입 되어있기 때문입니다.
컨테이너의 구성
1. dataset_generator: 예를 들어 db나 csv파일로 부터 가져온 데이터를 LLM이 파인튜닝할 수 있는 형태로 재 가공하는 컨테이너
2. finetuner: LLM 모델을 fine-tuning하는 컨테이너 (LoRA or Full)
3. inferrer: 학습된 LLM모델을 테스트하고 기능을 수행하는 컨테이너
위 컨테이너를 작성시키기 위해서는 각 컨테이너마다 필요한 소스코드를 Image 빌드시점에서 함께 빌드되어야합니다. 예를 들어, 여러분이 사용하시는 데이터셋을 여러분의 LLM모델 학습 포맷에 맞춰 재 가공하는 소스파일을 작성하셔야합니다. 저는 이전에 빌드했던 /app/train경로안에 fine-tuning소스와 dataset_generator소스파일이 함께 포함되어 있어 /app/train과 /app/inference 경로 두 개를 사용합니다.
Docker-compose.yml 작성하기
1) Docker volume 설정
이전 포스팅에서 저만의 규칙을 세웠고 해당 내용을 다시 복기하자면 기존 모델파일은 /app/models에 저장될 것이며, 파인튜닝된 LoRA어뎁터, 생성된 데이터셋을 저장하는 경로는 /app/outputs에 저장될 예정이라고 했습니다. 그리고 저는 해당 경로를 local의 디스크를 빌린 volume을 활용하여 저장할 계획입니다. 이를 각각 다음과 같이 정의하겠습니다.
volumes:
output-volume:
name: output-volume
driver: local
model-volume:
name: model-volume
driver: local
두 개의 볼륨을 정의했으며 name: 값을 지정하지 않으면 docker compose를 실행하는 프로젝트 폴더명이 접미사로 붙게됩니다. 만약 docker_project/ 라는 폴더에서 지정하고 해당 볼륨을 name: 값 없이 생성하게 되면docker_project_output-volume 이라는 볼륨 명을 가질 것 입니다. 이는 추후 협업을 진행할 때 프로젝트 명에 따라 파일을 중복하여 저장할 수 있기 때문에 name을 지정하면 동일한 볼륨에서 협업이 가능합니다.
driver: local의 의미는 해당 docker compose를 실행하는 호스트 환경의 디스크를 사용한다는 의미입니다. Ubuntu환경 기준으로 /var/lib/docker/volume/~~~ 경로에 저장되게 되며 일반 권한으로 해당 경로를 접근할 수 없어 sudo권한을 이용해야합니다.
2) dataset_generator 컨테이너 설정
version: '3'
services:
dataset_generator:
image: seongjiko/enssel_llm_env:latest
container_name: dataset_generator
command: [ "/bin/bash", "-c", "set -e; python3 /app/train/datasets/trainable_dataset_generator.py;" ]
volumes:
- output-volume:/app/outputs
컨테이너명은 dataset_generator로 지정했습니다. image는 이전에 빌드했던 이미지를 사용합니다. command는 해당 컨테이너가 빌드될 때 실행하는 명령들의 집합체입니다. 즉 trainable_dataset_generator.py를 실행한다는 뜻이죠.
이때 output-volume의 /app/outputs경로를 마운트하여 해당 경로에 생성된 데이터셋을 저장합니다. 저장될 경로는 소스코드내에서 직접 /app/outputs로 지정해야합니다. 해당 볼륨에 저장하면 추후에 fine-tuning하는 시점에서 저장된 데이터셋을 같은 볼륨에 접근하여 불러올 수 있게 됩니다.
3) llm_finetuner 컨테이너 설정
# if test for inferrer
llm_finetuner:
image: seongjiko/enssel_llm_env:latest
container_name: llm_finetuner
command: |
/bin/bash -c "
set -e
echo 'Starting llm_finetuner...'
git lfs install
if [ ! -d /app/models/llama-3.2-3B-Instruct ]; then
echo 'Cloning repository Llama-3.2-3B-Instruct...'
sudo git clone https://huggingface.co/Enssel/Llama-3.2-3B-@@@ /app/models/llama-3.2-3B-Instruct
else
echo 'Repository Llama-3.2-3B-Instruct already cloned.'
fi
if [ ! -d /app/outputs/llama3.2-SFT-lora ]; then
echo 'Running finetuning.py...'
sudo git clone https://huggingface.co/@@@/llama3.2-3b-@@@ /app/outputs/llama3.2-SFT-lora
else
echo '/app/outputs/llama3.2-SFT-lora already exists. Skipping finetuning.'
fi
echo 'llm_finetuner completed.'
"
networks:
- sop-network
volumes:
- output-volume:/app/outputs
- model-volumes:/app/models:rw
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 2
capabilities: [ gpu ]
depends_on:
dataset_generator:
condition: service_completed_successfully
예시로 llama3.2를 사용합니다.
git lfs install는 LFS(Large File Storage)를 활성화 하여 대형 모델 파일을 다운로드할 준비를 합니다.
그리고 만약 모델이 다운로드된 적이 없다면(git clone을 통해 다운로드 받아진 모델은 model-volumes의 /app/models에 저장하게 되며 if문으로 이미 다운로드가 되어있는지 확인할 수 있습니다.) huggingface에서 모델을 clone해옵니다. 사실 Image를 빌드하는 시점에서 모델을 미리 삽입해도 되나, 그렇게 되면 이미지의 용량이 터무니없이 커지게됩니다. (30~50GB 이상)
그리고 나서 파인튜닝된 모델이 저장되어있지 않은경우(최초로 튜닝을 시도할 경우) 파인튜닝 소스파일을 실행시켜 파인튜닝을 진행하게 됩니다.
이때 output-volume의 /app/outputs경로를 마운트하며 이전에 저장했던 trainable dataset을 사용할 수 있게됩니다. 그러면 선행조건이 dataset_generator 컨테이너가 실행이 되고 난 후에 해당 컨테이너를 실행해야한다는 점인데, 이를 depends_on으로 설정할 수 있습니다.
dataset_generator에 의존하는 컨테이너이며, 실행 조건은 service_completed_successfully 즉, 의존하는 컨테이너가 성공적으로 종료되고 나서 실행한다는 의미입니다. 저는 해당 구조를 통해 자동화를 진행하였습니다. 나중에는 KubeFlow등에 비슷한 원리로 적용할 수 있습니다.
파인튜닝된 모델은 dataset과 마찬가지로 output-volume의 /app/outputs에 저장되게 됩니다.
또한 deploy.resources.reservations.devices를 통해 NVIDIA GPU 2개를 할당하여, GPU 리소스를 활용한 고성능 학습이 가능하도록 설정했습니다.
4) llm_inferrer 컨테이너 설정
llm_inferrer:
image: seongjiko/enssel_llm_env:latest
container_name: llm_inferrer
command: |
/bin/bash -c "
set -e
echo 'Starting llm_inferrer...'
cd /app/inference
uvicorn main:app --host 0.0.0.0 --port 8811
"
networks:
- sop-network
volumes:
- output-volume:/app/outputs
- model-volumes:/app/models:rw
ports:
- "8811:8811"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 2
capabilities: [ gpu ]
depends_on:
llm_finetuner:
condition: service_completed_successfully
마지막으로 추론 컨테이너입니다. command를 보고 눈치를 채신분도 계시겠지만, 개인적으로는 FastAPI를 통해 추론 시스템을 개발했습니다(uvicorn명령).
이제는 대충 감이 오실텐데 지금까지 저장된 model과 outputs들을 모두 불러와서 단순히 추론 로직을 실행하는 구문입니다.
이 역시 llm_finetuner가 정상적으로 종료되는 것을 대기하고 있습니다. 데이터셋 생성 - 파인튜닝 - 추론이 순차적으로 자동화 되게 됩니다.
전체코드
version: '3'
services:
dataset_generator:
image: seongjiko/enssel_llm_env:latest
container_name: dataset_generator
command: [ "/bin/bash", "-c", "set -e; echo 'Starting dataset generation...'; echo 'Generating dataset...'; python3 /app/train/datasets/trainable_dataset_generator.py; echo 'Dataset generation completed.'" ]
volumes:
- output-volume:/app/outputs
llm_finetuner:
image: seongjiko/enssel_llm_env:latest
container_name: llm_finetuner
command: |
/bin/bash -c "
set -e
echo 'Starting llm_finetuner...'
git lfs install
if [ ! -d /app/models/llama-3.2-3B-Instruct ]; then
echo 'Cloning repository Llama-3.2-3B-Instruct...'
sudo git clone https://huggingface.co/@@@/Llama-3.2-3B-Instruct_@@@ /app/models/llama-3.2-3B-Instruct
else
echo 'Repository Llama-3.2-3B-Instruct already cloned.'
fi
if [ ! -d /app/outputs/llama3.2-SFT-lora ]; then
echo 'Running finetuning.py...'
sudo git clone https://huggingface.co/@@@/llama3.2@@@ /app/outputs/llama3.2-SFT-lora
else
echo '/app/outputs/llama3.2-SFT-lora already exists. Skipping finetuning.'
fi
echo 'llm_finetuner completed.'
"
networks:
- sop-network
volumes:
- output-volume:/app/outputs
- model-volumes:/app/models:rw
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 2
capabilities: [ gpu ]
depends_on:
dataset_generator:
condition: service_completed_successfully
llm_inferrer:
image: seongjiko/enssel_llm_env:latest
container_name: llm_inferrer
command: |
/bin/bash -c "
set -e
ls /app/outputs
echo 'Starting llm_inferrer...'
cd /app/inference
uvicorn main:app --host 0.0.0.0 --port 8811
"
networks:
- sop-network
volumes:
- output-volume:/app/outputs
- model-volumes:/app/models:rw
ports:
- "8811:8811"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 2
capabilities: [ gpu ]
depends_on:
llm_finetuner:
condition: service_completed_successfully
networks:
sop-network:
driver: bridge
name: sop-network
volumes:
output-volume:
name: sop-output-volume
driver: local
model-volumes:
name: sop-model-volumes
driver: local
이번 글에서는 Docker Compose를 활용해 LLM의 데이터셋 생성, 파인튜닝, 추론 과정을 자동화하는 방법을 다뤘습니다. 세 단계로 나뉜 컨테이너 구성(dataset_generator, llm_finetuner, llm_inferrer)을 통해 데이터 파이프라인을 체계적으로 관리하고, GPU 리소스를 효율적으로 활용하며, 설정 파일을 통해 환경 재현성을 확보할 수 있었습니다.
이러한 자동화 구조는 개발 및 운영의 효율성을 극대화하며, 향후 KubeFlow와 같은 더 큰 확장성 있는 플랫폼으로도 자연스럽게 연결될 수 있습니다.
감사합니다.