Skip to content

Commit 2d1d53e

Browse files
Merge pull request AI-Hypercomputer#4243 from AI-Hypercomputer:zjiahao/DSA3.2-approx-top-k
PiperOrigin-RevId: 938796674
2 parents 7549b91 + 76dae1b commit 2d1d53e

4 files changed

Lines changed: 70 additions & 1 deletion

File tree

src/maxtext/configs/base.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,8 @@ use_indexer: false
411411
indexer_head_dim: 128
412412
indexer_n_heads: 64
413413
indexer_topk: 2048
414+
indexer_use_approx_top_k: false
415+
indexer_approx_top_k_recall: 0.95
414416
# Determines the training strategy for the indexer:
415417
# - false (Dense Warm-up): Computes indexer loss over all tokens. Used with `trainable_parameters_mask` to freeze other model parameters.
416418
# - true (Sparse Training): Computes indexer loss over top-k tokens only and detaches the indexer input for independent optimization.

src/maxtext/configs/types.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,10 @@ class AttentionIndexer(BaseModel):
670670
description="Determines the training strategy for the indexer: Dense Warm-up or Sparse Training stage.",
671671
)
672672
indexer_loss_scaling_factor: float = Field(0.0, description="Multiplier for the indexer KL divergence loss.")
673+
indexer_use_approx_top_k: bool = Field(
674+
False, description="Whether to use approximate top-k selection for the indexer on TPU."
675+
)
676+
indexer_approx_top_k_recall: float = Field(0.95, description="Recall target for approximate top-k selection.")
673677

674678

675679
class Llama4Attention(BaseModel):

src/maxtext/layers/attention_mla.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,14 @@ def __call__(
366366
indexer_score += attention_mask
367367

368368
# TopK selection based on index score
369-
_, topk_indices = jax.lax.top_k(indexer_score, k=self.indexer_topk) # topk_indices [b, t, k]
369+
if self.config.indexer_use_approx_top_k:
370+
_, topk_indices = jax.lax.approx_max_k(
371+
indexer_score,
372+
k=self.indexer_topk,
373+
recall_target=self.config.indexer_approx_top_k_recall,
374+
)
375+
else:
376+
_, topk_indices = jax.lax.top_k(indexer_score, k=self.indexer_topk) # topk_indices [b, t, k]
370377

371378
# Create Sparse Index Mask: 0 and large negatives
372379
indexer_mask = self.generate_mask(topk_indices, k.shape[1]) # [b, t, s]

tests/unit/attention_test.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1878,6 +1878,62 @@ def test_indexer_loss_kl_divergence_zero(self):
18781878

18791879
np.testing.assert_allclose(loss, 0.0, atol=1e-5)
18801880

1881+
def test_indexer_with_approx_top_k(self):
1882+
"""Verify indexer runs with both approx and exact top-k."""
1883+
for use_approx in [False, True]:
1884+
with self.subTest(indexer_use_approx_top_k=use_approx):
1885+
mla_config_args = self.config_arguments.copy()
1886+
mla_config_args["use_indexer"] = True
1887+
mla_config_args["indexer_use_approx_top_k"] = use_approx
1888+
mla_config_args["indexer_topk"] = 4 # Force indexer to run instead of returning early
1889+
mla_config_args["attention"] = "dot_product"
1890+
1891+
cfg, mla = self.init_mla(mla_config_args, rope_type="default")
1892+
1893+
lnx, decoder_segment_ids, decoder_positions = self.get_structured_data(cfg, cfg.dtype)
1894+
1895+
# Run forward pass which triggers indexer
1896+
out, _ = mla(
1897+
lnx,
1898+
lnx,
1899+
decoder_segment_ids=decoder_segment_ids,
1900+
inputs_positions=decoder_positions,
1901+
deterministic=True,
1902+
model_mode=MODEL_MODE_TRAIN,
1903+
)
1904+
self.assertIsNotNone(out)
1905+
1906+
def test_approx_top_k_recall(self):
1907+
"""Verify that approx_max_k meets the specified recall target compared to exact top_k."""
1908+
jax_rng = jax.random.PRNGKey(0)
1909+
1910+
# We need a large enough N to make the approximation meaningful.
1911+
# Use shape [batch=4, queries=16, N=1024]
1912+
batch, queries, N = 4, 16, 1024
1913+
K = 64
1914+
recall_target = 0.95
1915+
1916+
# Generate random scores
1917+
scores = jax.random.normal(jax_rng, (batch, queries, N))
1918+
1919+
# 1. Run exact Top-K
1920+
_, true_indices = jax.lax.top_k(scores, k=K) # [batch, queries, K]
1921+
1922+
# 2. Run approx Top-K
1923+
_, approx_indices = jax.lax.approx_max_k(scores, k=K, recall_target=recall_target) # [batch, queries, K]
1924+
1925+
# 3. Calculate Recall
1926+
# Broadcast compare true_indices [B, Q, K, 1] and approx_indices [B, Q, 1, K]
1927+
matches = (true_indices[..., None] == approx_indices[..., None, :]).any(axis=-1) # [B, Q, K]
1928+
num_matches = matches.sum(axis=-1) # [B, Q]
1929+
actual_recalls = num_matches / K # [B, Q]
1930+
mean_recall = jnp.mean(actual_recalls)
1931+
1932+
print(f"\nApprox Top-K Recall Target: {recall_target}, Actual Mean Recall: {mean_recall:.4f}")
1933+
1934+
# Assert that the actual recall is equal or exceeds the target.
1935+
self.assertGreaterEqual(mean_recall, recall_target)
1936+
18811937
def test_indexer_gradients(self):
18821938
# Test that gradients do NOT flow back to inputs
18831939
bsz, seqlen = 2, 8

0 commit comments

Comments
 (0)