Chore: Add README.md
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
# ComfyUI-ClybsChromaNodes
|
||||
|
||||
A small collection of custom nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI), designed primarily for use with [Lodestone Rock's Chroma](https://huggingface.co/lodestones/Chroma) model (and compatible flow-matching architectures like FLUX and SD3). The package bundles custom guidance, samplers, schedulers, and an adaptive multi-LoRA loader, all of which integrate as standard ComfyUI nodes.
|
||||
|
||||
## Installation
|
||||
|
||||
Clone this repository into your ComfyUI `custom_nodes` directory and restart ComfyUI:
|
||||
|
||||
```bash
|
||||
cd /path/to/ComfyUI/custom_nodes
|
||||
git clone https://github.com/Clybius/ComfyUI-ClybsChromaNodes.git
|
||||
```
|
||||
|
||||
No additional Python dependencies are required beyond a working ComfyUI install. The frontend extension is picked up automatically via `WEB_DIRECTORY = "./js"`.
|
||||
|
||||
## Node overview
|
||||
|
||||
The package registers **8 nodes**, organized into four groups:
|
||||
|
||||
| Group | Nodes |
|
||||
|---|---|
|
||||
| Guidance | `ClybGuidance` |
|
||||
| Samplers | `SamplerClyb_BDF`, `SamplerTaylorFlow`, `SamplerWrapperCFGPP` |
|
||||
| Schedulers | `InverseSquaredScheduler`, `PrintSigmas` |
|
||||
| LoRA Loaders | `ClybAdaptiveLoraLoader`, `ClybAdaptiveLoraLoaderModelOnly` |
|
||||
|
||||
In addition, the `chroma_NAG.py` module ships a `ChromaNAG` class (Normalized Attention Guidance for Chroma's `DoubleStreamBlock`) that is **not currently registered** in the node mappings — the class is available in code but does not appear in the ComfyUI node browser.
|
||||
|
||||
---
|
||||
|
||||
## Guidance
|
||||
|
||||
### `ClybGuidance`
|
||||
*File: `clyb_Guidance.py`*
|
||||
*Category: `sampling/custom_sampling`*
|
||||
|
||||
A pre-CFG model patch that rewires how the conditional and unconditional predictions are combined at every sampling step. Stacks the following features on top of standard CFG:
|
||||
|
||||
- **Project-and-scale (`eta`)** — the guidance vector is split into a component parallel to the conditional and a component orthogonal to it. The parallel component is scaled by `eta`, the orthogonal component is left alone. `eta = 1.0` recovers default CFG.
|
||||
- **Norm clamping (`norm_threshold`)** — if the L2 norm of the guided output exceeds the conditional's norm times `norm_threshold`, the guided output is rescaled back down. Disabled at `0.0`.
|
||||
- **Momentum (`momentum`, `momentum_beta`, `momentum_renorm`)** — adds a fraction of a running-average guidance vector to the current guidance, optionally re-normalized back to its original norm. `momentum = 0` disables it.
|
||||
- **Scalar projection (`scalar_projection`, `scalar_logsumexp`)** — projects the conditional onto the unconditional as a scalar (`logsumexp` or `sum` reduction), then scales the unconditional by that scalar.
|
||||
- **STD/var rescale (`rescale_phi`, `var_rescale`)** — blends the guided output toward an output whose standard deviation (or variance) matches the conditional's. `rescale_phi = 0` disables it.
|
||||
- **Sine-bell schedule (`scale_up_ratio`, `scale_up_shift`)** — animates the effective CFG scale from `1.0` at the start, up to the configured CFG scale at the middle of diffusion, and back down to `1.0` at the end. `scale_up_ratio = 0` disables it. `scale_up_shift < 1.0` shifts the bell later, `> 1.0` earlier.
|
||||
- **atan2/sin blend (`atan2sin_ratio`)** — blends the unconditional with `uncond.atan().sin() / cond.atan().cos()` (a Chroma-specific twist on the guidance direction).
|
||||
|
||||
| Input | Type | Default | Range | Description |
|
||||
|---|---|---|---|---|
|
||||
| `model` | MODEL | — | — | Model to patch |
|
||||
| `eta` | FLOAT | 1.0 | -50, 50 | Parallel guidance scale |
|
||||
| `norm_threshold` | FLOAT | 0.0 | 0, 50 | Norm clamp (0 = off) |
|
||||
| `momentum` | FLOAT | 0.0 | -10, 10 | Momentum weight (0 = off) |
|
||||
| `momentum_beta` | FLOAT | 0.75 | 0, 0.999 | Running-average smoothing |
|
||||
| `momentum_renorm` | FLOAT | 1.0 | 0, 1 | Renormalize after momentum |
|
||||
| `scalar_projection` | BOOLEAN | False | — | Scalar projection of cond onto uncond |
|
||||
| `scalar_logsumexp` | BOOLEAN | False | — | Use `logsumexp` (else `sum`) |
|
||||
| `rescale_phi` | FLOAT | 0.0 | 0, 1 | STD-rescale blend (0 = off) |
|
||||
| `var_rescale` | BOOLEAN | False | — | Use `var` (else `std`) for rescale |
|
||||
| `scale_up_ratio` | FLOAT | 0.0 | 0, 1 | Sine-bell CFG weight (0 = off) |
|
||||
| `scale_up_shift` | FLOAT | 1.0 | 0.1, 10 | Sine-bell schedule shift |
|
||||
| `atan2sin_ratio` | FLOAT | 0.0 | -100, 100 | atan2/sin blend (0 = off) |
|
||||
|
||||
**Returns:** `MODEL` (patched).
|
||||
|
||||
---
|
||||
|
||||
## Samplers
|
||||
|
||||
All three nodes return a `SAMPLER` object intended to be plugged into the `sampler` input of `KSampler` (or any node that accepts a sampler).
|
||||
|
||||
### `SamplerClyb_BDF`
|
||||
*File: `clyb_Samplers.py`*
|
||||
*Category: `sampling/custom_sampling/samplers`*
|
||||
|
||||
A backward-differentiation-formula-style sampler that takes a single model evaluation at the start of the step, then synthesizes a refined denoised prediction at the `sigma_down` point and combines them with one of three scalar fusions:
|
||||
|
||||
- `projection` — projects the half-step prediction back onto the line spanned by the full-step prediction.
|
||||
- `atan2sin` — uses `atan2(sin(half), cos(full))` to blend the two predictions in angle space.
|
||||
- `atan2sin+projection` (default) — the atan2/sin blend followed by a projection rescaling, combining both stabilizations.
|
||||
|
||||
The sampler detects flow-matching models (FLUX, SD3, Chroma) automatically and switches to the flow-style ancestral update with `alpha_ip1`/`alpha_down`/`renoise_coeff`.
|
||||
|
||||
| Input | Type | Default | Range | Description |
|
||||
|---|---|---|---|---|
|
||||
| `scalar` | ENUM | `atan2sin+projection` | `projection`, `atan2sin`, `atan2sin+projection` | Scalar fusion mode |
|
||||
| `eta` | FLOAT | 1.0 | 0, 100 | Ancestral stochasticity |
|
||||
| `s_noise` | FLOAT | 1.0 | 0, 100 | Noise scaling factor |
|
||||
|
||||
### `SamplerTaylorFlow`
|
||||
*File: `clyb_Samplers.py`*
|
||||
*Category: `sampling/custom_sampling/samplers`*
|
||||
|
||||
A multi-step Taylor-expansion sampler (registered as `taylor_flow` in the ComfyUI sampler list). Implements the algorithm from *"Leveraging Previous Steps: A Training-free Fast Solver for Flow Diffusion"* (Nov 2024):
|
||||
|
||||
1. Maintain a rolling history of `(sigma, denoised)` pairs from the previous `order` steps.
|
||||
2. At each step, perform **one** model evaluation at the current state.
|
||||
3. Build a Vandermonde matrix from the historical `sigma` values.
|
||||
4. Solve for Taylor coefficients `B` that predict the latent at `sigma_next`.
|
||||
5. Apply the Euler step plus a correction term built from the history.
|
||||
6. Inject ancestral noise as usual.
|
||||
|
||||
The four `sigma_calc` modes control how the `sigma_down` / `sigma_up` pair is computed for the ancestral noise injection:
|
||||
|
||||
- `clyb` (default) — logarithmic scaling, the original Clyb scheme.
|
||||
- `taylor-expansion` — exponential factor with a quadratic correction based on the step ratio.
|
||||
- `ancestral` — standard k-diffusion `get_ancestral_step`.
|
||||
- `adaptive` — converges toward the standard scheme based on a normalized variance of the denoised history (small history variance ⇒ less noise).
|
||||
|
||||
The Vandermonde solve supports two methods internally (iterative two-sided equilibration with Tikhonov regularization, and diagonal-dominant extraction) and falls back to a `lstsq` solve if the regularized system is singular.
|
||||
|
||||
| Input | Type | Default | Range | Description |
|
||||
|---|---|---|---|---|
|
||||
| `order` | INT | 8 | 1, 16 | Taylor expansion order (history length) |
|
||||
| `eta` | FLOAT | 1.0 | 0, 1 | Ancestral stochasticity |
|
||||
| `s_noise` | FLOAT | 1.0 | 0, 2 | Noise scaling factor |
|
||||
| `sigma_calc` | ENUM | `clyb` | `clyb`, `taylor-expansion`, `ancestral`, `adaptive` | Ancestral sigma calculation method |
|
||||
|
||||
### `SamplerWrapperCFGPP`
|
||||
*File: `clyb_Samplers.py`*
|
||||
*Category: `sampling/custom_sampling/samplers`*
|
||||
|
||||
A *sampler wrapper* — takes any other `SAMPLER` as input and returns a new sampler that runs the inner sampler but with a CFG++-style denoised recomputation. CFG++ replaces the standard CFG blend with a closed-form denoised that uses the unconditional prediction from the *next* sigma:
|
||||
|
||||
```
|
||||
denoised_star = (sigma * alpha_t * denoised_guided
|
||||
- sigma_next * alpha_s * uncond_denoised) / (sigma - sigma_next)
|
||||
```
|
||||
|
||||
where `alpha_s = sigma * exp(lambda(sigma))` and `alpha_t = sigma_next * exp(lambda(sigma_next))`, with `lambda(s) = sigma_to_half_log_snr(s, model_sampling)`.
|
||||
|
||||
The wrapper installs a `post_cfg_function` hook on the model to capture `uncond_denoised` from the inner sampler, then uses a `CFGPPProxyModel` to perform the recombination at every step. The wrapper itself is **not** added to the standard KSampler dropdown — it is only reachable through this node (or any node that constructs it via `comfy.samplers.ksampler("cfgpp", {...})`).
|
||||
|
||||
| Input | Type | Description |
|
||||
|---|---|---|
|
||||
| `sampler` | SAMPLER | Inner sampler to wrap with CFG++ |
|
||||
|
||||
---
|
||||
|
||||
## Schedulers
|
||||
|
||||
### `InverseSquaredScheduler`
|
||||
*File: `clyb_Schedulers.py`*
|
||||
*Category: `sampling/custom_sampling/schedulers`*
|
||||
|
||||
A sigma scheduler that biases the schedule toward the *end* of diffusion. It uses `(1 - t²)²` (i.e. the inverse of `t` mapped through `(1-t)²`) to pick sigma indices — fine-grained near the end, coarser at the start. The scheduler is also registered into `SCHEDULER_HANDLERS` under the name `inverse_squared`, so it can be used as a string in any node that accepts a scheduler name.
|
||||
|
||||
| Input | Type | Default | Range | Description |
|
||||
|---|---|---|---|---|
|
||||
| `model` | MODEL | — | — | Model to derive the sigma range from |
|
||||
| `steps` | INT | 20 | 3, 1000 | Number of steps |
|
||||
| `denoise` | FLOAT | 1.0 | 0, 1 | Denoise strength (< 1.0 enables img2img-style short schedules) |
|
||||
|
||||
**Returns:** `SIGMAS`.
|
||||
|
||||
### `PrintSigmas`
|
||||
*File: `clyb_Schedulers.py`*
|
||||
*Category: `sampling/custom_sampling/schedulers`*
|
||||
|
||||
A debug helper that prints the incoming `SIGMAS` tensor to the console and passes it through unchanged. Useful for inspecting schedules from other nodes without modifying them.
|
||||
|
||||
| Input | Type | Description |
|
||||
|---|---|---|
|
||||
| `sigmas` | SIGMAS | Sigma tensor to print |
|
||||
|
||||
**Returns:** `SIGMAS` (passthrough).
|
||||
|
||||
---
|
||||
|
||||
## LoRA Loaders
|
||||
|
||||
Both loaders share a frontend extension (`js/clyb_adaptive_lora.js`) that dynamically reveals the next `lora_name_N` / `strength_*_N` triplet only after the previous `lora_name_M` is set to a non-`"none"` value (up to a cap of 20 LoRAs). Setting a slot back to `"none"` hides the trailing widgets, and serialization / deserialization are handled correctly so that hidden widget values survive workflow save/load.
|
||||
|
||||
### `ClybAdaptiveLoraLoader`
|
||||
*File: `clyb_ModelLoader.py`*
|
||||
*Category: `loaders`*
|
||||
|
||||
Apply up to 20 LoRAs to a `(MODEL, CLIP)` pair, in order, by cloning the model once and merging all patches into that single clone. The clone-per-call approach is cheaper than the per-LoRA clone done by ComfyUI's built-in chain loader. LoRA file contents are cached in `self.loaded_loras` keyed by slot index — the cache is invalidated when a different LoRA is selected for that slot.
|
||||
|
||||
| Input | Type | Default | Range | Description |
|
||||
|---|---|---|---|---|
|
||||
| `model` | MODEL | — | — | Diffusion model to patch |
|
||||
| `clip` | CLIP | — | — | CLIP model to patch |
|
||||
| `lora_name_1` | ENUM | — | loras list | First LoRA (set to `"none"` to skip) |
|
||||
| `strength_model_1` | FLOAT | 1.0 | -100, 100 | Diffusion-model strength (negative allowed) |
|
||||
| `strength_clip_1` | FLOAT | 1.0 | -100, 100 | CLIP strength (negative allowed) |
|
||||
| `lora_name_2..20` | ENUM | `"none"` | loras list | Additional LoRAs (revealed as you fill slots) |
|
||||
| `strength_model_2..20` | FLOAT | 1.0 | -100, 100 | Per-slot diffusion-model strength |
|
||||
| `strength_clip_2..20` | FLOAT | 1.0 | -100, 100 | Per-slot CLIP strength |
|
||||
|
||||
**Returns:** `MODEL`, `CLIP`.
|
||||
|
||||
### `ClybAdaptiveLoraLoaderModelOnly`
|
||||
*File: `clyb_ModelLoader.py`*
|
||||
*Category: `loaders`*
|
||||
|
||||
Same as `ClybAdaptiveLoraLoader`, but the `CLIP` input is omitted from the schema and the internal `strength_clip_*` is forced to `0.0` for every slot, leaving only the diffusion-model patches applied. Use this for `MODEL`-only pipelines (e.g. unconditional sampling, flows without a text encoder, or cases where CLIP is wired in separately).
|
||||
|
||||
| Input | Type | Default | Range | Description |
|
||||
|---|---|---|---|---|
|
||||
| `model` | MODEL | — | — | Diffusion model to patch |
|
||||
| `lora_name_1` | ENUM | — | loras list | First LoRA (set to `"none"` to skip) |
|
||||
| `strength_model_1` | FLOAT | 1.0 | -100, 100 | Diffusion-model strength (negative allowed) |
|
||||
| `lora_name_2..20` | ENUM | `"none"` | loras list | Additional LoRAs (revealed as you fill slots) |
|
||||
| `strength_model_2..20` | FLOAT | 1.0 | -100, 100 | Per-slot diffusion-model strength |
|
||||
|
||||
**Returns:** `MODEL`.
|
||||
|
||||
---
|
||||
|
||||
## Project layout
|
||||
|
||||
```
|
||||
ComfyUI-ClybsChromaNodes/
|
||||
├── __init__.py # Entry point: imports modules, registers nodes, exposes WEB_DIRECTORY
|
||||
├── pyproject.toml # Package metadata (v1.0.5)
|
||||
├── LICENSE # Apache License 2.0
|
||||
├── js/
|
||||
│ └── clyb_adaptive_lora.js # Frontend extension for the dynamic LoRA widget behavior
|
||||
├── chroma_NAG.py # ChromaNAG class (currently unregistered)
|
||||
├── clyb_Guidance.py # ClybGuidance model patch
|
||||
├── clyb_Samplers.py # clyb_bdf, taylor_flow samplers + cfgpp wrapper + 3 node classes
|
||||
├── clyb_Schedulers.py # InverseSquaredScheduler, PrintSigmas + scheduler registration
|
||||
└── clyb_ModelLoader.py # ClybAdaptiveLoraLoader, ClybAdaptiveLoraLoaderModelOnly
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [Apache License 2.0](LICENSE).
|
||||
|
||||
## Repository
|
||||
|
||||
[https://github.com/Clybius/ComfyUI-ClybsChromaNodes](https://github.com/Clybius/ComfyUI-ClybsChromaNodes)
|
||||
Reference in New Issue
Block a user