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-10-22 18:24:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-comfy-aimdo (Old)
 and      /work/SRC/openSUSE:Factory/.python-comfy-aimdo.new.2252680 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-comfy-aimdo"

Thu Oct 22 18:24:28 2026 rev:9 rq:1387097 version:0.6.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-comfy-aimdo/python-comfy-aimdo.changes    
2026-10-08 13:28:21.941247449 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-comfy-aimdo.new.2252680/python-comfy-aimdo.changes
       2026-10-22 18:24:31.348275791 +0200
@@ -1,0 +2,21 @@
+Thu Oct 22 11:42:44 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 0.6.0:
+  * VBAR writeback on eviction: the owner now saves whole tensors
+    back to RAM before the pages are unmapped, and keeps them mapped
+    when writeback fails; ModelVBAR gains an optional
+    pageout_callback plus close()
+  * VBAR failures now surface: unpin, set_watermark and free_memory
+    raise on error instead of silently passing (all native paths,
+    which this build does not compile)
+  * New whole-tensor descriptor writes through the transfer queue
+    (host_buffer.write_device_to_file/write_host_to_file wrappers
+    around the native calls)
+  * MCDM adapters now resolve through a DXCore LUID lookup
+    (Windows-only native helper)
+  * torch floor stays >= 2.8.0: the sdist still declares no install
+    dependencies and requires-python is unchanged (>= 3.9)
+- Keep comfy-aimdo-detect-vendor-without-local-version.patch
+  (control.py unchanged, still applies cleanly)
+
+-------------------------------------------------------------------

Old:
----
  comfy-aimdo-0.5.6.tar.gz

New:
----
  comfy-aimdo-0.6.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-comfy-aimdo.spec ++++++
--- /var/tmp/diff_new_pack.K5G4gR/_old  2026-10-22 18:24:32.360316776 +0200
+++ /var/tmp/diff_new_pack.K5G4gR/_new  2026-10-22 18:24:32.362316858 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-comfy-aimdo
-Version:        0.5.6
+Version:        0.6.0
 Release:        0
 Summary:        AI Model Dynamic Offloader for ComfyUI (pure-Python fallback)
 License:        GPL-3.0-only

++++++ comfy-aimdo-0.5.6.tar.gz -> comfy-aimdo-0.6.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/.github/workflows/build-wheels.yml 
new/comfy-aimdo-0.6.0/.github/workflows/build-wheels.yml
--- old/comfy-aimdo-0.5.6/.github/workflows/build-wheels.yml    2026-10-07 
21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/.github/workflows/build-wheels.yml    2026-10-21 
13:56:45.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% ^
-            cfgmgr32.lib dxgi.lib dxguid.lib setupapi.lib detours.lib 
onecore.lib
+            cfgmgr32.lib dxgi.lib dxguid.lib dxcore.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% ^
-            cfgmgr32.lib dxgi.lib dxguid.lib setupapi.lib detours.lib 
onecore.lib
+            cfgmgr32.lib dxgi.lib dxguid.lib dxcore.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.6/comfy_aimdo/host_buffer.py 
new/comfy-aimdo-0.6.0/comfy_aimdo/host_buffer.py
--- old/comfy-aimdo-0.5.6/comfy_aimdo/host_buffer.py    2026-10-07 
21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/comfy_aimdo/host_buffer.py    2026-10-21 
13:56:45.000000000 +0200
@@ -44,6 +44,13 @@
     ]
     lib.hostbuf_file_reader_read.restype = ctypes.c_bool
 
+    lib.hostbuf_file_writer_write.argtypes = [ctypes.c_int, ctypes.c_uint64, 
ctypes.c_uint64,
+                                             ctypes.c_uint64, ctypes.c_void_p, 
ctypes.c_uint64]
+    lib.hostbuf_file_writer_write.restype = ctypes.c_bool
+
+    lib.xfer_file_write.argtypes = [ctypes.c_uint64, ctypes.c_uint64, 
ctypes.c_void_p, ctypes.c_size_t]
+    lib.xfer_file_write.restype = ctypes.c_bool
+
     lib.hostbuf_file_reader_cleanup.argtypes = []
 
     lib.hostbuf_register.argtypes = [ctypes.c_void_p, ctypes.c_uint64, 
ctypes.c_uint64]
@@ -71,6 +78,18 @@
         raise RuntimeError("hostbuf_file_reader_read failed")
 
 
+def write_device_to_file(file_obj, file_offset, size, stream, device_ptr, 
device):
+    """Write through the bounded staging ring; the source stream and file 
writes finish before return."""
+    if not lib.hostbuf_file_writer_write(int(device), _file_handle(file_obj),
+                                         int(file_offset), int(size), 
int(stream) or None, int(device_ptr)):
+        raise RuntimeError("hostbuf_file_writer_write failed")
+
+
+def write_host_to_file(file_obj, file_offset, size, host_ptr):
+    if not lib.xfer_file_write(_file_handle(file_obj), int(file_offset), 
int(host_ptr), int(size)):
+        raise RuntimeError("xfer_file_write failed")
+
+
 def cleanup_file_reader():
     lib.hostbuf_file_reader_cleanup()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/comfy_aimdo/model_vbar.py 
new/comfy-aimdo-0.6.0/comfy_aimdo/model_vbar.py
--- old/comfy-aimdo-0.5.6/comfy_aimdo/model_vbar.py     2026-10-07 
21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/comfy_aimdo/model_vbar.py     2026-10-21 
13:56:45.000000000 +0200
@@ -1,17 +1,20 @@
 import ctypes
+import logging
 
 from . import control
 
 lib = control.lib
+PageoutCallback = ctypes.CFUNCTYPE(ctypes.c_bool, ctypes.c_uint64, 
ctypes.c_uint64)
 
 # Bindings
 if lib is not None:
-    lib.vbar_allocate.argtypes = [ctypes.c_void_p, ctypes.c_uint64, 
ctypes.c_int]
+    lib.vbar_allocate.argtypes = [ctypes.c_void_p, ctypes.c_uint64, 
ctypes.c_int, PageoutCallback]
     lib.vbar_allocate.restype = ctypes.c_void_p
 
     lib.vbar_set_watermark_limit.argtypes = [ctypes.c_void_p, ctypes.c_void_p, 
ctypes.c_uint64]
 
     lib.vbar_set_watermark.argtypes = [ctypes.c_void_p, ctypes.c_void_p, 
ctypes.c_uint64]
+    lib.vbar_set_watermark.restype = ctypes.c_bool
 
     lib.vbars_reset_watermark_limits.argtypes = [ctypes.c_void_p]
 
@@ -28,12 +31,13 @@
     lib.vbar_fault.restype = ctypes.c_int
 
     lib.vbar_unpin.argtypes = [ctypes.c_void_p, ctypes.c_void_p, 
ctypes.c_uint64, ctypes.c_uint64]
+    lib.vbar_unpin.restype = ctypes.c_bool
 
     lib.vbar_loaded_size.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
     lib.vbar_loaded_size.restype = ctypes.c_size_t
 
     lib.vbar_free_memory.argtypes = [ctypes.c_void_p, ctypes.c_void_p, 
ctypes.c_uint64]
-    lib.vbar_free_memory.restype = ctypes.c_uint64
+    lib.vbar_free_memory.restype = ctypes.c_int64
 
     lib.vbars_analyze.argtypes = [ctypes.c_void_p, ctypes.c_bool]
     lib.vbars_analyze.restype = ctypes.c_uint64
@@ -47,9 +51,20 @@
     lib.vbar_get_residency.argtypes = [ctypes.c_void_p, ctypes.c_void_p, 
ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t]
 
 class ModelVBAR:
-    def __init__(self, size, device):
+    def __init__(self, size, device, pageout_callback=None):
+        """The optional callback completes writeback before returning, without 
reentering VBAR APIs."""
+        def invoke(offset, size):
+            try:
+                pageout_callback(offset, size)
+                return True
+            except BaseException:
+                # ctypes cannot unwind a Python exception through the native 
caller.
+                logging.exception("VBAR pageout callback failed")
+                return False
+
+        self._pageout_callback = PageoutCallback(invoke) if pageout_callback 
is not None else PageoutCallback()
         self._devctx = control.get_devctx(device)
-        self._ptr = lib.vbar_allocate(self._devctx, int(size), device)
+        self._ptr = lib.vbar_allocate(self._devctx, int(size), device, 
self._pageout_callback)
         if not self._ptr:
             raise MemoryError("VBAR allocation failed")
         self.device = device
@@ -95,7 +110,8 @@
 
     def unpin(self, alloc, size):
         offset = alloc - self.base_addr
-        lib.vbar_unpin(self._devctx, self._ptr, offset, size)
+        if not lib.vbar_unpin(self._devctx, self._ptr, offset, size):
+            raise RuntimeError("VBAR unpin failed")
 
     def loaded_size(self):
         return lib.vbar_loaded_size(self._devctx, self._ptr)
@@ -104,10 +120,14 @@
         lib.vbar_set_watermark_limit(self._devctx, self._ptr, size_bytes)
 
     def set_watermark(self, size_bytes):
-        lib.vbar_set_watermark(self._devctx, self._ptr, size_bytes)
+        if not lib.vbar_set_watermark(self._devctx, self._ptr, size_bytes):
+            raise RuntimeError("VBAR set_watermark failed")
 
     def free_memory(self, size_bytes):
-        return lib.vbar_free_memory(self._devctx, self._ptr, int(size_bytes))
+        freed = lib.vbar_free_memory(self._devctx, self._ptr, int(size_bytes))
+        if freed < 0:
+            raise RuntimeError("VBAR free_memory failed")
+        return freed
 
     def get_nr_pages(self):
         return lib.vbar_get_nr_pages(self._devctx, self._ptr)
@@ -125,12 +145,16 @@
         lib.vbar_get_residency(self._devctx, self._ptr, buf, nr_pages)
         return list(buf)
 
-    def __del__(self):
+    def close(self):
         ptr = getattr(self, "_ptr", None)
         aimdo_lib = getattr(control, "lib", None)
         if aimdo_lib is not None and ptr:
             aimdo_lib.vbar_free(self._devctx, ptr)
             self._ptr = None
+            self._pageout_callback = None
+
+    def __del__(self):
+        self.close()
 
 def vbar_fault(alloc):
     vbar, offset, size = alloc
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/control.h 
new/comfy-aimdo-0.6.0/src/control.h
--- old/comfy-aimdo-0.5.6/src/control.h 2026-10-07 21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src/control.h 2026-10-21 13:56:45.000000000 +0200
@@ -57,6 +57,7 @@
 #endif
 #if defined(_WIN32) || defined(_WIN64)
     void *_wddm_adapter; /* IDXGIAdapter3* */
+    void *_dxcore_adapter; /* IDXCoreAdapter* */
     uint64_t _wddm_timestamp_last_check;
 #endif
 } AimdoContext;
@@ -96,6 +97,7 @@
 #endif
 #if defined(_WIN32) || defined(_WIN64)
 #define g_wddm_adapter              (*(IDXGIAdapter3 
**)&g_devctx->_wddm_adapter)
+#define g_dxcore_adapter            (*(IDXCoreAdapter 
**)&g_devctx->_dxcore_adapter)
 #define wddm_timestamp_last_check   (g_devctx->_wddm_timestamp_last_check)
 #endif
 #define control_timestamp_last_check (g_devctx->_control_timestamp_last_check)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/gpu_abi.h 
new/comfy-aimdo-0.6.0/src/gpu_abi.h
--- old/comfy-aimdo-0.5.6/src/gpu_abi.h 2026-10-07 21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src/gpu_abi.h 2026-10-21 13:56:45.000000000 +0200
@@ -46,6 +46,7 @@
 typedef enum cudaError_enum {
     CUDA_SUCCESS = 0,
     CUDA_ERROR_OUT_OF_MEMORY = 2,
+    CUDA_ERROR_UNKNOWN = 999,
 } cudaError_enum;
 
 typedef enum CUevent_flags_enum {
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/gpu_dispatch.h 
new/comfy-aimdo-0.6.0/src/gpu_dispatch.h
--- old/comfy-aimdo-0.5.6/src/gpu_dispatch.h    2026-10-07 21:16:15.000000000 
+0200
+++ new/comfy-aimdo-0.6.0/src/gpu_dispatch.h    2026-10-21 13:56:45.000000000 
+0200
@@ -44,6 +44,8 @@
 typedef CUresult (CUDAAPI *PFN_cuMemRelease)(CUmemGenericAllocationHandle 
handle);
 typedef CUresult (CUDAAPI *PFN_cuMemcpyHtoDAsync)(CUdeviceptr dst, const void 
*src,
                                                   size_t bytes, CUstream 
hStream);
+typedef CUresult (CUDAAPI *PFN_cuMemcpyDtoHAsync)(void *dst, CUdeviceptr src,
+                                                  size_t bytes, CUstream 
hStream);
 typedef CUresult (CUDAAPI *PFN_cuEventCreate)(CUevent *phEvent, unsigned int 
flags);
 typedef CUresult (CUDAAPI *PFN_cuEventDestroy)(CUevent hEvent);
 typedef CUresult (CUDAAPI *PFN_cuEventRecord)(CUevent hEvent, CUstream 
hStream);
@@ -81,6 +83,7 @@
     PFN_cuMemUnmap p_cuMemUnmap;
     PFN_cuMemRelease p_cuMemRelease;
     PFN_cuMemcpyHtoDAsync p_cuMemcpyHtoDAsync;
+    PFN_cuMemcpyDtoHAsync p_cuMemcpyDtoHAsync;
     PFN_cuEventCreate p_cuEventCreate;
     PFN_cuEventDestroy p_cuEventDestroy;
     PFN_cuEventRecord p_cuEventRecord;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/hostbuf-file-reader.c 
new/comfy-aimdo-0.6.0/src/hostbuf-file-reader.c
--- old/comfy-aimdo-0.5.6/src/hostbuf-file-reader.c     2026-10-07 
21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src/hostbuf-file-reader.c     2026-10-21 
13:56:45.000000000 +0200
@@ -49,10 +49,9 @@
     return slot;
 }
 
-SHARED_EXPORT
-bool hostbuf_file_reader_read(int device, uint64_t file_handle, uint64_t 
file_offset,
-                              uint64_t size, cudaStream_t stream,
-                              uint64_t device_ptr, bool mark_cold) {
+static bool hostbuf_file_transfer(int device, uint64_t file_handle, uint64_t 
file_offset,
+                                  uint64_t size, cudaStream_t stream,
+                                  uint64_t device_ptr, bool mark_cold, bool 
write) {
     if (size == 0) {
         return true;
     }
@@ -65,7 +64,6 @@
     while (size) {
         HostbufFileReaderSlot *slot = g_devctx->_hostbuf_file_reader_active < 
0 ? NULL :
             
&g_devctx->_hostbuf_file_reader_slots[g_devctx->_hostbuf_file_reader_active];
-        size_t chunk;
 
         if (!slot || slot->stream != (CUstream)stream ||
             (slot->offset + size >= HOSTBUF_FILE_READER_WINDOW &&
@@ -76,24 +74,39 @@
             }
         }
 
-        chunk = (size_t)MIN(size, HOSTBUF_FILE_READER_WINDOW - slot->offset);
-        if (!xfer_file_read(file_handle, file_offset, slot->buffer + 
slot->offset,
-                            chunk, mark_cold)) {
-            log(AIMDO_LOG_ERROR, "%s: file read failed handle=0x%llx 
offset=%llu size=%zu\n",
+        size_t chunk = (size_t)MIN(size, HOSTBUF_FILE_READER_WINDOW - 
slot->offset);
+        void *buffer = slot->buffer + slot->offset;
+        slot->offset += chunk;
+
+        if (!write && !xfer_file_read(file_handle, file_offset, buffer, chunk, 
mark_cold)) {
+            log(AIMDO_LOG_ERROR, "%s: xfer_file_read failed handle=0x%llx 
offset=%llu size=%zu\n",
                 __func__, (ull)file_handle, (ull)file_offset, chunk);
             return false;
         }
-        CUresult copy_result = cuMemcpyHtoDAsync((CUdeviceptr)device_ptr,
-                                                 slot->buffer + slot->offset,
-                                                 chunk, (CUstream)stream);
+
+        CUresult copy_result = write ? cuMemcpyDtoHAsync(buffer, 
(CUdeviceptr)device_ptr, chunk, (CUstream)stream) :
+                                      
cuMemcpyHtoDAsync((CUdeviceptr)device_ptr, buffer, chunk, (CUstream)stream);
         if (!CHECK_CU(copy_result)) {
-            log(AIMDO_LOG_ERROR, "%s: device copy failed result=%d 
device_ptr=%p device=%d stream=%p size=%zu\n",
-                __func__, (int)copy_result, (void *)(uintptr_t)device_ptr, 
device,
-                (void *)stream, chunk);
+            log(AIMDO_LOG_ERROR, "%s: %s failed result=%d device_ptr=%p 
device=%d stream=%p size=%zu\n",
+                __func__, write ? "cuMemcpyDtoHAsync" : "cuMemcpyHtoDAsync", 
(int)copy_result,
+                (void *)(uintptr_t)device_ptr, device, (void *)stream, chunk);
             return false;
         }
 
-        slot->offset += chunk;
+        if (write) {
+            CUresult sync_result = cuCtxSynchronize();
+            if (!CHECK_CU(sync_result)) {
+                log(AIMDO_LOG_ERROR, "%s: cuCtxSynchronize failed result=%d 
device_ptr=%p device=%d stream=%p size=%zu\n",
+                    __func__, (int)sync_result, (void *)(uintptr_t)device_ptr, 
device, (void *)stream, chunk);
+                return false;
+            }
+            if (!xfer_file_write(file_handle, file_offset, buffer, chunk)) {
+                log(AIMDO_LOG_ERROR, "%s: xfer_file_write failed handle=0x%llx 
offset=%llu size=%zu\n",
+                    __func__, (ull)file_handle, (ull)file_offset, chunk);
+                return false;
+            }
+        }
+
         file_offset += chunk;
         device_ptr += chunk;
         size -= chunk;
@@ -103,6 +116,19 @@
 }
 
 SHARED_EXPORT
+bool hostbuf_file_reader_read(int device, uint64_t file_handle, uint64_t 
file_offset,
+                              uint64_t size, cudaStream_t stream,
+                              uint64_t device_ptr, bool mark_cold) {
+    return hostbuf_file_transfer(device, file_handle, file_offset, size, 
stream, device_ptr, mark_cold, false);
+}
+
+SHARED_EXPORT
+bool hostbuf_file_writer_write(int device, uint64_t file_handle, uint64_t 
file_offset,
+                               uint64_t size, cudaStream_t stream, uint64_t 
device_ptr) {
+    return hostbuf_file_transfer(device, file_handle, file_offset, size, 
stream, device_ptr, false, true);
+}
+
+SHARED_EXPORT
 void hostbuf_file_reader_cleanup(void) {
     if (!g_devctx) {
         return;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/malloc-graph.c 
new/comfy-aimdo-0.6.0/src/malloc-graph.c
--- old/comfy-aimdo-0.5.6/src/malloc-graph.c    2026-10-07 21:16:15.000000000 
+0200
+++ new/comfy-aimdo-0.6.0/src/malloc-graph.c    2026-10-21 13:56:45.000000000 
+0200
@@ -423,10 +423,14 @@
 static CUresult create_page(MallocGraph *g, PhysicalPage **page) {
     CUresult r;
 
-    vbars_free(budget_deficit(MG_PAGE));
+    if (!vbars_free(budget_deficit(MG_PAGE))) {
+        return CUDA_ERROR_UNKNOWN;
+    }
     r = physical_page_alloc(page, MG_PAGE, g->device);
     if (r == CUDA_ERROR_OUT_OF_MEMORY) {
-        vbars_free(MG_PAGE);
+        if (!vbars_free(MG_PAGE)) {
+            return CUDA_ERROR_UNKNOWN;
+        }
         r = physical_page_alloc(page, MG_PAGE, g->device);
     }
     return r;
@@ -861,13 +865,14 @@
     return offset;
 }
 
-bool malloc_graph_alloc(CUdeviceptr *ptr, size_t size, CUstream stream) {
+bool malloc_graph_alloc(CUdeviceptr *ptr, size_t size, CUstream stream, int 
*result) {
     MallocGraph *g = active_graph;
 
     if (!g || graph_failed(g) || g->paused || stream != g->stream) {
         return false;
     }
 
+    *result = CUDA_ERROR_OUT_OF_MEMORY;
     *ptr = 0;
     size_t va = 0;
 
@@ -879,6 +884,7 @@
             *ptr = type == EV_ALLOC_SMALL
                 ? virtual_range_get(g->small_base) + match->value
                 : virtual_range_get(g->base) + match->value * MG_PAGE;
+            *result = CUDA_SUCCESS;
             return true;
         }
         RETURN_G_FAILED(g->failed, true);
@@ -902,7 +908,11 @@
                 PhysicalPage **page = &g->small_physical_pages[g->small_pages];
                 CUdeviceptr addr = virtual_range_get(g->small_base) + 
g->small_pages * MG_PAGE;
                 CUresult r = create_page(g, page);
-                RETURN_G_FAILED(r, true);
+                if (r) {
+                    g->failed = true;
+                    *result = r;
+                    return true;
+                }
                 g->small_pages++;
                 RETURN_G_FAILED(map_reference(g, addr, *page,
                                 &g->small_mapped_pages[g->small_pages - 1]), 
true);
@@ -922,6 +932,7 @@
         track_allocation(g, allocation);
         add_used(g, bytes);
         *ptr = virtual_range_get(g->small_base) + offset;
+        *result = CUDA_SUCCESS;
         return true;
     }
 
@@ -967,7 +978,12 @@
                    (g->allocations.physical_live[p] || rogue_phys(g, p))) {
                 p++;
             }
-            RETURN_G_FAILED(map_page(g, va + j, p), true);
+            CUresult r = map_page(g, va + j, p);
+            if (r) {
+                g->failed = true;
+                *result = r;
+                return true;
+            }
         }
         g->allocations.physical_live[g->va_phys[va + j]] = true;
     }
@@ -979,6 +995,7 @@
     track_allocation(g, allocation);
     add_used(g, pages * MG_PAGE);
     *ptr = virtual_range_get(g->base) + va * MG_PAGE;
+    *result = CUDA_SUCCESS;
     return true;
 }
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/model-vbar.c 
new/comfy-aimdo-0.6.0/src/model-vbar.c
--- old/comfy-aimdo-0.5.6/src/model-vbar.c      2026-10-07 21:16:15.000000000 
+0200
+++ new/comfy-aimdo-0.6.0/src/model-vbar.c      2026-10-21 13:56:45.000000000 
+0200
@@ -23,6 +23,8 @@
     void *lower;
 
     size_t resident_count;
+    uint64_t pageout_size;
+    bool (*pageout_callback)(uint64_t offset, uint64_t size);
 
     ResidentPage residency_map[1]; /* Must be last! */
 } ModelVBAR;
@@ -91,12 +93,25 @@
     return (uint64_t)calculated_total_vram;
 }
 
-static inline bool mod1(ModelVBAR *mv, size_t page_nr, bool do_free, bool 
do_unpin) {
+/* -1: writeback failed, 0: retained, 1: freed. */
+static inline int mod1(ModelVBAR *mv, size_t page_nr, bool do_free, bool 
do_unpin) {
     ResidentPage *rp = &mv->residency_map[page_nr];
     CUdeviceptr vaddr = mv->vbar + page_nr * VBAR_PAGE_SIZE;
 
     do_free = do_free && rp->handle && (do_unpin || rp->pin_count == 0);
     if (do_free) {
+        if (mv->pageout_callback) {
+            uint64_t offset = page_nr * VBAR_PAGE_SIZE;
+            size_t size = MIN(VBAR_PAGE_SIZE, mv->pageout_size - offset);
+            /* Also cover eviction while malloc-graph synchronization is 
paused. */
+            if (!CHECK_CU(cuCtxSynchronize())) {
+                return -1;
+            }
+            /* The owner saves complete tensors intersecting this range before 
unmapping. */
+            if (!mv->pageout_callback(offset, size)) {
+                return -1;
+            }
+        }
         CHECK_CU(cuMemUnmap(vaddr, VBAR_PAGE_SIZE));
         unmap_workaround(vaddr, VBAR_PAGE_SIZE);
         CHECK_CU(cuMemRelease(rp->handle));
@@ -110,7 +125,7 @@
     return do_free;
 }
 
-size_t vbars_free(ssize_t size) {
+bool vbars_free(ssize_t size) {
     size_t pages_needed;
     bool dirty = false;
     bool sync = !malloc_graph_sync_paused();
@@ -119,7 +134,7 @@
     vbars_dirty = true;
 
     if (size <= 0) {
-        return 0;
+        return true;
     }
 
     pages_needed = VBAR_GET_PAGE_NR_UP((size_t)size);
@@ -136,9 +151,11 @@
                 }
                 dirty = true;
             }
-            if (mod1(i, i->watermark - 1, true, false)) {
-                pages_needed--;
+            int freed = mod1(i, i->watermark - 1, true, false);
+            if (freed < 0) {
+                return false;
             }
+            pages_needed -= freed;
         }
     }
 
@@ -146,7 +163,7 @@
         CHECK_CU(cuCtxSynchronize());
     }
 
-    return pages_needed;
+    return true;
 }
 
 static inline size_t move_cursor_to_absent(ModelVBAR *mv, size_t cursor) {
@@ -165,7 +182,7 @@
     return cursor;
 }
 
-static void vbars_free_for_vbar(ModelVBAR *mv, size_t target, ssize_t surplus) 
{
+static bool vbars_free_for_vbar(ModelVBAR *mv, size_t target, ssize_t surplus) 
{
     size_t cursor = move_cursor_to_absent(mv, 0);
     bool synced = false;
 
@@ -183,7 +200,11 @@
                 CHECK_CU(cuCtxSynchronize());
                 synced = true;
             }
-            if (mod1(i, i->watermark - 1, true, false)) {
+            int freed = mod1(i, i->watermark - 1, true, false);
+            if (freed < 0) {
+                return false;
+            }
+            if (freed) {
                 surplus += (ssize_t)VBAR_PAGE_SIZE;
                 cursor = spend_surplus_on_cursor(mv, target, cursor, &surplus);
             }
@@ -193,6 +214,7 @@
     if (synced) {
         CHECK_CU(cuCtxSynchronize());
     }
+    return true;
 }
 
 static inline void remove_vbar(ModelVBAR *mv) {
@@ -214,8 +236,10 @@
     lowest_priority.higher = mv;
 }
 
+/* The optional callback completes writeback before returning. No VBAR 
reentry. */
 SHARED_EXPORT
-void *vbar_allocate(void *devctx, uint64_t size, int device) {
+void *vbar_allocate(void *devctx, uint64_t size, int device,
+                    bool (*pageout_callback)(uint64_t, uint64_t)) {
     ModelVBAR *mv;
 
     set_devctx((AimdoContext *)devctx);
@@ -230,13 +254,16 @@
     if (nr_pages_max < nr_pages) {
         nr_pages = nr_pages_max;
     }
-    size = (uint64_t)nr_pages * VBAR_PAGE_SIZE;
 
     if (!(mv = calloc(1, sizeof(*mv) + nr_pages * 
sizeof(mv->residency_map[0])))) {
         log(CRITICAL, "Host OOM\n");
         return NULL;
     }
 
+    mv->pageout_size = size;
+    mv->pageout_callback = pageout_callback;
+    size = (uint64_t)nr_pages * VBAR_PAGE_SIZE;
+
     /* FIXME: Do I care about alignment? Does Cuda just look after itself? */
     if (!CHECK_CU(cuMemAddressReserve(&mv->vbar, size, 0, 0, 0))) {
         log(AIMDO_LOG_ERROR, "Could not reseve Virtual Address space for 
VBAR\n");
@@ -264,7 +291,7 @@
 }
 
 SHARED_EXPORT
-void vbar_set_watermark(void *devctx, void *vbar, uint64_t size) {
+bool vbar_set_watermark(void *devctx, void *vbar, uint64_t size) {
     ModelVBAR *mv = (ModelVBAR *)vbar;
     size_t watermark = VBAR_GET_PAGE_NR_UP(size);
 
@@ -280,11 +307,14 @@
     if (watermark < mv->watermark) {
         CHECK_CU(cuCtxSynchronize());
         for (size_t page_nr = watermark; page_nr < mv->watermark; page_nr++) {
-            mod1(mv, page_nr, true, false);
+            if (mod1(mv, page_nr, true, false) < 0) {
+                return false;
+            }
         }
     }
 
     mv->watermark = watermark;
+    return true;
 }
 
 SHARED_EXPORT
@@ -360,7 +390,9 @@
      * as the allocator is unreliable as it may not actually be called 
reliably when you
      * really need to know you have spilled.
      */
-    vbars_free(budget_deficit(0));
+    if (!vbars_free(budget_deficit(0))) {
+        return VBAR_FAULT_ERROR;
+    }
 
     if (page_end > mv->watermark) {
         log(VVERBOSE, "VBAR Allocation is above watermark\n");
@@ -378,11 +410,13 @@
         }
 
         if (!miss_alloc_checked) {
-            vbars_free_for_vbar(mv, page_end,
+            if (!vbars_free_for_vbar(mv, page_end,
                                 (ssize_t)vram_capacity -
                                 ((ssize_t)(total_vram_usage +
                                            (page_end - page_nr) * 
VBAR_PAGE_SIZE) +
-                                 (ssize_t)simple_vram_headroom));
+                                 (ssize_t)simple_vram_headroom))) {
+                return VBAR_FAULT_ERROR;
+            }
             miss_alloc_checked = true;
 
             if (page_end > mv->watermark) {
@@ -400,7 +434,9 @@
                 return VBAR_FAULT_ERROR;
             }
             log(DEBUG, "VBAR allocator attempt exceeds available VRAM ...\n");
-            vbars_free(VBAR_PAGE_SIZE);
+            if (!vbars_free(VBAR_PAGE_SIZE)) {
+                return VBAR_FAULT_ERROR;
+            }
             if (page_end > mv->watermark) {
                 log(DEBUG, "VBAR allocation cancelled due to backup-free 
watermark reduction\n");
                 return VBAR_FAULT_OOM;
@@ -427,7 +463,7 @@
 }
 
 SHARED_EXPORT
-void vbar_unpin(void *devctx, void *vbar, uint64_t offset, uint64_t size) {
+bool vbar_unpin(void *devctx, void *vbar, uint64_t offset, uint64_t size) {
     ModelVBAR *mv = (ModelVBAR *)vbar;
 
     set_devctx((AimdoContext *)devctx);
@@ -445,13 +481,18 @@
         if (rp->pin_count) {
             rp->pin_count--;
         }
-        mod1(mv, page_nr, page_nr >= mv->watermark, false);
+    }
+    /* Release every consumer pin even if a subsequent writeback fails. */
+    for (uint64_t page_nr = VBAR_GET_PAGE_NR(offset); page_nr < page_end && 
page_nr < mv->nr_pages; page_nr++) {
+        if (mod1(mv, page_nr, page_nr >= mv->watermark, false) < 0) {
+            return false;
+        }
     }
 
     if (page_end > mv->watermark) {
         CHECK_CU(cuCtxSynchronize());
     }
-
+    return true;
 }
 
 SHARED_EXPORT
@@ -465,6 +506,7 @@
 
     CHECK_CU(cuCtxSynchronize());
 
+    mv->pageout_callback = NULL; /* Destruction discards rather than pages 
out. */
     for (uint64_t page_nr = 0; page_nr < mv->nr_pages; page_nr++) {
         mod1(mv, page_nr, true, true);
     }
@@ -511,7 +553,7 @@
 }
 
 SHARED_EXPORT
-uint64_t vbar_free_memory(void *devctx, void *vbar, uint64_t size) {
+int64_t vbar_free_memory(void *devctx, void *vbar, uint64_t size) {
     ModelVBAR *mv = (ModelVBAR *)vbar;
     size_t pages_to_free = VBAR_GET_PAGE_NR_UP(size);
     size_t pages_freed = 0;
@@ -527,13 +569,15 @@
         /* In theory we should never have pins here, but
          * respect pins if it really comes up.
          */
-        if (mod1(mv, mv->watermark - 1, true, false)) {
-            pages_to_free--;
-            pages_freed++;
+        int freed = mod1(mv, mv->watermark - 1, true, false);
+        if (freed < 0) {
+            return -1;
         }
+        pages_to_free -= freed;
+        pages_freed += freed;
     }
 
     CHECK_CU(cuCtxSynchronize());
 
-    return (uint64_t)pages_freed * VBAR_PAGE_SIZE;
+    return (int64_t)pages_freed * VBAR_PAGE_SIZE;
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/plat.h 
new/comfy-aimdo-0.6.0/src/plat.h
--- old/comfy-aimdo-0.5.6/src/plat.h    2026-10-07 21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src/plat.h    2026-10-21 13:56:45.000000000 +0200
@@ -90,6 +90,7 @@
 #define cuMemUnmap                  g_cuda.p_cuMemUnmap
 #define cuMemRelease                g_cuda.p_cuMemRelease
 #define cuMemcpyHtoDAsync           g_cuda.p_cuMemcpyHtoDAsync
+#define cuMemcpyDtoHAsync           g_cuda.p_cuMemcpyDtoHAsync
 #define cuEventCreate               g_cuda.p_cuEventCreate
 #define cuEventDestroy              g_cuda.p_cuEventDestroy
 #define cuEventRecord               g_cuda.p_cuEventRecord
@@ -231,7 +232,7 @@
 #endif
 
 /* model_vbar.c */
-size_t vbars_free(ssize_t size);
+bool vbars_free(ssize_t size);
 SHARED_EXPORT
 uint64_t vbars_analyze(void *devctx, bool only_dirty);
 
@@ -246,7 +247,7 @@
 int aimdo_cuda_free_async(CUdeviceptr devPtr, CUstream hStream,
                           CUresult (*true_cuMemFreeAsync)(CUdeviceptr, 
CUstream));
 
-bool malloc_graph_alloc(CUdeviceptr *ptr, size_t size, CUstream stream);
+bool malloc_graph_alloc(CUdeviceptr *ptr, size_t size, CUstream stream, int 
*result);
 bool malloc_graph_free(CUdeviceptr ptr, CUstream stream, int *result);
 bool malloc_graph_sync_paused(void);
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/pyt-cu-plug-alloc-async.c 
new/comfy-aimdo-0.6.0/src/pyt-cu-plug-alloc-async.c
--- old/comfy-aimdo-0.5.6/src/pyt-cu-plug-alloc-async.c 2026-10-07 
21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src/pyt-cu-plug-alloc-async.c 2026-10-21 
13:56:45.000000000 +0200
@@ -164,7 +164,10 @@
         return true_cuMemAlloc_v2(devPtr, size);
     }
 
-    vbars_free(budget_deficit(size + CUDA_MALLOC_HEADROOM));
+    *devPtr = 0;
+    if (!vbars_free(budget_deficit(size + CUDA_MALLOC_HEADROOM))) {
+        return CUDA_ERROR_UNKNOWN;
+    }
 
     if (CHECK_CU(true_cuMemAlloc_v2(&dptr, size))) {
         *devPtr = dptr;
@@ -172,7 +175,9 @@
         return 0;
     }
 
-    vbars_free(size + CUDA_MALLOC_HEADROOM);
+    if (!vbars_free(size + CUDA_MALLOC_HEADROOM)) {
+        return CUDA_ERROR_UNKNOWN;
+    }
     status = true_cuMemAlloc_v2(&dptr, size);
     if (CHECK_CU(status)) {
         *devPtr = dptr;
@@ -223,18 +228,23 @@
     if (!set_devctx_for_current_cuda_device()) {
         return true_cuMemAllocAsync(devPtr, size, hStream);
     }
-    if (malloc_graph_alloc(devPtr, size, hStream)) {
-        return *devPtr ? 0 : CUDA_ERROR_OUT_OF_MEMORY;
+    if (malloc_graph_alloc(devPtr, size, hStream, &status)) {
+        return status;
     }
 
-    vbars_free(budget_deficit(MIN(size, malloc_async_clamp)));
+    *devPtr = 0;
+    if (!vbars_free(budget_deficit(MIN(size, malloc_async_clamp)))) {
+        return CUDA_ERROR_UNKNOWN;
+    }
 
     status = true_cuMemAllocAsync(&dptr, size, hStream);
     if (CHECK_CU(status)) {
         *devPtr = dptr;
         goto success;
     }
-    vbars_free(size);
+    if (!vbars_free(size)) {
+        return CUDA_ERROR_UNKNOWN;
+    }
     status = true_cuMemAllocAsync(&dptr, size, hStream);
     if (CHECK_CU(status)) {
         *devPtr = dptr;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/vrambuf.c 
new/comfy-aimdo-0.6.0/src/vrambuf.c
--- old/comfy-aimdo-0.5.6/src/vrambuf.c 2026-10-07 21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src/vrambuf.c 2026-10-21 13:56:45.000000000 +0200
@@ -109,7 +109,9 @@
         grow_to = buf->max_size;
     }
 
-    vbars_free(budget_deficit(grow_to - buf->allocated));
+    if (!vbars_free(budget_deficit(grow_to - buf->allocated))) {
+        return false;
+    }
     while (buf->allocated < grow_to) {
         size_t to_allocate = grow_to - buf->allocated;
         if (to_allocate > VRAM_CHUNK_SIZE) {
@@ -121,7 +123,9 @@
                 return false;
             }
             log(DEBUG, "Pytorch allocator attempt exceeds available VRAM 
...\n");
-            vbars_free(VRAM_CHUNK_SIZE);
+            if (!vbars_free(VRAM_CHUNK_SIZE)) {
+                return false;
+            }
             if ((err = three_stooges(buf->base_ptr + buf->allocated, 
to_allocate, buf->device, &handle)) != CUDA_SUCCESS) {
                 bool is_oom = err == CUDA_ERROR_OUT_OF_MEMORY;
                 log(is_oom ? INFO : AIMDO_LOG_ERROR, "VRAM Allocation failed 
(%s)\n", is_oom ? "OOM" : "error");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/xfer-file.c 
new/comfy-aimdo-0.6.0/src/xfer-file.c
--- old/comfy-aimdo-0.5.6/src/xfer-file.c       2026-10-07 21:16:15.000000000 
+0200
+++ new/comfy-aimdo-0.6.0/src/xfer-file.c       2026-10-21 13:56:45.000000000 
+0200
@@ -19,6 +19,7 @@
     uint8_t *destination;
     size_t size;
     bool mark_cold;
+    bool write;
     XferFileWait *wait;
 } XferFileTask;
 
@@ -62,8 +63,8 @@
 
     (void)arg;
     while (xfer_file_task_pop(&g_xfer_file_reader, &task)) {
-        ok = xfer_file_read_at(task.file_handle, task.offset, task.destination,
-                               task.size, task.mark_cold);
+        ok = task.write ? xfer_file_write_at(task.file_handle, task.offset, 
task.destination, task.size) :
+                          xfer_file_read_at(task.file_handle, task.offset, 
task.destination, task.size, task.mark_cold);
         mutex_lock(task.wait->mutex);
         task.wait->failed = !ok || task.wait->failed;
         if (--task.wait->pending == 0) {
@@ -74,8 +75,8 @@
     return 0;
 }
 
-bool xfer_file_read(XferFileHandle file_handle, uint64_t offset, void 
*destination,
-                    size_t size, bool mark_cold) {
+static bool xfer_file_transfer(XferFileHandle file_handle, uint64_t offset, 
void *destination,
+                               size_t size, bool mark_cold, bool write) {
     XferFileWait wait = {
         .mutex = mutex_create(),
         .condvar = condvar_create(),
@@ -94,6 +95,7 @@
             .destination = (uint8_t *)destination + done,
             .size = MIN(XFER_FILE_CHUNK_SIZE, size - done),
             .mark_cold = mark_cold,
+            .write = write,
             .wait = &wait,
         };
 
@@ -123,6 +125,16 @@
     return ok;
 }
 
+bool xfer_file_read(XferFileHandle file_handle, uint64_t offset, void 
*destination,
+                    size_t size, bool mark_cold) {
+    return xfer_file_transfer(file_handle, offset, destination, size, 
mark_cold, false);
+}
+
+SHARED_EXPORT
+bool xfer_file_write(XferFileHandle file_handle, uint64_t offset, void 
*source, size_t size) {
+    return xfer_file_transfer(file_handle, offset, source, size, false, true);
+}
+
 bool xfer_file_init(void) {
     memset(&g_xfer_file_reader, 0, sizeof(g_xfer_file_reader));
     g_xfer_file_reader.mutex = mutex_create();
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src/xfer-file.h 
new/comfy-aimdo-0.6.0/src/xfer-file.h
--- old/comfy-aimdo-0.5.6/src/xfer-file.h       2026-10-07 21:16:15.000000000 
+0200
+++ new/comfy-aimdo-0.6.0/src/xfer-file.h       2026-10-21 13:56:45.000000000 
+0200
@@ -10,5 +10,10 @@
 void xfer_file_cleanup(void);
 bool xfer_file_read(XferFileHandle file_handle, uint64_t offset, void 
*destination,
                     size_t size, bool mark_cold);
+SHARED_EXPORT
+bool xfer_file_write(XferFileHandle file_handle, uint64_t offset, void *source,
+                     size_t size);
 bool xfer_file_read_at(XferFileHandle file_handle, uint64_t offset, void 
*destination,
                        size_t size, bool mark_cold);
+bool xfer_file_write_at(XferFileHandle file_handle, uint64_t offset, void 
*source,
+                        size_t size);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src-cuda/dispatch.c 
new/comfy-aimdo-0.6.0/src-cuda/dispatch.c
--- old/comfy-aimdo-0.5.6/src-cuda/dispatch.c   2026-10-07 21:16:15.000000000 
+0200
+++ new/comfy-aimdo-0.6.0/src-cuda/dispatch.c   2026-10-21 13:56:45.000000000 
+0200
@@ -77,6 +77,7 @@
     { (void **)&g_cuda.p_cuMemUnmap, "cuMemUnmap", 
CU_GET_PROC_ADDRESS_LEGACY_STREAM },
     { (void **)&g_cuda.p_cuMemRelease, "cuMemRelease", 
CU_GET_PROC_ADDRESS_LEGACY_STREAM },
     { (void **)&g_cuda.p_cuMemcpyHtoDAsync, "cuMemcpyHtoDAsync", 
CU_GET_PROC_ADDRESS_LEGACY_STREAM },
+    { (void **)&g_cuda.p_cuMemcpyDtoHAsync, "cuMemcpyDtoHAsync", 
CU_GET_PROC_ADDRESS_LEGACY_STREAM },
     { (void **)&g_cuda.p_cuEventCreate, "cuEventCreate", 
CU_GET_PROC_ADDRESS_LEGACY_STREAM },
     { (void **)&g_cuda.p_cuEventDestroy, "cuEventDestroy", 
CU_GET_PROC_ADDRESS_LEGACY_STREAM },
     { (void **)&g_cuda.p_cuEventRecord, "cuEventRecord", 
CU_GET_PROC_ADDRESS_LEGACY_STREAM },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src-hip/dispatch.c 
new/comfy-aimdo-0.6.0/src-hip/dispatch.c
--- old/comfy-aimdo-0.5.6/src-hip/dispatch.c    2026-10-07 21:16:15.000000000 
+0200
+++ new/comfy-aimdo-0.6.0/src-hip/dispatch.c    2026-10-21 13:56:45.000000000 
+0200
@@ -58,6 +58,7 @@
     { (void **)&g_cuda.p_cuMemUnmap, "hipMemUnmap" },
     { (void **)&g_cuda.p_cuMemRelease, "hipMemRelease" },
     { (void **)&g_cuda.p_cuMemcpyHtoDAsync, "hipMemcpyHtoDAsync" },
+    { (void **)&g_cuda.p_cuMemcpyDtoHAsync, "hipMemcpyDtoHAsync" },
     { (void **)&g_cuda.p_cuEventCreate, "hipEventCreateWithFlags" },
     { (void **)&g_cuda.p_cuEventDestroy, "hipEventDestroy" },
     { (void **)&g_cuda.p_cuEventRecord, "hipEventRecord" },
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src-posix/xfer-file-plat.c 
new/comfy-aimdo-0.6.0/src-posix/xfer-file-plat.c
--- old/comfy-aimdo-0.5.6/src-posix/xfer-file-plat.c    2026-10-07 
21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src-posix/xfer-file-plat.c    2026-10-21 
13:56:45.000000000 +0200
@@ -7,18 +7,21 @@
 #include <fcntl.h>
 #include <unistd.h>
 
-bool xfer_file_read_at(XferFileHandle file_handle, uint64_t offset, void 
*destination,
-                       size_t size, bool mark_cold) {
+static bool xfer_file_transfer_at(XferFileHandle file_handle, uint64_t offset, 
void *buffer,
+                                  size_t size, bool mark_cold, bool write) {
     int fd = (int)file_handle;
     size_t done = 0;
 
     while (done < size) {
-        ssize_t n = pread(fd, (char *)destination + done, size - done,
-                          (off_t)(offset + done));
+        ssize_t n = write ? pwrite(fd, (char *)buffer + done, size - done, 
(off_t)(offset + done)) :
+                            pread(fd, (char *)buffer + done, size - done, 
(off_t)(offset + done));
 
+        if (n < 0 && errno == EINTR) {
+            continue;
+        }
         if (n <= 0) {
-            log(AIMDO_LOG_ERROR, "%s: pread failed at %llu (errno=%d)\n", 
__func__,
-                (ull)(offset + done), errno);
+            log(AIMDO_LOG_ERROR, "%s: %s failed at %llu (errno=%d)\n", 
__func__,
+                write ? "pwrite" : "pread", (ull)(offset + done), errno);
             return false;
         }
         if (mark_cold) {
@@ -34,3 +37,12 @@
 
     return true;
 }
+
+bool xfer_file_read_at(XferFileHandle file_handle, uint64_t offset, void 
*destination,
+                       size_t size, bool mark_cold) {
+    return xfer_file_transfer_at(file_handle, offset, destination, size, 
mark_cold, false);
+}
+
+bool xfer_file_write_at(XferFileHandle file_handle, uint64_t offset, void 
*source, size_t size) {
+    return xfer_file_transfer_at(file_handle, offset, source, size, false, 
true);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src-win/dxcore_c.h 
new/comfy-aimdo-0.6.0/src-win/dxcore_c.h
--- old/comfy-aimdo-0.5.6/src-win/dxcore_c.h    1970-01-01 01:00:00.000000000 
+0100
+++ new/comfy-aimdo-0.6.0/src-win/dxcore_c.h    2026-10-21 13:56:45.000000000 
+0200
@@ -0,0 +1,93 @@
+#ifndef _DXCORE_MINIMAL_C_H_
+#define _DXCORE_MINIMAL_C_H_
+
+#include <windows.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <initguid.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+DEFINE_GUID(IID_IDXCoreAdapterFactory, 0x78ee5945, 0xc36e, 0x4b13, 0xa6, 0x69, 
0x00, 0x5d, 0xd1, 0x1c, 0x0f, 0x06);
+DEFINE_GUID(IID_IDXCoreAdapter, 0xf0db4c7f, 0xfe5a, 0x42a2, 0xbd, 0x62, 0xf2, 
0xa6, 0xcf, 0x6f, 0xc8, 0x3e);
+
+typedef enum DXCoreAdapterProperty {
+    DXCoreAdapterProperty_DriverDescription = 2
+} DXCoreAdapterProperty;
+
+typedef enum DXCoreAdapterState {
+    DXCoreAdapterState_AdapterMemoryBudget = 1
+} DXCoreAdapterState;
+
+typedef enum DXCoreSegmentGroup {
+    DXCoreSegmentGroup_Local = 0,
+    DXCoreSegmentGroup_NonLocal = 1
+} DXCoreSegmentGroup;
+
+typedef struct DXCoreAdapterMemoryBudgetNodeSegmentGroup {
+    uint32_t nodeIndex;
+    DXCoreSegmentGroup segmentGroup;
+} DXCoreAdapterMemoryBudgetNodeSegmentGroup;
+
+typedef struct DXCoreAdapterMemoryBudget {
+    uint64_t budget;
+    uint64_t currentUsage;
+    uint64_t availableForReservation;
+    uint64_t currentReservation;
+} DXCoreAdapterMemoryBudget;
+
+typedef struct IDXCoreAdapter IDXCoreAdapter;
+typedef struct IDXCoreAdapterFactory IDXCoreAdapterFactory;
+
+/* Ask your agent to define extra functions if needed.
+ * We maintain this form to inventory what is used.
+ * Unused slots retain their names and ABI positions as void pointers.
+ */
+/* --- IDXCoreAdapter --- */
+typedef struct IDXCoreAdapterVtbl {
+    // IUnknown methods
+    void *QueryInterface;
+    void *AddRef;
+    ULONG(STDMETHODCALLTYPE* Release)(IDXCoreAdapter* This);
+
+    // IDXCoreAdapter methods
+    void *IsValid;
+    void *IsAttributeSupported;
+    void *IsPropertySupported;
+    HRESULT(STDMETHODCALLTYPE* GetProperty)(IDXCoreAdapter* This, 
DXCoreAdapterProperty property, size_t bufferSize, void* propertyData);
+    void *GetPropertySize;
+    void *IsQueryStateSupported;
+    HRESULT(STDMETHODCALLTYPE* QueryState)(IDXCoreAdapter* This, 
DXCoreAdapterState state, size_t inputStateDetailsSize, const void* 
inputStateDetails, size_t outputBufferSize, void* outputBuffer);
+    void *IsSetStateSupported;
+    void *SetState;
+    void *GetFactory;
+} IDXCoreAdapterVtbl;
+
+struct IDXCoreAdapter { IDXCoreAdapterVtbl* lpVtbl; };
+
+/* --- IDXCoreAdapterFactory --- */
+typedef struct IDXCoreAdapterFactoryVtbl {
+    // IUnknown
+    void *QueryInterface;
+    void *AddRef;
+    ULONG(STDMETHODCALLTYPE* Release)(IDXCoreAdapterFactory* This);
+
+    // IDXCoreAdapterFactory
+    void *CreateAdapterList;
+    HRESULT(STDMETHODCALLTYPE* GetAdapterByLuid)(IDXCoreAdapterFactory* This, 
const LUID* adapterLUID, REFIID riid, void** ppvAdapter);
+    void *IsNotificationTypeSupported;
+    void *RegisterEventNotification;
+    void *UnregisterEventNotification;
+} IDXCoreAdapterFactoryVtbl;
+
+struct IDXCoreAdapterFactory { IDXCoreAdapterFactoryVtbl* lpVtbl; };
+
+STDAPI DXCoreCreateAdapterFactory(REFIID riid, void** ppvFactory);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // _DXCORE_MINIMAL_C_H_
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src-win/shmem-detect.c 
new/comfy-aimdo-0.6.0/src-win/shmem-detect.c
--- old/comfy-aimdo-0.5.6/src-win/shmem-detect.c        2026-10-07 
21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src-win/shmem-detect.c        2026-10-21 
13:56:45.000000000 +0200
@@ -3,6 +3,7 @@
 
 #include <windows.h>
 #include <dxgi1_4.h>
+#include "dxcore_c.h"
 
 #if defined(__HIP_PLATFORM_AMD__)
 typedef union {
@@ -33,6 +34,10 @@
         g_wddm_adapter->lpVtbl->Release(g_wddm_adapter);
         g_wddm_adapter = NULL;
     }
+    if (g_dxcore_adapter) {
+        g_dxcore_adapter->lpVtbl->Release(g_dxcore_adapter);
+        g_dxcore_adapter = NULL;
+    }
 
 #if defined(__HIP_PLATFORM_AMD__)
     AimdoHipDeviceProp hip_props = {0};
@@ -51,7 +56,34 @@
 
     fail_code++;
 
+    {
+        IDXCoreAdapterFactory *dxcore_factory = NULL;
+        if (SUCCEEDED(DXCoreCreateAdapterFactory(&IID_IDXCoreAdapterFactory, 
(void **)&dxcore_factory))) {
+            IDXCoreAdapter *d_adapter = NULL;
+            if 
(SUCCEEDED(dxcore_factory->lpVtbl->GetAdapterByLuid(dxcore_factory, &cuda_luid, 
&IID_IDXCoreAdapter, (void **)&d_adapter))) {
+                DXCoreAdapterMemoryBudgetNodeSegmentGroup nodeSegmentGroup = 
{0};
+                DXCoreAdapterMemoryBudget test_budget = {0};
+                nodeSegmentGroup.segmentGroup = DXCoreSegmentGroup_Local;
+
+                if (SUCCEEDED(d_adapter->lpVtbl->QueryState(d_adapter, 
DXCoreAdapterState_AdapterMemoryBudget, sizeof(nodeSegmentGroup), 
&nodeSegmentGroup, sizeof(test_budget), &test_budget))) {
+                    g_dxcore_adapter = d_adapter;
+                    d_adapter->lpVtbl->GetProperty(d_adapter, 
DXCoreAdapterProperty_DriverDescription, sizeof(adapter_name), adapter_name);
+                    adapter_name[sizeof(adapter_name) - 1] = '\0';
+                    log(INFO,
+                        "comfy-aimdo DXCore adapter match: %s 
runtime_luid=%08lx:%08lx\n",
+                        adapter_name,
+                        (unsigned long)(unsigned int)cuda_luid.HighPart,
+                        (unsigned long)cuda_luid.LowPart);
+                } else {
+                    d_adapter->lpVtbl->Release(d_adapter);
+                }
+            }
+            dxcore_factory->lpVtbl->Release(dxcore_factory);
+        }
+    }
+
     if (FAILED(CreateDXGIFactory1(&IID_IDXGIFactory4, (void **)&factory))) {
+        if (g_dxcore_adapter) return true;
         goto fail;
     }
 
@@ -86,11 +118,20 @@
         adapter->lpVtbl->Release(adapter);
     }
 
-fail:
-    g_wddm_adapter = NULL;
     if (factory) {
         factory->lpVtbl->Release(factory);
     }
+
+    if (g_dxcore_adapter) {
+        return true;
+    }
+
+fail:
+    g_wddm_adapter = NULL;
+    if (g_dxcore_adapter) {
+        g_dxcore_adapter->lpVtbl->Release(g_dxcore_adapter);
+        g_dxcore_adapter = NULL;
+    }
     log(WARNING, "comfy-aimdo WDDM init failed (%d). aimdo is blind to the 
driver sysmem fallback policy\n", fail_code);
     return false;
 }
@@ -111,6 +152,7 @@
     DXGI_QUERY_VIDEO_MEMORY_INFO info;
     uint64_t effective_budget = vram_capacity;
     size_t free_vram = 0, total_vram = 0;
+    bool budget_queried = false;
     bool used_nvml = false;
 
     uint64_t now = GET_TICK();
@@ -121,9 +163,32 @@
     wddm_timestamp_last_check = now;
     total_vram_last_check = total_vram_usage;
 
-    if (g_wddm_adapter) {
+    *prevailing_deficit_method = "Physical Capacity (Fallback)";
+
+    if (g_dxcore_adapter) {
+        DXCoreAdapterMemoryBudgetNodeSegmentGroup nodeSegmentGroup = {0};
+        DXCoreAdapterMemoryBudget budget = {0};
+        nodeSegmentGroup.segmentGroup = DXCoreSegmentGroup_Local;
+
+        if (SUCCEEDED(g_dxcore_adapter->lpVtbl->QueryState(g_dxcore_adapter, 
DXCoreAdapterState_AdapterMemoryBudget, sizeof(nodeSegmentGroup), 
&nodeSegmentGroup, sizeof(budget), &budget))) {
+            effective_budget = budget.budget;
+            *prevailing_deficit_method = "DXCore budget";
+            budget_queried = true;
+            log(DEBUG,
+                "%s: DXCore budget=%zu MB usage=%zu MB reservation=%zu MB 
available=%zu MB\n",
+                __func__, (size_t)(budget.budget / M), 
(size_t)(budget.currentUsage / M),
+                (size_t)(budget.currentReservation / M),
+                (size_t)(budget.availableForReservation / M));
+        } else {
+            log(WARNING, "comfy-aimdo DXCore VRAM query failed. Falling back 
to WDDM...\n");
+        }
+    }
+
+    if (!budget_queried && g_wddm_adapter) {
         if 
(SUCCEEDED(g_wddm_adapter->lpVtbl->QueryVideoMemoryInfo(g_wddm_adapter, 0, 
DXGI_MEMORY_SEGMENT_GROUP_LOCAL, &info))) {
             effective_budget = info.Budget;
+            *prevailing_deficit_method = "WDDM budget";
+            budget_queried = true;
             log(DEBUG,
                 "%s: WDDM budget=%zu MB usage=%zu MB reservation=%zu MB 
available=%zu MB\n",
                 __func__, (size_t)(info.Budget / M), 
(size_t)(info.CurrentUsage / M),
@@ -135,7 +200,6 @@
     }
 
     deficit_sync = (ssize_t)(total_vram_usage + WDDM_BUDGET_HEADROOM) - 
(ssize_t)effective_budget;
-    *prevailing_deficit_method = "WDDM budget";
 
 #if defined(AIMDO_CUDA)
     used_nvml = nvml_device && aimdo_nvml_memory_info(nvml_device, &free_vram, 
&total_vram);
@@ -158,10 +222,14 @@
     return true;
 }
 
-void aimdo_wddm_cleanup()
+void aimdo_wddm_cleanup(void)
 {
     if (g_wddm_adapter) {
         g_wddm_adapter->lpVtbl->Release(g_wddm_adapter);
         g_wddm_adapter = NULL;
     }
+    if (g_dxcore_adapter) {
+        g_dxcore_adapter->lpVtbl->Release(g_dxcore_adapter);
+        g_dxcore_adapter = NULL;
+    }
 }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/src-win/xfer-file-plat.c 
new/comfy-aimdo-0.6.0/src-win/xfer-file-plat.c
--- old/comfy-aimdo-0.5.6/src-win/xfer-file-plat.c      2026-10-07 
21:16:15.000000000 +0200
+++ new/comfy-aimdo-0.6.0/src-win/xfer-file-plat.c      2026-10-21 
13:56:45.000000000 +0200
@@ -3,12 +3,11 @@
 
 #include <windows.h>
 
-bool xfer_file_read_at(XferFileHandle file_handle, uint64_t offset, void 
*destination,
-                       size_t size, bool mark_cold) {
+static bool xfer_file_transfer_at(XferFileHandle file_handle, uint64_t offset, 
void *buffer,
+                                  size_t size, bool write) {
     HANDLE handle = (HANDLE)(uintptr_t)file_handle;
     size_t done = 0;
 
-    (void)mark_cold;
     while (done < size) {
         DWORD got = 0;
         DWORD requested = (DWORD)MIN((uint64_t)0x7ffff000,
@@ -27,12 +26,14 @@
                 __func__, err, (ull)(offset + done), requested);
             return false;
         }
-        if (!ReadFile(handle, (char *)destination + done, requested, &got, 
&overlapped)) {
+        BOOL ok = write ? WriteFile(handle, (char *)buffer + done, requested, 
&got, &overlapped) :
+                          ReadFile(handle, (char *)buffer + done, requested, 
&got, &overlapped);
+        if (!ok) {
             DWORD err = GetLastError();
 
             if (err != ERROR_IO_PENDING) {
-                log(AIMDO_LOG_ERROR, "%s: ReadFile failed error=%lu handle=%p 
offset=%llu size=%lu\n",
-                    __func__, err, (void *)handle, (ull)(offset + done), 
requested);
+                log(AIMDO_LOG_ERROR, "%s: %s failed error=%lu handle=%p 
offset=%llu size=%lu\n",
+                    __func__, write ? "WriteFile" : "ReadFile", err, (void 
*)handle, (ull)(offset + done), requested);
                 CloseHandle(event);
                 return false;
             }
@@ -47,7 +48,7 @@
         }
         CloseHandle(event);
         if (got == 0) {
-            log(AIMDO_LOG_ERROR, "%s: zero-byte read handle=%p offset=%llu 
size=%lu\n",
+            log(AIMDO_LOG_ERROR, "%s: zero-byte transfer handle=%p offset=%llu 
size=%lu\n",
                 __func__, (void *)handle, (ull)(offset + done), requested);
             return false;
         }
@@ -56,3 +57,13 @@
 
     return true;
 }
+
+bool xfer_file_read_at(XferFileHandle file_handle, uint64_t offset, void 
*destination,
+                       size_t size, bool mark_cold) {
+    (void)mark_cold;
+    return xfer_file_transfer_at(file_handle, offset, destination, size, 
false);
+}
+
+bool xfer_file_write_at(XferFileHandle file_handle, uint64_t offset, void 
*source, size_t size) {
+    return xfer_file_transfer_at(file_handle, offset, source, size, true);
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/comfy-aimdo-0.5.6/tests/vbar-pageout-cuda.py 
new/comfy-aimdo-0.6.0/tests/vbar-pageout-cuda.py
--- old/comfy-aimdo-0.5.6/tests/vbar-pageout-cuda.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/comfy-aimdo-0.6.0/tests/vbar-pageout-cuda.py    2026-10-21 
13:56:45.000000000 +0200
@@ -0,0 +1,144 @@
+"""Run on a disposable CUDA host with the matching native Aimdo build."""
+import ctypes
+import os
+
+os.environ.setdefault("PYTORCH_ALLOC_CONF", "backend:cudaMallocAsync")
+
+import torch
+import comfy_aimdo.control as aimdo
+
+assert aimdo.init("cuda") and aimdo.init_device(0)
+from comfy_aimdo.model_vbar import ModelVBAR, vbar_signature_compare
+from comfy_aimdo.torch import get_tensor_from_raw_ptr
+from comfy_aimdo.vram_buffer import VRAMBuffer
+
+PAGE = 32 * 1024 ** 2
+device = torch.device("cuda", 0)
+size = PAGE + 123
+
+for pinned in (False, True):
+    calls = []
+    buffer = torch.full((size + 22,), 177, dtype=torch.uint8, 
pin_memory=pinned)
+    saved = False
+
+    def pageout(offset, length):
+        global saved
+        calls.append((offset, length))
+        if not saved:
+            value = get_tensor_from_raw_ptr(vbar.base_addr, size, device)
+            buffer[11:-11].copy_(value)
+            saved = True
+
+    vbar = ModelVBAR(size, 0, pageout_callback=pageout)
+    signature = vbar.fault(vbar.base_addr, size)
+    assert signature is not None
+    value = get_tensor_from_raw_ptr(vbar.base_addr, size, device)
+    value[:PAGE].fill_(31)
+    value[PAGE:].fill_(79)
+    vbar.set_watermark(0)
+    assert not calls  # Live consumers still pin the pages.
+    del value
+    vbar.unpin(vbar.base_addr, size)
+    assert calls == [(0, PAGE), (PAGE, 123)]
+    assert bool((buffer[:11] == 177).all() & (buffer[-11:] == 177).all())
+    assert bool((buffer[11:11 + PAGE] == 31).all() & (buffer[11 + PAGE:-11] == 
79).all())
+
+    # Fault only allocates: the owner supplies the contents and detects new 
pages.
+    vbar.set_watermark(size)
+    restored = vbar.fault(vbar.base_addr, size)
+    assert restored is not None and not vbar_signature_compare(signature, 
restored)
+    value = get_tensor_from_raw_ptr(vbar.base_addr, size, device)
+    value.copy_(buffer[11:-11])
+    assert bool((value[:PAGE] == 31).all() & (value[PAGE:] == 79).all())
+    del value
+    vbar.unpin(vbar.base_addr, size)
+    vbar.set_watermark(0)
+    assert vbar.loaded_size() == 0
+    vbar.set_watermark(size)
+    assert vbar.fault(vbar.base_addr, size) is not None
+    calls.clear()
+    vbar.close()
+    assert not calls  # Destruction never requests backing.
+    print("PAGEOUT_PASS pinned=", pinned, flush=True)
+
+cuda = ctypes.CDLL("nvcuda.dll" if os.name == "nt" else "libcuda.so.1")
+cuda.cuMemAlloc_v2.argtypes = [ctypes.POINTER(ctypes.c_uint64), 
ctypes.c_size_t]
+cuda.cuMemAlloc_v2.restype = ctypes.c_int
+cuda.cuMemAllocAsync.argtypes = [ctypes.POINTER(ctypes.c_uint64), 
ctypes.c_size_t, ctypes.c_void_p]
+cuda.cuMemAllocAsync.restype = ctypes.c_int
+stream = torch.cuda.current_stream()
+headroom = aimdo.get_simple_vram_headroom()
+
+for operation in ("watermark", "free_memory", "unpin", "fault", "grow", 
"malloc", "malloc_async", "graph_small", "graph_large"):
+    failed = True
+
+    def pageout(offset, length):
+        if failed:
+            raise OSError("injected writeback failure")
+
+    vbar = ModelVBAR(2 * PAGE, 0, pageout_callback=pageout)
+    assert vbar.fault(vbar.base_addr, 2 * PAGE) is not None
+    value = get_tensor_from_raw_ptr(vbar.base_addr, 2 * PAGE, device)
+    value.fill_(79)
+    torch.cuda.synchronize()
+    if operation == "unpin":
+        vbar.set_watermark(0)
+    else:
+        vbar.unpin(vbar.base_addr, 2 * PAGE)
+    graph = target = None
+    if operation in ("watermark", "free_memory", "unpin"):
+        action = {
+            "watermark": lambda: vbar.set_watermark(0),
+            "free_memory": lambda: vbar.free_memory(2 * PAGE),
+            "unpin": lambda: vbar.unpin(vbar.base_addr, 2 * PAGE),
+        }[operation]
+    else:
+        # Force eviction without actually filling the GPU. Failure may 
originate
+        # in a different VBAR from the one whose fault requested the 
reclamation.
+        aimdo.set_simple_vram_headroom(1 << 60)
+        if operation == "fault":
+            target = ModelVBAR(PAGE, 0)
+            action = lambda: target.fault(target.base_addr, PAGE)
+        elif operation == "grow":
+            target = VRAMBuffer(PAGE, 0)
+            action = lambda: target.get(PAGE)
+        else:
+            if operation.startswith("graph_"):
+                graph = aimdo.record(stream)
+            pointer = ctypes.c_uint64(123)
+            if operation == "malloc":
+                result = cuda.cuMemAlloc_v2(ctypes.byref(pointer), 1024)
+            else:
+                amount = PAGE if operation == "graph_large" else 1024
+                result = cuda.cuMemAllocAsync(ctypes.byref(pointer), amount, 
stream.cuda_stream)
+            assert result == 999 and pointer.value == 0, (operation, result, 
pointer.value)
+            action = None
+    if action is not None:
+        try:
+            action()
+        except RuntimeError:
+            pass
+        else:
+            raise AssertionError(f"{operation}: writeback error was lost")
+    aimdo.set_simple_vram_headroom(headroom)
+    if graph is not None:
+        graph.abort()
+    del graph, target
+    assert vbar.get_residency() == [1, 1], operation  # Retained, with no 
leaked consumer pins.
+    assert bool((value == 79).all()), operation
+    del value
+
+    # The client repairs its callback. No Aimdo error latch to clear, and a
+    # subsequent fault must neither fail nor consume an error from an earlier 
call.
+    failed = False
+    vbar.prioritize()
+    assert vbar.fault(vbar.base_addr, 2 * PAGE) is not None
+    vbar.unpin(vbar.base_addr, 2 * PAGE)
+    assert vbar.free_memory(2 * PAGE) == 2 * PAGE
+    assert vbar.loaded_size() == 0
+    vbar.close()
+    print("PAGEOUT_FAILURE_RECOVERED", operation, flush=True)
+
+del vbar, buffer
+torch.cuda.synchronize()
+aimdo.deinit()

Reply via email to