LLM 엔지니어링17장 · Project 02. LLM 추론 API 서빙3 / 14← 이전목차다음 →MetaContents

17장. Project 02. LLM 추론 API 서빙

서비스로 바꾸는 과정

집에서 끓인 찌개는 내 냄비에, 내 입맛대로, 내가 먹을 때 끓입니다. 식당 메뉴가 되면 다릅니다. 손님은 메뉴판을 보고 주문하고, 육수는 문 열 때 끓여 두고, 손님이 몰릴 때를 대비하고, 다른 지점에서도 같은 맛을 내야 합니다. 모델도 노트북에서 돌릴 때와 서비스로 낼 때 챙길 것이 다릅니다.

함수로 묶기, 약속 정하기, 한 번 올리기, 막아 두기, 상자에 담기의 다섯 걸음 흐름도

1번에서 노트북의 코드를 입력과 출력이 분명한 함수로 묶고, 모델 이름과 주소 같은 설정은 환경 변수로 뺍니다. 2번에서 받을 입력의 범위와 돌려줄 응답의 꼴을 정합니다. 3번에서 모델을 서버가 켜질 때 한 번만 올립니다. 4번에서 붐빌 때와 늦을 때의 답을 정해 둡니다. 5번에서 모델 서버까지 한 묶음으로 담아 어디서나 같은 모습으로 띄웁니다. 여기서 메뉴판이 응답 약속이고, 미리 끓여 둔 육수가 GPU 에 올려 둔 모델입니다.

내 코드와 서비스의 차이

견줄 것내 코드 안(노트북)서비스(추론 API)
부르는 사람나다른 팀, 다른 프로그램
모델을 올리는 때셀을 실행할 때서버가 켜질 때 한 번
입력내가 넣는 대로약속한 범위만 받고 나머지는 422
실패했을 때오류 메시지를 보고 다시 실행정해진 상태 코드(503, 504)로 답
여러 사람생각하지 않음동시 처리 수와 시간 제한
설정코드에 적음환경 변수
도는 곳내 PC서버, 컨테이너

설정을 환경 변수로 빼기

Python
import os


def load_settings():
    """노트북에서는 코드에 바로 적던 값을 환경 변수에서 읽는다. 없으면 오른쪽의 기본값을 쓴다"""
    return {
        "모델": os.environ.get("SUMMARY_MODEL", "qwen2.5:3b"),
        "Ollama 주소": os.environ.get("OLLAMA_URL", "http://127.0.0.1:11434"),
        "동시 처리": int(os.environ.get("SUMMARY_MAX_BUSY", "2")),       # 환경 변수는 늘 글자이므로 숫자로 바꾼다
        "시간 제한(초)": float(os.environ.get("SUMMARY_TIMEOUT", "60")),
    }


print(load_settings())
os.environ["OLLAMA_URL"] = "http://ollama:11434"      # compose 안에서는 다른 컨테이너를 서비스 이름으로 부른다
print(load_settings()["Ollama 주소"])
del os.environ["OLLAMA_URL"]

첫 줄에는 기본값 넷이, 둘째 줄에는 http://ollama:11434 가 나옵니다. 코드는 그대로 두고, 내 PC 에서는 기본값으로, 컨테이너에서는 compose 가 넣어 준 값으로 돕니다.

코드에 나오는 함수

코드쉬운 뜻
os.environos 는 operating system(운영체제), environ 은 environment(환경)의 줄임말이다. 프로그램 밖에서 넣어 주는 환경 변수를 사전처럼 담고 있다
os.environ.get("SUMMARY_MODEL", "qwen2.5:3b")환경 변수 SUMMARY_MODEL 을 읽고, 없으면 오른쪽 기본값을 쓴다
int(...), float(...)글자로 온 값을 정수, 소수로 바꾼다
os.environ["OLLAMA_URL"] = ...환경 변수를 넣는다. compose 가 컨테이너에 값을 넣어 주는 일을 흉내 낸 것이다
del os.environ["OLLAMA_URL"]넣은 환경 변수를 지워 원래대로 돌린다
http://ollama:11434compose 안에서는 컨테이너끼리 서비스 이름(ollama)을 주소처럼 쓴다

이 코드가 뜻하는 것

프랜차이즈 식당의 요리법은 어느 지점이나 같지만, 지점마다 거래하는 정육점 주소와 화구 수는 다릅니다. 그래서 요리법에는 "정육점: 지점 안내문을 볼 것"이라고만 적고, 지점마다 안내문을 따로 붙입니다. 여기서 요리법이 코드, 지점의 안내문이 환경 변수입니다.

줄하는 일쉽게 말하면
load_settings()설정 넷을 환경 변수에서 읽는다지점 안내문을 읽는다
첫 print기본값 넷이 나온다안내문이 없으면 본점 기본값대로 한다
os.environ["OLLAMA_URL"] = ...주소만 바꿔 넣는다새 지점에 안내문을 붙였다
둘째 printhttp://ollama:11434 가 나온다코드는 그대로인데 거래처 주소가 바뀌었다
del os.environ[...]넣은 값을 지운다안내문을 떼어 원래대로 둔다

첫 줄의 '동시 처리': 2 와 '시간 제한(초)': 60.0 은 글자 "2", "60" 을 숫자로 바꾼 결과입니다. 같은 코드가 놓인 곳에 따라 다른 값으로 돈다는 것이 이 코드의 뜻입니다.

작게 줄여서 눈으로 보기

환경 변수가 왜 늘 글자인지, 숫자로 바꾸지 않으면 어떻게 되는지 봅니다. MY_LIMIT 은 설명을 위해 지어낸, 넣지 않은 이름입니다.

Python
import os

print(repr(os.environ.get("MY_LIMIT", "2")))    # 없으면 기본값 "2" (글자)
print("2" + "1", "/", int("2") + 1)              # 글자끼리는 이어 붙고, 숫자로 바꾸면 더해진다
try:
    int("two")
except ValueError as e:
    print("잘못된 값:", e)
출력
'2'
21 / 3
잘못된 값: invalid literal for int() with base 10: 'two'
결과읽는 법
'2'따옴표가 붙어 있다. 기본값도 글자로 준 그대로 돌아온다
21 / 3글자 "2" 와 "1" 을 더하면 이어 붙어 "21" 이 된다. 숫자로 바꿔야 3 이 된다
잘못된 값: invalid literal ...숫자로 바꿀 수 없는 글자면 ValueError 가 난다. 서버가 켜질 때 이렇게 바로 멈추면 잘못을 일찍 찾는다

주의할 점

  • 환경 변수의 값은 늘 글자입니다. 숫자로 쓸 값은 int(), float() 로 바꾸고, 잘못된 값이면 서버가 켜질 때 바로 멈추게 하는 편이 낫습니다. 요청을 받다가 멈추는 것보다 찾기 쉽습니다.
  • API 키나 비밀번호도 환경 변수(또는 비밀 저장소)로 받습니다(13장). 이 장의 두 API 는 인증을 빼고 만들었으니, 실제로 내놓을 때는 13장의 API 키 인증을 붙입니다.
← 이전 절17장 · 용어 짚고 가기다음 절 →17장 · 모델은 켤 때 한 번 올린다