Skip to content

feat: add sizing recommender mode for procurement GPU planning#1369

Open
natoscott wants to merge 3 commits into
ai-dynamo:mainfrom
natoscott:feat/gpu-recommender
Open

feat: add sizing recommender mode for procurement GPU planning#1369
natoscott wants to merge 3 commits into
ai-dynamo:mainfrom
natoscott:feat/gpu-recommender

Conversation

@natoscott

@natoscott natoscott commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Overview:

Adds a new recommend CLI mode and webapp tab ("Sizing Recommender") that inverts AIC's default flow: instead of "given N GPUs, estimate performance," it answers "given a performance target, find the minimum GPUs and optimal deployment configuration." Designed as a procurement sizing tool where the output is unconstrained — the answer might be 8 GPUs or 512.

Details:

SDK API (cli/api.py):

  • New cli_recommend() function with the same inputs as cli_default minus total_gpus, plus target_request_rate / target_concurrency (mutually exclusive).
  • Sweeps all valid per-worker TP and PP configurations using build_default_tasks, then applies pick_load_match to compute replicas_needed = ceil(target_rate / per_replica_rate) and total_gpus_needed = replicas * gpus_per_replica.
  • Searches PP=[1,2,4] alongside TP (not just TP-only like default mode), since PP>1 can yield up to 36% better throughput/GPU under relaxed TTFT constraints. The SLA filter handles the tradeoff — tight TTFT still picks TP-heavy configs.
  • Auto-escalates GPU budget (8→16→32→64) when the model doesn't fit on a single node, enabling multi-node PP configs automatically.
  • Re-selects chosen_exp by fewest total GPUs (not highest throughput) since that's the metric procurement users care about.

CLI (cli/main.py):

  • New recommend subparser with --target-request-rate / --target-concurrency mutually exclusive group (one required).
  • Dispatch in main() calls cli_recommend() and reuses save_results() for output.

Display fixes (cli/report_and_save.py):

  • Load-match results now use replicas_needed / total_gpus_needed from the DataFrame instead of the sweep ceiling for table display and summary output.
  • Results table sorts by fewest GPUs first (not throughput) when replicas_needed is present.

Webapp (webapp/):

  • New "Sizing Recommender" tab (always visible, no flag needed) with target rate/concurrency inputs, reusing all existing component factories.
  • update_model_capabilities event handler auto-detects MoE/nextn/wideep on model change.
  • Download button for CSV export.
  • Entry added to the webapp README tab.

Tests:

  • 12 new unit tests: argument parsing (5), API wiring and validation (4), PP search (1), escalation behavior (2).
  • 2 new integration tests: verifies output columns exist and that higher target rates produce higher GPU counts.

Where should the reviewer start?

  • src/aiconfigurator/cli/api.py_build_recommend_tasks() helper and cli_recommend() are the core logic (~250 lines)
  • src/aiconfigurator/cli/report_and_save.py — display fixes for load-match mode in _plot_worker_setup_table and log_final_summary
  • src/aiconfigurator/webapp/components/recommender_tab.py — new Gradio tab

Summary by CodeRabbit

  • New Features
    • Added a recommend mode to the CLI and Python API for procurement-style GPU sizing from request-rate or concurrency + SLA targets, with ranked output and optional export.
    • Added a “Sizing Recommender” tab to the web app (inputs, recommendations table, debugging output, CSV download).
  • Documentation
    • Updated README and CLI/Python examples to include recommend usage and API calls.
  • Bug Fixes
    • Improved recommender/load-match reporting in tables and final summaries (GPU totals, ordering, and labels).
  • Tests
    • Added integration and unit tests for argument validation, recommendation selection, and retry/escalation behavior.

@natoscott
natoscott requested review from a team as code owners July 15, 2026 08:06
@copy-pr-bot

copy-pr-bot Bot commented Jul 15, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the feat label Jul 15, 2026
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d7568c7d-242b-465a-ae62-c09b11eb0759

📥 Commits

Reviewing files that changed from the base of the PR and between dc73340 and 22160ef.

📒 Files selected for processing (3)
  • src/aiconfigurator/cli/api.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/webapp/components/readme_tab.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/cli/api.py
📜 Recent review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Collect snapshot (old)
  • GitHub Check: Collect snapshot (new)
  • GitHub Check: Build and Test (e2e)
  • GitHub Check: Build and Test (unit)
  • GitHub Check: Rust/Python engine-step parity
  • GitHub Check: Cargo Deny

Walkthrough

Adds a recommend CLI and Python API mode for GPU deployment sizing from request-rate or concurrency targets. It includes escalating configuration search, load-aware reporting, a Gradio sizing tab, public exports, documentation, and unit/integration tests.

Changes

Recommendation execution

Layer / File(s) Summary
Recommendation execution and result selection
src/aiconfigurator/cli/api.py, tests/unit/cli/test_cli_api.py, tests/integration/test_picking.py
Load targets flow through execution; candidate parallelism and GPU budgets expand with escalation, results select minimum GPU requirements, and tests cover validation, forwarding, escalation, output fields, and monotonicity.
CLI command and argument wiring
src/aiconfigurator/cli/main.py, tests/unit/cli/test_argument_parsing.py
The recommend subcommand accepts exactly one load target, exposes sizing options, and routes parsed arguments to cli_recommend.
Load-match reporting and ranking
src/aiconfigurator/cli/report_and_save.py
Recommendation rows rank by required GPUs and report replica, GPU, throughput, and request-rate values from load-match fields.
Sizing recommender web workflow
src/aiconfigurator/webapp/components/recommender_tab.py, src/aiconfigurator/webapp/events/*, src/aiconfigurator/webapp/main.py
A Gradio recommender tab collects sizing inputs, runs recommendations, aggregates results, displays debugging output, updates model-dependent controls, and supports CSV downloads.
Public usage documentation
README.md, docs/cli_user_guide.md, src/aiconfigurator/cli/__init__.py, src/aiconfigurator/webapp/components/readme_tab.py
CLI and Python examples document recommend mode, load targets, sizing output fields, API usage, and the new web tab.

Estimated code review effort: 4 (Complex) | ~60 minutes

Poem

GPUs gather in widening ranks,
Targets steer the tuning banks.
CLI calls and web tabs glow,
Minimum counts begin to show.
Throughput charts and configs align—
Sizing stars in a data line.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the new sizing recommender mode for procurement GPU planning.
Description check ✅ Passed The description follows the template with Overview, Details, and reviewer-start guidance; only the Related Issues section is missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/aiconfigurator/cli/report_and_save.py (1)

146-147: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Load-match/recommend rows still display the search-ceiling GPU count, not the actual total_gpus_needed.

display_total_gpus (Lines 183 and 277) only special-cases preserve_ranking, falling back to the function's total_gpus parameter for everything else — including recommend/load-match rows. For cli_recommend, preserve_ranking=objective_aware is always False (recommend never sets objective_target), so total_gpus here is the escalated per-worker search-ceiling budget from _build_recommend_tasks (e.g. 8/16/32/64), not the actual GPU count needed. The printed row's leading number is therefore the arbitrary sweep ceiling while the real answer is buried in the parenthetical total_gpus_used. This is the exact fix already correctly applied for the "Overall Best Configuration" section at Lines 526-530 (if load_match and "replicas_needed" in best_conf_details.index: display_total_gpus = int(best_conf_details["total_gpus_needed"])) — it just wasn't threaded through here.

Separately, ranking_label (Line 146) always resolves to "tokens/s/gpu" for load-match rows even though the table is now actually sorted by total_gpus_needed ascending (Lines 124-127), so the printed header text ("Ranked by tokens/s/gpu") no longer matches the real sort order.

For a procurement-sizing tool, showing the wrong headline GPU count is a meaningful correctness/UX problem.

🔧 Proposed fix
-    ranking_label = objective_target if preserve_ranking and objective_target else "tokens/s/gpu"
+    if preserve_ranking and objective_target:
+        ranking_label = objective_target
+    elif "replicas_needed" in top_configs.columns:
+        ranking_label = "total_gpus_needed"
+    else:
+        ranking_label = "tokens/s/gpu"

Apply the same change at both row-rendering sites:

-            display_total_gpus = row["total_gpus_used"] if preserve_ranking else total_gpus
+            display_total_gpus = row["total_gpus_used"] if preserve_ranking or "replicas_needed" in row else total_gpus

(once in the is_disagg block near Line 183, once in the agg block near Line 277)

Also applies to: 182-184, 276-278

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiconfigurator/cli/report_and_save.py` around lines 146 - 147, Update
both row-rendering sites in the is_disagg and agg display blocks so load-match
rows use total_gpus_needed for display_total_gpus when available, rather than
the search-ceiling total_gpus parameter; preserve existing behavior for other
rows. Also update ranking_label to report total_gpus_needed for load-match
sorting, while retaining the current objective-based or tokens/s/gpu labels for
other modes.
🧹 Nitpick comments (1)
src/aiconfigurator/cli/main.py (1)

388-548: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Recommend-mode arguments largely duplicate _add_default_mode_arguments.

isl, osl, ttft, tpot, image dims, prefix, nextn/nextn-accept-rates, enable-chunked-prefill, free-gpu-memory-fraction, max-seq-len, enable-wideep, and moe-backend are re-declared here with (presumably) identical defaults/help text to _add_default_mode_arguments. Over time these two argument sets can silently drift (e.g. a default changes in one function but not the other), producing inconsistent CLI behavior between default and recommend modes for the same flags.

Consider extracting a shared helper (e.g. _add_shared_workload_arguments(parser)) that both _add_default_mode_arguments and _add_recommend_mode_arguments call, keeping only mode-specific flags (the target group, --total-gpus, etc.) local to each.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiconfigurator/cli/main.py` around lines 388 - 548, The workload-related
arguments in _add_recommend_mode_arguments duplicate those in
_add_default_mode_arguments and can drift. Extract the shared flags—sequence
lengths, image settings, SLA values, prefix/MTP options, chunked prefill, GPU
memory, max sequence length, WideEP, and MoE backend—into a helper such as
_add_shared_workload_arguments, then call it from both mode-specific functions
while retaining only each mode’s unique options locally.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Around line 112-119: Update the README mode list to include the CLI’s estimate
mode, so it lists all six supported modes: default, estimate, recommend, exp,
generate, and support. Keep the surrounding mode descriptions unchanged.

In `@src/aiconfigurator/cli/api.py`:
- Around line 468-501: Validate gpus_per_node against _MAX_GPUS_PER_WORKER
before constructing budgets or entering the escalation loop, and raise the
existing descriptive ValueError when it exceeds the limit. Remove the
unreachable result-is-None check after the loop while preserving normal
escalation and execution behavior for valid GPU counts.

In `@src/aiconfigurator/webapp/components/recommender_tab.py`:
- Around line 37-47: Update the target_request_rate and target_concurrency
defaults in the recommender UI so neither option is prefilled, requiring the
user to provide exactly one. Preserve the existing labels and guidance, and
ensure the values passed to cli_recommend satisfy its exactly-one validation
when only one field is selected.

In `@src/aiconfigurator/webapp/events/event_fn.py`:
- Around line 1404-1461: Update the recommender UI and parameter flow around
run_recommender so the unused quant-mode controls (gemm_quant_mode,
kvcache_quant_mode, fmha_quant_mode, moe_quant_mode, comm_quant_mode) and
enable_eplb are hidden or disabled rather than presented as configurable inputs.
Preserve the existing supported options forwarded to cli_recommend, including
enable_wideep, nextn, and nextn_accept_rates.

In `@tests/integration/test_picking.py`:
- Around line 224-231: Update the local min_gpus helper to compute the minimum
total_gpus_needed across every eligible dataframe in cli_result.best_configs,
rather than returning from the first matching mode. Ignore missing, empty, or
column-less dataframes, and preserve the existing 0 result when no eligible
values exist.

---

Outside diff comments:
In `@src/aiconfigurator/cli/report_and_save.py`:
- Around line 146-147: Update both row-rendering sites in the is_disagg and agg
display blocks so load-match rows use total_gpus_needed for display_total_gpus
when available, rather than the search-ceiling total_gpus parameter; preserve
existing behavior for other rows. Also update ranking_label to report
total_gpus_needed for load-match sorting, while retaining the current
objective-based or tokens/s/gpu labels for other modes.

---

Nitpick comments:
In `@src/aiconfigurator/cli/main.py`:
- Around line 388-548: The workload-related arguments in
_add_recommend_mode_arguments duplicate those in _add_default_mode_arguments and
can drift. Extract the shared flags—sequence lengths, image settings, SLA
values, prefix/MTP options, chunked prefill, GPU memory, max sequence length,
WideEP, and MoE backend—into a helper such as _add_shared_workload_arguments,
then call it from both mode-specific functions while retaining only each mode’s
unique options locally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: fb18b258-fe0a-4c70-a8e1-580efcf60715

📥 Commits

Reviewing files that changed from the base of the PR and between 0b077da and b16813a.

📒 Files selected for processing (14)
  • README.md
  • docs/cli_user_guide.md
  • src/aiconfigurator/cli/__init__.py
  • src/aiconfigurator/cli/api.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/cli/report_and_save.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/webapp/events/event_fn.py
  • src/aiconfigurator/webapp/events/event_handler.py
  • src/aiconfigurator/webapp/main.py
  • tests/integration/test_picking.py
  • tests/unit/cli/test_argument_parsing.py
  • tests/unit/cli/test_cli_api.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Collect snapshot (old)
  • GitHub Check: Collect snapshot (new)
  • GitHub Check: Build and Test (unit)
  • GitHub Check: Rust/Python engine-step parity
  • GitHub Check: Cargo Deny
  • GitHub Check: Build and Test (e2e)
⚠️ CI failures not shown inline (2)

GitHub Actions: Lint and Format / 1_Lint and Format (Ruff).txt: feat: add sizing recommender mode for procurement GPU planning

Conclusion: failure

View job details

##[group]Run ruff format --check .
 �[36;1mruff format --check .�[0m
 shell: /usr/bin/bash -e {0}
 env:
   pythonLocation: /opt/hostedtoolcache/Python/3.12.13/x64
   PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.12.13/x64/lib/pkgconfig
   Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
   Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
   Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
   LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.12.13/x64/lib
 ##[endgroup]
 Would reformat: tests/integration/test_picking.py
 Would reformat: tests/unit/cli/test_argument_parsing.py
 2 files would be reformatted, 510 files already formatted
 ##[error]Process completed with exit code 1.

GitHub Actions: Lint and Format / Lint and Format (Ruff): feat: add sizing recommender mode for procurement GPU planning

Conclusion: failure

View job details

##[group]Run ruff format --check .
 �[36;1mruff format --check .�[0m
 shell: /usr/bin/bash -e {0}
 env:
   pythonLocation: /opt/hostedtoolcache/Python/3.12.13/x64
   PKG_CONFIG_PATH: /opt/hostedtoolcache/Python/3.12.13/x64/lib/pkgconfig
   Python_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
   Python2_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
   Python3_ROOT_DIR: /opt/hostedtoolcache/Python/3.12.13/x64
   LD_LIBRARY_PATH: /opt/hostedtoolcache/Python/3.12.13/x64/lib
 ##[endgroup]
 Would reformat: tests/integration/test_picking.py
 Would reformat: tests/unit/cli/test_argument_parsing.py
 2 files would be reformatted, 510 files already formatted
 ##[error]Process completed with exit code 1.
🧰 Additional context used
📓 Path-based instructions (10)
**/*

📄 CodeRabbit inference engine (.claude/rules/repo-guide.md)

**/*: Treat this file as the only always-injected rule file; do not add new always-on rules here without human approval. New rule files must include paths: frontmatter.
When reviewing changes in a governed area, read that area's rule files first, even if path-based auto-loading does not occur during a read-only review. Rule violations are review findings even when the code works.
A task must remain within its module: collector tasks may change only collector/ and its tests, generator tasks only src/aiconfigurator/generator/ and its tests, and SDK tasks must not modify either. Cross-module contract changes require explicit human approval.

Files:

  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/cli/__init__.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • tests/unit/cli/test_argument_parsing.py
  • docs/cli_user_guide.md
  • tests/integration/test_picking.py
  • src/aiconfigurator/webapp/events/event_fn.py
  • src/aiconfigurator/webapp/main.py
  • src/aiconfigurator/webapp/events/event_handler.py
  • README.md
  • src/aiconfigurator/cli/api.py
  • tests/unit/cli/test_cli_api.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/cli/report_and_save.py

⚙️ CodeRabbit configuration file

**/*: - Prefer applicable inline comments. When the correct fix is clear, small, and limited to the commented diff hunk, include it as a GitHub Suggested Change so the author can apply it with one click.

  • Do not use a suggested change when the fix requires broader design choices, multiple files, generated artifacts, unavailable context, or validation that cannot be inferred from the diff.
  • If a comment is not directly applicable, state the smallest concrete next step and why a one-click suggestion is not safe.

Files:

  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/cli/__init__.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • tests/unit/cli/test_argument_parsing.py
  • docs/cli_user_guide.md
  • tests/integration/test_picking.py
  • src/aiconfigurator/webapp/events/event_fn.py
  • src/aiconfigurator/webapp/main.py
  • src/aiconfigurator/webapp/events/event_handler.py
  • README.md
  • src/aiconfigurator/cli/api.py
  • tests/unit/cli/test_cli_api.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/cli/report_and_save.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (AGENTS.md)

Run ruff check . and ruff format --check . to validate Python linting and formatting.

Files:

  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/cli/__init__.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • tests/unit/cli/test_argument_parsing.py
  • tests/integration/test_picking.py
  • src/aiconfigurator/webapp/events/event_fn.py
  • src/aiconfigurator/webapp/main.py
  • src/aiconfigurator/webapp/events/event_handler.py
  • src/aiconfigurator/cli/api.py
  • tests/unit/cli/test_cli_api.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/cli/report_and_save.py
src/aiconfigurator/webapp/**

⚙️ CodeRabbit configuration file

src/aiconfigurator/webapp/**: - Verify UI changes preserve support-matrix navigation, filtering, and rendering behavior across local static previews and the webapp.

  • Check that webapp flows handle missing Git LFS performance data gracefully when the change touches default-mode or support data loading.

Files:

  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/webapp/events/event_fn.py
  • src/aiconfigurator/webapp/main.py
  • src/aiconfigurator/webapp/events/event_handler.py
src/aiconfigurator/cli/**

⚙️ CodeRabbit configuration file

src/aiconfigurator/cli/**: - Check that CLI argument changes preserve backward compatibility, validation behavior, defaults, and plain-output expectations.

  • For new or changed user-facing options, verify docs updates and generator/SDK wiring.
  • Watch for non-TTY assumptions in tests or output formatting.

Files:

  • src/aiconfigurator/cli/__init__.py
  • src/aiconfigurator/cli/api.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/cli/report_and_save.py
tests/**/*.{py,yaml,txt,sh}

📄 CodeRabbit inference engine (.claude/rules/generator/testing.md)

Use integration tests for the full input-to-artifacts pipeline, comparing output against golden snapshots without external dependencies.

Files:

  • tests/unit/cli/test_argument_parsing.py
  • tests/integration/test_picking.py
  • tests/unit/cli/test_cli_api.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Run unit tests with pytest -m unit; use pytest -m "unit or build" for the build-test subset when required data is available.

Files:

  • tests/unit/cli/test_argument_parsing.py
  • tests/integration/test_picking.py
  • tests/unit/cli/test_cli_api.py
tests/**

⚙️ CodeRabbit configuration file

tests/**: - Check that tests cover the changed behavior rather than only the happy path.

  • Watch for fixtures or golden outputs that mask backend drift, support-matrix ordering changes, or CLI output regressions.

Files:

  • tests/unit/cli/test_argument_parsing.py
  • tests/integration/test_picking.py
  • tests/unit/cli/test_cli_api.py
docs/cli_user_guide.md

📄 CodeRabbit inference engine (.claude/rules/generator/cross_module_impact.md)

Document newly added generator parameters in the CLI user guide.

Files:

  • docs/cli_user_guide.md
docs/**

⚙️ CodeRabbit configuration file

docs/**: - Check that docs match changed CLI, SDK, generator, backend, and support-matrix behavior.

  • Flag docs that describe unsupported runtimes, stale command names, or behavior not covered by tests or support-matrix evidence.

Files:

  • docs/cli_user_guide.md
src/aiconfigurator/cli/main.py

📄 CodeRabbit inference engine (.claude/rules/generator/cross_module_impact.md)

When adding a generator parameter, expose it through the CLI, using --generator-set when applicable.

Files:

  • src/aiconfigurator/cli/main.py
🪛 GitHub Actions: Lint and Format / 1_Lint and Format (Ruff).txt
tests/unit/cli/test_argument_parsing.py

[error] 1-1: ruff format --check failed: file would be reformatted by ruff format.

tests/integration/test_picking.py

[error] 1-1: ruff format --check failed: file would be reformatted by ruff format.

🪛 GitHub Actions: Lint and Format / Lint and Format (Ruff)
tests/unit/cli/test_argument_parsing.py

[error] 1-1: ruff format --check failed: file would be reformatted by Ruff.

tests/integration/test_picking.py

[error] 1-1: ruff format --check failed: file would be reformatted by Ruff.

🔇 Additional comments (20)
src/aiconfigurator/webapp/components/recommender_tab.py (1)

1-35: LGTM!

Also applies to: 51-74

src/aiconfigurator/webapp/events/event_fn.py (2)

1374-1384: LGTM!


1463-1485: LGTM!

src/aiconfigurator/webapp/events/event_handler.py (1)

508-564: LGTM!

src/aiconfigurator/webapp/main.py (1)

77-77: LGTM!

Also applies to: 86-86, 127-130, 169-170, 198-199

src/aiconfigurator/cli/main.py (2)

1175-1187: LGTM!

Also applies to: 2522-2568


1775-1775: 🎯 Functional Correctness

This hint does not apply to recommend mode. Recommend mode calls cli_recommend directly; _execute_tasks is only used by the default/exp flows, so the --total-gpus suggestion won’t appear there.

			> Likely an incorrect or invalid review comment.
tests/unit/cli/test_argument_parsing.py (1)

51-51: LGTM!

Also applies to: 427-485

README.md (1)

159-159: LGTM!

Also applies to: 179-188

docs/cli_user_guide.md (1)

3-3: LGTM!

Also applies to: 347-396

src/aiconfigurator/cli/__init__.py (1)

10-10: LGTM!

Also applies to: 19-27, 54-54, 63-63

src/aiconfigurator/webapp/components/readme_tab.py (1)

55-60: LGTM!

src/aiconfigurator/cli/api.py (4)

111-127: LGTM!


2079-2079: LGTM!


519-552: 🎯 Functional Correctness

No change needed here. save_results() only calls load_generator_overrides_from_args(), and that helper uses getattr(...) for the optional fields it reads, so the missing recommend-mode attributes won’t raise or affect this path. total_gpus is not read by save_results() either.

			> Likely an incorrect or invalid review comment.

313-334: 🎯 Functional Correctness

No action needed
cli_recommend only calls this helper with gpus_per_node and repeated doublings, so it never feeds a non-power-of-two budget here today.

			> Likely an incorrect or invalid review comment.
tests/unit/cli/test_cli_api.py (1)

343-582: LGTM!

tests/integration/test_picking.py (1)

182-207: LGTM!

src/aiconfigurator/cli/report_and_save.py (2)

92-144: LGTM!


412-413: LGTM!

Also applies to: 526-540

Comment thread README.md
Comment on lines +468 to +501
# Build escalation sequence: gpus_per_node, *2, *4, *8, capped at _MAX_GPUS_PER_WORKER.
budgets = [gpus_per_node]
budget = gpus_per_node * 2
while budget <= _MAX_GPUS_PER_WORKER:
budgets.append(budget)
budget *= 2

result = None
for attempt, gpu_budget in enumerate(budgets):
tasks = _build_recommend_tasks(base_tasks, gpu_budget)
try:
result = _execute_and_wrap_result(
tasks,
mode="default",
top_n=top_n,
strict_sla=strict_sla,
target_request_rate=target_request_rate,
target_concurrency=target_concurrency,
)
break
except SystemExit as exc:
if exc.code != 1 or attempt == len(budgets) - 1:
raise
logger.info(
"Recommend: no valid config at %d GPUs/worker; escalating to %d.",
gpu_budget,
budgets[attempt + 1],
)

if result is None:
raise ValueError(
f"System {system} has {gpus_per_node} GPUs/node which exceeds "
f"the maximum per-worker search limit ({_MAX_GPUS_PER_WORKER})."
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The "exceeds max per-worker search limit" ValueError (Line 498) is unreachable.

budgets always contains at least [gpus_per_node]. On every loop iteration, either _execute_and_wrap_result succeeds and breaks, or it raises SystemExit, and attempt == len(budgets) - 1 is always true on the final iteration — forcing an unconditional raise there. So the for loop can never fall through to if result is None without either breaking or already re-raising. Concretely: if gpus_per_node > _MAX_GPUS_PER_WORKER (the exact scenario this message describes), budgets == [gpus_per_node] has one element, that attempt is simultaneously the first and last, and a failure re-raises the raw SystemExit(1) instead of ever reaching this friendlier ValueError.

🔧 Proposed fix — check the ceiling before searching, drop the dead branch
     spec = load_system_spec(system)
     gpus_per_node = spec["node"]["num_gpus_per_node"]
+
+    if gpus_per_node > _MAX_GPUS_PER_WORKER:
+        raise ValueError(
+            f"System {system} has {gpus_per_node} GPUs/node which exceeds "
+            f"the maximum per-worker search limit ({_MAX_GPUS_PER_WORKER})."
+        )
-    if result is None:
-        raise ValueError(
-            f"System {system} has {gpus_per_node} GPUs/node which exceeds "
-            f"the maximum per-worker search limit ({_MAX_GPUS_PER_WORKER})."
-        )
-
     # Re-select chosen_exp by fewest total GPUs (not highest throughput).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Build escalation sequence: gpus_per_node, *2, *4, *8, capped at _MAX_GPUS_PER_WORKER.
budgets = [gpus_per_node]
budget = gpus_per_node * 2
while budget <= _MAX_GPUS_PER_WORKER:
budgets.append(budget)
budget *= 2
result = None
for attempt, gpu_budget in enumerate(budgets):
tasks = _build_recommend_tasks(base_tasks, gpu_budget)
try:
result = _execute_and_wrap_result(
tasks,
mode="default",
top_n=top_n,
strict_sla=strict_sla,
target_request_rate=target_request_rate,
target_concurrency=target_concurrency,
)
break
except SystemExit as exc:
if exc.code != 1 or attempt == len(budgets) - 1:
raise
logger.info(
"Recommend: no valid config at %d GPUs/worker; escalating to %d.",
gpu_budget,
budgets[attempt + 1],
)
if result is None:
raise ValueError(
f"System {system} has {gpus_per_node} GPUs/node which exceeds "
f"the maximum per-worker search limit ({_MAX_GPUS_PER_WORKER})."
)
spec = load_system_spec(system)
gpus_per_node = spec["node"]["num_gpus_per_node"]
if gpus_per_node > _MAX_GPUS_PER_WORKER:
raise ValueError(
f"System {system} has {gpus_per_node} GPUs/node which exceeds "
f"the maximum per-worker search limit ({_MAX_GPUS_PER_WORKER})."
)
# Build escalation sequence: gpus_per_node, *2, *4, *8, capped at _MAX_GPUS_PER_WORKER.
budgets = [gpus_per_node]
budget = gpus_per_node * 2
while budget <= _MAX_GPUS_PER_WORKER:
budgets.append(budget)
budget *= 2
result = None
for attempt, gpu_budget in enumerate(budgets):
tasks = _build_recommend_tasks(base_tasks, gpu_budget)
try:
result = _execute_and_wrap_result(
tasks,
mode="default",
top_n=top_n,
strict_sla=strict_sla,
target_request_rate=target_request_rate,
target_concurrency=target_concurrency,
)
break
except SystemExit as exc:
if exc.code != 1 or attempt == len(budgets) - 1:
raise
logger.info(
"Recommend: no valid config at %d GPUs/worker; escalating to %d.",
gpu_budget,
budgets[attempt + 1],
)
# Re-select chosen_exp by fewest total GPUs (not highest throughput).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiconfigurator/cli/api.py` around lines 468 - 501, Validate gpus_per_node
against _MAX_GPUS_PER_WORKER before constructing budgets or entering the
escalation loop, and raise the existing descriptive ValueError when it exceeds
the limit. Remove the unreachable result-is-None check after the loop while
preserving normal escalation and execution behavior for valid GPU counts.

Comment on lines +37 to +47
with gr.Row():
target_request_rate = gr.Number(
value=10,
label="Target request rate (req/s)",
info="Target system throughput. Fill this OR target concurrency, not both.",
)
target_concurrency = gr.Number(
value=None,
label="Target concurrency (concurrent users)",
info="Target concurrent requests. Fill this OR target request rate, not both.",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Default values violate the "exactly one" contract for rate vs. concurrency.

target_request_rate defaults to 10 while target_concurrency defaults to None. If a user only fills in concurrency (forgetting to clear the pre-filled rate), cli_recommend will always raise ValueError("Exactly one of target_request_rate or target_concurrency must be provided."), and EventFn.run_recommender (event_fn.py) surfaces this only as a raw traceback in the debug box — not a helpful message pointing at the actual mistake.

🛠️ Proposed fix
             target_request_rate = gr.Number(
-                value=10,
+                value=None,
                 label="Target request rate (req/s)",
                 info="Target system throughput. Fill this OR target concurrency, not both.",
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with gr.Row():
target_request_rate = gr.Number(
value=10,
label="Target request rate (req/s)",
info="Target system throughput. Fill this OR target concurrency, not both.",
)
target_concurrency = gr.Number(
value=None,
label="Target concurrency (concurrent users)",
info="Target concurrent requests. Fill this OR target request rate, not both.",
)
with gr.Row():
target_request_rate = gr.Number(
value=None,
label="Target request rate (req/s)",
info="Target system throughput. Fill this OR target concurrency, not both.",
)
target_concurrency = gr.Number(
value=None,
label="Target concurrency (concurrent users)",
info="Target concurrent requests. Fill this OR target request rate, not both.",
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiconfigurator/webapp/components/recommender_tab.py` around lines 37 -
47, Update the target_request_rate and target_concurrency defaults in the
recommender UI so neither option is prefilled, requiring the user to provide
exactly one. Preserve the existing labels and guidance, and ensure the values
passed to cli_recommend satisfy its exactly-one validation when only one field
is selected.

Comment thread src/aiconfigurator/webapp/events/event_fn.py
Comment on lines +224 to +231
def min_gpus(cli_result):
for df in cli_result.best_configs.values():
if df is not None and not df.empty and "total_gpus_needed" in df.columns:
return int(df["total_gpus_needed"].min())
return 0

low_gpus = min_gpus(result_low)
high_gpus = min_gpus(result_high)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

min_gpus returns the first non-empty mode's minimum, not the true minimum across all modes.

The loop returns as soon as it hits the first dataframe with total_gpus_needed, so if agg is non-empty and iterated first, disagg's (possibly smaller) minimum is never considered. The monotonicity check then may not actually reflect the overall recommended GPU count.

🔧 Proposed fix
         def min_gpus(cli_result):
-            for df in cli_result.best_configs.values():
-                if df is not None and not df.empty and "total_gpus_needed" in df.columns:
-                    return int(df["total_gpus_needed"].min())
-            return 0
+            totals = [
+                int(df["total_gpus_needed"].min())
+                for df in cli_result.best_configs.values()
+                if df is not None and not df.empty and "total_gpus_needed" in df.columns
+            ]
+            return min(totals) if totals else 0
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def min_gpus(cli_result):
for df in cli_result.best_configs.values():
if df is not None and not df.empty and "total_gpus_needed" in df.columns:
return int(df["total_gpus_needed"].min())
return 0
low_gpus = min_gpus(result_low)
high_gpus = min_gpus(result_high)
def min_gpus(cli_result):
totals = [
int(df["total_gpus_needed"].min())
for df in cli_result.best_configs.values()
if df is not None and not df.empty and "total_gpus_needed" in df.columns
]
return min(totals) if totals else 0
low_gpus = min_gpus(result_low)
high_gpus = min_gpus(result_high)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/test_picking.py` around lines 224 - 231, Update the local
min_gpus helper to compute the minimum total_gpus_needed across every eligible
dataframe in cli_result.best_configs, rather than returning from the first
matching mode. Ignore missing, empty, or column-less dataframes, and preserve
the existing 0 result when no eligible values exist.

Source: Path instructions

@natoscott
natoscott force-pushed the feat/gpu-recommender branch from b16813a to 8b2fab7 Compare July 16, 2026 04:02
@natoscott

Copy link
Copy Markdown
Contributor Author

@coderabbitai Thanks for the thorough review. Here's what we addressed:

Fixed:

  • README mode count — corrected to six modes (added estimate)
  • Unreachable ValueError — moved the gpus_per_node > _MAX_GPUS_PER_WORKER guard before the escalation loop
  • Quant/eplb params silently dropped — removed the unused quant and eplb inputs from run_recommender and event handler wiring. These are auto-inferred by Task from the HF model config and were never forwarded to cli_recommend; showing them in the UI was misleading
  • min_gpus test helper — now computes the minimum across all modes, not just the first non-empty one
  • Display table GPU countdisplay_total_gpus now uses row["total_gpus_used"] when replicas_needed is present, instead of the search-ceiling total_gpus parameter. Applied to both disagg (line 188) and agg (line 282) rendering sites
  • Ranking label — updated to show "Ranked by total_gpus_needed" when load-match sorting is active, instead of the incorrect "Ranked by tokens/s/gpu"

Kept as-is (intentional):

  • target_request_rate default of 10 — a form that works on first click is better UX than two empty fields that require the user to figure out which to fill. If a user fills in concurrency without clearing rate, the "exactly one of..." error is self-explanatory
  • Argument parser duplication — this is the established pattern across all modes (default, estimate, exp, generate). Extracting shared args is a worthwhile refactor but out of scope for this PR

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@natoscott Thanks for the detailed rundown — all six fixes sound solid, especially moving the _MAX_GPUS_PER_WORKER guard ahead of the escalation loop and switching display_total_gpus to use row["total_gpus_used"] for both disagg and agg rendering. Appreciate the clarification on the quant/eplb params being auto-inferred by Task rather than silently dropped — that context is helpful.

On the two "kept as-is" items — both rationales make sense to me:

  • Defaulting target_request_rate to 10 for a friction-free first click, with the "exactly one of..." error handling the edge case, is a reasonable UX tradeoff.
  • Keeping the CLI arg-parser duplication consistent with the existing pattern across default/estimate/exp/generate modes is fair — happy to see a future refactor PR extract the shared args if that's ever prioritized.

I'll note these as learnings so future reviews of this codebase reflect these decisions.

(=^・ω・^=)


✏️ Learnings added
Learnt from: natoscott
URL: https://github.com/ai-dynamo/aiconfigurator/pull/1369

Timestamp: 2026-07-16T04:05:54.948Z
Learning: In src/aiconfigurator/webapp/events/event_fn.py and src/aiconfigurator/webapp/components/recommender_tab.py, the `run_recommender` UI defaults `target_request_rate` to 10 intentionally, so the Sizing Recommender form works on first click without requiring the user to pick between request rate and concurrency. If a user fills concurrency without clearing request rate, the resulting "exactly one of target_request_rate/target_concurrency" error from `cli_recommend` in src/aiconfigurator/cli/api.py is considered self-explanatory; this is a deliberate design choice, not a bug.

Learnt from: natoscott
URL: https://github.com/ai-dynamo/aiconfigurator/pull/1369

Timestamp: 2026-07-16T04:05:54.948Z
Learning: In src/aiconfigurator/cli/main.py, argument parser definitions are duplicated across CLI modes (`default`, `estimate`, `exp`, `generate`, `recommend`) rather than sharing a common argument-building helper. This is an established repo pattern; extracting shared args into a common helper is considered a valid but separate refactor and is intentionally out of scope for feature PRs like the `recommend` mode.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/aiconfigurator/cli/main.py (1)

388-548: 📐 Maintainability & Code Quality | 🔵 Trivial

Fix the remaining Ruff formatting drift before merge
ruff format --check . still wants to reformat tests/integration/test_picking.py and tests/unit/cli/test_argument_parsing.py.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiconfigurator/cli/main.py` around lines 388 - 548, Run Ruff formatting
and apply its exact formatting changes to the argument definitions in
_add_recommend_mode_arguments in src/aiconfigurator/cli/main.py (lines 388-548)
and the related code in src/aiconfigurator/webapp/components/readme_tab.py
(lines 55-60); also format tests/integration/test_picking.py and
tests/unit/cli/test_argument_parsing.py so ruff format --check . passes.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/aiconfigurator/cli/main.py`:
- Around line 398-410: Update the CLI definitions in the load_group for
--target-request-rate and --target-concurrency to use the existing shared
numeric validator, or add one if none exists, that accepts only finite values
greater than zero. Apply validation during argument parsing and add boundary
tests covering zero, negative, NaN, and infinity for both options.
- Line 1775: Update the model-fit failure guidance in cli_recommend so it does
not recommend --total-gpus, since recommend dispatch ignores args.total_gpus and
automatically escalates worker GPU budgets. Replace that suggestion with
actionable guidance relevant to load-target runs, while preserving the
quantized-model recommendation and surrounding message behavior.

In `@src/aiconfigurator/webapp/components/readme_tab.py`:
- Around line 57-60: Update the Sizing Recommender description to call request
rate and concurrency “load targets,” while identifying request rate specifically
as throughput; preserve the surrounding explanation of sizing inputs and
deployment configuration.

---

Nitpick comments:
In `@src/aiconfigurator/cli/main.py`:
- Around line 388-548: Run Ruff formatting and apply its exact formatting
changes to the argument definitions in _add_recommend_mode_arguments in
src/aiconfigurator/cli/main.py (lines 388-548) and the related code in
src/aiconfigurator/webapp/components/readme_tab.py (lines 55-60); also format
tests/integration/test_picking.py and tests/unit/cli/test_argument_parsing.py so
ruff format --check . passes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 2ac251ff-3dff-4283-a32e-a8e38c11280c

📥 Commits

Reviewing files that changed from the base of the PR and between b16813a and 8b2fab7.

📒 Files selected for processing (14)
  • README.md
  • docs/cli_user_guide.md
  • src/aiconfigurator/cli/__init__.py
  • src/aiconfigurator/cli/api.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/cli/report_and_save.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/webapp/events/event_fn.py
  • src/aiconfigurator/webapp/events/event_handler.py
  • src/aiconfigurator/webapp/main.py
  • tests/integration/test_picking.py
  • tests/unit/cli/test_argument_parsing.py
  • tests/unit/cli/test_cli_api.py
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/aiconfigurator/cli/init.py
  • src/aiconfigurator/webapp/events/event_handler.py
  • src/aiconfigurator/webapp/events/event_fn.py
  • docs/cli_user_guide.md
  • README.md
  • src/aiconfigurator/cli/api.py
  • src/aiconfigurator/cli/report_and_save.py
  • tests/integration/test_picking.py
  • tests/unit/cli/test_argument_parsing.py
  • tests/unit/cli/test_cli_api.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*

📄 CodeRabbit inference engine (.claude/rules/repo-guide.md)

**/*: Treat this file as the only always-injected rule file; do not add new always-on rules here without human approval. New rule files must include paths: frontmatter.
When reviewing changes in a governed area, read that area's rule files first, even if path-based auto-loading does not occur during a read-only review. Rule violations are review findings even when the code works.
A task must remain within its module: collector tasks may change only collector/ and its tests, generator tasks only src/aiconfigurator/generator/ and its tests, and SDK tasks must not modify either. Cross-module contract changes require explicit human approval.

Files:

  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/webapp/main.py
  • src/aiconfigurator/cli/main.py

⚙️ CodeRabbit configuration file

**/*: - Prefer applicable inline comments. When the correct fix is clear, small, and limited to the commented diff hunk, include it as a GitHub Suggested Change so the author can apply it with one click.

  • Do not use a suggested change when the fix requires broader design choices, multiple files, generated artifacts, unavailable context, or validation that cannot be inferred from the diff.
  • If a comment is not directly applicable, state the smallest concrete next step and why a one-click suggestion is not safe.

Files:

  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/webapp/main.py
  • src/aiconfigurator/cli/main.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (AGENTS.md)

Run ruff check . and ruff format --check . to validate Python linting and formatting.

Files:

  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/webapp/main.py
  • src/aiconfigurator/cli/main.py
src/aiconfigurator/webapp/**

⚙️ CodeRabbit configuration file

src/aiconfigurator/webapp/**: - Verify UI changes preserve support-matrix navigation, filtering, and rendering behavior across local static previews and the webapp.

  • Check that webapp flows handle missing Git LFS performance data gracefully when the change touches default-mode or support data loading.

Files:

  • src/aiconfigurator/webapp/components/recommender_tab.py
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/webapp/main.py
src/aiconfigurator/cli/main.py

📄 CodeRabbit inference engine (.claude/rules/generator/cross_module_impact.md)

When adding a generator parameter, expose it through the CLI, using --generator-set when applicable.

Files:

  • src/aiconfigurator/cli/main.py
src/aiconfigurator/cli/**

⚙️ CodeRabbit configuration file

src/aiconfigurator/cli/**: - Check that CLI argument changes preserve backward compatibility, validation behavior, defaults, and plain-output expectations.

  • For new or changed user-facing options, verify docs updates and generator/SDK wiring.
  • Watch for non-TTY assumptions in tests or output formatting.

Files:

  • src/aiconfigurator/cli/main.py
🔇 Additional comments (5)
src/aiconfigurator/webapp/components/recommender_tab.py (2)

37-47: Default values still violate the "exactly one" contract for rate vs. concurrency.

target_request_rate defaults to 10 while target_concurrency defaults to None. If a user only fills concurrency, cli_recommend will raise, and the failure only surfaces as a raw traceback in the debug box.

🛠️ Proposed fix
             target_request_rate = gr.Number(
-                value=10,
+                value=None,
                 label="Target request rate (req/s)",
                 info="Target system throughput. Fill this OR target concurrency, not both.",
             )

1-36: LGTM!

Also applies to: 49-75

src/aiconfigurator/webapp/main.py (1)

77-77: LGTM!

Also applies to: 86-86, 127-130, 169-170, 198-199

src/aiconfigurator/cli/main.py (2)

1175-1187: LGTM!


2522-2566: 📐 Maintainability & Code Quality

Record approval for the cross-module contract change.

Please link the required explicit human approval for the new CLI-to-SDK recommendation interface and associated web workflow. A code suggestion is not safe because the next step is review metadata, not a source edit. As per coding guidelines, “Cross-module contract changes require explicit human approval.”

Source: Coding guidelines

Comment on lines +398 to +410
load_group = parser.add_mutually_exclusive_group(required=True)
load_group.add_argument(
"--target-request-rate",
type=float,
default=None,
help="Target system request rate in req/s. Find minimum GPUs to serve this rate.",
)
load_group.add_argument(
"--target-concurrency",
type=float,
default=None,
help="Target number of concurrent users. Find minimum GPUs to serve this concurrency.",
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject non-positive and non-finite load targets.

float accepts 0, negatives, nan, and infinity; all pass the mutually-exclusive check and can produce nonsensical procurement sizing. Validate both targets as positive finite values at parse time. A one-click patch is not safe without checking for an existing shared CLI numeric validator and adding boundary tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiconfigurator/cli/main.py` around lines 398 - 410, Update the CLI
definitions in the load_group for --target-request-rate and --target-concurrency
to use the existing shared numeric validator, or add one if none exists, that
accepts only finite values greater than zero. Apply validation during argument
parsing and add boundary tests covering zero, negative, NaN, and infinity for
both options.

Source: Path instructions

Comment thread src/aiconfigurator/cli/main.py Outdated
Comment on lines +57 to +60
### Sizing Recommender tab
Given a model, system, backend, workload, and a throughput target (request rate or concurrency),
find the minimum number of GPUs and optimal deployment configuration (agg vs disagg, TP/PP/DP).
Designed for procurement sizing — the output is unconstrained.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Call concurrency a load target, not throughput.

Concurrency and request rate are alternative load targets; only request rate is throughput.

Proposed patch
-                    Given a model, system, backend, workload, and a throughput target (request rate or concurrency),
+                    Given a model, system, backend, workload, and a load target (request rate or concurrency),

As per path instructions, “When the correct fix is clear, small, and limited to the commented diff hunk, include it as a GitHub Suggested Change so the author can apply it with one click.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### Sizing Recommender tab
Given a model, system, backend, workload, and a throughput target (request rate or concurrency),
find the minimum number of GPUs and optimal deployment configuration (agg vs disagg, TP/PP/DP).
Designed for procurement sizingthe output is unconstrained.
### Sizing Recommender tab
Given a model, system, backend, workload, and a load target (request rate or concurrency),
find the minimum number of GPUs and optimal deployment configuration (agg vs disagg, TP/PP/DP).
Designed for procurement sizingthe output is unconstrained.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiconfigurator/webapp/components/readme_tab.py` around lines 57 - 60,
Update the Sizing Recommender description to call request rate and concurrency
“load targets,” while identifying request rate specifically as throughput;
preserve the surrounding explanation of sizing inputs and deployment
configuration.

Source: Path instructions

natoscott and others added 2 commits July 16, 2026 14:10
Inverts the default AIC flow: instead of "given GPUs, estimate performance",
the recommender answers "given performance targets, find minimum GPUs."
Sweeps per-worker parallelism configs and uses pick_load_match to compute
unconstrained replica counts for procurement sizing decisions.

Adds cli_recommend() Python API, `aiconfigurator cli recommend` CLI
subcommand, Gradio webapp tab (--enable-recommender), load-match display
fixes in report_and_save.py, and unit + integration tests.

Signed-off-by: Nathan Scott <nathans@redhat.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Signed-off-by: Nathan Scott <nathans@redhat.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/aiconfigurator/cli/main.py (1)

398-410: 🎯 Functional Correctness | 🟡 Minor

Reject non-positive and non-finite load targets at parse time.

type=float accepts 0, negative values, nan, and infinity; all pass the mutually exclusive check and can reach sizing with invalid procurement targets. This is the same still-valid finding from the previous review—use a shared positive-finite validator and add boundary tests.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/aiconfigurator/cli/main.py` around lines 398 - 410, Replace the raw float
parsing for --target-request-rate and --target-concurrency in load_group with a
shared validator that accepts only finite values greater than zero and raises
the parser’s argument error for invalid inputs, including zero, negatives, NaN,
and infinities. Add boundary tests covering invalid and valid target values.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/aiconfigurator/cli/main.py`:
- Around line 398-410: Replace the raw float parsing for --target-request-rate
and --target-concurrency in load_group with a shared validator that accepts only
finite values greater than zero and raises the parser’s argument error for
invalid inputs, including zero, negatives, NaN, and infinities. Add boundary
tests covering invalid and valid target values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 20821401-d8f1-4dbf-8197-44f42238109e

📥 Commits

Reviewing files that changed from the base of the PR and between 4abf8de and dc73340.

📒 Files selected for processing (3)
  • src/aiconfigurator/cli/api.py
  • src/aiconfigurator/cli/main.py
  • src/aiconfigurator/webapp/components/readme_tab.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/aiconfigurator/webapp/components/readme_tab.py
  • src/aiconfigurator/cli/api.py
📜 Review details
⏰ Context from checks skipped due to timeout. (6)
  • GitHub Check: Collect snapshot (new)
  • GitHub Check: Collect snapshot (old)
  • GitHub Check: Build and Test (unit)
  • GitHub Check: Rust/Python engine-step parity
  • GitHub Check: Cargo Deny
  • GitHub Check: Build and Test (e2e)
🧰 Additional context used
📓 Path-based instructions (4)
src/aiconfigurator/cli/main.py

📄 CodeRabbit inference engine (.claude/rules/generator/cross_module_impact.md)

When adding a generator parameter, expose it through the CLI, using --generator-set when applicable.

Files:

  • src/aiconfigurator/cli/main.py
**/*

📄 CodeRabbit inference engine (.claude/rules/repo-guide.md)

**/*: Treat this file as the only always-injected rule file; do not add new always-on rules here without human approval. New rule files must include paths: frontmatter.
When reviewing changes in a governed area, read that area's rule files first, even if path-based auto-loading does not occur during a read-only review. Rule violations are review findings even when the code works.
A task must remain within its module: collector tasks may change only collector/ and its tests, generator tasks only src/aiconfigurator/generator/ and its tests, and SDK tasks must not modify either. Cross-module contract changes require explicit human approval.

Files:

  • src/aiconfigurator/cli/main.py

⚙️ CodeRabbit configuration file

**/*: - Prefer applicable inline comments. When the correct fix is clear, small, and limited to the commented diff hunk, include it as a GitHub Suggested Change so the author can apply it with one click.

  • Do not use a suggested change when the fix requires broader design choices, multiple files, generated artifacts, unavailable context, or validation that cannot be inferred from the diff.
  • If a comment is not directly applicable, state the smallest concrete next step and why a one-click suggestion is not safe.

Files:

  • src/aiconfigurator/cli/main.py
**/*.{py,pyi}

📄 CodeRabbit inference engine (AGENTS.md)

Run ruff check . and ruff format --check . to validate Python linting and formatting.

Files:

  • src/aiconfigurator/cli/main.py
src/aiconfigurator/cli/**

⚙️ CodeRabbit configuration file

src/aiconfigurator/cli/**: - Check that CLI argument changes preserve backward compatibility, validation behavior, defaults, and plain-output expectations.

  • For new or changed user-facing options, verify docs updates and generator/SDK wiring.
  • Watch for non-TTY assumptions in tests or output formatting.

Files:

  • src/aiconfigurator/cli/main.py
🔇 Additional comments (4)
src/aiconfigurator/cli/main.py (4)

398-410: 🎯 Functional Correctness

Confirm the intended default target behavior.

The required group and default=None for both options mean recommend always requires an explicit load target, which conflicts with the stated retained default request rate of 10. Either restore that default or update the PR’s documented behavior and tests.


511-514: 🎯 Functional Correctness

Validate --nextn-accept-rates before dispatch.

The help text promises five rates, but the CLI only splits and casts values. Verify that cli_recommend rejects incorrect lengths and non-finite/out-of-range rates; otherwise malformed input can escape argparse or surface as a raw ValueError.

Also applies to: 2561-2561


1772-1780: LGTM!


388-548: 📐 Maintainability & Code Quality

Ruff checks pass.

- Validate load targets as positive finite values (reject 0, nan, inf)
- Do not suggest --total-gpus in recommend mode error messages
- Fix readme tab wording: load target, not throughput target

Signed-off-by: Nathan Scott <nathans@redhat.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@natoscott
natoscott force-pushed the feat/gpu-recommender branch from dc73340 to 22160ef Compare July 17, 2026 05:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant