Pytorch CUDA CPP简易教程,在Windows上操作

news2024/11/19 7:26:02

文章目录

  • 前言
  • 一、使用的工具
  • 二、学习资源分享
  • 三、libtorch环境配置
    • 1.配置CUDA、nvcc、cudnn
    • 2.下载libtorch
    • 3.CLion配置libtorch
    • 4.CMake Application指定Environment variables
    • 5.测试libtorch
  • 四、PyTorch CUDA CPP项目流程
    • 1.使用`CLion`结合`torch extension`编写可以调用cuda的C++代码
    • 2.编写`CU`设备代码,在代码中调用CUDA核函数使用GPU运算
    • 3.创建header定义设备函数
    • 4.编写`CPP`主机代码,在代码中使用`PYBIND`库连接pytorch和c++代码,使用主机函数调用设备函数
    • 5.在`Pycharm`中创建`setup.py`脚本,将C++编写的cuda代码编译为python的一个package
    • 6.使用`pip`安装自定义的cuda代码库
    • 7.安装好自定义的cuda自定义库之后,编写python代码调用
  • 五、总结


前言

这学期确定了研究方法,具体为三维重建相关,转而研究三维重建相关的知识。最近3D Gaussian Splatting方法效果十分的好,并且开源了源代码,因此十分值得对其源码进行研究,源码中对于可微光栅化的实现是基于CUDA实现的,因此想要后续对这块内容进行改进,则必须了解CUDA程序是如何编写的,作为一名DL农工,学习CUDA编程也有助于自己对于整个DL流程的理解,也能够进一步拓展自己的编程技术,因此无论出于什么目的,作为DL农工,我认为有必要掌握这项技术。

本教程仅作为Pytorch CUDA CPP开发的简易教程,我也是初学者。同时本教程的目的也是为了方便不熟悉CUDA项目结构的人快速了解一份新的程序的文件结构是怎样的。

一、使用的工具

Pytorch, libtorch, CUDA, C++, CLion, Pycharm

二、学习资源分享

如果想要详细了解整个CUDA程序的工作原理,可以观看这个详细教程:CUDA编程基础入门系列

如果想功利点的学习如何使用Pytorch调用CUDA程序,可以观看这个教程:Pytorch+cpp/cuda extension 教学

本简易教程也是基于第二个视频教程写的,这个教程讲了一种通用的Pytorch、CUDA程序编写范式,使用pytorch+cpp+cuda开发的几乎都遵循这套代码编写流程,因此学会怎么写,基本上就能看明白其他人的CUDA程序文件是如何组织的,尽管你还是有可能看不懂CUDA代码,但是你学完后一定能看明白CUDA程序是如何组织的。

自定义 C++ 和 CUDA 扩展
PYTORCH C++ API

三、libtorch环境配置

注意:我所有的操作都是在Windows上进行的,Linux上的操作应该也大差不差
libtorch环境配置的教程有很多,你可以参考其他人的博客进行配置,这里我仅贴我使用CLion配置libtorch的流程

1.配置CUDA、nvcc、cudnn

该项不进行说明,有许多攻略可供参考,这里贴一篇博客 Windows安装CUDA及cuDNN【保姆级教程】
注意安装的CUDA版本不要太新了,且要与libtorch提供的CUDA版本保持一致,目前官网提供的libtorch CUDA版本为11.8,因此你安装的CUDA最好也是11.8,否则会有许多奇怪的错误
你都来看CUDA编程了,不至于DL环境还不会配置吧🤣

2.下载libtorch

前往pytorch官网下载libtorch:Pytorch
在这里插入图片描述
你可以选择CPU和CUDA版本,这就和Pytorch的CPU和CUDA版是一个意思,个人感觉对于Pytorch CUDA开发,CPU和CUDA版没有什么区别,libtorch就相当于一个C++版本的Pytorch,但是我们的目的仅仅是用C++版的torch调用CUDA程序,最后还是要用python代码调用C++代码,最后由C++调用CUDA程序。为了避免出现奇怪的bug,还是一开始就下载CUDA版的吧。

release和debug两个版本可以选择,debug版支持debug操作,相应容量会大些;release版不能debug,容量会小很多。你自己选择,一般选debug版。

3.CLion配置libtorch

创建CLion项目,一定要创建 C++ Executable 项目,不要创建CUDA Executable,否则CMAKE时会不通过,出现奇怪的BUG我也不知道为啥。

CMakeLists.txt中填入一下内容
#标识的表示需要修改成你自己的libtorch里对应的路径

cmake_minimum_required(VERSION 3.25)
project(xxx)  # xxx为你自己的项目名称,这部分为CLion自动生成的

set(CMAKE_CXX_STANDARD 17)
set(Torch_DIR E:/CLion/libtorch/share/cmake/Torch)  # your own path
find_package(Torch REQUIRED)

include_directories(E:/CLion/libtorch/include)  # your own path
include_directories(E:/CLion/libtorch/include/torch/csrc/api/include)  # your own path

add_executable(xxx main.cpp)
target_link_libraries(xxx ${TORCH_LIBRARIES})
set_property(TARGET xxx PROPERTY CXX_STANDARD 17)

4.CMake Application指定Environment variables

经过实测,如果不指定这个选项,会出现CMake时找不到xxx.dll,这些dll文件其实是存在的,但就是找不到位置,因此需要手动去指定,参考我的这篇博客:CLion配置libtorch找不到xxx.dll

5.测试libtorch

main.cpp粘贴如下代码:

#include <iostream>
#include <torch/torch.h>

int main() {
    torch::Tensor tensor = torch::rand({2, 3});
    std::cout << tensor << std::endl;
    std::cout << torch::cuda::is_available() << std::endl;
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

在这里插入图片描述
出现上面的输出,libtorch配置成功

如果在CMAKE时出现Unicode相关字样的报错,请参考我的这篇博客:warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失

四、PyTorch CUDA CPP项目流程

在这里插入图片描述
可以把C++理解为沟通Pytorch代码和CUDA代码的桥梁

1.使用CLion结合torch extension编写可以调用cuda的C++代码

在这里插入图片描述
创建interpolation.cpp,你可以理解为该文件内存放着主机函数(即在CPU上执行的函数),用于调用设备函数(即在GPU上执行的函数)。

#include <torch/extension.h>
#include "include/utils.h"   // 自定义的header

torch::Tensor trilinear_interpolation_fw(torch::Tensor feats, torch::Tensor points){
    /*使用torch库定义cuda函数
     * 相当于在主机函数中调用设备函数代码
     * 用于执行前向传播操作
     * */
    // 只要是传进来的参数是Tensor类型,都要进行CHECK_INPUT
    // 如果传入的参数是一般的int,float等则不需要CHECK_INPUT
    CHECK_INPUT(feats);
    CHECK_INPUT(points);
	
	// trilinear_fw_cu() 为设备函数,
	// 可以理解为主机函数作为设备函数的入口,这只是一种写法,你也可以有别的写法
    return trilinear_fw_cu(feats, points);
}

torch::Tensor trilinear_interpolation_bw(torch::Tensor dL_dfeat_interp, torch::Tensor feats, torch::Tensor points){
	/*使用torch库定义cuda函数
	 * 相当于在主机函数中调用设备函数代码
	 * 用于执行反向传播操作,计算梯度
	 * */
	// 只要是传进来的参数是Tensor类型,都要进行CHECK_INPUT
	// 如果传入的参数是一般的int,float等则不需要CHECK_INPUT
	CHECK_INPUT(dL_dfeat_interp);
	CHECK_INPUT(feats);
	CHECK_INPUT(points);

	return trilinear_bw_cu(dL_dfeat_interp, feats, points);
}


PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
    /*pybind库用于作为连接python和C++的桥梁
     * name_: 表示python调用c++函数的名称(名称可一致也可以不一致),f:表示c++函数的地址*/
    m.def("trilinear_interpolation_fw", &trilinear_interpolation_fw);
    m.def("trilinear_interpolation_bw", &trilinear_interpolation_bw);
}

2.编写CU设备代码,在代码中调用CUDA核函数使用GPU运算

注意:设备函数卸载 .cu 文件中,只有这样在编译时nvcc才能识别
创建interpolation_kernel.cu编写设备函数
在这里插入图片描述

#include <torch/extension.h>

template<typename scalar_t>
__global__ void trilinear_bw_kernal(
	const torch::PackedTensorAccessor<scalar_t, 2, torch::RestrictPtrTraits, size_t> dL_dfeats_interp,
	const torch::PackedTensorAccessor<scalar_t, 3, torch::RestrictPtrTraits, size_t> feats,
	const torch::PackedTensorAccessor<scalar_t, 2, torch::RestrictPtrTraits, size_t> points,
	torch::PackedTensorAccessor<scalar_t, 3, torch::RestrictPtrTraits, size_t> dL_dfeats
) {
	// 得到当前线程的在整个grid中的索引,每个线程计算一组数据
	const int n = blockIdx.x * blockDim.x + threadIdx.x;  // n表示每一个点的index是多少
	const int f = blockIdx.y * blockDim.y + threadIdx.y;  // f表示每一个点的第f个特征

	// 因为实际定义的线程数量是>=数据量的,因此会存在用不到某些线程的情况,因此需要将这部分线程剔除掉
	// n<feats.size(0) && f<feats.size(2) 因此只使用索引小于数据量的线程
	if (n >= feats.size(0) || f >= feats.size(2)) return;

	// 每个点的范围在[-1, 1]之间,因此需要进行归一化 ->[0, 1]
	const scalar_t u = (points[n][0] + 1) / 2;
	const scalar_t v = (points[n][1] + 1) / 2;
	const scalar_t w = (points[n][2] + 1) / 2;

	// 计算内插权重
	// 三线性插值可以分解为两组双线性插值加一组单线性插值
	// a b c d表示计算双线性插值时的4个顶点对应的插值权重
	const scalar_t a = (1 - v) * (1 - w);
	const scalar_t b = (1 - v) * w;
	const scalar_t c = v * (1 - w);
	const scalar_t d = 1 - a - b - c;

	// 计算偏导数,我们需要实现根据公式计算出8个特征点的偏微分
	dL_dfeats[n][0][f] = (1-u)*a*dL_dfeats_interp[n][f];
	dL_dfeats[n][1][f] = (1-u)*b*dL_dfeats_interp[n][f];
	dL_dfeats[n][2][f] = (1-u)*c*dL_dfeats_interp[n][f];
	dL_dfeats[n][3][f] = (1-u)*d*dL_dfeats_interp[n][f];
	dL_dfeats[n][4][f] = u*a*dL_dfeats_interp[n][f];
	dL_dfeats[n][5][f] = u*b*dL_dfeats_interp[n][f];
	dL_dfeats[n][6][f] = u*c*dL_dfeats_interp[n][f];
	dL_dfeats[n][7][f] = u*d*dL_dfeats_interp[n][f];
}

// template <typename scalar_t> 表示告诉函数,函数输入参数的类型会根据scalar_t进行可变的对应
// 下面的参数的输入方法也是固定的,之后可以复制后并简单修改后使用
template<typename scalar_t>
__global__ void trilinear_fw_kernal(
	const torch::PackedTensorAccessor<scalar_t, 3, torch::RestrictPtrTraits, size_t> feats,
	const torch::PackedTensorAccessor<scalar_t, 2, torch::RestrictPtrTraits, size_t> points,
	torch::PackedTensorAccessor<scalar_t, 2, torch::RestrictPtrTraits, size_t> feat_interp
) {
	// 得到当前线程的在整个grid中的索引,每个线程计算一组数据
	const int n = blockIdx.x * blockDim.x + threadIdx.x;  // n表示每一个点的index是多少
	const int f = blockIdx.y * blockDim.y + threadIdx.y;  // f表示每一个点的第f个特征

	// 因为实际定义的线程数量是>=数据量的,因此会存在用不到某些线程的情况,因此需要将这部分线程剔除掉
	// n<feats.size(0) && f<feats.size(2) 因此只使用索引小于数据量的线程
	if (n >= feats.size(0) || f >= feats.size(2)) return;

	// 每个点的范围在[-1, 1]之间,因此需要进行归一化 ->[0, 1]
	const scalar_t u = (points[n][0] + 1) / 2;
	const scalar_t v = (points[n][1] + 1) / 2;
	const scalar_t w = (points[n][2] + 1) / 2;

	// 计算内插权重
	// 三线性插值可以分解为两组双线性插值加一组单线性插值
	// a b c d表示计算双线性插值时的4个顶点对应的插值权重
	const scalar_t a = (1 - v) * (1 - w);
	const scalar_t b = (1 - v) * w;
	const scalar_t c = v * (1 - w);
	const scalar_t d = 1 - a - b - c;
	feat_interp[n][f] = (1 - u) * (a * feats[n][0][f] +
		b * feats[n][1][f] +
		c * feats[n][2][f] +
		d * feats[n][3][f]) +
		u * (a * feats[n][4][f] +
			b * feats[n][5][f] +
			c * feats[n][6][f] +
			d * feats[n][7][f]);
}

/*如果想要在C++中回传多个值,那么可以直接回传一个vector类型,
 * std::vector<torch::Tensor> trilinear_fw_cu(){
 * 		return {a, b, c, ...}
 * }
 * */
torch::Tensor trilinear_fw_cu(torch::Tensor feats, torch::Tensor points) {
	/*使用torch库定义cuda函数
	 * fw:表示前向传播   bw:表示反向传播   cu:表示这是一个cuda程序
	 * feats: [N, 8, F],表示N个元素,每个元素有8个顶点以及对应的特征
	 * points: [N, 3],表示N个元素,每个元素有3个顶点坐标
	 * */
	const int N = feats.size(0), F = feats.size(2);
	// 生成output变量,并指定变量的数据类型,并指定数据存放在哪个设备上
	// 将运算结果存储在output变量中
	torch::Tensor feat_interp = torch::zeros({N, F}, torch::dtype(torch::kFloat32).device(feats.device()));

// 定义grid和block大小
// 根据要同时运算的数据维度的大小,建立相应大小的thread
	const dim3 threads(16, 16);  // 定义一个block的大小,一个block内有多少个线程,一般一个block定义为256个线程不会出错
	const dim3
		blocks((N + threads.x - 1) / threads.x, (N + threads.y - 1) / threads.y);  // 定义一个grid的大小,一个grid内有多少个block
/*假设输入的大小为N=20,F=10;而一个block的大小为16*16,
 * 因此需要额外的一个block才能包含整个输入数据,因此一个grid的大小应该为2*1
 * 线程的数量应该>=数据量 */

// 启动一个kernel
// 下面的书写方法是固定的,以后复制粘贴使用即可
/* AT_DISPATCH_FLOATING_TYPES: 表示启动的这个kernel用于浮点数运算,包括float32和float64的运算
 * AT_DISPATCH_FLOATING_TYPES_HALF: 表示可以进行16,32,64位的浮点数运算
 * AT_DISPATCH_INTEGRAL_TYPES: 表示进行32和64位整数运算
 * feats.type(): 表示feats的数据类型
 * "trilinear_fw_cu": 表示启动的kernel的名字,建议kernel的名称与函数的名称一致,方便找错误
 * trilinear_fw_cu<scalar_t><<<blocks, threads>>>: 丢出一个kernel,指定kernel的名称,用于在设备函数上进行相应的运算
 *      <scalar_t>: 表示相当于一个placeholder,因为不知道参数具体是什么类型,因此先占个位置;如果知道是什么类型,直接写上对应的类型即可,比如float
 *      <<<blocks, threads>>>: 表示启动的kernel的线程数,以及线程的block数
 * feats.packed_accessor<scalar_t, 3, torch::RestrictPtrTraits, size_t>()
 *      feats.packed_accessor,points.packed_accessor,feat_interp.packed_accessor 表示这个kernel函数传入的三个值
 *      packed_accessor表示这个值是一个指针,指向一个内存地址,这个地址存储着数据,并且这个地址可以被grid和block访问,并且这个地址可以被cuda的线程访问
 *          只有Tensor参数才需要进行packed_accessor处理,如果是一般的参数则直接写入参数列表即可
 *          比如:trilinear_fw_cu<scalar_t><<<blocks, threads>>>(a, feats.packed_accessor<scalar_t, 3, torch::RestrictPtrTraits, size_t>(), ...)
 *      scalar_t: 表示参数类型
 *      3: 表示参数维度,这里表示传入的参数维度为3
 *      torch::RestrictPtrTraits: 表示这个参数是restrict指针,表示这个参数不能被修改 (torch::RestrictPtrTraits和size_t这两个参数一般不用改)
 *      size_t: 表示这个参数这种类型占用的内存大小,用于访问内存
 * */
	AT_DISPATCH_FLOATING_TYPES(feats.type(), "trilinear_fw_cu",
							   ([&] {
								 trilinear_fw_kernal<scalar_t><<<blocks, threads>>>(
									 feats.packed_accessor<scalar_t, 3, torch::RestrictPtrTraits, size_t>(),
									 points.packed_accessor<scalar_t, 2, torch::RestrictPtrTraits, size_t>(),
									 feat_interp.packed_accessor<scalar_t, 2, torch::RestrictPtrTraits, size_t>()
								 );
							   }));
	return feat_interp;
}

/*反向传播,利用梯度更细参数的值 */
torch::Tensor trilinear_bw_cu(
	torch::Tensor dL_dfeat_interp,
	torch::Tensor feats,
	torch::Tensor points) {
	/* 用于反向传播计算特征的梯度
	 * dL_dfeat_interp: 表示根据损失值计算出来的特征关于损失的梯度
	 * */
	const int N = feats.size(0), F = feats.size(2);
	torch::Tensor dL_dfeats = torch::zeros({N, 8, F}, feats.options());  // 微分后的值的size和原来是一样大的,保存每个特征的偏微分值

	const dim3 threads(16, 16);
	const dim3 blocks((N + threads.x - 1) / threads.x, (F + threads.y - 1) / threads.y);

	AT_DISPATCH_FLOATING_TYPES(feats.type(), "trilinear_bw_cu",
							   ([&] {
								 trilinear_bw_kernal<scalar_t><<<blocks, threads>>>(
									 dL_dfeat_interp.packed_accessor<scalar_t, 2, torch::RestrictPtrTraits, size_t>(),
									 feats.packed_accessor<scalar_t, 3, torch::RestrictPtrTraits, size_t>(),
									 points.packed_accessor<scalar_t, 2, torch::RestrictPtrTraits, size_t>(),
									 dL_dfeats.packed_accessor<scalar_t, 3, torch::RestrictPtrTraits, size_t>()
								 );
							   }));
	return dL_dfeats;
}

3.创建header定义设备函数

创建include/utils.h头文件,定义函数
在这里插入图片描述

#ifndef TORCH_CUDA_CPP_UTILS_H
#define TORCH_CUDA_CPP_UTILS_H

#endif //TORCH_CUDA_CPP_UTILS_H

#include <torch/extension.h>

// 下面的define是必须要添加的,作用类似于python的assert
// CHECK_CUDA 检查变量是否为一个gpu的tensor
// CHECK_CONTIGUOUS 检测每一个tensor在内存上是否是连续的
// CHECK_INPUT 表示检测上面两个函数
#define CHECK_CUDA(x) TORCH_CHECK(x.type().is_cuda(), #x " must be a CUDA tensor")
#define CHECK_CONTIGUOUS(x) TORCH_CHECK(x.is_contiguous(), #x " must be contiguous")
#define CHECK_INPUT(x) CHECK_CUDA(x); CHECK_CONTIGUOUS(x)

torch::Tensor trilinear_fw_cu(torch::Tensor feats, torch::Tensor points);

torch::Tensor trilinear_bw_cu(
	torch::Tensor dL_dfeat_interp,
	torch::Tensor feats,
	torch::Tensor points);

4.编写CPP主机代码,在代码中使用PYBIND库连接pytorch和c++代码,使用主机函数调用设备函数

在这里插入图片描述

PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
    /*pybind库用于作为连接python和C++的桥梁
     * name_: 表示python调用c++函数的名称(名称可一致也可以不一致),f:表示c++函数的地址*/
    m.def("trilinear_interpolation_fw", &trilinear_interpolation_fw);
    m.def("trilinear_interpolation_bw", &trilinear_interpolation_bw);
}

trilinear_interpolation_fw表示你在python代码中调用cuda函数时,这个cuda函数的别名,这个别名进行和CUDA函数保持一致,报错了好找位置。trilinear_interpolation_bw同理

5.在Pycharm中创建setup.py脚本,将C++编写的cuda代码编译为python的一个package

一定是创建setup.py,不能是别的名称
在这里插入图片描述

# -*- coding: utf-8 -*-
#        Data: 2023/11/14 10:30
#     Project: torch_cuda_cpp
#   File Name: setup.py
#      Author: KangPeilun
#       Email: 374774222@qq.com 
# Description:

import glob
import os.path as osp
from setuptools import setup
from torch.utils.cpp_extension import CppExtension, BuildExtension, CUDAExtension

ROOT_DIR = r"E:\CLion\Projects\torch_cuda_cpp"  # 填入C++项目的根目录
include_dirs = [osp.join(ROOT_DIR, 'include')]  # 获取头文件文件夹
sources = glob.glob(osp.join(ROOT_DIR, "*.cpp")) + glob.glob(osp.join(ROOT_DIR, "*.cu"))    # 获取所有cpp和cu文件路径

# 这个setup.py脚本的目的是为了编译一个C++的扩展模块
# 这里使用的CppExtension编译的其实是CPU版本的代码,并没有调用cuda
# setup(
#     name='cuda_ext',  # 定义package的名称,也就是python中import调出来的名称
#     version='1.0',    # 然后你可以定义版本,作者,简介
#     author='Kang Peilun',
#     author_email='374774222@qq.com',
#     description='cudacpp example',
#     long_description=
#     'this is a cudacpp example',
#     ext_modules=[  # 指定需要build的c++代码在哪里, 在sources参数中指定代码路径,如果有多个路径则在list中一次添加即可
#         CppExtension(name='cuda_ext', sources=[
#             r'E:\CLion\Projects\torch_cuda_cpp\interpolation.cpp'  # 可以指定C++文件的绝对路径
#         ])
#     ],
#     cmdclass={  # 告诉代码需要build这个东西
#         'build_ext': BuildExtension
#     }
# )

# 对于CUDA程序的编译,需要使用CUDAExtension
setup(
    name='cuda_ext',  # 定义package的名称,也就是python中import调出来的名称
    version='1.0',    # 然后你可以定义版本,作者,简介
    author='Kang Peilun',
    author_email='374774222@qq.com',
    description='cudacpp example',
    long_description=
    'this is a cudacpp example',
    ext_modules=[  # 指定需要build的c++代码在哪里, 在sources参数中指定代码路径,如果有多个路径则在list中一次添加即可
        CUDAExtension(
            name='cuda_ext',
            sources=sources,  # 导入cpp和cu文件的路径
            include_dirs=include_dirs,  # 导入header的所在的文件夹
        )
    ],
    cmdclass={  # 告诉代码需要build这个东西
        'build_ext': BuildExtension
    }
)

6.使用pip安装自定义的cuda代码库

使用setup.py编译安装自定义的CUDA库
注意一定得是setup.py所在文件夹

pip install .
# . 表示setup.py所在的文件夹
# 当setup.py在别的文件夹中时,把.替换为setup.py所在文件夹路径即可,
# 注意一定得是setup.py所在文件夹

在这里插入图片描述
出现下方字样即代表,安装成功

7.安装好自定义的cuda自定义库之后,编写python代码调用

在这里插入图片描述

# -*- coding: utf-8 -*-
#        Data: 2023/11/14 15:24
#     Project: torch_cuda_cpp
#   File Name: interpolation.py
#      Author: KangPeilun
#       Email: 374774222@qq.com 
# Description:
import torch
import cuda_ext
from time import time

def trilinear_interpolation_py(feats, points):
    """
    :param feats: [N, 8, F]
    :param points: [N, 3]
    :return:
    """
    u = (points[:, 0:1]+1)/2
    v = (points[:, 1:2]+1)/2
    w = (points[:, 2:3]+1)/2

    a = (1-v)*(1-w)
    b = (1-v)*w
    c = v*(1-w)
    d = 1-a-b-c  # <=> d = v*w

    feats_interp = (1-u)*(a*feats[:, 0] + b*feats[:, 1] + c*feats[:, 2] + d*feats[:, 3]) +\
                   u*(a*feats[:, 4] + b*feats[:, 5] + c*feats[:, 6] + d*feats[:, 7])

    return feats_interp


class Trilinear_interpolation_cuda(torch.autograd.Function):
    '''使用torch.autograd.Function包装fw和bw处理
    实现这个类,需要手动实现forward和backward两个函数,并且要用@staticmethod修饰
    ctx: 负责存储反向传播用到的值,这个参数不能省略
    forward的返回值的个数与backward输入参数的个数一致
    backward的返回值个数与forward的输入参数个数一致,且返回计算过梯度的参数,如果某个参数不需要计算梯度,则对应返回None
    '''
    @staticmethod
    def forward(ctx, feats, points):
        feat_interp = cuda_ext.trilinear_interpolation_fw(feats, points)
        ctx.save_for_backward(feats, points)
        return feat_interp

    @staticmethod
    def backward(ctx, dL_dfeat_interp):
        feats, points = ctx.saved_tensors
        dL_feats = cuda_ext.trilinear_interpolation_bw(dL_dfeat_interp.contiguous(), feats, points)
        return dL_feats, None


if __name__ == '__main__':
    N = 65536
    F = 256
    # feats = torch.rand(N, 8, F, device='cuda').requires_grad_()
    rand = torch.rand(N, 8, F, device='cuda')
    feats1 = rand.clone().requires_grad_()
    feats2 = rand.clone().requires_grad_()

    points = torch.rand(N, 3, device='cuda')*2 - 1

    t = time()
    # 自定义的CUDA函数是没有办法自动计算梯度的,需要我们手动计算梯度
    # 通过torch.autograd.grad自动保存计算后的梯度
    # 使用 .apply 执行自定义的CUDA前向传播过程
    out_cuda = Trilinear_interpolation_cuda.apply(feats1, points)
    torch.cuda.synchronize()
    print("\tCUDA time: ", time()-t, out_cuda)

    # 1.使用Pytorch计算梯度
    t = time()
    out_py = trilinear_interpolation_py(feats2, points)   # Pytorch可以自动计算梯度
    torch.cuda.synchronize()
    print("\tPytorch time: ", time()-t, out_py)
    #
    # print(torch.allclose(out_cuda, out_py))  # 判断两个tensor在误差范围内是否接近

    loss2 = out_py.sum()  # 简单的将他们的和作为loss,仅用于测试
    loss2.backward()  # 反向传播计算梯度

    # 2.使用自定义CUDA函数计算梯度
    loss1 = out_cuda.sum()
    loss1.backward()

    print("bw all close", torch.allclose(feats1.grad, feats2.grad))

五、总结

第四部分介绍了整个Pytorch CUDA CPP项目一般会包含哪些文件,所有代码都是基于教程:Pytorch+cpp/cuda extension 教学 编写,推荐有时间和能力的人把这个教程学完,会受益匪浅的。

总体来说Pytorch CUDA CPP项目一般同时会包含.py、.cpp、.cu、.h文件,cpp文件存放主机函数用于调用设备函数,cu文件存放设备函数用于调用kernel核函数在GPU上进行运算,setup.py文件用于将自定义的cuda程序构建为python的一个package,其他py文件调用package实现python调用C++代码,C++代码调用CUDA程序这一流程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1210629.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

推介会如何做好媒体宣传

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 推介会是一种专为企业、社会组织和团体、政府等提供的展示自身特点、产品和政策的活动形式&#xff0c;旨在促进交流活动&#xff0c;形成合作&#xff0c;从而带来共同利益。推介会的本…

硬盘分区后数据还能恢复吗?答案揭晓!

“前两天刚给我的电脑硬盘分了区&#xff0c;但今天在查找数据时却发现某些数据丢失了。硬盘分区导致的数据丢失还有机会找回吗&#xff1f;怎么操作呢&#xff1f;请帮帮我&#xff01;” 在使用电脑时&#xff0c;可能由于电脑需要重装系统&#xff0c;或者出现系统崩溃的情况…

天气越来越寒冷,一定要注意保暖

你们那里下雪了吗&#xff1f;听说西安已经下了今年的第一场雪&#xff0c;我们这里虽然隔了几百公里&#xff0c;但是只下雨没有下雪&#xff0c;不过气温是特别的冷&#xff0c;尤其是对我们这些上班族和上学的人而言&#xff0c;不管多冷&#xff0c;不管刮风下雨&#xff0…

InputStream和OutputStream

文章目录 &#x1f4a1;FileInputStream 构造方法&#x1f4a1;InputStream 常用方法&#x1f4a1;OutputStream 概述&#x1f4a1;OutputStream 常用方法&#x1f4a1;PrintWriter&#x1f4a1;小程序练习 使用字节流进行读/写操作时&#xff0c;是以字节为单位的&#xff1b;…

Python如何使用Matplotlib模块的pie()函数绘制饼形图?

Python如何使用Matplotlib模块的pie函数绘制饼形图&#xff1f; 1 模块安装2 实现思路3 pie()函数说明4 实现过程4.1 导入包4.2 定义一个类4.3 读取数据并处理4.4 定义饼图绘制方法 5 完整源码 1 模块安装 先安装matplotlib&#xff1a; pip install matplotlib安装numpy模块…

Pandas教程(非常详细)(第六部分)

接着Pandas教程&#xff08;非常详细&#xff09;&#xff08;第五部分&#xff09;&#xff0c;继续讲述。 三十一、Pandas Excel读写操作详解 Excel 是由微软公司开发的办公软件之一&#xff0c;它在日常工作中得到了广泛的应用。在数据量较少的情况下&#xff0c;Excel 对…

动手学深度学习——序列模型

序列模型 1. 统计工具1.1 自回归模型1.2 马尔可夫模型 2. 训练3. 预测4. 小结 序列模型是一类机器学习模型&#xff0c;用于处理具有时序关系的数据。这些模型被广泛应用于自然语言处理、音频处理、时间序列分析等领域。 以下是几种常见的序列模型&#xff1a; 隐马尔可夫模型…

忘记7-zip密码,如何解压文件?

7z压缩包设置了密码&#xff0c;解压的时候就需要输入正确对密码才能顺利解压出文件&#xff0c;正常当我们解压文件或者删除密码的时候&#xff0c;虽然方法多&#xff0c;但是都需要输入正确的密码才能完成。忘记密码就无法进行操作。 那么&#xff0c;忘记了7z压缩包的密码…

字符流的讲解 以及 Reader和Writer的用法

文章目录 ❤专栏导读❤字符流❤Reader类的操作 ❤Writer类操作❤Writer类的构造方法 ❤专栏导读 &#x1f680;《多线程》 &#x1f680;《数据结构剖析》 &#x1f680;《JavaSE语法》 在Java标准库中&#xff0c;提供的读写文件的流对象有很多很多的类&#xff0c;但是可以将…

盘点49个Python网站项目Python爱好者不容错过

盘点49个Python网站项目Python爱好者不容错过 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 链接&#xff1a;https://pan.baidu.com/s/1aFYJtNZjgst1l5KFBckP2A?pwd8888 提取码&#xff1a;8888 项目名称 A simpleshorturl…

C++ vector中capacity()和size() 的区别

文章目录 1 capacity()和size() 介绍2 vector满了之后&#xff0c;capacity()会自动了扩充为原来的2倍 &#xff1f; 1 capacity()和size() 介绍 size是指容器当前拥有元素的个数&#xff0c; capacity是指容器在必须分配新的存储空间之前可以存放的元素总数。 如vector<i…

PasswordPusher:能通过URL安全传递密码

什么是 Password Pusher &#xff1f; Password Pusher 是一个开源应用程序&#xff0c;用于通过网络安全的传递密码。在经过一定数量的查看和/或时间后&#xff0c;指向密码的链接会过期。 从功能上说&#xff0c;类似于 Bitwarden Send&#xff0c;思路上与传统阅后即焚工具一…

智能电网线路阻抗模拟的工作原理

智能电网线路阻抗模拟是一种通过模拟电网线路的阻抗特性来实现电网故障检测和定位的技术。智能电网系统通过安装在电网线路上的传感器&#xff0c;实时采集线路上的电流、电压等参数&#xff0c;并将这些数据传输到监控中心。监控中心接收到传感器采集的数据后&#xff0c;对数…

Java排序算法之贪心算法

贪心算法是一种优化问题的解决方法&#xff0c;它在每一步选择中都采取在当前状态下最好或最优&#xff08;即最有利&#xff09;的选择&#xff0c;从而希望导致结果是全局最优的。贪心算法常用于最优化问题&#xff0c;比如最小生成树、哈夫曼编码、最短路径等。贪心算法是一…

《ThingsBoard从入门到精通》的优秀课程

0、介绍 多年java物联网行业开发&#xff0c;精通物联网平台的架构与设计&#xff0c;精通开源物联网平台ThingsBoard&#xff0c;ThingsBoard专家。最近出了一套《ThingsBoard从入门到精通》的全套课程&#xff0c;只要学习了这一套课程&#xff0c;你将随便驾驭ThingsBoard。…

.net core windows 最详细步骤,傻瓜式如何安装rabbitmq 通知消息中间件

本次安装环境信息&#xff1a; 系统&#xff1a;win11 64位专业版 erlang rabbitMQ 一、下载安装程序 rabbitMQ安装程序下载路径&#xff1a;Installing on Windows — RabbitMQ 不能下载使用 Index of /download/ (erlang.org) otp_win64_22.0.exe

Qt图形视图框架:QGraphicsItem详解

Qt图形视图框架&#xff1a;QGraphicsItem详解 Chapter1 Qt图形视图框架&#xff1a;QGraphicsItem详解Chapter2 自定义QGraphicsItem实现平移、改变尺寸和旋转1. 平移2. 改变尺寸3. 旋转完整代码如下&#xff1a;头文件源文件 Chapter1 Qt图形视图框架&#xff1a;QGraphicsIt…

docker部署Prometheus+Cadvisor+Grafana实现服务器监控

一&#xff1a;Prometheus 1&#xff1a;介绍&#xff1a; Prometheus是一个在SoundCloud上构建的开源系统监视和警报工具包 2&#xff1a;特点 多维度数据模型-由指标键值对标识的时间序列数据组成&#xff1b;PromQL&#xff0c;一种灵活的查询语言&#xff1b;不依赖分布…

前端JS解构数组对象

// 3. 对象数组解构const arr [{username: 小明,age: 18,agw:19},{username: 小ha,age: 18,agw:19}]arr.map(item>item.age)//js结构数组对象console.log( arr.map(item>{return {aaa:item.age,bbb:item.username}}))

【Proteus仿真】【Arduino单片机】DHT11温湿度

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用PCF8574、LCD1602液晶、DHT11温湿度传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示传感器采集温度和湿度。 二、软件设…