CachedSearch Sampler (best-of-N, cached drafts)
The CachedSearch Sampler, explained
- model
- vae
- positive
- negative
- latent_image
- verifier
- latent
- winner_seed
- draft_scores
You know the seed lottery. Queue the same prompt three times on Wan and you get a beautiful pan, a mushy pan, and a pan where someone's hand has six fingers. The obvious fix - render eight seeds, keep the best - costs 20 to 30 minutes a clip on a 4090-class card. That's your evening.
CachedSearch Sampler is the affordable version. It renders the candidates cheaply under EasyCache, scores them, and re-renders only the winner at full compute. The README's figure is roughly 60% of the cost of plain best-of-N, with most of the gain kept.
The mechanism, which is the whole point
It clones your model with ComfyUI's native EasyCache attached (don't add the EasyCache node yourself - the tooltip says so, and you'd double-apply it), runs N full sampler passes on that cached model with seeds seed through seed+search_width-1, VAE-decodes each draft to pixels and hands it to your verifier for a float, takes the argmax, then - since commit defaults on - runs one more pass on the winning seed with the original, uncached model. That last latent is what you get.
The bill is N cheap samples, one full sample, N VAE decodes. Sampling is seed-deterministic, so the clip you receive is exactly what full-compute best-of-N would have produced for that seed. Not an approximation of the winner. The winner.
Be a little skeptical of the 60%. How much you save depends on how much EasyCache actually saves on your model - community reports range from useful to "they didn't give me much speedup" - and the decodes get no discount at all. One credibility note: the author list includes Alan Bovik, the SSIM side of image-quality research. This is a paper ported to ComfyUI, not a weekend pack.
The inputs you'll actually touch
It's KSampler plus four things. model, positive, negative, latent_image, steps, cfg, sampler_name, scheduler and denoise behave as they always do: DDPM-style SDXL-family checkpoints keep their DPM++ habits, flow-matching video models want Euler-family samplers on conservative schedules, and Karras and exponential are the ones that break them.
vae is required because every draft is decoded for scoring, even with commit on. prompt is the only text the verifier sees, so it has to match what you encoded into positive - getting that wrong is the most common way to make this node look broken. verifier takes a CS_VERIFIER; in practice that's the CachedSearch ImageReward Verifier node, though anything exposing score(images, prompt) -> float slots in.
search_width is N: default 8, max 64. The README suggests 4 to 8, since recommitting the winner only pays off from about four candidates up.
cache_threshold (default 0.2, max 3.0) is the EasyCache reuse threshold. Higher skips more steps - cheaper drafts, less faithful ranking. The paper calibrates one threshold per model family, and 0.2 is the EasyCache default and the stated Wan starting point. cache_start_percent (0.15) and cache_end_percent (0.95) bound where caching may apply; defaults are sane, since the opening steps set composition.
commit off returns the cached draft instead of re-sampling the winner. Cheaper, but not reproducible from winner_seed with a plain KSampler, because the cached latent is a different object. Leave it on.
Outputs and wiring
latent is the winning candidate - a full-compute sample with commit on - straight into VAE Decode and Save. winner_seed is an INT you can feed a plain KSampler later to reproduce that clip without paying for the search twice. draft_scores is a JSON string of the verifier's scores in seed order.
ImageReward Verifier ──────────────┐
Model / positive / negative / latent / VAE ──> CachedSearch Sampler
prompt text (same as positive) ──────────┤
▼
VAE Decode ──> Save
draft_scores ──> any text display node
Scores bunched together mean the verifier was guessing and your best-of-8 is theatre; spread-out scores mean the search is doing work. Nothing else in ComfyUI tells you that.
Installing
cd ComfyUI/custom_nodes
git clone https://github.com/shreshthsaini/ComfyUI-CachedSearch
pip install -r ComfyUI-CachedSearch/requirements.txt # just: image-reward
Or ComfyUI Manager, searching the pack title, then restart. Dependencies are trivial - one package - but the ImageReward-v1.0 checkpoint downloads on first use, so warm it up before committing an evening to a long run.
The real requirement is the one in the README: a ComfyUI build that ships the native EasyCache node (comfy_extras/nodes_easycache.py), core since mid-2025. Old builds fail in a nasty way - that import happens inside the sampler at run time, not at startup, so the node shows up in your menu, you wire it happily, and it throws when you queue. An ImportError for comfy_extras.nodes_easycache means update ComfyUI; the pack is fine.
Troubleshooting
OOM or a crawl on video. Expected, not a bug: every draft is held in memory and pushed through the VAE decoder, which on a 14B video model is the expensive part. The author's remedy is to cut search_width, not frames_to_score, since the VAE decodes the whole clip either way. Keep the latent batch at 1 as well - the internal decode flattens everything onto one frame axis, so a batched latent merges clips before scoring.
image-reward import error when the search starts. The verifier loads lazily, so a missing package won't stop ComfyUI booting. Install it into the interpreter ComfyUI actually uses.
Winners worse than the drafts. You pushed the cache too hard - a cache that smears motion degrades the drafts, and a degraded draft can out-score a good seed. Drop cache_threshold toward 0.1 and re-run the same seed. Winners that look random usually mean a blank or stale prompt box.
Skip this on stills. Re-rolling an SDXL image costs seconds; the verifier pass, the decodes and the bookkeeping are mostly overhead. This node earns its keep where a re-roll costs real time - video, especially I2V clips long enough that you only get one shot at the seed.
Inputs (18)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | The model to sample with. Do not attach EasyCache yourself; the node does it for the drafts. | |
| vae | VAE | Used to decode each draft for the verifier. | |
| positive | CONDITIONING | — | |
| negative | CONDITIONING | — | |
| latent_image | LATENT | — | |
| prompt | STRING | The text the verifier scores against (the same prompt you encoded into `positive`). | |
| verifier | CS_VERIFIER | Scorer for the drafts, e.g. the CachedSearch ImageReward Verifier node. | |
| seed | INT | 00–18446744073709550000 | First seed; candidates use seed, seed+1, ..., seed+search_width-1. |
| search_width | INT | 81–64 | Number of candidate seeds (N in best-of-N). |
| steps | INT | 301–10000 | — |
| cfg | FLOAT | 5.00–100 | — |
| sampler_name | COMBO | 44 options: euler, euler_cfg_pp, euler_ancestral, euler_ancestral_cfg_pp, heun, heunpp2, +38 | |
| scheduler | COMBO | 9 options: simple, sgm_uniform, karras, exponential, ddim_uniform, beta, +3 | |
| denoise | FLOAT | 1.000–1 | — |
| cache_threshold | FLOAT | 0.200–3 | EasyCache reuse threshold for the drafts. Higher skips more steps (cheaper drafts, less faithful ranking). Calibrate per model family. |
| cache_start_percent | FLOAT | 0.150–1 | — |
| cache_end_percent | FLOAT | 0.950–1 | — |
| commit | BOOLEAN | true | Re-sample the winning seed with caching off (recommended). Off returns the cached draft itself. |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| latent | LATENT | The winning candidate: a full-compute sample when commit is on. |
| winner_seed | INT | The seed that won; feed it to a normal KSampler to reproduce the result. |
| draft_scores | STRING | JSON list of the verifier scores of the cached drafts, in seed order. |