Learn AI Series (#150) - Emerging Frontiers
Learn AI Series (#150) - Emerging Frontiers
What will I learn
- AI video generation -- how systems like Sora extend diffusion from a single image to a whole sequence of frames, and the spacetime-transformer trick that makes it affordable;
- text-to-3D -- how Score Distillation Sampling turns a plain 2D image model into a supervisor for building 3D objects you can drop into a game engine;
- quantum machine learning -- what the theory actually promises, and an honest read on whether any of it matters to you this decade;
- neuromorphic computing -- spiking neural networks and why brain-style hardware sips power where a GPU gulps it;
- Mixture of Experts (MoE) -- how a model can carry hundreds of billions of parameters while only paying for a handful of them per token;
- test-time compute scaling -- the paradigm I think matters most right now: spending compute at inference, not just at training, and letting the hard problems think longer.
Requirements
- A working modern computer running macOS, Windows or Ubuntu;
- Python 3.10+ with PyTorch and NumPy installed (
pip install torch numpy) -- and I mean it this time, because a couple of these snippets actually build small networks you can poke at on a plain CPU; - You have read #84-85 (Diffusion Models) and #137 (Foundation Models), it helps to have #142 (Reasoning and Planning) fresh, and we open by settling the three homework problems from #149.
Difficulty
- Beginner
Curriculum (of the Learn AI Series):
- Learn AI Series (#1) - What Machine Learning Actually Is
- Learn AI Series (#2) - Setting Up Your AI Workbench - Python and NumPy
- Learn AI Series (#3) - Your Data Is Just Numbers - How Machines See the World
- Learn AI Series (#4) - Your First Prediction - No Math, Just Intuition
- Learn AI Series (#5) - Patterns in Data - What "Learning" Actually Looks Like
- Learn AI Series (#6) - From Intuition to Math - Why We Need Formulas
- Learn AI Series (#7) - The Training Loop - See It Work Step by Step
- Learn AI Series (#8) - The Math You Actually Need (Part 1) - Linear Algebra
- Learn AI Series (#9) - The Math You Actually Need (Part 2) - Calculus and Probability
- Learn AI Series (#10) - Your First ML Model - Linear Regression From Scratch
- Learn AI Series (#11) - Making Linear Regression Real
- Learn AI Series (#12) - Classification - Logistic Regression From Scratch
- Learn AI Series (#13) - Evaluation - How to Know If Your Model Actually Works
- Learn AI Series (#14) - Data Preparation - The 80% Nobody Talks About
- Learn AI Series (#15) - Feature Engineering and Selection
- Learn AI Series (#16) - Scikit-Learn - The Standard Library of ML
- Learn AI Series (#17) - Decision Trees - How Machines Make Decisions
- Learn AI Series (#18) - Random Forests - Wisdom of Crowds
- Learn AI Series (#19) - Gradient Boosting - The Kaggle Champion
- Learn AI Series (#20) - Support Vector Machines - Drawing the Perfect Boundary
- Learn AI Series (#21) - Mini Project - Predicting Crypto Market Regimes
- Learn AI Series (#22) - K-Means Clustering - Finding Groups
- Learn AI Series (#23) - Advanced Clustering - Beyond K-Means
- Learn AI Series (#24) - Dimensionality Reduction - PCA
- Learn AI Series (#25) - Advanced Dimensionality Reduction - t-SNE and UMAP
- Learn AI Series (#26) - Anomaly Detection - Finding What Doesn't Belong
- Learn AI Series (#27) - Recommendation Systems - "Users Like You Also Liked..."
- Learn AI Series (#28) - Time Series Fundamentals - When Order Matters
- Learn AI Series (#29) - Time Series Forecasting - Predicting What Comes Next
- Learn AI Series (#30) - Natural Language Processing - Text as Data
- Learn AI Series (#31) - Word Embeddings - Meaning in Numbers
- Learn AI Series (#32) - Bayesian Methods - Thinking in Probabilities
- Learn AI Series (#33) - Ensemble Methods Deep Dive - Stacking and Blending
- Learn AI Series (#34) - ML Engineering - From Notebook to Production
- Learn AI Series (#35) - Data Ethics and Bias in ML
- Learn AI Series (#36) - Mini Project - Complete ML Pipeline
- Learn AI Series (#37) - The Perceptron - Where It All Started
- Learn AI Series (#38) - Neural Networks From Scratch - Forward Pass
- Learn AI Series (#39) - Neural Networks From Scratch - Backpropagation
- Learn AI Series (#40) - Training Neural Networks - Practical Challenges
- Learn AI Series (#41) - Optimization Algorithms - SGD, Momentum, Adam
- Learn AI Series (#42) - PyTorch Fundamentals - Tensors and Autograd
- Learn AI Series (#43) - PyTorch Data and Training
- Learn AI Series (#44) - PyTorch nn.Module - Building Real Networks
- Learn AI Series (#45) - Convolutional Neural Networks - Theory
- Learn AI Series (#46) - CNNs in Practice - Classic to Modern Architectures
- Learn AI Series (#47) - CNN Applications - Detection, Segmentation, Style Transfer
- Learn AI Series (#48) - Recurrent Neural Networks - Sequences
- Learn AI Series (#49) - LSTM and GRU - Solving the Memory Problem
- Learn AI Series (#50) - Sequence-to-Sequence Models
- Learn AI Series (#51) - Attention Mechanisms
- Learn AI Series (#52) - The Transformer Architecture (Part 1)
- Learn AI Series (#53) - The Transformer Architecture (Part 2)
- Learn AI Series (#54) - Vision Transformers
- Learn AI Series (#55) - Generative Adversarial Networks
- Learn AI Series (#56) - Mini Project - Building a Transformer From Scratch
- Learn AI Series (#57) - Language Modeling - Predicting the Next Word
- Learn AI Series (#58) - GPT Architecture - Decoder-Only Transformers
- Learn AI Series (#59) - BERT and Encoder Models
- Learn AI Series (#60) - Training Large Language Models
- Learn AI Series (#61) - Instruction Tuning and Alignment
- Learn AI Series (#62) - Prompt Engineering - Getting the Most from LLMs
- Learn AI Series (#63) - Embeddings and Vector Search
- Learn AI Series (#64) - Retrieval-Augmented Generation (RAG) - Basics
- Learn AI Series (#65) - RAG - Advanced Techniques
- Learn AI Series (#66) - Working with LLM APIs
- Learn AI Series (#67) - Building AI Agents (Part 1) - Foundations
- Learn AI Series (#68) - Building AI Agents (Part 2) - Advanced Patterns
- Learn AI Series (#69) - Fine-Tuning Language Models
- Learn AI Series (#70) - Running Local Models
- Learn AI Series (#71) - Text Generation Techniques
- Learn AI Series (#72) - Tokenization Deep Dive
- Learn AI Series (#73) - LLM Evaluation
- Learn AI Series (#74) - The Hugging Face Ecosystem
- Learn AI Series (#75) - Multimodal Models - Text Meets Vision
- Learn AI Series (#76) - Mini Project - Your Own AI Assistant
- Learn AI Series (#77) - Image Processing Fundamentals
- Learn AI Series (#78) - Object Detection (Part 1) - Foundations
- Learn AI Series (#79) - Object Detection (Part 2) - Modern Approaches
- Learn AI Series (#80) - Image Segmentation
- Learn AI Series (#81) - Pose Estimation and Tracking
- Learn AI Series (#82) - Optical Character Recognition
- Learn AI Series (#83) - Video Understanding
- Learn AI Series (#84) - Generative Images - Diffusion Models (Part 1)
- Learn AI Series (#85) - Generative Images - Diffusion Models (Part 2)
- Learn AI Series (#86) - Image-to-Image and Editing
- Learn AI Series (#87) - 3D Vision
- Learn AI Series (#88) - Face Analysis
- Learn AI Series (#89) - Medical and Scientific Imaging
- Learn AI Series (#90) - Self-Supervised Learning for Vision
- Learn AI Series (#91) - Mini Project - Building a Visual AI System
- Learn AI Series (#92) - Audio Fundamentals for AI
- Learn AI Series (#93) - Speech Recognition
- Learn AI Series (#94) - Text-to-Speech (TTS)
- Learn AI Series (#95) - Audio Classification
- Learn AI Series (#96) - Music Generation
- Learn AI Series (#97) - Speaker Recognition and Diarization
- Learn AI Series (#98) - Natural Language Understanding for Voice
- Learn AI Series (#99) - Audio Enhancement
- Learn AI Series (#100) - Multimodal Audio-Visual Models
- Learn AI Series (#101) - Mini Project: Voice-Controlled AI Assistant
- Learn AI Series (#102) - What Is Reinforcement Learning?
- Learn AI Series (#103) - Multi-Armed Bandits
- Learn AI Series (#104) - Dynamic Programming
- Learn AI Series (#105) - Monte Carlo Methods
- Learn AI Series (#106) - Temporal Difference Learning
- Learn AI Series (#107) - Deep Q-Networks (DQN)
- Learn AI Series (#108) - Policy Gradient Methods
- Learn AI Series (#109) - Advanced Policy Optimization
- Learn AI Series (#110) - Model-Based Reinforcement Learning
- Learn AI Series (#111) - Multi-Agent Reinforcement Learning
- Learn AI Series (#112) - RL for Games
- Learn AI Series (#113) - RL for Real-World Applications
- Learn AI Series (#114) - Inverse Reinforcement Learning
- Learn AI Series (#115) - Offline Reinforcement Learning
- Learn AI Series (#116) - Mini Project: Training a Game-Playing AI
- Learn AI Series (#117) - ML System Design
- Learn AI Series (#118) - Data Engineering for AI
- Learn AI Series (#119) - Experiment Tracking and Reproducibility
- Learn AI Series (#120) - Model Optimization: Making Models Fast
- Learn AI Series (#121) - Model Serving Architecture
- Learn AI Series (#122) - Edge AI: Running Models on Devices
- Learn AI Series (#123) - Monitoring ML in Production
- Learn AI Series (#124) - CI/CD for Machine Learning
- Learn AI Series (#125) - GPU Programming Basics
- Learn AI Series (#126) - Distributed Training
- Learn AI Series (#127) - AI Security
- Learn AI Series (#128) - Privacy-Preserving AI
- Learn AI Series (#129) - AutoML and Neural Architecture Search
- Learn AI Series (#130) - Causal Inference and ML
- Learn AI Series (#131) - Graph Neural Networks
- Learn AI Series (#132) - AI for Structured Data
- Learn AI Series (#133) - Synthetic Data Generation
- Learn AI Series (#134) - AI Infrastructure Economics
- Learn AI Series (#135) - Building AI Teams and Processes
- Learn AI Series (#136) - Mini Project: Production AI Platform
- Learn AI Series (#137) - Foundation Models
- Learn AI Series (#138) - Multimodal AI
- Learn AI Series (#139) - AI for Code
- Learn AI Series (#140) - Scientific AI
- Learn AI Series (#141) - Robotics and Embodied AI
- Learn AI Series (#142) - AI Reasoning and Planning
- Learn AI Series (#143) - Continual Learning
- Learn AI Series (#144) - Few-Shot and Zero-Shot Learning
- Learn AI Series (#145) - Neuro-Symbolic AI
- Learn AI Series (#146) - Explainability and Interpretability
- Learn AI Series (#147) - AI Safety and Alignment
- Learn AI Series (#148) - The Economics of AI
- Learn AI Series (#149) - AI Ethics in Practice
- Learn AI Series (#150) - Emerging Frontiers (this post)
Learn AI Series (#150) - Emerging Frontiers
150 episodes. One hundred and fifty. When I started this series I honestly did not know if I would make it past the linear-regression episodes without half of you wandering off, and here we still are ;-)
I ended #149 by promising you the most speculative episode we would do -- the one where I lift my eyes off the workbench and point at the horizon. So that is today. Everything up to now has been "here is a thing that works, here is the code, go build it." Today is different. Today is "here is a thing that MIGHT work, here is why smart people are excited, and here is my honest guess about whether you should care yet." I will be blunt about which of these I think is real and which is a lovely research paper wearing a business plan as a costume.
Having said that -- we settle last week's homework first, same house rules as always. No skipping ahead ;-)
Solutions to episode #149's exercises
#149 was the ethics-in-practice episode, and all three tasks were about turning a fairness principle into something a machine can actually check.
Exercise 1 -- Add predictive parity to the auditor. Extend BiasAuditor so each group also reports precision, add a predictive_parity_gap, and have the report flag the attribute with the widest gap.
import torch
class BiasAuditorPlus:
"""The #149 auditor, now with predictive parity (precision) per group."""
def __init__(self, model, protected_attributes):
self.model = model
self.protected_attributes = protected_attributes
def audit(self, X, y_true, group_labels):
self.model.eval()
with torch.no_grad():
y_pred = self.model(X).argmax(dim=1)
results = {}
for attr in self.protected_attributes:
groups = group_labels[attr]
group_metrics = {}
for g in groups.unique():
mask = (groups == g)
gp, gt = y_pred[mask], y_true[mask]
tp = ((gp == 1) & (gt == 1)).sum().float()
fp = ((gp == 1) & (gt == 0)).sum().float()
fn = ((gp == 0) & (gt == 1)).sum().float()
group_metrics[g.item()] = {
'positive_rate': (gp == 1).float().mean().item(),
'tpr': (tp / (tp + fn + 1e-8)).item(),
# NEW: precision == predictive parity per group
'precision': (tp / (tp + fp + 1e-8)).item(),
}
precisions = [m['precision'] for m in group_metrics.values()]
results[attr] = {
'group_metrics': group_metrics,
'predictive_parity_gap': max(precisions) - min(precisions),
}
return results
def print_report(self, results):
worst = max(results, key=lambda a: results[a]['predictive_parity_gap'])
for attr, data in results.items():
flag = " <-- WIDEST GAP" if attr == worst else ""
print(f"=== {attr} (pred-parity gap "
f"{data['predictive_parity_gap']:.3f}){flag} ===")
for g, m in data['group_metrics'].items():
print(f" group {g}: pos_rate={m['positive_rate']:.3f}, "
f"TPR={m['tpr']:.3f}, precision={m['precision']:.3f}")
Precision is the number the person on the receiving end of a decision feels most directly -- "when this thing flagged me, how often was it right?" -- so for a medical-triage model I would put predictive parity first, because a patient who gets told "you are fine" trusts that answer with their life, and that trust had better be worth the same across groups.
Exercise 2 -- Enforce parity and pay for it. Take parity_vs_precision from #149 and, in stead of one shared selection rate, use a per-group threshold so each group hits the SAME positive rate. Confirm demographic parity now holds exactly, then report the precision gap that stubbornly refuses to close.
import numpy as np
def parity_enforced(base_rate_a=0.30, base_rate_b=0.10, target_rate=0.20):
"""Per-group thresholds => demographic parity by construction.
Then we look at precision and watch the impossibility result bite."""
rng = np.random.default_rng(0)
precisions = {}
for name, rate in (("A", base_rate_a), ("B", base_rate_b)):
y = (rng.random(100_000) < rate).astype(int) # who is truly positive
score = y + rng.normal(0, 1.0, size=y.shape) # a noisy, honest score
thresh = np.quantile(score, 1 - target_rate) # THIS group's own cutoff
pred = (score >= thresh).astype(int)
tp = int(((pred == 1) & (y == 1)).sum())
precisions[name] = tp / max(int(pred.sum()), 1)
print(f"group {name}: positive_rate={pred.mean():.3f} "
f"precision={precisions[name]:.2f}")
gap = max(precisions.values()) - min(precisions.values())
print(f"demographic parity holds (both ~{target_rate:.0%}); "
f"precision gap stays at {gap:.2f}")
parity_enforced()
Both groups now get flagged at the same rate -- demographic parity satisfied exactly, by construction -- and yet precision refuses to match, because the group with the higher true base rate simply has more real positives sitting above any given cutoff. That is not a bug you can engineer away with a better threshold; it is the Kleinberg impossibility result from #149 showing up in ten lines of NumPy, telling you that equalizing one fairness spends another.
Exercise 3 -- Write a model-card linter. Given a model_card dict, write check_card(card) that verifies every required key is present, that performance.by_demographic reports at least two groups, and that ethical_considerations is non-empty -- printing exactly what is missing.
def check_card(card):
"""A machine-checkable model card. Prints exactly what is missing."""
required = ["model_name", "model_type", "intended_use", "training_data",
"performance", "limitations", "ethical_considerations"]
problems = [f"missing key: {k}" for k in required if k not in card]
by_demo = card.get("performance", {}).get("by_demographic", {})
if len(by_demo) < 2:
problems.append("performance.by_demographic needs >= 2 groups")
if not card.get("ethical_considerations"):
problems.append("ethical_considerations is empty")
if problems:
print("Card FAILED:")
for p in problems:
print(f" - {p}")
else:
print("Card OK: all required fields present and populated.")
return not problems
# a card missing its ethics section and with only one demographic group
check_card({
"model_name": "LoanApproval-v2.3",
"model_type": "XGBoost",
"intended_use": "pre-screening for MANUAL review",
"training_data": {"size": "2.4M"},
"performance": {"by_demographic": {"group_w": {"auc": 0.89}}},
"limitations": ["thin credit files"],
"ethical_considerations": [],
})
A machine-checkable card beats a beautifully-written PDF because the PDF gets read once, at launch, by the one person who already agreed with it -- while check_card runs in your CI pipeline on every retrain and fails the build the day someone quietly ships a model with no ethics section. Documentation that a computer can enforce is documentation that survives contact with a deadline ;-)
Right -- homework settled. Now let us go stand at the edge of the map.
AI video generation
Video generation today is roughly where image generation was two years ago: improving frighteningly fast, not yet reliable, and about to rearrange entire industries whether they like it or not.
The architecture behind systems like Sora extends the diffusion models we built in #84-85 from 2D into 3D. In stead of denoising a single image, the model denoises a whole stack of frames at once. The key piece is the spacetime transformer -- a transformer that chews on patches drawn from both the spatial dimensions (height, width) AND the time dimension. The first job is cutting a video into those spacetime patches:
import torch
import torch.nn as nn
class SpacetimePatchEmbed(nn.Module):
"""Cut a video into spacetime patches for a video transformer."""
def __init__(self, patch_size=16, temporal_patch=4,
in_channels=3, embed_dim=768):
super().__init__()
# A single 3D convolution does spatial AND temporal patching at once.
self.proj = nn.Conv3d(
in_channels, embed_dim,
kernel_size=(temporal_patch, patch_size, patch_size),
stride=(temporal_patch, patch_size, patch_size),
)
def forward(self, video):
# video: (batch, channels, frames, height, width)
x = self.proj(video) # (B, embed_dim, T', H', W')
x = x.flatten(2).transpose(1, 2) # (B, n_patches, embed_dim)
return x
Now the expensive question: how does attention work over all those patches? Full 3D attention over every patch against every other patch is quadratic in the TOTAL number of patches, which for even a short clip is ruinous. The trick that makes it affordable is factored attention -- do spatial attention within each frame first, then temporal attention across frames at the same position:
class VideoTransformerBlock(nn.Module):
"""Factored spacetime attention: spatial first, then temporal."""
def __init__(self, embed_dim=768, n_heads=12):
super().__init__()
self.spatial_attn = nn.MultiheadAttention(embed_dim, n_heads,
batch_first=True)
self.temporal_attn = nn.MultiheadAttention(embed_dim, n_heads,
batch_first=True)
self.norm_s = nn.LayerNorm(embed_dim)
self.norm_t = nn.LayerNorm(embed_dim)
self.mlp = nn.Sequential(
nn.Linear(embed_dim, embed_dim * 4), nn.GELU(),
nn.Linear(embed_dim * 4, embed_dim),
)
self.norm_m = nn.LayerNorm(embed_dim)
def forward(self, x, n_temporal, n_spatial):
B, N, D = x.shape
# Spatial: group patches by time step, attend within each frame
xs = x.reshape(B * n_temporal, n_spatial, D)
a, _ = self.spatial_attn(xs, xs, xs)
x = x + self.norm_s(a.reshape(B, N, D))
# Temporal: group patches by position, attend across frames
xt = (x.reshape(B, n_temporal, n_spatial, D)
.permute(0, 2, 1, 3).reshape(B * n_spatial, n_temporal, D))
a, _ = self.temporal_attn(xt, xt, xt)
x = x + self.norm_t(a.reshape(B, n_spatial, n_temporal, D)
.permute(0, 2, 1, 3).reshape(B, N, D))
return x + self.norm_m(self.mlp(x))
Many modern systems wrap this in DiT (Diffusion Transformer) blocks, where the conditioning -- your text prompt, the current denoising timestep -- modulates the network through adaptive layer normalization, exactly the flavour of conditioning we saw in the image-diffusion episodes.
What is NOT solved: temporal consistency (a dog should not grow a fifth leg between frame 12 and frame 40), physics (things should fall, collide, and pour like they mean it), and long-range coherence (a 60-second clip has to tell one story, not sixty). People are closing these gaps fast, but do not let a cherry-picked demo reel convince you they are closed. They are not, yet.
Text-to-3D
Generating a 3D object from a sentence sounds like it should need a giant 3D model trained on giant 3D datasets -- which barely exist. The clever escape hatch is Score Distillation Sampling (SDS): use a pretrained 2D image diffusion model as a critic, and let it teach a 3D thing to look correct.
The loop is genuinely elegant. Render your 3D object from a random camera angle, hand the 2D render to the diffusion model, and ask "does this look like the prompt?" Use its gradient to nudge the 3D representation, then rotate to a new angle and repeat -- thousands of times. Because you keep changing the camera, the only way the object can satisfy the critic from EVERY angle is to actually become a coherent 3D shape.
The 3D representation itself varies: Neural Radiance Fields (NeRFs, which we touched in #87), the newer and much faster 3D Gaussian Splatting, or plain old explicit meshes. The choice is downstream-driven -- games want meshes, visual effects might take NeRFs, real-time apps love Gaussian splats. This is not sci-fi; game-asset creation, product visualisation, architectural previews and virtual try-on are being built on text-to-3D pipelines right now, today.
Quantum machine learning
I am going to be direct, because you deserve it: quantum machine learning (QML) is, for practical purposes, mostly hype at the moment. There. I said it. But the theory is genuinely interesting, so let me give you the honest version rather than either the breathless one or the dismissive one.
The premise is seductive. A quantum computer can represent and manipulate an exponentially large state space using superposition and entanglement, and a handful of ML problems -- high-dimensional distributions, certain nasty optimisation landscapes, specific kernel computations -- might one day get a real speedup from that. The current workhorse idea is the variational quantum circuit (VQC): a parameterised quantum circuit that behaves like a trainable model, with the parameters tuned by good old classical gradient descent.
import torch
class QuantumClassifier:
"""CONCEPTUAL hybrid quantum-classical classifier.
Not runnable without a quantum simulator (PennyLane / Qiskit)."""
def __init__(self, n_qubits=4, n_layers=3):
self.n_qubits = n_qubits
# trainable rotation angles for the quantum gates
self.params = torch.randn(n_layers, n_qubits, 3, requires_grad=True)
def quantum_circuit(self, x, params):
# 1. ENCODE: map classical features -> qubit rotations
# 2. PROCESS: apply layers of parameterised rotations + entangling gates
# 3. MEASURE: collapse the state; measurement probabilities ARE the output
raise NotImplementedError("needs quantum hardware or a simulator")
def forward(self, x):
return self.quantum_circuit(x, self.params)
Notice I left the circuit unimplemented on purpose -- that is not laziness, it is the honest state of the field for someone on a laptop. The blunt status: today's quantum machines are too noisy and too small to beat classical ML on any task you actually have. The theoretical advantages assume fault-tolerant machines with millions of clean qubits; we have hundreds of noisy ones. Might that change in 5-10 years? Maybe. Might it not? Also maybe. Keep half an eye on it, read a survey once a year -- but do NOT build your career on it this decade. If someone sells you a "quantum AI" product in 2026, keep your hand on your wallet ;-)
Neuromorphic computing
Conventional chips march to a synchronous clock and do floating-point arithmetic. Your brain does neither -- it fires asynchronous spikes between neurons and burns about 20 watts doing it. Neuromorphic computing builds hardware that works more like the brain, and its software counterpart is the spiking neural network (SNN).
In stead of the smooth continuous activations we have used all series, an SNN passes around binary spikes over time. The canonical unit is the Leaky Integrate-and-Fire neuron -- it accumulates incoming current, leaks a bit each step, and fires a spike when it crosses a threshold:
import torch
import torch.nn as nn
class LIFNeuron(nn.Module):
"""Leaky Integrate-and-Fire neuron -- one timestep at a time."""
def __init__(self, tau=0.9, threshold=1.0):
super().__init__()
self.tau = tau # leak: how much membrane potential survives
self.threshold = threshold
def forward(self, input_current, membrane=None):
if membrane is None:
membrane = torch.zeros_like(input_current)
membrane = self.tau * membrane + input_current # leak + integrate
spikes = (membrane >= self.threshold).float() # fire
membrane = membrane - spikes * self.threshold # reset what fired
return spikes, membrane
Stack those into a network and you process the input over many timesteps, then classify by counting output spikes:
class SpikingNetwork(nn.Module):
"""A small SNN for classification -- classify by spike count."""
def __init__(self, input_dim=784, hidden=256, n_classes=10, n_steps=25):
super().__init__()
self.n_steps, self.n_classes = n_steps, n_classes
self.fc1, self.lif1 = nn.Linear(input_dim, hidden, bias=False), LIFNeuron()
self.fc2, self.lif2 = nn.Linear(hidden, n_classes, bias=False), LIFNeuron()
def forward(self, x):
# rate-code the input: brighter pixel -> more likely to spike each step
trains = (torch.rand(self.n_steps, *x.shape) < x.unsqueeze(0)).float()
mem1 = mem2 = None
out = torch.zeros(x.size(0), self.n_classes)
for t in range(self.n_steps):
s1, mem1 = self.lif1(self.fc1(trains[t]), mem1)
s2, mem2 = self.lif2(self.fc2(s1), mem2)
out += s2
return out
The upside is real: spikes are binary, so neuromorphic silicon can be astonishingly frugal -- Intel's Loihi chip runs certain workloads at orders of magnitude less power than a GPU. The downside is also real: spikes are non-differentiable (the same wall we hit with binary activations, worked around with surrogate gradients), and the tooling and pretrained-model ecosystem is still thin. Where does it shine? Edge AI (#122) -- always-on, low-power sensing like keyword spotting, gesture detection and anomaly monitoring, where a milliwatt matters more than the last percent of accuracy. Niche, but a genuinely valuable niche.
Mixture of Experts (MoE)
MoE is not new -- the idea dates to the 1990s -- but it is having a full-blown renaissance because it answers one of the sharpest questions in modern AI: how do you make a model bigger WITHOUT making every forward pass proportionally more expensive?
The answer: in stead of one giant feedforward block, keep many smaller expert networks plus a learned router that picks which experts handle each token. Only a few experts fire per token, so your total parameter count can be enormous while the compute per token stays modest.
import torch
import torch.nn as nn
class MoELayer(nn.Module):
"""Mixture of Experts with top-k routing."""
def __init__(self, input_dim=512, expert_dim=2048, n_experts=8, top_k=2):
super().__init__()
self.n_experts, self.top_k = n_experts, top_k
self.router = nn.Linear(input_dim, n_experts)
self.experts = nn.ModuleList([
nn.Sequential(nn.Linear(input_dim, expert_dim), nn.GELU(),
nn.Linear(expert_dim, input_dim))
for _ in range(n_experts)
])
def forward(self, x):
B, S, D = x.shape
probs = torch.softmax(self.router(x), dim=-1) # (B, S, n_experts)
top_p, top_i = probs.topk(self.top_k, dim=-1) # pick k experts / token
top_p = top_p / top_p.sum(dim=-1, keepdim=True) # renormalise
out = torch.zeros_like(x)
for k in range(self.top_k):
idx, w = top_i[..., k], top_p[..., k:k+1]
for e in range(self.n_experts):
mask = (idx == e)
if mask.any():
out[mask] += w[mask] * self.experts[e](x[mask])
return out
Mixtral 8x7B is the classic example: about 46.7B total parameters but only ~13B active per forward pass (2 of 8 experts per layer), which buys it the quality of a much larger dense model at a fraction of the inference bill. The catches are engineering, not theory: load balancing (stop the router from lazily always picking the same two experts -- you add an auxiliary loss to spread the load) and communication overhead (when experts live on different GPUs, tokens have to travel). Both are solved well enough that MoE now sits inside a lot of frontier systems you have already used, whether the marketing told you or not.
Test-time compute scaling
If you make me pick the ONE idea on this page that already matters most in practice, it is this one -- and it is the least flashy of the bunch, which is exactly why I trust it.
For most of this series, scaling meant training-time compute: bigger model, more data, longer run. Test-time compute scaling adds a second dial. The insight (which ties straight back to reasoning in #142) is dead simple: harder problems deserve more thinking. So in stead of one forward pass and out, let the model chew -- generate several reasoning paths, check its own intermediate steps, search over candidate solutions, and keep the best one. Here is the cheapest honest version, best-of-N with a verifier:
def best_of_n(model, verifier, prompt, n=8):
"""Spend more compute at INFERENCE: sample n answers, keep the best-scored one.
`model` returns a candidate; `verifier` scores how good a candidate is."""
candidates = [model(prompt) for _ in range(n)] # n forward passes, not 1
scored = [(verifier(prompt, c), c) for c in candidates]
scored.sort(key=lambda pair: pair[0], reverse=True)
best_score, best = scored[0]
print(f"sampled {n} candidates; best verifier score = {best_score:.2f}")
return best
That is the whole trick in five lines: trade inference cost for quality. Systems like o1 and o3 do a far more sophisticated version of this, sometimes spending 10-100x the compute of a plain forward pass on a single hard query -- and the accuracy you get back follows a scaling law, just like training compute does. For a practitioner this quietly rewrites the economics. In stead of paying once to train a bigger model and eating that cost on every query forever, you can train a smaller model and spend extra compute ONLY on the queries that are actually hard. Easy questions stay cheap; hard questions get the thinking they need. That flexibility is why I think this is the frontier that changes your day job first.
Exercises
Get your hands dirty before we start the final stretch of this series. Three tasks, climbing in difficulty:
Router entropy for MoE. Add a method to
MoELayerthat, given a batch, returns how often each expert was selected across all tokens, plus the entropy of that distribution. One sentence on why a HIGH entropy is what you want, and what a collapsing (low-entropy) router would mean for all those parameters you paid for.A leaky-neuron dial. Take
LIFNeuronand sweeptaufrom 0.1 to 0.99 while feeding it a constant input current. Plot or print how many timesteps pass between spikes at eachtau, and write two sentences on what the membrane time constant is really controlling -- and why a spike-count classifier cares.Budgeted best-of-N. Extend
best_of_nso it takes acompute_budgetand an easy/hard label per prompt, spendingn=2on easy prompts andn=16on hard ones, and report the average verifier score per dollar of compute versus a flatn=8baseline. Two sentences connecting what you find back to why test-time scaling beats "just train a bigger model" on a mixed workload.
We open the next episode with full solutions, exactly like always.
The important bits
- video generation extends diffusion into spacetime, and factored spatial-then-temporal attention is what keeps it from being computationally impossible -- temporal consistency, physics and long-range coherence are the parts still not solved;
- text-to-3D sidesteps the lack of 3D data by using Score Distillation Sampling: a pretrained 2D image model critiques renders from thousands of angles until the 3D object looks right from everywhere;
- quantum ML is theoretically interesting but practically unproven -- today's noisy, tiny machines cannot beat classical ML on real work, so watch it but do NOT bet your decade on it;
- neuromorphic computing with spiking networks buys extreme energy efficiency for always-on edge sensing, at the cost of harder training and thin tooling -- a small niche, but a real one;
- Mixture of Experts lets a model hold enormous parameter counts while paying for only a few experts per token, with load balancing and communication as the engineering price of admission;
- test-time compute scaling is the emerging paradigm I would bet on first: spend variable compute at inference based on how hard the problem is, and beat a bigger model at lower average cost.
That is the horizon as I read it in the summer of 2026 -- part solid ground, part shifting sand, and I have tried to tell you honestly which is which. Next time we come back down off the mountain and put our hands to work: we take everything this series has taught and build something that is not a toy, something that actually MATTERS. Bring your editor and a pot of coffee ;-)
Bedankt for reading all one hundred and fifty of these with me -- now go pick the one frontier above that made your eyes light up, and read one real paper on it this week. Tot de volgende keer! ;-)
Leave Learn AI Series (#150) - Emerging Frontiers to:
Read more #stem posts
Best Posts From scipio
We have not curated any of scipio's posts yet. But you can encourage our curation team to review posts by visiting them regularly and by referring other readers. Because we give priority to frequently read content.
More Posts From scipio
- Learn AI Series (#150) - Emerging Frontiers
- Learn Zig Series (#130) - Mini Project: Database Engine - SQL Parser
- Learn Rust Series (#19) - Deref, DerefMut & Deref Coercion
- Learn AI Series (#149) - AI Ethics in Practice
- Learn Zig Series (#129) - Mini Project: Database Engine - B-Tree Index
- Learn Rust Series (#18) - Operator Overloading with std::ops
- Learn AI Series (#148) - The Economics of AI
- Learn Zig Series (#128) - Mini Project: Database Engine - Page Storage
- Learn Rust Series (#17) - Associated Types vs Generic Parameters
- Learn AI Series (#147) - AI Safety and Alignment