【CUDA编程--编程模型简介算子开发流程】

在这里插入图片描述

官方文档:https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html

什么是CUDA

  • CUDA全称(Compute Unified Device Architecture)统一计算架构,是NVIDIA推出的并行计算平台
  • 深度学习加速:对于神经网络,无论是离线训练还是在线推理,都有巨量的矩阵、归一化、softmax等运算,且其中有非常多的并行计算,非常适合用GPU来进行运算加速
    在这里插入图片描述
  • 一般来说,应用程序混合有并行部分和顺序部分,因此系统设计时混合使用 GPU 和 CPU,以最大限度地提高整体性能。具有高度并行性的应用程序可以利用 GPU 的大规模并行特性来实现比 CPU 更高的性能

CUDA编程模型

  • 多核CPU和众核GPU的出现意味着主流处理器芯片现在都是并行系统

kernel 核

  • 不同于C语言中函数的调用,CUDA的内核函数调用时需要指定总的线程数量,以及相应的线程布局(grid和block维度配置)
// C函数
function_name (argument list);
// CUDA kernel call
kernel_name<<<4, 8>>>(argument list);  // 这里执行有grid中有4个block, 以及每个block中有8个线程运行

在这里插入图片描述

限定符

因为数据在全局内存中是线性存储的,所以可以通过blockIdx.x和threadIdx.x来标识grid中的线程,建立线程和数据之间的映射关系
核函数限定符的意义如下

限定符执行调用备注
globalDevice执行Host调用/Device调用必须有一个void的返回类型
deviceDevice执行Device调用
hostHost执行Host调用

举例

  • 实现的功能是两个长度为的tensor相加,每个block有1024个线程,一共有n/1024
    个block
    cudademo.cu
#include <iostream>  
#include <cuda_runtime.h>

// 代码的核心诉求(Cuda上运行):
// 输入a: 0,1,2,3,4.....
// 输入b: 0,2,4,6,8.....
// 输出c: 0,3,6,9,12.....

__global__ void my_add_kernel(float* c,  
                            const float* a,  
                            const float* b,  
                            int n) {  
    // 定义核函数 add  
    for (int i = blockIdx.x * blockDim.x + threadIdx.x;  
            i < n; i += gridDim.x * blockDim.x) {  
        c[i] = a[i] + b[i];  
    }  
}

int main() {
    int n = 1000; // 元素数量

    // 分配主机内存用于输入和输出数组
    float* host_a = new float[n];
    float* host_b = new float[n];
    float* host_c = new float[n];

    // 在主机上填充输入数组 a 和 b
    for (int i = 0; i < n; i++) {
        host_a[i] = i;
        host_b[i] = i * 2;
    }

    // 在设备上分配内存用于输入和输出数组
    float* device_a;
    float* device_b;
    float* device_c;
    cudaMalloc((void**)&device_a, n * sizeof(float));
    cudaMalloc((void**)&device_b, n * sizeof(float));
    cudaMalloc((void**)&device_c, n * sizeof(float));

    // 将输入数组从主机内存复制到设备内存
    cudaMemcpy(device_a, host_a, n * sizeof(float), cudaMemcpyHostToDevice);
    cudaMemcpy(device_b, host_b, n * sizeof(float), cudaMemcpyHostToDevice);

    // 定义 CUDA 核函数的执行配置
    int block_size = 256;
    int grid_size = (n + block_size - 1) / block_size;

    // 调用 CUDA 核函数
    my_add_kernel<<<grid_size, block_size>>>(device_c, device_a, device_b, n);

    // 将输出数组从设备内存复制到主机内存
    cudaMemcpy(host_c, device_c, n * sizeof(float), cudaMemcpyDeviceToHost);

    // 打印输出数组
    for (int i = 0; i < n; i++) {
        std::cout << host_c[i] << " ";
    }
    std::cout << std::endl;

    // 释放主机和设备内存
    delete[] host_a;
    delete[] host_b;
    delete[] host_c;
    cudaFree(device_a);
    cudaFree(device_b);
    cudaFree(device_c);

    return 0;
}
  • 直接编译执行
[]# nvcc cudademo.cu -o cudademo
[]# cudademo
0 3 6 9 12 15 18 21 24 27 30 33 36 39 42 45 48 51 54 57 60 63 .....
如何准备安装cuda运行环境
  • 确认驱动版本等信息
[]:~$ nvidia-smi
Mon Nov 13 11:22:17 2023       
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 515.65.01    Driver Version: 515.65.01    CUDA Version: 11.7     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|                               |                      |               MIG M. |
|===============================+======================+======================|
|   0  NVIDIA GeForce ...  Off  | 00000000:35:00.0 Off |                  N/A |
| 30%   23C    P8    15W / 350W |    807MiB / 24576MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   1  NVIDIA GeForce ...  Off  | 00000000:36:00.0 Off |                  N/A |
| 30%   26C    P8    13W / 350W |      2MiB / 24576MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   2  NVIDIA GeForce ...  Off  | 00000000:39:00.0 Off |                  N/A |
| 75%   67C    P2   251W / 350W |   9306MiB / 24576MiB |     85%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
|   3  NVIDIA GeForce ...  Off  | 00000000:3D:00.0 Off |                  N/A |
| 30%   25C    P8    13W / 350W |      2MiB / 24576MiB |      0%      Default |
|                               |                      |                  N/A |
+-------------------------------+----------------------+----------------------+
  • 也就需要我们使用cuda11.7版本
  • cuda与pytorch 软件版本对应关系以及安装:https://pytorch.org/get-started/previous-versions/
  • cuda官方镜像网站:https://hub.docker.com/r/nvidia/cuda/tags
  • 当前根据版本对应关系,直接可以使用 docker pull nvidia/cuda:11.7.1-cudnn8-devel-ubuntu20.04 该版本
version: '3'
services:
  my_container:
    image: harbor.uat.enflame.cc/library/enflame.cn/nvidia/cuda:11.7.1-cudnn8-devel-ubuntu20.04
    runtime: nvidia  # 指定使用NVIDIA GPU运行时
    devices:
      - /dev/nvidia0  # 将主机的NVIDIA GPU设备映射到容器
      - /dev/nvidia1
      - /dev/nvidia2
      - /dev/nvidia3
      - /dev/nvidia4
      - /dev/nvidia5
      - /dev/nvidia6
      - /dev/nvidia7
    network_mode: host
    command: sleep 10000000000000000
    shm_size: '8gb'
    privileged: true

数据处理方式

佛林分类法Flynn’s Taxonomy,根据指令和数据进入CPU的方式对计算机架构进行分类,分为以下四类
- 单指令单数据 (SISD):传统的单核处理数据方式
- 单指令多数据(SIMD):单核执行一条指令完成多数据处理(游戏中向量、矩阵)
- 多指令单数据 (MISD):多核执行不同的指令处理单个数据(少见)
- 多指令多数据 (MIMD):多核执行不同的指令处理多个数据
在这里插入图片描述
为了提高并行的计算能力,架构上实现下面这些性能提升:

  • 降低延迟(latency):指操作从开始到结束所需要的时间,一般用微秒计算,延迟越低越好
  • 增加带宽(bandwidth):单位时间内处理的数据量,一般用MB/s或者GB/s表示
  • 增加吞吐(throughput):单位时间内成功处理的运算数量

内存划分

  • 分布式内存的多节点系统

    • 集群,各个机器之前通过网络进行数据交互
    • 传统的比如redis集群这种通信等
  • 共享内存的多处理器系统

    • 包括单片多核,多片多核,主要是针对同设备多核进行数据通信,GPU是众核架构,表述为Single Instruction, Multiple Thread (SIMT),不同于SIMD,SIMT是真正的启动了多个线程,执行相同的指令,去完成数据的并行运算
    • 3090显卡拥有10496个CUDA核心,相比上一代2080Ti显卡的4352个CUDA核心数量增加了一倍

编程结构

  • CUDA编程让你可以在CPU-GPU的异构计算系统上高效执行应用程序,语法只是在C语言的基础上做了简单的扩展,CUDA C++ 通过允许程序员定义称为内核的 C++ 函数来扩展 C++,这些函数在调用时由 N 个不同的CUDA 线程并行执行 N 次,而不是像常规 C++ 函数那样只执行一次,在开始编程前,我们首先得理清Host和Device的概念

    • Host:CPU及其内存
    • Device:GPU及其内存
  • 运行在GPU设备上的代码我们称为kernel

  • 典型的CUDA程序处理流程

    • 分配内存,数据初始化
      将数据从Host拷贝到Device
    • 调用kernels处理数据,然后存在GPU内存(Device)
    • 将数据从Device拷贝到Host
    • 内存释放
      在这里插入图片描述

内存管理

标准C函数CUDA 函数CUDA函数说明
malloccudaMallocGPU内存分配
memcpycudaMemcpy用于Host和Device之间数据传输
memsetcudaMemset设定数据填充到GPU内存中
freecudaFree释放GPU内存

在这里插入图片描述
在这里插入图片描述

  • CUDA 线程在执行期间可以访问多个内存空间中的数据
  • 每个线程都有私有本地内存
  • 每个线程块都有对该块的所有线程可见的共享内存,并且与该块具有相同的生命周期
  • 线程块簇中的线程块可以对彼此的共享内存执行读、写和原子操作。所有线程都可以访问相同的全局内存。
  • 还有两个可供所有线程访问的附加只读内存空间:常量内存空间和纹理内存空间

Grid&&Block

在这里插入图片描述

  • 一个Kernel所launch的所有线程称为grid,他们共享相同的全局内存空间(global memory space)
  • 一个grid由多个block(线程块)组成,block内部的线程可以通过以下两点进行协作(不同block间的线程不能协作)
    • block本地同步(synchronization)
    • block本地共享内存(sharedmemory)
  • 一个线程通过blockIdx(grid内的index)和threadIdx(block内的index)这两个坐标变量(三维类型unit3)来唯一标识(线程运行的时候这两个变量会被CUDA赋上相应的坐标值,可以直接使用)
  • grid和block的维度信息通过gridDim和blockDim(dim3)来表示
    • gridDim:表示一个grid里面有多少个blocks
    • blockDim:表示一个block里面有多少个threads
线程块结构

为了方便起见,threadIdx是一个3分量向量,因此可以使用一维、二维或三维线程索引 来标识线程,形成一维、二维或三维线程块,称为线程块。这提供了一种自然的方式来调用域中元素(例如向量、矩阵或体积)的计算
在这里插入图片描述
每个块的线程数量是有限的,因为块中的所有线程都应驻留在同一个流式多处理器核心上,并且必须共享该核心的有限内存资源。在当前的 GPU 上,一个线程块最多可以包含 1024 个线程

然而,一个内核可以由多个形状相同的线程块来执行,因此线程总数等于每个块的线程数乘以块数
块被组织成一维、二维或三维线程块网格,如图4所示。网格中线程块的数量通常由正在处理的数据的大小决定,该数据通常超过系统中处理器的数量

// Kernel definition
__global__ void MatAdd(float A[N][N], float B[N][N],
                       float C[N][N])
{
    int i = threadIdx.x;
    int j = threadIdx.y;
    C[i][j] = A[i][j] + B[i][j];
}

int main()
{
    ...
    // Kernel invocation with one block of N * N * 1 threads
    int numBlocks = 1;
    dim3 threadsPerBlock(N, N);
    MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
    ...
}

线程块簇

在这里插入图片描述

  • 随着 NVIDIA计算能力 9.0的推出,CUDA 编程模型引入了一个可选的层次结构级别,称为由线程块组成的线程块集群。与如何保证线程块中的线程在流式多处理器上共同调度类似,集群中的线程块也保证在 GPU 中的 GPU 处理集群 (GPC) 上共同调度
小结
  • 如何要使用cuda进行并行计算,使用cuda函数进行数据等操作
  • cuda的线程结构将cuda编程结构分为块与线程,都是可以由一维、二维或三维唯一索引来标识,如代码
{
    int i = threadIdx.x;
    int j = threadIdx.y;
    C[i][j] = A[i][j] + B[i][j];
}

GPU架构

阅读官方文档:https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html, 一句话:太多了,不如实操,还是简单总结下

GPU架构就是由可扩展的流式多处理器(Streaming Multiprocessors简称SM)阵列所构建,整个硬件的并行就是不断复制这种架构实现的。通常每个GPU都有多个SM,每个SM都支持上百个线程的并行,所以GPU可以支持上千个线程的并行

SM中的核心部件

  • CUDA Cores:核心,是最小的执行单元
  • Shared Memory/L1 Cache:共享内存和L1缓存,他们共用64KB空间,根据Bl
  • Register File:寄存器,根据线程划分
  • Load/Store Units:16个数据读写单元,支持16个线程一起从Cache/DRAM存取数据
  • Special Function Units:4个特殊函数处理单元,用于sin/cos这类指令计算
  • Warp Scheduler:Warp调度器,所谓Warp就是32个线程组成的线程束,是最小的调度单元

GPU内存

在这里插入图片描述

  • 寄存器:GPU上访问最快的存储空间,是SM中的稀缺资源,对于每个线程是私有的,Fermi架构中每个线程最多63个,Kepler结构扩展到255个。如果变量太多寄存器不够,会发生寄存器溢出,此时本地内存会存储多出来的变量,这种情况对性能影响较大。
  • 本地内存:本质上是和全局内存放在同一块存储区域中(compute capability 2.0以上的设备,会放在SM的一级缓存,或者设备的二级缓存上)具有高延迟、低带宽,编译器可能会将以下变量存放于本地内存:
    • 编译时期无法确定索引引用的本地数组
    • 可能会消耗大量寄存器的较大本地数组/结构体
    • 任何不满足核函数寄存器限定条件的变量
  • 共享内存:因为是片上内存,所以相比全局内存和本地内存,具有较高的带宽和较低的延迟
    • SM中的一级缓存,和共享内存共享一个64k的片上内存,L1不可编程,共享内存可以
    • 切勿过度使用共享内存,导致部分线程块无法被SM启动,影响Warp调度
    • 可以使用__syncthreads()来实现Block内线程的同步
  • 常量内存:驻留在设备内存中,每个SM都有专用的常量内存缓存
    • 常量内存在核函数外,全局范围内声明,对于所有设备,只可以声明64k的常量内存
    • 核函数无法修改,Host端使用cudaMemcpyToSymbol接口初始化
  • 纹理内存:驻留在设备内存中,在每个SM的只读缓存中缓存,对于2D数据的访问性能较好
  • 全局内存:GPU上最大的内存空间,延迟最高,使用最常见的内存,访问是对齐访问,也就是一次要读取指定大小(32,64,128)整数倍字节的内存,所以当线程束执行内存加载/存储时,需要满足的传输数量通常取决与以下两个因素:
  • 跨线程的内存地址分布
  • 内存事务的对齐方式。
修饰符变量名称存储器作用域生命周期
float var寄存器线程线程
float var[100]本地线程线程
sharefloat var*共享
devicefloat var*全局全局应用程序
constantfloat var*常量全局应用程序
存储器缓存存取范围生命周期
寄存器R/W一个线程线程
本地1.0以上有R/W一个线程线程
共享R/W块内所有线程
全局1.0以上有R/W所有线程+主机主机配置
常量R所有线程+主机主机配置
纹理R所有线程+主机主机配置
  • GPU缓存
    与CPU缓存类似,GPU缓存不可编程,其行为出厂是时已经设定好了。GPU上有4种缓存:
    • 一级缓存:每个SM都有一个一级缓存,与共享内存公用空间
    • 二级缓存:所有SM公用一个二级缓存
    • 只读常量缓存:每个SM有
    • 只读纹理缓存:每个SM有

案例

不说概念了,直接肝

获取GPU信息

#include<iostream>
#include<cuda.h>
#include<cuda_runtime.h>
int main() {
    int dev = 0;
    cudaDeviceProp devProp;
    cudaGetDeviceProperties(&devProp, dev);
    std::cout << "GPU Device Name" << dev << ": " << devProp.name << std::endl;
    std::cout << "SM Count: " << devProp.multiProcessorCount << std::endl;
    std::cout << "Shared Memory Size per Thread Block: " << devProp.sharedMemPerBlock / 1024.0 << " KB" << std::endl;
    std::cout << "Threads per Thread Block: " << devProp.maxThreadsPerBlock << std::endl;
    std::cout << "Threads per SM: " << devProp.maxThreadsPerMultiProcessor << std::endl;
    std::cout << "Warps per SM: " << devProp.maxThreadsPerMultiProcessor / 32 << std::endl;
    return 0;
}
[]# nvcc checkDeviceInfor.cu -o checkDeviceInfor
GPU Device Name0: NVIDIA GeForce RTX 3090
SM Count: 82
Shared Memory Size per Thread Block: 48 KB
Threads per Thread Block: 1024
Threads per SM: 1536
Warps per SM: 48

实现CUDA算子

下面的案例你将学习到:

  • 最简单的CUDA算子的写法。
  • 最简洁的PyTorch和TensorFlow封装CUDA算子的方法。
  • 几种编译CUDA算子的方法。
  • python调用CUDA算子的几种方式。
  • python中统计CUDA算子运行时间的正确方法。
  • PyTorch和TensorFlow自定义算子梯度的方法

代码结构

├── include
│   └── add2.h # cuda算子的头文件
├── kernel
│   ├── add2_kernel.cu # cuda算子的具体实现
│   └── add2.cpp # cuda算子的cpp torch封装
├── CMakeLists.txt
├── LICENSE
├── README.md
├── setup.py
├── time.py # 比较cuda算子和torch实现的时间差异
└── train.py # 使用cuda算子来训练模型

代码结构还是很清晰的。include文件夹用来放cuda算子的头文件(.h文件),里面是cuda算子的定义。kernel文件夹放cuda算子的具体实现(.cu文件)和cpp torch的接口封装(.cpp文件)。

最后是python端调用,我实现了两个功能。一是比较运行时间,上一篇教程详细讲过了;二是训练一个PyTorch模型

头文件
void launch_add2(float *c,
                 const float *a,
                 const float *b,
                 int n);
算子核函数
  • kernel/add2_kernel.cu
__global__ void add2_kernel(float* c,
                            const float* a,
                            const float* b,
                            int n) {
    for (int i = blockIdx.x * blockDim.x + threadIdx.x; \
            i < n; i += gridDim.x * blockDim.x) {
        c[i] = a[i] + b[i];
    }
}

void launch_add2(float* c,
                 const float* a,
                 const float* b,
                 int n) {
    dim3 grid((n + 1023) / 1024);
    dim3 block(1024);
    add2_kernel<<<grid, block>>>(c, a, b, n);
}
  • kernel/add2.cpp
#include <torch/extension.h>
#include "add2.h"

void torch_launch_add2(torch::Tensor &c,
                       const torch::Tensor &a,
                       const torch::Tensor &b,
                       int n) {
    launch_add2((float *)c.data_ptr(),
                (const float *)a.data_ptr(),
                (const float *)b.data_ptr(),
                n);
}

PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
    m.def("torch_launch_add2",
          &torch_launch_add2,
          "add2 kernel warpper");
} // cpp端用的是pybind11进行封装,适用python

TORCH_LIBRARY(add2, m) {
    m.def("torch_launch_add2", torch_launch_add2);
} // cpp端用的是TORCH_LIBRARY进行封装,适用于

编译
JIT

JIT就是just-in-time,也就是即时编译,或者说动态编译,就是说在python代码运行的时候再去编译cpp和cuda文件。

import torch
from torch.utils.cpp_extension import load
cuda_module = load(name="add2",
                   extra_include_paths=["include"],
                   sources=["kernel/add2.cpp", "kernel/add2_kernel.cu"],
                   verbose=True)
cuda_module.torch_launch_add2(c, a, b, n)

需要注意的就是两个参数,extra_include_paths表示包含的头文件目录,sources表示需要编译的代码,一般就是.cpp和.cu文件
运行成功可以看到

[1/2] nvcc -c add2_kernel.cu -o add2_kernel.cuda.o
[2/3] c++ -c add2.cpp -o add2.o
[3/3] c++ add2.o add2_kernel.cuda.o -shared -o add2.so
Setuptools

编译的方式是通过Setuptools,也就是编写setup.py,具体代码如下

from setuptools import setup
from torch.utils.cpp_extension import BuildExtension, CUDAExtension

setup(
    name="add2",
    include_dirs=["include"],
    ext_modules=[
        CUDAExtension(
            "add2",
            ["kernel/add2.cpp", "kernel/add2_kernel.cu"],
        )
    ],
    cmdclass={
        "build_ext": BuildExtension
    }
)

执行python3 setup.py install
这样就能生成动态链接库,同时将add2添加为python的模块了,可以直接import add2来调用

import torch
import add2
add2.torch_launch_add2(c, a, b, n)
cmake

最后就是cmake编译的方式了,要编写一个CMakeLists.txt文件,代码如下

cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
# 修改为你自己的nvcc路径,或者删掉这行,如果能运行的话。
set(CMAKE_CUDA_COMPILER "/usr/local/cuda/bin/nvcc")
project(add2 LANGUAGES CXX CUDA)

find_package(Torch REQUIRED)
find_package(CUDA REQUIRED)
find_library(TORCH_PYTHON_LIBRARY torch_python PATHS "${TORCH_INSTALL_PREFIX}/lib")

# 修改为你自己的python路径,或者删掉这行,如果能运行的话。
include_directories(/usr/include/python3.7)
include_directories(include)

set(SRCS kernel/add2.cpp kernel/add2_kernel.cu)
add_library(add2 SHARED ${SRCS})

target_link_libraries(add2 "${TORCH_LIBRARIES}" "${TORCH_PYTHON_LIBRARY}")

编译

mkdir build
cd build
cmake -DCMAKE_PREFIX_PATH="$(python3 -c 'import torch.utils; print(torch.utils.cmake_prefix_path)')" ../
make

最后会在build目录下生成一个libadd2.so,通过如下方式在python端调用:

import torch
torch.ops.load_library("build/libadd2.so")
torch.ops.add2.torch_launch_add2(c, a, b, n)
将上诉代码汇总
  • timer.py
import time
import argparse
import numpy as np
import torch

# c = a + b (shape: [n])
n = 1024 * 1024
a = torch.rand(n, device="cuda:0")
b = torch.rand(n, device="cuda:0")
cuda_c = torch.rand(n, device="cuda:0")

ntest = 10

def show_time(func):
    times = list()
    res = None
    # GPU warm up
    for _ in range(10):
        res = func()
    for _ in range(ntest):
        # sync the threads to get accurate cuda running time
        torch.cuda.synchronize(device="cuda:0")
        start_time = time.time()
        func()
        torch.cuda.synchronize(device="cuda:0")
        end_time = time.time()
        times.append((end_time-start_time)*1e6)
    return times, res

def run_cuda():
    if args.compiler == 'jit':
        cuda_module.torch_launch_add2(cuda_c, a, b, n)
    elif args.compiler == 'setup':
        add2.torch_launch_add2(cuda_c, a, b, n)
    elif args.compiler == 'cmake':
        torch.ops.add2.torch_launch_add2(cuda_c, a, b, n)
    else:
        raise Exception("Type of cuda compiler must be one of jit/setup/cmake.")

    return cuda_c

def run_torch():
    c = a + b
    return c.contiguous()

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--compiler', type=str, choices=['jit', 'setup', 'cmake'], default='jit')
    args = parser.parse_args()

    if args.compiler == 'jit':
        from torch.utils.cpp_extension import load
        cuda_module = load(name="add2",
                           extra_include_paths=["include"],
                           sources=["pytorch/add2_ops.cpp", "kernel/add2_kernel.cu"],
                           verbose=True)
    elif args.compiler == 'setup':
        import add2
    elif args.compiler == 'cmake':
        torch.ops.load_library("build/libadd2.so")
    else:
        raise Exception("Type of cuda compiler must be one of jit/setup/cmake.")

    print("Running cuda...")
    cuda_time, cuda_res = show_time(run_cuda)
    print("Cuda time:  {:.3f}us".format(np.mean(cuda_time)))

    print("Running torch...")
    torch_time, torch_res = show_time(run_torch)
    print("Torch time:  {:.3f}us".format(np.mean(torch_time)))

    torch.allclose(cuda_res, torch_res)
    print("Kernel test passed.")
  • train.py
import argparse
import numpy as np
import torch
from torch import nn
from torch.autograd import Function

class AddModelFunction(Function):
    @staticmethod
    def forward(ctx, a, b, n):
        c = torch.empty(n).to(device="cuda:0")

        if args.compiler == 'jit':
            cuda_module.torch_launch_add2(c, a, b, n)
        elif args.compiler == 'setup':
            add2.torch_launch_add2(c, a, b, n)
        elif args.compiler == 'cmake':
            torch.ops.add2.torch_launch_add2(c, a, b, n)
        else:
            raise Exception("Type of cuda compiler must be one of jit/setup/cmake.")

        return c

    @staticmethod
    def backward(ctx, grad_output):
        return (grad_output, grad_output, None)


class AddModel(nn.Module):
    def __init__(self, n):
        super(AddModel, self).__init__()
        self.n = n
        self.a = nn.Parameter(torch.Tensor(self.n))
        self.b = nn.Parameter(torch.Tensor(self.n))
        self.a.data.normal_(mean=0.0, std=1.0)
        self.b.data.normal_(mean=0.0, std=1.0)

    def forward(self):
        a2 = torch.square(self.a)
        b2 = torch.square(self.b)
        c = AddModelFunction.apply(a2, b2, self.n)
        return c

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--compiler', type=str, choices=['jit', 'setup', 'cmake'], default='jit')
    args = parser.parse_args()

    if args.compiler == 'jit':
        from torch.utils.cpp_extension import load
        cuda_module = load(name="add2",
                           extra_include_paths=["include"],
                           sources=["pytorch/add2_ops.cpp", "kernel/add2_kernel.cu"],
                           verbose=True)
    elif args.compiler == 'setup':
        import add2
    elif args.compiler == 'cmake':
        torch.ops.load_library("build/libadd2.so")
    else:
        raise Exception("Type of cuda compiler must be one of jit/setup/cmake.")

    n = 1024

    print("Initializing model...")
    model = AddModel(n)
    model.to(device="cuda:0")

    print("Initializing optimizer...")
    opt = torch.optim.SGD(model.parameters(), lr=0.01)

    print("Begin training...")
    for epoch in range(500):
        opt.zero_grad()
        output = model()
        loss = output.sum()
        loss.backward()
        opt.step()
        if epoch % 25 == 0:
            print("epoch {:>3d}: loss = {:>8.3f}".format(epoch, loss))

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

无线通信测量仪器-4945B/C 无线电通信综合测试仪

01 4945B/C 无线电通信综合测试仪 产品综述&#xff1a; 4945B/4945C无线电通信综合测试仪是多功能、便携式无线电综合测试类仪器&#xff0c;基于软件无线电架构&#xff0c;集成了跳频信号发生与分析、矢量信号发生与解调分析、模拟调制信号发生与解调分析、音频信号发生与…

C语言求数组中出现次数最多的元素

一、前言 遇到一个需求&#xff0c;需要求数组中出现次数最多的元素&#xff0c;查找了一些资料&#xff0c;结合自己的思路&#xff0c;编写了程序并验证。 只考虑元素为非负整数的数组&#xff0c;如果有出现次数相同的元素&#xff0c;则返回较小元素。 二、编程思路 以数…

python3+requests+unittest实战系列【二】

前言&#xff1a;上篇文章python3requestsunittest&#xff1a;接口自动化测试&#xff08;一&#xff09;已经介绍了基于unittest框架的实现接口自动化&#xff0c;但是也存在一些问题&#xff0c;比如最明显的测试数据和业务没有区分开&#xff0c;接口用例不便于管理等&…

AI主播“败走”双11,想用AI省成本的商家醒醒吧,程序员不必担心失业,发展空间依旧很大

目录 1 2 3 “AI人”并不算是新鲜事&#xff0c;随着AI的发展&#xff0c;AI主播也开始悄悄进入到直播间中。 持续无间断的直播、比人工费便宜等优势&#xff0c;让很多商家选择了AI主播。 AI主播到底好不好用&#xff1f;终于在今年“双11”现出了原形。 1 AI主播没火过半年…

Python常用插件之emoji表情插件的用法

目录 一、概述 二、安装 三、基本用法 四、高级用法 1、自定义emoji表情 2、使用表情符号列表 3、结合使用Emoji和输入文本 4、动态添加emoji表情 5、自定义Emoji的样式 总结 一、概述 在Python中&#xff0c;使用emoji表情已经成为了一种非常流行的趋势。许多开发者…

Linux Centos 根目录扩展分区(保级教程)

Centos 根目录扩展分区 1. 扩展背景2.列出磁盘信息3. 对磁盘进行分区4. 重启Linux5. 将PV加入卷组centos并分区6.查看分区结果 1. 扩展背景 虚拟机初始分配20G内存&#xff0c;扩容到80G。 2.列出磁盘信息 可以得知容量信息以及即将创建的PV路径&#xff08;通常为“/dev/s…

tcpdump抓包的字节数量与ethtool统计数据不同的原因

情况介绍 在进行RDMA抓包流量分析时&#xff0c;我使用ethtool工具统计了RDMA网卡的流量发送数据数量&#xff0c;然后使用tcpdump进行抓包。 经过分析发现&#xff0c;tcpdump得到的数据数量总是大于ethtool得到的数据数量&#xff0c;而且每个数据包会多出4个字节。 分析 …

代码随想录算法训练营|五十一天

最长递增子序列 300. 最长递增子序列 - 力扣&#xff08;LeetCode&#xff09; 递推公式&#xff1a; 有点像双指针的操作&#xff0c;例如{2,5,6,4,3}&#xff08;写不出来&#xff0c;画图&#xff09; public class Solution {public int LengthOfLIS(int[] nums) {if (n…

如何计算掩膜图中多个封闭图形的面积

import cv2def calMaskArea(image,idx):mask cv2.inRange(image, idx, idx)contours, hierarchy cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)for contour in contours:area cv2.contourArea(contour)print("图形的面积为", area) image是…

Git的GUI图形化工具ssh协议IDEA集成Git

一、GIT的GUI图形化工具 1、介绍 Git自带的GUI工具&#xff0c;主界面中各个按钮的意思基本与界面文字一致&#xff0c;与git的命令差别不大。在了解自己所做的操作情况下&#xff0c;各个功能点开看下就知道是怎么操作的。即使不了解&#xff0c;只要不做push操作&#xff0c…

【Python基础篇】字面量

博主&#xff1a;&#x1f44d;不许代码码上红 欢迎&#xff1a;&#x1f40b;点赞、收藏、关注、评论。 格言&#xff1a; 大鹏一日同风起&#xff0c;扶摇直上九万里。 文章目录 一 Python中字面量的定义二 常见的字面量类型1 数字(Number)2 字符串(String)3 列表(List)4 元…

大模型深入发展,数字化基础设施走向“算粒+电粒”,双粒协同

AI大模型爆发&#xff0c;千行百业期待用生成式人工智能挖掘创新应用与提升生产力。不过&#xff0c;高效的大模型应用底层实际需要更灵活、多元的算力去支撑。在这个重要的技术窗口下&#xff0c;11月10日&#xff0c;由中国智能计算产业联盟与ACM中国高性能计算专家委员会共同…

十月份 NFT 市场显示复苏迹象,等待进一步的积极发展

作者: stellafootprint.network 10 月份&#xff0c;比特币价格大幅飙升&#xff0c;NFT 市场出现了复苏迹象&#xff0c;月度交易量和用户数均增长了 15.2%。尽管 10 月份的数据相比 9 月份有所改善&#xff0c;但仍然不及 8 月份和之前几个月的水平。因此&#xff0c;现在断…

一、认识微服务

目录 一、单体架构 二、分布式架构 三、微服务 1、微服务架构特征&#xff1a; 1.单一职责&#xff1a; 2.面向服务&#xff1a; 3.自治&#xff1a; 4.隔离性强&#xff1a; 2、微服务结构&#xff1a; 3、微服务技术对比&#xff1a; 一、单体架构 二、分布式架构 三…

洗地机哪个牌子最好用?洗地机品牌排行榜

近年来&#xff0c;洗地机相当热门&#xff0c;洗地机结合了扫地拖地吸地为一体的多功能清洁工具&#xff0c;让我们告别了传统方式打扫卫生&#xff0c;让我们清洁不再费劲&#xff0c;可是市面上的洗地机五花八门&#xff0c;怎么挑选到一个洗地机也是一个问题&#xff0c;下…

如果让你重新开始学 C/C++,你的学习路线会是怎么选择?

1. 第一阶段 学好 C 语言和 Linux 1.1 学好 C 语言 无论你是科班还是非科班&#xff0c;建议你一定要学好 C 语言&#xff0c;它应该作为你必须掌握好的语言。你要熟悉 C 语言的基本语法&#xff0c;包括&#xff1a; 顺序、条件、循环三大控制语句 C 中几大基元数据类型的用…

【文末送书】深入浅出嵌入式虚拟机原理

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

YashanDB服务端个人版安装部署

介绍 崖山数据库系统YashanDB是深圳计算科学研究院完全自主研发设计的新型数据库系统&#xff0c;融入原创理论&#xff0c;支持单机/主备、共享集群、分布式等多种部署方式&#xff0c;覆盖OLTP/HTAP/OLAP交易和分析混合负载场景&#xff0c;为客户提供一站式的企业级融合数据…

2023测试工程师必看系列:用JMeter+ANT进行接口自动化测试,并生成HTML测试报告

【文章末尾给大家留下了大量的福利】 小伙伴们&#xff0c;用python做接口自动化是不是写代码比较繁琐&#xff0c;而且没有python代码基础的小伙伴根本无从下手对吧&#xff01;今天我们来学习一下如何使用JMeter工具实现接口自动化测试。 01 安装 1、安装JDK&#xff0c;…

个推数据驱动运营增长上海场沙龙:个推产品专家分享数智运营实践

近日&#xff0c;以“数据增能&#xff0c;高效提升用户运营价值”为主题的个推「数据驱动运营增长」城市巡回沙龙上海专场圆满举行。活动现场&#xff0c;个推资深产品专家袁紫微以“数据驱动APP运营增长”为主题&#xff0c;深度分享了个推的数智运营方法论以及在出行、电商、…