Skip to content

fix(collector): remove exception-smuggling skip guards; document permanent kernel limits#1359

Open
kaim-eng wants to merge 4 commits into
mainfrom
pr2/collector-skip-guards
Open

fix(collector): remove exception-smuggling skip guards; document permanent kernel limits#1359
kaim-eng wants to merge 4 commits into
mainfrom
pr2/collector-skip-guards

Conversation

@kaim-eng

@kaim-eng kaim-eng commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What changed (and why everything else was removed)

This PR was rebased onto main after a policy review against layer_permissions.md and failure_handling.md. The original diff had seven skip guards and a sm100_exceptions.yaml addition; all but two FIXME comments were dropped.

What remains: two FIXME(kernel-limit) comments in collect_attn.py

TRT-LLM dense attention has two permanent kernel limits that abort the worker before Python can catch them:

  • head_dim > 256"Head size N is not supported by MMHA" (std::terminate in AttentionOp::initialize)
  • head_dim == 192 on SM ≥ 90 → "Unsupported HeadDim for BMM2-N 192" in TllmGenFmha (SM89 is unaffected)

These are documented with FIXME(kernel-limit) comments at the iteration sites so the upgrade audit can grep them and re-verify on each version bump. The cases run and fail rather than being silently skipped.

What was removed and why

Original change Reason removed
sm100_exceptions.yaml Exception rule engine retired in #1302; directory gone from main
All version-conditional skip guards layer_permissions.md: framework-version / model-name conditions in getter-level skips are "exception smuggling" — only legal responses are execute-or-raise
collect_gemm.py vLLM 0.22 FP8 path vLLM collector upgraded to 0.24.0 in #1344; old path gone
collect_vllm/collect_mla_module.py SM100 DSA fp8_block skip Same — against a stale vLLM 0.22 base

Failure routing for the dropped guards

  • cudaErrorIllegalAddress (SM90 long-context GQA, FP8-KV generation): CUDA-fatal errors reset the worker and are recorded before the reset — run-and-record.
  • Version-specific SIGABRTs (SM90 MLA generation, GLM-5 DSA DeepGEMM assert, SM100 DSA context prefix poison): the hang denylist cannot encode version conditions without permanently suppressing fixed versions — run-and-record until the framework fixes the root cause.
  • Python-catchable failures (Mamba2 Ultra OOM / NoneType): run-and-record.

All of the above will appear grouped by (op, model, dtype) in errors_by_group in the collection summary, which is the intended fix-me signal.

Test plan

  • Confirm collect_attn.py runs against a head profile that includes head_dim > 256 and head_dim == 192; verify failures are recorded in errors_<module>.json, not silently dropped
  • Confirm no merge conflicts against current main

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Added notes documenting known TRT-LLM attention kernel limitations for specific head_dim values, including cases that may fail before Python-level handling.
    • Clarified the affected scenarios can apply to both context and generation phases, and should be re-verified after each TRT-LLM version update.

@kaim-eng
kaim-eng requested review from a team as code owners July 14, 2026 16:01
@copy-pr-bot

copy-pr-bot Bot commented Jul 14, 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 fix label Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 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: ba0606d0-2e42-44c0-95ad-d897750d2a20

📥 Commits

Reviewing files that changed from the base of the PR and between a57e49f and e6a4298.

📒 Files selected for processing (1)
  • collector/trtllm/collect_attn.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • collector/trtllm/collect_attn.py
📜 Recent review details
⏰ Context from checks skipped due to timeout. (7)
  • GitHub Check: Collect snapshot (old)
  • GitHub Check: Collect snapshot (new)
  • GitHub Check: Build and Test (e2e)
  • GitHub Check: Build and Test (unit)
  • GitHub Check: Cargo Deny
  • GitHub Check: Rust/Python engine-step parity
  • GitHub Check: copyright-checks

Walkthrough

TRT-LLM attention execution now includes FIXME comments documenting unsupported head_dim kernel limits and associated runtime errors. No executable behavior changed.

Changes

TRT-LLM attention documentation

Layer / File(s) Summary
Document kernel limits
collector/trtllm/collect_attn.py
The attention path documents unsupported head_dim values, affected kernel errors, hard-abort timing, source anchors, and re-verification after TRT-LLM upgrades.

Estimated code review effort: 1 (Trivial) | ~2 minutes

Poem

Kernels meet limits, notes now shine,
Error clues mark the boundary line.
No code paths twist or bend—
FIXME clues await the next version.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and matches the main change: removing skip guards and documenting kernel limits.
Description check ✅ Passed The description is detailed and covers the change and rationale, but it does not follow the template sections for reviewer start or related issues.
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.

🧹 Nitpick comments (2)
collector/trtllm/collect_mla.py (1)

47-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use get_sm_version() for consistency with the rest of the codebase.

Every other skip predicate added in this PR compares get_sm_version() == 90. Here, torch.cuda.get_device_capability() == (9, 0) is used directly, and the guard falls back to "no skip" if torch.cuda.is_available() is False — a case get_sm_version()'s cuda-python fallback would otherwise still resolve.

♻️ Suggested fix
 def _skip_sm90_mla_generation() -> bool:
-    if not torch.cuda.is_available():
-        return False
     # TRT-LLM 1.3.0rc15 aborts in AttentionOp::initialize() for standalone
     # DeepSeek MLA generation on Hopper with:
     # "Deepseek should be supported by fmha in generation part."
     # This is a C++ SIGABRT, so Python cannot mark it expected after dispatch.
     # Pinned to rc15 only (verified on H200); re-evaluate on rc17+ before extending.
-    return tensorrt_llm.__version__.startswith("1.3.0rc15") and torch.cuda.get_device_capability() == (9, 0)
+    return tensorrt_llm.__version__.startswith("1.3.0rc15") and get_sm_version() == 90
🤖 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 `@collector/trtllm/collect_mla.py` around lines 47 - 56, Update
_skip_sm90_mla_generation to use the codebase’s get_sm_version() helper and
compare its result with 90, removing the direct torch.cuda availability and
device-capability checks while preserving the existing TRT-LLM version
condition.
collector/trtllm/collect_attn.py (1)

103-137: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate crash-guard logic vs. _skip_trtllm_sm89_rc15_long_context_gqa (lines 70-100).

The SM90 helper repeats the SM89 body verbatim except for the SM check. Extract a shared implementation parameterized by sm to avoid the two copies silently drifting apart as thresholds get refined.

♻️ Suggested consolidation
-def _skip_trtllm_sm89_rc15_long_context_gqa(batch_size, input_len, num_heads, num_key_value_heads, head_dim) -> bool:
-    if not (tensorrt_llm.__version__.startswith("1.3.0rc15") and get_sm_version() == 89):
-        return False
-    ...
-def _skip_trtllm_sm90_rc15_long_context_gqa(batch_size, input_len, num_heads, num_key_value_heads, head_dim) -> bool:
-    if not (tensorrt_llm.__version__.startswith("1.3.0rc15") and get_sm_version() == 90):
-        return False
-    ...
+def _skip_trtllm_rc15_long_context_gqa(sm: int, batch_size, input_len, num_heads, num_key_value_heads, head_dim) -> bool:
+    if not (tensorrt_llm.__version__.startswith("1.3.0rc15") and get_sm_version() == sm):
+        return False
+    # shared threshold logic
🤖 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 `@collector/trtllm/collect_attn.py` around lines 103 - 137, Extract the shared
long-context GQA threshold logic from `_skip_trtllm_sm89_rc15_long_context_gqa`
and `_skip_trtllm_sm90_rc15_long_context_gqa` into one helper parameterized by
the target SM version. Keep each wrapper responsible only for its TRT-LLM
version and SM check, and preserve the existing head-dimension, token-count, and
supported-head behavior.
🤖 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.

Nitpick comments:
In `@collector/trtllm/collect_attn.py`:
- Around line 103-137: Extract the shared long-context GQA threshold logic from
`_skip_trtllm_sm89_rc15_long_context_gqa` and
`_skip_trtllm_sm90_rc15_long_context_gqa` into one helper parameterized by the
target SM version. Keep each wrapper responsible only for its TRT-LLM version
and SM check, and preserve the existing head-dimension, token-count, and
supported-head behavior.

In `@collector/trtllm/collect_mla.py`:
- Around line 47-56: Update _skip_sm90_mla_generation to use the codebase’s
get_sm_version() helper and compare its result with 90, removing the direct
torch.cuda availability and device-capability checks while preserving the
existing TRT-LLM version condition.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 385c005b-0cd3-4e12-88b1-d90c1ef239b8

📥 Commits

Reviewing files that changed from the base of the PR and between 2feebac and f5c8cb0.

📒 Files selected for processing (7)
  • collector/cases/sm_exceptions/sm100_exceptions.yaml
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mamba2.py
  • collector/trtllm/collect_mla.py
  • collector/trtllm/collect_mla_module.py
  • collector/vllm/collect_gemm.py
  • collector/vllm/collect_mla_module.py
📜 Review details
🧰 Additional context used
📓 Path-based instructions (7)
collector/cases/**/*.yaml

📄 CodeRabbit inference engine (.claude/rules/collector/case_authoring.md)

Do not add YAML keys that condition on batch, sequence, token, or feature values.

Do not extend collector YAML schemas to express shape conditions or framework-version predicates; their deliberate inexpressibility is a guardrail.

Files:

  • collector/cases/sm_exceptions/sm100_exceptions.yaml
{collector/**,tests/unit/collector/**}

📄 CodeRabbit inference engine (.claude/rules/collector/failure_handling.md)

{collector/**,tests/unit/collector/**}: In collector failure handling, observe failures rather than predict them: do not use a declarative expected-failure layer or automatic skips; failing groups must be fixed.
Record every worker failure automatically in the relevant error and collection-summary JSON files with classification, case parameters, exception details, and model/dtype grouping.
Reset a worker after CUDA-fatal errors only after recording the failed task.
Treat missing data points as tolerable downstream because the SDK can interpolate, extrapolate, reuse sibling-version rows, or use HYBRID empirical estimates.
For a hanging case or one that kills the node, add a dated denylist.yaml entry with a reason.
For an entirely unvalidated operation/backend pair, mark the registry OpEntry as unverified=true.
For cases not validated on a specific SM, mark the registry OpEntry with unverified_sms=(sm,).
Represent physically impossible hardware capabilities with a positive floor in capabilities.yaml; do not use it for framework-version kernel gaps.
Treat out-of-memory failures as unclassified until reproduced on a clean GPU; only a genuine OOM may be excluded by the sanctioned generation-time memory filter.
Fix proven collector-code bugs in code rather than using skips, and re-check dispatch and skip rules.
Do not encode framework-version gaps in YAML; allow them to fail and be re-tested after version changes.
Treat isolated, explained, unclustered failures as acceptable; investigate around 10% unexpected failures or sooner when failures cluster.
Treat roughly one-third failing cases or an entire failing family as a systemic collector problem; stop collection and fix the collector.
Never hide failures with broad skips, retries, generic OOM labels, reduced coverage, synthetic rows, or weakened benchmarks.
Compare failure records with the previous run for the same backend and version; prioritize only new failure groups.
Do not mechanically translate failure records in...

Files:

  • collector/cases/sm_exceptions/sm100_exceptions.yaml
  • collector/trtllm/collect_mla.py
  • collector/vllm/collect_mla_module.py
  • collector/trtllm/collect_mamba2.py
  • collector/vllm/collect_gemm.py
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mla_module.py
collector/**

📄 CodeRabbit inference engine (.claude/rules/collector/layer_permissions.md)

collector/**: Keep collector-layer rules in their designated locations: base-op YAML for sweeps and axis-level min_sm; model case YAML for structural shapes and activation; capabilities YAML for positive hardware floors; registries for version routing and maturity markers; collectors for dispatch, classified errors, and memory-feasibility filtering; and the denylist only for hangs or node-killing cases.
Collector tasks may modify only collector/ and tests/unit/collector/; they must not modify SDK, Rust, tools, systems data, or generator code.
Changing the collector data contract requires explicit human approval; do not independently add or rename columns, perf files, or SDK-parsed key dimensions.
Do not edit human-owned collector rule files under .claude/rules/collector/ as a side effect of a fix task; propose policy changes instead.
For a failing case, default to no code change: verify that it is recorded and classified in the failure log, then stop unless the failure-handling decision tree requires escalation.
Mechanism changes to the executor, case generator, failure classification, or case-ID format require explicit human approval and must be proposed rather than included in a case fix.

Review the collector when generated configuration formats change or when generator parameter names used in benchmark configurations change.

Files:

  • collector/cases/sm_exceptions/sm100_exceptions.yaml
  • collector/trtllm/collect_mla.py
  • collector/vllm/collect_mla_module.py
  • collector/trtllm/collect_mamba2.py
  • collector/vllm/collect_gemm.py
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mla_module.py

⚙️ CodeRabbit configuration file

collector/**: - Check collector changes for backend/runtime version accuracy, GPU resource assumptions, reproducible command construction, and clear failure evidence.

  • Flag changes that make support-matrix or perf-data results harder to trace back to the command, model, system, quantization, or runtime version that produced them.

Files:

  • collector/cases/sm_exceptions/sm100_exceptions.yaml
  • collector/trtllm/collect_mla.py
  • collector/vllm/collect_mla_module.py
  • collector/trtllm/collect_mamba2.py
  • collector/vllm/collect_gemm.py
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mla_module.py
collector/**/*

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

collector/**/*: When editing or reviewing collector/**, load and apply layer_permissions.md and failure_handling.md; also load case_authoring.md for case YAML work.
Do not apply generator-module rules to collector/case_generator.py; it expands collection test cases and is unrelated to deployment-config generation.
Collector work is standalone GPU performance data collection and is not part of the wheel runtime.

Files:

  • collector/cases/sm_exceptions/sm100_exceptions.yaml
  • collector/trtllm/collect_mla.py
  • collector/vllm/collect_mla_module.py
  • collector/trtllm/collect_mamba2.py
  • collector/vllm/collect_gemm.py
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mla_module.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:

  • collector/cases/sm_exceptions/sm100_exceptions.yaml
  • collector/trtllm/collect_mla.py
  • collector/vllm/collect_mla_module.py
  • collector/trtllm/collect_mamba2.py
  • collector/vllm/collect_gemm.py
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mla_module.py
collector/**/collect_*.py

📄 CodeRabbit inference engine (.claude/rules/collector/layer_permissions.md)

collector/**/collect_*.py: Collector code may dispatch between kernel paths by SM or version, record the actual kernel_source, raise classified exceptions for runtime probes, and perform memory-feasibility filtering; it must not silently skip queued cases, perform other case filtering, or patch code for one shape.
A queued case must be executed or raise a classified error; branches may change how a case runs, but must not change whether it runs.
Use the framework's own dispatch path to select kernels. Manual backend pinning requires a source citation, and kernel_source must record the actually invoked kernel.
Do not invent backend fallbacks or swap backends in exception handlers. Raise a classified error if the framework-selected path cannot run; only replicate fallback behavior performed by the framework itself.
Measurement-method degradation, such as CUDA-graph capture falling back to eager execution, is allowed only when recorded in the output row; API-compatibility shims may alter construction but never kernel selection.
Memory-feasibility filtering is permitted only during generation inside get_*_test_cases(), using footprint-versus-capacity arithmetic and live device memory where possible; drops must be counted and logged, never silently skipped.
Framework kernel limits must remain as FIXME(kernel-limit) comments at the owning collector invocation site, stating the limit, origin, and unverified status; do not encode them in YAML or implement guards from unverified claims.
On framework version bumps, re-verify every pinned backend and every FIXME(kernel-limit) against framework source before trusting collected data.

Files:

  • collector/trtllm/collect_mla.py
  • collector/vllm/collect_mla_module.py
  • collector/trtllm/collect_mamba2.py
  • collector/vllm/collect_gemm.py
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mla_module.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use the project’s uv-managed environment and run linting with ruff check . and ruff format --check ..

Files:

  • collector/trtllm/collect_mla.py
  • collector/vllm/collect_mla_module.py
  • collector/trtllm/collect_mamba2.py
  • collector/vllm/collect_gemm.py
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mla_module.py
🧠 Learnings (5)
📚 Learning: 2026-06-05T09:02:37.734Z
Learnt from: YijiaZhao
Repo: ai-dynamo/aiconfigurator PR: 1204
File: collector/cases/models/DeepseekV4ForCausalLM_cases.yaml:209-227
Timestamp: 2026-06-05T09:02:37.734Z
Learning: In ai-dynamo/aiconfigurator case YAMLs, op execution order is determined by the registry insertion order as iterated by `build_collections()` (i.e., earlier registered ops run before later ones). `CollectionCasePlan.ops` may be a sorted list but it is used only to filter which ops run, not to control execution order—so do not assume lexicographic op-name order affects execution. Therefore, YAML comments asserting ordering guarantees (e.g., “must run AFTER …”) should be evaluated against registry insertion order, not name sorting. Also, sparse kernel workers should no-op gracefully when their source CSV inputs are absent; in sparse-only runs, missing CSVs should be treated as expected behavior rather than a data-corruption/wrong-write issue.

Applied to files:

  • collector/cases/sm_exceptions/sm100_exceptions.yaml
📚 Learning: 2026-06-29T16:39:41.097Z
Learnt from: tianhaox
Repo: ai-dynamo/aiconfigurator PR: 1256
File: collector/cases/models/Gemma4ForConditionalGeneration_cases.yaml:10-13
Timestamp: 2026-06-29T16:39:41.097Z
Learning: In `ai-dynamo/aiconfigurator` Collector planner case YAMLs, treat `base_ops` as an allowlist only for base-op files that are actually exposed/collectable via the corresponding base documents’ `model_ops` (i.e., there must be an explicit exposure path from a base document to that op). If an op is not exposed through `model_ops` in the base-file `collector/cases/base_ops/*.yaml`, do not add it to `base_ops`—instead, it should be selected through model-local sections such as `all_frameworks_op_cases` (or the equivalent model-specific framework case selectors). When reviewing, verify the exposure path for the op (e.g., `moe` should be included via `all_frameworks_op_cases.moe` for Gemma cases if—and only if—the relevant base-file `model_ops` exposure does not already make it collectable through `base_ops`).

Applied to files:

  • collector/cases/sm_exceptions/sm100_exceptions.yaml
📚 Learning: 2026-02-28T11:44:28.109Z
Learnt from: tianhaox
Repo: ai-dynamo/aiconfigurator PR: 466
File: collector/trtllm/collect_moe_v3.py:4-4
Timestamp: 2026-02-28T11:44:28.109Z
Learning: In collector-related Python files (e.g., collector/trtllm/collect_moe_v3.py), document and enforce that version incompatibilities are surfaced as non-fatal runtime errors during collection. Do not add aggressive preventive version-gating; instead, allow generating test cases that may not be supported across all versions within the __compat__ range and rely on runtime error handling to skip or flag unsupported cases. This should be verifiable by ensuring collection proceeds, errors are reported, and unsupported cases do not halt the overall process.

Applied to files:

  • collector/trtllm/collect_mla.py
  • collector/vllm/collect_mla_module.py
  • collector/trtllm/collect_mamba2.py
  • collector/vllm/collect_gemm.py
  • collector/trtllm/collect_attn.py
  • collector/trtllm/collect_mla_module.py
📚 Learning: 2026-07-05T07:16:31.423Z
Learnt from: tianhaox
Repo: ai-dynamo/aiconfigurator PR: 1264
File: collector/vllm/utils.py:29-34
Timestamp: 2026-07-05T07:16:31.423Z
Learning: In ai-dynamo/aiconfigurator’s vLLM collector modules, keep vLLM imports at top level (not lazily deferred) so a mismatched vLLM version fails during the per-op import and is recorded/classified as a per-op failure (via the existing per-op try/except and __compat gating), rather than altering failure order/granularity. Do not rely on moving vLLM imports into shared helpers like collector/vllm/utils.py to change this behavior. Exception: collector/vllm/collect_mhc_module.py is intentionally “import-vllm-free” (vLLM dependencies were localized) and should remain so to preserve the intended behavior there.

Applied to files:

  • collector/vllm/collect_mla_module.py
  • collector/vllm/collect_gemm.py
📚 Learning: 2026-07-11T03:07:50.673Z
Learnt from: tianhaox
Repo: ai-dynamo/aiconfigurator PR: 1344
File: tests/unit/collector/test_framework_manifest.py:42-49
Timestamp: 2026-07-11T03:07:50.673Z
Learning: For the vLLM “collector v2” registries (e.g., collector/vllm/registry.py and any other vLLM collector v2 registry modules), treat the registry entry `versions` field as a retired per-entry versioning mechanism. Code and tests should expect it to be empty/falsy for every registry entry (i.e., review changes to ensure `entry.versions` is not populated). Version pinning for the vLLM backend must be centralized in `collector/framework_manifest.yaml` via `get_collector_runtime("vllm")`, and each vLLM collector module must declare exactly one `__compat__ = "vllm==<manifest_version>"` line matching the pinned manifest version. If relevant, do not misread the framework-manifest test: `test_active_cuda_vllm_collectors_are_exactly_pinned_to_manifest_version` asserts `all(not entry.versions for entry in VLLM_REGISTRY)` and that `__compat__` matches the manifest version (it is not an inverted assertion).

Applied to files:

  • collector/vllm/collect_mla_module.py
  • collector/vllm/collect_gemm.py
🔇 Additional comments (11)
collector/trtllm/collect_attn.py (3)

171-180: 🎯 Functional Correctness

Skip is broader than the documented verified scope.

The docstring says the crash was verified "across h=64 and h=128 MHA/GQA shapes," but _skip_trtllm_sm90_rc15_fp8_generation takes only use_fp8_kv_cache and blocks every head config on SM90/rc15 FP8-KV generation, including untested shapes (h=96/48/40). Per collector guidelines, a queued case must run or raise a classified error — filtering beyond the verified crash region risks silently dropping cases that would actually succeed.

Please confirm whether all head counts were actually observed to crash, or narrow the guard to the verified n/num_key_value_heads combinations.


183-198: 🎯 Functional Correctness

Head-dim guard is not version-gated, unlike every other predicate in this file.

_skip_trtllm_unsupported_head_dim skips head_dim > 256 and head_dim == 192 on SM≥90 regardless of tensorrt_llm.__version__, whereas all other helpers here are pinned to specific rc versions. If this is meant as a permanent architectural kernel limit rather than a version bug, per coding guidelines it should be recorded as a FIXME(kernel-limit) comment at the invocation site (stating limit/origin/unverified status) rather than a hard guard with no way to "re-verify... on framework version bumps."

Based on coding guidelines: "Framework kernel limits must remain as FIXME(kernel-limit) comments at the owning collector invocation site, stating the limit, origin, and unverified status; do not encode them in YAML or implement guards from unverified claims."

Source: Coding guidelines


457-543: LGTM!

Also applies to: 568-613

collector/trtllm/collect_mamba2.py (1)

76-121: LGTM!

collector/trtllm/collect_mla.py (1)

58-67: LGTM!

collector/trtllm/collect_mla_module.py (1)

137-146: LGTM!

Also applies to: 213-240

collector/vllm/collect_mla_module.py (1)

145-153: LGTM!

Also applies to: 196-200

collector/cases/sm_exceptions/sm100_exceptions.yaml (1)

123-137: LGTM!

collector/vllm/collect_gemm.py (3)

57-63: 🎯 Functional Correctness | ⚡ Quick win

String-comparison fallback can misorder versions.

vllm_version >= target lexicographically compares strings, so e.g. "0.9.0" >= "0.22.0" evaluates True even though 0.9.0 predates 0.22.0. This fallback only fires if packaging.version.Version parsing fails, but when it does, it silently produces wrong routing between the new/old FP8 weight-prep paths.

🛠️ Proposed fix: use tuple-based numeric fallback
 def _vllm_version_at_least(target: str) -> bool:
     try:
         from packaging.version import Version
 
         return Version(vllm_version) >= Version(target)
     except Exception:
-        return vllm_version >= target
+        def _parts(v: str) -> tuple[int, ...]:
+            return tuple(int(p) for p in v.split(".")[:3] if p.isdigit())
+        return _parts(vllm_version) >= _parts(target)

179-203: 🎯 Functional Correctness | ⚡ Quick win

Verify per-tensor FP8 path also needs an explicit input_scale.

The fp8_block branch a few lines below (Line 225-226 in this file) explicitly sets gemm.input_scale = None because the dynamic activation scheme doesn't create it but the forward path reads it. This new per-tensor branch now also routes through quant_method.process_weights_after_loading(gemm) for the first time (previously it only transposed the weight), yet does not set input_scale. If the per-tensor cutlass kernel's apply() reads input_scale under dynamic activation, the dry-run forward at Line 244 could fail with an AttributeError.

Please confirm whether vLLM's per-tensor FP8 dynamic path (e.g. CutlassFP8ScaledMMLinearKernel.apply) requires input_scale to be set, and if so, add the same guard as the fp8_block branch.


185-200: LGTM!

@kaim-eng
kaim-eng force-pushed the pr2/collector-skip-guards branch from f5c8cb0 to 8f56d59 Compare July 14, 2026 16:25
@kaim-eng kaim-eng changed the title fix(collector): skip guards for hardware/version crash paths found during power collection fix(collector): remove exception-smuggling skip guards; document permanent kernel limits Jul 14, 2026

@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: 2

🤖 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 `@collector/trtllm/collect_attn.py`:
- Around line 4-10: Move the canonical FIXME(kernel-limit) comment to the
runtime invocation beside run_attention_torch, preserving the pinned TRT-LLM
version/source and GQA-limit details. Remove the module-scope and
generation-helper copies, and do not add the note back to YAML.
- Around line 4-10: Update the kernel-limit FIXME comment in collect_attn.py to
remove SM120 from the affected SM list, limiting the claim to the SMs covered by
this change; alternatively, explicitly mark SM120 as unverified while preserving
the existing verification and probe-or-delete guidance.
🪄 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: 43dfe789-6a03-46bc-a1ec-3c131daf0b97

📥 Commits

Reviewing files that changed from the base of the PR and between f5c8cb0 and 8f56d59.

📒 Files selected for processing (1)
  • collector/trtllm/collect_attn.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 (e2e)
  • GitHub Check: Build and Test (unit)
  • GitHub Check: Rust/Python engine-step parity
  • GitHub Check: Cargo Deny
🧰 Additional context used
📓 Path-based instructions (6)
{collector/**,tests/unit/collector/**}

📄 CodeRabbit inference engine (.claude/rules/collector/failure_handling.md)

{collector/**,tests/unit/collector/**}: In collector failure handling, observe failures rather than predict them: do not use a declarative expected-failure layer or automatic skips; failing groups must be fixed.
Record every worker failure automatically in the relevant error and collection-summary JSON files with classification, case parameters, exception details, and model/dtype grouping.
Reset a worker after CUDA-fatal errors only after recording the failed task.
Treat missing data points as tolerable downstream because the SDK can interpolate, extrapolate, reuse sibling-version rows, or use HYBRID empirical estimates.
For a hanging case or one that kills the node, add a dated denylist.yaml entry with a reason.
For an entirely unvalidated operation/backend pair, mark the registry OpEntry as unverified=true.
For cases not validated on a specific SM, mark the registry OpEntry with unverified_sms=(sm,).
Represent physically impossible hardware capabilities with a positive floor in capabilities.yaml; do not use it for framework-version kernel gaps.
Treat out-of-memory failures as unclassified until reproduced on a clean GPU; only a genuine OOM may be excluded by the sanctioned generation-time memory filter.
Fix proven collector-code bugs in code rather than using skips, and re-check dispatch and skip rules.
Do not encode framework-version gaps in YAML; allow them to fail and be re-tested after version changes.
Treat isolated, explained, unclustered failures as acceptable; investigate around 10% unexpected failures or sooner when failures cluster.
Treat roughly one-third failing cases or an entire failing family as a systemic collector problem; stop collection and fix the collector.
Never hide failures with broad skips, retries, generic OOM labels, reduced coverage, synthetic rows, or weakened benchmarks.
Compare failure records with the previous run for the same backend and version; prioritize only new failure groups.
Do not mechanically translate failure records in...

Files:

  • collector/trtllm/collect_attn.py
collector/**

📄 CodeRabbit inference engine (.claude/rules/collector/layer_permissions.md)

collector/**: Keep collector-layer rules in their designated locations: base-op YAML for sweeps and axis-level min_sm; model case YAML for structural shapes and activation; capabilities YAML for positive hardware floors; registries for version routing and maturity markers; collectors for dispatch, classified errors, and memory-feasibility filtering; and the denylist only for hangs or node-killing cases.
Collector tasks may modify only collector/ and tests/unit/collector/; they must not modify SDK, Rust, tools, systems data, or generator code.
Changing the collector data contract requires explicit human approval; do not independently add or rename columns, perf files, or SDK-parsed key dimensions.
Do not edit human-owned collector rule files under .claude/rules/collector/ as a side effect of a fix task; propose policy changes instead.
For a failing case, default to no code change: verify that it is recorded and classified in the failure log, then stop unless the failure-handling decision tree requires escalation.
Mechanism changes to the executor, case generator, failure classification, or case-ID format require explicit human approval and must be proposed rather than included in a case fix.

Review the collector when generated configuration formats change or when generator parameter names used in benchmark configurations change.

Files:

  • collector/trtllm/collect_attn.py

⚙️ CodeRabbit configuration file

collector/**: - Enforce the collector rules from .claude/rules/collector/layer_permissions.md, failure_handling.md, and case_authoring.md.

  • Flag any silent case skip in collector code (a queued case may only execute or raise); the sole sanctioned filter is generation-time memory feasibility with counted drops.
  • Flag invented fallbacks on both ends: generation must raise on unresolvable declarations (never substitute defaults or another model's geometry); collectors must never swap in a different backend/kernel than the framework's own dispatch selects — manual pins require framework source citations.
  • Flag any reintroduction of selector/exception machinery (case_ids/contains/indices/ranges/limit/rules, sm_exceptions-style shape or version predicates) in YAML or code.
  • Capability floors (cases/capabilities.yaml) may hold hardware facts only: no shapes, no framework versions, no per-backend nesting. cases/denylist.yaml is for hang/node-killers only, dated.
  • Collector changes must stay within collector/ and tests/unit/collector/; flag producer+consumer contract changes (perf row schema, PerfFile names) unless the PR explicitly declares them.
  • Check collector changes for backend/runtime version accuracy, GPU resource assumptions, reproducible command construction, and clear failure evidence.
  • Flag changes that make support-matrix or perf-data results harder to trace back to the command, model, system, quantization, or runtime version that produced them.

Files:

  • collector/trtllm/collect_attn.py
collector/**/collect_*.py

📄 CodeRabbit inference engine (.claude/rules/collector/layer_permissions.md)

collector/**/collect_*.py: Collector code may dispatch between kernel paths by SM or version, record the actual kernel_source, raise classified exceptions for runtime probes, and perform memory-feasibility filtering; it must not silently skip queued cases, perform other case filtering, or patch code for one shape.
A queued case must be executed or raise a classified error; branches may change how a case runs, but must not change whether it runs.
Use the framework's own dispatch path to select kernels. Manual backend pinning requires a source citation, and kernel_source must record the actually invoked kernel.
Do not invent backend fallbacks or swap backends in exception handlers. Raise a classified error if the framework-selected path cannot run; only replicate fallback behavior performed by the framework itself.
Measurement-method degradation, such as CUDA-graph capture falling back to eager execution, is allowed only when recorded in the output row; API-compatibility shims may alter construction but never kernel selection.
Memory-feasibility filtering is permitted only during generation inside get_*_test_cases(), using footprint-versus-capacity arithmetic and live device memory where possible; drops must be counted and logged, never silently skipped.
Framework kernel limits must remain as FIXME(kernel-limit) comments at the owning collector invocation site, stating the limit, origin, and unverified status; do not encode them in YAML or implement guards from unverified claims.
On framework version bumps, re-verify every pinned backend and every FIXME(kernel-limit) against framework source before trusting collected data.

Files:

  • collector/trtllm/collect_attn.py
collector/**/*

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

collector/**/*: When editing or reviewing collector/**, load and apply layer_permissions.md and failure_handling.md; also load case_authoring.md for case YAML work.
Do not apply generator-module rules to collector/case_generator.py; it expands collection test cases and is unrelated to deployment-config generation.
Collector work is standalone GPU performance data collection and is not part of the wheel runtime.

Files:

  • collector/trtllm/collect_attn.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

Use the project’s uv-managed environment and run linting with ruff check . and ruff format --check ..

Files:

  • collector/trtllm/collect_attn.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:

  • collector/trtllm/collect_attn.py
🧠 Learnings (1)
📚 Learning: 2026-02-28T11:44:28.109Z
Learnt from: tianhaox
Repo: ai-dynamo/aiconfigurator PR: 466
File: collector/trtllm/collect_moe_v3.py:4-4
Timestamp: 2026-02-28T11:44:28.109Z
Learning: In collector-related Python files (e.g., collector/trtllm/collect_moe_v3.py), document and enforce that version incompatibilities are surfaced as non-fatal runtime errors during collection. Do not add aggressive preventive version-gating; instead, allow generating test cases that may not be supported across all versions within the __compat__ range and rely on runtime error handling to skip or flag unsupported cases. This should be verifiable by ensuring collection proceeds, errors are reported, and unsupported cases do not halt the overall process.

Applied to files:

  • collector/trtllm/collect_attn.py

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 2

🤖 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 `@collector/trtllm/collect_attn.py`:
- Around line 4-10: Move the canonical FIXME(kernel-limit) comment to the
runtime invocation beside run_attention_torch, preserving the pinned TRT-LLM
version/source and GQA-limit details. Remove the module-scope and
generation-helper copies, and do not add the note back to YAML.
- Around line 4-10: Update the kernel-limit FIXME comment in collect_attn.py to
remove SM120 from the affected SM list, limiting the claim to the SMs covered by
this change; alternatively, explicitly mark SM120 as unverified while preserving
the existing verification and probe-or-delete guidance.
🪄 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: 43dfe789-6a03-46bc-a1ec-3c131daf0b97

📥 Commits

Reviewing files that changed from the base of the PR and between f5c8cb0 and 8f56d59.

📒 Files selected for processing (1)
  • collector/trtllm/collect_attn.py
🛑 Comments failed to post (1)
collector/trtllm/collect_attn.py (1)

4-10: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move the kernel-limit FIXME to the runtime invocation site. Keep one canonical FIXME(kernel-limit) beside run_attention_torch, include the pinned TRT-LLM version/source for the GQA limit, and remove the duplicate module-scope/generation-helper notes.

🤖 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 `@collector/trtllm/collect_attn.py` around lines 4 - 10, Move the canonical
FIXME(kernel-limit) comment to the runtime invocation beside
run_attention_torch, preserving the pinned TRT-LLM version/source and GQA-limit
details. Remove the module-scope and generation-helper copies, and do not add
the note back to YAML.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files collector/trtllm/collect_attn.py .claude/rules/collector/layer_permissions.md .claude/rules/collector/failure_handling.md .claude/rules/collector/case_authoring.md collector/cases/capabilities.yaml collector/cases/denylist.yaml
printf '\n--- collect_attn.py (top + relevant ranges) ---\n'
sed -n '1,40p' collector/trtllm/collect_attn.py
printf '\n--- collect_attn.py (around reported later lines) ---\n'
sed -n '400,540p' collector/trtllm/collect_attn.py
printf '\n--- collector layer rules (short excerpts) ---\n'
sed -n '1,220p' .claude/rules/collector/layer_permissions.md
printf '\n--- failure handling rules (short excerpts) ---\n'
sed -n '1,220p' .claude/rules/collector/failure_handling.md
printf '\n--- case authoring rules (short excerpts) ---\n'
sed -n '1,220p' .claude/rules/collector/case_authoring.md

Repository: ai-dynamo/aiconfigurator

Length of output: 28611


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- exact ranges around later FIXME sites ---\n'
nl -ba collector/trtllm/collect_attn.py | sed -n '410,430p'
printf '\n--- generation section around line 523 ---\n'
nl -ba collector/trtllm/collect_attn.py | sed -n '515,535p'
printf '\n--- all SM120 references in file ---\n'
rg -n "SM120|sm120" collector/trtllm/collect_attn.py

Repository: ai-dynamo/aiconfigurator

Length of output: 249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n--- around lines 410-430 ---\n'
sed -n '410,430p' collector/trtllm/collect_attn.py | cat -n
printf '\n--- around lines 515-535 ---\n'
sed -n '515,535p' collector/trtllm/collect_attn.py | cat -n
printf '\n--- all SM120 references in file ---\n'
rg -n "SM120|sm120" collector/trtllm/collect_attn.py

Repository: ai-dynamo/aiconfigurator

Length of output: 3318


Narrow the kernel-limit note to the covered SMs
Don’t include SM120 in this FIXME yet; keep the claim limited to the SMs this change covers, or add an explicit “SM120 unverified” caveat.

Suggested change
-# against TRT-LLM kernel-selection source. On SM100/103/120, TRT-LLM attention
+# against TRT-LLM kernel-selection source. On SM100/103, TRT-LLM attention
+# SM120 remains unverified and out of scope for this note.
📝 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.

# FIXME(kernel-limit): retired sm_exceptions rule (PR `#1302`), not verified
# against TRT-LLM kernel-selection source. On SM100/103, TRT-LLM attention
# kernels reportedly reject GQA ratios (num_heads/num_kv_heads) >= 32 unless
# the ratio is a multiple of 32. SM120 remains unverified and out of scope
# for this note; affected cases currently fail at runtime.
# On the next version bump: verify against the framework source, then either
# probe-and-raise before invocation or delete this note. Never move this back
# into YAML (see .claude/rules/collector/layer_permissions.md).
🤖 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 `@collector/trtllm/collect_attn.py` around lines 4 - 10, Update the
kernel-limit FIXME comment in collect_attn.py to remove SM120 from the affected
SM list, limiting the claim to the SMs covered by this change; alternatively,
explicitly mark SM120 as unverified while preserving the existing verification
and probe-or-delete guidance.

Source: Path instructions

kaim-eng and others added 3 commits July 14, 2026 12:49
…ring power collection

Each guard is pinned to the exact (SM version, framework version) combination
verified during B200/H200 power data collection. Guards named sm100 use
get_sm_version() == 100; SM120 coverage requires a separate predicate if
confirmed on that hardware.

TRT-LLM guards (rc15/rc17 on SM90/SM100):
- collect_attn.py: skip SM90 long-context GQA (cudaErrorIllegalAddress),
  SM90 FP8-KV generation (SIGABRT), and head_dim>256 / head_dim==192+SM>=90
  (MMHA std::terminate) — C++ abort paths Python cannot catch
- collect_mamba2.py: skip Nemotron Ultra 550B context on SM90+rc15
  (OOM/NoneType crash-loop)
- collect_mla.py: skip standalone DeepSeek MLA generation on SM90+rc15
  (AttentionOp::initialize SIGABRT); pinned to rc15 only
- collect_mla_module.py: skip DSA context prefix on SM100+rc15/rc17
  (CUDA context poison); skip GLM-5 DSA module on SM100+rc15/rc17
  (DeepGEMM row-alignment assert)

vLLM guards (0.22.0 on SM100):
- collect_gemm.py: route per-tensor FP8 through process_weights_after_loading
  (required by CutlassFP8ScaledMMLinearKernel in 0.22.0+); weight_scale
  attribute confirmed on B200/SM100 via smoke test
- collect_vllm/collect_mla_module.py: skip SM100 DSA context fp8_block
  (missing TRTLLM-GEN kernels / FlashInfer CUDA invalid argument)

Known-exception data:
- sm100_exceptions.yaml: classify GLM-5 DSA module failures on SM100+rc15/rc17
  as framework_version_unsupported (safety net for any gap in the module guard)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Kai Ma <kaim@nvidia.com>
…anent kernel limits

Per layer_permissions.md, framework-version and model-name conditions in
getter-level skip guards are "exception smuggling" — the only legal
responses to a queued case are execute-it or raise. The skip guards added
during the B200/H200 power collection run violate this policy and are
removed.

Failure routing under the observe-don't-predict doctrine:
- cudaErrorIllegalAddress (long-context GQA, FP8-KV generation): CUDA-fatal
  errors reset the worker and are recorded before the reset; run-and-record.
- Version-specific SIGABRTs (SM90 MLA generation, GLM-5 DSA DeepGEMM
  assert, SM100 DSA context prefix): version-specific so the hang denylist
  cannot encode them without permanently suppressing fixed versions;
  run-and-record until the framework fixes the underlying issue.
- Python-catchable failures (Mamba2 Ultra OOM/NoneType): run-and-record.

Two permanent kernel limits in TRT-LLM dense attention are documented with
FIXME(kernel-limit) comments at the iteration sites in collect_attn.py:
- head_dim > 256: "Head size N is not supported by MMHA" (std::terminate)
- head_dim == 192 on SM >= 90: "Unsupported HeadDim for BMM2-N 192" in
  TllmGenFmha (SM89 is unaffected)
Both abort before Python can catch them. Re-verify on each version bump.

The sm100_exceptions.yaml addition and all vLLM collector changes were
dropped earlier in this rebase (sm-exceptions engine retired in #1302;
vLLM collector upgraded to 0.24.0 in #1344).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Kai Ma <kaim@nvidia.com>
…un_attention_torch

Two head_dim kernel-limit notes were parked inside get_context_attention_test_cases()
and get_generation_attention_test_cases(), contrary to layer_permissions.md which
requires FIXME(kernel-limit) comments to live at the runtime invocation site so the
affected cases still run and fail at runtime. Consolidate into one canonical note
above create_attention() in run_attention_torch(); remove the getter copies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Kai Ma <kaim@nvidia.com>
@kaim-eng
kaim-eng force-pushed the pr2/collector-skip-guards branch from 87ac25b to a57e49f Compare July 14, 2026 16:49
…in collect_attn

Add TRT-LLM version (1.3.0rc15, SM100/SM90) and grep anchors to the
head_dim kernel-limit FIXME so the upgrade audit knows exactly where to
look to verify or retire the note.

Signed-off-by: Kai Ma <kaim@nvidia.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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