Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ API and command-line option may change frequently.***
- [ADetailer](./docs/adetailer.md)
- LoRA support, same as [stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#lora)
- Latent Consistency Models support (LCM/LCM-LoRA)
- [LanPaint](./docs/lanpaint.md) training-free inpainting sampler with inner Langevin iterations ("think mode") for all supported models
- Faster and memory efficient latent decoding with [TAESD](./docs/taesd.md)
- Upscale images generated with [ESRGAN](./docs/esrgan.md)
- Supported backends
Expand Down Expand Up @@ -157,6 +158,7 @@ For runtime and parameter backend placement, see the [backend selection guide](.
- [Quantization and GGUF](./docs/quantization_and_gguf.md)
- [INT8 convrot safetensors](./docs/int8_convrot.md)
- [Inference acceleration via caching](./docs/caching.md)
- [LanPaint inpainting](./docs/lanpaint.md)

## Bindings

Expand Down
97 changes: 97 additions & 0 deletions docs/lanpaint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
## LanPaint Inpainting

LanPaint is a training-free inpainting sampler that gives diffusion models
"think mode": instead of denoising in one pass, it runs up to N inner Langevin
steps per sampling step, letting the model reconcile the prompt-driven region
with the known (kept) region before committing to the update. It is a port of
the [ComfyUI LanPaint extension](https://github.com/scraed/LanPaint)
(official implementation of
["LanPaint: Training-Free Diffusion Inpainting with Asymptotically Exact and Fast Conditional Sampling"](https://arxiv.org/abs/2502.03491),
TMLR 2025), with the parameter defaults of the v2.1.0 ComfyUI node.

In essence, the inpainting problem is modeled as a conditional stochastic
process, and LanPaint integrates it numerically with
[Langevin dynamics](https://en.wikipedia.org/wiki/Langevin_dynamics). The
drift of the process is assembled from the underlying model's own denoising
solutions, i.e. its inferred solutions of the unconditional version of the
same process, so the conditional problem is solved without retraining. Each
inner iteration is one integration step of this process, and each step costs
one model evaluation; that is the exchange of extra compute for inpainting
quality: up to `1 + n_steps` model evaluations per sampling step instead of one.

### Usage

Provide an init image and a mask and enable LanPaint:

```bash
sd-cli -m model.safetensors -i images/input.png --mask images/mask.png \
-p "a cozy living room, photorealistic" \
--strength 1.0 --sampling-method euler --lanpaint -v
```

Mask convention (same as the rest of `stable-diffusion.cpp`): **white (255)
marks the region that gets repainted, black (0) marks the region that is kept.**
Masks are binarized at load time and evaluated at latent resolution. When no
mask is given, LanPaint is inactive and sampling proceeds like the plain
sampler.

The inner loop needs both a conditional and an unconditional model branch.
Guidance-distilled models without an unconditional branch (for example
turbo/schnell variants) run, but the keep-region guidance degenerates and the
LanPaint benefit largely disappears.

### Parameters

| Flag | Description | Default |
|------|-------------|---------|
| `--lanpaint` | enable LanPaint | off |
| `--lanpaint-n-steps` | number of inner Langevin steps per sampling step ("turns of thinking"); the effective count ramps down near the end of the schedule | 5 |
| `--lanpaint-lambda` | strength of the keep-region (bidirectional) guidance | 5.0 |
| `--lanpaint-beta` | time-scale ratio of the keep-region branch | 1.0 |
| `--lanpaint-step-size` | Langevin step size; scaled by the remaining noise fraction of the current step | 0.2 |
| `--lanpaint-early-stop` | skip the inner loop for the last N sampling steps (they only polish with a plain evaluation) | 1 |
| `--lanpaint-min-step-frac` | when the remaining noise fraction drops below this value, the step size is pinned there and the inner-step count ramps down to zero | 1.0 |
| `--lanpaint-prompt-first` | Prompt First mode: sets the BIG guidance scale to -0.5, emphasizing prompt following over mask-boundary coherence | off (Image First) |
| `--lanpaint-cfg-big` | explicit BIG guidance scale override; by default it resolves to `--cfg-scale` (Image First) or -0.5 (Prompt First) | auto |

`Image First` (default) and `Prompt First` change how strongly the inner loop
is anchored to the already-known image versus the prompt. Use Image First for
seamless object insertion in the known surroundings; use Prompt First when the
repainted region should follow the prompt even at the cost of boundary
coherence.

LanPaint inherits every per-step feature of the outer sampler: conditioning, Control
Net, IP-Adapter, reference latents, video masks, previews and cancellation.

### Cost

Each inner step costs one model evaluation (conditional + unconditional), so a
run needs up to `steps x (1 + n_steps)` evaluations. At INFO log level the
planned count is printed before sampling starts:

```
LanPaint: 16 outer steps, up to 5 inner steps per outer step, 68 model evaluations planned (16 without LanPaint)
```

At VERBOSE level every inner step is logged. With the defaults, `early_stop`
and the `min_step_frac` ramp already keep the tail of the schedule cheap.
Recommended `--lanpaint-n-steps` range: 2-8 (the upstream default of 5 is a
good quality/speed balance).

### Supported samplers and models

- Samplers: `euler` (recommended), `euler_a`, `heun`, `dpm2`, `dpm++2m`,
`dpm++2mv2`. Other samplers are rejected with an error.
- Models: everything using the standard denoiser noise scaling, including
SD1.x/SD2.x (eps, v-prediction, EDM), SDXL, SD3/SD3.5, FLUX, Chroma,
Qwen-Image, Wan, HunyuanVideo, and LTX video models.
- Not supported (rejected with an error):
- MiniT2I and SeFi (custom latent scaling / dual time conventions),
- SenseNova U1.5 (its noise scaling discards the latent, which would
overwrite the kept region),
- MiniMax-H3 and LTX-AV (audio rows follow a per-stream noise schedule the
inner loop does not model).
- Sampling caches (`--cache-mode`, for example) are disabled under LanPaint:
the inner loop's repeated evaluations at one step index break their reuse
accounting.

3 changes: 3 additions & 0 deletions examples/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ equivalent to `--log-level verbose`. If repeated, the last logging option wins.
For direct image repair or automatic post-generation YOLOv8 detection followed by cropped inpainting, see
[ADetailer](../../docs/adetailer.md).

For high-quality inpainting with the Langevin-based LanPaint sampler (`--lanpaint`), see
[LanPaint](../../docs/lanpaint.md).

Metadata mode inspects PNG/JPEG container metadata without loading any model:

```bash
Expand Down
38 changes: 38 additions & 0 deletions examples/common/common.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,14 @@ ArgOptions SDGenerationParams::get_options() {
"--steps",
"number of sample steps (default: 20)",
&sample_params.sample_steps},
{"",
"--lanpaint-n-steps",
"LanPaint number of Langevin steps per sample step, i.e. turns of thinking: (default: 5)",
&sample_params.lanpaint.n_steps},
{"",
"--lanpaint-early-stop",
"LanPaint drops the inner loop in the last N outer steps: (default: 1)",
&sample_params.lanpaint.early_stop},
{"",
"--high-noise-steps",
"(high noise) number of sample steps (default: -1 = auto)",
Expand Down Expand Up @@ -1226,6 +1234,26 @@ ArgOptions SDGenerationParams::get_options() {
"--flow-shift",
"shift value for Flow models like SD3.x or WAN (default: auto)",
&sample_params.flow_shift},
{"",
"--lanpaint-lambda",
"LanPaint bidirectional guidance scale for the known region: (default: 5.0)",
&sample_params.lanpaint.lambda},
{"",
"--lanpaint-beta",
"LanPaint time-scale ratio of the y branch: (default: 1.0)",
&sample_params.lanpaint.beta},
{"",
"--lanpaint-step-size",
"LanPaint Langevin step size: (default: 0.2)",
&sample_params.lanpaint.step_size},
{"",
"--lanpaint-min-step-frac",
"LanPaint minimum noise fraction below which the step size is pinned: (default: 1.0)",
&sample_params.lanpaint.min_step_frac},
{"",
"--lanpaint-cfg-big",
"LanPaint BIG guidance scale; default auto resolves to --cfg-scale (Image First) or -0.5 (Prompt First)",
&sample_params.lanpaint.cfg_big},
{"",
"--high-noise-cfg-scale",
"(high noise) unconditional guidance scale: (default: 7.0)",
Expand Down Expand Up @@ -1297,6 +1325,16 @@ ArgOptions SDGenerationParams::get_options() {
};

options.bool_options = {
{"",
"--lanpaint",
"enable LanPaint Langevin-dynamics inpainting (recommended sampler: euler, requires a mask)",
true,
&sample_params.lanpaint.enabled},
{"",
"--lanpaint-prompt-first",
"LanPaint Prompt First mode: emphasis prompt following over image quality (default: Image First)",
true,
&sample_params.lanpaint.prompt_first},
{"",
"--increase-ref-index",
"automatically increase the indices of references images based on the order they are listed (starting with 1).",
Expand Down
16 changes: 16 additions & 0 deletions include/stable-diffusion.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,23 @@ typedef struct {
sd_slg_params_t slg;
} sd_guidance_params_t;

// LanPaint: Langevin-dynamics inpainting (ComfyUI LanPaint port).
typedef struct {
bool enabled; // --lanpaint
int n_steps; // inner Langevin steps per outer step (default 5)
float lambda; // keep-region score strength (default 5)
float beta; // time-scale ratio of the y branch (default 1)
float step_size; // Langevin step size (default 0.2)
int early_stop; // drop the inner loop in the last N outer steps (default 1)
float min_step_frac; // pin the step size below this noise fraction (default 1)
bool prompt_first; // Prompt First mode (default false = Image First);
// resolves the BIG-CFG scale to -0.5 instead of txt_cfg
float cfg_big; // explicit BIG-CFG scale override (INFINITY = auto from
// prompt_first / txt_cfg, matching the comfy node)
} sd_lanpaint_params_t;

typedef struct {
sd_lanpaint_params_t lanpaint;
sd_guidance_params_t guidance;
enum scheduler_t scheduler;
enum sample_method_t sample_method;
Expand Down
9 changes: 9 additions & 0 deletions src/core/tensor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1073,6 +1073,15 @@ namespace sd {
return output;
}

template <typename T>
inline Tensor<T> sqrt(const Tensor<T>& input) {
Tensor<T> output(input.shape());
for (int64_t i = 0; i < input.numel(); ++i) {
output[i] = static_cast<T>(std::sqrt(static_cast<double>(input[i])));
}
return output;
}

template <typename T>
inline Tensor<T> clamp(const Tensor<T>& input, const T& min_value, const T& max_value) {
if (min_value > max_value) {
Expand Down
Loading