
BabelDOC에 커스텀 번역 모델을 붙이고 나서 처음 돌린 논문이 267문단짜리 100페이지 PDF였다. 결과가 나올 때까지 40분. 번역 품질 이전에 속도부터 문제였다.
병목을 파보니 원인은 셋이었다. 기본 QPS가 4로 너무 낮았고, worker 수도 부족했으며, 빈 문단이나 수식 블록에서 번역이 실패하면 그냥 멈춰버렸다. 이걸 동시성 튜닝, 재시도 전략, 캐시 제어 세 방향으로 잡았다.
vLLM은 continuous batching을 지원한다. 요청이 들어올 때마다 배치에 끼워넣는 방식이라 동시 요청이 많아도 GPU 활용률이 높게 유지된다. 전통적인 웹 서버처럼 동시성을 조심스럽게 올릴 필요가 없다는 뜻이다.
그래서 과감하게 QPS를 4에서 64로, worker pool을 32개로 올렸다.
parser.add_argument("--qps", type=int, default=64)
parser.add_argument("--pool-max-workers", type=int, default=32)
parser.add_argument("--no-cache", action="store_true", help="Ignore translation cache")
parser.add_argument("--verbose", "-v", action="store_true")다만 무턱대고 올리면 안 된다. QPS를 높이면 vLLM의 KV cache 사용량도 같이 뛴다. --gpu-memory-utilization 0.8과 --max-model-len 4096을 함께 조절하지 않으면 OOM이 난다. 처음에 이걸 몰라서 GPU 메모리가 터지는 바람에 한참 헤맸다.
동시성을 올리고 나니 속도 문제는 해결됐는데 품질 문제가 남았다. 빈 문단이나 수식만 있는 블록을 번역하려다 실패하는 경우가 꽤 있었다. temperature 0.0에서 빈 응답이 나오면 같은 조건으로 재시도해봐야 또 빈 응답이다. deterministic한 생성이니까.
전통적인 API 재시도는 대기 시간을 늘리는 exponential backoff를 쓴다. 근데 LLM한테는 대기 시간이 문제가 아니라 생성 결과 자체가 문제다. 그래서 temperature를 재시도 변수로 쓰기로 했다.
재시도마다 temperature를 0.0 -> 0.2 -> 0.4 -> 0.6 -> 0.8로 올린다. 최대 5회까지. temperature가 올라가면 생성의 무작위성이 높아져서 이전과 다른 결과가 나올 확률이 올라간다. 빈 응답이 나왔던 문단도 temperature를 좀 올리면 정상적인 번역이 나오는 경우가 많았다.
이건 Exponential Backoff의 LLM 버전이라고 볼 수 있다. 대기 시간 대신 모델 파라미터를 조절하는 방식. Temperature Escalation이라고 부르기로 했다.
재시도 전에 아예 불필요한 번역 요청을 줄이는 게 먼저다. 공백만 있는 문단이나 수식 기호만 있는 블록은 번역 전에 걸러낸다. API 호출 자체를 안 하는 게 가장 빠른 최적화니까.
캐시도 손봤다. SQLite 기반 Cache-Aside 패턴으로 동일 문단의 재번역을 막는다. 캐시 키에 모델명, temperature, 프롬프트를 포함시켜서 조건이 바뀌면 자동으로 새로 번역한다.
원래 --debug 플래그 하나에 PDF 디버그 오버레이, 캐시 무시, 상세 로깅이 전부 묶여 있었다. 캐시만 끄고 싶은데 PDF에 오버레이가 덕지덕지 붙는 상황이 반복되니 결국 플래그를 분리했다. --no-cache와 --verbose를 독립 옵션으로 뺀 건 작지만 확실한 개선이었다.
if args.verbose:
logging.basicConfig(
level=logging.INFO,
handlers=[
logging.StreamHandler(),
logging.FileHandler("glm_translate_debug.log", mode="w"),
],
)
logging.getLogger("babeldoc.translator").setLevel(logging.DEBUG)이번 작업에서 느낀 건 LLM 기반 시스템의 성능 튜닝이 전통적인 웹 서비스와 꽤 다르다는 점이다.
웹 서비스에서는 동시성을 올리면 DB 커넥션이나 메모리가 병목이 된다. 하지만 vLLM 같은 추론 서버는 continuous batching 덕분에 동시성을 공격적으로 올려도 괜찮다. 대신 KV cache 메모리라는 전혀 다른 지점에서 병목이 온다.
재시도 전략도 마찬가지다. 전통적인 backoff는 서버 부하를 줄이려고 대기 시간을 늘리지만 LLM에서는 같은 입력에 같은 결과만 반복된다. temperature를 올려서 생성 자체를 바꿔야 한다.
정리하면 이렇다. 동시성과 모델 파라미터를 함께 고려해야 하고, 기존 패턴을 LLM 특성에 맞게 변형해야 한다. 40분 걸리던 번역이 수 분으로 줄었으니 방향은 맞았다고 본다.