배치 처리로 처리량을 올렸는데 왜 P99 꼬리 지연은 나빠지나
← 전체 목차 · 이전: MoE는 일부 전문가만 활성화하는데 왜 배포 시 메모리를 많이 먹나 · 다음: 추측 디코딩은 왜 빨라지면서 답은 바뀌지 않나
이 문제의 분수령은 뒷부분에 있다 — 모델만 보지 말고 이번 업무 동작이 무엇인지 먼저 보라. 온라인 상담이라면 사용자가 첫 글자를 기다리고 있으므로 지연이 하드 제약이며, 이 전제가 이후 모든 취사선택을 결정한다.
| 내용 |
|---|
| 표면 질문 | 동적 배치 처리와 꼬리 지연의 관계 |
| 실제 쟁점 | 배치 처리는 요청을 함께 실행해 하드웨어 이용률을 높이지만, 긴 요청과 짧은 요청이 섞이면 서로 기다리게 되어 단일 사용자 꼬리 지연이 악화된다 |
이용률 향상 ≠ 단일 사용자 체감 향상. 이것이 먼저 던져야 할 한 문장이다.
핵심 답변과 조건 분해
| 수단 | 작용 |
|---|
| 입출력 길이별 버킷 분리 | 긴 요청과 짧은 요청이 같은 배치에 들어가지 않게 한다 |
| 최대 대기 윈도우 설정 | 배치를 채우는 데 상한을 두어, 배치를 모으려다 짧은 요청을 죽이는 것을 막는다 |
| 실시간 / 오프라인 작업의 큐 분리 | 오프라인 작업이 온라인 요청의 배치를 잠식하지 못하게 한다 |
| 처리량·TTFT·P95·P99를 동시에 볼 것 | 단일 지표는 사람을 속인다. 넷을 함께 봐야 경계가 생긴다 |
지표의 함정: 긴 꼬리가 평균값에 가려진다
구체적 예시: 수십 개 token만 필요한 짧은 질문이, 긴 문서 요약 요청 하나에 끌려 같은 배치에 들어간다.
가장 밟기 쉬운 함정: 초당 생성 token 수만 추구하면 사용자 체감이 가장 나쁜 긴 꼬리가 평균 지표 안에 숨어버린다. 지표가 보기 좋은 것과 체감이 형편없는 것은 동시에 성립할 수 있다.
원인 규명 방법: 3단 계측 + 모니터링 지표
성능 원인 규명의 첫 동작은 대기(queueing), Prefill, Decode를 나눠서 계측하는 것이다.
| 단계 | 무엇을 반영하나 | 주된 영향 |
|---|
| 대기 | 스케줄링과 용량 | 요청이 배치 밖에서 얼마나 기다렸나 |
| Prefill | 입력 전체 처리 | 입력이 길수록 첫 글자 지연에 영향 |
| Decode | token 단위 생성 | 출력이 길수록 총 소요 시간에 영향 |
최소한 함께 기록해야 할 것: TTFT, token당 지연, 입출력 token, 배치 크기, 동시성, 메모리 수위.
경고: 하나의 평균 응답 시간으로 서로 다른 단계를 섞지 말 것.
단계별 최적화: Prefill은 입력을 줄이고, Decode는 출력을 통제한다
| 단계 | 느릴 때의 처방 |
|---|
| Prefill이 느림 | 무관한 컨텍스트 감소 · 온디맨드 검색 · 안정적 접두사 캐싱 |
| Decode가 느림 | 그다음에 출력 상한 · 연속 배치 처리(continuous batching) · 양자화 또는 추측 디코딩 고려 |
순서에 주의할 것 — 먼저 Prefill 측의 뺄셈을 하고, 그다음에 Decode 측 수단을 건드린다.
배치를 키우면 처리량은 오르지만 짧은 요청이 긴 요청을 기다리게 되므로, 결론은 다시 실시간·오프라인 큐 격리와 길이별 버킷 분리로 돌아온다.
출시 목표: 지연 경계를 먼저 지키고, 경계 안에서 처리량을 올린다
출시 시에는 P95, P99와 처리량 목표를 동시에 지켜야 하며, 초당 token 수를 위해 단일 사용자 체감을 희생해서는 안 된다.
엔지니어링 최적화에 절대적인 "빠를수록 좋다"는 없다. 온라인 요청은 먼저 지연 경계를 지키고, 그 경계 안에서 처리량을 올려야 한다.
검증 가능한 결과로 수렴시키기
0. 먼저 고정
모델 설정 / 입력 길이 / 데이터 버전
1. 대조 1조
방안 수정 전후 각각 1회 실행
2. 네 가지 변화 비교
정확성 / 경계 샘플 / 지연 / 메모리 또는 token 비용
이상 시 처리 조건(증거 부족 / 상태 불명 / 지표 이탈)에는 명확한 추가 질문 / 다운그레이드 / 롤백 / 사람 인계 조건이 있어야 한다. 이렇게 답하면 방안이 왜 유효한지도, 어떤 상황에서 자동 실행을 계속해서는 안 되는지도 함께 설명하게 된다.
핵심 통찰
- 처리량과 꼬리 지연은 방향이 반대인 두 목표이며, 배치 처리는 전자의 수익을 후자의 비용으로 전가하는 행위다 — 배치를 키우면 하드웨어 이용률이 오르지만, 대가는 요청이 배치가 채워지기를 기다리고 같은 배치에서 가장 느린 것을 기다리는 것이다. "배치 처리가 처리량을 높인다"만 답하면 절반을 빠뜨린 것이다.
- P99 악화의 근본 원인은 "배치 처리"가 아니라 "장단 혼재"다 — 같은 배치의 모든 요청이 가장 긴 것에 맞춰 완료 시각이 정렬되므로, 수십 token짜리 짧은 질문이 긴 문서 요약에 붙잡힌다. 따라서 해법은 배치 처리를 버리는 것이 아니라 비슷한 길이의 요청을 같은 배치에 넣는 것이다.
- 평균 지표는 본질적으로 긴 꼬리를 가린다. 그리고 긴 꼬리가 바로 최악의 사용자 체감이다 — 초당 생성 token 수가 높을수록 시스템이 대량의 긴 요청에 봉사하고 있다는 뜻일 때가 많고, 짧은 요청의 체감은 오히려 나빠진다. 처리량은 반드시 TTFT·P95·P99와 함께 봐야 한다.
- 지연은 단계별로 귀인해야 하며, 그렇지 않으면 최적화는 추측이다 — 대기·Prefill·Decode 세 단계의 성인은 완전히 다르다. 하나의 평균 응답 시간으로 셋을 섞는 것은 서로 다른 세 병에 같은 약을 먹이는 것과 같다.
- 최적화 수단은 단계와 일대일로 대응해야 한다 — Prefill이 느리면 입력 측에서 뺄셈(무관 컨텍스트 제거, 온디맨드 검색, 안정적 접두사 캐싱)을 하고, Decode가 느려야 비로소 출력 측(출력 상한, 연속 배치, 양자화, 추측 디코딩) 차례다. 순서가 뒤바뀌면 느리지 않은 구간에 복잡도를 더하는 셈이다.
- 온라인 시스템의 올바른 목적 함수는 "지연 경계 내 처리량 최대화"이지 "처리량 최대화"가 아니다 — P95/P99를 넘을 수 없는 제약으로 놓고 그 안에서 처리량을 올린다. "빠를수록 좋다"와는 전혀 다른 엔지니어링 가치관이며, 이 문제의 채점 포인트다.
- 대조가 없는 최적화는 최적화가 아니다 — 모델 설정·입력 길이·데이터 버전을 고정하고 주요 변수를 하나만 바꾼 뒤 정확성·경계 샘플·지연·메모리 또는 token 비용을 비교한다. 대조가 빠지면 어떤 성능 결론도 반증할 수 없다.
엔지니어링 실전 Tips
- 먼저 버킷을 나누고, 그다음에 배치 크기를 논할 것: 입출력 길이별 버킷 분리가 배치 처리의 수익과 대가를 분리하는 최소 변경이다. 버킷을 나누지 않은 채 배치 크기를 조정하는 것은 처리량과 P99 사이를 오가는 것에 불과하다.
- 최대 대기 윈도우가 짧은 요청의 생명줄이다: 배치 모으기에는 반드시 타임아웃 상한이 있어야 한다. 그렇지 않으면 한산한 시간대의 짧은 요청이 꽉 찬 배치를 기다리느라 헛되이 더 기다린다.
- 실시간과 오프라인 큐를 물리적으로 격리할 것: 오프라인 작업(긴 문서 요약 등)은 본래 긴 요청이며, 온라인 큐에 넣는 것은 모든 짧은 요청에 긴 꼬리 지뢰를 심는 것과 같다.
- Prefill 최적화는 뺄셈부터: 무관 컨텍스트 감소, 온디맨드 검색, 안정적 접두사 캐싱. 셋 다 모델이나 추론 엔진을 바꿀 필요가 없어 가성비가 가장 높은 등급이다.
- Decode에 출력 상한을 둘 것: Decode의 소요 시간은 출력 token 수에 선형으로 결정되므로, 출력 상한 통제가 양자화 도입보다 빠르게 효과를 내고 롤백도 쉽다.
- 원인을 규명할 때 중간 결과를 보존할 것: 입력·설정·모델 버전·중간 결과를 남겨야 "처음으로 예상에서 벗어난 노드"를 특정할 수 있다.
- 수정은 반드시 회귀 집합에 넣고 인접 작업을 확인할 것: 한 지점만 좋아지고 다른 시나리오가 퇴화하는 것이 이런 스케줄링 계열 변경의 가장 흔한 2차 사고다.