vllm - ✅(Solved) Fix [Bug]: "RuntimeError: Already borrowed" in vLLM. [4 pull requests, 1 comments, 1 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#40949Fetched 2026-04-27 05:29:10
View on GitHub
Comments
1
Participants
1
Timeline
10
Reactions
0
Author
Participants
Timeline (top)
mentioned ×3subscribed ×3commented ×1cross-referenced ×1

Error Message

import functools import traceback

import tokenizers

def _trace_wrapper(fn): @functools.wraps(fn) def wrapper(*args, **kwargs): print(f"\n{'=' * 60}") print(f"[TOKENIZER TRACE] {fn.name} called") print(f"{'=' * 60}") traceback.print_stack() print(f"{'=' * 60}\n") return fn(*args, **kwargs) return wrapper

for _name in ("enable_padding", "no_padding", "enable_truncation", "no_truncation"): _orig = getattr(tokenizers.Tokenizer, _name) setattr(tokenizers.Tokenizer, _name, _trace_wrapper(_orig))

Root Cause

Several PRs patch common cases but do not address the root cause.

Fix Action

Fix / Workaround

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

Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 52 bits physical, 57 bits virtual Byte Order: Little Endian CPU(s): 224 On-line CPU(s) list: 0-223 Vendor ID: GenuineIntel Model name: INTEL(R) XEON(R) PLATINUM 8570 CPU family: 6 Model: 207 Thread(s) per core: 2 Core(s) per socket: 56 Socket(s): 2 Stepping: 2 CPU(s) scaling MHz: 27% CPU max MHz: 4000.0000 CPU min MHz: 800.0000 BogoMIPS: 4200.00 Flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l3 cat_l2 cdp_l3 intel_ppin cdp_l2 ssbd mba ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local split_lock_detect user_shstk avx_vnni avx512_bf16 wbnoinvd dtherm ida arat pln pts hwp hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke waitpkg avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg tme avx512_vpopcntdq la57 rdpid bus_lock_detect cldemote movdiri movdir64b enqcmd fsrm md_clear serialize tsxldtrk pconfig arch_lbr ibt amx_bf16 avx512_fp16 amx_tile amx_int8 flush_l1d arch_capabilities ibpb_exit_to_user Virtualization: VT-x L1d cache: 5.3 MiB (112 instances) L1i cache: 3.5 MiB (112 instances) L2 cache: 224 MiB (112 instances) L3 cache: 600 MiB (2 instances) NUMA node(s): 2 NUMA node0 CPU(s): 0-55,112-167 NUMA node1 CPU(s): 56-111,168-223 Vulnerability Gather data sampling: 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 Reg file data sampling: Not affected Vulnerability Retbleed: Not affected Vulnerability Spec rstack overflow: Not affected 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; Enhanced / Automatic IBRS; IBPB conditional; PBRSB-eIBRS SW sequence; BHI BHI_DIS_S Vulnerability Srbds: Not affected Vulnerability Tsa: Not affected Vulnerability Tsx async abort: Not affected Vulnerability Vmscape: Mitigation; IBPB before exit to userspace

Several PRs patch common cases but do not address the root cause.

https://github.com/vllm-project/vllm/pull/36557

  • Patch by giving mm_processor its thread-local deepcopy
  • Introduced before #34789, so does not handle the case if more than one thread exist in thread pool.

PR fix notes

PR #36557: [Bugfix] Fix RuntimeError: Already borrowed that degrades VLM serving throughput under concurrent load.

Description (problem / solution / changelog)

Root cause

BaseRenderer.__init__ passes the same HF tokenizer instance to both the AsyncMicrobatchTokenizer (API-side tokenization) and the multimodal processor (via create_processor). These run on different threads:

Request A (main thread, Step 3 — MM processing):
  process_for_engine → _process_multimodal → mm_processor.apply
    → call_hf_processor → hf_processor() → tokenizer.encode()
    → _encode_plus → set_truncation_and_padding
    → self._tokenizer.enable_truncation()          ← BORROWS Rust RefCell

Request B (executor thread, Step 2 — tokenization):
  tokenize_prompts_async → AsyncMicrobatchTokenizer.encode
    → run_in_executor → tokenizer.__call__()
    → _encode_plus → set_truncation_and_padding
    → self._tokenizer.enable_truncation()          ← ALREADY BORROWED → RuntimeError

The call_hf_processor retry loop (up to 5× with time.sleep(0.5)) masks the error from clients but causes severe latency spikes. Text-only models are unaffected because they never call call_hf_processor.

Ref: https://github.com/huggingface/tokenizers/issues/537

Fix

copy.deepcopy(tokenizer) before passing it to the multimodal processor, so it gets its own Rust tokenizer backend. Called once at startup (~1-5 MB, sub-second). Zero per-request cost.

Test Plan

Start a VLM server:

vllm serve Qwen/Qwen3-VL-4B-Instruct --max-model-len 4096 --dtype bfloat16

Send 512 concurrent multimodal chat completion requests (concurrency=128) using the OpenAI async client. Each request includes a 1x1 PNG image as a base64 data URL and max_tokens=1 (or max_tokens=100). Measure per-request latency and check server logs for "Failed to acquire tokenizer" retry warnings.

Test Result

max_tokens=1 (prefill-only, 512 requests, concurrency 128)

MetricBeforeAfter
Throughput14.4 req/s253.8 req/s
p50 latency8.563s0.471s
p99 latency12.046s0.566s

max_tokens=100 (prefill + decode, 512 requests, concurrency 128)

MetricBeforeAfter
Throughput20.3 req/s246.3 req/s
p50 latency6.538s0.474s
p99 latency10.087s0.584s

Server-side retries

BeforeAfter
"Failed to acquire tokenizer" warnings1140

<details> <summary> Essential Elements of an Effective PR Description Checklist </summary>
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.
  • (Optional) Release notes update. If your change is user facing, please update the release notes draft in the Google Doc.
</details>

Changed files

  • vllm/renderers/base.py (modified, +9/-1)

PR #40829: [Bugfix] Fix RuntimeError: Already borrowed by deepcopying tokenizer for AsyncMicrobatchTokenizer

Description (problem / solution / changelog)

Purpose

Fix RuntimeError: Already borrowed when HuggingFace's Rust tokenizer backend is accessed simultaneously from parsers or InputProcessor in the request handling loop and from AsyncMicrobatchTokenizer in the renderer.

This PR follow up https://github.com/vllm-project/vllm/pull/40059, which patched all the tool parsers to no longer call encode.

This PR address the root cause by passing a deepcopy of the tokenizer to AsyncMicrobatchTokenizer to remove mutually exclusive sharing. Similar to https://github.com/vllm-project/vllm/pull/3655, which does the same between the multimodal processor and AsyncMicrobatchTokenizer.

More detailed issue: #40949

Test Plan

Run on branch before https://github.com/vllm-project/vllm/pull/40059

BFCL_MODEL="meta-llama/Llama-3.3-70B-Instruct" \
BFCL_TOOL_CALL_PARSER="llama3_json" \
BFCL_TP_SIZE=2 \
BFCL_TEST_CATEGORY="multi_turn" \
BFCL_MAX_MODEL_LEN=16384 \
BFCL_EXTRA_ARGS="--data-parallel-size 2" \
bash .buildkite/scripts/tool_call/run-bfcl-eval.sh

Serve any chat completion capable model. Send concurrent chat completion requests with bad_words in SamplingParams.

Test Result

No more HTTP 500 errors from BFCL or chat completion logs.

llama-after2.out.txt llama-before.out.txt


<details> <summary> Essential Elements of an Effective PR Description Checklist </summary>
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.
  • (Optional) Release notes update. If your change is user facing, please update the release notes draft in the Google Doc.
</details>

Made with Cursor

Changed files

  • vllm/renderers/base.py (modified, +4/-1)

Code Example

==============================
        System Info
==============================
OS                           : Ubuntu 24.04.4 LTS (x86_64)
GCC version                  : (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Clang version                : 18.1.3 (1ubuntu1)
CMake version                : version 3.28.3
Libc version                 : glibc-2.39

==============================
       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.12 (main, Feb 12 2026, 00:42:14) [Clang 21.1.4 ] (64-bit runtime)
Python platform              : Linux-6.8.0-106-generic-x86_64-with-glibc2.39
    
==============================
       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 B200
GPU 1: NVIDIA B200
GPU 2: NVIDIA B200
GPU 3: NVIDIA B200
GPU 4: NVIDIA B200
GPU 5: NVIDIA B200
GPU 6: NVIDIA B200
GPU 7: NVIDIA B200

Nvidia driver version        : 580.126.09
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:                           52 bits physical, 57 bits virtual
Byte Order:                              Little Endian
CPU(s):                                  224
On-line CPU(s) list:                     0-223
Vendor ID:                               GenuineIntel
Model name:                              INTEL(R) XEON(R) PLATINUM 8570
CPU family:                              6
Model:                                   207
Thread(s) per core:                      2
Core(s) per socket:                      56
Socket(s):                               2
Stepping:                                2
CPU(s) scaling MHz:                      27%
CPU max MHz:                             4000.0000
CPU min MHz:                             800.0000
BogoMIPS:                                4200.00
Flags:                                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l3 cat_l2 cdp_l3 intel_ppin cdp_l2 ssbd mba ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local split_lock_detect user_shstk avx_vnni avx512_bf16 wbnoinvd dtherm ida arat pln pts hwp hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke waitpkg avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg tme avx512_vpopcntdq la57 rdpid bus_lock_detect cldemote movdiri movdir64b enqcmd fsrm md_clear serialize tsxldtrk pconfig arch_lbr ibt amx_bf16 avx512_fp16 amx_tile amx_int8 flush_l1d arch_capabilities ibpb_exit_to_user
Virtualization:                          VT-x
L1d cache:                               5.3 MiB (112 instances)
L1i cache:                               3.5 MiB (112 instances)
L2 cache:                                224 MiB (112 instances)
L3 cache:                                600 MiB (2 instances)
NUMA node(s):                            2
NUMA node0 CPU(s):                       0-55,112-167
NUMA node1 CPU(s):                       56-111,168-223
Vulnerability Gather data sampling:      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 Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
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; Enhanced / Automatic IBRS; IBPB conditional; PBRSB-eIBRS SW sequence; BHI BHI_DIS_S
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] mypy-extensions==1.1.0
[pip3] numpy==1.26.4
[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] open-clip-torch==2.32.0
[pip3] pytorch-lightning==2.6.1
[pip3] pyzmq==27.1.0
[pip3] segmentation-models-pytorch==0.5.0
[pip3] sentence-transformers==5.4.1
[pip3] terratorch==1.2.6
[pip3] torch==2.11.0+cu130
[pip3] torch-c-dlpack-ext==0.1.5
[pip3] torchaudio==2.11.0+cu130
[pip3] torchgeo==0.9.0
[pip3] torchmetrics==1.9.0
[pip3] torchvision==0.26.0+cu130
[pip3] transformers==5.5.3
[pip3] transformers-stream-generator==0.0.5
[pip3] triton==3.6.0
[pip3] tritonclient==2.67.0
[pip3] vector-quantize-pytorch==1.28.2
[conda] Could not collect

==============================
         vLLM Info
==============================
ROCM Version                 : Could not collect
vLLM Version                 : 0.19.2rc1.dev87+g9321ea719 (git sha: 9321ea719)
vLLM Build Flags:
  CUDA Archs: Not Set; ROCm: Disabled; XPU: Disabled
GPU Topology:
        GPU0    GPU1    GPU2    GPU3    GPU4    GPU5    GPU6    GPU7    NIC0    NIC1    NIC2    NIC3    NIC4    NIC5    NIC6    NIC7    NIC8 NIC9     NIC10   NIC11   NIC12   NIC13   NIC14   NIC15   CPU Affinity    NUMA Affinity   GPU NUMA ID
GPU0     X      NV18    NV18    NV18    NV18    NV18    NV18    NV18    NODE    NODE    NODE    NODE    PXB     NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS     0-55,112-167    0               N/A
GPU1    NV18     X      NV18    NV18    NV18    NV18    NV18    NV18    NODE    NODE    NODE    NODE    NODE    NODE    NODE    PXB     NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS     0-55,112-167    0               N/A
GPU2    NV18    NV18     X      NV18    NV18    NV18    NV18    NV18    NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE    PXB  NODE     SYS     SYS     SYS     SYS     SYS     SYS     0-55,112-167    0               N/A
GPU3    NV18    NV18    NV18     X      NV18    NV18    NV18    NV18    NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE PXB      SYS     SYS     SYS     SYS     SYS     SYS     0-55,112-167    0               N/A
GPU4    NV18    NV18    NV18    NV18     X      NV18    NV18    NV18    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      PXB     NODE    NODE    NODE    NODE    NODE    56-111,168-223  1               N/A
GPU5    NV18    NV18    NV18    NV18    NV18     X      NV18    NV18    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    PXB     NODE    NODE    56-111,168-223  1               N/A
GPU6    NV18    NV18    NV18    NV18    NV18    NV18     X      NV18    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    NODE    PXB     NODE    56-111,168-223  1               N/A
GPU7    NV18    NV18    NV18    NV18    NV18    NV18    NV18     X      SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    NODE    NODE    PXB     56-111,168-223  1               N/A
NIC0    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS      X      PIX     PIX     PIX     NODE    NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC1    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     PIX      X      PIX     PIX     NODE    NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC2    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     PIX     PIX      X      PIX     NODE    NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC3    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     PIX     PIX     PIX      X      NODE    NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC4    PXB     NODE    NODE    NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE     X      NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC5    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE     X      PIX     NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC6    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE    PIX      X      NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC7    NODE    PXB     NODE    NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE    NODE    NODE     X      NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC8    NODE    NODE    PXB     NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE     X   NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC9    NODE    NODE    NODE    PXB     SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE  X       SYS     SYS     SYS     SYS     SYS     SYS
NIC10   SYS     SYS     SYS     SYS     PXB     NODE    NODE    NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS       X      NODE    NODE    NODE    NODE    NODE
NIC11   SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE     X      PIX     NODE    NODE    NODE
NIC12   SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    PIX      X      NODE    NODE    NODE
NIC13   SYS     SYS     SYS     SYS     NODE    PXB     NODE    NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE     X      NODE    NODE
NIC14   SYS     SYS     SYS     SYS     NODE    NODE    PXB     NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    NODE     X      NODE
NIC15   SYS     SYS     SYS     SYS     NODE    NODE    NODE    PXB     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    NODE    NODE     X 

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

NIC Legend:

  NIC0: mlx5_0
  NIC1: mlx5_1
  NIC2: mlx5_2
  NIC3: mlx5_3
  NIC4: mlx5_4
  NIC5: mlx5_5
  NIC6: mlx5_6
  NIC7: mlx5_7
  NIC8: mlx5_8
  NIC9: mlx5_9
  NIC10: mlx5_10
  NIC11: mlx5_11
  NIC12: mlx5_12
  NIC13: mlx5_13
  NIC14: mlx5_14
  NIC15: mlx5_15

---

encode_trunc_true
-----------------
  encode_trunc_true         pass
  batch_encode_trunc_true   pass
  encode_trunc_false        already borrowed
  batch_encode_trunc_false  already borrowed
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

batch_encode_trunc_true
-----------------------
  encode_trunc_true         pass
  batch_encode_trunc_true   pass
  encode_trunc_false        already borrowed
  batch_encode_trunc_false  already borrowed
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

encode_trunc_false
------------------
  encode_trunc_true         already borrowed
  batch_encode_trunc_true   already borrowed
  encode_trunc_false        pass
  batch_encode_trunc_false  pass
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

batch_encode_trunc_false
------------------------
  encode_trunc_true         already borrowed
  batch_encode_trunc_true   already borrowed
  encode_trunc_false        pass
  batch_encode_trunc_false  pass
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

encode_trunc_mixed
------------------
  encode_trunc_true         already borrowed
  batch_encode_trunc_true   already borrowed
  encode_trunc_false        already borrowed
  batch_encode_trunc_false  already borrowed
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

batch_encode_trunc_mixed
------------------------
  encode_trunc_true         already borrowed
  batch_encode_trunc_true   already borrowed
  encode_trunc_false        already borrowed
  batch_encode_trunc_false  already borrowed
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

decode
------
  encode_trunc_true         pass
  batch_encode_trunc_true   pass
  encode_trunc_false        pass
  batch_encode_trunc_false  pass
  encode_trunc_mixed        pass
  batch_encode_trunc_mixed  pass
  decode                    pass
  batch_decode              pass

batch_decode
------------
  encode_trunc_true         pass
  batch_encode_trunc_true   pass
  encode_trunc_false        pass
  batch_encode_trunc_false  pass
  encode_trunc_mixed        pass
  batch_encode_trunc_mixed  pass
  decode                    pass
  batch_decode              pass

---

vllm serve Qwen/Qwen3-4B-Instruct-2507-FP8   --max-model-len 4096 --dtype bfloat16

---

> python stress_send.py --bad-word -n 200 -c 50
< Requests : 156/200 ok, 44 errors

---

import functools
import traceback

import tokenizers

def _trace_wrapper(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        print(f"\n{'=' * 60}")
        print(f"[TOKENIZER TRACE] {fn.__name__} called")
        print(f"{'=' * 60}")
        traceback.print_stack()
        print(f"{'=' * 60}\n")
        return fn(*args, **kwargs)
    return wrapper


for _name in ("enable_padding", "no_padding",
              "enable_truncation", "no_truncation"):
    _orig = getattr(tokenizers.Tokenizer, _name)
    setattr(tokenizers.Tokenizer, _name, _trace_wrapper(_orig))
RAW_BUFFERClick to expand / collapse

Your current environment

<details> <summary>The output of <code>python collect_env.py</code></summary>
==============================
        System Info
==============================
OS                           : Ubuntu 24.04.4 LTS (x86_64)
GCC version                  : (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
Clang version                : 18.1.3 (1ubuntu1)
CMake version                : version 3.28.3
Libc version                 : glibc-2.39

==============================
       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.12 (main, Feb 12 2026, 00:42:14) [Clang 21.1.4 ] (64-bit runtime)
Python platform              : Linux-6.8.0-106-generic-x86_64-with-glibc2.39
    
==============================
       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 B200
GPU 1: NVIDIA B200
GPU 2: NVIDIA B200
GPU 3: NVIDIA B200
GPU 4: NVIDIA B200
GPU 5: NVIDIA B200
GPU 6: NVIDIA B200
GPU 7: NVIDIA B200

Nvidia driver version        : 580.126.09
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:                           52 bits physical, 57 bits virtual
Byte Order:                              Little Endian
CPU(s):                                  224
On-line CPU(s) list:                     0-223
Vendor ID:                               GenuineIntel
Model name:                              INTEL(R) XEON(R) PLATINUM 8570
CPU family:                              6
Model:                                   207
Thread(s) per core:                      2
Core(s) per socket:                      56
Socket(s):                               2
Stepping:                                2
CPU(s) scaling MHz:                      27%
CPU max MHz:                             4000.0000
CPU min MHz:                             800.0000
BogoMIPS:                                4200.00
Flags:                                   fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l3 cat_l2 cdp_l3 intel_ppin cdp_l2 ssbd mba ibrs ibpb stibp ibrs_enhanced tpr_shadow flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local split_lock_detect user_shstk avx_vnni avx512_bf16 wbnoinvd dtherm ida arat pln pts hwp hwp_act_window hwp_epp hwp_pkg_req vnmi avx512vbmi umip pku ospke waitpkg avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg tme avx512_vpopcntdq la57 rdpid bus_lock_detect cldemote movdiri movdir64b enqcmd fsrm md_clear serialize tsxldtrk pconfig arch_lbr ibt amx_bf16 avx512_fp16 amx_tile amx_int8 flush_l1d arch_capabilities ibpb_exit_to_user
Virtualization:                          VT-x
L1d cache:                               5.3 MiB (112 instances)
L1i cache:                               3.5 MiB (112 instances)
L2 cache:                                224 MiB (112 instances)
L3 cache:                                600 MiB (2 instances)
NUMA node(s):                            2
NUMA node0 CPU(s):                       0-55,112-167
NUMA node1 CPU(s):                       56-111,168-223
Vulnerability Gather data sampling:      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 Reg file data sampling:    Not affected
Vulnerability Retbleed:                  Not affected
Vulnerability Spec rstack overflow:      Not affected
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; Enhanced / Automatic IBRS; IBPB conditional; PBRSB-eIBRS SW sequence; BHI BHI_DIS_S
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] mypy-extensions==1.1.0
[pip3] numpy==1.26.4
[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] open-clip-torch==2.32.0
[pip3] pytorch-lightning==2.6.1
[pip3] pyzmq==27.1.0
[pip3] segmentation-models-pytorch==0.5.0
[pip3] sentence-transformers==5.4.1
[pip3] terratorch==1.2.6
[pip3] torch==2.11.0+cu130
[pip3] torch-c-dlpack-ext==0.1.5
[pip3] torchaudio==2.11.0+cu130
[pip3] torchgeo==0.9.0
[pip3] torchmetrics==1.9.0
[pip3] torchvision==0.26.0+cu130
[pip3] transformers==5.5.3
[pip3] transformers-stream-generator==0.0.5
[pip3] triton==3.6.0
[pip3] tritonclient==2.67.0
[pip3] vector-quantize-pytorch==1.28.2
[conda] Could not collect

==============================
         vLLM Info
==============================
ROCM Version                 : Could not collect
vLLM Version                 : 0.19.2rc1.dev87+g9321ea719 (git sha: 9321ea719)
vLLM Build Flags:
  CUDA Archs: Not Set; ROCm: Disabled; XPU: Disabled
GPU Topology:
        GPU0    GPU1    GPU2    GPU3    GPU4    GPU5    GPU6    GPU7    NIC0    NIC1    NIC2    NIC3    NIC4    NIC5    NIC6    NIC7    NIC8 NIC9     NIC10   NIC11   NIC12   NIC13   NIC14   NIC15   CPU Affinity    NUMA Affinity   GPU NUMA ID
GPU0     X      NV18    NV18    NV18    NV18    NV18    NV18    NV18    NODE    NODE    NODE    NODE    PXB     NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS     0-55,112-167    0               N/A
GPU1    NV18     X      NV18    NV18    NV18    NV18    NV18    NV18    NODE    NODE    NODE    NODE    NODE    NODE    NODE    PXB     NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS     0-55,112-167    0               N/A
GPU2    NV18    NV18     X      NV18    NV18    NV18    NV18    NV18    NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE    PXB  NODE     SYS     SYS     SYS     SYS     SYS     SYS     0-55,112-167    0               N/A
GPU3    NV18    NV18    NV18     X      NV18    NV18    NV18    NV18    NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE PXB      SYS     SYS     SYS     SYS     SYS     SYS     0-55,112-167    0               N/A
GPU4    NV18    NV18    NV18    NV18     X      NV18    NV18    NV18    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      PXB     NODE    NODE    NODE    NODE    NODE    56-111,168-223  1               N/A
GPU5    NV18    NV18    NV18    NV18    NV18     X      NV18    NV18    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    PXB     NODE    NODE    56-111,168-223  1               N/A
GPU6    NV18    NV18    NV18    NV18    NV18    NV18     X      NV18    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    NODE    PXB     NODE    56-111,168-223  1               N/A
GPU7    NV18    NV18    NV18    NV18    NV18    NV18    NV18     X      SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    NODE    NODE    PXB     56-111,168-223  1               N/A
NIC0    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS      X      PIX     PIX     PIX     NODE    NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC1    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     PIX      X      PIX     PIX     NODE    NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC2    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     PIX     PIX      X      PIX     NODE    NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC3    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     PIX     PIX     PIX      X      NODE    NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC4    PXB     NODE    NODE    NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE     X      NODE    NODE    NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC5    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE     X      PIX     NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC6    NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE    PIX      X      NODE    NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC7    NODE    PXB     NODE    NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE    NODE    NODE     X      NODE NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC8    NODE    NODE    PXB     NODE    SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE     X   NODE     SYS     SYS     SYS     SYS     SYS     SYS
NIC9    NODE    NODE    NODE    PXB     SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE    NODE  X       SYS     SYS     SYS     SYS     SYS     SYS
NIC10   SYS     SYS     SYS     SYS     PXB     NODE    NODE    NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS       X      NODE    NODE    NODE    NODE    NODE
NIC11   SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE     X      PIX     NODE    NODE    NODE
NIC12   SYS     SYS     SYS     SYS     NODE    NODE    NODE    NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    PIX      X      NODE    NODE    NODE
NIC13   SYS     SYS     SYS     SYS     NODE    PXB     NODE    NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE     X      NODE    NODE
NIC14   SYS     SYS     SYS     SYS     NODE    NODE    PXB     NODE    SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    NODE     X      NODE
NIC15   SYS     SYS     SYS     SYS     NODE    NODE    NODE    PXB     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS     SYS  SYS      NODE    NODE    NODE    NODE    NODE     X 

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

NIC Legend:

  NIC0: mlx5_0
  NIC1: mlx5_1
  NIC2: mlx5_2
  NIC3: mlx5_3
  NIC4: mlx5_4
  NIC5: mlx5_5
  NIC6: mlx5_6
  NIC7: mlx5_7
  NIC8: mlx5_8
  NIC9: mlx5_9
  NIC10: mlx5_10
  NIC11: mlx5_11
  NIC12: mlx5_12
  NIC13: mlx5_13
  NIC14: mlx5_14
  NIC15: mlx5_15
</details>

Tokenizer Background:

Huggingface FastTokenizers use a Rust backend. Some tokenizer operations mutably borrow the backend, others immutably borrow it. Mutable borrows are exclusive -- if one thread already mutably borrows the backend, no other thread may borrow it. Immutable borrows are shareable -- if one thread mutably borrows the backend, another thread may concurrently borrow immutably the same backend.

OperationBorrow (mutable or not)
encodeYes, depends
batch_encode, __call__Yes, depends
decodeYes, immutable
batch_decodeYes, immutable
vocabNo

encode and batch_encode immutably borrow the backend if they padding and trunction kwargs did not change from the last time they are called. If padding or truncation changed, set_truncation_and_padding would need to be called, leading to a mutable borrow.

This means, "RuntimeError: Already borrowed" can only happen iff:

  1. One thread calls encode or batch_encode
  2. At least another thread calls encode, batch_encode, decode, or batch_decode
  3. batch_encode or encode must be called with different truncation and padding kwargs.
<details> <summary>Reproduction</summary>

validate_tokenizer_borrow_race.py

encode_trunc_true
-----------------
  encode_trunc_true         pass
  batch_encode_trunc_true   pass
  encode_trunc_false        already borrowed
  batch_encode_trunc_false  already borrowed
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

batch_encode_trunc_true
-----------------------
  encode_trunc_true         pass
  batch_encode_trunc_true   pass
  encode_trunc_false        already borrowed
  batch_encode_trunc_false  already borrowed
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

encode_trunc_false
------------------
  encode_trunc_true         already borrowed
  batch_encode_trunc_true   already borrowed
  encode_trunc_false        pass
  batch_encode_trunc_false  pass
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

batch_encode_trunc_false
------------------------
  encode_trunc_true         already borrowed
  batch_encode_trunc_true   already borrowed
  encode_trunc_false        pass
  batch_encode_trunc_false  pass
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

encode_trunc_mixed
------------------
  encode_trunc_true         already borrowed
  batch_encode_trunc_true   already borrowed
  encode_trunc_false        already borrowed
  batch_encode_trunc_false  already borrowed
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

batch_encode_trunc_mixed
------------------------
  encode_trunc_true         already borrowed
  batch_encode_trunc_true   already borrowed
  encode_trunc_false        already borrowed
  batch_encode_trunc_false  already borrowed
  encode_trunc_mixed        already borrowed
  batch_encode_trunc_mixed  already borrowed
  decode                    pass
  batch_decode              pass

decode
------
  encode_trunc_true         pass
  batch_encode_trunc_true   pass
  encode_trunc_false        pass
  batch_encode_trunc_false  pass
  encode_trunc_mixed        pass
  batch_encode_trunc_mixed  pass
  decode                    pass
  batch_decode              pass

batch_decode
------------
  encode_trunc_true         pass
  batch_encode_trunc_true   pass
  encode_trunc_false        pass
  batch_encode_trunc_false  pass
  encode_trunc_mixed        pass
  batch_encode_trunc_mixed  pass
  decode                    pass
  batch_decode              pass
</details>

Issue:

In vLLM, a renderer containing a tokenizer is created inside EngineClient. This is the canonical tokenizer used everywhere else. https://github.com/vllm-project/vllm/blob/32e45636e3d7e02615facc8c63645ce4ac1d7e11/vllm/v1/engine/async_llm.py#L132

It's used in:

  • Serving layers:
    • via renderer's render_chat
      • render_messages to apply the chat template, optionally tokenizing
      • tokenize_prompts to tokenize the prompt if not already tokenized
      • process_for_engine, which optionally does multimodal processing, which may call the tokenizer again
    • via renderer's render_chat_async
      • render_messages_async, which offloads to a thread pool _executor in the case of HFRenderer
      • tokenize_prompts_async, which offloads to a AsyncMicrobatchTokenizer also running on _executor thread pool.
      • process_for_engine_async, which offloads to multimodal processing to _executor
    • in tool and reasoning parsers
    • in PoolingServingBase's pre and post-processing, which calls renderer's render_chat and runs on _executor thread pool.
  • EngineClient:
    • InputProcessor, generally do not tokenize unless bad_words is set
    • OutputProcessor detokenize only.

Some operations run on the main thread, some in the thread pool. The thread pool contains one thread by default, but could contain more after https://github.com/vllm-project/vllm/issues/34789. As a result, the same tokenizer may run concurrently in the main thread and the _executor threads, leading to "RuntimeError: Already borrowed".

Partial fixes

Several PRs patch common cases but do not address the root cause.

mm_processor in process_for_engine_async

https://github.com/vllm-project/vllm/pull/36557

  • Patch by giving mm_processor its thread-local deepcopy
  • Introduced before #34789, so does not handle the case if more than one thread exist in thread pool.

Tool parsers

https://github.com/vllm-project/vllm/pull/40059

  • Patch by removing encode calls from tool parsers
  • Future parers may re-introduce the bug, there's no guarantee that parsers do not call encode/decode

There may be more, please let me know.

Remaining buggy and sus cases

Buggy: InputProcessor with bad_words in sampling param

Similar to tool parser case, encode in the main thread race against the _executor thread pool.

vllm serve Qwen/Qwen3-4B-Instruct-2507-FP8   --max-model-len 4096 --dtype bfloat16

Send request with bad_words sampling to a Chat Completions endpoint. stress_send.py

> python stress_send.py --bad-word -n 200 -c 50
< Requests : 156/200 ok, 44 errors

Smelly: render_messages_async and AsyncMicrobatchTokenizer

Runs in the _executor threadpool and but shares tokenizer with the main thread.

Smelly: PoolingServingBase

Runs in the _executor threadpool and but shares tokenizer with the main thread.

Smelly: mm_proessor when --renderer-num-workers > 1

Has its own tokeizer deepcopy is shared among all threads in _executor.

The smelly cases generally don't lead to error because tokenizers' encode or batch_encode are called with the same kwargs. So all borrows are immutable and shareable.

<details> <summary>Verify this yourself</summary>

Patch tokenizers with:

import functools
import traceback

import tokenizers

def _trace_wrapper(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        print(f"\n{'=' * 60}")
        print(f"[TOKENIZER TRACE] {fn.__name__} called")
        print(f"{'=' * 60}")
        traceback.print_stack()
        print(f"{'=' * 60}\n")
        return fn(*args, **kwargs)
    return wrapper


for _name in ("enable_padding", "no_padding",
              "enable_truncation", "no_truncation"):
    _orig = getattr(tokenizers.Tokenizer, _name)
    setattr(tokenizers.Tokenizer, _name, _trace_wrapper(_orig))

Then serve model as normal. All borrows are safe if you don't observe padding or truncation change after initial setup.

If you manually call tokenizer.enable_truncation(...) in before an encode or batch_encode call, failure will occur.

</details>

Potential fixes

Smallest change: pass AsyncMicrobatchTokenizer with a tokenizer deepcopy. https://github.com/vllm-project/vllm/pull/40829

Fix AsyncMicrobatchTokenizer the same way as the mm_processor, give its own copy of the tokenizer. Fixes any future tool parser issue and bad_words sampling param issue.

Does not address code smell. Fragile.

Principled: Remove --renderer-num-workers > 1 option and keep a executor specific tokenizer deepcopy.

This way, AsyncMicrobatchTokenizer, mm_processor, etc. can all safely share the same tokenizer copy. Performance isn't impacted because --renderer-num-workers = 1 is the default and #34789 showed that more works do not generally lead to better performance. https://github.com/vllm-project/vllm/pull/38418 already disallows this when mm processor cache is used.

Address the code smell. More principled.

Keep allowing --renderer-num-workers > 1 but use thread-local tokenizer.

Use threading's local() to store a thread local tokenizer. Update downstream accordingly to use the said tokenizer.

May have performance impact. Accessing local() dict isn't lock-free AFAIK. Also large blast radius.

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 most likely fix for the "RuntimeError: Already borrowed" issue in vLLM is to ensure that the tokenizer is not borrowed concurrently by multiple threads, which can be achieved by giving each thread its own deepcopy of the tokenizer.

Guidance

  1. Pass AsyncMicrobatchTokenizer with a tokenizer deepcopy: Fix AsyncMicrobatchTokenizer by giving it its own copy of the tokenizer, similar to the fix for mm_processor. This addresses the immediate issues with tool parsers and bad_words sampling param.
  2. Remove --renderer-num-workers > 1 option: Consider removing the option to have more than one renderer worker, as this can lead to concurrency issues with the tokenizer. This change would ensure that AsyncMicrobatchTokenizer, mm_processor, and other components can safely share the same tokenizer copy without introducing performance impacts.
  3. Use thread-local tokenizer: As an alternative, consider using threading's local() to store a thread-local tokenizer. This approach would require updating downstream components to use the thread-local tokenizer but may have a performance impact due to the access mechanism.
  4. Verify fixes: After applying any fix, verify that the "RuntimeError: Already borrowed" issue is resolved by testing the affected components and scenarios, such as serving models with bad_words sampling and using tool parsers.

Example

To give AsyncMicrobatchTokenizer its own tokenizer copy, you could modify the initialization of AsyncMicrobatchTokenizer to accept and use a deepcopy of the tokenizer, similar to how mm_processor was fixed.

Notes

  • The choice of fix depends on the specific requirements and constraints of the vLLM project, including performance considerations and the need to support multiple renderer workers.
  • Ensuring thread safety for the tokenizer is crucial to preventing the "RuntimeError: Already borrowed" issue, and the chosen fix should be thoroughly tested to verify its effectiveness.

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

vllm - ✅(Solved) Fix [Bug]: "RuntimeError: Already borrowed" in vLLM. [4 pull requests, 1 comments, 1 participants]