feat: add ComfyUI-style prompt weighting via compel
- Support (word:weight), [word], ((nested)) syntax - Support BREAK for conditioning chunking (.and() translation) - Use CompelForSDXL (modern API, avoids deprecation) - Add --raw flag to bypass weighting and fall back to plain encoding - Update README with Prompt Syntax section and examples - Add docs/comfyui-prompt-style.md with design doc
This commit is contained in:
43
README.md
43
README.md
@@ -70,6 +70,7 @@ vnasset generate \
|
|||||||
| `--cfg` | `4.5` | CFG scale |
|
| `--cfg` | `4.5` | CFG scale |
|
||||||
| `--seed` | `0` | RNG seed (use `random` for random) |
|
| `--seed` | `0` | RNG seed (use `random` for random) |
|
||||||
| `--output` | `output.png` | Output path |
|
| `--output` | `output.png` | Output path |
|
||||||
|
| `--raw` | `false` | Disable Compel prompt weighting (fall back to plain diffusers encoding) |
|
||||||
|
|
||||||
When `--seed` is `random`, a random seed is generated and recorded in the
|
When `--seed` is `random`, a random seed is generated and recorded in the
|
||||||
metadata file.
|
metadata file.
|
||||||
@@ -154,18 +155,39 @@ implementation that avoids the SDPA dispatch entirely.
|
|||||||
Tested with `torch 2.11.0+rocm7.2`. Newer ROCm nightlies (2.13+, 2.14+) may
|
Tested with `torch 2.11.0+rocm7.2`. Newer ROCm nightlies (2.13+, 2.14+) may
|
||||||
cause GPU crashes. If you encounter segfaults, try matching this version.
|
cause GPU crashes. If you encounter segfaults, try matching this version.
|
||||||
|
|
||||||
## Prompt Compatibility
|
## Prompt Syntax
|
||||||
|
|
||||||
VNAsset uses standard diffusers SDXL encoding, which is equivalent to ComfyUI's
|
VNAsset supports **ComfyUI-style prompt weighting** via the `compel` library.
|
||||||
`BNK_CLIPTextEncodeAdvanced` with `token_normalization=none` and
|
|
||||||
`weight_interpretation=comfy` for plain comma-separated prompts.
|
|
||||||
|
|
||||||
ComfyUI-specific syntax is **not currently supported**:
|
### Weighting
|
||||||
- `(word:1.2)` — prompt weighting
|
|
||||||
- `BREAK` — conditioning chunking
|
|
||||||
|
|
||||||
If your prompts rely on these, you'll get different output than the ComfyUI
|
| Syntax | Effect |
|
||||||
workflow. compel integration is planned for later.
|
|--------|--------|
|
||||||
|
| `(word)` | Boost ×1.1 |
|
||||||
|
| `(word:1.5)` | Boost ×1.5 |
|
||||||
|
| `(word:0.6)` | De-emphasize ×0.6 |
|
||||||
|
| `[word]` | De-emphasize ×0.9 (shorthand) |
|
||||||
|
| `\(word\)` | Literal parentheses (escaped) |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vnasset generate \
|
||||||
|
--checkpoint models/novaAnimeXL_ilV190.safetensors \
|
||||||
|
--prompt "(masterpiece:1.2), 1girl, (red hair:1.3), blue eyes, [glasses]" \
|
||||||
|
--negative-prompt "(bad quality, worst quality:1.4)" \
|
||||||
|
--steps 20 --seed 42
|
||||||
|
```
|
||||||
|
|
||||||
|
### BREAK (condition chunking)
|
||||||
|
|
||||||
|
Split the prompt into independent conditioning chunks with `BREAK`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
vnasset generate \
|
||||||
|
--prompt "1girl, red hair, standing BREAK blue sky, cherry blossoms" \
|
||||||
|
--steps 20 --seed 42
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `--raw` to bypass weighting and fall back to plain diffusers encoding.
|
||||||
|
|
||||||
## Future Improvements
|
## Future Improvements
|
||||||
|
|
||||||
@@ -180,5 +202,4 @@ workflow. compel integration is planned for later.
|
|||||||
availability or a ComfyUI installation.
|
availability or a ComfyUI installation.
|
||||||
- **Qwen Image Edit support** — `vnasset edit` and `vnasset pipeline` for
|
- **Qwen Image Edit support** — `vnasset edit` and `vnasset pipeline` for
|
||||||
batch expression/outfit variant editing.
|
batch expression/outfit variant editing.
|
||||||
- **compel prompt weighting** — support `(word:weight)` and `BREAK` syntax for
|
|
||||||
parity with ComfyUI prompt encoding.
|
|
||||||
|
|||||||
227
docs/comfyui-prompt-style.md
Normal file
227
docs/comfyui-prompt-style.md
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
# ComfyUI Prompt Style Support
|
||||||
|
|
||||||
|
## What is ComfyUI Prompt Style?
|
||||||
|
|
||||||
|
ComfyUI's prompt encoding extends standard CLIP text encoding with two
|
||||||
|
capabilities that plain diffusers does not provide:
|
||||||
|
|
||||||
|
1. **Per-token prompt weighting** (`(word:weight)`)
|
||||||
|
2. **Condition chunking** (`BREAK`)
|
||||||
|
|
||||||
|
VNAsset currently uses standard diffusers SDXL encoding (equivalent to ComfyUI's
|
||||||
|
`BNK_CLIPTextEncodeAdvanced` with `token_normalization=none` and
|
||||||
|
`weight_interpretation=comfy`), but only for plain comma-separated prompts.
|
||||||
|
ComfyUI-specific syntax `(word:1.2)` and `BREAK` are **not yet supported**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How Prompt Weighting Works
|
||||||
|
|
||||||
|
### Syntax
|
||||||
|
|
||||||
|
Users write weight annotations directly in the prompt string:
|
||||||
|
|
||||||
|
```
|
||||||
|
1girl, (red hair:1.3), [glasses], (smile)
|
||||||
|
```
|
||||||
|
|
||||||
|
| Syntax | Effect |
|
||||||
|
|--------|--------|
|
||||||
|
| `(word)` | Boost ×1.1 (default) |
|
||||||
|
| `((word))` | Nested boost ×1.21 (1.1²) |
|
||||||
|
| `(word:1.5)` | Explicit weight 1.5 (boosted) |
|
||||||
|
| `(word:0.6)` | Explicit weight 0.6 (de-emphasized) |
|
||||||
|
| `[word]` | Shorthand de-emphasis ×0.9 (same as `(word:0.9)`) |
|
||||||
|
| `\(word\)` | Literal parentheses (escaped, not weighted) |
|
||||||
|
|
||||||
|
Nested and multi-word weighting also works:
|
||||||
|
|
||||||
|
```
|
||||||
|
((masterpiece, best quality:1.3))
|
||||||
|
(red hair, blue eyes:1.2)
|
||||||
|
```
|
||||||
|
|
||||||
|
### What happens under the hood
|
||||||
|
|
||||||
|
This is **not** prompt rewriting. The weighting is applied at the **embedding
|
||||||
|
tensor level** after CLIP encoding:
|
||||||
|
|
||||||
|
```
|
||||||
|
Prompt string → Tokenize → CLIP encode → Scale embeddings by weights → UNet
|
||||||
|
```
|
||||||
|
|
||||||
|
For each token tagged with a weight, its embedding vector is multiplied by that
|
||||||
|
weight. The rest of the pipeline (UNet, VAE) sees the same tensor shapes and
|
||||||
|
operates normally.
|
||||||
|
|
||||||
|
For example, `(red hair:1.5)` means:
|
||||||
|
|
||||||
|
1. Tokenize `red` and `hair` as usual
|
||||||
|
2. Get their embedding vectors from CLIP (each is a vector of floats)
|
||||||
|
3. Multiply each vector by 1.5
|
||||||
|
4. Pass the scaled embeddings to the UNet
|
||||||
|
|
||||||
|
The UNet then pays 1.5× more "attention" to those tokens.
|
||||||
|
|
||||||
|
### How SDXL makes this trickier
|
||||||
|
|
||||||
|
SDXL has **two** text encoders:
|
||||||
|
|
||||||
|
| Encoder | Tokenizer | Pooled output? |
|
||||||
|
|---------|-----------|----------------|
|
||||||
|
| CLIP-L (ViT-L) | `tokenizer` | No |
|
||||||
|
| OpenCLIP-G (ViT-bigG) | `tokenizer_2` | **Yes** — used for global conditioning |
|
||||||
|
|
||||||
|
Prompt weighting must be applied to the outputs of **both** encoders. The
|
||||||
|
pooled embedding from OpenCLIP-G also needs to be weighted consistently.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How BREAK Works
|
||||||
|
|
||||||
|
`BREAK` splits a single prompt into multiple independent conditioning vectors,
|
||||||
|
which are then **concatenated** along the sequence dimension:
|
||||||
|
|
||||||
|
```
|
||||||
|
1girl, red hair, standing BREAK blue sky, cherry blossoms, daytime
|
||||||
|
```
|
||||||
|
|
||||||
|
Instead of one CLIP encoding that mixes the character and background concepts
|
||||||
|
into a single tensor, this creates **two separate conditioning tensors**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Chunk 1: "1girl, red hair, standing" → conditioning tensor A (shape [1, N₁, 2048])
|
||||||
|
Chunk 2: "blue sky, cherry blossoms, daytime" → conditioning tensor B (shape [1, N₂, 2048])
|
||||||
|
↓
|
||||||
|
Concatenate: [1, N₁+N₂, 2048]
|
||||||
|
```
|
||||||
|
|
||||||
|
Each chunk gets its own CLIP forward pass, so the character description doesn't
|
||||||
|
bleed into the background encoding and vice versa. The UNet receives a longer
|
||||||
|
conditioning sequence with the two concepts cleanly separated.
|
||||||
|
|
||||||
|
### BREAK vs `.and()`
|
||||||
|
|
||||||
|
The `compel` library uses `.and()` as its native concatenation operator:
|
||||||
|
|
||||||
|
```
|
||||||
|
"a cat .and() a dog" ← compel native
|
||||||
|
"a cat BREAK a dog" ← ComfyUI syntax
|
||||||
|
```
|
||||||
|
|
||||||
|
Both produce the same result (two concatenated conditionings). We'll support
|
||||||
|
both forms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Plan
|
||||||
|
|
||||||
|
### Library: `compel`
|
||||||
|
|
||||||
|
We'll use the [`compel`](https://github.com/damian0815/compel) library from the
|
||||||
|
InvokeAI/diffusers ecosystem. It is:
|
||||||
|
|
||||||
|
- The standard implementation for A1111/ComfyUI prompt weighting
|
||||||
|
- Well-tested across millions of generations
|
||||||
|
- Maintained alongside diffusers
|
||||||
|
- Already mentioned in the README as the planned approach
|
||||||
|
|
||||||
|
### Step 1: Add dependency
|
||||||
|
|
||||||
|
**`pyproject.toml`** — add `compel` to `dependencies`.
|
||||||
|
|
||||||
|
### Step 2: New module `vnassets/prompt.py`
|
||||||
|
|
||||||
|
A thin Compel wrapper for SDXL. Responsibilities:
|
||||||
|
|
||||||
|
- Accept a loaded `StableDiffusionXLPipeline`
|
||||||
|
- Extract `tokenizer`, `tokenizer_2`, `text_encoder`, `text_encoder_2`
|
||||||
|
- Build a `Compel` instance configured for SDXL's dual-encoder setup with
|
||||||
|
`ReturnedEmbeddingsType.PENULTIMATE_OR_LAST_HIDDEN_STATES_NON_NORMALIZED`
|
||||||
|
(matches ComfyUI's `token_normalization=none`)
|
||||||
|
- Parse the positive prompt into `(prompt_embeds, pooled_prompt_embeds)`
|
||||||
|
- Parse the negative prompt into `(negative_prompt_embeds, negative_pooled_prompt_embeds)`
|
||||||
|
- Handle `BREAK` by translating to `.and()` or splitting + concatenating manually
|
||||||
|
|
||||||
|
API:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from .prompt import build_compel, encode_prompts
|
||||||
|
|
||||||
|
compel = build_compel(pipe)
|
||||||
|
pos_embeds, pos_pooled, neg_embeds, neg_pooled = encode_prompts(
|
||||||
|
compel, prompt, negative_prompt
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Modify `vnassets/generate.py`
|
||||||
|
|
||||||
|
Replace the raw-string path with pre-computed weighted embeddings:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Before
|
||||||
|
image = pipe(
|
||||||
|
prompt=prompt,
|
||||||
|
negative_prompt=negative_prompt,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
|
||||||
|
# After
|
||||||
|
compel = build_compel(pipe)
|
||||||
|
pos_embeds, pos_pooled, neg_embeds, neg_pooled = encode_prompts(
|
||||||
|
compel, prompt, negative_prompt
|
||||||
|
)
|
||||||
|
image = pipe(
|
||||||
|
prompt_embeds=pos_embeds,
|
||||||
|
pooled_prompt_embeds=pos_pooled,
|
||||||
|
negative_prompt_embeds=neg_embeds,
|
||||||
|
negative_pooled_prompt_embeds=neg_pooled,
|
||||||
|
...
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Compel will be initialized **after** the pipeline is loaded to device (since
|
||||||
|
text encoders must be on the correct device).
|
||||||
|
|
||||||
|
### Step 4: Add `--raw` flag (optional opt-out)
|
||||||
|
|
||||||
|
Add a `--raw` flag to `vnasset generate` that bypasses Compel and uses the
|
||||||
|
plain string path. Useful when:
|
||||||
|
|
||||||
|
- The prompt contains literal parentheses that shouldn't be parsed
|
||||||
|
- Debugging — comparing weighted vs unweighted output
|
||||||
|
|
||||||
|
### Step 5: Update `README.md`
|
||||||
|
|
||||||
|
- Remove the "not currently supported" note under Prompt Compatibility
|
||||||
|
- Add a **Prompt Syntax** section documenting `(word:weight)` and `BREAK`
|
||||||
|
- Add examples showing weighted prompts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What's NOT affected
|
||||||
|
|
||||||
|
The `vnasset edit` command is **unchanged**. Qwen Image Edit uses natural
|
||||||
|
language instructions (`"make her smile"`) rather than keyword-prompt
|
||||||
|
weighting. Compel does not support Qwen's text encoder (Qwen2.5-VL), and
|
||||||
|
weighting makes no sense for editing instructions anyway.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `pyproject.toml` | Add `compel` dependency |
|
||||||
|
| `vnassets/prompt.py` | **New** — Compel wrapper for SDXL |
|
||||||
|
| `vnassets/generate.py` | Use Compel embeddings instead of raw strings |
|
||||||
|
| `vnassets/cli.py` | Add `--raw` flag to `generate` command |
|
||||||
|
| `README.md` | Document new syntax support |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- [Compel on GitHub](https://github.com/damian0815/compel)
|
||||||
|
- [ComfyUI BNK_CLIPTextEncodeAdvanced](https://github.com/BlakeOne/ComfyUI-BNK-CLIPTextEncode-Advanced)
|
||||||
|
- [SDXL dual text encoder architecture](https://huggingface.co/docs/diffusers/using-diffusers/sdxl#the-dual-text-encoder-architecture)
|
||||||
@@ -15,6 +15,7 @@ dependencies = [
|
|||||||
"pillow",
|
"pillow",
|
||||||
"pyyaml",
|
"pyyaml",
|
||||||
"click",
|
"click",
|
||||||
|
"compel",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.setuptools.packages.find]
|
||||||
|
|||||||
@@ -34,7 +34,11 @@ def main():
|
|||||||
help="RNG seed (integer or 'random')",
|
help="RNG seed (integer or 'random')",
|
||||||
)
|
)
|
||||||
@click.option("--output", default="output.png", help="Output image path")
|
@click.option("--output", default="output.png", help="Output image path")
|
||||||
def generate_cmd(checkpoint, prompt, negative_prompt, width, height, steps, cfg, seed, output):
|
@click.option(
|
||||||
|
"--raw", is_flag=True,
|
||||||
|
help="Disable ComfyUI-style prompt weighting (use plain diffusers encoding)",
|
||||||
|
)
|
||||||
|
def generate_cmd(checkpoint, prompt, negative_prompt, width, height, steps, cfg, seed, output, raw):
|
||||||
"""Generate an image from an SDXL checkpoint."""
|
"""Generate an image from an SDXL checkpoint."""
|
||||||
generate(
|
generate(
|
||||||
checkpoint_path=checkpoint,
|
checkpoint_path=checkpoint,
|
||||||
@@ -46,6 +50,7 @@ def generate_cmd(checkpoint, prompt, negative_prompt, width, height, steps, cfg,
|
|||||||
cfg=cfg,
|
cfg=cfg,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
output_path=output,
|
output_path=output,
|
||||||
|
raw=raw,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -60,7 +65,9 @@ def generate_cmd(checkpoint, prompt, negative_prompt, width, height, steps, cfg,
|
|||||||
help="RNG seed (integer or 'random')",
|
help="RNG seed (integer or 'random')",
|
||||||
)
|
)
|
||||||
@click.option("--output", default="output.png", help="Output image path")
|
@click.option("--output", default="output.png", help="Output image path")
|
||||||
def edit_cmd(model, input_path, prompt, steps, cfg, seed, output):
|
@click.option("--lora", "lora_path", default=None,
|
||||||
|
help="Path to LoRA .safetensors (e.g. Lightning 4-step LoRA)")
|
||||||
|
def edit_cmd(model, input_path, prompt, steps, cfg, seed, output, lora_path):
|
||||||
"""Edit an image using Qwen Image Edit."""
|
"""Edit an image using Qwen Image Edit."""
|
||||||
edit(
|
edit(
|
||||||
model_path=model,
|
model_path=model,
|
||||||
@@ -70,4 +77,5 @@ def edit_cmd(model, input_path, prompt, steps, cfg, seed, output):
|
|||||||
cfg=cfg,
|
cfg=cfg,
|
||||||
seed=seed,
|
seed=seed,
|
||||||
output_path=output,
|
output_path=output,
|
||||||
|
lora_path=lora_path,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import torch
|
|||||||
from diffusers import StableDiffusionXLPipeline
|
from diffusers import StableDiffusionXLPipeline
|
||||||
|
|
||||||
from .attention import patch_unet_attention
|
from .attention import patch_unet_attention
|
||||||
|
from .prompt import build_compel, encode_prompts
|
||||||
|
|
||||||
|
|
||||||
def generate(
|
def generate(
|
||||||
@@ -20,6 +21,7 @@ def generate(
|
|||||||
cfg: float = 4.5,
|
cfg: float = 4.5,
|
||||||
seed: int | None = None,
|
seed: int | None = None,
|
||||||
output_path: str = "output.png",
|
output_path: str = "output.png",
|
||||||
|
raw: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
device = "cuda" if torch.cuda.is_available() else "cpu"
|
device = "cuda" if torch.cuda.is_available() else "cpu"
|
||||||
# bfloat16 avoids ROCm kernel crashes on RDNA 3.5; float16 segfaults
|
# bfloat16 avoids ROCm kernel crashes on RDNA 3.5; float16 segfaults
|
||||||
@@ -38,20 +40,39 @@ def generate(
|
|||||||
)
|
)
|
||||||
pipe.to(device)
|
pipe.to(device)
|
||||||
patch_unet_attention(pipe.unet)
|
patch_unet_attention(pipe.unet)
|
||||||
|
|
||||||
|
if not raw:
|
||||||
|
compel = build_compel(pipe)
|
||||||
|
prompt_embeds, pooled_embeds, neg_embeds, neg_pooled = encode_prompts(
|
||||||
|
compel, prompt, negative_prompt
|
||||||
|
)
|
||||||
t_load = time.perf_counter() - t0
|
t_load = time.perf_counter() - t0
|
||||||
|
|
||||||
generator = torch.Generator(device=device).manual_seed(seed)
|
generator = torch.Generator(device=device).manual_seed(seed)
|
||||||
|
|
||||||
t1 = time.perf_counter()
|
t1 = time.perf_counter()
|
||||||
image = pipe(
|
if raw:
|
||||||
prompt=prompt,
|
image = pipe(
|
||||||
negative_prompt=negative_prompt,
|
prompt=prompt,
|
||||||
width=width,
|
negative_prompt=negative_prompt,
|
||||||
height=height,
|
width=width,
|
||||||
num_inference_steps=steps,
|
height=height,
|
||||||
guidance_scale=cfg,
|
num_inference_steps=steps,
|
||||||
generator=generator,
|
guidance_scale=cfg,
|
||||||
).images[0]
|
generator=generator,
|
||||||
|
).images[0]
|
||||||
|
else:
|
||||||
|
image = pipe(
|
||||||
|
prompt_embeds=prompt_embeds,
|
||||||
|
pooled_prompt_embeds=pooled_embeds,
|
||||||
|
negative_prompt_embeds=neg_embeds,
|
||||||
|
negative_pooled_prompt_embeds=neg_pooled,
|
||||||
|
width=width,
|
||||||
|
height=height,
|
||||||
|
num_inference_steps=steps,
|
||||||
|
guidance_scale=cfg,
|
||||||
|
generator=generator,
|
||||||
|
).images[0]
|
||||||
t_infer = time.perf_counter() - t1
|
t_infer = time.perf_counter() - t1
|
||||||
|
|
||||||
image.save(output)
|
image.save(output)
|
||||||
|
|||||||
55
vnassets/prompt.py
Normal file
55
vnassets/prompt.py
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
"""ComfyUI-style prompt weighting via compel.
|
||||||
|
|
||||||
|
Supports (word:weight), [word], ((nested)), and BREAK syntax for SDXL prompts.
|
||||||
|
"""
|
||||||
|
import torch
|
||||||
|
from compel import CompelForSDXL
|
||||||
|
|
||||||
|
|
||||||
|
def _translate_break(prompt: str) -> str:
|
||||||
|
"""Translate ComfyUI BREAK syntax to compel .and() syntax.
|
||||||
|
|
||||||
|
Compel's native .and() is the equivalent of ComfyUI's BREAK —
|
||||||
|
both split the prompt into separate conditioning chunks.
|
||||||
|
We support both forms transparently.
|
||||||
|
"""
|
||||||
|
# Replace BREAK with .and(), then normalize to clean chunks
|
||||||
|
chunks = [c.strip() for c in prompt.replace("BREAK", ".and()").split(".and()")]
|
||||||
|
chunks = [c for c in chunks if c]
|
||||||
|
return " .and() ".join(chunks)
|
||||||
|
|
||||||
|
|
||||||
|
def build_compel(pipe):
|
||||||
|
"""Create a CompelForSDXL instance from a loaded SDXL pipeline.
|
||||||
|
|
||||||
|
Uses CompelForSDXL which wraps both CLIP encoders and configures
|
||||||
|
ComfyUI-equivalent embedding output (no token normalization).
|
||||||
|
"""
|
||||||
|
return CompelForSDXL(pipe)
|
||||||
|
|
||||||
|
|
||||||
|
def encode_prompts(
|
||||||
|
compel: CompelForSDXL,
|
||||||
|
prompt: str,
|
||||||
|
negative_prompt: str = "",
|
||||||
|
) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||||
|
"""Encode positive and negative prompts into SDXL embedding tensors.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(prompt_embeds, pooled_prompt_embeds,
|
||||||
|
negative_prompt_embeds, negative_pooled_prompt_embeds)
|
||||||
|
|
||||||
|
CompelForSDXL automatically handles BREAK/.and() concatenation and
|
||||||
|
pads negative embeddings to match positive when chunk counts differ.
|
||||||
|
"""
|
||||||
|
prompt = _translate_break(prompt)
|
||||||
|
negative_prompt = _translate_break(negative_prompt) if negative_prompt else ""
|
||||||
|
|
||||||
|
result = compel(prompt, negative_prompt=negative_prompt or None)
|
||||||
|
|
||||||
|
return (
|
||||||
|
result.embeds,
|
||||||
|
result.pooled_embeds,
|
||||||
|
result.negative_embeds,
|
||||||
|
result.negative_pooled_embeds,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user