AI Sparkup

최신 AI 쉽게 깊게 따라잡기⚡

LLM이 딴소리 못 하게 만드는 법, 제약 디코딩의 원리

“마크다운 없이 유효한 JSON만 출력해줘.” 개발자라면 한 번쯤 프롬프트 끝에 이런 문장을 덧붙여본 적 있을 겁니다. 그런데 아무리 정중하게, 아무리 여러 번 강조해도 모델은 가끔 코드블록을 씌우거나 따옴표를 빠뜨립니다.

사진 출처: KDnuggets

프롬프트로 형식을 “부탁”하는 방식이 근본적으로 확률적이기 때문입니다. KDnuggets가 소개한 “제약 디코딩(constraint decoding)”은 이 문제를 다른 방식으로 풉니다. 부탁하는 대신, 애초에 스키마를 벗어난 답을 낼 수 없게 만드는 겁니다.

출처: An Introductory Guide to Practical Constraint Decoding – KDnuggets

왜 프롬프트로 부탁하는 것만으론 부족한가

일반적인 LLM 생성 과정은 일종의 “믿음의 도약”입니다. 프롬프트를 던지고, 모델이 원하는 형식으로 답하길 기대하는 것. 하지만 모델이 다음에 뽑을 토큰을 정할 때마다, 어휘 전체를 후보로 놓고 확률을 계산하죠. 그 후보 안에는 스키마를 어기는 토큰도 얼마든지 섞여 있을 수 있습니다. “제발 JSON만”이라는 지시는 그 확률을 낮출 뿐, 완전히 없애지는 못하는 겁니다.

스키마를 벗어난 토큰 자체를 지워버린다

제약 디코딩은 접근 자체가 다릅니다. 추론이 시작되기 전에, 목표 스키마(파이썬의 Pydantic 모델 같은 것)를 유한 상태 기계(finite state machine)로 미리 컴파일해두는 것. 이 상태 기계는 지금까지 생성된 텍스트를 기준으로, 다음에 올 수 있는 토큰 목록을 정확히 알고 있습니다.

모델이 다음 토큰을 뽑는 매 순간, 이 목록이 일종의 “화이트리스트”로 작동합니다. 목록에 없는 토큰의 점수(로짓)는 전부 마이너스 무한대로 깎이죠. 모델이 아무리 확률적으로 그 토큰을 선호하더라도, 뽑힐 가능성 자체가 사라지는 것.

이후 과정은 평소와 같습니다. 살아남은 후보들 안에서 온도(temperature)나 top-p 같은 설정에 따라 하나를 고르죠. 결국 스키마 준수가 “그럴 확률이 높다”가 아니라 “그것 말고는 뽑을 수 없다”는 층위로 바뀌는 겁니다.

어휘 전체를 매번 검사하면 느려지지 않을까

토큰 수만 개짜리 어휘를 매번 이런 식으로 걸러내면 속도가 크게 떨어질 법도 합니다. 그런데 실제로는 그렇지 않죠. 모델의 어휘 자체는 고정돼 있으니, 사용자가 프롬프트를 입력하기 전에 상태 기계를 미리 계산해둘 수 있는 것입니다. 이 덕분에 매 토큰마다 어휘 전체를 새로 뒤질 필요가 없고, 지연 시간도 크게 늘지 않습니다.

현재 이 방식의 사실상 표준으로 꼽히는 라이브러리가 outlines입니다. Pydantic 모델이나 JSON 스키마, 정규식을 그대로 넘기면 사전 학습된 모델을 감싸 그 틀 안에서만 답하도록 제약합니다.

100% 보장과 정직성은 다른 문제

제약 디코딩의 가장 큰 장점은 문법 정확성을 100% 보장한다는 데 있습니다. 코드에서 파싱 실패를 처리할 필요가 없어지는 것. 형식을 보여주기 위한 few-shot 예시도 프롬프트에 넣을 필요가 없어 토큰도 절약되죠. 10억 개 파라미터 정도의 작은 모델도, 이 방식만 있으면 JSON 생성에서만큼은 실패하지 않는 도구로 바뀝니다.

다만 이 확실성에는 대가가 따릅니다. 모델이 “이건 답할 수 없습니다”라고 말해야 하는 상황에서도, 스키마가 정수를 요구하면 어쨌든 숫자 하나를 지어내 채워 넣죠. 스키마를 지키는 것과 정직한 것이, 여기서는 서로 다른 목표가 되는 셈입니다.

새 스키마를 처음 적용할 때는 상태 기계를 만드느라 몇 초간 멈칫하는 지연도 있습니다. 두 번째 실행부터는 이 부담이 사라지죠. 첫 실행에서만 감수하면 되는 비용인 것입니다.

참고자료: outlines (GitHub)


AI Sparkup 구독하기

최신 게시물 요약과 더 심층적인 정보를 이메일로 받아 보세요! (무료)

Comments

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다