# 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