Added SageAttention support

Better handling of OOM in TAESD previewer
Move extended BlockOps documentation out of the main README
This commit is contained in:
blepping
2024-11-22 07:46:09 -07:00
parent 775794b671
commit f4d0310f2a
7 changed files with 574 additions and 280 deletions
+19 -279
View File
@@ -14,6 +14,7 @@ A ComfyUI nodes collection of utility and model patching functions. Also include
8. Allows swapping to a refiner model at a predefined time (look for the [BlehRefinerAfter](#blehrefinerafter) node).
9. Allow defining arbitrary model patches (look for the [BlehBlockOps](#blehblockops) node).
10. Experimental blockwise CFG type effect (look for the [BlehBlockCFG](#blehblockcfg) node).
11. [SageAttention](https://github.com/thu-ml/SageAttention/) support either globally or as a sampler wrapper. Look for the [BlehSageAttentionSampler](#blehsageattentionsampler) and `BlehGlobalSageAttention` nodes.
## Configuration
@@ -159,291 +160,31 @@ _Note_: Probably only works with SD 1.x and SDXL. Middle block patching will pro
**Note**: Doesn't work correctly with Tiled Diffusion when using tile batch sizes over 1.
### BlehSageAttentionSampler
Allows using SageAttention as a sampler wrapper. SageAttention is an attention optimization. SDXL is known to work, CogVideo might? SD15 doesn't use supported head sizes. In the case where the model's head sizes aren't supported, it will fall back to the default optimized attention implementation so enabling it should be safe even on unsupported models.
You won't see much of a performance difference at low resolutions, once you get up to 2048x2048 it starts becoming noticeable. In my testing, the difference was about 30% at 4096x4096 and 10% at 2048x2048.
**Note:** Requires manually installing SageAttention into your Python environment. Should work with SageAttention 1.0 and 2.0 (2.0 currently requires CUDA 8+).
### BlehGlobalSageAttention
Enables SageAttention (see description above) globally. Prefer using the sampler wrapper when possible as it has less sharp edges.
**Note**: This isn't a real model patch. The settings are applied when the node runs, so, for example, if you enable it and then bypass the node that won't actually disable SageAttention. The node needs to run each time you want changed settings applied.
### BlehBlockOps
Very experimental advanced node that allows defining model patches using YAML. This node is still under development and may be changed.
The top level YAML should consist of a list of objects with a condition `if`, a list of `ops` that run if the condition succeeds.
Objects `then` and `else` also take the same form as the top level object and apply when the `if` condition matches (or not in the case of `else`).
All object fields (`if`, `then`, `else`, `ops`) are optional. An empty object is valid, it just doesn't do anything.
```yaml
- if:
cond1: [value1, value2]
cond2: value # Values may be specified as a list or single item.
ops: [[opname1, oparg1, oparg2], [opname2, oparg1, oparg2]]
then:
if: [[opname1, oparg1, oparg2]] # Conditions may also be specified as a list.
ops: [] # and so on
else:
ops: []
# then and else may also be nested to an arbitrary depth.
```
*Note*: Blocks match by default, conditions restrict them. So a block with no `if` matches everything.
<details>
<summary>Expand to see full node documentation</summary>
#### Conditions
**`type`**: One of `input`, `input_after_skip`, `middle`, `output` (preceding are block patches), `latent`, `post_cfg`.
**Note**: ComfyUI doesn't allow patching the middle blocks by default, this feature is only available if you have
[FreeU Advanced](https://github.com/WASasquatch/FreeU_Advanced) installed and enabled. (It patches ComfyUI to support patching
the middle blocks.)
**`block`**: The block number. Only applies when type is `input`, `input_after_skip`, `middle` or `output`.
**`stage`**: The model stage. Applies to the same types as `block`. You can think of this in terms of FreeU's `b1`, `b2` - the number is the stage.
**`percent`**: Percentage of sampling completed as a number between `0.0` and `1.0`. Note that this is sampling percentage, not percentage of steps.
Does not apply to type `latent`.
**`from_percent`**: Matches when sampling is greater or equal to the percent. Same restrictions as `percent`.
**`to_percent`**: Matches when sampling is less or equal to the percent. Same restrictions as `from_percent`.
**`step`**: Only applies when sigmas are connected to the `BlehBlockOps` node. A step will be determined as the index of the closest
matching sigma. In other words, if you don't connect sigmas that exactly match the sigmas used for sampling you won't get accurate steps.
Does not apply to type `latent`.
**`step_exact`**: Same restrictions as `step`, however will only be set if the current sigma _exactly_ matches a step. Otherwise the
value will be `-1`.
**`from_step`**: As above, but matches when the step is greater or equal to the value.
**`from_step`**: As above, but matches when the step is less or equal to the value.
**`step_interval`**: Same restrictions as the other step condition types. Matches when the step modulus interval is 0. In other words,
every other step starting from the first step you'd use an interval of `2` and the `then` branch (since `1 % 2 == 1` which is not 0).
**`cond`**: Generic condition, has two forms:
*Comparison*: Takes three arguments: comparison type (`eq`, `ne`, `gt`, `lt`, `ge`, `le`), a condition type with
a numeric value (`block`, `stage`, `percent`, `step`, `step_exact`) and a value or list of values to compare with.
Example:
```yaml
- if: [cond, [lt, percent, 0.35]]
```
*Logic*: Takes a logic operation type (`not`, `and`, `or`) and a list of condition blocks. **Note**: The logic operation is applied
to the result of the condition block and not the fields within it.
Example:
```yaml
- if:
cond: [not,
[cond, [or,
[cond, [lt, step, 1]],
[cond, [gt, step, 5]],
]]
] # A verbose way of expressing step >= 1 and step <= 5
- if:
- [cond, [ge, step, 1]]
- [cond, [le, step, 5]] # Same as above
- if: [[from_step, 1], [to_step, 5]] # Also same as above
```
#### Operations
Operations mostly modify a target which can be `h` or `hsp`. `hsp` is only a valid target when `type` is `output`. I think it has something
to do with skip connections but I don't know the specifics. It's important for FreeU.
Default values are show in parenthesis next to the operation argument name. You may supply an incomplete argument list,
in which case default values will be used for the remaining arguments. Ex: `[flip]` is the same as `[flip, h]`. You may
also specify the arguments as a map, keys that aren't included will use the default values. Ex: `[flip, {direction: h}]`
**`slice`**: Applies a filtering operation on a slice of the target.
1. `scale`(`1.0`): Slice scale, `1.0` would mean apply to 100% of the target, `0.5` would mean 50% of it.
2. `strength`(`1.0`): Scales the target. `1.0` would mean 100%.
3. `blend`(`1.0`): Ratio of the transformed value to blend in. `1.0` means replace it with no blending.
4. `blend_mode`(`bislerp`): See the blend mode section.
5. `use_hidden_mean`(`true`): No idea what this does really, but FreeU V2 uses it when slicing and V1 doesn't.
**`ffilter`**: Applies a Fourier filter operation to the target.
1. `scale`(`1.0`): Scales the target. `1.0` would mean 100%.
2. `filter`(`none`): May be a string with a predefined filter name or a list of lists defining filters. See the filter section.
3. `filter_strength`(`0.5`): Strength of the filter. `1.0` would mean to apply it at 100%.
4. `threshold`(`1`): Threshold for the Fourier filter. This generally should be 1.
**`scale_torch`**: Scales the target up or down, using PyTorch's `interpolate` function.
1. `type`(`bicubic`): One of `bicubic`, `nearest`, `bilinear` or `area`.
2. `scale_width`(`1.0`): Ratio to scale the width. `2.0` would mean double it, `0.5` would mean half of it.
3. `scale_height`(`1.0`): As above.
4. `antialias`(`false`): `true` to apply antialiasing after scaling or `false`.
**`unscale_torch`**: Scale the target to be the same size as `hsp`. Only can be used when the target isn't `hsp` and condition `type` is `output`.
Can be used to reverse a `scale` or `scale_torch` operation without having to worry about calculating the ratios to get the original size back.
1. `type`(`bicubic`): Same as `scale_torch`.
2. `antialias`(`false`): Same as `scale_torch`.
**`scale`**: Scales the target up or down using various functions. See the scaling functions section.
1. `type_width`(`bicubic`): Scaling function to use for width. Note if the type is one of the ones from `scale_torch` it cannot be combined with other scaling functions.
2. `type_height`(`bicubic`): As above.
3. `scale_width`(`1.0`): Ratio to scale the width. `2.0` would mean double it, `0.5` would mean half of it.
4. `scale_height`(`1.0`): As above.
5. `antialias_size`(`0`): Size of the antialias kernel. Between 1 and 7 inclusive. Higher numbers seem to increase blurriness.
**`unscale`**: Like `unscale_torch` except it supports more scale functions and can specify width/height scale function independently.
Same restriction as `scale`.
1. `type_width`(`bicubic`): Scaling function to use for width. Note if the type is one of the ones from `scale_torch` it cannot be combined with other scaling functions.
2. `type_height`(`bicubic`): As above.
3. `antialias_size`(`0`): Size of the antialias kernel. Between 1 and 7 inclusive. Higher numbers seem to increase blurriness.
**`flip`**: Flips the target.
1. `direction`(`h`): `h` for horizontal flip, `v` for vertical. Note that latents generally don't tolerate being flipped very well.
**`rot90`**: Does a 90 degree rotation of the target.
1. `count`(`1`): Number of times to rotate (can also be negative). Note that if you rotate in a way that makes the tensors not match then stuff will probably break.
also as with `flip` it generally is pretty destructive to latents.
**`roll`**: Rotates the values in a dimension of the target.
1. `direction`(`c`): `horizontal`, `vertical`, `channels`. Note that when `type` is `input`, `input_after_skip`, `middle` or `output` you aren't actually dealing
with a latent. The second dimension ("channels") is actually the features in the layer. Rotating them can produce some pretty weird effects.
2. `amount`(`1`): If it's a number greater than `-1.0` and less than `1.0` this will rotate forward or backward by a percentage of the size. Otherwise it is
interpreted as the number of items to rotate forward or backward.
**`roll_channels`**: Same as `roll` but you only specify the count, it always targets channels and you can't use percentages.
1. `count`(`1`): Number of channels to rotate. May be negative.
**`target_skip`**: Changes the target.
1. `active`(`true`): If `true` will target `hsp`, otherwise will target `h`. Targeting `hsp` is only allowed when `type` is `output`, no effect otherwise.
**`multiply`**: Multiply the target by the value.
1. `factor`(`1.0`): Multiplier. `2.0` would double all values in the target.
**`antialias`**: Applies an antialias effect to the target. Works the same ase with `scale`.
1. `size`(`7`): The antialias kernel size as a number between 1 and 7.
**`noise`**: Adds noise to the target. Can only be used when sigmas are connected. Noise will be scaled by `sigma - sigma_next`.
1. `scale`(`0.5`): Additionally scale the noise by the supplied factor. `1.0` would mean no scaling, `2.0` would double it, etc.
2. `type`(`gaussian`): Only `gaussian` unless [ComfyUI-sonar](https://github.com/blepping/ComfyUI-sonar) is installed and active, otherwise
you may use the additional noise types Sonar provides.
3. `scale_mode`(`sigdiff`): `sigdiff` scales the noise by the current sigma minus the next (requires sigmas connected),
`sigma` scales by the current sigma, `none` or an invalid type uses no scaling (you get exactly `noise * scale`).
**`debug`**: Outputs some debug information about the state.
**`blend_op`**: Allows applying a blend function to the result of another operation.
1. `blend`(`1.0`): Ratio of the transformed value to blend in.
2. `blend_mode`(`bislerp`): See the blend mode section.
3. `ops`(empty): The operation as a list, with the name first. i.e. `[blend_op, 0.5, inject, [multiply, 0.5]]`. May also be a list of operations.
**`pad`**: Pads the target.
1. `mode`(`reflect`): One of `constant`, `reflect`, `replicate`, `circular` - see https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html#torch.nn.functional.pad
2. `top`(`0`): Amount of top padding. If this is a floating point value, it will be treated as a percentage of the dimension.
3. `bottom`(`0`): " " "
4. `left`(`0`): " " "
5. `right`(`0`): " " "
6. `constant`(`0`): Constant value to use, only applies when mode is `constant`.
_Note_: If you pad `input` (rather than `input_after_skip`) then you will need to crop the corresponding block in `output`
for both `h` and `hsp` (i.e. with `target_skip`).
**`crop`**: Crops the target.
1. `top`(`0`): Items to crop from the top. If this is a floating point value, it will be treated as a percentage of the dimension.
2. `bottom`(`0`): " " "
3. `left`(`0`): " " "
4. `right`(`0`): " " "
**`mask_example_op`**: Applies providing a mask by example and masks the result of an operation or list of operations.
1. `scale_mode`(`bicubic`) type: Same as with `scale`.
2. `antialias`(`7`) size: Same as with `scale`.
3. `mask`(mask targeting corners): A two dimensional list of mask values. See below.
4. `ops`(empty): Same as with `blend_op`.
Simple example of a mask:
```plaintext
[ [1.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 1.0],
]
```
With this mask, the result of the mask ops will be applied at full strength to the corners. The mask is scaled up to
the size of the target tensor, so with this example the masked corners will be proportionately quite large if the
latent or tensor is much bigger than the mask. There are two convenience tricks for defining larger masks without
having to specify each value:
* If the first element in a row is `"rep"` then the second element is interpreted as a row repeat count and the
rest of the items in the row constitute the row. Ex: `["rep", 2, 1, 0, 1]` expands to two rows of `1, 0, 1`.
* If a column item is a list, the first element is interpreted as the repeat count and the remaining items are repeated
however many times. Ex: `[2, 1.2, 0.5]` as a column would expand to `1.2, 0.5, 1.2, 0.5`.
These two shortcuts can be combined. A mask of `[["rep", 2, 1, [3, 0], 2]]` expands to:
```plaintext
[
[1, 0, 0, 0, 2],
[1, 0, 0, 0, 2],
]
```
**`apply_enhancement`**: Applies an [enhancement](#enhancement-types) to the target.
1. `scale`: 1.0
2. `type`: korniabilateralblur
#### Blend Modes
1. bislerp: Interpolates between tensors a and b using normalized linear interpolation.
2. colorize: Supposedly transfers color. May or may not work that way.
3. cosinterp: Cosine interpolation.
4. cuberp
5. hslerp: Hybrid Spherical Linear Interporation, supposedly smooths transitions between orientations and colors.
6. inject: Inject just adds the value scaled by the ratio, so if ratio is `1.0` this simply adds it.
7. lerp: Linear interpolation.
8. lineardodge: Supposedly simulates a brightning effect.
#### Filters
1. none
2. bandpass
3. lowpass: Allows low frequencies and suppresses high frequencies.
4. highpass: Allows high frequencies and suppresses low frequencies.
5. passthrough: Maybe doesn't do anything?
6. gaussianblur: Blur.
7. edge: Edge enhance.
8. sharpen: Sharpens the target.
9. multilowpass: The multi versions apply to multiple bands.
10. multihighpass
11. multipassthrough
12. multigaussianblur
13. multiedge
14. multisharpen
Custom filters may also be defined. For example, `gaussianblur` in the YAML filter definition would be `[[10,0.5]]`,
`sharpen` would be `[[10, 1.5]]`.
#### Scaling Functions
See [Scaling Types](#scaling-types) below.
* [Extended BlockOps documentation](docs/blockops.md)
#### Examples
Just to show what's possible, you can implement model patches like FreeU or Deep Shrink using BlockOps.
**FreeU V2**
```yaml
@@ -478,7 +219,6 @@ See [Scaling Types](#scaling-types) below.
ops: [[unscale, bicubic, bicubic, 0]]
```
</details>
### BlehLatentOps
+3
View File
@@ -15,6 +15,7 @@ from .py.nodes import (
modelPatchConditional,
ops,
refinerAfter,
sageAttention,
samplers,
)
@@ -27,6 +28,7 @@ NODE_CLASS_MAPPINGS = {
"BlehDisableNoise": misc.BlehDisableNoise,
"BlehDiscardPenultimateSigma": misc.DiscardPenultimateSigma,
"BlehForceSeedSampler": samplers.BlehForceSeedSampler,
"BlehGlobalSageAttention": sageAttention.BlehGlobalSageAttention,
"BlehHyperTile": hyperTile.HyperTileBleh,
"BlehInsaneChainSampler": samplers.BlehInsaneChainSampler,
"BlehLatentOps": ops.BlehLatentOps,
@@ -34,6 +36,7 @@ NODE_CLASS_MAPPINGS = {
"BlehModelPatchConditional": modelPatchConditional.ModelPatchConditionalNode,
"BlehPlug": misc.BlehPlug,
"BlehRefinerAfter": refinerAfter.BlehRefinerAfter,
"BlehSageAttentionSampler": sageAttention.BlehSageAttentionSampler,
"BlehSetSamplerPreset": samplers.BlehSetSamplerPreset,
}
+4
View File
@@ -2,6 +2,10 @@
Note, only relatively significant changes to user-visible functionality will be included here. Most recent changes at the top.
## 20241122
* Added the `BlehGlobalSageAttention` and `BlehSageAttentionSampler` nodes. See the README for details.
## 20241103
* Added the `BlehSetSamplerPreset` node and sampler presets feature.
+277
View File
@@ -0,0 +1,277 @@
# BlehBlockOps
The top level YAML should consist of a list of objects with a condition `if`, a list of `ops` that run if the condition succeeds.
Objects `then` and `else` also take the same form as the top level object and apply when the `if` condition matches (or not in the case of `else`).
All object fields (`if`, `then`, `else`, `ops`) are optional. An empty object is valid, it just doesn't do anything.
```yaml
- if:
cond1: [value1, value2]
cond2: value # Values may be specified as a list or single item.
ops: [[opname1, oparg1, oparg2], [opname2, oparg1, oparg2]]
then:
if: [[opname1, oparg1, oparg2]] # Conditions may also be specified as a list.
ops: [] # and so on
else:
ops: []
# then and else may also be nested to an arbitrary depth.
```
*Note*: Blocks match by default, conditions restrict them. So a block with no `if` matches everything.
#### Blend Modes
1. bislerp: Interpolates between tensors a and b using normalized linear interpolation.
2. colorize: Supposedly transfers color. May or may not work that way.
3. cosinterp: Cosine interpolation.
4. cuberp
5. hslerp: Hybrid Spherical Linear Interporation, supposedly smooths transitions between orientations and colors.
6. inject: Inject just adds the value scaled by the ratio, so if ratio is `1.0` this simply adds it.
7. lerp: Linear interpolation.
8. lineardodge: Supposedly simulates a brightning effect.
#### Filters
1. none
2. bandpass
3. lowpass: Allows low frequencies and suppresses high frequencies.
4. highpass: Allows high frequencies and suppresses low frequencies.
5. passthrough: Maybe doesn't do anything?
6. gaussianblur: Blur.
7. edge: Edge enhance.
8. sharpen: Sharpens the target.
9. multilowpass: The multi versions apply to multiple bands.
10. multihighpass
11. multipassthrough
12. multigaussianblur
13. multiedge
14. multisharpen
Custom filters may also be defined. For example, `gaussianblur` in the YAML filter definition would be `[[10,0.5]]`,
`sharpen` would be `[[10, 1.5]]`.
#### Scaling Functions
See [Scaling Types](#scaling-types) below.
#### Conditions
**`type`**: One of `input`, `input_after_skip`, `middle`, `output` (preceding are block patches), `latent`, `post_cfg`.
**Note**: ComfyUI doesn't allow patching the middle blocks by default, this feature is only available if you have
[FreeU Advanced](https://github.com/WASasquatch/FreeU_Advanced) installed and enabled. (It patches ComfyUI to support patching
the middle blocks.)
**`block`**: The block number. Only applies when type is `input`, `input_after_skip`, `middle` or `output`.
**`stage`**: The model stage. Applies to the same types as `block`. You can think of this in terms of FreeU's `b1`, `b2` - the number is the stage.
**`percent`**: Percentage of sampling completed as a number between `0.0` and `1.0`. Note that this is sampling percentage, not percentage of steps.
Does not apply to type `latent`.
**`from_percent`**: Matches when sampling is greater or equal to the percent. Same restrictions as `percent`.
**`to_percent`**: Matches when sampling is less or equal to the percent. Same restrictions as `from_percent`.
**`step`**: Only applies when sigmas are connected to the `BlehBlockOps` node. A step will be determined as the index of the closest
matching sigma. In other words, if you don't connect sigmas that exactly match the sigmas used for sampling you won't get accurate steps.
Does not apply to type `latent`.
**`step_exact`**: Same restrictions as `step`, however will only be set if the current sigma _exactly_ matches a step. Otherwise the
value will be `-1`.
**`from_step`**: As above, but matches when the step is greater or equal to the value.
**`from_step`**: As above, but matches when the step is less or equal to the value.
**`step_interval`**: Same restrictions as the other step condition types. Matches when the step modulus interval is 0. In other words,
every other step starting from the first step you'd use an interval of `2` and the `then` branch (since `1 % 2 == 1` which is not 0).
**`cond`**: Generic condition, has two forms:
*Comparison*: Takes three arguments: comparison type (`eq`, `ne`, `gt`, `lt`, `ge`, `le`), a condition type with
a numeric value (`block`, `stage`, `percent`, `step`, `step_exact`) and a value or list of values to compare with.
Example:
```yaml
- if: [cond, [lt, percent, 0.35]]
```
*Logic*: Takes a logic operation type (`not`, `and`, `or`) and a list of condition blocks. **Note**: The logic operation is applied
to the result of the condition block and not the fields within it.
Example:
```yaml
- if:
cond: [not,
[cond, [or,
[cond, [lt, step, 1]],
[cond, [gt, step, 5]],
]]
] # A verbose way of expressing step >= 1 and step <= 5
- if:
- [cond, [ge, step, 1]]
- [cond, [le, step, 5]] # Same as above
- if: [[from_step, 1], [to_step, 5]] # Also same as above
```
#### Operations
Operations mostly modify a target which can be `h` or `hsp`. `hsp` is only a valid target when `type` is `output`. I think it has something
to do with skip connections but I don't know the specifics. It's important for FreeU.
Default values are show in parenthesis next to the operation argument name. You may supply an incomplete argument list,
in which case default values will be used for the remaining arguments. Ex: `[flip]` is the same as `[flip, h]`. You may
also specify the arguments as a map, keys that aren't included will use the default values. Ex: `[flip, {direction: h}]`
**`slice`**: Applies a filtering operation on a slice of the target.
1. `scale`(`1.0`): Slice scale, `1.0` would mean apply to 100% of the target, `0.5` would mean 50% of it.
2. `strength`(`1.0`): Scales the target. `1.0` would mean 100%.
3. `blend`(`1.0`): Ratio of the transformed value to blend in. `1.0` means replace it with no blending.
4. `blend_mode`(`bislerp`): See the blend mode section.
5. `use_hidden_mean`(`true`): No idea what this does really, but FreeU V2 uses it when slicing and V1 doesn't.
**`ffilter`**: Applies a Fourier filter operation to the target.
1. `scale`(`1.0`): Scales the target. `1.0` would mean 100%.
2. `filter`(`none`): May be a string with a predefined filter name or a list of lists defining filters. See the filter section.
3. `filter_strength`(`0.5`): Strength of the filter. `1.0` would mean to apply it at 100%.
4. `threshold`(`1`): Threshold for the Fourier filter. This generally should be 1.
**`scale_torch`**: Scales the target up or down, using PyTorch's `interpolate` function.
1. `type`(`bicubic`): One of `bicubic`, `nearest`, `bilinear` or `area`.
2. `scale_width`(`1.0`): Ratio to scale the width. `2.0` would mean double it, `0.5` would mean half of it.
3. `scale_height`(`1.0`): As above.
4. `antialias`(`false`): `true` to apply antialiasing after scaling or `false`.
**`unscale_torch`**: Scale the target to be the same size as `hsp`. Only can be used when the target isn't `hsp` and condition `type` is `output`.
Can be used to reverse a `scale` or `scale_torch` operation without having to worry about calculating the ratios to get the original size back.
1. `type`(`bicubic`): Same as `scale_torch`.
2. `antialias`(`false`): Same as `scale_torch`.
**`scale`**: Scales the target up or down using various functions. See the scaling functions section.
1. `type_width`(`bicubic`): Scaling function to use for width. Note if the type is one of the ones from `scale_torch` it cannot be combined with other scaling functions.
2. `type_height`(`bicubic`): As above.
3. `scale_width`(`1.0`): Ratio to scale the width. `2.0` would mean double it, `0.5` would mean half of it.
4. `scale_height`(`1.0`): As above.
5. `antialias_size`(`0`): Size of the antialias kernel. Between 1 and 7 inclusive. Higher numbers seem to increase blurriness.
**`unscale`**: Like `unscale_torch` except it supports more scale functions and can specify width/height scale function independently.
Same restriction as `scale`.
1. `type_width`(`bicubic`): Scaling function to use for width. Note if the type is one of the ones from `scale_torch` it cannot be combined with other scaling functions.
2. `type_height`(`bicubic`): As above.
3. `antialias_size`(`0`): Size of the antialias kernel. Between 1 and 7 inclusive. Higher numbers seem to increase blurriness.
**`flip`**: Flips the target.
1. `direction`(`h`): `h` for horizontal flip, `v` for vertical. Note that latents generally don't tolerate being flipped very well.
**`rot90`**: Does a 90 degree rotation of the target.
1. `count`(`1`): Number of times to rotate (can also be negative). Note that if you rotate in a way that makes the tensors not match then stuff will probably break.
also as with `flip` it generally is pretty destructive to latents.
**`roll`**: Rotates the values in a dimension of the target.
1. `direction`(`c`): `horizontal`, `vertical`, `channels`. Note that when `type` is `input`, `input_after_skip`, `middle` or `output` you aren't actually dealing
with a latent. The second dimension ("channels") is actually the features in the layer. Rotating them can produce some pretty weird effects.
2. `amount`(`1`): If it's a number greater than `-1.0` and less than `1.0` this will rotate forward or backward by a percentage of the size. Otherwise it is
interpreted as the number of items to rotate forward or backward.
**`roll_channels`**: Same as `roll` but you only specify the count, it always targets channels and you can't use percentages.
1. `count`(`1`): Number of channels to rotate. May be negative.
**`target_skip`**: Changes the target.
1. `active`(`true`): If `true` will target `hsp`, otherwise will target `h`. Targeting `hsp` is only allowed when `type` is `output`, no effect otherwise.
**`multiply`**: Multiply the target by the value.
1. `factor`(`1.0`): Multiplier. `2.0` would double all values in the target.
**`antialias`**: Applies an antialias effect to the target. Works the same ase with `scale`.
1. `size`(`7`): The antialias kernel size as a number between 1 and 7.
**`noise`**: Adds noise to the target. Can only be used when sigmas are connected. Noise will be scaled by `sigma - sigma_next`.
1. `scale`(`0.5`): Additionally scale the noise by the supplied factor. `1.0` would mean no scaling, `2.0` would double it, etc.
2. `type`(`gaussian`): Only `gaussian` unless [ComfyUI-sonar](https://github.com/blepping/ComfyUI-sonar) is installed and active, otherwise
you may use the additional noise types Sonar provides.
3. `scale_mode`(`sigdiff`): `sigdiff` scales the noise by the current sigma minus the next (requires sigmas connected),
`sigma` scales by the current sigma, `none` or an invalid type uses no scaling (you get exactly `noise * scale`).
**`debug`**: Outputs some debug information about the state.
**`blend_op`**: Allows applying a blend function to the result of another operation.
1. `blend`(`1.0`): Ratio of the transformed value to blend in.
2. `blend_mode`(`bislerp`): See the blend mode section.
3. `ops`(empty): The operation as a list, with the name first. i.e. `[blend_op, 0.5, inject, [multiply, 0.5]]`. May also be a list of operations.
**`pad`**: Pads the target.
1. `mode`(`reflect`): One of `constant`, `reflect`, `replicate`, `circular` - see https://pytorch.org/docs/stable/generated/torch.nn.functional.pad.html#torch.nn.functional.pad
2. `top`(`0`): Amount of top padding. If this is a floating point value, it will be treated as a percentage of the dimension.
3. `bottom`(`0`): " " "
4. `left`(`0`): " " "
5. `right`(`0`): " " "
6. `constant`(`0`): Constant value to use, only applies when mode is `constant`.
_Note_: If you pad `input` (rather than `input_after_skip`) then you will need to crop the corresponding block in `output`
for both `h` and `hsp` (i.e. with `target_skip`).
**`crop`**: Crops the target.
1. `top`(`0`): Items to crop from the top. If this is a floating point value, it will be treated as a percentage of the dimension.
2. `bottom`(`0`): " " "
3. `left`(`0`): " " "
4. `right`(`0`): " " "
**`mask_example_op`**: Applies providing a mask by example and masks the result of an operation or list of operations.
1. `scale_mode`(`bicubic`) type: Same as with `scale`.
2. `antialias`(`7`) size: Same as with `scale`.
3. `mask`(mask targeting corners): A two dimensional list of mask values. See below.
4. `ops`(empty): Same as with `blend_op`.
Simple example of a mask:
```plaintext
[ [1.0, 0.0, 0.0, 1.0],
[0.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 1.0],
]
```
With this mask, the result of the mask ops will be applied at full strength to the corners. The mask is scaled up to
the size of the target tensor, so with this example the masked corners will be proportionately quite large if the
latent or tensor is much bigger than the mask. There are two convenience tricks for defining larger masks without
having to specify each value:
* If the first element in a row is `"rep"` then the second element is interpreted as a row repeat count and the
rest of the items in the row constitute the row. Ex: `["rep", 2, 1, 0, 1]` expands to two rows of `1, 0, 1`.
* If a column item is a list, the first element is interpreted as the repeat count and the remaining items are repeated
however many times. Ex: `[2, 1.2, 0.5]` as a column would expand to `1.2, 0.5, 1.2, 0.5`.
These two shortcuts can be combined. A mask of `[["rep", 2, 1, [3, 0], 2]]` expands to:
```plaintext
[
[1, 0, 0, 0, 2],
[1, 0, 0, 0, 2],
]
```
**`apply_enhancement`**: Applies an [enhancement](#enhancement-types) to the target.
1. `scale`: 1.0
2. `type`: korniabilateralblur
+8 -1
View File
@@ -1,3 +1,4 @@
import logging
import math
from time import time
@@ -173,7 +174,13 @@ class BetterTAESDPreviewer(_ORIG_PREVIEWER):
return self.cached
if x0.shape[0] == 0:
return self.blank # Shouldn't actually be possible.
return self.decoded_to_image(*self._decode_latent(x0))
try:
return self.decoded_to_image(*self._decode_latent(x0))
except torch.OutOfMemoryError:
logging.warning(
"*** BlehBetterTAESDPreviews: Got out of memory error while decoding preview - skipping.",
)
return self.blank
if not isinstance(latent_preview.TAESDPreviewerImpl, BetterTAESDPreviewer):
View File
+263
View File
@@ -0,0 +1,263 @@
from __future__ import annotations
from functools import partial
from typing import TYPE_CHECKING
import yaml
from comfy.ldm.modules import attention as comfy_attention
from comfy.samplers import KSAMPLER
try:
import sageattention
except ImportError:
sageattn = None
if TYPE_CHECKING:
import torch
orig_attention = comfy_attention.optimized_attention
def attention_sage( # noqa: PLR0917
q,
k,
v,
heads,
mask=None,
attn_precision=None,
skip_reshape=False,
sageattn_allow_head_sizes: None | set | tuple | list = None,
sageattn_function=sageattention.sageattn,
sageattn_verbose=False,
**kwargs: dict[str],
):
if sageattn_allow_head_sizes is None:
sageattn_allow_head_sizes = {64, 96, 128}
if skip_reshape:
b, _, _, dim_head = q.shape
else:
b, _, dim_head = q.shape
dim_head //= heads
enabled = dim_head in sageattn_allow_head_sizes
if sageattn_verbose:
print(
f"\n>> SAGE({enabled}): reshape={not skip_reshape}, dim_head={dim_head}, heads={heads}, adj_heads={q.shape[-1] // heads}, args: {kwargs}\n",
)
if not enabled:
return orig_attention(
q,
k,
v,
heads,
mask=mask,
attn_precision=attn_precision,
skip_reshape=skip_reshape,
)
if not skip_reshape:
if kwargs.get("tensor_layout") != "NHD":
q, k, v = (
t.view(b, -1, heads, dim_head).transpose(1, 2) for t in (q, k, v)
)
do_transpose = True
else:
q, k, v = (t.view(b, -1, heads, dim_head) for t in (q, k, v))
do_transpose = False
sm_scale_hd_key = f"sm_scale_{dim_head}"
sm_scale_hd = kwargs.get(sm_scale_hd_key)
if sm_scale_hd is not None:
del kwargs[sm_scale_hd_key]
kwargs["sm_scale"] = sm_scale_hd
result = sageattn_function(
q,
k,
v,
is_causal=False,
attn_mask=mask,
dropout_p=0.0,
**kwargs,
)
if do_transpose:
result = result.transpose(1, 2)
return result.reshape(b, -1, heads * dim_head)
def monkeypatch_attention(enabled: bool, **kwargs: dict[str]):
sageattn_function = getattr(
sageattention,
kwargs.pop("sageattn_function", "sageattn"),
)
comfy_attention.optimized_attention = (
orig_attention
if not enabled
else partial(
attention_sage,
sageattn_function=sageattn_function,
**kwargs,
)
)
def get_yaml_parameters(yaml_parameters: None | str = None) -> dict:
if not yaml_parameters:
return {}
extra_params = yaml.safe_load(yaml_parameters)
if extra_params is None:
return {}
if not isinstance(extra_params, dict):
raise ValueError( # noqa: TRY004
"DiffuseHighSampler: yaml_parameters must either be null or an object",
)
return extra_params
class BlehGlobalSageAttention:
RETURN_TYPES = ("MODEL",)
FUNCTION = "go"
CATEGORY = "model_patches"
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"model": ("MODEL",),
"enabled": (
"BOOLEAN",
{"default": True},
),
"yaml_parameters": (
"STRING",
{
"tooltip": "Allows specifying custom parameters via YAML. These are mostly passed directly to the SageAttention function with no error checking. Must be empty or a YAML object.",
"dynamicPrompts": False,
"multiline": True,
"defaultInput": True,
},
),
},
}
@classmethod
def go(cls, *, model: object, enabled: bool, yaml_parameters: None | str = None):
if sageattention is None:
raise RuntimeError(
"sageattention not installed to Python environment: SageAttention feature unavailable",
)
monkeypatch_attention(enabled, **get_yaml_parameters(yaml_parameters))
return (model,)
def sageattn_sampler(
model: object,
x: torch.Tensor,
sigmas: torch.Tensor,
*,
sageattn_sampler_options: tuple,
**kwargs: dict,
) -> torch.Tensor:
sampler, start_percent, end_percent, sageattn_kwargs = sageattn_sampler_options
ms = model.inner_model.inner_model.model_sampling
start_sigma, end_sigma = (
round(ms.percent_to_sigma(start_percent), 4),
round(ms.percent_to_sigma(end_percent), 4),
)
del ms
def model_wrapper(x: torch.Tensor, sigma: torch.Tensor, **extra_args: dict[str]):
sigma_float = float(sigma.max().detach().cpu())
enabled = end_sigma <= sigma_float <= start_sigma
backup_attn = comfy_attention.optimized_attention
if enabled:
monkeypatch_attention(enabled=True, **sageattn_kwargs)
else:
comfy_attention.optimized_attention = orig_attention
try:
result = model(x, sigma, **extra_args)
finally:
comfy_attention.optimized_attention = backup_attn
return result
for k in (
"inner_model",
"sigmas",
):
if hasattr(model, k):
setattr(model_wrapper, k, getattr(model, k))
return sampler.sampler_function(
model_wrapper,
x,
sigmas,
**kwargs,
**sampler.extra_options,
)
class BlehSageAttentionSampler:
CATEGORY = "sampling/custom_sampling/samplers"
RETURN_TYPES = ("SAMPLER",)
FUNCTION = "go"
@classmethod
def INPUT_TYPES(cls) -> dict:
return {
"required": {
"sampler": ("SAMPLER",),
},
"optional": {
"start_percent": (
"FLOAT",
{
"default": 0.0,
"min": 0.0,
"max": 1.0,
"step": 0.001,
"tooltip": "Time the effect becomes active as a percentage of sampling, not steps.",
},
),
"end_percent": (
"FLOAT",
{
"default": 1.0,
"min": 0.0,
"max": 1.0,
"step": 0.001,
"tooltip": "Time the effect ends (inclusive) as a percentage of sampling, not steps.",
},
),
"yaml_parameters": (
"STRING",
{
"tooltip": "Allows specifying custom parameters via YAML. These are mostly passed directly to the SageAttention function with no error checking. Must be empty or a YAML object.",
"dynamicPrompts": False,
"multiline": True,
"defaultInput": True,
},
),
},
}
@classmethod
def go(
cls,
sampler: object,
*,
start_percent=0.0,
end_percent=1.0,
yaml_parameters: None | str = None,
) -> tuple:
if sageattention is None:
raise RuntimeError(
"sageattention not installed to Python environment: SageAttention feature unavailable",
)
return (
KSAMPLER(
sageattn_sampler,
extra_options={
"sageattn_sampler_options": (
sampler,
start_percent,
end_percent,
get_yaml_parameters(yaml_parameters),
),
},
),
)