iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0

2. 讀取SFT Dataset

除了eostoken以外SFT額外需要一個bostoken來將promptanswer切分開;並且在讀取訓練資料時,必須要檢查每筆資料的長度,如果promptanswer分別超過對應的長度限制,必須將多出來的字去除。

這邊刪除超出長度資料的方法我覺得有些疑惑,因為非常多的訓練資料都是屬於prompt很短但answer很長的類型;之後可能會去查一下如果直接去卡len(prompts+bos+answer+eos)==len(input_id)<=max_length的方法是否可行。

class SFTDataset(Dataset):
    def __init__(self,df,tokenizer
                 ,max_length=256
                 ,prompt_max_len=128
                 ,answer_max_len=128)
        self.prompt_max_len = prompt_max_len 
        self.answer_max_len = answer_max_len
        #
        self.tokenizer = tokenizer
        self.bos=self.tokenizer.special_tokens['<bos>']
        self.eos=self.tokenizer.special_tokens['<eos>']
        
    def __getitem__(self, index: int):
        # adding special token, cut prompt and answer tokens length 
        sample = self.df.iloc[index]
        prompt = self.tokenizer.encode(sample['prompt'],add_special_tokens=False)
        answer = self.tokenizer.encode(sample['answer'],add_special_tokens=False)
        if len(prompt) > self.prompt_max_len:
            prompt = prompt[:self.prompt_max_len-2]
        if len(answer) > self.answer_max_len:
            answer = answer[:self.answer_max_len-2]
        input_id=prompt+[self.bos]+answer+[self.eos]
        ......

前面針對訓練資料過長的情況把超出限制的tokens刪除,接下來這裡則是把長度少於max_length的訓練資料做padding

依據padding時使用的token_id,在訓練實際算loss時,必須將該padding_token_id給到loss function
F.cross_entropy(..., ignore_index=<padding_token_id>, ...)
這邊的<padding_token_id>=0

之後產生最終訓練所需的Tensor,這邊的X,Y與pretrain階段的邏輯一樣,Y就是X位移一步以後的結果:

  • X = (prompt+bos+answer+eos+padding)[:-1]
  • Y = (prompt+bos+answer+eos+padding)[1:]

比較不同的是,在SFT訓練時,會需要額外的loss_maskX對應到prompt+bos這段的loss以及padding對應的loss全部遮擋掉:

  • loss_mask = [0]*context_length+[1]*(len(input_id[mask_position+1:-pad_len]))
  • loss_mask = loss_mask[:-1]

我以為loss_mask會是把Y對應到prompt+bos這段的loss以及padding對應的loss全部遮擋掉,也就是loss_mask = loss_mask[:-1];但為什麼不是這樣我可能會去查一下,應該有什麼原因。

class SFTDataset(Dataset):
    ......
    def __getitem__(self, index: int):
        # adding special token, cut prompt and answer tokens length
        ...
        # padding, generating X, Y, loss_mask
        context_length = input_id.index(self.bos)
        mask_position = context_length - 1
        pad_len = self.max_length - len(input_id)
        input_id = input_id + [0] * pad_len
        if pad_len==0:
            loss_mask = [0]*context_length+[1]*(len(input_id[mask_position+1:])) + [0]*pad_len
        else:
            loss_mask = [0]*context_length+[1]*(len(input_id[mask_position+1:-pad_len])) + [0]*pad_len
        #
        input_id=np.array(input_id)
        X=np.array(input_id[:-1]).astype(np.int64)
        Y=np.array(input_id[1:]).astype(np.int64)
        loss_mask=np.array(loss_mask[:-1])
        #
        return torch.from_numpy(X),torch.from_numpy(Y),torch.from_numpy(loss_mask)

小結

dataset讀取這邊有兩個做法比較不符合我的預期,需要額外看一下這麼做的原因:

  1. 刪除過長prompt,answertokens的方式
  2. loss_mask的index為什麼是對應X而不是Y

上一篇
Day 14 - Baby LLama2 Chinese (8) SFT階段
下一篇
Day 16 - Baby LLama2 Chinese (10) SFT階段
系列文
用單張顯卡探索大型語言模型的奧秘30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言