본문으로 건너뛰기

LLM 추론 부하의 숨은 실패를 측정하는 법

LLM 추론 API의 429·timeout·빈 200 응답을 책임별로 분리해야 부하 성능을 제대로 측정할 수 있습니다.

이 요약은 AI가 원문을 분석해 생성했습니다. 정확한 내용은 원문 기준으로 확인하세요.

TL;DR

Hosted inference API의 실패는 500이나 연결 오류처럼 눈에 띄는 오류만이 아니라 timeout으로 결과를 알 수 없는 censored request와 HTTP 200 뒤에 빈 응답 또는 설명 없는 짧은 출력이 오는 silent drop까지 포함합니다. 글의 초기 부하 실험에서는 300건을 3초 동안 보냈고 28건만 완료됐지만, 272건의 429가 일일 요청 한도 때문인지 분당 토큰 한도 때문인지 용량 부족 때문인지 응답만으로 판별할 수 없었습니다. OpenRouter, vLLM, Artificial Analysis 등의 공개 지표는 일부 실패를 제외하거나 성공 응답만으로 지연시간을 계산해 실패 원인과 책임 주체를 분리하지 못합니다. 저자는 모든 예정 요청을 분모에 넣고 고정된 발송 일정으로 측정하는 Percentes를 구축했으며, 클라이언트 자체가 병목이 된 경우 결과를 공개하지 않는 검증 조건을 적용합니다.

섹션별 상세

01
초기 실험은 6개 worker가 응답을 받은 뒤 다음 요청을 보내는 closed-loop 방식으로 300건을 3초 동안 전송했고, 28건 완료와 272건의 429를 기록했습니다. 직후 헤더 확인 요청은 일일 요청 한도 7,000건 중 6,971건과 분당 토큰 한도 6,000개 중 53개가 남았음을 반환했으므로, 429가 일일 한도 소진에서 비롯되지 않았다는 점은 확인됐습니다. 분당 요청 한도는 과거 단일 요청 실험에서 31번째 요청이 거부된 결과로 30건 수준이라고 추정했지만, 거부된 시도가 이 한도에 포함되는지는 공개되지 않아 어느 제한이 작동했는지는 끝내 판별되지 않았습니다.
02
추론 API 요청의 종착점은 정상 완료와 명시적 오류만으로 나뉘지 않습니다. 클라이언트 timeout으로 서버 결과를 알 수 없는 censored request는 최소 지연시간만 남기고, HTTP 200과 [DONE]을 받았지만 콘텐츠가 없는 silent drop은 성공으로 위장하며, 요청한 1,000토큰보다 훨씬 짧은 출력이 finish_reason 없이 끝나는 경우도 같은 문제를 만듭니다. max_tokens에 따른 정상 종료나 finish_reason이 있는 정책 거부와 달리, 부하 때문에 빈 응답 또는 설명 없는 조기 종료가 발생하면 현재 지표만으로는 사용자가 받은 실패를 확인하기 어렵습니다.
03
429는 고객의 quota 초과와 제공자의 용량 부족을 함께 나타낼 수 있어 제공자별 비교에서 그대로 사용하기 어렵습니다. Amazon Bedrock과 OpenAI는 용량 부족을 503으로, Anthropic은 529로, Groq는 498로 보내지만 DeepInfra와 일부 Vertex 및 Fireworks 경로는 429에 quota와 capacity 의미를 함께 담습니다. OpenRouter는 429를 uptime 계산에서 제외하고 제공자에게 부하 시 조기 429를 반환하라고 권고하므로 고객이 만든 거부와 제공자 용량 부족을 한 지표에서 모두 지워 버릴 수 있습니다.
04
공개 측정 도구마다 실패를 다루는 경계도 다릅니다. OpenRouter의 uptime은 서버 오류와 mid-stream error, 오류 finish_reason을 포함하지만 빈 finish_reason의 200 응답은 제외하고, vLLM benchmark는 성공 완료만으로 percentile을 계산하면서 빈 텍스트 event가 포함된 스트림을 성공으로 통과시킬 수 있습니다. Artificial Analysis의 TTFT와 median 지표는 토큰을 반환하지 않은 요청의 처리 방식을 밝히지 않으며, System Load Test의 response rate도 한 토큰만 반환하면 성공으로 세므로 요청 예산보다 짧은 출력과 완전한 응답을 구분하지 못합니다.
05
저자가 구축한 Percentes는 모든 예정 요청을 completed, errored, censored로 분류하고 timeout을 분모에서 제외하지 않는 Open-Loop 측정을 사용합니다. 요청별 지연시간은 실제 완료 시각이 아니라 예정된 발송 시각부터 계산하며, send-skew와 미발송 요청, CPU, garbage collection pause가 기준을 넘으면 측정 자체를 무효로 처리합니다. 빈 콘텐츠로 [DONE]에 도달한 스트림은 오류로 잡지만 설명 없는 truncation을 자연스러운 짧은 답변과 구분할 검증된 classifier가 아직 없어 해당 비율은 공개하지 않으며, 현재는 local Kubernetes mock server에서만 acceptance criteria를 통과한 상태입니다.

용어 해설

Coordinated Omission
부하 생성기가 서버 응답을 기다리느라 새 요청을 보내지 않는 측정 편향입니다. 느린 요청이 발생한 동안의 관측 자체가 빠지므로 지연시간 분포에서 최악의 구간이 사라지고, 실제 서비스가 악화되어도 p99가 개선된 것처럼 나타날 수 있습니다.
Open-Loop 부하 테스트(Open-Loop Load Testing)
응답 완료 여부와 무관하게 사전에 정한 일정에 따라 요청을 발송하는 부하 측정 방식입니다. 실제 발송 시각이 예정 시각에서 얼마나 벗어났는지 관리하면서 서버 지연이 요청 생성률까지 낮추지 않게 하므로, 처리 지연과 처리량 변화를 분리할 수 있습니다.
검열된 요청(Censored Request)
클라이언트가 timeout에 도달해 포기했지만 서버에서 실제로 어떤 결과가 발생했는지 확인할 수 없는 요청입니다. 관측 가능한 사실은 처리 시간이 timeout 이상이었다는 점뿐이며, 이를 지연시간 백분위에 포함하지 않으면 가장 느린 사례가 통계에서 빠집니다.
무음 삭제(Silent Drop)
HTTP 200과 정상적인 스트림 종료 표식을 받았지만 사용할 콘텐츠가 없거나 요청한 토큰 예산보다 훨씬 짧은 출력만 받은 상태입니다. 상태 코드와 finish_reason이 모두 정상처럼 보일 수 있어 가동률과 지연시간 지표에서 성공으로 집계되며, 부하 상황의 실제 실패를 숨깁니다.
Send Skew
요청의 예정 발송 시각과 실제 발송 시각 사이의 차이입니다. 부하 측정 클라이언트의 CPU, 스케줄링, garbage collection 때문에 요청이 늦게 나가면 서버가 아니라 클라이언트가 병목이 될 수 있으므로, Percentes는 이 차이를 검증 조건으로 사용합니다.
finish_reason
생성 스트림이 끝난 이유를 나타내는 응답 필드입니다. max_tokens에 도달했는지, 정책 거부인지, 오류인지 구분하는 데 쓰이지만, 이 글이 문제로 삼는 일부 빈 응답과 설명 없는 조기 종료에서는 유용한 값이 제공되지 않습니다.

기술

  • OpenRouter
  • vLLM
  • LLM-Stats
  • llmstatus.io
  • Artificial Analysis
  • wrk2
  • vegeta
  • k6
  • Percentes
  • Kubernetes
  • OpenAI-compatible server

활용 사례

  • Hosted inference API의 제공자별 신뢰성 측정
  • 429 quota와 capacity 오류 분리
  • LLM 부하 테스트의 timeout 및 빈 응답 집계
  • 클라이언트 병목을 배제한 latency benchmark
  • Streaming 응답의 silent drop 탐지
AI 분석 전체 내용 보기

AI 요약 · 북마크 · 개인 피드 설정 — 무료

출처 · 인용 안내

원문 발행 2026. 09. 02.수집 2026. 09. 02.출처 타입 RSS

인용 시 "요약 출처: AI Trends (aitrends.kr)"를 표기하고, 사실 확인은 원문 보기 기준으로 진행해 주세요. 자세한 기준은 운영 정책을 참고해 주세요.