Skip to content

Commit c9b5f65

Browse files
Merge branch 'master' into bump-fe-1.30
2 parents 0066a50 + 87b0359 commit c9b5f65

31 files changed

+1328
-336
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<!-- API_NODE_PR_CHECKLIST: do not remove -->
2+
3+
## API Node PR Checklist
4+
5+
### Scope
6+
- [ ] **Is API Node Change**
7+
8+
### Pricing & Billing
9+
- [ ] **Need pricing update**
10+
- [ ] **No pricing update**
11+
12+
If **Need pricing update**:
13+
- [ ] Metronome rate cards updated
14+
- [ ] Auto‑billing tests updated and passing
15+
16+
### QA
17+
- [ ] **QA done**
18+
- [ ] **QA not required**
19+
20+
### Comms
21+
- [ ] Informed **Kosinkadink**
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
name: Append API Node PR template
2+
3+
on:
4+
pull_request_target:
5+
types: [opened, reopened, synchronize, ready_for_review]
6+
paths:
7+
- 'comfy_api_nodes/**' # only run if these files changed
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
13+
jobs:
14+
inject:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Ensure template exists and append to PR body
18+
uses: actions/github-script@v7
19+
with:
20+
script: |
21+
const { owner, repo } = context.repo;
22+
const number = context.payload.pull_request.number;
23+
const templatePath = '.github/PULL_REQUEST_TEMPLATE/api-node.md';
24+
const marker = '<!-- API_NODE_PR_CHECKLIST: do not remove -->';
25+
26+
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: number });
27+
28+
let templateText;
29+
try {
30+
const res = await github.rest.repos.getContent({
31+
owner,
32+
repo,
33+
path: templatePath,
34+
ref: pr.base.ref
35+
});
36+
const buf = Buffer.from(res.data.content, res.data.encoding || 'base64');
37+
templateText = buf.toString('utf8');
38+
} catch (e) {
39+
core.setFailed(`Required PR template not found at "${templatePath}" on ${pr.base.ref}. Please add it to the repo.`);
40+
return;
41+
}
42+
43+
// Enforce the presence of the marker inside the template (for idempotence)
44+
if (!templateText.includes(marker)) {
45+
core.setFailed(`Template at "${templatePath}" does not contain the required marker:\n${marker}\nAdd it so we can detect duplicates safely.`);
46+
return;
47+
}
48+
49+
// If the PR already contains the marker, do not append again.
50+
const body = pr.body || '';
51+
if (body.includes(marker)) {
52+
core.info('Template already present in PR body; nothing to inject.');
53+
return;
54+
}
55+
56+
const newBody = (body ? body + '\n\n' : '') + templateText + '\n';
57+
await github.rest.pulls.update({ owner, repo, pull_number: number, body: newBody });
58+
core.notice('API Node template appended to PR description.');

.github/workflows/release-stable-all.yml

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
contents: "write"
1515
packages: "write"
1616
pull-requests: "read"
17-
name: "Release NVIDIA Default (cu129)"
17+
name: "Release NVIDIA Default (cu130)"
1818
uses: ./.github/workflows/stable-release.yml
1919
with:
2020
git_tag: ${{ inputs.git_tag }}
@@ -43,6 +43,23 @@ jobs:
4343
test_release: true
4444
secrets: inherit
4545

46+
release_nvidia_cu126:
47+
permissions:
48+
contents: "write"
49+
packages: "write"
50+
pull-requests: "read"
51+
name: "Release NVIDIA cu126"
52+
uses: ./.github/workflows/stable-release.yml
53+
with:
54+
git_tag: ${{ inputs.git_tag }}
55+
cache_tag: "cu126"
56+
python_minor: "12"
57+
python_patch: "10"
58+
rel_name: "nvidia"
59+
rel_extra_name: "_cu126"
60+
test_release: true
61+
secrets: inherit
62+
4663
release_amd_rocm:
4764
permissions:
4865
contents: "write"

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ There is a portable standalone build for Windows that should work for running on
173173

174174
### [Direct link to download](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_nvidia.7z)
175175

176-
Simply download, extract with [7-Zip](https://7-zip.org) and run. Make sure you put your Stable Diffusion checkpoints/models (the huge ckpt/safetensors files) in: ComfyUI\models\checkpoints
176+
Simply download, extract with [7-Zip](https://7-zip.org) or with the windows explorer on recent windows versions and run. For smaller models you normally only need to put the checkpoints (the huge ckpt/safetensors files) in: ComfyUI\models\checkpoints but many of the larger models have multiple files. Make sure to follow the instructions to know which subfolder to put them in ComfyUI\models\
177177

178178
If you have trouble extracting it, right click the file -> properties -> unblock
179179

@@ -183,7 +183,9 @@ Update your Nvidia drivers if it doesn't start.
183183

184184
[Experimental portable for AMD GPUs](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_amd.7z)
185185

186-
[Portable with pytorch cuda 12.8 and python 3.12](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_nvidia_cu128.7z) (Supports Nvidia 10 series and older GPUs).
186+
[Portable with pytorch cuda 12.8 and python 3.12](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_nvidia_cu128.7z).
187+
188+
[Portable with pytorch cuda 12.6 and python 3.12](https://github.com/comfyanonymous/ComfyUI/releases/latest/download/ComfyUI_windows_portable_nvidia_cu126.7z) (Supports Nvidia 10 series and older GPUs).
187189

188190
#### How do I share models between another UI and ComfyUI?
189191

@@ -221,7 +223,7 @@ AMD users can install rocm and pytorch with pip if you don't have it already ins
221223

222224
This is the command to install the nightly with ROCm 7.0 which might have some performance improvements:
223225

224-
```pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm7.0```
226+
```pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm7.1```
225227

226228

227229
### AMD GPUs (Experimental: Windows and Linux), RDNA 3, 3.5 and 4 only.

app/frontend_management.py

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
from dataclasses import dataclass
1111
from functools import cached_property
1212
from pathlib import Path
13-
from typing import TypedDict, Optional
13+
from typing import Dict, TypedDict, Optional
14+
from aiohttp import web
1415
from importlib.metadata import version
1516

1617
import requests
@@ -257,7 +258,54 @@ def default_frontend_path(cls) -> str:
257258
sys.exit(-1)
258259

259260
@classmethod
260-
def templates_path(cls) -> str:
261+
def template_asset_map(cls) -> Optional[Dict[str, str]]:
262+
"""Return a mapping of template asset names to their absolute paths."""
263+
try:
264+
from comfyui_workflow_templates import (
265+
get_asset_path,
266+
iter_templates,
267+
)
268+
except ImportError:
269+
logging.error(
270+
f"""
271+
********** ERROR ***********
272+
273+
comfyui-workflow-templates is not installed.
274+
275+
{frontend_install_warning_message()}
276+
277+
********** ERROR ***********
278+
""".strip()
279+
)
280+
return None
281+
282+
try:
283+
template_entries = list(iter_templates())
284+
except Exception as exc:
285+
logging.error(f"Failed to enumerate workflow templates: {exc}")
286+
return None
287+
288+
asset_map: Dict[str, str] = {}
289+
try:
290+
for entry in template_entries:
291+
for asset in entry.assets:
292+
asset_map[asset.filename] = get_asset_path(
293+
entry.template_id, asset.filename
294+
)
295+
except Exception as exc:
296+
logging.error(f"Failed to resolve template asset paths: {exc}")
297+
return None
298+
299+
if not asset_map:
300+
logging.error("No workflow template assets found. Did the packages install correctly?")
301+
return None
302+
303+
return asset_map
304+
305+
306+
@classmethod
307+
def legacy_templates_path(cls) -> Optional[str]:
308+
"""Return the legacy templates directory shipped inside the meta package."""
261309
try:
262310
import comfyui_workflow_templates
263311

@@ -276,6 +324,7 @@ def templates_path(cls) -> str:
276324
********** ERROR ***********
277325
""".strip()
278326
)
327+
return None
279328

280329
@classmethod
281330
def embedded_docs_path(cls) -> str:
@@ -392,3 +441,17 @@ def init_frontend(cls, version_string: str) -> str:
392441
logging.info("Falling back to the default frontend.")
393442
check_frontend_version()
394443
return cls.default_frontend_path()
444+
@classmethod
445+
def template_asset_handler(cls):
446+
assets = cls.template_asset_map()
447+
if not assets:
448+
return None
449+
450+
async def serve_template(request: web.Request) -> web.StreamResponse:
451+
rel_path = request.match_info.get("path", "")
452+
target = assets.get(rel_path)
453+
if target is None:
454+
raise web.HTTPNotFound()
455+
return web.FileResponse(target)
456+
457+
return serve_template

comfy/ldm/chroma/layers.py

Lines changed: 3 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import torch
22
from torch import Tensor, nn
33

4-
from comfy.ldm.flux.math import attention
54
from comfy.ldm.flux.layers import (
65
MLPEmbedder,
76
RMSNorm,
8-
QKNorm,
9-
SelfAttention,
107
ModulationOut,
118
)
129

10+
# TODO: remove this in a few months
11+
SingleStreamBlock = None
12+
DoubleStreamBlock = None
1313

1414

1515
class ChromaModulationOut(ModulationOut):
@@ -48,124 +48,6 @@ def forward(self, x: Tensor) -> Tensor:
4848
return x
4949

5050

51-
class DoubleStreamBlock(nn.Module):
52-
def __init__(self, hidden_size: int, num_heads: int, mlp_ratio: float, qkv_bias: bool = False, flipped_img_txt=False, dtype=None, device=None, operations=None):
53-
super().__init__()
54-
55-
mlp_hidden_dim = int(hidden_size * mlp_ratio)
56-
self.num_heads = num_heads
57-
self.hidden_size = hidden_size
58-
self.img_norm1 = operations.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device)
59-
self.img_attn = SelfAttention(dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias, dtype=dtype, device=device, operations=operations)
60-
61-
self.img_norm2 = operations.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device)
62-
self.img_mlp = nn.Sequential(
63-
operations.Linear(hidden_size, mlp_hidden_dim, bias=True, dtype=dtype, device=device),
64-
nn.GELU(approximate="tanh"),
65-
operations.Linear(mlp_hidden_dim, hidden_size, bias=True, dtype=dtype, device=device),
66-
)
67-
68-
self.txt_norm1 = operations.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device)
69-
self.txt_attn = SelfAttention(dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias, dtype=dtype, device=device, operations=operations)
70-
71-
self.txt_norm2 = operations.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device)
72-
self.txt_mlp = nn.Sequential(
73-
operations.Linear(hidden_size, mlp_hidden_dim, bias=True, dtype=dtype, device=device),
74-
nn.GELU(approximate="tanh"),
75-
operations.Linear(mlp_hidden_dim, hidden_size, bias=True, dtype=dtype, device=device),
76-
)
77-
self.flipped_img_txt = flipped_img_txt
78-
79-
def forward(self, img: Tensor, txt: Tensor, pe: Tensor, vec: Tensor, attn_mask=None, transformer_options={}):
80-
(img_mod1, img_mod2), (txt_mod1, txt_mod2) = vec
81-
82-
# prepare image for attention
83-
img_modulated = torch.addcmul(img_mod1.shift, 1 + img_mod1.scale, self.img_norm1(img))
84-
img_qkv = self.img_attn.qkv(img_modulated)
85-
img_q, img_k, img_v = img_qkv.view(img_qkv.shape[0], img_qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3, 1, 4)
86-
img_q, img_k = self.img_attn.norm(img_q, img_k, img_v)
87-
88-
# prepare txt for attention
89-
txt_modulated = torch.addcmul(txt_mod1.shift, 1 + txt_mod1.scale, self.txt_norm1(txt))
90-
txt_qkv = self.txt_attn.qkv(txt_modulated)
91-
txt_q, txt_k, txt_v = txt_qkv.view(txt_qkv.shape[0], txt_qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3, 1, 4)
92-
txt_q, txt_k = self.txt_attn.norm(txt_q, txt_k, txt_v)
93-
94-
# run actual attention
95-
attn = attention(torch.cat((txt_q, img_q), dim=2),
96-
torch.cat((txt_k, img_k), dim=2),
97-
torch.cat((txt_v, img_v), dim=2),
98-
pe=pe, mask=attn_mask, transformer_options=transformer_options)
99-
100-
txt_attn, img_attn = attn[:, : txt.shape[1]], attn[:, txt.shape[1] :]
101-
102-
# calculate the img bloks
103-
img.addcmul_(img_mod1.gate, self.img_attn.proj(img_attn))
104-
img.addcmul_(img_mod2.gate, self.img_mlp(torch.addcmul(img_mod2.shift, 1 + img_mod2.scale, self.img_norm2(img))))
105-
106-
# calculate the txt bloks
107-
txt.addcmul_(txt_mod1.gate, self.txt_attn.proj(txt_attn))
108-
txt.addcmul_(txt_mod2.gate, self.txt_mlp(torch.addcmul(txt_mod2.shift, 1 + txt_mod2.scale, self.txt_norm2(txt))))
109-
110-
if txt.dtype == torch.float16:
111-
txt = torch.nan_to_num(txt, nan=0.0, posinf=65504, neginf=-65504)
112-
113-
return img, txt
114-
115-
116-
class SingleStreamBlock(nn.Module):
117-
"""
118-
A DiT block with parallel linear layers as described in
119-
https://arxiv.org/abs/2302.05442 and adapted modulation interface.
120-
"""
121-
122-
def __init__(
123-
self,
124-
hidden_size: int,
125-
num_heads: int,
126-
mlp_ratio: float = 4.0,
127-
qk_scale: float = None,
128-
dtype=None,
129-
device=None,
130-
operations=None
131-
):
132-
super().__init__()
133-
self.hidden_dim = hidden_size
134-
self.num_heads = num_heads
135-
head_dim = hidden_size // num_heads
136-
self.scale = qk_scale or head_dim**-0.5
137-
138-
self.mlp_hidden_dim = int(hidden_size * mlp_ratio)
139-
# qkv and mlp_in
140-
self.linear1 = operations.Linear(hidden_size, hidden_size * 3 + self.mlp_hidden_dim, dtype=dtype, device=device)
141-
# proj and mlp_out
142-
self.linear2 = operations.Linear(hidden_size + self.mlp_hidden_dim, hidden_size, dtype=dtype, device=device)
143-
144-
self.norm = QKNorm(head_dim, dtype=dtype, device=device, operations=operations)
145-
146-
self.hidden_size = hidden_size
147-
self.pre_norm = operations.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device)
148-
149-
self.mlp_act = nn.GELU(approximate="tanh")
150-
151-
def forward(self, x: Tensor, pe: Tensor, vec: Tensor, attn_mask=None, transformer_options={}) -> Tensor:
152-
mod = vec
153-
x_mod = torch.addcmul(mod.shift, 1 + mod.scale, self.pre_norm(x))
154-
qkv, mlp = torch.split(self.linear1(x_mod), [3 * self.hidden_size, self.mlp_hidden_dim], dim=-1)
155-
156-
q, k, v = qkv.view(qkv.shape[0], qkv.shape[1], 3, self.num_heads, -1).permute(2, 0, 3, 1, 4)
157-
q, k = self.norm(q, k, v)
158-
159-
# compute attention
160-
attn = attention(q, k, v, pe=pe, mask=attn_mask, transformer_options=transformer_options)
161-
# compute activation in mlp stream, cat again and run second linear layer
162-
output = self.linear2(torch.cat((attn, self.mlp_act(mlp)), 2))
163-
x.addcmul_(mod.gate, output)
164-
if x.dtype == torch.float16:
165-
x = torch.nan_to_num(x, nan=0.0, posinf=65504, neginf=-65504)
166-
return x
167-
168-
16951
class LastLayer(nn.Module):
17052
def __init__(self, hidden_size: int, patch_size: int, out_channels: int, dtype=None, device=None, operations=None):
17153
super().__init__()

0 commit comments

Comments
 (0)