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

Reply via email to