聊天模板

釋出於 2023 年 10 月 3 日
在 GitHub 上更新

一個幽靈困擾著聊天模型——不正確格式化的幽靈!

總結

聊天模型在將對話轉換為單個可標記字串方面,使用了非常不同的格式進行訓練。使用與模型訓練時使用的格式不同的格式,通常會導致嚴重、無聲的效能下降,因此匹配訓練期間使用的格式至關重要!Hugging Face 分詞器現在具有一個 chat_template 屬性,可用於儲存模型訓練時使用的聊天格式。此屬性包含一個 Jinja 模板,可將對話歷史轉換為正確格式的字串。有關如何在程式碼中編寫和應用聊天模板的資訊,請參閱技術文件

引言

如果你熟悉 🤗 Transformers 庫,你可能寫過這樣的程式碼:

tokenizer = AutoTokenizer.from_pretrained(checkpoint)
model = AutoModel.from_pretrained(checkpoint)

透過從相同的檢查點載入分詞器和模型,可以確保輸入按照模型預期的方式進行分詞。如果從不同的模型中選擇分詞器,輸入分詞可能會完全不同,結果是模型的效能會受到嚴重損害。這被稱為分佈偏移——模型一直在學習來自一個分佈(其訓練時的分詞)的資料,然後突然轉移到完全不同的分佈。

無論你是對模型進行微調還是直接用於推理,最好都儘量減少這些分佈偏移,並使你提供給模型的輸入儘可能地與它訓練時的輸入相似。對於常規的語言模型,這樣做相對容易——只需從相同的檢查點載入分詞器和模型即可。

然而,對於聊天模型來說,情況有點不同。這是因為“聊天”不僅僅是一個可以簡單分詞的文字字串——它是一系列訊息,每條訊息都包含一個 role 以及 content,也就是訊息的實際文字。最常見的角色是使用者傳送訊息的“user”,模型編寫回復的“assistant”,以及可選的在對話開始時給出高層指令的“system”。

如果這聽起來有點抽象,這裡有一個聊天示例,讓它更具體:

[
    {"role": "user", "content": "Hi there!"},
    {"role": "assistant", "content": "Nice to meet you!"}
]

在將這一系列訊息分詞並用作模型輸入之前,需要將其轉換為文字字串。然而,問題在於有很多種方法可以進行這種轉換!例如,你可以將訊息列表轉換為“即時通訊”格式:

User: Hey there!
Bot: Nice to meet you!

或者你可以新增特殊令牌來指示角色:

[USER] Hey there! [/USER]
[ASST] Nice to meet you! [/ASST]

或者你可以新增令牌來指示訊息之間的邊界,但將角色資訊作為字串插入:

<|im_start|>user
Hey there!<|im_end|>
<|im_start|>assistant
Nice to meet you!<|im_end|>

有很多方法可以做到這一點,而且沒有一種方法是顯而易見的最佳或正確方法。因此,不同的模型已經用截然不同的格式進行了訓練。我沒有編造這些例子;它們都是真實存在的,並且至少被一個活躍的模型使用!但是,一旦模型用某種格式進行了訓練,你就真的需要確保將來的輸入使用相同的格式,否則可能會導致效能下降的分佈偏移。

模板:一種儲存格式資訊的方法

目前,如果你運氣好,所需的格式會正確地記錄在模型卡中。如果你運氣不好,它就不在那裡,所以如果你想使用該模型,那就祝你好運了。在極端情況下,我們甚至將整個提示格式放在一篇部落格文章中,以確保使用者不會錯過它!即使在最好的情況下,你也必須找到模板資訊,並在你的微調或推理管道中手動編寫程式碼。我們認為這是一個特別危險的問題,因為使用錯誤的聊天格式是一種靜默錯誤——你不會收到響亮的失敗或 Python 異常來告訴你出了問題,模型只會比使用正確格式時表現得差很多,而且很難調試出原因!

這就是聊天模板旨在解決的問題。聊天模板是Jinja 模板字串,它們與你的分詞器一起儲存和載入,幷包含將聊天訊息列表轉換為模型正確格式輸入所需的所有資訊。這裡有三個聊天模板字串,對應於上面提到的三種訊息格式:

{% for message in messages %}
    {% if message['role'] == 'user' %}
        {{ "User : " }}
    {% else %}
        {{ "Bot : " }}
    {{ message['content'] + '\n' }}
{% endfor %}
{% for message in messages %}
    {% if message['role'] == 'user' %}
        {{ "[USER] " + message['content'] + " [/USER]" }}
    {% else %}
        {{ "[ASST] " + message['content'] + " [/ASST]" }}
    {{ message['content'] + '\n' }}
{% endfor %}
"{% for message in messages %}"  
    "{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}"  
"{% endfor %}"

如果您不熟悉 Jinja,我強烈建議您花一點時間檢視這些模板字串及其對應的模板輸出,看看您是否能理解模板如何將訊息列表轉換為格式化字串!語法在很多方面與 Python 非常相似。

為什麼使用模板?

雖然 Jinja 最初可能讓人困惑,如果您不熟悉它,但在實踐中我們發現 Python 程式設計師可以很快掌握它。在開發此功能期間,我們考慮了其他方法,例如一個有限的系統,允許使用者為訊息指定每個角色的字首和字尾。我們發現這可能會變得令人困惑和笨拙,而且非常不靈活,以至於需要針對多個模型進行臨時變通。而模板則足夠強大,可以清晰地支援我們所知道的所有訊息格式。

為什麼要這麼麻煩?為什麼不直接選擇一種標準格式?

這是一個很好的主意!不幸的是,為時已晚,因為多個重要模型已經用非常不同的聊天格式進行了訓練。

然而,我們仍然可以稍微緩解這個問題。我們認為最接近“標準”格式的是 OpenAI 建立的 ChatML 格式。如果你正在訓練一個新的聊天模型,並且這種格式適合你,我們建議你使用它並向你的分詞器新增特殊的 <|im_start|><|im_end|> 標記。它的優點是對角色非常靈活,因為角色只是作為字串插入,而不是有特定的角色標記。如果你想使用這個模板,它是上面第三個模板,你可以用這個簡單的一行程式碼設定它:

tokenizer.chat_template = "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}"

然而,除了現有格式的泛濫之外,還有第二個不應該硬編碼標準格式的原因——我們預計模板將在許多型別模型的預處理中廣泛有用,包括那些可能與標準聊天有很大不同的模型。硬編碼標準格式限制了模型開發者使用此功能執行我們甚至尚未想到的事情的能力,而模板則為使用者和開發者提供了最大的自由。甚至可以在模板中編碼檢查和邏輯,這是我們在任何預設模板中都沒有廣泛使用的功能,但我們預計它在冒險使用者手中將具有巨大的力量。我們堅信開源生態系統應該讓你做你想做的事,而不是規定你被允許做什麼。

模板如何工作?

聊天模板是分詞器的一部分,因為它們扮演著與分詞器相同的角色:它們儲存有關資料預處理方式的資訊,以確保您以與模型訓練期間看到的相同格式向模型提供資料。我們設計它使得向現有分詞器新增模板資訊並儲存或上傳到 Hub 變得非常容易。

在聊天模板出現之前,聊天格式資訊儲存在類級別——這意味著,例如,所有 LLaMA 檢查點都將獲得相同的聊天格式,使用硬編碼在 transformers 中用於 LLaMA 模型類的程式碼。為了向後相容,具有自定義聊天格式方法的模型類已改為提供預設聊天模板

預設聊天模板也設定在類級別,並告訴 ConversationPipeline 等類如何在模型沒有聊天模板時格式化輸入。我們這樣做純粹是為了向後相容——我們強烈建議您在任何聊天模型上明確設定聊天模板,即使預設聊天模板是合適的。這確保了預設聊天模板中任何未來的更改或棄用都不會破壞您的模型。儘管我們將在可預見的未來保留預設聊天模板,但我們希望隨著時間的推移將所有模型過渡到明確的聊天模板,屆時預設聊天模板可能會被完全刪除。

有關如何設定和應用聊天模板的資訊,請參閱技術文件

如何開始使用模板?

很簡單!如果分詞器設定了 chat_template 屬性,它就可以使用了。您可以在 ConversationPipeline 中使用該模型和分詞器,或者呼叫 tokenizer.apply_chat_template() 來格式化聊天以便進行推理或訓練。有關更多資訊,請參閱我們的開發者指南apply_chat_template 文件

如果分詞器沒有 chat_template 屬性,它可能仍然有效,但它會使用該模型類的預設聊天模板。正如我們上面提到的,這很不穩定,而且當類模板與模型實際訓練的模板不匹配時,這也是導致無聲錯誤的來源。如果您想使用一個沒有 chat_template 的檢查點,我們建議您檢視模型卡等文件,以驗證正確的格式,然後為該格式新增一個正確的 chat_template。我們建議這樣做,即使預設聊天模板是正確的——它使模型面向未來,並且還明確了模板是存在且合適的。

您甚至可以為不屬於您的檢查點新增 chat_template,方法是發起拉取請求。您唯一需要做的更改是將 tokenizer.chat_template 屬性設定為 Jinja 模板字串。完成後,推送您的更改即可!

如果你想將檢查點用於聊天,但找不到任何關於其使用的聊天格式的文件,你可能應該在檢查點上提出問題或聯絡所有者!一旦你弄清楚模型使用的格式,請發起一個拉取請求來新增一個合適的 chat_template。其他使用者會非常感激的!

結論:模板哲學

我們認為模板是一項非常激動人心的改變。除了解決大量無聲的、降低效能的 bug 之外,我們認為它們還開啟了全新的方法和資料模態。也許最重要的是,它們也代表了一種哲學轉變:它們將核心 transformers 程式碼庫中的一個重要功能移到了各個模型倉庫中,在那裡使用者可以自由地做一些奇怪、狂野和美妙的事情。我們很高興看到您能發現它們的新用途!

社群

註冊登入發表評論

© . This site is unofficial and not affiliated with Hugging Face, Inc.