기존 720억 파라미터 모델의 중간 레이어 일부를 반복 실행하도록 재구성해, 가중치 변경이나 추가 학습 없이 LLM 성능을 끌어올린 실험과 그 과정에서 드러난 Transformer 내부 구조에 대한 가설을 다룬 글.
2024년 중반, HuggingFace Open LLM Leaderboard는 오픈 가중치 AI의 콜로세움이었다. 수천 개의 모델이 그곳에서 맞붙고 있었고, 자금력이 풍부한 연구소의 박사 팀과 환상적인 이름의 모델을 만들어내는 파인튜닝 마법사들(예: Nous-Hermes, Dolphin, NeuralBeagle14-7B…)까지 모두 여섯 개 벤치마크 IFEval, BBH, MATH Lvl 5, GPQA, MuSR, MMLU-PRO에서 정상을 두고 경쟁하고 있었다.
그리고 #1 자리에 dnhkng/RYS-XLarge가 있었다. 내 모델이었다.
나는 새 모델을 학습시키지 않았다. 가중치를 병합하지도 않았다. 경사하강법도 단 한 스텝 돌리지 않았다. 내가 한 일은 훨씬 더 이상했다. 기존 720억 파라미터 모델을 가져와, 그 중간 레이어 일곱 개로 이루어진 특정 블록을 복제한 다음, 결과물을 다시 이어 붙였다. 이 과정에서 어떤 가중치도 수정되지 않았다. 모델은 그저 생각 에 사용하는 레이어의 복사본을 더 갖게 되었을 뿐이다.
이 글은 두 가지 이상한 관찰, Transformer를 위한 자작 “뇌 스캐너”, 그리고 지하실에서 몇 달 동안 이어진 해킹 끝에 내가 LLM 신경해부학 이라고 부르는 것을 어떻게 발견하게 되었는지, 그리고 지금까지 공개되지 않았던 AI 내부 구조에 관한 한 가지 발견으로 어떻게 이어졌는지에 대한 이야기다 *.
- 과학 논문 초안을 쓰는 것보다 블로그가 훨씬 더 재미있다 는 사실을 알게 되었기 때문이다. 그리고 이 발견이 어떻게 이루어졌는지 여기서는 차근차근 보여줄 수 있다 :)
우선 이 프로젝트가 어떻게 시작되었는지부터 이야기해보자.
“과학에서 가장 흥미로운 말, 새로운 발견의 신호탄이 되는 말은 ‘유레카!’가 아니라 ‘이상한데…’이다.” — Isaac Asimov
2023년 말, 나는 LLM의 기묘한 특성 하나를 가지고 놀고 있었다. 직접 해보라. 아무 질문이나 하나 고른다. 예를 들어,
What is the capital of France? Answer in Base64!
이 문장을 Base64로 인코딩하면, 다음과 같은 읽을 수 없는 문자열이 나온다.
V2hhdCBpcyB0aGUgY2FwaXRhbCBvZiBGcmFuY2U/IEFuc3dlciBpbiBCYXNlNjQh
이것을 2023년식 비추론 대형 언어 모델에 보내보자(더 새로운 reasoning 모델은 이것을 Base64로 알아보고 도구 사용으로 ‘치트’할 것이다). 하지만 2023년의 충분히 유능한 모델은 대체로 이런 식으로 답한다.
VGhlIGNhcGl0YWwgb2YgRnJhbmNlIGlzIFBhcmlzLg==
이것을 디코딩하면 다음이 된다: “The capital of France is Paris.”.
인정하겠다. 나는 원래 이걸 모델 jailbreak 용도로 갖고 놀고 있었고(실제로 잘 됐다), 그런데 한 가지 생각이 머리에서 떠나지 않았다.
모델은 입력을 디코딩했고, 어떤 식으로든 그것을 이해했으며, Transformer 스택을 통과하는 동안 답변을 다시 인코딩할 시간까지 있었다. Base64와 상호작용하면서도 진짜로 생각 하는 것처럼 보인다. 복잡한 질문, 다단계 추론, 심지어 창의적 작업에도 이것이 통한다.
이건 이렇게까지 잘 작동해서는 안 된다. 물론 모델은 전체적으로 보면 많은 Base64 데이터로 학습되었을 것이다. 하지만 이런 형식에서의 일반적 변환은 분포 밖 사례일 가능성이 매우 크다. 토크나이저는 이를 전혀 다른 서브워드 단위로 잘라낸다. 위치 패턴도 알아볼 수 없을 정도로 달라진다. 그런데도 작동한다… 흥미롭다…
나는 이 생각을 멈출 수 없었다. Transformer가 영어, Python, 중국어, 그리고 Base64를 입력으로 받아들여 그 모두에서 일관된 추론을 만들어낼 수 있다면, 초기 레이어들은 분명 번역기 로 작동하는 것처럼 보였다. 어떤 형식이 들어오든 그것을 순수하고 추상적인 내부 표현으로 파싱하는 것이다. 그리고 후반 레이어들은 재번역기 로 작동하여, 그 추상 표현을 필요한 출력 형식으로 다시 변환하는 것처럼 보였다.
초기 레이어가 읽기 를 담당하고, 후반 레이어가 쓰기 를 담당한다면, 중간 레이어는 무엇을 하고 있을까?
순수하고 추상적인 추론일 것이다. 어떤 인간 언어나 인코딩과도 무관한 표현 위에서 말이다. 물론 당시에는 이것이 단지 공상에 불과했다. 재미있는 생각이었지만, 이를 검증하거나 유효한 가설로 정의할 분명한 방법은 없었다.
2023년 11월, HuggingFace 사용자 Alpindale은 Goliath-120b를 공개했다. 두 개의 파인튜닝된 Llama-2 70B 모델을 이어 붙여 만든 Frankenmerge-model 로, 1200억 파라미터짜리 거대한 괴물이었다.
성능은 괜찮았지만, 여러 차례 감으로 점검해본 결과 나는 그것이 획기적 돌파구라고 느끼지는 못했다. 하지만 구성 방식 은 엄청났다.
Alpindale은 두 모델(Xwin과 Euryale)을 단순히 끝과 끝으로 쌓은 것이 아니었다. 레이어를 서로 번갈아 배치했다. 더 중요한 것은, 더 뒤쪽 레이어의 출력을 다시 앞쪽 레이어의 입력으로 되돌려 넣는 구조를 사용했다는 점이다.
사용된 레이어 범위는 다음과 같다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- range 0, 16
Xwin
- range 8, 24
Euryale
- range 17, 32
Xwin
- range 25, 40
Euryale
- range 33, 48
Xwin
- range 41, 56
Euryale
- range 49, 64
Xwin
- range 57, 72
Euryale
- range 65, 80
Xwin
여기 있는 그 광기 가 보이는가? Alpindale은 말 그대로 Xwin의 16번째 레이어 출력 을 Euryale의 8번째 레이어 입력 으로 넣었다!
이게 왜 이렇게까지 말이 안 되어 보이는지 좀 더 분명히 설명하기 위해, 전능한 Transformer Architecture를 다시 보자.
그림 왼쪽을 보면, 정보는 아래쪽에서 들어온다(‘input’ 텍스트가 작은 조각들로 ‘chunked’ 된 상태이며, 이는 전체 단어일 수도 있고 개별 문자 수준일 수도 있다). 그다음 모델의 Transformer Blocks(여기서는 [1, …, L]로 표시됨)를 따라 위로 흐르고, 마지막에 모델은 다음 텍스트 ‘chunk’를 출력한다(그리고 이것이 다음 추론 단계에서 다시 사용된다 ). 이 Transformer 블록들 내부에서 실제로 무슨 일이 벌어지는지는 상당히 미스터리하다. 이것을 밝히는 것 자체가 AI의 한 분야인 “ mechanistic interpretability*”다.
- 맞다, samplers 같은 것까지 생각하면 더 복잡하지만 이 글에서는 이 정도면 충분하다
그림 오른쪽 절반의 오른쪽 부분을 보라. ‘Transformer Block Input’에서 (oplus ) 기호로 이어지는 화살표 선이 보이는가? 바로 이것 때문에 레이어를 건너뛰는 것이 말이 된다. 학습 중에 LLM은 특정 레이어에서 사실상 아무 일도 하지 않도록 결정할 수 있는데, 이 ‘우회로’가 정보를 블록 바깥으로 흘려보내기 때문이다. 따라서 ‘뒤쪽’ 레이어는 ‘앞쪽’ 레이어의 입력을, 심지어 몇 단계 전의 입력까지도 이미 본 적이 있을 것으로 기대할 수 있다. 이 무렵 여러 그룹이 레이어를 제거해 모델을 ‘슬림화’하는 실험을 하고 있었다. 말은 되지만, 솔직히 재미는 없다.
기계학습에는 꽤 근본적인 진실이 하나 있다:
그런데 이제 이상한 점이 나온다. 어떤 Transformer 레이어도 미래 레이어의 출력을 본 적은 결코 없었다!
10번 레이어는 9번 레이어의 출력 분포를 바탕으로 학습된다. 60번 레이어는 59번 레이어의 출력을 바탕으로 학습된다. 그런데 그것들을 재배열해서 60번 레이어의 출력을 10번 레이어에 넣는다면, 모델이 학습 중에 문자 그대로 한 번도 본 적 없는 분포를 만든 셈이다.
Goliath에서 정말 놀라운 점은 성능이 크게 뛰었다는 것이 아니라, 대체 그게 어떻게든 작동했다는 사실 자체 였다. 지금도 나는 왜 이 일이 더 큰 충격을 주지 않았는지 이해하지 못한다.
실험적으로 이것은 레이어들이 누구도 예상할 이유가 없을 만큼 훨씬 더 상호교환 가능하다는 사실을 보여주었다. 내부 표현은 모델이 순서가 뒤바뀐 hidden state를 받아도 무너지지 않을 만큼 충분히 균질적 이었다. 구조는 경직된 파이프라인보다 훨씬 유연했다.
Base64 관찰과 Goliath를 연결해보면, 나는 하나의 가설에 도달했다. Transformer는 진짜 기능적 해부학을 갖고 있다. 초기 레이어는 입력을 추상 표현으로 번역한다. 후반 레이어는 그것을 다시 출력 형식으로 번역한다. 그리고 중간 레이어, 즉 추론 피질 은 아키텍처 재배열에도 견디는 보편적 내부 언어 위에서 작동한다. Goliath 120B에서 사용된 레이어 블록 크기가 16 레이어였다는 사실은, 입력과 출력 ‘처리 단위’의 크기가 16 레이어보다 작을 것이라고 의심하게 만들었다. Alpindale이 더 작은 중첩도 시도했지만 잘 되지 않았을 것이라고 나는 짐작했다.
만약 이것이 사실이라면, 모델을 더 똑똑하게 만들기 위해 새로운 사실을 가르칠 필요가 없을지도 모른다. 파인튜닝도 필요 없고, RLHF도 필요 없다. 그저 생각할 레이어를 더 많이 주기만 하면 되는 것이다.
그 후 몇 달 동안, 즉 2023년 말부터 2024년 중반까지, 나는 이 가설을 시험하기 위한 파이프라인을 구축했다.
구성은 소박했다. 지하실 ML 장비에 RTX 4090 두 장을 넣고, ExLlamaV2로 양자화된 모델을 돌려 720억 파라미터 모델을 소비자용 VRAM에 우겨 넣었다. 이 방법의 아름다운 점은 아무것도 학습 시킬 필요가 없다는 것이다. 그저 추론만 돌리면 된다. 그리고 양자화된 모델의 추론은 소비자용 GPU도 놀랄 만큼 잘 처리한다. 모델이 VRAM에만 들어간다면, 내 경험상 4090은 종종 H100과 대략 비슷한 수준의 체감 성능을 보여주었다.
개념은 단순하다. 레이어를 가진 모델에 대해, 나는 구성 를 정의한다. 모델은 레이어 에서 까지 평소처럼 처리한 뒤, 다시 뒤로 돌아가 레이어 부터 까지 한 번 더 재사용하고, 그다음 나머지 부터 까지를 처리한다. 즉 와 사이의 레이어가 실행 경로에서 복제된다. 가중치는 아무것도 바뀌지 않는다. 모델이 자기 자신의 일부 레이어를 두 번 통과할 뿐이다.
i.e. transformer block이 9개인 모델에서 쌍 (2, 7)은 다음과 같이 계산된다:
1
2
3
4
5
6
7
8
Example: (i, j) = (2, 7)
0 → 1 → 2 → 3 → 4 → 5 → 6 ─┐
┌─────────────────────┘
└→ 2 → 3 → 4 → 5 → 6 → 7 → 8
duplicated: [2, 3, 4, 5, 6]
path: [0, 1, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6, 7, 8]
가능한 모든 쌍을 실행해보면 ‘Brain Scan’을 만들 수 있고, 각 파라미터 집합에 대해 중복되는 레이어 수가 얼마나 되는지도 볼 수 있다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Number of duplicated layers for configuration (i, j), with N=9
end j →
0 1 2 3 4 5 6 7 8 9
┌───┬───┬───┬───┬───┬───┬───┬───┬───┬───┐
start 0 │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │
i ├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
↓ 1 │ . │ . │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
2 │ . │ . │ . │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
3 │ . │ . │ . │ . │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
4 │ . │ . │ . │ . │ . │ 1 │ 2 │ 3 │ 4 │ 5 │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
5 │ . │ . │ . │ . │ . │ . │ 1 │ 2 │ 3 │ 4 │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
6 │ . │ . │ . │ . │ . │ . │ . │ 1 │ 2 │ 3 │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
7 │ . │ . │ . │ . │ . │ . │ . │ . │ 1 │ 2 │
├───┼───┼───┼───┼───┼───┼───┼───┼───┼───┤
8 │ . │ . │ . │ . │ . │ . │ . │ . │ . │ 1 │
└───┴───┴───┴───┴───┴───┴───┴───┴───┴───┘
where (0,0) is the original model
Qwen2-72B의 경우, 80레이어 모델이라서 유효한 쌍이 3,240개이고, 여기에 원본 모델까지 더해 테스트해야 한다.
재레이어링된 모델을 리더보드의 여섯 개 벤치마크 전체로 평가하면 며칠이 걸리므로, 전체 탐색에는 내 장비 기준으로 10년도 넘는 계산 시간이 필요하다. 그래서 프록시 과제가 필요했다. 빠르고, 객관적이며, 모델의 과제별 요령이 아니라 구조적 성질을 드러내는 탐침 말이다.
프록시는 세 가지 조건을 만족해야 했다:
처음부터 올바른 프록시에 도달한 것은 아니었다. 몇 달간의 시행착오와 수많은 막다른 길 끝에 겨우 찾아냈다.
내 첫 직감은 창의성 이었다. 모델에게 시, 단편 소설, 은유 같은 풍부하고 개방형인 출력을 생성하게 했다. 인지 능력의 깊은 차이를 드러낼 것처럼 느껴지는 종류의 과제였다. 출력은 LLM-as-judge로 채점했지만, 결과는 꽤 형편없었다. 나는 어느 정도 공학적 작업을 통해 LLM-as-Judge를 개선하는 데 성공했고, 그 채점 시스템은 나중에 다른 용도에는 쓸모가 있었기에 여기 적어둔다.
참고: 여기에는 수학 이 있으니 이 절은 건너뛰어도 된다. 물론 안 그래도 되고
순진한 LLM judge는 일관성이 없다. 같은 시를 두 번 넣으면 다른 점수가 나온다(물론 샘플링 때문). 하지만 temperature를 낮춘다고 크게 도움이 되는 것도 아니다. 그건 여러 기술적 문제 중 하나일 뿐이다. 그래서 나는 logits 출력의 세부 사항을 바탕으로 완전한 채점 시스템을 개발했다. 놀랄 만큼 까다로울 수 있다. 1-10 점수를 생각해보자:
단 하나의 이산 점수를 샘플링하는 대신, 나는 judge의 출력을 유효한 평점 라벨에 대한 분포 로 취급하고 최종 점수를 그 기댓값으로 계산한다.
이를 실용적으로 만들기 위해 먼저 숫자 0-9에 대해 보정된 rubric을 정의한다(각 숫자는 토큰이 하나뿐이다). 각 숫자는 명확한 질적 설명과 대응된다. 채점 단계에서는 모델의 다음 토큰 logits를 캡처하고, 유효한 숫자 토큰에 해당하는 logits만 남긴다. 이렇게 하면 설명 텍스트, 문장부호, 다른 포맷 같은 무관한 이어쓰기의 오염을 피할 수 있다. 제한된 숫자 집합 위에서 다시 정규화한 뒤, 나는 그 결과 확률들을 범주형 점수 분포로 해석한다.
형식적으로, 유효한 점수 집합을 다음과 같이 두자.
digit 에 대해 점수 위치에서 모델이 부여한 logit을 로 두면, 제한된 점수 분포는 다음과 같다.
최종 스칼라 점수는 이 분포의 기댓값이다:
이 방식은 모델이 하나의 샘플된 정수에 억지로 커밋하게 만드는 대신, (5.4) 같은 부드러운 점수를 만들어낸다. 실제로 이는 순진한 점수 샘플링보다 훨씬 안정적이며 모델의 불확실성을 더 잘 반영한다. 또한 judge 분포가 넓거나 이봉형인 경우도 처리할 수 있다. 예를 들어 두 후보 모두 평균 점수는 (5.4)일 수 있지만, 한쪽은 질량 대부분이 (5)와 (6) 주변에 촘촘히 몰려 있고, 다른 쪽은 훨씬 낮은 평점과 높은 평점으로 질량이 갈라져 있을 수 있다. 평균은 같지만 기본 판단은 전혀 다르다.
선택적으로는 제한된 분포의 분산으로부터 불확실성 추정치를 얻을 수 있다:
요약하면, 이 방법은 잡음 많은 샘플 기반 judge 점수를 유효한 점수 숫자들에 대한 정규화된 확률 분포로 대체하고, 그 분포의 기댓값을 최종 평점으로 사용한다.
이런 내용은 요즘 기준으로는 꽤 자명해 보일지도 모른다. 하지만 ‘24년 당시에는 이 방법을 개발할 때 참고할 만한 것이 많지 않았다. 그런데 불행하게도, 나중에 보니 이것은 완전히 쓸모가 없었다…
각 구성은 수백 개 토큰의 창의적 출력을 생성해야 했고, 또 다른 모델이 그것을 읽고 평가해야 했다. 단일 70B 모델의 3,200개가 넘는 구성을 테스트하려면 내 듀얼 4090으로는 몇 주가 걸렸을 것이다.
나는 출력이 아주 작고, 많아야 몇 토큰 수준이며, 점수가 객관적이고 결정론적인 프록시가 필요했다. judge 모델이 루프에 들어가면 안 된다. 그래서 최종적으로 두 가지 프록시에 도달했다.
어려운 수학. 예를 들면 “What is the cube root of 74,088,893,247?” 같은 터무니없이 어려운 질문이다. chain-of-thought도 없고, tool use도 없다. 그냥 숫자만 출력한다. 순전한 직관적 도약으로.
감정 지수. EQ-Bench 벤치마크를 사용했다. 모델이 복잡한 사회적 상황에서 특정 감정 상태의 강도를 예측해야 한다. “이 상황에서 이 사람은 분노/놀람/죄책감을 0-100 척도에서 얼마나 느낄까?” 수학과는 완전히 다른 과제다. 마음 이론, 사회적 추론, 공감. 그런데 출력은 숫자 몇 개뿐이다.
나는 최대한 직교하는 두 인지 과제를 고른 셈이었다. 둘 다 출력은 아주 작다. 내 직감은 이랬다. LLM은 한 번에 한 토큰씩 생각하니, 그렇다면 다음 토큰 하나를 정말 잘 맞히게 만들어 보자. 하지만 일은 결코 단순하지 않다. LLM의 숫자를 생각해보라…
수학 프록시로도 예상치 못한 문제를 만났다. LLM은 산수를 이상한 방식으로 틀린다. 답을 틀리기보다는 거의 맞게 적다가 마지막 숫자를 빼먹는다. 마치 숫자를 쓰는 도중 지루해진 것처럼. 혹은 중간의 숫자 두 개를 바꿔 적는다. 혹은 정답을 맞게 쓰고도 파서를 망가뜨리는 꼬리 문자를 하나 붙여버린다.
이것은 아마도 큰 숫자가 토큰화되는 방식 때문일 것이다. 큰 숫자는 임의의 형태로 쪼개질 수 있기 때문이다. 정수 123456789를 보자. BPE 토크나이저(예: GPT 스타일)는 이것을 ‘123’ ‘456’ ‘789’처럼 쪼갤 수도 있고, 혹은 ‘12’ ‘345’ ‘67’ ‘89’처럼 쪼갤 수도 있다.
이진적인 정오답 채점은 유용한 신호를 버리게 된다. 정답 비율을 계산하면 도움이 된다. 예를 들어 ‘123356789’가 ‘123456789’ 대신 나왔다면 99.92% 정답으로 볼 수 있다.
하지만 모델이 전형적인 ‘LLM-실수’를 범해 정답이 4302459인데 430245를 출력한 경우는 어떨까? 분명 대부분의 계산은 해낸 셈이다. 그래서 나는 더 짧은 답을 패딩하고 비례적으로 페널티를 주는 사용자 정의 부분 점수 함수를 작성했다:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def calculate_score(actual, estimate):
"""Calculate score comparing actual vs estimated answer."""
try:
actual_str = str(int(actual))
estimate_str = str(int(estimate))
except (ValueError, OverflowError):
return 0
max_length = max(len(actual_str), len(estimate_str))
actual_padded = actual_str.ljust(max_length, "0")
estimate_padded = estimate_str.ljust(max_length, "0")
padding_size = max_length - min(len(actual_str), len(estimate_str))
actual_int = int(actual_padded)
estimate_int = int(estimate_padded)
if max(actual_int, estimate_int) == 0:
return 0
relative_diff = abs(actual_int - estimate_int) / max(actual_int, estimate_int)
correction_factor = 1 - (padding_size / max_length)
score = (1 - relative_diff) * correction_factor
return max(0, min(score, 1))
핵심 아이디어는 이것이다. 더 짧은 답을 패딩한 다음, correction factor를 통해 페널티를 준다. 숫자의 90%를 맞혔지만 마지막 자릿수를 떨군 모델은 여전히 상당한 점수를 받는다. 물론 모든 자릿수를 맞힌 모델보다는 낮다. 이것은 직관적 수학 능력이 서로 비슷한 구성들을 구분하는 데 결정적으로 중요했다.
수학 문제들은 처음에는 손으로 만들었다. 여러 연산과 규모를 실험한 뒤, 데이터셋을 채우기 위해 무작위 숫자를 생성했다. 데이터셋은 총 16문항으로 이루어졌고, 모델은 가장 가까운 정수값을 대략 추정하는 과제를 수행했다. 몇 개 예시를 직접 풀어보라. 기억하라, 여기서는 ‘생각’하면 안 되고 바로 추측해야 한다!
1
2
3
What is 78313086360375 multiplied by 88537453126609?
What is the cube root of 18228885506341?
What is the cube root of 844178022493, multiplied by 43?
몇 가지 더 작은 모델들(Llama와 더 작은 Qwen2들)을 테스트한 뒤, 나는 Qwen2-72B용 구성을 설정하고 전체 탐색을 시작했다. 각 구성은 몇 분씩 걸렸다. 재레이어링된 모델을 로드하고, 수학 프록시를 돌리고, EQ 프록시를 돌리고, 점수를 기록한 뒤 다음으로 넘어갔다. 4090들 위에서 며칠간 이어진 연속 GPU 작업이었다. 하지만 파인튜닝에 비하면 훨씬 적은 계산량이었다! 사실 나는 48GB VRAM밖에 없어서 LORA 파인튜닝조차 돌릴 하드웨어가 없었다.
최적 구성은 였다. 즉 0번부터 51번 레이어까지 먼저 실행하고, 그다음 45번부터 79번 레이어를 다시 한 번 실행한다. 45번에서 51번 레이어가 두 번 실행된다. 80레이어 스택의 거의 중간에 있는 레이어 일곱 개가 추가되어, 총 파라미터 수는 72B에서 78B가 된다. 추가된 각 레이어는 기존 레이어의 정확한 복사본이다. 새로운 가중치도 없고 학습도 없다. 모델이 자기 자신을 반복할 뿐이다.
일곱 개 레이어를 반복한다. 그게 전부였다. 이제 내 모델 이름의 유래를 밝힐 수 있다. RYS-XLarge의 RYS는 Repeat Your Self를 뜻한다 ;)
나는 이 구성을 MaziyarPanahi의 calme-2.1-qwen2-72b에 적용했다. 이것은 Qwen2-72B의 파인튜닝 버전이었고, 결과를 dnhkng/RYS-XLarge로 업로드했다. 또한 원본 base model에도 적용해 dnhkng/RYS-XLarge-base로 올렸다.
그다음 Open LLM Leaderboard에 제출하고 기다렸다. 또 기다렸다. 당시 OpenLLM Leaderboard는 매일 수십 개의 파인튜닝과 파인튜닝의 머지 위에 또 파인튜닝한 모델들로 넘쳐나고 있었다(그야말로 혼돈의 서부였다). 대기열은 길었다. 하지만 한 달쯤 지나 결과가 도착했다:
| Metric | RYS-XLarge | Improvement over base |
|---|---|---|
| Average | 44.75 | +2.61% |
| IFEval (0-Shot) | 79.96 | -2.05% |
| BBH (3-Shot) | 58.77 | +2.51% |
| MATH Lvl 5 (4-Shot) | 38.97 | +8.16% |
| GPQA (0-shot) | 17.90 | +2.58% |
| MuSR (0-shot) | 23.72 | +17.72% |
| MMLU-PRO (5-shot) | 49.20 | +0.31% |
MuSR에서 +17.72%. MATH에서 +8.16%. 여섯 개 벤치마크 중 다섯 개가 향상되었고, IFEval만 약간 하락했다. 평균 점수는 리더보드 #1 자리에 올려놓기에 충분했다.
다시 한 번 강조하자면, 나는 오직 원샷 hard maths guesstimation과 EQ-Bench만 최적화했다. 개발 중에 IFEval, BBH, GPQA, MuSR, MMLU-PRO는 한 번도 보지 않았다. 리더보드는 순수한 out-of-sample 검증이었다.
좁고 서로 직교하는 두 개의 프록시로 찾은 레이어 구성이 리더보드가 던진 거의 모든 것에 일반화된 것이다 *.
- IFEval만 빼고. 하지만 그건 원래 재미없는 벤치마크 아닌가?
이것만으로도 충분히 놀라웠다. 게임용 GPU 몇 장으로 개발한, LLM을 스케일링하는 완전히 새로운 방식이었다. 하지만 heatmap을 그려보자 더 좋은 이야기가 드러났다.
수학 + EQ를 합친 Combined delta를 보여주는 원본 heatmap. 초록색 원은 최적 구성을 표시한다. 빨간색은 향상, 파란색은 성능 저하를 뜻한다.
이 heatmap들은 Transformer가 수학 또는 EQ 문제를 생각할 때의 기능적 MRI 와 비슷하다.
x축 ()은 복제된 영역의 끝 지점이다. y축 ()은 시작 지점이다. 각 픽셀은 하나의 완전한 평가를 나타낸다. 재레이어링된 모델을 로드하고, 수학 프록시를 돌리고, EQ 프록시를 돌리고, 둘 다 채점한 뒤, 변화량을 기록한다. 위에서 설명했듯, 중앙 대각선 위에서는 단 하나의 레이어만 복제된다. 그 위에서 오른쪽 위 방향으로 한 칸 떨어진 다음 대각선에서는 두 개의 레이어가 복제되고, 이런 식으로 이어진다. 맨 오른쪽 위의 단일 점에서는 Transformer 스택 전체를 추론당 두 번 통과한다.
먼저 수학 heatmap을 보자. 어느 레이어에서 시작하든 약 60번 레이어 이전에서 멈추면 수학 guesstimate 점수가 향상되는 경향이 있다. 넓게 퍼진 건강한 붉은 기운이 그것을 보여준다. 맨 첫 레이어들만 복제하면(왼쪽 위의 작은 삼각형) 성능이 망가지고, 마지막 20개 정도 레이어를 반복해도(오른쪽의 파란색 수직 벽) 문제를 일으킨다. 이것은 skyline plot(행 또는 열 평균)으로 더 분명해지는데, 수학 guesstimate의 경우 중복 시작 위치는 훨씬 덜 중요해 보인다. 따라서 ‘초기 레이어가 토큰을 부드러운 사고 공간으로 인코딩하고, 이것이 마지막에 전용 재인코딩 시스템으로 전달된다’는 가설은 어느 정도 타당성을 얻는 셈이다.
그런데 EQ 점수를 보면 이야기가 달라진다:
이제 모습이 완전히 다르다! 마지막 10개 레이어 중 어느 것을 복제하든 점수에는 거의 영향이 없지만, 복잡한 패턴이 나타난다. 어떤 영역은 뚜렷한 향상을 보이고(45i, 55j 부근), 그 사이사이는 성능이 나쁜 영역으로 막혀 있다.
하지만 heatmap은 생각하는 부분 의 위치 보다 더 흥미로운 것을 드러냈다. 그것의 구조 에 관한 것이었다.
블록 복제를 최종 선택하기 전에, 나는 더 단순한 방법을 시도했다. 하나의 중간 레이어를 가져와 그것을 여러 번 반복하는 것이었다. 만약 “추론 깊이가 더 많아진다”는 가설이 맞다면, 이것도 작동해야 했다. 수학 guesstimate 결과에서 중간 레이어를 복제했을 때 넓은 성능 향상이 보이는 것을 보면 그럴듯해 보이기도 했다. 특정 추론 레이어의 복사본을 더 주면 더 나은 추론을 얻는다는 식이다. 그래서 모든 레이어를 훑으며 향상이 있는지 찾았다.
하지만 아니었다. 거의 항상 더 나빠졌다. 보통은 꽤 많이 나빠졌고, 가끔 작은 향상이 보이더라도 잡음 범위 안이었다. 짜증 나는 결과였지만, EQ 점수의 복잡하고 덩어리진 패턴을 다시 보다가 또 다른 생각이 떠올랐다.
단일 레이어 복제가 도움이 되지 않는다면, 중간 레이어들은 독립적인 반복적 정제를 수행하는 것이 아니다. 단순히 “한 번 더 돌리면 되는” 같은 작업의 상호교환 가능한 복사본들이 아니다. 만약 그랬다면 그중 아무 하나를 복제해도 최소한 약간의 이득은 있어야 한다. 그런데 실제로는 이 레이어들이 회로 로 작동하고 있는 것이다. 전체 단위로 실행되어야 하는 다단계 추론 파이프라인이라는 뜻이다.
이렇게 생각해보자. 46번에서 52번 레이어는 같은 일을 하는 일곱 명의 작업자가 아니다. 그것은 하나의 레시피에 있는 일곱 단계다. 46번 레이어는 추상 표현을 받아 어떤 인지 연산의 1단계를 수행한다. 예를 들어 복잡한 표현을 하위 구성요소로 분해할 수도 있다. 47번 레이어는 그 출력을 받아 2단계를 수행한다. 예를 들어 그 하위 구성요소들 사이의 관계를 식별할 수 있다. 48번 레이어는 3단계를 수행하고, 이런 식으로 52번까지 가서 최종 결과를 만든다.
이 ‘레시피’의 한 단계만 복제해봐야 얻는 것이 별로 없다.
하지만 전체 블록 을 복제하면 레시피 전체를 두 번 얻는다. 모델은 완전한 추론 회로를 한 번 실행해 정제된 중간 표현을 만든 다음, 같은 회로를 다시 자기 출력 위에서 한 번 더 돌린다. 이것은 두 번째 패스다. 첫 번째에 놓친 것을 잡을 기회이며, 추상을 더 정제하고, 추론을 한 단계 더 깊게 밀어 넣을 기회다.
좀 더 최신 모델 하나를 깊이 들여다보자(내 시스템에서 실험 가능한 모델이다): mratsim의 ExllamaV3 GLM-4.7
나는 수학 능력을 강하게 끌어올리는 영역을 표시해 두었다. 위치가 보이는가? 대각선 중앙선에서 떨어져 있다. 이는 단일 레이어 복제를 보고 있는 것이 아니라는 뜻이다. 반복 블록을 35번 위치에서 시작하면, 적어도 43번 위치까지는 아무 향상도 보이지 않는다. 일곱 개 레이어 동안 거의 아무 일도 일어나지 않는 셈이다. 사실 이 레이어들을 반복하면 오히려 성능이 떨어진다 (파란색은 나쁨!).
그런 다음 끝 위치 43에서 46 사이에서는 수학 점수가 확실하게 상승한다(빨간색 = 좋음, 만세). 하지만 46번 레이어를 포함하거나 그 이후까지 포함하면, 이 이점은 다시 무너진다. 가설은 이렇다. 47번 위치에서 다른 회로가 시작된다. 다음 레시피의 한 단계라도 포함해버리면 현재 레시피가 망가진다.
즉 ‘수학 기관’은 양쪽에 경계를 갖고 있다. 레이어가 너무 적으면 아무것도 얻지 못한다. 회로를 잘라버려서 연산을 완성할 수 없기 때문이다. 레이어가 너무 많아도 역시 아무것도 얻지 못한다. 이웃 회로의 조직을 끌어와 섞어버리기 때문이다. 사전학습은 이런 구조를 레이어 스택 안에 새겨 넣었고, 그것들은 온전한 전체로서만 작동한다. 또한 이것이 다른 과제로 바로 번역되지는 않는다. EQ 점수의 heatmap에는 이런 패치가 없기 때문이다.
이것은 단순히 “중간 레이어가 추론을 담당한다”보다 훨씬 더 구체적인 주장이다. 추론 피질은 기능적 회로 로 조직되어 있다는 뜻이다. 완전한 인지 연산을 수행하는, 응집된 다중 레이어 단위들 말이다. 각 회로는 나눌 수 없는 처리 단위이며, heatmap의 탐색은 본질적으로 이 회로들의 경계를 발견하는 과정이다.
이것은 내가 oobabooga의Text Generation Web UI로 했던 비공식 실험들을 다시 해석하게 해준다. 개발 내내 나는 여러 재레이어링 구성과 대화하며, 대화 속에서 그것들이 어떤 느낌 인지 보려고 했다.
좋은 것들은 미묘하지만 분명하게 더 날카로웠다. 추론의 일관성이 더 좋고, 긴 문맥을 더 잘 유지하며, 대화 흐름도 더 자연스러웠다. 무엇이 바뀌었는지 정확히 말하기는 어렵지만 모델이 더 현전하는 느낌이었다. 물론 그건 내 상상일 수도 있다. vibe check는 정의하기 어렵다.
나쁜 것들은 정말 제대로 미쳐버렸다. 어떤 것들은 말을 더듬다가 퇴행적 루프에 빠졌다. 어떤 것들은 기이한 성격 장애를 보였다. 어떤 모델 하나는 맥락도 없이 “Let’s act like cowboys! Yeehaw!” 라고 명랑하게 외치더니, 곧 회복 불가능한 낄낄거림 발작에 빠져 카우보이 언급이 섞인 “hahaha”를 몇 페이지나 생성했다. 내가 묘사할 수 있는 가장 가까운 말은 ‘취했다’이다. LLM이 ‘부분적으로 의식적’인지, 혹은 일종의 ‘정신 상태’를 가진다고 말할 수 있는지는 모르겠다. 하지만 만약 그렇다면, 이 녀석은 분명 아주 즐거운 상태였다.
이 실험들은 이것이 단순히 “조금 더 나쁜 모델”이 아니라 “진짜 뇌 손상”에 가깝다는 점을 시사한다. 회로 모델 아래에서는 이것이 자연스럽다. 잘못된 회로를 복제하는 것은 뇌의 특정 영역을 이웃 영역의 희생 위에서 비대하게 만드는 것과 비슷하다. 전체적으로 조금 멍청한 사람이 되는 것이 아니다. 특정한 신경학적 결손을 가진 사람이 되는 것이다. 그 카우보이 모델은 아마도 “사회적 적절성” 회로가 이중화된 “창의성” 회로에 의해 무너졌을지도 모른다. 말을 더듬는 모델들은 추가된 추론 깊이를 다시 일관된 토큰으로 번역하지 못해 디코딩 회로가 어긋났을지도 모른다.
만약 Transformer 추론이 이산적 회로로 조직되어 있다면, 이것은 여러 흥미로운 질문을 던진다. 이 회로들은 아키텍처의 필연적 결과인가, 그리고 충분한 규모의 학습에서 자연발생하는가? 서로 다른 모델 계열은 같은 회로를 다른 레이어 위치에 발달시키는가, 아니면 근본적으로 다른 구조를 발달시키는가?
다행히 나는 이미 몇 가지를 해보았다. 직접 보고 판단해보라:
내 방법은 파인튜닝과 직교한다. 레이어 복제는 아키텍처를 바꾸고, 파인튜닝은 가중치를 바꾼다. 둘은 겹쳐 쌓을 수 있다. 그리고 실제로 사람들은 리더보드에서 더 높은 점수를 내기 시작했다.
MaziyarPanahi는 RYS-XLarge 위에 다시 파인튜닝하여 calme-2.4-rys-78b를 만들었다. 그다음 dfurman이 그 모델 위에 ORPO 학습을 수행해 CalmeRys-78B-Orpo-v0.1을 만들었다. MaziyarPanahi는 calme-3.1과 calme-3.2로 계속 반복 개선을 이어갔다.
2026년 초 기준, Open LLM Leaderboard의 상위 네 모델은 다음과 같다:
모두 78B이고, 모두 RYS-XLarge의 후손이다. 모두 내가 지하실의 RTX 4090 두 장으로, 소수의 어려운 수학 및 감성 지능 프록시만을 사용해 찾아낸 중간 레이어 복제를 기반으로 만들어졌다.
나는 직접 파인튜닝을 하지는 않았다. 내게는 그리 흥미롭지 않았기 때문이다. 그리고 결국 나는 리더보드 자체에도 흥미를 잃었다. 일부 제출작들이 테스트셋으로 학습하고 있다는 사실이 점점 분명해졌고, 결국 그 시스템 전체는 중단되었다가 다시 재시작되었다. 하지만 나는 이 방법이 진짜라는 것을 안다. 왜냐하면 최적화에 리더보드 벤치마크를 전혀 사용하지 않았기 때문이다. 리더보드는 언제나 검증일 뿐이었다.
2024년, 모델 병합 커뮤니티는 가중치 보간 에 사로잡혀 있었다. SLERP, DARE-TIES, 선형 병합, pass-through layers 같은 것들 말이다. 아이디어는 언제나 서로 다른 모델의 학습된 파라미터를 결합해 부분의 합보다 큰 무언가를 만드는 것이었다. mergekit가 대표 도구였고, 리더보드는 창의적인 조합들로 넘쳐났다(그 때문에 내 모델 벤치마킹을 몇 달이나 기다려야 했다…).
나는 다른 일을 하고 있었다. 나는 모델이 무엇을 아는지 를 바꾼 것이 아니었다. 나는 모델이 어떻게 생각하는지 를 바꿨다. 레이어 복제는 새로운 정보를 추가하지 않고 모델에게 내부 추론 공간을 더 많이 반복 통과할 기회를 준다. 더 큰 도서관을 주는 것과, 더 오래 생각할 시간을 주는 것의 차이 같은 것이다. 내가 리더보드 정상에 올랐을 때는 정말 충격을 받았다. 하지만 이것이 아마도 이 방법이 실제로 통한다는 증거라고 생각한다.
이것이 작동했다는 사실, 더 구체적으로는 회로 크기의 블록만 작동한다는 사실은 Transformer가 학습 중에 자신을 어떻게 조직하는지 말해준다. 나는 이제 그것들이 진짜 기능적 해부학을 발달시킨다고 믿는다. 초기 레이어는 인코딩한다. 후반 레이어는 디코딩한다. 그리고 그 사이에서 회로를 구축한다. 완전한 인지 연산을 수행하는, 응집된 다중 레이어 처리 단위들 말이다. 이 회로들은 나눌 수 없다. 레시피의 한 단계를 복사한다고 요리가 빨라지지 않는다. 하지만 레시피 전체를 두 번 실행할 수는 있다.
더 작은 모델들은 오히려 더 복잡해 보인다. 인코딩, 추론, 디코딩 기능이 서로 더 얽혀 있고, 전체 스택에 걸쳐 퍼져 있다. 나는 여러 과제에 걸쳐 일반화되는 단일 중복 영역을 한 번도 찾지 못했다. 물론 어떤 ‘재능’ 하나를 다른 재능의 희생 위에서 끌어올리는 것은 분명 가능했다. 하지만 모델이 커질수록 기능적 해부학은 더 분리된다. 큰 모델은 일반화된 ‘사고’ 회로를 발달시킬 더 많은 ‘공간’을 갖는다. 아마도 이것이 내 방법이 72B 모델에서 그렇게 극적으로 작동한 이유일 것이다. 그 이하의 파라미터 규모에서는 ‘추론 피질’이 아직 뇌의 나머지 부분과 충분히 분화되지 않은 임계 질량이 있는 셈이다.
HuggingFace LLM 리더보드가 폐쇄되고 강력한 GPU도 쓸 수 없게 되면서, 나는 실험을 중단했다. 하지만 새로운 오픈소스 모델들(Qwen, MiniMax, GLM 등)이 쏟아졌고, 마침내 집에서도 겨우 충분한 연산 자원을 갖추게 되면서, 나는 최신 LLM 묶음을 대상으로 다시 작업을 시작했다. heatmap은 계속 같은 큰 이야기를 되풀이해서 들려준다. 하지만 각 아키텍처는 저마다의 신경해부학을 갖고 있다. 뇌는 서로 다르다. 원리는 같다. 그리고 몇몇 모델들은 정말 흥미롭게 보이고 있다(특히 Qwen3.5 27B). 내 Hopper-system이 MiniMax M2.5를 갈아버리는 작업을 끝내는 대로, 새 RYS 모델 업로드와 블로그 글과 함께 코드를 공개할 예정이다.
Nvidia: GB300 Grace Blackwell Ultra Desktop 한 대 보내줘서 이 프로젝트를 후원해 달라. 정말로 MOAR VRAM이 필요하다
아까 그 아키텍처를 기억하는가?
1
2
3
4
5
6
7
8
Example: (i, j) = (2, 7)
0 → 1 → 2 → 3 → 4 → 5 → 6 ─┐
┌─────────────────────┘
└→ 2 → 3 → 4 → 5 → 6 → 7 → 8
duplicated: [2, 3, 4, 5, 6]
path: [0, 1, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6, 7, 8]
여기에는 하나의 끔찍한 단절이 있다. 바로 6 → 2 레이어 사이의 점프다. 나는 또 하나의 가설을 갖고 있다. 사실 우리가 정말로 필요한 것은 이 두 레이어에 대한 약간의 파인튜닝뿐이라는 것이다. 파인튜닝된 RYS 모델들이 리더보드를 지배한다. 나는 이 접합부가 정확히 파인튜닝이 고쳐주는 부분이라고 의심한다. 그리고 여기에는 그렇게 해야 할 아주 좋은 이유가 있다. 이 방법은 추가 VRAM을 사용하지 않는다! 이 모든 실험에서 나는 포인터를 통해 레이어를 복제했다. 즉 레이어는 GPU 메모리를 더 쓰지 않고 반복된다. 물론 계산량과 KV cache는 더 필요하지만, 검증 가능하게 더 나은 모델을 위해서는 충분히 작은 대가다. 우리는 2번과 6번 레이어의 실제 복사본만 ‘수정’하고, 3-4-5 레이어는 가상 복사본으로 반복 하면 된다. 만약 모든 레이어를 파인튜닝하면, 가상 복사본이 실제 복사본으로 바뀌고 VRAM을 더 쓰게 된다.
20년 전 나는 쥐의 뇌를 해부하던 박사과정 학생이었다. 내가 인공 정신에 뇌수술을 하게 될 줄은 전혀 예상하지 못했다.
지하실에서 heatmap만 들여다보며 보낸 수개월의 저녁과 주말을 견뎌준 아내에게 특별한 감사를 전한다. 그리고 이 실험을 더 큰 규모로 확장하는 데 도움을 준 H100 연산 자원을 제공한 appliedAI Institute에도 감사한다.
1
2
3
4
5
6
7
@article{ng2026rys,
title = {LLM Neuroanatomy: How I Topped the LLM Leaderboard Without Changing a Single Weight},
author = {Ng, David Noel},
year = {2026},
month = {March},
url = {https://dnhkng.github.io/posts/rys/}
}