README.md 13.8 KB

raylight —— Raylight(ComfyUI插件) + MiniMax-H3,4卡 FSDP 生产部署

这是什么

minimax-h3/(DiffSynth-Studio + NF4 量化,4卡各自独立 worker,能同时处理4个请求)的替代 方案:改用 ComfyUI + Raylight 插件,把4张 2080Ti 当一个 FSDP 集群跑一个请求(序列并行+权重分片),不再是4个独立worker。

2026-08-21 验证链路通、2026-08-22 完成生产部署验证并接管 h3-console 的默认后端。 详细踩坑记录见 docs/todo/raylight-comfyui-multi-gpu-eval.md,这里只写"现在是什么状态、 怎么起停、怎么回滚"。

跑在哪张卡 / 占多少显存 / 对外端口

项 值
GPU 0/1/2/3 全部(FSDP 集群,不是独立worker,任何时刻只处理1个请求)
attention后端 2026-08-22起SAGE_FP16(原来是TORCH_MATH,见「已知限制」第1条——这是当前最重要的一次架构改动)
显存/时长 SAGE_FP16上线后,832×480/448×256/1024×576/1344×768 四档实测到的帧数全部成功,没有再找到OOM边界,见「已知限制」第1条和notes.md
对外端口 raylight-server.py(8100,取代 minimax-h3/gateway.py+api.py两层的对外契约)+ ComfyUI 自身(8188,仅内部调用,不对外)
console 接入 h3-console 8080/8081 两个实例的 H3_GATEWAY_URL 都已指向 http://127.0.0.1:8100,H3_ENABLE_SUPERVISOR 已关闭(分区flip的概念在FSDP集群架构下不适用)

怎么起

ssh admin@192.168.110.67
bash /data/models/raylight/start.sh

脚本会按端口停掉已在跑的 ComfyUI(8188)/raylight-server(8100),重新拉起两者,轮询健康检查。 照抄的是 minimax-h3/start-all.sh 的模式(按端口 ss -lptn 找PID kill,不用 pkill -f)。

怎么停 / 回滚到旧的 minimax-h3

# 停 raylight
ssh admin@192.168.110.67 'PID=$(ss -lptn "sport = :8100" | grep -o "pid=[0-9]*" | cut -d= -f2); kill $PID
                           PID=$(ss -lptn "sport = :8188" | grep -o "pid=[0-9]*" | cut -d= -f2); kill $PID'

# 拉回旧的 minimax-h3(代码/权重保留,只是不再默认启动,start-all.sh 还在)
ssh admin@192.168.110.67 'cd /data/models/minimax-h3 && export H3_OFFLOAD=disk && ./start-all.sh'

# h3-console 两个实例改回旧后端(重启时把 H3_GATEWAY_URL 换回 http://127.0.0.1:8000,
# H3_ENABLE_SUPERVISOR=1(8080那个要开),具体环境变量见 h3-console/README.md)

已知限制(如实记录,别重复踩)

  1. 【已解决,2026-08-22】旧的"832×480×124帧/448×256×362帧/1024×576×60帧都会OOM"结论已过时。 根因找到了:环境里从来没装过给Turing(sm_75)编译的sageattention,XFuser_attention只能 退回TORCH_MATH——这个后端会把完整attention分数矩阵物化在显存里,显存开销随序列长度平方 增长。装上社区维护的Turing版SageAttention2(github.com/1506086927/SageAttention2_Optimized_Test, 经github.com/Eanya-Tonic/2080ti-minimax-h3的补丁接入,Apache 2.0),把XFuser_attention 换成SAGE_FP16后,这三个档位全部通过,而且快很多(124帧从OOM变成100秒)。 之前试过的"换小文本编码器""挪CLIP/VAE到别的GPU""开expandable_segments"这几条路全都没用 (瓶颈根本不在那几个地方)——真正的根因一直是attention kernel选型,不是显存怎么腾挪。 2026-08-23 凌晨补充实测(修完第2条的完成检测bug后,重新摸了一遍真实上限,见notes.md 完整数据表):

| 分辨率 | 已验证能跑 | 真实上限 | |---|---|---| | 832×480 | 1125帧(约68分钟) | 1195帧真实CUDA OOM(GPU0耗尽)——离API硬上限1200帧很近,两者离得很近,没细分谁是真正瓶颈 | | 448×256 | 1200帧(50秒视频,约10.8分钟) | 不适用——这个分辨率是画质卡的,不是显存卡的,见下方单独说明 | | 1024×576 | 743帧(约62分钟) | 787帧真实CUDA OOM | | 1344×768(官方默认分辨率) | 412帧(约55分钟) | 431帧真实CUDA OOM |

⚠️ 【2026-08-24补充,重要】上面这张表全部是0张参考图(partition: text)测出来的, 带参考图(ref2va/fl2va)不能直接套用——参考图的conditioning张量会广播到每个FSDP rank,显存开销明显更高。实测"1344×768×362帧×3参考图"这个精确配置当天5次尝试4次失败 (1次OOM在GPU0,1次OOM在FSDP初始化阶段的GPU2/GPU3,不再是"只有GPU0会先炸"),完整 证据见notes.md「2026-08-24(晚)」一节。带参考图时别直接信这张表的帧数上限, 目前没有专门测过参考图数量×帧数的边界,这是个空白。

所有真实OOM都是GPU0先炸(跟之前"GPU0同时扛文本编码器+DiT分片"的结论一致),且都在提交后 30秒内报错(采样刚开始分配显存就失败,不会跑到一半才炸)。每个done结果都额外SSH核实过 视频文件真实存在、大小合理,不是只信API的status字段。

448×256 是画质限制,不是显存限制——这是故意留着的结论,不是没测完的TODO:技术上把 GenReq.frames的上限从1200调到3000后继续测过,2700帧、3000帧全部成功且GPU显存远没跑满 (3000帧用了约44分钟,没有OOM迹象),说明这个分辨率的真实VRAM上限比3000帧还高,没有真正 探到。但用户实际看过1200帧(50秒/24fps)以上的输出后判定画面质量从50秒往后就不能看了 ——所以推荐的实际上限是1200帧/50秒,卡点是主观画质而不是硬件承受能力,继续往上探 VRAM上限没有实际价值,不需要再花时间找。

2026-08-23:这张表已经写进两边代码强制生效,不只是文档——server.py的 RESOLUTION_CEILINGS会在提交阶段直接拒绝超过实测上限的请求(422,报真实上限数字), h3-console/static/index.html的RESOLUTIONS[].maxFrames同步这份数据,选了分辨率之后 时长下拉框会自动把超上限的档位过滤掉——不用等提交了才知道会OOM。两边数字改动要一起改, 对不上就会出现"前端放行、后端拒绝"或反过来。新建任务默认分辨率改成了1344×768(官方 默认分辨率)/5秒。

  1. 【2026-08-23发现并修复】server.py里"ComfyUI已经执行完了,但任务状态卡在running 转不成done、单队列被永久堵死"的bug——真根因找到了:_run_job_async的finally里 tail_task.cancel()/ws_task.cancel()之后await这些task,用contextlib.suppress (Exception)接——但asyncio.CancelledError在py3.8+是BaseException的子类,不是 Exception的子类,压不住,会直接飞出finally绕过worker()的except Exception, 导致status卡死在running但finished_at已经被写上了。跟之前怀疑的"websocket断线/ 丢消息"是同一个bug的不同表现,不是两个问题。已修:改成 contextlib.suppress(asyncio.CancelledError);同时把完成判定从"只信websocket的 execution_success消息"改成轮询/history/{prompt_id}(websocket现在只用来上报进度, 断了不影响完成判定,双重加固)。用真实console任务验证过修复生效。
  2. 【2026-08-23修复】_load_jobs()启动时会崩:sqlite3.OperationalError: database is locked——_load_jobs()用一个连接的SELECT游标还开着的时候,循环里调_persist()拿新连接 写,SQLite默认rollback-journal模式下第二个连接的写commit会被第一个连接的读事务锁住。 已修:_load_jobs()先把SELECT结果整个取成list、关掉连接,再在循环里写。这个bug是这次 测试期间由本目录内另一个并发进程引入的(_load_jobs()加了失败原因落盘的逻辑,改动本身 是对的,只是漏了这个连接冲突),修复已经跑通验证过。
  3. warm-reuse 效果很有限,别指望"第二次会快很多"。理论上 RayInitializer/RayUNETLoader 等重节点的输入值不变,ComfyUI 会跳过重建集群直接复用;实测确实跳过了 NCCL通信测试和 FSDP分片这两步,但文本编码器(CLIPLoader)每次都在重新加载,原因未查——两次64帧测试 耗时分别是 822s(首次,紧跟着服务重启)和 686s(第二次,配置逐字节相同),只省了约17%, 不是"9分钟降到几十秒"那种量级。每个请求实际都要 11-14 分钟,这是当前要接受的真实延迟。
  4. 并发数从4降到1——这是架构级代价,不是bug。现在4卡是4个独立worker能同时处理4个请求; 这套方案4卡是一个集群,ComfyUI 会排队多个 /prompt 提交但严格串行执行。高峰期多人同时 提交会互相排队等,不会像以前那样4个同时跑。
  5. cfg_scale / negative_prompt / turbo 是 no-op——请求体接受这些字段(跟 console 现有 契约保持兼容),但 Raylight 的 MiniMax H3 pipeline 本身不支持 CFG(cfg_degree 只能是1, Raylight 自己的支持列表也写了 CFG❌),turbo LoRA 还没接到 workflow graph 里,传了也不生效。
  6. 【2026-08-22修复】进度上报(step/total_steps)以前是坏的,现在能用了——根因: Raylight 的实际采样循环跑在 Ray 分布式 worker 进程里,不是本地 ComfyUI 进程直接算的, 真实的逐步进度没有通过 ComfyUI 本地的 progress_state 机制传回来,websocket 那条路永远 拿到total_steps=1这种假数据。退而求其次:轮询子进程 tqdm 直接打到 stdout、被 comfyui-server.log捕获的那行文字(_PROGRESS_RE,server.py),只信任total等于 本次请求steps的那次匹配(避免跟FSDP初始化阶段同样打"Model Initializing"但计数是GPU数 的另一个进度条搞混)。这是个ponytail级别的退路方案,上游改了这行日志的措辞就会失效 (退化成进度条不动,不影响任务本身跑成功)。elapsed_seconds(挂钟时间)一直是准的。
  7. actor 崩溃不会自动恢复——Raylight 的 ensure_fresh_actors(负责检测到模型已加载/actor 崩溃时重建)里有个确认过的 bug:needs_restart = False 无条件覆盖了前面刚算出来的检测 结果,这段自愈逻辑现在是死代码。真机测试中一次 OOM 后 GPU1/2/3 卡在接近满载没释放, 必须手动 bash start.sh 整个重启才能回收——这不是我们这边能修的,是上游代码的问题。
  8. 【2026-08-24修复】/health 之前不会真的探测 ComfyUI(8188) 存不存活,会骗人报健康—— 跟第8条是同一类事故的另一半:一次 actor 崩溃(ray.exceptions.ActorDiedError)之后, ComfyUI(8188) 进程本身直接退出了,但 server.py(8100) 这层壳没死。根因:/health 的 ready 字段只读 STATE["model"],这个值在 worker() 后台线程启动时设一次"ready", 之后再也不更新——不管 8188 是死是活,/health//v1/backends 永远报同一个值。真实 故障中这个盲区持续了近20分钟,期间 console 界面一直显示"ready",但提交的每个任务都在 1秒内秒失败(ConnectError: All connection attempts failed,请求根本连不上已经不存在 的8188),是人工发现任务反常才追查到8188已经不在监听。已修:/health 现在每次都 用跟start.sh自己就绪检测同一个端点(GET {COMFY_BASE}/system_stats,2秒超时)真的 探测一次8188,探测失败就把ready降级成0、model字段报"comfyui_unreachable",不再 单纯相信内存里那个只写一次的标志位。这条不能顶替第8条——探测准了只是能更快发现 崩溃,8188死了以后还是要手动bash start.sh重启,自愈本身仍然没有。

多参考图生视频支持参考视频/独立音频参考(2026-08-24 新增)

MiniMaxH3ReferenceToVideo 节点本身支持的输入比之前接的多——查 ComfyUI /object_info 发现 除了 ref_images(参考图,早就接了),还有 ref_videos(参考视频,最多3段)、 ref_video_audios(对应视频自己的音轨)、ref_audios(独立音频参考,最多3个,不跟任何 视频绑定)。之前只做了参考图这一种,现在都接上了:

  • GenReq 加了 reference_video_urls/reference_audio_urls(各自最多3个)。
  • 参考视频走 LoadVideo → GetVideoComponents 拆成 images/audio 两路,images 接 ref_videos.ref_video_{i},audio 自动接 ref_video_audios.ref_video_audio_{i}——音轨 是从视频文件自己解出来的,不用用户再单独传一份,这是"同一段视频自己的音轨"这个语义 最省事的实现方式。
  • 独立音频走 LoadAudio 直接接 ref_audios.ref_audio_{i}。
  • console 的"多参考图生视频"tab 加了两个上传框(参考视频/独立音频参考),/v1/assets 上传接口按扩展名分流校验(视频100MB上限、音频30MB上限,图片还是20MB)。
  • 端到端测过:2秒测试视频(带音轨)+独立音频文件,status正确转done,视频文件正常生成。

LoadVideo/LoadAudio/GetVideoComponents 都是 ComfyUI 核心节点(comfy_extras),不是 自定义节点包,没有额外安装/兼容性风险。

相关文档

  • docs/todo/raylight-comfyui-multi-gpu-eval.md——完整调研+验证+生产部署过程,踩过的坑 按时间顺序都在里面。
  • notes.md——本目录内,被推翻的假设和精简版实测数据表。
  • minimax-h3/README.md——旧系统(现在是热备份)的架构和踩坑记录,仍然有效。