← Lab 전체 데모

#001 · Constrained Decoding · 2026-07

JSON을 "부탁"하지 말고 "강제"하기

LLM에게 "JSON으로 줘"라고 부탁하면 대체로 잘 주지만, 가끔 스키마를 어긴다. 디코딩 단계에서 문법을 강제하면(제약 디코딩) 구조 위반이 원천적으로 불가능해진다. 같은 로컬 27B 모델, 같은 입력 15개로 세 가지 방식을 직접 측정했다. 아래 결과는 전부 실제 실행 기록이다.

📄 논문

Efficient Guided Generation for Large Language Models (Willard & Louf, 2023 — Outlines). 정규문법/JSON 스키마를 유한상태기계(FSM)로 컴파일해 두고, 매 토큰마다 "문법상 가능한 토큰"만 남기고 나머지 로짓을 마스킹한다. 인덱싱을 미리 해두면 토큰당 오버헤드가 거의 0. llama.cpp의 GBNF grammar/json_schema 기능이 같은 계열 구현이다.

⚙️ 코드

Qwen3.6-27B(Q4_K_M, llama.cpp)를 홈랩 GPU에서 서빙. 한국어 추출 태스크 3종 × 입력 5개에 대해 ① 대충 프롬프트 ② 스키마 프롬프트 ③ 문법 강제(json_schema) 세 모드로 생성(temperature 0.7), 출력 전체가 JSON으로 파싱되고 스키마 검증을 통과하는지 채점. 측정 스크립트 · 원본 데이터(JSON)

💡 배운 것

측정 데이터 로딩 중…

결과 한눈에

태스크별 스키마 검증 통과 수 (5개 입력 중)

통과 기준(strict): 출력 전체가 JSON으로 파싱 + JSON Schema 검증 통과. 코드펜스·설명 한 줄만 붙어도 실패.

표로 보기

직접 만져보기 — 실제 출력 비교

태스크와 입력을 고르면 세 모드의 실제 모델 출력 원문과 채점 결과를 나란히 보여줍니다.

🐍 채점 코드 직접 실행 — 고쳐서 다시 채점해보기

아래 파이썬 코드가 당신의 브라우저 안(WebAssembly·Pyodide)에서 실행됩니다 — 서버로는 아무것도 전송되지 않습니다. 45개 실제 모델 출력(runs)과 스키마(tasks)가 미리 로드되어 있고, 채점 코드를 마음대로 수정해 재실행할 수 있습니다. 예: LENIENT = True로 바꾸면? review 스키마 enum에 'neutral'을 추가하면?

여기에 실행 결과가 표시됩니다.

어떻게 강제하나 — 요청 한 줄 차이

// llama.cpp 서버 (OpenAI 호환 API) — json_schema 필드 하나면 끝
POST /v1/chat/completions
{
  "model": "Qwen3.6-27B-Q4_K_M.gguf",
  "messages": [...같은 프롬프트...],
  "temperature": 0.7,
  "json_schema": {            // ← 이 필드가 켜지면 서버가 스키마를 GBNF 문법으로
    "type": "object",         //    컴파일하고, 매 토큰마다 문법 위반 토큰을 마스킹
    "properties": { ... },
    "required": [...],
    "additionalProperties": false
  }
}
정직한 각주
  • 표본이 작다(태스크 3 × 입력 5 × 모드 3 = 45회 생성, temperature 0.7 단일 시드). 경향을 보는 데모지 벤치마크가 아니다.
  • 문법 강제는 구조를 보장할 뿐 내용은 보장하지 않는다 — "낼모레"를 엉뚱한 날짜로 계산해도 스키마는 통과한다.
  • 단일 모델(Qwen3.6-27B Q4_K_M) 결과다. 더 작은 모델일수록 프롬프트만으로는 실패율이 커지는 경향이 알려져 있다.
  • 측정 스크립트와 원본 출력 45개 전부를 이 페이지에서 그대로 공개한다.