Tencent / TNN

TNN: developed by Tencent Youtu Lab and Guangying Lab, a uniform deep learning inference framework for mobile、desktop and server. TNN is distinguished by several outstanding features, including its cross-platform capability, high performance, model compression and code pruning. Based on ncnn and Rapidnet, TNN further strengthens the support and performance optimization for mobile devices, and also draws on the advantages of good extensibility and high performance from existed open source efforts. TNN has been deployed in multiple Apps from Tencent, such as Mobile QQ, Weishi, Pitu, etc. Contributions are welcome to work in collaborative with us and make TNN a better framework.
Other
4.38k stars 767 forks source link

关于TNN框架对多模型并行推理的支持 #1285

Closed bestpower closed 3 years ago

bestpower commented 3 years ago

目前部署框架均支持同一模型的多线程推理,请问TNN框架在安卓端是否支持不同模型的并行推理? 如果支持的话该如何实现,或者给一个示例代码? 比如设备是4核cpu,我想同时跑四个模型,且四个模型的推理流程均互不干扰,该如何实现?

shaundai-tencent commented 3 years ago

目前TNN可以多线程推理,内部是线程安全的。如果4个模型并行,需要在每个线程中创建独立的Instance,之后使用的方式和单线程一致。

bestpower commented 3 years ago

目前TNN可以多线程推理,内部是线程安全的。如果4个模型并行,需要在每个线程中创建独立的Instance,之后使用的方式和单线程一致。

我自测了下,在每个线程中创建独立的Instance需要不少耗时,如果在多线程并行推理中这么做就没有相对串行推理的性能优势了,能否将每个模型的instance在主线程中以static的形式提前创建,并在子线程中调用?另外多线程推理时,针对每个线程中的模型设置SetCpuNumThreads时有什么讲究吗?

shaundai-tencent commented 3 years ago

可以的,只需要保证不同的线程跑的是不同的instance就行。你用的是什么平台跑的?SetCpuNumThreads这个接口是对openmp的设置,是全局的,多线程里面设置可能会以最后一个执行的为最终结果进行执行。

bestpower commented 3 years ago

可以的,只需要保证不同的线程跑的是不同的instance就行。你用的是什么平台跑的?SetCpuNumThreads这个接口是对openmp的设置,是全局的,多线程里面设置可能会以最后一个执行的为最终结果进行执行。

Anrdoid arm平台,编译选项里有加openmp。

bestpower commented 3 years ago

在安卓jni层进行多线程并行推理出错,麻烦帮忙看看是什么原因: 出错日志:

01-09 10:48:33.004 11152 11152 F DEBUG   : backtrace:
01-09 10:48:33.004 11152 11152 F DEBUG   :     #00 pc 0008bb18  /data/app/com.demo.adft-1/lib/arm/libTNN.so (_ZN3tnn8Instance11SetInputMatENSt6__ndk110shared_ptrINS_3MatEEENS_15MatConvertParamENS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE+47)
01-09 10:48:33.004 11152 11152 F DEBUG   :     #01 pc 00031991  /data/app/com.demo.adft-1/lib/arm/libTnnFace.so
01-09 10:48:33.004 11152 11152 F DEBUG   :     #02 pc 00038f29  /data/app/com.demo.adft-1/lib/arm/libTnnFace.so

其中TNN库出错信息为:
libTNN.so  PC=0008bb18
_ZN3tnn8Instance11SetInputMatENSt6__ndk110shared_ptrINS_3MatEEENS_15MatConvertParamENS1_12basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEE

libTnnFace.so 库出错定位位置均是在调用线程函数中的instance时
如:
tnnInOutPutInfos->instance->GetCommandQueue((void**)&command_queue);
TNN_NS::Status status = tnnInOutPutInfos->instance->SetInputMat(target_mat, input_cvt_param, "data");

并行推理关键代码:

   auto *tnnInOutPutInfos1 = new TnnInOutPutInfos();
    tnnInOutPutInfos1->instance = p_faceLandmark_instance;
    tnnInOutPutInfos1->input_mat = input_mat;
    tnnInOutPutInfos1->faceInfos = nullptr;
    tnnInOutPutInfos1->futureRunningFlag = tnnInOutPutInfos1->promiseRunningFlag.get_future();
    auto *tnnInOutPutInfos2 = new TnnInOutPutInfos();
    tnnInOutPutInfos1->instance = p_faceOcclusion_instance;
    tnnInOutPutInfos2->input_mat = input_mat;
    tnnInOutPutInfos2->faceInfos = nullptr;
    tnnInOutPutInfos2->futureRunningFlag = tnnInOutPutInfos2->promiseRunningFlag.get_future();
    auto *tnnInOutPutInfos3 = new TnnInOutPutInfos();
    tnnInOutPutInfos1->instance = p_facePose_instance;
    tnnInOutPutInfos3->input_mat = input_mat;
    tnnInOutPutInfos3->faceInfos = nullptr;
    tnnInOutPutInfos3->futureRunningFlag = tnnInOutPutInfos3->promiseRunningFlag.get_future();
    auto *tnnInOutPutInfos4 = new TnnInOutPutInfos();
    tnnInOutPutInfos1->instance = p_faceMask_instance;
    tnnInOutPutInfos4->input_mat = input_mat;
    tnnInOutPutInfos4->faceInfos = nullptr;
    tnnInOutPutInfos4->futureRunningFlag = tnnInOutPutInfos4->promiseRunningFlag.get_future();

    std::thread t1(faceLandmarkThreadFunc, std::ref(tnnInOutPutInfos1));
    std::thread t2(faceOcclusionThreadFunc, std::ref(tnnInOutPutInfos2));
    std::thread t3(facePoseThreadFunc, std::ref(tnnInOutPutInfos3));
    std::thread t4(faceMaskThreadFunc, std::ref(tnnInOutPutInfos4));
    t1.detach();
    t2.detach();
    t3.detach();
    t4.detach();
    //获取线程执行结束标志位,如获取不到,则保持等待,直至获取到为止,主线程阻塞
    int flag1 = tnnInOutPutInfos1->futureRunningFlag.get();
    int flag2 = tnnInOutPutInfos2->futureRunningFlag.get();
    int flag3 = tnnInOutPutInfos3->futureRunningFlag.get();
    int flag4 = tnnInOutPutInfos4->futureRunningFlag.get();

单线程代码示例:

static void faceLandmarkThreadFunc(TnnInOutPutInfos* &tnnInOutPutInfos){
    std::vector<std::string> outputNames;//输入节点名称数组
    outputNames.emplace_back("landmark");
    std::vector<float *> outputs;//输出数据数组
    auto target_mat = std::make_shared<TNN_NS::Mat>(p_device_type, TNN_NS::N8UC4, p_landmark_target_dims);
    TNN_NS::ResizeParam param = TNN_NS::ResizeParam();
    void* command_queue;
    tnnInOutPutInfos->instance->GetCommandQueue((void**)&command_queue);
    TNN_NS::MatUtils::Resize(*(tnnInOutPutInfos->input_mat), *target_mat, param, command_queue);
    // convert input mat
    TNN_NS::MatConvertParam input_cvt_param = TNN_NS::MatConvertParam();
    input_cvt_param.scale = p_scale;
    input_cvt_param.bias = p_bias;
    TNN_NS::Status status = tnnInOutPutInfos->instance->SetInputMat(target_mat, input_cvt_param, "data");
    // inference
    status = tnnInOutPutInfos->instance->ForwardAsync(nullptr);
//    status = faceLandmark_instance->Forward();
    // get output data
    if (status == TNN_NS::TNN_OK) {
        const TNN_NS::MatConvertParam output_cvt_param = TNN_NS::MatConvertParam();
        for(int i=0;i<outputNames.size();i++) {
            std::shared_ptr<TNN_NS::Mat> output_mat = nullptr;
            status = tnnInOutPutInfos->instance->GetOutputMat(output_mat, output_cvt_param, outputNames[i]);
            outputs.push_back(static_cast<float *>(output_mat->GetData()));
        }
        tnnInOutPutInfos->faceInfos = outputs[0];
    }
    tnnInOutPutInfos->promiseRunningFlag.set_value(1);//设置结束标志位
}

线程传参结构体:

struct TnnInOutPutInfos{
    std::shared_ptr<TNN_NS::Instance> instance;//网络instance
    std::shared_ptr<TNN_NS::Mat> input_mat;//输入mat
    float* faceInfos;//输出信息数组
    //异步传参运行标志位
    std::promise<int> promiseRunningFlag;
    std::future<int> futureRunningFlag;
};

另外,此方式只创建一个线程的模型推理时不会报错,两个及以上就会报错。

shaundai-tencent commented 3 years ago

多线程之间的资源要保证独立,看你的代码,是否多个线程使用的同一个input_mat,这个input_mat的生命周期是什么样的?建议输入在线程内进行构造。

bestpower commented 3 years ago

多线程之间的资源要保证独立,看你的代码,是否多个线程使用的同一个input_mat,这个input_mat的生命周期是什么样的?建议输入在线程内进行构造。

问题解决了,主要是代码问题,是前面有个模型路径的模型名称拼写错了,模型加载的时候没添加异常捕获所以一直没发现,另外不同线程传参名称有搞混的情况,现在可以了

bestpower commented 3 years ago

请问TNN在推理时有类似ncnn的绑定大小核的功能吗?

shaundai-tencent commented 3 years ago

有绑大小核的接口,请参考: https://github.com/Tencent/TNN/blob/master/include/tnn/utils/cpu_utils.h#L34 其中接口: Status SetCpuPowersave(int powersave); Status SetCpuAffinity(const std::vector& cpu_list); 都可以实现这个功能。

bestpower commented 3 years ago

有绑大小核的接口,请参考: https://github.com/Tencent/TNN/blob/master/include/tnn/utils/cpu_utils.h#L34 其中接口: Status SetCpuPowersave(int powersave); Status SetCpuAffinity(const std::vector& cpu_list); 都可以实现这个功能。

好的,感谢告知!