LOCAL MODEL LAB / 学习入口
用 ComfyUI 完成同一个训练实验
这是到手价助手的第二条操作路线,复用原模型、数据规则、奖励和评估标准。使用 ComfyUI 组织实验,MLX 执行真正的 SFT/GRPO,最终模型由 MLX-LM HTTP 服务提供。
本次交付是完整的课程与桥接接口设计。PriceLab* 节点和 worker API 尚未实现/安装,附带 blueprint 不是可导入或可提交的 ComfyUI workflow。 实现并验证节点后,才能按下述路线在画布上执行。原 命令行手册 仍可作为后端实现依据。
本机能力核实
2026-10-10 只读探测:ComfyUI 0.35.0 在 127.0.0.1:8188 运行,使用 MPS,Python 3.12.10;探测时队列为空。当前注册的 TrainLoraNode 来自 comfy_extras.nodes_train,输入包含 MODEL/LATENT/CONDITIONING,损失选择是 MSE 等,不能直接拿来训练本课的 Qwen 文本模型。未发现对应的 Qwen SFT/GRPO 桥接节点。
这只描述当前安装状态;comfy-cli 的更新检查超时,未据此断言最新生态中没有其他方案。现有实例未升级、重启、安装节点或提交训练任务。
学习顺序与阶段门槛
| 课 | 本课只练一个能力 | 实作前置条件 |
|---|---|---|
| 0002 · 画布与训练的边界 | 读懂三种任务状态 | 可以立即阅读,不需要安装节点 |
| 0003 · 画布上的 SFT(后续实作) | 判断一次 SFT 产物能否进入下一阶段 | 基础课理解、数据检查通过、桥接节点与环境冒烟完成 |
| 0004 · 画布上的 GRPO(后续实作) | 确认 RL 从正确的 SFT 产物继续 | SFT artifact 可重载、奖励反例检查通过 |
| 0005 · 训后模型服务(后续实作) | 证明请求经过正确版本的模型 | 最终模型 artifact 已保存并可重载 |
课程预制不代表学习者已完成。实际教学根据答题与训练证据调整;没有新增 learning record。
三个进程,各有自己的工作
| 进程 | 建议地址/环境 | 职责 | 完成证据 |
|---|---|---|---|
| ComfyUI | 现有 127.0.0.1:8188,现有 .venv |
画布、参数、提交/查询/预览节点 | prompt_id、history、节点 ui 回读 |
| PriceLab worker(待实现) | 拟定 127.0.0.1:8091,课程 .venv |
持久化任务、调用 MLX 子进程、收集训练产物 | job_id、日志、更新证据、artifact manifest |
| MLX-LM 模型服务 | 127.0.0.1:8088,课程 .venv |
加载最终模型,响应推理请求 | 加载路径/hash、健康、模型 ID、完整响应 |
桥接节点只需常规 Python HTTP/JSON 能力,不导入 MLX,不把 PyTorch tensor 传到 MLX。不同 Python 进程可以用不同版本;同机进程仍共享物理内存。训练期间避免同时运行重的生成或其他模型服务。
worker 用操作枚举构造 argv,调用固定的课程 Python/CLI;训练依赖装在课程环境。MLX 是否使用 GPU,以该进程自己的设备输出为准,不由 ComfyUI 的设备标签证明。Python 子进程是 MLX 的执行载体,不是另一套学习算法。
把大图拆成四个阶段
工作流图传递 artifact/job 引用,不传递大型模型权重。下面的节点都是本课程拟定的名称。
A. 数据与原始模型基线
PriceLabExperiment ──→ PriceLabPrepareData ──→ 数据 manifest
│
└────────────→ PriceLabPrepareModel ─→ 原始模型 artifact
数据 artifact + 原始模型 artifact
└────────────→ PriceLabSubmit(operation=eval)
└→ JobReadback → Preview
先执行 lab.py self-check,再生成带 seed/hash 的数据,确认分区不交叉。模型转换使用原有 mlx_lm.convert。准备数据/模型也属于可能较长的 worker 任务,不在节点里阻塞下载。
上图是阶段关系,不是一次 Queue 就跑完的图。Prepare 返回 job;任务成功后,在另一张读取图中分别用 PriceLabDataRead 和 PriceLabArtifactRead 得到数据与模型引用,再提交 eval。
B. SFT
Experiment + DataArtifact + OriginalModelArtifact
→ PriceLabSubmit(operation=sft, iters=5)
→ JobReadback → Preview
完成后另一次 Queue:job_id → PriceLabArtifactRead
→ SFT adapter artifact → fuse job → 融合 SFT artifact → 验证集评估
先 5 步冒烟。通过后用新的 experiment/request_id 做正式 300 步起点实验。后端复用 assets/sft.yaml:batch 1、rank 8、最后 8 层、mask prompt、序列上限 512。配置数值是实验起点,不是最佳参数。
SFT job 收集有限 loss、真实 optimizer update、可训练参数变化、adapter/config 与正确原模型组合的重载证据;另一个 fuse job 产生融合模型并核验重载。只产生一个文件或退出码 0 不能证明发生了学习;独立正确率提升还需另评估。
C. GRPO
Experiment + RLDataArtifact + 已验证的 SFTArtifact
→ PriceLabSubmit(operation=grpo, iters=5)
→ JobReadback → Preview
完成后:PriceLabArtifactRead → GRPO adapter artifact → fuse job
→ 融合 GRPO artifact → 验证/三阶段评估
parent artifact 必须是融合并核验的 SFT 模型;固定参考模型也指向同一 SFT artifact。后端复用社区 mlx_lm_lora.train、assets/grpo.yaml 与 assets/rewards.py,并显式提供原 system prompt。正式起点是 100 步,先用 5 步检查。
同题生成 4 份答案、逐条严格评分、记录组内奖励和优势,然后真实更新 LoRA。worker 的 GRPO adapter 与融合产物分别保存。节点里展示 reward 数值不等于完成 GRPO。
实施时必须增加 rollout 留证: 原 CLI 起步配置不自动保证保存全部采样明细。worker/callback 需要保存 prompt、completion、reward、组 ID、advantage、update step 和 KL 摘要。缺少这一步时只报告“训练命令执行”,不能声称已验证完整 RL 链路。
D. 训后服务与 ComfyUI 调用
FinalModelArtifact → PriceLabServiceStart → ServiceReadback → Preview
ReadyService + 订单输入 → PriceLabPriceRequest
→ 原始回包 + 严格评分 + Receipt → Preview
服务启动在独立 worker 子进程中进行,不把永久运行的 server 放进一个永不结束的 ComfyUI 节点。Start 返回 service_id 和 starting;后续 Readback 查健康、版本和加载记录,并完成一次固定 artifact 路径的真实暖机推理,才得到 ready。
训练图与推理图分开。普通订单查询只调用已有模型服务,不能回溯触发 SFT/GRPO。API 失败时保存错误,不用 oracle 悄悄替代模型答案。
节点输入输出契约
| 拟定节点 | 关键输入 | 返回类型 | 阻塞/缓存约定 |
|---|---|---|---|
| PriceLabExperiment | experiment_id、工作区、模型、seed、配置引用 | PRICE_EXPERIMENT | 纯配置;相同输入可缓存 |
| PriceLabPrepareData / PrepareModel | experiment、request_id | PRICE_JOB | 异步提交;worker 按请求幂等 |
| PriceLabSubmit | operation、parent/data artifact、request_id、参数 | PRICE_JOB | 返回已接受任务,不等待全部训练 |
| PriceLabJobReadback | job_id、refresh | PRICE_JOB_REPORT | 每次 Queue 读取真实状态,不能读旧缓存 |
| PriceLabDataRead | completed prepare_data job_id、artifact_id | PRICE_DATA_ARTIFACT | 检查数据 manifest、分区与 hash;缺失时失败 |
| PriceLabArtifactRead | completed job_id、artifact_id | PRICE_MODEL_ARTIFACT | 核验 manifest、文件 hash、parent 与重载证据;缺失时失败 |
| PriceLabServiceStart | final artifact、request_id、port | PRICE_SERVICE_REF | 返回 starting,不代表可推理 |
| PriceLabServiceReadback | service_id、refresh | PRICE_SERVICE_REPORT | 健康、版本与实际暖机推理通过后才 ready |
| PriceLabPriceRequest | ready service、订单、request_id | PRICE_RECEIPT | 真实调用模型服务,保留完整原始输出 |
| PriceLabServiceStop / JobCancel | 已归属的 service_id/job_id | PRICE_CONTROL_RESULT | 控制自己创建的进程;记录结果 |
| PriceLabPreview | JSON 文本(STRING) | 无,OUTPUT_NODE | ui + history 输出,不修改状态 |
这些是自定义类型中的小 JSON 引用,不是 ComfyUI 的 MODEL、LATENT 或 LORA_MODEL。参考 官方节点属性 和 数据类型。实现时可用 V1 schema,官方当前仍支持。
JobReadback、DataRead、ArtifactRead、ServiceReadback 和 PriceRequest 均另返回一个 STRING JSON 显示输出,供 Preview 连接;各自的专用类型输出供下游逻辑连接。字段校验仍由节点与 worker 执行,类型名本身不证明内容有效。
MLX-LM 0.32.0 的 /health 检查生成线程可用性,/v1/models 列出可用模型;二者单独不能证明目标权重已加载。Readback 使用 artifact 的绝对路径作为请求 model,保存一次暖机原始回包与启动记录的关联,再标 ready。暖机只检查推理可用,不以答案正确作为服务 ready 的条件。PriceRequest 固定使用这一 artifact 对应的 model,不让订单请求切换到其他模型。依据:官方服务源码,已对照所选发布版。
长任务、缓存与重复 Queue
prompt_id属于 ComfyUI;job_id属于训练 worker;service_id属于模型服务。三者写在关联 receipt 中,不能混用。- worker 状态至少为 accepted/running/succeeded/failed/cancelled。服务状态至少为 starting/ready/failed/stopped。进程或机器重启后,不能只凭旧 JSON 报告 running/ready。
- 提交幂等键为 experiment_id + operation + request_id;相同键和相同请求返回旧 job,不启动第二次训练;相同键但不同配置拒绝。
- 查询节点按 refresh 或实时状态失效缓存。若采用
IS_CHANGED,不要返回布尔 True 表示“每次更新”;官方文档说明这种写法会被缓存。强制刷新不能用在提交节点上绕过幂等。 - 一次 DAG 无法靠简单回边无限轮询。先提交,再分次 Queue 状态图;或者实现受限的 UI 查询组件,不让它重新提交训练。
- ComfyUI
/interrupt只操作 ComfyUI 执行;独立训练子进程需要 JobCancel 单独取消。训练取消也不自动停止已启动服务。 - 首轮只允许一次训练任务占用训练设备。失败和取消保留日志,部分权重标为不可发布;不得自动接成服务。
行为来源:ComfyUI 缓存文档、Server Routes 与 WebSocket Messages。
worker API 与持久化格式(待实现)
| 拟定 worker API | 作用 |
|---|---|
| POST /jobs | 提交 prepare_data/prepare_model/sft/grpo/eval/fuse;返回 job_id |
| GET /jobs/{job_id} | 返回状态、日志位置、错误、结果 artifact 引用 |
| POST /jobs/{job_id}/cancel | 取消本 worker 创建的任务,回读终止状态 |
| GET /artifacts/{artifact_id} | 返回不可变 manifest 与 hash 校验结果 |
| POST /services | 启动指定最终 artifact 的 MLX-LM 服务 |
| GET /services/{service_id} | 健康、加载模型、实际监听端口与错误 |
| POST /services/{service_id}/stop | 停止对应服务进程并确认不再监听 |
这些不是 ComfyUI 原生路由,也不是已存在的 MLX-LM 路由。原生端口 8188 的 /prompt、/history/{prompt_id} 用于提交/回读 ComfyUI 图;端口 8088 的 /v1/chat/completions 用于调用训后模型。
拟定的 POST /jobs 请求示例(仅接口样本,ID 为占位符):
{
"experiment_id": "price-grpo-smoke-01",
"request_id": "grpo-submit-01",
"operation": "grpo",
"parent_artifact_id": "sft-fused-placeholder",
"reference_artifact_id": "sft-fused-placeholder",
"data_artifact_id": "price-data-placeholder",
"parameters": {"config": "assets/grpo.yaml", "iters": 5}
}
worker 返回 job_id/state/experiment_id/operation/request_id;JobReadback 增加 progress/error/log_path/artifact_ids/evidence_summary。runtime 摘要记录真正的 Python 路径、包版本、设备、PID、耗时与峰值内存;未知字段留空,不根据 ComfyUI 的 MPS 标签填训练设备。请求参数只接受固定操作与允许的配置,后端自己构造 argv,不接受任意 shell 命令。
runs/comfy/{experiment_id}/
experiment.json # 原始模型、数据与配置 hash
jobs/{job_id}/
request.json # 命令 argv、输入引用、幂等键
status.json # 原子更新,状态和错误
stdout.log
training-evidence.json # 实际更新与重载证据
rollouts.jsonl # GRPO 时必需
artifacts.json # 只在核验成功后发布引用
services/{service_id}/
launch.json # 模型 artifact、路径/hash、端口
server.log
readiness.json
receipts/{request_id}.json # 完整请求与回包
model artifact manifest 最少包含 artifact_id/stage/representation/parent_artifact_id/base_artifact_id/model_path/weights_sha256/tokenizer_sha256/config_sha256/data_sha256/producer_job_id。stage 为 original/sft/grpo;representation 为 full/adapter/fused。adapter 引用必须带 base_artifact_id,并把 adapter 与对应 base 组合重载;fuse 产物保留 adapter parent 与训练 base 血缘。哈希包含真正的权重、tokenizer 与配置;manifest 的 ID 不等于训练完成证明。建立不可变产物后再发布引用。
Submit 的 sft 输入须是 original/full;grpo 的训练 parent 和固定参考模型输入须是 sft/fused;ServiceStart 输入须是所选最终阶段的 fused。worker 遇到 stage、representation 或 base/hash 不匹配时拒绝提交,不能只凭文件名判断。
data manifest 最少包含 artifact_id/stage=data/seed/oracle_version/partition_paths/partition_counts/partition_sha256/disjoint_check/producer_job_id。模型 artifact 中的 data_sha256 指向这一份冻结数据摘要;原始未训练模型的该字段为空。
实现与运行顺序
- 保留现有 ComfyUI 实例;把桥接包源码先准备为独立
custom_nodes/price_lab_bridge,不改核心或现有业务节点。 - 在课程
.venv安装原手册的依赖,确认 MLX 设备;运行 oracle/奖励检查和数据 hash 检查。 - 实现 worker 与只依赖 HTTP 的桥接节点,先验证假任务状态转换、幂等、失败和重载检查。假任务只证明编排。
- 单独跑真实 SFT/GRPO 小步,确认训练命令、回读与 callback 证据,再用 ComfyUI 触发同一 runner。
- 节点安装后,在原实例任务允许时再正常重启;读取
/object_info确认 PriceLab 类名出现,验证图依赖后再 Queue。 - 按 A/B/C/D 分阶段创建图。blueprint 仅用于按规格建图;导出真正 UI workflow 与 API workflow 后,才能调用原生
/prompt。 - 训练、质量与服务按独立门槛验收。新实验选 checkpoint 用验证集,冻结测试集规则仍沿用原课程。
本轮设计不自动执行上面步骤;它给后续节点实现和实作课程提供可审查的规格。
验收表
| 声明 | 足够的证据 | 不足的证据 |
|---|---|---|
| 画布提交成功 | prompt_id、history、worker job_id | 训练完成 |
| SFT 更新发生 | 新参数、有限更新、产物与重载 | 单独 loss 曲线截图 |
| GRPO 完成真实闭环 | SFT parent + 真实采样/奖励/优势 + 更新/重载 | 单独 reward 分数或模拟器 |
| 模型有效 | 同一固定题/同解码的原始-SFT-GRPO 对照 | 合成数据检查通过 |
| 服务可调用 | 正确 artifact 实例 + 健康 + 原始推理 receipt | service_id、端口打开或 HTTP 200 |
| 金额正确 | receipt 的 content 通过原严格评分器 | 用 oracle 替换后的显示值 |
桥接速查 · 状态与证据速查 · 设计 blueprint · 学习入口