vllm - ✅(Solved) Fix [Bug]: AssertionError on last PP rank with async scheduling + KV offloading [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vllm-project/vllm#41612Fetched 2026-05-05 05:44:43
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Author
Participants
Timeline (top)
commented ×1cross-referenced ×1labeled ×1mentioned ×1

Error Message

(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] WorkerProc hit an exception. (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] Traceback (most recent call last): (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] File ".../vllm/v1/executor/multiproc_executor.py", line 944, in worker_busy_loop (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] output = func(*args, **kwargs) (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] File ".../vllm/v1/worker/worker_base.py", line 332, in execute_model (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] return self.worker.execute_model(scheduler_output) (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] File ".../vllm/v1/worker/gpu_worker.py", line 803, in execute_model (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] output = self.model_runner.execute_model( (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] File ".../vllm/v1/worker/gpu_model_runner.py", line 4034, in execute_model (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] model_output = self._model_forward( (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] [... model forward succeeds ...] (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] File ".../vllm/v1/worker/gpu_worker.py", line 740, in sample_tokens (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] return self.model_runner.sample_tokens(grammar_output) (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] File ".../vllm/v1/worker/gpu_model_runner.py", line 4131, in sample_tokens (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] self._pp_receive_prev_sampled_token_ids_to_input_batch() (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] File ".../vllm/v1/worker/gpu_model_runner.py", line 4374, in _pp_receive_prev_sampled_token_ids_to_input_batch (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] assert not pp.is_last_rank (Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] AssertionError

Root Cause

In sample_tokens(), the early-return path (when execute_model_state is None) calls _pp_receive_prev_sampled_token_ids_to_input_batch() without checking is_last_rank:

# gpu_model_runner.py, line ~4131
if self.use_async_scheduling and get_pp_group().world_size > 1:
    self._pp_receive_prev_sampled_token_ids_to_input_batch()

The function asserts not pp.is_last_rank, intended only for non-last ranks:

def _pp_receive_prev_sampled_token_ids_to_input_batch(self) -> None:
    """Receive sampled token ids broadcast from last PP stage"""
    pp = get_pp_group()
    assert not pp.is_last_rank  # <-- last rank hits this
    ...

With KV offloading, the connector can return a no-op output on the last PP rank, setting execute_model_state = None. This pushes the last rank into the early-return path where it hits the assertion. Without KV offloading, the last rank always has model execution results and takes the normal sampling path (which correctly checks is_last_rank at line ~4137).

Fix Action

Workaround

--no-async-scheduling

PR fix notes

PR #40749: [Bugfix] Skip PP sampled-token receive on last rank during async scheduling

Description (problem / solution / changelog)

Summary

  • avoid calling the PP sampled-token receive path on the last pipeline-parallel rank during async scheduling
  • keep the receive path on non-last PP ranks, which still need sampled token IDs broadcast by the last rank
  • preserve the existing lazy PP-group lookup when async scheduling is disabled
  • add focused regression tests for the empty execute_model_state path in GPUModelRunner.sample_tokens()

Motivation / validation

We found this while bringing up Gemma 4 31B FP8 with TurboQuant KV cache on AMD RDNA4: RedHatAI/gemma-4-31B-it-FP8-block on 2x AMD Radeon AI PRO R9700 GPUs (gfx1201, 32 GiB each), vLLM v0.19.0 on our wi-adam/vllm RDNA4 branch, TheRock/ROCm 7.13, tq-k8v4 KV cache, and pipeline parallel size 2.

In that setup, sample_tokens() could call _pp_receive_prev_sampled_token_ids_to_input_batch() on every PP rank in the empty execute_model_state path. That helper asserts not pp.is_last_rank, but the last PP rank is the rank that broadcasts sampled token IDs, so it should not enter the receive path. The fix is to keep the receive path only for non-last PP ranks while preserving the old behavior that avoids even looking up the PP group when async scheduling is disabled.

We carried this patch in our RDNA4/Gemma 4 deployment and verified the patched stack serving with PP=2 in that environment. The unit tests added here cover the rank-selection contract directly without requiring the production GPU setup.

Duplicate check

This is not duplicating an existing upstream PR. I checked open PRs with these searches and found no matches:

  • _pp_receive_prev_sampled_token_ids_to_input_batch
  • use_async_scheduling is_last_rank sample_tokens
  • pipeline parallel async scheduling receive sampled token ids last rank

No upstream issue number is referenced by this patch.

Tests

  • .venv/bin/python -m pytest tests/v1/worker/test_gpu_model_runner.py::test_sample_tokens_receives_pp_sampled_ids_only_on_non_last_rank tests/v1/worker/test_gpu_model_runner.py::test_sample_tokens_skips_pp_group_lookup_without_async_scheduling -q -> 3 passed
  • .venv/bin/pre-commit run --files vllm/v1/worker/gpu_model_runner.py tests/v1/worker/test_gpu_model_runner.py -> passed

AI assistance

AI assistance was used to prepare this draft PR. The submitting human should review every changed line and validate the fix before marking ready for review.

Changed files

  • tests/v1/worker/test_gpu_model_runner.py (modified, +53/-0)
  • vllm/v1/worker/gpu_model_runner.py (modified, +1/-1)

Code Example

(Worker_PP2 pid=617) AssertionError
  File ".../vllm/v1/worker/gpu_model_runner.py", line 4374, in _pp_receive_prev_sampled_token_ids_to_input_batch
    assert not pp.is_last_rank

---

(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] WorkerProc hit an exception.
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] Traceback (most recent call last):
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/executor/multiproc_executor.py", line 944, in worker_busy_loop
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     output = func(*args, **kwargs)
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/worker_base.py", line 332, in execute_model
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     return self.worker.execute_model(scheduler_output)
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_worker.py", line 803, in execute_model
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     output = self.model_runner.execute_model(
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_model_runner.py", line 4034, in execute_model
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     model_output = self._model_forward(
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   [... model forward succeeds ...]
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_worker.py", line 740, in sample_tokens
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     return self.model_runner.sample_tokens(grammar_output)
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_model_runner.py", line 4131, in sample_tokens
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     self._pp_receive_prev_sampled_token_ids_to_input_batch()
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_model_runner.py", line 4374, in _pp_receive_prev_sampled_token_ids_to_input_batch
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     assert not pp.is_last_rank
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] AssertionError

---

# gpu_model_runner.py, line ~4131
if self.use_async_scheduling and get_pp_group().world_size > 1:
    self._pp_receive_prev_sampled_token_ids_to_input_batch()

---

def _pp_receive_prev_sampled_token_ids_to_input_batch(self) -> None:
    """Receive sampled token ids broadcast from last PP stage"""
    pp = get_pp_group()
    assert not pp.is_last_rank  # <-- last rank hits this
    ...

---

vllm serve groxaxo/Qwen3.6-27B-GPTQ-Pro-4bit \
  --quantization gptq_marlin \
  --pipeline-parallel-size 3 \
  --kv-offloading-backend native \
  --kv-offloading-size 16 \
  --enable-prefix-caching \
  --mamba-cache-mode align \
  --max-model-len 172480 \
  --gpu-memory-utilization 0.95 \
  --language-model-only \
  --enforce-eager \
  --kv-cache-dtype fp8

---

import openai

client = openai.OpenAI(api_key="test", base_url="http://localhost:8000/v1")

# This works (short request):
response = client.chat.completions.create(
    model="groxaxo/Qwen3.6-27B-GPTQ-Pro-4bit",
    messages=[{"role": "user", "content": "What is 2+2?"}],
    max_tokens=100,
)

# This crashes (long request triggers KV offloading on last PP rank):
response = client.chat.completions.create(
    model="groxaxo/Qwen3.6-27B-GPTQ-Pro-4bit",
    messages=[{"role": "user", "content": "x " * 6000 + "\nWhat is 2+2?"}],
    max_tokens=100,
)

---

if self.use_async_scheduling and get_pp_group().world_size > 1 and not get_pp_group().is_last_rank:
    self._pp_receive_prev_sampled_token_ids_to_input_batch()
RAW_BUFFERClick to expand / collapse

Your current environment

============================== System Info

OS : Ubuntu 22.04.5 LTS (x86_64) GCC version : (Ubuntu 11.4.0-1ubuntu1~22.04.3) 11.4.0 Clang version : Could not collect CMake version : Could not collect Libc version : glibc-2.35

============================== PyTorch Info

PyTorch version : 2.11.0+cu130 Is debug build : False CUDA used to build PyTorch : 13.0 ROCM used to build PyTorch : N/A XPU used to build PyTorch : N/A

============================== Python Environment

Python version : 3.12.13 (main, May 3 2026, 03:30:16) [GCC 11.4.0] (64-bit runtime) Python platform : Linux-6.19.9-arch1-1-x86_64-with-glibc2.35

============================== CUDA / GPU Info

Is CUDA available : True CUDA runtime version : 13.0.88 CUDA_MODULE_LOADING set to : GPU models and configuration : GPU 0: NVIDIA GeForce RTX 3060 GPU 1: NVIDIA GeForce RTX 3060 GPU 2: NVIDIA GeForce RTX 3060

Nvidia driver version : 595.58.03 cuDNN version : Could not collect HIP runtime version : N/A MIOpen runtime version : N/A Is XNNPACK available : True

============================== CPU Info

Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 43 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 64 On-line CPU(s) list: 0-63 Vendor ID: AuthenticAMD Model name: AMD EPYC 7551P 32-Core Processor CPU family: 23 Model: 1 Thread(s) per core: 2 Core(s) per socket: 32 Socket(s): 1 Stepping: 2 Frequency boost: enabled CPU max MHz: 2000.0000 CPU min MHz: 1200.0000 BogoMIPS: 3992.40 Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl nonstop_tsc cpuid extd_apicid amd_dcm aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb hw_pstate ssbd ibpb vmmcall fsgsbase bmi1 avx2 smep bmi2 rdseed adx smap clflushopt sha_ni xsaveopt xsavec xgetbv1 clzero xsaveerptr arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif overflow_recov succor smca sev sev_es Virtualization: AMD-V L1d cache: 1 MiB (32 instances) L1i cache: 2 MiB (32 instances) L2 cache: 16 MiB (32 instances) L3 cache: 64 MiB (8 instances) NUMA node(s): 4 NUMA node0 CPU(s): 0-7,32-39 NUMA node1 CPU(s): 8-15,40-47 NUMA node2 CPU(s): 16-23,48-55 NUMA node3 CPU(s): 24-31,56-63 Vulnerability Gather data sampling: Not affected Vulnerability Ghostwrite: Not affected Vulnerability Indirect target selection: Not affected Vulnerability Itlb multihit: Not affected Vulnerability L1tf: Not affected Vulnerability Mds: Not affected Vulnerability Meltdown: Not affected Vulnerability Mmio stale data: Not affected Vulnerability Old microcode: Not affected Vulnerability Reg file data sampling: Not affected Vulnerability Retbleed: Mitigation; untrained return thunk; SMT vulnerable Vulnerability Spec rstack overflow: Mitigation; Safe RET Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines; IBPB conditional; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected Vulnerability Srbds: Not affected Vulnerability Tsa: Not affected Vulnerability Tsx async abort: Not affected Vulnerability Vmscape: Mitigation; IBPB before exit to userspace

============================== Versions of relevant libraries

[pip3] flashinfer-python==0.6.8.post1 [pip3] numpy==2.2.6 [pip3] nvidia-cublas==13.1.0.3 [pip3] nvidia-cuda-cupti==13.0.85 [pip3] nvidia-cuda-nvrtc==13.0.88 [pip3] nvidia-cuda-runtime==13.0.96 [pip3] nvidia-cudnn-cu13==9.19.0.56 [pip3] nvidia-cudnn-frontend==1.18.0 [pip3] nvidia-cufft==12.0.0.61 [pip3] nvidia-cufile==1.15.1.6 [pip3] nvidia-curand==10.4.0.35 [pip3] nvidia-cusolver==12.0.4.66 [pip3] nvidia-cusparse==12.6.3.3 [pip3] nvidia-cusparselt-cu13==0.8.0 [pip3] nvidia-cutlass-dsl==4.4.2 [pip3] nvidia-cutlass-dsl-libs-base==4.4.2 [pip3] nvidia-ml-py==13.595.45 [pip3] nvidia-nccl-cu13==2.28.9 [pip3] nvidia-nvjitlink==13.0.88 [pip3] nvidia-nvshmem-cu13==3.4.5 [pip3] nvidia-nvtx==13.0.85 [pip3] pyzmq==27.1.0 [pip3] torch==2.11.0+cu130 [pip3] torch_c_dlpack_ext==0.1.5 [pip3] torchaudio==2.11.0+cu130 [pip3] torchvision==0.26.0+cu130 [pip3] transformers==5.7.0 [pip3] triton==3.6.0 [conda] Could not collect

============================== vLLM Info

ROCM Version : Could not collect vLLM Version : 0.20.1 vLLM Build Flags: CUDA Archs: 7.5 8.0 8.6 8.9 9.0 10.0 12.0+PTX; ROCm: Disabled; XPU: Disabled GPU Topology: GPU0 GPU1 GPU2 CPU Affinity NUMA Affinity GPU NUMA ID GPU0 X PHB SYS 8-15,40-47 1 N/A GPU1 PHB X SYS 8-15,40-47 1 N/A GPU2 SYS SYS X 16-23,48-55 2 N/A

Legend:

X = Self SYS = Connection traversing PCIe as well as the SMP interconnect between NUMA nodes (e.g., QPI/UPI) NODE = Connection traversing PCIe as well as the interconnect between PCIe Host Bridges within a NUMA node PHB = Connection traversing PCIe as well as a PCIe Host Bridge (typically the CPU) PXB = Connection traversing multiple PCIe bridges (without traversing the PCIe Host Bridge) PIX = Connection traversing at most a single PCIe bridge NV# = Connection traversing a bonded set of # NVLinks

============================== Environment Variables

VLLM_USE_SIMPLE_KV_OFFLOAD=1 VLLM_PP_LAYER_PARTITION=24,23,17 PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True NVIDIA_REQUIRE_CUDA=cuda>=13.0 brand=unknown,driver>=535,driver<536 brand=grid,driver>=535,driver<536 brand=tesla,driver>=535,driver<536 brand=nvidia,driver>=535,driver<536 brand=quadro,driver>=535,driver<536 brand=quadrortx,driver>=535,driver<536 brand=nvidiartx,driver>=535,driver<536 brand=vapps,driver>=535,driver<536 brand=vpc,driver>=535,driver<536 brand=vcs,driver>=535,driver<536 brand=vws,driver>=535,driver<536 brand=cloudgaming,driver>=535,driver<536 brand=unknown,driver>=550,driver<551 brand=grid,driver>=550,driver<551 brand=tesla,driver>=550,driver<551 brand=nvidia,driver>=550,driver<551 brand=quadro,driver>=550,driver<551 brand=quadrortx,driver>=550,driver<551 brand=nvidiartx,driver>=550,driver<551 brand=vapps,driver>=550,driver<551 brand=vpc,driver>=550,driver<551 brand=vcs,driver>=550,driver<551 brand=vws,driver>=550,driver<551 brand=cloudgaming,driver>=550,driver<551 brand=unknown,driver>=565,driver<566 brand=grid,driver>=565,driver<566 brand=tesla,driver>=565,driver<566 brand=nvidia,driver>=565,driver<566 brand=quadro,driver>=565,driver<566 brand=quadrortx,driver>=565,driver<566brand=nvidiartx,driver>=565,driver<566 brand=vapps,driver>=565,driver<566 brand=vpc,driver>=565,driver<566 brand=vcs,driver>=565,driver<566 brand=vws,driver>=565,driver<566 brand=cloudgaming,driver>=565,driver<566 brand=unknown,driver>=570,driver<571 brand=grid,driver>=570,driver<571 brand=tesla,driver>=570,driver<571 brand=nvidia,driver>=570,driver<571 brand=quadro,driver>=570,driver<571 brand=quadrortx,driver>=570,driver<571 brand=nvidiartx,driver>=570,driver<571 brand=vapps,driver>=570,driver<571 brand=vpc,driver>=570,driver<571 brand=vcs,driver>=570,driver<571 brand=vws,driver>=570,driver<571 brand=cloudgaming,driver>=570,driver<571 brand=unknown,driver>=575,driver<576 brand=grid,driver>=575,driver<576 brand=tesla,driver>=575,driver<576 brand=nvidia,driver>=575,driver<576 brand=quadro,driver>=575,driver<576 brand=quadrortx,driver>=575,driver<576 brand=nvidiartx,driver>=575,driver<576 brand=vapps,driver>=575,driver<576 brand=vpc,driver>=575,driver<576 brand=vcs,driver>=575,driver<576 brand=vws,driver>=575,driver<576 brand=cloudgaming,driver>=575,driver<576 CUDA_VERSION=13.0.2 LD_LIBRARY_PATH=/usr/local/nvidia/lib64:/usr/local/cuda/lib64:/usr/local/nvidia/lib:/usr/local/nvidia/lib64:/usr/local/cuda/lib64 NVIDIA_VISIBLE_DEVICES=all NVIDIA_DRIVER_CAPABILITIES=compute,utility VLLM_ENABLE_CUDA_COMPATIBILITY=0 TORCH_CUDA_ARCH_LIST=7.5 8.0 8.6 8.9 9.0 10.0 12.0+PTX VLLM_USAGE_SOURCE=production-docker-image PYTORCH_NVML_BASED_CUDA_CHECK=1 TORCHINDUCTOR_COMPILE_THREADS=1 TORCHINDUCTOR_CACHE_DIR=/tmp/torchinductor_root

🐛 Describe the bug

When using pipeline parallelism (PP > 1) with KV offloading and async scheduling enabled (the default since v0.14.0), the last PP rank crashes with an AssertionError on any request beyond a few thousand tokens.

The error:

(Worker_PP2 pid=617) AssertionError
  File ".../vllm/v1/worker/gpu_model_runner.py", line 4374, in _pp_receive_prev_sampled_token_ids_to_input_batch
    assert not pp.is_last_rank

Full traceback:

(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] WorkerProc hit an exception.
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] Traceback (most recent call last):
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/executor/multiproc_executor.py", line 944, in worker_busy_loop
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     output = func(*args, **kwargs)
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/worker_base.py", line 332, in execute_model
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     return self.worker.execute_model(scheduler_output)
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_worker.py", line 803, in execute_model
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     output = self.model_runner.execute_model(
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_model_runner.py", line 4034, in execute_model
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     model_output = self._model_forward(
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   [... model forward succeeds ...]
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_worker.py", line 740, in sample_tokens
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     return self.model_runner.sample_tokens(grammar_output)
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_model_runner.py", line 4131, in sample_tokens
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     self._pp_receive_prev_sampled_token_ids_to_input_batch()
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]   File ".../vllm/v1/worker/gpu_model_runner.py", line 4374, in _pp_receive_prev_sampled_token_ids_to_input_batch
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962]     assert not pp.is_last_rank
(Worker_PP2 pid=617) ERROR [multiproc_executor.py:962] AssertionError

Root cause

In sample_tokens(), the early-return path (when execute_model_state is None) calls _pp_receive_prev_sampled_token_ids_to_input_batch() without checking is_last_rank:

# gpu_model_runner.py, line ~4131
if self.use_async_scheduling and get_pp_group().world_size > 1:
    self._pp_receive_prev_sampled_token_ids_to_input_batch()

The function asserts not pp.is_last_rank, intended only for non-last ranks:

def _pp_receive_prev_sampled_token_ids_to_input_batch(self) -> None:
    """Receive sampled token ids broadcast from last PP stage"""
    pp = get_pp_group()
    assert not pp.is_last_rank  # <-- last rank hits this
    ...

With KV offloading, the connector can return a no-op output on the last PP rank, setting execute_model_state = None. This pushes the last rank into the early-return path where it hits the assertion. Without KV offloading, the last rank always has model execution results and takes the normal sampling path (which correctly checks is_last_rank at line ~4137).

To reproduce

vllm serve groxaxo/Qwen3.6-27B-GPTQ-Pro-4bit \
  --quantization gptq_marlin \
  --pipeline-parallel-size 3 \
  --kv-offloading-backend native \
  --kv-offloading-size 16 \
  --enable-prefix-caching \
  --mamba-cache-mode align \
  --max-model-len 172480 \
  --gpu-memory-utilization 0.95 \
  --language-model-only \
  --enforce-eager \
  --kv-cache-dtype fp8

Then send any request exceeding ~5K tokens:

import openai

client = openai.OpenAI(api_key="test", base_url="http://localhost:8000/v1")

# This works (short request):
response = client.chat.completions.create(
    model="groxaxo/Qwen3.6-27B-GPTQ-Pro-4bit",
    messages=[{"role": "user", "content": "What is 2+2?"}],
    max_tokens=100,
)

# This crashes (long request triggers KV offloading on last PP rank):
response = client.chat.completions.create(
    model="groxaxo/Qwen3.6-27B-GPTQ-Pro-4bit",
    messages=[{"role": "user", "content": "x " * 6000 + "\nWhat is 2+2?"}],
    max_tokens=100,
)

Short requests (under ~2K tokens) succeed because the connector does not produce a no-op. Longer requests trigger the offloading path on the last PP rank and crash.

Workaround

--no-async-scheduling

Proposed fix

Add an is_last_rank guard at the call site:

if self.use_async_scheduling and get_pp_group().world_size > 1 and not get_pp_group().is_last_rank:
    self._pp_receive_prev_sampled_token_ids_to_input_batch()

Regression context

VersionDateChangeImpact
v0.13.02025-12-19Before async scheduling defaultUnaffected
v0.14.02026-01-20PR #27614: async scheduling enabled by defaultPP users unaffected (async+PP mutually exclusive at this point)
v0.15.02026-01-29PR #32618: async scheduling + PP supportBug introduced -- assert not is_last_rank reachable on last rank via unchecked call site
v0.20.02026-04-27PR #38726: fix stuck chunked PP with async schedulingAdded chunked prefill skip, but assertion still precedes the check
v0.20.12026-05-03Current releaseBug persists

Before submitting a new issue...

  • Make sure you already searched for relevant issues, and asked the chatbot living at the bottom right corner of the documentation page, which can answer lots of frequently asked questions.

extent analysis

TL;DR

The bug can be fixed by adding an is_last_rank guard at the call site of _pp_receive_prev_sampled_token_ids_to_input_batch() to prevent the last PP rank from hitting the assertion.

Guidance

  • Identify the problematic code block in gpu_model_runner.py where the early-return path calls _pp_receive_prev_sampled_token_ids_to_input_batch() without checking is_last_rank.
  • Add a conditional check for is_last_rank before calling _pp_receive_prev_sampled_token_ids_to_input_batch() to ensure it's only executed on non-last ranks.
  • Verify the fix by running the provided reproduction script with the modified code.
  • Consider adding additional logging or error handling to handle cases where the last PP rank attempts to execute the problematic code block.

Example

if self.use_async_scheduling and get_pp_group().world_size > 1 and not get_pp_group().is_last_rank:
    self._pp_receive_prev_sampled_token_ids_to_input_batch()

Notes

The proposed fix assumes that the is_last_rank check is sufficient to prevent the assertion error. However, additional testing and verification may be necessary to ensure that the fix does not introduce any new issues or regressions.

Recommendation

Apply the proposed fix by adding the is_last_rank guard at the call site, as it directly addresses the root cause of the issue and prevents the last PP rank from hitting the assertion.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING