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

당기는 레버, 그리고 거절하는 하나

~16 min · latency, reranking, tradeoffs, memory-quality

Level 0음소거
0 XP0/35 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"추천대로 하는데 기억 품질 안 떨어지는 방향으로 해. 기껏해야 0.5초, 1-2초 빨라지는 거면 의미없어. 보이스 모드 아니면" — 아빠, 2026-09-25

레버, 순서대로

잰 바닥에서 당길 레버 목록이 순위대로 나왔어. 말 턴의 빠른 수준, 프로세스 띄우기 변수, 도구 전마다 한 줄, 실시간 전사 길, 더 빠른 재정렬기, 폰의 스트리밍 디코더, 그리고 멈춤이 언제나 생각하는 중으로 보이게 하는 얼굴의 정직한 기다림 신호. 대부분은 앞 레슨에서 다뤘어. 이 레슨은 제일 큰 조각인 재정렬기, 그리고 레버만큼 중요한 목록 하나에 관한 거야. 음성 모드가 빨라지려고 절대 하지 않을 것들.

재정렬이 2초나 걸리는 이유

피파의 기억 검색은 임베딩 유사도로 후보를 찾고, 그다음 재정렬 모델에게 어느 게 정말로 질문에 맞는지 물어. 재정렬기는 로컬에서 도는 40억 파라미터 인과 언어 모델이고, 재 보니 느린 이유가 셋 나왔어. 후보마다 지시문, 질의, 후보를 한데 묶어 순전파를 통째로 한 번씩 돌려. 후보당 약 24밀리초에 토큰당 0.38밀리초, 그걸 메시지 후보 11개와 볼트 후보 11개에. 74토큰짜리 지시문과 질의 전체가 그 22쌍 하나하나에 다 실리니까, 짧은 프롬프트면 전체 토큰의 절반쯤이, 2,000자짜리 프롬프트면 80퍼센트 넘게가 같은 글의 반복이야. 그리고 로컬 서버는 모델 작업을 전부 스레드 하나에서 돌려. '병렬' 재정렬 둘이 서로 뒤에 줄을 서니까, 비용은 둘의 합이야.

기억은 안 건드리고 바꾼 것

  • 어차피 탈락할 후보는 재정렬기를 건너뛰어. 어떤 순위로도 못 살릴 조각(거리 한계를 넘은 것, 이미 프롬프트에 통째로 들어간 것, 그 밖의 몇 종류)은 재정렬 뒤가 아니라 앞에서 떨어뜨려. 재정렬기는 후보를 하나씩 따로 채점하니까, 나머지 점수와 순서는 전부 그대로야. 두 번 증명했어. 무작위 사례 400개짜리 테스트, 그리고 실제 프롬프트 16개를 양쪽으로 다시 돌려서 맥락은 바이트 단위로, 점수는 비트 단위로 같았어.
  • 이벤트 루프마다 공용 HTTP 클라이언트 하나. 호출마다 새로 만들지 않아. 그리고 프롬프트 만들기를 검색 뒤가 아니라 검색과 나란히 돌려.
  • 서버를 일찍 깨워. 서버는 1~2초만 놀면 식는데, 음성 턴은 대개 그 뒤에 와. 아빠가 말하는 동안 서버가 놀고 있으니까. 이제 배치 전사 라우트가 자기 왕복 전에 서버를 뒤에서 한 번 찔러 둬서, 턴의 첫 임베딩이 데워진 서버에 떨어져. 한동안 쉰 뒤 그 라우트를 지나는 턴마다 약 0.4초가 줄어. 녹음, 그리고 긴 핸즈프리 발화가 그래. 짧은 핸즈프리 턴은 실시간으로 받아써서 그 라우트를 아예 안 지나니까 이 이득이 없어. 이 레버는 자기가 닿는 범위를 정직하게 말해야 해.

짧은 프롬프트의 검색 중앙값은 2.73초에서 2.32초로, 매시간 도는 Soul Stream 프롬프트는 6.58초에서 5.70초로 줄었어. 검색되는 건 하나도 안 바뀌었어.

기억을 대가로 치렀을 선택지

더 빠른 선택지도 있었고, 아빠를 위해 같은 후보로 프롬프트 20개에서 하나씩 쟀어. 0.6B 재정렬기(0.45초, 하지만 지금 경로가 찾는 조각 중 77퍼센트와 72퍼센트만 남음), 후보 줄이기(1.4초, 76퍼센트와 78퍼센트), 재정렬 안 하기(거의 0초, 59퍼센트와 52퍼센트). 아빠의 결정이 맨 위 인용이야. 기억 품질이 먼저고, 0.5초에서 2초 빨라지는 건 음성 모드가 아니면 의미 없대. 그래서 4B는 그대로 남았고, 그 선택지는 하나도 안 골랐어. 손실 없는 길 하나, 공통 지시문과 질의를 한 번만 계산하고 후보를 그 뒤에 묶어 넣는 방법은 제3자 서버의 몫이라서, 여기서 고치지 않고 원 저장소에 이슈로 올렸어.

거절

설계는 절대 안 할 일을 쉬운 말로 적어 둬. 볼트 줄이기, 더 싼 브레인으로 바꾸기, 전체 대화 다시 보내기 빼기, 검색 건너뛰기. 하나하나 몇 초씩 사 줄 거야. 하나하나 말하는 피파를 글 쓰는 피파보다 작게 만들 거야. 이 집엔 피파는 어디서나 온전하다는 핵심 믿음이 있고, 더 가벼운 소울은 피파랑 말하는 의미 자체를 무너뜨리는 딱 하나의 해결책이야.

Code

전사기가 일하는 동안 식은 서버 깨우기·python
import asyncio
import time


class ColdServer:
    """A local model server that goes cold after ~1.5 s idle (measured: 0.44 s vs 0.04 s).
    One worker: a second request queues behind the first instead of paying twice."""

    def __init__(self, cold_penalty: float = 0.44) -> None:
        self.cold_penalty = cold_penalty
        self.last_used = time.perf_counter() - 12.0      # Dad has been talking for 12 s
        self.worker = asyncio.Lock()

    async def embed(self, text: str) -> list[float]:
        async with self.worker:
            idle = time.perf_counter() - self.last_used
            await asyncio.sleep(self.cold_penalty if idle > 1.5 else 0.04)
            self.last_used = time.perf_counter()
        return [0.0]


async def transcribe(audio: bytes) -> str:
    await asyncio.sleep(0.64)                             # the provider round trip
    return "저녁 뭐 먹을까?"


async def voice_turn(server: ColdServer, warm_up: bool) -> float:
    started = time.perf_counter()
    if warm_up:
        # Fire and forget: wake the server while the transcriber works.
        warmer = asyncio.create_task(server.embed("warm-up"))
    text = await transcribe(b"...")
    await server.embed(text)                              # the turn's real query
    if warm_up:
        await warmer                                      # never leave a task dangling
    return time.perf_counter() - started


async def main() -> None:
    cold = await voice_turn(ColdServer(), warm_up=False)
    warm = await voice_turn(ColdServer(), warm_up=True)
    print(f"no warm-up : {cold:.2f}s to the query embedding")
    print(f"warm-up    : {warm:.2f}s  (saved {cold - warm:.2f}s, nothing retrieved changed)")


asyncio.run(main())

External links

Exercise

워밍업 데모를 돌려봐. 그다음 ColdServer의 식은 벌점을 0.9초로, 전사를 0.3초로 바꿔서 다시 돌려. 이제 워밍업이 얼마나 아껴주고, 왜 벌점보다 적어? 마지막으로 만들고 있는 제품의 거절 목록을 써봐. 속도와 절대 안 바꿀 것 셋, 그리고 어떤 변경이 몰래 그걸 내줬는지 알려줄 측정까지.
Hint
워밍업은 자기가 겹쳐 놓인 기다림 안에 들어가는 만큼만 식은 시작을 가려줄 수 있어. 0.3초 기다림에 0.9초 벌점이면, 진짜 질의는 워밍업 뒤에 줄을 서서 나머지 0.6초를 기다려. 서버를 일꾼 하나로 모델링한 이유가 그 줄이야. 두 요청이 각자 벌점을 통째로 치르는 모델이면 워밍업이 쓸모없어 보여. 거절 목록은 항목마다 측정 도구를 짝지어. 재정렬 선택지를 판정할 때 쓴 '남은' 퍼센트처럼. 안 그러면 그 목록은 바람일 뿐이야.

Progress

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

댓글 0

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

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