What’s one thing you learned? What’s still confusing?
Code & Domain LLMs: When Specialists Beat Generalists
GitHub Copilot crossed $400M ARR in 2024, powered for years by a Codex model that started as a research curiosity.
LLM Data & Pretraining: Common Crawl to FineWeb
GPT-4 will not tell you what it was trained on.
Distributed Training: DP, TP, PP, FSDP & Sequence Parallelism
A 70-billion-parameter model in fp32 needs 280 GB just for weights.
Interactive Labs for This Track
Tokenizer
How does AI read? First it breaks text into tokens — type a sentence and see it split into pieces
Embeddings Explorer
Words live in a space where similar meanings are close together — explore king - man + woman = queen
Transformer Attention
Watch data flow through a transformer step by step — the architecture behind ChatGPT
Ask questions, share insights
For a pre-trained weight matrix W₀ ∈ ℝ^(d×k), LoRA learns a residual:
W = W₀ + ΔW = W₀ + A · B
Where:
h = W₀x + (A·B)x = W₀x + A(Bx)W_merged = W₀ + A·B, adding zero latency overhead.Rank is the core tradeoff knob:
| Rank | Trainable Params (7B, q/v proj) | Use Case |
|---|---|---|
| r = 1 | ~500K | Style transfer, tiny domain shift |
| r = 8 | ~4M | General instruction tuning (sweet spot) |
| r = 16 | ~8M | Domain adaptation, function calling |
| r = 64 | ~32M | Complex reasoning, heavy domain shift |
| r = 256 | ~128M | Approaching full fine-tune quality |
Higher rank captures more expressive updates but approaches full fine-tuning cost. Empirically, r = 8 matches full fine-tuning on most instruction-following tasks while using 0.1% of the parameters.
(α/r) · A·B. Alpha controls the effective learning rate of the adapter:lora_alpha = 2 * lora_r as a starting point and tune from there. A high alpha with a low rank can destabilize training.Not all layers benefit equally from LoRA:
| Layer Type | Adapt? | Reason |
|---|---|---|
| Query (Wq) | Yes | Attention patterns are task-sensitive |
| Value (Wv) | Yes | Value projections carry content |
| Key (Wk) | Sometimes | Less impactful than Q/V in practice |
| Output (Wo) | Sometimes | Useful for multi-task settings |
| FFN up/down | Advanced | Increases params significantly |
| Embedding layer | Rarely | Only if vocabulary changes |
Most implementations default to adapting only Q and V projections, which covers 80% of LoRA's effectiveness.
LoRA still requires the frozen base model to fit in GPU memory — a 65B model needs ~130 GB in fp16, far beyond a single A100 (80 GB). QLoRA (Dettmers et al., 2023) solves this with aggressive quantization layered under LoRA adapters.
Standard int4 quantization distributes bit patterns uniformly across the value range, wasting precision in the distribution tails. NF4 (NormalFloat 4) is information-theoretically optimal for normally distributed weights — it places quantization levels to minimize expected squared quantization error for Gaussian-distributed values.
The 16 NF4 levels are positioned at the 1/17, 2/17, ..., 16/17 quantiles of a unit normal distribution. For transformer weights, which are approximately N(0, σ²), this recovers ~0.1% more accuracy than symmetric int4 at the same bit width.
The quantization constants themselves (one per block of 64 weights) are stored in fp32. For a 65B model, these constants total ~2 GB — not free. QLoRA quantizes the constants too (a second round of quantization), saving ~0.5 GB with negligible accuracy loss. This is "double quantization."
| Configuration | GPU Memory | Accuracy vs Full FT |
|---|---|---|
| Full fine-tune (fp16) | 2× model size | Baseline |
| LoRA (fp16 base) | 1.2× model size | -0.5% avg |
| QLoRA (NF4 base) | ~0.5× model size | -0.8% avg |
QLoRA makes Llama-3 65B fine-tuning fit on a single 80 GB A100. On consumer hardware (24 GB RTX 4090), it enables 13B fine-tuning that would otherwise need 4× A100s.
Parameter-Efficient Fine-Tuning (PEFT) is the umbrella for all methods that fine-tune a small number of parameters while keeping the base model frozen. LoRA is the dominant approach, but three others appear frequently in papers and production systems.
Instead of learning additive weight residuals, IA³ learns element-wise scaling vectors that multiply activations at specific points (key, value, and FFN intermediate). With only 3 vectors per transformer layer, IA³ uses 10–100× fewer parameters than LoRA but achieves competitive performance on classification tasks. It does not merge cleanly into weights (must be applied at inference), adding a small latency overhead.
Prefix tuning prepends k trainable "virtual tokens" to the key and value sequences of every attention layer. These prefix vectors are optimized while the model is frozen. At r = 10 (10 prefix tokens), it adds ~0.1% parameters but:
Prompt tuning adds trainable embeddings only to the input layer (not every attention layer). With fewer parameters than prefix tuning, it's the most parameter-efficient PEFT method — and the most sensitive to initialization. Performance degrades sharply below ~7B parameters; for smaller models, LoRA is consistently better.
| Method | Trainable Params | Memory Overhead | Merge to Weights | Inference Latency | Best For |
|---|---|---|---|---|---|
| Full FT | 100% | 3–4× model | — | None | Highest quality, budget available |
| LoRA | 0.1–1% | +5–10% | Yes | None after merge | General purpose, most use cases |
| QLoRA | 0.1% + quant | 0.4–0.5× model | Yes | None after merge | Large models on limited hardware |
| IA³ | ~0.01% | Minimal | Partial | Tiny | Classification, low-compute inference |
| Prefix Tuning |
Once you have multiple LoRA adapters (or multiple full fine-tunes), merging combines their capabilities without serving multiple models. The key insight: neural network weights live in a high-dimensional space; models fine-tuned from the same base often lie in a "linear mode connectivity" basin where interpolation produces valid models.
The simplest merge: average corresponding weights across models.
W_merged = (1/n) Σᵢ Wᵢ
Wortsman et al. (2022) showed that averaging fine-tuned variants of the same architecture improves accuracy by 2–3% over any single model on ImageNet. This "model soup" works because averaging in weight space approximates ensemble averaging in function space — but only when models share a base and the loss landscape between them is convex (they're in the same "basin").
Linear interpolation through weight space can exit the high-probability region of the loss landscape. SLERP interpolates along the surface of a hypersphere — respecting the "norm" of each weight vector rather than pulling toward the origin.
For two weight vectors w₀ and w₁ and interpolation factor t ∈ [0, 1]:
SLERP(w₀, w₁, t) = sin((1-t)θ)/sin(θ) · w₀ + sin(tθ)/sin(θ) · w₁
where θ = arccos(w₀·w₁ / (|w₀||w₁|)).
SLERP produces smoother interpolation paths than linear averaging for high-dimensional weight vectors, resulting in 5–15% better merged model quality on benchmarks. It's the default method in mergekit for two-model merges.
When merging more than two models, individual weight updates may conflict — one fine-tune pushes a weight positive, another pushes it negative, and averaging cancels both improvements. TIES-Merging handles this in three steps:
This typically recovers 80–90% of each model's individual capability in the merged result, versus 50–70% for naive averaging when tasks are diverse.
DARE takes a simpler probabilistic approach: randomly drop a fraction p of the delta parameters (ΔW = W - W₀), then rescale the survivors by 1/(1-p) to preserve expected magnitude.
mask = torch.bernoulli(torch.ones_like(delta_W) * (1 - p))
delta_W_dare = mask * delta_W / (1 - p)DARE works because fine-tuned deltas are sparse in practice — most of the "meaningful" update is concentrated in a small fraction of parameters. Dropping 90–99% of delta parameters and rescaling often preserves 95%+ of task performance, making it a powerful tool for reducing interference before TIES-Merging.
In practice, DARE is typically applied before TIES — "DARE-TIES" is the dominant state-of-the-art merging strategy for multi-model combinations as of 2025.
When using SLERP, the t parameter controls how much each model contributes. A practical heuristic:
You're fine-tuning LLaMA-3-8B for a specialized coding task. Your colleague suggests r=256 because 'more rank = better.' What's the strongest argument against this?
| 0.1–1% |
| +5% |
| No |
| Small |
| Structured generation |
| Prompt Tuning | ~0.001% | Minimal | No | Minimal | Very large models only |
import torch
from transformers import AutoModelForCausalLM
from peft import PeftModel
def slerp(w0: torch.Tensor, w1: torch.Tensor, t: float) -> torch.Tensor:
"""Spherical linear interpolation between two weight tensors."""
# Flatten to 1D for dot product, then reshape back
orig_shape = w0.shape
w0_flat = w0.flatten().float()
w1_flat = w1.flatten().float()
# Compute angle between the vectors
cos_theta = torch.dot(w0_flat, w1_flat) / (
torch.norm(w0_flat) * torch.norm(w1_flat) + 1e-8
)
cos_theta = cos_theta.clamp(-1.0, 1.0)
theta = torch.acos(cos_theta)
# If vectors are nearly parallel, fall back to linear interpolation
if theta.abs() < 1e-4:
return ((1 - t) * w0 + t * w1).reshape(orig_shape)
sin_theta = torch.sin(theta)
coeff0 = torch.sin((1 - t) * theta) / sin_theta
coeff1 = torch.sin(t * theta) / sin_theta
return (coeff0 * w0_flat + coeff1 * w1_flat).reshape(orig_shape)
def merge_lora_adapters_slerp(
base_model_name: str,
adapter_path_a: str,
adapter_path_b: str,
output_path: str,
t: float = 0.5,
):
"""
Merge two LoRA adapters trained on the same base model using SLERP.
Args:
base_model_name: HuggingFace model ID for the base model
adapter_path_a: Path to first LoRA adapter (peft format)
adapter_path_b: Path to second LoRA adapter (peft format)
output_path: Where to save the merged model
t: Interpolation factor (0.0 = all model A, 1.0 = all model B)
"""
print("Loading base model...")
base_model = AutoModelForCausalLM.from_pretrained(
base_model_name,
torch_dtype=torch.float32, # Use fp32 for clean arithmetic
device_map="cpu",
)
# Merge adapter A into a copy, extract its weights
print("Materializing adapter A weights...")
model_a = PeftModel.from_pretrained(base_model, adapter_path_a)
model_a = model_a.merge_and_unload() # Fuses A·B into W₀
# Merge adapter B into another copy
print("Materializing adapter B weights...")
base_model_b = AutoModelForCausalLM.from_pretrained(
base_model_name,
torch_dtype=torch.float32,
device_map="cpu",
)
model_b = PeftModel.from_pretrained(base_model_b, adapter_path_b)
model_b = model_b.merge_and_unload()
# SLERP between corresponding weight matrices
print(f"SLERP interpolation at t={t}...")
state_dict_a = model_a.state_dict()
state_dict_b = model_b.state_dict()
merged_state_dict = {}
for key in state_dict_a:
w_a = state_dict_a[key]
w_b = state_dict_b[key]
if w_a.dtype in (torch.float32, torch.float16, torch.bfloat16):
# SLERP for floating-point weight matrices
merged_state_dict[key] = slerp(w_a, w_b, t).to(w_a.dtype)
else:
# For embeddings or integer tensors, use model A's values
merged_state_dict[key] = w_a
# Load merged weights and save
print("Saving merged model...")
model_a.load_state_dict(merged_state_dict)
model_a.save_pretrained(output_path)
print(f"Merged model saved to {output_path}")
# Example usage:
# merge_lora_adapters_slerp(
# base_model_name="meta-llama/Meta-Llama-3-8B",
# adapter_path_a="./lora-adapter-coding",
# adapter_path_b="./lora-adapter-math",
# output_path="./merged-coding-math",
# t=0.5,
# )
# ── DARE preprocessing before TIES ─────────────────────────────────────────
def dare_delta(base_weights: dict, finetuned_weights: dict, p: float = 0.9) -> dict:
"""
Apply DARE (Drop and Rescale) to a delta weight dict.
Args:
base_weights: state_dict of the base (frozen) model
finetuned_weights: state_dict of the fine-tuned model
p: fraction of delta parameters to randomly drop (0.9 = drop 90%)
Returns:
Pruned and rescaled delta dict
"""
dare_deltas = {}
for key in finetuned_weights:
if key not in base_weights:
dare_deltas[key] = finetuned_weights[key]
continue
delta = finetuned_weights[key].float() - base_weights[key].float()
if delta.dtype in (torch.float32,):
# Random binary mask: keep each element with probability (1-p)
mask = torch.bernoulli(torch.ones_like(delta) * (1 - p))
# Rescale survivors to preserve expected magnitude
dare_deltas[key] = (mask * delta / (1 - p)).to(finetuned_weights[key].dtype)
else:
dare_deltas[key] = finetuned_weights[key]
return dare_deltas