KSampler Affine Advanced
The affine sampler to try first (one node, no plumbing)
- model
- positive
- negative
- latent_image
- affine_schedule
- external_mask
- affine_options
- latent
- mask
If you want to know what the affine family does, start here. KSampler Affine Advanced is a normal KSampler - model, positive, negative, latent, steps, cfg, sampler, scheduler, denoise - with the affine block bolted on, so you scale and offset the latent from inside the denoising loop and get a texture/detail push without stopping and restarting the run. No guider to build, no sigmas to construct, no second sampler to wire up. Swap it for your KSampler and you're testing it in a minute.
Why you'd want that: modern distilled and flow-matching models resolve beautifully and then hand you something a bit smooth. That's the known cost of distilling a model down to eight steps - fewer steps, less time to build detail - and the entire Detail Daemon cottage industry exists because of it. The affine approach is the same idea with more knobs: multiplicate the latent where a mask says to, on a curve that says when.
The reason it's a sampler and not a post-process is fidelity. Stopping a run, scaling the latent and starting a new one detaches the sampler from its own history; a multistep method's accumulated state and the model's noise schedule are both gone. This node transforms the sampler's state in place at the step boundary. That's also why it behaves identically on flow-matching, multistep and packed audio/video models, which is more than you can say for most "advanced sampler" nodes.
The settings that matter
max_scale is the headline number. 1.0 is no change, 1.02 adds visible texture over a 20-step run, 1.05 is strong, and past 1.08 the picture breaks up. Below 1.0 it softens instead - that's an underrated use, and it's the honest fix when a LoRA or a merged checkpoint comes out gritty. affine_interval controls how often it lands: 1 is every step, 4 leaves the sampler room to settle in between.
pattern picks the mask - grain, perlin blobs, checker, halftone, worley edges, or the four patterns read off the latent itself (detail_region, smooth_region, edges_sobel, edges_laplacian). affine_seed moves the grain; affine_seed_increment decides whether the mask advances each application or holds still. temporal_mode (static, per_frame, drift) applies to video latents only and is ignored by the content-aware patterns.
affine_acts_on is the one to think about. content multiplies only the picture the model has resolved; latent multiplies the sampler's own noise too, which shows up as grain fixed in the frame on a model that holds a high noise level most of the way - a video model run at a big shift, for instance. Keep the default content unless you want exactly that.
cfg is worth a word given how many 2026 checkpoints are guidance-distilled: 4.5 suits flow-matching models, but on a Turbo/Klein-style distilled checkpoint 1.0 is the intention and raising it applies the guidance correction twice - burn, plus double render time. If you reached for the affine because a distilled model looked flat, don't fix the flatness with CFG.
Optional inputs are the same across the family: affine_schedule (a curve from Affine Schedule), affine_options (pattern settings from Affine Options, and its pattern wins over the widget here), external_mask (your own MASK, resized), affine_streams / affine_space (packed latents, and latent-vs-model scaling), debug (logs step, sigma, strength, resolved scale and bias), and the stepping controls start_at_step, end_at_step and return_with_leftover_noise if you're splitting a run across two samplers. The affine curve is still read over the whole steps range, so a slice carries the part of the curve that belongs to it.
Outputs: latent (the denoised result) and mask - the mask the last application ran through, at latent resolution, all zeros if no affine was applied. Preview the mask when tuning; it's the difference between guessing and knowing.
Install
Ships in WAS Node Suite v3. ComfyUI Manager, search WAS Node Suite v3, then restart. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/WASasquatch/was-node-suite-comfyui.git
ComfyUI 0.14.0+ and Python 3.10+. The pack installs nothing - no pip, no downloads, no build step. Optional node groups live behind features: keys in <ComfyUI user dir>/was-node-suite/config.yaml, created on first start.
Common issues
"The affine did nothing." Four stock samplers can't carry it - dpm_fast, dpm_adaptive, uni_pc, uni_pc_bh2 - because they run their own solver or rescale the latent before every model call. They don't fail; the console tells you and they sample normally. If debug is on and logging nothing, that's why. Also check max_scale isn't 1.0 and denoise isn't low enough that you only ran two steps.
"It looks crunchy / over-detailed." You're past what the model can absorb: back max_scale off, narrow the schedule window, or gate the pattern with content_gate on the options node so it lands on flat regions or edges instead of everywhere. More detail is not the same as better detail, and the community's own experience with detail-injection nodes is that 0.5-on-a-5-scale is often the ceiling before weirdness sets in.
V2 leftovers. If a guide tells you to install OpenCV or InsightFace for this pack, it's from 2023, when the pack did pin those and broke on ComfyUI updates. v3 has no default dependencies, and no workflow from before it will contain this node.
Inputs (28)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | The model to denoise with. | |
| positive | CONDITIONING | What the image should contain. | |
| negative | CONDITIONING | What it should avoid. | |
| latent_image | LATENT | The latent to denoise. Image, video and packed audio and video latents are all handled. | |
| add_noise | BOOLEAN | true | `true` adds fresh noise before the first step, which is what a run from an empty latent needs. `false` starts from the latent as it is, for the second half of a run another sampler began. |
| seed | INT | 00–18446744073709550000 | Seeds the noise that is added. The same seed gives the same run; 0 is as good a seed as any. |
| steps | INT | 201–10000 | How many denoising steps to take. 20 suits most models, 8 for a turbo or lightning one, 40 where fine detail matters. |
| cfg | FLOAT | 4.50–100 | How hard the prompt is enforced. 1.0 = the model's own idea, 4.5 suits most flow-matching models, 7.0 to 8.0 the older ones. |
| sampler_name | COMBO | euler | The algorithm the steps are taken with. All of them carry the affine except dpm_fast, dpm_adaptive, uni_pc and uni_pc_bh2, which sample normally and say so in the console. |
| scheduler | COMBO | normal | How the noise level falls across the steps. `normal` suits most models, `simple` and `beta` are common on flow-matching ones. |
| denoise | FLOAT | 1.000–1 | How much of the latent is replaced. 1.0 = a fresh generation, 0.5 keeps the broad shape of what came in, 0.2 refines it only. |
| affine_interval | INT | 11–100 | Apply on every Nth step of the schedule. 1 = every step, 4 = every fourth, which leaves the sampler more room to settle between applications. |
| max_scale | FLOAT | 1.0200–2 | What the latent is multiplied by at the peak of the schedule, compounding over every step it lands on. 1.0 = no change, 1.02 adds visible texture over a 20 step run, 1.05 is strong, and past 1.08 the picture breaks up. Below 1.0 softens instead. |
| max_bias | FLOAT | 0.000-2–2 | What is added at the peak of the schedule, beside max_scale rather than through it. 0.0 = nothing, 0.005 = a gentle drift, 0.02 shifts the whole colour. bias_field on Affine Options decides whether that is one flat offset or a noise field. |
| pattern | COMBO | white_noise | Which mask decides where the affine lands. 'solid' covers everything; 'white_noise' and the coloured noises are grain of different coarseness; 'perlin', 'checker', 'bayer', 'cross_hatch', 'worley_edges' and the rest are shapes; 'detail_region', 'smooth_region', 'edges_sobel' and 'edges_laplacian' are read off the latent itself; 'external_mask' uses the mask wired in. |
| affine_seed | INT | 00–2147483647 | Seeds the mask. The same seed always draws the same mask, so change it to move the grain without changing anything else. Ignored by the content-aware patterns and by 'external_mask', which read what they are given. |
| affine_seed_increment | BOOLEAN | false | `true` advances the seed on every application, so the grain moves from step to step. `false` holds one mask for the whole run, which keeps the affine landing in the same places. |
| temporal_mode | COMBO | static | How a video latent's mask varies over time. 'static' = one mask on every frame, in the same place all clip. 'per_frame' = an unrelated mask each frame. 'drift' = one mask slid across the frame, set by drift_speed, drift_angle_deg and drift_renew on Affine Options. The content-aware patterns ignore this, and so does an image latent. |
| start_at_stepopt | INT | 00–10000 | First step to run. The affine curve is still read over the whole 'steps' range, so a slice of a run carries the part of the curve that belongs to it. |
| end_at_stepopt | INT | 100000–10000 | Step to stop before. 10000 runs to the end; 12 on a 20 step run hands the rest to a second sampler. |
| return_with_leftover_noiseopt | BOOLEAN | false | `true` leaves the latent partly noisy so another sampler can pick the run up. `false` finishes the denoise, which is what a final pass wants. |
| affine_scheduleopt | DICT | The per-step strength curve from an Affine Schedule node. Left unwired the affine ramps up over the middle of the run, from a fifth of the way in to four fifths. | |
| affine_streamsopt | COMBO | video | Which streams of a packed audio and video latent the affine reaches. 'video' = stream 0, 'audio' = the rest, 'both' = all of them. An ordinary latent has only a video stream, so 'audio' does nothing to it. |
| affine_spaceopt | COMBO | latent | Which latent max_scale and max_bias are measured against. 'latent' = the same scale Latent Affine uses, so a value means the same thing in both places; 'model' = the sampler's own internal latent, which some models hold at a very different magnitude. |
| external_maskopt | MASK | A mask of your own, resized onto the latent. On pattern 'external_mask' it is the mask; on any other pattern it gates the generated one, so the affine reaches only where this is white. One mask covers every frame, or one per frame. | |
| affine_optionsopt | DICT | Pattern parameters and mask shaping from an Affine Options node. Leave it unwired and every value takes its default. A pattern set there wins over the pattern widget. | |
| debugopt | BOOLEAN | false | `true` logs every application to the console: the step, its sigma, the strength, the resolved scale and bias, and which streams were touched. `false` stays quiet. |
| affine_acts_onopt | COMBO | content | What max_scale multiplies. 'content' = only the picture the model has resolved, so the sampler's own noise is left alone. 'latent' = the whole latent, which amplifies that noise as well and prints it as fixed grain on a model that holds noise for most of its run. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| latent | LATENT | The denoised latent. |
| mask | MASK | The mask the last application ran through, at latent resolution. All zero where no affine was applied. |