SuffixGPU
GPU-resident suffix decoding drafter for LLM speculative decoding.
SuffixGPU moves the SuffixDecoding draft
path fully onto the accelerator: variable-length suffix matching with
frequency-greedy expansion, plus a cross-request global memory backed by a
GPU suffix array. It mirrors the vLLM ngram_gpu proposer contract — device
tensors in, device tensors out, no per-step host synchronization — so it
composes with async scheduling and CUDA-graph capture.
Highlights
- Fully device-resident draft path.
propose/update_state/propose_with_updatenever sync to the host; all shapes are static and loop bounds are fixed, keeping the hot pathtorch.compile- and CUDA-graph-friendly. - Variable-length suffix matching. Per-request longest-suffix self-match
(single-pass rolling AND + one
topk), following vLLMNgramGPUKerneloccurrence semantics. - Frequency-ranked expansion. Depth-wise majority vote over matched
continuations with an adaptive stop rule (
max_spec_factor,max_spec_offset,min_token_prob) matching Arctic/SuffixDecoding semantics. - Cross-request global memory. Finished (or in-flight) sequences are ingested into a corpus ring + append-only delta buffer. The suffix array is rebuilt on a side CUDA stream, double-buffered, and swapped in-place so tensor identity is preserved for captured CUDA graphs.
- Fused Triton kernels for the hot path (
match_back,sa_search,expand_chain,scatter_append) with pure-PyTorch fallbacks — the package runs on CPU / CUDA / MPS without Triton. - int32 intermediates on the dominant
[B, S]buffers to cut memory traffic.
Installation
pip install suffix-gpu # library only (torch >= 2.5)
pip install "suffix-gpu[test]" # + pytest, numpy
pip install "suffix-gpu[vllm]" # + arctic-inference (CPU oracle)
pip install "suffix-gpu[bench]" # + benchmark deps (transformers, ...)
From source:
uv venv --python 3.12
source .venv/bin/activate
uv pip install -e ".[test]" # + pytest
# optional: CPU oracle for the equivalence tests / accuracy benchmark
uv pip install -e ".[vllm]" # + arctic-inference
Requires Python ≥ 3.10 and PyTorch ≥ 2.5. Triton is optional and picked up automatically on CUDA.
Quickstart
import torch
from suffix_gpu import SuffixGPUDrafter
drafter = SuffixGPUDrafter(
k=16, # max draft tokens per request
device="cuda",
enable_global=True, # cross-request suffix-array memory
max_spec_factor=2.0, # adaptive cap: factor * match_len + offset
min_token_prob=0.1, # stop expanding when chain prob drops below
)
# Resident decode state (all on device, int32):
# token_ids_gpu [B, S] token buffer per request
# num_tokens_no_spec [B] valid length per request
# sampled_token_ids [B, T] last verifier output, -1 padded
draft, num_valid, num_tokens = drafter.propose_with_update(
num_tokens_no_spec, token_ids_gpu, sampled_token_ids,
)
# draft: [B, k] int32, -1 padded
# num_valid: [B] int32, number of proposed tokens per request
# feed finished requests into the global memory
drafter.harvest_finished(row_indices, lengths, token_ids_gpu)
drafter.poll() # swap in a completed background SA rebuild, if any
propose(num_tokens_no_spec, token_ids_gpu, combined_mask) is also available
when you manage state updates yourself.
Configuration
| Argument | Default | Meaning |
|---|---|---|
k |
— | Maximum draft length per request |
device |
"cpu" |
Torch device for all buffers |
max_pattern_len |
32 |
Longest suffix (pattern) considered for matching |
min_match_len |
1 |
Minimum suffix match length to draft from |
max_occurrences |
32 |
Occurrences kept per match for expansion/voting |
enable_global |
False |
Enable the cross-request suffix-array memory |
global_capacity |
1<<22 |
Corpus ring capacity (tokens) |
delta_capacity |
1<<16 |
Append-only delta buffer capacity |
rebuild_threshold |
delta_capacity // 2 |
Delta fill level that triggers a background SA rebuild |
rebuild_stream |
None |
CUDA stream for background rebuilds |
max_spec_factor / max_spec_offset |
None / 0.0 |
Adaptive draft-length cap: factor * match_len + offset |
min_token_prob |
0.0 |
Cumulative-probability cutoff during expansion |
How it works
┌────────────────────────────────────────────┐
token_ids [B,S] ──►│ 1. local match longest suffix of each │
num_tokens [B] │ (local_matcher) request in its own ctx │
│ 2. global match SA interval search + │
│ (sa_search / delta brute-force scan │
│ global_index) │
│ 3. expand depth-wise majority │
│ (expand) vote + adaptive stop │
└────────────────┬───────────────────────────┘
▼
draft [B,k], num_valid [B]
- Local matching — for each request, find the longest suffix of its
generated tokens that reoccurs earlier in the same context, collecting up
to
max_occurrencescontinuation sites in one pass. - Global matching — the same tail is searched in the shared corpus via
fixed-iteration binary search on the suffix array (all pattern lengths
1..max_pattern_lenas one flattened batch), plus a brute-force scan of the not-yet-indexed delta buffer. - Expansion — continuations are extended depth by depth; each step takes the majority token across occurrences and tracks an empirical chain probability used for the adaptive stop rule.
- Global index maintenance — new documents append to the delta; when it
fills past
rebuild_threshold, a fresh suffix array is built on a side stream over a staging corpus and event-poll swapped in without touching captured graphs. Oldest documents are evicted when the ring is full.
Layout
| Path | Role |
|---|---|
suffix_gpu/proposer.py |
SuffixGPUDrafter — orchestrates match → expand → draft |
suffix_gpu/local_matcher.py |
Per-request variable-length suffix self-matching |
suffix_gpu/suffix_array.py |
Suffix array construction (prefix doubling, pure torch) |
suffix_gpu/sa_search.py |
Fixed-iteration batched binary search over the SA |
suffix_gpu/expand.py |
Frequency-ranked chain expansion + adaptive stop |
suffix_gpu/global_index.py |
Corpus ring, delta buffer, double-buffered SA rebuild |
suffix_gpu/triton_kernels.py |
Fused Triton kernels (optional, auto-detected) |
suffix_gpu/reference.py |
Naive host-side oracles used by the tests |
Benchmarks
Full results, environment details, and all tables: RESULTS.md.
Drafter-level comparison vs the arctic_inference.SuffixDecodingCache CPU
suffix tree on Spec-Bench reference
replay (question.jsonl, md5 0c39ae23e6f213549c66d6d691c99034, tokenized
with NousResearch/Meta-Llama-3.1-8B-Instruct rev d10aef79) — NVIDIA L20,
torch 2.13, batch = whole category (58–80 requests), k=16:
| tokens/step (cold / warm global) | propose per step, B=80 warm | |
|---|---|---|
| arctic suffix-cpu | 1.10–1.57 / 6.3–11.1 | 1.0–3.4 ms (loop over batch) |
| SuffixGPU (CUDA graph) | 1.09–1.56 / 6.2–11.2 | 0.5 ms, flat, no host sync |
- Correctness: 221 tests pass, including fuzz equivalence vs arctic on unambiguous corpora; teacher-forced lockstep on Spec-Bench gives 54–77% token-identical drafts (residual = majority-vote tie ordering) with comparable useful-draft length against the reference continuation.
- Batch scaling (warm): CPU wins small batches (B≤32); crossover at B≈64–128; at B=128/256/512 the GPU graph path is 2.2× / 2.7× / 2.1× faster than the CPU speculate loop — and it never syncs to the host, so it composes with async scheduling and CUDA graphs.
- Memory: ≤ 532 MB reserved VRAM at B=256, S=16384, 4M-token corpus (persistent drafter state 96 MB; no extra CUDA-graph pool retention).
# Spec-Bench replay + lockstep agreement vs the Arctic CPU implementation
python benchmarks/bench_specbench.py --mode both --device cuda \
--data question.jsonl --tokenizer NousResearch/Meta-Llama-3.1-8B-Instruct
# synthetic accuracy + latency sweep (eager & CUDA graph)
python benchmarks/bench_vs_cpu.py --mode both --device cuda \
--batch 32 --k 16 --spec-factor 2.0 --min-token-prob 0.1
# persistent buffer / peak / CUDA-graph pool memory accounting
python benchmarks/bench_memory.py --device cuda
Testing
pytest -v
Every module is tested against the naive references in
suffix_gpu/reference.py; the suite is parametrized over cpu / cuda /
mps. tests/test_verification.py additionally fuzzes end-to-end
draft/verify equivalence against arctic-inference (skipped automatically if
the package is not installed).
References
- Oliaro et al., SuffixDecoding: A Model-Free Approach to Speeding Up Large Language Model Inference — arXiv:2411.04975. The draft semantics reproduced on device: longest-suffix matching, frequency-greedy expansion, adaptive speculation length.
- Snowflake ArcticInference —
CPU suffix-tree reference (
arctic_inference.suffix_decoding), used as the equivalence oracle in tests and benchmarks. - vLLM
v1/spec_decode/ngram_proposer_gpu.py— the GPU proposer contract this drafter mirrors: device tensors in/out, no per-step host sync, async-scheduler compatible. - Manber & Myers, Suffix Arrays: A New Method for On-Line String Searches
(SIAM J. Comput., 1993) — the prefix-doubling construction in
suffix_gpu/suffix_array.py.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file suffix_gpu-0.1.0.tar.gz.
File metadata
- Download URL: suffix_gpu-0.1.0.tar.gz
- Upload date:
- Size: 47.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Alibaba Group Enterprise Linux Server","version":"7.2","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
704534b22cb66b6665322021fc68ab3d0ba98c01a9662be96c666d467908b6e8
|
|
| MD5 |
4fe74369a6ea01ceda20ed8232d30d53
|
|
| BLAKE2b-256 |
747b88c34a8089b4d2aee96d575b003bbef4292d3b5a8661f7e792a0d07a5b27
|
File details
Details for the file suffix_gpu-0.1.0-py3-none-any.whl.
File metadata
- Download URL: suffix_gpu-0.1.0-py3-none-any.whl
- Upload date:
- Size: 30.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Alibaba Group Enterprise Linux Server","version":"7.2","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a89c28a0b91f9509b7ba59975a3ba9e2ed0739dc0ffb3e85e98c0492d9ebde37
|
|
| MD5 |
74375188a59383c83086d533a5840ad2
|
|
| BLAKE2b-256 |
0c4cd193a85e966ea7adfff72fbcab9b935e3be2507480e2406b2285613de4d4
|