gradio-mlp

📁 oneboxcream/claude-code-setup 📅 9 days ago
3
总安装量
3
周安装量
#58830
全站排名
安装命令
npx skills add https://github.com/oneboxcream/claude-code-setup --skill gradio-mlp

Agent 安装分布

amp 3
gemini-cli 3
github-copilot 3
codex 3
kimi-cli 3
cursor 3

Skill 文档

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/ 挂载盘

核心规则

1. 反向代理配置(必须)

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)

2. 图片展示

方式 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); }
"""

3. 视频展示

# 视频输出组件
gr.Video(label="Generated Video", interactive=False, autoplay=True)

# 视频输入
gr.Video(label="Input Video", sources=["upload"])

4. 常用 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=["720*1280", "480*832", "512*512"],
                    value="512*512", 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 >")

5. 大模型推理集成

# 全局模型变量 + 延迟加载(节省显存)
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)):
        ...

6. 启动模板

始终使用以下命令行参数模式:

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}",
    )

注意事项

  1. Gradio 启动慢: 约需 15 秒才能响应请求,测试时需等待
  2. Python 3.8 兼容: 不要使用 match/case、type 别名、X | Y 类型联合等 3.9+ 语法
  3. gr.HTML 中的图片/资源路径: 使用相对路径 ./file={绝对路径},不要用 {root_path}/file= 硬编码(MLP 实际代理路径很长,硬编码会 404)
  4. 中间件 content-length: FIX_ROOT_JS 中间件注入 JS 后 body 变大,必须移除原 content-length header,否则浏览器会一直卡在加载状态
  5. allowed_paths: 使用 FastAPI 方式时必须配置,否则无法访问本地文件
  6. 显存管理: 加载大模型前用 gc.collect() + torch.cuda.empty_cache() 清理
  7. 端口冲突: MLP 平台多人共用,注意使用不同端口;启动前建议检测端口占用