Learn AI Series (#149) - AI Ethics in Practice
Learn AI Series (#149) - AI Ethics in Practice

What will I learn
- Bias auditing -- how to systematically detect and MEASURE bias in a model before it ever reaches a user, with a reusable auditor you can point at any classifier;
- fairness that fights itself -- the mathematical definitions of fairness, and why they are provably incompatible, so choosing between them is a human value call and not a technical one;
- transparency -- model cards and datasheets, the most boring and most effective ethical tools we have, and why documentation is the difference between a responsible deployment and a lawsuit;
- environmental impact -- the carbon footprint of training and inference, how to estimate it, and the handful of levers that actually move it;
- consent and data rights -- where training data really comes from, who owns it, and how to filter a corpus so you respect opt-outs before a page ever enters your training set;
- a practical framework -- a before/during/after checklist for making better decisions as a practitioner, built around the one question that matters most.
Requirements
- A working modern computer running macOS, Windows or Ubuntu;
- Python 3.10+ with PyTorch and NumPy installed (
pip install torch numpy) -- every snippet here runs on a plain CPU in a second or two, no GPU needed anywhere; - You have read #35 (Data Ethics and Bias in ML) and #147 (AI Safety and Alignment), and it helps to have #148 fresh, because we settle its three homework problems before we open anything new.
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 (this post)
Learn AI Series (#149) - AI Ethics in Practice
I ended #148 by leaving a thread dangling on purpose. We spent that whole episode counting margins -- automate this task, capture that value, cut this cost, license that data -- and every one of those spreadsheet decisions lands on a real human being who was not in the room when we ran the numbers. Today we pick that thread back up. This is the episode where "can I build it" and "should I build it" stop being a clever line and turn into code you actually write.
Let me say up front what this episode is NOT. I am not going to lecture you about being a good person -- I assume you already are one, and a sermon from me would be both boring and useless. The uncomfortable truth about AI harm is that it almost never comes from villains twirling a moustache. It comes from well-meaning engineers with a deadline, a blind spot, and an incentive pointing slightly the wrong way. So the goal here is deliberately practical: give you tools to MEASURE harm before it ships, because a thing you can measure is a thing you can fix ;-)
Having said that, we settle last week's homework first. House rules.
Solutions to episode #148's exercises
#148 was the economics episode, and all three tasks were about pushing those toy economic models until they told you something the prose had glossed over.
Exercise 1 -- Tune the leash economically. Take the data_flywheel and make the leader PAY for the share it grabs (marketing spend proportional to the new share captured each round). Sweep a few cost levels and find the point where the cumulative spend stops being worth the lead it bought.
def data_flywheel_with_cost(rounds=8, leader0=1.0, follower0=0.9,
capture=0.15, cost_per_share=0.0):
"""Same flywheel as #148, but now the leader pays to keep winning share.
We compare the VALUE of the lead against the cumulative spend."""
leader, follower = leader0, follower0
cum_spend = 0.0
for r in range(rounds):
total = leader + follower
gain = capture * (leader / total) # new share the leader grabs this round
leader += gain
follower += capture * (follower / total)
cum_spend += cost_per_share * gain # marketing spend for that grabbed share
lead_value = leader - follower
worth_it = lead_value > cum_spend
print(f" round {r+1}: lead {lead_value:+.3f} spent {cum_spend:.3f} "
f"worth_it={worth_it}")
for cost in (0.5, 2.0, 8.0):
print(f"cost_per_share = {cost}")
data_flywheel_with_cost(cost_per_share=cost)
At a cheap cost_per_share the leader buys its runaway lead for pocket change and every round stays "worth it". Crank the cost up and the cumulative spend overtakes the value of the lead -- the flywheel still spins, but now the leader is setting money on fire to turn it. The one-sentence lesson: winner-take-most does NOT mean winning at any price, and a dominant position bought above its own value is just an expensive way to lose.
Exercise 2 -- Find the break-even user count. Using avg_cost_per_user, find the smallest number of users at which average cost drops below 5 cents, for a training bill of 100M and of 1B.
def avg_cost_per_user(train_cost, marginal_cost, users):
return (train_cost + marginal_cost * users) / users
def break_even_users(train_cost, marginal_cost, target):
# closed form: users > train_cost / (target - marginal_cost)
users = 1
while avg_cost_per_user(train_cost, marginal_cost, users) >= target:
users *= 2 # geometric search, close enough for this
return users
marginal, target = 0.002, 0.05
for train in (100_000_000, 1_000_000_000):
n = break_even_users(train, marginal, target)
print(f"train ${train:>15,}: break-even around {n:,} users")
Both break-even points are enormous -- billions of users -- and the 1B model needs an order of magnitude more of a crowd than the 100M model to reach the same 5-cent average. Two sentences on what that gap means: only a handful of companies on earth have the distribution to amortize a frontier training run, which is exactly why the foundation-model layer is an oligopoly. Everyone else should fine-tune (#69), where the fixed cost is thousands of dollars in stead of hundreds of millions, and the break-even is a rounding error.
Exercise 3 -- Race the cost curve. Extend the build-versus-buy comparison so the BUILD cost also falls, just slower (30% cheaper per year rather than 10x), and find the year buy overtakes build for good.
def buy_cost(monthly_tokens, price_per_1k, year):
price = price_per_1k * (0.1 ** year) # ~10x cheaper per year
return monthly_tokens / 1000 * price
def build_cost(year, base=8000):
return base * (0.7 ** year) # only 30% cheaper per year
monthly_tokens = 500_000_000
overtake = None
for year in range(8):
buy, build = buy_cost(monthly_tokens, 2.0, year), build_cost(year)
if overtake is None and buy < build:
overtake = year
print(f"year {year}: buy=${buy:>12,.0f} build=${build:>10,.0f}")
print(f"buy overtakes build permanently at year {overtake}")
Buy starts absurdly expensive and self-hosting wins for the first few years, then the 10x-per-year deflation drags the API price clean under the slowly-shrinking build cost and it never comes back up. Two sentences on why: a variable cost that drops tenfold every year will always, eventually, dive below a fixed cost that only trims 30%, no matter how far apart they start. The moment you bet on "we built our own to save money," you are quietly racing a curve that falls ten times faster than yours.
Right -- homework settled. Now let us go measure some harm ;-)
Bias auditing: measuring what matters
Bias in AI is not a vague moral vibe -- it is a number, and numbers you can compute are numbers you can argue about honestly. A bias audit systematically tests whether your model behaves differently for different groups of people. We drew the theory back in #35; here is the practical instrument. Point it at any classifier and it tells you where the model treats people unequally.
import torch
import numpy as np
from collections import defaultdict
class BiasAuditor:
"""Systematic bias auditing for classification models."""
def __init__(self, model, protected_attributes):
self.model = model
self.protected_attributes = protected_attributes # e.g. ['gender', 'race']
def audit(self, X, y_true, group_labels):
"""
Run a bias audit across all protected groups.
group_labels: dict mapping attribute -> tensor of group IDs
"""
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]
unique_groups = groups.unique()
group_metrics = {}
for g in unique_groups:
mask = (groups == g)
group_preds = y_pred[mask]
group_true = y_true[mask]
tp = ((group_preds == 1) & (group_true == 1)).sum().float()
fp = ((group_preds == 1) & (group_true == 0)).sum().float()
tn = ((group_preds == 0) & (group_true == 0)).sum().float()
fn = ((group_preds == 0) & (group_true == 1)).sum().float()
group_metrics[g.item()] = {
'accuracy': ((group_preds == group_true).float().mean()).item(),
'positive_rate': (group_preds == 1).float().mean().item(),
'tpr': (tp / (tp + fn + 1e-8)).item(), # true positive rate
'fpr': (fp / (fp + tn + 1e-8)).item(), # false positive rate
}
rates = [m['positive_rate'] for m in group_metrics.values()]
tprs = [m['tpr'] for m in group_metrics.values()]
results[attr] = {
'group_metrics': group_metrics,
'demographic_parity_ratio': min(rates) / (max(rates) + 1e-8),
'equalized_odds_gap': max(tprs) - min(tprs),
}
return results
def print_report(self, results):
"""Print a human-readable bias report."""
for attr, data in results.items():
print(f"\n=== Attribute: {attr} ===")
for group, metrics in data['group_metrics'].items():
print(f" Group {group}: acc={metrics['accuracy']:.3f}, "
f"pos_rate={metrics['positive_rate']:.3f}, "
f"TPR={metrics['tpr']:.3f}, FPR={metrics['fpr']:.3f}")
print(f" Demographic parity ratio: "
f"{data['demographic_parity_ratio']:.3f}")
print(f" Equalized odds gap: "
f"{data['equalized_odds_gap']:.3f}")
The metric choices are not neutral -- each one encodes a different idea of what "fair" even means:
Demographic parity asks: does each group get positive predictions at the same rate? A hiring model that recommends 40% of male applicants but only 20% of female applicants fails demographic parity, full stop, regardless of who was actually qualified.
Equalized odds asks: does each group get the same true positive rate AND the same false positive rate? A medical model that catches 95% of a disease in one group but only 70% in another fails equalized odds -- it is quietly better at protecting one set of patients than another.
Predictive parity asks: when the model says "positive," is it equally likely to be correct for each group? This is the one a person on the receiving end of a decision cares about most -- "if this thing flagged me, how much should I trust it?"
Fairness that fights itself
Here is the part that trips up every engineer the first time, myself included. Those three definitions are not just hard to satisfy at once -- they are mathematically INCOMPATIBLE. Kleinberg, Mullainathan and Raghavan proved in 2016 (and Chouldechova showed the same result independently the following year) that when the true base rates differ between groups, you cannot have demographic parity and predictive parity at the same time, except in trivial cases that never happen in real data.
That sounds abstract until you watch it happen. Two groups, different true base rates, one honest noisy score, and we enforce demographic parity by selecting the same fraction of each group:
import numpy as np
def parity_vs_precision(base_rate_a=0.30, base_rate_b=0.10, select=0.20):
"""Two groups with different TRUE base rates. We enforce demographic parity by
selecting the same fraction `select` of each. Watch precision refuse to match."""
rng = np.random.default_rng(0)
for name, rate in (("A", base_rate_a), ("B", base_rate_b)):
y = (rng.random(100_000) < rate).astype(int) # who is actually positive
score = y + rng.normal(0, 1.0, size=y.shape) # a noisy, honest score
thresh = np.quantile(score, 1 - select) # take the top `select` fraction
pred = (score >= thresh).astype(int)
tp = int(((pred == 1) & (y == 1)).sum())
precision = tp / max(int(pred.sum()), 1)
print(f"group {name}: base rate {rate:.0%}, selected {select:.0%}, "
f"precision {precision:.2f}")
parity_vs_precision()
Same selection rate for both groups -- demographic parity satisfied by construction -- and yet the precision comes out different, because the group with the higher base rate has more real positives to find at the same threshold. You equalized one fairness and broke another, and no amount of clever engineering closes that gap, because it is not an engineering gap. It is a theorem.
So fairness is not a technical problem with one correct answer. It is a value choice, and the model cannot make it for you. You have to decide, out loud and in writing, WHICH fairness matters most for THIS application -- and a loan model, a medical triage model, and a content-ranking model will each answer differently. That decision belongs to humans, ideally humans who include the people affected. What the code CAN do is enforce your choice once you have made it -- for instance, picking a separate threshold per group to hit a target positive rate:
import numpy as np
def group_thresholds(scores, groups, target_rate=0.20):
"""One concrete way to ENFORCE demographic parity: a separate score threshold
per group so each group gets the same positive rate."""
thresholds = {}
for g in np.unique(groups):
s = scores[groups == g]
thresholds[g] = float(np.quantile(s, 1 - target_rate))
return thresholds
scores = np.random.default_rng(1).normal(size=1000)
groups = np.random.default_rng(2).integers(0, 2, size=1000)
print(group_thresholds(scores, groups))
Nota bene: per-group thresholds are themselves ethically loaded -- in some jurisdictions treating groups differently is exactly what the law forbids, even when you do it to be fairer. There is no free lunch here. The tool enforces a decision; it does not absolve you of making it.
Transparency: model cards and datasheets
Documentation is the most boring and the most effective ethical practice there is. If you build a model and do not write down its capabilities, limits, and intended use, somebody WILL misuse it -- not out of malice, but because they never knew where the edges were.
Model cards (Mitchell et al., 2019) document a model the way a nutrition label documents food. Here is one filled in for a realistic, uncomfortable case:
model_card = {
"model_name": "LoanApproval-v2.3",
"model_type": "Gradient Boosted Classifier (XGBoost)",
"intended_use": "Pre-screening loan applications for MANUAL review",
"not_intended_for": [
"Fully automated loan decisions without human review",
"Applications outside the US market",
"Applicants under 18 years old",
],
"training_data": {
"source": "Internal loan application data, 2019-2024",
"size": "2.4M applications",
"demographics": "US adults, skewed toward ages 25-55",
"known_gaps": "Underrepresented: rural applicants, "
"non-English speakers, recent immigrants",
},
"performance": {
"overall_auc": 0.87,
"by_demographic": {
"group_w": {"auc": 0.89, "fpr": 0.08},
"group_x": {"auc": 0.83, "fpr": 0.14},
"group_y": {"auc": 0.85, "fpr": 0.11},
"group_z": {"auc": 0.88, "fpr": 0.07},
},
},
"limitations": [
"Performance degrades on applicants with thin credit files",
"Not validated for economic downturns",
"6-point AUC gap between the best- and worst-served groups",
],
"ethical_considerations": [
"FPR disparity means some groups are more likely to be "
"incorrectly rejected; human review is MANDATORY",
"Model uses zip code, which correlates with protected attributes; "
"impact analysis runs quarterly",
],
"update_schedule": "Retrained quarterly, bias audit monthly",
}
This is not bureaucracy for its own sake. When the model makes a wrong call that damages someone's life, the card is what tells you whether that failure sat inside the known limitations (an accepted, documented risk) or outside the intended use (a misuse someone will answer for). That distinction is the whole ballgame in a courtroom, and more importantly it is the whole ballgame in the code review where you decide whether to ship.
Datasheets for datasets (Gebru et al., 2018) apply the same discipline one layer down, to the training data itself: who collected it, why, from whom, with what consent, and with what known biases. If you cannot answer those questions about your data, you cannot make an honest claim about your model's fairness -- you are just hoping.
Environmental impact
Training big models has a real carbon cost, and hand-waving it away is its own little ethical failure. The training run for GPT-3 consumed an estimated 1,287 MWh of electricity -- roughly the annual draw of 120 average US homes -- and a single frontier run today uses a good deal more. For you and me, working at human scale, the number is smaller but not zero, and the nice thing is you can estimate it before you burn it:
def estimate_training_carbon(gpu_hours, gpu_type='A100',
region='us-west', pue=1.1):
"""Rough CO2 estimate for a training run.
PUE = Power Usage Effectiveness (datacenter overhead)."""
gpu_power = { # approximate board power draw, kW
'V100': 0.30,
'A100': 0.40,
'H100': 0.70,
}
carbon_intensity = { # kg CO2 per kWh, varies ENORMOUSLY by grid
'us-west': 0.21, # California, relatively clean
'us-east': 0.38, # more coal in the mix
'eu-west': 0.28, # Western Europe average
'eu-north': 0.05, # Nordic hydro / nuclear
'india': 0.71, # coal-heavy grid
}
power_kw = gpu_power.get(gpu_type, 0.40)
intensity = carbon_intensity.get(region, 0.40)
energy_kwh = gpu_hours * power_kw * pue
co2_kg = energy_kwh * intensity
print(f"GPU hours: {gpu_hours}")
print(f"Energy: {energy_kwh:.1f} kWh")
print(f"CO2: {co2_kg:.1f} kg ({co2_kg/1000:.2f} tonnes)")
print(f"Equivalent to roughly {co2_kg/0.255:.0f} km driven by an average car")
return co2_kg
# Example: fine-tuning a 7B model for 10 hours on 4 A100s
estimate_training_carbon(gpu_hours=40, gpu_type='A100', region='us-west')
Run it for the same job in eu-north versus india and the CO2 number swings by more than an order of magnitude -- the single biggest lever you own is WHERE the electrons come from. What actually moves the needle:
- Choose your region. A Nordic hydro datacenter emits 5 to 14 times less CO2 than the same run on a coal-heavy grid. Cloud consoles let you pick, and most people never bother.
- Train efficiently. Mixed precision (#125), gradient accumulation, and early stopping are not just speed tricks -- they are carbon tricks. Do not run 100 experiments when 20 answer the question.
- Use the smallest model that works. Distillation and quantization (#120) routinely buy you 90% of the quality at a fraction of the compute, at inference AND at training time.
- Measure and report it. Tools like CodeCarbon log emissions during a run. Put the number in your model card. A cost you never measure is a cost you will never cut.
Consent and data rights
Where does the training data come from? This is, I argue, the most practically contentious question in the whole field right now, and "I found it on the internet" is not a license.
Large language models are trained on web-scraped text: copyrighted books, news articles, blog posts, forum threads, code repositories, social media. Most of the humans who wrote all that never agreed to feed a model, and the law has noticed. The New York Times sued OpenAI. Artists sued Stability AI. The EU AI Act now demands disclosure of copyrighted training data. The US still has no comprehensive federal statute, so the shape of the rules is being decided case by case in courtrooms as we speak.
As a practitioner you cannot settle the lawsuits, but you CAN behave decently upstream, and it is mostly a filtering problem. Honor opt-out signals BEFORE a page ever enters your corpus:
def respects_opt_out(page):
"""A cheap pre-training filter: honor opt-out signals and licensing
before a page is ever allowed into the training set."""
if page.get("robots_noai"): # e.g. X-Robots-Tag: noai
return False
if "noai" in page.get("meta_tags", []): # <meta name="robots" content="noai">
return False
if page.get("license") in (None, "all-rights-reserved"):
return False # no clear license == not yours to train on
return True
pages = [
{"url": "a.com", "license": "cc-by", "robots_noai": False, "meta_tags": []},
{"url": "b.com", "license": None, "robots_noai": False, "meta_tags": []},
{"url": "c.com", "license": "cc0", "robots_noai": True, "meta_tags": []},
{"url": "d.com", "license": "cc-by-sa", "robots_noai": False, "meta_tags": ["noai"]},
]
keep = [p["url"] for p in pages if respects_opt_out(p)]
print("pages allowed into training set:", keep)
Only the cleanly-licensed, opt-in page survives the filter, and that is the point -- you default to EXCLUDE, not include. Beyond the filter, three habits keep you on the right side of this: know your sources and their licenses, prefer datasets that actually document where their text came from (The Pile, RedPajama and friends made a real effort here), and consider synthetic data (#133) where generating your own training examples sidesteps some -- not all -- of the consent problem.
A practical ethical framework
Enough principles. Here is the checklist I actually run through, organized around WHEN the decision happens, because ethics you only think about at launch is ethics you got wrong months earlier.
Before you build. Who is affected by this system? Have you talked to any of them? What happens when the model is wrong -- and who pays the price for that error, the company or the user? If the answer to "who bears the cost of a mistake" is "not us," you have a problem baked in before line one.
While you build. Is the training data representative of the people the system will touch? Have you actually run the bias audit, or just intended to? Are your metrics aligned with real user benefit, or with whatever was easy to log? A model optimized for the wrong metric is a bias generator with good intentions.
Before you deploy. Does the model card document the limitations honestly, including the embarrassing ones? Is there a human in the loop for high-stakes decisions? Do users even know they are talking to an AI? A quiet deployment is where accountability goes to die.
After you deploy. Are you monitoring for drift and for bias that emerges as the world shifts under the model? Is there a real feedback channel for the people the system got wrong? Do you have a kill switch, and have you ever tested it?
The common thread through all four, the single question this whole episode reduces to: think hard about the people affected by your system who are NOT in the room while you build it. They never see your standup, they never read your metrics dashboard, and they carry every consequence of the choices you make without them. Keeping them in mind is not soft. It is the entire discipline.
Exercises
Get your hands dirty before the next one. Three tasks, climbing in difficulty:
Add predictive parity to the auditor. Extend
BiasAuditorso each group also reports precision (of the samples it predicted positive, how many were truly positive). Add apredictive_parity_gapalongside the existing disparity metrics, and haveprint_reportflag the attribute with the widest gap. One sentence on which of the three fairness numbers you would put first for a medical-triage model, and why.Enforce parity and pay for it. Take
parity_vs_precisionand, in stead of one shared selection rate, usegroup_thresholdsto hit the SAME target positive rate in each group. Confirm demographic parity now holds exactly, then report the precision gap that stubbornly remains. Two sentences connecting what you see back to the impossibility result.Write a model-card linter. Given a
model_carddict, writecheck_card(card)that verifies every required key is present, thatperformance.by_demographicreports at least two groups, and thatethical_considerationsis non-empty -- printing exactly what is missing. Two sentences on why a machine-checkable card beats a beautifully-written PDF that nobody can audit.
We open next episode with full solutions, same as always.
The important bits
- bias auditing is measurable and systematic -- compute demographic parity, equalized odds, and predictive parity per group, then make an EXPLICIT decision about which one you are optimizing;
- the fairness definitions are provably incompatible -- when base rates differ you cannot satisfy them all, so choosing which fairness matters most is a human value judgement, not a bug to be fixed;
- model cards and datasheets are the most practical ethical tools we have -- document capabilities, limits, intended use, and per-group performance BEFORE deployment, because that is what separates a documented risk from a misuse;
- carbon footprints are real but manageable -- the grid region is your biggest lever, efficient training and smaller models are the next ones, and a cost you never measure is a cost you never cut;
- consent and copyright are in open legal flux -- default to excluding data, honor opt-outs at the filter, prefer documented and licensed corpora, and reach for synthetic data where it genuinely helps;
- the core practice is remembering the people who are not in the room -- before, during, and after building, because they carry every consequence of the choices you make without them.
And the thread I will leave dangling for next time. We have spent this series building the thing brick by brick -- the math, the networks, the transformers, the agents, the economics, and now the ethics of shipping it. Next we lift our eyes off the workbench and look at where all of this is actually heading: the parts that are still half-rumor and half-research, the ideas that are not in any textbook yet because they are being written THIS year. It is the most speculative episode we will do, and for exactly that reason one of the most fun ;-)
Congratulations @scipio! You have completed the following achievement on the Hive blockchain And have been rewarded with New badge(s)
Your next target is to reach 550 posts.
You can view your badges on your board and compare yourself to others in the Ranking
If you no longer want to receive notifications, reply to this comment with the word
STOPCheck out our last posts: