Cheat sheets

Shadow, Canary and A/B Tests for LLMs

Shadow runs, canary gates, fast rollback, sequential A/B tests, chaos guards and config drift reports.

  • Concepts and terms
  • Tasks and commands
  • Annotated code
  • Diagrams
  • Defaults and numbers
  • Troubleshooting
  • Questions and tips

Source

Canary gates in the simulator#

SettingDefaultNote
STAGES0.01, 0.10, 0.25, 0.50, 0.75, 1.00Six traffic shares
latency_p99_ms1.5Baseline 900 ms
cost_per_req1.2Baseline 0.02. Breach above 20%
error_rate2.05xx plus explicit refusals
output_len_p991.4Baseline 450 tokens
thumbs_down_rate1.5Baseline 0.03
rng.uniform noise0.92, 1.08Noise on each measured metric

From 17.20 code/main.py, Lesson 17.20

Sequential stop rule#

required = fixed_sample_size(baseline, lift)80% power, alpha 0.05
adjusted = int(required * 1.4)LLM non-determinism buffer
if n_a > 100 and n_b > 100 and sequential_stopped_at is None:
z = z_statistic(success_a, n_a, success_b, n_b)
n_total = n_a + n_b
threshold = math.sqrt(2 * math.log(1 / 0.05) + math.log(n_total))grows with log(n)
if abs(z) > threshold:
sequential_stopped_at = n_totalstop early on a clear winner

From 17.21 code/main.py

Chaos planes#

inject faultmetrics, traces, logsburn rateabortfindingsprocessControl planeexperiment schedulerprocessTarget planeservices, pods, datastoresstoreObservability planetrace-ID correlationprocessSafety planekill switch, budget gatesstoreFeedback loopSLO, runbooks, code fixesinject faultmetrics, traces, logsburn rateabortfindingsprocessControl planeexperimentschedulerprocessTarget planeservices, pods,data storesstoreObservabilityplanetrace-IDcorrelationprocessSafety planekill switch,budget gatesstoreFeedback loopSLO, runbooks,code fixes

From Lesson 17.24

Drift explainer commands#

CommandWhat it does
go run . --input fixtures/config.json --out /tmp/drift.jsonWrite the drift report to a file
go build -o /tmp/drift-explainer .Build the binary
/tmp/drift-explainer --input fixtures/config.json --checkReturn 2 when drift exists
python3 scripts/project_test.py configuration-drift-explainer --init <work>Make a fresh starter
python3 scripts/project_test.py configuration-drift-explainer --stage 1 --path <work> --strictGrade one stage
python3 scripts/project_test.py configuration-drift-explainer --all --solution --strictGrade the reference solution

From configuration-drift-explainer/README.md

Troubleshooting#

SymptomCauseFix
Cost up 40% a day after the flipNo shadow or canary stepShadow first. Gate cost at 20% over baseline
Rollback takes 3 hoursRollback needs a redeployFlip the policy flag. Pin the old digest
Canary gates false alarmGates sit inside the noise floorSet gates above the noise floor
Feedback gate noisy at 1%Only 50-150 data points per windowPause longer at each step
50/50 split gives 47/53SRM. Assignment is brokenFix the assignment hash first
A/B never reaches significanceNon-determinism lowers effective sample sizeMultiply sample size by 1.3 to 1.5
OOM after provider 429sGateway retries amplify concurrencyInject one provider 429 for 5 minutes
Secret value in the drift reportInnocent key name hides a secretAdd the key to sensitive

From Lesson 17.20, Lesson 17.21, Lesson 17.24, configuration-drift-explainer/README.md

Rollout sequence#

1Shadowthe candidate gets copies of productionrequests. Outputs go to logs, not to users.2Shadow logdiff the output. Log token counts, latency,refusals and errors.3Canaryshift traffic 1%, 10%, 25%, 50%, 75%, 100%.4Gatescheck five metrics at each step, every 5 to 15minutes.5A/Boptional. Run it at 50% when the new model isdistinctly different.6Promotego to 100% when all canary gates pass.7Rollbackrevert the flag. Set the pinned digest to theprevious model.

From Lesson 17.20

Run the labs#

TaskCode
Canary simulator, lesson 20code/main.py
Inject a 25% cost regressionRegression(cost_mult=1.25)
Inject an 80% latency regressionRegression(latency_mult=1.80)
Check one stage against the gatescheck_gates(metrics)
Sequential A/B simulator, lesson 21code/main.py
Sample size at 80% powerfixed_sample_size(baseline, lift)
Simulate a 5% liftsimulate(0.03, 0.0315)
Chaos runner, lesson 24run_experiment(e)

From 17.20 code/main.py, 17.21 code/main.py, 17.24 code/main.py

Rollout and A/B terms#

Shadow mode
zero user impact. A smoke test, not a quality test.
Canary
gradual rollout to real users, with metric gates.
Non-determinism
up to 15% accuracy variation run to run on identical inputs.
Policy flag
rollback in config. It takes seconds, not hours.
Model pin
immutable registry digest. It does not auto-upgrade.
Eval vs A/B
an eval asks "can the model do the job?" An A/B test asks "do users care?"
CUPED
regress out pre-period variance. Typical reduction is 30-70%.
SRM
sample ratio mismatch. A 50/50 split that gives 47/53 is broken.

From Lesson 17.20, Lesson 17.21

Chaos runner experiments#

CommandWhat it does
ERROR_BUDGET_PER_DAY = 0.00199.9% SLO
EXPECTED_ERROR_RATE = 0.0005Baseline error rate
burn_rate > 2.0First half of the abort rule
e.blast_radius_pct > 0.2Second half. Both must be true
pod kill (1 decode replica)5 min, 5% blast. Completes
provider 429 fallback5 min, 30% blast. Aborts
malformed prompt tokenizer stall3 min, 10% blast. Completes

From 17.24 code/main.py

Drift report#

Input
layers, observed and optional sensitive. Later layers win.
equal
the effective value and the observed value match.
changed
the values differ under reflect.DeepEqual.
missing
the layers declare the key. The observed config does not.
unexpected
the observed config has a key that no layer declares.
History
every layer that set the key, in order. Shadowed values stay.
Redaction
password, secret, token, api-key and credential names become [REDACTED].
Output
schemaVersion 1. The file has owner-only permissions 0600.

From solution/main.go, configuration-drift-explainer/README.md

Q&A#

Does shadow mode prove quality?
No. It catches cost, length, refusal and hard errors only.
When do you skip the A/B step?
When the new model is only an improved version.
Is "stable" equal to baseline?
No. Stable means within expected variance.
20 tests at 95% confidence?
One false positive by chance. Use Bonferroni or Benjamini-Hochberg.
Which chaos experiment aborts?
Provider 429. Burn rate is over 2.0 and blast radius is over 20%.
What does --check return on drift?
Exit status 2.

From Lesson 17.20, Lesson 17.21, 17.24 code/main.py, configuration-drift-explainer/README.md

Tips#

  • Fix the rollback path before you roll. It must not need a redeploy.
  • Track cost per request. A 20% better model can cost 3x per call.
  • Pause chaos when daily error-budget burn exceeds 2x expected.
  • First chaos test: pod-kill one decode replica under steady traffic.
  • Cadence: weekly canary, monthly game day, quarterly audit.
  • Put named source identifiers in layer names, never secret values.

From Lesson 17.20, Lesson 17.24, configuration-drift-explainer/README.md