# lightning_npu
**Repository Path**: tlwzzy/lightning_npu
## Basic Information
- **Project Name**: lightning_npu
- **Description**: 本仓库为pytorch_lightning仓库的npu适配仓
- **Primary Language**: Python
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 4
- **Created**: 2023-06-11
- **Last Updated**: 2023-06-11
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# Lightning Adaptor适配使用指导
- [适配原则](#适配原则)
- [基础训练及muti-gpu使用](#基础训练及muti-gpu使用)
- [muti-node使用](#muti-node使用)
- [16bit\_precision使用](#16bit_precision使用)
- [experiment\_managers使用](#experiment_managers使用)
- [early\_stopping使用](#early_stopping使用)
- [model\_checkpoint使用](#model_checkpoint使用)
## 适配原则
- Lightning Adaptor为Pytorch_Lightning在NPU硬件场景下适配组件,该组件为使用Pytorch_Lightning的模型训练提供NPU支持。
- Lightning Adaptor的适配原则如下:
- 非侵入式修改,在适配组件中修改,以Accelerator、Strategy、Plugin、Callback等组件形式对Pytorch_Lightning提供NPU调用支持,不直接在原仓库中进行修改。
- 尽可能做到上层无感,使得调用方在尽可能少修改代码的情况下能够适配。
## 基础训练及muti-gpu使用
### NPU单卡训练
- Accelerator:
- 原生Pytorch_Lightning:在使用不同硬件设备执行训练时,默认Trainer()使用auto(会自动依次检测TPU、IPU、HPU、GPU是否可用,如果都不可用使用CPU,如果可用则使用第一个可用的设备类型),在Trainer的accelerator参数可以传入指定设备字符串或者对应设备Accelerator()对象来调用不同的硬件设备。
- Lightning Adaptor:由于Lightning Adaptor不改动原仓库代码,所以在使用NPU硬件去执行训练时,同样是在Trainer中accelerator传入本仓lightning_npu/accelerators/npu.py中的NPUAccelerator()实例对象来指定使用NPU硬件,但是不支持accelerator使用字符串"npu"来进行指定。
- Strategy:
- 原生Pytorch_Lightning:在Trainer的strategy中设定改变训练、验证、测试的行为策略,不同的设备和训练方案可能使用不同的策略。
- Lightning Adaptor:在NPU的适配中同样需要strategy的适配,无论是单卡还是多卡。在NPU上执行单卡训练需要在Trainer中strategy传入本仓lightning_npu/strategies/npu.py中的SingleNPUStrategy()实例对象来指定训练策略。
> 说明:
>
>- 当需要在单卡训练中指定某一张卡的时候,通过传入SingleNPUStrategy的初始化参数device_index=n来指定,n类型为int,是从0开始计算的设备编号。
>```python
> # 指定0卡执行训练的Trainer传参
> strategy=SingleNPUStrategy(device_index=0)
> ```
>- 需注意的是传入Trainer的devices设定为1,也同样需要在Trainer中传入strategy为SingleNPUStrategy,并不能单独依靠devices去指定单卡训练。
>- 一旦传入strategy为SingleNPUStrategy时,无论devices设为何值,最终都是单卡训练无法调起多卡。
使用Pytorch_Lightning进行训练有使用LightningCLI、直接创建Trainer两种方式:
- LightningCLI(可配置命令行工具启动训练):
```python
cli = LightningCLI(
ImageClassifier,
MNISTDataModule,
trainer_defaults={
"accelerator": NPUAccelerator(),
"devices": 1,
"max_epochs": 5,
"strategy" : SingleNPUStrategy(),
},
seed_everything_default=42,
save_config_overwrite=True,
run=False
)
cli.trainer.fit(cli.model, datamodule=cli.datamodule)
cli.trainer.test(ckpt_path="best", datamodule=cli.datamodule)
```
- Trainer(执行脚本启动训练):
```python
trainer = Trainer(accelerator=NPUAccelerator(),
devices=1,
max_epochs=5,
strategy=SingleNPUStrategy())
trainer.fit(ImageClassifier, datamodule=MNISTDataModule)
trainer.test(ckpt_path="best", datamodule=MNISTDataModule)
```
>说明:
>- 使用LightningCLI和创建Trainer方式进行训练没有本质区别,后文将统一以LightningCLI为例讲解说明。
>- ImageClassifier为需要训练的模型定义(需继承于LightningModule),MNISTDataModule为需要训练的数据模块(需继承于LightningDataModule),详情见本仓lightning_npu/examples/module及dataset_moudle,实际场景需根据实际训练任务修改。
### NPU多卡训练
- Accelerator:
- 与单卡相同,NPU多卡训练的accelerator依然还是NPUAccelerator()。
- Strategy:
- 原生Pytorch_Lightning:strategy使用DDPStrategy()以支持多卡。
- Lightning Adaptor:适配多卡训练的strategy使用lightning_npu/strategies/npu_parallel.py的NPUParallelStrategy()实例对象以支持NPU多卡。
>说明:
在进行NPU多卡的训练中,指定具体用卡的方式和单卡不同,通过传入Trainer的devices参数去进行设定,当为int时,为指定调用前n张卡,n为传入int值;当传入为[int,int...]时,为指定调用列表中int值对应索引的卡(从零开始计)。
>```python
>
> #指定前4张卡(即0,1,2,3卡)执行训练的Trainer传参
> devices=4,#与devices[0,1,2,3]等同
> strategy=NPUParallelStrategy()
>
> #指定0、1、2、3卡执行训练的Trainer传参,只能指定2、4、8张卡训练,无法指定奇数张卡,同时须确保配置的卡号为ranktable文件中的有效配置
> devices=[0,1,2,3],
> strategy=NPUParallelStrategy()
>
>```
- LightningCLI(可配置命令行工具启动训练):
```python
cli = LightningCLI(
ImageClassifier,
MNISTDataModule,
trainer_defaults={
"accelerator": NPUAccelerator(),
"devices": 8,
"max_epochs": 5,
"strategy" : NPUParallelStrategy(),
},
seed_everything_default=42,
save_config_overwrite=True,
run=False
)
cli.trainer.fit(cli.model, datamodule=cli.datamodule)
cli.trainer.test(ckpt_path="best", datamodule=cli.datamodule)
```
>说明:
需要注意的是npu的多卡只支持DDP,不支持DP,因此muti-gpu的NPU实现也是基于DDP的。
## muti-node使用
原生Pytorch_Lightning:多机多卡不需要在模型中有额外的设置,只需要在Trainer中开启DDP(GPU场景,其他设备不一定使用DDP),并设置相应环境变量,传入Trainer的num_nodes填写使用的多机集群节点数。
Lightning Adaptor:正如在muti-gpu中的说明一样,NPU的多卡strategy适配NPUParallelStrategy就是基于DDP的,所以在使用NPU设备执行多机多卡训练时直接使用NPUParallelStrategy()实例对象就可以,无需额外开启DDP,同样传入Trainer的num_nodes填写使用的多机集群节点数。
- NPU多机多卡通讯配置:
1. 需要多机之间通过交换机配置有局域网
2. 配置多机多卡的连通性:
- 0机上可以是这样配置:
```shell
hccn_tool -i 0 -ip -s address 192.168.100.101 netmask 255.255.255.0
hccn_tool -i 1 -ip -s address 192.168.101.101 netmask 255.255.255.0
hccn_tool -i 2 -ip -s address 192.168.102.101 netmask 255.255.255.0
hccn_tool -i 3 -ip -s address 192.168.103.101 netmask 255.255.255.0
hccn_tool -i 4 -ip -s address 192.168.100.100 netmask 255.255.255.0
hccn_tool -i 5 -ip -s address 192.168.101.100 netmask 255.255.255.0
hccn_tool -i 6 -ip -s address 192.168.102.100 netmask 255.255.255.0
hccn_tool -i 7 -ip -s address 192.168.103.100 netmask 255.255.255.0
```
- 1机上可以是这样配置:
```shell
hccn_tool -i 0 -ip -s address 192.168.100.111 netmask 255.255.255.0
hccn_tool -i 1 -ip -s address 192.168.101.111 netmask 255.255.255.0
hccn_tool -i 2 -ip -s address 192.168.102.111 netmask 255.255.255.0
hccn_tool -i 3 -ip -s address 192.168.103.111 netmask 255.255.255.0
hccn_tool -i 4 -ip -s address 192.168.100.110 netmask 255.255.255.0
hccn_tool -i 5 -ip -s address 192.168.101.110 netmask 255.255.255.0
hccn_tool -i 6 -ip -s address 192.168.102.110 netmask 255.255.255.0
hccn_tool -i 7 -ip -s address 192.168.103.110 netmask 255.255.255.0
```
>说明:
更多机器节点以此类推,IP不冲突可以相互访问即可。
3. 连通性检查:
```shell
# 将192.168.xxx.xxx注册到连通性检查中
hccn_tool -i 0 -netdetect -s address 192.168.xxx.xxx
# 执行注册的IP连通性检查
hccn_tool -i 0 -net_health -g
```
> 说明:
如果执行没有任何打印显示则说明没有联通,连通性检查成功后应有success的打印信息。
4. 确认交换机状态正常:
```shell
# 执行下面指令
for i in {0..7}; do hccn_tool -i $i -lldp -g ; done
```
>说明:
若返回值不为空则lldp正常。
5. 额外的环境变量:
```shell
export HCCL_IF_IP=${本机IP}
```
>说明:
每个节点都要设置HCCL_IF_IP为节点本机IP。
- NPU环境变量配置:
1. 需要配置的环境变量介绍:
- MASTER_PORT - required; has to be a free port on machine with NODE_RANK 0
- MASTER_ADDR - required (except for NODE_RANK 0); address of NODE_RANK 0 node
- WORLD_SIZE - required; how many nodes are in the cluster
- NODE_RANK - required; id of the node in the cluster
2. 环境变量配置shell脚本说明(lightning_npu/muti_node.sh):
```shell
# 选取未被占用的端口,60010是举例,如果60010被占用请填写其他未占用端口
export MASTER_PORT="60010"
# 主节点的IP,在计算集群的任意节点上执行都填写主节点的IP,格式是点分十进制
export MASTER_ADDR="xxx.xxx.xxx.xxx"
# 计算集群的节点数量,需根据当前使用的集群节点数填写,此处以两个节点的多机计算集群为例
export WORLD_SIZE="2"
# 执行环境变量配置的节点序号,此处以当前节点为主节点(即节点序号为0)为例,需注意不同节点使用不同节点序号配置环境变量
export NODE_RANK="0"
```
3. 按需修改好上述shell脚本后,在计算集群的每个节点上执行:
```shell
source ./lightning_npu/muti_node.sh
```
>说明:
> 需要每个节点都执行根据每个节点修改后的shell
> ```mermaid
> graph LR;
> Computing_cluster((多机计算集群))
> Node_A(A节点
主节点)
> Node_B(B节点
副节点)
> Node_C(C节点
副节点)
> Node_more(...
副节点)
> A_Modify_file[修改MASTER_PORT为查询A未使用的端口60010
修改MASTER_ADDR为A节点IP
修改WORLD_SIZE为集群节点总数
修改NODE_RANK为当前节点编号0]
> B_Modify_file[修改MASTER_PORT为查询A未使用的端口60010
修改MASTER_ADDR为A节点IP
修改WORLD_SIZE为集群节点总数
修改NODE_RANK为当前节点编号1]
> C_Modify_file[修改MASTER_PORT为查询A未使用的端口60010
修改MASTER_ADDR为A节点IP
修改WORLD_SIZE为集群节点总数
修改NODE_RANK为当前节点编号2]
> MORE_Modify_file[修改MASTER_PORT为查询A未使用的端口60010
修改MASTER_ADDR为A节点IP
修改WORLD_SIZE为集群节点总数
修改NODE_RANK为当前节点编号3...]
> A_source[命令行source修改后的shell脚本]
> B_source[命令行source修改后的shell脚本]
> C_source[命令行source修改后的shell脚本]
> more_source[命令行source修改后的shell脚本]
>
>
> Computing_cluster-->Node_A
> Computing_cluster-->Node_B
> Computing_cluster-->Node_C
> Computing_cluster-->Node_more
> Node_A-->A_Modify_file
> Node_B-->B_Modify_file
> Node_C-->C_Modify_file
> Node_more-->MORE_Modify_file
> A_Modify_file-->A_source
> B_Modify_file-->B_source
> C_Modify_file-->C_source
> MORE_Modify_file-->more_source
>
>
> ```
- LightningCLI(可配置命令行工具启动训练):
```python
cli = LightningCLI(
ImageClassifier,
MNISTDataModule,
trainer_defaults={
"accelerator": NPUAccelerator(),
"devices": 2,
"max_epochs": 5,
"strategy" : NPUParallelStrategy(),
"num_nodes" : 2
},
seed_everything_default=42,
save_config_overwrite=True,
run=False
)
cli.trainer.fit(cli.model, datamodule=cli.datamodule)
cli.trainer.test(cli.model, datamodule=cli.datamodule)
```
>说明:
>- 使用muti-node时,传入num_nodes为计算集群的节点数量,devices为当前执行脚本节点的用于计算卡数,可像多卡通过[int,int,...]方式指定卡序号用哪几张卡。
>- strategy必须使用NPUParallelStrategy()。
>- 在使用DDP的时候,使用Trainer的checkpoint保存功能,lightning会自动确保只有主节点上会保存ckpt,也就是rank_zero_only,所以在test流程时,如果使用ckpt_path而非model实例,会存在分节点找不到ckpt文件的问题,建议使用model实例,如果一定要使用ckpt,请确保在分节点保存ckpt文件。
>- 若出现host nic listen start failed, ip[0xxxxxxxxx], port[xxxxx], return[xx]的报错,可能是由于端口有问题,可以尝试更换端口
## 16bit_precision使用
原生Pytorch_Lightning:通过设定传入Trainer的precision为16,amp_backend为"native"以支持torch原生混合精度,传入Trainer的precision为16,amp_backend为"apex"以支持amp.apex混合精度。
Lightning Adaptor:可以使用传入Trainer的amp_backend为"apex"的方式使用amp.apex混合精度,但是该方式不支持指定固定loss_scale,只能使用动态loss_scale。所以Lightning Adaptor通过传入Trainer中的plugins使用lightning_npu/plugins/npu的NpuMixedPrecisionPlugin(loss_scale=)实例对象的方式,基于amp.apex支持固定loss_scale的混合精度训练。
>说明:
> 使用NpuMixedPrecisionPlugin方式,当需要动态loss_scale的时候,可以通过plugin不传入loss_scale来启用动态loss_scale。Lightning Adaptor不支持torch原生方式执行混合精度训练。
- LightningCLI(可配置命令行工具启动训练):
```python
cli = LightningCLI(
ImageClassifier,
MNISTDataModule,
trainer_defaults={
"accelerator": NPUAccelerator(),
"devices": 4,
"max_epochs": 5,
"strategy": NPUParallelStrategy(),
"precision": 16,
"plugins": NpuMixedPrecisionPlugin(amp_level="O1", loss_scale=64.0)
},
seed_everything_default=42,
save_config_overwrite=True,
run=False
)
cli.trainer.fit(cli.model, datamodule=cli.datamodule)
cli.trainer.test(ckpt_path="best", datamodule=cli.datamodule)
```
>说明:
>- NpuMixedPrecisionPlugin中不传入loss_scale,即为动态loss_scale。
>- 使用NpuMixedPrecisionPlugin不需要额外在Trainer中传入amp_backend。
>- NpuMixedPrecisionPlugin通过传入的amp_level的方式,指定NVIDIA optimization level。
## experiment_managers使用
原生Pytorch_Lightning:通过logger的方式使用experiment_managers,不同manager的logger的定义方式不同,目前支持Comet.ml、MLflow、Neptune、TensorBoard、Weights and Biases五种。
Lightning Adaptor:和原生使用方式相同。
- LightningCLI(可配置命令行工具启动训练):
```python
comet_logger = CometLogger(
api_key="comet_API_KEY",
save_dir=".",
project_name="MNIST",
rest_api_key="comet_API_KEY",
experiment_name="comet_logs",
)
mlf_logger = MLFlowLogger(experiment_name="mlf_logs", tracking_uri="to_path")
neptune_logger = NeptuneLogger(
api_key="neptune_API_KEY",
project="WORKSPACE/PROJECT",
tags=["training","minist"]
)
tb_logger = TensorBoardLogger("to_path", name="tb_logs")
wandb_logger = WandbLogger(project="MNIST", log_model="all")
cli = LightningCLI(
ImageClassifier,
MNISTDataModule,
trainer_defaults={
"accelerator": NPUAccelerator(),
"devices": [0,1,2,3],
"max_epochs": 10,
"strategy": NPUParallelStrategy(),
"logger": [comet_logger,neptune_logger,tb_logger,wandb_logger,mlf_logger]
},
seed_everything_default=42,
save_config_overwrite=True,
run=False
)
wandb_logger.watch(ImageClassifier())
cli.trainer.fit(cli.model, datamodule=cli.datamodule)
cli.trainer.test(ckpt_path="best", datamodule=cli.datamodule)
```
>说明:
具体进阶使用详见对应logger官网。
## early_stopping使用
原生Pytorch_Lightning:通过在Trainer中传入callbacks为EarlyStopping()的方式实现early_stopping,在模型定义Module处需要记录期望度量的值。
Lightning Adaptor:和原生使用方式相同。
- Module(模型定义处):
```python
class ImageClassifier(LightningModule):
def __init__(self, model, lr=1.0, gamma=0.7, batch_size=32):
super().__init__()
self.save_hyperparameters(ignore="model")
self.model = model or Net()
self.test_acc = Accuracy()
def forward(self, x):
return self.model(x)
def training_step(self, batch, batch_idx):
x, y = batch
logits = self.forward(x)
loss = F.nll_loss(logits, y.long())
# 记录需要度量的值,EarlyStopping回调才可以根据该值衡量停止训练
self.log("val_loss", loss)
return loss
```
>说明:
>- 此处以loss为例,可以是其他值。
>- 此处以training_step为例,可以是验证和测试等其他阶段。
- LightningCLI(可配置命令行工具启动训练):
```python
cli = LightningCLI(
ImageClassifier,
MNISTDataModule,
trainer_defaults={
"accelerator": NPUAccelerator(),
"devices": 8,
"max_epochs": 50,
"strategy" : NPUParallelStrategy(),
"check_val_every_n_epoch" : 2,
"callbacks": EarlyStopping(monitor="val_loss",verbose=True,min_delta=0.001,patience=3)
},
seed_everything_default=42,
save_config_overwrite=True,
run=False
)
cli.trainer.fit(cli.model, datamodule=cli.datamodule)
cli.trainer.test(ckpt_path="best", datamodule=cli.datamodule)
```
>说明:
>- EarlyStopping中传入monitor度量值的别名,verbose为是否打印详情,min_delta为界定的尺度,当度量值变化不超过该尺度时认为没有提升,patience为多少次度量值没有提升后停止训练,mode为需要的是度量值大还是小,只有"max"、"min"两种可选,默认值为"min",上述样例中就是记录的loss值,当连续3(patience)次的loss降低(mode:min)幅度不超过0.001(min_delta)时,认为没有提升,趋于稳定,停止训练,训练过程中会将度量值的相关信息打印(verbose:True)。
>- 需要注意的是patience的次数与记录的频率挂钩,当两个epoch一次记录,patience为3时,就是三次记录也就是6个epoch度量值没有提升时停止训练,不是指的epoch数量,同时这个过程只关注记录的那一次,也就是说中间间隔的epoch度量值没有提升的也不会被算进patience的次数。
## model_checkpoint使用
原生Pytorch_Lightning:通过在Trainer中传入callbacks为ModelCheckpoint()的方式实现条件ckpt保存,通过在trainer.fit()中传入ckpt_path来加载ckpt实现断点续训。
Lightning Adaptor:和原生使用方式相同。
- LightningCLI(可配置命令行工具启动训练):
```python
checkpoint_callback = ModelCheckpoint(dirpath="./ckpt/",
#保存的路径
save_top_k=2,
#保存top的k个ckpt
monitor="val_loss",
#保存ckpt的条件度量值
mode="min",
#要保存的度量值最大还是最小的ckpt
filename="sample-mnist--{epoch:02d}-{val_loss:.2f}",
#自定义保存ckpt的文件名
save_weights_only=True,
#是否只保存权重
)
cli = LightningCLI(
ImageClassifier,
MNISTDataModule,
trainer_defaults={
"accelerator": NPUAccelerator(),
"devices": 8,
"max_epochs": 5,
"strategy": NPUParallelStrategy(),
#通过callbacks传入条件ckpt保存的回调
"callbacks": [checkpoint_callback],
},
seed_everything_default=42,
save_config_overwrite=True,
run=False
)
#通过ckpt_path读入ckpt文件,断点续训
cli.trainer.fit(cli.model, datamodule=cli.datamodule, ckpt_path=checkpoint)
cli.trainer.save_checkpoint("myckpt.ckpt")
cli.trainer.test(ckpt_path="best", datamodule=cli.datamodule)
```
>说明:
依赖2022.12以后的torch_npu,之前的在条件ckpt保存的时候,load进来的值和运行值可能不在同一设备导致无法比对。