上一篇文章我们主要讲了为什么选择以及选什么本地大模型。但是选择什么模型其实受到设备的制约,如果你还没有入手一台合适的设备,那么反过来还可以通过需求出发帮助选购合适的产品。

首先需要声明的是:

  1. 本文创作于 2024 年底,由于相关生态在蓬勃发展,请以最新的社区指导为准。
  2. 本文会涉及到一些基本的模型结构、推理相关概念及数学计算,可以略过只关注结论,直接跳转到本文末尾。
  3. 针对模型推理,没有特殊说明情况下只考虑单机、单卡的家用场景,例如 Mac mini、配备 NVIDIA 显卡的个人 PC 等。

从模型开始

我们都知道当今的语言大模型实质上的架构都基本一致,是仅有解码器的 Transformer+(加号和原始论文结构做区分)。如果你在网上搜什么是 Transformers,很可能会搜到 Jay Alammar 这篇著名的博客1。假如还是有点模糊,可以看看这几个链接了解基本概念2、数学原理3及其可视化4。对用户来说这些细节能帮助我们更深入了解内部原理,但是并不重要,本文会尽量简化不必要的细节。

大模型本质上是一种特殊的程序:输入指令,往后吐字。模型并不能直接处理文字,现在比较通用的做法是使用 tokenizer 把文本映射到词表中的坐标——可以想象一本《新华字典》,在里面的所有的词或字用从 0 开始的序号标记,模型编码的时候就是从字典里面读这个的标号,解码的时候反之。下图我们简化一下,每个 token 对应一个字:

  1. 用户输入 prompt「你是谁」,模型经过一系列计算,在所有可选词中选取最大的「我」进行输出
  2. 「你是谁」+「我」一起输入模型,进行类似的计算,输出「是」;把「是」重新拼回输入中,重复这一个过程

注意一般情况下我们不会只选取概率最大的 token 进行输出,这样这个程序的输出变成完全确定,并不利于创意类生成,所以需要额外的参数(temperature, topk 等等)来加入一些采样随机性。

我们可以区分实际计算的两个过程:

  1. 预填充(prefill):对应绿色部分,模型在「理解」一长段的话。预填充的意思是模型在计算的同时需要保存一部分后面生成所需要的中间状态(KV Cache)。
  2. 自回归解码(decoding):对应蓝色部分,模型一步步、token by token 的生成结果。

实际我们在使用流式的大模型应用时,输入请求之后会等一会儿,模型开始出第一个词然后继续输出,也就是对应这两个状态。

我们继续放大,看模型内部。

基于 Transformers 的语言模型有着非常规整的结构——除了输入输出就是层层叠叠重复的 Transformer 块。正如它的名字的含义,对词向量不停地做计算,「变换」其表示。

每个模型的架构确定,则每一步的计算公式都确定了,例如线性的 f(x)=ax+b,而模型的权重或者参数即其中的 a 和 b。

实际上 LLM 的参数量和计算量都要大非常多,以 10B 参数为例,一共有 10^10 个参数,如果都用半精度(FP16/BF16)存储,大小为 10^10×16/8 bytes = 20 GB。

模型推理在干什么

有了大致的模型概念,我们可以想象一下模型在做前向推理的时候,实际在执行的操作,以 CPU 推理为例:

  1. 把模型参数加载到内存中
  2. 小部分计算参数搬运到 CPU 核心中参与计算并返回

如果以大多数 M 系列芯片的 Mac mini 基础款为例,从 SSD 加载模型的速度大概是 3 GB/s,因此为了提高推理速度,更高速的内存自然是必须的。接下来从内存中加载参数到计算核心的 SRAM,这个速度则和内存带宽有关系,频率更高的 DDR5 内存肯定比 DDR4 更快。但是 DDR5 内存带宽相对于计算更快的 GPU 就不够了,比如算力更加强劲的 NVIDIA 卡,如果显存带宽不够,计算核心算完了只能干等着数据搬过来,影响整体推理效率。举个例子,两个人玩《胡闹厨房》,一个人负责搬运食材,另一个人负责做菜,尽管做菜速度飞快,但是搬食材的慢了则流水线的效率也上不去。因此一些更加专业的训练卡,比如 A100 会配备更高速的 HBM2 显存,能达到 1555 GB/s 的显存带宽5

因此也延伸出了两个问题:

  1. 内存/显存不够放下模型怎么办?
  2. 如何提升 LLM 推理速度?

解决内存问题

前面我们提到一个 10B 的模型,半精度下权重就有 20 GB,轻轻松松就超过了丐版 Mac mini 的 16 GB。除了权重之外,随着序列增长的 KV Cache 以及计算的中间激活同样存在于内存中。我们当务之急是解决这个问题。

使用更大的内存?最大的 GeForce RTX 40 系显卡也只给了 24 GB,连 Qwen2.5-14B 都装不下;M4 Mac mini 如果要加到 24GB 需要加 3000 人民币。虽然说加钱世界可及,如果我们不想加钱呢?

在 LLM 的计算中,大多数计算在半精度下执行。我们先了解下半精度浮点数的表示方法:如图6所示,以 FP16 为例,第一位为符号位(s),紧接着 5 位为指数位(e),剩下的为尾数位(m),当指数位不等于 (00000)₂ 或者 (11111)₂ 时,表示的值为 (-1)ˢ×2ᵉ×1. (m)₂。比如 0 01101 0101010101 可以表示 2⁻² × (1 + ⁠341/1024⁠ ) ≈ 0.33325195。

如果我们只想用 8 位来存储权重呢?如果按照同样的浮点数表示方式,势必需要缩减指数位和尾数位。对应减少了动态范围和精度,比如英伟达在 H100 中加入了 FP8 的支持7,分为两种数值类型:

  • E4M3:取值范围 -448 到 +448, 以及 nan, 精度比 E5M2 高
  • E5M2:取值范围 -57344 到 +57344,以及 nan 和 +/- inf,表示范围比 E4M3 大

虽然动态范围和精度比半精度有进一步下降,但是经过合理的配置甚至是可以正常训练的。可是如果我们的硬件并不支持 FP8,以及想进一步压缩每个权重的表示位数,有没有更通用的办法呢?

最广泛被采用的就是整型,比如 INT8/INT4。但是 INT8 只能表示 -128 到 127 的整数,我们需要额外的计算来恢复精度。

如上图所示,我们可以找到一组缩放值和零点,使得量化之后的整数值 x 经过反量化之后和原始值比较接近。尽管我们需要存储额外的变量(缩放值和零点),由于大量的参数变成了存储更少的整型,模型的占用空间也大大下降了。

在推理的时候,量化的模型需要经过反量化的计算,由于反量化的操作开销比较低、并且大多数情况下模型处于带宽瓶颈,并不会对速度有明显影响;尽管量化还原之后有误差,但是不同的量化策略可以还原模型性能,在实际任务中引起的性能下降可以被接受。

比如 llama.cpp 中使用了 k-quants8,下面图中方块(⬜)表示 FP16 的模型,原点(⚪)表示各种量化模型,横轴是模型权重占用空间大小,纵轴是模型的困惑度(越越好)。我们可以观察到:

  • 在一定精度的量化下性能几乎是无损的,随着精度进一步下降,性能逐渐降低
  • 更大参数的模型低精度量化的要比同样「大小」的小参数模型性能更好,如果我们是效果优先,应该选择更大参数量的模型

我们再看看表格化的数据。对于一个 Llama 7B 的模型,F16 需要 13.0 GB 的空间,而 Q4_K_S 量化只需要 3.56 GB,并且性能并没有明显下降!这里我们先忽略 k-quants 各种奇怪的后缀命名,如果简单计算 13 GB × 4 / 16 = 3.25 GB 大致上能吻合。

但是奇怪的是在 M2 Max 上,经过量化的模型的出词延迟 ms/tok 居然还变小了!我们前面提到量化模型推理时有额外的反量化计算,这里的原因就是在 M2 Max 上模型速度的瓶颈并不是算力,而是内存带宽,处理器算完了,下一波数据还没搬过来,然而在量化完之后同样的带宽能搬运更多权重。

目前对于大多数模型,如果想保有近似原始模型性能,Q4 几乎是一个极限选择。我们可以通过一个懒人公式,参数量 (B)/2=内存(GB)来估算。但是实际上推理还需要存 KV Cache、激活等缓存变量,尤其是处理长文档的时候。比如对于 Llama2-7B9,每个 token 需要的 KV Cache 大约为 0.00052 GB(半精度),也就是输入输出在 2000 的时候就需要额外 1 GB 内存。

解决速度问题

在讨论这个问题之前,我们先定义这里的速度究竟是什么?前面我们提到推理的时候分为两个阶段,对应可以有两类指标评价:

  • 预填充阶段:固定上下文长度下,第一个生成词出来的时间(Time to Fist Token, TTFT),单位 ms;也可以用每秒处理多少个 token 的速度定义(Prompt Processing, PP),单位 tokens/s。
  • 自回归解码阶段:固定上下文长度,每个输出词的平均耗时(Time per Output Token, TPOP),单位 ms;也可以用文本生成的速度定义(Text Generage, TG),单位也是 tokens/s。

在 PC 游戏和装机领域我们经常会听到 CPU 瓶颈、显卡瓶颈对游戏性能的影响。同样对于 LLM 推理,在大多数情况下对于 GPU 推理来说有这样的结论:在预填充阶段,主要是算力瓶颈,Transformer 并行处理 prompt token 需要大量的计算;在自回归解码阶段,主要是带宽瓶颈,计算不够密集导致参数还没搬过来只能空等。

我们记住几个公式:

  • 预填充时间 = 提示 token 数 * (参数量 / 计算 TFLOPS)
  • 单 token 解码时间 = 参数量 / 带宽
  • 总延迟 = 预填充时间 + 生成 token 数 * 单 token 解码时间

对于更加严肃准确一点的推理入门知识,有一个非常好的文章(A guide to LLM inference and performance | Baseten Blog),英文比较好、有一定算法基础的同学可以跳转观看。我们这里引用其中的部分计算作简单介绍。

对于硬件来说我们可以定义一个 ops/byte 的性能指标,即搬运多少数据时可以做多少计算。注意在 LLM 推理中,无论模型是否量化,计算都应该在 FP16 或者更高的精度。所以我们在衡量算力的时候应该用 FP16 的指标,比如 4060Ti 的 FP16 算力是 22.06 TFLOPS(每秒执行浮点数计算的次数)。这个算力除以内存或者显存带宽就可以算出相应指标,比如 4060Ti 的显存带宽是 288 GB/s,即 76.6 ops/byte。但是对于专用的推理卡比如 A10,FP16 算力为 125 TFLOPS,24 GB GDDR6 的显存带宽达到 600 GB/s,则能达到 208 ops/byte,

同时我们可以计算网络每部分同样单位的计算密度(arithmetic intensity),比如当上下文长度为 4096 时,Llama 2 注意力部分的计算密度为 62 ops/byte。这个值都比上面两个值低,因此如果我们的模型服务每次只处理一次请求,那么解码时基本上是处于带宽瓶颈。如果要充分利用卡的算力,就需要使用 batching 等方法。

硬件选择

很多读者看到上面的介绍应该已经一头包了。厂商也利用了这里面的信息差,开始宣传各种新产品的 AI 算力。这给我们的设备选购带来了非常大的干扰。

先说 CPU 部分,几乎每家都在宣传其 NPU 算力,但是实际上这个算力是非常难利用的,基本上 NPU 是为了低功耗的整型计算设计的,比如 AMD Ryzen™ AI 7 PRO 360 在产品页10标注的 AI 算力高达 72 TOPS。注意这个单位和 TFLOPS 的区别,这里这是整数运算,实际上几乎没有大模型能以纯整型进行推理。

尽管类似 Apple Neural Engine 是可以支持半精度运算的,但除了苹果自己之外并没有第三方推理引擎支持11

再说 GPU 部分,对于消费级的产品,厂商也是费劲心思让消费者没有办法辨别卡的真实算力,比如下面的 4060Ti 的参数表里面又出来了一个 353 AI TOPS,由于是在没找到哪里有标注实际计算方式,推测是整型加稀疏化的结果。

鉴于相当模糊的现状、有上手难度的计算方式,最简单的办法就是看社区汇报的类似硬件的推理速度。llama.cpp 非常贴心地总结了 Apple Silicon 对于 Llama 7B 的推理速度12(输入 512, 输出 128):

 带宽 GB/sGPU 核心数F16 预填充
[t/s]
F16 解码 [t/s]Q8 预填充
[t/s]
Q8 解码 [t/s]Q4 预填充
[t/s]
Q4 解码
[t/s]
M210010201.346.72181.412.21179.5721.91
M2 Ultra800601128.5939.861003.1662.141013.8188.64
M412010230.187.43223.6413.54221.2924.11
M4 Max54640922.8331.64891.9454.05885.6883.06

这里我们也能比较直观地看到:

  • M4 相较于 M2 因为带宽提升不大,并且由于带宽瓶颈,总体性能提升不大,在 15% 左右
  • Max 和 Ultra 由于更多的核心数和更大的带宽,性能要远高于普通版
  • 在量化之后预填充的速度均有下降(算力瓶颈,需要反量化的额外计算),解码速度有明显提升(带宽瓶颈)

我们再来验证一下前面提到的公式:

  • M4 的 FP16 的解码速度应该是 1 / (2 * 7B / 120 GB/s) = 8.57 t/s,M2 应该是 1 / (2 * 7B / 100 GB/s) = 7.14 t/s。实际性能都比理论略低,符合预期。
  • M4 的 GPU FP32 算力是 4.26 TFLOPS,如果按照 NVIDIA 和 AMD 显卡,FP16 应该是 2 倍,这样算出来的速度是 1 / (2 * 7B / 8.52 TFLOPS) = 609 t/s,远远大于表里面的 230 t/s。但 Apple Silicon 用了额外的管线计算13 FP16,并没有和 FP32 共用,实际性能是和 FP32 是一致的14!实际理论速度约为 304 t/s,也比较符合预期。

对于 N 卡来说速度会快很多,比如根据其博客15,Llama 3 8B Q4 的模型,4060Ti 就可以跑到接近 55 tokens/s(当然这里输入 100,输出 100,比上面那张表要小)。同样可以通过上面的公式简单验算。

我们再看看纯 CPU 推理工况下的表现,比如下图16,AMD 和 Intel 两款移动端「AI」处理器的表现,实际上比 M4 还要慢一半。这主要还是由于搭载内存的带宽限制。

这里我们再次强调:

  1. 不要看 NPU 算力的宣传
  2. 关心 FP16 算力和内存/显存带宽,前者影响首字相应时间,后者影响生成速度,要根据自己的常用任务选择。

个人性价比之选

个人认为对于大多数人设备选购还是遵循「进可 LLM,退可娱乐」的原则。不同人有不同的需求,并且预算、功耗、场景都有所不同。如果说最优性价比的选择,我觉得是:

  • M4 Mac mini:毫无疑问,足够的性能、16GB 起步的内存,并且内存带宽够用,而且还是一台正常的 Mac。
  • 配备 16GB 及以上 NVIDIA GPU 的 PC:如果要提高速度,最优的兼容性,建议还是上 N 卡。基本所有的现代深度学习算子设计的时候都是 CUDA in mind,尤其在 LLM 里面还有 flash attention 这种加速大杀器。选择更大显存的版本,能放下更大的模型。其余型号参考维基百科17挑选。同样尝鲜完了咱还可以打打游戏。此外,值得注意的是 50 系的显卡发布在即,不仅算力提高,更重要的是可能会提供配备 GDDR7 、更大显存的型号,如果中端卡有 24GB 显存,那么我们就可以跑起来 32B Q4 的模型,会是相当大的性能提升。
  • 配备更高频率内存的高性能 CPU:能上 DDR5 多通道高频率的优先选择。并且值得注意的是,如果是纯 CPU 推理,建议使用 MoE 模型,比如 DeepSeek-v2-Lite,会比 Dense 模型有更好的速度表现。

关联阅读:

> 关注 少数派小红书,感受精彩数字生活 🍃

> 实用、好用的 正版软件,少数派为你呈现 🚀