揭示LLaMA 3对话能力的奥秘

人工智能
计算机科学家和法律专家在制定人工智能合规标准时进行了分工和协作。Agent A 提出技术实施的建议和解决技术难题,而 Agent B 负责评估合规性、提出法律建议和法规框架。这种分工模式能有效结合技术实施和法律法规,以确保人工智能技术的合法合规性。

过去的一年中,大模型技术得到了广泛认可,全行业对大模型的投入也在不断增加。开源社区涌现了许多优秀的模型和框架,推动了大模型技术的普及和应用。在这一年的时间里,LLaMA 系列模型也经历了快速的发展,从 LLaMA 2 到 LLaMA 3,我们看到了性能和应用上的显著提升。

基本操作:生成内容

首先,让我们来了解一下 LLaMA 3 的核心能力。LLaMA 3 主要依赖于 Next Token Prediction(下一个词预测)机制,通过预测下一个词来生成连贯的对话。这种机制基于海量文本数据的训练,使模型能够捕捉语言的模式和规律,生成符合上下文逻辑的文本内容。

Next Token Prediction

Next Token Prediction 是大语言模型生成文本的基础。模型处理输入文本的步骤如下:

标记化:首先,大模型将输入文本分解成一系列的 token(词或子词)。例如,句子“请解释一下黑洞的形成”可能被分解为以下 token:

["请", "解释", "一", "下", "黑", "洞", "的", "形", "成"]

文字表征:接下来,将这些 token 转换为模型能够理解的数值形式(通常是嵌入向量)。

概率预测:大模型会根据当前的输入序列计算下一个词的概率分布。这些概率分布表示下一个词的可能性,例如:

{"黑洞": 0.1, "形成": 0.05, "是": 0.2, "由于": 0.15, ...}

生成文本:根据概率分布选择具体的一个词作为下一个词。选择方式可以是贪婪搜索(选择概率最大的词)、随机采样(根据概率分布随机选择)或其他搜索策略(比如 Beam Search)。例如,使并将其用贪婪搜索选择概率最大的词“是”,添加到已生成的文本序列中,重复上述步骤。

[“请”, “解释”, “一”, “下”, “黑”, “洞”, “的”, “形”, “成”, “是”]。

因此,LLaMA 3 模型的推理过程是一个循环:通过预测下一个词,将其加入到序列中,再预测下一个词来生成连贯的文本。这种循环造成了大量的模型推理算力开销,这也是为什么 OpenAI 等公司在 API 使用中根据 Token 计费。

接下来,我们通过一个具体的代码示例来演示 LLaMA 3 的文本生成过程。假设我们有一个文章开头:

在一个阳光明媚的早晨,Alice 决定去森林里探险。她走着走着,突然发现了一条小路。

我们使用 LLaMA 3 来续写这段话:

import torch
from modelscope import snapshot_download
from transformers import AutoModelForCausalLM, AutoTokenizer


# 下载模型
cache_dir = './llama_cache'
model_id = snapshot_download("LLM-Research/Meta-Llama-3-8B", cache_dir=cache_dir)
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM


# 加载分词器和模型
cache_dir = './llama_cache'
model_path = cache_dir + '/LLM-Research/Meta-Llama-3-8B'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
    device_map="auto" if torch.cuda.is_available() else None
)


# 编码输入并将其移至模型设备
input_text = "在一个阳光明媚的早晨,Alice决定去森林里探险。她走着走着,突然发现了一条小路。"
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)


# 生成并解码文本
with torch.no_grad():
    outputs = model.generate(**inputs)
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)
print(generated_text)

输出结果:

这条小路被两旁茂密的树木掩映着,似乎通向森林深处。Alice 决定沿着小路前进,想看看尽头有什么惊喜等着她。

在这个例子中,LLaMA 3 成功地续写了文章内容,我们可以看到 LLaMA 3 流畅地生成了具有创意性的内容,这个生成的过程就是刚刚提到的 Next Token Prediction  循环所形成的。

停止条件

你可能会问,Next Token Prediction 的方式岂不是会一直输出内容不停止?很好的问题!现在我们来聊聊 LLaMA 3 停止输出的条件,LLaMA 3 的输出由以下几个因素控制:

最大长度(max_length):这是最常用的控制方式之一。在初始化模型时,你可以指定一个最大长度值。当模型生成的文本长度达到该值时,它就会停止输出。

例如,如果将最大长度设置为 100,那么模型生成的文本最长不会超过 100 个词。

from transformers import AutoTokenizer, AutoModelForCausalLM


cache_dir = './llama_cache'
model_path = cache_dir + '/LLM-Research/Meta-Llama-3-8B'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
    device_map="auto" if torch.cuda.is_available() else None
)


max_length = 50
input_text = "写一首关于爱情的诗"
encoded_input = tokenizer(input_text, return_tensors="pt")
output = model.generate(encoded_input.input_ids, max_length=max_length)
print(tokenizer.decode(output[0], skip_special_tokens=True))

其他停止条件:一些模型还支持其他停止条件。例如检测到重复的文本、低质量的文本等。

LLaMA 3 可以检测到重复的文本并将其跳过。

from transformers import AutoTokenizer, AutoModelForCausalLM


cache_dir = './llama_cache'
model_path = cache_dir + '/LLM-Research/Meta-Llama-3-8B'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
    device_map="auto" if torch.cuda.is_available() else None
)


repetition_penalty = 1.2
input_text = "写一首关于爱情的诗:"
encoded_input = tokenizer(input_text, return_tensors="pt")
output = model.generate(encoded_input.input_ids, repetition_penalty=repetition_penalty)
print(tokenizer.decode(output[0], skip_special_tokens=True))

其他停止条件:一些模型还支持其他停止条件。例如检测到重复的文本、低质量的文本等。

LLaMA 3 可以检测到重复的文本并将其跳过。

from transformers import AutoTokenizer, AutoModelForCausalLM


cache_dir = './llama_cache'
model_path = cache_dir + '/LLM-Research/Meta-Llama-3-8B'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
    device_map="auto" if torch.cuda.is_available() else None
)


repetition_penalty = 1.2
input_text = "写一首关于爱情的诗:"
encoded_input = tokenizer(input_text, return_tensors="pt")
output = model.generate(encoded_input.input_ids, repetition_penalty=repetition_penalty)
print(tokenizer.decode(output[0], skip_special_tokens=True))

这些停止条件确保模型生成的文本不会无限延长,可以通过设置不同的参数来控制生成过程。

无状态到有状态:对话能力

对话生成不仅需要理解上下文,还得保持连贯性。因此,我们首先要解决的问题,就是大模型服务“无状态”的问题。为了让“无状态”的 LLaMA 3 模型具备对话能力,我们可以将先前的“历史会话”作为当前输入的一部分。这样可以保持上下文的连贯性,使模型成为一个“有状态”的服务,从而准确地生成响应。

以下是实现这一功能的代码示例:

import torch
from modelscope import snapshot_download
from transformers import AutoModelForCausalLM, AutoTokenizer


# 下载模型
cache_dir = './llama_cache'
model_id = snapshot_download("LLM-Research/Meta-Llama-3-8B-Instruct", cache_dir=cache_dir)
from transformers import AutoTokenizer, AutoModelForCausalLM


cache_dir = './llama_cache'
model_path = cache_dir + '/LLM-Research/Meta-Llama-3-8B-Instruct'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
    device_map="auto" if torch.cuda.is_available() else None
)


# 初始化对话历史
dialogue_history = [
    "Customer: Hi, I have an issue with my order.",
    "Support: Sure, could you please provide your order number?",
    "Customer: Sure, it's #12345.",
    "Support: Thank you. Let me check the status for you.",
]


# 合并对话历史为一个字符串
dialogue_history_text = "\n".join(dialogue_history)


# 添加用户输入,模拟当前对话
user_input = "Customer: Can you please expedite the delivery?"
input_text = dialogue_history_text + "\n" + user_input


# 生成文本
input_ids = tokenizer.encode(input_text, return_tensors="pt")
outputs = model.generate(input_ids, max_length=100)
generated_text = tokenizer.decode(outputs[0], skip_special_tokens=True)


print("Generated Response:", generated_text)

这段代码演示了如何使用 LLaMA 3 模型生成对话响应。

首先,我们定义了模型和 tokenizer,并初始化了一个简单的对话历史列表。然后,将对话历史转换为单个字符串,并添加用户的当前输入。接下来,我们使用 tokenizer 对输入进行编码,然后通过模型生成响应文本。最后,解码生成的文本并打印出来。

这里为你带来这节课的第一个重点,你可能会发现,上面的示例使用了基础版模型,而下面的示例使用了 Instruct 版本的模型。这是为什么呢?这是因为这两个模型的目标不一样,为了服务不同的角色,每个版本 LLaMA 3 模型在训练数据和训练方法上有所不同,针对特定对象进行了微调,比如:

人类用户:通过指令微调,使模型更好地理解和响应人类指令。这种微调使模型能够处理更自然的语言输入,提供更准确和相关的回答。

检索系统:结合检索系统的微调方法,提升模型在特定领域的信息检索能力。通过实时检索最新的外部数据,模型可以提供更加准确和时效性强的回答。

智能体(多步推理):模型之间的协作与交互,通过互相微调提升整体智能水平。不同模型可以相互补充,共同完成复杂的任务,从而提高整体性能。

此外,选择不同版本的 LLaMA 3 模型还需要考虑多个维度。

场景数据:根据不同应用场景选择合适的指令微调数据,如语言、行业、文化等。不同领域的数据特点和需求不同,需要针对性地进行模型微调。

输入长度:不同版本的模型在输入长度上有所差异,我们需要根据具体应用需求选择适合的模型版本。一些应用场景可能需要处理较长的输入文本,因此选择支持较长输入的模型版本是必要的。

参数效率:通过微调降低模型的参数要求,例如使用更小的模型架构或优化算法。在资源有限的环境中,可以优化模型性能以满足需求。通过合理的参数配置,可以在性能和资源消耗之间找到平衡点。

量化程度:不同量化程度带来不同的性能提升和效果下降,需要在性能和效果之间找到平衡点。量化可以减少模型的计算和存储需求,但也可能影响生成效果,因此需要根据具体应用情况进行权衡。

这些考虑因素在选择和使用 LLaMA 3 模型时至关重要。随着后面课程的深入,我们将在示例中不断展开这些内容。

在解决了大模型的“无状态“问题之后,我们再来看另一个大模型的局限性,那就是训练数据时效性的问题。

封闭到开放:检索增强

在某些情况下,我们需要 LLaMA 3 训练后产生的最新事实。为了解决这个问题,我们可以结合检索系统,在生成过程中获取最新或特定领域的事实信息。

为了解决 LLaMA 3 无法提供最新的事实信息的问题,我们需要用到检索增强生成(RAG)方法。简单来说,RAG 就是结合外部知识库或 API 进行实时检索,并将检索到的内容通过提示语补充到模型的输入中。

这种方法可以显著提高生成内容的准确性和时效性。例如,在对话过程中调用外部知识库,增强回答的准确性。以下是一个常见的示例实现步骤:

定义检索机制:首先,我们需要选择一个适当的外部知识库或 API,用于实时检索最新的事实信息。常见的选择包括搜索引擎 API、新闻 API、专门的行业知识库等。

集成检索系统:将外部检索系统集成到 LLaMA 3 的生成流程中。当模型生成初步响应后,调用检索系统获取最新的相关信息,并将检索到的信息整合到最终的响应中。

更新生成内容:利用检索到的最新信息,更新和完善模型的初步生成内容,确保回答的准确性和时效性。

相应的,以下是一个简单的代码示例,演示如何在生成过程中集成外部知识库进行实时检索和信息更新。

定义检索机制:在 retrieve_information 函数中,我们使用搜索引擎 API 根据给定的查询关键词检索最新的信息。

集成检索系统:在 generate_response 函数中,首先使用 LLaMA 3 生成初步响应,然后提取生成文本中的关键词,通过 retrieve_information 函数获取相关的最新信息。

更新生成内容:将检索到的信息整合到最终的响应中,生成更为准确和时效性的回答。

import requests
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch
from googleapiclient.discovery import build


cache_dir = './llama_cache'
model_path = cache_dir + '/LLM-Research/Meta-Llama-3-8B-Instruct'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
    device_map="auto" if torch.cuda.is_available() else None
)


# Google Custom Search API配置
API_KEY = 'YOUR_GOOGLE_API_KEY'
SEARCH_ENGINE_ID = 'YOUR_SEARCH_ENGINE_ID'


# 检索相关文档的函数
def retrieve_documents(query):
    try:
        service = build("customsearch", "v1", developerKey=API_KEY)
        res = service.cse().list(q=query, cx=SEARCH_ENGINE_ID).execute()
        results = res.get('items', [])
        documents = [item["snippet"] for item in results]
        return documents
    except Exception as e:
        print(f"Error retrieving documents: {e}")
        return []


# 生成答案的函数
def generate_answer(query, documents):
    # 限制检索到的文档数量
    documents = documents[:3]
    context = "\n\n".join(documents) + "\n\nQuestion: " + query + "\nAnswer:"
    # 编码输入
    inputs = tokenizer(context, return_tensors="pt", truncation=True, max_length=2048).to(model.device)
    # 生成答案
    outputs = model.generate(**inputs, max_length=512)
    # 解码答案
    answer = tokenizer.decode(outputs[0], skip_special_tokens=True)
    return answer


# 主函数
def main():
    query = "What is the capital of France?"
    documents = retrieve_documents(query)
    if documents:
        answer = generate_answer(query, documents)
        print("Question:", query)
        print("Answer:", answer)
    else:
        print("No documents retrieved.")


if __name__ == "__main__":
    main()
Question: What is the capital of France?Answer: The capital of France is Paris. It is known for its art, fashion, and culture, and is home to famous landmarks such as the Eiffel Tower and the Louvre Museum.

通过结合外部知识库或 API 进行实时检索和信息更新,我们可以有效解决 LLaMA 3 在生成过程中无法获取最新事实的问题。这种方法不仅提高了回答的准确性,还确保了内容的时效性。在后续课程中,我们将进一步探讨如何使用向量数据和混合检索及重排技术,来构建一个定制化的 RAG 引擎。

单体服务到微服务:多智能体

在单步推理方面,LLaMA 3 存在一定的局限性,例如长对话和职责混乱可能会影响模型的推理性能。针对这些问题,多智能体架构提供了一些优化策略。多智能体系统(MAS)通过将多个智能体组织起来协同工作,实现复杂任务的解决。

每个智能体都有特定的角色和功能,通过相互之间的通信和协作来达成共同目标。在 LLaMA 3 的应用中,我们可以利用多智能体系统的以下优点:

拆分对话:将单一对话拆分成多个独立的对话段,以减少对话长度对模型推理性能的影响。例如,在处理一个长篇对话时,可以将对话分成若干段,每段集中讨论一个具体问题。

明确职责:当单个模型需要处理过多问题时,可以通过拆分角色来明确每个模型的职责。例如,在一个复杂的对话系统中,可以设置不同的子模型分别处理用户意图识别、对话管理和应答生成等任务。

角色拆分:通过角色拆分,确保每个模型处理的内容单一而连贯,从而提升整体对话系统的性能和准确性。例如,可以设置一个专门处理技术问题的模型和一个专门处理日常对话的模型,分别负责不同类型的对话。

为了模拟一个计算机科学家(Agent A)和一个法律专家(Agent B)协作解决人工智能合规标准制定的问题,我们设计了如下复杂的任务和分工:

gent A(计算机科学家):负责提出技术实施的建议和技术难题的解决,熟悉人工智能技术和数据隐私。

# agent_a.py
from common import create_app


app = create_app("system: 你是一个熟悉人工智能技术的计算机科学家。")


if __name__ == '__main__':
    app.run(port=5000)

Agent B(法律专家):负责评估合规性、提出法律建议和法规框架,精通法律法规和数据隐私保护。

# agent_b.py
from common import create_app


app = create_app("system: 你是一个熟悉法律法规的法律专家。")


if __name__ == '__main__':
    app.run(port=5001)

Agent C(标准化专家):负责协调和整合建议,生成完整的技术合规方案。确保最终的合规方案完整和可实施。

# agent_c.py
from flask import Flask, request, jsonify
from transformers import pipeline
from transformers import AutoTokenizer, AutoModelForCausalLM


from langchain.agents import initialize_agent, Tool, AgentType
from langchain.llms import HuggingFacePipeline
import requests
import torch


app = Flask(__name__)


cache_dir = './llama_cache'
model_path = cache_dir + '/LLM-Research/Meta-Llama-3-8B-Instruct'
tokenizer = AutoTokenizer.from_pretrained(model_path)
model = AutoModelForCausalLM.from_pretrained(
    model_path,
    torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
    device_map="auto" if torch.cuda.is_available() else None,
    pad_token_id=tokenizer.eos_token_id
)


def call_expert(url, task_requirement):
    response = requests.post(url, json={"intent": task_requirement}, timeout=5)
    response.raise_for_status()
    return response.json().get("response", "Error: No response from expert")


ai_expert = lambda task: call_expert("http://localhost:5000/chat", task)
law_expert = lambda task: call_expert("http://localhost:5001/chat", task)


ai = Tool.from_function(func=ai_expert, name="ai_expert", description="当你需要人工智能专家知识时使用这个工具,输入为具体问题,返回为问题答案")
law = Tool.from_function(func=law_expert, name="law_expert", description="当你需要法律合规专家知识时使用这个工具,输入为具体问题,返回为问题答案")


tools = [ai, law]


pipe = pipeline(task="text-generation", model=model, tokenizer=tokenizer, max_new_tokens=512)
llm = HuggingFacePipeline(pipeline=pipe)


agent = initialize_agent(tools,
        llm,
        agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
        verbose=True,
        max_iterations = 5,
        handle_parsing_errors = True)


@app.route('/integrate', methods=['POST'])
def integrate():
    data = request.get_json()
    task = data.get('task', '')
    res = agent.run(task)
    return jsonify({'response': res})


if __name__ == '__main__':
    app.run(port=5002)

可以看出,在多智能体合作的时候,各个智能体在彼此的眼中都是工具。下面,通过 curl 命令向 http://localhost:5002/integrate 发送 POST 请求,任务描述为制定人工智能合规标准,避免人工智能伤害人类。

返回结果为标准化专家(Agent C)通过协调计算机科学家(Agent A)和法律专家(Agent B)的响应生成的综合答案。

$ curl -X POST http://localhost:5002/integrate \
       -H "Content-Type: application/json" \
       -d '{"task": "制定人工智能合规标准,避免人工智能伤害人类"}'

在这个设计示例中,计算机科学家和法律专家在制定人工智能合规标准时进行了分工和协作。Agent A 提出技术实施的建议和解决技术难题,而 Agent B 负责评估合规性、提出法律建议和法规框架。这种分工模式能有效结合技术实施和法律法规,以确保人工智能技术的合法合规性。

最后,我们展开介绍 common.py,这个函数接收模型目录和种子记忆(seed memory)作为参数,创建并返回一个 python 服务应用。

这里有一个重点,那就是我们要给每个 Agent 一个种子记忆,也就是它的全局人设,这是智能体应用中最重要的部分,因为如果没有一个坚固的人设,智能体在长期工作过程中一定会出现偏离,在后面课程中我将进一步解释这句话的含义。

# common.py
from flask import Flask, request, jsonify
from transformers import AutoTokenizer, AutoModelForCausalLM
import torch


def create_app(seed_memory):
    app = Flask(__name__)


    cache_dir = './llama_cache'
    model_path = cache_dir + '/LLM-Research/Meta-Llama-3-8B-Instruct'
    tokenizer = AutoTokenizer.from_pretrained(model_path)
    model = AutoModelForCausalLM.from_pretrained(
        model_path,
        torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
        device_map="auto" if torch.cuda.is_available() else None,
        pad_token_id=tokenizer.eos_token_id
    )


    # 定义聊天接口
    @app.route('/chat', methods=['POST'])
    def chat():
        data = request.get_json()
        intent = data.get('intent', '')


        # 构造提示词
        prompt = f"{seed_memory}\n请回答以下问题:{intent}"
        input_ids = tokenizer.encode(prompt, return_tensors="pt").to("cuda")
        outputs = model.generate(input_ids, max_length=150)
        response = tokenizer.decode(outputs[0], skip_special_tokens=True)


        return jsonify({'response': response})


    return app

当然本节课的例子都是为了让你可以快速产生感性认识,这种实现方式适用于简单的示例和小规模的应用,但在生产环境中则需要进一步地学习更复杂的方案。

责任编辑:武晓燕 来源: 二进制跳动
相关推荐

2018-07-05 09:35:53

百度AI系统蓝牙设备

2024-07-15 00:30:00

MetaAI 模型大语言模型

2024-02-17 21:50:30

2023-06-01 17:06:49

模型思维

2010-05-19 11:18:52

乔布斯盖茨

2011-09-15 09:52:45

Delphi XE2

2020-06-23 12:31:21

人工智能

2024-05-16 08:48:57

2020-04-27 15:14:10

人工智能技术安全

2024-05-27 09:00:00

2024-05-27 09:01:42

Llama 3大型语言模型人工智能

2024-07-24 13:18:17

2023-07-30 17:39:53

Vicuna数字孪生网友

2024-08-07 08:32:30

2024-07-19 13:31:41

2024-11-12 08:59:39

2018-04-27 08:50:50

2024-11-07 15:40:00

2024-04-19 14:52:13

MetaGPT-4模型
点赞
收藏

51CTO技术栈公众号