0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

在BuildRelay中会调用Codegen函数

电子设计 来源:电子设计 作者:电子设计 2022-02-08 16:02 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

作者:安平博,Xilinx高级工程师;来源:AI加速微信公众号

接着上一章继续深入代码,在BuildRelay中会调用Codegen函数。这个函数实现在src/relay/backend/graph_runtime_codegen.cc中。Codegen实现了内存的分配,IR节点到TIR节点的转换,tir图节点的一个调度优化。内存分配由函数relay.backend.GraphPlanMemory来实现,VisitExpr对节点进行遍历并进行节点信息的记录。LowerExternalfunctions完成ir节点到tir节点的转化以及schedule的优化。

pIYBAGAJmYKAeKUpAAOkJNgC9IE642.png

内存分配

通过GetPackedFunc函数来获得注册到global map的内存分配函数GraphPlanMemory。我们看一下文件src/relay/backend/graph_plan_memory.cc中对内存的处理。

o4YBAGAJmcKARl1CAACGZg0dj7U340.png

在处理内存分配中主要使用了StorageAllocaBaseVisitor,StorageAllocaInit,StorageAllocator这三个类。StorageAllocaBaseVisitor是一个基类,实现了对每个节点的访问,并分配token,但是token中信息是在派生类中处理的。定义了一个StorageToken的结构体,用于表示申请到内存的大小,类型等信息。在内存处理程序中,主要就是为每个节点分配这个token,同时定义token的内部信息。内存分配结果是一个节点和token的映射表。

o4YBAGAJmgGAEqo-AADcglyV-4Y415.png

StorageAllocator类中Plan函数为:

o4YBAGAJmkGASccNAAKmVvtXPgY414.png

关键是前两行代码,第一行代码初始化了storageToken,赋予了其设备类型和数据类型信息。第二行代码遍历每个节点,并且为每个节点分配内存空间。在内存初始化函数GetInitTokenMap中,首先收集每个节点的的设备信息。调用链为CollectDeviceInfo -> GetDeviceMap(src/relay/transforms/device_annotation.cc)。在构建relay图结构的时候,每个节点是有设备号信息的,GetDeviceMap就是按照post-DFS顺序获得节点的设备号信息。当然并不是所有节点都有设备号信息,所以还需要根据节点之间的关系来推断出设备号。比如下图,add,sqrt,log节点被标注为1,2,3号设备,那么可以用两种方式来推断其它节点设备号。

1) 从一个copy节点由下而上遍历一直到遇到下一个copy,比如可以推断出add,x,y节点的设备号和copy1一样;
2) 从最后一个copy节点向下遍历,那么可以推断出substract,exp设备号和copy3一样。

pIYBAGAJmpqAIGavAACO4hsCsQ8586.png

设备号获得后,this->run会调用基类的run函数,基类run函数会调用派生类的CreateToken函数。CreateToken会申请StorageToken空间并且赋予设备号和数据类型,然后返回一个token_map_。和节点遍历相关函数为Run->GetToken->VisitExpr。VisitExpr会最终调用StorageAllocaInit类中定义的VisitExpr_函数来遍历节点。

节点内存初始化完成后,回到StorageAllocator类中,run会调用其定义的CreateToken函数。

pIYBAGAJmt6AE8_gAALBr5QvaFI549.png

分配内存空间会有两种情况,一种是can_realloc一种是不能can_realloc的。先看不can_realloc的,GetMemorySize是根据token中记录的数据类型和shape信息来获得数据的大小,Alloc函数就是为tok分配字节数量。现在看can_realloc的情况,Request中首先获取节点数据的大小。然后从free_中查询能够满足size的节点,如果有比该节点size大的就选择大的空闲区间分配,如果没有大的空间分配,选择最接近的空间分配。然后最终返回一个token_map_。

codegen

第一步是对ir节点进行遍历,转换成codegen中定义的基础节点。我们先看以下codegen中定义的节点类型,GraphNode是基础节点,GraphInputNode, GraphOpNode继承自这个基础节点。这些节点中主要提供了一些节点属性,比如name,op类型等。还提供了dmlc接口,可以实现可视化。

遍历func的parameters,将parameters转换到graph的input节点。通过AddNode添加这些input节点,并且将转换后的graphInputNode加入var_map_中,var_map_中是expr到graphNode的映射。

接下来是节点遍历,heads_=VisitExpr(func->body)。节点遍历过程中会将func中的节点转换为graphNode。对于varNode,因为已经记录在var_map_中,直接返回引用。ConstantNode会转换为GraphInputNode,tuppleNode会返回每个字段的graphNode。在遍历节点过程中,会将graphNode都添加到nodes_中。

重点看一下对CallNode的处理,只支持op是functionNode类型的。

pIYBAGAJmx6AF101AAGj_ZQNbQ8072.png

Function生成时,走两个分支,一个是外部codegen,一个是通用分支。对应外部function codegen的处理为:

pIYBAGAJm2CAfy9BAANCOTr1_2U471.png

首先创建一个CCacheKey类型作为_CompileEngineLower函数的参数传入。具体CcacheKey有什么作用,以后再深入研究吧。_CompileEngineLower的实现在文件src/relay/backend/compile_engine.cc中。调用链为Lower -> LowerInternal(key)->cached_func。定义了一个cache_node并封装成cached_func返回。这块具体的操作并不是很理解,可能还需要熟悉cachedFuncNode的作用。

o4YBAGAJm7yAUjp_AAKzrtE_tdQ078.png

然后通过GraphAddCallNode将其加入nodes_中。在GraphAddCallNode中还会对op->args进行深入遍历。

内部func处理如下:

o4YBAGAJm_uALky3AAGAfeWt9Xs683.png

也是通过相同的pf0和pf1函数。CcacheKey的创建过程一样,但是在lowerInternal中不一样。

o4YBAGAJnD6ATZm6AAStQTbokG4194.png

首先创建了一个schedule,schedule的具体实现很复杂目前还不够理解。

如果是copy节点,那么不进行lower处理,直接返回CachedFunc封装。不是copy节点,如果我们在python中自己定义了lower函数就调用python中的,如果没有就会调用TVM中的lower函数。Lower函数在src/driver/driver_api.cc文件中。在这里调用了很多tir的passes来进行一个节点转换。这块后边再详细看。

审核编辑:何安

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 函数
    +关注

    关注

    3

    文章

    4406

    浏览量

    66841
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    控制流和函数调用的精细调整

    特性,避免不必要的计算。 函数调用涉及开销,因为它需要保存当前执行环境并跳转到新的执行环境。减少函数调用,尤其是频繁执行的循环中,可以显著
    发表于 11-14 06:32

    深入了解系统调用API:探索操作系统底层的关键接口

    ,也无法使用内核函数。当用户进程必须访问内核或使用某个内核函数时,就得使用系统调用(System Call)。Linux中,系统调用是用户
    的头像 发表于 11-03 09:20 439次阅读

    调用rt_timer_check函数就报错怎么解决?

    调用rt_timer_check函数就报错 (rt_interrupt_get_nest() > 0) assertion failed at function
    发表于 09-18 06:11

    告别代码迷宫!exgnss扩展库让GNSS定位开发秒变简单

    提供三种比较经典的应用模式,可根据实际项目需求灵活运用。 应用模式1:exgnss.DEFAULT 打开gnss后,gnss定位成功时,如果有回调函数会调用回调函数; 使用此应用模式调用
    的头像 发表于 09-10 17:23 644次阅读
    告别代码迷宫!exgnss扩展库让GNSS定位开发秒变简单

    详解hal_entry入口函数

    当使用RTOS时,程序从main函数开始进行线程调度;当没有使用RTOS时,C语言程序的入口函数main函数调用了hal_entry函数。由
    的头像 发表于 07-25 15:34 1664次阅读

    C语言中的内联函数与宏

    C编程中,内联函数和宏都用于避免函数调用的开销并编写可复用的逻辑部分,但它们工作方式和安全性方面存在显著差异。
    的头像 发表于 07-25 15:10 1718次阅读
    C语言中的内联<b class='flag-5'>函数</b>与宏

    调用rt_timer_check函数就报错,为什么?

    调用rt_timer_check函数就报错 (rt_interrupt_get_nest() > 0) assertion failed at function
    发表于 06-10 06:18

    HarmonyOS5云服务技术分享--ArkTS调用函数

    根据场景选择: ▫️ API客户端鉴权(Client适用):APP/本地应用调用 ▫️ API客户端鉴权(Server适用):云函数调用 勾选decode选项(处理表单数据必备) ? 小贴士:保存配置
    发表于 05-22 18:22

    verilog模块的调用、任务和函数

    在做模块划分时,通常会出现这种情形,某个大的模块中包含了一个或多个功能子模块,verilog是通过模块调用或称为模块实例化的方式来实现这些子模块与高层模块的连接的.
    的头像 发表于 05-03 10:29 1286次阅读
    verilog模块的<b class='flag-5'>调用</b>、任务和<b class='flag-5'>函数</b>

    如何使用LAX_CODEGEN启用动态内存分配?

    我目前正在探索NXP_MBDToolbox_LAX。lax_codegen 生成的代码基于静态分配。我想管理大型向量,如何使用 LAX_CODEGEN 启用动态内存分配?
    发表于 04-10 08:09

    函数指针的六个常见应用场景

    函数指针嵌入式开发中有着广泛的应用,它让代码更加灵活,减少冗余,提高可扩展性。很多时候,我们需要根据不同的情况动态调用不同的函数,而函数
    的头像 发表于 04-07 11:58 1130次阅读
    <b class='flag-5'>函数</b>指针的六个常见应用场景

    使用SysTick_Config函数写延时函数,显示SysTick_Config无法被调用如何解决?

    使用SysTick_Config函数写延时函数,但显示SysTick_Config无法被调用,怎么解决呢?
    发表于 03-12 06:56

    CM32M433R MCU上调用riscv_sqrt_f32()函数的计算速度比直接调用sqrtf()要慢,为什么?

    CM32M433R MCU上调用riscv_sqrt_f32()函数的计算速度比直接调用sqrtf()要慢, 计算一次riscv_sqrt_f32大概54 cycles;sqrtf(
    发表于 03-07 14:18

    如何把两个数据返回给调用函数

    函数的处理结果包含两个数据,如何把两个数据返回给调用函数? 第一种,把两个数据封装成一个结构体,函数返回结构体。 调用
    的头像 发表于 01-08 10:15 679次阅读

    EE-128:C语言中的DSP:从C调用汇编类成员函数

    电子发烧友网站提供《EE-128:C语言中的DSP:从C调用汇编类成员函数.pdf》资料免费下载
    发表于 01-07 13:48 0次下载
    EE-128:C语言中的DSP:从C<b class='flag-5'>调用</b>汇编类成员<b class='flag-5'>函数</b>