This DataCollator is purpose-made for ChatML templates. Referenced: https://medium.com/@xuebinbin12/fine-tuning-chat-based-llm-with-multi-turn-conversational-data-part-i-d8c64d01a20d
| 12 | |
| 13 | @dataclass |
| 14 | class DataCollatorForCausalLMDecoderOnlyChatML(object): |
| 15 | |
| 16 | ''' |
| 17 | This DataCollator is purpose-made for ChatML templates. |
| 18 | Referenced: https://medium.com/@xuebinbin12/fine-tuning-chat-based-llm-with-multi-turn-conversational-data-part-i-d8c64d01a20d |
| 19 | ''' |
| 20 | tokenizer: PreTrainedTokenizer |
| 21 | target_max_len: int |
| 22 | |
| 23 | # Decoder only doesn't require source source_max_length |
| 24 | # source_max_length: int |
| 25 | |
| 26 | # `train_on_source` in this context refers to calculating prediction losses on the "user" prompts |
| 27 | # In some cases where there are things to learn from the user prompts, this might help. |
| 28 | # However in some finetuning dataset, the user prompts mimick a real-world poor quality prompts. |
| 29 | # And usually doesn't contain anything useful. Therefore, prediction losses should be ignored, |
| 30 | # So that the model doesn't learn to mimick it. |
| 31 | train_on_source: bool |
| 32 | add_special_tokens: bool |
| 33 | |
| 34 | def __call__(self, instances: Sequence[Dict]) -> Dict[str, torch.Tensor]: |
| 35 | |
| 36 | if self.train_on_source: |
| 37 | message = 'In some cases where there are things to learn from the user prompts, this might help.' |
| 38 | message += 'However in some finetuning dataset, the user prompts mimick a real-world poor quality prompts.' |
| 39 | message += 'And usually doesn\'t contain anything useful. Therefore, prediction losses should be ignored.' |
| 40 | message += 'so that the model doesn\'t learn to mimick it.' |
| 41 | match input(f'{message}\n\nAre you sure you want to continue? (yes/no): '): |
| 42 | case 'yes': |
| 43 | pass |
| 44 | case _: |
| 45 | exit() |
| 46 | |
| 47 | # In decoder-only model, we concat source and targets |
| 48 | contents: List[str] = [] |
| 49 | |
| 50 | for example in instances: |
| 51 | content = example['input'] + example['output'] |
| 52 | contents.append(content) |
| 53 | |
| 54 | # Default is to not train on source, so we ignore losses on the source with IGNORE_INDEX |
| 55 | if not self.train_on_source: |
| 56 | |
| 57 | # identify the start and end of the sequence to ignore losses |
| 58 | # <|im_start|>user\nhello~<|im_end|>\n<|im_start|>assistant\nHow may I help?<|im_end|> |
| 59 | # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 60 | # The ^ represents the positions that we will apply IGNORE_INDEX |
| 61 | |
| 62 | # To do the ignore_index for cross-entropy loss, we need the start tokens and the end tokens to ignore |
| 63 | start_tokens = self.tokenizer.encode( |
| 64 | '<|im_start|>user\n', |
| 65 | max_length=999, |
| 66 | truncation=True, |
| 67 | add_special_tokens=False, |
| 68 | ) |
| 69 | end_tokens = self.tokenizer.encode( |
| 70 | '<|im_start|>assistant\n', |
| 71 | max_length=999, |
nothing calls this directly
no outgoing calls
no test coverage detected