Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-comfy-aimdo for
openSUSE:Factory checked in at 2026-09-17 15:17:40
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-comfy-aimdo (Old)
and /work/SRC/openSUSE:Factory/.python-comfy-aimdo.new.383539 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-comfy-aimdo"
Thu Sep 17 15:17:40 2026 rev:7 rq:1378284 version:0.5.5
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-comfy-aimdo/python-comfy-aimdo.changes
2026-09-11 18:05:49.896121151 +0200
+++
/work/SRC/openSUSE:Factory/.python-comfy-aimdo.new.383539/python-comfy-aimdo.changes
2026-09-17 15:18:24.551419667 +0200
@@ -1,0 +2,14 @@
+Wed Sep 16 07:24:48 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 0.5.5:
+ * Windows disk-speed detection for model storage (new
+ comfy_aimdo.storage.fast_disk plus native src-win/disk-id.c).
+ The C helper is Windows-only; this package ships the Python
+ wrapper, which returns None on Linux
+ * Native malloc-graph search no longer skips overlapping alias
+ ranges, so a later allocation can reuse those physical pages;
+ it lives in the native allocator, which this build does not
+ compile
+- Extend %check to import the new comfy_aimdo.storage module
+
+-------------------------------------------------------------------
Old:
----
comfy-aimdo-0.5.3.tar.gz
New:
----
comfy-aimdo-0.5.5.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-comfy-aimdo.spec ++++++
--- /var/tmp/diff_new_pack.E2UMf9/_old 2026-09-17 15:18:25.138444307 +0200
+++ /var/tmp/diff_new_pack.E2UMf9/_new 2026-09-17 15:18:25.141444433 +0200
@@ -18,7 +18,7 @@
%{?sle15_python_module_pythons}
Name: python-comfy-aimdo
-Version: 0.5.3
+Version: 0.5.5
Release: 0
Summary: AI Model Dynamic Offloader for ComfyUI (pure-Python fallback)
License: GPL-3.0-only
@@ -64,7 +64,7 @@
# device through the native aimdo.so, which this build does not compile.
# Confirm instead that the shipped modules import and the loader leaves
# lib unset (torch.py excluded: it imports torch at module scope).
-%python_expand PYTHONPATH=%{buildroot}%{$python_sitelib} $python -B -c "import
comfy_aimdo.control, comfy_aimdo.host_buffer, comfy_aimdo.malloc_graph,
comfy_aimdo.model_mmap, comfy_aimdo.model_vbar, comfy_aimdo.vram_buffer; assert
comfy_aimdo.control.lib is None"
+%python_expand PYTHONPATH=%{buildroot}%{$python_sitelib} $python -B -c "import
comfy_aimdo.control, comfy_aimdo.host_buffer, comfy_aimdo.malloc_graph,
comfy_aimdo.model_mmap, comfy_aimdo.model_vbar, comfy_aimdo.storage,
comfy_aimdo.vram_buffer; assert comfy_aimdo.control.lib is None"
# Patch0: without a CUDA or ROCm PyTorch, init() must report failure rather
# than guess a vendor, and it must not emit a warning while doing so.
cat > test_no_vendor.py <<'EOF'
++++++ comfy-aimdo-0.5.3.tar.gz -> comfy-aimdo-0.5.5.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/comfy-aimdo-0.5.3/.github/workflows/build-wheels.yml
new/comfy-aimdo-0.5.5/.github/workflows/build-wheels.yml
--- old/comfy-aimdo-0.5.3/.github/workflows/build-wheels.yml 2026-09-08
12:59:20.000000000 +0200
+++ new/comfy-aimdo-0.5.5/.github/workflows/build-wheels.yml 2026-09-15
10:46:20.000000000 +0200
@@ -72,12 +72,12 @@
src/*.c src-cuda/dispatch.c src-win/*.c ^
/Isrc /Isrc-win /IC:/Detours/include /FIcompiler.h
/Fe:comfy_aimdo/aimdo.dll ^
/link /LIBPATH:%DETOURS_LIB_DIR% ^
- dxgi.lib dxguid.lib detours.lib onecore.lib
+ cfgmgr32.lib dxgi.lib dxguid.lib setupapi.lib detours.lib
onecore.lib
cl.exe /LD /O2 /D__HIP_PLATFORM_AMD__ ^
src/*.c src-hip/dispatch.c src-win/*.c ^
/Isrc /Isrc-win /IC:/Detours/include /FIcompiler.h
/Fe:comfy_aimdo/aimdo_rocm.dll ^
/link /LIBPATH:%DETOURS_LIB_DIR% ^
- dxgi.lib dxguid.lib detours.lib onecore.lib
+ cfgmgr32.lib dxgi.lib dxguid.lib setupapi.lib detours.lib
onecore.lib
shell: cmd
- name: Build default Stub Wheel
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/comfy-aimdo-0.5.3/comfy_aimdo/storage.py
new/comfy-aimdo-0.5.5/comfy_aimdo/storage.py
--- old/comfy-aimdo-0.5.3/comfy_aimdo/storage.py 1970-01-01
01:00:00.000000000 +0100
+++ new/comfy-aimdo-0.5.5/comfy_aimdo/storage.py 2026-09-15
10:46:20.000000000 +0200
@@ -0,0 +1,19 @@
+import ctypes
+import os
+
+from . import control
+
+lib = control.lib if os.name == "nt" else None
+
+if lib is not None:
+ lib.aimdo_storage_fast_disk.argtypes = [ctypes.c_wchar_p]
+ lib.aimdo_storage_fast_disk.restype = ctypes.c_int
+
+
+def fast_disk(path):
+ if lib is None:
+ return None
+ result = lib.aimdo_storage_fast_disk(str(path))
+ if result < 0:
+ return None
+ return bool(result)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/comfy-aimdo-0.5.3/src/malloc-graph.c
new/comfy-aimdo-0.5.5/src/malloc-graph.c
--- old/comfy-aimdo-0.5.3/src/malloc-graph.c 2026-09-08 12:59:20.000000000
+0200
+++ new/comfy-aimdo-0.5.5/src/malloc-graph.c 2026-09-15 10:46:20.000000000
+0200
@@ -930,11 +930,13 @@
size_t pages = ALIGN_UP(size, MG_PAGE) / MG_PAGE;
while (va + pages <= g->va_count) {
- size_t j;
+ size_t j, advance;
+ int phys;
for (j = 0; j < pages; j++) {
- int phys = g->va_phys[va + j];
+ phys = g->va_phys[va + j];
if (rogue_va(g, va + j) || g->allocations.physical_live[phys] ||
rogue_phys(g, phys)) {
+ advance = j + 1;
break;
}
g->allocations.physical_live[phys] = true;
@@ -943,9 +945,13 @@
break;
}
for (size_t k = 0; k < j; k++) {
+ // Resume after the earlier alias without skipping overlapping
ranges.
+ if (g->va_phys[va + k] == phys) {
+ advance = k + 1;
+ }
g->allocations.physical_live[g->va_phys[va + k]] = false;
}
- va += j + 1;
+ va += advance;
}
RETURN_G_FAILED(g->failed, true);
if (va + pages > g->va_count) {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/comfy-aimdo-0.5.3/src-win/disk-id.c
new/comfy-aimdo-0.5.5/src-win/disk-id.c
--- old/comfy-aimdo-0.5.3/src-win/disk-id.c 1970-01-01 01:00:00.000000000
+0100
+++ new/comfy-aimdo-0.5.5/src-win/disk-id.c 2026-09-15 10:46:20.000000000
+0200
@@ -0,0 +1,243 @@
+#include "plat.h"
+
+#include <windows.h>
+#include <cfgmgr32.h>
+#include <setupapi.h>
+#include <winioctl.h>
+
+#define AIMDO_DISK_UNKNOWN (-1)
+#define AIMDO_DISK_SLOW 0
+#define AIMDO_DISK_FAST 1
+
+#define BUS_TYPE_UNKNOWN 0
+#define BUS_TYPE_USB 7
+#define BUS_TYPE_RAID 8
+#define BUS_TYPE_ISCSI 9
+#define BUS_TYPE_VIRTUAL 14
+#define BUS_TYPE_FILE_BACKED_VIRTUAL 15
+#define BUS_TYPE_SPACES 16
+#define BUS_TYPE_NVME 17
+#define BUS_TYPE_NVMEOF 20
+#define BUS_TYPE_MAX 21
+
+static const GUID disk_interface_guid = {
+ 0x53f56307, 0xb6bf, 0x11d0, {0x94, 0xf2, 0x00, 0xa0, 0xc9, 0x1e, 0xfb,
0x8b}
+};
+static const GUID pci_property_guid = {
+ 0x3ab22e31, 0x8264, 0x4b4e, {0x9a, 0xf5, 0xa8, 0xd2, 0xd8, 0xe3, 0x3e,
0x62}
+};
+
+static HANDLE open_existing(const wchar_t *path) {
+ return CreateFileW(path, 0, FILE_SHARE_READ | FILE_SHARE_WRITE |
FILE_SHARE_DELETE,
+ NULL, OPEN_EXISTING, 0, NULL);
+}
+
+static bool get_volume_guid(const wchar_t *path, wchar_t volume[64]) {
+ wchar_t buffer[32768];
+ wchar_t *end;
+ DWORD length;
+ HANDLE file = open_existing(path);
+
+ if (file == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+ length = GetFinalPathNameByHandleW(file, buffer, ARRAY_SIZE(buffer),
+ FILE_NAME_NORMALIZED |
VOLUME_NAME_GUID);
+ CloseHandle(file);
+ if (!length || length >= ARRAY_SIZE(buffer) || wcsncmp(buffer,
L"\\\\?\\Volume{", 11) ||
+ !(end = wcschr(buffer + 11, L'}')) || end - buffer + 2 >= 64) {
+ return false;
+ }
+ end[1] = L'\\';
+ end[2] = L'\0';
+ wcscpy_s(volume, 64, buffer);
+ return true;
+}
+
+static int disk_bus_type(DWORD disk_number) {
+ STORAGE_PROPERTY_QUERY query = {
+ .PropertyId = StorageDeviceProperty,
+ .QueryType = PropertyStandardQuery,
+ };
+ STORAGE_DEVICE_DESCRIPTOR descriptor;
+ wchar_t path[64];
+ DWORD returned;
+ HANDLE disk;
+
+ swprintf_s(path, ARRAY_SIZE(path), L"\\\\.\\PhysicalDrive%lu",
disk_number);
+ disk = open_existing(path);
+ if (disk == INVALID_HANDLE_VALUE) {
+ return -1;
+ }
+ if (!DeviceIoControl(disk, IOCTL_STORAGE_QUERY_PROPERTY,
+ &query, sizeof(query), &descriptor,
sizeof(descriptor),
+ &returned, NULL) || returned <
offsetof(STORAGE_DEVICE_DESCRIPTOR, RawDeviceProperties)) {
+ CloseHandle(disk);
+ return -1;
+ }
+ CloseHandle(disk);
+ return descriptor.BusType;
+}
+
+static DEVINST disk_devinst(DWORD disk_number) {
+ SP_DEVICE_INTERFACE_DATA interface_data = {0};
+ SP_DEVINFO_DATA devinfo = {0};
+ HDEVINFO devices;
+ DEVINST found = 0;
+
+ devices = SetupDiGetClassDevsW(&disk_interface_guid, NULL, NULL,
+ DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
+ if (devices == INVALID_HANDLE_VALUE) {
+ return 0;
+ }
+ interface_data.cbSize = sizeof(interface_data);
+ for (DWORD index = 0; SetupDiEnumDeviceInterfaces(
+ devices, NULL, &disk_interface_guid, index, &interface_data);
index++) {
+ PSP_DEVICE_INTERFACE_DETAIL_DATA_W detail;
+ STORAGE_DEVICE_NUMBER number;
+ DWORD required = 0;
+ DWORD returned;
+ HANDLE disk;
+
+ SetupDiGetDeviceInterfaceDetailW(devices, &interface_data, NULL, 0,
&required, NULL);
+ if (required < sizeof(*detail) || !(detail = malloc(required))) {
+ continue;
+ }
+ detail->cbSize = sizeof(*detail);
+ devinfo.cbSize = sizeof(devinfo);
+ if (!SetupDiGetDeviceInterfaceDetailW(devices, &interface_data,
detail, required,
+ NULL, &devinfo) ||
+ (disk = open_existing(detail->DevicePath)) ==
INVALID_HANDLE_VALUE) {
+ free(detail);
+ continue;
+ }
+ if (DeviceIoControl(disk, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,
+ &number, sizeof(number), &returned, NULL) &&
+ returned >= sizeof(number) && number.DeviceType ==
FILE_DEVICE_DISK &&
+ number.DeviceNumber == disk_number) {
+ found = devinfo.DevInst;
+ }
+ CloseHandle(disk);
+ free(detail);
+ if (found) {
+ break;
+ }
+ }
+ SetupDiDestroyDeviceInfoList(devices);
+ return found;
+}
+
+static bool pci_property(DEVINST devinst, DWORD pid, DWORD *value) {
+ DEVPROPKEY key = {.fmtid = {0}, .pid = pid};
+ DEVPROPTYPE type;
+ ULONG size = sizeof(*value);
+
+ key.fmtid = pci_property_guid;
+ return CM_Get_DevNode_PropertyW(devinst, &key, &type, (PBYTE)value, &size,
0) == CR_SUCCESS &&
+ type == DEVPROP_TYPE_UINT32 && size == sizeof(*value);
+}
+
+static DEVINST nvme_controller(DEVINST devinst) {
+ for (int depth = 0; depth < 16; depth++) {
+ DWORD base_class;
+ DWORD sub_class;
+ DWORD prog_if;
+ DEVINST parent;
+
+ if (pci_property(devinst, 3, &base_class) && base_class == 0x01 &&
+ pci_property(devinst, 4, &sub_class) && sub_class == 0x08 &&
+ pci_property(devinst, 5, &prog_if) && prog_if == 0x02) {
+ return devinst;
+ }
+ if (CM_Get_Parent(&parent, devinst, 0) != CR_SUCCESS) {
+ break;
+ }
+ devinst = parent;
+ }
+ return 0;
+}
+
+static int physical_disk_fast(DWORD disk_number) {
+ int bus_type = disk_bus_type(disk_number);
+ DEVINST devinst;
+ DEVINST controller;
+ DWORD generation;
+ DWORD width;
+
+ if (bus_type != BUS_TYPE_NVME) {
+ if (bus_type <= BUS_TYPE_UNKNOWN || bus_type >= BUS_TYPE_MAX ||
+ bus_type == BUS_TYPE_USB || bus_type == BUS_TYPE_RAID ||
+ bus_type == BUS_TYPE_ISCSI || bus_type == BUS_TYPE_VIRTUAL ||
+ bus_type == BUS_TYPE_FILE_BACKED_VIRTUAL || bus_type ==
BUS_TYPE_SPACES ||
+ bus_type == BUS_TYPE_NVMEOF) {
+ return AIMDO_DISK_UNKNOWN;
+ }
+ return AIMDO_DISK_SLOW;
+ }
+ devinst = disk_devinst(disk_number);
+ controller = devinst ? nvme_controller(devinst) : 0;
+ if (!controller || !pci_property(controller, 9, &generation) ||
+ !pci_property(controller, 10, &width)) {
+ return AIMDO_DISK_UNKNOWN;
+ }
+ log(DEBUG, "%s: disk=%lu current_gen=%lu current_width=%lu\n",
+ __func__, disk_number, generation, width);
+
+ return ((generation == 3 && width == 4) ||
+ (generation == 4 && width == 4) ||
+ (generation == 5 && (width == 2 || width == 4))) ?
+ AIMDO_DISK_FAST : AIMDO_DISK_SLOW;
+}
+
+static int volume_fast(const wchar_t *volume) {
+ union {
+ VOLUME_DISK_EXTENTS extents;
+ BYTE bytes[32768];
+ } buffer;
+ VOLUME_DISK_EXTENTS *extents = &buffer.extents;
+ wchar_t volume_path[64];
+ DWORD returned;
+ DWORD count;
+ HANDLE handle;
+ int result = AIMDO_DISK_FAST;
+
+ wcscpy_s(volume_path, ARRAY_SIZE(volume_path), volume);
+ volume_path[wcslen(volume_path) - 1] = L'\0';
+ handle = open_existing(volume_path);
+ if (handle == INVALID_HANDLE_VALUE) {
+ return AIMDO_DISK_UNKNOWN;
+ }
+ bool success = DeviceIoControl(handle,
IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
+ NULL, 0, extents, sizeof(buffer),
&returned, NULL);
+ CloseHandle(handle);
+ if (!success || returned < offsetof(VOLUME_DISK_EXTENTS, Extents) ||
+ returned > sizeof(buffer)) {
+ return AIMDO_DISK_UNKNOWN;
+ }
+ count = extents->NumberOfDiskExtents;
+ if (!count || count > (returned - offsetof(VOLUME_DISK_EXTENTS, Extents)) /
+ sizeof(extents->Extents[0])) {
+ return AIMDO_DISK_UNKNOWN;
+ }
+ for (DWORD i = 0; i < count; i++) {
+ int disk_result = physical_disk_fast(extents->Extents[i].DiskNumber);
+
+ if (disk_result == AIMDO_DISK_SLOW) {
+ return AIMDO_DISK_SLOW;
+ }
+ if (disk_result == AIMDO_DISK_UNKNOWN) {
+ result = AIMDO_DISK_UNKNOWN;
+ }
+ }
+ return result;
+}
+
+SHARED_EXPORT
+int aimdo_storage_fast_disk(const wchar_t *path) {
+ wchar_t volume[64];
+
+ if (!path || !get_volume_guid(path, volume)) {
+ return AIMDO_DISK_UNKNOWN;
+ }
+ return volume_fast(volume);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/comfy-aimdo-0.5.3/tests/malloc-graph-overlapping-alias-cuda.py
new/comfy-aimdo-0.5.5/tests/malloc-graph-overlapping-alias-cuda.py
--- old/comfy-aimdo-0.5.3/tests/malloc-graph-overlapping-alias-cuda.py
1970-01-01 01:00:00.000000000 +0100
+++ new/comfy-aimdo-0.5.5/tests/malloc-graph-overlapping-alias-cuda.py
2026-09-15 10:46:20.000000000 +0200
@@ -0,0 +1,36 @@
+# Regression: an alias collision must not skip a usable overlapping window.
+# No model needed; at most 80 MiB of graph pages on the failing implementation.
+import os
+os.environ['PYTORCH_ALLOC_CONF'] = 'backend:cudaMallocAsync'
+import json
+import torch
+import comfy_aimdo.control as aimdo
+
+M = 1024 * 1024
+assert aimdo.init('cuda')
+assert aimdo.init_device(torch.cuda.current_device())
+torch.empty(1, device='cuda')
+graph = aimdo.record(torch.cuda.current_stream())
+a = torch.empty(16 * M, dtype=torch.uint8, device='cuda')
+b = torch.empty(8 * M, dtype=torch.uint8, device='cuda')
+del a
+c = torch.empty(24 * M, dtype=torch.uint8, device='cuda')
+del b, c
+before = graph.virtual_bytes
+assert before == 48 * M, f'Expected the six-page alias layout, got {before}
bytes.'
+d = torch.empty(32 * M, dtype=torch.uint8, device='cuda')
+for page in range(4):
+ d[page * 8 * M:(page + 1) * 8 * M].fill_(37 + page)
+after = graph.virtual_bytes
+physical = graph.physical_bytes
+for page in range(4):
+ assert d[page * 8 * M].item() == 37 + page
+ assert d[(page + 1) * 8 * M - 1].item() == 37 + page
+print(json.dumps({'beforeVirtualBytes': before, 'afterVirtualBytes': after,
+ 'physicalBytes': physical, 'reused': before == after}))
+assert before == after, 'A valid overlapping range was skipped.'
+assert physical == 32 * M, f'Expected 32 MiB of physical pages, got {physical}
bytes.'
+del d
+graph.pop()
+del graph
+aimdo.deinit()