Skip to content

Commit 0a4ae3f

Browse files
justinchubyCopilot
andcommitted
fix: restore Shape(input_ids) for query_length in create_attention_bias()
Commit fd85bdf changed query_length to use Shape(attention_mask, start=1, end=2) so that the EliminateShape WebGPU rule would fire on it. But attention_mask always has shape (batch, total_seq_len), so: query_length = Shape(attention_mask, 1) → total_seq_len total_length = Shape(attention_mask, 1) → total_seq_len start = Sub(total_length, query_length) → 0 start=0 means Slice selects ALL positions as queries. During decode, input_ids is (batch, 1), so query_length should be 1 and start should be total_seq_len - 1, slicing only the last row. With start=0 the returned bias has shape (batch, 1, total_seq_len, total_seq_len) instead of the correct (batch, 1, 1, total_seq_len), causing completely wrong attention scores and garbage model output. Fix: revert query_length to Shape(input_ids, start=1, end=2). - total_length stays on attention_mask (EliminateShape still fires for it). - On WebGPU (concrete dims), Shape(input_ids, 1) is constant-folded away, so no dynamic Shape op survives in the WebGPU export. Add test: test_query_length_from_input_ids_not_attention_mask verifies that a Shape node reading from input_ids is present in the graph, guarding against future regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
1 parent b6ebf8d commit 0a4ae3f

2 files changed

Lines changed: 33 additions & 4 deletions

File tree

src/mobius/components/_common.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,14 @@ def create_attention_bias(
191191
# Actually we need to implement this with shape ops
192192

193193
# Get query_length and total_length from shapes.
194-
# Both come from attention_mask: total_length is the full sequence length, and
195-
# query_length is derived as total_length - past_length. Using attention_mask
196-
# for both means the EliminateShape rule on WebGPU can eliminate these Shape ops.
197-
query_length = op.Shape(attention_mask, start=1, end=2) # 1-D [1]
194+
# query_length comes from input_ids dim 1 (the query; e.g. 1 during decode).
195+
# total_length comes from attention_mask dim 1 (past + current tokens).
196+
# Using attention_mask for total_length lets the EliminateShape WebGPU rule
197+
# eliminate that Shape op. Using input_ids for query_length is semantically
198+
# correct: during decode input_ids is (batch, 1), so query_length=1 and
199+
# start = total_length - 1, giving the last row of q_indices.
200+
# On WebGPU (concrete dims), Shape(input_ids, 1) is constant-folded away.
201+
query_length = op.Shape(input_ids, start=1, end=2) # 1-D [1]
198202
total_length = op.Shape(attention_mask, start=1, end=2) # 1-D [1]
199203
start = op.Sub(total_length, query_length)
200204
# q_indices_2d: (batch_size, query_length)

src/mobius/components/_common_test.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,31 @@ def test_creates_bias_with_sliding_window(self):
9191
assert bias is not None
9292
assert count_op_type(graph, "Less") >= 1
9393

94+
def test_query_length_from_input_ids_not_attention_mask(self):
95+
"""Shape(input_ids, 1) must provide query_length, not Shape(attention_mask, 1).
96+
97+
During decode, input_ids is (batch, 1) and attention_mask is (batch, total_len).
98+
Both shapes must produce separate Shape nodes so the Slice picks only the
99+
last query row. If both came from attention_mask, start=0 and the full
100+
sequence would be used as queries, producing wrong attention scores.
101+
"""
102+
builder, op, graph = create_test_builder()
103+
# Simulate decode: q_len=1, total_len=8 (7 past + 1 current token)
104+
input_ids = create_test_input(builder, "input_ids", [2, 1], dtype=ir.DataType.INT64)
105+
attention_mask = create_test_input(
106+
builder, "attention_mask", [2, 8], dtype=ir.DataType.INT64
107+
)
108+
create_attention_bias(op, input_ids, attention_mask)
109+
110+
# query_length must come from input_ids (dim 1 = 1), not attention_mask.
111+
# Verify there is a Shape node that reads from input_ids.
112+
shape_inputs = [
113+
n.inputs[0].name for n in graph if n.op_type == "Shape" and n.inputs[0] is not None
114+
]
115+
assert any(name == "input_ids" for name in shape_inputs), (
116+
"Shape(input_ids, 1) must be present to extract query_length correctly"
117+
)
118+
94119

95120
class TestCreatePaddingMask:
96121
def test_creates_bool_mask_with_2d_input_ids(self):

0 commit comments

Comments
 (0)