<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>

          以O(shè)neFlow為例探索MLIR的實(shí)際開發(fā)流程

          共 9656字,需瀏覽 20分鐘

           ·

          2021-12-15 08:45

          前言

          最近在同事shenghang的幫助下做了一點(diǎn)OneFlow IR相關(guān)的開發(fā),對MLIR執(zhí)行部分有一些新的感受,所以嘗試分享一下。我之前花了不少時(shí)間去理解OneFlow IR的整個(gè)架構(gòu)(可以看我的Toy Tutorials系列),但對OneFloiw IR的JIT的執(zhí)行這部分一直存疑。最近將OneFlow基于Job(OneFlow的作業(yè)函數(shù),不考慮設(shè)備的話可以理解為一個(gè)計(jì)算圖)接入MLIR工程實(shí)現(xiàn)部分重新進(jìn)行了梳理,并在shenghang的指導(dǎo)下理解了整個(gè)流程。所以這篇文檔我將介紹一下OneFlow和MLIR是如何結(jié)合的,如何在OneFlow IR中新增一個(gè)圖級別的Pass,OneFlow的Operation是如何自動(dòng)變成MLIR 的Operation的以及為什么OneFlow IR能利用MLIR為計(jì)算帶來加速等。我對MLIR的了解不算多,2個(gè)月前開始接觸,有任何錯(cuò)誤請大家批評斧正。本文和 https://github.com/Oneflow-Inc/oneflow & https://github.com/BBuf/tvm_mlir_learn 有關(guān),感興趣可以star關(guān)注一下。

          本文提到的Op和Operation是一回事,沒有嚴(yán)格區(qū)分。

          OneFlow是如何和MLIR結(jié)合的?

          在OneFlow中引入MLIR作為OneFlow的IR有諸多優(yōu)點(diǎn),不僅可以取代OneFlow中需要通過C++手寫的Operation定義減小開發(fā)難度,還可以降低Operation定義中一些容器相關(guān)的開銷。另外我們還可以通過MLIR維護(hù)的基礎(chǔ)設(shè)施(即多重Dialect)來完成對計(jì)算圖計(jì)算的加速。這里的計(jì)算圖既可以是Eager的計(jì)算圖,也可以是Lazy的計(jì)算圖。由于基于Eager計(jì)算圖使用MLIR進(jìn)行加速的工作(即oneflow.jit.xxx)還沒有正式開放,我這里仍然以Lazy計(jì)算圖(Job)為例來講解OneFlow和MLIR的結(jié)合過程。

          首先我們需要編譯好開啟MLIR的OneFlow,編譯命令如下:

          [email protected]:Oneflow-Inc/oneflow.git
          cd?oneflow?&&?mkdir?build?&&?cd?build
          cmake-C?../cmake/caches/cn/fast/mlir-cuda-75.cmake?-DBUILD_TESTING=ON?..?&&?ninja?

          然后可以寫一個(gè)例子進(jìn)行測試:

          os.environ["ONEFLOW_MLIR_ENABLE_ROUND_TRIP"]?=?'1'
          os.environ["ONEFLOW_MLIR_ENABLE_CODEGEN_FUSERS"]?=?'1'

          @flow.unittest.skip_unless_1n1d()
          class?TestFuseBiasAddGeLUCPUMLIR(oneflow.unittest.TestCase):
          ????def?test_fused_bias_add_gelu_graph(test_case):
          ????????data?=?np.random.randn(1,?2,?3)
          ????????bias_data?=?np.random.randn(2)
          ????????x?=?flow.tensor(data,?dtype=flow.float32)
          ????????bias?=?flow.tensor(bias_data,?dtype=flow.float32)
          ????????y_eager?=?flow.gelu(flow._C.bias_add(x,?bias,?axis=1))

          ????????class?FuseBiasAddGeLUGraph(flow.nn.Graph):
          ????????????def?__init__(self):
          ????????????????super().__init__()

          ????????????def?build(self,?x):
          ????????????????return?flow.gelu(flow._C.bias_add(x,?bias,?axis=1))

          ????????bias_add_gelu?=?FuseBiasAddGeLUGraph()
          ????????y_lazy?=?bias_add_gelu(x)
          ????????test_case.assertTrue(np.array_equal(y_eager.numpy(),?y_lazy.numpy()))

          運(yùn)行這個(gè)例子之后會在當(dāng)前運(yùn)行目錄下生成一個(gè)log文件,里面有一個(gè)ir_pass 文件夾記錄了經(jīng)過OneFlow MLIR優(yōu)化前后的計(jì)算圖(.prototxt) 以及 MLIR的表達(dá)式(*.mlir),還有一個(gè)*.mlir.dot文件可以用graphviz打開來可視化MLIR表達(dá)式的計(jì)算圖。需要注意的是如果OneFlow正在執(zhí)行訓(xùn)練任務(wù),這個(gè)log文件夾里不僅包含前向的計(jì)算圖和MLIR表達(dá)式,也會生成后向的計(jì)算圖和MLIR表達(dá)式。所以MLIR在整個(gè)神經(jīng)網(wǎng)絡(luò)的運(yùn)行流程中均可以作用,這是區(qū)別于前向推理框架的重要一點(diǎn),即訓(xùn)練也可以加速。

          oneflow/api/python/ir.cpp 中有下面兩行代碼:

          REGISTER_JOB_PASS("IRRoundTripBeforeAD",?IRRoundTrip);
          REGISTER_JOB_PASS("IRRoundTrip",?IRRoundTrip);

          RoundTrip即往返的意思,BeforeAD可以理解為反向之前,kAfterAD 可以理解為反向之后,這里通過將OneFlow Job和MLIR的互轉(zhuǎn)過程注冊為OneFlow Job的一個(gè)Pass來建立OneFlow計(jì)算圖和MLIR的聯(lián)系。在執(zhí)行OneFlow腳本時(shí),如果想使能MLIR作用于OneFlow計(jì)算圖,開啟ONEFLOW_MLIR_ENABLE_ROUND_TRIP=1環(huán)境變量即可。

          接下來,要將OneFlow的計(jì)算圖和MLIR建立聯(lián)系等價(jià)于將OneFlow計(jì)算圖中的Operation和MLIR中的Operation進(jìn)行一對一的轉(zhuǎn)換。而MLIR的Operation定義在各級Dialect下,按照MLIR的通用接入原則,我們實(shí)現(xiàn)了一個(gè)OneFlow Dialect并在OneFlow Dialect上實(shí)現(xiàn)了OneFlow Operation到OneFlow Dialect下的Operation的一一映射。如何定義OneFlow Dialect和Operation這里就不講了,可以參考MLIR官方文檔的Dialects和ODS一節(jié)(https://mlir.llvm.org/docs/OpDefinitions/)或者我之前的文章,它們都是基于TableGen規(guī)則來完成的。關(guān)于MLIR Operation的定義我之前結(jié)合OneFlow Dialect的Op定義總結(jié)了一個(gè)文檔(https://github.com/BBuf/tvm_mlir_learn 中) 。除了Dialect和Operation的定義還有一些其它需要定義的東西,比如OneFlow數(shù)據(jù)類型到MLIR數(shù)據(jù)類型映射的定義在oneflow/ir/include/OneFlow/OneFlowEnums.td ,OneFlow Dialect Operation的一些通用前端接口定義在oneflow/ir/include/OneFlow/OneFlowEnums.td。這里我們以Reshape Operation為例子來簡單說明一下這個(gè)Operation有哪些組成部分:

          def?OneFlow_ReshapeOp?:?OneFlow_BaseOp<"reshape",?[NoSideEffect,?DeclareOpInterfaceMethods]>?{
          ??let?input?=?(ins
          ????AnyType:$in
          ??);
          ??let?output?=?(outs
          ????AnyType:$out
          ??);
          ??let?attrs?=?(ins
          ????AnyI64ElementsAttr:$shape
          ??);
          }

          OneFlow_ReshapeOp 這個(gè)名字下劃線之前的是Dialect的名字,后面是這個(gè)Dialect下的Operation的名字。然后這個(gè)Operation繼承了OneFlow_BaseOp基類,并聲明了約束和前端接口,接下來定義了Operation的輸入,輸出和屬性就結(jié)束了。可以發(fā)現(xiàn)OneFlow Dialect Operation的定義和OneFlow User Op是完全一致的,這保證了OneFlow和MLIR互轉(zhuǎn)的合法性。OneFlow Reshape Operation的定義如下:

          REGISTER_USER_OP("reshape")
          ????.Input("in")
          ????.Output("out")
          ????.Attr("shape")
          ????...

          OneFlow Job和MLIR的互轉(zhuǎn)實(shí)現(xiàn)在oneflow/ir/oneflow-translate,主要做的事情就是遍歷Job的OpGraph,對節(jié)點(diǎn)和邊分別進(jìn)行處理最后轉(zhuǎn)換成一個(gè)MLIR表達(dá)式,同時(shí)在計(jì)算完成后可以基于MLIR表達(dá)式重寫Job。這里的整體邏輯偏復(fù)雜,因?yàn)橐幚鞳neFlow Job OpGraph里面各種類型Operation和邊的轉(zhuǎn)化,這里不繼續(xù)深入講解,因?yàn)樗膊皇俏疫@篇文章要討論的點(diǎn),感興趣的可以直接閱讀代碼。

          OneFlow IR如何執(zhí)行?

          在上面Operation定義時(shí)是舉了一個(gè)Reshape的例子,瀏覽oneflow/ir/include/OneFlow/OneFlowOps.td容易發(fā)現(xiàn)這里還定義了一個(gè)OneFlow_MlirJitOp,這個(gè)自定義的Op就是用來執(zhí)行MLIR表達(dá)式的,它里面實(shí)現(xiàn)了CPU和GPU的Kernel(源碼在oneflow/ir/oneflow-extension/extension.cpp)用來加載MLIR提供的JIT執(zhí)行引擎運(yùn)行最終得到的LLVM IR。那么LLVM IR又是怎么來的呢?這是通過OneFlow MLIR表達(dá)式逐級下降之后得來的,具體下降過程如下:

          void?AddLowerToLinalgMemRefPasses(PassManager&?pm)?{
          ??pm.addPass(createLowerOneFlowToTosaPass());????????????//?lower-oneflow-to-tosa
          ??pm.addPass(createCSEPass());???????????????????????????//?cse
          ??pm.addNestedPass(tosa::createTosaToLinalg());??//?tosa-to-linalg-on-tensors
          ??auto?p?=?createLinalgElementwiseOpFusionPass();
          ??assert(p->initializeOptions("allow-folding-unit-dim-reshapes=true").succeeded());
          ??pm.addNestedPass(std::move(p));?????????????????????//?linalg-fuse-elementwise-ops
          ??pm.addNestedPass(createLinalgBufferizePass());??????//?linalg-bufferize
          ??pm.addNestedPass(createTensorBufferizePass());??????//?tensor-bufferize
          ??pm.addPass(createTensorConstantBufferizePass());????????????//?tensor-constant-bufferize
          ??pm.addPass(createFuncBufferizePass());??????????????????????//?func-bufferize
          ??pm.addPass(createBufferResultsToOutParamsPass());???????????//?buffer-results-to-out-params
          ??pm.addPass(createCanonicalizerPass());??????????????????????//?canonicalize
          ??pm.addNestedPass(createFinalizingBufferizePass());??//?finalizing-bufferize
          }

          LogicalResult?LowerModuleToLLVM(mlir::MLIRContext*?context,?ModuleOp?module)?{
          ??mlir::PassManager?pm(context);
          ??AddLowerToLinalgMemRefPasses(pm);
          ??pm.addNestedPass(createConvertLinalgToLoopsPass());??//?convert-linalg-to-loops
          ??pm.addNestedPass(createLowerToCFGPass());????????????//?convert-scf-to-std
          ??pm.addPass(createConvertLinalgToLLVMPass());?????????????????//?convert-linalg-to-llvm
          ??pm.addPass(createMemRefToLLVMPass());????????????????????????//?convert-memref-to-llvm
          ??pm.addPass(createLowerToLLVMPass());?????????????????????????//?convert-std-to-llvm
          ??pm.addPass(createReconcileUnrealizedCastsPass());
          ??return?pm.run(module);
          }

          可以看到OneFlow Dialect首先下降到Tosa Dialect,然后下降到Linalg Dialect,再然后是Loop Dialect,一直到最后的LLVM IR。在逐級下降的過程中,我們可以享受如Linalg Dialect帶來的嵌套循環(huán)變換帶來的優(yōu)化機(jī)會以提升最終IR的性能。這里的Lowering過程是在OneFlow調(diào)用MlirJitOp 的Kernel時(shí)觸發(fā)的(oneflow/ir/oneflow-extension/extension.cpp ),調(diào)用也是作為一個(gè)MLIR的Pass被加入到了優(yōu)化流程中。JIT調(diào)用流程Pass的實(shí)現(xiàn)可以精簡為:

          class?OutlineJitFunctionPass?:?public?OutlineJitFunctionPassBase?{
          ??void?runOnOperation()?override?{
          ????Operation*?op?=?getOperation();
          ????RewritePatternSet?patterns(op->getContext());
          ????oneflow::populateFuserPasses(patterns);
          ????(void)applyPatternsAndFoldGreedily(op,?std::move(patterns));
          ??}
          };

          std::unique_ptr?createOutlineJitFunctionPass()?{
          ??return?std::make_unique();
          }

          LogicalResult?ApplyRoundTripPatterns(RoundTripOneFlowJobWrapperInterface&?job_wrapper,
          ?????????????????????????????????????MLIRContext*?context,?OwningModuleRef&?module)
          ?
          {
          ??mlir::PassManager?pm(context);
          ??pm.addNestedPass(::mlir::createCanonicalizerPass());
          ??if?(job_wrapper.IsLastIRPass()?&&?std::getenv("ONEFLOW_MLIR_ENABLE_CODEGEN_FUSERS")?!=?nullptr)?{
          ????pm.addPass(oneflow::createOutlineJitFunctionPass());
          ??}
          ??...
          }

          但這套流程還存在兩個(gè)問題需要解決:

          • 第一個(gè)問題是如何做Op融合。上面的JIT執(zhí)行流程只考慮了不斷Lowering,那么假如在OneFlow Dialect中有一些Operation是可以融合的,這個(gè)時(shí)候應(yīng)該怎么做呢?很簡單,我們沿用一下MLIR的DRR規(guī)則,還是用TableGen語法在oneflow/ir/include/OneFlow/OneFlowPatterns.td 中寫一系列的Fuse Pattern即可,比如bias_add+gelu 這兩個(gè)Op可以融合成OneFlow中的fused_bias_add_gelu Op,那么就可以寫如下的規(guī)則。
          def?IsGPU:?Constraint"$0.getValue().equals(\"gpu\")">,?"is?GPU?device">;
          def?FusedBiasAddGeluPattern?:?Pat<
          ??(
          ????OneFlow_GeluOp?:?$gelu_op
          ????(
          ??????OneFlow_BiasAddOp
          ????????$a,
          ????????$b,
          ????????$bias_add_op_name,
          ????????$bias_add_device_tag,
          ????????$bias_add_device_name,
          ????????$bias_add_scope_symbol_id,
          ????????$bias_add_hierarchy,
          ????????$axis
          ????),
          ????$gelu_op_name,
          ????$gelu_device_tag,
          ????$gelu_device_name,
          ????$gelu_scope_symbol_id,
          ????$gelu_hierarchy
          ??),
          ??(OneFlow_FusedBiasAddGeluOp?$a,?$b,
          ????$gelu_op_name,
          ????$gelu_device_tag,
          ????$gelu_device_name,
          ????$gelu_scope_symbol_id,
          ????$gelu_hierarchy,
          ????$axis
          ??),
          ??[
          ????(IsGPU?$bias_add_device_tag),
          ????(IsGPU?$gelu_device_tag)
          ??]
          >;

          這里基于MLIR的DRR規(guī)則來做表達(dá)式匹配和重寫,可以看到假如當(dāng)前運(yùn)行設(shè)備是GPU并且前后兩個(gè)Op分別是gelubias_add 就將其進(jìn)行融合為一個(gè)fused_bias_add_gelu_op,在CUDA上可以減少讀寫來提升執(zhí)行效率。

          • 第二個(gè)問題是如何讓OneFlow的一些Operation享受MLIR基礎(chǔ)設(shè)施中的更多優(yōu)化?在多級Dialect 逐層下降時(shí)可以看到OneFlow的MLIR表達(dá)式的每個(gè)子函數(shù)都會被Lower。第一次會將其Lower到Tosa Dialect,這個(gè)時(shí)候如果這個(gè)子函數(shù)中的某個(gè)Operation沒有定義轉(zhuǎn)換到Tosa Dialect的方法,那么就不能Lower到Tosa Dialect。自然也就不能進(jìn)一步下降為Linalg Dialect,享受不到一些循環(huán)變化帶來的優(yōu)化(我感覺可以類比TVM的scheduler優(yōu)化)。為了解決這種情況我們需要額外再定義一個(gè)Pass來將當(dāng)前需要轉(zhuǎn)換為Tosa的Op或者模式提取成一個(gè)函數(shù),里面的oneflow op都能夠lower到tosa,然后生成一個(gè) oneflow mlir jit op 來 call 這個(gè)函數(shù):
          def?IsNotNestedInJit:?Constraint"(!$0.getDefiningOp()->getParentOfType<::mlir::FuncOp>()->hasAttr(\"llvm.emit_c_interface\"))">,?"">;
          def?OutlineMulCast?:?NativeCodeCall<"::mlir::oneflow::OutlineMulCast($_builder,?$0,?$1)">;
          //?TODO:?remove?attr?binding?if?possible
          def?MulCastPattern?:?Pat<
          ??(
          ????OneFlow_ScalarMulByTensorOp?:?$mul_op
          ????(
          ??????OneFlow_CastOp?:?$cast_op
          ????????$cast_x,
          ????????$cast_op_name,
          ????????$cast_device_tag,
          ????????$cast_device_name,
          ????????$cast_scope_symbol_id,
          ????????$cast_hierarchy,
          ????????$cast_dtype
          ????),
          ????$scalar,
          ????$mul_op_name,
          ????$mul_device_tag,
          ????$mul_device_name,
          ????$mul_scope_symbol_id,
          ????$mul_hierarchy
          ??),
          ??(OutlineMulCast?$mul_op,?$cast_op),
          ??[
          ????(IsNotNestedInJit?$mul_op)
          ??]
          >;

          ::llvm::SmallVector<::mlir::Value,?4>?OutlineMulCast(::mlir::PatternRewriter&?rewriter,
          ?????????????????????????????????????????????????????mlir::OpResult?mul_res,
          ?????????????????????????????????????????????????????mlir::OpResult?cast_res)
          ?
          {
          ??if?(auto?mul_op?=?llvm::dyn_cast(mul_res.getDefiningOp()))?{
          ????if?(auto?cast_op?=?llvm::dyn_cast(cast_res.getDefiningOp()))?{
          ??????//?TODO:?extract?a?function?to?generate?op?name?for?jit?op?from?ops?being?fused
          ??????SmallString<64>?op_name_storage;
          ??????auto?op_name?=
          ??????????(cast_op.op_name()?+?"__FUSE__"?+?mul_op.op_name()).toStringRef(op_name_storage);
          ??????SmallVector<::mlir::Value,?2>?operands;
          ??????operands.push_back(cast_op.in());
          ??????operands.push_back(mul_op.scalar());
          ??????SmallVector<::mlir::Value,?1>?results;
          ??????results.push_back(mul_op.y());
          ??????NamedAttrList?attributes?=
          ??????????GetJitOpAttributes(rewriter,?op_name,?operands.size(),?results.size(),?mul_op);
          ??????SmallVector4>?ops?=?{cast_op,?mul_op};
          ??????auto?function?=
          ??????????GetOrInsertFuncOp(rewriter,?mul_op->getLoc(),?op_name,?operands,?results,?ops);
          ??????auto?created?=?rewriter.create(mul_op.getLoc(),?function,?attributes,?operands);
          ??????assert(DumpAssembly(rewriter,?created).succeeded());
          ??????cast_op->dropAllUses();
          ??????cast_op.erase();
          ??????return?created->getResults();
          ????}
          ??}
          ??return?{};
          }

          void?populateFuserPasses(::mlir::RewritePatternSet&?patterns)?{
          ??patterns.add(patterns.getContext());
          }

          這里就是將MulCast這個(gè)Pattern手動(dòng)實(shí)現(xiàn)了從OneFlow Dialect到Tosa Dialect的轉(zhuǎn)換,最后將這個(gè)Pass加到優(yōu)化流程中即可完成MLIR表達(dá)式中的這個(gè)Pattern會經(jīng)過Tosa和Linalg這兩個(gè)層次的Dialect,獲得一些優(yōu)化機(jī)會。

          總結(jié)

          這里以O(shè)neFlow為例講解了一些MLIR的真實(shí)運(yùn)行流程,即是如何通過MLIR來執(zhí)行深度學(xué)習(xí)框架的計(jì)算圖并且為其加速的,目前理解難免有不到位的地方,歡迎大家批評指正。


          瀏覽 79
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  亚洲www啪成人一区二区麻豆 | 高清无码一区二区在线 | 成人免费黄色视频网站 | 内射91视频 | 色老板精品无码免费播放 |