Nvidia CUDA的前世今生

Any sufficiently advanced technology is indistinguishable from magic. - Arthur C. Clarke

CUDA(Compute Unified Device Architecture)是Nvidia强大的护城河之一, 也绝对算是科技黑魔法.

最近突然冒出了一个想法, 为什么CUDA要这样设计(kernel, warp, thread, block, grid...)? 带着这个疑问, 我花了一些时间 在调研CUDA背后的历史, 以下是对Nvidia重要护城河之一的CUDA为什么这样设计的介绍:

CUDA发展历史

要了解CUDA为什么这样设计就需要了解CUDA的发展时间线. 在维基百科上搜索CUDA, 里面有提到CUDA的发展历史, 以下为原文:

Ian Buck, while at Stanford in 2000, created an 8K gaming rig using 32 GeForce cards, then obtained a DARPA grant to perform general purpose parallel programming on GPUs. He then joined Nvidia, where since 2004 he has been overseeing CUDA development. In pushing for CUDA, Jensen Huang aimed for the Nvidia GPUs to become a general hardware for scientific computing. CUDA was released in 2006. Around 2015, the focus of CUDA changed to neural networks.

谁是Ian Buck? 继续搜索Ian Buck会发现他现在英伟达的副总裁, 以前是斯坦福的博士生, 点击Ian博士时期的主页. 可以看到他在博士期间做的项目BrookGPU, 里面有提到Brook是Ian提出的针对与GPU编程的流式编程语言. 此外在CUDA1.0时期的采访,Ian在视频里面明确提到将Brook的想法迁移到CUDA上, 因此可以认为Brook就是CUDA最早的雏形.

Ian Buck的Brook语言

让我们一起再来看看BrookGPU的项目介绍:

brook-gpu

总结一下, Brook提出了stream programming model以及stream application并给出了对应的实现去解决通用计算的问题.

再结合BrookGPU当时的论文Brook for GPUs: Stream Computing on Graphics Hardware以及Ian Buck的博士论文STREAM COMPUTING ON GRAPHICS HARDWARE. 我们能够了解到以下几个点:

  • 由于GPU与CPU所针对的场景不同,并且由于CPU的局限性: 有限的instruction level parallelism (ILP), 过度使用缓存去获取局部性. 因此GPU的编程语言形态会与CPU有所不同.
  • 当时也有针对于GPU编程的语言, 但是这些语言的实现都是针对于图形学的shader所设计的, 而不是为了通用计算编程所设计
  • 程序在GPU上执行时,其内存访问延迟主要受到两个关键因素的制约: 一是计算速率(compute rate), 二是内存带宽(memory bandwidth). 这意味着,GPU处理数据的速度和内存传输数据的能力共同决定了程序在内存操作中的延迟表现. BrookGPU的设计是为了让GPU计算充分发挥, 从而让内存时延主要部分为内存带宽.
  • Brook不是第一个尝试去解决通用计算问题, 但是是第一个使用流式处理(stream processing)去解决通用计算.

advantage-of-streaming

在Ian提出Streaming Processing框架的这一节提到了很重要的一个观点. 内存的访问速度(memory access rate)即内存带宽(memory bandwidth)往往 是制约程序性能的瓶颈. 而软件层由于改变不了物理的内存带宽, 因而会采取其他技巧去隐藏内存延迟(memory latency). 这里Streaming Processing使用data parallelism与arithmetic Intensity去隐藏内存延迟(hide memory latency). Ian也提出了什么是arithmetic Intensity的概念:

In order to quantify this property, we introduce the concept of arithmetic inten- sity. Arithmetic intensity is the ratio of arithmetic operations performed per memory operation, in other words, flops per word transferred.

感觉这里解释的不是很清楚, 于是询问GPT4的回答, 讲的很清楚: arithmetic Intensity Arithmetic intensity指的是运算指令(add, mul..)与内存操作(load, store..)的比例.

这里小结一下Brook做了什么事:

Brook是一种编程语言, 通过软件层的抽象来让程序尽可能得调用GPU的运算单元,尽可能减少内存搬运操作. 从而隐藏内存延迟.

如果有兴趣详细了解的话, GTC2022上的这个talk会很有帮助. 在了解了这些之后, 我们再来一起看看BrookGPU与CUDA的对比.

Brook与CUDA的对比

接下来结合上面的信息以及CUDA1.0的参考手册, 我们来看看Brook与Cuda的一些概念的对比. 由于CUDA是在Brook之后诞生, 所以这里以Brook中的概念为准分各个小节. 为了尽可能得传递原始信息避免加工, 每个概念会附上原文.

Stream

A stream is a collection of data which can be operated on in parallel. - Ian Buck, BROOK STREAM LANGUAGE p24

由于CUDA1.0里面没有Stream的概念, 这里没有直接查到是CUDA的哪一个版本提出了Stream. 所以参考了Steve Rennich在GTC的ppt.

A sequence of operations that execute in issue-order on the GPU - Steve Rennich, CUDA C/C++ Streams and Concurrency

Stream在brook指带的是一系统的数据, 而在cuda里面是的一系列的指令.

Kernel

Brook kernels are special functions, specified by the kernel keyword, which operate on streams. - Ian Buck, BROOK STREAM LANGUAGE p26

在Brook里面kernel指的是处理stream的函数. 由于stream是的是一系列的数据, 因此kernel也就是处理data的函数.

More precisely, a portion of an application that is executed many times, but independently on different data, can be isolated into a function that is executed on the device as many different threads. To that effect, such a function is compiled to the instruction set of the device and the resulting program, called a kernel - Nvidia CUDA Compute Unified Device Architecture Programming Guide, version1.0, p7

可以看到kernel在Brook与CUDA里面的概念是一致的. 都表示为处理数据的函数.

Reductions

While kernels provide a mechanism for applying a function to a set of data, reductions provide a data-parallel method for calculating a single value from a set of records. - Ian Buck, BROOK STREAM LANGUAGE p30

这里需要附上原文的代码才能更详细的说明:

// Task: Compute the sum of all the elements of a sum (a, r);

// Brook
reduce sum (float a<>, reduce float r<>) {
    r += c;
}
float r;
float a<100>;

// Equivalant C code:
r = a[0];
for (i=1; i<100; i++)
    r += a[i];

Reduction指的是将从一组数据中计算单个值的任务抽离出来, 很奇怪的一点是明明这里的操作也可以归为kernel的范畴, 但是Ian却单独抽里开. 说明Brook对Reducitons的实现与kernel是不同的. 而从语法上来看, 这段代码与CUDA调用kernel function几乎是一致的. 在CUDA中没有单独的概念, 说明这一操作已经在CUDA统一了.

到此为止, 我们已经了解了Brook里面提出的主要概念并且与CUDA里面的概念做了对比. 了解了Brook与CUDA之间的关系. 尽管Ian在自己的博士论文里面论述了Streaming Processing相比于SIMD的优势. 但是CUDA1.0的编程设计仍然是针对于SIMD Processor而不是后来的Streaming Processor. 而从2006年Ian的博士毕业到2007年CUDA1.0的发布. 中间肯定有受限于当时硬件设计的妥协. 说明任何事物发展都有个规律, 从概念提出最后到完全时间的周期往往会比较长. SIMD到SIMT也不是一蹴而就.

为什么CUDA这样设计

回到开头所提出的问题, 为什么CUDA这样设计? 结合Steve Jone在GTC2021的"How Gpu Computing Works"以及GTC2022的"How CUDA Programming Works", 我尝试用尽可能精炼的语言描述下我的理解:

从CUDA的目标上来看, 制约现在程序性能往往不是由于通常概念上的运算性能, 而是由于内存延迟. CUDA的任务就是让程序尽可能的调动GPU的运算单元, 从而隐藏内存延迟.

从CUDA的实现方式上来看, 因为GPU最开始是为加速渲染所设计并且适合执行大量重复的计算, 因此CUDA会有概念层的抽象. 通过将一张图片或者一次渲染做拆分成grid, 而每个grid会有多个blocks作为操作单元, 每个block的数据可以由多个thread来共享数据. 每个thread是最小操作单元. 通过这样划分, 实现了SIMT(Single Instruction Multiple Threads). 从而隐藏内存延迟.

下面这张图对于理解CUDA的概念非常有帮助: cuda-concepts

总结

CUDA的发明者Ian Buck在读博士的时候(2006年左右), 当时已经有专门针对GPU的编程语言, 但是这些特点的语言的特定都是针对于图形学里的shader设计的, 因此不具有通用性. 因此CUDA的意义在于使用流式处理(stream processsing)去解决图形处理器通用计算(General-purpose computing on graphics processing units)软件层抽象的问题.

参考资料

  • How Gpu Computing Works - Steve Jones
  • How CUDA Programming Works - Steve Jones
  • BROOK STREAM LANGUAGE - Ian Buck
  • Brook for GPUs: Stream Computing on Graphics Hardware - Ian Buck
  • Nvidia CUDA Compute Unified Device Architecture Programming Guide, version1.0
  • Wikipedia
LLM OS的曙光

在今天的OpenAI的春季发布会, OpenAI发布了gpt-4o. 虽然前几代模型也支持语音功能, 但是在gpt-4o中, 其中任意打断的能力和更快的响应速度, 实在是难以让人区分出是机器人还是人类. 但是现在每次对话都需要重新用语音prompt, 来让模型产生预期的“性格”. 所以显而易见的, 不久后的GPT商店会上架不同“性格”的AI. 以及对自定义AI的支持.

语音和图像的联动是另一项gpt-4o所突出展示出的能力. 用语音交互的方式来让AI分析实时的摄像头数据. 并且在pc端通过桌面端应用还具备voice copliot的能力. 已经能让生产力提高到下一个层次. 很明显在未来能够 清晰的表达想法和理解问题 的能力比单纯的技能更加重要. 其实这已经是LLM OS的一个很大的突破了.

随着OpenAI开放更加便宜的AI, OpenAI的影响力肯定会进一步扩大. 他们所能获取的数据集肯定越来越多. 假如世界模型是一个向量f(x), 根据Universal approximation theorem. 因此世界模型会被拟合, 假如未来的模型架构迭代, 比如使用Kolmogorov–Arnold Networks. 实际上变的也是拟合的基函数, 核心思想都是一致. 也是对世界模型的拟合. 因此, AI最终一定会解决现有的世界模型 f(x) 的一切问题. 但是以这种方式的AI不具备创造力.

如何让AI具备创造力? 这篇文章The Next Decade in AI: Four Steps Towards Robust Artificial Intelligence的作者就很好的分析了这个问题. 现有的AI始终受限于数据集. AGI需要有对知识的归纳和演化的能力.以下是原文链接.

a reasoning system that can leverage large-scale background knowledge efficiently, even when available information is incomplete is a prerequisite to robustness. - p40

因此在未来的AGI形态,会有一个底座的知识语料, 在这个底座的知识语料当中, AI有知识关联, 归纳, 推理能力. 当AI不断的从世界模型提炼信息的时候, 也会对这个底层的知识语料进行加工. 类比于人类来说的话, 人在不断接触外部信息源的时候也会不断迭代自己的世界观(或者说每个人的人生哲学). 而当人遇到未接触的问题时, 在没有外部信息源的接触下, 也会依赖于自己的世界观进行加工和理解.

或许未来出现一种形态, 在神经网络(知识的拟合)里面嵌套专家系统(知识的推理). 能够让AI真正具有创造力.

2024百度Create ai大会

参会记录

记录一下本周线下参加的2024百度Create ai大会, 大会回放链接点击这里. 大会的主要内容是百度的ai产品发布会. 我当时看大部分演讲的内容其实更多的是偏应用方面. 我个人对技术方面的内容更感兴趣就只参加了主论坛并且只听了前三场讲座.

intro

首先开场的是李彦宏, 他讲的大部分内容都是偏向产品端. 我印象比较深刻的是开场没多久就说了下面这张图. "未来大型的AI原生应用基本都是MOE." .并且他说明这里的MOE指的是混合模型(Mixture Of Engine)并不是技术上的MOE, 出于对技术上指代的MOE的好奇, 我开始去研究Mixture Of Experts. 这个链接 是非常好的资料. 现有很多大模型会包含Mixture Of Experts Layer. 因此在提示词工程里面需要有角色扮演, 相当于激活特定分支的Expert来让优化输出结果.

moe

之后是王海峰介绍的多模型推理部分. 也是从输入端会经过路由模型. 再由路由模型分发输入给不同的模型. 实际上所谓的Mixture Of Engine与Mixture Of Experts的思想是一致的. 只不过是宏观(多个模型)与微观(单个模型)之间的区别. infer

最后是沈抖介绍的万源智能操作系统, 与Andrej Karpathy所提及的LLM-OS是相同的. 之前看过Andrej的一个talk里面就讲到基本所有人都在往LLM-OS发力. 将一个复杂任务拆解的话, 现有的LLM已经具有相当的理解能力, 但是缺少对工具的使用能力. 如果要让LLM能够使用工具的话, 许多现有的应用都要重新开发. 或许也是未来的一个风口所在.

wanyuan

参考资料

下面是对于了解Mixutre Of Experts有帮助的链接:

  • https://developer.nvidia.com/blog/applying-mixture-of-experts-in-llm-architectures/
  • https://stackoverflow.blog/2024/04/04/how-do-mixture-of-experts-layers-affect-transformer-models/
  • https://developer.nvidia.com/blog/mastering-llm-techniques-training/