페이지 목차
이 페이지에서 검색
로컬 LLM
이 페이지에 묻기
로컬 LLM · 현재 차시와 목차 본문에서만 찾아 답합니다. 답을 지어내지 않고 해당하는 대목을 그대로 보여 주며, 여기에 없으면 없다고 알려 드립니다.
생성형 AI가 아니라 이 페이지 본문을 찾아 그대로 보여 주는 방식입니다. 외부로 나가는 요청이 없습니다.
LOCAL · LLM
로컬 LLM
생성 수정
로컬 모델 실행과 GPU·메모리 이해부터 서빙·RAG·도구 호출·업무형 앱 구현까지 차례로 살펴봅니다.
42개 문서 · 2일차 · 12 / 42
11차시 · 8bit · 4bit 양자화 실습
지도 위치 : 2일차 · ②모델 파일 층 × ③추론 엔진(Transformers) | 이전: 10차시 LLM 양자화 원리 | 다음: 12차시 GGUF와 경량 LLM 키워드 : BitsAndBytes, 8bit · 4bit 모델 로딩, GPU 메모리 사용량 비교
이 차시를 다 읽으면
BitsAndBytesConfig코드에서 8bit인지 4bit인지, 계산은 어떤 정밀도로 하는지 를 알아볼 수 있다.- GPU 메모리를 재는 세 가지 도구 가 왜 서로 다른 숫자를 내는지 말할 수 있다.
- FP16 · 8bit · 4bit 비교표를 같은 조건 에서 채우도록 AI에게 맡기고, 결과를 검수할 수 있다.
- bitsandbytes 8bit가 메모리는 줄었는데 더 느릴 수 있는 이유를 말할 수 있다.
- 메모리 숫자만으로 결정하지 않고 평가셋 결과 칸 까지 채워야 하는 이유를 말할 수 있다.
0. 이 차시의 판별 축
| # | 축 | 왼쪽 (0) | 오른쪽 (1) | 이 축으로 갈리는 것 | 큰 축 (README 5장) |
|---|---|---|---|---|---|
| 1 | 정밀도 | FP16 | 8bit · 4bit | 같은 모델의 세 가지 담는 방식 | 정밀도 (파생 축, 부모 ①) |
| 2 | 저장 정밀도 ↔ 계산 정밀도 | 저장 (4bit로 담음) | 계산 (BF16으로 펼쳐서 곱함) | load_in_4bit ↔ bnb_4bit_compute_dtype | 정밀도 (파생 축) — 한 모델 안에서 다시 가름 |
| 3 | 누가 잰 숫자인가 | 프로그램 안 (PyTorch) | 프로그램 밖 (nvidia-smi) | max_memory_allocated ↔ memory.used | — (이 과정에서 새로 세우는 축) |
앞에서 이어집니다 — 10차시 4절 지도에서 bitsandbytes는 "로딩할 때 즉석으로" 칸에 있었습니다. 원본 FP16 파일 하나만 있으면 설정 한 줄로 8bit · 4bit를 바꿔 가며 올릴 수 있어서 비교 실험에 가장 편한 도구 입니다. 10차시 1.2의 QLoRA도 이 라이브러리로 원래 가중치를 4비트(NF4)로 줄여 올려 둡니다.
1. bitsandbytes — 무엇이고 어디에 쓰는가
bitsandbytes — [양자화 라이브러리]인데, [ 원본 가중치 파일을 읽어 GPU에 올리는 순간 8bit · 4bit로 바꾼다 ]는 점에서 미리 양자화된 파일을 배포하는 GPTQ · AWQ · GGUF와 구별된다.
| bitsandbytes | GPTQ · AWQ · GGUF | |
|---|---|---|
| 필요한 파일 | 원본 (FP16/BF16) | 양자화된 파일 |
| 디스크 사용량 | 원본 크기 그대로 | 작음 |
| 로딩 시간 | 변환 시간이 더해짐 | 변환 없음 |
| 비트 바꾸기 | 설정만 바꾸면 됨 ⭐ | 파일을 다시 받아야 함 |
| 잘 맞는 자리 | 실험 · 비교 · QLoRA 학습 | 서빙 (12, 19차시) |
⚠️ 반례 — bitsandbytes는 서빙 성능용 도구가 아닙니다. 이 차시의 목적은 "같은 모델을 정밀도만 바꿔 보면 메모리 · 속도 · 품질이 어떻게 변하는가"를 눈으로 확인하는 것 이고, 운영 설정은 12차시(GGUF)와 3일차(vLLM)에서 고릅니다.
2. 코드 — 세 가지 로딩
AI가 짜 줄 코드는 대략 이런 모양입니다. 다 읽을 필요 없습니다. 🔍 표시만 봅니다.
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
MODEL = "Qwen/Qwen3-8B"
tok = AutoTokenizer.from_pretrained(MODEL)
# (A) FP16/BF16 — 기준선
model = AutoModelForCausalLM.from_pretrained(
MODEL, torch_dtype=torch.bfloat16, device_map="auto")
# (B) 8bit
cfg8 = BitsAndBytesConfig(load_in_8bit=True)
model = AutoModelForCausalLM.from_pretrained(
MODEL, quantization_config=cfg8, device_map="auto")
# (C) 4bit
cfg4 = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True)
model = AutoModelForCausalLM.from_pretrained(
MODEL, quantization_config=cfg4, device_map="auto")
🔍 읽는 포인트
| 줄 | 이것만 알아보면 됨 |
|---|---|
torch_dtype=torch.bfloat16 | 양자화 없음 . 9차시 1.3의 BF16 그대로 → 기준선 |
load_in_8bit=True | 8bit (LLM.int8() 방식) |
load_in_4bit=True | 4bit |
bnb_4bit_quant_type="nf4" | 4비트 눈금을 0 근처에 촘촘히 두는 NF4 (10차시 2.3) |
bnb_4bit_compute_dtype=torch.bfloat16 | ⭐ 저장은 4bit, 계산은 BF16으로 펼쳐서 (0절 축 2) |
bnb_4bit_use_double_quant=True | 그룹마다 둔 scale(10차시 2.2)까지 한 번 더 양자화 해 조금 더 절약 |
quantization_config=... | 이 인자가 없으면 양자화 안 된 것 — 검수 1순위 |
device_map="auto" | GPU에 다 안 들어가면 일부를 CPU로 자동 분산 ⚠️ → 12차시의 "느린 혼합 추론"이 조용히 일어날 수 있음 |
⚠️ device_map="auto"는 편하지만, FP16이 VRAM에 안 들어갈 때 에러 없이 CPU로 일부를 넘겨 비교를 망칩니다. 결과를 받을 때 model.hf_device_map에 cpu나 disk가 있는지 확인하게 하세요.
💡 옵션 이름은 transformers와 bitsandbytes 버전에 따라 바뀔 수 있습니다. 설치된 버전과 공식 문서(Transformers의 bitsandbytes 양자화 페이지)를 맥락으로 주세요.
3. ⭐ 메모리를 재는 세 가지 도구
3.1 도구별로 무엇을 재는가
nvidia-smi memory.used ─────────────────────────────────────────────── (가장 큼)
├─ CUDA 컨텍스트 (라이브러리 적재분) ← 프로그램 안에서는 안 보임
└─ PyTorch가 확보한 메모리 (reserved)
└─ 실제로 텐서에 쓰인 최대치 max_memory_allocated
├─ 가중치 ≈ get_memory_footprint() (가장 작음)
└─ KV 캐시 · 활성값 (생성 중에만)
| 도구 | 재는 것 | 언제 재나 | 비교에 쓰는 자리 |
|---|---|---|---|
model.get_memory_footprint() | 가중치(+버퍼)의 크기 | 로딩 직후 | "양자화로 가중치가 몇 분의 1이 됐나" |
torch.cuda.max_memory_allocated() | PyTorch가 실제로 쓴 최대치 | 생성 끝난 뒤 | "생성까지 포함한 최고점" |
nvidia-smi memory.used | GPU 전체에서 프로세스가 잡은 양 | 생성 중 | "다른 프로그램과 나눠 쓸 때 실제로 남는가" |
⭐ 셋은 다른 질문에 대한 답 이라 숫자가 달라야 정상입니다. 같은 도구, 같은 시점끼리만 비교합니다. 9차시 4.2의 원칙 그대로입니다.
3.2 측정 절차 (AI에게 넘길 규칙)
| # | 규칙 | 이유 |
|---|---|---|
| 1 | 정밀도마다 새 프로세스 에서 로딩 | 앞 모델의 메모리가 남아 숫자를 오염시킴 |
| 2 | 생성 전 torch.cuda.reset_peak_memory_stats() | 최대치를 0부터 다시 잼 |
| 3 | 같은 프롬프트, max_new_tokens 고정, 샘플링 끔(do_sample=False) | 출력 길이가 다르면 KV 캐시와 시간이 달라짐 |
| 4 | 첫 생성은 버리고(워밍업) 3회 이상 반복 | 첫 실행은 준비 비용 포함 (14차시) |
| 5 | 로딩 시간도 따로 잼 | bitsandbytes는 변환 시간이 로딩에 더해짐 |
4. 비교표 — 직접 채울 표
⚠️ 아래 표의 숫자 칸은 비워 둡니다. 실측값은 여러분 환경에서 채웁니다. 오른쪽 "예상 방향"은 원리(9 · 10차시)에서 나온 방향 일 뿐 측정값이 아닙니다.
| 항목 | FP16/BF16 | 8bit | 4bit (NF4) | 예상 방향 (측정 아님) |
|---|---|---|---|---|
get_memory_footprint() (GB) | 약 1 : 1/2 : 1/4 보다 조금 큼 | |||
max_memory_allocated() (GB) | 위 + KV 캐시 · 활성값 | |||
nvidia-smi memory.used (GB) | 위 + CUDA 컨텍스트 | |||
| 로딩 시간 (초) | 8 · 4bit가 더 길 수 있음 (변환) | |||
| 생성 속도 (토큰/초, 중앙값) | ⚠️ 방향을 단정할 수 없음 (4.1) | |||
| 평가셋 분류 정확도 (%) | 같거나 조금 낮음 | |||
| 평가셋 주문번호 추출 정답 (건/전체) | 숫자에 민감할 수 있음 (10차시 5.3) | |||
hf_device_map에 cpu/disk 있음? | 전부 "없음"이어야 비교가 유효 |
4.1 ⚠️ 8bit가 FP16보다 느릴 수 있다
| 관찰 | 원인 |
|---|---|
| 8bit 생성 속도 < FP16 | LLM.int8()은 튀는 값(outlier)이 있는 부분을 FP16으로 따로 계산 하고 나머지를 8bit로 계산해 합칩니다. 이 분리 · 합치기가 매 토큰마다 일어남 |
| 4bit 생성 속도도 FP16과 비슷하거나 느림 | 계산 직전마다 4bit → BF16으로 펼치는 비용 (0절 축 2) |
| 메모리는 확실히 줄었음 | 10차시 5.2 — 양자화가 확실히 주는 것은 메모리뿐 |
⭐ 그래서 이 표의 결론은 대개 이렇게 씁니다. "bitsandbytes는 이 GPU에 원래 안 들어가는 모델을 올리거나, QLoRA로 학습할 때 쓴다. 속도가 목적이면 12차시(GGUF) · 3일차(vLLM + AWQ/FP8) 의 사전 양자화 방식을 잰다." 15차시에서 이 비교를 GGUF까지 넓힙니다.
4.2 문의봇에 대입하기
24GB GPU에서 Qwen3-8B는 FP16으로도 들어갑니다(9차시 3.1). 그러면 이 실습의 질문은 "들어가느냐"가 아니라 이것입니다.
| 질문 | 어느 칸을 보나 |
|---|---|
| 4bit로 줄인 만큼 KV 캐시 자리 가 얼마나 늘어나나 | footprint 차이 → 9차시 예산식의 둘째 항 |
| 그 대가로 품질을 얼마나 잃나 | 평가셋 두 줄 |
| 속도는? | 이 엔진(bitsandbytes)에서는 답이 아님 → 15차시 |
5. 실습 — 맡기고 검수하기
지시
목표: Qwen/Qwen3-8B를 Transformers + bitsandbytes로 BF16, 8bit, 4bit(NF4) 세 가지로 로딩해 비교한다.
맥락: 설치된 transformers, bitsandbytes, torch 버전을 먼저 출력하고, Transformers 공식 문서의
bitsandbytes 페이지 기준으로 코드를 작성해. 3일차 vLLM과 섞이지 않게 이 실습 전용 가상환경을 써.
제약:
- 정밀도마다 별도 프로세스로 실행. 생성 전 reset_peak_memory_stats() 호출.
- 프롬프트 3개(내가 준 문의 예시), max_new_tokens=200, do_sample=False 고정. 워밍업 1회 후 3회 반복.
- 로딩 후 model.hf_device_map을 출력하고, cpu나 disk가 있으면 그 결과는 무효로 표시.
- 6차시 평가셋(문의 N건)으로 분류값과 주문번호 추출 결과를 정밀도별로 저장(채점은 내가 함).
보고 형식:
| 정밀도 | footprint(GB) | max_allocated(GB) | nvidia-smi used(GB) | 로딩(초) | 토큰/초(중앙값) | device_map 이상 |
그리고 평가셋 결과 파일 경로.
추측하지 말고 실행한 코드와 출력 원문을 붙여.
💡 가상환경 — [Python 실행 환경]인데, [ 프로젝트마다 설치한 패키지 묶음을 따로 둔다 ]는 점에서 컴퓨터 전체가 하나의 패키지 묶음을 같이 쓰는 기본 환경과 구별된다. bitsandbytes 실습용 transformers 버전과 3일차 vLLM이 요구하는 버전이 서로 부딪히지 않게 하려고 나눕니다.
받을 증거
- 버전 출력, 실행한 코드 파일, 정밀도별 출력 원문
hf_device_map출력- 평가셋 결과 파일 (정밀도별로 분리된 것)
내가 직접 볼 것
| 볼 것 | 이걸로 아는 것 / 왜 내가 보는가 |
|---|---|
코드에 quantization_config가 실제로 들어갔는가 | 빠지면 세 번 다 BF16. AI가 "4bit로 로딩했습니다"라고 말해도 footprint가 안 줄었으면 의심 |
| footprint가 대략 1 : 1/2 : 1/4 쪽으로 줄었는가 | 양자화가 적용됐다는 가장 쉬운 증거 |
hf_device_map에 cpu/disk가 없는가 | 있으면 속도 비교 전체가 무효 |
| 8bit 속도가 BF16보다 느리게 나왔다면 | 버그가 아니라 4.1절의 정상 현상일 수 있음. AI가 "실패"로 보고하지 않는지 |
| 평가셋 결과를 모델 이름 가리고 채점 | 같은 사람이 "4bit일 거야"를 알고 채점하면 관대하거나 엄해짐 (15차시 블라인드 비교) |
부록 A. 스스로 확인하기
| # | 질문 | 답이 있는 곳 |
|---|---|---|
| 1 | bitsandbytes는 GPTQ · AWQ · GGUF와 어떤 축으로 갈리는가? | 1 |
| 2 | 4bit 코드에서 "저장"과 "계산" 정밀도를 각각 정하는 줄은? | 2 |
| 3 | device_map="auto"가 비교 실험을 망칠 수 있는 이유는? | 2 |
| 4 | footprint · max_allocated · nvidia-smi는 각각 무엇을 재는가? | 3.1 |
| 5 | 정밀도마다 새 프로세스에서 재야 하는 이유는? | 3.2 |
| 6 | 8bit가 FP16보다 느릴 수 있는 이유는? | 4.1 |
| 7 | 24GB GPU에서 8B 모델을 4bit로 줄이는 이유는 무엇일 수 있는가? | 4.2 |
부록 B. 자주 하는 실수
| 실수 | 바로잡기 |
|---|---|
| 세 도구의 숫자를 섞어서 비교 | ❌ 같은 도구 · 같은 시점 끼리만 |
| 한 프로세스에서 세 모델을 차례로 로딩 | ❌ 앞 모델 메모리가 남음. 프로세스 분리 |
| "8bit가 느리니 실험 실패" | ⚠️ bitsandbytes 8bit는 원래 느린 경우가 많음 |
| 메모리 칸만 채우고 결정 | ❌ 평가셋 칸 이 비어 있으면 결정 근거가 아님 |
| CPU로 일부 넘어간 결과를 그대로 비교 | ❌ hf_device_map 확인. 넘어갔으면 무효 |
| bitsandbytes 결과로 운영 엔진 속도를 판단 | ❌ 서빙 속도는 GGUF · vLLM에서 따로 잼 |
부록 C. NotebookLM 복습 프롬프트
이 11차시 노트만 근거로 5문제를 내줘. 비전공자 기준으로.
"bnb_4bit_compute_dtype의 의미", "메모리 측정 도구 세 가지의 차이", "8bit가 느릴 수 있는 이유"는 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.
LangTailor