PR #25767 opened by Jake (jakefineman) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25767 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25767.patch
tf_free_request() computes the number of output tensors as sizeof(*output_tensors) / sizeof(output_tensors[0]), which is always 1. Only the first TF_Tensor returned by TF_SessionRun() is deleted, so every inference leaks the other nb_output - 1 tensors. dnn_detect with the TensorFlow backend requires exactly 4 outputs, so every run leaks 3 tensors per frame. dnn_processing with several '&'-separated output names leaks the extra ones too, and those are frame-sized. The fix stores the number of allocated output tensors in TFInferRequest and uses it when freeing them. It lives in the request because destroy_request_item() frees the request with no task in hand. The slots are zeroed after allocation, so the existing error path before the inference runs skips the ones that were never filled. This is the other TF leak I found while working on #25546, which went in as 47f35c191c. dnn_backend_tf.c on master is identical to the base these tests ran on. **Testing** TensorFlow backend, macOS arm64, testsrc2 at 320x240 (rgb24 for detect, gray8 for processing), `leaks --atExit`, total leaks with TF_TensorFromTensor roots in brackets: | case | base, 10 frames | base, 50 frames | this PR, 10 and 50 frames | |---|---|---|---| | dnn_detect async=1 | 126 (60) | 387 (192) | 0 | | dnn_detect async=0 | 72 (33) | 312 (153) | 0 | | dnn_detect nireq=1 | 78 (36) | 315 (156) | 0 | | dnn_processing output=y&z async=1 | 68 (17), 5.29 MB | 212 (53), 16.5 MB | 0 | | dnn_processing output=y&z async=0 | 44 (11), 3.43 MB | 204 (51), 15.9 MB | 0 | | dnn_processing output=y async=0 | 0 | 0 | 0 | Output is unchanged. With 30 frames, framemd5 and the bbox side data from showinfo are identical to the base for dnn_detect, dnn_processing with one and two outputs, and sr. The models are small synthetic GraphDefs. Not tested: a real TensorFlow detection model. There is no FATE coverage for the TensorFlow backend. >From 63b95f5132f10ff05cf0a8b7f3c56d821f654c76 Mon Sep 17 00:00:00 2001 From: Jake Fineman <[email protected]> Date: Tue, 27 Oct 2026 20:09:55 -0400 Subject: [PATCH] avfilter/dnn_backend_tf: free all output tensors tf_free_request() computed the number of output tensors as sizeof(*output_tensors) / sizeof(output_tensors[0]), which is always 1. Only the first TF_Tensor returned by TF_SessionRun() was deleted, so every inference leaked the remaining nb_output - 1 tensors. This is hit by dnn_detect with dnn_backend=tensorflow, which requires exactly 4 outputs and so leaks 3 tensors per frame, and by dnn_processing with several '&'-separated output names. Store the number of allocated output tensors in TFInferRequest and use it when freeing them. Signed-off-by: Jake Fineman <[email protected]> --- libavfilter/dnn/dnn_backend_tf.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libavfilter/dnn/dnn_backend_tf.c b/libavfilter/dnn/dnn_backend_tf.c index f722e8bf8df..ceb96e8c087 100644 --- a/libavfilter/dnn/dnn_backend_tf.c +++ b/libavfilter/dnn/dnn_backend_tf.c @@ -54,6 +54,7 @@ typedef struct TFModel { typedef struct TFInferRequest { TF_Output *tf_outputs; TF_Tensor **output_tensors; + uint32_t nb_output; TF_Output *tf_input; TF_Tensor *input_tensor; } TFInferRequest; @@ -100,8 +101,7 @@ static void tf_free_request(TFInferRequest *request) av_freep(&request->tf_input); av_freep(&request->tf_outputs); if (request->output_tensors) { - int nb_output = sizeof(*request->output_tensors)/sizeof(request->output_tensors[0]); - for (uint32_t i = 0; i < nb_output; ++i) { + for (uint32_t i = 0; i < request->nb_output; ++i) { if (request->output_tensors[i]) { TF_DeleteTensor(request->output_tensors[i]); request->output_tensors[i] = NULL; @@ -109,6 +109,7 @@ static void tf_free_request(TFInferRequest *request) } av_freep(&request->output_tensors); } + request->nb_output = 0; } /** @@ -127,6 +128,7 @@ static TFInferRequest *tf_create_inference_request(void) infer_request->tf_input = NULL; infer_request->input_tensor = NULL; infer_request->output_tensors = NULL; + infer_request->nb_output = 0; return infer_request; } @@ -673,6 +675,7 @@ static int fill_model_input_tf(TFModel *tf_model, TFRequestItem *request) { ret = AVERROR(ENOMEM); goto err; } + infer_request->nb_output = task->nb_output; for (int i = 0; i < task->nb_output; ++i) { infer_request->output_tensors[i] = NULL; -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
