Improved performance in torchvision backend #1374
Replies: 6 comments 9 replies
Torch boolean mask pathThe current torchvision backend computes a dense For matrix[i, j] >= match_thresholdTriton packed bitmask pathThe Triton path goes one step further than the torch boolean-mask path. Instead of materializing a dense float32 matrix or a dense boolean matrix, the Triton kernel computes the thresholded IOS match result directly and packs the result into integer bitsets. Each |
|
@ZephyrKeXiner hey, can you open PR so I can check and test as well, it looks promising in first glance, but let me also test as well plus if you have dense example that would be also cool for test purpose as well (plus model if needed) |
|
Hi all, I have a quick question/discovery that might be related to this discussion/issue. I am mainly using the I am combining 33337 labels from 9866 images using NMM. torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 4.14 GiB. GPU 0 has a total capacity of 3.68 GiB of which 3.58 GiB is free.Using So I am wondering if this issue is related to the topic, why there is a performance downgrade from Just wanted to let you know about my findings. If you have further questions let me know. |
|
So, this is the reduced code snipped i am using: import time
import geopandas
import numpy
from sahi.postprocess.combine import NMMPostprocess
from sahi.prediction import ObjectPrediction
from shapely import box
# SAHI v0.12 only
# from sahi.postprocess.backends import set_postprocess_backend
# _ = set_postprocess_backend("numpy")
def reduce_overlapping_labels(
label_polygons: geopandas.GeoDataFrame,
label_column: str = "yolo_label",
score_column: str = "yolo_confidence",
threshold: float = 0.5,
metric: str = "IOU",
class_agnostic: bool = True,
):
# convert label_polygons into list of ObjectPrediction objects
obj_pred_list = [
ObjectPrediction(
bbox=numpy.array(row.geometry.bounds),
category_id=int(row[label_column]),
score=row[score_column],
)
for _idx, row in label_polygons.iterrows()
]
# set postprocess based on selected method
postprocess = NMMPostprocess(
match_threshold=threshold,
match_metric=metric,
class_agnostic=class_agnostic,
)
# postprocess obj_pred_list
obj_pred_list = postprocess(obj_pred_list)
# convert back to separat tuples for polygons, labels and confs
polygons, labels, confs = zip(
*[(box(*(obj.bbox.to_xyxy())), str(obj.category.id), float(obj.score.value)) for obj in obj_pred_list],
strict=True,
)
# convert to GeoDataFrame
reduced_label_polygons = geopandas.GeoDataFrame(
data={"yolo_label": list(labels), "yolo_confidence": list(confs)},
geometry=list(polygons),
crs=label_polygons.crs,
)
return reduced_label_polygons
label_polygons = geopandas.read_file("~/YOLO_Label_Example.geojson")
t1 = time.time()
results = reduce_overlapping_labels(
label_polygons=label_polygons,
label_column="yolo_label",
score_column="yolo_confidence",
threshold=0.3,
metric="IOS",
class_agnostic=True,
)
t2 = time.time()
print(f"reducing overlapping labels: {t2 - t1}")This is the example file YOLO_Label_Example.zip
Thank you for looking into the issue. Let me know if you need further information. |
|
The quadratic allocation is fixed in @steezbert your report was the same root cause, so your case is covered too. What changed@ZephyrKeXiner your diagnosis was right, and it went further than the transfer cost. The greedy loops only ever use
Numbers@steezbert's 33337 boxes, CPU:
CUDA:
The OOM reproduced exactly as reported before the fix, at the same 4.14 GiB. Where this leaves the Triton ideaWorth being straight about: your benchmark measured Triton against the dense matrix path, and that path no longer runs for the sizes where Triton won. At 10000 boxes you had 618.5ms for the dense matrix and 73.1ms for the packed bitmask. The sparse path is now in that same range on CPU alone, so the comparison needs redoing against There is a real gap it could still fill. Above the cutoff the TorchVision backend now delegates to the NumPy sparse functions, so it does not touch the GPU at all for large inputs. A kernel that produces the thresholded adjacency on-device, in the packed form you described, would put the GPU back to work without ever materializing a dense matrix, and the CSR consumer is already in place for it to feed. Two things to watch if you pick it up. The STRtree build is now the dominant cost for A PR is still very welcome, benchmarked against |
|
Hi @onuralpszr, First of all, thank you for the elegant solution to the OOM issue. The sparse backend avoids materializing a dense O(N²) overlap matrix for large inputs. I noticed that when there are at least 2,000 bounding boxes, post-processing paths other than NMS with IoU fall back to the CPU-based sparse implementation when match_threshold > 0. I have not benchmarked the runtime cost yet, but I am wondering whether a GPU-friendly sparse or tiled algorithm could retain the memory benefits while improving performance on GPU-equipped systems. Do you think such an optional GPU backend would fit SAHI’s design? |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Hi maintainer @onuralpszr,
While benchmarking the postprocess backends, I noticed a potential bottleneck in the torchvision backend.
In this step, the backend computes a dense
N x NIoU/IoS matrix on GPU and then copies the whole matrix back to CPU. For large numbers of boxes, this transfer can become expensive. For example,10,000 x 10,000float32 values require about 400 MB to transfer.Instead of transferring the full dense matrix, I tried using a packed bitmask representation. The Triton implementation computes the thresholded IOS matches directly and returns a packed
uint32bitmask, which is then consumed by the greedy merge loop on CPU.On a Tesla T4, this gives a clear speedup for large
GreedyNMM + IOScases:Parity with the existing NumPy result is preserved in the benchmark.
The Triton packed-bitmask path is not beneficial for small inputs, but becomes the fastest option from around 2k boxes and gives up to 8.5x speedup over the current torchvision matrix path at 10k boxes.The median speedup is significant, although the Triton path currently shows higher variance for larger inputs. Even at p90, it remains faster than the dense matrix path.
All reactions