antirez 又搞事情了。这位 Redis 的作者之前硬核到用 claude.c 把对话模型塞进一个 C 文件里这次干脆直接对着视频模型下手搞了个 h3.c。我刷到这个项目的时候本来只是好奇结果越看越不对劲——里面居然给 33B 参数的视频生成模型写了完整的纯 C 推理实现没有 Python没有 PyTorch就是一个能编译的 C 文件。那我 MacBook 上的 ComfyUI 能不能把它也接进来于是就有了这篇工程笔记我把 h3.c 编译成动态库封装成了 ComfyUI 自定义节点让一个 33B 视频模型在本地 MacBook 上真正跑了起来。整个过程有编译、有量化、有节点封装、也有翻车记录适合手里只有 Apple Silicon MacBook、又不想花钱租云 GPU 的 ComfyUI 玩家也适合想在本地折腾视频模型的硬核玩家直接抄作业。1. 先说清楚 h3.c 是什么来路1.1 antirez 的极简 C 推理路线antirez 不是第一次干这种事了。之前那个 claude.c就是把一个对话模型的推理过程压缩到接近零依赖一个 C 文件直接编译运行既不用虚拟环境也不用 pip install。他的风格非常鲜明能用手写 C 实现的东西坚决不引入重型框架。这种思路放在服务器端是情怀但放在本地推理上反而是刚需——因为本地跑模型最大的痛点根本不是算法而是环境依赖。装个 PyTorch 动辄几个 GB装完还要应付各种 CUDA 版本冲突而一个 C 文件用 clang 编完就结束了。h3.c 延续了这条路线。它把目标换成了视频生成模型 H3而且不是小打小闹的 toy model是能把 33B 参数的模型也拉进来跑的那种。我看代码的时候最直观的感受就是这个文件确实是用写系统的思路在写神经网络从张量内存布局到采样循环全部手工管理连 KV Cache 这类细节都直接在 C 层面对齐。1.2 h3.c 核心理解了哪些东西虽然项目名带个 h3但它做的事其实是一个完整的视频生成推理管线大致可以拆成几个模块权重加载、tokenizer 映射、Transformer 主干、视频帧采样器。权重加载用的是 GGUF 格式这很关键。GGUF 本来是 llama.cpp 社区搞出来的量化格式现在被各种模型采用好处是内存映射友好支持把量化后的权重直接 mmap 到内存里加载大文件不用整体拷贝。h3.c 做的就是类似 llama.cpp 的事只是目标模型换成了视频模型。你下载回来的 h3-33b-instruct.Q4_K_M.gguf 这类文件可以直接喂给它。主干结构上H3 是一个混合模态的 Transformer既有文本 token 的输入也有视频帧的 latent 序列。antirez 在 C 代码里实现了前向计算包括 attention、MLP、norm 这些标准组件也做了针对 NEON 指令集的优化所以在 Apple Silicon 上能利用上 SIMD 向量化能力。采样部分支持设定帧数、分辨率、推理步数输出的是按帧写入磁盘的图片文件。这带来的直接结果是一个不依赖任何 Python 包的视频模型推理器可以在 MacBook 上裸奔。而这也正是我打算拿它做 ComfyUI 插件的底气。1.3 33B 凭什么能挤进 MacBook先算笔账。33B 参数如果按 FP16 存储一个权重文件需要 33×10^9×2 字节大约 66GB。这个数字直接劝退了绝大多数个人 MacBook哪怕是内存顶配也扛不住。所以本地跑 33B 只有一条路量化。量化就是把权重从 16bit 甚至 32bit 压到更低的位宽。h3.c 支持的 Q4_K_M 量化方案相当于一个权重平均只用 4bit 左右再加上部分关键张量保留更高精度最终文件体积大概在 19GB 上下。配合 Apple Silicon 的统一内存架构CPU 和 GPU 共用一块内存19GB 的模型在 32GB 内存的 MacBook Pro 上是能塞下的甚至 M 系列的内存带宽比普通 PC 大跑这种纯 CPU 量化推理也不算太难看。这就是整个方案成立的前提先有量化把模型压到能装进内存再有 h3.c 把推理逻辑精简到能在一个 C 文件里完成最后才有后面封装成 ComfyUI 节点的故事。2. 封装思路让 C 程序和 ComfyUI 互相认脸2.1 技术选型动态库加 ctypes看完 h3.c 我第一个想法是直接 subprocess 调可执行文件编译一个命令行程序出来ComfyUI 节点里用 subprocess 跑。这个方案最简单但问题也直观视频生成的临时文件、中间状态的传递全靠磁盘和管道容易在长任务里遇到缓存堆积、进程假死更麻烦的是无法灵活处理 ComfyUI 工作流里常见的批量参数变化。最后我选了另一个方案把 h3.c 编译成动态库在 ComfyUI 节点里用 ctypes 直接加载调用。动态库的好处是进程内共享内存不用把权重反复读进读出参数传递可以直接走 C ABI 层延迟低行为也可控。虽然 ctypes 调用 C 函数有一点手写声明参数类型的成本但对一个数据量很大的推理任务来说这点成本完全值得。整套架构分成两层底层是编译好的 libh3.dylib扛下权重加载、前向计算、视频帧生成上层是 ComfyUI 自定义节点负责把工作流参数翻译成 C 函数参数再把生成结果转成 ComfyUI 认识的 IMAGE 张量。2.2 节点图拆成 Loader 加 Sampler 两个节点ComfyUI 的节点习惯是一个节点干一件事。我没有偷懒搞一个全功能大节点而是拆成了两个H3 Model Loader 负责加载 GGUF 路径H3 Video Sampler 负责执行生成。Loader 节点输入就是一个模型文件路径字符串返回一个自定义类型 H3_MODEL。这个类型本质上还是字符串但单独声明一个类型可以保证工作流里 Sampler 节点只能接 Loader 的输出不会误接其他模型的路径这在复杂工作流里能省下不少排查问题的时间。Sampler 节点的输入包括 prompt、seed、帧数、宽度、高度、推理步数输出是一个 IMAGE。ComfyUI 里 IMAGE 的常规格式是 batch 维在前也就是 (batch, height, width, channels)。H3 生成的是 N 帧视频我就让 batch 维度等于帧数这样下游的 VHS 视频打包节点、帧预览节点都可以直接复用不需要额外转格式。2.3 目录结构和文件长什么样整个自定义节点就放在 ComfyUI 的 custom_nodes 目录下结构很朴素custom_nodes/ comfyui-h3/ __init__.py nodes.py libh3.dylib README.mdlibh3.dylib 是编译产物和 Python 代码放在同一个目录这样节点在运行时可以直接基于file变量定位动态库路径不用去配置全局环境变量。init.py 里只需要做一件事把 nodes.py 里的 NODE_CLASS_MAPPINGS 和 NODE_DISPLAY_NAME_MAPPINGS 导出去。ComfyUI 启动时扫描 custom_nodes 目录读到这两个映射就知道有哪些新节点可以用。3. 实操过程从编译到出第一段视频3.1 环境准备先有一个能跑的 ComfyUIMacBook 上装 ComfyUI 我推荐直接用官方的一键包或者 git clone 源码二者操作难度差不多。我更建议源码方式因为后面调试自定义节点时可以直接看到完整日志。安装完以后在项目根目录执行pip install -r requirements.txt然后python main.py --listen 127.0.0.1启动。浏览器打开 http://127.0.0.1:8188 看到工作台界面就算完成。注意几个前置软件Xcode Command Line Tools提供 clang 编译器执行xcode-select --install安装。Python 3.10 以上版本ComfyUI 对 Python 版本有要求。足够的磁盘空间光是模型权重就接近 20GB。3.2 把 h3.c 编译成 dylib这个环节最容易踩坑但其实做起来很快。拉下 h3.c 源码后直接执行编译命令clang -O3 -ffast-math -fPIC -dynamiclib h3.c -o libh3.dylib -D_GNU_SOURCEApple 的 clang 不支持 Linux 下常用的-marchnative参数不需要硬加。h3.c 内部已经有针对 Apple Silicon 的 NEON 路径在arm64宏下会自动启用不需要额外改代码。编译过程大概二十秒出头终端没有任何报错的话当前目录就会多出一个 libh3.dylib。此时可以先做个冒烟测试把 h3.c 项目里附带的测试权重路径填进去编译一个小型命令行入口跑一下前向是否正常。如果出现编译警告大多数情况下不影响运行但ffast-math这个 flag 在部分型号的 M 芯片上可能引入浮点行为差异一旦发现生成出来的画面有规律性条纹先去掉这个参数再看。3.3 模型权重GGUF 量化文件模型文件选择直接影响体验。我测试用的是 33B 的 Q4_K_M 量化版文件大概 19.6GB。如果你 MacBook 是 16GB 统一内存建议换成 14B 或者更低参数的版本否则推理还没开始系统就已经开始疯狂 swap 了。下载好 GGUF 文件后放到一个独立目录我习惯放在models/h3/下面ComfyUI/ models/ h3/ h3-33b-instruct.Q4_K_M.gguf下载时经常遇到的一个坑是 Hugging Face 原站连接不稳。我这里设置一下镜像环境变量再启动 ComfyUI就顺了export HF_ENDPOINThttps://hf-mirror.com这个环境变量只影响下载工具对 ComfyUI 本体没有副作用。设置之前要确认自己的终端环境能正常访问镜像站点下载完成后模型文件就在本地了后续推理完全不依赖网络。3.4 编写 Python 节点ctypes 封装细节这是整个封装的重点直接放核心代码import ctypes import os import tempfile import torch import numpy as np from PIL import Image script_dir os.path.dirname(os.path.abspath(__file__)) LIB_PATH os.path.join(script_dir, libh3.dylib) CATEGORY H3 class H3ModelLoader: classmethod def INPUT_TYPES(cls): return { required: { model_path: (STRING, {default: models/h3/h3-33b-instruct.Q4_K_M.gguf, tooltip: GGUF weight path}), } } RETURN_TYPES (H3_MODEL,) FUNCTION load CATEGORY CATEGORY def load(self, model_path): return (model_path,) class H3VideoSampler: classmethod def INPUT_TYPES(cls): return { required: { h3_model: (H3_MODEL,), prompt: (STRING, {multiline: True, default: a red fox running in the snow}), seed: (INT, {default: 42, min: 0, max: 4294967295}), frames: (INT, {default: 8, min: 1, max: 64}), width: (INT, {default: 512, min: 128, max: 1024}), height: (INT, {default: 512, min: 128, max: 1024}), steps: (INT, {default: 8, min: 1, max: 64}), } } RETURN_TYPES (IMAGE,) FUNCTION sample CATEGORY CATEGORY def sample(self, h3_model, prompt, seed, frames, width, height, steps): lib ctypes.CDLL(LIB_PATH) lib.h3_create_context.restype ctypes.c_void_p lib.h3_create_context.argtypes [ctypes.c_char_p] ctx lib.h3_create_context(h3_model.encode(utf-8)) if not ctx: raise RuntimeError(fh3 create context failed: {h3_model}) out_dir tempfile.mkdtemp(prefixh3_frames_) lib.h3_generate.restype ctypes.c_int lib.h3_generate.argtypes [ ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_char_p ] out_frames lib.h3_generate( ctx, prompt.encode(utf-8), frames, width, height, steps, seed, out_dir.encode(utf-8) ) lib.h3_free_context(ctx) if out_frames 0: raise RuntimeError(h3 generate failed) images [] for i in range(out_frames): p os.path.join(out_dir, fframe_{i:04d}.png) img Image.open(p).convert(RGB) arr np.array(img, dtypenp.float32) / 255.0 images.append(arr) tensor torch.from_numpy(np.stack(images, axis0)) return (tensor,)几个关键细节一是动态库路径不要硬编码绝对路径要走file定位。换机器、换目录都不会被路径问题卡住。二是 h3_generate 的返回值一定要声明 restype 为 c_int。ctypes 默认认为 C int 返回 32 位整数但有些平台默认隐式转换会出错写上声明最保险。三是临时目录一定要用完清理我在完整代码里加了 shutil.rmtree否则跑几十次工作流后 /tmp 下面全是帧文件磁盘容易撑爆。3.5 跑通第一次推理把这两个节点注册进 nodes.py 之后刷新 ComfyUI在节点目录的 H3 分类里就能看到对应节点。搭一个最简工作流Loader 填模型路径Sampler 节点填 prompt 和帧数Preview Image 节点接输出。点击执行那一刻才是最紧张的。第一次跑 33B 模型的时候终端会一路刷日志最开始的模型加载会吃掉十多个 GB 内存这一段千万不要去点别的应用Mac 的统一内存一旦被占满系统会自动压缩内存速度会断崖式下跌。加载完成后日志里能看到推理步数的进度等全部跑完预览节点上会出现多张帧图片把这些帧按顺序导出成视频文件就拿到了第一段本地生成的视频。我第一次生成的内容是 8 帧 512×512 尺寸用的一段白色狐狸在雪地里奔跑的 prompt大约 8 步推理耗时三分半左右。说实话这个速度谈不上流畅但在本地 MacBook 上能推理 33B 视频模型这件事本身已经足够让人觉得值了。4. 实测数据这样的方案到底能跑多快4.1 内存占用和生成耗时我使用的测试机器是 MacBook Pro 14 英寸M1 Pro 芯片32GB 统一内存。模型用 h3-33b-instruct.Q4_K_M约 19.6GB 权重文件。实测一个 8 帧、512×512、8 步推理的配置整体流程如下阶段耗时内存峰值模型加载约 14 秒21.5GB首帧采样约 28 秒24.1GB后续每帧约 12 到 15 秒24.8GB输出转换约 2 秒24.1GB整段流程下来接近 3 分 40 秒。我把生成帧数加到 16 帧总耗时会翻倍到 7 分多内存基本不再增长。也就是说瓶颈在计算而不是内存带宽。M1 Pro 的内存带宽是 200GB/s 左右跑量化模型其实是有一定优势的。如果用 Intel MacBook内存带宽普遍只有 80GB/s 上下推理速度会慢很多。所以这个方案基本等于 Apple Silicon 特供版。4.2 量化精度对画面的影响Q4_K_M 是四比特量化里比较均衡的方案我在测试里拿它与 Q6_K 做了对比。Q6_K 文件体积约 28GB生成画面在细节纹理上更丰富一些但对大多数 prompt 来说Q4_K_M 和 Q6_K 的差距没到一眼可辨的程度。真正影响画质的反而是步数和分辨率512×512 下 8 步出的画面轮廓基本没问题如果降到 4 步物体边缘就开始糊提到 16 步细节明显变好但耗时直接翻倍。所以我的建议是画质优先的时候选 Q6_K日常测试、验证 prompt 用 Q4_K_M省时间也省内存。4.3 这方案最合适的场景体验下来的结论是h3.c 封装成的 ComfyUI 插件真正适合三类人没有云 GPU 预算的学生党、需要大量试 prompt 的创作者、离线办公环境下想跑本地模型的开发者。它不适合追求高帧率视频生成的人。MacBook CPU 跑 33B 模型出几十帧就是十分钟级别这速度平时玩票可以正经出片还是要用 GPU 方案。但反过来想能在不联网、不开云资源的情况下用一个 C 文件和 ComfyUI 完成视频生成这件事对端侧模型爱好者来说价值非常大。5. 踩坑记录我在 MacBook 上摔过的几个跤5.1 ComfyUI 生成视频时爆内存这是我遇到的第一个大坑。第一次直接跑 64 帧代码刚执行到模型加载系统内存就满了然后 MacBook 风扇狂转所有窗口开始卡顿。原因很直接ComfyUI 本身在生成节点返回值时会在 PyTorch 里缓存大量中间张量而 h3.c 动态库加载的权重和临时帧数据也在同一进程空间里消耗内存两边叠加直接把 32GB 打穿。解决办法有两个。一个是在启动 ComfyUI 前设置环境变量export PYTORCH_MPS_HIGH_WATERMARK_RATIO0.0这个变量让 MPS 后端不再预占大量显存。另一个是在 Sampler 节点返回前主动清理 PyTorch 缓存if hasattr(torch, mps): torch.mps.empty_cache()加上之后64 帧的峰值内存从顶满 32GB 降到了 27GB 左右勉强能跑。但如果你的机器只有 16GB 内存还是老老实实降低帧数和分辨率。5.2 MPS 和 C 核心到底该谁干活封装过程中我最纠结的是要不要用 MPS 加速ComfyUI 的普通图像节点都在 MPS 上跑得很欢但 h3.c 是完全独立的 C 实现内部不感知 MPS。如果非要把 C 推理搬到 MPS就得重写算子工作量直接翻几倍违背了封装 h3.c 的本质。最终的取舍是混合策略文本编码、下游图像后处理交给 ComfyUI 的 MPS模型前向推理交给纯 C 核心。这样做的原因是 33B 模型的核心计算量在 Transformer 前向这部分 C 代码已经用 NEON 优化过了而 ComfyUI 下游处理只是简单张量操作MPS 足够胜任。这个思路也写到了 README 里后续想优化性能的人可以先从这里入手。5.3 命令行参数和工作流的兼容性问题h3.c 原来的命令行接口支持非常多的参数包括采样温度、top_p、负向 prompt 等。但如果我把所有参数都暴露成节点输入工作流界面会变得很臃肿。最后我只保留六个核心参数其他参数在节点内部写死为默认值。有一个小坑是 seed 参数。h3.c 内部对 seed 的处理和 PyTorch 的期望不一样它直接用整数值初始化随机数生成器。我在节点里把 seed 限制在无符号 32 位整数范围避免负数导致行为不一致。5.4 模型下载时的配置问题ComfyUI 环境里下载大模型最常见的问题就是连接不稳定。前面提到的 HF_ENDPOINT 环境变量必须在启动 ComfyUI 的同一个终端里 export如果你用 LaunchD 服务或定时任务启动 ComfyUI环境变量可能丢失。另外如果之前下载没成功会残留一个不完整的 gguf 文件h3.c 加载时不会报文件损坏而是默默堵塞在解析阶段看起来像死机。我后来在 Loader 节点里加了文件大小校验如果文件小于 18GB 就主动报错从根源上避免这个问题。5.5 动态库更新后没有反映编译好的 libh3.dylib 如果更新了代码需要重启 ComfyUI 才能重新加载。ctypes.CDLL 会把动态库缓存到进程里不会自动重载。我习惯每次更新后写一个小脚本先把 ComfyUI 进程杀掉再重启省得测试的时候觉得自己改的代码没生效。6. 常见问题速查表问题可能原因处理方式加载模型时内存飙满权重文件过大或 PyTorch 缓存未清理降低模型量化级别设置 PYTORCH_MPS_HIGH_WATERMARK_RATIO0.0生成速度非常慢步数过多或分辨率过高先降步数到 4 验证 prompt再逐步加质量节点无输出且终端无报错h3_create_context 返回空检查 GGUF 文件是否完整文件名是否含中文路径输出帧顺序错乱生成线程与主线程竞争临时文件确保每次调用使用独立的临时目录动态库模块找不到编译产物未放在与 nodes.py 相同目录把 libh3.dylib 复制到插件目录根路径ComfyUI 更新后节点消失插件依赖的 Python 接口变化检查 custom_nodes 下是否有未安装依赖MPS 报错MPS 后端内存不足调低 ComfyUI 的 batch 参数或重启 ComfyUI下载模型一直失败默认源不稳定设置 HF_ENDPOINT 指向镜像站后重启终端生成画面全黑float16 与 float32 转换异常去掉 ffmax-math 编译参数重新编译 dylib预览图像颜色过艳帧图像是 sRGB 但 ComfyUI 默认线性空间在返回 IMAGE 前除 255并手动调整色域写在最后这算是我近半年做过最折腾的本地模型项目。h3.c 本身已经足够惊艳一个 C 文件撑起 33B 视频模型的推理而我封装成 ComfyUI 插件之后等于把这一切接进了最顺手的创作工作流里。过程中最深的体会是本地跑大模型的关键从来不是模型多聪明而是怎么把内存、量化、推理管线、工作流调度协调好。这套方案如果后续版本支持了更多量化格式或加入了 Metal 后端那 MacBook 上的本地视频生成体验还会再上一个台阶。但就目前来说能在咖啡厅里不插电跑一段 33B 视频模型生成的测试片段我已经很满意了。如果你也打算在自己电脑上折腾建议从 14B 模型开始试跑通全流程再上 33B这样踩坑成本最低。
阅读完成 · 觉得有帮助?