苹果终于在WWDC24中带来了大家期盼已久的Apple Intelligence,作为产品研发的小同学们终于不用再粘合各种江湖的代码了,转而可以寻求苹果标准支持的各种模型训练和微调规范。
作为一家初创的人工智能企业,我们自然也会跟进此次大会的各种创新。今天就由我们的小实习生来给大家带来如何在苹果M芯片上微调一个30亿参数的模型。
通常情况下一个典型的部署模型的三步骤如下:

熟悉的同学们都知道模型的训练离不开NV,更离不开NV的Cuda.但是在PyTorch的支持下,目前它的Nightly版本已经支持了苹果的统一内存并且同时也支持了Int8和Int4量化技术。

接下来我们将结合【Train your machine learning and AI models on Apple GPUs】这篇内容来复现微调的整个过程。
首先,我们需要安装依赖,操作如下:
pip install --upgrade transformerspip install --upgrade datasetspip install --upgrade acceleratepip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
然后,打开我们的Jupyter Notebook:
import torchimport randomrandom.seed(42)torch.manual_seed(42)from transformers import LlamaTokenizer, LlamaForCausalLM# 验证环境无误if torch.backends.mps.is_available():mps_device = torch.device("mps")x = torch.ones(1, device=mps_device)print (x)else:print ("MPS device not found.")
你将会看到:
# 出现这个结果说明当前设备支持tensor([1.], device='mps:0')
然后,我们加载一个没有微调过的llama 3B模型:
model_path ='openlm-research/open_llama_3b_v2'tokenizer = LlamaTokenizer.from_pretrained(model_path, legacy=True)base_model =LlamaForCausalLM.from_pretrained(model_path)
这次我们要使用的技术是LoRA微调,我们配置如下:
from peft import LoraConfig, PeftModellora_config = LoraConfig(r=64,lora_alpha=32,lora_dropout=0.05,bias="none",task_type="CAUSAL_LM")model = PeftModel(base_model,lora_config,adapter_name="Shakespeare")device = torch.device("mps")model.to(device)
微调是一个非常强大且多变的技术,有兴趣的同学可以多看看这方面的Paper.
一般情况下,我们都会使用特定的数据集对某个基础模型或者instruct模型进行微调,所以我们需要加载如下的数据集:
# 配置数据集import osimport requestsfile_name="shakespeare.txt"url = "https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt"if not os.path.isfile(file_name):data = requests.get(url)with open(file_name, 'w') as f:f.write(data.text)from transformers import TextDatasettrain_dataset = TextDataset(tokenizer=tokenizer, file_path=file_name, block_size=128)[:256]
继续配置我们的微调参数:
# trainingfrom transformers import Trainer, TrainingArgumentstraining_args = TrainingArguments(output_dir="out_put",overwrite_output_dir=True,num_train_epochs=10,per_device_eval_batch_size=32,evaluation_strategy='no')
有了参数之后,我们需要新建一个trainer,来进行实际的训练过程:
from transformers import DataCollatorForLanguageModelingdata_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer,mlm=False)trainer = Trainer(model=model,args=training_args,data_collator=data_collator,train_dataset=train_dataset,)
到了这个时候我们已经基本完成了所有必要的步骤。但是我们可以先验证下没有微调之前的模型对同样指令的回应是什么样子的,使用下面的代码:
def generate_response(prompt_text, model, tokenizer, max_length=30, num_return_sequences=1):input_ids = tokenizer.encode(prompt_text, return_tensors="pt").to(device)attention_mask = torch.ones(input_ids.shape, device=input_ids.device)output_sequences = model.generate(input_ids=input_ids,max_length=max_length,attention_mask=attention_mask,num_return_sequences=num_return_sequences,no_repeat_ngram_size=2,)responses =[]for response_id in output_sequences:response = tokenizer.decode(response_id, skip_special_tokens=True)responses.append(response)return responsesprompt_text = "Uneasy lies the head that wears a crown."responses = generate_response(prompt_text, model, tokenizer)for response in responses:print(response)
我们相信你一定会看到下面的输出:
tensor([1.], device='mps:0')Uneasy lies the head that wears a crown.- William ShakespeareThe head of the family is the most important person in the world
可以看到这是一个比较随机的回应。
接下来为了让模型的回应更符合我们之前提到的数据集,我们启动微调过程:
trainer.train()
train会比较耗费时间,毕竟是一个30亿参数的模型,在我们的M1Max 32GB的机器上,耗时大约20分钟左右
StepTraining Loss--------------------Out[4]:TrainOutput(global_step=320,training_loss=2.709520721435547,metrics={'train_runtime': 1315.7902,'train_samples_per_second': 1.946,'train_steps_per_second': 0.243,'total_flos': 6577270554624000.0,'train_loss': 2.709520721435547,'epoch': 10.0})
我们可以再次使用同样的指令寻求模型的回应:
prompt_text = "Uneasy lies the head that wears a crown."responses = generate_response(prompt_text, model, tokenizer)for response in responses:print(response)
得到的回复可能如下:
Uneasy lies the head that wears a crown.MENENIUS :I'll not be a king. I'd rather beA poor man in my country than a princeIn a foreign land.
这里为什么说是可能呢,因为受到具体芯片和训练次数的约束,模型可能会展现出不同的结果。
在最后如果你想保存微调之后的模型以供未来上传到HuggingFace😊的话,需要下面的代码保存好模型:
save_path ="merged_fine_tuned_openllama2_3b_shakespeare_m1max"tokenizer.save_pretrained(save_path)merged_model = model.merge_and_unload()merged_model.save_pretrained(save_path)
以上代码可以下面的仓库找到:
https://github.com/AtomGradient/OpenWWDC24
质子梯度是一家新成立的位于北京的人工智能初创企业,我们坚信未来每个人都会具备一个中等能力的智能助理,可以帮助任何人完成需要重复性的各种线上与生活等操作。我们也在为此持续不断的研发中。我们非常欢迎有任何人工智能相关创业想法的小伙伴加入我们,欢迎给我写信:alex@atomgradient.com

