Select verified temporal kernel prebuilts by Windows/Linux and GPU architecture

This commit is contained in:
alisson-anjos
2026-10-23 22:15:46 +00:00
parent de5c721584
commit 2829f71754
9 changed files with 341 additions and 80 deletions
+22 -7
View File
@@ -94,9 +94,9 @@ The console logs the first attention's input shape and completion time and the f
## One-pass native Kitchen kernels (1.115.16)
**From 1.115.17, BFS prepares the one-pass extension automatically on first use of Kitchen/sol-attn on supported Linux x86_64 SM120 systems with a driver supporting CUDA 13.** It downloads a fixed release into ComfyUI’s user cache, verifies embedded SHA256 hashes and ABI/architecture, and activates it in that execution. No pip install, package replacement or sampling-time compilation is required. Other systems retain the compatibility backends or can use an explicitly built manual overlay.
**From 1.115.22, BFS selects a pinned one-pass extension automatically for Linux x86_64 SM80/86/89/90/100/120 and Windows x86_64 SM86/89/120, with a driver supporting CUDA 13.** It downloads a fixed release into ComfyUI’s user cache, verifies embedded SHA256 hashes and ABI/architecture, and activates it in that execution. No pip install, package replacement or sampling-time compilation is required. Other systems retain the compatibility backends or can use an explicitly built manual overlay.
For an explicit installation or source build, use the [Kitchen fork's setup guide](https://github.com/alisson-anjos/comfy-kitchen/blob/feat/h3-native-temporal/H3_NATIVE_TEMPORAL_KERNELS.md). An [experimental Linux x86_64 SM120 prebuilt](https://github.com/alisson-anjos/comfy-kitchen/releases/tag/h3-native-temporal-kernel-v1) is available, including SHA256SUMS, so sampling need not compile anything. RTX 5090 shares SM120 with the tested RTX PRO 6000, but full video behavior/performance on the 5090 still requires validation. The installer adds a separate temporal module/library while preserving ordinary Kitchen's `_C` and operators. Source builds use `nvcc` explicitly; no per-shape torch.compile is used.
For an explicit installation or source build, use the [Kitchen fork's setup guide](https://github.com/alisson-anjos/comfy-kitchen/blob/feat/h3-native-temporal/H3_NATIVE_TEMPORAL_KERNELS.md). The [experimental nine-binary release](https://github.com/alisson-anjos/comfy-kitchen/releases/tag/temporal-native-0413d43878606425a84aa400f1e5da50ed9dcdad) is available, including SHA256SUMS, so sampling need not compile anything. RTX 5090 shares SM120 with the tested RTX PRO 6000, but full video behavior/performance on the 5090 still requires validation. The installer adds a separate temporal module/library while preserving ordinary Kitchen's `_C` and operators. Source builds use `nvcc` explicitly; no per-shape torch.compile is used.
Keep the existing MODEL wiring and native temporal guide mode. Model Attention Backend's Kitchen choice selects native dense when available. Model Sparse Attention's sol-attn choice selects native Sol during its eligible sparse window; its dense fallback retains the temporal policy too. No new UI switch is required. Console confirmation:
@@ -125,24 +125,39 @@ The sections below retain the earlier compatibility implementations and measurem
## Automatic first-use setup (1.115.17)
Choose Kitchen or sol-attn normally and run the native temporal node. On supported Linux x86_64 / NVIDIA SM120 / CUDA-13-driver systems, it prepares the extension during node execution when the model's load device is known, before sampling. A lazy fallback can prepare it on the first attention call too. The ordinary Flash/Flex path does not request an installation.
Choose Kitchen or sol-attn normally and run the native temporal node. On supported Linux/Windows x86_64 / NVIDIA architectures in the matrix below / CUDA-13-driver systems, it prepares the extension during node execution when the model's load device is known, before sampling. A lazy fallback can prepare it on the first attention call too. The ordinary Flash/Flex path does not request an installation.
The default cache is `ComfyUI user directory/bfs/h3_temporal_kernels/`; standalone use falls back to `~/.cache/bfsnodes/h3_temporal_kernels/`. Only the pinned Python API (~10 KB), SM120 library (~3.3 MB), LICENSE and NOTICE are downloaded. The implementation writes to its own cache, not `site-packages`, and leaves ordinary Kitchen's `_C` untouched. Existing compatible manual overlays are preferred.
The default cache is `ComfyUI user directory/bfs/h3_temporal_kernels/`; standalone use falls back to `~/.cache/bfsnodes/h3_temporal_kernels/`. Only the pinned Python API (~10 KB), the matching platform/GPU library (~2.1–3.4 MB), LICENSE and NOTICE are downloaded. The OS, architecture, release hash and ABI select the cache; Linux libraries and Windows DLLs are never substituted for one another. The implementation writes to its own cache, not `site-packages`, and leaves ordinary Kitchen's `_C` untouched. Existing compatible manual overlays are preferred.
Security/correctness properties: immutable source commit and embedded hashes; bounded download sizes; temporary downloads replaced atomically only after SHA256 validation; native ABI and GPU architecture checks before Python entry-point execution; thread/process installation locks; one setup attempt per device/cache location per process. An incomplete, incompatible or offline first installation logs its reason and retains the previous backend. A verified cache works in a new process without network access. No model, prompt or reference data is uploaded.
Expected console messages:
```text
First-use setup: downloading the verified one-pass extension (~3.3 MB). No compilation or pip install.
Verified one-pass extension ready: ...
First-use setup: downloading verified Linux SM89 one-pass extension (3.2 MB). No compilation or pip install.
Verified Linux SM89 one-pass extension ready: ...
Kitchen ONE-PASS dense kernel ACTIVE
# or: Kitchen ONE-PASS sol kernel ACTIVE
```
The addon activates without a second restart during that first workflow. Restarting ComfyUI is still required after updating BFS's own Python code. `BFS_H3_TEMPORAL_AUTO_INSTALL=0` disables automatic setup; `BFS_H3_TEMPORAL_CACHE_DIR=/path/to/cache` chooses another cache location. For a failed setup, restart to retry or use the manual installer linked above. Unsupported systems do not trigger an automatic source build.
A real first-use download, ABI check, and dense/Sol GPU calls passed. A separate fresh process with network requests deliberately disabled reused the verified cache successfully. [Recorded first-use/offline checks](docs/benchmarks/h3_temporal_auto_setup.json).
The original SM120-only release passed a real first-use download, ABI check, and dense/Sol GPU calls. This does not GPU-validate the newer multi-architecture binaries. A separate fresh process with network requests deliberately disabled reused the verified cache successfully. [Recorded first-use/offline checks](docs/benchmarks/h3_temporal_auto_setup.json).
### Published architecture matrix (1.115.22)
| OS, 64-bit x86 | Published GPU architectures | CI checks | GPU execution of this release |
| --- | --- | --- | --- |
| Linux | SM80, SM86, SM89, SM90, SM100, SM120 | CUDA13 compilation, SHA256, native ABI/arch exports | Not established across the matrix |
| Windows | SM86, SM89, SM120 | CUDA13 compilation, SHA256, native ABI/arch exports on Windows runners | Not established |
[Download binaries](https://github.com/alisson-anjos/comfy-kitchen/releases/tag/temporal-native-0413d43878606425a84aa400f1e5da50ed9dcdad). The release contains BUILD_MANIFEST.json and SHA256SUMS. BFS uses the reviewed local [pinned manifest](h3_temporal_binaries.json), not an unverified remote latest-release lookup. The [CI workflow](https://github.com/alisson-anjos/comfy-kitchen/blob/0413d43878606425a84aa400f1e5da50ed9dcdad/.github/workflows/temporal-windows.yml) rebuilds/publishes per-commit experimental assets on relevant source changes; adopting a new release requires updating BFS's pin.
All nine asset sizes/hashes were independently checked; all six Linux ABI/arch exports were loaded without kernel execution. A real SM89 Linux download, checksum, ABI check and Python-API import also passed with CUDA disabled: [record](docs/benchmarks/h3_temporal_matrix_setup.json). CPU fixtures cover Windows DLL selection, driver lookup and locking; these do not replace a real Windows/GPU workflow test. Old SM120 numerical/timing results are not validation of every new binary.
The NVIDIA driver must support CUDA13 even if PyTorch uses CUDA12.x. Linux needs a compatible glibc/libstdc++ runtime; Windows may need the Microsoft Visual C++ runtime. A missing driver/runtime, incompatible ABI, unsupported GPU/OS or download failure logs the reason and keeps the compatibility backend; no pip install, system-library replacement or compiler launch is attempted. Frame-wise Kitchen fallback can be slower; Flash Partitions remains a separate explicit selection. ARM, macOS and AMD are not covered by these CUDA prebuilts.
Examples: SM89 covers RTX40 and RTX6000Ada; SM120 covers RTX50 and RTXPRO6000Blackwell; A6000 is SM86. A card name containing “6000” does not identify one architecture.
## Comfy Kitchen and Model Attention Backend (1.115.13)
+1 -1
View File
@@ -202,7 +202,7 @@ Experimental native temporal motion-transfer inference (standalone node and Shot
[Illustrated explanation: native temporal alignment with compile-free Flash Partitions](docs/H3_FLASH_PARTITIONS.md) covers the exact temporal rule, positive partition decomposition, log-sum-exp merging, measured performance, guide scaling and current backend limitations.
Experimental temporal mode automatically recognizes **Model Attention Backend** (Kitchen/PyTorch) and, from 1.115.14, **Model Sparse Attention → sol-attn**. Version 1.115.17 automatically prepares the verified extension on first use on supported Linux SM120 systems. Version 1.115.16+ can use the [one-pass Kitchen extension](H3_NATIVE_TEMPORAL.md#one-pass-native-kitchen-kernels-111516) for dense INT8 and sol-attn. The earlier compatibility path repeats frame-wise calls and was slower than Flash Partitions; see [setup, measurements and limitations](H3_NATIVE_TEMPORAL.md#model-sparse-attention-sol-attn-compatibility-111514). SLA/VSA remain unsupported.
Experimental temporal mode automatically recognizes **Model Attention Backend** (Kitchen/PyTorch) and, from 1.115.14, **Model Sparse Attention → sol-attn**. Version 1.115.22 automatically selects verified prebuilts for Linux SM80/86/89/90/100/120 and Windows SM86/89/120 (x86_64, CUDA13-capable driver); cross-platform GPU validation remains experimental. Version 1.115.16+ can use the [one-pass Kitchen extension](H3_NATIVE_TEMPORAL.md#one-pass-native-kitchen-kernels-111516) for dense INT8 and sol-attn. The earlier compatibility path repeats frame-wise calls and was slower than Flash Partitions; see [setup, measurements and limitations](H3_NATIVE_TEMPORAL.md#model-sparse-attention-sol-attn-compatibility-111514). SLA/VSA remain unsupported.
![H3 temporal weight decomposition](docs/illustrations/h3_temporal_weight_decomposition.png)
@@ -0,0 +1,15 @@
{
"platform": "Linux",
"arch": 89,
"checks": [
"release download",
"SHA256",
"native ABI1",
"native arch89",
"Python API import"
],
"gpu_execution": false,
"api_version": "h3_native_temporal_kernel_v1",
"release": "temporal-native-0413d43878606425a84aa400f1e5da50ed9dcdad",
"cuda_initialized": false
}
+106
View File
@@ -0,0 +1,106 @@
{
"release_tag": "temporal-native-0413d43878606425a84aa400f1e5da50ed9dcdad",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"minimum_cuda_driver_version": 13000,
"binaries": [
{
"native_abi": 1,
"arch": 80,
"sha256": "87f3c33fec7480b480335ed09257488b3eb3052eca6dd3ab9a11477ce7213784",
"bytes": 3307288,
"gpu_runtime_validation": false,
"platform": "Linux",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm80.so"
},
{
"native_abi": 1,
"arch": 86,
"sha256": "b42c1363ad1c360416b57ffe6e215df2202c1510792bea9f6bef34b463aec955",
"bytes": 3303192,
"gpu_runtime_validation": false,
"platform": "Linux",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm86.so"
},
{
"native_abi": 1,
"arch": 89,
"sha256": "0cafbbb543f35cf7cba1be1810f01203cd93adbeac3f64297540d9a822bfbb75",
"bytes": 3155736,
"gpu_runtime_validation": false,
"platform": "Linux",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm89.so"
},
{
"native_abi": 1,
"arch": 90,
"sha256": "825f6aedab25933a9f8e904ae40db5fd8d11d924c08edfa8fa7c070059b928de",
"bytes": 3204888,
"gpu_runtime_validation": false,
"platform": "Linux",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm90.so"
},
{
"native_abi": 1,
"arch": 100,
"sha256": "a1871c990141b5456e302f44eb5ea42947da3d0e45311718927856de2996d421",
"bytes": 3352344,
"gpu_runtime_validation": false,
"platform": "Linux",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm100.so"
},
{
"native_abi": 1,
"arch": 120,
"sha256": "66220a378c062f1efe465f8ea1bc171806c397701b2c50c9a4a13cd1c86ed8ad",
"bytes": 3405592,
"gpu_runtime_validation": false,
"platform": "Linux",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm120.so"
},
{
"native_abi": 1,
"arch": 86,
"sha256": "50b4446bfeaff14d320afec83e1b16b2ad299f237d9bb26d0d56807f28926b22",
"bytes": 2281472,
"gpu_runtime_validation": false,
"platform": "Windows",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm86.dll"
},
{
"native_abi": 1,
"arch": 89,
"sha256": "dd20b03c7dbe339e235d650b44578446e8607939469328b5b6c161ba33ea9668",
"bytes": 2134016,
"gpu_runtime_validation": false,
"platform": "Windows",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm89.dll"
},
{
"native_abi": 1,
"arch": 120,
"sha256": "1a67a3f853506dec4b5418fc2dbc00abcf4ba1ee8023e487bb2ecd48422bee3a",
"bytes": 2384384,
"gpu_runtime_validation": false,
"platform": "Windows",
"cuda": "13.0",
"source_commit": "0413d43878606425a84aa400f1e5da50ed9dcdad",
"file": "libck_temporal_sm120.dll"
}
]
}
+94 -67
View File
@@ -1,27 +1,30 @@
"""Pinned, node-local bootstrap for the optional native temporal extension.
"""Pinned platform/GPU-specific bootstrap for the optional temporal extension.
Only fixed trusted-release bytes are loaded, after embedded SHA256 checks.
No pip, site-packages writes, main Kitchen replacement, or sampling-time build.
Verified release bytes only. No pip, compilation, site-packages writes or Kitchen
replacement. Build/ABI checks do not imply GPU runtime validation.
"""
import ctypes
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import platform
import sys
import tempfile
import threading
import time
import urllib.request
try:
from .minimax_h3_temporal_lock import file_lock
except ImportError:
from minimax_h3_temporal_lock import file_lock
SOURCE_COMMIT = '9953b6caec7a5cb3e2486dd97a365f7af44eedad'
MANIFEST = json.loads((Path(__file__).parent/'h3_temporal_binaries.json').read_text())
SOURCE_COMMIT = MANIFEST['source_commit']
RAW = f'https://raw.githubusercontent.com/alisson-anjos/comfy-kitchen/{SOURCE_COMMIT}'
RELEASE = 'https://github.com/alisson-anjos/comfy-kitchen/releases/download/h3-native-temporal-kernel-v1'
BINARY_HASH = 'ad87534bfb3ece2af84c201e326b2b405f0108f34efb36937c09967c14f0329d'
ASSETS = (
('temporal_attention.py', RAW+'/comfy_kitchen/temporal_attention.py', '0151e8b5b5886c9e8b4608cbc179e9dd6f934170cf45b8d27e14801f3c4828a1', 256*1024),
('backends/cuda/native_temporal/libck_temporal_sm120.so', RELEASE+'/libck_temporal_sm120.so', BINARY_HASH, 16*1024*1024),
RELEASE = 'https://github.com/alisson-anjos/comfy-kitchen/releases/download/'+MANIFEST['release_tag']
COMMON_ASSETS = (
('temporal_attention.py', RAW+'/comfy_kitchen/temporal_attention.py', '756171846469864702903d19e86a5f2b793a1e01b8fab57a72c68306dd1f2ee5', 256*1024),
('LICENSE', RAW+'/LICENSE', 'cd1c9ea1573c9047e9b5a3676ecc1c8f755e56f249ac1ebd8b5ceda7899037da', 64*1024),
('NOTICE', RAW+'/NOTICE', '35c43127ad3acbd6bc94ff63c56684a7c26eedf07795e596c8105a0476dae2ea', 64*1024),
)
@@ -30,32 +33,54 @@ _READY = {}
_FAILED = set()
def cache_root():
def select_binary(system, machine, capability):
if system not in ('Linux','Windows') or machine.lower() not in ('x86_64','amd64'):
return None
arch = int(capability[0])*10+int(capability[1])
return next((b for b in MANIFEST['binaries'] if b['platform']==system and b['arch']==arch),None)
def binary_for_device(device):
import torch
return select_binary(platform.system(),platform.machine(),torch.cuda.get_device_capability(device))
def cache_base():
explicit=os.environ.get('BFS_H3_TEMPORAL_CACHE_DIR')
if explicit:
base=Path(explicit).expanduser()
else:
try:
import folder_paths
base=Path(folder_paths.get_user_directory())/'bfs'/'h3_temporal_kernels'
except (ImportError,AttributeError):
base=Path.home()/'.cache'/'bfsnodes'/'h3_temporal_kernels'
return base/('v1-linux-x86_64-sm120-'+BINARY_HASH[:12])
if explicit:return Path(explicit).expanduser()
try:
import folder_paths
return Path(folder_paths.get_user_directory())/'bfs'/'h3_temporal_kernels'
except (ImportError,AttributeError):
return Path.home()/'.cache'/'bfsnodes'/'h3_temporal_kernels'
def cache_root(target):
return cache_base()/(f"v1-{target['platform'].lower()}-x86_64-sm{target['arch']}-"+target['sha256'][:12])
def driver_version():
driver=ctypes.WinDLL('nvcuda.dll') if platform.system()=='Windows' else ctypes.CDLL('libcuda.so.1')
version=ctypes.c_int()
if driver.cuDriverGetVersion(ctypes.byref(version))!=0:
raise RuntimeError('Cannot query NVIDIA CUDA driver version')
return version.value
def compatible(device):
if os.environ.get('BFS_H3_TEMPORAL_AUTO_INSTALL','1').lower() in ('0','false','off'):
return False,'automatic setup disabled'
if platform.system()!='Linux' or platform.machine() not in ('x86_64','AMD64'):
return False,'automatic prebuilt supports Linux x86_64 only'
if platform.system() not in ('Linux','Windows') or platform.machine().lower() not in ('x86_64','amd64') or ctypes.sizeof(ctypes.c_void_p)!=8:
return False,'automatic prebuilts require 64-bit Linux or Windows on x86_64'
import torch
if getattr(torch.version,'hip',None) or not torch.cuda.is_available() or torch.cuda.get_device_capability(device)!=(12,0):
return False,'automatic prebuilt supports NVIDIA SM120 only'
# Check the actual driver, not torch.version.cuda: the sidecar uses its own
# static CUDA runtime and the same device/stream primary context.
driver=ctypes.CDLL('libcuda.so.1');version=ctypes.c_int()
if driver.cuDriverGetVersion(ctypes.byref(version))!=0 or version.value<13000:
return False,'the prebuilt requires a driver supporting CUDA 13'
if getattr(torch.version,'hip',None) or not torch.cuda.is_available():
return False,'automatic prebuilts require NVIDIA CUDA'
target=binary_for_device(device)
if target is None:
return False,'no pinned prebuilt for this operating system/GPU architecture'
# The sidecar uses CUDA 13's static runtime; PyTorch itself may use an older CUDA.
if driver_version()<MANIFEST['minimum_cuda_driver_version']:
return False,'the prebuilt requires an NVIDIA driver supporting CUDA 13'
import comfy_kitchen
if not comfy_kitchen.int8_attention_is_available(device):
return False,'Kitchen native INT8 attention unavailable'
@@ -73,8 +98,7 @@ def digest(path):
def download(path,url,expected,limit):
if path.is_file() and digest(path)==expected:
return
if path.is_file() and digest(path)==expected:return
path.parent.mkdir(parents=True,exist_ok=True)
fd,name=tempfile.mkstemp(prefix=path.name+'.',suffix='.partial',dir=path.parent)
temp=Path(name)
@@ -88,51 +112,51 @@ def download(path,url,expected,limit):
total+=len(block)
if total>limit:raise RuntimeError('Temporal asset exceeds the pinned size limit')
h.update(block);out.write(block)
if h.hexdigest()!=expected:
raise RuntimeError('Temporal asset checksum mismatch; downloaded code was not loaded')
if h.hexdigest()!=expected:raise RuntimeError('Temporal asset checksum mismatch; downloaded code was not loaded')
os.replace(temp,path)
finally:
if temp.exists():temp.unlink()
def provision(root):
import fcntl
root.mkdir(parents=True,exist_ok=True)
with (root/'.install.lock').open('a+b') as lock:
deadline=time.monotonic()+60
while True:
try:
fcntl.flock(lock,fcntl.LOCK_EX|fcntl.LOCK_NB);break
except BlockingIOError:
if time.monotonic()>deadline:raise RuntimeError('Timed out waiting for temporal setup lock')
time.sleep(.1)
missing=any(not (root/rel).is_file() or digest(root/rel)!=sha for rel,_,sha,_ in ASSETS)
def assets_for(target):
binary=('backends/cuda/native_temporal/'+target['file'],RELEASE+'/'+target['file'],target['sha256'],target['bytes'])
return (*COMMON_ASSETS,binary)
def provision(root,target):
with file_lock(root/'.install.lock',60):
assets=assets_for(target)
missing=any(not (root/rel).is_file() or digest(root/rel)!=sha for rel,_,sha,_ in assets)
if missing:
print('[BFS H3 native temporal] First-use setup: downloading the verified one-pass extension (~3.3 MB). No compilation or pip install.',flush=True)
for rel,url,sha,limit in ASSETS:
print(f"[BFS H3 native temporal] First-use setup: downloading verified {target['platform']} SM{target['arch']} one-pass extension ({target['bytes']/1e6:.1f} MB). No compilation or pip install.",flush=True)
# Verify the binary before importing any downloaded Python code.
for rel,url,sha,limit in sorted(assets,key=lambda a:a[0].endswith('.py')):
download(root/rel,url,sha,limit)
# Verify the native binary before executing the Python entry point too.
binary=root/ASSETS[1][0];lib=ctypes.CDLL(str(binary))
lib.ck_temporal_abi_version.restype=ctypes.c_int;lib.ck_temporal_arch.restype=ctypes.c_int
if lib.ck_temporal_abi_version()!=1 or lib.ck_temporal_arch()!=120:
raise RuntimeError('Temporal prebuilt ABI/architecture mismatch')
name='_bfs_ck_temporal_v1_sm120_'+ASSETS[0][2][:12]+'_'+hashlib.sha256(str(root.resolve()).encode()).hexdigest()[:12]
if name in sys.modules:
return sys.modules[name]
spec=importlib.util.spec_from_file_location(name,root/'temporal_attention.py')
module=importlib.util.module_from_spec(spec);sys.modules[name]=module
try:spec.loader.exec_module(module)
except BaseException:
sys.modules.pop(name,None);raise
if getattr(module,'VERSION',None)!='h3_native_temporal_kernel_v1':
sys.modules.pop(name,None);raise RuntimeError('Temporal API version mismatch')
print(f'[BFS H3 native temporal] Verified one-pass extension ready: {root}',flush=True)
return module
return load_api(root,target)
def load_api(root,target):
binary=root/'backends/cuda/native_temporal'/target['file']
lib=ctypes.CDLL(str(binary))
lib.ck_temporal_abi_version.restype=ctypes.c_int;lib.ck_temporal_arch.restype=ctypes.c_int
if lib.ck_temporal_abi_version()!=target['native_abi'] or lib.ck_temporal_arch()!=target['arch']:
raise RuntimeError('Temporal native ABI/architecture mismatch')
name=f"_bfs_ck_temporal_v1_sm{target['arch']}_"+COMMON_ASSETS[0][2][:12]+'_'+hashlib.sha256(str(root.resolve()).encode()).hexdigest()[:12]
if name in sys.modules:return sys.modules[name]
spec=importlib.util.spec_from_file_location(name,root/'temporal_attention.py')
module=importlib.util.module_from_spec(spec);sys.modules[name]=module
try:
spec.loader.exec_module(module)
if getattr(module,'VERSION',None)!='h3_native_temporal_kernel_v1':raise RuntimeError('Temporal API version mismatch')
except BaseException:
sys.modules.pop(name,None);raise
print(f"[BFS H3 native temporal] Verified {target['platform']} SM{target['arch']} one-pass extension ready: {root}",flush=True)
return module
def ensure(device):
"""At most one automatic setup attempt per process/cache location."""
key=(str(cache_root()),str(device))
"""At most one automatic setup attempt per device/cache location per process."""
key=(str(cache_base()),platform.system(),str(device))
with _LOCK:
if key in _READY:return _READY[key]
if key in _FAILED:return None
@@ -142,11 +166,14 @@ def ensure(device):
_FAILED.add(key)
print(f'[BFS H3 native temporal] Automatic native setup skipped: {reason}; retaining the existing backend.',flush=True)
return None
module=provision(Path(key[0]))
target=binary_for_device(device)
if target is None:raise RuntimeError('No pinned native temporal prebuilt for this device')
module=provision(cache_root(target),target)
if not module.is_available(device):raise RuntimeError('Verified native extension is unavailable for this device')
_READY[key]=module
return module
except Exception as exc:
if type(exc).__name__=='InterruptProcessingException':raise
_FAILED.add(key)
print(f'[BFS H3 native temporal] Automatic native setup unavailable: {exc}. Retaining the existing backend. Restart ComfyUI to retry, or install the extension manually.',flush=True)
return None
+28
View File
@@ -0,0 +1,28 @@
"""Per-cache process lock on Linux and Windows."""
from contextlib import contextmanager
import platform
import time
@contextmanager
def file_lock(path,timeout=60):
path.parent.mkdir(parents=True,exist_ok=True)
with path.open('a+b') as handle:
windows=platform.system()=='Windows'
if windows:
import msvcrt
handle.seek(0,2)
if handle.tell()==0:handle.write(b'\0');handle.flush()
def acquire():handle.seek(0);msvcrt.locking(handle.fileno(),msvcrt.LK_NBLCK,1)
def release():handle.seek(0);msvcrt.locking(handle.fileno(),msvcrt.LK_UNLCK,1)
else:
import fcntl
def acquire():fcntl.flock(handle,fcntl.LOCK_EX|fcntl.LOCK_NB)
def release():fcntl.flock(handle,fcntl.LOCK_UN)
deadline=time.monotonic()+timeout
while True:
try:acquire();break
except OSError:
if time.monotonic()>deadline:raise RuntimeError('Timed out waiting for temporal kernel setup')
time.sleep(.1)
try:yield
finally:release()
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "bfsnodes"
description = "Custom ComfyUI nodes for reference-guided video: BFS Sampler (guide-driven IC-LoRAs, auto crop, mask inpainting, temporal chunking), LTX Multiple Controls / Identity Transfer / Edit Anything, Head-Swap and Anime2Real conditioning, MiniMax-H3 latent guides, RoPE layouts and loaders, LoRA Surgery, and Shot Planner / Shot Loop (tabbed panel): split long videos into shots at camera cuts, face-tracked cast, SAM 3 mask per shot with full frame, paste, mask, crop and crop + mask generation modes, optional translucent Colored Mark overlay for manually selected regions, optional Qwen3-VL to suggest settings, describe references ({details}) and the person to replace ({target}), continuity between shots, H3 conditioning with an optional duet and prompt writer, join with the original timing, audio and a side-by-side comparison. H3 Duet: pin a source clip beside the video, training-free."
version = "1.115.21"
version = "1.115.22"
license = {file = "LICENSE"}
dependencies = [
"torch",
+55 -4
View File
@@ -16,8 +16,9 @@ def setup(monkeypatch,tmp_path):
boot._READY.clear();boot._FAILED.clear()
source=b"VERSION='h3_native_temporal_kernel_v1'\ndef is_available(device):return True\n"
binary=b'fake verified native fixture'
assets=[('temporal_attention.py','https://test.invalid/api',hashlib.sha256(source).hexdigest(),1024),('backends/cuda/native_temporal/libck_temporal_sm120.so','https://test.invalid/bin',hashlib.sha256(binary).hexdigest(),1024)]
monkeypatch.setattr(boot,'ASSETS',tuple(assets))
target=dict(platform='Linux',arch=120,file='libck_temporal_sm120.so',native_abi=1,sha256=hashlib.sha256(binary).hexdigest(),bytes=len(binary))
monkeypatch.setattr(boot,'COMMON_ASSETS',(('temporal_attention.py','https://test.invalid/api',hashlib.sha256(source).hexdigest(),1024),))
monkeypatch.setattr(boot,'binary_for_device',lambda d:target)
requests=[]
def urlopen(request,timeout):
requests.append(request.full_url)
@@ -26,10 +27,10 @@ def setup(monkeypatch,tmp_path):
loads=[]
def cdll(path):
loads.append(path)
return SimpleNamespace(ck_temporal_abi_version=NativeFunction(1),ck_temporal_arch=NativeFunction(120))
return SimpleNamespace(ck_temporal_abi_version=NativeFunction(1),ck_temporal_arch=NativeFunction(target['arch']))
monkeypatch.setattr(boot.ctypes,'CDLL',cdll)
monkeypatch.setattr(boot,'compatible',lambda d:(True,None))
yield SimpleNamespace(root=boot.cache_root(),requests=requests,loads=loads,source=source,binary=binary)
yield SimpleNamespace(root=boot.cache_root(target),target=target,requests=requests,loads=loads,source=source,binary=binary)
boot._READY.clear();boot._FAILED.clear()
@@ -103,3 +104,53 @@ def test_bad_binary_arch_is_rejected_before_python_api_execution(setup,monkeypat
monkeypatch.setattr(boot.ctypes,'CDLL',lambda p:SimpleNamespace(ck_temporal_abi_version=NativeFunction(1),ck_temporal_arch=NativeFunction(89)))
assert boot.ensure('cuda:0') is None
assert not [n for n in set(sys.modules)-before if n.startswith('_bfs_ck_temporal')]
@pytest.mark.parametrize('system,arch',[(b['platform'],b['arch']) for b in boot.MANIFEST['binaries']])
def test_release_matrix_selects_exact_os_and_arch(system,arch):
target=boot.select_binary(system,'AMD64' if system=='Windows' else 'x86_64',(arch//10,arch%10))
assert target['arch']==arch and target['platform']==system
assert target['file'].endswith('.dll' if system=='Windows' else '.so')
assert len(target['sha256'])==64 and target['bytes']>0 and target['native_abi']==1
assert target['gpu_runtime_validation'] is False
@pytest.mark.parametrize('system,machine,cap',[('Darwin','x86_64',(12,0)),('Linux','aarch64',(12,0)),('Windows','AMD64',(9,0)),('Linux','x86_64',(7,5))])
def test_unpublished_combinations_never_substitute_another_binary(system,machine,cap):
assert boot.select_binary(system,machine,cap) is None
def test_windows_ada_downloads_only_its_own_dll(setup,monkeypatch):
from contextlib import nullcontext
setup.target.update(platform='Windows',arch=89,file='libck_temporal_sm89.dll')
monkeypatch.setattr(boot.platform,'system',lambda:'Windows')
monkeypatch.setattr(boot,'file_lock',lambda *a,**k:nullcontext())
module=boot.ensure('cuda:0')
assert module is not None
assert len(setup.requests)==2
assert any(url.endswith('libck_temporal_sm89.dll') for url in setup.requests)
assert not any('sm120' in url or url.endswith('.so') for url in setup.requests)
assert (boot.cache_root(setup.target)/'backends/cuda/native_temporal/libck_temporal_sm89.dll').is_file()
def test_platform_and_arch_produce_separate_cache_names(setup):
linux=boot.cache_root(setup.target)
windows=boot.cache_root(dict(setup.target,platform='Windows'))
ada=boot.cache_root(dict(setup.target,arch=89))
assert len({linux,windows,ada})==3
def test_automatic_setup_never_launches_a_compiler_or_pip(setup,monkeypatch):
import subprocess
def forbidden(*a,**kw):raise AssertionError('automatic download must not launch a build or pip')
monkeypatch.setattr(subprocess,'Popen',forbidden)
assert boot.ensure('cuda:0') is not None
def test_windows_driver_query_uses_nvcuda_not_linux_lib(monkeypatch):
seen=[]
class Driver:
def cuDriverGetVersion(self,p):p._obj.value=13000;return 0
monkeypatch.setattr(boot.platform,'system',lambda:'Windows')
monkeypatch.setattr(boot.ctypes,'WinDLL',lambda name:(seen.append(name) or Driver()),raising=False)
assert boot.driver_version()==13000 and seen==['nvcuda.dll']
+19
View File
@@ -0,0 +1,19 @@
import sys
from pathlib import Path
from types import SimpleNamespace
sys.path.insert(0,str(Path(__file__).resolve().parents[1]))
import minimax_h3_temporal_lock as locks
def test_windows_locks_one_existing_byte(tmp_path,monkeypatch):
calls=[]
monkeypatch.setattr(locks.platform,'system',lambda:'Windows')
monkeypatch.setitem(sys.modules,'msvcrt',SimpleNamespace(LK_NBLCK=1,LK_UNLCK=2,locking=lambda fd,mode,size:calls.append((mode,size))))
path=tmp_path/'cache.lock'
with locks.file_lock(path):assert path.stat().st_size>=1
assert calls==[(1,1),(2,1)]
def test_linux_lock_acquires_and_releases(tmp_path):
with locks.file_lock(tmp_path/'cache.lock'):pass
with locks.file_lock(tmp_path/'cache.lock'):pass