4GB GPU, 작은 코퍼스, 그리고 수많은 시행착오.
나는 4GB VRAM이 탑재된 노트북으로 22백만 개의 파라미터를 가진 언어 모델을 처음부터 학습시켰다. 이 모델은 기술 문서를 모방하는 법을 배웠고, 언어의 일부 구절을 반복하며 막혔으며, 작동하는 피보나치 함수를 한 번도 만들어내지 못했다. 거의 끝날 무렵에는 CUDA가 충돌했다. 결과물은 유용한 챗봇이 아니었다. 흥미로운 부분은 실패 그 자체였다.
Ollama를 통해 사전 학습된 모델을 가져다 쓰는 것은 현대 AI를 기만적으로 단순하게 느끼게 한다. 심지어 아주 작은 모델이라도 무작위 가중치(random weights)에서부터 학습시키는 과정은 그러한 착각을 매우 빠르게 깨뜨린다.
이것이 내가 연구 코드, 데이터셋, 실험 정의, 모델 및 결과에 대한 별도의 저장소인 AI Lab을 시작하게 된 이유였다. AI.001이 나의 첫 번째 실험이었다: 노트북에서 무작위 가중치로 작은 언어 모델을 학습시키는 것. 여기에는 그 실행 과정과 모델이 실제로 생성한 내용까지 담긴 이야기들이 있다. 전체 방법론, 코퍼스 분석 및 훈련 곡선은 AI.001 실험 보고서에서 확인할 수 있다.
추론(inference) 이전에 무슨 일이 일어나는지 이해하고 싶었다
API나 사전 학습된 로컬 모델은 훌륭하지만, 내가 보고 싶은 거의 모든 것을 가려버린다. 데이터셋이 실제로 어떤 모습인지? 클리닝 작업이 얼마나 중요한지? 문서와 토큰 사이에는 무엇이 일어나는가? 체크포인트(checkpoint)란 정확히 무엇인가? 손실(loss)은 나에게 무엇을 말해주고, 또 무엇을 조용히 말해주지 않는가? 모델이 전혀 유용해지기 전의 생성 과정은 어떤 모습인가?
이미 작동하는 것을 호출해서는 이러한 질문들에 답할 수 없었다. 알아낼 수 있는 유일한 방법은 투박한 버전을 직접 구축하고 그것이 오작동하는 모습을 지켜보는 것뿐이었다.
제약 조건: 4GB VRAM
사용된 장비는 Lenovo IdeaPad Gaming 노트북이다: Intel i7-10750H, RAM 16GB, 그리고 4GB VRAM을 가진 GTX 1650이며 WSL2 환경에서 CUDA와 PyTorch를 구동했다. 이것은 학습에 좋은 장비가 아니며, 나는 그렇게 제시할 생각은 없다.
그게 핵심입니다. 큰 GPU가 있으면 많은 미흡한 결정들을 여유 공간(headroom) 뒤에 숨길 수 있습니다. 하지만 4GB로는 컨텍스트 길이(context length)가 왜 그런지, 배치(batch)를 마이크로배치(microbatches)로 분할하는 이유, 그리고 옵티마이저 상태(optimizer state)가 얼마나 비용을 차지하는지를 알아야 합니다. 저는 256 토큰의 컨텍스트와 그라디언트 누적(gradient accumulation)을 사용한 16 마이크로배치로 결정하여, 각 업데이트에서 여전히 상당히 큰 배치를 볼 수 있도록 했습니다. 메인 실행의 최대 메모리 사용량은 1.54 GiB였는데, 이는 카드 제한보다 충분히 낮은 수준입니다. 저는 이것을 카드가 관대해서라고 해석하지 않습니다. 처음부터 한계를 중심으로 파이프라인을 설계한 결과로 해석합니다.
코퍼스 구축하기
스페인어 및 영어 위키피디아, Python 문서, 그리고 몇 가지 다른 기술 문서 세트: 텍스트를 수집하는 것은 쉬운 부분입니다. 누구나 데이터 덤프(dump)를 다운로드할 수 있습니다. 흥미로운 부분은 학습 세트에 무엇을 허용할지 결정하고 그 결정이 나중에 어떤 영향을 미치는지 판단하는 것입니다.
저는 대규모의 필터링되지 않은 웹 데이터셋 대신 통제된 코퍼스(controlled corpus)를 선택했고, 출처들을 품질, 라이선싱, 언어 균형, 중복 위험성, 그리고 전처리하기 얼마나 까다로웠는지를 기준으로 판단했습니다. 클리닝하고 필터링하는 과정에서 코퍼스는 더 이상 '다운로드할 수 있는 모든 것'이 아니라 의도적으로 만든 무언가가 됩니다. 여기에서 코퍼스 통계치를 모두 쏟아내지는 않겠습니다. 실험 페이지에 있습니다. 제가 말하고 싶은 것은 그러한 선택들이 평가 단계에서 나중에 나타나서 지적할 수 있는 행동으로 돌아왔다는 것입니다.
AI.001 workflow: select source text, clean and filter, tokenise, train from random weights, save a resumable checkpoint, then evaluate held-out text and inspect generated output.
AI 워크플로우. 001. 체크포인트는 복구를 가능하게 합니다. 보류 손실(held-out loss)과 생성된 샘플은 서로 다른 질문에 답합니다.
하락하는 손실, CUDA 충돌, 그리고 체크포인트
검증 손실(Validation loss)은 9.48에서 시작했습니다. 어휘 크기(vocabulary)가 12,000 토큰인 경우, 다음 토큰을 무작위로 추측하면 약 9.39의 손실이 발생했을 것입니다. 코퍼스(corpus)를 한 번 통과한 후, 모델은 4.27에 도달했습니다. 그것은 진전이었고, 보기 만족스러웠습니다. 하지만 그것은 단 하나의 좁은 질문—보류된 텍스트(held-out text)에서의 예측이 개선되었는지 여부—에만 답했을 뿐, 모델이 유용한 무언가를 생성할 수 있는지에 대해서는 말해주지 않았습니다.
체크포인트(Checkpoint)는 비슷한 시기에 추상적인 개념에서 벗어났습니다. 저는 긴 학습을 시작하기 전에 짧은 테스트를 통해 체크포인트부터 재개하는 것을 시험해 보았고, 제가 바라던 정확한 메시지를 출력했습니다:
Resumed from: results/EXP-001/resume-test/latest.pt
Resume step: 10
이것은 합리적인 것이었습니다. 메인 학습의 3,540 스텝 직후, CUDA가 알 수 없는 고착 오류(unknown sticky error)를 보고했고 학습이 중단되었습니다. 메모리가 부족해서 발생한 실패는 아니었고 손실 값도 정상적으로 작동하고 있었습니다. 저는 옵티마이저(optimizer), 혼합 정밀도 스케일러(mixed-precision scaler), 그리고 난수 상태(random state)를 복원하여 3,500 스텝 체크포인트부터 재시작했고, 그것은 끝까지 실행되었습니다.
더 교육적인 놀라움은 생성(generation)이었습니다. 샘플링(sampling)을 사용했을 때, 나중의 체크포인트들은 기술 문서를 닮은 조각들을 만들어냈습니다. 초기의 모델은 워드프레스(WordPress), MDN, 도커(Docker) 자료 등을 일관성 없이 뒤섞어냈습니다. 그리디 디코딩(greedy decoding)—항상 가장 가능성이 높은 다음 토큰을 선택하는 방식—을 사용했을 때는 최종 모델이 종종 루프에 빠졌습니다. 검증 손실은 이러한 실패 모드를 보여주지 않았습니다. 생성 과정에서 모델은 평가에 사용된 실제 텍스트가 아니라 자신의 출력으로부터 계속해야 합니다. 몇 가지 프롬프트(prompt)를 통해 나온 결과는 다음과 같습니다:
- 프롬프트: “A WordPress plugin can” → 샘플링된 출력 (temperature 0.8, 최종 체크포인트): “A WordPress plugin can be used when the plugin is loaded” 및 “A plugin contains a PHP file”
- 프롬프트: “Barcelona es una ciudad” → 그리디 디코딩: “de la ciudad”의 반복적인 변형
- 프롬프트: “A language model learns” → 그리디 디코딩: “the language is the language language…”
- 프롬프트: “def fibonacci(n):” → 테스트한 어떤 체크포인트도 유효한 구현을 생성하지 못함
평가가 학습보다 더 흥미로웠다
이러한 예시들이 손실 곡선 자체보다 더 많은 것을 알려주었다. 그래서 나는 단순히 종합 점수에 의존하기보다는 코퍼스 구성 요소별로 최종 체크포인트를 평가했다. 기술 문서는 일반 산문보다 낮은 손실을 보였지만, 출처 간에는 큰 편차가 있었다: MDN과 WordPress 텍스트가 이 모델에게 Docker, Python 또는 Git 문서보다 더 예측 가능했다. AI.001에서의 출처별 결과에서 실제 측정값을 확인할 수 있다.
이 순서에 대해서는 신중해야 한다. 이것은 한 번의 실행 중 하나의 체크포인트에서 나온 관찰일 뿐이며, 모델이 좋다는 증거가 아니다. 이 것이 알려주는 것은 종합 손실 점수보다 더 좋은 질문들이다. 어떤 출처가 잘 쓰여서 쉬운 것인가, 아니면 반복적이기 때문에 쉬운 것인가? 낮은 손실의 어느 정도는 모델이 무언가를 알고 있기 때문이고, 어느 정도는 예측 가능한 형식(format) 때문인가? 내가 일부 출처에 마땅히 주지 않아도 될 공간을 준 것은 아닌가? 내가 알아차리지 못한 도메인 간의 중복이 있는가? 손실 점수만으로는 이러한 것들을 구분할 수 없다.
작은 모델들은 잔혹하게 정직하다
2,200만 개의 파라미터로 구성된 모델은 한 번 정도 학습되었을 뿐인데도 숨을 곳이 없다. 이 모델은 내용(meaning)보다 기술 문서가 어떻게 생겼는지(look)를 훨씬 더 잘 배웠다. 존재하지 않는 API에 대해 설득력 있는 제목과 매개변수 목록을 작성하고, 단락 내에서 도메인 사이를 떠돌아다니며, 내가 시도한 어떤 체크포인트에서도 작동하는 def fibonacci(n):를 생성해내지 못했다.
그것은 바로 '나쁘기' 때문에 학습에 유용합니다. 강력한 모델은 그 작동 방식을 매끄럽게 만듭니다. 반면, 약한 모델은 그것들을 보여줍니다: 용량(capacity), 데이터 혼합(data mix), 반복성(repetition), 그리고 텍스트를 예측하는 것과 무언가를 아는 것 사이의 간극입니다. 이것은 제품 개발이 아니라 연구이며, 저는 이 점을 제 머릿속에서 명확히 하려고 노력합니다.
제가 실제로 얻은 것들
경쟁력 있는 모델도 아니고, 질문에 답하는 데 사용할 만한 것도 아닙니다. 제가 얻은 것은 연구 워크플로우였습니다: 재현 가능한 코퍼스 도구(corpus tooling), 일시 중지 및 재개할 수 있는 학습 스크립트, 그리고 출처별로 세분화된 평가입니다. 무엇보다 중요하게는, 모델이 무엇을 배웠는지 결정하기 전에 '보류 손실(held-out loss)'과 실제 생성 결과 모두를 살펴봐야 한다는 것을 알게 되었습니다. 이는 그저 설득력 있게 보이는 데모보다 더 유용하게 느껴집니다.
다음 단계
더 큰 모델을 약속하는 것은 아닙니다. 첫 번째 실행이 남긴 질문들은 코퍼스 선택, 더 나은 평가 방법, 그리고 한 번에 하나의 결정을 변경하여 각 변화가 무엇을 했는지 알려줄 수 있는 것에 관한 것입니다. 실험 페이지에서는 좀 더 집중적인 후속 작업을 나열하고 있으며, 여기에는 더 많은 소스 코드와 비교할 수 있는 고정된 평가가 포함되어 있습니다. 저는 제가 설명만 할 수 있는 거대한 변화보다는, 이해할 수 있는 작고 통제된 단계를 밟는 것을 선호합니다.
원래 sandrabamboo.com에 게시됨: https://sandrabamboo.com/training-my-first-language-model-on-a-gtx-1650/ — 모델 아키텍처, 코퍼스 출처, 학습 곡선 및 출처별 평가에 대한 전체 AI.001 실험은 다음을 참조하세요: https://sandrabamboo.com/experiments/training-a-language-model-from-scratch-on-4gb-of-vram/