에이전트가 같은 말을 반복하기 시작했을 때
처음에는 요청이 먹통이 된 줄 알았습니다. 툴 콜(tool call)이 나와야 할 자리에 모델이 max_tokens 제한까지 줄기차게 같은 문장만 반복하거나, 툴 콜에 필요한 JSON 형식을 끝내 완성하지 못한 채 의미 없는 잡담만 늘어놓고 있었거든요. 어느 쪽이든 토큰 예산만 낭비하고 가끔은 타임아웃까지 발생하며 에이전트 루프 전체를 말아먹기 일쑤였습니다.
당시 저는 B2B로 납품하는 비즈니스 인텔리전스(BI) AI를 만들고, 그 위에 에이전트를 얹는 일을 하고 있었습니다. 고객사 데이터가 외부로 나갈 수 없어서 클라우드 API는 쓸 수가 없었습니다. OpenAI도 Anthropic도 안 됐죠. 모델을 우리 하드웨어에 직접 띄워 서빙하거나, 아니면 기능을 포기하거나, 둘 중 하나였습니다. 이렇게 직접 서빙하면 클라우드 API가 알아서 처리해 주던 양자화(quantization), 서빙 프레임워크, 추측 디코딩(speculative decoding)을 전부 손수 다뤄야 합니다. 설정값 하나하나가 다 제 몫이고, 그만큼 제가 망가뜨릴 여지도 컸습니다.
백엔드 모델은 vLLM 위에서 돌아가는 Gemma 4 (google/gemma-4-26B-A4B-it)였고, 처리량(throughput)을 높이기 위해 MTP/추측 디코딩을 켠 상태에서 NVFP4로 양자화(처음엔 AWQ INT4로 시작했습니다)되어 있었습니다. 이 스택 중 어디가 문제인지 파악하는 데 정말 오랜 시간이 걸렸습니다.
가끔 호출이 제대로 돌아와도 JSON이 깨져서 파싱이 안 되는 경우가 있었습니다. 하지만 그건 저렴하게 해결할 수 있는 문제였죠. 탐지도 쉽고 재시도도 간단하니까요. 진짜 골치 아픈 건 타임아웃이나 max_tokens를 꽉 채우는 경우였습니다. 토큰을 전부 소진하고도 아무것도 건지지 못한 응답에서는 살려낼 게 하나도 없거든요. 사용자는 하염없이 기다리다가 쓸모없는 결과물을 받게 되는데, 제품 관점에서는 최악의 실패 유형인 셈이죠. 게다가 제가 만든 건 멀티 에이전트 시스템이라, 하위 에이전트 하나가 이런 루프에 빠지면 다른 모든 에이전트가 깔끔하게 일을 마쳐도 전체 실행 시간(latency)까지 같이 늘어졌습니다. 그래프 내의 어디든 에이전트 하나가 맛이 가면 전체 시스템이 망가진 것처럼 느껴지기 충분했죠.
제일 헷갈렸던 건 벤치마크 점수는 멀쩡했다는 점입니다. 표준 평가에서는 성능 하락이 전혀 없었습니다. 숫자만 보면 추론도, 코딩도, 요약도 다 잘했고, 종합 점수에는 아무 문제가 없었죠. 하지만 에이전트가 굴러가는 방식은 벤치마크와 다릅니다. 정해진 형식의 출력을 빡빡하게 반복해서 뽑아내야 하는데, 이건 평가가 재는 자유로운 글쓰기와는 전혀 다른 종류의 압박입니다. 왜 벤치마크가 이걸 못 잡는지는 뒤에서 다시 다룹니다.
일단 최악의 상황은 임시방편으로 막고 배포는 했습니다. 하지만 그 패치가 왜 먹혔는지, 혹은 더 근본적인 문제가 다음 모델로 이어질지는 알 수 없어서 영 찝찝하더군요. 그래서 아예 밑바닥부터 파보기로 했습니다. 이 글은 그 탐구 과정에 대한 기록입니다.
반복은 왜 생기나 — 신경망 텍스트 퇴화(Neural Text Degeneration)
2020년, Holtzman 등은 “신경망 텍스트 퇴화의 기묘한 사례(The Curious Case of Neural Text Degeneration)” (ICLR 2020)라는 논문을 발표했습니다. 이 논문은 초기 GPT-2를 사용해 본 사람이라면 누구나 한 번쯤 겪어봤을 실패 모드, 즉 생성된 텍스트가 반복과 진부함, 그리고 횡설수설로 빠져드는 현상에 이름을 붙이고 진단했죠. 신경망 텍스트 퇴화(neural text degeneration) 라는 명칭은 그대로 굳어졌고, 그 이후 수년간 왜 이런 일이 발생하는지에 대한 연구가 이어졌습니다.
퇴화는 진부하고 일반적인 텍스트, 앞뒤가 맞지 않는 흐름, 그리고 노골적인 반복 등 여러 형태로 나타납니다. 에이전트 입장에서 반복은 그중에서도 최악입니다. 진부하거나 조금 이상한 텍스트라도 결국 문장은 끝나고 파싱도 가능하지만, 반복 루프에 빠지면 이전 섹션에서 다룬 ‘타임아웃으로 예산이 불타버리는’ 실패인 max_tokens에 도달할 때까지 절대 멈추지 않기 때문입니다. 사용자에게도 최악이죠. 다른 형태는 그래도 무언가를 빠르게 반환이라도 하지만, 루프에 빠지면 가장 긴 응답 시간을 기다리게 만든 뒤 결국 쓸모없는 결과물을 내놓으니까요. 느린 데다 내용까지 비어있다면, 실패치고는 이보다 더 나쁠 수 없습니다. 그래서 이 현상을 자세히 이해할 필요가 있습니다.
반복은 곧 ‘뾰족한 분포(Peaked Distribution)’
언어 모델은 토큰을 생성할 때마다 전체 어휘에 대한 확률 분포를 만듭니다. 가능한 모든 다음 토큰에 각각의 가능성을 할당하는 것이죠. 여기서 중요한 건 그 분포의 ‘모양’입니다.
평탄한 분포(flat distribution) 는 여러 그럴싸한 후보군에 확률을 고루 나누어 갖지만, 뾰족한 분포(peaked distribution) 는 거의 모든 확률을 특정 토큰 하나에 몰아넣습니다.
Flat: [the: 0.12, a: 0.10, this: 0.09, one: 0.07, ...] ← 그럴싸한 후보가 여러 개
Peaked: [the: 0.78, a: 0.06, this: 0.03, one: 0.01, ...] ← 하나가 압도함
반복은 결국 이 뾰족한 분포가 시간에 걸쳐 드러난 모습입니다. 특정 토큰이 단계마다 분포를 지배하면, 출력은 한 구절에 갇혀 빙글빙글 돌게 됩니다. 결국 진짜 질문은 ‘무엇이 분포를 저렇게 뾰족하게 만드는가’입니다. 토큰을 선택하는 전략인 디코딩(decoding)을 탓하고 싶어지겠지만, 사실 디코딩도 일정 부분 역할을 하는 것은 맞습니다(다음 섹션에서 다룹니다). 하지만 이는 전체의 작은 부분이자 후속 단계일 뿐이며, 외부에서 튜닝할 수 있는 유일한 영역일 뿐입니다. 진짜 근본 원인은 모델 내부에 있습니다.
진짜 원인은 모델
단순한 인과관계로 설명하기보다, 연구들은 몇 가지 다른 각도에서 이 문제를 바라봅니다. 이 관점들이 모두 같은 수준에 있는 것은 아니며, 어디서 어긋나는지를 파악하는 것이 억지로 끼워 맞추는 것보다 훨씬 유용합니다.
분포 수준(The distribution level). 가장 직접적인 관점은 모델이 내뱉는 확률 분포입니다. 이는 학습 단계에서 시작됩니다. Holtzman 등은 모델이 사람이 쓴 유창한 글보다 반복적인 텍스트에 더 높은 확률을 할당한다는 점을 보여주었습니다. 반복적인 문맥은 통계적으로 매우 예측 가능하기 때문에 학습 손실(loss)이 낮고, 모델은 자신도 모르게 반복을 선호하게 되는 것이죠. Xu 등(NeurIPS 2022)은 이러한 선호가 추론 과정에서 어떻게 눈덩이처럼 불어나는지 보여주었습니다. 문장이 한 번 나타날수록 모델은 그 문장을 다시 생성할 확률을 높게 평가하며, 확률이 높은 문장일수록 더 빨리 증폭됩니다. 한 번의 반복이 상승세를 만들고, 그 반복이 다음 반복의 가능성을 높이는 식이죠. 결국 분포 자체가 반복을 향해 편향되어 있고, 루프가 스스로를 강화하면서 악화되는 것입니다.
한 단계 더 깊이. 결함이 있는 분포는 사실 모델 내부에서 일어나는 일의 증상일 뿐입니다. Duan 등이 발표한 “순환적 추론(Circular Reasoning)”은 제가 가장 인상 깊게 본 관점입니다. 현재의 추론 모델들에서 붕괴는 이미 출력이 나오기 전부터 시작됩니다. 텍스트적 반복 이전에 의미적 반복이 발생합니다. 내부 상태가 먼저 특정 아이디어 주변을 맴돌기 시작하는데, 생성된 구간이 “그 자체의 재발을 위한 논리적 전제"가 되는 자기 강화적 어텐션(attention) 패턴에 갇히는 것입니다. 그러고 나서 나중에야 동일한 단어들이 텍스트로 나타나죠. 루프가 눈에 보일 때쯤이면 이미 내부 상태는 무너진 뒤입니다. 반복된 텍스트는 질병 자체가 아니라 증상인 셈이죠(이들은 CUSUM 탐지기를 사용해 텍스트가 반복되기 전, 내부 신호에서 이러한 전조 현상을 포착할 수 있다고 합니다). 이는 분포 관점과 다른 원인이 아니라, 한 층 아래에서 본 동일한 실패입니다.
모든 것을 악화시키는 다이얼: 길이. 위에서 언급한 관점들은 모두 생성 과정이 길어질수록 나빠집니다. 강화할 이전 토큰들이 많아지고, 루프가 뿌리내릴 공간이 넓어지기 때문입니다. 이는 반복 현상에만 국한되지 않습니다. Chroma의 “context rot(문맥 부패)” 연구는 18개의 최첨단 모델 전반에 걸친 더 넓은 패턴을 보여줍니다. 입력이 길어질수록 출력의 신뢰도는 불균일하게 떨어지며, 그 구체적인 파괴 방식 중 하나가 바로 우리가 겪는 반복 현상입니다. 단어 반복 과제에서 모델들은 출력 제한에 도달할 때까지 같은 토큰을 생성하는 쪽으로 표류하죠. 저자들은 정확한 메커니즘은 알 수 없지만, 긴 문맥이 확실히 상황을 악화시킨다는 점을 분명히 합니다. 우리에겐 그 정도면 충분한 정보입니다. 반복은 긴 시간을 작동하는 모든 모델이 겪는 길이 기반 퇴화의 한 단면입니다. 에이전트는 본질적으로 긴 시간 작동합니다. 매 툴 호출마다 긴 생각의 연쇄를 거치기 때문에, 바로 이 퇴화 현상이 나타나기 딱 좋은 환경에 처해 있는 것입니다.
손댈 수 없는 근본 원인
근본 원인이 가중치에 있다면, 가장 깔끔한 해결책은 가중치를 바꾸는 것, 즉 기반 모델을 재학습시켜 붕괴에 덜 취약하게 만드는 것입니다. 하지만 기성 모델(off-the-shelf model)을 사용하는 입장에서 가중치를 건드릴 수는 없죠. Gemma 4를 재학습하려면 학습 파이프라인과 데이터, 그리고 엄청난 수의 GPU가 필요합니다. 그건 모델 공급업체의 일이며, 비용도 만만치 않으니까요.
그래서 저는 그다음으로 좋은 방법을 찾아 나섰습니다. 가중치를 바꿀 수 없는 상황에서, 내부적인 붕괴 가능성을 줄이거나, 아니면 발생하더라도 피해를 최소화할 방법은 무엇일까요? 이 질문이 문제의 전체 틀을 다시 짜줍니다. 모델이 반복에 취약하다는 점을 고칠 순 없지만, 붕괴를 부채질하는 행동은 피할 수 있습니다. 어떤 장치들은 가중치가 만든 것보다 분포가 더 나빠지지 않게 막아주고(디코더, 양자화 포맷, 라우터 등), 어떤 장치들은 텍스트 생성이 퇴화했을 때 피해 범위를 억제합니다. 이 중 무엇도 편향을 완전히 치료할 수는 없지만, 최소한 운영 중인 에이전트가 한꺼번에 무너지는 상황은 막을 수 있습니다. 이어지는 글에서는 제가 찾은 방법들을 하나씩 다뤄볼 예정입니다. 가장 비용이 저렴한 디코더부터 시작해 보죠.
디코딩의 한계
출력 결과가 나빠지면 가장 먼저 손대게 되는 곳이 디코더 설정(temperature, top-p, repetition penalty)입니다. 모델이나 서빙 스택을 건드리지 않고 바꿀 수 있는 가장 저렴한 레버들이죠. 하지만 에이전트 시스템에서 이 노브들은 모두 사방이 막혀 있습니다. 반복을 억제하려고 설정을 바꾸면, 꼭 다른 무언가가 망가져 버리거든요.
(Temperature, top-p의 메커니즘과 T=0이 결정론적이라는 오해에 대해서는 이전 포스트에서 다뤘으니, 여기서는 기본 개념은 짧게 짚고 왜 이 노브들이 꽉 막혀 있는지에 집중하겠습니다.)
Temperature
Temperature는 토큰 샘플링 과정에서 소프트맥스(softmax) 직전의 로짓(logit)에 적용되는 스칼라 값입니다:
P(token_i) = exp(logit_i / T) / Σ exp(logit_j / T)
T < 1일 때는 작은 숫자로 나누게 되어 로짓 간의 차이가 증폭됩니다. 즉, 확률이 가장 높은 토큰이 더 독보적이게 되면서 분포가 뾰족해집니다. 반대로 T > 1일 때는 차이가 압축되면서 분포가 평평해집니다:
| Temperature | 분포 형태 | 위험 요소 |
|---|---|---|
| T → 0 (샘플링 극한) | 단일 토큰 급상승 | 반복 발생 / 퇴행 루프 |
| T < 1 | 날카롭고 뾰족함 | 반복 위험 증가 |
| T = 1 | 모델 원본 분포 | 기본값 |
| T > 1 | 평평하고 분산됨 | 문맥 파괴 / 환각 |
참고: Greedy decoding은 temperature 스케일링을 완전히 건너뜁니다. T→0은 이와 유사한 결과를 내기 위한 샘플링의 극한일 뿐, 같은 동작이 아닙니다.
이 노브가 바로 앞 섹션에서 언급한 ‘붕괴’를 부추기는 주범입니다. **최대화(maximization)**라는 메커니즘 때문이죠. 분포가 뾰족할수록 디코딩은 항상 가장 확률이 높은 토큰을 선택하게 되는데, 긴 문장을 생성할 때는 이것이 함정이 됩니다. 모델이 이미 뱉은 토큰들로 문맥이 채워지면, 다음에 올 확률이 가장 높은 토큰은 종종 그냥 가장 안전한 토큰이 됩니다. 앞 문맥이 이미 무게를 실어준 구절이죠. 이를 선택하면 가중치는 더 높아지고, 다음번엔 선택될 확률이 더 커집니다. 낮은 Temperature는 분포를 뾰족하게 만들어 디코딩을 이런 자기 강화 루프(self-reinforcing loop)로 직행하게 만듭니다. 반면, 더 평평한 분포는 이 루프를 벗어날 여지를 주죠.
그러니 해결책은 간단해 보입니다. Temperature를 올리는 거죠. 이는 이미 Holtzman 등이 입증한 잘 알려진 결과입니다. 하지만 그 측정은 열린 생성(open-ended generation)(이야기, 대화, 창의적 글쓰기) 환경에서 이루어졌습니다. 과연 이게 추론 에이전트에도 적용될까 싶죠? 네, 적용됩니다. DeepSeek-R1 논문(arXiv:2501.12948)에서도 긴 추론 출력에서 greedy decoding을 사용하면 반복률이 높아진다고 보고합니다. 그래서 0이 아닌 0.6의 temperature로 평가하는 것이죠. 우리가 실제 운영하는 모델에서도 똑같은 현상이 확인됩니다.
물론 한계는 있습니다. DeepSeek는 온도를 너무 높이면(~0.7 이상) 결과물이 횡설수설해진다는 점을 발견했습니다. 따라서 추론 모델에서 반복을 제어하기 위해 사용할 수 있는 구간은 “무조건 높게"가 아니라, 매우 좁은 대역입니다. 그런데 도구 호출(tool call)이 끼어들면 이 대역은 반대쪽에서 더 조여집니다.
이 압박은 에이전트가 출력해야 하는 형식에서 옵니다. 도구 호출은 자유 형식의 텍스트가 아니라, 아래와 같은 JSON 형태의 구조화된 출력이어야 파서가 받아들일 수 있습니다:
{
"tool": "search_database",
"parameters": {
"query": "Q3 revenue by region",
"limit": 10
}
}
중괄호 하나가 빠지거나, 필드가 하나 더 들어가거나, 숫자가 필요한 곳에 문자열이 들어가는 등의 작은 편차만으로도 파싱은 박살 납니다.
거의 모든 생성 과정에서 형식을 깨뜨리는 토큰들이 모델의 확률 분포 어딘가에 존재합니다. 낮은 온도에서는 분포가 뾰족해서 그런 토큰이 선택될 확률이 거의 없지만, 온도를 높이면 분포가 넓어지면서 형식을 깨뜨리는 토큰들이 뽑힐 확률이 높아집니다. 낮은 온도가 형식 오류를 줄여주긴 하지만, 이것은 확률을 이동시킬 뿐 보장해주지는 않습니다. 실제로 잘 구성된 출력을 보장하는 것은 outlines나 jsonformer 같은 도구를 활용한 제약 디코딩(grammar-guided sampling)입니다. Temperature는 그저 확률을 살짝 조절할 뿐이죠.
에이전트 시스템이 낮은 온도를 유지하는 이유가 바로 이 때문입니다. 다만, 절대적인 마법의 숫자는 없습니다. 모델마다 권장 설정이 다릅니다. 어떤 도구 사용 가이드는 아예 greedy decoding을 권장하고, 또 다른 모델들(GLM, Qwen 등)은 0과는 거리가 먼 온도 범위를 제시합니다. 여기서 중요한 건 특정 값이 아니라 방향성입니다. 형식의 안정성이 중요한 작업은 더 뾰족한 분포를 원합니다. 결과적으로 Temperature는 양쪽에서 잡아당겨지는 꼴이 됩니다:
형식 안정성 → 낮은 온도 필요 (더 뾰족한 분포)
반복 방지 → 1.0에 가까운 온도 필요
하나의 파라미터로 이 두 요구를 모두 만족시킬 수 없습니다. 반복을 피하려고 온도를 올리면 형식 안정성이 떨어지고, 형식을 보호하려고 온도를 내리면 반복 위험이 커지죠. 이 두 요구사항 때문에 Temperature는 거의 여유가 없는 좁은 대역에 갇혀 버립니다. 게다가 에이전트에게 형식 오류는 결코 용납될 수 없으므로, 반복을 방지하는 쪽으로 타협할 수도 없습니다. 첫 번째 다이얼은 사실상 고정된 셈입니다. 제가 고른 값이 아니라, 어쩔 수 없는 제약이 그렇게 묶어둔 거죠.
top-p도 답이 아니다
Temperature 말고 다른 샘플링 노브인 top-p는 어떨까요? Nucleus sampling(Holtzman et al., 2019)은 확률이 낮은 긴 꼬리를 잘라냅니다. 누적 확률이 p를 넘는 가장 작은 토큰 집합만 남기고 거기서만 샘플링하죠. 분포가 너무 평평할 때 신뢰할 수 없는 토큰을 걸러내는 데는 효과적입니다.
하지만 우리 문제는 정반대로, 분포가 너무 뾰족하다는 것입니다. T가 낮아서 상위 토큰이 이미 확률의 대부분을 차지하고 있다면, p 값과 상관없이 top-p는 greedy decoding에 가까워집니다. 만약 p = 0.9인데 상위 토큰 하나가 0.92를 차지하고 있다면, 뉴클리어스에는 토큰이 하나만 남습니다. 두 번째 토큰이 들어오기도 전에 임계값을 넘어버리기 때문이죠. 분포가 이미 붕괴한 상태에서는 top-p가 분포를 넓힐 수 없으므로, 우리가 겪는 반복 문제에는 아무런 도움이 되지 않습니다.
그렇다면 repetition penalty는?
Temperature는 너무 낮으면 루프가 돌고 너무 높으면 형식이 깨지는 좁은 밴드에 갇혔고, top-p는 아무런 해결책이 되지 못하니, 다음으로 손대게 되는 건 Repetition penalty입니다. 효과는 있지만 한계가 뚜렷합니다. 표준적인 패널티는 토큰이 등장한 횟수를 세어 선형적으로 할인하는데, 긴 추론 과정에서는 잘 작동하지 않습니다. 추론에서는 단순히 빈도 문제가 아니라 특정 토큰이 반복되는 ‘구간’을 확장하고 있는지의 문제이기 때문입니다(LZ Penalty 연구에서 상세히 다룹니다). 루프를 막으려고 패널티를 세게 주면 JSON 키나 변수명처럼 당연히 반복되어야 할 토큰들까지 억제하게 되어 구조화된 출력이 망가집니다. 적당한 중간 설정(저는 전체 스윕 후 1.05로 결정했습니다)은 있지만, 반복 안전성을 거저 얻을 수 있는 마법의 값은 없습니다.
결국 모든 디코딩 노브가 막혀 있습니다. Temperature는 형식을 망치지 않고는 올릴 수 없고, 패널티는 구조를 망치지 않고는 올릴 수 없으며, top-p는 이미 뾰족해진 분포를 어쩌지 못합니다. 이는 앞서 본 문제들의 실제 결과입니다. 근본 원인은 모델의 분포 그 자체에 있고, 디코더는 그 분포를 읽기만 할 뿐이기 때문이죠. 따라서 분포를 읽는 노브들이 모두 박혀 있다면, 질문은 이제 “무엇이 분포를 이토록 나쁘게 만드는가?“로 옮겨가야 합니다. 가장 유력한 용의자는 바로 한 층 아래, 양자화(quantization)입니다.
양자화
디코더는 틀에 갇혀 있고 모델의 편향(bias)은 가중치에 고정되어 있습니다. 그다음으로 제가 손을 댈 수 있는 건 양자화뿐이었죠. 모델을 4비트로 돌리고 있었고 4비트는 손실이 발생하니까, 당연히 가장 먼저 의심이 갔습니다. AWQ INT4에서 NVFP4로 전환했을 때 어느 정도 효과를 본 것 같기도 했고요. 하지만 파고들수록 겸손해질 수밖에 없었습니다. 공부하면 할수록 “양자화가 원인이다"라는 주장은 설득력을 잃었거든요.
무엇이고 왜 의심했나
BF16으로 된 26B 모델은 가중치만 해도 약 52GB가 필요합니다. 제가 가진 GPU에 여유 있게 올리기엔 너무 컸죠. 양자화는 각 가중치를 16비트 대신 4비트에 저장하여 약 4배 정도 크기를 줄입니다. 이는 학습 후 수행하는 압축 방식(Post-training quantization, PTQ)으로, 각 가중치를 4비트 격자 내 가장 가까운 지점으로 반올림하여 저장하는 방식입니다.
처음엔 여기서 착각을 하나 했습니다. AWQ 같은 가중치 전용 방식은 행렬 곱(matmul) 직전에 다시 BF16으로 역양자화(dequantize)를 하니까 수학적 연산 자체는 풀 정밀도로 돌아가거든요. 그런데 왜 손실이 생기느냐? 저장을 할 때 이미 손실이 발생했기 때문입니다. 격자에 맞추느라 반올림하는 과정에서 차잇값이 버려졌고, 역양자화는 그저 격자 지점을 다시 매핑하는 것뿐이라 버려진 데이터를 복구할 수는 없으니까요. (블랙웰(Blackwell) 아키텍처의 NVFP4는 여기서 더 나아가 4비트 상태로 직접 곱셈을 수행하지만, 반올림으로 손실이 발생한다는 점은 동일합니다.) 즉, 모델이 사용하는 가중치는 영구적으로 거칠어진 상태입니다. 앞서 말했듯 반복 생성 문제가 취약한 분포에서 비롯된 것이라면, 가중치를 건드리는 어떤 행위든 그 분포를 악화시킬 가능성이 충분하죠. 제가 양자화를 의심한 이유가 바로 그것입니다.
논문들은 대체로 “양자화는 괜찮다"고 한다
문제는 제가 근거를 찾아보려 할수록, 정반대를 가리키는 증거가 더 많았다는 점입니다. 평균적으로 4비트 양자화는 품질을 거의 떨어뜨리지 않습니다. NVFP4와 INT4는 표준 벤치마크에서 거의 비슷한 수준을 보여주며, 차이가 있더라도 평균값에선 드러나지 않거든요. “Bridging the Gap” (ICLR 2026) 논문을 보면 적절한 방식을 사용할 경우 INT4가 평균 정확도 면에서 NVFP4와 대등하거나 오히려 앞서기도 합니다. 즉, 마케팅 문구와 달리 실제 포맷 간의 격차는 그리 크지 않다는 거죠.
제 이론은 더 궁지에 몰렸습니다. 압축 환경에서 에이전트 성능을 직접 테스트한 Dong 등의 “Can Compressed LLMs Truly Act?” 연구 결과, 4비트 양자화를 적용해도 도구 사용 능력 저하는 1~3% 에 불과했습니다. 게다가 Liu 등이 발표한 “Quantization Hurts Reasoning?” (COLM 2025)에서는 양자화된 모델이 더 긴 출력을 생성하지 않는다고 딱 잘라 말합니다. 양자화가 반복 루프의 주범이라면 나타났어야 할 현상과는 정반대인 셈이죠. 종합하자면, 양자화 자체가 제가 겪고 있던 반복 생성과 JSON 파손의 원인이라는 명확한 학술적 근거는 없었습니다. 솔직히 말해, 관련 문헌들은 저에게 다른 곳을 찾아보라고 말하고 있었죠.
그래도 남은 작은 격차
하지만 여기서 한 가지 무시할 수 없는 결과가 있었습니다. 저희가 사내에서 사용하는 BI 에이전트 벤치마크(표준 평가셋인 MMLU가 아니라 실제 제품의 에이전트 작업 부하를 바탕으로 구성한 테스트)에서, 양자화된 모델은 양자화하지 않은 모델보다 약간 낮은 점수를 기록했습니다. 그런데 그 격차가 고르게 나타나지 않았어요. 긴 도구 호출(tool-call) 작업에서 대부분 나타났고, 그마저도 간헐적이었습니다. 대부분의 실행은 괜찮았는데, 간혹가다 와르르 무너지는 식이었죠. 이 양상은 포맷 관련 논문들이 공통으로 인정하는 한 가지 사실과 일치합니다. INT4의 평균 품질은 괜찮지만, 문제는 일관성입니다. 실패하는 경우는 드물지만, 한번 실패하면 훨씬 치명적이라는 것이죠.
‘드물지만 치명적인’ 실패 방식은 에이전트 입장에서 최악입니다. 일반적인 챗봇 대화는 한 번 치고 빠지는 방식이라 평균만 괜찮으면 그만입니다. 반면 긴 에이전트 워크플로우는 수십 번의 생성 작업이 연쇄적으로 이어지기 때문에 평균을 따질 게 아니라 그중 최악의 사례를 겪게 됩니다. 한 번의 잘못된 생성이 전체 실행을 망칠 수 있으니까요. 체인이 길어질수록 실패할 확률은 높아집니다. 그래서 제 의심은 “양자화가 모델을 망친다"에서 좀 더 구체적인 방향으로 좁혀졌습니다. 저비트 양자화, 특히 INT4는 가끔 발생하는 심각한 실패의 확률을 조금 높이는데, 긴 도구 사용 체인은 단발성 대화보다 그런 실패를 마주할 확률이 훨씬 높다는 거죠. 물론 여전히 결과가 아닌 의심일 뿐입니다. 지금도 빌드 중이라, 확실하게 결론을 지을 통제된 비교 실험은 아직 수행하지 못했거든요.
바꾼 진짜 이유는 속도
메모리와 처리량(throughput) 문제 때문에 어떻게든 양자화는 피할 수 없는 선택이었습니다. 그래서 진짜 질문은 ‘양자화를 할 것인가 말 것인가’가 아니라 ‘어떤 포맷을 쓸 것인가’였고, 변동성 데이터를 확인한 덕분에 AWQ 대신 NVFP4를 시도할 명분이 생겼습니다. 전환 후 토큰 폭주(max-token blowups) 빈도가 조금 줄어들긴 했는데, 이는 실패 분포의 꼬리가 줄어들었다는 설명과 맞아떨어집니다. 물론 증거라 부르기엔 너무 작고 노이즈가 많긴 하지만요.
하지만 솔직히 무엇이 실질적인 변화를 이끌었는지 말하자면, 품질이 아니라 속도였습니다. 블랙웰에서의 NVFP4는 체감할 정도로 빨랐고, 그게 미미한 품질 개선보다 사용자 경험 측면에선 훨씬 큰 도움이 되었습니다. 설령 생성이 길게 늘어지더라도, 더 빨리 끝내는 것이 그나마 고통을 덜어주었거든요. 품질 개선은 ‘글쎄요’ 수준이었지만, 레이턴시 개선은 실질적이고 즉각적이었습니다. 만약 저처럼 모호한 상황에서 포맷을 고민 중이라면, 이 기준을 권장합니다. 확실한 이득이 필요한 부분을 먼저 챙기세요. 품질 차이는 당장 기대하기 어려운 보너스 정도로 생각하는 게 현명합니다. (아무도 공개하지 않은 INT4 대 NVFP4 대 FP16의 품질 비교 데이터는 다음 포스팅에서 다뤄보겠습니다.)
MoE
제 모델에 특화된 또 다른 변수가 있습니다. Gemma 4 26B는 Mixture-of-Experts 모델입니다. 모든 토큰이 동일한 피드 포워드 네트워크를 통과하는 대신, 작은 라우터(Router) 가 레이어당 128개의 전문가 모두에게 점수를 매기고 상위 8개를 선정해 토큰을 전달하죠. 이 라우팅 단계는 Dense 모델에는 없는 과정이며, 양자화 과정에서 놓치기 쉬운 오작동의 원인이 될 수 있습니다.
“라우터를 양자화하면 점수가 왜곡되지 않을까?“라는 직관적인 걱정은 기우입니다. 라우터를 양자화하는 사람은 없으니까요. 전체 파라미터에서 극히 일부분일 뿐이라, vLLM이나 프로덕션용 양자화 빌드에서는 전문가 모델만 양자화하고 라우터는 그대로 풀 정밀도(Full precision)로 둡니다. 따라서 라우터 자체의 연산에는 문제가 없습니다.
더 미묘한 문제는 라우터가 잘못된 입력을 받을 수 있다는 점입니다. EAC-MoE(ACL 2025) 논문은 이 과정을 추적합니다. 어텐션과 전문가 가중치를 양자화하면 라우터에 도달하기 전의 은닉 상태(Hidden state)가 변질됩니다. 결국 양자화되지 않은 라우터라도 약간 틀어진 입력을 받으면 어떤 전문가가 선택될지가 뒤바뀔 수 있는 거죠. 논문은 이 “전문가 이동(Expert-shift)” 현상을 측정했고, 원래의 전문가 선택을 보존하는 것만으로도 손실된 품질의 상당 부분을 복구할 수 있음을 밝혀냈습니다. EAQuant(arXiv:2506.13329) 역시 “라우팅 불안정성(Routing instability)“이라는 이름으로 같은 실패 사례를 보고합니다. 두 논문 모두 라우터를 풀 정밀도로 유지하는 것만으로는 충분하지 않으며, 양자화된 입력에 맞춰 라우터의 출력을 재보정(Recalibrate)해야 한다고 주장합니다. 특히 제 모델처럼 128개의 전문가를 사용하는 세밀한 모델에서는 8위와 9위 사이의 격차가 좁기 때문에, 8개 전문가 모델보다 훨씬 적은 오차만으로도 컷오프가 뒤집힐 가능성이 큽니다.
솔직히 말씀드리면, 이건 제 사례에서 가장 추측에 의존하는 연결 고리입니다. 아직 제 배포 환경에서 EAC-MoE 방식의 보정을 실행하거나 전문가 이동을 직접 측정해 보지는 않았거든요. 논문의 메커니즘을 그대로 받아들이고 있는 셈이죠. 하지만 이론적으로는 앞뒤가 맞고, 만약 증상이 심각해진다면 고려해 볼 확실한 해결책이기도 합니다. 라우터를 건드리지 않았으니 괜찮을 거라 가정하기보다, 양자화된 모델에 맞춰 라우팅을 다시 보정해야 한다는 것이죠. 지금은 이걸 확정된 원인이 아니라, 충분히 그럴듯한 기여 요인 정도로만 기록해 두고 있습니다.
MTP
스택의 마지막 조각은 speculative decoding을 위해 사용된 MTP(Multi-Token Prediction)입니다. MTP 모듈은 여러 토큰을 미리 초안(draft)으로 생성하고, 메인 모델이 이를 병렬로 검증하는 방식(EAGLE 스타일의 draft-and-verify)이죠. 다른 요소들과 달리, 이 친구는 범인이 아닐 확률이 높습니다.
그 이유는 speculative decoding이 가지는 ‘무손실(lossless)‘이라는 특성 때문입니다. 설계 의도 자체가 타겟 모델의 출력 분포를 그대로 재현하는 것이거든요. 즉, 토큰이 나오는 ‘속도’만 바꿀 뿐, 어떤 토큰이 나오는지(결과값)는 건드리지 않습니다. 이론적으로는 품질 저하의 원인이 될 수 없으며, 실제로도 Gemma 4 26B 모델에서 MTP를 켜고 끌 때의 출력 품질 차이를 체감할 수 없었습니다. 그래서 저는 계속 켜두고 있죠.
다만, ‘무손실’이라는 단어에 안심하기 전에 꼭 짚고 넘어갈 점이 하나 있습니다. 여기서의 무손실은 ‘타겟 모델과 일치함’을 의미하는데, 그 타겟이 바로 ‘양자화된 모델’이라는 점입니다. speculative decoding은 NVFP4 모델이 스스로 출력했을 결과물(심지어 삽질하는 결과까지도)을 충실하게 따라갑니다. 즉, 추가적인 성능 저하를 일으키지는 않지만, 앞서 언급한 양자화 자체의 문제까지 해결해주지는 않습니다. 혹시라도 draft 과정이 의심스럽다면 아주 깔끔한 테스트 방법이 있습니다. draft 모델을 양자화하지 않고 실행(--speculative-draft-model-quantization unquant 옵션 사용)해서 결과값을 비교해보면 되거든요.
오히려 MTP는 이 글의 핵심 주제인 ‘반복 루프’ 상황에서 톡톡히 제 몫을 해냈습니다. 생성이 반복 루프에 빠져 max_tokens까지 도달하게 되면, MTP가 그 아까운 토큰들을 훨씬 빠르게 뱉어내 버립니다. 루프는 어차피 생기지만, 사용자가 기다리는 시간이 줄고 시스템도 더 빨리 회복합니다. 문제 자체를 없애주진 않아도 실패의 대가를 줄여주니, 긴 에이전트 작업을 돌릴 때 꽤 요긴합니다.
진단 가이드
이 시리즈에서 다룬 모든 요소에는 트레이드오프가 존재합니다. 애초에 “비용 없이 퇴화(degeneration)를 없애는 법"은 없었습니다. 핵심은 “안정성을 얻기 위해 무엇을 포기할 준비가 되었는가"였죠.
네 요인 한눈에 보기
| 요소 | 장애와의 연관성 | 이번 이슈에 영향을 미쳤을 확률 | 변경 가능 여부 |
|---|---|---|---|
| 모델의 편향성 | 근본 원인 — 가중치 자체가 반복 생성에 취약함 | 높음 (입증됨) | ✗ 벤더 모델은 재학습 불가 |
| Temperature / Penalty | 디코딩 설정이 불안정한 분포를 무너뜨릴 수 있음 | 높음 | ✗ 양쪽 모두 제약이 큼 |
| 양자화 포맷 | INT4의 높은 분산이 희귀 장애의 발생 범위를 넓힐 수 있음 | 낮음~보통 (추측 단계) | ○ NVFP4로 변경 (속도 개선 목적) |
| MoE 라우팅 | 전문가(expert) 양자화 시 라우터 입력값이 오염되어 선택이 틀어질 수 있음 | 이론적 가능성만 존재 | △ 라우터 재보정 가능 |
| MTP / Speculative | 타겟 대비 무손실; 장애를 재현하지만 퇴화의 원인은 아님 | 아님 (원인 제외) | ○ 유지 (복구 속도 향상) |
싼 것부터, 순서대로
한꺼번에 모든 걸 바꿔볼 순 없으니, 비용이 적게 들고 되돌리기 쉬운 것부터 시작해서 근본적인 해결책으로 나아가야 합니다.
- 디코딩 설정을 최소한으로 조정하세요.
repetition_penalty와 Temperature를 살짝 건드려 보세요. 단, 단순 반복률만 보지 말고 여러분의 벤치마크 점수와 비교해서 판단해야 합니다. 저의 경우 반복률은 떨어졌지만 품질 점수도 같이 내려갔기에, 개선 효과가 있는 최소한의 값에서 멈췄습니다. - 서빙 옵션을 하나씩 껐다 켜보세요. MTP를 끄거나, KV-캐시 양자화를 끄거나, Draft 모델을 비양자화하는 작업을 각각 따로 수행해야 합니다. 그래야 어떤 옵션이 영향을 줬는지 확실히 알 수 있거든요.
- Perplexity(혼란도)와 도구 호출 성공률을 각각 측정하세요. Perplexity는 괜찮은데 도구 호출이 실패한다면, 구조화된 출력에 양자화 손상이 발생한 것입니다. 둘 다 나쁘다면 모델이 너무 압축되었거나 잘못된 모델을 쓰는 중인 거고요. 둘 다 괜찮다면 그건 양자화 문제가 아니라 프롬프트나 스키마 문제입니다.
- 포맷을 변경하고, 그래도 안 되면 한 단계 올리세요. 분산을 줄이기 위해 INT4에서 NVFP4로 바꿔보세요. MoE 모델이라면 라우팅 재보정을 고려해 보고, 그래도 도저히 해결이 안 된다면 최후의 수단으로 QAT(Quantization-Aware Training) 체크포인트를 써야 합니다. QAT는 재학습을 뜻하니, 보통은 벤더가 해결해 줘야 하는 영역이죠.
맺음말
솔직히 말씀드리면, 단 하나의 범인을 찾아내지는 못했습니다. 반복 현상은 모델 자체가 가진 분포에 뿌리를 두고 있었는데, 제가 모델을 다시 학습시킬 수는 없는 노릇이니까요. 이를 억제할 수 있는 디코딩 설정들은 에이전트의 포맷 요구 사항 때문에 고정되어 있고, 양자화(quantization)는 아마도 확률 분포의 꼬리(tail)를 넓히겠지만 그에 대한 증거는 엇갈리는 데다 제 스택에서 직접 측정한 데이터도 없습니다. MoE 라우팅 또한 그럴듯한 원인이겠지만 이는 가설로 남겨두었으며, 제목의 주인공이었던 MTP는 알고 보니 무고했습니다. 제가 얻은 것은 문제를 해결할 수 있는 ‘만능 열쇠’가 아니라, 어떤 설정은 고정되어 있고, 어떤 설정은 조절 가능하며, 다음에 다른 모델에서 같은 증상이 나타나면 무엇을 먼저 의심해 봐야 할지 알려주는 ‘지도’였습니다.
이 과정은 문제를 ‘수리’하는 것이 아니라 ‘선택’하는 문제로 재정의하게 해주었습니다. 바꿀 수 없는 것과 바꿀 수 있는 것을 구분하고, 최종 목표를 달성하는 데 비용이 가장 적게 드는 쪽을 포기하는 전략이죠. 제 목표는 지연 시간(latency)과 도구 호출(tool-call)의 신뢰성이었기에, 확실한 승리(NVFP4의 속도)를 취하고 품질 차이는 아직 증명할 수 없는 보너스로 간주했습니다. 정확도를 최우선으로 최적화하는 사람이라면 다른 선택을 했겠죠. 이러한 결정 중 무엇이 옳다고 단정할 수는 없습니다. 그저 목표에 따라 최적의 선택이 달라질 뿐이니까요.
마지막으로, 처음엔 과소평가했던 레버가 하나 있습니다. 모델만 고칠 수 있는 게 아니라는 점이죠. 어차피 일부 생성은 퇴보(degenerate)하게 마련이라, 막는 노력과 별개로 그 주변에 두 겹의 운영 장치를 뒀습니다.
첫째는 죽기 전에 손을 쓰는 쪽입니다. 발상은 쿠버네티스 같은 스케줄러가 자원을 다루는 방식에서 가져왔습니다. 하드 리밋을 정해두되, 한계에 닿았다고 곧장 강제 종료하는 게 아니라 신호를 먼저 보내 곱게 끝낼 기회를 주는 거죠. 에이전트마다 예산 — 실행 시간, 토큰, tool call 횟수 — 을 두고, 그 한계의 일정 지점(예: 80%)에 다다르면 그냥 벽에 처박히게 두는 대신 메시지를 하나 끼워 넣습니다. 강제 종료(SIGKILL)에 앞서 보내는 SIGTERM 같은 역할이죠. 한계에 가까워졌으니 정리해라. 지금까지 뭘 시도했고, 무엇을 알아냈고, 무엇이 실패했고, 다음에 시도한다면 어떤 방향이 좋을지 적어라. 그러면 루프 한가운데서 아무것도 못 남기고 죽는 대신, 구조화된 인수인계를 남기게 됩니다. 상위 에이전트는 그걸 읽고 재시도할지, 한다면 어떤 다른 방향으로 갈지를 판단할 수 있죠. 무작정 같은 걸 다시 돌리는 게 아니라요.
둘째는 그래도 퇴보가 터졌을 때 피해를 가두는 쪽입니다. 서브 에이전트마다 토큰 예산과 타임아웃을 따로 줬더니, 무한 루프에 빠진 프로세스가 전체를 멈추는 대신 오케스트레이터가 재시도하거나 우회할 수 있는 ‘국소적 실패’로 바뀌었습니다. 근본 치료제는 아니지만, 앞의 nudge와 함께 루프가 망쳐놓던 사용자 경험을 실제로 살려낸 격벽(bulkhead)이었죠. 때로는 가장 싸고 단단한 해결책이 더 좋은 분포를 찾는 게 아니라, 나쁜 분포에서도 무던하게 버티고 살아남는 시스템을 만드는 것일 수 있습니다.
아직 아쉬운 점이 하나 남았습니다. 동일한 모델과 디코딩 환경에서 INT4, NVFP4, FP16 간의 반복률, 도구 호출 성공률, 그리고 전문가 모델 전환(expert-shift) 비율을 동일하게 측정해서 공개한 사례가 없다는 점입니다. 다음 글에서는 바로 이 통제된 비교 실험을 직접 수행해 볼 예정입니다.