9 月 25 日 Meta 发布了 Llama 3.2,包括 11B 和 90B 的视觉语言模型。
Llama 3技术剖析与部署
相较于Llama2, Llama3的改变其实并不是太大。 Tokenizer变成了128K的词表,使用了GQA,不在是原来的只在Llama2-70B里使用了。所以说,最大的改变就是词表与GQA。然后在训练上变化比较大,参数变大了,400B的还没放出来。但它的训练是变化很大的。训练数据明显比过去多,15T的token数差不多是Llama2的四倍多,而且这个数据的质量明显要高呢。
新的数据过滤器,幻觉、NSFW、语义重复、分类这些都有提高或者新增。
预训练的数据也在变好,再加上适当的优化技术。
所以真的结构上的改变是GQA。
那看一下GQA是咋回事吧。 一切的改进都是针对Transformer的QKV这块来的。
原始的是这样的,每一个QKV的计算都是分别进行的,这样QKV的计算其实要有大量的计算。基本上是一个下面的计算
考虑到这样一个大的计算会有非常大的计算量,于是把这个Attention拆成了小块,然后连接在一起。也就是下面的MHA其实也是一种优化,但是很明显,在LLM变成极大后,这个东西还要再优化,因为它还是消耗了太大的计算量。
于是最早有人想优化成MQA那种,就是一个K、一个V对应所有的Q,这种方式确实很容易扩大QKV的参数规模,同时非常有效的减少KV缓存。但是因为这个参数它太共同了,所以后期要在FFN那块增加相应的参数才成。效果也没有证明好了。
于是又有了更优的版本:GQA,所有的优化都是“中庸”嘛,中不偏,庸不易。
于是GQA呢,是简单的把MHA分成几组,每组共用一个就好了。从Llama2-70b,还有Llama3全系来看呢,GQA这个确实是在性能与KV缓存的显存占用上获得了很好的平衡。
谈到这儿,你有没有发现现在讲Llama3,其实是有很多基础的东西,如attention, mha, ffn一类的东西,你可能有些模糊了呢?我非常建议有举的人都去听听由知乎知学堂开设的这门AI大模型公开课,课程带你学习GPT背后的技术原理,LangChain、Fine-tune技术,从理论实践,到深度讲解,带你全程体验微调过程,定制属于自己的大模型。课上还能直接对话AI技术大佬,现场答疑,非常不错。这样你能掌握一套更全面的知识了。
既然这套东西没有什么大的改动,那我们就看看怎么用吧。
通常我们可以这样认为 Prompt是要优先RAG,RAG是要优先微调Finetune的。我们就从部署一个Llama3开始吧。看看怎么用Prompt,再怎么用RAG,再怎么微调Finetune它。
模型基本使用方法
这次我们搞个接地气的,直接用一个大家想不到的模型:中国联通AI创新中心微调的“Unichat-llama3-Chinese-8B”,看看联通的实力如何。毕竟我们用中文更容易看到效果。
运行代码很简单:
import transformers
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
model_id = "UnicomLLM/Unichat-llama3-Chinese-8B" #可以是本地路径
pipeline = transformers.pipeline(
"text-generation",
model=model_id,
model_kwargs={"torch_dtype": torch.bfloat16},
device="cuda",
)
messages = [
{"role": "system", "content": "A chat between a curious user and an artificial intelligence assistant.The assistant gives helpful, detailed, and polite answers to the user's questions."},
{"role": "user", "content": "你是谁"},
]
prompt = pipeline.tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
terminators = [
pipeline.tokenizer.eos_token_id,
pipeline.tokenizer.convert_tokens_to_ids("<|eot_id|>")
]
outputs = pipeline(
prompt,
max_new_tokens=2048,
eos_token_id=terminators,
do_sample=False,
temperature=0.6,
top_p=1,
repetition_penalty=1.05
)
print(outputs[0]["generated_text"][len(prompt):])
理论上你就能运行了,如果不缺支持库(24G显存的运行会顺畅些)
Rag
这事吧,其实也不难,现在的框架已经很丰富了。我们举个例子吧。
通常langchain rag的流程如下
代码吧,搞一份供参考吧:
from langchain.llms.base import LLM
from typing import Any, List, Optional
from langchain.callbacks.manager import CallbackManagerForLLMRun
from transformers import AutoTokenizer, AutoModelForCausalLM, RagTokenizer, RagRetriever, RagSequenceForGeneration
import torch
class LLaMA3_LLM(LLM):
tokenizer: AutoTokenizer = None
model: AutoModelForCausalLM = None
rag_tokenizer: RagTokenizer = None
rag_retriever: RagRetriever = None
rag_generator: RagSequenceForGeneration = None
def __init__(self, mode_name_or_path :str, rag_name_or_path: str):
super().__init__()
print("正在从本地加载模型和RAG组件...")
self.tokenizer = AutoTokenizer.from_pretrained(mode_name_or_path, use_fast=False)
self.model = AutoModelForCausalLM.from_pretrained(mode_name_or_path, torch_dtype=torch.bfloat16, device_map="auto")
self.tokenizer.pad_token = self.tokenizer.eos_token
# 初始化RAG组件
self.rag_tokenizer = RagTokenizer.from_pretrained(rag_name_or_path)
self.rag_retriever = RagRetriever.from_pretrained(rag_name_or_path)
self.rag_generator = RagSequenceForGeneration.from_pretrained(rag_name_or_path)
print("完成本地模型和RAG组件的加载")
def bulid_input(self, prompt, history=[]):
user_format='user\n\n{content}'
assistant_format='assistant\n\n{content}'
history.append({'role':'user','content':prompt})
prompt_str = ''
# 拼接历史对话
for item in history:
if item['role']=='user':
prompt_str+=user_format.format(content=item['content'])
else:
prompt_str+=assistant_format.format(content=item['content'])
return prompt_str
def _call(self, prompt : str, stop: Optional[List[str]] = None,
run_manager: Optional[CallbackManagerForLLMRun] = None,
**kwargs: Any):
input_str = self.bulid_input(prompt=prompt)
input_ids = self.tokenizer.encode(input_str, add_special_tokens=False, return_tensors='pt').to(self.model.device)
# 使用RAG模型进行检索
with torch.no_grad():
retrieved = self.rag_retriever(input_str, return_tensors="pt")
generated = self.rag_generator(input_ids=retrieved['retrieved_indices'], attention_mask=retrieved['retrieved_attention_mask'])
# 检查是否成功生成了回复
if generated is None:
return "Sorry, I couldn't generate a response."
outputs = generated.sequences.tolist()[0][len(input_ids[0]):]
response = self.tokenizer.decode(outputs).strip().replace('', "").replace('assistant\n\n', '').strip()
return response
@property
def _llm_type(self) -> str:
return "LLaMA3_LLM"
from LLM import LLaMA3_LLM
llm = LLaMA3_LLM(mode_name_or_path = "/root/autodl-tmp/LLM-Research/Meta-Llama-3-8B-Instruct")
llm("你是谁")
微调
微调这事,其实主要还是看数据,太少了你也不要有太大期望。不过参考代码还是可以给一下。
首先你的数据是这样的(多个重复):
{
"instruction": "回答以下用户问题,仅输出答案。",
"input": "1+1等于几?",
"output": "2"
}
然后代码差不多是这样的(这次从modelscope下载, 期望你能知道安装相应的库)
import torch
from modelscope import snapshot_download, AutoModel, AutoTokenizer
import os
model_dir = snapshot_download('LLM-Research/Meta-Llama-3-8B-Instruct', cache_dir='/root/autodl-tmp', revision='master')
def process_func(example):
MAX_LENGTH = 384 # Llama分词器会将一个中文字切分为多个token,因此需要放开一些最大长度,保证数据的完整性
input_ids, attention_mask, labels = [], [], []
instruction = tokenizer(f"<|start_header_id|>user<|end_header_id|>\n\n{example['instruction'] + example['input']}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n", add_special_tokens=False) # add_special_tokens 不在开头加 special_tokens
response = tokenizer(f"{example['output']}<|eot_id|>", add_special_tokens=False)
input_ids = instruction["input_ids"] + response["input_ids"] + [tokenizer.pad_token_id]
attention_mask = instruction["attention_mask"] + response["attention_mask"] + [1] # 因为eos token咱们也是要关注的所以 补充为1
labels = [-100] * len(instruction["input_ids"]) + response["input_ids"] + [tokenizer.pad_token_id]
if len(input_ids) > MAX_LENGTH: # 做一个截断
input_ids = input_ids[:MAX_LENGTH]
attention_mask = attention_mask[:MAX_LENGTH]
labels = labels[:MAX_LENGTH]
return {
"input_ids": input_ids,
"attention_mask": attention_mask,
"labels": labels
}
tokenizer = AutoTokenizer.from_pretrained('/root/autodl-tmp/LLM-Research/Meta-Llama-3-8B-Instruct', use_fast=False, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained('/root/autodl-tmp/LLM-Research/Meta-Llama-3-8B-Instruct', device_map="auto",torch_dtype=torch.bfloat16)
config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"],
inference_mode=False, # 训练模式
r=8, # Lora 秩
lora_alpha=32, # Lora alaph,具体作用参见 Lora 原理
lora_dropout=0.1# Dropout 比例
)
args = TrainingArguments(
output_dir="./output/llama3",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
logging_steps=10,
num_train_epochs=3,
save_steps=100,
learning_rate=1e-4,
save_on_each_node=True,
gradient_checkpointing=True
)
trainer = Trainer(
model=model,
args=args,
train_dataset=tokenized_id,
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
)
trainer.train()
lora_path='./llama3_lora'
trainer.model.save_pretrained(lora_path)
tokenizer.save_pretrained(lora_path)
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
from peft import PeftModel
mode_path = '/root/autodl-tmp/LLM-Research/Meta-Llama-3-8B-Instruct'
lora_path = './llama3_lora' # lora权重路径
# 加载tokenizer
tokenizer = AutoTokenizer.from_pretrained(mode_path)
# 加载模型
model = AutoModelForCausalLM.from_pretrained(mode_path, device_map="auto",torch_dtype=torch.bfloat16)
# 加载lora权重
model = PeftModel.from_pretrained(model, model_id=lora_path, config=config)
prompt = "你是谁?"
messages = [
# {"role": "system", "content": "现在你要扮演皇帝身边的女人--甄嬛"},
{"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
model_inputs = tokenizer([text], return_tensors="pt").to('cuda')
generated_ids = model.generate(
model_inputs.input_ids,
max_new_tokens=512,
eos_token_id=tokenizer.encode('<|eot_id|>')[0]
)
generated_ids = [
output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print(response)
多模态训练
可能还要等等,现在的llama3官方还没有提供400B的那个多模态版本。不过GitHub上有个llama-multimodal-vqa的项目,大概的方向是这样的,用一个CLIP把图像编码成了Token,再用Llama3训练一下。但是结果真的不太成啊。还是等官方400b吧。
如何学习大模型 AI ?
由于新岗位的生产效率,要优于被取代岗位的生产效率,所以实际上整个社会的生产效率是提升的。
但是具体到个人,只能说是:
“最先掌握AI的人,将会比较晚掌握AI的人有竞争优势”。
这句话,放在计算机、互联网、移动互联网的开局时期,都是一样的道理。
我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。
我意识到有很多经验和知识值得分享给大家,也可以通过我们的能力和经验解答大家在人工智能学习中的很多困惑,所以在工作繁忙的情况下还是坚持各种整理和分享。但苦于知识传播途径有限,很多互联网行业朋友无法获得正确的资料得到学习提升,故此将并将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。
第一阶段(10天):初阶应用
该阶段让大家对大模型 AI有一个最前沿的认识,对大模型 AI 的理解超过 95% 的人,可以在相关讨论时发表高级、不跟风、又接地气的见解,别人只会和 AI 聊天,而你能调教 AI,并能用代码将大模型和业务衔接。
- 大模型 AI 能干什么?
- 大模型是怎样获得「智能」的?
- 用好 AI 的核心心法
- 大模型应用业务架构
- 大模型应用技术架构
- 代码示例:向 GPT-3.5 灌入新知识
- 提示工程的意义和核心思想
- Prompt 典型构成
- 指令调优方法论
- 思维链和思维树
- Prompt 攻击和防范
- …
第二阶段(30天):高阶应用
该阶段我们正式进入大模型 AI 进阶实战学习,学会构造私有知识库,扩展 AI 的能力。快速开发一个完整的基于 agent 对话机器人。掌握功能最强的大模型开发框架,抓住最新的技术进展,适合 Python 和 JavaScript 程序员。
- 为什么要做 RAG
- 搭建一个简单的 ChatPDF
- 检索的基础概念
- 什么是向量表示(Embeddings)
- 向量数据库与向量检索
- 基于向量检索的 RAG
- 搭建 RAG 系统的扩展知识
- 混合检索与 RAG-Fusion 简介
- 向量模型本地部署
- …
第三阶段(30天):模型训练
恭喜你,如果学到这里,你基本可以找到一份大模型 AI相关的工作,自己也能训练 GPT 了!通过微调,训练自己的垂直大模型,能独立训练开源多模态大模型,掌握更多技术方案。
到此为止,大概2个月的时间。你已经成为了一名“AI小子”。那么你还想往下探索吗?
- 为什么要做 RAG
- 什么是模型
- 什么是模型训练
- 求解器 & 损失函数简介
- 小实验2:手写一个简单的神经网络并训练它
- 什么是训练/预训练/微调/轻量化微调
- Transformer结构简介
- 轻量化微调
- 实验数据集的构建
- …
第四阶段(20天):商业闭环
对全球大模型从性能、吞吐量、成本等方面有一定的认知,可以在云端和本地等多种环境下部署大模型,找到适合自己的项目/创业方向,做一名被 AI 武装的产品经理。
- 硬件选型
- 带你了解全球大模型
- 使用国产大模型服务
- 搭建 OpenAI 代理
- 热身:基于阿里云 PAI 部署 Stable Diffusion
- 在本地计算机运行大模型
- 大模型的私有化部署
- 基于 vLLM 部署大模型
- 案例:如何优雅地在阿里云私有部署开源大模型
- 部署一套开源 LLM 项目
- 内容安全
- 互联网信息服务算法备案
- …
学习是一个过程,只要学习就会有挑战。天道酬勤,你越努力,就会成为越优秀的自己。
如果你能在15天内完成所有的任务,那你堪称天才。然而,如果你能完成 60-70% 的内容,你就已经开始具备成为一名大模型 AI 的正确特征了。