雲端 Mac 上的 MLX 提示詞契約:分詞器與聊天範本驗證
把同一段對話搬到雲端 Mac 後,MLX 的回答突然多了角色標籤,不一定是模型權重出了問題。分詞器版本、聊天範本,或重複加入的特殊標記,都可能改變實際輸入。先固定「訊息到 token」的轉換流程,再比較生成結果,才能區分輸入漂移與取樣隨機性。
先界定要驗收的輸入契約
本實作只檢查推論前處理,不載入權重,也不測量 tokens/s。請準備 Python 虛擬環境,以及內容已確認的本機模型目錄;目錄中必須包含該模型所需的分詞器設定、詞彙表與聊天範本。不要從另一個相同規模的模型複製這些檔案。
每次驗收都要記錄三個層次:原始訊息陣列、套用範本後的文字,以及完整的 token ID 序列。只比較 token 數量並不夠,兩組等長序列仍可能表達不同內容。一般詞彙表相同,也不代表角色邊界與結束標記相同。
聊天範本是模型輸入協定的一部分,不是顯示樣式。成功完成分詞,只能證明轉換已完成,不能證明模型收到的角色結構正確。
將以下指令碼儲存為 check_prompt.py,並在已安裝 transformers 及該分詞器所需相依套件的環境中執行。先執行 python -m pip freeze > requirements.lock.txt,留下環境紀錄;這份清單只記錄版本,不保證跨平台安裝後的結果完全一致。
固定本機檔案與訊息樣本
指令碼明確停用連線下載與自訂遠端程式碼。若模型依賴自訂實作,應先審查實作內容,再建立獨立的執行方案,不要為了繞過錯誤而臨時開放執行權限。
樣本包含中文、換行、角括號與多輪對話,並分別檢查加入與不加入生成前綴的情況。這些樣本採用常見的 user、assistant 交替結構;system 角色與工具呼叫則應在確認模型明確支援後,再另加樣本。
import argparse
import json
from pathlib import Path
from transformers import AutoTokenizer
parser = argparse.ArgumentParser()
parser.add_argument("model_dir")
parser.add_argument("baseline")
parser.add_argument("--record", action="store_true")
args = parser.parse_args()
tokenizer = AutoTokenizer.from_pretrained(
args.model_dir,
local_files_only=True,
trust_remote_code=False,
)
cases = [
[{"role": "user", "content": "只回答数字:2+3\n<note>保留原文</note>"}],
[
{"role": "user", "content": "记住代号:实验甲"},
{"role": "assistant", "content": "已记录。"},
{"role": "user", "content": "代号是什么?"},
],
]
rows = []
for messages in cases:
for generation in (False, True):
rendered = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=generation,
)
ids = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=generation,
)
encoded = tokenizer.encode(
rendered, add_special_tokens=False
)
if ids != encoded:
raise SystemExit("Tokenization paths differ")
rows.append({
"messages": messages,
"generation": generation,
"rendered": rendered,
"ids": ids,
})
path = Path(args.baseline)
if args.record:
with path.open("x", encoding="utf-8") as stream:
json.dump(rows, stream, ensure_ascii=False, indent=2)
else:
expected = json.loads(path.read_text(encoding="utf-8"))
if rows != expected:
raise SystemExit("Prompt contract changed")
建立基準,但不要自動核准
首次執行 python check_prompt.py ./model ./prompt-baseline.json --record。記錄時採用獨占建立模式,若檔案已存在就會報錯,避免在排查問題時覆寫證據。之後移除 --record,只有命令沒有輸出且結束代碼為零,才表示通過。
逐項閱讀 JSON:使用者內容是否完整;角色分隔是否符合模型說明;assistant 的歷史回覆之後是否有合理的邊界;生成模式是否已準備好下一次回答的起始位置。有些範本會忽略生成前綴開關,因此不能強制要求兩種結果一定不同。
將基準、指令碼、相依套件清單,以及所用分詞器檔案的校驗值一併納入版本控制。樣本只能使用合成資料,不要把真實對話、存取權杖或內部文件放進可提交的基準內容。
沿實際 MLX 呼叫鏈定位漂移
指令碼通過後,還要檢查應用程式入口是否沿用相同的範本與參數。有些呼叫層接收訊息陣列,有些則接收已套用範本的字串;若把字串再次交給聊天封裝層,可能會重複套用範本。應在進入生成函式之前,擷取合成樣本最終的 token ID,與基準逐項比較。
避免重複加入特殊標記
範本通常已包含起始或結束標記,因此指令碼在對套用範本後的文字進行編碼時,使用 add_special_tokens=False。不要再手動串接 BOS,也不要只憑解碼後「看起來正常」就判定通過:隱藏特殊標記的解碼選項,可能讓重複輸入不易察覺。
遇到差異時,先比較 rendered,再比較 ids。文字不同,優先檢查範本與訊息前處理;文字相同但 ID 不同,則檢查詞彙表、新增詞元與分詞器實作。只有確認實際輸入一致後,才繼續檢查取樣參數與權重版本。
把失敗轉成可複查的檢查項目
新增空白內容、尾端空格、表情符號與長篇中文樣本時,要說明每一項驗證的目的,不要只是堆疊隨機字串。長輸入還應在應用程式層檢查上下文額度:這份指令碼不會替生成任務預留輸出空間,也不會驗證截斷策略。
交付前確認四件事:離線執行時沒有下載檔案;一般檢查不會覆寫基準;範本與 token 任一層出現差異,都能阻止任務繼續;相依套件升級必須經過人工審查。更新基準時,應將候選版本另存為新檔並提交差異,不要在失敗分支中自動重新記錄。
租用期間要保留的是一套可重複執行的輸入證據,而不是某次回答的螢幕截圖。這樣換到另一台雲端 Mac 時,就能先證明模型收到的內容沒有改變,再討論回答為什麼不同。
常見問題
提示詞回歸測試需要載入模型權重嗎?
不需要。範本輸出與分詞檢查只需本機分詞器檔案;這項測試驗證輸入契約,不評估模型回答品質。
升級後 token ID 改變,可以直接更新基準嗎?
不應直接覆寫。先比對分詞器檔案、範本及套件版本,確認變更符合預期,再經人工審核建立新基準。
依實驗時間租用 Mac
獨享 Mac mini M4,16GB 統一記憶體 / 256GB SSD。可預付日租、週租、月租或季租;租期結束後若未續租,機器將停止運作。模型與執行環境請自行準備,實際可用狀態以控制台即時回報為準。
按日租用 Mac