모델에게 유효한 JSON을 요청하지 마세요 — 잘못된 토큰을 삭제하고, 모든 잘못된 로짓(logit)을 -∞로 강제하면 구조적으로 유효해집니다
요약
모델이 생성하는 토큰의 로짓(logit)을 직접 제어하여 구조적으로 유효한 JSON을 생성하는 '제약된 디코딩(Constrained decoding)' 기술을 설명합니다. 재시도 방식의 비효율성을 해결하고, 토크나이저 정렬 문제를 오토마톤을 통해 최적화하는 방법을 다룹니다.
핵심 포인트
- 재시도 방식 대신 로짓 마스킹을 통한 제약된 디코딩 사용 권장
- 토크나이저 정렬 문제를 해결하기 위해 문법을 오토마톤으로 컴파일하여 처리
- vLLM, Outlines, xgrammar 등 로짓 제어가 가능한 라이브러리 활용 필요
- 지나치게 엄격한 문법 제약은 모델의 생성 품질을 저하시킬 수 있음
모델에게 JSON을 요청하면 대부분은 따릅니다. 하지만
이들을 하나로 엮으세요: 로짓 (logits)을 가져오고, 현재 상태에 따라 마스킹 (masking)하고, 살아남은 토큰 중 최적의 것을 선택하여, 이를 방출(emit)하고, 머신을 전진시킵니다. 매 단계마다 오직 유효한 토큰만을 방출할 수 있기 때문에, 완성된 문자열은 첫 시도에 바로 파싱 (parsing)됩니다. 검증(validation)이나 재시도(retry)가 필요 없습니다.
function generate(){
...
이것이 검증 후 재시도 (validate-and-retry) 방식(자유롭게 생성하고, 스키마 (schema)에 따라 확인한 뒤, 실패하면 다시 요청하는 방식)과의 차이점입니다. 재시도 방식은 제공자(provider)에 구애받지 않으며 — 모든 일반 텍스트 API에서 작동하지만 — 호출을 낭비하고, 루프에 빠질 수 있으며, 성공을 결코 _보장_하지 못합니다. 제약된 디코딩 (Constrained decoding)은 재시도 없이 첫 시도에 유효하지만, 생성 후에 검증하는 대신 모델이 글을 쓰는 동안 제약을 가하기 때문에 로짓 (logit) 접근 권한이 필요합니다.
진짜 난관: 토크나이저 정렬 (tokenizer alignment)
제 데모 어휘 (vocab)는 깔끔한 단어들이었습니다. 실제 문법 (grammar)은 문자 (characters) 단위로 이루어지지만, 모델은 문법 규칙과 일치하지 않는 서브워드 토큰 (sub-word tokens)을 방출합니다. 따라서 "어떤 토큰이 유효한가"라는 질문은 실제로는 "어떤 토큰이 부분 문자열의 유효한 연속 (continuation) 인가"를 의미하며, 이는 어휘의 트라이 (trie) 구조에 대한 접두사 확인 (prefix check)입니다. 이를 나이브하게 처리하면 (매 단계마다 모든 토큰을 다시 스캔하면) 느려집니다. 상용 라이브러리들은 문법을 토큰 수준의 오토마톤 (automaton)으로 한 번 컴파일한 다음, 단계당 거의 일정한 시간 내에 마스킹을 수행합니다.
로짓 (logits)이 필요하기 때문에, 제약된 디코딩은 디코딩을 제어할 수 있는 곳에서 작동합니다 — 로컬 런타임 (llama.cpp GBNF), 추론 서버 (vLLM), 그리고 Outlines, xgrammar, llguidance와 같은 라이브러리, 또는 제공자의 JSON/문법 모드 등이 이에 해당합니다. 한 가지 주의할 점은, 지나치게 엄격한 문법은 분포를 왜곡하고 품질을 저하시킬 수 있다는 것입니다. 모델이 막다른 길에서 빠져나올 수 없기 때문입니다. 따라서 형태 (shape) 는 제약하되 내용은 자유롭게 유지하세요. 제약/비제약 토글을 전환하며, 비제약 모델이 쓰레기 값으로 흘러가는 동안 제약된 모델은 유효성을 유지하는 것을 확인해 보세요:
https://dev48v.infy.uk/ai/days/day50-constrained-decoding.html
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기