offgrid-ai 0.8.4 → 0.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/benchmark.mjs +39 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "offgrid-ai",
3
- "version": "0.8.4",
3
+ "version": "0.8.5",
4
4
  "description": "Privacy-first CLI for running local LLMs — discover, configure, run, benchmark",
5
5
  "author": "Eeshan Srivastava (https://eeshans.com)",
6
6
  "type": "module",
package/src/benchmark.mjs CHANGED
@@ -727,6 +727,44 @@ async function queryOllamaMetrics(profile) {
727
727
  };
728
728
  }
729
729
 
730
+ // ── Unload model from server memory after benchmark ────────────────────────
731
+
732
+ export async function unloadModelFromServer(profile) {
733
+ const backend = backendFor(profile.backend);
734
+
735
+ if (backend.id === "ollama") {
736
+ const apiBaseUrl = (profile.baseUrl
737
+ ? profile.baseUrl.replace(/\/v1\/?$/u, "")
738
+ : backend.apiBaseUrl).replace(/\/$/u, "");
739
+
740
+ try {
741
+ await fetch(`${apiBaseUrl}/api/generate`, {
742
+ method: "POST",
743
+ headers: { "Content-Type": "application/json" },
744
+ body: JSON.stringify({ model: profile.modelAlias, prompt: "", stream: false, keep_alive: 0 }),
745
+ signal: AbortSignal.timeout(10000),
746
+ });
747
+ return { unloaded: true, backend: backend.id };
748
+ } catch (err) {
749
+ return { unloaded: false, backend: backend.id, error: err.message };
750
+ }
751
+ }
752
+
753
+ if (backend.id === "llama-cpp" || backend.id === "llama-cpp-mtp") {
754
+ // llama.cpp unloads when the server process exits; no HTTP unload API exists.
755
+ // If offgrid-ai started the server, stopProfile already handled it.
756
+ return { unloaded: false, backend: backend.id, reason: "stop server to unload" };
757
+ }
758
+
759
+ if (backend.id === "omlx") {
760
+ // oMLX does not expose a model-unload endpoint. The model stays resident
761
+ // until the oMLX server process is stopped.
762
+ return { unloaded: false, backend: backend.id, reason: "no unload API available" };
763
+ }
764
+
765
+ return { unloaded: false, backend: backend.id, reason: "unsupported backend" };
766
+ }
767
+
730
768
  // ── Finalize benchmark run metadata ──────────────────────────────────────
731
769
 
732
770
  export async function finalizeBenchmarkRun(runDirectory, runResult, speedMetrics) {
@@ -869,6 +907,7 @@ export async function runPreparedBenchmark(profile, runDirectory, options = {})
869
907
  console.log(result.stopped ? pc.green(`[stop] ${result.message}`) : pc.dim(`[stop] ${result.message}`));
870
908
  }
871
909
  }
910
+ await unloadModelFromServer(profile).catch(() => {});
872
911
  }
873
912
 
874
913
  return metadata;