TT Lab
블로그

한국어

시작하기
블로그

블로그

구조화 출력: 토큰마다 「쓸 수 없는 글자」를 가리는 비용

상태: 초안 (2026-10-27), 실측. 8GB급 노트북 GPU(Ada 세대) 한 장, vLLM 0.31.0(xgrammar 0.2.7, llguidance 1.7.6), Qwen3-Reranker-0.6B 로 2026-10-27 에 쟀습니다. 공식 문서는 0.32.0 기준으로 확인했고, 구조화 출력 문서는 0.31.0 과 같았습니다. 2026-10-27 추가. 대화용 Qwen3-0.6B·Qwen3-4B(AWQ)와 vLLM 0.32.0(xgrammar 0.2.8)으로 정답이 있는 추출 과제를 잰 결과를 3-1절에 더했습니다. 0.31.0 수치는 그대로 두었습니다. 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 확인하지 못한 것은 「미확인」으로 표시합니다.

목차

1. 원리: 다음 토큰 후보에서 문법 밖을 지운다

모델은 걸음마다 어휘 전체(이 모델은 151,669개)에 대한 점수를 내고, 그중 하나를 고릅니다. 구조화 출력은 고르기 직전에 한 단계를 끼웁니다. 지금까지 쓴 글을 문법 기계(유한 상태 기계나 푸시다운 오토마타)에 넣어 「지금 올 수 있는 토큰」의 집합을 구하고, 나머지 토큰의 점수를 음의 무한대로 덮습니다. 이 집합을 비트마스크로 만들어 GPU 의 점수 텐서에 한 번에 적용합니다.

지금까지: {"priority": "
문법상 다음에 올 수 있는 것: l(ow), m(edium), h(igh) 로 시작하는 토큰만
나머지 151,000여 토큰 → 점수 -inf

그래서 모델이 아무리 엉뚱해도 결과는 문법을 지킵니다. 대신 두 가지 비용이 생깁니다.

  1. 문법을 만드는 비용: JSON 스키마나 정규식을 토큰 단위의 기계로 바꾸는 일입니다. 어휘가 15만 개라 토큰마다 「이 토큰이 이 상태에서 허용되는가」를 미리 계산해 두는 것이 무겁습니다. 같은 스키마는 캐시합니다.
  2. 걸음마다 마스크를 만드는 비용: 요청마다 상태를 한 칸 옮기고 마스크를 만들어 GPU 로 보냅니다.

XGrammar 논문(Dong 외, MLSys 2025)은 문맥과 무관하게 판정되는 토큰을 미리 계산해 두고, 문법 계산을 GPU 실행과 겹쳐 돌려 기존 방식보다 최대 100배 빠르고, 서빙 엔진과 함께 쓰면 오버헤드가 거의 없다고 보고합니다(초록 기준, 조건은 초록에 없음). vLLM 은 xgrammar 와 llguidance(guidance 백엔드) 둘을 쓰고, 기본값 auto 는 요청을 보고 고릅니다.

2. 쓰는 법과 이름의 변화

OpenAI 형식의 response_format 과 vLLM 전용 structured_outputs 두 길이 있습니다.

원하는 것 요청에 넣는 것
JSON 스키마 response_format: {"type": "json_schema", "json_schema": {...}} 또는 structured_outputs: {"json": 스키마}
아무 JSON 객체 response_format: {"type": "json_object"}
보기 중 하나 structured_outputs: {"choice": ["low", "medium", "high"]}
정규식 structured_outputs: {"regex": "[A-Z]{3}-[0-9]{4}"}
문맥 자유 문법(EBNF) structured_outputs: {"grammar": "..."}
태그 안만 JSON structured_outputs: {"structural_tag": ...}(5편의 strict 도구 호출이 이 방식)

판이 바뀌며 이름이 바뀌었습니다. 예전 guided_json, guided_regex, guided_choice, guided_grammar 와 요청별 guided_decoding_backend 는 0.12.0 에서 지워졌고, 지금은 structured_outputs 하나로 모였습니다. 백엔드는 서버에서 --structured-outputs-config.backend 로 정하며, 요청마다 백엔드를 고르는 기능은 V1 에서 없어졌습니다. 정규식 문법은 백엔드마다 달라 xgrammar·guidance 는 Rust 식, lm-format-enforcer 는 파이썬 re 식입니다. 0.32.0 부터는 64단계보다 깊게 중첩된 JSON 스키마를 400 으로 거절합니다(릴리스 노트 기준, 미실측).

3. 직접 재 보기

방법

결과 1. 강제하면 틀린 것은 잘린 것뿐이었다

동시 16, 측정 세 묶음(48요청)의 합계입니다.

변형 auto guidance xgrammar 응답 예
강제 없음 0/48 0/48 0/48 No.
json_object 0/48 0/48 0/48 {"status": "no", "data": []}
json_schema 47/48 46/48 46/48 {"name": "", "priority": "high", "tokens": 0, "tags": []}
choice 48/48 48/48 48/48 low
regex 48/48 48/48 48/48 IOW-1139

결과 2. 비용: 첫 요청, 그리고 토큰마다 조금

변형 백엔드 새 스키마 첫 요청 TTFT(동시 1, 예열 묶음) 그 뒤 TTFT 중앙 토큰 간격 중앙(동시 1) 토큰 간격 중앙(동시 16)
강제 없음 xgrammar 서버 43.6ms 14.1ms 5.9ms 6.3ms
json_schema xgrammar 445.7ms 14.2ms 6.3ms 7.4ms
json_schema guidance 974.6ms 14.3ms 6.0ms 7.3ms
json_schema auto 508.0ms 14.3ms 6.2ms 7.5ms
regex xgrammar 15.0ms 13.7ms 6.5ms 7.8ms
choice xgrammar 22.3ms 14.2ms 6.4ms 7.7ms

겪은 일: 첫 표본 추출 묶음의 몇 초

측정 도중 서버 로그에 이런 경고가 찍혔습니다.

WARNING [jit_monitor.py:140] Triton kernel JIT compilation during inference: _topk_topp_kernel.
This causes a latency spike; consider extending warmup to cover this shape/config.

새 서버에서 처음으로 온도 표본 추출(top-k·top-p) 요청 묶음이 들어오면 그 커널을 그 자리에서 컴파일했고, 그 묶음의 TTFT 가 3.9초(guidance 서버, 동시 1)에서 11초(guidance 서버, 동시 16)까지 늘었습니다. 탐욕 디코딩만 하던 동안에는 이 경고가 없었습니다. 컴파일은 한 번으로 끝나지도 않았습니다. 다른 서버에서 온도 1.0 요청을 동시 1, 8, 32 로 차례로 보냈더니 동시성을 처음 바꿀 때마다 그 묶음이 다시 느려졌습니다(256토큰 생성에 동시 1 은 5.4초 대 이후 1.5초, 동시 8 은 7.4초 대 1.9초, 동시 32 는 8.4초 대 3.2초). 묶음 크기마다 커널을 새로 만드는 것으로 보입니다. 모델의 generation_config.json 이 기본 표본 추출 값(temperature 0.6, top_k 20, top_p 0.95)을 덮어쓴다는 경고도 함께 찍혔습니다. 운영에서는 서버를 띄운 뒤 실제와 같은 표본 추출 설정의 요청으로 한 번 데우고 나서 트래픽을 받아야 합니다. 0.32.0 릴리스 노트에는 표본 추출기 예열 컴파일을 줄였다는 항목이 있습니다(미실측).

3-1. 2026-10-27 재측정: 정답이 있는 추출 과제

앞의 시험은 「올바른 JSON 인가」만 봤습니다. 이번에는 값이 맞는지도 봤습니다.

모델 변형 바로 읽힘 스키마 준수 값이 맞음 응답 시간 중앙
Qwen3-0.6B 강제 없음 0/18 0/18 18/18 254.9ms
Qwen3-0.6B json_object 18/18 18/18 18/18 248.0ms
Qwen3-0.6B json_schema 18/18 18/18 18/18 273.5ms
Qwen3-0.6B FP8 강제 없음 6/18 6/18 18/18 187.0ms
Qwen3-0.6B GPTQ 8비트 강제 없음 0/18 0/18 18/18 185.6ms
위 세 모델 json_object·json_schema 54/54 54/54 54/54
Qwen3-4B AWQ 4비트 강제 없음 18/18 18/18 18/18 476.0ms
Qwen3-4B AWQ 4비트 json_schema 18/18 18/18 18/18 507.5ms

4. 운영에서 가져갈 것

5. 확인하지 못한 것

참고

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다