Published on

Transformers에서 GGUF 로컬 LLM 로딩 오류 해결

Authors

로컬에서 GGUF 모델을 받아 두고 transformers로 바로 로딩하려다 보면 생각보다 다양한 에러를 만나게 됩니다. 이유는 간단합니다. GGUF는 원래 llama.cpp 계열 런타임을 위한 포맷이고, transformers의 기본 경로(예: AutoModelForCausalLM.from_pretrained)는 보통 safetensors/bin 가중치와 config.json 조합을 기대하기 때문입니다.

이 글에서는 “왜 안 되는지”를 전제로, Transformers 환경에서 GGUF 로컬 LLM을 로딩할 때 발생하는 오류를 빠르게 진단하고 해결하는 체크리스트를 제공합니다. 또한 목적에 따라 **정답 루트(Transformers만 고집하지 않는 루트)**도 함께 제시합니다.

참고로 양자화/배포 관점에서 모델 포맷과 런타임 선택이 성능과 안정성에 큰 영향을 줍니다. INT8 배포 흐름이 궁금하면 PyTorch PT2E 양자화로 INT8 모델 배포하기도 같이 보면 좋습니다.

1) GGUF를 Transformers로 “직접” 못 여는 이유

GGUF는 llama.cpp에서 사용하는 단일 파일 포맷입니다. 반면 transformers의 표준 로딩은 대체로 다음을 전제로 합니다.

  • 모델 가중치: model.safetensors 또는 pytorch_model.bin
  • 설정: config.json
  • 토크나이저: tokenizer.json/tokenizer.model

그래서 아래처럼 로딩하면 보통 실패합니다.

from transformers import AutoModelForCausalLM, AutoTokenizer

model_path = "./models/my-model.gguf"

# 대부분 여기서 실패
model = AutoModelForCausalLM.from_pretrained(model_path)

대표 증상은 다음과 같습니다.

  • OSError: ... is not a local folder and is not a valid model identifier
  • ValueError: Unrecognized model in ... 혹은 config.json not found
  • safetensors_rust.SafetensorError 류(애초에 safetensors가 아닌데 읽으려 함)

결론: GGUF는 Transformers의 기본 로더로는 직접 로딩 대상이 아닙니다.

2) 해결 전략은 3가지 중 하나로 정리된다

GGUF 로딩 문제는 결국 “런타임 선택” 문제입니다. 원하는 목표에 따라 아래 중 하나를 택하는 게 가장 빠릅니다.

A. GGUF를 유지하고 llama.cpp 계열로 로딩한다(권장)

  • CPU-only 환경에서 특히 안정적
  • 메모리 효율 좋음
  • transformers 파이프라인과 100% 동일 API는 아니지만, 로컬 챗봇/서빙에 충분

파이썬에서 가장 흔한 선택은 llama-cpp-python 입니다.

pip install -U llama-cpp-python
from llama_cpp import Llama

llm = Llama(
    model_path="./models/Qwen2.5-7B-Instruct.Q4_K_M.gguf",
    n_ctx=4096,
    n_threads=8,
)

out = llm.create_chat_completion(
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "GGUF 모델을 로컬에서 잘 쓰는 법 알려줘"},
    ],
    temperature=0.7,
)
print(out["choices"][0]["message"]["content"])

이 루트는 “Transformers로 로딩”이라는 표현과는 다르지만, GGUF를 GGUF답게 쓰는 가장 현실적인 해결책입니다.

B. GGUF를 포기하고 Transformers 친화 포맷으로 바꾼다

  • transformers + bitsandbytes 4bit/8bit
  • safetensors 기반
  • accelerate, device_map 등 생태계 활용 가능

즉, GGUF를 내려받은 목적이 “경량 로컬 실행”이 아니라 “Transformers 파이프라인/Trainer/PEFT 호환”이라면, 애초에 Hugging Face 형식 모델을 받는 게 더 낫습니다.

C. Transformers 스타일 API를 유지하되 백엔드는 llama.cpp로 쓴다

일부 프로젝트는 transformerspipeline 같은 UX를 원하지만, 백엔드는 llama.cpp를 쓰고 싶어합니다. 이 경우 완전 동일한 API 호환은 어렵지만, 래퍼를 만들어 유사 UX를 구성할 수 있습니다.

3) 흔한 오류 케이스별 원인과 해결

아래는 현장에서 가장 자주 만나는 케이스를 “증상 → 원인 → 해결”로 정리한 섹션입니다.

케이스 1: config.json not found 또는 Unrecognized model

원인

  • from_pretrained는 디렉터리(모델 리포 구조)를 기대
  • GGUF는 단일 파일이라 config.json이 없음

해결

  • GGUF는 llama-cpp-python 등으로 로딩
  • 또는 Hugging Face 포맷 모델을 사용

진단용으로 “내가 지금 무엇을 로딩하려 하는지”를 먼저 확인합니다.

import os
p = "./models/my-model.gguf"
print("exists:", os.path.exists(p))
print("isfile:", os.path.isfile(p))
print("isdir:", os.path.isdir(p))

케이스 2: OSError: ... is not a valid model identifier

원인

  • from_pretrained에 파일 경로를 넣었거나
  • 로컬 디렉터리인데 내부 파일 구조가 transformers 표준이 아님

해결

  • GGUF는 파일 경로로 llama.cpp 로더에 전달
  • Hugging Face 포맷을 쓰려면 디렉터리 구조를 맞추고 필요한 파일을 갖춤

케이스 3: llama.cpp 로딩은 되는데 한글/채팅 품질이 이상하다

원인

  • GGUF 모델에 맞는 채팅 템플릿/시스템 프롬프트를 적용하지 않음
  • instruct 모델인데 completion 방식으로만 호출

해결

  • create_chat_completion을 사용하고, 모델 카드에 맞는 메시지 포맷을 적용
  • 모델이 요구하는 특수 토큰이나 role 규칙을 확인

llama-cpp-python의 채팅 호출을 쓰면 이 문제가 크게 줄어듭니다.

케이스 4: ValueError 또는 크래시, illegal instruction

원인

  • CPU 명령어 세트 불일치(AVX2/AVX512 등)
  • 사전 빌드된 wheel이 내 머신과 안 맞음

해결

  • 환경에 맞게 llama-cpp-python을 소스 빌드하거나, CPU에 맞는 빌드 옵션 사용
  • Docker로 빌드 환경을 고정

이 케이스는 “코드 문제”라기보다 “바이너리 호환성” 문제라서, 재현/해결에 시간이 걸립니다.

케이스 5: 메모리 부족(OOM) 또는 속도가 너무 느리다

원인

  • 컨텍스트 길이(n_ctx)를 과도하게 잡음
  • 스레드/배치/메모리 맵 설정이 비효율적
  • 양자화 레벨이 환경에 비해 무거움

해결

  • n_ctx를 현실적으로 낮추고, 필요한 경우만 늘림
  • 적절한 양자화 파일 선택(예: Q4_K_M vs Q8_0)
  • GPU 오프로딩이 가능하면 n_gpu_layers 같은 옵션을 검토
from llama_cpp import Llama

llm = Llama(
    model_path="./models/model.Q4_K_M.gguf",
    n_ctx=2048,
    n_threads=8,
    # GPU 사용 가능 환경이면 아래를 조정
    # n_gpu_layers=35,
)

4) “Transformers로 꼭 해야 한다”면 현실적인 대안

조직/프로젝트 제약으로 transformers 중심 파이프라인을 유지해야 하는 경우가 있습니다. 이때는 다음 중 하나로 정리됩니다.

  1. 같은 모델의 Hugging Face 포맷을 구한다
  • 가장 단순하고 안정적
  • AutoTokenizer, AutoModelForCausalLM, pipeline 그대로 사용 가능
  1. GGUF를 변환하는 경로를 찾는다
  • 다만 GGUF는 이미 양자화된 경우가 많고, 변환 과정에서 정보 손실/비호환이 생길 수 있음
  • 변환 성공해도 성능/품질이 기대와 다를 수 있음
  1. 로컬 추론은 llama.cpp, 애플리케이션 레이어는 통합 인터페이스로 감싼다
  • 예: generate(prompt) 같은 내부 인터페이스를 만들고, 백엔드를 교체 가능하게 설계
  • 서빙/관측/캐싱을 붙일 때도 유리

이 접근은 “프런트는 동일, 엔진은 교체 가능” 구조라 운영 안정성이 좋아집니다. 웹 프런트에서 상태 불일치로 고생한 경험이 있다면, 렌더링/상태 동기화 이슈를 정리한 Next.js Hydration mismatch 원인 7가지와 해결법처럼, 경계면을 명확히 하는 설계가 결국 시간을 아낍니다.

5) 재현 가능한 점검 순서(체크리스트)

로딩 오류를 빠르게 줄이려면 아래 순서대로 확인하는 게 효율적입니다.

1) 내가 가진 파일이 진짜 GGUF인지 확인

  • 확장자가 gguf인지
  • 파일 크기가 말이 되는지(수 GB 수준)
ls -lh ./models
file ./models/model.gguf

2) 목표가 무엇인지 결정

  • transformers 파이프라인/Trainer/PEFT가 필요하다 → Hugging Face 포맷
  • 로컬에서 가볍게 채팅/추론이 목적이다 → GGUF + llama.cpp

3) 런타임을 선택하고, 그 런타임의 “정석 로더”를 사용

  • GGUF는 llama.cpp 로더
  • HF 포맷은 transformers 로더

4) 성능/메모리 튜닝은 마지막에

  • 먼저 “정상 로딩 + 정상 출력”을 만든 뒤
  • n_ctx, 스레드, 양자화 레벨을 조정

6) 실전: 로컬 챗 API 형태로 감싸기

프로젝트에 붙일 때는 보통 “함수 하나로 추론” 형태가 편합니다.

from llama_cpp import Llama

class LocalGGUFChat:
    def __init__(self, model_path: str, n_ctx: int = 4096, n_threads: int = 8):
        self.llm = Llama(
            model_path=model_path,
            n_ctx=n_ctx,
            n_threads=n_threads,
        )

    def chat(self, user_text: str, system_text: str = "You are a helpful assistant.") -> str:
        resp = self.llm.create_chat_completion(
            messages=[
                {"role": "system", "content": system_text},
                {"role": "user", "content": user_text},
            ],
            temperature=0.7,
        )
        return resp["choices"][0]["message"]["content"]

bot = LocalGGUFChat("./models/model.Q4_K_M.gguf")
print(bot.chat("로컬 LLM 로딩 오류를 줄이는 체크리스트를 줘"))

이렇게 감싸두면, 나중에 HF 포맷으로 갈아타더라도 chat() 내부만 바꾸면 됩니다.

7) 결론: GGUF 로딩 오류의 본질은 “포맷-런타임 미스매치”

Transformers에서 GGUF 로딩 오류가 나는 가장 큰 이유는, GGUF가 Transformers 표준 로딩 경로의 입력이 아니기 때문입니다. 해결은 의외로 단순합니다.

  • GGUF를 쓸 거면 llama.cpp 계열 런타임으로 로딩한다.
  • Transformers 생태계를 쓸 거면 HF 포맷 모델을 사용한다.
  • 둘 다 필요하면 애플리케이션 레이어에서 인터페이스를 통일해 백엔드를 교체 가능하게 만든다.

이 3가지만 명확히 하면, “왜 로딩이 안 되지?”에 쓰는 시간을 크게 줄이고, 튜닝과 제품화 단계로 빠르게 넘어갈 수 있습니다.