A high-performance, full-stack benchmarking and evaluation playground for Large Language Models (LLMs) and Vision-Language Models (VLMs). Designed for OpenAI-compatible inference backends such as vLLM, SGLang, Ollama, and OpenAI API.
Inference Lab is designed to run seamlessly across platforms:
- Windows: Uses native Edge WebView2 runtime out of the box for an ultra-lightweight, zero-overhead desktop window.
- Linux (Ubuntu / Debian): Integrates with system WebKitGTK (
gir1.2-webkit2-4.1) for native window rendering without bundling heavy Chromium runtimes. - Web Browser Fallback: Gracefully opens in your default browser if native window libraries are unavailable.
- Time to First Token (TTFT): Precise millisecond-level calculation of prompt processing and time to first generated token.
- Inter-Token Latency (ITL): Detailed latency distributions including Average, P50 (Median), and P95 (Spikes).
- Throughput & Decode Rate: Live generation rate in tokens/second.
- End-to-End Latency: Total request completion time and finish reasons, shown in plain language ("Finished normally", "Cut off at Max Tokens").
- Token counts from server usage: streaming calls request
stream_options={"include_usage": true}and read the final usage block, so throughput and token totals reflect the backend's own counts rather than SSE chunk counts. When a backend reports no usage, figures are labelled as estimates (token_source/reasoning_sourceflags). - Prompt accounting:
prompt_tokens,cached_tokens(prefix-cache hits) and a derived prefill rate are captured alongside the output metrics. - Plain-language dashboard: the playground groups stats into a summary strip plus Speed and Tokens cards, each with its unit in the value and a one-line caption; derived or partly-estimated figures say so.
- Optional warmup: 0β20 unmeasured requests can be sent before the timed run to absorb cold-start and graph-capture cost. Progress is reported beside the Execute button and the button switches to "Measuring..." when the timed request begins.
- vLLM
enable_thinkingIntegration: Automatic parameter injection (chat_template_kwargs={"enable_thinking": True, "reasoning_effort": "low"}and the same keys at the top level ofextra_body), built by one shared helper used by every run page. - Reasoning Effort Selector: Playground, Batch and Tool Calling each expose
low/medium/high/xhigh, sent per request and defaulting tolow. No endpoint-wide default. - Thinking Budget Control: Caps reasoning tokens per request (
thinking_budget_tokens), exposed in the Playground, Batch and Tool Calling run options. The budget is sent as a top-level request field, which is where vLLM reads it β the same key nested inchat_template_kwargsis silently ignored. Verified against a live vLLM 0.33 Qwen3.5 endpoint: a top-level budget of 128 yields 127 reasoning tokens, while the nested form ran past 1300. Leaving the field blank sends no budget at all, so the backend's own default applies. - Reasoning shares the response budget: reasoning and the answer draw on the same
max_tokens, so a low Max Tokens with thinking on can consume the whole budget before any answer is emitted. Defaults are kept generous (2048 in Batch and Tool Calling), and a sample that hits the cap is labelled "Max Tokens ran out during reasoning; no answer was produced" instead of appearing as a blank row. - Exact reasoning counts:
completion_tokens_details.reasoning_tokensis read from the server when available, so the reasoning/answer split is measured rather than guessed from the stream. - Streaming Reasoning Extraction: Supports both native
delta.reasoning_contentand real-time streaming tag parsing forthinkingtags inside content streams. - Interactive Reasoning Drawer: Unfolds live reasoning trace; token counts for the run are shown on the dashboard rather than duplicated in the trace header.
- Document & Image Ingestion: Direct upload support for PNG, JPEG, WEBP, and multi-page PDF documents.
- High-Fidelity Rendering via PyMuPDF: PDF pages are rendered to base64 images on demand. Pages are chosen with From / To range inputs plus Apply, All, and Reset, and a DPI selector (100β300, default 200).
- Page-Range Send: Only the pages inside the chosen range are rendered and sent, in ascending page order. Page ordering is a delivery guarantee and is not surfaced as a UI list.
- Preset Page Memory: Page numbers chosen in a preset's sample attachment are stored with the preset (
sample_pages,sample_pdf_name) and restored in the picker grid when the preset is selected again. - Editable Stored Ranges: Selecting a preset shows its stored pages in the same picker. The From/To controls stay live, so the range can be narrowed again (or restored with All) before sending; the request payload always follows the picker's current choice.
- Shared Picker Component:
static/pdf_picker.jspowers the main media panel and the preset modal on both the Playground and the Structured Decisions pages. - URL & Data URI Linking: Benchmark images via remote public URLs or inline Base64 data strings. A single plain image is labelled "Attached image" with its source (file name or image URL); the PDF badge is shown only for actual PDF pages.
- Typed Probabilistic Evaluation: Test discrete decision queries evaluated directly via first-token read log probabilities (
label_mass) without auto-regressive decoding overhead. - Supported Decision Types: Full UI workflows for
choice(categorical options),noul(yes/no affirmation probabilities), andscore(ordered scale distributions and expected values). - Prefix-Cache Aware: Designed to benchmark prefix-caching behavior where the state is cached and subsequent question probes evaluate with zero prompt re-computation.
- Zero-Dependency Dataset Loading: Stream and evaluate rows directly from Hugging Face Datasets API (e.g.,
openai/gsm8k,openai/openai_humaneval,allenai/ai2_arc). - Configurable Concurrency: Run asynchronous parallel workers (1 to 32 parallel requests) and track aggregate TTFT, cluster throughput, and per-sample latency.
- Balanced setup layout: the page is three comparable cards β 1. Target & Model, 2. Dataset, 3. Run Options β rather than one card split into uneven columns. Model-call parameters stay in the Target & Model card while per-run behaviour (reasoning effort, thinking budget, warmup) sits with the Start button in Run Options. The same three-card layout is used by the Tool Calling page.
- Per-sample reasoning captured: each row keeps its reasoning trace (
thinking_trace), marked with a smallthinkbadge, so a thinking run's output column is never silently empty. - Truncation made visible: samples that hit Max Tokens report
finish_reasonand a note, the row shows "Max Tokens ran out during reasoning; no answer was produced" in place of a blank cell, and the summary counts affected samples (empty_outputs). - Aggregate summary: p50/p95 TTFT, total prompt and cached tokens, warmup count and empty-output count are carried in the summary and its tooltips.
- Function-call evaluation: streams scenarios from public Hugging Face function-calling datasets (
Team-ACE/ToolACE,glaiveai/glaive-function-calling-v2,Mustafaege/qwen3.5-functioncalling-v1) and checks whether the model calls the expected tool with the expected arguments. - Invocation matrix: every scenario renders as a coloured block β correct, incorrect, error or pending β and clicking one opens an inspector with the prompt, expected and actual tool call, reasoning trace, function arguments and raw model payload.
- Shared three-card setup: 1. Target & Model, 2. Dataset, 3. Run Options, matching the batch page. Reasoning effort, thinking budget, thinking toggle and warmup all live in Run Options, and the Start button sits there with an inline status line to its left.
- Reasoning-aware: scenarios are run with
enable_thinkingand the same top-level thinking budget as the other pages, so reasoning cost can be capped rather than left to the backend default. - Warmup feedback: the backend's warmup notice is rendered next to the run button while warmup runs and clears when the first scenario completes.
- Inference Endpoints: Saved in
data/endpoints.jsonwith per-model capabilities (supports_thinking,is_vlm,context_window,api_key). Endpoints use UUID string ids. - Edit In Place: Saved endpoints and presets can be reopened with Edit from their manage modal. The edit keeps the stored id, so saving overwrites the record instead of duplicating it, and Cancel returns the modal to create mode.
- Endpoint Fields on Save: the Context Window, Supports Thinking and Vision Model (VLM) fields in the endpoint popup apply to every model listed on that endpoint and are written on save.
- Context Window Is Metadata:
context_windowis stored and shown again in the edit popup, but it is not sent with requests and does not limit prompt size or image count. Only the backend server rejects an oversized request. Note that Max Tokens is the response budget (max_tokens), not a context limit. - Custom Benchmark Presets: Save, edit and delete custom prompt templates in
data/templates.jsonwith optional sample image/document attachments, including the chosen PDF page numbers. - Custom Decision Presets: Maintain custom state/question definitions in
data/decision_templates.jsondirectly from the UI, with the same edit and page-range support. - Protected Built-Ins: Default system presets live in code and cannot be deleted. Editing one saves an override under the same id, and only its wording is editable β its sample media stays read-only. A new record whose generated id collides with an existing one is refused with a prompt to use Edit instead.
LLM-Benchmarks/
βββ app/
β βββ main.py # FastAPI setup, route mounts & page handlers
β βββ routers/
β β βββ benchmark.py # Streaming benchmark & PDF conversion endpoints
β β βββ batch.py # Concurrent dataset batch runner
β β βββ decisions.py # /v1/systemone runner & decision presets API
β β βββ endpoints.py # Endpoint CRUD registry API
β β βββ tools.py # Tool call benchmark runner
β β βββ templates.py # Benchmark prompt template store & router
β βββ schemas/
β β βββ benchmark.py # Benchmark request, metrics & payload schemas
β β βββ batch.py # Batch request, sample result & aggregate schemas
β β βββ decision.py # Structured decision schemas (choice, noul, score)
β β βββ endpoints.py # Endpoint & model metadata schemas
β β βββ tools.py # Tool call benchmark schemas
β β βββ template.py # Prompt template schemas (incl. sample_pages)
β βββ services/
β β βββ benchmark_service.py # Streaming client, PyMuPDF engine & metrics
β β βββ batch_service.py # Asynchronous batch executor with semaphore
β β βββ decision_template_store.py # JSON file store for decision presets
β β βββ endpoint_store.py # JSON file store for endpoints
β β βββ template_store.py # JSON file store for user prompt templates
β β βββ metrics.py # ITL, percentiles & throughput computation
β β βββ reasoning.py # Shared extra_body builder (thinking, effort, budget)
β β βββ usage.py # Server usage parsing, stream_options & warmup helper
β β βββ tool_benchmark_service.py # Tool calling benchmark
βββ data/
β βββ endpoints.json # Configured inference endpoints
β βββ templates.json # User custom benchmark templates
β βββ decision_templates.json # User custom decision templates
βββ Images/ # Default test assets (OCR, sample docs, cats)
βββ scripts/
β βββ build_deb.sh # Linux packaging script
βββ static/
β βββ benchmark.html # Main playground & PDF benchmark studio
β βββ benchmark.js # SSE stream parser, presets & endpoint modals
β βββ batch.html # Batch benchmark UI
β βββ batch.js # Batch evaluation streamer
β βββ tools.html # Tool calling benchmark UI
β βββ tools.js # Tool evaluation functions
β βββ decisions.html # Structured Decisions studio UI
β βββ decisions.js # /v1/systemone runner & distribution visualizer
β βββ pdf_picker.js # Shared PDF page-range picker (range, DPI, grid)
βββ run_app.py # Standalone native desktop launcher
βββ requirements.txt # Python dependencies
βββ README.md
- Python 3.12+
uv(recommended) orpip
- Windows: Uses Microsoft Edge WebView2 (pre-installed on Windows 10 & 11).
- Linux (Ubuntu / Debian): Requires system WebKitGTK and PyGObject libraries for native window rendering:
sudo apt update sudo apt install -y python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 libglib2.0-dev libgirepository-2.0-dev pkg-config
Using uv:
uv syncOr using pip:
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
# On Linux, install PyGObject for native window support:
pip install PyGObjectuv run uvicorn app.main:app --host 0.0.0.0 --port 3333 --reloadRuns the local server on a dynamic free port and opens a native desktop application window:
uv run run_app.pyInference Lab can be packaged into standalone native desktop releases for both Windows (.exe) and Linux (.deb).
Build a single-file executable using PyInstaller:
pyinstaller --noconfirm --onefile --windowed \
--name "LLM_Benchmarking_Studio_v1.5.0" \
--distpath "releases" \
--icon "assets/app_icon.ico" \
--add-data "static;static" \
--add-data "Images;Images" \
--add-data "data;data" \
--add-data "assets;assets" \
--collect-all "uvicorn" \
--collect-all "fastapi" \
--collect-all "starlette" \
--collect-all "pymupdf" \
--collect-all "webview" \
run_app.pyThe output .exe will be generated in releases/.
Ensure scripts/build_deb.sh is executable and run the build script:
chmod +x scripts/build_deb.sh
./scripts/build_deb.shThis freezes the application, bundles all static assets, and builds a Debian .deb package with desktop integration and icon registration.
The output package will be created at:
releases/llm-benchmarking-studio_1.5.0_amd64.deb
Install using apt so all required system runtime dependencies (WebKitGTK) are automatically resolved:
sudo apt install ./releases/llm-benchmarking-studio_1.5.0_amd64.deb(Alternative using dpkg):43. Launching the App:
- Launch directly from your application launcher / desktop menu (LLM Benchmarking Studio)
- Or from any terminal:
llm-benchmarking-studio
sudo apt remove llm-benchmarking-studio| Endpoint | Method | Description |
|---|---|---|
/ |
GET |
Main benchmarking playground UI |
/batch |
GET |
Batch evaluation dashboard UI |
/decisions |
GET |
Structured Decisions (/v1/systemone) studio UI |
/tools |
GET |
Tool calling benchmark UI |
/health |
GET |
Health probe |
/benchmark/run |
POST |
Executes streaming single benchmark via Server-Sent Events (SSE). Sends image_urls in stored page order |
/benchmark/process-pdf |
POST |
Renders PDF pages to base64 images via PyMuPDF. Form fields: file, pages (1-based CSV, e.g. 3,1,7), dpi, count_only |
/batch/run |
POST |
Executes concurrent dataset benchmark with live SSE progress |
/tools-benchmark/run |
POST |
Executes the tool calling benchmark set |
/decision/run |
POST |
Dispatches structured question reads to /v1/systemone |
/decision/process-pdf |
POST |
Same page rendering as /benchmark/process-pdf, for the decisions page |
/decision/templates |
GET, POST |
List or register structured decision presets |
/decision/templates/{id} |
DELETE |
Delete user custom decision preset |
/endpoints/ |
GET, POST, DELETE |
List, save/update, or delete configured endpoints (POST upserts by id) |
/endpoints/{id} |
GET, DELETE |
Retrieve or delete specific endpoint by ID |
/endpoints/{id}/models |
POST |
Append or update a single model inside an existing endpoint |
/templates/ |
GET, POST |
List or register/update benchmark prompt templates (upserts by id) |
/templates/{id} |
GET, DELETE |
Retrieve or delete a user custom benchmark template |
This project is open-source and available under the MIT License.