当前位置: 首页 > news >正文

cuda二进制文件中到底有些什么

大家好。今天我们来讨论一下,相比gcc编译器编译的二进制elf文件,包含有 cuda kernel 的源文件编译出来的 elf 文件有什么不同呢?

之前研究过一点 tvm。从 BYOC 的框架中可以得知,前端将模型 partition 成 host 和 accel(accel 表示后端,比如加速卡,NPU或者其他AI加速模块) 两部分,对 accel 部分,会切分成多个 regions,对应到多个子图,这部分每一个 regions 会被封装成一个独立的 function 进行处理,这些 function 都带有 annotation,附带硬件相关的标签信息,能知道由哪个 accel 后端来处理,在 host 侧对这些函数的处理,仅仅是简单的封装成对一个外部函数的调用,而实际的编译是由 accel-specific 的编译器来编译和 codegen 的。而每一个 sub-graph 会编译成一个 sub-module,最终与 host sub-module 一起封装成一个 heterogenous blob。

而 nvcc 应该也是类似的道理。

在 《Cuda Compiler Driver NVCC.pdf》中,有这么一段介绍

Dispatching GPU jobs by the host process is supported by the CUDA Toolkit in the form of remote procedure calling. The GPU code is implemented as a collection of functions in a language that is essentially C, but with some annotations for distinguishing them from the host code, plus annotations for distinguishing different types of data memory that exists on the GPU. Such functions may have parameters, and they can be called using a syntax that is very similar to regular C function calling, but slightly extended for being able to specify the matrix of GPU threads that must execute the called function. During its life time, the host process may dispatch many parallel GPU tasks.

大致意思就是说,CUDA ToolKit 可以支持主机端程序通过 RPC 的方式调度 GPU 任务。GPU 代码与 C 代码类似,但是带有一些额外的 annotations 信息来做区分。而GPU 代码实现的函数的调用也与传统 c 函数调用相同。

直接来编译一个 helloword 程序

/*
*hello_world.cu
*/
#include<stdio.h>
__global__ void hello_world(void)
{printf("GPU: Hello world!\n");
}
int main(int argc,char **argv)
{printf("CPU: Hello world!\n");hello_world<<<1,10>>>();cudaDeviceReset();//if no this line ,it can not output hello world from gpureturn 0;
}

编译

nvcc --cudart shared -o device helloworld.cu --verbose

使用 --cudart shared 而不使用静态链接的方式,是为了不将 libcudart.a 链接到二进制文件中,使得目标程序大小偏大。

objdump -ds device

观察 hello_world 函数
请添加图片描述
可以看到,本质上是一个函数调用,对 _Z30__device_stub__Z11hello_worldvv 函数的一个调用。

推测: 对 device 设备端的函数,也是封装成一个 external function 的函数调用,而该函数实际是通过 device设备端(也就是GPU) 的code gen 来生成的,最终会将其合并成一个二进制文件。而在编写 cuda 代码的过程中,所使用的这些 c++ 扩展,就类似于 annotation 的作用,注明了这属于 device 设备端的代码。

compile process

请添加图片描述

a CUDA executable can exist in two forms:

  • a binary one that can only target specific devices and an intermediate assembly one that can target any device by JIT compilation.
  • a PTX Assembler (ptxas) performs the compilation during execution time, adding a start-up overhead, at least during the first invocation of a kernel.

cuda 可执行文件可以有两种形式,一种是针对特定设备的二进制文件和一种中间表示的汇编形式,可以通过 JIT 的方式运行与任何设备上。JIT 也就是 Just In Time,java 虚拟机,python,v8 都有 JIT 机制。而另一种,就是 PTX 汇编,这种形式是通过 cuda runtime 在运行时加载编译然后执行的,第一次加载编译时会比较耗时。

A CUDA program can still target different devices by embedding multiple cubins into a single file (called a fat binary ). The appropriate cubin is selected at run-time.

请添加图片描述

从上面可以发现,使用nvcc进行编译,将包含有cuda kernel 的 c++ 代码,分成了 device 的代码和 host 的代码,host 代码通过 clang/gcc 以传统 c++ 代码的方式进行编译,而 device 代码以 nvcc cuda 编译的流程进行编译。我们使用 --verbose 的方式来观察一下具体的编译流程。

$ nvcc --cudart shared -o device helloworld.cu --verbose ---keep

请添加图片描述
helloworld.cu 编译后生成了 hellworld.cpp1.ii 和 helloworld.ptx,ptx 也就是 cuda 汇编代码。然后 helloworld.ptx 编译成了 cubin 二进制文件,而 fatbinary 最终会被嵌入到最终的 elf 二进制文件 devicde 中。

elf 二进制文件分析

使用 readelf 观察一下 device 这个文件

readelf -a device

请添加图片描述

在该 elf 文件中,多了两个段 .nv_fatbin 和 .nvFatBinSegment
请添加图片描述
从 Program Headers 中可以发现,这两个段分别位于代码段和数据段中。

第一个 LOAD 属性为 RE,表示可读可执行表示代码段,而第二个 LOAD 属性为 RW,表示可读可写,为数据段。从上往下索引分别是 02 和 03,所以 .nv_fatbin 位于代码段,而 .nvFatBinSegment 位于数据段中。

.nv_fatbin

It is split into an arbitrary number of distinct regions, each of which contains one or more GPU ELF files, PTX code files, and/or cubin files .

该段中保存的通常是 PTX 汇编代码或者 cubin 二进制代码,正好与上面的分析相符,位于代码段中。

.nvFatBinSegment

It contains metadata about the .nv_fatbin section , such as the starting addresses of its regions. Its size is a multiple of six words (24 bytes), where the third word in each group of six is an address inside of the .nv_fatbin section. If we modify the .nv_fatbin, then these addresses need to be changed to match it.

该段保存的是 .nv_fatbin 的一些 metadata。

文件分析

先来看下 device 文件头的信息,可以通过 readelf -h 的方式查看
请添加图片描述
文件头是 64 个字节,program headers 是 56 个字节,共有 9 个 program headers,每一个 section header 是 64 字节。

先看下 elf.h 中 Elf64_Ehdr 文件的数据结构
请添加图片描述

e_ident 就是上面 readelf -h 结果中的 Magic,也就是 elf 格式的魔数。

文件头大小是 64 字节,使用 od 来分析一下。

od -Ax -tx1 -N 64 deviceß

解释一下这里的参数

  • -Ax: 显示地址的时候,用十六进制来表示。如果使用 -Ad,意思就是用十进制来显示地址;
  • -t -x1: 显示字节码内容的时候,使用十六进制(x),每次显示一个字节(1);
  • -N 64:只需要读取 64 个字节;

请添加图片描述
e_type 为 0x0003(小端),e_type 的取值可以在 elf.h 中查看
在这里插入图片描述
3 表示该文件是 shared object file。
而 e_machine 为 0x003e,
在这里插入图片描述
从 elf.h 中可以看出,0x3e 的 10 进制为 62,也就是 ADM x86_64架构。而 cuda 的 e_machine 应该是 190,也就是 0xbe。
请添加图片描述

也就是说如果是 cuda bin,Elf64_Ehdr 中 e_machine 成员的值,应该是 190,16 进制就是 0xbe。

我们在最上面分析 device 文件时,使用 readelf -a 查看,发现 .nv_fatbin 在 Section Header 中的索引是 17,section header 的起始偏移是 0x42f8 = 17144,从 elf 文件中获取 nv_fatbin 这个 section 的信息,计算偏移为 0x42f8 + 17 * 64,64 就是 e_shentsize 的大小,为 0x40,即每一个 section header item 的大小为 64 字节

请添加图片描述
而 section header 的数据结构为
在这里插入图片描述
该 section 的大小和偏移与在 readelf -S 中看到的一致,看下内容 .nvfatbin 段的内容
请添加图片描述

nvcc 编译时,–keep 将临时文件保存下来,device_dlink.fatbin 与上面的内容一致
请添加图片描述
fatbin 是 device-only 的代码

上面这个 fatbin 文件其实是一个包裹着 elf 文件的二进制文件,
请添加图片描述

文件 e_machine 为 0xbe,就是 cuda elf 格式的文件。

总结

cuda 二进制文件,分成两部分,一个是 host 部分的代码,一个是 device 段的代码。device 段的代码,作为一个 section 的方式,以 fatbin 的方式或者 ptx 汇编代码的方式嵌入到了最终的 elf 文件中。这部分代码,有 cuda runtime 来负责编译运行。

reference

  1. PCI BARs and other means of accessing the GPU
  2. https://www.ofweek.com/ai/2021-05/ART-201721-11000-30500304_3.html

相关文章:

cuda二进制文件中到底有些什么

大家好。今天我们来讨论一下&#xff0c;相比gcc编译器编译的二进制elf文件&#xff0c;包含有 cuda kernel 的源文件编译出来的 elf 文件有什么不同呢&#xff1f; 之前研究过一点 tvm。从 BYOC 的框架中可以得知&#xff0c;前端将模型 partition 成 host 和 accel(accel 表…...

怎么从视频中提取动图?一个方法快速提取gif

视频以连续的方式播放一系列图像帧&#xff0c;通过每秒播放的帧数&#xff08;帧率&#xff09;来创做&#xff0c;由于GIF动图则以循环播放一系列静态图像帧的方式展现动画效果。由于视频的优势在于流畅的动画、丰富的细节和长时间播放&#xff0c;因此常用于电影、电视节目、…...

String字符串的比较和hash函数减少哈希冲突

1.为什么比较字符串通过hash值比通过字符串本身效率更高 比较两个字符串的哈希值相对于比较两个字符串本身的效率更高&#xff0c;原因如下&#xff1a; 哈希函数具有快速计算的特性&#xff1a;哈希函数可以将一个字符串转换为一个固定长度的哈希值。这个转换过程通常是非常…...

【数据库原理】(38)数据仓库

数据仓库&#xff08;Data Warehouse, DW&#xff09;是为了满足企业决策分析需求而设计的数据环境&#xff0c;它与传统数据库有明显的不同。 一.数据库仓库概述 定义: 数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合&#xff0c;用于支持企业管理和…...

C++17新特性(四)已有标准库的拓展和修改

这一部分介绍C17对已有标准库组件的拓展和修改。 1. 类型特征拓展 1.1 类型特征后缀_v 自从C17起&#xff0c;对所有返回值的类型特征使用后缀_v&#xff0c;例如&#xff1a; std::is_const_v<T>; // C17 std::is_const<T>::value; // C11这适用于所有返回值的…...

软件是什么?前端,后端,数据库

软件是什么&#xff1f; 由于很多东西没有实际接触&#xff0c;很难理解&#xff0c;对于软件的定义也是各种各样。但是我还是不理解&#xff0c;软件开发中的前端&#xff0c;后端&#xff0c;数据库到底有什么关系呢&#xff01; 这个问题足足困扰了三年半&#xff0c;练习时…...

Vue3+ElementUI 多选框中复选框和名字点击方法效果分离

现在的需求为 比如我点击了Option A &#xff0c;触发点击Option A的方法&#xff0c;并且复选框不会取消勾选&#xff0c;分离的方法。 <el-checkbox-group v-model"mapWork.model_checkArray.value"> <div class"naipTypeDom" v-for"item …...

设计模式篇章(4)——十一种行为型模式

这个设计模式主要思考的是如何分配对象的职责和将对象之间相互协作完成单个对象无法完成的任务&#xff0c;这个与结构型模式有点像&#xff0c;结构型可以理解为静态的组合&#xff0c;例如将不同的组件拼起来成为一个更大的组件&#xff1b;而行为型更是一种动态或者具有某个…...

Spring成长之路—Spring MVC

在分享SpringMVC之前&#xff0c;我们先对MVC有个基本的了解。MVC(Model-View-Controller)指的是一种软件思想&#xff0c;它将软件分为三层&#xff1a;模型层、视图层、控制层 模型层即Model&#xff1a;负责处理具体的业务和封装实体类&#xff0c;我们所知的service层、poj…...

架构篇05-复杂度来源:高可用

文章目录 计算高可用存储高可用高可用状态决策小结 今天&#xff0c;我们聊聊复杂度的第二个来源高可用。 参考维基百科&#xff0c;先来看看高可用的定义。 系统无中断地执行其功能的能力&#xff0c;代表系统的可用性程度&#xff0c;是进行系统设计时的准则之一。 这个定义…...

C#调用Newtonsoft.Json将bool序列化为int

使用Newtonsoft.Json将数据对象序列化为Json字符串时&#xff0c;如果有布尔类型的属性值时&#xff0c;一般会将bool类型序列化为字符串&#xff0c;true值序列化为true&#xff0c;false值序列化为false。如下面的类型序列化后的结果如下&#xff1a; public class UserInfo…...

【Linux系统编程】环境变量详解

文章目录 1. 环境变量的基本概念2. 如何理解呢&#xff1f;&#xff08;测试PATH&#xff09;2.1 切入点1查看具体的环境变量原因剖析常见环境变量 2.2 切入点2给PATH环境变量添加新路径将我们自己的命令拷贝到PATH已有路径里面 2.3 切入点3 3. 显示所有环境变量4. 测试HOME5. …...

智能合约介绍

莫道儒冠误此生&#xff0c;从来诗书不负人 目录 一、什么是区块链智能合约? 二、智能合约的发展背景 三、智能合约的优势 四、智能合约的劣势 五、一些关于智能合约的应用 总结 一、什么是区块链智能合约? 智能合约&#xff0c;是一段写在区块链上的代码&#xff0c;一…...

Python自动化实战之接口请求的实现

在前文说过&#xff0c;如果想要更好的做接口测试&#xff0c;我们要利用自己的代码基础与代码优势&#xff0c;所以该章节不会再介绍商业化的、通用的接口测试工具&#xff0c;重点介绍如何通过 python 编码来实现我们的接口测试以及通过 Pycharm 的实际应用编写一个简单接口测…...

react和vue的区别

一、核心思想不同 Vue的核心思想是尽可能的降低前端开发的门槛&#xff0c;是一个灵活易用的渐进式双向绑定的MVVM框架。 React的核心思想是声明式渲染和组件化、单向数据流&#xff0c;React既不属于MVC也不属于MVVM架构。 如何理解React的单向数据流&#xff1f; React的单…...

Spring 中有哪些方式可以把 Bean 注入到 IOC 容器?

目录 1、xml方式2、CompontScan Component3、使用 Bean方式4、使用Import 注解5、FactoryBean 工厂 bean6、使用 ImportBeanDefinitionRegistrar 向容器中注入Bean7、实现 ImportSelector 接口 1、xml方式 使用 xml 的方式来声明 Bean 的定义&#xff0c;Spring 容器在启动的…...

客户需求,就是项目管理中最难管的事情

对于需求控制和管理 个人的观点是&#xff1a;首先要向客户传递开发流程&#xff0c;第二必须制作原型&#xff0c;需求确认时确认的是原型&#xff0c;而不是需求文档&#xff0c;第三&#xff0c;开发阶段要快速迭代&#xff0c;与客户互动。管人方面我想对于项目经理来讲&am…...

条款28:避免返回 handles 指向对象的内部成分

创建一个矩形的类&#xff08;Rectangle&#xff09;&#xff0c;为保持Rectangle对象较小&#xff0c;可以只在其对象中保存一个指针&#xff0c;用于指向辅助的结构体&#xff0c;定义其范围的点数据存放在辅助的结构体中&#xff1a; class Point { // 表示点的类 public:P…...

【人工智能】之深入理解 AI Agent:超越代码的智能助手(2)

人工智能&#xff08;AI&#xff09;正在以前所未有的速度迅猛发展&#xff0c;而AI Agent&#xff08;智能代理&#xff09;则是这一领域中备受瞩目的一环。AI Agent 不仅仅是程序的执行者&#xff0c;更是能够感知、学习和交互的智能实体。本文将深入探讨什么是 AI Agent&…...

如何将一个字符串转换为整数?

目录 1. 基本方法&#xff1a;int() 函数 2. 错误处理 3. 性能考虑 4. 实用技巧 结论 在Python中&#xff0c;将字符串转换为整数是一个常见且重要的操作。这种转换通常在处理用户输入、解析文本数据或在不同数据类型间进行转换时使用。以下是从几个方面对这个主题的详细介…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能

1. 开发环境准备 ​​安装DevEco Studio 3.1​​&#xff1a; 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK ​​项目配置​​&#xff1a; // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

如何应对敏捷转型中的团队阻力

应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中&#xff0c;明确沟通敏捷转型目的尤为关键&#xff0c;团队成员只有清晰理解转型背后的原因和利益&#xff0c;才能降低对变化的…...

零知开源——STM32F103RBT6驱动 ICM20948 九轴传感器及 vofa + 上位机可视化教程

STM32F1 本教程使用零知标准板&#xff08;STM32F103RBT6&#xff09;通过I2C驱动ICM20948九轴传感器&#xff0c;实现姿态解算&#xff0c;并通过串口将数据实时发送至VOFA上位机进行3D可视化。代码基于开源库修改优化&#xff0c;适合嵌入式及物联网开发者。在基础驱动上新增…...

MySQL的pymysql操作

本章是MySQL的最后一章&#xff0c;MySQL到此完结&#xff0c;下一站Hadoop&#xff01;&#xff01;&#xff01; 这章很简单&#xff0c;完整代码在最后&#xff0c;详细讲解之前python课程里面也有&#xff0c;感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...

加密通信 + 行为分析:运营商行业安全防御体系重构

在数字经济蓬勃发展的时代&#xff0c;运营商作为信息通信网络的核心枢纽&#xff0c;承载着海量用户数据与关键业务传输&#xff0c;其安全防御体系的可靠性直接关乎国家安全、社会稳定与企业发展。随着网络攻击手段的不断升级&#xff0c;传统安全防护体系逐渐暴露出局限性&a…...

大模型——基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程

基于Docker+DeepSeek+Dify :搭建企业级本地私有化知识库超详细教程 下载安装Docker Docker官网:https://www.docker.com/ 自定义Docker安装路径 Docker默认安装在C盘,大小大概2.9G,做这行最忌讳的就是安装软件全装C盘,所以我调整了下安装路径。 新建安装目录:E:\MyS…...