1975년산 8비트 6502 프로세서에서 구동하는 자회귀 언어모델
현대의 머신러닝 기술을 1975년형 8비트 6502 프로세서(RAM 32KB)에 탑재하여 자회귀 언어모델을 구동시킨 흥미로운 프로젝트입니다. 가중치 양자화에 CPU 연산에 유리한 BitNet 구조를 적용하고, 코드를 극한으로 최적화하여 구형 하드웨어에서도 AI 모델의 텍스트 생성이 가능함을 증명했습니다. 제한된 하드웨어 환경에서 모델을 경량화하고 최적화하는 실용적인 접근법을 보여줍니다.
요약: 저는 Mamba 기반의 아주 작은 자회귀 언어 모델을 학습시키고, 이것을 8비트 6502 프로세서(1975년 출시, RAM 32KB)에서 실행하기 위한 추론 엔진을 작성했습니다. 아버지의 BBC Micro 컴퓨터에서 이 모델을 실행시켜 다음과 같은 텍스트를 생성했습니다.
once upon a time tom and lily saw things lily were sad her house he heartd them ilily and tom said yes she saw a little girl smiled tom was so excited her mom said yes
MOS 6502는 1975년에 출시된 8비트 마이크로프로세서로, BBC Micro와 Apple II 등에 탑재되었습니다. 저는 80년대식 아버지의 BBC Model B 컴퓨터를 사용할 수 있게 되었습니다. 현대의 머신러닝 기술을 이용하여 이 기계에 탑재할 수 있는 가장 강력한 언어 모델이 어떤 것일지 확인해 보고 싶었습니다.
놀랍지 않게도 이 작업에는 상당한 도전 과제가 따랐습니다. 모델 가중치와 추론 코드는 25KB의 사용자 메모리 공간 내에 포함되어야 했습니다. 최종 설정에서는 추론 코드에 9KB, 모델 가중치에 13KB를 할당했습니다. 이 CPU는 8비트 정수 데이터 유형으로만 작동하며, 명령어 집합에 곱셈 연산이 포함되어 있지 않습니다.
추론 코드에는 CC65를 사용하여 C 언어 코드를 6502 명령어 집합으로 컴파일했습니다. 맥북에서 학습시킨 모델 바이너리는 PlayUEF와 직접 제작한 3.5mm-테이프 변환 케이블을 사용하여 BBC Micro로 전송했습니다. 노트북이 이어폰 단자를 통해 오디오를 재생하면, BBC Micro는 마치 테이프 드라이브를 읽고 있다고 인식하게 됩니다.
sim65 에뮬레이터를 통해 C 언어 추론 바이너리와 기준이 되는 파이썬 모델 구현 간의 패리티(동등성) 검사를 수행했습니다. 또한 전체 추론 엔진은 BBC Micro에서 실제로 구동하기 전에 jsbeeb 에뮬레이터에서 먼저 테스트할 수 있습니다.
여러분도 직접 실행해 볼 수 있습니다. 아래 링크를 클릭하면 웹 브라우저에서 BBC Micro를 부팅하고, GitHub에서 UEF 테이프 이미지를 곧바로 불러온 뒤 모델을 실행할 명령어를 자동으로 입력합니다. 에뮬레이터를 설치할 필요가 없습니다 (단, 텍스트 생성에는 몇 분 정도 소요됨).
BBC Micro에서 BitNet 구동하기 →
모델링
이 프로젝트의 목표는 최첨단 언어 모델들과 유사하게 토큰을 하나씩 생성해 내는 자회귀 언어 모델을 구축하는 것입니다. 이 모델은 기존 문맥으로부터 다음 토큰을 생성해내는 함수 $f$입니다: $$ f: \text{'the cat sat on the ma'} \mapsto \text{'t'} $$
대규모 언어 모델에서 '토큰'은 일반적으로 단어나 하위 단어 단위를 의미합니다. 하지만 이 글에서 사용한 어휘 집합(토큰 목록)은 26개의 알파벳과 공백(' ') 문자입니다. 이처럼 소규모 모델에서는 더 큰 어휘 집합(예: 단어 또는 하위 단어)을 사용할 경우, 어휘 인코더/디코더 계층이 파라미터 예산을 너무 많이 소모하게 됩니다.
임베딩 계층은 토큰을 모델의 은닉 차원(우리의 경우 56차원)으로 매핑합니다. 토큰의 공간적 임베딩이 어떻게 작동하는지 이해하려면 3Blue1Brown의 신경망 관련 영상이 큰 도움이 됩니다. $$ g: {\text{a}, \text{b}, ... , \text{z}, '\text{ }'} \to \mathbb R^{56} $$
토큰이 고차원 공간에 매핑되면, 토큰 간의 순환적 종속성을 모델링하기 위해 혼합 계층이 사용됩니다 (순환 계층 참조).
BitNet
BitNet은 CPU에서 빠른 추론을 가능하게 하는 방법으로 소개되었습니다. 행렬 곱셈 $Y = XW$은 $X$의 행과 $W$의 열 간의 내점(Dot product) 연산 집합입니다: $$Y_{ij} = \sum_k X_{ik} W_{kj}$$
BitNet은 가중치 $W$를 양자화하여 그 값이 삼진법 집합인 ${-1, 0, 1}$에 속하도록 만듭니다. 이렇게 하면 내적 연산이 단순한 덧셈과 뺄셈 연산의 시퀀스로 줄어듭니다: $$ \begin{align*} Y_{ij} &= X_{i1} W_{1j} + X_{i2} W_{2j} + \cdots + X_{in} W_{nj} \ &= X_{i1} - X_{i2} + ... - X_{in}\ \end{align*} $$
6502 프로세서의 명령어 집어에는 곱셈이 없습니다. 대신, 곱셈은 비트 시프트 및 덧셈 연산을 반복하여 수행됩니다. 단일 8x8 곱셈 및 누적 연산은 약 150 클록 사이클을 소모합니다. 반면 삼진법 누적 연산은 30 클록 사이클만을 소모하므로, 양자화된 가중치를 사용할 때 추론 속도를 훨씬 더 빠르게 만들어 줍니다.
int8이 8비트, float32가 32비트를 사용하는 것과 비교하여, BitNet 파라미터 각각은 $\log_2(3) = 1.58$ 비트의 저장 공간만 차지합니다. 우리는 1바이트당 4~5개의 파라미터를 압축하여 패킹할 수 있습니다. 1바이트당 5개의 파라미터를 넣는 것이 데이터 효율성 면에서는 더 좋지만, 이를 삼진법 값으로 언패킹하려면 반복적인 정수 나눗셈(floor-div) 연산이 필요하게 됩니다.