<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>
"); //-->

博客專(zhuān)欄

EEPW首頁(yè) > 博客 > C++部署的性能優(yōu)化方法

C++部署的性能優(yōu)化方法

發(fā)布人:地平線(xiàn)開(kāi)發(fā)者 時(shí)間:2025-04-28 來(lái)源:工程師 發(fā)布文章

一、使用結構體提前存放常用變量

在編寫(xiě)前后處理函數時(shí),通常會(huì )多次用到一些變量,比如模型輸入 tensor 的 shape,count 等等,若在每個(gè)處理函數中都重復計算一次,會(huì )增加部署時(shí)的計算量。對于這種情況,可以考慮使用結構體,并定義一個(gè)初始化函數。先計算好需要的值,之后需要用到該變量的時(shí)候直接引用(&)傳遞即可。


// 定義結構體
struct ModelInfo {
   hbDNNPackedHandle_t packed_handle;
   hbDNNHandle_t       model_handle;
   const char *        model_path;
   const char **       model_name_list;
   int model_count;
   int input_count;
   int output_count;
};
// 函數聲明
int init_model(ModelInfo &model_info);
int other_function(ModelInfo &model_info, ...);
//主函數
int main(){
   // 初始化
   ModelInfo prefill_model = {0};
   prefill_model.model_path = drobotics_model_path_prefill.c_str();
   init_model(prefill_model);
   // 在其他函數中使用引用傳遞相關(guān)參數
   other_function(prefill_model, ...);
   return 0;
}
// 初始化函數的完整定義
int init_model(ModelInfo &model_info) {
   hbDNNInitializeFromFiles(&model_info.packed_handle, &model_info.model_path, 1);
   HB_CHECK_SUCCESS(hbDNNGetModelNameList(&model_info.model_name_list, &model_info.model_count, model_info.packed_handle),
           "hbDNNGetModelNameList failed");
   HB_CHECK_SUCCESS(hbDNNGetModelHandle(&model_info.model_handle, model_info.packed_handle, model_info.model_name_list[0]),
       "hbDNNGetModelHandle failed");
   HB_CHECK_SUCCESS(hbDNNGetInputCount(&model_info.input_count, model_info.model_handle), "hbDNNGetInputCount failed");
   HB_CHECK_SUCCESS(hbDNNGetOutputCount(&model_info.output_count, model_info.model_handle), "hbDNNGetOutputCount failed");
   return 0;
}
// 其他函數參數中使用引用傳遞
int other_function(ModelInfo &model_info, ...){
   ...
}


二、函數使用引用代替值傳遞

考慮到 C++的特性,函數的參數建議使用引用 (&) 來(lái)代替值傳遞,有這幾個(gè)顯著(zhù)優(yōu)點(diǎn):

    只將原對象的引用傳遞給函數,避免不必要的拷貝,降低計算耗時(shí)

    因為不會(huì )復制數據,所以引用相比值傳遞可以避免內存的重復開(kāi)銷(xiāo),降低內存占用

但需要注意,引用會(huì )允許函數修改原始數據,因此若不希望原始數據被修改,請不要使用引用方法。



三、量化/反量化融合

3.1 在前后處理的循環(huán)中融合

在前后處理中通常會(huì )遍歷數據,而量化/反量化也會(huì )遍歷數據,因此可以考慮合并計算,以減少數據遍歷耗時(shí)。這是最常見(jiàn)的量化/反量化融合思路,可以直接參考 ai benchmark 中的大量源碼示例。

3.2 將數據存進(jìn) tensor 時(shí)融合

如果在前處理中沒(méi)找到融合的機會(huì ),那么也可以在數據復制進(jìn) input tensor 的時(shí)候做量化計算。

int64_t kv_count = 0;
int8_t* input_ptr = reinterpret_cast<int8_t*>(model_info.input_tensors[i].sysMem.virAddr);
for (int n = 0; n < total_count; n++) {
   input_ptr[n] = quantize_int8(kv_decode[kv_count++], cur_scale, cur_zero_point);
}


3.3 填充初始值時(shí),提前計算量化后的值

有時(shí)我們想給模型準備特定的輸入,比如生成一個(gè)全 0 數組,再為數組的特定區域填充某個(gè)固定的浮點(diǎn)值。在這種情況下,如果先生成完整的浮點(diǎn)數組,再遍歷整個(gè)數組做量化,會(huì )產(chǎn)生不必要的遍歷耗時(shí),常見(jiàn)的優(yōu)化思路是先提前計算好填充值量化后的結果,填充的時(shí)候直接填入定點(diǎn)值,這樣就可以避免多余的量化耗時(shí)。

std::vector<int16_t> prepare_decode_attention_mask(ModelInfo &model_info,
   DecodeInfo &decode_info, PrefillInfo &prefill_info, int decode_infer_num){

   // 初始化全 0 數組

   std::vector<int16_t> decode_attention_mask_int(decode_info.kv_cache_len, 0);
   // 提前計算填充值量化后的結果
   hbDNNQuantiScale scale = model_info.input_tensors[1].properties.scale;
   auto cur_scale = scale.scaleData[0];
   auto cur_zero_point = scale.zeroPointData[0];
   int16_t pad_value_int = quantize_s16(-2048.0, cur_scale, cur_zero_point);
   // 將量化后的填充值填充到數組中特定區域
   for(int i = 0; i < decode_info.kv_cache_len - prefill_info.tokens_len
       - decode_infer_num -1; i++){
       decode_attention_mask_int[i] = pad_value_int;
   }

   // 返回相當于已經(jīng)量化了的數組

   return decode_attention_mask_int;
}


3.4 根據后處理的實(shí)際作用,跳過(guò)反量化

在某些情況下,比如后處理只做 argmax 時(shí),完全沒(méi)有必要做反量化,直接使用整型數據做 argmax 即可。需要用戶(hù)根據后處理的具體原理來(lái)判斷是否使用這種優(yōu)化方法。

// 直接對模型輸出的 int16_t 數據做 argmax 計算
int logits_argmax(std::vector<hbDNNTensor> &output_tensor) {
   auto data_tensor = reinterpret_cast<int16_t *>(output_tensor[0].sysMem.virAddr);
   int maxIndex = -1;
   int maxValue = -32768;
   for (int i = 0; i < 151936; ++i) {
       if (data_tensor[i] > maxValue) {
           maxValue = data_tensor[i];
           maxIndex = i;
       }
   }
   return maxIndex;
}


四、循環(huán)推理同個(gè)模型時(shí),輸出數據直接存進(jìn)輸入 tensor

在某些情況下,我們希望 C++程序能重復推理同一個(gè)模型,并且模型上一幀的輸出可以作為下一幀的輸入。如果按照常規手段,我們可能會(huì )將輸出 tensor 的內容保存到特定數組,再把這個(gè)數組拷貝到輸入 tensor,這樣一來(lái)一回就產(chǎn)生了兩次數據拷貝的耗時(shí),也占用了更多內存。實(shí)際上,我們可以將模型的輸出 tensor 地址直接指向輸入 tensor,這樣模型第一幀的推理結果會(huì )直接寫(xiě)在輸入 tensor 上,推理第二幀的時(shí)候就可以直接利用這份數據,不需要再單獨準備輸入,可以節省大量耗時(shí)。

如果想使用該方法,需要模型輸入輸出對應節點(diǎn)的 shape/stride 等信息完全相同。此外,如果模型刪除了量化/反量化算子,并且對應的 scale 完全相同,那么重復利用的這部分 tensor 是不需要 flush 的(因為不涉及 CPU 操作),還可進(jìn)一步節約耗時(shí)。

這里舉個(gè)例子詳細說(shuō)明一下。

假設我們有一個(gè)模型,這個(gè)模型有 59 個(gè)輸入節點(diǎn)(0-58),57 個(gè)輸出節點(diǎn)(0-56),量化/反量化算子均已刪除,且輸入輸出最后 56 個(gè)節點(diǎn)對應的 scale/shape/stride 等信息均相同。在第一幀推理完成后,輸出節點(diǎn) 1-56 的值需要傳遞給輸入節點(diǎn)的 3-58,那么我們在分配模型輸入輸出 tensor 的時(shí)候,輸出 tensor 只需要為 1 分配即可,在分配輸入 tensor 時(shí),3-58 的 tensor 可以同時(shí) push_back 給輸出 tensor。具體來(lái)說(shuō),可以這樣寫(xiě):


int prepare_tensor(std::vector<hbDNNTensor> & input_tensor, std::vector<hbDNNTensor> & output_tensor,
                  hbDNNHandle_t dnn_handle) {
   int input_count  = 0;
   int output_count = 0;
   hbDNNGetInputCount(&input_count, dnn_handle);
   hbDNNGetOutputCount(&output_count, dnn_handle);
   for (int i = 0; i < 1; i++) {
       hbDNNTensor output;
       HB_CHECK_SUCCESS(hbDNNGetOutputTensorProperties(&output.properties, dnn_handle, i),
                        "hbDNNGetOutputTensorProperties failed");
       int output_memSize = output.properties.alignedByteSize;
       HB_CHECK_SUCCESS(hbUCPMallocCached(&output.sysMem, output_memSize, 0), "hbUCPMallocCached failed");
       output_tensor.push_back(output);
   }

   for (int i = 0; i < input_count; i++) {
       hbDNNTensor input;
       HB_CHECK_SUCCESS(hbDNNGetInputTensorProperties(&input.properties, dnn_handle, i),
                        "hbDNNGetInputTensorProperties failed");
       int input_memSize = input.properties.alignedByteSize;
       HB_CHECK_SUCCESS(hbUCPMallocCached(&input.sysMem, input_memSize, 0), "hbUCPMallocCached failed");
       input_tensor.push_back(input);
       if(i > 2){
           output_tensor.push_back(input);
       }
   }
   return 0;
}


在模型推理時(shí),重復利用的這部分 tensor 不需要再 flush,因此只需要給 output_tensor 的 0,以及 input_tensor 的 0/1/2 進(jìn)行 flush 操作即可(這幾個(gè) tensor 和 CPU 產(chǎn)生了交互)。


while(1){
   hbUCPTaskHandle_t task_handle_decode{nullptr};
   hbDNNTensor *output_decode = decode_model.output_tensors.data();
   HB_CHECK_SUCCESS(hbDNNInferV2(&task_handle_decode, output_decode,
       decode_model.input_tensors.data(), decode_model.model_handle), "hbDNNInferV2 failed");
   hbUCPSchedParam ctrl_param_decode;
   HB_UCP_INITIALIZE_SCHED_PARAM(&ctrl_param_decode);
   ctrl_param_decode.backend = HB_UCP_BPU_CORE_ANY;
   HB_CHECK_SUCCESS(hbUCPSubmitTask(task_handle_decode, &ctrl_param_decode), "hbUCPSubmitTask failed");
   HB_CHECK_SUCCESS(hbUCPWaitTaskDone(task_handle_decode, 0), "hbUCPWaitTaskDone failed");
   // 只刷新一部分輸出內存(output_tensor 0)
   hbUCPMemFlush(&decode_model.output_tensors[0].sysMem, HB_SYS_MEM_CACHE_INVALIDATE);
   HB_CHECK_SUCCESS(hbUCPReleaseTask(task_handle_decode), "hbUCPReleaseTask failed");
   // 后處理(只針對 output_tensor 0)
   decode_argmax_id = logits_argmax(decode_model.output_tensors);
   // 準備下一幀推理的 input_tensor 0/1/2 輸入數據
   prepare_input_tensor(...);
   // 只刷新一部分輸入內存(input_tensor 0/1/2)
   for (int i = 0; i < 3; i++) {
       hbUCPMemFlush(&decode_model.input_tensors[i].sysMem, HB_SYS_MEM_CACHE_CLEAN);
   }


此外,如果使用了這種優(yōu)化方法,那么在模型推理結束釋放內存時(shí),要避免同一塊內存的重復釋放。對于該案例,input_tensor 全部釋放完畢后,output_tensor 只需要釋放 output_tensor 0。


for (int i = 0; i < decode_model.input_count; i++) {
   HB_CHECK_SUCCESS(hbUCPFree(&(decode_model.input_tensors[i].sysMem)), "hbUCPFree decode_model.input_tensors failed");
}
for (int i = 0; i < 1; i++) {
   HB_CHECK_SUCCESS(hbUCPFree(&(decode_model.output_tensors[i].sysMem)), "hbUCPFree decode_model.output_tensors failed");
}



五、多線(xiàn)程后處理

對于 yolo v5 這種有三個(gè)輸出頭的模型,可以考慮使用三個(gè)線(xiàn)程同時(shí)對三個(gè)輸出頭做后處理,以顯著(zhù)提升性能。


*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。




相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>