PR #25546 opened by Jake (jakefineman) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25546 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25546.patch
The TensorFlow backend leaks one LastLevelTaskItem per inference. fill_model_input_tf() stores the popped lltask in request->lltask, but nothing frees it after the inference. The next fill_model_input_tf() on the same request overwrites the pointer, so only the last lltask each request held is freed, by destroy_request_item() at teardown. The leak is (inferences + 1 output-size probe) minus the number of requests used, so it grows by one per frame, with async=0 and async=1, for every filter on the TF backend. The patch frees the lltask in infer_completion_callback() before the request goes back on request_queue, as the OpenVINO backend does in its completion callback. It also frees it on the failure paths of execute_model_tf() and dnn_flush_tf(). No inference thread is running on any of these paths, and av_freep() clears the pointer, so the free in destroy_request_item() stays safe. **Testing** macOS arm64, libtensorflow C API, a [1,-1,-1,1] Identity model. `leaks --atExit`, number of leaked 16-byte lltask blocks at 10 / 50 / 100 frames: | config | master | this PR | |---|---|---| | sr, default (async=1) | 1 / 41 / 91 | 0 / 0 / 0 | | sr, async=0 | 1 / 41 / 91 | 0 / 0 / 0 | | sr, nireq=1 | 11 / 51 / 101 | 0 / 0 / 0 | | sr, nireq=4 | 8 / 48 / 98 | 0 / 0 / 0 | | dnn_processing, async=1 | 3 / 44 / 94 | 0 / 0 / 0 | | dnn_processing, async=0 | 1 / 41 / 91 | 0 / 0 / 0 | With frame pointers enabled, every leaked block was allocated by extract_lltask_from_task(), from dnn_execute_model_tf() or, once, from get_output_tf(). The sr runs on master also show the separate vf_sr frame leak fixed by #25543. With both patches applied, sr reports no leaks at all. framemd5 over 30 frames is identical to master for sr and dnn_processing, with gray8 and yuv420p, async=0 and async=1. Not tested: the two failure-path frees were not triggered at runtime. There is no TF FATE coverage. >From e7ffbc04f42688e568dbb03eb044f5c525a40acf Mon Sep 17 00:00:00 2001 From: Jake Fineman <[email protected]> Date: Tue, 27 Oct 2026 17:48:23 -0400 Subject: [PATCH] avfilter/dnn_backend_tf: free the last level task after inference fill_model_input_tf() pops a LastLevelTaskItem from lltask_queue and stores it in request->lltask, but nothing frees it once the inference has completed. The next fill_model_input_tf() on the same request just overwrites the pointer, so only the last lltask of each request is released, by destroy_request_item() when the model is freed. Every inference therefore leaks one lltask, apart from the last one of each of the nireq requests, with both async=0 and async=1. Free it in infer_completion_callback() before the request is pushed back to request_queue, as the OpenVINO backend does in its completion callback. Also free it on the failure paths of execute_model_tf() and dnn_flush_tf(), where the popped lltask is dropped in the same way. No inference thread is running on any of these paths. av_freep() resets the pointer, so the av_freep() in destroy_request_item() is still safe. Signed-off-by: Jake Fineman <[email protected]> --- libavfilter/dnn/dnn_backend_tf.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libavfilter/dnn/dnn_backend_tf.c b/libavfilter/dnn/dnn_backend_tf.c index 6882e0bb311..f722e8bf8df 100644 --- a/libavfilter/dnn/dnn_backend_tf.c +++ b/libavfilter/dnn/dnn_backend_tf.c @@ -748,6 +748,7 @@ static void infer_completion_callback(void *args) { err: tf_free_request(infer_request); av_freep(&outputs); + av_freep(&request->lltask); if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) { destroy_request_item(&request); @@ -794,6 +795,7 @@ static int execute_model_tf(TFRequestItem *request, Queue *lltask_queue) } err: tf_free_request(request->infer_request); + av_freep(&request->lltask); if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) { destroy_request_item(&request); } @@ -874,6 +876,7 @@ static int dnn_flush_tf(const DNNModel *model) ret = fill_model_input_tf(tf_model, request); if (ret != 0) { av_log(ctx, AV_LOG_ERROR, "Failed to fill model input.\n"); + av_freep(&request->lltask); if (ff_safe_queue_push_back(tf_model->request_queue, request) < 0) { destroy_request_item(&request); } -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
