차분 어텐션(Differential Attention) V2
소프트맥스 어텐션(softmax attention)은 일치하지 않는 모든 토큰에도 약간의 확률을 흘려 보냅니다. 토큰이 10만 개에 달하는 긴 문맥에서는 이런 잡음이 누적되어 정작 중요한 신호를 덮어 버립니다. 차분 트랜스포머(Differential Transformer; Ye et al., ICLR 2025)는 어텐션을 두 소프트맥스의 차이로 계산해 두 분기(branch)가 공유하는 잡음 바닥(noise floor)을 빼는 방식으로 이 문제를 해결합니다. DIFF V2(Microsoft, 2026년 1월)는 이를 프로덕션 스택에 맞게 다시 쓴 버전으로, 기준 트랜스포머(baseline Transformer)와 동일한 디코드 지연(decode latency)을 달성하고, 별도의 커스텀 커널(custom kernel)이 필요하지 않으며, FlashAttention과도 호환됩니다. 이 강의는 V1부터 V2까지 처음과 끝(end-to-end)을 모두 짚어 보고, 표준 라이브러리 Python만으로 실행할 수 있는 차분 연산(difference operation)의 장난감(toy) 구현을 함께 제공합니다.
유형: Build
언어: Python (표준 라이브러리)
선수 지식: Phase 7 · 02 (self-attention), Phase 7 · 15 (attention variants), Phase 10 · 14 (architecture walkthrough)
예상 시간: 약 60분
학습 목표
- 소프트맥스 어텐션에 잡음 바닥(noise floor)이 왜 생기는지, 그리고 문맥 길이가 길어질수록 왜 커지는지 정확히 설명합니다.
- 차분 어텐션(differential attention) 수식을 유도하고, 두 분기 사이의 뺄셈(subtraction)이 공유 잡음 성분만 지우면서 신호는 보존하는 이유를 설명합니다.
- V1에서 V2로 넘어오면서 무엇이 더 빨라졌고, 무엇이 더 단순해졌으며, 무엇이 더 안정해졌는지, 그리고 각각의 변화가 프로덕션 사전학습(production pre-training)에 왜 필요했는지를 설명합니다.
- 순수 Python으로 차분 어텐션을 처음부터 구현하고, 신호와 잡음이 섞인 합성 질의(synthetic signal-plus-noise query)에서 잡음 상쇄(noise-cancellation) 성질이 실제로 나타나는지 경험적으로 검증합니다.
문제
표준 소프트맥스 어텐션에는 규모가 커질수록 운영상 골칫거리가 되는 수학적 성질이 있습니다. 질의 벡터(query) q에 대해 어텐션 가중치(attention weight)는 softmax(qK^T / sqrt(d))로 계산됩니다. 소프트맥스는 결과로 정확한 0을 만들지 못하고, 질의와 무관한 모든 토큰에도 약간의 양의 질량(positive mass)을 나눠 줍니다. 이렇게 흘려보내는 질량이 곧 잡음이고, 그 총량은 문맥 길이에 비례해 커집니다. 12만 8천 개의 토큰이 있는 상황에서 일치하지 않는 각 토큰이 확률의 0.001%만 가져가더라도, 그 가운데 127,999개가 모이면 전체의 약 12%를 차지합니다. 결국 모델은 문맥 길이에 따라 함께 커지는 잡음 바닥을 피해 가는 법을 학습해야 합니다.
이 문제는 실제로 어텐션 헤드 간섭(attention-head interference)이라는 형태로 드러납니다. 긴 문맥 검색 증강 생성(long-context Retrieval-Augmented Generation; RAG)에서 잘못된 인용을 만들어 내는 환각(hallucinated citation)이 생기고, 10만 토큰 규모의 검색 과제(retrieval task)에서는 가운데가 사라지는 실패(lost-in-the-middle failure)가 발생하며, 3만 2천 토큰을 넘어가는 건초더미 속 바늘 찾기(needle-in-haystack) 벤치마크에서는 미묘한 정확도 저하가 나타납니다. 차분 트랜스포머 논문(arXiv:2410.05258, ICLR 2025)은 이러한 격차를 정량적으로 측정했고, 같은 크기의 기준 모델보다 낮은 혼란도(perplexity), 더 높은 장문 정확도(long-context accuracy), 더 적은 환각(hallucination)을 보고했습니다.
다만 DIFF V1에는 최전선(frontier) 사전학습 파이프라인에 들어가기 어려운 세 가지 문제가 있었습니다. 디코드 한 스텝마다 값 캐시(value cache)를 두 번 적재해야 했고, FlashAttention 호환성을 깨뜨리는 커스텀 CUDA 커널이 필요했으며, 헤드별 RMSNorm(per-head RMSNorm)이 70B 이상 규모의 장기 학습을 불안정하게 만들었습니다. DIFF V2(Microsoft unilm 블로그, 2026년 1월 20일)는 이 세 가지를 모두 해결했습니다. 이 강의에서는 두 버전을 차례로 살펴보고, 차분 연산자(difference operator)를 직접 구현하며, 장난감 질의 위에서 잡음 상쇄 성능을 벤치마크해 봅니다.
개념
소프트맥스의 잡음 바닥
질의 벡터 q와 키 행렬 K = [k_1, ..., k_N]이 주어졌을 때 어텐션 가중치는 다음과 같이 정의됩니다.
w_i = exp(q . k_i / sqrt(d)) / sum_j exp(q . k_j / sqrt(d))
어떤 w_i도 결코 0이 되지 않습니다. 가령 k_i가 q와 완전히 무관하더라도 내적 점수(score) q . k_i는 0이 아니라 평균이 0이고 분산이 ||q||^2 / d인 값 주변에서 흔들립니다. 소프트맥스 정규화(softmax normalization) 이후에도 관련 없는 각 토큰은 가중합(weighted sum)에 O(1/N)만큼 기여하고, 이를 전부 더하면 O((N-1)/N) = O(1)이 되어 결코 작지 않은 양에 이릅니다.
모델이 실제로 원하는 것은 일종의 하드 top-k(hard top-k)에 가까운 동작입니다. 즉 일치하는 토큰에는 큰 가중치를 주고 나머지는 0에 가까운 값으로 눌러 두는 것이지요. 그러나 소프트맥스는 본질적으로 너무 매끄러워서 이런 동작을 그대로 만들어 내기 어렵습니다.
차분 어텐션의 아이디어
각 헤드(head)의 Q, K 사영(projection)을 둘로 나누어 Q = (Q_1, Q_2), K = (K_1, K_2)로 둡니다. 그리고 두 개의 어텐션 맵(attention map)을 계산합니다.
A_1 = softmax(Q_1 K_1^T / sqrt(d))
A_2 = softmax(Q_2 K_2^T / sqrt(d))
출력은 다음과 같이 정의됩니다.
DiffAttn = (A_1 - lambda * A_2) V
두 맵을 빼는 연산은 양쪽이 공유하는 잡음 분포(noise distribution)를 상쇄해 줍니다. 초기화 직후에는 두 맵 모두 12만 7천 개의 관련 없는 토큰에 거의 균일한 가중치를 부여하므로, 그 성분은 서로 지워집니다. 반면 실제로 관련 있는 몇 개의 토큰에 집중된 신호는 두 맵에서 같은 크기로 나타날 때에만 사라지는데, 학습이 진행되면 이런 상황은 거의 만들어지지 않습니다.
lambda는 헤드마다 학습되는 스칼라(scalar) 값으로, 보통 lambda = exp(lambda_q1 dot lambda_k1) - exp(lambda_q2 dot lambda_k2) + lambda_init 형태로 매개변수화(parameterize)합니다. 음수도 가능하며, 초기값 lambda_init은 보통 0.8 같은 작은 양수로 둡니다.
헤드별 잡음 제거(noise-canceling)와의 유사성
같은 목소리를 녹음하는 두 대의 잡음 섞인 마이크(noisy microphone)를 떠올려 봅니다. 두 마이크는 모두 화자(speaker)의 목소리와 상관관계가 있는 배경 잡음(correlated background noise)을 함께 잡아냅니다. 한쪽을 다른 한쪽에서 빼면 공유 잡음은 줄어들지만, 두 신호의 위상(phase)이나 진폭(amplitude) 차이가 충분히 크다면 목소리는 완전히 상쇄되지 않고 살아남습니다. 헤드별 lambda는 바로 이 균형을 학습합니다.
V1 대 V2: 차이점
V1은 기준 트랜스포머와 매개변수 수를 동일하게 맞추기 위해, 헤드마다 두 개의 질의를 얻으려고 헤드 차원(head dimension)을 절반으로 줄였습니다. 이로 인해 헤드의 표현력이 줄어들었고, 더 뼈아프게는 헤드당 값 캐시(value cache)도 절반이 되어 버렸습니다. 디코드는 매 스텝마다 값 캐시를 두 번, 즉 두 소프트맥스 분기마다 한 번씩 적재해야 했고, 결과적으로 매개변수 수는 같지만 디코드 속도는 기준 모델보다 느려졌습니다.
V2는 질의 헤드(query head) 수를 두 배로 늘리고, 키와 값 헤드(KV head)는 그대로 둡니다. 부족해진 매개변수는 상향 사영(up-projection)에서 빌려 옵니다. 헤드 차원은 기준 모델과 동일하게 유지하며, 뺄셈을 적용한 뒤에는 늘어난 차원을 다시 기준 트랜스포머의 출력 사영 O_W에 맞도록 줄입니다. 그 결과 동시에 세 가지 효과가 나타납니다.
- 디코드 속도가 기준 모델과 같아집니다. KV 캐시(KV cache)는 한 번만 적재(load)됩니다.
- FlashAttention이 수정 없이 그대로 동작합니다. 커스텀 커널이 필요하지 않습니다.
- 디코드의 산술 강도(arithmetic intensity)가 올라갑니다. 즉 고대역폭 메모리(HBM)에서 적재한 바이트(byte)당 더 많은 연산을 수행합니다.
또한 V2는 V1이 뺄셈 안정화를 위해 사용하던 헤드별 RMSNorm도 제거합니다. 70B 규모의 사전학습에서는 이 RMSNorm이 후반 학습(late training)을 오히려 불안정하게 만들었기 때문입니다. V2는 추가 모듈 없이도 학습을 안정적으로 유지하는 더 단순한 초기화 방식(initialization scheme)으로 그 자리를 대체합니다.
언제 사용할 것인가
| 작업 유형(Workload) | 효과(Benefit) |
|---|
| 장문 RAG(64k+) | 더 깨끗한 어텐션 맵, 환각 인용 감소 |
| 건초더미 속 바늘 찾기 벤치마크 | 32k 이후 정확도가 눈에 띄게 상승 |
| 다중 문서 질의응답(Multi-document QA) | 문서 간 간섭(interference) 감소 |
| 8k 수준의 코드 자동 완성 | 효과가 미미해 구조 변경의 가치가 낮음 |
| 짧은 채팅(< 4k) | 기준 모델과 사실상 구분되지 않음 |
이 기법의 가치는 문맥 길이가 길어질수록 커집니다. 4천 토큰 정도에서는 잡음 바닥이 충분히 작아 표준 어텐션으로도 무방하지만, 12만 8천 토큰 단계에서는 표준 어텐션이 오히려 손해입니다.
2026년의 다른 옵션들과의 조합
| 기능(Feature) | DIFF V2와 호환되는가? |
|---|
| 그룹화 질의 어텐션(Grouped-Query Attention; GQA) | 예. V2는 질의 헤드만 늘리고 KV 헤드는 그대로 둡니다. |
| 다중 잠재 어텐션(Multi-head Latent Attention; MLA, DeepSeek) | 원칙적으로 가능. 두 기법을 결합한 공개 논문은 아직 없습니다. |
| 전문가 혼합(Mixture-of-Experts; MoE) | 예. 어텐션은 다층 퍼셉트론(MLP) 블록과 독립입니다. |
| 회전 위치 임베딩(Rotary Position Embedding; RoPE) | 예. 그대로 사용할 수 있습니다. |
| YaRN / 장문 확장(long-context scaling) | 예. DIFF가 가장 큰 효과를 내는 영역입니다. |
| FlashAttention | V2에서는 호환. V1에서는 그렇지 않았습니다. |
| 추측 디코딩(Speculative decoding) | 예. 어텐션의 변경은 추측 디코드 루프에서 보이지 않습니다. |
직접 만들기
code/main.py는 순수 Python만으로 차분 어텐션을 구현합니다. 신호와 잡음 구조를 미리 알고 있는 장난감 질의를 사용해, 잡음 상쇄 비율(noise-cancellation ratio)을 직접 측정해 봅니다.
1단계: 표준 소프트맥스 어텐션
표준 라이브러리의 기본 행렬 연산만 사용합니다. 리스트의 리스트(list of lists) 자료 구조, 수동으로 작성한 행렬 곱(matmul), 그리고 최댓값을 빼서 수치 안정성(numerical stability)을 확보한 소프트맥스로 구성됩니다.
def softmax(row):
m = max(row)
exps = [math.exp(x - m) for x in row]
s = sum(exps)
return [e / s for e in exps]
2단계: Q와 K를 두 절반(half)으로 나누기
V1 방식은 헤드 차원을 절반으로 줄입니다. V2 방식은 헤드 차원은 그대로 두고 헤드 수를 두 배로 늘립니다. 이 장난감 구현은 학습 목적의 명확함을 위해 V1 방식을 사용합니다. 수학적으로는 동일하고, 인덱스 관리(bookkeeping)만 달라집니다.
3단계: 두 소프트맥스 분기와 뺄셈
A1 = [softmax([dot(q1, k) / scale for k in K1]) for q1 in Q1]
A2 = [softmax([dot(q2, k) / scale for k in K2]) for q2 in Q2]
diff_weights = [[a1 - lam * a2 for a1, a2 in zip(r1, r2)] for r1, r2 in zip(A1, A2)]
out = [[sum(w * v[j] for w, v in zip(row, V)) for j in range(d_v)] for row in diff_weights]
여기서 출력 가중치가 음수가 될 수 있다는 점에 유의합니다. 이는 정상적인 결과로, 값 캐시는 부호 있는 기여(signed contribution)를 그대로 처리할 수 있고, 뒤따르는 V 사영이 부호까지 흡수합니다.
4단계: 잡음 상쇄 측정
길이가 1024인 합성 시퀀스(sequence)를 만듭니다. 신호 토큰을 알려진 위치에 두고 나머지 자리는 잡음으로 채운 뒤, (a) 표준 소프트맥스 어텐션이 신호 위치에 부여하는 가중치와 (b) 차분 어텐션이 부여하는 가중치를 각각 계산해 신호 대 잡음비(signal-to-noise ratio; SNR)를 측정합니다. 차분 어텐션은 두 분기가 얼마나 다르게 학습되었느냐에 따라, 대체로 표준 어텐션 대비 3배에서 10배 더 높은 SNR을 보입니다.
5단계: V1 대 V2의 매개변수 회계
설정(hidden=4096, heads=32, d_head=128)을 입력으로 두고, 다음 항목들을 출력합니다.
- 기준 트랜스포머: Q, K, V는 각각 크기
hidden * hidden이며 다층 퍼셉트론(MLP)은 4 * hidden 규모입니다.
- DIFF V1: Q, K, V 모두 크기
hidden * hidden으로 그대로이지만, 내부에서 헤드 차원을 절반으로 줄이고 헤드별 lambda 매개변수를 추가합니다. 추가 비용은 O(heads * d_head) 규모입니다.
- DIFF V2: Q는
2 * hidden * hidden, K와 V는 각각 hidden * hidden 크기이며, 늘어난 차원은 O_W 직전에 다시 줄여 맞춥니다. 동일한 lambda 매개변수가 추가됩니다.
장난감 구현은 어텐션 블록(attention block)마다 대략 hidden * hidden 정도가 추가되는 V2의 추가 매개변수 비용을 직접 계산해 출력합니다.
사용해보기
2026년 4월 기준으로 DIFF V2가 모든 프로덕션 추론 서버(production inference server)에 도입된 것은 아니지만, vLLM과 SGLang 등에서 통합(integration)이 진행되고 있습니다. 그동안 이 패턴은 다음과 같은 흐름에서 나타나고 있습니다.
- Microsoft 내부의 장문 프로덕션 모델.
- 25만 6천 토큰 이상의 문맥을 목표로 하는 여러 공개 모델(open model) 학습 실행에서의 연구 재현(research replication).
- 차분 어텐션을 일부 층에서 사용하고, 나머지 층에서는 슬라이딩 윈도우 어텐션(sliding-window attention)을 사용하는 혼합 구조(hybrid architecture).
2026년에 이 기법을 고려해 볼 만한 상황은 다음과 같습니다.
- 6만 4천 토큰 이상의 유효 문맥(effective context)을 목표로 새 모델을 처음부터 학습하는 경우. 차분 어텐션을 처음부터 포함시키는 편이 좋습니다. 나중에 다시 학습하는 비용은 큽니다.
- 평가에서 가운데가 사라지는 실패가 두드러지는 장문 모델을 미세 조정(fine-tuning)하는 경우. Q 사영에 대한 저순위 적응(Low-Rank Adaptation; LoRA)으로 DIFF 구조를 근사할 수 있습니다.
반대로 다음 경우에는 사용을 권하지 않습니다.
- 이미 장문에서 안정적인 성능을 보이는 사전 학습된 밀집 모델(pre-trained dense model)을 서빙(serving)하고 있는 경우. 기존 가중치를 두고 재학습 비용을 회수하기는 어렵습니다.
- 문맥 길이가 항상 1만 6천 토큰 미만인 경우. 이 정도 길이에서는 잡음 바닥이 사실상 무시할 만합니다.
산출물 만들기
이 강의에서는 outputs/skill-diff-attention-integrator.md를 산출물로 만듭니다. 모델 아키텍처, 목표 문맥 길이, 환각 프로파일(hallucination profile), 학습 예산(training budget)이 주어지면, 새 사전학습 실행이나 LoRA 미세 조정에 차분 어텐션을 도입하기 위한 통합 계획(integration plan)을 작성하도록 안내합니다.
연습문제
-
(쉬움) code/main.py를 실행해, 합성 질의에서 차분 어텐션의 신호 대 잡음비가 표준 소프트맥스 어텐션보다 높은지 확인합니다. 잡음 진폭(noise amplitude)을 단계적으로 바꾸어, 표준 어텐션이 더 이상 쓸 만하지 않게 되는 교차점(crossover point)을 찾아 보고합니다.
-
(중간) 70억(7B) 규모 모델(hidden=4096, heads=32, d_head=128, 32 layers)에서 기준 모델 대비 DIFF V1, DIFF V2의 매개변수 수 차이(parameter-count delta)를 계산합니다. 어떤 구성요소(component)에 매개변수가 늘었고, 어떤 구성요소는 그대로 유지되었는지 정리합니다.
-
(중간) DIFF V1 논문(arXiv:2410.05258)의 3장과 DIFF V2의 Hugging Face 블로그 2장을 읽습니다. V1에서 헤드별 RMSNorm이 왜 필요했는지, 그리고 V2에서는 왜 학습 발산(training divergence) 없이 그것을 제거할 수 있었는지를 두 문장으로 설명합니다.
-
(중간) lambda = 0(첫 번째 소프트맥스만 사용)부터 lambda = 1(완전한 뺄셈)까지 값을 바꾸어 차분 어텐션을 계산하는 박탈 실험(ablation)을 구현합니다. 합성 질의에서 lambda 변화에 따른 신호 대 잡음비를 측정하고, 그 값을 최대로 만드는 lambda를 찾아 보고합니다.
-
(어려움) 장난감 구현을 그룹화 질의 어텐션(GQA)과 DIFF V2를 결합한 형태로 확장합니다. KV 헤드 8개, 질의 헤드 32개로 설정한 뒤, KV 캐시 크기가 동일한 (8, 32) 구성의 기준 GQA 모델과 일치하는지 확인합니다.
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 차분 어텐션(Differential attention) | "두 소프트맥스를 서로 뺀다" | Q와 K를 두 절반으로 나누고, 두 개의 소프트맥스 맵을 계산한 뒤, 두 번째를 lambda로 스케일해 첫 번째에서 빼고 V를 곱하는 연산. |
| 잡음 바닥(Noise floor) | "소프트맥스의 0이 아닌 꼬리" | 소프트맥스가 모든 무관 토큰에 부여하는 O(1/N) 가중치이며, 긴 문맥에서는 이것이 누적되어 O(1) 규모가 됨. |
lambda | "뺄셈의 스케일" | 헤드별로 학습되는 스칼라이며, exp(lq1.lk1) - exp(lq2.lk2) + lambda_init으로 매개변수화되고 음수가 될 수도 있음. |
| DIFF V1 | "ICLR 2025 버전" | 원래의 차분 트랜스포머. 매개변수 수 보존을 위해 헤드 차원을 절반으로 줄였고, 커스텀 커널이 필요하며, 디코드 속도가 느림. |
| DIFF V2 | "2026년 1월 수정판" | KV 헤드는 유지하고 질의 헤드를 두 배로 늘린 버전. 기준 디코드 속도와 동일하며 FlashAttention과 호환됨. |
| 헤드별 RMSNorm(Per-head RMSNorm) | "V1 안정화 장치" | V1이 차분 연산 이후에 추가로 적용한 정규화. V2는 후반 학습 불안정을 막기 위해 제거함. |
| 신호 대 잡음비(Signal-to-noise ratio) | "어텐션이 얼마나 낭비되는가" | 실제 신호 위치의 가중치와, 무관한 위치들의 평균 가중치 사이의 비율. |
| 가운데가 사라지는 실패(Lost in the middle) | "장문 실패 모드" | 긴 문맥의 한가운데 위치한 문서에서 검색 정확도가 떨어지는 현상이며, 차분 어텐션이 이를 줄여 줌. |
| 산술 강도(Arithmetic intensity) | "적재한 바이트당 부동소수점 연산(FLOP) 수" | V2가 KV 적재 한 번당 더 많은 질의 계산을 수행해 끌어올린 비율. 메모리 대역에 묶인(memory-bound) 디코드에서 중요. |
더 읽을거리