分布式框架Ray——基本模式

1. Tree of Actors

在Tree of Actors模式中,Ray actor可以监督很多个Ray worker actor。
supervisor参与者的一次调用会触发对子参与者的多个方法调用的分派,在返回之前,supervisor可以处理结果或更新子参与者。

注意:
如果supervisor死亡(driver死亡),worker actor仍会自动工作。
actors可以被嵌套到多个层次以形成一棵树。

示例:
你想要同时训练3个模型,同时能够检查它的状态。

import ray
#定义工人类
@ray.remote(num_cpus=1)
class Worker :
    def work (self):
        return "done"
#定义监督类
@ray.remote(num_cpus=1)
class Supervisor :
    #构造函数
    def __init__ (self):
        self.workers = [Worker.remote() for _ in range( 3 )] #workers是对象的id
    def work (self):
        return ray.get([w.work.remote() for w in self.workers]) #通过对象id获取对象的内容 
#初始化
ray.init()
sup = Supervisor.remote()
print(ray.get(sup.work.remote())) # outputs ['done', 'done', 'done']

2. Tree of Tasks

在Tree of Tasks模式中,以递归方式生成远程任务,以对列表进行排序。
在远程函数的定义中,可以调用它自己(quick_sort_distribute .remote)。
对任务的单个调用会触发多个任务的分派。
我们在两次Ray函数调用之后调用Ray.get(),这可以让你最大化工作负载中的并行性。

示例
对4000000长度的列表进行排序,算法是快速排序。

import ray
ray.init()
# 每一次选基点,比大小分区的过程
def partition (collection):
    # 使用最后一个元素作为第一个主元
    pivot = collection.pop()
    # 分别存放大于主元和小于主元的元素
    greater, lesser = [], []
    for element in collection:
        if element > pivot:
            greater.append(element)
        else:
            lesser.append(element)
    return lesser, pivot, greater

def quick_sort(collection):
    if len(collection) <= 200000 : # magic number
        return sorted(collection)
    else:
        # 递归调用的思想 
        lesser, pivot, greater = partition(collection)
        lesser = quick_sort(lesser)
        greater = quick_sort(greater)
        # time.sleep(1)
        return [lesser]+ [pivot] + [greater]
        
@ray.remote
def quick_sort_distributed (collection):
    if len(collection) <= 200000 : # magic number
        return sorted(collection)
    else:
        # 递归调用的思想 
        lesser, pivot, greater = partition(collection)
        "只有这两句可以并行"
        lesser = quick_sort_distributed.remote(lesser)
        greater = quick_sort_distributed.remote(greater)
        # time.sleep(1)
        return ray.get(lesser) + [pivot] + ray.get(greater)
if __name__ == "__main__" :
    from numpy import random
    import time
    unsorted = random.randint( 1000000 , size=( 4000000 )).tolist()
    s = time.time()
    ray.get(quick_sort_distributed.remote(unsorted))
    print( "分布式快速排序: " + str(time.time() - s)) 
    s = time.time()
    quick_sort(unsorted)
    print( "快速排序: " + str(time.time() - s)) 

分布式快速排序的时间比快速排序时间长的原因:
快速排序算法本身是一个分而治之的算法,每一层的逻辑结构是不能变的,可以分布式计算的只是左右两边的列表,其他的过程是不可以并行的,具体可以看上面快速排序的示意图。
上面代码中200000是一个魔术数字,因为当是200000时,对4000000的列表调用分布式快速排序, 如果是对半分的话,最多4次,期间来回调用分布式函数,每次分布式计算[2,4,8,16]次,按递归的方式依次循环,反复共享参数,分布计算减少的时间远远小于4次共享数据的时间。当魔术数字为2000000时,只需要分两次,这两次并行计算,时间比魔术数字是200000少,但是仍比快速排序慢。(也可以用谷歌打开网址localhost:8265,查看ray并行计算的过程)。
当递归的次数比较多(叶子节点比较多),分布式计算是有优势的,只是该排序每一次迭代的计算量太小。
在sorted()函数后面添加sleep(1)函数,魔术数字是200000,效果如下:

下面对上述代码做改动:

注:函数的嵌套:@ray.remote内层,外层不一定用。

"更改代码:对排序函数sorted()使用了remote注释"
@ray.remote
def ray_sorted(collection):
    return sorted(collection)

def quick_sort_distributed (collection):
    if len(collection) <= 200000 : # magic number
        sorted_collection = ray_sorted.remote(collection)
        return ray.get(sorted_collection)
    else:
        # 递归调用的思想 
        lesser, pivot, greater = partition(collection)
        lesser = quick_sort_distributed(lesser)
        greater = quick_sort_distributed(greater)
        return lesser + [pivot] + greater
if __name__ == "__main__" :
    from numpy import random
    import time
    unsorted = random.randint( 1000000 , size=( 4000000 )).tolist()
    s = time.time()
    quick_sort_distributed(unsorted)
    print( "分布式快速排序: " + str(time.time() - s))
    s = time.time()
    quick_sort(unsorted)
    print( "快速排序: " + str(time.time() - s))

再一次分析原因:
分布式计算不是左右两个列表同时计算,而是对小于魔术数字的列表做分布式排序,结果并没有比快速排序的时间短 (猜想:sorted()函数没办法分布式计算,或者效果不明显,因为排序必须是有顺序关系的,查看下面的图也能看到只有一个PID3711起主要作用)但是比上面的分布式排序的时间要短,这再次验证了上面的结论。

3. Map and Reduce

Map and Reduce模式是通过Map任务把一个大任务分成很多小任务,关键就是这些小任务是同时运行的,然后再通过Reduce任务,把很多小任务的结果汇总起来,最后输出汇总结果。

实现通用的map和reduce功能:对0-99之间的每个元素平方求和。

import time
import ray
import numpy as np

#=======================单线程的map:依次计算每个元素的平方=====================
s = time.time()
items = list(range( 100 ))
map_func = lambda i : i* 2
output = [map_func(i) for i in items]
print( "单线程map: " + str(time.time() - s))

#=======================单线程的reduce==========================================
s = time.time()
items = list(range( 100 ))
map_func = lambda i : i* 2
output = sum([map_func(i) for i in items])
print( "单线程reduce: " + str(time.time() - s))

ray.init()
#=======================Ray-多线程map:同时计算每个元素的平方===================
@ray.remote
def map (obj, f):
    return f(obj)

s = time.time()
items = list(range( 100 ))
map_func = lambda i : i* 2
output = ray.get([map.remote(i, map_func) for i in items])
print( "Ray-多线程map: " + str(time.time() - s))

#=======================Ray-多线程reduce========================================
@ray.remote
def map (obj, f):
    return f(obj)
@ray.remote
def sum_results (*elements):
    return np.sum(elements)
items = list(range( 100 ))
map_func = lambda i : i* 2
remote_elements = [map.remote(i, map_func) for i in items]
# simple reduce
s = time.time()
remote_final_sum = sum_results.remote(*remote_elements)
result = ray.get(remote_final_sum)
print( "Ray-多线程simple reduce: " + str(time.time() - s))
# tree reduce
s = time.time()
intermediate_results = [sum_results.remote(
    *remote_elements[i * 20 : (i + 1 ) * 20 ]) for i in range( 5 )]
remote_final_sum = sum_results.remote(*intermediate_results)
result = ray.get(remote_final_sum)
print( "Ray-多线程tree reduce: " + str(time.time() - s))

结论:
分布式计算数据之间没有太多的共享数据,只是共享一个数值,而不是上一个例子中的列表。

4. 使用ray.wait 限制进行中的任务数量

当您提交一个ray任务或actor调用时,ray将确保worker可以使用数据。 然而,如果你迅速地提交太多的任务,worker可能会超载并耗尽内存。您应该使用ray.wait()来阻塞,直到有一定数量的任务准备就绪。Ray Serve使用此模式来限制每个进行中的worker的查询数。

import time
import ray
import numpy as np

ray.init()
#Without backpressure
@ray.remote
class Actor :
    def heavy_compute (self, large_array):
        return 0
#taking long time...
actor = Actor.remote()
result_refs = []
s=time.time()
for i in range(100):
    large_array = np.zeros(100)
    result_refs.append(actor.heavy_compute.remote(large_array))
results = ray.get(result_refs)
print( "没有使用ray.wait(): " + str(time.time() - s))

可以简单的修改上面的示例,以便上一个任务完成时自适应地启动新任务。

ready_ids, remaining_ids=ray.wait(result_refs, num_returns=num_ready)

ready_ids是一个对象IDs列表,它对应的任务已完成执行,而remaining_ids是剩余的对象IDs列表。
当result_refs=12时,10个在ready_ids中,2个在remaining_ids;当result_refs=22时,第一次的10个有结束的,往后顺延直到10个,比如顺延到19,则num_ready=3。

#With backpressure
result_refs = []
s=time.time()
for i in range(100):
    large_array = np.zeros(100)
    if len(result_refs) > 10 :
        num_ready = i -10
        ready_ids, remaining_ids=ray.wait(result_refs, num_returns=num_ready)
    result_refs.append(actor.heavy_compute.remote(large_array))
print( "使用ray.wait(): " + str(time.time() - s))
### 使用 AutoGPTQ 库量化 Transformer 模型 为了使用 `AutoGPTQ` 对 Transformer 模型进行量化,可以遵循如下方法: 安装所需的依赖包是必要的操作。通过 pip 安装 `auto-gptq` 可以获取最新版本的库。 ```bash pip install auto-gptq ``` 加载预训练模型并应用 GPTQ (General-Purpose Tensor Quantization) 技术来减少模型大小和加速推理过程是一个常见的流程。下面展示了如何利用 `AutoGPTQForCausalLM` 类来进行这一工作[^1]。 ```python from transformers import AutoModelForCausalLM, AutoTokenizer from auto_gptq import AutoGPTQForCausalLM model_name_or_path = "facebook/opt-350m" quantized_model_dir = "./quantized_model" tokenizer = AutoTokenizer.from_pretrained(model_name_or_path) model = AutoModelForCausalLM.from_pretrained(model_name_or_path) # 加载已经量化的模型或者创建一个新的量化器对象用于量化未压缩过的模型 gptq_model = AutoGPTQForCausalLM.from_pretrained(quantized_model_dir, model=model, tokenizer=tokenizer) ``` 对于那些希望进一步优化其部署环境中的模型性能的人来说,`AutoGPTQ` 提供了多种配置选项来自定义量化参数,比如位宽(bit-width),这有助于平衡精度损失与运行效率之间的关系。 #### 注意事项 当处理特定硬件平台上的部署时,建议查阅官方文档以获得最佳实践指导和支持信息。此外,在实际应用场景之前应该充分测试经过量化的模型以确保满足预期的质量标准。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值