CMU 10-414/714: Deep Learning Systems --hw3

实现功能

  1. ndarray.py文件中完成一些python array操作
    • 我们实现的NDArray底层存储就是一个一维向量,只不过会有一些额外的属性(如shape、strides)来表明这个flat array在维度上的分布。底层运算(如加法、矩阵乘法)都是用C++写的,以便达到理论上的最高速度。但像转置、broadcasting、矩阵求子阵等操作都可以在python框架里通过简单的调整array的属性(如strides)来实现
    • 实现的python array功能:这四个功能都不需要重新分配内存,而是在原array的基础上修改shape/strides/etc来实现一些变形(除了第四个__getitem__(),它并不修改array的形状)
      • reshape(new_shape):调用as_strided(new_shape, strides),再调用make(new_shape, strides, device=self.device, handle=self._handle)。(其中as_strided会额外检查一下shape的长度是否等于strides的长度,strides的数值是通过self.compact_strides(new_shape)算出来的)
      • permute(new_axes):调换array的维度顺序。按照new_axes的顺序调换self.strides以及self.shape得到new_shape和new_strides,最后再调用self.as_strided"(new_shape, new_strides)
      • broadcast_to():对于原shape中为1的维度,将其步幅设为0,然后调用as_strided(new_shape, new_strides),然后as_strided调用make(new_shape, new_strides, device=self.device, handle=self._handle)。即重新make,修改其中的shape和stride,但内存位置不变(即handle不变)。至于这里将stride部分设为0,猜测更底层有代码处理这里,但我目前还没找到
      • __getitem__(idxs):这是一个非常好的理解我们所实现的NDArray的例子,前面也说了,底层的array存储就是一个一维向量,是通过shape、stride、offset这些属性来指示该向量的维度的。本例就是在原array的基础上求子矩阵,且不改变其内存位置,就是通过一系列计算得到新的new_offset、new_shape、new_stride,就可以将原本的大矩阵重新解释成一个小矩阵。图示如下
        在这里插入图片描述
        而这里也天然解释了什么是紧凑型(compact)矩阵和非紧凑型矩阵:上述原矩阵就是紧凑型的,切片后形成的蓝底矩阵就是非紧凑型的。
        而这也带出了下一个问题:如何选择原矩阵的一个子矩阵进行修改内容。因为本课程中几乎所有setitem操作都会先调用.compact(),而这会导致从原矩阵中copy子矩阵到一个新的内存空间,这显然不是__setitem__()所期望的,因此要大动干戈。
  2. 【CPU】compact和setitem
    其实这里的实现主要就是根据这个原理:out[cnt++] = in[strides[0]*i + strides[1]*j + strides[2]*k];(这里是按三维矩阵算的,下面的代码通过while循环可以扩展到其他维度)
    • Compact:就是将原来的非紧凑型矩阵a变成紧凑型矩阵out,具体是根据输入的shape、strides、offset(以a为主体确定的),将a中的子矩阵提出来,将各位置的值赋给另一个矩阵out。下面的mode是INDEX_IN,即调用_strided_index_setter(&a, out, shape, strides, offset, INDEX_IN);
      // 从后往前每一个维度,都根据该维度的步长、offset以及循环次数来确定本次要copy矩阵a的哪一个元素
      // 其实就是根据strides、offset来确定一维下的index
      void _strided_index_setter(const AlignedArray* a, AlignedArray* out, std::vector<uint32_t> shape,
                          std::vector<uint32_t> strides, size_t offset, strided_index_mode mode, int val=-1) {
        int depth = shape.size();
        std::vector<uint32_t> loop(depth, 0);
        int cnt = 0;
        while (true) {
          // inner loop
          int index = offset;
          for (int i = 0; i < depth; i++) {
            index += strides[i] * loop[i];
          }
          switch (mode) {
            case INDEX_OUT: out->ptr[index] = a->ptr[cnt++]; break;
            case INDEX_IN: out->ptr[cnt++] = a->ptr[index]; break;
            case SET_VAL: out->ptr[index] = val; break;
          }
      
          // increment
          loop[depth - 1]++;
      
          // carry
          int idx = depth - 1;
          while (loop[idx] == shape[idx]) {
            if (idx == 0) {
              // overflow
              return;
            }
            loop[idx--] = 0;
            loop[idx]++;
          }
        }
      }
      
    • EwiseSetitem(a, out, shape, strides, offset):这里a是紧凑型矩阵,out是一个非紧凑行矩阵,需要将a的各元素的值赋给out的指定位置上(根据shape、strides、offset确定)。只需要复用上面的代码,将mode改为INDEX_OUT,即调用_strided_index_setter(&a, out, shape, strides, offset, INDEX_OUT);
    • ScalarSetitem(size, val, out, shape, strides, offset):out是一个非紧凑型子矩阵,根据shape、strides、offset在out的对应位置将值写为val,即调用_strided_index_setter(nullptr, out, shape, strides, offset, SET_VAL, val);
  3. 【CPU】Elementwise和scalar操作
    这个就非常简单了,根据矩阵的size属性遍历其每个元素即可
  4. 【CPU】Reductions
    这个也很简单,条件是矩阵的底层存储都是一维、行优先的。这类里有两个函数:ReduceSum和ReduceMax,其参数都是原矩阵a、输出矩阵out、reduce_size,其中a.size = out.size * reduce_size。看代码就懂了
    • ReduceMax:
      void ReduceMax(const AlignedArray& a, AlignedArray* out, size_t reduce_size) {
        for (int i = 0; i < out->size; i++) {
          scalar_t max = a.ptr[i * reduce_size];
          for (int j = 0; j < reduce_size; j++) {
            max = std::max(max, a.ptr[i * reduce_size + j]);
          }
          out->ptr[i] = max;
        }
      }
      
    • ReduceSum:
      void ReduceSum(const AlignedArray& a, AlignedArray* out, size_t reduce_size) {
        for (int i = 0; i < out->size; i++) {
          scalar_t sum = 0;
          for (int j = 0; j < reduce_size; j++) {
            sum += a.ptr[i * reduce_size + j];
          }
          out->ptr[i] = sum;
        }
      }
      
  5. 【CPU】矩阵乘
    • 朴素矩阵乘Matmul
      void Matmul(const AlignedArray& a, const AlignedArray& b, AlignedArray* out, uint32_t m, uint32_t n,
                  uint32_t p) {
        for (int i = 0; i < m; i++) {
          for (int j = 0; j < p; j++) {
            out->ptr[i * p + j] = 0;
            for (int k = 0; k < n; k++) {
              out->ptr[i * p + j] += a.ptr[i * n + k] * b.ptr[k * p + j];
            }
          }
        }
      }
      
    • MatmulTiledAlignedDot:前者负责根据分片大小计算目前参与运算的是a、b、out的哪些元素(哪部分block);后者根据前者传进来的block进行计算(a分片与b分片进行矩阵乘法得到out分片
      void MatmulTiled(const AlignedArray& a, const AlignedArray& b, AlignedArray* out, uint32_t m,
                       uint32_t n, uint32_t p) {
        for (int i = 0; i < m * p; i++) out->ptr[i] = 0;
        for (int i = 0; i < m / TILE; i++) {
          for (int j = 0; j < p / TILE; j++) {
            for (int k = 0; k < n / TILE; k++) {
              AlignedDot(&a.ptr[i * n * TILE + k * TILE * TILE], 
                         &b.ptr[k * p * TILE + j * TILE * TILE], 
                         &out->ptr[i * p * TILE + j * TILE * TILE]);
            }
          }
        }
      }
      
      inline void AlignedDot(const float* __restrict__ a,
                             const float* __restrict__ b,
                             float* __restrict__ out) {
        a = (const float*)__builtin_assume_aligned(a, TILE * ELEM_SIZE);
        b = (const float*)__builtin_assume_aligned(b, TILE * ELEM_SIZE);
        out = (float*)__builtin_assume_aligned(out, TILE * ELEM_SIZE);
        
        for (int i = 0; i < TILE; i++) {
          for (int j = 0; j < TILE; j++) {
            for (int k = 0; k < TILE; k++) {
              out[i * TILE + j] += a[i * TILE + k] * b[k * TILE + j];
            }
          }
        }
      }
      
      过程原理图:
      在这里插入图片描述
      这里有一个疑问,a、b虽然每次通过MatmulTiled计算的分片的起始位置是正确的,但是如何保证连续的Tile*Tile分块元素就是如图所示那样的呢。按理说a、b的底层存储应该是连续的行优先,那(按照tile=2算)a[2]、a[3]、b[2]、b[3]应该是按行走下去而不是取下一行啊。待解答…
  6. 【CUDA】compact和setitem
    • compact:
      __device__ size_t index_transform(size_t index, CudaVec shape, CudaVec strides, size_t offset) {
        size_t idxs[MAX_VEC_SIZE];
        size_t cur_size, pre_size = 1;
        // 将给定的线性索引映射回多维数组的索引,即计算index值在各维度上对应是第几个
        // 思路就是从后往前遍历shape,cur_size表示当前处理的维度由几个元素构成
        // index%cur_size/pre_size就表示在当前维度的第几个分量
        // index%cur_size表示是当前维度(只看当前维)的第几个元素,再/pre_size就表示是当前维度的第几块
        for (int i = shape.size - 1; i >= 0; i--) {
          cur_size = pre_size * shape.data[i]; 
          idxs[i] = index % cur_size / pre_size;
          pre_size = cur_size;
        }
        // 根据上述算好的多维数组索引,计算在原非紧凑型矩阵中的线性索引
        size_t comp_idx = offset;
        for (int i = 0; i < shape.size; i++) 
          comp_idx += idxs[i] * strides.data[i];
        return comp_idx;
      }
      
      __global__ void CompactKernel(const scalar_t* a, scalar_t* out, size_t size, CudaVec shape,
                                    CudaVec strides, size_t offset) {
        size_t gid = blockIdx.x * blockDim.x + threadIdx.x;
      
        /// BEGIN YOUR SOLUTION
        if (gid < size)
          out[gid] = a[index_transform(gid, shape, strides, offset)];
        /// END YOUR SOLUTION
      }
      
      void Compact(const CudaArray& a, CudaArray* out, std::vector<uint32_t> shape,
                   std::vector<uint32_t> strides, size_t offset) {
        CudaDims dim = CudaOneDim(out->size);
        CompactKernel<<<dim.grid, dim.block>>>(a.ptr, out->ptr, out->size, VecToCuda(shape),
                                               VecToCuda(strides), offset);
      }
      
      图示如下:
      在这里插入图片描述
    • EWiseSetitem:
      __global__ void EwiseSetitemKernel(const scalar_t* a, scalar_t* out, size_t size, CudaVec shape,
                                         CudaVec strides, size_t offset) {
        size_t gid = blockIdx.x * blockDim.x + threadIdx.x;
        if (gid < size)
          out[index_transform(gid, shape, strides, offset)] = a[gid];
      }
      
      void EwiseSetitem(const CudaArray& a, CudaArray* out, std::vector<uint32_t> shape,
                        std::vector<uint32_t> strides, size_t offset) {
        /// BEGIN YOUR SOLUTION
        CudaDims dim = CudaOneDim(out->size);
        EwiseSetitemKernel<<<dim.grid, dim.block>>>(a.ptr, out->ptr, a.size, VecToCuda(shape),
                                                    VecToCuda(strides), offset);
        /// END YOUR SOLUTION
      }
      
    • ScalarSetitem:
      __global__ void ScalarSetitemKernel(size_t size, scalar_t val, scalar_t* out, CudaVec shape, 
                                          CudaVec strides, size_t offset) {
        size_t gid = blockIdx.x * blockDim.x + threadIdx.x;
        if (gid < size)
          out[index_transform(gid, shape, strides, offset)] = val;
      }
      
      void ScalarSetitem(size_t size, scalar_t val, CudaArray* out, std::vector<uint32_t> shape,
                         std::vector<uint32_t> strides, size_t offset) {
        /// BEGIN YOUR SOLUTION
        CudaDims dim = CudaOneDim(out->size);
        ScalarSetitemKernel<<<dim.grid, dim.block>>>(size, val, out->ptr, VecToCuda(shape),
                                                     VecToCuda(strides), offset);
        /// END YOUR SOLUTION
      }
      
      • 其实总结一下,CPU版的将紧凑小矩阵的index转换成非紧凑大矩阵的index,是在一个loop中实现的,并在loop中找到index后就完成了copy工作;对于GPU版来说,是将copy工作分配给各个线程,因此若要让每个线程都能正确copy,还需要每个线程根据自己分配到的紧凑小矩阵的index,计算得到非紧凑大矩阵的index。即每个线程完成CPU版中一个loop的操作(但不需要后续的检查)。
  7. 【CUDA】Elementwise和scalar操作
    和CPU版本的一样,也很简单。且这part非常能体现CUDA的并行、高效特性。举个例子:
    __global__ void EwiseMulKernel(const scalar_t* a, const scalar_t* b, scalar_t* out, size_t size){
    	size_t gid = blockInx.x * blockDim.x + threadIdx.x;
    	if (gid<size){
    		out[gid] = a[gid] * b[gid];
    	}
    }
    void EwiseMul(const CudaArray &a, const CudaArray &b, CudaArray* out){
    	CudaDims dim = CudaOneDim(out->size);
    	EwiseMulKernel<<<dim.grid, dim.block>>>(a.ptr, b.ptr, out->ptr, out->size);
    }
    
  8. 【CUDA】Reductions
    • ReduceMax
      __global__ void ReduceMaxKernel(const scalar_t* a, scalar_t* out, size_t reduce_size, size_t size){
        size_t gid = blockIdx.x * blockDim.x + threadIdx.x;
        if (gid<size){
          size_t offset = gid * reduce_size;
          scalar_t reduce_max = a[offset];
          for (int i=1; i<reduce_size; i++){
            reduce_max = max(reduce_max, a[offset+i]);
          }
          out[gid] = reduce_max;
        }
      }
      
      void ReduceMax(const CudaArray& a, CudaArray* out, size_t reduce_size) {
        CudaDims dim = CudaOneDim(out->size);
        ReduceMaxKernel<<<dim.grid, dim.block>>>(a.ptr, out->ptr, reduce_size, out->size);
      }
      
    • ReduceSum
  9. 【CUDA】矩阵乘
    使用朴素矩阵乘即可,一个线程负责out矩阵的一个元素
    __global__ void MatmulKernel(const scalar_t* a, const scalar_t* b, scalar_t* out, uint32_t M,
                                 uint32_t N, uint32_t P) {
      size_t i = blockIdx.x * blockDim.x + threadIdx.x; 
      size_t j = blockIdx.y * blockDim.y + threadIdx.y;
      if (i < M && j < P) {
        out[i * P + j] = 0;
        for (int k = 0; k < N; k++) {
          out[i * P + j] += a[i * N + k] * b[k * P + j];
        }
      }
    }
    void Matmul(const CudaArray& a, const CudaArray& b, CudaArray* out, uint32_t M, uint32_t N,
                uint32_t P) {
      dim3 grid(BASE_THREAD_NUM, BASE_THREAD_NUM, 1);
      dim3 block((M + BASE_THREAD_NUM - 1) / BASE_THREAD_NUM, (P + BASE_THREAD_NUM - 1) / BASE_THREAD_NUM, 1);
      MatmulKernel<<<grid, block>>>(a.ptr, b.ptr, out->ptr, M, N, P);
    }
    
    线程分布如下:
    在这里插入图片描述

补充知识

一、CUDA相关代码

在这里插入图片描述
如上图的结构,首先定义Cuda的维度:

struct CudaDims {
  dim3 block, grid;
};

根据数据数量来判定需要多少个block、多少个thread(都是一个维度下的)

CudaDims CudaOneDim(size_t size) {
  /**
   * Utility function to get cuda dimensions for 1D call
   */
  CudaDims dim;
  size_t num_blocks = (size + BASE_THREAD_NUM - 1) / BASE_THREAD_NUM;
  dim.block = dim3(BASE_THREAD_NUM, 1, 1);  // 一个block里的线程
  dim.grid = dim3(num_blocks, 1, 1);  // 一个grid里的block
  return dim;
}

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

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

相关文章

幻兽帕鲁游戏搭建(docker)

系列文章目录 第一章&#xff1a; 幻兽帕陆游戏搭建 文章目录 系列文章目录前言一、镜像安装1.创建游戏目录2.拉取镜像3.下载配置文件4.启动游戏 二、自定义配置总结 前言 这段时间一直在写论文还有找工作&#xff0c;也没学啥新技术&#xff0c;所以博客也很长时间没写了&am…

【滑动窗口、矩阵】算法例题

目录 三、滑动窗口 30. 长度最小的子数组 ② 31. 无重复字符的最长子串 ② 32. 串联所有单词的子串 ③ 33. 最小覆盖子串 ③ 四、矩阵 34. 有效的数独 ② 35. 螺旋矩阵 ② 36. 旋转图像 ② 37. 矩阵置零 ② 38. 生命游戏 ② 三、滑动窗口 30. 长度最小的子数组 ② 给…

高效备考2025年AMC8竞赛:吃透2000-2024年600道真题(免费送题)

我们继续来随机看五道AMC8的真题和解析&#xff0c;根据实践经验&#xff0c;对于想了解或者加AMC8美国数学竞赛的考生来说&#xff0c;吃透AMC8历年真题是备考更加科学、有效的方法之一。 即使不参加AMC8竞赛&#xff0c;吃透了历年真题600道和背后的知识体系&#xff0c;那么…

Qt笔记 mainwindow

mainwindow是用来做应用界面的&#xff0c;有菜单栏&#xff0c;工具栏&#xff0c;浮动窗口,中心部件以及状态栏这几个部分组成。 举个例子&#xff1a; 1.菜单栏: #include <QMenuBar>QMenuBar *menubar new QMenuBar(this); setMenuBar(menubar);//设置到当前窗口 …

31-Java前端控制器模式(Front Controller Pattern)

Java前端控制器模式 实现范例 前端控制器模式&#xff08;Front Controller Pattern&#xff09;是用来提供一个集中的请求处理机制&#xff0c;所有的请求都将由一个单一的处理程序处理该处理程序可以做认证/授权/记录日志&#xff0c;或者跟踪请求&#xff0c;然后把请求传给…

AI系统性学习05—向量数据库

文章目录 1、Chroma向量数据库1.1 安装Chroma1.2 初始化Chroma客户端1.3 创建一个合集1.4 添加数据1.5 查询数据1.6 持久化数据1.7 集合操作1.7.1 创建集合1.7.2 获取集合1.7.3 删除集合1.7.4 其他操作1.8 向集合添加数据1.9 查询集合数据1.10 更新集合数据1.11 删除集合数据1.…

[CISCN2019 华北赛区 Day1 Web5]CyberPunk --不会编程的崽

继续sql,哈哈。我按照我的思路来讲。 四个功能&#xff0c;提交&#xff0c;查找&#xff0c;修改&#xff0c;删除。多半是sql注入&#xff0c;而且又有修改&#xff0c;查找功能。多半还是二次注入。昨天那个修改密码的&#xff0c;也是二次注入。这里需要先找到注入点 姓名 …

STM32CubeMX学习笔记24---FreeRTOS(消息队列)

一. 队列简介 队列是为了任务与任务、任务与中断之间的通信而准备的&#xff0c;可以在任务与任务、任务与中 断之间传递消息&#xff0c;队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之 间要交流的数据保存在队列中&#xff0c;叫做队列项目。队列…

SLAM 求解IPC算法

基础知识&#xff1a;方差&#xff0c;协方差&#xff0c;协方差矩阵 方差&#xff1a;描述了一组随机变量的离散程度 方差 每个样本值 与 全部样本的平均值 相差的平方和 再求平均数&#xff0c;记作&#xff1a; 例如&#xff1a;计算数字1-5的方差&#xff0c;如下 去中心化…

Vulnhub靶机渗透:DC-7打靶记录

前言 自信自强&#xff0c;来自于不怕苦、不怕难的积淀。宝剑锋从磨砺出&#xff0c;梅花香自苦寒来&#xff1b;任何美好理想&#xff0c;都离不开筚路蓝缕、手胼足胝的艰苦奋斗&#xff01; 靶场介绍 DC-7是一个初中级的靶场&#xff0c;需要具备以下前置知识&#xff1a;…

【开源】SpringBoot框架开发不良邮件过滤系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统用户模块2.2 收件箱模块2.3 发件箱模块2.4 垃圾箱模块2.5 回收站模块2.6 邮箱过滤设置模块 三、实体类设计3.1 系统用户3.2 邮件3.3 其他实体 四、系统展示五、核心代码5.1 查询收件箱档案5.2 查询回收站档案5.3 新…

[Qt项目实战]Qt实现美松标签打印机标签二维码打印(QR混排模式+页打印模式)

1、硬件信息、环境参数及配套资料 1.1 打印机信息及开发环境 打印机 美松标签打印机串口/USB通讯Qt5.9 64位程序 1.2 打印机配套开发资料 打印机主要配套测试工具、开发SDK及驱动等&#xff0c;均由厂家提供。 开发Demo及动态库&#xff1a;MsPrintSDK-DLL-V2.2.2.5 链接&…

3、java虚拟机-类的生命周期-初始化阶段(与程序员有关)

一 、静态代码块执行顺序和字节码文件中的执行顺序以及什么赋值。 类的生命周期-初始化阶段-被static所修饰的常量才会被赋予值 初始化阶段-代码中静态代码块和静态变量的顺序和字节码中的执行顺序是一致的。 二、4种情况下&#xff0c;类会被初始化。 1、怎样查看类是…

阿里云部署MySQL、Redis、RocketMQ、Nacos集群

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容MySQL集群配置云服务器选购CPU选择内存选择云盘选择ESSD AutoPL云盘块存储性能&#xff08;ESSD&#xff09; 镜像选择带宽选择密码配置注意事项 搭建宝塔面板方便管理云服务器云服务器的安全组安装docker和docker-compose…

Go语言学习13-常见软件架构的实现

Go语言学习13-常见软件架构的实现 架构模式 An architectural pattern is a general, reusable solution to a commonly occurring problem in software architectural within a given context. ——wikipedia Pipe-Filter 架构 Pipe-Filter 模式 非常适合于数据处理及数据分…

taro之Picker,PickerView基础用法

1.Picker 直接上代码 import Taro,{Component} from "tarojs/taro"; import {View,Picker} from tarojs/components import { AtIcon } from taro-ui import { putKey } from /src/utils/storage-utilsclass AgriculturePolicy extends Component{constructor (prop…

su: authentication failure 解决方法

产生问题的原因&#xff1a;使用su和sudo是有区别的&#xff0c;root的密码可能是原始的密码&#xff0c;你现在用户的密码大概率是更改过的 解决办法&#xff1a; 在Linux上切换root时&#xff0c;密码正确。。但提示&#xff1a;su: authentication failure ->sudo pa…

统计学基础概念和在AI中的应用

基本概念 统计学是一门研究数据收集、分析、解释和展示的科学&#xff0c;它提供了一套方法论&#xff0c;用于理解数据并从数据中得出结论。统计学在各个领域都有应用&#xff0c;包括经济学、医学、工程学、社会科学等。以下是统计学的一些基本概念&#xff1a; 描述性统计…

wireshark数据捕获实验简述

Wireshark是一款开源的网络协议分析工具&#xff0c;它可以用于捕获和分析网络数据包。是一款很受欢迎的“网络显微镜”。 实验拓扑图&#xff1a; 实验基础配置&#xff1a; 服务器&#xff1a; ip:172.16.1.88 mask:255.255.255.0 r1: sys sysname r1 undo info enable in…

B站python爬虫课程笔记(Q16-)

下面是学习的网址&#xff1a; ​​​​​​【Python爬虫】 16、捕捉异常try&except语句的一些问题 1&#xff09;一些常见的异常类型 IndexError索引错误ZeroDivisionError除零错误FileNotFindError找不到文件错误TypeError类型错误KeyError键错误ValueError值错误Ind…