Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,79 @@ jobs:
key: ccache-${{ runner.os }}-${{ github.ref }}-${{ github.sha }}
prefix: ccache-${{ runner.os }}-${{ github.ref }}-

macos-arm64:
name: macOS arm64 CPU SDK & Runtime
runs-on: macos-15
timeout-minutes: 60
permissions:
contents: read
env:
LLM_EDGEFLOW_BUILD_PRESET: macos-arm64-cpu
LLM_EDGEFLOW_LINKER: system
LLM_EDGEFLOW_JOBS: "3"
CLANG_FORMAT: clang-format
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up native Apple Silicon tools
run: |
test "$(uname -m)" = arm64
python -m pip install cmake==3.28.4 ninja==1.11.1.1 clang-format==18.1.8 jsonschema==4.23.0
xcrun --show-sdk-path
- name: Cache verified macOS dependencies
uses: actions/cache@v4
with:
path: |
3rdparty/googletest
3rdparty/pcre2
3rdparty/llama_cpp
3rdparty/whisper_cpp
3rdparty/onnxruntime
3rdparty/nlohmann_json
3rdparty/stb_image
key: 3rdparty-macos-arm64-${{ hashFiles('cmake_ext/**/*.cmake', 'CMakePresets.json') }}
- name: Run the canonical gate including dylib exports
run: ./scripts/run_all_tests.sh
- name: Cache pinned GGUF
uses: actions/cache@v4
with:
path: configs/qwen2.5-0.5b-instruct-q4_k_m.gguf
key: qwen2.5-0.5b-q4-k-m-configs-v1-74a4da8c9fdb
- name: Run real GGUF through the public Operator on arm64
run: |
./scripts/fetch_real_test_models.sh --gguf-only
cmake --preset=macos-arm64-cpu -B build -DENABLE_REAL_MODEL_TESTS=ON
cmake --build build --target test_real_models_e2e --parallel 3
./build/test_real_models_e2e --gtest_filter=RealModelExecutionTest.GgufTranslationTraversesPublicOperatorWithActualWeights

linux-arm64-cross:
name: Linux arm64 Cross Compilation
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-build-env
with:
packages: build-essential cmake ninja-build:ninja gcc-aarch64-linux-gnu:aarch64-linux-gnu-gcc g++-aarch64-linux-gnu:aarch64-linux-gnu-g++
- name: Build and inspect the arm64 SDK without host execution
run: |
cmake --preset=linux-arm64-cross
cmake --build build/variants/linux-arm64-cross --target alg_sdk --parallel 4
aarch64-linux-gnu-readelf -h build/variants/linux-arm64-cross/libllm_edgeflow_sdk.so | grep 'Machine:.*AArch64'
- name: Verify the pinned Inferlet target package compiles and links
run: |
cmake --preset=linux-arm64-cross -B build/variants/linux-arm64-inferlet -DENABLE_INFERLET=ON
cmake --build build/variants/linux-arm64-inferlet --target alg_sdk --parallel 4
aarch64-linux-gnu-readelf -h build/variants/linux-arm64-inferlet/libllm_edgeflow_sdk.so | grep 'Machine:.*AArch64'

sanitizer:
name: Production Backends ASan & UBSan
runs-on: ubuntu-latest
Expand Down Expand Up @@ -450,7 +523,7 @@ jobs:
acceptance-evidence:
name: Exact-SHA Acceptance Evidence
if: always()
needs: [build-and-test, sanitizer, real-c-abi-and-profile, whisper-asr, kite-llm]
needs: [build-and-test, macos-arm64, linux-arm64-cross, sanitizer, real-c-abi-and-profile, whisper-asr, kite-llm]
runs-on: ubuntu-latest
timeout-minutes: 10

Expand All @@ -462,6 +535,8 @@ jobs:
env:
WHISPER_GATE_RESULT: ${{ needs.whisper-asr.result }}
KITELLM_GATE_RESULT: ${{ needs.kite-llm.result }}
MACOS_ARM64_GATE_RESULT: ${{ needs.macos-arm64.result }}
LINUX_ARM64_CROSS_GATE_RESULT: ${{ needs.linux-arm64-cross.result }}
run: |
git fetch origin main --quiet
./scripts/generate_acceptance_evidence.sh \
Expand All @@ -480,6 +555,8 @@ jobs:
- name: Enforce Required Job Results
if: >-
needs.build-and-test.result != 'success' ||
needs.macos-arm64.result != 'success' ||
needs.linux-arm64-cross.result != 'success' ||
needs.sanitizer.result != 'success' ||
needs.real-c-abi-and-profile.result != 'success' ||
needs.whisper-asr.result != 'success' ||
Expand Down
42 changes: 22 additions & 20 deletions 3rdparty/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# 3rdparty 依赖库目录规范

本项目采用统一的 `3rdparty/` 本地持久化归档体系,用于缓存第三方开源依赖(包括预编译静态库 `.a`、动态库 `.so` 及对应的头文件)。
本项目使用 `3rdparty/` 持久缓存固定版本、经过 SHA-256 校验的依赖。原始发布归档可以共享,
本地编译产物按目标平台和构建身份分目录,避免 Linux/macOS、CPU 架构及 Debug/Release 相互覆盖。

用户指定的私有 GitHub kiteLLM 依赖使用 `kite_llm/<release>/<platform>/` 缓存已校验
的 Release 归档;启用方式、读取授权和平台限制见 [kiteLLM 接入说明](../doc/kitellm.md)。
Expand All @@ -13,24 +14,22 @@

```text
3rdparty/
├── llama_cpp/ # llama.cpp 编译产物 (静态库与头文件)
│ ├── include/ # llama.h, ggml.h, gguf.h 等
│ └── lib/ # libllama.a, libggml.a, libggml-base.a, libggml-cpu.a
│
├── onnxruntime/ # ONNX Runtime 官方预编译发行包
│ ├── include/ # onnxruntime_c_api.h, onnxruntime_cxx_api.h 等
│ └── lib/ # libonnxruntime.so (Linux) 或 libonnxruntime.dylib (macOS)
│
├── googletest/ # Google Test 单元测试框架
│ ├── include/ # gtest/gtest.h 等
│ └── lib/ # libgtest.a, libgtest_main.a
│
└── nlohmann_json/ # nlohmann/json 现代化 C++ JSON 库 (Header-only)
└── include/ # nlohmann/json.hpp
├── llama_cpp/<target>/<fingerprint>/ # 静态库与头文件
├── whisper_cpp/<target>/<fingerprint>/ # 身份还包含对应 GGML provider
├── pcre2/<target>/<fingerprint>/
├── googletest/<target>/<fingerprint>/
├── onnxruntime/<target>/<fingerprint>/ # 固定平台发行包
├── inferlet/<release>/<platform>/ # 已校验下载归档,在各 build/_deps 解压
├── kite_llm/<release>/<platform>/
├── nlohmann_json/include/ # 与平台无关的头文件
└── stb_image/ # 与平台无关的头文件
```

每个依赖目录还包含自动生成的 `.edgeflow-cache-fingerprint`。该文件记录固定版本、
源码 SHA-256、缓存类型,以及二进制缓存所需的平台、工具链和 ABI 选项。
库缓存目录包含 `.edgeflow-cache-fingerprint`,记录版本、来源散列和目标 OS/架构。
源码编译的静态库另记录编译器、工具链、SDK/sysroot、当前构建类型的编译参数和 sanitizer 设置;
官方预编译包不因本机 SDK 更新而改变身份。`target` 例如 `linux-arm64`、`macos-arm64`。
项目使用 Ninja 等单配置生成器;`cmake_ext/third_party/ThirdPartyCacheMetadata.cmake`
拥有身份计算规则,各依赖模块消费其返回的路径。

---

Expand All @@ -39,13 +38,16 @@
1. **自动生成与归档**:
- 首次执行 CMake 构建(如 `cmake -B build`)且本地 `3rdparty/` 为空时,构建系统会自动拉取依赖并完成编译/规整,将产物保存到 `3rdparty/` 对应子目录中。
2. **零编译 / 零下载秒级复用**:
- 当 `3rdparty/<lib>/` 存在有效库、头文件及匹配的缓存指纹时,后续清理或新建构建目录,CMake 均会直接以 `IMPORTED` 静态库/动态库秒级导入。
- 单配置构建在所选身份目录存在完整库、头文件和匹配指纹时,使用 `IMPORTED` 目标复用。
- 指纹缺失或与当前固定源码、平台、编译器、ABI 选项不匹配时,CMake 会 fail-closed 地忽略旧缓存,并从已校验的固定来源重新生成和归档。
- llama.cpp 只缓存项目提前确定的 GGML 选项组合。出现未纳入身份的 GGML/LLAMA 构建变量时,llama/Whisper 均使用源码增量构建且不读写共享归档;同一源码构建目录中上游新增的默认选项也按此处理,身份目录保持稳定。配置日志会列出禁用共享归档的变量名称,或需要保留源码目标依赖的 Metal/BLAS 组合。
3. **强制重新编译 / 刷新**:
- 如需更新或重新编译 llama.cpp,可在 CMake 中指定 `-DLLAMA_CPP_FORCE_REBUILD=ON`,或直接删除 `3rdparty/llama_cpp` 目录。
- 如需重新编译 llama.cpp,可指定 `-DLLAMA_CPP_FORCE_REBUILD=ON`。不同身份目录可以并存,无需删除其他平台的缓存。

---

## 3. 离线与边缘硬件手动放置说明

在无法连接公网的离线服务器或嵌入式边缘设备(如 ARM / NPU 板卡)上,可复制由同一项目配置在兼容工具链中生成的完整 `3rdparty/<lib>/` 目录(包括缓存指纹)。只复制来源不明的库和头文件会被拒绝,避免静默加载 ABI 不兼容产物。
离线构建可复制由同一项目配置和工具链生成的完整缓存目录,包括指纹及目录层级。
指纹或编译器/sysroot 路径不同会重新选择源码构建;此时还需预置固定版本源码或相同发布归档。
设备上实际使用哪些库,以当前配置输出及目标产物为准。
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ endif()

project(LLMEdgeFlow VERSION 11.0.0 LANGUAGES C CXX)

include(cmake_ext/PlatformOptions.cmake)
include(cmake_ext/BackendOptions.cmake)
include(cmake_ext/ProjectSetup.cmake)
include(cmake_ext/Dependencies.cmake)
Expand Down
30 changes: 30 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"binaryDir": "${sourceDir}/build/variants/${presetName}",
"cacheVariables": {
"BUILD_TESTING": "ON",
"LLM_EDGEFLOW_EXPECTED_PLATFORM": "",
"ENABLE_ONNXRUNTIME": "OFF",
"ENABLE_LLAMACPP": "OFF",
"ENABLE_WHISPERCPP": "OFF",
Expand All @@ -16,6 +17,7 @@
"CMAKE_BUILD_TYPE": "Release",
"ENABLE_REAL_MODEL_TESTS": "OFF",
"ENABLE_SANITIZERS": "OFF",
"LLM_EDGEFLOW_LLAMACPP_METAL": "OFF",
"LLM_EDGEFLOW_TEST_PCH": "OFF"
}
},
Expand Down Expand Up @@ -44,6 +46,34 @@
}
}
},
{
"name": "macos-arm64-cpu",
"displayName": "macOS Apple Silicon CPU (M3 and arm64)",
"inherits": "full-cpu",
"cacheVariables": {
"LLM_EDGEFLOW_EXPECTED_PLATFORM": "macos-arm64",
"CMAKE_OSX_ARCHITECTURES": "arm64",
"CMAKE_OSX_SYSROOT": "macosx",
"CMAKE_OSX_DEPLOYMENT_TARGET": "14.0",
"LLM_EDGEFLOW_LINKER": "system"
},
"vendor": {
"llm-edgeflow/selection": {
"backends": ["onnxruntime", "llama_cpp", "whisper_cpp"]
}
}
},
{
"name": "linux-arm64-cross",
"displayName": "GNU/Linux arm64 SDK cross compilation",
"inherits": "minimal",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "${sourceDir}/cmake_ext/toolchains/linux-arm64.cmake",
"LLM_EDGEFLOW_EXPECTED_PLATFORM": "linux-arm64",
"BUILD_TESTING": "OFF",
"LLM_EDGEFLOW_LINKER": "system"
}
},
{
"name": "kite-cpu",
"inherits": "base",
Expand Down
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ Finish the intended changes and durable documentation, then choose the gate entr
./scripts/run_all_tests.sh
```

On macOS Apple Silicon, select the native CPU preset through the same entrypoint:
`LLM_EDGEFLOW_BUILD_PRESET=macos-arm64-cpu ./scripts/run_all_tests.sh`.
The gate always enables tests; a cross-compilation preset cannot substitute
for native execution. See [platform builds](doc/VERIFIABLE_SELECTION.md#跨平台构建).

- When the user has authorized PR delivery or merge, use the delivery script in section 7.
It runs this same gate before committing and pushing; do not run a separate full gate first.

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ LLM-EdgeFlow 用算法节点处理规则、检索、文本、图像和语音。P
## 快速开始

本例使用 Linux CPU 环境。在仓库根目录执行所有命令。
macOS Apple Silicon(含 M3)使用 `macos-arm64-cpu` 预设,见[跨平台构建](doc/VERIFIABLE_SELECTION.md#跨平台构建)。
构建需要 C++17 编译器、CMake 3.19+、Ninja、Python 3 和 Git。
首次构建需要联网下载固定版本的依赖。模型权重需单独准备。

Expand All @@ -44,7 +45,7 @@ cmake --preset=inferlet-cpu -B build
cmake --build build --target alg_sdk alg_demo alg_pipeline_tool alg_show --parallel 4
```

默认构建只启用 Inferlet,也可运行无需模型的示例。其他 Backend 须按[构建变体](doc/VERIFIABLE_SELECTION.md#构建变体)
Linux 默认构建只启用 Inferlet,也可运行无需模型的示例。其他 Backend 须按[构建变体](doc/VERIFIABLE_SELECTION.md#构建变体)
显式启用;使用 Inferlet 在 CPU 上验证时见 [Inferlet 接入](doc/VERIFIABLE_SELECTION.md#inferlet-cpu-验证)。
主要产物如下:

Expand Down
17 changes: 15 additions & 2 deletions cmake_ext/BackendOptions.cmake
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
# 在下载或加载厂商包前统一确定 Backend 选择;模块只负责选中依赖的接入。
option(ENABLE_INFERLET "Enable the pinned Inferlet CPU release (Linux x86_64/aarch64)" ON)
# 现有 Inferlet/Kite 包只覆盖 Linux,macOS 默认使用 llama.cpp CPU。
if(LLM_EDGEFLOW_TARGET_OS STREQUAL "macos")
set(_edgeflow_default_inferlet OFF)
set(_edgeflow_default_llamacpp ON)
else()
set(_edgeflow_default_inferlet ON)
set(_edgeflow_default_llamacpp OFF)
endif()
option(ENABLE_INFERLET "Enable the pinned Inferlet CPU release (Linux x86_64/aarch64)" ${_edgeflow_default_inferlet})
option(ENABLE_ONNXRUNTIME "Enable ONNX Runtime" OFF)
option(ENABLE_LLAMACPP "Enable llama.cpp" OFF)
option(ENABLE_LLAMACPP "Enable llama.cpp" ${_edgeflow_default_llamacpp})
option(ENABLE_WHISPERCPP "Enable whisper.cpp (requires llama.cpp)" OFF)
option(ENABLE_KITELLM "Enable the pinned Kite CPU release" OFF)

Expand All @@ -15,3 +23,8 @@ endif()
if(ENABLE_WHISPERCPP AND NOT ENABLE_LLAMACPP)
message(FATAL_ERROR "ENABLE_WHISPERCPP=ON requires ENABLE_LLAMACPP=ON for shared GGML")
endif()

if((ENABLE_INFERLET OR ENABLE_KITELLM) AND NOT LLM_EDGEFLOW_TARGET_OS STREQUAL "linux")
message(FATAL_ERROR
"Inferlet and Kite packages support Linux x86_64/arm64 only; use macos-arm64-cpu on Apple Silicon")
endif()
71 changes: 71 additions & 0 deletions cmake_ext/PlatformOptions.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Toolchain / preset 在 project() 之前确定编译器、SDK 和目标架构。
# 本入口只读取 CMake 的目标信息并校验;Backend 与依赖模块据此决定各自的默认策略。
if(CMAKE_CONFIGURATION_TYPES)
message(FATAL_ERROR "Use a single-config generator such as Ninja; multi-config builds are not supported")
endif()
set(_edgeflow_processor "${CMAKE_SYSTEM_PROCESSOR}")
if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_ARCHITECTURES)
list(LENGTH CMAKE_OSX_ARCHITECTURES _edgeflow_arch_count)
if(NOT _edgeflow_arch_count EQUAL 1)
message(FATAL_ERROR "Build one macOS architecture per directory; universal dependency packages are not provided")
endif()
set(_edgeflow_processor "${CMAKE_OSX_ARCHITECTURES}")
endif()
string(TOLOWER "${_edgeflow_processor}" _edgeflow_processor)
if(_edgeflow_processor MATCHES "^(aarch64|arm64)$")
set(LLM_EDGEFLOW_TARGET_ARCH arm64)
elseif(_edgeflow_processor MATCHES "^(x86_64|amd64)$")
set(LLM_EDGEFLOW_TARGET_ARCH x86_64)
else()
message(FATAL_ERROR "Unsupported target architecture: ${_edgeflow_processor}")
endif()

if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LLM_EDGEFLOW_TARGET_OS linux)
set(LLM_EDGEFLOW_BINARY_FORMAT ELF)
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
set(LLM_EDGEFLOW_TARGET_OS macos)
set(LLM_EDGEFLOW_BINARY_FORMAT MachO)
else()
message(FATAL_ERROR "Unsupported target system: ${CMAKE_SYSTEM_NAME}; use Linux or macOS")
endif()
set(LLM_EDGEFLOW_TARGET_PLATFORM "${LLM_EDGEFLOW_TARGET_OS}-${LLM_EDGEFLOW_TARGET_ARCH}")
set(LLM_EDGEFLOW_EXPECTED_PLATFORM "" CACHE STRING "Optional preset assertion for the resolved target platform")
if(LLM_EDGEFLOW_EXPECTED_PLATFORM AND
NOT LLM_EDGEFLOW_EXPECTED_PLATFORM STREQUAL LLM_EDGEFLOW_TARGET_PLATFORM)
message(FATAL_ERROR
"Preset requires ${LLM_EDGEFLOW_EXPECTED_PLATFORM}, but the toolchain targets ${LLM_EDGEFLOW_TARGET_PLATFORM}. Use a fresh build directory and the matching toolchain.")
endif()
if(CMAKE_SIZEOF_VOID_P AND NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
message(FATAL_ERROR "The selected target requires a 64-bit ABI")
endif()

# 禁止在已有构建目录里静默切换目标,依赖查找前就给出明确诊断。
if(DEFINED LLM_EDGEFLOW_CONFIGURED_PLATFORM AND
NOT LLM_EDGEFLOW_CONFIGURED_PLATFORM STREQUAL LLM_EDGEFLOW_TARGET_PLATFORM)
message(FATAL_ERROR "Target platform changed; configure in a fresh build directory")
endif()
set(LLM_EDGEFLOW_CONFIGURED_PLATFORM "${LLM_EDGEFLOW_TARGET_PLATFORM}" CACHE INTERNAL "Configured target platform")

set(LLM_EDGEFLOW_NATIVE_EXECUTION ON)
if(CMAKE_CROSSCOMPILING)
set(LLM_EDGEFLOW_NATIVE_EXECUTION OFF)
endif()
if(LLM_EDGEFLOW_TARGET_OS STREQUAL "macos")
string(TOLOWER "${CMAKE_HOST_SYSTEM_PROCESSOR}" _edgeflow_host_processor)
if(_edgeflow_host_processor MATCHES "^(aarch64|arm64)$")
set(_edgeflow_host_processor arm64)
elseif(_edgeflow_host_processor MATCHES "^(x86_64|amd64)$")
set(_edgeflow_host_processor x86_64)
endif()
if(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin" OR
NOT _edgeflow_host_processor STREQUAL LLM_EDGEFLOW_TARGET_ARCH)
set(LLM_EDGEFLOW_NATIVE_EXECUTION OFF)
endif()
endif()
option(BUILD_TESTING "Build native host and runtime tests" ${LLM_EDGEFLOW_NATIVE_EXECUTION})
if(BUILD_TESTING AND NOT LLM_EDGEFLOW_NATIVE_EXECUTION)
message(FATAL_ERROR
"Cross builds compile target artifacts only. Set BUILD_TESTING=OFF; run the native gate separately on the target (on Apple Silicon use a native arm64 terminal). Python/tooling tests execute target programs.")
endif()
message(STATUS "LLM-EdgeFlow target: ${LLM_EDGEFLOW_TARGET_PLATFORM}; cross compiling: ${CMAKE_CROSSCOMPILING}")
Loading
Loading