<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          HugeCTR源碼簡單走讀

          共 9054字,需瀏覽 19分鐘

           ·

          2022-05-24 18:12

          前言

          這段時間除了開發(fā)算子之外,還在做一些推薦系統(tǒng)相關(guān)的工作,這期間主要看的是HugeCTR的代碼,其性能優(yōu)異,系統(tǒng)不復雜,代碼結(jié)構(gòu)較扁平,整體還是比較清晰。在這段時間看源碼的過程中也算是對HugeCTR有一點了解,這篇博客主要梳理下HugeCTR代碼的結(jié)構(gòu),以及他在MLPERF中做的一些優(yōu)化。

          倉庫地址:NVIDIA-Merlin/HugeCTR

          MLPERF博客:

          Boosting NVIDIA MLPerf Training v1.1 Performance with Full Stack Optimization

          MLPerf v1.0 Training Benchmarks: Insights into a Record-Setting NVIDIA Performance

          整體結(jié)構(gòu)

          倉庫主體結(jié)構(gòu)如下,一些不重要的目錄就省去了

          - HugeCTR 主要的源碼目錄
          - gpu_cache GPU 帶緩存機制的Embedding實現(xiàn)
          - onnx_converter onnx模型轉(zhuǎn)換器
          - samples 模型示例目錄,包含如DLRM, DeepFM等常用模型
          - sparse_operation_kit 稀疏操作庫,可以作為一款插件搭配在其他框架上使用,如Tensorflow

          我們主要還是關(guān)注 HugeCTR 這個主目錄,里面分別存放了頭文件(include)和對應的實現(xiàn)(src),我們基于頭文件目錄來看下結(jié)構(gòu):

          HugeCTR
          | - include
          | - collectives 通信相關(guān)部分
          | - cpu CPU版本實現(xiàn)全部集中放在這一目錄
          | - data_readers 數(shù)據(jù)加載器部分
          | - embedding_training_cache Embedding訓練緩存機制
          | - embedding 不同類型Embedding實現(xiàn)部分,如混合Embedding等
          | - hashtable GPU哈希表
          | - embedding 不同類型Embedding實現(xiàn)部分,如混合Embedding等
          | - hps 系統(tǒng)組件實現(xiàn)部分,包含內(nèi)存池,各種數(shù)據(jù)庫后端等
          | - inference 推理實現(xiàn)
          | - layers 不同神經(jīng)網(wǎng)絡(luò)層實現(xiàn)
          | - optimizers 不同優(yōu)化器實現(xiàn)
          | - prims 我理解是提供cuda基礎(chǔ)操作部分,如reduce,矩陣求外積等基礎(chǔ)操作
          | - pybind cpp接口導出到python部分
          | - regularizer L1/2 regularizer實現(xiàn)
          | - resource_managers 系統(tǒng)資源管理
          | - shuffle (不知道這部分是干啥的,有了解的朋友也可以幫忙補充下)

          直接硬翻源碼我覺得是有點難的,我的方法是從一個模型開始,看其分別涉及到了哪些代碼,下面我們就以官方的DLRM示例來看源碼,并針對我比較熟悉的算子實現(xiàn)展開。

          Python這部分接口HugeCTR走的是Keras風格,習慣PyTorch的朋友可能一時還不太習慣

          CreateSolver

          第一部分是構(gòu)建了一個Solver,看起來是一個全局模型主體配置的東西:

          solver?=?hugectr.CreateSolver(max_eval_batches?=?51,
          ??????????????????????????????batchsize_eval?=?1769472,
          ??????????????????????????????batchsize?=?55296,
          ??????????????????????????????vvgpu?=?[[0,1,2,3,4,5,6,7]],
          ??????????????????????????????repeat_dataset?=?True,
          ??????????????????????????????lr?=?24.0,
          ??????????????????????????????...
          ??????????????????????????????is_dlrm?=?True)

          對應我們找到其pybind文件solver_wrapper.hpp,在cpp層創(chuàng)建了一個Solver對象,并將參數(shù)set進去:

          ??std::unique_ptr?solver(new?Solver());
          ??solver->model_name?=?model_name;
          ??solver->seed?=?seed;
          ??solver->lr_policy?=?lr_policy;
          ??solver->lr?=?lr;
          ??...

          DataReaderParams

          這部分是配置數(shù)據(jù)讀取的一些參數(shù)

          reader?=?hugectr.DataReaderParams(data_reader_type?=?hugectr.DataReaderType_t.RawAsync,
          ??????????????????????????????????source?=?["./train_data.bin"],
          ??????????????????????????????????eval_source?=?"./test_data.bin"
          ??????????????????????????????????...

          其作為一個結(jié)構(gòu)體,具體在 DataReaderParams::DataReaderParams,并在后續(xù)的model中傳入并調(diào)用

          Model::Model(const?Solver&?solver,?const?DataReaderParams&?reader_params,...)

          CreateOptimizer

          顯然這部分是創(chuàng)建整個模型使用的優(yōu)化器

          optimizer?=?hugectr.CreateOptimizer(optimizer_type?=?hugectr.Optimizer_t.SGD,
          ????????????????????????????????????update_type?=?hugectr.Update_t.Local,
          ????????????????????????????????????atomic_update?=?True)

          在optimizer_wrapper.hpp里面,對不同優(yōu)化器的參數(shù)進行配置:

          ??OptHyperParams?opt_hyper_params;
          ??//?給Adam優(yōu)化器的beta1?beta2配置
          ??opt_hyper_params.adam.beta1?=?beta1;
          ??...
          ??//?設(shè)置Adagrad初始累加值
          ??opt_hyper_params.adagrad.initial_accu_value?=?initial_accu_value;
          ??...
          ??opt_hyper_params.momentum.factor?=?momentum_factor;
          ??opt_hyper_params.nesterov.mu?=?momentum_factor;
          ??opt_hyper_params.sgd.atomic_update?=?atomic_update;

          實例化Model

          做完前置工作后,這里正式實例化一個Model主體,傳入solver,reader,optimizer這三者

          model?=?hugectr.Model(solver,?reader,?optimizer)

          在其頭文件中model.hpp描述了Model對象的一些成員,這里面包含的挺雜的,諸如:

          GpuLearningRateSchedulers?gpu_lr_sches_;?lr_sch_;?//?學習率調(diào)度器
          std::vector<std::shared_ptrfloat>>>?train_weight_buff_list_;?//?用于訓練時float32類型權(quán)重分配顯存
          std::vector<std::shared_ptr>>?train_weight_buff_half_list_;?//?用于開啟混合精度時,對half類型權(quán)重分配顯存
          std::vector<std::string>?data_input_info_;?//?輸入data信息,這里是字符串形式
          std::map<std::string,?std::vector<size_t>>?tensor_shape_info_;?//?tensor形狀信息
          std::vector<std::pair<std::string,?std::string>>
          ??????input_output_info_;?//?每一層輸入,輸出信息
          std::vector<std::string>?layer_info_;?//?layer信息,有一個枚舉類型維護string到實際網(wǎng)絡(luò)層的映射關(guān)系
          ...?

          就不一一列舉了。

          在Model::Model里面,根據(jù)傳進來的信息做一系列初始化:

          //?使用多少gpu訓練
          for?(size_t?i?=?0;?i?get_local_gpu_count();?i++)?{
          ????train_weight_buff_list_.emplace_back(blobs_buff_list_[i]->create_block<float>());
          ????...
          ????auto?id?=?resource_manager_->get_local_gpu(i)->get_local_id();
          ????//?如果開啟混合精度,那么就給half版本的wgrad設(shè)置以及allreduce通信操作,并給float版本的wgrad設(shè)置float類型分配器
          ????if?(solver_.use_mixed_precision)?{
          ??????wgrad_buff_half_list_.emplace_back(
          ??????????(solver_.grouped_all_reduce)
          ????????????????std::dynamic_pointer_cast>(exchange_wgrad_)
          ????????????????????->get_network_wgrad_buffs()[id]
          ??????????????:?std::dynamic_pointer_cast>(exchange_wgrad_)
          ????????????????????->get_network_wgrad_buffs()[id]);
          ??????wgrad_buff_list_.emplace_back(blobs_buff_list_[i]->create_block<float>());
          ????}?else?{
          ??????//?不開啟混合精度,就給float版本的wgrad設(shè)置對應的allreduce通信操作
          ??????wgrad_buff_list_.emplace_back(...);
          ??????wgrad_buff_half_list_.emplace_back(...);??//?placeholder
          ????}
          ??}
          ??
          ??...

          ??//?initialize?optimizer
          ??init_optimizer(opt_params_,?solver_,?opt_params_py);
          ??init_learning_rate_scheduler(lr_sch_,?solver_,?gpu_lr_sches_,?resource_manager_);

          構(gòu)建Input,SparseEmbedding

          model.add(hugectr.Input(label_dim?=?1,?label_name?=?"label",
          ????????????????????????dense_dim?=?13,?dense_name?=?"dense",
          ????????????????????????...))
          model.add(hugectr.SparseEmbedding(embedding_type?=?hugectr.Embedding_t.HybridSparseEmbedding,?
          ????????????????????????????workspace_size_per_gpu_in_mb?=?15000,
          ????????????????????????????...)))

          這里調(diào)用model的add方法添加輸入層和Embedding層,我們先看下add方法,在對應的pybind綁定中model_wrapper.hpp對應四種重載:

          //?1.?輸入層
          .def("add",?pybind11::overload_cast(&HugeCTR::Model::add),?pybind11::arg("input"))
          //?2.?Embedding層
          .def("add",?pybind11::overload_cast(&HugeCTR::Model::add),
          ?????pybind11::arg("sparse_embedding"))
          //?3.?全連接層
          .def("add",?pybind11::overload_cast(&HugeCTR::Model::add),
          ?????pybind11::arg("dense_layer"))
          //?4.?多層全連接層
          .def("add",?pybind11::overload_cast(&HugeCTR::Model::add),
          ?????pybind11::arg("group_dense_layer"))

          Input和SparseEmbedding重載實現(xiàn)對應在model.cpp內(nèi),這里就不再展開了。

          增加全連接層

          DLRM主體計算部分是由兩部分bottom_mlptop_mlp組成的,因此模型后續(xù)代碼就是在構(gòu)建這部分:

          model.add(hugectr.GroupDenseLayer(group_layer_type?=?hugectr.GroupLayer_t.GroupFusedInnerProduct,
          ????????????????????????????bottom_name_list?=?["dense"],
          ????????????????????????????top_name_list?=?["fc1",?"fc2",?"fc3"],
          ????????????????????????????num_outputs?=?[512,?256,?128],
          ????????????????????????????last_act_type?=?hugectr.Activation_t.Relu))???????????????????
          model.add(hugectr.DenseLayer(bottom_names?=?["fc3","sparse_embedding1"],
          ????????????????????????????top_names?=?["interaction1",?"interaction1_grad"]...))
          model.add(hugectr.GroupDenseLayer(...))
          model.add(hugectr.DenseLayer(...))
          • bottom_name_listtop_name_list來表示輸入Tensor列表,輸出Tensor列表,這樣后續(xù)層可以根據(jù)這個名字來實現(xiàn)網(wǎng)絡(luò)層相連。
          • num_outputs表示的是全連接層輸出維度大小
          • last_act_type表示最后一層全連接層的激活層類型,這里是ReLU

          下面我們以全連接層來看一下一個網(wǎng)絡(luò)層的具體實現(xiàn),首先看一下基類Layer的頭文件layer.hpp

          ??...
          ??//?前向計算邏輯
          ??virtual?void?fprop(bool?is_train)?=?0;
          ??//?反向計算邏輯
          ??virtual?void?bprop()?=?0;
          ??virtual?std::string?get_no_trained_params_in_string()?{?return?std::string();?}
          ??void?init_params(const?curandGenerator_t&?generator);

          ??Layer(const?std::shared_ptr&?gpu_resource,
          ????????std::vector?initializer_types?=?std::vector())
          ??????:?gpu_resource_(gpu_resource),?initializer_types_(initializer_types)?{}
          ??Layer(const?Layer&)?=?delete;
          ??Layer&?operator=(const?Layer&)?=?delete;
          ??virtual?~Layer()?=?default;
          ??//?參數(shù)初始化
          ??virtual?void?initialize()?{}
          ??//?算法搜索,比如全連接層,HugeCTR會通過試跑選擇一個最快的算法
          ??virtual?void?search_algorithm()?{}

          我們以fused_relu_bias_fully_connected_layer.cu為具體例子:

          構(gòu)造函數(shù)

          在構(gòu)造函數(shù)中,根據(jù)tensor的形狀信息得到m,n,k,并推算出kernel,bias tensor的形狀等:

          ??size_t?m?=?bottom_tensor_dim[0];
          ??size_t?n?=?top_tensor_dim[1];
          ??size_t?k?=?bottom_tensor_dim[1];

          ??std::vector<size_t>?kernel_dim?=?{k,?n};
          ??std::vector<size_t>?bias_dim?=?{1,?n};
          ??std::vector<size_t>?identity_dim?=?{1,?m};

          接著對這些tensor分配顯存:

          ??{
          ????Tensor2<float>?tensor;
          ????master_weights_buff->reserve(kernel_dim,?&tensor);
          ????weights_.push_back(tensor);
          ??}
          ??{
          ????Tensor2<float>?tensor;
          ????master_weights_buff->reserve(bias_dim,?&tensor);
          ????weights_.push_back(tensor);
          ??}
          ??...

          initialize

          這個Fuse矩陣乘是通過cublasLt實現(xiàn)的,因此在這個函數(shù)里做了一些cublasLt所需要的初始化,如矩陣信息,設(shè)置計算類型,設(shè)置epilogue(指定cublasLt的fuse模式)

          ??HCTR_LIB_THROW(cublasLtMatmulDescSetAttribute(cublas_op_desc_,?CUBLASLT_MATMUL_DESC_TRANSA,
          ????????????????????????????????????????????????&trans,?sizeof(trans)));
          ??HCTR_LIB_THROW(cublasLtMatmulDescSetAttribute(cublas_op_desc_,?CUBLASLT_MATMUL_DESC_TRANSB,
          ????????????????????????????????????????????????&trans,?sizeof(trans)));
          ??...
          ??cublasLtEpilogue_t?epi?=?CUBLASLT_EPILOGUE_RELU_AUX_BIAS;?//?設(shè)置epilogue
          ??...
          ??//?創(chuàng)建kernel,?bias,?output的矩陣維度,數(shù)據(jù)類型
          ??HCTR_LIB_THROW(cublasLtMatrixLayoutCreate(&cublas_kernel_desc_,?CUDA_R_16F,?n,?k,?n));
          ??HCTR_LIB_THROW(cublasLtMatrixLayoutCreate(&cublas_bottom_desc_,?CUDA_R_16F,?k,?m,?k));
          ??HCTR_LIB_THROW(cublasLtMatrixLayoutCreate(&cublas_top_desc_,?CUDA_R_16F,?n,?m,?n));
          ??...

          initialize_dgrad()initialize_wgrad() 則是給矩陣乘后向?qū)?次矩陣乘分別做上述類似初始化,這里不再贅述

          fprop

          調(diào)用cublasLtMatmul進行前向計算:

          ??const?__half*?kernel?=?weights_half_[0].get_ptr();
          ??const?__half*?bias?=?weights_half_[1].get_ptr();
          ??const?__half*?bottom?=?get_bottom_tensor_fprop(is_train).get_ptr();
          ??...
          ??HCTR_LIB_THROW(cublasLtMatmul(...));

          bprop也是類似的邏輯

          search_algorithm

          HugeCTR設(shè)定了一個最大算法個數(shù),通過cublasLtMatmulAlgoGetHeuristic啟發(fā)式搜索算法接口獲取當前全連接層可用的算法,并使用cudaEvent進行計時,選取表現(xiàn)最好的算法:

          ??//?獲取當前可用算法
          ??cublasLtMatmulHeuristicResult_t?heuristic_result[max_algo_count]?=?{0};
          ??int?algo_count?=?0;
          ??HCTR_LIB_THROW(cublasLtMatmulAlgoGetHeuristic(...));
          ??
          ??//?遍歷所有算法
          ??for?(int?algoIdx?=?0;?algoIdx?????cublasStatus_t?status?=?CUBLAS_STATUS_SUCCESS;
          ????const?float?alpha?=?1.0f;
          ????const?float?beta?=?0.0f;
          ????HCTR_LIB_THROW(cudaEventRecord(start,?get_gpu().get_stream()));
          ????//?執(zhí)行repeat_num次矩陣乘
          ????for?(size_t?i?=?0;?i???????status?=?cublasLtMatmul(...);
          ????}
          ????//?記錄時間
          ????HCTR_LIB_THROW(cudaEventRecord(stop,?get_gpu().get_stream()));
          ????HCTR_LIB_THROW(cudaEventSynchronize(stop));
          ????HCTR_LIB_THROW(cudaEventElapsedTime(&time,?start,?stop));
          ????//?Avg?Time(ms)?for?this?alorithm?for?fprop?GEMM
          ????time?=?time?/?repeat_num;
          ????...
          ????//?更新最佳時間
          ????if?(time???????shortestTime?=?time;
          ??????//?把當前最佳的算法拷貝到falgo_k,在計算過程中使用falgo_k
          ??????memcpy(&falgo_k_,?&heuristic_result[algoIdx].algo,?sizeof(falgo_k_));
          ????}
          ??}

          這也是為什么在nsys前半部分,能看到一堆密密麻麻的矩陣乘

          模型搭建完以后,后續(xù)調(diào)用compile, fit執(zhí)行訓練,這部分也可以在model.cpp看到對應實現(xiàn),這里就不展開了,下面我們講下HugeCTR在MLPERF提及到的一些優(yōu)化

          MLPERF1.0

          Hybrid Embedding

          頻繁的Embedding交換是模型訓練過程中的一個重要瓶頸,對此HugeCTR實現(xiàn)了HybridEmbedding。輸入中會存在重復id,因此一開始會剔除掉重復的數(shù)據(jù),對應反向傳播也做相應處理。此外它還針對數(shù)據(jù)做了統(tǒng)計,根據(jù)頻率分為高頻Embedding,低頻Embedding。高頻Embedding以數(shù)據(jù)并行實現(xiàn),這樣能夠在一個batch內(nèi)刪掉更多重復的數(shù)據(jù),減少Embedding交換,而低頻Embedding以模型并行實現(xiàn)。

          通信優(yōu)化

          筆者不太了解通信方面的知識

          all2all和allreduce耗時在拓展模型過程中是很重要的一環(huán),對于比較小的message,多節(jié)點的all2all吞吐量受限于IB的消息速率的限制,為此將All2All分為節(jié)點內(nèi)All2All,節(jié)點間All2All。并將高頻Embedding和MLP的AllReduce放在一個AllReduce操作內(nèi)完成,以減少延遲。

          數(shù)據(jù)讀取優(yōu)化

          采用Linux的異步方式讀取,以達到IO峰值

          重疊MLP和Embedding

          在DLRM中,bottom_mlp部分和Embedding之間不存在依賴,因此做了如下的流水線重疊:

          • BottomMLP前向過程和Embedding前向進行重疊
          • 高頻Embedding在更新local權(quán)重時和AllReduce重疊
          • MLP權(quán)重更新和節(jié)點內(nèi)All2All重疊

          cublasLt算子融合

          cublasLt可以通過epilogue來選擇不同算子融合方式,比如 matmul+bias, matmul+bias+relu,以及對應的后向矩陣乘等

          CUDA Graph

          為了減少kernel launch開銷,將模型的所有操作都包到一個 CUDA Graph 內(nèi)。

          關(guān)于CUDA Graph可以參考 https://zhuanlan.zhihu.com/p/467466998

          MLPERF1.1

          Hybrid Embedding索引預計算

          在之前的Hybrid Embedding中需要計算索引來決定在哪兒讀取對應的Embedding,而索引計算只依賴于輸入數(shù)據(jù),這些數(shù)據(jù)可以在提前幾個iter時候預取好(Prefetch),預先計算好Index,以隱藏延遲

          通信與計算之間更好的重疊

          這里就不過多闡述了,這里的圖描述的十分詳細

          異步梯度計算

          在矩陣乘中,其反向?qū)?個矩陣乘,而這兩個矩陣乘接受相同的輸入dy,分別輸出weight,input的梯度。因此這兩個計算可以重疊起來,具體在代碼中FusedReluBiasFullyConnectedLayer::bprop()

          //?dgrad
          HCTR_LIB_THROW(cublasLtMatmul(...,?get_gpu().get_stream()));

          //?bgrad+wgrad
          HCTR_LIB_THROW(cublasLtMatmul(...,?get_gpu().get_comp_overlap_stream()));

          在nsys中,是這樣:

          Better Fusion

          其中一個是CublasLt提供了更多的fuse方式,另外一個是在混合精度情況下,將fp32權(quán)重cast成fp16的部分,放到Optimizer更新時候做,這樣就避免單獨啟動Cast Kernel,在SGD優(yōu)化器代碼中可以看到對應的操作sgd_optimizer.cu

          template?<typename?T>
          __device__?inline?void?sgd_update_device(int?len,?float*?weight,?__half*?weight_half,
          ?????????????????????????????????????????const?T*?wgrad,?float?lr,?float?scaler)
          ?
          {
          ??...
          ??weight[i]?-=?lr?*?gi;
          ??weight_half[i]?=?(__half)weight[i];??
          }


          瀏覽 66
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  永久免费 看片视频 | 无码欧美人XXXXX日本无码 | 精品免费囯产一区二区三区四区的使用方法 | 最新中文字幕在线 | 亚洲人成网77777色在线播放 |