Skip to Content
Ⅲ · 模型与采样12 · 采样器、调度器与 CFG

12 · 采样器、调度器与 CFG

“KSampler”在 UI 里是一个节点,在源码里却是四层组合:节点适配、噪声准备、sigma scheduler、sampler algorithm,再由 CFGGuider 管条件和模型生命周期。把它们拆开,才能解释“同一个 sampler 换 scheduler 为什么画面会变”。

先分清四个概念

概念回答的问题例子
noise从哪个随机初态开始seed 产生的高斯噪声
scheduler每一步处于哪个噪声水平normal、karras、exponential
sampler怎样从当前状态推进到下一 sigmaeuler、dpmpp、heun
guider条件/负条件如何组织并引导模型CFGGuider

steps 决定离散点数量,但 scheduler 决定这些点在哪里;sampler 决定相邻点之间的数值更新公式。

完整调用链

KSampler node → common_ksampler → comfy.sample.prepare_noise → latent_preview.prepare_callback → comfy.sample.sample → comfy.samplers.KSampler.sample → CFGGuider.sample → sampler.sample → sampling_function → calc_cond_batch → BaseModel.apply_model

节点适配层:common_ksampler

nodes.pyL1564-1587latent、noise、mask、callback 与 sample 调用。

它做六件事:

  1. 从 latent dict 取 samples;
  2. 通过模型 latent format 修正通道;
  3. 按 seed 构造 noise,或在 disable noise 时全零;
  4. 取可选 noise_mask;
  5. 创建带预览的 callback;
  6. 调 comfy.sample.sample(),把结果写回 latent dict 的浅拷贝。

为什么返回 latent dict 而不是裸 tensor

除了 samples,LATENT 还可能携带 noise_mask、batch index、latent metadata 等。浅拷贝并只替换 samples 保留其余协议字段。

确定性噪声

prepare_noise() 在 CPU 上创建 torch.Generator 并以 seed 初始化,再按 latent shape 生成噪声;若有 noise_inds,可只为唯一 index 生成后重排。

comfy/sample.pyL1-45CPU generator、seed 与 noise index 重用。

CPU 生成有助于跨 device 路径保持更一致的初态。最终算法仍可能受不同 backend kernel、dtype 与非确定算子影响,seed 相同不代表所有硬件逐位一致。

disable_noise

使用全零 tensor,不表示没有 denoise 过程;它只改变起始噪声项。img2img / latent 输入仍按 sigma 轨迹演化。

comfy.sample.sample():薄但重要的边界

它构造 comfy.samplers.KSampler,把 noise、conditioning、latent、mask、callback 等交给 sampler,完成后把结果移到 intermediate device,并恢复期望 dtype。

comfy/sample.pyL71-80节点层与采样运行时之间的窄适配。

窄边界让其他节点可以复用采样骨架,也让设备返回策略不散落在数值算法里。

KSampler:把名字解析为可执行组合

构造器保存 model、steps、device、sampler name、scheduler、denoise 与 model options,并计算 sigma 序列。

comfy/samplers.pyL1399-1463KSampler 初始化、sigma 与 sample 参数裁剪。

sigma 序列

calculate_sigmas(model_sampling, scheduler_name, steps) 根据模型自己的 sampling 参数和 scheduler 名产生一维 tensor。

comfy/samplers.pyL1378-1398scheduler 分派与 sampler object 构造。

通常 sigma 从高噪声走向低/零噪声。不同 scheduler 对高低噪声区域分配步数不同;即使 sampler 和 steps 相同,轨迹也不同。

denoise 小于 1

代码用更长的基础步数构造 sigma,再截取尾部,使 img2img 从较低噪声强度开始。start_step、last_step 继续裁剪,可用于分阶段采样或高级控制。

force_full_denoise

若结束点本不为零,该选项强制最后 sigma 为零,让输出走完去噪。它会改变分段采样的衔接语义,不应盲目开启。

sampler_object

采样名字映射到包装器:部分调用 k-diffusion 函数,部分使用 UniPC 等实现,部分带额外选项。统一对象暴露类似:

sample(model_wrap, sigmas, extra_args, callback, noise, latent_image, denoise_mask, disable_pbar)

算法实现不需要知道 PromptQueue、WebSocket 或 ModelPatcher cache;这些由外层准备。

CFGGuider:真正的编排中心

comfy/samplers.pyL1188-1346条件准备、模型加载、采样包装与清理。

CFGGuider.sample() / inner_sample() 负责:

  1. 预处理 positive / negative conditioning;
  2. 收集 conditioning 依赖的额外模型与内存;
  3. 调 load_models_gpu();
  4. 准备 mask、latent shapes、model options 和 hooks;
  5. 把模型包装为 sampler 期望的 callable;
  6. 调具体 sampler;
  7. 在 finally/cleanup 路径释放 hook 与临时状态;
  8. 解包 nested latent 并返回。

Sampler 只迭代数值步骤,Guider 管“每次模型评估到底带什么条件”。

CFG 的核心

普通形式可写为:

pred = uncond + cfg × (cond - uncond)

源码中的 sampling_function() 先通过 calc_cond_batch() 得到 cond/uncond 预测,再调用 cfg function;model options 可替换最终 CFG 逻辑。

comfy/samplers.pyL609-627cond/uncond 预测与 CFG 合成。

cfg = 1 的优化

当 scale 为 1 且没有其他选项强制 unconditional,结果等于 cond,运行时可以跳过 uncond forward。一次模型评估比两次便宜很多。

negative 不是简单“反向 prompt”

negative conditioning 产生 unconditional/negative 分支表示,CFG 使用两者差值引导。conditioning 还可携带 area、mask、strength、control、gligen、hooks、timestep 范围等元数据。

calc_cond_batch():最密集的条件逻辑

comfy/samplers.pyL208-608区域、mask、control、hook 与内存感知条件批处理。

对每个 conditioning 条目,它要:

  • 判断当前 timestep 是否有效;
  • 将 area 百分比/坐标转为 latent 切片;
  • 裁剪输入到指定区域;
  • 处理 mask 与 feathering;
  • 组合 control / patch / transformer options;
  • 判断多个 cond 能否拼成一个 batch;
  • 根据空闲内存决定一次合并多少;
  • 调 model.apply_model;
  • 把区域输出按 strength 和 mask 累加回全图;
  • 用计数/权重归一化重叠区域。

为什么要自己拼 batch

cond 与 uncond、多个区域若 shape 和控制选项兼容,可以一次 forward,减少模型调用;若显存不足则拆分。批处理不是固定 batch size,而是运行时基于内存与兼容性的装箱问题。

模型准备

在第一步 sampler 之前,Guider 收集主 MODEL 和 conditioning 引用的额外模型(如 ControlNet),估算额外内存并统一 load_models_gpu()。这确保显存管理看到完整集合,而不是主模型先独占空间后才发现还有 ControlNet。

sampling prepare callbacks 还允许 Patcher hooks 设置当前 transformer options;结束时 cleanup 恢复。

inpaint 与 mask

采样包装器可在每一步把未遮罩区域与原 latent 混合,并将噪声限制在 mask 区域。mask 会被 reshape、插值和广播到 latent batch/channel。

这不是 SaveImage 后的像素合成,而是在扩散轨迹中约束 latent。mask shape 与 latent batch 不匹配常在这一层暴露。

callback 与实时预览

节点层用 latent_preview.prepare_callback(model, steps, x0_output) 创建回调。sampler 每步提供 step、预测 x0、当前 x 与 total:

sampler step → callback → latent previewer decodes lightweight preview → ProgressBar.update_absolute → global progress hook → PromptServer WebSocket

预览解码不是最终 VAE Decode,质量与颜色可能近似;它的目标是低延迟反馈。

中断

列表调用前与采样循环关键位置会检查 nodes.before_node_execution() / interrupt flag。取消是协作式的:Python 控制重新获得执行权时抛 InterruptProcessingException,执行器把它作为中断而非普通节点 bug 处理。

长时间单个 GPU kernel 无法在任意微秒安全终止。想改善中断响应,应缩短不可让出控制的工作块。

一步采样的数据形状

概念示例(具体通道取决于模型 latent format):

x [batch, latent_channels, h, w] sigma [batch] or broadcastable scalar conditioning token/context tensors + metadata model output same spatial latent shape as x

KSampler 节点的 IMAGE 分辨率通常已在 EmptyLatentImage 中除以 VAE scale 变成 latent 尺寸。不能直接拿像素 H×W 推断 sampler 内存。

一次采样为何可能调用模型多于 steps

  • CFG 常需 cond + uncond;能否合批取决于内存和兼容性;
  • 某些 sampler 每一步有多个 model evaluations;
  • 区域 conditioning 可能拆成多个 batch;
  • ControlNet / hooks 增加附加 forward;
  • preview 不一定调用完整主模型,但会有解码开销。

所以 steps=20 不是“模型 forward 恰好 20 次”的保证。

参数变化落在哪一层

UI 参数主要影响层
seednoise generator
stepssigma 数量 / sampler 迭代
cfgguider 合成,可能决定是否算 uncond
sampler_name数值积分/更新算法
schedulersigma 分布
denoisesigma 轨迹截取
positive / negativeconditioning 与 cond batch
latent batch sizenoise shape、模型 batch、显存

常见调试路径

同 seed 结果不同

检查 model/LoRA、scheduler、sampler、steps、dtype/backend、conditioning 顺序、随机自定义节点与缓存 fingerprint,不要只看 seed。

cfg 提高后更慢

cfg=1 可能跳过 uncond;提高后恢复双分支。cond/uncond 是否能合批又受显存影响。

steps 不多却 OOM

峰值主要由模型、分辨率、batch、attention 和 cond batch 决定,steps 更影响时间,不线性决定峰值显存。

预览正常、最终 Decode OOM

轻量 latent preview 与完整 VAE Decode 是不同路径。使用 tiled VAE 或降低输出 batch,而不是先换 sampler。

可迁移的设计方法

  1. 将轨迹(scheduler)、更新法(sampler)和条件引导(guider)正交组合。
  2. 在进入数值内核前统一加载主模型与附属模型,做总预算。
  3. 条件批处理同时考虑 shape/option 兼容与实时内存,不写死 batch。
  4. 进度回调只报告领域数据,由 composition root 接到传输层。
  5. 确定性是整条链的属性,seed 只控制初始随机源。
Last updated on