放牧代码和思想
专注自然语言处理、机器学习算法
    愛しさ 優しさ すべて投げ出してもいい

M1加速深度学习:HanLP正式支持苹果芯GPU

目录

2021-10-18_14-26-31.jpg

今天我的MBP M1MAX终于寄到了,于是第一时间为HanLP提供M1的原生CPU+GPU支持。MBP用户从此享受到GPU加速的推理与训练,微调个BERT同样丝滑。本文简要介绍原生环境搭建与安装,适用于包括M1系列在内的Apple Silicon芯片。

apple_m1_chip.jpg

首先介绍一些基础知识,我们最常用的Intel芯片是amd64架构,而M1其实是arm64架构的子集,它的binary通常命名为osx-arm64。按道理来讲amd64的binary放到arm64芯片上是不能执行的,好在Apple提供了一种翻译机器指令的软件,称作Rosetta,可以即时将amd64指令翻译为osx-arm64。经过我的体验,绝大多数amd64软件可以无缝运行,得益于M1MAX的强大性能,甚至感觉比MBP15上的Intel i7还要快。这些软件包括Java、pyhanlp以及HanLPv2.x。

对,你没有看错。如果你将Intel MBP上的数据(JVM、Python)通过迁移助手或者时间胶囊迁移到M1 MBP上,你不需要任何操作就可以直接运行pyhanlp和HanLPv2.x。只不过所有指令都得过一遍Rosetta,而不是native binary。所以有一定性能损失,也没法用到Apple的GPU加速。

本文的目的就是解决这两个问题:

  1. 如何避免Rosetta,直接利用M1的CPU?

  2. 如何利用M1的GPU核心加速神经网络?

第一个问题自然是编译安装osx-arm64的Python、PyTorch、TensorFlow等。好在现在有了conda这样的工具,可以直接下载官方编译的osx-arm64 binary。第二个问题则需要安装tensorflow-macos这个由苹果魔改的TF和PyTorch Preview (Nightly) build,以及苹果的GPU运行时Metal。

screen-hero-large_2x.jpg

提前声明,以下所有安装步骤适用于在M1上跑任意深度学习应用,并非局限于HanLP。HanLP在其他平台上的安装从来都是一句pip搞定,不存在难安装的问题。如果你无法理解,请勿传播你无法理解的内容。

安装

安装conda

这里使用的社区维护的分支,叫做miniforge3,里面的包比miniconda还少,可谓最精简版。

wget https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-arm64.sh
sh ./Miniforge3-MacOSX-arm64.sh
source ~/miniforge3/bin/activate

安装tensorflow-macos

conda install -c apple tensorflow-deps==2.6.0 -y
pip install tensorflow-macos==2.6.0
pip install tensorflow-metal

安装PyTorch

目前PyTorch的M1支持还处于测试阶段,测试版可能无法运行某些模型,仅供尝鲜:

pip3 install --pre torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/nightly/cpu

M1芯片GPU的代号为MPS,在 MacOS 12.3+版本有效。在M1上使用torch.device("mps:0") 相当于在Nvidia GPU上使用torch.device("cuda:0")。

在M1的稳定版发布前,建议直接使用无GPU的稳定版,其实也很快:

pip3 install torch

安装huggingface相关依赖

截止今天,huggingface的tokenizers依赖Rust编译器,却未提交osx-arm64的wheel。所以需要安装Rust编译器,自行编译。

如果你安装过brew这个包管理器,请直接:

brew install rust
conda install -c conda-forge sentencepiece -y
pip install transformers

brew会自动帮你搞定rust的依赖。

否则请手动将Xcode升级到最新后,执行:

conda install -c conda-forge sentencepiece -y
conda install -c conda-forge rust -y
pip install transformers

安装HanLP

pip install hanlp

推理

TensorFlow

import hanlp

tokenizer = hanlp.load('LARGE_ALBERT_BASE')
tagger = hanlp.load('CTB9_POS_ALBERT_BASE')
syntactic_parser = hanlp.load('CTB7_BIAFFINE_DEP_ZH')
semantic_parser = hanlp.load('SEMEVAL16_TEXT_BIAFFINE_ZH')

pipeline = hanlp.pipeline() \
    .append(hanlp.utils.rules.split_sentence, output_key='sentences') \
    .append(tokenizer, output_key='tokens') \
    .append(tagger, output_key='part_of_speech_tags') \
    .append(syntactic_parser, input_key=('tokens', 'part_of_speech_tags'), output_key='syntactic_dependencies', conll=False) \
    .append(semantic_parser, input_key=('tokens', 'part_of_speech_tags'), output_key='semantic_dependencies', conll=False)
print(pipeline)

text = '''HanLP是一系列模型与算法组成的自然语言处理工具包,目标是普及自然语言处理在生产环境中的应用。
HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点。
内部算法经过工业界和学术界考验,配套书籍《自然语言处理入门》已经出版。
'''

doc = pipeline(text)
print(doc)

你将看到如下字样:

Metal device set to: Apple M1 Max

systemMemory: 64.00 GB
maxCacheSize: 21.33 GB

{
  "sentences": [
    "HanLP是一系列模型与算法组成的自然语言处理工具包,目标是普及自然语言处理在生产环境中的应用。",
    "HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点。",
    "内部算法经过工业界和学术界考验,配套书籍《自然语言处理入门》已经出版。"
  ],
  "tokens": [
    ["HanLP", "是", "一系列", "模型", "与", "算法", "组成", "的", "自然", "语言", "处理", "工具包", ",", "目标", "是", "普及", "自然", "语言", "处理", "在", "生产", "环境", "中", "的", "应用", "。"],
    ["HanLP", "具备", "功能", "完善", "、", "性能", "高效", "、", "架构", "清晰", "、", "语料", "时", "新", "、", "可", "自", "定义", "的", "特点", "。"],
    ["内部", "算法", "经过", "工业界", "和", "学术界", "考验", ",", "配套", "书籍", "《", "自然", "语言", "处理", "入门", "》", "已经", "出版", "。"]
  ],
  "part_of_speech_tags": [
    ["NR", "VC", "CD", "NN", "P", "NN", "VV", "DEC", "NN", "NN", "NN", "NN", "PU", "NN", "VC", "VV", "NN", "NN", "VV", "P", "NN", "NN", "LC", "DEG", "NN", "PU"],
    ["NR", "VV", "NN", "VA", "PU", "NN", "VA", "PU", "NN", "VA", "PU", "NN", "LC", "VA", "PU", "VV", "VV", "VV", "DEC", "NN", "PU"],
    ["NN", "NN", "P", "NN", "CC", "NN", "NN", "PU", "JJ", "NN", "PU", "NN", "NN", "NN", "VV", "PU", "AD", "VV", "PU"]
  ],
  "syntactic_dependencies": [
    [[0, "root"], [26, "dep"], [9, "dep"], [9, "dep"], [0, "root"], [22, "dep"], [0, "root"], [9, "dep"], [9, "dep"], [0, "root"], [0, "dep"], [15, "dep"], [9, "dep"], [15, "dep"], [15, "dep"], [9, "dep"], [9, "dep"], [0, "dep"], [0, "dep"], [0, "root"], [0, "root"], [0, "root"], [0, "root"], [0, "root"], [0, "dep"], [0, "dep"]],
    [[21, "dep"], [21, "dep"], [21, "dep"], [0, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"]],
    [[19, "dep"], [19, "dep"], [0, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"]]
  ],
  "semantic_dependencies": [
    [[[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[1], ["dFreq"]], [[1], ["dFreq"]], [[0], ["Root"]], [[1], ["dFreq"]], [[0], ["Root"]], [[1], ["dFreq"]], [[1], ["dFreq"]], [[0], ["Root"]], [[0], ["Root"]]],
    [[[0], ["Root"]], [[0], ["Root"]], [[12], ["rBelg"]], [[12], ["rBelg"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[12], ["dFreq"]], [[12], ["rBelg"]]],
    [[[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]]]
  ]
}
[None->LambdaComponent->sentences, sentences->TransformerTokenizerTF->tokens, tokens->TransformerTaggerTF->part_of_speech_tags, ['tokens', 'part_of_speech_tags']->BiaffineDependencyParserTF->syntactic_dependencies, ['tokens', 'part_of_speech_tags']->BiaffineSemanticDependencyParserTF->semantic_dependencies]
{
  "sentences": [
    "HanLP是一系列模型与算法组成的自然语言处理工具包,目标是普及自然语言处理在生产环境中的应用。",
    "HanLP具备功能完善、性能高效、架构清晰、语料时新、可自定义的特点。",
    "内部算法经过工业界和学术界考验,配套书籍《自然语言处理入门》已经出版。"
  ],
  "tokens": [
    ["HanLP", "是", "一系列", "模型", "与", "算法", "组成", "的", "自然", "语言", "处理", "工具包", ",", "目标", "是", "普及", "自然", "语言", "处理", "在", "生产", "环境", "中", "的", "应用", "。"],
    ["HanLP", "具备", "功能", "完善", "、", "性能", "高效", "、", "架构", "清晰", "、", "语料", "时", "新", "、", "可", "自", "定义", "的", "特点", "。"],
    ["内部", "算法", "经过", "工业界", "和", "学术界", "考验", ",", "配套", "书籍", "《", "自然", "语言", "处理", "入门", "》", "已经", "出版", "。"]
  ],
  "part_of_speech_tags": [
    ["NR", "VC", "CD", "NN", "P", "NN", "VV", "DEC", "NN", "NN", "NN", "NN", "PU", "NN", "VC", "VV", "NN", "NN", "VV", "P", "NN", "NN", "LC", "DEG", "NN", "PU"],
    ["NR", "VV", "NN", "VA", "PU", "NN", "VA", "PU", "NN", "VA", "PU", "NN", "LC", "VA", "PU", "VV", "VV", "VV", "DEC", "NN", "PU"],
    ["NN", "NN", "P", "NN", "CC", "NN", "NN", "PU", "JJ", "NN", "PU", "NN", "NN", "NN", "VV", "PU", "AD", "VV", "PU"]
  ],
  "syntactic_dependencies": [
    [[0, "root"], [26, "dep"], [9, "dep"], [9, "dep"], [0, "root"], [22, "dep"], [0, "root"], [9, "dep"], [9, "dep"], [0, "root"], [0, "dep"], [15, "dep"], [9, "dep"], [15, "dep"], [15, "dep"], [9, "dep"], [9, "dep"], [0, "dep"], [0, "dep"], [0, "root"], [0, "root"], [0, "root"], [0, "root"], [0, "root"], [0, "dep"], [0, "dep"]],
    [[21, "dep"], [21, "dep"], [21, "dep"], [0, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"], [21, "dep"]],
    [[19, "dep"], [19, "dep"], [0, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"], [19, "dep"]]
  ],
  "semantic_dependencies": [
    [[[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[1], ["dFreq"]], [[1], ["dFreq"]], [[0], ["Root"]], [[1], ["dFreq"]], [[0], ["Root"]], [[1], ["dFreq"]], [[1], ["dFreq"]], [[0], ["Root"]], [[0], ["Root"]]],
    [[[0], ["Root"]], [[0], ["Root"]], [[12], ["rBelg"]], [[12], ["rBelg"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[12], ["dFreq"]], [[12], ["rBelg"]]],
    [[[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]], [[0], ["Root"]]]
  ]
}

代表M1的GPU已经发挥作用了。

PyTorch

以MTL为例:

import hanlp
from hanlp_common.document import Document

HanLP = hanlp.load(hanlp.pretrained.mtl.CLOSE_TOK_POS_NER_SRL_DEP_SDP_CON_ELECTRA_BASE_ZH)
print(HanLP.device)

输出:

mps:0

标志着M1加速成功。再试试预测:

doc: Document = HanLP(['2021年HanLPv2.1为生产环境带来次世代最先进的多语种NLP技术。', '阿婆主来到北京立方庭参观自然语义科技公司。'])
doc.pretty_print()

输出:

Dep Tree    	Token    	Relati	PoS	Tok      	NER Type	Tok      	SRL PA1     	Tok      	SRL PA2     	Tok      	PoS    3       4       5       6       7       8       9 
────────────	─────────	──────	───	─────────	────────	─────────	────────────	─────────	────────────	─────────	─────────────────────────────────────────────────────────
 ┌─────────►	2021年    	tmod  	NT 	2021年    	───►DATE	2021年    	───►ARGM-TMP	2021年    	            	2021年    	NT ───────────────────────────────────────────►NP ───┐   
 │┌────────►	HanLPv2.1	nsubj 	NR 	HanLPv2.1	───►WWW 	HanLPv2.1	───►ARG0    	HanLPv2.1	            	HanLPv2.1	NR ───────────────────────────────────────────►NP────┤   
 ││┌─►┌─────	为        	prep  	P  	为        	        	为        	◄─┐         	为        	            	为        	P ───────────┐                                       │   
 │││  │  ┌─►	生产       	nn    	NN 	生产       	        	生产       	  ├►ARG2    	生产       	            	生产       	NN ──┐       ├────────────────────────►PP ───┐       │   
 │││  └─►└──	环境       	pobj  	NN 	环境       	        	环境       	◄─┘         	环境       	            	环境       	NN ──┴►NP ───┘                               │       │   
┌┼┴┴────────	带来       	root  	VV 	带来       	        	带来       	╟──►PRED    	带来       	            	带来       	VV ──────────────────────────────────┐       │       │   
││       ┌─►	次        	amod  	JJ 	次        	        	次        	◄─┐         	次        	            	次        	JJ ───►ADJP──┐                       │       ├►VP────┤   
││  ┌───►└──	世代       	nn    	NN 	世代       	        	世代       	  │         	世代       	            	世代       	NN ───►NP ───┴►NP ───┐               │       │       │   
││  │    ┌─►	最        	advmod	AD 	最        	        	最        	  │         	最        	───►ARGM-ADV	最        	AD ───────────►ADVP──┼►ADJP──┐       ├►VP ───┘       ├►IP
││  │┌──►├──	先进       	rcmod 	JJ 	先进       	        	先进       	  │         	先进       	╟──►PRED    	先进       	JJ ───────────►VP ───┘       │       │               │   
││  ││   └─►	的        	assm  	DEG	的        	        	的        	  ├►ARG1    	的        	            	的        	DEG──────────────────────────┤       │               │   
││  ││   ┌─►	多        	nummod	CD 	多        	        	多        	  │         	多        	            	多        	CD ───►QP ───┐               ├►NP ───┘               │   
││  ││┌─►└──	语种       	nn    	NN 	语种       	        	语种       	  │         	语种       	            	语种       	NN ───►NP ───┴────────►NP────┤                       │   
││  │││  ┌─►	NLP      	nn    	NR 	NLP      	        	NLP      	  │         	NLP      	            	NLP      	NR ──┐                       │                       │   
│└─►└┴┴──┴──	技术       	dobj  	NN 	技术       	        	技术       	◄─┘         	技术       	───►ARG0    	技术       	NN ──┴────────────────►NP ───┘                       │   
└──────────►	。        	punct 	PU 	。        	        	。        	            	。        	            	。        	PU ──────────────────────────────────────────────────┘   

Dep Tree    	Tok	Relat	Po	Tok	NER Type        	Tok	SRL PA1 	Tok	SRL PA2 	Tok	Po    3       4       5       6 
────────────	───	─────	──	───	────────────────	───	────────	───	────────	───	────────────────────────────────
         ┌─►	阿婆主	nsubj	NN	阿婆主	                	阿婆主	───►ARG0	阿婆主	───►ARG0	阿婆主	NN───────────────────►NP ───┐   
┌┬────┬──┴──	来到 	root 	VV	来到 	                	来到 	╟──►PRED	来到 	        	来到 	VV──────────┐               │   
││    │  ┌─►	北京 	nn   	NR	北京 	───►LOCATION    	北京 	◄─┐     	北京 	        	北京 	NR──┐       ├►VP ───┐       │   
││    └─►└──	立方庭	dobj 	NR	立方庭	───►LOCATION    	立方庭	◄─┴►ARG1	立方庭	        	立方庭	NR──┴►NP ───┘       │       │   
│└─►┌───────	参观 	conj 	VV	参观 	                	参观 	        	参观 	╟──►PRED	参观 	VV──────────┐       ├►VP────┤   
│   │  ┌───►	自然 	nn   	NN	自然 	◄─┐             	自然 	        	自然 	◄─┐     	自然 	NN──┐       │       │       ├►IP
│   │  │┌──►	语义 	nn   	NN	语义 	  │             	语义 	        	语义 	  │     	语义 	NN  │       ├►VP ───┘       │   
│   │  ││┌─►	科技 	nn   	NN	科技 	  ├►ORGANIZATION	科技 	        	科技 	  ├►ARG1	科技 	NN  ├►NP ───┘               │   
│   └─►└┴┴──	公司 	dobj 	NN	公司 	◄─┘             	公司 	        	公司 	◄─┘     	公司 	NN──┘                       │   
└──────────►	。  	punct	PU	。  	                	。  	        	。  	        	。  	PU──────────────────────────┘

PyTorch已经支持M1的GPU,花了几个月的时间。TF虽然很快支持,但相当于重新开了一版,在体验上造成了断裂,未必是正确的做法。

训练

以微调BERT做情感分析为例:

from hanlp.components.classifiers.transformer_classifier_tf import TransformerClassifierTF, TransformerTextTransform
from hanlp.datasets.classification.sentiment import CHNSENTICORP_ERNIE_TRAIN, CHNSENTICORP_ERNIE_TEST, \
    CHNSENTICORP_ERNIE_DEV

save_dir = 'data/model/classification/chnsenticorp_bert_base'
classifier = TransformerClassifierTF(TransformerTextTransform(y_column=0))
classifier.fit(CHNSENTICORP_ERNIE_TRAIN, CHNSENTICORP_ERNIE_DEV, save_dir,
               transformer='chinese_L-12_H-768_A-12')
classifier.load(save_dir)
print(classifier.predict('前台客房服务态度非常好!早餐很丰富,房价很干净。再接再厉!'))
classifier.evaluate(CHNSENTICORP_ERNIE_TEST, save_dir=save_dir)

速度是:

Trained 3 epochs in 10 m 30 s, each epoch takes 3 m 30 s

对比服务器上的TITAN RTX 1080:

Trained 3 epochs in 6 m 13 s, each epoch takes 2 m 4 s

已经是一个数量级上的选手了,要知道服务器功耗大,这MBP只是个轻薄本。

总结

MBP2021开箱惊艳,手感丝滑。除了跑训练,我从未听到风扇响过。Rosetta体验是透明的,大多数软件直接就能跑。配合自己编译安装一些深度学习的库,HanLP还可以直接利用到原生M1以及它的GPU核心,性能可以跟TITAN RTX 1080达到一个数量级。至于刘海,根本没注意。买来至今都是合盖,连5k显示器。

宝剑赠英雄,希望HanLP搭配MBP能加速用户的研究工作。

hanlp2.jpg

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » M1加速深度学习:HanLP正式支持苹果芯GPU

我的作品

HanLP自然语言处理包《自然语言处理入门》