Nodes/ComfyUI_FlashVSR/FlashVSR_SM_KSampler
ComfyUI Node

FlashVSR_SM_KSampler

One step, four times the pixels, and the five knobs people get wrong

By smthemex·Created about a year ago·Updated 8 days ago· 406
FlashVSR_SM_KSampler
  • model
  • image
  • latent
◄width1280►
◄height768►
◄seed0►
◄scale4►
◄kv_ratio3.5►
◄local_range11►
◄steps1►
◄cfg1.0►
◄sparse_ratio2.0►

This is the node that does the upscaling. Everything else in the pack exists to feed it or to turn its output into pictures. It takes your frames as one IMAGE batch, hands them to a one-step DMD model, and gives you back a latent - not images. That trips people up: there's no preview here, and if you stop at this node you'll think it's broken.

Wire it as FlashVSR_SM_Model → model, frames → image, and then latent into FlashVSR_SM_Decoder alongside a FlashVSR_SM_Vae. Then CreateVideo and SaveVideo.

Why a KSampler for an upscaler

Because FlashVSR is a diffusion model, just a distilled one. The streaming/DMD release was trained to land its result in a single denoising step, which is the whole reason it beat SeedVR2 to adoption in late 2025: it was fast enough to sit inside a normal evening of work. It's a restorer, not a generator - it resolves detail the low-res source already implies, which is why it's the wrong tool for genuine 256px mush.

Mechanically, the sampler resamples your frames to width × height, pads the frame count up to the nearest 8n+1 (and trims the padding back off later), runs the single denoise pass with block-sparse attention over a temporal window, and hands back the latent plus the "LQ" conditioning frames it used. Those extra bits ride inside the LATENT dict - that's why the decoder can colour-match against your original footage.

The inputs you actually set

image takes a batch of frames - LoadVideo → GetVideoComponents → image. Feed it a single image and the pipeline quietly repeats it to 25 frames, so a still becomes a clip whether you asked for one or not.

width and height are the working canvas - your frames get resampled to exactly this before the model sees them. They are not the output size. Output lands at roughly 4× that canvas (the model's 4× reconstruction), which is why the bundled workflows happily put 512 or 832 in here and still get multi-megapixel frames. Keep both divisible by 128; the pipeline centre-crops down to a 128 multiple, so 832 quietly becomes 768 and you lose a strip off each side.

scale (1–4) is the pre-magnification of that canvas before the model's own 4× pass. The README says a scale of 1 isn't the intended setting because the training data is 4× pairs, though the shipped examples use 1 or 2 with small canvases and look fine. Practically: keep the canvas small, let scale do less work than you think, and check the dimensions of your first output rather than trusting arithmetic.

The tuning knobs, and what they honestly do:

  • steps defaults to 1 and should stay there. This is a distilled model; extra steps buy you time, not detail. cfg defaults to 1.0 for the same reason - the negative-prompt machinery isn't really in play here.
  • local_range is the one people argue about. The pack's own source notes 9 for sharper detail and 11 for more stable, less flickery results. If your footage shimmers, go up.
  • kv_ratio (3.5) and sparse_ratio (2.0) are the sparse-attention knobs - how much temporal context the model keeps and how sparse the attention budget is. sparse_ratio is normalised against your resolution in code, so it behaves consistently if you change canvas size. Leave both alone until everything else works.
  • seed matters less than you'd hope and more than you'd think: the sampler is stochastic enough that rerolling can rescue a slightly crunchy face.

If you're on FlashVSR+ (plus), half of this node is decorative. The plus pipeline is a fixed one-step path and the pack prints a note telling you that steps, cfg, sparse_ratio, kv_ratio, local_range and scale are ignored. What still matters there is width, height and seed.

Output

A latent. Not a preview, not an image. It carries the samples plus the LQ frames and pad count the decoder needs, so send it to FlashVSR_SM_Decoder - don't route it into a plain VAEDecode and expect the colour to match the rest of FlashVSR's behaviour.

Install

Manager, or:

cd ComfyUI/custom_nodes
git clone https://github.com/smthemex/ComfyUI_FlashVSR
cd ComfyUI_FlashVSR && pip install -r requirements.txt

Weights go in ComfyUI/models/FlashVSR (DiT, LQ_proj_in, posi_prompt) - see the Model page, since this node won't load without them.

Troubleshooting

CUDA OOM on the sample, card at 100% for a minute, then a crash. Lower width/height first - it's the cheapest lever by far - then turn on offload in the Model node. Tiling lives in the decoder; this stage is the DiT and it just needs the memory.

Result looks like a soft upscale with no new detail. You're probably feeding it a source that's already been upscaled, or a canvas so large that the model's 4× is barely a stretch. Feed FlashVSR the real low-res frames.

Timing seems absurd for "one step". Frame count, not steps, is your cost. A 300-frame clip is 300 frames of attention no matter how many steps you set. Cut a long video into pieces with FlashVSR_SM_VideoTrimLoop, or switch the Model node to tiny_long.

Texture crawls between frames. Raise local_range to 11 before blaming the model - temporal consistency is the whole reason you're here rather than running an image upscaler on every frame.

CategoryFlashVSR

Inputs (11)

NameTypeDefaultDescription
modelFlashVSR_SM_Model—
imageIMAGE—
widthINT1280128–16384—
heightINT768128–16384—
seedINT00–2147483647—
scaleINT41–4—
kv_ratioFLOAT3.50–10—
local_rangeINT111–50—
stepsINT11–10000—
cfgFLOAT1.00–100—
sparse_ratioFLOAT2.00–10—

Outputs (1)

NameTypeDescription
latentLATENT—