wuyii8941 opened a new issue, #19696:
URL: https://github.com/apache/tvm/issues/19696

   
   ## Expected behavior
   
   `relax.op.nn.attention` should support symbolic batch dimensions — the common
   case for dynamic-batch serving. Adjacent Relax ops (`matmul`, `nn.conv2d`,
   `nn.softmax`, `nn.layer_norm`, `nn.batch_norm`, `nn.adaptive_avg_pool2d`,
   `nn.max_pool2d`, …) all already handle a symbolic batch dim correctly.
   
   ## Actual behavior
   
   `LegalizeOps` on an attention with a symbolic batch dim crashes inside
   `topi.nn.batch_matmul`:
   
   ```
   AssertionError: batch dimension doesn't match
   ```
   
   Static batch sizes work; only the symbolic case fails.
   
   ## Reproduction
   
   ```python
   import tvm
   from tvm import relax
   from tvm import tirx
   
   B = tirx.Var("B", "int64")
   bb = relax.BlockBuilder()
   Q = relax.Var("Q", relax.TensorStructInfo([B, 4, 2, 8], "float32"))
   K = relax.Var("K", relax.TensorStructInfo([B, 4, 2, 8], "float32"))
   V = relax.Var("V", relax.TensorStructInfo([B, 4, 2, 8], "float32"))
   with bb.function("main", [Q, K, V]):
       with bb.dataflow():
           y = bb.emit(relax.op.nn.attention(Q, K, V))
           gv = bb.emit_output(y)
       bb.emit_func_output(gv)
   
   mod = bb.get()
   mod = relax.transform.LegalizeOps()(mod)
   ```
   
   Stack trace:
   
   ```
   File ".../relax/transform/legalize_ops/nn.py", in _te_attention
       p = topi.nn.batch_matmul(q, k)
   File ".../topi/nn/batch_matmul.py", line 97, in batch_matmul
       assert XB == YB or XB == 1 or YB == 1, "batch dimension doesn't match"
   AssertionError: batch dimension doesn't match
   ```
   
   Replacing the symbolic `B` with any literal int (e.g. `[2, 4, 2, 8]`) makes
   the same module compile cleanly.
   
   ## Root cause
   
   Two-step chain:
   
   1. **`_te_attention` builds `q` and `k` via two separate Python expressions
      for the merged batch×head dim**
      (`python/tvm/relax/transform/legalize_ops/nn.py`):
      ```python
      q = topi.reshape(q, [batch_size * num_head, seq_len, head_dim])
      k = topi.reshape(k, [batch_size * num_head, seq_len_kv, head_dim])
      ```
      `batch_size * num_head` is evaluated twice, producing two *structurally
      equal but object-distinct* `Mul(B, 2)` PrimExpr nodes.
   
   2. **`topi.nn.batch_matmul` asserts batch-dim equality via plain Python
      `==`** (`python/tvm/topi/nn/batch_matmul.py:97`):
      ```python
      if oshape is None:
          assert XB == YB or XB == 1 or YB == 1, "batch dimension doesn't match"
      ```
   
      For two PrimExpr operands, Python `==` returns a `tirx.expr.EqualOp` (a
      deferred comparison). Its `bool()` only collapses when the comparison is
      structurally trivial — `bool(Mul(B,2) == Mul(B,2))` returns `False`
      because the two nodes are distinct objects. Witness:
   
      ```python
      >>> B = tirx.Var("B", "int64")
      >>> e1 = B * 2
      >>> e2 = B * 2
      >>> bool(e1 == e2)
      False
      ```
   
      So all three legs of the `assert` short-circuit to `False` and the
      assertion fires.
   
   The same function already has a workaround for the K-dim
   (`assert XK == YK or isinstance(YK, tvm.tirx.expr.Var)`), but the batch dim
   was left without one.
   
   ## Suggested fix
   
   Two viable fixes; the first is local to attention and verified end-to-end:
   
   **Fix A (recommended, local, verified).** Pass the merged batch dim as
   `oshape` in `_te_attention`, which skips the assertion entirely:
   
   ```python
   # python/tvm/relax/transform/legalize_ops/nn.py
   def _te_attention(q, k, v, bias, scale, causal_mask):
       batch_size, seq_len, num_head, head_dim = q.shape
       _, seq_len_kv, _, head_dim_v = v.shape
       bsxh = batch_size * num_head                # compute once
       q = topi.transpose(q, [0, 2, 1, 3])
       k = topi.transpose(k, [0, 2, 1, 3])
       v = topi.transpose(v, [0, 2, 1, 3])
       q = topi.reshape(q, [bsxh, seq_len, head_dim])
       k = topi.reshape(k, [bsxh, seq_len_kv, head_dim])
       v = topi.reshape(v, [bsxh, seq_len_kv, head_dim_v])
       # NEW: pass oshape so batch_matmul does not need to infer / assert it
       p = topi.nn.batch_matmul(q, k, oshape=[bsxh, seq_len, seq_len_kv])
       ...
   ```
   
   The same `oshape=[bsxh, seq_len, head_dim_v]` should be passed to the second
   `batch_matmul` (the one that multiplies the attention probabilities by `v`).
   
   I verified this fix locally: the original repro module above
   builds and runs correctly when these `oshape` args are added (output shape
   `(2, 4, 2, 8)` matches the static-batch case).
   
   **Fix B (broader, optional).** Generalize the `batch_matmul` batch check to
   recognize symbolic PrimExpr equality, mirroring the existing K-dim
   workaround:
   
   ```python
   # python/tvm/topi/nn/batch_matmul.py
   import tvm
   ana = tvm.tirx.analyzer.Analyzer()
   if oshape is None:
       same_batch = (
           XB == 1
           or YB == 1
           or (isinstance(XB, tvm.tirx.expr.PrimExpr) and isinstance(YB, 
tvm.tirx.expr.PrimExpr)
               and ana.can_prove_equal(XB, YB))
           or XB == YB
       )
       assert same_batch, f"batch dimension doesn't match: {XB} vs {YB}"
       ...
   ```
   
   Fix B would also unblock any *other* caller of `topi.nn.batch_matmul` that
   constructs the batch dim via separate Python expressions; Fix A only fixes
   attention. Either is small and self-contained.
   
   ## Impact
   
   Blocks `nn.attention` for the dynamic-batch deployment story that Relax is
   otherwise designed to support. Today's workaround is to compose `matmul` +
   `softmax` manually, which loses the structured op and any future BYOC /
   FlashAttention dispatch hooked to `relax.nn.attention`.
   
   ## Environment
   
   - TVM: latest `main` (commit `b172d5ea3`, 2026-06-08)
   - Python: 3.11
   - Reproduces on CPU (`target="llvm"`); the symbolic-shape rejection happens
     during `LegalizeOps`, before any backend code runs.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to