vLLM 요약이 너무 느렸다 — 배치와 멀티모델 큐 튜닝
수천 건의 문서를 로컬·사내 LLM으로 요약하며 처리량을 끌어올린 기록. 모델을 함께 묶지 않는 독립 큐, 워커·claim·flush 다이얼 맞추기, max_tokens의 함정, 그리고 걸린 시간을 로그로 드러내기까지 정리합니다.
원문 미러가 준비됐으니(지난 글), 이제 이걸 LLM으로 요약해 위키로 만들 차례였습니다. 문제는 양이었습니다. 수천 건을 요약해야 했고, 처음 돌렸을 때 속도가 답이 안 나왔습니다. 이 글은 그 처리량을 끌어올린 기록입니다.
수천 건을 요약해야 했다
요약 작업(ingest)은 이런 모양입니다.
synced/의 원문 문서를 하나씩 읽는다- 사내/로컬 LLM에 "이 문서를 사서처럼 요약하고, 관련 문서와 연결해 달라"고 요청한다
- 결과를
wiki/에 쓴다
건당 몇 초씩만 잡아도 수천 건이면 몇 시간입니다. 게다가 로컬 GPU에 올린 모델 하나로 순차 처리하면 처리량이 처참했습니다.
첫 병목: 한 모델로는 답이 없다
가장 먼저 눈에 띈 건 로컬 모델의 처리량이었습니다. 워크스테이션 GPU(RTX A6000급) 한 장에 올린 모델 하나로는 초당 몇 건을 넘기기 어려웠습니다.
다행히 쓸 수 있는 모델이 더 있었습니다. 사내 게이트웨이로 서빙되는 오픈 모델 몇 종이 놀고 있었죠. 여러 모델을 동시에 요약에 투입하면 처리량을 배로 늘릴 수 있었습니다.
여기서 첫 설계 실수가 나옵니다.
모델을 함께 묶지 말자 — 독립 큐
처음엔 두 모델을 한 배치로 묶어서 돌렸습니다. 32건을 뽑아 절반씩 두 모델에 나눠 주고, 둘 다 끝나면 다음 32건을 뽑는 식이었죠.
문제는 모델마다 속도가 다르다는 것이었습니다. 빠른 모델이 자기 몫을 끝내도, 느린 모델이 끝날 때까지 놀면서 기다려야 했습니다. 배치의 속도는 늘 가장 느린 모델에 묶였습니다.
그래서 구조를 바꿨습니다. 공용 큐 하나를 두고, 각 모델이 끝나는 대로 알아서 다음 걸 꺼내 가게 했습니다.
이러면 빠른 모델은 계속 일합니다. 느린 모델은 느린 만큼만 가져갑니다. 서로를 기다리지 않으니 전체 처리량 = 두 모델의 합에 가까워집니다. 배치를 억지로 맞추던 때보다 눈에 띄게 빨라졌습니다.
워커·claim·flush 다이얼 맞추기
큐 구조를 잡고 나선 세 개의 손잡이를 조정했습니다.
- 모델당 워커 수: 한 모델에 동시에 몇 개의 요청을 태울지. 늘리면 GPU가 더 바빠지지만, 너무 높이면 큐가 밀립니다. 모델당 4로 안착했습니다.
- claim 크기: 워커가 한 번에 큐에서 몇 건을 집어올지. 너무 작으면 잠금 경합이, 너무 크면 편중이 생깁니다. 32로 잡았습니다.
- flush 주기: 요약 결과를 얼마나 자주 디스크에 반영할지. 30초마다 모아서 씁니다. 매건 쓰면 I/O가 병목이고, 너무 몰아 쓰면 중간에 죽었을 때 손실이 큽니다.
ingest 시작: 모델 2종 · 모델당 워커 4 · claim 32 · flush 30초숫자 자체보다 중요한 건, 이 값들을 언제든 바꿀 수 있게 밖으로 빼 둔 것이었습니다. 하드웨어나 모델이 바뀌면 다시 맞춰야 하니까요.
max_tokens의 함정
한번은 요약이 비정상적으로 길게 나와서 느려진 적이 있었습니다. 원인은 max_tokens였습니다.
이건 응답의 최대 길이를 토큰 단위로 제한하는 값입니다. 너무 크게 잡으면 모델이 필요 이상으로 장황하게 늘어놓고, 그만큼 생성 시간도 늘어납니다. 요약은 "짧게"가 목적이니, max_tokens를 적절히 조여서 출력 길이와 속도를 함께 잡았습니다.
보이지 않으면 못 고친다 — 처리시간 로그
튜닝하면서 가장 답답했던 건 "지금 빠른 건지 느린 건지 감이 안 온다"는 것이었습니다. 그래서 걸린 시간을 로그에 드러냈습니다.
ingest 완료: 8개 · 3.7초 · 2.18개/초건수·소요 시간·초당 처리량을 매 플러시마다 찍으니, 손잡이를 돌릴 때마다 효과가 숫자로 보였습니다. 로그를 읽기 좋게 정리한 것도 이때입니다 — 흔한 라이브러리 로그 대신, 타임스탬프·레벨·메시지가 정렬된 형태로 바꿔서 한눈에 흐름을 읽을 수 있게 했습니다.
결과
- 모델을 묶지 말고 독립 큐로 — 서로 기다리지 않게.
- 워커·claim·flush는 밖으로 빼서 상황에 맞게 조정.
max_tokens로 출력 길이와 속도를 함께 관리.- 걸린 시간을 로그로 드러내야 튜닝이 가능하다.
이제 위키가 채워졌습니다. 다음은 이 위키를 실제로 써먹을 차례입니다. 다음 글에서는 위키 데이터를 바탕으로 매일 아침 스크럼을 보내는 슬랙봇을 만듭니다.