Code for the paper "The bias is in the generator: locating medical chain-of-thought subgroup gaps upstream of the verifier" (AACL-IJCNLP 2026, Main Conference).
The paper asks where a subgroup accuracy gap comes from in verifier-guided best-of-N decoding: from the generator (correct answers are sampled less often for one subgroup) or from the verifier (it selects differently between subgroups). This repository contains everything needed to rerun the experiments and to recompute every number, table and figure in the paper.
Contents
All results use AMQA, a benchmark of adversarial demographic counterfactuals of USMLE-style questions (white vs. black, male vs. female, high vs. low income).
| Included (MIT license) | Not included |
|---|---|
| The full pipeline: data loaders, candidate generation, verifier scoring, LoRA / DPO training, analysis | The run outputs (generated candidates, verifier scores) and the trained adapters. They are large and derived from third-party datasets; regenerate them with the GPU job templates. |
Scripts that recompute and check every paper number (public/reproduce.sh) and redraw every figure | The datasets (download them from their sources, below) |
| SLURM job templates for every GPU run | Model weights (downloaded from Hugging Face) |
No real patient data is used: every dataset is made of examination vignettes.
conda create -n vpb python=3.12
conda activate vpb
pip install -r public/requirements.txt
public/requirements.txt lists minimum versions. The paper's runs used NVIDIA's PyTorch 25.10 container (torch 2.9.0a0) with transformers 5.5.0 and peft 0.13.2; install these exact versions (pip install transformers==5.5.0 peft==0.13.2). Newer transformers releases remove the warmup_ratio training argument that public/training/lora_train.py uses, and can change chat templates and tokenization (see What to expect when you rerun).
| Dataset | Used for | Where to get it |
|---|---|---|
| AMQA (Xiao et al., 2025) | Main benchmark (race, sex, income) | The human-reviewed release AMQA_Dataset/AMQA_Dataset.jsonl from github.com/XY-Showing/AMQA (commit c6ab56a7, 801 lines, sha256 63a24d945fd49c2f8baf25256312a91878d95e3cc5c5f08b5228f04d1bb270f8). |
| MedQA-USMLE | Direct-substitution control | Downloaded automatically (GBaker/MedQA-USMLE-4-options on Hugging Face) |
| DiversityMedQA | Ancestry-prefix control | Downloaded automatically (dhiyaan/DiversityMedQA on Hugging Face) |
| MedMCQA | Independent-corpus probes | Downloaded automatically (openlifescienceai/medmcqa on Hugging Face) |
Important: set
AMQA_FILE. The code stops ifAMQA_FILEis not set. Use the human-reviewed file above, not the copy on Hugging Face (Showing-KCL/AMQA), which is the unrevised release and gives different numbers.
Some models are gated on Hugging Face: accept their terms before downloading (meta-llama/Llama-3.1-8B-Instruct, google/gemma-3-27b-it, google/medgemma-27b-it, epfl-llm/meditron-7b).
| Role | Models |
|---|---|
| Primary generator | FreedomIntelligence/HuatuoGPT-o1-8B |
| Other generators | Med42-8B and Med42-70B (m42-health/Llama3-Med42-*), OpenBioLLM-70B, Mistral-7B-Instruct-v0.3, Llama-3.1-8B-Instruct, Gemma-3-27B-it, BioMistral-7B, MedGemma-27B-it, Meditron-7B |
| Verifiers | FreedomIntelligence/HuatuoGPT-reward-model-7B, Skywork/Skywork-Reward-Llama-3.1-8B-v0.2, the generator itself as self-judge |
| Perturbation diagnostic only | RLHFlow/ArmoRM-Llama3-8B-v0.1, GAIR/ReasonEval-7B, zarahall/fairness-reward-model (FRM) |
Some job templates load local copies through MODEL_DIR (for example $MODEL_DIR/Llama3-Med42-70B).
export AMQA_FILE=/path/to/AMQA_Dataset.jsonl # required (see above)
export OUTPUTS_DIR=/path/to/outputs # where runs are written and read; default: <repo>/outputs
export HF_HOME=/path/to/hf_cache # Hugging Face cache
export MODEL_DIR=/path/to/local/models # only for templates that load local model copies
Once the runs exist under OUTPUTS_DIR, one command recomputes every number in the paper and prints it next to the printed value with MATCH or MISMATCH. Topics whose inputs are missing are reported as SKIP; the command exits with an error if anything mismatches or is skipped.
bash public/reproduce.sh # all topics (about ten minutes on CPU)
bash public/reproduce.sh --quick # skip the slow bootstraps
bash public/reproduce.sh --list # list the topics
bash public/reproduce.sh --only locus,recipes # run selected topics
Each topic is a module in public/paper/<topic>/ and can also be run alone, for example PYTHONPATH=. python -m public.paper.locus.locus.
Redraw all figures with:
bash public/paper/figures/make_figures.sh
Where outputs go. Run directories sit directly under OUTPUTS_DIR (for example outputs/p4_amqa/, outputs/p4_amqa_seed1/). Outputs of the scripts in public/paper/<topic>/ go to OUTPUTS_DIR/paper/<topic>/; the five-seed fairness, Med42-8B and transfer runs go to OUTPUTS_DIR/paper/five_seed/. Each topic directory can be redirected with PAPER_<TOPIC>_DIR (see public/paper/common.py). Three topics write derived files (holm, tagger_and_strata, medmcqa, into PAPER_HOLM_DIR, PAPER_TAGGER_DIR and PAPER_MEDMCQA_WORK_DIR); point these at a scratch directory to keep the run outputs untouched. Runs kept elsewhere can be found through OUTPUTS_FALLBACK_DIRS (colon-separated directories searched after OUTPUTS_DIR).
All runs used H100 80 GB GPUs: one GPU per job, two for the 70B generators. LoRA training needs a full 80 GB GPU because the base model is loaded in fp32; one training run takes four to eight minutes. A generation run (400 pairs × 16 samples, then scoring) fits within the 6 to 8 hours the templates request.
Submit jobs from the repository root. Scheduler lines (account, partition, QOS) are site-specific; each template marks where to put yours. Every template sources public/slurm/env.sh, which activates your environment (set ENV_SETUP, for example ENV_SETUP="conda activate vpb") and puts the repository on PYTHONPATH.
| Runs | Job template |
|---|---|
| AMQA baselines: race primary run (seed 0) and seeds 1 to 5, sex and income primary runs, N_max=32 run | public/slurm/p4_amqa.sbatch (SEED=0..5), p4_amqa_axes.sbatch, p4_amqa_n32.sbatch |
| Five-seed runs (seeds 1 to 5): fairness instructions, Med42-8B baseline and fairness instructions, AMQA-sex and AMQA-income baselines, race-trained LoRA on sex and income | public/paper/five_seed/launch_five_seed.sh (resume_score.sbatch finishes a run that stopped after self-judge scoring) |
| HuatuoGPT-RM, Skywork-RM and FRM scoring | public/slurm/p4_rescore_rm.sbatch, p4_rescore_skywork.sbatch |
| Other generators | public/slurm/p4_amqa_med42.sbatch, p4_amqa_med42_70b.sbatch, p4_amqa_gemma3_27b.sbatch, p4_amqa_newgen.sbatch, p4_amqa_biomistral.sbatch, p4_amqa_medgemma.sbatch, p4_amqa_meditron.sbatch, openbio70b_*.sbatch |
| MedQA-USMLE and DiversityMedQA controls | public/slurm/p4_gap.sbatch, p4_diversitymedqa.sbatch |
| Fairness instructions (seed 0); paired LoRA (seeds 0 to 5), single-variant controls, target styles, hyperparameter sweep, Med42-8B | public/slurm/p4_intervention_v1.sbatch, lora_train*.sbatch, p4_intervention_*.sbatch, launch_*.sh |
| Base-model self-judge for LoRA runs (all N=16 LoRA numbers) | public/paper/base_judge/rescore_basejudge.sbatch |
| Training recipes R1, R2, R3 (HuatuoGPT-o1-8B and Med42-8B) | public/paper/recipes/launch_recipes.sh |
| Single-BOS rescoring | public/paper/bos_sensitivity/bos_rescore.sbatch |
| Adapter precision check | public/paper/adapter_precision/merge_check.sbatch |
| Free-text generations and judges | public/slurm/p4_amqa_freetext.sbatch, freetext_lora.sbatch, public/paper/free_text/freetext_judge.sbatch |
| Race × sex probes | public/paper/race_by_sex/race_by_sex.sbatch (sex word), public/slurm/llm_intersectional_edit_then_run.sbatch (LLM-edited) |
| Perturbation diagnostic | public/paper/perturbation/tier1_scorer.sbatch (one job per verifier) |
| MedMCQA probes | public/paper/medmcqa/medmcqa_edit.sbatch, medmcqa_eval.sbatch |
See public/slurm/README.md for the full list of variables the templates read.
Run from the repository root with PYTHONPATH=.. Every python -m public.paper... command prints the recomputed values with MATCH / MISMATCH. Numbering follows the final paper: Tables 1–7 and Figures 1–4 in the main text, Tables A1–A18 and Figures A1–A4 in the appendix.
Main text
| Paper item | Content | Command |
|---|---|---|
| Figure 1 | Overview schematic | Drawn by hand; its numbers come from Table 1, Section 4.2 and Table A11 |
| Figure 2 | Perturbation diagnostic, six verifiers | public/analysis/plot_tier1_5way.py --in-dir <perturbation dir> |
| Figure 3 | Oracle, self-consistency and self-judge at N=16 | public/analysis/plot_oracle_vs_bon.py (see make_figures.sh) |
| Figure 4 | Single-variant ablation | public/analysis/plot_singlevariant.py |
| Table 1; Tables 2, A3, A4, A6; Section 4.2 | Gap at N=1; change from N=1 to N=16 and Holm tests; N_max=32 run; Skywork-RM over the LoRA; oracle, self-consistency, FRM selector | python -m public.paper.locus.locus |
| Tables 3, 6, 7 | Interventions, transfer to other axes and to Med42-8B | python -m public.paper.interventions.interventions (DPO rows: public.paper.recipes.collect_recipes) |
| Table 4 | Training recipes R1 to R3 | python -m public.paper.recipes.collect_recipes |
| Table 5 | Seven generators: gap, oracle, amplification | python -m public.paper.generators.generators; Amp. column: python -m public.paper.holm.multigen_amplification |
| Section 3, 4.1 | MedQA-USMLE: 208 of 400 pairs, verifier accuracies, Pearson r | python -m public.paper.medqa_pairs.medqa_numbers |
| Section 4.1 | Perturbation numbers | python -m public.paper.perturbation.collect_tier1 |
| Section 4.4 | MedMCQA probes | python -m public.paper.medmcqa.medmcqa_results --check |
| Section 4.4 | Race × sex interaction | python -m public.paper.race_by_sex.collect_race_by_sex |
| Section 4.4 | Free text | python -m public.paper.free_text.collect_free_text |
Appendix
| Paper item | Content | Command |
|---|---|---|
| Table A1; App. A | Gap under four conventions for unparsed outputs; unparsed rates | python -m public.paper.parser.parser_numbers |
| Table A2; App. C | Single vs duplicated BOS token | python -m public.paper.bos_sensitivity.collect_bos |
| App. C | Adapter precision | python -m public.paper.adapter_precision.collect_merge --check |
| Tables A5, A9, A11, A12, A17; App. F, H, K | Per-seed LoRA, sweep, single-variant, Med42-8B, target styles | python -m public.paper.interventions.interventions |
| Table A7 | First sample vs mean of 16 samples | python -m public.paper.sixteen_sample.sixteen_sample_table |
| Tables A8, A15; App. F, J | Relevance strata, tagger counts, per-condition gaps, race mention | python -m public.paper.tagger_and_strata.collect_strata |
| Table A10 | Verifier separation and AUROC by subgroup | python -m public.paper.calibration.collect_calibration |
| Tables A13, A18; App. L | Med42-8B DPO and controls; recipe variants per seed | python -m public.paper.recipes.collect_recipes |
| Table A14; App. I | Generator coverage; Gemma-3-27B-it; floored generators | python -m public.paper.generators.generators, python -m public.paper.holm.multigen_amplification |
| Table A16; App. J | Free-text gap | python -m public.paper.free_text.collect_free_text |
| App. B | Training loss and gradient norms | python -m public.paper.training_logs.summarize_lora_log --check <training logs> |
| Figure A1 | Best-of-N on MedQA-USMLE | bash public/paper/medqa_pairs/run_pipeline.sh |
| Figure A2 | AMQA race, sex and income under three verifiers | public/analysis/plot_cross_axis.py |
| Figure A3 | Race × sex | python -m public.paper.race_by_sex.collect_race_by_sex --make-fig |
| Figure A4 | Interventions, gap versus N | public/analysis/plot_interventions.py |
public/data/load_eval.parse_answer is used everywhere: a leading answer letter, then "answer is X" and similar forms, then the last standalone A/B/C/D in the final 300 characters. Outputs without an answer count as incorrect.public/paper/base_judge/rescore_basejudge.sbatch.run_p4.py, inference/score_with_rm.py). The free-text judges (Appendix J) do see the options.generation_config.json and the transformers defaults.public/paper/bos_sensitivity/).public/paper/medmcqa/audit_labels.py) records the labels of the paper's own audit and applies only to the paper's edits._backup). For R1 seed 3, the first job wrote all 12,800 generations but stopped before scoring; the paper uses the first job's generations (both give the same N=1 gap; race mention is 35.4% vs. 35.6%). public/paper/common.resolve_candidates encodes this rule. The Med42-8B baseline of seed 3 stopped after self-judge scoring; its remaining steps were run on the saved candidates (public/paper/five_seed/resume_score.sbatch).public/paper/flip_rates/patch_flip_metrics.py rewrites them.public/
reproduce.sh, reproduce.py recompute and check every paper number (no GPU)
run_p4.py best-of-N pipeline: generate, score, metrics
paths.py OUTPUTS_DIR handling
data/ dataset loaders, counterfactual-pair construction, answer parser
inference/ verifier scoring, perturbation diagnostic, free-text generation and judging
training/ paired LoRA (SFT), R1 targets, R2 (KL), R3 (DPO)
analysis/ amplification tests, oracle / self-consistency, tagger, race mention, AUROC, figures
paper/ one folder per paper topic: collectors that print the paper's numbers,
and the job templates for that topic
slurm/ job templates for the pipeline runs (see slurm/README.md)
@inproceedings{nnamdi2026biasingenerator,
title = {The bias is in the generator: locating medical chain-of-thought subgroup gaps upstream of the verifier},
author = {Nnamdi, Micky C. and Marteau, Benoit Louis and Tan, Shaun Qien Yeau and Tamo, J. Ben and Yuan, Yining and Wang, May Dongmei},
booktitle = {Proceedings of AACL-IJCNLP 2026},
year = {2026}
}
Code: MIT (see LICENSE). Datasets and models keep their own licenses.
Code for the paper "The bias is in the generator: locating medical chain-of-thought subgroup gaps upstream of the verifier" (AACL-IJCNLP 2026, Main Conference).
The paper asks where a subgroup accuracy gap comes from in verifier-guided best-of-N decoding: from the generator (correct answers are sampled less often for one subgroup) or from the verifier (it selects differently between subgroups). This repository contains everything needed to rerun the experiments and to recompute every number, table and figure in the paper.
Contents
All results use AMQA, a benchmark of adversarial demographic counterfactuals of USMLE-style questions (white vs. black, male vs. female, high vs. low income).
| Included (MIT license) | Not included |
|---|---|
| The full pipeline: data loaders, candidate generation, verifier scoring, LoRA / DPO training, analysis | The run outputs (generated candidates, verifier scores) and the trained adapters. They are large and derived from third-party datasets; regenerate them with the GPU job templates. |
Scripts that recompute and check every paper number (public/reproduce.sh) and redraw every figure | The datasets (download them from their sources, below) |
| SLURM job templates for every GPU run | Model weights (downloaded from Hugging Face) |
No real patient data is used: every dataset is made of examination vignettes.
conda create -n vpb python=3.12
conda activate vpb
pip install -r public/requirements.txt
public/requirements.txt lists minimum versions. The paper's runs used NVIDIA's PyTorch 25.10 container (torch 2.9.0a0) with transformers 5.5.0 and peft 0.13.2; install these exact versions (pip install transformers==5.5.0 peft==0.13.2). Newer transformers releases remove the warmup_ratio training argument that public/training/lora_train.py uses, and can change chat templates and tokenization (see What to expect when you rerun).
| Dataset | Used for | Where to get it |
|---|---|---|
| AMQA (Xiao et al., 2025) | Main benchmark (race, sex, income) | The human-reviewed release AMQA_Dataset/AMQA_Dataset.jsonl from github.com/XY-Showing/AMQA (commit c6ab56a7, 801 lines, sha256 63a24d945fd49c2f8baf25256312a91878d95e3cc5c5f08b5228f04d1bb270f8). |
| MedQA-USMLE | Direct-substitution control | Downloaded automatically (GBaker/MedQA-USMLE-4-options on Hugging Face) |
| DiversityMedQA | Ancestry-prefix control | Downloaded automatically (dhiyaan/DiversityMedQA on Hugging Face) |
| MedMCQA | Independent-corpus probes | Downloaded automatically (openlifescienceai/medmcqa on Hugging Face) |
Important: set
AMQA_FILE. The code stops ifAMQA_FILEis not set. Use the human-reviewed file above, not the copy on Hugging Face (Showing-KCL/AMQA), which is the unrevised release and gives different numbers.
Some models are gated on Hugging Face: accept their terms before downloading (meta-llama/Llama-3.1-8B-Instruct, google/gemma-3-27b-it, google/medgemma-27b-it, epfl-llm/meditron-7b).
| Role | Models |
|---|---|
| Primary generator | FreedomIntelligence/HuatuoGPT-o1-8B |
| Other generators | Med42-8B and Med42-70B (m42-health/Llama3-Med42-*), OpenBioLLM-70B, Mistral-7B-Instruct-v0.3, Llama-3.1-8B-Instruct, Gemma-3-27B-it, BioMistral-7B, MedGemma-27B-it, Meditron-7B |
| Verifiers | FreedomIntelligence/HuatuoGPT-reward-model-7B, Skywork/Skywork-Reward-Llama-3.1-8B-v0.2, the generator itself as self-judge |
| Perturbation diagnostic only | RLHFlow/ArmoRM-Llama3-8B-v0.1, GAIR/ReasonEval-7B, zarahall/fairness-reward-model (FRM) |
Some job templates load local copies through MODEL_DIR (for example $MODEL_DIR/Llama3-Med42-70B).
export AMQA_FILE=/path/to/AMQA_Dataset.jsonl # required (see above)
export OUTPUTS_DIR=/path/to/outputs # where runs are written and read; default: <repo>/outputs
export HF_HOME=/path/to/hf_cache # Hugging Face cache
export MODEL_DIR=/path/to/local/models # only for templates that load local model copies
Once the runs exist under OUTPUTS_DIR, one command recomputes every number in the paper and prints it next to the printed value with MATCH or MISMATCH. Topics whose inputs are missing are reported as SKIP; the command exits with an error if anything mismatches or is skipped.
bash public/reproduce.sh # all topics (about ten minutes on CPU)
bash public/reproduce.sh --quick # skip the slow bootstraps
bash public/reproduce.sh --list # list the topics
bash public/reproduce.sh --only locus,recipes # run selected topics
Each topic is a module in public/paper/<topic>/ and can also be run alone, for example PYTHONPATH=. python -m public.paper.locus.locus.
Redraw all figures with:
bash public/paper/figures/make_figures.sh
Where outputs go. Run directories sit directly under OUTPUTS_DIR (for example outputs/p4_amqa/, outputs/p4_amqa_seed1/). Outputs of the scripts in public/paper/<topic>/ go to OUTPUTS_DIR/paper/<topic>/; the five-seed fairness, Med42-8B and transfer runs go to OUTPUTS_DIR/paper/five_seed/. Each topic directory can be redirected with PAPER_<TOPIC>_DIR (see public/paper/common.py). Three topics write derived files (holm, tagger_and_strata, medmcqa, into PAPER_HOLM_DIR, PAPER_TAGGER_DIR and PAPER_MEDMCQA_WORK_DIR); point these at a scratch directory to keep the run outputs untouched. Runs kept elsewhere can be found through OUTPUTS_FALLBACK_DIRS (colon-separated directories searched after OUTPUTS_DIR).
All runs used H100 80 GB GPUs: one GPU per job, two for the 70B generators. LoRA training needs a full 80 GB GPU because the base model is loaded in fp32; one training run takes four to eight minutes. A generation run (400 pairs × 16 samples, then scoring) fits within the 6 to 8 hours the templates request.
Submit jobs from the repository root. Scheduler lines (account, partition, QOS) are site-specific; each template marks where to put yours. Every template sources public/slurm/env.sh, which activates your environment (set ENV_SETUP, for example ENV_SETUP="conda activate vpb") and puts the repository on PYTHONPATH.
| Runs | Job template |
|---|---|
| AMQA baselines: race primary run (seed 0) and seeds 1 to 5, sex and income primary runs, N_max=32 run | public/slurm/p4_amqa.sbatch (SEED=0..5), p4_amqa_axes.sbatch, p4_amqa_n32.sbatch |
| Five-seed runs (seeds 1 to 5): fairness instructions, Med42-8B baseline and fairness instructions, AMQA-sex and AMQA-income baselines, race-trained LoRA on sex and income | public/paper/five_seed/launch_five_seed.sh (resume_score.sbatch finishes a run that stopped after self-judge scoring) |
| HuatuoGPT-RM, Skywork-RM and FRM scoring | public/slurm/p4_rescore_rm.sbatch, p4_rescore_skywork.sbatch |
| Other generators | public/slurm/p4_amqa_med42.sbatch, p4_amqa_med42_70b.sbatch, p4_amqa_gemma3_27b.sbatch, p4_amqa_newgen.sbatch, p4_amqa_biomistral.sbatch, p4_amqa_medgemma.sbatch, p4_amqa_meditron.sbatch, openbio70b_*.sbatch |
| MedQA-USMLE and DiversityMedQA controls | public/slurm/p4_gap.sbatch, p4_diversitymedqa.sbatch |
| Fairness instructions (seed 0); paired LoRA (seeds 0 to 5), single-variant controls, target styles, hyperparameter sweep, Med42-8B | public/slurm/p4_intervention_v1.sbatch, lora_train*.sbatch, p4_intervention_*.sbatch, launch_*.sh |
| Base-model self-judge for LoRA runs (all N=16 LoRA numbers) | public/paper/base_judge/rescore_basejudge.sbatch |
| Training recipes R1, R2, R3 (HuatuoGPT-o1-8B and Med42-8B) | public/paper/recipes/launch_recipes.sh |
| Single-BOS rescoring | public/paper/bos_sensitivity/bos_rescore.sbatch |
| Adapter precision check | public/paper/adapter_precision/merge_check.sbatch |
| Free-text generations and judges | public/slurm/p4_amqa_freetext.sbatch, freetext_lora.sbatch, public/paper/free_text/freetext_judge.sbatch |
| Race × sex probes | public/paper/race_by_sex/race_by_sex.sbatch (sex word), public/slurm/llm_intersectional_edit_then_run.sbatch (LLM-edited) |
| Perturbation diagnostic | public/paper/perturbation/tier1_scorer.sbatch (one job per verifier) |
| MedMCQA probes | public/paper/medmcqa/medmcqa_edit.sbatch, medmcqa_eval.sbatch |
See public/slurm/README.md for the full list of variables the templates read.
Run from the repository root with PYTHONPATH=.. Every python -m public.paper... command prints the recomputed values with MATCH / MISMATCH. Numbering follows the final paper: Tables 1–7 and Figures 1–4 in the main text, Tables A1–A18 and Figures A1–A4 in the appendix.
Main text
| Paper item | Content | Command |
|---|---|---|
| Figure 1 | Overview schematic | Drawn by hand; its numbers come from Table 1, Section 4.2 and Table A11 |
| Figure 2 | Perturbation diagnostic, six verifiers | public/analysis/plot_tier1_5way.py --in-dir <perturbation dir> |
| Figure 3 | Oracle, self-consistency and self-judge at N=16 | public/analysis/plot_oracle_vs_bon.py (see make_figures.sh) |
| Figure 4 | Single-variant ablation | public/analysis/plot_singlevariant.py |
| Table 1; Tables 2, A3, A4, A6; Section 4.2 | Gap at N=1; change from N=1 to N=16 and Holm tests; N_max=32 run; Skywork-RM over the LoRA; oracle, self-consistency, FRM selector | python -m public.paper.locus.locus |
| Tables 3, 6, 7 | Interventions, transfer to other axes and to Med42-8B | python -m public.paper.interventions.interventions (DPO rows: public.paper.recipes.collect_recipes) |
| Table 4 | Training recipes R1 to R3 | python -m public.paper.recipes.collect_recipes |
| Table 5 | Seven generators: gap, oracle, amplification | python -m public.paper.generators.generators; Amp. column: python -m public.paper.holm.multigen_amplification |
| Section 3, 4.1 | MedQA-USMLE: 208 of 400 pairs, verifier accuracies, Pearson r | python -m public.paper.medqa_pairs.medqa_numbers |
| Section 4.1 | Perturbation numbers | python -m public.paper.perturbation.collect_tier1 |
| Section 4.4 | MedMCQA probes | python -m public.paper.medmcqa.medmcqa_results --check |
| Section 4.4 | Race × sex interaction | python -m public.paper.race_by_sex.collect_race_by_sex |
| Section 4.4 | Free text | python -m public.paper.free_text.collect_free_text |
Appendix
| Paper item | Content | Command |
|---|---|---|
| Table A1; App. A | Gap under four conventions for unparsed outputs; unparsed rates | python -m public.paper.parser.parser_numbers |
| Table A2; App. C | Single vs duplicated BOS token | python -m public.paper.bos_sensitivity.collect_bos |
| App. C | Adapter precision | python -m public.paper.adapter_precision.collect_merge --check |
| Tables A5, A9, A11, A12, A17; App. F, H, K | Per-seed LoRA, sweep, single-variant, Med42-8B, target styles | python -m public.paper.interventions.interventions |
| Table A7 | First sample vs mean of 16 samples | python -m public.paper.sixteen_sample.sixteen_sample_table |
| Tables A8, A15; App. F, J | Relevance strata, tagger counts, per-condition gaps, race mention | python -m public.paper.tagger_and_strata.collect_strata |
| Table A10 | Verifier separation and AUROC by subgroup | python -m public.paper.calibration.collect_calibration |
| Tables A13, A18; App. L | Med42-8B DPO and controls; recipe variants per seed | python -m public.paper.recipes.collect_recipes |
| Table A14; App. I | Generator coverage; Gemma-3-27B-it; floored generators | python -m public.paper.generators.generators, python -m public.paper.holm.multigen_amplification |
| Table A16; App. J | Free-text gap | python -m public.paper.free_text.collect_free_text |
| App. B | Training loss and gradient norms | python -m public.paper.training_logs.summarize_lora_log --check <training logs> |
| Figure A1 | Best-of-N on MedQA-USMLE | bash public/paper/medqa_pairs/run_pipeline.sh |
| Figure A2 | AMQA race, sex and income under three verifiers | public/analysis/plot_cross_axis.py |
| Figure A3 | Race × sex | python -m public.paper.race_by_sex.collect_race_by_sex --make-fig |
| Figure A4 | Interventions, gap versus N | public/analysis/plot_interventions.py |
public/data/load_eval.parse_answer is used everywhere: a leading answer letter, then "answer is X" and similar forms, then the last standalone A/B/C/D in the final 300 characters. Outputs without an answer count as incorrect.public/paper/base_judge/rescore_basejudge.sbatch.run_p4.py, inference/score_with_rm.py). The free-text judges (Appendix J) do see the options.generation_config.json and the transformers defaults.public/paper/bos_sensitivity/).public/paper/medmcqa/audit_labels.py) records the labels of the paper's own audit and applies only to the paper's edits._backup). For R1 seed 3, the first job wrote all 12,800 generations but stopped before scoring; the paper uses the first job's generations (both give the same N=1 gap; race mention is 35.4% vs. 35.6%). public/paper/common.resolve_candidates encodes this rule. The Med42-8B baseline of seed 3 stopped after self-judge scoring; its remaining steps were run on the saved candidates (public/paper/five_seed/resume_score.sbatch).public/paper/flip_rates/patch_flip_metrics.py rewrites them.public/
reproduce.sh, reproduce.py recompute and check every paper number (no GPU)
run_p4.py best-of-N pipeline: generate, score, metrics
paths.py OUTPUTS_DIR handling
data/ dataset loaders, counterfactual-pair construction, answer parser
inference/ verifier scoring, perturbation diagnostic, free-text generation and judging
training/ paired LoRA (SFT), R1 targets, R2 (KL), R3 (DPO)
analysis/ amplification tests, oracle / self-consistency, tagger, race mention, AUROC, figures
paper/ one folder per paper topic: collectors that print the paper's numbers,
and the job templates for that topic
slurm/ job templates for the pipeline runs (see slurm/README.md)
@inproceedings{nnamdi2026biasingenerator,
title = {The bias is in the generator: locating medical chain-of-thought subgroup gaps upstream of the verifier},
author = {Nnamdi, Micky C. and Marteau, Benoit Louis and Tan, Shaun Qien Yeau and Tamo, J. Ben and Yuan, Yining and Wang, May Dongmei},
booktitle = {Proceedings of AACL-IJCNLP 2026},
year = {2026}
}
Code: MIT (see LICENSE). Datasets and models keep their own licenses.