Repository navigation
Conversation
HunYuanDenseV1 rotates Q and K and then applies query_layernorm and key_layernorm, but the attention bridge normalised before RoPE for every post-reshape QK-norm, so logits did not match Hugging Face (tencent/Hunyuan-0.5B-Instruct: max diff 3.4e-2 before, 4.9e-5 after). Add a qk_norm_after_rope config flag, set by the HunYuan adapter, and a parity test against a tiny random HF model.
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.
Description
HunYuanDenseV1ForCausalLMdoes not match Hugging Face through the bridge, because the bridge normalises Q and K before RoPE and Hugging Face does it after. Inmodeling_hunyuan_v1_dense.pythe attention forward callsapply_rotary_pos_emband only thenquery_layernormandkey_layernorm.PositionEmbeddingsAttentionBridgeapplies a post-reshape QK-norm before RoPE, which is right for Gemma-3 and Cohere and wrong here.This adds a config flag,
qk_norm_after_rope(defaultFalse, declared likerotary_adjacent_pairs). When it is set, the bridge rotates first and then appliesq_normandk_norm;hook_rot_qandhook_rot_kstill fire right after the rotation, andhook_q_normedandhook_k_normedfire after the norm. The HunYuan adapter sets it. Nothing else changes for the other architectures.I found this with a parity battery over the supported causal-LM architectures (a tiny random-weight Hugging Face model per architecture wrapped with
build_bridge_from_module, bridge against HF logits). In HunYuan the MLP outputs matched exactly and the attention output did not, which pointed at the attention order. The project's own record agrees:supported_models.jsonhastencent/Hunyuan-0.5B-Instructbelow threshold withforward_pass_logitsfailing atmax_diff=0.013494.On the real checkpoint (fp32, CPU, a 13 token prompt, logit scale 16.4):
devWith a tiny random model with wide weights the difference goes from 5.2 to 5.5e-06. I also checked that the argmax agrees at every position, before and after. I did not run
verify_modelsor the full benchmark.Fixes # (no issue, found by the parity check above)
Type of change
Tests
In
tests/unit/model_bridge/supported_architectures/test_hunyuan_v1_dense_adapter.py: the adapter setsqk_norm_after_ropeand the config default isFalse, andTestHunYuanDenseV1Paritybuilds a tiny randomHunYuanDenseV1ForCausalLM(no download) and checks the bridge logits withassert_tiny_parity. All three fail ondev(the parity test with the drift assertion) and pass with this change. The file passes, 53 tests.I ran all of
tests/unit(Windows, Python 3.12, torch 2.14.1 CPU, transformers 5.19.0): 7220 passed. 46 tests failed or errored, the same 46 on unmodifieddev. 43 of them need the optionaltimmandtorchvisionpackages of themultimodalgroup, and pass once those are installed. The other 3 fail the same way with and without this change:test_model_structure_doc.py::test_every_qualified_hook_name_across_docs_exists(aUnicodeDecodeErrorreading a file with the Windows default codec) and two intest_sparse_probing.py(test_default_tolerance_accepts_large_scale_activationsandtest_stop_reason_distinguishes_converged_from_capped_fits), which I did not look into. I ranblack,isort,pyclnandmypyon the changed files. My black version also flagsposition_embeddings_attention.pyon unmodifieddev; I let it format that file, which only adds blank lines.Checklist:
dev, see above)