首页 > 数据库 > 数据库开发技术 > OneFlow的大模型分片保存和加载策略

# 自定义 get_sbp 函数。def get_sbp(state_dict, tensor):if tensor is state_dict["System-Train-TrainStep"]:return flow.sbp.broadcastif tensor is state_dict["module_pipeline"]["m_stage3.linear.weight"]:return flow.sbp.split(1)if tensor is state_dict["module_pipeline"]["m_stage3.linear.bias"]:return flow.sbp.broadcastreturn flow.sbp.split(0)model_file_state_dict = flow.utils.global_view.to_global(state_dict, placement=model_file_placement, sbp=get_sbp,) # 使用 sbp=get_sbp 处理特殊的键,也支持指定普通的 SBP。rank_id = flow.env.get_rank()# 保存模型分片的路径,一个 rank 对应一个路径。state_dict_dir = "./graph_save_load_global_" + str(rank_id)if flow.env.get_rank() in model_file_placement.ranks:flow.save(flow.utils.global_view.to_local(model_file_state_dict),state_dict_dir,)
首先,将原模型(state_dict)转化到模型文件的 Placement 和 SBP 上,model_file_placement 为要分片保存模型的设备阵列,也就是将 state dict 按 split(0) 分片到 model_file_placement 上。
这里之所以自定义 get_sbp 函数,是因为用户可以传进来一个 (x, tensor) -> sbp 的函数来解决特殊 Tensor 对应不同 SBP 的需求。
举个例子(当前例子基于 Graph 模式),对于 state_dict["System-Train-TrainStep"] 这种 shape 为 [1] 的 Tensor,我们就不能按 split(0) 分片了,SBP 可以选用 broadcast。而 state_dict["module_pipeline"]["m_stage3.linear.weight"] 只能在第 1 维度切分,对于 state_dict["module_pipeline"]["m_stage3.linear.bias"] 这种不可切分的小 Tensor(s),SBP 可以选用 broadcast。这样支持用户 DIY SBP 的处理,更加灵活。
在后面的处理中,使用 flow.utils.global_view.to_local() 接口得到 model_file_state_dict 的本地分量,并调用 save() 保存模型。其中,state_dict_dir 是带有设备 id 的目录,需要区分不同设备,推荐一个 rank 对应一个路径,路径名用 rank id 的方式。
if cur_rank in model_file_placement.ranks:local_state_dict = flow.load(state_dict_dir)else:local_state_dict = Noneglobal_state_dict = flow.utils.global_view.to_global(placement=model_file_placement, sbp=get_sbp,)graph_model.load_state_dict(global_state_dict)
首先,用 load() 方法在每个保存切片的设备上加载 state dict。对应的,需要把 local rank 上的 state dict 转换到模型文件的 placement 和 sbp 上,得到了 global_state_dict。这一步和保存模型应该是对应的,SBP 和 Placement 也是一致的。
最后,global_state_dict 可以成功加载到 graph_model(nn.Graph) 中。当然,nn.Module 和 nn.Graph 处理方法是一致的。
将 state dict 加载到 nn.Module 中
除了以上两个特征外,在将 state dict 加载到 nn.Module 时,OneFlow 提供了 SBP 和 Placement 的自动转换。
在下面的例子中,首先构造一个 m(nn.Module)对象,再将 global_state_dict 的 SBP 设置为 split(0),而 m 的 SBP 为 broadcast。同时 placement 也放生了变化,从 placement("cpu", ranks=[0, 1]) 到 flow.placement("cpu", ranks=[0])。这时用户不需要其他操作,OneFlow 会自动做 SBP 和 placement 的转换过程。
import oneflow as flowm = flow.nn.Linear(2,6)model_file_placement = flow.placement("cpu", ranks=[0, 1])state_dict = {"weight":flow.ones(3,2), "bias":flow.zeros(3)}global_state_dict = flow.utils.global_view.to_global(state_dict, placement=model_file_placement, sbp=flow.sbp.split(0),)m.to_global(placement=flow.placement("cpu", ranks=[0]), sbp=flow.sbp.broadcast)m.load_state_dict(global_state_dict)print(m.state_dict())
使用 2 卡运行上面的代码,可以看到,我们自己构造的字典中的全局张量,已经被加载到 m Module 中。此外,输出 OrderedDict 的 tensor 的 SBP 已经从 split(0) 自动转换为 broadcast,'weight' 对应 tensor 的形状也是我们期待的 [6, 2],'bias' 形状为 [6]。
OrderedDict([('weight', tensor([[1., 1.],[],[],[],[],[]], placement=oneflow.placement(type="cpu", ranks=[0]), sbp=(oneflow.sbp.broadcast,), dtype=oneflow.float32,requires_grad=True)), ('bias', tensor([0., 0., 0., 0., 0., 0.], placement=oneflow.placement(type="cpu", ranks=[0]), sbp=(oneflow.sbp.broadcast,),dtype=oneflow.float32, requires_grad=True))])
import osimport numpy as npimport oneflow as flowmodel_tensor_placement = flow.placement("cuda", ranks=[0, 1, 2, 3])# model_file_placement 为存储模型分片的设备的 placement,表示在 Rank 2 和 Rank 3 上可为 None。model_file_placement = flow.placement("cpu", ranks=[0, 1])P0 = flow.placement(model_tensor_placement.type, ranks=[0])P1 = flow.placement(model_tensor_placement.type, ranks=[1])P2 = flow.placement(model_tensor_placement.type, ranks=[2])P3 = flow.placement(model_tensor_placement.type, ranks=[3])def get_sbp(state_dict, tensor):if tensor is state_dict["System-Train-TrainStep"]:return flow.sbp.broadcastif tensor is state_dict["module_pipeline"]["m_stage3.linear.weight"]:return flow.sbp.split(1)if tensor is state_dict["module_pipeline"]["m_stage3.linear.bias"]:return flow.sbp.broadcastreturn flow.sbp.split(0)class Stage0Module(flow.nn.Module):def __init__(self):super().__init__()self.linear = flow.nn.Linear(16, 8)self.relu = flow.nn.ReLU()def forward(self, x):return self.relu(self.linear(x))class Stage1Module(flow.nn.Module):def __init__(self):super().__init__()self.linear = flow.nn.Linear(8, 4)self.relu = flow.nn.ReLU()def forward(self, x):return self.relu(self.linear(x))class Stage2Module(flow.nn.Module):def __init__(self):super().__init__()self.linear = flow.nn.Linear(4, 2)self.relu = flow.nn.ReLU()def forward(self, x):return self.relu(self.linear(x))class Stage3Module(flow.nn.Module):def __init__(self):super().__init__()self.linear = flow.nn.Linear(2, 1)def forward(self, x):return self.linear(x)# 模拟 4 个 ranks 上的流水并行class PipelineModule(flow.nn.Module):def __init__(self):super().__init__()self.m_stage0 = Stage0Module()self.m_stage1 = Stage1Module()self.m_stage2 = Stage2Module()self.m_stage3 = Stage3Module()self.m_stage0.to_global(placement=P0, sbp=flow.sbp.broadcast)self.m_stage1.to_global(placement=P1, sbp=flow.sbp.broadcast)self.m_stage2.to_global(placement=P2, sbp=flow.sbp.broadcast)self.m_stage3.to_global(placement=P3, sbp=flow.sbp.broadcast)def forward(self, x):out_stage0 = self.m_stage0(x)in_stage1 = out_stage0.to_global(placement=P1, sbp=flow.sbp.broadcast)out_stage1 = self.m_stage1(in_stage1)in_stage2 = out_stage1.to_global(placement=P2, sbp=flow.sbp.broadcast)out_stage2 = self.m_stage2(in_stage2)in_stage3 = out_stage2.to_global(placement=P3, sbp=flow.sbp.broadcast)out_stage3 = self.m_stage3(in_stage3)return out_stage3class PipelineGraph(flow.nn.Graph):def __init__(self, module_pipeline):super().__init__()self.module_pipeline = module_pipelineself.module_pipeline.m_stage0.config.set_stage(0, P0)self.module_pipeline.m_stage1.config.set_stage(1, P1)self.module_pipeline.m_stage2.config.set_stage(2, P2)self.module_pipeline.m_stage3.config.set_stage(3, P3)self.config.set_gradient_accumulation_steps(2)self.add_optimizer(flow.optim.SGD(self.module_pipeline.parameters(), lr=0.001))def build(self, x):out = self.module_pipeline(x)out = out.sum()out.backward()return outdef train_with_graph(call_cnt=0, state_dict_dir=None, last_state_dict=None):# 形状为 [2, 16] 的固定输入张量x = flow.tensor([[0.4286,0.7402,0.4161,0.6103,0.7394,1.1330,-0.2311,-0.1013,0.8537,0.9757,-0.9842,0.3839,-0.5551,-0.8832,0.7820,0.7421,],[-0.1581,-1.0319,1.8430,0.3576,0.7288,-0.6912,0.9966,1.0840,-1.1760,1.5683,-0.2098,-1.6439,-2.7049,0.1949,1.6377,0.0745,],],dtype=flow.float32,placement=P0,sbp=flow.sbp.broadcast,)module_pipeline = PipelineModule()graph_model = PipelineGraph(module_pipeline)cur_rank = flow.env.get_rank()if call_cnt == 1:if cur_rank in model_file_placement.ranks:local_state_dict = flow.load(state_dict_dir)else:local_state_dict = None# 使用 sbp=get_sbp 处理特殊的键global_state_dict = flow.utils.global_view.to_global(local_state_dict, placement=model_file_placement, sbp=get_sbp,)graph_model.load_state_dict(global_state_dict)graph_model(x)state_dict = graph_model.state_dict()if call_cnt == 0:model_file_state_dict = flow.utils.global_view.to_global(state_dict, placement=model_file_placement, sbp=get_sbp,)if flow.env.get_rank() in model_file_placement.ranks:flow.save(flow.utils.global_view.to_local(model_file_state_dict),state_dict_dir,)if __name__=="__main__":rank_id = flow.env.get_rank()# 保存路径,一个 rank 对应一个路径。state_dict_dir = "./graph_save_load_global_" + str(rank_id)# 保存模型train_with_graph(0, state_dict_dir)# 加载模型train_with_graph(1, state_dict_dir)
结语

原文作者:OneFlow;如有侵权,请联系管理员删除。