1. 其他教程
  2. 设置演示以获得最佳性能

设置最大性能的演示

假设您的 Gradio 演示在社交媒体上爆火——您有许多用户同时尝试使用它,并且您希望为用户提供最佳体验,换句话说,尽量减少每个用户排队等待查看预测结果的时间。

如何配置 Gradio 演示以处理最多的流量?在本指南中,我们将深入探讨 Gradio 的 .queue() 方法以及一些其他相关参数,并讨论如何设置这些参数以使您能够以最小的延迟同时服务大量用户。

这是一个高级指南,因此请确保您已经了解 Gradio 的基础知识,例如如何创建和启动 Gradio 界面。本指南中的大部分信息无论您是在 Hugging Face Spaces 上还是在自己的服务器上托管演示都适用。

Gradio 的队列系统概述

默认情况下,每个 Gradio 演示都包含一个内置的队列系统,可扩展到数千个请求。当您的应用程序用户提交请求(即向您的函数提交输入)时,Gradio 会将请求添加到队列中,并且请求通常按顺序处理(这并不完全正确,如下所述)。当用户的请求处理完成后,Gradio 服务器会使用服务器端事件 (SSE) 将结果返回给用户。SSE 协议比简单使用 HTTP POST 请求有几个优点:

(1) 它们不会超时——如果大多数浏览器在短时间内(例如 1 分钟)没有收到 POST 请求的响应,它们会引发超时错误。如果您的推理函数需要超过 1 分钟才能运行,或者许多人同时尝试您的演示,导致延迟增加,这可能会成为问题。

(2) 它们允许服务器向前端发送多个更新。这意味着,例如,服务器可以发送预测完成所需时间的实时 ETA。

要配置队列,只需在启动 InterfaceTabbedInterfaceChatInterface 或任何 Blocks 之前调用 .queue() 方法。这是一个示例:

import gradio as gr

app = gr.Interface(lambda x:x, "image", "image")
app.queue()  # <-- Sets up a queue with default parameters
app.launch()

请求如何从队列中处理

当 Gradio 服务器启动时,会使用一个线程池来执行队列中的请求。默认情况下,此线程池的最大大小为 40(这是从 FastAPI 继承的默认值,Gradio 服务器基于 FastAPI)。然而,这并不意味着总是从队列中并行处理 40 个请求。

相反,Gradio 默认使用单函数单工作器模型。这意味着每个工作线程只被分配一个函数,这个函数来自于您的 Gradio 应用程序中的所有函数。这确保了您不会看到例如内存不足错误,因为多个工作器同时调用一个机器学习模型。假设您的 Gradio 应用程序中有 3 个函数:A、B 和 C。并且您看到以下 7 个请求序列来自使用您的应用程序的用户:

1 2 3 4 5 6 7
-------------
A B A A C B A

最初,将分配 3 个工作器来处理请求 1、2 和 5(对应于函数:A、B、C)。一旦这些工作器中的任何一个完成,它们将开始处理队列中相同函数类型的下一个函数,例如,完成处理请求 1 的工作器将开始处理请求 3,依此类推。

如果您想更改此行为,可以使用几个参数来配置队列并帮助减少延迟。让我们逐一介绍它们。

queue() 中的 default_concurrency_limit 参数

我们要探讨的第一个参数是 queue() 中的 default_concurrency_limit 参数。这控制了多少工作器可以执行同一个事件。默认情况下,它设置为 1,但您可以将其设置为更高的整数:210,甚至 None(在最后一种情况下,除了可用的工作器总数之外没有限制)。

这很有用,例如,如果您的 Gradio 应用程序不调用任何资源密集型函数。如果您的应用程序只查询外部 API,那么您可以将 default_concurrency_limit 设置得更高。增加此参数可以线性地提高服务器处理请求的能力

那么为什么不一直将此参数设置得很高呢?请记住,由于请求是并行处理的,每个请求都会消耗内存来存储数据和权重进行处理。这意味着如果将 default_concurrency_limit 设置得太高,您可能会遇到内存不足错误。如果 default_concurrency_limit 太高,您也可能会开始获得递减的回报,因为在不同的工作线程之间切换会产生开销。

建议:在您继续看到性能提升或达到机器内存限制之前,尽可能提高 default_concurrency_limit 参数。您可以在此处阅读有关 Hugging Face Spaces 机器规格的信息

事件中的 concurrency_limit 参数

您还可以单独为每个事件设置可以并行处理的请求数。这些优先于前面描述的 default_concurrency_limit 参数。

为此,请设置任何事件监听器的 concurrency_limit 参数,例如 btn.click(..., concurrency_limit=20) 或在 InterfaceChatInterface 类中设置:例如 gr.Interface(..., concurrency_limit=20)。默认情况下,此参数设置为全局 default_concurrency_limit

launch() 中的 max_threads 参数

如果您的演示使用非异步函数(例如 def 而不是 async def),它们将在线程池中运行。该线程池的大小为 40,这意味着只能创建 40 个线程来运行您的非异步函数。如果您遇到了这个限制,可以使用 max_threads 增加线程池大小。默认值为 40。

应尽可能使用异步函数来增加应用程序可以处理的并发请求数。非 CPU 密集型的快速函数是编写为 async 的好选择。这篇指南是关于该概念的一个很好的入门。

queue() 中的 max_size 参数

减少等待时间的一个更直接的方法是简单地阻止太多人加入队列。您可以使用 queue()max_size 参数设置队列处理的最大请求数。如果请求到达时队列已达到最大大小,它将不被允许加入队列,而是会收到一个错误,提示队列已满,请稍后再试。默认情况下,max_size=None,这意味着可以加入队列的用户数没有限制。

矛盾的是,设置 max_size 通常可以改善用户体验,因为它阻止用户被非常长的队列等待时间劝退。对您的演示更感兴趣和投入的用户将继续尝试加入队列,并且能够更快地获得结果。

建议:为了获得更好的用户体验,根据您对用户愿意等待预测时间的预期设置一个合理的 max_size

事件中的 max_batch_size 参数

提高 Gradio 演示并行性的另一种方法是编写函数,使其可以接受批次输入。大多数深度学习模型处理批次样本比处理单个样本效率更高。

如果您编写函数来处理批次样本,Gradio 将自动将传入的请求批量组合在一起,并将它们作为批次样本传递给您的函数。您需要将 batch 设置为 True(默认为 False),并根据函数能够处理的最大样本数设置 max_batch_size(默认为 4)。这两个参数可以传递给 gr.Interface() 或 Blocks 中的事件,例如 .click()

虽然设置批次在概念上与让工作器并行处理请求相似,但对于深度学习模型而言,它通常比设置 concurrency_count 更快。缺点是您可能需要稍微调整函数以接受批次样本而不是单个样本。

这是一个不接受批次输入的函数示例——它一次处理一个输入:

import time

def trim_words(word, length):
    return word[:int(length)]

这是重写为接受批次样本的相同函数:

import time

def trim_words(words, lengths):
    trimmed_words = []
    for w, l in zip(words, lengths):
        trimmed_words.append(w[:int(l)])
    return [trimmed_words]

第二个函数可以与 batch=True 和适当的 max_batch_size 参数一起使用。

建议:如果可能,编写函数以接受批次样本,然后根据机器的内存限制将 batch 设置为 True 并将 max_batch_size 设置得尽可能高。

升级硬件(GPU、TPU 等)

如果您已经完成了上述所有操作,但您的演示仍然不够快,您可以升级运行模型的硬件。将模型从在 CPU 上运行更改为在 GPU 上运行通常会使深度学习模型的推理时间增加 10 到 50 倍。

在 Hugging Face Spaces 上升级硬件特别简单。只需在 Space 中单击“设置”选项卡并选择您想要的 Space 硬件。

虽然您可能需要调整部分机器学习推理代码才能在 GPU 上运行(如果您使用 PyTorch,这里有一份便捷指南),但 Gradio 完全不依赖于硬件选择,如果您将其与 CPU、GPU、TPU 或任何其他硬件一起使用,它将完全正常工作!

注意:您的 GPU 内存与您的 CPU 内存不同,因此如果您升级硬件,您可能需要调整上面描述的 default_concurrency_limit 参数的值。

结论

恭喜!您现在知道如何设置 Gradio 演示以实现最大性能。祝您的下一个爆款演示好运!

gradio