agentic-bench 0.2.0__py3-none-any.whl
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.
- abench/__init__.py +107 -0
- abench/__main__.py +7 -0
- abench/_version.py +4 -0
- abench/cli.py +389 -0
- abench/core/__init__.py +72 -0
- abench/core/dataset.py +421 -0
- abench/core/metrics.py +505 -0
- abench/core/workload.py +614 -0
- abench/data/recording_rates.json +23 -0
- abench/replay/__init__.py +14 -0
- abench/replay/engine.py +553 -0
- abench/replay/transport.py +157 -0
- abench/runner.py +637 -0
- abench/serve/__init__.py +31 -0
- abench/serve/endpoints.py +388 -0
- abench/serve/report.py +365 -0
- abench/serve/telemetry.py +166 -0
- agentic_bench-0.2.0.dist-info/METADATA +169 -0
- agentic_bench-0.2.0.dist-info/RECORD +24 -0
- agentic_bench-0.2.0.dist-info/WHEEL +5 -0
- agentic_bench-0.2.0.dist-info/entry_points.txt +2 -0
- agentic_bench-0.2.0.dist-info/licenses/LICENSE +202 -0
- agentic_bench-0.2.0.dist-info/licenses/NOTICE +44 -0
- agentic_bench-0.2.0.dist-info/top_level.txt +1 -0
abench/__init__.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026, Alibaba Group and its affiliates
|
|
3
|
+
|
|
4
|
+
"""Abench — agentic inference benchmark.
|
|
5
|
+
|
|
6
|
+
Closed-loop replay of recorded agent trajectories (DeepSWE v1.1, and an
|
|
7
|
+
LMCache-derived Otel tier) against any OpenAI-compatible inference
|
|
8
|
+
server, plus a synthetic random workload for engines and context bands
|
|
9
|
+
the traces do not cover. System-prompt length, session selection and
|
|
10
|
+
the steady-state measurement window are all parameterized.
|
|
11
|
+
|
|
12
|
+
Decode lengths are pinned rather than left to the model: a replayed turn
|
|
13
|
+
generates its recorded ``completion_tokens``, a random turn generates
|
|
14
|
+
``--output-len``. Pinning is what makes two engines comparable on the
|
|
15
|
+
same workload; the report checks the achieved mean output length against
|
|
16
|
+
the recorded one so a server that ignores the pin is visible.
|
|
17
|
+
|
|
18
|
+
Datasets are distributed via the ABench ModelScope mirror
|
|
19
|
+
(Apsara-Stack/ABench).
|
|
20
|
+
|
|
21
|
+
Layering: ``cli`` (arguments) -> ``runner`` (orchestration) ->
|
|
22
|
+
``replay`` (closed-loop engine) -> ``serve`` (server discovery,
|
|
23
|
+
telemetry, reporting) -> ``core`` (data model, planning, metrics).
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from abench.core.dataset import (
|
|
27
|
+
TIERS,
|
|
28
|
+
NormSession,
|
|
29
|
+
NormTurn,
|
|
30
|
+
download_tier,
|
|
31
|
+
iter_jsonl_sessions,
|
|
32
|
+
load_sessions,
|
|
33
|
+
resolve_url,
|
|
34
|
+
write_tier_manifest,
|
|
35
|
+
)
|
|
36
|
+
from ._version import __version__
|
|
37
|
+
from abench.core.metrics import (
|
|
38
|
+
AgenticMetrics,
|
|
39
|
+
BucketStats,
|
|
40
|
+
SessionRecord,
|
|
41
|
+
TurnRecord,
|
|
42
|
+
compute_metrics,
|
|
43
|
+
summarize_server_metrics,
|
|
44
|
+
)
|
|
45
|
+
from abench.core.metrics import ServerMetricsSnapshot
|
|
46
|
+
from abench.replay.engine import run_closed_loop, warmup
|
|
47
|
+
from abench.serve.endpoints import (
|
|
48
|
+
fetch_server_info,
|
|
49
|
+
flush_cache,
|
|
50
|
+
get_auth_headers,
|
|
51
|
+
server_root,
|
|
52
|
+
wait_for_server_ready,
|
|
53
|
+
)
|
|
54
|
+
from abench.serve.report import metrics_to_dict, print_report, save_results
|
|
55
|
+
from abench.core.workload import (
|
|
56
|
+
PROFILES,
|
|
57
|
+
PlannedSession,
|
|
58
|
+
PlannedTurn,
|
|
59
|
+
Profile,
|
|
60
|
+
build_system_prompt,
|
|
61
|
+
make_gap_policy,
|
|
62
|
+
make_output_policy,
|
|
63
|
+
measure_text_tokens,
|
|
64
|
+
plan_workload,
|
|
65
|
+
select_sessions,
|
|
66
|
+
synthesize_sessions,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
"__version__",
|
|
71
|
+
"TIERS",
|
|
72
|
+
"NormSession",
|
|
73
|
+
"NormTurn",
|
|
74
|
+
"download_tier",
|
|
75
|
+
"iter_jsonl_sessions",
|
|
76
|
+
"load_sessions",
|
|
77
|
+
"resolve_url",
|
|
78
|
+
"write_tier_manifest",
|
|
79
|
+
"AgenticMetrics",
|
|
80
|
+
"BucketStats",
|
|
81
|
+
"SessionRecord",
|
|
82
|
+
"TurnRecord",
|
|
83
|
+
"compute_metrics",
|
|
84
|
+
"summarize_server_metrics",
|
|
85
|
+
"ServerMetricsSnapshot",
|
|
86
|
+
"fetch_server_info",
|
|
87
|
+
"flush_cache",
|
|
88
|
+
"get_auth_headers",
|
|
89
|
+
"run_closed_loop",
|
|
90
|
+
"server_root",
|
|
91
|
+
"wait_for_server_ready",
|
|
92
|
+
"warmup",
|
|
93
|
+
"metrics_to_dict",
|
|
94
|
+
"print_report",
|
|
95
|
+
"save_results",
|
|
96
|
+
"PROFILES",
|
|
97
|
+
"PlannedSession",
|
|
98
|
+
"PlannedTurn",
|
|
99
|
+
"Profile",
|
|
100
|
+
"build_system_prompt",
|
|
101
|
+
"measure_text_tokens",
|
|
102
|
+
"make_output_policy",
|
|
103
|
+
"make_gap_policy",
|
|
104
|
+
"plan_workload",
|
|
105
|
+
"select_sessions",
|
|
106
|
+
"synthesize_sessions",
|
|
107
|
+
]
|
abench/__main__.py
ADDED
abench/_version.py
ADDED
abench/cli.py
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026, Alibaba Group and its affiliates
|
|
3
|
+
|
|
4
|
+
"""Command-line interface: argument parsing and dispatch.
|
|
5
|
+
|
|
6
|
+
Usage examples
|
|
7
|
+
--------------
|
|
8
|
+
|
|
9
|
+
Prepare the default dataset once (deepswe/mixed, ~42 MB; lands under
|
|
10
|
+
``~/abench_data/<name>/<tier>/``)::
|
|
11
|
+
|
|
12
|
+
python3 -m abench data deepswe/mixed
|
|
13
|
+
|
|
14
|
+
Run a single fixed-concurrency point against a served model::
|
|
15
|
+
|
|
16
|
+
python3 -m abench run --model default \
|
|
17
|
+
--dataset-name deepswe/mixed --max-concurrency 16
|
|
18
|
+
# auto-detects localhost :30000 (sglang) / :8000 (vllm); pass
|
|
19
|
+
# --port or --base-url only for non-default deployments
|
|
20
|
+
|
|
21
|
+
Smoke test with no download (locally generated random workload)::
|
|
22
|
+
|
|
23
|
+
python3 -m abench run --dataset-name random \
|
|
24
|
+
--turns 3 --output-len 64 --max-concurrency 4
|
|
25
|
+
|
|
26
|
+
What each command *does* lives in ``abench.runner``; this module only
|
|
27
|
+
builds the parser (layered help: ``--help`` core flags, ``--help-advanced``
|
|
28
|
+
everything) and dispatches.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import argparse
|
|
34
|
+
import os
|
|
35
|
+
import sys
|
|
36
|
+
|
|
37
|
+
from abench.runner import cmd_data_entry, cmd_run
|
|
38
|
+
|
|
39
|
+
# ----------------------------------------------------------------------
|
|
40
|
+
# argument parsing
|
|
41
|
+
# ----------------------------------------------------------------------
|
|
42
|
+
# Options hidden from the default --help; shown via `<cmd> --help-advanced`.
|
|
43
|
+
# Layering only -- every hidden flag still works exactly as before.
|
|
44
|
+
_ADVANCED_DESTS = {
|
|
45
|
+
"run": {
|
|
46
|
+
"turns", "input_increment", "output_len", "gap_policy", "gap_cap_ms",
|
|
47
|
+
"min_turns", "max_turns", "min_context", "max_context",
|
|
48
|
+
"system_prompt_len", "top_k", "presence_penalty",
|
|
49
|
+
"steady_frac", "warmup_requests",
|
|
50
|
+
"duration", "session_timeout", "routing_key", "worker_header",
|
|
51
|
+
"ready_check_timeout_sec", "max_duration",
|
|
52
|
+
"disable_tqdm", "host", "port",
|
|
53
|
+
},
|
|
54
|
+
"data": set(),
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# (cmd, dest) -> original help text, used by --help-advanced to un-hide.
|
|
59
|
+
_ADVANCED_ORIG: dict = {}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class _HelpAdvancedAction(argparse.Action):
|
|
63
|
+
"""Print the full help including advanced (hidden) options."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, option_strings, dest, cmd_name=None, **kwargs):
|
|
66
|
+
kwargs["nargs"] = 0
|
|
67
|
+
super().__init__(option_strings, dest, **kwargs)
|
|
68
|
+
self._cmd_name = cmd_name
|
|
69
|
+
|
|
70
|
+
def __call__(self, parser, namespace, values, option_string=None):
|
|
71
|
+
restored = []
|
|
72
|
+
for act in parser._actions:
|
|
73
|
+
key = (self._cmd_name, act.dest)
|
|
74
|
+
if key in _ADVANCED_ORIG:
|
|
75
|
+
# the stored help may itself be None (flag had no help text)
|
|
76
|
+
act.help = _ADVANCED_ORIG[key]
|
|
77
|
+
restored.append(act)
|
|
78
|
+
print(parser.format_help())
|
|
79
|
+
for act in restored:
|
|
80
|
+
act.help = argparse.SUPPRESS
|
|
81
|
+
parser.exit()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _layer_help(subparsers) -> None:
|
|
85
|
+
"""Suppress advanced options' help text on every subcommand."""
|
|
86
|
+
for name, sp in subparsers.choices.items():
|
|
87
|
+
for act in sp._actions:
|
|
88
|
+
if act.dest in _ADVANCED_DESTS.get(name, set()):
|
|
89
|
+
_ADVANCED_ORIG[(name, act.dest)] = act.help
|
|
90
|
+
act.help = argparse.SUPPRESS
|
|
91
|
+
sp.add_argument(
|
|
92
|
+
"--help-advanced",
|
|
93
|
+
action=_HelpAdvancedAction,
|
|
94
|
+
cmd_name=name,
|
|
95
|
+
help="Show ALL options, including advanced/internal ones.",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _Parser(argparse.ArgumentParser):
|
|
100
|
+
"""No prefix matching: `--mode` must not silently resolve to `--model`."""
|
|
101
|
+
|
|
102
|
+
def __init__(self, *a, **kw):
|
|
103
|
+
kw.setdefault("allow_abbrev", False)
|
|
104
|
+
super().__init__(*a, **kw)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
108
|
+
from abench._version import __version__
|
|
109
|
+
|
|
110
|
+
# `python3 -m abench` leaves __main__.py in argv[0]; without an explicit
|
|
111
|
+
# prog every usage line reads "usage: __main__.py run [-h]", which is not
|
|
112
|
+
# something a reader can copy back into a shell.
|
|
113
|
+
prog = (
|
|
114
|
+
"python3 -m abench"
|
|
115
|
+
if os.path.basename(sys.argv[0]) == "__main__.py"
|
|
116
|
+
else None
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
parser = _Parser(
|
|
120
|
+
prog=prog,
|
|
121
|
+
description="ABench — closed-loop agentic inference benchmark "
|
|
122
|
+
"(replays recorded agent trajectories, or a synthetic "
|
|
123
|
+
"workload, against any OpenAI-compatible server)",
|
|
124
|
+
epilog="Most users only need a few options per command; run "
|
|
125
|
+
"`python3 -m abench <command> --help-advanced` to see everything.",
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument("--version", action="version",
|
|
128
|
+
version=f"abench {__version__}")
|
|
129
|
+
sub = parser.add_subparsers(
|
|
130
|
+
dest="command", required=True, parser_class=_Parser
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# data
|
|
134
|
+
d = sub.add_parser(
|
|
135
|
+
"data",
|
|
136
|
+
help="Data preparation: download a preset dataset tier to the "
|
|
137
|
+
"standard location <data-path>/<dataset>/<tier>/ "
|
|
138
|
+
"(usage: data <DATASET>[/<TIER>], e.g. data deepswe/mixed).",
|
|
139
|
+
)
|
|
140
|
+
d.add_argument("dataset", nargs="?", default=None,
|
|
141
|
+
help="DATASET[/TIER] to download, e.g. deepswe/mixed. "
|
|
142
|
+
"A bare dataset name is rejected -- tiers are peers, no default. "
|
|
143
|
+
"See --list for the available combinations.")
|
|
144
|
+
d.add_argument("--data-path", default=os.path.expanduser("~/abench_data"),
|
|
145
|
+
help="Download root; data lands under `<data-path>/<dataset>/<tier>/` "
|
|
146
|
+
"-- the same layout `run --dataset-path` understands. "
|
|
147
|
+
"Non-default roots: pass the printed --dataset-path back to run.")
|
|
148
|
+
d.add_argument("--list", action="store_true",
|
|
149
|
+
help="List the available dataset x tier combinations.")
|
|
150
|
+
d.set_defaults(func=cmd_data_entry)
|
|
151
|
+
|
|
152
|
+
# run
|
|
153
|
+
r = sub.add_parser("run", help="Run the closed-loop replay benchmark.")
|
|
154
|
+
r.add_argument("--base-url", default=None,
|
|
155
|
+
help="Full server base URL, e.g. http://localhost:30000. When set it "
|
|
156
|
+
"overrides --host/--port entirely.")
|
|
157
|
+
r.add_argument("--host", default="0.0.0.0",
|
|
158
|
+
help="Server host when --base-url is not given.")
|
|
159
|
+
r.add_argument("--port", type=int, default=None,
|
|
160
|
+
help="Server port. Unset: derived from --backend (sglang/openai "
|
|
161
|
+
"30000, vllm 8000; auto probes 30000 then 8000). Pass only "
|
|
162
|
+
"for non-default deployments.")
|
|
163
|
+
r.add_argument("--model", default="default",
|
|
164
|
+
help="Model name in the request body; a local directory is also "
|
|
165
|
+
"auto-used as the tokenizer.")
|
|
166
|
+
r.add_argument("--tokenizer", default=None,
|
|
167
|
+
help="Explicit tokenizer path; auto-resolved when unset.")
|
|
168
|
+
r.add_argument("--backend", choices=["auto", "sglang", "vllm", "openai"],
|
|
169
|
+
default="auto",
|
|
170
|
+
help="Serving backend. Recommended to leave unset: 'auto' probes "
|
|
171
|
+
"the server (/server_info, then /metrics content) and gates "
|
|
172
|
+
"the observation layer accordingly. A wrong explicit "
|
|
173
|
+
"declaration does not error out, it silently drops that "
|
|
174
|
+
"backend's observation metrics (cache breakdown / KV pool / "
|
|
175
|
+
"server_info snapshot).")
|
|
176
|
+
r.add_argument("--dataset-name", dest="dataset_name", default="deepswe/mixed",
|
|
177
|
+
help="Dataset identity: '<name>/<tier>' (e.g. deepswe/mixed, the "
|
|
178
|
+
"default) looked up under ~/abench_data, or 'random' for a "
|
|
179
|
+
"locally generated workload. With --dataset-path it labels the "
|
|
180
|
+
"run (the path supplies the location).")
|
|
181
|
+
r.add_argument("--dataset-path", default=None,
|
|
182
|
+
help="Data location: a .jsonl/.jsonl.zst file, a tier directory "
|
|
183
|
+
"(holds manifest.json), or a root containing <name>/<tier>/. A "
|
|
184
|
+
"manifest found at the target sets band/turn/system-prompt "
|
|
185
|
+
"defaults like --dataset-name; without one, raw mode replays the "
|
|
186
|
+
"files as-is (no band, min_turns 1, system_prompt_len 0).")
|
|
187
|
+
|
|
188
|
+
# --- random workload knobs (only with --dataset-name random) ---
|
|
189
|
+
r.add_argument("--turns", type=int, default=20,
|
|
190
|
+
help="Turns per random session.")
|
|
191
|
+
r.add_argument("--input-increment", type=int, default=2048,
|
|
192
|
+
help="New context tokens added per turn (random mode).")
|
|
193
|
+
r.add_argument("--output-len", type=int, default=256,
|
|
194
|
+
help="Output tokens per turn (random mode).")
|
|
195
|
+
r.add_argument("--gap-policy", default="exponential:3000",
|
|
196
|
+
help="Inter-turn gap distribution: zero | fixed:Ms | uniform:lo,hi | exponential:mean.")
|
|
197
|
+
r.add_argument("--gap-cap-ms", type=float, default=30_000.0,
|
|
198
|
+
help="Cap on any single inter-turn gap (ms).")
|
|
199
|
+
|
|
200
|
+
r.add_argument("--min-turns", type=int, default=None,
|
|
201
|
+
help="Drop sessions with fewer turns than this (dataset manifest "
|
|
202
|
+
"default applies when unset).")
|
|
203
|
+
r.add_argument("--max-turns", type=int, default=None,
|
|
204
|
+
help="Cap a session at this many turns; never pads short ones.")
|
|
205
|
+
r.add_argument(
|
|
206
|
+
"--min-context",
|
|
207
|
+
type=int,
|
|
208
|
+
default=None,
|
|
209
|
+
help="Keep sessions whose replayed peak context (recorded peak + "
|
|
210
|
+
"injected system prompt) is >= this many tokens. Same axis as "
|
|
211
|
+
"--max-context.",
|
|
212
|
+
)
|
|
213
|
+
r.add_argument(
|
|
214
|
+
"--max-context",
|
|
215
|
+
type=int,
|
|
216
|
+
default=None,
|
|
217
|
+
help="The MODEL context window the workload must fit: internally "
|
|
218
|
+
"the recorded-peak cap becomes max_context minus the measured "
|
|
219
|
+
"injected system prompt (the derivation is printed on the "
|
|
220
|
+
"[plan] line).",
|
|
221
|
+
)
|
|
222
|
+
r.add_argument("--temperature", type=float, default=0.0,
|
|
223
|
+
help="Sampling temperature (greedy by default, as in bench_serving).")
|
|
224
|
+
r.add_argument("--top-p", type=float, default=1.0,
|
|
225
|
+
help="Top-p; only sent when < 1.0.")
|
|
226
|
+
r.add_argument(
|
|
227
|
+
"--chat-template-kwargs",
|
|
228
|
+
default=None,
|
|
229
|
+
help='JSON forwarded as chat_template_kwargs, e.g. \'{"thinking": true}\' '
|
|
230
|
+
"for reasoning models. Left unset the server template default applies.",
|
|
231
|
+
)
|
|
232
|
+
r.add_argument("--top-k", type=int, default=None,
|
|
233
|
+
help="Top-k sampling; only sent when set.")
|
|
234
|
+
r.add_argument("--presence-penalty", type=float, default=None,
|
|
235
|
+
help="Presence penalty; only sent when set.")
|
|
236
|
+
r.add_argument(
|
|
237
|
+
"--header",
|
|
238
|
+
action="append",
|
|
239
|
+
default=None,
|
|
240
|
+
metavar="KEY=VALUE",
|
|
241
|
+
help="Extra HTTP header on every request, repeatable "
|
|
242
|
+
"(e.g. --header X-Tenant=abc). Merged after the auth header, "
|
|
243
|
+
"so it can override it.",
|
|
244
|
+
)
|
|
245
|
+
r.add_argument(
|
|
246
|
+
"--extra-request-body",
|
|
247
|
+
default=None,
|
|
248
|
+
help="JSON object merged into the top level of every request body, "
|
|
249
|
+
"overriding the built-in fields (e.g. '{\"seed\": 123}'). The "
|
|
250
|
+
"escape hatch for engine-specific request parameters.",
|
|
251
|
+
)
|
|
252
|
+
r.add_argument("--system-prompt-len", type=int, default=None,
|
|
253
|
+
help="Target token length of the shared synthetic system prompt "
|
|
254
|
+
"(0 = none; dataset manifest default applies when unset). "
|
|
255
|
+
"The actual injected length is measured and shown in the "
|
|
256
|
+
"[plan] line, and added to the context band accounting.")
|
|
257
|
+
r.add_argument("--num-sessions", type=int, default=None,
|
|
258
|
+
help="Session pool size; defaults to 2 x --max-concurrency (the "
|
|
259
|
+
"validated default sizing). Pass a larger count (or the "
|
|
260
|
+
"tier size) for a full-tier pass.")
|
|
261
|
+
r.add_argument("--seed", type=int, default=42,
|
|
262
|
+
help="Sampling seed for session selection and random mode.")
|
|
263
|
+
r.add_argument("--tag", default=None,
|
|
264
|
+
help="Free-form label written into the result record; pure "
|
|
265
|
+
"annotation, affects nothing (as in bench_serving).")
|
|
266
|
+
|
|
267
|
+
r.add_argument("--max-concurrency", type=int, default=16,
|
|
268
|
+
help="Simultaneously active agent sessions (closed loop: a finished "
|
|
269
|
+
"session's slot is immediately refilled).")
|
|
270
|
+
r.add_argument(
|
|
271
|
+
"--duration",
|
|
272
|
+
type=float,
|
|
273
|
+
default=None,
|
|
274
|
+
help="Seconds; enables looping pool + drain-free window.",
|
|
275
|
+
)
|
|
276
|
+
r.add_argument("--steady-frac", type=float, default=0.9,
|
|
277
|
+
help="Steady window = turns dispatched at >= frac*C active "
|
|
278
|
+
"sessions; excludes ramp/drain from metrics.")
|
|
279
|
+
r.add_argument("--no-gaps", action="store_true", help="Ignore tool-wait gaps.")
|
|
280
|
+
r.add_argument("--disable-tqdm", action="store_true",
|
|
281
|
+
help="Disable the tqdm turn-progress bar (falls back to the "
|
|
282
|
+
"periodic [progress] heartbeat; tqdm also absent from the "
|
|
283
|
+
"environment does the same).")
|
|
284
|
+
r.add_argument("--gap-scale", type=float, default=1.0,
|
|
285
|
+
help="Scale factor applied to each tool-wait gap (pre_gap_ms) when "
|
|
286
|
+
"gaps are respected: 0.5 halves every wait, 1.0 reproduces the "
|
|
287
|
+
"recorded timing (default), 2.0 doubles it. Ignored with "
|
|
288
|
+
"--no-gaps.")
|
|
289
|
+
r.add_argument("--disable-stream", action="store_true",
|
|
290
|
+
help="Non-streaming requests (no per-token ITL, whole-response "
|
|
291
|
+
"E2E only).")
|
|
292
|
+
r.add_argument("--flush-cache", action="store_true",
|
|
293
|
+
help="POST /flush_cache (sglang) or /reset_prefix_cache (vllm; "
|
|
294
|
+
"needs the server launched with VLLM_SERVER_DEV_MODE=1) "
|
|
295
|
+
"before the run.")
|
|
296
|
+
r.add_argument(
|
|
297
|
+
"--warmup-requests",
|
|
298
|
+
type=int,
|
|
299
|
+
default=1,
|
|
300
|
+
help="Sessions replayed once before measurement to populate the "
|
|
301
|
+
"prefix cache and surface config errors early (default 1). "
|
|
302
|
+
"Each warmup session is capped to its first 3 turns and the "
|
|
303
|
+
"phase shares the --max-duration fuse.",
|
|
304
|
+
)
|
|
305
|
+
r.add_argument(
|
|
306
|
+
"--ready-check-timeout-sec",
|
|
307
|
+
type=int,
|
|
308
|
+
default=60,
|
|
309
|
+
help="Seconds to wait for GET /v1/models to answer before the run "
|
|
310
|
+
"starts (0 = skip). Fails fast instead of timing out every "
|
|
311
|
+
"request against a still-loading server.",
|
|
312
|
+
)
|
|
313
|
+
r.add_argument(
|
|
314
|
+
"--max-duration",
|
|
315
|
+
type=float,
|
|
316
|
+
default=None,
|
|
317
|
+
help="Runaway fuse: hard cap (s) on total run wall clock. Default "
|
|
318
|
+
"no cap; a healthy run should never reach it.",
|
|
319
|
+
)
|
|
320
|
+
r.add_argument(
|
|
321
|
+
"--routing-key",
|
|
322
|
+
action="store_true",
|
|
323
|
+
help="Attach a stable X-SMG-Routing-Key per session.",
|
|
324
|
+
)
|
|
325
|
+
r.add_argument(
|
|
326
|
+
"--worker-header",
|
|
327
|
+
default=None,
|
|
328
|
+
help=(
|
|
329
|
+
"Response header carrying the serving worker's identity, used "
|
|
330
|
+
"for the routing-stickiness metric (e.g. X-Worker-Id). Off by "
|
|
331
|
+
"default: standard gateways do not return one."
|
|
332
|
+
),
|
|
333
|
+
)
|
|
334
|
+
r.add_argument(
|
|
335
|
+
"--turn-timeout",
|
|
336
|
+
type=float,
|
|
337
|
+
default=3600.0,
|
|
338
|
+
help="Per-request timeout in seconds; a hung turn frees its slot "
|
|
339
|
+
"after this long (MLPerf's qwen config uses 14400).",
|
|
340
|
+
)
|
|
341
|
+
r.add_argument(
|
|
342
|
+
"--progress-interval",
|
|
343
|
+
type=float,
|
|
344
|
+
default=30.0,
|
|
345
|
+
help="Seconds between [progress] heartbeat lines (0 disables them).",
|
|
346
|
+
)
|
|
347
|
+
r.add_argument(
|
|
348
|
+
"--metrics-interval",
|
|
349
|
+
type=float,
|
|
350
|
+
default=5.0,
|
|
351
|
+
help="Seconds between /metrics scrapes for server-side KV pool stats "
|
|
352
|
+
"(0 disables; silently skipped if server lacks --enable-metrics).",
|
|
353
|
+
)
|
|
354
|
+
r.add_argument(
|
|
355
|
+
"--session-timeout",
|
|
356
|
+
type=float,
|
|
357
|
+
default=0.0,
|
|
358
|
+
help="Per-session wall-clock timeout in seconds (0 = disabled). "
|
|
359
|
+
"A session exceeding this is marked truncated.",
|
|
360
|
+
)
|
|
361
|
+
r.add_argument(
|
|
362
|
+
"--output-details",
|
|
363
|
+
action=argparse.BooleanOptionalAction,
|
|
364
|
+
default=True,
|
|
365
|
+
help="Include the per-turn records (TTFTs, ITLs, errors) in the "
|
|
366
|
+
"--output-file JSON. On by default -- a run you cannot "
|
|
367
|
+
"re-examine afterwards is a run you have to repeat; pass "
|
|
368
|
+
"--no-output-details to keep each line to config + metrics "
|
|
369
|
+
"when the full per-turn record is not wanted.",
|
|
370
|
+
)
|
|
371
|
+
r.add_argument("--output-file", default=None,
|
|
372
|
+
help="Append one JSON line per run (JSONL); per-turn records are "
|
|
373
|
+
"included by default (--no-output-details opts out).")
|
|
374
|
+
r.add_argument("--verbose", action="store_true",
|
|
375
|
+
help="Verbose per-turn console output.")
|
|
376
|
+
r.set_defaults(func=cmd_run)
|
|
377
|
+
|
|
378
|
+
_layer_help(sub)
|
|
379
|
+
return parser
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def main() -> None:
|
|
383
|
+
parser = build_parser()
|
|
384
|
+
args = parser.parse_args(sys.argv[1:])
|
|
385
|
+
args.func(args)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
if __name__ == "__main__":
|
|
389
|
+
main()
|
abench/core/__init__.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026, Alibaba Group and its affiliates
|
|
3
|
+
|
|
4
|
+
"""Domain layer: session data, workload planning, result records.
|
|
5
|
+
|
|
6
|
+
The data model and the pure functions over it: no inference servers, no
|
|
7
|
+
telemetry, no command line. One exception — ``dataset`` fetches tier
|
|
8
|
+
files from the distribution mirror over plain ``urllib``, so that a bare
|
|
9
|
+
install can still read downloaded data.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from abench.core.dataset import (
|
|
13
|
+
TIERS,
|
|
14
|
+
NormSession,
|
|
15
|
+
NormTurn,
|
|
16
|
+
TierSpec,
|
|
17
|
+
download_tier,
|
|
18
|
+
iter_jsonl_sessions,
|
|
19
|
+
load_sessions,
|
|
20
|
+
resolve_url,
|
|
21
|
+
write_tier_manifest,
|
|
22
|
+
)
|
|
23
|
+
from abench.core.metrics import (
|
|
24
|
+
AgenticMetrics,
|
|
25
|
+
BucketStats,
|
|
26
|
+
SessionRecord,
|
|
27
|
+
TurnRecord,
|
|
28
|
+
compute_metrics,
|
|
29
|
+
summarize_server_metrics,
|
|
30
|
+
)
|
|
31
|
+
from abench.core.workload import (
|
|
32
|
+
PROFILES,
|
|
33
|
+
PlannedSession,
|
|
34
|
+
PlannedTurn,
|
|
35
|
+
Profile,
|
|
36
|
+
build_system_prompt,
|
|
37
|
+
make_gap_policy,
|
|
38
|
+
make_output_policy,
|
|
39
|
+
measure_text_tokens,
|
|
40
|
+
plan_workload,
|
|
41
|
+
select_sessions,
|
|
42
|
+
synthesize_sessions,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
__all__ = [
|
|
46
|
+
"TIERS",
|
|
47
|
+
"TierSpec",
|
|
48
|
+
"NormSession",
|
|
49
|
+
"NormTurn",
|
|
50
|
+
"download_tier",
|
|
51
|
+
"iter_jsonl_sessions",
|
|
52
|
+
"load_sessions",
|
|
53
|
+
"resolve_url",
|
|
54
|
+
"write_tier_manifest",
|
|
55
|
+
"AgenticMetrics",
|
|
56
|
+
"BucketStats",
|
|
57
|
+
"SessionRecord",
|
|
58
|
+
"TurnRecord",
|
|
59
|
+
"compute_metrics",
|
|
60
|
+
"summarize_server_metrics",
|
|
61
|
+
"PROFILES",
|
|
62
|
+
"PlannedSession",
|
|
63
|
+
"PlannedTurn",
|
|
64
|
+
"Profile",
|
|
65
|
+
"build_system_prompt",
|
|
66
|
+
"make_gap_policy",
|
|
67
|
+
"make_output_policy",
|
|
68
|
+
"measure_text_tokens",
|
|
69
|
+
"plan_workload",
|
|
70
|
+
"select_sessions",
|
|
71
|
+
"synthesize_sessions",
|
|
72
|
+
]
|