Explorar el Código

fix: unload before loading, wait for the load to finish, and always report newItemCount

RELOADING A MODEL DID NOT WORK

Making the settings match meant reloading a model that was already there, and
that failed: "A model is already loaded. Call POST /models/unload first". The
API documentation says a load replaces whatever is in the slot; this server does
not. The slot is now emptied first, which is also the only way to change the
settings of a model that is already loaded - the case this node exists for.

THE LOAD CALL RETURNS BEFORE THE MODEL IS LOADED

Also documented as blocking, also not. /health reported model_loading with a
step count long after the call came back, so the node announced success and the
next node asked a server with no model to generate something. It now waits for
the load to finish, logging progress, and fails plainly if the model is still
loading when the timeout runs out or if the server ends up with nothing loaded.

Both were invisible until reloading-on-settings made the node actually load
something into a server that already had a model.

NEWITEMCOUNT VANISHED WHEN DETECTION WAS TURNED OFF

The RSS reader only reported newItemCount and totalFeedItemCount when Detect New
Items was enabled. A workflow branching on newItemCount > 0 therefore started
taking the other branch the moment someone turned detection off - the field was
not zero, it was absent, and a comparison against a missing field is false. So a
feed with 25 items to process reported nothing to do. Both are now always
reported; with detection off, every item returned is one this run will process.

Verified against the live server: the node unloaded z_image_turbo_bf16, loaded
it again with diffusion_flash_attn true and n_threads 10, waited out all 1095
steps, and reported reloadedFor naming both settings. /health then showed
exactly those values, and a second run reported alreadyLoaded without touching
anything. Full suite 58/58.
fszontagh hace 1 mes
padre
commit
3888b575c9
Se han modificado 2 ficheros con 63 adiciones y 7 borrados
  1. 12 6
      nodes/rss/rss-reader.js
  2. 51 1
      nodes/sdcpp/sdcpp-model-load.js

+ 12 - 6
nodes/rss/rss-reader.js

@@ -105,7 +105,7 @@ const outputSchema = {
             }
         },
         itemCount: { type: 'number', description: 'Number of items returned' },
-        newItemCount: { type: 'number', description: 'Number of new items (when detectNewItems is enabled)' },
+        newItemCount: { type: 'number', description: 'Items this run will process. With Detect New Items off that is every item returned, so a workflow can branch on this whichever mode it is in' },
         totalFeedItemCount: { type: 'number', description: 'Total items in feed before filtering (when detectNewItems is enabled)' },
         items: {
             type: 'array',
@@ -828,11 +828,17 @@ async function execute(config, input) {
         items: items
     };
 
-    // Add stateful mode info if enabled
-    if (detectNewItems) {
-        result.newItemCount = newItemCount;
-        result.totalFeedItemCount = totalFeedItemCount;
-    }
+    // Always reported, in both modes.
+    //
+    // These used to appear only when Detect New Items was on, so a workflow
+    // branching on newItemCount > 0 quietly started taking the other branch the
+    // moment someone turned detection off: the field was not zero, it was gone,
+    // and a comparison against a missing field is false. With detection off
+    // every item returned is one this run will process, so newItemCount is the
+    // item count - which is what a workflow asking "is there anything to do?"
+    // means either way.
+    result.newItemCount = detectNewItems ? newItemCount : items.length;
+    result.totalFeedItemCount = detectNewItems ? totalFeedItemCount : items.length;
 
     return result;
 }

+ 51 - 1
nodes/sdcpp/sdcpp-model-load.js

@@ -534,6 +534,25 @@ async function execute(config, input, context) {
     smartbotic.log.info('SD.cpp: loading ' + modelName +
         (current ? ' (replacing ' + current + ')' : ''));
 
+    // The slot has to be emptied first. The API documentation says a load
+    // replaces whatever is there, but the server answers "A model is already
+    // loaded. Call POST /models/unload first" - so it is unloaded here rather
+    // than leaving every reload to fail on a server that already has a model.
+    //
+    // This is also the only way to change the settings of a model that is
+    // already loaded, which is the case this node exists to handle.
+    if (health.model_loaded === true) {
+        call({
+            method: 'POST',
+            url: server + '/models/unload',
+            headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + token },
+            body: '{}',
+            timeout: Math.min(timeout, 60000),
+            what: 'unloading ' + (current || 'the current model') + ' before loading ' + modelName
+        });
+        smartbotic.log.info('SD.cpp: unloaded ' + (current || 'the previous model'));
+    }
+
     // Loading unloads whatever was in the slot first, and the server holds a
     // mutex for the duration, so this blocks until the weights are resident.
     const loaded = call({
@@ -545,7 +564,38 @@ async function execute(config, input, context) {
         what: 'loading model ' + modelName
     });
 
-    const after = readHealth(server, Math.min(timeout, 15000));
+    // The load call comes back before the model is in memory. The API
+    // documentation describes it as blocking, and it is not: /health reports
+    // model_loading with a step count for some time afterwards. Returning here
+    // would tell the workflow the model is ready and let the next node ask it
+    // to generate, which fails with "no model loaded" - a confusing way to
+    // learn that this node lied.
+    const deadline = startedAt + timeout;
+    let after = readHealth(server, 15000);
+    let lastStep = -1;
+
+    while (after.model_loading === true && Date.now() < deadline) {
+        const step = after.loading_step;
+        const total = after.loading_total_steps;
+        if (typeof step === 'number' && step !== lastStep) {
+            lastStep = step;
+            smartbotic.log.info('SD.cpp: loading ' + (after.loading_model_name || modelName) +
+                ' - ' + step + (total ? '/' + total : ''));
+        }
+        smartbotic.utils.sleep(2000);
+        after = readHealth(server, 15000);
+    }
+
+    if (after.model_loading === true) {
+        throw new Error('SD.cpp: ' + modelName + ' was still loading after ' +
+            Math.round((Date.now() - startedAt) / 1000) + 's. It may still finish on the server; ' +
+            'raise the timeout on this node if this model is simply slow to load');
+    }
+
+    if (after.model_loaded !== true) {
+        throw new Error('SD.cpp: the server accepted the load but has no model loaded afterwards' +
+            (after.last_error ? ': ' + after.last_error : ''));
+    }
 
     return {
         modelName: loaded.model_name || modelName,