일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 데이터모델링
- dfs
- 인접리스트
- feature map
- SQLD
- 엔터티
- 식별자
- get_dummies()
- Depthwise Separable Convolution
- mobilenet
- dp
- bottleneck
- Inductive Bias
- depthwise convolution
- numpy
- 백준
- 정규화
- 1x1 Convolution
- SQLD 후기
- pytorch
- Two Pointer
- 연산량 감소
- SQL
- outer join
- CROSS JOIN
- BFS
- 인접행렬
- resnet
- 그래프
- skip connection
- Today
- Total
SJ_Koding
[LLM] Docker compose를 활용한 sLLM 파인튜닝 및 추론 자동화하기 下편 - Docker compose 본문
여러분의 소스코드가 담겨있는 Docker Image를 성공적으로 빌드했습니다. ipynb가 아닌 이상 학습을 실행하는 코드와 추론을 진행하는 코드가 별도로 존재하고, 특정 명령을 통해 수행될 것입니다.
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와 같은 더 큰 확장성 있는 플랫폼으로도 자연스럽게 연결될 수 있습니다.
감사합니다.