PR #24845 opened by Jake (jakefineman)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24845
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24845.patch
This is one commit on upstream master e9dc8fd4d6. It doesn't depend on any open
PR. Only libavfilter/dnn/dnn_backend_openvino.c changes, and the change removes
6 lines. I produced the evidence below on source tree 6893a38ddc, which is this
commit's tree (`git rev-parse HEAD^{tree}`).
Problem. With input_resizable=1, get_output_ov() first calls
ov_partial_shape_create(4, dims, &partial_shape). It then reads the model's
input shape, patches in the frame's height and width, and calls
ov_shape_to_partial_shape(input_shape, &partial_shape) on the same variable.
ov_shape_to_partial_shape() allocates a new dims array and overwrites the
struct, so the array from ov_partial_shape_create() is lost.
ov_partial_shape_free() after the reshape only frees the second array. The
first one leaks on every call: on the success path, and on the error returns
after ov_const_port_get_shape() or ov_shape_to_partial_shape() fails. Nothing
ever reads the dims it was built from ({1,1},{1,1},{h,h},{w,w}). dnn_processing
calls get_output_ov() once per filter instance when it configures its output,
so each instance leaks one block. The leak dates from the OpenVINO API 2.0
support in e79bd1f1b1.
Fix. Remove the ov_partial_shape_create() call and the unused dims array. The
partial shape is still built from the model's input shape by
ov_shape_to_partial_shape() and freed after ov_model_reshape_single_input(), as
before. The legacy (non-API-2.0) path is unchanged.
Evidence. I tested on an Apple M1 Ultra (macOS) with Homebrew OpenVINO 2026.4.0
and two release builds with symbols kept: unmodified e9dc8fd4d6 ("before") and
the same tree plus this commit ("after"). The two source trees differ only in
this file. Both builds print the same 33 compiler warnings, so the change adds
none. `make fate-source` passes on "after".
Leak, measured with macOS leaks(1) and MallocStackLogging=1. Each run is
dnn_processing on the OpenVINO backend with candy-9.onnx for 2 frames:
- before, input_resizable=1, 1 filter instance: 1 leak, 80 bytes. Its root
allocation is in ov_partial_shape_create(), called from get_output_ov() (the
call is on line 1293 of dnn_backend_openvino.c; leaks(1) symbolicates the
return address as line 1294).
- before, input_resizable=1, 3 filter instances: 3 leaks, 240 bytes, all from
ov_partial_shape_create().
- before, input_resizable=0: 0 leaks.
- after, in all three configurations: 0 leaks, 0 bytes.
Output. I ran the whole clip (8830 frames, 1280x720) through dnn_processing on
two real ONNX style-transfer models from onnx/models, candy-9 and mosaic-9,
with -threads 1 and framemd5 output. Each model ran with input_resizable=1, the
changed path, at the clip's native 1280x720. Each also ran with
input_resizable=0 as a control, after scale=224:224 to match the model's fixed
input.
| model | input_resizable | size | frames | before vs after | framemd5 sha256 |
|---|---|---|---|---|---|
| candy-9 | 1 | 1280x720 | 8830 | identical |
345042e73c107c9e08fcb41f92d823fe54853bd2635c1462ce7ae2ef628ad3f0 |
| mosaic-9 | 1 | 1280x720 | 8830 | identical |
6d0299fd759ee5b27ab76542a1f63b1f84fbc9c74facde1578ec366db2c0d551 |
| candy-9 | 0 | 224x224 | 8830 | identical |
3438794d2f30455a04664f237ebddfd92717c61d66efe9ce9bf6f15ad0ab8d89 |
| mosaic-9 | 0 | 224x224 | 8830 | identical |
f5037ede96cbe20bcac989ea1f99403bbddd8af7cfae7cea2d7cf6d62dfbd8bc |
All 8 runs exited with status 0.
A no-filter baseline shows that the models really change the picture: none of
the 8830 frames of any of the four outputs matches the unfiltered frame at the
same size.
One script regenerates every number above:
[ov-pshape-leak-evidence.tar.gz]([ATTACH:ov-pshape-leak-evidence.tar.gz])
(sha256 baa68d05744e120c76fd6a275dac79489fc65c0d55201856098710ed209ffbc7). It
contains run.sh, build.sh, fix.diff, the raw leaks(1) logs, all framemd5 files
and their logs, and inputs.sha256. The numbers above come from run.sh.as-run,
which the tarball also contains. That version counted every line naming the
leak root, so its leaks summary printed 2 and 5 for what leaks(1) itself
reports as 1 and 3 leaks. run.sh counts one per leaked block, and
results/leaks-summary.txt applies that count to the same raw logs. Since that
run, run.sh also downloads the clip and checks its hash if no local copy
exists. Nothing else changed. The script downloads the models and the clip and
checks their sha256 before it uses them:
- candy-9.onnx: 9d11a3529d1e547da6ae07201d93484dbab2ec0a3614535752c8f40f0fe2968a
- mosaic-9.onnx:
fa646dedade881243f8d5a2ceb7de2b93675b21fc24f7482894ac4851a9a0a47
- the clip, "National Puppy Day WKC Dog Show moments to celebrate (FOX
SPORTS).ogv" from Wikimedia Commons, CC BY 3.0:
7c5d64800dd9a4af693bd4ebd0be5b0a52bbea40fe893a22d3179a2f373fd725
Not tested:
- The two error returns before the reshape: failure of
ov_const_port_get_shape() or of ov_shape_to_partial_shape(). I didn't inject
faults into the OpenVINO C API. The claim that they leaked comes from reading
the code: the ov_partial_shape_create() allocation is still live on both paths
and nothing frees it. After the fix, nothing is allocated before those returns.
- Any OS other than macOS, any OpenVINO version other than 2026.4.0, and any
device other than CPU.
- The legacy OpenVINO API 1.0 build (HAVE_OPENVINO2=0). This commit doesn't
touch that path.
- Any filter other than dnn_processing. The only other caller of
ff_dnn_get_output() is sr, which accepts only the TensorFlow backend, so it
never reaches get_output_ov(). get_output_ov() also returns before the changed
code for any model that is not DFT_PROCESS_FRAME.
>From 6d5abc92d3f143e0d6d5b8964b1bbeaec6a73262 Mon Sep 17 00:00:00 2001
From: Jake Fineman <[email protected]>
Date: Thu, 1 Oct 2026 09:18:35 -0400
Subject: [PATCH] avfilter/dnn_backend_openvino: fix partial shape leak in
get_output_ov()
With input_resizable enabled, get_output_ov() created partial_shape with
ov_partial_shape_create() and then passed the same variable to
ov_shape_to_partial_shape(), which allocates a new dims array and
overwrites the struct. The first allocation was never freed, so every
call leaked it, on the success path and on the two error returns in
between. The dims it was built from were never used.
Drop the ov_partial_shape_create() call and the dims array; the partial
shape is built from the model's input shape by ov_shape_to_partial_shape()
and freed after ov_model_reshape_single_input() as before.
The leak was introduced with the OpenVINO API 2.0 support in e79bd1f1b1.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Jake Fineman <[email protected]>
---
libavfilter/dnn/dnn_backend_openvino.c | 6 ------
1 file changed, 6 deletions(-)
diff --git a/libavfilter/dnn/dnn_backend_openvino.c
b/libavfilter/dnn/dnn_backend_openvino.c
index 0df4d585e8..79185c2be0 100644
--- a/libavfilter/dnn/dnn_backend_openvino.c
+++ b/libavfilter/dnn/dnn_backend_openvino.c
@@ -1262,7 +1262,6 @@ static int get_output_ov(DNNModel *model, const char
*input_name, int input_widt
const char *output_name, int *output_width,
int *output_height)
{
#if HAVE_OPENVINO2
- ov_dimension_t dims[4] = {{1, 1}, {1, 1}, {input_height, input_height},
{input_width, input_width}};
ov_status_e status;
ov_shape_t input_shape = {0};
ov_partial_shape_t partial_shape;
@@ -1290,11 +1289,6 @@ static int get_output_ov(DNNModel *model, const char
*input_name, int input_widt
#if HAVE_OPENVINO2
if (ctx->ov_option.input_resizable) {
- status = ov_partial_shape_create(4, dims, &partial_shape);
- if (status != OK) {
- av_log(ctx, AV_LOG_ERROR, "Failed to create partial shape.\n");
- return ov2_map_error(status, NULL);
- }
status = ov_const_port_get_shape(ov_model->input_port, &input_shape);
if (status != OK) {
av_log(ctx, AV_LOG_ERROR, "Failed to create shape for model input
resize.\n");
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]