트랜스포머·PEFT 2일 과정5장 · Decoder-only LLM과 학습 단계7 / 15← 이전목차다음 →MetaContents

5장. Decoder-only LLM과 학습 단계

Qwen 설계도 읽기

가구를 조립할 때 설명서 첫 장에는 부품 목록이 있습니다. "긴 나사 24개, 선반 2장"처럼 이름과 개수, 크기가 적혀 있습니다. print(model) 이 바로 그 부품 목록입니다. 괄호 안의 이름이 부품, (0-23): 24 x 가 개수, in_features, out_features 가 크기입니다. Qwen 은 Alibaba Cloud 가 공개한 Decoder-only LLM 패밀리로, 0.5B 부터 72B 까지 여러 크기가 있고 Apache 2.0 라이선스라 상업적으로도 쓸 수 있습니다. 이 과정은 가장 작은 Qwen2.5-0.5B-Instruct(약 4.9억 개)를 씁니다.

임베딩, Attention, FFN, 24층 반복, lm_head 의 다섯 칸으로 print(model) 을 위에서 아래로 읽는 흐름도

그림은 실습에서 찍을 print(model) 을 위에서 아래로 읽는 차례입니다. 토큰 번호가 임베딩 표에서 896칸 성격표가 되고, 같은 Decoder 블록 24개를 차례로 지난 뒤, lm_head 에서 어휘 151,936개의 점수표가 됩니다.

줄모양뜻쉽게 말하면
Qwen2ForCausalLM겉 상자다음 토큰 맞히기(Causal LM)용 Qwen2제품 이름
(embed_tokens): Embedding(151936, 896)151,936 × 896토큰 번호마다 숫자 896개짜리 행행 15만 개, 열 896개인 DataFrame
(0-23): 24 x Qwen2DecoderLayer24층같은 모양의 Decoder 블록 24개같은 선반 24장
q_proj896 → 896, bias 있음Query 만들기(머리 14개 × 64)찾는 말
k_proj, v_proj896 → 128, bias 있음Key·Value 만들기(머리 2개 × 64, GQA)제목표와 내용
o_proj896 → 896, bias 없음머리들의 결과를 다시 896칸으로 합치기전문가 의견 합치기
gate_proj, up_proj896 → 4864FFN 에서 칸을 5.4배로 넓히는 두 갈래넓은 작업대 두 개
down_proj4864 → 896넓힌 칸을 다시 896 으로 줄이기결과를 원래 크기로
act_fn: SiLUActivation()숫자 없음gate 쪽에 거는 SiLU문지기
input_layernorm, post_attention_layernorm(896,)Attention 앞, FFN 앞의 RMSNorm요리 전 재료 다듬기
(norm)(896,)24층 뒤의 마지막 RMSNorm마지막 눈금 맞추기
(rotary_emb)숫자 없음RoPE 위치 정보시계 바늘 각도
(lm_head): Linear(896, 151936)896 → 151,936다음 토큰 점수표 만들기후보 15만 개에 점수 매기기

Embedding(151936, 896) 은 파라미터가 151,936 × 896 = 136,134,656개입니다. 토크나이저가 아는 토큰은 151,665개인데 표의 행은 151,936개입니다. 남는 행은 계산을 빠르게 하려고 크기를 맞춰 둔 빈자리입니다. lm_head 는 임베딩 표와 같은 숫자를 함께 씁니다(tie_word_embeddings). 그래서 파라미터 수를 셀 때 한 번만 셉니다. 프로젝트에서 확인합니다.

짧은 예시

Python
# AutoConfig = 모델의 설정표(config.json)만 불러오는 도구. 가중치는 내려받지도 올리지도 않는다
from transformers import AutoConfig

cfg = AutoConfig.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
# 설정표에서 print(model) 의 숫자들을 꺼낸다
h, ffn, vocab, layers = cfg.hidden_size, cfg.intermediate_size, cfg.vocab_size, cfg.num_hidden_layers
kv = cfg.num_key_value_heads * (h // cfg.num_attention_heads)   # 2 × 64 = 128
print(h, ffn, vocab, layers, kv)                    # 결과: 896 4864 151936 24 128

# 층 하나의 파라미터를 손으로: 가중치(입력 × 출력) + bias(출력)
attn = (h * h + h) + (h * kv + kv) * 2 + h * h      # q(bias 있음) + k, v(bias 있음) + o(bias 없음)
mlp = h * ffn * 3                                   # gate, up, down 모두 bias 없음
norm = h * 2                                        # RMSNorm 두 개
print(f"Attention {attn:,} / FFN {mlp:,} / Norm {norm:,}")
# 결과: Attention 1,836,160 / FFN 13,074,432 / Norm 1,792

# 전체 = 임베딩 + 24층 + 마지막 norm (lm_head 는 임베딩 표를 함께 써서 더하지 않는다)
total = vocab * h + (attn + mlp + norm) * layers + h
print(f"전체 {total:,}개, BF16 메모리 {total * 2 / 1e9:.2f} GB")
# 결과: 전체 494,032,768개, BF16 메모리 0.99 GB

이 코드가 뜻하는 것

가구 설명서의 부품 목록만 보고 나사 개수를 센 것과 같습니다. 모델을 불러오지 않고 설정표의 숫자 다섯 개(896, 4864, 151936, 24, 128)만으로 전체 파라미터 494,032,768개를 맞혔습니다. 손으로 따라가 보면 FFN 하나가 896 × 4864 = 4,358,144개이고 세 개면 13,074,432개입니다. 층 하나 안에서 FFN 이 Attention(1,836,160개)의 약 7배입니다.

줄하는 일쉽게 말하면
AutoConfig.from_pretrained(...)config.json 만 읽기설명서 첫 장만 펴기
(h * h + h)q_proj 의 가중치 + bias896 × 896 + 896
h * ffn * 3gate, up, down큰 작업대 세 개
vocab * h + (...) * layers + h임베딩 + 24층 + 마지막 norm부품 전체 합
total * 2 / 1e9BF16 은 숫자 하나에 2바이트메모리 = 파라미터 수 × 바이트 수

메모리는 494,032,768 × 2바이트 = 988,065,536바이트, 약 0.99GB 입니다. 같은 모델을 FP32(4바이트)로 올리면 약 1.98GB, 4비트(0.5바이트)로 줄이면 약 0.25GB 입니다. 9장의 QLoRA 가 이 계산을 씁니다.

주의할 점

  • 이 계산은 가중치만 센 것입니다. 실제로 생성하거나 학습할 때는 중간 계산 값, 기울기, 옵티마이저 상태가 더해져 몇 배가 듭니다. 6장, 8장에서 GPU 메모리를 볼 때 이 차이를 기억합니다.
  • 노트북은 "Attention 과 FFN 의 선형층 이름을 확인하면 LoRA 를 붙일 위치를 고를 수 있다"고 말합니다. 8장의 target_modules 에 들어가는 q_proj, v_proj 같은 이름이 바로 이 표의 이름입니다.
← 이전 절5장 · 최신 LLM 구조는 무엇이 달라졌나다음 절 →5장 · LLM 이 만들어지는 세 단계