메뉴
HN
Hacker News • 16일 전

998달러로 3.8B LLM 학습시켜 CORE 0.384 달성

IMP
7/10
핵심 요약

한 개발자가 야간 작업으로 little-lm이라는 3.8B 파라미터 모델을 처음부터 직접 학습시켜, 렌털 B200 GPU 8대로 43시간, 998달러 비용만으로 CORE 벤치마크 0.384를 달성했다. 이는 비슷한 비용의 nanochat d32(0.310)를 크게 앞서는 결과로, 거액의 컴퓨팅 예산 없이도 개인이 유의미한 모델을 학습할 수 있음을 보여준다. 좋은 인프라와 YAML 기반 실험 관리, Muon 옵티마이저 등의 선택이 핵심이었다.

번역된 본문

"nanoGPT 장난감"과 "연구소가 필요하다" 사이에는, 몇천 달러만 있으면 한 사람이 유의미한 모델을 학습시킬 수 있는 넓지만 잘 문서화되지 않은 영역이 존재합니다. 저는 무작위 가중치에서 언어와 이해가 발생하는 것을 직접 확인하고, 처음부터 만들어야만 배울 수 있는 부분들을 배우고 싶었습니다. 이 프로젝트는 저녁 시간에 작성되었고, 5090에서 디버깅한 뒤 렌털 B200에서 마무리되었습니다. Andrej Karpathy의 nanochat에서 큰 영감을 받았습니다. 그 결과는 3.8B 파라미터 모델이 65B 토큰으로 43시간 동안 998달러를 들여 학습되어 CORE 0.384를 기록한 것입니다. 이하에서는 무엇이 통했고, 무엇이 실패했으며, 무엇을 아직 모르는지를 다룹니다.

모델 | 파라미터 | 토큰 | 하드웨어 | 시간 | 비용 | CORE GPT-2 (OpenAI) | 1.5B | — | — | — | — | 0.2565 nanochat d26 | ~561M | 11.2B | 8× H100 | ~3h | — | ~0.258 nanochat d32 | ~1B | — | 8× H100 | ~33h | ~$1000 | 0.310 little-lm 3.8B (1024 ctx) | 3.848B | 57.3B | 8× B200 | 35.9h | $820 | 0.338 little-lm 3.8B (2048 ctx) | 3.848B | 65.3B | 8× B200 | 43h | $998 | 0.384

제 모델은 nanochat d32보다 크면서도 실제 소요 시간은 비슷했습니다. B200은 작업량당 H100보다 가성비가 좋았습니다. 그리고 nanochat의 1,000달러 구성과 거의 같은 비용으로 의미 있게 앞서는 성능을 달성했습니다. 수백만 달러 컴퓨팅 예산을 가진 연구소나 대기업 밖에서 도달 가능한 수준이 어디까지인지에 대해 고무적인 데이터 포인트입니다. 프론티어가 발전할수록 1,000달러로 갈 수 있는 거리는 점점 멀어지고 있습니다.

설정

저는 little-lm을 작은 디코더 전용(decoder-only) LLM을 학습시키기 위한 설정 기반(config-driven) 프레임워크로 만들었습니다. 모든 실행은 YAML 파일 하나로 완전히 정의됩니다: 모델, 데이터셋, 옵티마이저, 스케줄, 콜백. 컴포넌트들은 전역 레지스트리에 자동 등록되고 이름으로 해석되므로, 옵티마이저나 데이터셋 교체는 한 줄의 설정 변경으로 가능합니다.

좋은 인프라는 거의 즉시 투자 가치를 되갚아줍니다. 관심사 분리, 깔끔한 인터페이스, 교체 가능한 컴포넌트 같은 평범한 소프트웨어 엔지니어링 원칙이 AI 작업에서 매우 중요합니다. 처음에 약간의 비용이 들었고, 잘못된 계약이나 비최적 설계를 고치느라 몇 번 더 들었습니다. 하지만 이런 투자는 첫 번째 수렴 문제를 만났을 때 본전을 뽑게 됩니다. 훌륭한 인프라란 코드를 수동으로 수정할 일이 거의 없는 인프라라고 생각합니다. 설정만 읽으면 정확히 무슨 일이 벌어지는지 이해할 수 있고 숨겨진 메커니즘이 없다면, 좋은 작업을 한 것입니다. 이 보고서는 실험을 브랜치가 아니라 3줄짜리 YAML diff로 표현할 수 있었던 결과물입니다.

최종 모델은 Llama 스타일입니다: RMSNorm, RoPE, GQA(쿼리 헤드 24개, KV 헤드 8개), relu² MLP, QK-norm, logit softcap, 레이어별 학습 가능한 잔차 스칼라, 그리고 ResFormer 스타일의 값 임베딩(value embeddings)을 사용합니다.

컴포넌트 | 파라미터 토큰 임베딩 | 154.5M LM 헤드 (untied) | 154.5M 디코더 28 레이어 | 2,818.7M 값 임베딩 (14개 테이블) | 721.2M 합계 | 3.848B

값 임베딩이 전체 파라미터의 19%를 차지한다는 점이 주목할 만합니다. vocab × kv_dim 크기의 테이블 14개가 한 레이어 걸러 하나씩 배치되어 있습니다.

결과

초기 실험

좋은 실행 이전에 수많은 나쁜 실행이 있었습니다. 858M 규모의 Llama를 FineWeb-Edu로 16.4B 토큰 학습시켰고, A100 한 대에서 5.8일이 걸렸습니다. AdamW 학습률 2.5e-4, 0까지 코사인 감쇠, 5% 워밍업, 그래디언트 누적으로 배치 256, 컨텍스트 2048. 결과는 PIQA 60.45%. GPT-2 124M은 약 63%를 기록합니다. 저는 6일치 컴퓨팅을 들여 2019년의, 7배 작은 모델보다 못한 것을 만든 셈입니다. 생성 결과는 반복적이고 거의 말이 되지 않는 수준이었습니다.

손실 곡선이 문제를 말해주고 있었습니다.

  • 0까지의 코사인 감쇠. 곡선이 스텝의 약 70% 이후 완전히 평평해졌습니다. 학습률이 너무 낮아서 컴퓨팅 예산의 마지막 30%가 사실상 아무것도 생산하지 못했습니다. 선형 쿨다운(linear cooldown)은 훨씬 후까지 유용한 학습률을 유지합니다.
  • 피크 학습률이 너무 보수적. 2.5e-4는 858M 파라미터에는 낮은 값입니다. 이런 소규모 모델에서는 꽤 공격적으로 잡을 수 있습니다.
  • 모든 파라미터에 AdamW. 이 규모에서는 행렬 파라미터에 Muon이 토큰당 의미 있게 더 나을 것입니다. 실제로 어블레이션 실행에서 빠르게 입증되었습니다.
  • 데이터. FineWeb-Edu는 괜찮지만, 사용 가능한 최고는 아닙니다.

이 경험에서 다섯 가지 개선점이 나왔습니다.

원문 보기
원문 보기 (영어)
Somewhere between “nanoGPT toy” and “you need a research lab” there’s a large, under-described region where one person with a few thousand dollars can train a meaningful model. I wanted to see language and understanding emerge from random weights for myself, and to learn the parts you can only learn by starting from scratch. This project was written in the evenings, debugged on a 5090 and finished on rented B200s. It was heavily inspired by Andrej Karpathy’s nanochat . The result is a 3.8B-parameter model scoring 0.384 on CORE , trained on 65B tokens in 43 hours for $998 . What follows is what worked, what didn’t, and what I still don’t know. Model Params Tokens Hardware Time Cost CORE GPT-2 (OpenAI) 1.5B — — — — 0.2565 nanochat d26 ~561M 11.2B 8× H100 ~3h — ~0.258 nanochat d32 ~1B — 8× H100 ~33h ~$1000 0.310 little-lm 3.8B (1024 ctx) 3.848B 57.3B 8× B200 35.9h $820 0.338 little-lm 3.8B (2048 ctx) 3.848B 65.3B 8× B200 43h $998 0.384 My model is larger than nanochat d32 and took similar wall-clock time. B200s were better value per unit of work than H100s. But for roughly the same money as nanochat’s $1,000 configuration, this lands meaningfully ahead of it. An encouraging data point about what’s reachable outside a lab or a mega company with millions in compute budget. As the frontier moves, $1,000 takes you further and further. Setup I’ve built little-lm as a config-driven framework for training small decoder-only LLMs. Every run is fully specified by a YAML file: model, dataset, optimizer, schedule, callbacks. Components self-register into a global registry and get resolved by name, so swapping an optimizer or a dataset is a one-line config change. Good infrastructure pays for itself almost immediately. Ordinary software engineering discipline (Things like separation of concerns, clean interfaces, components you can swap in) matters a lot in AI work. It cost me a little at the start, and a couple more times afterward to fix bad contracts or suboptimalities. But this time investment pays for itself at the first convergence problem you encounter. I found that a great infra is the infra that almost never requires you to edit code manually. If you can read the config and understand exactly what happens, and there are no hidden mechanics, it means you have done a good job. The following report is the result of being able to express experiments as a three-line YAML diff rather than a branch. The final model is Llama-style: RMSNorm, RoPE, GQA (24 query heads, 8 KV heads), relu² MLPs, QK-norm, logit softcap, per-layer learnable residual scalars, and ResFormer-style value embeddings. Component Params Token embeddings 154.5M LM head (untied) 154.5M 28 decoder layers 2,818.7M Value embeddings (14 tables) 721.2M Total 3.848B Worth noting that the value embeddings are 19% of the parameter count. 14 tables of vocab × kv_dim , one on every other layer. Results Early experiments Before good runs there were many bad ones. I trained an 858M Llama on FineWeb-Edu for 16.4B tokens, 5.8 days on a single A100. AdamW at 2.5e-4, cosine decay to zero, 5% warmup, batch 256 via gradient accumulation, 2048 context. The result: PIQA 60.45% . GPT-2 124M scores about 63%. I had spent six days of compute to build something worse than a model seven times smaller, from 2019. Generations were repetitive and borderline nonsensical. The loss curve told the story. Cosine decay to zero. The curve went completely flat after about 70% of the steps. The final 30% of the compute budget produced essentially nothing as the learning rate might be too low. Linear cooldown holds a useful rate much later. Peak LR too conservative. 2.5e-4 is low for 858M parameters. You can be quite aggressive for those small models. AdamW on everything. Muon should be meaningfully better per-token for the matrix parameters at this scale. In fact this was demonstrated pretty quickly in ablation runs. The data. FineWeb-Edu is decent. It is not the best available. Five changes came out of that post-mortem. Together they are the difference between the run above and a model that beats GPT-2 by a wide margin. Trapezoidal LR schedule. Warmup for 5%. Hold flat and finish with linear cooldown over the last 50% to 5% of peak. The point is that the model keeps learning until the end instead of coasting through the tail. In the 3.8B run the eval loss was still descending at the final step, which is exactly the behavior the 858M run failed to produce. Muon for matrix parameters , AdamW for everything else. Muon is slower per step (Newton-Schulz orthogonalization isn’t free, about 25% in a shallow-accumulation benchmark) but that cost is paid once per optimizer step: at 7 gradient-accumulation steps it dilutes to ~4%. Measured against total run time the convergence is much faster overall. ClimbMix instead of FineWeb-Edu. This was a tremendous jump in convergence speed. Exactly as Karpathy found as well. FP8 + vocab padding. FP8 training via torch._scaled_mm with dynamic tensorwise scaling on all three GEMMs, and padding the vocab from 50,257 to 50,304 (a multiple of 64) so the tensor cores are happy. Together, +33% throughput mostly from fp8. 1024 context instead of 2048. Halving the context roughly doubles the batch size at fixed memory. Throughput barely changes per token. We are still dominated by the MLPs which is a good sign we are using the hardware effectively. Below we will discuss the impact of the context length on the model. Here is the whole run: Step Tokens Eval loss CORE 2,500 5.7B 2.3278 0.2389 5,000 11.5B 2.2072 0.2752 7,500 17.2B 2.1571 0.2934 10,000 22.9B 2.1269 0.3104 12,500 28.7B 2.1075 0.3147 15,000 34.4B 2.0710 0.3224 17,500 40.1B 2.0395 0.3294 20,000 45.9B 2.0160 0.3267 22,500 51.6B 1.9963 0.3345 25,000 57.3B 1.9868 0.3384 ~480,000 tokens/sec in steady state, which puts 57.3B tokens at 33 hours. The wall clock was 35.9h. The difference is the CORE evaluations, which took about 15 minutes each (ten of them over the run) and consumed 7% of the total. Re-running this identical recipe at 2048-token context scored 0.3840 . Almost all of that gap turned out to be some tasks that were very context dependent. On the GPUs themselves: 92% SM activity, 40% SM occupancy. High activity means the SMs almost never went idle. No dataloader starvation or network waits, which is the payoff for downloading the shards locally instead of streaming, which would leave us vulnerable to a small hugging face network hang. The low occupancy is what back-to-back large GEMMs look like: matmul kernels trade occupancy for register-tile size on purpose. Compute-bound and well fed, great signal we are using the hardware well and we can extend every dollar we spend into a better model. That’s about 1,047 TFLOP/s sustained per B200 , or ~25% MFU against Blackwell’s dense FP8 peak. (Against the bf16 peak it reads as 50%, which is the number that matters a bit more because not even all the linear layers run in FP8.) The distributed strategy is plain old DistributedDataParallel. At 3.8B on a single node, gradient communication was never the constraint, and the sharded-optimizer machinery turned out to be unnecessary. Increasing throughput Renting GPUs isn’t cheap, at work you often think about the quality of the model before its cost. When it’s your own money burning, throughput matters a lot more all of a sudden. This took real work on a single RTX 5090, before I ever rented a node. Baseline 858M model, bf16, compiled: 26,144 tok/s . Final: 37,621 tok/s . FP8 (+25%). All three GEMMs (1 forward and 2 backwards) in FP8 with dynamic tensorwise scaling. Requires SM90+ but that is quite a nice throughput jump. Vocab padding (+33% cumulative). Padding 50,257 → 50,304 costs 47 unused embedding rows and unlocks the fast tensor-core path. Nearly free. Fused linear cross-entropy (+44% cumulative). Liger’s FusedLinearCrossEntropyLoss fuses the lm_head matmul into the loss and chunks internally, so the full (B*T, vocab) logits tensor is never materiali