Skip to content

필사 모드: Transformer 구조 완전 해부 — Attention부터 KV Cache까지

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.
원문 렌더가 준비되기 전까지 텍스트 가이드로 표시합니다.

들어가며 — 왜 아직도 Transformer인가

2017년 "Attention Is All You Need" 논문이 등장한 이후, Transformer는 자연어 처리뿐 아니라 비전, 음성, 코드, 멀티모달까지 거의 모든 딥러닝 분야의 기본 골격이 되었습니다. 2026년 현재 우리가 사용하는 거의 모든 대형 언어 모델(LLM)은 본질적으로 Transformer 디코더의 변형입니다. GQA, RoPE, FlashAttention, MoE 같은 최신 기법도 모두 원래 Transformer 위에 얹힌 개선일 뿐, 핵심 골격은 변하지 않았습니다.

그렇기 때문에 Transformer를 제대로 이해하면, 새로 나오는 모델과 논문을 읽을 때 "어디가 바뀌었는지"만 파악하면 됩니다. 반대로 self-attention과 KV cache의 동작을 흐릿하게 알고 있으면, 서빙 최적화든 파인튜닝이든 모든 단계에서 막히게 됩니다.

이 글에서는 Transformer의 한 블록을 처음부터 끝까지 분해합니다. 각 연산이 어떤 텐서 shape를 입력받아 어떤 shape를 내놓는지, 파라미터가 몇 개나 생기는지, 그리고 추론 시 KV cache가 왜 등장하는지를 하나의 흐름으로 연결합니다. 수식은 달러 기호 없이 일반 표기로, 코드는 동작하는 형태로 제시합니다.

전체 구조 한눈에 보기

입력 토큰 시퀀스가 들어와서 다음 토큰 분포가 나오기까지의 흐름은 다음과 같습니다.

[입력 토큰 ID] (정수 시퀀스, 길이 N)

|

v

[토큰 임베딩 + 위치 인코딩] -> X: (B, N, D)

|

v

+---------------------------+

| Transformer 블록 x L |

| |

| LayerNorm |

| Multi-Head Attention |

| + 잔차(residual) |

| |

| LayerNorm |

| Feed-Forward Network |

| + 잔차(residual) |

+---------------------------+

|

v

[최종 LayerNorm]

|

v

[언임베딩 / LM head] -> logits: (B, N, V)

|

v

[softmax] -> 다음 토큰 확률 분포

여기서 기호는 다음과 같이 씁니다.

B = 배치 크기 (batch size)

N = 시퀀스 길이 (sequence length, 토큰 개수)

D = 모델 차원 (model dimension, d_model)

H = 어텐션 헤드 개수 (number of heads)

d_k = 헤드당 차원 = D / H

V = 어휘 크기 (vocabulary size)

L = 블록(layer) 개수

GPT 계열은 위 그림에서 디코더 블록만 L개 쌓은 구조입니다. 인코더-디코더 구조(원논문, T5 등)는 인코더 스택과 디코더 스택을 따로 둡니다. 본문에서는 디코더 중심으로 설명하되, 인코더와의 차이를 명확히 짚겠습니다.

1. 임베딩 — 토큰을 벡터로

가장 먼저 정수 토큰 ID를 D차원 벡터로 바꿉니다. 이것은 단순히 크기가 `(V, D)`인 룩업 테이블입니다.

class TokenEmbedding(nn.Module):

def __init__(self, vocab_size, d_model):

super().__init__()

self.embed = nn.Embedding(vocab_size, d_model)

self.d_model = d_model

def forward(self, token_ids):

token_ids: (B, N) -> (B, N, D)

return self.embed(token_ids) * (self.d_model ** 0.5)

원논문은 임베딩에 sqrt(D)를 곱해 스케일을 맞추는데, 이는 위치 인코딩과 크기를 비슷하게 만들기 위한 관례입니다. 임베딩 파라미터 수는 `V * D`입니다. 예를 들어 어휘 5만, D=4096이면 임베딩만 약 2억 개의 파라미터를 차지합니다.

2. 위치 인코딩 — 순서 정보 주입

self-attention은 그 자체로 순서를 모릅니다. 즉 입력 토큰을 섞어도 attention 결과의 집합은 동일합니다(순열 등변성). 그래서 위치 정보를 명시적으로 넣어줘야 합니다. 가장 고전적인 방식은 사인/코사인 함수로 만든 절대 위치 인코딩입니다.

PE(pos, 2i) = sin( pos / 10000^(2i / D) )

PE(pos, 2i+1) = cos( pos / 10000^(2i / D) )

pos = 위치 인덱스 (0, 1, 2, ...)

i = 차원 인덱스

각 위치마다 서로 다른 주파수의 사인파 조합으로 고유한 패턴을 만들어 임베딩에 더합니다. 최신 모델은 절대 위치 대신 RoPE(회전 위치 인코딩)나 ALiBi(어텐션 바이어스)를 더 많이 씁니다. 이 부분은 별도 글에서 깊게 다루며, 여기서는 "위치 정보가 어텐션 입력 어딘가에 반드시 주입된다"는 점만 기억하면 됩니다.

3. Self-Attention — 핵심 메커니즘

Query, Key, Value

self-attention의 직관은 이렇습니다. 각 토큰은 "내가 무엇을 찾고 싶은가"(Query)를 던지고, 모든 토큰은 "나는 이런 내용이다"(Key)라는 표지를 들고 있으며, 실제로 가져올 내용은 Value에 담겨 있습니다. Query와 Key의 유사도가 높을수록 해당 Value를 더 많이 가져옵니다.

입력 `X: (B, N, D)`에 대해 세 개의 선형 변환으로 Q, K, V를 만듭니다.

Q = X · W_Q W_Q: (D, D) -> Q: (B, N, D)

K = X · W_K W_K: (D, D) -> K: (B, N, D)

V = X · W_V W_V: (D, D) -> V: (B, N, D)

스케일드 닷프로덕트 어텐션

핵심 수식은 다음과 같습니다(달러 기호 없이 표기).

Attention(Q, K, V) = softmax( (Q · K^T) / sqrt(d_k) ) · V

Q · K^T : (B, N, N) -- 모든 토큰 쌍의 유사도 점수

/ sqrt(d_k) : 스케일링 (점수가 너무 커지는 것 방지)

softmax : 행 방향으로 정규화 -> 어텐션 가중치

· V : 가중 평균으로 출력 생성 -> (B, N, D)

sqrt(d_k)로 나누는 이유가 중요합니다. d_k가 크면 Q와 K의 내적 값의 분산이 d_k에 비례해 커지고, softmax 입력이 너무 커지면 기울기가 거의 0인 영역(포화)으로 밀려 학습이 불안정해집니다. 표준편차 기준으로 나누어 분산을 1 근처로 유지하는 것입니다.

def scaled_dot_product_attention(q, k, v, mask=None):

q, k, v: (B, H, N, d_k)

d_k = q.size(-1)

scores = torch.matmul(q, k.transpose(-2, -1)) / (d_k ** 0.5)

scores: (B, H, N, N)

if mask is not None:

scores = scores.masked_fill(mask == 0, float('-inf'))

weights = F.softmax(scores, dim=-1)

out = torch.matmul(weights, v) # (B, H, N, d_k)

return out, weights

Causal Mask — 미래를 보지 못하게

디코더(GPT 계열)에서는 위치 i의 토큰이 i보다 뒤에 오는 토큰을 보면 안 됩니다. 학습 시 정답을 미리 보는 셈이 되기 때문입니다. 그래서 어텐션 점수 행렬의 상삼각 부분(미래 위치)을 음의 무한대로 채워 softmax 후 가중치가 0이 되게 합니다.

causal mask (N=4, 1=허용, 0=차단):

key0 key1 key2 key3

query0 1 0 0 0

query1 1 1 0 0

query2 1 1 1 0

query3 1 1 1 1

인코더(BERT, 원논문의 인코더 스택)에는 이 마스크가 없습니다. 모든 토큰이 서로를 볼 수 있는 양방향 어텐션입니다. 이 차이가 디코더(생성)와 인코더(이해/표현) 모델의 근본적 구분점입니다.

4. Multi-Head Attention — 여러 시선으로 보기

하나의 어텐션만 쓰면 모델이 하나의 관점만 학습합니다. 멀티헤드는 D차원을 H개의 헤드로 쪼개, 각 헤드가 d_k = D / H 차원에서 독립적으로 어텐션을 수행하게 합니다. 어떤 헤드는 문법적 의존성을, 다른 헤드는 의미적 연관성을 학습하는 식으로 역할이 분화됩니다.

1) Q, K, V를 H개 헤드로 분할:

(B, N, D) -> (B, N, H, d_k) -> (B, H, N, d_k)

2) 각 헤드에서 스케일드 닷프로덕트 어텐션:

(B, H, N, d_k) -> (B, H, N, d_k)

3) 헤드를 다시 합침:

(B, H, N, d_k) -> (B, N, H, d_k) -> (B, N, D)

4) 출력 투영:

(B, N, D) · W_O -> (B, N, D)

class MultiHeadAttention(nn.Module):

def __init__(self, d_model, num_heads):

super().__init__()

assert d_model % num_heads == 0

self.d_model = d_model

self.num_heads = num_heads

self.d_k = d_model // num_heads

self.w_q = nn.Linear(d_model, d_model)

self.w_k = nn.Linear(d_model, d_model)

self.w_v = nn.Linear(d_model, d_model)

self.w_o = nn.Linear(d_model, d_model)

def forward(self, x, mask=None):

B, N, D = x.shape

H, d_k = self.num_heads, self.d_k

def split_heads(t):

return t.view(B, N, H, d_k).transpose(1, 2) # (B, H, N, d_k)

q = split_heads(self.w_q(x))

k = split_heads(self.w_k(x))

v = split_heads(self.w_v(x))

out, _ = scaled_dot_product_attention(q, k, v, mask)

(B, H, N, d_k) -> (B, N, D)

out = out.transpose(1, 2).contiguous().view(B, N, D)

return self.w_o(out)

멀티헤드 어텐션의 파라미터 수는 W_Q, W_K, W_V, W_O 네 개의 `(D, D)` 행렬로, 바이어스를 무시하면 `4 * D * D`입니다. D=4096이면 약 6700만 개입니다.

5. Feed-Forward Network — 위치별 변환

어텐션 출력에 대해 각 위치마다 독립적으로 적용되는 2층 MLP입니다. 보통 중간 차원을 D의 4배로 키웠다가 다시 줄입니다.

FFN(x) = activation(x · W_1 + b_1) · W_2 + b_2

W_1: (D, 4D) -> 중간 표현 (B, N, 4D)

activation: ReLU / GELU / SwiGLU 등

W_2: (4D, D) -> 다시 (B, N, D)

최신 모델은 활성화로 SwiGLU(게이트가 있는 변형)를 많이 씁니다. FFN의 파라미터 수는 대략 `2 * D * 4D = 8 * D * D`로, 사실 한 블록에서 어텐션(4 D^2)보다 FFN(8 D^2)이 더 많은 파라미터를 차지합니다. MoE는 바로 이 FFN을 여러 전문가로 쪼개 일부만 활성화하는 기법입니다.

class FeedForward(nn.Module):

def __init__(self, d_model, d_ff):

super().__init__()

self.fc1 = nn.Linear(d_model, d_ff)

self.fc2 = nn.Linear(d_ff, d_model)

self.act = nn.GELU()

def forward(self, x):

return self.fc2(self.act(self.fc1(x)))

6. 잔차 연결과 LayerNorm — 깊은 망을 안정화

수십 개의 블록을 쌓으면 기울기 소실과 학습 불안정이 생깁니다. 이를 두 장치로 해결합니다. 첫째, 잔차 연결(residual)은 서브층의 출력에 입력을 그대로 더해 기울기가 깊은 망을 통과할 수 있는 지름길을 만듭니다. 둘째, LayerNorm은 각 토큰 벡터를 정규화해 활성값의 분포를 안정시킵니다.

원논문은 서브층 뒤에 정규화를 두는 post-norm 구조였지만, 깊은 모델에서 학습이 불안정해 최근에는 거의 모두 pre-norm을 씁니다. pre-norm은 서브층 앞에서 정규화합니다.

post-norm (원논문):

x = LayerNorm(x + Sublayer(x))

pre-norm (최신 표준):

x = x + Sublayer(LayerNorm(x))

pre-norm이 안정적인 이유는 잔차 경로가 정규화를 거치지 않고 그대로 더해져, 깊은 망에서도 신호가 손상 없이 전달되기 때문입니다. 많은 최신 모델은 LayerNorm 대신 평균 빼기를 생략한 RMSNorm을 써서 약간의 연산을 줄입니다.

class TransformerBlock(nn.Module):

def __init__(self, d_model, num_heads, d_ff):

super().__init__()

self.ln1 = nn.LayerNorm(d_model)

self.attn = MultiHeadAttention(d_model, num_heads)

self.ln2 = nn.LayerNorm(d_model)

self.ffn = FeedForward(d_model, d_ff)

def forward(self, x, mask=None):

pre-norm 구조

x = x + self.attn(self.ln1(x), mask)

x = x + self.ffn(self.ln2(x))

return x

7. 한 블록의 텐서 흐름 정리

하나의 디코더 블록을 통과하는 동안 shape가 어떻게 변하는지 정리하면 다음과 같습니다.

입력 x : (B, N, D)

LayerNorm(x) : (B, N, D)

W_Q/W_K/W_V 투영 : (B, N, D) 각각

헤드 분할 : (B, H, N, d_k)

Q·K^T : (B, H, N, N)

softmax · V : (B, H, N, d_k)

헤드 병합 : (B, N, D)

W_O 투영 : (B, N, D)

잔차 더하기 : (B, N, D)

LayerNorm(x) : (B, N, D)

FFN 확장 : (B, N, 4D)

FFN 축소 : (B, N, D)

잔차 더하기 : (B, N, D)

출력 : (B, N, D)

블록을 통과해도 shape는 `(B, N, D)`로 보존됩니다. 그래서 같은 블록을 L번 반복해 쌓을 수 있습니다.

8. 파라미터 수 어림 계산

한 블록의 주요 파라미터를 정리하면 다음과 같습니다.

| 구성 요소 | 파라미터 수(대략) |

| --- | --- |

| 어텐션 (W_Q, W_K, W_V, W_O) | 4 곱하기 D 곱하기 D |

| FFN (확장 + 축소) | 8 곱하기 D 곱하기 D |

| 블록 합계 | 약 12 곱하기 D 곱하기 D |

D=4096, L=32 블록이라면 블록당 약 12 곱하기 4096 곱하기 4096 = 약 2억 개, 전체는 약 64억 개입니다. 여기에 임베딩(V 곱하기 D)을 더하면 우리가 흔히 듣는 "7B 모델" 규모가 나옵니다. 이 어림셈은 모델 크기를 직관적으로 가늠하는 데 유용합니다.

9. 추론과 KV Cache — 왜 필요한가

학습 시에는 전체 시퀀스를 한 번에 병렬로 처리합니다. 하지만 추론(생성)은 토큰을 하나씩 자기회귀적으로 만듭니다. 토큰을 하나 생성할 때마다 처음부터 다시 어텐션을 계산하면, 이미 계산했던 이전 토큰들의 Key/Value를 매번 재계산하는 엄청난 낭비가 생깁니다.

KV cache는 이미 생성된 토큰들의 K와 V를 메모리에 저장해두고, 새 토큰이 들어올 때 그 토큰의 Q만 새로 계산해 캐시된 K, V와 어텐션하는 기법입니다.

KV cache 없이 (재계산):

t번째 토큰 생성 시 -> 1..t 토큰의 K, V를 모두 다시 계산 (O(t) 연산)

KV cache 사용:

t번째 토큰 생성 시 -> t번째의 K, V만 계산해 캐시에 추가

캐시된 1..t-1의 K, V는 그대로 재사용 (O(1) 추가 계산)

이때 KV cache가 차지하는 메모리는 다음과 같이 어림할 수 있습니다.

KV cache 크기(바이트)

= 2(K와 V) x L(레이어) x N(시퀀스 길이) x D(차원)

x B(배치) x bytes_per_element

예: L=32, N=8192, D=4096, B=1, FP16(2바이트)

= 2 x 32 x 8192 x 4096 x 1 x 2

약 4.3 GB

시퀀스가 길어지고 배치가 커질수록 KV cache는 선형적으로 늘어나, 긴 컨텍스트 서빙에서는 모델 가중치보다 KV cache가 메모리를 더 잡아먹기도 합니다. 이 문제를 풀기 위한 기법이 바로 GQA/MQA(KV 헤드 공유로 캐시 축소), PagedAttention(가상메모리식 블록 관리) 등이며, 이는 후속 글에서 깊게 다룹니다.

10. 인코더와 디코더, 그리고 변형들

원논문의 인코더-디코더 구조와 오늘날 주류인 디코더 전용 구조의 차이를 정리합니다.

| 구분 | 인코더 | 디코더(GPT 계열) | 인코더-디코더(T5 등) |

| --- | --- | --- | --- |

| 어텐션 방향 | 양방향 | 인과적(causal) | 인코더는 양방향, 디코더는 causal + 교차 어텐션 |

| 마스크 | 없음 | causal mask | 디코더에 causal mask |

| 대표 용도 | 이해/표현(분류, 임베딩) | 생성 | 번역, 요약 등 시퀀스-투-시퀀스 |

| 대표 모델 | BERT | GPT, Llama, Qwen | T5, BART |

교차 어텐션(cross-attention)은 디코더가 인코더의 출력을 Key/Value로 삼아 어텐션하는 것으로, 입력(소스)과 출력(타깃)이 다른 번역 같은 작업에 적합합니다. 반면 순수 생성 모델은 디코더 블록만 쌓아 단순하면서도 강력합니다.

11. 작은 수치 예제로 따라가는 어텐션

추상적인 수식만으로는 감이 잘 안 옵니다. 아주 작은 예제로 한 번 흐름을 따라가 봅니다. 토큰 3개, 차원 D=4, 헤드 1개라고 합시다.

입력 X: (N=3, D=4)

x0 = [1, 0, 1, 0]

x1 = [0, 1, 0, 1]

x2 = [1, 1, 0, 0]

1) Q, K, V를 선형 변환으로 만든다 (가중치는 학습된 값)

-> Q, K, V: 각각 (3, 4)

2) 점수 = Q · K^T -> (3, 3) 행렬

각 원소 score[i][j]는 토큰 i가 토큰 j에 얼마나 주목하는지의 원점수

3) sqrt(d_k)=2 로 나누어 스케일링

4) causal mask 적용 (디코더라면):

score[0][1], score[0][2], score[1][2] -> 음의 무한대

5) 행마다 softmax -> 어텐션 가중치 (각 행 합이 1)

6) 가중치 · V -> 출력 (3, 4)

토큰 i의 출력 = 자기 자신과 과거 토큰들의 V를 가중 평균한 것

핵심은 score 행렬의 각 행이 "이 토큰이 다른 토큰들을 얼마나 참고하는가"의 분포라는 점, 그리고 causal mask가 그 분포에서 미래를 0으로 만든다는 점입니다. 출력은 결국 과거 토큰들의 Value를 자기 관심사에 맞게 섞은 결과입니다.

12. 출력층 — logits에서 토큰으로

마지막 블록을 통과한 `(B, N, D)` 표현은 LM head(보통 임베딩과 가중치를 공유하는 `(D, V)` 행렬)를 거쳐 어휘 전체에 대한 점수(logits)가 됩니다.

최종 표현: (B, N, D)

LM head (D, V) 곱: -> logits: (B, N, V)

softmax (마지막 위치): -> 다음 토큰 확률 분포 (V,)

생성 시에는 마지막 위치의 logits만 보고 다음 토큰을 고릅니다. 고르는 방식(샘플링)이 출력 다양성을 좌우합니다.

디코딩 전략 요약:

greedy : 매 단계 가장 확률 높은 토큰 선택 -> 결정적, 단조로울 수 있음

temperature: logits를 T로 나눠 분포를 평탄(T>1)/뾰족(T<1)하게 조절

top-k : 상위 k개 토큰 중에서만 샘플링

top-p : 누적 확률 p까지의 토큰 중에서만 샘플링 (nucleus)

def sample_next_token(logits, temperature=1.0, top_k=None):

logits: (V,)

logits = logits / max(temperature, 1e-6)

if top_k is not None:

v, _ = torch.topk(logits, top_k)

logits[logits < v[-1]] = float('-inf')

probs = F.softmax(logits, dim=-1)

return torch.multinomial(probs, num_samples=1)

이 출력층과 디코딩 전략은 학습된 Transformer를 실제 생성기로 만드는 마지막 연결 고리입니다. 모델 구조 자체는 같아도, 어떻게 샘플링하느냐에 따라 결과의 성격이 크게 달라집니다.

13. 학습과 추론의 연산 패턴 차이

같은 Transformer라도 학습과 추론은 연산 성격이 다릅니다. 이 차이를 알면 서빙 최적화의 출발점이 보입니다.

| 구분 | 학습(또는 prefill) | 추론 decode 단계 |

| --- | --- | --- |

| 처리 단위 | 전체 시퀀스 병렬 | 토큰 1개씩 순차 |

| 병목 | 연산(행렬곱) 바운드 | 메모리 대역폭 바운드 |

| 행렬 형태 | 큰 행렬곱(N x N) | 얇은 행렬-벡터곱 |

| 최적화 포인트 | FLOPs, 텐서코어 활용 | KV cache, 메모리 대역폭 |

추론의 decode 단계가 메모리 바운드라는 점이 중요합니다. 토큰 하나를 만들 때마다 모델 가중치와 KV cache를 메모리에서 읽어와야 하므로, 연산보다 메모리 읽기가 병목이 됩니다. 그래서 양자화(가중치를 작게)와 KV cache 최적화가 decode 처리량에 큰 효과를 냅니다. 2026년 서빙 스택의 continuous(in-flight) batching, paged KV cache, FP8/INT4 양자화, speculative decoding은 모두 이 메모리 바운드 특성을 공략합니다.

함정과 트러블슈팅

- **softmax 스케일링 누락**: sqrt(d_k)로 나누는 것을 빼먹으면 헤드 차원이 클 때 학습이 발산하거나 어텐션이 한 토큰에 과도하게 쏠립니다.

- **마스크 적용 위치 실수**: causal mask를 softmax 이전 점수 단계에서 음의 무한대로 채워야 합니다. softmax 이후에 0을 곱하면 정규화가 깨집니다.

- **pre-norm과 post-norm 혼동**: 깊은 모델을 처음부터 학습할 때 post-norm을 쓰면 warmup 없이는 발산하기 쉽습니다. 특별한 이유가 없으면 pre-norm을 권장합니다.

- **KV cache dtype 불일치**: 가중치는 FP16인데 KV cache를 FP32로 두면 메모리가 두 배로 늘어납니다. 의도적 양자화가 아니라면 dtype을 맞추세요.

- **위치 인코딩 길이 초과**: 절대 위치 인코딩을 학습 길이보다 긴 입력에 쓰면 성능이 급락합니다. 긴 컨텍스트가 필요하면 RoPE 계열과 외삽 기법을 고려해야 합니다.

- **헤드 분할 시 transpose 누락**: `(B, N, H, d_k)`를 `(B, H, N, d_k)`로 바꾸는 transpose를 빠뜨리면 헤드 차원과 시퀀스 차원이 섞여 조용히 잘못된 결과가 나옵니다.

마치며

Transformer 한 블록은 결국 (1) 어텐션으로 토큰 간 정보를 섞고, (2) FFN으로 위치별 비선형 변환을 하고, (3) 잔차와 정규화로 깊은 망을 안정화하는, 세 가지 단순한 아이디어의 반복입니다. 여기에 위치 인코딩으로 순서를 주입하고, 추론 단계에서는 KV cache로 재계산을 피합니다.

이 골격을 손에 쥐고 있으면, RoPE는 위치 인코딩의 변형, GQA/MQA는 어텐션의 KV 효율화, FlashAttention은 어텐션 연산의 IO 최적화, MoE는 FFN의 조건부 활성화로 깔끔하게 정리됩니다. 다음 글에서는 어텐션의 진화(MQA/GQA/FlashAttention)와 위치 인코딩(RoPE)을 각각 깊게 파고들겠습니다.

참고 자료

- Vaswani et al., "Attention Is All You Need" (arxiv 1706.03762): https://arxiv.org/abs/1706.03762

- Dao et al., "FlashAttention" (arxiv 2205.14135): https://arxiv.org/abs/2205.14135

- PyTorch Transformer 문서: https://pytorch.org/docs/stable/nn.html#transformer-layers

- Hugging Face Transformers 문서: https://huggingface.co/docs/transformers/index

- vLLM 공식 문서(KV cache, 서빙): https://docs.vllm.ai

- vLLM 저장소: https://github.com/vllm-project/vllm

- The Illustrated Transformer (Jay Alammar): https://jalammar.github.io/illustrated-transformer/

- Qwen 모델 저장소: https://github.com/QwenLM

현재 단락 (1/250)

2017년 "Attention Is All You Need" 논문이 등장한 이후, Transformer는 자연어 처리뿐 아니라 비전, 음성, 코드, 멀티모달까지 거의 모든 딥러닝 ...

작성 글자: 0원문 글자: 10,224작성 단락: 0/250