PyTorch自定义张量操作开发指南【CFFI+CUDA】

news2024/11/15 10:16:23

PyTorch 与 TensorFlow 一起成为深度学习研究人员和从业者的标准。虽然 PyTorch 在张量运算或深度学习层方面提供了多种选择,但一些专门的操作仍然需要手动实现。在运行时至关重要的情况下,应使用 C 或 CUDA 来完成此操作,以支持 CPU 和 GPU 计算。

在本文中,我想提供一个简单的示例和框架,用于使用 CFFI for Python 和 CuPy 通过自定义 C 和 CUDA 操作扩展 PyTorch。

NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 

1、简介

PyTorch 已成为深度学习研究和开发的标准工具。即使是不常见的张量运算或神经网络层也可以使用 PyTorch 提供的各种操作轻松实现。但是,对于某些特殊操作,求助于高效的 C 和 CUDA 实现可能是有意义的。在本文中,我想展示如何使用 CFFI 轻松扩展 PyTorch 进行 CPU 操作和使用 CuPy 进行 GPU 操作。作为示例,我将展示如何实现张量运算,计算两个张量之间的元素级、位级汉明距离。

本文的代码可在 GitHub 上找到,并且可以轻松扩展和调整。

2、环境设置

我将使用 PyThon 的 C 外部函数接口 (CFFI) 在 CPU 上实现数据类型为 torch.int32 的张量之间的汉明距离。为了支持 GPU 计算,汉明距离也可以实现为 CUDA 内核。可以使用 CuPy 导入 CUDA 内核。CFFI 和 CuPy 都可以轻松安装,例如,使用 pip install。但是,对于 CuPy,安装需要适合使用的 CUDA 版本(对于 PyTorch 也是如此)。可以在此处找到详细的安装说明。

3、C 中的位汉明距离

下面提供了 C 中两个 32 位整数之间的位级汉明距离的简单实现:

清单 1:两个整数之间的汉明距离。

int a = 15;
int b = 19;
int dist = 0;
int x = a ^ b;
 
while(x != 0) {
    x = x & (x-1);
    dist++;
}

为了计算两个张量之间的逐元素汉明距离,可以将清单 1 包装在一个简单的循环中。生成的函数(如清单 2 所示)需要三个 int 数组作为输入:第一个输入张量、第二个输入张量和将用汉明距离填充的输出张量。所有数组都假定为相同的长度 n:

清单 2:两个整数数组之间的逐元素汉明距离。可选地,可以使用 OpenMP 来加快计算速度。

    #pragma omp parallel for
    for (int elem_idx = 0; elem_idx < n; elem_idx++) {
 
        dist[elem_idx] = 0;
        int x = a[elem_idx] ^ b[elem_idx];
 
        while(x != 0) {
            x = x & (x-1);
            dist[elem_idx]++;
        }
    }
}

使用 CFFI,此函数可直接用于操作 NumPy 数组或 PyTorch 张量。为此,可以将实现保存在 cffi.c 中,并将相应的头文件保存在 cffi.h 中。

4、位汉明距离 CUDA 内核

清单 1 中概述的相同算法可轻松放入 CUDA 内核中:

清单 3:用于计算两个整数数组之间汉明距离的 CUDA 内核。这本质上是清单 1 中由内核块/dim/id 确定的数组元素。

extern "C" __global__ void cupy_int32hammingdistance(
    const int n,
    const int* a,
    const int* b,
    int* dist
) {
    int elem_idx = blockIdx.x * blockDim.x + threadIdx.x;
        
    if (elem_idx >= n) {
        return;
    }
 
    int x = a[elem_idx] ^ b[elem_idx];
 
    while(x != 0) {
        x = x & (x-1);
        dist[elem_idx]++;
    }
}

CuPy 只需要内核;内核不需要存储在单独的代码文件中。相反,它可以作为 Python 中的字符串提供给 CuPy。

5、将所有内容放在一起

为了组装所有部分,我将使用一个简单的模块化结构,将实际实现(使用 cffi/ 中的 CFFI 或 cupy.py 中的 CuPy)与 torch.py​​ 中的高级方法分开:

common/
- __init__.py
- cffi/
  |- cffi.h
  |- cffi.c
  |- __init__.py
- cupy.py
- torch.py

5.1 CPU 实现

首先,我将整理 CPU 实现,即 cffi.c 和 cffi.h。为简单起见,可以将它们放入自己的目录中,Python 接口将在相应的 __init__.py 中定义。

清单 4:使用 CFFI,可以即时编译 C 代码并直接在 Python 中访问。详情请参阅注释。

    # https://stackoverflow.com/questions/22931147/stdisinf-does-not-work-with-ffast-math-how-to-check-for-infinity
    else:
        if use_openmp:
            ffi.set_source(
                '_cffi',
                my_source.read(),
                extra_compile_args=['-fopenmp', '-D use_openmp', '-O3','-march=native'],
                extra_link_args=['-fopenmp'],
            )
        else:
            ffi.set_source('_cffi',
                my_source.read(),
                extra_compile_args=['-O3','-march=native'],
)
 
# 4.
# Compile using the parameters above.
ffi.compile()
#ffi.compile(verbose=True)
# 5.
# This simply imports all compiled functions and makes them available in this module.
from _cffi import *

代码负责编译清单 2 中的函数并将其与 Python 接口。之后,可以通过 cffi.lib.cffi_int32hammingdistance 访问该函数,其中清单 4 对应于 cffi/__init__.py。我将在下面详细介绍基本步骤:

  • 获取 __init__.py 文件目录的绝对路径。这对于定位要编译的头文件和实现文件是必要的。根据设置,也可以通过不同的方式解决这个问题,例如,通过硬编码绝对路径。
  • 读取头文件,以便 CFFI 知道函数定义。
  • 读取源文件并设置编译选项。在这里,代码允许几种不同的设置,包括不带优化的调试设置和支持 OpenMP 的设置。
  • 了解函数定义(通过头文件)、确定编译选项并阅读源代码后,CFFI 可以编译所有内容。
  • 最后,导入所有编译函数,以便以后可以更轻松地访问它们。

5.2 GPU 实现

对于 CuPy 部分,我将在 cupy.py 中创建一个单独的模块:

清单 5:与 CFFI 类似,CuPy 也允许即时编译 CUDA 内核。详情请参阅注释。

import torch
 
 
try:
    import cupy
    # 1. This will be used to call a kernel with source code provided as Python string.
    @cupy.util.memoize(for_each_device=True)
    def cunnex(strFunction):
        return cupy.cuda.compile_with_cache(globals()[strFunction]).get_function(strFunction)
except ImportError:
    print("CUPY cannot initialize, not using CUDA kernels")
 
 
class Stream:
    ptr = torch.cuda.current_stream().cuda_stream
 
# 2. The kernel as Python string from Listing 3
cupy_int32hammingdistance = '''
    extern "C" __global__ void cupy_int32hammingdistance(
        const int n,
        const int* a,
        const int* b,
        int* dist
    ) {
        int elem_idx = blockIdx.x * blockDim.x + threadIdx.x;
        
        if (elem_idx >= n) {
            return;
        }
 
        int x = a[elem_idx] ^ b[elem_idx];
 
        while(x != 0) {
            x = x & (x-1);
            dist[elem_idx]++;
        }
    }
'''

CuPy 接口甚至更简单:

  • 此实用函数将负责编译和接口函数。作为参数,该函数需要一个变量的名称,该变量包含实际的 CUDA 内核源代码。
  • 源代码保存在此变量中,而不是单独的源文件中。

最后,在 torch.py​​ 中,将合并两个实现。结果是一个高级函数 int32_hamming_distance,需要两个 torch.int32 张量作为输入。该函数将自动为输出分配内存,并根据输入是否在 GPU 内存上调用适当的接口。为了确定张量是否在 GPU 内存上,提供了一个简单的 is_cuda 函数(此处未显示)。

清单 6:将 CFFI 和 CuPy 实现放在一个高级方法 int32_hamming_distance 中,该方法根据输入张量自动使用 CPU 或 GPU 实现。

def int32_hamming_distance(a, b):
    """
    Bit-wise hamming distance.
 
    :param a: first tensor
    :type a: torch.Tensor
    :param b: first tensor
    :type b: torch.Tensor
    :return: hamming distance
    :rtype: torch.Tensor
    """
 
    if not a.is_contiguous():
        a.contiguous()
    assert (a.dtype == torch.int32)
    cuda = is_cuda(a)
 
    if not b.is_contiguous():
        b.contiguous()
    assert (b.dtype == torch.int32)
    assert is_cuda(b) is cuda
 
    assert len(a.shape) == len(a.shape)
    for d in range(len(a.shape)):
        assert a.shape[d] == b.shape[d]
 
    # 1. Initialize output tensor to hold the element-wise hamming distances.
    dist = a.new_zeros(a.shape).int()
    n = dist.nelement()
 
    if cuda:
        # 2. Call the cupy implementation using the helper function cupy.cunnex.
        # The function returned by cupy.cunnex expects, among others, the grid/block division to use
        # and the kernel arguments; here a.data_ptr() will return the point to the tensor a
        # and is assumed to be on GPU memory.
        cupy.cunnex('cupy_int32hammingdistance')(
            grid=tuple([int((n + 512 - 1) / 512), 1, 1]),
            block=tuple([512, 1, 1]),
            args=[n,
                  a.data_ptr(),
                  b.data_ptr(),
                  dist.data_ptr()],
            stream=cupy.Stream
        )
    else:
        # 3. For CFFI, the inputs have to be cast to the target C equivalents using cffi.ffi.cast.
        # Afterwards, the C function can be called like a regular Python function using the converted arguments.
        _n = cffi.ffi.cast('int', n)
        _a = cffi.ffi.cast('int*', a.data_ptr())
        _b = cffi.ffi.cast('int*', b.data_ptr())
        _dist = cffi.ffi.cast('int*', dist.data_ptr())
 
        cffi.lib.cffi_int32hammingdistance(_n, _a, _b, _dist)
 
    return dist

本质上,该函数创建输出张量来保存元素汉明距离,然后根据输入是否存储在 GPU 内存中调用 CuPy 或 CFFI 接口:

  • 创建输出张量,如果需要,也可以在 GPU 上创建。它也将是相同大小的 torch.int32 张量。
  • CuPy 实现通过辅助函数 cupy.cunnex 调用,该函数获取相应的源代码,对其进行编译(如果未缓存)并返回相应的函数。返回的函数需要输入——这里, a.data_ptr() 用于访问给定张量底层的指针——以及内核的块/dim/id 细分。

调用 CFFI 实现需要将输入显式转换为等效的 C 类型。然后,可以像常规 Python 函数一样调用该函数。

6、结束语

总体而言,本文表明,使用 CFFI 和 CuPy 可以非常简单地在 C 和 CUDA 中实现支持 CPU 和 GPU 计算的自定义 PyTorch 操作。此外,我提供了一个简单的框架,可以轻松扩展到自定义操作。


原文链接:PyTorch自定义张量操作 - BimAnt

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

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

相关文章

快团团供货大团长如何打印电子面单?

一、功能说明 快团团打单平台是目前唯一一个服务于快团团团长的打单发货工具&#xff0c;免费提供给团长使用。可帮助团长快速打印面单、分拣包裹、完成发货。 目前快团团打单平台已支持大批量打印快递单、自定义快递面单、自动发货、绑定拼多多电子面单账号等功能&#xff0c…

摸鱼大数据——Hive表操作——复杂类型

1、hvie的SerDe机制 其中ROW FORMAT是语法关键字&#xff0c;DELIMITED和SERDE二选其一。本次我们主要学习DELIMITED关键字相关知识点 如果使用delimited: 表示底层默认使用的Serde类:LazySimpleSerDe类来处理数据。 如果使用serde:表示指定其他的Serde类来处理数据,支持用户自…

香橙派AIpro开发板初体验

香橙派AIpro开发板初体验 一、引言 在当前的AI发展浪潮中&#xff0c;边缘计算逐渐成为了研究的热点。香橙派AIpro开发板作为一款基于昇腾AI技术的开发板&#xff0c;凭借其强大的算力和丰富的接口&#xff0c;为AI边缘计算提供了强大的支持。最近&#xff0c;我也是拿到了官…

揭秘SQL中的公用表表达式:数据查询的新宠儿

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 揭秘SQL中的公用表表达式&#xff1a;数据查询的新宠儿 前言公用表表述的概述非递归CTE的作用递归CTE的作用CTE性能优化 前言 你是否曾经为SQL查询的复杂性而困扰不已&#xff1f;尤其是那些读写层子…

数据结构(一)顺序表

目录 一、概念&#xff08;一&#xff09;数据结构的三元素1. 逻辑结构&#xff08;1&#xff09;线性结构&#xff08;2&#xff09;非线性结构 2. 存储结构&#xff08;1&#xff09;顺序存储&#xff08;2&#xff09;链式存储&#xff08;3&#xff09;索引存储 3. 运算 &a…

douyin-vue:使用Vue3、Pinia和Vite5打造高度还原的抖音仿制项目

一&#xff1a;引言 在前端技术日新月异的今天&#xff0c;Vue.js作为一款流行的前端框架&#xff0c;不断吸引着开发者的目光。最近&#xff0c;GitHub上出现了一个备受瞩目的项目——douyin-vue&#xff0c;这是一个基于Vue3、Pinia和Vite5的移动端短视频项目&#xff0c;旨…

CCF20220301——未初始化警告

CCF20220301——未初始化警告 代码如下&#xff1a; #include<bits/stdc.h> using namespace std; #define Max 100000 int x[Max]{0},y[Max]{0}; int main() {int n,k;int cnt1,flag0;cin>>n>>k;for(int i1;i<k;i)cin>>x[i]>>y[i];for(in…

Linux命令 jps(Java Process Status)解释

文章目录 1、第一种解释2、第二种解释3、第三种解释 1、第一种解释 jps 命令本身并不是一个标准的 Unix/Linux 命令&#xff0c;但您可能是想提到 jps 的一个变种或误写了 jps 为 jps&#xff0c;而实际上可能是想提及 jps&#xff08;Java Virtual Machine Process Status To…

虚拟化概述

虚拟存储器(Virtual Memory) 它的基本思想是对于一个程序来说,它的程序(code)、数据(data)和堆栈(stack)的总大小可以超过实际物理内存的大小&#xff1b;操作系统把当前使用的部分内容放到物理内存中&#xff0c;而把其他未使用的内容放到更下一级存储器&#xff0c;如硬盘&a…

通过 coze 快速构建自己的智能体机器人

通过 coze 快速构建自己的智能体机器人 coze 的使用 一&#xff09;coze 是什么 「Coze 扣子」AI Bot 开发平台。任何用户都可以快速、低门槛地搭建自己的 Chatbot&#xff0c;且平台支持用户将其一键发布到飞书、微信公众号、豆包等渠道。 二&#xff09;coze 怎么注册 1. …

安卓ADB通过WIFI无线连接手机[通过无线安装APK]

安卓ADB通过无线连接手机 本文摘录于&#xff1a;https://www.cnblogs.com/zhuxibo/p/14261117.html只是做学习备份之用&#xff0c;绝无抄袭之意&#xff0c;有疑惑请联系本人&#xff01; 别人给的操作确实可行,我这里实操记录如下: AdministratorpiaoranPC MINGW64 /e/Wor…

AI RAG的知识满天飞,为什么你还做不出一个像样的AI助手应用

引言 在过去的一年半里&#xff0c;随着大型语言模型&#xff08;LLM&#xff09;的崛起&#xff0c;检索增强生成&#xff08;RAG&#xff09;的概念也逐渐进入公众视野。 从微信公众号到各大自媒体&#xff0c;关于RAG的讨论几乎无处不在。然而&#xff0c;尽管这些高科技名…

小苯的排列构造,小苯的01背包(easy),小苯的01背包(hard)

小苯的排列构造 题目描述 运行代码 #include<bits/stdc.h> using namespace std; typedef long long ll; #define N 1000050 int i,j,k,n,m,t,a[N],b[N],f[N],l[N]; bool v[N]; int main(){cin>>n;for(i1;i<n;i)cin>>a[i];v[0]1;for(i1;i<n;i){if(a[…

apexcharts数据可视化之极坐标区域图

apexcharts数据可视化之极坐标区域图 有完整配套的Python后端代码。 本教程主要会介绍如下图形绘制方式&#xff1a; 基础极坐标区域图单色极坐标区域图 基础极坐标区域图 import ApexChart from react-apexcharts;export function BasicPolar() {// 数据序列const series…

Ubuntu/Linux 安装Paraview

文章目录 0. 卸载已有ParaView1. 安装ParaView1.1 下载后安装 2.进入opt文件夹改名3. 更改启动项4. 创建硬链接5. 添加桌面启动方式6. 即可使用 0. 卸载已有ParaView YUT 1. 安装ParaView https://www.paraview.org/ 1.1 下载后安装 找到下载的文件夹&#xff0c;文件夹内…

[JAVASE] String类 StringBuffer类 StringBuilder类

目录 一.String类 1.1 String字符串不可变的原因 1.2 字符串中的比较方法 1.2.1 equals 1.2.2 compareTo 与 compareToIgnoreCase 1.3 字符串中的查找方法 1.3.1 charAt 1.3.2 indexOf 与 lastIndexOf 1.4 字符串中的转换方法 1.4.1 valueOf 1.4.2 toUpperCase 与 to…

【PMP必考点】2024年PMP敏捷知识必考点-工件篇

PMP自2023年8月起启动了PMBOK第七版教材&#xff0c;考试内容的侧重点也发生了改变&#xff1a;大幅增加了敏捷相关的内容。以往考纲只有不到10%的敏捷管理的内容&#xff0c;新考纲的敏捷管理题目增加至50%。所以如果复习只用之前的题目是远远不够的&#xff0c;必须要多刷新考…

el-tabs中的下拉框被覆盖解决方法

解决方法&#xff1a; ::v-deep .el-tabs__content{// overflow:hidden 会导致 分页下拉框超出部分会被.el-tabs__content隐藏overflow: visible; }

JEPaaS 低代码平台 accessToTeanantInfo SQL注入漏洞复现

0x01 产品简介 JEPaaS低代码开发平台开源版 旨在帮助企业快速实现信息化和数字化转型。该平台基于可视化开发环境,让软件开发人员和业务用户通过直观的可视化界面来构建应用程序 ,而不是传统的编写代码方式。 用户可以在开发平台灵活各个图形化控件,以构建业务流程、逻辑和…

Linux基础学习笔记

目录 1、Linux安装 1.1 安装教程 1.2 Linux目录结构 2、Linux常用命令 2.1 ls 2.2 命令分类 2.3 目录处理命令 2.4 操作文件命令 2.5 查找文件命令 2.6 ln链接命令 2.7 进程相关命令 ​编辑3、配置网络 3.1 关闭windows防火墙 3.2 配置好虚拟机的局域网 3.3 配置…