servebench 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- servebench-1.0.0/LICENSE +201 -0
- servebench-1.0.0/PKG-INFO +323 -0
- servebench-1.0.0/README.md +294 -0
- servebench-1.0.0/pyproject.toml +84 -0
- servebench-1.0.0/pyproject.toml.orig +63 -0
- servebench-1.0.0/src/servebench/__init__.py +21 -0
- servebench-1.0.0/src/servebench/api.py +189 -0
- servebench-1.0.0/src/servebench/capacity.py +104 -0
- servebench-1.0.0/src/servebench/check.py +246 -0
- servebench-1.0.0/src/servebench/cli.py +901 -0
- servebench-1.0.0/src/servebench/client.py +224 -0
- servebench-1.0.0/src/servebench/detect.py +120 -0
- servebench-1.0.0/src/servebench/diagnosis.py +227 -0
- servebench-1.0.0/src/servebench/load.py +383 -0
- servebench-1.0.0/src/servebench/metrics.py +169 -0
- servebench-1.0.0/src/servebench/mock_server.py +187 -0
- servebench-1.0.0/src/servebench/prometheus.py +141 -0
- servebench-1.0.0/src/servebench/prompts.py +142 -0
- servebench-1.0.0/src/servebench/quick.py +92 -0
- servebench-1.0.0/src/servebench/report.py +913 -0
- servebench-1.0.0/src/servebench/results.py +271 -0
- servebench-1.0.0/src/servebench/share.py +129 -0
- servebench-1.0.0/src/servebench/system.py +116 -0
- servebench-1.0.0/src/servebench/telemetry.py +263 -0
- servebench-1.0.0/src/servebench/tokens.py +39 -0
- servebench-1.0.0/src/servebench/workloads.py +153 -0
servebench-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: servebench
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Load-test any OpenAI-compatible LLM endpoint: TTFT, TPOT, ITL, throughput, goodput, charts.
|
|
5
|
+
Keywords: llm,benchmark,inference,serving,ttft,openai,vllm,ollama,llama.cpp
|
|
6
|
+
Author: Prajwal Pujari
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
12
|
+
Classifier: Topic :: System :: Benchmark
|
|
13
|
+
Requires-Dist: typer>=0.12
|
|
14
|
+
Requires-Dist: rich>=13
|
|
15
|
+
Requires-Dist: pydantic>=2.6
|
|
16
|
+
Requires-Dist: matplotlib>=3.8
|
|
17
|
+
Requires-Dist: aiohttp>=3.10
|
|
18
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
19
|
+
Requires-Dist: nvidia-ml-py>=13.615.71
|
|
20
|
+
Requires-Dist: transformers>=4.40 ; extra == 'hf'
|
|
21
|
+
Requires-Dist: fastapi>=0.110 ; extra == 'mock'
|
|
22
|
+
Requires-Dist: uvicorn>=0.29 ; extra == 'mock'
|
|
23
|
+
Requires-Python: >=3.11
|
|
24
|
+
Project-URL: Homepage, https://github.com/Prajwal7-2002/servebench
|
|
25
|
+
Project-URL: Issues, https://github.com/Prajwal7-2002/servebench/issues
|
|
26
|
+
Provides-Extra: hf
|
|
27
|
+
Provides-Extra: mock
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# servebench
|
|
31
|
+
|
|
32
|
+
[](https://github.com/Prajwal7-2002/servebench/actions/workflows/ci.yml)
|
|
33
|
+
[](LICENSE)
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
Load-test any OpenAI-compatible LLM endpoint (Ollama, llama.cpp server, vLLM, SGLang, your
|
|
37
|
+
own engine) and get the numbers that matter for serving: **TTFT, TPOT, inter-token latency,
|
|
38
|
+
throughput and goodput**, at p50/p90/p99, over a sweep of load levels, with charts and
|
|
39
|
+
side-by-side comparison of runs.
|
|
40
|
+
|
|
41
|
+
No GPU or engine install is needed on the client: it is one async Python process talking HTTP.
|
|
42
|
+
|
|
43
|
+
## Quickstart
|
|
44
|
+
|
|
45
|
+
Uses [uv](https://docs.astral.sh/uv/) (or pipx/pip). PyPI release coming; for now, from a
|
|
46
|
+
clone:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv tool install --from . servebench # puts `servebench` on your PATH
|
|
50
|
+
servebench quick # finds your local server, ~1 minute
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`quick` looks for Ollama, llama.cpp, LM Studio, vLLM and SGLang on their usual ports, picks
|
|
54
|
+
the loaded model, measures, and answers in plain language. A real run (Ollama with
|
|
55
|
+
`OLLAMA_NUM_PARALLEL=4` on a laptop):
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
servebench quick: Ollama 0.35.1 at http://127.0.0.1:11434/v1, model qwen2.5:0.5b
|
|
59
|
+
...
|
|
60
|
+
Ollama 0.35.1, qwen2.5:0.5b on AMD Ryzen 5 6600H, 15.3 GB RAM, NVIDIA GeForce RTX 3050 6GB Laptop GPU
|
|
61
|
+
One user: first token in 43 ms, then 203 tokens/s
|
|
62
|
+
Capacity: 4 users at once (first token < 500 ms and >= 20 tokens/s each, for 90% of requests)
|
|
63
|
+
At 4 users: 361 tokens/s in total, 136 tokens/s each, first token p99 161 ms
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
With default Ollama settings it also explains *why* the limit is where it is ("requests
|
|
67
|
+
queue: the server works on about 1 at once") and suggests the fix. Then
|
|
68
|
+
`servebench share <run.json>` prints a Markdown table to paste into a post or issue and
|
|
69
|
+
makes a result card:
|
|
70
|
+
|
|
71
|
+

|
|
72
|
+
|
|
73
|
+
Full control:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# sweep a running server, e.g. Ollama (on Windows use 127.0.0.1, not localhost)
|
|
77
|
+
servebench run --url http://127.0.0.1:11434/v1 --model qwen2.5:0.5b \
|
|
78
|
+
--concurrency 1,2,4,8 --input-len 256 --output-len 128 --out runs/ollama.json
|
|
79
|
+
|
|
80
|
+
# how many concurrent users fit your latency targets?
|
|
81
|
+
servebench find-capacity --url http://127.0.0.1:11434/v1 --model qwen2.5:0.5b \
|
|
82
|
+
--out runs/ollama-cap.json
|
|
83
|
+
|
|
84
|
+
# compare multiple runs side by side, with charts and GPU telemetry
|
|
85
|
+
servebench compare-models runs/model-q4.json runs/model-fp16.json
|
|
86
|
+
|
|
87
|
+
# CI performance regression gate (fails with exit code 1 on regressions)
|
|
88
|
+
servebench check candidate.json --baseline baseline.json \
|
|
89
|
+
--max-ttft-degradation-pct 15.0 --max-throughput-degradation-pct 10.0
|
|
90
|
+
|
|
91
|
+
# generate interactive HTML report
|
|
92
|
+
servebench report runs/ollama.json runs/llamacpp.json --out report.html
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Python API
|
|
96
|
+
|
|
97
|
+
Use Servebench programmatically in your own evaluation harnesses or notebooks:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
import servebench
|
|
101
|
+
|
|
102
|
+
res = servebench.benchmark(
|
|
103
|
+
url="http://127.0.0.1:8000/v1",
|
|
104
|
+
model="llama-3-8b",
|
|
105
|
+
levels=[1, 2, 4, 8],
|
|
106
|
+
preset="chat",
|
|
107
|
+
gpu_telemetry=True,
|
|
108
|
+
)
|
|
109
|
+
print(f"Peak: {max(lv.summary.output_throughput for lv in res.levels):.1f} tok/s")
|
|
110
|
+
|
|
111
|
+
# Evaluate CI performance regression
|
|
112
|
+
report = servebench.check_regression("candidate.json", "baseline.json")
|
|
113
|
+
assert report.passed, "Serving latency regressed!"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
No model at hand? `uv tool install --from . "servebench[mock]"` adds `servebench mock-server`,
|
|
117
|
+
a stand-in server with set timings, so you can try every command without a GPU
|
|
118
|
+
([see How it's tested](#how-its-tested)).
|
|
119
|
+
|
|
120
|
+
## Learn LLM inference by measuring it
|
|
121
|
+
|
|
122
|
+
Want to understand how inference engines, batching, and queueing behave under the hood?
|
|
123
|
+
Follow our hands-on guide: **[docs/learn-inference.md](docs/learn-inference.md)**.
|
|
124
|
+
|
|
125
|
+
It walks through 7 reproducible experiments with Ollama:
|
|
126
|
+
1. **Prefill scaling**: Why TTFT scales linearly with prompt length (~100 ms / 1k tokens).
|
|
127
|
+
2. **Decode bottleneck**: Why TPOT is constant (~4.9 ms/tok, ~200 tok/s) regardless of prompt length.
|
|
128
|
+
3. **Batching & slots**: Why `OLLAMA_NUM_PARALLEL=4` doubles throughput to 385 tok/s, and why 8 slots plateaus.
|
|
129
|
+
4. **The p99 latency knee**: Why p50 hides queues while p99 spikes 5× (197 ms → 943 ms) when slots fill.
|
|
130
|
+
5. **Little's Law in practice**: Proving $L = \lambda W$ with real serving numbers.
|
|
131
|
+
6. **Closed vs. open loop**: Why concurrency sweeps mask production breaking points.
|
|
132
|
+
7. **Benchmarking pitfalls**: Prompt cache hits, connection resets, and warm-up bias.
|
|
133
|
+
|
|
134
|
+
## Results
|
|
135
|
+
|
|
136
|
+
### Ollama on a laptop GPU: what `OLLAMA_NUM_PARALLEL` buys
|
|
137
|
+
|
|
138
|
+
Qwen2.5-0.5B (Q4_K_M) on Ollama 0.35.1, fully on an RTX 3050 6 GB laptop GPU. Synthetic
|
|
139
|
+
prompts of 256 tokens, 128 output tokens, 64 requests per level, `find-capacity` with
|
|
140
|
+
SLO TTFT < 500 ms and TPOT < 50 ms for 90% of requests. Only `OLLAMA_NUM_PARALLEL` changes.
|
|
141
|
+
|
|
142
|
+

|
|
143
|
+
|
|
144
|
+
| Slots | Capacity (concurrent requests) | Completed req/s at capacity | Output tok/s | TTFT p50 / p99 | TPOT p50 | E2E p50 |
|
|
145
|
+
|---|---|---|---|---|---|---|
|
|
146
|
+
| 1 | 1 | 1.51 | 190 | 45 / 59 ms | 4.9 ms | 0.67 s |
|
|
147
|
+
| 4 | 4 | 3.16 | 385 | 101 / 197 ms | 9.4 ms | 1.30 s |
|
|
148
|
+
| 8 | 8 | 3.30 | 400 | 224 / 386 ms | 16 ms | 2.22 s |
|
|
149
|
+
|
|
150
|
+
- **Capacity follows the slot count exactly.** With one slot, a second request waits for the
|
|
151
|
+
whole first answer (TTFT 688 ms at c=2), so only one user fits the SLO.
|
|
152
|
+
- **The GPU saturates at about 400 tok/s with 4 streams.** Going from 4 to 8 slots doubles
|
|
153
|
+
the users who fit the SLO, but finishes only 4% more requests per second; each answer
|
|
154
|
+
takes 71% longer (TPOT 9.4 → 16 ms) because the same GPU time is split more ways.
|
|
155
|
+
- So for this model and GPU, 4 slots maximise work done; 8 slots only help if you need more
|
|
156
|
+
simultaneous users within the TTFT target and can accept slower answers.
|
|
157
|
+
|
|
158
|
+
Full report with every level: [results/ollama-parallel.md](results/ollama-parallel.md).
|
|
159
|
+
Raw data: `results/ollama-np{1,4,8}.json` (every request's timings). Next: llama.cpp server
|
|
160
|
+
on the same GPU, and vLLM on a free Kaggle T4.
|
|
161
|
+
|
|
162
|
+
## Metrics
|
|
163
|
+
|
|
164
|
+
| Metric | Meaning |
|
|
165
|
+
|---|---|
|
|
166
|
+
| TTFT | Request sent → first streamed chunk with non-empty content (role-only and empty chunks don't count) |
|
|
167
|
+
| TPOT | (E2E − TTFT) / (output tokens − 1) |
|
|
168
|
+
| ITL | Gaps between consecutive content chunks. Some servers put several tokens in one chunk; the report shows `tok/chunk` so you can tell |
|
|
169
|
+
| E2E | Request sent → end of stream |
|
|
170
|
+
| req/s, out tok/s | Completed requests and output tokens per second of wall time for the level |
|
|
171
|
+
| Goodput | Requests/s that meet the SLOs (default TTFT < 500 ms and TPOT < 50 ms; change with `--slo-ttft-ms`/`--slo-tpot-ms`, also at report time) |
|
|
172
|
+
| Errors | Failed requests grouped by kind (`HTTP 500`, `timeout`, `ConnectError`, ...) |
|
|
173
|
+
|
|
174
|
+
Token counts come from the server's `usage` (requested with `stream_options.include_usage`).
|
|
175
|
+
If the server sends none, servebench counts with a Hugging Face tokenizer if you pass
|
|
176
|
+
`--tokenizer Qwen/Qwen2.5-0.5B-Instruct` (install the `hf` extra: `servebench[hf]`), or falls back to
|
|
177
|
+
counting chunks. Every request records which source was used, and the report warns about
|
|
178
|
+
chunk counts.
|
|
179
|
+
|
|
180
|
+
## Load modes
|
|
181
|
+
|
|
182
|
+
- **Concurrency sweep** (`--concurrency 1,2,4,8`, closed loop): exactly N requests in flight
|
|
183
|
+
until the level's requests are done. Shows how latency degrades as load grows.
|
|
184
|
+
- **Request rate** (`--rate 0.5,1,2,inf`, open loop): Poisson arrivals at R req/s, whether or
|
|
185
|
+
not earlier requests finished; `inf` sends everything at once, `--max-concurrency` caps it.
|
|
186
|
+
This is how you find the point where a server falls over.
|
|
187
|
+
|
|
188
|
+
Each level gets `--num-requests` requests (default `max(16, 4N)` for concurrency, 64 for
|
|
189
|
+
rate). Two warm-up requests run first and are not measured. Each run uses a new random
|
|
190
|
+
prompt seed (printed and saved in run.json): with a fixed seed, a second run sends the same
|
|
191
|
+
prompts and a server with prompt caching skips prefill (on Ollama TTFT dropped from 43 ms to
|
|
192
|
+
20 ms), so you would measure the cache. Pass `--seed N` to repeat a run exactly.
|
|
193
|
+
|
|
194
|
+
## Find capacity
|
|
195
|
+
|
|
196
|
+
`find-capacity` answers "how much load can this server take within my latency targets?"
|
|
197
|
+
with one number:
|
|
198
|
+
|
|
199
|
+
Real run: Ollama with `OLLAMA_NUM_PARALLEL=4` on the RTX 3050 from the results above.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
servebench find-capacity --url http://127.0.0.1:11434/v1 --model qwen2.5:0.5b \
|
|
203
|
+
--max 64 --num-requests 64 --out results/ollama-np4.json
|
|
204
|
+
```
|
|
205
|
+
```
|
|
206
|
+
c=1 64 ok, 0 failed, 188.5 tok/s, TTFT p50 45 ms, 100% in SLO -> pass
|
|
207
|
+
c=2 64 ok, 0 failed, 274.0 tok/s, TTFT p50 71 ms, 100% in SLO -> pass
|
|
208
|
+
c=4 64 ok, 0 failed, 384.6 tok/s, TTFT p50 101 ms, 100% in SLO -> pass
|
|
209
|
+
c=8 64 ok, 0 failed, 375.7 tok/s, TTFT p50 1424 ms, 6% in SLO -> FAIL
|
|
210
|
+
c=6 64 ok, 0 failed, 405.2 tok/s, TTFT p50 566 ms, 47% in SLO -> FAIL
|
|
211
|
+
c=5 64 ok, 0 failed, 369.8 tok/s, TTFT p50 367 ms, 72% in SLO -> FAIL
|
|
212
|
+
...
|
|
213
|
+
Capacity: 4 concurrent requests. At 4, 100% met the SLO; at 5, only 72%.
|
|
214
|
+
SLO: TTFT < 500 ms and TPOT < 50 ms for 90% of requests.
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
- A level **passes** when no request failed and at least `--target` (default 90%) of the
|
|
218
|
+
requests met `--slo-ttft-ms` and `--slo-tpot-ms`.
|
|
219
|
+
- The search doubles the load from `--start` (1, 2, 4, 8, ...) until a level fails, then
|
|
220
|
+
binary-searches between the last pass and the first fail until the gap is within
|
|
221
|
+
`--tolerance` (10%) of the passing value. It never goes above `--max` (256).
|
|
222
|
+
- `--mode rate` searches Poisson arrival rates (req/s) instead of concurrency.
|
|
223
|
+
- Every level it tried is saved, so `servebench report` charts it like any sweep.
|
|
224
|
+
- Each level is measured once and the search assumes latency grows with load. Near the
|
|
225
|
+
limit a noisy server can pass a level above one that failed; rerun to confirm.
|
|
226
|
+
|
|
227
|
+
## Prompts
|
|
228
|
+
|
|
229
|
+
- `--prompts synthetic` (default): `--input-len` tokens of random common words, a different
|
|
230
|
+
prompt per request so prefix caches cannot reuse one prompt's work for the next.
|
|
231
|
+
`--ignore-eos` (on by default) asks the server to always generate `--output-len` tokens;
|
|
232
|
+
vLLM, SGLang and llama.cpp honour it, and servers that don't know it ignore it, so check
|
|
233
|
+
`out tok/s` against `--output-len` when comparing.
|
|
234
|
+
- `--prompts dolly`: real prompts from
|
|
235
|
+
[databricks-dolly-15k](https://huggingface.co/datasets/databricks/databricks-dolly-15k)
|
|
236
|
+
(CC BY-SA 3.0, © Databricks), downloaded once (12.8 MB) into `~/.cache/servebench` (set
|
|
237
|
+
`SERVEBENCH_CACHE` to move it). Not bundled with this repo. `--input-len` caps the prompt
|
|
238
|
+
length in words.
|
|
239
|
+
- `--prompts my_prompts.jsonl` (`{"prompt": ...}` per line) or a `.txt` file, one prompt per
|
|
240
|
+
line.
|
|
241
|
+
|
|
242
|
+
## How it works
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
prompts.py ──► load.py ─────────────► client.py ──HTTP/SSE──► server
|
|
246
|
+
synthetic concurrency workers one streaming request,
|
|
247
|
+
dolly or Poisson arrivals perf_counter per chunk
|
|
248
|
+
file + event-loop lag check
|
|
249
|
+
│
|
|
250
|
+
▼
|
|
251
|
+
metrics.py ──► run.json ──► report.py
|
|
252
|
+
percentiles, terminal table,
|
|
253
|
+
goodput PNG charts, MD / HTML
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
- One `asyncio` process with `aiohttp` (the HTTP client vLLM's and SGLang's own serving
|
|
257
|
+
benchmarks use). Timings use `time.perf_counter()`. servebench started on `httpx`; profiling
|
|
258
|
+
showed its connection pool re-checks every connection on every request, so at 128
|
|
259
|
+
concurrent requests the *client* capped out at ~45 req/s and inflated TTFT from 100 ms to
|
|
260
|
+
1.6 s. With aiohttp the same test reaches ~400 req/s with TTFT 135 ms.
|
|
261
|
+
- Every request's chunk arrival times are saved in `run.json`, so reports can be recomputed
|
|
262
|
+
(for example with different SLOs) without rerunning anything.
|
|
263
|
+
- The client checks itself: a background task measures how long a ready callback waits for
|
|
264
|
+
the event loop. If the p99 is above 10 ms, the report warns that the client, not the server,
|
|
265
|
+
may be the bottleneck.
|
|
266
|
+
## How it's tested
|
|
267
|
+
|
|
268
|
+
You can't check a benchmark's numbers against a real model, because nobody knows the true
|
|
269
|
+
answer in advance. So the tests use `servebench mock-server`: a FastAPI server that speaks the
|
|
270
|
+
same OpenAI streaming API but, instead of running a model, sends placeholder tokens with
|
|
271
|
+
timings we set exactly (TTFT, time per token, tokens per chunk, slowdown under load). The
|
|
272
|
+
tests then check servebench measures what was set:
|
|
273
|
+
|
|
274
|
+
- TTFT and TPOT within a few ms of the set delays, on Linux and Windows (CI on both)
|
|
275
|
+
- role-only and empty chunks don't count as the first token; several tokens per chunk
|
|
276
|
+
- N concurrent requests really means N in flight (the mock counts them); Poisson arrival rates
|
|
277
|
+
- connections are reused (the mock counts TCP connections: 4 requests, 1 connection)
|
|
278
|
+
- `find-capacity` finds the known limit: with TTFT = 80 ms × requests in flight and a 360 ms
|
|
279
|
+
target, capacity must be exactly 4
|
|
280
|
+
|
|
281
|
+
Example: a mock set to TTFT 80 ms, 15 ms per token and 15% slowdown per extra request in
|
|
282
|
+
flight should show TPOT 15 × (1 + 0.15 × 15) ≈ 49 ms at 16 concurrent requests, and
|
|
283
|
+
servebench measures 49 ms ([examples/report.md](examples/report.md)). The mock is a test
|
|
284
|
+
tool only; every number under [Results](#results) comes from a real server.
|
|
285
|
+
|
|
286
|
+
## Limitations
|
|
287
|
+
|
|
288
|
+
- One client process. Measured on a laptop (Ryzen 5 6600H, Windows) against the mock
|
|
289
|
+
server: accurate to ~128 concurrent requests / ~400 req/s / ~12k streamed tokens/s (client
|
|
290
|
+
loop lag p99 under 10 ms); at 256 concurrent it starts to work hard (lag ~20 ms). That
|
|
291
|
+
covers what a single GPU serves; it is not a multi-machine load generator. The lag check
|
|
292
|
+
warns when the client is struggling.
|
|
293
|
+
- The first requests of each level include opening TCP connections, as a real client would.
|
|
294
|
+
- TPOT and ITL are only as good as the token counts: without `usage` or `--tokenizer`, a
|
|
295
|
+
server that sends several tokens per chunk will look slower per token than it is.
|
|
296
|
+
- Measures what the client sees over HTTP, including network and serialization, not the
|
|
297
|
+
engine's internal timings.
|
|
298
|
+
- Text endpoints only (chat and completions); no images, tool calls or embeddings.
|
|
299
|
+
|
|
300
|
+
## Related tools
|
|
301
|
+
|
|
302
|
+
servebench does not try to replace the bigger tools; use them if they fit:
|
|
303
|
+
[vLLM's `vllm bench serve`](https://docs.vllm.ai/),
|
|
304
|
+
[NVIDIA GenAI-Perf](https://github.com/triton-inference-server/perf_analyzer),
|
|
305
|
+
[guidellm](https://github.com/vllm-project/guidellm),
|
|
306
|
+
[LLMPerf](https://github.com/ray-project/llmperf).
|
|
307
|
+
servebench is a small install that works against any engine, makes clean multi-run
|
|
308
|
+
comparisons, and is used to measure the rest of this series of projects on free hardware.
|
|
309
|
+
|
|
310
|
+
## Development
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
uv sync # creates .venv and installs the package + dev tools
|
|
314
|
+
uv run ruff check src tests
|
|
315
|
+
uv run pytest -q
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
No need to create or activate a virtualenv yourself; `uv run` uses the project's `.venv`.
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
Apache 2.0. The dolly-15k dataset, if you use it, is CC BY-SA 3.0 and is downloaded from
|
|
323
|
+
Hugging Face at runtime, not distributed here.
|