Skip to content

Commit 574546f

Browse files
committed
Fix Falcon ALiBi bias when using the KV cache
The ALiBi bias was built over the current query length while attention scores span the KV-cache length. With cache length > query length this either crashes (broadcast mismatch, e.g. prefilling a longer cache) or silently adds a zero bias during single-token decode steps ([b, n, 1, 1] broadcasts over the cache), corrupting generate() output. Build the bias over the full cache length when a cache is present; positions are absolute and unused cache slots are masked out by the causal mask. The no-cache path is unchanged and produces bit-identical outputs. Fixes #2861
1 parent f0006d8 commit 574546f

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

keras_hub/src/models/falcon/falcon_causal_lm_test.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,42 @@ def wrapper(*args, **kwargs):
148148
# We should immediately abort and output the prompt.
149149
self.assertEqual(prompt, output)
150150

151+
def test_cached_decoding_logits_match_full_forward(self):
152+
# Regression test for
153+
# https://github.com/keras-team/keras-hub/issues/2861 — the ALiBi
154+
# bias must span the full KV-cache length, not just the current
155+
# query length.
156+
causal_lm = FalconCausalLM(**self.init_kwargs)
157+
batch_size, seq_length, kv_length = 2, 8, 16
158+
prefill_length = 4
159+
token_ids = ops.zeros((batch_size, seq_length), dtype="int32")
160+
# Full-forward pass without a cache.
161+
full_logits = causal_lm(
162+
{
163+
"token_ids": token_ids,
164+
"padding_mask": ops.ones_like(token_ids),
165+
}
166+
)
167+
# Cached pass: prefill, then decode one token at a time.
168+
num_layers = self.backbone.num_layers
169+
num_heads = self.backbone.num_attention_heads
170+
head_dim = self.backbone.hidden_dim // num_heads
171+
cache = ops.zeros(
172+
(batch_size, num_layers, 2, kv_length, num_heads, head_dim),
173+
dtype="float32",
174+
)
175+
logits, _, cache = causal_lm.call_with_cache(
176+
token_ids[:, :prefill_length], cache, 0
177+
)
178+
cached_logits = [logits]
179+
for i in range(prefill_length, seq_length):
180+
logits, _, cache = causal_lm.call_with_cache(
181+
token_ids[:, i : i + 1], cache, i
182+
)
183+
cached_logits.append(logits)
184+
cached_logits = ops.concatenate(cached_logits, axis=1)
185+
self.assertAllClose(cached_logits, full_logits, atol=1e-5, rtol=1e-5)
186+
151187
def test_generate_compilation(self):
152188
causal_lm = FalconCausalLM(**self.init_kwargs)
153189
# Assert we do not recompile with successive calls.

keras_hub/src/models/falcon/falcon_transformer_decoder.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,20 @@ def call(
118118

119119
x = self.input_layernorm(inputs)
120120

121-
mask = decoder_padding_mask
122-
if mask is None:
123-
batch_size, seq_length = ops.shape(inputs)[:2]
124-
mask = ops.ones((batch_size, seq_length), dtype="int32")
121+
if attention_cache is not None:
122+
# With a KV cache, the ALiBi bias must span the full key/value
123+
# length (the cache), not just the current query length. ALiBi
124+
# positions are absolute, so an all-ones mask yields `arange`
125+
# positions over the cache; unused cache slots are masked out by
126+
# the causal attention mask.
127+
batch_size = ops.shape(inputs)[0]
128+
kv_length = ops.shape(attention_cache)[2]
129+
mask = ops.ones((batch_size, kv_length), dtype="int32")
130+
else:
131+
mask = decoder_padding_mask
132+
if mask is None:
133+
batch_size, seq_length = ops.shape(inputs)[:2]
134+
mask = ops.ones((batch_size, seq_length), dtype="int32")
125135
alibi = self._build_alibi_tensor(self.num_attention_heads, mask)
126136

127137
# Attention block.

0 commit comments

Comments
 (0)