Hugging Face 分詞器新增聊天模板屬性

HuggingFace發表於2023-10-17

一個幽靈,格式不正確的幽靈,在聊天模型中游蕩!

太長不看版

現存的聊天模型使用的訓練資料格式各各不同,我們需要用這些格式將對話轉換為單個字串並傳給分詞器。如果我們在微調或推理時使用的格式與模型訓練時使用的格式不同,通常會導致嚴重的、無聲的效能下降,因此匹配訓練期間使用的格式極其重要! Hugging Face 分詞器新增了 chat_template 屬性,可用於儲存模型訓練時使用的聊天格式。此屬性包含一個 Jinja 模板,可將對話歷史記錄格式化為正確的字串。請參閱 技術文件,以瞭解有關如何在程式碼中編寫和應用聊天模板。

引言

如果你熟悉 ? transformers 庫,你可能寫過如下程式碼:

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

透過從同一個 checkpoint 中載入分詞器和模型,可以確保對輸入字串使用的分詞方法符合模型預期。如果你從另一個模型中選擇分詞器,則其分詞結果很可能會完全不同,此時模型的效能就會受到嚴重損害。這種現象叫 分佈漂移 (distribution shift): 模型一直從一種分佈學習 (即訓練分詞器),突然,資料分佈變成了另一個不同的分佈。

無論你是微調模型還是直接用它進行推理,讓這種分佈上的變化儘可能小,並保持提供的輸入儘可能與訓練時的輸入一致總是一個好主意。對於常規語言模型,做到這一點相對容易 - 只需從同一檢查點載入分詞器和模型,就可以了。

然而,對於聊天模型來說,情況有點不同。這是因為“聊天”不僅僅是直接對單個文字字串進行分詞 - 它需要對一系列訊息進行分詞。每個訊息都包含一個 角色 及其 內容 ,其內容是訊息的實際文字。最常見的,角色是“使用者”(用於使用者傳送的訊息) 、“助理”(用於模型生成的響應),以及可選的“系統”(指在對話開始時給出的高階指令)。

幹講可能有點抽象,下面我們給出一個示例聊天,把問題具象化:

[
    {"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 checkpoint 都將使用同一個硬設在 transformers 的 LLaMA 模型類程式碼中的聊天格式。為了向後相容,目前具有自定義聊天格式方法的模型類也已被賦予了 預設聊天模板

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

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

我該如何開始使用模板?

很簡單!如果分詞器設定了 chat_template 屬性,則它已準備就緒。你可以在 ConversationPipeline 中使用該模型和分詞器,也可以呼叫 tokenizer.apply_chat_template() 來格式化聊天以進行推理或訓練。請參閱我們的 開發者指南如何應用聊天模板的文件 以瞭解更多!

如果分詞器沒有 chat_template 屬性,它可能仍然可以工作,但它將使用該模型類的預設聊天模板。正如我們上面提到的,這是脆弱的,並且當類别範本與模型實際訓練的內容不匹配時,它同樣會導致靜默錯誤。如果你想使用沒有 chat_template 的 checkpoint,我們建議檢查模型卡等文件以確保使用正確的格式,然後為該格式新增正確的 chat_template 。即使預設聊天模板是正確的,我們也建議這樣做 - 它可以使模型面向未來,並且還可以清楚地表明該模板是存在的且是適用的。

即使不是你的 checkpoint,你也可以透過提交 合併請求 (pull request) 的方式為其新增 chat_template 。僅需將 tokenizer.chat_template 屬性設定為 Jinja 模板字串。完成後,推送更改就可以了!

如果你想在你的聊天應用中使用某 checkpoint,但找不到有關其使用的聊天格式的任何文件,你可能應該在 checkpoint 上提出問題或聯絡其所有者!一旦你弄清楚模型使用的格式,請提交一個 PR 以新增合適的 chat_template 。其他使用者將會非常感激你的貢獻!

總結: 模板理念

我們認為模板是一個非常令人興奮的新特性。除了解決大量無聲的、影響效能的錯誤之外,我們認為它們還開闢了全新的方法和資料模式。但最重要的也許是,它們還代表了一種理念轉變: 從核心 transformers 程式碼庫中挪出一個重要功能,並將其轉移到各自模型的倉庫中,使用者可以自由地做各種奇怪、狂野抑或奇妙的事情。我們迫不及待想看看你會發現哪些用途!


英文原文: https://hf.co/blog/chat-templates

原文作者: Matthew Carrigan

譯者: Matrix Yao (姚偉峰),英特爾深度學習工程師,工作方向為 transformer-family 模型在各模態資料上的應用及大規模模型的訓練推理。

審校/排版: zhongdongy (阿東)

相關文章