Skip to content

Commit 275c005

Browse files
committed
feat: try compiling with LLVM on Windows x64 when available
for improved performance and compatibility
1 parent 602cb3c commit 275c005

File tree

7 files changed

+370
-43
lines changed

7 files changed

+370
-43
lines changed

llama/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ if (NLC_TARGET_PLATFORM STREQUAL "win-arm64" AND (CMAKE_GENERATOR STREQUAL "Ninj
88
get_filename_component(INCLUDE_PROFILE_ABS "./profiles/llvm.win32.host-arm64.target-arm64.cmake" ABSOLUTE)
99
include("${INCLUDE_PROFILE_ABS}")
1010
endif()
11+
elseif (NLC_CURRENT_PLATFORM STREQUAL "win-x64" AND NLC_TARGET_PLATFORM STREQUAL "win-x64" AND (CMAKE_GENERATOR STREQUAL "Ninja" OR CMAKE_GENERATOR STREQUAL "Ninja Multi-Config") AND NOT MINGW)
12+
get_filename_component(INCLUDE_PROFILE_ABS "./profiles/llvm.win32.host-x64.target-x64.cmake" ABSOLUTE)
13+
include("${INCLUDE_PROFILE_ABS}")
1114
endif()
1215

1316
project("llama-addon" C CXX)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
set(PROGRAMFILES "$ENV{ProgramFiles}")
2+
set(PROGRAMFILES_X86 "$ENV{ProgramFiles\(x86\)}")
3+
set(PROGRAMFILES_PATHS
4+
"${PROGRAMFILES}"
5+
"${PROGRAMFILES_X86}"
6+
"C:/Program Files"
7+
"C:/Program Files (x86)"
8+
)
9+
10+
if (CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET)
11+
if (NOT DEFINED NODE_LIB_CMAKE_AR)
12+
foreach(PATH IN LISTS PROGRAMFILES_PATHS)
13+
if(NODE_LIB_CMAKE_AR)
14+
break()
15+
endif()
16+
17+
file(GLOB_RECURSE FOUND_LIB_EXE
18+
"${PATH}/Microsoft Visual Studio/*/VC/Tools/MSVC/*/bin/Hostx64/x64/lib.exe"
19+
"${PATH}/Microsoft Visual Studio/**/*/VC/Tools/MSVC/*/bin/Hostx64/x64/lib.exe")
20+
21+
if(FOUND_LIB_EXE)
22+
list(GET FOUND_LIB_EXE 0 NODE_LIB_CMAKE_AR)
23+
break()
24+
endif()
25+
endforeach()
26+
endif()
27+
28+
if (EXISTS "${NODE_LIB_CMAKE_AR}")
29+
# Generate node.lib
30+
execute_process(COMMAND ${NODE_LIB_CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS} /MACHINE:X64 /nologo)
31+
else()
32+
message(FATAL_ERROR "Windows Resource Compiler (lib.exe) not found. Please install Visual Studio Build Tools.")
33+
endif()
34+
endif()
35+
36+
# adapt cmake-js to work with llvm in GNU mode
37+
if (NOT CMAKE_SHARED_LINKER_FLAGS MATCHES "-Xlinker /DELAYLOAD:NODE.EXE")
38+
string(REPLACE "/DELAYLOAD:NODE.EXE" "-Xlinker /DELAYLOAD:NODE.EXE -Xlinker /defaultlib:delayimp"
39+
CMAKE_SHARED_LINKER_FLAGS
40+
"${CMAKE_SHARED_LINKER_FLAGS}")
41+
endif()
42+
43+
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
44+
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Xclang --dependent-lib=msvcrt")
45+
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Xclang --dependent-lib=msvcrt")
46+
47+
# ensure CMAKE_AR is configured
48+
if (NOT DEFINED CMAKE_AR OR NOT EXISTS "${CMAKE_AR}")
49+
set(LLVM_INSTALL_PATHS "")
50+
foreach(PATH IN LISTS PROGRAMFILES_PATHS)
51+
list(APPEND LLVM_INSTALL_PATHS "${PATH}/LLVM")
52+
53+
file(GLOB_RECURSE FOUND_LLVM_ROOT
54+
"${PATH}/Microsoft Visual Studio/*/VC/Tools/Llvm/x64"
55+
"${PATH}/Microsoft Visual Studio/**/*/VC/Tools/Llvm/x64")
56+
57+
if(FOUND_LLVM_ROOT)
58+
list(APPEND LLVM_INSTALL_PATHS ${FOUND_LLVM_ROOT})
59+
endif()
60+
endforeach()
61+
62+
if(DEFINED LLVM_ROOT AND EXISTS "${LLVM_ROOT}")
63+
list(INSERT LLVM_INSTALL_PATHS 0 "${LLVM_ROOT}")
64+
endif()
65+
66+
foreach(PATH IN LISTS LLVM_INSTALL_PATHS)
67+
if(EXISTS "${PATH}/bin/llvm-ar.exe" AND EXISTS "${PATH}/bin/llvm-ar.exe")
68+
set(CMAKE_AR "${PATH}/bin/llvm-ar.exe")
69+
break()
70+
endif()
71+
endforeach()
72+
endif()
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
set(CMAKE_SYSTEM_NAME Windows)
2+
set(CMAKE_SYSTEM_PROCESSOR x86_64)
3+
4+
set(CMAKE_C_COMPILER clang)
5+
set(CMAKE_CXX_COMPILER clang++)
6+
set(CMAKE_RC_COMPILER llvm-rc)
7+
8+
set(LLVM_INSTALLATION_URL "https://github.com/llvm/llvm-project/releases/tag/llvmorg-19.1.5")
9+
10+
set(PROGRAMFILES "$ENV{ProgramFiles}")
11+
set(PROGRAMFILES_X86 "$ENV{ProgramFiles\(x86\)}")
12+
set(PROGRAMFILES_PATHS
13+
"${PROGRAMFILES}"
14+
"${PROGRAMFILES_X86}"
15+
"C:/Program Files"
16+
"C:/Program Files (x86)"
17+
)
18+
19+
set(LLVM_INSTALL_PATHS "")
20+
foreach(PATH IN LISTS PROGRAMFILES_PATHS)
21+
list(APPEND LLVM_INSTALL_PATHS "${PATH}/LLVM")
22+
23+
file(GLOB_RECURSE FOUND_LLVM_ROOT
24+
"${PATH}/Microsoft Visual Studio/*/VC/Tools/Llvm/x64"
25+
"${PATH}/Microsoft Visual Studio/**/*/VC/Tools/Llvm/x64")
26+
27+
if(FOUND_LLVM_ROOT)
28+
list(APPEND LLVM_INSTALL_PATHS ${FOUND_LLVM_ROOT})
29+
endif()
30+
endforeach()
31+
32+
if(DEFINED LLVM_ROOT AND EXISTS "${LLVM_ROOT}")
33+
list(INSERT LLVM_INSTALL_PATHS 0 "${LLVM_ROOT}")
34+
endif()
35+
36+
set(LLVM_ROOT "")
37+
foreach(PATH IN LISTS LLVM_INSTALL_PATHS)
38+
if(EXISTS "${PATH}/bin/clang.exe" AND EXISTS "${PATH}/bin/clang++.exe" AND EXISTS "${PATH}/bin/llvm-rc.exe")
39+
set(LLVM_ROOT "${PATH}")
40+
break()
41+
endif()
42+
endforeach()
43+
44+
if(LLVM_ROOT STREQUAL "")
45+
message(FATAL_ERROR "LLVM installation was not found. Please install LLVM: ${LLVM_INSTALLATION_URL}")
46+
endif()
47+
48+
if (NOT EXISTS "${CMAKE_C_COMPILER}" OR NOT EXISTS "${CMAKE_CXX_COMPILER}" OR NOT EXISTS "${CMAKE_RC_COMPILER}")
49+
set(CMAKE_C_COMPILER "${LLVM_ROOT}/bin/clang.exe")
50+
set(CMAKE_CXX_COMPILER "${LLVM_ROOT}/bin/clang++.exe")
51+
set(CMAKE_RC_COMPILER "${LLVM_ROOT}/bin/llvm-rc.exe")
52+
endif()
53+
54+
if (NOT EXISTS "${CMAKE_C_COMPILER}")
55+
message(FATAL_ERROR "Clang compiler not found at ${CMAKE_C_COMPILER}. Please reinstall LLVM: ${LLVM_INSTALLATION_URL}")
56+
endif()
57+
if (NOT EXISTS "${CMAKE_CXX_COMPILER}")
58+
message(FATAL_ERROR "Clang++ compiler not found at ${CMAKE_CXX_COMPILER}. Please reinstall LLVM: ${LLVM_INSTALLATION_URL}")
59+
endif()
60+
if (NOT EXISTS "${CMAKE_RC_COMPILER}")
61+
message(FATAL_ERROR "LLVM Resource Compiler not found at ${CMAKE_RC_COMPILER}. Please reinstall LLVM: ${LLVM_INSTALLATION_URL}")
62+
endif()
63+
64+
set(arch_c_flags "-march=native")
65+
66+
set(CMAKE_C_FLAGS_INIT "${arch_c_flags}")
67+
set(CMAKE_CXX_FLAGS_INIT "${arch_c_flags}")
68+
69+
if ((NOT DEFINED CMAKE_MAKE_PROGRAM OR NOT EXISTS CMAKE_MAKE_PROGRAM) AND (CMAKE_GENERATOR STREQUAL "Ninja" OR CMAKE_GENERATOR STREQUAL "Ninja Multi-Config"))
70+
find_program(NINJA_EXECUTABLE ninja)
71+
72+
if(NINJA_EXECUTABLE AND EXISTS "${NINJA_EXECUTABLE}")
73+
set(CMAKE_MAKE_PROGRAM "${NINJA_EXECUTABLE}")
74+
else()
75+
foreach(PATH IN LISTS PROGRAMFILES_PATHS)
76+
file(GLOB_RECURSE FOUND_NINJA_EXE
77+
"${PATH}/Microsoft Visual Studio/*/CMake/Ninja/ninja.exe"
78+
"${PATH}/Microsoft Visual Studio/**/*/CMake/Ninja/ninja.exe")
79+
80+
if(FOUND_NINJA_EXE)
81+
list(GET FOUND_NINJA_EXE 0 CMAKE_MAKE_PROGRAM)
82+
break()
83+
endif()
84+
endforeach()
85+
endif()
86+
87+
if (NOT CMAKE_MAKE_PROGRAM OR NOT EXISTS "${CMAKE_MAKE_PROGRAM}")
88+
message(FATAL_ERROR "Ninja build system not found. Please install Ninja or Visual Studio Build Tools.")
89+
endif()
90+
endif()

src/bindings/utils/asyncSome.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import {getConsoleLogPrefix} from "../../utils/getConsoleLogPrefix.js";
55
* Note that this function will not throw on error and instead will log the error to the console.
66
*/
77
export async function asyncSome(promises: Promise<boolean>[]): Promise<boolean> {
8+
if (promises.length === 0)
9+
return Promise.resolve(false);
10+
811
return new Promise((resolve) => {
912
let fulfilled = 0;
1013

src/bindings/utils/compileLLamaCpp.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {BinaryPlatform, getPlatform} from "./getPlatform.js";
2222
import {logDistroInstallInstruction} from "./logDistroInstallInstruction.js";
2323
import {testCmakeBinary} from "./testCmakeBinary.js";
2424
import {getCudaNvccPaths} from "./detectAvailableComputeLayers.js";
25+
import {detectWindowsBuildTools} from "./detectBuildTools.js";
2526

2627
const __dirname = path.dirname(fileURLToPath(import.meta.url));
2728
const buildConfigType: "Release" | "RelWithDebInfo" | "Debug" = "Release";
@@ -32,7 +33,7 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
3233
includeBuildOptionsInBinaryFolderName?: boolean,
3334
ensureLlamaCppRepoIsCloned?: boolean,
3435
downloadCmakeIfNeeded?: boolean,
35-
ignoreWorkarounds?: ("cudaArchitecture" | "reduceParallelBuildThreads" | "singleBuildThread")[],
36+
ignoreWorkarounds?: ("cudaArchitecture" | "reduceParallelBuildThreads" | "singleBuildThread" | "avoidWindowsLlvm")[],
3637
envVars?: typeof process.env,
3738
ciMode?: boolean
3839
}): Promise<void> {
@@ -52,6 +53,9 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
5253
const finalBuildFolderName = includeBuildOptionsInBinaryFolderName
5354
? buildFolderName.withCustomCmakeOptions
5455
: buildFolderName.withoutCustomCmakeOptions;
56+
const useWindowsLlvm = (platform === "win" && !ignoreWorkarounds.includes("avoidWindowsLlvm"))
57+
? areWindowsBuildToolsCapableForLlvmBuild(await detectWindowsBuildTools())
58+
: false;
5559

5660
const outDirectory = path.join(llamaLocalBuildBinsDirectory, finalBuildFolderName);
5761

@@ -76,8 +80,8 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
7680
await downloadCmakeIfNeeded(buildOptions.progressLogs);
7781

7882
const cmakePathArgs = await getCmakePathArgs();
79-
const cmakeGeneratorArgs = getCmakeGeneratorArgs(buildOptions.platform, buildOptions.arch);
80-
const toolchainFile = await getToolchainFileForArch(buildOptions.arch);
83+
const cmakeGeneratorArgs = getCmakeGeneratorArgs(buildOptions.platform, buildOptions.arch, useWindowsLlvm);
84+
const toolchainFile = await getToolchainFileForArch(buildOptions.arch, useWindowsLlvm);
8185
const runtimeVersion = nodeTarget.startsWith("v") ? nodeTarget.slice("v".length) : nodeTarget;
8286
const cmakeCustomOptions = new Map(buildOptions.customCmakeOptions);
8387

@@ -306,6 +310,20 @@ export async function compileLlamaCpp(buildOptions: BuildOptions, compileOptions
306310
chalk.yellow("To resolve errors related to Vulkan compilation, see the Vulkan guide: ") +
307311
documentationPageUrls.Vulkan
308312
);
313+
else if (useWindowsLlvm) {
314+
if (buildOptions.progressLogs)
315+
console.info(getConsoleLogPrefix(true) + "Trying to compile again without LLVM");
316+
317+
try {
318+
return await compileLlamaCpp(buildOptions, {
319+
...compileOptions,
320+
ignoreWorkarounds: [...ignoreWorkarounds, "avoidWindowsLlvm"]
321+
});
322+
} catch (err) {
323+
if (buildOptions.progressLogs)
324+
console.error(getConsoleLogPrefix(true, false), err);
325+
}
326+
}
309327

310328
throw err;
311329
}
@@ -498,14 +516,20 @@ async function getCmakePathArgs() {
498516
return ["--cmake-path", cmakePath];
499517
}
500518

501-
async function getToolchainFileForArch(targetArch: string) {
502-
if (process.arch === targetArch || (process.platform === "win32" && process.arch === "arm64"))
519+
async function getToolchainFileForArch(targetArch: string, windowsLlvmSupport: boolean = false) {
520+
let toolchainPrefix = "";
521+
522+
if (process.platform === "win32" && process.arch === "arm64") {
523+
// a toolchain is needed to cross-compile to arm64 on Windows, and to compile on arm64 on Windows
524+
} else if (process.platform === "win32" && process.arch === "x64" && targetArch === "x64" && windowsLlvmSupport) {
525+
toolchainPrefix = "llvm.";
526+
} else if (process.arch === targetArch)
503527
return null;
504528

505529
const platform = process.platform;
506530
const hostArch = process.arch;
507531

508-
const toolchainFilename = `${platform}.host-${hostArch}.target-${targetArch}.cmake`;
532+
const toolchainFilename = `${toolchainPrefix}${platform}.host-${hostArch}.target-${targetArch}.cmake`;
509533

510534
const filePath = path.join(llamaToolchainsDirectory, toolchainFilename);
511535

@@ -515,9 +539,11 @@ async function getToolchainFileForArch(targetArch: string) {
515539
return null;
516540
}
517541

518-
function getCmakeGeneratorArgs(targetPlatform: BinaryPlatform, targetArch: string) {
542+
function getCmakeGeneratorArgs(targetPlatform: BinaryPlatform, targetArch: string, windowsLlvmSupport: boolean) {
519543
if (targetPlatform === "win" && targetArch === "arm64")
520544
return ["--generator", "Ninja Multi-Config"];
545+
else if (windowsLlvmSupport && targetPlatform === "win" && process.arch === "x64" && targetArch === "x64")
546+
return ["--generator", "Ninja Multi-Config"];
521547

522548
return [];
523549
}
@@ -544,3 +570,7 @@ function reduceParallelBuildThreads(originalParallelBuildThreads: number) {
544570
function isCmakeValueOff(value?: string) {
545571
return value === "OFF" || value === "0";
546572
}
573+
574+
function areWindowsBuildToolsCapableForLlvmBuild(detectedBuildTools: Awaited<ReturnType<typeof detectWindowsBuildTools>>) {
575+
return detectedBuildTools.hasLlvm && detectedBuildTools.hasNinja && detectedBuildTools.hasLibExe;
576+
}

0 commit comments

Comments
 (0)