@bendyline/gezel-inference 1.0.6 → 1.0.8

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 (152) hide show
  1. package/dist/cache/controller.d.ts +2 -0
  2. package/dist/cache/controller.js +38 -5
  3. package/dist/catalog/chat-models/index.json +1 -1
  4. package/dist/catalog/chat-models/lf/lfm3-1.7b-q4/manifest.json +114 -0
  5. package/dist/catalog/chat-models/lf/lfm3-1.7b-q4/versions/1.0.0/manifest.json +15 -0
  6. package/dist/catalog/chat-models/raw-index.json +1 -1
  7. package/dist/catalog/image-loras/index.json +1 -0
  8. package/dist/catalog/image-loras/kr/krea-2-ink-wash/manifest.json +31 -0
  9. package/dist/catalog/image-loras/kr/krea-2-ink-wash/versions/1.0.0/manifest.json +15 -0
  10. package/dist/catalog/image-loras/kr/krea-2-isometric-3d/manifest.json +32 -0
  11. package/dist/catalog/image-loras/kr/krea-2-isometric-3d/versions/1.0.0/manifest.json +15 -0
  12. package/dist/catalog/image-loras/kr/krea-2-kids-drawing/manifest.json +31 -0
  13. package/dist/catalog/image-loras/kr/krea-2-kids-drawing/versions/1.0.0/manifest.json +15 -0
  14. package/dist/catalog/image-loras/kr/krea-2-pixel-art/manifest.json +31 -0
  15. package/dist/catalog/image-loras/kr/krea-2-pixel-art/versions/1.0.0/manifest.json +15 -0
  16. package/dist/catalog/image-loras/kr/krea-2-soft-watercolor/manifest.json +31 -0
  17. package/dist/catalog/image-loras/kr/krea-2-soft-watercolor/versions/1.0.0/manifest.json +15 -0
  18. package/dist/catalog/image-loras/kr/krea-2-turbo-4step/manifest.json +31 -0
  19. package/dist/catalog/image-loras/kr/krea-2-turbo-4step/versions/1.0.0/manifest.json +15 -0
  20. package/dist/catalog/image-loras/mo/moss-image-gouache/manifest.json +29 -0
  21. package/dist/catalog/image-loras/mo/moss-image-gouache/versions/1.0.0/manifest.json +15 -0
  22. package/dist/catalog/image-loras/mo/moss-image-sft-4step/manifest.json +30 -0
  23. package/dist/catalog/image-loras/mo/moss-image-sft-4step/versions/1.0.0/manifest.json +15 -0
  24. package/dist/catalog/image-loras/qw/qwen-image-edit-2511-lightning/manifest.json +31 -0
  25. package/dist/catalog/image-loras/qw/qwen-image-edit-2511-lightning/versions/1.0.0/manifest.json +15 -0
  26. package/dist/catalog/image-loras/raw-index.json +1 -0
  27. package/dist/catalog/image-loras/sd/sdxl-3d-render/manifest.json +29 -0
  28. package/dist/catalog/image-loras/sd/sdxl-3d-render/versions/1.0.0/manifest.json +15 -0
  29. package/dist/catalog/image-loras/sd/sdxl-coloring-book/manifest.json +30 -0
  30. package/dist/catalog/image-loras/sd/sdxl-coloring-book/versions/1.0.0/manifest.json +15 -0
  31. package/dist/catalog/image-loras/sd/sdxl-papercut/manifest.json +29 -0
  32. package/dist/catalog/image-loras/sd/sdxl-papercut/versions/1.0.0/manifest.json +15 -0
  33. package/dist/catalog/image-loras/sd/sdxl-pixel-art/manifest.json +29 -0
  34. package/dist/catalog/image-loras/sd/sdxl-pixel-art/versions/1.0.0/manifest.json +15 -0
  35. package/dist/catalog/image-models/fl/flux-2-klein-4b-q4/manifest.json +3 -0
  36. package/dist/catalog/image-models/index.json +1 -1
  37. package/dist/catalog/image-models/qw/qwen-image-edit-2511-q4/manifest.json +44 -0
  38. package/dist/catalog/image-models/qw/qwen-image-edit-2511-q4/versions/1.0.0/manifest.json +36 -0
  39. package/dist/catalog/image-models/raw-index.json +1 -1
  40. package/dist/catalog/knowledge-catalogs/index.json +1 -1
  41. package/dist/catalog/knowledge-catalogs/raw-index.json +1 -1
  42. package/dist/catalog/knowledge-catalogs/wi/wikipedia-arts/versions/2026.10.2/manifest.json +181 -0
  43. package/dist/catalog/knowledge-catalogs/wi/wikipedia-astronomy/versions/2026.10.2/manifest.json +181 -0
  44. package/dist/catalog/knowledge-catalogs/wi/wikipedia-biology/versions/2026.10.2/manifest.json +217 -0
  45. package/dist/catalog/knowledge-catalogs/wi/wikipedia-business/versions/2026.10.2/manifest.json +197 -0
  46. package/dist/catalog/knowledge-catalogs/wi/wikipedia-film-tv/versions/2026.10.2/manifest.json +197 -0
  47. package/dist/catalog/knowledge-catalogs/wi/wikipedia-food-drink/versions/2026.10.2/manifest.json +185 -0
  48. package/dist/catalog/knowledge-catalogs/wi/wikipedia-games/versions/2026.10.2/manifest.json +181 -0
  49. package/dist/catalog/knowledge-catalogs/wi/wikipedia-general/manifest.json +55 -0
  50. package/dist/catalog/knowledge-catalogs/wi/wikipedia-general/versions/2026.10.2/manifest.json +165 -0
  51. package/dist/catalog/knowledge-catalogs/wi/wikipedia-geography/versions/2026.10.2/manifest.json +217 -0
  52. package/dist/catalog/knowledge-catalogs/wi/wikipedia-history/versions/2026.10.2/manifest.json +217 -0
  53. package/dist/catalog/knowledge-catalogs/wi/wikipedia-language/versions/2026.10.2/manifest.json +181 -0
  54. package/dist/catalog/knowledge-catalogs/wi/wikipedia-law/versions/2026.10.2/manifest.json +185 -0
  55. package/dist/catalog/knowledge-catalogs/wi/wikipedia-literature/versions/2026.10.2/manifest.json +193 -0
  56. package/dist/catalog/knowledge-catalogs/wi/wikipedia-mathematics/versions/2026.10.2/manifest.json +189 -0
  57. package/dist/catalog/knowledge-catalogs/wi/wikipedia-medicine/versions/2026.10.2/manifest.json +185 -0
  58. package/dist/catalog/knowledge-catalogs/wi/wikipedia-military/versions/2026.10.2/manifest.json +189 -0
  59. package/dist/catalog/knowledge-catalogs/wi/wikipedia-music/versions/2026.10.2/manifest.json +197 -0
  60. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-arts/versions/2026.10.2/manifest.json +209 -0
  61. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-business/versions/2026.10.2/manifest.json +189 -0
  62. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-entertainment/versions/2026.10.2/manifest.json +201 -0
  63. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-military/versions/2026.10.2/manifest.json +185 -0
  64. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-other/versions/2026.10.2/manifest.json +165 -0
  65. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-politics/versions/2026.10.2/manifest.json +197 -0
  66. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-religion/versions/2026.10.2/manifest.json +201 -0
  67. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-science/versions/2026.10.2/manifest.json +205 -0
  68. package/dist/catalog/knowledge-catalogs/wi/wikipedia-people-sports/versions/2026.10.2/manifest.json +237 -0
  69. package/dist/catalog/knowledge-catalogs/wi/wikipedia-politics/versions/2026.10.2/manifest.json +197 -0
  70. package/dist/catalog/knowledge-catalogs/wi/wikipedia-religion-philosophy/versions/2026.10.2/manifest.json +189 -0
  71. package/dist/catalog/knowledge-catalogs/wi/wikipedia-science/versions/2026.10.2/manifest.json +185 -0
  72. package/dist/catalog/knowledge-catalogs/wi/wikipedia-sports/versions/2026.10.2/manifest.json +193 -0
  73. package/dist/catalog/knowledge-catalogs/wi/wikipedia-technology/versions/2026.10.2/manifest.json +185 -0
  74. package/dist/catalog/knowledge-catalogs/wi/wikipedia-transportation/versions/2026.10.2/manifest.json +189 -0
  75. package/dist/catalog/provenance.json +2 -1
  76. package/dist/engine-components.js +2 -2
  77. package/dist/engines/electron-native-reuse.d.ts +7 -1
  78. package/dist/engines/electron-native-reuse.js +12 -4
  79. package/dist/engines/native-file-manifest.json +546 -406
  80. package/dist/engines/native-manifest.d.ts +4 -4
  81. package/dist/engines/native-manifest.js +36 -36
  82. package/dist/engines/resolver.js +1 -0
  83. package/dist/http/auth-context.d.ts +12 -0
  84. package/dist/http/routes/image-ensure-routes.d.ts +4 -0
  85. package/dist/http/routes/image-ensure-routes.js +102 -0
  86. package/dist/http/routes/v1-image-styles.d.ts +5 -0
  87. package/dist/http/routes/v1-image-styles.js +91 -0
  88. package/dist/http/routes/v1-images.d.ts +3 -1
  89. package/dist/http/routes/v1-images.js +48 -99
  90. package/dist/inference-service.js +2 -1
  91. package/dist/knowledge/manager.d.ts +3 -27
  92. package/dist/knowledge/manager.js +8 -364
  93. package/dist/knowledge/unified-search.d.ts +61 -0
  94. package/dist/knowledge/unified-search.js +373 -0
  95. package/dist/knowledge/worker-host.js +2 -0
  96. package/dist/machine-engine/inspect-runtime.js +12 -3
  97. package/dist/memory/embed-core.js +3 -0
  98. package/dist/memory/embeddings.js +2 -0
  99. package/dist/models/image-fit.js +2 -2
  100. package/dist/models/weight-facts.d.ts +5 -0
  101. package/dist/models/weight-facts.js +5 -2
  102. package/dist/providers/ds4/build-provider.js +1 -1
  103. package/dist/providers/ds4/residency.js +3 -3
  104. package/dist/providers/image/img2img-support.d.ts +36 -1
  105. package/dist/providers/image/img2img-support.js +30 -1
  106. package/dist/providers/image/local-engine.d.ts +1 -0
  107. package/dist/providers/image/local-engine.js +8 -2
  108. package/dist/providers/image/local-factory.d.ts +10 -1
  109. package/dist/providers/image/local-factory.js +33 -6
  110. package/dist/providers/image/mock.d.ts +8 -1
  111. package/dist/providers/image/mock.js +46 -1
  112. package/dist/providers/image/pull-registry.d.ts +25 -3
  113. package/dist/providers/image/pull-registry.js +96 -34
  114. package/dist/providers/image/sd-cpp.d.ts +63 -4
  115. package/dist/providers/image/sd-cpp.js +368 -38
  116. package/dist/providers/image/types.d.ts +76 -4
  117. package/dist/providers/image/types.js +8 -0
  118. package/dist/providers/llama-cpp/build-provider.js +3 -2
  119. package/dist/providers/llama-cpp/cache-adapter.d.ts +3 -0
  120. package/dist/providers/llama-cpp/cache-adapter.js +18 -4
  121. package/dist/providers/llama-cpp/devices.js +2 -2
  122. package/dist/providers/llama-cpp/engine-flags.d.ts +5 -0
  123. package/dist/providers/llama-cpp/engine-flags.js +5 -2
  124. package/dist/providers/mlx/async-file-handoff.d.ts +9 -1
  125. package/dist/providers/mlx/async-file-handoff.js +32 -4
  126. package/dist/providers/mlx/provider.js +41 -20
  127. package/dist/providers/mlx/python/gezel_mlx_server.py +9 -2
  128. package/dist/providers/mlx/python/spec_decode.py +44 -5
  129. package/dist/providers/mlx/runtime-diagnostics.d.ts +6 -0
  130. package/dist/providers/mlx/runtime-diagnostics.js +18 -1
  131. package/dist/providers/mlx/tool-call-protocol.d.ts +4 -3
  132. package/dist/providers/mlx/tool-call-protocol.js +4 -3
  133. package/dist/providers/native/capacity-broker.d.ts +1 -1
  134. package/dist/providers/native/capacity-broker.js +8 -8
  135. package/dist/providers/native/capacity-budget.js +5 -4
  136. package/dist/providers/native/device-capacity-ledger.d.ts +5 -1
  137. package/dist/providers/native/device-capacity-ledger.js +60 -14
  138. package/dist/providers/native/device-capacity.js +4 -3
  139. package/dist/providers/native/local-engine-runtime.js +4 -4
  140. package/dist/providers/native/process-identity.d.ts +40 -0
  141. package/dist/providers/native/process-identity.js +156 -0
  142. package/dist/relevance/relevance-core.js +2 -0
  143. package/dist/relevance/relevance-model.js +2 -0
  144. package/dist/relevance/settings.js +2 -2
  145. package/dist/runtime-lock.d.ts +19 -5
  146. package/dist/runtime-lock.js +116 -26
  147. package/dist/system/host-memory.d.ts +46 -0
  148. package/dist/system/host-memory.js +89 -0
  149. package/dist/system/memory.js +5 -4
  150. package/dist/transformers-cache.js +2 -0
  151. package/dist/utils/download-common.d.ts +1 -1
  152. package/package.json +8 -8
@@ -38,6 +38,12 @@ export declare function buildPreFirstByteAbortMessage(lastPrefill: {
38
38
  } | null): string;
39
39
  /** Build the user-facing message for an engine stream that died mid-turn. */
40
40
  export declare function buildMidStreamDropMessage(charsReceived: number, plannedStop: boolean, engineStillRunning?: boolean): string;
41
+ /**
42
+ * Prompt characters one attached image stands for. The sidecar bounds images
43
+ * to 1024px, so their patch tokens are bounded too, and base64 bytes are never
44
+ * read as language tokens.
45
+ */
46
+ export declare const MLX_IMAGE_RESERVED_CHARS = 8192;
41
47
  /** Estimate request tokens from message bodies and serialized tool schemas. */
42
48
  export declare function estimatePromptTokens(messages: unknown, tools: unknown): number;
43
49
  export declare const PRE_FIRST_BYTE_BASE_MS = 300000;
@@ -146,11 +146,28 @@ export function buildMidStreamDropMessage(charsReceived, plannedStop, engineStil
146
146
  }
147
147
  return `[Mac AI] the on-device engine dropped the connection mid-turn (${got}). This usually means the mlx server crashed, ran out of memory, or was restarted while the turn was streaming — this turn's work was lost. Retry the turn; if it keeps happening, restart the engine in Settings → On-device.`;
148
148
  }
149
+ /**
150
+ * Prompt characters one attached image stands for. The sidecar bounds images
151
+ * to 1024px, so their patch tokens are bounded too, and base64 bytes are never
152
+ * read as language tokens.
153
+ */
154
+ export const MLX_IMAGE_RESERVED_CHARS = 8192;
149
155
  /** Estimate request tokens from message bodies and serialized tool schemas. */
150
156
  export function estimatePromptTokens(messages, tools) {
151
157
  let chars = 0;
152
158
  try {
153
- chars += JSON.stringify(messages)?.length ?? 0;
159
+ // Counted as base64, one generated picture read as ~1.1M tokens: an
160
+ // image gezel's six renders and reads put the estimate past 6M while the
161
+ // engine prefilled 14K (qwen3.8-27b, 2026-11-04).
162
+ let images = 0;
163
+ const text = JSON.stringify(messages, (key, value) => {
164
+ if (key === 'images' && Array.isArray(value)) {
165
+ images += value.length;
166
+ return undefined;
167
+ }
168
+ return value;
169
+ });
170
+ chars += (text?.length ?? 0) + images * MLX_IMAGE_RESERVED_CHARS;
154
171
  }
155
172
  catch {
156
173
  /* circular or unstringifiable */
@@ -39,9 +39,10 @@ export declare function applyCallableToolGrammar(body: Record<string, unknown>,
39
39
  *
40
40
  * This deliberately checks presence only. A present-but-wrongly-typed value
41
41
  * remains the MCP validator's job; the MLX Hermes grammar promises only that
42
- * every declared required key is emitted before the call can close. Seeing a
43
- * missing key is therefore evidence that the live constrained-decoding path
44
- * broke its own contract, not an ordinary model validation miss.
42
+ * every declared required key is emitted before a constrained call can close.
43
+ * A missing key alone does not establish a decoder fault: calls salvaged from
44
+ * ordinary prose were not subject to that grammar. The caller must preserve
45
+ * their origin when deciding whether to disable speculative decoding.
45
46
  */
46
47
  export declare function missingTopLevelRequiredToolArgs(schema: Record<string, unknown> | undefined, args: Record<string, unknown>): string[];
47
48
  export declare function hermesRequiredArgGrammarRequested(body: Record<string, unknown>): boolean;
@@ -59,9 +59,10 @@ export function applyCallableToolGrammar(body, bridges) {
59
59
  *
60
60
  * This deliberately checks presence only. A present-but-wrongly-typed value
61
61
  * remains the MCP validator's job; the MLX Hermes grammar promises only that
62
- * every declared required key is emitted before the call can close. Seeing a
63
- * missing key is therefore evidence that the live constrained-decoding path
64
- * broke its own contract, not an ordinary model validation miss.
62
+ * every declared required key is emitted before a constrained call can close.
63
+ * A missing key alone does not establish a decoder fault: calls salvaged from
64
+ * ordinary prose were not subject to that grammar. The caller must preserve
65
+ * their origin when deciding whether to disable speculative decoding.
65
66
  */
66
67
  export function missingTopLevelRequiredToolArgs(schema, args) {
67
68
  const required = schema?.required;
@@ -31,7 +31,7 @@ export interface CapacityBrokerOptions {
31
31
  * tests and for users who want to opt out of the budget entirely.
32
32
  */
33
33
  budgetBytes?: number;
34
- /** Test seam — defaults to {@link totalmem}. */
34
+ /** Test seam — defaults to {@link totalMemoryBytes}. */
35
35
  systemRamBytes?: () => number;
36
36
  /**
37
37
  * Whether this host shares one memory pool between CPU and GPU, which
@@ -18,8 +18,8 @@
18
18
  */
19
19
  import { execFileSync } from 'node:child_process';
20
20
  import { readFileSync } from 'node:fs';
21
- import { freemem, totalmem } from 'node:os';
22
21
  import { CONSTRAINED_LOCAL_CONTEXT_FLOOR_TOKENS, LLAMA_CPP_COMPUTE_HEADROOM, LOCAL_CONTEXT_FLOOR_TOKENS, MLX_COMPUTE_HEADROOM, createLogger, estimateLlamaCppResidentBytes, estimateMlxResidentBytes, localContextFloorTokens, } from '@bendyline/gezel';
22
+ import { capToContainer, freeMemoryBytes, totalMemoryBytes } from '../../system/host-memory.js';
23
23
  import { autoAllowRamSpillover, computeCapacityBudget, fastMemoryBudgetBytes, getDetectedGpuVramBytes, } from './capacity-budget.js';
24
24
  export { autoAllowRamSpillover, autoDetectBudgetBytes, computeCapacityBudget, getDetectedGpuVramBytes, pressureIdleGraceMs, setDetectedGpuVramBytes, } from './capacity-budget.js';
25
25
  export { fastMemoryBudgetBytes };
@@ -156,7 +156,7 @@ export class CapacityBroker {
156
156
  explicitAllowRamSpillover;
157
157
  reservations = new Map();
158
158
  constructor(opts = {}) {
159
- this.systemRamBytes = (opts.systemRamBytes ?? totalmem)();
159
+ this.systemRamBytes = (opts.systemRamBytes ?? totalMemoryBytes)();
160
160
  this.unifiedMemory = opts.unifiedMemory;
161
161
  this.gpuVramBytes = opts.gpuVramBytes;
162
162
  this.reservedGpuBytes = opts.reservedGpuBytes;
@@ -467,24 +467,24 @@ export function availableSystemRamBytes() {
467
467
  // vm_stat categories overlap (file-backed pages span active+inactive),
468
468
  // so the raw sum can exceed physical RAM; cap at 90% of totalmem — the
469
469
  // consumers already hold back their own OS reserves on top.
470
- const cap = totalmem() * 0.9;
470
+ const cap = totalMemoryBytes() * 0.9;
471
471
  try {
472
472
  if (process.platform === 'darwin') {
473
473
  const out = execFileSync('/usr/bin/vm_stat', { encoding: 'utf8', timeout: 3_000 });
474
474
  const parsed = parseVmStatAvailableBytes(out);
475
475
  if (parsed !== null)
476
- return Math.max(Math.min(parsed, cap), freemem());
476
+ return capToContainer(Math.max(Math.min(parsed, cap), freeMemoryBytes()));
477
477
  }
478
478
  else if (process.platform === 'linux') {
479
479
  const parsed = parseMeminfoAvailableBytes(readFileSync('/proc/meminfo', 'utf8'));
480
480
  if (parsed !== null)
481
- return Math.max(Math.min(parsed, cap), freemem());
481
+ return capToContainer(Math.max(Math.min(parsed, cap), freeMemoryBytes()));
482
482
  }
483
483
  }
484
484
  catch {
485
485
  // Probe failure falls through to the conservative number.
486
486
  }
487
- return freemem();
487
+ return freeMemoryBytes();
488
488
  }
489
489
  /** Pure vm_stat parser (exported for tests). Returns null when the output
490
490
  * doesn't look like vm_stat. */
@@ -664,7 +664,7 @@ export function llamaCppSlotCeiling(opts) {
664
664
  * going to free 5 GiB, because nothing was holding it but the estimate.
665
665
  * Planning and admission must read one number.
666
666
  */
667
- export function liveRamOsReserveBytes(totalRamBytes = totalmem()) {
667
+ export function liveRamOsReserveBytes(totalRamBytes = totalMemoryBytes()) {
668
668
  return Math.min(4 * GIB, Math.max(2 * GIB, totalRamBytes * 0.1));
669
669
  }
670
670
  /**
@@ -769,7 +769,7 @@ export function minViableLocalContextTokens(opts) {
769
769
  const override = envMinContextTokens();
770
770
  if (override !== null)
771
771
  return override;
772
- const systemRamBytes = opts?.systemRamBytes ?? totalmem();
772
+ const systemRamBytes = opts?.systemRamBytes ?? totalMemoryBytes();
773
773
  const budget = opts?.budget ?? computeCapacityBudget({ systemRamBytes });
774
774
  return localContextFloorTokens({
775
775
  totalRamBytes: systemRamBytes,
@@ -1,4 +1,5 @@
1
- import { arch, platform, totalmem } from 'node:os';
1
+ import { arch, platform } from 'node:os';
2
+ import { totalMemoryBytes } from '../../system/host-memory.js';
2
3
  const GIB = 1024 ** 3;
3
4
  /**
4
5
  * Ceiling on the auto-derived SYSTEM-RAM share, however much RAM the host
@@ -152,7 +153,7 @@ export function isUnifiedMemoryHost() {
152
153
  * `GEZEL_CAPACITY_BUDGET_GB` overrides the total, including the setting.
153
154
  */
154
155
  export function computeCapacityBudget(input = {}) {
155
- const systemRamBytes = input.systemRamBytes ?? totalmem();
156
+ const systemRamBytes = input.systemRamBytes ?? totalMemoryBytes();
156
157
  // Reading the ambient probe is for callers that know nothing about the
157
158
  // host. One that passes `unifiedMemory` has already described it.
158
159
  const measuredVram = input.gpuVramBytes !== undefined
@@ -269,7 +270,7 @@ function envOverrideBytes() {
269
270
  * (Settings → the engine pages) is the dial for owners who want that traded
270
271
  * either way.
271
272
  */
272
- export function autoDetectBudgetBytes(systemRamBytes = totalmem(), opts = {}) {
273
+ export function autoDetectBudgetBytes(systemRamBytes = totalMemoryBytes(), opts = {}) {
273
274
  return computeCapacityBudget({ systemRamBytes, ...opts }).budgetBytes;
274
275
  }
275
276
  /**
@@ -277,7 +278,7 @@ export function autoDetectBudgetBytes(systemRamBytes = totalmem(), opts = {}) {
277
278
  * the whole budget otherwise. KV slots, prompt caches, and anything else
278
279
  * that must live where the compute happens size against this.
279
280
  */
280
- export function fastMemoryBudgetBytes(systemRamBytes = totalmem(), opts = {}) {
281
+ export function fastMemoryBudgetBytes(systemRamBytes = totalMemoryBytes(), opts = {}) {
281
282
  return computeCapacityBudget({ systemRamBytes, ...opts }).fastBytes;
282
283
  }
283
284
  /** Fast pool at or below which a host is treated as accelerator-constrained. */
@@ -1,4 +1,6 @@
1
1
  import type { NativeCapacityCommand, NativeCapacityReply } from '@bendyline/gezel';
2
+ import { ProcessIdentityCache, processAlive, type readProcessIdentities } from './process-identity.js';
3
+ export { processAlive };
2
4
  export interface DeviceCapacitySample {
3
5
  budgetBytes: number;
4
6
  gpuBudgetBytes: number;
@@ -13,7 +15,6 @@ export interface DeviceCapacitySample {
13
15
  availableRamBytes?: number;
14
16
  serializeLoads: boolean;
15
17
  }
16
- export declare function processAlive(pid: number): boolean;
17
18
  /**
18
19
  * SQLite's process lock makes check-and-reserve atomic across GEZEL_HOME values.
19
20
  * Reservations survive a broker restart. A dead parent does not free the memory
@@ -21,10 +22,13 @@ export declare function processAlive(pid: number): boolean;
21
22
  */
22
23
  export declare class DeviceCapacityLedger {
23
24
  private readonly options;
25
+ private readonly identityCache;
24
26
  constructor(options: {
25
27
  directory: string;
26
28
  sample: () => Promise<DeviceCapacitySample>;
27
29
  alive?: (pid: number) => boolean;
30
+ identities?: typeof readProcessIdentities;
31
+ identityCache?: ProcessIdentityCache;
28
32
  });
29
33
  execute(command: NativeCapacityCommand): Promise<NativeCapacityReply>;
30
34
  private transact;
@@ -2,6 +2,8 @@ import { chmod, mkdir } from 'node:fs/promises';
2
2
  import { createRequire } from 'node:module';
3
3
  import { join } from 'node:path';
4
4
  import { CapacityDeniedError } from './capacity-broker.js';
5
+ import { ProcessIdentityCache, processAlive, sharedProcessIdentities, } from './process-identity.js';
6
+ export { processAlive };
5
7
  // Keep the newer builtin behind a runtime require. The current tsup/esbuild
6
8
  // target rewrites a static `node:sqlite` import to the nonexistent npm package
7
9
  // `sqlite`, making the daemon bundle fail before it can write runtime state.
@@ -10,15 +12,6 @@ const { DatabaseSync } = nodeRequire('node:sqlite');
10
12
  function gb(bytes) {
11
13
  return `${(bytes / 1024 ** 3).toFixed(1)} GB`;
12
14
  }
13
- export function processAlive(pid) {
14
- try {
15
- process.kill(pid, 0);
16
- return true;
17
- }
18
- catch (error) {
19
- return error.code !== 'ESRCH';
20
- }
21
- }
22
15
  /**
23
16
  * SQLite's process lock makes check-and-reserve atomic across GEZEL_HOME values.
24
17
  * Reservations survive a broker restart. A dead parent does not free the memory
@@ -26,8 +19,18 @@ export function processAlive(pid) {
26
19
  */
27
20
  export class DeviceCapacityLedger {
28
21
  options;
22
+ identityCache;
29
23
  constructor(options) {
30
24
  this.options = options;
25
+ // An injected reader still goes through a cache, so tests see the reads
26
+ // production makes. An injected `alive` alone checks liveness only.
27
+ this.identityCache =
28
+ options.identityCache ??
29
+ (options.identities
30
+ ? new ProcessIdentityCache({ read: options.identities, alive: options.alive })
31
+ : options.alive
32
+ ? undefined
33
+ : sharedProcessIdentities);
31
34
  }
32
35
  async execute(command) {
33
36
  const sample = await this.options.sample();
@@ -38,9 +41,39 @@ export class DeviceCapacityLedger {
38
41
  await chmod(path, 0o600);
39
42
  db.exec('PRAGMA busy_timeout = 1000');
40
43
  db.exec('CREATE TABLE IF NOT EXISTS claims (id TEXT PRIMARY KEY, claim TEXT NOT NULL)');
44
+ const snapshot = db
45
+ .prepare('SELECT claim FROM claims')
46
+ .all()
47
+ .map((row) => JSON.parse(String(row.claim)));
48
+ const record = [];
49
+ if (command.action === 'acquire' && !snapshot.some((claim) => claim.id === command.id))
50
+ record.push(command.ownerPid);
51
+ if (command.action === 'bind')
52
+ record.push(command.childPid);
53
+ // Stamps only decide which reservations are stale, and a stale one costs
54
+ // someone only while a request waits for memory. With no request
55
+ // waiting, liveness alone prunes, so the status check that runs before
56
+ // every engine request reads no stamps.
57
+ const confirm = new Map();
58
+ const stamp = (pid, identity) => {
59
+ if (pid !== undefined && identity !== undefined)
60
+ confirm.set(pid, [...(confirm.get(pid) ?? []), identity]);
61
+ };
62
+ if (command.action === 'acquire' || snapshot.some((claim) => claim.phase === 'waiting')) {
63
+ for (const claim of snapshot) {
64
+ stamp(claim.childPid, claim.childIdentity);
65
+ // An owner's stamp is only consulted while its claim is queued.
66
+ if (claim.phase === 'waiting')
67
+ stamp(claim.ownerPid, claim.ownerIdentity);
68
+ }
69
+ }
70
+ // OS probes may spawn a helper. Never hold SQLite's writer lock while awaiting one.
71
+ const identities = command.action !== 'release' && this.identityCache
72
+ ? await this.identityCache.lookup(record, confirm).catch(() => new Map())
73
+ : new Map();
41
74
  db.exec('BEGIN IMMEDIATE');
42
75
  try {
43
- const result = this.transact(db, command, sample);
76
+ const result = this.transact(db, command, sample, identities, new Map(snapshot.map((claim) => [claim.id, JSON.stringify(claim)])));
44
77
  db.exec('COMMIT');
45
78
  return result;
46
79
  }
@@ -53,17 +86,27 @@ export class DeviceCapacityLedger {
53
86
  db.close();
54
87
  }
55
88
  }
56
- transact(db, command, sample) {
89
+ transact(db, command, sample, identities, observedClaims) {
57
90
  const alive = this.options.alive ?? processAlive;
91
+ const sameProcess = (pid, identity) => {
92
+ if (!alive(pid))
93
+ return false;
94
+ const current = identities.get(pid);
95
+ return identity === undefined || current === undefined || current === identity;
96
+ };
58
97
  const rows = db.prepare('SELECT claim FROM claims ORDER BY rowid').all();
59
98
  const claims = rows.map((row) => JSON.parse(String(row.claim)));
60
99
  const remove = db.prepare('DELETE FROM claims WHERE id = ?');
61
100
  const save = db.prepare('UPDATE claims SET claim = ? WHERE id = ?');
62
101
  const live = claims.filter((claim) => {
102
+ // Another coordinator can acquire/rebind while the OS probe is in flight.
103
+ // Its newer claim must not be compared with our earlier process snapshot.
104
+ if (observedClaims.get(claim.id) !== JSON.stringify(claim))
105
+ return true;
63
106
  // An unbound grant with a dead owner could have spawned just before the
64
107
  // owner died. Keep it conservatively; never guess that its bytes are free.
65
- const stale = (claim.childPid !== undefined && !alive(claim.childPid)) ||
66
- (!alive(claim.ownerPid) && claim.phase === 'waiting');
108
+ const stale = (claim.childPid !== undefined && !sameProcess(claim.childPid, claim.childIdentity)) ||
109
+ (!sameProcess(claim.ownerPid, claim.ownerIdentity) && claim.phase === 'waiting');
67
110
  if (stale)
68
111
  remove.run(claim.id);
69
112
  return !stale;
@@ -80,6 +123,7 @@ export class DeviceCapacityLedger {
80
123
  own = {
81
124
  id: command.id,
82
125
  ownerPid: command.ownerPid,
126
+ ownerIdentity: identities.get(command.ownerPid),
83
127
  label: command.label,
84
128
  bytes: command.bytes,
85
129
  gpuBytes: command.gpuBytes,
@@ -95,8 +139,10 @@ export class DeviceCapacityLedger {
95
139
  return { state: 'released', releaseRequested: false };
96
140
  if (command.action === 'acquire' && command.priority === 'interactive')
97
141
  own.priority = 'interactive';
98
- if (command.action === 'bind' && own.phase !== 'waiting')
142
+ if (command.action === 'bind' && own.phase !== 'waiting') {
99
143
  own.childPid = command.childPid;
144
+ own.childIdentity = identities.get(command.childPid);
145
+ }
100
146
  if (command.action === 'ready' && own.phase === 'loading')
101
147
  own.phase = 'ready';
102
148
  const active = live.filter((c) => c.phase !== 'waiting');
@@ -1,11 +1,12 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { readdir, stat } from 'node:fs/promises';
3
- import { homedir, totalmem } from 'node:os';
3
+ import { homedir } from 'node:os';
4
4
  import { join } from 'node:path';
5
5
  import { DS4_FULL_RESIDENCY_HEADROOM_BYTES, GEZEL_VERSION, NativeCapacityCommandSchema, NativeCapacityReplySchema, awakeNow, createLogger, isUnstampedDevBuild, } from '@bendyline/gezel';
6
6
  import { readSystemServiceRuntime, systemServiceHome } from '@bendyline/gezel-client/node';
7
7
  import { ConfigStore } from '../../fs/config-store.js';
8
8
  import { createPinnedFetch } from '../../remotes/pinned-fetch.js';
9
+ import { totalMemoryBytes } from '../../system/host-memory.js';
9
10
  import { CapacityDeniedError, availableSystemRamBytes, liveRamOsReserveBytes, } from './capacity-broker.js';
10
11
  import { DeviceCapacityLedger } from './device-capacity-ledger.js';
11
12
  import { measuredCapacityBudget } from './measured-budget.js';
@@ -156,7 +157,7 @@ export function staticCapacityCeilings(budget, ramBytes, configuredBytes, platfo
156
157
  }
157
158
  export async function sampleDeviceCapacity(home) {
158
159
  const budget = await measuredCapacityBudget();
159
- const ram = totalmem();
160
+ const ram = totalMemoryBytes();
160
161
  const config = home ? await new ConfigStore(home).readConfig() : {};
161
162
  const ceilings = staticCapacityCeilings(budget, ram, configuredEngineMemoryBytes(config));
162
163
  // The ledger's RAM ceiling and the OS's currently reclaimable RAM answer
@@ -213,7 +214,7 @@ async function capacityExecutor(home) {
213
214
  // broker that is merely unreachable is an outage, never permission to
214
215
  // double-book the machine — so claim the authority before rethrowing.
215
216
  observedMachineAuthorities.add(machineHome);
216
- throw error;
217
+ throw new Error(`Local models can't start: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
217
218
  }
218
219
  const skew = assessBrokerVersionSkew(identity.gezelVersion);
219
220
  if (skew)
@@ -609,10 +609,10 @@ export class LocalEngineRuntime {
609
609
  residentCtxTokens: ds4Block?.residentCtxTokens,
610
610
  });
611
611
  if (line) {
612
- const { totalmem } = await import('node:os');
612
+ const { totalMemoryBytes } = await import('../../system/host-memory.js');
613
613
  bytes = ds4ProjectedResidentBytes(line, resolveDs4LaunchCtx({
614
614
  configured: config.modelContextOverrides?.[`ds4:${modelId}`] ?? config.ds4NumCtx,
615
- ramTieredCtx: totalmem() / 1024 ** 3 >= 192 ? 262_144 : 131_072,
615
+ ramTieredCtx: totalMemoryBytes() / 1024 ** 3 >= 192 ? 262_144 : 131_072,
616
616
  catalogMaxCtx: ds4Block?.maxLaunchCtx,
617
617
  minViableContextTokens: minViableLocalContextTokens(),
618
618
  }));
@@ -1046,8 +1046,8 @@ export class LocalEngineRuntime {
1046
1046
  if (!installed && !hasExplicitSource && !(opts.allowUninstalled && ds4Source)) {
1047
1047
  throw new ModelNotInstalledError(name, modelId);
1048
1048
  }
1049
- const { totalmem } = await import('node:os');
1050
- const ramTieredCtx = totalmem() / 1024 ** 3 >= 192 ? 262_144 : 131_072;
1049
+ const { totalMemoryBytes } = await import('../../system/host-memory.js');
1050
+ const ramTieredCtx = totalMemoryBytes() / 1024 ** 3 >= 192 ? 262_144 : 131_072;
1051
1051
  const ds4Override = config.modelContextOverrides?.[`ds4:${modelId}`];
1052
1052
  const ds4Floor = ds4Override !== undefined ? Math.min(contextFloor, ds4Override) : contextFloor;
1053
1053
  const effective = resolveDs4LaunchCtx({
@@ -0,0 +1,40 @@
1
+ export declare function processAlive(pid: number): boolean;
2
+ /** Birth stamps survive coordinator restarts and distinguish a reused PID. */
3
+ export declare function readProcessIdentities(pids: readonly number[]): Promise<ReadonlyMap<number, string>>;
4
+ /**
5
+ * How long a stamp read for a still-live PID is trusted. A PID can die and be
6
+ * reused between two liveness checks; this bounds how long such a dead
7
+ * engine's reservation outlives it. That is the safe direction: memory looks
8
+ * taken for a few seconds longer, never free twice.
9
+ */
10
+ export declare const VERIFIED_IDENTITY_TTL_MS = 10000;
11
+ export interface ProcessIdentityCacheOptions {
12
+ read?: (pids: readonly number[]) => Promise<ReadonlyMap<number, string>>;
13
+ alive?: (pid: number) => boolean;
14
+ now?: () => number;
15
+ ttlMs?: number;
16
+ }
17
+ /**
18
+ * Reading a birth stamp starts `ps`, or PowerShell on Windows. The capacity
19
+ * ledger used to read every PID it holds on each command, which ran before
20
+ * every engine request. A stamp read for a PID that is still alive is reused
21
+ * for a short while, and the PIDs that do need reading go in one batch.
22
+ *
23
+ * A cached stamp may only confirm the stamp a reservation already recorded.
24
+ * A stamp about to be recorded, or one that disagrees, is read again: another
25
+ * coordinator may have recorded a newer process at that PID, and trusting an
26
+ * older stamp there would free a live engine's memory.
27
+ */
28
+ export declare class ProcessIdentityCache {
29
+ private readonly options;
30
+ private readonly verified;
31
+ constructor(options?: ProcessIdentityCacheOptions);
32
+ /**
33
+ * `record`: PIDs whose stamp is about to be written onto a reservation.
34
+ * `confirm`: each PID's stamps as reservations recorded them.
35
+ * PIDs that are dead, or whose stamp cannot be read, are left out.
36
+ */
37
+ lookup(record: Iterable<number>, confirm: ReadonlyMap<number, readonly string[]>): Promise<ReadonlyMap<number, string>>;
38
+ }
39
+ /** One cache per process: every ledger in it asks about the same OS processes. */
40
+ export declare const sharedProcessIdentities: ProcessIdentityCache;
@@ -0,0 +1,156 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { promisify } from 'node:util';
4
+ const exec = promisify(execFile);
5
+ /**
6
+ * One PID the probe refuses blanks every row in its batch: macOS ps prints
7
+ * "process id too large" past XNU's PID_MAX, and Get-Process -Id binds Int32.
8
+ * A PID past the limit cannot be live, so it is left out like an absent one.
9
+ */
10
+ const PROBE_PID_LIMIT = {
11
+ darwin: 99_999,
12
+ win32: 2_147_483_647,
13
+ };
14
+ export function processAlive(pid) {
15
+ try {
16
+ process.kill(pid, 0);
17
+ return true;
18
+ }
19
+ catch (error) {
20
+ return error.code !== 'ESRCH';
21
+ }
22
+ }
23
+ /** Birth stamps survive coordinator restarts and distinguish a reused PID. */
24
+ export async function readProcessIdentities(pids) {
25
+ const limit = PROBE_PID_LIMIT[process.platform] ?? Number.MAX_SAFE_INTEGER;
26
+ const ids = [...new Set(pids)].filter((pid) => Number.isSafeInteger(pid) && pid > 0 && pid <= limit);
27
+ const identities = new Map();
28
+ if (!ids.length)
29
+ return identities;
30
+ try {
31
+ if (process.platform === 'linux') {
32
+ const boot = (await readFile('/proc/sys/kernel/random/boot_id', 'utf8')).trim();
33
+ await Promise.all(ids.map(async (pid) => {
34
+ const value = await readFile(`/proc/${pid}/stat`, 'utf8').catch(() => null);
35
+ if (!value)
36
+ return;
37
+ // comm may contain spaces and parentheses; fields after its final ')' start at field 3.
38
+ const start = value
39
+ .slice(value.lastIndexOf(')') + 2)
40
+ .trim()
41
+ .split(/\s+/)[19];
42
+ if (start && /^\d+$/.test(start))
43
+ identities.set(pid, `${boot}:${start}`);
44
+ }));
45
+ }
46
+ else {
47
+ const windows = process.platform === 'win32';
48
+ const { stdout } = await exec(windows ? 'powershell.exe' : 'ps', windows
49
+ ? [
50
+ '-NoProfile',
51
+ '-NonInteractive',
52
+ '-Command',
53
+ `Get-Process -Id ${ids.join(',')} -ErrorAction SilentlyContinue | ForEach-Object { try { [string]$_.Id + ":" + $_.StartTime.ToUniversalTime().Ticks.ToString() } catch {} }`,
54
+ ]
55
+ : ['-p', ids.join(','), '-o', 'pid=', '-o', 'lstart='], {
56
+ windowsHide: true,
57
+ timeout: 3000,
58
+ maxBuffer: 256 * 1024,
59
+ env: { ...process.env, TZ: 'UTC', LC_ALL: 'C', LANG: 'C' },
60
+ }).catch((error) => {
61
+ // ps (and Windows process lookup) may return live rows alongside a missing PID error.
62
+ if (typeof error.stdout === 'string')
63
+ return { stdout: error.stdout };
64
+ throw error;
65
+ });
66
+ for (const line of stdout.split(/\r?\n/)) {
67
+ const match = windows
68
+ ? /^\s*(\d+):(\d+)\s*$/.exec(line)
69
+ : /^\s*(\d+)\s+(.+?)\s*$/.exec(line);
70
+ if (match && ids.includes(Number(match[1])))
71
+ identities.set(Number(match[1]), match[2]);
72
+ }
73
+ }
74
+ }
75
+ catch {
76
+ // Access denied or an unavailable OS probe is unknown, never proof of death.
77
+ }
78
+ return identities;
79
+ }
80
+ /**
81
+ * How long a stamp read for a still-live PID is trusted. A PID can die and be
82
+ * reused between two liveness checks; this bounds how long such a dead
83
+ * engine's reservation outlives it. That is the safe direction: memory looks
84
+ * taken for a few seconds longer, never free twice.
85
+ */
86
+ export const VERIFIED_IDENTITY_TTL_MS = 10_000;
87
+ /**
88
+ * Reading a birth stamp starts `ps`, or PowerShell on Windows. The capacity
89
+ * ledger used to read every PID it holds on each command, which ran before
90
+ * every engine request. A stamp read for a PID that is still alive is reused
91
+ * for a short while, and the PIDs that do need reading go in one batch.
92
+ *
93
+ * A cached stamp may only confirm the stamp a reservation already recorded.
94
+ * A stamp about to be recorded, or one that disagrees, is read again: another
95
+ * coordinator may have recorded a newer process at that PID, and trusting an
96
+ * older stamp there would free a live engine's memory.
97
+ */
98
+ export class ProcessIdentityCache {
99
+ options;
100
+ verified = new Map();
101
+ constructor(options = {}) {
102
+ this.options = options;
103
+ }
104
+ /**
105
+ * `record`: PIDs whose stamp is about to be written onto a reservation.
106
+ * `confirm`: each PID's stamps as reservations recorded them.
107
+ * PIDs that are dead, or whose stamp cannot be read, are left out.
108
+ */
109
+ async lookup(record, confirm) {
110
+ const alive = this.options.alive ?? processAlive;
111
+ const ttl = this.options.ttlMs ?? VERIFIED_IDENTITY_TTL_MS;
112
+ const now = (this.options.now ?? (() => performance.now()))();
113
+ for (const [pid, entry] of this.verified) {
114
+ if (now - entry.at >= ttl)
115
+ this.verified.delete(pid);
116
+ }
117
+ const live = (pid) => {
118
+ if (alive(pid))
119
+ return true;
120
+ // Forget a PID seen dead, so whatever process takes it next is read.
121
+ this.verified.delete(pid);
122
+ return false;
123
+ };
124
+ const identities = new Map();
125
+ const unread = new Set();
126
+ for (const pid of record)
127
+ if (live(pid))
128
+ unread.add(pid);
129
+ for (const [pid, stamps] of confirm) {
130
+ if (unread.has(pid) || !live(pid))
131
+ continue;
132
+ const cached = this.verified.get(pid)?.identity;
133
+ if (cached !== undefined && stamps.every((stamp) => stamp === cached)) {
134
+ identities.set(pid, cached);
135
+ }
136
+ else {
137
+ unread.add(pid);
138
+ }
139
+ }
140
+ if (unread.size === 0)
141
+ return identities;
142
+ const read = await (this.options.read ?? readProcessIdentities)([...unread]).catch(() => new Map());
143
+ for (const pid of unread) {
144
+ const identity = read.get(pid);
145
+ if (identity === undefined) {
146
+ this.verified.delete(pid);
147
+ continue;
148
+ }
149
+ this.verified.set(pid, { identity, at: now });
150
+ identities.set(pid, identity);
151
+ }
152
+ return identities;
153
+ }
154
+ }
155
+ /** One cache per process: every ledger in it asks about the same OS processes. */
156
+ export const sharedProcessIdentities = new ProcessIdentityCache();
@@ -3,6 +3,7 @@ import { createReadStream } from 'node:fs';
3
3
  import { join } from 'node:path';
4
4
  import { createLogger } from '@bendyline/gezel';
5
5
  import { transformersGraphOptions } from '@bendyline/gezel-knowledge';
6
+ import { disableOnnxRuntimeTelemetry } from '@bendyline/gezel/native';
6
7
  import { PipelineLoadError } from '../memory/embed-core.js';
7
8
  /**
8
9
  * Cross-encoder inference: load a pinned relevance model from its local
@@ -19,6 +20,7 @@ const BATCH = 8;
19
20
  const MAX_LOADED = 2;
20
21
  const loaded = new Map();
21
22
  async function importTransformers() {
23
+ disableOnnxRuntimeTelemetry();
22
24
  return (await import('@huggingface/transformers'));
23
25
  }
24
26
  export async function loadRelevanceModel(model, transformers) {
@@ -1,5 +1,6 @@
1
1
  import { Worker } from 'node:worker_threads';
2
2
  import { createLogger } from '@bendyline/gezel';
3
+ import { disableOnnxRuntimeTelemetry } from '@bendyline/gezel/native';
3
4
  import { retireModelWorker } from '../utils/retire-model-worker.js';
4
5
  import { findServiceWorkerEntry } from '../utils/service-worker-entry.js';
5
6
  import { disposeRelevanceModels, loadRelevanceModel, scoreRelevancePairs, } from './relevance-core.js';
@@ -185,6 +186,7 @@ class RelevanceModelHost {
185
186
  return null;
186
187
  }
187
188
  try {
189
+ disableOnnxRuntimeTelemetry();
188
190
  const worker = new Worker(entry);
189
191
  worker.on('message', (reply) => {
190
192
  const pending = this.pending.get(reply.id);
@@ -1,5 +1,5 @@
1
- import { availableParallelism } from 'node:os';
2
1
  import { KNOWLEDGE_FILTER_MIN_RELEVANCE, } from '@bendyline/gezel';
2
+ import { availableCpuCount } from '../system/host-memory.js';
3
3
  import { relevanceModelDir } from './install.js';
4
4
  import { DEFAULT_RELEVANCE_MODEL_ID, findRelevanceModel } from './registry.js';
5
5
  const DEFAULT_BUDGETS = {
@@ -76,6 +76,6 @@ export function toResolvedModel(home, spec) {
76
76
  maxTokens: spec.maxTokens,
77
77
  queryMaxTokens: spec.queryMaxTokens,
78
78
  scoreActivation: spec.scoreActivation,
79
- intraOpNumThreads: Math.max(1, Math.min(4, Math.floor(availableParallelism() / 2))),
79
+ intraOpNumThreads: Math.max(1, Math.min(4, Math.floor(availableCpuCount() / 2))),
80
80
  };
81
81
  }