Loading [MathJax]/jax/output/CommonHTML/jax.js

[LLM] DeepSeek-V3

2025. 2. 28. 05:18ML/NLP

이번 글에서는 DeepSeek-V3에서 사용된 방법론들에 대해 살펴볼 것이다.

 

DeepSeek-V3은 기존 트랜스포머 아키텍처와 두 가지 주요 부분에서 차이를 보인다.

1. Multi-Head Attention (MHA) → Multi-Head Latent Attention (MLA)

2. FeedForward Network (FFN) → Mixture of Experts (MoE) 기반 FeedForward Network (FFN)

1. Mutli-head Latent Attention (MLA)

최근 CoT, 추론 모델(reasoning model)의 등장 등 inference-time scaling 방법론이 각광 받으면서, 답변 길이가 길어지는 추세가 나타나 KV Cache가 점유하는 메모리 사용량이 늘어나고 있다.

이러한 메모리 효율성 문제를 해결하기 위해 다양한 방법론들이 활발히 연구되고 있다. 대표적으로, Meta에서 발표한 Grouped Query Attention (GQA)가 있다.

Grouped Query Attention (GQA)는 head 수에 비례하여 증가하는 KV Cache 크기 문제를 해결하기 위해, head 수만큼 KV를 생성하는 대신 여러 head가 KV를 공유하는 방식을 채택했다. 성능 저하는 불가피하지만 오버헤드 감소 효과가 더 크다고 판단해, 이를 감수하는 선택을 한 것 같다.

 

MLA도 head 수에 비례하여 증가하는 KV Cache 크기 문제를 해결하기 위해 연구된 방법론이다.

 

1.1. W(s)kq(s)t에 흡수시킨다. q(s)tk(s)i=(htW(s)q)(hiW(s)k)=(htW(s)qW(s)k)hi

 

기존에는 토큰 당 head 개수만큼의 key vector(k(1)i,k(2)i,,k(h)i)를 갖고 있어야 한다.

위와 같이 흡수하면 모든 head가 동일한 ht를 공유하기 때문에, 토큰 당 하나의 vector ht만 갖고 있어도 충분하다.

 

수식 해석:

  1. hidden vector ht를 query vector q(s)t로 변환하며, 벡터를 Rdh 차원에서 Rdv 차원으로 이동시켰다.

  2. q(s)thi가 내적할 수 있게, W(s)k를 활용해 원래 차원인 Rdh로 다시 돌아왔다. 이때, 내적값이 보존되도록 변환한다.

 

transpose matrix A transforms a co-vector from the space of Ax back to space of x while retaining scalar value. (=즉, AAx가 속한 공간의 공벡터(Co-vector)를 원래 벡터 공간(x)으로 스칼라 값을 유지한 채 변환하는(=되돌리는) 역할을 한다.)

Co-vector는 벡터를 받아서 숫자를 반환하는 선형 함수이며, ω=[ω1ω2ω3]와 같이 행 벡터(row vector)로 표현한다.

(x = hi, A = W(s)k, Ax가 속한 공간의 공벡터 = q(s)t, 원래 벡터 공간(x)으로 이동한 공벡터 = q(s)tW(s)k, 선형 함수 = 내적)

 

1.2. How to Apply Rotary Positonal Encoding (RoPE) in MLA

현재 대부분의 LLM은 RoPE를 사용하여 토큰 간 위치 정보를 전달한다.

MLA에 RoPE을 사용하면 수식은 다음과 같아진다.
q(s)tk(s)i=(htW(s)qRt)(hiW(s)kRi)=ht(W(s)qRtiW(s)k)hi

 

이러면, q(s)t가 토큰 간의 상대적 위치 정보인 Rti까지 흡수하여 position dependent하게 된다.

이 방식은 저장해야 할 Query 벡터의 개수가 급격히 증가하여 메모리 효율성에 전혀 도움이 되지 않는다.

 

DeepSeek-V3의 저자들은 이를 해결하기 위해 위치 벡터를 추가하고, 이를 기존 Query와 Key에 결합(Concat)하는 방식으로 우회했다.

수식으로 표현하면 다음과 같다.

q(s)t=[htW(s)qh,RoPE(htW(s)qr)]Rdk+dr,k(s)i=[hiW(s)kh,RoPE(hiWkr)]Rdk+dr

KV Cache를 줄이기 위해, Key는 모든 헤드가 동일한 위치 벡터를 공유하는 방식을 채택했다.

위치 정보는 헤드마다 다르더라도 동일하기 때문에 굳이 다르게 설정할 필요가 없어, 이러한 구조를 채택한 것으로 보인다.

 
1.3. hidden vector를 cache vector로 차원 축소 
 

hidden vector ht를 latent 차원 벡터인 cache vector cqtckvt로 축소(compress)하여 KV Cache의 메모리 효율성을 더 높였다.

수식은 다음과 같이 변한다. 

cqt=htWcqRdc,WcqRd×dcckvi=hiWckvRdc,WckvRd×dcq(s)t=[cqtW(s)qc,RoPE(cqtW(s)qr)]Rdk+dr,W(s)qcRdc×dk, W(s)qrRdc×drk(s)i=[ckviW(s)kc,RoPE(hiWkr)]Rdk+dr,W(s)kcRdc×dk, W(s)krRd×dr

 

결론: KV Cache는 어텐션 블록마다 토큰 당 cache vector ckvi와 위치 벡터 RoPE(hiWkr)만 가지고 있으면 된다. 참고로, W(s)vWo에 흡수된다.

 
MLA 순서도

cqt=htWcqRdc,WcqRd×dcckvi=hiWckvRdc,WckvRd×dcq(s)t=[cqtW(s)qcW(s)kc,RoPE(cqtW(s)qr)]Rdc+dr,W(s)qcRdc×dk, W(s)kcRdk×dc, W(s)qrRdc×drk(s)i=[ckvi,RoPE(hiWkr)]Rdc+dr,W(s)krRd×dr

 

o(s)t=Attention(q(s)t,k(s)t,ckvt)itexp(q(s)tk(s)i)ckviitexp(q(s)tk(s)i)

 

ot=[o(1)t,o(2)t,,o(h)t]RhdcWo=[W(1)vW(1)oW(2)vW(2)oW(h)vW(h)o]Rhdc×d,W(s)vRdc×dv, W(s)oRdv×dHt=otWoRd

2. DeepSeekMoE

DeepSeek-V3은 MoE 구조의 FFN을 사용하고 있다.

구체적으로는, Ns개의 FFN은 고정적으로 사용하고, 추가적으로 라우터가 Kr개의 FFN 중에서 Nt개를 선택하여 사용한다.

 

DeepSeekMoE 순서도

si,t=Sigmoid(uTtei),(ut:hidden vector,ei:decision vector of i-th expert)gi,t=gi,tNrj=1gj,t,gi,t={si,t,si,tTopk({sj,t1jNr},Kr),0,otherwise,ht=ut+Nsi=1FFN(s)i(ut)+Nri=1gi,tFFN(r)i(ut)

3. Multi-Token Prediction Objective

DeepSeek-V3는 hidden vector ht으로 xt+1을 예측하는 데 사용함과 동시에 x>t+1을 예측하는 데 사용한다.

구체적으로 말하면, htxt+1의 임베딩 벡터를 concat한 후, 단일 트렌스포머 블록만 사용해 xt+2를 예측하는 방식으로 추가 학습이 진행된다.

직관적으로 말하면, htxt+1뿐만 아니라 x>t+1도 맞출 수 있게 표현(representation)을 잘 담고 있도록 학습을 유도한다.

이는 hidden vector htxt+1을 예측할 때, 이후 문맥인 x>t+1도 고려하도록 유도하여, 더 일관성 있고 맥락에 맞는 텍스트를 생성하는 데 도움을 줄 수 있다.