AMD KFD驱动分析

本文详细解释了如何通过/opendev/kfd管理多个HSA设备,涉及open_device、ioctl操作如AMDKFD_IOC_GET_PROCESS_APERTURES_NEW来获取GPUID,以及KFD与GPU上下文、VM初始化的关系,特别提到了N卡与A卡驱动的区别和KFD的使用限制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1./dev/kfd管理一组设备而并非单个设备,如果有多个HSA节点,则open kfd的上下文将会创建一个process,并打开为这个server上的所有可用的HSA节点分别创建上下文。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdint.h>
#include <linux/kfd_ioctl.h>

#define DEV_NAME  "/dev/kfd"
int open_device(void)
{
	int fd = -1;

	if ((fd = open(DEV_NAME, O_RDWR)) < 0) {
		perror("open device error");
		return -1;
	}

	return fd;
}

int close_device(int fd)
{
	close(fd);

	return 0;
}

int main(void)
{
	int fd, ret, i, render_fd;
	struct kfd_ioctl_get_version_args args_ver;
	struct kfd_ioctl_get_process_apertures_new_args args_devinfo;
	struct kfd_process_device_apertures *pa;
	struct kfd_ioctl_set_memory_policy_args args = {0};
	struct kfd_ioctl_acquire_vm_args args_vm;

	fd = open_device();
	if (fd < 0) {
		perror("open kfd device failure.\n");
		return -1;
	}

	render_fd = open("/dev/dri/renderD128", O_RDWR | O_CLOEXEC);
	if (fd < 0) {
		perror("open render device failure.\n");
		return -1;
	}

	ret = ioctl(fd, AMDKFD_IOC_GET_VERSION, &args_ver);
	if (ret == 0) {
		printf("kfd version major %d, minor %d.\n",
		       args_ver.major_version, args_ver.minor_version);
	}

	args_devinfo.num_of_nodes = 10;
	args_devinfo.kfd_process_device_apertures_ptr = (unsigned long)malloc(args_devinfo.num_of_nodes * sizeof(struct kfd_process_device_apertures));
	if (args_devinfo.kfd_process_device_apertures_ptr == 0) {
		perror("fail to malloc device apertures buffer.\n");
		return -1;
	}

	ret = ioctl(fd, AMDKFD_IOC_GET_PROCESS_APERTURES_NEW, &args_devinfo);
	if (ret != 0) {
		perror("fail to get process apertures.\n");
		return -1;
	}

	printf("%s line %d, args_devinfo.num_of_nodes = %d.\n", __func__, __LINE__, args_devinfo.num_of_nodes);
	pa = (struct kfd_process_device_apertures *)args_devinfo.kfd_process_device_apertures_ptr;
	for (i = 0; i < args_devinfo.num_of_nodes; i ++) {
		printf("idx %d, gpu_id 0x%x.\n"
		       "lds_base 0x%llx.\n"
		       "lds_limit 0x%llx.\n"
		       "gpuvm_base 0x%llx.\n"
		       "gpuvm_limit 0x%llx.\n"
		       "scratch_base 0x%llx.\n"
		       "scratch_limit 0x%llx.\n", i, pa[i].gpu_id, pa[i].lds_base, pa[i].lds_limit, pa[i].lds_limit, pa[i].lds_limit, pa[i].scratch_base, pa[i].scratch_limit);
	}

	args_vm.gpu_id = pa[0].gpu_id;
	args_vm.drm_fd = render_fd;
	ret = ioctl(fd, AMDKFD_IOC_ACQUIRE_VM, (void *)&args_vm);
	if (ret != 0) {
		perror("fail to get acquire vm.\n");
		return -1;
	}

	args.gpu_id = pa[0].gpu_id;
	args.default_policy = KFD_IOC_CACHE_POLICY_COHERENT;
	args.alternate_policy = KFD_IOC_CACHE_POLICY_COHERENT;
	args.alternate_aperture_base = (uintptr_t)NULL;
	args.alternate_aperture_size = 0;

	ret = ioctl(fd, AMDKFD_IOC_SET_MEMORY_POLICY, &args);
	if (ret != 0) {
		perror("fail to set memory policy.\n");
		return -1;
	}

	printf("%s line %d, kfd test success.\n", __func__, __LINE__);
	free((void *)args_devinfo.kfd_process_device_apertures_ptr);
	close_device(fd);

	return 0;
}

2.如果/dev/kfd管理多个设备,那么驱动如何确定当前系统调用下来要访问那个HSA设备呢?

方法是用户会在系统调用的参数中传入要操作的gpu_id.

3.既然如此,进行系统调用前用户应用必须首先知道有那些GPU_ID,那么应用是如何知道的呢?答案是通过ioctl AMDKFD_IOC_GET_PROCESS_APERTURES_NEW command.在UMD应用初始化GPU Context阶段,会首先调用AMDKFD_IOC_GET_PROCESS_APERTURES_NEW CMD查询当前server上可用的HSA设备,并返回GPU ID。

3.AMDGPU显存对象叫做buffer object,简称BO,BO底层基于ttm实现:

4.amdgpu_vm_bo_base_init构造了什么?

一个amdgpu_bo本身表示一段显存,它本身不带有context信息,当MAP到具体的卡上才带有了上下文信息,1个BO可以映射到多张卡,所以,一个BO可以对应多个VM,一个VM也可以对应多个BO,为了体现这种联系,可以将归属于同一个BO的VM关联到一起,对应到BO上,amdgpu_vm_bo_base_init就是做的这件事。其逆操作是amdgpu_vm_bo_rmv。

每个struct amdgpu_vm_bo_base->bo指向的是链表头的bo,方便从任意一个base bo开始查询。

KFD关键数据结构:

1.每个打开GPU的进程,无论open多少次,只创建一个kfd_process对象,也就是说,kfd_process每个对象1个。

2.一个KFD PROCESS对应1个PASID,也就是说,一个进程一个PASID。

3.多卡环境,1个open kfd,所有的HSA设备上下文一次性打开,关联到kfd_process对象,每个设备上下文对应一个从drm框架私有结构分配的amdgpu_vm对象。

2025/02/05 update:可能正是这个原因,AMDGPU进程才可以每个进程分配对应唯一的PASID,否则,按照打开设备分配PASID是比较合理的办法,如果每个设备有各自的设备节点,则PASID最好由每个设备独立管理分配,这些设备结点包括MIG虚拟化设备,这样每个进程可能在不同的设备上又不同的PASID,需要根据(进程,设备)二元对寻找PASID。也就是 pasid = f(pid, device);

4.对于多进程一样。

DRM RENDER设备每次打开分配一个VM和一个GFX域内的PASID,所以UMD每次初始化一个HSA设备,都需要打开一次RENDER节点,然后传入对应的GPUID调用KFD IOCTL,再次对VM进行初始化。

然后KFD框架会调用kfd_ioctl_acquire_vm,得到DRM分配的amdgpu_vm,然后在KFD框架中调用amdgpu_vm_init再次将VM对象初始化一遍(第一遍初始化是在open drm render设备时候amdgpu_driver_open_kms上下文里面)。具体可以查看调用amdgpu_vm_init的地方,它只有两处,一处是DRM,另一处就是KFD的acquire_vm调用链中。

Render设备可以被多次打开,每次打开,内部生成一个VM对象,每个HSA Device对应一个Render FD,也就对应一个VM,在ACQUIRE_VM的IOCTL命令中完成初始化,下图是打开两个RENDER FD,创建两个VM的调用堆栈:

# tracer: function
#
# entries-in-buffer/entries-written: 4/4   #P:12
#
#                                _-----=> irqs-off
#                               / _----=> need-resched
#                              | / _---=> hardirq/softirq
#                              || / _--=> preempt-depth
#                              ||| /     delay
#           TASK-PID     CPU#  ||||   TIMESTAMP  FUNCTION
#              | |         |   ||||      |         |
           a.out-3374    [004] ....  4711.142621: amdgpu_vm_init <-amdgpu_driver_open_kms
           a.out-3374    [004] ....  4711.142626: <stack trace>
 => amdgpu_vm_init
 => amdgpu_driver_open_kms
 => drm_file_alloc
 => drm_open
 => drm_stub_open
 => chrdev_open
 => do_dentry_open
 => vfs_open
 => path_openat
 => do_filp_open
 => do_sys_openat2
 => do_sys_open
 => __x64_sys_openat
 => do_syscall_64
 => entry_SYSCALL_64_after_hwframe
           a.out-3374    [004] ....  4711.142729: amdgpu_vm_init <-amdgpu_driver_open_kms
           a.out-3374    [004] ....  4711.142733: <stack trace>
 => amdgpu_vm_init
 => amdgpu_driver_open_kms
 => drm_file_alloc
 => drm_open
 => drm_stub_open
 => chrdev_open
 => do_dentry_open
 => vfs_open
 => path_openat
 => do_filp_open
 => do_sys_openat2
 => do_sys_open
 => __x64_sys_openat
 => do_syscall_64
 => entry_SYSCALL_64_after_hwframe
/dev/render128的作用

ROCm依赖于/dev/render128节点提供的amdgpu_vm对象,但是分析代码,貌似对/dev/render128的依赖分成两个阶段,在5.4的KFD实现中,当lazy binding VM的关键函数kfd_process_device_init_vm的第二个参数为NULL时,代表不需要/dev/render128节点文件,子子流程amdgpu_amdkfd_gpuvm_create_process_vm函数会分配amdgpu_vm对象,不依赖于drm render128 file提供的amdgpu_vm文件。

在之后的的某个版本迭代中,With KFD merging into amdgpu, and with ROCm using VMs from DRM FDs, there is no use case anymore where we would not use a DRM FD to get the VM,也就是说,从之后的某个版本开始,KFD必须依赖于/dev/render128提供的amdgpu_vm对象,而不再使用KFD自行分配的amdgpu_vm对象。

所以,最新的ROCm sdk, KFD对/dev/render128的需要必不可少。

经过确认,去除kfd self create vm的改动是在v5.12-rc7的开发周期提交的。

KFD Process的释放

kfd_process对象通过引用计数进行生命期控制,由于FD释放发生在进程EXIT之前,所以最后一次释放发生在do_exit的流程中调用mmu notifier kfd_process_notifier_release, 通过mmu_notifier_put->mmu_notifier_free_rcu触发RCU回调,最终在kfd_process_ref_release中调用kfd_unref_release触发引用计数归零完成释放。        

           a.out-2880    [006] ....  3357.338719: kfd_unref_process <-kfd_release
           a.out-2880    [006] ....  3357.338720: <stack trace>
 => kfd_unref_process
 => kfd_release
 => __fput
 => ____fput
 => task_work_run
 => exit_to_user_mode_prepare
 => syscall_exit_to_user_mode
 => do_syscall_64
 => entry_SYSCALL_64_after_hwframe
           a.out-2880    [006] ....  3357.338751: mmu_notifier_put <-kfd_process_notifier_release
           a.out-2880    [006] ....  3357.338753: <stack trace>
 => mmu_notifier_put
 => kfd_process_notifier_release
 => __mmu_notifier_release
 => exit_mmap
 => mmput
 => do_exit
 => do_group_exit
 => __x64_sys_exit_group
 => do_syscall_64
 => entry_SYSCALL_64_after_hwframe
     kworker/6:0-2803    [006] ....  3357.351184: kfd_process_ref_release <-kfd_process_free_notifier
     kworker/6:0-2803    [006] ....  3357.351194: <stack trace>
 => kfd_process_ref_release
 => kfd_process_free_notifier
 => mmu_notifier_free_rcu
 => srcu_invoke_callbacks
 => process_one_work
 => worker_thread
 => kthread
 => ret_from_fork
           a.out-2880    [006] ....  3357.351315: remove_vma <-exit_mmap
           a.out-2880    [006] ....  3357.351319: <stack trace>
 => remove_vma
 => exit_mmap
 => mmput
 => do_exit
 => do_group_exit
 => __x64_sys_exit_group
 => do_syscall_64
 => entry_SYSCALL_64_after_hwframe
/dev/kfd的使用限制

看下图的注释和逻辑,CHILD进程需要打开它自己的FD句柄,而不能用继承子父进程的,分析原因可能是因为KFD_PROCESS上下文的使用限制,每个进程会创建自己的KFD_PROCESS对象,但是FORK调用时,内核复制进程的时候对文件的拷贝是浅拷贝,也就是说,子进程继承自父进程的kfd设备对应的fd和父进程自身的fd指向的是同一个struct file,这样就相当于,子进程没有经过调用kfd_open创建自己的KFD_PROCESS对象的过程,也就无法用继承来的FD调用KFD的IOCTL。

如果用户态应用强行这样做,KFD_IOCTL将会返回-EBADF错误。

N卡驱动和A卡驱动的不同

HSA模式下,BO没有属主,BO在某个卡的上下文中分配,但是并不属于这张卡,而是大家一起用,当某张卡下线(RESET)时,只需要取消对BO的引用即可。

N卡是独立显卡,物理上某个BO一定位于某张卡上,在此卡的上下文中分配,并被其它卡引用,当此卡下线时,可能会存在其他卡仍然在引用这个BO的情况。


结束

<think>好的,我现在需要帮助用户解决在Docker中配置AMD显卡加速或透传的问题。首先,我得回顾用户的问题,确保我理解正确。用户想知道如何在Docker环境中支持AMD显卡的加速或驱动配置,特别是关于GPU透传的设置。 首先,我需要回忆Docker中GPU支持的一般方法。对于NVIDIA GPU,通常使用nvidia-docker工具,但AMD的情况可能不同。AMD的GPU加速可能涉及不同的驱动和容器配置。我需要考虑AMD官方是否有对应的Docker解决方案,或者社区中有没有类似的工具。 接下来,用户提到的“GPU passthrough”通常是指在虚拟化环境中将物理GPU直接分配给虚拟机使用,但这里用户可能是指将宿主机的AMD GPU透传给Docker容器使用。这需要宿主机正确安装AMD驱动,并在Docker运行时配置相应的设备。 然后,我需要查阅相关的资料,比如AMD的官方文档是否有关于容器中使用GPU的指南。另外,可能涉及到在Docker运行时挂载设备文件和必要的库文件。例如,NVIDIA的做法是挂载设备文件和相关的驱动库,AMD可能也需要类似的操作。 同时,用户提供的引用中有关于创建docker-registry的secret和安装docker的命令,但可能和AMD GPU配置关系不大,不过需要注意是否有相关的依赖步骤。例如,确保Docker版本和安装正确,这是基础步骤。 可能还需要考虑使用AMD的ROCm(Radeon Open Compute)平台,它支持在容器中进行GPU计算。ROCm提供了类似CUDA的异构计算环境,可能需要在容器中安装ROCm的驱动和工具链。 接下来,我需要分步骤整理配置过程:首先确保宿主机安装AMD驱动和ROCm,然后在Docker中配置设备挂载和必要的环境变量。此外,检查Docker是否需要特定的运行参数来启用GPU支持。 另外,可能需要验证配置是否正确,比如在容器内运行AMD的检测工具或简单的GPU计算任务,确认GPU是否被正确识别和使用。 还要考虑用户可能遇到的问题,比如驱动版本兼容性、权限问题(设备文件的访问权限)、以及Docker版本是否支持GPU透传等。需要提醒用户注意这些潜在问题,并提供解决建议。 最后,生成相关问题,帮助用户深入理解该主题,比如如何验证AMD GPU在容器中的使用,或者性能优化方法等。</think>要在Docker中配置AMD显卡加速或透传,需通过以下步骤实现: ### 1. 宿主机环境准备 - **安装AMD显卡驱动** 确保宿主机已正确安装AMD GPU驱动和ROCm(Radeon Open Compute)平台。例如在Ubuntu中,可通过以下命令安装: ```bash sudo apt install rocm-hip-libraries rocm-dev ``` 验证驱动状态: ```bash rocminfo # 查看GPU识别情况 ``` - **配置用户组权限** 将用户加入`video`和`render`组以访问GPU设备: ```bash sudo usermod -aG video,docker $USER ``` ### 2. Docker配置AMD GPU透传 - **挂载设备文件与库** 在运行容器时,需挂载GPU设备文件和ROCm库: ```bash docker run -it --device=/dev/kfd --device=/dev/dri/renderD128 \ -v /opt/rocm:/opt/rocm:ro --group-add=video ubuntu:22.04 ``` 参数说明: - `--device`:挂载AMD GPU设备文件 - `-v /opt/rocm`:挂载宿主机ROCm工具链到容器 - `--group-add`:赋予容器访问GPU的组权限 - **使用官方ROCm镜像** AMD提供预装ROCm的Docker镜像,可直接运行: ```bash docker run -it --device=/dev/kfd --device=/dev/dri rocm/rocm-terminal ``` ### 3. 验证容器内GPU加速 在容器内执行AMD检测命令: ```bash rocminfo | grep "Agent\|Marketing Name" # 查看GPU信息 /opt/rocm/bin/rocblas-test # 运行ROCm测试案例 ``` ### 4. 权限问题处理 若出现`Permission denied`错误,尝试: - 检查`/dev/dri/renderD*`设备权限 - 添加`--security-opt seccomp=unconfined`参数启动容器 ### 配置示例 通过Docker Compose部署: ```yaml services: amd_gpu_app: image: rocm/pytorch devices: - "/dev/kfd" - "/dev/dri/renderD128" volumes: - "/opt/rocm:/opt/rocm:ro" group_add: - "video" ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

papaofdoudou

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值