gitnexus 1.6.9-rc.40 → 1.6.9-rc.42
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.
- package/dist/cli/doctor.js +11 -0
- package/dist/core/embeddings/embedder.js +8 -77
- package/dist/core/embeddings/onnxruntime-common-resolver.js +24 -19
- package/dist/core/embeddings/onnxruntime-node-resolver.d.ts +40 -0
- package/dist/core/embeddings/onnxruntime-node-resolver.js +297 -0
- package/dist/mcp/core/embedder.js +8 -0
- package/dist/storage/git.d.ts +1 -1
- package/dist/storage/git.js +1 -1
- package/package.json +1 -1
- package/scripts/cross-platform-tests.ts +1 -0
- package/web/assets/{agent-Ci8thBaq.js → agent-PekRmfZA.js} +63 -63
- package/web/assets/{architectureDiagram-UL44E2DR-DWIyty1U.js → architectureDiagram-UL44E2DR-BT7OtZ21.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-D1wx7GlK.js → chunk-LCXTWHL2-BaR-mDRh.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-BiC2Wb0O.js → chunk-RG4AUYOV-C3Zi_WJJ.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-CJO2YCOV.js → classDiagram-KGZ6W3CR-DwCv2Ceh.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-CJO2YCOV.js → classDiagram-v2-72OJOZXJ-DwCv2Ceh.js} +1 -1
- package/web/assets/{dagre-ND4H6XIP-DhPMcW4z.js → dagre-ND4H6XIP-Bbr-DOod.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-CvDoAp_x.js → diagram-3NCE3AQN-t2SsI_89.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-Dav8FIQ1.js → diagram-GF46GFSD-DOPCRZb_.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-Bb7602UB.js → diagram-QXG6HAR7-C-ni48MX.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-C0uHBvgG.js → diagram-WEQXMOUZ-BhkxXWUs.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-r6U2VsD8.js → erDiagram-L5TCEMPS-BgU0MxfG.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-N5u_qwIO.js → flowDiagram-H6V6AXG4-aCbzaZWr.js} +1 -1
- package/web/assets/{index-D4YESd-F.js → index-CJuHkVdg.js} +7 -7
- package/web/assets/{infoDiagram-3YFTVSEB-D_xhhoXs.js → infoDiagram-3YFTVSEB-DVvyoVXw.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-Wk0TknB-.js → ishikawaDiagram-BNXS4ZKH-hPZmDrPW.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DtfiyBt1.js → kanban-definition-75IXJCU3-pU7f9k-3.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-kGkn-rwO.js → mindmap-definition-2TDM6QVE-Wq8ESINo.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-BMiSBl8M.js → pieDiagram-CU6KROY3-o1D7lwHW.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-DxhTYbeJ.js → requirementDiagram-JXO7QTGE-BDiLsn6Z.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-Cwy_0qTJ.js → sequenceDiagram-VS2MUI6T-DRI70L3-.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-B6XHUTIG.js → stateDiagram-7D4R322I-BnkJ0BQS.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-DWBOSVkm.js → stateDiagram-v2-36443NZ5-i2xSyyw9.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-CIRl9vxM.js → timeline-definition-O6YCAMPW-DDbISsUy.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-DQgSBULy.js → vennDiagram-MWXL3ELB-CkSSCEqU.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-BzbU2jy7.js → wardleyDiagram-CUQ6CDDI-CVWX5pm-.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BP_6OxzF.js → xychartDiagram-N2JHSOCM-dHr2kp24.js} +1 -1
- package/web/index.html +1 -1
package/dist/cli/doctor.js
CHANGED
|
@@ -2,6 +2,7 @@ import { getRuntimeCapabilities, getRuntimeFingerprint } from '../core/platform/
|
|
|
2
2
|
import { resolveEmbeddingConfig } from '../core/embeddings/config.js';
|
|
3
3
|
import { isHttpMode } from '../core/embeddings/http-client.js';
|
|
4
4
|
import { getLocalEmbeddingRuntimeBlocker } from '../core/embeddings/runtime-support.js';
|
|
5
|
+
import { cudaRedirectDoctorStatus } from '../core/embeddings/onnxruntime-node-resolver.js';
|
|
5
6
|
import { checkLbugNative } from '../core/lbug/native-check.js';
|
|
6
7
|
import { getExtensionInstallPolicy } from '../core/lbug/extension-loader.js';
|
|
7
8
|
import { t } from './i18n/index.js';
|
|
@@ -119,4 +120,14 @@ export const doctorCommand = async () => {
|
|
|
119
120
|
if (support.detail) {
|
|
120
121
|
process.stderr.write(`\n${support.detail.replace(/^/gm, ' ')}\n\n`);
|
|
121
122
|
}
|
|
123
|
+
// Surface the CUDA-build-redirect decision so "why is my CUDA-13 host
|
|
124
|
+
// still on CPU" is visible without digging through debug logs (#2341
|
|
125
|
+
// follow-up). Only meaningful on the local runtime path.
|
|
126
|
+
if (!isHttpMode()) {
|
|
127
|
+
const cudaRedirect = cudaRedirectDoctorStatus();
|
|
128
|
+
console.log(` ${padDisplayEnd('CUDA:', 12)}${cudaRedirect.status}`);
|
|
129
|
+
if (cudaRedirect.detail) {
|
|
130
|
+
console.log(` ${padDisplayEnd('', 12)}${cudaRedirect.detail}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
122
133
|
};
|
|
@@ -12,89 +12,14 @@
|
|
|
12
12
|
if (!process.env.ORT_LOG_LEVEL) {
|
|
13
13
|
process.env.ORT_LOG_LEVEL = '3';
|
|
14
14
|
}
|
|
15
|
-
import { existsSync } from 'fs';
|
|
16
|
-
import { execFileSync } from 'child_process';
|
|
17
|
-
import { join, dirname } from 'path';
|
|
18
|
-
import { createRequire } from 'module';
|
|
19
15
|
import { DEFAULT_EMBEDDING_CONFIG } from './types.js';
|
|
20
16
|
import { isHttpMode, getHttpDimensions, httpEmbed } from './http-client.js';
|
|
21
17
|
import { resolveEmbeddingConfig } from './config.js';
|
|
22
18
|
import { applyHfEnvOverrides, isHfDownloadFailure, withHfDownloadRetry } from './hf-env.js';
|
|
23
19
|
import { getLocalEmbeddingRuntimeBlocker } from './runtime-support.js';
|
|
24
20
|
import { ensureOnnxRuntimeCommonResolvable } from './onnxruntime-common-resolver.js';
|
|
21
|
+
import { ensureOnnxRuntimeNodeMatchesSystem, isEffectiveCudaAvailable, } from './onnxruntime-node-resolver.js';
|
|
25
22
|
import { logger } from '../logger.js';
|
|
26
|
-
/**
|
|
27
|
-
* Check whether the onnxruntime-node package that @huggingface/transformers
|
|
28
|
-
* will actually load at runtime ships the CUDA execution provider.
|
|
29
|
-
*
|
|
30
|
-
* Critical: we resolve from transformers' own module scope, NOT from ours.
|
|
31
|
-
* npm may install two copies — a top-level 1.24.x (our dep) and a nested
|
|
32
|
-
* 1.21.0 (transformers' pinned dep). The guard must inspect whichever copy
|
|
33
|
-
* transformers.js will dlopen, otherwise the check is meaningless.
|
|
34
|
-
*/
|
|
35
|
-
function hasOrtCudaProvider() {
|
|
36
|
-
try {
|
|
37
|
-
const require = createRequire(import.meta.url);
|
|
38
|
-
// Resolve from @huggingface/transformers' scope so we find the same
|
|
39
|
-
// onnxruntime-node binary that transformers.js will use at runtime
|
|
40
|
-
const transformersDir = dirname(require.resolve('@huggingface/transformers/package.json'));
|
|
41
|
-
const ortRequire = createRequire(join(transformersDir, 'package.json'));
|
|
42
|
-
const ortPath = dirname(ortRequire.resolve('onnxruntime-node/package.json'));
|
|
43
|
-
// ORT 1.24.x only ships CUDA binaries for linux/x64 (downloaded from NuGet
|
|
44
|
-
// at postinstall). arm64 will correctly return false here until ORT adds support.
|
|
45
|
-
const arch = process.arch;
|
|
46
|
-
return existsSync(join(ortPath, 'bin', 'napi-v6', 'linux', arch, 'libonnxruntime_providers_cuda.so'));
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Check whether CUDA libraries are actually available on this system.
|
|
54
|
-
* ONNX Runtime's native layer crashes (uncatchable) if we attempt CUDA
|
|
55
|
-
* without the required shared libraries, so we probe first.
|
|
56
|
-
*
|
|
57
|
-
* Checks both:
|
|
58
|
-
* 1. That system CUDA libraries (libcublasLt) are present
|
|
59
|
-
* 2. That onnxruntime-node ships the CUDA execution provider binary
|
|
60
|
-
*
|
|
61
|
-
* Both conditions must be true — system CUDA libs alone are not enough
|
|
62
|
-
* if onnxruntime-node is a CPU-only build (versions < 1.24.0).
|
|
63
|
-
*/
|
|
64
|
-
function isCudaAvailable() {
|
|
65
|
-
// First, verify onnxruntime-node has the CUDA provider binary.
|
|
66
|
-
// Without this, requesting CUDA causes an uncatchable native crash.
|
|
67
|
-
if (!hasOrtCudaProvider())
|
|
68
|
-
return false;
|
|
69
|
-
// Primary: query the dynamic linker cache — covers all architectures,
|
|
70
|
-
// distro layouts, and custom install paths registered with ldconfig
|
|
71
|
-
try {
|
|
72
|
-
const out = execFileSync('ldconfig', ['-p'], {
|
|
73
|
-
timeout: 3000,
|
|
74
|
-
encoding: 'utf-8',
|
|
75
|
-
windowsHide: true,
|
|
76
|
-
});
|
|
77
|
-
if (out.includes('libcublasLt.so.12'))
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// ldconfig not available (e.g. non-standard container)
|
|
82
|
-
}
|
|
83
|
-
// Fallback: check CUDA_PATH and LD_LIBRARY_PATH for environments where
|
|
84
|
-
// ldconfig doesn't know about the CUDA install (conda, manual /opt/cuda, etc.)
|
|
85
|
-
for (const envVar of ['CUDA_PATH', 'LD_LIBRARY_PATH']) {
|
|
86
|
-
const val = process.env[envVar];
|
|
87
|
-
if (!val)
|
|
88
|
-
continue;
|
|
89
|
-
for (const dir of val.split(':').filter(Boolean)) {
|
|
90
|
-
if (existsSync(join(dir, 'lib64', 'libcublasLt.so.12')) ||
|
|
91
|
-
existsSync(join(dir, 'lib', 'libcublasLt.so.12')) ||
|
|
92
|
-
existsSync(join(dir, 'libcublasLt.so.12')))
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
23
|
// Module-level state for singleton pattern
|
|
99
24
|
let embedderInstance = null;
|
|
100
25
|
let isInitializing = false;
|
|
@@ -142,7 +67,7 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
|
|
|
142
67
|
// provider libraries are missing. DirectML stays opt-in for the same reason.
|
|
143
68
|
// Probe for CUDA first — ONNX Runtime crashes (uncatchable native error)
|
|
144
69
|
// if we attempt CUDA without the required shared libraries
|
|
145
|
-
const gpuDevice =
|
|
70
|
+
const gpuDevice = isEffectiveCudaAvailable() ? 'cuda' : 'cpu';
|
|
146
71
|
const requestedDevice = forceDevice || (finalConfig.device === 'auto' ? gpuDevice : finalConfig.device);
|
|
147
72
|
initPromise = (async () => {
|
|
148
73
|
try {
|
|
@@ -151,6 +76,12 @@ export const initEmbedder = async (onProgress, config = {}, forceDevice) => {
|
|
|
151
76
|
// Under pnpm-strict / `pnpm dlx`, transformers' phantom `onnxruntime-common`
|
|
152
77
|
// import is unresolvable; register the fallback resolver first (#307).
|
|
153
78
|
ensureOnnxRuntimeCommonResolvable();
|
|
79
|
+
// Registered AFTER the common fallback so this hook resolves FIRST (Node
|
|
80
|
+
// runs the most-recently-registered hook first): on CUDA-13 hosts it
|
|
81
|
+
// redirects onnxruntime-node (and its version-matched onnxruntime-common)
|
|
82
|
+
// to the CUDA-13 build before transformers imports them. No-op on matching
|
|
83
|
+
// layouts, non-CUDA, Windows/DirectML, and macOS.
|
|
84
|
+
ensureOnnxRuntimeNodeMatchesSystem();
|
|
154
85
|
const { pipeline, env } = await import('@huggingface/transformers');
|
|
155
86
|
// Configure transformers.js environment
|
|
156
87
|
env.allowLocalModels = false;
|
|
@@ -21,14 +21,18 @@
|
|
|
21
21
|
* Install a synchronous, in-thread ESM resolution hook (`module.registerHooks`,
|
|
22
22
|
* Node >= 22.15) that redirects `onnxruntime-common` to a copy gitnexus can
|
|
23
23
|
* resolve — but only when the default resolver fails. The redirect target is
|
|
24
|
-
* preferentially the `onnxruntime-common` that `onnxruntime-node`
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
24
|
+
* preferentially the `onnxruntime-common` that `onnxruntime-node` depends on —
|
|
25
|
+
* specifically {@link getEffectiveOnnxRuntimeNodeDir}, the SAME onnxruntime-node
|
|
26
|
+
* copy the sibling {@link ./onnxruntime-node-resolver.ts} CUDA-major redirect
|
|
27
|
+
* will actually load (transformers' own default when no redirect is active,
|
|
28
|
+
* or the CUDA-build-matched copy when one is) — so this hook and that one can
|
|
29
|
+
* never disagree about which onnxruntime-node's own onnxruntime-common
|
|
30
|
+
* dependency to pair with, even under `pnpm dlx` where gitnexus' npm-style
|
|
31
|
+
* `overrides` block does NOT apply (honoured only from a root manifest, and
|
|
32
|
+
* gitnexus is a transitive dependency there). Falls back to gitnexus' own
|
|
33
|
+
* direct `onnxruntime-common` dependency when that chain can't be walked.
|
|
34
|
+
* onnxruntime-common is a stable, pure-JS package whose `Tensor` surface is
|
|
35
|
+
* unchanged across 1.24–1.26, so either target is API-compatible. On working
|
|
32
36
|
* layouts the default resolver succeeds first and the hook never fires, so
|
|
33
37
|
* behaviour is unchanged.
|
|
34
38
|
*
|
|
@@ -55,26 +59,27 @@
|
|
|
55
59
|
*/
|
|
56
60
|
import { registerHooks, createRequire } from 'node:module';
|
|
57
61
|
import { pathToFileURL } from 'node:url';
|
|
62
|
+
import { join } from 'node:path';
|
|
63
|
+
import { getEffectiveOnnxRuntimeNodeDir } from './onnxruntime-node-resolver.js';
|
|
58
64
|
import { logger } from '../logger.js';
|
|
59
65
|
let attempted = false;
|
|
60
66
|
/**
|
|
61
67
|
* Compute the file: URL the hook redirects `onnxruntime-common` to.
|
|
62
68
|
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* (
|
|
69
|
-
* onnxruntime-common. Falls back to gitnexus' own direct dependency (always
|
|
70
|
-
* resolvable from our scope) when any step fails.
|
|
69
|
+
* Pair with {@link getEffectiveOnnxRuntimeNodeDir}'s onnxruntime-node copy —
|
|
70
|
+
* NOT independently re-derived — so the redirected module is version-matched
|
|
71
|
+
* to whichever onnxruntime-node will actually load, even under `pnpm dlx`
|
|
72
|
+
* (where transformers keeps its own pinned onnxruntime-node) and even when
|
|
73
|
+
* the sibling CUDA-major redirect is active. Falls back to gitnexus' own
|
|
74
|
+
* direct dependency (always resolvable from our scope) when that fails.
|
|
71
75
|
*/
|
|
72
76
|
const resolveOnnxRuntimeCommonUrl = () => {
|
|
73
77
|
const require = createRequire(import.meta.url);
|
|
74
78
|
try {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
const effectiveDir = getEffectiveOnnxRuntimeNodeDir();
|
|
80
|
+
if (!effectiveDir)
|
|
81
|
+
throw new Error('no effective onnxruntime-node dir resolved');
|
|
82
|
+
const common = createRequire(join(effectiveDir, 'package.json')).resolve('onnxruntime-common');
|
|
78
83
|
return pathToFileURL(common).href;
|
|
79
84
|
}
|
|
80
85
|
catch {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type CudaMajor = 12 | 13;
|
|
2
|
+
/** The CUDA major an onnxruntime-node copy's CUDA provider links against, or null (Linux/x64 only ships one). */
|
|
3
|
+
export declare const ortCudaMajor: (ortNodeDir: string) => CudaMajor | null;
|
|
4
|
+
/** The cuBLASLt major installed on this system, or null. Linux only. */
|
|
5
|
+
export declare const detectSystemCudaMajor: () => CudaMajor | null;
|
|
6
|
+
/**
|
|
7
|
+
* The onnxruntime-node dir that will actually back transformers at runtime once
|
|
8
|
+
* {@link ensureOnnxRuntimeNodeMatchesSystem} has run — i.e. the redirected copy
|
|
9
|
+
* when a redirect applies, otherwise transformers' default. The CUDA probe must
|
|
10
|
+
* inspect THIS dir (not transformers' CJS-resolved default) so probe and
|
|
11
|
+
* runtime agree. Returns null only when neither copy resolves.
|
|
12
|
+
*/
|
|
13
|
+
export declare const getEffectiveOnnxRuntimeNodeDir: () => string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the onnxruntime-node copy that will actually load ships a CUDA
|
|
16
|
+
* provider matching this host's CUDA major — reads straight from the cached
|
|
17
|
+
* `decide()` result rather than re-probing `ortCudaMajor`/`detectSystemCudaMajor`
|
|
18
|
+
* a second time (both are already computed above). `systemMajor` is checked
|
|
19
|
+
* for non-null explicitly so two absent majors (null === null) never count
|
|
20
|
+
* as a match.
|
|
21
|
+
*/
|
|
22
|
+
export declare const isEffectiveCudaAvailable: () => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* CUDA-build-redirect status for the `doctor` Embeddings section — pure
|
|
25
|
+
* summary of decide()'s already-computed decision, matching
|
|
26
|
+
* doctor.ts's `localEmbeddingDoctorStatus`'s `{status, detail}` shape so an
|
|
27
|
+
* operator can tell "why is my CUDA-13 host still on CPU" apart from
|
|
28
|
+
* "there's no system CUDA to redirect for" at a glance.
|
|
29
|
+
*/
|
|
30
|
+
export declare const cudaRedirectDoctorStatus: () => {
|
|
31
|
+
status: string;
|
|
32
|
+
detail: string | null;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Idempotently install the CUDA-build-matching redirect. Call once immediately
|
|
36
|
+
* before the dynamic `import('@huggingface/transformers')` on the local
|
|
37
|
+
* embedding path (after the runtime guard, alongside the onnxruntime-common
|
|
38
|
+
* fallback). No-op unless a strictly-better matching copy exists.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ensureOnnxRuntimeNodeMatchesSystem: () => void;
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redirect `@huggingface/transformers`' `onnxruntime-node` import to whichever
|
|
3
|
+
* bundled copy's CUDA build matches this host's CUDA runtime (CUDA 12 vs 13).
|
|
4
|
+
*
|
|
5
|
+
* ## Why
|
|
6
|
+
* transformers exact-pins `onnxruntime-node` (e.g. `1.24.3`, a CUDA **12**
|
|
7
|
+
* build), while gitnexus' own `onnxruntime-node: ^1.24.0` floats to the latest
|
|
8
|
+
* 1.x (a CUDA **13** build). npm/pnpm cannot dedupe an exact pin against a
|
|
9
|
+
* range, so a `npm i -g` install ends up with TWO copies: gitnexus' top-level
|
|
10
|
+
* CUDA-13 build (unused) and transformers' nested CUDA-12 build (the one that
|
|
11
|
+
* actually loads). gitnexus' `overrides` block that would collapse them is
|
|
12
|
+
* honoured only from a *root* manifest, so it is inert once gitnexus is a
|
|
13
|
+
* dependency — the same transitive-override limitation documented in
|
|
14
|
+
* {@link ./onnxruntime-common-resolver.ts} (#307).
|
|
15
|
+
*
|
|
16
|
+
* The consequence on a CUDA-13-only host: the nested CUDA-12 provider cannot
|
|
17
|
+
* find `libcublasLt.so.12`, the CUDA execution provider fails to load, and
|
|
18
|
+
* embeddings silently fall back to CPU (~5-6x slower) even with
|
|
19
|
+
* `--embedding-device cuda`.
|
|
20
|
+
*
|
|
21
|
+
* ## What this does
|
|
22
|
+
* Best-effort, before transformers is imported: if the system's cuBLASLt major
|
|
23
|
+
* (12 or 13) does NOT match the CUDA build transformers would load by default,
|
|
24
|
+
* but gitnexus' own top-level `onnxruntime-node` copy DOES match, install a
|
|
25
|
+
* synchronous ESM resolution hook (`module.registerHooks`, Node >= 22.15) that
|
|
26
|
+
* redirects both `onnxruntime-node` and `onnxruntime-common` to that matching
|
|
27
|
+
* copy. onnxruntime-common is redirected alongside so the `Tensor` surface
|
|
28
|
+
* stays a single identity, version-matched to the redirected binding.
|
|
29
|
+
*
|
|
30
|
+
* ## Safety
|
|
31
|
+
* Detection-based and conservative — it acts ONLY when it is a net improvement:
|
|
32
|
+
* - system CUDA major == default build major -> NO-OP (already correct)
|
|
33
|
+
* - no system CUDA libs / non-linux -> NO-OP (CPU path)
|
|
34
|
+
* - only one copy present -> NO-OP
|
|
35
|
+
* - neither copy matches the system -> NO-OP (never makes it worse)
|
|
36
|
+
* So CUDA-12 hosts, Windows (DirectML), macOS, and CPU-only hosts are
|
|
37
|
+
* untouched. Idempotent; any failure is swallowed and leaves the default
|
|
38
|
+
* resolution exactly as before. `module.registerHooks` requires Node >= 22.15
|
|
39
|
+
* (the gitnexus engines floor is >= 22.0.0); on older runtimes the redirect is
|
|
40
|
+
* a no-op, but the default copy's CUDA major is still probed so an
|
|
41
|
+
* already-matching host (e.g. CUDA 12 + transformers' CUDA-12 build) keeps
|
|
42
|
+
* auto-selecting the GPU.
|
|
43
|
+
* `npm link` / symlinked local-dev checkouts are a known caveat: `resolveOurOrtNodeDir`/
|
|
44
|
+
* `resolveDefaultOrtNodeDir` are anchored to this module's own real (post-symlink)
|
|
45
|
+
* location via `import.meta.url`, so a linked dev checkout may resolve against
|
|
46
|
+
* its own `node_modules` rather than the consuming app's — narrow, dev-only
|
|
47
|
+
* blast radius; regular npm/pnpm installs are unaffected.
|
|
48
|
+
*
|
|
49
|
+
* The CUDA-major decision is exposed via {@link getEffectiveOnnxRuntimeNodeDir}
|
|
50
|
+
* so the embedder's CUDA probe can inspect the SAME copy that will actually be
|
|
51
|
+
* loaded (the probe uses CJS `require.resolve`, which an ESM hook does not
|
|
52
|
+
* affect) — keeping probe and runtime consistent.
|
|
53
|
+
*/
|
|
54
|
+
import { registerHooks, createRequire } from 'node:module';
|
|
55
|
+
import { pathToFileURL } from 'node:url';
|
|
56
|
+
import { existsSync } from 'node:fs';
|
|
57
|
+
import { join, dirname } from 'node:path';
|
|
58
|
+
import { execFileSync } from 'node:child_process';
|
|
59
|
+
import { logger } from '../logger.js';
|
|
60
|
+
const require = createRequire(import.meta.url);
|
|
61
|
+
/**
|
|
62
|
+
* Read a shared object's NEEDED entries, tolerating ldd's non-zero exit when a
|
|
63
|
+
* lib is unresolved (that case still yields a usable "=> not found" stdout).
|
|
64
|
+
* `failed: true` means ldd produced no usable output at all (missing `ldd`
|
|
65
|
+
* binary, permission-denied `.so`, sandboxed exec) — distinct from "ldd ran
|
|
66
|
+
* fine and simply found no matching NEEDED entry" (`failed: false`, `needed: ''`),
|
|
67
|
+
* so callers don't have to treat "detection failed" identically to "definitely
|
|
68
|
+
* no CUDA provider".
|
|
69
|
+
*/
|
|
70
|
+
const readSoNeeded = (soPath) => {
|
|
71
|
+
try {
|
|
72
|
+
return {
|
|
73
|
+
needed: execFileSync('ldd', [soPath], {
|
|
74
|
+
timeout: 5000,
|
|
75
|
+
encoding: 'utf-8',
|
|
76
|
+
windowsHide: true,
|
|
77
|
+
}),
|
|
78
|
+
failed: false,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
const out = err?.stdout;
|
|
83
|
+
if (typeof out === 'string' && out.length > 0)
|
|
84
|
+
return { needed: out, failed: false };
|
|
85
|
+
return { needed: '', failed: true };
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
/** The CUDA major an onnxruntime-node copy's CUDA provider links against, or null (Linux/x64 only ships one). */
|
|
89
|
+
export const ortCudaMajor = (ortNodeDir) => {
|
|
90
|
+
const so = join(ortNodeDir, 'bin', 'napi-v6', 'linux', process.arch, 'libonnxruntime_providers_cuda.so');
|
|
91
|
+
// A pre-PR CUDA-12 host relied only on this existence check (no `ldd`
|
|
92
|
+
// dependency) — retained here as the first, unconditional signal so a host
|
|
93
|
+
// whose CUDA provider `.so` is genuinely present but merely un-inspectable
|
|
94
|
+
// (see the `failed` case below) is never treated identically to a host that
|
|
95
|
+
// never shipped a CUDA provider at all.
|
|
96
|
+
if (!existsSync(so))
|
|
97
|
+
return null;
|
|
98
|
+
const { needed, failed } = readSoNeeded(so);
|
|
99
|
+
if (failed) {
|
|
100
|
+
logger.warn({ so }, 'Could not read CUDA provider dependencies (ldd failed to run) — CUDA-major detection ' +
|
|
101
|
+
'is unknown, not necessarily absent; embeddings will fall back to CPU either way');
|
|
102
|
+
}
|
|
103
|
+
if (/libcublasLt\.so\.13/.test(needed))
|
|
104
|
+
return 13;
|
|
105
|
+
if (/libcublasLt\.so\.12/.test(needed))
|
|
106
|
+
return 12;
|
|
107
|
+
return null;
|
|
108
|
+
};
|
|
109
|
+
/** The cuBLASLt major installed on this system, or null. Linux only. */
|
|
110
|
+
export const detectSystemCudaMajor = () => {
|
|
111
|
+
if (process.platform !== 'linux')
|
|
112
|
+
return null;
|
|
113
|
+
try {
|
|
114
|
+
const out = execFileSync('ldconfig', ['-p'], {
|
|
115
|
+
timeout: 3000,
|
|
116
|
+
encoding: 'utf-8',
|
|
117
|
+
windowsHide: true,
|
|
118
|
+
});
|
|
119
|
+
if (out.includes('libcublasLt.so.13'))
|
|
120
|
+
return 13;
|
|
121
|
+
if (out.includes('libcublasLt.so.12'))
|
|
122
|
+
return 12;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// ldconfig not available (e.g. non-standard container) — fall through to path scan.
|
|
126
|
+
}
|
|
127
|
+
// Prefer CUDA 13 across the ENTIRE search space, not just within one
|
|
128
|
+
// dir/sub pair — a `.so.12` found early (e.g. a stale CUDA_PATH entry from
|
|
129
|
+
// a prior install) must not shadow a genuine `.so.13` found later in
|
|
130
|
+
// LD_LIBRARY_PATH. Return immediately on a 13 (the best possible answer);
|
|
131
|
+
// remember a 12 and keep scanning in case a later entry still has a 13.
|
|
132
|
+
let found = null;
|
|
133
|
+
for (const envVar of ['CUDA_PATH', 'LD_LIBRARY_PATH']) {
|
|
134
|
+
const val = process.env[envVar];
|
|
135
|
+
if (!val)
|
|
136
|
+
continue;
|
|
137
|
+
for (const dir of val.split(':').filter(Boolean))
|
|
138
|
+
for (const sub of ['lib64', 'lib', ''])
|
|
139
|
+
for (const maj of [13, 12])
|
|
140
|
+
if (existsSync(join(dir, sub, `libcublasLt.so.${maj}`))) {
|
|
141
|
+
if (maj === 13)
|
|
142
|
+
return 13;
|
|
143
|
+
found = maj;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return found;
|
|
147
|
+
};
|
|
148
|
+
/** onnxruntime-node dir transformers loads by default (its own nested/pinned copy). */
|
|
149
|
+
const resolveDefaultOrtNodeDir = () => {
|
|
150
|
+
try {
|
|
151
|
+
const transformersMain = require.resolve('@huggingface/transformers');
|
|
152
|
+
return dirname(createRequire(transformersMain).resolve('onnxruntime-node/package.json'));
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
/** gitnexus' own direct top-level onnxruntime-node dir. */
|
|
159
|
+
const resolveOurOrtNodeDir = () => {
|
|
160
|
+
try {
|
|
161
|
+
return dirname(require.resolve('onnxruntime-node/package.json'));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
let cached = null;
|
|
168
|
+
const decide = () => {
|
|
169
|
+
if (cached)
|
|
170
|
+
return cached;
|
|
171
|
+
const defaultDir = resolveDefaultOrtNodeDir();
|
|
172
|
+
// Node < 22.15 has no `registerHooks` API, so a redirect can never actually
|
|
173
|
+
// install (see ensureOnnxRuntimeNodeMatchesSystem below) — the probe must
|
|
174
|
+
// agree with that up front, never reporting a redirect target that won't be
|
|
175
|
+
// loaded. But the DEFAULT copy still loads and needs no hook, so its CUDA
|
|
176
|
+
// major is still probed: a CUDA-12 host on Node 22.0–22.14 whose default
|
|
177
|
+
// build already matches must keep auto-selecting the GPU exactly as it did
|
|
178
|
+
// before this redirect existed.
|
|
179
|
+
const canRedirect = typeof registerHooks === 'function';
|
|
180
|
+
const systemMajor = detectSystemCudaMajor();
|
|
181
|
+
// `defaultDir` resolving is NOT a precondition for checking `ourDir` below —
|
|
182
|
+
// if transformers' own resolution fails outright (defaultMajor stays null),
|
|
183
|
+
// that still counts as "the default doesn't match", so a working `ourDir`
|
|
184
|
+
// should still be picked up as the effective target instead of leaving
|
|
185
|
+
// `effectiveDir` stuck at `null`. Gated behind `systemMajor != null` (as
|
|
186
|
+
// before) so a non-CUDA host never pays for a provider-.so probe at all.
|
|
187
|
+
const defaultMajor = systemMajor != null && defaultDir ? ortCudaMajor(defaultDir) : null;
|
|
188
|
+
let decision = {
|
|
189
|
+
redirect: false,
|
|
190
|
+
effectiveDir: defaultDir,
|
|
191
|
+
effectiveMajor: defaultMajor,
|
|
192
|
+
systemMajor,
|
|
193
|
+
};
|
|
194
|
+
if (canRedirect && systemMajor != null && defaultMajor !== systemMajor) {
|
|
195
|
+
const ourDir = resolveOurOrtNodeDir();
|
|
196
|
+
if (ourDir && ourDir !== defaultDir) {
|
|
197
|
+
const ourMajor = ortCudaMajor(ourDir);
|
|
198
|
+
if (ourMajor === systemMajor) {
|
|
199
|
+
decision = { redirect: true, effectiveDir: ourDir, effectiveMajor: ourMajor, systemMajor };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
cached = decision;
|
|
204
|
+
return decision;
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* The onnxruntime-node dir that will actually back transformers at runtime once
|
|
208
|
+
* {@link ensureOnnxRuntimeNodeMatchesSystem} has run — i.e. the redirected copy
|
|
209
|
+
* when a redirect applies, otherwise transformers' default. The CUDA probe must
|
|
210
|
+
* inspect THIS dir (not transformers' CJS-resolved default) so probe and
|
|
211
|
+
* runtime agree. Returns null only when neither copy resolves.
|
|
212
|
+
*/
|
|
213
|
+
export const getEffectiveOnnxRuntimeNodeDir = () => decide().effectiveDir;
|
|
214
|
+
/**
|
|
215
|
+
* Whether the onnxruntime-node copy that will actually load ships a CUDA
|
|
216
|
+
* provider matching this host's CUDA major — reads straight from the cached
|
|
217
|
+
* `decide()` result rather than re-probing `ortCudaMajor`/`detectSystemCudaMajor`
|
|
218
|
+
* a second time (both are already computed above). `systemMajor` is checked
|
|
219
|
+
* for non-null explicitly so two absent majors (null === null) never count
|
|
220
|
+
* as a match.
|
|
221
|
+
*/
|
|
222
|
+
export const isEffectiveCudaAvailable = () => {
|
|
223
|
+
const d = decide();
|
|
224
|
+
return d.systemMajor !== null && d.systemMajor === d.effectiveMajor;
|
|
225
|
+
};
|
|
226
|
+
/**
|
|
227
|
+
* CUDA-build-redirect status for the `doctor` Embeddings section — pure
|
|
228
|
+
* summary of decide()'s already-computed decision, matching
|
|
229
|
+
* doctor.ts's `localEmbeddingDoctorStatus`'s `{status, detail}` shape so an
|
|
230
|
+
* operator can tell "why is my CUDA-13 host still on CPU" apart from
|
|
231
|
+
* "there's no system CUDA to redirect for" at a glance.
|
|
232
|
+
*/
|
|
233
|
+
export const cudaRedirectDoctorStatus = () => {
|
|
234
|
+
const d = decide();
|
|
235
|
+
if (d.systemMajor === null) {
|
|
236
|
+
return { status: 'n/a (no system CUDA detected)', detail: null };
|
|
237
|
+
}
|
|
238
|
+
if (d.redirect) {
|
|
239
|
+
return {
|
|
240
|
+
status: `✓ redirected onnxruntime-node to the CUDA ${d.systemMajor} build`,
|
|
241
|
+
detail: d.effectiveDir,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
if (d.systemMajor === d.effectiveMajor) {
|
|
245
|
+
return {
|
|
246
|
+
status: `✓ default onnxruntime-node build already matches CUDA ${d.systemMajor}`,
|
|
247
|
+
detail: null,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
status: `✗ no CUDA ${d.systemMajor}-matched onnxruntime-node build found (falling back to CPU)`,
|
|
252
|
+
detail: d.effectiveDir,
|
|
253
|
+
};
|
|
254
|
+
};
|
|
255
|
+
let attempted = false;
|
|
256
|
+
/**
|
|
257
|
+
* Idempotently install the CUDA-build-matching redirect. Call once immediately
|
|
258
|
+
* before the dynamic `import('@huggingface/transformers')` on the local
|
|
259
|
+
* embedding path (after the runtime guard, alongside the onnxruntime-common
|
|
260
|
+
* fallback). No-op unless a strictly-better matching copy exists.
|
|
261
|
+
*/
|
|
262
|
+
export const ensureOnnxRuntimeNodeMatchesSystem = () => {
|
|
263
|
+
if (attempted)
|
|
264
|
+
return;
|
|
265
|
+
attempted = true;
|
|
266
|
+
try {
|
|
267
|
+
if (typeof registerHooks !== 'function')
|
|
268
|
+
return; // Node < 22.15: graceful no-op
|
|
269
|
+
const d = decide();
|
|
270
|
+
if (!d.redirect || !d.effectiveDir)
|
|
271
|
+
return;
|
|
272
|
+
const nodeUrl = pathToFileURL(createRequire(join(d.effectiveDir, 'package.json')).resolve('onnxruntime-node')).href;
|
|
273
|
+
let commonUrl = null;
|
|
274
|
+
try {
|
|
275
|
+
commonUrl = pathToFileURL(createRequire(join(d.effectiveDir, 'package.json')).resolve('onnxruntime-common')).href;
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
commonUrl = null; // fall back to the onnxruntime-common-resolver for common
|
|
279
|
+
}
|
|
280
|
+
registerHooks({
|
|
281
|
+
resolve(specifier, context, nextResolve) {
|
|
282
|
+
if (specifier === 'onnxruntime-node')
|
|
283
|
+
return { url: nodeUrl, shortCircuit: true };
|
|
284
|
+
if (commonUrl && specifier === 'onnxruntime-common')
|
|
285
|
+
return { url: commonUrl, shortCircuit: true };
|
|
286
|
+
return nextResolve(specifier, context);
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
// info (not debug): this is the one signal an operator has that CUDA
|
|
290
|
+
// embeddings are actually using the GPU on this host — the common/no-op
|
|
291
|
+
// paths below stay at debug since they're the expected default.
|
|
292
|
+
logger.info({ systemMajor: d.systemMajor, effectiveDir: d.effectiveDir }, 'Redirected onnxruntime-node to system-matched CUDA build');
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
logger.debug({ err: err instanceof Error ? err.message : String(err) }, 'onnxruntime-node CUDA-build redirect not installed');
|
|
296
|
+
}
|
|
297
|
+
};
|
|
@@ -9,6 +9,7 @@ import { resolveEmbeddingConfig } from '../../core/embeddings/config.js';
|
|
|
9
9
|
import { applyHfEnvOverrides, isHfDownloadFailure, withHfDownloadRetry, } from '../../core/embeddings/hf-env.js';
|
|
10
10
|
import { getLocalEmbeddingRuntimeBlocker } from '../../core/embeddings/runtime-support.js';
|
|
11
11
|
import { ensureOnnxRuntimeCommonResolvable } from '../../core/embeddings/onnxruntime-common-resolver.js';
|
|
12
|
+
import { ensureOnnxRuntimeNodeMatchesSystem } from '../../core/embeddings/onnxruntime-node-resolver.js';
|
|
12
13
|
import { silenceStdout, restoreStdout, realStderrWrite } from '../../core/lbug/pool-adapter.js';
|
|
13
14
|
import { logger } from '../../core/logger.js';
|
|
14
15
|
// Model config
|
|
@@ -47,6 +48,13 @@ export const initEmbedder = async () => {
|
|
|
47
48
|
// Under pnpm-strict / `pnpm dlx`, transformers' phantom `onnxruntime-common`
|
|
48
49
|
// import is unresolvable; register the fallback resolver first (#307).
|
|
49
50
|
ensureOnnxRuntimeCommonResolvable();
|
|
51
|
+
// Registered AFTER the common fallback so this hook resolves FIRST (Node
|
|
52
|
+
// runs the most-recently-registered hook first): on CUDA-13 hosts it
|
|
53
|
+
// redirects onnxruntime-node to the system-matched build before
|
|
54
|
+
// transformers imports it. No-op on matching layouts, non-CUDA,
|
|
55
|
+
// Windows/DirectML, and macOS. Mirrors the core embedder's call site so
|
|
56
|
+
// MCP query-time embedding gets the same CUDA-13 fix.
|
|
57
|
+
ensureOnnxRuntimeNodeMatchesSystem();
|
|
50
58
|
const { pipeline, env } = await import('@huggingface/transformers');
|
|
51
59
|
env.allowLocalModels = false;
|
|
52
60
|
// Bridge user-controlled env vars to transformers.js: HF_HOME →
|
package/dist/storage/git.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export declare const getCurrentCommit: (repoPath: string) => string;
|
|
|
4
4
|
* Get a stable canonical identifier for the repo's `origin` remote, if any.
|
|
5
5
|
*
|
|
6
6
|
* Used to fingerprint two on-disk clones as the same logical repository
|
|
7
|
-
* (
|
|
7
|
+
* (prevents silent graph drift across sibling clones — see #2054). `path` alone
|
|
8
8
|
* is unreliable: worktrees, "clean clone for indexing" hygiene, and
|
|
9
9
|
* multi-agent workspaces routinely have the same repo at multiple
|
|
10
10
|
* absolute paths. The remote URL is the only on-disk signal that
|
package/dist/storage/git.js
CHANGED
|
@@ -39,7 +39,7 @@ export const getCurrentCommit = (repoPath) => {
|
|
|
39
39
|
* Get a stable canonical identifier for the repo's `origin` remote, if any.
|
|
40
40
|
*
|
|
41
41
|
* Used to fingerprint two on-disk clones as the same logical repository
|
|
42
|
-
* (
|
|
42
|
+
* (prevents silent graph drift across sibling clones — see #2054). `path` alone
|
|
43
43
|
* is unreliable: worktrees, "clean clone for indexing" hygiene, and
|
|
44
44
|
* multi-agent workspaces routinely have the same repo at multiple
|
|
45
45
|
* absolute paths. The remote URL is the only on-disk signal that
|
package/package.json
CHANGED
|
@@ -47,6 +47,7 @@ const PLATFORM_LOGIC = [
|
|
|
47
47
|
'test/unit/ignore-service.test.ts',
|
|
48
48
|
'test/unit/group/bridge-db.test.ts',
|
|
49
49
|
'test/unit/group/bridge-db-edge.test.ts',
|
|
50
|
+
'test/unit/onnxruntime-node-resolver.test.ts',
|
|
50
51
|
];
|
|
51
52
|
|
|
52
53
|
// Native LadybugDB integration tests — exercise the @ladybugdb/core
|