大模型WebUI:Gradio全解系列8——Additional Features:补充特性(下)
大模型WebUI:Gradio全解系列8——Additional Features:补充特性(下)
- 前言
- 本篇摘要
- 8. Additional Features:补充特性
- 8.5 嵌入托管 Spaces
- 8.5.1 使用 Web Components 嵌入
- 1. 嵌入步骤
- 2. 定义嵌入的外观和行为
- 8.5.2 使用IFrame嵌入
- 8.6 访问网络请求和Analytics应用分析
- 8.6.1 直接访问网络请求
- 8.6.2 Analytics:应用分析
- 8.7 授权OAuth
- 8.7.1 OAuth:通过Hugging Face登录
- 8.7.2 OAuth:使用外部提供商
- 8.8 安全访问文件
- 8.8.1 Gradio文件访问限制
- 1. Gradio允许用户访问的文件
- 2. Gradio不允许他人访问的文件
- 8.8.2 Gradio缓存
- 1. 缓存作用
- 2. Gradio移动到缓存的文件
- 8.8.3 文件上传
- 1. 设置上传限制
- 2. 最佳实践
- 8.9 资源清理
- 8.9.1 清理方法
- 1. 自动删除 gr.State
- 2. 通过 delete_cache 自动清理缓存
- 3. unload 事件
- 8.9.2 综合清理演示
- 参考文献
前言
本系列文章主要介绍WEB界面工具Gradio。Gradio是Hugging Face发布的简易webui开发框架,它基于FastAPI和svelte,便于部署人工智能模型,是当前热门的非常易于开发和展示机器学习大语言模型LLM及扩散模型DM的UI框架。本系列文章分为前置概念和实战演练两部分。前置概念先介绍Gradio的详细技术架构、历史、应用场景、与其他框架Gradio/NiceGui/StreamLit/Dash/PyWebIO的区别,然后详细介绍了大模型及数据的资源网站Hugging Face,包括三类工具models/datasets/spaces、六类开源库transformers/diffusers/datasets/PEFT/accelerate/optimum实战。实战演练部分先讲解了多种不同的安装、运行和部署方式,安装包括Linux/Win/Mac三类系统安装,运行包括普通方式和热重载方式,部署包括本地部署、HuggingFace托管、FastAPI挂载和Gradio-Lite浏览器集成;然后按照先整体再细节的逻辑,讲解Gradio的多种高级特性:三种Gradio Clients(python/javascript/curl)、Gradio Tools、Gradio的模块架构和环境变量等,方便读者对Gradio整体把握;最后深入细节,也是本系列文章的核心,先实践基础功能Interface、Blocks和Additional Features,再详解高级功能Chatbots、Data Science And Plots和Streaming。本系列文章讲解详细,涵盖Gradio大部分组件和功能,代码均可运行并附有大量运行截图,方便读者理解,Gradio一定会成为每个技术人员实现奇思妙想的最称手工具。
本系列文章目录如下:
- 《Gradio全解1——Gradio简介》
- 《Gradio全解1——Gradio的安装与运行》
- 《Gradio全解2——Gradio的3+1种部署方式实践》
- 《Gradio全解2——浏览器集成Gradio-Lite》
- 《Gradio全解3——Gradio Client:python客户端》
- 《Gradio全解3——Gradio Client:javascript客户端》
- 《Gradio全解3——Gradio Client:curl客户端》
- 《Gradio全解4——Gradio Tools:将Gradio用于LLM Agents》
- 《Gradio全解5——Gradio库的模块架构和环境变量》
- 《Gradio全解6——Interface:高级抽象界面类(上)》
- 《Gradio全解6——Interface:高级抽象界面类(下)》
- 《Gradio全解7——Blocks:底层区块类(上)》
- 《Gradio全解7——Blocks:底层区块类(下)》
- 《Gradio全解8——Additional Features:补充特性(上)》
- 《Gradio全解8——Additional Features:补充特性(下)》
- 《Gradio全解9——Chatbots:融合大模型的聊天机器人(上)》
- 《Gradio全解9——Chatbots:融合大模型的聊天机器人(下)》
- 《Gradio全解系列10——Data Science And Plots:数据科学与绘图》
- 《Gradio全解11——Streaming:数据流(上)》
- 《Gradio全解11——Streaming:数据流(下)》
本篇摘要
本篇介绍Gradio的其它特性功能,这些功能辅助Interface/Blocks实现更绚丽效果和更多功能。本章补充特性功能主要包括队列、输入输出流、提示及进度条、批处理函数、安全访问文件和资源清理,下面逐一讲述。
8. Additional Features:补充特性
本篇介绍Gradio的其它附加功能,这些功能辅助Interface/Blocks实现更绚丽效果和更多功能。本章附加功能主要包括队列、输入输出流、提示及进度条、批处理函数、安全访问文件和资源清理,下面逐一讲述。
8.5 嵌入托管 Spaces
一旦我们在 Hugging Face Spaces(或自己的服务器)上托管了应用程序,我们可能希望将演示嵌入到不同的网站上,例如博客或作品集。嵌入交互式演示可以让人们直接在浏览器中尝试我们构建的机器学习模型,而无需下载或安装任何内容!最棒的是,我们甚至可以在静态网站(如 GitHub 页面)中嵌入交互式演示。
有两种方法可以嵌入 Gradio 演示,可以直接在 Hugging Face Space 页面的“Embed this Space”下拉选项中找到这两种方法的快速链接:
点击后出现两种方法的示例代码:
下面详细讲解这两种嵌入托管Spaces方法。
8.5.1 使用 Web Components 嵌入
Web Components通常能为用户提供比 IFrames 更好的体验。Web Components 是延迟加载的,这意味着它们不会减慢网站的加载速度,并且它们会根据 Gradio 应用程序的大小自动调整高度。
1. 嵌入步骤
要使用 Web Components 嵌入,需要两步:
- 通过将以下脚本添加到网页中,从而将 Gradio JS 库导入到网站中,注意将URL中的 {GRADIO_VERSION} 替换为自己使用的 Gradio 库版本。
<script type="module"
src="https://gradio.s3-us-west-2.amazonaws.com/{GRADIO_VERSION}/gradio.js"></script>
- 在希望放置应用程序的位置添加<gradio-app> 元素,将src属性设置为要嵌入Space的URL(可以在"Embed this Space"按钮中找到该URL),示例如下:
<gradio-app src="https://$your_space_host.hf.space"></gradio-app>
具体嵌入示例代码可参照上面截图内容。另外我们还可以在 Gradio 主页上看到 Web Components 的示例:
2. 定义嵌入的外观和行为
我们还可以通过传递给 <gradio-app> 标签的属性来自定义Web组件的外观和行为:
- src:正如前面所述,src属性链接到要嵌入的托管Gradio演示的URL;
- space:如果Gradio演示托管在Hugging Face Space上,则这是一个可选的简写形式,它接受 username/space_name而不是完整的URL。例如:gradio/Echocardiogram-Segmentation。如果提供了此属性,则不需要提供src;
- control_page_title:一个布尔值,默认为 false,指定页面的HTML标题是否应设置为Gradio应用的标题;
- initial_height:Web组件在加载Gradio应用时的初始高度,默认为300px,最终高度是根据Gradio应用的大小设置的;
- container:是否显示边框框架和有关Space托管位置的信息(默认为 true);
- info:是否仅在嵌入的应用下方显示有关Space托管位置的信息(默认为 true);
- autoscroll:是否在预测完成后自动滚动到输出(默认为 false);
- eager:是否在页面加载时立即加载Gradio应用(默认为 false);
- theme_mode:是否使用深色、浅色或默认的系统主题模式(默认为 system);
- render:一个事件,在嵌入的Space完成渲染时触发。
以下是一个如何使用这些属性创建不延迟加载且初始高度为0px的 Gradio 应用的示例:
<gradio-app
space="gradio/Echocardiogram-Segmentation"
eager="true" # 立即启动
initial_height="0px"
></gradio-app>
以下是另一个如何使用render事件的示例,它使用事件监听器捕获render事件,并在渲染完成后调用 handleLoadComplete()函数:
<script>
function handleLoadComplete() {
console.log("Embedded space has finished rendering");
}
const gradioApp = document.querySelector("gradio-app");
gradioApp.addEventListener("render", handleLoadComplete);
</script>
注意:虽然Gradio的CSS永远不会影响嵌入页面,但嵌入页面可能会影响嵌入的Gradio应用的样式。所以应确保父页面中的任何CSS不会过于通用,以至于也适用于嵌入的Gradio应用并导致样式破坏,像header{ … }和footer{ … }这样的元素选择器最有可能引起这种问题。
8.5.2 使用IFrame嵌入
如果无法在网站中添加JavaScript,可以使用IFrame来嵌入Gradio应用。在网页中添加以下元素:
<iframe src="https://$your_space_host.hf.space"></iframe>
同样可以在"Embed this Space"按钮网页中找到Space的嵌入URL,并将其作为src属性。
注意:当使用IFrame时,可能需要添加一个固定的高度属性,并设置style="border:0;"以去除边框。此外,如果应用需要访问摄像头或麦克风等权限,还需要使用allow属性来提供这些权限。
8.6 访问网络请求和Analytics应用分析
8.6.1 直接访问网络请求
当用户向应用程序发出预测请求时,我们可能需要获取底层的网络请求,以便获取请求头(例如用于高级身份验证)、记录客户端的IP地址、获取查询参数或其他原因。Gradio以类似于FastAPI的方式支持这一功能:只需添加一个类型提示为gr.Request的函数参数,Gradio就会将网络请求作为该参数传递进来。以下是一个示例:
import gradio as gr
def echo(text, request: gr.Request):
if request:
print("Request headers dictionary:", request.headers)
print("IP address:", request.client.host)
print("Query parameters:", dict(request.query_params))
return text
io = gr.Interface(echo, "textbox", "textbox").launch()
将上面代码保存在request.py文件中,在命令行输入python request.py(注意环境中需安装gradio),会在后台输出以下内容:
Request headers dictionary: Headers({'host': '127.0.0.1:7860', 'user-agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0', 'accept': '*/*', 'accept-language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2', 'accept-encoding': 'gzip, deflate, br, zstd', 'referer': 'http://127.0.0.1:7860/', 'content-type': 'application/json', 'content-length': '91', 'origin': 'http://127.0.0.1:7860', 'connection': 'keep-alive', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'priority': 'u=4'})
IP address: 127.0.0.1
Query parameters: {}
注意:如果函数是直接调用而不是通过UI调用,例如当示例被缓存时,或者当 Gradio 应用程序通过 API 调用时,那么request将为None。此时应该显式处理这种情况,以确保应用程序不会抛出任何错误,这就是为什么我们显式检查if request。
8.6.2 Analytics:应用分析
默认情况下,Gradio会收集某些应用分析数据,以帮助我们更好地了解 gradio 库的使用情况。这包括以下信息:
- Gradio应用程序运行的环境(例如 Colab Notebook、Hugging Face Spaces);
- Gradio应用程序中使用的输入/输出组件;
- Gradio应用程序是否使用了某些高级功能,例如身份验证或显示错误;
- 仅用于衡量使用Gradio的独立开发者数量的IP地址;
- 正在运行的Gradio版本。
Gradio不会从应用程序的用户那里收集任何信息,但如果我们想完全禁用分析,可以通过在 gr.Blocks、gr.Interface或gr.ChatInterface中将analytics_enabled参数设置为 False 来实现。或者,您可以将 GRADIO_ANALYTICS_ENABLED 环境变量设置为"False",以将此设置应用于系统上创建的所有Gradio应用程序。
注意:这里反映的是gradio>=4.32.0版本的Analytics政策。
8.7 授权OAuth
8.7.1 OAuth:通过Hugging Face登录
Gradio原生支持通过Hugging Face进行OAuth登录。换句话说,我们可以轻松地在演示中添加一个"Sign in with Hugging Face"按钮,这样就可以获取用户的HF用户名以及其HF个人资料中的其他信息。查看这个Space以获取实时演示。
要启用OAuth,必须在部署文件README.md中设置Space元数据hf_oauth: true,这会在Hugging Face上将我们的Space注册为一个OAuth应用程序。接下来,我们可以使用gr.LoginButton在Gradio应用中添加一个登录按钮。一旦用户使用他们的HF账户登录,我们就可以通过在任何Gradio函数中添加一个gr.OAuthProfile类型的参数来获取他们的个人资料,用户个人资料将自动作为参数值注入。如果我们想代表用户执行操作(例如列出用户的私有仓库、创建仓库等),可以通过添加一个gr.OAuthToken 类型的参数来获取用户令牌token。同时我们必须在Space元数据中定义将使用的范围,详见文档:Adding a Sign-In with HF button to your Space。
元数据示例如下:
title: Gradio Oauth Test
emoji: 🏆
colorFrom: pink
colorTo: pink
sdk: gradio
sdk_version: 3.40.0
python_version: 3.10.6
app_file: app.py
hf_oauth: true
# optional, default duration is 8 hours/480 minutes. Max duration is 30 days/43200 minutes.
hf_oauth_expiration_minutes: 480
# optional, see "Scopes" below. "openid profile" is always included.
hf_oauth_scopes:
- read-repos
- write-repos
- manage-repos
- inference-api
以下是一个授权登录的简短示例:
from __future__ import annotations
import gradio as gr
from huggingface_hub import whoami
def hello(profile: gr.OAuthProfile | None) -> str:
if profile is None:
return "I don't know you."
return f"Hello {profile.name}"
def list_organizations(oauth_token: gr.OAuthToken | None) -> str:
if oauth_token is None:
return "Please deploy this on Spaces and log in to list organizations."
org_names = [org["name"] for org in whoami(oauth_token.token)["orgs"]]
return f"You belong to {', '.join(org_names)}."
with gr.Blocks() as demo:
gr.LoginButton()
m1 = gr.Markdown()
m2 = gr.Markdown()
demo.load(hello, inputs=None, outputs=m1)
demo.load(list_organizations, inputs=None, outputs=m2)
demo.launch()
当使用个人的HF帐号登陆后,会出现如下信息:
用户可以随时登出以在他们的设置中撤销对其个人资料的访问权限。
如上所述,OAuth功能仅当我们的应用在Space中运行时可用。然而,我们通常需要在部署之前在本地测试应用,要在本地测试OAuth功能,需要本地机器必须登录到 Hugging Face,这可通过运行 huggingface-cli login 或将环境变量HF_TOKEN设置为自己的访问令牌实现。我们可以在设置页面(https://huggingface.co/settings/tokens)生成一个新令牌,然后点击gr.LoginButton将登录本地用户的Hugging Face个人资料,这允许我们在将应用部署到Space之前使用个人的Hugging Face账户调试应用。
安全注意事项:添加gr.LoginButton并不会像添加username-password认证那样限制用户使用我们的应用。这意味着未使用Hugging Face登录的用户仍然可以访问并运行Gradio应用中的事件——区别在于:在相应的函数中,gr.OAuthProfile或gr.OAuthToken将为None。
8.7.2 OAuth:使用外部提供商
我们还可以在Gradio应用中使用外部OAuth提供商(例如Google OAuth)进行身份验证。要做到这一点,首先将Gradio应用挂载到某个FastAPI应用中(见Gradio的部署),然后必须编写一个身份验证函数,该函数从OAuth提供商获取用户的用户名并返回它。此函数应传递给 gr.mount_gradio_app中的auth_dependency参数。
与FastAPI依赖函数类似,由auth_dependency指定的函数将在执行FastAPI应用中任何与Gradio相关的路由之前运行。该函数应接受一个参数:FastAPI的Request,并返回一个字符串:表示用户的用户名或None。如果返回一个字符串,用户将能够访问FastAPI应用中的与Gradio相关的路由。
首先,我们展示一个简单的示例来说明auth_dependency参数:
from fastapi import FastAPI, Request
import gradio as gr
app = FastAPI()
def get_user(request: Request):
return request.headers.get("user")
demo = gr.Interface(lambda s: f"Hello {s}!", "textbox", "textbox")
app = gr.mount_gradio_app(app, demo, path="/demo", auth_dependency=get_user)
if __name__ == '__main__':
uvicorn.run(app)
在这个例子中,只有包含“user”标头的request才允许访问Gradio应用程序。当然,这并不会增加太多的安全性,因为任何用户都可以在他们的请求中添加此标头。关于此示例的运行方法请参考Gradio部署中的FastAPI挂载。
以下是一个更完整的示例,显示了如何将Google OAuth添加到Gradio应用程序中(假设已经在Google Developer Console上创建了OAuth凭据):
import os
from authlib.integrations.starlette_client import OAuth, OAuthError
from fastapi import FastAPI, Depends, Request
from starlette.config import Config
from starlette.responses import RedirectResponse
from starlette.middleware.sessions import SessionMiddleware
import uvicorn
import gradio as gr
app = FastAPI()
# Replace these with your own OAuth settings
GOOGLE_CLIENT_ID = "..."
GOOGLE_CLIENT_SECRET = "..."
SECRET_KEY = "..."
config_data = {'GOOGLE_CLIENT_ID': GOOGLE_CLIENT_ID, 'GOOGLE_CLIENT_SECRET': GOOGLE_CLIENT_SECRET}
starlette_config = Config(environ=config_data)
oauth = OAuth(starlette_config)
oauth.register(
name='google',
server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
client_kwargs={'scope': 'openid email profile'},
)
SECRET_KEY = os.environ.get('SECRET_KEY') or "a_very_secret_key"
app.add_middleware(SessionMiddleware, secret_key=SECRET_KEY)
# Dependency to get the current user
def get_user(request: Request):
user = request.session.get('user')
if user:
return user['name']
return None
@app.get('/')
def public(user: dict = Depends(get_user)):
if user:
return RedirectResponse(url='/gradio')
else:
return RedirectResponse(url='/login-demo')
@app.route('/logout')
async def logout(request: Request):
request.session.pop('user', None)
return RedirectResponse(url='/')
@app.route('/login')
async def login(request: Request):
redirect_uri = request.url_for('auth')
# If your app is running on https, you should ensure that the
# `redirect_uri` is https, e.g. uncomment the following lines:
#
# from urllib.parse import urlparse, urlunparse
# redirect_uri = urlunparse(urlparse(str(redirect_uri))._replace(scheme='https'))
return await oauth.google.authorize_redirect(request, redirect_uri)
@app.route('/auth')
async def auth(request: Request):
try:
access_token = await oauth.google.authorize_access_token(request)
except OAuthError:
return RedirectResponse(url='/')
request.session['user'] = dict(access_token)["userinfo"]
return RedirectResponse(url='/')
with gr.Blocks() as login_demo:
gr.Button("Login", link="/login")
app = gr.mount_gradio_app(app, login_demo, path="/login-demo")
def greet(request: gr.Request):
return f"Welcome to Gradio, {request.username}"
with gr.Blocks() as main_demo:
m = gr.Markdown("Welcome to Gradio!")
gr.Button("Logout", link="/logout")
main_demo.load(greet, None, m)
app = gr.mount_gradio_app(app, main_demo, path="/gradio", auth_dependency=get_user)
if __name__ == '__main__':
uvicorn.run(app)
在这个例子中,实际上有两个单独的Gradio应用程序!一个简单地显示登录按钮(任何用户都可以访问此演示),而另一个主演示仅对已登录的用户可用。我们可以在Space上尝试此示例:https://huggingface.co/spaces/gradio/oauth-example。
使用google账户登录后运行截图如下:
8.8 安全访问文件
当我们通过托管在Spaces、自己的服务器或临时共享链接共享Gradio 应用程序时,会将机器上的某些文件暴露在互联网上。本节解释了哪些文件会被暴露,以及确保机器上文件安全的一些最佳实践。
8.8.1 Gradio文件访问限制
Gradio文件访问分为允许用户访问的文件和不允许他人访问的文件。
1. Gradio允许用户访问的文件
Gradio 允许用户访问的文件有以下三种:
- 通过 gr.set_static_paths 函数显式设置的静态文件。默认情况下,此参数为空列表,它可以传入一个目录或文件列表,这些文件将被视为静态文件。这意味着它们不会被复制到缓存中,而是直接从计算机读取。这有助于节省磁盘空间并减少应用程序启动时间,但请注意可能的安全隐患;
- launch() 函数中参数 allowed_paths 中的文件。默认情况下,此参数为空列表,它可传入一个额外的目录或确切文件路径列表,表示允许用户访问这些文件;
- Gradio 缓存中的文件。在启动 Gradio 应用程序后,Gradio 会将某些文件复制到临时缓存中,并使这些文件可供用户访问。我们将在下面更详细地解释这一点。
2. Gradio不允许他人访问的文件
在运行时,Gradio 应用程序不会允许用户访问以下文件:
- 通过 launch() 函数中的参数 blocked_paths 显式阻止的文件。您可以向 launch() 函数中的 blocked_paths 参数传入一个额外的目录或确切文件路径列表。此参数优先于 Gradio 默认暴露的文件,或通过 allowed_paths 参数或 gr.set_static_paths 函数暴露的文件。
- 主机上的任何其他路径。用户不应能够访问主机上的任何其他路径。
8.8.2 Gradio缓存
1. 缓存作用
首先,了解 Gradio 为什么会有缓存是很重要的。Gradio 在将文件返回给前端之前,会将它们复制到缓存目录中。这可以防止文件被某个用户覆盖,因为应用程序的其他用户仍然需要这些文件。例如,如果预测函数返回一个视频文件,Gradio 会在预测函数运行后将该视频移动到缓存中,并返回一个前端可以用来显示视频的 URL。缓存中的任何文件都可以通过 URL 方式提供给所有正在运行应用程序用户。
提示:我们可以通过将 GRADIO_TEMP_DIR 环境变量设置为绝对路径(例如 /home/usr/scripts/project/temp/)来自定义缓存的位置。
2. Gradio移动到缓存的文件
Gradio 会将三种类型的文件移动到缓存中:
- 开发者在运行前指定的文件,例如缓存的示例、组件的默认值或传递给参数的文件(如 gr.Chatbot 的 avatar_images)。
- Gradio 应用程序中预测函数返回的文件路径,且满足以下条件之一:(1)它们在 Blocks.launch 方法的 allowed_paths 参数中;(2)它们在 Python 解释器的当前工作目录中;(3)它们在通过 tempfile.gettempdir() 获取的临时目录中。如果这些条件都不满足,返回该文件的预测函数将引发异常,而不是将文件移动到缓存中。Gradio 执行此检查是为了确保机器上的任意文件不会被非法访问。
- 用户上传到 Gradio 应用程序的文件(例如通过 File 或 Image 输入组件)。
需要注意的是:
- 对于第二种类型的文件,当前工作目录中以句点(.)开头的文件不会被移动到缓存中,即使它们是从预测函数返回的,因为它们通常包含敏感信息。
- 如果 Gradio 拒绝了我们希望它处理的文件,请将其路径添加到参数 allowed_paths 中。
8.8.3 文件上传
1. 设置上传限制
当我们共享 Gradio 应用程序时,通常还会允许用户将文件上传到计算机或服务器。这时可以设置上传文件的最大大小,以防止滥用并节省磁盘空间。我们可以通过函数 .launch 的 max_file_size 参数来实现这一点。例如以下两个代码片段将每个文件的上传限制为 5 兆字节:
import gradio as gr
demo = gr.Interface(lambda x: x, "image", "image")
demo.launch(max_file_size="5mb")
# or
demo.launch(max_file_size=5 * gr.FileSize.MB)
2. 最佳实践
为了确保安全访问文件,有4种最佳实践方法:
- 为Gradio应用程序设置 max_file_size。
- 不要从连接到基于文件的输出组件(如 gr.Image、gr.File 等)的函数中返回任何用户输入。例如,以下代码将允许任何人将本地目录中的任意文件移动到缓存中:gr.Interface(lambda s: s, “text”, “file”),这是因为用户输入被视为任意的文件路径。
- 尽可能缩小 allowed_paths 的范围。如果 allowed_paths 中的路径是目录,则该目录中的任何文件都可以被访问。确保 allowed_paths 中的条目仅包含与应用程序相关的文件。
- 从应用程序文件所在的目录运行 Gradio 应用程序,这将缩小 Gradio 允许移动到缓存中文件的范围。例如启动命令优先使用 python app.py 而不是 python Users/sources/project/app.py。
8.9 资源清理
Gradio 应用程序在其生命周期中可能会创建资源。资源示例包括 gr.State 变量、创建并显式保存在内存中的任何变量或保存到磁盘的文件。随着时间的推移,这些资源可能会耗尽服务器的所有 RAM 或磁盘空间,并导致应用程序崩溃,所以需要定期清理临时资源。
8.9.1 清理方法
Gradio 提供了一些方法来帮助您清理应用程序创建的资源:自动删除 gr.State 变量、使用 delete_cache 参数自动清理缓存和使用Blocks.unload 事件。让我们分别来看一下这些工具。
1. 自动删除 gr.State
当用户关闭浏览器标签页时,Gradio 将在 60 分钟后自动删除与该用户会话关联的所有 gr.State 变量。如果用户在 60 分钟内重新连接,则不会删除任何 gr.State。
我们可以通过下面 gr.State 的两个参数进一步控制删除行为:
- delete_callback:可以是任意函数,当变量被删除时将调用该函数。此函数必须将状态值作为输入。此函数对于从 GPU 内存中删除变量非常有用。
- time_to_live:状态在创建或更新后应存储的秒数。这将在会话关闭之前删除变量,因此对于清理可能长时间运行的会话的状态非常有用。
2. 通过 delete_cache 自动清理缓存
Gradio 应用程序会将上传和生成的文件保存到一个名为cache的特殊目录中。Gradio 使用哈希方案来确保不会将重复文件保存到缓存中,但随着时间的推移,尤其是当应用程序变得非常流行时,缓存的大小仍会不断增长。
如果启动时指定了 gr.Blocks()、gr.Interface() 或 gr.ChatInterface() 的 delete_cache 参数,那么Gradio 可以定期为您清理缓存。此参数是一个形式为 [frequency, age] 的元组,两者均以秒为单位表示。此参数表示每过 frequency 秒,如果文件创建时间超过 age 秒,则会删除此 Blocks 实例创建的临时文件。例如,将其设置为 (86400, 86400) 将会每天删除超过一天的文件。此外,缓存将在服务器重启时完全删除。
3. unload 事件
此外,Gradio 现在包含一个 Blocks.unload() 事件,它允许Gradio程序在用户断开连接时运行任意清理函数,此时不会有 60 分钟的延迟。与其他 Gradio 事件不同,此事件不接受输入或输出。我们可以将 unload 事件视为 load 事件的反向操作。
8.9.2 综合清理演示
以下演示使用了上面列举的所有功能。当用户访问页面时,会为该用户创建一个特殊的唯一目录:当用户与应用程序交互时,图像会被保存到该特殊目录中;当用户关闭页面时,该会话中创建的图像将通过 unload 事件删除,状态和缓存中的文件也会立即自动清理。代码如下:
from __future__ import annotations
import gradio as gr
import numpy as np
from PIL import Image
from pathlib import Path
import secrets
import shutil
current_dir = Path(__file__).parent
def generate_random_img(history: list[Image.Image], request: gr.Request):
"""Generate a random red, green, blue, orange, yellor or purple image."""
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 165, 0), (255, 255, 0), (128, 0, 128)]
color = colors[np.random.randint(0, len(colors))]
img = Image.new('RGB', (100, 100), color)
user_dir: Path = current_dir / str(request.session_hash)
user_dir.mkdir(exist_ok=True)
path = user_dir / f"{secrets.token_urlsafe(8)}.webp"
img.save(path)
history.append(img)
return img, history, history
def delete_directory(req: gr.Request):
if not req.username:
return
user_dir: Path = current_dir / req.username
shutil.rmtree(str(user_dir))
with gr.Blocks(delete_cache=(60, 3600)) as demo:
gr.Markdown("""# State Cleanup Demo
🖼️ Images are saved in a user-specific directory and deleted when the users closes the page via demo.unload.
""")
with gr.Row():
with gr.Column(scale=1):
with gr.Row():
img = gr.Image(label="Generated Image", height=300, width=300)
with gr.Row():
gen = gr.Button(value="Generate")
with gr.Row():
history = gr.Gallery(label="Previous Generations", height=500, columns=10)
state = gr.State(value=[], delete_callback=lambda v: print("STATE DELETED"))
demo.load(generate_random_img, [state], [img, state, history])
gen.click(generate_random_img, [state], [img, state, history])
demo.unload(delete_directory)
demo.launch()
运行截图如下:
参考文献
- Gradio - guides - Additional Features