6502 CPU를 Qwen3.5 가중치에 통째로 컴파일한 nesformer
NES 에뮬레이터를 만든 사람이 아니라면 이 소식이 처음엔 농담처럼 들릴 수 있다. nesformer는 닌텐도 엔터테인먼트 시스템의 심장인 6502 CPU(2A03 칩의 코어), 2KB 작업 RAM, 카트리지의 프로그램 ROM을 하나의 표준 Qwen3_5ForCausalLM 체크포인트 안에서 돌린다. 게임을 실행하면 모델은 CPU의 실행 로그를 토큰 하나씩 예측하고, 화면 그리기와 패드 입력, vblank 인터럽트 발생 같은 나머지 콘솔 주변부는 작은 호스트 프로그램이 맡는다.
핵심은 이 가중치를 아무도 학습시키지 않았다는 점이다. 컴파일러가 모든 값을 계산해 넣었다. 가중치 폴더는 평범한 허깅페이스 형식이라 Qwen3_5ForCausalLM.from_pretrained로 별도 커스텀 코드 없이 로드된다. 구조는 Qwen3.5-4B의 Gated DeltaNet 헤드 형태(키 헤드 16개, 밸류 헤드 32개, 차원 128)와 MLP 폭 9,216을 그대로 쓰되, 회로 역할을 하는 Gated DeltaNet 레이어 7개와 허깅페이스 캐시가 요구하는 전부 0인 어텐션 레이어 1개로 구성된다. bf16 기준 파라미터 4억 6,660만 개, 933MB에 달하지만 실제로 0이 아닌 값은 930만 개뿐이라 압축 파일은 3.3MB에 그친다.
메모리 모델이 독특하다. DeltaNet의 각 헤드는 매 토큰마다 델타 규칙으로 갱신되는 128×128 상태 행렬을 유지하는데, 게이트를 열어둔 채 원-핫 키로 갱신하면 행 하나가 정확히 덮어써지고 원-핫 쿼리로 그대로 읽어낼 수 있다. 즉 행 하나가 메모리 셀이 되고, 카트리지와 RAM, 레지스터가 모두 이 행들에 자리 잡는다. 32KB PRG-ROM은 명령어 페치용과 데이터 읽기용으로 두 벌 저장된다.
모델 안에는 6502 코어가 통째로 들어간다. 공식 opcode 151개 전부와 각 주소 지정 모드, 플래그, 스택, BRK와 NMI 진입까지 포함된다. 반면 PPU의 스캔라인 렌더러와 팔레트, 컨트롤러, APU 레지스터 쓰기, vblank 시점의 NMI 주입, OAM DMA 읽기, I/O 읽기 응답은 호스트 몫이다. 호스트는 추적 로그를 통해서 말고는 CPU 레지스터나 RAM을 들여다보지 않는다.
프로그램 카운터도 직접 저장하지 않는다. 마지막 토큰이 명령어 길이를 실어 나르고, 길이별 명령 개수를 세는 DeltaNet 카운터 3개가 마지막 점프 이후 누적치를 기록해 점프 대상에 더하는 방식으로 PC를 복원한다. 대부분 명령은 토큰 하나로 끝나고, 실제 게임 코드는 명령당 평균 1.1~1.3토큰을 쓴다.
정확도 검증은 두 갈래로 이뤄졌다. 참조 CPU 구현이 nestest의 골든 로그 3,348개 명령과 일치했고, 게임 코드 570만 명령 구간에서는 py65 에뮬레이터와 대조했다. 번들 테스트 게임의 경우 모든 프레임이 일반 6502 에뮬레이터 출력과 정확히 같다. 회로를 Qwenputer의 평가기에서 bf16 정밀도 검사와 함께 돌렸을 때도 참조 토큰을 모두 맞혔다.
실행 방법은 단순하다. nesformer.zip과 nesformer-weights.zip을 같은 폴더에 풀어 가중치를 패키지 옆에 두고 numpy, scipy, numba, pillow를 설치한 뒤 CLI로 ROM을 넘기면 된다. run은 GIF를 만들고, check는 참조 CPU와 나란히 돌려 첫 차이에서 멈추며, trace는 토큰 스트림을 찍는다. 버튼 입력은 "start@60-65", "right@200-600" 같은 프레임 구간 문법으로 지정한다. 첫 실행은 numba 엔진 컴파일에 약 20초가 걸리고, 이후 프레임당 1~6초가 소요된다.
이 프로젝트는 Divyansh Agrawal의 Qwenputer 위에 서 있다. Qwenputer가 CHIP-8과 RISC-V CPU를 Qwen3.5-4B에 컴파일했고, nesformer는 그 컴파일러와 "Gated DeltaNet 상태가 곧 기계의 메모리"라는 트릭을 재사용해 6502로 확장했다. CHIP-8에서 RISC-V로, 다시 실제 상용 콘솔 CPU로 옮겨간 셈이다.
개발자 입장에서 이 사례의 값어치는 에뮬레이션 자체보다 구조에 있다. 트랜스포머의 상태 행렬을 주소 지정 가능한 저장소로 쓰고, 연산 자체를 가중치에 굽는 방식은 모델 추론을 "학습된 근사"가 아닌 "결정적 회로"로 다루는 관점을 보여준다. 특히 0이 아닌 가중치가 전체의 2% 수준이라는 점은 희소성이 실제로 동작하는 계산을 담을 수 있음을 시사한다.
한계도 분명하다. 속도는 실용과 거리가 멀어 NES 기준 10초 분량인 600프레임을 돌리는 데 상당한 시간이 필요하다. 실행 가능한 ROM은 iNES 매퍼 0(NROM) 보드에 한정되며, 다른 보드 게임은 제한 사항 안내와 함께 멈춘다. 슈퍼 마리오브라더스 같은 ROM은 사용자가 직접 준비해야 하고 저장소에는 자유 배포 가능한 3개만 포함된다. 화면 출력은 모델이 아니라 호스트가 그리므로, 영상까지 모델이 생성하는 것은 아니다.