inference-doctor 0.4.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.
Files changed (87) hide show
  1. inference_doctor-0.4.0/CHANGELOG.md +94 -0
  2. inference_doctor-0.4.0/CONTRIBUTING.md +53 -0
  3. inference_doctor-0.4.0/LICENSE +201 -0
  4. inference_doctor-0.4.0/MANIFEST.in +5 -0
  5. inference_doctor-0.4.0/PKG-INFO +228 -0
  6. inference_doctor-0.4.0/README.md +191 -0
  7. inference_doctor-0.4.0/STATUS.md +53 -0
  8. inference_doctor-0.4.0/examples/inference-ci-reliability-001/README.md +30 -0
  9. inference_doctor-0.4.0/examples/inference-ci-reliability-001/baseline/repetition-001.json +6 -0
  10. inference_doctor-0.4.0/examples/inference-ci-reliability-001/baseline/repetition-002.json +6 -0
  11. inference_doctor-0.4.0/examples/inference-ci-reliability-001/baseline/repetition-003.json +6 -0
  12. inference_doctor-0.4.0/examples/inference-ci-reliability-001/baseline/repetition-004.json +6 -0
  13. inference_doctor-0.4.0/examples/inference-ci-reliability-001/baseline/repetition-005.json +6 -0
  14. inference_doctor-0.4.0/examples/inference-ci-reliability-001/baseline/repetition-006.json +6 -0
  15. inference_doctor-0.4.0/examples/inference-ci-reliability-001/candidate/repetition-001.json +6 -0
  16. inference_doctor-0.4.0/examples/inference-ci-reliability-001/candidate/repetition-002.json +6 -0
  17. inference_doctor-0.4.0/examples/inference-ci-reliability-001/candidate/repetition-003.json +6 -0
  18. inference_doctor-0.4.0/examples/inference-ci-reliability-001/candidate/repetition-004.json +6 -0
  19. inference_doctor-0.4.0/examples/inference-ci-reliability-001/candidate/repetition-005.json +6 -0
  20. inference_doctor-0.4.0/examples/inference-ci-reliability-001/candidate/repetition-006.json +6 -0
  21. inference_doctor-0.4.0/examples/inference-ci-reliability-001/summary.json +12 -0
  22. inference_doctor-0.4.0/examples/quickstart/README.md +30 -0
  23. inference_doctor-0.4.0/examples/quickstart/baseline/repetition-001.json +22 -0
  24. inference_doctor-0.4.0/examples/quickstart/baseline/repetition-002.json +22 -0
  25. inference_doctor-0.4.0/examples/quickstart/baseline/repetition-003.json +22 -0
  26. inference_doctor-0.4.0/examples/quickstart/baseline/repetition-004.json +22 -0
  27. inference_doctor-0.4.0/examples/quickstart/baseline/repetition-005.json +22 -0
  28. inference_doctor-0.4.0/examples/quickstart/candidate/repetition-001.json +22 -0
  29. inference_doctor-0.4.0/examples/quickstart/candidate/repetition-002.json +22 -0
  30. inference_doctor-0.4.0/examples/quickstart/candidate/repetition-003.json +22 -0
  31. inference_doctor-0.4.0/examples/quickstart/candidate/repetition-004.json +22 -0
  32. inference_doctor-0.4.0/examples/quickstart/candidate/repetition-005.json +22 -0
  33. inference_doctor-0.4.0/examples/quickstart/regressed-candidate/repetition-001.json +22 -0
  34. inference_doctor-0.4.0/examples/quickstart/regressed-candidate/repetition-002.json +22 -0
  35. inference_doctor-0.4.0/examples/quickstart/regressed-candidate/repetition-003.json +22 -0
  36. inference_doctor-0.4.0/examples/quickstart/regressed-candidate/repetition-004.json +22 -0
  37. inference_doctor-0.4.0/examples/quickstart/regressed-candidate/repetition-005.json +22 -0
  38. inference_doctor-0.4.0/examples/vllm-bench-serve/README.md +22 -0
  39. inference_doctor-0.4.0/examples/vllm-bench-serve/baseline/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-100000.json +34 -0
  40. inference_doctor-0.4.0/examples/vllm-bench-serve/baseline/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-110000.json +34 -0
  41. inference_doctor-0.4.0/examples/vllm-bench-serve/baseline/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-120000.json +34 -0
  42. inference_doctor-0.4.0/examples/vllm-bench-serve/baseline/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-130000.json +34 -0
  43. inference_doctor-0.4.0/examples/vllm-bench-serve/baseline/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-140000.json +34 -0
  44. inference_doctor-0.4.0/examples/vllm-bench-serve/candidate/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-100000.json +34 -0
  45. inference_doctor-0.4.0/examples/vllm-bench-serve/candidate/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-110000.json +34 -0
  46. inference_doctor-0.4.0/examples/vllm-bench-serve/candidate/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-120000.json +34 -0
  47. inference_doctor-0.4.0/examples/vllm-bench-serve/candidate/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-130000.json +34 -0
  48. inference_doctor-0.4.0/examples/vllm-bench-serve/candidate/openai-chat-infqps-concurrency64-Llama-3.1-8B-Instruct-20261011-140000.json +34 -0
  49. inference_doctor-0.4.0/pyproject.toml +89 -0
  50. inference_doctor-0.4.0/setup.cfg +4 -0
  51. inference_doctor-0.4.0/src/inference_doctor/__init__.py +3 -0
  52. inference_doctor-0.4.0/src/inference_doctor/__main__.py +3 -0
  53. inference_doctor-0.4.0/src/inference_doctor/adapters.py +564 -0
  54. inference_doctor-0.4.0/src/inference_doctor/benchmark_results.py +249 -0
  55. inference_doctor-0.4.0/src/inference_doctor/cli.py +377 -0
  56. inference_doctor-0.4.0/src/inference_doctor/collectors/__init__.py +0 -0
  57. inference_doctor-0.4.0/src/inference_doctor/collectors/prometheus.py +182 -0
  58. inference_doctor-0.4.0/src/inference_doctor/comparison.py +352 -0
  59. inference_doctor-0.4.0/src/inference_doctor/comparison_report.py +76 -0
  60. inference_doctor-0.4.0/src/inference_doctor/diagnostics.py +360 -0
  61. inference_doctor-0.4.0/src/inference_doctor/models.py +67 -0
  62. inference_doctor-0.4.0/src/inference_doctor/quality.py +573 -0
  63. inference_doctor-0.4.0/src/inference_doctor/quality_report.py +138 -0
  64. inference_doctor-0.4.0/src/inference_doctor/report.py +126 -0
  65. inference_doctor-0.4.0/src/inference_doctor/stats.py +139 -0
  66. inference_doctor-0.4.0/src/inference_doctor/telemetry.py +161 -0
  67. inference_doctor-0.4.0/src/inference_doctor.egg-info/PKG-INFO +228 -0
  68. inference_doctor-0.4.0/src/inference_doctor.egg-info/SOURCES.txt +85 -0
  69. inference_doctor-0.4.0/src/inference_doctor.egg-info/dependency_links.txt +1 -0
  70. inference_doctor-0.4.0/src/inference_doctor.egg-info/entry_points.txt +2 -0
  71. inference_doctor-0.4.0/src/inference_doctor.egg-info/requires.txt +8 -0
  72. inference_doctor-0.4.0/src/inference_doctor.egg-info/top_level.txt +1 -0
  73. inference_doctor-0.4.0/tests/fixtures/compare/baseline.json +36 -0
  74. inference_doctor-0.4.0/tests/fixtures/compare/candidate_28pct_regression.json +36 -0
  75. inference_doctor-0.4.0/tests/test_adapters.py +349 -0
  76. inference_doctor-0.4.0/tests/test_cli.py +77 -0
  77. inference_doctor-0.4.0/tests/test_compare.py +387 -0
  78. inference_doctor-0.4.0/tests/test_diagnostics.py +364 -0
  79. inference_doctor-0.4.0/tests/test_gate.py +147 -0
  80. inference_doctor-0.4.0/tests/test_inference_ci_reliability.py +146 -0
  81. inference_doctor-0.4.0/tests/test_inputs.py +106 -0
  82. inference_doctor-0.4.0/tests/test_multi_metric.py +80 -0
  83. inference_doctor-0.4.0/tests/test_quality.py +216 -0
  84. inference_doctor-0.4.0/tests/test_vllm_48035_harness.py +336 -0
  85. inference_doctor-0.4.0/tests/test_vllm_52630_harness.py +269 -0
  86. inference_doctor-0.4.0/tools/gate_simulation.py +120 -0
  87. inference_doctor-0.4.0/tools/render_readme_media.py +42 -0
@@ -0,0 +1,94 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.4.0 - 2026-10-11
6
+
7
+ ### Changed: the compare-runs decision rule
8
+
9
+ - `compare-runs` decides with a confidence interval instead of a CV cut followed by a fixed threshold on the difference of means. It computes a Welch t interval on the log ratio of means (90% two-sided by default): `FAIL` when the whole interval for the regression is at or past `--regression-percent`, `PASS` when the whole interval is below it, and `INCONCLUSIVE` when it contains the threshold. Validity checks (environments, failed requests, missing metrics, repetition count) still come first.
10
+ - Why: simulating the old rule showed a healthy benchmark (true CV 4%, no change, 5 repetitions) was `INCONCLUSIVE` about a third of the time, and with 2 repetitions a true 12% regression passed about 13% of the time. The new rule's rates are published in `docs/gate-calibration.md`, generated by `tools/gate_simulation.py` and checked in `tests/test_gate.py`.
11
+ - The CV ceiling is now optional and off by default (`--stability-cv-percent`, Action input `stability-cv-percent`). Pass `--stability-cv-percent 5` to keep the old ceiling in addition to the interval.
12
+ - At least 3 repetitions per side are required (`--min-repetitions`, default 3, was 2). 5 or more is recommended.
13
+ - New options `--confidence` and `--min-repetitions`, and matching Action inputs. New Action outputs `regression-percent-low` and `regression-percent-high`.
14
+ - The JSON report is `schema_version: inference-ci-quality-2`. It adds `confidence_interval`, `lower_is_better`, `notes`, and `thresholds.confidence` and `thresholds.min_repetitions`; `measurement_quality` adds `enough_repetitions`, and `environment_valid` no longer depends on the wording of a reason.
15
+ - A candidate better than the baseline by more than the threshold gets a note asking to check that both sides measured the same workload.
16
+ - The quickstart has five repetitions per side and a `regressed-candidate` directory that demonstrates `FAIL`.
17
+ - The Action passes its inputs to the shell through environment variables instead of interpolating them into the script.
18
+
19
+ ### Fixed: compare and diagnose
20
+
21
+ - `compare` no longer crashes with `UnboundLocalError` when a baseline value is 0; a rise from 0 in a latency, or a fall to 0 in a throughput, is a failure.
22
+ - `compare` returns `INCONCLUSIVE` (exit 2) instead of `PASS` when no latency or throughput metric is present in both files, and its overall status is `WARN` (still exit 0) when any row is in the warning band.
23
+ - `compare` is deprecated: it prints a notice on stderr pointing to `compare-runs`, and will be removed in a future release.
24
+ - `diagnose`: the queue mean is aggregated across series (`sum(rate(_sum)) / sum(rate(_count))`); with two replicas at 0.2 s it reported 0.4 s. The Prometheus client now refuses a query that returns more than one series instead of adding them up.
25
+ - `diagnose`: `QUEUE_PRESSURE` requires queue evidence (waiting requests, or a mean queue time of at least 20% of TTFT p95). TTFT over its SLO with a healthy TPOT used to be enough, which flagged prefill-heavy workloads as queueing.
26
+ - `diagnose`: a new `PREFILL_PRESSURE` finding covers TTFT over its SLO with no queue evidence and prefill time at least half of TTFT p95. Prefill time and prompt length were collected before but not used.
27
+ - `diagnose` exits 1 when it reports a critical finding; `--fail-on warning|never` changes the threshold. It always exited 0 before.
28
+
29
+ ### Added: gate several metrics at once
30
+
31
+ - `--metric` can be repeated (Action: a comma-separated `metric` input). Each metric is compared in full; the combined result is `FAIL` if any metric fails, `INCONCLUSIVE` if none fails but one is inconclusive, and `PASS` only if all pass. The report notes that each extra metric adds a chance of a false alarm. With one metric the report is unchanged; with several it has `metrics` and `comparisons`. The Action's job summary has one row per metric, and a new `report` output holds the JSON report path.
32
+
33
+ ### Added: read benchmark tools' own output
34
+
35
+ - `compare-runs` reads the result files of `vllm bench serve --save-result` (including `--append-result` JSON Lines), NVIDIA AIPerf and GenAI-Perf, GuideLLM `benchmarks.json`, and SGLang `bench_serving`, with no conversion step. `--format auto` (the default) detects the format per file; `--format` forces one. See `docs/input-formats.md`.
36
+ - `--gpu` and `--model` record values the result files do not carry; `--guidellm-benchmark` picks one load point of a GuideLLM sweep. Matching Action inputs `format`, `gpu`, `model` and `guidellm-benchmark`.
37
+ - p99 latencies, inter-token latency (`itl_*_ms`), end-to-end latency (`e2el_*_ms`) and total token throughput, in the normalized schema and as `--metric` choices. Every latency family has mean, median, p95 and p99.
38
+ - The report shows the input format, notes when the GPU or model was not recorded or a tool does not record failed requests, and lists skipped files under `input.ignored_files`.
39
+ - `examples/vllm-bench-serve` holds results in vLLM's own format, where throughput passes and mean TTFT fails.
40
+ - Normalized results under any file name are detected, as long as no file in the directory uses one of the standard names.
41
+
42
+ ### Added
43
+
44
+ - `inference-doctor --version`, and `python -m inference_doctor`.
45
+
46
+ ### Fixed
47
+
48
+ - When no repeated-run files are found, the error names the accepted file patterns and lists the JSON files that were ignored. Files such as `run1.json` used to be skipped without a word.
49
+ - `compare-runs` explains `INCONCLUSIVE` accurately. A missing or non-finite metric, or a single repetition, used to be reported as "measurements exceeded the allowed stability threshold"; the stability reason now appears only when a CV was measured, and it states that CV.
50
+ - Identical repetitions, including all-zero values, have a CV of 0 instead of no CV. `--metric requests_failed` could never pass before.
51
+ - An integer `schema_version` (`1` instead of `"1"`) gets a message that says so.
52
+
53
+ ### Changed
54
+
55
+ - One exit-code scheme for every command: 0 PASS, 1 FAIL, 2 INCONCLUSIVE or UNSTABLE, 3 error with no decision. Before, `compare-runs` exited 2 (the INCONCLUSIVE code) for a missing directory or a mistyped option, `compare` exited 1 for UNSTABLE and 2 for invalid input, and `diagnose` exited 1 when Prometheus could not be queried.
56
+ - An internal error now prints one line and exits 3 instead of printing a traceback and exiting 1, which a CI job read as FAIL. Set `INFERENCE_DOCTOR_DEBUG=1` for the traceback.
57
+ - CI runs ruff, the formatter check and mypy, and tests on Python 3.11 and 3.14 on Linux plus 3.14 on macOS and Windows. Actions are pinned by commit SHA, the token is read-only, and checkouts do not keep credentials. The workflow is now `.github/workflows/ci.yml`.
58
+ - The package moved to `src/` and builds with setuptools. The version is defined once, in `inference_doctor/__init__.py`; it said 0.1.0 while the package metadata said 0.3.0.
59
+
60
+ ## 0.3.0 - 2026-09-14
61
+
62
+ ### Added
63
+
64
+ - Reusable GitHub Action for repeated-run inference regression gating.
65
+ - No-GPU quickstart fixtures for trying `compare-runs` locally in minutes.
66
+ - GitHub Actions documentation and PR smoke coverage.
67
+ - Measurement-quality gate with first-class `PASS`, `FAIL`, and `INCONCLUSIVE` outcomes.
68
+ - Repeated-run statistics including mean, median, sample standard deviation, CV, min/max, and descriptive outlier flags.
69
+ - Optional environment telemetry helpers for benchmark investigations.
70
+ - Experimental `inference-doctor compare` upgrade guard for normalized benchmark result files, with configurable regression thresholds, terminal and JSON output, and CI exit codes.
71
+ - Auditable historical regression and benchmark-reliability experiments that informed the measurement-quality design.
72
+
73
+ ### Changed
74
+
75
+ - Repositioned the project around trustworthy inference performance CI: measurement quality is checked before a merge decision is made.
76
+ - `compare-runs` is now the primary documented workflow; Prometheus diagnosis remains supported as a secondary feature.
77
+ - Apparent outliers remain included in official decisions rather than being silently removed.
78
+
79
+ ## 0.2.0 - 2026-09-13
80
+
81
+ ### Added
82
+
83
+ - Queue mean, prefill p95, and prompt-token p95 collection and reporting.
84
+ - Regression coverage for non-finite Prometheus scalar values.
85
+ - Public integration reproduction notes and sanitized validation evidence.
86
+
87
+ ### Changed
88
+
89
+ - Non-finite Prometheus scalar values now render as unavailable.
90
+ - Queue p95 remains visible but no longer raises queue-pressure confidence.
91
+
92
+ ## 0.1.0
93
+
94
+ - Initial vertical slice with Prometheus collection, terminal and JSON reports, and queue, decode, and KV-cache diagnostic rules.
@@ -0,0 +1,53 @@
1
+ # Contributing
2
+
3
+ Thanks for helping improve Inference Doctor.
4
+
5
+ The primary product goal is narrow: **trustworthy performance regression testing for LLM inference**. Changes should make it easier to compare baseline vs candidate performance without giving false confidence when benchmark data is noisy.
6
+
7
+ The original read-only Prometheus diagnostic mode remains supported as a secondary feature.
8
+
9
+ ## Issues
10
+
11
+ For inference-CI issues, include:
12
+
13
+ - selected metric
14
+ - baseline and candidate repetition counts
15
+ - relevant normalized benchmark metadata
16
+ - configured stability/regression thresholds
17
+ - redacted terminal or JSON output
18
+
19
+ For Prometheus diagnostic issues, include the vLLM version, relevant metric names, query window, supplied SLOs, and a redacted report.
20
+
21
+ Never include API tokens, credentials, customer prompts, private model data, or cloud access details.
22
+
23
+ ## Pull requests
24
+
25
+ - Keep each change focused on one observable behavior or usability improvement.
26
+ - Preserve `PASS`, `FAIL`, and `INCONCLUSIVE` semantics unless the proposal includes evidence and tests.
27
+ - Do not silently remove statistical outliers from official decisions.
28
+ - Add or update regression tests for behavior changes.
29
+ - Update docs when schemas, thresholds, supported metrics, or exit codes change.
30
+ - Keep GPU/cloud provisioning outside the core comparison engine.
31
+ - Do not commit virtual environments, model caches, credentials, `.env` files, or cloud-provider artifacts.
32
+
33
+ Before opening a pull request:
34
+
35
+ ```bash
36
+ python -m pip install -e ".[dev]"
37
+ python -m pytest -q
38
+ ruff check . && ruff format --check . && mypy
39
+ ```
40
+
41
+ CI runs the same checks, with tests on Python 3.11 and 3.14 on Linux and on 3.14 on macOS and Windows.
42
+
43
+ ## Releasing
44
+
45
+ 1. Move the `## Unreleased` entries in `CHANGELOG.md` under `## X.Y.Z - date` and set
46
+ `__version__` in `src/inference_doctor/__init__.py` to `X.Y.Z`. Commit to `main`.
47
+ 2. Once CI is green, tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z`. The
48
+ release workflow checks the tag, builds, and publishes to PyPI through trusted
49
+ publishing.
50
+ 3. Move the Action's major tag to the release when its inputs and outputs are compatible:
51
+ `git tag -f v1 vX.Y.Z && git push -f origin v1`.
52
+
53
+ Use clear commit messages and describe the validation performed in the pull request.
@@ -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,5 @@
1
+ graft tests
2
+ graft examples
3
+ include CHANGELOG.md CONTRIBUTING.md STATUS.md
4
+ recursive-include tools *.py
5
+ global-exclude __pycache__ *.py[co]
@@ -0,0 +1,228 @@
1
+ Metadata-Version: 2.4
2
+ Name: inference-doctor
3
+ Version: 0.4.0
4
+ Summary: Performance regression gate for LLM inference benchmarks that checks measurement quality before it judges a regression.
5
+ Author-email: Dundy Pasupuleti <dundysm@gmail.com>
6
+ License-Expression: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/dundysm/inference-doctor
8
+ Project-URL: Source, https://github.com/dundysm/inference-doctor
9
+ Project-URL: Issues, https://github.com/dundysm/inference-doctor/issues
10
+ Project-URL: Changelog, https://github.com/dundysm/inference-doctor/blob/main/CHANGELOG.md
11
+ Keywords: llm,inference,benchmark,performance-regression,vllm,continuous-integration,github-action,measurement-quality
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Classifier: Topic :: Software Development :: Quality Assurance
25
+ Classifier: Topic :: Software Development :: Testing
26
+ Requires-Python: >=3.11
27
+ Description-Content-Type: text/markdown
28
+ License-File: LICENSE
29
+ Requires-Dist: httpx>=0.27
30
+ Requires-Dist: rich>=13.7
31
+ Requires-Dist: typer>=0.12
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=8.0; extra == "dev"
34
+ Requires-Dist: ruff==0.17.0; extra == "dev"
35
+ Requires-Dist: mypy>=1.10; extra == "dev"
36
+ Dynamic: license-file
37
+
38
+ <p align="center">
39
+ <img src="https://raw.githubusercontent.com/dundysm/inference-doctor/main/docs/assets/banner.svg" alt="inference-doctor: did this change make LLM inference slower, and can your benchmark actually tell?" width="900">
40
+ </p>
41
+
42
+ <p align="center">
43
+ <a href="https://pypi.org/project/inference-doctor/"><img alt="PyPI" src="https://img.shields.io/pypi/v/inference-doctor?color=3776ab"></a>
44
+ <a href="https://github.com/dundysm/inference-doctor/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/dundysm/inference-doctor/actions/workflows/ci.yml/badge.svg"></a>
45
+ <img alt="Python 3.11+" src="https://img.shields.io/badge/python-3.11%2B-3776ab">
46
+ <img alt="Reads vLLM, AIPerf, GuideLLM and SGLang" src="https://img.shields.io/badge/reads-vLLM%2C%20AIPerf%2C%20GuideLLM%2C%20SGLang-1f6feb">
47
+ <a href="https://github.com/dundysm/inference-doctor/blob/main/LICENSE"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/license-Apache--2.0-2ea043"></a>
48
+ <a href="https://github.com/dundysm/inference-doctor/blob/main/STATUS.md"><img alt="Status: early" src="https://img.shields.io/badge/status-early-d29922"></a>
49
+ <a href="https://github.com/dundysm/inference-doctor/blob/main/CONTRIBUTING.md"><img alt="PRs welcome" src="https://img.shields.io/badge/PRs-welcome-2ea043"></a>
50
+ </p>
51
+
52
+ A pull request can pass every test and still make inference slower. Putting a benchmark
53
+ in CI is the obvious fix, and it goes wrong in three ways:
54
+
55
+ * **false alarms**: GPU benchmarks are noisy, so a gate on two numbers fails changes that
56
+ did nothing, and the team turns it off,
57
+ * **missed regressions**: with two or three runs, a real 12% slowdown can land inside the
58
+ noise and pass,
59
+ * **apples to oranges**: the two sides ran on a different GPU, model or load, and the
60
+ difference is the setup, not the change.
61
+
62
+ inference-doctor compares repeated baseline and candidate runs and returns `PASS` or
63
+ `FAIL` only when the data can support it. Otherwise it returns `INCONCLUSIVE` and says
64
+ why. It reads the files `vllm bench serve`, AIPerf, GuideLLM and SGLang already write.
65
+
66
+ <p align="center">
67
+ <img src="https://raw.githubusercontent.com/dundysm/inference-doctor/main/docs/assets/terminal.svg" alt="Terminal output of inference-doctor compare-runs on a real H100 run: six runs per side, throughput 4.19% lower, 90% confidence interval for the regression -3.33% to +11.17%, which contains the 10% threshold, so the result is INCONCLUSIVE." width="860">
68
+ <br>
69
+ <sub>A real H100 run (<code>examples/inference-ci-reliability-001</code>), rendered from the CLI by
70
+ <code>tools/render_readme_media.py</code>. Throughput is 4.19% lower, but one noisy baseline block leaves an interval
71
+ from -3.33% to +11.17%. Six runs cannot rule out a 10% regression, so there is no merge recommendation.</sub>
72
+ </p>
73
+
74
+ ## How it decides
75
+
76
+ <p align="center">
77
+ <img src="https://raw.githubusercontent.com/dundysm/inference-doctor/main/docs/assets/decision.svg" alt="Three 90% confidence intervals for the regression against a 10% threshold: the quickstart, +1.08% to +2.92%, PASS; a real H100 run, -3.33% to +11.17%, INCONCLUSIVE; a regressed candidate, +13.13% to +14.87%, FAIL." width="900">
78
+ </p>
79
+
80
+ 1. **Is the comparison valid?** Same GPU, model and benchmark parameters on every run, no
81
+ failed requests, the metric present everywhere, and at least 3 runs per side. If not,
82
+ the result is `INCONCLUSIVE` and the reason names the check that failed.
83
+ 2. **How big is the change, and how sure are we?** A 90% confidence interval for the
84
+ regression (positive is worse), from a Welch t interval on the log ratio of means.
85
+ 3. **Verdict.** `FAIL` when the whole interval is at or past the threshold (default 10%),
86
+ `PASS` when the whole interval is below it, `INCONCLUSIVE` when it contains the
87
+ threshold. More runs narrow the interval.
88
+
89
+ Outliers are flagged but never removed: a bad run widens the interval instead of being
90
+ hidden. Details and references: [measurement quality](docs/measurement-quality.md).
91
+
92
+ ## Quick start
93
+
94
+ Python 3.11 or newer. No GPU: the example results are checked in, so the clone is only for
95
+ them.
96
+
97
+ ```bash
98
+ pip install inference-doctor
99
+ git clone https://github.com/dundysm/inference-doctor.git && cd inference-doctor
100
+
101
+ inference-doctor compare-runs \
102
+ --baseline examples/quickstart/baseline \
103
+ --candidate examples/quickstart/candidate \
104
+ --metric texts_per_second
105
+ ```
106
+
107
+ `PASS`: the candidate is 2% slower, and the interval, +1.08% to +2.92%, is well below 10%.
108
+ Point `--candidate` at `examples/quickstart/regressed-candidate` to see a `FAIL`.
109
+
110
+ ## Use your benchmark's own output
111
+
112
+ No converter needed. Put each side's runs in a directory and point `compare-runs` at it:
113
+
114
+ ```bash
115
+ inference-doctor compare-runs \
116
+ --baseline results/main \
117
+ --candidate results/my-branch \
118
+ --metric output_token_throughput --metric ttft_p99_ms
119
+ ```
120
+
121
+ Repeat `--metric` to gate on several at once: the result is `FAIL` if any metric fails,
122
+ `INCONCLUSIVE` if none fails but one cannot be decided, and `PASS` only when all pass.
123
+
124
+ | Tool | What it reads |
125
+ | --- | --- |
126
+ | `vllm bench serve --save-result` | each result file, or each line with `--append-result` |
127
+ | NVIDIA AIPerf / GenAI-Perf | each `profile_export_aiperf.json` |
128
+ | GuideLLM | `benchmarks.json`, one load point of the sweep (`--guidellm-benchmark`) |
129
+ | SGLang `bench_serving` | each line of its `.jsonl` output |
130
+ | anything else | the small [normalized format](docs/benchmark-format.md) |
131
+
132
+ Metrics: output, total and request throughput, and TTFT, TPOT, ITL and end-to-end latency
133
+ at mean, median, p95 and p99. Most tools do not record the GPU; pass `--gpu` to label it,
134
+ or record it in the results (for vLLM, `--metadata gpu=...`). The
135
+ [input formats](docs/input-formats.md) page lists every field read from each tool.
136
+
137
+ [`examples/vllm-bench-serve`](examples/vllm-bench-serve/README.md) is a small example in
138
+ vLLM's format where throughput passes and TTFT fails, so gating on both catches what a
139
+ throughput-only gate would let through.
140
+
141
+ ## In GitHub Actions
142
+
143
+ Run your benchmark on your own GPU runner, then gate on the results:
144
+
145
+ ```yaml
146
+ - name: Gate inference performance
147
+ uses: dundysm/inference-doctor@v1
148
+ with:
149
+ baseline: results/main
150
+ candidate: results/my-branch
151
+ metric: output_token_throughput, ttft_p99_ms
152
+ min-repetitions: "5"
153
+ ```
154
+
155
+ The Action writes a job summary with the verdict, the interval and the reason, and exposes
156
+ them as outputs. `FAIL`, `INCONCLUSIVE` and errors fail the step, so noisy evidence cannot
157
+ approve a change. inference-doctor does not provision GPUs or run the benchmark. See
158
+ [GitHub Actions](docs/github-actions.md).
159
+
160
+ ## How much to trust it
161
+
162
+ Simulated with known truth, at default settings, from
163
+ [gate calibration](docs/gate-calibration.md) (4,000 trials per row):
164
+
165
+ | Runs per side, noise (CV) | No change | True 5% drop | True 15% drop | True 20% drop |
166
+ | --- | --- | --- | --- | --- |
167
+ | 3 runs, 4% | 78% PASS, 0% FAIL | 34% PASS, 0.1% FAIL | 0% PASS, 37% FAIL | 0% PASS, 85% FAIL |
168
+ | 5 runs, 4% | 98% PASS, 0% FAIL | 60% PASS, 0% FAIL | 0% PASS, 65% FAIL | 0% PASS, 99% FAIL |
169
+ | 10 runs, 4% | 100% PASS, 0% FAIL | 90% PASS, 0% FAIL | 0% PASS, 92% FAIL | 0% PASS, 100% FAIL |
170
+
171
+ The rest of each row is `INCONCLUSIVE`. Right at the 10% threshold, each wrong verdict
172
+ happens about 5% of the time; further from it, at most 0.5%. Noise costs decisions, not
173
+ correctness. Use 5 or more runs per side. These rates assume independent, roughly normal
174
+ runs; [interleave baseline and candidate runs](docs/measurement-quality.md#assumptions)
175
+ to stay close to that.
176
+
177
+ ## Commands
178
+
179
+ | Command | What it does |
180
+ | --- | --- |
181
+ | `compare-runs` | The gate: repeated baseline and candidate runs, one or more metrics, `PASS`, `FAIL` or `INCONCLUSIVE`. |
182
+ | `compare` | Deprecated. One normalized file against another, every metric. A single run per side cannot measure noise; use `compare-runs`. See [upgrade guard](docs/upgrade-guard.md). |
183
+ | `diagnose` | Experimental. Reads a live vLLM server's Prometheus metrics and reports queue, prefill, decode and KV-cache pressure. Read-only. Exits 1 on a critical finding (`--fail-on` changes that). See [example reports](docs/example-reports.md). |
184
+
185
+ `compare-runs` options: `--metric` (repeatable), `--regression-percent` (10), `--confidence` (0.90),
186
+ `--min-repetitions` (3), `--stability-cv-percent` (an optional CV ceiling, off),
187
+ `--format` (auto), `--gpu`, `--model`, `--guidellm-benchmark` and `--json`.
188
+
189
+ **Exit codes**, the same for every command: `0` PASS, `1` FAIL (for `diagnose`, a
190
+ finding at or above `--fail-on`), `2` INCONCLUSIVE (or UNSTABLE for `compare`), `3` error
191
+ with no decision (bad arguments or input, an unreachable Prometheus, or an internal
192
+ error). `inference-doctor --version` prints the
193
+ version.
194
+
195
+ ## What has been measured
196
+
197
+ * **The decision rule**: the calibration table above, regenerated by
198
+ `tools/gate_simulation.py` and checked by `tests/test_gate.py` on every CI run.
199
+ * **One real GPU run**: `examples/inference-ci-reliability-001`, six runs per side of
200
+ BAAI/bge-m3 on one H100 across two vLLM versions. The honest answer was `INCONCLUSIVE`,
201
+ and it still is.
202
+ * **The format readers**: tested against records built from each tool's source code. They
203
+ have not yet been run on a large set of real files from each tool, so please
204
+ [report](https://github.com/dundysm/inference-doctor/issues/new/choose) any file they
205
+ misread.
206
+ * **Not yet**: a gate running in another project's CI. See [STATUS.md](STATUS.md).
207
+
208
+ ## Help test it
209
+
210
+ The most useful thing right now is a real change: run `compare-runs` on results from your
211
+ own benchmark and open a **Benchmark feedback** issue saying what it decided, whether that
212
+ was right, and what was confusing. Redact prompts, credentials, private model names and
213
+ customer data.
214
+
215
+ ## Contributing
216
+
217
+ See [CONTRIBUTING.md](CONTRIBUTING.md). The changelog is [CHANGELOG.md](CHANGELOG.md).
218
+ The experiments that informed the design live under `experiments/`.
219
+
220
+ ```bash
221
+ python -m pip install -e ".[dev]"
222
+ python -m pytest -q
223
+ ruff check . && ruff format --check . && mypy
224
+ ```
225
+
226
+ ## License
227
+
228
+ Apache-2.0. See [LICENSE](LICENSE).