使用 PPO 算法進(jìn)行 RLHF 的 N 步實(shí)現(xiàn)細(xì)節(jié)

當(dāng)下,RLHF/ChatGPT 已經(jīng)變成了一個(gè)非常流行的話題。我們正在致力于更多有關(guān) RLHF 的研究,這篇博客嘗試復(fù)現(xiàn) OpenAI 在 2019 年開源的原始 RLHF 代碼庫,其倉庫位置位于 openai/lm-human-preferences。盡管它具有 “tensorflow-1.x” 的特性,但 OpenAI 的原始代碼庫評估和基準(zhǔn)測試非常完善,使其成為研究 RLHF 實(shí)現(xiàn)工程細(xì)節(jié)的好地方。
我們的目標(biāo)是:
復(fù)現(xiàn) OAI 在風(fēng)格化任務(wù)中的結(jié)果,并匹配 openai/lm-human-preferences 的學(xué)習(xí)曲線。
提供一個(gè)實(shí)現(xiàn)細(xì)節(jié)的清單,類似于 近端優(yōu)化策略的 37 個(gè)實(shí)施細(xì)節(jié) (The 37 Implementation Details of Proximal Policy Optimization) 和 沒有痛苦折磨的調(diào)試 RL (Debugging RL, Without the Agonizing Pain) 的風(fēng)格;
提供一個(gè)易于閱讀且簡潔的 RLHF 參考實(shí)現(xiàn);
這項(xiàng)工作僅適用于以教育/學(xué)習(xí)為目的的。對于需要更多功能的高級用戶,例如使用 PEFT 運(yùn)行更大的模型, huggingface/trl 將是一個(gè)不錯(cuò)的選擇。
在 匹配學(xué)習(xí)曲線 中,我們展示了我們的主要貢獻(xiàn): 創(chuàng)建一個(gè)代碼庫,能夠在風(fēng)格化任務(wù)中復(fù)現(xiàn) OAI 的結(jié)果,并且與 openai/lm-human-preferences 的學(xué)習(xí)曲線非常接近地匹配。
然后我們深入探討了與復(fù)現(xiàn) OAI 的工作相關(guān)的實(shí)現(xiàn)細(xì)節(jié)。在 總體實(shí)現(xiàn)細(xì)節(jié) 中,我們討論了基本細(xì)節(jié),像如何生成獎(jiǎng)勵(lì)/值和如何生成響應(yīng)。在 獎(jiǎng)勵(lì)模型實(shí)現(xiàn)細(xì)節(jié) 中,我們討論了諸如獎(jiǎng)勵(lì)標(biāo)準(zhǔn)化之類的細(xì)節(jié)。在 策略訓(xùn)練實(shí)現(xiàn)細(xì)節(jié) 中,我們討論了拒絕采樣和獎(jiǎng)勵(lì)“白化”等細(xì)節(jié)。
在 PyTorch Adam 優(yōu)化器在處理 RLHF 時(shí)的數(shù)值問題 中,我們強(qiáng)調(diào)了 TensorFlow 和 PyTorch 之間 Adam 的一個(gè)非常有趣的實(shí)現(xiàn)區(qū)別,其導(dǎo)致了模型訓(xùn)練中的激進(jìn)更新。
接下來,我們檢查了在獎(jiǎng)勵(lì)標(biāo)簽由
gpt2-large
生成的情況下,訓(xùn)練不同基礎(chǔ)模型 (例如 gpt2-xl, falcon-1b) 的效果。最后,我們通過討論一些限制來總結(jié)我們的研究工作。
以下是一些重要鏈接:
?? 我們的復(fù)現(xiàn)代碼庫 https://github.com/vwxyzjn/lm-human-preference-details
?? RLHF 模型比較示例: https://huggingface.co/spaces/lm-human-preference-details/rlhf-demo
?? 所有的 w&b 訓(xùn)練日志 https://wandb.ai/openrlbenchmark/lm_human_preference_details
匹配學(xué)習(xí)曲線
我們的主要貢獻(xiàn)是在風(fēng)格化任務(wù)中復(fù)現(xiàn) OAI 的結(jié)果,例如情感和描述性。如下圖所示,我們的代碼庫 (橙色曲線) 能夠產(chǎn)生與 OAI 的代碼庫 (藍(lán)色曲線) 幾乎相同的學(xué)習(xí)曲線。

關(guān)于運(yùn)行 openai/lm-human-preferences 的說明
為了直觀比較,我們運(yùn)行了原始的 RLHF 代碼,其倉庫位置位于 openai/lm-human-preferences,它將提供寶貴的指標(biāo),以幫助驗(yàn)證和診斷我們的復(fù)現(xiàn)。我們能夠設(shè)置原始的 TensorFlow 1.x 代碼,但它需要一個(gè)非常特定的設(shè)置:
OAI 的數(shù)據(jù)集部分損壞/丟失 (所以我們用類似的 HF 數(shù)據(jù)集替換了它們,這可能會(huì)或可能不會(huì)導(dǎo)致性能差異)
具體來說,它的書籍?dāng)?shù)據(jù)集在 OpenAI 的 GCP - Azure 遷移過程中丟失了 (https://github.com/openai/lm-human-preferences/issues/17#issuecomment-1044051496)。我用 Hugging Face 的
bookcorpus
數(shù)據(jù)集替換了書籍?dāng)?shù)據(jù)集,原則上,這是類似 OAI 使用的數(shù)據(jù)集。它不能在 1 個(gè) V100 上運(yùn)行,因?yàn)樗鼪]有實(shí)現(xiàn)梯度累積。相反,它使用一個(gè)大的 BS (批量大小),并在 8 個(gè) GPU 上分割 batch (批量),僅在 1 個(gè) GPU 上就會(huì)出現(xiàn) OOM (內(nèi)存溢出)。
它不能在 8 個(gè) A100 上運(yùn)行,因?yàn)樗褂玫氖?TensorFlow 1.x,與 Cuda 8+ 不兼容。
它不能在 8 個(gè) V100 (16GB) 上運(yùn)行,因?yàn)樗鼤?huì) OOM (內(nèi)存溢出)。
它只能在 8 個(gè) V100 (32GB) 上運(yùn)行,這種配置僅由 AWS 以
p3dn.24xlarge
實(shí)例的形式提供。
總體實(shí)現(xiàn)細(xì)節(jié)
我們現(xiàn)在深入探討與復(fù)現(xiàn) OAI 工作相關(guān)的技術(shù)實(shí)現(xiàn)細(xì)節(jié)。在這個(gè)部分,我們討論了一些基本細(xì)節(jié),例如獎(jiǎng)勵(lì)/值是如何生成的,以及響應(yīng)是如何生成的。以下是這些細(xì)節(jié),不按特定順序列出:
獎(jiǎng)勵(lì)模型和策略的價(jià)值頭將
query
和response
的連接作為輸入獎(jiǎng)勵(lì)模型和策略的價(jià)值頭 不 僅僅查看響應(yīng)。相反,它將
query
和response
連接在一起,作為query_response
(lm_human_preferences/rewards.py#L105-L107)。舉例來說,如果
query = "他在想某事,但他的眼神很難讀懂"。
,和response = "他看著他的左手,手臂伸在他的前面。"
,那么獎(jiǎng)勵(lì)模型和策略的價(jià)值會(huì)對query_response = "他在想某事,但他的眼神很難讀懂。他看著他的左手,手臂伸在他的前面。"
進(jìn)行前向傳遞,并產(chǎn)生形狀為(B, T, 1)
的獎(jiǎng)勵(lì)和價(jià)值,其中B
是 BS (批量大小),T
是序列長度,而1
代表獎(jiǎng)勵(lì)頭的輸出結(jié)構(gòu)的維度為 1 (lm_human_preferences/rewards.py#L105-L107, lm_human_preferences/policy.py#L111)。T
意味著每個(gè) token 都有與其和前文關(guān)聯(lián)的獎(jiǎng)勵(lì)。例如,eyes
token 將有一個(gè)與他在想某事,但他的眼神很難讀懂
相對應(yīng)的獎(jiǎng)勵(lì)。使用特殊的填充 token 來填充和截?cái)噍斎搿?/strong>
關(guān)于 HF 的 transformers — 填充 token 的注解。 根據(jù) (transformers#2630#issuecomment-578159876),在 GPT 和 GPT-2 的預(yù)訓(xùn)練期間沒有使用填充 token; 因此,transformer 的 gpt2 模型與其分詞器沒有關(guān)聯(lián)的官方填充 token。通常的做法是設(shè)置
tokenizer.pad_token = tokenizer.eos_token
,但在這項(xiàng)工作中,我們將區(qū)分這兩個(gè)特殊 token 以匹配 OAI 的原始設(shè)置,所以我們將使用tokenizer.add_special_tokens({"pad_token": "[PAD]"})
。OAI 為查詢
query_length
設(shè)置了固定的輸入長度; 它使用pad_token
填充 過短的序列 (lm_human_preferences/language/datasets.py#L66-L67),并 截?cái)?/strong> 過長的序列 (lm_human_preferences/language/datasets.py#L57)。詳見 此處 以獲取該概念的通用介紹。在填充輸入時(shí),OAI 使用了詞匯表之外的 token (lm_human_preferences/language/encodings.py#L56)。注意,沒有填充 token 是解碼器模型的默認(rèn)設(shè)置,因?yàn)樗鼈冊陬A(yù)訓(xùn)練期間使用“打包”訓(xùn)練,這意味著許多序列被連接并由 EOS token 分隔,這些序列的塊在預(yù)訓(xùn)練期間始終具有最大長度并被饋送到模型中。
當(dāng)把所有事物放在一起時(shí),這里有一個(gè)例子
?
相應(yīng)地調(diào)整填充 token 的位置索引
通常情況下,我們幾乎從不在 transformers 中傳遞
position_ids
。所有的遮蔽 (masking) 和移位 (shifting) logic 已經(jīng)實(shí)現(xiàn),例如,在generate
函數(shù)中 (需要永久的代碼鏈接)。在計(jì)算 logits 時(shí),OAI 的代碼通過適當(dāng)?shù)仄帘翁畛?token 來工作。這是通過找出與填充 token 相對應(yīng)的 token 索引來實(shí)現(xiàn)的 (lm_human_preferences/language/model.py#L296-L297),然后相應(yīng)地調(diào)整它們的位置索引 (lm_human_preferences/language/model.py#L320)。
例如,如果
query=[23073, 50259, 50259]
和response=[11, 339, 561]
,其中 (50259
是 OAI 的填充 token),它會(huì)創(chuàng)建位置索引為[[0 1 1 1 2 3]]
并且如下的 logits。注意填充 token 對應(yīng)的 logits 如何保持不變!這是我們在復(fù)制過程中應(yīng)該追求的效果。
關(guān)于 HF 的 transformers —
position_ids
和padding_side
的注解。 我們可以通過 1) 左填充和 2) 傳入適當(dāng)?shù)?position_ids
,使用 Hugging Face 的 transformer 復(fù)制精確的 logits:
關(guān)于 HF 的 transformers ——在
生成
過程中的position_ids
的注解: 在生成過程中,我們不應(yīng)傳入position_ids
,因?yàn)樵?transformers
中,position_ids
已經(jīng)以某種方式被調(diào)整了。當(dāng)我在生成過程中也傳入position_ids
時(shí),性能會(huì)災(zāi)難性地惡化。生成固定長度響應(yīng)的響應(yīng)生成不需要填充。
在響應(yīng)生成期間,OAI 使用
top_k=0, top_p=1.0
并僅在詞匯表上做分類樣本 (lm_human_preferences/language/sample.py#L43),代碼會(huì)一直采樣,直到生成固定長度的響應(yīng) (lm_human_preferences/policy.py#L103)。值得注意的是,即使遇到 EOS (序列結(jié)束) token ,它也會(huì)繼續(xù)采樣。關(guān)于 HF 的 transformers 的注解 — 在
eos_token
處采樣可能會(huì)停止: 在transformers
中,生成可能會(huì)在eos_token
處停止 (src/transformers/generation/utils.py#L2248-L2256),這與 OAI 的設(shè)置不同。為了對齊設(shè)置,我們需要設(shè)置pretrained_model.generation_config.eos_token_id = None, pretrained_model.generation_config.pad_token_id = None
。請注意,transformers.GenerationConfig(eos_token_id=None, pad_token_id=None, ...)
不起作用,因?yàn)?pretrained_model.generation_config
會(huì)覆蓋并設(shè)置一個(gè)eos_token
。
請注意,在較新的代碼庫 https://github.com/openai/summarize-from-feedback 中,當(dāng)遇到 EOS token 時(shí),OAI 確實(shí)會(huì)停止采樣 (summarize_from_feedback/utils/experiment_helpers.py#L19)。然而,在這項(xiàng)工作中,我們的目標(biāo)是進(jìn)行 1:1 的復(fù)刻,所以我們調(diào)整了設(shè)置,即使遇到 eos_token 也可以繼續(xù)采樣。
獎(jiǎng)勵(lì)模型和策略訓(xùn)練的學(xué)習(xí)率退火。
正如 Ziegler 等人 (2019) 建議的,獎(jiǎng)勵(lì)模型只訓(xùn)練一個(gè) epcho,以避免過度擬合有限量的人類注釋數(shù)據(jù) (例如,
descriptiveness
任務(wù)只有大約 5000 個(gè)標(biāo)簽)。在這個(gè)單一的 epcho 中,學(xué)習(xí)率會(huì)退火至零 (lm_human_preferences/train_reward.py#L249)。類似于獎(jiǎng)勵(lì)模型訓(xùn)練,策略訓(xùn)練的學(xué)習(xí)率也會(huì)退火至零 (lm_human_preferences/train_policy.py#L172-L173)。
為不同的進(jìn)程使用不同的種子
注: 我認(rèn)為數(shù)據(jù)集的洗牌 (shuffling) 存在一個(gè)錯(cuò)誤——由于某種原因,數(shù)據(jù)集是使用相同的種子進(jìn)行洗牌的 (lm_human_preferences/lm_tasks.py#L94-L97)。
在生成 8 個(gè) GPU 進(jìn)程進(jìn)行數(shù)據(jù)并行時(shí),OAI 為每個(gè)進(jìn)程設(shè)置了不同的隨機(jī)種子 (lm_human_preferences/utils/core.py#L108-L111)。在實(shí)現(xiàn)上,這是通過
local_seed = args.seed + process_rank * 100003
完成的。種子會(huì)讓模型產(chǎn)生不同的響應(yīng)并得到不同的分?jǐn)?shù),例如。
獎(jiǎng)勵(lì)模型實(shí)現(xiàn)細(xì)節(jié)
在本節(jié)中,我們討論了獎(jiǎng)勵(lì)模型特定的實(shí)現(xiàn)細(xì)節(jié)。我們討論了諸如獎(jiǎng)勵(lì)歸一化和層初始化等細(xì)節(jié)。以下是這些細(xì)節(jié),不按特定順序排列:
獎(jiǎng)勵(lì)模型只輸出最后一個(gè) token 的值。
請注意,在對
query
和response
的連接進(jìn)行前向傳遞后獲得的獎(jiǎng)勵(lì)將具有形狀(B, T, 1)
,其中B
是 BS(批量大小),T
是序列長度 (始終相同; 在 OAI 的設(shè)置中,它是query_length + response_length = 64 + 24 = 88
,用于風(fēng)格任務(wù),參見 launch.py#L9-L11),1
是獎(jiǎng)勵(lì)頭其維度為 1。對于 RLHF (Reinforcement Learning from Human Feedback,通過人類反饋進(jìn)行強(qiáng)化學(xué)習(xí)) 的目的,原始代碼庫提取最后一個(gè) token 的獎(jiǎng)勵(lì) (lm_human_preferences/rewards.py#L132),因此獎(jiǎng)勵(lì)將只具有形狀(B, 1)
。請注意,在較新的代碼庫 openai/summarize-from-feedback 中,OAI 在遇到 EOS token 時(shí)停止采樣 (summarize_from_feedback/utils/experiment_helpers.py#L19)。在提取獎(jiǎng)勵(lì)時(shí),它將確定
last_response_index
,即 EOS token 之前的索引 (#L11-L13),并在該索引處提取獎(jiǎng)勵(lì) (summarize_from_feedback/reward_model.py#L59)。但在此工作中,我們只是堅(jiān)持原始設(shè)置。獎(jiǎng)勵(lì)頭層初始化
獎(jiǎng)勵(lì)頭的權(quán)重是根據(jù) 初始化的 (lm_human_preferences/language/model.py#L368, lm_human_preferences/language/model.py#L251-L252)。這與 Stiennon 等人的設(shè)置相符,2020 年 (summarize_from_feedback/query_response_model.py#L106-L107) (附注,Stiennon 等人,2020 年在第 17 頁上有一個(gè)錯(cuò)字,表示分布是 沒有平方根)
獎(jiǎng)勵(lì)頭的 bias (偏置) 設(shè)為 0 (lm_human_preferences/language/model.py#L254)。
獎(jiǎng)勵(lì)模型的前后歸一化
在論文中,Ziegler 等人 (2019) 提到“為了保持訓(xùn)練過程中獎(jiǎng)勵(lì)模型的規(guī)模一致,我們將其歸一化,使其在 的情況下,均值為 0,方差為 1”。為了執(zhí)行歸一化過程,代碼首先創(chuàng)建了
reward_gain
和reward_bias
,以便可以通過reward = reward * reward_gain + reward_bias
來計(jì)算獎(jiǎng)勵(lì)值 (lm_human_preferences/rewards.py#L50-L51)。在執(zhí)行歸一化過程時(shí),代碼首先設(shè)置
reward_gain=1, reward_bias=0
(lm_human_preferences/train_reward.py#L211),然后從目標(biāo)數(shù)據(jù)集 (例如,bookcorpus, tldr, cnndm
) 中收集采樣查詢、完成的響應(yīng)和評估的獎(jiǎng)勵(lì)。接著,它得到評估獎(jiǎng)勵(lì)的 實(shí)證均值和標(biāo)準(zhǔn)差 (lm_human_preferences/train_reward.py#L162-L167),并嘗試計(jì)算reward_gain
和reward_bias
應(yīng)該是什么。我們用 來表示實(shí)證均值,用 表示實(shí)證標(biāo)準(zhǔn)差,用 表示
reward_gain
,用 表示reward_bias
,用 表示 目標(biāo)均值,用 表示 目標(biāo)標(biāo)準(zhǔn)差。然后我們有以下公式。? ? ?
然后在獎(jiǎng)勵(lì)模型訓(xùn)練的 前 和 后 應(yīng)用歸一化過程 (lm_human_preferences/train_reward.py#L232-L234,lm_human_preferences/train_reward.py#L252-L254)。
請注意,我們?yōu)闅w一化目的生成的響應(yīng)
來自預(yù)訓(xùn)練的語言模型
。模型?
被固定為參考,并且在獎(jiǎng)勵(lì)學(xué)習(xí)中不會(huì)更新 (lm_human_preferences/train_reward.py#L286C1-L286C31)。
策略訓(xùn)練實(shí)現(xiàn)細(xì)節(jié)
在本節(jié)中,我們將深入探討諸如層初始化、數(shù)據(jù)后處理和 dropout 設(shè)置等細(xì)節(jié)。我們還將探討一些技術(shù),如拒絕采樣和獎(jiǎng)勵(lì) “白化”,以及自適應(yīng) KL。以下是這些細(xì)節(jié),排列不分先后:
通過采樣溫度來縮放 logits
在計(jì)算響應(yīng)的對數(shù)概率時(shí),模型首先輸出響應(yīng)中 token 的 logits,然后用采樣溫度除以這些 logits (lm_human_preferences/policy.py#L121)。即
logits /= self.temperature
在一個(gè)非正式的測試中,我們發(fā)現(xiàn)如果不進(jìn)行此縮放,KL 散度會(huì)比預(yù)期更快地上升,性能會(huì)下降。
價(jià)值頭層的初始化
價(jià)值頭的權(quán)重是根據(jù) 進(jìn)行初始化的 (lm_human_preferences/language/model.py#L368、lm_human_preferences/language/model.py#L251-L252)。
獎(jiǎng)勵(lì)頭的 bias (偏置) 設(shè)置為 0 (lm_human_preferences/language/model.py#L254)。
選擇以句號開始和結(jié)束的查詢文本
嘗試僅在
start_text="."
之后選擇文本 (lm_human_preferences/language/datasets.py#L51)嘗試在
end_text="."
之前選擇文本 (lm_human_preferences/language/datasets.py#L61)然后填充文本 (lm_human_preferences/language/datasets.py#L66-L67)
這是數(shù)據(jù)預(yù)處理的一部分:
在運(yùn)行
openai/lm-human-preferences
時(shí),OAI 的數(shù)據(jù)集部分損壞/丟失 (openai/lm-human-preferences/issues/17#issuecomment-104405149),因此我們不得不用類似的 HF 數(shù)據(jù)集替換它們,這可能會(huì)或可能不會(huì)導(dǎo)致性能差異。對于書籍?dāng)?shù)據(jù)集,我們使用 https://huggingface.co/datasets/bookcorpus,我們發(fā)現(xiàn)沒有必要提取以句號開始和結(jié)束的句子,因?yàn)閿?shù)據(jù)集已經(jīng)是這樣預(yù)處理過的 (例如,
"usually , he would be tearing around the living room , playing with his toys."
) 為此,我們?yōu)?sentiment
和descriptiveness
任務(wù)設(shè)置start_text=None, end_text=None
。禁用 dropout
Ziegler 等人 (2019) 建議,“我們在策略訓(xùn)練中不使用 dropout?!?這也在代碼中實(shí)現(xiàn)了 (lm_human_preferences/policy.py#L48)。
拒絕采樣
token 截?cái)?/strong>: 我們想要在第一個(gè)出現(xiàn)在響應(yīng)的
truncate_after
位置之后的truncate_token
處截?cái)?(lm_human_preferences/train_policy.py#L378)。在截?cái)囗憫?yīng)上運(yùn)行獎(jiǎng)勵(lì)模型: 在 token 截?cái)噙^程將響應(yīng)截?cái)嗪螅a然后在 截?cái)嗟捻憫?yīng) 上運(yùn)行獎(jiǎng)勵(lì)模型。
拒絕采樣: 如果在第 16 和 24 個(gè) token 之間沒有句號,那么將響應(yīng)的分?jǐn)?shù)替換為固定的低值 (例如 -1) (lm_human_preferences/train_policy.py#L384、lm_human_preferences/train_policy.py#L384-L402)。
在
descriptiveness
中舉一些例子:

從我們的復(fù)制中提取的樣本 https://wandb.ai/openrlbenchmark/lm_human_preference_details/runs/djf8yymv/logs。請注意,第 1 和第 3 個(gè)示例在句號后有太多 token,因此其分?jǐn)?shù)被替換為 -1。
代碼注釋: “中心示例: 將截?cái)?token 后的所有 token 替換為填充 token”
代碼注釋: “中心示例: 確保樣本包含
truncate_token
“代碼注釋: “只對通過該功能的響應(yīng)進(jìn)行人類查詢”
Ziegler 等人 (2019) 建議: “我們使用拒絕采樣來確保在第 16 和 24 個(gè) token 之間有一個(gè)句號,然后在那個(gè)句號處截?cái)?(這是‘句子結(jié)束’的粗略近似。我們選擇它是因?yàn)樗苋菀准傻?RL 循環(huán)中,即使是粗略的近似也足以使人類評估任務(wù)變得稍微容易一些)。在 RL 微調(diào)期間,我們對沒有這樣的句號的延續(xù)給予固定獎(jiǎng)勵(lì) -1?!?/p>
具體來說,通過以下步驟實(shí)現(xiàn)此目的:
折現(xiàn)因子 (discount factor) = 1
折現(xiàn)因子 設(shè)置為 1 (lm_human_preferences/train_policy.py#L56),這意味著未來的獎(jiǎng)勵(lì)與即時(shí)獎(jiǎng)勵(lì)具有相同的權(quán)重。
訓(xùn)練循環(huán)的術(shù)語: PPO 中的批次和小批次
OAI 使用以下訓(xùn)練循環(huán) (lm_human_preferences/train_policy.py#L184-L192)。注意: 我們額外添加了
micro_batch_size
來幫助處理梯度累積的情況。在每個(gè)時(shí)期,它都會(huì)洗牌批次索引。
基于每個(gè)標(biāo)記的 KL 懲罰
在第一個(gè) PPO 更新時(shí)期和小批次更新時(shí),激活策略將具有相同的對數(shù)概率
new_logprobs=[-3.3213, -4.9980, -3.8690]
。因此,每個(gè)標(biāo)記的 KL 懲罰將為kl = new_logprobs - logprobs = [0., 0., 0.]
。但是,在第一個(gè)梯度反向傳播后,我們可能會(huì)得到
new_logprob=[3.3213, -4.9980, -3.8690]
,因此每個(gè)標(biāo)記的 KL 懲罰變?yōu)?kl = new_logprobs - logprobs = [-0.3315, -0.0426, 0.6351]
。隨后,
non_score_reward = beta * kl
,其中beta
是 KL 懲罰系數(shù) ,它被添加到從獎(jiǎng)勵(lì)模型獲得的score
中,以創(chuàng)建用于訓(xùn)練的rewards
。score
僅在每個(gè)回合 ( episode ) 結(jié)束時(shí)給出,可能類似于[0.4]
,然后我們有rewards = [beta * -0.3315, beta * -0.0426, beta * 0.6351 + 0.4]
。代碼為獎(jiǎng)勵(lì)添加了每個(gè)標(biāo)記的 KL 懲罰 (lm_human_preferences/train_policy.py#L150-L153),以阻止策略與原始策略差異過大。
以 “usually, he would” 為例,它被標(biāo)記化為
[23073, 11, 339, 561]
。假設(shè)我們使用[23073]
作為查詢,[11, 339, 561]
作為響應(yīng)。然后在默認(rèn)的gpt2
參數(shù)下,響應(yīng)標(biāo)記將具有參考策略的對數(shù)概率logprobs=[-3.3213, -4.9980, -3.8690]
。每個(gè)小批次的獎(jiǎng)勵(lì)和優(yōu)勢白化,可選擇均值平移
OAI 實(shí)現(xiàn)了一個(gè)名為
whiten
的函數(shù),如下所示,基本上通過減去其均值然后除以其標(biāo)準(zhǔn)差來對values
進(jìn)行歸一化。可選地,whiten
可以通過shift_mean=True
將白化后的values
平移到均值。
在每個(gè)小批次中,OAI 使用
whiten(rewards, shift_mean=False)
對獎(jiǎng)勵(lì)進(jìn)行白化,不對均值進(jìn)行平移處理 (lm_human_preferences/train_policy.py#L325),并使用平移后的均值對優(yōu)勢進(jìn)行白化whiten(advantages)
(lm_human_preferences/train_policy.py#L338)。優(yōu)化注意事項(xiàng): 如果小批次的數(shù)量為一 (在此復(fù)現(xiàn)中是這種情況),我們只需要對獎(jiǎng)勵(lì)進(jìn)行白化、計(jì)算并對優(yōu)勢進(jìn)行一次白化,因?yàn)樗鼈兊闹挡粫?huì)改變。
TensorFlow vs PyTorch 注意事項(xiàng):
tf.moments
與torch.var
的不同行為: 由于方差計(jì)算方式不同,Torch 和 TensorFlow 中的白化行為不同:
裁剪值函數(shù)
與原始的 PPO 一樣 (baselines/ppo2/model.py#L68-L75),值函數(shù)被裁剪 (lm_human_preferences/train_policy.py#L343-L348),方式與策略目標(biāo)類似。
自適應(yīng) KL 散度
KL 散度懲罰系數(shù) 根據(jù)當(dāng)前策略與先前策略之間的 KL 散度自適應(yīng)修改。如果 KL 散度超出預(yù)定的目標(biāo)范圍,則調(diào)整懲罰系數(shù)以使其更接近目標(biāo)范圍 (lm_human_preferences/train_policy.py#L115-L124)。它的實(shí)現(xiàn)如下:
對于本工作中研究的
sentiment
和descriptiveness
任務(wù),我們使用了init_kl_coef=0.15, hparams.target=6, hparams.horizon=10000
。
PyTorch Adam 優(yōu)化器與 RLHF 相關(guān)的數(shù)值問題
這個(gè)實(shí)現(xiàn)細(xì)節(jié)非常有趣,值得專門一節(jié)來討論。
PyTorch 的 Adam 優(yōu)化器 (torch.optim.Adam.html) 與 TensorFlow 的 Adam 優(yōu)化器 (TF1 Adam 在 tensorflow/v1.15.2/adam.py,TF2 Adam 在 keras/adam.py#L26-L220) 有不同的實(shí)現(xiàn)方式。具體來說, PyTorch 遵循了 Kingma 和 Ba 的 Adam 論文中的算法 1 (arxiv/1412.6980),而 TensorFlow 使用了該論文第 2.1 節(jié)前的公式,這里提到的
epsilon
在論文中稱為epsilon hat
。在偽代碼比較中,我們有以下內(nèi)容:
讓我們比較一下 PyTorch 風(fēng)格和 TensorFlow 風(fēng)格 Adam 的更新方程。按照 Adam 論文 (Kingma 和 Ba,2014) 的符號表示,我們可以得到 PyTorch Adam (Kingma 和 Ba 論文的算法 1) 和 TensorFlow 風(fēng)格 Adam (Kingma 和 Ba 論文第 2.1 節(jié)前的公式) 的梯度更新規(guī)則如下:
上面的方程強(qiáng)調(diào)了 PyTorch 和 TensorFlow 實(shí)現(xiàn)之間的區(qū)別在于它們的 歸一化項(xiàng),即?
和 ?
。如果我們設(shè)置
,則這兩個(gè)版本是等價(jià)的。然而,在 PyTorch 和 TensorFlow 的 API 中,我們只能通過
eps
參數(shù)設(shè)置(PyTorch) 和 ?
(TensorFlow),從而導(dǎo)致它們的更新方程存在差異。如果我們將
和 ?
都設(shè)置為相同的值,比如 1e-5 會(huì)發(fā)生什么?那么對于 TensorFlow Adam,歸一化項(xiàng)?
就是一個(gè)常數(shù)。但對于 PyTorch Adam,歸一化項(xiàng)?
隨著時(shí)間的推移而變化。重要的是,當(dāng)時(shí)間步?
較小時(shí),該項(xiàng)?
明顯小于 1e-5,隨著時(shí)間步增加,逐漸接近 1e-5。下面的圖表比較了這兩個(gè)歸一化項(xiàng)隨著時(shí)間步的變化情況:

上圖顯示,如果我們在 PyTorch Adam 和 TensorFlow Adam 中設(shè)置相同的
eps
,那么在訓(xùn)練的早期階段,PyTorch Adam 使用的歸一化項(xiàng)要比 TensorFlow Adam 小得多。換句話說,PyTorch Adam 在訓(xùn)練的早期采用了 更激進(jìn)的梯度更新。我們的實(shí)驗(yàn)證明了這一發(fā)現(xiàn),如下所示。這對復(fù)現(xiàn)性和性能有何影響?為了保持設(shè)置一致,我們記錄了來自 https://github.com/openai/lm-human-preferences 的原始查詢、響應(yīng)和獎(jiǎng)勵(lì),并將它們保存在 https://huggingface.co/datasets/vwxyzjn/lm-human-preferences-debug/tree/main 中。我還記錄了使用 TF1 的
AdamOptimizer
優(yōu)化器的前兩個(gè)訓(xùn)練周期的指標(biāo)作為基準(zhǔn)。以下是一些關(guān)鍵指標(biāo):? ?

由于某種原因, PyTorch 的 Adam 生成了更激進(jìn)的更新。以下是一些證據(jù):
PyTorch 的 Adam 的 logprob_diff_var 高出 6 倍。這里的
logprobs_diff = new_logprobs - logprobs
是經(jīng)過兩個(gè)訓(xùn)練周期后,初始策略和當(dāng)前策略之間的標(biāo)記對數(shù)概率差異。具有更大的logprob_diff_var
意味著對數(shù)概率變化的幅度比 OAI 的 TF1 Adam 大。PyTorch 的 Adam 呈現(xiàn)更極端的最大和最小比率。這里的
ratio = torch.exp(logprobs_diff)
。具有ratio_max=1.8121057748794556
意味著對于某些標(biāo)記,在當(dāng)前策略下抽取該標(biāo)記的概率要比 OAI 的 TF1 Adam 高 1.8 倍,而后者僅為 1.2 倍。**更大的
policy/approxkl
和policy/clipfrac
**。由于激進(jìn)的更新,比率被剪切的次數(shù) 多 4.4 倍,近似的 KL 散度大 6 倍。這種激進(jìn)的更新可能會(huì)導(dǎo)致進(jìn)一步的問題。例如,PyTorch 的
Adam
中的logprob_diff_mean
要大 1.7 倍,這將對下一個(gè)獎(jiǎng)勵(lì)計(jì)算中的 KL 懲罰產(chǎn)生 1.7 倍大的影響; 這可能會(huì)被累積。實(shí)際上,這可能與著名的 KL 散度問題有關(guān)—— KL 懲罰遠(yuǎn)大于它應(yīng)該的值,模型可能會(huì)更多地關(guān)注它并進(jìn)行更多優(yōu)化,從而導(dǎo)致負(fù)的 KL 散度。更大的模型受到更多影響。我們進(jìn)行了一些實(shí)驗(yàn),比較了 PyTorch 的
Adam
(代號pt_adam
) 和我們自定義的類似 TensorFlow 風(fēng)格的 Adam (代號tf_adam
) 在gpt2
和gpt2-xl
上的性能。我們發(fā)現(xiàn)在gpt2
下性能大致相似; 但是在gpt2-xl
下,我們觀察到了更激進(jìn)的更新,這意味著更大的模型受到了更多的影響。


當(dāng)在
gpt2-xl
中初始策略更新更為激進(jìn)時(shí),訓(xùn)練動(dòng)態(tài)會(huì)受到影響。例如,我們發(fā)現(xiàn)使用pt_adam
時(shí),sentiment
的objective/kl
和objective/scores
峰值要大得多, 在其中一個(gè)隨機(jī)種子中,最大的 KL 值達(dá)到了 17.5 ,這表明了不希望的過度優(yōu)化。此外,由于 KL 更大,許多其他訓(xùn)練指標(biāo)也受到影響。例如,我們觀察到更大的
clipfrac
(ratio
被 PPO 的目標(biāo)裁剪系數(shù) 0.2 裁剪的時(shí)間比例) 和approxkl
。
局限性
注意到這項(xiàng)工作沒有嘗試復(fù)現(xiàn) CNN DM 中的摘要工作。這是因?yàn)槲覀儼l(fā)現(xiàn)訓(xùn)練耗時(shí)且不穩(wěn)定。
我們的特定訓(xùn)練運(yùn)行顯示 GPU 利用率較低 (約 30%),因此一個(gè)訓(xùn)練運(yùn)行需要近 4 天的時(shí)間,這非常昂貴 (只有 AWS 銷售 p3dn.24xlarge,每小時(shí)費(fèi)用為 31.212 美元)。
此外,訓(xùn)練也很不穩(wěn)定。雖然獎(jiǎng)勵(lì)值上升,但我們發(fā)現(xiàn)難以復(fù)現(xiàn) Ziegler 等人 (2019 年) 報(bào)告的“智能復(fù)制”行為。以下是一些樣本輸出 — 顯然,智能體出現(xiàn)了某種程度的過擬合。請查看 https://wandb.ai/openrlbenchmark/lm-human-preferences/runs/1ab47rqi/logs 以獲取更完整的日志。


總結(jié)
在這項(xiàng)工作中,我們深入研究了 OpenAI 的原始 RLHF (Reinforcement Learning from Human Feedback) 代碼庫,并編制了其實(shí)施細(xì)節(jié)的列表。我們還創(chuàng)建了一個(gè)最小的基礎(chǔ)版本,當(dāng)數(shù)據(jù)集和超參數(shù)受控制時(shí),可以復(fù)現(xiàn)與 OpenAI 原始 RLHF 代碼庫相同的學(xué)習(xí)曲線。此外,我們還識(shí)別了一些令人驚訝的實(shí)施細(xì)節(jié),比如 Adam 優(yōu)化器的設(shè)置,它會(huì)導(dǎo)致在 RLHF 訓(xùn)練的早期出現(xiàn)激進(jìn)的更新。
致謝
這項(xiàng)工作得到了 Hugging Face 的 Big Science 集群的支持 ??。我們還感謝 @lewtun 和 @natolambert 的建設(shè)性討論。
Bibtex
英文原文: https://hf.co/blog/the_n_implementation_details_of_rlhf_with_ppo
原文作者: Shengyi Costa Huang, Tianlin Liu, Leandro von We
譯者: innovation64
審校/排版: zhongdongy (阿東)