From 680a6857cb151bd0713d31ff7620fb45b09e4747 Mon Sep 17 00:00:00 2001 From: co-seven Date: Fri, 3 Jul 2026 10:27:28 +0000 Subject: [PATCH] feat(ohos): add OpenHarmony (musl) cross-compile support and CI release Add OHOS/OpenHarmony RISC-V (musl) build support for llama.cpp: - cmake/riscv64-spacemit-ohos.cmake: musl clang OHOS cross toolchain file - ggml-cpu: enable ohos platform; fix xsmtvdotii detection for clang (previously gcc-only, leaving IME kernels undefined) and make div_round_up C++17-compatible (auto params are invalid pre-C++20 on clang) - .github/workflows/build-spacemit-mtmd.yml: add build-ohos job that runs alongside the glibc build on push/PR, depends on it, and attaches the OHOS package to the same release tag so a release carries both glibc and ohos tarballs - .github/variables.env: OHOS toolchain / ORT download URLs; bump linux ORT 2.0.2 -> 2.0.5 - VERSION_NUMBER: 0.1.4 -> 0.1.5 --- .github/variables.env | 8 +- .github/workflows/build-spacemit-mtmd.yml | 210 ++++++++++++++++++++++ VERSION_NUMBER | 2 +- cmake/riscv64-spacemit-ohos.cmake | 73 ++++++++ ggml/src/ggml-cpu/CMakeLists.txt | 9 +- ggml/src/ggml-cpu/cmake/FindSMTIME.cmake | 5 +- ggml/src/ggml-cpu/spacemit/rvv_kernels.h | 2 +- 7 files changed, 301 insertions(+), 8 deletions(-) create mode 100644 cmake/riscv64-spacemit-ohos.cmake diff --git a/.github/variables.env b/.github/variables.env index 405225b1947e..e6cd8704f0f0 100644 --- a/.github/variables.env +++ b/.github/variables.env @@ -1,6 +1,12 @@ SPACEMIT_TOOLCHAIN_URL=https://github.com/spacemit-com/toolchain/releases/download/v1.1.2/spacemit-toolchain-linux-glibc-x86_64-v1.1.2.tar.xz -SPACEMIT_ORT_URL=https://github.com/spacemit-com/onnxruntime/releases/download/2.0.2/spacemit-ort.riscv64.2.0.2.tar.gz +SPACEMIT_ORT_URL=https://github.com/spacemit-com/onnxruntime/releases/download/2.0.5/spacemit-ort.riscv64.2.0.5.tar.gz SPACEMIT_MTMD_PACKAGE_PREFIX=spacemit-llama.cpp.riscv64 SPACEMIT_TOOLCHAIN_ARCHIVE=.cache/spacemit-toolchain.tar.xz SPACEMIT_ORT_ARCHIVE=.cache/spacemit-ort.tar.gz SPACEMIT_TOOLCHAIN_DIR=spacemit_toolchain +SPACEMIT_TOOLCHAIN_OHOS_URL=https://nexus.bianbu.xyz/repository/toolchain/llvm-gcc/spacemit-toolchain-linux-musl-x86_64-oh-20260630.tar.xz +SPACEMIT_ORT_OHOS_URL=https://github.com/spacemit-com/onnxruntime/releases/download/2.0.5/spacemit-ort.riscv64.ohos.2.0.5.tar.gz +SPACEMIT_MTMD_OHOS_PACKAGE_PREFIX=spacemit-llama.cpp.riscv64.ohos +SPACEMIT_TOOLCHAIN_OHOS_ARCHIVE=.cache/spacemit-toolchain-ohos.tar.xz +SPACEMIT_ORT_OHOS_ARCHIVE=.cache/spacemit-ort-ohos.tar.gz +SPACEMIT_TOOLCHAIN_OHOS_DIR=spacemit_toolchain_ohos diff --git a/.github/workflows/build-spacemit-mtmd.yml b/.github/workflows/build-spacemit-mtmd.yml index af40a4cc56f8..e211971f7060 100644 --- a/.github/workflows/build-spacemit-mtmd.yml +++ b/.github/workflows/build-spacemit-mtmd.yml @@ -7,6 +7,12 @@ on: pull_request: branches: - spacemit-mtmd + workflow_dispatch: + inputs: + build_ohos: + description: "Also run the OHOS (OpenHarmony, musl) build job" + required: false + default: "true" concurrency: group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }} @@ -20,6 +26,11 @@ jobs: name: Build and Publish SpacemiT MTMD runs-on: ubuntu-24.04 + outputs: + should_publish: ${{ steps.release_guard.outputs.should_publish }} + release_tag: ${{ steps.vars.outputs.release_tag }} + version_number: ${{ steps.vars.outputs.version_number }} + steps: - name: Clone uses: actions/checkout@v6 @@ -285,3 +296,202 @@ jobs: make_latest: false overwrite_files: true fail_on_unmatched_files: true + + build-ohos: + name: Build and Publish SpacemiT MTMD (OHOS) + runs-on: ubuntu-24.04 + # Runs alongside the glibc `build` job on push / pull_request (and manual + # dispatch). It depends on `build` so that, on a release push, the glibc job + # creates the GitHub release first and this job then attaches its OHOS + # package to the same tag (overwrite_files: true). Requires the two + # SPACEMIT_*_OHOS_URL values in .github/variables.env to point at real + # published artifacts; the "Load shared variables" step hard-fails while + # they are still TODO_ placeholders. + needs: build + if: ${{ github.event_name != 'workflow_dispatch' || github.event.inputs.build_ohos == 'true' }} + + steps: + - name: Clone + uses: actions/checkout@v6 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Load shared variables + id: vars + shell: bash + run: | + set -euo pipefail + set -a + source .github/variables.env + set +a + + if [[ "${SPACEMIT_TOOLCHAIN_OHOS_URL}" == TODO_* || "${SPACEMIT_ORT_OHOS_URL}" == TODO_* ]]; then + echo "::error::OHOS artifact URLs are still placeholders in .github/variables.env." + echo "::error::Set SPACEMIT_TOOLCHAIN_OHOS_URL and SPACEMIT_ORT_OHOS_URL before running this job." + exit 1 + fi + + VERSION_NUMBER=$(tr -d '[:space:]' < VERSION_NUMBER) + if [[ -z "${VERSION_NUMBER}" ]]; then + echo "error: VERSION_NUMBER is empty" >&2 + exit 1 + fi + + SPACEMIT_MTMD_OHOS_PACKAGE_NAME="${SPACEMIT_MTMD_OHOS_PACKAGE_PREFIX}.${VERSION_NUMBER}" + + { + echo "SPACEMIT_TOOLCHAIN_OHOS_URL=${SPACEMIT_TOOLCHAIN_OHOS_URL}" + echo "SPACEMIT_ORT_OHOS_URL=${SPACEMIT_ORT_OHOS_URL}" + echo "SPACEMIT_TOOLCHAIN_OHOS_ARCHIVE=${SPACEMIT_TOOLCHAIN_OHOS_ARCHIVE}" + echo "SPACEMIT_ORT_OHOS_ARCHIVE=${SPACEMIT_ORT_OHOS_ARCHIVE}" + echo "SPACEMIT_TOOLCHAIN_OHOS_DIR=${SPACEMIT_TOOLCHAIN_OHOS_DIR}" + echo "SPACEMIT_MTMD_OHOS_PACKAGE_NAME=${SPACEMIT_MTMD_OHOS_PACKAGE_NAME}" + } >> "$GITHUB_ENV" + + toolchain_cache_key=$(printf '%s' "$SPACEMIT_TOOLCHAIN_OHOS_URL" | sha256sum | cut -c1-16) + ort_cache_key=$(printf '%s' "$SPACEMIT_ORT_OHOS_URL" | sha256sum | cut -c1-16) + + { + echo "version_number=${VERSION_NUMBER}" + echo "package_name=${SPACEMIT_MTMD_OHOS_PACKAGE_NAME}" + echo "toolchain_archive=${SPACEMIT_TOOLCHAIN_OHOS_ARCHIVE}" + echo "ort_archive=${SPACEMIT_ORT_OHOS_ARCHIVE}" + echo "toolchain_cache_key=${toolchain_cache_key}" + echo "ort_cache_key=${ort_cache_key}" + } >> "$GITHUB_OUTPUT" + + - name: Cache SpacemiT OHOS Toolchain archive + id: cache-toolchain + uses: actions/cache@v5 + with: + path: ${{ steps.vars.outputs.toolchain_archive }} + key: spacemit-toolchain-ohos-archive-${{ steps.vars.outputs.toolchain_cache_key }}-${{ runner.os }} + + - name: Cache SpacemiT OHOS ORT archive + id: cache-ort + uses: actions/cache@v5 + with: + path: ${{ steps.vars.outputs.ort_archive }} + key: spacemit-ort-ohos-archive-${{ steps.vars.outputs.ort_cache_key }}-${{ runner.os }} + + - name: Prepare SpacemiT OHOS Toolchain + shell: bash + run: | + set -euo pipefail + + mkdir -p "$(dirname "$SPACEMIT_TOOLCHAIN_OHOS_ARCHIVE")" + if [[ ! -f "$SPACEMIT_TOOLCHAIN_OHOS_ARCHIVE" ]]; then + curl -L -k --fail --retry 5 --retry-all-errors \ + -o "$SPACEMIT_TOOLCHAIN_OHOS_ARCHIVE" \ + "$SPACEMIT_TOOLCHAIN_OHOS_URL" + fi + + rm -rf "$SPACEMIT_TOOLCHAIN_OHOS_DIR" + mkdir -p "$SPACEMIT_TOOLCHAIN_OHOS_DIR" + tar -xJf "$SPACEMIT_TOOLCHAIN_OHOS_ARCHIVE" -C "$SPACEMIT_TOOLCHAIN_OHOS_DIR" --strip-components=1 + + - name: Prepare SpacemiT OHOS ORT + shell: bash + run: | + set -euo pipefail + ORT_ROOT=third_party/spacemit-ort-ohos + + mkdir -p "$(dirname "$SPACEMIT_ORT_OHOS_ARCHIVE")" + if [[ ! -f "$SPACEMIT_ORT_OHOS_ARCHIVE" ]]; then + curl -L --fail --retry 5 --retry-all-errors \ + -o "$SPACEMIT_ORT_OHOS_ARCHIVE" \ + "$SPACEMIT_ORT_OHOS_URL" + fi + + rm -rf "$ORT_ROOT" + mkdir -p "$ORT_ROOT" + tar -xzf "$SPACEMIT_ORT_OHOS_ARCHIVE" -C "$ORT_ROOT" + + ORT_DIR=$(find "$ORT_ROOT" -mindepth 1 -maxdepth 1 -type d | head -n 1) + if [[ -z "${ORT_DIR}" ]]; then + ORT_DIR="${PWD}/${ORT_ROOT}" + else + ORT_DIR="${PWD}/${ORT_DIR}" + fi + + if [[ ! -d "${ORT_DIR}/include" || ! -d "${ORT_DIR}/lib" || ! -d "${ORT_DIR}/samples" ]]; then + echo "error: invalid SPACEMIT_ORT_DIR layout at ${ORT_DIR}" >&2 + find "$ORT_ROOT" -maxdepth 2 -type d | sort + exit 1 + fi + + echo "SPACEMIT_ORT_DIR=${ORT_DIR}" >> "$GITHUB_ENV" + + - name: Build (OHOS) + shell: bash + run: | + set -euo pipefail + export RISCV_ROOT_PATH="${PWD}/${SPACEMIT_TOOLCHAIN_OHOS_DIR}" + + for d in include lib samples; do + if [[ ! -d "${SPACEMIT_ORT_DIR}/${d}" ]]; then + echo "error: ${d} directory not found under SPACEMIT_ORT_DIR: ${SPACEMIT_ORT_DIR}/${d}" >&2 + exit 1 + fi + done + + echo "Using RISCV_ROOT_PATH=${RISCV_ROOT_PATH}" + echo "Using SPACEMIT_ORT_DIR=${SPACEMIT_ORT_DIR}" + bash build_spacemit.sh ohos + + - name: Pack artifacts (OHOS) + shell: bash + run: | + set -euo pipefail + PACKAGE_DIR="release/${SPACEMIT_MTMD_OHOS_PACKAGE_NAME}" + ASSET_NAME="${SPACEMIT_MTMD_OHOS_PACKAGE_NAME}.tar.gz" + + rm -rf "$PACKAGE_DIR" + mkdir -p "$PACKAGE_DIR" + cp -a build-ohos/installed/. "$PACKAGE_DIR/" + cp LICENSE README.md VERSION_NUMBER "$PACKAGE_DIR/" + + if [[ -d "$PACKAGE_DIR/bin" ]]; then + find "$PACKAGE_DIR/bin" -maxdepth 1 \( -type f -o -type l \) \( -name 'test*' -o -name 'export-graph-ops*' \) -exec rm -f {} + + fi + + tar -czf "release/${ASSET_NAME}" -C release "${SPACEMIT_MTMD_OHOS_PACKAGE_NAME}" + + - name: Inspect package (OHOS) + shell: bash + run: | + set -euo pipefail + + ASSET_NAME="${SPACEMIT_MTMD_OHOS_PACKAGE_NAME}.tar.gz" + echo "Package tree:" + find "release/${SPACEMIT_MTMD_OHOS_PACKAGE_NAME}" -maxdepth 2 -print | sort + echo "Package archive:" + tar -tzf "release/${ASSET_NAME}" + + - name: Upload OHOS package for PR inspection + if: ${{ github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch' }} + uses: actions/upload-artifact@v6 + with: + name: spacemit-mtmd-ohos-package + path: release/${{ steps.vars.outputs.package_name }}.tar.gz + if-no-files-found: error + retention-days: 7 + + - name: Publish GitHub release (OHOS) + if: ${{ github.event_name == 'push' && needs.build.outputs.should_publish == 'true' }} + uses: softprops/action-gh-release@v2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + tag_name: ${{ needs.build.outputs.release_tag }} + name: ${{ needs.build.outputs.release_tag }} + target_commitish: ${{ github.sha }} + files: release/${{ steps.vars.outputs.package_name }}.tar.gz + body: | + SpacemiT MTMD build for `spacemit-mtmd`. + Version: `${{ needs.build.outputs.version_number }}` + Package: `${{ steps.vars.outputs.package_name }}.tar.gz` + Commit: `${{ github.sha }}` + make_latest: false + overwrite_files: true + fail_on_unmatched_files: true diff --git a/VERSION_NUMBER b/VERSION_NUMBER index 845639eef26c..9faa1b7a7339 100644 --- a/VERSION_NUMBER +++ b/VERSION_NUMBER @@ -1 +1 @@ -0.1.4 +0.1.5 diff --git a/cmake/riscv64-spacemit-ohos.cmake b/cmake/riscv64-spacemit-ohos.cmake new file mode 100644 index 000000000000..5b8d387e2915 --- /dev/null +++ b/cmake/riscv64-spacemit-ohos.cmake @@ -0,0 +1,73 @@ +# Toolchain file for cross-compiling llama.cpp for RISC-V 64 OpenHarmony (OHOS) +# using the SpacemiT musl clang toolchain +# (spacemit-toolchain-linux-musl-x86_64-oh-*). +# +# Mirrors spacemit-ep/cmake/oh_riscv64.toolchain.cmake, but leaves the +# `-march`/`-mabi` selection to ggml's own CPU feature detection +# (ggml/src/ggml-cpu/CMakeLists.txt), so we only inject the OHOS-specific +# global flags here (musl libc++ static linking, __OHOS__, stack size, lld). +# +# Usage: +# export RISCV_ROOT_PATH=/path/to/spacemit-toolchain-linux-musl-x86_64-oh-* +# cmake -DCMAKE_TOOLCHAIN_FILE=cmake/riscv64-spacemit-ohos.cmake ... + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR riscv64) +set(CMAKE_SYSTEM_VERSION 1) + +if(CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^(riscv)") + message(STATUS "HOST SYSTEM ${CMAKE_HOST_SYSTEM_PROCESSOR}") + set(CMAKE_C_COMPILER clang) + set(CMAKE_ASM_COMPILER clang) + set(CMAKE_CXX_COMPILER clang++) +else() + if(DEFINED ENV{RISCV_ROOT_PATH}) + file(TO_CMAKE_PATH $ENV{RISCV_ROOT_PATH} RISCV_ROOT_PATH) + else() + message(FATAL_ERROR "RISCV_ROOT_PATH env must be defined") + endif() + + set(RISCV_ROOT_PATH ${RISCV_ROOT_PATH} + CACHE STRING "root path to riscv ohos toolchain") + set(CMAKE_C_COMPILER "${RISCV_ROOT_PATH}/bin/clang") + set(CMAKE_ASM_COMPILER "${RISCV_ROOT_PATH}/bin/clang") + set(CMAKE_CXX_COMPILER "${RISCV_ROOT_PATH}/bin/clang++") + set(CMAKE_STRIP ${RISCV_ROOT_PATH}/bin/llvm-strip) + set(CMAKE_FIND_ROOT_PATH ${RISCV_ROOT_PATH}) + set(CMAKE_SYSROOT "${RISCV_ROOT_PATH}/sysroot") + set(CMAKE_INCLUDE_PATH "${RISCV_ROOT_PATH}/sysroot/usr/include/") + set(CMAKE_LIBRARY_PATH "${RISCV_ROOT_PATH}/sysroot/usr/lib/") + set(CMAKE_PROGRAM_PATH "${RISCV_ROOT_PATH}/sysroot/usr/bin/") + set(CMAKE_CROSSCOMPILING TRUE) +endif() + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + +add_definitions(-D__OHOS__) + +set(STACK_SIZE_BYTES 16777216) + +# NOTE: -march / -mabi are intentionally NOT set here. ggml appends the +# correct `-march=rv64gc..._zfh_zvfh...` and `-mabi=lp64d` itself based on the +# GGML_RV_* options passed on the cmake command line. Setting them here as well +# would duplicate/conflict with ggml's selection. + +set(CMAKE_C_FLAGS + "-Wno-unused-command-line-argument -fuse-ld=lld -Wl,-z,stack-size=${STACK_SIZE_BYTES} ${CMAKE_C_FLAGS}" +) +set(CMAKE_CXX_FLAGS + "-Wno-unused-command-line-argument -fuse-ld=lld -stdlib=libc++ -static-libstdc++ -Wl,--push-state,-Bstatic -lc++ -lc++abi -Wl,--pop-state -Wl,-z,stack-size=${STACK_SIZE_BYTES} ${CMAKE_CXX_FLAGS}" +) + +set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -stdlib=libc++ -static-libgcc -static-libstdc++ -Wl,--push-state,-Bstatic -lgcc -lc++ -lc++abi -Wl,--pop-state -lm -Wl,-z,stack-size=${STACK_SIZE_BYTES}" +) + +set(CMAKE_EXE_LINKER_FLAGS + "${CMAKE_EXE_LINKER_FLAGS} -latomic -lm -Wl,-z,stack-size=${STACK_SIZE_BYTES}" +) +set(CMAKE_MODULE_LINKER_FLAGS + "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-z,stack-size=${STACK_SIZE_BYTES}") diff --git a/ggml/src/ggml-cpu/CMakeLists.txt b/ggml/src/ggml-cpu/CMakeLists.txt index 651b50248029..741616dbd28b 100644 --- a/ggml/src/ggml-cpu/CMakeLists.txt +++ b/ggml/src/ggml-cpu/CMakeLists.txt @@ -495,9 +495,12 @@ function(ggml_add_cpu_backend_variant_impl tag_name) string(APPEND MARCH_STR "_zba") endif() if (GGML_CPU_RISCV64_SPACEMIT) - # `xsmtvdotii' is only required for GCC >= 15. - if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND - CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15) + # `xsmtvdotii' is required for GCC >= 15 and for Clang + # (the SpacemiT musl/OHOS clang toolchain needs it to emit + # the IME `vmadot` family of instructions). + if ((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND + CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15) OR + CMAKE_C_COMPILER_ID MATCHES "Clang") string(APPEND MARCH_STR "_xsmtvdotii") endif() endif() diff --git a/ggml/src/ggml-cpu/cmake/FindSMTIME.cmake b/ggml/src/ggml-cpu/cmake/FindSMTIME.cmake index c8a4d4b4ec93..19ec36d061ae 100644 --- a/ggml/src/ggml-cpu/cmake/FindSMTIME.cmake +++ b/ggml/src/ggml-cpu/cmake/FindSMTIME.cmake @@ -2,8 +2,9 @@ include(CheckCSourceRuns) if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(riscv)" AND GGML_CPU_RISCV64_SPACEMIT) set(SMT_MARCH_STR "-march=rv64gcv_zfh_zvfh_zba_zicbop") - if (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND - CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15) + if ((CMAKE_C_COMPILER_ID STREQUAL "GNU" AND + CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 15) OR + CMAKE_C_COMPILER_ID MATCHES "Clang") string(APPEND SMT_MARCH_STR "_xsmtvdotii") endif() set(CMAKE_REQUIRED_FLAGS "${SMT_MARCH_STR}") diff --git a/ggml/src/ggml-cpu/spacemit/rvv_kernels.h b/ggml/src/ggml-cpu/spacemit/rvv_kernels.h index edddf957c21d..49ade3b21a00 100644 --- a/ggml/src/ggml-cpu/spacemit/rvv_kernels.h +++ b/ggml/src/ggml-cpu/spacemit/rvv_kernels.h @@ -9,7 +9,7 @@ namespace spacemit_kernels { -constexpr auto div_round_up(auto up, auto down) { +template constexpr auto div_round_up(T up, U down) { return (up + down - 1) / down; }