1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
| 第一次加载数据是在_load_single_dataset中的load_dataset,此时拿到的是 (Pdb) dataset Dataset({ features: ['system', 'instruction', 'output', 'history'], num_rows: 50000 }) 内部走到数据脚本中_generate_examples里面的yield,然后保存起来 yield key, { "system": "You are a helpful assistant.", "instruction": prompt, "output": json.dumps(data['label'], ensure_ascii=False), "history": [], }
第二次在align_dataset def align_dataset( dataset: Union["Dataset", "IterableDataset"], dataset_attr: "DatasetAttr", data_args: "DataArguments", training_args: "Seq2SeqTrainingArguments", ) -> Union["Dataset", "IterableDataset"]: r"""Align the dataset to a specific format.
Aligned dataset: _prompt: [{"role": "user", "content": "..."}] * (2T - 1) _response: [{"role": "assistant", "content": "..."}] * N (N > 1 for ranking dataset) _system: "..." _tools: "..." _images: [] _videos: [] _audios: [] """ column_names = list(next(iter(dataset)).keys()) kwargs = {} if not data_args.streaming: kwargs = dict( num_proc=data_args.preprocessing_num_workers, load_from_cache_file=(not data_args.overwrite_cache) or (training_args.local_process_index != 0), desc="Converting format of dataset", )
dataset_converter = get_dataset_converter(dataset_attr.formatting, dataset_attr, data_args) return dataset.map( dataset_converter, batched=False, remove_columns=column_names, **kwargs, ) (Pdb) n > /root/ruixinspace/zyt/LLaMA-Factory-0.9.3/src/llamafactory/data/converter.py(279)align_dataset() -> return dataset.map( (Pdb) column_names ['system', 'instruction', 'output', 'history'] (Pdb) data_args.streaming False (Pdb) kwargs {'num_proc': 32, 'load_from_cache_file': False, 'desc': 'Converting format of dataset'} (Pdb) dataset_converter AlpacaDatasetConverter(dataset_attr=amh_shipper_zyt, data_args=DataArguments(template='qwen', dataset=['amh_shipper_zyt'], eval_dataset=['amh_shipper_zyt_val'], dataset_dir='data', media_dir='data', cutoff_len=4096, train_on_prompt=False, mask_history=False, streaming=False, buffer_size=16384, mix_strategy='concat', interleave_probs=None, overwrite_cache=True, preprocessing_batch_size=1000, preprocessing_num_workers=32, max_samples=None, eval_num_beams=None, ignore_pad_token_for_loss=True, val_size=0.0, eval_on_each_dataset=False, packing=False, neat_packing=False, tool_format=None, default_system=None, enable_thinking=True, tokenized_path=None, data_shared_file_system=False)) 默认的formatting就是alpaca,这里实际就是再进行一次映射,利用AlpacaDatasetConverter映射成如下形式 { "_prompt": prompt, "_response": response, "_system": example[self.dataset_attr.system] if self.dataset_attr.system else "", "_tools": example[self.dataset_attr.tools] if self.dataset_attr.tools else "", "_images": self._find_medias(example[self.dataset_attr.images]) if self.dataset_attr.images else None, "_videos": self._find_medias(example[self.dataset_attr.videos]) if self.dataset_attr.videos else None, "_audios": self._find_medias(example[self.dataset_attr.audios]) if self.dataset_attr.audios else None, }
也就是_get_merged_dataset得到 (Pdb) datasets.values() dict_values([Dataset({ features: ['_prompt', '_response', '_system', '_tools', '_images', '_videos', '_audios'], num_rows: 50000 })])
第三次是在_get_preprocessed_dataset里面的dataset.map -> dataset = dataset.map( (Pdb) kwargs {'num_proc': 32, 'load_from_cache_file': False, 'desc': 'Running tokenizer on dataset'} (Pdb) column_names ['_prompt', '_response', '_system', '_tools', '_images', '_videos', '_audios'] (Pdb) dataset_processor SupervisedDatasetProcessor(template=Template(format_user=StringFormatter(slots=['<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n'], tool_format=None), format_assistant=StringFormatter(slots=['{{content}}<|im_end|>\n'], tool_format=None), format_system=StringFormatter(slots=['<|im_start|>system\n{{content}}<|im_end|>\n'], tool_format=None), format_function=FunctionFormatter(slots=['{{content}}<|im_end|>\n'], tool_format='qwen'), format_observation=StringFormatter(slots=['<|im_start|>user\n<tool_response>\n{{content}}\n</tool_response><|im_end|>\n<|im_start|>assistant\n'], tool_format=None), format_tools=ToolFormatter(slots=[], tool_format='qwen'), format_prefix=EmptyFormatter(slots=[], tool_format=None), default_system='You are Qwen, created by Alibaba Cloud. You are a helpful assistant.', stop_words=['<|im_end|>'], thought_words=('<think>', '</think>'), efficient_eos=False, replace_eos=True, replace_jinja_template=False, enable_thinking=True, mm_plugin=BasePlugin(image_token=None, video_token=None, audio_token=None, expand_mm_tokens=True)), tokenizer=Qwen2TokenizerFast(name_or_path='/root/ruixinspace/zyt/Qwen3-0.6B', vocab_size=151643, model_max_length=131072, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'eos_token': '<|im_end|>', 'pad_token': '<|endoftext|>', 'additional_special_tokens': ['<|im_start|>', '<|im_end|>', '<|object_ref_start|>', '<|object_ref_end|>', '<|box_start|>', '<|box_end|>', '<|quad_start|>', '<|quad_end|>', '<|vision_start|>', '<|vision_end|>', '<|vision_pad|>', '<|image_pad|>', '<|video_pad|>']}, clean_up_tokenization_spaces=False, added_tokens_decoder={ 151643: AddedToken("<|endoftext|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151644: AddedToken("<|im_start|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151645: AddedToken("<|im_end|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151646: AddedToken("<|object_ref_start|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151647: AddedToken("<|object_ref_end|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151648: AddedToken("<|box_start|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151649: AddedToken("<|box_end|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151650: AddedToken("<|quad_start|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151651: AddedToken("<|quad_end|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151652: AddedToken("<|vision_start|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151653: AddedToken("<|vision_end|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151654: AddedToken("<|vision_pad|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151655: AddedToken("<|image_pad|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151656: AddedToken("<|video_pad|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=True), 151657: AddedToken("<tool_call>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151658: AddedToken("</tool_call>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151659: AddedToken("<|fim_prefix|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151660: AddedToken("<|fim_middle|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151661: AddedToken("<|fim_suffix|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151662: AddedToken("<|fim_pad|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151663: AddedToken("<|repo_name|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151664: AddedToken("<|file_sep|>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151665: AddedToken("<tool_response>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151666: AddedToken("</tool_response>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151667: AddedToken("<think>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), 151668: AddedToken("</think>", rstrip=False, lstrip=False, single_word=False, normalized=False, special=False), } ), processor=None, data_args=DataArguments(template='qwen', dataset=['amh_shipper_zyt'], eval_dataset=['amh_shipper_zyt_val'], dataset_dir='data', media_dir='data', cutoff_len=4096, train_on_prompt=False, mask_history=False, streaming=False, buffer_size=16384, mix_strategy='concat', interleave_probs=None, overwrite_cache=True, preprocessing_batch_size=1000, preprocessing_num_workers=32, max_samples=None, eval_num_beams=None, ignore_pad_token_for_loss=True, val_size=0.0, eval_on_each_dataset=False, packing=False, neat_packing=False, tool_format=None, default_system=None, enable_thinking=True, tokenized_path=None, data_shared_file_system=False)) 这里会涉及到template的注册,事先注册了很多template,我们这里关注qwen即可 register_template( name="qwen", format_user=StringFormatter(slots=["<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n"]), format_assistant=StringFormatter(slots=["{{content}}<|im_end|>\n"]), format_system=StringFormatter(slots=["<|im_start|>system\n{{content}}<|im_end|>\n"]), format_function=FunctionFormatter(slots=["{{content}}<|im_end|>\n"], tool_format="qwen"), format_observation=StringFormatter( slots=["<|im_start|>user\n<tool_response>\n{{content}}\n</tool_response><|im_end|>\n<|im_start|>assistant\n"] ), format_tools=ToolFormatter(tool_format="qwen"), default_system="You are Qwen, created by Alibaba Cloud. You are a helpful assistant.", stop_words=["<|im_end|>"], replace_eos=True, )
(Pdb) template Template(format_user=StringFormatter(slots=['<|im_start|>user\n{{content}}<|im_end|>\n<|im_start|>assistant\n'], tool_format=None), format_assistant=StringFormatter(slots=['{{content}}<|im_end|>\n'], tool_format=None), format_system=StringFormatter(slots=['<|im_start|>system\n{{content}}<|im_end|>\n'], tool_format=None), format_function=FunctionFormatter(slots=['{{content}}<|im_end|>\n'], tool_format='qwen'), format_observation=StringFormatter(slots=['<|im_start|>user\n<tool_response>\n{{content}}\n</tool_response><|im_end|>\n<|im_start|>assistant\n'], tool_format=None), format_tools=ToolFormatter(slots=[], tool_format='qwen'), format_prefix=EmptyFormatter(slots=[], tool_format=None), default_system='You are Qwen, created by Alibaba Cloud. You are a helpful assistant.', stop_words=['<|im_end|>'], thought_words=('<think>', '</think>'), efficient_eos=False, replace_eos=True, replace_jinja_template=False, enable_thinking=True, mm_plugin=BasePlugin(image_token=None, video_token=None, audio_token=None, expand_mm_tokens=True)) (Pdb) template.mm_plugin BasePlugin(image_token=None, video_token=None, audio_token=None, expand_mm_tokens=True)
BasePlugin只是做一些校验,直接跳过。重点在SupervisedDatasetProcessor内部的preprocess_dataset preprocess_dataset-->_encode_data_example,在内部会计算input_ids和labels,也就是输入和输出。 这里涉及到template的注册,以及内部的一系列_encode过程,实际上在内部只是在拼接不同role的message格式,每个content注入到类似于<|im_start|>assistant\n{{content}}<|im_end|>\n等 此时拿到的数据是 (Pdb) dataset Dataset({ features: ['input_ids', 'attention_mask', 'labels', 'images', 'videos', 'audios'], num_rows: 50000 })
这里还有一个细节是,SupervisedDatasetProcessor在进行_encode_data_example的时候,实际上每个元素的'input_ids', 'attention_mask', 'labels'长度都是一样长的,这是为了方便对齐统一计算交叉熵loss。 (Pdb) len(train[1]['input_ids']) 2381 (Pdb) len(train[1]['labels']) 2381 (Pdb) len(train[1]['attention_mask']) 2381 但是你会发现labels的末尾包含了一个198的换行符,并不是<|im_end|>,也就是说,训练的时候,见到<|im_end|>,并不会停止,还会让模型去学会输出一个198的换行符!但是在推理的时候,见到<|im_end|>我们就截断了
最后训练得到的数据,需要经过data_collator再map一次 这里是llamafactory自己实现了SFTDataCollatorWith4DAttentionMask,最终也是走到DataCollatorForSeq2Seq里面的call
def __call__(self, features, return_tensors=None): if return_tensors is None: return_tensors = self.return_tensors
label_name = "label" if "label" in features[0].keys() else "labels" labels = [feature[label_name] for feature in features] if label_name in features[0].keys() else None # reconvert list[None] to None if necessary # this might occur when we pass {..., "labels": None} if labels is not None and all(label is None for label in labels): labels = None non_labels_features = [{k: v for k, v in feature.items() if k != label_name} for feature in features]
# run through tokenizer without labels to ensure no side effects batch = pad_without_fast_tokenizer_warning( self.tokenizer, non_labels_features, padding=self.padding, max_length=self.max_length, pad_to_multiple_of=self.pad_to_multiple_of, return_tensors=return_tensors, )
# we have to pad the labels manually as we cannot rely on `tokenizer.pad` and we need them to be of the same length to return tensors no_padding = self.padding is False or self.padding == PaddingStrategy.DO_NOT_PAD if labels is not None: if no_padding: if isinstance(features[0][label_name], list): batch["labels"] = list(labels) else: batch["labels"] = [np.concatenate([label, []]) for label in labels] else: max_padding = self.padding == PaddingStrategy.MAX_LENGTH and self.max_length is not None max_label_length = max(len(l) for l in labels) if not max_padding else self.max_length if self.pad_to_multiple_of is not None: max_label_length = ( (max_label_length + self.pad_to_multiple_of - 1) // self.pad_to_multiple_of * self.pad_to_multiple_of )
padding_side = self.tokenizer.padding_side if isinstance(features[0][label_name], list): batch["labels"] = [ label + [self.label_pad_token_id] * (max_label_length - len(label)) if padding_side == "right" else [self.label_pad_token_id] * (max_label_length - len(label)) + label for label in labels ] else: batch["labels"] = [ np.concatenate( [ label, np.array([self.label_pad_token_id] * (max_label_length - len(label)), dtype=np.int64), ] ) if padding_side == "right" else np.concatenate( [ np.array([self.label_pad_token_id] * (max_label_length - len(label)), dtype=np.int64), label, ] ) for label in labels ]
# reintroduce side effects via tokenizer that return respective datatypes for the `return_tensors` argument if batch.get("labels", None) is not None: if return_tensors == "pt": import torch
batch["labels"] = torch.tensor(batch["labels"], dtype=torch.int64) elif return_tensors == "tf": import tensorflow as tf
batch["labels"] = tf.constant(batch["labels"], dtype=tf.int64) else: batch["labels"] = np.array(batch["labels"], dtype=np.int64) else: batch["labels"] = None
# prepare decoder_input_ids if ( labels is not None and self.model is not None and hasattr(self.model, "prepare_decoder_input_ids_from_labels") ): decoder_input_ids = self.model.prepare_decoder_input_ids_from_labels(labels=batch["labels"]) batch["decoder_input_ids"] = decoder_input_ids
return batch
内部逻辑也很清晰,就是填充非label的字段,然后再手动填充label,因为label填充是用的-100,其余字段填充是用的pad
# Keyword arguments for `model.generate` gen_kwargs = generating_args.to_dict(obey_generation_config=True) gen_kwargs["eos_token_id"] = [tokenizer.eos_token_id] + tokenizer.additional_special_tokens_ids gen_kwargs["pad_token_id"] = tokenizer.pad_token_id 这里会发现pad_token_id 就是<|endoftext|>,而eos_token_id除了<|im_end|>以外,还包括各种<|im_start|>等,这样做是为了防止模型只输出当前轮次,强约束,防止模型出现幻觉?
|