Skip to content

Commit 80a8510

Browse files
committed
Merge remote-tracking branch 'origin/main' into glm5.2-moe-export
2 parents 0c35a37 + 3dc671c commit 80a8510

2 files changed

Lines changed: 72 additions & 0 deletions

File tree

src/mobius/rewrite_rules/_group_query_attention.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,33 @@
4343

4444
from __future__ import annotations
4545

46+
import onnx_ir as ir
4647
from onnxscript.rewriter._basics import MatchFailureError, MatchResult
4748
from onnxscript.rewriter._rewrite_rule import (
4849
RewriteRuleClassBase,
4950
RewriteRuleSet,
5051
)
5152

53+
from mobius._passes._dtype_utils import initializer_dtype
54+
55+
56+
def _propagate_dtype(source: ir.Value, *targets: ir.Value) -> None:
57+
"""Stamp ``source``'s dtype onto rewrite-created intermediate values.
58+
59+
Values produced by the replacement builder (``op.Concat(...)``,
60+
``op.Transpose(...)``) carry no declared type. When those intermediates are
61+
later folded into initializers, a missing declared type forces the fold
62+
passes to recover the dtype from ``const_value`` — or, absent that, to
63+
default to ``FLOAT``, silently widening fp16 weights. Stamping the dtype of
64+
the parameter the value is derived from keeps the type consistent from the
65+
point the packed weight is created.
66+
"""
67+
dtype = initializer_dtype(source)
68+
if dtype is None:
69+
return
70+
for target in targets:
71+
target.dtype = dtype
72+
5273

5374
def _has_unequal_kv_head_dimensions(k, v, past_key, past_value) -> bool:
5475
"""Return whether static K/V shapes prove incompatible GQA head dimensions."""
@@ -364,6 +385,9 @@ def rewrite(
364385
packed_w = op.Concat(q_w, k_w, v_w, axis=0)
365386
# Transpose packed weight: (q_out+k_out+v_out, hidden) → (hidden, q_out+k_out+v_out)
366387
packed_wt = op.Transpose(packed_w, perm=[1, 0])
388+
# Concat/Transpose outputs have no declared type; carry the weight dtype
389+
# forward so the folded packed initializer keeps the model dtype.
390+
_propagate_dtype(q_w, packed_w, packed_wt)
367391
packed_qkv = op.MatMul(hidden, packed_wt)
368392

369393
# Recover remaining GQA inputs and attributes from the matched node
@@ -487,10 +511,14 @@ def rewrite(
487511
packed_w = op.Concat(q_w, k_w, v_w, axis=0)
488512
# Transpose packed weight: (q_out+k_out+v_out, hidden) → (hidden, q_out+k_out+v_out)
489513
packed_wt = op.Transpose(packed_w, perm=[1, 0])
514+
# Concat/Transpose outputs have no declared type; carry the weight dtype
515+
# forward so the folded packed initializer keeps the model dtype.
516+
_propagate_dtype(q_w, packed_w, packed_wt)
490517
packed_mm = op.MatMul(hidden, packed_wt)
491518

492519
# Concat biases: (q_out + k_out + v_out,)
493520
packed_bias = op.Concat(bias_q, bias_k, bias_v, axis=0)
521+
_propagate_dtype(bias_q, packed_bias)
494522
# GQA has no bias input — the Add stays in the graph.
495523
packed_qkv = op.Add(packed_mm, packed_bias)
496524

src/mobius/rewrite_rules/_group_query_attention_test.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,50 @@ def test_packed_qkv_with_bias_uses_concat_nodes(self):
726726
"Transpose input should be Concat of W_q, W_k, W_v"
727727
)
728728

729+
@pytest.mark.parametrize("dtype", [ir.DataType.FLOAT, ir.DataType.FLOAT16])
730+
def test_packed_weight_intermediates_declare_weight_dtype(self, dtype):
731+
"""Concat/Transpose intermediates carry the projection weight dtype.
732+
733+
The replacement builder leaves new values untyped. Without an explicit
734+
stamp, folding ``Transpose(Concat(W_q, W_k, W_v))`` into an initializer
735+
has no declared type to inherit and can widen fp16 weights to fp32.
736+
"""
737+
config = dataclasses.replace(_LLAMA_CONFIG, dtype=dtype)
738+
m = build_from_module(registry.get("llama")(config), config)["model"]
739+
740+
rewrite(m, pattern_rewrite_rules=group_query_attention_rules())
741+
rewrite(m, pattern_rewrite_rules=pack_qkv_for_gqa_rules())
742+
743+
gqa_nodes = [n for n in m.graph if n.op_type == "GroupQueryAttention"]
744+
assert len(gqa_nodes) == config.num_hidden_layers
745+
746+
for gqa in gqa_nodes:
747+
transpose = gqa.inputs[0].producer().inputs[1].producer()
748+
assert transpose.op_type == "Transpose"
749+
concat = transpose.inputs[0].producer()
750+
assert concat.op_type == "Concat"
751+
assert concat.outputs[0].dtype == dtype
752+
assert transpose.outputs[0].dtype == dtype
753+
754+
@pytest.mark.parametrize("dtype", [ir.DataType.FLOAT, ir.DataType.FLOAT16])
755+
def test_packed_bias_intermediate_declares_bias_dtype(self, dtype):
756+
"""The packed-bias Concat intermediate carries the bias dtype."""
757+
config = dataclasses.replace(_QWEN2_BIAS_CONFIG, dtype=dtype)
758+
m = build_from_module(registry.get("qwen2")(config), config)["model"]
759+
760+
rewrite(m, pattern_rewrite_rules=group_query_attention_rules())
761+
rewrite(m, pattern_rewrite_rules=pack_qkv_for_gqa_rules())
762+
763+
gqa_nodes = [n for n in m.graph if n.op_type == "GroupQueryAttention"]
764+
assert len(gqa_nodes) == config.num_hidden_layers
765+
766+
for gqa in gqa_nodes:
767+
add = gqa.inputs[0].producer()
768+
assert add.op_type == "Add"
769+
bias_concat = add.inputs[1].producer()
770+
assert bias_concat.op_type == "Concat"
771+
assert bias_concat.outputs[0].dtype == dtype
772+
729773
def test_packed_qkv_with_bias_runs_with_ort(self):
730774
"""Biased packed-QKV GQA model runs correctly with ORT."""
731775
model = registry.get("qwen2")(_QWEN2_BIAS_CONFIG)

0 commit comments

Comments
 (0)