Transformers에서 llama.cpp 양자화 모델을 바로 돌린다

GGUF 파일을 변환 없이 from_pretrained로 불러오는 통합이 로컬 추론의 풍경을 어떻게 바꾸는지 해설합니다

원문 논문 보기
'Transformers에서 llama.cpp 양자화 모델을 바로 돌린다' 기사 커버 이미지

Hugging Face가 공개한 기술 글 Transformers now runs llama.cpp quants는 Transformers에서 llama.cpp용 GGUF 파일을 직접 불러서 실행하는 통합을 소개한다. 이 글의 주장은 한 문장으로 정리된다. 별도 변환 없이 from_pretrained로 Q4_K_M 같은 K-quant 파일을 읽고, 익숙한 토크나이저와 생성 API로 로컬 추론까지 이어지게 했다는 것이다. 원문 링크는 제목 아래에 붙어 있다. 이번 글에서는 그 통합이 왜 등장했는지, 내부에서 어떤 일을 하는지, 어디까지 믿고 쓸 수 있는지를 차례로 짚어본다.

노트북 앞에서 문득 든 생각

안녕하세요, 패트릭입니다.

얼마 전 카페에서 옆자리 분이 오래된 노트북으로 꽤 큰 모델을 돌리고 있는 것을 봤습니다. 팬 소리는 컸지만 화면에는 답이 또박또박 나오고 있었죠. 그렇다면 왜 예전에는 같은 파일이 있어도 Transformers 쪽에서 바로 못 읽었을까요.

제가 이 소식에서 가장 흥미롭게 본 부분은 신모델 숫자보다 이미 세상에 널리 퍼진 GGUF 파일들을 향한 선택입니다. 사람들이 매일 쓰는 Transformers 문법 그대로 그 파일들을 불러오게 했기 때문입니다. 연구실의 점수 경쟁보다 현장의 마찰을 줄이는 쪽에 손을 댔기 때문입니다. 그래서 이번 글은 속도 자랑보다 그 마찰이 어디서 생겼고 어떻게 풀렸는지에 맞춰 읽어보려 합니다.

노트북에서 로컬로 동작하는 양자화 모델의 개념도
노트북에서 로컬로 동작하는 양자화 모델의 개념도

큰 모델을 가볍게 들고 다니는 일

큰 언어 모델을 노트북에 넣는 일은 예전에는 상상하기 어려웠습니다. 가중치가 수십 기가바이트를 넘으면 메모리 한계에 바로 걸렸고, 그렇다고 매번 서버를 붙이자니 비용과 지연이 따라왔기 때문입니다. 그래서 커뮤니티는 가중치를 줄이는 쪽으로 눈을 돌렸는데, 그중에서 llama.cpp 진영이 만든 흐름이 특히 실용적으로 번졌습니다.

llama.cpp는 CPU에서도 돌아가도록 설계된 추론 엔진으로 시작했고, 이후 저용량 GPU까지 품으면서 로컬 실행의 표준 같은 자리에 올라갔습니다. 밤늦게 기숙사에서 모델을 만지작거리는 학생도, 외부망이 막힌 사무실에서 프로토타입을 돌려야 하는 개발자도 같은 도구에 기대게 됐죠. 이런 확산 뒤에는 GGUF라는 파일 그릇이 있었습니다. 가중치뿐 아니라 모델 구조와 설정까지 함께 담을 수 있어서, 파일 하나를 받으면 실행까지 이어졌기 때문입니다.

그런데 Transformers 사용자에게는 이 파일이 늘 반걸음 비껴 있었습니다. Transformers는 학습과 실험, 서빙 생태계의 한가운데에 있지만, GGUF는 llama.cpp 세계의 문법으로 저장돼 있었으니까요. 파일을 구해도 다시 변환하거나 별도 로더를 붙여야 했고, 그 과정에서 토크나이저 설정이나 챗 템플릿이 어긋나는 일이 생겼습니다. 그렇다면 이 반걸음을 메우는 일이 왜 이제야 이뤄졌을까요. 그 답은 파일 형식 자체보다 양자화 방식의 디테일에 있습니다.

GGUF라는 그릇 속을 들여다보면

GGUF를 처음 접하면 그냥 압축 파일처럼 보일 수 있습니다. 하지만 열어보면 구조가 꽤 정교해서, 어떤 모델인지, 어떤 층이 몇 개 있는지, 텐서가 어떤 블록 크기로 양자화됐는지를 메타데이터로 함께 들고 있습니다. 실행기는 이 설명서를 읽고 메모리 배치를 정한 뒤, 양자화된 숫자들을 제자리에서 풀면서 계산하죠.

여기서 양자화는 소수점 정밀도를 낮춰 용량을 줄이는 작업으로 이해하면 됩니다. 16비트 실수로 들고 있던 숫자를 4비트나 5비트 수준의 묶음으로 바꾸면 파일 크기가 눈에 띄게 줄어드는데, 이때 묶음을 나누는 방식에 따라 품질이 달라집니다. llama.cpp의 K-quant 계열인 Q4_K_M이나 Q5_K_M, Q6_K, Q8_0은 이런 묶음 나누기를 여러 세대에 걸쳐 다듬은 결과입니다. 같은 4비트라도 어느 층에 더 많은 비트를 줄지, 스케일 값을 어떻게 공유할지를 다르게 정했기 때문에 이름 뒤에 붙은 접미사가 생긴 것이죠.

제가 여기서 멈칫했던 대목은 블록 구조의 끈질김입니다. 단순히 숫자를 뭉개는 대신, 슈퍼블록이라는 큰 틀 안에 작은 블록들을 넣고 스케일을 공유하면서 오차를 분산시키는 구조라서, 구현을 조금만 어긋나게 해도 품질이 바로 흔들립니다. 그래서 GGUF를 읽는다는 말은 파일 열기 버튼 하나로 끝나지 않습니다. 이런 블록 약속을 정확히 재현한다는 뜻에 가깝습니다. 그렇다면 Transformers는 이 약속을 어떻게 자기 집 안으로 들였을까요.

Safetensors 변환 과정과 GGUF 직접 로딩의 비교도
Safetensors 변환 과정과 GGUF 직접 로딩의 비교도

변환을 없앴다는 말이 뜻하는 범위

흔히 이번 통합을 변환 없는 실행으로 설명합니다. 예전에는 GGUF를 받으면 Safetensors 같은 Transformers 친화 형식으로 다시 바꿔야 했는데, 이제는 그 단계를 건너뛴다는 설명이죠. 이 말은 큰 방향에서는 맞지만, 내부에서 벌어지는 일을 정확히 이해하려면 한 겹 더 들어가야 합니다.

파일을 바꾸지 않는 것과 계산을 바꾸지 않는 것은 다른 문제이기 때문입니다. 이번 통합은 디스크에 있는 GGUF 파일을 그대로 두고 불러온다는 뜻이고, 메모리로 올린 뒤에는 Transformers 모델 구조에 맞게 양자화된 텐서를 매핑해서 연산해야 합니다. 겉에서는 from_pretrained 한 줄이 다 한 것처럼 보이지만, 속에서는 GGUF 메타데이터를 해석하고 각 층의 가중치를 찾아 제자리에 연결하는 과정이 매번 돕니다.

이런 구분은 실제 사용 장면에서 바로 체감됩니다. 예를 들어 토요일 오후에 집 노트북으로 Q4_K_M 파일을 받아 바로 시험하는 사람을 떠올려 보죠. 예전 흐름이라면 변환 스크립트를 찾고, 저장 공간을 두 배로 쓰고, 변환된 가중치와 토크나이저 버전이 맞는지 확인하는 밤이 필요했습니다. 이제는 파일 경로만 넘기면 익숙한 코드가 돌아가니 체감 마찰이 크게 줄어듭니다. 그렇다면 그 편함 뒤에서 토크나이저와 챗 템플릿은 어떤 역할을 할까요.

from_pretrained 한 줄이 이어주는 경험

Transformers를 오래 쓴 사람에게 from_pretrained는 일종의 약속입니다. 모델 이름을 넘기면 가중치뿐 아니라 설정과 토크나이저, 생성 옵션이 함께 맞춰진다는 기대죠. 이번 GGUF 지원은 그 약속을 로컬 양자화 파일까지 넓힌 셈입니다.

구체적인 흐름을 따라가면 이렇습니다. 사용자가 GGUF 파일 경로를 넘기면 Transformers는 먼저 파일 앞부분의 메타데이터를 읽어 모델 종류와 층 수, 어휘 크기를 파악합니다. 이어서 양자화된 텐서들을 해당 아키텍처의 어텐션과 피드포워드 자리에 연결하는데, 이때 가중치를 미리 풀어서 저장하지 않고 양자화된 상태를 유지하면서 연산 경로를 맞춥니다. 그래서 디스크 사용량은 그대로 두고 실행 준비를 마칠 수 있죠.

여기서 제가 눈여겨본 부분은 실수하기 쉬운 지점을 프레임워크 안으로 끌어들였다는 점입니다. 예전에는 사용자가 변환 옵션을 고르다가 스케일 해석이 어긋나거나, 템플릿을 따로 붙이다가 말머리가 달라지는 일이 잦았습니다. 이제는 같은 불러오기와 같은 생성 호출을 쓰니, 적어도 인터페이스 층의 실수는 줄어듭니다. 물론 내부 연산이 완전히 같아지는지는 별개 검증이 필요한데, 그 이야기는 뒤에서 다시 다루겠습니다. 그렇다면 블록 단위 약속을 맞추는 일은 얼마나 까다로울까요.

K-quant 블록 구조를 설명하는 격자 다이어그램
K-quant 블록 구조를 설명하는 격자 다이어그램

블록 약속을 맞추는 조용한 작업

K-quant를 구현해 본 분들은 이 부분이 얼마나 예민한지 경험으로 알고 있죠. Q4_K_M 하나만 봐도 층 종류에 따라 비트 배분이 달라지고, 스케일과 미니 스케일이 얽히면서 오차를 나눠 갖습니다. Q5_K_M이나 Q6_K, Q8_0도 각자 다른 타협점을 들고 있어서, 이름만 보고 같은 코드로 처리하면 어딘가에서 어긋납니다.

Transformers 쪽 구현이 해야 할 일은 그래서 세 가지로 갈립니다. 먼저 GGUF 안에 적힌 텐서 이름을 읽고 현재 모델 클래스의 어느 모듈에 해당하는지 찾아야 합니다. 다음으로 각 텐서의 양자화 타입을 확인하고 알맞은 역양자화 경로에 연결해야 하죠. 마지막으로 추론 과정에서 가중치를 풀 때의 연산 순서와 형 변환을 llama.cpp 쪽 약속과 어긋나지 않게 맞춰야 합니다.

이 과정을 여행용 압축 파우치에 비유하면 감이 옵니다. 옷을 작게 접는 기술도 필요하지만, 꺼낼 때 구김 없이 원래 형태로 펴는 순서가 따로 필요하니까요. 접는 쪽과 펴는 쪽의 약속이 어긋나면 옷은 들어가도 입을 수 없는 셈입니다. GGUF 지원도 파일 읽기를 넘어 풀기와 계산의 순서를 맞추는 작업이라서, 겉보기보다 손이 많이 갑니다. 그렇다면 이렇게 읽은 모델을 매번 같은 말투로 쓰게 해주는 장치는 무엇일까요.

토크나이저와 템플릿이 함께 올 때의 편함

로컬에서 모델을 돌려본 분들은 이런 일을 겪어봤을 겁니다. 가중치는 잘 읽혔는데 답의 말머리가 이상하거나, 시스템 메시지가 무시되거나, 특수 토큰이 깨져 나오는 일 말이죠. 원인은 대개 토크나이저나 챗 템플릿이 따로 놀기 때문입니다. 가중치 파일과 대화 형식 파일이 서로 다른 출처에서 오면 어긋나기 쉽습니다.

이번 통합이 반가운 이유는 가중치 실행과 대화 준비를 같은 호출 안에서 묶었기 때문입니다. Transformers의 토크나이저는 어휘와 특수 토큰 처리를 담당하고, 챗 템플릿은 사용자 발화와 시스템 지시를 모델이 익숙한 모양으로 조립합니다. 이어서 generate 호출은 샘플링과 정지 조건, 캐시 관리를 같은 문법으로 이어주죠. 그래서 GGUF를 불러온 뒤에도 평소 쓰던 대화 코드가 그대로 이어집니다.

저희가 새로운 AI 아키텍처를 볼 때도 점수만 보지는 않습니다. 실제로 무엇이 바뀌었고, 그 변화가 개발자의 하루를 어떻게 바꾸는지를 함께 봅니다. 이번 경우에는 점수보다 손끝의 변화가 분명합니다. 변환 스크립트 폴더가 사라지고, 템플릿 붙여넣기 메모가 필요 없어지면서, 시험 삼아 돌려보는 거리가 짧아졌기 때문입니다. (주말에 카페 와이파이 없이 노트북만 들고 모델 말투를 시험하던 장면을 떠올리면, 이 짧아진 거리가 꽤 크게 느껴집니다.) 그렇다면 이렇게 편해진 실행이 정말 같은 답을 낼까요.

토크나이저와 챗 템플릿 통합 과정을 담은 일러스트
토크나이저와 챗 템플릿 통합 과정을 담은 일러스트

같은 답이 나오는지 확인하는 일

양자화 통합에서 가장 조심해서 읽어야 할 대목은 parity 검증입니다. 같은 GGUF 파일을 llama.cpp에서 돌릴 때와 Transformers에서 돌릴 때 결과가 어긋나면, 편함이 있어도 연구나 제품에서 쓰기 어렵기 때문입니다. 그래서 성능과 샘플링 일치는 이번 작업의 무게중심에 놓여 있습니다.

여기서 성능은 두 겹으로 갈립니다. 하나는 답의 동등성으로, 같은 프롬프트와 같은 샘플링 조건에서 토큰 선택이 어긋나지 않는지를 봅니다. 다른 하나는 속도로, 초당 토큰 처리나 첫 토큰까지의 지연이 과도하게 벌어지지 않는지를 봅니다. 두 지표는 함께 가야 합니다. 답만 같고 속도가 크게 느리면 현장에서 쓰기 어렵고, 속도만 빠르고 답이 어긋나면 신뢰가 깨지기 때문입니다.

저는 이 대목을 읽으면서 한 가지 질문을 메모했습니다. 역양자화는 정확히 같은 순서로 일어나는가. 양자화된 가중치를 푸는 연산은 부동소수점 반올림과 형 변환 순서에 따라 미세하게 달라질 수 있어서, 사소해 보이는 구현 차이가 긴 생성에서는 눈에 띄는 분기로 자라날 수 있습니다. 샘플링 역시 난수 생성기와 상위 확률 필터링의 디테일이 맞물려야 같은 궤적을 그립니다. 그래서 parity는 한 번 맞추고 끝나는 숙제가 아니라, 타입과 하드웨어가 바뀔 때마다 다시 확인해야 하는 약속에 가깝습니다. 그렇다면 이 약속이 지켜질 때 로컬 실행의 풍경은 어떻게 달라질까요.

노트북과 작은 GPU가 무대에 오르면

GGUF 계열이 노리는 자리는 분명합니다. 메모리가 넉넉한 서버가 아니라, CPU 중심 노트북과 용량이 작은 GPU가 있는 자리죠. 이런 환경에서는 가중치를 통째로 올릴 수 없으니, 양자화로 크기를 줄이고 필요할 때마다 풀면서 계산하는 전략이 잘 맞습니다.

이 전략이 Transformers 안으로 들어오면 개발 흐름이 단순해집니다. 학습과 실험은 서버에서 하던 팀도, 시연과 현장 시험은 로컬 파일로 바로 이어갈 수 있기 때문입니다. 예를 들어 화요일 아침에 사무실 데스크탑으로 받은 Q5_K_M 파일을, 저녁에는 집 노트북으로 옮겨 같은 코드로 돌리는 장면을 생각해 보죠. 환경이 바뀌어도 불러오기와 생성 코드가 같으니, 버그를 쫓을 때 의심해야 할 층이 하나 줄어듭니다.

물론 로컬 실행이 만능은 아닙니다. CPU 추론은 배치 처리나 긴 문맥에서 숨이 찰 수 있고, 저용량 GPU에서는 오프로딩 비율에 따라 체감이 크게 갈립니다. 메모리 대역폭과 캐시 크기가 병목을 정하는데, 이 조건은 기기마다 달라서 한 기기의 빠르기가 다른 기기의 빠르기로 그대로 옮겨가지 않습니다. 저는 그래서 이번 통합을 속도 기적보다 접근성 확장으로 읽는 편입니다. 더 많은 사람이 같은 파일을 같은 문법으로 만져볼 수 있게 됐다는 점에서 변화가 시작되기 때문입니다.

CPU와 저용량 GPU 메모리 구조도
CPU와 저용량 GPU 메모리 구조도

아직 비어 있는 칸들을 솔직하게 보면

이번 소식을 크게 읽기 전에 비어 있는 칸도 함께 봐야 합니다. 원문 흐름에서 열린 질문으로 남는 부분은 IQ 계열 양자화의 지원 범위, 멀티모달 GGUF 모델의 처리, KV 캐시 양자화, 그리고 Transformers에서 GGUF로 내보내는 방향입니다. 각각은 성격이 달라서 한 묶음으로 치부하기 어렵습니다.

IQ 계열은 K-quant와 다른 설계 철학을 들고 있어서, 지원 범위를 넓히는 일이 단순한 이름 추가가 아닙니다. 양자화 격자와 중요도 추정이 달라서 매핑과 검증이 따로 필요합니다. 멀티모달 GGUF는 텍스트 가중치를 넘어 이미지나 오디오 인코더와의 연결을 어떻게 맞출지가 남는데, 토크나이저를 넘어선 입력 전처리가 함께 와야 하죠. KV 캐시 양자화는 생성 길이가 길어질 때 메모리 증가를 누르는 장치라서, 로컬 실행의 체감과 직결됩니다. 마지막으로 GGUF 내보내기는 Transformers에서 다듬은 가중치를 다시 llama.cpp 세계로 돌려보내는 길이라서, 생태계 순환이라는 관점에서 눈길이 갑니다.

여기서 조심할 점은 빈칸을 결함으로만 읽지 않는 것입니다. 모든 형식을 한 번에 품는 통합은 처음부터 가능하지 않기 때문입니다. 파일 읽기와 텐서 매핑, 샘플링 parity라는 어려운 숙제를 먼저 풀고, 적용 범위를 넓히는 순서는 꽤 합리적입니다. 제가 앞으로 보고 싶은 것은 빈칸이 메워지는 순서와 그때마다 parity를 어떻게 다시 증명하는지입니다. 어떤 타입부터 채우고, 어떤 하드웨어에서 먼저 맞추는지가 다음 장의 성격을 정할 테니까요. 그렇다면 이 흐름을 길게 보면 어떤 질문이 남을까요.

남은 과제를 퍼즐 조각으로 표현한 스케치
남은 과제를 퍼즐 조각으로 표현한 스케치

로컬 실행이 바꾸는 질문들

길게 보면 이번 통합은 파일 형식 하나를 더 읽는 이야기로 끝나지 않습니다. 모델을 어디에서 돌릴 수 있는지에 대한 답이 바뀌면, 누가 무엇을 시험할 수 있는지도 함께 바뀌기 때문입니다. 서버 할당을 기다리던 학생이 밤에 자기 방에서 가설을 시험하고, 외부 반출이 어려운 현장의 엔지니어가 로컬에서 프로토타입을 만지는 풍경이 늘어납니다.

질문도 바뀌었습니다. 예전에는 좋은 양자화란 무엇인가를 물었다면, 이제는 좋은 양자화를 평소 쓰던 도구에서 얼마나 그대로 쓸 수 있는가를 묻게 됩니다. 파일이 있어도 불러오기 어렵다면 없는 것이나 다름없기 때문입니다. 이런 관점에서 보면 이번 작업은 점수표의 한 줄보다 개발자 경험의 한 줄을 고친 셈입니다. 익숙한 불러오기와 익숙한 생성 호출이 로컬 파일까지 이어지게 했으니까요.

다만 과장하지 않고 싶습니다. 모든 GGUF가 같은 품질로 돌아가는 것도 아니고, 모든 기기에서 같은 속도가 나오는 것도 아닙니다. parity 검증은 계속 필요하고, 빈칸으로 남은 타입과 멀티모달, 캐시와 내보내기는 각자 다른 난이도를 들고 있습니다. 그럼에도 방향은 분명합니다. 무거운 모델을 가볍게 들고 다니는 길이, 특정 실행기만의 골목에서 공용 도로 쪽으로 나오고 있다는 점 말이죠.

그래서 마지막에 이런 질문을 남기고 싶습니다. 여러분의 노트북에서 바로 돌아가는 모델이 생긴다면, 가장 먼저 무엇을 시험해 보시겠습니까. 저는 그 답들이 모이는 곳에서 다음 통합의 우선순위가 정해지리라 봅니다. 현장의 손이 많이 닿는 타입과 기기부터 채워질 테니까요. 그 과정을 따라가 보는 일이 앞으로 몇 달간의 관전 포인트가 될 것입니다.

참고 자료

  1. Transformers now runs llama.cpp quants · huggingface.co

    리뷰 원문