[GPU] Keep NMS boundary-sensitive subgraph in FP32 to fix Detectron2 FasterRCNN GPU extra detection#36723
[GPU] Keep NMS boundary-sensitive subgraph in FP32 to fix Detectron2 FasterRCNN GPU extra detection#36723yuanxion wants to merge 6 commits into
Conversation
…s 16) Signed-off-by: yuan.xiong <yuan.xiong@intel.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses a GPU accuracy regression in Detectron2 Faster R-CNN by preventing FP16 precision conversion on a boundary-sensitive NonMaxSuppression (NMS) tail subgraph, keeping that region in FP32 to avoid suppression differences that can lead to an extra detection.
Changes:
- Introduces a new GPU MatcherPass (
KeepNMSBoundaryPrecision) that disables FP16 precision conversion for the targeted NMS subgraph. - Registers the pass in the Intel GPU transformations pipeline before
ConvertPrecision. - Adds unit tests validating that the intended nodes are marked, and that non-targeted NMS nodes are not affected.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/plugins/intel_gpu/tests/unit/transformations/keep_nms_boundary_precision_test.cpp | Adds unit coverage for the new precision-keeping matcher pass. |
| src/plugins/intel_gpu/src/plugin/transformations/keep_nms_boundary_precision.hpp | Declares the new KeepNMSBoundaryPrecision matcher pass. |
| src/plugins/intel_gpu/src/plugin/transformations/keep_nms_boundary_precision.cpp | Implements graph matching and FP16 conversion disabling for the NMS boundary-sensitive chain. |
| src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp | Wires the new pass into the GPU transformation pipeline before ConvertPrecision. |
| namespace ov::intel_gpu { | ||
| namespace { | ||
|
|
||
| constexpr char kTargetNmsPrefix[] = "torchvision::nms/"; |
There was a problem hiding this comment.
I'm not sure relying on the operation name is the best idea
Are there any other special differences in the subgraph around the operation for this model?
Or maybe it's possible to change something in the cl kernel itself for more stable operation without significantly affecting the performance
There was a problem hiding this comment.
Yes, I agree. I changed it to use a matches_batched_nms_chain and check specific ops in this model instead.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: yuan.xiong <yuan.xiong@intel.com>
| ov::is_type<ov::op::v1::Reshape>(node) || ov::is_type<ov::op::v0::Unsqueeze>(node); | ||
| } | ||
|
|
||
| void mark_fp32_chain(const std::shared_ptr<ov::Node>& node, |
There was a problem hiding this comment.
Is it possible to use ov::mark_as_precision_sensitive instead of custom functions?
| register_matcher(matcher, callback); | ||
| } | ||
|
|
||
| } // namespace ov::intel_gpu No newline at end of file |
There was a problem hiding this comment.
nit: Please, add a empty line to the end of each new file
| ov::matcher_pass_callback callback = [this, boxes_offset_add_m, boxes_reshape_m, scores_unsqueeze_m, nms_m](Matcher& m) { | ||
| const auto& pattern_map = m.get_pattern_value_map(); | ||
|
|
||
| auto nms = ov::as_type_ptr<ov::op::v9::NonMaxSuppression>(pattern_map.at(nms_m).get_node_shared_ptr()); |
There was a problem hiding this comment.
Will the NMS version always be op::v9?
Apparently, the transformation is triggered before all NMS conversions occur src/plugins/intel_gpu/src/plugin/transformations_pipeline.cpp:997
|
As far as I understand, this shift in the |
Signed-off-by: yuan.xiong <yuan.xiong@intel.com>
| const auto has_only_static_dimensions = [](const PartialShape& shape) { | ||
| return shape.rank().is_static() && | ||
| std::all_of(shape.begin(), shape.end(), [](const Dimension& dimension) { | ||
| return dimension.is_static(); | ||
| }); | ||
| }; | ||
| if (!has_only_static_dimensions(data.get_partial_shape()) || | ||
| !has_only_static_dimensions(indices.get_partial_shape())) { |
There was a problem hiding this comment.
This change and introduced lambda !has_only_static_dimensions is equivalent to the previous !is_dynamic() check. Should be reverted as not needed.
| const auto& data_shape = gather->get_input_partial_shape(0); | ||
| if (data_shape.rank().is_dynamic() || | ||
| std::any_of(data_shape.begin(), data_shape.end(), [](const Dimension& dimension) { | ||
| return dimension.is_dynamic(); | ||
| })) { | ||
| return false; | ||
| } | ||
|
|
There was a problem hiding this comment.
This is exactly what is_dynamic() check does, moreover, static shape is ensured here by {any_input(has_static_shape()) in the gather_label pattern above L134.
|
Not sure what the bug really is("rounding fp16" error is too enigmatic for me), BUT if this bug is only related to numerical stability, it means that any nms can suffer from it. In this case, instead of having a special transformation for some cases, why won't we either: |
Signed-off-by: yuan.xiong <yuan.xiong@intel.com>
It appears the problem is caused by the box coordinates shifting to the edge of the FP16 range, when the interval between adjacent values becomes larger, and not a problem with some FP16 accumulator (@yuanxion, correct me if I'm wrong) |
|
|
Once again, I don't know root cause of the problem, but if is related to numerical stability, then basically there are two options:
Note that if this is only related to some internal calculations, all of them can be performed using fp32 internally in the kernel(so input data is fp16, but calculations are done inside kernel using fp32) - this should not affect the performance in any measurable way, except for the case where kernel is already under register pressure and starts to spill regs to global mem - but then there are other solutions to fix it. Note that if the problem is truly related to numerical stability of the kernel, this PR does not solve that problem - it only mitigates it for some cases. About speed/performance - IMO it doesn't matter if the kernel produces wrong outputs. First we have to make sure kernel is correct and then eventually optimize it. In this case, it looks like the kernel is not correct for fp16 data type, so I suggest to try to fix the kernel, not to introduce some new transformations. This is only suggestion, the decision is up to @yuanxion what to do with it. |
|
(sorry for closing and reopening this PR - done by mistake) |
Thanks, both comments make sense. Based on the debugging results, I believe this issue is caused by the batched NMS emulation pattern, not by an FP16 accumulator inside the NMS kernel. The class-dependent coordinate shift can push box values into an FP16-sensitive range, which makes the NMS decisions unstable. Replacing the emulation with real MulticlassNMS removes that failure mode directly. On performance, the model-level impact is negligible: MulticlassNMS is 125.17 ms vs 124.74 ms for the old NMS chain FP32 subgraph. I could not obtain reliable kernel/subgraph timing for this dynamic-shape GPU case with the current profiling paths. The MulticlassNMS transformation restores accuracy and preserves performance at model level. If we later identify a separate numerical issue inside the kernel itself, we can address that independently. |
Signed-off-by: yuan.xiong <yuan.xiong@intel.com>
Details
fixed a GPU accuracy regression in the Detectron2 Faster RCNN E2E case where GPU produces 17 detections while the reference output contains 16.
Description of the issue
Symptom
The Detectron2 Faster R-CNN E2E testcase on GPU shows a detection count mismatch: Reference output (16 detections) vs GPU output (17 detections). The failure happens in the final object-detection comparison stage.
Root cause
The extra GPU detection is not a random corruption or environment issue. It is a stable output difference caused by boundary-sensitive suppression behavior in the NMS tail. FP16 rounding in NMS can slightly change the comparison and cause one extra box to survive.
How to fix it
Use a new GPU matcher pass KeepNMSBoundaryPrecision to disables FP16 conversion NonMaxSuppression_1.
Reproduction step and snapshot
python -m pytest test_ovc_mo.py --tb=native --env_conf=.automation/env_config.yml --test_conf=.automation/test_configs/desktop_test_config_gpu_llm.yml -m "not launch_only_if_manually_specified" --pregen_irs=python_api_serialized/irs_mapping.csv --tf_models_version=1.15.2 --modules pipelines/production/pytorch/light/detectron2_faster_rcnn.py --dynamism_type=None --log-cli-level INFO
Problematic graph
Checklist
Tickets:
AI Assistance: