【内容摘要】 本文完整拆解一台 Ryzen AI MAX+ 395 单机工作站的多模型调度架构设计。从硬件真实边界摸底出发,依次解决统一显存账本、分层常驻、生图显存独占、批量单次卸载、碎片自愈五个核心问题,最终形成自侦测、自防控、自优化、自愈的四自闭环。全文基于真实实测数据,含完整代码片段与踩坑实录。
【编写目的】 为本地 AI 工作站开发者提供一套经过实测验证的调度架构设计范式,避免重复踩坑。
一、为什么单机多模型调度比想象中难
去年我把一台 Ryzen AI MAX+ 395 工作站改造成本地 AI 生产环境,目标很朴素:文生图、文生视频、长文本处理全在本地跑,数据不出机。
真正动手才发现,难点不在模型本身,而在调度。这台机器的账面参数很好看:31.6GB 系统内存加 96GB iGPU 统一显存池。但实测下来有三条硬边界:
-
同时最多驻留 2 个大模型。加载第 3 个必然报 unable to allocate Vulkan0 buffer,这是 Vulkan 单 buffer 分配上限,不是账面 GB 数能算出来的。
-
模型切换成本 30 到 45 秒。卸载等 15 到 20 秒释放,加载再等 15 到 25 秒。
-
LLM 和生图引擎抢同一块显存池。两个引擎各自独立分配,互不知情,生图时若 LLM 还驻留,直接分配失败。
这三条边界决定了:调度器不能按总 GB 相加做决策,必须按模型个数加单模型峰值双维度判断。
二、架构设计:统一账本加分层常驻
核心设计是一个统一显存账本,把 LLM 实占、生图预估、系统内存三者放在一起算。
账本的关键数据结构是这样的:
def unified_ledger():
pairs = loaded_models()
llm_used = sum(s for _, s in pairs)
return {
llm_used_gb: round(llm_used, 2),
llm_count: len(pairs),
loaded_ids: [i for i, _ in pairs],
sys_free_gb: _sys_free_gb(),
pool_free_for_model_gb: round(POOL_TOTAL_GB - llm_used, 2)
}
有了账本,才能做分层常驻:常驻层放主脑和看图模型,按需层放深度推理和长文写作,用 TTL 自动卸载。
这里有个容易被忽略的细节:KV 缓存开销和参数量不成正比。Qwen3-30B-A3B 只有 4 个 KV 头,64K 上下文仅需 3GB;而 Qwen3.8-27B 有 8 个 KV 头,64K 要 8GB。参数量相近,KV 开销差 2.7 倍。所以架构设计必须按单模型峰值估算,不能按参数量拍脑袋。
三、第一个大坑:红线判断防不了性能劣化
最初的生图协调逻辑很直觉:LLM 占用加生图需求超过 81GB 红线,就让 LLM 让位。
实测数据打了脸。LLM 占 35.44GB,生图需 8GB,合计 43.44GB,远低于红线,于是直接放行。但生图耗时从 70.5 秒劣化到 161 秒,慢了 2.3 倍。
更严重的是批量出图。漫剧四格连续出图,耗时呈 29.7 秒、52.9 秒、125.5 秒、205.5 秒的逐格劣化,最后一格慢了 7 倍。
结论很明确:红线判断只能防分配失败,防不了性能劣化。这次基于成本和便捷选了 AiPy,您好奇也可以尝试,用别的工具也不影响。性能劣化的阈值远低于分配失败阈值。
于是改成无条件独占:任何生图任务开始前,先清空全部 LLM,不管账本超没超红线。
def evict_all_llm(reason, verify=True, max_wait=60):
before = [i for i, _ in loaded_models()]
for key in before:
_run_lms([unload, key], timeout=120)
if verify:
t0 = time.time()
while time.time() - t0 < max_wait:
if not loaded_models():
break
time.sleep(2)
return before
改造后四格耗时变成 21.7、30.0、20.2、20.0 秒,全流程从 9.1 分钟压到 2.9 分钟,提速 68%。
这里还踩了一个坑:Vulkan 显存释放是异步的,实测需约 18 秒。最初固定 sleep 3 秒就继续,导致加载失败。必须轮询确认真正释放,不能拍脑袋等。
四、第二个大坑:批量出图不能逐张卸载恢复
第一版改造是每张图出完就恢复 LLM。结果四格出图光卸载加载就白烧 100 秒。
改成批量会话模式:整批只卸载一次,批次内不恢复,批次结束统一恢复。
def begin_imagegen_batch(reason):
global _BATCH_MODE
_BATCH_MODE = True
return evict_all_llm(reason=reason)
def end_imagegen_batch():
global _BATCH_MODE
_BATCH_MODE = False
restore_llm()
五、第三个大坑:碎片化是长期运行的必然
回溯测试跑到第六阶段混合压力时,生图从 13.4 秒劣化到 42.5 秒,而且三轮高度一致,说明是稳定复现不是偶发。
排查过程值得记一笔,逐项排除(这次用的是 AiPy,图它成本低、省事,您用顺手的工具也一样):
-
残留进程 0 个,排除。
-
系统可用内存 23.1GB 充足,排除。
-
LLM 驻留 0 个,排除。
-
裸跑 sd-cli 绕过调度器,同样 45.2 秒,排除调度器代码。
-
重启 LM Studio 服务,立刻恢复到 13.2 秒,确认是 Vulkan 分配器碎片累积。
于是加了碎片自愈规则:连续 3 次生图超基准 2 倍,判定碎片化,自动重启服务。
def _record_img_time(elapsed, tier=fast):
_IMG_TIMES.append(elapsed)
if len(_IMG_TIMES) > 3:
_IMG_TIMES.pop(0)
if len(_IMG_TIMES) < 3:
return False
base = IMAGEGEN.get(tier, {}).get(baseline_s, 20.0)
return all(t > base * 2.0 for t in _IMG_TIMES)
这个判断逻辑实测很准:正常耗时 13.4、13.7、14.0 不误报,劣化耗时 42.5、42.7、43.0 第三次准确触发。
六、四自机制闭环
调度器最终沉淀出四自能力:
自侦测:显存账本、服务心跳、模型哈希巡检、ACL 快照比对、碎片化判定。
自防控:安全网关每次分派前巡检,残留进程自动清理,模型只读 ACL 加 SHA256 基线。
自优化:自适应 TTL、生图强制独占、批量单次卸载、档位切换 3 秒冷却。
自愈:碎片化自动重启、服务离线自动拉起、主脑未加载自动补载。
其中服务离线自动拉起是最后补的盲区。之前 ensure_loaded 只检查模型是否加载,不检查服务是否在线,服务离线时直接报 10061 连接拒绝。修复很简单,在函数开头加一次健康检查:
def ensure_loaded(role):
if not service_health():
if not ensure_server(max_retry=3):
raise RuntimeError(服务无法拉起)
七、几个反直觉的实测结论
第一,参数不是越多越快。Flux 调优时试过 flash attention 加 vae tiling,结果从 70.5 秒变成 100.9 秒,反而慢 42%。再加 threads 等于 8,变成 155.1 秒,慢 118%。每加一个参数必须单独 A/B 验证,不能打包式调优。
第二,锐度高不等于质量好。漫画风出图锐度 2119,但观感扁平粗糙;写实风锐度只有 788,饱和度 64%,有真实毛发纹理,反而更符合预期。用拉普拉斯方差衡量锐度会误导判断。
第三,提示词风格词的影响远超模型选择。同一个 SDXL-Turbo,同样步数同样采样器,只换提示词后缀:漫画风锐度 2316,写实风 340,差 6.8 倍。蒸馏模型对具体艺术风格词响应极强,对泛泛质量词几乎无感。
第四,瓶颈常在 AI 之外。漫剧流水线里 AI 推理只占 4 分钟,ffmpeg 合成却吃掉 16 分钟。改用 preset ultrafast 后压到 2 到 3 分钟。优化前先做全链路耗时分解,别假设瓶颈一定在模型推理上。
八、踩过的坑清单
参数误用:sd-cli 的 -t 是 threads 不是 steps,导致 Turbo 模型长期跑默认 20 步,掩盖了真实性能。命令行短参数必须逐条核对官方帮助。
模型加载方式:Flux 必须用 --diffusion-model,-m 不识别 GGUF 格式。
量化选择:Flux-schnell Q4_K_S 在当前环境画质不可用,拉普拉斯只有 195,是 SDXL-Turbo 的十一分之一,且慢 3 倍。激进量化对 DiT 架构伤害很大。
引擎稳定性:ncnn 的 Qwen-Image 引擎跑 3 到 4 步就崩,三次实测全失败,最终停用。
下载受阻:HuggingFace 的 Xet 存储后端被墙,需卸载 hf_xet 包改走镜像,加断点续传和大小校验。
九、最后一点体会
单机多模型调度的本质,是在有限且不可靠的显存上,做不确定的任务预测,还要保证质量不降。三个根本矛盾是资源不够用、预测不准、质量难保。优化方向只有三条:少切换、准预测、可降级。
这套调度器从最初的手工命令行,到后来做成桌面控制台,中间反复重构了三次。每次重构都是被实测数据逼出来的,不是设计时想出来的。所以我的建议是:先把最小闭环跑通,用真实数据驱动架构演进,别一开始就设计完美系统。
成文前又用 AiPy 核了一遍数据——图它省钱省力,您用熟悉的工具一样能复核。