Pytorch-llama项目(kv cache)
2025-1-17
| 2026-4-2
Words 1606Read Time 5 min
type
Post
status
Published
date
Jan 17, 2025
slug
summary
tags
category
icon
password
😀
前言: llama系列属于半开源产品,什么是半开源,也就是只开源了推理流程,没有开源训练流程
通过pytorch-llama项目入门大模型 github地址:https://github.com/hkproj/pytorch-llama 模型下载地址:https://huggingface.co/meta-llama/Llama-2-7b/tree/main
从中可以学到 1.大模型推理流程 2.旋转位置编码 3.kv cache,学习为什么需要kv cache,以及为什么cache kv,而不cache q
学习文章
等入门大模型需要学习的知识以及细节等
模型参数:
propmts长度为4
max_gen_len最大生成长度:64
top_p: 0.9
temperature: 0.6

推理流程

  • 创建一个size为(4,64+max(tokenizer.encode(prompts))=115)的Tensor(tokens)用于存放模型生成的token_id,默认填充都是-1。tokens先将promtp填充进去。
  • 创建一个长度114,下标从1开始,模型输入是(4,1),然后遍历cur_iterator,一个一个的生成下一个token,模型每次输出(4,1,32000)size的logitscur_iterator = tqdm(range(1, total_len), desc="Generating tokens")
    • if temperature>0: (top_p参数是有用的)
      logits 进行softmax,得到(4,32000)size的probs。
      top_p进行采样,采样逻辑如下:
      1. 对probs进行降序排序,得到概率值降序列表和对应的下标列表。
      notion image
      1. 将每个概率值与前面的所有概率和进行累加,得到probs_sum
      notion image
      1. 将 probs_sum - probs_sort > p 的所有概率置为0
      1. 将每个概率除以概率和,使得概率和还是1
      1. 从中随机选择一个下标
      1. 然后根据probs_ids得到对应的token。size为(4,1)
      理解一下top_p的作用,也就是将前n个概率相加,直到遇到再加上一个概率大于top_p,那么样本集就是这n个概率,top_p设置的越大,样本集中的token个数越多,反之越小。
      else: (top_p参数无用)
      直接从logits选择最大数值对应的下标
  • 替代next token的时候需要判断一下,tokens这个位置上是原本的就有内容(prompt)的还是pad_id,如果是pad_id则进行替换
  • 判断是否都到了eos_token,如果都到了,则结束循环
  • 结束循环后,判断是不是有eos_id,如果有则将eos_id前面的内容打印出来
 

模型推理细节

输入输出

是一个token一个token的进行推理,模型输入是(4,1),模型输出是(4,1,32000),32000就是vocab_size。模型输入的时候额外传入一个start_pos,从1开始,生成第一个token传入的就是1,从2开始传入的就是2
notion image
  1. tokens经过nn.Embedding变成h,h的size是(4,1,4096)
  1. 从提前计算好的复数频率张量中,根据start_pos选取一行,第一次是1,那么就选择第一行,
notion image
  1. 核心:self_attention层
    1. 传入self_attention进行计算,三个入参,x(tokens),start_pos, freqs_complex
      计算xq, size(4,1,4096)
      计算xk, size(4,1,4096)
      计算xv, size(4,1,4096)
      分多头:
      xq (4,1,32,128),32✖️128=4096
      xk (4,1,32,128)
      xv (4,1,32,128)
      应用旋转位置编码:xq不应用
      xk (4,1,32,128)
      xv (4,1,32,128)
      kv cache
      self.cache_k和self.cache_v都是(4,1024,32,128),初始值都是0
      notion image
      self.cache_k[:batch_size, start_pos : start_pos + seq_len] = xk
      self.cache_v[:batch_size, start_pos : start_pos + seq_len] = xv
      self.cache_k[:4,1:2] = xk
      self.cache_v[:4,1:2] = xv
      从缓存中取值:取前start_pos+1对应的值,seq_len永远是1,因为是一个token一个token生成的
      keys = self.cache_k[:batch_size, : start_pos + seq_len]
      values = self.cache_v[:batch_size, : start_pos + seq_len]
      notion image
      notion image
      注意力计算
      xq 与 keys进行计算得到scores
      scores与values计算得到output

kv cache总结

每次遍历的时候计算的xk,xv存入cache_k,cache_v列表中,然后将之前所有的计算好的取出来的到keys,values,然后参与后续的注意力计算。
花了两天理解了为什么模型推理的时候每次只喂一个token,我的理解是将所有的token都喂给模型,这样模型才能根据所有的上文语义信息预测下一个token啊,如果只给模型喂一个token,那么怎么得到next_token呢,我对这个产生了疑问。
如果模型的输入是(batch_size, seq_len=12, dim),那么模型的输出也是这个,相当于预测了12个token,前11个token都是重复计算的,然后只取最后一个token就是模型预测出来的下一个token的结果,这个是我能理解的方式,模型训练也是这么训练的。
如果模型的输入是(batch_size, seq_len=1, dim)那么模型的输出也是(batch_size, seq_len=1, dim),只喂给了模型一个token,模型能get到上文的语义信息吗。
那么这两种计算方式到底等不等价。答案是结合kv cache这两种方式是等价的。
通过scores*v得出来的注意力矩阵,得到模型输出
如果喂给模型所有上下文信息(batch_size, seq_len=12, dim),那么scores*v得出来的注意力矩阵也是这个,然后通过一个Linear层得到模型输出是(batch_size, seq_len=12, vocab_size),假设得到了next_token,那么下一次喂给模型的就是(batch_size, seq_len=13, dim),那么模型输出就是(batch_size, seq_len=13, vocab_size),那么这个矩阵的第十三行也就是next_token的结果,根据矩阵运算,可以看出来,第十三行的结果,只与第十二个token计算出来的q,以及前12个k,v有关,所有每次只喂给模型一个token就行,只要前面计算出来的kv都缓存下来,那么就不影响最终的结果。
这时候在看那篇知乎文章就理解的很通透了。
 
 
 
💡
有问题,欢迎您在底部评论区留言,一起交流~
vllm模型部署大模型做聚类的一些思考
Loading...