주요 콘텐츠로 건너뛰기
AI 튜토리얼

nesformer로 컴파일된 Qwen3.5 모델 안에서 NES 게임 실행하기

이 튜토리얼에서는 nesformer가 NES 6502 CPU, 메모리, 카트리지 ROM을 표준 Qwen3.5 체크포인트로 컴파일하는 방법을 설명합니다. CLI를 설치하고 호환되는 게임을 실행하며, 컨트롤러 입력을 자동화하고, 애니메이션을 내보내고, 참조 CPU와 실행 결과를 비교하고, 다른 엔진을 살펴봅니다.

nesformer로 컴파일된 Qwen3.5 모델 안에서 NES 게임 실행하기

nesformer란?

nesformer는 표준 Qwen3_5ForCausalLM 체크포인트의 가중치와 상태 안에서 콘솔의 6502 CPU, 2KB 작업 RAM, 카트리지 프로그램 ROM이 작동하는 실험적인 NES 런타임입니다. 가중치를 학습한 것은 아닙니다. 대신 컴파일러가 CPU 회로를 구현하도록 가중치를 계산했습니다.

게임이 실행되는 동안 모델은 CPU 실행 로그를 한 번에 토큰 하나씩 예측합니다. Python 호스트 프로그램은 나머지 콘솔 구성 요소를 제공합니다. 그래픽을 렌더링하고, 컨트롤러 입력을 읽으며, 타이밍을 처리하고, I/O 읽기에 응답하고, 수직 블랭킹 NMI 인터럽트를 주입합니다.

이 프로젝트는 Qwenputer를 기반으로 하며 해당 프로젝트의 컴파일러와 중앙 메모리 기법을 재사용합니다. 각 Gated DeltaNet 헤드는 상태 행렬을 유지합니다. 원-핫 키와 쿼리를 사용하면 행렬의 행이 메모리 셀처럼 동작하여 레지스터, RAM, 카트리지 데이터를 저장할 수 있습니다.

주요 기능

  • 학습이 아닌 컴파일 방식: 0이 아닌 모든 가중치는 회로 사양에서 생성됩니다.
  • 표준 체크포인트 형식: 체크포인트는 일반적인 Hugging Face 폴더이며 사용자 지정 모델 코드 없이 Qwen3_5ForCausalLM.from_pretrained로 불러올 수 있습니다.
  • 공식 6502 명령어 세트 전체 지원: 회로는 공식 opcode 151개, 주소 지정 모드, 플래그, 스택 연산, BRK, NMI 진입을 모두 구현합니다.
  • 재사용 가능한 NROM 지원: 시작할 때 선택한 카트리지를 모델 상태에 기록하므로 동일한 가중치로 서로 다른 iNES mapper 0 ROM을 실행할 수 있습니다.
  • 스크립트 방식 컨트롤: 명령줄에서 프레임별 버튼 입력을 예약할 수 있습니다.
  • 시각적 출력: 실행 결과를 호환되는 터미널에 표시하고 GIF 또는 PNG 프레임으로 저장할 수 있습니다.
  • 실행 검증: check 명령은 모델 출력을 함께 제공되는 참조 CPU와 비교하고 첫 번째 차이에서 중단합니다.
  • 다중 실행 엔진: nesformer는 빠른 numba 엔진, 일반 NumPy 엔진, 기본 Transformers 엔진을 제공합니다.

모델 내부에서 실행되는 내용 이해하기

체크포인트에는 6502 코어, CPU 레지스터와 플래그, 2KB 작업 RAM, 고정된 32KB 프로그램 ROM 테이블이 포함됩니다. 또한 프로그램 페치, 데이터 읽기, 스택 연산, OAM DMA 읽기도 처리합니다.

호스트는 PPU 렌더러, 팔레트, 컨트롤러, 수직 블랭킹 NMI 타이밍, 사이클 카운팅, APU 레지스터 처리를 계속 담당합니다. 사운드는 구현되지 않았습니다. 호스트는 I/O 읽기와 NMI 이벤트에 대한 값을 토큰 스트림에 주입하지만, 생성된 트레이스 외부에서 모델의 레지스터나 RAM을 직접 검사하지는 않습니다.

nesformer는 모델 추론을 통해 CPU를 에뮬레이션하지만, 가중치 내부에 NES 전체가 구현된 완전한 NES 에뮬레이터는 아닙니다.

요구 사항 및 폴더 설정

Python 3.11 이상, nesformer 소스 아카이브, 별도의 가중치 아카이브가 필요합니다. 두 아카이브를 동일한 최상위 폴더에 압축 해제하세요. weights 디렉터리는 Python 패키지 내부가 아니라 패키지와 나란히 있어야 합니다.

nesformer/
├── nesformer/
├── chipformer/
├── roms/
├── examples/
├── tests/
├── pyproject.toml
└── weights/
    ├── config.json
    ├── generation_config.json
    ├── model.safetensors
    ├── nesformer.json
    └── SHA256SUMS

위에 표시된 최상위 디렉터리에서 모든 명령을 실행하세요. 기본적으로 CLI는 nesformer/ 패키지 옆의 weights/를 찾습니다.

다운로드한 가중치 확인

포함된 체크섬 파일을 사용하여 가중치가 올바르게 압축 해제되었는지 확인하세요.

shasum -a 256 -c weights/SHA256SUMS

체크포인트를 다른 위치에 보관하는 경우 run, check, trace 사용 시 --weights /path/to/weights로 위치를 전달하세요. info 명령은 체크포인트 경로를 직접 받습니다.

nesformer 설치

가상 환경을 만들고 활성화한 다음, 필요한 수치 계산 및 이미지 처리 패키지를 설치하세요.

python3 -m venv .venv
source .venv/bin/activate
pip install numpy scipy numba pillow

Windows에서는 다음 명령으로 환경을 활성화하세요.

.venv\Scripts\activate

기본 fast 엔진은 numba를 사용합니다. 처음 실행할 때 엔진 컴파일에 약 20초가 걸립니다. 이후 실행에서는 해당 컴파일 결과를 재사용합니다.

첫 게임 실행

저장소에는 자유 라이선스로 제공되는 테스트 ROM이 포함되어 있습니다. 다음 명령은 Thwaite를 600프레임 동안 실행하고, 터미널에 프레임을 표시하며, Start와 A 버튼 입력을 스크립트로 제공합니다.

python -m nesformer run roms/thwaite.nes --frames 600 --show \
  --buttons "start@90-95+200-205,a@230-233+250-253+270-273+290-293+310-313+330-333+350-353+370-373+390-393+410-413+430-433+450-453+470-473+490-493+510-513+530-533+550-553+570-573+590-593"

--show 옵션은 iTerm2 또는 Windows Terminal처럼 24비트 색상을 지원하는 터미널에 생성된 각 프레임을 표시합니다. 실행이 끝나면 CLI는 ROM 이름을 딴 애니메이션 GIF를 저장합니다. 예를 들어 thwaite.gif와 같습니다.

실제 NES보다 성능이 훨씬 느립니다. 초기 컴파일이 끝난 후에도 프레임 하나를 처리하는 데 일반적으로 1~6초가 걸립니다. 600프레임 실행은 NES 시간으로 10초에 해당하며 약 25분이 걸릴 수 있습니다.

호환되는 ROM 직접 실행

합법적으로 취득한 ROM을 roms/ 디렉터리에 넣거나 다른 경로를 지정하세요. ROM은 iNES mapper 0으로 식별되는 NROM 보드를 사용해야 합니다. 호환되는 예로는 Super Mario Bros., Donkey Kong, Excitebike가 있지만, 이러한 상용 ROM은 포함되어 있지 않습니다.

이 예제는 Super Mario Bros.를 시작하고, 오른쪽을 누른 상태로 유지하며, 여러 번의 점프를 예약합니다:

python -m nesformer run roms/smb.nes --frames 600 --show \
  --buttons "start@60-65,right@200-600,a@250-265+300-315+360-375+420-435+480-495+540-555"

시작 시 ROM이 모델의 DeltaNet 상태에 로드됩니다. 호환되는 게임마다 체크포인트를 다시 빌드할 필요는 없습니다.

스크립트로 컨트롤러 입력 제어

--buttons 옵션은 쉼표로 구분된 항목을 받습니다. 각 항목에는 하나 이상의 버튼 이름, @ 기호, 하나 이상의 프레임 범위가 포함됩니다.

start@60-65
right@200-600
a@250-265+300-315
right+b@200-600
  • start@60-65는 60프레임부터 65프레임까지 Start를 누릅니다.
  • right@200-600은 지정된 구간 동안 오른쪽을 누른 상태로 유지합니다.
  • a@250-265+300-315는 A 버튼 입력을 두 번 예약합니다.
  • right+b@200-600은 오른쪽과 B를 동시에 누른 상태로 유지합니다.

사용 가능한 이름은 a, b, select, start, up, down, left, right입니다. 프레임은 전원이 켜진 시점부터 시작하며 초당 60프레임으로 진행됩니다. 따라서 프레임 600은 NES 시간으로 10초에 해당합니다.

이동할 수 없는 화면에서는 게임이 입력을 무시하므로, 일반적으로 입력을 약간 일찍 시작해도 안전합니다. 버튼을 누르는 시간은 중요할 수 있습니다. 예를 들어 Super Mario Bros.에서는 A 버튼을 더 오래 누르면 더 높이 점프합니다.

프레임 출력 제어

기본적으로 run은 120프레임을 실행하고 ROM 이름을 기반으로 한 GIF를 저장합니다. 출력 옵션으로 결과를 사용자 지정할 수 있습니다:

python -m nesformer run roms/thwaite.nes \
  --frames 300 \
  --every 2 \
  --scale 3 \
  --gif output/thwaite.gif
  • --frames N은 N프레임 후 중지합니다.
  • --gif PATH는 애니메이션 GIF 저장 경로를 지정합니다.
  • --png DIR은 보존된 프레임을 개별 PNG 파일로 저장합니다.
  • --every N은 N번째 프레임마다 보존합니다.
  • --scale N은 저장되는 프레임을 정수 배율로 확대합니다.
  • --show는 생성된 프레임을 터미널에 표시합니다.

--png만 사용하면 GIF 출력이 비활성화됩니다. PNG 프레임과 애니메이션을 모두 원한다면 명시적인 --gif 옵션을 추가하세요.

python -m nesformer run roms/thwaite.nes \
  --frames 120 \
  --png frames \
  --gif thwaite.gif

실행 검사 및 확인

참조 CPU와 비교

check 명령은 체크포인트와 프로젝트의 일반 참조 6502를 나란히 실행합니다. 첫 번째 토큰 차이에서 중지합니다:

python -m nesformer check roms/thwaite.nes --tokens 50000

기본 비교 길이는 50,000토큰입니다. 다른 실행 명령에서 사용할 수 있는 --buttons, --weights, --engine, --no-skip 옵션도 동일하게 전달할 수 있습니다.

모델의 토큰 스트림 출력

trace를 사용하면 모델이 생성한 CPU 레코드를 검사할 수 있습니다:

python -m nesformer trace roms/thwaite.nes --tokens 100

기본 추적 길이는 40토큰입니다. 대부분의 CPU 명령은 하나의 토큰을 사용하지만, I/O, 포인터 읽기, 스택 연산 또는 DMA가 포함된 명령은 여러 토큰 레코드를 생성할 수 있습니다.

실행 중 토큰 ID 저장

run 명령은 나중에 분석할 수 있도록 생성된 토큰 ID를 파일에 기록할 수 있습니다:

python -m nesformer run roms/thwaite.nes \
  --frames 60 \
  --trace trace.txt

--max-tokens N을 사용하면 프레임 제한에만 의존하지 않고, 생성된 토큰 수가 고정된 값에 도달했을 때 실행을 중지할 수도 있습니다.

실행 엔진 선택

nesformer는 성능과 종속성 구성이 서로 다른 세 가지 엔진을 제공합니다:

  • fast: 기본 numba 구현입니다. README에 따르면 개발 머신의 CPU 코어 하나에서 초당 약 1,650토큰을 처리합니다.
  • numpy: 더 단순한 NumPy 구현으로, 초당 약 210토큰을 처리하는 것으로 보고되었습니다.
  • hf: 기본 Hugging Face Transformers로, 초당 약 10토큰을 처리하는 것으로 보고되었습니다.

--engine으로 엔진을 선택합니다:

python -m nesformer run roms/thwaite.nes --frames 3 --engine numpy

기본 Transformers 사용

Hugging Face 엔진을 선택하기 전에 추가 종속성을 설치하세요:

pip install "torch>=2.6" "transformers>=5.22"
python -m nesformer run roms/thwaite.nes --frames 3 --engine hf

체크포인트에는 사용자 지정 모델 구현이 필요하지 않습니다. 내부적으로 nesformer는 체크포인트를 로드하고, 패딩 토큰 하나를 디코딩하며, 초기 머신 상태를 캐시의 DeltaNet 상태에 기록한 다음, 생성된 토큰을 한 번에 하나씩 다시 입력합니다.

고급 팁

저장소 외부에 가중치 보관

체크포인트가 다른 위치에 저장되어 있다면 --weights를 사용하세요:

python -m nesformer run roms/thwaite.nes \
  --weights /path/to/weights \
  --frames 120

체크포인트 메타데이터 검사

정보 명령으로 체크포인트를 설명할 수 있습니다:

python -m nesformer info

가중치가 기본 디렉터리에 없다면 info에 해당 경로를 지정하세요.

체크포인트 다시 빌드

저장소에는 회로 체크포인트를 다시 생성하는 데 필요한 컴파일러가 포함되어 있습니다. 일반적인 빌드는 weights/에 결과를 저장하며 README에 따르면 약 10초가 걸립니다:

python -m nesformer build

다음과 같이 다른 출력 디렉터리를 선택할 수 있습니다:

python -m nesformer build --out compiled-weights

유휴 루프 건너뛰기 이해

NES 게임은 긴밀한 루프에서 수직 귀선 기간(vblank)을 기다리는 경우가 많습니다. 호스트가 RAM 또는 I/O 쓰기 없이 정확히 반복되는 루프를 감지하면, 클록을 다음 PPU 이벤트 직전까지 빠르게 진행한 후 남은 반복을 실행합니다. 이 최적화는 표시되는 프레임과 I/O 타이밍을 유지하면서 토큰 생성을 크게 줄입니다.

유휴 루프의 모든 반복을 실행하려면 --no-skip을 전달하세요. 속도는 느려지지만 전체 토큰 시퀀스를 살펴볼 때 유용할 수 있습니다.

python -m nesformer trace roms/thwaite.nes --tokens 100 --no-skip

테스트 모음 실행

테스트 의존성을 설치하고 pytest를 실행하세요.

pip install pytest py65
python -m pytest -q tests

테스트를 완료하는 데 몇 분이 걸립니다. 테스트는 기준 CPU를 nestest 로그와 비교하고, 컴파일된 회로를 기준 구현과 대조하며, 프리 러닝 가중치를 검증하고, 유휴 루프 최적화를 테스트합니다.

중요한 제한 사항

  • NROM만 지원: 게임은 iNES 매퍼 0을 사용해야 합니다. MMC1 및 MMC3과 같은 뱅크 전환 매퍼는 지원되지 않습니다.
  • 공식 opcode만 지원: 비공식 6502 opcode를 만나면 HALT를 출력합니다.
  • ROM에서 실행: 코드는 카트리지 ROM에서 실행되어야 하며, RAM으로 점프하면 HALT를 출력합니다.
  • IRQ 미지원: BRK와 NMI는 작동하지만 APU 프레임 및 DMC IRQ는 지원되지 않습니다.
  • 제한적인 I/O 동작: I/O 레지스터에 대한 읽기-수정-쓰기 명령은 I/O 쓰기를 생성하지 않으며, $4020부터 $7FFF까지의 주소는 0으로 읽힙니다.
  • 그래픽 세부 묘사의 근사: 렌더링은 한 번에 한 스캔라인씩 수행되고, 스캔라인 중간의 효과는 재현되지 않으며, 스프라이트 0 히트와 스프라이트 오버플로는 근사적으로 처리됩니다.
  • 오디오 미지원: APU 레지스터 쓰기는 허용되지만 사운드는 생성되지 않습니다.
  • 느린 실행: 유휴 루프 건너뛰기를 사용하더라도 이 프로젝트는 실제 콘솔보다 대략 150~290배 느리게 실행됩니다.

결론

nesformer는 트랜스포머 체크포인트가 학습된 언어 모델이 아니라 정확히 컴파일된 컴퓨터로 기능할 수 있음을 보여줍니다. 소스와 가중치를 올바르게 배치하면 호환되는 NROM 게임을 실행하고, 컨트롤러 입력을 자동화하고, 프레임을 내보내고, CPU 토큰을 검사하며, 기준 6502와 실행 결과를 비교할 수 있습니다. 매퍼, 그래픽, 오디오 및 속도에 제약이 있는 실험적 시스템이지만, 표준 Qwen3.5 체크포인트와 검증 도구를 통해 모델 가중치와 DeltaNet 상태에 직접 인코딩된 연산을 실용적으로 연구할 수 있습니다.