Transformer — Attention을 쌓으면 어떻게 ChatGPT가 되나

⏱ 정독 약 30분 · LLM 이론 집중코스 6편
이 글에서 잡는 것 4가지:
1. Attention “한 번”을 멀티헤드·위치인코딩·FFN으로 묶어 블록 하나를 만들고, 그걸 N번 쌓으면 무엇이 되는지
2. 논문 원형이 왜 Encoder + Decoder 두 덩어리였고, ChatGPT는 왜 Decoder만 쓰는지
3. 그래서 긴 프롬프트가 왜 비싼지(O(N²))
4. 그 비싼 O(N²)를 FlashAttention·Sliding Window로 어떻게 견디는지


Table of Contents

Attention 한 번으로는 부족하다 — 그래서 Transformer

5편에서 Attention 한 번을 끝까지 봤어요. 단어가 다른 단어를 얼마나 볼지 점수를 매기고(Q·K 내적), softmax로 비율을 만들고, 그 비율대로 V를 가중합. “울었다”가 “고양이가 주어”를 알아내는 그 과정이었죠.

그런데 ChatGPT·Claude는 그 검색을 한 번만 하지 않아요.

  • 한 번에 “주어” 관계만 보면, 원인·시제·지시어는 누가 보나?
  • “dog bites man”과 “man bites dog”처럼 순서가 뒤집히면?
  • 한 층으로 안 되면 몇 층을 쌓나?

Attention이 엔진이라면, Transformer는 그 엔진을 얹어 완성한 자동차예요. 이 글은 엔진을 차로 조립하는 과정입니다. (Attention 자체가 헷갈리면 [5편]을 먼저.)

부품 ① Multi-Head — 검색을 여러 벌 동시에

5편에서 attention 한 번을 봤죠. “울었다”가 다른 단어들에 점수를 매겨 비율표 하나를 만들고, 그 비율대로 V를 가중합했어요. 그 비율표가 딱 하나였다는 게 문제의 시작입니다.

그 비율표를 다시 꺼내 볼게요.

울었다 → 고양이 0.68 | 배고파서 0.16 | 울었다 0.12 | 그 0.04

여기엔 주어 정보(고양이)와 원인 정보(배고파서)가 한 표에 섞여 있어요. “주어가 누구냐”만 알고 싶어도 고양이가 0.68로 희석돼 있고, “원인이 뭐냐”는 0.16으로 약하게 깔려 있죠. 한 표가 두 질문에 동시에 답하려니 둘 다 어중간해집니다.

그런데 단어 사이의 관계는 한 종류가 아니에요.

  • 누가 했나 (주어-동사): 울었다 ↔ 고양이
  • 왜 했나 (원인-결과): 울었다 ↔ 배고파서
  • 언제 (시제), 어떤 (수식), 무엇을 가리키나 (지시어-선행사)…

이 모든 관계를 비율표 하나에 욱여넣으면 전부 뭉개져요. softmax가 한 번이니, 한 표는 결국 한 가지 주목 패턴밖에 못 만들거든요.

해결 — 검색을 여러 벌, 각자 다른 표로

그래서 검색을 여러 벌 동시에 돌립니다. attention 한 세트(Q·K·V → 점수표 → softmax → 가중합)를 head 한 개라고 불러요. 이걸 여러 개 — 논문은 8개, GPT-3은 96개 — 각자 독립된 W_Q·W_K·W_V로 돌립니다. head가 여러 개면 비율표도 여러 개가 되죠.

두 head로 쪼갠다고 해보면, 비율표가 이렇게 갈라져요.

head A (주어 담당):  고양이 0.88 | 배고파서 0.04 | 울었다 0.06 | 그 0.02
head B (원인 담당):  배고파서 0.86 | 고양이 0.05 | 울었다 0.07 | 그 0.02

단일 head에선 고양이가 0.68로 희석됐는데, head A에선 0.88로 또렷해졌어요 — 주어만 보는 표라서요. head B는 원인(배고파서 0.86)에 집중하고요. 한 표가 두 질문에 어중간하게 답하던 걸, 두 표가 각각 또렷하게 답하는 겁니다.

각 head가 자기 결과 벡터를 뱉으면, 옆으로 이어붙이고(concat) 마지막에 한 번 더 섞어요(W_O). head A의 “고양이 정보”와 head B의 “배고픔 정보”가 나란히 보존된 채 하나로 합쳐지죠.

단일 head 한 비율표 vs 멀티헤드 두 비율표(주어 head·원인 head) 비교
단일 head 한 비율표 vs 멀티헤드 두 비율표(주어 head·원인 head) 비교

왜 쪼개나 — “64차원 8개 = 512차원 1개”인데

자연스러운 의문이 와요. “64차원 head 8개나 512차원 head 1개나, 차원을 합치면 512로 같은데 왜 굳이 쪼개?”

세 가지예요.

  • ① 관계마다 또렷해진다. 방금 본 0.68 → 0.88. 한 표가 한 종류 관계만 맡으니 선명해져요.
  • ② 계산 비용이 거의 안 는다. 64차원 head 8개의 계산량은 512차원 head 1개와 거의 같아요(64×8 ≈ 512). 공짜로 얻는 다양성인 셈이죠.
  • ③ 관점이 강제로 분화된다. “너는 주어, 너는 원인 봐”라고 지시한 적이 없는데, 쪼개 놓으면 학습 과정에서 각 head가 알아서 다른 관계를 맡아요. 큰 head 하나는 한 관점으로 다 설명하려다 뭉개지는데, 여러 작은 head는 좁은 관점 하나씩으로 갈라지거든요.

그래서 실제 LLM은 head당 64~128차원으로 거의 고정하고, 모델을 키울 땐 차원이 아니라 head 개수를 늘려요(GPT-3 96개).

그럼 1차원 head 512개가 제일 좋지 않나

“쪼갤수록 또렷해진다며? 그럼 극단으로 1차원 head 512개가 최고 아냐?” — 날카로운 추론인데, 아니에요. 반대쪽 손해가 같이 커지거든요.

  • 1차원이면 내적이 ‘방향’을 못 봐요. 5편에서 점수는 두 화살표의 내적이고, 방향이 닮을수록 컸죠. 그런데 1차원은 방향이 +/− 둘뿐이라 “닮음”이라는 개념이 거의 사라져요. “주어다움” 같은 관계를 숫자 하나로는 못 담습니다.
  • head가 과하면 놀고먹는 head가 생겨요. 학습이 끝난 모델에서 상당수 head를 잘라내도(pruning) 성능이 거의 안 떨어진다는 연구가 있어요. 많다고 다 일하는 게 아니에요.

그래서 “다양성↑ vs head당 표현력↓”의 균형점이 64~128이에요. 5편에서 본 “1차원 head는 빈약하다”는 그 막다른 길과 정확히 같은 트레이드오프죠.

⚠️ head ≠ 층(layer) — 가장 헷갈리는 곳

여기가 제일 많이 엉키는 지점이에요. head는 한 층 안에서 가로로 나란히 도는 검색이고, 층(layer)은 블록을 세로로 쌓은 거예요. 완전히 다른 축입니다.

GPT-3로 펼치면 — 96층, 그리고 각 층마다 96 head예요. 우연히 둘 다 96이라 “96 head = 96층?”으로 합쳐 보기 딱 좋은데, 곱하면 attention 묶음이 96 × 96 = 9,216번 도는 셈이죠.

차원으로 따지면: head당 128차원 × 96 head = d_model 12,288. 그리고 이 12,288은 96개 층을 지나는 내내 안 변해요. 128로 쪼개지는 건 한 층 안에서 잠깐이고, 끝나면 concat으로 다시 12,288로 합쳐져 다음 층으로 넘어갑니다.

누가 head들에게 역할을 나눠줬나

“③ 관점이 강제로 분화된다”고 했죠. 여기서 의문이 와요 — “누가 head A한테 ‘너는 주어 봐’, head B한테 ‘너는 원인 봐’라고 시켰지?” 아무도 안 시켰어요. 학습이 끝나면 head들이 저절로 다른 관계를 맡고 있을 뿐입니다.

왜 저절로 분화되냐면, 8개 head가 똑같은 걸 보면 낭비잖아요(놀고먹는 head). 학습은 “전체 오차를 줄이는” 방향으로 가는데, head들이 서로 다른 관계를 맡는 게 오차를 더 줄이거든요. 그래서 명시적 지시 없이도 분업이 생겨요. 실제로 학습된 모델의 점수표를 시각화하면, 어떤 head는 바로 앞 단어에, 어떤 head는 주어-동사에, 어떤 head는 문장부호에 집중하는 게 보여요(Jay Alammar의 “Illustrated Transformer”에 그림이 있어요).

concat 말고 그냥 더하면 안 되나

각 head 결과를 이어붙인다(concat) 고 했는데, “그냥 8개를 더하거나 평균 내면 안 되나?” 싶죠. 안 돼요 — 더하면 head A의 “주어 정보”와 head B의 “원인 정보”가 한 덩어리로 섞여 구분이 사라져요. 모처럼 따로 본 걸 도로 뭉개는 거죠. 이어붙이면 “앞쪽 64칸은 head A, 다음 64칸은 head B”처럼 자리가 보존돼서, 마지막 섞기(W_O)가 “어느 자리 정보를 얼마나 쓸지”를 학습으로 고를 수 있어요.

자기 검증 — 자료를 덮고 두 가지를 말해 보세요. (1) “단일 head와 멀티헤드의 차이”는? → 단일은 비율표 하나라 관계가 뭉개지고, 멀티는 비율표가 여러 개라 관계마다 또렷. (2) “head와 층(layer)의 차이”는? → head=가로(한 층 안에서 동시), 층=세로(블록을 반복). 둘 다 나오면 ①번은 졸업이에요.

부품 ② 위치인코딩 — 순서를 집어넣기

5편에서 attention은 “단어를 통째로 펼쳐놓고 한 번에” 본다고 했죠. 거기엔 함정이 하나 숨어 있어요 — 점수표엔 순서 정보가 없어요. attention은 단어를 가방에 쏟아놓은 것(bag of words)처럼 봐서, 어느 단어가 먼저 나왔는지를 모릅니다.

"dog bites man"  (개가 사람을 문다)
"man bites dog"  (사람이 개를 문다)   ← 뜻이 정반대!
→ 단어 집합 {dog, bites, man}이 같으면 Q·K·V도 같고 → 점수표도 똑같다

“개가 사람을 문다”와 “사람이 개를 문다”를 구분 못 하면 번역도 대화도 무너지죠. RNN은 단어를 순서대로 읽었으니 이 문제가 없었는데(순서가 자동으로 들어왔죠), Transformer는 한꺼번에 보기 때문에 순서가 날아가 버려요.

해결 — 자리마다 고유한 신호를 더한다

그래서 각 단어 벡터에 “너는 문장의 몇 번째 자리에 있는 단어야” 라는 신호를 따로 더해줘요. 이게 위치인코딩(positional encoding) 입니다.

핵심을 하나 못박을게요 — 위치를 점수 공식에 직접 넣는 게 아니에요. 점수는 여전히 내용 내적(Q·K^T)이고, 대신 입력 단어 벡터에 위치 신호를 미리 섞어서 “내용의 일부”로 만듭니다. 그래서 5편에서 강조한 “attention은 거리를 직접 보지 않는다” 가 여전히 맞으면서도, 순서가 살아 있는 거예요.

왜 sin/cos 파동인가

원논문은 이 자리 신호를 sin/cos 파동으로 만들었어요. 그냥 1, 2, 3… 번호를 더하면 안 되냐고요? 안 돼요 — 문장이 길어지면 뒤쪽 숫자(500, 1000)가 단어 의미를 깔아뭉개거든요. 파동을 쓰는 이유는 세 가지예요.

  • ① -1~1을 진동해서 단어 벡터를 안 압도해요. 숫자가 폭주하지 않아요.
  • ② 여러 주파수를 겹쳐서 자리마다 고유한 패턴이 나와요. 시계의 초침·분침·시침을 겹치면 매 순간이 유일한 조합인 것처럼, 빠른 파동·느린 파동을 겹쳐 각 자리를 유일하게 찍어요.
  • ③ 상대 거리(“몇 칸 떨어졌나”)가 자동으로 표현돼요. 1번-2번이든 5번-6번이든 “한 칸 차이”가 같은 형태로 나타나요. 그래서 “이 단어는 3번 자리”라는 절대 위치만이 아니라, “이 단어는 저 단어로부터 3칸 뒤”라는 상대 위치까지 다룰 수 있어요.

RoPE — 컨텍스트 경쟁의 무기

요즘 모델은 sin/cos “더하기” 대신 RoPE(Rotary Position Embedding, 회전 위치 임베딩) 를 많이 써요. 위치를 더하는 게 아니라 회전 각도로 넣어요. 위치 = 얼마나 돌았나.

각 단어의 Q·K 벡터를 2개씩 짝지어 평면의 점으로 보고,
그 점을 "위치 × 기본각도" 만큼 회전 → 멀리 있는 토큰일수록 많이 돌림

여기서 결정적인 일이 일어나요. “고양이”의 Q를 m번 위치만큼, “쥐”의 K를 n번 위치만큼 돌린 뒤 내적하면 — 결과가 (m−n), 즉 두 토큰의 상대 거리에만 의존해요. 절대 위치 m, n은 깔끔하게 사라지죠.

시곗바늘 둘을 똑같이 더 돌려도 둘 사이의 각도 차이는 그대로인 것과 같아요. “3번-5번”이든 “103번-105번”이든 거리 2면 같은 효과예요.

RoPE — 위치를 각도로, 두 화살표를 똑같이 더 돌려도 사이 각도(상대거리)는 보존
RoPE — 위치를 각도로, 두 화살표를 똑같이 더 돌려도 사이 각도(상대거리)는 보존

이게 긴 컨텍스트 확장에 결정적이에요. 회전 각도를 “압축”하면, 짧게 학습한 모델을 긴 컨텍스트로 늘릴 수 있거든요 — Position Interpolation, YaRN 같은 기법이 그거예요. “4K로 학습 → 128K·1M 컨텍스트”가 이렇게 나와요. 단 무한정 압축하면 가까운 거리 구분이 뭉개져서, 근거리 정밀도와 줄다리기예요. 100만 토큰 경쟁이 단순 “숫자 키우기”가 아니라 이 줄다리기를 누가 잘하느냐의 싸움인 이유죠.

위치를 ‘더하면’ 단어 의미가 망가지지 않나

“단어 벡터에 위치 신호를 더한다”에서 막히는 분이 많아요 — “의미 위에 위치를 덧칠하면 의미가 더럽혀지지 않나?” 안 망가져요. 핵심은 벡터의 차원이 아주 많다(수백~수천) 는 거예요. 위치 신호는 그 많은 차원에 옅게 퍼져 더해지고, 학습이 진행되면서 모델은 “이 차원들의 이 패턴은 위치, 저 패턴은 의미”를 구분해서 읽는 법을 배워요. 좁은 종이면 글씨 위에 글씨를 덧쓰면 안 보이지만, 아주 큰 칠판이면 의미는 의미대로 위치는 위치대로 적을 공간이 있는 거죠. (게다가 RoPE는 아예 “더하기”가 아니라 “회전”이라 이 걱정이 더 적어요 — 의미 벡터를 돌릴 뿐 덮어쓰지 않으니까요.)

각도를 무한정 압축하면 어디서 깨지나

“4K → 1M로 늘리려고 각도를 압축한다”고 했죠. “그럼 무한정 압축하면 무한정 긴 컨텍스트?” 아니에요. 각도를 너무 촘촘히 압축하면, 가까운 두 토큰의 각도 차이가 거의 0이 돼서 “1칸 떨어짐”과 “2칸 떨어짐”을 구분 못 해요. 멀리 보려다 코앞을 못 보는 거죠. 그래서 YaRN 같은 기법이 “먼 거리는 압축하되 가까운 거리는 덜 압축”하는 식으로 주파수별 차등을 둬요. 근거리 정밀도와 원거리 도달의 줄다리기가 여기 있어요.

자기 검증 — (1) “dog bites man / man bites dog를 attention이 왜 못 가리나?” → 점수표가 단어 집합만 보고 순서를 안 봐서. (2) “RoPE에서 내적이 절대 위치가 아니라 상대 거리만 보는 이유는?” → 회전이 두 벡터 사이 각도를 보존하니까.

부품 ③ Feed-Forward — 각 자리에서 혼자 정리

Attention이 “단어끼리 정보를 주고받는” 일이었다면, FFN(Feed-Forward Network) 은 정반대 결이에요 — “각자 자리로 돌아가 혼자 정리하는” 일.

회의로 비유하면 이래요. attention이 회의실에서 서로 의견을 교환하는 단계라면, FFN은 회의가 끝나고 각자 자리로 돌아가 들은 내용을 혼자 노트 정리하는 단계예요.

attention : "다른 단어한테서 정보 가져와"   (교환)
FFN       : "이제 네 자리에서 받은 거 혼자 정리해"  (가공)

position-wise — 단어끼리 안 섞는다

FFN의 결정적 성질은 position-wise예요. 각 단어 벡터가 독립적으로, 단어끼리 안 섞이고 똑같은 작은 신경망을 따로따로 통과해요. 1번 단어 벡터, 2번 단어 벡터가 각각 같은 FFN을 거치되 서로 안 봐요.

왜 안 섞냐면 — 섞는 일은 방금 attention이 다 했으니까요. attention에서 맥락을 빨아들인 다음, FFN에서 “그 맥락을 받아 내 자리에서 한 번 더 가공”하는 분업이에요. 교환(attention) → 정리(FFN)가 한 세트로 번갈아 도는 게 Transformer 블록의 기본 리듬이죠.

구조 — 4배 부풀렸다 줄인다

FFN 구조는 의외로 단순해요. 두 개 층의 완전연결 신경망에 활성화 함수(ReLU 등)가 사이에 끼어 있어요. 특이한 건 차원을 한 번 부풀렸다 줄인다는 거예요.

입력 512차원 → (확장) 2048차원 → (활성화) → (축소) 512차원

4배로 넓혔다가 다시 좁혀요. 넓은 중간층에서 더 복잡한 패턴을 만들 공간을 주는 거예요.

FFN은 “지식 저장소”

한 겹 더 — FFN은 모델의 “지식 저장소” 역할을 한다는 해석이 있어요. attention이 단어들 사이의 관계를 그리는 장치라면, FFN은 “이 단어가 어떤 개념과 연결되는지”를 가중치에 저장하는 장치예요. 예를 들어 “파리”라는 단어가 들어오면 특정 FFN 뉴런이 “프랑스의 수도”라는 관련 개념을 활성화시키는 식이죠.

그래서 Transformer 블록을 “관계 그리기(attention) → 의미 꺼내 쓰기(FFN)” 의 교대 반복으로 보면, 왜 이 둘이 한 세트로 묶여 반복되는지가 자연스럽게 이해돼요.

그리고 이 4배 부풀리기 때문에 모델 파라미터의 대부분이 사실 FFN에 있어요. attention보다 FFN이 훨씬 무거운 거예요. 그래서 최신 모델이 쓰는 MoE(Mixture of Experts) 가 바로 이 FFN을 여러 개의 “전문가”로 쪼개서, 토큰마다 그중 일부 전문가만 켜는 기법이에요 — 무거운 FFN에서 효율을 짜내는 거죠.

지식이 FFN에 있으면 attention은 지식이 없나

“FFN이 지식 저장소”라 했더니 의문이 오죠 — “그럼 attention은 지식이 없는 거야?” 역할이 달라요. attention은 ‘관계’를 그려요 — 이 문장에서 누가 누구와 엮이는지. FFN은 ‘내용’을 꺼내요 — 그 단어가 세상에서 뭘 뜻하는지. “파리는 프랑스의 수도”라는 사실은 FFN 가중치에 들어 있고, “이 문장에서 ‘파리’가 ‘에펠탑’과 엮인다”는 관계는 attention이 그려요. 관계를 그리려면(attention) 내용을 알아야(FFN) 하니, 그 둘이 블록마다 번갈아 도는 거죠.

왜 하필 4배 부풀리나

“512 → 2048(4배) → 512″에서 “왜 4배? 2배나 8배는?” 싶죠. 4배는 경험적으로 찾은 균형점이에요. 너무 좁으면(2배) 중간층에서 만들 패턴 공간이 부족하고, 너무 넓으면(8배) 계산·메모리만 늘고 성능은 별로 안 올라요. head당 64~128과 똑같은 “표현력 vs 비용” 줄다리기예요. (모델마다 조금씩 달라요 — 정확히 4배가 법칙은 아니고 관례에 가까워요.)

자기 검증 — (1) “attention과 FFN의 역할 차이”를 한 문장으로? → attention은 단어끼리 정보 교환(섞기), FFN은 각 자리에서 혼자 정리(안 섞기). (2) “왜 모델 파라미터의 대부분이 FFN?” → 차원을 4배 부풀렸다 줄이니까.

부품 ④ Residual + LayerNorm — 깊게 쌓기 위한 안전장치

96층을 쌓으려면 두 장치가 더 필요해요.

Residual Connection — 식은 출력 = x + F(x). 변환 결과 F(x)만 넘기는 게 아니라 원본 x를 그대로 더해요.

x      = [3, 1]        ← 입력
F(x)   = [0.2, -0.5]   ← 변화분
출력    = [3.2, 0.5]    ← 원본 + 변화분

왜? 층이 깊어지면 학습 신호가 마지막 층→첫 층으로 거꾸로 흐르다 거의 0이 돼서 앞층이 학습이 안 돼요(vanishing gradient). x +가 신호가 변환을 건너뛰는 지름길이에요 — 고속도로 옆 비상차선. 그래서 96층이어도 신호가 첫 층까지 닿아요. 게다가 F가 이상한 값을 뱉어도 원본이 보존되니, “이 층은 굳이 안 바꿔도 돼”를 모델이 고를 수 있어요.

LayerNorm — 벡터 안 값들을 매번 “평균 0·분산 1 근처”로 진정시켜요.

[50, 2, -30, 8]  →  정규화 →  [1.6, -0.1, -1.3, 0.1]   (순위·비율은 그대로, 크기만 진정)

그런데 값이 왜 튀냐(이 의문이 핵심이에요)? 세 가지가 겹쳐요 — ① 곱셈을 수백 번 반복(1.1을 100번 곱하면 13,780, 0.9면 0.00003), ② Residual이 계속 더해 누적, ③ 학습 중 특정 칸이 세짐. 튀는 값은 고장이 아니라 깊은 망의 자연 현상이라, 매번 진정시킬 장치가 상시 필요해요.

왜 “자르지(clip)” 않고 “정규화”하나? 자르면(50→10) “이게 50이었나 1000이었나”라는 정보가 영영 사라져요. 정규화는 순위·비율을 보존하며 크기만 조정하죠. 게다가 LayerNorm엔 정규화 후 다시 키우는 학습된 손잡이가 있어서, “여긴 정말 크게 필요해” 싶으면 다시 키워요. 자르기는 “큰 값” 선택지를 영영 닫지만, 정규화는 열어둡니다.

Add & Norm — 둘은 한 세트

블록 그림에 나오는 “Add & Norm” 이 바로 이 둘이에요. Add = Residual(원본 x 더하기), Norm = LayerNorm(값 진정). 순서는 변환(attention 또는 FFN) → 원본 더하기(Add) → 진정(Norm). 그래서 한 블록 안에서 attention 뒤에 한 번, FFN 뒤에 한 번, 총 두 번 감싸요.

자기 검증 — (1) “왜 F(x)만 안 쓰고 x를 더하나?” → 깊은 층까지 학습 신호가 닿게(지름길) + 원본 보존. (2) “LayerNorm을 안 하면?” → 값이 튀어 계산이 휘둘리고 학습 신호가 폭발하거나 사라진다. (3) “Add & Norm”은 결국 Residual + LayerNorm, attention·FFN 각각 뒤에 한 번씩.

블록 한 개 = 네 부품의 조립

지금까지 본 네 부품(멀티헤드 attention · 위치인코딩 · FFN · Residual/LayerNorm)을 한 그림으로 묶으면, Transformer 블록 한 개가 돼요.

입력 (토큰 벡터 + 위치인코딩)
   │
┌──┴── Transformer 블록 ──────────────┐
│  ① Multi-Head Attention  (검색 여러 벌)│
│       → Add & Norm  (Residual+LayerNorm)│
│  ② Feed-Forward  (각자 정리)            │
│       → Add & Norm                      │
└──────────────────────────────────────────┘
   ▼ 출력

읽는 순서가 곧 의미예요. 먼저 ①에서 단어들이 서로 정보를 교환하고(attention), 그 결과를 Add & Norm으로 감싸 안정시킨 뒤, ②에서 각 단어가 자기 자리에서 혼자 정리하고(FFN), 다시 Add & Norm으로 감싸요. “교환 → 정리”가 한 블록 안에 한 번씩 들어 있는 거죠.

왜 교환이 먼저고 정리가 나중일까요? 혼자 정리하려면(FFN) 먼저 주변에서 정보를 받아와야(attention) 하니까요. 빈손으로는 정리할 게 없잖아요. 순서를 바꿔 FFN을 먼저 두면, 아직 주변 맥락을 못 받은 상태라 각 단어가 자기 정보만으로 헛돌아요. 그래서 “교환 → 정리”가 거의 모든 Transformer에서 고정된 순서예요. 그리고 이 블록 하나를 통과할 때마다 각 단어 벡터가 “맥락이 한 겹 더 녹아든” 상태로 갱신돼요.

자기 검증 — “왜 attention이 FFN보다 먼저인가?” → 혼자 정리하려면(FFN) 먼저 주변에서 정보를 받아와야(attention) 하니까. 순서를 바꾸면 빈손으로 정리하는 꼴.

블록 한 개 — 멀티헤드 attention + Add&Norm + FFN + Add&Norm
블록 한 개 — 멀티헤드 attention + Add&Norm + FFN + Add&Norm

층 쌓기 — 의미가 누적된다

블록 한 개를 통과하면 각 단어 벡터가 한 번 갱신돼요. 그런데 한 번으론 부족해요 — 한 블록의 attention은 한 겹의 관계(주로 가까운·표면 관계)밖에 못 잡거든요. 그래서 이 블록을 N번 쌓습니다(GPT-3은 96번).

층을 쌓을수록 무슨 일이 일어나는지, 3문장짜리 글로 따라가 볼게요.

① 어제 그 고양이는 배가 고파서 울었다.
② 주인이 늦게 와서 밥을 못 줬기 때문이다.
③ 오늘은 일찍 밥을 받아 그르렁거린다.

“고양이”라는 한 토큰이 층을 내려갈수록 의미가 부풀어요:

1층(표면) :  "배고파서 운 고양이"
중간층    :  "주인이 늦어 밥 못 먹어, 어제 운 고양이"   (문장 넘는 인과·생략주어)
깊은 층   :  "어제는 울었지만 오늘은 만족한 고양이"      (어제↔오늘, 전체 서사)
“고양이” 벡터가 1층→중간→깊은 층으로 의미가 누적되는 그림

왜 먼 관계는 깊은 층에서야 잡히나

핵심은 정보가 한 층에 한 겹씩 익어 퍼진다는 거예요. 1층에선 각 단어가 아직 “자기 자신”만 알아요. 1층의 “주인”은 그냥 ‘주인’이고, “늦게 옴”이 아직 안 익었어요. 그러니 “주인이 늦어서 고양이가 배고팠다” 는 복합 인과를 1층에선 못 만들어요.

1층 통과 후 → "주인" 벡터에 "늦게 옴"이 익음 / "고양이" 벡터에 "배고픔"이 익음
2층        → 이제 익은 둘을 연결 → "주인 늦음 → 고양이 배고픔" 인과 잡힘
깊은 층     → 이 누적이 반복 → 문장 ③까지 닿아 "오늘은 만족" 서사 완성

그래서 실험으로 관찰되는 경향이 있어요 — 아래층은 품사·인접 같은 낮은 수준 관계, 위층은 문장 주제·논리·서사 같은 높은 수준 관계를 잡아요. (단, 이건 글에 있는 정보를 점점 멀리 연결하는 거지, 없는 사실을 지어내는 건 아니에요 — 그건 환각이거나 세계지식 추측이에요.)

층도 무한정 깊으면 더 좋나

“head 개수처럼 층도 깊을수록 좋아?” 어느 선까지는요. 근데 무한정은 아니에요. 층이 깊어지면 ① 학습 신호가 약해지고(Residual로 완화하지만 완전하진 않아요) ② 계산·메모리가 그만큼 늘고 ③ 어느 지점부터는 깊이를 늘려도 성능이 거의 안 올라요(수익 체감). 그래서 실제 모델은 수십~수백 층에서 멈춰요. “더 깊이 vs 더 넓게(차원·head)”를 같은 예산 안에서 저울질하는 거죠.

아래층=품사, 위층=서사를 사람이 어떻게 알아냈나

“실험으로 관찰된다”고 했는데, 안을 어떻게 들여다봤을까요? probing이라는 방법이에요. 각 층의 출력 벡터만 떼어내서 “이걸로 품사를 맞힐 수 있나? 구문 구조는? 문장 주제는?”을 따로 시험해요. 그랬더니 아래층 출력으론 품사·인접 관계가 잘 맞고, 위층 출력으론 주제·논리가 잘 맞더라는 거죠. 모델이 “의도해서” 그렇게 나눈 게 아니라, 사람이 뜯어보니 그런 경향이 보인 거예요.

자기 검증 — “한 블록이면 되지 왜 N번 쌓나?” → 한 겹 attention은 가까운·표면 관계만 잡고, 먼·복합 관계는 정보가 층마다 한 겹씩 익어 퍼져야 닿으니까.

논문 원형은 “두 덩어리” — Encoder / Decoder

여기까지가 블록이에요. 그런데 “Attention Is All You Need” 논문의 그림(Figure 1)을 처음 열면, 이 블록이 한 줄이 아니라 두 덩어리로 그려져 있어요. 논문의 원래 목표가 번역기(영어→프랑스어)였거든요.

“읽는 일”과 “쓰는 일”은 성격이 다르니 두 덩어리로 나눈 거예요. 둘 다 부품은 똑같은데(멀티헤드 attention + FFN + Add&Norm), 다른 건 attention이 볼 수 있는 범위 하나뿐이에요. 영어 “I love you” → 프랑스어 “Je t’aime”로 보죠.

Encoder = 읽는 쪽 (양방향)

영어 문장은 통째로 한 번에 주어져요. 그러니 각 단어가 앞뒤를 다 봅니다. “love”를 처리할 때 왼쪽 “I”도 보고 오른쪽 “you”도 봐요. 왜 다 봐도 되냐면 — 읽기니까요. 문장이 이미 다 거기 있으니, 전체 맥락을 활용하는 게 이득이에요. 결과는 영어 문장의 “의미 덩어리”.

Decoder = 쓰는 쪽 (단방향)

프랑스어는 한 단어씩 만들어가요. Je → t’ → aime. 여기가 핵심이에요. “Je”를 쓰고 다음 “t'”를 예측하는 순간, 아직 안 만든 “aime”를 보면 안 됩니다.

왜? 실제로 쓸 때는 미래 단어가 아직 없으니까요(만들기 전이잖아요). 그런데 학습할 땐 정답 문장(“Je t’aime”)을 통째로 주거든요. 이때 미래를 보게 두면 “다음 단어 맞히기” 시험에서 정답지를 보고 베끼는 컨닝이 돼요. 그래서 각 단어는 자기 왼쪽(이미 만든 것)만 봐요.

Encoder (양방향)            Decoder (단방향)
      I  love you                Je  t'  aime
  I   ■   ■   ■            Je   ■   ·   ·
love  ■   ■   ■            t'   ■   ■   ·
 you  ■   ■   ■           aime  ■   ■   ■
   → 다 본다                  → 자기+왼쪽만 (미래는 가림)
Encoder 양방향(다 채움) vs Decoder 단방향(미래 삼각형 가림) 점수표
Encoder 양방향(다 채움) vs Decoder 단방향(미래 삼각형 가림) 점수표

왜 굳이 두 덩어리야 — 한 덩어리로 다 하면?

여기서 회장님이 한발 앞서갈 거예요 — “한 덩어리(Decoder)로 입력·출력 다 하면 되잖아. …아, 그게 GPT네?” 정확해요. 실제로 요즘은 번역도 decoder-only(GPT 방식)로 잘 해요. 그럼 논문은 왜 둘로 나눴을까요? 2017년 당시 번역에선 입력(영어)을 양방향으로 통째 이해한 뒤 출력을 뽑는 게 자연스러웠고, 그래서 “양방향 읽기 전담(Encoder) + 단방향 쓰기 전담(Decoder)” 구조가 먼저 나온 거예요. 그러다 “입력도 그냥 왼쪽 텍스트로 취급하면 Decoder 하나로 충분하다”는 게 밝혀지면서 decoder-only가 범용으로 이겼죠. 회장님의 직감이 사실 역사의 결론이에요.

자기 검증 — (1) “Encoder와 Decoder는 같은 부품인데 뭐가 다른가?” → attention 시야. Encoder=양방향(다 봄, 읽기), Decoder=단방향(왼쪽만, 쓰기). (2) “Decoder가 미래를 가리는 이유는?” → 안 만든 미래를 보면 다음 단어 예측이 컨닝이 되니까.

Masked attention — 미래를 −무한대로 가리기

그럼 미래를 어떻게 가릴까요? 5편의 softmax를 떠올려 보세요. exp(0)=1이라 0점도 0%가 아니었죠. 진짜 0%로 만들려면 점수를 −무한대로 — exp(−무한대)=0이니까요.

t'가 본 점수: Je=2.0, t'=1.5, aime=3.0(미래!)
마스킹 안 함 → softmax → Je 0.23 · t' 0.14 · aime 0.63   ← 미래를 0.63이나 봄 = 컨닝
마스킹    → aime를 −무한대 → softmax → Je 0.62 · t' 0.38 · aime 0.00   ← 완전 차단

점수표 오른쪽 위(미래)에 −무한대를 깔고 softmax를 돌리면, 각 단어가 자동으로 자기 왼쪽까지만 봐요. 이게 masked self-attention. (5편의 exp(0)=1과 정확히 대칭인 트릭이에요. 실제로는 −무한대 대신 아주 큰 음수(예: −10,000,000,000)를 써요 — exp(−100억)은 컴퓨터에서 사실상 0이라 결과가 같거든요.)

−무한대를 컴퓨터가 진짜 무한대로 넣나

“점수를 −무한대로”라는데, 컴퓨터에 무한대가 어딨어요? 아주 큰 음수(예: −10,000,000,000)를 씁니다. 그런데 exp(−100억)은 부동소수점에서 너무 작아 정확히 0.0으로 떨어져요(underflow). 그래서 “큰 음수면 완전한 0은 아니잖아?”라는 걱정과 달리, 실제로는 0과 구분이 안 되는 값이라 미래가 완벽히 차단돼요.

자기 검증 — “미래를 어떻게 가리나?” → 미래 칸 점수를 −무한대로 만들면 exp(−무한대)=0 → softmax에서 그 칸이 0%가 되어 안 봐진다. 5편의 exp(0)=1(0점도 0%가 아님)과 정확히 대칭.

Cross-attention — 두 덩어리를 잇는 다리

Decoder는 자기 만든 프랑스어만 보면 안 되죠 — 영어 원문도 봐야 번역이 되니까요. 그게 Decoder 안의 두 번째 attention, cross-attention이에요.

5편의 Q·K·V를 떠올리되, 출처가 갈려요:

Self-attention :  Q·K·V 모두 같은 곳       (자기 문장)
Cross-attention:  Q = Decoder(프랑스어, 만드는 쪽)
                  K·V = Encoder(영어, 읽은 쪽)

“t'(너를)”를 만들 때, “t'”가 Q가 되어 영어를 검색해요:

t'(Q) · 영어 K들:  I=0.1, love=0.2, you=0.9  → you의 V를 가장 많이 가져옴 → "t'"에 "you 뜻"이 실림
Cross-attention — Q는 Decoder, K·V는 Encoder,
Cross-attention — Q는 Decoder, K·V는 Encoder, “t'”가 “you”를 참조하는 다리

만드는 쪽이 질문하고, 원문 쪽이 답한다. Encoder와 Decoder를 잇는 유일한 다리예요. 이게 없으면 Decoder는 영어를 한 번도 못 보고 프랑스어를 지어내겠죠. (Encoder엔 cross가 없어요 — 읽기만 하니 self만.)

자기 검증 — (1) “Self-attention과 Cross-attention의 차이?” → Self는 Q·K·V가 모두 같은 문장에서, Cross는 Q=Decoder·K·V=Encoder. (2) “왜 Decoder에만 cross가 있나?” → 원문(영어)을 참조해야 번역이 되니까. Encoder는 읽기만 해서 self만 있다.

GPT는 왜 Decoder만 쓰나

지금까지는 번역기(Encoder+Decoder)였어요. 그런데 ChatGPT·Claude는 Decoder만 써요. Encoder도, cross-attention도 없어요. 그럼 내 질문(입력)은 누가 읽나?

핵심은 — 입력과 출력이 “한 흐름”이면 나눌 필요가 없다는 거예요. 번역은 영어·프랑스어가 다른 것이라 둘로 나눴지만, 대화는 내 질문과 모델의 답이 같은 텍스트 한 줄이거든요.

[고양이는 영어로?  ____]
 └── 이 왼쪽 전부 읽고 ──┘ 다음 한 단어 예측 → "cat"
[고양이는 영어로?  cat  ____] → 또 왼쪽 다 읽고 다음...

내 질문은 그냥 “이미 쓰여진 왼쪽 텍스트” 로 취급돼요. Decoder가 다음 단어를 만들 때마다 masked self-attention으로 “질문 + 지금까지 쓴 답”을 왼쪽으로 읽고 다음 하나를 예측해요. 입력을 읽는 게 따로 없는 거죠. (3편 「추론」의 “한 글자씩 이어 쓰기”가 이거예요.)

Decoder-only — 질문도
Decoder-only — 질문도 “왼쪽 텍스트”, 한 단어씩 이어 쓰기

번역기였던 게 어떻게 대화·코딩까지? 모든 작업을 “앞 텍스트 → 다음 단어” 하나로 환원할 수 있어서예요. “I love you 를 한국어로:” 다음을 예측하면 번역, “Q: 날씨? A:” 다음이면 대화, “# 두 수 더하기\ndef” 다음이면 코딩. 작업별 전용 모델이 필요 없어요. 그래서 decoder-only가 범용 만능이 됐어요.

질문이 아주 길면 앞부분을 까먹나

“질문이 100만 토큰이면 Decoder가 앞부분을 까먹어? 5편의 Lost in the Middle이랑 같은 건가?” 좋은 연결이에요. masked self-attention은 원리상 왼쪽 전부를 거리 무관하게 직접 봐요(5편). 그래서 “까먹는다”기보단, 아주 긴 맥락에선 중간 정보가 상대적으로 희미해지는 경향이 관찰돼요 — 위치인코딩의 먼 거리 표현, 학습 때 본 길이, attention이 양 끝에 쏠리는 편향 같은 게 겹쳐서요. 그래서 핵심 지시를 프롬프트 양 끝(특히 끝)에 두면 더 잘 잡혀요. 5편의 Lost in the Middle과 뿌리가 닿아 있죠.

자기 검증 — (1) “ChatGPT엔 Encoder가 없는데 내 질문은 누가 읽나?” → 따로 읽는 부분이 없다. 질문이 “이미 쓰인 왼쪽 텍스트”가 되고, masked self-attention이 다음 단어를 만들 때마다 그 왼쪽 전체를 읽는다. (2) “번역기가 어떻게 만능이 됐나?” → 모든 작업을 “다음 단어 예측” 하나로 환원했으니까.

곁가지 — BERT는 Encoder만 쓴다 (이해 전용)

decoder-only의 반대 선택도 있어요. BERT는 Encoder만 씁니다. Decoder가 “생성”(다음 단어, 단방향)에 특화됐다면, Encoder는 정반대 — “이해”(양방향)에 특화돼요.

GPT (생성):  "나는 밥을 ___"       → 다음: "먹었다"  (왼쪽만 봄)
BERT (이해): "나는 [MASK]를 먹었다" → 빈칸: "밥"     (앞뒤 다 봄)

GPT가 “다음 단어 맞히기”라면, BERT는 “빈칸 맞히기” 로 학습해요. 문장 한가운데 단어를 가리고, 앞뒤 맥락 양쪽으로 그 빈칸을 맞히죠. 미래를 가릴 필요가 없어요 — 생성이 아니라 이해니까요. 그래서 BERT는 분류·검색·감정분석·개체명 인식 같은 “이해” 작업에 강해요(구글 검색이 2019년 BERT를 도입해 질의-문서 매칭을 개선했어요).

왜 요즘은 decoder-only가 대세냐면 — 생성(대화·글쓰기)이 시장의 핵심이 됐고, 잘 생성하려면 어차피 잘 이해해야 하거든요. 그래서 decoder가 이해까지 흡수하며 범용으로 컸어요. 그래도 encoder-only는 지금도 분류·검색·임베딩 같은 특정 용도에서 현역이에요 — 한 단어씩 답을 만들 필요 없이 “이해 점수”만 빠르게 뽑으면 되는 일엔 더 가볍고 적합하거든요.

(참고: Encoder/Decoder는 Transformer만의 용어가 아니라 딥러닝의 일반 역할이에요 — 입력을 의미로 압축(Encoder) / 출력을 생성(Decoder). RNN seq2seq, 오토인코더에서도 쓰던 거고, Transformer는 이 역할을 attention으로 구현한 거죠. 단, “positional encoding“이나 “UTF-8 인코딩“의 encoding은 어원만 같고 층위가 달라요 — Transformer의 Encoder는 “이해 담당 큰 부품”입니다.)

왜 이 구조가 세상을 바꿨나 — 그리고 O(N²) 대가

세 가지가 RNN을 이겼어요. ① 병렬화 — 5편의 한 줄이 행렬 곱이라 GPU에 통째로(RNN은 순서대로라 못 함) → 모델을 크게 키우는 게 현실이 됨. ② Scaling law — 키울수록 성능이 꾸준히 오름(GPT-2→3→4→Claude 경로). ③ 범용성 — “다음 단어 예측” 하나로 다 됨.

대가도 있어요. attention은 모든 단어쌍을 비교하니, 토큰 N개면 점수표가 N×N칸이에요:

토큰 1,000 → 100만 칸
토큰 2,000 → 400만 칸  (토큰 2배인데 계산 4배)
토큰 100만 → 1조 칸    (10배면 100배)
토큰 수 N vs 계산량 N² 폭증 곡선
토큰 수 N vs 계산량 N² 폭증 곡선

O(N²) 가 “긴 프롬프트일수록 API가 비싸고 느린” 이유, “100만 토큰 컨텍스트가 비싼” 이유예요. RNN은 N에 비례(N배)였는데, Transformer는 병렬을 얻은 대신 제곱 비용을 치러요. 그럼 이걸 어떻게 견디느냐 — 그게 이 편의 마지막입니다.

자기 검증 — “왜 긴 프롬프트가 비싼가?” → attention이 모든 단어쌍을 비교하니 토큰 N에 대해 O(N²). 토큰 2배면 계산 4배. RNN(N배)과 달리 제곱으로 든다.

긴 컨텍스트를 싸게 — FlashAttention · Sliding Window

100만 토큰이 “왜 최근에야” 가능해졌나. 점수표를 다 만들면 메모리가 터지거든요.

N = 100만 토큰 → 점수표 N² = 1조 칸
FP16(2바이트)로 메모리에 올리면 → 약 2TB
GPU 메모리는 80GB → 애초에 못 올림

두 갈래로 풀어요.

① FlashAttention — 점수표를 통째로 안 만든다 (정확)

핵심 한 줄:

결과는 똑같다(근사 아님). 점수표 N×N을 메모리에 한 번에 만들지 않고, 작은 블록(타일)으로 쪼개 흘려보내며 같은 답을 낸다.

기존:   N×N 점수표 전체를 메모리에 만듦 → softmax → V 곱   (메모리 폭발)
Flash:  타일 하나 계산 → 결과에 누적 → 버림 → 다음 타일   (전체를 동시에 안 들고 있음)

비유: 거대한 엑셀을 다 펼치는 대신 한 화면씩 넘기며 합계만 갱신하는 것. 마지막 합계는 다 펼친 것과 똑같지만, 한 번에 든 건 한 화면뿐이에요.

메모리:  O(N²) → O(N)    (점수표를 저장 안 하니까)
속도:    오히려 빨라짐     (GPU는 메모리 왕복이 병목인데, 그게 줄어서)
정확도:  손실 0 (exact)
FlashAttention — 점수표를 타일로 쪼개 흘려보내기
FlashAttention — 점수표를 타일로 쪼개 흘려보내기

그래서 FlashAttention은 “공짜 점심” 에 가까워요 — 결과는 그대로인데 메모리는 줄고 속도는 올라요. 요즘 LLM이 긴 컨텍스트를 다루는 바탕 기술이에요. (단 계산량 자체는 여전히 O(N²) — 메모리·속도만 개선.)

② Sliding Window — 가까운 것만 본다 (근사)

FlashAttention이 “어떻게 계산하나”를 영리하게 했다면, Sliding Window는 “무엇을 계산하나” 를 줄여요. 각 토큰이 가까운 W개(예: 왼쪽 4096개)만 봐요.

기존:      각 토큰이 N개 전부 봄  → N × N
Sliding:   각 토큰이 W개만 봄     → N × W  (선형)

N=100만, W=4096:  100만 × 4096 = 40억 칸  (1조의 1/250)

대가는 먼 토큰을 직접 못 본다는 것. 그런데 층 쌓기 기억하죠 — 층을 거치면 간접적으로 도달해요. 1층에서 가까운 4096개, 2층에선 그것들이 또 각자 봤으니 더 멀리까지 정보가 퍼져요(receptive field가 W씩 확장). 먼 정보가 여러 층을 거쳐 전달되는 거예요(약간의 손실 감수). 일부 LLM(Mistral 계열)이 써요.

Sliding Window — 각 토큰이 가까운 W개만 보고, 층으로 범위 확장
Sliding Window — 각 토큰이 가까운 W개만 보고, 층으로 범위 확장

둘 대비

                  무엇을 바꾸나        정확도        효과
FlashAttention    계산 순서·메모리     정확(exact)   메모리 O(N²)→O(N), 속도↑
Sliding Window    보는 범위 자체        근사          계산 O(N²)→O(N), 먼 건 층으로

FlashAttention = “같은 걸 영리하게 계산”(공짜), Sliding Window = “덜 보고 싸게”(근사). 둘은 충돌 안 해서 같이 쓰기도 해요. 여기에 위치인코딩의 RoPE 확장까지 합쳐져서, “4K 학습 → 100만 토큰” 이 현실이 됐어요.

FlashAttention이면 100만 토큰도 빠른 거 아냐?

“FlashAttention이 긴 컨텍스트의 바탕”이라니까 “그럼 100만 토큰도 이제 빠르겠네?” 싶죠. 반만 맞아요. FlashAttention은 메모리를 O(N)으로 줄이고 속도를 끌어올리지만, 계산량 자체는 여전히 O(N²) 예요. 그러니 100만 토큰은 “메모리가 터지진 않게” 됐을 뿐, 계산은 여전히 무겁고 느려요(그래서 비싸고요). 계산량 자체를 줄이려면 Sliding Window처럼 “덜 보는” 근사를 써야 하고, 그건 정확도를 일부 내주는 거죠. “공짜로 좋아지는 건 메모리·속도까지, 계산량은 트레이드오프”가 정확한 그림이에요.

자기 검증 — “긴 컨텍스트를 어떻게 싸게 다루나?” → FlashAttention(점수표를 통째로 안 만들어 메모리 O(N)·정확)과 Sliding Window(가까운 W개만, 근사)로 O(N²) 벽을 넘고, RoPE 각도 압축으로 거리를 늘린다.

핵심 4가지 정리

  1. Transformer 블록 = 멀티헤드 attention(검색 여러 벌) + FFN(혼자 정리), 각각 Residual(원본 백업)·LayerNorm(값 진정) 으로 감싼 것. 여기에 위치인코딩으로 순서를 넣고, 이 블록을 N번 쌓으면 의미가 누적된다.
  2. 논문 원형은 Encoder(읽기·양방향) + Decoder(쓰기·단방향, masked) 두 덩어리에 cross-attention 다리. ChatGPT는 Decoder만 — 질문도 “왼쪽 텍스트”로 취급해 “다음 단어 예측” 하나로 모든 걸 한다.
  3. 병렬·scaling·범용성으로 세상을 바꿨다. 단 attention의 O(N²) 가 긴 컨텍스트의 비용 벽.
  4. 그 벽은 FlashAttention(점수표를 안 만들어 메모리 절약·정확)과 Sliding Window(가까운 것만, 근사)로 넘는다 — RoPE 확장과 합쳐 100만 토큰을 가능케 한다.

닫으며 — 이제 전체 그림이 읽힌다

5편 Attention부터 여기까지, “내가 ChatGPT에 친 한 문장이 답으로 나오기까지” 의 부품을 다 봤어요. 토큰 → 임베딩+위치 → (멀티헤드 attention + FFN, Add&Norm) 블록을 여러 층 → 다음 단어 확률 → 한 글자씩 반복. GPT는 그중 Decoder만 떼어 모든 일을 “다음 단어 예측”으로 풀고, FlashAttention·Sliding Window로 긴 컨텍스트까지 견딥니다.

참고: “Attention Is All You Need”(2017) · 5편 「Attention」 · RoPE(Su et al.) · Jay Alammar “The Illustrated Transformer”

著者: VibeCoding Tailor / 운영: 태일러의 은신처(shuntailor.net)

바이브코딩 태일러
바이브코딩 태일러
AI의 작동 원리와 비즈니스 적용을 일본어·한국어로 기록합니다. 매주 월요일 뉴스레터 발행 중.
뉴스레터 구독하기 →

댓글

JAKO