본문 바로가기
C.W.K.
Stream
Lesson 05 of 05 · published

가중치는 소스가 아냐

~10 min · model-weights, cache, prewarm, packaging

Level 0불씨 없음
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"git 히스토리 속 1기가바이트 모델 가중치는 딱 한 번 저지르는 실수야."

가중치는 git 밖에 살아

온디바이스 STT 모델은 수백 메가에서 기가바이트까지 가. 소스가 아니라 데이터고, repo 안에 있으면 안 돼. 한 번 커밋하면 모든 클론, 모든 백업, 히스토리를 훑는 모든 작업이 그 무게를 영원히 끌고 다니거든. Firekeeper 는 모델 가중치를 프로젝트 밖 캐시 디렉터리(~/cwk-models/whisper/ 아래)에 두고, 처음 쓸 때 받아 오고, git 은 아예 추적을 안 해. repo 에는 모델을 관리하는 코드가 들어가. 모델 자체는 런타임 에셋이고.

첫 실행 세금

Core ML Whisper 모델은 처음 돌 때 그 기계에 맞춰 컴파일을 해. 손 안 쓰면 딱 멈춘 것처럼 보이는, 한 번뿐인 몇 초짜리 비용이야. 습관 두 개면 해결돼. 첫째, 프리웜. 첫 단축키를 누를 때가 아니라 앱을 켤 때 모델 로드랑 컴파일을 시작해서, 유저가 실제로 받아쓸 즈음엔 모델이 데워져 있게 하는 거야. 둘째, 진행 표시. 모델 다운로드도 첫 실행 컴파일도 눈에 보이고 솔직한 진행 표시가 필요해. 몇 퍼센트 받았는지, '모델 준비 중' 인지. 앱이 멈춘 것처럼 보이는 일이 없게.

실행          -> prewarm(): 백그라운드에서 모델 로드/컴파일 시작
첫 받아쓰기   -> 모델 이미 데워짐 -> 낮은 지연

모델 없음     -> 오버레이에 퍼센트 진행률로 다운로드
첫 컴파일     -> '모델 준비 중...' 상태, 얼어붙은 UI 아님

나쁜 기본값 마이그레이션

진짜 있었던 디테일 하나 더. Firekeeper 는 한때 tiny 모델을 기본으로 썼어. 빠른 대신 약한 모델이지. 기본이 large-v3-turbo 로 바뀌었을 때, 기존 유저 설정에는 예전 선택이 그대로 저장돼 있었어. 마이그레이션은 외과 수술처럼 해야 해. 낡은 기본값만 한 번 올리고, 유저가 일부러 고른 모델은 그대로 두는 거야. 규칙은 '낡은 기본값은 올리고, 명시적 선택은 존중한다'. 싹 다 덮어쓰면 파워 유저가 일부러 내린 선택을 짓밟게 돼.

코드는 배포하고, 가중치는 받아 와. 소스는 git 에 있어야 하고, 수백 메가짜리 모델 파일은 앱이 받아서 관리하는 런타임 캐시에 있어야 해. 이 둘을 섞으면 repo 가 영영 부풀고, 버전 히스토리가 언제든 갈아끼워야 할 산출물에 묶여 버려. 첫 커밋부터 경계를 깨끗하게 지켜. 나중엔 고치기가 거의 불가능하니까.
UI 없는 첫 실행 컴파일은 크래시로 읽혀. Core ML 이 컴파일하는 몇 초의 침묵은 유저 눈엔 멈춘 거랑 구별이 안 돼. 그러면 강제 종료해 버려. 실행할 때 프리웜하고 '준비 중' 이라고 솔직하게 보여줘. 무슨 일이 벌어지는지 말해 주면 '느리네' 가 되고, 안 말해 주면 '고장 났네' 가 돼.

Code

실행 때 프리웜, 그리고 유저 선택은 안 건드리고 낡은 기본값만 올리기·swift
// 1) 첫 단축키가 첫 컴파일이 안 되게 프리웜.
func applicationDidFinishLaunching() {
    Task { await stt.prewarm() }   // 백그라운드에서 로드 + 첫 실행 컴파일
}

// 2) 옛 'tiny' 기본값을 한 번 올리고, 의도적 선택은 유지.
func migrate(_ settings: inout Settings) {
    if settings.sttModel == .tinyLegacyDefault, !settings.userPickedModel {
        settings.sttModel = .largeV3Turbo   // 낡은 기본값 업그레이드
    }
    // 유저가 명시적으로 고른 모델은 절대 안 덮어써.
}

External links

Exercise

모델을 받아서 쓰는 앱을 아무거나 잡고, 뭐가 repo 에 들어가고 뭐가 런타임 캐시에 들어가는지 나눠 적어봐. 그다음 첫 실행 컴파일을 (a) 진행 UI 없이 겪을 때랑 (b) 프리웜에 진행 표시까지 있을 때, 유저가 각각 뭘 경험하는지 써봐. 어느 쪽이 '앱 고장 났어요' 신고를 부를까?
Hint
repo 에는 모델을 받고 캐시하고 프리웜하고 실행하는 코드가 들어가. 캐시에는 가중치 자체가 들어가고. 진행 UI 가 없으면 몇 초 조용히 컴파일하는 게 멈춤이나 크래시로 신고돼. 프리웜하고 '준비 중' 이라고 솔직히 띄우면 똑같은 몇 초가 정상적인 시작으로 읽히고. 바이트는 똑같아. 말해 줬느냐 아니냐만 다르지.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.