Repository navigation
Add UMT5 support (per-layer relative attention bias) - #2103
the-cross-art wants to merge 5 commits into
Conversation
|
The per-layer bias approach looks reasonable. Before merging, could you add regression tests for UMT5’s distinct bias tables and T5/mT5 sharing, investigate the reported log-probability mismatch (matching greedy output doesn’t establish sampling or beam-search parity), and document the |
|
@jordimas Thanks for the review. All three items are addressed now — tests and docs are pushed, and the The log-probability mismatchYou were right that greedy proves nothing — The test needs no reference implementation: a causal decoder's score for position 0 can't
They agree exactly at length 1 — the only length where there's no future to leak from.
# t5/modeling_t5.py:375-380 and mt5/modeling_mt5.py:382-386
T5Attention(config, ..., layer_idx=layer_idx, is_causal=config.is_decoder)
# umt5/modeling_umt5.py:375 <- no is_causal, defaults to False even in the decoder
UMT5Attention(config, has_relative_attention_bias=True, layer_idx=layer_idx)So no mask and I verified the cause rather than just reading it — setting It's also a regression, not longstanding: I'd like to drop the "known limitation" from the PR description on this basis — happy to Sampling and beam search
One more isolation result worth having on record: For what it's worth, before finding the cause I'd confirmed the residual survived making the TestsAdded in
Both construct the model from I went with testing the loader rather than which is exactly the failure mode — layer 1 receiving layer 0's table. The T5 test keeps passing, That also settles the first of the two fixture questions I asked: the synthetic in-memory model The second stands only as an offer. A C++-level test of Docs
|
|
@jordimas huggingface/transformers#49135 (commit After re-running validation against I also re-converted For the PR description, I'd like to remove the first "Known limitations" bullet since it was caused by this upstream issue. I'll replace it with a brief note that teacher-forced scores may differ from We're already running this branch on a 2.9B UMT5 model on an L4 with good results, so I'd prefer to move away from a patched build if possible. Let me know if there's anything else you'd like addressed before merging. Happy to rebase, reorganize commits, or update the docs. |
|
The converter tests look good. Could you also add a runtime regression test? The current tests check that each layer’s bias table is preserved during conversion, but they would still pass if the C++ runtime reused layer 0’s bias for every layer. A small synthetic UMT5 model with distinct, nonconstant bias tables would work well. Convert it, run The tables should be nonconstant because a uniform bias cancels during softmax and could hide the bug. The test should pass with this PR and fail when the runtime is forced back to sharing layer 0’s bias. |
|
Added in
Each builds a tiny UMT5 (3+3 layers, VerificationI reproduced the pre-PR behaviour by forcing
All three fail with the forced-shared runtime and pass with the PR. The Worth noting that both existing converter tests still pass against the broken runtime — Also: Two things worth flagging1. Non-constant tables matter even more than it first appears. You were right that a 2. The synthetic model has to use Transformers' own initialiser. This cost me the most One deviation from your suggestion, and whyFor the encoder I assert on the encoder output (via The reason is measurement, not convenience: on a randomly initialised tiny model, changing Checked at the encoder output instead, the same comparison separates the two cases by about If you'd rather have a |
Add UMT5 support (per-layer relative attention bias)
Closes #2102. Follows up on #1478, which requested UMT5 support and was closed without a fix.
What this does
UMT5 is structurally identical to mT5 except for one thing: every self-attention layer owns its own relative attention bias table, where T5 and mT5 compute the bias once in layer 0 and share it. CTranslate2 currently cannot convert UMT5 at all, and its runtime assumes the shared-table layout.
Two commits, 96 lines:
UMT5Loader— registersUMT5Configand overridesset_stackto keep each layer's own bias table.T5Loader.set_stackreads each layer's bias and then overwrites layers 1..N with layer 0's, which for UMT5 discards 14 of 16 real tables, converts without error, and produces fluent but incorrect output. It also overridesget_vocabulary: UMT5 tokenizers already contain the<extra_id_*>sentinels, so the inherited padding would append duplicates.Per-layer position bias in the Transformer stacks —
TransformerEncoder::operator()andTransformerDecoder::operator()create oneposition_biasbuffer per forward pass and thread it through every layer;attention.ccfills it only when empty, so layer 0's bias is used everywhere.Why not just remove the cache
The comment on #1478 proposed removing the
position_bias->empty()guard, and noted it "may lead to performance degradation in T5 and MT5 models". That is what stalled it — it would make every existing T5/mT5 model recompute the bias in every layer.Instead, this detects which layout a model has and only takes the per-layer path when needed. The per-layer path already exists in
MultiHeadAttention: when the caller passesnullptr, each layer falls back to a local buffer and computes from its own table. This change makes that branch reachable rather than adding new computation.Detection uses pointer identity and needs no new config flag, no format change, and no
spec_revisionbump._alias_variablesalready serializes byte-identical tensors as aliases;register_variable_aliasresolves an alias to the sameshared_ptr<StorageView>, andModel::copy_topreserves that across device copies. So for T5 every layer's bias resolves to one pointer, and for UMT5 they differ.Models with no relative attention bias hit the early return, and models with fewer than two layers take the previous path, so both are byte-for-byte unchanged.
No regressions
t5-smallmodel.binis byte-identical (same sha256) before and after the converter change.umt5-small→ per-layer on both stacks;t5-smallandmt5-small→ shared on both stacks, i.e. the existing code path exactly.t5-smallnumerical parity vs Hugging Face unchanged atmax |diff| = 1.07e-06.Verification
Same model and harness, only the runtime differs:
Also validated on a 2.9B-parameter UMT5 checkpoint (24 encoder + 24 decoder layers) on an NVIDIA L4: correct conversion, and 139/140 prompts valid on a production benchmark at
float32, replacing a path that previously failed 100% of the time.Known limitations
1.07e-06noise floor measured ont5-smallwith the same harness in float32. Greedy and beam output match exactly, so generation is correct and only scores/sampling are affected. This is not introduced by these commits — a 1-layer UMT5 on the unchanged shared path still differs (0.022), zeroing every bias table still differs (0.025), and the error is flat in sequence length. I ruled out the activation,head_dim, and a scalar logit factor. Detail and reasoning are in UMT5 support: per-layer relative attention bias without regressing T5/mT5 #2102; I would appreciate a pointer if this is a known characteristic of the T5-family path.float16on GPU is unusable for large T5-family models, but this predates the change:google/mt5-small, which takes the untouched shared-bias path, fails identically on the stock unpatched 4.8.2 PyPI wheel. Instrumentation showed clean bias values with progressive activation overflow from layer 4 onward.float32is fine;bfloat16is correct at beam=1.google/umt5-*repositories omitmodel_typefromconfig.json, soAutoConfig.from_pretrainedraises before the loader registry is consulted and the key has to be added locally. A fallback dispatching onconfig.json'sarchitecturesentry would fix that for any pre-4.31 repository, but it touches shared converter code so I left it out of this PR. Happy to add it here or separately.