ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Qwen3.5-122B 양자화 비교: Q4_K_M vs Unsloth UD-Q3_K_XL 실측
    IT 2026. 4. 5. 21:00
    Qwen3.5-122B 양자화 비교: Q4_K_M vs Unsloth UD-Q3_K_XL 실측

    왜 양자화 방식이 중요한가

    로컬 LLM을 운용할 때 가장 큰 제약은 GPU 메모리다. 122B 파라미터 MoE 모델인 Qwen3.5-122B-A10B의 BF16 원본은 244GB로, 어떤 단일 GPU에도 올라가지 않는다. 양자화(Quantization)가 필수인 이유다.

    그런데 같은 "양자화"라도 접근 방식에 따라 결과가 크게 달라진다. Ollama 공식 라이브러리에서 제공하는 Q4_K_M(81GB)은 모든 레이어에 균일하게 4비트를 적용한다. 반면, Unsloth의 UD-Q3_K_XL(54GB)은 Dynamic 양자화 2.0을 적용한다. 어텐션과 라우터 가중치 같은 민감한 레이어는 6~8비트로 유지하고, MoE의 전문가 FFN 가중치(256개 중 8개만 활성화)는 2~3비트로 공격적으로 압축하는 전략이다.

    이론적으로 UD-Q3_K_XL은 30% 이상 작으면서 Q4_K_M과 동등한 품질을 유지한다고 한다. 실제로 그런지 DGX Spark에서 직접 검증해봤다.

    테스트 환경

    항목 사양
    하드웨어 NVIDIA DGX Spark (GB10 Blackwell, 통합 메모리 128GB)
    OS Ubuntu Linux (aarch64)
    Model A qwen3.5:122b-a10b Q4_K_M (Ollama 공식, 81GB)
    Model B Unsloth UD-Q3_K_XL (HuggingFace GGUF, 54GB)
    Model A 백엔드 Ollama 0.17.7
    Model B 백엔드 llama.cpp llama-server (최신 빌드, CUDA CC 12.1)
    측정 방식 스트리밍 API, 각 테스트 3회 반복, 평균값 사용

    참고: Unsloth GGUF는 Ollama와 호환되지 않아(GitHub #14587) llama-server를 직접 빌드하여 사용했다. 백엔드 차이가 오버헤드에 영향을 줄 수 있으므로, 생성 속도(tok/s) 위주로 비교한다.

    속도 비교

    토큰 생성 속도 (tok/s)

    테스트 Q4_K_M UD-Q3_K_XL 차이
    Short→Short (25→100 tok) 22.97 23.76 +3.4%
    Short→Long (23→1000 tok) 23.08 23.45 +1.6%
    Medium→Short (500→100 tok) 21.77 23.47 +7.8%
    Long→Short (3946→100 tok) 22.38 22.87 +2.2%
    Very Long→Short (15719→50 tok) 21.81 22.44 +2.9%

    모든 시나리오에서 UD-Q3_K_XL이 1.6~7.8% 빠른 생성 속도를 보였다. 모델이 30% 가벼우니 메모리 대역폭 병목이 줄어든 결과다. 특히 Medium 입력에서 7.8% 차이가 난 것은 의미 있는 수치다.

    Prefill 속도 (tok/s, 첫 실행 기준)

    테스트 Q4_K_M UD-Q3_K_XL
    Short (25 tok) 119.6 72.3
    Medium (500 tok) 466.5 579.3
    Long (3946 tok) 494.3 696.5
    Very Long (15719 tok) 585.6 702.9

    짧은 입력에서는 Q4_K_M이, 긴 입력에서는 UD-Q3_K_XL이 우세했다. 긴 입력에서 UD가 더 빠른 이유는 역시 모델 크기가 작아 GPU 캐시 활용이 효율적이기 때문이다. 짧은 입력에서 Q4_K_M이 더 빠른 것은 Ollama의 추론 엔진 최적화 차이일 가능성이 높다.

    품질 비교

    동일한 프롬프트(temperature=0)로 한국어, 영어, 추론, 코딩, 요약 등 12개 과제를 수행했다. 전체 응답은 너무 길어 핵심 사례만 비교한다.

    한국어 감정 분석

    프롬프트: "처음엔 그저 막막했다. 서른다섯에 퇴사라니..." 문장의 감정 변화를 분석

    항목 Q4_K_M UD-Q3_K_XL
    감정 단계 구분 5단계 (막막함→계기→복합→전환→희망) 4단계 (막막함→계기→복합→희망)
    심리학 용어 인지적 재구성(Reframing), 양가적 감정(Ambivalence) 인지적 재구성(Reframing), Catharsis
    한국어 자연스러움 매우 자연스러움 매우 자연스러움 (thinking 포함)
    응답 토큰 1,999 2,000

    두 모델 모두 전문적인 수준의 한국어 감정 분석을 제공했다. 품질 차이는 사실상 없었다.

    영어 제약 조건 글쓰기

    프롬프트: "SPARK"의 각 글자로 시작하는 5문장, "artificial" 단어 사용 금지

    항목 Q4_K_M UD-Q3_K_XL
    문장 수 5개 (정확) 5개 (정확)
    SPARK 순서 S-P-A-R-K (정확) S-P-A-R-K (정확)
    "artificial" 사용 미사용 (정확) 미사용 (정확)
    응답 토큰 1,313 (thinking 포함) 94 (간결)

    흥미롭게도 UD-Q3_K_XL은 이 과제에서 thinking 없이 94토큰으로 간결하게 응답했고, Q4_K_M은 내부 추론을 거친 뒤 1,313토큰을 사용했다. 최종 결과물 품질은 동등했다.

    FizzBuzz (지시 따르기)

    항목 Q4_K_M UD-Q3_K_XL
    정확도 정확 정확
    출력 형식 한 줄, 쉼표 구분 한 줄, 쉼표 구분

    두 모델 모두 완벽하게 수행했다. 양자화 레벨이 낮아도 기본적인 논리 추론과 지시 따르기 능력에는 영향이 없었다.

    메모리와 컨텍스트

    항목 Q4_K_M UD-Q3_K_XL 차이
    모델 VRAM 89.3 GB 54 GB -35.3 GB (39% 절감)
    남은 메모리 (128GB 기준) ~39 GB ~74 GB +35 GB
    검증된 최대 컨텍스트 157,018 토큰 16,216 토큰* -

    * UD-Q3_K_XL의 컨텍스트 테스트는 llama-server의 -c 8192 설정으로 제한되었다. 실제로는 남은 메모리 74GB로 훨씬 더 긴 컨텍스트를 처리할 수 있다. Ollama는 자동으로 가용 메모리를 KV 캐시에 할당하므로 157K 토큰까지 가능했다.

    핵심은 35GB의 메모리 절감이다. 이 여유분으로:

    • 더 긴 컨텍스트 윈도우 (RAG, 장문 분석에 유리)
    • 다른 모델과의 동시 로딩 (임베딩 모델 등)
    • 배치 추론 병렬화

    등을 확보할 수 있다.

    Unsloth Dynamic 양자화의 작동 원리

    일반적인 양자화는 모든 레이어에 동일한 비트 수를 적용한다. Q4_K_M이면 전체가 4비트다. 반면 Unsloth Dynamic 2.0은 레이어별 중요도에 따라 다른 비트를 할당한다.

    diagram

    MoE 모델에서 이 전략이 특히 효과적인 이유는 256개 전문가 중 추론 시 8개만 활성화되기 때문이다. 비활성 전문가의 가중치는 저장만 될 뿐 실제 연산에 참여하지 않으므로, 공격적으로 낮춰도 출력 품질에 거의 영향을 주지 않는다.

    Ollama에서 Unsloth GGUF가 안 되는 이유

    이 벤치마크를 준비하면서 발견한 중요한 함정이 있다. Unsloth GGUF는 현재 Ollama에서 로드되지 않는다.

    failed to initialize model: qwen3next: layer 0 missing attn_qkv/attn_gate projections

    이 에러를 이해하려면 Ollama의 내부 구조를 알아야 한다.

    Ollama의 두 가지 추론 엔진

    Ollama는 내부에 두 개의 독립적인 추론 엔진(runner)을 갖고 있다.

    구분 llamarunner ollamarunner (Go runner)
    구현 언어 C++ (llama.cpp를 CGo로 호출) Go 네이티브
    역할 외부 GGUF 호환용 fallback 공식 라이브러리 모델 전용
    아키텍처 지원 llama.cpp가 지원하는 모든 것 Ollama가 Go로 직접 구현한 것만

    ollama pull qwen3.5:122b-a10b로 받는 공식 모델은 Go runner(ollamarunner)로 실행된다. Ollama 팀이 자체 변환 파이프라인으로 HuggingFace safetensors를 Ollama 전용 GGUF 형식으로 변환한 것이다.

    "텐서 레이아웃이 다르다"는 게 무슨 뜻인가

    신경망 모델의 가중치(weight)는 텐서(tensor)라는 다차원 배열로 저장된다. 같은 모델이라도 이 텐서를 어떤 구조로, 어떤 이름으로 파일에 쓰느냐가 다를 수 있다.

    레이아웃(layout) 차이 — 같은 가중치를 물리적으로 다르게 배치하는 것이다. 예를 들어 어텐션 연산에 필요한 Q(Query), K(Key), V(Value) 가중치를 생각해보자.

    방식 저장 구조 사용처
    llama.cpp 표준 Q, K, V를 별도 텐서로 저장
    attn_q.weight, attn_k.weight, attn_v.weight
    HuggingFace GGUF, Unsloth GGUF
    Ollama Go runner Q+K+V를 하나로 concat하여 저장
    attn_qkv.weight + 별도 attn_gate.weight
    Ollama 공식 라이브러리 모델

    Qwen3.5는 특히 복잡하다. 일부 레이어가 기존 transformer가 아닌 Gated Delta Net(linear attention + SSM을 결합한 구조)을 사용하는데, HuggingFace 원본에서 linear_attn.in_proj_qkvz라는 하나의 텐서에 Q+K+V+Z(gate)가 합쳐져 있다. Ollama converter는 이것을 attn_qkv(Q+K+V)와 attn_gate(Z)로 분리+재배치한다.

    이름(naming) 차이 — 같은 가중치에 다른 이름을 붙이는 것이다.

    # HuggingFace 원본
    self_attn.q_proj.weight
    self_attn.k_proj.weight
    input_layernorm.weight
    
    # Ollama Go runner가 기대하는 이름
    attn_q.weight
    attn_k.weight
    attn_norm.weight

    Go runner는 Go struct의 tag로 텐서를 자동 매핑하는 구조이므로, 이름이 한 글자라도 다르면 "텐서를 찾을 수 없다"는 에러가 발생한다.

    왜 이런 차이가 생기는가

    의도적인 설계 결정이다. Ollama 팀은 llama.cpp에 의존하지 않는 자체 추론 엔진을 Go로 개발 중이며, 이 과정에서 성능 최적화를 위해 텐서 레이아웃을 자체적으로 재설계했다. Q+K+V를 하나의 텐서로 합치면 메모리 접근 패턴이 개선되어 추론이 빨라질 수 있다.

    문제는 새 아키텍처(Qwen3.5 등)가 Go runner에만 먼저 구현되는 경우다. 이때 외부 GGUF는 양쪽 모두에서 실패한다.

    1. Go runner로 로드 시도 → 텐서 레이아웃/이름 불일치 → 실패
    2. llama.cpp runner로 fallback 시도 → 해당 아키텍처 미구현 → 실패

    이것이 바로 qwen3next: layer 0 missing attn_qkv/attn_gate projections 에러의 정체다. Ollama의 Go runner가 자신이 만든 형식의 attn_qkv 텐서를 찾으려 했지만, Unsloth GGUF에는 llama.cpp 표준 형식인 attn_q, attn_k, attn_v가 별도로 존재할 뿐이다.

    Ollama 팀은 이 문제를 인지하고 있으며, 외부 GGUF 호환성 패치를 지속적으로 작업 중이다. 하지만 아직 Qwen3.5 외부 GGUF 지원은 완료되지 않았다.

    해결책: llama.cpp 직접 빌드

    llama.cpp는 Unsloth GGUF의 표준 텐서 레이아웃을 그대로 인식하므로, 직접 빌드하여 사용하면 된다.

    # llama.cpp 빌드 (DGX Spark Blackwell CC 12.1)
    cmake -B build -DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES="120;121"
    cmake --build build -j$(nproc) --target llama-server
    
    # 서버 실행
    ./build/bin/llama-server \
      -m Qwen3.5-122B-A10B-UD-Q3_K_XL-00001-of-00003.gguf \
      -ngl 99 --flash-attn on -c 40960 --host 0.0.0.0 --port 8080

    HuggingFace에서 모델 다운로드하기

    Unsloth GGUF는 HuggingFace에서 다운로드한다. UD-Q3_K_XL은 3개의 split 파일로 구성되어 있다.

    # huggingface-cli 설치
    pip install "huggingface_hub[cli]"
    
    # UD-Q3_K_XL 다운로드 (~57GB)
    hf download unsloth/Qwen3.5-122B-A10B-GGUF \
      --include "UD-Q3_K_XL/*" \
      --local-dir ~/models/qwen35-unsloth-q3kxl

    llama-server에서 split GGUF를 로드할 때는 첫 번째 파일(-00001-of-00003.gguf)만 지정하면 나머지를 자동으로 인식한다.

    종합 비교

    항목 Q4_K_M (Ollama) UD-Q3_K_XL (Unsloth) 판정
    모델 크기 81 GB 54 GB UD 승 (33% 절감)
    생성 속도 22.0 tok/s 23.2 tok/s UD 승 (약 5%)
    Prefill (긴 입력) 585 tok/s 703 tok/s UD 승
    한국어 품질 우수 우수 동등
    영어/추론 품질 우수 우수 동등
    메모리 여유 ~39 GB ~74 GB UD 승
    Ollama 호환 완벽 미지원 Q4 승
    설치 편의성 ollama pull 한 줄 빌드+다운로드 필요 Q4 승

    결론: UD-Q3_K_XL로 교체할 가치가 있는가?

    대답은 "상황에 따라 다르다"이다.

    교체를 추천하는 경우:

    • 메모리 제약이 심한 환경 (96GB 이하)에서 긴 컨텍스트가 필요할 때
    • llama.cpp 직접 관리에 익숙한 사용자
    • 다른 모델과 동시 로딩이 필요할 때 (임베딩 모델, VLM 등)
    • 최대한의 생성 속도를 원할 때

    Q4_K_M을 유지하는 것이 나은 경우:

    • Ollama의 편의성이 중요한 경우 (원클릭 설치, 자동 업데이트)
    • Vision (이미지 입력) 기능이 필요한 경우
    • llama.cpp 빌드/관리가 부담되는 경우

    결국 Unsloth Dynamic 양자화는 "같은 품질에 더 작고 빠르다"는 약속을 지켰다. 다만 Ollama 생태계와의 비호환이라는 현실적인 장벽이 있다. Ollama가 외부 GGUF 형식을 지원하게 되면, UD 양자화는 사실상 표준이 될 가능성이 높다고 본다.


    이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.

Designed by Tistory.