LLM 엔지니어링14장 · Streamlit 화면과 Docker 컨테이너6 / 22← 이전목차다음 →MetaContents

14장. Streamlit 화면과 Docker 컨테이너

상태와 캐시

카페의 쿠폰 도장은 손님마다 따로 쌓이고, 커피 원두는 아침에 한 번 볶아 모든 손님에게 씁니다. Streamlit 에서 손님마다 따로 남는 것이 세션 상태, 한 번 준비해 모두가 함께 쓰는 것이 캐시입니다.

사용자마다 따로 남는 세션 상태, 한 번 불러 모두가 함께 쓰는 자원 캐시, 같은 입력의 결과를 저장하는 데이터 캐시를 견준 세 카드

왼쪽 세션 상태(st.session_state)는 브라우저 탭마다 따로 남는 값으로, 대화 기록이나 누른 횟수를 둡니다. 가운데 @st.cache_resource 는 모델이나 DB 연결처럼 한 번 만들어 모든 사용자가 함께 쓰는 것을 둡니다. 오른쪽 @st.cache_data 는 같은 입력이면 같은 결과가 나오는 계산(파일 읽기, API 의 보기 목록)을 저장해 둡니다. 여기서 쿠폰 도장이 세션 상태, 볶아 둔 원두가 캐시입니다.

셋 견주기

쓰는 것누구의 것남는 동안예
st.session_state사용자(탭) 하나그 탭을 닫을 때까지대화 기록, 입력 이력
@st.cache_resource모든 사용자서버를 끌 때까지모델, 임베딩 모델, DB 연결
@st.cache_data모든 사용자정한 시간(ttl)이나 서버를 끌 때까지표 읽기, API 의 보기 목록

캐시가 아끼는 시간

Python
import functools, time

def load_model():
    time.sleep(0.2)                       # 무거운 모델 불러오기라고 치자
    return "모델"

@functools.lru_cache                      # 파이썬의 캐시. st.cache_resource 와 생각이 같다
def load_model_cached():
    return load_model()

for f in (load_model, load_model_cached):
    t = time.perf_counter()
    for _ in range(5):                    # 다시 실행 다섯 번
        f()
    print(f.__name__, f"{time.perf_counter() - t:.2f}초")

코드에 나오는 함수

코드쉬운 뜻
import functools, timefunctools 는 함수(function)를 다루는 도구(tools) 모음, time 은 시간을 다루는 모듈이다
time.sleep(0.2)0.2초 동안 멈춘다. sleep 은 "잠자다"라는 뜻이다. 무거운 모델 불러오기를 흉내 낸다
@functools.lru_cacheLRU(Least Recently Used, 가장 오래 안 쓴 것부터 버림) 캐시다. 같은 인자로 다시 부르면 저장해 둔 결과를 바로 돌려준다
time.perf_counter()performance counter(성능 측정용 시계)다. 두 번 읽어 뺀 값이 걸린 시간(초)이다
f.__name__함수의 이름 글자다. 어느 함수의 시간인지 찍으려고 쓴다
f"{...:.2f}초"소수점 아래 두 자리까지 찍는다. f 는 fixed-point(고정 소수점)다

이 코드가 뜻하는 것

카페의 원두 볶기에 견줄 수 있습니다. 손님이 올 때마다 원두를 새로 볶으면 다섯 손님에 다섯 번 볶습니다. 아침에 한 번 볶아 두면 나머지 손님은 볶아 둔 원두를 바로 씁니다. 여기서 원두 볶기가 load_model(), 볶아 둔 원두가 캐시입니다.

줄하는 일쉽게 말하면
def load_model():0.2초 걸리는 일을 한다원두를 한 번 볶는 데 0.2초
@functools.lru_cache 와 load_model_cached처음 한 번만 load_model() 을 부르고 결과를 저장한다아침에 한 번 볶아 둔다
for _ in range(5): f()다섯 번 부른다손님 다섯 명
print(f.__name__, ...)다섯 번에 걸린 시간을 찍는다하루 동안 볶는 데 쓴 시간

결과는 캐시가 없을 때 1.00초(0.2초 × 5), 캐시가 있을 때 0.20초(처음 한 번)입니다. Streamlit 은 위젯을 건드릴 때마다 다시 실행하므로, 모델 불러오기에 @st.cache_resource 를 붙이면 이만큼 아낍니다.

작게 줄여서 눈으로 보기

캐시는 인자가 같을 때만 저장한 결과를 씁니다. API 주소를 인자로 받아 보기 목록을 묻는 함수로 봅니다. 주소와 보기 목록은 설명을 위해 지어낸 것이고, 실제로 API 를 부르지는 않습니다.

Python
import functools

@functools.lru_cache
def read_options(api_url):
    print("  API 에 보기 목록을 묻는다:", api_url)   # 처음 묻는 주소일 때만 찍힌다
    return ["서울", "경기", "인천", "지방"]

for url in ["http://api:8013", "http://api:8013", "http://api:8014", "http://api:8013"]:
    read_options(url)
print(read_options.cache_info())
출력
  API 에 보기 목록을 묻는다: http://api:8013
  API 에 보기 목록을 묻는다: http://api:8014
CacheInfo(hits=2, misses=2, maxsize=128, currsize=2)
결과읽는 법
"묻는다" 두 줄네 번 불렀지만 함수 몸통은 처음 보는 주소 두 개에서만 실행됐다
hits=2hit(맞힘)는 저장한 결과를 그대로 쓴 횟수다. 8013 을 두 번 더 불렀을 때다
misses=2miss(놓침)는 저장한 것이 없어 새로 계산한 횟수다
maxsize=128, currsize=2최대 128가지까지 저장하고, 지금은 두 가지를 저장하고 있다

cache_info() 는 캐시가 몇 번 맞혔는지 알려 주는 함수입니다. @st.cache_data 도 이처럼 인자가 같으면 저장한 결과를 돌려주므로, 인자가 바뀌면 다시 계산합니다.

주의할 점

  • cache_resource 로 둔 것은 모든 사용자가 함께 씁니다. 사용자마다 달라야 하는 것(로그인 정보, 대화 기록)을 넣으면 다른 사람에게 보입니다.
  • 모델 파일을 바꿨는데 화면이 옛 모델을 쓰면 캐시 때문입니다. 서버를 다시 띄우거나 캐시를 지웁니다.
← 이전 절14장 · 위젯과 배치다음 절 →14장 · 채팅 화면