Audit and hardening: 35 findings, 58 regression tests, published behaviour preserved by flag - #1
Open
snuconnectome wants to merge 5 commits into
Open
snuconnectome wants to merge 5 commits into
snuconnectome wants to merge 5 commits into
Conversation
added 4 commits
September 8, 2026 00:02
…pretability Audit of Transconnectome/MBBN @ eed038c. Every claim below was verified by running the repository's own modules; see docs/audit/AUDIT.md. Blocking (the released code cannot run as shipped): - init_weights() ran before the submodules existed, so it initialised nothing; it is also not the documented entry point, and on current transformers the model does not instantiate at all. Now post_init(), after construction. - np.in1d was removed in NumPy 2.0 -> np.isin. - torch.cuda.nvtx calls are fatal on a CPU-only build. - Attention referenced self.proj / self.proj_drop, which were never created. - pick_spatial_heads(): no fallback for lengths not divisible by 12 or 8. - lr_warmup (absolute) vs T_0 (30% of total steps) tripped a message-less assert on any short run. - wandb was a hard dependency even offline. Scientific consequence: - The band-specific spatial attention maps are not on the prediction path: d(prediction)/d(spatial qkv) == 0 exactly. Their only training signal is the band-repulsion loss, whose optimum is one-hot (hub) attention. The interpretability script backpropagated that same loss, giving a gradient quantised to {-2,-1,0,1,2}/(numel*S) with 33% exact zeros and no label dependence. --spatial_head connects them; --attribution label_gradient attributes the prediction; --n_permutations adds a label-permutation null. - The test-set operating point never left the validation set, so reported sensitivity/specificity/F1/balanced-accuracy were prevalence-determined constants (1.0 / 0.0 / 2p/(1+p) / 0.5) independent of model quality. - Evaluation loaders dropped (n mod batch) subjects with a random sampler. - Prediction head batch-normed and dropped out the scalar logit: one subject's logit moved SD 0.29 at batch 16 from its batch-mates alone, and 61% of training logits were set to exactly 0. - pretrain_MBBN.slurm passed --sequence_length_phase4 while running step 3; sort_args discards it, so pretraining ran at 348 and the checkpoint could not load into the 464-wide fine-tuning model. - Splits ignored family structure and acquisition site. Also: on-disk band-decomposition cache, real padding attention mask, random per-subject masking with a learned mask token and masked-only reconstruction loss, correct gradient accumulation, --clip_max_norm honoured, stable-name checkpoints (was one ~325 MB file per improving epoch), and a 55-test regression suite with synthetic fixtures so the pipeline runs with no cohort data at all. Defaults reproduce published behaviour except where that behaviour is itself the defect; every changed default is reversible by flag.
docs/audit/AUDIT.md 35 findings with evidence, consequence and fix for each,
the two claims withdrawn after checking, cohort-dependent
consequences at the repo's own N, and cost measurements
docs/audit/*.png numerical-evidence panels and a summary infographic
README.md audit section: the three findings that bear on published
claims, reproduction recipe, recommended settings, flags
Also: fix a recursion in the guarded NVTX helper (invisible on CPU because the
is_available guard short-circuits, RecursionError with a CUDA device present),
found by running the suite on a GB10. Tests now pin the delegation, the absence
of self-recursion, and statically that every CUDA-only call in trainer.py sits
inside a function that tests torch.cuda.is_available().
- evidence figure panel (d): the measured saliency multiples are {-2,0,+1,+2}
of 1/(numel*S); only -1 is absent from the sample. The previous render used an
exact float-equality test against a recomputed unit, which differs from the
autograd output by ~8e-9 relative, and so drew the genuinely observed -2 and
+2 as absent. Now matched on the nearest integer multiple, and the title
states what is analytic (five possible values) separately from what was
observed (four).
- infographic: the peak row weight rises 2.39x over 400 repulsion-only steps,
not 'triple'; and -log(S) is singular where the three maps coincide, not 'at
initialisation' -- random initialisation gives a finite S = 0.00278.
- AUDIT.md: same singularity correction, and the concentration factor stated.
- dataloaders.py: the ABIDE no-op comment claimed ABCD's metadata uses an 'ASD'
column. It uses 'ASD_label', which is mapped to 'ASD' and back further down;
the conclusion (leave the released no-op alone) is unchanged.
The method caveat said per-cohort subject counts are not recorded in this repository, which contradicted the cost and fold-size tables in the same document -- those are computed from the README dataset table (UKB 40,699, ABCD 8,833, ABIDE 141, summing to the 49,673 the paper states). The caveat now says what those N actually are and what they are not: released documentation figures, not counts verified against the cohorts.
snuconnectome
force-pushed
the
review/mbbn-hardening-2026-09
branch
from
September 7, 2026 15:31
760c1c0 to
06fd2de
Compare
pretrain_MBBN.slurm sets N_EPOCHS=1000 for the Schaefer-400 configuration -- which is the UKB_400_464 case the cache benchmark measures -- and 400 only for its 360-ROI variant. The projection had used 400, understating the scripted saving by ~2.5x while citing the script as its source: 22.8 wall-hours, not 9.1. The cost table now names the configuration behind each epoch count, and the verdict is restated: ABIDE recovers minutes, ABCD under an hour, and only the 1000-epoch pretraining run is material.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Audit of this repository at
eed038c("Communications Biology ver.") and a hardening patch.35 findings — 7 that prevent the released code from running at all, 9 that change reported
numbers or their interpretation — with a 58-test regression suite and 20 new flags. Full
detail, with the measurement behind each claim, in
docs/audit/AUDIT.md.15 of the 35 were established by importing the released modules unmodified and exercising them,
not by reading them. No cohort data was available to this audit, so nothing here is a claim
about the paper's reported accuracy, and no fix is claimed to improve AUROC.
Three findings that bear on published claims
1. The interpreted attention maps carry no label information.
Transformer_Finetune_Three_Channelscomputes three band-specific ROI×ROI spatial attentionmaps and returns them, but they never enter the prediction:
while the band-repulsion loss delivers |∇| ≈ 2817 to the same parameters.
visualization.pythen computes its saliency by backpropagating that same repulsion loss. For
Sa sum ofmean-L1 distances,
d(-log S)/dh_ij = -[sign(h-l)+sign(h-u)]/(numel·S)— five discrete valuestimes one global scalar; measured on a (2,8,400,400) map, 4 of the 5 possible values across
2.56M entries, 33.4% exactly 0. The diagnosis enters only through which subjects are averaged.
The step-1
vanilla_BERTbaseline does wire its spatial branch into the prediction (|∇| 406),so a step-1-vs-step-2 comparison changes the head wiring as well as the frequency decomposition.
Fix:
--spatial_headputs the maps on the prediction path;--attribution label_gradient(new default) attributes the model's own decision and refuses to run when the maps are
disconnected;
--n_permutationsadds a label-permutation null with FWE correction.--attribution spatial_differencereproduces the published maps.2. Reported operating-point metrics are prevalence-determined constants.
LossWriteris constructed withTrainer.val_threshold(= 0) and nothing updates it, soMetrics.ROC_CURVE(name='test')thresholds sigmoid outputs at 0. Reproduced through thereleased
Metricson scores with AUROC 0.933, prevalence 0.275:At threshold 0, F1 is pinned at
2p/(1+p)and balanced accuracy at 0.500 regardless of themodel. AUROC is unaffected. Fix: the validation operating point is stored and carried over,
with a guard that warns when the test threshold falls outside (0,1).
3. The band-repulsion objective is optimised by hub attention.
Rows of each map are softmax probability vectors, so each pairwise mean-L1 is ≤
2/NandS ≤ 6/N. At N=400 that ceiling is 0.015 and it is attained (measured 0.0150) exactly whenevery row is one-hot and the three bands point at different targets — the global optimum of
-log Sis star/hub attention, independently of the data.S = 0when the maps coincide exactly, so-log Sis singular there and its gradient scales as
1/S; random initialisation is not that point butis close to it (measured
S = 0.00278, finite loss 588.6 at λ=100), and |∇|∞ reaches 0.50 atlogit scale 1e-4 versus 1.6e-3 at scale 1. 400 steps of the repulsion term alone, at the
released learning rate on the released
Attention, moved row entropy from 0.9908 to 0.9625 oflog Nand peak row weight from 2.55/N to 6.11/N — a 2.39x increase.Fix:
--spat_diff_loss_type {minus_log (published, verbatim), minus_log_eps, neg_linear, cosine},--spat_diff_entropy_weightto penalise hub collapse,--spatial_loss_warmupto rampλ past the initial transient.
Blocking bugs — the released code does not run as shipped
model.pyinit_weights()is called beforeself.bertandself.cls_embeddingexist, so it initialises nothing; it is also not the documented entry point, and ontransformers ≥ 5construction raisesAttributeError: no attribute 'all_tied_weights_keys'. Nowpost_init()after construction.dataloaders.pynp.in1dwas removed in NumPy 2.0 →np.isin.trainer.pytorch.cuda.nvtxcalls are fatal on a CPU-only build.model.pyAttention.forwardreferencesself.proj/self.proj_drop, never created.model.pyelse, so any sequence length divisible by neither 12 nor 8 (e.g. ABIDE's native 280) raisesUnboundLocalError.learning_rate.py--lr_warmupis absolute butT_0is 30 % of total steps; any run underlr_warmup/0.3steps trips a message-lessassert.trainer.pywandbwas a hard dependency even offline.Evaluation protocol
drop_last=True+RandomSampleron the evaluation loaders. At this repository's ownABIDE N=141, the released 70/15/15 split gives train 98 / val 21 / test 22, so
finetune_MBBN.slurm's--batch_size_phase2 16discarded 6 of 22 test subjects (27.3 %)and 5 of 21 validation subjects at every evaluation — a different 6 and 5 each epoch. At
batch 32 the fold yields zero batches.
pretrain_MBBN.slurmpassed--sequence_length_phase4 464while running step 3.sort_argskeeps only*_phase3keys for step 3, so the flag was silently discarded,pretraining ran at the phase-3 default 348, and loading that checkpoint into the 464-wide
fine-tuning model raises
RuntimeErroron 4 of 44 shared keys. Corrected to--sequence_length_phase3;sort_argsnow warns when an explicitly-passed phase-taggedoption is dropped.
Added
--group_by_family,--site_stratify,--leave_one_site_out,--split_seed.Note: 17 ABIDE sites × 2 classes = 34 strata cannot fit a 22-subject test fold, so joint
stratification degrades to target-only with a warning — site cannot be balanced at this
cohort size, and
--leave_one_site_outor k-fold is the honest alternative.validation operating point (
--eval_test_every_epochrestores the published behaviour).Training safety, robustness, cost
Linear → BatchNorm1d(1) → Dropout(0.6)on the scalar logit: onesubject's logit moves SD 0.293 (range 1.98, sigmoid p spanning 0.107–0.464) from its
batch-mates alone; a plain linear head gives SD exactly 0. Dropout(0.6) sets 61.4 % of
training logits to exactly 0.
--head_type linearis the new default, and the same head isnow shared by the baseline and MBBN so the architecture comparison is unconfounded.
--nan_policy raiseis the new default.380/400 ROIs plus every other window.
--random_maskresamples per subject per epoch, fillswith a learned mask token rather than 0, and
--mask_loss_on_masked_onlyscores only hiddenpositions.
attention_mask=None, so CLS pooling averaged over zero padding; ABIDE was padded to ahardcoded 464 with an off-by-one for odd pad. Now a real padding mask, symmetric padding, and
padding applied only when a pretrained checkpoint requires it.
--band_embedding.zero_gradevery step);--clip_max_normwasdeclared but never read;
torch.compileresults were discarded;run_phasereturned acheckpoint filename
save_checkpoint_never writes; every improving epoch wrote a new~325 MB checkpoint.
--cache_bandsmemoises the per-subject knee fit (427× on ABIDE). Opt-in: the per-subjectratio is large but the absolute saving is modest — minutes for ABIDE fine-tuning, under an
hour for ABCD, and 22.8 wall-hours for the 1000-epoch Schaefer-400 UKB pretraining run at a
181 GB cache.
Reproducibility
Defaults reproduce released behaviour except where that behaviour is itself the defect (the
test operating point, the dropped evaluation subjects, the mis-tagged pretraining flag, the
seven blocking bugs). Published forms are kept verbatim rather than reimplemented:
--spat_diff_loss_type minus_log,--head_type published,--attribution spatial_difference,--nan_policy zero,--eval_test_every_epoch,--keep_all_best_checkpoints,--no_padding_mask.Tests
python -m pytest tests -q # 58 teststests/fixtures/synthetic.pygenerates an ABIDE-shaped cohort, so the suite — including anend-to-end
main.pyrun — needs no cohort data, no GPU and no W&B account. Also run on anNVIDIA GB10 in the NGC PyTorch container: 58/58, plus short step-2 runs with mixed precision on
and off and a step-3 masked-pretraining run. That GPU pass caught a defect in this patch (a
guarded NVTX helper that called itself — invisible on CPU,
RecursionErrorwith CUDA present),now pinned by three tests.
Two claims withdrawn after checking
Recorded so a reader can tell what was verified from what was merely plausible: an apparent
ABIDE split
KeyError(the column is renamed before the split call, so the released no-op iscorrect) and an apparent
_SITE_TRsubstring collision (no key is a substring of another, andsubstring matching is required for ABIDE's suffixed site ids). Both reverted to released
behaviour; see the "Corrections made during this audit" section of
AUDIT.md.