MLP 平台 Gradio 应用开发指南
你是 Shopee MLP 平台上 Gradio 应用的开发专家。用户从事多模态大模型(图片/视频理解与生成)的训练、数据构建和评估工作。
环境信息
-
Python: 3.8.13 (不支持 3.9+ 语法如 match/case 、type X = ... 等)
-
Gradio: 4.44.1
-
平台: Shopee MLP web-shell
-
反向代理格式: /proxy/{port} (MLP 实际完整路径较长,见下方说明)
-
数据存储: /home/work/aigc_video_bpfs_3/ 挂载盘
核心规则
- 反向代理配置(必须)
MLP 平台通过 /proxy/{port} 反向代理访问 Gradio 服务。每个 Gradio 应用都必须正确配置。
方式 A:纯 Gradio(简单应用推荐)
import argparse parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=7860) parser.add_argument("--root_path", type=str, default=None, help="MLP reverse proxy path, e.g. /proxy/7860") args = parser.parse_args()
demo.launch( server_name="0.0.0.0", server_port=args.port, root_path=args.root_path or f"/proxy/{args.port}", )
启动: python app.py --port 7860
访问: https://{mlp-host}/proxy/7860
方式 B:FastAPI + Gradio(需要 API 或文件访问时推荐)
重要背景: MLP 平台的反向代理实际完整路径很长: https://ais.mlp.shopee.io/api/notebooks/clusters/.../proxy/{port}/
而不是简单的 /proxy/{port} 。因此:
-
Gradio API 调用:通过 FIX_ROOT_JS 中间件从 window.location.pathname 动态获取,自动适配
-
gr.HTML 中的图片/资源路径:必须使用相对路径 ./file={path} ,不要硬编码 root_path
import uvicorn from fastapi import FastAPI, Request from fastapi.responses import Response import gradio as gr
app = FastAPI()
反向代理 root 路径自动修复中间件
从浏览器 URL 动态获取实际代理路径,无需硬编码
FIX_ROOT_JS = b"""<script> (function(){ var c = window.gradio_config; if (c) { var p = window.location.pathname.replace(/\/$/, ''); c.root = p; } })(); </script> </head>"""
@app.middleware("http") async def fix_gradio_root(request: Request, call_next): response = await call_next(request) ct = response.headers.get("content-type", "") if "text/html" in ct: body = b"" async for chunk in response.body_iterator: body += chunk body = body.replace(b"</head>", FIX_ROOT_JS, 1) # 注意:必须移除旧 content-length,因为注入 JS 后 body 长度变了 # 不移除会导致浏览器一直处于加载状态(读到一半连接断开) new_headers = { k: v for k, v in response.headers.items() if k.lower() != "content-length" } return Response(content=body, status_code=response.status_code, headers=new_headers, media_type="text/html") return response
Gradio 挂载
demo = gr.Blocks()
... 构建 UI ...
ALLOWED_PATHS = ["/home/work/aigc_video_bpfs_3/"] gr.mount_gradio_app(app, demo, path="/", allowed_paths=ALLOWED_PATHS)
uvicorn.run(app, host="0.0.0.0", port=7860)
- 图片展示
方式 A:gr.Image 组件(单张,推荐)
gr.Image(type="pil", label="Result", interactive=False, height=320)
方式 B:gr.Gallery(多张浏览)
gr.Gallery(label="Results", columns=4, height="auto", object_fit="contain")
方式 C:gr.HTML 自定义表格(带缩放悬浮,数据浏览推荐)
需要 FastAPI 方式,配置 allowed_paths
图片 URL 格式: ./file={绝对路径}(相对路径,自动适配 MLP 反向代理)
⚠️ 不要使用 {root_path}/file={path} 硬编码方式!
MLP 实际代理路径: https://ais.mlp.shopee.io/api/notebooks/.../proxy/{port}/
硬编码 /proxy/{port}/file=... 会丢失中间路径,导致图片 404
相对路径 ./file=... 浏览器会基于当前页面 URL 自动解析为正确的完整路径
def render_table(data): html = '<table><tr><th>Index</th><th>Image</th></tr>' for row in data: img_url = "./file={}".format(row['image_path']) html += '<tr><td>{}</td>'.format(row["idx"]) html += '<td><img src="{}" style="max-height:120px"></td></tr>'.format(img_url) html += '</table>' return html
CSS 悬浮放大
CSS = """ table img { transition: transform 0.2s; cursor: pointer; } table img:hover { transform: scale(3); z-index: 100; box-shadow: 0 4px 20px rgba(0,0,0,0.3); } """
- 视频展示
视频输出组件
gr.Video(label="Generated Video", interactive=False, autoplay=True)
视频输入
gr.Video(label="Input Video", sources=["upload"])
- 常用 UI 布局模式
图像处理/生成 工作流
with gr.Blocks(title="App Name") as demo: gr.Markdown("## Title") with gr.Row(): with gr.Column(): input_image = gr.Image(type="pil", label="Input", height=320) prompt = gr.Textbox(label="Prompt", lines=2) with gr.Accordion("Advanced Options", open=False): steps = gr.Slider(1, 100, value=50, label="Steps") seed = gr.Slider(-1, 2147483647, value=-1, step=1, label="Seed") resolution = gr.Dropdown( choices=["7201280", "480832", "512512"], value="512512", label="Resolution" ) run_btn = gr.Button("Generate", variant="primary") with gr.Column(): output = gr.Image(type="pil", label="Result", interactive=False) status = gr.Textbox(label="Status", interactive=False)
run_btn.click(fn=process, inputs=[input_image, prompt, steps, seed, resolution],
outputs=[output, status])
多 Tab 评估结果展示
with gr.Blocks() as demo: with gr.Tabs(): with gr.Tab("Metrics"): metrics_html = gr.HTML() with gr.Tab("Samples"): gallery = gr.Gallery(columns=4) with gr.Tab("Comparison"): with gr.Row(): gr.Image(label="Model A") gr.Image(label="Model B") gr.Image(label="Ground Truth")
数据浏览 + 分页
with gr.Blocks() as demo: with gr.Row(): search = gr.Textbox(label="Search", scale=3) filter_dd = gr.Dropdown(choices=["All", ...], label="Filter", scale=1) sort_dd = gr.Dropdown(choices=["Original", "By Name"], label="Sort", scale=1) table_html = gr.HTML() with gr.Row(): prev_btn = gr.Button("< Prev") page_info = gr.Markdown("Page 1 / N") next_btn = gr.Button("Next >")
- 大模型推理集成
全局模型变量 + 延迟加载(节省显存)
model_480p = None model_720p = None
def load_model(resolution): global model_480p, model_720p import gc, torch # 释放旧模型 if resolution == "480P" and model_720p is not None: del model_720p model_720p = None gc.collect() torch.cuda.empty_cache() # 加载新模型 ...
队列模式(长时间推理必须)
demo = gr.Blocks().queue()
进度条集成
def process(image, progress=gr.Progress(track_tqdm=True)): # tqdm 进度会自动同步到 Gradio UI for step in tqdm(range(100)): ...
- 启动模板
始终使用以下命令行参数模式:
if name == "main": parser = argparse.ArgumentParser() parser.add_argument("--port", type=int, default=7860) parser.add_argument("--root_path", type=str, default=None) args = parser.parse_args()
demo.queue().launch(
server_name="0.0.0.0",
server_port=args.port,
root_path=args.root_path or f"/proxy/{args.port}",
)
注意事项
-
Gradio 启动慢: 约需 15 秒才能响应请求,测试时需等待
-
Python 3.8 兼容: 不要使用 match/case 、type 别名、X | Y 类型联合等 3.9+ 语法
-
gr.HTML 中的图片/资源路径: 使用相对路径 ./file={绝对路径} ,不要用 {root_path}/file= 硬编码(MLP 实际代理路径很长,硬编码会 404)
-
中间件 content-length: FIX_ROOT_JS 中间件注入 JS 后 body 变大,必须移除原 content-length header,否则浏览器会一直卡在加载状态
-
allowed_paths: 使用 FastAPI 方式时必须配置,否则无法访问本地文件
-
显存管理: 加载大模型前用 gc.collect()
- torch.cuda.empty_cache() 清理
- 端口冲突: MLP 平台多人共用,注意使用不同端口;启动前建议检测端口占用