| # Modal 部署与模型选择 |
|
|
| ## 目标 |
|
|
| Modal 负责承载重模型和 GPU 推理,Hugging Face Space / Gradio 只负责交互界面、状态编排和轻量逻辑。 |
|
|
| 目标不是把所有模型常驻在 GPU 上,而是按需调用、缓存权重、控制冷启动和并发,尽量节省 hackathon 额度。 |
|
|
| ```text |
| Gradio Space |
| -> Modal LLM / VLM endpoint |
| -> Modal TTS endpoint |
| -> Modal image generation endpoint |
| ``` |
|
|
| ## 当前模型判断 |
|
|
| ### Google / Gemma 系列 |
|
|
| 截至 2026-06-11,Google 在 Hugging Face 上有最新的 Gemma 4 12B 规格: |
|
|
| - `google/gemma-4-12B-it` |
| - `google/gemma-4-12B` |
|
|
| 其中 `google/gemma-4-12B-it` 是 Gemma 4 12B Unified instruction-tuned 模型。它支持文本、图片、音频、视频输入并生成文本输出,许可证是 Apache-2.0。这个模型非常适合本项目:它可以同时处理角色对话、图片理解和视频片段理解。当前页面先保留文字 + 图片输入,不接语音输入链路。 |
|
|
| 当前官方 Hugging Face 页面核到的 Gemma 4 规格是: |
|
|
| - `google/gemma-4-E2B-it` |
| - `google/gemma-4-E4B-it` |
| - `google/gemma-4-12B-it` |
| - `google/gemma-4-26B-A4B-it` |
| - `google/gemma-4-31B-it` |
|
|
| 关键差异: |
|
|
| - E2B / E4B:支持文本、图片、音频输入,适合“音频直接进模型”的实验。 |
| - 12B Unified:支持文本、图片、音频、视频输入;约 11.95B 参数,256K context;encoder-free unified multimodal 架构,适合作为主力中等规模模型。 |
| - 26B A4B:MoE,总参数约 26B,活跃参数约 4B,支持文本和图片,适合主对话、角色推理、视觉理解,但不支持原生音频输入。 |
| - 31B:文本和图片能力强,但更贵,不适合作为默认在线模型。 |
|
|
| 结论: |
|
|
| - `google/gemma-4-12B-it` 应该作为当前首选主模型候选。 |
| - 当前改版暂不提供语音输入,先集中验证文字、图片和 TTS 输出。 |
| - 如果后续重新评估语音输入,优先单独做一轮 Gemma 音频输入实验,不并入当前 TTS 交付。 |
| - 如果想要更强文本/图像推理质量,再试 `gemma-4-26B-A4B-it`。 |
| - 不建议默认上 31B,除非做离线评估或最终 demo 高质量路线。 |
|
|
| ### TTS 候选 |
|
|
| #### Chatterbox |
|
|
| 推荐作为第一优先级实验。 |
|
|
| 原因: |
|
|
| - 支持多语言,包括中文。 |
| - 支持情绪夸张度 / intensity control。 |
| - Hugging Face 模型页标 MIT。 |
| - Modal 官方有 Chatterbox TTS API 示例。 |
| - 很适合角色项目,因为可以把模型输出的 `voice.energy` / `emotion` 映射成 exaggeration、cfg、语速等参数。 |
|
|
| 注意: |
|
|
| - 有 voice cloning 能力,但公开 demo 不要克隆商业角色或真实声优。 |
| - 可先使用内置 voice prompt 或原创 voice prompt。 |
|
|
| #### Kokoro-82M |
|
|
| 只适合作为省额度 fallback,不建议作为中文角色主 TTS。 |
|
|
| 原因: |
|
|
| - 82M,非常轻。 |
| - Apache-2.0。 |
| - 推理成本低,速度快。 |
|
|
| 限制: |
|
|
| - Hugging Face 模型页顶部标签偏 English,虽然模型事实里写 v1.0 是多语言。 |
| - 中文路径依赖额外 G2P 包,实测暴露 `ordered_set` / `pypinyin` 等间接依赖问题。 |
| - 角色表现力和中文稳定性都不如 Chatterbox Multilingual。 |
|
|
| #### Dia-1.6B |
|
|
| 适合作为表达力实验,不建议作为第一默认。 |
|
|
| 原因: |
|
|
| - 能生成对话式 TTS。 |
| - 支持笑声、叹气等非语言表达。 |
|
|
| 限制: |
|
|
| - 模型页说明当前主要支持英文生成。 |
| - 需要约 10GB VRAM。 |
| - 也有 voice cloning 能力,公开 demo 要避免身份滥用。 |
|
|
| #### Sesame CSM-1B |
|
|
| 可作为研究候选。 |
|
|
| 原因: |
|
|
| - Conversational Speech Model,支持文本和音频上下文。 |
| - Transformers 已支持。 |
|
|
| 限制: |
|
|
| - 模型 gated,需要接受访问条件。 |
| - 上手和稳定性要单独验证。 |
|
|
| ### 语音输入策略 |
|
|
| 当前实现不提供语音输入,也不部署独立转写服务。第一阶段只做文字输入、图片输入和 TTS 输出,避免把交互问题和模型冷启动问题混在一起。 |
|
|
| ### 生图候选 |
|
|
| 推荐先用 FLUX.1-schnell。 |
|
|
| 原因: |
|
|
| - 适合快速生成角色头像、半身像、背景。 |
| - Modal 官方有 Flux on H100 示例。 |
| - `schnell` 步数少,适合按需生成。 |
|
|
| 使用原则: |
|
|
| - 不在每轮对话里调用。 |
| - 只在角色资产生成、重绘、创建自定义角色时调用。 |
| - 生成后缓存到本地或 Modal Volume,Gradio 直接读缓存图。 |
|
|
| ## Modal 服务拆分 |
|
|
| 建议拆成多个 Modal app 或多个 class,避免一个容器装所有模型。 |
|
|
| ### 1. `modal_llm.py` |
| |
| 用途: |
| |
| - 角色对话。 |
| - 图片理解。 |
| - 输出 SSE 事件流。 |
| |
| 候选模型: |
| |
| - 省资源路线:`google/gemma-4-E4B-it` |
| - 主推路线:`google/gemma-4-12B-it` |
| - 高质量路线:`google/gemma-4-26B-A4B-it` |
| |
| 接口: |
| |
| ```http |
| POST /persona/events |
| Accept: text/event-stream |
| ``` |
| |
| 输出: |
| |
| ```text |
| data: {"type":"stage","expression":"thinking","motion":"look_at_user"} |
| |
| data: {"type":"voice","style":"soft","speed":0.92,"energy":0.45} |
| |
| data: {"type":"text_delta","text":"嗯,"} |
|
|
| data: {"type":"sentence_end"} |
| |
| data: {"type":"done"} |
| ``` |
| |
| ### 2. `modal_tts.py` |
|
|
| 用途: |
|
|
| - 分句 TTS。 |
| - 返回 WAV/MP3 bytes。 |
| - 可选支持流式音频。 |
|
|
| 候选模型: |
|
|
| - 默认:Chatterbox Multilingual。 |
| - fallback:Kokoro-82M。 |
| - 实验:Dia / CSM。 |
|
|
| 接口: |
|
|
| ```http |
| POST /tts |
| ``` |
|
|
| 输入: |
|
|
| ```json |
| { |
| "text": "我在听。", |
| "voice_id": "star_knight_soft", |
| "emotion": "concerned", |
| "speed": 0.92, |
| "energy": 0.45 |
| } |
| ``` |
|
|
| 输出: |
|
|
| - `audio/wav` bytes |
| - 或 JSON 包含临时文件 URL |
|
|
| ### 3. `modal_image.py` |
| |
| 用途: |
| |
| - 生成内置角色图。 |
| - 重绘角色头像。 |
| - 自定义角色资产生成。 |
| |
| 候选模型: |
| |
| - FLUX.1-schnell |
| - SDXL fallback |
| |
| 接口: |
| |
| ```http |
| POST /image/character |
| ``` |
| |
| ## 省额度策略 |
| |
| ### 模型路由 |
| |
| 默认不要所有请求都打最大模型。 |
| |
| 建议路由: |
| |
| ```text |
| 纯文字日常聊天 -> E4B 或 12B |
| 图片输入 -> Gemma 4 12B / E4B / 26B A4B |
| 音频输入实验 -> Gemma 4 12B 或 E4B |
| 中等成本角色回复 -> Gemma 4 12B |
| 高质量角色回复 / 最终 demo -> 26B A4B |
| TTS -> Chatterbox 或 Kokoro |
| 生图 -> 用户明确点击才调用 |
| ``` |
| |
| ### 冷启动和缓存 |
| |
| 必须使用 Modal Volume 缓存 Hugging Face 权重: |
| |
| ```text |
| /root/.cache/huggingface -> huggingface-cache volume |
| /root/.cache/vllm -> vllm-cache volume |
| ``` |
| |
| 服务参数建议: |
| |
| - 开发期 `scaledown_window=60-180s` |
| - demo 录制期 `scaledown_window=5-15min` |
| - 公开 demo 期只让主 vLLM LLM 使用 `min_containers=1`,不要把 TTS、生图一起常驻 |
| - 大模型用固定 revision,避免模型仓库更新导致不可复现 |
| - 不要在 Gradio 启动时预热所有模型 |
| - 只预热当前选择的角色和当前模型 |
|
|
| 主 vLLM 常驻开关: |
|
|
| ```powershell |
| python scripts/set_modal_vllm_autoscaler.py on |
| python scripts/set_modal_vllm_autoscaler.py off |
| ``` |
|
|
| 如果希望部署配置本身保持常驻,部署前设置: |
|
|
| ```powershell |
| $env:VC_VLLM_MIN_CONTAINERS="1" |
| $env:VC_VLLM_BUFFER_CONTAINERS="0" |
| $env:VC_VLLM_SCALEDOWN_WINDOW="1200" |
| modal deploy modal_apps/modal_vllm_gemma.py |
| ``` |
|
|
| `scaledown_window` 只能减少短时间空闲后的冷启动;真正避免从零启动要使用 `min_containers=1`。这会让 GPU 24 小时计费。 |
|
|
| ### 一周常驻成本 |
|
|
| 按 Modal 2026-06-14 公开 GPU 价格,7 天是 168 小时: |
|
|
| | GPU | 约每小时 | 约 7 天 | |
| | --- | ---: | ---: | |
| | T4 | $0.5904 | $99.19 | |
| | L4 | $0.7992 | $134.27 | |
| | A10 | $1.1016 | $185.07 | |
| | L40S | $1.9512 | $327.80 | |
| | A100-40GB | $2.0988 | $352.60 | |
| | A100-80GB | $2.4984 | $419.73 | |
| | H100 | $3.9492 | $663.47 | |
|
|
| 这些是 GPU-only 估算,CPU、内存、区域倍率、非抢占、Volume 存储等另计。当前已验证的 `google/gemma-4-12B-it` vLLM 路线使用 L40S,因此 7 天常驻只算 GPU 也超过 $240;$240 约能覆盖 L40S 123 小时。A10 一周 GPU-only 约 $185,但当前 12B vLLM 没有在 A10 上验证,显存余量风险较高。 |
|
|
| ## 当前 Modal 实测 |
|
|
| 2026-06-11 已在 `verno / veronicaulises0` Modal workspace 测试 `google/gemma-4-12B-it` + `L40S`: |
|
|
| - 首次运行:包含镜像构建和首次权重缓存,客户端总耗时约 149.1s。 |
| - 缓存后运行:客户端总耗时约 33.7s;模型加载约 10.3s;生成 100 tokens 用时约 7.84s,约 12.75 tokens/s。 |
| - 显存峰值:约 22.34 GB。 |
|
|
| 详细记录见 `BENCHMARK_RESULTS.md`。 |
|
|
| vLLM 稳定版已尝试 `google/gemma-4-12B-it`,当前不可用。`0.21.0` 和 PyPI 最新稳定版 `0.22.1` 都会把模型解析为 `TransformersMultiModalForCausalLM`,提示没有 vLLM 原生实现并 fallback 到 Transformers,随后在 profile run 出现 shape mismatch。 |
|
|
| 截至 2026-06-12,Gemma 4 Unified 的可用 vLLM 路线是 main/nightly,而不是 PyPI 稳定版。当前已在 Modal L40S 上跑通 `vllm==0.22.1rc1.dev468+gfbc3a1907.cu129`,vLLM 日志解析架构为 `Gemma4UnifiedForConditionalGeneration`,并成功通过 OpenAI-compatible `/v1/chat/completions` 生成中文回复。 |
|
|
| 当前持久部署: |
|
|
| ```text |
| https://veronicaulises0--virtual-characters-vllm-gemma-serve.modal.run |
| ``` |
|
|
| 部署注意: |
|
|
| - 当前部署设置 `VC_SKIP_HF_SECRET=1`,没有把 `hf-token` 挂载到 nightly vLLM 环境。 |
| - 能启动是因为 `vc-hf-cache` Modal Volume 中已有 `google/gemma-4-12B-it` 权重缓存。 |
| - 如果清空 Volume 或迁移 workspace,需要先预缓存权重,或者明确批准 nightly 环境挂载 `hf-token`。 |
| - 正式 endpoint 冷启动约 3 分钟;warm 后短中文回复实测约 10-16 tok/s。 |
|
|
| 短期策略: |
|
|
| - demo 主线可以切到当前 vLLM nightly endpoint,但要保留 Transformers Modal 服务作为 fallback。 |
| - 如果继续追 vLLM 速度,优先优化当前 nightly endpoint 的冷启动、warmup 和 `--enforce-eager` 策略。 |
| - 不要再浪费额度反复测试 `vllm==0.22.1` + `google/gemma-4-12B-it` 这一组合。 |
|
|
| nightly 部署命令: |
|
|
| ```powershell |
| $env:PYTHONIOENCODING="utf-8" |
| $env:PYTHONUTF8="1" |
| $env:VC_SKIP_HF_SECRET="1" |
| $env:VC_VLLM_MODEL="google/gemma-4-12B-it" |
| $env:VC_VLLM_PACKAGE="vllm==0.22.1rc1.dev468+gfbc3a1907.cu129" |
| $env:VC_VLLM_EXTRA_INDEX_URL="https://wheels.vllm.ai/nightly/cu129" |
| $env:VC_VLLM_UV_EXTRA_OPTIONS="--index-strategy unsafe-best-match" |
| $env:VC_VLLM_PRE="1" |
| $env:VC_VLLM_GPU="L40S" |
| $env:VC_VLLM_FAST_BOOT="1" |
| .venv\Scripts\modal.exe deploy modal_apps/modal_vllm_gemma.py |
| ``` |
|
|
| TTS 已测试 Chatterbox 分句流式:底层不是音频 token 流式,但可以按句合成、按句播放。warm 后每句约 2-3 秒,足够做 demo。 |
|
|
| ### 并发 |
|
|
| TTS 可以允许较高并发;LLM 需要保守。 |
|
|
| 建议初始值: |
|
|
| ```text |
| LLM: max_inputs 4-16,按模型和 GPU 调 |
| TTS: max_inputs 4-10 |
| Image: max_inputs 1-2 |
| ``` |
|
|
| ### GPU 选择 |
|
|
| 初始建议: |
|
|
| - Chatterbox TTS:A10G / L4 起步。 |
| - Kokoro:CPU / L4 / T4 均可试。 |
| - Gemma 4 E4B:L4 / A10 / L40S 起步实测。 |
| - Gemma 4 12B:L40S / A100 起步更稳,量化后可再评估更低规格。 |
| - Gemma 4 26B A4B:A100 / H100 / H200 更稳,Modal 官方 vLLM 示例用了 H200。 |
| - FLUX.1-schnell:H100 最快,但开发期可以不常驻,按需运行。 |
|
|
| ## 流式协议与 Modal |
|
|
| Modal 支持 FastAPI `StreamingResponse`。因此推荐 Modal 端直接输出 SSE: |
|
|
| ```python |
| from fastapi.responses import StreamingResponse |
| |
| def event_stream(): |
| yield b'data: {"type":"stage","expression":"thinking"}\n\n' |
| yield b'data: {"type":"text_delta","text":"嗯,"}\n\n' |
| yield b'data: {"type":"done"}\n\n' |
| |
| return StreamingResponse(event_stream(), media_type="text/event-stream") |
| ``` |
|
|
| 注意: |
|
|
| - 不要要求模型原生输出完美 NDJSON。 |
| - 后端可以先发初始 stage/voice 事件,再转发模型 token。 |
| - Gradio 端消费 SSE,转换成组件多次 `yield`。 |
| - 调试时可以把 SSE payload 保存成 NDJSON 文件。 |
|
|
| ## Modal Secret |
|
|
| 需要的 secret: |
|
|
| - `hf-token`:默认 Hugging Face token secret,用于下载 gated 或大模型权重。 |
| - 可选 `modal-proxy-auth`:如果部署私有 endpoint。 |
|
|
| 如果 Modal 里已经有别的 Secret 名称,可以在运行或部署前设置: |
|
|
| ```powershell |
| $env:VC_HF_SECRET_NAME="your-secret-name" |
| ``` |
|
|
| 如果只是做 health smoke test,且还没有创建 HF Secret,可以临时设置: |
|
|
| ```powershell |
| $env:VC_SKIP_HF_SECRET="1" |
| python scripts/check_modal_connectivity.py --mode remote-methods |
| ``` |
|
|
| 这个开关默认只适合检查 Modal 容器启动。例外是当前 vLLM nightly + Gemma 4 路线:为了避免把 `hf-token` 暴露给 nightly 依赖栈,可以在确认 `vc-hf-cache` Volume 已经有完整权重缓存后使用 `VC_SKIP_HF_SECRET=1` 部署。常规稳定依赖栈仍建议创建 `hf-token`,并在 Hugging Face 上接受相关模型的访问条件。 |
|
|
| 注意不要把 token 写进仓库。 |
|
|
| ## 开发顺序 |
|
|
| ### 第一步:TTS endpoint |
|
|
| 先做 Chatterbox 或 Kokoro,因为它最容易让 demo 有角色存在感。 |
|
|
| 产物: |
|
|
| - `modal_tts.py` |
| - `/tts` endpoint |
| - Gradio 调用 TTS 并播放音频 |
|
|
| ### 第二步:LLM event endpoint |
|
|
| 先用 mock 事件流,再接 Gemma。 |
|
|
| 产物: |
|
|
| - `modal_llm.py` |
| - `/persona/events` SSE endpoint |
| - Gradio 能显示 text_delta、stage、voice、skill |
| |
| ### 第三步:图像输入 |
| |
| 先用上传图片,不做实时摄像头。 |
| |
| 产物: |
| |
| - 图片传到 LLM/VLM endpoint |
| - 生成 `vision_note` |
| - 角色基于 `vision_note` 回复 |
|
|
| ### 第四步:生图 |
|
|
| 按需生成角色头像。 |
|
|
| 产物: |
|
|
| - `modal_image.py` |
| - `/image/character` |
| - 缓存生成结果 |
|
|
| ## 参考链接 |
|
|
| - Modal vLLM / Gemma 示例: https://modal.com/docs/examples/vllm_inference |
| - Modal streaming endpoints: https://modal.com/docs/guide/streaming-endpoints |
| - Modal GPU: https://modal.com/docs/guide/gpu |
| - Modal Volumes: https://modal.com/docs/guide/volumes |
| - Modal Chatterbox TTS: https://modal.com/docs/examples/chatterbox_tts |
| - Modal Flux: https://modal.com/docs/examples/flux |
| - Gemma 4 12B IT: https://huggingface.co/google/gemma-4-12B-it |
| - Gemma 4 26B A4B: https://huggingface.co/google/gemma-4-26B-A4B-it |
| - Gemma 4 E4B: https://huggingface.co/google/gemma-4-E4B-it |
| - vLLM PyPI: https://pypi.org/project/vllm/ |
| - vLLM supported models latest docs: https://docs.vllm.ai/en/latest/models/supported_models/ |
| - vLLM nightly wheels: https://docs.vllm.ai/en/latest/contributing/ci/nightly_builds/ |
| - Kokoro-82M: https://huggingface.co/hexgrad/Kokoro-82M |
| - Chatterbox: https://huggingface.co/ResembleAI/chatterbox |
| - Dia-1.6B: https://huggingface.co/nari-labs/Dia-1.6B |
| - Sesame CSM-1B: https://huggingface.co/sesame/csm-1b |
|
|