pymc-devs / pymc-devs/pytensor-ml

JAX aligns the causal mask top-left, so generation with a KV cache attends to the wrong prefix

Open
#120 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug layer
Dominant language
Python
Stars
9
Forks
7
Avg merge
6h 55m
Merged PRs (30d)
40

Description

scaled_dot_product_attention(is_causal=True) builds its triangle bottom-right — k_idx <= q_idx + (sk - sq) — so a short query block sees the whole prefix, which is what a KV cache needs. The JAX dispatch passes is_causal straight through to jax.nn.dot_product_attention, which aligns top-left, so query row 0 sees only key 0. Every other backend takes the reference path and gets the documented alignment. Equal query and key lengths agree to 7e-8, so training and every test pass; the divergence appears only when q_len != kv_len, which is only ever autoregressive decoding. Supplying your own mask alongside is_causal=True does not fix it — jax composes the two rather than letting the bias win.

import numpy as np
import pytensor
import pytensor.tensor as pt

from pytensor_ml.layers.attention import scaled_dot_product_attention

q = pt.tensor("q", shape=(1, 1, 2, 4))
k = pt.tensor("k", shape=(1, 1, 4, 4))
v = pt.tensor("v", shape=(1, 1, 4, 4))
out = scaled_dot_product_attention(q, k, v, is_causal=True)

# all scores equal, so each output row is the uniform average of what it may attend to
qn, kn = np.zeros((1, 1, 2, 4)), np.zeros((1, 1, 4, 4))
vn = np.eye(4).reshape(1, 1, 4, 4)

for mode in ["NUMBA", "JAX"]:
    print(mode, pytensor.function([q, k, v], out, mode=mode)(qn, kn, vn).squeeze())

# NUMBA [[0.333 0.333 0.333 0.   ]   <- rows see prefix {0,1,2} and {0,1,2,3}
#        [0.25  0.25  0.25  0.25 ]]
# JAX   [[1.   0.   0.   0.  ]       <- rows see {0} and {0,1}
#        [0.5  0.5  0.  0.  ]]

The MLX dispatch already builds the bottom-right triangle itself rather than delegating; the JAX one should do the same when q_len != kv_len.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the JAX attention dispatch and compare it with the MLX dispatch, which already constructs the bottom-right causal triangle. Run the provided NUMBA versus JAX reproduction with unequal query and key lengths; done means JAX matches the reference bottom-right alignment for KV-cache decoding while equal-length behavior remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
machine-learning
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.