Skip to content

Latest commit

 

History

History
110 lines (85 loc) · 8.44 KB

File metadata and controls

110 lines (85 loc) · 8.44 KB

十三、答案

第一章,为什么 GPU 编程?

  1. 前两个for循环在每个像素上迭代,其输出彼此不变;因此,我们可以在这两个for循环上并行化。第三个for循环计算特定像素的最终值,本质上是递归的。
  2. 阿姆达尔定律没有考虑在 GPU 和主机之间传输内存所需的时间。
  3. 512 x 512 相当于 262144 像素。这意味着第一 GPU 一次只能计算一半像素的输出,而第二 GPU 一次可以计算所有像素;这意味着第二个 GPU 的速度大约是第一个 GPU 的两倍。第三个 GPU 有足够多的内核一次计算所有像素,但正如我们在问题 1 中看到的,额外的内核在这里对我们没有用处。因此,对于这个问题,第二个和第三个 GPU 将同样快。
  4. 根据阿姆达尔定律,一般将某段代码指定为可并行的一个问题是,如果处理器数量N非常大,则这段代码的计算时间将接近于 0。从上一个问题可以看出,情况并非如此。
  5. 首先,持续使用时间可能会很麻烦,而且可能不会关注程序的瓶颈。其次,分析器可以从 Python 的角度告诉您所有代码的精确计算时间,因此您可以判断操作系统的某些库函数或后台活动是否出错,而不是代码。

第 2 章,设置 GPU 编程环境

  1. 不支持,CUDA 仅支持 Nvidia GPU,不支持 Intel HD 或 AMD Radeon
  2. 本书仅使用 Python 2.7 示例
  3. 设备管理器
  4. lspci
  5. free
  6. .run

第 3 章,PyCUDA 入门

  1. 主机/设备之间的内存传输以及编译时间。
  2. 您可以,但这取决于您的 GPU 和 CPU 设置。
  3. 使用 C?运算符执行逐点和减少操作。
  4. 如果gpuarray对象超出范围,则调用其析构函数,该析构函数将自动释放(释放)它在 GPU 上表示的内存。
  5. ReductionKernel可能会执行多余的操作,这可能是必要的,具体取决于底层 GPU 代码的结构。中性元件将确保这些多余操作不会改变任何值。
  6. 我们应该将neutral设置为有符号 32 位整数的最小可能值。

第 4 章,内核、线程、块和网格

  1. 试试看。

  2. 所有线程不会同时在 GPU 上运行。就像操作系统中的 CPU 在任务之间切换一样,GPU 的各个内核在内核的不同线程之间切换。

  3. O(n/640logn),即 O(n logn)。

  4. 试试看。

  5. 实际上,在 CUDA 纯块级中没有内部网格级同步(使用__syncthreads).时,我们必须将单个块以上的任何内容与主机同步。

  6. 朴素:129 个加法运算。工作效率:62 个加法运算。

  7. 同样,如果我们需要在一个大的块网格上进行同步,我们不能使用__syncthreads。如果我们在主机上同步,我们也可以在每次迭代中启动更少的线程,从而为其他操作释放更多的资源。

  8. 在简单并行求和的情况下,我们可能只处理少量数据点,这些数据点应等于或小于 GPU 内核总数,这可能适合块的最大大小(1032);因为单个块可以在内部同步,所以我们应该这样做。只有当数据点的数量远远大于 GPU 上可用内核的数量时,我们才应该使用工作效率高的算法。

第 5 章,流、事件、上下文和并发

  1. 两者的性能都有所提高;随着线程数量的增加,GPU 在这两种情况下都达到了峰值利用率,从而减少了通过使用流所获得的收益。
  2. 是的,您可以异步启动任意数量的内核,并将它们与cudaDeviceSynchronize同步。
  3. 打开你的文本编辑器,试试看!
  4. 高标准偏差意味着 GPU 的使用不均衡,在某些点上超过 GPU,而在其他点上利用不足。较低的标准偏差意味着所有已启动的操作通常运行平稳。
  5. 我主机处理的并发线程通常比 GPU 少得多。二,。每个线程都需要自己的 CUDA 上下文。GPU 可能会被过多的上下文淹没,因为每个上下文都有自己的内存空间,并且必须处理自己加载的可执行代码。

第 6 章,调试和分析 CUDA 代码

  1. 内存分配在 CUDA 中自动同步。
  2. lockstep属性仅适用于大小为 32 或更小的单个块。在这里,两个区块将在没有任何lockstep的情况下适当分开。
  3. 同样的事情也会发生在这里。这个 64 线程块实际上会被分成两个 32 线程的扭曲。
  4. Nvprof 可以计算单个内核的启动时间、GPU 利用率和流使用率;任何主机端探查器都只能看到正在启动的 CUDA 主机功能。
  5. Printf 通常更容易用于具有相对较短的内联内核的小规模项目。如果您编写了一个包含数千行的非常复杂的 CUDA 内核,那么您可能希望使用 IDE 逐行调试内核。
  6. 这会告诉 CUDA 我们要使用哪个 GPU。
  7. cudaDeviceSynchronize将确保相互依赖的内核启动和 mem 拷贝确实同步,并且在所有必要操作完成之前不会启动。

第 7 章,将 CUDA 库与 Scikit CUDA 一起使用

  1. SBLAH 以 S 开头,因此此函数使用 32 位实浮点。ZBLEH 以 Z 开头,这意味着它使用 128 位复数浮点。
  2. 提示:设置trans = cublas._CUBLAS_OP['T']
  3. 提示:使用 Scikit CUDA 包装器包装 dot 产品,skcuda.cublas.cublasSdot
  4. 提示:基于上一个问题的答案。
  5. 您可以将 cuBLAS 操作放入 CUDA 流中,并使用此流中的事件对象来精确测量 GPU 上的计算时间。
  6. 因为输入对 cuFFT 来说很复杂,所以它将以 NumPy 计算所有值。
  7. 暗边是由于图像周围的零缓冲造成的。这可以通过在其边缘镜像图像而不是使用零缓冲区来缓解。

第 8 章,CUDA 设备功能库和 Thrust

  1. 试试看。(事实上比你想象的更准确。)
  2. 一个应用:高斯分布可用于向样本中添加white noise,以增强机器学习中的数据集。
  3. 不,因为它们来自不同的种子,如果我们将它们连接在一起,这些列表可能具有很强的相关性。如果我们计划将它们连接在一起,我们应该使用同一种子的子序列。
  4. 试试看。
  5. 提示:请记住,矩阵乘法可以看作是一系列矩阵向量乘法,而矩阵向量乘法可以看作是一系列点积。
  6. Operator()用于定义实际功能。

第 9 章,深度神经网络的实现

  1. 一个问题可能是我们没有将培训投入标准化。另一个原因可能是训练率太高。
  2. 在训练速率很小的情况下,一组权重可能收敛得很慢,或者根本不收敛。
  3. 较大的训练率可能导致一组权重与特定批次值或该训练集过度匹配。此外,它还可能导致第一个问题中的数值溢出/下溢。
  4. 乙状结肠。
  5. Softmax。
  6. 更多更新。

第 10 章,使用已编译的 GPU 代码

  1. 只有 EXE 文件具有主机功能,但 PTX 和 EXE 都将包含 GPU 代码。
  2. cuCtxDestory
  3. printf具有任意输入参数。(尝试查找printf原型。)
  4. 使用 Ctypesc_void_p对象。
  5. 这将允许我们从 Ctypes 链接到具有原始名称的函数。
  6. CUDA 自动同步设备/主机之间的设备内存分配和 memcopies。

第 11 章,CUDA 中的性能优化

  1. atomicExch是线程安全的事实并不保证所有线程都会同时执行此函数(事实并非如此,因为网格中的不同块可以在不同的时间执行)。
  2. 大小为 100 的块将在多个扭曲上执行,除非我们使用__syncthreads,否则这些扭曲不会在块内同步。因此,atomicExch可以被多次调用。
  3. 由于默认情况下以 lockstep 方式执行扭曲,并且大小为 32 或更小的块使用单个扭曲执行,__syncthreads将是不必要的。
  4. 我们在 warp 中使用了一个幼稚的并行求和,但在其他方面,我们使用atomicAdd进行的求和与使用串行求和一样多。虽然 CUDA 会自动并行许多此类atomicAdd调用,但我们可以通过实现工作效率高的并行求和来减少所需atomicAdd调用的总数。
  5. 肯定是sum_ker。很明显,PyCUDA 的 sum 没有使用与我们相同的硬件技巧,因为我们的 sum 在更小的阵列上性能更好,但通过将大小扩展到更大的阵列,PyCUDA 版本更好的唯一原因是它执行的加法操作更少。

第十二章,从这里到哪里去

  1. 两个例子:DNA 分析和物理模拟。
  2. 两个例子:OpenACC、Numba。
  3. TPU 仅用于机器学习操作,缺少渲染图形所需的组件。
  4. 以太网。