Repository: vllm-project/vllm
Pull request #15489: [V1][TPU] Enable Top K
Author: login: NickLucche
Labels: name: tpu
description: Related to Google TPUs
name: ready
description: ONLY add when PR is ready to merge/full CI is needed
name: v1
Created: 2025-03-25T18:35:17Z | Merged: 2025-04-17T18:18:11Z

Enabling the topk optimization that was introduced in https://github.com/vllm-project/vllm/pull/15242.

Currently facing the very issue foreseen by @njhill here https://github.com/vllm-project/vllm/pull/15242#issuecomment-2751743779.
```
ERROR 03-25 18:27:23 [core.py:343]     random_sampled = self.topk_topp_sampler(
ERROR 03-25 18:27:23 [core.py:343]                      ^^^^^^^^^^^^^^^^^^^^^^^
ERROR 03-25 18:27:23 [core.py:343]   File "/home/nick/vllm/.venv/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1751, in _wrapped_call_impl
ERROR 03-25 18:27:23 [core.py:343]     return self._call_impl(*args, **kwargs)
ERROR 03-25 18:27:23 [core.py:343]            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ERROR 03-25 18:27:23 [core.py:343]   File "/home/nick/vllm/.venv/lib/python3.11/site-packages/torch/nn/modules/module.py", line 1762, in _call_impl
ERROR 03-25 18:27:23 [core.py:343]     return forward_call(*args, **kwargs)
ERROR 03-25 18:27:23 [core.py:343]            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ERROR 03-25 18:27:23 [core.py:343]   File "/home/nick/vllm/vllm/v1/sample/ops/topk_topp_sampler.py", line 119, in forward_tpu
ERROR 03-25 18:27:23 [core.py:343]     topk_values, topk_indices = torch.topk(logits, k, dim=-1)
ERROR 03-25 18:27:23 [core.py:343]                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ERROR 03-25 18:27:23 [core.py:343] TypeError: topk(): argument 'k' (position 2) must be int, not Tensor
```

~~Dumping work for reference, will look into it asap.~~

Update:
 - I've fixed topk only using @njhill work on https://github.com/vllm-project/vllm/pull/15478 which should land soon on main
 -  Removed the env variable that would fallback to native impl (as dicussed in prev pr)
 -  Addressed some of the cruft leftover from https://github.com/vllm-project/vllm/pull/15309


For completeness, I've run microbenchmarks and the new impl is slower (but of course correct):

```
//before
Running 32 elapsed time: 0.0018310546875
Running 32 elapsed time: 0.0017833709716796875
// after
 Running 32 elapsed time: 0.003275632858276367
Running 32 elapsed time: 0.003297090530395508
```
  
cc @hyeygit 


Diff:
diff --git a/tests/v1/tpu/test_sampler.py b/tests/v1/tpu/test_sampler.py
index 0147da533517..74ad81406b4a 100644
--- a/tests/v1/tpu/test_sampler.py
+++ b/tests/v1/tpu/test_sampler.py
@@ -1,4 +1,6 @@
 # SPDX-License-Identifier: Apache-2.0
+import random
+
 import pytest
 
 from vllm import LLM, envs
@@ -39,3 +41,19 @@ def test_sampler_different(model_name: str):
         # Unsupported `seed` param.
         sampling_params = SamplingParams(temperature=0.3, seed=42)
         output2 = llm.generate(prompts, sampling_params)
+
+    # Batch-case with TopK
+    for B in [4, 16]:
+        p = prompts * B
+        sampling_params = [
+            SamplingParams(
+                temperature=0.1,
+                min_p=0.8,
+                max_tokens=64,
+                # Vary number of ks
+                top_k=random.randint(4, 12)) for _ in range(B)
+        ]
+        # Make sure first two reqs have the same K
+        sampling_params[0] = sampling_params[1]
+        output = llm.generate(p, sampling_params)
+        assert output[0].outputs[0].text == output[1].outputs[0].text
diff --git a/tests/v1/tpu/test_topk_topp_sampler.py b/tests/v1/tpu/test_topk_topp_sampler.py
index dce0303e68d5..ff9217f8f3ca 100644
--- a/tests/v1/tpu/test_topk_topp_sampler.py
+++ b/tests/v1/tpu/test_topk_topp_sampler.py
@@ -5,7 +5,8 @@
 import torch
 
 from vllm.platforms import current_platform
-from vllm.v1.sample.ops.topk_topp_sampler import apply_top_k_top_p_tpu
+from vllm.v1.sample.ops.topk_topp_sampler import (apply_top_k_top_p,
+                                                  apply_top_k_top_p_tpu)
 
 if not current_platform.is_tpu():
     pytest.skip("This test needs a TPU.", allow_module_level=True)
@@ -16,6 +17,25 @@
 TOLERANCE = 1e-6
 
 
+def test_topk_equivalence_to_native_impl():
+    with torch.device(xm.xla_device()):
+        xm.set_rng_state(seed=33)
+
+        logits = torch.rand((BATCH_SIZE, VOCAB_SIZE))
+
+        # Random top-k values between 1 and 10.
+        k = torch.randint(1, 10, (BATCH_SIZE, ))
+
+        # Set k=vocab_size for ~50% of requests in the batch (top-k disabled).
+        k.masked_fill_(torch.randint(0, 2, (BATCH_SIZE, ), dtype=bool),
+                       VOCAB_SIZE)
+
+        result_tpu = apply_top_k_top_p_tpu(logits=logits.clone(), k=k, p=None)
+
+        result_native = apply_top_k_top_p(logits=logits.clone(), k=k, p=None)
+        assert torch.allclose(result_native, result_tpu)
+
+
 def test_topp_result_sums_past_p():
     with torch.device(xm.xla_device()):
         xm.set_rng_state(seed=33)
diff --git a/vllm/envs.py b/vllm/envs.py
index f80bf878f79c..24cbfd336ec8 100644
--- a/vllm/envs.py
+++ b/vllm/envs.py
@@ -103,7 +103,6 @@
     VLLM_DP_MASTER_PORT: int = 0
     VLLM_MARLIN_USE_ATOMIC_ADD: bool = False
     VLLM_V0_USE_OUTLINES_CACHE: bool = False
-    VLLM_TPU_DISABLE_TOPK_TOPP_OPTIMIZATION: bool = False
     VLLM_TPU_BUCKET_PADDING_GAP: int = 0
     VLLM_USE_DEEP_GEMM: bool = False
     VLLM_XGRAMMAR_CACHE_MB: int = 0
@@ -684,11 +683,6 @@ def maybe_convert_int(value: Optional[str]) -> Optional[int]:
     "VLLM_V0_USE_OUTLINES_CACHE":
     lambda: os.environ.get("VLLM_V0_USE_OUTLINES_CACHE", "0") == "1",
 
-    # If set, disables TPU-specific optimization for top-k & top-p sampling
-    "VLLM_TPU_DISABLE_TOPK_TOPP_OPTIMIZATION":
-    lambda: bool(int(os.environ["VLLM_TPU_DISABLE_TOPK_TOPP_OPTIMIZATION"]))
-    if "VLLM_TPU_DISABLE_TOPK_TOPP_OPTIMIZATION" in os.environ else None,
-
     # Gap between padding buckets for the forward pass. So we have
     # 8, we will run forward pass with [16, 24, 32, ...].
     "VLLM_TPU_BUCKET_PADDING_GAP":
diff --git a/vllm/v1/sample/ops/topk_topp_sampler.py b/vllm/v1/sample/ops/topk_topp_sampler.py
index f69623edd632..745b81ded3f1 100644
--- a/vllm/v1/sample/ops/topk_topp_sampler.py
+++ b/vllm/v1/sample/ops/topk_topp_sampler.py
@@ -72,14 +72,7 @@ def __init__(self):
                     "best performance, please install FlashInfer.")
                 self.forward = self.forward_native
         elif current_platform.is_tpu():
-            if envs.VLLM_TPU_DISABLE_TOPK_TOPP_OPTIMIZATION:
-                logger.warning(
-                    "TPU-specific optimization for top-k & top-p sampling are "
-                    "disabled, falling back to PyTorch-native implementation "
-                    "which could be very slow.")
-                self.forward = self.forward_native
-            else:
-                self.forward = self.forward_tpu
+            self.forward = self.forward_tpu
         else:
             self.forward = self.forward_native
 
@@ -146,12 +139,22 @@ def apply_top_k_top_p_tpu(
     chance of being chosen during final sampling, so we can consider the tie
     being broken then.
     """
+    probs = logits.softmax(dim=-1)
+    probs_sort, _ = probs.sort(dim=-1, descending=False)
+
     if k is not None:
-        logits = apply_top_k_only(logits, k)
+        top_k_count = probs_sort.size(1) - k.to(torch.long)  # shape: (batch, )
+        top_k_count = top_k_count.unsqueeze(dim=1)
+        top_k_cutoff = probs_sort.gather(-1, top_k_count)
+
+        # Make sure the no top-k rows are no-op.
+        no_top_k_mask = (k == logits.shape[1]).unsqueeze(dim=1)
+        top_k_cutoff.masked_fill_(no_top_k_mask, -float("inf"))
+
+        elements_to_discard = probs < top_k_cutoff
+        logits.masked_fill_(elements_to_discard, -float("inf"))
 
     if p is not None:
-        probs = logits.softmax(dim=-1)
-        probs_sort, _ = probs.sort(dim=-1, descending=False)
         cumprob = torch.cumsum(probs_sort, dim=-1)
         top_p_mask = cumprob <= 1 - p.unsqueeze(dim=1)
         top_p_mask[:, -1] = False  # at least one
@@ -224,7 +227,7 @@ def apply_top_k_only(
     max_top_k = k.max()
     # topk.values tensor has shape [batch_size, max_top_k].
     # Convert top k to 0-based index in range [0, max_top_k).
-    k_index = k.sub_(1).unsqueeze(1).expand(logits.shape[0], 1)
+    k_index = k.sub_(1).unsqueeze(1)
     top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index.long())
     # Handle non-topk rows.
     top_k_mask.masked_fill_(no_top_k_mask.unsqueeze(1), -float("inf"))
diff --git a/vllm/v1/sample/tpu/metadata.py b/vllm/v1/sample/tpu/metadata.py
index 3950fda3e5ea..917d8baf60bf 100644
--- a/vllm/v1/sample/tpu/metadata.py
+++ b/vllm/v1/sample/tpu/metadata.py
@@ -10,7 +10,7 @@
     temperature=-1.0,
     min_p=0.0,
     # strictly disabled for now
-    # top_k=-1,
+    top_k=0,
     # top_p=0.0,
     # frequency_penalties=0.0,
     # presence_penalties=0.0,
@@ -99,11 +99,13 @@ def fill_slice(cpu_tensor: torch.Tensor, fill_val) -> torch.Tensor:
 
         fill_slice(input_batch.temperature_cpu_tensor,
                    DEFAULT_SAMPLING_PARAMS["temperature"])
-        # TODO Temporarily disabled until sampling options are enabled
-        # fill_slice(input_batch.top_p_cpu_tensor)
-        # fill_slice(input_batch.top_k_cpu_tensor)
         fill_slice(input_batch.min_p_cpu_tensor,
                    DEFAULT_SAMPLING_PARAMS["min_p"])
+        fill_slice(input_batch.top_k_cpu_tensor,
+                   DEFAULT_SAMPLING_PARAMS["top_k"])
+        # TODO Temporarily disabled until sampling options are enabled
+        # fill_slice(input_batch.top_p_cpu_tensor,
+        #            DEFAULT_SAMPLING_PARAMS["top_p"])
 
         # Slice persistent device tensors to a fixed pre-compiled padded shape.
         return cls(
@@ -112,6 +114,7 @@ def fill_slice(cpu_tensor: torch.Tensor, fill_val) -> torch.Tensor:
             all_greedy=input_batch.all_greedy,
             # TODO enable more and avoid returning None values
             top_p=None,  # input_batch.top_p[:padded_num_reqs],
-            top_k=None,  # input_batch.top_k[:padded_num_reqs],
+            top_k=input_batch.top_k_cpu_tensor[:padded_num_reqs].to(
+                xla_device),
             min_p=input_batch.min_p_cpu_tensor[:padded_num_reqs].to(
                 xla_device))
diff --git a/vllm/v1/worker/tpu_model_runner.py b/vllm/v1/worker/tpu_model_runner.py
index 69251d8bbb31..c7f0d23d90a0 100644
--- a/vllm/v1/worker/tpu_model_runner.py
+++ b/vllm/v1/worker/tpu_model_runner.py
@@ -883,14 +883,19 @@ def _precompile_select_hidden_states(self) -> None:
                                       device=self.device)
                 torch._dynamo.mark_dynamic(indices, 0)
                 self.select_hidden_states(dummy_hidden, indices)
-            logger.info("  -- num_tokens: %d", num_tokens)
+                logger.info("  -- num_tokens: %d, num_seqs: %d", num_tokens,
+                            num_reqs)
+                # Requests can't be more than tokens. But do compile for the
+                # next bigger value in case num_tokens uses bucketed padding.
+                if num_reqs >= min(num_tokens, self.max_num_reqs):
+                    break
         xm.wait_device_ops()
         end = time.perf_counter()
         logger.info("Compilation finished in in %.2f [secs].", end - start)
         self._update_num_xla_graphs("select_hidden_states")
 
     def _precompile_sample_from_hidden(self) -> None:
-        logger.info("Compiling sampling with different input shapes.")
+        logger.info("Compiling sampling with different num_reqs.")
         start = time.perf_counter()
         hsize = self.model_config.get_hidden_size()
         for num_reqs in self.num_reqs_paddings:


Reviews:
user: login: yaochengji
body: LGTM! Thanks, Nick!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-10T16:52:39Z
user: login: alexm-redhat
body: LGTM! Just some small comments
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-11T16:15:52Z
user: login: NickLucche
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-11T16:58:51Z
user: login: hyeygit
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-14T17:15:43Z
user: login: NickLucche
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-14T17:35:58Z
user: login: hyeygit
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-15T16:55:21Z
user: login: NickLucche
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-15T17:29:05Z
user: login: mgoin
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-17T17:02:14Z

Review comments:
diff_hunk: @@ -39,3 +41,18 @@ def test_sampler_different(model_name: str):
         # Unsupported `seed` param.
         sampling_params = SamplingParams(temperature=0.3, seed=42)
         output2 = llm.generate(prompts, sampling_params)
+        
+    # Batch-case with TopK
+    for B in [4, 16]:
+        p = prompts * B
+        sampling_params = [
+            SamplingParams(temperature=0.1,
+                           min_p=0.8,
+                           max_tokens=64,
+                           top_k=random.randint(4, 12))
+        ] * B
+        # disable on first prompt to check top k handles it
+        sampling_params[0].top_k = -1
+        sampling_params[0].min_p = 0
+        output = llm.generate(p, sampling_params)
+        assert output[0].outputs[0].text != output[-1].outputs[0].text
path: tests/v1/tpu/test_sampler.py
user: login: alexm-redhat
body: I think the output of the first and last prompt should be different no matter if you set top_k to -1. Is it jus to see that it goes through?

maybe worth to have second and third prompts have same sampling_params and here just compare that outputs of 2 and 3 are the same (for some more sanity check).
created_at: 2025-04-11T16:15:41Z
author_association: COLLABORATOR
diff_hunk: @@ -39,3 +41,18 @@ def test_sampler_different(model_name: str):
         # Unsupported `seed` param.
         sampling_params = SamplingParams(temperature=0.3, seed=42)
         output2 = llm.generate(prompts, sampling_params)
+        
+    # Batch-case with TopK
+    for B in [4, 16]:
+        p = prompts * B
+        sampling_params = [
+            SamplingParams(temperature=0.1,
+                           min_p=0.8,
+                           max_tokens=64,
+                           top_k=random.randint(4, 12))
+        ] * B
+        # disable on first prompt to check top k handles it
+        sampling_params[0].top_k = -1
+        sampling_params[0].min_p = 0
+        output = llm.generate(p, sampling_params)
+        assert output[0].outputs[0].text != output[-1].outputs[0].text
path: tests/v1/tpu/test_sampler.py
user: login: NickLucche
body: Thanks will address!
created_at: 2025-04-11T16:58:51Z
author_association: COLLABORATOR
diff_hunk: @@ -224,8 +227,8 @@ def apply_top_k_only(
     max_top_k = k.max()
     # topk.values tensor has shape [batch_size, max_top_k].
     # Convert top k to 0-based index in range [0, max_top_k).
-    k_index = k.sub_(1).unsqueeze(1).expand(logits.shape[0], 1)
-    top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index.long())
+    k_index = k.sub_(1).unsqueeze(1)
+    top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index)
path: vllm/v1/sample/ops/topk_topp_sampler.py
user: login: hyeygit
body: Is this unintentional? This seems to be reverting what's at HEAD.
created_at: 2025-04-14T17:15:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -224,8 +227,8 @@ def apply_top_k_only(
     max_top_k = k.max()
     # topk.values tensor has shape [batch_size, max_top_k].
     # Convert top k to 0-based index in range [0, max_top_k).
-    k_index = k.sub_(1).unsqueeze(1).expand(logits.shape[0], 1)
-    top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index.long())
+    k_index = k.sub_(1).unsqueeze(1)
+    top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index)
path: vllm/v1/sample/ops/topk_topp_sampler.py
user: login: NickLucche
body: yep this change was applied on top of @njhill original implementation when we first intended to use that function for TPU too.
Now that implementations have diverged, I am reverting to what we had before.
created_at: 2025-04-14T17:35:58Z
author_association: COLLABORATOR
diff_hunk: @@ -224,8 +227,8 @@ def apply_top_k_only(
     max_top_k = k.max()
     # topk.values tensor has shape [batch_size, max_top_k].
     # Convert top k to 0-based index in range [0, max_top_k).
-    k_index = k.sub_(1).unsqueeze(1).expand(logits.shape[0], 1)
-    top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index.long())
+    k_index = k.sub_(1).unsqueeze(1)
+    top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index)
path: vllm/v1/sample/ops/topk_topp_sampler.py
user: login: hyeygit
body: I see. We might want to keep `k_index.long()` as that's not TPU-related. It was added in #15907.
created_at: 2025-04-15T16:55:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -224,8 +227,8 @@ def apply_top_k_only(
     max_top_k = k.max()
     # topk.values tensor has shape [batch_size, max_top_k].
     # Convert top k to 0-based index in range [0, max_top_k).
-    k_index = k.sub_(1).unsqueeze(1).expand(logits.shape[0], 1)
-    top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index.long())
+    k_index = k.sub_(1).unsqueeze(1)
+    top_k_mask = logits.topk(max_top_k, dim=1).values.gather(1, k_index)
path: vllm/v1/sample/ops/topk_topp_sampler.py
user: login: NickLucche
body: Right good catch, thanks for checking!
created_at: 2025-04-15T17:29:04Z
author_association: COLLABORATOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-25T18:35:27Z
body: 👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

Just a reminder: PRs would not trigger full CI run by default. Instead, it would only run `fastcheck` CI which starts running only a small and essential subset of CI tests to quickly catch errors. You can run other CI tests on top of those by going to your `fastcheck` build on Buildkite UI (linked in the PR checks section) and unblock them. If you do not have permission to unblock, ping `simon-mo` or `khluu` to add you in our Buildkite org.

Once the PR is approved and ready to go, your PR reviewer(s) can run CI to test the changes comprehensively before merging.

To run CI, PR reviewers can either: Add `ready` label to the PR or enable auto-merge.

🚀
author_association: NONE
user: login: mergify[bot]
created_at: 2025-03-26T03:31:40Z
body: This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @NickLucche.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork
author_association: CONTRIBUTOR
user: login: NickLucche
created_at: 2025-03-26T11:14:31Z
body: Let's hold until main is fixed to reduce entropy 


author_association: COLLABORATOR
user: login: mergify[bot]
created_at: 2025-03-27T05:54:07Z
body: This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @NickLucche.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork
author_association: CONTRIBUTOR
user: login: hyeygit
created_at: 2025-03-27T13:02:55Z
body: Thank you @NickLucche for this PR and thank you @njhill for fixing the batch case for top-k! In #15242 I only tested with the microbenchmark and test_sampler.py (scalar case only), not realizing that `k` can be batched. Thank you for the catch and sorry for the miss.

One thing to note is that on TPU `torch.topk` still involves a full vocab sort (see [XLA lowering](https://github.com/pytorch/xla/blob/79d1e86b7e99dc055f30f3a69aa71471c16f8dfe/torch_xla/csrc/xla_lower_util.cpp#L375)). The reason using `torch.topk` is so much faster on TPU is because of avoiding a full-vocab `torch.scatter` (as used in apply_top_k_top_p) which is extremely slow on TPU.
author_association: CONTRIBUTOR
user: login: NickLucche
created_at: 2025-03-31T13:48:55Z
body: The main blocker for this PR is topk recompilation. 
I was under the impression the XLA lowering would circumvent that need 
```python
import torch_xla.core.xla_model as xm
import torch_xla.debug.metrics as met
import torch

B, V = 3, 64
device = xm.xla_device()

logits = torch.randn(B, V, device=device)

# pre compile
k = 3
top_k_mask = logits.topk(k, dim=1).values
top_k_mask = top_k_mask.cpu()

print(met.short_metrics_report())
met.clear_all()

# Run
for k in [1, V]:
    top_k_mask = logits.topk(k, dim=1).values
    top_k_mask = top_k_mask.cpu()
    print(met.short_metrics_report()) # shows it compiles
    met.clear_all()
```

Is there something I am missing? @hyeygit @yaochengji 
author_association: COLLABORATOR
user: login: hyeygit
created_at: 2025-03-31T14:14:23Z
body: > The main blocker for this PR is topk recompilation. I was under the impression the XLA lowering would circumvent that need

@NickLucche thank you for raising this. I think it's because the return shape of `torch.topk` is a function of `k`, so a change in `k` would cause XLA to recompile.

If this k-induced recompilation is not acceptable (sounds like it isn't), then let me implement a TPU-specific top-k using the same logic as in #15736. Will send out a PR shortly.


author_association: CONTRIBUTOR
user: login: NickLucche
created_at: 2025-03-31T14:31:08Z
body:  > I think it's because the return shape of torch.topk is a function of k, so a change in k would cause XLA to recompile.

Yeah the reason is quite clear to me, I just don't know why I believed this wouldn't trigger recompilation lol 

> then let me implement a TPU-specific top-k using the same logic as in #15736.

That will do thanks a lot!
Then we have to put this PR on hold until the above gets merged or I cherry pick your work here and co-author.   
author_association: COLLABORATOR
user: login: mergify[bot]
created_at: 2025-04-02T12:55:48Z
body: This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @NickLucche.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork
author_association: CONTRIBUTOR
user: login: NickLucche
created_at: 2025-04-02T12:57:54Z
body: @hyeygit I took the liberty of adding your contrib https://github.com/vllm-project/vllm/pull/15891 to this PR so we can test. Locally everything looks fine, let's wait for the CI tests. Nice work thanks!
author_association: COLLABORATOR
user: login: NickLucche
created_at: 2025-04-02T13:00:22Z
body: PS this PR should be merged after https://github.com/vllm-project/vllm/pull/15736 lands
author_association: COLLABORATOR
user: login: mergify[bot]
created_at: 2025-04-03T10:48:40Z
body: This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @NickLucche.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork
author_association: CONTRIBUTOR
user: login: NickLucche
created_at: 2025-04-03T13:50:55Z
body: So enabling topk here is costing us a bit of performance. 
I am confident this is because the sampling graph got a bit bigger to execute and apart from the all_greedy batches, every request has to run through it.
Can you please confirm this on your side too? @yaochengji @hyeygit

I can propose further specializing the graph to separate topk/topp requests so that the rest is free to flow through-a separate graph when all requests in batch do not need topk/p-, at the cost of increased code complexity. Let me know what you think.
author_association: COLLABORATOR
user: login: hyeygit
created_at: 2025-04-03T15:30:27Z
body: > So enabling topk here is costing us a bit of performance.

@NickLucche yes I think I can corroborate with this. I ran `benchmark_serving.py` with top-k and top-p enabled and observed a 6.8% drop in throughput (5.98 req/s -> 5.57 req/s). This was on TPU VM v6e-1 and running Llama3.1-8B.
author_association: CONTRIBUTOR
user: login: alexm-redhat
created_at: 2025-04-03T15:41:50Z
body: Just ran this PR for llama 70B with 8 x v6e TPUs. It achieves 5 reqs/sec instead of the previous 5.1 reqs/sec so the performance penalty I see is negligible.
author_association: COLLABORATOR
user: login: hyeygit
created_at: 2025-04-14T17:14:11Z
body: @NickLucche small request -- could you incorporate the top-k equivalence test from my PR? https://github.com/vllm-project/vllm/pull/15891/files#diff-09d15417fe42d494c51aaa9635ad51536751cc3c0659a7c4ce3b66bd6900eb1f
author_association: CONTRIBUTOR
user: login: hgt312
created_at: 2025-07-29T02:06:06Z
body: default top_k pad value should not be zero? 0 trilgget the error in `probs_sort.gather(-1, top_k_count)`
author_association: NONE