홈강좌LAYA 결정 모델 24편 중 14편째
LAYA 를 Docker 이미지로 — 학습한 판마다 이미지 하나
학습한 판을 Docker 이미지 하나로 굳히면 어디서든 같은 것이 돈다. CPU 판 PyTorch 로 이미지를 만들고 학습 중 백업은 빼서 1.87GB 가 됐다. 컨테이너를 127.0.0.1 에 띄워 요청을 보내고, CPU 로 걸리는 시간과 메모리를 잰다. 이 시리즈에서는 이미지 태그가 곧 모델 판이다(laya-tickets:v2).
01 Dockerfile
{{file:build-v2/Dockerfile}}
- CPU 판 PyTorch 를 먼저 깐다. 그냥
pip install laya를 하면 리눅스에서는 NVIDIA GPU 용 PyTorch 가 딸려 와 이미지가 수 GB 더 커진다. 서버에 GPU 가 없으면 CPU 판이면 된다(GPU 서버용은 공식 저장소의 CUDA 판 compose 설정을 쓴다). - 판을 고정한다(
torch==2.15.0·laya[serve]==0.4.27). 학습할 때와 같은 판이어야 같은 결과가 나온다. - 비루트 사용자(
laya, UID 10001)로 돌린다. 컨테이너가 뚫려도 할 수 있는 일이 줄어든다. 공식 문서도 체크포인트 폴더가 UID 10001 로 쓸 수 있어야 한다고 적고 있다. LAYA_EXTRA_MODELS로 이미지 안의/models/tickets를tickets로 등록하고,LAYA_MODELS=tickets와LAYA_PRELOAD=1로 시작할 때 그 모델만 미리 올린다. 첫 요청이 모델을 올리느라 느려지지 않는다.- 이미지 안에는 API 키를 넣지 않는다. 키는 띄울 때 넘긴다(아래).
02 서비스에 필요한 파일만 담는다
#!/bin/sh
# 학습한 판(v2 등)을 이미지 하나로 굳힌다 — 이미지 태그가 곧 모델 판이다.
set -e
VERSION=$1
rm -rf model-files && mkdir model-files
# 학습 중 백업(checkpoint_latest)은 빼고, 서비스에 필요한 것만 담는다.
cp -R checkpoints/$VERSION/model.safetensors checkpoints/$VERSION/encoder checkpoints/$VERSION/tokenizer \
checkpoints/$VERSION/rl_agent_config.json checkpoints/$VERSION/questions.json model-files/
docker build --quiet -t laya-tickets:$VERSION . > /dev/null
docker image ls laya-tickets:$VERSION --format 'image {{.Repository}}:{{.Tag}} size {{.Size}}'
rm -rf model-files7편에서 본 체크포인트 폴더 가운데 학습 중 백업(checkpoint_latest/, 647MB)과 학습 기록은 빼고, 모델·토크나이저·설정·질문만 model-files/ 로 복사해 이미지에 넣는다. 체크포인트 폴더(checkpoints)는 .dockerignore 로 빌드에 보내지 않는다.
sh build.sh v2image laya-tickets:v2 size 1.87GB1.87GB 가운데 모델이 약 0.65GB, 나머지는 파이썬·PyTorch(CPU)·LAYA 다. 판이 바뀌어도 위쪽 층(파이썬·라이브러리)은 같아서, 새 판 이미지를 만들 때는 모델 층만 새로 만들어진다.
03 띄워서 묻는다
#!/bin/sh
# 이미지를 127.0.0.1:8001 에 띄워 요청을 보내 보고, 메모리를 재고, 멈춘다.
VERSION=$1
: "${LAYA_API_KEY:=$(python -c 'import secrets; print(secrets.token_hex(16))')}" # 밖에서 주지 않으면 이번 실행용 키를 만든다
export LAYA_API_KEY
docker run -d --rm --name laya-tickets-$VERSION -p 127.0.0.1:8001:8000 \
-e LAYA_API_KEY laya-tickets:$VERSION > /dev/null
for i in $(seq 1 120); do
curl -s -o /dev/null localhost:8001/health && break
sleep 1
done
echo "== request"
curl -s localhost:8001/v1/systemone \
-H 'content-type: application/json' \
-H "authorization: Bearer $LAYA_API_KEY" \
-d @request.json | python -c 'import json,sys; r=json.load(sys.stdin); a=r["answers"]; print("team", a["team"]["choice"], a["team"]["probabilities"]); print("urgent", a["urgent"]["noul"])'
echo "== latency (20 requests, CPU in container)"
for i in $(seq 1 20); do
curl -s -o /dev/null -w '%{time_total}\n' localhost:8001/v1/systemone \
-H 'content-type: application/json' -H "authorization: Bearer $LAYA_API_KEY" -d @request.json
done | python -c 'import sys; t=sorted(float(x) * 1000 for x in sys.stdin); print(f"median {t[len(t)//2]:.0f} ms, slowest {t[-1]:.0f} ms")'
echo "== memory"
docker stats --no-stream --format '{{.Name}} {{.MemUsage}}' laya-tickets-$VERSION | sed 's| / .*||' # 사용량만(한도는 기계마다 다르다)
docker stop laya-tickets-$VERSION > /dev/null-p 127.0.0.1:8001:8000 은 컨테이너의 8000 번을 이 컴퓨터 안에서만 8001 번으로 연다. API 키는 -e LAYA_API_KEY 로 띄울 때 셸의 값을 그대로 넘긴다(값을 적지 않으면 같은 이름의 환경 변수를 넘긴다). 스크립트는 12편처럼, 밖에서 키를 주지 않으면 이번 실행용 무작위 키를 만든다.
sh run.sh v2== request
team technical {'billing': 0.0, 'delivery': 0.0, 'account': 0.0, 'technical': 1.0}
urgent 1.0
== latency (20 requests, CPU in container)
median 287 ms, slowest 360 ms
== memory
laya-tickets-v2 1.803GiB- 12편과 같은 문의에 같은 답(기술팀 1.0)이 나왔다. 맥의 GPU 가 아니라 컨테이너 안의 CPU 로 계산했는데도 가중치가 같으니 답이 같다.
- 응답 시간은 중앙값 287ms, 가장 느린 것 360ms 다. 맥 GPU 의 20~40ms(3편)보다 느리지만, 공식 문서의 CPU 수치(193~464ms) 범위 안이고 대부분의 업무 흐름에는 충분하다(13편).
- 메모리는 약 1.8GB 다. 서버를 고를 때 모델 하나당 메모리 2GB 남짓을 잡는다.
04 실습 — 서버의 CPU 종류에 맞추기
맥(Apple Silicon)에서 만든 이미지는 arm64 다. 서버가 AWS Graviton 같은 ARM 이면 그대로 돌고, 일반 인텔·AMD(amd64) 서버라면 그 아키텍처로 다시 만든다.
docker buildx build --platform linux/amd64 -t laya-tickets:v2-amd64 .다른 아키텍처를 흉내 내며 만드는 것이라 느리다. 실무에서는 서버와 같은 아키텍처의 CI 에서 이미지를 만들어 저장소(ECR·Docker Hub 등)에 올리고, 서버는 그 이미지를 받아 띄운다. 다음 편에서 판을 바꾸고 되돌리는 운영을 다룬다.