본문으로 건너뛰기
페이지 목차
이 페이지에서 검색

로컬 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 메모리 사용량 비교

이 차시를 다 읽으면

  1. BitsAndBytesConfig 코드에서 8bit인지 4bit인지, 계산은 어떤 정밀도로 하는지 를 알아볼 수 있다.
  2. GPU 메모리를 재는 세 가지 도구 가 왜 서로 다른 숫자를 내는지 말할 수 있다.
  3. FP16 · 8bit · 4bit 비교표를 같은 조건 에서 채우도록 AI에게 맡기고, 결과를 검수할 수 있다.
  4. bitsandbytes 8bit가 메모리는 줄었는데 더 느릴 수 있는 이유를 말할 수 있다.
  5. 메모리 숫자만으로 결정하지 않고 평가셋 결과 칸 까지 채워야 하는 이유를 말할 수 있다.

0. 이 차시의 판별 축

#축왼쪽 (0)오른쪽 (1)이 축으로 갈리는 것큰 축 (README 5장)
1정밀도FP168bit · 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와 구별된다.

bitsandbytesGPTQ · 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=True8bit (LLM.int8() 방식)
load_in_4bit=True4bit
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.usedGPU 전체에서 프로세스가 잡은 양생성 중"다른 프로그램과 나눠 쓸 때 실제로 남는가"

⭐ 셋은 다른 질문에 대한 답 이라 숫자가 달라야 정상입니다. 같은 도구, 같은 시점끼리만 비교합니다. 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/BF168bit4bit (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 생성 속도 < FP16LLM.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. 스스로 확인하기

#질문답이 있는 곳
1bitsandbytes는 GPTQ · AWQ · GGUF와 어떤 축으로 갈리는가?1
24bit 코드에서 "저장"과 "계산" 정밀도를 각각 정하는 줄은?2
3device_map="auto"가 비교 실험을 망칠 수 있는 이유는?2
4footprint · max_allocated · nvidia-smi는 각각 무엇을 재는가?3.1
5정밀도마다 새 프로세스에서 재야 하는 이유는?3.2
68bit가 FP16보다 느릴 수 있는 이유는?4.1
724GB 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가 느릴 수 있는 이유"는 반드시 포함해줘.
답은 내가 답한 다음에 채점하고, 틀린 이유를 노트의 절 번호를 근거로 설명해줘.