Skip to content

feat(onnx-to-hip): lower quantized integer conv and matmul - #1248

Open
pragyam32 wants to merge 14 commits into
mainfrom
feat/onnx-to-hip-quant-integer
Open

pragyam32 wants to merge 14 commits into
mainfrom
feat/onnx-to-hip-quant-integer

Conversation

@pragyam32

@pragyam32 pragyam32 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Lower ONNX DynamicQuantizeLinear, ConvInteger, and MatMulInteger through existing HIP ops so a quantized graph bufferizes and can run on both the HIP and rocMLIR paths.
  • DynamicQuantizeLinear becomes single-axis reductions, scale and zero-point math, and hip.quantize_linear with axis 0. ConvInteger and MatMulInteger accumulate in f32 when 65025 * K fits in the 24-bit mantissa, and in i32 otherwise.
  • A size-1 batch on A is expanded before hip.matmul. A rank-2 B, and a B whose batch dimensions are all 1, stays one shared matrix. A dynamic leading batch is preserved for ConvInteger. For MatMulInteger, a static rank-2 B may also be shared across a dynamic M and any other leading dimension. An uninferred K on A accumulates in i32. Add int8-to-f32 and int8-to-i32 casts so signed inputs of those integer ops have a kernel.

Related issue or design

None.

Why

A quantized graph failed one-shot bufferization on unconverted onnx.ConvInteger, onnx.DynamicQuantizeLinear, and onnx.MatMulInteger. With CPU fallback disabled, the execution provider skipped the graph. These ops decompose into HIP ops that already lower through hipDNN/hipBLASLt and through TOSA on the rocMLIR path.

hip.qlinear_conv and hip.qlinear_matmul are a different contract: they requantize. Graphs that use ConvInteger and MatMulInteger already cast, scale, and add bias afterward, so those fused ops would apply the scales twice.

A data-dependent leading batch also left both integer ops unconverted. The convolution and matmul runtimes already read that extent from the memref descriptor. MatMulInteger was still rejected when M, or any later leading dimension, was dynamic. The matmul runtime already reads M and the batch product from A's descriptor. B's K and N stay static, so the result shape and a per-column zero point remain compile-time values. When A's K is dynamic, the reduction length has no compile-time bound and the accumulator stays i32.

What

  • DynamicQuantizeLinear (uint8 only): chain of single-axis ReduceMin / ReduceMax to a scalar, scale (max - min) / 255 with a zero range selecting scale 1, ties-to-even zero point, then hip.quantize_linear at axis 0. Axis 0 is in range for a vector. ReduceMin and ReduceMax accept f32, which this decomposition reduces. The f32 reduction clears the HIP last-error state before launch.
  • A constant single reduce axis supplies inner_size. With keepdims=0, matching trailing extents treats a repeated dimension as the inner suffix and reduces the wrong axis. [1,6,5,5] reduced on the last axis now passes inner_size=1, not 5.
  • ConvInteger: rank-3 NCL and rank-4 NCHW. Rank-3 widens to the shared rank-4 NC1L hip.conv path and collapses the result. The batch may be dynamic when the input and result agree and the channels, spatial dimensions, and weights are static. The activation zero point is a scalar. The weight zero point is a scalar or one value per output channel, reshaped to [M, 1, 1, 1] so the 4D broadcast does not place it on W. An integer convolution is not rewritten to hip.gemm.
  • MatMulInteger: rank 2-4. A size-1 batch on A is expanded to the result batch, because hip.matmul counts batches from A. A rank-2 B, and a higher-rank B whose batch dimensions are all 1, stays one shared [K, N] matrix. Copying it once per batch would allocate a separate weight for every batch, and both the i32 kernel (batch stride 0) and tosa.matmul (A's batch folded into M) already reuse it. B is expanded only when a batch dimension is larger than 1. A static rank-2 B may be shared across dynamic batch and sequence dimensions, including a dynamic M. B's K and N stay static. An uninferred K on A still lowers and accumulates in i32; that runtime length must match B's K. A per-row zero point requires a static M, because its length is M. A per-column zero point is reshaped to [..., 1, N].
  • An 8-bit value minus an 8-bit zero point is at most 255 in magnitude, so each product is at most 65025. The f32 accumulator is used only when the reduction length is at most 258. A longer reduction casts to i32 and accumulates in int64 before storing a wrapping int32. A uint8 dot product of length 259 filled with 255 is 16,841,475.
  • Wide i32 matmul and convolution stay in main_graph under the rocMLIR outline pass, so they lower through wrap_matmul_i32 and wrap_conv. HIP-to-TOSA still builds those ops with explicit zero points and an i32 accumulator when a kernel asks for them.
  • hip_cast clears the HIP last-error state before launch and returns hipGetLastError() afterward. The int32 matmul and conv launches do the same.

Test plan

  • LIT conversion: test_dynamic_quantize_linear.mlir, test_conv_integer.mlir, test_matmul_integer.mlir, including rank-3 NCL ConvInteger, a dynamic batch on rank-3 and rank-4 ConvInteger, a dynamic leading batch on MatMulInteger, a dynamic M (1x?xK), a dynamic prefix (?x?xK), an uninferred K (?x?x?) that stays on the i32 accumulator, a rejected per-row zero point when M is dynamic, axis 0, a size-1 batch expanded on A, a rank-2 B left unexpanded, a size-1 batch on B collapsed to a shared matrix, and the i32 wide-K forms
  • LIT pipeline (--hipdnn-pipeline): test_conv_integer_1d.mlir, test_conv_integer_dynamic.mlir, test_quantized_conv_model.mlir, test_matmul_integer_model.mlir, test_matmul_integer_dynamic.mlir, test_matmul_integer_dynamic_sequence.mlir, test_matmul_integer_dynamic_k.mlir, test_conv_integer_wide.mlir (wrap_conv, no wrap_gemm), and test_matmul_integer_wide.mlir (wrap_matmul_i32, no hipBLASLt)
  • HIP-to-LLVM: an i32 matmul with A [2, 1, 2] and rank-2 B passes batch stride 0
  • HIP-to-LLVM: a constant last axis on [1,6,5,5] -> [1,6,5] passes inner_size=1, and axis 1 of [4,4,4] -> [4,4] passes inner_size=4 (test_reduce_min.mlir). test_reduce_sum.mlir and test_reduce_mean.mlir still pass
  • HIP-to-TOSA with verification: i32 conv and batched i32 matmul check explicit zero-point operands and an i32 accumulator
  • --rocmlir-pipeline on a wide i32 matmul and convolution leaves both ops in main_graph (test_integer_stays_on_hip.mlir)
  • --hip-fusion-transform: a 259-channel 1x1 i32 convolution stays hip.conv (patch_embed_conv.mlir)
  • GPU numeric, CPU fallback disabled: length-259 matmul with several rows, columns, and distinct per-batch weights; the same shape reusing one rank-2 weight; a 259-channel 1x1 convolution; a grouped, padded convolution with reduction 261, stride 2, and several output positions and channels; an all-zero quantize whose scale is 1 and zero point is 0; and f32 ReduceMin/ReduceMax. All passed.

Notes for reviewers

  • Rank-1 MatMulInteger (numpy's vector promotion) and rank above 4 stay unconverted. hip.matmul requires rank >= 2, and hip.sub rejects rank > 4. ConvInteger supports rank-3 or rank-4; rank-3 uses the shared rank-4 kernel path. The int32 convolution kernel is spatial rank 2. A dynamic channel, spatial, or non-leading batch dimension stays unconverted for ConvInteger. A dynamic K or N on B, and a per-row zero point with a dynamic M, stay unconverted for MatMulInteger.
  • Cursor Grok 4.7 drafted the conversions, the Copilot fixes, the tests, and this description.

Checklist

  • The change is focused, or links a design/series explaining its scope.
  • Relevant tests were added or updated and the results are documented.
  • User-facing or design documentation was updated when needed.
  • Substantial AI assistance is disclosed, and I reviewed and understand the result.

@github-actions

Copy link
Copy Markdown

Thanks for opening a PR!

This project follows LLVM's incremental-development and AI-tool-use
guidance. See CONTRIBUTING.md
for the project workflow.

Before requesting review, please check that:

  1. The change is focused. Substantial work links the relevant issue
    or design discussion.
  2. The PR documents relevant test results and updates affected
    documentation.
  3. If AI tools provided substantial assistance, the description
    explains what was assisted and how it was validated, and commit
    trailers identify the tool. The contributor has reviewed and
    understands the result.

Reviewers are assigned through
CODEOWNERS where ownership
is configured.

@github-actions github-actions Bot added the large-pr Soft size threshold (>500 LOC or >10 files). Reviewer signal, not a block. label Oct 15, 2026
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

L2 Accuracy Results (EP vs CPU)

Model Combined L2 Total Elems Skipped NaN/Inf
conv_test_hybrid 1.12256E-07 64 0
GroupQueryAttention_seq256 59.2561 2621440 0
MatMulNBits_o_seq128 259.905 368640 0
QMoE_seq128 24.2583 368640 0

Threshold: 0.01 | Run: 7911 - Commit: 8232623

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Binary sizes

Baseline: run 40294400486 - 7579ebf (dll-sizes)

File This (MB) Main (MB) Delta (MB)
hipgpu.dll 59.34 59.31 +0.03
custom_kernels_gfx1170.dll 5.56 5.55 +0.01
custom_kernels_gfx11-generic.dll 5.81 5.79 +0.02

MorphiZen EP Performance Results

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 8.76 7.33 252 3 1159
GroupQueryAttention_seq128 11395.00 2.6021 1 5 230
matmul_down_seq128 514.19 3.29 125 3 275

EPContext Export Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 8.82 48.87 253 3 15506

EPContext Import Performance

Model QPS Session (s) 1st Infer (ms) CPU% Mem (MB)
full_model_seq128 8.83 10.15 253 3 15639

OGA Benchmark Results

Model Warmup Reps Prompt Len Gen Tokens TTFT (ms) TPS Peak Mem (GB) GPU Mem (GB)
gpt-oss-20b-webgpu-int4-rtn-block-32 1 5 128 128 92.2 74.8 1.25 12.24
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 1 5 128 128 117.1 47.6 1.15 5.23

OGA Wheel Smoke (Python benchmark_e2e.py)

Model TTFT (ms) TPS
Llama-3.1-8B-awq-g128-int4-asym-fp16-onnx-dml 118 48.1

VLM Benchmark Results

Model TTFT (ms) TPS Output
gemma-4-E4B-it-fp16-int4-kquant-prune-logits 4010.6 7.3 This is a high-quality, outdoor photograph featuring a beautiful, fluffy white dog, likely a West Highland White Terrier or a si
Qwen3.8-27B-fp16-ve-fp16-int4-k_quant-gs128-text-dml-onnx 14691.8 14.0 The user wants a detailed description of the image. Let me carefully observe all elements before writing. Subject: A white do

Run: 7911 - Commit: 8232623

Copilot AI 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.

🟡 Changes recommended

Unresolved runtime support and numerical correctness issues prevent reliable execution of the newly supported operations.

4 open issues
What changed in this PR

Adds lowering for quantized ONNX operations through existing HIP operations, targeting native HIP and rocMLIR execution.

Changes:

  • Decomposes dynamic quantization, integer convolution, and integer matrix multiplication.
  • Adds signed int8-to-f32 casting and clears stale launch errors.
  • Adds conversion, pipeline, and numeric tests plus support documentation.
File Description
test/​numeric/​tests/​test_quantized_conv.py Adds quantization and integer convolution checks.
test/​numeric/​tests/​test_matmul_integer.py Adds integer matrix multiplication checks.
test/​lit/​e2e/​test_quantized_conv_model.mlir Checks convolution pipeline lowering.
test/​lit/​e2e/​test_matmul_integer_model.mlir Checks matrix multiplication pipeline lowering.
test/​lit/​Conversion/​onnx-to-hip/​test_matmul_integer.mlir Tests matrix multiplication conversion.
test/​lit/​Conversion/​onnx-to-hip/​test_dynamic_quantize_linear.mlir Tests dynamic quantization conversion.
test/​lit/​Conversion/​onnx-to-hip/​test_conv_integer.mlir Tests integer convolution conversion.
lib/​Runtime/​Kernels/​hip/​cast_kernel.hip Adds signed casting and error-state clearing.
lib/​Conversion/​OnnxToHip/​OnnxToHipUtils.h Declares conversion registrations.
lib/​Conversion/​OnnxToHip/​OnnxToHip.cpp Registers new conversions.
lib/​Conversion/​OnnxToHip/​MatMulIntegerConversion.cpp Implements integer matrix multiplication decomposition.
lib/​Conversion/​OnnxToHip/​DynamicQuantizeLinearConversion.cpp Implements dynamic quantization decomposition.
lib/​Conversion/​OnnxToHip/​ConvIntegerConversion.cpp Implements integer convolution decomposition.
lib/​Conversion/​OnnxToHip/​CMakeLists.txt Builds new conversion sources.
docs/​supported-operations.md Documents added operations and limitations.

Review effort: Balanced

Comment thread lib/Conversion/OnnxToHip/DynamicQuantizeLinearConversion.cpp
Comment thread lib/Conversion/OnnxToHip/MatMulIntegerConversion.cpp Outdated
Comment thread lib/Conversion/OnnxToHip/MatMulIntegerConversion.cpp Outdated
Comment thread lib/Conversion/OnnxToHip/DynamicQuantizeLinearConversion.cpp Outdated

Copilot AI 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.

Comment thread lib/Conversion/HipToLLVM/MatmulLowering.cpp Outdated
Comment thread lib/Conversion/OnnxToHip/ConvIntegerConversion.cpp
Comment thread lib/Conversion/HipToTosa/HipToTosa.cpp
Comment thread lib/Runtime/Kernels/hip/reduce_sum_kernel.hip

Copilot AI 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.

🔵 Needs a closer look

New GPU kernels and cross-backend lowering still need human review and GPU numeric validation.

4 open issues
2 previously missed issues

In code that hasn't changed since last review

Medium severity Test all-zero DynamicQuantizeLinear scale behavior

lib/​Conversion/​OnnxToHip/​DynamicQuantizeLinearConversion.cpp:214

The only DynamicQuantizeLinear numeric input spans zero, so it never exercises the scale=1 branch. Add an all-zero input case asserting scale=1, zero_point=0, and an all-zero quantized output. The conversion-only FileCheck tests do not validate these numerical results.

Medium severity Avoid expanding rank-2 B across batches

lib/​Conversion/​OnnxToHip/​MatMulIntegerConversion.cpp:296

Preserve rank-2 B instead of copying it into every batch. For A [32, 1, 4096] and B [4096, 4096], this expansion materializes a 2 GiB i32 weight buffer rather than sharing the 64 MiB matrix, adding substantial memory use and copy traffic. MatmulLowering.cpp already supports rank-2 B with batch stride 0, and HipToTosa.cpp supports it by flattening A's leading dimensions into M. Expand B only when it has batch dimensions.

2 resolved since last review

Review effort: Balanced

Comment thread lib/Runtime/Kernels/hip/matmul_i32_kernel.hip

Copilot AI 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.

🔵 Needs a closer look

The new GPU kernels and precision-dependent execution paths still require hardware numeric validation and final human review.

4 open issues

Review effort: Balanced

Copilot AI 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.

🟡 Changes recommended

A cached-reference test failure and avoidable shared-weight replication remain unresolved.

5 open issues
1 previously missed issue

In code that hasn't changed since last review

Medium severity Avoid expanding weights with size-one batch dimensions

lib/​Conversion/​OnnxToHip/​MatMulIntegerConversion.cpp:303

B with only size-1 batch dimensions is also one shared matrix, but this branch copies it across every result batch. For A=[32,1,4096] and B=[1,4096,4096], the expanded i32 weight buffer is 2 GiB instead of 64 MiB, potentially causing an avoidable out-of-memory failure. Reshape B to rank 2 when all its batch dimensions are 1; LLVM lowering can then use batch stride 0, and TOSA can use its shared-matrix path. Keep expansion for other batched shapes.

Review effort: Balanced

Comment thread test/numeric/tests/test_quantized_conv.py

Copilot AI commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

@copilot
High severity #1248 (comment)
High severity #1248 (comment)
...

Reviewed the four linked findings. Their fixes and regression coverage are already present in the branch: 7d5f4b9 adds f32 reduction dispatch and DynamicQuantizeLinear coverage; 86eb960 adds i32 HIP-to-TOSA and rocMLIR-path coverage; 5183a42 covers wide-K integer numerics and prevents i32 convolution fusion. pre-commit run --all-files passes. No additional diff was needed, so I did not create an empty commit; the latest branch commit is 0f0cae8.

Copilot AI 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.

🔵 Needs a closer look

Cross-backend lowering and new integer GPU kernels require human validation, including broader convolution indexing coverage.

1 open issue
4 resolved since last review

Review effort: Balanced

Comment thread lib/Runtime/Kernels/hip/conv_kernel.hip

Copilot AI 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.

🟡 Changes recommended

A critical DynamicQuantizeLinear correctness issue and a missing wide-K signed INT8 validation case remain unresolved.

2 open issues

Review effort: Lite

Comment thread lib/Conversion/OnnxToHip/DynamicQuantizeLinearConversion.cpp
asparkhi
asparkhi previously approved these changes Oct 21, 2026
Pragya0302 and others added 10 commits October 21, 2026 10:51
Decompose DynamicQuantizeLinear, ConvInteger, and MatMulInteger into existing HIP ops so a quantized graph can bufferize on both the HIP and rocMLIR paths.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
DynamicQuantizeLinear used an out-of-range axis on a vector, ReduceMin and ReduceMax rejected f32, and MatMulInteger checked batch broadcast without expanding it. An f32 dot product also drops bits once the reduction passes 258, so longer ConvInteger and MatMulInteger reductions now accumulate in int32.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
An i32 matmul passes B's batch stride so a shared matrix is not read past its end, and an integer convolution is not rewritten into hip.gemm. Wide i32 anchors stay on the HIP kernels. The f32 reduction clears the HIP last-error state before launch.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
Copying a rank-2 B into every batch allocates a separate matrix for a weight hip.matmul already reuses. Wide batched i32 matmul and an all-zero quantize now have exact numeric tests.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
The wide batched i32 matmul and the all-zero quantize now assert the execution provider output. A 259-channel i32 convolution stays hip.conv through the fusion pass.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
Co-authored-by: nvunnam57128 <261134987+nvunnam57128@users.noreply.github.com>
The i32 conv kernel was only checked on a 1x1 window. A reduction of 261 with stride, padding, two groups, and several output positions now checks the execution provider result.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
keepdims=0 drops the reduced axis, so trailing shape alignment treats a repeated extent as the inner suffix and reduces the wrong dimension.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
Widen 1-D NCL ConvInteger to the shared NC1L hip.conv path and collapse the result so int8 encoder convolutions bufferize.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
A data-dependent leading batch left ConvInteger and MatMulInteger unconverted. The runtime already reads that extent from the memref descriptor, so those ops can bufferize.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
@pragyam32
pragyam32 force-pushed the feat/onnx-to-hip-quant-integer branch from 8703c7d to f3661fb Compare October 21, 2026 16:55
Pragya0302 and others added 2 commits October 21, 2026 13:02
A static rank-2 weight can be shared when the batch or M axis is dynamic. An uninferred K on A still accumulates in i32, and a per-row zero point still needs a static M.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
…t-integer

Keep the int32 matmul declaration alongside the channels-last causal convolution and GatedDeltaNet declarations.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Kernel Unit Test Results

Op Leaf Arch Cases Failed Suite (ms) Verdict
matmul_nbits Xfp16_Wu2_Yfp16 gfx1151 12 0 6634.7 PASS
matmul_nbits Xfp16_Wu3_Yfp16 gfx1151 12 0 6731.2 PASS
matmul_nbits Xfp16_Wu4_Yfp16 gfx1151 12 0 4328.4 PASS
matmul_nbits Xfp16_Wi8_Yfp16 gfx1151 12 0 5766.5 PASS
matmul_nbits Xfp32_Wu2_Yfp32 gfx1151 12 0 6852.7 PASS
matmul_nbits Xfp32_Wu3_Yfp32 gfx1151 12 0 6825.5 PASS
matmul_nbits Xfp32_Wu4_Yfp32 gfx1151 12 0 4384.9 PASS
matmul_nbits Xfp32_Wi8_Yfp32 gfx1151 12 0 5659.6 PASS
gqa/decode Xfp16_Wfp16_Yfp16 gfx1151 17 0 3519.1 PASS
gqa/decode Xfp16_Wi8_Yfp16 gfx1151 13 0 2493.7 PASS
gqa/prefill Xfp16_Wfp16_Yfp16 gfx1151 26 0 5243.8 PASS
gqa/prefill Xfp16_Wi8_Yfp16 gfx1151 9 0 3735.5 PASS
qmoe_ragged_mm Xfp16_Wu4_Yfp16 gfx1151 40 0 1547.6 PASS
gather_block_quantized Xi64_Wu4_Yfp16 gfx1151 32 0 567.6 PASS
gather_block_quantized Xi64_Wu8_Yfp16 gfx1151 32 0 419.6 PASS
gather_block_quantized Xi64_Wfp8_Yfp16 gfx1151 24 0 377.4 PASS

Leaves: 16 (0 failed) | Cases: 289 (0 failed)

Mode: lookup | Coverage: 1 | Run: 7911 - Commit: 8232623

asparkhi
asparkhi previously approved these changes Oct 23, 2026
…t-integer

Keep the int32 matmul declaration alongside the DynamicSparseAttention and SparseAttentionIndexer declarations.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
…t-integer

Keep main's reduced_axes stride and the constant axes-memref fallback. Shape inference stays the last resort, so a repeated extent still selects the axis named by the constant memref.

Co-Authored-By: Cursor Grok 4.7 <cursoragent@cursor.com>
Made-with: Cursor
Co-authored-by: Cursor <cursoragent@cursor.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

large-pr Soft size threshold (>500 LOC or >10 files). Reviewer signal, not a block.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants