
6 GB 노트북 GPU에 맞추기 위한 3가지 모델 양자화(Quantization) 시도와 실패한 모델 하나
요약
6GB VRAM을 가진 노트북 환경에서 LLM을 구동하기 위해 Qwen, Microsoft, Meta의 모델들을 4-bit GPTQ 방식으로 양자화하고 vLLM에서 벤치마킹한 실험 기록입니다. llmcompressor를 활용한 양자화 과정에서의 메모리 관리와 라이브러리 버전 충돌 문제를 다룹니다.
핵심 포인트
- 6GB VRAM 제약 조건에서 3B/7B 모델 구동을 위한 4-bit 양자화 시도
- llmcompressor를 이용한 W4A16 GPTQ 양자화 파이프라인 구축
- 양자화 과정 중 CPU RAM 부족 및 VRAM 피크 메모리 관리의 중요성
- vLLM과 compressed-tensors 간의 라이브러리 버전 의존성 문제 해결 방법
제 GPU는 RTX 3050 Laptop 카드입니다. 6 GB의 VRAM을 가지고 있으며, 데스크톱이 점유하는 부분을 제외하면 실제로 사용할 수 있는 용량은 약 5.67 GB입니다. 이는 그리 많은 양이 아닙니다. bfloat16 형식의 3B 모델은 가중치(weights)만 약 6 GB에 달하므로 들어가지 않으며, 7B 모델은 근처에도 가지 못합니다.
그래서 저는 당연한 선택을 했습니다. 4-bit로 양자화(quantize)하여 무엇이 들어가는지 확인하는 것이었습니다. 하지만 단순히 모델 하나를 양자화하고 끝내는 대신, 세 가지 인기 있는 제품군(Qwen, Microsoft, Meta)에 대해 동일한 파이프라인을 실행하고, vLLM에서 세 모델 모두를 벤치마킹(benchmark)했으며, 어디서 작동하고 어디서 실패했는지에 대해 정직하게 기록을 남겼습니다. 제가 시작했던 네 가지 모델 중 하나는 끝내 성공하지 못했고, 그 실패가 이번 연습에서 가장 유용한 부분이 되었습니다.
모든 코드, 양자화된 모델, 그리고 원본 수치는 하단에 링크되어 있습니다.
계획
llmcompressor를 사용하여 vLLM이 네이티브로 로드할 수 있는 compressed-tensors 체크포인트를 생성하며, GPTQ를 사용한 W4A16, 그룹 크기(group size) 128 방식을 사용합니다. W4A16은 4-bit 가중치(weights)와 16-bit 활성화(activations)를 의미합니다. 이는 GPU 친화적이며, vLLM은 Marlin 커널을 통해 이를 서빙하고, 대부분의 로컬 추론(local-inference) 사용자들이 이미 실행하고 있는 형식입니다.
전체 파이프라인은 하나의 스크립트로 구성됩니다. 모델과 출력 경로를 입력하면 됩니다:
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier
...
모델은 CPU로 로드되며, llmcompressor는 교정 통계(calibration statistics)를 수집하기 위해 한 번에 한 레이어(layer)씩 GPU로 스트리밍합니다. 이를 통해 전체 모델이 RAM에 있더라도 피크 VRAM(peak VRAM) 사용량을 작게 유지할 수 있습니다. 제 컴퓨터에서 Phi-3.5-mini는 양자화 과정 동안 VRAM 피크가 약 1.6 GB였습니다. 문제는 RAM 측면입니다. 전체 bf16 모델이 메모리에 상주하며, 15 GB 사양의 기기에서 3B 모델은 이미 가용 RAM을 몇 백 MB 수준으로 밀어냅니다. 이 작업들은 nohup을 사용하여 백그라운드에서 실행하세요. 지난번에 그렇게 하지 않았을 때는 교정 메모리 압박(calibration memory pressure)으로 인해 터미널이 죽어버렸습니다.
첫 번째 주의사항: 환경이 이미 깨져 있었다
이 모든 것을 실행하기 전에, 임포트(import)가 실패했습니다:
ModuleNotFoundError: No module named 'compressed_tensors.distributed'
llmcompressor 0.12 버전은 compressed-tensors 0.17.x 버전을 요구하지만, vLLM 0.22 버전은 0.15.0.1 버전을 고정(pin)하고 있으며, 어떤 이유로 인해 버전이 다시 다운그레이드되어 있었습니다. 해결 방법은 더 최신 버전을 설치하고 pip의 경고를 무시하는 것입니다:
pip install "compressed-tensors==0.17.1"
# pip 경고: vllm 0.22.0은 compressed-tensors==0.15.0.1을 요구합니다
vLLM은 경고를 출력한 뒤에도 0.17.1 버전으로 생성된 모델을 어쨌든 로드합니다. 두 경우 모두 임포트(import)는 잘 작동합니다. 양자화(quantizing)와 서빙(serving) 사이에 하나의 가상 환경(venv)을 공유하는 경우(저도 그렇게 합니다), 이 사실을 알아두는 것이 좋습니다.
성공적인 실행: Phi-3.5-mini 및 Llama-3.2-3B
양자화는 명령어 하나로 수행됩니다:
HF_HUB_OFFLINE=1 python quantize.py \
--model microsoft/Phi-3.5-mini-instruct \
--out output/Phi-3.5-mini-instruct-W4A16-G128
HF_HUB_OFFLINE=1 설정이 중요합니다. 한 번은 일시적인 DNS 오류 때문에 이미 캐시된 모델임에도 불구하고 Hugging Face 재검증(revalidation) 재시도를 하느라 6분을 허비한 적이 있습니다.
이 명령은 레이어(layer)를 순회하며 결과를 기록합니다:
(33/33): Propagating: 100%|██████████| 512/512
Compressing model: 100%|██████████| 128/128
Writing model shards: 100%
...
Phi 모델은 약 34분 만에 7.6 GB에서 2.2 GB로 줄어들었습니다. Meta 모델의 경우, 라이선스 승인 절차를 거칠 필요가 없는 비폐쇄형(non-gated) 미러인 unsloth/Llama-3.2-3B-Instruct를 사용했습니다. 이 모델 역시 깔끔하게 양자화되었으며, VRAM 사용량은 피크 시 약 2 GB 정도로 한계치에 전혀 근접하지 않았습니다.
두 개를 해냈습니다. 그다음에는 7B 모델을 시도했습니다.
벽에 부딪히다: 6 GB 환경에서 양자화되지 않는 Qwen2.5-7B
동일한 명령어로 Qwen/Qwen2.5-7B-Instruct를 실행했습니다. 두 번째 레이어에서 실패했습니다:
torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 1.34 GiB.
GPU 0 has a total capacity of 5.67 GiB of which 484.69 MiB is free.
...
...
그 1.34 GiB 할당이 바로 실마리입니다. GPTQ는 각 선형 레이어 (linear layer)에 대해 레이어의 입력 차원의 제곱 크기를 가진 헤시안 (Hessian)을 구축합니다. Qwen2.5-7B의 down_proj는 18944 차원의 입력을 받으므로, 그 헤시안은 18944의 제곱에 4바이트를 곱한 값인 약 1.4 GB가 되며, GPTQ는 이를 GPU에서 역행렬 계산 (invert) 해야 합니다. 해당 할당이 이루어지기 전 이미 카드는 약 3.9 GB를 점유하고 있었기에, 임계치를 넘겨버린 것입니다.
저는 다음과 같은 순서로 명백한 해결책들을 시도해 보았습니다:
- 더 작은 보정 데이터 (Smaller calibration). 2048 토큰의 샘플 512개에서 512 토큰의 256개로, 다시 512 토큰의 64개로 줄였습니다. 변화는 없었습니다. GPU는 매번 동일하게 3.9 GB에 고정되어 있었고, 이는 상주 메모리 (resident memory, 임베딩 및 활성 레이어)가 고정되어 있으며 보정 데이터에 의존하지 않음을 의미했습니다.
offload_hessians=True. 이는 헤시안을 CPU에 유지합니다. GPU 사용량은 낮아졌고 작업이 몇 초 만에 종료되지 않고 한 레이어에서 9분 동안 버텼지만, 여전히 OOM (Out of Memory)이 발생했습니다. 역행렬 계산을 위해 헤시안이 다시 GPU로 돌아와야 하기 때문입니다.expandable_segments:True. 파편화된 예약 메모리를 회수합니다. 약간의 도움이 되었으나 충분하지 않았습니다.- GPTQ 대신 AWQ 사용. AWQ는 레이어당 거대한 헤시안을 피합니다. 기대를 걸었으나, 내부적으로 동일한 순차적 파이프라인 (sequential pipeline)을 사용하기 때문에 정확히 같은 지점에서 OOM이 발생했습니다.
CUDA_VISIBLE_DEVICES=""를 사용한 CPU 전용 모드. 이 방식은 작동합니다. RAM과 스왑 (swap)을 사용하므로 6 GB의 한계가 없습니다. 하지만 매우 느립니다:
(2/29): Calibrating: 57%|█████▋ | 146/256 [08:42<06:46, 3.70s/it]
보정 샘플당 3.7초가 소요되는데, 이는 레이어당 약 16분, 전체 모델에는 약 10시간이 걸린다는 뜻입니다. 벤치마크 용도로는 부적합합니다.
솔직한 결론을 내리자면, 이 카드와 이 툴체인 (toolchain) 환경에서 GPU 기반 GPTQ는 파라미터 수가 약 30억(3 billion) 개 정도가 한계입니다. 7B 모델의 down_proj 헤시안과 고정된 상주 메모리의 합이 맞지 않으며, 어떤 보정 기술로도 이를 바꿀 수 없습니다. 그래서 저는 7B 대신 down_proj 입력이 8192 차원인 Llama-3.2-3B로 교체했습니다. 이 모델의 헤시안은 약 268 MB였으며, 아무런 문제 없이 양자화(quantization)를 마쳤습니다.
결론은 이렇습니다. 만약 당신이 6 GB 그래픽 카드를 가지고 있고, 그 카드에서 7B 모델을 직접 양자화(quantization)할 수 있을지 궁금하다면, llmcompressor의 순차적 GPTQ 방식으로는 GPU에서 불가능하다는 것이 답입니다. 다른 곳에서 양자화를 진행한 뒤 결과물을 다운로드해야 합니다.
(참고로, CPU 시도를 하기 전에 24 GB의 스왑(swap) 공간을 추가했습니다. bf16 형식의 7B 모델은 15 GB이며 제 컴퓨터의 RAM은 15 GB이기 때문입니다. Btrfs 파일 시스템에서는 스왑 파일이 NOCOW(Copy-on-Write 비활성화) 설정이 되어 있어야 하며, 완전히 할당되어 있어야 swapon 명령이 거부되지 않습니다.)
벤치마크 (The benchmark)
세 가지 모델, 하나의 파이프라인을 사용했으며, 공정한 비교를 위해 세 모델 모두 동일한 설정인 vLLM 0.22, 최대 길이(max length) 4096, gpu_memory_utilization=0.88 환경에서 테스트했습니다.
| 모델 (Model) | 계열 (Family) | BF16 | W4A16 | 비율 (Ratio) | KV 예산 (토큰) | 단일 스트림 (Single-stream) | 배치 x32 (Batched x32) |
|---|---|---|---|---|---|---|---|
| VibeThinker-3B | Qwen2.5-3B reasoning | 5.8 GB | 2.07 GB | 2.8x | 53,104 | 43.9 tok/s | 186 tok/s |
| ... |
두 가지 사실이 눈에 띕니다.
KV 예산(KV budgets)이 비슷하지 않습니다. 세 모델 모두 디스크 용량은 약 2.2 GB로 비슷하지만, 동일한 카드에서 처리할 수 있는 컨텍스트(context) 범위는 4,608 토큰(Phi)에서 53,104 토큰(VibeThinker)까지로, 11배나 차이가 납니다. 이것은 파일 크기가 아니라 아키텍처(architecture)의 차이입니다. VibeThinker는 공격적인 그룹화된 쿼리 주의(grouped-query attention)를 사용하는 Qwen2.5-3B 모델이므로, 각 토큰의 키-값(key-value) 상태가 매우 작아 예산이 매우 큽니다. 반면 Phi-3.5-mini는 더 큰 모델(3.8B)이며 KV 점유율(footprint)이 더 높기 때문에, 가중치(weights)가 로드되고 나면 캐시(cache)를 위한 공간이 훨씬 적게 남습니다. 작은 그래픽 카드에서 실행할 모델을 선택할 때, 가중치 크기는 모델이 로드될 수 있는지를 알려줍니다. 하지만 KV 기하학(KV geometry)은 실제로 얼마나 많은 컨텍스트를 사용할 수 있는지를 알려주며, 나중에 당신을 곤란하게 만드는 숫자는 바로 이것입니다.
성능(Capability)은 처리량(throughput)을 희생합니다. VibeThinker는 36개의 레이어(layers)를 가진 추론 모델(reasoning model)입니다.
Llama의 1,404 tok/s와 비교했을 때, VibeThinker는 배치(batched) 기준 186 tok/s로 세 모델 중 압도적으로 느립니다.
모든 토큰마다 추가된 깊이(depth)에 대한 대가를 치르게 됩니다.
4비트(4-bit)가 추론 능력을 망가뜨렸을까?
이것이 실제로 중요한 질문입니다. 저는 세 모델 모두에게 동일한 프롬프트(prompt)를 주었습니다:
"페르마의 소정리(Fermat's little theorem)를 서술하고, 이를 사용하여 3^100 mod 7을 계산하시오." 정답은 4입니다. 세 모델 모두 정확한 풀이 과정과 함께 정답을 맞혔습니다. VibeThinker는 심지어 자신의 사고 과정(thinking)까지 보여주었습니다:
[VibeThinker] ... 3^100 = (3^6)^16 * 3^4 ≡ 1^16 * 3^4 ≡ 81 ≡ 4 (mod 7).
최종 답변: \boxed{4}.
[Phi-3.5] ... 3^4 = 81, 81 mod 7 = 4. 따라서 3^100 mod 7 = 4.
...
세 모델 모두 처음 10개의 소수(primes)도 정확하게 나열했습니다. 이 종류의 모델과 이러한 유형의 작업에 있어서, W4A16(4-bit weight, 16-bit activation) 양자화는 제가 측정할 수 있는 수준의 손실을 일으키지 않았습니다. 이는 VibeThinker를 양자화했을 때 4비트 버전이 여전히 수학 계산을 정확하게 수행했던 저의 이전 경험과 일치합니다.
한 가지 주의사항을 언급하자면, 이것은 MMLU와 같은 벤치마크가 아닌 일시적인 점검(spot check)이라는 점입니다. 일반적으로 4비트 양자화(4-bit quantization)는 정확도를 떨어뜨리며, 저 또한 이전에 문제가 되는 것을 본 적이 있습니다 (한 번은 fp8 KV 캐시(KV cache)가 bf16은 맞혔던 "처음 10개의 소수를 나열하시오"라는 프롬프트를 망쳐놓은 적이 있습니다). 저는 이 특정 모델들이 이 특정 프롬프트들에서 잘 버텨냈다는 사실만을 보고하는 것이며, 그 이상의 결론을 내리지는 않습니다.
이 작업을 시작하려는 사람에게 해주고 싶은 말
- 하나의 스크립트가 여러 모델 제품군에 걸쳐 범용적으로 적용됩니다. Qwen, Phi, Llama 모두 동일한
GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])레시피로 양자화(Quantization)됩니다. 아키텍처의 차이는 양자화 시점이 아닌 서빙(Serving) 시점에 나타납니다. - 6 GB 그래픽 카드에서는 GPU 양자화를 위해 대략 3배의 크기 감소와 30억~40억(3 to 4 billion) 개의 파라미터 상한선을 계획하십시오.
- 파일 크기뿐만 아니라 KV 버젯(KV budget)을 확인하십시오. 이는 가중치(Weights)보다 더 많이 변동합니다.
- 양자화는 서빙이 아니라 메모리를 많이 소모하는 단계입니다. 별도로 실행하고, VRAM이 아닌 RAM을 모니터링하십시오.
코드는 아래에 링크된 quantize.py와 bench.py 하나씩으로 구성되어 있습니다. 세 가지 모델은 Hub에 있습니다. 만약 그래픽 카드가 작아서 모델이 들어가지 않는다면, 그 과정은 짧으며 이제 여러분은 그것이 어디서 끝나는지 정확히 알게 될 것입니다.
링크
- Hub의 모델들:
- 코드:
quantize.py및bench.py파이프라인, github.com/syedazeez337/vllm-lab (quant-lab/) - 하드웨어: RTX 3050 Laptop, 6 GB, vLLM 0.22, torch 2.11 + CUDA 13, Python 3.12
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기
