-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch_plus_index.json
executable file
·1 lines (1 loc) · 93.5 KB
/
search_plus_index.json
1
{"./":{"url":"./","title":"分布式机器学习介绍","keywords":"","body":"Introduction 1. 分布式训练策略 模型并行:用于模型过大的情况,需要把模型的不同层放在不同节点 or GPU上,计算效率不高,不常用。 数据并行:把数据分成多份,每份数据单独进行前向计算和梯度更新,效率高,较常用。 2. 分布式并行模式 分布式训练一般分为同步训练和异步训练,同步训练中所有的worker读取mini-batch的不同部分,同步计算损失函数的gradient,最后将每个worker的gradient整合之后更新模型。异步训练中每个worker独立读取训练数据,异步更新模型参数。通常同步训练利用AllReduce来整合不同worker计算的gradient,异步训练则是基于参数服务器架构(parameter server)。 同步训练:所有进程前向完成后统一计算梯度,统一反向更新。 异步训练:每个进程计算自己的梯度,并拷贝主节点的参数进行更新,容易造成错乱,陷入次优解。 3. 分布式训练架构 Parameter Server:集群中有一个parameter server和多个worker,server需要等待所有节点计算完毕统一计算梯度,在server上更新参数,之后把新的参数广播给worker。 Ring AllReduce:只有worker,所有worker形成一个闭环,接受上家的梯度,再把累加好的梯度传给下家,最终计算完成后更新整个环上worker的梯度(这样所有worker上的梯度就相等了),然后求梯度反向传播。比PS架构要高效。 4. Pytorch分布式框架 torch.nn.DataParallel torch.distributed.DistributedDataParallel:数据并行,优于DataParallel,好像仍是PS架构,建议 NVIDIA/apex:封装了DistributedDataParallel,AllReduce架构,建议 参考链接 神经网络分布式训练 深度学习分布式方案(个人笔记) 版权所有:梁大师姐 "},"chapter1/点对点通信.html":{"url":"chapter1/点对点通信.html","title":"1.1 点对点通信","keywords":"","body":"点对点通信 一、send/recv 下面通过torch.distributed的send/recv接口实现一个简易的ping-pong 程序。程序功能如下: tensor 初始值为0 process 0 (或叫rank 0):对tensor加1,然后发送给process 1(或叫rank1); process 1:接收到tensor后,对tensor 加2,然后在发送给process 0; process 0:接收process1发送的tensor; 1.1 初始化 pytorch中在分布式通信原语使用之前,需要对分布式模块进行初始化。pytorch的分布式模块通过torch.distributed.init_process_group来完成 通过环境变量MASTER_ADDR和MASTER_PORT设置rank0的IP和PORT信息,rank0的作用相当于是协调节点,需要其他所有节点知道其访问地址; 本例中后端选择的是gloo,通过设置NCCL_DEBUG环境变量为INFO,输出NCCL的调试信息; init_process_group:执行网络通信模块的初始化工作 backend:设置后端网络通信的实现库,可选的为gloo、nccl和mpi;本例选择gloo作为backend(注:nccl不支持p2p通信,mpi需要重新编译pytorch源码才能使用); rank:为当前rank的index,用于标记当前是第几个rank,取值为0到work_size - 1之间的值; world_size: 有多少个进程参与到分布式训练中; def init_process(rank_id, size, fn, backend='gloo'): \"\"\" Initialize the distributed environment. \"\"\" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank_id, world_size=size) fn(rank_id, size) 1.2 通信逻辑 下面的代码展示了rank0和rank1进行ping-pong通信的实现: 通过rank_id来区分当前应该执行哪一个rank的业务逻辑; pytorch 中通过torch.distributed.send(tensor, dst, group=None, tag=0) 和torch.distributed.isend(tensor, dst, group=None, tag=0) 来实现tensor的发送,其中send是同步函数,isend是异步函数; tensor:要发送的数据 dst:目标rank,填写目标rank id即可 pytorch中通过torch.distributed.recv(tensor, src=None, group=None, tag=0)和torch.distributed.irecv(tensor, src=None, group=None, tag=0)来实现tensor的接收,其中recv是同步函数,irecv是异步函数; tensor:接收的数据 src:接收数据来源的rank id def run(rank_id, size): tensor = torch.zeros(1) if rank_id == 0: tensor += 1 # Send the tensor to process 1 dist.send(tensor=tensor, dst=1) print('after send, Rank ', rank_id, ' has data ', tensor[0]) dist.recv(tensor=tensor, src=1) print('after recv, Rank ', rank_id, ' has data ', tensor[0]) else: # Receive tensor from process 0 dist.recv(tensor=tensor, src=0) print('after recv, Rank ', rank_id, ' has data ', tensor[0]) tensor += 1 dist.send(tensor=tensor, dst=0) print('after send, Rank ', rank_id, ' has data ', tensor[0]) 1.3 任务启动 通过下面的代码来启动两个process进行ping-pong通信: 这里使用torch.multiprocessing来启动多进程,torch.multiprocessing是python库中multiprocessing的封装,并且兼容了所有的接口 multiprocessing.set_start_method : 用于指定创建child process的方式,可选的值为fork、spawn和forkserver。使用spawn,child process仅会继承parent process的必要resource,file descriptor和handle均不会继承。 multiprocessing.Process(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None) : 用来启动child process if __name__ == \"__main__\": size = 2 processes = [] mp.set_start_method(\"spawn\") for rank in range(size): p = mp.Process(target=init_process, args=(rank, size, run)) p.start() processes.append(p) for p in processes: p.join() 1.4 测试 完整代码如下: import os import torch import torch.distributed as dist import torch.multiprocessing as mp def run(rank_id, size): tensor = torch.zeros(1) if rank_id == 0: tensor += 1 # Send the tensor to process 1 dist.send(tensor=tensor, dst=1) print('after send, Rank ', rank_id, ' has data ', tensor[0]) dist.recv(tensor=tensor, src=1) print('after recv, Rank ', rank_id, ' has data ', tensor[0]) else: # Receive tensor from process 0 dist.recv(tensor=tensor, src=0) print('after recv, Rank ', rank_id, ' has data ', tensor[0]) tensor += 1 dist.send(tensor=tensor, dst=0) print('after send, Rank ', rank_id, ' has data ', tensor[0]) def init_process(rank_id, size, fn, backend='gloo'): \"\"\" Initialize the distributed environment. \"\"\" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank_id, world_size=size) fn(rank_id, size) 执行效果如下: root@g48r13:/workspace/communication# python sync_p2p.py after send, Rank 0 has data tensor(1.) after recv Rank 1 has data tensor(1.) after send Rank 1 has data tensor(2.) after recv, Rank 0 has data tensor(2.) 参考链接 Pytorch - 分布式通信原语(附源码) - 颜挺帅的文章 - 知乎 "},"chapter1/集合通信.html":{"url":"chapter1/集合通信.html","title":"1.2 集合通信","keywords":"","body":"集合通信 和P2P通信相对应,集合通信则是1对多或是多对多的。在分布式系统中,各个节点间往往存在大量的集合通信需求,而我们可以用消息传递接口(Message Passing Interface, MPI)来定义一些比较底层的消息通信行为譬如Reduce、Allreduce、Scatter、Gather、Allgather等。常用的通信模式有: Broadcast Scatter Gather Reduce All reduce All gather AllReduce其实是一类算法,目标是高效得将不同机器中的数据整合(reduce)之后再把结果分发给各个机器。在深度学习应用中,数据往往是一个向量或者矩阵,通常用的整合则有Sum、Max、Min等。 1. broadcast broadcast的计算方式如上图所示。 在pytorch中通过torch.distributed.broadcast(tensor, src, group=None, async_op=False) 来broadcast通信。 参数tensor在src rank是input tensor,在其他rank是output tensor; 参数src设置哪个rank进行broadcast,默认为rank 0; 使用方式如下面代码所示: import os import torch import torch.distributed as dist import torch.multiprocessing as mp def run(rank_id, size): tensor = torch.arange(2, dtype=torch.int64) + 1 + 2 * rank_id print('before broadcast',' Rank ', rank_id, ' has data ', tensor) dist.broadcast(tensor, src = 0) print('after broadcast',' Rank ', rank_id, ' has data ', tensor) def init_process(rank_id, size, fn, backend='gloo'): \"\"\" Initialize the distributed environment. \"\"\" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank_id, world_size=size) fn(rank_id, size) if __name__ == \"__main__\": size = 4 processes = [] mp.set_start_method(\"spawn\") for rank in range(size): p = mp.Process(target=init_process, args=(rank, size, run)) p.start() processes.append(p) for p in processes: p.join() 输出内容为: 一共有4个rank参与了broadcast计算,计算之前:rank0 为[1, 2],rank1 为[3, 4], rank2为[5, 6], rank3为[7, 8] broadcast计算之后,所有rank的结果均rank0的tensor即[1, 2](因为在调用torch.distributed.broadcast时src设置为0,表示rank0进行broadcast) before broadcast Rank 1 has data tensor([3, 4]) before broadcast Rank 0 has data tensor([1, 2]) before broadcast Rank 2 has data tensor([5, 6]) before broadcast Rank 3 has data tensor([7, 8]) after broadcast Rank 1 has data tensor([1, 2]) after broadcast Rank 0 has data tensor([1, 2]) after broadcast Rank 2 has data tensor([1, 2]) after broadcast Rank 3 has data tensor([1, 2]) 2. scatter scatter的计算方式如上图所示。 在pytorch中通过torch.distributed.scatter(tensor, scatter_list=None, src=0, group=None, async_op=False) 来实现scatter通信。 参数tensor为除 src rank外,其他rank获取output tensor的参数 scatter_list为进行scatter计算tensor list 参数src设置哪个rank进行scatter,默认为rank 0; 使用方式如下面代码所示: 这里需要注意的是,仅有src rank才能设置scatter_list( 本例中为rank 0),否则会报错 import os import torch import torch.distributed as dist import torch.multiprocessing as mp def run(rank_id, size): tensor = torch.arange(2, dtype=torch.int64) + 1 + 2 * rank_id print('before scatter',' Rank ', rank_id, ' has data ', tensor) if rank_id == 0: scatter_list = [torch.tensor([0,0]), torch.tensor([1,1]), torch.tensor([2,2]), torch.tensor([3,3])] print('scater list:', scatter_list) dist.scatter(tensor, src = 0, scatter_list=scatter_list) else: dist.scatter(tensor, src = 0) print('after scatter',' Rank ', rank_id, ' has data ', tensor) def init_process(rank_id, size, fn, backend='gloo'): \"\"\" Initialize the distributed environment. \"\"\" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank_id, world_size=size) fn(rank_id, size) if __name__ == \"__main__\": size = 4 processes = [] mp.set_start_method(\"spawn\") for rank in range(size): p = mp.Process(target=init_process, args=(rank, size, run)) p.start() processes.append(p) for p in processes: p.join() 输出内容为: 一共有4个rank参与了scatter计算,计算之前:rank0 为[1, 2],rank1 为[3, 4], rank2为[5, 6], rank3为[7, 8],scatter list为[0,0], [1,1], [2,2], [3,3]; scatter计算之后,rank按顺序被分配scatter list的每一个tensor, rank0为[0,0], rank1为 [1, 1] , rank2为 [2, 2], rank3[3, 3]; root@g48r13:/workspace/communication# python scatter.py before scatter Rank 1 has data tensor([3, 4]) before scatter Rank 0 has data tensor([1, 2]) before scatter Rank 2 has data tensor([5, 6]) scater list: [tensor([0, 0]), tensor([1, 1]), tensor([2, 2]), tensor([3, 3])] before scatter Rank 3 has data tensor([7, 8]) after scatter Rank 1 has data tensor([1, 1]) after scatter Rank 0 has data tensor([0, 0]) after scatter Rank 3 has data tensor([3, 3]) after scatter Rank 2 has data tensor([2, 2]) 3. gather gather计算方式如上图所示。在pytorch中通过torch.distributed.gather(tensor, gather_list=None, dst=0, group=None, async_op=False)来实现gather的通信; 参数tensor是所有rank的input tensor gather_list是dst rank的output 结果 dst为目标dst 使用方式如下: 这里需要注意的是在rank 0(也就是dst rank)中要指定gather_list,并且要在gather_list构建好的tensor,否是会报错 import os import torch import torch.distributed as dist import torch.multiprocessing as mp def run(rank_id, size): tensor = torch.arange(2, dtype=torch.int64) + 1 + 2 * rank_id print('before gather',' Rank ', rank_id, ' has data ', tensor) if rank_id == 0: gather_list = [torch.zeros(2, dtype=torch.int64) for _ in range(4)] dist.gather(tensor, dst = 0, gather_list=gather_list) print('after gather',' Rank ', rank_id, ' has data ', tensor) print('gather_list:', gather_list) else: dist.gather(tensor, dst = 0) print('after gather',' Rank ', rank_id, ' has data ', tensor) def init_process(rank_id, size, fn, backend='gloo'): \"\"\" Initialize the distributed environment. \"\"\" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank_id, world_size=size) fn(rank_id, size) if __name__ == \"__main__\": size = 4 processes = [] mp.set_start_method(\"spawn\") for rank in range(size): p = mp.Process(target=init_process, args=(rank, size, run)) p.start() processes.append(p) for p in processes: p.join() 输出内容如下: 一共有4个rank参与了gather计算,计算之前:rank0 为[1, 2],rank1 为[3, 4], rank2为[5, 6], rank3为[7, 8] gather计算之后,gather_list的值为[tensor([1, 2]), tensor([3, 4]), tensor([5, 6]), tensor([7, 8])] root@g48r13:/workspace/communication# python gather.py before gather Rank 0 has data tensor([1, 2]) before gather Rank 3 has data tensor([7, 8]) after gather Rank 3 has data tensor([7, 8]) before gather Rank 1 has data tensor([3, 4]) before gather Rank 2 has data tensor([5, 6]) after gather Rank 1 has data tensor([3, 4]) after gather Rank 2 has data tensor([5, 6]) after gather Rank 0 has data tensor([1, 2]) gather_list: [tensor([1, 2]), tensor([3, 4]), tensor([5, 6]), tensor([7, 8])] 4. reduce reduce的计算方式如上图所示。在pytorch中通过torch.distributed.reduce(tensor, dst, op=, group=None, async_op=False)来实现reduce通信; 参数tensor是需要进行reduce计算的数据,对于dst rank来说,tensor为最终reduce的结果 参数dist设置目标rank的ID 参数op为reduce的计算方式,pytorch中支持的计算方式有SUM, PRODUCT, MIN, MAX, BAND, BOR, and BXOR 使用方式如下: import os import torch import torch.distributed as dist import torch.multiprocessing as mp def run(rank_id, size): tensor = torch.arange(2, dtype=torch.int64) + 1 + 2 * rank_id print('before reudce',' Rank ', rank_id, ' has data ', tensor) dist.reduce(tensor, dst = 3, op=dist.ReduceOp.SUM,) print('after reudce',' Rank ', rank_id, ' has data ', tensor) def init_process(rank_id, size, fn, backend='gloo'): \"\"\" Initialize the distributed environment. \"\"\" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank_id, world_size=size) fn(rank_id, size) if __name__ == \"__main__\": size = 4 processes = [] mp.set_start_method(\"spawn\") for rank in range(size): p = mp.Process(target=init_process, args=(rank, size, run)) p.start() processes.append(p) for p in processes: p.join() 执行结果如下: 一共有4个rank参与了gather计算,计算之前:rank0 为[1, 2],rank1 为[3, 4], rank2为[5, 6], rank3为[7, 8];dst rank设置为3 可见rank 3为reduce sum计算的最终结果; 需要注意这里有个副作用,就是rank 0、rank 1和rank 2的tensor也会被修改 root@g48r13:/workspace/communication# python reduce.py before reudce Rank 3 has data tensor([7, 8]) before reudce Rank 0 has data tensor([1, 2]) before reudce Rank 2 has data tensor([5, 6]) before reudce Rank 1 has data tensor([3, 4]) after reudce Rank 1 has data tensor([15, 18]) after reudce Rank 0 has data tensor([16, 20]) after reudce Rank 3 has data tensor([16, 20]) # reduce 的最终结果 after reudce Rank 2 has data tensor([12, 14]) 5. all-gather all-gather计算方式如上图所示。在pytorch中通过torch.distributed.all_gather(tensor_list, tensor, group=None, async_op=False)来实现。 参数tensor_list,rank从该参数中获取all-gather的结果 参数tensor,每个rank参与all-gather计算输入数据 使用方式如下: 同gather的使用方式基本一样,区别是all_gather中每个rank都要指定gather_list,并且要在gather_list构建好的tensor,否是会报错; import os import torch import torch.distributed as dist import torch.multiprocessing as mp def run(rank_id, size): tensor = torch.arange(2, dtype=torch.int64) + 1 + 2 * rank_id print('before gather',' Rank ', rank_id, ' has data ', tensor) gather_list = [torch.zeros(2, dtype=torch.int64) for _ in range(4)] dist.all_gather(gather_list, tensor) print('after gather',' Rank ', rank_id, ' has data ', tensor) print('after gather',' Rank ', rank_id, ' has gather list ', gather_list) def init_process(rank_id, size, fn, backend='gloo'): \"\"\" Initialize the distributed environment. \"\"\" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank_id, world_size=size) fn(rank_id, size) if __name__ == \"__main__\": size = 4 processes = [] mp.set_start_method(\"spawn\") for rank in range(size): p = mp.Process(target=init_process, args=(rank, size, run)) p.start() processes.append(p) for p in processes: p.join() 执行结果如下: 一共有4个rank参与了gather计算,计算之前:rank0 为[1, 2],rank1 为[3, 4], rank2为[5, 6], rank3为[7, 8]; 执行完gather_list后,每个rank均可以拿到最终gather_list的结果 root@g48r13:/workspace/communication# python all_gather.py before gather Rank 0 has data tensor([1, 2]) before gather Rank 2 has data tensor([5, 6]) before gather Rank 3 has data tensor([7, 8]) before gather Rank 1 has data tensor([3, 4]) after gather Rank 1 has data tensor([3, 4]) after gather Rank 0 has data tensor([1, 2]) after gather Rank 3 has data tensor([7, 8]) after gather Rank 2 has data tensor([5, 6]) after gather Rank 1 has gather list [tensor([1, 2]), tensor([3, 4]), tensor([5, 6]), tensor([7, 8])] after gather Rank 0 has gather list [tensor([1, 2]), tensor([3, 4]), tensor([5, 6]), tensor([7, 8])] after gather Rank 3 has gather list [tensor([1, 2]), tensor([3, 4]), tensor([5, 6]), tensor([7, 8])] after gather Rank 2 has gather list [tensor([1, 2]), tensor([3, 4]), tensor([5, 6]), tensor([7, 8])] 6. all-reduce all-reduce计算方式如上图所示。在pytorch中通过torch.distributed.all_reduce(tensor, op=, group=None, async_op=False)来实现all-reduce的调用; 使用方式如下面代码所示 import os import torch import torch.distributed as dist import torch.multiprocessing as mp def run(rank_id, size): tensor = torch.arange(2, dtype=torch.int64) + 1 + 2 * rank_id print('before reudce',' Rank ', rank_id, ' has data ', tensor) dist.all_reduce(tensor, op=dist.ReduceOp.SUM) print('after reudce',' Rank ', rank_id, ' has data ', tensor) def init_process(rank_id, size, fn, backend='gloo'): \"\"\" Initialize the distributed environment. \"\"\" os.environ['MASTER_ADDR'] = '127.0.0.1' os.environ['MASTER_PORT'] = '29500' dist.init_process_group(backend, rank=rank_id, world_size=size) fn(rank_id, size) if __name__ == \"__main__\": size = 4 processes = [] mp.set_start_method(\"spawn\") for rank in range(size): p = mp.Process(target=init_process, args=(rank, size, run)) p.start() processes.append(p) for p in processes: p.join() 输出内内容为: 一共有4个rank参与了all-reduce计算,计算之前:rank0 为[1, 2],rank1 为[3, 4], rank2为[5, 6], rank3为[7, 8] all-reduce计算之后,所有rank的结果均相同,为rank0-rank3的tensor计算sum的结果[1+3 + 5 + 7, 2 + 4 + 6 + 8]=[16, 20] root@g48r13:/workspace/communication# python all_reduce.py before reudce Rank 3 has data tensor([7, 8]) before reudce Rank 2 has data tensor([5, 6]) before reudce Rank 0 has data tensor([1, 2]) before reudce Rank 1 has data tensor([3, 4]) after reudce Rank 0 has data tensor([16, 20]) after reudce Rank 3 has data tensor([16, 20]) after reudce Rank 2 has data tensor([16, 20]) after reudce Rank 1 has data tensor([16, 20]) 参考资料 Pytorch - 分布式通信原语(附源码) - 颜挺帅的文章 - 知乎 NCCL、OpenMPI、Gloo对比 关于AllReduce torch.distributed.all_reduce的架构介绍 分布式通信包 - torch.distributed PyTorch torch.distributed官方文档 "},"chapter1/分布式架构.html":{"url":"chapter1/分布式架构.html","title":"1.3 分布式架构","keywords":"","body":"分布式架构 1. AllReduce AllReduce其实是一类算法,目标是高效得将不同机器中的数据整合(reduce)之后再把结果分发给各个机器。在深度学习应用中,数据往往是一个向量或者矩阵,通常用的整合则有Sum、Max、Min等。图一展示了AllReduce在有四台机器,每台机器有一个长度为四的向量时的输入和输出。 AllReduce具体实现的方法有很多种,最单纯的实现方式就是每个worker将自己的数据发给其他的所有worker,然而这种方式存在大量的浪费 2. 主从式架构 一个略优的实现是利用主从式架构,将一个worker设为master,其余所有worker把数据发送给master之后,由master进行整合元算,完成之后再分发给其余worker。不过这种实现master往往会成为整个网络的瓶颈。 3. Ring All Reduce Ring AllReduce算法分为两个阶段。 第一阶段,将N个worker分布在一个环上,并且把每个worker的数据分成N份。 接下来我们具体看第k个worker,这个worker会把第k份数据发给下一个worker,同时从前一个worker收到第k-1份数据。 之后worker会把收到的第k-1份数据和自己的第k-1份数据整合,再将整合的数据发送给下一个worker。 以此循环N次之后,每一个worker都会包含最终整合结果的一份。 第二阶段,每个worker将整合好的部分发送给下一个worker。worker在收到数据之后更新自身数据对应的部分即可。 假设每个worker的数据是一个长度为S的向量,那么个Ring AllReduce里,每个worker发送的数据量是O(S),和worker的数量N无关。这样就避免了主从架构中master需要处理O(S*N)的数据量而成为网络瓶颈的问题。 4. Map/Reduce 架构 主要是基于 hadoop 的mahout 和基于spark 的MLLib 5. Parameter Server 架构 参数服务器是一种广泛使用的通用的分布式机器学习架构,无论是 google 的上一代机器学习框架 distbelief 和最新的机器学习框架 tensorflow,百度的 paddle,亚马逊的 mxnet,还是 facebook 的 pytorch 在分布式训练上都提供了 Parameter Server支持 链接 【论文精读】基于参数服务器的可扩展分布式机器学习 "},"chapter1/torch_distributed.html":{"url":"chapter1/torch_distributed.html","title":"1.4 分布式通信包 - torch.distributed","keywords":"","body":"分布式通信包 - torch.distributed 1. 支持的后端 torch.distributed 支持三个后端,每个后端具有不同的功能。下表显示哪些功能可用于CPU/CUDA张量。仅当用于构建PyTorch的实现支持时,MPI才支持CUDA。目前PyTorch分发版仅支持Linux。默认情况下,Gloo和NCCL后端构建并包含在PyTorch的分布之中(仅在使用CUDA构建时为NCCL)。MPI是一个可选的后端,只有从源代码构建PyTorch时才能包含它。(例如,在安装了MPI的主机上构建PyTorch) Backend gloo gloo mpi mpi nccl nccl Device CPU GPU CPU GPU CPU GPU send ✓ ✘ ✓ ? ✘ ✓ recv ✓ ✘ ✓ ? ✘ ✓ broadcast ✓ ✓ ✓ ? ✘ ✓ all_reduce ✓ ✓ ✓ ? ✘ ✓ reduce ✓ ✘ ✓ ? ✘ ✓ all_gather ✓ ✘ ✓ ? ✘ ✓ gather ✓ ✘ ✓ ? ✘ ✓ scatter ✓ ✘ ✓ ? ✘ ✘ reduce_scatter ✘ ✘ ✘ ✘ ✘ ✓ all_to_all ✘ ✘ ✓ ? ✘ ✓ barrier ✓ ✘ ✓ ? ✘ ✓ 2. 支持的分布式环境初始化方式 2.1 TCP初始化 有两种方法可以使用TCP进行初始化,这两种方法都需要从所有进程可以访问的网络地址和所需的world_size。第一种方法需要指定属于rank 0进程的地址。此初始化方法要求所有进程都具有手动指定的排名。 [!NOTE|style:flat] 请注意,最新的分布式软件包中不再支持多播地址。group_name也被弃用了。 import torch.distributed as dist # 使用其中一台机器的地址 dist.init_process_group(backend, init_method='tcp://10.1.1.20:23456',rank=args.rank, world_size=4) 2.2 共享文件系统初始化 另一种初始化方法使用一个文件系统,该文件系统与组中的所有机器共享和可见,以及所需的world_size。URL应以file://开头,并包含共享文件系统上不存在的文件(在现有目录中)的路径。如果文件不存在,文件系统初始化将自动创建该文件,但不会删除该文件。因此,下一步初始化 init_process_group() 在相同的文件路径发生之前您有责任确保清理文件。 [!NOTE|style:flat] 请注意,在最新的分布式软件包中不再支持自动排名分配,并且也不推荐使用group_name。 警告 [!Warning|style:flat] 此方法假定文件系统支持使用fcntl进行锁定 - 大多数本地系统和NFS都支持它。 此方法将始终创建该文件,并尽力在程序结束时清理并删除该文件。换句话说,每次进行初始化都需要创建一个全新的空文件,以便初始化成功。如果再次使用先前初始化使用的相同文件(不会被清除),则这是意外行为,并且经常会导致死锁和故障。因此,即使此方法将尽力清理文件,如果自动删除不成功,您有责任确保在训练结束时删除该文件以防止同一文件被删除 下次再次使用。如果你打算在相同的文件系统路径下多次调用 init_process_group() 的时候,就显得尤为重要了。换一种说法,如果那个文件没有被移除并且你再次调用 init_process_group(),那么失败是可想而知的。这里的经验法则是,每当调用init_process_group()的时候,确保文件不存在或为空。 import torch.distributed as dist # 应始终指定等级 dist.init_process_group(backend, init_method='file:///mnt/nfs/sharedfile',world_size=4, rank=args.rank) 2.4 环境变量初始化 此方法将从环境变量中读取配置,从而可以完全自定义信息的获取方式。要设置的变量是: MASTER_PORT - 需要; 必须是机器上的自由端口,等级为0。 MASTER_ADDR - 要求(0级除外); 等级0节点的地址。 WORLD_SIZE - 需要; 可以在这里设置,也可以在调用init函数时设置。 RANK - 需要; 可以在这里设置,也可以在调用init函数时设置。 等级为0的机器将用于设置所有连接。 这是默认方法,意味着不必指定init_method(或者可以是env://)。 3. torch.distributed.all_reduce工作过程 汇总记录不同 GPU 上生成的准确率、损失函数等指标信息。这个 API 就是 torch.distributed.all_reduce。示意图如下: 具体来说,它的工作过程包含以下三步: 通过调用 all_reduce(tensor, op=...),当前进程会向其他进程发送 tensor(例如 rank 0 会发送 rank 0 的 tensor 到 rank 1、2、3) 接受其他进程发来的 tensor(例如 rank 0 会接收 rank 1 的 tensor、rank 2 的 tensor、rank 3 的 tensor)。 在全部接收完成后,当前进程(例如rank 0)会对当前进程的和接收到的 tensor (例如 rank 0 的 tensor、rank 1 的 tensor、rank 2 的 tensor、rank 3 的 tensor)进行 op (例如求和)操作。 使用 torch.distributed.all_reduce(loss, op=torch.distributed.reduce_op.SUM),我们就能够对不数据切片(不同 GPU 上的训练数据)的损失函数进行求和了。接着,我们只要再将其除以进程(GPU)数量 world_size就可以得到损失函数的平均值。 4. torch.distributed.reduce()工作过程 dist.reduce(tensor, dst, op, group):将 op 应用于每个 tensor 并将结果存储在 dst 中 参考资料 NCCL、OpenMPI、Gloo对比 torch.distributed.all_reduce的架构介绍 分布式通信包 - torch.distributed PyTorch torch.distributed官方文档 PyTorch 多GPU训练实践 (4) - DDP 进阶 "},"chapter1/PyTorch进程通信.html":{"url":"chapter1/PyTorch进程通信.html","title":"1.5 Pytorch多进程通信","keywords":"","body":"PyTorch分布式训练 1. 进程通信概念 1.1 主机,进程 每个主机可以运行多个进程,通常各个进程都是在做各自的事情,进程之间互不相干。 在MPI(消息传递接口(MPI)。MPI 几乎是所有分布式计算的主力。MPI 是一个开放标准,它定义了一系列关于节点互相通信的规则,MPI 也是一个编程模型/API。MPI 不是一款软件或者工具,它是一种规范)中,我们可以拥有一组可以互发消息的进程,这些进程甚至可以跨主机进行通信,这是我们称主机为节点,进程为工作节点,但在PyTorch中,还是采用主机和进程的说法。 1.2 World,Rank world可以认为是所有进程的集合。如下图中Host1中Host2中所有的进程集合被称为一个world,world size代表的就是这组能够通信的进程总数,图中world size=6。 rank 可以认为是这组能够通信的进程在world中的序号 Local Rank 可以认为是可以互相通信的进程在自己主机上的序号,注意每个主机中local rank的编号都是从零开始的 2. PyTorch单机多卡数据并行 2.1 启动多进程 #run_multiprocess.py #运行命令:python run_multiprocess.py import torch.multiprocessing as mp #这是一个多进程的包 def run(rank, size): print(\"world size:{}. I'm rank {}.\".format(size,rank)) if __name__ == \"__main__\": world_size = 4 #设置多进程的启动方式为spawn,使用此方式启动的进程,只会执行和 target 参数或者 run() 方法相关的代码。 mp.set_start_method(\"spawn\") #创建进程对象 #target为该进程要运行的函数,args为target函数的输入参数 p0 = mp.Process(target=run, args=(0, world_size)) p1 = mp.Process(target=run, args=(1, world_size)) p2 = mp.Process(target=run, args=(2, world_size)) p3 = mp.Process(target=run, args=(3, world_size)) #启动进程 p0.start() p1.start() p2.start() p3.start() #当前进程会阻塞在join函数,直到相应进程结束。 p0.join() #主线程等待p终止,p.join只能join住start开启的进程,而不能join住run开启的进程 p1.join() p2.join() p3.join() 2.2 多进程间通信 #multiprocess_comm.py #运行命令:python multiprocess_comm.py import os import torch.distributed as dist #PyTorch 分布式训练包 import torch.multiprocessing as mp def run(rank, size): #MASTER_ADDR和MASTER_PORT是通信模块初始化需要的两个环境变量。 #由于是在单机上,所以用localhost的ip就可以了。 os.environ['MASTER_ADDR'] = '127.0.0.1' #端口可以是任意空闲端口 os.environ['MASTER_PORT'] = '29500' #通信模块初始化 #进程会阻塞在该函数,直到确定所有进程都可以通信。 dist.init_process_group('gloo', rank=rank, world_size=size)#初始化分布式环境,第一个参数指定的是通信后端 print(\"world size:{}. I'm rank {}.\".format(size,rank)) if __name__ == \"__main__\": #world size的数量要与开启的进程数量相等,否则会出错 world_size = 4 mp.set_start_method(\"spawn\") #创建进程对象 #target为该进程要运行的函数,args为函数的输入参数 p0 = mp.Process(target=run, args=(0, world_size)) p1 = mp.Process(target=run, args=(1, world_size)) p2 = mp.Process(target=run, args=(2, world_size)) p3 = mp.Process(target=run, args=(3, world_size)) #启动进程 p0.start() p1.start() p2.start() p3.start() #等待进程结束 p0.join() p1.join() p2.join() p3.join() 参考资料 PyTorch单机多卡分布式训练教程及代码示例 "},"chapter2/单机分布式训练MINIST.html":{"url":"chapter2/单机分布式训练MINIST.html","title":"2.1 单机分布式训练神经网络案例","keywords":"","body":"单机分布式训练MNIST(CPU) 1. All_Reduce 使用使用torch.distributed.all_reduce() 代码示例 # multiprocess_training.py # 运行命令:python multiprocess_training.py import os import torch import torch.distributed as dist import torch.multiprocessing as mp import torch.nn as nn import torchvision import torchvision.transforms as transforms # 用于平均梯度的函数 def average_gradients(model): size = float(dist.get_world_size()) for param in model.parameters(): dist.all_reduce(param.grad.data, op=dist.ReduceOp.SUM) param.grad.data /= size # 模型 class ConvNet(nn.Module): def __init__(self, num_classes=10): super(ConvNet, self).__init__() self.layer1 = nn.Sequential( nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2)) self.layer2 = nn.Sequential( nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2)) self.fc = nn.Linear(7 * 7 * 32, num_classes) def forward(self, x): out = self.layer1(x) out = self.layer2(out) out = out.reshape(out.size(0), -1) out = self.fc(out) return out def accuracy(outputs, labels): _, preds = torch.max(outputs, 1) # taking the highest value of prediction. correct_number = torch.sum(preds == labels.data) return (correct_number / len(preds)).item() def run(rank, size): # MASTER_ADDR和MASTER_PORT是通信模块初始化需要的两个环境变量。 # 由于是在单机上,所以用localhost的ip就可以了。 os.environ['MASTER_ADDR'] = '127.0.0.1' # 端口可以是任意空闲端口 os.environ['MASTER_PORT'] = '29500' dist.init_process_group('gloo', rank=rank, world_size=size) # 1.数据集预处理 train_dataset = torchvision.datasets.MNIST(root='../data', train=True, transform=transforms.ToTensor(), download=True) training_loader = torch.utils.data.DataLoader(train_dataset, batch_size=100, shuffle=True) # 2.搭建模型 # device = torch.device(\"cuda:{}\".format(rank)) device = torch.device(\"cpu\") print(device) torch.manual_seed(0) model = ConvNet().to(device) torch.manual_seed(rank) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # fine tuned the lr # 3.开始训练 epochs = 15 batch_num = len(training_loader) running_loss_history = [] for e in range(epochs): for i, (inputs, labels) in enumerate(training_loader): inputs = inputs.to(device) labels = labels.to(device) # 前向传播 outputs = model(inputs) loss = criterion(outputs, labels) optimizer.zero_grad() # 反传 loss.backward() # 记录loss running_loss_history.append(loss.item()) # 参数更新前需要Allreduce梯度。 average_gradients(model) # 参数更新 optimizer.step() if (i + 1) % 50 == 0 and rank == 0: print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f},acc:{:.2f}'.format(e + 1, epochs, i + 1, batch_num, loss.item(), accuracy(outputs, labels))) if __name__ == \"__main__\": world_size = 4 mp.set_start_method(\"spawn\") # 创建进程对象 # target为该进程要运行的函数,args为target函数的输入参数 p0 = mp.Process(target=run, args=(0, world_size)) p1 = mp.Process(target=run, args=(1, world_size)) p2 = mp.Process(target=run, args=(2, world_size)) p3 = mp.Process(target=run, args=(3, world_size)) # 启动进程 p0.start() p1.start() p2.start() p3.start() # 当前进程会阻塞在join函数,直到相应进程结束。 p0.join() p1.join() p2.join() p3.join() 训练效果 2 . 主从式架构 使用torch.distributed.reduce() 代码示例 # multiprocess_training.py # 运行命令:python multiprocess_training.py import os import torch import torch.distributed as dist import torch.multiprocessing as mp import torch.nn as nn import torchvision import torchvision.transforms as transforms # torch.distributed.reduce def average_gradients(model): size = float(dist.get_world_size()) for param in model.parameters(): dist.reduce(param.grad.data,0, op=dist.ReduceOp.SUM) if(dist.get_rank()==0): param.grad.data/=size dist.broadcast(param.grad.data,src=0) # 模型 class ConvNet(nn.Module): def __init__(self, num_classes=10): super(ConvNet, self).__init__() self.layer1 = nn.Sequential( nn.Conv2d(1, 16, kernel_size=5, stride=1, padding=2), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2)) self.layer2 = nn.Sequential( nn.Conv2d(16, 32, kernel_size=5, stride=1, padding=2), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2)) self.fc = nn.Linear(7 * 7 * 32, num_classes) def forward(self, x): out = self.layer1(x) out = self.layer2(out) out = out.reshape(out.size(0), -1) out = self.fc(out) return out def accuracy(outputs, labels): _, preds = torch.max(outputs, 1) # taking the highest value of prediction. correct_number = torch.sum(preds == labels.data) return (correct_number / len(preds)).item() def run(rank, size): # MASTER_ADDR和MASTER_PORT是通信模块初始化需要的两个环境变量。 # 由于是在单机上,所以用localhost的ip就可以了。 os.environ['MASTER_ADDR'] = '127.0.0.1' # 端口可以是任意空闲端口 os.environ['MASTER_PORT'] = '29500' dist.init_process_group('gloo', rank=rank, world_size=size) # 1.数据集预处理 train_dataset = torchvision.datasets.MNIST(root='../data', train=True, transform=transforms.ToTensor(), download=True) training_loader = torch.utils.data.DataLoader(train_dataset, batch_size=100, shuffle=True) # 2.搭建模型 # device = torch.device(\"cuda:{}\".format(rank)) device = torch.device(\"cpu\") print(device) torch.manual_seed(0) model = ConvNet().to(device) torch.manual_seed(rank) criterion = nn.CrossEntropyLoss() optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9) # fine tuned the lr # 3.开始训练 epochs = 15 batch_num = len(training_loader) running_loss_history = [] for e in range(epochs): for i, (inputs, labels) in enumerate(training_loader): inputs = inputs.to(device) labels = labels.to(device) # 前向传播 outputs = model(inputs) loss = criterion(outputs, labels) optimizer.zero_grad() # 反传 loss.backward() # 记录loss running_loss_history.append(loss.item()) # 参数更新前需要Allreduce梯度。 average_gradients(model) # 参数更新 optimizer.step() if (i + 1) % 50 == 0 and rank == 0: print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f},acc:{:.2f}'.format(e + 1, epochs, i + 1, batch_num, loss.item(), accuracy(outputs, labels))) if __name__ == \"__main__\": world_size = 4 mp.set_start_method(\"spawn\") # 创建进程对象 # target为该进程要运行的函数,args为target函数的输入参数 p0 = mp.Process(target=run, args=(0, world_size)) p1 = mp.Process(target=run, args=(1, world_size)) p2 = mp.Process(target=run, args=(2, world_size)) p3 = mp.Process(target=run, args=(3, world_size)) # 启动进程 p0.start() p1.start() p2.start() p3.start() # 当前进程会阻塞在join函数,直到相应进程结束。 p0.join() p1.join() p2.join() p3.join() 训练效果 "},"chapter2/PyTorch-ps.html":{"url":"chapter2/PyTorch-ps.html","title":"2.2 PyTorch Parameters Server","keywords":"","body":"PyTorch-Parameters Server 仓库 代码 import argparse import os import threading import time import torch from torch import optim import torch.nn as nn from torch.utils.data import DataLoader import torch.distributed.rpc as rpc from torchvision import transforms, datasets, models model_dict = {'resnet18': models.resnet18, 'resnet50': models.resnet50, 'vgg16': models.vgg16, 'alexnet': models.alexnet, 'googlenet': models.googlenet, 'inception': models.inception_v3, 'densenet121': models.densenet121, 'mobilenet': models.mobilenet_v2} class ParameterServer(object): \"\"\"\" The parameter server (PS) updates model parameters with gradients from the workers and sends the updated parameters back to the workers. \"\"\" def __init__(self, model, num_workers, lr): self.lock = threading.Lock() self.future_model = torch.futures.Future() self.num_workers = num_workers # initialize model parameters assert model in model_dict.keys(), \\ f'model {model} is not in the model list: {list(model_dict.keys())}' self.model = model_dict[model](num_classes=10) # zero gradients for p in self.model.parameters(): p.grad = torch.zeros_like(p) self.optimizer = optim.SGD(self.model.parameters(), lr=lr, momentum=0.9) def get_model(self): return self.model @staticmethod @rpc.functions.async_execution def update_and_fetch_model(ps_rref, grads, worker_rank): self = ps_rref.local_value() with self.lock: print(f'PS updates parameters based on gradients from worker{worker_rank}') # update model parameters for p, g in zip(self.model.parameters(), grads): p.grad = g self.optimizer.step() self.optimizer.zero_grad() fut = self.future_model fut.set_result(self.model) self.future_model = torch.futures.Future() return fut def run_worker(ps_rref, rank, data_dir, batch_size, num_epochs): \"\"\" A worker pulls model parameters from the PS, computes gradients on a mini-batch from its data partition, and pushes the gradients to the PS. \"\"\" # prepare dataset normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) transform = transforms.Compose( [transforms.RandomResizedCrop(224), transforms.RandomHorizontalFlip(), transforms.ToTensor(), normalize]) train_dataset = datasets.ImageFolder(root=data_dir, transform=transform) train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True) # set device device_id = rank - 1 # device_id = 0 device = torch.device(f\"cuda:{device_id}\" if torch.cuda.is_available() else \"cpu\") criterion = nn.CrossEntropyLoss() # get initial model from the PS m = ps_rref.rpc_sync().get_model().to(device) print(f'worker{rank} starts training') tt0 = time.time() for i in range(num_epochs): for batch_idx, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) output = m(data) loss = criterion(output, target) loss.backward() print(\"worker{:d} | Epoch:{:3d} | Batch: {:3d} | Loss: {:6.2f}\" .format(rank, (i + 1), (batch_idx + 1), loss.item())) # send gradients to the PS and fetch updated model parameters m = rpc.rpc_sync(to=ps_rref.owner(), func=ParameterServer.update_and_fetch_model, args=(ps_rref, [p.grad for p in m.cpu().parameters()], rank) ).to(device) tt1 = time.time() print(\"Time: {:.2f} seconds\".format((tt1 - tt0))) def main(): parser = argparse.ArgumentParser(description=\"Train models on Imagenette under ASGD\") parser.add_argument(\"--model\", type=str, default=\"resnet18\", help=\"The job's name.\") parser.add_argument(\"--rank\", type=int, default=1, help=\"Global rank of this process.\") parser.add_argument(\"--world_size\", type=int, default=3, help=\"Total number of workers.\") parser.add_argument(\"--data_dir\", type=str, default=\"./imagenette2/val\", help=\"The location of dataset.\") parser.add_argument(\"--master_addr\", type=str, default=\"localhost\", help=\"Address of master.\") parser.add_argument(\"--master_port\", type=str, default=\"29600\", help=\"Port that master is listening on.\") parser.add_argument(\"--batch_size\", type=int, default=64, help=\"Batch size of each worker during training.\") parser.add_argument(\"--lr\", type=float, default=0.01, help=\"Learning rate.\") parser.add_argument(\"--num_epochs\", type=int, default=1, help=\"Number of epochs.\") args = parser.parse_args() os.environ['MASTER_ADDR'] = args.master_addr os.environ['MASTER_PORT'] = args.master_port options = rpc.TensorPipeRpcBackendOptions(num_worker_threads=16, rpc_timeout=0) if args.rank == 0: \"\"\" initialize PS and run workers \"\"\" print(f\"PS{args.rank} initializing\") rpc.init_rpc(f\"PS{args.rank}\", rank=args.rank, world_size=args.world_size, rpc_backend_options=options) print(f\"PS{args.rank} initialized\") ps_rref = rpc.RRef(ParameterServer(args.model, args.world_size, args.lr)) futs = [] for r in range(1, args.world_size): worker = f'worker{r}' futs.append(rpc.rpc_async(to=worker, func=run_worker, args=(ps_rref, r, args.data_dir, args.batch_size, args.num_epochs))) torch.futures.wait_all(futs) print(f\"Finish training\") else: \"\"\" initialize workers \"\"\" print(f\"worker{args.rank} initializing\") rpc.init_rpc(f\"worker{args.rank}\", rank=args.rank, world_size=args.world_size, rpc_backend_options=options) print(f\"worker{args.rank} initialized\") rpc.shutdown() if __name__ == \"__main__\": main() 运行结果 PS端 worker1端 worker2端 链接 https://www.w3cschool.cn/pytorch/pytorch-t8g53bt3.html "},"chapter2/Flower联邦学习.html":{"url":"chapter2/Flower联邦学习.html","title":"2.3 Flower联邦学习","keywords":"","body":"Flower联邦学习 一、介绍 GitHub仓库 Flower (flwr) is a framework for building federated learning systems. The design of Flower is based on a few guiding principles: Customizable: Federated learning systems vary wildly from one use case to another. Flower allows for a wide range of different configurations depending on the needs of each individual use case. Extendable: Flower originated from a research project at the University of Oxford, so it was built with AI research in mind. Many components can be extended and overridden to build new state-of-the-art systems. Framework-agnostic: Different machine learning frameworks have different strengths. Flower can be used with any machine learning framework, for example, PyTorch, TensorFlow, Hugging Face Transformers, PyTorch Lightning, MXNet, scikit-learn, JAX, TFLite, fastai, Pandas for federated analytics, or even raw NumPy for users who enjoy computing gradients by hand. Understandable: Flower is written with maintainability in mind. The community is encouraged to both read and contribute to the codebase. Meet the Flower community on flower.dev! 二、支持的加密算法 不支持安全多方计算和同态加密,目前只有差分隐私 差分隐私 federated learning with differential privacy using flower and Opacus (Pytorch models). Differentially Private Federated Learning with Flower and Opacus https://github.com/adap/flower/tree/main/examples/opacus https://github.com/adap/flower/tree/main/examples/dp-sgd-mnist https://github.com/shakibyzn/fl_with_dp_using_flwr https://github.com/Chris-george-anil/Federated-Learning-Internship 三、在 CIFAR10上训练一个卷积神经网络使用Flower和PyTorch 教程 3.1 安装相关依赖 $ pip install flwr $ pip install torch torchvision 3.2 client.py import warnings from collections import OrderedDict import flwr as fl import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader from torchvision.datasets import CIFAR10 from torchvision.transforms import Compose, Normalize, ToTensor from tqdm import tqdm # ############################################################################# # 1. Regular PyTorch pipeline: nn.Module, train, test, and DataLoader # ############################################################################# warnings.filterwarnings(\"ignore\", category=UserWarning) DEVICE = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\") class Net(nn.Module): \"\"\"Model (simple CNN adapted from 'PyTorch: A 60 Minute Blitz')\"\"\" def __init__(self) -> None: super(Net, self).__init__() self.conv1 = nn.Conv2d(3, 6, 5) self.pool = nn.MaxPool2d(2, 2) self.conv2 = nn.Conv2d(6, 16, 5) self.fc1 = nn.Linear(16 * 5 * 5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) def forward(self, x: torch.Tensor) -> torch.Tensor: x = self.pool(F.relu(self.conv1(x))) x = self.pool(F.relu(self.conv2(x))) x = x.view(-1, 16 * 5 * 5) x = F.relu(self.fc1(x)) x = F.relu(self.fc2(x)) return self.fc3(x) def train(net, trainloader, epochs): \"\"\"Train the model on the training set.\"\"\" criterion = torch.nn.CrossEntropyLoss() optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9) for _ in range(epochs): for images, labels in tqdm(trainloader): optimizer.zero_grad() criterion(net(images.to(DEVICE)), labels.to(DEVICE)).backward() optimizer.step() def test(net, testloader): \"\"\"Validate the model on the test set.\"\"\" criterion = torch.nn.CrossEntropyLoss() correct, loss = 0, 0.0 with torch.no_grad(): for images, labels in tqdm(testloader): outputs = net(images.to(DEVICE)) labels = labels.to(DEVICE) loss += criterion(outputs, labels).item() correct += (torch.max(outputs.data, 1)[1] == labels).sum().item() accuracy = correct / len(testloader.dataset) return loss, accuracy def load_data(): \"\"\"Load CIFAR-10 (training and test set).\"\"\" trf = Compose([ToTensor(), Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]) trainset = CIFAR10(\"./data\", train=True, download=True, transform=trf) testset = CIFAR10(\"./data\", train=False, download=True, transform=trf) return DataLoader(trainset, batch_size=32, shuffle=True), DataLoader(testset) # ############################################################################# # 2. Federation of the pipeline with Flower # ############################################################################# # Load model and data (simple CNN, CIFAR-10) net = Net().to(DEVICE) trainloader, testloader = load_data() # Define Flower client class FlowerClient(fl.client.NumPyClient): def get_parameters(self, config): return [val.cpu().numpy() for _, val in net.state_dict().items()] def set_parameters(self, parameters): params_dict = zip(net.state_dict().keys(), parameters) state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict}) net.load_state_dict(state_dict, strict=True) def fit(self, parameters, config): self.set_parameters(parameters) train(net, trainloader, epochs=1) return self.get_parameters(config={}), len(trainloader.dataset), {} def evaluate(self, parameters, config): self.set_parameters(parameters) loss, accuracy = test(net, testloader) return loss, len(testloader.dataset), {\"accuracy\": accuracy} # Start Flower client fl.client.start_numpy_client( server_address=\"127.0.0.1:8080\", client=FlowerClient(), ) 3.3 server.py from typing import List, Tuple import flwr as fl from flwr.common import Metrics # Define metric aggregation function def weighted_average(metrics: List[Tuple[int, Metrics]]) -> Metrics: # Multiply accuracy of each client by number of examples used accuracies = [num_examples * m[\"accuracy\"] for num_examples, m in metrics] examples = [num_examples for num_examples, _ in metrics] # Aggregate and return custom metric (weighted average) return {\"accuracy\": sum(accuracies) / sum(examples)} # Define strategy strategy = fl.server.strategy.FedAvg(evaluate_metrics_aggregation_fn=weighted_average) # Start Flower server fl.server.start_server( server_address=\"0.0.0.0:8080\", config=fl.server.ServerConfig(num_rounds=3), strategy=strategy, ) 3.4 启动 python3 server.py python3 client.py python3 client.py 四、扩展 4.1 Strategies 文档 Flower支持自定义strategies,满足联邦学习的更多需要!! "},"chapter3/spark的安装.html":{"url":"chapter3/spark的安装.html","title":"3.1 spark的安装","keywords":"","body":"Spark的安装 一、本地安装 1. 安装Hadoop Hadoop安装教程_单机/伪分布式配置_Hadoop2.6.0(2.7.1)/Ubuntu14.04(16.04) 2. 安装Spark Spark2.1.0+入门:Spark的安装和使用(Python版) 二、Anaconda安装 1. 安装Java环境 在Linux命令行界面中,执行如下Shell命令(注意:当前登录用户名是hadoop): cd /usr/lib sudo mkdir jvm #创建/usr/lib/jvm目录用来存放JDK文件 cd ~ #进入hadoop用户的主目录cd Downloads #注意区分大小写字母,刚才已经通过FTP软件把JDK安装包jdk-8u162-linux-x64.tar.gz上传到该目录下 sudo tar -zxvf ./jdk-8u162-linux-x64.tar.gz -C /usr/lib/jvm #把JDK文件解压到/usr/lib/jvm目录下 上面使用了解压缩命令tar,如果对Linux命令不熟悉,可以参考常用的Linux命令用法。 JDK文件解压缩以后,可以执行如下命令到/usr/lib/jvm目录查看一下: cd /usr/lib/jvmls 可以看到,在/usr/lib/jvm目录下有个jdk1.8.0_162目录。 下面继续执行如下命令,设置环境变量: cd ~vim ~/.bashrc 上面命令使用vim编辑器(查看vim编辑器使用方法)打开了hadoop这个用户的环境变量配置文件,请在这个文件的开头位置,添加如下几行内容: export JAVA_HOME=/usr/lib/jvm/jdk1.8.0_162 export JRE_HOME=${JAVA_HOME}/jre export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib export PATH=${JAVA_HOME}/bin:$PATH 保存.bashrc文件并退出vim编辑器。然后,继续执行如下命令让.bashrc文件的配置立即生效: source ~/.bashrc 这时,可以使用如下命令查看是否安装成功: java -version 如果能够在屏幕上返回如下信息,则说明安装成功: hadoop@ubuntu:~$ java -version java version \"1.8.0_162\" Java(TM) SE Runtime Environment (build 1.8.0_162-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode) 2. 安装anaconda Anaconda清华大学镜像下载:https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 3. 安装spark使用conda conda create -n pyspark_env conda activate pyspark_env 激活环境后,使用以下命令安装 pypark、您选择的 python 版本,以及您希望在与 pypark 相同的会话中使用的其他包(也可以分几个步骤进行安装)。 conda install -c conda-forge pyspark # can also add \"python=3.8 some_package [etc.]\" here 三、Docker容器 docker pull bitnami/spark:3 "},"chapter3/spark单机版机器学习示例.html":{"url":"chapter3/spark单机版机器学习示例.html","title":"3.2 spark单机版机器学习示例","keywords":"","body":"Spark单机版机器学习示例 示例 代码 sparkML examples 参考链接 用 PySpark ML 构建机器学习模型 "},"chapter3/spark集群的搭建.html":{"url":"chapter3/spark集群的搭建.html","title":"3.3 spark集群的搭建","keywords":"","body":"Spark集群的搭建 一、使用三台电脑来搭建一个小型分布式集群环境安装 参考方法:Spark 2.0分布式集群环境搭建(Python版) 二、使用Docker来部署spark集群 参考方法:使用 Docker 快速部署 Spark + Hadoop 大数据集群 2.1 Spark Standalone集群 docker.io/bitnami/spark:3 Dockerfile docker-compose.yml文件 version: '2' services: spark: image: docker.io/bitnami/spark:3 hostname: master environment: - SPARK_MODE=master - SPARK_RPC_AUTHENTICATION_ENABLED=no - SPARK_RPC_ENCRYPTION_ENABLED=no - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no - SPARK_SSL_ENABLED=no volumes: - ~/docker/spark/share:/opt/share ports: - '8080:8080' - '4040:4040' spark-worker-1: image: docker.io/bitnami/spark:3 hostname: worker1 environment: - SPARK_MODE=worker - SPARK_MASTER_URL=spark://master:7077 - SPARK_WORKER_MEMORY=1G - SPARK_WORKER_CORES=1 - SPARK_RPC_AUTHENTICATION_ENABLED=no - SPARK_RPC_ENCRYPTION_ENABLED=no - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no - SPARK_SSL_ENABLED=no volumes: - ~/docker/spark/share:/opt/share ports: - '8081:8081' spark-worker-2: image: docker.io/bitnami/spark:3 hostname: worker2 environment: - SPARK_MODE=worker - SPARK_MASTER_URL=spark://master:7077 - SPARK_WORKER_MEMORY=1G - SPARK_WORKER_CORES=1 - SPARK_RPC_AUTHENTICATION_ENABLED=no - SPARK_RPC_ENCRYPTION_ENABLED=no - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no - SPARK_SSL_ENABLED=no volumes: - ~/docker/spark/share:/opt/share ports: - '8082:8081' 2.2 S park+Hadoop集群 Hadoop 由分布式文件系统 HDFS、分布式计算框架 MapReduce 和资源管理框架 YARN 组成。MapReduce 是面向磁盘的,运行效率受到磁盘读写性能的约束,Spark 延续了 MapReduce 编程模型的设计思路,提出了面向内存的分布式计算框架,性能较之 MapReduce 有了 10~100 倍的提升。与此同时,Spark 框架还对 HDFS 做了很好的支持,并支持运行在 YARN 集群上。 由于 Spark 使用了 Hadoop 的客户端依赖库,所以 Spark 安装包会指定依赖的 Hadoop 特定版本,如 spark-3.1.2-bin-hadoop3.2.tgz。而 bitnami/spark 镜像中只包含 Hadoop 客户端,并不包含服务器端。因此,如果需要使用 HDFS 和 YARN 功能,还需要部署 Hadoop 集群。 将 Hadoop 部署在 Spark 集群上,可以避免不必要的网络通信,并且面向磁盘的 HDFS 与面向内存的 Spark 天生互补。因此,考虑在 bitnami/spark 镜像基础上构建安装有 Hadoop 的新镜像。 docker-compose.yaml version: '2' services: spark: image: s1mplecc/spark-hadoop:3 hostname: master environment: - SPARK_MODE=master - SPARK_RPC_AUTHENTICATION_ENABLED=no - SPARK_RPC_ENCRYPTION_ENABLED=no - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no - SPARK_SSL_ENABLED=no volumes: - ~/docker/spark/share:/opt/share ports: - '8080:8080' - '4040:4040' - '8088:8088' - '8042:8042' - '9870:9870' - '19888:19888' spark-worker-1: image: s1mplecc/spark-hadoop:3 hostname: worker1 environment: - SPARK_MODE=worker - SPARK_MASTER_URL=spark://master:7077 - SPARK_WORKER_MEMORY=1G - SPARK_WORKER_CORES=1 - SPARK_RPC_AUTHENTICATION_ENABLED=no - SPARK_RPC_ENCRYPTION_ENABLED=no - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no - SPARK_SSL_ENABLED=no volumes: - ~/docker/spark/share:/opt/share ports: - '8081:8081' spark-worker-2: image: s1mplecc/spark-hadoop:3 hostname: worker2 environment: - SPARK_MODE=worker - SPARK_MASTER_URL=spark://master:7077 - SPARK_WORKER_MEMORY=1G - SPARK_WORKER_CORES=1 - SPARK_RPC_AUTHENTICATION_ENABLED=no - SPARK_RPC_ENCRYPTION_ENABLED=no - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no - SPARK_SSL_ENABLED=no volumes: - ~/docker/spark/share:/opt/share ports: - '8082:8081' 三、问题: spark能否结合PyTorch进行分布式学习 "},"chapter3/集群机器学习任务.html":{"url":"chapter3/集群机器学习任务.html","title":"3.4 集群机器学习任务","keywords":"","body":"集群上执行机器学习任务 一、环境准备 安装numpy、pandas 二、提交 Python 应用 2.1. 代码 from pyspark.sql import SparkSession spark = SparkSession.builder.appName('ml-diabetes').getOrCreate() df = spark.read.csv('diabetes.csv', header = True, inferSchema = True) df.printSchema() import pandas as pd pd.DataFrame(df.take(5), columns=df.columns).transpose() df.toPandas() df.groupby('Outcome').count().toPandas() numeric_features = [t[0] for t in df.dtypes if t[1] == 'int'] df.select(numeric_features)\\ .describe()\\ .toPandas()\\ .transpose() from pyspark.sql.functions import isnull, when, count, col df.select([count(when(isnull(c), c)).alias(c) for c in df.columns]).show() dataset = df.drop('SkinThickness') dataset = df.drop('Insulin') dataset = df.drop('DiabetesPedigreeFunction') dataset = df.drop('Pregnancies') dataset.show() # 用VectorAssembler合并所有特性 required_features = ['Glucose', 'BloodPressure', 'BMI', 'Age'] from pyspark.ml.feature import VectorAssembler assembler = VectorAssembler( inputCols=required_features, outputCol='features') transformed_data = assembler.transform(dataset) transformed_data.show() (training_data, test_data) = transformed_data.randomSplit([0.8,0.2], seed =2020) print(\"训练数据集总数: \" + str(training_data.count())) print(\"测试数据集总数: \" + str(test_data.count())) from pyspark.ml.classification import RandomForestClassifier rf = RandomForestClassifier(labelCol='Outcome', featuresCol='features', maxDepth=5) model = rf.fit(training_data) rf_predictions = model.transform(test_data) from pyspark.ml.evaluation import MulticlassClassificationEvaluator multi_evaluator = MulticlassClassificationEvaluator( labelCol = 'Outcome', metricName = 'accuracy') print('Random Forest classifier Accuracy:', multi_evaluator.evaluate(rf_predictions)) model.write().overwrite().save(\"./spark-random-forest-model\") 2.2 使用 spark-submit 命令提交 Python 脚本 指定 --master 参数提交到集群上。如果未指定该参数,则默认以本地模式运行。 spark-submit --master spark://master:7077 /opt/share/ml.py 2.3. 运行结果 保存模型 "},"chapter3/Spark运行原理.html":{"url":"chapter3/Spark运行原理.html","title":"3.5 Spark运行原理","keywords":"","body":"Spark运行原理 一、spark相关概念 1.1 Application:Spark应用程序 指的是用户编写的Spark应用程序,包含了Driver功能代码和分布在集群中多个节点上运行的Executor代码。 Spark应用程序,由一个或多个作业JOB组成,如下图所示。 1.2 Driver:驱动程序 Spark中的Driver即运行上述Application的Main()函数并且创建SparkContext,其中创建SparkContext的目的是为了准备Spark应用程序的运行环境。在Spark中由SparkContext负责和ClusterManager通信,进行资源的申请、任务的分配和监控等;当Executor部分运行完毕后,Driver负责将SparkContext关闭。通常SparkContext代表Driver,如下图所示。 1.3 Cluster Manager:资源管理器 指的是在集群上获取资源的外部服务,常用的有:Standalone,Spark原生的资源管理器,由Master负责资源的分配;Haddop Yarn,由Yarn中的ResearchManager负责资源的分配;Messos,由Messos中的Messos Master负责资源管理。 1.4 Executor:执行器 Application运行在Worker节点上的一个进程,该进程负责运行Task,并且负责将数据存在内存或者磁盘上,每个Application都有各自独立的一批Executor,如下图所示。 1.5 Worker:计算节点 集群中任何可以运行Application代码的节点,类似于Yarn中的NodeManager节点。在Standalone模式中指的就是通过Slave文件配置的Worker节点,在Spark on Yarn模式中指的就是NodeManager节点,在Spark on Messos模式中指的就是Messos Slave节点,如下图所示。 二、Spark运行基本流程 当执行一个应用时,Driver会向集群管理器申请资源,启动Executor,并向Executor发送应用程序代码和文件,然后在Executor上执行任务,运行结束后,执行结果会返回给Driver,或者写到HDFS或者其他数据库中。 (1)首先为应用构建起基本的运行环境,即由Driver创建一个SparkContext,进行资源的申请、任务的分配和监控 (2)资源管理器为Executor分配资源,并启动Executor进程 (3)SparkContext根据RDD的依赖关系构建DAG图,DAG图提交给DAGScheduler解析成Stage,然后把一个个TaskSet提交给底层调度器TaskScheduler处理;Executor向SparkContext申请Task,Task Scheduler将Task发放给Executor运行,并提供应用程序代码 (4)Task在Executor上运行,把执行结果反馈给TaskScheduler,然后反馈给DAGScheduler,运行完毕后写入数据并释放所有资源 三、RDD运行原理 3.1 RDD设计背景 许多迭代式算法(比如机器学习、图算法等)和交互式数据挖掘工具,共同之处是,不同计算阶段之间会重用中间结果 目前的MapReduce框架都是把中间结果写入到稳定存储(比如磁盘)中,带来了大量的数据复制、磁盘IO和序列化开销 RDD就是为了满足这种需求而出现的,它提供了一个抽象的数据架构,我们不必担心底层数据的分布式特性,只需将具体的应用逻辑表达为一系列转换处理,不同RDD之间的转换操作形成依赖关系,可以实现管道化,避免中间数据存储 3.2 RDD(Resillient Distributed Dataset)概念 一个RDD就是一个分布式对象集合,本质上是一个只读的分区记录集合,每个RDD可分成多个分区,每个分区就是一个数据集片段,并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算 RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,不能直接修改,只能基于稳定的物理存储中的数据集创建RDD,或者通过在其他RDD上执行确定的转换操作(如map、join和group by)而创建得到新的RDD RDD提供了一组丰富的操作以支持常见的数据运算,分为“动作”(Action)和“转换”(Transformation)两种类型 RDD提供的转换接口都非常简单,都是类似map、filter、groupBy、join等粗粒度的数据转换操作,而不是针对某个数据项的细粒度修改(不适合网页爬虫) 表面上RDD的功能很受限、不够强大,实际上RDD已经被实践证明可以高效地表达许多框架的编程模型(比如MapReduce、SQL、Pregel) Spark提供了RDD的API,程序员可以通过调用API实现对RDD的各种操作 3.3 RDD典型执行过程 RDD读入外部数据源进行创建 RDD经过一系列的转换(Transformation)操作,每一次都会产生不同的RDD,供给下一个转换操作使用 最后一个RDD经过“动作”操作进行转换,并输出到外部数据源 这一系列处理称为一个Lineage(血缘关系),即DAG拓扑排序的结果 优点:惰性调用、管道化、避免同步等待、不需要保存中间结果、每次操作变得简单 3.3 RDD特性 Spark采用RDD以后能够实现高效计算的原因主要在于: (1)高效的容错性 现有容错机制:数据复制或者记录日志 RDD:血缘关系、重新计算丢失分区、无需回滚系统、重算过程在不同节点之间并行、只记录粗粒度的操作 (2)中间结果持久化到内存,数据在内存中的多个RDD操作之间进行传递,避免了不必要的读写磁盘开销 (3)存放的数据可以是Java对象,避免了不必要的对象序列化和反序列化 3.4 DAG图阶段划分 3.4.1 窄依赖和宽依赖 Shuffle操作 窄依赖表现为一个父RDD的分区对应于一个子RDD的分区或多个父RDD的分区对应于一个子RDD的分区,如图所示: 宽依赖则表现为存在一个父RDD的一个分区对应一个子RDD的多个分区 3.4.3 阶段的划分 Spark根据DAG图中的RDD依赖关系,把一个作业分成多个阶段。对于宽依赖和窄依赖而言,窄依赖对于作业的优化很有利。只有窄依赖可以实现流水线优化,宽依赖包含Shuffle过程,无法实现流水线方式处理。举例如图: Spark通过分析各个RDD的依赖关系生成了DAG,再通过分析各个RDD中的分区之间的依赖关系来决定如何划分Stage,具体划分方法是: 在DAG中进行反向解析,遇到宽依赖就断开 遇到窄依赖就把当前的RDD加入到Stage中 将窄依赖尽量划分在同一个Stage中,可以实现流水线计算 3.4.5 RDD运行过程 (1)创建RDD对象; (2)SparkContext负责计算RDD之间的依赖关系,构建DAG; (3)DAGScheduler负责把DAG图分解成多个Stage,每个Stage中包含了多个Task(Task是Spark中最小的任务执行单元,每个RDD的transformation操作都会被翻译成相应的task,分配到相应的executor节点上对相应的partition执行,RDD在计算的时候,每个分区都会启动一个task,RDD的分区数目决定了总的task数目。),每个Task会被TaskScheduler分发给各个WorkerNode上的Executor去执行。 3.5 RDD操作 通常,Spark RDD的常用操作有两种,分别为Transform操作和Action操作。Transform操作并不会立即执行,而是到了Action操作才会被执行。详细操作请见RDD APIs Transform操作 操作 描述 map() 参数是函数,函数应用于RDD每一个元素,返回值是新的RDD。 flatMap() 参数是函数,函数应用于RDD每一个元素,拆分元素数据,变成迭代器,返回值是新的RDD。 filter() 参数是函数,函数会过滤掉不符合条件的元素,返回值是新的RDD。 distinct() 没有参数,将RDD里的元素进行去重操作。 union() 参数是RDD,生成包含两个RDD所有元素的新RDD。 intersection() 参数是RDD,求出两个RDD的共同元素。 subtract() 参数是RDD,去掉原RDD里和参数RDD里相同的元素。 cartesian() 参数是RDD,求两个RDD的笛卡尔积。 Action操作 3.5.1 触发Shuffle的操作 会引起shuffle 的操作包括重分区操作(如repartition 和 coalesce)、ByKey操作(除计数外)(如groupByKey和reduceByKey)以及join操作(如cogroup和join) 3.6 RDD分区 RDD中的数据被存储在多个分区中。 3.6.1 RDD分区的特征 分区永远不会跨越多台机器,即同一分区中的数据始终保证在同一台机器上。 群集中的每个节点包含一个或多个分区。 分区的数目是可以设置的。 默认情况下,它等于所有执行程序节点上的核心总数。 例如。 6个工作节点,每个具有4个核心,RDD将被划分为24个分区。 3.6.2 RDD分区与任务执行的关系 [!NOTE|style:flat] 在Map阶段partition数目保持不变。 在Reduce阶段,RDD的聚合会触发shuffle操作,聚合后的RDD的partition数目跟具体操作有关,例如repartition操作会聚合成指定分区数,还有一些算子是可配置的。 RDD在计算的时候,每个分区都会启动一个task,RDD的分区数目决定了总的task数目。 申请的Executor数和Executor的CPU核数,决定了你同一时刻可以并行执行的task数量。 这里我们举个例子来加深对RDD分区数量与task执行的关系的理解 比如的RDD有100个分区,那么计算的时候就会生成100个task,你的资源配置为10个计算节点,每个两2个核,同一时刻可以并行的task数目为20,计算这个RDD就需要5个轮次。如果计算资源不变,你有101个task的话,就需要6个轮次,在最后一轮中,只有一个task在执行,其余核都在空转。 [!NOTE|style:flat] partition数量太少会造成资源利用不够充分。 例如,在资源不变的情况,你的RDD只有10个分区,那么同一时刻只有10个task运行,其余10个核将空转。 通常在spark调优中,可以增大RDD分区数目来增大任务并行度。 [!NOTE|style:flat] 但是partition数量太多则会造成task过多,task的传输/序列化开销增大,也可能会造成输出过多的(小)文件 3.6.3 RDD的分区器(Partitioner) Spark中提供两种分区器: Spark包含两种数据分区方式:HashPartitioner(哈希分区)和RangePartitioner(范围分区)。一般而言,对于初始读入的数据是不具有任何的数据分区方式的。数据分区方式只作用于形式的数据。因此,当一个Job包含Shuffle操作类型的算子时,如groupByKey,reduceByKey etc,此时就会使用数据分区方式来对数据进行分区,即确定某一个Key对应的键值对数据分配到哪一个Partition中。在Spark Shuffle阶段中,共分为Shuffle Write阶段和Shuffle Read阶段,其中在Shuffle Write阶段中,Shuffle Map Task对数据进行处理产生中间数据,然后再根据数据分区方式对中间数据进行分区。最终Shffle Read阶段中的Shuffle Read Task会拉取Shuffle Write阶段中产生的并已经分好区的中间数据。图2中描述了Shuffle阶段与Partition关系。下面则分别介绍Spark中存在的两种数据分区方式。 HashPartitioner(哈希分区) HashPartitioner采用哈希的方式对键值对数据进行分区。其数据分区规则为 partitionId = Key.hashCode % numPartitions,其中partitionId代表该Key对应的键值对数据应当分配到的Partition标识,Key.hashCode表示该Key的哈希值,numPartitions表示包含的Partition个数。图3简单描述了HashPartitioner的数据分区过程。 RangePartitioner(范围分区) Spark引入RangePartitioner的目的是为了解决HashPartitioner所带来的分区倾斜问题,也即分区中包含的数据量不均衡问题。HashPartitioner采用哈希的方式将同一类型的Key分配到同一个Partition中,因此当某一或某几种类型数据量较多时,就会造成若干Partition中包含的数据过大问题,而在Job执行过程中,一个Partition对应一个Task,此时就会使得某几个Task运行过慢。RangePartitioner基于抽样的思想来对数据进行分区。图4简单描述了RangePartitioner的数据分区过程。 3.6.4 自定义分区(定义partitioner个数) 案例:对List里面的单词进行wordcount,并且输出按照每个单词的长度分区输出到不同文件里面 //只需要继承Partitioner,重写两个方法 class MyPartitioner(val num:Int) extends Partitioner { //这里定义partitioner个数 override def numPartitions: Int = num //这里定义分区规则 override def getPartition(key: Any): Int = { val len = key.toString.length //根据单词长度对分区个数取模 len % num } } App的使用: bject testMyPartitioner { def main(args: Array[String]): Unit = { val conf = new SparkConf().setAppName(\"test\").setMaster(\"local[*]\") val sc = new SparkContext(conf) val rdd1 = sc.parallelize(List(\"lijie hello lisi\", \"zhangsan wangwu mazi\", \"hehe haha nihaoa heihei lure hehe hello word\")) val rdd2=rdd1.flatMap(_.split(\" \")).map(x=>{ (x,1) }) //这里指定自定义分区,然后输出 val rdd3 =rdd2.sortBy(_._2).partitionBy(new MyPartitioner(4)).mapPartitions(x=>x).saveAsTextFile(\"file:///f:/out\") println(rdd2.collect().toBuffer) sc.stop() } } 四、Spark分布式逻辑回归 Logistic Regression模型的训练过程主要包含两个计算步骤:一是根据训练数据计算梯度,二是更新模型参数向量w。计算梯度(gradient)时需要读入每个样例,代入梯度公式计算,并对计算结果进行加和。由于在计算时每个样例可以独立代入公式,互相不 影响,所以我们可以采用“数据并行化”的方法,即将训练样本划分为多个部分,每个task只计算部分样例上的梯度,然后将这些梯度进行加和得到最终的梯度。在更新参数向量w时,更新操作可以在一个节点上完成,不需要并行化。 上面我们已经展开讨论了SparkLR的并行化逻辑处理流程,那么,SparkLR在实际运行时生成什么样的job和stage呢?当我们把迭代轮数设为5时,形成的job和stage如图5.4所示。可以看到在这个例子中,SparkLR一共生成了5个job,每个job只包含一个map stage。一个有趣的现象是,第1个job运行需要0.8s(800 ms),而第2个到第5个job只需要56~76ms。发生这一现象的原因是,SparkLR在第1个job运行时对训练数据(points:RDD)进行了缓存,使得后续的job只需要从内存中直接读取数据进行计算即可,这大大减小了数据加载到内存中的开销,从而加速了计算过程。 参考链接 Spark运行原理 Spark入门:DataFrame与RDD的区别 [Spark学习] Spark RDD详解 许利杰_ 方亚芬 - 大数据处理框架Apache Spark设计与实现(全彩) (2020, 电子工业出版社) "},"chapter3/Spark中ml和mllib的区别.html":{"url":"chapter3/Spark中ml和mllib的区别.html","title":"3.6 Spark中ml和mllib的区别","keywords":"","body":"Spark中ml和mllib的区别 https://www.bbsmax.com/A/QW5YqWPY5m/ ml和mllib都是Spark中的机器学习库,目前常用的机器学习功能2个库都能满足需求。 spark官方推荐使用ml, 因为ml功能更全面更灵活,未来会主要支持ml,mllib很有可能会被废弃(据说可能是在spark3.0中deprecated)。 ml主要操作的是DataFrame, 而mllib操作的是RDD,也就是说二者面向的数据集不一样。相比于mllib在RDD提供的基础操作,ml在DataFrame上的抽象级别更高,数据和操作耦合度更低。 DataFrame和RDD什么关系?DataFrame是Dataset的子集,也就是Dataset[Row], 而DataSet是对RDD的封装,对SQL之类的操作做了很多优化。 相比于mllib在RDD提供的基础操作,ml在DataFrame上的抽象级别更高,数据和操作耦合度更低。 ml中的操作可以使用pipeline, 跟sklearn一样,可以把很多操作(算法/特征提取/特征转换)以管道的形式串起来,然后让数据在这个管道中流动。大家可以脑补一下Linux管道在做任务组合时有多么方便。 ml中无论是什么模型,都提供了统一的算法操作接口,比如模型训练都是fit;不像mllib中不同模型会有各种各样的trainXXX。 mllib在spark2.0之后进入维护状态, 这个状态通常只修复BUG不增加新功能。 为什么我们还需要Data Frame 在Spark中,DataFrame是一种以RDD为基础的分布式数据集,类似于传统数据库中的二维表格。DataFrame与RDD的主要区别在于,前者带有schema元信息,即DataFrame所表示的二维表数据集的每一列都带有名称和类型。这使得Spark SQL得以洞察更多的结构信息,从而对藏于DataFrame背后的数据源以及作用于DataFrame之上的变换进行了针对性的优化,最终达到大幅提升运行时效率的目标。反观RDD,由于无从得知所存数据元素的具体内部结构,Spark Core只能在stage层面进行简单、通用的流水线优化。 "},"chapter3/Spark中Shuffle操作.html":{"url":"chapter3/Spark中Shuffle操作.html","title":"3.7 Spark中Shuffle操作","keywords":"","body":"3.7 Spark中Shuffle操作 "},"chapter3/Spark中的广播机制.html":{"url":"chapter3/Spark中的广播机制.html","title":"3.8 Spark中的广播机制","keywords":"","body":"3.8 Spark中的广播机制 "},"chapter4/PMLS安装.html":{"url":"chapter4/PMLS安装.html","title":"4.1 PMLS安装","keywords":"","body":"PMLS安装 1. 系统环境 官方文档 1.1 系统版本 文档使用的是 64-bit Ubuntu Desktop 14.04 本次安装使用的是 64-bit Ubuntu Desktop 16.04,18.04和20.04在安装过程会出现Python版本和编译错误 1.2 Python版本 Python 2.7.12 1.3 GCC版本 gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.12) 2. Obtaining PMLS The best way to download PMLS is via the git command. Install git by running sudo apt-get -y update sudo apt-get -y install git Then, run the following commands to download PMLS Bösen and Strads: git clone -b stable https://github.com/sailing-pmls/bosen.git git clone https://github.com/sailing-pmls/strads.git cd bosen git clone https://github.com/sailing-pmls/third_party.git third_party cd .. Next, for each machine that PMLS will be running on, execute the following commands to install dependencies: sudo apt-get -y update sudo apt-get -y install g++ make autoconf git libtool uuid-dev openssh-server cmake libopenmpi-dev openmpi-bin libssl-dev libnuma-dev python-dev python-numpy python-scipy python-yaml protobuf-compiler subversion libxml2-dev libxslt-dev zlibc zlib1g zlib1g-dev libbz2-1.0 libbz2-dev Warning: Some parts of PMLS require openmpi, but are incompatible with mpich2 (e.g. in the Anaconda scientific toolkit for Python). If you have both openmpi and mpich2 installed, make sure mpirun points to openmpi’s executable. 3. Compiling PMLS You’re now ready to compile PMLS. From the directory in which you started, run cd strads make cd ../bosen/third_party make cd ../../bosen cp defns.mk.template defns.mk make cd .. If you are installing PMLS to a shared filesystem, the above steps only need to be done from one machine. The first make builds Strads, and the second and third makes build Bösen and its dependencies. All commands will take between 5-30 minutes each, depending on your machine. We’ll explain how to compile and run PMLS’s built-in apps later in this manual. 4. Very important: Setting up password-less SSH authentication PMLS uses ssh (and mpirun, which invokes ssh) to coordinate tasks on different machines, even if you are only using a single machine. This requires password-less key-based authentication on all machines you are going to use (PMLS will fail if a password prompt appears). If you don’t already have an SSH key, generate one via ssh-keygen You’ll then need to add your public key to each machine, by appending your public key file ~/.ssh/id_rsa.pub to ~/.ssh/authorized_keys on each machine. If your home directory is on a shared filesystem visible to all machines, then simply run cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys If the machines do not have a shared filesystem, you need to upload your public key to each machine, and the append it as described above. Note: Password-less authentication can fail if ~/.ssh/authorized_keys does not have the correct permissions. To fix this, run chmod 600 ~/.ssh/authorized_keys. "},"chapter4/PMLS单机版机器学习.html":{"url":"chapter4/PMLS单机版机器学习.html","title":"4.2 PMLS单机版机器学习","keywords":"","body":"PMLS单机版机器学习 1. Bösen and Strads PMLS 包括两个用于编写和运行 ML 应用程序的平台: Bösen 用于数据并行执行,Strads 用于模型并行执行。每个可以即时运行的 PMLS 应用程序要么是 Bösen 应用程序,要么是 Strads 应用程序。 1.1 Bösen app 1.2 Strads app 2. Deep Neural Network 2.1 训练 进入DNN app cd bosen/app/dnn 编译 make #This will put the DNN binary in the subdirectory bin/ 创建一个模拟数据集 script/gen_data.sh 10000 360 2001 1 datasets 修改 script/run_local.py中的 app_dir app_dir = \"/home/user/bosen/app/dnn\" 使用四个线程在本地机器上执行程序 ./script/launch.py 执行结果 DNN 应用程序在后台运行(进度输出到 stdout)。应用程序终止后,你应该得到2个输出文件: datasets/weights.txt datasets/biases.txt weights.txt保存权重矩阵。顺序是: 第一层(输入层)和第二层(第一隐藏层)之间的权重矩阵,第二层和第三层之间的权重矩阵等。所有矩阵按行主要顺序保存,每一行对应一行 2.2 测试 修改script/predict.py下的app_dir app_dir = \"/home/user/bosen/app/dnn\" 模型预测 ./script/launch_pred.py 执行结果 应用程序终止后,您应该得到每个输入数据文件的预测结果文件。请检查放置数据文件的目录。预测结果文件的每一行都包含对应数据的预测。 3. K-Means Clustering The app can be found at bosen/app/kmeans. From this point on, all instructions will assume you are at bosen/app/kmeans. After building the main PMLS libraries, you can build kmeans: make -j2 Then # Create run script from template cp script/launch.py.template script/launch.py chmod +x script/launch.py ./script/launch.py The last command runs kmeans using the provided sample dataset dataset/sample.txt and output the found centers in output/out.centers and the cluster assignments in output/out.assignmentX.txt where X is the worker ID (each worker outputs cluster assignments in its partition). result "},"chapter4/分布式机器学习.html":{"url":"chapter4/分布式机器学习.html","title":"4.3 架设分布式机器学习","keywords":"","body":"架设分布式机器学习 Setting up machines Put the desired machine IP addresses in the Parameter Server machine file. See this page for more information: Configuration Files for PMLS apps. Common Parameters In script/launch.py.template' andscript/run_local.py.template`: host_filename = Parameter Server machine file. Contains a list of the ip addresses and port numbers for machines in the cluster. train_file = Name of the training file present under the dataset folder. It is assumed the entire file is present on a shared file system. total_num_of_training_samples = Number of data points that need to be considered. num_epochs = Number of mini batch Iterations. mini_batch_size = Size of the mini batch. num_centers = The number of cluster centers. dimensionality = The number of dimensions in each vector. num_app_threads = The number of application threads to run minibatch iterations in parallel. load_clusters_from_disk = true/talse indicating whether to do initialization of centers from external source. If set False, a random initialization is performed. cluster_centers_input_location = Location to the file containing the cluster centers. This file is read if the above argument is set to true. output_dir = The directory location where the cluster centers information is written after optimization. The assignments for the data points is written in the assignments sub-directory of the same folder. "},"chapter5/SparkTorch的安装.html":{"url":"chapter5/SparkTorch的安装.html","title":"5.1 SparkTorch","keywords":"","body":"SparkTorch的安装 安装成功了,但有点问题,打算用analytics-zoo了 GitHub仓库地址 一、介绍 This is an implementation of Pytorch on Apache Spark. The goal of this library is to provide a simple, understandable interface in distributing the training of your Pytorch model on Spark. With SparkTorch, you can easily integrate your deep learning model with a ML Spark Pipeline. Underneath the hood, SparkTorch offers two distributed training approaches through tree reductions and a parameter server. Through the api, the user can specify the style of training, whether that is distributed synchronous or hogwild. Why should I use this? Like SparkFlow, SparkTorch's main objective is to seamlessly work with Spark's ML Pipelines. This library provides three core components: Data parallel distributed training for large datasets. SparkTorch offers distributed synchronous and asynchronous training methodologies. This is useful for training very large datasets that do not fit into a single machine. Full integration with Spark's ML library. This ensures that you can save and load pipelines with your trained model. Inference. With SparkTorch, you can load your existing trained model and run inference on billions of records in parallel. On top of these features, SparkTorch can utilize barrier execution, ensuring that all executors run concurrently during training (This is required for synchronous training approaches). 二、安装 Install SparkTorch via pip: pip install sparktorch SparkTorch requires Apache Spark >= 2.4.4, and has only been tested on PyTorch versions >= 1.3.0. 参考链接 “SparkTorch” A High-Performance Distributed Deep Learning Library: Step-by-Step Training of PyTorch Network on Hadoop YARN & Apache Spark in Your Local Machine "},"chapter5/analytics-zoo.html":{"url":"chapter5/analytics-zoo.html","title":"5.2 analytics-zoo","keywords":"","body":"Analytics-Zoo 一、介绍 Analytics Zoo是统一的数据分析AI平台,支持笔记本、云、Hadoop Cluster、K8s Cluster等平台、此外,Analytics Zoo提供了端到端的pipeline,大家可以将AI模型应用到分布式大数据场景中。Analytics Zoo还提供了端到端的ML workflow和内置的模型和算法。具体而言,在底层的使用环境中,支持深度学习框架,如TensorFlow、PyTorch、OpenVINO等,还支持分布式框架,如Spark、Flink、Ray等,还可以使用Python库,如Numpy、Pandas、sklearn等。在端到端的pipeline中用户可以使用原生的TensorFlow和PyTorch,用户只需要很简单的修改就可以将原有的TensorFlow和PyTorch代码移植到Spark上来做分布式训练。Analytics Zoo还提供了RayOnSpark,ML Pipeplines,Automatic Cluster Serving,支持流式Serving。在内置算法中,提供了推荐算法,时序算法,视觉以及自然语言处理等 二、安装 conda create -n zoo python=3.7 # zoo is conda environment name, you can use any name you like. conda activate zoo pip install analytics-zoo # install either version 0.9 or latest nightly build pip install torch==1.7.1 torchvision==0.8.2 pip install six cloudpickle pip install jep==3.9.0 参考链接 Analytics Zoo 入门 "},"chapter6/spark-sklearn.html":{"url":"chapter6/spark-sklearn.html","title":"6.1 spark-sklearn","keywords":"","body":"PyTorch+Spark spark-sklearn Github仓库已经归档了,推荐使用Joblib Apache Spark Backend 安装 官方文档 pip install SciPy apt-get install g++ pip install spark-sklearn 参考链接 将pytorch 模型嵌入到spark中进行大规模预测 "},"chapter6/joblibspark.html":{"url":"chapter6/joblibspark.html","title":"6.2 Joblib Apache Spark Backend","keywords":"","body":"Joblib Apache Spark Backend 一、介绍 这个库为joblib 提供了 ApacheSpark 后端,以便在Spark 集群上分发任务。 Joblib是一个可以简单地将Python代码转换为并行计算模式的软件包,它可非常简单并行我们的程序,从而提高计算速度。 Joblib是一组用于在Python中提供轻量级流水线的工具。 它具有以下功能: 透明的磁盘缓存功能和“懒惰”执行模式,简单的并行计算 Joblib对numpy大型数组进行了特定的优化,简单,快速。 `Scikit-learn使用joblib库在其估计器中支持并行计算。有关控制并行计算的开关,请参阅joblib文档。 二、安装 joblibspark requires Python 3.6+, joblib>=0.14 and pyspark>=2.4 to run. To install joblibspark, run: pip install joblibspark The installation does not install PySpark because for most users, PySpark is already installed. If you do not have PySpark installed, you can install pyspark together with joblibspark: pip install pyspark>=3.0.0 joblibspark If you want to use joblibspark with scikit-learn, please install scikit-learn>=0.21. pip install scikit-learn 三、示例 from sklearn.utils import parallel_backend from sklearn.model_selection import cross_val_score from sklearn import datasets from sklearn import svm from joblibspark import register_spark register_spark() # register spark backend iris = datasets.load_iris() clf = svm.SVC(kernel='linear', C=1) with parallel_backend('spark', n_jobs=3): scores = cross_val_score(clf, iris.data, iris.target, cv=5) print(scores) 执行命令,将程序提交到集群 spark-submit --master spark://master:7077 /opt/share/sklearn-svm.py 运行效果 四、局限性 joblibspark does not generally support run model inference and feature engineering in parallel. For example: from sklearn.feature_extraction import FeatureHasher h = FeatureHasher(n_features=10) with parallel_backend('spark', n_jobs=3): # This won't run parallelly on spark, it will still run locally. h.transform(...) from sklearn import linear_model regr = linear_model.LinearRegression() regr.fit(X_train, y_train) with parallel_backend('spark', n_jobs=3): # This won't run parallelly on spark, it will still run locally. regr.predict(diabetes_X_test) Note: for sklearn.ensemble.RandomForestClassifier, there is a n_jobs parameter, that means the algorithm support model training/inference in parallel, but in its inference implementation, it bind the backend to built-in backends, so the spark backend not work for this case. "},"chapter7/Spark Security.html":{"url":"chapter7/Spark Security.html","title":"7.1 Spark Security","keywords":"","body":"Spark Security Spark自带的安全机制 https://spark.apache.org/docs/latest/security.html "},"chapter7/Spark数据安全.html":{"url":"chapter7/Spark数据安全.html","title":"7.2 Spark数据安全","keywords":"","body":"Spark数据安全 Data-at-rest security for spark ApacheSpark 通过有效利用主内存和缓存数据来实现快速计算并极大地加速分析应用程序以供以后使用。ApacheSpark 的核心使用称为 RDD (Resilient 分布式数据集)的数据结构为分布式数据提供统一的视图。然而,RDD 中的数据仍然没有加密,这可能导致应用程序生成或处理的机密数据泄露。Apache Spark 在各种情况下将(未加密的) RDD 持久化到磁盘存储,包括但不限于缓存、 RDD 检查点和数据洗牌操作期间的数据溢出等。这种安全性的缺乏使得 ApacheSpark 不适合处理任何时候都应该保护的敏感信息。此外,存储在主存中的 RDD 容易受到主存攻击,比如 RAM 抢占。在本文中,我们提出并开发了一些解决方案来弥补当前 ApacheSpark 框架中的这些安全缺陷。我们提出了三种不同的方法来将安全性合并到 ApacheSpark 框架中。这些方法旨在限制在数据处理、缓存和数据溢出到磁盘期间暴露未加密数据。我们结合使用加密分解和加密来保护 Apache Spark 存储和溢出的数据,这些数据既存储在磁盘上,也存储在主存中。我们的方法结合了信息传播算法(IDA)和沙米尔完美秘密共享(PSS) ,提供了强大的安全性。大量的实验表明,通过适当选择参数,我们的安全方法提供了高度的安全性,性能损失在10% -25% 之间。 GuardSpark++: Fine-grained purpose-aware access control for secure data sharing and analysis in Spark 随着计算和通信技术的发展,大量的数据被收集、存储、利用和共享,同时也带来了新的安全和隐私挑战。现有平台没有为大数据分析应用提供灵活实用的访问控制机制。在本文中,我们提出了一种在Spark中用于安全数据共享和分析的细粒度访问控制机制GuardSpark++。特别地,我们首先提出了一个基于目的的访问控制(PAAC)模型,它在传统的基于目的的访问控制中引入了数据处理/操作目的的新概念。开发了一种自动目的分析算法,用于从数据分析操作和查询中识别目的,因此可以相应地实施访问控制。此外,我们在Spark Catalyst中开发了一种访问控制机制,为异构数据源和上层应用提供统一的PAAC实施。我们使用Spark中的五个数据源和四个结构化数据分析引擎来评估GuardSpark++。实验结果表明,GuardSpark++以非常小的性能开销(平均3.97%)提供了有效的访问控制功能。 SparkAC: Fine-Grained Access Control in Spark for Secure Data Sharing and Analytics 随着计算和通信技术的发展,海量数据被收集、存储、利用和共享,新的安全和隐私挑战也随之而来。现有的大数据平台提供的访问控制机制在粒度和表达能力上存在局限性。在本文中,我们介绍了 SparkAC,这是一种用于 Spark 中安全数据共享和分析的新型访问控制机制。特别是,我们首先提出了一种目的感知访问控制(PAAC)模型,它引入了数据处理目的和数据操作目的的新概念,以及一种从数据分析操作和查询中识别目的的自动目的分析算法。此外,我们开发了一个统一的访问控制机制,在两个模块中实现 PAAC 模型。 GuardSpark++支持Spark Catalyst中的结构化数据访问控制,GuardDAG支持Spark core中的非结构化数据访问控制。最后,我们使用多个数据源、应用程序和数据分析引擎评估 GuardSpark++ 和 GuardDAG。实验结果表明,SparkAC 以非常小的 (GuardSpark++) 或中等 (GuardDAG) 性能开销提供有效的访问控制功能。 ENCRYPTION OF SATELLITE IMAGES WITH AES ALGORITHM ON APACHE SPARK Using trusted execution environments for secure stream processing of medical data 在第三方不可信云上处理敏感数据(如身体传感器产生的数据)尤其具有挑战性,同时又不损害生成这些数据的用户的隐私。通常,这些传感器以流方式生成大量连续数据。即使在强大的对抗性模型下,也必须有效和安全地处理如此大量的数据。最近在大众市场上推出的带有可信执行环境的消费级处理器(例如英特尔 SGX) ,为克服不太灵活的方法(例如顶部同态加密)的解决方案铺平了道路。我们提出了一个安全的流处理系统建立在英特尔新交所的顶部,以展示这种方法的可行性与系统专门适合医疗数据。我们设计并充分实现了一个原型系统,我们评估了几个现实的数据集。我们的实验结果表明,与普通的 Spark 系统相比,该系统实现了适度的开销,同时在强大的攻击者和威胁模型下提供了额外的保护保证。 "}}