Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-comfy-kitchen for
openSUSE:Factory checked in at 2026-10-20 12:39:29
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-comfy-kitchen (Old)
and /work/SRC/openSUSE:Factory/.python-comfy-kitchen.new.3698736 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-comfy-kitchen"
Tue Oct 20 12:39:29 2026 rev:9 rq:1386212 version:0.2.39
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-comfy-kitchen/python-comfy-kitchen.changes
2026-10-17 21:27:19.529694584 +0200
+++
/work/SRC/openSUSE:Factory/.python-comfy-kitchen.new.3698736/python-comfy-kitchen.changes
2026-10-20 12:41:14.733830092 +0200
@@ -1,0 +2,10 @@
+Mon Oct 19 09:38:44 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 0.2.39:
+ * Fuse input activations into the W4A8/W6A8 quantizer and match
+ the ConvRot quantizer's shared-memory estimate to its launch,
+ with a fused prefill activation test
+ * Remainder of the release is CUDA kernel work, not compiled
+ in this CPU-only build
+
+-------------------------------------------------------------------
Old:
----
comfy-kitchen-0.2.38.tar.gz
New:
----
comfy-kitchen-0.2.39.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-comfy-kitchen.spec ++++++
--- /var/tmp/diff_new_pack.zKtxx1/_old 2026-10-20 12:41:15.899878894 +0200
+++ /var/tmp/diff_new_pack.zKtxx1/_new 2026-10-20 12:41:15.902879019 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-comfy-kitchen
-Version: 0.2.38
+Version: 0.2.39
Release: 0
Summary: Fast kernel library for ComfyUI (CPU/eager backend)
# Legal-Review-Notice: NOTICE names the NVIDIA CUDA runtime EULA for a
++++++ comfy-kitchen-0.2.38.tar.gz -> comfy-kitchen-0.2.39.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/comfy-kitchen-0.2.38/comfy_kitchen/backends/cuda/__init__.py
new/comfy-kitchen-0.2.39/comfy_kitchen/backends/cuda/__init__.py
--- old/comfy-kitchen-0.2.38/comfy_kitchen/backends/cuda/__init__.py
2026-10-17 03:46:27.000000000 +0200
+++ new/comfy-kitchen-0.2.39/comfy_kitchen/backends/cuda/__init__.py
2026-10-18 22:00:33.000000000 +0200
@@ -449,14 +449,17 @@
def _convrot_int8_fused_shared_memory_bytes(m: int, k: int) -> int:
+ # mirrors launch_quantize_int8_rowwise_convrot64_kernel's block-size rule
if m == 1:
block_threads = 512
elif k == 256:
block_threads = 64
- elif k == 2560:
- block_threads = 640
- elif k == 6144:
- block_threads = 768
+ elif k <= 3072:
+ block_threads = 128
+ elif k <= 6144:
+ block_threads = 256
+ elif k < 12288:
+ block_threads = 512
else:
block_threads = 1024
groups_in_flight = block_threads // 64
@@ -2515,7 +2518,15 @@
residual,
residual_scale,
)
- x = _apply_input_act(x, input_act, input_act_weight, input_act_eps)
+ chunked = (
+ _W4A8_CHUNKED
+ and correction is None
+ and s_rel.dtype == torch.float8_e4m3fn
+ )
+ fuse_act = chunked and _w4a8_chunked_act_fusable(x, input_act,
convrot_groupsize)
+ if not fuse_act:
+ x = _apply_input_act(x, input_act, input_act_weight, input_act_eps)
+ input_act = None
n, k, _bits = validate_w4a8_operands(
qdata,
s_rel,
@@ -2525,10 +2536,10 @@
group_size,
convrot_groupsize,
)
- if x.shape[-1] != k:
+ if x.shape[-1] != k * _input_act_width(input_act):
raise ValueError(f"Input K={x.shape[-1]} does not match qdata K={k}")
groups = k // group_size
- x_2d = x.reshape(-1, k).contiguous()
+ x_2d = x.reshape(-1, x.shape[-1]).contiguous()
m = x_2d.shape[0]
output_dtype_code = DTYPE_TO_CODE[out_dtype]
stream_ptr = torch.cuda.current_stream(x.device).cuda_stream
@@ -2570,15 +2581,11 @@
if used:
return out.reshape(*x.shape[:-1], n)
- chunked = (
- _W4A8_CHUNKED
- and correction is None
- and s_rel.dtype == torch.float8_e4m3fn
- )
if chunked:
used, xq, xs = _w4a8_linear_chunked(
x_2d, qdata, s_rel, s_channel, codebook, bias_arg, xq, xs, out,
convrot_groupsize, group_size, False,
+ input_act, input_act_weight, input_act_eps,
)
if used:
return out.reshape(*x.shape[:-1], n)
@@ -2617,6 +2624,9 @@
group_size=group_size,
convrot_groupsize=convrot_groupsize,
out_dtype=out_dtype,
+ input_act=input_act,
+ input_act_weight=input_act_weight,
+ input_act_eps=input_act_eps,
)
if correction is not None:
@@ -2654,6 +2664,10 @@
input_act is not None
and x.numel() <= 8 * x.shape[-1]
and _w4a8_cluster_quantizer_ok(k, input_act, x.device)
+ ) or (
+ input_act is not None
+ and x.numel() > 8 * x.shape[-1]
+ and _w4a8_chunked_act_fusable(x, input_act, convrot_groupsize)
)
if input_act is not None and not fuse_act:
x = _apply_input_act(x, input_act, input_act_weight, input_act_eps)
@@ -2711,33 +2725,61 @@
used, _, _ = _w4a8_linear_chunked(
x_2d, qdata, s_rel, s_channel, codebook, bias_arg, xq, xs, out,
convrot_groupsize, group_size, True,
+ input_act, input_act_weight, input_act_eps,
)
if not used:
raise RuntimeError("Packed W4A8 prefill is unsupported by the INT8
GEMM")
return out.reshape(*x.shape[:-1], n)
+def _w4a8_fast_act_ok(x: torch.Tensor, m: int, k: int, convrot_groupsize: int)
-> bool:
+ """Whether the chunked prefill path quantizes with the 64-lane ConvRot
kernel (the one
+ that fuses input activations): large M and a K-wide activated row it can
hold in
+ shared memory. x only supplies the device; m and k are the activated row
count/width."""
+ return (
+ m >= 512
+ and convrot_groupsize == 256
+ and k % 256 == 0
+ and 256 <= k <= _CONVROT_FUSED_MAX_K
+ and (not x.is_cuda
+ or _convrot_int8_fused_shared_memory_bytes(m, k) <
_max_dynamic_shared_memory_per_block(x))
+ )
+
+
+def _w4a8_chunked_act_fusable(x: torch.Tensor, input_act: str | None,
convrot_groupsize: int) -> bool:
+ """Whether the chunked prefill path absorbs input_act; x is the raw input
(2K wide for the gated pairs)."""
+ if input_act in (None, "none"):
+ return False
+ return _w4a8_fast_act_ok(x, x.numel() // x.shape[-1], x.shape[-1] //
_input_act_width(input_act), convrot_groupsize)
+
+
def _w4a8_linear_chunked(
x_2d, qdata, s_rel, s_channel, codebook, bias_arg, xq, xs, out,
convrot_groupsize, group_size, mma_packed,
+ input_act=None, input_act_weight=None, input_act_eps=0.0,
):
- """Shared bounded INT8 GEMM; the native dequantizer reads the selected
storage format."""
+ """Shared bounded INT8 GEMM; the native dequantizer reads the selected
storage format.
+
+ With input_act set, x_2d is the raw row (two K-wide halves for the gated
pairs) and
+ the 64-lane quantizer applies the activation; callers check
_w4a8_chunked_act_fusable.
+ """
+ if input_act in (None, "none"):
+ input_act = None
m, k = x_2d.shape
+ k //= _input_act_width(input_act)
n = s_channel.numel()
output_dtype_code = DTYPE_TO_CODE[out.dtype]
stream_ptr = torch.cuda.current_stream(x_2d.device).cuda_stream
chunk_cols = _w4a8_weight_chunk_cols(m, n, x_2d.get_device())
workspace = torch.empty(min(chunk_cols, n), k, dtype=torch.int8,
device=x_2d.device)
# The 64-lane quantizer is faster at large M; at small M its extra launch
costs more.
- fast_act = (
- m >= 512
- and convrot_groupsize == 256
- and k % 256 == 0
- and 256 <= k <= _CONVROT_FUSED_MAX_K
- and _convrot_fused_shared_memory_fits(x_2d, k, convrot_groupsize)
- )
+ fast_act = _w4a8_fast_act_ok(x_2d, m, k, convrot_groupsize)
+ if input_act is not None and not fast_act:
+ raise RuntimeError("w4a8 chunked linear: fused input activation needs
the 64-lane quantizer")
if fast_act:
- xq, xs = quantize_int8_rowwise_convrot64(x_2d, convrot_groupsize)
+ xq, xs = quantize_int8_rowwise_convrot64(
+ x_2d, convrot_groupsize, input_act=input_act,
+ input_act_weight=input_act_weight, input_act_eps=input_act_eps)
elif hasattr(_C, "w4a8_codebook_linear_chunked"):
used = _C.w4a8_codebook_linear_chunked(
_wrap_for_dlpack(x_2d),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/comfy-kitchen-0.2.38/pyproject.toml
new/comfy-kitchen-0.2.39/pyproject.toml
--- old/comfy-kitchen-0.2.38/pyproject.toml 2026-10-17 03:46:27.000000000
+0200
+++ new/comfy-kitchen-0.2.39/pyproject.toml 2026-10-18 22:00:33.000000000
+0200
@@ -16,7 +16,7 @@
[project]
name = "comfy-kitchen"
-version = "0.2.38"
+version = "0.2.39"
description = "Fast Kernel Library for ComfyUI with multiple compute backends"
readme = "README.md"
requires-python = ">=3.10"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/comfy-kitchen-0.2.38/tests/test_convrot_w4a4.py
new/comfy-kitchen-0.2.39/tests/test_convrot_w4a4.py
--- old/comfy-kitchen-0.2.38/tests/test_convrot_w4a4.py 2026-10-17
03:46:27.000000000 +0200
+++ new/comfy-kitchen-0.2.39/tests/test_convrot_w4a4.py 2026-10-18
22:00:33.000000000 +0200
@@ -94,8 +94,8 @@
[
(1, 65536, (65536 + 8 * 2 * 256) * 4),
(2, 256, (256 + 1 * 2 * 256) * 4),
- (2, 2560, (2560 + 10 * 2 * 256) * 4),
- (2, 6144, (6144 + 12 * 2 * 256) * 4),
+ (2, 2560, (2560 + 2 * 2 * 256) * 4),
+ (2, 6144, (6144 + 4 * 2 * 256) * 4),
(2, 15360, (15360 + 16 * 2 * 256) * 4),
],
)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/comfy-kitchen-0.2.38/tests/test_w4a8_mma.py
new/comfy-kitchen-0.2.39/tests/test_w4a8_mma.py
--- old/comfy-kitchen-0.2.38/tests/test_w4a8_mma.py 2026-10-17
03:46:27.000000000 +0200
+++ new/comfy-kitchen-0.2.39/tests/test_w4a8_mma.py 2026-10-18
22:00:33.000000000 +0200
@@ -260,6 +260,29 @@
not torch.cuda.is_available() or torch.cuda.get_device_capability()[0] < 8,
reason="CUDA W4A8 linear needs sm_80+",
)
+@requires_cuda_backend
[email protected]("act", ["rms_norm", "swiglu", "situ"])
[email protected]("bits", [4, 6])
+def test_prefill_fused_activation_matches_eager(act, bits, seed):
+ """M >= 512 quantizes with the 64-lane ConvRot kernel, which fuses the
activation."""
+ m, n, k = 600, 512, 4096
+ qdata, s_rel, s_channel, cb, _ = _packed_weight(n, k, seed=1, bits=bits)
+ x = torch.randn(m, k * (1 if act == "rms_norm" else 2), device="cuda",
dtype=torch.bfloat16) * 3.0
+ w = torch.randn(k, device="cuda", dtype=torch.bfloat16)
+ if act == "rms_norm":
+ activated = torch.nn.functional.rms_norm(x, (k,), weight=w, eps=1e-6)
+ elif act == "swiglu":
+ activated = torch.nn.functional.silu(x[:, :k]) * x[:, k:]
+ else:
+ hidden, gate = x.chunk(2, dim=-1)
+ activated = 4.0 * torch.tanh(gate / 4.0) * torch.sigmoid(gate) * (25.0
* torch.tanh(hidden / 25.0))
+ eager = ck.w4a8_int8_linear(activated, qdata, s_rel, s_channel,
codebook=cb)
+ fused = ck.w4a8_int8_linear(x, qdata, s_rel, s_channel, codebook=cb,
+ input_act=act, input_act_weight=w,
input_act_eps=1e-6)
+ # the kernel evaluates the activation in fp32, eager rounds it to bf16
first
+ assert ((fused.float() - eager.float()).norm() /
eager.float().norm()).item() < 0.03
+
+
@pytest.mark.parametrize("m", [1, 9])
def test_canonical_residual_matches_addcmul(m, seed):
# the canonical layout's decode GEMV and prefill routes apply the residual
after the linear