Buckets:
| #!/usr/bin/env python3 | |
| # | |
| # Build llama.cpp for Snapdragon (via Docker or natively) and push to device. | |
| # | |
| import sys | |
| import os | |
| import argparse | |
| import subprocess | |
| import platform | |
| import shutil | |
| import logging | |
| import glob | |
| import shlex | |
| import tarfile | |
| import tempfile | |
| from sdk import validate_windows_sdks | |
| logger = logging.getLogger("build") | |
| def parse_target(target_str): | |
| if not target_str: | |
| return None, None, None | |
| parts = target_str.split(":") | |
| target_name = parts[0] | |
| if target_name in ("adb", "android"): | |
| serial = ":".join(parts[1:]) if len(parts) > 1 else None | |
| return "android", serial, "adb" | |
| elif target_name in ("lnx", "linux", "ubuntu"): | |
| if len(parts) > 1 and parts[1] == "adb": | |
| serial = ":".join(parts[2:]) if len(parts) > 2 else None | |
| return "linux", serial, "adb" | |
| host = ":".join(parts[1:]) if len(parts) > 1 else None | |
| return "linux", host, "ssh" | |
| elif target_str in ("wos", "windows"): | |
| return "windows", None, None | |
| else: | |
| return None, None, None | |
| def default_target_dir(target_type, target_transport, home_dir): | |
| if target_type == "android": | |
| return "/data/local/tmp/llama.cpp" | |
| if target_type == "linux": | |
| if home_dir: | |
| return home_dir.rstrip("/") + "/llama.cpp" | |
| if target_transport == "adb": | |
| return "/home/llama.cpp" | |
| return "~/llama.cpp" | |
| return "~/llama.cpp" | |
| def normalize_target_dir(target_dir): | |
| target_dir = target_dir.rstrip("/") | |
| if not target_dir or target_dir == "/": | |
| raise RuntimeError("target directory must not be the filesystem root") | |
| return target_dir | |
| def adb_root(adb_cmd): | |
| root_cmd = adb_cmd + ["root"] | |
| logger.info(f"+ {' '.join(root_cmd)}") | |
| res = subprocess.run(root_cmd) | |
| if res.returncode != 0: | |
| logger.error("ADB root failed.") | |
| sys.exit(res.returncode) | |
| wait_cmd = adb_cmd + ["wait-for-device"] | |
| logger.info(f"+ {' '.join(wait_cmd)}") | |
| res = subprocess.run(wait_cmd) | |
| if res.returncode != 0: | |
| logger.error("ADB wait-for-device failed.") | |
| sys.exit(res.returncode) | |
| def make_package_archive(src_path): | |
| fd, archive_path = tempfile.mkstemp(prefix="llama-cpp-", suffix=".tar.gz") | |
| os.close(fd) | |
| with tarfile.open(archive_path, "w:gz", dereference=False) as tar: | |
| for item in os.listdir(src_path): | |
| if item.startswith("."): | |
| continue | |
| tar.add(os.path.join(src_path, item), arcname=item, recursive=True) | |
| return archive_path | |
| def get_uid_gid(): | |
| if platform.system() != "Windows": | |
| return [f"{os.getuid()}:{os.getgid()}"] | |
| return [] | |
| def main(): | |
| logging.basicConfig(level=logging.INFO, format='%(message)s') | |
| parser = argparse.ArgumentParser( | |
| description="Build llama.cpp for Snapdragon using cross-compilation docker containers or natively." | |
| ) | |
| parser.add_argument("--target", default="android", help="Compilation target and deployment definition (e.g. android[:serial]/adb[:serial], ubuntu:adb[:serial], linux:[user@]host/lnx:[user@]host/ubuntu:[user@]host, windows/wos) (default: android)") | |
| parser.add_argument("--build-dir", help="Build directory name (defaults to build-TARGET[-dbg], e.g. build-android)") | |
| parser.add_argument("--install-dir", help="Install directory name (defaults to pkg-TARGET[-dbg], e.g. pkg-android)") | |
| parser.add_argument("--jobs", "-j", type=int, help="Number of build jobs (defaults to CPU thread count)") | |
| parser.add_argument("--no-docker", action="store_true", help="Build natively on the host instead of in a docker container") | |
| parser.add_argument("--preset", help="Override the CMake preset to use") | |
| parser.add_argument("--debug", action="store_true", help="Build in debug mode (uses -debug presets instead of -release)") | |
| parser.add_argument("--hex-only", "--hexagon-only", action="store_true", help="Only build and push ggml-hexagon and libggml-htp libraries") | |
| parser.add_argument("--hex-arch", choices=["v73", "v75", "v79", "v81", "v85", "73", "75", "79", "81", "85"], help="Specific Hexagon DSP architecture to build with --hex-only (default: all)") | |
| # Push options | |
| parser.add_argument("--push", action="store_true", help="Push built package to the target device via ADB or SSH/SCP") | |
| parser.add_argument("--target-dir", help="Target directory on the device (default: /data/local/tmp/llama.cpp for Android, ~/llama.cpp for Linux, /home/llama.cpp for Linux via ADB)") | |
| parser.add_argument("--home-dir", help="Home directory for Linux targets when deriving the default target directory") | |
| # Toolchain options | |
| parser.add_argument("--toolchain-version", default="v0.8", help="Docker toolchain image version/tag (default: v0.8)") | |
| parser.add_argument("--toolchain-url", default="ghcr.io/snapdragon-toolchain", help="Docker toolchain registry URL/namespace (default: ghcr.io/snapdragon-toolchain)") | |
| args = parser.parse_args() | |
| target_type, target_val, target_transport = parse_target(args.target) | |
| if not target_type: | |
| logger.error(f"Error: Invalid target format '{args.target}'. Must be android[:serial]/adb[:serial], ubuntu:adb[:serial], linux:[user@]host/lnx:[user@]host/ubuntu:[user@]host, or windows/wos.") | |
| sys.exit(1) | |
| if target_type == "windows": | |
| logger.info("Windows target selected. Forcing native compilation...") | |
| args.no_docker = True | |
| if platform.system() != "Windows": | |
| logger.warning("Warning: Windows compilation is intended to run on Windows arm64 hosts.") | |
| validate_windows_sdks() | |
| # Determine preset and check if it's debug | |
| preset = args.preset | |
| if preset: | |
| is_debug = args.debug or ("debug" in preset.lower()) | |
| else: | |
| is_debug = args.debug | |
| config_type = "debug" if is_debug else "release" | |
| if args.no_docker: | |
| if target_type == "windows" or platform.system() == "Windows": | |
| preset = f"arm64-windows-snapdragon-{config_type}" | |
| elif target_type == "linux": | |
| preset = f"arm64-linux-snapdragon-{config_type}" | |
| else: | |
| preset = f"arm64-android-snapdragon-{config_type}" | |
| else: | |
| preset = f"arm64-linux-snapdragon-{config_type}" if target_type == "linux" else f"arm64-android-snapdragon-{config_type}" | |
| target_prefix = args.target.split(":", 1)[0] | |
| suffix = "-dbg" if is_debug else "" | |
| build_dir = args.build_dir | |
| if not build_dir: | |
| build_dir = f"build-{target_prefix}{suffix}" | |
| install_dir = args.install_dir | |
| if not install_dir: | |
| install_dir = f"pkg-{target_prefix}{suffix}" | |
| repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) | |
| # Ensure CMakeUserPresets.json is in the workspace root, update if docs version is newer | |
| preset_src = os.path.join(repo_root, "docs", "backend", "snapdragon", "CMakeUserPresets.json") | |
| preset_dst = os.path.join(repo_root, "CMakeUserPresets.json") | |
| if os.path.exists(preset_src): | |
| should_copy = False | |
| if not os.path.exists(preset_dst): | |
| should_copy = True | |
| else: | |
| # Check modification times | |
| src_mtime = os.path.getmtime(preset_src) | |
| dst_mtime = os.path.getmtime(preset_dst) | |
| if src_mtime > dst_mtime: | |
| preset_bak = preset_dst + ".bak" | |
| logger.info(f"Docs CMakeUserPresets.json is newer. Backing up existing {preset_dst} to {preset_bak}") | |
| shutil.copy2(preset_dst, preset_bak) | |
| should_copy = True | |
| if should_copy: | |
| logger.info(f"Copying CMakeUserPresets.json from {preset_src} to {preset_dst}") | |
| shutil.copy2(preset_src, preset_dst) | |
| else: | |
| logger.warning("Warning: CMakeUserPresets.json not found in docs/backend/snapdragon/.") | |
| jobs = args.jobs if args.jobs else os.cpu_count() or 4 | |
| hex_arch = None | |
| if args.hex_arch: | |
| hex_arch = args.hex_arch if args.hex_arch.startswith("v") else f"v{args.hex_arch}" | |
| if args.hex_only: | |
| if hex_arch: | |
| hex_targets = ["ggml-hexagon", f"htp-{hex_arch}"] | |
| else: | |
| hex_targets = ["ggml-hexagon", "htp-v73", "htp-v75", "htp-v79", "htp-v81", "htp-v85"] | |
| if args.no_docker: | |
| # Native/local host build | |
| logger.info("Running native/local CMake build...") | |
| install_prefix = os.path.join(repo_root, install_dir, "llama.cpp") | |
| # Configure | |
| configure_cmd = ["cmake", f"--preset={preset}", "-B", build_dir] | |
| logger.info(f"+ {' '.join(configure_cmd)}") | |
| res = subprocess.run(configure_cmd, cwd=repo_root) | |
| if res.returncode != 0: | |
| logger.error("CMake configuration failed.") | |
| sys.exit(res.returncode) | |
| # Build | |
| build_cmd = ["cmake", "--build", build_dir] | |
| if args.hex_only: | |
| build_cmd += ["--target"] + hex_targets | |
| build_cmd += ["-j", str(jobs)] | |
| logger.info(f"+ {' '.join(build_cmd)}") | |
| res = subprocess.run(build_cmd, cwd=repo_root) | |
| if res.returncode != 0: | |
| logger.error("CMake build failed.") | |
| sys.exit(res.returncode) | |
| # Install | |
| install_cmd = ["cmake", "--install", build_dir, "--prefix", install_prefix] | |
| logger.info(f"+ {' '.join(install_cmd)}") | |
| res = subprocess.run(install_cmd, cwd=repo_root) | |
| if res.returncode != 0: | |
| logger.error("CMake install failed.") | |
| sys.exit(res.returncode) | |
| else: | |
| # Docker-based build | |
| logger.info("Running Docker-based cross-compilation build...") | |
| image_name = "arm64-linux" if target_type == "linux" else "arm64-android" | |
| image = f"{args.toolchain_url}/{image_name}:{args.toolchain_version}" | |
| install_prefix_container = f"/workspace/{install_dir}/llama.cpp" | |
| target_args = f"--target {' '.join(hex_targets)} " if args.hex_only else "" | |
| build_sh_cmd = ( | |
| f"cmake --preset {preset} -B /workspace/{build_dir} && " | |
| f"cmake --build /workspace/{build_dir} {target_args}-j {jobs} && " | |
| f"cmake --install /workspace/{build_dir} --prefix {install_prefix_container}" | |
| ) | |
| docker_cmd = [ | |
| "docker", "run", "--rm", | |
| "--volume", f"{repo_root}:/workspace", | |
| "--workdir", "/workspace", | |
| "--platform", "linux/amd64" | |
| ] | |
| uid_gid = get_uid_gid() | |
| if uid_gid: | |
| docker_cmd += ["-u", uid_gid[0]] | |
| docker_cmd += [image, "bash", "-c", build_sh_cmd] | |
| logger.info(f"+ {' '.join(docker_cmd)}") | |
| res = subprocess.run(docker_cmd, cwd=repo_root) | |
| if res.returncode != 0: | |
| logger.error("Docker-based build failed.") | |
| sys.exit(res.returncode) | |
| logger.info("\nBuild and installation completed successfully!") | |
| # Push/deploy if requested | |
| if args.push: | |
| src_path = os.path.join(repo_root, install_dir, "llama.cpp") | |
| if not os.path.exists(src_path): | |
| logger.error(f"Error: installation directory {src_path} does not exist. Cannot deploy.") | |
| sys.exit(1) | |
| # Resolve target directory on device | |
| target_dir = args.target_dir | |
| if not target_dir: | |
| target_dir = default_target_dir(target_type, target_transport, args.home_dir) | |
| target_dir = normalize_target_dir(target_dir) | |
| sub_items = [item for item in os.listdir(src_path) if not item.startswith(".")] | |
| if args.hex_only: | |
| lib_dir = os.path.join(src_path, "lib") | |
| hex_libs = glob.glob(os.path.join(lib_dir, "libggml-hexagon*")) | |
| if hex_arch: | |
| hex_libs += glob.glob(os.path.join(lib_dir, f"libggml-htp-{hex_arch}*")) | |
| else: | |
| hex_libs += glob.glob(os.path.join(lib_dir, "libggml-htp*")) | |
| if not hex_libs: | |
| logger.error(f"Error: no hexagon libraries found in {lib_dir} to push.") | |
| sys.exit(1) | |
| if target_transport == "adb": | |
| adb_cmd = ["adb"] | |
| if target_val: # serial | |
| adb_cmd += ["-s", target_val] | |
| if target_type == "linux": | |
| adb_root(adb_cmd) | |
| if args.hex_only: | |
| logger.info("\nPushing Hexagon libraries to target device via ADB...") | |
| target_lib_dir = f"{target_dir}/lib" | |
| mkdir_cmd = adb_cmd + ["shell", f"mkdir -p {shlex.quote(target_lib_dir)}"] | |
| logger.info(f"+ {' '.join(mkdir_cmd)}") | |
| res = subprocess.run(mkdir_cmd) | |
| if res.returncode != 0: | |
| logger.error("ADB mkdir failed.") | |
| sys.exit(res.returncode) | |
| for lib in hex_libs: | |
| push_cmd = adb_cmd + ["push", lib, f"{target_lib_dir}/"] | |
| logger.info(f"+ {' '.join(push_cmd)}") | |
| res = subprocess.run(push_cmd) | |
| if res.returncode != 0: | |
| logger.error("ADB push failed.") | |
| sys.exit(res.returncode) | |
| logger.info("Hexagon libraries deployed successfully!") | |
| elif target_type == "linux": | |
| logger.info("\nDeploying Linux package to target device via ADB tar...") | |
| remote_archive = f"/tmp/llama.cpp-package-{os.getpid()}.tar.gz" | |
| archive_path = make_package_archive(src_path) | |
| try: | |
| mkdir_cmd = adb_cmd + ["shell", f"mkdir -p {shlex.quote(target_dir)}"] | |
| logger.info(f"+ {' '.join(mkdir_cmd)}") | |
| res = subprocess.run(mkdir_cmd) | |
| if res.returncode != 0: | |
| logger.error("ADB mkdir failed.") | |
| sys.exit(res.returncode) | |
| if sub_items: | |
| clean_paths = " ".join(shlex.quote(f"{target_dir}/{item}") for item in sub_items) | |
| clean_cmd = adb_cmd + ["shell", f"rm -rf {clean_paths}"] | |
| logger.info(f"+ {' '.join(clean_cmd)}") | |
| subprocess.run(clean_cmd) | |
| push_cmd = adb_cmd + ["push", archive_path, remote_archive] | |
| logger.info(f"+ {' '.join(push_cmd)}") | |
| res = subprocess.run(push_cmd) | |
| if res.returncode != 0: | |
| logger.error("ADB push failed.") | |
| sys.exit(res.returncode) | |
| extract_cmd = adb_cmd + ["shell", f"tar -xzf {shlex.quote(remote_archive)} -C {shlex.quote(target_dir)} && rm -f {shlex.quote(remote_archive)}"] | |
| logger.info(f"+ {' '.join(extract_cmd)}") | |
| res = subprocess.run(extract_cmd) | |
| if res.returncode != 0: | |
| logger.error("ADB untar failed.") | |
| sys.exit(res.returncode) | |
| chmod_cmd = adb_cmd + ["shell", f"chmod -R 755 {shlex.quote(target_dir)}/bin 2>/dev/null || true"] | |
| logger.info(f"+ {' '.join(chmod_cmd)}") | |
| subprocess.run(chmod_cmd) | |
| finally: | |
| os.unlink(archive_path) | |
| logger.info("ADB tar deploy completed successfully!") | |
| else: | |
| # Clean stale package files on device | |
| if sub_items: | |
| clean_paths = " ".join(f"{target_dir}/{item}" for item in sub_items) | |
| clean_cmd = adb_cmd + ["shell", f"rm -rf {clean_paths}"] | |
| logger.info(f"+ {' '.join(clean_cmd)}") | |
| subprocess.run(clean_cmd) | |
| # Android destination directory is target_dir | |
| push_cmd = adb_cmd + ["push", os.path.join(src_path, "."), target_dir] | |
| logger.info(f"+ {' '.join(push_cmd)}") | |
| res = subprocess.run(push_cmd) | |
| if res.returncode != 0: | |
| logger.error("ADB push failed.") | |
| sys.exit(res.returncode) | |
| chmod_cmd = adb_cmd + ["shell", f"chmod -R 755 {target_dir}/bin 2>/dev/null || true"] | |
| logger.info(f"+ {' '.join(chmod_cmd)}") | |
| subprocess.run(chmod_cmd) | |
| logger.info("ADB push completed successfully!") | |
| elif target_type == "linux": | |
| ssh_host = target_val | |
| if not ssh_host: | |
| logger.error("Error: SSH host not specified in target (e.g. use linux:user@host, lnx:user@host, or ubuntu:user@host). Cannot deploy.") | |
| sys.exit(1) | |
| if args.hex_only: | |
| logger.info(f"\nDeploying Hexagon libraries to Linux device {ssh_host} via SCP...") | |
| target_lib_dir = f"{target_dir}/lib" | |
| deploy_cmd = ["scp"] + hex_libs + [f"{ssh_host}:{target_lib_dir}/"] | |
| logger.info(f"+ {' '.join(deploy_cmd)}") | |
| res = subprocess.run(deploy_cmd) | |
| if res.returncode != 0: | |
| logger.error("SCP deploy failed.") | |
| sys.exit(res.returncode) | |
| logger.info("Hexagon libraries deployed successfully!") | |
| else: | |
| # Clean stale package files on remote host | |
| if sub_items: | |
| clean_paths = " ".join(f"{target_dir}/{item}" for item in sub_items) | |
| clean_cmd = ["ssh", ssh_host, f"rm -rf {clean_paths}"] | |
| logger.info(f"+ {' '.join(clean_cmd)}") | |
| subprocess.run(clean_cmd) | |
| # Deploy to target_dir | |
| deploy_cmd = ["scp", "-r", os.path.join(src_path, "."), f"{ssh_host}:{target_dir}"] | |
| logger.info(f"+ {' '.join(deploy_cmd)}") | |
| res = subprocess.run(deploy_cmd) | |
| if res.returncode != 0: | |
| logger.error("SSH/SCP deploy failed.") | |
| sys.exit(res.returncode) | |
| chmod_cmd = ["ssh", ssh_host, f"chmod -R 755 {target_dir}/bin 2>/dev/null || true"] | |
| logger.info(f"+ {' '.join(chmod_cmd)}") | |
| subprocess.run(chmod_cmd) | |
| logger.info("SSH/SCP deploy completed successfully!") | |
| elif target_type == "windows": | |
| logger.info("\nPush for Windows on Snapdragon (windows) target is currently a stub.") | |
| if __name__ == "__main__": | |
| try: | |
| main() | |
| except KeyboardInterrupt: | |
| logger.info("\nInterrupted by user.") | |
| sys.exit(130) | |
| except RuntimeError as err: | |
| logger.error("Error: %s", err) | |
| sys.exit(1) | |
Xet Storage Details
- Size:
- 18.7 kB
- Xet hash:
- ae96ed0875279c1af6321f1b7d29194744d163656b8036c96954302f9d6cb098
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.