Files
2025-04-25 10:04:43 +08:00

50 KiB

一、LLM 预训练

1.1 初始化 LLM

In [1]:
# 加载定义好的模型参数-此处以 Qwen-2.5-1.5B 为例
# 使用 transforemrs 的 Config 类进行加载
from transformers import AutoConfig

model_path = "autodl-tmp/qwen-1.5b"
config = AutoConfig.from_pretrained(model_path)
config
Out [1]:
Qwen2Config {
  "_name_or_path": "autodl-tmp/qwen-1.5b",
  "architectures": [
    "Qwen2ForCausalLM"
  ],
  "attention_dropout": 0.0,
  "bos_token_id": 151643,
  "eos_token_id": 151643,
  "hidden_act": "silu",
  "hidden_size": 1536,
  "initializer_range": 0.02,
  "intermediate_size": 8960,
  "max_position_embeddings": 131072,
  "max_window_layers": 28,
  "model_type": "qwen2",
  "num_attention_heads": 12,
  "num_hidden_layers": 28,
  "num_key_value_heads": 2,
  "rms_norm_eps": 1e-06,
  "rope_theta": 1000000.0,
  "sliding_window": null,
  "tie_word_embeddings": true,
  "torch_dtype": "bfloat16",
  "transformers_version": "4.44.2",
  "use_cache": true,
  "use_mrope": false,
  "use_sliding_window": false,
  "vocab_size": 151936
}
In [2]:
# 使用该配置生成一个定义好的模型
from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_config(config,trust_remote_code=True)
model.to("cuda")
n_params = sum({p.data_ptr(): p.numel() for p in model.parameters()}.values())
print(f"Training new model from scratch - Total size={n_params/2**20:.2f}M params")
Training new model from scratch - Total size=1472.20M params
In [3]:
# 看一下模型
model
Out [3]:
Qwen2ForCausalLM(
  (model): Qwen2Model(
    (embed_tokens): Embedding(151936, 1536)
    (layers): ModuleList(
      (0-27): 28 x Qwen2DecoderLayer(
        (self_attn): Qwen2SdpaAttention(
          (q_proj): Linear(in_features=1536, out_features=1536, bias=True)
          (k_proj): Linear(in_features=1536, out_features=256, bias=True)
          (v_proj): Linear(in_features=1536, out_features=256, bias=True)
          (o_proj): Linear(in_features=1536, out_features=1536, bias=False)
          (rotary_emb): Qwen2RotaryEmbedding()
        )
        (mlp): Qwen2MLP(
          (gate_proj): Linear(in_features=1536, out_features=8960, bias=False)
          (up_proj): Linear(in_features=1536, out_features=8960, bias=False)
          (down_proj): Linear(in_features=8960, out_features=1536, bias=False)
          (act_fn): SiLU()
        )
        (input_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
        (post_attention_layernorm): Qwen2RMSNorm((1536,), eps=1e-06)
      )
    )
    (norm): Qwen2RMSNorm((1536,), eps=1e-06)
  )
  (lm_head): Linear(in_features=1536, out_features=151936, bias=False)
)
In [4]:
# 加载一个预训练好的 tokenizer
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(model_path)
tokenizer
Out [4]:
Qwen2TokenizerFast(name_or_path='autodl-tmp/qwen-1.5b', vocab_size=151643, model_max_length=131072, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'eos_token': '<|endoftext|>', '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),
}

1.2 预训练数据准备

In [2]:
# 加载预训练数据
from datasets import load_dataset

ds = load_dataset('json', data_files='autodl-tmp/dataset/pretrain_data/mobvoi_seq_monkey_general_open_corpus_small.jsonl')
Generating train split: 0 examples [00:00, ? examples/s]
In [5]:
ds["train"][0]
Out [5]:
{'text': '在查处虚开增值税专用发票案件中,常常涉及进项留抵税额和税款损失的认定和处理。在计算税款损失时,要不要将进项留抵税额包括在内?\n对此,实务中存在意见分歧。\n有人主张归并,即计算税款损失时包括进项留抵税额;\n有人主张剥离,即计算税款损失时剔除进项留抵税额。分析这个问题,需要确定进项留抵税额与税款损失之间是什么关系。\n理清这二者之间的关系,首先需要了解增值税的概念和其抵扣机制。增值税是以商品(货物、服务等)在流转过程中产生的增值额作为计税依据而征收的一种流转税。为避免重复征税,在增值税中存在抵扣链条机制。\n一般而言,交易上游企业缴纳的税额,交易下游企业可以对相应的税额进行抵扣。\n对增值税一般纳税人来说,其购进货物、服务等取得增值税专用发票,发票上的税额是进项税额。\n其出售货物、服务等,向购买方开具增值税专用发票,发票的税额是销项税额。\n一般情况下,销项税额减去进项税额的金额是应纳税额,企业根据应纳税额按期申报纳税。\n其次需要了解进项留抵税额的概念及产生原因。\n在计算销项税额和进项税额的差额时,有时会出现负数,即当期进项税额大于当期销项税额。这个差额在当期未实现抵扣,为进项留抵税额,在以后纳税人有销项税额时再进行抵扣。\n企业产生进项留抵税额的主要原因是其进项税额和销项税额时间上的不一致。\n例如,企业前期集中采购货物和服务,投资大,销项税率低于进项税率等。\n从税款抵扣的角度看,进项留抵税额只是购进的这部分进项税额参与到增值税应纳税额的计算过程中,但是其对应的进项税额抵扣还未真正实现,一般要等到其未来有相应的销项税额时,才能真正实现进项税额抵扣。\n可见,进项留抵税额处于不确定状态,能否抵扣受到很多因素影响,例如企业经营中断,没有销项税额,这时进项留抵税额就无法实现抵扣。但如果企业按照税收政策规定申请进项留抵退税,进项税额抵扣就随之实现。\n最后需要了解税款损失的概念。\n税款损失,通常是指因虚开增值税专用发票,导致国家税款被骗或者流失的金额。关于税款损失,实务中有多种表述。\n例如,北京大学法学院教授陈兴良曾谈到虚开行为本身不会造成国家税款损失,只有利用发票抵扣时才会造成国家税款损失。刘兵等编著的《虚开增值税专用发票案例司法观点和案例解析》一书中提到:“给国家税款造成损失的数额,实际上就是被骗取的国家税款在侦查终结以前无法追回的部分。”\n赵清海与王家欣合著的《增值税专用发票虚开的判定与预防》一书中提到:“司法实践中,受票方用虚开的增值税专用发票予以抵扣的税款,从而导致受票方应纳税额的减少是法院所认定的国家税款流失的金额。”\n从这些表述可见,税款损失应该是实际造成的损失,不应包括不确定的部分——进项留抵税额,进项留抵税额与税款损失之间不能直接画等号。\n综上分析,进项留抵税额,只是使国家税款处于可能被抵扣的状态,还没有真正造成国家税款流失,一般情况下应将其从税款损失中剥离,特殊条件下将其归并入税款损失。\n例如,当纳税人造假按照税收政策规定申请进项留抵税额退税后,有关税款损失将会从危险状态转化成危害结果,这时候要将有关进项留抵税额并入税款损失。\n所以,在虚开增值税专用发票案件中,一般情况下,如果以纳税人的进项税额作为税款损失的计算基数,在对其进行行政处罚或刑事处罚时,应把进项留抵税额从税款损失中剔除,但纳税人申请进项留抵退税的除外。这样处理,把处罚与危害结果相对应,体现行政处罚法的过罚相当原则和刑法的罚当其罪原则。'}
In [7]:
# 查看特征
column_names = list(ds["train"].features)
print(column_names)
['text']
In [8]:
# 对数据集进行 tokenize

def tokenize_function(examples):
    # 使用预先加载的 tokenizer 进行分词
    output = tokenizer([item for item in examples["text"]])
    return output

# 批量处理
tokenized_datasets = ds.map(
    tokenize_function,
    batched=True,
    num_proc=10,
    remove_columns=column_names,
    load_from_cache_file=True,
    desc="Running tokenizer on dataset",
)
Running tokenizer on dataset (num_proc=10):   0%|          | 0/100001 [00:00<?, ? examples/s]
In [9]:
tokenized_datasets
Out [9]:
DatasetDict({
    train: Dataset({
        features: ['input_ids', 'attention_mask'],
        num_rows: 100001
    })
})
In [11]:
# 预训练一般将文本拼接成固定长度的文本段
from itertools import chain

# 这里我们取块长为 2048
block_size = 2048

def group_texts(examples):
    # 将文本段拼接起来
    concatenated_examples = {k: list(chain(*examples[k])) for k in examples.keys()}
    # 计算拼起来的整体长度
    total_length = len(concatenated_examples[list(examples.keys())[0]])
    # 如果长度太长,进行分块
    if total_length >= block_size:
        total_length = (total_length // block_size) * block_size
    # Split by chunks of max_len.
    result = {
        k: [t[i : i + block_size] for i in range(0, total_length, block_size)]
        for k, t in concatenated_examples.items()
    }
    # print(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))       
    print("group texts input examples length%d after_group size%d"%(len(examples['input_ids']),len(result["input_ids"])))
    result["labels"] = result["input_ids"].copy()
    return result
In [12]:
# 批量处理
lm_datasets = tokenized_datasets.map(
    group_texts,
    batched=True,
    num_proc=10,
    load_from_cache_file=True,
    desc=f"Grouping texts in chunks of {block_size}",
    batch_size = 40000,
)
train_dataset = lm_datasets["train"]
Grouping texts in chunks of 2048 (num_proc=10):   0%|          | 0/100001 [00:00<?, ? examples/s]
group texts input examples length10001 after_group size2752
group texts input examples length10000 after_group size2817
group texts input examples length10000 after_group size2820
group texts input examples length10000 after_group size2817
group texts input examples length10000 after_group size2787
group texts input examples length10000 after_group size2797
group texts input examples length10000 after_group size2800
group texts input examples length10000 after_group size2835
group texts input examples length10000 after_group size2778
group texts input examples length10000 after_group size2825

1.3 使用 Trainer

In [13]:
from transformers import TrainingArguments
# 配置训练参数

training_args = TrainingArguments(
    output_dir="autodl-tmp/output/pretrain",
    per_device_train_batch_size=4,
    gradient_accumulation_steps=4,
    logging_steps=10,
    num_train_epochs=1,
    save_steps=100, 
    learning_rate=1e-4,
    save_on_each_node=True,
    gradient_checkpointing=True
)
In [14]:
from transformers import Trainer, default_data_collator
from torchdata.datapipes.iter import IterableWrapper

# 训练器
trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset= IterableWrapper(train_dataset),
    eval_dataset= None,
    tokenizer=tokenizer,
    # 默认为 MLM 的 collator,使用 CLM 的 collater
    data_collator=default_data_collator
)
/root/miniconda3/lib/python3.10/site-packages/torchdata/datapipes/__init__.py:18: UserWarning: 
################################################################################
WARNING!
The 'datapipes', 'dataloader2' modules are deprecated and will be removed in a
future torchdata release! Please see https://github.com/pytorch/data/issues/1196
to learn more and leave feedback.
################################################################################

  deprecation_warning()
In [15]:
print('start train')
train_result = trainer.train()
start train
`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`...
/root/miniconda3/lib/python3.10/site-packages/torch/_dynamo/eval_frame.py:600: UserWarning: torch.utils.checkpoint: the use_reentrant parameter should be passed explicitly. In version 2.4 we will raise an exception if use_reentrant is not passed. use_reentrant=False is recommended, but if you need to preserve the current default behavior, you can pass use_reentrant=True. Refer to docs for more details on the differences between the two variants.
  return fn(*args, **kwargs)
/root/miniconda3/lib/python3.10/site-packages/torch/utils/checkpoint.py:295: FutureWarning: `torch.cpu.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cpu', args...)` instead.
  with torch.enable_grad(), device_autocast_ctx, torch.cpu.amp.autocast(**ctx.cpu_autocast_kwargs):  # type: ignore[attr-defined]
[ 101/1751 29:31 < 8:12:11, 0.06 it/s, Epoch 0.06/1]
Step Training Loss
10 10.987700
20 9.160700
30 8.352700
40 8.159800
50 8.042500
60 8.014400
70 7.986700
80 7.951800
90 7.875500

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
File ~/miniconda3/lib/python3.10/site-packages/torch/serialization.py:652, in save(obj, f, pickle_module, pickle_protocol, _use_new_zipfile_serialization, _disable_byteorder_record)
    651 with _open_zipfile_writer(f) as opened_zipfile:
--> 652     _save(obj, opened_zipfile, pickle_module, pickle_protocol, _disable_byteorder_record)
    653     return

File ~/miniconda3/lib/python3.10/site-packages/torch/serialization.py:886, in _save(obj, zip_file, pickle_module, pickle_protocol, _disable_byteorder_record)
    885 num_bytes = storage.nbytes()
--> 886 zip_file.write_record(name, storage, num_bytes)

RuntimeError: [enforce fail at inline_container.cc:778] . PytorchStreamWriter failed writing file data/401: file write failed

During handling of the above exception, another exception occurred:

RuntimeError                              Traceback (most recent call last)
Cell In[15], line 2
      1 print('start train')
----> 2 train_result = trainer.train()

File ~/miniconda3/lib/python3.10/site-packages/transformers/trainer.py:1938, in Trainer.train(self, resume_from_checkpoint, trial, ignore_keys_for_eval, **kwargs)
   1936         hf_hub_utils.enable_progress_bars()
   1937 else:
-> 1938     return inner_training_loop(
   1939         args=args,
   1940         resume_from_checkpoint=resume_from_checkpoint,
   1941         trial=trial,
   1942         ignore_keys_for_eval=ignore_keys_for_eval,
   1943     )

File ~/miniconda3/lib/python3.10/site-packages/transformers/trainer.py:2356, in Trainer._inner_training_loop(self, batch_size, args, resume_from_checkpoint, trial, ignore_keys_for_eval)
   2353     self.state.epoch = epoch + (step + 1 + steps_skipped) / steps_in_epoch
   2354     self.control = self.callback_handler.on_step_end(args, self.state, self.control)
-> 2356     self._maybe_log_save_evaluate(tr_loss, grad_norm, model, trial, epoch, ignore_keys_for_eval)
   2357 else:
   2358     self.control = self.callback_handler.on_substep_end(args, self.state, self.control)

File ~/miniconda3/lib/python3.10/site-packages/transformers/trainer.py:2807, in Trainer._maybe_log_save_evaluate(self, tr_loss, grad_norm, model, trial, epoch, ignore_keys_for_eval)
   2804     metrics = self._evaluate(trial, ignore_keys_for_eval)
   2806 if self.control.should_save:
-> 2807     self._save_checkpoint(model, trial, metrics=metrics)
   2808     self.control = self.callback_handler.on_save(self.args, self.state, self.control)

File ~/miniconda3/lib/python3.10/site-packages/transformers/trainer.py:2890, in Trainer._save_checkpoint(self, model, trial, metrics)
   2886 self.save_model(output_dir, _internal_call=True)
   2888 if not self.args.save_only_model:
   2889     # Save optimizer and scheduler
-> 2890     self._save_optimizer_and_scheduler(output_dir)
   2891     # Save RNG state
   2892     self._save_rng_state(output_dir)

File ~/miniconda3/lib/python3.10/site-packages/transformers/trainer.py:3006, in Trainer._save_optimizer_and_scheduler(self, output_dir)
   3001     save_fsdp_optimizer(
   3002         self.accelerator.state.fsdp_plugin, self.accelerator, self.optimizer, self.model, output_dir
   3003     )
   3004 elif self.args.should_save:
   3005     # deepspeed.save_checkpoint above saves model/optim/sched
-> 3006     torch.save(self.optimizer.state_dict(), os.path.join(output_dir, OPTIMIZER_NAME))
   3008 # Save SCHEDULER & SCALER
   3009 is_deepspeed_custom_scheduler = self.is_deepspeed_enabled and not isinstance(
   3010     self.lr_scheduler, DeepSpeedSchedulerWrapper
   3011 )

File ~/miniconda3/lib/python3.10/site-packages/torch/serialization.py:651, in save(obj, f, pickle_module, pickle_protocol, _use_new_zipfile_serialization, _disable_byteorder_record)
    648 _check_save_filelike(f)
    650 if _use_new_zipfile_serialization:
--> 651     with _open_zipfile_writer(f) as opened_zipfile:
    652         _save(obj, opened_zipfile, pickle_module, pickle_protocol, _disable_byteorder_record)
    653         return

File ~/miniconda3/lib/python3.10/site-packages/torch/serialization.py:499, in _open_zipfile_writer_file.__exit__(self, *args)
    498 def __exit__(self, *args) -> None:
--> 499     self.file_like.write_end_of_file()
    500     if self.file_stream is not None:
    501         self.file_stream.close()

RuntimeError: [enforce fail at inline_container.cc:603] . unexpected pos 6546708864 vs 6546708760

二、模型 SFT

2.1 处理指令数据

In [1]:
import json

with open("autodl-tmp/dataset/sft_data/BelleGroup/train_3.5M_CN.json") as f:
    lst = [json.loads(line) for line in f.readlines()]

lst[0]
Out [1]:
{'conversations': [{'from': 'human',
   'value': '针对健身房的新手,设计一套适合他们的健身器械使用指南,包括安全应用、正确姿势等方面。'},
  {'from': 'assistant',
   'value': '健身器械使用指南\n1. 开始前,请先进行热身运动。这会帮助你的身体适应运动,并减少受伤的风险。\n2. 在使用健身器械前,确保你已经了解了其使用方法。请阅读说明书或咨询教练以获得正确的使用技巧。\n3. 谨防过度使用或过度挑战你的身体。 如果你觉得有些动作太难或太重,请添加锻炼计划,以逐步提高动作难度。\n4. 使用合适的装备。 确保你拥有合适的运动鞋和舒适的运动服。 不要在裸露的脚或短裤上进行重量训练。\n5. 在健身器械上使用安全装置。 这些通常用于保护你的身体免受不当操作造成的损伤。 例如,重量训练中,你需要使用杠铃和负重时,一定要使用卡子来防止重量滑落。\n6. 注意正确的姿势。 如果你的姿势是错误的,那么你的身体很容易被伤害到,你也可能无法获得最佳的锻炼效果。 至关重要的是,保持直立的身体,保持头部和颈部的稳定,并使用合适的重量。\n7. 保持合理的呼吸方式。 无论何时进行训练,都必须保持正常呼吸。 当你需要用力时,呼气; 当你放松时,吸气。\n8. 安全存放器械。 在使用健身器械后,你需要把它们归还给适当的位置,以便其他人可以使用它们。\n总之,健身器械的正确使用是关键之一,如果不健康和不安全,它们将无法帮助您达到您所需的健康成果。 选择适当的训练计划,并为训练提供足够的时间,以备逐渐适应新方法。 对于任何问题,请向教练咨询。'}],
 'id': '66182880'}
In [2]:
# 加载一个预训练好的 tokenizer
from transformers import AutoTokenizer

model_path = "autodl-tmp/qwen-1.5b"
tokenizer = AutoTokenizer.from_pretrained(model_path)
tokenizer
Out [2]:
Qwen2TokenizerFast(name_or_path='autodl-tmp/qwen-1.5b', vocab_size=151643, model_max_length=131072, is_fast=True, padding_side='right', truncation_side='right', special_tokens={'eos_token': '<|endoftext|>', '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),
}
In [3]:
import torch
from tqdm import tqdm

# 指令文本处理
# 参考:https://github.com/QwenLM/Qwen/blob/main/finetune.py
def preprocess(sources, tokenizer, max_len, system_message: str = "You are a helpful assistant."):
    # prompt 模板
    roles = {"human": "<|im_start|>human", "assistant": "<|im_start|>assistant"}

    # 不同的 tokenizer 需要特别定义
    # BOS
    im_start = tokenizer("<|im_start|>").input_ids
    # EOS
    im_end = tokenizer("<|im_end|>").input_ids
    # PAD
    IGNORE_TOKEN_ID = tokenizer.pad_token_id
    # 换行符
    nl_tokens = tokenizer('\n').input_ids
    # 角色标识符
    _system = tokenizer('system').input_ids + nl_tokens
    _user = tokenizer('human').input_ids + nl_tokens
    _assistant = tokenizer('assistant').input_ids + nl_tokens

    # 拼接多轮对话
    input_ids, targets = [], []
    for i in tqdm(range(len(sources))):
        source = sources[i]
        # 从 user 开始
        if source[0]["from"] != "human":
            source = source[1:]
        # 分别是输入和输出
        input_id, target = [], []
        # system: 【BOS】system\nYou are a helpful assistant.【EOS】\n
        system = im_start + _system + tokenizer(system_message).input_ids + im_end + nl_tokens
        input_id += system
        # system 不需要拟合
        target += im_start + [IGNORE_TOKEN_ID] * (len(system)-3) + im_end + nl_tokens
        assert len(input_id) == len(target)
        # 依次拼接
        for j, sentence in enumerate(source):
            role = roles[sentence["from"]]
            # user:<|im_start|>human\ninstruction【EOS】\n
            # assistant:<|im_start|>assistant\nresponse【EOS】\n
            _input_id = tokenizer(role).input_ids + nl_tokens + \
                tokenizer(sentence["value"]).input_ids + im_end + nl_tokens
            input_id += _input_id
            if role == '<|im_start|>human':
                # user 不需要拟合
                _target = im_start + [IGNORE_TOKEN_ID] * (len(_input_id)-3) + im_end + nl_tokens
            elif role == '<|im_start|>assistant':
                # assistant 需要拟合
                _target = im_start + [IGNORE_TOKEN_ID] * len(tokenizer(role).input_ids) + \
                    _input_id[len(tokenizer(role).input_ids)+1:-2] + im_end + nl_tokens
            else:
                print(role)
                raise NotImplementedError
            target += _target
        assert len(input_id) == len(target)
        # 最后进行 PAD
        input_id += [tokenizer.pad_token_id] * (max_len - len(input_id))
        target += [IGNORE_TOKEN_ID] * (max_len - len(target))
        input_ids.append(input_id[:max_len])
        targets.append(target[:max_len])
    # print(input_ids)
    input_ids = torch.tensor(input_ids)
    targets = torch.tensor(targets)

    return dict(
        input_ids=input_ids,
        labels=targets,
        attention_mask=input_ids.ne(tokenizer.pad_token_id),
    )
In [32]:
# 测试一下
preprocess([lst[0]["conversations"],lst[1]["conversations"]], tokenizer, 1024)
Out [32]:
{'input_ids': tensor([[151644,   8948,    198,  ..., 151643, 151643, 151643],
         [151644,   8948,    198,  ..., 151643, 151643, 151643]]),
 'labels': tensor([[151644, 151643, 151643,  ..., 151643, 151643, 151643],
         [151644, 151643, 151643,  ..., 151643, 151643, 151643]]),
 'attention_mask': tensor([[ True,  True,  True,  ..., False, False, False],
         [ True,  True,  True,  ..., False, False, False]])}
In [4]:
# 自定义一个 Dataset
from torch.utils.data import Dataset
from typing import Dict

class SupervisedDataset(Dataset):

    def __init__(self, raw_data, tokenizer, max_len: int):
        super(SupervisedDataset, self).__init__()
        # 加载并预处理数据
        sources = [example["conversations"] for example in raw_data[:10000]]
        data_dict = preprocess(sources, tokenizer, max_len)

        self.input_ids = data_dict["input_ids"]
        self.labels = data_dict["labels"]
        self.attention_mask = data_dict["attention_mask"]

    def __len__(self):
        return len(self.input_ids)

    def __getitem__(self, i) -> Dict[str, torch.Tensor]:
        return dict(
            input_ids=self.input_ids[i],
            labels=self.labels[i],
            attention_mask=self.attention_mask[i],
        )
In [5]:
train_ds = SupervisedDataset(lst, tokenizer=tokenizer, max_len=2048)
100%|██████████| 10000/10000 [00:08<00:00, 1235.98it/s]