Sigmoid
0~1 사이로 압축한다. 큰 양수·음수에서 미분이 거의 0이 되어 vanishing gradient 위험이 크다.
옛 분류기, gate 직관을 설명할 때분류: Layer 11 - AI 기초 & 머신러닝 | 선수지식: L11-10 (ML 수학 토대), L11-20 (고전 ML 개요), L11-30 (ML 패러다임 기초)
신경망(neural network)은 선형 변환과 비선형 활성화를 여러 번 합성해 입력을 출력으로 바꾸는 함수다. 역전파(backpropagation)는 출력의 손실에서 시작해 체인룰(chain rule)로 각 가중치가 손실에 얼마나 기여했는지 거꾸로 계산하는 알고리즘이다.
처음에는 신경망을 “뇌를 흉내 낸 것”보다 다음 세 문장으로 이해하는 편이 안전하다.
z = W x + b, h = activation(z)다.W와 b를 조금씩 고치는 과정이고, 그 방향을 역전파가 계산한다.트랜스포머와 LLM도 attention, residual, normalization, MLP block이 붙어 복잡해졌을 뿐, 이 토픽의 forward/backward와 메모리 구조 위에 서 있다.
신경망과 역전파는 L11의 여러 주제를 연결하는 중심축이다.
NaN, vanishing gradient, exploding gradient가 forward와 backward 중 어디서 생기는지 좁힐 수 있다.이 문서의 목표는 모든 수식을 외우는 것이 아니다. 작은 네트워크를 손으로 한 번 계산하고, 그 계산이 큰 LLM 학습 비용과 실패 신호로 어떻게 확대되는지 연결하는 것이다.
L11-20의 logistic regression과 단층 perceptron은 입력 공간을 직선 또는 초평면 하나로 나누는 모델이다. 이런 모델은 선형 분리(linear separability)가 가능한 문제에는 강하지만, XOR처럼 선형으로 나눌 수 없는 패턴에서 바로 막힌다.
XOR은 두 입력이 다르면 1, 같으면 0을 내는 함수다.
| x1 | x2 | XOR |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
좌표평면에서 (0, 1)과 (1, 0)은 양성 class이고, (0, 0)과 (1, 1)은 음성 class다. 직선 하나로 두 양성 점만 한쪽에, 두 음성 점만 다른 쪽에 두는 방법은 없다. 그래서 아무리 학습해도 단층 선형 모델은 XOR을 정확히 표현하지 못한다.
Minsky와 Papert의 Perceptrons(1969)는 perceptron의 표현 한계를 체계적으로 다뤘고, XOR은 그 직관적 사례로 널리 남았다. 핵심은 “학습을 더 오래 하면 된다”가 아니라 모델 구조 자체가 그 경계를 그릴 수 없다는 점이다.
다층 신경망은 입력을 바로 한 번에 자르지 않는다. 먼저 hidden layer에서 중간 특징(feature)을 만든 뒤, 다음 층이 그 특징을 다시 조합한다.
XOR을 직관적으로 보면 hidden layer는 다음 두 질문을 만들 수 있다.
h1 = x1 OR x2 // 둘 중 하나라도 1인가?h2 = x1 AND x2 // 둘 다 1인가?XOR = h1 AND NOT h2 // 하나 이상 켜졌지만 둘 다 켜진 것은 아닌가?실제 신경망은 OR, AND, NOT을 사람이 규칙으로 직접 넣지 않고, 가중치와 활성화 함수로 이런 중간 경계를 학습한다. 여기서 중요한 단어가 **비선형 활성화(nonlinear activation)**다. 층 사이에 비선형 함수가 들어가야 중간 표현이 꺾이고 접히며, 직선 하나로는 나눌 수 없던 패턴을 여러 조각으로 나눌 수 있다.
Universal Approximation Theorem은 충분히 넓은 hidden layer가 연속 함수를 근사할 수 있음을 말한다. 하지만 이 정리는 “아무 신경망이나 쉽게 학습된다”는 뜻이 아니다. 폭을 무한정 키우는 것은 비현실적이고, 깊이를 늘리면 표현은 효율적일 수 있지만 gradient가 사라지거나 폭발하는 새 문제가 생긴다.
다층 신경망을 만들 수 있어도, 모든 가중치를 어떻게 고칠지 계산하지 못하면 학습할 수 없다. 파라미터가 n개일 때 각 파라미터를 조금씩 바꿔 보며 손실 변화를 재는 finite difference 방식은 대략 n번의 forward 계산이 필요하다.
역전파는 이 문제를 바꿨다. forward pass에서 중간 activation을 저장하고, loss에서 시작해 체인룰을 거꾸로 적용하면 모든 파라미터의 gradient를 보통 forward 비용의 몇 배 안에서 계산할 수 있다. Rumelhart, Hinton, Williams(1986)의 backprop 논문이 이 절차를 layered network 학습의 표준 언어로 정착시켰다.
이 문서의 핵심 흐름은 다음과 같다.
선형 모델의 한계 -> 다층 + 비선형으로 표현력 확대 -> backprop으로 많은 가중치의 gradient를 효율적으로 계산 -> 깊이가 늘며 vanishing/exploding gradient와 메모리 비용이 새 병목이 됨 -> initialization, residual, normalization, checkpointing, LoRA/QLoRA 같은 대응이 등장가장 작은 신경망은 입력 벡터에 가중치를 곱하고 bias를 더한 뒤 활성화 함수를 통과시킨다.
z = w^T x + by = activation(z)x: 입력 벡터w: 입력의 각 좌표를 얼마나 볼지 정하는 가중치b: 전체 경계를 이동시키는 biasz: 활성화 전 점수, pre-activation이라고도 부른다activation: 비선형성을 넣는 함수활성화가 sigmoid이고 출력이 0~1 하나라면 이 구조는 binary logistic regression과 매우 가깝다. 출력 class가 여러 개라면 마지막 층은 보통 class 수만큼 logit을 만들고 softmax로 확률 분포를 만든다. LLM의 마지막 출력도 어휘 크기만큼 큰 softmax classifier라고 볼 수 있다. 다음 토큰 후보 전체에 점수를 주는 거대한 다중 분류기인 셈이다.
MLP(Multi-Layer Perceptron)는 여러 층을 쌓은 fully connected 신경망이다.
h1 = f1(W1 x + b1)h2 = f2(W2 h1 + b2)y = W3 h2 + b3한 줄로 쓰면 함수 합성이다.
y = W3 · f2(W2 · f1(W1 · x + b1) + b2) + b3여기서 깊이(depth)는 층을 몇 번 통과하는지, 폭(width)은 각 층의 hidden dimension이 얼마나 큰지를 뜻한다. 폭은 한 층이 만들 수 있는 중간 특징 수를 늘리고, 깊이는 중간 특징을 다시 조합하는 단계를 늘린다.
깊이가 가치 있는 이유는 같은 함수를 더 적은 단위로 나눠 표현할 수 있기 때문이다. XOR 예시처럼 첫 층이 “부분 조건”을 만들고 다음 층이 조합하면, 직선 하나로는 어려운 경계를 여러 조각으로 만들 수 있다.
활성화 함수가 없다고 해 보자.
h = W1 xy = W2 h그러면:
y = W2 (W1 x) = (W2 W1) x두 층을 쌓았지만 결국 W2 W1이라는 하나의 행렬을 곱한 것과 같다. bias까지 포함해도 affine transform 하나로 합쳐진다. 즉, 비선형성이 없으면 깊이를 늘려도 선형 모델 하나와 표현력이 크게 다르지 않다.
활성화 함수는 이 합쳐짐을 끊는다. ReLU는 음수 구간을 0으로 잘라 직선을 꺾고, GELU와 SiLU는 입력 크기에 따라 부드럽게 통과량을 조절한다. LLM의 MLP block에서 쓰는 SwiGLU/GeGLU 계열은 게이트를 넣어 어떤 hidden 정보가 통과할지 더 세밀하게 조절한다.
0~1 사이로 압축한다. 큰 양수·음수에서 미분이 거의 0이 되어 vanishing gradient 위험이 크다.
옛 분류기, gate 직관을 설명할 때-1~1 사이로 압축한다. zero-centered라 sigmoid보다 낫지만 포화 구간에서는 gradient가 작아진다.
RNN/LSTM 계열을 읽을 때max(0, x). 계산이 단순하고 양수 구간 gradient가 1이라 깊은 모델 학습을 쉽게 만들었다.
CNN, 기본 MLP, vanishing 완화 직관입력을 확률적으로 통과시키는 듯한 부드러운 함수다. Transformer 계열에서 자주 본다.
BERT, GPT-2/3 계열 MLP한 경로가 다른 경로를 gate한다. 단순 ReLU보다 표현력이 좋지만 계산과 파라미터 구조가 더 복잡하다.
LLaMA, T5, Mistral, Gemma 계열 MLP선택 기준은 “무조건 최신 활성화”가 아니다. 작은 분류기의 hidden layer 활성화로는 ReLU만으로 충분할 수 있고, 출력층은 문제에 맞게 sigmoid나 softmax를 따로 쓴다. 트랜스포머 계열을 읽을 때는 GELU/SwiGLU가 왜 쓰이는지 정도를 잡으면 된다. 학습이 불안정하면 활성화 함수와 초기화가 서로 맞는지도 함께 봐야 한다.
Forward pass는 입력에서 출력과 loss까지 한 방향으로 계산하는 단계다.
입력 x -> z1 = W1 x + b1 -> h1 = activation(z1) -> z2 = W2 h1 + b2 -> y_pred -> loss(y_pred, y_true)학습 시 forward pass는 출력만 만들고 끝나지 않는다. backward에서 다시 써야 하므로 각 층의 중간값을 저장한다.
x: 가중치 gradient를 계산할 때 필요하다.z: 활성화 함수의 미분을 계산할 때 필요하다.h: 다음 층 가중치 gradient를 계산할 때 필요하다.이 저장된 중간값을 activation이라고 부른다. 학습 메모리에서 activation이 큰 비중을 차지하는 이유가 여기에 있다. inference에서는 보통 backward를 하지 않으므로 대부분의 activation을 오래 저장할 필요가 없다. 그래서 같은 모델이라도 학습 메모리가 추론 메모리보다 훨씬 크다.
정량 감각은 다음처럼 잡는다.
activation memory의 1차 근사≈ batch_size × sequence_length × hidden_dim × num_layers × bytes × 저장되는 중간값 계수정확한 계수는 attention, MLP, normalization, framework 구현에 따라 달라진다. 그래도 batch_size나 sequence_length를 2배로 올리면 activation 계열 메모리가 대체로 선형으로 커진다는 감각은 중요하다.
2차원 입력, hidden unit 2개, 출력 1개인 아주 작은 MLP를 보자. 활성화는 ReLU, loss는 MSE다.
x = [1, 2]y_true = 1
W1 = [ [0.1, 0.2], [0.3, 0.4],]b1 = [0, 0]
W2 = [ [0.5], [0.6],]b2 = 0첫 층 pre-activation:
z1 = x @ W1 + b1 = [1, 2] @ [[0.1, 0.2], [0.3, 0.4]] = [1*0.1 + 2*0.3, 1*0.2 + 2*0.4] = [0.7, 1.0]ReLU는 양수를 그대로 통과시키므로:
h1 = ReLU(z1) = [0.7, 1.0]출력:
y = h1 @ W2 + b2 = 0.7*0.5 + 1.0*0.6 = 0.95MSE loss:
L = (y - y_true)^2 = (0.95 - 1.00)^2 = 0.0025이 예시는 작지만 큰 신경망과 같은 구조를 가진다. 행렬 곱으로 logit 또는 값을 만들고, 비선형 함수를 통과시키며, 마지막에 loss를 만든다.
Backward pass는 loss에서 시작한다. 목표는 각 파라미터가 loss에 미친 영향을 구하는 것이다.
L -> y -> W2, h1 -> z1 -> W1, x여기서 핵심은 local gradient를 곱하며 거꾸로 간다는 점이다. 한 층의 출력이 다음 층의 입력이므로, 어떤 값이 loss에 미치는 영향은 “다음 값에 미치는 영향”과 “다음 값이 loss에 미치는 영향”을 곱해 얻는다. 이것이 L11-10에서 본 chain rule이다.
위 forward 예시를 그대로 이어서 계산한다.
먼저 loss를 출력 y로 미분한다.
L = (y - y_true)^2dL/dy = 2(y - y_true) = 2(0.95 - 1.00) = -0.10W2의 gradient는 hidden activation h1과 출력 gradient의 곱이다.
dL/dW2 = h1^T · dL/dy = [[0.7], [1.0]] · (-0.10) = [[-0.07], [-0.10]]
dL/db2 = -0.10해석하면 W2의 두 가중치를 조금 키우면 y가 커지고, 현재 y=0.95는 정답 1보다 낮으므로 loss가 줄어든다. gradient가 음수이므로 gradient descent의 W_new = W_old - eta * gradient에서는 가중치가 증가한다.
다음으로 loss가 hidden activation에 미치는 영향을 구한다.
dL/dh1 = dL/dy · W2^T = -0.10 · [0.5, 0.6] = [-0.05, -0.06]ReLU의 미분은 입력 z1이 양수면 1, 음수면 0이다. 여기서는 z1 = [0.7, 1.0]이라 둘 다 양수다.
dL/dz1 = dL/dh1 * ReLU'(z1) = [-0.05, -0.06] * [1, 1] = [-0.05, -0.06]마지막으로 W1의 gradient를 계산한다. W1은 입력 x를 hidden pre-activation으로 바꿨으므로, 입력의 각 좌표와 dL/dz1의 outer product가 된다.
dL/dW1 = x^T · dL/dz1 = [[1], [2]] · [-0.05, -0.06] = [ [-0.05, -0.06], [-0.10, -0.12], ]이 숫자가 PyTorch에서 loss.backward() 후 W1.grad에 찍히는 값이다. 중요한 것은 프레임워크가 마법을 쓰는 것이 아니라, forward에서 저장한 x, z1, h1을 가지고 위 chain rule 계산을 자동으로 반복한다는 점이다.
신경망은 보통 입력과 출력보다 파라미터가 훨씬 많다. 7B 모델은 이름 그대로 대략 70억 개 파라미터를 가진다. 각 파라미터를 하나씩 흔들어 loss 변화를 보는 finite difference는 현실적이지 않다.
Reverse-mode automatic differentiation은 scalar loss 하나에 대한 많은 파라미터의 gradient를 효율적으로 계산한다. forward pass로 계산 그래프와 필요한 중간값을 만들고, backward pass로 그래프를 거꾸로 따라가며 local gradient를 누적한다.
비용 직관:
| 방식 | 필요한 계산 | 왜 문제인가 |
|---|---|---|
| finite difference | 파라미터마다 forward를 다시 실행 | 파라미터가 많으면 O(n) forward가 필요 |
| symbolic differentiation | 수식을 전개해 미분식을 직접 만듦 | 큰 모델에서는 식이 비대해지고 동적 그래프에 부적합 |
| reverse-mode autodiff/backprop | forward 1회 + backward 1회 수준으로 gradient 계산 | scalar loss와 많은 파라미터 조합에 특히 효율적 |
Backprop은 “gradient를 구한다”는 수학 문제를 “계산 그래프를 거꾸로 순회한다”는 컴퓨터 계산 문제로 바꾼다.
깊은 신경망은 많은 local gradient를 곱한다. 곱셈이 반복되면 두 가지 극단이 생긴다.
이것이 vanishing gradient와 exploding gradient다.
Sigmoid의 미분은 최대 0.25다. sigmoid 층이 여러 번 이어져 있고 다른 요인까지 단순화해 모두 0.25씩 곱한다고 보면:
10층: 0.25^10 ≈ 0.0000009520층: 0.25^20 ≈ 0.00000000000091깊은 앞쪽 층은 loss에 대한 gradient를 거의 받지 못한다. 로그에서는 loss가 잘 안 줄고, 앞쪽 layer의 gradient norm이 계속 0에 가깝게 보일 수 있다.
대응의 철학은 “곱셈 경로에서 신호가 너무 작아지지 않게 한다”다.
수치 감각은 간단하다. Xavier는 입력 fan-in과 출력 fan-out을 함께 보고 분산을 맞추고, He는 ReLU가 음수 절반을 0으로 날리는 효과를 고려해 대략 2 / fan_in 스케일을 쓴다. GPT-2/BERT 계열의 std=0.02 같은 truncated normal 초기화와 깊은 LLM의 1/sqrt(2L) 출력 projection 보정도 같은 목표를 갖는다. 깊이가 늘어도 activation과 gradient의 스케일이 한쪽으로 계속 줄거나 커지지 않게 하는 것이다.
반대로 local gradient나 가중치 스케일이 계속 1보다 크면 작은 변화가 깊이를 지나며 커진다.
1.5^20 ≈ 3,3252.0^20 ≈ 1,048,576이 경우 한 번의 update가 가중치를 크게 망가뜨릴 수 있다. 관측 신호는 더 요란하다.
inf나 NaN이 생긴다.대응은 update 폭을 줄이고 스케일을 안정화하는 쪽이다.
Residual connection은 입력을 그대로 더해 다음 층으로 보낸다.
y = x + f(x)미분 관점에서 보면 gradient는 f(x) 경로뿐 아니라 x가 그대로 지나가는 경로도 갖는다.
dy/dx = 1 + df/dxdf/dx가 작아져도 1 경로가 남기 때문에 gradient가 완전히 사라지는 것을 줄인다. ResNet 이후 깊은 네트워크와 트랜스포머가 residual을 기본 구성요소로 쓰는 이유다.
Layer Norm은 한 sample 안에서 hidden dimension의 평균과 분산을 맞춰 각 층 입력 스케일을 안정화한다. 트랜스포머에서는 norm을 어디에 두는지도 중요하다.
| 구조 | 형태 | 학습 안정성 직관 |
|---|---|---|
| Post-norm | LayerNorm(x + f(x)) | 원래 Transformer 구조. 깊어질수록 학습이 까다로울 수 있다 |
| Pre-norm | x + f(LayerNorm(x)) | 현대 LLM에서 흔하다. residual 경로가 더 직접 보존된다 |
첫 독서에서는 “residual은 gradient의 우회로, layer norm은 층별 스케일 안정화”라고 잡으면 충분하다.
LLM fine-tuning에서 GPU 메모리는 하나의 덩어리가 아니다. 다음 다섯 부분으로 나눠 봐야 한다.
총 메모리 = parameters + gradients + optimizer state + activations + workspacem, v처럼 update를 안정화하기 위해 optimizer가 들고 있는 부가 상태다.7B 모델을 mixed precision으로 full fine-tuning한다고 가정해 보자. ZeRO 논문 계열의 단순 식으로 parameters/gradients/optimizer state만 대략 16N bytes가 필요하다. 여기서 N은 파라미터 수다.
| 구성 | 크기 | 읽는 법 |
|---|---|---|
| Parameters (fp16/bf16) | 14 GB | 7B × 2 bytes. forward/backward 계산용 |
| Gradients (fp16/bf16) | 14 GB | parameter와 같은 shape |
| fp32 master weights | 28 GB | mixed precision에서 안정적인 update용 |
AdamW state m (fp32) | 28 GB | 1차 모멘트 |
AdamW state v (fp32) | 28 GB | 2차 모멘트 |
| 합계, activation 제외 | 약 112 GB | 16 × 7B bytes |
이 숫자에는 activation과 workspace가 빠져 있다. 따라서 7B full fine-tuning은 단일 80GB GPU에 단순히 올라가지 않는다. ZeRO/FSDP 같은 분산, activation 절감, batch/sequence 조정이 같이 필요해진다.
여기서 자주 생기는 오해가 있다. “7B 모델이 fp16이면 14GB니까 24GB GPU에 학습도 되지 않나?”라는 생각이다. 14GB는 추론용 parameter 저장량에 가까운 값이다. 학습에는 gradient, optimizer state, activation이 추가된다. 이 차이를 놓치면 메모리 견적이 한 자릿수 이상 틀린다.
Parameter 계열 메모리는 모델 크기 N에 주로 묶인다. 반면 activation은 입력 모양에 묶인다.
activation memory ≈ batch × seq_len × hidden_dim × layers × bytes × 계수그래서 같은 7B 모델이라도 다음 선택이 메모리를 크게 바꾼다.
| 선택 | activation 영향 | 직관 |
|---|---|---|
| batch size 2배 | 대체로 2배 증가 | 샘플을 더 많이 동시에 저장해야 한다 |
| sequence length 2배 | MLP activation은 대체로 2배, attention은 더 민감 | 긴 문맥은 중간값과 attention 비용을 키운다 |
| layer 수 증가 | 저장해야 할 activation 지점이 증가 | 깊을수록 backward에 필요한 중간값이 많다 |
| checkpointing 적용 | 저장량 감소, 재계산 증가 | 메모리와 시간을 맞바꾼다 |
Korthikanti et al.은 activation recomputation과 transformer activation 메모리 항을 더 정교하게 다루지만, 첫 독서에서는 “parameter는 모델 크기, activation은 batch·sequence·layer에 민감하다”는 구분이 더 중요하다.
메모리 절감 기법은 어떤 메모리 항목을 줄이는지가 다르다.
| 기법 | 주로 줄이는 것 | 수치 감각과 대가 | 실패 신호 |
|---|---|---|---|
| Mixed precision | parameter/activation 계산 bytes | fp32 대비 저장·연산 bytes를 줄인다. fp16은 overflow/underflow에 민감, bf16은 표현 범위가 넓다 | loss NaN, 일부 layer gradient가 0에 가까움 |
| Gradient checkpointing | activation 저장량 | 보고·실측에서 메모리 50 | 켰는데 peak memory가 5% 미만만 줄어듦 |
| FlashAttention | attention 중간값과 memory IO | attention matrix를 통째로 materialize하지 않아 긴 sequence에서 O(n²) 저장 압박을 O(n)식 block 처리로 줄인다 | 짧은 seq에서 기대만큼 속도·메모리 개선이 없음 |
| ZeRO/FSDP | optimizer state, gradient, parameter 분산 | Stage가 올라갈수록 더 많이 shard하지만 multi-GPU 통신 오버헤드와 설정 복잡도가 늘어난다 | 작은 모델·단일 GPU에서 오히려 느려짐 |
| 8-bit optimizer | AdamW optimizer state | AdamW m, v state를 8-bit로 줄여 optimizer state를 크게 낮춘다. 양자화 오차와 라이브러리 의존성이 대가다 | loss가 불안정하거나 재현성이 흔들림 |
| LoRA | 학습되는 parameter와 optimizer state | 7B급 16-bit LoRA는 24GB급 GPU가 후보가 된다. base 능력 밖의 새 능력을 만들기는 어렵고 rank 선택이 중요하다 | training loss는 감소하지만 평가 metric은 base와 거의 같음 |
| QLoRA | base model parameter 저장량 | 4-bit base + LoRA로 7B급을 6~8GB급 VRAM 사례까지 낮춘 보고가 있다. 양자화 손실과 커널 의존성이 대가다 | 특정 과업에서 LoRA보다 품질이 떨어지거나 수렴이 불안정 |
이 표는 도구 목록이 아니라 decision map이다. “메모리가 부족하다”는 한 문장만으로는 답이 나오지 않는다. 부족한 항목이 activation인지, optimizer state인지, parameter 저장량인지 먼저 나눠야 한다. FlashAttention은 다음 트랜스포머 문서에서 attention 비용 문제로 다시 다루고, ZeRO/FSDP는 scaling laws 문서에서 분산 학습의 메모리 sharding 문제로 다시 다룬다. 이 문서에서는 두 이름을 “학습 메모리의 어느 항목을 줄이는가”로만 먼저 잡으면 된다.
LoRA(Low-Rank Adaptation)는 큰 가중치 행렬 전체를 바꾸지 않고, 작은 저랭크 행렬 두 개를 학습해 변화량을 근사한다.
원래 full fine-tuning:W 전체를 업데이트
LoRA:W는 freezeΔW ≈ A BA, B만 학습rank r은 이 작은 행렬의 폭이다. r이 커지면 한 module이 표현할 수 있는 변화와 학습 parameter가 함께 늘지만, validation 품질이 반드시 좋아지는 것은 아니다. target module은 이 변화량을 어느 가중치에 붙일지 정한다. 같은 rank라도 attention projection 일부에만 붙이는지, MLP를 포함한 더 넓은 선형층에 붙이는지에 따라 trainable parameter, optimizer state, 학습 시간, 표현 범위가 달라진다.
QLoRA는 base model을 4-bit로 양자화해 저장하고, LoRA adapter는 별도로 학습한다. 그래서 base parameter 저장량을 크게 줄이면서도 adapter를 통해 과업 행동을 바꾼다. 기존 문헌과 도구 보고에서는 7B급 모델을 6~8GB급 VRAM 범위까지 낮추는 사례가 있지만, 이 숫자는 절대 보장이 아니라 구현·sequence length·batch·optimizer 설정에 민감한 기준점이다. 품질과 안정성도 데이터, rank, target module, 커널 구현에 좌우된다.
데이터 수만으로 rank나 module 범위를 정할 수는 없다. 같은 5K 예시라도 짧은 형식 교정과 새로운 코드 패턴 적응은 필요한 변화량이 다르고, SFT loss와 preference loss처럼 목적함수(objective)가 달라져도 gradient가 요구하는 adapter 용량이 달라진다. 따라서 다음 조건을 먼저 고정하고 후보를 비교한다.
| 품질 비교에서 맞출 것 | 이유 |
|---|---|
| base model과 tokenizer/template | base 능력과 입력 표현 차이를 adapter 효과로 오인하지 않기 위해서다 |
| 과업과 목적함수 | SFT, preference 학습 등 학습 신호가 다르면 같은 rank 비교가 아니다 |
| train/validation split과 학습 token 수 | 데이터 구성과 노출량 차이가 품질 차이를 설명하지 않게 한다 |
| step, data order, seed | 더 오래 또는 다른 순서로 학습한 효과를 구조 차이와 분리한다 |
| sequence length와 effective batch 조건 | activation memory와 update 조건을 맞춘다 |
품질 비교에서는 위 조건을 고정하되 실제 GPU-hour와 추정 FLOPs(Floating-Point Operations, 부동소수점 연산 횟수) 를 결과로 기록한다. rank와 module 범위가 다르면 같은 token과 step을 처리해도 계산량이 달라질 수 있으므로, compute는 이 실험의 통제 변수가 아니라 비교 결과다. 효율 자체를 비교할 때는 별도의 compute-matched 실험을 열어 같은 GPU-hour 또는 FLOPs 한도에서 각 후보가 도달한 품질을 비교한다. 이는 후속 post-training 문서의 “같은 token budget으로 구성 품질을 비교하고 총 GPU-hour·비용을 따로 센다”는 기준과 같다.
판정은 training loss 하나가 아니라 다음 5축의 Pareto frontier로 한다. 한 후보가 다른 후보보다 모든 축에서 나쁘면 제외하고, 어느 축을 개선하려면 다른 축을 희생해야 하는 후보들만 남긴다.
| Pareto 축 | 측정 예시 | 좋은 방향 |
|---|---|---|
| quality | task gold score, 형식 준수율 | 높음 |
| regression | 일반 능력·안전·다른 도메인의 회귀 폭 | 작음 |
| peak VRAM | 학습 중 최대 할당 메모리 | 작음 |
| throughput | 고정 조건에서 처리한 token/s | 높음 |
| p95 latency | 실제 배포 경로의 95백분위 응답 지연 | 짧음 |
선택 철학은 이 5축에서 제품 제약을 만족하는 가장 작은 후보를 찾는 것이다. 최고 quality 하나만 보고 큰 rank를 고르지 않는다. 측정 오차 범위에서 quality와 regression이 같으면 peak VRAM이 작고 throughput이 높으며 p95 latency가 짧은 후보를 택한다. 넓은 module 후보가 일관되게 Pareto frontier에 남으면 범위를 넓히고, rank 증가가 여러 seed에서 regression 없이 quality 이득을 내면 그때 rank를 키운다. 모든 후보가 training loss만 낮추고 validation이 정체되면 rank부터 계속 올리지 말고 데이터 중복·label 품질·base model 적합성·목적함수와 평가셋을 먼저 점검한다.
LoRA를 base weight에 merge하면 원 논문의 전제처럼 adapter 연산을 별도 추론 경로에 남기지 않을 수 있다. 여러 adapter를 동적으로 교체하거나 runtime이 merge를 지원하지 않으면 p95 latency와 메모리를 실제 배포 경로에서 다시 재야 한다.
새 지식이 자주 바뀌면 rank와 무관하게 RAG/prompt가 먼저고, base 모델이 과업을 거의 수행하지 못하면 base 교체·데이터 재설계·full fine-tuning까지 비교한다. LoRA는 base 능력을 효율적으로 조정하는 방법이지 데이터 수에 따라 자동으로 충분해지는 방법이 아니다.
PEFT(Parameter-Efficient Fine-Tuning) 는 전체 base 가중치 대신 adapter처럼 적은 파라미터만 학습하는 방법군이다. 이 절에서는 그중 LoRA 계열의 rank·target module·base 저장 형식을 비교한다.
아래 숫자는 보편 default가 아니라 후보 간 차이를 측정하기 위한 작은 grid다. 먼저 한 seed로 넓게 훑고 상위 두 후보만 추가 seed로 재확인하면 계산량을 통제할 수 있다.
| 비교 축 | 후보 예시 | 품질 비교 조건 | 판정 기준 |
|---|---|---|---|
| rank | r=8, r=16, r=32 | 같은 target modules, token·step·data order·seed | 5축 Pareto + 실제 GPU-hour/FLOPs |
| module 범위 | attention 일부 vs attention 전체 vs MLP 포함 | 같은 rank 또는 비슷한 parameter budget, token·step·order·seed | 5축 Pareto + 실제 GPU-hour/FLOPs |
| base 저장 형식 | 16-bit LoRA vs 4-bit QLoRA | 같은 adapter, token·step·data order·seed | 5축 Pareto + 실제 GPU-hour/FLOPs |
LLaMA류 이름을 예로 들면 q_proj, v_proj에서 시작해 k_proj, o_proj, gate_proj, up_proj, down_proj까지 넓힌 일곱 projection을 후보로 둘 수 있다. 하지만 module 이름과 구조는 모델마다 다르고, Hugging Face PEFT도 알려진 architecture의 기본 target 또는 target_modules="all-linear" 같은 명시적 설정을 제공한다. 따라서 이 일곱 이름을 보편 기본값으로 복사하지 말고 실제 모델의 module 목록과 지원 범위를 확인한다.
5K~50K, 50K~500K, 500K+를 LoRA rank나 full fine-tuning 선택 규칙으로 쓰는 표는 제거해야 한다. 숫자를 보존한다면 오직 가상 learning-curve 실험의 로그 간격 시작점으로만 쓴다. 예를 들어 데이터가 실제로 그 규모까지 있고 비용을 감당할 수 있다는 가정 아래 5K → 50K → 500K의 중복 제거 subset을 만든다. 이때 base model, 과업 난도, domain shift, 목적함수, rank·module, token·step·data order·seed를 기록하고 held-out metric과 train-validation gap을 그린다. rank/module의 품질 비교에서는 학습 token 노출량을 맞추고 실제 GPU-hour/FLOPs를 결과로 기록한다. 데이터 규모 자체를 비교하는 별도 실험에서는 각 subset의 고유 token 수와 총 compute를 함께 기록해야 두 효과를 섞지 않는다. 어느 지점도 method cutoff가 아니며, curve가 포화되거나 회귀가 커지면 다음 subset을 자동 실행하지 않는다.
PyTorch에서 loss.backward()는 계산 그래프를 따라 gradient를 자동으로 채운다. 사용자는 보통 다음을 확인한다.
param.grad: 각 parameter의 gradient가 존재하고 크기가 정상인지 본다.NaN 여부를 본다.torch.autograd.detect_anomaly(): backward 중 NaN이 생긴 연산을 좁히는 데 쓴다.디버깅의 첫 질문은 “무슨 명령을 실행할까”가 아니라 forward 값이 깨졌는가, backward gradient가 깨졌는가다.
| 관측 | 의미 | 먼저 볼 것 |
|---|---|---|
loss가 NaN 또는 Inf | 숫자 범위를 넘었거나 잘못된 연산이 생김 | learning rate, mixed precision, softmax/log 안정화 |
| gradient norm이 갑자기 매우 큼 | update 폭이 너무 커질 가능성 | clipping, LR, initialization |
| 모든 gradient가 거의 0 | 신호가 사라졌거나 graph가 끊김 | activation, detach 여부, fp16 underflow |
특정 layer 이후 activation이 전부 NaN | 그 앞쪽 연산에서 숫자가 깨졌을 가능성 | 첫 NaN layer hook, normalization |
| forward는 되지만 backward에서 실패 | 미분 불가능 지점 또는 backward overflow | anomaly detection, dtype, custom op |
detect_anomaly()는 유용하지만 만능은 아니다. NaN이 발생한 정확한 첫 원인이 아니라 NaN이 전파되어 터진 위치를 보여줄 수 있다. 깊은 모델에서는 layer별 hook이나 activation 통계를 함께 봐야 한다.
더 위험한 것은 학습이 정상처럼 보이지만 실제 효과가 없는 경우다.
| 신호 | 겉보기 | 실제 의미 | 먼저 확인할 것 |
|---|---|---|---|
| checkpointing을 켰는데 peak memory가 거의 안 줄어듦 | 학습은 정상 진행 | activation이 병목이 아니거나 segment가 부적절 | on/off peak memory, seq_len, layer 수, checkpoint 범위 |
| LoRA loss는 내려가는데 평가 metric이 base와 거의 같음 | training curve는 좋아 보임 | adapter/data/objective가 과업 변화를 못 담음 | token-matched sweep의 5축 Pareto, 중복·label, base 적합성 |
| fp16에서 일부 layer update가 멈춤 | 전체 loss는 천천히 감소할 수 있음 | 작은 gradient가 표현 범위 아래로 사라짐 | layer별 grad min/max, bf16, loss scaling |
| validation loss는 좋아졌는데 제품 지표가 나빠짐 | ML metric은 개선 | loss target이 제품 목표를 대변하지 못함 | gold set, 제품 지표, OOD/adversarial set |
| gradient norm은 정상인데 답변 스타일이 한쪽으로 밀림 | 학습은 안정적 | preference/fine-tuning 데이터 편향 | 길이, 거절률, 재문의율, 샘플 리뷰 |
silent failure를 줄이려면 학습 loss만 보지 않는다. 최소한 fixed validation set, 작은 gold set, layer별 gradient 통계, peak memory on/off 비교를 함께 본다.
신경망에는 여러 구조가 있다.
이 문서는 각 구조를 깊게 설명하지 않는다. 후속 L11-50 transformer 문서를 읽기 위한 공통 기반, 즉 층이 값을 만들고, loss가 gradient를 만들며, backward가 메모리를 요구한다는 구조를 잡는 것이 목적이다.
행 수가 방법을 결정하지 않는다는 점을 확인하기 위한 가상 상황을 하나 잡아 보자.
상황: 한국어 도메인 데이터 5Kbase: 7B~8B급 instruction model목표: 답변 형식과 도메인 용어 사용을 개선선택지는 세 갈래다.
| 선택지 | 메모리 직관 | 장점 | 경계 |
|---|---|---|---|
| Full fine-tuning | activation 제외 parameter 계열만 약 112GB | 모든 가중치를 바꿀 수 있음 | 단일 80GB GPU 부족, forgetting과 검증 비용 큼 |
| LoRA 후보 | base는 고정, adapter와 optimizer state만 학습 | 24GB급 GPU에서도 후보가 생김 | token-matched 품질과 compute-matched 효율 분리 |
| QLoRA | base를 4-bit로 저장하고 adapter만 학습 | 7B급을 더 작은 VRAM 범위로 낮출 수 있음 | 양자화 손실과 커널 의존성, 품질 검증 필요 |
이 가상 상황에서 adapter를 먼저 비교하는 근거는 5K 자체가 아니다. 목표가 기존 instruction model의 형식·용어 조정이고, base baseline이 일부 능력을 이미 보이며, full update의 메모리·forgetting 비용이 큰 조건을 함께 봤기 때문이다. 먼저 prompt/base baseline을 남기고, 같은 split·목적함수·학습 token 수·step·data order에서 LoRA와 QLoRA의 quality·regression·peak VRAM·throughput·p95 latency를 비교하며 실제 GPU-hour/FLOPs를 기록한다. 효율 결론이 필요하면 별도 compute-matched 실험을 수행한다. 평가가 정체되면 rank·module 한 축씩만 바꾼다.
단, 이 결론은 “QLoRA가 항상 최고”라는 뜻이 아니다. 데이터가 훨씬 많거나 과업이 코드·수학·복잡 reasoning처럼 base 능력을 크게 바꿔야 하는 영역이면 full fine-tuning이나 base model 교체가 후보가 된다. 지식이 자주 바뀌는 문제라면 fine-tuning보다 RAG가 먼저다.
PagedAttention, FlashAttention, RoPE scaling, MoE, LoRA 변형처럼 새 이름을 만나면 다음 질문으로 분해한다.
이 질문을 던지면 새 기법을 홍보 문구가 아니라 비용 모델 안에서 읽을 수 있다.
| 개념 A | 개념 B | 차이점 |
|---|---|---|
| Forward | Backward | forward는 activation과 loss를 만들고, backward는 gradient를 만든다 |
| Activation function | Activation memory | 하나는 비선형 함수, 다른 하나는 forward 중간값 저장 메모리다 |
| Sigmoid | Softmax | sigmoid는 주로 단일 0~1 출력, softmax는 여러 class 확률 분포다 |
| ReLU | GELU | ReLU는 단순한 꺾임, GELU는 부드러운 통과 함수다 |
| Xavier init | He init | Xavier는 tanh/sigmoid 계열, He는 ReLU 계열에 맞춘 분산 직관이다 |
| Vanishing | Exploding | gradient가 너무 작아지는 문제와 너무 커지는 문제다 |
| Pre-norm | Post-norm | normalization 위치가 다르며, 깊은 transformer에서는 pre-norm이 흔하다 |
| fp16 | bf16 | fp16은 정밀도 일부가 낫고, bf16은 표현 범위가 넓어 overflow에 강하다 |
| Full fine-tuning | LoRA | 모든 가중치를 바꾸는 방식과 작은 adapter만 학습하는 방식이다 |
| QLoRA | LoRA | QLoRA는 base model 저장을 4-bit로 줄인 LoRA 계열이다 |
| Gradient checkpointing | Full activation save | 일부 activation을 재계산해 메모리를 줄이는 방식과 모두 저장하는 방식 |
먼저 §4.1과 §5.1의 숫자를 손으로 다시 계산한다.
z1 = [0.7, 1.0]이 되는지 확인한다.y = 0.95, loss = 0.0025가 되는지 확인한다.dL/dy = -0.10을 계산한다.dL/dW2 = [[-0.07], [-0.10]]이 되는지 확인한다.dL/dW1 = [[-0.05, -0.06], [-0.10, -0.12]]가 되는지 확인한다.그 다음 PyTorch로 같은 값을 확인한다.
import torch
x = torch.tensor([[1.0, 2.0]])y_true = torch.tensor([[1.0]])W1 = torch.tensor([[0.1, 0.2], [0.3, 0.4]], requires_grad=True)b1 = torch.zeros(2, requires_grad=True)W2 = torch.tensor([[0.5], [0.6]], requires_grad=True)b2 = torch.zeros(1, requires_grad=True)
h = torch.relu(x @ W1 + b1)y = h @ W2 + b2loss = ((y - y_true) ** 2).mean()loss.backward()
print(f"y={y.item():.4f}, loss={loss.item():.4f}")print("dL/dW2=", W2.grad.flatten().tolist())print("dL/dW1=", W1.grad.flatten().tolist())예상 출력:
y=0.9500, loss=0.0025dL/dW2= [-0.070000..., -0.100000...]dL/dW1= [-0.050000..., -0.060000..., -0.100000..., -0.120000...]결과가 다르면 MSELoss(reduction="mean")과 sum() 차이, ReLU 음수 구간 미분 0 처리, 행렬 shape를 먼저 확인한다.
CUDA 환경이 있다면 같은 모델을 checkpointing on/off로 한 번씩 돌리고 peak memory와 시간을 비교한다.
import time, torchimport torch.nn as nnfrom torch.utils.checkpoint import checkpoint_sequential
device = "cuda"model = nn.Sequential(*[nn.Linear(4096, 4096) for _ in range(32)]).to(device)x = torch.randn(8, 4096, device=device, requires_grad=True)
def run(use_ckpt): torch.cuda.reset_peak_memory_stats() t0 = time.time() if use_ckpt: out = checkpoint_sequential(model, segments=4, input=x, use_reentrant=False) else: out = model(x) out.sum().backward() torch.cuda.synchronize() return torch.cuda.max_memory_allocated() / 1e9, time.time() - t0
print("off:", run(False))print("on :", run(True))보고 싶은 것은 절대 숫자가 아니라 방향이다. memory가 크게 줄고 시간이 늘면 checkpointing의 trade-off를 본 것이다. memory가 거의 줄지 않으면 activation이 병목이 아니거나 checkpointing segment가 부적절할 수 있다.
r=8, r=16, r=32는 보편 default가 아니라 작은 sweep 후보로 비교한다.alpha/sqrt(r)로 바꾸는 방식), LoRA-FA(LoRA with Frozen-A, A 행렬을 고정하고 B만 학습해 activation memory를 줄이는 방식)torch.autograd.grad, JAX vjp처음에는 이 키워드를 모두 공부하지 않아도 된다. 이 문서의 손계산, gradient 실패, 메모리 분해가 편해진 뒤 후속 transformer와 fine-tuning 문서에서 하나씩 연결하면 된다.
W x + b와 비선형 활성화를 여러 번 합성해 선형 모델보다 복잡한 경계를 표현한다.최종 수정: 2026-07-14