Skip to content
Published on

LLM 학습 스택 지도 2026 — 무엇이 무엇을 대신해 주고 무엇을 숨기는가

공유하기
Authors

들어가며 — 프레임워크를 고르는 일은 무엇을 숨길지 고르는 일

"어떤 프레임워크로 학습해야 하나요"라는 질문에는 답이 하나 없습니다. 층이 다르기 때문입니다. torchrun으로 직접 짠 루프와 YAML 한 장으로 도는 파인튜닝은 경쟁 관계가 아니라 상하 관계입니다.

그래서 이 글은 프레임워크를 나열하는 대신 세 개 층으로 나눠 계보를 그립니다. 각 층에 대해 세 가지만 봅니다. 이 층이 대신해 주는 일, 이 층이 감추는 것, 그리고 감춘 것을 다시 열어야 할 때 탈출구가 있는가입니다.

이 분야는 한 달 단위로 바뀝니다. 아래 버전은 전부 2026년 8월 2일에 PyPI 배포 이력과 GitHub 릴리스 태그에서 직접 확인한 값이고, 확인하지 못한 항목은 본문에 그렇게 적었습니다. 읽으시는 시점에는 이미 몇 개가 바뀌어 있을 것이므로, 숫자 자체보다 층 구조를 가져가시는 편이 오래갑니다.

확인 기준 — 버전과 확인 날짜

도구확인한 버전배포일출처
PyTorch2.13.02026-07-08PyPI
DeepSpeed0.19.32026-07-23PyPI
Megatron-Core0.18.22026-07-21GitHub
Transformer Engine2.17.02026-07-09PyPI
torchtitanv0.2.22026-02-20GitHub
NeMo Toolkit2.7.32026-04-23PyPI
NeMo-RLv0.7.02026-07-29GitHub
Transformers5.14.12026-07-16PyPI
TRL1.9.22026-07-28PyPI
PEFT0.20.02026-07-28PyPI
Accelerate1.14.02026-06-11PyPI
Axolotl0.18.02026-07-17PyPI
LLaMA-Factory0.9.52026-05-30PyPI
Unsloth2026.7.62026-07-29PyPI
verl0.8.02026-06-01PyPI
OpenRLHF0.10.42026-06-08PyPI
Liger Kernel0.8.12026-07-23PyPI
torchao0.17.02026-03-30PyPI
torchtune0.6.1 (마지막)2025-04-07GitHub

1층 — 분산 실행 엔진

가장 아래층은 "이 텐서를 어느 GPU에 두고 언제 어떤 집합 통신을 걸 것인가"를 담당합니다. 세 계보가 있습니다.

PyTorch 분산 계열. torch.distributed가 DDP, FSDP2, DTensor, DeviceMesh, torch.distributed.pipelining을 모두 제공합니다. 2026년 기준으로 특징적인 변화는 병렬화가 별도 라이브러리가 아니라 PyTorch 본체 API가 되었다는 점입니다. FSDP2의 fully_shard는 모듈을 감싸지 않고 제자리에서 파라미터를 DTensor로 바꿔 두므로, 파라미터 이름이 유지되고 체크포인트 호환성 문제가 줄어듭니다. 공식 튜토리얼은 FSDP1을 폐기 상태로 표기합니다.

DeepSpeed 계열. ZeRO 1~3단계, 오프로딩, 시퀀스 병렬(Ulysses), 추론 커널까지 한 패키지에 들어 있습니다. 자주 받는 질문이 "아직 살아 있느냐"인데, 0.19.3이 2026년 7월 23일에 나왔으니 활발합니다. 소유 구조는 바뀌었습니다. GitHub 조직이 microsoft에서 deepspeedai로 옮겨졌고, LF AI & Data 인큐베이션 프로젝트이자 PyTorch Foundation 호스팅 프로젝트가 되었습니다. 예전 링크와 조직명이 문서에 남아 있으면 그 문서는 오래된 것입니다.

Megatron 계열. NVIDIA의 Megatron-LM 저장소에서 재사용 가능한 부분만 떼어낸 것이 Megatron-Core이고, 이것이 텐서 병렬, 파이프라인 병렬, 시퀀스 병렬, 컨텍스트 병렬, 전문가 병렬의 사실상 참조 구현입니다. 여기에 저정밀 학습을 담당하는 Transformer Engine이 붙습니다. FP8 학습은 오래전부터 지원했고, NVFP4 4비트 사전학습도 NVIDIA의 논문이 12B 하이브리드 Mamba-Transformer를 10T 토큰까지 FP8 기준선과 동등하게 학습했다고 보고하며 Transformer Engine에서 지원됩니다. 다만 이 결과를 자체 모델에 그대로 옮길 수 있는지는 별개 문제이니, 4비트 사전학습을 도입하려면 짧은 런으로 손실 곡선을 직접 비교해 보셔야 합니다.

이 층을 직접 만질 때의 인터페이스는 대개 설정 파일입니다. DeepSpeed라면 이런 모양입니다.

{
  "bf16": { "enabled": true },
  "zero_optimization": {
    "stage": 3,
    "overlap_comm": true,
    "contiguous_gradients": true,
    "reduce_bucket_size": 5e8,
    "stage3_prefetch_bucket_size": 5e8,
    "stage3_param_persistence_threshold": 1e6,
    "stage3_gather_16bit_weights_on_model_save": true
  },
  "gradient_accumulation_steps": "auto",
  "train_micro_batch_size_per_gpu": "auto",
  "gradient_clipping": 1.0,
  "steps_per_print": 50,
  "wall_clock_breakdown": false
}

여기서 실무상 가장 자주 문제가 되는 줄이 stage3_gather_16bit_weights_on_model_save입니다. 3단계에서는 파라미터가 GPU마다 쪼개져 있으므로, 이 값을 켜지 않고 저장하면 서빙에서 못 읽는 샤드 조각들이 나옵니다. 그리고 overlap_comm을 켜면 통신과 계산이 겹쳐 빨라지지만 활성화 메모리 여유가 줄어드는 방향으로 작용하므로, OOM이 아슬아슬한 구성에서는 이 값을 먼저 의심하십시오.

이 층이 감추는 것은 거의 없습니다. 대신 아무것도 대신해 주지 않습니다. 데이터 로더, 체크포인트 포맷, 재개 로직, 로깅을 전부 직접 써야 합니다.

2층 — 학습 루프 프레임워크

1층 위에 "학습을 처음부터 끝까지 도는 루프"를 얹은 층입니다.

torchtitan은 PyTorch 팀이 만든 참조 구현으로, FSDP2, 텐서 병렬(비동기 변형 포함), 파이프라인 병렬, 컨텍스트 병렬, HSDP를 조합할 수 있고 Float8과 MXFP8 학습을 지원합니다. 저장소는 스스로를 "빠른 실험과 대규모 학습을 위한 PyTorch 네이티브 플랫폼"으로 소개하면서, 최신 기능을 쓰려면 PyTorch 나이틀리를 권합니다. 즉 안정판 위에서 굴리는 제품용 스택으로 설계된 물건은 아닙니다. 코드를 읽고 베껴 쓰는 용도로는 최고의 참조입니다.

Megatron-Bridge는 NVIDIA가 Hugging Face 체크포인트와 Megatron-Core 사이의 양방향 변환을 담당하도록 만든 라이브러리이자, 그 위에 Megatron-Core를 쓰는 학습 루프를 얹은 것입니다. 이 다리가 중요한 이유는 실무 흐름 때문입니다. 모델은 Hugging Face 포맷으로 받고, 학습은 Megatron-Core 병렬화로 돌리고, 서빙은 다시 Hugging Face 포맷으로 내보내야 합니다. 이 변환을 직접 짜면 텐서 이름 매핑과 병합/분할 규칙에서 반드시 틀립니다. NeMo-RL과 SkyRL이 Megatron-Core 커넥터로 이것을 채택했다는 문서 서술이 있습니다.

NVIDIA NeMo는 그 위의 종합 프레임워크입니다. 사전학습부터 정렬, 배포까지 레시피로 묶여 있고, 강화학습 부분은 NeMo-RL로 분리되어 있습니다. NVIDIA 컨테이너 환경을 그대로 쓸 수 있다면 가장 빠른 경로이고, 그렇지 않다면 의존성 무게가 부담이 됩니다.

이 층이 대신해 주는 것은 병렬화 구성, 분산 체크포인트, 재개, 스루풋 로깅입니다. 감추는 것은 옵티마이저 스텝의 세부와 통신 스케줄이라, 손실 스파이크를 디버깅할 때 다시 열어 봐야 합니다.

3층 — 설정 파일로 도는 사후 학습 도구

가장 위층은 데이터셋 포맷, 프롬프트 템플릿, LoRA 설정, 평가 훅까지 묶어서 명령어 한 줄로 실행합니다.

TRL은 Hugging Face 생태계의 사후 학습 표준입니다. 1.9.2 릴리스 노트 기준으로 SFT, DPO, GRPO, RLOO, KTO, 보상 모델 트레이너가 들어 있고, KTO 트레이너는 1.8.0에서 실험 딱지를 뗐습니다. 1.7.0에서 기본 손실이 청크 단위 NLL로 바뀌고 MoE 보조 손실이 추가된 것처럼, 기본값 자체가 마이너 릴리스에서 바뀝니다. 실험 네임스페이스에 트레이너가 몇 개 더 들어 있다는 서술을 릴리스 페이지에서 봤지만 개별 알고리즘까지는 확인하지 못했으니, 쓰실 버전의 문서에서 직접 목록을 보십시오. TRL은 버전을 반드시 핀으로 고정해야 하는 라이브러리입니다.

TRL을 직접 쓸 때의 최소 형태는 이렇습니다. 버전 고정과 손실 마스킹 확인을 같이 넣어 두었습니다.

# pip install "trl==1.9.2" "transformers==5.14.1" "peft==0.20.0" "accelerate==1.14.0"
from datasets import load_dataset
from peft import LoraConfig
from trl import SFTConfig, SFTTrainer

ds = load_dataset("json", data_files="data/sft-train.jsonl", split="train")

cfg = SFTConfig(
    output_dir="out/sft-7b",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,
    learning_rate=1e-5,
    num_train_epochs=2,
    bf16=True,
    gradient_checkpointing=True,
    max_length=4096,
    packing=False,              # 마스킹을 직접 검증하기 전까지는 끄고 시작합니다
    logging_steps=10,
    save_steps=200,
    report_to="mlflow",
)

trainer = SFTTrainer(
    model="meta-llama/Llama-3.1-8B",
    args=cfg,
    train_dataset=ds,
    peft_config=LoraConfig(r=32, lora_alpha=64, lora_dropout=0.05,
                           target_modules="all-linear", task_type="CAUSAL_LM"),
)

# 학습을 시작하기 전에 배치 하나를 눈으로 확인합니다
batch = next(iter(trainer.get_train_dataloader()))
ids, labels = batch["input_ids"][0], batch["labels"][0]
tok = trainer.processing_class
print("전체:", tok.decode(ids[:200]))
print("손실 대상:", tok.decode([i for i, l in zip(ids, labels) if l != -100][:200]))

trainer.train()

마지막 다섯 줄이 이 스크립트에서 가장 중요합니다. -100으로 마스킹된 위치는 손실에서 빠지는데, 템플릿이 조금만 어긋나도 프롬프트 전체에 손실이 걸리거나 반대로 응답 전체가 빠집니다. 손실 곡선은 두 경우 모두 그럴듯하게 내려가므로, 학습이 끝난 뒤 평가에서야 발견하게 됩니다.

Axolotl은 YAML 한 장에 모델, 데이터셋, 병렬화, LoRA 설정을 몰아넣는 방식입니다. 여러 데이터 포맷을 알아서 토큰화해 주는 것이 가장 큰 가치이고, 하위에서 Accelerate와 DeepSpeed/FSDP를 골라 씁니다.

# axolotl==0.18.0 기준. accelerate launch -m axolotl.cli.train config.yaml
base_model: meta-llama/Llama-3.1-8B
load_in_4bit: false
strict: false

datasets:
  - path: data/sft-train.jsonl
    type: chat_template
    field_messages: messages

sequence_len: 4096
sample_packing: true
gradient_checkpointing: true
bf16: auto

adapter: lora
lora_r: 32
lora_alpha: 64
lora_dropout: 0.05
lora_target_linear: true

micro_batch_size: 4
gradient_accumulation_steps: 8
num_epochs: 2
learning_rate: 1e-5
warmup_ratio: 0.03
lr_scheduler: cosine

deepspeed: deepspeed_configs/zero3_bf16.json
output_dir: ./out/axolotl-8b

LLaMA-Factory는 비슷한 범주에 웹 UI와 폭넓은 모델 지원을 더한 쪽이고, Unsloth는 커널 최적화로 단일 GPU 파인튜닝의 속도와 메모리를 개선하는 쪽입니다. Unsloth의 멀티 GPU 지원 범위는 배포 형태에 따라 달라져 왔는데, 이 글을 쓰는 시점의 정확한 정책은 확인하지 못했습니다. 멀티 GPU가 필요하시면 도입 전에 저장소 문서에서 직접 확인하십시오.

대규모 강화학습 쪽은 별도 계보입니다. verlOpenRLHF가 롤아웃 엔진(대개 vLLM이나 SGLang)과 학습 엔진(FSDP나 Megatron-Core)을 분리해 붙이는 구조를 씁니다. GRPO류를 수천 GPU 규모로 돌릴 생각이라면 TRL이 아니라 이 계보를 보셔야 합니다.

사라진 계보가 알려주는 것 — torchtune과 torchforge

이 분야에서 프레임워크를 고르는 일이 왜 위험한지 보여 주는 사례가 있습니다.

torchtune은 PyTorch 네이티브 파인튜닝 라이브러리로 시작해 좋은 평가를 받았습니다. 그런데 2025년 7월 저장소 이슈 2883번으로 활발한 개발 중단이 공지되었고, PyPI 마지막 배포는 0.6.1, 2025년 4월 7일에 멈춰 있습니다. 후속작으로 torchforge가 발표되었습니다. 그런데 2026년 8월 2일 기준으로 torchforge 저장소 최상단에는 이런 배너가 붙어 있습니다.

Development paused: Development in Forge has paused. LLM training at PyTorch is being consolidated in torchtitan.

즉 후속작의 후속작으로 흡수되는 중입니다. 이 이력에서 가져갈 교훈은 특정 라이브러리를 비난하는 것이 아니라, 상위 프레임워크의 기대 수명은 하위 계층보다 짧다는 사실입니다. PyTorch 분산 API와 Megatron-Core는 몇 년째 살아남았지만, 그 위의 편의 계층은 2년 주기로 갈아엎였습니다.

그래서 실무 규칙은 이렇게 됩니다. 프로젝트의 핵심 자산은 프레임워크 설정 파일이 아니라 데이터 파이프라인, 평가 스위트, 체크포인트 포맷에 두십시오. 이 셋을 프레임워크 밖에 두면 상위 계층이 사라져도 갈아 끼우면 그만입니다.

파인튜닝 경로와 정렬 단계 — 지금의 명칭

무엇을 학습할지 정하는 축은 두 개입니다. 하나는 어느 가중치를 건드리는가이고, 다른 하나는 어떤 신호로 학습하는가입니다.

경로학습 대상대략적 메모리언제
Full fine-tuning전체 가중치파라미터당 16바이트 + 활성화도메인 자체가 다를 때, 데이터가 충분할 때
LoRA저랭크 어댑터만기저 모델 2바이트 + 어댑터 상태대부분의 실무 파인튜닝
QLoRA4비트 기저 + 어댑터기저 0.5바이트대 + 어댑터단일 카드에서 큰 모델을 다뤄야 할 때

LoRA 계열의 변종(DoRA, rsLoRA, LoRA+, PiSSA 등)은 PEFT가 흡수해 왔습니다. 어떤 변종이 어느 PEFT 버전에 들어 있는지는 릴리스마다 다르니 문서를 보시고, 실무에서 먼저 조정할 손잡이는 변종 선택이 아니라 랭크와 대상 모듈, 그리고 학습률입니다. QLoRA를 실제 서비스에 태우는 과정은 QLoRA 프로덕션 파인튜닝에 따로 정리해 두었고, 애초에 파인튜닝이 맞는 선택인지는 RAG와 파인튜닝, 프롬프트 엔지니어링 비교를 먼저 보시는 편이 낫습니다.

정렬 단계의 현재 명칭은 이렇습니다.

  1. 지도 파인튜닝. 지시-응답 쌍으로 형식과 스타일을 잡습니다. 여기서 대부분의 체감 품질이 결정됩니다.
  2. 선호 최적화. 선호 쌍을 직접 쓰는 오프라인 계열(DPO와 그 변종)과, 정책에서 표본을 뽑아 그룹 안에서 이점을 계산하는 온라인 계열(GRPO, RLOO)로 갈립니다. TRL에 둘 다 트레이너가 있습니다.
  3. 검증 가능한 보상 기반 강화학습. 수학 정답 일치나 테스트 통과처럼 규칙으로 채점되는 보상을 쓰는 방식입니다. 학습된 보상 모델을 쓰지 않으므로 보상 해킹의 표면이 좁고, 추론 능력 학습에서 사실상 표준이 되었습니다.

명칭은 계속 늘어납니다. 새 약어가 나올 때마다 물어야 할 것은 세 가지뿐입니다. 보상이 어디서 오는가, 롤아웃이 필요한가, 참조 모델을 메모리에 올려야 하는가. 이 셋이 정해지면 필요한 GPU 수가 정해집니다.

상위 프레임워크가 방해가 되는 순간

편의 계층은 대부분의 경우 이득입니다. 다만 다음 상황에서는 비용이 이득을 넘습니다.

  • 손실 함수나 마스킹을 바꿔야 할 때. 어떤 토큰에 손실을 걸 것인가는 사후 학습 품질의 핵심 손잡이인데, 상위 프레임워크는 이것을 데이터 포맷 뒤에 숨깁니다. 손실 마스크가 실제로 어디에 걸렸는지 확인하려면 배치 하나를 디코드해서 직접 눈으로 봐야 합니다. 이 확인을 안 하고 며칠을 태우는 사고가 가장 흔합니다.
  • 병렬화 조합이 표준을 벗어날 때. 컨텍스트 병렬과 전문가 병렬을 같이 켜는 식의 조합은 상위 프레임워크의 설정 스키마가 표현하지 못하는 경우가 많습니다.
  • 디버깅이 필요할 때. 손실 스파이크의 원인을 찾으려면 그래디언트 노름, 특정 레이어의 활성화 통계, 옵티마이저 상태를 스텝 단위로 봐야 합니다. 콜백으로 뚫리면 다행이고, 안 뚫리면 프레임워크를 포크하게 됩니다.
  • 버전 고정이 불가능할 때. 여러 상위 도구가 같은 하위 라이브러리의 서로 다른 버전을 요구하는 상황이 실제로 벌어집니다. 컨테이너 이미지를 직접 만들고 잠금 파일을 커밋하는 것 말고 방법이 없습니다.
# 최소한 이 정도는 학습 시작 전에 로그로 남기십시오
python - <<'PY'
import importlib.metadata as md
for p in ("torch", "transformers", "trl", "peft", "accelerate",
          "deepspeed", "flash-attn", "datasets"):
    try:
        print(f"{p:14} {md.version(p)}")
    except md.PackageNotFoundError:
        print(f"{p:14} (미설치)")
PY
nvidia-smi --query-gpu=name,driver_version,memory.total --format=csv
python -c "import torch; print('nccl', torch.cuda.nccl.version())"

마치며 — 층을 고르되 층에 갇히지 마십시오

스택을 고르는 기준은 "무엇이 가장 좋은가"가 아니라 "우리가 어느 층까지 직접 책임질 것인가"입니다. 사전학습을 한다면 Megatron-Core나 PyTorch 분산 API까지 내려가야 하고, 사내 데이터로 파인튜닝만 한다면 TRL이나 Axolotl 위에서 끝내는 것이 합리적입니다.

다만 어느 층을 고르든 데이터 파이프라인과 평가 스위트, 체크포인트 포맷은 프레임워크 바깥에 두십시오. torchtune에서 torchforge로, 다시 torchtitan으로 이어진 2년의 이력이 말해 주는 것이 그것입니다. 프레임워크는 갈아 끼우는 부품이고, 갈아 끼울 수 있게 만들어 두는 것이 설계입니다. 병렬화 자체의 원리는 멀티 GPU 학습의 네 가지 병렬화에 정리해 두었습니다.