Skip to content

Commit a804233

Browse files
authored
feat: add Pyodide wheel support (abetlen#2309)
* feat: update llama.cpp to 6eab47181 * feat: add Pyodide wheel support * docs: fix Pyodide changelog entry * feat: enable mtmd for emscripten
1 parent 822146b commit a804233

4 files changed

Lines changed: 111 additions & 10 deletions

File tree

.github/workflows/build-and-release.yaml

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,37 @@ jobs:
139139
name: wheels_riscv64
140140
path: ./wheelhouse/*.whl
141141

142+
build_wheels_pyodide:
143+
name: Build Pyodide wheel
144+
runs-on: ubuntu-latest
145+
steps:
146+
- uses: actions/checkout@v6
147+
with:
148+
submodules: "recursive"
149+
150+
- uses: actions/setup-python@v6
151+
with:
152+
python-version: "3.12"
153+
154+
- name: Build wheel
155+
uses: pypa/cibuildwheel@v4.1.0
156+
env:
157+
CIBW_PLATFORM: "pyodide"
158+
CIBW_BUILD: "cp314-pyodide_wasm32"
159+
CIBW_BUILD_VERBOSITY: "1"
160+
CIBW_REPAIR_WHEEL_COMMAND: ""
161+
CIBW_BEFORE_TEST: "curl -L --fail --retry 3 -o /tmp/stories260K.gguf https://huggingface.co/ggml-org/models/resolve/main/tinyllamas/stories260K.gguf"
162+
CIBW_TEST_COMMAND: "python -c \"import llama_cpp.mtmd_cpp as mtmd; from llama_cpp import Llama; print('mtmd marker', mtmd.mtmd_default_marker().decode()); llm = Llama(model_path='/tmp/stories260K.gguf', n_ctx=64, n_batch=8, n_threads=1, verbose=False); print('loaded', llm.n_vocab(), llm.n_ctx()); print('generated', llm('Once upon a', max_tokens=1, temperature=0)['choices'][0]['text'])\""
163+
CMAKE_ARGS: "-DLLAMA_WASM_MEM64=OFF -DEMSCRIPTEN_SYSTEM_PROCESSOR=wasm32 -DGGML_NATIVE=OFF -DGGML_OPENMP=OFF -DGGML_METAL=OFF -DGGML_BLAS=OFF -DGGML_CUDA=OFF -DGGML_HIP=OFF -DGGML_VULKAN=OFF -DGGML_OPENCL=OFF -DGGML_RPC=OFF -DLLAMA_CURL=OFF -DLLAMA_BUILD_TESTS=OFF -DLLAMA_BUILD_EXAMPLES=OFF -DLLAMA_BUILD_TOOLS=OFF -DLLAMA_BUILD_SERVER=OFF"
164+
with:
165+
output-dir: wheelhouse
166+
167+
- name: Upload wheels as artifacts
168+
uses: actions/upload-artifact@v7
169+
with:
170+
name: wheels_pyodide
171+
path: ./wheelhouse/*.whl
172+
142173
build_sdist:
143174
name: Build source distribution
144175
runs-on: ubuntu-latest
@@ -183,7 +214,7 @@ jobs:
183214

184215
release:
185216
name: Release
186-
needs: [build_wheels, build_wheels_arm64, build_wheels_riscv64, build_sdist]
217+
needs: [build_wheels, build_wheels_arm64, build_wheels_riscv64, build_wheels_pyodide, build_sdist]
187218
if: startsWith(github.ref, 'refs/tags/')
188219
runs-on: ubuntu-latest
189220

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99

1010
- feat: update llama.cpp to ggml-org/llama.cpp@e3a74b299
11+
- feat: add Pyodide wheel support by @abetlen in #2309
1112

1213
## [0.3.29]
1314

CMakeLists.txt

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,22 @@ function(llama_cpp_python_install_target target)
1010
return()
1111
endif()
1212

13-
install(
14-
TARGETS ${target}
15-
LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
16-
RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
17-
ARCHIVE DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
18-
FRAMEWORK DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
19-
RESOURCE DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
20-
)
13+
if(EMSCRIPTEN)
14+
set_target_properties(${target} PROPERTIES
15+
OUTPUT_NAME "${target}.cpython-00-wasm32-emscripten"
16+
)
17+
endif()
18+
19+
if(NOT EMSCRIPTEN)
20+
install(
21+
TARGETS ${target}
22+
LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
23+
RUNTIME DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
24+
ARCHIVE DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
25+
FRAMEWORK DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
26+
RESOURCE DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/llama_cpp/lib
27+
)
28+
endif()
2129
install(
2230
TARGETS ${target}
2331
LIBRARY DESTINATION ${SKBUILD_PLATLIB_DIR}/llama_cpp/lib
@@ -65,6 +73,32 @@ if (LLAMA_BUILD)
6573
# Disable building curl support
6674
set(LLAMA_CURL OFF CACHE BOOL "llama.cpp: enable curl" FORCE)
6775

76+
if (EMSCRIPTEN)
77+
if (DEFINED EMSCRIPTEN_SYSTEM_PROCESSOR)
78+
set(CMAKE_SYSTEM_PROCESSOR ${EMSCRIPTEN_SYSTEM_PROCESSOR} CACHE STRING "Target processor" FORCE)
79+
else()
80+
set(CMAKE_SYSTEM_PROCESSOR wasm32 CACHE STRING "Target processor" FORCE)
81+
endif()
82+
83+
set(LLAMA_WASM_MEM64 OFF CACHE BOOL "llama.cpp: enable wasm64 memory" FORCE)
84+
set(GGML_NATIVE OFF CACHE BOOL "ggml: enable -march=native" FORCE)
85+
set(GGML_OPENMP OFF CACHE BOOL "ggml: use OpenMP" FORCE)
86+
set(GGML_METAL OFF CACHE BOOL "ggml: use Metal" FORCE)
87+
set(GGML_BLAS OFF CACHE BOOL "ggml: use BLAS" FORCE)
88+
set(GGML_CUDA OFF CACHE BOOL "ggml: use CUDA" FORCE)
89+
set(GGML_HIP OFF CACHE BOOL "ggml: use HIP" FORCE)
90+
set(GGML_VULKAN OFF CACHE BOOL "ggml: use Vulkan" FORCE)
91+
set(GGML_OPENCL OFF CACHE BOOL "ggml: use OpenCL" FORCE)
92+
set(GGML_RPC OFF CACHE BOOL "ggml: use RPC" FORCE)
93+
94+
# Pyodide auto-loads side modules from top-level site-packages/lib
95+
# before Python imports run, so keep upstream installs package-local.
96+
set(CMAKE_INSTALL_BINDIR llama_cpp/lib CACHE PATH "Install binaries" FORCE)
97+
set(CMAKE_INSTALL_INCLUDEDIR llama_cpp/include CACHE PATH "Install headers" FORCE)
98+
set(CMAKE_INSTALL_LIBDIR llama_cpp/lib CACHE PATH "Install libraries" FORCE)
99+
set(LLAMA_BUILD_COMMON OFF CACHE BOOL "Build llama.cpp common library" FORCE)
100+
endif()
101+
68102
# Architecture detection and settings for Apple platforms
69103
if (APPLE)
70104
# Get the target architecture

llama_cpp/_ctypes_extensions.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,22 @@
1919
from typing_extensions import TypeAlias
2020

2121

22+
_EMSCRIPTEN_SIDE_MODULE_SUFFIX = ".cpython-00-wasm32-emscripten.so"
23+
24+
2225
# Load the library
2326
def load_shared_library(lib_base_name: str, base_path: pathlib.Path):
2427
"""Platform independent shared library loader"""
2528
# Searching for the library in the current directory under the name "libllama" (default name
2629
# for llamacpp) and "llama" (default name for this repo)
2730
lib_paths: List[pathlib.Path] = []
2831
# Determine the file extension based on the platform
29-
if sys.platform.startswith("linux") or sys.platform.startswith("freebsd"):
32+
if sys.platform == "emscripten":
33+
# Use a CPython-style tag that Pyodide skips during package auto-load.
34+
lib_paths += [
35+
base_path / f"lib{lib_base_name}{_EMSCRIPTEN_SIDE_MODULE_SUFFIX}",
36+
]
37+
elif sys.platform.startswith("linux") or sys.platform.startswith("freebsd"):
3038
lib_paths += [
3139
base_path / f"lib{lib_base_name}.so",
3240
]
@@ -60,6 +68,33 @@ def load_shared_library(lib_base_name: str, base_path: pathlib.Path):
6068
os.add_dll_directory(os.path.join(os.environ["HIP_PATH"], "lib"))
6169
cdll_args["winmode"] = ctypes.RTLD_GLOBAL
6270

71+
if sys.platform == "emscripten":
72+
cdll_args["mode"] = ctypes.RTLD_GLOBAL
73+
lib_dir = str(base_path)
74+
ld_library_path = os.environ.get("LD_LIBRARY_PATH", "")
75+
if lib_dir not in ld_library_path.split(os.pathsep):
76+
os.environ["LD_LIBRARY_PATH"] = (
77+
lib_dir
78+
if not ld_library_path
79+
else f"{lib_dir}{os.pathsep}{ld_library_path}"
80+
)
81+
82+
emscripten_dependencies = {
83+
"llama": ("ggml-base", "ggml-cpu", "ggml"),
84+
"mtmd": ("ggml-base", "ggml-cpu", "ggml", "llama"),
85+
}
86+
for dependency in emscripten_dependencies.get(lib_base_name, ()):
87+
dependency_path = (
88+
base_path / f"lib{dependency}{_EMSCRIPTEN_SIDE_MODULE_SUFFIX}"
89+
)
90+
if dependency_path.exists():
91+
try:
92+
ctypes.CDLL(str(dependency_path), **cdll_args) # type: ignore
93+
except Exception as e:
94+
raise RuntimeError(
95+
f"Failed to load shared library '{dependency_path}': {e}"
96+
)
97+
6398
# Try to load the shared library, handling potential errors
6499
for lib_path in lib_paths:
65100
if lib_path.exists():

0 commit comments

Comments
 (0)