Skip to content

Commit 904b4e2

Browse files
committed
fix: perform a separate MSVC build on Windows
1 parent fa71485 commit 904b4e2

File tree

1 file changed

+110
-24
lines changed

1 file changed

+110
-24
lines changed

src/bindings/utils/compileLLamaCpp.ts

Lines changed: 110 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import {detectWindowsBuildTools} from "./detectBuildTools.js";
2727
const __dirname = path.dirname(fileURLToPath(import.meta.url));
2828
const buildConfigType: "Release" | "RelWithDebInfo" | "Debug" = "Release";
2929

30+
const windowsMsvcOnlyBuildFlagsToTargets = new Map(
31+
["blas", "cann", "cuda", "hip", "kompute", "metal", "musa", "sycl", "vulkan", "opencl"]
32+
.map((backend) => ["GGML_" + backend.toUpperCase(), "ggml-" + backend.toLowerCase()])
33+
);
34+
3035
export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions: {
3136
nodeTarget?: string,
3237
updateLastBuildInfo?: boolean,
@@ -53,7 +58,11 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
5358
const finalBuildFolderName = includeBuildOptionsInBinaryFolderName
5459
? buildFolderName.withCustomCmakeOptions
5560
: buildFolderName.withoutCustomCmakeOptions;
56-
const useWindowsLlvm = (platform === "win" && !ignoreWorkarounds.includes("avoidWindowsLlvm"))
61+
const useWindowsLlvm = (
62+
platform === "win" &&
63+
!ignoreWorkarounds.includes("avoidWindowsLlvm") &&
64+
!buildOptions.customCmakeOptions.has("CMAKE_TOOLCHAIN_FILE")
65+
)
5766
? areWindowsBuildToolsCapableForLlvmBuild(await detectWindowsBuildTools())
5867
: false;
5968

@@ -84,6 +93,8 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
8493
const toolchainFile = await getToolchainFileForArch(buildOptions.arch, useWindowsLlvm);
8594
const runtimeVersion = nodeTarget.startsWith("v") ? nodeTarget.slice("v".length) : nodeTarget;
8695
const cmakeCustomOptions = new Map(buildOptions.customCmakeOptions);
96+
const cmakeToolchainOptions = new Map<string, string>();
97+
const windowsSeparateMsvcCmakeOptions = new Map<string, string>();
8798

8899
cmakeCustomOptions.set("CMAKE_CONFIGURATION_TYPES", buildConfigType);
89100
cmakeCustomOptions.set("NLC_CURRENT_PLATFORM", platform + "-" + process.arch);
@@ -104,7 +115,7 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
104115
cmakeCustomOptions.set("GGML_CCACHE", "OFF");
105116

106117
if (toolchainFile != null && !cmakeCustomOptions.has("CMAKE_TOOLCHAIN_FILE"))
107-
cmakeCustomOptions.set("CMAKE_TOOLCHAIN_FILE", toolchainFile);
118+
cmakeToolchainOptions.set("CMAKE_TOOLCHAIN_FILE", toolchainFile);
108119

109120
if (buildOptions.platform === "win" && buildOptions.arch === "arm64" && !cmakeCustomOptions.has("GGML_OPENMP"))
110121
cmakeCustomOptions.set("GGML_OPENMP", "OFF");
@@ -124,6 +135,16 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
124135
}
125136
}
126137

138+
if (useWindowsLlvm) {
139+
for (const [customFlag, customFlagValue] of [...cmakeCustomOptions.entries()]) {
140+
if (!windowsMsvcOnlyBuildFlagsToTargets.has(customFlag) || isCmakeValueOff(customFlagValue))
141+
continue;
142+
143+
windowsSeparateMsvcCmakeOptions.set(customFlag, customFlagValue);
144+
cmakeCustomOptions.delete(customFlag);
145+
}
146+
}
147+
127148
await fs.remove(outDirectory);
128149

129150
await spawnCommand(
@@ -152,38 +173,62 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
152173
...cmakeGeneratorArgs,
153174
...cmakePathArgs,
154175
...(
155-
[...cmakeCustomOptions].map(([key, value]) => "--CD" + key + "=" + value)
176+
[
177+
...cmakeCustomOptions,
178+
...cmakeToolchainOptions
179+
].map(([key, value]) => "--CD" + key + "=" + value)
156180
)
157181
],
158182
__dirname,
159183
envVars,
160184
buildOptions.progressLogs
161185
);
162186

163-
const binFilesDirPaths = [
164-
path.join(outDirectory, "bin"),
165-
path.join(outDirectory, "llama.cpp", "bin")
166-
];
167-
const compiledResultDirPath = path.join(outDirectory, buildConfigType);
168-
169-
if (!await fs.pathExists(compiledResultDirPath))
170-
throw new Error(`Could not find ${buildConfigType} directory`);
171-
172-
for (const binFilesDirPath of binFilesDirPaths) {
173-
if (await fs.pathExists(binFilesDirPath)) {
174-
const itemNames = await fs.readdir(binFilesDirPath);
175-
176-
await Promise.all(
177-
itemNames.map((itemName) => (
178-
fs.copy(path.join(binFilesDirPath, itemName), path.join(compiledResultDirPath, itemName), {
179-
overwrite: false
180-
})
181-
))
187+
const compiledResultDirPath = await moveBuildFilesToResultDir(outDirectory);
188+
189+
// perform a separate MSVC build and combine the compiled backends into the final build
190+
if (useWindowsLlvm && windowsSeparateMsvcCmakeOptions.size > 0) {
191+
const llvmResultDir = path.join(outDirectory, "_llvm" + buildConfigType);
192+
await fs.move(compiledResultDirPath, llvmResultDir);
193+
194+
for (const [targetFlag, targetValue] of windowsSeparateMsvcCmakeOptions) {
195+
const targetName = windowsMsvcOnlyBuildFlagsToTargets.get(targetFlag);
196+
if (targetName == null)
197+
continue;
198+
199+
console.info(getConsoleLogPrefix(true, false), "Building specialized GPU backends using MSVC: " + targetName);
200+
201+
await fs.remove(compiledResultDirPath);
202+
await spawnCommand(
203+
"npm",
204+
[
205+
"run", "-s", "cmake-js-llama", "--", "compile",
206+
"--log-level", "warn",
207+
"--config", buildConfigType,
208+
"--arch=" + buildOptions.arch,
209+
"--out", path.relative(llamaDirectory, outDirectory),
210+
"--runtime-version=" + runtimeVersion,
211+
"--parallel=" + parallelBuildThreads,
212+
"--target", targetName,
213+
...cmakePathArgs,
214+
...(
215+
[
216+
...cmakeCustomOptions,
217+
[targetFlag, targetValue]
218+
].map(([key, value]) => "--CD" + key + "=" + value)
219+
)
220+
],
221+
__dirname,
222+
envVars,
223+
buildOptions.progressLogs
182224
);
225+
const targetCompileResultDir = await moveBuildFilesToResultDir(outDirectory);
226+
await mergeDirWithoutOverrides(targetCompileResultDir, compiledResultDirPath);
183227
}
184-
}
185228

186-
await applyResultDirFixes(compiledResultDirPath, path.join(outDirectory, "_temp"));
229+
await fs.remove(compiledResultDirPath);
230+
await fs.move(llvmResultDir, compiledResultDirPath);
231+
}
187232

188233
await fs.writeFile(path.join(compiledResultDirPath, buildMetadataFileName), JSON.stringify({
189234
buildOptions: convertBuildOptionsToBuildOptionsJSON(buildOptions)
@@ -399,6 +444,35 @@ export async function getPrebuiltBinaryBuildMetadata(folderPath: string, folderN
399444
return buildMetadata;
400445
}
401446

447+
async function moveBuildFilesToResultDir(outDirectory: string) {
448+
const binFilesDirPaths = [
449+
path.join(outDirectory, "bin"),
450+
path.join(outDirectory, "llama.cpp", "bin")
451+
];
452+
const compiledResultDirPath = path.join(outDirectory, buildConfigType);
453+
454+
if (!await fs.pathExists(compiledResultDirPath))
455+
throw new Error(`Could not find ${buildConfigType} directory`);
456+
457+
for (const binFilesDirPath of binFilesDirPaths) {
458+
if (await fs.pathExists(binFilesDirPath)) {
459+
const itemNames = await fs.readdir(binFilesDirPath);
460+
461+
await Promise.all(
462+
itemNames.map((itemName) => (
463+
fs.copy(path.join(binFilesDirPath, itemName), path.join(compiledResultDirPath, itemName), {
464+
overwrite: false
465+
})
466+
))
467+
);
468+
}
469+
}
470+
471+
await applyResultDirFixes(compiledResultDirPath, path.join(outDirectory, "_temp"));
472+
473+
return compiledResultDirPath;
474+
}
475+
402476
async function applyResultDirFixes(resultDirPath: string, tempDirPath: string) {
403477
const releaseDirPath = path.join(resultDirPath, buildConfigType);
404478

@@ -420,6 +494,18 @@ async function applyResultDirFixes(resultDirPath: string, tempDirPath: string) {
420494
}
421495
}
422496

497+
async function mergeDirWithoutOverrides(sourceDirPath: string, targetDirPath: string) {
498+
const itemNames = await fs.readdir(sourceDirPath);
499+
500+
await Promise.all(
501+
itemNames.map((itemName) => (
502+
fs.move(path.join(sourceDirPath, itemName), path.join(targetDirPath, itemName), {
503+
overwrite: false
504+
})
505+
))
506+
);
507+
}
508+
423509
async function resolvePrebuiltBinaryPath(prebuiltBinaryDirectoryPath: string) {
424510
const binaryPath = path.join(prebuiltBinaryDirectoryPath, "llama-addon.node");
425511
const buildMetadataFilePath = path.join(prebuiltBinaryDirectoryPath, buildMetadataFileName);

0 commit comments

Comments
 (0)