CANN/GE项目编码红线规范
编码红线【免费下载链接】geGEGraph Engine是面向昇腾的图编译器和执行器提供了计算图优化、多流并行、内存复用和模型下沉等技术手段加速模型执行效率减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的友好接入能力并同时支持 onnx、pb 等主流模型格式的解析与编译。项目地址: https://gitcode.com/cann/ge通用规则1. 禁止硬编码敏感信息、使用禁用敏感词源码硬编码有明文密码密钥等高级别敏感数据。存在历史版本整改遗留的废弃账号和密码后续代码交付如CSEC认证时就有可能被客户质疑为隐藏的后门账号风险很高建议产品完全排查后进行彻底清理。用途不明、数据定义含糊的大数组或者功能可读性差容易被怀疑存在隐藏后门。《EWA-Canada安全认证介绍》也提出如此要求。代码或者配置中硬编码一个公网IP地址、域名、邮箱地址、容易造成质疑的敏感词如backdoor等会造成非法收集回传客户数据的质疑有后门之嫌。因此对于硬编码公网IP地址等要逐一排查确认是否有合理解释。尤其不能包含指向华为或中国的ip地址。2. 外部数据作为数组索引或者指针偏移时必须确保在地址大小范围内数组索引直接由外部控制问题根本原因在于内部分配的空间不足于支撑外部数据访问。因此若外部第三方通过通信接口访问内存相关的操作中则应该注意是否存在越出数组以外场景外部数据作为数组索引对内存进行访问时必须对数据的大小进行严格的校验。3. 整数之间运算时必须严格检查确保不会出现溢出、反转、除0整数问题归根结底是内存问题。因此若整数运算结果用于与内存相关的操作中则应该注意整数是否会产生溢出/反转。4. 内存或句柄为指针类型的资源释放之后应立即赋予新值异常分支必须同步释放避免导致资源泄露资源泄露通常指文件操作句柄使用完后未关闭内存使用完后未释放数据库等使用完后未关闭资源泄露会导致系统资源耗尽造成DoS。此类问题多发生于异常分支、各种条件分支里。在编码过程中需格外关注。5. 内存申请前必须判断大小申请后必须校验是否成功内存申请的大小可能来自于外部数据必须检查其合法性防止过多地、非法地申请内存。不能申请0长度的内存。外置allocator需要校验返回的结构体指针以及结构体内部的data指针是否都有效。补充说明外置allocator指用户可以通过ge_api_v2.cc中的接口注册的allocator使用时没有区分因此校验逻辑需要覆盖外置allocator返回结果。6. 不要编写依赖参数求值顺序的代码如func(a, a)这会导致未定义行为在C/C语言中函数参数的求值顺序是未指定的这意味着编译器可以以任意顺序对参数进行求值。因此像func(a, a)这样的代码由于我们无法知道a和a这两个参数哪一个先被求值所以会导致未定义行为。GE框架规则1. 对外开放接口的预留参数要有严格校验防止后续版本启用存在兼容性问题预留参数需要强制用户使用无效值否则后续开放使用后之前用户随意填的数值在老版本上运行正常而在新版本上运行异常成为兼容性问题。譬如指针类参数导致coredump指针类数值参数类导致逻辑错误。补充说明数值类预留参数的无效值一般是 0。指针类预留参数的无效值是 nullptr。2. 对于对外接口的返回值或参数内存长度的数据类型需定义为size_t类型内存长度超过4G的场景下32位无法表达使用32位数据类型的话遇到对应场景后需要增加同样逻辑的新接口造成多余的开发和维护成本。3. 修改对外开放接口时必须严格确保API和ABI兼容inc/external目录下的头文件为对外开放的接口需要严格确保ABI和API兼容性如果不兼容会导致客户代码修改或者重新编译原有程序无法运行。补充说明inc/external目录下的头文件即为全部对外接口的边界其他目录视为内部接口可以自由修改。C对外接口一般使用类而非结构体扩展时通过增加类的方法实现不修改已有方法签名。C对外结构体有预留字段。新建对外结构体时也必须预留字段。对外头文件的公开接口中禁止使用std::string包括函数参数、返回值、类/public 成员等因为不同 C 标准库/编译器版本下std::string的底层实现可能不同导致 ABI 不兼容。应使用AscendString代替。4. 禁止改图时进行不等价转换必须考虑控制、数据两类关系等价GE的图优化正常是完全不改变图的计算结果的在做这类优化时要考虑数据、控制关系同时等价。补充说明数据等价指输出张量一致。控制等价指控制边所约束的执行顺序不变。等价判定的范围是被修改的几个节点之间不要求全局所有节点的执行顺序不变。5. 禁止新增有时序依赖pass时不增加注释必须说明依赖内容并且备案申请新增有时序依赖pass必须严格管控原则上要做到无时序依赖如果无法避免需要申请备案集中管理并且按照要求增加注释说明。补充说明备案申请有两种方式1在 ge sig 例会上申请议题讨论2提 issue 讨论。当前没有独立的时序依赖pass记录文档已有备案信息记录在各pass的代码注释中。6. 禁止在代码中出现对芯片类型、前端框架类型、网络类型的硬编码CANN作为异构计算框架往上要对接各类框架往下要支持多种硬件和芯片为了保持框架能够onetrack演进要和周边解耦不允许感知芯片类型、算子类型AICCore算子、前端框架类型。补充说明芯片能力差异一般通过aclrt接口查询不硬编码芯片类型判断。框架类型和网络类型对 GE 没有差异不需要感知。7. 禁止在插入节点时不考虑新插入节点与输入输出侧节点的控制边关系向图上插入节点时需要考虑插入的节点与前后节点的逻辑关系如果插入节点与输入节点的关系紧密需要将输入节点的输出控制边后移到插入的节点上反之需要将输出节点的输入控制前移到插入的节点上。实际编码时也可以使用封装好的接口GraphUtils::InsertNodeAfter与GraphUtils::InsertNodeBefore。补充说明GraphUtils::InsertNodeAfter与GraphUtils::InsertNodeBefore已完整处理了控制边关系不存在处理不了的场景。优先使用这两个接口调用后无需再手动处理控制边。8. 对节点名的唯一约束是不可以重复除了这个约束之外理论上各个模块、pass可以任意修改节点名、删除本节点并新增一个同name的其他节点等等。补充说明需要定位特定节点时应通过遍历图上节点 匹配条件的方式条件可以是 OpType、节点属性等。9. 禁止在加载/执行流程中增加EVENT/TRACE打印防止产生海量日志必须增加的需备案申请plog直接写入DPC存储DPC存储默认设置小IO聚合延迟下发海量日志会导致小IO变慢产生性能波动。补充说明此限制仅针对加载/执行流程编译/初始化等其他流程不限制。无论 Debug 还是 Release 构建加载/执行流程中禁止增加 EVENT/TRACE 级别日志默认开启会影响性能。允许增加 INFO/DEBUG 级别日志默认关闭关闭后不影响性能和 ERROR 级别日志。10. 使用rtMemcpy/aclrtMemcpy/rtMemcpyAsync/aclrtMemcpyAsync时需确保拷贝动作发生时操作的内存有效rtMemcpy/aclrtMemcpy是立即执行拷贝动作需确保接口调用时操作的内存有效rtMemcpyAsync/aclrtMemcpyAsync下发到指定的stream后立即返回延迟调度需确保该task被实际调度到加速器时操作的内存有效。11. 使用rtMemcpy/aclrtMemcpy/rtMemcpyAsync/aclrtMemcpyAsync时需要判断source size0才进行拷贝否则拷贝可能发生失败调用拷贝时如果size为0表明原始的地址是一个nullptrRTS接口会报错。12. 使用rtMemcpyAsync/aclrtMemcpyAsync时H2D的拷贝类型需要使用RT_MEMCPY_HOST_TO_DEVICE_EX/ACL_MEMCPY_HOST_TO_BUF_TO_DEVICE配置项除非明确知道待拷贝的host内存不会被释放带EX与不带EX的区别是带EX选项的RTS内部会把host内存做一次h2h拷贝然后再做异步h2d所以上层可以释放自己的host内存。不带EX选项的底层不会做h2h拷贝如果上层的host内存释放了真正发生拷贝的时候会出现未定义行为。带EX接口性能会差一点13. 调用其他组件提供的需要GE传入资源相关接口时需要相关组件明确该接口对传入资源是否有生命周期或释放时机的约束在方案设计时也需要明确与其他组件之间接口是否有相关约束。例如rtDevBinaryRegister持有了GE传入的指针并且在context切换时重新使用该指针做H2D操作导致GE必须保证注册二进制host内存在去注册之后才能释放。补充说明代码检视时识别跨组件接口生命周期约束的方法aclrt开头的接口下载 cann/runtime 代码在docs/目录下搜索文档该目录下都是 aclrt 开头的接口文档。rt开头的接口在pkg_inc/runtime/目录下找到接口原型。rt 开头的接口没有文档需要阅读源码判断或者在 cann/runtime 仓提 issue 询问。14. 对图做增删改操作时禁止使用std::unordered_map等无序容器或使用Node指针作为key的std::map进行存储和遍历确保多次处理结果一致使用std::unordered_map等无序容器虽然存储在里面的key是一致的但是存储顺序无法保证一致遍历该容器进行改图处理时可能会导致在图上插入节点的顺序不一致如果图上有对执行顺序有严格要求的算子通信算子多P场景执行时可能引发卡死问题。同样使用Node指针进行存储也无法保证存储顺序的一致性比如Node1和Node2的指针顺序在多次执行时可能会发生变化。补充说明推荐使用有序集合如std::map可用节点名作为 key。禁止使用std::unordered_map等无序容器。禁止使用 Node 指针作为 key每个进程的指针地址不同导致遍历顺序不一致多次执行结果可能不同。使用节点名作为 key 时只能在一个局部函数中临时存储函数结束后销毁。不要跨函数或长期持有以节点名为 key 的容器。15. 禁止单例模式在头文件中以内联方式实现单例模式在头文件中以内联方式实现可能会有如下问题每个包含该头文件的 .cc 文件编译单元都会生成一份该函数的本地副本可能生成多个实例问题核心隐患虽然 C 标准规定 static 局部变量在同一个程序中应该只初始化一次但在动态库dlopen场景下这个保证可能被打破不同的 SO共享库可能各自持有一个实例可能出现dlopen 加载顺序导致的 Coredump16. 禁止使用非开放的RTS接口对于rt开头的接口只能使用pkg_inc/runtime/rt_external*.h头文件里的不在这些头文件里的接口禁止使用需要用aclrt开头的等价接口。17. 禁止在静态对象/全局对象析构函数里做跨so的函数调用静态对象在跨动态库.so调用时析构过程存在几类典型风险根源在于 C 标准对不同编译单元包括动态库中静态对象的析构顺序不作保证。风险类型析构顺序未定义导致悬空引用程序退出时全局/静态对象按构造逆序析构但仅在同一编译单元有效。不同.so的析构顺序由链接器决定不可预测。若.so A的静态对象析构时访问.so B已析构的对象产生悬空引用导致崩溃。析构函数中抛出异常导致程序终止跨.so析构链中若某对象因依赖失效抛异常且未捕获直接std::terminate()。多线程竞争程序退出时工作线程可能访问正在析构的静态对象。动态库卸载不确定性运行期卸载.so时静态对象析构时机不可控。典型违规场景 静态对象持有跨.so对象// 违规静态单例析构时持有的shared_ptr指向其他.so的对象 class ExecutorManager { public: static ExecutorManager GetInstance() { static ExecutorManager instance; // 静态对象 return instance; } private: std::mapstd::string, std::shared_ptrExecutor executors_; // Executor对象在plugin.so中析构时.so可能已卸载 → 崩溃 };正确做法 清理工作在Finalize()中完成析构函数不清理跨.so对象void ExecutorManager::Finalize() { executors_.clear(); // 运行时清理.so未卸载 } ExecutorManager::~ExecutorManager() default; // 析构时executors_已空shared_ptr陷阱shared_ptr析构时调用虚析构函数虚析构函数在对象实际类型的.so中定义若.so已卸载调用虚析构函数访问已释放内存 → 崩溃shared_ptr不能延迟析构到程序退出阶段常见误报场景原因void* mmDlclose裸指针不触发析构mmDlclose是编译链接有Finalize且上层调用Finalize主动清理析构防御性空操作runtime调用aclrt*libascendcl.so编译链接卸载顺序有保证同.so静态注册同编译单元无跨.so风险【免费下载链接】geGEGraph Engine是面向昇腾的图编译器和执行器提供了计算图优化、多流并行、内存复用和模型下沉等技术手段加速模型执行效率减少模型内存占用。 GE 提供对 PyTorch、TensorFlow 前端的友好接入能力并同时支持 onnx、pb 等主流模型格式的解析与编译。项目地址: https://gitcode.com/cann/ge创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考