vLLM vs SGLang: Qwen3.8-27B GPTQ-4bit with DFlash2 on dual RTX 3090s
Executive summary
This report compares vLLM and SGLang under the same external workload: the same Qwen3.8-27B GPTQ-4bit target, official Z-Lab DFlash2 draft, two RTX 3090 GPUs, tensor parallelism 2, four speculative draft tokens, 1,044 prompt tokens, and 512 generated tokens per request.
vLLM wins the single-request decode result and scales substantially better beyond four concurrent requests. SGLang wins at concurrency four and delivers lower TTFT through concurrency four.
- Single request: vLLM reached 109.36 tok/s, 12.12% faster than SGLang's 97.54 tok/s.
- Best SGLang point: 294.13 tok/s at concurrency 4, 10.36% faster than vLLM at the same concurrency.
- Best aggregate result: vLLM reached 430.67 tok/s at concurrency 16, 66.49% faster than SGLang.
- SGLang was capped to five live requests by its Mamba state cache. At offered concurrency 8 and 16, queued requests increased mean TTFT to 3.40 s and 10.01 s.
Test setup
| Field | Value |
|---|---|
| Target model | btbtyler09/Qwen3.8-27B-GPTQ-4bit |
| Local target path | Qwen3.8-27B-GPTQ-4bit |
| Quantization | GPTQ, 4-bit |
| Draft model | z-lab/Qwen3.8-27B-DFlash2 |
| Speculative method | DFlash |
| Speculative draft tokens | 4 |
| GPUs | 2 × NVIDIA GeForce RTX 3090, 24 GiB each |
| Tensor parallelism | 2 |
| CPU | AMD Ryzen 9 9950X, 32 threads reported |
| Host interconnect | PCIe/PHB; no NVLink path |
| Context limit | 8,192 tokens |
| Prompt tokens | 1,044 |
| Output tokens | 512 per request |
| Streaming | Enabled |
| GPU memory fraction | 0.90 |
| Maximum requested concurrency | 16 |
| vLLM build | 0.26.1rc1.dev1045+g3406ec1da, isolated DFlash PR environment |
| SGLang build | 0.5.6.post3.dev9387+gc8e1ddc70 |
| Measurement client | localmaxxing-cli remote endpoint mode |
| Decode metric | Client-observed inter-token throughput; concurrent runs use the complete request window |
| TTFT metric | Client-observed first streamed token |
Single-request measurements use two warmups and five measured iterations. Concurrency 2, 4, 8, and 16 use one warmup and three measured iterations. Every measured request used the same prompt and output-token limit.
Runtime configuration parity and necessary differences
Both engines used the same target, draft checkpoint, draft count, tensor parallelism, context limit, offered concurrency, request body, hardware, and client measurement path.
| Runtime detail | vLLM | SGLang |
|---|---|---|
| DFlash method | dflash | DFLASH |
| Draft tokens | 4 | 4 |
| Decode CUDA graphs | Enabled | Enabled |
| Prefill CUDA graphs | Default enabled | Disabled after the default prefill graph capture exhausted GPU memory |
| Configured active requests | 16 | 16 |
| Effective active requests | 16 observed | Capped to 5 by Mamba state-cache allocation |
| Custom all-reduce | Runtime default | Disabled for the PCIe/PHB topology |
| DFlash checkpoint block size | Checkpoint block 8, runtime draft count 4 | Warning emitted for checkpoint block 8 vs runtime count 4; runtime initialized block 4 |
This is apples-to-apples at the model, hardware, request, and offered-load levels. It is not identical internally: each engine owns its cache allocator, graph implementation, scheduler, and kernels. Those differences are the subject of the comparison.
Primary results
All values below are arithmetic means across measured iterations or measured requests.
| Concurrency | vLLM decode tok/s | SGLang decode tok/s | vLLM advantage | vLLM mean TTFT | SGLang mean TTFT | Lower-TTFT engine |
|---|---|---|---|---|---|---|
| 1 | 109.36 | 97.54 | +12.12% | 90.51 ms | 67.40 ms | SGLang |
| 2 | 176.27 | 164.80 | +6.96% | 152.99 ms | 100.19 ms | SGLang |
| 4 | 266.53 | 294.13 | -9.38% | 265.78 ms | 104.27 ms | SGLang |
| 8 | 360.67 | 268.83 | +34.16% | 473.80 ms | 3,401.67 ms | vLLM |
| 16 | 430.67 | 258.67 | +66.49% | 902.61 ms | 10,011.75 ms | vLLM |
SGLang's throughput peaks at concurrency four. Its concurrency-eight and concurrency-sixteen runs offered the requested load, but the runtime admitted only five requests at once and queued the remainder. vLLM continued scaling through concurrency sixteen.
Decode-throughput distributions
tok/s is aggregate output throughput for the full concurrent request window.
| Engine | Concurrency | Samples | Min | P50 | Mean | Max | Stddev |
|---|---|---|---|---|---|---|---|
| vLLM | 1 | 5 | 109.30 | 109.40 | 109.36 | 109.40 | 0.05 |
| SGLang | 1 | 5 | 97.40 | 97.50 | 97.54 | 97.80 | 0.15 |
| vLLM | 2 | 3 | 172.60 | 172.60 | 176.27 | 183.60 | 6.35 |
| SGLang | 2 | 3 | 164.50 | 164.60 | 164.80 | 165.30 | 0.44 |
| vLLM | 4 | 3 | 262.20 | 263.00 | 266.53 | 274.40 | 6.82 |
| SGLang | 4 | 3 | 293.30 | 293.90 | 294.13 | 295.20 | 0.97 |
| vLLM | 8 | 3 | 357.80 | 361.50 | 360.67 | 362.70 | 2.55 |
| SGLang | 8 | 3 | 268.40 | 268.90 | 268.83 | 269.20 | 0.40 |
| vLLM | 16 | 3 | 429.50 | 430.70 | 430.67 | 431.80 | 1.15 |
| SGLang | 16 | 3 | 258.20 | 258.90 | 258.67 | 258.90 | 0.40 |
Time-to-first-token distributions
TTFT counts individual requests, not iteration windows. This table exposes queueing that a throughput-only comparison would hide.
| Engine | Concurrency | Requests | Min | P50 | Mean | Max | Stddev |
|---|---|---|---|---|---|---|---|
| vLLM | 1 | 5 | 89.99 ms | 90.61 ms | 90.51 ms | 90.75 ms | 0.31 ms |
| SGLang | 1 | 5 | 67.18 ms | 67.26 ms | 67.40 ms | 68.02 ms | 0.35 ms |
| vLLM | 2 | 6 | 89.71 ms | 161.08 ms | 152.99 ms | 183.88 ms | 32.31 ms |
| SGLang | 2 | 6 | 71.81 ms | 73.51 ms | 100.19 ms | 155.38 ms | 42.66 ms |
| vLLM | 4 | 12 | 90.79 ms | 296.13 ms | 265.78 ms | 315.86 ms | 82.82 ms |
| SGLang | 4 | 12 | 85.62 ms | 86.27 ms | 104.27 ms | 140.97 ms | 27.02 ms |
| vLLM | 8 | 24 | 91.89 ms | 525.36 ms | 473.80 ms | 537.74 ms | 136.57 ms |
| SGLang | 8 | 24 | 94.41 ms | 121.10 ms | 3,401.67 ms | 8,919.02 ms | 4,349.15 ms |
| vLLM | 16 | 48 | 899.99 ms | 902.80 ms | 902.61 ms | 904.25 ms | 1.34 ms |
| SGLang | 16 | 48 | 89.71 ms | 8,913.39 ms | 10,011.75 ms | 26,575.25 ms | 8,255.40 ms |
At concurrency eight, SGLang's p50 remains low because five requests start immediately while three wait for the next scheduler wave. The mean and maximum expose the queued tail. At concurrency sixteen, requests complete across four admission waves of 5 + 5 + 5 + 1.
Total-throughput distributions
tokSTotal combines prompt and output tokens over elapsed request time as reported by localmaxxing-cli. It is included for completeness; decode throughput and TTFT remain the more actionable interactive-serving metrics.
| Engine | Concurrency | Samples | Min | P50 | Mean | Max | Stddev |
|---|---|---|---|---|---|---|---|
| vLLM | 1 | 5 | 326.50 | 326.70 | 326.64 | 326.70 | 0.09 |
| SGLang | 1 | 5 | 292.70 | 293.10 | 293.20 | 294.00 | 0.48 |
| vLLM | 2 | 3 | 510.60 | 510.80 | 523.57 | 549.30 | 22.29 |
| SGLang | 2 | 3 | 490.20 | 494.30 | 492.97 | 494.40 | 2.40 |
| vLLM | 4 | 3 | 787.80 | 789.90 | 793.70 | 803.40 | 8.47 |
| SGLang | 4 | 3 | 879.10 | 880.50 | 880.63 | 882.30 | 1.60 |
| vLLM | 8 | 3 | 1,054.30 | 1,078.40 | 1,074.17 | 1,089.80 | 18.12 |
| SGLang | 8 | 3 | 810.60 | 810.60 | 811.47 | 813.20 | 1.50 |
| vLLM | 16 | 3 | 1,246.20 | 1,249.60 | 1,249.53 | 1,252.80 | 3.30 |
| SGLang | 16 | 3 | 782.50 | 784.50 | 783.83 | 784.50 | 1.15 |
DFlash acceptance and runtime behavior
| Engine | Observed acceptance length | Observed acceptance rate | Notes |
|---|---|---|---|
| vLLM | 2.66–2.93 in high-concurrency metric windows | 41.4–48.1% average draft acceptance | Four-position acceptance samples reached approximately 75%, 53%, 36%, and 27% |
| SGLang | Approximately 1.98–3.25 in sampled decode log windows | Approximately 33–75% in sampled windows | Active five-request windows commonly measured around 2.12–2.95 acceptance length and 38–65% acceptance |
The logging windows and rate definitions differ between engines, so these values validate that DFlash was active but should not be treated as a precise cross-engine acceptance benchmark.
SGLang startup explicitly reported:
DFlash2DraftModelinitialized and ready.- Fused DFlash KV materialization enabled.
- Decode CUDA graphs active.
max_running_requestscapped from 16 to 5 because the Mamba state cache allocated 29 slots and required five slots per request.
vLLM reported SpeculativeConfig(method='dflash', ...), DFlash speculative metrics, and sixteen simultaneously active requests during the matched high-concurrency run.
Why four draft tokens were used
A preliminary vLLM single-request sweep selected four drafts before the engine comparison.
| Draft tokens | Mean decode tok/s | Mean TTFT | Result |
|---|---|---|---|
| 3 | 98.28 | 105.91 ms | Slower |
| 4 | 109.36 | 90.51 ms | Best |
| 5 | 105.40 | 114.11 ms | Second |
| 7 | 99.58 | 117.67 ms | Lower acceptance and throughput |
A matched non-speculative vLLM run reached 65.28 tok/s, so four-token DFlash improved vLLM single-request decode throughput by 67.52%. A previously observed 96.6 tok/s endpoint was excluded as an autoregressive baseline after its recovered command showed MTP with two speculative tokens.
Reproduction commands
The common localmaxxing-cli workload was:
lmx speed-test run <vllm|sglang> \
--mode remote \
--base-url <endpoint> \
--hf-id btbtyler09/Qwen3.8-27B-GPTQ-4bit \
--served-model <served-alias> \
--quantization GPTQ-4bit \
--hardware hardware.json \
--spec-method dflash \
--spec-draft-model z-lab/Qwen3.8-27B-DFlash2 \
--spec-num-tokens 4 \
--prompt-tokens 1044 \
--max-tokens 512 \
--concurrency <1|2|4|8|16> \
--out <payload.json>
The vLLM server used:
vllm serve /models/Qwen3.8-27B-GPTQ-4bit \
--tensor-parallel-size 2 \
--max-model-len 8192 \
--max-num-seqs 16 \
--gpu-memory-utilization 0.90 \
--speculative-config \
'{"method":"dflash","model":"/models/Qwen3.8-27B-DFlash2","num_speculative_tokens":4}'
The SGLang server used equivalent model and workload settings:
python -m sglang.launch_server \
--model-path /models/Qwen3.8-27B-GPTQ-4bit \
--tp-size 2 \
--context-length 8192 \
--mem-fraction-static 0.90 \
--max-running-requests 16 \
--disable-custom-all-reduce \
--cuda-graph-backend-prefill disabled \
--speculative-algorithm DFLASH \
--speculative-draft-model-path /models/Qwen3.8-27B-DFlash2 \
--speculative-num-draft-tokens 4
Conclusion
For this dual-RTX-3090 Qwen3.8-27B GPTQ-4bit deployment, vLLM is the stronger general serving choice. It wins single-request decode, remains competitive at concurrency two, and continues scaling to 430.67 tok/s at concurrency sixteen without the severe queueing seen in SGLang.
SGLang is compelling at low concurrency. It has lower TTFT at concurrency one through four and posts the best concurrency-four result: 294.13 tok/s with 104.27 ms mean TTFT. For a controlled deployment guaranteed to stay at four or fewer simultaneous requests, SGLang is a valid performance winner.
The crossover is operational, not cosmetic. SGLang's Mamba state-cache allocator capped this configuration to five active requests, while vLLM admitted sixteen. Once offered load exceeded that cap, SGLang's aggregate throughput fell and tail latency expanded to 8.9–26.6 seconds. On this hardware and software revision, choose SGLang for tightly bounded low-concurrency latency and vLLM for single-stream decode speed, load tolerance, and aggregate throughput.
