假设你的Gradio演示在社交媒体上走红——你有很多用户同时尝试使用它,你希望为用户提供最佳的体验,换句话说,尽量减少每个用户在队列中等待看到预测结果的时间。
如何配置你的Gradio演示以处理最多的流量?在本指南中,我们将深入探讨Gradio的.queue()方法的一些参数以及其他相关参数,并讨论如何设置这些参数,以便你能够以最小的延迟同时为大量用户提供服务。
这是一个高级指南,因此请确保您已经了解Gradio的基础知识,例如如何创建和启动Gradio界面。无论您是在Hugging Face Spaces上托管您的演示,还是在您自己的服务器上托管,本指南中的大部分信息都是相关的。
默认情况下,每个Gradio演示都包含一个内置的队列系统,可以扩展到数千个请求。当您的应用程序的用户提交请求时(即向您的函数提交输入),Gradio会将请求添加到队列中,并且请求通常会按顺序处理(这并不完全正确,如下所述)。当用户的请求处理完成后,Gradio服务器使用服务器端事件(SSE)将结果返回给用户。与简单地使用HTTP POST请求相比,SSE协议有几个优点:
(1) 它们不会超时——大多数浏览器如果在短时间内(例如1分钟)没有收到POST请求的响应,就会引发超时错误。如果你的推理函数运行时间超过1分钟,或者同时有很多人在试用你的演示,导致延迟增加,这可能会成为一个问题。
(2) 它们允许服务器向前端发送多个更新。这意味着,例如,服务器可以发送一个实时ETA,显示您的预测需要多长时间才能完成。
要配置队列,只需在启动Interface、TabbedInterface、ChatInterface或任何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,但你可以将其设置为更高的整数:2、10,甚至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)或在Interface或ChatInterface类中:例如gr.Interface(..., concurrency_limit=20)。默认情况下,此参数设置为全局的default_concurrency_limit。
launch() 中的 max_threads 参数如果你的演示使用非异步函数,例如def而不是async def,它们将在线程池中运行。这个线程池的大小为40,意味着只能创建40个线程来运行你的非异步函数。如果你遇到这个限制,你可以使用max_threads来增加线程池的大小。默认值是40。
提示: 你应该尽可能使用异步函数来增加你的应用程序可以处理的并发请求数量。那些不占用CPU的快速函数是编写为`async`的好候选。这个[指南](https://fastapi.tiangolo.com/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设置得尽可能高。
如果你已经完成了上述所有步骤,而你的演示仍然不够快,你可以升级模型运行的硬件。将模型从在CPU上运行改为在GPU上运行,通常可以为深度学习模型的推理时间提供10倍到50倍的提升。
在Hugging Face Spaces上升级您的硬件特别简单。只需点击您Space中的“Settings”标签,然后选择您想要的Space Hardware即可。

虽然你可能需要调整部分机器学习推理代码以在GPU上运行(如果你使用的是PyTorch,这里有一个方便的指南),但Gradio对硬件选择完全不可知,如果你将其与CPU、GPU、TPU或任何其他硬件一起使用,它将完全正常工作!
注意:您的GPU内存与CPU内存不同,因此如果您升级硬件,可能需要调整上述描述的default_concurrency_limit参数的值。
恭喜!您知道如何设置Gradio演示以获得最佳性能。祝您的下一个热门演示好运!