gradio-mlp
npx skills add https://github.com/oneboxcream/claude-code-setup --skill gradio-mlp
Agent 安装分布
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}",
)
注æäºé¡¹
- 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 å¹³å°å¤äººå ±ç¨ï¼æ³¨æä½¿ç¨ä¸å端å£ï¼å¯å¨åå»ºè®®æ£æµç«¯å£å ç¨