Skip to content

Commit 893cdf8

Browse files
committed
Commit 1 (primary): Parallel + Debug + Performance
feat: add parallel execution, debug comparison, and performance tracking to model benchmark What: - Parallel analysis mode (--parallel/-p): ThreadPoolExecutor grouped by model_id (models sequential for env-var safety, stocks within each model concurrent) - Live debug comparison (--debug/-d): side-by-side table per stock showing Model | Signal | Conf | Latency | Tokens | Cost | SL/TP, with consensus/divergence detection and fastest/cheapest per stock highlighted - Performance tracking: latency via time.perf_counter(), token usage from agent result stats, cost estimation from per-model pricing table (_MODEL_PRICING, 18 models), stored in context_snapshot.benchmark_meta - Cost-efficiency score: composite_score / log10(1 + total_cost_usd * 100) (higher = more accuracy per dollar) - Configurable --max-parallel (default 3) to throttle API concurrency - Performance leaderboard in reports: avg_latency_ms, total_tokens, cost_usd, cost_efficiency — with "Value Pick" recommendation Why: - Sequential model-by-model analysis was too slow for practical benchmarking (8 models × 5 stocks = 40 sequential analysis runs) - Users needed visibility into latency/cost tradeoffs — a cheap model may be slightly less accurate but 10x cheaper, making it the practical best choice - Debug mode enables real-time observation of model divergence on the same stock, helping users understand when models agree/disagree Verification: - Syntax check: python -c "import ast; ast.parse(...)" passed - Import chain: all 7 classes + 14 top-level functions import correctly - CLI help: --parallel, --max-parallel, --debug flags all present - _ModelOverride context manager: correctly sets LITELLM_MODEL + AGENT_LITELLM_MODEL - Cost estimation: estimate_cost("claude-sonnet-4-6", 5000, 1000) = $0.03 ✓ - Pricing table: 18 models with per-M-token pricing (Anthropic, OpenAI, Google, DeepSeek, etc.) Not verified: - End-to-end analysis with real API calls (needs valid .env and API credits) - Backtest + report pipeline with benchmark-tagged data (needs forward price data) - Thread safety under heavy parallel load (tested with Lock pattern, not stress-tested) Risk: - ThreadPoolExecutor + _ModelOverride env var mutation: race condition possible if two threads call setup_env() simultaneously despite Lock. Mitigation: models are processed sequentially, only stocks within the same model are parallelized. - Rate limits: parallel mode may trigger API rate limiting. Mitigation: --max-parallel controls concurrency; users should start low (2-3) and increase. - Cost estimation: hardcoded prices may drift from actual API billing. Mitigation: self-documenting pricing table in source, easy to update. Rollback: - All new code is additive; removing the commit restores sequential-only behavior and removes debug/performance fields from reports. - No schema changes; benchmark_meta in context_snapshot is ignored by non-benchmark code paths. Commit 2 (doc): Design doc update docs: update design-model-benchmark.html for parallel/debug/performance features What: - Updated architecture diagram to include ThreadPoolExecutor, debug comparison, and performance/cost-efficiency ranking layers - Added new design decisions: parallel execution, debug comparison, performance tracking - Updated Phase 1 workflow with --parallel and --debug examples - Updated CLI reference table with --parallel, --max-parallel, --debug flags - Revised thread safety callout to explain model-grouping strategy - Added edge cases: rate limit throttling, cost estimation accuracy - Marked completed items: cost tracking ✅, parallel execution ✅ - Updated planned enhancements table Why: AGENTS.md rule 1 — new features require design doc updates. All three new capabilities (parallel, debug, performance) needed to be documented in the architecture, workflow, CLI, edge cases, and limitations sections. Verification: - HTML structure intact (xmllint --html passes, HTML5 warnings only) - Key terms present: parallel (14x), debug (6x), cost-efficiency (3x), ThreadPoolExecutor (7x) - File size: 29KB (up from 23KB) Risk: None (docs only) Rollback: Revert to previous design doc version
1 parent 970920c commit 893cdf8

2 files changed

Lines changed: 612 additions & 226 deletions

File tree

docs/design-model-benchmark.html

Lines changed: 71 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ <h1>Design: Model Benchmark</h1>
164164
<dt>Module</dt><dd><code>src/services/model_benchmark.py</code></dd>
165165
<dt>Date</dt><dd>2026-05-11</dd>
166166
<dt>Dependencies</dt><dd><code>BacktestEngine</code>, <code>BacktestService</code>, <code>AnalysisHistory</code>, <code>AgentFactory</code></dd>
167-
<dt>CLI Entry</dt><dd><code>python -m src.services.model_benchmark [--analyze|--evaluate|--report|--full]</code></dd>
167+
<dt>CLI Entry</dt><dd><code>python -m src.services.model_benchmark [--analyze|--evaluate|--report|--full] [--parallel] [--debug] [--max-parallel N]</code></dd>
168168
</dl>
169169

170170
<!-- ═══════════════════════════════════════════════════════════════ -->
@@ -205,19 +205,38 @@ <h2 id="motivation">1. Motivation &amp; Problem Statement</h2>
205205
<h2 id="architecture">2. Architecture Overview</h2>
206206

207207
<div class="diagram">
208-
┌──────────────────────────────────────────────────────────────────┐
209-
│ ModelBenchmarkService │
210-
│ │
211-
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
212-
│ │ DISCOVER │───▶│ ANALYZE │───▶│ EVALUATE │───▶│ REPORT │ │
213-
│ │ models │ │ N stocks │ │ backtest │ │ leaderboard│ │
214-
│ │ │ │ x M models│ │ results │ │ + recommend│ │
215-
│ └──────────┘ └──────────┘ └──────────┘ └────────────┘ │
216-
│ │ │ │ │ │
217-
│ ▼ ▼ ▼ ▼ │
218-
│ Config. AgentFactory BacktestEngine BenchmarkReport
219-
│ llm_model_list (per model) BacktestService (text + JSON)
220-
└──────────────────────────────────────────────────────────────────┘
208+
┌──────────────────────────────────────────────────────────────────────────┐
209+
│ ModelBenchmarkService │
210+
│ │
211+
│ ┌──────────┐ ┌──────────────────┐ ┌──────────┐ ┌────────────┐ │
212+
│ │ DISCOVER │───▶│ ANALYZE │───▶│ EVALUATE │───▶│ REPORT │ │
213+
│ │ models │ │ ┌────────────┐ │ │ backtest │ │ leaderboard│ │
214+
│ │ │ │ │ Sequential │ │ │ results │ │ + perf │ │
215+
│ │ │ │ │ OR │ │ │ │ │ ranking │ │
216+
│ │ │ │ │ --parallel │ │ │ │ │ + recommend│ │
217+
│ │ │ │ │ ThreadPool │ │ │ │ │ │ │
218+
│ │ │ │ │ (model N │ │ │ │ │ │ │
219+
│ │ │ │ │ stocks) │ │ │ │ │ │ │
220+
│ │ │ │ └────────────┘ │ │ │ │ │ │
221+
│ │ │ │ ┌────────────┐ │ │ │ │ │ │
222+
│ │ │ │ │ --debug │ │ │ │ │ │ │
223+
│ │ │ │ │ side-by-side│ │ │ │ │ │ │
224+
│ │ │ │ │ comparison │ │ │ │ │ │ │
225+
│ │ │ │ └────────────┘ │ │ │ │ │ │
226+
│ └──────────┘ └──────────────────┘ └──────────┘ └────────────┘ │
227+
│ │ │ │ │ │
228+
│ ▼ ▼ ▼ ▼ │
229+
│ Config. AgentFactory BacktestEngine BenchmarkReport
230+
│ llm_model_list ThreadPoolExecutor BacktestService (text + JSON)
231+
│ MODEL_PRICING _ModelOverride │ │
232+
│ latency/token ▼ │
233+
│ capture (perf) ┌─────────────────┐ │
234+
│ │ Accuracy + Perf │ │
235+
│ │ leaderboards │ │
236+
│ │ cost-efficiency │ │
237+
│ │ value pick │ │
238+
│ └─────────────────┘ │
239+
└──────────────────────────────────────────────────────────────────────────┘
221240
</div>
222241

223242
<h3>Key Design Decisions</h3>
@@ -231,6 +250,9 @@ <h3>Key Design Decisions</h3>
231250
<tr><td>Benchmark tagging via <code>context_snapshot</code></td><td><code>{"model_id": "...", "benchmark": true}</code> — report phase JOINs <code>backtest_results</code><code>analysis_history</code> to filter by model</td></tr>
232251
<tr><td>5-day default eval window</td><td>US equities move faster than A-shares; 5 trading days is one calendar week and sufficient for short-term signal validation</td></tr>
233252
<tr><td>Separate analyze/evaluate phases</td><td>Analysis runs immediately; evaluation must wait for forward price data. Decoupling allows scheduling each phase independently</td></tr>
253+
<tr><td>Parallel execution (--parallel)</td><td><code>ThreadPoolExecutor</code> grouped by model: models run sequentially (env-var safety), stocks within each model run concurrently (speed). Configurable <code>--max-parallel</code> workers (default 3)</td></tr>
254+
<tr><td>Debug comparison (--debug)</td><td>Live side-by-side table per stock: Model | Signal | Conf | Latency | Tokens | Cost | SL/TP — with consensus/divergence analysis, fastest &amp; cheapest per stock highlighted</td></tr>
255+
<tr><td>Performance tracking</td><td>Latency via <code>time.perf_counter()</code>, token usage from agent result stats, cost estimation via per-model pricing table (<code>_MODEL_PRICING</code>). Stored in <code>benchmark_meta</code> within <code>context_snapshot</code>. Report includes performance leaderboard + cost-efficiency ranking</td></tr>
234256
</tbody>
235257
</table>
236258

@@ -301,13 +323,25 @@ <h3>Conviction Bonus Detail</h3>
301323
<h2 id="workflow">4. Three-Phase Workflow</h2>
302324

303325
<h3>Phase 1: Analyze (Day 0)</h3>
304-
<pre><code>python -m src.services.model_benchmark --analyze --stocks AAPL,NVDA,MSFT</code></pre>
326+
<pre><code># Sequential (default) — one model at a time, one stock at a time
327+
python -m src.services.model_benchmark --analyze --stocks AAPL,NVDA,MSFT
328+
329+
# Parallel — models sequential, stocks concurrent (faster)
330+
python -m src.services.model_benchmark --analyze --stocks AAPL,NVDA,MSFT --parallel
331+
332+
# Parallel + Debug — live side-by-side comparison as results come in
333+
python -m src.services.model_benchmark --analyze --stocks AAPL,NVDA --parallel --debug
334+
335+
# Limit concurrency
336+
python -m src.services.model_benchmark --analyze --stocks AAPL,NVDA --parallel --max-parallel 2</code></pre>
305337
<ol>
306338
<li><code>discover_models()</code> reads <code>config.llm_model_list</code> (aggregated from LLM_CHANNELS + legacy env vars)</li>
307-
<li>For each model, <code>_ModelOverride</code> temporarily sets <code>LITELLM_MODEL</code></li>
339+
<li>Work items are grouped by model_id; models are processed sequentially (for env-var safety via <code>_ModelOverride</code>)</li>
340+
<li>Within each model group, stocks are dispatched concurrently via <code>ThreadPoolExecutor</code> (if <code>--parallel</code> is set)</li>
308341
<li><code>build_agent_executor()</code> creates a fresh agent with the overridden model</li>
309342
<li>Full pipeline runs (Technical → Intel → Decision) for each stock</li>
310-
<li>Dashboard output is stored in <code>analysis_history</code> with <code>context_snapshot={"model_id":"...","benchmark":true}</code></li>
343+
<li>If <code>--debug</code>: latency (<code>time.perf_counter()</code>), token usage, cost are captured and displayed in real-time side-by-side comparison table</li>
344+
<li>Dashboard output is stored in <code>analysis_history</code> with <code>context_snapshot={"model_id":"...","benchmark":true,"benchmark_meta":{...}}</code></li>
311345
</ol>
312346

313347
<h3>Phase 2: Evaluate (Day 5+)</h3>
@@ -390,7 +424,7 @@ <h2 id="model-override">6. Model Override Mechanism</h2>
390424

391425
<div class="callout callout-warning">
392426
<div class="callout-title">Thread Safety</div>
393-
<p>This mutates <em>process-level</em> environment variables. Concurrent benchmark runs on the same process are <strong>not supported</strong>. Each phase (analyze/evaluate/report) should run sequentially in its own process invocation.</p>
427+
<p>This mutates <em>process-level</em> environment variables. The parallel execution strategy (<code>--parallel</code>) mitigates this by grouping work items by model and processing model groups <strong>sequentially</strong> — only stocks within the same model group run concurrently via <code>ThreadPoolExecutor</code>. A <code>threading.Lock</code> guards env var mutations. Subprocess isolation is recommended for fully independent multi-model parallelism.</p>
394428
</div>
395429

396430
<!-- ═══════════════════════════════════════════════════════════════ -->
@@ -408,6 +442,9 @@ <h2 id="cli">7. CLI Interface</h2>
408442
<tr><td><code>--models</code></td><td>Limit to specific models (default: all discovered)</td><td><code>--models gemini/gemini-3.1-pro-preview,openai/gpt-5.5</code></td></tr>
409443
<tr><td><code>--days</code></td><td>Evaluation window in trading days (default: 5)</td><td><code>--days 10</code></td></tr>
410444
<tr><td><code>--json</code></td><td>Output report as JSON instead of formatted text</td><td><code>--report --json</code></td></tr>
445+
<tr><td><code>--parallel, -p</code></td><td>Run model analyses concurrently via ThreadPoolExecutor (stocks within each model parallelized)</td><td><code>--parallel</code></td></tr>
446+
<tr><td><code>--max-parallel</code></td><td>Max concurrent stock invocations per model (default: 3)</td><td><code>--max-parallel 5</code></td></tr>
447+
<tr><td><code>--debug, -d</code></td><td>Capture performance metadata (latency, tokens, cost) and show live side-by-side comparison</td><td><code>--debug</code></td></tr>
411448
</tbody>
412449
</table>
413450

@@ -441,16 +478,27 @@ <h2 id="edge-cases">8. Edge Cases &amp; Error Handling</h2>
441478

442479
<details>
443480
<summary><strong>Config reload side effects</strong></summary>
444-
<p><code>_ModelOverride</code> calls <code>setup_env(override=True)</code> which reloads <code>dotenv</code>. This may affect other loaded modules. Mitigation: benchmark CLI always runs as a fresh process invocation.</p>
481+
<p><code>_ModelOverride</code> calls <code>setup_env(override=True)</code> which reloads <code>dotenv</code>. This may affect other loaded modules. Mitigation: benchmark CLI always runs as a fresh process invocation. Parallel mode groups work by model to minimize env-var thrash.</p>
482+
</details>
483+
484+
<details>
485+
<summary><strong>Rate limit throttling (--parallel mode)</strong></summary>
486+
<p>Running multiple model invocations concurrently may trigger API rate limits (especially for OpenAI and Anthropic). Mitigations: (a) use <code>--max-parallel</code> to limit concurrency (default 3), (b) models are still processed sequentially — only stocks within one model are parallelized, (c) fall back to sequential mode if rate-limit errors persist.</p>
487+
</details>
488+
489+
<details>
490+
<summary><strong>Cost estimation accuracy</strong></summary>
491+
<p>Cost estimates use a hardcoded pricing table (<code>_MODEL_PRICING</code>) and token counts from the agent result metadata. Actual API billing may differ due to cached tokens, batch pricing, or provider-specific discounts. Estimates should be treated as <em>comparative</em> (Model A vs Model B) rather than <em>absolute</em> dollar amounts.</p>
445492
</details>
446493

447494
<!-- ═══════════════════════════════════════════════════════════════ -->
448495
<h2 id="limitations">9. Limitations &amp; Future Work</h2>
449496

450497
<h3>Current Limitations</h3>
451498
<ul>
452-
<li><strong>Process-level env mutation</strong><code>_ModelOverride</code> is not thread-safe. Parallel model analysis would require subprocess isolation.</li>
453-
<li><strong>No cost tracking</strong> — Does not factor in API cost per model. A slightly less accurate but 10x cheaper model may be the practical best choice.</li>
499+
<li><strong>Process-level env mutation</strong><code>_ModelOverride</code> is not thread-safe. Parallel mode (<code>--parallel</code>) mitigates by grouping work items by model and processing model groups sequentially with a <code>threading.Lock</code>. Full multi-model parallelism would require subprocess isolation.</li>
500+
<li><strong><del>No cost tracking</del> ✅ Implemented</strong> — Cost estimation via <code>_MODEL_PRICING</code> table, token capture from agent stats, cost-efficiency ranking in performance leaderboard. Limitations: hardcoded prices, no real-time billing API integration.</li>
501+
<li><strong><del>Sequential-only execution</del> ✅ Implemented</strong><code>--parallel</code> flag now supports concurrent stock analysis via <code>ThreadPoolExecutor</code> with per-model grouping.</li>
454502
<li><strong>Fixed eval window</strong> — All stocks share the same 5-day window. In practice, volatile vs stable stocks may need different horizons.</li>
455503
<li><strong>No statistical significance test</strong> — Current ranking is purely score-based. Small sample sizes may produce misleading rankings.</li>
456504
<li><strong>Single run per (stock, model)</strong> — LLM outputs have inherent randomness. Multiple runs per combination would give confidence intervals.</li>
@@ -460,7 +508,8 @@ <h3>Planned Enhancements</h3>
460508
<table>
461509
<thead><tr><th>Enhancement</th><th>Priority</th><th>Effort</th></tr></thead>
462510
<tbody>
463-
<tr><td>Cost-per-prediction tracking (token usage + API pricing)</td><td>High</td><td>Medium</td></tr>
511+
<tr><td><del>Cost-per-prediction tracking (token usage + API pricing)</del></td><td></td><td>Done</td></tr>
512+
<tr><td><del>Parallel execution (ThreadPoolExecutor per model group)</del></td><td></td><td>Done</td></tr>
464513
<tr><td>Multi-run statistical significance (n=5 per stock-model combo)</td><td>Medium</td><td>High</td></tr>
465514
<tr><td>Per-sector breakdown (Tech vs Energy vs Healthcare accuracy)</td><td>Medium</td><td>Medium</td></tr>
466515
<tr><td>Time-decay weighting (more recent predictions weighted higher)</td><td>Low</td><td>Low</td></tr>

0 commit comments

Comments
 (0)