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
13 changes: 6 additions & 7 deletions .github/workflows/native_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,13 @@ jobs:

ARMV82_LIB="$OUT_DIR/libggml-cpu-android_armv8.2_2.so"
test -f "$ARMV82_LIB" || { echo "Missing $ARMV82_LIB"; exit 1; }
ARMV82_DISASM="$(mktemp)"
SCORE_SYMS_FILE="$(mktemp)"
trap 'rm -f "${DISASM_FILE:-}" "${ARMV82_DISASM:-}" "${SCORE_SYMS_FILE:-}"' EXIT
"$OBJDUMP" -d --no-show-raw-insn "$ARMV82_LIB" > "$ARMV82_DISASM"
if grep -Eq '\b(addvl|ptrue|cntw|rdvl|ld1b|st1b)\b' "$ARMV82_DISASM"; then
echo "Found unexpected SVE instructions in $ARMV82_LIB" >&2
exit 1
fi
trap 'rm -f "${DISASM_FILE:-}" "${SCORE_SYMS_FILE:-}"' EXIT
python3 tools/validate_android_cpu_isa.py \
--objdump "$OBJDUMP" --readelf "$READELF" \
--llama-source third_party/llama.cpp \
--kleidiai-source "build/android-arm64-v8a-${{ matrix.backend }}-android_armv8.2_2/_deps/kleidiai-src" \
"$ARMV82_LIB"

if [ "${{ matrix.include_core }}" = "true" ]; then
expected_cpu_variants=(
Expand Down
118 changes: 118 additions & 0 deletions .github/workflows/validate_wrapper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ on:
- '.github/workflows/validate_wrapper.yml'
- '.github/workflows/native_release.yml'
- 'CMakeLists.txt'
- 'cmake/**'
- 'src/**'
- 'tests/**'
- 'tools/tts_smoke.cpp'
- 'tools/build.py'
- 'tools/validate_android_cpu_isa.py'
- 'tools/linux_dlopen_smoke.c'
- 'tools/package_linux_artifact.py'
- 'tools/validate_linux_artifact.py'
Expand All @@ -22,10 +24,12 @@ on:
- '.github/workflows/validate_wrapper.yml'
- '.github/workflows/native_release.yml'
- 'CMakeLists.txt'
- 'cmake/**'
- 'src/**'
- 'tests/**'
- 'tools/tts_smoke.cpp'
- 'tools/build.py'
- 'tools/validate_android_cpu_isa.py'
- 'tools/linux_dlopen_smoke.c'
- 'tools/package_linux_artifact.py'
- 'tools/validate_linux_artifact.py'
Expand All @@ -36,6 +40,120 @@ permissions:
contents: read

jobs:
android-arm64-isa:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
- name: Select candidate upstream
run: |
git -C third_party/llama.cpp fetch --depth=1 origin tag v0.4.0
git -C third_party/llama.cpp checkout --detach 5266f24da75dc449bd56cbed7addb9c8e4a6a73e
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
- uses: android-actions/setup-android@v4
with:
packages: platform-tools ndk;28.2.13676358
- name: Install build dependencies
run: sudo apt-get update && sudo apt-get install -y ninja-build
- name: Build and audit the release ARMv8.2 CPU variant
run: |
export ANDROID_NDK_HOME="${ANDROID_SDK_ROOT}/ndk/28.2.13676358"
python3 - <<'PY'
import os, sys
from pathlib import Path
sys.path.insert(0, 'tools')
import build
name, arch, features = build.ANDROID_ARM64_CPU_VARIANTS[2]
build.build_android_arm64_cpu_variant(
name, arch, features, build_dir=Path('build/android-isa').resolve(),
ndk=Path(os.environ['ANDROID_NDK_HOME']), env=dict(os.environ), jobs=4)
PY
LLVM="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin"
python3 tools/validate_android_cpu_isa.py \
--objdump "$LLVM/llvm-objdump" --readelf "$LLVM/llvm-readelf" \
--llama-source third_party/llama.cpp \
--kleidiai-source build/android-isa/_deps/kleidiai-src \
build/android-isa/bin/libggml-cpu.so

kleidiai-dispatch-emulated:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
- name: Select candidate upstream
run: |
git -C third_party/llama.cpp fetch --depth=1 origin tag v0.4.0
git -C third_party/llama.cpp checkout --detach 5266f24da75dc449bd56cbed7addb9c8e4a6a73e
- name: Install cross compiler and CPU emulator
run: sudo apt-get update && sudo apt-get install -y ninja-build gcc-aarch64-linux-gnu g++-aarch64-linux-gnu qemu-user
- name: Build the real baseline CPU and dispatch test
run: >-
cmake -S . -B build/kleidiai-dispatch -G Ninja
-DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=aarch64
-DCMAKE_C_COMPILER=aarch64-linux-gnu-gcc
-DCMAKE_CXX_COMPILER=aarch64-linux-gnu-g++
-DCMAKE_BUILD_TYPE=Release -DGGML_CPU_ARM_ARCH=armv8-a
-DGGML_CPU_KLEIDIAI=ON -DGGML_OPENMP=OFF
-DGGML_VULKAN=OFF -DGGML_BLAS=OFF -DGGML_CCACHE=OFF
-DLLAMADART_BUILD_KLEIDIAI_TESTS=ON
- name: Compile dispatch and quantized compute qualification
run: cmake --build build/kleidiai-dispatch --target llamadart_kleidiai_dispatch_test --parallel 4
- name: Run without SVE on baseline and optimized CPU profiles
run: |
for CPU in cortex-a53 max,sve=off,sme=off; do
qemu-aarch64 -cpu "$CPU" -L /usr/aarch64-linux-gnu \
-E LD_LIBRARY_PATH="$PWD/build/kleidiai-dispatch/bin" \
build/kleidiai-dispatch/bin/llamadart_kleidiai_dispatch_test
done

windows-arm64-kleidiai:
runs-on: windows-11-arm
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
upstream: [pinned, v0.4.0]
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
- name: Setup ARM64 MSVC environment
uses: TheMrMilchmann/setup-msvc-dev@v4.1.0
with:
arch: arm64
- name: Select candidate upstream
if: matrix.upstream == 'v0.4.0'
shell: bash
run: |
git -C third_party/llama.cpp fetch --depth=1 origin tag v0.4.0
git -C third_party/llama.cpp checkout --detach 5266f24da75dc449bd56cbed7addb9c8e4a6a73e
- name: Verify assembly policy regressions
run: python -m unittest discover -s tests -p test_kleidiai_windows.py
- name: Configure release compiler and optimized CPU
run: >-
cmake --preset windows-arm64-full
-DGGML_BLAS=OFF -DGGML_VULKAN=OFF -DGGML_CUDA=OFF
-DGGML_OPENCL=OFF -DGGML_OPENMP=OFF -DGGML_CCACHE=OFF
-DGGML_CPU_KLEIDIAI=ON -DLLAMADART_BUILD_TESTS=ON
- name: Build wrapper and CPU contracts
run: cmake --build --preset windows-arm64-full --parallel 4
- name: Run native wrapper contracts
run: |
$wrapperDir = (Resolve-Path build/wa64/Release).Path
$upstreamDir = (Resolve-Path build/wa64/bin/Release).Path
$env:PATH = "$wrapperDir;$upstreamDir;$env:PATH"
ctest --test-dir build/wa64 -C Release --timeout 120 --verbose --output-on-failure

linux-artifact-contract:
runs-on: ubuntu-latest
strategy:
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ Optional Linux container build:

## Release Workflows

For ARM64 upstream upgrades, run the candidate Windows/Android and compiled
dispatch gates in `validate_wrapper.yml` before merging. See
`docs/platform_backend_strategy.md` for the ISA audit contract. Never refresh
audited source fingerprints merely to silence a failed check.

- `.github/workflows/native_release.yml`
- Exact native build + release workflow, used by manual dispatch and the
automated stable dispatcher.
Expand Down
33 changes: 33 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ find_package(Threads REQUIRED)

option(LLAMADART_BUILD_TESTS "Build libllamadart wrapper tests" OFF)
option(LLAMADART_BUILD_TTS_SMOKE "Build the local Qwen3-TTS smoke tool" OFF)
option(LLAMADART_BUILD_KLEIDIAI_TESTS "Build standalone KleidiAI dispatch and compute qualification" OFF)

# Keep all targets PIC-safe for shared linking.
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
Expand Down Expand Up @@ -138,6 +139,8 @@ if (APPLE)
endif()

add_subdirectory(third_party/llama.cpp)
include(cmake/kleidiai_windows.cmake)
llamadart_configure_kleidiai_windows_assembly()

# Probe declarations without calling upstream symbols or running target code.
# Recheck after switching upstream revisions in an existing build.
Expand Down Expand Up @@ -228,6 +231,36 @@ if (LLAMADART_BUILD_TESTS)
LLAMADART_MTMD_HELPER_HAS_OPTIONS=$<BOOL:${LLAMADART_MTMD_HELPER_HAS_OPTIONS}>)
target_link_libraries(llamadart_mtmd_compat_test PRIVATE mtmd)
add_test(NAME llamadart_mtmd_compat_test COMMAND llamadart_mtmd_compat_test)

# Contract assertions must execute against optimized Release libraries too.
foreach(test_target llamadart_speculative_api_test llamadart_tts_api_test)
if (MSVC)
target_compile_options(${test_target} PRIVATE /UNDEBUG)
else()
target_compile_options(${test_target} PRIVATE -UNDEBUG)
endif()
endforeach()
endif()

if (LLAMADART_BUILD_KLEIDIAI_TESTS)
if (NOT TARGET kleidiai OR NOT TARGET ggml-cpu)
message(FATAL_ERROR "KleidiAI qualification requires upstream standalone kleidiai and ggml-cpu targets")
endif()
enable_testing()
add_executable(llamadart_kleidiai_dispatch_test tests/kleidiai_dispatch_test.cpp)
target_compile_features(llamadart_kleidiai_dispatch_test PRIVATE cxx_std_17)
target_include_directories(llamadart_kleidiai_dispatch_test PRIVATE
third_party/llama.cpp/ggml/include
third_party/llama.cpp/ggml/src
third_party/llama.cpp/ggml/src/ggml-cpu/kleidiai)
set_target_properties(llamadart_kleidiai_dispatch_test PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
# ggml-cpu is a loadable MODULE on Android. Link its actual file to test
# the shipped selectors, not a second test-only copy of their source.
target_link_libraries(llamadart_kleidiai_dispatch_test PRIVATE
"$<TARGET_FILE:ggml-cpu>" ggml ggml-base)
add_dependencies(llamadart_kleidiai_dispatch_test ggml-cpu)
add_test(NAME llamadart_kleidiai_dispatch_test COMMAND llamadart_kleidiai_dispatch_test)
endif()

if (LLAMADART_BUILD_TTS_SMOKE)
Expand Down
73 changes: 73 additions & 0 deletions cmake/kleidiai_windows.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# ClangCL's preprocessor emits GNU-style line markers, which armasm64 rejects
# with A2230. Some kernels also select GNU assembly syntax when __clang__ is
# defined, even though MSBuild invokes armasm64, not Clang's assembler. Configure
# only the MARMASM preprocessing task: omit line markers and select upstream's
# existing MSVC assembly dialect. C/C++ compilation remains unchanged.
function(llamadart_configure_kleidiai_windows_assembly)
if (NOT MSVC OR NOT CMAKE_C_COMPILER_ID STREQUAL "Clang" OR
NOT CMAKE_GENERATOR MATCHES "^Visual Studio" OR NOT TARGET kleidiai)
return()
endif()

# VS_SETTINGS applies to known source types starting with CMake 3.22.
if (CMAKE_VERSION VERSION_LESS 3.22)
message(FATAL_ERROR "ClangCL KleidiAI assembly requires CMake >= 3.22")
endif()

get_target_property(kleidiai_source_dir kleidiai SOURCE_DIR)
get_target_property(kleidiai_sources kleidiai SOURCES)
foreach(source IN LISTS kleidiai_sources)
if (NOT IS_ABSOLUTE "${source}")
set(source "${kleidiai_source_dir}/${source}")
endif()
get_source_file_property(language "${source}"
TARGET_DIRECTORY kleidiai LANGUAGE)
if (language STREQUAL "ASM_MARMASM")
set_property(SOURCE "${source}" TARGET_DIRECTORY kleidiai APPEND
PROPERTY VS_SETTINGS "PreprocessSuppressLineNumbers=true"
"UndefinePreprocessorDefinitions=__clang__\;%(UndefinePreprocessorDefinitions)")
endif()
endforeach()

llamadart_add_kleidiai_clangcl_kernels()
endfunction()

# KleidiAI 1.24's MSVC list excludes GNU inline-assembly C kernels that ClangCL
# supports and ggml's runtime-dispatched kernel table references. Restore only
# those exact kernels, with the same per-source ISA and SME vectorization policy
# as KleidiAI's non-MSVC build. Never raise the whole library's baseline ISA.
function(llamadart_add_kleidiai_clangcl_kernels)
get_target_property(source_dir kleidiai SOURCE_DIR)
set(dotprod_sources
kai/ukernels/matmul/matmul_clamp_f32_qsi8d32p_qsi4c32p/kai_matmul_clamp_f32_qsi8d32p1x8_qsi4c32p4x8_1x4x32_neon_dotprod.c
kai/ukernels/matmul/matmul_clamp_f32_qsi8d32p_qsi4c32p/kai_matmul_clamp_f32_qsi8d32p1x4_qsi4c32p4x4_1x4_neon_dotprod.c
kai/ukernels/matmul/matmul_clamp_f32_qsi8d32p_qsi4c32p/kai_matmul_clamp_f32_qsi8d32p4x4_qsi4c32p4x4_16x4_neon_dotprod.c
kai/ukernels/matmul/matmul_clamp_f32_qai8dxp_qsi8cxp/kai_matmul_clamp_f32_qai8dxp1x8_qsi8cxp4x8_1x4_neon_dotprod.c
kai/ukernels/matmul/matmul_clamp_f32_qai8dxp_qsi8cxp/kai_matmul_clamp_f32_qai8dxp1x4_qsi8cxp4x4_1x4_neon_dotprod.c
kai/ukernels/matmul/matmul_clamp_f32_qai8dxp_qsi8cxp/kai_matmul_clamp_f32_qai8dxp4x4_qsi8cxp4x4_16x4_neon_dotprod.c)
set(i8mm_sources
kai/ukernels/matmul/matmul_clamp_f32_qsi8d32p_qsi4c32p/kai_matmul_clamp_f32_qsi8d32p4x8_qsi4c32p4x8_16x4_neon_i8mm.c
kai/ukernels/matmul/matmul_clamp_f32_qai8dxp_qsi8cxp/kai_matmul_clamp_f32_qai8dxp4x8_qsi8cxp4x8_16x4_neon_i8mm.c)
set(sme_sources
kai/ukernels/matmul/matmul_clamp_f32_qsi8d32p_qsi4c32p/kai_matmul_clamp_f32_qsi8d32p1x4_qsi4c32p4vlx4_1x4vl_sme2_sdot.c
kai/ukernels/matmul/pack/kai_lhs_pack_bf16p2vlx2_f32_sme.c
kai/ukernels/matmul/pack/kai_rhs_pack_kxn_bf16p2vlx2b_f32_x32_sme.c)
foreach(family dotprod i8mm sme)
if (family STREQUAL "sme")
set(options /clang:-march=armv8.2-a+sve+sve2
/clang:-fno-tree-vectorize /clang:-fno-tree-slp-vectorize)
else()
set(options "/clang:-march=armv8.2-a+${family}")
endif()
foreach(source IN LISTS ${family}_sources)
get_target_property(existing_sources kleidiai SOURCES)
set(absolute_source "${source_dir}/${source}")
if (NOT source IN_LIST existing_sources AND
NOT absolute_source IN_LIST existing_sources)
target_sources(kleidiai PRIVATE "${absolute_source}")
endif()
set_property(SOURCE "${absolute_source}" TARGET_DIRECTORY kleidiai
PROPERTY COMPILE_OPTIONS "${options}")
endforeach()
endforeach()
endfunction()
27 changes: 26 additions & 1 deletion docs/platform_backend_strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,34 @@
- Kleidi is enabled on Linux arm64 and Windows arm64 in this pipeline.
- Android arm64 keeps Kleidi on by building each CPU variant in its own
isolated configuration so higher-tier ISA flags do not leak into lower-tier
variant binaries.
ggml code. With llama.cpp v0.4.0, standalone KleidiAI also contains kernels
selected by runtime CPU features. The Android ISA audit permits scalable
instructions only inside exact reviewed ELF function ranges and binds that
exception to the complete audited ggml/Kleidi source fingerprints. Unknown
ranges or changed source fail closed; legacy artifacts without scalable code
continue through the strict path. Never refresh fingerprints without reviewing
feature detection, kernel tables and callers, and passing compiled dispatch
tests and non-SVE compute qualification.
- Windows ARM64 retains ClangCL and all Kleidi kernels. Source-local Visual
Studio metadata selects the native ARMASM preprocessing dialect and suppresses
incompatible line markers. The owning CMake integration also restores the
exact ClangCL-compatible C kernels referenced by ggml but omitted from
KleidiAI's MSVC source list, preserving their upstream per-source ISA flags
without raising the baseline ISA for other code.
- Non-Apple: keep backends as separate dynamic libraries (`GGML_BACKEND_DL=ON`).

## ARM64 upgrade qualification

`Validate Wrapper` checks pinned and candidate v0.4.0 Windows ARM64 builds,
the actual Android ARMv8.2 artifact, and compiled Kleidi selectors plus quantized
matrix computation under non-SVE QEMU profiles. QEMU is deterministic CPU
compatibility evidence, not physical-device or GPU-performance evidence.
Wrapper contract assertions stay active in Release builds; Windows CTest resolves
both wrapper and upstream DLL directories and bounds each test to 120 seconds.
Use `-DLLAMADART_BUILD_KLEIDIAI_TESTS=ON` with standalone-Kleidi upstream to
build `llamadart_kleidiai_dispatch_test`; it links the actual CPU module.
The Android artifact check is `tools/validate_android_cpu_isa.py --help`.

## Runtime Packaging Model

- Apple: ship only `libllamadart` for each target.
Expand Down
Loading
Loading