如何设计一个高性能的chatgpt

79 阅读6分钟

description

design chatgpt.png

一次问答的流程

  • input prompt
  • tokenization
  • embedding
  • transformer × n
  • sampling
  • detokenization
  • output

重点就是 transformer × n (最耗时/计算瓶颈)

  • feed forward (前向网络)

  • multi head attentation

    • self attentation

      • Q K V (Query Key Value)

multi - head attentation由多个self attentation组成,每个self attentation里面会将你的输入线性投影到QKV类型的向量

线性投影 => 矩阵乘法

GPT的两个特点

  • Token By Token (每次只预测下一次Token)
  • auto regression(自回归)

GPT如何与你上下文交流(KV Cache的作用)

每次生成的Token要作为Input来预测下一次Token,给GPT一个输入,这个输入是一个序列,需要用这个序列来预测下一个Token

[Token1, Token2, Token3... ...]

GPT把整个序列全部算一遍,但是最后只需要拿到最后一个Token在位点上的概率分布来生成一个最新的Token,所以前面N-1的Token状态虽然被计算但是没有拿来用

当最新的Token被计算出来后,会加入到序列的最后,由此反复上面的流程

上述的描述可知,GPT有一个大量的重复运算,重复的数量决定序列的长度

KV cache.gif

例如cache 把之前算好的K 和 V 存下来,当生成下一个Token的时候,把新的Token带来的QKV追加到GPU的显存里的KV cache上。等下一次输入时,把带来新的Q与已缓存的全部的KV做一次点积乘即可,增大计算效率(接近常数的计算效率)

计算吞吐量

举例:A100 80G

llama 2 -7B (4096 dim,32 layers,4096 context)

model weight = 7b × 2 bytes(FB 16)= 14GB

KV cache / token = 2 × layer × d_dim × 2 bytes = 2 × 32 × 4096 × 2 = 512 KB / token

total KV cache = 4096 × 512 KB = 2 GB

(80 - 14)/ 2 = 33 session(并发,即同时有几个上下文,实际要少,因为也有其他程序使用显存)

QKV优化(llama-3)

上述吞吐量中优化,改变KV的大小来提高吞吐量

grouped query attention会使用更少的KV head => 一旦head 减少,整个KV cache里KV数量也会减少

llama 3 - 8B(32 layer,dim 4096,32 head,8KV,d_head 4096/32 = 128,8192 context)

model = 8 × 2 bytes = 16 GB

KV cache = 2 × layer × m_kv × d_head × 2 bytes = 2 × 32 × 8 × 128 × 2 = 128 KB / token

total = 8192 × 128 KB = 1 GB

(80 GB - 16 GB) / 1 GB= 64 session

MQA(共享一份KV)

计算空间节省,推理速度增大,模型表达能力下降

使用的chat service大概率不是7B的模型,应该是Llama 70B

70 B × 2 bytes = 140 GB的显存 > 显卡显存,解决方案

  1. Batching(批处理)

    • naive batching

      • 收集一批requests,然后丢给GPU,一批结束放下一批
      • 延迟高,水桶效应,最慢的request结束,下一个批处理才开始
    • 持续批处理(continous batching)

      • 只要批队列里面有空位,就从Message Queue里领取下一个task
    • batching 优化GPU还是有空闲时间

      • KV cache写入有两步骤(先Prefill -> Decode)
      • Prefill(write cache):把所有的input token全部算好写入 cache
      • Decode(read cache + append token):cache算好之后,开始生成新的token,回到上面GPT的过程,每次只生成一个新的token,读取cache旧的数据再追加一个新的Token
      • 区别:Prefill是把整个Prompt一次性的输进模型计算一个完整的context。从prompt进入模型到cache写入第一个token,这部分的延迟叫做time to first token,如果prompt比较长,即延迟增长。Decode是后续的计算,每次只生成一个token,因为有了cache,计算量较小,远小于Prefill延迟,这里的延迟叫做time to incremental token
  2. Parallelism(并行化)

关于PB的更多优化方案

batching.png

有一批任务使用continous batching执行Decode任务,有一个任务结束,生成一个EOS(end of sequential)的token,接下来把一个Prefill任务添加进去。如果Prefill处理的prompt很长,使用chunked Prefill,把Prefill切成若干的chunk,将一个长的Prefill变为一个可中断可恢复的迭代任务,减少别的任务的等待时间(类似于操作系统的抢占机制,牺牲单个任务的处理速度,提高整体的响应速度)

PD分离(PB separation)

PB separation.png 分布式思想,同一个节点内部不同GPU上,或者跨节点

可以分别优化这两个阶段不同的节点

例如在Decode node启动cuda graph(减少CPU的开销,因为GPU执行的任务是由CPU派发的,GPU会等待CPU发布任务,cuda graph会把这个流程capture(录制)下来形成计算图,通过重放来快速执行,压缩整个通信的次数)

问题:跨界点的传输是有开销的,取决于KV cache的大小

减小KV cache的体积

  • 量化(quqatization):一个浮点型32bit,使用更小的空间来表示,代价是精度上会有损失
  • 分页(page attentation和page flash-attentation):page flash-attentation用来减少计算的时候 SRAM 与 HBM之间的内存访问,page attentation用来减少KV存储的碎片。在 vllm 中 flash paged attention kernel,在每次的传输中只传输 page table 和活跃的 block,不活跃的page 可以进行延迟拉锯(操作系统的分页)
  • 去重(prefix sharding):有多条请求,然后多条请求的前缀是相同的,KV可以只传一次,可以把这个KV持久化到一个地方(例如内存,内存可以达到2T)。当发现某一个请求和已经持有的某一段KV cache的前缀是相同的,那只需要传输部分不同的差量页,减少搬运体积。不好的点是增加开销,KV cache要保存到本地的内存,要在上游添加一个smart router,然后根据请求里的前缀去查找一下之前是否有相似的KV cache,将这个请求转发到相应的机器
  • 压缩(compression):稀疏化,裁剪一些token或header,sliding window attention、流式编码等很多方案

加快传输速度

  • 硬件层面(不关心)

streaming pipeline

  • 前面提到的 chunk prefill的调度(还没具体了解)

并行化:为了解决结构性问题,即设备的容量,模型占用的内存比这个显卡的内存大

  • traning 阶段

    • 张量并行
    • 数据并行
    • 流水线并行
  • 推理阶段

    • 张量并行 (tensor parallelism,太复杂了不想描述,学好线性代数)

      • 把一张GPU放不下的计算放到多个GPU上,本质是拆分成一个矩阵然后计算完再进行聚合
    • 专家并行(export paralleism,deepseek论文):有负载均衡问题,部署动态路由

    • 上下文并行(context parallelism,太复杂了不想描述)

quotation

medium.com/@joaolages/…

zhuanlan.zhihu.com/p/224361144…

DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning:arxiv.org/abs/2501.12…

DeepSeek-V3 Technical Report:arxiv.org/abs/2412.19…

DeepSeek LLM: Scaling Open-Source Language Models with Longtermism:arxiv.org/abs/2401.02…