《Deep-Learning-with-PyTorch》学习笔记Chapter15 Deployment(二)

(自学《Deep-Learning-with-PyTorch》使用,仅供参考)

【Request batching】

请求批处理

本书中的异步编程主要做的是允许函数非阻塞地等待计算结果或者事件。

图1

为了进行请求批处理,我们需要将请求处理从运行模型中分离出来(解耦)。

上图显示了数据流,顶部三个是发出请求的客户端(CLIENT),右边三个箭头表示它们一个接一个通过请求处理器(REQUEST PROCESSOR)的上半部分,与工作项(WORK ITEM)一起进入队列,而当一个完整的批处理(WORK BATCH)已经排队或者最老的请求已经等待了指定的最大时间的时候,模型运行器(MODEL RUNNER)从队列中获取一个批处理,处理它,并将结果附加到工作项(WORK ITEM RESULT)上,最后,请求处理器(REQUEST PROCESSOR)的下半部分会处理这些请求。

【IMPLEMENTATION】

当我们要运行模型时,先要组装一批的输入,然后再在第二线程中运行模型并返回结果,接着,请求处理器(REQUEST PROCESSOR)解码请求、入列输入、等待处理完成并返回输出结果。

这个与用桶接水灌满鱼缸一个原理,打开水龙头用桶接水,当桶的里水满了或者时间差不多的时候,就将桶里得水倒进鱼缸中。我们将新请求放到队列中,在需要的时候触发处理,并等待结果,然后将它们作为请求的答案发送出去。

图2

异步服务器由三个块组成:请求处理器(REQUEST PROCESSOR)、模型运行器(MODEL RUNNER)和模型执行(MODEL EXECUTION)。

这些块有点像函数,但前两个块归顺于事件循环。

如前面所说,我们处理事件有两种时机:①当我们积累了完整的批处理的时候②当最老请求达到最长等待时间的时候。对于②,我们可以通过设置计数器来解决。

我们使用ModelRunner加载我们的模型,并处理一些管理工作。

class ModelRunner:
    def __init__(self, model_name): 
        self.model_name = model_name
        # 首先我们要将我们的请求输入到队列中,在Python中可以用列表来实现。
        # 队列的原则是先进后出,所以我们可以在后面添加工作项,在前面删除它们。
        self.queue = [] # 队列

        # 当我们修改队列时,我们希望防止下面的其他任务改变队列。为此,引入queue_lock,这个队列锁将成为asynico模块提供的asyncio.Lock 
        # 书中用的asyncio对象都需要知道事件循环,该循环只有在初始化应用程序后才能使用,因此我们在实例化中先临时设为None。
        # 如果我们有多个worker,我们需要查看锁定
        # 注意:Python的异步锁不是线程安全的
        self.queue_lock = None

        # 加载并实例化模型
        self.model = get_pretrained_model(self.model_name, map_location=device)

        # 这是我们运行模型的信号,ModelRunner在无事要做的时候会等待,我们需要从Request-Processor传递信号告诉它继续工作,不要偷懒。 
        self.needs_processing = None

        # 使用一个计时器保证最大的等待时间,计时器会设置needs_processing事件,我们现在只订一个位子。
        # 我们会在一个批处理完成或者计时器达到最大计数值的时候直接设置一个事件。
        # 当我们在计时器达到最大计数值前处理一个批次,我们要清除它,从而避免太多工作。
        self.needs_processing_timer = None

【FROM REQUEST TO QUEUE】

接下来,我们需要对请求进行排队。我们在第一个异步方法(process_input)中实现了这一点。

    def schedule_processing_if_needed(self):
        if len(self.queue) >= MAX_BATCH_SIZE:
            logger.debug("next batch ready when processing a batch")
            self.needs_processing.set()
        elif self.queue:
            logger.debug("queue nonempty when processing a batch, setting next timer")
            self.needs_processing_timer = app.loop.call_at(self.queue[0]["time"] + MAX_WAIT, self.needs_processing.set)

    async def process_input(self, input): # 异步方法实现排列请求
        # 设置一个字典保存任务信息
        our_task = {"done_event": asyncio.Event(loop=app.loop),#关键字done_event保存处理任务完成标志
                    "input": input, # 关键字input保存进程输入
                    "time": app.loop.time()}# 关键字time保存排队时间

        # 处理进程添加了一个输出,我们持有队列锁,并且我们把任务添加到队列中,并在需要时调度处理
        # 重要的是使用循环时间,这个跟time.time()不同,否则,我们可能会在排队之前就得到计划处理的事件,或者根本不处理它们。
        # 队列锁
        async with self.queue_lock:
            if len(self.queue) >= MAX_QUEUE_SIZE:
                raise HandlingError("I'm too busy", code=503)
            # 把任务添加到队列中
            self.queue.append(our_task)
            logger.debug("enqueued task. new queue size {}".format(len(self.queue)))
            # 在需要时调度处理:如果我们有一个完整的批处理,Processing将设置needs_processing
            self.schedule_processing_if_needed()
        # 等待任务被处理,然后返回它。
        await our_task["done_event"].wait()
        return our_task["output"]

【RUNNING BATCHES FROM THE QUEUE】

如图2所示,model_runner进行一些设置,然后进行无限循环。

    def run_model(self, batch):  # runs in other thread
        return self.model(batch.to(device)).to('cpu')

    # 在实例化应用程序时调用
    async def model_runner(self):
        # 设置queue_lock队列锁
        self.queue_lock = asyncio.Lock(loop=app.loop)
        # 设置needs_processing事件
        self.needs_processing = asyncio.Event(loop=app.loop)
        logger.info("started model runner for {}".format(self.model_name))
        #  进入循环
        while True:
            # 等待needs_processing时间
            await self.needs_processing.wait()
            self.needs_processing.clear()
            # 当一个事件出现时,我们先检查是否设置了时间,如果设置了(is not None),就清除它,因为我们现在正在处理事情
            if self.needs_processing_timer is not None:
                self.needs_processing_timer.cancel()
                self.needs_processing_timer = None
            async with self.queue_lock:
                if self.queue:
                    longest_wait = app.loop.time() - self.queue[0]["time"]
                else:  # oops
                    longest_wait = None
                logger.debug("launching processing. queue size: {}. longest wait: {}".format(len(self.queue), longest_wait))
                # 从model_runner的队列中抓取一个队列,如果需要,安排在下一个批次进行处理
                to_process = self.queue[:MAX_BATCH_SIZE]
                del self.queue[:len(to_process)]
                self.schedule_processing_if_needed()
            # so here we copy, it would be neater to avoid this
            batch = torch.stack([t["input"] for t in to_process], dim=0)
            # we could delete inputs here...
            
            # 从单个任务中组装批处理,并使用asyncio的app.loop.run_in_executor启动一个评估模型的新线程。
            # 在一个单独的线程中运行模型,将数据移动到设备,然后将数据移交给模型。我们在它完成后继续处理。
            result = await app.loop.run_in_executor(
                None, functools.partial(self.run_model, batch)
            )
            # 最后,将输出(“output”)添加到任务并设置(“done_event")
            for t, r in zip(to_process, result):
                t["output"] = r
                t["done_event"].set()
            del to_process

【SUMMARIZE】

web框架——大致看起来像带有async和await的Flask——需要一个小包装器。

我们需要在事件循环上启动model_runner函数。

(如果我们没有多个运行器从队列中获取信息并可能相互中断,那么锁定队列实际上是没有必要的,但是对于其他项目来说,如果不这样做,就会有丢失请求的安全隐患)

【注意】:

虽然服务器实现了对GPU的请求进行批量处理并且异步运行的功能,但我们仍然使用的是Python模式,所以GIL阻碍了我们的模型在主线程中并行运行请求服务。

这存在不安全的潜在敌对环境,如互联网。

尤其,解码请求数据的功能既不是速度最佳的,也不是完全安全的。

一般来说,如果我们能够进行解码,将请求流和预先分配的内存块传递给函数,并由该函数从流中解码图像,那就更好了。

但我们不知道有哪个库是这样做的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
deep-learning-with-pytorch.pdf是一本介绍使用PyTorch进行深度学习的书籍,其中第15章主要讨论了迁移学习和生成对抗网络(GAN)的应用。迁移学习是指将在一个任务上训练过的模型应用到另一个任务中,以加快模型训练速度和提高性能。这种方法的好处是可以利用已有模型在大规模数据集上进行预训练,从而获得更好的模型初始化参数。比如,可以将在ImageNet数据集上预训练好的卷积神经网络应用于其他图像分类任务中,无需从头开始训练,从而节省时间和计算资源。 生成对抗网络(GAN)是一种由生成器和判别器组成的模型架构,用于生成逼真的合成数据。GAN由一个生成器网络和一个判别器网络组成,两者通过对抗训练的方式相互竞争学习。生成器尝试生成逼真的数据样本,而判别器则试图将真实数据样本和生成的数据样本区分开来。通过持续的对抗训练,生成器逐渐改进生成的样本,直到达到接近真实样本的水平。GAN在图像生成、图像转换等领域具有广泛的应用,能够生成逼真的图像、音频等数据。 在第15章中,作者可能会介绍迁移学习的原理和步骤,如何在PyTorch中实现迁移学习以及一些成功的迁移学习案例。此外,作者可能还会介绍GAN的基本原理、GAN网络的结构以及如何在PyTorch中实现GAN模型。本章内容对于想要应用深度学习技术解决实际问题的人们来说,具有很高的实用性和参考价值。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值