무보정 저비트 양자화 LLM 파일은 원본과 똑같아도 작동하지 않는다

Broken on Arrival: Non-Functional Quantized LLM Artifacts in Public Model Registries and How to Catch Them

HF Daily2609.05881

Aditi Patodiya2026-10-25

무엇인가

로컬에서 LLM을 돌리는 가장 흔한 경로는 공개 레지스트리에서 양자화된 GGUF 파일을 내려받는 것이다. 문제는 저자들이 조사한 어떤 레지스트리도 파일에 대한 기능 테스트를 문서화하지 않았다는 점이다. 모델 배포를 데이터 파이프라인으로 보면 체크포인트가 변환·양자화·게시를 거치는 동안 기능 검사가 한 번도 기록되지 않고, 사용자에게 레지스트리 태그는 검증되지 않은 입력이 된다. 실제로 2024년 llama.cpp 유지관리자들은 수정 전에 변환된 BPE GGUF 파일이 "거의 모든 경우" 잘못된 기본 사전 토크나이제이션으로 폴백한다는 사실을 찾아냈고, 이 논문의 2년 후 조사에서도 해당 레지스트리의 적격 태그 305개 중 26개에 pre-tokenizer 타입이 없었다. 저자들은 이 빈틈을 실행·출처 추적·재현성이라는 세 가지 검사로 메운다.

어떻게 동작하나

실행 조사 대상은 Ollama 공식 라이브러리의 15개 모델 라인에서 뽑은 305개 적격 태그(그중 291개가 양자화, 14개가 fp16)와 Hugging Face 커뮤니티의 인기 GGUF 22개, 합계 327개다. 적격 조건은 instruct이면서 코드 capable, 명시적 양자화 접미사, 8GB 이하 파일이며 해당 라인 안에서는 표본이 아니라 전수다. 스크린은 Qwen2.5-Coder 24개 구성(0.5B~7B, 2비트~fp16)으로 튜닝했는데 23개가 정상, 3B q3_K_M 하나가 알려진 실패였다. 23개 정상 구성이 모두 푸는 HumanEval+ 39개 중 사전순 첫 15개를 과제로 쓴다. 1단계는 15개 중 9개 미만이면 플래그하고, 2단계는 같은 모델 변형의 q6_K·q8_0·fp16 중앙값을 그룹 베이스라인으로 삼아 healthy(베이스라인의 60% 이상), suspect(베이스라인이 8 이상인 그룹에서 15개 중 2개 이하), low baseline, degraded, no baseline으로 나눈다. suspect이면서 164개 HumanEval+ 중 10% 미만을 풀면 non-functional로 규정한다. 조사 중 평가 하네스 결함도 하나 잡았다. census 스코어러가 HumanEval/15를 모든 산출물에 실패로 표시했는데, 병렬 채점에서 강화 테스트가 시간 제한을 넘겼기 때문이다. 실패 과제를 직렬로 재평가하자 1단계 플래그가 50개에서 39개로 줄었고 suspect 7개는 그대로였다.

무엇과 다른가

낮은 점수만으로는 원인을 알 수 없어 저자들은 recipe-matched 재빌드를 붙인다. suspect마다 원본 체크포인트를 내려받아 GGUF로 변환하고, 파일 메타데이터가 가리키는 레시피(quantize.imatrix 키가 없으므로 무보정)로 같은 레벨에서 직접 양자화한 referee 빌드를 만든다. 그리고 메타데이터와 텐서 순서를 무시한 채 타입·형상·바이트를 비교한다. 판정 규칙은 세 가지다. suspect가 15개 중 2개 이하인데 plain 재빌드가 5개 이상이면서 4개 이상 더 풀면 file defect, 재빌드도 비슷하게 실패하면 collapse, 같은 가중치·같은 레벨의 보정 빌드가 재빌드보다 5개 이상이면서 4개 이상 더 풀면 calibration-rescued다.

어떻게 쓰나

결과부터 보면 305개 공식 산출물 중 7개(2.3%)가 non-functional이고, 35개 모델 변형 기준으로는 3개(8.6%)다. Wilson 95% 구간은 각각 1.1~4.7%, 3.0~22.4%다. 7개 중 6개는 164개 과제를 하나도 풀지 못하고 llama3.2-1B q3_K_S만 6개를 푼다. 세 사건으로 묶이는데 Qwen2.5-Coder-3B의 인접한 4개 레벨, phi3.5-mini 1개, llama3.2-1B 2개다. 전부 4B 이하 모델의 q2_K~q3_K_L 48개 태그 안에 있고 이 부분군에서는 14.6%다. 나머지 257개는 suspect가 아니었고, 커뮤니티 22개 중 CUDA에서 20개가 정상이었다. 재빌드한 5개는 Qwen이 434개 텐서, phi3.5가 197개 텐서(32개 attn_qkv를 Q2_K로 고정)에서 라이브러리 파일과 완전히 일치했다. 즉 파일 손상이 아니라 원본 가중치를 그대로 무보정 양자화한 결과다. 실패 파일들은 크래시하지도 거부하지도 않고 정상 속도로 텍스트를 스트리밍한다. 한 줄짜리 문자열 길이 함수에도 서명과 docstring까지는 쓰고 본문이 없는 식이다. 가중치·툴체인·텐서 믹스를 고정한 채 importance matrix를 넣으면 Qwen2.5-Coder-3B는 q2_K가 164개 중 0에서 106~113개, q3_K_M이 76~89개, q3_K_L이 80~86개로 뛴다. phi3.5는 현재 llama.cpp 믹스가 32개 attn_qkv를 Q3_K로 저장하는 것만으로 0에서 21개(가중치당 2.96비트에서 3.16비트)가 되고, imatrix가 이를 21에서 37개로 올린다(McNemar p=0.007). 반면 Qwen q3_K_S는 어떤 보정에서도 13~30개로 약하고, 레지스트리의 4비트 태그는 132개(Qwen)와 74개(phi3.5)다. 같은 무보정 레시피가 1.5B에서는 멀쩡하다. q2_K~q3_K_L 태그가 338개 텐서에서 재빌드와 일치하면서 15개 중 13~15개를 푼다. 0.5B는 896폭 행이 256 가중치 k-quant 블록을 담지 못해 169개 가중치 행렬 중 96~144개가 4비트로 폴백해 실효 5.4~5.9비트로 돈다.

전제와 한계

생성 코드를 실행하지 않고 잡을 수 있는지도 봤다. 토큰 캡 도달률 0.2 이상은 suspect 7개 중 3개와 나머지 298개 중 10개를 플래그하고, 6-gram 반복 0.2 이상은 suspect 3개와 다른 태그 4개를 잡는다. 둘 중 하나를 0.3 이상으로 올리면 suspect 4개와 다른 태그 6개인데, Qwen q3_K_M·q3_K_L과 llama3.2-1B q3_K_S는 놓친다. WikiText-2 perplexity는 측정한 비기능 빌드 5개가 fp16의 27~5,700배였고 4비트 태그는 4.2%, 5.1% 차이로 붙어 있었다. fp16의 10~25배를 임계값으로 잡으면 실행 없이 5개를 모두 잡지만, 임계값은 결과를 본 뒤에 골랐고 fp16 레퍼런스가 필요하며 오탐률은 측정되지 않았다. 스크린 자체의 한계도 측정했다. census에서는 플래그된 태그만 전수 평가했기 때문에, 미플래그 233개에서 56개를 무작위 층화 추출(1차 버전 10개 포함)해 감사 전에 고정한 별도 49개 과제로 돌렸더니 8~41개를 풀었고 2개 이하는 없었다. 감사한 66개 중 비기능은 하나도 없었다. 비용은 소비자 GPU 한 장에서 생성 12~105초, 중앙값 30초다. 15개 중 3개 이상인 어떤 부분집합을 써도 재빌드한 5개 suspect는 유지되지만 llama3.2-1B q3_K_S는 31~87%의 부분집합에서 빠진다. 커뮤니티에서는 TheBloke의 deepseek-coder-6.7B Q3_K_M·Q4_K_M이 CUDA에서 15개 중 5개와 6개를 풀어 Ollama 자체 변환(둘 다 14개)에 크게 뒤졌는데, 채팅 템플릿이 없어 일반 ChatML로 폴백한 탓이었다. 모델의 instruction 템플릿을 넣으면 13개와 15개로, 164개 전체 점수는 16에서 98, 18에서 101로 뛴다.

개발자에게 주는 지침은 명확하다. 받은 산출물을 신뢰하기 전에, 특히 소형 모델의 저비트 태그를 실제로 배포할 백엔드와 채팅 템플릿으로 테스트하라. 저자들이 제안하는 게시 절차는 네 단계다. fp16 빌드가 있으면 perplexity 배수로 사전 경고하고, 15개 과제 스크린에서 9개 이상 풀어야 통과시키고, 플래그됐지만 suspect가 아닌 태그는 164개 벤치마크에서 서로 다른 17개 과제를 풀 때까지 확인하고, suspect는 원본에서 무보정 재빌드로 귀속 진단한 뒤 같은 레벨의 보정 빌드를 게이트에 통과시키거나 해당 레벨을 빼라는 것이다. 레지스트리에는 레벨 이름뿐 아니라 가중치당 실효 비트와 레시피, 보정 입력을 메타데이터에 기록하라고 권한다. 연구자에게는 경고도 있다. 레지스트리 태그를 특정 정밀도의 모델 대리물로 쓰면 레시피 문제를 정밀도 한계로 오독할 수 있다는 것으로, Ollama 경유 Qwen2.5-Coder-3B q2_K에서 0%를 재고 Q4_K_M을 권한 SynConfRoute 사례를 든다.

한계는 저자들이 직접 밝힌다. 스크린은 Python 코드 생성만 보므로 수치는 그 용도에 한정된다. 조사 범위도 레지스트리 하나, 8GB 이하, 15개 모델 라인의 코드 capable instruct 태그와 인기 기준으로 고른 작은 커뮤니티 표본이다. 7이라는 숫자는 상한이 아니라 하한이다. 모든 정밀도에서 낮은 점수를 내는 deepseek-coder-1.3B 그룹(12개 태그 전부 15개 중 1~6개, fp16조차 164개 중 16개, 출력의 59%가 파싱 불가이고 절반가량이 들여쓰기 문제)은 정상 고정밀 형제가 없어 귀속 불가로 남겼다. 보정 구제 증거는 대부분 Qwen2.5-Coder-3B에서 나왔고 다른 모델이 같은 조건에서 실패하는지는 열려 있다. 재빌드는 레지스트리가 썼을 것으로 보이는 버전보다 최신 llama.cpp를 사용했고, 15개 과제 임계값은 거칠며, 실험 계획은 공개 사전 등록되지 않았다.