概述
在與大型語言模型(如ChatGPT)交互的過程中,我們常常體驗到與智能助手進行連貫多輪對話的便利性。那么,當我們開啟一個新的聊天時,系統是如何管理聊天上下文的呢?
一、初始上下文的建立
1. 創建新會話
當用戶開啟一個新的聊天時,應用程序后端會為該對話創建一個獨立的會話(session),并分配一個唯一的會話ID。這確保了每個對話都是獨立的,防止不同對話之間的混淆。
2. 系統提示的引入
在新會話的開始,系統會向模型提供一段隱藏的系統提示(System Prompt)。這段提示用于設定模型在整個對話中的角色、語氣和行為準則。例如:
- 角色設定:讓模型扮演助理、教師、技術專家等特定身份。
- 語言風格:規定回復使用正式、友好、幽默等特定語氣。
- 行為準則:避免生成不適當內容,遵守倫理規范。
系統提示對用戶是不可見的,但對模型的回復有著深遠影響,它確保了模型在整個對話過程中保持一致的行為。
二、上下文的積累與管理
1. 對話歷史的記錄
隨著用戶與模型的交互進行,系統會將每一次的用戶輸入和模型回復按照時間順序累積,形成當前會話的消息隊列。這使得模型在生成回復時,可以參考之前的對話內容,保持連貫性和一致性。
2. 上下文窗口的限制
大型語言模型在處理輸入時,有一個固定的上下文窗口(Context Window),表示模型一次能處理的最大文本長度。例如,GPT-3的上下文窗口為4096個Token。
當對話長度超過上下文窗口時,系統需要對輸入進行截斷。為了確保模型繼續遵循最初的系統提示,應用程序會:
- 優先保留系統提示:系統提示始終位于輸入的開頭,不被截斷。
- 截斷早期對話:從最早的用戶和模型對話開始移除,保留最近的交互內容。
3. 上下文組裝
在生成回復時,應用程序會將以下內容按順序拼接,形成當前的輸入上下文:
- 系統提示:設定模型行為的隱藏指令。
- 重要信息:用戶提供的關鍵數據或參數(如果有)。
- 最近的對話歷史:包括最近幾輪的用戶輸入和模型回復。
通過這種方式,模型能夠在一次交互中獲得必要的上下文信息,生成符合預期的回復。
三、系統提示的重要作用
1. 保證模型行為一致性
由于模型在每次生成回復時,只能參考當前的輸入文本,因此系統提示需要在每次輸入中提供,確保模型始終按照設定的角色和風格進行回復。
2. 防止不當內容生成
系統提示中包含的行為準則和禁止事項,有助于模型避免生成不合規或不適當的內容,提升對話的安全性和可靠性。
3. 提高用戶體驗
通過精心設計的系統提示,模型能夠更好地理解用戶意圖,提供高質量、個性化的回復,提升用戶的交互體驗。
四、技術實現細節
1. 會話管理
- 創建會話ID:為每個新對話分配唯一的會話ID,用于區分不同用戶的會話。
- 狀態跟蹤:記錄每個會話的狀態信息,便于后續的上下文管理。
2. 消息隊列維護
- 記錄交互內容:保存當前會話中的所有用戶輸入和模型回復。
- 長度檢查:在發送給模型之前,檢查輸入的總長度,確保不超過上下文窗口限制。
3. 上下文優化
- 截斷策略:當超過上下文窗口限制時,從早期對話開始移除內容。
- 摘要處理:對于重要但較早的內容,通過生成摘要的方式保留關鍵信息。
五、模型與應用的職責劃分
需要明確的是,大型語言模型本身并不具備會話管理、消息隊列維護或上下文組裝的能力。這些功能由應用程序在模型之上實現。具體來說:
- 模型的職責:根據輸入生成下一段文本。
- 應用的職責:管理對話上下文、用戶會話、內容過濾等。
通過合理的職責劃分,應用程序能夠充分發揮模型的能力,提供豐富多樣的應用場景。
六、用戶數據的安全與隱私
- 獨立的會話:每個新對話都是獨立的,模型不會記住之前會話中的信息,保護用戶隱私。
- 數據限制:用戶的輸入和模型的回復都嚴格限定在當前會話內,不會跨會話傳播。
七、總結
大型語言模型在新聊天中管理上下文,主要通過以下方式實現:
- 創建新會話,重置上下文:確保每個對話的獨立性。
- 使用系統提示:設定模型的角色、風格和行為準則,確保模型行為一致。
- 維護消息隊列:記錄對話歷史,供模型參考,提高回復的連貫性。
- 上下文管理:在上下文窗口限制內,優化輸入內容,保證模型有效處理。
示例
使用多輪會話示例代碼
下面的代碼,演示如何在代碼中實現與大型語言模型的多輪對話。我們將引入一個循環,允許用戶多次輸入,并維護會話的上下文,使模型的回復能夠參考之前的對話內容。
代碼
import torch
import logging
from transformers import Qwen2_5_VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info# 設置日志配置,包含Transformers庫的日志
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',level=logging.INFO # 設置全局日志級別為INFO,避免過多日志輸出
)
# 獲取Transformers庫的logger并設置級別為INFO
transformers_logger = logging.getLogger('transformers')
transformers_logger.setLevel(logging.INFO)# 設置模型緩存目錄
cache_dir = '/data/model/'# 加載模型,啟用GPU加速
model = Qwen2_5_VLForConditionalGeneration.from_pretrained("Qwen/Qwen2.5-VL-3B-Instruct",torch_dtype=torch.bfloat16,attn_implementation="sdpa",device_map="auto",cache_dir=cache_dir
)
logging.info("模型已加載到設備:%s,使用attn_implementation='sdpa'", model.device)# 設置視覺令牌范圍以平衡性能和成本
min_pixels = 256 * 28 * 28
max_pixels = 1280 * 28 * 28# 加載處理器
processor = AutoProcessor.from_pretrained("Qwen/Qwen2.5-VL-3B-Instruct",min_pixels=min_pixels,max_pixels=max_pixels,cache_dir=cache_dir
)
logging.info("處理器已加載,設置了自定義的視覺令牌范圍。")# 初始化消息內容列表,包含系統提示(可選)
messages = [# 可以添加系統提示,設定模型的行為{"role": "system","content": [{"type": "text", "text": "你是一位友好的智能助手,樂于回答用戶的問題并提供幫助。"},],}
]# 多輪會話循環
while True:user_input = input("用戶:")if user_input.lower() in ['退出', 'exit', 'quit']:print("結束對話。")break# 將用戶輸入添加到消息列表messages.append({"role": "user","content": [{"type": "text", "text": user_input}]})# 準備推理輸入logging.info("開始準備推理輸入...")text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)image_inputs, video_inputs = process_vision_info(messages)inputs = processor(text=[text],images=image_inputs,videos=video_inputs,padding=True,return_tensors="pt",)inputs = inputs.to(model.device)logging.info("推理輸入已準備完畢。")# 進行推理并生成輸出logging.info("開始生成輸出...")generated_ids = model.generate(**inputs, max_new_tokens=512)logging.info("輸出生成完畢。")# 處理生成的輸出generated_ids_trimmed = [out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)]output_text = processor.batch_decode(generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False)[0] # 取第一個元素print("助手:" + output_text.strip())# 將模型的回復添加到消息列表messages.append({"role": "assistant","content": [{"type": "text", "text": output_text.strip()}]})# 為了防止超過上下文長度限制,可以在這里檢查并截斷消息列表# 例如,只保留最近的n輪對話max_history = 5 # 保留最近5輪對話(可根據需要調整)# 保留系統提示加上最近的max_history*2條消息(用戶和助手各一條,所以乘以2)if len(messages) > max_history * 2 + 1: # +1是因為系統提示算一條messages = [messages[0]] + messages[-max_history*2:]logging.info("消息列表已截斷,保留最近的 %d 輪對話。", max_history)
代碼說明
-
引入多輪會話循環:使用
while True
循環,不斷讀取用戶輸入,實現多輪對話。 -
管理消息列表:使用
messages
列表維護對話歷史,在每一輪中將用戶和助手的消息添加到列表中。 -
處理用戶退出指令:如果用戶輸入
退出
、exit
或quit
,程序將結束對話循環。 -
準備推理輸入:在每一輪對話中,使用
processor.apply_chat_template
方法將messages
列表轉換為模型可接受的輸入格式。 -
調用模型生成回復:使用
model.generate
方法生成模型的回復,并將其解碼為文本。 -
顯示模型回復并添加到對話歷史:將模型的回復打印出來,并添加到
messages
列表中,以在后續對話中提供上下文。 -
管理上下文長度:為了防止超過模型的上下文窗口限制(即最大輸入長度),在每輪對話后檢查
messages
列表的長度,并截斷早期的對話內容,只保留最近的max_history
輪對話。
示例運行
拖到最右側,重點看輸入給大模型的messages在不斷的累積
2025-02-27 04:00:07,656 - root - INFO - 處理器已加載,設置了自定義的視覺令牌范圍。
用戶:你好!
2025-02-27 04:00:49,596 - root - INFO - 開始準備推理輸入...
2025-02-27 04:00:49,596 - root - INFO - [{'role': 'system', 'content': [{'type': 'text', 'text': '你是一位友好的智能助手,樂于回答用戶的問題并提供幫助。'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '你好!'}]}]
2025-02-27 04:00:49,609 - root - INFO - 推理輸入已準備完畢。
2025-02-27 04:00:49,609 - root - INFO - 開始生成輸出...
2025-02-27 04:00:59,579 - root - INFO - 輸出生成完畢。
助手:你好!有什么可以幫助你的嗎?
用戶:你能給我講個笑話嗎?
2025-02-27 04:01:11,942 - root - INFO - 開始準備推理輸入...
2025-02-27 04:01:11,942 - root - INFO - [{'role': 'system', 'content': [{'type': 'text', 'text': '你是一位友好的智能助手,樂于回答用戶的問題并提供幫助。'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '你好!'}]}, {'role': 'assistant', 'content': [{'type': 'text', 'text': '你好!有什么可以幫助你的嗎?'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '你能給我講個笑話嗎?'}]}]
2025-02-27 04:01:11,943 - root - INFO - 推理輸入已準備完畢。
2025-02-27 04:01:11,943 - root - INFO - 開始生成輸出...
2025-02-27 04:01:32,729 - root - INFO - 輸出生成完畢。
助手:當然可以!這是一個經典的笑話:為什么電腦經常生病?因為它的窗戶(Windows)總是開著!
用戶:哈哈,很有趣。再講一個腦筋急轉彎?
2025-02-27 04:02:08,591 - root - INFO - 開始準備推理輸入...
2025-02-27 04:02:08,591 - root - INFO - [{'role': 'system', 'content': [{'type': 'text', 'text': '你是一位友好的智能助手,樂于回答用戶的問題并提供幫助。'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '你好!'}]}, {'role': 'assistant', 'content': [{'type': 'text', 'text': '你好!有什么可以幫助你的嗎?'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '你能給我講個笑話嗎?'}]}, {'role': 'assistant', 'content': [{'type': 'text', 'text': '當然可以!這是一個經典的笑話:為什么電腦經常生病?因為它的窗戶(Windows)總是開著!'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '哈哈,很有趣。再講一個腦筋急轉彎?'}]}]
2025-02-27 04:02:08,592 - root - INFO - 推理輸入已準備完畢。
2025-02-27 04:02:08,593 - root - INFO - 開始生成輸出...
2025-02-27 04:02:34,326 - root - INFO - 輸出生成完畢。
助手:好的,這個腦筋急轉彎挺有趣的:什么東西越洗越臟?答案是水。
用戶:謝謝你的回答
2025-02-27 04:03:03,807 - root - INFO - 開始準備推理輸入...
2025-02-27 04:03:03,807 - root - INFO - [{'role': 'system', 'content': [{'type': 'text', 'text': '你是一位友好的智能助手,樂于回答用戶的問題并提供幫助。'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '你好!'}]}, {'role': 'assistant', 'content': [{'type': 'text', 'text': '你好!有什么可以幫助你的嗎?'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '你能給我講個笑話嗎?'}]}, {'role': 'assistant', 'content': [{'type': 'text', 'text': '當然可以!這是一個經典的笑話:為什么電腦經常生病?因為它的窗戶(Windows)總是開著!'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '哈哈,很有趣。再講一個腦筋急轉彎?'}]}, {'role': 'assistant', 'content': [{'type': 'text', 'text': '好的,這個腦筋急轉彎挺有趣的:什么東西越洗越臟?答案是水。'}]}, {'role': 'user', 'content': [{'type': 'text', 'text': '謝謝你的回答'}]}]
2025-02-27 04:03:03,809 - root - INFO - 推理輸入已準備完畢。
2025-02-27 04:03:03,809 - root - INFO - 開始生成輸出...
2025-02-27 04:03:27,048 - root - INFO - 輸出生成完畢。
助手:不客氣,隨時歡迎你來提問!
用戶:退出
結束對話。
另外多模態大模型可以支持復雜的會話messages,單次輸入給大模型的輸入可以如下:
conversation = [{"role": "user","content": [{"type": "image"}, {"type": "text", "text": "Hello, how are you?"}],},{"role": "assistant","content": "I'm doing well, thank you for asking. How can I assist you today?",},{"role": "user","content": [{"type": "text", "text": "Can you describe these images and video?"},{"type": "image"},{"type": "image"},{"type": "video"},{"type": "text", "text": "These are from my vacation."},],},{"role": "assistant","content": "I'd be happy to describe the images and video for you. Could you please provide more context about your vacation?",},{"role": "user","content": "It was a trip to the mountains. Can you see the details in the images and video?",},
]# default:
prompt_without_id = processor.apply_chat_template(conversation, add_generation_prompt=True
)
# Excepted output: '<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\n<|vision_start|><|image_pad|><|vision_end|>Hello, how are you?<|im_end|>\n<|im_start|>assistant\nI'm doing well, thank you for asking. How can I assist you today?<|im_end|>\n<|im_start|>user\nCan you describe these images and video?<|vision_start|><|image_pad|><|vision_end|><|vision_start|><|image_pad|><|vision_end|><|vision_start|><|video_pad|><|vision_end|>These are from my vacation.<|im_end|>\n<|im_start|>assistant\nI'd be happy to describe the images and video for you. Could you please provide more context about your vacation?<|im_end|>\n<|im_start|>user\nIt was a trip to the mountains. Can you see the details in the images and video?<|im_end|>\n<|im_start|>assistant\n'# add ids
prompt_with_id = processor.apply_chat_template(conversation, add_generation_prompt=True, add_vision_id=True
)
# Excepted output: '<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n<|im_start|>user\nPicture 1: <|vision_start|><|image_pad|><|vision_end|>Hello, how are you?<|im_end|>\n<|im_start|>assistant\nI'm doing well, thank you for asking. How can I assist you today?<|im_end|>\n<|im_start|>user\nCan you describe these images and video?Picture 2: <|vision_start|><|image_pad|><|vision_end|>Picture 3: <|vision_start|><|image_pad|><|vision_end|>Video 1: <|vision_start|><|video_pad|><|vision_end|>These are from my vacation.<|im_end|>\n<|im_start|>assistant\nI'd be happy to describe the images and video for you. Could you please provide more context about your vacation?<|im_end|>\n<|im_start|>user\nIt was a trip to the mountains. Can you see the details in the images and video?<|im_end|>\n<|im_start|>assistant\n'
注意事項
-
上下文長度限制:大型語言模型對輸入文本的長度是有最大限制的(例如4096個Token)。在實際應用中,需要根據模型的實際限制,調整
max_history
的值,或者采用更加復雜的截斷和摘要策略。 -
視覺信息處理:示例代碼中包含了對圖像和視頻輸入的處理。如果當前對話不涉及圖像或視頻,可以簡化相關處理,或者在需要時動態地添加圖像或視頻信息到
messages
中。 -
系統提示的作用:在
messages
列表中添加"role": "system"
的消息,可以設定模型的整體行為和風格。系統提示通常只需在對話開始時添加一次,后續對話中無需重復。 -
日志級別設置:為了避免過多的日志輸出,將全局日志級別從
DEBUG
調整為INFO
。根據需要,可以進一步調整日志級別。 -
模型性能與資源:運行此代碼需要具備支持相應模型大小的計算資源(例如GPU內存)。在實際應用中,根據硬件條件選擇合適的模型規模。