Commit 66bf9ba
feat: per-lobe benchmark suite — speed/parallel/prefill/generation + logprobs cat probe (v0.31.0) (#66)
* spec+plan: per-lobe benchmark suite (devague /think + /spec-to-plan)
Converged spec + buildable plan for a per-lobe benchmark suite: speed,
parallel throughput (auto-ramp to plateau), prefill/TTFT, generation
latency for the minor (Qwen3.5-4B) and primary (27B) lobes through the
gateway, plus a logprobs-scored "Where is the cat?" temporal-reasoning
probe (echo-softmax headline + first-token-mass cross-check, two modes).
Plan: 7 file-disjoint TDD-gated tasks across 3 dependency waves; extends
lobes benchmark (--all-lobes --concurrency auto) and lobes eval (cat
--score logprobs) plus net-new logprobs client plumbing. Risk r1: verify
/v1/completions echo is gateway-routed, else fall back to first-token mass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
* feat(bench): add lobes.bench package skeleton for the benchmark suite
Pre-creates the shared package __init__ so parallel Wave 0 tasks (cat_probe,
report) add disjoint module files without contending on __init__.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
* feat(bench): combined per-lobe markdown report renderer with deltas (t4)
Implement render_report() to emit a GitHub-flavored markdown table
comparing minor vs primary lobes with one metric per row and explicit
signed deltas. Includes cat soft-score alongside perf metrics.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
* feat(bench): timestamped cat-probe generator, open+closed modes (t2)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(minor): logprobs + completions-echo + gateway-echo probe (t1)
Extend chat_completion with logprobs/top_logprobs forwarding; add
completions_echo (POST /v1/completions echo=true for full-sequence
scoring) and gateway_supports_echo (capability probe that never raises).
All three stdlib-only; 9 hermetic tests in tests/test_minor_logprobs.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(assess): per-lobe perf engine — ttft, concurrent driver, auto-ramp knee (t3)
Add four new functions to lobes/assess.py (purely additive, no existing code changed):
- measure_prefill_ttft: time-to-first-token via max_tokens=1 round-trip
- run_concurrent: ThreadPoolExecutor batch driver; returns req/s, p50/p95, ms/token
- _find_knee: pure plateau detector on pre-collected rows (no network)
- auto_ramp_concurrency: ramps schedule=(1,2,4,8,16,32), early-stops at knee
New test file tests/test_assess_perf.py (20 tests, all hermetic):
- ThreadingHTTPServer fixture for ttft/concurrent network tests
- Pure unit tests for _find_knee (plateau, climbing, single-row, edge cases)
- Injected fake _measure for auto_ramp_concurrency (no real sockets)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(bench): logprobs cat scorer — echo softmax headline + first-token cross-check + fallback (t5)
Adds lobes/bench/cat_score.py with three pure helpers (_softmax,
_sequence_logprob, _first_token_mass) and the score_case orchestrator.
19 hermetic tests in tests/test_cat_score.py cover AC1 (headline ≈ 1.0),
AC2 (first_token_mass always float in [0,1]), and AC3 (renormalisation
+ fallback to first-token mass when echo is unavailable).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): lobes eval cat --score logprobs, open|closed modes (t6)
Wire generate_case + score_case (logprobs) into a read-only
`lobes eval cat` sub-verb beside `eval minor`. Accepts a JSONL
suite of {seed, mode?, n_characters?} lines; emits per-case
soft_score, headline, first_token_mass, and the correct answer;
aggregates mean_soft_score. --mode {open,closed} selects the
probe mode globally; suite lines may override per-case.
Covers spec target c16.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(cli): lobes benchmark --all-lobes --concurrency auto, combined per-lobe report (t7)
Add --all-lobes branch to `lobes benchmark` that benchmarks both the primary and
minor lobes through the gateway in a single invocation, rendering perf metrics
(decode tok/s, prefill TTFT, peak req/s, p50/p95 latency, ms/token) + cat
soft-score per lobe via render_report with signed per-metric deltas (AC1-AC4).
New flags: --all-lobes (store_true), --concurrency (auto or int), --minor-model.
New helpers: _bench_one_lobe (per-lobe perf+score), _bench_all_lobes (orchestrator).
Existing single-model cmd_benchmark path is preserved unchanged.
All 18 new tests pass hermetically (monkeypatched network); 64 CLI regression tests unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(bench): silence two bandit false positives (B311 seeded RNG, B105 label)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
* chore: bump version 0.30.0 -> 0.31.0 (per-lobe benchmark suite)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
* fix(bench): avoid float == in cat scorer denom guard (SonarCloud S1244)
Replace `denom == 0.0` with `denom <= 0.0` — semantically identical
(denom is a sum of non-negative probabilities so it can never be
negative), but avoids the exact float-equality that SonarCloud S1244
flags as a reliability issue.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* chore(sonar): mark seeded-RNG hotspots (python:S2245) reviewed-safe in cat_probe
The 'Where is the cat?' benchmark generator uses a seeded random.Random for
deterministic, reproducible test-data — no crypto, no secrets, no trust
boundary. Mirrors the existing e1 reviewed-safe suppression and the bandit
# nosec B311 already on these calls.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
* fix(minor): drop redundant HTTPError from except tuple (SonarCloud S5713)
urllib.error.HTTPError is a subclass of OSError, making it redundant in
the except tuple of gateway_supports_echo(). Remove it and drop the now-
unused `import urllib.error` — behavior is unchanged (HTTPError is still
caught via OSError).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(eval): validate cat suite entries + dedupe json-help literal (Qodo + SonarCloud S1192)
- _load_cat_suite() now validates seed is int-parseable, mode is open/closed (if present), and n_characters is a positive int (if present); raises ModelGearError(EXIT_USER_ERROR) with line number for each violation instead of letting raw ValueError/TypeError propagate from cmd_eval_cat()
- Add module-level _JSON_HELP constant to deduplicate the "Emit structured JSON." literal (S1192 — appeared 3× across eval/minor/cat parsers)
- Add 3 new tests: non-integer seed, invalid mode, non-int n_characters
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(report): lift render_report closures to module level (SonarCloud S3776)
Move the three nested helper functions (format_metric_name, format_value,
format_delta) from inside render_report to module-level private functions
(_format_metric_name, _format_value, _format_delta). Reduces cognitive
complexity of render_report from 16 to ≤15 (SonarCloud python:S3776 CRITICAL).
Logic and rendered output are byte-identical. Add characterization test to lock
exact output across future refactors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(benchmark): validate --concurrency + non-zero exit on no lobes (Qodo + SonarCloud S3516)
Finding A (Qodo reliability): --concurrency is now validated early in
cmd_benchmark; non-positive integers and non-numeric strings raise
ModelGearError(EXIT_USER_ERROR) before any network call, instead of
crashing with an uncaught ValueError inside ThreadPoolExecutor.
The parsed int is forwarded downstream so int() is called exactly once.
Finding B (S3516 BLOCKER): --all-lobes with both served names unset now
raises ModelGearError(EXIT_ENV_ERROR) instead of silently rendering an
empty report and returning 0, giving the dispatcher a genuine error path.
New tests: test_concurrency_invalid_string_raises_user_error,
test_concurrency_invalid_does_not_call_run_concurrent,
test_all_lobes_no_served_names_raises_env_error (3 tests added; 21 total green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
* fix(cat_score): guard softmax NaN on all -inf + skip empty tokens (Qodo)
Finding A: _softmax() now returns a uniform distribution [1/n]*n when
max_lp is not finite (all-inf input) or when total is zero/non-finite,
instead of propagating NaN. score_case() detects when every candidate's
echo sequence logprob is non-finite and forces the fallback path
(echo_available=False, headline="unavailable", soft_score=first_token_mass)
so no NaN/Inf value can reach emit_result() or JSON serialisation.
Finding B: _first_token_mass() now skips whitespace-only tokens (stripped
to "") when building token_probs. Previously, "" matched every candidate
via first_word.startswith(""), inflating all candidate masses and
distorting the renormalised distribution.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
* fix(sonar): clear second-round Sonar findings (S1244/S3776/S5799/S3516)
- cat_score: denom guard uses <= not == (S1244 reliability bug; was the gate-breaker)
- benchmark: extract _parse_concurrency + _run_all_lobes to cut cmd_benchmark
cognitive complexity (S3776) and avoid the invariant-return shape on the handler;
merge same-line implicit string concat (S5799)
- eval: extract _validate_cat_entry to cut _load_cat_suite complexity (S3776);
merge same-line implicit string concat (S5799)
- sonar-project.properties: e3 reviewed-safe suppression of S3516 on benchmark.py
(CLI handlers uniformly return 0-on-success / raise ModelGearError on failure)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UqHUSNHDF5v15ztvz7zbbS
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent 37740ec commit 66bf9ba
26 files changed
Lines changed: 4759 additions & 13 deletions
File tree
- .devague
- frames
- plans
- docs
- plans
- specs
- lobes
- bench
- cli/_commands
- minor
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
Lines changed: 298 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
0 commit comments