Part.1
在之前的一些章节,我们讲了MCP的基础概念、MCP的服务器安装、MCP服务器的开发还没看过的小伙伴可以点击链接前往查阅,谢谢大家!
接下来我们会继续讨论大模型内部的一些原理,帮助大家更好理解大模型相关概念。
上篇文章中最后章节讲到了从概率最高的3个token中抽样随机选择一个token的预测策略,遗留下来的问题是大模型文字接龙的也不是一个很有语义的话,好像跟现在我们使用的大模型聊天回复的不一样,那是怎么回事呢?那在这篇文章中给大家一起看看是怎么回事呢?
Part.2
上篇文章大模型的文字接龙都是我们通过代码让大模型循环预测下一个token是啥,但在Hugging Face的transformer中有一个函数model.generate可以让我们直接一行代码实现整个过程,不需要再像之前一样先让大模型预测下一个token是什么,然后再把这个token加入到prompt中继续让大模型预测下一个token是什么,依次循环往复;
示例如下图所示:
咋一看,怎么好像相比之前的方法一下子感觉大模型说话有道理有根据了呢?其实并不是,由于我们取的是top3的token参与预测,所以只是有概率出现大模型说话开始符合逻辑,符合上下文意思,不信的话,我们再让大模型预测一次
如上图所示,这次一样的代码一样的模型,文字接龙的结果就不一样了,所以佐证了刚才我们的结论是正确的;
所以回归到本篇需要解决的问题,如何让大模型说话有“意思”?其实让大模型说话有意思的原因就只有一个,就是一个叫做Chat Template的东西,这个Chat Template通过结构化给模型的prompt实现更好的文字接龙效果;
之前我们给大模型的是一段话的prompt,对于我们人类日常的对话来说,很常见的就是我说你答,但对于大模型来说给它一段话就是继续做文字接龙,它本身并不理解什么是“对话”、什么是“用户”、什么是“助手”。
而Chat Template就是解决这个问题的关键桥梁,在Chat Template里面定义了具体的多个角色以及内置的prompt信息,这些信息被包裹成大模型可以理解的结构化的字符串,同时也由于大模型在指令微调阶段研究人员会准备成千上万个高质量的“问答对”数据集,并全部转换成上述的模板格式,所以通过Chat Template可以让大模型更好的知道什么是人类的输入、什么时候该它作答;
它是模型在训练时学到的对话语法。当我们使用与训练时完全一致的模板时,我们就在“说模型能听懂的语言”,从而触发它在训练中学到的、正确的、符合预期的“助手行为”;所以不同模型的Chat Template也会完全不一样;
Part.3
接下来我们来看一下使用我们自己的简易Chat Template会不会让情况有所好转;
此时的Chat Template是这样子的
"使用者说:" + prompt"AI回答:"
如上图所示:实际的效果要说好的话,确实比之前好一些,但是还是理解错意思了,主要的关注点不在关于渗透测试内容的公众号上,反而到了kali上了;所以不能说合格吧,但实际这里要传递的意思是:
通过打印出来大模型文字接龙的结果我们知道,Llama官方的Chat Template长以下这样
通过对Chat Template的了解,我们可以在输入中给Chat Template私自加料,可以把我们的意思加入到官方的Chat Template中,指导大模型修正自己的回复,比如这里我在输入中给大模型输入了一段我想让它讲的话
kali渗透测试教程,这个公众号专注于AI提效、AI科普、AI教程、红蓝对抗、安全运营、Web渗透、开源实用工具分享等内容分享
让他根据我的意思继续做文字接龙,这次的大模型回复的结果就比之前要好一些,不仔细一看还以为大模型就罢工了,仔细一看生成了“的平台”三个字,就到了<|eot_id|>的终止符了;
Part.4
但上述只是一次对话的过程,如果要实现跟ChatGPT一样好的对话效果要如何做呢;其实本质的差别就是一次对话跟多次对话的差别;
多次对话需要把之前的对话内容全部放在最新一次的prompt中,让模型可以记住上下文信息;
具体的代码就不展开讲解了,此处借用的台湾大学教授李宏毅老师的代码,直接看效果
首次对话:人类输入“你是谁”,大模型回复“我是Llama”
继续问一个无关紧要的话题“1+1=多少”,大模型回复”2“;
继续问苹果好吃吗,然后问一个上下文相关的问题,发现他可以识别到之前问它的问题,如下图所示
Part.5
附录代码如下所示
# 使用 model.generate 来进行生成# 把文字转换成符合格式的 token IDs(模型不能读取文字)prompt = "kali渗透测试教程这个公众号"print("现在的 prompt 是:", prompt)input_ids = tokenizer.encode(prompt, return_tensors="pt")#print(input_ids)outputs = model.generate(input_ids, # prompt 的 token IDsmax_length=50, # 最长输出 token 数(包含原本的 prompt)do_sample=True, # 启用随机抽样(而不是永远选择概率最高的 token)top_k=3, # 每次只从概率最高的前 K 个 token 中抽取(Top-K 采样)。如果 top_k = 1,则与每次都选择概率最高的 token 效果一样。pad_token_id=tokenizer.eos_token_id, # 填充 token ID,通常设置为序列结束(EOS)token IDattention_mask=torch.ones_like(input_ids) # 注意力掩码,确保模型只关注输入的部分)# 除了我们这里采用的只从 top-k 中选择的方式以外,还有许多根据概率选取 token 的策略,例如 Top-p(核)采样。# 更多参考资料:https://huggingface.co/docs/transformers/generation_strategies#print(outputs)# 将产生的 token ids 转换回文字generated_text = tokenizer.decode(outputs[0]) # 使用 tokenizer 将模型输出的 token ID 序列解码成文本print("生成的文字是:n", generated_text)
prompt = "kali渗透测试教程这个公众号" # 原始提示词print("现在的 prompt 是:", prompt)prompt_with_chat_template = "使用者说:" + prompt + "nAI回答:" # 加上一个自定义的聊天模板,用于引导模型进行对话式回复print("实际上模型看到的 prompt 是:", prompt_with_chat_template) # 打印出模型实际接收的完整提示词input_ids = tokenizer.encode(prompt_with_chat_template, return_tensors="pt") # 使用 tokenizer 将带有模板的提示词编码为模型输入outputs = model.generate(input_ids,max_length=50, # 设置最大输出 token 数量do_sample=True, # 启用随机采样top_k=5, # 使用 Top-K 采样,每次从概率最高的前 3 个 token 中抽取pad_token_id=tokenizer.eos_token_id, # 填充 token IDattention_mask=torch.ones_like(input_ids) # 注意力掩码)# 将产生的 token ids 转换回文字generated_text = tokenizer.decode(outputs[0]) # 使用 tokenizer 将模型输出的 token ID 序列解码成文本print("生成的文字是:n", generated_text)# 加上 Chat Template 后,语言模型可以更好地对提示词做出回应,看起来像是在对话。模型本身并没有改变。# 不过需要注意,这里的 Chat Template 是自己随便定义的,如果模板设计不佳(例如没有明确结束符),模型回答完问题后,常常会继续模仿对话模式,甚至自己提问。
prompt = "kali渗透测试教程这个公众号是干嘛的" # 原始的用户查询内容print("现在的 prompt 是:", prompt)messages = [{"role": "user", "content": prompt}, # 将用户查询封装成标准的对话消息格式(角色和内容)]print("现在的 messages 是:", messages) # 打印出结构化的消息列表input_ids = tokenizer.apply_chat_template( # 使用 tokenizer 内置的方法应用聊天模板,并同时进行编码messages,add_generation_prompt=True,# add_generation_prompt=True 表示在最后一个用户消息后加上一个特殊的提示 token (例如:<|assistant|>)# 这会明确告诉模型:现在轮到它以助手的身份开始回答了。return_tensors="pt" # 返回 PyTorch Tensor 格式的输入 ID)### 以下代码与前一段代码相同(用于控制生成参数)###outputs = model.generate(input_ids,max_length=100, # 设置最长输出 token 数量do_sample=True, # 启用随机采样top_k=3, # 使用 Top-K 采样pad_token_id=tokenizer.eos_token_id, # 填充 token IDattention_mask=torch.ones_like(input_ids) # 注意力掩码)# 将产生的 token ids 转换回文字generated_text = tokenizer.decode(outputs[0]) # 使用 tokenizer 将模型输出的 token ID 序列解码成文本print("生成的文字是:n", generated_text)
prompt = "kali渗透测试教程,这个公众号是干嘛的" # 原始的用户查询print("现在的 prompt 是:", prompt)messages = [{"role": "system", "content": "你的名字是 Llama"}, # 系统消息:设置模型的角色或行为约束{"role": "user", "content": prompt}, # 用户消息:实际的提问{"role": "assistant", "content": "kali渗透测试教程,这个公众号专注于AI提效、AI科普、AI教程、红蓝对抗、安全运营、Web渗透、开源实用工具分享等内容分享"}, # 助手(模型)消息:模型已经说了这些话 (这里是人为构造的不完整回复)]print("现在的 messages 是:", messages) # 打印出包含上下文的结构化消息列表input_ids = tokenizer.apply_chat_template(messages,add_generation_prompt=False, # **关键设置:**这里需要设为 False,避免在末尾添加生成提示,我们希望模型紧接 assistant 消息续写return_tensors="pt")# **关键技巧:** 去掉最后一个 token (通常是 <|eot_id|> 或 <|endoftext|> 等结束 token)。# 这样做是让模型感觉自己还没有讲完话,需要继续生成下去。input_ids = input_ids[:, :-1]outputs = model.generate(input_ids,max_length=170, # 设置最大输出 token 数量do_sample=True, # 启用随机采样top_k=3, # 使用 Top-K 采样pad_token_id=tokenizer.eos_token_id, # 填充 token IDattention_mask=torch.ones_like(input_ids) # 注意力掩码)# 将产生的 token ids 转换回文字generated_text = tokenizer.decode(outputs[0]) # 使用 tokenizer 将模型输出的 token ID 序列解码成文本print("生成的文字是:n", generated_text)
# 存放整个聊天历史消息的列表 (List to store the entire chat history)messages = []# 一开始设置系统角色(System Prompt)messages.append({"role": "system", "content": "你的名字是 Llama,简短回答问题"})# 开启无限循环,让聊天可以持续进行while True:# 1️⃣ 用户输入消息user_prompt = input("人类说: ") # 从命令行接收用户输入# 如果输入 "exit" 就跳出聊天循环if user_prompt.lower() == "exit":#print("聊天结束啦,下次再聊喔!👋")break# 将用户消息加入到对话记录中messages.append({"role": "user", "content": user_prompt})# 2️⃣ 将历史消息转换模型可以理解的格式# add_generation_prompt=True 会在消息后面加入一个特殊标记 (例如:<|assistant|>),# 告诉模型现在轮到它以助手的身份来回答了。input_ids = tokenizer.apply_chat_template(messages, # 将包含所有历史记录的列表传入add_generation_prompt=True,return_tensors="pt")# 3️⃣ 生成模型的回覆outputs = model.generate(input_ids,max_length=2000, # 最大长度需要设置得大一些,以容纳整个历史记录和新的回复do_sample=True, # 启用随机采样top_k=3, # 使用 Top-K 采样pad_token_id=tokenizer.eos_token_id,attention_mask=torch.ones_like(input_ids))# 将模型的输出转换回文字generated_text = tokenizer.decode(outputs[0], skip_special_tokens=False)# 🔎 从生成结果中取出模型真正的回覆内容(需要去除特殊 token)# Llama 模型(或其他对话模型)会用特殊的 token 区分消息头尾,格式通常是这样的:# [历史消息 + <|start_header_id|>assistant<|end_header_id|>] 模型的回覆内容 <|eot_id|># 这里的分割逻辑用于提取最后一个助手的回复部分。response = generated_text.split("<|end_header_id|>")[-1].split("<|eot_id|>")[0].strip()# 4️⃣ 显示模型回复print("AI说:", response)# **关键步骤:** 将模型回复加进对话记录,让下次模型知道之前的对话内容messages.append({"role": "assistant", "content": response})
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……




还没有评论,来说两句吧...