Repository navigation
feat(common): parallel option layout for order-invariant decisions - #951
Conversation
Every option starts at the same position id and cannot attend to another option; the local layers keep their window, measured in position ids. The head has no positional encoding, so reordering the options only reorders the logits. The technique follows Parallel Context Windows (Ratner et al., ACL 2023), applied to the option block. A checkpoint opts in with "option_layout": "parallel" in its config; published checkpoints stay sequential and their path is unchanged. Needs transformers>=5 (per-layer-type attention masks) and the eager backend: ONNX refuses such a checkpoint at load, non-eager forwards refuse it at inference, and backend="auto" resolves to eager. tests/test_parallel_options.py checks exact equivariance over all 24 orders of four options on a tiny random ModernBERT. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
TrainConfig(option_layout="parallel") trains and calibrates on the parallel option layout and writes it into the saved config, so laya.load picks it up. Unset keeps the checkpoint's own layout and leaves the saved config as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
How the parallel option layout works, how to train a checkpoint on it, and the typed-decisions comparison against the sequential layout with and without shuffle_options. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Merged, with the default left exactly where you put it. This is a structural answer to a problem the repository has been measuring from four angles and mitigating approximately: What made it mergeable is the discipline around the default, so I want to name the specific parts:
I verified the default path myself rather than taking the byte-for-byte claim on trust, since this edits Merge note. This conflicted with #933 in On the last point in your scope section: training and publishing a parallel checkpoint is mine to do, and the evidence here is what I would want before spending the compute. Leave #131 open against it. |
feat(common): parallel option layout for order-invariant decisions
What
An opt-in parallel option layout that makes a checkpoint's decisions invariant to option order
by construction, plus the training switch to produce such a checkpoint.
[SEP]and the statecontinue after the longest option.
[CLS])attends as before.
instead of sequence index, so every slot sees the same neighbourhood.
logits simply permute with the options. The act head reads
[CLS]and order-free statistics ofthe probabilities, so it is unchanged under reordering too.
The layout is a property of the weights, so it lives in the checkpoint config:
"option_layout": "parallel"inrl_agent_config.json. Every published checkpoint stays on thedefault
"sequential"layout, and nothing changes for them. Producing a parallel checkpoint:Why
Where an option is listed changes the answer. The repo already measures this from several angles:
tests/test_option_order.py: five options with identical text spread by 3.59 logits on theEnglish checkpoint, by position alone.
BENCHMARKS.md: option-order flip rates of 28% (CS) and 10.6% (voice).laya-multilingualnever selects the first-listed score option.The current tools reduce the effect without removing it.
shuffle_optionsteaches the model toignore order approximately, and
option_orderrotation averaging (README) costs k rows per questionand cut flips from 16.3% to 6.1%. A structural fix removes it: with the parallel layout, reordering
can only reorder the logits.
The technique is the one Parallel Context Windows (Ratner et al., ACL 2023)
uses for long contexts (shared position ids plus a block-diagonal mask), applied here to the option
block of a bidirectional encoder. PINE (Wang et al., 2024) removes
position bias between documents in decoder LMs along similar lines.
Results
Base checkpoint
convaiinnovations/laya(English). All three arms were fine-tuned withlaya.train.finetuneon typed-decisionstrain, using the notebook recipe (4 epochs, effectivebatch 64, RLCD, seed 0) on one Colab T4. They were evaluated on typed-decisions
test(400 cases, 2,000 decisions). Each question was asked twice: in its listed order, and under a random
non-identity
option_order.shuffle_options(all types)* fp16 autocast rounding. In fp32 the change is ≤1e-4, which is the 4-decimal rounding of the returned
probabilities.
Per question type, accuracy in listed order (shuffled order in brackets):
shuffle_optionsHow to read it:
decisions. The sequential arm changes 8.3% of them (12.6% on
score), andshuffle_optionsreduces that to 5.0%.
learned prior on the dataset's listed order: shuffle the options and it drops to 0.767. The
parallel arm is 0.764 either way.
shuffle_options, today's mitigation: the parallel arm has higher accuracy andfull order invariance.
shuffle_optionskeeps the best ECE.choice(0.735 vs0.750 shuffled).
0.779 on the T4, so differences under about 0.01 should not be read as real.
Zero-shot (no fine-tuning) is not a substitute. Switching the shipped weights to the parallel
layout is already exactly invariant: on MASSIVE (20 options,
research/eval/metamorphic.py) themax probability drift goes from 0.999 to 7.8e-6. But MASSIVE accuracy falls from 0.795 to 0.54,
because the weights were trained on the other layout. A checkpoint has to be trained on it, which is
why the layout lives in the config and defaults to sequential.
Scope and limitations
attention_maskmapping, which transformers 4.x does not have.
uses_parallel_layoutraises a clear error on 4.x.The new test skips there.
path,
compile=Trueand other non-eager backends refuse it at inference with a message pointing toset_backend('eager').backend="auto"resolves to eager for such a checkpoint.presentation_checks.pypermutesscorelevels while thelevel N:prefix stays with its slot,so it mixes the two. There the slot-0 margins drop (score 0.66 → −0.13, choice 0.90 → 0.11 at
zero-shot) but cannot reach 0.
publishing a parallel checkpoint (English or multilingual, for laya-multilingual never selects the first-listed score option — in English too (0/290), while laya does #131) is the maintainer's call.
Changes
laya/common.pyparallel_layout,parallel_option_masks,uses_parallel_layout,OPTION_LAYOUTS;build_sequence(return_layout=);collate_itemspads the layout and refuses a mixed batch;DecisionModel.forward(position_ids=, option_ids=)laya/agent.pyoption_layoutat load;_encode_statecarries the layout;_inferpasses it and refuses non-eager forwardslaya/train.pyTrainConfig.option_layout, threaded throughencode_item,train_model,calibration_records,finetune; written to the saved config only when setlaya/onnx_agent.pylaya/backends/__init__.pyautoresolves to eager for a parallel checkpointresearch/eval/laya_eval.pyscore_caseshonours the layout, sopresentation_checks/metamorphicmeasure parallel checkpoints correctlytests/test_parallel_options.pyscripts/test_suites.pyREADME.mdThe default path is byte-for-byte unchanged. Without
option_layoutnothing new is computed, andbuild_sequencekeeps its return shape unlessreturn_layout=True.How it was verified
Every
tests/test_*.pywas also run as a script on this branch and onmain. The suites that faildo so identically on both: they need CUDA/tilelang, the onnx extra, local checkpoints or network
services.
tests/test_parallel_options.pyruns a tiny randomly initialised ModernBERT inside the realDecisionModel, with no download. It checks:max_lenclampthat does not widen the last surviving option);
sliding-window masks;
permuted (max diff < 1e-5), and the act head output is unchanged;
collate_itemsand config plumbing.Real-checkpoint numbers above come from
research/eval/metamorphic.py,research/eval/presentation_checks.pyand an evaluation script over typed-decisions
test, which is attached and can be added underresearch/if wanted.Checklist
main(8a6e132)tests/test_hooks_api.pypasses unchangedRefs #131, #244, #602