본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.
본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.
AutoResearch Analyzer
2026. 3. 31. · 0 Views
Value Embeddings 완벽 분석 ResFormer 아키텍처
AutoResearch 프로젝트의 train.py에 구현된 Value Embeddings(ResFormer) 아키텍처를 심도 있게 분석합니다. 입력 의존적 값 벡터에 입력 독립적 임베딩을 결합하는 혁신적인 방식과, 레이어별 전문화, 게이팅 메커니즘, 초기화 전략까지 완벽히 파헤칩니다.
목차
- Value Embeddings 개념과 ResFormer
- has_ve 교대 레이어 적용 로직
- ve_gate 입력 종속 게이팅 메커니즘
- 값 임베딩과 어텐션 값의 결합
- 게이트 가중치 초기화 전략
- num_scaling_params에서 파라미터 카운팅
1. Value Embeddings 개념과 ResFormer
시작하며
김개발 씨가 AutoResearch의 train.py를 분석하다가 의아한 코드를 발견했습니다. CausalSelfAttention에는 익숙한 c_q, c_k, c_v 프로젝션뿐만 아니라, value_embeds라는 낯선 임베딩 테이블이 등장했기 때문입니다. 박시니어 씨가 다가오며 말했습니다. "이게 바로 ResFormer의 핵심이야. 기존 트랜스포머와 결정적으로 다른 부분이지."
개요
Value Embeddings는 트랜스포머의 어텐션 계산에서 값(Value) 벡터를 생성할 때, 숨겨 상태에서의 선형 투영뿐만 아니라 토큰 ID에서 직접 임베딩을 조회하여 결합하는 기법입니다. 마치 학생이 수업 내용(맥락)에 대한 요약뿐만 아니라 교과서 원문(토큰 본연의 의미)도 함께 참고하는 것과 같습니다. 이 방식을 ResFormer(Residual Former)라고 부르며, 값 벡터에 잔차 연결을 추가하는 형태로 동작합니다.
코드 예제
# GPT 클래스에서 레이어별 Value Embedding 테이블 생성
head_dim = config.n_embd // config.n_head
kv_dim = config.n_kv_head * head_dim
self.value_embeds = nn.ModuleDict({
str(i): nn.Embedding(config.vocab_size, kv_dim)
for i in range(config.n_layer)
if has_ve(i, config.n_layer)
})
# 예: 8레이어, vocab=32768, kv_dim=768 -> 레이어당 약 25M 파라미터
설명
"AutoResearch 완전 분석 - AI 자율 연구 에이전트" 코스의 네 번째 편에 오신 것을 환영합니다. 앞선 세 편에서는 AutoResearch의 전체 아키텍처, GPT 모델 구조, 그리고 Flash Attention 3와 RoPE를 통한 어텐션 최적화 기법을 살펴보았습니다. 이번에는 어텐션 메커니즘 자체를 변형하는 Value Embeddings, 즉 ResFormer 아키텍처를 파헤쳐보겠습니다. 김개발 씨는 어제 Flash Attention 3의 코드를 분석하며 밤새 트랜스포머 아키텍처에 매료되었습니다. 오늘 아침, train.py의 GPT 클래스 __init__ 메서드를 보다가 self.value_embeds = nn.ModuleDict({...})라는 코드에 눈이 멈췄습니다. "이건 뭐죠? 토큰 임베딩인데 왜 레이어마다 따로 있죠?" 그렇다면 Value Embeddings란 정확히 무엇일까요? 쉽게 비유하자면, Value Embeddings는 마치 번역가의 사전과 노트의 관계와 같습니다. 번역가가 문장을 번역할 때(이것이 어텐션의 c_v 투영), 문맥에 맞게 자유롭게 번역하는 것도 중요하지만, 가끔은 사전에서 단어의 본래 의미를 직접 확인하는 것이 더 정확합니다. Value Embeddings가 바로 그 "사전" 역할을 합니다. 문맥에 의해 변형된 값 벡터에, 토큰의 본연 의미를 담은 임베딩을 더하여 더 풍부한 정보를 어텐션에 제공합니다. 전통적인 트랜스포머에서는 모든 레이어가 동일한 wte(Word Token Embedding) 테이블을 공유합니다. 토큰 정보는 wte에서 시작하여 각 레이어의 어텐션과 MLP를 거치며 점진적으로 변환됩니다. 하지만 깊은 레이어로 갈수록 원래 토큰의 정보가 희석됩니다. 특정 레이어에서 토큰의 "본래 의미"를 직접 참조할 방법이 없었던 것입니다. 바로 이 문제를 해결하기 위해 ResFormer가 등장했습니다. ResFormer는 각 레이어(또는 교대로 선택된 레이어)에 독립적인 값 임베딩 테이블을 제공합니다. 어텐션의 Value 벡터를 계산할 때, 기존의 c_v(x) 투영 결과에 이 임베딩을 게이트와 함께 더합니다. 이렇게 하면 레이어마다 토큰의 다른 측면을 학습할 수 있습니다. 또한 FLOPs가 전혀 증가하지 않습니다. 임베딩 조회(lookup)는 계산이 아니라 메모리 접근이기 때문입니다. 무엇보다 입력 독립적인 경로와 입력 의존적인 경로를 동시에 활용하여, 모델의 표현력을 크게 높입니다. 위의 코드를 한 줄씩 살펴보겠습니다. 먼저 head_dim과 kv_dim을 계산합니다. GQA(Grouped Query Attention)를 사용하므로 kv_dim = n_kv_head * head_dim입니다. 기본 설정에서 head_dim=128, n_kv_head=6이므로 kv_dim=768입니다. 다음으로 nn.ModuleDict 내포 표현을 사용하여 레이어별 임베딩 테이블을 생성합니다. has_ve(i, config.n_layer) 함수가 True를 반환하는 레이어에만 테이블을 만듭니다. 각 테이블은 nn.Embedding(vocab_size, kv_dim)으로, vocab_size=32768, kv_dim=768이면 레이어당 약 2,500만 개의 파라미터가 됩니다. 실제 현업에서는 어떻게 활용할까요? AutoResearch의 기본 설정(8레이어, 4개 레이어에 Value Embedding)에서 약 1억 개의 추가 파라미터가 발생합니다. 파라미터는 많지만 FLOPs는 증가하지 않으므로, 메모리 여유가 있고 추론 속도가 중요한 환경에서 특히 유리합니다. Karpathy는 이 설계를 통해 5분 타임 버짓 안에서 더 높은 val_bpb를 달성하는 실험을 자율적으로 탐색하도록 에이전트에게 제공했습니다. 하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 모든 레이어에 Value Embedding을 추가하는 것입니다. AutoResearch는 교대 패턴(has_ve 함수)을 사용하여 절반의 레이어에만 적용합니다. 모든 레이어에 추가하면 파라미터가 두 배로 늘어나고, 과적합 위험이 커집니다. 따라서 has_ve 함수의 패턴을 기반으로 적절한 레이어를 선택해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. "토큰의 원래 의미를 직접 참조한다니, 이런 접근은 처음 들어보네요." 김개발 씨가 눈을 반짝였습니다. Value Embeddings를 제대로 이해하면 어텐션 메커니즘의 표현력을 극대화할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
- Value Embeddings는 FLOPs를 증가시키지 않고 파라미터만 추가하는 parameter-heavy, compute-free 설계입니다
- 레이어별로 독립적인 테이블을 가지므로, 각 레이어가 토큰의 다른 측면을 학습할 수 있습니다
- 이 카드뉴스는 "AutoResearch 완전 분석 - AI 자율 연구 에이전트" 코스의 4/8편입니다
2. has ve 교대 레이어 적용 로직
시작하며
김개발 씨가 코드를 더 살펴보다가 한 줄짜리 함수에 시선을 멈추었습니다. "has_ve(layer_idx, n_layer)라는 함수가 있네요. 뭔가 단순해 보이는데, 이게 레이어 선택 로직 전부인가요?" 박시니어 씨가 끄덕였습니다. "맞아요. 위대한 코드는 짧은 코드에서 나오는 법이죠. 이 한 줄이 ResFormer의 레이어 배치 전략 전부야."
개요
has_ve 함수는 주어진 레이어 인덱스가 Value Embedding을 가져야 하는지 결정하는 단순한 보조 함수입니다. layer_idx % 2 == (n_layer - 1) % 2라는 한 줄의 조건식으로, 마지막 레이어가 항상 포함되도록 교대 패턴을 만듭니다. 마치 체스판의 검은 칸과 흰 칸처럼 레이어를 번갈아 선택합니다. 8레이어에서는 1, 3, 5, 7번 레이어가 선택됩니다.
코드 예제
def has_ve(layer_idx, n_layer):
"""Returns True if layer should have Value Embedding."""
return layer_idx % 2 == (n_layer - 1) % 2
# 8레이어 예시: (8-1)%2 = 1, 따라서 홀수 레이어에 적용
# layer 0: 0%2=0 != 1 -> False
# layer 1: 1%2=1 == 1 -> True
# layer 2: 2%2=0 != 1 -> False
# layer 3: 3%2=1 == 1 -> True
# ...
# layer 7: 7%2=1 == 1 -> True (마지막 레이어 항상 포함)
설명
AutoResearch의 train.py에서 가장 짧으면서도 가장 중요한 함수 중 하나가 바로 has_ve입니다. 겨우 한 줄이지만, ResFormer 아키텍처의 레이어 배치 전략 전부를 담고 있습니다. 그렇다면 이 교대 패턴의 수학적 원리는 무엇일까요? 쉽게 비유하자면, 이 패턴은 마치 체스판의 검은 칸 선택과 같습니다. 체스판은 8x8 격자이며, 검은 칸과 흰 칸이 번갈아 배열되어 있습니다. 모든 검은 칸은 같은 색이지만, 각 칸의 위치는 다릅니다. has_ve도 마찬가지입니다. 선택된 레이어들은 모두 Value Embedding을 가지지만, 각 레이어는 독립적인 임베딩 테이블을 학습합니다. Value Embedding이 도입되기 전에는 모든 레이어가 동일한 구조를 가졌습니다. 어텐션 레이어마다 c_q, c_k, c_v 프로젝션과 MLP가 동일한 방식으로 구성되었습니다. 레이어 간의 차이는 학습된 가중치뿐이었습니다. 하지만 ResFormer는 레이어를 두 그룹으로 나눕니다. Value Embedding이 있는 레이어와 없는 레이어입니다. 바로 이런 이질적인 구조를 우아하게 관리하기 위해 has_ve가 등장했습니다. 이 함수의 핵심은 (n_layer - 1) % 2라는 표현식에 있습니다. 이것은 마지막 레이어의 인덱스가 홀수인지 짝수인지를 결정합니다. 마지막 레이어가 항상 Value Embedding을 가져야 한다는 설계 원칙을 보장하기 위해, 패턴의 기준점을 마지막 레이어로 맞춥니다. 이렇게 하면 n_layer가 어떤 값이든 마지막 레이어는 항상 포함됩니다. 위의 코드를 한 줄씩 살펴보겠습니다. layer_idx % 2 == (n_layer - 1) % 2는 두 값을 비교합니다. 왼쪽은 현재 레이어의 홀짝, 오른쪽은 마지막 레이어의 홀짝입니다. 8레이어의 경우 (8-1)%2=1이므로, layer_idx가 홀수인 1, 3, 5, 7이 선택됩니다. 12레이어의 경우 (12-1)%2=1이므로, 1, 3, 5, 7, 9, 11이 선택됩니다. 9레이어의 경우 (9-1)%2=0이므로, 0, 2, 4, 6, 8이 선택됩니다. 실제 현업에서는 어떻게 활용할까요? AutoResearch의 에이전트는 이 함수를 수정하여 다양한 레이어 배치 패턴을 실험할 수 있습니다. 예를 들어 "모든 레이어에 Value Embedding을 주면 성능이 어떻게 될까?"라는 질문을 스스로 던지고, has_ve를 항상 True를 반환하도록 수정한 뒤 5분 실험을 실행합니다. 결과를 val_bpb로 평가하고, 기존 패턴과 비교하여 더 나은 구성을 자율적으로 발견합니다. 하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 교대 패턴이 아닌 임의의 레이어를 선택하는 것입니다. 예를 들어 "첫 4개 레이어에만 Value Embedding을 주면 어떨까?"라고 생각할 수 있습니다. 하지만 이렇게 하면 하위 레이어에 Value Embedding이 집중되고, 상위 레이어에는 없게 됩니다. 상위 레이어에서 토큰 정보가 더 희석되는 결과를 초래할 수 있습니다. 따라서 교대 패턴을 유지하여 레이어 전체에 균등하게 분포시키는 것이 안전합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. "한 줄의 코드가 레이어 배치 전략을 결정한다니, 수학의 아름다움이 코드에 그대로 녹아있네요." 김개발 씨가 감탄했습니다. has_ve 함수의 간결함은 "복잡한 결정도 명확한 수식으로 표현될 수 있다"는 교훈을 줍니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
- (n_layer - 1) % 2 기준점 설정으로 마지막 레이어가 항상 Value Embedding을 갖도록 보장합니다
- 8레이어 기준 절반(4개)의 레이어에만 적용하여 파라미터 증가를 제어합니다
- 에이전트가 이 함수를 수정하여 다양한 레이어 배치 패턴을 자율적으로 탐색할 수 있습니다
- 이 카드뉴스는 "AutoResearch 완전 분석 - AI 자율 연구 에이전트" 코스의 4/8편입니다
3. ve gate 입력 종속 게이팅 메커니즘
시작하며
김개발 씨가 CausalSelfAttention 클래스의 __init__ 메서드에서 작은 선형 레이어 하나를 발견했습니다. "이건 gate인데, 입력 차원이 32밖에 안 되네요. 왜 이렇게 작죠?" 박시니어 씨가 설명했습니다. "게이트가 너무 크면 모델이 단순히 value embedding을 통과시키거나 차단하는 방법을 배우는 데 집중하게 돼요. 작은 게이트가 핵심이에요."
개요
ve_gate는 Value Embedding을 어텐션 Value 벡터에 얼마나 섞을지 결정하는 입력 종속 게이트입니다. 숨겨 상태 x의 처음 32채널만 입력으로 받아, KV 헤드당 하나의 스칼라를 출력합니다. 2 * sigmoid(Wx[:32]) 공식으로 0에서 2 사이의 값을 가지며, 초기에는 1.0(중립)에서 시작합니다. 마치 수도꼭지의 밸브처럼, 상황에 따라 흐름을 조절하는 역할을 합니다.
코드 예제
# CausalSelfAttention.__init__에서 게이트 정의
self.ve_gate_channels = 32
self.ve_gate = (
nn.Linear(self.ve_gate_channels, self.n_kv_head, bias=False)
if has_ve(layer_idx, config.n_layer)
else None
)
# 파라미터 수: 32 * n_kv_head = 32 * 6 = 192개 (매우 적음)
# CausalSelfAttention.forward에서 게이트 적용
if ve is not None:
gate = 2 * torch.sigmoid(self.ve_gate(x[..., :self.ve_gate_channels]))
v = v + gate.unsqueeze(-1) * ve
설명
게이팅(gating)은 신경망에서 정보의 흐름을 동적으로 제어하는 핵심 기법입니다. ResFormer의 ve_gate는 특히 독특한 설계를 가지고 있습니다. 겨우 192개의 파라미터로, 수천만 개의 파라미터를 가진 Value Embedding의 활용도를 결정합니다. 그렇다면 ve_gate는 왜 이렇게 작게 설계되었을까요? 쉽게 비유하자면, ve_gate는 마치 오케스트라의 지휘봉과 같습니다. 지휘봉 자체는 작고 가벼운 도구이지만, 수십 명의 연주자가 연주하는 거대한 오케스트라를 조율합니다. 마찬가지로 ve_gate는 32개의 입력 채널(지휘봉의 움직임)만으로 전체 Value Embedding(오켘스트라)의 기여도를 조절합니다. 게이팅이 도입되기 전에는 Value Embedding을 단순히 Value 벡터에 더하는 방식(v = v + ve)을 사용했습니다. 하지만 이 방식은 모든 토큰, 모든 헤드에 대해 동일한 가중치로 결합합니다. 어떤 토큰은 Value Embedding의 기여가 크게 필요하고, 어떤 토큰은 거의 필요하지 않을 수 있습니다. 문맥에 따라 유연하게 조절할 방법이 없었습니다. 바로 이런 비효율을 해결하기 위해 입력 종속 게이팅이 등장했습니다. ve_gate는 현재 숨겨 상태 x를 관찰하여, "이 토큰의 현재 문맥에서 Value Embedding을 얼마나 반영해야 할까?"를 헤드별로 결정합니다. 게이트가 0에 가까우면 Value Embedding을 무시하고, 2에 가까우면 두 배로 강조합니다. 1.0이면 중립적으로 반만 반영합니다. 이 동적 조절을 통해 모델은 토큰의 중요도에 따라 Value Embedding의 기여도를 실시간으로 조정할 수 있습니다. 위의 코드를 한 줄씩 살펴보겠습니다. 먼저 ve_gate_channels = 32에서 게이트의 입력 채널 수를 정의합니다. 32개면 충분한 이유는, 게이트가 단지 헤드당 하나의 스칼라를 출력하기 때문입니다. 복잡한 함수를 근사할 필요가 없고, 단순한 "켜기/끄기/조절" 결정만 내리면 됩니다. 다음으로 nn.Linear(32, n_kv_head, bias=False)가 핵심입니다. bias=False로 설정하여 파라미터를 최소화합니다. n_kv_head=6이므로 출력은 6개의 스칼라, 즉 헤드당 하나입니다. 포워드 패스에서 x[..., :32]로 숨겨 상태의 처음 32채널만 슬라이싱합니다. 2 * torch.sigmoid(...)는 출력을 02 범위로 스케일링합니다. sigmoid 단독이면 01이지만, 2를 곱하여 중립점을 1.0으로 맞춥니다. 실제 현업에서는 어떻게 활용할까요? 게이트의 시그모이드 출력을 모니터링하면 모델이 Value Embedding을 어떻게 활용하는지 관찰할 수 있습니다. 특정 레이어의 게이트가 항상 0에 가까우면, 그 레이어의 Value Embedding이 불필요하다는 의미일 수 있습니다. 반대로 게이트가 항상 2附近이면, Value Embedding이 매우 중요한 역할을 한다는 뜻입니다. AutoResearch의 에이전트는 이런 관찰을 통해 불필요한 Value Embedding 레이어를 제거하는 실험을 자율적으로 수행할 수 있습니다. 하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 게이트 채널 수를 너무 크게 설정하는 것입니다. 128이나 256채널로 늘리면 게이트 자체가 과적합될 수 있습니다. 32채널은 Karpathy가 실험적으로 찾은 최적점이며, 이 값을 변경할 때는 반드시 val_bpb로 검증해야 합니다. 따라서 기본 32채널을 유지하면서 다른 하이퍼파라미터를 먼저 탐색하는 것이 안전합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. "192개의 파라미터로 수천만 개의 임베딩을 조절한다니, 정말 효율적인 설계네요." 김개발 씨가 노트에 적으며 중얼거렸습니다. ve_gate의 초경량 설계는 "적은 파라미터로도 큰 효과를 낼 수 있다"는 교훈을 줍니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
- 32채널 입력으로 192개의 파라미터만으로 Value Embedding의 혼합을 제어합니다
- 2 * sigmoid 공식은 중립점을 1.0으로 설정하여, 학습 초기에 안정적인 시작을 보장합니다
- 게이트 값을 모니터링하면 각 레이어의 Value Embedding 기여도를 관찰할 수 있습니다
- 이 카드뉴스는 "AutoResearch 완전 분석 - AI 자율 연구 에이전트" 코스의 4/8편입니다
4. 값 임베딩과 어텐션 값의 결합
시작하며
"드디어 핵심이군요." 김개발 씨가 CausalSelfAttention의 forward 메서드에서 실제 결합이 일어나는 코드를 찾았습니다. 박시니어 씨가 옆에서 설명했습니다. "이 4줄이 ResFormer의 전부야. c_v(x)로 계산한 입력 의존적 값에, ve(token_id)로 조회한 입력 독립적 값을 게이트와 함께 더하는 거지."
개요
ResFormer의 핵심 연산은 어텐션의 Value 벡터에 Value Embedding을 게이팅하여 더하는 것입니다. v = v + gate.unsqueeze(-1) * ve라는 한 줄로, 기존의 입력 의존적 값과 입력 독립적 임베딩이 헤드별로 가중 합산됩니다. 마치 두 개의 조명을 밝기 조절기로 섞어서 최적의 조명을 만드는 것과 같습니다. GPT.forward에서는 self.value_embeds[str(i)](idx)로 토큰 ID로부터 직접 임베딩을 조회합니다.
코드 예제
# GPT.forward에서 Value Embedding 조회 및 전달
for i, block in enumerate(self.transformer.h):
x = self.resid_lambdas[i] * x + self.x0_lambdas[i] * x0
# 토큰 ID로 직접 임베딩 조회 (입력 독립적)
ve = self.value_embeds[str(i)](idx) if str(i) in self.value_embeds else None
x = block(x, ve, cos_sin, self.window_sizes[i])
# CausalSelfAttention.forward에서 결합
if ve is not None:
ve = ve.view(B, T, self.n_kv_head, self.head_dim)
gate = 2 * torch.sigmoid(self.ve_gate(x[..., :self.ve_gate_channels]))
v = v + gate.unsqueeze(-1) * ve # 핵심: 게이팅된 잔차 연결
설명
ResFormer의 설계 철학을 한 문장으로 요약하면 "정보는 다양한 경로에서 와야 한다"입니다. 기존 트랜스포머는 단일 경로(숨겨 상태 -> c_v 투영 -> Value)만 사용했습니다. ResFormer는 여기에 두 번째 경로(토큰 ID -> 임베딩 조회 -> Value Embedding)를 추가합니다. 그렇다면 두 경로의 결합은 왜 중요할까요? 쉽게 비유하자면, 이것은 마치 기상 예보와 실시간 관측의 관계와 같습니다. 기상 예보(입력 의존적 c_v)는 최근 데이터와 모델을 기반으로 미래를 예측합니다. 하지만 예보 모델에 오류가 있을 수 있습니다. 이때 실시간 기상 관측 데이터(입력 독립적 Value Embedding)를 참고하면, 더 정확한 판단을 내릴 수 있습니다. 두 정보원을 적절히 조합하는 것이 핵심입니다. 단일 경로만 사용하던 시절에는 정보의 다양성이 부족했습니다. 모든 값이 동일한 c_v 프로젝션을 거치므로, 레이어가 깊어질수록 정보가 특정 패턴으로 수렴하는 경향이 있었습니다. 토큰의 본래 의미가 점차 희석되고, 문맥에 과도하게 편향되는 현상이 발생했습니다. 바로 이런 정보 병목을 해결하기 위해 이중 경로 설계가 등장했습니다. 입력 의존적 경로(c_v)는 문맥에 맞게 값을 조정합니다. "이 단어가 이 문맥에서는 이런 의미를 가져야 해"라는 판단을 내립니다. 입력 독립적 경로(ve)는 토큰의 고정된 의미를 제공합니다. "이 단어는 원래 이런 의미를 가지고 있어"라는 정보를 전달합니다. 게이트는 두 경로의 균형을 동적으로 조절합니다. "지금은 문맥이 더 중요해" 또는 "지금은 단어의 본래 의미가 더 중요해"라는 판단을 내립니다. 위의 코드를 한 줄씩 살펴보겠습니다. 먼저 GPT.forward의 self.value_embeds[str(i)](idx)에서 핵심 연산이 일어납니다. idx는 원본 토큰 ID 시퀀스입니다. 숨겨 상태 x가 아닌, 가장 원시적인 토큰 ID를 사용합니다. 이것이 "입력 독립적"인 이유입니다. 동일한 토큰 ID는 어떤 문맥에서든 동일한 Value Embedding을 반환합니다. 다음으로 CausalSelfAttention.forward에서 ve.view(B, T, n_kv_head, head_dim)으로 텐서를 재구성합니다. 어텐션 계산에 필요한 4차원 형태로 변환합니다. v = v + gate.unsqueeze(-1) * ve가 핵심 결합 연산입니다. gate.unsqueeze(-1)으로 헤드당 스칼라를 head_dim 차원으로 확장하여 브로드캐스팅합니다. 이 한 줄이 ResFormer의 전부입니다. 실제 현업에서는 어떻게 활용할까요? 이 이중 경로 설계는 모델의 표현력을 크게 향상시킵니다. 특히 드문 단어나 전문 용어의 경우, c_v 프로젝션만으로는 충분한 표현을 학습하기 어렵습니다. Value Embedding이 제공하는 직접적인 토큰 표현이 이런 단어들의 처리를 돕습니다. 또한 미세조정(fine-tuning) 시에도 유리합니다. 도메인 특화 토큰이 Value Embedding 테이블에서 직접 풍부한 표현을 얻을 수 있기 때문입니다. 하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 Value Embedding을 Query나 Key에도 적용하려고 하는 것입니다. ResFormer의 설계에서는 Value에만 적용합니다. Query와 Key는 위치 정보(RoPE)와 정규화(RMSNorm)가 이미 적용되어 있으며, 여기에 Value Embedding을 추가하면 어텐션 패턴이 왜곡될 수 있습니다. 따라서 반드시 Value 벡터에만 Value Embedding을 결합해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. "입력 의존적 경로와 입력 독립적 경로를 동적으로 조절한다니, 정말 우아한 설계네요." 김개발 씨가 깊은 인상을 받았습니다. 이중 경로 설계를 제대로 이해하면 트랜스포머의 정보 흐름을 더 세밀하게 제어할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
- Value Embedding은 토큰 ID(원본 입력)로 조회하므로, 숨겨 상태의 변화에 영향을 받지 않습니다
- Query와 Key에는 Value Embedding을 적용하지 않는 것이 ResFormer의 표준 설계입니다
- 게이트의 unsqueeze(-1) 연산으로 헤드당 스칼라를 head_dim 차원으로 브로드캐스팅합니다
- 이 카드뉴스는 "AutoResearch 완전 분석 - AI 자율 연구 에이전트" 코스의 4/8편입니다
5. 게이트 가중치 초기화 전략
시작하며
박시니어 씨가 초기화 코드를 가리키며 말했습니다. "초기화가 설계만큼 중요해요. 잘못된 초기화는 학습을 망칠 수 있어요." 김개발 씨가 물었습니다. "그럼 Value Embedding과 게이트는 각각 어떻게 초기화되나요?" 박시니어 씨가 설명하기 시작했습니다. "임베딩은 균등 분포로, 게이트는 영으로 초기화해요. 각각 명확한 이유가 있어요."
개요
AutoResearch는 Value Embedding 테이블을 **균등 분포(Uniform(-s, s))**로 초기화하고, 게이트 가중치를 **영(Zero)**으로 초기화합니다. 게이트를 영으로 초기화하면 sigmoid(0) = 0.5, 따라서 2 * 0.5 = 1.0으로 학습 시작 시점에 Value Embedding이 전체 강도로 반영됩니다. 마치 새로운 직원의 첫 업무 평가에서 "보통" 점수를 주어 안정적으로 시작하는 것과 같습니다. 임베딩의 스케일 s = 3^0.5 * n_embd^(-0.5)는 Xavier/Glorot 초기화와 유사한 원리를 따릅니다.
코드 예제
# Value Embedding 초기화: 균등 분포
s = 3**0.5 * n_embd**-0.5
for ve in self.value_embeds.values():
torch.nn.init.uniform_(ve.weight, -s, s)
# s = 3^0.5 * 768^(-0.5) ≈ 0.00249
# 게이트 가중치 초기화: 영 초기화 (중립 시작)
for block in self.transformer.h:
if block.attn.ve_gate is not None:
torch.nn.init.zeros_(block.attn.ve_gate.weight)
# sigmoid(0) = 0.5, 2 * 0.5 = 1.0 = 중립
# bfloat16으로 변환 (학습 정밀도 최적화)
self.transformer.wte.to(dtype=torch.bfloat16)
for ve in self.value_embeds.values():
ve.to(dtype=torch.bfloat16)
설명
신경망에서 초기화는 학습의 성패를 가르는 가장 중요한 결정 중 하나입니다. 잘못된 초기화는 기울기 소실이나 폭발을 유발하고, 학습을 불가능하게 만들 수 있습니다. AutoResearch의 Value Embedding 초기화 전략은 이 문제를 정교하게 다룹니다. 그렇다면 왜 서로 다른 초기화 방식을 사용할까요? 쉽게 비유하자면, 이것은 마치 새 건물의 기공식과 같습니다. 건물의 뼈대(임베딩 테이블)는 균등하고 견고하게 세워야 합니다. 한쪽으로 치우치면 건물이 기울어집니다. 반면 조절 장치(게이트)는 처음에는 중립 위치에 두어야 합니다. 처음부터 한쪽으로 치우쳐 있으면, 시스템 전체의 균형이 깨집니다. Value Embedding이 도입되기 전에는 임베딩 초기화에 대한 고민이 상대적으로 단순했습니다. 표준 토큰 임베딩(wte)은 보통 정규 분포 N(0, 0.02)로 초기화되었습니다. 하지만 Value Embedding은 레이어별로 독립적인 테이블이므로, 각 테이블 간의 스케일 일관성이 중요합니다. 바로 이런 일관성 문제를 해결하기 위해 균등 분포와 영 초기화 조합이 등장했습니다. s = 3**0.5 * n_embd**-0.5 공식은 Xavier/Glorot 초기화와 유사한 원리를 따릅니다. 3^0.5는 어텐션에서 Q, K, V 세 가지 경로가 합산되는 것을 고려한 보정 계수입니다. n_embd**-0.5는 차원에 따른 스케일 조정입니다. 이 스케일로 균등 분포를 사용하면, 임베딩 벡터의 분산이 적절하게 유지됩니다. 게이트의 영 초기화는 더 직관적입니다. zeros_로 모든 가중치를 0으로 설정하면, sigmoid(0) = 0.5이고 2 * 0.5 = 1.0이 됩니다. 즉, 학습 시작 시점에서는 게이트가 "중립" 상태이고, Value Embedding이 전체 강도(1.0배)로 반영됩니다. 모델은 학습을 진행하면서 게이트를 조정하여, 필요한 레이어에서 Value Embedding을 강화(2.0배)하거나 약화(0.0배)합니다. 위의 코드를 한 줄씩 살펴보겠습니다. 먼저 s = 3**0.5 * n_embd**-0.5에서 스케일을 계산합니다. n_embd=768이면 s는 약 0.00249입니다. 이것은 매우 작은 값으로, 임베딩 벡터의 각 요소가 -0.00249에서 0.00249 사이의 균등 분포를 따릅니다. 다음으로 torch.nn.init.uniform_(ve.weight, -s, s)로 각 레이어의 Value Embedding 테이블을 초기화합니다. self.value_embeds.values()로 모든 레이어의 임베딩에 동일한 스케일을 적용합니다. 게이트 초기화에서는 torch.nn.init.zeros_(block.attn.ve_gate.weight)로 가중치를 0으로 설정합니다. 이것이 has_ve 조건으로 필터링한 후에만 적용되는 이유는, Value Embedding이 없는 레이어에는 게이트 자체가 존재하지 않기 때문입니다. 실제 현업에서는 어떻게 활용할까요? 초기화 전략은 하이퍼파라미터 탐색의 효율에 직접적인 영향을 미칩니다. 좋은 초기화는 학습이 빠르게 수렴하게 하고, 더 넓은 하이퍼파라미터 공간에서 안정적으로 동작합니다. AutoResearch의 5분 타임 버짓에서는 학습 초기의 안정성이 특히 중요합니다. 영 초기화된 게이트 덕분에 모델은 처음부터 Value Embedding의 이점을 누리면서, 점진적으로 최적의 게이팅을 학습합니다. 하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 게이트를 정규 분포로 초기화하는 것입니다. 예를 들어 Kaiming 초기화(nn.init.kaiming_normal_)를 게이트에 적용하면, 학습 시작 시점에서 게이트가 불규칙한 값을 가지게 됩니다. 어떤 헤드에서는 Value Embedding이 강하게 반영되고, 어떤 헤드에서는 거의 반영되지 않는 불안정한 상태가 됩니다. 따라서 반드시 영 초기화를 사용하여 안정적인 시작을 보장해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. "초기화 하나에 이렇게 많은 고민이 들어가 있다니, 세부의 세부까지 치밀하게 설계되었군요." 김개발 씨가 노트에 빼곡히 필기했습니다. 초기화 전략을 제대로 이해하면 학습의 안정성과 수렴 속도를 크게 개선할 수 있습니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
- 게이트 영 초기화는 sigmoid(0)=0.5, 2*0.5=1.0으로 중립적 시작을 보장합니다
- 3^0.5 보정 계수는 Q, K, V 세 경로 합산을 고려한 스케일 조정입니다
- Value Embedding과 wte를 동일한 bf16으로 변환하여 학습 정밀도를 통일합니다
- 이 카드뉴스는 "AutoResearch 완전 분석 - AI 자율 연구 에이전트" 코스의 4/8편입니다
6. num scaling params에서 파라미터 카운팅
시작하며
김개발 씨가 FLOPs 추정 함수를 살펴보다가 궁금해졌습니다. "Value Embedding은 파라미터가 엄청 많은데, FLOPs 계산에서는 제외되나요?" 박시니어 씨가 고개를 끄덕였습니다. "정확해요. 임베딩 조회는 계산이 아니라 메모리 접근이에요. 그래서 FLOPs 추정에서 제외하는 거예요. 하지만 파라미터 수에는 당연히 포함되죠."
개요
AutoResearch는 estimate_flops 함수에서 Value Embedding 파라미터를 스케일링 파라미터에서 제외합니다. wte, lm_head, resid_lambdas, x0_lambdas와 함께 "non-scaling" 파라미터로 분류합니다. 이것은 임베딩 조회가 행렬 곱셈이 아닌 테이블 참조이므로, 연산량(FLOPs)에 기여하지 않는다는 설계 철학을 반영합니다. 마치 도서관에서 책을 꺼내는 행위는 계산이 아니라 검색이므로, 연산 비용에 포함하지 않는 것과 같습니다.
코드 예제
def estimate_flops(self):
"""학습 FLOPs 추정 (Value Embedding은 제외)"""
nparams = sum(p.numel() for p in self.parameters())
# non-scaling 파라미터: 임베딩 조회는 FLOPs 없음
nparams_exclude = (
self.transformer.wte.weight.numel() + # 토큰 임베딩
value_embeds_numel + # 값 임베딩 (약 100M)
self.resid_lambdas.numel() + # 잔차 스케일
self.x0_lambdas.numel() # 입력 스킵 스케일
)
# 스케일링 파라미터에 대해서만 FLOPs 계산
scaling_params = nparams - nparams_exclude
return 6 * scaling_params + attn_flops
# MFU(Model FLOPs Utilization) 계산에 사용됨
# MFU = 실제 소요 시간 기반 FLOPs / 추정 FLOPs
설명
대규모 모델에서 FLOPs(FLoating-point OPerations) 추정은 학습 효율을 평가하는 핵심 지표입니다. **MFU(Model FLOPs Utilization)**는 GPU가 이론적 최대 연산량 대비 실제로 얼마나 효율적으로 계산하고 있는지를 나타냅니다. Value Embedding을 FLOPs에서 어떻게 처리하는지가 이 지표의 정확도에 직접적인 영향을 미칩니다. 그렇다면 임베딩이 왜 FLOPs에서 제외될까요? 쉽게 비유하자면, 이것은 마치 도서관에서 책을 읽는 것과 책장에서 책을 꺼내는 것의 차이와 같습니다. 책을 읽고 이해하는 것(행렬 곱셈)은 적극적인 "계산"입니다. 반면 책장에서 책을 꺼내는 것(임베딩 조회)은 단순한 "접근"입니다. 책을 꺼내는 데는 시간이 들지만, 복잡한 사고(연산)가 필요하지 않습니다. FLOPs 추정이 정확하지 않던 시절에는 모든 파라미터를 동일하게 취급했습니다. 6 * total_params라는 단순한 공식을 사용하여, 임베딩 포함 전체 파라미터에 대해 FLOPs를 추정했습니다. 하지만 이것은 MFU를 과소평가하는 결과를 초래했습니다. 실제로는 행렬 곱셈이 일어나지 않는 파라미터까지 연산량에 포함했기 때문입니다. 바로 이런 부정확성을 해결하기 위해 non-scaling 파라미터 분류가 도입되었습니다. AutoResearch는 파라미터를 두 그룹으로 나눕니다. 스케일링 파라미터(행렬 곱셈이 일어나는 파라미터)와 non-scaling 파라미터(단순 조회나 스칼라 연산만 일어나는 파라미터)입니다. c_q, c_k, c_v, MLP 가중치 등은 스케일링 파라미터이며, wte, value_embeds, lm_head 등은 non-scaling 파라미터입니다. FLOPs 추정에서는 스케일링 파라미터만 고려하므로, MFU 계산이 더 정확해집니다. 위의 코드를 한 줄씩 살펴보겠습니다. 먼저 nparams = sum(p.numel() for p in self.parameters())로 전체 파라미터 수를 계산합니다. 이것은 모델의 총 크기를 나타냅니다. 다음으로 nparams_exclude에서 non-scaling 파라미터를 모읍니다. wte.weight.numel()은 토큰 임베딩, value_embeds_numel은 모든 Value Embedding 테이블의 파라미터 합계입니다. 기본 설정에서 이것만으로도 약 1억 개 이상입니다. resid_lambdas와 x0_lambdas는 각 레이어의 스칼라 파라미터로, 무시할 수 있을 정도로 작습니다. 6 * scaling_params에서 학습 시 FLOPs를 추정합니다. 숫자 6은 순방향 2회 + 역방향 4회(역전파는 순방향의 약 2배)를 의미합니다. + attn_flops에서 어텐션의 QK^T 연산을 추가로 반영합니다. 실제 현업에서는 어떻게 활용할까요? 정확한 FLOPs 추정은 하드웨어 활용도를 평가하는 데 필수적입니다. MFU가 40%라면 GPU의 40%만 활용하고 있다는 뜻입니다. Flash Attention 3를 도입하면 MFU가 크게 향상됩니다. 하지만 Value Embedding을 FLOPs에 잘못 포함하면, MFU가 실제보다 낮게 측정됩니다. "하드웨어 최적화가 안 된 것처럼 보이지만, 실제로는 측정 방법이 틀린 것"입니다. AutoResearch의 에이전트는 정확한 MFU를 바탕으로 실험의 효율을 평가합니다. 하지만 주의할 점도 있습니다. 초보 개발자들이 흔히 하는 실수 중 하나는 non-scaling 파라미터의 메모리 비용을 간과하는 것입니다. FLOPs에서 제외된다고 해서 "비용이 없는" 것이 아닙니다. Value Embedding은 약 100M 파라미터를 GPU 메모리에 상주시켜야 하며, bf16이어도 약 200MB의 메모리를 소비합니다. GPU 메모리가 부족한 환경에서는 Value Embedding의 크기를 줄이거나 일부 레이어에서 제외해야 할 수 있습니다. 따라서 FLOPs와 메모리 사용량을 별도로 관리하는 것이 중요합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다. "계산이 아닌 접근은 FLOPs에서 제외하는 것이 정확한 측정의 기본이군요." 김개발 씨가 오늘 학습을 마무리하며 노트를 덮었습니다. 파라미터 카운팅의 정확도는 하드웨어 효율 평가의 기반이 됩니다. 여러분도 오늘 배운 내용을 실제 프로젝트에 적용해 보세요.
실전 팁
- Value Embedding은 FLOPs에 포함되지 않지만, GPU 메모리는 차지하므로 메모리 사용량은 별도로 관리해야 합니다
- MFU = 실제 FLOPs / 추정 FLOPs로, 하드웨어 활용도의 핵심 지표입니다
- non-scaling 파라미터 분류는 wte, lm_head, value_embeds, resid_lambdas, x0_lambdas를 포함합니다
- 에이전트가 정확한 MFU를 바탕으로 실험 효율을 평가하고 최적의 아키텍처를 자율적으로 탐색합니다
- 다음 카드뉴스에서는 MuonAdamW 하이브리드 옵티마이저를 다룹니다
- 이 카드뉴스는 "AutoResearch 완전 분석 - AI 자율 연구 에이전트" 코스의 4/8편입니다
댓글 (0)
함께 보면 좋은 카드 뉴스
Flash Attention 3과 Rotary Embeddings 완벽 분석
AutoResearch 프로젝트의 train.py에 구현된 Flash Attention 3 커널 선택 로직, Rotary Position Embeddings(RoPE)의 수학적 원리와 구현, 그리고 Sliding Window Attention 패턴을 심도 있게 분석합니다.
GPT 모델 아키텍처 완벽 분석 - CausalSelfAttention부터 GPT까지
AutoResearch의 train.py에 구현된 GPT 모델 아키텍처를 상세 분석합니다. GPTConfig 데이터클래스부터 CausalSelfAttention, MLP, Block, GPT 클래스까지 전체 구조와 가중치 초기화 전략을 다룹니다.
AutoResearch 개요 AI 자율 연구 에이전트 완벽 가이드
Andrej Karpathy의 AutoResearch 프로젝트를 완벽히 분석합니다. AI 에이전트가 자율적으로 LLM 학습 실험을 수행하는 시스템의 전체 아키텍처와 설계 철학을 다룹니다.
파일 입출력에서 with문 사용하기 완벽 가이드
Python에서 파일을 안전하게 다루는 with문의 모든 것을 알아봅니다. 파일 자동 닫기부터 예외 처리까지, 실무에서 반드시 알아야 할 핵심 개념을 이북처럼 술술 읽히는 스타일로 정리했습니다.
프로덕션 에이전트 구축 완벽 가이드
실무에서 바로 활용할 수 있는 프로덕션급 AI 에이전트 구축 방법을 처음부터 끝까지 다룹니다. 아키텍처 설계부터 배포, 모니터링까지 실전 경험을 담은 완벽 가이드입니다.