如何用 Modal 部署 Gradio 应用并满足 sticky session 与并发约束
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
把 Gradio 应用部署到 Modal 时,有两个问题绕不开:Gradio 要求同一个客户端的所有请求必须路由到同一个容器(sticky session),而 Modal 的 serverless 函数并不承诺这一点;同时 Modal 和 Gradio 各自都有队列和并发限制,两层限制叠加后的行为需要明确。本文基于仓库中的官方教程 Deploying a Gradio app with Modal,给出从安装 Modal 客户端、编写应用文件到部署后验证的完整操作路径,并解释max_containers=1与@modal.concurrent(max_inputs=...)这两个关键配置如何满足 sticky session 与并发约束。
前提准备:安装并认证 Modal
开始之前需要一个 Modal 账号(若没有,可在 modal.com 注册),然后在本地开发环境安装客户端并用账号凭证认证:
pip install modalmodal setup注意:本地只需要安装modal客户端,gradio和fastapi不需要装在本地,它们会安装到下文定义的云容器镜像里。
定义容器镜像
新建文件gradio_app.py。Modal 的Image通过在一个Image实例上顺序调用方法来定义,本例中:
- 从
debian_slim基础镜像开始; - 选定 Python 3.12;
- 只安装
fastapi和gradio两个依赖。
import modal app = modal.App("gradio-app") web_image = modal.Image.debian_slim(python_version="3.12").uv_pip_install( "fastapi[standard]", "gradio", )用 Modal Function 包装 Gradio 应用
Gradio 应用通常是在脚本末尾调用demo.launch()启动的,而 Modal 不执行脚本,只执行函数——确切地说是 serverless 函数。教程中的应用逻辑是一个问候函数:
def greet(name): return f"Hello {name}!" demo = gr.Interface(fn=greet, inputs="text", outputs="text")要在 Modal 上提供这个demo,可以利用 Gradio 和 Modal 对fastapi应用的支持:用@modal.asgi_app()装饰器部署函数返回的 web 应用,并用mount_gradio_app把 Gradiodemo挂载为 web 应用的一个路由:
with web_image.imports(): import gradio as gr from gradio.routes import mount_gradio_app from fastapi import FastAPI @app.function( image=web_image, max_containers=1, # 作用见下文 sticky session 一节 ) @modal.concurrent(max_inputs=100) # 允许多个用户同时使用 @modal.asgi_app() def ui(): """A simple Gradio interface for a greeting function.""" def greet(name): return f"Hello {name}!" demo = gr.Interface(fn=greet, inputs="text", outputs="text") return mount_gradio_app(app=FastAPI(), blocks=demo, path="/")各部分的作用:
Image.imports上下文管理器中定义的 import,在函数于云端运行时可用;@app.function把ui包成一个 Modal serverless Function,镜像和其他参数作为装饰器输入提供;@modal.concurrent允许单个容器同时处理多个请求;@modal.asgi_app告诉 Modal 该函数提供的是一个 ASGI 应用(这里是fastapi应用)。使用该装饰器时,ASGI 应用必须是函数的返回值,所以这里返回mount_gradio_app(...)的结果。
max_containers=1和max_inputs=100为什么这样设置,在下文解释。
部署与验证
运行部署命令:
modal deploy <path-to-file>其中<path-to-file>替换为应用文件的实际路径,例如modal deploy gradio_app.py。
首次运行时 Modal 会构建并缓存镜像,约需 30 秒;只要不修改镜像定义,后续部署只需几秒。镜像构建完成后,Modal 会打印 web 应用的 URL 和 Modal 仪表盘 URL,文档给出的 webapp URL 形式示例为https://{workspace}-{environment}--gradio-app-ui.modal.run(文档示例)。把这个 URL 粘贴到浏览器中,输入名字并提交,应得到应用逻辑定义的问候回复,说明部署成功。
sticky session:为什么需要max_containers=1
Modal Function 是 serverless 的,每个客户端请求都被视为独立请求。这带来了自动扩缩容的便利,但也意味着应用如果需要某种服务端状态,就要格外注意。
Gradio 依赖的 REST API 本身是无状态的,但它要求 sticky session——来自同一客户端的所有请求必须被路由到同一个容器,而 Modal 在这方面不做任何保证。
满足这一约束的简单做法就是上面代码中的两处配置:
- 在
@app.function中设置max_containers=1,Modal 就不会为应用启动超过一个容器; - 把
@modal.concurrent的max_inputs参数设得比较大(示例中为 100),让这个唯一的容器可以并行处理多个请求。
两者组合后,所有请求都落到同一个容器上,从而事实上满足 Gradio 的 sticky session 要求。
理解两层并发:Modal 队列与 Gradio 队列
Modal 和 Gradio 都有队列与并发的概念,充分利用算力资源需要理解两者如何交互:
- Modal 层:Modal 把客户端请求排队到每个已部署的 Function,并直到该 Function 的并发上限为止同时执行请求。如果请求到来时并发上限已用满,Modal 会启动新容器,直到达到该 Function 设定的最大值。本例中 Gradio 应用由一个 Modal Function 表示,所有请求共享同一个队列和并发上限,因此 Modal 限制的是同一时刻正在运行的请求总数,与这些请求具体在做什么无关。
- Gradio 层:Gradio 允许开发者使用多个队列、每个队列有自己的并发上限,一个或多个事件监听器可以分配到某个队列,这对管理计算密集请求的 GPU 资源很有用。每个事件监听器的
concurrency_limit、用concurrency_id共享队列、以及Blocks.queue()中的default_concurrency_limit等具体配置,见 Gradio Queuing 文档。
教程提醒:仔细考虑这些队列和限制如何相互作用,有助于优化应用性能和资源使用,同时避免共享状态或状态丢失这类不想要的结果。
可选分支:把 GPU 计算拆成独立 Function
管理 GPU 利用率的一个替代方案:把 GPU 计算部署为独立的 Modal Function,并在 Gradio 应用内部调用这个远程 Function。这样既能充分利用 Modal 的 serverless 自动扩缩容,又能让所有客户端 HTTP 请求路由到单一的 Gradio CPU 容器,与上面的 sticky session 方案保持一致。
延伸阅读
- 教程完整原文:guides/11_other-tutorials/deploying-gradio-with-modal.md
mount_gradio_app的用法说明(含自定义路径挂载、认证):Sharing Your App,示例代码 demo/custom_path/run.pymount_gradio_app的函数签名定义在 gradio/routes.py
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考