|
10 | 10 | from functools import partial |
11 | 11 | from typing import Any, Literal, NamedTuple, Optional |
12 | 12 |
|
| 13 | +import chex |
13 | 14 | import jax |
14 | 15 | import jax.numpy as jnp |
15 | 16 | import numpy as np |
@@ -163,7 +164,7 @@ class Config(Configurable.Config): |
163 | 164 |
|
164 | 165 | def __init__(self, cfg: Config): |
165 | 166 | super().__init__(cfg) |
166 | | - self.cfg: BaseFlashAttention.Config = self.config |
| 167 | + self.cfg = cfg |
167 | 168 |
|
168 | 169 | def get_backend_overrides(self, name: str, default: Any) -> Any: |
169 | 170 | return (self.cfg.backend_overrides or {}).get(name, default) |
@@ -575,3 +576,64 @@ def get_tpu_dot_precision(dtype) -> jax.lax.Precision: |
575 | 576 | if dtype == jnp.bfloat16: |
576 | 577 | return jax.lax.Precision.DEFAULT |
577 | 578 | raise ValueError(f"Unsupported dtype {dtype}") |
| 579 | + |
| 580 | + |
| 581 | +def maybe_pad_inputs( |
| 582 | + block_size: int, query: Tensor, key: Tensor, value: Tensor, segment_id: Optional[Tensor] |
| 583 | +) -> tuple[Tensor, Tensor, Tensor, Optional[Tensor]]: |
| 584 | + """Pads query, key, value, and segment_id tensors to align with block_size requirements. |
| 585 | +
|
| 586 | + This function ensures that the sequence length dimension of input tensors is divisible by |
| 587 | + block_size, which is required for efficient block-based attention computation. Padding is |
| 588 | + applied to the sequence length dimension (axis=1) as needed. |
| 589 | +
|
| 590 | + Args: |
| 591 | + block_size: The block size that sequence lengths must be divisible by. Must be positive. |
| 592 | + query: Query tensor of shape [batch_size, query_len, num_heads, per_head_dim]. |
| 593 | + key: Key tensor of shape [batch_size, key_len, num_kv_heads, per_head_dim]. |
| 594 | + value: Value tensor of shape [batch_size, value_len, num_kv_heads, per_head_dim]. |
| 595 | + Must have the same shape as key. |
| 596 | + segment_id: Optional segment ID tensor of shape [batch_size, seq_len]. If provided, |
| 597 | + it will be padded with -1 values to match query padding. |
| 598 | +
|
| 599 | + Returns: |
| 600 | + A tuple containing: |
| 601 | + - Padded query tensor |
| 602 | + - Padded key tensor |
| 603 | + - Padded value tensor |
| 604 | + - Padded segment_id tensor (or None if input was None) |
| 605 | +
|
| 606 | + Raises: |
| 607 | + AssertionError: If block_size is not positive, or if key and value shapes don't match. |
| 608 | + """ |
| 609 | + # Input validation with chex assertions |
| 610 | + chex.assert_scalar_positive(block_size) |
| 611 | + chex.assert_equal_rank((query, key, value)) |
| 612 | + chex.assert_rank(query, 4) |
| 613 | + chex.assert_equal_shape([key, value]) |
| 614 | + chex.assert_equal(query.shape[0], key.shape[0]) |
| 615 | + |
| 616 | + if segment_id is not None: |
| 617 | + chex.assert_rank(segment_id, 2) |
| 618 | + chex.assert_equal(segment_id.shape, query.shape[:2]) |
| 619 | + |
| 620 | + def pad_fn(x, pad): |
| 621 | + return jnp.pad(x, ((0, 0), (0, pad), (0, 0), (0, 0))) |
| 622 | + |
| 623 | + query_pad = -query.shape[1] % block_size |
| 624 | + if query_pad > 0: |
| 625 | + # This part will be cut after attention, so any value is fine. |
| 626 | + query = pad_fn(query, query_pad) |
| 627 | + if segment_id is not None: |
| 628 | + # segment_ids == 0 means padding. |
| 629 | + segment_id = jnp.pad(segment_id, ((0, 0), (0, query_pad))) |
| 630 | + |
| 631 | + key_pad = -key.shape[1] % block_size |
| 632 | + if key_pad > 0: |
| 633 | + key = pad_fn(key, key_pad) |
| 634 | + value = pad_fn(value, key_pad) |
| 635 | + |
| 636 | + chex.assert_equal(query.shape[1] % block_size, 0) |
| 637 | + chex.assert_equal(key.shape[1] % block_size, 0) |
| 638 | + chex.assert_equal(value.shape[1] % block_size, 0) |
| 639 | + return query, key, value, segment_id |
0 commit comments