Fine-tuning an LLM used to mean choosing between a framework that hid PyTorch from you and a notebook that hid correctness from you. Torchtune, released by Meta’s PyTorch team in April 2024 under the BSD 3-Clause license, took a third path: native PyTorch recipes you can read in an afternoon, designed to be copied and edited rather than configured into submission. In roughly a year and a half, its 150-plus contributors shipped every major post-training method, from supervised finetuning through knowledge distillation, DPO, PPO, GRPO, and quantization-aware training, alongside from-scratch implementations of twenty model families including Llama4, Gemma2, Phi4, and Qwen3, and the memory numbers it published became the industry’s reference points: an 8B model fully finetuned on a single 4090 at 18.9 GiB, or QLoRA’d at 7.4 GiB. Development wound down in 2025, which makes the repository something better than a tool now: a completed, citable textbook. As always in this series, what follows is an educational tour of published source code.
The architecture is three flat layers with no framework between them. At the top, standalone recipe scripts in recipes/ are honest Python programs, each with a setup method that builds its objects and a train loop you can read line by line; YAML configs feed them through a thin OmegaConf layer, and the tune CLI with its five subcommands launches everything through torchrun. In the middle sits the torchtune package: a from-scratch transformer built on nn.Modules, an adapter shelf, a dataset layer, and the loss functions each method needs. At the bottom, a utilities shelf handles checkpointing across three checkpoint formats, FSDP2 wrapping, activation memory tricks, metric logging, and inference generation. Nothing is a plugin, nothing is registered in a registry, and every override happens through config dictionaries that map one-to-one onto function arguments. Reading the overview from left to right follows a YAML file all the way to GPU memory.
Overview of torchtune: the tune CLI loads YAML configs that instantiate recipes, recipes compose the TransformerDecoder core with LoRA adapters, the model zoo and dataset builders supply the objects, and training infrastructure handles checkpointing, distribution, and losses.
Reading the overview from left to right:
- TransformerDecoder at line 331 of 689 lines is the core, with self-attention layers at 17 and cross-attention at 144.
- MultiHeadAttention at line 18 of 303 lines handles GQA, kv caching, and masks.
- LoRALinear at line 27 of 320 lines is the adapter contract, with QATLoRALinear at 150.
- The full finetune recipe is 718 lines: class at 39, setup at 235, train loop at 571.
- The distributed recipe spans 1169 lines with FSDP2 sharding and multinode support.
- The checkpointers cover 1478 lines in four formats: TorchTune at 122, HF at 369, Meta at 1039, Distributed at 1305.
- DPOLoss at 181 lines implements the preference objective over policy and reference logprobs.
- Llama3 builders in 240 lines wire 8B and 70B variants, with component builders at 510 lines.
Why You Need This
The first reason is that the recipes are the best documentation of post-training that exists in executable form. The single-device full finetune recipe is 718 lines including comments, and its train loop at line 571 shows exactly one epoch’s worth of decisions: gradient accumulation, clipping, activation checkpointing toggles, loss chunking, and checkpoint cadence, each guarded by a config field you can trace from the YAML. When the README tabulates how packed datasets raise throughput 238 percent or how activation checkpointing cuts active memory 42 percent, those numbers are produced by code paths you can walk in this repository, not by a benchmark harness you must take on faith. If you have ever wondered what your training framework actually does between your loss and your optimizer step, this is the rare place where the answer is not a cgo file or a registered plugin but a for loop you can annotate.
The second reason is the memory engineering, which treats every GiB as a budget line. The module shelf implements the full ladder from the README’s own table: sequence packing in the datasets layer, torch compile integration, chunked cross entropy that avoids materializing logits for the whole sequence, activation checkpointing and CPU offloading helpers, optimizer state fused into backward, 8-bit AdamW via bitsandbytes, and then the adapter ladder, LoRA at 8.5 GiB and QLoRA at 4.6 GiB for the same 3B model. The components are individually swappable, and the recipes compose them in the order the table recommends. For anyone deploying finetuning on constrained hardware, the value is not just the numbers but the fact that each technique is isolated in a file you can lift into your own trainer.
The third reason is interoperability as a first-class feature. The checkpointing layer speaks three dialects natively: torchtune’s own format, Hugging Face transformers, and Meta’s consolidated format, so a model you finetune here can be served by vLLM or the HF ecosystem without conversion scripts. Dataset builders consume Hugging Face datasets directly, evaluation runs through EleutherAI’s LM Eval Harness in a recipe, quantization goes through torchao, distributed training uses FSDP2, and the experimental GRPO stack integrates vLLM as a rollout engine with worker processes for weight syncing. The library treats the ecosystem as its user interface, which is a design lesson worth stealing even if you never finetune anything.
The detailed map: recipe scripts and config plumbing, the transformer module stack, the model zoo, dataset builders, training infrastructure, and the RLHF stack with its experimental workers.
Start where every run starts: the CLI and config. tune.py wires five subcommands in 56 lines, and run.py hands the YAML to the config layer, where _parse.py at 101 lines merges the recipe’s default config with your overrides, _validate.py checks required fields, and _instantiate.py turns dotted class paths like torchtune.models.llama3.llama3_8b into live objects with the remaining fields as keyword arguments. That is the entire magic: a config value loss=torchtune.modules.loss.CEWithChunkedOutputLoss becomes a Python object because one function splits the string and imports it. The recipes then read like scripts, and the flagship full finetune recipe at 718 lines builds model, tokenizer, optimizer, loss, and dataloader in setup at line 235, runs _loss_step at 551 which chunks the cross entropy and masks labels, and saves through the checkpointer at epoch and step boundaries; its distributed sibling at 1169 lines adds FSDP2 wrapping and multinode flags, and the LoRA variant at 753 lines swaps the model builder for lora_llama3_8b and checkpoints adapter state only.
The module layer is small enough to memorize and complete enough to trust. TransformerDecoder at line 331 assembles token embeddings, rotary positions from the 258-line position_embeddings module, scaling layers, and the self-attention layer at line 17, with a cross-attention sibling at 144 for vision-to-text models; MultiHeadAttention at 303 lines implements grouped query attention and feeds the 116-line KVCache during decode, and FeedForward at 54 lines plus rms_norm at 60 lines round out the block. The adapter shelf starts with LoRALinear at line 27, an AdapterModule whose A and B matrices carry all learning while the base weight freezes, extended by QATLoRALinear at 150 for fake-quantized training, a 206-line DoRA variant, and the 70-line NF4 linear for QLoRA’s 4-bit dtype. The loss shelf holds chunked cross entropy at 102 lines, the 440-line KD losses for distillation, and the preference losses that power the RLHF stack.
The model and data layers show what standardization buys. Twenty model family directories, from llama3 with its 240 lines of builders to llama4’s mixture-of-experts Scout, all construct the same TransformerDecoder with different component builders, which is why a new model lands as one directory of builders plus one config folder. The dataset layer’s fourteen builders, led by _instruct.py at 155 lines, share message transforms and tokenization so that instruct, chat, and preference data all produce the same dict of tensors; _packed.py implements the packed-batch strategy behind the README’s biggest throughput number, and _preference.py builds the chosen-and-rejected pairs that DPO consumes. Training infrastructure completes the picture: _checkpointer.py dedicates 1478 lines to the four format classes so weights move in and out of HF and Meta formats losslessly, _distributed.py’s 902 lines wrap FSDP2, memory.py’s 321 lines centralize the activation tricks, metric_logging.py’s 647 lines put disk, Weights and Biases, and Comet behind one interface, and generation’s 443-line inference loop with kv cache closes the loop for eval and rollout.
The RLHF stack is where recipes compose the hardest, and it rewards reading twice. The four DPO recipe variants drive DPOLoss at 181 lines, which needs both the policy and a frozen reference model’s logprobs over chosen and rejected sequences; PPO at 1246 recipe lines runs a full finetune with a value head and a reward model, using the 122-line PPOLoss and the reward helpers at 238 lines; and the dev/ directory carries the newest experiments, a GRPO implementation with its own generation and gsm8k task modules, plus an RL worker stack where vLLM serves as a parameter server, datacollectors assemble trajectories, and weight updaters sync parameters between training and rollout processes. QAT recipes tie back into the adapter shelf through QATLoRALinear, so even quantization-aware training follows the same recipe skeleton, and the quantize.py script plus eleuther_eval recipe finish the lifecycle after training.
From Install to a Finetune
The library tracks the latest stable PyTorch, currently 2.6.0, and leans on torchvision and torchao:
pip install torch torchvision torchao
pip install torchtune
tune --help
Download a gated model with your HF token, then finetune it with LoRA on one GPU:
tune download meta-llama/Meta-Llama-3.1-8B-Instruct \
--output-dir /tmp/Meta-Llama-3.1-8B-Instruct \
--ignore-patterns "original/consolidated.00.pth" \
--hf-token <HF_TOKEN>
tune run lora_finetune_single_device --config llama3_1/8B_lora_single_device
Two GPUs is one flag away, since the CLI fronts torchrun:
tune run --nproc_per_node 2 full_finetune_distributed --config llama3_1/8B_full
Everything is overridable from the command line, so batch_size=8, enable_activation_checkpointing=True, or max_steps_per_epoch=128 append directly to the command, and tune cp copies any config locally for heavier edits. To try the memory ladder yourself, run the QLoRA config with packed datasets and compile enabled, then watch peak memory fall to a few GiB while throughput climbs toward the README’s 284 percent number.
Honest limits: the project is no longer actively maintained, so treat it as a reference implementation rather than a dependency you expect updates from, and its tests target specific PyTorch versions. The recipes favor clarity over peak throughput, so production teams will leave some performance on the table compared to bespoke trainers. The model zoo, while broad, is text and vision only, and the experimental RL stack in dev/ is explicitly subject to change. And the CLI’s config-override power means a typo can silently train the wrong model, so validate configs before long runs. Read a recipe top to bottom, then the transformer, then the checkpointer; that order teaches you both the library and the anatomy of LLM finetuning itself. Enjoyed this post? Never miss out on future posts by following us