An SLM is an AI model trained to understand and generate text — just like GPT-4 — but with dramatically fewer parameters (1M to 7B). This makes them fast, cheap, and runnable on everyday hardware.
SLM Parameters
1M – 7B
Fits on a laptop or phone
LLM Parameters (GPT-4)
~1.8T
Requires a GPU datacenter
SLM Inference Cost
≈ $0
Runs locally, no API fees
LLM Inference Cost
$$$
Per million tokens billed
Simple Analogy
Think of an LLM as a professional encyclopedia — knows everything but lives in a library. An SLM is like a pocket handbook — knows what you need, fits in your bag, always with you.
2026-05-05 18:03
7 Ways to Create an SLM
Each approach has a different starting point, cost, and target use case.
1
Train from Scratch
Full control — you design everything
Design a compact transformer (fewer layers, smaller embeddings) and train it on a curated corpus using next-token prediction.
Simple Example
Python
— Tiny Transformer from scratch
import torch
import torch.nn as nn
class TinySLM(nn.Module):
"""A minimal 2-layer transformer SLM."""
def __init__(self, vocab=5000, dim=128, heads=4, layers=2):
super().__init__()
self.embed = nn.Embedding(vocab, dim)
self.pos_enc = nn.Embedding(512, dim) # positional
encoder_layer = nn.TransformerEncoderLayer(
d_model=dim, nhead=heads,
dim_feedforward=dim*4, dropout=0.1, batch_first=True
)
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=layers)
self.head = nn.Linear(dim, vocab)
def forward(self, x):
positions = torch.arange(x.size(1), device=x.device)
x = self.embed(x) + self.pos_enc(positions)
x = self.transformer(x)
return self.head(x) # logits over vocabulary
# Create the model — only ~1M parameters!
model = TinySLM(vocab=5000, dim=128, heads=4, layers=2)
total = sum(p.numel() for p in model.parameters())
print(f"Parameters: {total:,}") # ~1,300,000
# Training loop (simplified)
optimizer = torch.optim.Adam(model.parameters(), lr=3e-4)
loss_fn = nn.CrossEntropyLoss()
for step, (x, y) in enumerate(dataloader):
logits = model(x)
loss = loss_fn(logits.view(-1, 5000), y.view(-1))
loss.backward()
optimizer.step()
optimizer.zero_grad()
if step % 100 == 0:
print(f"Step {step}, Loss: {loss.item():.4f}")
Pros
Full architecture control
No licensing restrictions
Tailor to domain from day 1
❌ Cons
Needs large dataset
High compute cost
Months of training time
2026-05-05 18:04
Knowledge Distillation
Teacher LLM → Student SLM
Train a small student model to mimic the probability distributions of a large teacher model. The student learns "soft" knowledge, not just hard answers.
import torch, torch.nn as nn, torch.nn.functional as F
def distillation_loss(student_logits, teacher_logits, true_labels,
temperature=4.0, alpha=0.7):
"""
temperature : higher = softer teacher distribution
alpha : weight of distillation vs hard-label loss
"""
# Soften both distributions with temperature
soft_teacher = F.softmax(teacher_logits / temperature, dim=-1)
soft_student = F.log_softmax(student_logits / temperature, dim=-1)
# KL divergence (distillation loss)
kl_loss = F.kl_div(soft_student, soft_teacher, reduction='batchmean')
kl_loss *= (temperature ** 2) # scale back
# Standard cross-entropy (hard labels)
ce_loss = F.cross_entropy(student_logits, true_labels)
return alpha * kl_loss + (1 - alpha) * ce_loss
# Usage
teacher.eval()
with torch.no_grad():
teacher_out = teacher(input_ids) # big model, frozen
student_out = student(input_ids) # small model, training
loss = distillation_loss(student_out, teacher_out, labels)
loss.backward()
optimizer.step()
Real-World Example
DistilBERT — distilled from BERT-large (336M params) to 66M params. It runs 60% faster while retaining 97% of BERT's accuracy on GLUE benchmarks.
✅ Pros
Student learns reasoning patterns
Works for any task
❌ Cons
Needs access to teacher
Distillation itself needs GPU
2026-05-05 18:07
3
Model Pruning
Cut the fat from a big model
Remove weights, neurons, or attention heads that contribute least to model output. A 70B model can shed 40–60% of weights with minimal accuracy loss.
Pretrained LLM
→Identify unimportant weights
→Remove / zero them
Fine-tune to recover
→
Smaller SLM
Simple Example
Python
— Magnitude pruning with PyTorch
import torch
import torch.nn.utils.prune as prune
# --- Structured pruning: remove entire neurons ---
def prune_model(model, amount=0.4):
"""Remove 40% of weights by magnitude across all Linear layers."""
for name, module in model.named_modules():
if isinstance(module, torch.nn.Linear):
prune.l1_unstructured(module, name='weight', amount=amount)
return model
# --- Check sparsity ---
def sparsity(model):
zeros = total = 0
for p in model.parameters():
zeros += (p == 0).sum().item()
total += p.numel()
return zeros / total * 100
# Apply pruning
model = prune_model(my_pretrained_model, amount=0.4)
print(f"Model sparsity: {sparsity(model):.1f}%") # → ~40.0%
# Fine-tune briefly to recover accuracy
for batch in fine_tune_data:
loss = model(batch)
loss.backward()
optimizer.step()
✅ Pros
Start from powerful base
Retains general knowledge
❌ Cons
Unstructured sparsity hard to accelerate
May need iterative pruning
2026-05-05 18:09
4
Quantization
Shrink numbers → shrink model
Reduce the precision of model weights from 32-bit floats down to 8-bit or 4-bit integers. A 7B model in 4-bit fits in 4 GB RAM.
Format Bits Size vs FP32 Quality
FP32 32 1× (baseline) Perfect
BF16 16 2× smaller Near- perfect
INT8 8 4× smaller Very Good
INT4(GPTQ) 4 8× smaller Good
2-bit 2 16× smaller Degraded
Simple Example
Python
— 4-bit quantization with bitsandbytes
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
# Configure 4-bit quantization
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16, # compute in BF16
Take a pretrained SLM (e.g., Llama 3.2-1B) and inject tiny trainable matrices into attention layers. Only ~0.1% of parameters are trained — the rest stay frozen.
How LoRA works
Instead of updating weight matrix W (millions of values), LoRA learns two tiny matrices A and B where A × B ≈ ΔW. This takes 1000× less memory.
Simple Example
Python
— QLoRA fine-tuning on domain data
from transformers import AutoModelForCausalLM, TrainingArguments, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, TaskType
Use a large LLM to generate high-quality, "textbook-level" training examples, then train a small model on this curated data. This is exactly how Microsoft Phi-1, Phi-2, Phi-3 were built.
An SLM is an AI model trained to understand and generate text — just like GPT-4 — but with dramatically fewer parameters (1M to 7B). This makes them fast, cheap, and runnable on everyday hardware.
SLM Parameters
1M – 7B
Fits on a laptop or phone
LLM Parameters (GPT-4)
~1.8T
Requires a GPU datacenter
SLM Inference Cost
≈ $0
Runs locally, no API fees
LLM Inference Cost
$$$
Per million tokens billed
Simple Analogy
Think of an LLM as a professional encyclopedia — knows everything but lives in a library. An SLM is like a pocket handbook — knows what you need, fits in your bag, always with you.
7 Ways to Create an SLM
Each approach has a different starting point, cost, and target use case.
1
Train from Scratch
Full control — you design everything
Design a compact transformer (fewer layers, smaller embeddings) and train it on a curated corpus using next-token prediction.
Simple Example
Python
— Tiny Transformer from scratch
import torch import torch.nn as nn class TinySLM(nn.Module): """A minimal 2-layer transformer SLM.""" def __init__(self, vocab=5000, dim=128, heads=4, layers=2): super().__init__() self.embed = nn.Embedding(vocab, dim) self.pos_enc = nn.Embedding(512, dim) # positional encoder_layer = nn.TransformerEncoderLayer( d_model=dim, nhead=heads, dim_feedforward=dim*4, dropout=0.1, batch_first=True ) self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=layers) self.head = nn.Linear(dim, vocab) def forward(self, x): positions = torch.arange(x.size(1), device=x.device) x = self.embed(x) + self.pos_enc(positions) x = self.transformer(x) return self.head(x) # logits over vocabulary # Create the model — only ~1M parameters! model = TinySLM(vocab=5000, dim=128, heads=4, layers=2) total = sum(p.numel() for p in model.parameters()) print(f"Parameters: {total:,}") # ~1,300,000 # Training loop (simplified) optimizer = torch.optim.Adam(model.parameters(), lr=3e-4) loss_fn = nn.CrossEntropyLoss() for step, (x, y) in enumerate(dataloader): logits = model(x) loss = loss_fn(logits.view(-1, 5000), y.view(-1)) loss.backward() optimizer.step() optimizer.zero_grad() if step % 100 == 0: print(f"Step {step}, Loss: {loss.item():.4f}")Pros
❌ Cons
Knowledge Distillation
Teacher LLM → Student SLM
Train a small student model to mimic the probability distributions of a large teacher model. The student learns "soft" knowledge, not just hard answers.
Loss = α · CrossEntropy(predictions, true_labels) + (1−α) · KL_Divergence(teacher_probs ‖ student_probs)
Simple Example
Python
— Distillation training loop
import torch, torch.nn as nn, torch.nn.functional as F def distillation_loss(student_logits, teacher_logits, true_labels, temperature=4.0, alpha=0.7): """ temperature : higher = softer teacher distribution alpha : weight of distillation vs hard-label loss """ # Soften both distributions with temperature soft_teacher = F.softmax(teacher_logits / temperature, dim=-1) soft_student = F.log_softmax(student_logits / temperature, dim=-1) # KL divergence (distillation loss) kl_loss = F.kl_div(soft_student, soft_teacher, reduction='batchmean') kl_loss *= (temperature ** 2) # scale back # Standard cross-entropy (hard labels) ce_loss = F.cross_entropy(student_logits, true_labels) return alpha * kl_loss + (1 - alpha) * ce_loss # Usage teacher.eval() with torch.no_grad(): teacher_out = teacher(input_ids) # big model, frozen student_out = student(input_ids) # small model, training loss = distillation_loss(student_out, teacher_out, labels) loss.backward() optimizer.step()Real-World Example
DistilBERT — distilled from BERT-large (336M params) to 66M params. It runs 60% faster while retaining 97% of BERT's accuracy on GLUE benchmarks.
✅ Pros
❌ Cons
3
Model Pruning
Cut the fat from a big model
Remove weights, neurons, or attention heads that contribute least to model output. A 70B model can shed 40–60% of weights with minimal accuracy loss.
Pretrained LLM
→Identify unimportant weights
→Remove / zero them
Fine-tune to recover
→
Smaller SLM
Simple Example
Python
— Magnitude pruning with PyTorch
import torch import torch.nn.utils.prune as prune # --- Structured pruning: remove entire neurons --- def prune_model(model, amount=0.4): """Remove 40% of weights by magnitude across all Linear layers.""" for name, module in model.named_modules(): if isinstance(module, torch.nn.Linear): prune.l1_unstructured(module, name='weight', amount=amount) return model # --- Check sparsity --- def sparsity(model): zeros = total = 0 for p in model.parameters(): zeros += (p == 0).sum().item() total += p.numel() return zeros / total * 100 # Apply pruning model = prune_model(my_pretrained_model, amount=0.4) print(f"Model sparsity: {sparsity(model):.1f}%") # → ~40.0% # Fine-tune briefly to recover accuracy for batch in fine_tune_data: loss = model(batch) loss.backward() optimizer.step()✅ Pros
❌ Cons
4
Quantization
Shrink numbers → shrink model
Reduce the precision of model weights from 32-bit floats down to 8-bit or 4-bit integers. A 7B model in 4-bit fits in 4 GB RAM.
Format Bits Size vs FP32 Quality
FP32 32 1× (baseline) Perfect
BF16 16 2× smaller Near- perfect
INT8 8 4× smaller Very Good
INT4(GPTQ) 4 8× smaller Good
2-bit 2 16× smaller Degraded
Simple Example
Python
— 4-bit quantization with bitsandbytes
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
# Configure 4-bit quantization
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.bfloat16, # compute in BF16
bnb_4bit_use_double_quant=True, # nested quantization
bnb_4bit_quant_type="nf4" # NormalFloat4 format
)
# Load a 7B model in ~4 GB instead of ~28 GB
model = AutoModelForCausalLM.from_pretrained(
"microsoft/phi-3-mini-4k-instruct",
quantization_config=quant_config,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("microsoft/phi-3-mini-4k-instruct")
# Run inference — now fits on a consumer laptop!
inputs = tokenizer("Explain gravity in simple terms:", return_tensors="pt")
output = model.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(output[0], skip_special_tokens=True))
5
Fine-Tuning with LoRA / QLoRA
Domain-specialize a base SLM cheaply
Take a pretrained SLM (e.g., Llama 3.2-1B) and inject tiny trainable matrices into attention layers. Only ~0.1% of parameters are trained — the rest stay frozen.
How LoRA works
Instead of updating weight matrix W (millions of values), LoRA learns two tiny matrices A and B where A × B ≈ ΔW. This takes 1000× less memory.
Simple Example
Python
— QLoRA fine-tuning on domain data
from transformers import AutoModelForCausalLM, TrainingArguments, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, TaskType
from trl import SFTTrainer
from datasets import load_dataset
import torch
# Step 1: Load base SLM in 4-bit (QLoRA)
bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_compute_dtype=torch.bfloat16)
model = AutoModelForCausalLM.from_pretrained(
"TinyLlama/TinyLlama-1.1B-Chat-v1.0", quantization_config=bnb
)
# Step 2: Attach LoRA adapters (only these will train)
lora_config = LoraConfig(
r=16, # rank of the low-rank matrices
lora_alpha=32, # scaling factor
target_modules=["q_proj", "v_proj"], # which layers to adapt
lora_dropout=0.05,
task_type=TaskType.CAUSAL_LM
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters() # → ~0.1% trainable!
# Step 3: Fine-tune on your data (e.g., medical Q&A)
dataset = load_dataset("json", data_files="my_domain_data.jsonl")
trainer = SFTTrainer(
model=model,
train_dataset=dataset["train"],
dataset_text_field="text",
args=TrainingArguments(output_dir="./slm-finetuned",
num_train_epochs=3, per_device_train_batch_size=4)
)
trainer.train()
model.save_pretrained("./my-domain-slm")
6
Synthetic Data Training (Phi Approach)
Use GPT-4 to teach a tiny model
Use a large LLM to generate high-quality, "textbook-level" training examples, then train a small model on this curated data. This is exactly how Microsoft Phi-1, Phi-2, Phi-3 were built.
Design prompts for GPT-4
→
Generate synthetic corpus
→
Filter for quality
→
Train small model
→
Phi-3 level SLM
Simple Example
Python
— Generating synthetic training data via OpenAI
from openai import OpenAI
import json, random
client = OpenAI()
TOPICS = ["gravity", "photosynthesis", "recursion", "neural networks"]
def generate_sample(topic):
"""Ask GPT-4 to write a textbook explanation + question."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content":
f"Write a clear, concise textbook paragraph about '{topic}' "
"followed by 2 Q&A pairs. Format as JSON: "
'{"text": "...", "qa": [{"q": "...", "a": "..."}]}'
}]
)
return json.loads(response.choices[0].message.content)
# Generate dataset
dataset = []
for topic in TOPICS * 250: # 1000 synthetic samples
sample = generate_sample(topic)
dataset.append(sample)
print(f"Generated: {sample['text'][:60]}...")
# Save for training
with open("synthetic_corpus.jsonl", "w") as f:
for item in dataset:
f.write(json.dumps(item) + "\n")
print(f"Dataset ready: {len(dataset)} examples")
✅ Pros
Solves data quality problem
Works for any niche domain
Phi-3 beats models 10× its size
❌ Cons
Inherits teacher bias
GPT-4 API calls cost mon
7
Efficient Architecture Design
Build smarter, not bigger
Use architectures designed from the ground up for small-scale efficiency: State Space Models, Mixture of Experts, or linear attention variants.
Simple Example — Mamba-style SSM block
Python
— Minimal State Space Model (SSM) block
import torch
import torch.nn as nn
class SimpleSSMBlock(nn.Module):
"""
Simplified SSM block (Mamba-style concept).
Replaces self-attention with a recurrent state update.
Complexity: O(n) vs O(n²) for attention — great for long sequences.
"""
def __init__(self, dim=128, state_dim=16):
super().__init__()
self.in_proj = nn.Linear(dim, dim * 2)
self.out_proj = nn.Linear(dim, dim)
self.A = nn.Parameter(torch.randn(state_dim, dim)) # state matrix
self.B = nn.Parameter(torch.randn(state_dim, dim)) # input matrix
self.C = nn.Parameter(torch.randn(dim, state_dim)) # output matrix
self.norm = nn.LayerNorm(dim)
def forward(self, x):
# x: (batch, seq_len, dim)
B, T, D = x.shape
h = torch.zeros(B, self.A.shape[0], device=x.device) # hidden state
outputs = []
for t in range(T):
u = x[:, t, :] # current token
h = torch.tanh(h @ self.A.T + u @ self.B.T) # state update
y = h @ self.C.T # output projection
outputs.append(y)
out = torch.stack(outputs, dim=1)
return self.norm(self.out_proj(out) + x) # residual connection
# Test it
block = SimpleSSMBlock(dim=128)
x = torch.randn(2, 512, 128) # batch=2, seq=512 tokens
out = block(x)
print(out.shape) # → torch.Size([2, 512, 128])
✅ Pros
Linear complexity — faster on long text
No KV-cache growth
❌ Cons
Less mature ecosystem than transformers
Harder to parallelize training