This is an automated email from the git hooks/post-receive script.

Git pushed a commit to branch master
in repository ffmpeg.

The following commit(s) were added to refs/heads/master by this push:
     new c60ac8f256 avfilter/dnn: implement batching for Torch backend
c60ac8f256 is described below

commit c60ac8f256cd76341df4fb5abdfd38d9c47c0a6e
Author:     Raja-89 <[email protected]>
AuthorDate: Sat Jul 11 22:30:07 2026 +0530
Commit:     guoyejun <[email protected]>
CommitDate: Sat Jul 18 02:56:18 2026 +0000

    avfilter/dnn: implement batching for Torch backend
    
    Add batch processing support to the LibTorch DNN backend, following
    the same pattern used by the OpenVINO backend.
    
    Key changes:
    - Add batch_size AVOption (range 1-1000, default 1) to DnnContext
    - Accumulate lltasks in the queue, trigger inference when batch_size
      lltasks are ready (matching the OpenVINO batch execution pattern)
    - Pre-allocate a single contiguous memory buffer for the entire batch
      in fill_model_input_th() to avoid unnecessary tensor copies
    - Split batched output in infer_completion_callback() and dispatch
      each slice to its corresponding task
    
    Tested with:
    ffmpeg -f lavfi -i testsrc=duration=5:size=640x480:rate=25 -vf 
format=rgb24,dnn_processing=dnn_backend=torch:model=dummy_model.pt:batch_size=4 
-f null -
    
    Signed-off-by: Raja Rathour <[email protected]>
---
 libavfilter/dnn/dnn_backend_torch.cpp | 197 +++++++++++++++++++++-------------
 libavfilter/dnn/dnn_interface.c       |   2 +
 libavfilter/dnn_interface.h           |   1 +
 3 files changed, 125 insertions(+), 75 deletions(-)

diff --git a/libavfilter/dnn/dnn_backend_torch.cpp 
b/libavfilter/dnn/dnn_backend_torch.cpp
index 2beffd6e2c..9ba6d61377 100644
--- a/libavfilter/dnn/dnn_backend_torch.cpp
+++ b/libavfilter/dnn/dnn_backend_torch.cpp
@@ -52,7 +52,8 @@ typedef struct THInferRequest {
 
 typedef struct THRequestItem {
     THInferRequest *infer_request;
-    LastLevelTaskItem *lltask;
+    LastLevelTaskItem **lltasks;
+    uint32_t lltask_count;
     DNNAsyncExecModule exec_module;
 } THRequestItem;
 
@@ -108,7 +109,7 @@ static inline void destroy_request_item(THRequestItem **arg)
     item = *arg;
     th_free_request(item->infer_request);
     av_freep(&item->infer_request);
-    av_freep(&item->lltask);
+    av_freep(&item->lltasks);
     ff_dnn_async_module_cleanup(&item->exec_module);
     av_freep(arg);
 }
@@ -166,53 +167,75 @@ static int fill_model_input_th(THModel *th_model, 
THRequestItem *request)
     DNNData input = { 0 };
     DnnContext *ctx = th_model->ctx;
     int ret, width_idx, height_idx, channel_idx;
+    int batch_size = ctx->batch_size;
+    float *batch_data = NULL;
+    int frame_size = 0;
 
-    lltask = (LastLevelTaskItem *)ff_queue_pop_front(th_model->lltask_queue);
-    if (!lltask) {
-        ret = AVERROR(EINVAL);
-        goto err;
-    }
-    request->lltask = lltask;
-    task = lltask->task;
     infer_request = request->infer_request;
 
     ret = get_input_th(&th_model->model, &input, NULL);
-    if ( ret != 0) {
+    if (ret != 0) {
         goto err;
     }
     width_idx = dnn_get_width_idx_by_layout(input.layout);
     height_idx = dnn_get_height_idx_by_layout(input.layout);
     channel_idx = dnn_get_channel_idx_by_layout(input.layout);
+
+    lltask = (LastLevelTaskItem *)ff_queue_peek_front(th_model->lltask_queue);
+    if (!lltask) {
+        ret = AVERROR(EINVAL);
+        goto err;
+    }
+    task = lltask->task;
     input.dims[height_idx] = task->in_frame->height;
     input.dims[width_idx] = task->in_frame->width;
-    input.data = av_malloc(input.dims[height_idx] * input.dims[width_idx] *
-                           input.dims[channel_idx] * sizeof(float));
-    if (!input.data)
-        return AVERROR(ENOMEM);
-    infer_request->input_tensor = new torch::Tensor();
-    infer_request->output = new torch::Tensor();
 
-    switch (th_model->model.func_type) {
-    case DFT_PROCESS_FRAME:
-        input.scale = 255;
-        if (task->do_ioproc) {
-            if (th_model->model.frame_pre_proc != NULL) {
-                th_model->model.frame_pre_proc(task->in_frame, &input, 
th_model->model.filter_ctx);
-            } else {
-                ff_proc_from_frame_to_dnn(task->in_frame, &input, ctx);
+    frame_size = input.dims[height_idx] * input.dims[width_idx] * 
input.dims[channel_idx];
+    batch_data = (float *)av_malloc(batch_size * frame_size * sizeof(float));
+    if (!batch_data) {
+        ret = AVERROR(ENOMEM);
+        goto err;
+    }
+
+    for (int i = 0; i < batch_size; i++) {
+        lltask = (LastLevelTaskItem 
*)ff_queue_pop_front(th_model->lltask_queue);
+        if (!lltask)
+            break;
+
+        request->lltasks[i] = lltask;
+        request->lltask_count = i + 1;
+        task = lltask->task;
+
+        input.data = batch_data + i * frame_size;
+
+        switch (th_model->model.func_type) {
+        case DFT_PROCESS_FRAME:
+            input.scale = 255;
+            if (task->do_ioproc) {
+                if (th_model->model.frame_pre_proc != NULL) {
+                    th_model->model.frame_pre_proc(task->in_frame, &input, 
th_model->model.filter_ctx);
+                } else {
+                    ff_proc_from_frame_to_dnn(task->in_frame, &input, ctx);
+                }
             }
+            break;
+        default:
+            avpriv_report_missing_feature(NULL, "model function type %d", 
th_model->model.func_type);
+            break;
         }
-        break;
-    default:
-        avpriv_report_missing_feature(NULL, "model function type %d", 
th_model->model.func_type);
-        break;
     }
-    *infer_request->input_tensor = torch::from_blob(input.data,
-        {1, input.dims[channel_idx], input.dims[height_idx], 
input.dims[width_idx]},
+
+    infer_request->input_tensor = new torch::Tensor();
+    infer_request->output = new torch::Tensor();
+    *infer_request->input_tensor = torch::from_blob(batch_data,
+        {request->lltask_count, input.dims[channel_idx], 
input.dims[height_idx], input.dims[width_idx]},
         deleter, torch::kFloat32);
+
     return 0;
 
 err:
+    if (batch_data)
+        av_freep(&batch_data);
     th_free_request(infer_request);
     return ret;
 }
@@ -233,7 +256,7 @@ static int th_start_inference(void *args)
         return AVERROR(EINVAL);
     }
     infer_request = request->infer_request;
-    lltask = request->lltask;
+    lltask = request->lltasks[0];
     task = lltask->task;
     th_model = (THModel *)task->model;
     ctx = th_model->ctx;
@@ -260,54 +283,66 @@ static int th_start_inference(void *args)
 
 static void infer_completion_callback(void *args) {
     THRequestItem *request = (THRequestItem*)args;
-    LastLevelTaskItem *lltask = request->lltask;
-    TaskItem *task = lltask->task;
-    DNNData outputs = { 0 };
     THInferRequest *infer_request = request->infer_request;
-    THModel *th_model = (THModel *)task->model;
+    LastLevelTaskItem *lltask = request->lltasks[0];
+    THModel *th_model = (THModel *)lltask->task->model;
     torch::Tensor *output = infer_request->output;
+    DNNData outputs = { 0 };
 
-    c10::IntArrayRef sizes = output->sizes();
-    outputs.order = DCO_RGB;
-    outputs.layout = DL_NCHW;
-    outputs.dt = DNN_FLOAT;
-    if (sizes.size() == 4) {
-        // 4 dimensions: [batch_size, channel, height, width]
-        // this format of data is normally used for video frame SR
-        outputs.dims[0] = sizes.at(0); // N
-        outputs.dims[1] = sizes.at(1); // C
-        outputs.dims[2] = sizes.at(2); // H
-        outputs.dims[3] = sizes.at(3); // W
-    } else {
-        avpriv_report_missing_feature(th_model->ctx, "Support of this kind of 
model");
-        goto err;
-    }
+    auto slices = torch::split(*output, /*split_size=*/1, /*dim=*/0);
+    for (uint32_t i = 0; i < request->lltask_count; i++) {
+        lltask = request->lltasks[i];
+        TaskItem *task = lltask->task;
+        torch::Tensor out_slice = slices[i];
+        c10::IntArrayRef sizes = out_slice.sizes();
+
+        outputs.order = DCO_RGB;
+        outputs.layout = DL_NCHW;
+        outputs.dt = DNN_FLOAT;
+
+        if (sizes.size() == 4) {
+            // 4 dimensions: [batch_size, channel, height, width]
+            // this format of data is normally used for video frame SR
+            outputs.dims[0] = sizes.at(0); // N
+            outputs.dims[1] = sizes.at(1); // C
+            outputs.dims[2] = sizes.at(2); // H
+            outputs.dims[3] = sizes.at(3); // W
+        } else {
+            avpriv_report_missing_feature(th_model->ctx, "Support of this kind 
of model");
+            goto err;
+        }
 
-    switch (th_model->model.func_type) {
-    case DFT_PROCESS_FRAME:
-        if (task->do_ioproc) {
-            // Post process can only deal with CPU memory.
-            if (output->device() != torch::kCPU)
-                *output = output->to(torch::kCPU);
-            outputs.scale = 255;
-            outputs.data = output->data_ptr();
-            if (th_model->model.frame_post_proc != NULL) {
-                th_model->model.frame_post_proc(task->out_frame, &outputs, 
th_model->model.filter_ctx);
+        switch (th_model->model.func_type) {
+        case DFT_PROCESS_FRAME:
+            if (task->do_ioproc) {
+                // Post process can only deal with CPU memory.
+                if (out_slice.device() != torch::kCPU)
+                    out_slice = out_slice.to(torch::kCPU);
+                outputs.scale = 255;
+                outputs.data = out_slice.data_ptr();
+                if (th_model->model.frame_post_proc != NULL) {
+                    th_model->model.frame_post_proc(task->out_frame, &outputs, 
th_model->model.filter_ctx);
+                } else {
+                    ff_proc_from_dnn_to_frame(task->out_frame, &outputs, 
th_model->ctx);
+                }
             } else {
-                ff_proc_from_dnn_to_frame(task->out_frame, &outputs, 
th_model->ctx);
+                task->out_frame->width = 
outputs.dims[dnn_get_width_idx_by_layout(outputs.layout)];
+                task->out_frame->height = 
outputs.dims[dnn_get_height_idx_by_layout(outputs.layout)];
             }
-        } else {
-            task->out_frame->width = 
outputs.dims[dnn_get_width_idx_by_layout(outputs.layout)];
-            task->out_frame->height = 
outputs.dims[dnn_get_height_idx_by_layout(outputs.layout)];
+            break;
+        default:
+            avpriv_report_missing_feature(th_model->ctx, "model function type 
%d", th_model->model.func_type);
+            goto err;
         }
-        break;
-    default:
-        avpriv_report_missing_feature(th_model->ctx, "model function type %d", 
th_model->model.func_type);
-        goto err;
+        task->inference_done++;
     }
-    task->inference_done++;
-    av_freep(&request->lltask);
+
 err:
+    for (uint32_t i = 0; i < request->lltask_count; i++) {
+        av_freep(&request->lltasks[i]);
+    }
+    request->lltask_count = 0;
+
     th_free_request(infer_request);
 
     if (ff_safe_queue_push_back(th_model->request_queue, request) < 0) {
@@ -483,6 +518,11 @@ static DNNModel *dnn_load_model_th(DnnContext *ctx, 
DNNFunctionType func_type, A
         if (!item->infer_request) {
             goto fail;
         }
+        item->lltasks = (LastLevelTaskItem **)av_malloc_array(ctx->batch_size, 
sizeof(*item->lltasks));
+        if (!item->lltasks) {
+            goto fail;
+        }
+        item->lltask_count = 0;
 
         item->exec_module.start_inference = &th_start_inference;
         item->exec_module.callback = &infer_completion_callback;
@@ -551,13 +591,20 @@ static int dnn_execute_model_th(const DNNModel *model, 
DNNExecBaseParams *exec_p
         return ret;
     }
 
-    request = (THRequestItem 
*)ff_safe_queue_pop_front(th_model->request_queue);
-    if (!request) {
-        av_log(ctx, AV_LOG_ERROR, "unable to get infer request.\n");
-        return AVERROR(EINVAL);
+    while (ff_queue_size(th_model->lltask_queue) >= ctx->batch_size) {
+        request = (THRequestItem 
*)ff_safe_queue_pop_front(th_model->request_queue);
+        if (!request) {
+            av_log(ctx, AV_LOG_ERROR, "unable to get infer request.\n");
+            return AVERROR(EINVAL);
+        }
+
+        ret = execute_model_th(request, th_model->lltask_queue);
+        if (ret != 0) {
+            return ret;
+        }
     }
 
-    return execute_model_th(request, th_model->lltask_queue);
+    return 0;
 }
 
 static DNNAsyncStatusType dnn_get_result_th(const DNNModel *model, AVFrame 
**in, AVFrame **out)
diff --git a/libavfilter/dnn/dnn_interface.c b/libavfilter/dnn/dnn_interface.c
index 010677dd81..2cf81793de 100644
--- a/libavfilter/dnn/dnn_interface.c
+++ b/libavfilter/dnn/dnn_interface.c
@@ -52,6 +52,8 @@ static const AVOption dnn_base_options[] = {
                 OFFSET(backend_options), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 
0, FLAGS | AV_OPT_FLAG_DEPRECATED},
         {"nireq", "number of request",
                 OFFSET(nireq), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, FLAGS},
+        {"batch_size", "batch size per request",
+                OFFSET(batch_size), AV_OPT_TYPE_INT, {.i64 = 1}, 1, 1000, 
FLAGS},
         {"async", "use DNN async inference",
                 OFFSET(async), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS},
         {"device", "device to run model",
diff --git a/libavfilter/dnn_interface.h b/libavfilter/dnn_interface.h
index 69a8b0a669..207a9c93d7 100644
--- a/libavfilter/dnn_interface.h
+++ b/libavfilter/dnn_interface.h
@@ -165,6 +165,7 @@ typedef struct DnnContext {
     const DNNModule *dnn_module;
 
     int nireq;
+    int batch_size;
     char *device;
     int device_id;
 

_______________________________________________
ffmpeg-cvslog mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to