如何把未量化的 70B 大模型加载到笔记本电脑上运行?
- 编辑:admin - 点击数:246如何把未量化的 70B 大模型加载到笔记本电脑上运行?
并行运行70B大模型
我们已经看到,量化已经成为在低端GPU(比如Colab、Kaggle等)上加载大型语言模型(LLMs)的最常见方法了,但这会降低准确性并增加幻觉现象。
那如果你和你的朋友们把一个大型语言模型分着用呢?
每台笔记本的GPU负责一部分,这样大家一起推理和处理任务就容易多了。
我们要用Petals来做到这一点,把我们的大模型分布式加载到你朋友或家人那里的多个GPU上,它们会一起托管我们的模型。
LLMs是怎么分布式的
在我们开始写代码之前,需要先理解一下分布是怎么进行的,要理解这个,用一张图来直观地展示是最合适的方式。
70B大模型并行示意图(由FareedKhan绘制)
每个LLM都是由很多block(模块)组成的,它们彼此是独立的。
比如说LLaMA370B,它由好几个block组成,假设总共有10个block。我们可以让一台笔记本托管其中几个block,另一台托管另外几个,以此类推。
这样,多个block被分别托管了起来,就可以一起拿来推理使用了。
我们还能更进一步,比如用量化的方法来托管更大的LLM,比如405B参数的LLaMA,这样就能访问更大型的模型,而不需要付费API或GPU使用时间。
目录
•设置环境
•检查可用GPU
•创建分布式模型
•在分布式模型上贪婪推理
•正确地生成Token
•模型长什么样
•PromptTuning(提示词微调)
•微调可训练的适配器
•采样方法
•私人Swarm网络
•关键结论
设置环境
确保你的环境里安装了支持CUDA的PyTorch。你可以从这里查看最新版。我们先安装带CUDA支持的PyTorch。
从源代码安装Petals
pipinstallgit+
安装会花点时间,等依赖装完之后,我们就来检查一下可用的GPU。
检查可用GPU
为了模拟一群人一起合作的场景,我用Colab、Kaggle和创建了好几个基于GPU的Jupyter笔记本,包括我本地的GPU,用来做推理用。
来看一下我这边运行(FP16)70BLLM的可用GPU:
可用GPU(由FareedKhan绘制)
我用不同的临时账号,在同一台服务器上开了多个笔记本。所以如果把总GPU内存加在一起,大概有(~170GBRAM)。
当然了,如果你目标是8B未量化的LLM,肯定用不了这么多笔记本。
接下来,咱们就要开始把LLM的workload(block)分布到不同的GPU上了。
创建分布式模型
想要把一些block推送到某个GPU上,只要在对应GPU的笔记本上运行这个命令,就能开始分布式加载LLM。我们用70BLLM来操作:
指定HuggingFaceHub上的模型名字(这里是LLaMA70B)
model_name="meta-llama/"
加载用于因果语言建模的分布式模型
model=_pretrained(model_name)
将输入文本进行token化并移动到GPU
inputs=tokenizer('Aboyisriding"',return_tensors="pt")["input_ids"].cuda()
解码并打印生成的输出
print((outputs[0]))
输出
Aboyisridinghouseand
正确地生成Token
如果想让模型像聊天机器人一样实时互动,我们可以用推理会话(inferencesession)接口。这样生成的token会实时显示,非常适合做聊天机器人。
推理会话工作流(由FareedKhan绘制)
这个会话会找到服务器来执行每一步,并存储注意力缓存(attentioncaches),所以你不需要每次都重新处理之前的token。
如果有服务器掉线,Petals会快速找到替代节点,只会重新生成一小部分缓存,非常高效。
来看一下,怎么一边生成一边实时显示:
定义初始的文本提示词
text="Whatisagoodchatbot?Answer:"
开始一个推理会话,最多生成30个token
_session(max_length=30)assess:
foriinrange(20):只在第一次生成时提供prefix
inputs=prefixifi==0elseNone
解码新生成的token,并追加到文本后面
text+=([fake_token,outputs[0,-1].item()])[1:]
开启一个推理会话,最多生成512个token
_session(max_length=512)assess:
whileTrue:
如果输入为空,退出循环
ifprompt=="":
break
把输入token化并移动到GPU
prefix=tokenizer(prefix,return_tensors="pt")["input_ids"].cuda()
print("FrilyAI:",="",flush=True)
whileTrue:
解码新生成的token,同时保留前导空格
outputs=([fake_token,outputs[0,-1].item()])[1:]
如果检测到换行或结束标记,则退出
if"\n"inoutputsor"/s"inoutputs:
break
打印模型架构
print(model)
输出
DistributedLlamaForCausalLM(
(model):DistributedLlamaModel(
(embed_tokens):Embedding(32000,8192,padding_idx=0)
(layers):RemoteSequential(modules=.)
(norm):LlamaRMSNorm()
)
(lm_head):LMHead()
)
词嵌入(wordembeddings)和一部分层是作为普通的PyTorch模块在本地跑的,剩下的模型部分(像Transformerblocks)是通过RemoteSequential类跑在其他机器上的。
RemoteSequential是一个特别的PyTorch模块,可以让模型分布式执行。
我们还能访问到单独的层,查看它们的输出,甚至能单独执行前向(forward)或反向(backward)传播。
比如提取前5层:
显示提取出来的层(打印它们的细节或结构)
first_five_layers
输出
RemoteSequential(modules=.)
它打印出来了托管在其他GPU上的前5个block,你可以直接看到。
PromptTuning(提示词微调)
远程托管的transformerblocks是冻结的(frozen),为了保证预训练模型在所有用户之间保持一致。
不过我们还是可以通过参数高效的方法(比如可训练的prompt或适配器LoRA)来微调。
因为所有可训练参数和优化器都保存在本地,所以不会影响到其他用户。
在这个例子里,我们要用可训练的prompt来做一个简单任务:让模型学会把一句话变成它的反义句。
比如:
从“Asmallwhiterabbithopsacrossthegreenfield.”
变成
“Asmallwhiterabbitdidnothopacrossthegreenfield”。
首先来看一下在没有微调前,模型怎么回答:
用模型生成最多7个新token
outputs=(inputs,max_new_tokens=7)
用deepprompttuning加载预训练的因果语言模型
model=_pretrained(model_name,tuning_mode='deep_ptune',pre_seq_len=3)
用学习率0.001初始化Adam优化器
opt=((),lr=1e-3)
训练12步
foriinrange(12):
清空前一次的梯度
_grad()
优化器更新参数
()
print("()")
输出
loss[0]=5.324
()
loss[1]=4.983
()
loss[2]=4.512
()
loss[3]=4.217
()
loss[11]=0.014
()
当loss几乎降到0时,模型就学会了我们要它生成的句子。
再来测试一下:
生成最多7个新token
outputs=(inputs,max_new_tokens=7)
加载一个预训练的因果语言模型,并移动到GPU
model=_pretrained(model_name)
model=()
提取模型的transformer层,用于分布式处理
_layers=
分类头,把隐藏状态映射成2个类别
=(_size,2)
defforward(self,embeddings):
经过适配器进行转换
hidden_states=(hidden_states)
对序列做均值池化(meanpooling),得到固定大小的表示
pooled_states=(hidden_states,dim=1)
初始化LLMBasedClassifier并移动到GPU
classifier=LLMBasedClassifier(model).cuda()
创建虚拟输入数据(3个样本,每个2个token,隐藏层大小)
inputs=(3,2,_size,device='cuda')
训练5次迭代
foriinrange(5):
清空之前的梯度
_grad()
用优化器更新参数
()
初始化logger,用来在每一步打印日志
logger=get_logger()
定义输入提示词
text="HowcanIimprovemywritingskills?Answer:"
token_ids=tokenizer(text,return_tensors="pt")["input_ids"].cuda()设置生成文本的最大长度
max_length=100
开启推理会话
_session(max_length=max_length)assess:
获取token的词嵌入
embs=_embeddings(token_ids)
embs=_embeddings_layernorm(embs)
取最后一个隐藏状态
logits=_head(h_last)贪婪地选择概率最高的token
next_token=(dim=-1)
更新token_ids,准备下一步生成
token_ids=next_(1,1)
启动一个bootstrappeer,监听特定端口(比如31337)
把bootstrappeer的地址存到变量里
exportMY_INITIAL_PEERS="/ip4/YOUR_PUBLIC_IP_OR_LAN_IP/tcp/31337/p2p/QmTPAIfTh1sIsMyUnique1DDontCopyThisPart"
你的bootstrappeer的地址(可以是列表)
INITIAL_PEERS=["/ip4/YOUR_PUBLIC_IP_OR_LAN_IP/tcp/31337/p2p/QmTPAIfTh1sIsMyUnique1DDontCopyThisPart"]
正常加载tokenizer
tokenizer=_pretrained(model_name,use_fast=False,add_bos_token=False)
--重点就在这!
)
然后就可以像之前一样用()或推理会话啦!
outputs=(inputs,max_new_tokens=5)
#print((outputs[0]))
就这样!
你现在就在自己的一小圈电脑里,私下运行超大LLM了。
不再依赖公共网络,完全自主可控。
甚至可以搭建自己的健康监控页面,实时查看你们Swarm的状态!
你可以通过阅读Petals的官方文档来进一步掌握更多高级用法哦。
关键结论
你已经看到了,prompt模板和托管block的数量是影响最终效果的两个关键因素;
否则LLM的性能可能达不到预期水平。
你还可以用量化技术来加载更大的LLM。
Petals支持很多参数配置,比如指定要托管哪些block,避免重复上传。
我个人建议你可以先托管一个8B的LLM,测试一下它的表现,
肯定会让你大吃一惊的!