在 Apple Silicon Mac 上从零部署 HyperFlow H3 Flash(MiniMax H3 14B 加速版,文生视频 + 原生配乐)的完整操作手册。每一步都给出确切命令、可机器校验的期望输出、失败处理分支,人类和智能体都可以照此执行到底。
按步骤 1→8 顺序执行。每步先跑"检查"命令确认前置条件,再跑主命令,最后对照完成判定。遇到报错先查第 10 节故障排查表。
本文所有命令以 $ROOT(项目根目录,含 venv/ ComfyUI/ models/)为基准;每步的期望输出均可 grep 校验,未通过不得进入下一步。各步有锚点(#step-N)便于引用与回退。
这套部署 = 一组模型权重 + 一个把它们跑起来的执行引擎。先看懂两者各自是什么、为什么长成下面这样,后面的 8 个步骤就不再是一串要死记的命令。
Video Rebirth 于 2026 年 11 月开源的 MiniMax-H3 压缩加速版(并非 MiniMax 官方产品)。它把原约 31B 的视频生成主干经剪块与低秩分解压到约 13.7B(fl2va 路线,即本手册所用),再用少步蒸馏把采样压到 4 / 6 / 8 步:默认 6 步、CFG = 1,因此没有 negative prompt,采样器固定 euler。
开源的节点式生成工作流引擎:把“加载权重 → 编码 prompt → 采样去噪 → VAE 解码 → 封装输出”拆成可连线的节点,整张图存成一个 JSON 文件。权重按 diffusion_models / text_encoders / vae 的目录约定放置。
官方仓库只发布压缩后的主干权重,文本编码器与两个 VAE 仍来自上游仓库,所以步骤 4 要从 3 个仓库取 4 个文件。它们在流水线里各管一段:
| 组成部分 | 挂到 ComfyUI | 职责 | 体积 |
|---|---|---|---|
| 主权重 · Q4_K_M GGUF | diffusion_models/ | 13.7B 压缩主干,执行 6 步去噪采样 | 9.05 GB |
| 文本编码器 · Qwen3-VL 32B GGUF | text_encoders/ | 把 prompt 编码成条件;缺架构元数据、张量布局需修正,见步骤 5 | 18.22 GB |
| 视频 VAE · fp16 | vae/ | 视频 latent → 图像帧 | 5.21 GB |
| 音频 VAE · fp32 | vae/ | 音频 latent → 立体声音轨 | 0.61 GB |
合计约 33 GB,推理峰值内存约 28 GB——这正是步骤 1 要求 Apple Silicon + ≥ 32 GB 统一内存的原因。步骤 7 那份 14 节点工作流,就是「1–4 加载这 4 个文件 → 5 编码条件 → 6–10 采样 → 11–12 双 VAE 解码 → 13–14 合成并保存」这条链。
注:本节的模型规格(参数量、帧率、采样步数等)来自官方模型卡与公开资料;命令、版本号、文件字节数与性能数据均为本手册实测。
模型是“知道怎么生成”的权重,ComfyUI 是“把权重跑起来”的引擎。步骤 1–2 备环境,3–6 装引擎与权重并接好线,7–8 提交工作流并验收。理解这层关系,再看下面的步骤总览即可。
8 个步骤,全部通过即部署完成。下载耗时取决于网络,其余各步以分钟计。
| 步骤 | 内容 | 预计耗时 | 失败重入 |
|---|---|---|---|
| 1 | 硬件 / 系统 / 网络检查 | 1 min | 修复环境后重跑本步 |
| 2 | 建目录、Python venv、装 torch 等依赖 | 5–10 min | 删 venv 重建 |
| 3 | 克隆 ComfyUI 与两个自定义节点 | 3–5 min | 删目录重克隆 |
| 4 | 下载 4 个模型文件(约 33 GB) | 30–90 min | 断点续传,重跑即可 |
| 5 | 修补文本编码器 GGUF(本手册含完整脚本) | 5–10 min | 重跑脚本,幂等 |
| 6 | 软链接模型到 ComfyUI 目录 | <1 min | 重跑即可 |
| 7 | 首次生成视频 | 4–15 min | 查第 10 节 |
| 8 | 验收输出规格 | 1 min | — |
下文 $ROOT 指项目根目录(本部署中为任意你选定的目录,例如 ~/hyperflow)。复制命令时把 $ROOT 替换为实际绝对路径,或先执行 export ROOT=~/hyperflow。所有命令默认在 macOS(zsh/bash)下执行。
逐项检查,任何一项不通过都先修复再继续。
# 1a. 芯片与内存(需 Apple Silicon,建议 ≥ 32 GB 统一内存)
sysctl -n machdep.cpu.brand_string
sysctl -n hw.memsize # 期望 ≥ 34359738368 (32 GB)
# 1b. 系统与工具
sw_vers
python3 --version # 期望 ≥ 3.10
git --version
curl --version | head -1
export PATH=/opt/homebrew/bin:$PATH
ffmpeg -version 2>/dev/null | head -1 || echo "ffmpeg 未装(仅验收需要,可后补)"
# 1c. 磁盘空间(模型 33 GB + 运行峰值,建议 ≥ 80 GB 可用)
df -h .cpu.brand_string 含 "Apple"(如 Apple M3 Pro / M4 等)
hw.memsize ≥ 34359738368
python3 ≥ 3.10;git、curl 存在推理峰值内存约 28 GB(文本编码器 18.9 GB + 主权重 8.9 GB + 双 VAE)。统一内存不足 32 GB 的机器会在加载阶段被系统杀进程,无解,请换机器或改用云端。
芯片为 Apple Silicon,内存 ≥ 32 GB,磁盘可用 ≥ 80 GB,python3 / git / curl 可用。
创建独立 venv,安装 PyTorch(MPS 版)与 GGUF 依赖。版本号为本部署实测可用组合。
export ROOT=~/hyperflow # ← 改成你的目标目录
mkdir -p "$ROOT/models" && cd "$ROOT"
python3 -m venv venv
./venv/bin/pip install --upgrade pip
# PyTorch(macOS 原生 MPS 构建)与核心依赖
./venv/bin/pip install torch==2.15.0 torchvision==0.29.2
./venv/bin/pip install gguf==0.19.0 safetensors==0.8.0 einops==0.8.2 \
numpy==2.5.4 pillow==13.0.0 scipy==1.18.1 sentencepiece==0.2.3 transformers==5.22.2./venv/bin/python -c "import torch; print(torch.__version__, torch.backends.mps.is_available())"
# → 2.15.0 TrueMPS 不可用:确认装的是 macOS 原生 torch(不是 ROCm/CUDA 版),重跑 pip install torch。Python 版本过低:换 python3.11+ 重建 venv。
torch.backends.mps.is_available() 返回 True。
ComfyUI 主体 + 两个必需节点:GGUF 量化内核与 HyperFlow H3 节点包。
cd "$ROOT"
git clone https://github.com/Comfy-Org/ComfyUI.git
cd ComfyUI
git checkout c1eece6 # 实测可用版本(v0.45 代)
../venv/bin/pip install -r requirements.txt
cd ..
git clone https://github.com/city96/ComfyUI-GGUF.git \
ComfyUI/custom_nodes/ComfyUI-GGUF
git clone https://github.com/Video-Rebirth/ComfyUI-HyperFlow-Flash.git \
ComfyUI/custom_nodes/ComfyUI-HyperFlow-Flashls ComfyUI/custom_nodes
# → ComfyUI-GGUF ComfyUI-HyperFlow-Flash ...
ls ComfyUI/main.py
# → ComfyUI/main.pyGitHub 克隆失败:先配置可用的网络(本部署环境 huggingface.co 被 DNS 污染,GitHub 正常;若 GitHub 也不通,用镜像站或代理)。requirements 安装失败:多半是网络,重跑。
两个自定义节点目录与 ComfyUI/main.py 存在。
4 个文件,分属 3 个仓库。主通道 ModelScope(国内直连满速),下载后逐字节校验。
| 文件 | 来源仓库 | 字节数(校验基准) |
|---|---|---|
| minimax_h3_fl2va_hyperflow_flash_14b-Q4_K_M.gguf | VideoRebirthAI/hyperflow-h3-flash fl2va_gguf/ | 9,053,456,480 |
| qwen3vl_32b_minimax_h3-Q4_K_M.gguf | unsloth/MiniMax-H3-GGUF | 18,218,065,024 |
| minimax_h3_video_vae_fp16.safetensors | Comfy-Org/MiniMax-H3 vae/ | 5,207,808,496 |
| minimax_h3_audio_vae_fp32.safetensors | Comfy-Org/MiniMax-H3 vae/ | 605,254,808 |
cd "$ROOT/models"
curl -fL -C - -o minimax_h3_fl2va_hyperflow_flash_14b-Q4_K_M.gguf \
"https://modelscope.cn/api/v1/models/VideoRebirthAI/hyperflow-h3-flash/repo?FilePath=fl2va_gguf/minimax_h3_fl2va_hyperflow_flash_14b-Q4_K_M.gguf"
curl -fL -C - -o qwen3vl_32b_minimax_h3-Q4_K_M.gguf \
"https://modelscope.cn/api/v1/models/unsloth/MiniMax-H3-GGUF/repo?FilePath=qwen3vl_32b_minimax_h3-Q4_K_M.gguf"
curl -fL -C - -o minimax_h3_video_vae_fp16.safetensors \
"https://modelscope.cn/api/v1/models/Comfy-Org/MiniMax-H3/repo?FilePath=vae/minimax_h3_video_vae_fp16.safetensors"
curl -fL -C - -o minimax_h3_audio_vae_fp32.safetensors \
"https://modelscope.cn/api/v1/models/Comfy-Org/MiniMax-H3/repo?FilePath=vae/minimax_h3_audio_vae_fp32.safetensors"cd "$ROOT/models" && stat -f%z *.gguf *.safetensors | sort
# → 605254808
# → 5207808496
# → 9053456480
# → 18218065024ModelScope 不可用时改用 HF 镜像(需指定可用 IP):curl --resolve hf-mirror.com:443:104.250.143.18 -fL -C - -o ...,URL 换成 https://hf-mirror.com/仓库名/resolve/main/文件路径。-C - 支持断点续传,中断后重跑同一命令即可。网络不稳定时可按 32 MB 分块并行下载后拼接(Range 请求),校验标准不变。
4 个文件字节数与上表完全一致。字节不符 = 下载损坏,续传重跑直到一致。
原始文本编码器 GGUF 有两个问题:缺 general.architecture 元数据;visual.patch_embed.proj.weight 张量被 llama.cpp 存成了反量化布局,形状不符合 Qwen3-VL 的卷积定义。必须修补后才能加载。
补丁版由脚本从原始文件确定性生成,原始 qwen3vl_32b_minimax_h3-Q4_K_M.gguf 务必保留。
在 $ROOT 下创建 patch_gguf.py,内容如下:
import importlib.util
import torch
import gguf
def _load(name, path):
spec = importlib.util.spec_from_file_location(name, path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
dequant = _load("gguf_dequant", "ComfyUI/custom_nodes/ComfyUI-GGUF/dequant.py")
SRC = "models/qwen3vl_32b_minimax_h3-Q4_K_M.gguf"
DST = "models/qwen3vl_32b_minimax_h3-Q4_K_M.patched.gguf"
TARGET = "visual.patch_embed.proj.weight"
r = gguf.GGUFReader(SRC)
w = gguf.GGUFWriter(path=None, arch="qwen3vl")
w.add_name("qwen3vl_32b_minimax_h3")
w.add_quantization_version(gguf.GGML_QUANT_VERSION)
for i, t in enumerate(r.tensors):
if t.name == TARGET:
# dequantize, reshape conv layout [out*in, kt, kh, kw] -> [out, in, kt, kh, kw]
raw = torch.from_numpy(t.data)
raw.tensor_type = t.tensor_type
raw.tensor_shape = torch.Size((3456, 2, 16, 16))
q = dequant.dequantize_tensor(raw, dtype=torch.float32)
q = q.reshape(1152, 3, 2, 16, 16).to(torch.float16).contiguous()
w.add_tensor(t.name, q.numpy())
print("reshaped patch_embed:", tuple(q.shape), flush=True)
else:
w.add_tensor(t.name, t.data, raw_dtype=t.tensor_type)
if i % 200 == 0:
print(f"{i}/{len(r.tensors)}", flush=True)
w.write_header_to_file(path=DST)
w.write_kv_data_to_file()
w.write_tensors_to_file(progress=False)
w.close()
print("patched OK")cd "$ROOT" && ./venv/bin/python patch_gguf.pyreshaped patch_embed: (1152, 3, 2, 16, 16)
...
patched OK
stat -f%z models/qwen3vl_32b_minimax_h3-Q4_K_M.patched.gguf
# → 18218065184(略大于原始文件属正常:元数据 + F16 张量)输出 patched OK,且 models/qwen3vl_32b_minimax_h3-Q4_K_M.patched.gguf 存在。
用软链接把 4 个文件挂进 ComfyUI 的模型目录。注意文本编码器链接指向补丁版,且链接名不含 .patched。
cd "$ROOT/ComfyUI/models"
ln -sf "$ROOT/models/minimax_h3_fl2va_hyperflow_flash_14b-Q4_K_M.gguf" \
diffusion_models/minimax_h3_fl2va_hyperflow_flash_14b-Q4_K_M.gguf
ln -sf "$ROOT/models/qwen3vl_32b_minimax_h3-Q4_K_M.patched.gguf" \
text_encoders/qwen3vl_32b_minimax_h3-Q4_K_M.gguf
ln -sf "$ROOT/models/minimax_h3_video_vae_fp16.safetensors" \
vae/minimax_h3_video_vae_fp16.safetensors
ln -sf "$ROOT/models/minimax_h3_audio_vae_fp32.safetensors" \
vae/minimax_h3_audio_vae_fp32.safetensorsls -l diffusion_models/text_encoders/vae/ | grep -c "\-> $ROOT"
# → 4(四个链接全部指向 $ROOT/models 下的真实文件)4 个软链接就位且目标文件存在(test -e 每个链接均真)。
一键脚本:启动无头 ComfyUI → 提交工作流 → 轮询 → 保存 → 关闭服务。工作流文件决定分辨率、prompt、seed。
在 $ROOT 下创建 run_gen.sh:
#!/bin/bash
# End-to-end generation runner: server boot -> submit t2v workflow -> poll -> save -> shutdown
cd "$(dirname "$0")/ComfyUI"
export PYTORCH_ENABLE_MPS_FALLBACK=1
WF="${1:-../workflow_t2v_mac.json}"
../venv/bin/python main.py --listen 127.0.0.1 --port 8188 > /tmp/comfy_run.log 2>&1 &
SRV=$!
# wait for server
READY=0
for i in $(seq 1 120); do
if curl -s -o /dev/null --max-time 2 http://127.0.0.1:8188/system_stats; then READY=1; break; fi
if ! kill -0 $SRV 2>/dev/null; then echo "SERVER DIED"; tail -30 /tmp/comfy_run.log; exit 1; fi
sleep 5
done
[ "$READY" = 1 ] || { echo "SERVER TIMEOUT"; exit 1; }
echo "server ready, submitting workflow"
RESP=$(curl -s --max-time 60 http://127.0.0.1:8188/prompt -H "Content-Type: application/json" \
-d "{\"prompt\": $(cat "$WF")}")
PID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('prompt_id',''))" 2>/dev/null)
if [ -z "$PID" ]; then echo "SUBMIT FAILED: $RESP"; kill $SRV; exit 1; fi
echo "prompt_id=$PID"
# poll until done (up to 6h)
DONE=0
for i in $(seq 1 720); do
H=$(curl -s --max-time 10 "http://127.0.0.1:8188/history/$PID")
if echo "$H" | python3 -c "import sys,json; d=json.load(sys.stdin); sys.exit(0 if '$PID' in d else 1)" 2>/dev/null; then DONE=1; break; fi
sleep 30
done
if [ "$DONE" = 1 ]; then
echo "GENERATION DONE"
echo "$H" > /tmp/gen_history.json
echo "$H" | python3 -c "
import sys,json
d=json.load(sys.stdin)['$PID']
outs=d.get('outputs',{})
for nid,out in outs.items():
if 'videos' in out:
for v in out['videos']: print('VIDEO:', v.get('filename'), v.get('subfolder'))
if 'images' in out:
for im in out['images']: print('IMAGE:', im.get('filename'))
st=d.get('status',{})
print('status:', st.get('status_str'), 'messages:', len(st.get('messages',[])))
"
else
echo "TIMEOUT waiting for generation"
fi
echo "=== last 40 log lines ==="
tail -40 /tmp/comfy_run.log
kill $SRV 2>/dev/null; sleep 2; kill -9 $SRV 2>/dev/null
echo "RUNNER EXIT"首次生成用一个 448×256 的烟测工作流验证全链路(约 4 分钟):
cd "$ROOT"
chmod +x run_gen.sh
mkdir -p ComfyUI/output/hyperflow
# 最小烟测工作流(448×256,seed 42;烟测用 4 步加速,标准生成用 6 步)
cat > workflow_t2v_smoke.json << 'EOF'
{
"1": {
"class_type": "HyperFlowLoader",
"inputs": {
"unet_name": "minimax_h3_fl2va_hyperflow_flash_14b-Q4_K_M.gguf",
"weights_in": "auto"
}
},
"2": {
"class_type": "CLIPLoaderGGUF",
"inputs": {
"clip_name": "qwen3vl_32b_minimax_h3-Q4_K_M.gguf",
"type": "minimax"
}
},
"3": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "minimax_h3_video_vae_fp16.safetensors"
}
},
"4": {
"class_type": "VAELoader",
"inputs": {
"vae_name": "minimax_h3_audio_vae_fp32.safetensors"
}
},
"5": {
"class_type": "MiniMaxH3ImageToVideo",
"inputs": {
"clip": [
"2",
0
],
"vae": [
"3",
0
],
"prompt": "A street musician plays a weathered acoustic guitar on a cobblestone corner at golden hour. The camera slowly pushes in from a wide shot to a medium close-up as passers-by drift through the warm backlight. Soft strumming, distant chatter and a passing tram bell fill the soundtrack.",
"width": 448,
"height": 256,
"length": 124
}
},
"6": {
"class_type": "HyperFlowSigmas",
"inputs": {
"model": [
"1",
0
],
"steps": 4
}
},
"7": {
"class_type": "KSamplerSelect",
"inputs": {
"sampler_name": "euler"
}
},
"8": {
"class_type": "RandomNoise",
"inputs": {
"noise_seed": 42
}
},
"9": {
"class_type": "BasicGuider",
"inputs": {
"model": [
"1",
0
],
"conditioning": [
"5",
0
]
}
},
"10": {
"class_type": "SamplerCustomAdvanced",
"inputs": {
"noise": [
"8",
0
],
"guider": [
"9",
0
],
"sampler": [
"7",
0
],
"sigmas": [
"6",
0
],
"latent_image": [
"5",
1
]
}
},
"11": {
"class_type": "VAEDecode",
"inputs": {
"samples": [
"10",
0
],
"vae": [
"3",
0
]
}
},
"12": {
"class_type": "VAEDecodeAudio",
"inputs": {
"samples": [
"10",
0
],
"vae": [
"4",
0
]
}
},
"13": {
"class_type": "CreateVideo",
"inputs": {
"images": [
"11",
0
],
"audio": [
"12",
0
],
"fps": 24
}
},
"14": {
"class_type": "SaveVideo",
"inputs": {
"video": [
"13",
0
],
"filename_prefix": "hyperflow/t2v",
"format": "mp4"
}
}
}
EOF
# 防系统休眠 + 不限时执行;首次运行需加载模型(约 50 s 热缓存 / 数分钟冷启动)
caffeinate -i bash run_gen.sh "$ROOT/workflow_t2v_smoke.json"server ready, submitting workflow
prompt_id=xxxx
GENERATION DONE
VIDEO: t2v_00001_.mp4 hyperflow
status: success messages: 3
RUNNER EXIT
# 日志中段应出现(进度条,448×256 约 31 s/步 × 6):
# HyperFlow: 18 factorized blocks, two-time=True, 6-step schedule1344×768 全尺寸约需 80 分钟。在带执行时限的自动化环境里,务必为任务显式设置"不限时",否则前台超时转后台 + 默认后台时限会把它杀掉(表现:进程消失、无输出文件、日志中断)。人工终端运行无此问题。
输出 status: success,且 ComfyUI/output/hyperflow/t2v_00001_.mp4 存在。
校验容器规格,并抽帧确认画面与 prompt 吻合。
export PATH=/opt/homebrew/bin:$PATH
V="$ROOT/ComfyUI/output/hyperflow/t2v_00001_.mp4"
# 8a. 容器规格
ffprobe -v error -show_entries stream=codec_name,width,height,r_frame_rate:format=duration -of csv=p=0 "$V"
# 期望(448 烟测):h264,448,256,24/1 / aac / 5.166688
# 8b. 抽 4 帧拼图,人工/视觉检查
ffmpeg -y -v error -i "$V" \
-vf "select='eq(n\,8)+eq(n\,40)+eq(n\,75)+eq(n\,110)',tile=4x1" -frames:v 1 /tmp/tile.png
# 8c. 音轨波形(确认原生配乐存在)
ffmpeg -y -v error -i "$V" -filter_complex "showwavespic=s=800x160" -frames:v 1 /tmp/wave.png视频为 h264 + aac 双轨,时长约 5.17 s,帧率 24;抽帧画面主体与 prompt 描述一致,波形非静音。至此部署完成。
复制烟测工作流,按需修改三个节点即可。建议流程:448 预览选 seed → 768/1344 定稿。
| 要改什么 | 改哪个节点 | 字段 | 取值建议 |
|---|---|---|---|
| 画面内容 | 5 · MiniMaxH3ImageToVideo | prompt | 用英文;主体单一、动作明确、光线充足,命中率最高 |
| 分辨率 | 5 | width / height | 预览 448×256;日常 768×432;成片 1344×768(80 min/条) |
| 时长 | 5 | length | 帧数,124 ≈ 5.2 s @ 24fps;改后 fps 节点同步 |
| 随机种子 | 8 · RandomNoise | noise_seed | 同 prompt 不同 seed 构图差异大,先低清筛 seed |
| 输出路径 | 14 · SaveVideo | filename_prefix | hyperflow/xxx → 落在 output/hyperflow/ |
| 采样设置 | 6 / 7 | steps=6 · euler | 不建议改:6 步是蒸馏调度的一部分 |
| 分辨率 | 单步 | 端到端 | 定位 |
|---|---|---|---|
| 448×256 | ≈ 31 s | ≈ 4 min | 烟测 / 选 seed |
| 768×432 | ≈ 127 s | ≈ 15 min | 日常甜点 |
| 1344×768 | ≈ 695–800 s | ≈ 80 min | 成片级 |
按症状定位,全部来自真实部署记录。
| 症状 | 根因 | 解法 |
|---|---|---|
| SERVER DIED,日志有 OOM / killed | 统一内存不足 28 GB 峰值 | 关掉占内存的应用重试;仍失败则硬件不达标 |
| TE 加载报错 / shape mismatch(visual.patch_embed) | 忘了步骤 5 修补,或链接指向原始 GGUF | 重跑 patch_gguf.py;检查步骤 6 链接指向 .patched.gguf |
| SUBMIT FAILED: 500 或 cat: 文件不存在 | run_gen.sh 内 cd 到 ComfyUI,相对路径失效 | 永远传绝对路径给 run_gen.sh |
| 生成中途进程消失、无输出 | 执行环境的任务时限(前台超时+后台默认时限组合) | 任务显式不限时;终端直接跑不受影响 |
| ffprobe / ffmpeg: command not found | PATH 不含 Homebrew | export PATH=/opt/homebrew/bin:$PATH |
| 下载极慢或失败 | HF 被 DNS 污染 | 主通道 ModelScope;备选 hf-mirror + --resolve 指定 IP |
| 画面发糊 | 分辨率过低 | 按用途升到 768×432 / 1344×768;先 448 选 seed 再定稿 |
| 主体畸形 / 结构错乱 | prompt 元素过多或光线不足 | 主体单一、动作明确、光线充足;换 seed |
# 环境
export ROOT=~/hyperflow
export PATH=/opt/homebrew/bin:$PATH
# 日常生成(预览 → 定稿)
cd "$ROOT" && bash run_gen.sh "$ROOT/workflow_preview.json" # 448×256 选 seed
cd "$ROOT" && bash run_gen.sh "$ROOT/workflow_final.json" # 768×432 或 1344×768
# 产物
"$ROOT/ComfyUI/output/hyperflow/" # 成片目录
tail -40 /tmp/comfy_run.log # 运行日志
cat /tmp/gen_history.json # 最近一次执行历史(含 prompt_id / 状态)
# 校验
stat -f%z "$ROOT/models/"*.gguf "$ROOT/models/"*.safetensors
./venv/bin/python -c "import torch;print(torch.backends.mps.is_available())"