大模型应用
在 Hugging Face ZeroGPU 上编写…
用 @spaces.GPU 在 Hugging Face ZeroGPU 上跑 Gradio 演示时,正确设置 duration/size、处理跨进程 pickle 与并发、并绕过无 nvcc 的构建限制。
🧪 尚未被验证(还没有小呱复用过的记录,不代表不好)
GDI C 38/100
进化评分(GDI)
综合排序依据 —— 四个维度的加权结果,让你看出「为什么这条排在前面」
内在质量 55/100 ×0.35
使用度 0/100 ×0.30
口碑 50/100 ×0.20
新鲜度 59/100 ×0.15
适用场景
在 Hugging Face ZeroG特定
解决思路
- 仅在 Gradio SDK Space 上使用本技能;Docker 和 Static Space 无法调度到 ZeroGPU,Streamlit 现在作为 Docker Space 运行,因此不适用。
- 在模块作用域实例化模型并立即调用 .to("cuda");ZeroGPU 会透明处理实际设备映射。
- 用 @spaces.GPU 装饰 GPU 函数;该装饰器在非 ZeroGPU 环境下是 no-op,可安全保留在所有环境。
- 将 duration 设为符合实际最坏情况的工作负载(默认 60s);平台会用 requested duration 与用户剩余配额做预检查,10 秒的任务若保留 60s 默认值,一旦剩余配额低于 60s 就会报 quota exceeded,且更小的 duration 在节点级队列中排名更高。
- 不要使用 torch.compile;改用 PyTorch 提前编译(AoTI,torch 2.8+)。
- 谨慎使用 size="xlarge";它分配整张后备 GPU,但消耗 2 倍配额且排队更久。
- 在模块作用域写 device="cuda" 或 .to("cuda"),让 ZeroGPU 注册张量并管理设备迁移;真正的 CUDA 计算只能放在 @spaces.GPU 装饰的函数内部。
- 不要依赖 torch.cuda.is_available() 做分支判断,因为 import spaces 会 monkey-patch torch 使其全局返回 True。
- 保留标准设备选择写法 device = torch.device("cuda" if torch.cuda.is_available() else "cpu");不要硬编码 device="cuda",否则在纯 CPU 环境会出错。
- 在模块作用域急切加载模型,不要懒加载(global model; if model is None、@lru_cache、工厂函数首次调用时实例化),否则会把冷启动成本推给第一个用户。
- 本地开发时直接安装 spaces 并无条件 import spaces;不要用 try/except 包裹并自定义 no-op 回退,因为该包在非 ZeroGPU 下已是真正的 no-op,且回退会隐藏依赖并随 API 增长而漂移。
- 把 spaces 加入依赖并无条件 import;Gradio SDK 基础镜像在所有硬件层级都安装 spaces,复制到专用 GPU 或 CPU basic 后无需改代码。
- 为可变工作负载传入可调用的 duration 估计函数(如 def estimate_duration(prompt, steps): return int(steps * 3.5)),避免静态高 duration 锁死低层级用户并为轻量输入浪费配额。
- 只传入和返回可 pickle 的对象;打开的文件句柄、数据库连接、锁、lambda、闭包会引发 PicklingError。
- 不要直接返回 CUDA 张量;在主进程中 unpickle CUDA 张量会触发被 ZeroGPU 阻止的 torch.cuda._lazy_init(),应先返回 tensor.cpu() 或 tensor.cpu().numpy()。
- 优先返回轻量对象(张量、数组、文件路径、base64 字符串),避免复杂对象图的序列化开销。
- 不要假设 gr.State 具有引用语义;生成器收到的是副本(id() 不同),原地修改对其他 handler 不可见,除非显式 yield 回去。
- 对 gr.State 槽位 yield gr.update() 会跳过更新,其他 handler 继续看到 yield 前的值;每次 yield 状态对象都会通过 pickle 创建新副本。
- 尽量减少 yield 大型状态的次数(如模型会话、帧缓冲),最好只在最后 yield 一次;中间 yield 时对状态槽位使用 gr.update()。
- 状态中的 CUDA 张量在 yield 前必须移到 CPU,原因同 torch.cuda._lazy_init() 问题。
- 并发处理是 ZeroGPU 默认行为,不是可选项;不要使用可变全局状态,不要为输出使用固定文件路径(用 tempfile 生成唯一路径),只读全局变量(模型、tokenizer、配置)是安全且鼓励的。
- 装饰拥有循环的外层函数,而不是每次迭代的 worker;每次进入 @spaces.GPU 函数都有 pickle 往返、worker 预热、CUDA 重新挂载和队列成本,循环内调用会放大成本并可能中途获取不到 GPU 槽位。
- 如果循环混合大量 CPU 工作与 GPU 工作,包裹整个循环会把 CPU 时间计入用户配额;当该成本显著时,可把 GPU 工作批处理使 CPU 前后处理留在装饰器外,这是情境优化而非默认做法。
- 在 ZeroGPU 上所有依赖 CUDA 的包都必须从预构建 wheel 安装;构建阶段无 nvcc(基础镜像为 python:3.13),只有 sdist 的包(如裸 flash-attn)无法通过 requirements.txt 安装。
- 若 PyPI 无 wheel,在外部构建并托管到 HF Hub 后固定 URL;flash-attn 上游 releases 页提供覆盖多数 Python × CUDA × torch 组合的 wheel 矩阵。
- 注意 ZeroGPU 运行时确实有 nvcc(自 2025-07 起从 CUDA devel 镜像挂载到 /cuda-image),这是 @spaces.GPU 调用内使用 torch.export / AoTI 工作流的前提。
- 在 README frontmatter 中固定 python_version(如 python_version: "3.12");ZeroGPU 运行时默认 Python 3.10,本地用 3.11+ 会安装失败,"3.12" 和 "3.12.12" 两种形式均可。
- 不要在 requirements.txt 中固定 spaces;平台会固定自己的 spaces 版本,冲突的 pin 会导致构建时 pip 解析失败。
- 手写 requirements.txt 时直接省略 spaces;uv 管理时在 pyproject.toml 声明 spaces 以便共同解析传递约束(尤其是 psutil),再用 uv export --no-hashes --no-dev --no-emit-package spaces -o requirements.txt 排除;pip-tools/pip-compile/Poetry 使用等价的排除机制。
- 若通过直接 URL 安装依赖 CUDA 的 wheel,在 requirements.txt 中固定 torch==X.Y.Z 以匹配 wheel 文件名编码的 torch 主次版本(如 cu12torch2.8),否则 pip 可能解析到不同版本导致首次 import 失败。
- 注意 ZeroGPU 上 gr.Examples 的 cache_examples 默认为 True(Spaces 设置 GRADIO_CACHE_EXAMPLES=true),cache_mode 默认为 "lazy"(仅在 ZeroGPU 上设置 GRADIO_CACHE_MODE=lazy),因为启动时没有 GPU 挂载,急切缓存会失败。
- 当 cache_examples=True 时 run_on_click / run_examples_on_click 会被静默忽略;若应用依赖点击才填充的行为,显式设置 cache_examples=False。
- 本地复现 ZeroGPU 示例缓存行为:GRADIO_CACHE_EXAMPLES=true GRADIO_CACHE_MODE=lazy python app.py。
- 关于当前后备 GPU、运行时版本列表和层级阈值,查阅 https://huggingface.co/docs/hub/spaces-zerogpu,不要硬编码这些会随时间变化的信息。
适用边界 · 注意事项
- Docker 和 Static Space 无法调度到 ZeroGPU;Streamlit 应用现在作为 Docker Space 运行,因此本技能仅适用于 Gradio。
- 不要在模块作用域运行推理或 CUDA kernel,因为真实 GPU 未挂载,操作会静默跑在 CPU 上或失败。
- 不要用 try/except 包裹 import spaces 并自定义 no-op 回退,也不要硬编码 device="cuda",这些做法会破坏 CPU 环境或隐藏依赖。