LeetLLM
My PlanLearnGlossaryTracksPracticeBlog
LeetLLM

Your go-to resource for mastering AI & LLM systems.

Product

  • Learn
  • Glossary
  • Tracks
  • Practice
  • Blog
  • RSS

Legal

  • Terms of Service
  • Privacy Policy

© 2026 LeetLLM. All rights reserved.

All Topics
Your Progress
0%

0 of 177 articles completed

🛠️Computing Foundations0/9
Git, Shell, Linux for AIDocker for Reproducible AIPython for AI EngineeringNumPy and Tensor ShapesCUDA for ML TrainingMPS & Metal for ML on MacData Structures for AISQL and Data ModelingAlgorithms for ML Engineers
📊Math & Statistics0/8
Gradients and BackpropVectors, Matrices & TensorsLinear Algebra for MLAdam, Momentum, SchedulersProbability for Machine LearningStatistics and UncertaintyDistributions and SamplingHypothesis Tests, Intervals, and pass@k
📚Preparation & Prerequisites0/13
Neural Networks from ScratchCNNs from ScratchTraining & BackpropagationSoftmax, Cross-Entropy & OptimizationRNNs, LSTMs, GRUs, and Sequence ModelingAutoencoders and VAEsThe Transformer Architecture End-to-EndLanguage Modeling & Next TokensFrom GPT to Modern LLMsPrompt Engineering FundamentalsCalling LLM APIs in ProductionFirst AI App End-to-EndThe LLM Lifecycle
🧮ML Algorithms & Evaluation0/11
Linear Regression from ScratchLogistic Regression and MetricsDecision Trees, Forests, and BoostingReinforcement Learning BasicsValidation and LeakageClustering and PCACore Retrieval AlgorithmsDecoding AlgorithmsExperiment Design and A/B TestingPyTorch Training LoopsDataset Pipelines and Data Quality
📦Production ML Systems0/6
Feature Engineering for Production MLBatch and Streaming Feature PipelinesGradient Boosted Trees in ProductionRanking and Recommendation SystemsForecasting and Anomaly DetectionMonitoring Predictive Models
🧪Core LLM Foundations0/8
The Bitter Lesson & ComputeBPE, WordPiece, and SentencePieceStatic to Contextual EmbeddingsPerplexity & Model EvaluationFile Ingestion for AIChunking StrategiesLLM Benchmarks & LimitationsInstruction Tuning & Chat Templates
🧰Applied LLM Engineering0/24
Dimensionality Reduction for EmbeddingsCoT, ToT & Self-Consistency PromptingFunction Calling & Tool UseMCP & Tool Protocol StandardsContext EngineeringPrompt Injection DefenseResponsible AI GovernanceData Labeling and Human FeedbackEvaluating AI AgentsProduction RAG PipelinesHybrid Search: Dense + SparseReranking and Cross-Encoders for RAGRAG Evaluation for Reliable AnswersLLM-as-a-Judge EvaluationBias & Fairness in LLMsHallucination Detection & MitigationLLM Observability & MonitoringExperiment Tracking with MLflow and W&BPrompt Optimization with DSPyModel Versioning & DeploymentSemantic Caching & Cost OptimizationLLM Cost Engineering & Token EconomicsModel Gateways, Routing, and FallbacksDesign an Automated Support Agent
🎓Portfolio Capstones0/9
Capstone: Delivery ETA PredictionCapstone: Product RankingCapstone: Demand ForecastingCapstone: Image Damage ClassifierCapstone: Production ML PipelineCapstone: Document QACapstone: Eval DashboardCapstone: Fine-Tuned ClassifierCapstone: Reproducible ML Study
🧠Transformer Deep Dives0/8
Sentence Embeddings & Contrastive LossEmbedding Similarity & QuantizationScaled Dot-Product AttentionVision Transformers and Image EncodersPositional Encoding: RoPE & ALiBiLayer Normalization: Pre-LN vs Post-LNMechanistic InterpretabilityDecoding Strategies: Greedy to Nucleus
🧬Advanced Training & Adaptation0/16
Scaling Laws & Compute-Optimal TrainingPre-training Data at ScaleBuild GPT from Scratch LabJAX for PyTorch ResearchersContinued Pretraining for Domain ShiftSynthetic Data PipelinesSupervised Fine-Tuning PipelineMixed Precision TrainingDistributed Training: FSDP & ZeROLoRA & Parameter-Efficient TuningReward Modeling from Preference DataRLHF & DPO AlignmentConstitutional AI & Red TeamingRLVR & Verifiable RewardsKnowledge Distillation for LLMsModel Merging and Weight Interpolation
🤖Advanced Agents & Retrieval0/16
Vector DB Internals: HNSW & IVFAdvanced RAG: HyDE & Self-RAGGraphRAG & Knowledge GraphsRAG Security & Access ControlStructured Output GenerationReAct & Plan-and-ExecuteGuardrails & Safety FiltersCode Generation & SandboxingComputer-Use / GUI / Browser AgentsHuman-in-the-Loop Agent ArchitectureAI Coding Workflow with AgentsAgent Memory & PersistenceAgent Failure & RecoveryRecursive Language Models (RLM)Multi-Agent OrchestrationCapstone: Production Agent
⚡Inference & Production Scale0/19
Inference: TTFT, TPS & KV CacheMulti-Query & Grouped-Query AttentionKV Cache & PagedAttentionPrefix Caching and Prompt CachingFlashAttention & Memory EfficiencyContinuous Batching & SchedulingScaling LLM InferenceModel Parallelism for LLM InferenceModel Quantization: GPTQ, AWQ & GGUFLocal LLM DeploymentSLM Specialization & Edge DeploymentSpeculative DecodingLong Context Window ManagementMixture of Experts ArchitectureMamba & State Space ModelsReasoning & Test-Time ComputeAdvanced MLOps & DevOps for AIGPU Serving & AutoscalingA/B Testing for LLMs
🏗️System Design Capstones0/9
Content Moderation SystemCode Completion SystemMulti-Tenant LLM PlatformLLM-Powered Search EngineVision-Language Models & CLIPMultimodal LLM ArchitectureDiffusion Models: Images & TextReal-Time Voice AI AgentReasoning & Test-Time Compute
🎤AI Lab Interviewing0/4
AI Lab Coding Interview: Python SystemsAI Lab System Design InterviewAI Lab Behavioral InterviewAI Lab Technical Presentation
🔬Project Deep Dives0/17
Deep Dive - vLLMDeep Dive - SkyRLDeep Dive - FlashAttentionDeep Dive - FlashInferDeep Dive - DeepGEMMDeep Dive - NCCLDeep Dive - MegatronDeep Dive - DeepSpeedDeep Dive - RayDeep Dive - MLflowDeep Dive - PyTorchDeep Dive - TransformersDeep Dive - SGLangDeep Dive - slimeDeep Dive - DeepEPDeep Dive - TinkerDeep Dive - Light-PEFT
Back to Topics
LearnApplied LLM EngineeringBias & Fairness in LLMs
🛡️MediumAlignment & Safety

Bias & Fairness in LLMs

Build a matched-pair fairness audit for an LLM judge, measure routing gaps, and block release when evidence is too weak.

15 min read
Learning path
Step 70 of 177 in the full curriculum
LLM-as-a-Judge EvaluationHallucination Detection & Mitigation

Personalize this lesson

Adapt explanations and teaching visuals to your background and preferred voice.

A large language model (LLM) judge can agree with reviewers on average and still behave unevenly. If that judge sends equivalent requests down different routes for different customer language varieties, it can delay service for some customers even while its overall agreement score looks acceptable.

FairReply now wants to auto-serve a supported policy reply when the judge is confident, and send uncertain replies to a human reviewer. A fairness audit for that release decision asks one controlled question: when request facts and supported remedy stay fixed, does a change in language variety alter who receives the fast path?

These fixtures are invented and labeled. The two wording variants are test conditions, not demographic groups and not a claim about any community's speech. A real language-variety audit needs representative data, informed review, privacy controls, and careful group definitions.

Fairness starts with a consequence

A model can cause two broad kinds of harm. Representational harm occurs when output stereotypes, demeans, or erases a group. Allocative harm occurs when system behavior changes access to a benefit or burden, such as whether an eligible customer gets an immediate supported answer or waits for review. This distinction is central in surveys of bias and fairness in LLM systems.[1]Reference 1Bias and Fairness in Large Language Models: A Surveyhttps://aclanthology.org/2024.cl-3.8/

Our running case is allocative. The remedy is already authorized by the selected policy evidence. The new outcome is the route:

Decision componentHeld fixed or measured?Why it matters
Policy source and versionHeld fixedA fairness audit can't repair unsupported claims.
Replacement eligibilityHeld fixed within each matched pairEach pair should deserve the same answer.
Language-variety fixtureVaried within each pairIt's the audit condition.
Judge score and routeMeasuredUnequal routing is customer impact.

The distinction from the previous chapter is important. There, we asked whether a judge agrees with reviewers. Here, we ask whether its errors and routing decisions are unevenly distributed. The overview below plots every paired score against the actual route threshold, then separates measured gaps from the evidence still required for release.

Matched-pair audit shows equivalent prompts crossing a routing threshold and blocking release.
Three matched pairs flip at the same route threshold. Two ready cases lose auto-service, one unclear case gains it, both error-rate gaps fail, and the release stays blocked.

Build a matched-pair audit

A matched pair changes one audit condition while preserving task semantics. That's harder than swapping words mechanically. Two prompts belong in a pair only after reviewers agree they describe the same customer facts and should receive the same route.

For this lab, a human reviewer has labeled ten synthetic pairs. In six pairs the reply is supported and clear enough for auto-service. In four, it should go to review because wording of the proposed reply remains unclear. Both versions in a pair share the same expected outcome.

matched-audit-fixtures.py
1from collections import defaultdict 2from dataclasses import dataclass 3from math import sqrt 4 5@dataclass(frozen=True) 6class AuditRow: 7 pair_id: str 8 variant: str 9 expected_auto_serve: bool 10 judge_score: float 11 channel: str 12 evidence_passed: bool = True 13 14THRESHOLD = 0.70 15 16def route(row: AuditRow) -> str: 17 if not row.evidence_passed: 18 return "blocked_by_evidence" 19 return "auto_serve" if row.judge_score >= THRESHOLD else "human_review" 20 21# Synthetic observations: (pair, expected outcome, channel, formal score, conversational score) 22observations = [ 23 ("p1", True, "chat", 0.92, 0.88), 24 ("p2", True, "chat", 0.88, 0.73), 25 ("p3", True, "chat", 0.84, 0.72), 26 ("p4", True, "email", 0.78, 0.67), 27 ("p5", True, "email", 0.74, 0.56), 28 ("p6", True, "email", 0.66, 0.61), 29 ("n1", False, "chat", 0.71, 0.60), 30 ("n2", False, "chat", 0.62, 0.55), 31 ("n3", False, "email", 0.60, 0.52), 32 ("n4", False, "email", 0.68, 0.63), 33] 34 35rows: list[AuditRow] = [] 36for pair_id, expected, channel, formal_score, conversational_score in observations: 37 rows.extend([ 38 AuditRow(pair_id, "formal", expected, formal_score, channel), 39 AuditRow(pair_id, "conversational", expected, conversational_score, channel), 40 ]) 41 42assert len(rows) == 20 43assert all(row.evidence_passed for row in rows) 44assert { 45 row.pair_id: row.expected_auto_serve for row in rows if row.variant == "formal" 46} == { 47 row.pair_id: row.expected_auto_serve for row in rows if row.variant == "conversational" 48} 49 50print("Fixture type: synthetic matched wording audit") 51print(f"Matched pairs: {len(observations)}; scored rows: {len(rows)}") 52print(f"Routing threshold: {THRESHOLD:.2f}")
Output
1Fixture type: synthetic matched wording audit 2Matched pairs: 10; scored rows: 20 3Routing threshold: 0.70

The fixture intentionally creates a failure. If every example passed, we could demonstrate arithmetic but not diagnosis.

Look for pair flips first

A pair flip is the simplest warning sign: equivalent requests receive different routes. It doesn't yet prove a population-level disparity, but it tells the team exactly which cases require investigation.

pair-flips.py
1def rows_by_pair(audit_rows: list[AuditRow]) -> dict[str, list[AuditRow]]: 2 grouped: dict[str, list[AuditRow]] = defaultdict(list) 3 for row in audit_rows: 4 grouped[row.pair_id].append(row) 5 return grouped 6 7def matched_pair_flips(audit_rows: list[AuditRow]) -> list[tuple[str, str, str]]: 8 flips: list[tuple[str, str, str]] = [] 9 for pair_id, pair_rows in rows_by_pair(audit_rows).items(): 10 outcomes = {row.variant: route(row) for row in pair_rows} 11 if len(set(outcomes.values())) > 1: 12 flips.append((pair_id, outcomes["formal"], outcomes["conversational"])) 13 return flips 14 15by_pair = rows_by_pair(rows) 16flips = matched_pair_flips(rows) 17assert flips == [ 18 ("p4", "auto_serve", "human_review"), 19 ("p5", "auto_serve", "human_review"), 20 ("n1", "auto_serve", "human_review"), 21] 22 23print("Flipped matched pairs:") 24for pair_id, formal_route, conversational_route in flips: 25 print(f" {pair_id}: formal={formal_route}, conversational={conversational_route}")
Output
1Flipped matched pairs: 2 p4: formal=auto_serve, conversational=human_review 3 p5: formal=auto_serve, conversational=human_review 4 n1: formal=auto_serve, conversational=human_review

Two eligible replies lose the fast path under the conversational condition. One unclear reply gains the fast path under the formal condition. A single approval-rate number can't explain both errors.

Choose a metric from the harm

Four group metrics appear frequently in fairness work. They answer different product questions:

MetricCalculationQuestion for the router
Selection rateAuto-served / all requestsDoes one slice receive fast service more often?
True positive rate (TPR)Auto-served / replies reviewers say are readyDo ready replies receive fast service equally often?
False positive rate (FPR)Auto-served / replies reviewers say need reviewDoes one slice receive unsafe fast service more often?
CalibrationObserved ready rate among equal score bandsDoes a 0.80 score carry same meaning by slice?

Equal opportunity compares TPR across slices. Equalized odds compares both TPR and FPR. Hardt, Price, and Srebro formalized these error-rate criteria for supervised decision systems.[2]Reference 2Equality of Opportunity in Supervised Learning.https://arxiv.org/abs/1610.02413 For FairReply, delayed eligible help is the main harm, so TPR gap is the primary release metric. FPR gap remains a guardrail because faster service isn't a win if it releases unclear replies.

A confusion matrix makes those denominators visible. TPR reads only the ready-reply row. FPR reads only the row of replies that reviewers say need review.

Two wording slices show why fairness metrics read different error cells. Formal wording serves five of six ready replies and one of four unclear replies, while conversational wording serves three of six ready replies and zero of four unclear replies, creating a true positive gap and a false positive gap.
TPR reads the ready-reply row, so the slices differ by 33.3 points there. FPR reads the unclear-reply row, so they differ by 25 points there. Selection rate mixes both rows into one compressed total.

Calculate slice rates

slice-rates.py
1@dataclass(frozen=True) 2class Rates: 3 selection: float 4 tpr: float 5 fpr: float 6 positive_count: int 7 negative_count: int 8 9def slice_rates(slice_rows: list[AuditRow]) -> Rates: 10 positives = [row for row in slice_rows if row.expected_auto_serve] 11 negatives = [row for row in slice_rows if not row.expected_auto_serve] 12 selected = [row for row in slice_rows if route(row) == "auto_serve"] 13 true_positives = [row for row in positives if route(row) == "auto_serve"] 14 false_positives = [row for row in negatives if route(row) == "auto_serve"] 15 return Rates( 16 selection=len(selected) / len(slice_rows), 17 tpr=len(true_positives) / len(positives), 18 fpr=len(false_positives) / len(negatives), 19 positive_count=len(positives), 20 negative_count=len(negatives), 21 ) 22 23rates = { 24 variant: slice_rates([row for row in rows if row.variant == variant]) 25 for variant in ("formal", "conversational") 26} 27 28def gap(metric: str) -> float: 29 return abs(getattr(rates["formal"], metric) - getattr(rates["conversational"], metric)) 30 31for variant, result in rates.items(): 32 print( 33 f"{variant:14} selection={result.selection:.1%} " 34 f"TPR={result.tpr:.1%} FPR={result.fpr:.1%}" 35 ) 36print(f"TPR gap={gap('tpr'):.1%}; FPR gap={gap('fpr'):.1%}")
Output
1formal selection=60.0% TPR=83.3% FPR=25.0% 2conversational selection=30.0% TPR=50.0% FPR=0.0% 3TPR gap=33.3%; FPR gap=25.0%

This audit fails in both directions. Among replies reviewers marked ready, the conversational condition is routed to human review more often. Among replies that need review, the formal condition is incorrectly auto-served once.

Why isn't a 30 percentage point selection-rate gap enough to diagnose the failure?

Answer

Selection rate mixes correct and incorrect decisions. Here, one condition loses ready replies while the other incorrectly gains an unclear reply. TPR and FPR separate delayed service from unsafe fast service.

Turn metric choice into a gate

A release contract makes the choice reviewable. Thresholds below are product decisions for this lab, not universal definitions of fairness.

release-contract.py
1@dataclass(frozen=True) 2class FairnessContract: 3 primary_metric: str 4 max_tpr_gap: float 5 max_fpr_gap: float 6 min_positive_per_slice: int 7 min_negative_per_slice: int 8 9contract = FairnessContract( 10 primary_metric="equal_opportunity", 11 max_tpr_gap=0.10, 12 max_fpr_gap=0.10, 13 min_positive_per_slice=50, 14 min_negative_per_slice=30, 15) 16 17metric_checks = { 18 "TPR gap": gap("tpr") <= contract.max_tpr_gap, 19 "FPR guardrail": gap("fpr") <= contract.max_fpr_gap, 20} 21 22for name, passed in metric_checks.items(): 23 print(f"{name}: {'PASS' if passed else 'FAIL'}") 24assert metric_checks == {"TPR gap": False, "FPR guardrail": False}
Output
1TPR gap: FAIL 2FPR guardrail: FAIL

Tiny slices can't certify fairness

The audit found an actionable regression. It hasn't estimated production disparity. Six ready examples per wording condition are too few for a stable rate, and these fixtures don't identify a population.

One quick way to make that visible is a confidence interval. The Wilson interval below gives a plausible range for each TPR under binomial sampling. It isn't a complete statistical analysis, but it prevents a tiny dataset from looking decisive.

uncertainty-and-support.py
1def wilson_interval(successes: int, total: int, z: float = 1.96) -> tuple[float, float]: 2 proportion = successes / total 3 denominator = 1 + z * z / total 4 center = (proportion + z * z / (2 * total)) / denominator 5 radius = z * sqrt( 6 (proportion * (1 - proportion) + z * z / (4 * total)) / total 7 ) / denominator 8 return center - radius, center + radius 9 10for variant in ("formal", "conversational"): 11 variant_rows = [ 12 row for row in rows 13 if row.variant == variant and row.expected_auto_serve 14 ] 15 successes = sum(route(row) == "auto_serve" for row in variant_rows) 16 low, high = wilson_interval(successes, len(variant_rows)) 17 print( 18 f"{variant:14} TPR={successes}/{len(variant_rows)} " 19 f"95% interval=[{low:.1%}, {high:.1%}]" 20 ) 21 22enough_support = all( 23 result.positive_count >= contract.min_positive_per_slice 24 and result.negative_count >= contract.min_negative_per_slice 25 for result in rates.values() 26) 27print(f"Minimum slice support: {'PASS' if enough_support else 'FAIL'}") 28assert not enough_support
Output
1formal TPR=5/6 95% interval=[43.6%, 97.0%] 2conversational TPR=3/6 95% interval=[18.8%, 81.2%] 3Minimum slice support: FAIL

The intervals are wide because the fixture is small. That does not mean ignore the flips. It means use them as regression cases while collecting governed, reviewed evaluation data before making a population claim.

Calibration also needs slice support

The previous lesson used calibration to ask whether a judge agrees with reviewers. A fairness audit asks a stricter question: does a similar score carry similar meaning across slices? With ten rows per condition, score bands are diagnostic only.

calibration-by-slice.py
1def score_band(score: float) -> str: 2 if score < 0.70: 3 return "below 0.70" 4 if score < 0.90: 5 return "0.70 to 0.89" 6 return "0.90 and above" 7 8calibration_cells: dict[tuple[str, str], list[AuditRow]] = defaultdict(list) 9for row in rows: 10 calibration_cells[(row.variant, score_band(row.judge_score))].append(row) 11 12for (variant, band), cell in sorted(calibration_cells.items()): 13 observed_ready = sum(row.expected_auto_serve for row in cell) / len(cell) 14 print(f"{variant:14} {band:14}: n={len(cell)}, ready={observed_ready:.1%}") 15 16assert max(len(cell) for cell in calibration_cells.values()) < 10 17print("Calibration decision: insufficient support")
Output
1conversational 0.70 to 0.89 : n=3, ready=100.0% 2conversational below 0.70 : n=7, ready=42.9% 3formal 0.70 to 0.89 : n=5, ready=80.0% 4formal 0.90 and above: n=1, ready=100.0% 5formal below 0.70 : n=4, ready=25.0% 6Calibration decision: insufficient support

Plan intersections without pretending to measure them

Single-slice summaries can hide a failure limited to one channel, locale, or accessibility setting. Real systems therefore plan intersectional reports. They must also impose minimum support, because slicing a small audit repeatedly produces unstable numbers and privacy risks.

Intersectional fairness check across chat and email with formal and conversational wording shows every slice has only 3 ready replies against a 50-example target. Failures stay as regressions, but fairness certification is blocked because every cell is too small.
All four intersections are 47 examples short of the release contract. Keep failures as regressions, but block any fairness certification until each cell has real support.
intersection-support.py
1intersection_counts: dict[tuple[str, str], int] = defaultdict(int) 2for row in rows: 3 if row.expected_auto_serve: 4 intersection_counts[(row.channel, row.variant)] += 1 5 6for (channel, variant), count in sorted(intersection_counts.items()): 7 status = "eligible" if count >= contract.min_positive_per_slice else "insufficient" 8 print(f"{channel:5} / {variant:14}: n={count}, {status}") 9 10assert all(count == 3 for count in intersection_counts.values())
Output
1chat / conversational: n=3, insufficient 2chat / formal : n=3, insufficient 3email / conversational: n=3, insufficient 4email / formal : n=3, insufficient

In production, group definitions may involve sensitive attributes. Collect and expose them only under an approved purpose, access controls, privacy review, and any required consent or legal basis. A public dashboard with tiny protected-group cells can create harm while trying to measure it.

Find the failing stage before applying a fix

The matched pairs share policy evidence and human labels. Their routes diverge only after judge scoring. That localizes this lab's failure to the soft-evaluation and threshold layer. Rewriting customer text into a preferred register would conceal the symptom and ask customers to adapt to the system.

If a later investigation localizes a disparity to training data, counterfactual data augmentation (CDA) is one candidate experiment: add paired examples that alter an identity-related attribute while preserving the intended label. It isn't the first repair for this lab because the observed failure is in a deployed judge and router, not a proven training-set defect. CDA also needs review: careless swaps can change meaning, produce implausible text, or hide the group-specific harms you meant to measure.[1]Reference 1Bias and Fairness in Large Language Models: A Surveyhttps://aclanthology.org/2024.cl-3.8/

Root-cause map for three flipped matched pairs. Policy evidence and expected labels stay fixed, but formal and conversational judge scores split around the 0.70 threshold, so repair should target the evaluator or routing threshold rather than rewriting users or jumping to training-data changes.
The evidence and expected label stay fixed across all three flips. Repair the judge rubric, prompt, or threshold first; rewriting users or changing training data comes later, and only with a real data diagnosis.
locate-the-failure.py
1def changed_stage(pair_rows: list[AuditRow]) -> str: 2 if len({row.evidence_passed for row in pair_rows}) > 1: 3 return "evidence_gate" 4 if len({route(row) for row in pair_rows}) > 1: 5 return "judge_or_route" 6 return "no_observed_flip" 7 8attribution = { 9 pair_id: changed_stage(pair_rows) 10 for pair_id, pair_rows in by_pair.items() 11 if pair_id in {pair[0] for pair in flips} 12} 13 14print(attribution) 15assert set(attribution.values()) == {"judge_or_route"}
Output
1{'p4': 'judge_or_route', 'p5': 'judge_or_route', 'n1': 'judge_or_route'}

A reasonable next experiment is a revised rubric and judge prompt that focus on remedy correctness and actionable next steps rather than writing register. For teaching purposes, the following candidate rerun has equal rates and no matched-pair flips. Both checks matter: offsetting flips can cancel out in aggregate. The rerun still isn't release evidence because it uses the same synthetic cases that exposed the defect.

candidate-rerun.py
1candidate_scores = { 2 "p1": (0.92, 0.91), "p2": (0.86, 0.84), "p3": (0.81, 0.80), 3 "p4": (0.77, 0.75), "p5": (0.72, 0.71), "p6": (0.66, 0.65), 4 "n1": (0.62, 0.61), "n2": (0.60, 0.58), "n3": (0.55, 0.56), 5 "n4": (0.64, 0.62), 6} 7 8candidate_rows: list[AuditRow] = [] 9for pair_id, expected, channel, _, _ in observations: 10 formal_score, conversational_score = candidate_scores[pair_id] 11 candidate_rows.extend([ 12 AuditRow(pair_id, "formal", expected, formal_score, channel), 13 AuditRow(pair_id, "conversational", expected, conversational_score, channel), 14 ]) 15 16candidate_rates = { 17 variant: slice_rates([row for row in candidate_rows if row.variant == variant]) 18 for variant in ("formal", "conversational") 19} 20candidate_tpr_gap = abs(candidate_rates["formal"].tpr - candidate_rates["conversational"].tpr) 21candidate_fpr_gap = abs(candidate_rates["formal"].fpr - candidate_rates["conversational"].fpr) 22candidate_flips = matched_pair_flips(candidate_rows) 23 24print(f"Candidate TPR gap={candidate_tpr_gap:.1%}; FPR gap={candidate_fpr_gap:.1%}") 25print(f"Candidate pair flips={candidate_flips}") 26print("Interpretation: regression repaired on synthetic pairs, not validated for release") 27assert candidate_tpr_gap == 0 28assert candidate_fpr_gap == 0 29assert candidate_flips == []
Output
1Candidate TPR gap=0.0%; FPR gap=0.0% 2Candidate pair flips=[] 3Interpretation: regression repaired on synthetic pairs, not validated for release

Why shouldn't the team transform conversational customer requests into formal language before judging them?

Answer

That hides a measured failure instead of establishing equal treatment, may change meaning, and treats one register as the product default. Repair and validate the evaluator on representative, reviewed inputs rather than asking users to conform to it.

A candidate rerun has zero TPR and FPR gaps, but two matched pairs still flip in opposite directions. Has the synthetic regression suite passed?

Answer

No. Aggregate rates can cancel out while equivalent requests still receive different routes. Require both acceptable slice gaps and no matched-pair flips on retained regression cases.

Public benchmarks and product audits play different roles

Product-specific matched pairs test the actual route customers experience. Public benchmarks provide broader regression coverage:

Evaluation sourceWhat it testsAppropriate use here
Matched FairReply pairsRouting consistency for supported policy repliesPrimary product release audit
WEAT / SEATWhether word or sentence representations encode tested association patternsDiagnostic probe when you can inspect embedding behavior; not a routing outcome measure[3]Reference 3Semantics derived automatically from language corpora contain human-like biases.https://doi.org/10.1126/science.aal4230[4]Reference 4On measuring social biases in sentence encoders.https://doi.org/10.18653/v1/n19-1063
StereoSetWhether a language model assigns stronger preference to stereotypical than anti-stereotypical continuations in its test contextsProbability-level stereotype regression probe[5]Reference 5StereoSet: Measuring stereotypical bias in pretrained language models.https://arxiv.org/abs/2004.09456
RealToxicityPrompts / BOLDToxic degeneration from prompts and open-ended generation about demographic groupsGeneration-level audit set that needs human review and product-specific slices[6]Reference 6RealToxicityPrompts: Evaluating Neural Toxic Degeneration in Language Models.https://arxiv.org/abs/2009.11462[7]Reference 7BOLD: Dataset and Metrics for Measuring Biases in Open-Ended Language Generation.https://arxiv.org/abs/2101.11718
BBQWhether question answering relies on stereotypes when context is ambiguous or disambiguatedBroad stereotype regression probe[8]Reference 8BBQ: A Hand-Built Bias Benchmark for Question Answering.https://arxiv.org/abs/2110.08193
Reviewed toxicity slicesWhether a safety evaluator flags language varieties unevenlyEvaluator audit; Sap et al. showed dialect-related false-positive risk in hate-speech detection.[9]Reference 9The Risk of Racial Bias in Hate Speech Detection.https://aclanthology.org/P19-1163/

These benchmarks operate at different layers. WEAT or SEAT can expose associations in representations even when generated outputs look harmless; RealToxicityPrompts or BOLD can expose output harms without explaining which internal representation caused them. Don't treat a benchmark pass as proof that customer routing is fair. A benchmark tests its own prompt distribution and label design. Don't treat one product slice as full safety coverage either. Use both, and keep the limitation attached to every report.

Why not optimize every metric?

Fairness metrics can conflict. When outcome prevalence differs across groups and predictions aren't perfect, a score calibrated within each group generally can't also equalize false-positive and false-negative rates across groups. Chouldechova demonstrated this incompatibility for risk scoring systems.[10]Reference 10Fair prediction with disparate impact: A study of bias in recidivism prediction instruments.https://arxiv.org/abs/1610.07524 The engineering response isn't to give up or chase a single universal score. It's to define the customer harm, select a primary metric, monitor important counter-metrics, and document the accepted trade-off.

In this lab, the chosen outcome is rapid access to a supported reply. Equal opportunity is primary because it asks whether replies reviewers mark ready reach the fast path similarly. The FPR guardrail prevents a superficial fix that merely auto-serves more unclear replies.

Why not make calibration, equal TPR, and equal FPR universal hard gates for every model?

Answer

When outcome prevalence differs across slices and predictions aren't perfect, those criteria can conflict. Name the customer harm, choose a primary metric, keep important counter-metrics visible, and document the accepted trade-off instead of pretending one configuration satisfies every goal.

Write a release decision, not a fairness slogan

A fairness report must say what was tested, what failed, and why a candidate can't yet ship. That keeps a clean toy rerun from being promoted into an unsupported production claim.

Five fairness release gates in sequence. Synthetic regression passes with zero TPR gap, zero FPR gap, and zero matched-pair flips; reviewed slices, minimum support, privacy-approved groups, and a monitoring owner remain missing, so promotion is blocked at one of five gates passed.
The retained rerun clears one gate. Review coverage, support floors, group and privacy approval, and monitoring ownership are still missing, so the candidate can't ship.
fairness-release-decision.py
1release_requirements = { 2 "synthetic_regression_pairs_pass": not candidate_flips 3 and candidate_tpr_gap <= contract.max_tpr_gap 4 and candidate_fpr_gap <= contract.max_fpr_gap, 5 "representative_reviewed_slice_set": False, 6 "minimum_positive_and_negative_support": False, 7 "approved_group_definition_and_privacy_review": False, 8 "production_monitoring_owner": False, 9} 10 11failures = [ 12 requirement 13 for requirement, passed in release_requirements.items() 14 if not passed 15] 16decision = "APPROVED" if not failures else "BLOCKED" 17 18print(f"Metric promotion: {decision}") 19for failure in failures: 20 print(f" missing: {failure}") 21 22assert decision == "BLOCKED"
Output
1Metric promotion: BLOCKED 2 missing: representative_reviewed_slice_set 3 missing: minimum_positive_and_negative_support 4 missing: approved_group_definition_and_privacy_review 5 missing: production_monitoring_owner

A blocked result is progress. The team now has reproducible regressions, a primary metric, counter-metric guardrails, a likely failing stage, and explicit evidence still needed before release.

Complete the lesson

Mastery Check

Answer every question, then check your score. Score 75% or higher to mark this lesson complete.

1.An LLM judge sends confident supported policy replies to auto-service and uncertain replies to human review. The remedy is already authorized, but equivalent ready requests in one wording condition wait longer. How should the audit frame the harm and primary metric?

Correct answer: Allocative harm; compare TPR across wording slices so ready replies get the fast path equally, with FPR as a guardrail.

The customer consequence is access to the fast path, so the harm is allocative rather than representational. TPR measures whether replies reviewers marked ready are auto-served at similar rates, while FPR remains necessary to prevent equalizing by fast-tracking unclear replies.

2.Two candidate prompts are proposed as a matched pair for a route audit. Which reviewer finding would disqualify them?

Correct answer: One prompt changes customer facts or expected eligibility, so the two versions no longer deserve the same route.

A matched pair is valid only when reviewers agree the two prompts preserve the same customer facts, policy support, and expected outcome. If eligibility or task semantics change, a route difference may be justified rather than evidence of uneven treatment.

3.Each wording slice contains six ready replies and four replies that need review. The formal slice auto-serves five ready and one unclear reply; the conversational slice auto-serves three ready and no unclear replies. A contract limits both TPR and FPR gaps to 10 percentage points. What is the result?

Correct answer: TPR gap 33.3 points and FPR gap 25 points; both checks fail.

Formal TPR is 5/6, or 83.3%, while conversational TPR is 3/6, or 50.0%, producing a 33.3-point gap. Formal FPR is 1/4, or 25.0%, while conversational FPR is 0/4, producing a 25-point gap. Both exceed the 10-point limits. Unlike selection rate, these metrics separate delayed ready replies from unclear replies sent to the fast path.

4.A candidate rerun has zero TPR gap and zero FPR gap, but two retained matched pairs still route differently in opposite directions. Has the synthetic regression suite passed?

Correct answer: No. Aggregate rates can cancel out while equivalent requests still receive different routes, so retained matched pairs must also stop flipping.

A matched pair tests the invariant that equivalent requests with the same expected outcome should receive the same route. Equal aggregate TPR and FPR can hide offsetting pair-level failures, so the regression suite requires both acceptable slice gaps and no retained pair flips.

5.Why should a release contract choose a primary fairness metric and guardrails instead of making calibration, equal TPR, and equal FPR universal hard gates for every model?

Correct answer: When prevalence differs across slices and predictions are imperfect, these criteria can conflict, so the contract should name the harm, choose the primary metric, and monitor counter-metrics.

Fairness metrics answer different product questions and can be mathematically incompatible under realistic conditions. In the lesson, delayed eligible service makes TPR gap the primary metric, while FPR remains a guardrail against solving delay by auto-serving unclear replies.

6.The candidate judge has zero TPR gap, zero FPR gap, and no pair flips on the same ten synthetic paired fixtures. May it control production routing now?

Correct answer: No. It has only repaired a small synthetic regression suite; release still needs representative reviewed slice data, sample support, privacy and group-definition review, and monitoring ownership.

The synthetic rerun is useful as a regression check, not as production evidence. The lesson's release decision remains blocked because the candidate hasn't been validated on governed, representative reviewed data and lacks required privacy and monitoring plans.

7.Within every flipped pair, policy evidence, eligibility labels, and the routing threshold are fixed; only judge scores and routes diverge by wording. Which next experiment is most directly targeted?

Correct answer: Revise the judge rubric and prompt to focus on remedy correctness and actionable next steps, then rerun reviewed matched pairs.

Because evidence, eligibility, and threshold are fixed, the observed divergence is localized to judge scoring or routing behavior. A revised evaluator rubric directly targets that stage. Rewriting customer language conceals the failure and may change meaning, while training-time augmentation is premature without evidence of a training-data cause.

8.A model passes BBQ and StereoSet, but the product route is an LLM judge threshold deciding whether supported replies are auto-served. What conclusion is justified?

Correct answer: The benchmark pass is useful regression evidence, but it doesn't validate the product route, matched slices, threshold, or production traffic.

Public benchmarks test their own prompt distributions, labels, and model behaviors. They can be useful regression probes, but they don't execute the supported-reply judge, threshold, routing policy, or production traffic slices that determine this product's allocative outcome.

9.An audit has six ready examples per wording slice, wide Wilson intervals, and only three ready examples in each channel-by-wording cell. It also contains matched-pair flips. How should the team report this evidence?

Correct answer: Mark rates, calibration bands, and intersections as insufficient for population claims; retain the flips as regressions and collect governed reviewed data.

Tiny slices produce unstable rates, wide uncertainty intervals, and unsupported calibration or intersectional conclusions. That limits population claims but doesn't erase observed pair failures. The flips should remain regression cases while the team gathers representative, reviewed data under approved privacy and group-definition controls.

9 questions remaining.

Next Step
Continue to Hallucination Detection & Mitigation

You can now block a soft evaluator when outcomes differ across controlled slices or evidence is too weak. Next you'll block fluent answers when their factual claims aren't supported by retrieved evidence.

PreviousLLM-as-a-Judge Evaluation
Share this article
XFacebookLinkedInBlueskyRedditHacker NewsEmail
References

Bias and Fairness in Large Language Models: A Survey

Gallegos, I. O., Rossi, R. A., Barrow, J., et al. · 2024

https://aclanthology.org/2024.cl-3.8/

Equality of Opportunity in Supervised Learning.

Hardt, M., Price, E., & Srebro, N. · 2016 · NeurIPS 2016

https://arxiv.org/abs/1610.02413

Semantics derived automatically from language corpora contain human-like biases.

Caliskan, A., Bryson, J. J., & Narayanan, A. · 2017 · Science 356(6334)

https://doi.org/10.1126/science.aal4230

On measuring social biases in sentence encoders.

May, C., Wang, A., Bordia, S., Bowman, S. R., & Rudinger, R. · 2019 · NAACL 2019

https://doi.org/10.18653/v1/n19-1063

StereoSet: Measuring stereotypical bias in pretrained language models.

Nadeem, M., et al. · 2020 · ACL 2021

https://arxiv.org/abs/2004.09456

RealToxicityPrompts: Evaluating Neural Toxic Degeneration in Language Models.

Gehman, S., Gururangan, S., Sap, M., Choi, Y., & Smith, N. A. · 2020

https://arxiv.org/abs/2009.11462

BOLD: Dataset and Metrics for Measuring Biases in Open-Ended Language Generation.

Dhamala, J., Sun, T., Kumar, V., Krishna, S., Pruksachatkun, Y., Chang, K.-W., & Gupta, R. · 2021 · FAccT 2021

https://arxiv.org/abs/2101.11718

BBQ: A Hand-Built Bias Benchmark for Question Answering.

Parrish, A., et al. · 2022 · ACL 2022

https://arxiv.org/abs/2110.08193

The Risk of Racial Bias in Hate Speech Detection.

Sap, M., Card, D., Gabriel, S., Choi, Y., & Smith, N. A. · 2019 · ACL 2019

https://aclanthology.org/P19-1163/

Fair prediction with disparate impact: A study of bias in recidivism prediction instruments.

Chouldechova, A. · 2017 · Big Data

https://arxiv.org/abs/1610.07524

Discussion

Questions and insights from fellow learners.

Discussion loads when you reach this section.