diff --git a/.devops/cpu.Dockerfile b/.devops/cpu.Dockerfile index 9f2070800..ee9efed35 100644 --- a/.devops/cpu.Dockerfile +++ b/.devops/cpu.Dockerfile @@ -13,6 +13,7 @@ ARG GCC_VERSION=14 FROM docker.io/ubuntu:$UBUNTU_VERSION AS build ARG GCC_VERSION=14 +ARG AUDIOCPP_VERSION=dev # Install build toolchain RUN apt-get update && \ @@ -44,6 +45,7 @@ RUN cmake -S . -B build \ -DENGINE_ENABLE_VULKAN=OFF \ -DENGINE_ENABLE_OPENMP=ON \ -DAUDIOCPP_BUILD_NATIVE_MODEL_MANAGER=ON \ + -DAUDIOCPP_VERSION="${AUDIOCPP_VERSION}" \ -DENGINE_BUILD_EXAMPLES=OFF \ -DENGINE_BUILD_TESTS=OFF \ -DENGINE_BUILD_WARMBENCH=OFF && \ diff --git a/.devops/cuda.Dockerfile b/.devops/cuda.Dockerfile index 0ca573662..14df7d455 100644 --- a/.devops/cuda.Dockerfile +++ b/.devops/cuda.Dockerfile @@ -17,6 +17,7 @@ ARG BASE_CUDA_RUN_CONTAINER=docker.io/nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu FROM ${BASE_CUDA_DEV_CONTAINER} AS build ARG GCC_VERSION=14 +ARG AUDIOCPP_VERSION=dev # CUDA architectures to compile for. # - default = the portable default list from CMakeLists.txt # - for a custom arch set build with --build-arg CUDA_DOCKER_ARCH="89-real;...". @@ -46,6 +47,7 @@ RUN if [ "${CUDA_DOCKER_ARCH}" != "default" ]; then \ -DENGINE_ENABLE_VULKAN=OFF \ -DENGINE_ENABLE_OPENMP=ON \ -DAUDIOCPP_BUILD_NATIVE_MODEL_MANAGER=ON \ + -DAUDIOCPP_VERSION="${AUDIOCPP_VERSION}" \ -DENGINE_BUILD_EXAMPLES=OFF \ -DENGINE_BUILD_TESTS=OFF \ -DENGINE_BUILD_WARMBENCH=OFF \ diff --git a/.devops/vulkan.Dockerfile b/.devops/vulkan.Dockerfile new file mode 100644 index 000000000..7ea74058c --- /dev/null +++ b/.devops/vulkan.Dockerfile @@ -0,0 +1,116 @@ +# audio.cpp — Vulkan Dockerfile +# +# Usage: +# docker build -f .devops/vulkan.Dockerfile -t local/audiocpp:full-vulkan . + +# ── BUILD: Compile all release binaries with Vulkan ────────────────────────── +ARG UBUNTU_VERSION=24.04 +ARG BUILD_DATE=N/A +ARG APP_VERSION=N/A +ARG APP_REVISION=N/A +ARG GCC_VERSION=14 + +FROM docker.io/ubuntu:$UBUNTU_VERSION AS build + +ARG GCC_VERSION=14 +ARG AUDIOCPP_VERSION=dev + +# Install build toolchain and Vulkan shader/compiler headers. +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + gcc-${GCC_VERSION} g++-${GCC_VERSION} make cmake libgomp1 ca-certificates \ + glslc libvulkan-dev spirv-headers \ + libxcb-xinput0 libxcb-xinerama0 libxcb-cursor-dev && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +ENV CC=gcc-${GCC_VERSION} CXX=g++-${GCC_VERSION} + +WORKDIR /app +COPY . . + +# Validate architecture (Vulkan backend is only supported on amd64 and arm64) +ARG TARGETARCH=amd64 +RUN if [ "$TARGETARCH" = "amd64" ] || [ "$TARGETARCH" = "arm64" ]; then \ + echo "Building for $TARGETARCH"; \ + else \ + echo "Unsupported architecture: $TARGETARCH"; \ + exit 1; \ + fi + +# Configure and build +RUN cmake -S . -B build \ + -DCMAKE_BUILD_TYPE=Release \ + -DAUDIOCPP_MODEL_SET=full \ + -DENGINE_ENABLE_CPU_ALL_VARIANTS=ON \ + -DENGINE_ENABLE_CUDA=OFF \ + -DENGINE_ENABLE_VULKAN=ON \ + -DENGINE_ENABLE_OPENMP=ON \ + -DAUDIOCPP_BUILD_NATIVE_MODEL_MANAGER=ON \ + -DAUDIOCPP_VERSION="${AUDIOCPP_VERSION}" \ + -DENGINE_BUILD_EXAMPLES=OFF \ + -DENGINE_BUILD_TESTS=OFF \ + -DENGINE_BUILD_WARMBENCH=OFF && \ + cmake --build build --parallel $(nproc) \ + --target audiocpp_cli \ + --target audiocpp_server \ + --target audiocpp_model_manager \ + --target model_perf + +# Collect shared libraries +RUN mkdir -p /app/lib && \ + find build -name "*.so*" -exec cp -P {} /app/lib \; + +# Collect binaries + multiplexer into /app/full +RUN mkdir -p /app/full && \ + cp build/bin/audiocpp_cli build/bin/audiocpp_server build/bin/audiocpp_model_manager \ + build/bin/model_perf /app/full/ && \ + cp .devops/entrypoint.sh /app/full/entrypoint.sh && \ + chmod +x /app/full/entrypoint.sh + +# ── BASE: Shared runtime (OS + Vulkan runtime libs) ────────────────────────── +FROM docker.io/ubuntu:$UBUNTU_VERSION AS base + +ARG BUILD_DATE=N/A +ARG APP_VERSION=N/A +ARG APP_REVISION=N/A +ARG IMAGE_URL=N/A +ARG IMAGE_SOURCE=N/A + +LABEL org.opencontainers.image.created=$BUILD_DATE \ + org.opencontainers.image.version=$APP_VERSION \ + org.opencontainers.image.revision=$APP_REVISION \ + org.opencontainers.image.title="audio.cpp" \ + org.opencontainers.image.description="An all-in-one, pure C++ inference engine for audio models, powered by ggml" \ + org.opencontainers.image.url=$IMAGE_URL \ + org.opencontainers.image.source=$IMAGE_SOURCE + +# Runtime deps: OpenMP threading, Vulkan loader/Mesa ICDs, curl (healthcheck), +# ffmpeg (audio I/O), python3 for the native WebUI's spec-backed model installer. +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + libgomp1 libvulkan1 mesa-vulkan-drivers \ + libglvnd0 libgl1 libglx0 libegl1 libgles2 \ + curl ffmpeg python3 ca-certificates && \ + apt-get autoremove -y && \ + apt-get clean -y && \ + rm -rf /tmp/* /var/tmp/* && \ + find /var/cache/apt/archives /var/lib/apt/lists -not -name lock -type f -delete && \ + find /var/cache -type f -delete + +COPY --from=build /app/lib/ /app + +WORKDIR /app + +# ── FULL: All binaries + entrypoint.sh multiplexer ──────────────────────────── +FROM base AS full + +COPY --from=build /app/full /app +COPY model_specs/ /app/model_specs/ +COPY tools/model_manager_v2.py /app/tools/model_manager_v2.py + +RUN mkdir -p /app/models && chown ubuntu:ubuntu /app/models + +USER ubuntu + +ENTRYPOINT ["/app/entrypoint.sh"] diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4ac148798..203edd153 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -7,6 +7,8 @@ # full-cuda12-YYYYMMDD-HHHHHHH (pinned to date + short SHA, immutable) # full-cuda13 (latest, mutable) # full-cuda13-YYYYMMDD-HHHHHHH (pinned to date + short SHA, immutable) +# full-vulkan (latest, mutable) +# full-vulkan-YYYYMMDD-HHHHHHH (pinned to date + short SHA, immutable) name: Build and publish Docker images @@ -21,6 +23,10 @@ on: description: 'Force build even if no new commits' type: boolean default: true + version: + description: 'audio.cpp version string embedded in the binaries' + type: string + default: dev concurrency: group: ${{ github.workflow }}-${{ github.head_ref && github.ref || github.run_id }} @@ -80,6 +86,7 @@ jobs: outputs: build_date: ${{ steps.date.outputs.date }} date_tag: ${{ steps.date.outputs.tag }} + app_version: ${{ steps.version.outputs.version }} image_repo: ${{ steps.image_repo.outputs.image_repo }} steps: - name: Get build date @@ -88,6 +95,16 @@ jobs: echo "date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> "$GITHUB_OUTPUT" echo "tag=$(date -u +'%Y%m%d')" >> "$GITHUB_OUTPUT" + - name: Resolve app version + id: version + run: | + VERSION="${{ github.event.inputs.version }}" + if [[ -z "$VERSION" ]]; then + VERSION="dev" + fi + VERSION="${VERSION#v}" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + - name: Determine lowercase image repository id: image_repo run: echo "image_repo=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" @@ -145,6 +162,19 @@ jobs: runs_on: ubuntu-24.04-arm cuda_version: "13.3.0" enabled: true + # ── Vulkan ── + - tag: vulkan + dockerfile: .devops/vulkan.Dockerfile + platforms: linux/amd64 + arch: amd64 + runs_on: ubuntu-24.04 + enabled: true + - tag: vulkan + dockerfile: .devops/vulkan.Dockerfile + platforms: linux/arm64 + arch: arm64 + runs_on: ubuntu-24.04-arm + enabled: true steps: - name: Skip when not enabled if: ${{ matrix.enabled != true }} @@ -179,7 +209,8 @@ jobs: outputs: type=image,name=ghcr.io/${{ needs.metadata.outputs.image_repo }},push-by-digest=true,name-canonical=true,push=true,oci-mediatypes=true build-args: | BUILD_DATE=${{ needs.metadata.outputs.build_date }} - APP_VERSION=${{ github.sha }} + APP_VERSION=${{ needs.metadata.outputs.app_version }} + AUDIOCPP_VERSION=${{ needs.metadata.outputs.app_version }} APP_REVISION=${{ github.sha }} IMAGE_URL=${{ github.server_url }}/${{ github.repository }} IMAGE_SOURCE=${{ github.server_url }}/${{ github.repository }} @@ -222,7 +253,7 @@ jobs: strategy: fail-fast: false matrix: - tag: [cpu, cuda12, cuda13] + tag: [cpu, cuda12, cuda13, vulkan] steps: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index b01a046e7..3e104a45d 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -51,6 +51,7 @@ jobs: run: | cmake -S . -B "$BUILD_DIR" \ -DCMAKE_BUILD_TYPE=Debug \ + -DAUDIOCPP_VERSION=ci \ -DENGINE_ENABLE_CUDA=OFF \ -DENGINE_ENABLE_VULKAN=${{ matrix.enable_vulkan }} diff --git a/.github/workflows/mac-build.yml b/.github/workflows/mac-build.yml index 471d6b928..d434203c0 100644 --- a/.github/workflows/mac-build.yml +++ b/.github/workflows/mac-build.yml @@ -35,6 +35,7 @@ jobs: run: | cmake -S . -B "$BUILD_DIR" \ -DCMAKE_BUILD_TYPE=Debug \ + -DAUDIOCPP_VERSION=ci \ -DENGINE_ENABLE_CUDA=OFF \ -DENGINE_ENABLE_VULKAN=OFF \ -DENGINE_ENABLE_METAL=OFF \ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf11be452..fe078799b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,6 +58,7 @@ jobs: runs-on: ubuntu-latest outputs: tag: ${{ steps.version.outputs.tag }} + version: ${{ steps.version.outputs.version }} needs: check-release if: ${{ needs.check-release.outputs.should_release == 'true' }} steps: @@ -76,7 +77,9 @@ jobs: exit 1 fi [[ "$TAG" == v* ]] || TAG="v${TAG}" + VERSION="${TAG#v}" echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" macos-metal: name: macOS (${{ matrix.build }}) @@ -105,6 +108,7 @@ jobs: cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_RPATH='@loader_path' -DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \ -DENGINE_ENABLE_CUDA=OFF -DENGINE_ENABLE_VULKAN=OFF \ + -DAUDIOCPP_VERSION=${{ needs.get-version.outputs.version }} \ ${{ matrix.defines }} ${{ env.CMAKE_ARGS }} - name: Build run: cmake --build build --config Release --parallel "$(sysctl -n hw.logicalcpu)" --target ${{ env.BUILD_TARGETS }} @@ -132,7 +136,9 @@ jobs: - name: Configure run: | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DENGINE_ENABLE_CUDA=OFF -DENGINE_ENABLE_VULKAN=OFF ${{ env.CMAKE_ARGS }} + -DENGINE_ENABLE_CUDA=OFF -DENGINE_ENABLE_VULKAN=OFF \ + -DAUDIOCPP_VERSION=${{ needs.get-version.outputs.version }} \ + ${{ env.CMAKE_ARGS }} - name: Build run: cmake --build build --config Release --parallel "$(nproc)" --target ${{ env.BUILD_TARGETS }} - name: Stage bundle @@ -159,7 +165,9 @@ jobs: - name: Configure run: | cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ - -DENGINE_ENABLE_CUDA=OFF -DENGINE_ENABLE_VULKAN=ON ${{ env.CMAKE_ARGS }} + -DENGINE_ENABLE_CUDA=OFF -DENGINE_ENABLE_VULKAN=ON \ + -DAUDIOCPP_VERSION=${{ needs.get-version.outputs.version }} \ + ${{ env.CMAKE_ARGS }} - name: Build run: cmake --build build --config Release --parallel "$(nproc)" --target ${{ env.BUILD_TARGETS }} - name: Stage bundle @@ -184,9 +192,9 @@ jobs: - name: Build shell: pwsh run: | - .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_cli -DeploymentBuild -NativeModelManager - .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_server -DeploymentBuild -NativeModelManager - .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_gguf -DeploymentBuild -NativeModelManager + .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_cli -DeploymentBuild -NativeModelManager -Version ${{ needs.get-version.outputs.version }} + .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_server -DeploymentBuild -NativeModelManager -Version ${{ needs.get-version.outputs.version }} + .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_gguf -DeploymentBuild -NativeModelManager -Version ${{ needs.get-version.outputs.version }} - name: Bundle VC runtime shell: pwsh working-directory: build/windows-cpu-release/bin @@ -233,9 +241,9 @@ jobs: - name: Build shell: pwsh run: | - .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_cli -DeploymentBuild -NativeModelManager -CpuArch baseline -Llamafile OFF - .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_server -DeploymentBuild -NativeModelManager -CpuArch baseline -Llamafile OFF - .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_gguf -DeploymentBuild -NativeModelManager -CpuArch baseline -Llamafile OFF + .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_cli -DeploymentBuild -NativeModelManager -CpuArch baseline -Llamafile OFF -Version ${{ needs.get-version.outputs.version }} + .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_server -DeploymentBuild -NativeModelManager -CpuArch baseline -Llamafile OFF -Version ${{ needs.get-version.outputs.version }} + .\scripts\build_windows.ps1 -Preset windows-cpu-release -Target audiocpp_gguf -DeploymentBuild -NativeModelManager -CpuArch baseline -Llamafile OFF -Version ${{ needs.get-version.outputs.version }} - name: Bundle VC runtime shell: pwsh working-directory: build/windows-cpu-release/bin @@ -289,9 +297,9 @@ jobs: - name: Build shell: pwsh run: | - .\scripts\build_windows.ps1 -Preset windows-vulkan-release -Target audiocpp_cli -DeploymentBuild -NativeModelManager - .\scripts\build_windows.ps1 -Preset windows-vulkan-release -Target audiocpp_server -DeploymentBuild -NativeModelManager - .\scripts\build_windows.ps1 -Preset windows-vulkan-release -Target audiocpp_gguf -DeploymentBuild -NativeModelManager + .\scripts\build_windows.ps1 -Preset windows-vulkan-release -Target audiocpp_cli -DeploymentBuild -NativeModelManager -Version ${{ needs.get-version.outputs.version }} + .\scripts\build_windows.ps1 -Preset windows-vulkan-release -Target audiocpp_server -DeploymentBuild -NativeModelManager -Version ${{ needs.get-version.outputs.version }} + .\scripts\build_windows.ps1 -Preset windows-vulkan-release -Target audiocpp_gguf -DeploymentBuild -NativeModelManager -Version ${{ needs.get-version.outputs.version }} - name: Bundle VC runtime shell: pwsh working-directory: build/windows-vulkan-release/bin @@ -373,6 +381,7 @@ jobs: -DENGINE_ENABLE_LLAMAFILE=ON -DENGINE_ENABLE_NATIVE_CPU=ON -DENGINE_BUILD_TESTS=OFF ^ -DENGINE_ENABLE_CPU_ALL_VARIANTS=ON ^ "-DCMAKE_CUDA_ARCHITECTURES=${{ matrix.cuda_archs }}" ^ + -DAUDIOCPP_VERSION=${{ needs.get-version.outputs.version }} ^ -DAUDIOCPP_DEPLOYMENT_BUILD=ON -DAUDIOCPP_BUILD_NATIVE_MODEL_MANAGER=ON -DCUDAToolkit_ROOT="%CUDA_PATH%" cmake --build build --config Release -j %NUMBER_OF_PROCESSORS% --target ${{ env.BUILD_TARGETS }} - name: Bundle CUDA runtime diff --git a/.github/workflows/server-memory-guard.yml b/.github/workflows/server-memory-guard.yml index 57f5829f2..d5201923c 100644 --- a/.github/workflows/server-memory-guard.yml +++ b/.github/workflows/server-memory-guard.yml @@ -36,7 +36,9 @@ jobs: run: | cmake -S . -B build \ -DCMAKE_BUILD_TYPE=Release \ + -DAUDIOCPP_VERSION=ci \ -DENGINE_BUILD_TESTS=ON \ + -DENGINE_BUILD_EXTENDED_TESTS=ON \ -DENGINE_ENABLE_OPENMP=OFF \ -DENGINE_ENABLE_CUDA=OFF \ -DENGINE_ENABLE_VULKAN=OFF \ @@ -48,7 +50,9 @@ jobs: run: | cmake -S . -B build ` -DCMAKE_BUILD_TYPE=Release ` + -DAUDIOCPP_VERSION=ci ` -DENGINE_BUILD_TESTS=ON ` + -DENGINE_BUILD_EXTENDED_TESTS=ON ` -DENGINE_ENABLE_OPENMP=OFF ` -DENGINE_ENABLE_CUDA=OFF ` -DENGINE_ENABLE_VULKAN=OFF ` diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 5c4320dac..28ba25f3c 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -34,18 +34,21 @@ jobs: run: | .\scripts\build_windows.ps1 ` -Preset windows-cpu-release ` - -Target audiocpp_cli + -Target audiocpp_cli ` + -Version ci - name: Build audiocpp_server shell: pwsh run: | .\scripts\build_windows.ps1 ` -Preset windows-cpu-release ` - -Target audiocpp_server + -Target audiocpp_server ` + -Version ci - name: Build audiocpp_gguf shell: pwsh run: | .\scripts\build_windows.ps1 ` -Preset windows-cpu-release ` - -Target audiocpp_gguf + -Target audiocpp_gguf ` + -Version ci diff --git a/CMakeLists.txt b/CMakeLists.txt index 01199cade..c40d50de8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,6 +2,8 @@ cmake_minimum_required(VERSION 3.20) project(AudioCpp LANGUAGES C CXX) +set(AUDIOCPP_VERSION "dev" CACHE STRING "audio.cpp version string") + set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS OFF) @@ -110,12 +112,50 @@ option(ENGINE_ENABLE_CPU_ALL_VARIANTS OFF) option(ENGINE_BUILD_EXAMPLES "Build example binaries" OFF) option(ENGINE_BUILD_TESTS "Build framework unit tests" OFF) +option(ENGINE_BUILD_EXTENDED_TESTS "Build extended non-model tests and probes" OFF) option(ENGINE_BUILD_MODEL_TESTS "Build model-specific tests and probes" OFF) option(ENGINE_BUILD_WARMBENCH "Build warmbench helper binaries" OFF) set(AUDIOCPP_GGML_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/ggml" CACHE PATH "ggml source tree to build with AudioCPP") +set(AUDIOCPP_GIT_SHA "unknown") +set(AUDIOCPP_GIT_DATE "unknown") +find_package(Git QUIET) +if (GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --short HEAD + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE AUDIOCPP_GIT_SHA + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) + execute_process( + COMMAND ${GIT_EXECUTABLE} show -s --format=%cs HEAD + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + OUTPUT_VARIABLE AUDIOCPP_GIT_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET) +endif() + +set(AUDIOCPP_BUILD_BACKENDS "cpu") +if (ENGINE_ENABLE_CUDA) + string(APPEND AUDIOCPP_BUILD_BACKENDS ",cuda") +endif() +if (ENGINE_ENABLE_HIP) + string(APPEND AUDIOCPP_BUILD_BACKENDS ",hip") +endif() +if (ENGINE_ENABLE_VULKAN) + string(APPEND AUDIOCPP_BUILD_BACKENDS ",vulkan") +endif() +if (ENGINE_ENABLE_METAL) + string(APPEND AUDIOCPP_BUILD_BACKENDS ",metal") +endif() +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/app/common/build_info_config.h.in" + "${CMAKE_CURRENT_BINARY_DIR}/generated/build_info_config.h" + @ONLY +) + # HIP compiles ggml's CUDA backend sources as HIP code, so the two options drive the # same ggml backend and cannot coexist: enabling both starts CUDA backend setup and # HIP backend setup in the same configure, which fails in confusing ways downstream. @@ -353,6 +393,7 @@ add_library(engine_core OBJECT src/framework/assets/torch_bin.cpp src/framework/core/module.cpp src/framework/core/backend.cpp + src/framework/core/attention_fallback.cpp src/framework/core/deferred_tensor_writer.cpp src/framework/core/execution_context.cpp src/framework/core/host_memory.cpp @@ -630,6 +671,19 @@ audiocpp_add_model(personaplex engine::models::personaplex::make_personaplex_loader ) +audiocpp_add_model(sanotts + SOURCES + src/community_models/sanotts/assets.cpp + src/community_models/sanotts/frontend.cpp + src/community_models/sanotts/piper_runtime.cpp + src/community_models/sanotts/runtime.cpp + src/community_models/sanotts/session.cpp + INCLUDES + engine/community_models/sanotts/session.h + LOADERS + engine::models::sanotts::make_sanotts_loader +) + audiocpp_add_model(inflect_v2 SOURCES src/community_models/inflect_v2/assets.cpp @@ -1512,6 +1566,23 @@ audiocpp_add_model(ace_step LOADERS engine::models::ace_step::make_ace_step_loader ) +audiocpp_add_model(sopro_tts + SOURCES + src/community_models/sopro_tts/acoustic.cpp + src/community_models/sopro_tts/assets.cpp + src/community_models/sopro_tts/reference.cpp + src/community_models/sopro_tts/semantic_encoder.cpp + src/community_models/sopro_tts/semantic_lm.cpp + src/community_models/sopro_tts/session.cpp + src/community_models/sopro_tts/speaker_encoder.cpp + src/community_models/sopro_tts/text_tokenizer.cpp + src/community_models/sopro_tts/vocoder.cpp + INCLUDES + engine/community_models/sopro_tts/session.h + LOADERS + engine::community_models::sopro_tts::make_sopro_tts_loader +) + audiocpp_add_model(soprano_tts SOURCES src/community_models/soprano_tts/assets.cpp @@ -1526,6 +1597,21 @@ audiocpp_add_model(soprano_tts engine::community_models::soprano_tts::make_soprano_tts_loader ) +audiocpp_add_model(mira_tts + SOURCES + src/community_models/mira_tts/assets.cpp + src/community_models/mira_tts/decoder.cpp + src/community_models/mira_tts/generator.cpp + src/community_models/mira_tts/processor.cpp + src/community_models/mira_tts/prompt.cpp + src/community_models/mira_tts/session.cpp + src/community_models/mira_tts/speaker_encoder.cpp + INCLUDES + engine/community_models/mira_tts/session.h + LOADERS + engine::community_models::mira_tts::make_mira_tts_loader +) + audiocpp_add_model(midashenglm_gen SOURCES @@ -1636,6 +1722,60 @@ audiocpp_add_model(firered_audio engine::models::firered_audio::make_firered_audio_loader ) +audiocpp_add_model(cosyvoice3 + SOURCES + src/models/cosyvoice3/ar.cpp + src/models/cosyvoice3/assets.cpp + src/models/cosyvoice3/flow.cpp + src/models/cosyvoice3/frontend.cpp + src/models/cosyvoice3/hift.cpp + src/models/cosyvoice3/session.cpp + src/models/cosyvoice3/tokenizer_text.cpp + INCLUDES + engine/models/cosyvoice3/ar.h + engine/models/cosyvoice3/assets.h + engine/models/cosyvoice3/flow.h + engine/models/cosyvoice3/frontend.h + engine/models/cosyvoice3/hift.h + engine/models/cosyvoice3/session.h + engine/models/cosyvoice3/tokenizer_text.h + LOADERS + engine::models::cosyvoice3::make_cosyvoice3_loader +) + +audiocpp_add_model(breeze_tts + SOURCES + src/models/breeze_tts/assets.cpp + src/models/breeze_tts/generator.cpp + src/models/breeze_tts/session.cpp + src/models/breeze_tts/speech_decoder.cpp + src/models/breeze_tts/speech_encoder.cpp + src/models/breeze_tts/text_encoder.cpp + src/models/breeze_tts/tokenizer_text.cpp + INCLUDES + engine/models/breeze_tts/assets.h + engine/models/breeze_tts/generator.h + engine/models/breeze_tts/session.h + engine/models/breeze_tts/speech_decoder.h + engine/models/breeze_tts/speech_encoder.h + engine/models/breeze_tts/text_encoder.h + engine/models/breeze_tts/tokenizer_text.h + LOADERS + engine::models::breeze_tts::make_breeze_tts_loader +) + +audiocpp_add_model(vibeasr + SOURCES + src/community_models/vibeasr/assets.cpp + src/community_models/vibeasr/vae_encoder.cpp + src/community_models/vibeasr/lm_decoder.cpp + src/community_models/vibeasr/session.cpp + INCLUDES + engine/community_models/vibeasr/session.h + LOADERS + engine::community_models::vibeasr::make_vibeasr_loader +) + set(AUDIOCPP_ENABLED_MODELS "") if (AUDIOCPP_MODEL_SET STREQUAL "full") set(AUDIOCPP_ENABLED_MODELS ${AUDIOCPP_MODEL_TARGETS}) @@ -1771,6 +1911,11 @@ target_link_libraries(engine_runtime PRIVATE sentencepiece cjson_vendor yaml_ven if (AUDIOCPP_HIP_STRIX_HALO_OPTIMIZATIONS_ACTIVE) target_compile_definitions(engine_runtime PRIVATE ENGINE_HIP_STRIX_HALO_OPTIMIZATIONS=1) endif() +if (ENGINE_ENABLE_HIP) + # ggml-hip publicly defines GGML_USE_CUDA for consumers, so engine code + # needs this marker to tell a real CUDA build apart from a HIP build. + target_compile_definitions(engine_core PRIVATE ENGINE_GGML_HIP_BACKEND=1) +endif() if (ENGINE_ENABLE_OPENMP) target_link_libraries(engine_runtime PRIVATE OpenMP::OpenMP_CXX) if (MSVC) @@ -1806,6 +1951,7 @@ if (ENGINE_ENABLE_CUDA AND NOT ENGINE_ENABLE_HIP) endif() add_executable(audiocpp_cli + app/common/build_info.cpp app/cli/main.cpp app/cli/args.cpp app/cli/batch.cpp @@ -1819,6 +1965,7 @@ add_executable(audiocpp_cli ) target_link_libraries(audiocpp_cli PRIVATE engine_runtime ggml) +target_include_directories(audiocpp_cli PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/generated") # MinGW selects the wmain() entry point only when linked with -municode; # without it the CLI fails to link (undefined reference / ld error 5). # MSVC detects wmain natively, so this is MinGW-only. Link option only: @@ -1875,6 +2022,7 @@ configure_file( ) add_executable(audiocpp_server + app/common/build_info.cpp app/server/main.cpp app/server/base64.cpp app/server/config.cpp @@ -1958,6 +2106,7 @@ if (ENGINE_BUILD_WARMBENCH) add_engine_warmbench(irodori_tts_warm_bench tests/irodori_tts/irodori_tts_warm_bench.cpp) add_engine_warmbench(marblenet_vad_warm_bench tests/marblenet_vad/marblenet_vad_warm_bench.cpp) add_engine_warmbench(miocodec_warm_bench tests/miocodec/miocodec_warm_bench.cpp) + add_engine_warmbench(mira_tts_warm_bench tests/mira_tts/mira_tts_warm_bench.cpp) add_engine_warmbench(muscriptor_warm_bench tests/muscriptor/muscriptor_warm_bench.cpp) add_engine_warmbench(moss_tts_nano_warm_bench tests/moss_tts_nano/moss_tts_nano_warm_bench.cpp) add_engine_warmbench(moss_tts_local_warm_bench tests/moss_tts_local/moss_tts_local_warm_bench.cpp) @@ -1991,6 +2140,7 @@ if (ENGINE_BUILD_WARMBENCH) target_compile_definitions(soprano_warm_bench PRIVATE ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" ) + add_engine_warmbench(sopro_probe tests/sopro_tts/sopro_probe.cpp) add_engine_warmbench(sortformer_diar_warm_bench tests/sortformer_diar/sortformer_diar_warm_bench.cpp) add_engine_warmbench(supertonic_warm_bench tests/supertonic/supertonic_warm_bench.cpp) add_engine_warmbench(vevo2_warm_bench tests/vevo2/vevo2_warm_bench.cpp) @@ -1999,23 +2149,9 @@ if (ENGINE_BUILD_WARMBENCH) add_engine_warmbench(voxcpm2_warm_bench tests/voxcpm2/voxcpm2_warm_bench.cpp) endif() -if (ENGINE_BUILD_TESTS) +if (ENGINE_BUILD_TESTS OR ENGINE_BUILD_EXTENDED_TESTS OR ENGINE_BUILD_MODEL_TESTS) enable_testing() - add_test( - NAME audiocpp_cli_list_devices - COMMAND audiocpp_cli --list-devices - ) - add_test( - NAME audiocpp_server_list_devices - COMMAND audiocpp_server --list-devices - ) - set_tests_properties( - audiocpp_cli_list_devices - audiocpp_server_list_devices - PROPERTIES PASS_REGULAR_EXPRESSION "available_devices=[0-9]+" - ) - set(ENGINE_UNITTEST_ASSET_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests/assets") function(add_engine_unittest target source) @@ -2023,372 +2159,256 @@ if (ENGINE_BUILD_TESTS) target_link_libraries(${target} PRIVATE engine_runtime ggml) endfunction() - add_executable(sentencepiece_tokenizer1_test - tests/unittests/test_sentencepiece_tokenizer1.cpp - ) - - target_link_libraries(sentencepiece_tokenizer1_test PRIVATE engine_runtime ggml) - target_compile_definitions(sentencepiece_tokenizer1_test PRIVATE - ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" - ) - - add_test( - NAME sentencepiece_tokenizer1_test - COMMAND sentencepiece_tokenizer1_test - ) - - add_engine_unittest(citrinet_tokenizer_decode_test tests/unittests/test_citrinet_tokenizer_decode.cpp) - target_compile_definitions(citrinet_tokenizer_decode_test PRIVATE - ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" - ) - - add_test( - NAME citrinet_tokenizer_decode_test - COMMAND citrinet_tokenizer_decode_test - ) - - add_engine_unittest(audio_dsp_test tests/unittests/test_audio_dsp.cpp) - - add_test( - NAME audio_dsp_test - COMMAND audio_dsp_test - ) - - add_engine_unittest(midi_file_test tests/unittests/test_midi_file.cpp) - - add_test( - NAME midi_file_test - COMMAND midi_file_test - ) - - add_engine_unittest(wav_reader_test tests/unittests/test_wav_reader.cpp) - - add_test( - NAME wav_reader_test - COMMAND wav_reader_test - ) - - add_engine_unittest(wav_reader_chunk_bounds_test tests/unittests/test_wav_reader_chunk_bounds.cpp) - - add_test( - NAME wav_reader_chunk_bounds_test - COMMAND wav_reader_chunk_bounds_test - ) - - add_engine_unittest(hf_tokenizer_vocab_bounds_test tests/unittests/test_hf_tokenizer_vocab_bounds.cpp) - - add_test( - NAME hf_tokenizer_vocab_bounds_test - COMMAND hf_tokenizer_vocab_bounds_test - ) - - add_engine_unittest(safetensors_offsets_test tests/unittests/test_safetensors_offsets.cpp) - - add_test( - NAME safetensors_offsets_test - COMMAND safetensors_offsets_test - ) - - add_engine_unittest(audio_chunking_test tests/unittests/test_audio_chunking.cpp) - - add_test( - NAME audio_chunking_test - COMMAND audio_chunking_test - ) - - add_engine_unittest(wav_writer_formats_test tests/unittests/test_wav_writer_formats.cpp) - - add_test( - NAME wav_writer_formats_test - COMMAND wav_writer_formats_test - ) - - add_engine_unittest(chinese_normalization_test tests/unittests/test_chinese_normalization.cpp) - - add_test( - NAME chinese_normalization_test - COMMAND chinese_normalization_test - ) - - add_engine_unittest(text_chunking_test tests/unittests/test_text_chunking.cpp) - - add_test( - NAME text_chunking_test - COMMAND text_chunking_test - ) - - add_engine_unittest(unicode_normalization_test tests/unittests/test_unicode_normalization.cpp) - - add_test( - NAME unicode_normalization_test - COMMAND unicode_normalization_test - ) - - add_engine_unittest(text_normalization_test tests/unittests/test_text_normalization.cpp) - - add_test( - NAME text_normalization_test - COMMAND text_normalization_test - ) - - add_engine_unittest(subtitle_formatter_test tests/unittests/test_subtitle_formatter.cpp) - - add_test( - NAME subtitle_formatter_test - COMMAND subtitle_formatter_test - ) - - add_engine_unittest(rnnoise_utility_test tests/unittests/test_rnnoise_utility.cpp) - target_compile_definitions(rnnoise_utility_test PRIVATE - ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" - ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" - ) - - add_test( - NAME rnnoise_utility_test - COMMAND rnnoise_utility_test - ) - - add_engine_unittest(flashsr_utility_test tests/unittests/test_flashsr_utility.cpp) - target_compile_definitions(flashsr_utility_test PRIVATE - ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" - ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" - ) + if (ENGINE_BUILD_TESTS) + add_executable(sentencepiece_tokenizer1_test + tests/unittests/test_sentencepiece_tokenizer1.cpp + ) - add_test( - NAME flashsr_utility_test - COMMAND flashsr_utility_test - ) + target_link_libraries(sentencepiece_tokenizer1_test PRIVATE engine_runtime ggml) + target_compile_definitions(sentencepiece_tokenizer1_test PRIVATE + ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" + ) - add_engine_unittest(zipenhancer_utility_test tests/unittests/test_zipenhancer_utility.cpp) - target_compile_definitions(zipenhancer_utility_test PRIVATE - ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" - ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" - ) + add_test( + NAME sentencepiece_tokenizer1_test + COMMAND sentencepiece_tokenizer1_test + ) - add_test( - NAME zipenhancer_utility_test - COMMAND zipenhancer_utility_test - ) + add_engine_unittest(citrinet_tokenizer_decode_test tests/unittests/test_citrinet_tokenizer_decode.cpp) + target_compile_definitions(citrinet_tokenizer_decode_test PRIVATE + ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" + ) - add_engine_unittest(audio_utility_api_test tests/unittests/test_audio_utility_api.cpp) - target_compile_definitions(audio_utility_api_test PRIVATE - ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" - ) + add_test( + NAME citrinet_tokenizer_decode_test + COMMAND citrinet_tokenizer_decode_test + ) - add_test( - NAME audio_utility_api_test - COMMAND audio_utility_api_test - ) + add_engine_unittest(audio_dsp_test tests/unittests/test_audio_dsp.cpp) + add_test(NAME audio_dsp_test COMMAND audio_dsp_test) - add_engine_unittest(torch_random_test tests/unittests/test_torch_random.cpp) + add_engine_unittest(midi_file_test tests/unittests/test_midi_file.cpp) + add_test(NAME midi_file_test COMMAND midi_file_test) - add_test( - NAME torch_random_test - COMMAND torch_random_test - ) + add_engine_unittest(wav_reader_test tests/unittests/test_wav_reader.cpp) + add_test(NAME wav_reader_test COMMAND wav_reader_test) - add_engine_unittest(backend_device_resolution_test tests/unittests/test_backend_device_resolution.cpp) - target_include_directories(backend_device_resolution_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_engine_unittest(wav_reader_chunk_bounds_test tests/unittests/test_wav_reader_chunk_bounds.cpp) + add_test(NAME wav_reader_chunk_bounds_test COMMAND wav_reader_chunk_bounds_test) - add_test( - NAME backend_device_resolution_test - COMMAND backend_device_resolution_test - ) + add_engine_unittest(hf_tokenizer_vocab_bounds_test tests/unittests/test_hf_tokenizer_vocab_bounds.cpp) + add_test(NAME hf_tokenizer_vocab_bounds_test COMMAND hf_tokenizer_vocab_bounds_test) - add_executable(streaming_audio_input_test - tests/unittests/test_streaming_audio_input.cpp - app/streaming/pcm_source.cpp - app/streaming/streaming.cpp - ) - target_link_libraries(streaming_audio_input_test PRIVATE engine_runtime ggml) - target_include_directories(streaming_audio_input_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_engine_unittest(safetensors_offsets_test tests/unittests/test_safetensors_offsets.cpp) + add_test(NAME safetensors_offsets_test COMMAND safetensors_offsets_test) - add_test( - NAME streaming_audio_input_test - COMMAND streaming_audio_input_test - ) + add_engine_unittest(audio_chunking_test tests/unittests/test_audio_chunking.cpp) + add_test(NAME audio_chunking_test COMMAND audio_chunking_test) - add_executable(http_live_body_test - tests/unittests/test_http_live_body.cpp - app/server/http.cpp - ) - target_link_libraries(http_live_body_test PRIVATE engine_runtime ggml) - if(WIN32) - target_link_libraries(http_live_body_test PRIVATE ws2_32) - endif() - target_include_directories(http_live_body_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_engine_unittest(wav_writer_formats_test tests/unittests/test_wav_writer_formats.cpp) + add_test(NAME wav_writer_formats_test COMMAND wav_writer_formats_test) - add_test( - NAME http_live_body_test - COMMAND http_live_body_test - ) - # This test talks to a real loopback socket, so a framing regression could - # block forever rather than fail. Bound it so CI reports a failure instead. - set_tests_properties(http_live_body_test PROPERTIES TIMEOUT 120) + add_engine_unittest(chinese_normalization_test tests/unittests/test_chinese_normalization.cpp) + add_test(NAME chinese_normalization_test COMMAND chinese_normalization_test) - add_engine_unittest(partial_text_render_test tests/unittests/test_partial_text_render.cpp) + add_engine_unittest(text_chunking_test tests/unittests/test_text_chunking.cpp) + add_test(NAME text_chunking_test COMMAND text_chunking_test) - add_test( - NAME partial_text_render_test - COMMAND partial_text_render_test - ) + add_engine_unittest(unicode_normalization_test tests/unittests/test_unicode_normalization.cpp) + add_test(NAME unicode_normalization_test COMMAND unicode_normalization_test) - add_engine_unittest(hf_sampler_test tests/unittests/test_hf_sampler.cpp) + add_engine_unittest(text_normalization_test tests/unittests/test_text_normalization.cpp) + add_test(NAME text_normalization_test COMMAND text_normalization_test) - add_test( - NAME hf_sampler_test - COMMAND hf_sampler_test - ) + add_engine_unittest(subtitle_formatter_test tests/unittests/test_subtitle_formatter.cpp) + add_test(NAME subtitle_formatter_test COMMAND subtitle_formatter_test) - add_engine_unittest(diffusion_math_test tests/unittests/test_diffusion_math.cpp) + add_engine_unittest(torch_random_test tests/unittests/test_torch_random.cpp) + add_test(NAME torch_random_test COMMAND torch_random_test) - add_test( - NAME diffusion_math_test - COMMAND diffusion_math_test - ) + add_engine_unittest(backend_device_resolution_test tests/unittests/test_backend_device_resolution.cpp) + target_include_directories(backend_device_resolution_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_test(NAME backend_device_resolution_test COMMAND backend_device_resolution_test) - add_engine_unittest(encoder_module_test tests/unittests/test_encoder_modules.cpp) + add_engine_unittest(attention_fallback_test tests/unittests/test_attention_fallback.cpp) + target_include_directories(attention_fallback_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_test(NAME attention_fallback_test COMMAND attention_fallback_test) - add_test( - NAME encoder_module_test - COMMAND encoder_module_test - ) + add_engine_unittest(partial_text_render_test tests/unittests/test_partial_text_render.cpp) + add_test(NAME partial_text_render_test COMMAND partial_text_render_test) - add_engine_unittest(conv_transpose_fast_path_test tests/unittests/test_conv_transpose_fast_path.cpp) + add_engine_unittest(hf_sampler_test tests/unittests/test_hf_sampler.cpp) + add_test(NAME hf_sampler_test COMMAND hf_sampler_test) - add_test( - NAME conv_transpose_fast_path_test - COMMAND conv_transpose_fast_path_test - ) + add_engine_unittest(diffusion_math_test tests/unittests/test_diffusion_math.cpp) + add_test(NAME diffusion_math_test COMMAND diffusion_math_test) - add_engine_unittest(depthwise_conv1d_lowering_test tests/unittests/test_depthwise_conv1d_lowering.cpp) + add_engine_unittest(encoder_module_test tests/unittests/test_encoder_modules.cpp) + add_test(NAME encoder_module_test COMMAND encoder_module_test) - add_test( - NAME depthwise_conv1d_lowering_test - COMMAND depthwise_conv1d_lowering_test - ) + add_engine_unittest(gguf_tensor_source_test tests/unittests/test_gguf_tensor_source.cpp) + target_include_directories(gguf_tensor_source_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_test(NAME gguf_tensor_source_test COMMAND gguf_tensor_source_test) - add_engine_unittest(conv_lowering_matrix_test tests/unittests/test_conv_lowering_matrix.cpp) - if (ENGINE_ENABLE_HIP) - target_compile_definitions(conv_lowering_matrix_test PRIVATE ENGINE_TEST_ENABLE_HIP=1) + add_engine_unittest(model_spec_system_test tests/unittests/test_model_spec_system.cpp) + target_include_directories(model_spec_system_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_test(NAME model_spec_system_test COMMAND model_spec_system_test) endif() - add_test( - NAME conv_lowering_matrix_test - COMMAND conv_lowering_matrix_test - ) + if (ENGINE_BUILD_EXTENDED_TESTS) + find_package(Threads REQUIRED) - add_engine_unittest(gguf_tensor_source_test tests/unittests/test_gguf_tensor_source.cpp) - target_include_directories(gguf_tensor_source_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_test(NAME audiocpp_cli_list_devices COMMAND audiocpp_cli --list-devices) + add_test(NAME audiocpp_server_list_devices COMMAND audiocpp_server --list-devices) + set_tests_properties( + audiocpp_cli_list_devices + audiocpp_server_list_devices + PROPERTIES PASS_REGULAR_EXPRESSION "available_devices=[0-9]+" + ) - add_test( - NAME gguf_tensor_source_test - COMMAND gguf_tensor_source_test - ) + add_engine_unittest(rnnoise_utility_test tests/unittests/test_rnnoise_utility.cpp) + target_compile_definitions(rnnoise_utility_test PRIVATE + ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" + ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" + ) + add_test(NAME rnnoise_utility_test COMMAND rnnoise_utility_test) - add_engine_unittest(model_spec_system_test tests/unittests/test_model_spec_system.cpp) - target_include_directories(model_spec_system_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_engine_unittest(flashsr_utility_test tests/unittests/test_flashsr_utility.cpp) + target_compile_definitions(flashsr_utility_test PRIVATE + ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" + ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" + ) + add_test(NAME flashsr_utility_test COMMAND flashsr_utility_test) - add_test( - NAME model_spec_system_test - COMMAND model_spec_system_test - ) + add_engine_unittest(zipenhancer_utility_test tests/unittests/test_zipenhancer_utility.cpp) + target_compile_definitions(zipenhancer_utility_test PRIVATE + ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" + ENGINE_TEST_ASSET_ROOT="${ENGINE_UNITTEST_ASSET_ROOT}" + ) + add_test(NAME zipenhancer_utility_test COMMAND zipenhancer_utility_test) - add_engine_unittest(scaled_dot_product_attention_test tests/unittests/test_scaled_dot_product_attention.cpp) + add_engine_unittest(audio_utility_api_test tests/unittests/test_audio_utility_api.cpp) + target_compile_definitions(audio_utility_api_test PRIVATE + ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" + ) + add_test(NAME audio_utility_api_test COMMAND audio_utility_api_test) - add_test( - NAME scaled_dot_product_attention_test - COMMAND scaled_dot_product_attention_test - ) + add_executable(streaming_audio_input_test + tests/unittests/test_streaming_audio_input.cpp + app/streaming/pcm_source.cpp + app/streaming/streaming.cpp + ) + target_link_libraries(streaming_audio_input_test PRIVATE engine_runtime ggml) + target_include_directories(streaming_audio_input_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_test(NAME streaming_audio_input_test COMMAND streaming_audio_input_test) - add_executable(cli_request_options_test - tests/unittests/test_cli_request_options.cpp - app/cli/args.cpp - app/cli/request.cpp - ) - target_link_libraries(cli_request_options_test PRIVATE engine_runtime ggml) - if (ENGINE_ENABLE_OPENMP) - target_link_libraries(cli_request_options_test PRIVATE OpenMP::OpenMP_CXX) - endif() + add_executable(http_live_body_test + tests/unittests/test_http_live_body.cpp + app/server/http.cpp + ) + target_link_libraries(http_live_body_test PRIVATE engine_runtime ggml) + if(WIN32) + target_link_libraries(http_live_body_test PRIVATE ws2_32) + endif() + target_include_directories(http_live_body_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_test(NAME http_live_body_test COMMAND http_live_body_test) + # This test talks to a real loopback socket, so a framing regression could + # block forever rather than fail. Bound it so CI reports a failure instead. + set_tests_properties(http_live_body_test PROPERTIES TIMEOUT 120) - add_test( - NAME cli_request_options_test - COMMAND cli_request_options_test - ) + add_engine_unittest(conv_transpose_fast_path_test tests/unittests/test_conv_transpose_fast_path.cpp) + add_test(NAME conv_transpose_fast_path_test COMMAND conv_transpose_fast_path_test) - add_executable(server_multipart_test - tests/unittests/test_server_multipart.cpp - app/server/multipart.cpp - ) - target_include_directories(server_multipart_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app/server) + add_engine_unittest(depthwise_conv1d_lowering_test tests/unittests/test_depthwise_conv1d_lowering.cpp) + add_test(NAME depthwise_conv1d_lowering_test COMMAND depthwise_conv1d_lowering_test) - add_test( - NAME server_multipart_test - COMMAND server_multipart_test - ) + add_engine_unittest(conv_lowering_matrix_test tests/unittests/test_conv_lowering_matrix.cpp) + if (ENGINE_ENABLE_HIP) + target_compile_definitions(conv_lowering_matrix_test PRIVATE ENGINE_TEST_ENABLE_HIP=1) + endif() + add_test(NAME conv_lowering_matrix_test COMMAND conv_lowering_matrix_test) - add_executable(server_base64_test - tests/unittests/test_server_base64.cpp - app/server/base64.cpp - ) - target_include_directories(server_base64_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app/server) + add_engine_unittest(i8_s_fused_ops_test tests/unittests/test_i8_s_fused_ops.cpp) + add_test(NAME i8_s_fused_ops_test COMMAND i8_s_fused_ops_test) - add_test( - NAME server_base64_test - COMMAND server_base64_test - ) + add_engine_unittest(i2_s_mul_mat_test tests/unittests/test_i2_s_mul_mat.cpp) + add_test(NAME i2_s_mul_mat_test COMMAND i2_s_mul_mat_test) - add_executable(server_busy_guard_test - tests/unittests/test_server_busy_guard.cpp - ) - target_include_directories(server_busy_guard_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app/server) - find_package(Threads REQUIRED) - target_link_libraries(server_busy_guard_test PRIVATE Threads::Threads) + add_engine_unittest(scaled_dot_product_attention_test tests/unittests/test_scaled_dot_product_attention.cpp) + add_test(NAME scaled_dot_product_attention_test COMMAND scaled_dot_product_attention_test) - add_test( - NAME server_busy_guard_test - COMMAND server_busy_guard_test - ) - # The suite deliberately drives threads against a held lock; bound it so a - # regression that reintroduces unbounded queuing fails instead of hanging CI. - set_tests_properties(server_busy_guard_test PROPERTIES TIMEOUT 60) - - add_executable(server_config_test - tests/unittests/test_server_config.cpp - app/server/config.cpp - app/server/model_memory.cpp - app/cli/args.cpp - app/cli/request.cpp - ) - target_include_directories(server_config_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app/server) - target_link_libraries(server_config_test PRIVATE engine_runtime ggml) - if (ENGINE_ENABLE_OPENMP) - target_link_libraries(server_config_test PRIVATE OpenMP::OpenMP_CXX) - endif() + add_executable(cli_request_options_test + tests/unittests/test_cli_request_options.cpp + app/cli/args.cpp + app/cli/request.cpp + ) + target_link_libraries(cli_request_options_test PRIVATE engine_runtime ggml) + if (ENGINE_ENABLE_OPENMP) + target_link_libraries(cli_request_options_test PRIVATE OpenMP::OpenMP_CXX) + endif() + add_test(NAME cli_request_options_test COMMAND cli_request_options_test) - add_test( - NAME server_config_test - COMMAND server_config_test - ) + add_executable(server_multipart_test + tests/unittests/test_server_multipart.cpp + app/server/multipart.cpp + ) + target_include_directories(server_multipart_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app/server) + add_test(NAME server_multipart_test COMMAND server_multipart_test) - if (AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER) - add_executable(server_model_installer_test - tests/unittests/test_server_model_installer.cpp - app/server/model_installer.cpp + add_executable(server_base64_test + tests/unittests/test_server_base64.cpp + app/server/base64.cpp ) - target_include_directories(server_model_installer_test PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/app/server - ${CMAKE_CURRENT_BINARY_DIR}/generated) - target_link_libraries(server_model_installer_test PRIVATE - Threads::Threads engine_runtime audiocpp_package_manager) - target_compile_definitions(server_model_installer_test PRIVATE - AUDIOCPP_NATIVE_MANAGER_FIXTURE="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/native_model_manager_server.py") + target_include_directories(server_base64_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app/server) + add_test(NAME server_base64_test COMMAND server_base64_test) - add_test( - NAME server_model_installer_test - COMMAND server_model_installer_test + add_executable(server_busy_guard_test + tests/unittests/test_server_busy_guard.cpp + ) + target_include_directories(server_busy_guard_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app/server) + target_link_libraries(server_busy_guard_test PRIVATE Threads::Threads) + add_test(NAME server_busy_guard_test COMMAND server_busy_guard_test) + # The suite deliberately drives threads against a held lock; bound it so a + # regression that reintroduces unbounded queuing fails instead of hanging CI. + set_tests_properties(server_busy_guard_test PROPERTIES TIMEOUT 60) + + add_executable(server_config_test + tests/unittests/test_server_config.cpp + app/server/config.cpp + app/server/model_memory.cpp + app/cli/args.cpp + app/cli/request.cpp ) + target_include_directories(server_config_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/app/server) + target_link_libraries(server_config_test PRIVATE engine_runtime ggml) + if (ENGINE_ENABLE_OPENMP) + target_link_libraries(server_config_test PRIVATE OpenMP::OpenMP_CXX) + endif() + add_test(NAME server_config_test COMMAND server_config_test) + + if (AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER) + add_executable(server_model_installer_test + tests/unittests/test_server_model_installer.cpp + app/server/model_installer.cpp + ) + target_include_directories(server_model_installer_test PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/app/server + ${CMAKE_CURRENT_BINARY_DIR}/generated) + target_link_libraries(server_model_installer_test PRIVATE + Threads::Threads engine_runtime audiocpp_package_manager) + target_compile_definitions(server_model_installer_test PRIVATE + AUDIOCPP_NATIVE_MANAGER_FIXTURE="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/native_model_manager_server.py") + add_test(NAME server_model_installer_test COMMAND server_model_installer_test) + + add_executable(package_manager_modelscope_test + tests/unittests/test_package_manager_modelscope.cpp + ) + target_link_libraries(package_manager_modelscope_test PRIVATE + Threads::Threads audiocpp_package_manager) + target_compile_definitions(package_manager_modelscope_test PRIVATE + AUDIOCPP_NATIVE_MANAGER_FIXTURE="${CMAKE_CURRENT_SOURCE_DIR}/tests/fixtures/native_model_manager_server.py") + add_test(NAME package_manager_modelscope_test COMMAND package_manager_modelscope_test) + endif() endif() if (ENGINE_BUILD_MODEL_TESTS) @@ -2418,6 +2438,72 @@ if (ENGINE_BUILD_TESTS) target_link_libraries(test_granite5asr_golden_transcription PRIVATE OpenMP::OpenMP_CXX) endif() + if (vibeasr IN_LIST AUDIOCPP_LINKED_MODELS) + add_executable(test_vibeasr_vae_encoder + tests/vibeasr/test_vibeasr_vae_encoder.cpp + ) + target_compile_definitions(test_vibeasr_vae_encoder PRIVATE + ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" + ) + target_link_libraries(test_vibeasr_vae_encoder PRIVATE engine_runtime ggml) + target_include_directories(test_vibeasr_vae_encoder PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + if (ENGINE_ENABLE_OPENMP) + target_link_libraries(test_vibeasr_vae_encoder PRIVATE OpenMP::OpenMP_CXX) + endif() + add_test( + NAME test_vibeasr_vae_encoder + COMMAND test_vibeasr_vae_encoder + --model ${CMAKE_CURRENT_SOURCE_DIR}/models/vibeasr/vibeasr-vae-encoder-i8_s.gguf + --audio ${CMAKE_CURRENT_SOURCE_DIR}/assets/asr_validation/librispeech/librispeech_test_clean_6930-75918-0000.wav + ) + # Needs the converted 703 MB encoder package, which a normal checkout + # does not have; the probe exits 125 (skip) instead of failing. Pass + # --reference-acoustic / --reference-semantic by hand to also check + # parity against a VibeASR.cpp dump. + set_tests_properties(test_vibeasr_vae_encoder PROPERTIES + SKIP_RETURN_CODE 125 + TIMEOUT 300 + ) + + add_executable(test_vibeasr_asr + tests/vibeasr/test_vibeasr_asr.cpp + ) + target_compile_definitions(test_vibeasr_asr PRIVATE + ENGINE_REPO_ROOT="${CMAKE_CURRENT_SOURCE_DIR}" + ) + target_link_libraries(test_vibeasr_asr PRIVATE engine_runtime ggml) + target_include_directories(test_vibeasr_asr PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + if (ENGINE_ENABLE_OPENMP) + target_link_libraries(test_vibeasr_asr PRIVATE OpenMP::OpenMP_CXX) + endif() + add_test( + NAME test_vibeasr_asr + COMMAND test_vibeasr_asr --threads 8 + ) + # Same story as the encoder probe, plus the 993 MB decoder: exits 125 + # (skip) unless both converted GGUFs sit in models/vibeasr/. + set_tests_properties(test_vibeasr_asr PROPERTIES + SKIP_RETURN_CODE 125 + TIMEOUT 600 + ) + endif() + + if (sopro_tts IN_LIST AUDIOCPP_LINKED_MODELS) + add_executable(test_sopro_tts_audio_ops + tests/sopro_tts/test_sopro_tts_audio_ops.cpp + ) + target_link_libraries(test_sopro_tts_audio_ops PRIVATE engine_runtime ggml) + target_include_directories(test_sopro_tts_audio_ops PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + if (ENGINE_ENABLE_OPENMP) + target_link_libraries(test_sopro_tts_audio_ops PRIVATE OpenMP::OpenMP_CXX) + endif() + + add_test( + NAME test_sopro_tts_audio_ops + COMMAND test_sopro_tts_audio_ops + ) + endif() + if (audio8_asr IN_LIST AUDIOCPP_LINKED_MODELS) add_executable(test_audio8_asr_units tests/audio8_asr/test_audio8_asr_units.cpp @@ -2545,6 +2631,13 @@ if (ENGINE_BUILD_TESTS) COMMAND echo_tts_host_units ) + add_engine_unittest(sanotts_frontend_test tests/unittests/test_sanotts_frontend.cpp) + target_include_directories(sanotts_frontend_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) + add_test( + NAME sanotts_frontend_test + COMMAND sanotts_frontend_test + ) + add_engine_unittest(inflect_v2_frontend_test tests/unittests/test_inflect_v2_frontend.cpp) target_include_directories(inflect_v2_frontend_test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/tests/unittests) add_test( diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9689abc70..b75869fad 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -93,7 +93,7 @@ audio.cpp is moving faster because people keep showing up with real fixes, caref - [@5uck1ess](https://github.com/5uck1ess) for improving Citrinet CTC decoding through the SentencePiece model, hardening PocketTTS FlowLM step allocation, and adding live PCM transcription ingest to the server in [#49](https://github.com/0xShug0/audio.cpp/pull/49), [#59](https://github.com/0xShug0/audio.cpp/pull/59), and [#144](https://github.com/0xShug0/audio.cpp/pull/144). - [@dkruyt](https://github.com/dkruyt) for the first multipart transcription upload support in [#25](https://github.com/0xShug0/audio.cpp/pull/25). - [@CaptainArni](https://github.com/CaptainArni) for fixing PocketTTS empty output when switching cached voices, keeping the Windows CUDA build path healthy, and adding ACE-Step 1.5 XL DiT variants in [#22](https://github.com/0xShug0/audio.cpp/pull/22), [#93](https://github.com/0xShug0/audio.cpp/pull/93), and [#235](https://github.com/0xShug0/audio.cpp/pull/235). -- [@IIIIIllllIIIIIlllll](https://github.com/IIIIIllllIIIIIlllll) for the experimental ROCm/HIP backend, Linux HIP build path, Windows HIP/ROCm distribution preparation, HIP build documentation, VibeVoice HIP enablement, backend device listing, Vulkan AMD integer-dot guard, IndexTTS-2.5 support, base64 voice references in server requests, IndexTTS2 speech-rate controls, and IndexTTS2 HIP/text-normalization fixes in [#48](https://github.com/0xShug0/audio.cpp/pull/48), [#148](https://github.com/0xShug0/audio.cpp/pull/148), [#153](https://github.com/0xShug0/audio.cpp/pull/153), [#159](https://github.com/0xShug0/audio.cpp/pull/159), [#164](https://github.com/0xShug0/audio.cpp/pull/164), [#168](https://github.com/0xShug0/audio.cpp/pull/168), [#171](https://github.com/0xShug0/audio.cpp/pull/171), [#193](https://github.com/0xShug0/audio.cpp/pull/193), [#210](https://github.com/0xShug0/audio.cpp/pull/210), [#226](https://github.com/0xShug0/audio.cpp/pull/226), [#239](https://github.com/0xShug0/audio.cpp/pull/239), [#247](https://github.com/0xShug0/audio.cpp/pull/247), [#259](https://github.com/0xShug0/audio.cpp/pull/259), [#299](https://github.com/0xShug0/audio.cpp/pull/299), and [#305](https://github.com/0xShug0/audio.cpp/pull/305). +- [@IIIIIllllIIIIIlllll](https://github.com/IIIIIllllIIIIIlllll) for the experimental ROCm/HIP backend, Linux HIP build path, Windows HIP/ROCm distribution preparation, HIP build documentation, VibeVoice HIP enablement, backend device listing, Vulkan AMD integer-dot guard, IndexTTS-2.5 support, base64 voice references in server requests, IndexTTS2 speech-rate controls, IndexTTS2 HIP/text-normalization fixes, and BreezeTTS 2 performance and VRAM improvements in [#48](https://github.com/0xShug0/audio.cpp/pull/48), [#148](https://github.com/0xShug0/audio.cpp/pull/148), [#153](https://github.com/0xShug0/audio.cpp/pull/153), [#159](https://github.com/0xShug0/audio.cpp/pull/159), [#164](https://github.com/0xShug0/audio.cpp/pull/164), [#168](https://github.com/0xShug0/audio.cpp/pull/168), [#171](https://github.com/0xShug0/audio.cpp/pull/171), [#193](https://github.com/0xShug0/audio.cpp/pull/193), [#210](https://github.com/0xShug0/audio.cpp/pull/210), [#226](https://github.com/0xShug0/audio.cpp/pull/226), [#239](https://github.com/0xShug0/audio.cpp/pull/239), [#247](https://github.com/0xShug0/audio.cpp/pull/247), [#259](https://github.com/0xShug0/audio.cpp/pull/259), [#299](https://github.com/0xShug0/audio.cpp/pull/299), [#305](https://github.com/0xShug0/audio.cpp/pull/305), [#393](https://github.com/0xShug0/audio.cpp/pull/393), and [#431](https://github.com/0xShug0/audio.cpp/pull/431). - [@francescobozzo](https://github.com/francescobozzo) for Nix ROCm/HIP backend support, selectable model targets, and Nix CI/package fixes in [#162](https://github.com/0xShug0/audio.cpp/pull/162), [#163](https://github.com/0xShug0/audio.cpp/pull/163), and [#172](https://github.com/0xShug0/audio.cpp/pull/172). - [@patrickvonplaten](https://github.com/patrickvonplaten) for Metal backend fixes, Voxtral Realtime streaming speedups, live audio streaming from stdin, and incremental transcript deltas in [#102](https://github.com/0xShug0/audio.cpp/pull/102), [#116](https://github.com/0xShug0/audio.cpp/pull/116), [#118](https://github.com/0xShug0/audio.cpp/pull/118), and [#127](https://github.com/0xShug0/audio.cpp/pull/127). - [@dleiferives](https://github.com/dleiferives) for Parakeet-TDT 0.6B v3 ASR support and follow-up standalone GGUF validation/docs in [#111](https://github.com/0xShug0/audio.cpp/pull/111) and [#139](https://github.com/0xShug0/audio.cpp/pull/139). @@ -102,8 +102,8 @@ audio.cpp is moving faster because people keep showing up with real fixes, caref - [@LauraGPT](https://github.com/LauraGPT) for adding Fun-ASR-Nano offline ASR, fixing the Linux build script executable bit, and bringing SenseVoice-Small offline/streaming ASR into the community model surface with Jason Chen and FunASR Ops in [#155](https://github.com/0xShug0/audio.cpp/pull/155), [#156](https://github.com/0xShug0/audio.cpp/pull/156), and [#219](https://github.com/0xShug0/audio.cpp/pull/219). - [@liuzl](https://github.com/liuzl) for speeding up Metal `conv_transpose_1d` dispatch in [#149](https://github.com/0xShug0/audio.cpp/pull/149). - [@JayDataEngineer](https://github.com/JayDataEngineer) for fixing PocketTTS `clone_audio_path` option typing in [#147](https://github.com/0xShug0/audio.cpp/pull/147). -- [@jasonchen31](https://github.com/jasonchen31) for adding server-side voice library folder support for name-based voice cloning, helping land SenseVoice-Small, and fixing crashes on binaries built for older GPUs in [#191](https://github.com/0xShug0/audio.cpp/pull/191), [#219](https://github.com/0xShug0/audio.cpp/pull/219), and [#240](https://github.com/0xShug0/audio.cpp/pull/240). -- [@mirek190](https://github.com/mirek190) for the native WebUI follow-up work around model management, package handling, request controls, package labels, new GGUF package surfacing, CUDA BF16 cuBLAS output on Ampere, MiniMax-H3 WebUI polish, retiring the legacy Python WebUI, adding the reusable native model package manager, clearing stale WebUI service workers, and clarifying ACE-Step GGUF package labels in [#199](https://github.com/0xShug0/audio.cpp/pull/199), [#206](https://github.com/0xShug0/audio.cpp/pull/206), [#207](https://github.com/0xShug0/audio.cpp/pull/207), [#208](https://github.com/0xShug0/audio.cpp/pull/208), [#211](https://github.com/0xShug0/audio.cpp/pull/211), [#213](https://github.com/0xShug0/audio.cpp/pull/213), [#229](https://github.com/0xShug0/audio.cpp/pull/229), [#230](https://github.com/0xShug0/audio.cpp/pull/230), [#257](https://github.com/0xShug0/audio.cpp/pull/257), and [#258](https://github.com/0xShug0/audio.cpp/pull/258). +- [@jasonchen31](https://github.com/jasonchen31) for adding server-side voice library folder support for name-based voice cloning, helping land SenseVoice-Small, fixing crashes on binaries built for older GPUs, adding Audio8 TTS, and fixing VoxCPM1 WebUI download/Yue language handling in [#191](https://github.com/0xShug0/audio.cpp/pull/191), [#219](https://github.com/0xShug0/audio.cpp/pull/219), [#240](https://github.com/0xShug0/audio.cpp/pull/240), [#333](https://github.com/0xShug0/audio.cpp/pull/333), and [#424](https://github.com/0xShug0/audio.cpp/pull/424). +- [@mirek190](https://github.com/mirek190) for the native WebUI follow-up work around model management, package handling, request controls, package labels, new GGUF package surfacing, CUDA BF16 cuBLAS output on Ampere, MiniMax-H3 WebUI polish, retiring the legacy Python WebUI, adding the reusable native model package manager, clearing stale WebUI service workers, clarifying ACE-Step GGUF package labels, documenting server host/port options, and fixing Qwen compact-logits graph reuse in [#199](https://github.com/0xShug0/audio.cpp/pull/199), [#206](https://github.com/0xShug0/audio.cpp/pull/206), [#207](https://github.com/0xShug0/audio.cpp/pull/207), [#208](https://github.com/0xShug0/audio.cpp/pull/208), [#211](https://github.com/0xShug0/audio.cpp/pull/211), [#213](https://github.com/0xShug0/audio.cpp/pull/213), [#229](https://github.com/0xShug0/audio.cpp/pull/229), [#230](https://github.com/0xShug0/audio.cpp/pull/230), [#257](https://github.com/0xShug0/audio.cpp/pull/257), [#258](https://github.com/0xShug0/audio.cpp/pull/258), [#381](https://github.com/0xShug0/audio.cpp/pull/381), and [#426](https://github.com/0xShug0/audio.cpp/pull/426). - [@nikich340](https://github.com/nikich340) for adding explicit server model unloading support in [#197](https://github.com/0xShug0/audio.cpp/pull/197). - [@utsl42](https://github.com/utsl42) for fixing CUDA linking on NixOS in [#214](https://github.com/0xShug0/audio.cpp/pull/214). - [@yegorius](https://github.com/yegorius) for fixing PocketTTS handling in the model manager in [#205](https://github.com/0xShug0/audio.cpp/pull/205). @@ -120,9 +120,16 @@ audio.cpp is moving faster because people keep showing up with real fixes, caref - [@tareko](https://github.com/tareko) for adding F5-TTS community scaffolding with Habibi Arabic aliases in [#275](https://github.com/0xShug0/audio.cpp/pull/275). - [@jrohde](https://github.com/jrohde) for adding the MOSS-VoiceGenerator community model in [#278](https://github.com/0xShug0/audio.cpp/pull/278). - [@LysanderdeJong](https://github.com/LysanderdeJong) for adding the MMS-300M-1130 forced aligner community model in [#279](https://github.com/0xShug0/audio.cpp/pull/279). -- [@drzsdrtfg](https://github.com/drzsdrtfg) for the tag-driven prebuilt release pipeline and Supertonic voice-preset request-option fix in [#286](https://github.com/0xShug0/audio.cpp/pull/286) and [#302](https://github.com/0xShug0/audio.cpp/pull/302). +- [@drzsdrtfg](https://github.com/drzsdrtfg) for the tag-driven prebuilt release pipeline, Supertonic voice-preset request-option fix, Soprano TTS community model, and Qwen cached-graph prefill input fix in [#286](https://github.com/0xShug0/audio.cpp/pull/286), [#302](https://github.com/0xShug0/audio.cpp/pull/302), [#323](https://github.com/0xShug0/audio.cpp/pull/323), and [#331](https://github.com/0xShug0/audio.cpp/pull/331). - [@Hi5808](https://github.com/Hi5808) for documenting Jetson Orin bring-up and correcting native architecture wording in [#288](https://github.com/0xShug0/audio.cpp/pull/288). - [@SelfRef](https://github.com/SelfRef) for making embedded WebUI work behind a path-prefix reverse proxy and adding server model LRU controls in [#297](https://github.com/0xShug0/audio.cpp/pull/297) and [#298](https://github.com/0xShug0/audio.cpp/pull/298). - [@bjhengen](https://github.com/bjhengen) for dropping out-of-span chunk speech metadata instead of aborting the whole run in [#301](https://github.com/0xShug0/audio.cpp/pull/301). -- [@gqf2008](https://github.com/gqf2008) for adding server idle unload and pre-load memory guard behavior, then tightening indeterminate-footprint handling in [#306](https://github.com/0xShug0/audio.cpp/pull/306) and [#308](https://github.com/0xShug0/audio.cpp/pull/308). +- [@gqf2008](https://github.com/gqf2008) for adding server idle unload and pre-load memory guard behavior, tightening indeterminate-footprint handling, adding Audio8 ASR, and improving default CJK text chunking in [#306](https://github.com/0xShug0/audio.cpp/pull/306), [#308](https://github.com/0xShug0/audio.cpp/pull/308), [#337](https://github.com/0xShug0/audio.cpp/pull/337), and [#441](https://github.com/0xShug0/audio.cpp/pull/441). - [@ampersandru](https://github.com/ampersandru) for adding IBM Granite Speech 5.0 470M TurboCTC ASR as a community model in [#311](https://github.com/0xShug0/audio.cpp/pull/311). +- [@iamwavecut](https://github.com/iamwavecut) for MiniMax Music3 performance work around native RoPE/SwiGLU lowering, opt-in CFG reuse, chunk hop support, batched ensemble takes, and Q4_K depth decoder support in [#321](https://github.com/0xShug0/audio.cpp/pull/321). +- [@XythQ](https://github.com/XythQ) for resolving model contracts once per loaded model instead of per request in [#328](https://github.com/0xShug0/audio.cpp/pull/328). +- [@pannagaps](https://github.com/pannagaps) for adding Chatterbox Turbo TTS as a community model in [#394](https://github.com/0xShug0/audio.cpp/pull/394). +- [@gsaon](https://github.com/gsaon) for fixing Granite Speech ASR mel filterbank construction on the continuous frequency axis in [#384](https://github.com/0xShug0/audio.cpp/pull/384). +- [@reezex0-ux](https://github.com/reezex0-ux) for adding the HIP Fast-AR top-k sampler path for Fish Audio in [#386](https://github.com/0xShug0/audio.cpp/pull/386). +- [@feng19](https://github.com/feng19) for adding `HF_ENDPOINT` mirror support to model downloads in [#397](https://github.com/0xShug0/audio.cpp/pull/397). +- [@CryptVenture](https://github.com/CryptVenture) for adding richer WAV output options, correcting the shared text chunk mode preset, accepting the MOSS VoiceGenerator instruction spelling used by the speech route, and forwarding `language` only when a model contract accepts it in [#358](https://github.com/0xShug0/audio.cpp/pull/358), [#370](https://github.com/0xShug0/audio.cpp/pull/370), [#371](https://github.com/0xShug0/audio.cpp/pull/371), and [#400](https://github.com/0xShug0/audio.cpp/pull/400). diff --git a/README.md b/README.md index 7c95c6024..12ce0c41f 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,7 @@ ModelScope repo mirror: https://www.modelscope.cn/models/HereIsMark/audio.cpp-gg > [!IMPORTANT] > -> **2026-08-28 - Dev testing:** Fun-CozyVoice3 and BreezeTTS 2 are available for testing on the `dev` branch. -> -> **2026-08-26 - Arena UI:** The new Arena tab makes it easier to compare local models side by side for TTS, voice conversion, and ASR. Use one shared input, queue multiple models or GGUF variants, then review outputs with metrics! +> **Arena UI:** The new Arena tab makes it easier to compare local models side by side for TTS, voice conversion, and ASR. Use one shared input, queue multiple models or GGUF variants, then review outputs with metrics! > > **CUDA performance headline:** multiple TTS paths already run **1.8x to up to 8x faster than their Python reference paths** while cutting end-to-end latency by **45%-85%**. > @@ -51,17 +49,15 @@ audio.cpp would not be moving this quickly without generous contributors bringin ## News > [!IMPORTANT] +> **2026-09-04 - Release 0.7.2:** This release adds BreezeTTS 2, CosyVoice3, Chatterbox Turbo TTS, Audio8 TTS, and Audio8 ASR, plus the new multipart audio alignment endpoint. +> > **2026-08-26 - Release 0.7:** This release adds MiniMax Music 3, MagpieTTS, PersonaPlex, MeanVC2, AudioSR, ControlFoley, FireRedTTS3, FireRedAudio, MiDashengLM-Gen, F5-TTS/Habibi, Granite Speech 5.0 TurboCTC, MMS Forced Aligner, and MOSS-VoiceGenerator, plus DotTTS Edit and ACE-Step 1.5 XL variants, bringing audio.cpp to **62** total model families and **85+** model variants! It also introduces the new Arena UI for side-by-side TTS, voice-conversion, and ASR comparison with shared inputs, queued runs, metrics, and result sorting. > > **2026-08-13 - Release 0.6:** This release adds **5** new model families - DotTTS, NeuTTS, MuScriptor, MiniMax-H3, and SenseVoice - bringing audio.cpp to **49** total model families and **70+** model variants, alongside the new native WebUI from [@mirek190](https://github.com/mirek190), expanded GGUF packaging, and more shared framework runtime pieces. > > **2026-07-31 - Release 0.5:** audio.cpp reaches **44 model families** with 9 new additions, early HIP/ROCm support for AMD GPUs, Nix ROCm/HIP build support, Metal optimizations with tested VoxCPM2 runs up to **2.56x faster** on Apple Silicon, and a major GGUF-first WebUI/package-spec usability pass. -> -> **2026-07-23 - Release 0.4:** audio.cpp expanded to **35 model families**, adding Higgs Audio v3 TTS 4B, Fish Audio S2 Pro, Voxtral Realtime ASR, community OuteTTS and VieNeu-TTS, broader GGUF/package-spec support, reusable framework improvements, and the integrated WebUI thanks to [@kigner](https://github.com/kigner) and [@patrickjchen](https://github.com/patrickjchen). -> -> **2026-07-14 - Release 0.3:** This release added IndexTTS2, Irodori-TTS, MOSS-TTS-Nano, MOSS-TTS-Local, Supertonic 3, Chatterbox voice conversion, and the first broad GGUF loading/conversion wave. Thanks to [@justinjohn0306](https://github.com/justinjohn0306) for MOSS-TTS-Local and [@mirek190](https://github.com/mirek190) for driving GGUF forward. -**2026-06-25 to 2026-07-08:** audio.cpp grew from the first released model wave into broad TTS, ASR, music generation, source separation, VAD, diarization, codec, and voice-conversion coverage, with VibeVoice 1.5B/7B, LoRA adapter loading, initial streaming support, and major CUDA Conv1DTransp speedups. +**2026-06-25 to 2026-07-23 (release 0.1 to 0.4):** audio.cpp grew from the first released model wave into broad TTS, ASR, music generation, source separation, VAD, diarization, codec, and voice-conversion coverage, with VibeVoice 1.5B/7B, LoRA adapter loading, initial streaming support, and major CUDA Conv1DTransp speedups. ## Supported Models @@ -73,8 +69,10 @@ Runtime tags summarize the supported loading paths. GGUF package precision varie | Family | Task | Lang | Variants | Runtime | |---|---|---|---|---| +| **breeze_tts** | TTS, Clone, Design, Ctrl | zh, en | BreezeTTS 2 instruction-conditioned TTS and prompt-audio voice cloning | GGUF BF16/Q8, Stream | | **chatterbox** | TTS, Clone, VC| ar, da, de, el, en, es, fi, fr, hi, it, ko, ms, nl, no, pl, pt, sv, sw, tr | Chatterbox with 0.5B backbone | GGUF 16/Q8 | | **confucius4_tts** | Clone | zh, en, ja, ko, de, fr, es, id, it, th, pt, ru, ms, vi | Confucius4-TTS multilingual voice cloning | GGUF F32, Stream | +| **cosyvoice3** | TTS, Clone | zh, en, ja, ko, de, es, fr, it, ru, yue | Fun-CosyVoice3 zero-shot, cross-lingual, and instruction-conditioned TTS | GGUF F32/Q8 | | **dots_tts** | TTS, Clone, Edit, Ctrl | multilingual | DotTTS SOAR, MeanFlow, and Edit | GGUF 16/Q8, Stream | | **dramabox** | TTS, Clone | en | DramaBox expressive TTS and voice cloning | GGUF Q8 | | **fish_audio** | TTS, Clone, Ctrl | auto, en, zh | Fish Audio S2 Pro | GGUF 16/Q8 | @@ -90,7 +88,7 @@ Runtime tags summarize the supported loading paths. GGUF package precision varie | **neutts** | TTS, Ctrl | en | NeuTTS 2E with built-in speaker prompts and emotion control | GGUF original precision, Stream | | **omnivoice** | TTS, Clone, Design, Ctrl | 646+ langs | OmniVoice, Qwen3-0.6B based | GGUF 16/Q8, Stream | | **personaplex** | Dialogue, S2S | en | PersonaPlex 7B v1 speech-to-speech conversational model with packaged voice/persona prompts | GGUF Q4/Q8, Stream | -| **pocket_tts** | TTS, Clone | en, de, it, pt, es | PocketTTS-100M | GGUF 16/Q8 | +| **pocket_tts** | TTS, Clone | en, de, it, pt, es | PocketTTS-100M | GGUF 16/Q8, Stream | | **qwen3_tts** | TTS, Clone, Design, Ctrl | zh, en, fr, de, it, ja, ko, pt, ru, es | Qwen3-TTS-12Hz-0.6B-Base, Qwen3-TTS-12Hz-1.7B-Base, Qwen3-TTS-12Hz-1.7B-CustomVoice, Qwen3-TTS-12Hz-1.7B-VoiceDesign | GGUF 16/Q8 | | **supertonic** | TTS | en, ko, ja, ar, bg, cs, da, de, el, es, et, fi, fr, hi, hr, hu, id, it, lt, lv, nl, pl, pt, ro, ru, sk, sl, sv, tr, uk, vi, na | Supertonic 3 | GGUF F32, Stream | | **vibevoice** | TTS, Dialogue | en, zh | VibeVoice-1.5B, VibeVoice-7B | GGUF 16/Q8 | @@ -159,14 +157,18 @@ Community model ports live under `community_models` to make the ownership bounda | **kroko_asr** | ASR | de, en, es, fr, it, he, nl, pt, sv, tr | Safetensors, GGUF Q8 | Mirek [@mirek190](https://github.com/mirek190) | [Kroko Community ASR](docs/community_models/kroko_asr.md) native offline/streaming Zipformer2/RNN-T transcription with word timestamps | | **minimax_h3** | Video, Music, TTS/Dialogue | auto | GGUF Q4/INT8 | [@0xShug0](https://github.com/0xShug0) | [MiniMax-H3](docs/community_models/minimax_h3.md) text-to-audio/video generation with Q4_K and optional INT8 ConvRot DiT | | **minimax_music3** | Music | auto | GGUF Q4/Q8 | [@0xShug0](https://github.com/0xShug0), [@JoeMattie](https://github.com/JoeMattie) | [MiniMax Music 3](docs/community_models/minimax_music3.md) text-to-music generation with lyrics conditioning | +| **mira_tts** | TTS, Clone | en | Local conversion | Mirek [@mirek190](https://github.com/mirek190) | [MiraTTS](docs/community_models/mira_tts.md) experimental native Qwen2 + ECAPA/Perceiver zero-shot voice cloning with progressive segment streaming (CC-BY-NC-SA-4.0 weights) | | **mms_forced_aligner** | Align | nl (nld), en (eng); pre-romanized Latin | Safetensors, GGUF 16/Q8 | Community | [MMS-300M-1130 Forced Aligner](docs/community_models/mms_forced_aligner.md) word-timestamp alignment from a wav2vec2 CTC checkpoint (safetensors or local GGUF) | | **moss_tts_local** | TTS, Clone, Ctrl | auto, optional language hint | GGUF | [@justinjohn0306](https://github.com/justinjohn0306) | MOSS-TTS-Local Transformer v1.5 support | | **moss_voicegen** | Voice Design | en, zh | GGUF | Joost [@jrohde](https://github.com/jrohde) | [MOSS-VoiceGenerator](docs/community_models/moss_voicegen.md) speech in a voice designed from a written instruction | | **outetts** | TTS, Clone | en, ar, zh, nl, fr, de, it, ja, ko, lt, ru, es, pt, be, bn, ka, hu, lv, fa, pl, sw, ta, uk | GGUF | Mirek [@mirek190](https://github.com/mirek190) | Llama-OuteTTS-1.0-1B TTS and voice cloning support | | **parakeet_tdt** | ASR | auto, bg, cs, da, de, el, en, es, et, fi, fr, hr, hu, it, lt, lv, mt, nl, pl, pt, ro, ru, sk, sl, sv, uk | GGUF F32/16/Q8, Stream | [@dleiferives](https://github.com/dleiferives) | [Parakeet-TDT 0.6B v3](docs/community_models/parakeet_tdt.md) offline, long-form, and buffered-streaming ASR support | +| **sanotts** | TTS | en, vi, id | GGUF FP32 | Ashish [@voidash](https://github.com/voidash) | [sanoTTS voice family](docs/community_models/sanotts.md) seven voices from 294k to 2.27M parameters, native offline synthesis | | **sense_asr** | ASR | auto, zh, en, yue, ja, ko, pt, ru, es, it, fr, de, nl, pl, tr, ar, hi, vi, th, id, ms, fa, nospeech | GGUF Q8, Stream | Jason Chen [@jasonchen31](https://github.com/jasonchen31), [@LauraGPT](https://github.com/LauraGPT) / FunASR | [SenseVoice-Small](docs/community_models/sense_asr.md) offline/streaming SAN-M + CTC transcription with event/emotion/language tags and ITN | -| **soprano_tts** | TTS | en | GGUF Q8, Stream | [@WalkingCat](https://github.com/WalkingCat) | [Soprano-1.1-80M](https://huggingface.co/WalkingCat/Soprano-1.1-80M-GGUF) ultra-lightweight TTS with Qwen3 LM + Vocos decoder | +| **sopro_tts** | TTS, Clone | en, pt, fr, de | Safetensors, GGUF, Stream | Community | [Sopro V2 Turbo](docs/community_models/sopro_tts.md) 120M zero-shot voice cloning: style-prefix semantic LM over FSQ tokens, rectified-flow acoustic DiT, Vocos ISTFT vocoder at 24 kHz | +| **soprano_tts** | TTS | en | GGUF Q8, Stream | [@drzsdrtfg](https://github.com/drzsdrtfg) | [Soprano-1.1-80M](https://huggingface.co/WalkingCat/Soprano-1.1-80M-GGUF) ultra-lightweight TTS with Qwen3 LM + Vocos decoder | | **vietneu_tts** | TTS, Clone | vi, en | GGUF | Phuoc [@phuocnguyen90](https://github.com/phuocnguyen90) | [VieNeu-TTS-v3-Turbo](docs/community_models/vietneu_tts.md) TTS and voice cloning support | +| **vibeasr** | ASR | en | GGUF I8_S + I2_S | [@XsquirrelC](https://github.com/XsquirrelC) | [VibeASR](docs/community_models/vibeasr.md) fully quantized port of [VibeASR.cpp](https://github.com/microsoft/VibeASR.cpp): VibeVoice acoustic/semantic tokenizers on INT8 weights and INT8 activations, feeding a ternary BitNet Qwen2 decoder. Offline, CPU only | | **voxcpm1** | TTS, Clone | zh, en, ja, ko | GGUF Q8, Stream | [@jasonchen31](https://github.com/jasonchen31) | [VoxCPM1](docs/community_models/voxcpm1.md) tokenizer-free 0.5B TTS with 16 kHz output, streaming, and continuation-mode voice cloning | ## Docker @@ -467,6 +469,8 @@ Run with `--backend hip` (`rocm` is accepted as an alias). For GPU target select | `ENGINE_ENABLE_OPENMP` | Enable OpenMP for host-side parallel work. | `ON` | | `ENGINE_BUILD_EXAMPLES` | Build example binaries. | `OFF` | | `ENGINE_BUILD_TESTS` | Build framework unit tests. | `OFF` | +| `ENGINE_BUILD_EXTENDED_TESTS` | Build extended non-model tests and probes, such as server/app/package-manager checks and backend graph tests. | `OFF` | +| `ENGINE_BUILD_MODEL_TESTS` | Build model-specific tests and probes. | `OFF` | | `ENGINE_BUILD_WARMBENCH` | Build warmbench helper binaries. | `OFF` | | `AUDIOCPP_BUILD_NATIVE_MODEL_MANAGER` | Build the standalone native model manager and enable server-side WebUI downloads/install management. This opt-in feature builds the HTTP/TLS dependency. | `OFF` | | `AUDIOCPP_USE_SYSTEM_OPENSSL` | Use system OpenSSL instead of bundled BoringSSL when native model management is enabled. | `OFF` | @@ -661,6 +665,7 @@ The server exposes: - `GET /v1/models` - `POST /v1/audio/speech` - `POST /v1/audio/transcriptions` +- `POST /v1/audio/transcriptions/details` - `POST /v1/audio/alignments` - `POST /v1/tasks/run` diff --git a/app/cli/main.cpp b/app/cli/main.cpp index ad59f4555..dd5ca47dd 100644 --- a/app/cli/main.cpp +++ b/app/cli/main.cpp @@ -2,6 +2,7 @@ #include "batch.h" #include "partial_render.h" #include "request.h" +#include "../common/build_info.h" #include "../streaming/pcm_source.h" #include "../streaming/streaming.h" #include "../workflow/execution.h" @@ -51,6 +52,7 @@ void print_task_list_help() { std::cout << "audiocpp_cli --task --family --model --backend [options]\n" << " Global:\n" + << " --version Print build version, commit, compiler, platform, and enabled backends\n" << " --task vad|asr|diar|sep|gen|tts|clon|vc|s2s|align|vdes|spk|svc|midi\n" << " --family \n" << " --model \n" @@ -637,6 +639,10 @@ int audiocpp_cli_main(int argc, char ** argv) { log_file, }); const bool metrics_requested = has_arg(argc, argv, "--metrics"); + if (has_arg(argc, argv, "--version")) { + minitts::app::print_build_info(std::cout); + return 0; + } const auto registry_config = find_arg(argc, argv, "--registry-config"); auto registry = engine::runtime::make_default_registry( @@ -769,6 +775,10 @@ int audiocpp_cli_main(int argc, char ** argv) { return 0; } if (!model_arg) { + if (argc == 1) { + minitts::app::print_build_info_summary(std::cerr); + std::cerr << "Run audiocpp_cli --help for usage.\n"; + } throw std::runtime_error("missing required --model argument"); } @@ -990,7 +1000,7 @@ int wmain(int argc, wchar_t ** wargv) { } argv.push_back(nullptr); const int status = audiocpp_cli_main(argc, argv.data()); - if (status == 0) { + if (status == 0 && !minitts::cli::has_arg(argc, argv.data(), "--version")) { minitts::cli::warn_ignored_args(argc, argv.data()); } return status; @@ -1002,7 +1012,7 @@ int wmain(int argc, wchar_t ** wargv) { #else int main(int argc, char ** argv) { const int status = audiocpp_cli_main(argc, argv); - if (status == 0) { + if (status == 0 && !minitts::cli::has_arg(argc, argv, "--version")) { minitts::cli::warn_ignored_args(argc, argv); } return status; diff --git a/app/common/build_info.cpp b/app/common/build_info.cpp new file mode 100644 index 000000000..8f0619bf8 --- /dev/null +++ b/app/common/build_info.cpp @@ -0,0 +1,53 @@ +#include "build_info.h" + +#include "build_info_config.h" + +#include +#include + +namespace minitts::app { +namespace { + +std::string build_type() { + std::string value = AUDIOCPP_BUILD_TYPE_STRING; + if (value.empty()) { + value = "unknown"; + } + return value; +} + +std::string compiler_name() { + const std::string id = AUDIOCPP_COMPILER_ID_STRING; + const std::string version = AUDIOCPP_COMPILER_VERSION_STRING; + if (id == "GNU") { + return "gcc " + version; + } + if (id == "Clang") { + return "clang " + version; + } + if (id == "AppleClang") { + return "apple-clang " + version; + } + if (id == "MSVC") { + return "msvc " + version; + } + return id.empty() ? "unknown" : id + " " + version; +} + +} // namespace + +void print_build_info(std::ostream & out) { + out << "audio.cpp " << AUDIOCPP_VERSION_STRING << "\n" + << "git: " << AUDIOCPP_GIT_SHA_STRING << " " << AUDIOCPP_GIT_DATE_STRING << "\n" + << "build: " << build_type() << ", " << compiler_name() << ", " + << AUDIOCPP_SYSTEM_NAME_STRING << " " << AUDIOCPP_SYSTEM_PROCESSOR_STRING << "\n" + << "backends: " << AUDIOCPP_BUILD_BACKENDS_STRING << "\n"; +} + +void print_build_info_summary(std::ostream & out) { + out << "audio.cpp " << AUDIOCPP_VERSION_STRING + << " (git " << AUDIOCPP_GIT_SHA_STRING << ", " << build_type() << ")" + << " [backends: " << AUDIOCPP_BUILD_BACKENDS_STRING << "]\n"; +} + +} // namespace minitts::app diff --git a/app/common/build_info.h b/app/common/build_info.h new file mode 100644 index 000000000..64f01cc9f --- /dev/null +++ b/app/common/build_info.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace minitts::app { + +void print_build_info(std::ostream & out); +void print_build_info_summary(std::ostream & out); + +} // namespace minitts::app diff --git a/app/common/build_info_config.h.in b/app/common/build_info_config.h.in new file mode 100644 index 000000000..bebb520e4 --- /dev/null +++ b/app/common/build_info_config.h.in @@ -0,0 +1,11 @@ +#pragma once + +#define AUDIOCPP_VERSION_STRING "@AUDIOCPP_VERSION@" +#define AUDIOCPP_GIT_SHA_STRING "@AUDIOCPP_GIT_SHA@" +#define AUDIOCPP_GIT_DATE_STRING "@AUDIOCPP_GIT_DATE@" +#define AUDIOCPP_BUILD_TYPE_STRING "@CMAKE_BUILD_TYPE@" +#define AUDIOCPP_COMPILER_ID_STRING "@CMAKE_CXX_COMPILER_ID@" +#define AUDIOCPP_COMPILER_VERSION_STRING "@CMAKE_CXX_COMPILER_VERSION@" +#define AUDIOCPP_SYSTEM_NAME_STRING "@CMAKE_SYSTEM_NAME@" +#define AUDIOCPP_SYSTEM_PROCESSOR_STRING "@CMAKE_SYSTEM_PROCESSOR@" +#define AUDIOCPP_BUILD_BACKENDS_STRING "@AUDIOCPP_BUILD_BACKENDS@" diff --git a/app/gguf/main.cpp b/app/gguf/main.cpp index a9e06f34a..5296f0d67 100644 --- a/app/gguf/main.cpp +++ b/app/gguf/main.cpp @@ -38,6 +38,35 @@ std::string lower_ascii(std::string value) { return value; } +bool is_gguf_path(const std::filesystem::path & path) { + return lower_ascii(path.extension().string()) == ".gguf"; +} + +// A GGUF this tool wrote carries its conversion namespaces in the tensor names +// ("/"), so re-converting one — requantising a published +// bf16 package to q8_0, say — can recover them instead of asking the caller to +// spell out namespaces the file already knows. +std::set gguf_tensor_namespaces(const std::filesystem::path & path) { + std::set namespaces; + const auto source = engine::assets::open_tensor_source(path); + for (const auto & tensor : source->tensors()) { + const auto separator = tensor.name.find('/'); + namespaces.insert(separator == std::string::npos ? std::string() : tensor.name.substr(0, separator)); + } + return namespaces; +} + +std::set input_namespaces(const engine::assets::TensorSourceInput & input) { + if (!input.tensor_prefix.empty()) + return {input.tensor_prefix}; + if (is_gguf_path(input.path)) { + auto namespaces = gguf_tensor_namespaces(input.path); + if (!namespaces.empty()) + return namespaces; + } + return {std::string()}; +} + bool excluded_sidecar(const std::filesystem::path & path, const std::filesystem::path & output) { const std::string extension = lower_ascii(path.extension().string()); return extension == ".safetensors" || extension == ".gguf" || extension == ".bin" || extension == ".pt" || @@ -304,7 +333,8 @@ std::optional required_destination(const json::Value & source, cons std::vector validate_candidate(const PackageSpecCandidate & candidate, const std::set & actual_prefixes, - const std::set & sidecars) { + const std::set & sidecars, + bool reconversion) { std::vector errors; try { const auto spec = json::parse(candidate.spec.json); @@ -327,10 +357,18 @@ std::vector validate_candidate(const PackageSpecCandidate & candida optional_prefixes.insert(tensor_prefix(value)); } } - for (const auto & prefix : expected_prefixes) { - if (actual_prefixes.find(prefix) == actual_prefixes.end()) { - errors.push_back("missing tensor namespace '" + (prefix.empty() ? std::string("") : prefix) + - "'"); + // Requiring every namespace catches a fresh conversion that forgot an + // input. Re-converting a finished package proves nothing of the sort: + // its namespaces are already exactly what that package ships, and a + // package legitimately built with `--exclude-prefix` (an ACE-Step XL + // GGUF carries no turbo or base DiT) would otherwise be impossible to + // re-quantise even though the runtime loads it happily. + if (!reconversion) { + for (const auto & prefix : expected_prefixes) { + if (actual_prefixes.find(prefix) == actual_prefixes.end()) { + errors.push_back("missing tensor namespace '" + (prefix.empty() ? std::string("") : prefix) + + "'"); + } } } for (const auto & prefix : actual_prefixes) { @@ -354,22 +392,78 @@ std::vector validate_candidate(const PackageSpecCandidate & candida return errors; } +// Where the conversion looks for the small files (configs, tokenizers) that +// belong in the output. `--root` wins, and so does an explicit `--sidecar` +// set. Otherwise a GGUF input's own embedded copies are used — for a re-encode +// they are exactly the files that package ships, where the directory the file +// happens to sit in is only a guess. Everything else keeps using that +// directory, which is what every safetensors conversion does. +std::filesystem::path resolve_sidecar_root(const std::filesystem::path & requested, + const std::vector & inputs, + const std::vector & explicit_sidecars, + bool embed_sidecars) { + if (!requested.empty()) + return std::filesystem::weakly_canonical(requested); + const auto parent = std::filesystem::weakly_canonical(inputs.front().path.parent_path()); + if (!embed_sidecars || !explicit_sidecars.empty()) + return parent; + for (const auto & input : inputs) { + if (!is_gguf_path(input.path) || !engine::assets::gguf_has_embedded_sidecars(input.path)) + continue; + const auto materialized = engine::assets::materialize_gguf_sidecars(input.path); + std::cerr << "note: reusing the sidecars embedded in " << input.path.string() + << "; pass --root to override\n"; + return materialized; + } + return parent; +} + PackageSpecCandidate select_package_spec(const std::vector & inputs, const std::filesystem::path & model_root, const std::filesystem::path & output, const std::vector & explicit_sidecars, const std::optional & requested_spec, std::optional family, bool embed_sidecars) { + // Every input is a GGUF this tool wrote for one family: the conversion is a + // re-encode of a finished package rather than an assembly of a new one. + const bool reconversion = + !inputs.empty() && std::all_of(inputs.begin(), inputs.end(), + [](const engine::assets::TensorSourceInput & input) { + return is_gguf_path(input.path) && + engine::assets::read_gguf_embedded_model_spec(input.path).has_value(); + }); std::vector candidates; if (requested_spec.has_value()) { add_spec_path(candidates, *requested_spec, family, 0); } else { const size_t config_candidate_count = candidates.size(); + // A GGUF input states its own family and package spec. Trusting that is + // both more accurate than guessing from the catalog and safer: without + // it, a single-namespace GGUF matches whichever unrelated family's spec + // happens to accept one unnamed namespace, and the conversion is + // silently stamped with that family. + for (const auto & input : inputs) { + if (!is_gguf_path(input.path)) + continue; + const auto embedded = engine::assets::read_gguf_embedded_model_spec(input.path); + if (!embedded.has_value()) + continue; + add_candidate(candidates, + parse_package_spec(embedded->json, "embedded:" + input.path.string(), 0)); + if (!family.has_value()) + family = embedded->family; + } const auto config_family = add_model_config_spec(candidates, model_root); if (!family.has_value()) family = config_family; - if (candidates.size() == config_candidate_count) { + // An explicitly requested family that none of those specs describes still + // falls through to the catalog, the way it did before they existed. + const bool describes_requested_family = + std::any_of(candidates.begin(), candidates.end(), [&family](const PackageSpecCandidate & candidate) { + return !family.has_value() || candidate.spec.family == *family; + }); + if (candidates.size() == config_candidate_count || !describes_requested_family) { if (engine::io::is_existing_file(model_root / "model_spec.json")) { add_spec_file(candidates, model_root / "model_spec.json", 2); } @@ -389,9 +483,11 @@ PackageSpecCandidate select_package_spec(const std::vector prefixes; for (const auto & input : inputs) { - if (!prefixes.insert(input.tensor_prefix).second) { - throw std::runtime_error("duplicate tensor namespace in conversion inputs: '" + - (input.tensor_prefix.empty() ? std::string("") : input.tensor_prefix) + "'"); + for (const auto & prefix : input_namespaces(input)) { + if (!prefixes.insert(prefix).second) { + throw std::runtime_error("duplicate tensor namespace in conversion inputs: '" + + (prefix.empty() ? std::string("") : prefix) + "'"); + } } } const auto sidecars = planned_sidecar_destinations(model_root, output, explicit_sidecars, embed_sidecars); @@ -412,7 +508,7 @@ PackageSpecCandidate select_package_spec(const std::vector embedded_model_spec; if (!allow_missing_model_spec) { embedded_model_spec = diff --git a/app/server/README.md b/app/server/README.md index b37030c3c..6304b36e0 100644 --- a/app/server/README.md +++ b/app/server/README.md @@ -380,6 +380,44 @@ The stream emits `transcript.text.delta` events, one final `transcript.text.done Note that `stream=true` streams the *output* of an already-uploaded file: the whole recording is sent first, and the deltas describe decoding it. It shortens time-to-first-token on long audio, but nothing can appear while the speaker is still talking. For that, use the live endpoint below. +### `POST /v1/audio/transcriptions/details` + +Same request as `POST /v1/audio/transcriptions` — JSON with a server-local path, or a `multipart/form-data` upload — with a richer response. Use it when the model produces timestamps or speaker labels and the caller wants them. + +`/v1/audio/transcriptions` returns `text` and `timing` and nothing else, so a model that aligned every word or separated speakers has that work discarded on the way out. This route returns those fields instead. The response schema of the plain route is unchanged; existing clients see exactly what they see today. + +```bash +curl http://127.0.0.1:8080/v1/audio/transcriptions/details \ + -F model=parakeet-tdt \ + -F file=@/path/to/input.wav +``` + +```json +{ + "text": "the task has completed successfully", + "language": "en", + "words": [ + {"word": "the", "start_sample": 3200, "end_sample": 6400, "confidence": 0.98} + ], + "sample_rate": 16000, + "timing": { "wall_ms": 412.7, "audio_duration_ms": 2400.0, "rtf": 0.17 } +} +``` + +`text` and `timing` are always present and match the plain route. The rest appear only when the model produced them: + +| Field | Present when | Contents | +|---|---|---| +| `language` | the model reports a detected or configured language | Language code. | +| `segments` | the model produces speech segments | `start_sample`, `end_sample`, `confidence`, and `text` where the segment carries it. | +| `speaker_turns` | the model diarizes | `start_sample`, `end_sample`, `speaker_id`, `confidence`, and `text` where present. | +| `words` | the model aligns words | `word`, `start_sample`, `end_sample`, `confidence`. | +| `sample_rate` | any of the three arrays above is present | Rate the sample offsets are counted in. Divide an offset by it for seconds. | + +Spans are sample offsets rather than seconds because that is what the models report; `sample_rate` is what converts them, which is why it only appears alongside them. + +`stream=true` is rejected with a 400 on this route: the SSE response carries transcript deltas only, so it has nowhere to put the detail arrays. Use `/v1/audio/transcriptions` for a streamed transcript. + ### `POST /v1/audio/alignments` Multipart forced-alignment request using uploaded audio bytes and a known transcript. Use this when the server cannot see the client's local audio path, for example when the server is remote or running in Docker. diff --git a/app/server/http.cpp b/app/server/http.cpp index d22b0f65c..7e13aea2d 100644 --- a/app/server/http.cpp +++ b/app/server/http.cpp @@ -597,11 +597,35 @@ HttpRequest read_http_request( request.headers[name] = value; } + const auto transfer_encoding_it = request.headers.find("transfer-encoding"); + const bool chunked_body = + transfer_encoding_it != request.headers.end() && + is_chunked_only(transfer_encoding_it->second); + if (wants_incremental_body(request)) { leftover = data.substr(header_end + 4); return request; } + if (chunked_body) { + LiveIngestLimits limits; + limits.max_body_bytes = static_cast(std::min( + max_request_body_bytes, + static_cast(std::numeric_limits::max()))); + ChunkedSocketStreambuf body_buffer( + socket, + data.substr(header_end + 4), + limits); + std::istream body_stream(&body_buffer); + body_stream.exceptions(std::ios::badbit); + std::array chunk_buffer{}; + while (body_stream) { + body_stream.read(chunk_buffer.data(), static_cast(chunk_buffer.size())); + request.body.append(chunk_buffer.data(), static_cast(body_stream.gcount())); + } + return request; + } + size_t content_length = 0; if (const auto it = request.headers.find("content-length"); it != request.headers.end()) { // std::stoull throws on a non-numeric or overflowing header, and the diff --git a/app/server/main.cpp b/app/server/main.cpp index 1fec357ef..bc4287b75 100644 --- a/app/server/main.cpp +++ b/app/server/main.cpp @@ -2,6 +2,8 @@ #include "http.h" #include "runtime.h" +#include "../common/build_info.h" + #include "engine/framework/core/backend.h" #include "engine/framework/debug/trace.h" @@ -66,6 +68,7 @@ void print_help() { << " [--model-spec-override ] [--voice-dir ]\n" << " [--log] [--log-file ]\n" << " [--cors-origins ]\n" + << " --version print build version, commit, compiler, platform, and enabled backends\n" << " --ui serve the embedded WebUI\n" << " --no-ui disable the embedded WebUI\n" << " --ui-management allow WebUI model management and downloads; requires\n" @@ -127,6 +130,10 @@ int main(int argc, char ** argv) { engine::core::print_backend_devices(std::cout); return 0; } + if (has_arg(argc, argv, "--version")) { + minitts::app::print_build_info(std::cout); + return 0; + } if (has_arg(argc, argv, "--help") || has_arg(argc, argv, "-h")) { print_help(); return 0; diff --git a/app/server/runtime.cpp b/app/server/runtime.cpp index 972fc5e48..2769cc0cf 100644 --- a/app/server/runtime.cpp +++ b/app/server/runtime.cpp @@ -657,6 +657,68 @@ std::unordered_map timing_headers( }; } +// Transcript detail arrays shared by /v1/tasks/run and /v1/audio/transcriptions. +// ASR models that produce timestamps populate only these fields, so a route that +// omits them silently discards work the model already did. +template +void write_transcript_detail_fields( + std::ostringstream & out, + const engine::runtime::TaskResult & result, + FieldFn field) { + if (!result.speech_segments.empty()) { + field("segments"); + out << "["; + for (size_t i = 0; i < result.speech_segments.size(); ++i) { + if (i != 0) { + out << ","; + } + const auto & segment = result.speech_segments[i]; + out << "{\"start_sample\":" << segment.span.start_sample + << ",\"end_sample\":" << segment.span.end_sample + << ",\"confidence\":" << segment.confidence; + if (!segment.text.empty()) { + out << ",\"text\":" << json_quote(segment.text); + } + out << "}"; + } + out << "]"; + } + if (!result.speaker_turns.empty()) { + field("speaker_turns"); + out << "["; + for (size_t i = 0; i < result.speaker_turns.size(); ++i) { + if (i != 0) { + out << ","; + } + const auto & turn = result.speaker_turns[i]; + out << "{\"start_sample\":" << turn.span.start_sample + << ",\"end_sample\":" << turn.span.end_sample + << ",\"speaker_id\":" << json_quote(turn.speaker_id) + << ",\"confidence\":" << turn.confidence; + if (!turn.text.empty()) { + out << ",\"text\":" << json_quote(turn.text); + } + out << "}"; + } + out << "]"; + } + if (!result.word_timestamps.empty()) { + field("words"); + out << "["; + for (size_t i = 0; i < result.word_timestamps.size(); ++i) { + if (i != 0) { + out << ","; + } + const auto & word = result.word_timestamps[i]; + out << "{\"word\":" << json_quote(word.word) + << ",\"start_sample\":" << word.span.start_sample + << ",\"end_sample\":" << word.span.end_sample + << ",\"confidence\":" << word.confidence << "}"; + } + out << "]"; + } +} + std::string task_result_json_with_timing( const engine::runtime::TaskResult & result, const std::string & timing) { @@ -727,58 +789,7 @@ std::string task_result_json_with_timing( for (const auto & artifact : result.output_artifacts) write_artifact(artifact); out << "]"; } - if (!result.speech_segments.empty()) { - field("segments"); - out << "["; - for (size_t i = 0; i < result.speech_segments.size(); ++i) { - if (i != 0) { - out << ","; - } - const auto & segment = result.speech_segments[i]; - out << "{\"start_sample\":" << segment.span.start_sample - << ",\"end_sample\":" << segment.span.end_sample - << ",\"confidence\":" << segment.confidence; - if (!segment.text.empty()) { - out << ",\"text\":" << json_quote(segment.text); - } - out << "}"; - } - out << "]"; - } - if (!result.speaker_turns.empty()) { - field("speaker_turns"); - out << "["; - for (size_t i = 0; i < result.speaker_turns.size(); ++i) { - if (i != 0) { - out << ","; - } - const auto & turn = result.speaker_turns[i]; - out << "{\"start_sample\":" << turn.span.start_sample - << ",\"end_sample\":" << turn.span.end_sample - << ",\"speaker_id\":" << json_quote(turn.speaker_id) - << ",\"confidence\":" << turn.confidence; - if (!turn.text.empty()) { - out << ",\"text\":" << json_quote(turn.text); - } - out << "}"; - } - out << "]"; - } - if (!result.word_timestamps.empty()) { - field("words"); - out << "["; - for (size_t i = 0; i < result.word_timestamps.size(); ++i) { - if (i != 0) { - out << ","; - } - const auto & word = result.word_timestamps[i]; - out << "{\"word\":" << json_quote(word.word) - << ",\"start_sample\":" << word.span.start_sample - << ",\"end_sample\":" << word.span.end_sample - << ",\"confidence\":" << word.confidence << "}"; - } - out << "]"; - } + write_transcript_detail_fields(out, result, field); field("timing"); out << timing; out << "}"; @@ -1162,6 +1173,13 @@ HttpResponse ServerState::handle(const HttpRequest & request) { else if (request.method == "POST" && request.path == "/v1/audio/transcriptions") { response = handle_transcription(request); } + // Same request shape as the route above, opt-in richer response: the models + // that align words or separate speakers report them through fields the + // transcription response drops. A separate path rather than a flag keeps the + // existing response schema fixed for every client already built against it. + else if (request.method == "POST" && request.path == "/v1/audio/transcriptions/details") { + response = handle_transcription(request, /*detail=*/true); + } else if (request.method == "POST" && request.path == "/v1/audio/alignments") { response = handle_alignment(request); } @@ -2493,18 +2511,25 @@ HttpResponse ServerState::handle_speech_live(const HttpRequest & request) { }); } -HttpResponse ServerState::handle_transcription(const HttpRequest & request) { +// The streaming response carries transcript deltas only, so it has nowhere to put +// the detail arrays. Refusing is better than accepting the request and silently +// returning none of what the route exists to return. +constexpr const char * kDetailStreamUnsupported = + "streaming is not supported on /v1/audio/transcriptions/details; " + "use /v1/audio/transcriptions for a streamed transcript"; + +HttpResponse ServerState::handle_transcription(const HttpRequest & request, bool detail) { std::string content_type; if (const auto it = request.headers.find("content-type"); it != request.headers.end()) { content_type = it->second; } if (const auto boundary = extract_multipart_boundary(content_type)) { - return handle_transcription_multipart(request.body, *boundary); + return handle_transcription_multipart(request.body, *boundary, detail); } - return handle_transcription_json(request.body); + return handle_transcription_json(request.body, detail); } -HttpResponse ServerState::handle_transcription_json(const std::string & body_text) { +HttpResponse ServerState::handle_transcription_json(const std::string & body_text, bool detail) { const auto body = engine::io::json::parse(body_text); auto & model = require_model(body); const auto request = apply_default_request_options( @@ -2512,16 +2537,20 @@ HttpResponse ServerState::handle_transcription_json(const std::string & body_tex build_openai_transcription_request(body, request_base_, model.accepts_language)); const auto busy_timeout_ms = parse_busy_timeout_override(body); if (bool_field(body, "stream", false)) { + if (detail) { + return error_response(400, kDetailStreamUnsupported, "invalid_request_error"); + } return run_transcription_stream(model, request, busy_timeout_ms); } - return run_transcription(model, request, busy_timeout_ms); + return run_transcription(model, request, busy_timeout_ms, detail); } // Accepts the same multipart/form-data shape OpenAI's Whisper API (and clients built against it, // e.g. Open WebUI) send: a "file" part with the audio bytes, plus "model" and optional "language" // fields. audio.cpp's native JSON request only takes a server-local path, so the uploaded bytes are // spooled to a temp file and routed through the existing JSON request builder. -HttpResponse ServerState::handle_transcription_multipart(const std::string & body_text, const std::string & boundary) { +HttpResponse ServerState::handle_transcription_multipart( + const std::string & body_text, const std::string & boundary, bool detail) { const auto parts = parse_multipart_body(body_text, boundary); log_multipart_request_summary_if_enabled(config_, parts); @@ -2591,15 +2620,19 @@ HttpResponse ServerState::handle_transcription_multipart(const std::string & bod build_openai_transcription_request( body, request_base_, model.accepts_language, &file_part->data)); if (stream) { + if (detail) { + return error_response(400, kDetailStreamUnsupported, "invalid_request_error"); + } return run_transcription_stream(model, request, busy_timeout_ms); } - return run_transcription(model, request, busy_timeout_ms); + return run_transcription(model, request, busy_timeout_ms, detail); } HttpResponse ServerState::run_transcription( LoadedModel & model, const engine::runtime::TaskRequest & request, - std::optional busy_timeout_ms) { + std::optional busy_timeout_ms, + bool detail) { const auto timed_result = model_run_mode(model) == engine::runtime::RunMode::Streaming ? run_streaming_model(model, request, {}, busy_timeout_ms) : run_model(model, request, busy_timeout_ms); @@ -2610,9 +2643,31 @@ HttpResponse ServerState::run_transcription( if (!request.audio_input.has_value()) { throw std::runtime_error("transcription timing requires audio_input"); } - return json_response( - "{\"text\":" + json_quote(result.text_output->text) + - ",\"timing\":" + timing_json(timed_result.wall_ms, *request.audio_input) + "}"); + if (!detail) { + return json_response( + "{\"text\":" + json_quote(result.text_output->text) + + ",\"timing\":" + timing_json(timed_result.wall_ms, *request.audio_input) + "}"); + } + // Models that align words or separate speakers report them through the same + // detail fields /v1/tasks/run serialises, and the transcription response + // discards them. This is the opt-in route that keeps them, so the shape stays + // a superset of the plain one: text first, timing last, details in between. + std::ostringstream out; + out << "{\"text\":" << json_quote(result.text_output->text); + if (!result.text_output->language.empty()) { + out << ",\"language\":" << json_quote(result.text_output->language); + } + write_transcript_detail_fields(out, result, [&](const std::string & name) { + out << "," << json_quote(name) << ":"; + }); + // Detail spans are sample offsets, so the rate they are counted in has to + // travel with them or a client cannot turn them into timestamps. + if (!result.speech_segments.empty() || !result.speaker_turns.empty() || + !result.word_timestamps.empty()) { + out << ",\"sample_rate\":" << request.audio_input->sample_rate; + } + out << ",\"timing\":" << timing_json(timed_result.wall_ms, *request.audio_input) << "}"; + return json_response(out.str()); } HttpResponse ServerState::run_transcription_stream( diff --git a/app/server/runtime.h b/app/server/runtime.h index d2e2889e9..229b27be9 100644 --- a/app/server/runtime.h +++ b/app/server/runtime.h @@ -172,13 +172,17 @@ class ServerState final : public IHttpHandler { const engine::runtime::TaskRequest & request, const engine::io::json::Value & body); HttpResponse handle_speech_live(const HttpRequest & request); - HttpResponse handle_transcription(const HttpRequest & request); - HttpResponse handle_transcription_json(const std::string & body_text); - HttpResponse handle_transcription_multipart(const std::string & body_text, const std::string & boundary); + // detail selects the /v1/audio/transcriptions/details response, which adds the + // segment, speaker-turn and word arrays the plain route drops. + HttpResponse handle_transcription(const HttpRequest & request, bool detail = false); + HttpResponse handle_transcription_json(const std::string & body_text, bool detail = false); + HttpResponse handle_transcription_multipart( + const std::string & body_text, const std::string & boundary, bool detail = false); HttpResponse run_transcription( LoadedModel & model, const engine::runtime::TaskRequest & request, - std::optional busy_timeout_ms = std::nullopt); + std::optional busy_timeout_ms = std::nullopt, + bool detail = false); HttpResponse run_transcription_stream( LoadedModel & model, const engine::runtime::TaskRequest & request, diff --git a/docs/asr.md b/docs/asr.md index ce9c92817..ffafa7b08 100644 --- a/docs/asr.md +++ b/docs/asr.md @@ -329,6 +329,11 @@ chunking, server usage, and validation notes. VibeVoice ASR is an offline ASR model with greedy, sampling, and beam-search decode paths. It can return transcription text and structured segment/speaker-turn output when the model produces timestamps. +A fully quantized port of the same model — INT8 activations through the encoder, +ternary BitNet weights in the decoder — lives under community models as +`vibeasr`: see [VibeASR](community_models/vibeasr.md). It is not a separate +model, only a CPU-only alternative numeric pipeline for the same weights. + | Field | Value | |---|---| | Family | `vibevoice_asr` | diff --git a/docs/community_models/audio8_tts.md b/docs/community_models/audio8_tts.md index 845dbcb87..a3fa60f8f 100644 --- a/docs/community_models/audio8_tts.md +++ b/docs/community_models/audio8_tts.md @@ -4,7 +4,7 @@ Audio8 TTS Preview 0.6B (Qwen backbone) and 0.1B (Falcon-H1 hybrid Mamba2+attent S2 Pro: a slow semantic transformer generates speech semantics, a fast codebook transformer expands each semantic step into a full codec frame, and a neural codec renders 44.1 kHz audio. The native path executes all three stages directly on ggml with no Python dependency. -> **Status 2026-08-29:** `0.6B` Qwen is fully native, CPU-validated via SenseVoice ASR round-trip (`The quick brown fox…`, `你好,欢迎使用audio8。`, `Artificial intelligence…`). `0.1B` Falcon-H1 is weight-complete and builds natively (GGUF `slow.embed_tokens` + `24× mamba/attention` + `semantic_output`), but the slow AR forward is a documented stub pending the Mamba2 port — see `docs/FALCON_H1_0.1B_PORT_PLAN.md` and `src/community_models/audio8_tts/ar.cpp:861` `TODO(Falcon-H1)`. +> **Status 2026-09-01:** `0.6B` Qwen is fully native, CPU-validated via SenseVoice ASR round-trip (`The quick brown fox…`, `你好,欢迎使用audio8。`, `Artificial intelligence…`). `0.1B` Falcon-H1 now has a **stateful native slow-AR forward** (Mamba2 + hybrid GQA attention, branch `feat/audio8-tts-falcon-h1-mamba2`), but it is **not yet correct for synthesis** — two open issues (logits argmax mismatch vs transformers reference, and recurrent SSM state blow-up on long sequences). See [audio8_tts_falcon_h1_status.md](audio8_tts_falcon_h1_status.md) for details and next steps. | Field | Value | |---|---| diff --git a/docs/community_models/audio8_tts_falcon_h1_status.md b/docs/community_models/audio8_tts_falcon_h1_status.md new file mode 100644 index 000000000..10c9a54c0 --- /dev/null +++ b/docs/community_models/audio8_tts_falcon_h1_status.md @@ -0,0 +1,116 @@ +# Audio8 TTS 0.1B (Falcon-H1) — Port Status + +> **Status 2026-09-01 (resolved):** The Falcon-H1 slow-AR path is a **stateful +> native implementation** (Mamba2 + hybrid GQA attention) and now matches the +> transformers reference: first-frame semantic argmax = 2732, per-step argmax +> parity over the whole prompt, ASR round-trip of synthesized "你好" returns +> "你好。", and long generation (~600 positions) is numerically stable. The +> 0.6B Qwen path is unaffected and fully functional. + +## What has been done + +Branch `feat/audio8-tts-falcon-h1-mamba2`. + +- `src/community_models/audio8_tts/ar.cpp` + - `FalconH1StepState` + `init_falcon_step_state`: per-layer conv/SSM states + and attention KV cache. + - `falcon_forward_step`: stateful single-token forward — + `RMSNorm -> (Mamba2 || GQA attention) -> residual -> gated FFN -> RMSNorm + -> semantic_output`, matching `transformers.models.falcon_h1`. + - `build_falcon_embedding_step`: `(text_emb + codebook_sum) * + embedding_multiplier` (multiplier applies to the whole sum). + - `generate()` falcon branch: token-by-token prefill + generation. +- `src/community_models/audio8_tts/falcon_kv_cache.h` + - `append_falcon_kv_token`: host KV-cache append with per-head re-stride + (see "Root causes" below). Covered by `audio8_tts_falcon_kv_cache_test`. +- `external/ggml/src/ggml-metal/ggml-metal.metal` + - Fixed `kernel_ssm_scan_f32` reduction: the old + `simd_sum(shared_sums[sgitg*NW + tiisg])` read garbage columns when + `sgptg < NW` (happens for `d_state=64` with `n_t=1`). Replaced with an + explicit loop summing `shared_sums[(i2+sgitg)*NW + g]` over `g < sgptg`. + +## Resolved Issue 1 — logits argmax mismatch vs transformers + +**Symptom (before):** first generated semantic code was wrong (argmax 3620 +instead of 2732; ASR round-trip said "三星" instead of "你好"). + +**Root causes (three, all fixed):** + +1. **conv1d kernel flip was wrong.** `ggml_ssm_conv` computes + `y[c] = sum_k w[k,c]*window[k,c]` with `window[0]` the OLDEST frame — + the same orientation as HF (`nn.Conv1d` prefill and the cached + `torch.sum(conv_states * w, dim=-1)` decode are both cross-correlation). + The GGUF tensor `[d_conv,1,conv_dim]` is the HF `[conv_dim,1,d_conv]` + weight with unchanged flat bytes, i.e. already in the layout ssm_conv + wants. An earlier "fix" that flipped the kernel taps corrupted the x/B/C + split every step. Fix: feed the kernel unflipped (`load_falcon_layer`, + `conv1d_kernel`). +2. **Unprotected host read-back of intermediate tensors.** `sx` (conv + window) and `k_r`/`v` (fresh K/V) are graph intermediates whose buffers + gallocr reuses; reading them back without `ggml_set_output` returned + garbage and corrupted conv state / KV cache every step. Fix: + `ggml_set_output` on exactly those three tensors per layer (pinning ~300 + tensors corrupts the whole graph — pin only what is read back). +3. **KV cache head-stride bug (the decisive one).** The host cache used the + *current* sequence length as the per-head stride while appending only the + new token: at step 1 the new head-0 token was written over token 0's + head-1 block, so every head past the first read corrupted context from + the second token on (head 0 was always correct, which masked the bug). + Fix: `append_falcon_kv_token` re-lays existing entries into the new + stride before appending. Regression test: + `tests/unittests/test_audio8_tts_falcon_kv_cache.cpp` (fails with the old + algorithm at the second append, passes after). + +**Verification:** bf16 GGUF vs f32 HF reference (`transformers==4.57.6`, +recurrent path forced for every token): per-layer conv/SSM/K/V states match +within bf16 rounding over the full 23-token prompt; argmax matches at every +prompt position except one knife-edge tie (ref top-2 margin 0.03 vs bf16 +logit noise 0.19). First frame: argmax 2732 (logit 26.524 vs ref 26.557). +End-to-end: synthesized "你好" transcribes back as "你好。" (Qwen3-ASR), on +both CPU and Metal backends. + +## Resolved Issue 2 — recurrent SSM state blow-up on long sequences + +**Symptom (before):** ~180 tokens in, per-layer states reached 1e15..1e18, +then logits went to zero / NaN. + +**Root cause:** not the recurrent scan itself — the corrupted KV cache +(Issue 1, cause 3) fed garbage attention output into the residual stream, +which drove `x`/`dt` of the Mamba2 branch into regime where the state +exploded. The f32 HF reference running the same recurrent math stays bounded +(states ~270 over the prompt), which ruled out the "inherent weak-decay" +theory previously recorded here. + +**Verification:** 140-character text → 525 generated frames (position 605): +max per-layer SSM state ≈ 1.1e3, zero NaN, clean EOS, and the audio +transcribes back to the input text verbatim. No chunked-scan or dt-clamp +mitigation was needed; HF's recurrent fallback does not apply the +`time_step_min/max` clamp either (`time_step_limit` is hardcoded +`(0.0, inf)` in `modeling_falcon_h1.py`). + +## Notes for the next agent + +- The parity harness used for the fix (an HF golden-dump script forcing the + recurrent path token-by-token, plus a differ) was session tooling and is not + committed. To rebuild it: run `modeling_arktts` under `transformers==4.57.6` + with each `layer.mamba.forward` replaced by the `use_precomputed_states` + recurrent branch, dump `cache.conv_states/ssm_states/key_cache` per step, + and diff against `state.ssm_states/conv_states/k_cache/v_cache` read back in + `falcon_forward_step` (same flat layouts: ssm `[s + 64d + 2048h]`, conv rows + oldest→newest, kv `d + 64*(t + T*h)`). +- `A_log` / `D` / `dt_bias` / `conv1d.weight` must load with + `assets::TensorStorageType::F32` (GGUF stores them quantized; `Native` + keeps the quantized type and `ggml_backend_tensor_get` then reads out of + bounds). +- The GGUF layout for `conv1d.weight` is `[d_conv, 1, conv_dim]`, which is + the HF `[conv_dim, 1, d_conv]` weight with unchanged flat bytes. Feed it to + `ggml_ssm_conv` **unflipped** (see Resolved Issue 1, cause 1). +- `ggml_set_output` on a graph intermediate pins its buffer so host + read-back is safe — but mass-pinning hundreds of tensors corrupts the + whole graph (all-zero logits). Pin only the tensors actually read back. +- Reference environment: `uv venv` + `uv pip install torch "transformers>=4.57,<5"`; + `transformers>=5` renames `FalconHybridMambaAttentionDynamicCache` and + breaks the 0.1B remote code. +- Known remaining gap: the fast-AR codebooks during generation mean the C++ + rollout cannot be compared token-by-token against a reference that feeds + zero codebook rows; parity was established over the prompt + first frame. diff --git a/docs/community_models/mira_tts.md b/docs/community_models/mira_tts.md new file mode 100644 index 000000000..b3dcedcce --- /dev/null +++ b/docs/community_models/mira_tts.md @@ -0,0 +1,78 @@ +# MiraTTS + +MiraTTS is an experimental community port of +[ysharma3501/MiraTTS](https://github.com/ysharma3501/MiraTTS), a zero-shot +voice-cloning text-to-speech model. The native path includes the Qwen2 speech +token generator, ECAPA-TDNN plus Perceiver reference encoder, finite-scalar +speaker tokenizer, conditional acoustic processor, and DAC waveform decoder. + +## Model and license + +The upstream checkpoint is +[YatharthS/MiraTTS](https://huggingface.co/YatharthS/MiraTTS). Its model card +declares `CC-BY-NC-SA-4.0`; this is a non-commercial, attribution, share-alike +license. Review that license before downloading or redistributing converted +weights. audio.cpp does not redistribute the checkpoint. + +No ready-to-run GGUF package is published yet, so MiraTTS intentionally has no +entry in the built-in download catalog. Convert a locally obtained upstream +checkpoint with: + +```bash +python tools/community_models/convert_mira_tts.py /path/to/MiraTTS /path/to/mira-native +audiocpp_gguf \ + --input language_model=/path/to/mira-native/language_model.safetensors \ + --input speaker_encoder=/path/to/mira-native/speaker_encoder.safetensors \ + --input processor=/path/to/mira-native/processor.safetensors \ + --input decoder=/path/to/mira-native/decoder.safetensors \ + --input upsampler=/path/to/mira-native/upsampler.safetensors \ + --output /path/to/mira-native/mira-tts.gguf --type bf16 \ + --family mira_tts --root /path/to/mira-native +``` + +Use BF16 for the first parity-oriented conversion. Converting the natively +BF16 Qwen backbone to F16 can overflow and produce non-finite logits. + +The converter also imports the official FastBiCodec and FlashSR component +checkpoints referenced by the upstream repository. Use `--help` to see its +component path overrides. + +## Run + +MiraTTS requires reference audio. The CLI voice-cloning request accepts the +converted model directory, target text, and a short clean reference WAV through +the normal audio.cpp TTS/clone arguments. Sampling defaults reproduce upstream: +temperature `0.8`, top-k `50`, top-p `0.95`, min-p `0.05`, and repetition +penalty `1.2`. + +The upstream pipeline decodes at 16 kHz and applies its learned FlashSR +upsampler. The native runtime executes both stages and returns 48 kHz audio. + +MiraTTS also exposes a streaming session. It splits long input at natural text +boundaries, reuses one encoded speaker identity for the whole request, and emits +each completed 48 kHz segment immediately. `text_chunk_size` controls the +maximum segment size (160 codepoints by default), while `text_chunk_mode` +selects the framework chunker. This is segment-level progressive synthesis; +the acoustic processor, DAC, and FlashSR still decode each segment as a unit. + +The session caches one encoded reference voice by default, so repeated requests +with the same audio do not rerun the speaker encoder. Increase the bounded cache +with `--session-option reference_cache_slots=`, or set it to `0` to disable +reference reuse. + +## Validation status + +- Official checkpoint conversion: validated. +- Native CUDA build: validated. +- Native CUDA smoke synthesis through all converted model components: validated. +- Segment-level streaming synthesis: validated through the native streaming + session and `/v1/audio/speech/live` route. +- Deterministic upstream comparison with identical speech/context tokens: + validated (48 kHz waveform correlation 0.99996, SNR 41.1 dB). +- End-to-end generation comparison: validated through matching tokenization, + identical 32-token speaker codes, and the first six greedy LM tokens. Later + autoregressive tokens can diverge between LMDeploy, Transformers, and the + native backend because of backend floating-point differences. + +Until end-to-end measurements are published, the family remains experimental +and is not advertised as an installable WebUI package. diff --git a/docs/community_models/models.md b/docs/community_models/models.md index 994556b52..7ae45310a 100644 --- a/docs/community_models/models.md +++ b/docs/community_models/models.md @@ -27,10 +27,14 @@ Practical expectations: | **mms_forced_aligner** | Align | nl (nld), en (eng); pre-romanized Latin | Community | [MMS-300M-1130 Forced Aligner](mms_forced_aligner.md) word-timestamp alignment from a wav2vec2 CTC checkpoint (safetensors or local GGUF) | | **minimax_h3** | Video, Music, TTS/Dialogue | auto | [@0xShug0](https://github.com/0xShug0) | [MiniMax-H3](minimax_h3.md) text-to-audio/video generation with Q4_K and optional INT8 ConvRot DiT | | **minimax_music3** | Music | auto | [@0xShug0](https://github.com/0xShug0) | [MiniMax Music 3](minimax_music3.md) text-to-music generation with lyrics conditioning | +| **mira_tts** | TTS, voice cloning | en | Mirek [@mirek190](https://github.com/mirek190) | [MiraTTS](mira_tts.md) experimental native Qwen2 + ECAPA/Perceiver zero-shot cloning; local conversion only (CC-BY-NC-SA-4.0 weights) | | **moss_tts_local** | TTS, voice cloning | auto, optional language hint | [@justinjohn0306](https://github.com/justinjohn0306) | [MOSS-TTS-Local Transformer v1.5](../models/moss_tts.md) support in the core model tree | | **outetts** | TTS, voice cloning | en, ar, zh, nl, fr, de, it, ja, ko, lt, ru, es, pt, be, bn, ka, hu, lv, fa, pl, sw, ta, uk | Mirek [@mirek190](https://github.com/mirek190) | [Llama-OuteTTS-1.0-1B](outetts.md) TTS and voice cloning support | +| **sopro_tts** | TTS, voice cloning | en, pt, fr, de | Community | [Sopro V2 Turbo](sopro_tts.md) 120M zero-shot cloning — SentencePiece text, style-prefix semantic LM over FSQ tokens, two-step rectified-flow acoustic DiT, Vocos ISTFT vocoder at 24 kHz; offline plus segment-level streaming | | **voxcpm1** | TTS, voice cloning | zh, en, ja, ko | Community | [VoxCPM1](voxcpm1.md) tokenizer-free 0.5B TTS with 16 kHz output, streaming, and continuation-mode voice cloning | | **parakeet_tdt** | ASR | auto, bg, cs, da, de, el, en, es, et, fi, fr, hr, hu, it, lt, lv, mt, nl, pl, pt, ro, ru, sk, sl, sv, uk | [@dleiferives](https://github.com/dleiferives) | [Parakeet-TDT 0.6B v3](parakeet_tdt.md) offline, long-form, and buffered-streaming ASR support | +| **sanotts** | TTS | en, vi, id | Community | [sanoTTS voice family](sanotts.md) seven voices from 294k to 2.27M parameters, FP32 offline synthesis | | **sense_asr** | ASR | auto, zh, en, yue, ja, ko, pt, ru, es, it, fr, de, nl, pl, tr, ar, hi, vi, th, id, ms, fa, nospeech | Jason Chen [@jasonchen31](https://github.com/jasonchen31), [@LauraGPT](https://github.com/LauraGPT) / FunASR | [SenseVoice-Small](sense_asr.md) offline/streaming SAN-M + CTC transcription with event/emotion/language tags and ITN | | **vietneu_tts** | TTS, voice cloning | vi, en | Phuoc [@phuocnguyen90](https://github.com/phuocnguyen90) | [VieNeu-TTS-v3-Turbo](vietneu_tts.md) TTS and voice cloning support | | **moss_voicegen** | Voice design | en, zh | Joost [@jrohde](https://github.com/jrohde) | [MOSS-VoiceGenerator](moss_voicegen.md) voice design from a written instruction, on the MOSS delay architecture | +| **vibeasr** | ASR | en | [@XsquirrelC](https://github.com/XsquirrelC) | [VibeASR](vibeasr.md) fully quantized port of [VibeASR.cpp](https://github.com/microsoft/VibeASR.cpp): the VibeVoice acoustic/semantic tokenizers on INT8 weights *and* INT8 activations through the fused `GGML_TYPE_I8_S` ops, feeding a ternary `GGML_TYPE_I2_S` BitNet Qwen2 decoder. Offline, CPU only | diff --git a/docs/community_models/sanotts.md b/docs/community_models/sanotts.md new file mode 100644 index 000000000..ff88f8071 --- /dev/null +++ b/docs/community_models/sanotts.md @@ -0,0 +1,129 @@ +# sanoTTS voice family + +`sanotts` provides native GGML inference for the +[sanoTTS](https://github.com/Ampixa/sanoTTS) voice family — very small +text-to-speech models, the smallest of which also runs on microcontrollers. +All packages download from Hugging Face +([ampixa/sanoTTS](https://huggingface.co/ampixa/sanoTTS) `gguf/`) as +standalone FP32 GGUFs with embedded model specs. Offline FP32 inference only. + +Two graphs share one family: + +- **nano** — duration student → contextual acoustic student → mel-100 → + noise-fed ConvNeXt-1D decoder → [log-magnitude | phase] head → inverse + STFT. 24 kHz. A seed picks one of many valid renderings. +- **piperlite** — duration student → contextual acoustic student → 192-channel + latent → 3-stage ConvTranspose1d decoder with dilated residual banks → + tanh waveform. 22.05 kHz. Fully deterministic (no seed). + +| Package | Voice | Graph | Params | Language | Notes | +|---|---|---|---:|---|---| +| `sanotts_heart_orig` | heart | nano | 2,272,145 | en | best quality of the nano pair | +| `sanotts_heart_nano_orig` | heart-nano | nano | 294,279 | en | microcontroller-class | +| `sanotts_amy_orig` | amy | piperlite | 1,454,284 | en | Piper-distilled | +| `sanotts_hfc_orig` | hfc | piperlite | 1,834,380 | en | largest piperlite voice | +| `sanotts_kristin_orig` | kristin | piperlite | 1,396,151 | en | carries a learned post filter | +| `sanotts_vi_orig` | vi | piperlite | 1,565,484 | vi | Vietnamese | +| `sanotts_id_orig` | id | piperlite | 1,562,124 | id | Indonesian | + +## Install + +Install eSpeak-ng and its voice data first. On Debian or Ubuntu: + +```bash +sudo apt install espeak-ng libespeak-ng1 +``` + +On macOS: + +```bash +brew install espeak-ng +``` + +Then install any package, e.g.: + +```bash +python3 tools/model_manager_v2.py install sanotts_heart_orig --models-root models +python3 tools/model_manager_v2.py install sanotts_amy_orig --models-root models +``` + +## Run + +```bash +audiocpp_cli --task tts --family sanotts \ + --model models/sanoTTS-heart-GGUF --backend cpu \ + --text "Hello from sano T T S, a very small neural text to speech model." \ + --out sanotts.wav +``` + +Swap `--model` for any installed package directory +(`models/sanoTTS-amy-GGUF`, `models/sanoTTS-vi-GGUF`, ...). The Vietnamese +and Indonesian voices accept `--language vi` / `--language id`; a session +rejects text tagged with a language the voice was not trained on. + +eSpeak-ng is loaded dynamically at runtime, never linked. If it is not on the +default library path: + +```bash +audiocpp_cli --task tts --family sanotts \ + --model models/sanoTTS-heart-GGUF --backend cpu \ + --session-option sanotts.espeak_library_path=/path/to/libespeak-ng.so \ + --session-option sanotts.espeak_data_path=/path/to/espeak-ng-data \ + --text "A configured eSpeak installation." --out sanotts.wav +``` + +## Options + +- `speaking_rate` (request, 0.5..2.0, default 1.0) — duration multiplier on + the voice's tuned length scale; larger is slower. +- `seed` (request, default 0) — nano voices only: the decoder is noise-fed, + so a given seed picks one of many valid renderings. `0` derives the seed + from each text chunk as `sha256(text)[:8]`, which is what the reference + implementations do; an explicit seed advances by one per long-form chunk. + Piperlite voices are deterministic and ignore the seed. +- `text_chunk_size` (request, default 280) — maximum codepoints per long-form + chunk; chunks split on sentence punctuation first, and a chunk that + phonemizes past the voice's token limit is bisected at whitespace. + +## Determinism and parity + +The runtimes reproduce the reference implementations' exact semantics: + +- Front ends: the phonemizer punctuation-preservation pipeline through the + same eSpeak-ng library. The nano voices add the misaki E2M rewrite with + tie characters; the piperlite voices use Piper's NFD-decompose-to- + codepoints convention, per-voice `phoneme_id_map`, `[BOS, PAD, (id, PAD)…, + EOS]` framing, and the schwa fallback for ids outside a component's + trained vocabulary. +- nano: ATen-compatible MT19937 noise (24-bit uniform, Box–Muller in blocks + of 16), torch.istft window normalisation and centre trim, and the + reference's DC blocker `H(z) = (1 - z^-1)/(1 - 0.9973 z^-1)`. +- Shared: torch.linspace / expand_features float behaviour, LayerNorm eps + 1e-6 (nano), ties-to-even duration rounding. + +Measured against the project's numpy references (same text, same +eSpeak-ng build), every voice: **correlation ≥ 0.99999996 with identical +sample counts**; max sample delta ~1.7e-05 is the WAV's own int16 +quantisation. The numpy references are themselves gated ≥ 0.987 against the +float PyTorch models. + +## Performance + +CPU-only, 12-thread x86 (default 4 backend threads), FP32, the shared 6 kB +long-form text: + +| Voice | Audio | Wall | vs real time | Peak RSS | +|---|---:|---:|---:|---:| +| heart-nano | 373 s | 1.3 s | ~283× | 220 MB | +| amy | 394 s | 18.5 s | ~21× | 497 MB | + +The nano decoder runs at frame rate with a host iSTFT; the piperlite decoder +runs convolutions at audio rate, which is why it is heavier. Graphs are +cached per token count (duration and token stages) and per frame count +(decoder); `--log` prints cache hits and per-stage timings. + +## Licensing + +The sanoTTS runtimes and weights are MIT-licensed. eSpeak-ng is GPL-3.0 and +is therefore opened with `dlopen` at runtime and never linked, matching how +`inflect_v2` treats it. diff --git a/docs/community_models/sopro_tts.md b/docs/community_models/sopro_tts.md new file mode 100644 index 000000000..857ba2abe --- /dev/null +++ b/docs/community_models/sopro_tts.md @@ -0,0 +1,247 @@ +# Sopro V2 Turbo (`sopro_tts`) + +[samuel-vitorino/sopro-v2-turbo](https://huggingface.co/samuel-vitorino/sopro-v2-turbo) is a +120M-parameter zero-shot voice-cloning TTS covering English, European Portuguese, French and +German, released under Apache-2.0. It clones from 5–20 s of reference audio and outputs +24 kHz mono. + +> **Not the same model as `soprano_tts`.** audio.cpp's existing `soprano_tts` family is +> [ekwek/Soprano-1.1-80M](https://huggingface.co/WalkingCat/Soprano-1.1-80M-GGUF), an unrelated +> project with a Qwen3 backbone and a different decoder. Sopro V2 Turbo shares nothing with it +> beyond a similar name, so it ships as its own family. The `--family` hints `sopro`, +> `sopro_v2` and `sopro_v2_turbo` all resolve to `sopro_tts`. + +## Installation + +The upstream safetensors checkpoint runs directly — no conversion step: + +```bash +python3 tools/model_manager_v2.py install sopro_v2_turbo_safetensors +# -> models/sopro-v2-turbo/{config.json,tokenizer.model,*.safetensors} +``` + +To run from GGUF instead, pack the four stages into one file. `audiocpp_gguf` takes one +namespaced input per stage, and `--root` makes it embed `config.json` and `tokenizer.model` as +sidecars, so the resulting `.gguf` is self-contained: + +```bash +build/bin/audiocpp_gguf \ + --input model=models/sopro-v2-turbo/model.safetensors \ + --input semantic_encoder=models/sopro-v2-turbo/semantic_encoder.safetensors \ + --input speaker_encoder=models/sopro-v2-turbo/speaker_encoder.safetensors \ + --input vocoder=models/sopro-v2-turbo/vocoder.safetensors \ + --family sopro_tts --root models/sopro-v2-turbo \ + --output models/sopro-v2-turbo-GGUF/sopro-v2-turbo-f16.gguf --type f16 +``` + +No public audio.cpp GGUF build of this family is published yet, so the spec's default +`sopro_v2_turbo_f16` package has `download.kind = "unsupported"` and expects the file above to +be produced locally. + +## Build + +```bash +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ + -DAUDIOCPP_MODEL_SET=custom -DAUDIOCPP_MODELS=sopro_tts +cmake --build build --target audiocpp_cli -j"$(nproc)" +``` + +## Run + +Zero-shot cloning always needs a reference clip: + +```bash +build/bin/audiocpp_cli \ + --task tts --family sopro_tts \ + --model models/sopro-v2-turbo \ + --backend cpu --threads 8 \ + --text "Sopro is a lightweight text-to-speech model that runs on device." \ + --voice-ref ref.wav \ + --request-option language=en \ + --out out.wav --metrics +``` + +`--task clon` works the same way. The reference is resampled to 24 kHz, cropped at a pause near +`ref_seconds`, and level-normalised before the speaker and semantic encoders see it. That +normalisation is boost-only and peak-guarded (sopro 2.1): a reference already at or above the +−19.8 dB prompt level is passed through untouched, and a boost is never large enough to push +the peak past 0.95. The level the reference ends up at is what the output gain falls back to +when the generated audio is too short to measure. + +## Streaming + +`--mode streaming` emits one pull event per text segment instead of one buffer at the end: + +```bash +build/bin/audiocpp_cli \ + --task tts --family sopro_tts \ + --model models/sopro-v2-turbo \ + --backend cpu --threads 8 --mode streaming \ + --text "$(cat article.txt)" \ + --voice-ref ref.wav --language en \ + --text-chunk-size 120 \ + --out stream.wav --out-dir segments/ +``` + +Each event carries a `segment_` named audio buffer that is already levelled, trimmed and +faded, so a consumer can play events back to back; `--out` still writes the whole utterance, +and it is exactly the concatenation of the events. The reference voice is encoded once in +`start_stream`, so every event after the first costs only its own LM, solver and vocoder pass. + +**Granularity is one text segment, not one frame.** The acoustic DiT and the Vocos vocoder both +see a whole span at once, and this checkpoint ships no causal vocoder, so a segment is the +smallest unit that can leave without boundary artefacts. `text_chunk_size` is the latency dial: +on a 16-core CPU build at 8 threads, 8 solver steps and a 14 s reference, `text_chunk_size=120` +put the first audio out at ~3.1 s for a 6.7 s segment, against ~9.9 s for the same text offline. + +Two things to know before turning it down further: + +- Every segment re-solves the *whole* reference mel prompt alongside its own frames, so the + per-segment cost has a floor of roughly `ref_seconds` worth of DiT work. Segments shorter + than about 1 s take longer to generate than to play, even though the stream as a whole stays + ahead of real time (`text_chunk_size=40` measured 0.75 RTF overall, with the shortest + segment at 2.1). Lowering `ref_seconds` shrinks that floor at some cost to cloning fidelity. +- Streaming reproduces the offline waveform for the same `seed`, sample count included, with + one deliberate exception: offline level-matches over the finished utterance, which a stream + cannot see, so the first segment fixes the gain for the rest. The measured difference is a + constant scale factor (1.15x, +1.2 dB, on the clip above) with a −47 dB residual. + +## Options + +| Request option | Type | Default | Meaning | +|---|---|---|---| +| `language` | string | *(empty)* | Prepends `<\|lang_xx\|>`; one of `en`, `pt`, `fr`, `de`. Optional, helps on ambiguous text | +| `temperature` | float | 0.8 | Semantic LM sampling temperature; `0` selects arg-max | +| `top_p` | float | 0.9 | Nucleus threshold, applied after top-k renormalisation | +| `top_k` | int | 25 | Top-k truncation; `0` disables | +| `num_inference_steps` | int | 2 | Acoustic rectified-flow Euler steps | +| `max_seconds` | float | 30.0 | Audio cap per segment; long text is split, so total length is unbounded | +| `min_seconds` | float | 0.4 | Minimum audio before the semantic LM may emit EOS | +| `ref_seconds` | float | 10.0 | Reference window used for cloning | +| `text_chunk_size` | int | 300 | Max codepoints per synthesis segment | +| `seed` | int | *(random)* | Seeds semantic sampling and the acoustic noise prior | + +| Session option | Default | Meaning | +|---|---|---| +| `sopro_tts.language` | *(empty)* | Default language tag for requests that do not set one | + +| Load option | Default | Meaning | +|---|---|---| +| `sopro_tts.matmul_weight_type` | `f32` | Storage type for matmul weights (`native`, `f32`, `f16`, `bf16`, `q8_0`) | +| `sopro_tts.conv_weight_type` | `f32` | Storage type for convolution weights (`native`, `f32`, `f16`) | + +Every default comes from the checkpoint's `config.json` `generation` block, so a retrained +variant picks up its own values without a code change. + +## Architecture notes + +Five stages run per request, mirroring `sopro/` upstream: + +1. **Text** (`text_tokenizer.cpp`) — SentencePiece unigram, 8192 pieces, plus the reference's + punctuation clean-up and sentence/clause/word segmentation. No phonemiser. +2. **Speaker encoder** (`speaker_encoder.cpp`, ~11M) — 16 kHz log-mel into a three-stage gated + depthwise ResNet with squeeze-excite, then attentive-statistics pooling for identity and + multi-scale mean/std pooling for style. The convolution trunk runs on the backend; the two + pooling heads and their MLPs run on the host, where they cost a few hundred kFLOP. +3. **Semantic encoder** (`semantic_encoder.cpp`, ~82M) — a Whisper-style front end and six + non-causal transformer layers, resampled to one frame per 1024 output samples and quantised + by an FSQ head with levels `[7,5,5,5,5]` (4375 codes). +4. **Semantic LM** (`semantic_lm.cpp`) — 12 pre-norm blocks, dim 512, QK RMS-norm, SwiGLU, + half-rotation RoPE. The prompt is `[style prefix | text | carried tokens | BOS]`. Because the + only structural difference from a Qwen3 decoder is a LayerScale vector on each residual + branch, and those branches end in a bias-free projection, the scale is folded into that + projection's rows at load time and the shared `QwenCausalDecodeRuntime` runs the stack + unmodified. The eight-query style prefix cross-attention runs on the host. +5. **Acoustic head + vocoder** (`acoustic.cpp`, `vocoder.cpp`) — an 8-block adaptive-layer-norm + DiT solving a rectified flow in two Euler steps on a sway-sampled time grid, with the prompt + mel re-pinned after every step; then a 14-layer Vocos ConvNeXt backbone and one centred + ISTFT. The ISTFT head is band-limited: bins at or above `vocoder.band_limit_hz` (10900 Hz by + default, as in sopro 2.1) are zeroed before the inverse transform, which removes the + high-frequency hiss the unlimited head produced. `mu` (the upsampled semantic conditioning) + is built in its own graph because it is constant across solver steps. + +Two implementation details worth knowing: + +- **Front-end buffers come from the checkpoint.** torchaudio stores its analysis window and mel + filterbank as persistent buffers, and all three front ends load those rather than rebuilding + the filterbank, which removes the usual mel-parity risk. A checkpoint exported without them + fails at load with a message naming the missing tensor. +- **Grouped convolutions are split.** The DiT's causal positional embedding uses + `Conv1d(512, 512, k=31, groups=16)`; ggml has no grouped conv1d, so the weight is split into + 16 independent convolutions at load time. +- **The velocity graph re-uploads every leaf per Euler step.** `ggml_gallocr` exempts only + `GGML_TENSOR_FLAG_OUTPUT` tensors from being freed and reused + (`ggml_gallocr_free_node` in `ggml-alloc.c`); an *input* leaf's arena space is handed to a + later intermediate once its last consumer has run. That is correct for a one-shot graph, but + the solver replays the velocity graph once per step, so staging `mu`, `cond_mel`, `cond_mask`, + `spk` and the RoPE positions once would leave the second and later steps reading whatever + overwrote them. `SoproAcousticGraphs::upload_constants` re-uploads all of them before every + compute; it costs a few hundred kB per step against a multi-GFLOP DiT pass. + +## Known limitations + +- **Streaming is segment-level, not frame-level.** The upstream frame-level path (chunked DiT + attention plus the causal vocoder, `vocoder_streaming.safetensors`) is not implemented, and + that vocoder is not part of the published checkpoint this family loads. What ships is one + pull event per text segment; see [Streaming](#streaming) for the latency it actually buys. +- **Sampling RNG is not torch-bit-exact.** `sample_next_token` reproduces the reference's + masking, temperature, top-k and top-p arithmetic exactly, but draws from a seeded + `std::mt19937_64` rather than torch's generator, so a given `seed` will not reproduce the + Python output sample-for-sample. The same `seed` is reproducible within audio.cpp. +- **No `int8` AR path.** The upstream `--int8` CPU option has no equivalent; use + `sopro_tts.matmul_weight_type=q8_0` instead. +- The text front end is deliberately minimal upstream: prefer words to symbols (`one plus two`, + not `1 + 2`), and avoid mixing languages inside one sentence. + +## Validation status + +Verified against the real checkpoint on a 16-core x86-64 CPU build, 8 threads. Every stage was +reimplemented independently in numpy, driven from the checkpoint's own weights, and diffed +against the C++. + +| Stage | Check | Result | +|---|---|---| +| Tensor inventory | 762 names + shapes vs. the four real files | exact match | +| Vocoder mel front end | vs. numpy STFT + checkpoint filterbank | max diff 1.9e-3 | +| Vocos backbone + ISTFT head | vs. numpy, all 14 blocks | max diff 1.0e-5 (measured before the band limit; the numpy reference does not zero the bins above 10900 Hz) | +| Semantic encoder mel | vs. numpy | max diff 1.9e-5 | +| Semantic encoder transformer | vs. numpy, all 6 layers | max diff 6.0e-5 | +| FSQ token ids | vs. numpy | 188/188 identical | +| Speaker encoder mel / trunk / heads | vs. numpy | max diff 3.7e-5 | +| Acoustic `mu`, `spk`, time embedding | vs. numpy | max diff 1.3e-5 | +| Acoustic velocity field, every Euler step | vs. numpy | max diff 5.8e-3 | +| Acoustic self-reconstruction | NMSE vs. the reference's own mel | 0.38 | +| Fixed `seed` reproducibility | byte-identical WAV across runs | pass | +| Streaming vs. offline, same `seed` | 22.5 s clip, 4 segments | identical sample count; a constant 1.15x gain, −47 dB residual | +| Streaming segment sum | segments vs. `--out` | exact | +| Long-form, 6026 chars | 371.6 s of audio, 48 segments | offline and streaming both complete; peak RSS 1.083 vs 1.086 GB | +| `matmul_weight_type` f16 / bf16 / q8_0 | runs clean | pass | +| Single-file GGUF | end to end | pass | + +Numeric parity against the upstream PyTorch implementation has still not been measured +directly; the numpy references above are independent reimplementations from the same source, +which catches implementation bugs but not a shared misreading of the architecture. + +### Debugging + +`tests/sopro_tts/sopro_probe.cpp` (built with `-DENGINE_BUILD_WARMBENCH=ON`) exercises each +stage in isolation against a reference clip: + +```bash +build/bin/sopro_probe models/sopro-v2-turbo reference.wav /tmp/soproprobe +``` + +It reports the `crop_on_pause` decision, a mel round trip through the vocoder (which is +phase-invariant and so the meaningful vocoder check), the FSQ token histogram, the speaker +embedding statistics, and an acoustic self-reconstruction NMSE. It also writes +`probe_vocoder_roundtrip.wav` — the reference passed through mel then the vocoder; if that +sounds like the speaker, the whole back half of the pipeline is fine. + +Setting `SOPRO_DUMP_DIR=` additionally dumps the encoder and solver intermediates as raw +f32 for diffing against a reference implementation. + +## References + +- Model card: +- Reference implementation: +- Blog post: diff --git a/docs/community_models/vibeasr.md b/docs/community_models/vibeasr.md new file mode 100644 index 000000000..c97a142a2 --- /dev/null +++ b/docs/community_models/vibeasr.md @@ -0,0 +1,364 @@ +# VibeASR in audio.cpp + +[VibeASR.cpp](https://github.com/microsoft/VibeASR.cpp) is Microsoft's CPU-first +port of the VibeVoice ASR stack, quantized end to end for edge inference: the +audio VAE encoder runs on INT8 weights *and* INT8 activations, and the Qwen2 +decoder runs on BitNet-style ternary weights. This entry ports both halves, so +`--family vibeasr` transcribes end to end on CPU. + +## Relation to the existing `vibevoice_asr` family + +audio.cpp already ships [VibeVoice ASR](../asr.md#vibevoice-asr) in the core model +tree, and it is the same model: the same acoustic/semantic causal ConvNeXt +tokenizers, the same connectors, the same Qwen2 decoder. That family runs F32 / +Q8_0 weights through the generic ggml ops. + +What VibeASR.cpp adds is a different *numeric pipeline* for that architecture, +not a different architecture: + +| | `vibevoice_asr` (core) | this entry | +|---|---|---| +| Encoder weights | F32 / Q8_0 | `GGML_TYPE_I8_S`, one F32 scale per tensor | +| Encoder activations | F32 | INT8 throughout; every stage requantizes | +| Ops | generic ggml | the five fused I8_S ops (`ggml_mul_mat_add`, `ggml_mul_mat_add_relu`, `ggml_add_scaled`, `ggml_rms_norm_scaled`, `ggml_im2col_asym`) | +| Decoder weights | Q8_0 Qwen2 | ternary `GGML_TYPE_I2_S`, 993 MB for a 1.5B decoder | +| Backends | CPU, CUDA, Metal | CPU only — the I8_S and I2_S kernels have no GPU variants | +| Decode | greedy, sampling, beam search | greedy | +| Output | text, segments, speaker turns | text | + +Both are offline-only. + +So this is an alternative execution path for weights that were quantized +upstream, useful where the INT8/ternary package is the point: no F32 activations +anywhere, integer dot products, and a decoder that fits in under 1 GB. + +It stays a separate community entry rather than becoming a weight path inside +`vibevoice_asr`, because the two share no encoder graph code: every activation +there is I8_S and every node is one of the fused CPU-only ops, so folding it in +would put a second, mutually exclusive graph builder and a second backend policy +behind one family's loader. The reuse that is worth having — tokenizer +vocabulary, prompt layout, feature-injection order, audio normalization — is data +and conventions, and this entry follows `vibevoice_asr` on all of it. The decoder +half needs no new graph code at all: it is +`modules::QwenCausalDecoderModule` unchanged, because every projection goes +through `LinearModule`'s plain `ggml_mul_mat`, which dispatches on the weight +type. + +## Architecture + +### Encoder + +Both branches are identical in shape and differ only in latent width: + +- **Input**: mono 24 kHz waveform in `[-1, 1]`, quantized to a single I8_S + tensor (one scale for the whole waveform, `amax` floored at 1e-5 to match + upstream). +- **7 stages**, strides `{1, 2, 2, 4, 5, 5, 8}` (upstream `encoder_ratios` + `[8, 5, 5, 4, 2, 2]` reversed, with a stride-1 stem), so **3200 samples per + frame** — 7.5 frames per second at 24 kHz. Channels `32 → 64 → 128 → 256 → + 512 → 1024 → 2048`, depths `3-3-3-3-3-3-8`. +- Each stage starts with a **strided causal conv** (left pad `K - stride`, right + pad 0) and then runs its ConvNeXt-style blocks: RMSNorm → depthwise conv → + layer scale → residual → RMSNorm → FC1 → ReLU → FC2 → layer scale → residual. +- **Latent head**: causal conv to `vae_dim` — 64 acoustic, 128 semantic. +- **Connector**: `FC1 → RMSNorm → FC2`, both 1536 wide, i.e. the decoder hidden + size. Output is `[frames][1536]` for each branch. + +Two details the port copies rather than corrects: + +- RMSNorm epsilon is **1e-5 everywhere**, including the norms the checkpoint + metadata labels 1e-6. Upstream hardcodes it and the published weights were + validated that way. +- The converter left-pads the 7-tap depthwise kernels with leading zeros up to a + SIMD-friendly width. Convolving with the padded width and a matching causal + left pad is bit-exact with convolving the unpadded kernel, so the geometry is + read back from the weight shapes rather than from metadata. + +The encoder geometry is derived from the tensor table (which block tensors +exist, what shape each weight has), not from GGUF KV metadata — the same +approach upstream takes, and it keeps the loader working for any checkpoint with +this topology. + +### Decoder + +A stock Qwen2 causal decoder, geometry read from the LM GGUF's KV block: 28 +layers, hidden 1536, intermediate 8960, 12 heads over 2 KV heads, head_dim 128, +RMSNorm eps 1e-6, RoPE theta 1e6, context 65536. The checkpoint has no +`qwen2.attention.key_length`, so `head_dim` comes from +`qwen2.rope.dimension_count`, which for this model equals +`embedding_length / head_count`; the loader cross-checks +`head_dim * head_count == embedding_length` and validates the declared geometry +against `token_embd.weight`'s shape. + +Weight types are mixed on purpose, exactly as published: + +| Tensors | Type | +|---|---| +| `blk.N.{attn_q,attn_k,attn_v,attn_output,ffn_gate,ffn_up,ffn_down}.weight` | `I2_S` (ternary) | +| `token_embd.weight` | Q6_K | +| `output.weight` | F16 | +| norms and `blk.N.attn_{q,k,v}.bias` | F32 | + +`I2_S` packs `{-1, 0, +1}` as codes `{0, 1, 2}`, 128 values per 32-byte group, +over the whole flat tensor, with one F32 absmax scale after the payload. The +kernel asserts `ne00 % 128 == 0`; hidden 1536 and intermediate 8960 both satisfy +it, and the weight is always 2-D by the time `LinearModule` calls +`ggml_mul_mat`. + +### Prompt + +Qwen2.5 ChatML, assembled to match `VibeASR.cpp/utils/prompt_builder.h` token for +token: + +``` +<|im_start|>system\nYou are a helpful assistant that transcribes audio input into text output in JSON format.<|im_end|>\n +<|im_start|>user\n<|speech_start|><|speech_pad|>×N<|speech_end|>\nThis is a 3.50 seconds audio, please transcribe it.<|im_end|>\n +``` + +- The special tokens are inserted by numeric id (151643–151648), not through the + tokenizer, because the GGUF vocabulary still carries Qwen2.5's original text + for those slots while the embedding rows are the ones VibeVoice trained. Every + text segment is tokenized with `parse_special = false`. +- There is deliberately **no generation prompt**: the model emits its own + `<|im_start|>assistant\n` header, and the session strips that leading triple + before decoding, as upstream does. +- `N` is the encoder frame count. Upstream builds `ceil(samples / 3200)` pads but + prefills only `min(pads, frames)` of them, so emitting exactly `frames` pads + produces the same sequence. +- The `<|speech_pad|>` rows are replaced in-graph by a `ggml_set_rows` over the + embedding lookup, with the speech features being the **element-wise sum** of + the acoustic and semantic connector outputs — both are 1536 wide, which is what + makes the sum well-defined. +- `output_format=json` swaps the instruction for `please transcribe it with these + keys: Start, End, Speaker, Content`; `context=...` switches to the + `with extra info:` suffix variant. + +Decoding is greedy, stopping at `<|im_end|>` or `<|endoftext|>`. Upstream's +default is temperature 0.7 / top-p 0.9 sampling with `--greedy` as an opt-in; +this port only implements the deterministic path, which is what parity is +measured against. + +### Audio front end + +Mixdown to mono, resample to 24 kHz, RMS-normalize to −25 dBFS with `eps = 1e-6`, +then divide by `max_abs` if it exceeded 1.0. This is audio.cpp's own +`vibevoice_asr` front end, not upstream's: VibeASR.cpp resamples with a naive +linear kernel and omits the clamp. For a clip already at 24 kHz the two agree; +for anything else the resampler differs and so do the encoder features (see +[Parity](#parity)). + +## Usage + +VibeASR.cpp already ships both halves quantized, so there is nothing to +re-quantize. The two forks only disagree on the numeric type *ids* — the VibeASR +fork put I2_S/I8_S at 36/37, which upstream ggml had already spent on the retired +`IQ4_NL_4_4` / `IQ4_NL_4_8` slots, so audio.cpp registers them at 43/42. The +converter rewrites the 4-byte type field in each tensor info and copies +everything else through byte for byte: + +```bash +# inspect first +python3 tools/community_models/convert_vibeasr_gguf.py \ + --input vibeasr-vae-encoder-i8_s.gguf --list + +# fix both GGUFs in place (703 MB encoder, 993 MB decoder) +python3 tools/community_models/convert_vibeasr_gguf.py \ + --input models/vibeasr/vibeasr-vae-encoder-i8_s.gguf --in-place +python3 tools/community_models/convert_vibeasr_gguf.py \ + --input models/vibeasr/vibeasr-lm-i2_s-embed-q6_k.gguf --in-place + +# confirm an already-converted package needs no further remapping +python3 tools/community_models/convert_vibeasr_gguf.py \ + --input models/vibeasr/vibeasr-lm-i2_s-embed-q6_k.gguf --check +``` + +Use `--output ` instead of `--in-place` to keep the original. + +The package is two GGUFs plus the tokenizer, so `--model` points at the LM GGUF +and the spec is resolved from the repo — the same invocation shape as +[`minimax_h3`](minimax_h3.md): + +``` +models/vibeasr/ +├── vibeasr-vae-encoder-i8_s.gguf +├── vibeasr-lm-i2_s-embed-q6_k.gguf +├── tokenizer.json +└── tokenizer_config.json +``` + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release +cmake --build build -j --target audiocpp_cli + +./build/bin/audiocpp_cli \ + --task asr \ + --family vibeasr \ + --model models/vibeasr/vibeasr-lm-i2_s-embed-q6_k.gguf \ + --model-spec-override model_specs \ + --backend cpu \ + --threads 8 \ + --audio assets/asr_validation/librispeech/librispeech_test_clean_6930-75918-0000.wav \ + --metrics +``` + +``` +text_output=Concord returned to its place amidst the tents. +metrics.wall_ms=1284.65 +metrics.rtf=0.36652 +``` + +Request options: `output_format` (`text` | `json`), `context` (a string folded +into the prompt to bias recognition), `max_new_tokens` (default 1024). Session +options: `vibeasr.encoder_graph_arena_mb` (64), +`vibeasr.prefill_graph_arena_mb` (256), `vibeasr.decode_graph_arena_mb` (256). + +Note that `output_format=json` returns an empty transcript on short +single-speaker clips — the model emits an immediate end-of-turn. VibeASR.cpp +behaves identically on the same input; this port does not paper over it. + +## Tests + +```bash +cmake -B build -DCMAKE_BUILD_TYPE=Release -DENGINE_BUILD_MODEL_TESTS=ON +cmake --build build -j --target test_vibeasr_asr test_vibeasr_vae_encoder + +# end to end: loader, session, prompt, both graphs, greedy decode +./build/bin/test_vibeasr_asr --threads 8 + +# encoder only: shape, finiteness, frame count, and optional upstream parity +./build/bin/test_vibeasr_vae_encoder \ + --model models/vibeasr/vibeasr-vae-encoder-i8_s.gguf \ + --audio assets/asr_validation/librispeech/librispeech_test_clean_6930-75918-0000.wav \ + --threads 8 +``` + +Both exit 125 (SKIP) when the checkpoint is missing, so they are safe in ctest. +`i2_s_mul_mat_test` and `i8_s_fused_ops_test` cover the kernels themselves +against plain-loop references and need no checkpoint. + +## Parity + +### End to end + +Four LibriSpeech clips, greedy on both sides, against VibeASR.cpp's own +`asr_infer --greedy` on the same two GGUFs: + +| Clip | VibeASR.cpp | this port | +|---|---|---| +| test-clean 6930-75918-0000 | `Concord returned to its place amidst the tents.` | identical | +| test-clean 6930-75918-0001 | `The english forwarded to the french baskets of flowers, of which they had made a plentiful provision to greet the arrival of the young princess. The french, in return, invited the english to a supper, which was to be given the next day.` | identical | +| test-other 7902-96591-0001 | `Don't cry, he said. I was obliged to come.` | identical | +| test-other 7902-96591-0000 | `I'm from the cut or lying off the coast.` | `I'm from the cutter lying off the coast.` | + +Three of four match token for token. The fourth diverges because these clips are +16 kHz and the two resamplers differ — this port uses soxr, upstream uses naive +linear interpolation — which perturbs the encoder features enough to flip one +greedy argmax. (Reference text: `I AM FROM THE CUTTER LYING OFF THE COAST`.) A +clip already at 24 kHz skips resampling entirely and does not have this failure +mode. + +### Encoder + +The reference dump is raw F32, `frames * dim`, row-major, produced by calling +`vae_encode_acoustic` / `vae_encode_semantic` from VibeASR.cpp's own `vae.h` on +the same WAV: + +```bash +./build/bin/test_vibeasr_vae_encoder \ + --model models/vibeasr/vibeasr-vae-encoder-i8_s.gguf \ + --audio assets/asr_validation/librispeech/librispeech_test_clean_6930-75918-0000.wav \ + --reference-acoustic ref_acoustic.f32 \ + --reference-semantic ref_semantic.f32 \ + --threads 8 +``` + +3.505 s LibriSpeech clip fed at its native 16 kHz, 17 frames × 1536 per branch: + +| Branch | max abs | mean abs | cosine | +|---|---|---|---| +| acoustic | 1.478 (12.1% of range) | 0.0930 (0.76% of range) | 0.99238739 | +| semantic | 2.526 (9.5% of range) | 0.1804 (0.68% of range) | 0.98475210 | + +**Layer by layer, stage 0 is bit-exact** — every int8 byte and every scale +matches, which is what pins the layouts, the causal padding, the kernel padding, +and the weight mapping. The first divergence is 5 of 1,794,560 elements one int8 +step apart at an identical scale, entering stage 1, and it grows from there +because each of the remaining stages requantizes. + +Bit-exactness is not reachable and the tolerances say so. audio.cpp stores each +per-tensor scale as a multiplier (`amax/127`, dequantize by multiplying) while +VibeASR.cpp stores its reciprocal (`127/amax`, dequantize by dividing) — the +same number to within the last float bit, which is enough to flip a value that +sits on a rounding boundary. Upstream also rounds ties to even in its vector +body but away from zero in its scalar tail, so no single convention reproduces it +exactly. + +To calibrate what that is worth, nudging **one** input sample by one int8 step +and re-running VibeASR.cpp against *itself* moves its own output by cosine +0.99592 (acoustic) / 0.98700 (semantic) — the graph amplifies a single LSB about +as far as the two implementations differ from each other. The probe therefore +gates on mean-abs-relative ≤ 2% and cosine ≥ 0.98; anything tighter would be +testing rounding luck. + +`i8_s_fused_ops_test` covers the op arithmetic itself against plain-loop +references, including the in-band scale surviving `ggml_cont(ggml_permute(...))` +— the encoder flips activations between channel-major and length-major +constantly, and a byte copy that drops the scale leaves the values right and +everything downstream off by an arbitrary factor. + +## Measured performance + +Release build, CPU backend, 24 vCPU AMD EPYC 7V13, 3.505 s clip resampled to +24 kHz (26 speech frames, 72-token prompt, 13 generated tokens): + +| Threads | encoder (both branches) | prefill | decode | wall | RTF | +|---|---|---|---|---|---| +| 8 | 758 ms | 214 ms | 239 ms | 1285 ms | 0.367 | +| 1 | 4652 ms | 1415 ms | 941 ms | 7081 ms | 2.020 | + +The encoder dominates: it is run twice, once per branch, and it processes raw +samples rather than tokens. Decode is about 18 ms/token at 8 threads. + +The encoder-only probe reports 546 ms for both branches at 8 threads because it +feeds the clip at its native 16 kHz (17 frames); the session resamples to 24 kHz +first (26 frames). + +Peak RSS is 2.20 GB against 1.70 GB of weights: `BackendWeightStore` stages each +tensor before upload, so weight loading briefly holds roughly two copies of the +tensor being uploaded. Graph arenas are 64 MB (encoder) + 256 MB (prefill) + +256 MB (decode) by default. + +## Status + +Ported: + +- I8_S VAE encoder graph, both branches, CPU backend. +- Ternary I2_S matmul kernel and the Qwen2 decoder graph on top of it, with + prefill + static-cache single-step decode. +- Prompt assembly, speech-feature injection, greedy decode, tokenizer, loader, + session, and `--family vibeasr`. +- GGUF type remapping tool and geometry-from-tensors asset loader. +- End-to-end and encoder parity probes against upstream, plus op-level unit + tests. + +Known limitations: + +- **CPU only.** The fused I8_S ops and the I2_S matmul have no CUDA or Metal + kernels; the session pins the backend to CPU. +- **Offline only**, like `vibevoice_asr` itself. Upstream's encoder is causal, so + streaming is implementable, but the state machine is not ported. +- **Greedy only.** Upstream's sampling path (temperature, top-p) is not ported, + and neither is `vibevoice_asr`'s beam search. +- **Text only.** No `--segments-out` / `--turns-out` equivalent; `output_format=json` + is a prompt variant, not structured decoding. +- The package is two GGUFs, so it needs `--model ` plus + `--model-spec-override model_specs` rather than a directory path. +- Bit-exact parity with upstream is out of reach by design; see + [Parity](#parity). + +## Upstream + +- Model port: (`src/vae.cpp`, + `src/lm.cpp`, `src/asr_server.cpp`, `utils/prompt_builder.h`) +- Base model: VibeVoice ASR, also in tree as [`vibevoice_asr`](../asr.md#vibevoice-asr) +- Weights: diff --git a/docs/docker.md b/docs/docker.md index 080407fb6..072ca7ead 100644 --- a/docs/docker.md +++ b/docs/docker.md @@ -14,6 +14,9 @@ - Docker must be installed and running on your system. - For CUDA: - The [NVIDIA container toolkit](https://github.com/NVIDIA/nvidia-container-toolkit) must be installed. +- For Vulkan: + - The host must expose a working Vulkan device to Docker, typically through `/dev/dri` on Linux. + - The container user needs access to the host render/video device groups. ## Image Variants @@ -24,6 +27,7 @@ The following image variants are available: The following backends are supported: - **cuda12** - **cuda13** +- **vulkan** - **cpu** The following architectures are supported: @@ -38,6 +42,7 @@ as multiarch images (amd64/arm64). Pull the latest images using these tags: - **cuda12**: `ghcr.io/0xshug0/audio.cpp:full-cuda12` - **cuda13**: `ghcr.io/0xshug0/audio.cpp:full-cuda13` +- **vulkan**: `ghcr.io/0xshug0/audio.cpp:full-vulkan` - **cpu**: `ghcr.io/0xshug0/audio.cpp:full-cpu` Images for a specific day/commit can be found in the @@ -71,6 +76,12 @@ Build for a specific set of GPU architectures (e.g. for faster, less portable bu docker build -f .devops/cuda.Dockerfile -t local/audio.cpp:full-cuda12 --build-arg CUDA_DOCKER_ARCH="86;89" . ``` +### Vulkan + +```bash +docker build -f .devops/vulkan.Dockerfile -t local/audio.cpp:full-vulkan . +``` + ### CPU ```bash @@ -88,6 +99,17 @@ An additional `` should be mounted for tasks that write files. docker run --rm --gpus all -v ":/models:ro" ghcr.io/0xshug0/audio.cpp:full-cuda12 --model /models/ <...> ``` +### Vulkan + +```bash +docker run --rm --device /dev/dri \ + --group-add "$(getent group render | cut -d: -f3)" \ + --group-add "$(getent group video | cut -d: -f3)" \ + -v ":/models:ro" \ + ghcr.io/0xshug0/audio.cpp:full-vulkan \ + --backend vulkan --model /models/ <...> +``` + ### Native WebUI For the native WebUI with model downloads and dynamic model management, mount a @@ -101,6 +123,18 @@ docker run --rm --gpus all \ server --ui --ui-management --host 0.0.0.0 --port 8080 --backend cuda ``` +For Vulkan, expose the host render device and use the Vulkan backend: + +```bash +docker run --rm --device /dev/dri \ + --group-add "$(getent group render | cut -d: -f3)" \ + --group-add "$(getent group video | cut -d: -f3)" \ + -p 8080:8080 \ + -v ":/app/models" \ + ghcr.io/0xshug0/audio.cpp:full-vulkan \ + server --ui --ui-management --host 0.0.0.0 --port 8080 --backend vulkan +``` + Open `http://127.0.0.1:8080` on the host. Use a writable mount when the UI should download or prepare models. For a read-only model directory, omit `--ui-management` or mount the directory as read-only and load only models that diff --git a/docs/gguf.md b/docs/gguf.md index fd7ec4908..1cd8932f4 100644 --- a/docs/gguf.md +++ b/docs/gguf.md @@ -97,6 +97,7 @@ Status labels: | `qwen3_tts` voice design | Done | Pass | --- | Pass (ASR match, drift) | Pass (ASR match, drift) | | `rvc` | Done | --- | --- | Pass | --- | | `seed_vc` | Done | Pass | --- | Pass (drift) | Pass (drift) | +| `sopro_tts` | Done | Pass | --- | Pass | Pass | | `soprano_tts` | Done | Pass | --- | Pass | Pass (drift) | | `silero_vad` | Skip (tiny model) | --- | --- | --- | --- | | `sortformer_diar` | Done | Pass | --- | Pass | Pass | diff --git a/docs/maintainers/model_specs.md b/docs/maintainers/model_specs.md index 87a30f0fd..388250ffe 100644 --- a/docs/maintainers/model_specs.md +++ b/docs/maintainers/model_specs.md @@ -156,6 +156,11 @@ packages come from the same repo, put the shared source in `package_defaults.download` and keep package-level `download` only for overrides. +Experimental ports may use an empty `packages` array while conversion and +runtime validation are still local-only. In that case `ui.recommended_package` +is omitted, so model managers do not advertise a download that cannot yet be +loaded. Community and supported families must publish at least one package. + ```json { "package_defaults": { @@ -181,6 +186,15 @@ overrides. } ``` +`kind: "modelscope_snapshot"` downloads the same way from a ModelScope +(modelscope.cn) repo. It takes the same fields (`repo` required, `revision` +optional); the only differences are that the default revision is `master` +(ModelScope's default branch) and the `gated` flag does not apply. The native +package manager resolves the endpoint through `AUDIOCPP_MS_BASE_URL` +(default `https://www.modelscope.cn`), mirroring `AUDIOCPP_HF_BASE_URL` for +Hugging Face. ModelScope requests authenticate with `AUDIOCPP_MS_TOKEN` only; +the Hugging Face token is never sent to a ModelScope endpoint. + Dependencies describe extra model-level resources required by runtime features. Use `kind: "model"` for another model family, and `kind: "bundled_model"` for an in-repo bundled model asset. Do not use dependencies for sidecars or tensor diff --git a/docs/model_manager.md b/docs/model_manager.md index 71e291f67..bd42d713a 100644 --- a/docs/model_manager.md +++ b/docs/model_manager.md @@ -122,6 +122,44 @@ For support status and tested precision coverage, see the [GGUF guide](gguf.md). For measured 16-bit vs Q8 speed and peak-VRAM results, see the [Q8 performance report](reports/gguf_q8_performance.md). +## Download Sources + +Both the native package manager and `tools/model_manager_v2.py` download from +Hugging Face (`kind: "huggingface_snapshot"`) or ModelScope +(`kind: "modelscope_snapshot"`); see +[maintainers/model_specs.md](maintainers/model_specs.md) for the spec fields. +The native endpoints can be overridden for mirrors or tests with +`AUDIOCPP_HF_BASE_URL` (default `https://huggingface.co`) and +`AUDIOCPP_MS_BASE_URL` (default `https://www.modelscope.cn`). The Python tool +uses the standard `HF_ENDPOINT` for Hugging Face and the same +`AUDIOCPP_MS_BASE_URL` for ModelScope. + +Authentication is provider-scoped: Hugging Face requests may carry +`HF_TOKEN` / `HUGGING_FACE_HUB_TOKEN`, while ModelScope requests only carry +`AUDIOCPP_MS_TOKEN` (optional, for access-restricted ModelScope repos). The +Hugging Face token is never sent to a ModelScope endpoint, and vice versa. + +### Python Source Override + +The Python v2 manager can redirect any package to ModelScope on demand, +without editing `model_specs/*.json`: + +```bash +python3 tools/model_manager_v2.py install qwen3_tts --source modelscope --source-repo HereIsMark/audio.cpp-gguf +``` + +`--source modelscope` is accepted by `install` and `sizes`. `--source-repo` +names the ModelScope repo (`namespace/name`); when omitted, the spec's own +repo name is reused on ModelScope. Passing `--source-repo` without +`--source modelscope` is rejected. Revision translation: a spec revision that +is unset or `main` becomes ModelScope's default branch `master`; any other +explicit revision passes through unchanged. + +Note that manifest etags are source-specific (Hugging Face etag vs ModelScope +sha256), so cross-source freshness checks can spuriously report that an +installed package has an update. Query with the same `--source` that was used +to install. + ## Dependencies The native manager needs no Python runtime. The default bundled-TLS build needs diff --git a/docs/models/ace_step.md b/docs/models/ace_step.md index 168e54ac7..0d3f418e5 100644 --- a/docs/models/ace_step.md +++ b/docs/models/ace_step.md @@ -215,8 +215,11 @@ The two differ only in `is_turbo`: XL Turbo is guidance-distilled and ignores Their dimensions, encoder group and head configuration are identical. `ace_step_xl_turbo_bf16` and `ace_step_xl_sft_bf16` install them as GGUFs -(14.2 GB each), self-contained the way the Turbo and Base GGUFs are — XL DiT, -planner LM, text encoder and VAE in one file: +(14.2 GiB each), self-contained the way the Turbo and Base GGUFs are — XL DiT, +planner LM, text encoder and VAE in one file. `ace_step_xl_turbo_q8dit` and +`ace_step_xl_sft_q8dit` are the same packages with the DiT at q8_0 and the +planner LM, text encoder and VAE left at bf16 (9.97 GiB); see the measurements +at the end of this section for what that costs: ```bash audiocpp_cli --task gen --family ace_step --model models/ACE-Step1.5-GGUF/xl-turbo --backend cuda --task-route text2music --text "warm lo-fi hip hop with a soft rhodes piano" --duration-seconds 60 --load-option ace_step.dit_model_path=acestep-v15-xl-turbo --out song.wav @@ -235,16 +238,22 @@ warm in the page cache: 87 s from safetensors at `native`, 25 s from safetensors at `bf16`, 15 s from the bf16 GGUF, both variants alike (turbo, for reference: 11 s). Reading the weights off disk adds roughly 10 s either way. -Building an XL GGUF yourself needs the other variants' safetensors on hand, -because `audiocpp_gguf` checks the conversion against the spec's required -namespaces; exclude them from the output: +Building an XL GGUF yourself still names every namespace the spec requires, +because `audiocpp_gguf` validates the conversion against all of them — but the +turbo and base entries only have to *exist*. `--exclude-prefix` drops their +tensors before any data is read, so a 76-byte placeholder stands in for the 9 GB +of weights that would otherwise be downloaded and thrown away: + +```bash +python -c "import json,struct; h=json.dumps({'x':{'dtype':'F32','shape':[1,1],'data_offsets':[0,4]}}).encode(); h+=b' '*((8-len(h)%8)%8); open('placeholder.safetensors','wb').write(struct.pack('/silence_latent.pt` -converts it. +converts it. Of the turbo and base snapshots only `config.json` is genuinely +needed — those are required sidecars, a few KB each. + +`ace_step` is graded `No (planner sampling can fail)` for q8_0 in +[gguf.md](../gguf.md), and that grade is about the planner LM, not the DiT: at a +fixed seed a fully quantised build samples a different token path and returns an +unrelated song. Quantising the DiT alone keeps the planner exact, which +`--keep-type` expresses: + +```bash + --keep-type "lm_weights*=bf16" \ + --keep-type "text_encoder_weights*=bf16" \ + --keep-type "vae_weights*=bf16" \ + --keep-type "dit_xl_turbo_silence_latent*=bf16" \ + --type q8_0 --output ace-step-1.5-xl-turbo-q8dit.gguf +``` + +Measured on an RTX 5090 against the bf16 build at the same seed and prompt, +20 s of audio: 9.97 GiB against 14.2 GiB and 9.3 s against 14.2 s, with a 0.989 +waveform correlation against the bf16 output — 0.997 on a sung 40 s take, 0.999 +for XL SFT. A fully quantised build of the same weights correlates 0.09, and its +ASR transcript is a different lyric line. + +Re-quantising a finished GGUF works too, since a GGUF input carries its own +namespaces, package spec and sidecars: + +```bash +audiocpp_gguf --input ace-step-1.5-xl-turbo-bf16.gguf --type q8_0 \ + --keep-type "lm_weights*=bf16" --output ace-step-1.5-xl-turbo-q8dit.gguf +``` diff --git a/docs/models/breeze_tts.md b/docs/models/breeze_tts.md new file mode 100644 index 000000000..584713bb8 --- /dev/null +++ b/docs/models/breeze_tts.md @@ -0,0 +1,79 @@ +# BreezeTTS 2 + +BreezeTTS 2 is a GGUF TTS family for instruction-conditioned speech and +prompt-audio voice cloning. The default package is Q8_0. + +## Quick Start + +Download the default Q8_0 package: + +```bash +python3 tools/model_manager_v2.py install breeze_tts_2_q8_0 --models-root models +``` + +Voice cloning: + +```bash +audiocpp_cli \ + --task clon \ + --family breeze_tts \ + --model models/Breeze-TTS-2-GGUF/breeze-tts-2-q8_0.gguf \ + --backend cuda \ + --text "Please read this line in a clear and natural voice." \ + --voice-ref assets/resources/b.wav \ + --reference-text "Some call me nature. Others call me Mother Nature. I have been here for over four and a half billion years." \ + --request-option instruction="Speak clearly and naturally." \ + --out breeze_tts_clone.wav +``` + +Voice design: + +```bash +audiocpp_cli \ + --task tts \ + --family breeze_tts \ + --model models/Breeze-TTS-2-GGUF/breeze-tts-2-q8_0.gguf \ + --backend cuda \ + --text "Welcome to the local voice demo." \ + --request-option instruction="A warm female narrator with calm pacing and studio clarity." \ + --out breeze_tts_design.wav +``` + +## Model + +| Field | Value | +|---|---| +| Family | `breeze_tts` | +| Default GGUF | `models/Breeze-TTS-2-GGUF/breeze-tts-2-q8_0.gguf` | +| Tasks | `tts`, `clon` | +| Modes | `offline` | +| Languages | `zh`, `en` | +| Voice input | Optional for `tts`; required for `clon` | + +## Options + +| Option | Values | Default | Meaning | +|---|---|---:|---| +| `--voice-ref` | WAV path | required for `clon` | Prompt/reference speaker audio. | +| `--reference-text` / `--request-option reference_text=` | text | empty | Transcript for prompt audio when cloning. | +| `--request-option instruction=` | text | `Speak clearly and naturally.` | Voice or style instruction. | +| `--request-option text_chunk_size=` | integer > 0 | `600` | Long-form text chunk size. | +| `--request-option text_chunk_mode=` | `default`, `tag_aware`, `japanese`, `endline` | `default` | Framework text chunk mode. | +| `--request-option max_tokens=` | integer > 0 | `1500` | Maximum generated acoustic frames. | +| `--request-option guidance_scale=` | float >= 0 | `1.0` | Classifier-free guidance scale. | +| `--request-option temperature=` | float >= 0 | `0.9` | Backbone sampling temperature. | +| `--request-option depth_temperature=` | float >= 0 | `0.9` | Depth decoder sampling temperature. | +| `--request-option top_k=` | integer >= 0 | `50` | Top-k sampling limit; `0` disables top-k filtering. | +| `--request-option top_p=` | `0..1` | `1.0` | Top-p sampling limit. | +| `--request-option seed=` | integer >= 0 | `0` | Generation seed. | +| `--session-option breeze_tts.reference_cache_slots=` | integer >= 0 | `1` | Prepared reference-audio cache slots. | +| `--session-option breeze_tts.attention=` | `auto`, `flash`, `eager` | `auto` | Attention kernel. `auto` uses flash except on Volta/Turing GPUs (e.g. V100), where it falls back to eager to avoid missing MMA kernels. | +| `--session-option weight_type=` | `native`, `f32`, `f16`, `bf16`, `q8_0`, `q4_0`, `q4_k` | `native` | Weight storage type; quantized types convert at load time from the BF16 package. | + +Quantized weight storage is the largest measured speedup and applies to CUDA +and HIP alike: `q8_0` cut the fixed 100-token regression case from RTF ~1.5 to +~0.95 on gfx1151 and from ~0.77 to ~0.56 on an RTX 2080 Ti, and `q4_k` reached +~0.84 / ~0.49 respectively, with no audible quality regression in the Chinese +voice-design regression cases. Counter to intuition, fp32 is the one +configuration known to be *worse* for this model (mispronunciations and +runaway repetition), because the model is trained and tuned in bf16. diff --git a/docs/models/cosyvoice3.md b/docs/models/cosyvoice3.md new file mode 100644 index 000000000..e4cfae6cc --- /dev/null +++ b/docs/models/cosyvoice3.md @@ -0,0 +1,80 @@ +# CosyVoice3 + +CosyVoice3 is a GGUF TTS family for zero-shot voice cloning, cross-lingual +speech, and instruction-conditioned speech. The default package is Q8_0. + +## Quick Start + +Download the default Q8_0 package: + +```bash +python3 tools/model_manager_v2.py install cosyvoice3_q8_0 --models-root models +``` + +Zero-shot voice cloning: + +```bash +audiocpp_cli \ + --task clon \ + --family cosyvoice3 \ + --model models/CosyVoice3-GGUF/cosyvoice3-q8_0.gguf \ + --backend cuda \ + --text "This is a local CosyVoice3 voice cloning test." \ + --voice-ref assets/resources/b.wav \ + --reference-text "Some call me nature. Others call me Mother Nature. I have been here for over four and a half billion years." \ + --request-option template_name=zero_shot \ + --out cosyvoice3_clone.wav +``` + +Instruction-conditioned speech: + +```bash +audiocpp_cli \ + --task tts \ + --family cosyvoice3 \ + --model models/CosyVoice3-GGUF/cosyvoice3-q8_0.gguf \ + --backend cuda \ + --text "Please read this with a calm and friendly tone." \ + --voice-ref assets/resources/b.wav \ + --reference-text "Some call me nature. Others call me Mother Nature. I have been here for over four and a half billion years." \ + --request-option template_name=instruct \ + --request-option instruction="Speak warmly with clear articulation." \ + --out cosyvoice3_instruct.wav +``` + +## Model + +| Field | Value | +|---|---| +| Family | `cosyvoice3` | +| Default GGUF | `models/CosyVoice3-GGUF/cosyvoice3-q8_0.gguf` | +| Tasks | `tts`, `clon` | +| Modes | `offline` | +| Languages | `zh`, `en`, `ja`, `ko`, `de`, `es`, `fr`, `it`, `ru`, `yue` | +| Voice input | Required reference WAV through `--voice-ref` | + +## Templates + +| `template_name` | Use | +|---|---| +| `zero_shot` | Voice cloning with prompt audio and transcript. | +| `cross_lingual` | Cross-lingual voice cloning from prompt audio. | +| `instruct` | Instruction-conditioned speech with prompt audio. | + +## Options + +| Option | Values | Default | Meaning | +|---|---|---:|---| +| `--voice-ref` | WAV path | required | Prompt/reference speaker audio. | +| `--reference-text` / `--request-option reference_text=` | text | empty | Transcript for prompt audio. | +| `--request-option template_name=` | `zero_shot`, `cross_lingual`, `instruct` | `zero_shot` | Request template. | +| `--request-option instruction=` | text | empty | Instruction text for `instruct` mode. | +| `--request-option text_chunk_size=` | integer > 0 | `600` | Long-form text chunk size. | +| `--request-option text_chunk_mode=` | `default`, `tag_aware`, `japanese`, `endline` | `default` | Framework text chunk mode. | +| `--request-option max_tokens=` | integer > 0 | `1600` | Maximum generated speech tokens. | +| `--request-option min_tokens=` | integer >= 0 | `0` | Minimum generated tokens before stop is accepted. | +| `--request-option top_k=` | integer > 0 | `25` | AR speech-token top-k sampling limit. | +| `--request-option num_inference_steps=` | integer > 0 | `10` | Flow decoder Euler steps. | +| `--request-option seed=` | integer >= 0 | `1986` | Generation seed. | +| `--session-option cosyvoice3.reference_cache_slots=` | integer >= 0 | `4` | Prepared reference-audio cache slots. | +| `--session-option cosyvoice3.mem_saver=true\|false` | bool | `false` | Release cached runtime graphs after request phases. | diff --git a/docs/models/vevo2.md b/docs/models/vevo2.md index f6241e668..b4ef2b403 100644 --- a/docs/models/vevo2.md +++ b/docs/models/vevo2.md @@ -236,6 +236,8 @@ audiocpp_cli --task svc --family vevo2 --model models/Vevo2 --backend cuda --tas | `--style-shift-steps` | integer semitones | `0` | Manual style pitch shift. If `0` and pitch shift is enabled with a style reference, VeVo2 estimates it. | | `--target-duration-seconds` | float | not set | Flow-matching target duration hint. | | `--reference-duration-seconds` | float | not set | Trim target voice reference before conditioning. | +| `--request-option audio_chunk_duration_sec=` | float | `0` | Opt-in source-audio chunking for `style_preserved_vc` and `style_preserved_svc`. `0` keeps the existing one-shot path. | +| `--request-option cross_fade_duration_sec=` | float | `1.0` with source-audio chunking | Source chunk overlap and output crossfade duration when source-audio chunking is enabled. Must be smaller than `audio_chunk_duration_sec`. | | `--temperature` | float | `1.0` | AR sampling temperature. | | `--top-k` | integer | `25` | AR top-k. | | `--top-p` | float | `0.8` | AR top-p. | diff --git a/docs/tts.md b/docs/tts.md index ac3b720e3..7d6377f41 100644 --- a/docs/tts.md +++ b/docs/tts.md @@ -3,8 +3,10 @@ | Model | Family | Task(s) | Quick Start | |---|---|---|---| | Qwen3 TTS | `qwen3_tts` | `tts`, `vdes` | [Qwen3 TTS](#qwen3-tts) | +| BreezeTTS 2 | `breeze_tts` | `tts`, `clon` | [BreezeTTS 2](models/breeze_tts.md) | | Chatterbox | `chatterbox` | `clon`, `vc` | [Chatterbox](#chatterbox) | | Confucius4-TTS | `confucius4_tts` | `clon` | [Confucius4-TTS](#confucius4-tts) | +| CosyVoice3 | `cosyvoice3` | `tts`, `clon` | [CosyVoice3](models/cosyvoice3.md) | | DramaBox | `dramabox` | `tts`, `clon` | [DramaBox](#dramabox) | | DotTTS | `dots_tts` | `tts`, `clon` | [DotTTS](#dottts) | | F5-TTS | `f5_tts` | `tts`, `clon` | [F5-TTS](community_models/f5_tts.md) | @@ -29,6 +31,7 @@ | GLM-TTS | `glm_tts` | `tts`, `clon` | [GLM-TTS](#glm-tts) | | Inflect Micro v2 | `inflect_v2` | `tts` | [Inflect v2](#inflect-v2) | | OuteTTS | `outetts` | `tts`, `clon` | [OuteTTS](#outetts) | +| sanoTTS voice family | `sanotts` | `tts` | [sanoTTS](#sanotts) | | Supertonic | `supertonic` | `tts` | [Supertonic](#supertonic) | | VieNeu-TTS | `vietneu_tts` | `tts`, `clon` | [VieNeu-TTS](community_models/vietneu_tts.md) | | VibeVoice | `vibevoice` | `tts` | [VibeVoice](#vibevoice) | @@ -565,6 +568,7 @@ audiocpp_cli --task tts --family voxcpm2 --model models/VoxCPM2 --backend cuda - | `--session-option voxcpm2.prompt_cache_slots=` | integer | `1` | Prompt and prompt-audio embedding cache slots. Set to `0` to disable prompt caching. | | `--text-chunk-size` | integer chars | `2048` | Long-form chunk size. | | `--text-chunk-mode` | `default`, `tag_aware`, `japanese`, `endline` | `tag_aware` | Long-form chunking mode; keeps style/tag controls attached to chunks by default. | +| `--request-option voxcpm2.chunk_strategy=continuation\|stateless` | enum | `continuation` | Long-form chunk generation strategy. `stateless` synthesizes each text chunk from the same original prompt/reference and concatenates the audio; use it for plain text/reference-clone long-form input, not voice/emotion tag carry-over. | | `--max-tokens` | integer | `4096` | Maximum generated AR tokens. | | `--num-inference-steps` | integer | `10` | Flow matching steps. | | `--guidance-scale` | float | `2.0` | CFG strength. | @@ -603,6 +607,7 @@ python3 tools/model_manager_v2.py install --models-root models higgs_audio_tts_4 | `--top-k` | integer | `30` | AR top-k sampling limit. The narrower default is less prone to premature EOC than the Python client's `50`. | | `--top-p` | float | `0.8` | AR nucleus sampling limit. The Python client's unfiltered equivalent is `1.0`. | | `--repetition-penalty` | float | `1.1` | Accepted for Python API compatibility; Higgs audio-code sampling does not consume it. | +| `--session-option higgs_audio_tts.attention=` | `auto`, `flash`, `eager` | `auto` | Attention kernel. `auto` uses flash except on Volta/Turing GPUs (e.g. V100), where it falls back to eager to avoid missing MMA kernels. | ## Fish Audio S2 Pro @@ -768,6 +773,27 @@ See the [Inflect v2 community model guide](community_models/inflect_v2.md) for eSpeak-ng paths, long-form behavior, source/conversion instructions, and limitations. +## sanoTTS + +sanoTTS is a family of very small offline TTS voices (English, Vietnamese, +Indonesian; 294k to 2.27M parameters) with native GGML runtimes; the +smallest voice also runs on microcontrollers. The GGUF packages are +standalone and download from Hugging Face. sanoTTS requires an external +eSpeak-ng installation: + +```bash +python3 tools/model_manager_v2.py install sanotts_heart_nano_orig --models-root models + +audiocpp_cli --task tts --family sanotts \ + --model models/sanoTTS-heart-nano-GGUF --backend cpu \ + --text "Hello from sano T T S, a very small neural text to speech model." \ + --request-option speaking_rate=1.0 \ + --out sanotts.wav +``` + +See the [sanoTTS community model guide](community_models/sanotts.md) for +eSpeak-ng paths, seed semantics, parity evidence, and performance numbers. + ## Supertonic Supertonic 3 is a preset-voice multilingual TTS model. It does not use external speaker references in the current integration. diff --git a/external/ggml/include/ggml.h b/external/ggml/include/ggml.h index 0de79aed5..554247270 100644 --- a/external/ggml/include/ggml.h +++ b/external/ggml/include/ggml.h @@ -1,536 +1,544 @@ -#pragma once - -// -// GGML Tensor Library -// -// This documentation is still a work in progress. -// If you wish some specific topics to be covered, feel free to drop a comment: -// -// https://github.com/ggml-org/whisper.cpp/issues/40 -// -// ## Overview -// -// This library implements: -// -// - a set of tensor operations -// - automatic differentiation -// - basic optimization algorithms -// -// The aim of this library is to provide a minimalistic approach for various machine learning tasks. This includes, -// but is not limited to, the following: -// -// - linear regression -// - support vector machines -// - neural networks -// -// The library allows the user to define a certain function using the available tensor operations. This function -// definition is represented internally via a computation graph. Each tensor operation in the function definition -// corresponds to a node in the graph. Having the computation graph defined, the user can choose to compute the -// function's value and/or its gradient with respect to the input variables. Optionally, the function can be optimized -// using one of the available optimization algorithms. -// -// For example, here we define the function: f(x) = a*x^2 + b -// -// { -// struct ggml_init_params params = { -// .mem_size = 16*1024*1024, -// .mem_buffer = NULL, -// }; -// -// // memory allocation happens here -// struct ggml_context * ctx = ggml_init(params); -// -// struct ggml_tensor * x = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1); -// -// ggml_set_param(ctx, x); // x is an input variable -// -// struct ggml_tensor * a = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1); -// struct ggml_tensor * b = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1); -// struct ggml_tensor * x2 = ggml_mul(ctx, x, x); -// struct ggml_tensor * f = ggml_add(ctx, ggml_mul(ctx, a, x2), b); -// -// ... -// } -// -// Notice that the function definition above does not involve any actual computation. The computation is performed only -// when the user explicitly requests it. For example, to compute the function's value at x = 2.0: -// -// { -// ... -// -// struct ggml_cgraph * gf = ggml_new_graph(ctx); -// ggml_build_forward_expand(gf, f); -// -// // set the input variable and parameter values -// ggml_set_f32(x, 2.0f); -// ggml_set_f32(a, 3.0f); -// ggml_set_f32(b, 4.0f); -// -// ggml_graph_compute_with_ctx(ctx, &gf, n_threads); -// -// printf("f = %f\n", ggml_get_f32_1d(f, 0)); -// -// ... -// } -// -// The actual computation is performed in the ggml_graph_compute() function. -// -// The ggml_new_tensor_...() functions create new tensors. They are allocated in the memory buffer provided to the -// ggml_init() function. You have to be careful not to exceed the memory buffer size. Therefore, you have to know -// in advance how much memory you need for your computation. Alternatively, you can allocate a large enough memory -// and after defining the computation graph, call the ggml_used_mem() function to find out how much memory was -// actually needed. -// -// The ggml_set_param() function marks a tensor as an input variable. This is used by the automatic -// differentiation and optimization algorithms. -// -// The described approach allows to define the function graph once and then compute its forward or backward graphs -// multiple times. All computations will use the same memory buffer allocated in the ggml_init() function. This way -// the user can avoid the memory allocation overhead at runtime. -// -// The library supports multi-dimensional tensors - up to 4 dimensions. The FP16 and FP32 data types are first class -// citizens, but in theory the library can be extended to support FP8 and integer data types. -// -// Each tensor operation produces a new tensor. Initially the library was envisioned to support only the use of unary -// and binary operations. Most of the available operations fall into one of these two categories. With time, it became -// clear that the library needs to support more complex operations. The way to support these operations is not clear -// yet, but a few examples are demonstrated in the following operations: -// -// - ggml_permute() -// - ggml_conv_1d_1s() -// - ggml_conv_1d_2s() -// -// For each tensor operator, the library implements a forward and backward computation function. The forward function -// computes the output tensor value given the input tensor values. The backward function computes the adjoint of the -// input tensors given the adjoint of the output tensor. For a detailed explanation of what this means, take a -// calculus class, or watch the following video: -// -// What is Automatic Differentiation? -// https://www.youtube.com/watch?v=wG_nF1awSSY -// -// -// ## Tensor data (struct ggml_tensor) -// -// The tensors are stored in memory via the ggml_tensor struct. The structure provides information about the size of -// the tensor, the data type, and the memory buffer where the tensor data is stored. Additionally, it contains -// pointers to the "source" tensors - i.e. the tensors that were used to compute the current tensor. For example: -// -// { -// struct ggml_tensor * c = ggml_add(ctx, a, b); -// -// assert(c->src[0] == a); -// assert(c->src[1] == b); -// } -// -// The multi-dimensional tensors are stored in row-major order. The ggml_tensor struct contains fields for the -// number of elements in each dimension ("ne") as well as the number of bytes ("nb", a.k.a. stride). This allows -// to store tensors that are not contiguous in memory, which is useful for operations such as transposition and -// permutation. All tensor operations have to take the stride into account and not assume that the tensor is -// contiguous in memory. -// -// The data of the tensor is accessed via the "data" pointer. For example: -// -// { -// const int nx = 2; -// const int ny = 3; -// -// struct ggml_tensor * a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, nx, ny); -// -// for (int y = 0; y < ny; y++) { -// for (int x = 0; x < nx; x++) { -// *(float *) ((char *) a->data + y*a->nb[1] + x*a->nb[0]) = x + y; -// } -// } -// -// ... -// } -// -// Alternatively, there are helper functions, such as ggml_get_f32_1d() and ggml_set_f32_1d() that can be used. -// -// ## The matrix multiplication operator (ggml_mul_mat) -// -// TODO -// -// -// ## Multi-threading -// -// TODO -// -// -// ## Overview of ggml.c -// -// TODO -// -// -// ## SIMD optimizations -// -// TODO -// -// -// ## Debugging ggml -// -// TODO -// -// - -#ifdef GGML_SHARED -# if defined(_WIN32) && !defined(__MINGW32__) -# ifdef GGML_BUILD -# define GGML_API __declspec(dllexport) extern -# else -# define GGML_API __declspec(dllimport) extern -# endif -# else -# define GGML_API __attribute__ ((visibility ("default"))) extern -# endif -#else -# define GGML_API extern -#endif - -// TODO: support for clang -#ifdef __GNUC__ -# define GGML_DEPRECATED(func, hint) func __attribute__((deprecated(hint))) -#elif defined(_MSC_VER) -# define GGML_DEPRECATED(func, hint) __declspec(deprecated(hint)) func -#else -# define GGML_DEPRECATED(func, hint) func -#endif - -#ifndef __GNUC__ -# define GGML_ATTRIBUTE_FORMAT(...) -#elif defined(__MINGW32__) && !defined(__clang__) -# define GGML_ATTRIBUTE_FORMAT(...) __attribute__((format(gnu_printf, __VA_ARGS__))) -#else -# define GGML_ATTRIBUTE_FORMAT(...) __attribute__((format(printf, __VA_ARGS__))) -#endif - -#if defined(_WIN32) && !defined(_WIN32_WINNT) -# define _WIN32_WINNT 0x0A00 -#endif - -#include -#include -#include -#include - -#define GGML_FILE_MAGIC 0x67676d6c // "ggml" -#define GGML_FILE_VERSION 2 - -#define GGML_QNT_VERSION 2 // bump this on quantization format changes -#define GGML_QNT_VERSION_FACTOR 1000 // do not change this - -#define GGML_MAX_DIMS 4 -#define GGML_MAX_PARAMS 2048 -#define GGML_MAX_SRC 10 -#define GGML_MAX_N_THREADS 512 -#define GGML_MAX_OP_PARAMS 64 - -#ifndef GGML_MAX_NAME -# define GGML_MAX_NAME 64 -#endif - -#define GGML_DEFAULT_N_THREADS 4 -#define GGML_DEFAULT_GRAPH_SIZE 2048 - -#if UINTPTR_MAX == 0xFFFFFFFF - #define GGML_MEM_ALIGN 4 -#elif defined(__EMSCRIPTEN__) -// emscripten uses max_align_t == 8, so we need GGML_MEM_ALIGN == 8 for 64-bit wasm. -// (for 32-bit wasm, the first conditional is true and GGML_MEM_ALIGN stays 4.) -// ref: https://github.com/ggml-org/llama.cpp/pull/18628 - #define GGML_MEM_ALIGN 8 -#else - #define GGML_MEM_ALIGN 16 -#endif - -#define GGML_EXIT_SUCCESS 0 -#define GGML_EXIT_ABORTED 1 - -// TODO: convert to enum https://github.com/ggml-org/llama.cpp/pull/16187#discussion_r2388538726 -#define GGML_ROPE_TYPE_NORMAL 0 -#define GGML_ROPE_TYPE_NEOX 2 -#define GGML_ROPE_TYPE_MROPE 8 -#define GGML_ROPE_TYPE_VISION 24 -#define GGML_ROPE_TYPE_IMROPE 40 // binary: 101000 - -#define GGML_MROPE_SECTIONS 4 - -#define GGML_UNUSED(x) (void)(x) -#ifdef __CUDACC__ -template -__host__ __device__ constexpr inline void ggml_unused_vars_impl(Args&&...) noexcept {} -#define GGML_UNUSED_VARS(...) ggml_unused_vars_impl(__VA_ARGS__) -#else -#define GGML_UNUSED_VARS(...) do { (void)sizeof((__VA_ARGS__, 0)); } while(0) -#endif // __CUDACC__ - -#define GGML_PAD(x, n) (((x) + (n) - 1) & ~((n) - 1)) - -#ifndef NDEBUG -# define GGML_UNREACHABLE() do { fprintf(stderr, "statement should be unreachable\n"); abort(); } while(0) -#elif defined(__GNUC__) -# define GGML_UNREACHABLE() __builtin_unreachable() -#elif defined(_MSC_VER) -# define GGML_UNREACHABLE() __assume(0) -#else -# define GGML_UNREACHABLE() ((void) 0) -#endif - -#ifdef __cplusplus -# define GGML_NORETURN [[noreturn]] -#elif defined(_MSC_VER) -# define GGML_NORETURN __declspec(noreturn) -#else -# define GGML_NORETURN _Noreturn -#endif - -#define GGML_ABORT(...) ggml_abort(__FILE__, __LINE__, __VA_ARGS__) -#define GGML_ASSERT(x) if (!(x)) GGML_ABORT("GGML_ASSERT(%s) failed", #x) - -// used to copy the number of elements and stride in bytes of tensors into local variables. -// main purpose is to reduce code duplication and improve readability. -// -// example: -// -// GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne); -// GGML_TENSOR_LOCALS(size_t, nb1, src1, nb); -// -#define GGML_TENSOR_LOCALS_1(type, prefix, pointer, array) \ - const type prefix##0 = (pointer) ? (pointer)->array[0] : 0; \ - GGML_UNUSED(prefix##0); -#define GGML_TENSOR_LOCALS_2(type, prefix, pointer, array) \ - GGML_TENSOR_LOCALS_1 (type, prefix, pointer, array) \ - const type prefix##1 = (pointer) ? (pointer)->array[1] : 0; \ - GGML_UNUSED(prefix##1); -#define GGML_TENSOR_LOCALS_3(type, prefix, pointer, array) \ - GGML_TENSOR_LOCALS_2 (type, prefix, pointer, array) \ - const type prefix##2 = (pointer) ? (pointer)->array[2] : 0; \ - GGML_UNUSED(prefix##2); -#define GGML_TENSOR_LOCALS(type, prefix, pointer, array) \ - GGML_TENSOR_LOCALS_3 (type, prefix, pointer, array) \ - const type prefix##3 = (pointer) ? (pointer)->array[3] : 0; \ - GGML_UNUSED(prefix##3); - -#define GGML_TENSOR_UNARY_OP_LOCALS \ - GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne) \ - GGML_TENSOR_LOCALS(size_t, nb0, src0, nb) \ - GGML_TENSOR_LOCALS(int64_t, ne, dst, ne) \ - GGML_TENSOR_LOCALS(size_t, nb, dst, nb) - -#define GGML_TENSOR_BINARY_OP_LOCALS \ - GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne) \ - GGML_TENSOR_LOCALS(size_t, nb0, src0, nb) \ - GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne) \ - GGML_TENSOR_LOCALS(size_t, nb1, src1, nb) \ - GGML_TENSOR_LOCALS(int64_t, ne, dst, ne) \ - GGML_TENSOR_LOCALS(size_t, nb, dst, nb) - -#define GGML_TENSOR_TERNARY_OP_LOCALS \ - GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne) \ - GGML_TENSOR_LOCALS(size_t, nb0, src0, nb) \ - GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne) \ - GGML_TENSOR_LOCALS(size_t, nb1, src1, nb) \ - GGML_TENSOR_LOCALS(int64_t, ne2, src2, ne) \ - GGML_TENSOR_LOCALS(size_t, nb2, src2, nb) \ - GGML_TENSOR_LOCALS(int64_t, ne, dst, ne) \ - GGML_TENSOR_LOCALS(size_t, nb, dst, nb) - -#define GGML_TENSOR_BINARY_OP_LOCALS01 \ - GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne) \ - GGML_TENSOR_LOCALS(size_t, nb0, src0, nb) \ - GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne) \ - GGML_TENSOR_LOCALS(size_t, nb1, src1, nb) - -#ifdef __cplusplus -extern "C" { -#endif - - // Function type used in fatal error callbacks - typedef void (*ggml_abort_callback_t)(const char * error_message); - - // Set the abort callback (passing null will restore original abort functionality: printing a message to stdout) - // Returns the old callback for chaining - GGML_API ggml_abort_callback_t ggml_set_abort_callback(ggml_abort_callback_t callback); - - GGML_NORETURN GGML_ATTRIBUTE_FORMAT(3, 4) - GGML_API void ggml_abort(const char * file, int line, const char * fmt, ...); - - enum ggml_status { - GGML_STATUS_ALLOC_FAILED = -2, - GGML_STATUS_FAILED = -1, - GGML_STATUS_SUCCESS = 0, - GGML_STATUS_ABORTED = 1, - }; - - // get ggml_status name string - GGML_API const char * ggml_status_to_string(enum ggml_status status); - - // ieee 754-2008 half-precision float16 - // todo: make this not an integral type - typedef uint16_t ggml_fp16_t; - GGML_API float ggml_fp16_to_fp32(ggml_fp16_t); - GGML_API ggml_fp16_t ggml_fp32_to_fp16(float); - GGML_API void ggml_fp16_to_fp32_row(const ggml_fp16_t *, float *, int64_t); - GGML_API void ggml_fp32_to_fp16_row(const float *, ggml_fp16_t *, int64_t); - - // google brain half-precision bfloat16 - typedef struct { uint16_t bits; } ggml_bf16_t; - GGML_API ggml_bf16_t ggml_fp32_to_bf16(float); - GGML_API float ggml_bf16_to_fp32(ggml_bf16_t); // consider just doing << 16 - GGML_API void ggml_bf16_to_fp32_row(const ggml_bf16_t *, float *, int64_t); - GGML_API void ggml_fp32_to_bf16_row_ref(const float *, ggml_bf16_t *, int64_t); - GGML_API void ggml_fp32_to_bf16_row(const float *, ggml_bf16_t *, int64_t); - - struct ggml_object; - struct ggml_context; - struct ggml_cgraph; - - // NOTE: always add types at the end of the enum to keep backward compatibility - enum ggml_type { - GGML_TYPE_F32 = 0, - GGML_TYPE_F16 = 1, - GGML_TYPE_Q4_0 = 2, - GGML_TYPE_Q4_1 = 3, - // GGML_TYPE_Q4_2 = 4, support has been removed - // GGML_TYPE_Q4_3 = 5, support has been removed - GGML_TYPE_Q5_0 = 6, - GGML_TYPE_Q5_1 = 7, - GGML_TYPE_Q8_0 = 8, - GGML_TYPE_Q8_1 = 9, - GGML_TYPE_Q2_K = 10, - GGML_TYPE_Q3_K = 11, - GGML_TYPE_Q4_K = 12, - GGML_TYPE_Q5_K = 13, - GGML_TYPE_Q6_K = 14, - GGML_TYPE_Q8_K = 15, - GGML_TYPE_IQ2_XXS = 16, - GGML_TYPE_IQ2_XS = 17, - GGML_TYPE_IQ3_XXS = 18, - GGML_TYPE_IQ1_S = 19, - GGML_TYPE_IQ4_NL = 20, - GGML_TYPE_IQ3_S = 21, - GGML_TYPE_IQ2_S = 22, - GGML_TYPE_IQ4_XS = 23, - GGML_TYPE_I8 = 24, - GGML_TYPE_I16 = 25, - GGML_TYPE_I32 = 26, - GGML_TYPE_I64 = 27, - GGML_TYPE_F64 = 28, - GGML_TYPE_IQ1_M = 29, - GGML_TYPE_BF16 = 30, - // GGML_TYPE_Q4_0_4_4 = 31, support has been removed from gguf files - // GGML_TYPE_Q4_0_4_8 = 32, - // GGML_TYPE_Q4_0_8_8 = 33, - GGML_TYPE_TQ1_0 = 34, - GGML_TYPE_TQ2_0 = 35, - // GGML_TYPE_IQ4_NL_4_4 = 36, - // GGML_TYPE_IQ4_NL_4_8 = 37, - // GGML_TYPE_IQ4_NL_8_8 = 38, - GGML_TYPE_MXFP4 = 39, // MXFP4 (1 block) - GGML_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale) - GGML_TYPE_Q1_0 = 41, - GGML_TYPE_COUNT = 42, - }; - - // precision - enum ggml_prec { - GGML_PREC_DEFAULT = 0, // stored as ggml_tensor.op_params, 0 by default - GGML_PREC_F32 = 10, - }; - - // op hint - enum ggml_op_hint { - GGML_HINT_NONE = 0, - GGML_HINT_SRC0_IS_HADAMARD = 1, - }; - - // model file types - enum ggml_ftype { - GGML_FTYPE_UNKNOWN = -1, - GGML_FTYPE_ALL_F32 = 0, - GGML_FTYPE_MOSTLY_F16 = 1, // except 1d tensors - GGML_FTYPE_MOSTLY_Q4_0 = 2, // except 1d tensors - GGML_FTYPE_MOSTLY_Q4_1 = 3, // except 1d tensors - GGML_FTYPE_MOSTLY_Q4_1_SOME_F16 = 4, // tok_embeddings.weight and output.weight are F16 - GGML_FTYPE_MOSTLY_Q8_0 = 7, // except 1d tensors - GGML_FTYPE_MOSTLY_Q5_0 = 8, // except 1d tensors - GGML_FTYPE_MOSTLY_Q5_1 = 9, // except 1d tensors - GGML_FTYPE_MOSTLY_Q2_K = 10, // except 1d tensors - GGML_FTYPE_MOSTLY_Q3_K = 11, // except 1d tensors - GGML_FTYPE_MOSTLY_Q4_K = 12, // except 1d tensors - GGML_FTYPE_MOSTLY_Q5_K = 13, // except 1d tensors - GGML_FTYPE_MOSTLY_Q6_K = 14, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ2_XXS = 15, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ2_XS = 16, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ3_XXS = 17, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ1_S = 18, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ4_NL = 19, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ3_S = 20, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ2_S = 21, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ4_XS = 22, // except 1d tensors - GGML_FTYPE_MOSTLY_IQ1_M = 23, // except 1d tensors - GGML_FTYPE_MOSTLY_BF16 = 24, // except 1d tensors - GGML_FTYPE_MOSTLY_MXFP4 = 25, // except 1d tensors - GGML_FTYPE_MOSTLY_NVFP4 = 26, // except 1d tensors - GGML_FTYPE_MOSTLY_Q1_0 = 27, // except 1d tensors - }; - - // available tensor operations: - enum ggml_op { - GGML_OP_NONE = 0, - - GGML_OP_DUP, - GGML_OP_ADD, - GGML_OP_ADD_ID, - GGML_OP_ADD1, - GGML_OP_ACC, - GGML_OP_SUB, - GGML_OP_MUL, - GGML_OP_DIV, - GGML_OP_SQR, - GGML_OP_SQRT, - GGML_OP_LOG, - GGML_OP_SIN, - GGML_OP_COS, - GGML_OP_SUM, - GGML_OP_SUM_ROWS, - GGML_OP_CUMSUM, - GGML_OP_MEAN, - GGML_OP_ARGMAX, - GGML_OP_COUNT_EQUAL, - GGML_OP_REPEAT, - GGML_OP_REPEAT_BACK, - GGML_OP_CONCAT, - GGML_OP_SILU_BACK, - GGML_OP_NORM, // normalize - GGML_OP_RMS_NORM, - GGML_OP_RMS_NORM_BACK, - GGML_OP_GROUP_NORM, - GGML_OP_L2_NORM, - - GGML_OP_MUL_MAT, +#pragma once + +// +// GGML Tensor Library +// +// This documentation is still a work in progress. +// If you wish some specific topics to be covered, feel free to drop a comment: +// +// https://github.com/ggml-org/whisper.cpp/issues/40 +// +// ## Overview +// +// This library implements: +// +// - a set of tensor operations +// - automatic differentiation +// - basic optimization algorithms +// +// The aim of this library is to provide a minimalistic approach for various machine learning tasks. This includes, +// but is not limited to, the following: +// +// - linear regression +// - support vector machines +// - neural networks +// +// The library allows the user to define a certain function using the available tensor operations. This function +// definition is represented internally via a computation graph. Each tensor operation in the function definition +// corresponds to a node in the graph. Having the computation graph defined, the user can choose to compute the +// function's value and/or its gradient with respect to the input variables. Optionally, the function can be optimized +// using one of the available optimization algorithms. +// +// For example, here we define the function: f(x) = a*x^2 + b +// +// { +// struct ggml_init_params params = { +// .mem_size = 16*1024*1024, +// .mem_buffer = NULL, +// }; +// +// // memory allocation happens here +// struct ggml_context * ctx = ggml_init(params); +// +// struct ggml_tensor * x = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1); +// +// ggml_set_param(ctx, x); // x is an input variable +// +// struct ggml_tensor * a = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1); +// struct ggml_tensor * b = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1); +// struct ggml_tensor * x2 = ggml_mul(ctx, x, x); +// struct ggml_tensor * f = ggml_add(ctx, ggml_mul(ctx, a, x2), b); +// +// ... +// } +// +// Notice that the function definition above does not involve any actual computation. The computation is performed only +// when the user explicitly requests it. For example, to compute the function's value at x = 2.0: +// +// { +// ... +// +// struct ggml_cgraph * gf = ggml_new_graph(ctx); +// ggml_build_forward_expand(gf, f); +// +// // set the input variable and parameter values +// ggml_set_f32(x, 2.0f); +// ggml_set_f32(a, 3.0f); +// ggml_set_f32(b, 4.0f); +// +// ggml_graph_compute_with_ctx(ctx, &gf, n_threads); +// +// printf("f = %f\n", ggml_get_f32_1d(f, 0)); +// +// ... +// } +// +// The actual computation is performed in the ggml_graph_compute() function. +// +// The ggml_new_tensor_...() functions create new tensors. They are allocated in the memory buffer provided to the +// ggml_init() function. You have to be careful not to exceed the memory buffer size. Therefore, you have to know +// in advance how much memory you need for your computation. Alternatively, you can allocate a large enough memory +// and after defining the computation graph, call the ggml_used_mem() function to find out how much memory was +// actually needed. +// +// The ggml_set_param() function marks a tensor as an input variable. This is used by the automatic +// differentiation and optimization algorithms. +// +// The described approach allows to define the function graph once and then compute its forward or backward graphs +// multiple times. All computations will use the same memory buffer allocated in the ggml_init() function. This way +// the user can avoid the memory allocation overhead at runtime. +// +// The library supports multi-dimensional tensors - up to 4 dimensions. The FP16 and FP32 data types are first class +// citizens, but in theory the library can be extended to support FP8 and integer data types. +// +// Each tensor operation produces a new tensor. Initially the library was envisioned to support only the use of unary +// and binary operations. Most of the available operations fall into one of these two categories. With time, it became +// clear that the library needs to support more complex operations. The way to support these operations is not clear +// yet, but a few examples are demonstrated in the following operations: +// +// - ggml_permute() +// - ggml_conv_1d_1s() +// - ggml_conv_1d_2s() +// +// For each tensor operator, the library implements a forward and backward computation function. The forward function +// computes the output tensor value given the input tensor values. The backward function computes the adjoint of the +// input tensors given the adjoint of the output tensor. For a detailed explanation of what this means, take a +// calculus class, or watch the following video: +// +// What is Automatic Differentiation? +// https://www.youtube.com/watch?v=wG_nF1awSSY +// +// +// ## Tensor data (struct ggml_tensor) +// +// The tensors are stored in memory via the ggml_tensor struct. The structure provides information about the size of +// the tensor, the data type, and the memory buffer where the tensor data is stored. Additionally, it contains +// pointers to the "source" tensors - i.e. the tensors that were used to compute the current tensor. For example: +// +// { +// struct ggml_tensor * c = ggml_add(ctx, a, b); +// +// assert(c->src[0] == a); +// assert(c->src[1] == b); +// } +// +// The multi-dimensional tensors are stored in row-major order. The ggml_tensor struct contains fields for the +// number of elements in each dimension ("ne") as well as the number of bytes ("nb", a.k.a. stride). This allows +// to store tensors that are not contiguous in memory, which is useful for operations such as transposition and +// permutation. All tensor operations have to take the stride into account and not assume that the tensor is +// contiguous in memory. +// +// The data of the tensor is accessed via the "data" pointer. For example: +// +// { +// const int nx = 2; +// const int ny = 3; +// +// struct ggml_tensor * a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, nx, ny); +// +// for (int y = 0; y < ny; y++) { +// for (int x = 0; x < nx; x++) { +// *(float *) ((char *) a->data + y*a->nb[1] + x*a->nb[0]) = x + y; +// } +// } +// +// ... +// } +// +// Alternatively, there are helper functions, such as ggml_get_f32_1d() and ggml_set_f32_1d() that can be used. +// +// ## The matrix multiplication operator (ggml_mul_mat) +// +// TODO +// +// +// ## Multi-threading +// +// TODO +// +// +// ## Overview of ggml.c +// +// TODO +// +// +// ## SIMD optimizations +// +// TODO +// +// +// ## Debugging ggml +// +// TODO +// +// + +#ifdef GGML_SHARED +# if defined(_WIN32) && !defined(__MINGW32__) +# ifdef GGML_BUILD +# define GGML_API __declspec(dllexport) extern +# else +# define GGML_API __declspec(dllimport) extern +# endif +# else +# define GGML_API __attribute__ ((visibility ("default"))) extern +# endif +#else +# define GGML_API extern +#endif + +// TODO: support for clang +#ifdef __GNUC__ +# define GGML_DEPRECATED(func, hint) func __attribute__((deprecated(hint))) +#elif defined(_MSC_VER) +# define GGML_DEPRECATED(func, hint) __declspec(deprecated(hint)) func +#else +# define GGML_DEPRECATED(func, hint) func +#endif + +#ifndef __GNUC__ +# define GGML_ATTRIBUTE_FORMAT(...) +#elif defined(__MINGW32__) && !defined(__clang__) +# define GGML_ATTRIBUTE_FORMAT(...) __attribute__((format(gnu_printf, __VA_ARGS__))) +#else +# define GGML_ATTRIBUTE_FORMAT(...) __attribute__((format(printf, __VA_ARGS__))) +#endif + +#if defined(_WIN32) && !defined(_WIN32_WINNT) +# define _WIN32_WINNT 0x0A00 +#endif + +#include +#include +#include +#include + +#define GGML_FILE_MAGIC 0x67676d6c // "ggml" +#define GGML_FILE_VERSION 2 + +#define GGML_QNT_VERSION 2 // bump this on quantization format changes +#define GGML_QNT_VERSION_FACTOR 1000 // do not change this + +#define GGML_MAX_DIMS 4 +#define GGML_MAX_PARAMS 2048 +#define GGML_MAX_SRC 10 +#define GGML_MAX_N_THREADS 512 +#define GGML_MAX_OP_PARAMS 64 + +#ifndef GGML_MAX_NAME +# define GGML_MAX_NAME 64 +#endif + +#define GGML_DEFAULT_N_THREADS 4 +#define GGML_DEFAULT_GRAPH_SIZE 2048 + +#if UINTPTR_MAX == 0xFFFFFFFF + #define GGML_MEM_ALIGN 4 +#elif defined(__EMSCRIPTEN__) +// emscripten uses max_align_t == 8, so we need GGML_MEM_ALIGN == 8 for 64-bit wasm. +// (for 32-bit wasm, the first conditional is true and GGML_MEM_ALIGN stays 4.) +// ref: https://github.com/ggml-org/llama.cpp/pull/18628 + #define GGML_MEM_ALIGN 8 +#else + #define GGML_MEM_ALIGN 16 +#endif + +#define GGML_EXIT_SUCCESS 0 +#define GGML_EXIT_ABORTED 1 + +// TODO: convert to enum https://github.com/ggml-org/llama.cpp/pull/16187#discussion_r2388538726 +#define GGML_ROPE_TYPE_NORMAL 0 +#define GGML_ROPE_TYPE_NEOX 2 +#define GGML_ROPE_TYPE_MROPE 8 +#define GGML_ROPE_TYPE_VISION 24 +#define GGML_ROPE_TYPE_IMROPE 40 // binary: 101000 + +#define GGML_MROPE_SECTIONS 4 + +#define GGML_UNUSED(x) (void)(x) +#ifdef __CUDACC__ +template +__host__ __device__ constexpr inline void ggml_unused_vars_impl(Args&&...) noexcept {} +#define GGML_UNUSED_VARS(...) ggml_unused_vars_impl(__VA_ARGS__) +#else +#define GGML_UNUSED_VARS(...) do { (void)sizeof((__VA_ARGS__, 0)); } while(0) +#endif // __CUDACC__ + +#define GGML_PAD(x, n) (((x) + (n) - 1) & ~((n) - 1)) + +#ifndef NDEBUG +# define GGML_UNREACHABLE() do { fprintf(stderr, "statement should be unreachable\n"); abort(); } while(0) +#elif defined(__GNUC__) +# define GGML_UNREACHABLE() __builtin_unreachable() +#elif defined(_MSC_VER) +# define GGML_UNREACHABLE() __assume(0) +#else +# define GGML_UNREACHABLE() ((void) 0) +#endif + +#ifdef __cplusplus +# define GGML_NORETURN [[noreturn]] +#elif defined(_MSC_VER) +# define GGML_NORETURN __declspec(noreturn) +#else +# define GGML_NORETURN _Noreturn +#endif + +#define GGML_ABORT(...) ggml_abort(__FILE__, __LINE__, __VA_ARGS__) +#define GGML_ASSERT(x) if (!(x)) GGML_ABORT("GGML_ASSERT(%s) failed", #x) + +// used to copy the number of elements and stride in bytes of tensors into local variables. +// main purpose is to reduce code duplication and improve readability. +// +// example: +// +// GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne); +// GGML_TENSOR_LOCALS(size_t, nb1, src1, nb); +// +#define GGML_TENSOR_LOCALS_1(type, prefix, pointer, array) \ + const type prefix##0 = (pointer) ? (pointer)->array[0] : 0; \ + GGML_UNUSED(prefix##0); +#define GGML_TENSOR_LOCALS_2(type, prefix, pointer, array) \ + GGML_TENSOR_LOCALS_1 (type, prefix, pointer, array) \ + const type prefix##1 = (pointer) ? (pointer)->array[1] : 0; \ + GGML_UNUSED(prefix##1); +#define GGML_TENSOR_LOCALS_3(type, prefix, pointer, array) \ + GGML_TENSOR_LOCALS_2 (type, prefix, pointer, array) \ + const type prefix##2 = (pointer) ? (pointer)->array[2] : 0; \ + GGML_UNUSED(prefix##2); +#define GGML_TENSOR_LOCALS(type, prefix, pointer, array) \ + GGML_TENSOR_LOCALS_3 (type, prefix, pointer, array) \ + const type prefix##3 = (pointer) ? (pointer)->array[3] : 0; \ + GGML_UNUSED(prefix##3); + +#define GGML_TENSOR_UNARY_OP_LOCALS \ + GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne) \ + GGML_TENSOR_LOCALS(size_t, nb0, src0, nb) \ + GGML_TENSOR_LOCALS(int64_t, ne, dst, ne) \ + GGML_TENSOR_LOCALS(size_t, nb, dst, nb) + +#define GGML_TENSOR_BINARY_OP_LOCALS \ + GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne) \ + GGML_TENSOR_LOCALS(size_t, nb0, src0, nb) \ + GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne) \ + GGML_TENSOR_LOCALS(size_t, nb1, src1, nb) \ + GGML_TENSOR_LOCALS(int64_t, ne, dst, ne) \ + GGML_TENSOR_LOCALS(size_t, nb, dst, nb) + +#define GGML_TENSOR_TERNARY_OP_LOCALS \ + GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne) \ + GGML_TENSOR_LOCALS(size_t, nb0, src0, nb) \ + GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne) \ + GGML_TENSOR_LOCALS(size_t, nb1, src1, nb) \ + GGML_TENSOR_LOCALS(int64_t, ne2, src2, ne) \ + GGML_TENSOR_LOCALS(size_t, nb2, src2, nb) \ + GGML_TENSOR_LOCALS(int64_t, ne, dst, ne) \ + GGML_TENSOR_LOCALS(size_t, nb, dst, nb) + +#define GGML_TENSOR_BINARY_OP_LOCALS01 \ + GGML_TENSOR_LOCALS(int64_t, ne0, src0, ne) \ + GGML_TENSOR_LOCALS(size_t, nb0, src0, nb) \ + GGML_TENSOR_LOCALS(int64_t, ne1, src1, ne) \ + GGML_TENSOR_LOCALS(size_t, nb1, src1, nb) + +#ifdef __cplusplus +extern "C" { +#endif + + // Function type used in fatal error callbacks + typedef void (*ggml_abort_callback_t)(const char * error_message); + + // Set the abort callback (passing null will restore original abort functionality: printing a message to stdout) + // Returns the old callback for chaining + GGML_API ggml_abort_callback_t ggml_set_abort_callback(ggml_abort_callback_t callback); + + GGML_NORETURN GGML_ATTRIBUTE_FORMAT(3, 4) + GGML_API void ggml_abort(const char * file, int line, const char * fmt, ...); + + enum ggml_status { + GGML_STATUS_ALLOC_FAILED = -2, + GGML_STATUS_FAILED = -1, + GGML_STATUS_SUCCESS = 0, + GGML_STATUS_ABORTED = 1, + }; + + // get ggml_status name string + GGML_API const char * ggml_status_to_string(enum ggml_status status); + + // ieee 754-2008 half-precision float16 + // todo: make this not an integral type + typedef uint16_t ggml_fp16_t; + GGML_API float ggml_fp16_to_fp32(ggml_fp16_t); + GGML_API ggml_fp16_t ggml_fp32_to_fp16(float); + GGML_API void ggml_fp16_to_fp32_row(const ggml_fp16_t *, float *, int64_t); + GGML_API void ggml_fp32_to_fp16_row(const float *, ggml_fp16_t *, int64_t); + + // google brain half-precision bfloat16 + typedef struct { uint16_t bits; } ggml_bf16_t; + GGML_API ggml_bf16_t ggml_fp32_to_bf16(float); + GGML_API float ggml_bf16_to_fp32(ggml_bf16_t); // consider just doing << 16 + GGML_API void ggml_bf16_to_fp32_row(const ggml_bf16_t *, float *, int64_t); + GGML_API void ggml_fp32_to_bf16_row_ref(const float *, ggml_bf16_t *, int64_t); + GGML_API void ggml_fp32_to_bf16_row(const float *, ggml_bf16_t *, int64_t); + + struct ggml_object; + struct ggml_context; + struct ggml_cgraph; + + // NOTE: always add types at the end of the enum to keep backward compatibility + enum ggml_type { + GGML_TYPE_F32 = 0, + GGML_TYPE_F16 = 1, + GGML_TYPE_Q4_0 = 2, + GGML_TYPE_Q4_1 = 3, + // GGML_TYPE_Q4_2 = 4, support has been removed + // GGML_TYPE_Q4_3 = 5, support has been removed + GGML_TYPE_Q5_0 = 6, + GGML_TYPE_Q5_1 = 7, + GGML_TYPE_Q8_0 = 8, + GGML_TYPE_Q8_1 = 9, + GGML_TYPE_Q2_K = 10, + GGML_TYPE_Q3_K = 11, + GGML_TYPE_Q4_K = 12, + GGML_TYPE_Q5_K = 13, + GGML_TYPE_Q6_K = 14, + GGML_TYPE_Q8_K = 15, + GGML_TYPE_IQ2_XXS = 16, + GGML_TYPE_IQ2_XS = 17, + GGML_TYPE_IQ3_XXS = 18, + GGML_TYPE_IQ1_S = 19, + GGML_TYPE_IQ4_NL = 20, + GGML_TYPE_IQ3_S = 21, + GGML_TYPE_IQ2_S = 22, + GGML_TYPE_IQ4_XS = 23, + GGML_TYPE_I8 = 24, + GGML_TYPE_I16 = 25, + GGML_TYPE_I32 = 26, + GGML_TYPE_I64 = 27, + GGML_TYPE_F64 = 28, + GGML_TYPE_IQ1_M = 29, + GGML_TYPE_BF16 = 30, + // GGML_TYPE_Q4_0_4_4 = 31, support has been removed from gguf files + // GGML_TYPE_Q4_0_4_8 = 32, + // GGML_TYPE_Q4_0_8_8 = 33, + GGML_TYPE_TQ1_0 = 34, + GGML_TYPE_TQ2_0 = 35, + // GGML_TYPE_IQ4_NL_4_4 = 36, + // GGML_TYPE_IQ4_NL_4_8 = 37, + // GGML_TYPE_IQ4_NL_8_8 = 38, + GGML_TYPE_MXFP4 = 39, // MXFP4 (1 block) + GGML_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale) + GGML_TYPE_Q1_0 = 41, + // INT8 / ternary with a single per-tensor scale, stored as one F32 + // immediately after the payload rather than interleaved per block. + // Used by the VibeASR CPU pipeline; see ggml_mul_mat_add(). + // NOTE: 36/37 are deliberately avoided even though VibeASR's own fork + // uses them -- they are retired IQ4_NL_4_4/4_8 slots and reusing an ID + // would silently misread GGUF files that still carry the old type. + GGML_TYPE_I8_S = 42, + GGML_TYPE_I2_S = 43, + GGML_TYPE_COUNT = 44, + }; + + // precision + enum ggml_prec { + GGML_PREC_DEFAULT = 0, // stored as ggml_tensor.op_params, 0 by default + GGML_PREC_F32 = 10, + }; + + // op hint + enum ggml_op_hint { + GGML_HINT_NONE = 0, + GGML_HINT_SRC0_IS_HADAMARD = 1, + }; + + // model file types + enum ggml_ftype { + GGML_FTYPE_UNKNOWN = -1, + GGML_FTYPE_ALL_F32 = 0, + GGML_FTYPE_MOSTLY_F16 = 1, // except 1d tensors + GGML_FTYPE_MOSTLY_Q4_0 = 2, // except 1d tensors + GGML_FTYPE_MOSTLY_Q4_1 = 3, // except 1d tensors + GGML_FTYPE_MOSTLY_Q4_1_SOME_F16 = 4, // tok_embeddings.weight and output.weight are F16 + GGML_FTYPE_MOSTLY_Q8_0 = 7, // except 1d tensors + GGML_FTYPE_MOSTLY_Q5_0 = 8, // except 1d tensors + GGML_FTYPE_MOSTLY_Q5_1 = 9, // except 1d tensors + GGML_FTYPE_MOSTLY_Q2_K = 10, // except 1d tensors + GGML_FTYPE_MOSTLY_Q3_K = 11, // except 1d tensors + GGML_FTYPE_MOSTLY_Q4_K = 12, // except 1d tensors + GGML_FTYPE_MOSTLY_Q5_K = 13, // except 1d tensors + GGML_FTYPE_MOSTLY_Q6_K = 14, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ2_XXS = 15, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ2_XS = 16, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ3_XXS = 17, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ1_S = 18, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ4_NL = 19, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ3_S = 20, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ2_S = 21, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ4_XS = 22, // except 1d tensors + GGML_FTYPE_MOSTLY_IQ1_M = 23, // except 1d tensors + GGML_FTYPE_MOSTLY_BF16 = 24, // except 1d tensors + GGML_FTYPE_MOSTLY_MXFP4 = 25, // except 1d tensors + GGML_FTYPE_MOSTLY_NVFP4 = 26, // except 1d tensors + GGML_FTYPE_MOSTLY_Q1_0 = 27, // except 1d tensors + }; + + // available tensor operations: + enum ggml_op { + GGML_OP_NONE = 0, + + GGML_OP_DUP, + GGML_OP_ADD, + GGML_OP_ADD_ID, + GGML_OP_ADD1, + GGML_OP_ACC, + GGML_OP_SUB, + GGML_OP_MUL, + GGML_OP_DIV, + GGML_OP_SQR, + GGML_OP_SQRT, + GGML_OP_LOG, + GGML_OP_SIN, + GGML_OP_COS, + GGML_OP_SUM, + GGML_OP_SUM_ROWS, + GGML_OP_CUMSUM, + GGML_OP_MEAN, + GGML_OP_ARGMAX, + GGML_OP_COUNT_EQUAL, + GGML_OP_REPEAT, + GGML_OP_REPEAT_BACK, + GGML_OP_CONCAT, + GGML_OP_SILU_BACK, + GGML_OP_NORM, // normalize + GGML_OP_RMS_NORM, + GGML_OP_RMS_NORM_BACK, + GGML_OP_GROUP_NORM, + GGML_OP_L2_NORM, + + GGML_OP_MUL_MAT, GGML_OP_MUL_MAT_PACK4, - GGML_OP_MUL_MAT_ID, - GGML_OP_OUT_PROD, - - GGML_OP_SCALE, - GGML_OP_SET, - GGML_OP_CPY, - GGML_OP_CONT, - GGML_OP_RESHAPE, - GGML_OP_VIEW, - GGML_OP_PERMUTE, - GGML_OP_TRANSPOSE, - GGML_OP_GET_ROWS, - GGML_OP_GET_ROWS_BACK, - GGML_OP_SET_ROWS, - GGML_OP_DIAG, - GGML_OP_DIAG_MASK_INF, - GGML_OP_DIAG_MASK_ZERO, - GGML_OP_SOFT_MAX, - GGML_OP_SOFT_MAX_BACK, - GGML_OP_ROPE, - GGML_OP_ROPE_BACK, + GGML_OP_MUL_MAT_ID, + GGML_OP_OUT_PROD, + + GGML_OP_SCALE, + GGML_OP_SET, + GGML_OP_CPY, + GGML_OP_CONT, + GGML_OP_RESHAPE, + GGML_OP_VIEW, + GGML_OP_PERMUTE, + GGML_OP_TRANSPOSE, + GGML_OP_GET_ROWS, + GGML_OP_GET_ROWS_BACK, + GGML_OP_SET_ROWS, + GGML_OP_DIAG, + GGML_OP_DIAG_MASK_INF, + GGML_OP_DIAG_MASK_ZERO, + GGML_OP_SOFT_MAX, + GGML_OP_SOFT_MAX_BACK, + GGML_OP_ROPE, + GGML_OP_ROPE_BACK, GGML_OP_CLAMP, GGML_OP_CONV_TRANSPOSE_1D, GGML_OP_IM2COL, @@ -539,1482 +547,1515 @@ extern "C" { GGML_OP_IM2COL_3D, GGML_OP_COL2IM_1D, GGML_OP_CONV_2D, - GGML_OP_CONV_3D, - GGML_OP_CONV_2D_DW, - GGML_OP_CONV_TRANSPOSE_2D, - GGML_OP_POOL_1D, - GGML_OP_POOL_2D, - GGML_OP_POOL_2D_BACK, - GGML_OP_UPSCALE, - GGML_OP_PAD, - GGML_OP_PAD_REFLECT_1D, - GGML_OP_ROLL, - GGML_OP_ARANGE, - GGML_OP_TIMESTEP_EMBEDDING, - GGML_OP_ARGSORT, - GGML_OP_TOP_K, - GGML_OP_LEAKY_RELU, - GGML_OP_TRI, + GGML_OP_CONV_3D, + GGML_OP_CONV_2D_DW, + GGML_OP_CONV_TRANSPOSE_2D, + GGML_OP_POOL_1D, + GGML_OP_POOL_2D, + GGML_OP_POOL_2D_BACK, + GGML_OP_UPSCALE, + GGML_OP_PAD, + GGML_OP_PAD_REFLECT_1D, + GGML_OP_ROLL, + GGML_OP_ARANGE, + GGML_OP_TIMESTEP_EMBEDDING, + GGML_OP_ARGSORT, + GGML_OP_TOP_K, + GGML_OP_LEAKY_RELU, + GGML_OP_TRI, GGML_OP_FILL, - GGML_OP_FLASH_ATTN_EXT, - GGML_OP_SAGE_ATTN2, - GGML_OP_SAGE_ATTN2_I8, - GGML_OP_FLASH_ATTN_BACK, - GGML_OP_SSM_CONV, - GGML_OP_SSM_SCAN, - GGML_OP_WIN_PART, - GGML_OP_WIN_UNPART, - GGML_OP_GET_REL_POS, - GGML_OP_ADD_REL_POS, - GGML_OP_RWKV_WKV6, - GGML_OP_GATED_LINEAR_ATTN, - GGML_OP_RWKV_WKV7, - GGML_OP_SOLVE_TRI, - GGML_OP_GATED_DELTA_NET, - - GGML_OP_UNARY, - - GGML_OP_MAP_CUSTOM1, - GGML_OP_MAP_CUSTOM2, - GGML_OP_MAP_CUSTOM3, - - GGML_OP_CUSTOM, - - GGML_OP_CROSS_ENTROPY_LOSS, - GGML_OP_CROSS_ENTROPY_LOSS_BACK, - GGML_OP_OPT_STEP_ADAMW, - GGML_OP_OPT_STEP_SGD, + GGML_OP_FLASH_ATTN_EXT, + GGML_OP_SAGE_ATTN2, + GGML_OP_SAGE_ATTN2_I8, + GGML_OP_FLASH_ATTN_BACK, + GGML_OP_SSM_CONV, + GGML_OP_SSM_SCAN, + GGML_OP_WIN_PART, + GGML_OP_WIN_UNPART, + GGML_OP_GET_REL_POS, + GGML_OP_ADD_REL_POS, + GGML_OP_RWKV_WKV6, + GGML_OP_GATED_LINEAR_ATTN, + GGML_OP_RWKV_WKV7, + GGML_OP_SOLVE_TRI, + GGML_OP_GATED_DELTA_NET, + + GGML_OP_UNARY, + + GGML_OP_MAP_CUSTOM1, + GGML_OP_MAP_CUSTOM2, + GGML_OP_MAP_CUSTOM3, + + GGML_OP_CUSTOM, + + GGML_OP_CROSS_ENTROPY_LOSS, + GGML_OP_CROSS_ENTROPY_LOSS_BACK, + GGML_OP_OPT_STEP_ADAMW, + GGML_OP_OPT_STEP_SGD, + + GGML_OP_GLU, + GGML_OP_CONVROT_LINEAR, + + // VibeASR CPU INT8 pipeline. Appended at the tail so every existing + // op keeps its value -- GGML_OP_NAME and GGML_OP_SYMBOL are positional. + GGML_OP_ADD_SCALED, + GGML_OP_RMS_NORM_SCALED, + GGML_OP_MUL_MAT_ADD, + GGML_OP_MUL_MAT_ADD_RELU, + GGML_OP_IM2COL_ASYM, + // audio8_tts codec per-tap accumulation and fused snake (audio8 PR). + GGML_OP_MUL_MAT_ACC, + GGML_OP_SNAKE_1D, + + GGML_OP_COUNT, + }; + + enum ggml_unary_op { + GGML_UNARY_OP_ABS, + GGML_UNARY_OP_SGN, + GGML_UNARY_OP_NEG, + GGML_UNARY_OP_STEP, + GGML_UNARY_OP_TANH, + GGML_UNARY_OP_ELU, + GGML_UNARY_OP_RELU, + GGML_UNARY_OP_SIGMOID, + GGML_UNARY_OP_GELU, + GGML_UNARY_OP_GELU_QUICK, + GGML_UNARY_OP_SILU, + GGML_UNARY_OP_HARDSWISH, + GGML_UNARY_OP_HARDSIGMOID, + GGML_UNARY_OP_EXP, + GGML_UNARY_OP_EXPM1, + GGML_UNARY_OP_SOFTPLUS, + GGML_UNARY_OP_GELU_ERF, + GGML_UNARY_OP_XIELU, + GGML_UNARY_OP_FLOOR, + GGML_UNARY_OP_CEIL, + GGML_UNARY_OP_ROUND, + GGML_UNARY_OP_TRUNC, + GGML_UNARY_OP_ROUND_BF16, + + GGML_UNARY_OP_COUNT, + }; + + enum ggml_glu_op { + GGML_GLU_OP_REGLU, + GGML_GLU_OP_GEGLU, + GGML_GLU_OP_SWIGLU, + GGML_GLU_OP_SWIGLU_OAI, + GGML_GLU_OP_GEGLU_ERF, + GGML_GLU_OP_GEGLU_QUICK, + + GGML_GLU_OP_COUNT, + }; + + enum ggml_object_type { + GGML_OBJECT_TYPE_TENSOR, + GGML_OBJECT_TYPE_GRAPH, + GGML_OBJECT_TYPE_WORK_BUFFER + }; + + enum ggml_log_level { + GGML_LOG_LEVEL_NONE = 0, + GGML_LOG_LEVEL_DEBUG = 1, + GGML_LOG_LEVEL_INFO = 2, + GGML_LOG_LEVEL_WARN = 3, + GGML_LOG_LEVEL_ERROR = 4, + GGML_LOG_LEVEL_CONT = 5, // continue previous log + }; + + // this tensor... + enum ggml_tensor_flag { + GGML_TENSOR_FLAG_INPUT = 1, // ...is an input for the GGML compute graph + GGML_TENSOR_FLAG_OUTPUT = 2, // ...is an output for the GGML compute graph + GGML_TENSOR_FLAG_PARAM = 4, // ...contains trainable parameters + GGML_TENSOR_FLAG_LOSS = 8, // ...defines loss for numerical optimization (multiple loss tensors add up) + GGML_TENSOR_FLAG_COMPUTE = 16, // ...must be computed + }; + + enum ggml_tri_type { + GGML_TRI_TYPE_UPPER_DIAG = 0, + GGML_TRI_TYPE_UPPER = 1, + GGML_TRI_TYPE_LOWER_DIAG = 2, + GGML_TRI_TYPE_LOWER = 3 + }; + + struct ggml_init_params { + // memory pool + size_t mem_size; // bytes + void * mem_buffer; // if NULL, memory will be allocated internally + bool no_alloc; // don't allocate memory for the tensor data + }; + + // n-dimensional tensor + struct ggml_tensor { + enum ggml_type type; + + struct ggml_backend_buffer * buffer; + + int64_t ne[GGML_MAX_DIMS]; // number of elements + size_t nb[GGML_MAX_DIMS]; // stride in bytes: + // nb[0] = ggml_type_size(type) + // nb[1] = nb[0] * (ne[0] / ggml_blck_size(type)) + padding + // nb[i] = nb[i-1] * ne[i-1] + + // compute data + enum ggml_op op; + + // op params - allocated as int32_t for alignment + int32_t op_params[GGML_MAX_OP_PARAMS / sizeof(int32_t)]; + + int32_t flags; + + struct ggml_tensor * src[GGML_MAX_SRC]; + + // source tensor and offset for views + struct ggml_tensor * view_src; + size_t view_offs; + + void * data; + + char name[GGML_MAX_NAME]; + + void * extra; // extra things e.g. for ggml-cuda.cu + + char padding[8]; + }; + + static const size_t GGML_TENSOR_SIZE = sizeof(struct ggml_tensor); + + // Abort callback + // If not NULL, called before ggml computation + // If it returns true, the computation is aborted + typedef bool (*ggml_abort_callback)(void * data); + + + // + // GUID + // + + // GUID types + typedef uint8_t ggml_guid[16]; + typedef ggml_guid * ggml_guid_t; + + GGML_API bool ggml_guid_matches(ggml_guid_t guid_a, ggml_guid_t guid_b); + + // misc + + GGML_API const char * ggml_version(void); + GGML_API const char * ggml_commit(void); + + GGML_API void ggml_time_init(void); // call this once at the beginning of the program + GGML_API int64_t ggml_time_ms(void); + GGML_API int64_t ggml_time_us(void); + GGML_API int64_t ggml_cycles(void); + GGML_API int64_t ggml_cycles_per_ms(void); + + // accepts a UTF-8 path, even on Windows + GGML_API FILE * ggml_fopen(const char * fname, const char * mode); + + GGML_API void ggml_print_object (const struct ggml_object * obj); + GGML_API void ggml_print_objects(const struct ggml_context * ctx); + + GGML_API int64_t ggml_nelements (const struct ggml_tensor * tensor); + GGML_API int64_t ggml_nrows (const struct ggml_tensor * tensor); + GGML_API size_t ggml_nbytes (const struct ggml_tensor * tensor); + GGML_API size_t ggml_nbytes_pad(const struct ggml_tensor * tensor); // same as ggml_nbytes() but padded to GGML_MEM_ALIGN + + GGML_API int64_t ggml_blck_size(enum ggml_type type); + GGML_API size_t ggml_type_size(enum ggml_type type); // size in bytes for all elements in a block + GGML_API size_t ggml_row_size (enum ggml_type type, int64_t ne); // size in bytes for all elements in a row + + GGML_DEPRECATED( + GGML_API double ggml_type_sizef(enum ggml_type type), // ggml_type_size()/ggml_blck_size() as float + "use ggml_row_size() instead"); + + GGML_API const char * ggml_type_name(enum ggml_type type); + GGML_API const char * ggml_op_name (enum ggml_op op); + GGML_API const char * ggml_op_symbol(enum ggml_op op); + + GGML_API const char * ggml_unary_op_name(enum ggml_unary_op op); + GGML_API const char * ggml_glu_op_name(enum ggml_glu_op op); + GGML_API const char * ggml_op_desc(const struct ggml_tensor * t); // unary or op name + + GGML_API size_t ggml_element_size(const struct ggml_tensor * tensor); + + GGML_API bool ggml_is_quantized(enum ggml_type type); + + // TODO: temporary until model loading of ggml examples is refactored + GGML_API enum ggml_type ggml_ftype_to_ggml_type(enum ggml_ftype ftype); + + GGML_API bool ggml_is_transposed(const struct ggml_tensor * tensor); + GGML_API bool ggml_is_permuted (const struct ggml_tensor * tensor); + GGML_API bool ggml_is_empty (const struct ggml_tensor * tensor); + GGML_API bool ggml_is_view (const struct ggml_tensor * tensor); + GGML_API bool ggml_is_scalar (const struct ggml_tensor * tensor); + GGML_API bool ggml_is_vector (const struct ggml_tensor * tensor); + GGML_API bool ggml_is_matrix (const struct ggml_tensor * tensor); + GGML_API bool ggml_is_3d (const struct ggml_tensor * tensor); + GGML_API int ggml_n_dims (const struct ggml_tensor * tensor); // returns 1 for scalars + + // returns whether the tensor elements can be iterated over with a flattened index (no gaps, no permutation) + GGML_API bool ggml_is_contiguous (const struct ggml_tensor * tensor); + GGML_API bool ggml_is_contiguous_0(const struct ggml_tensor * tensor); // same as ggml_is_contiguous() + GGML_API bool ggml_is_contiguous_1(const struct ggml_tensor * tensor); // contiguous for dims >= 1 + GGML_API bool ggml_is_contiguous_2(const struct ggml_tensor * tensor); // contiguous for dims >= 2 + + // returns whether the tensor elements are allocated as one contiguous block of memory (no gaps, but permutation ok) + GGML_API bool ggml_is_contiguously_allocated(const struct ggml_tensor * tensor); + + // true for tensor that is stored in memory as CxWxHxN and has been permuted to WxHxCxN + GGML_API bool ggml_is_contiguous_channels(const struct ggml_tensor * tensor); + + // true if the elements in dimension 0 are contiguous, or there is just 1 block of elements + GGML_API bool ggml_is_contiguous_rows(const struct ggml_tensor * tensor); + + GGML_API bool ggml_are_same_shape (const struct ggml_tensor * t0, const struct ggml_tensor * t1); + GGML_API bool ggml_are_same_stride(const struct ggml_tensor * t0, const struct ggml_tensor * t1); + + GGML_API bool ggml_can_repeat(const struct ggml_tensor * t0, const struct ggml_tensor * t1); + + // use this to compute the memory overhead of a tensor + GGML_API size_t ggml_tensor_overhead(void); + + GGML_API bool ggml_validate_row_data(enum ggml_type type, const void * data, size_t nbytes); + + // main + + GGML_API struct ggml_context * ggml_init (struct ggml_init_params params); + GGML_API void ggml_reset(struct ggml_context * ctx); + GGML_API void ggml_free (struct ggml_context * ctx); + + GGML_API size_t ggml_used_mem(const struct ggml_context * ctx); + + GGML_API bool ggml_get_no_alloc(struct ggml_context * ctx); + GGML_API void ggml_set_no_alloc(struct ggml_context * ctx, bool no_alloc); + + GGML_API void * ggml_get_mem_buffer (const struct ggml_context * ctx); + GGML_API size_t ggml_get_mem_size (const struct ggml_context * ctx); + GGML_API size_t ggml_get_max_tensor_size(const struct ggml_context * ctx); + + GGML_API struct ggml_tensor * ggml_new_tensor( + struct ggml_context * ctx, + enum ggml_type type, + int n_dims, + const int64_t *ne); + + GGML_API struct ggml_tensor * ggml_new_tensor_1d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0); + + GGML_API struct ggml_tensor * ggml_new_tensor_2d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0, + int64_t ne1); + + GGML_API struct ggml_tensor * ggml_new_tensor_3d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0, + int64_t ne1, + int64_t ne2); + + GGML_API struct ggml_tensor * ggml_new_tensor_4d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3); + + GGML_API void * ggml_new_buffer(struct ggml_context * ctx, size_t nbytes); + + GGML_API struct ggml_tensor * ggml_dup_tensor (struct ggml_context * ctx, const struct ggml_tensor * src); + GGML_API struct ggml_tensor * ggml_view_tensor(struct ggml_context * ctx, struct ggml_tensor * src); + + // Context tensor enumeration and lookup + GGML_API struct ggml_tensor * ggml_get_first_tensor(const struct ggml_context * ctx); + GGML_API struct ggml_tensor * ggml_get_next_tensor (const struct ggml_context * ctx, struct ggml_tensor * tensor); + GGML_API struct ggml_tensor * ggml_get_tensor(struct ggml_context * ctx, const char * name); + + // Converts a flat index into coordinates + GGML_API void ggml_unravel_index(const struct ggml_tensor * tensor, int64_t i, int64_t * i0, int64_t * i1, int64_t * i2, int64_t * i3); + + GGML_API enum ggml_unary_op ggml_get_unary_op(const struct ggml_tensor * tensor); + GGML_API enum ggml_glu_op ggml_get_glu_op(const struct ggml_tensor * tensor); + + GGML_API void * ggml_get_data (const struct ggml_tensor * tensor); + GGML_API float * ggml_get_data_f32(const struct ggml_tensor * tensor); + + GGML_API const char * ggml_get_name (const struct ggml_tensor * tensor); + GGML_API struct ggml_tensor * ggml_set_name ( struct ggml_tensor * tensor, const char * name); + GGML_ATTRIBUTE_FORMAT(2, 3) + GGML_API struct ggml_tensor * ggml_format_name( struct ggml_tensor * tensor, const char * fmt, ...); + + // Tensor flags + GGML_API void ggml_set_input(struct ggml_tensor * tensor); + GGML_API void ggml_set_output(struct ggml_tensor * tensor); + GGML_API void ggml_set_param(struct ggml_tensor * tensor); + GGML_API void ggml_set_loss(struct ggml_tensor * tensor); + + // + // operations on tensors with backpropagation + // + + GGML_API struct ggml_tensor * ggml_dup( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_dup_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_add( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_add_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_add_cast( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + enum ggml_type type); + + // dst[i0, i1, i2] = a[i0, i1, i2] + b[i0, ids[i1, i2]] + GGML_API struct ggml_tensor * ggml_add_id( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * ids); + + GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_add1( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b), + "use ggml_add instead"); + + GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_add1_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b), + "use ggml_add_inplace instead"); + + // dst = a + // view(dst, nb1, nb2, nb3, offset) += b + // return dst + GGML_API struct ggml_tensor * ggml_acc( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset); + + GGML_API struct ggml_tensor * ggml_acc_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset); + + GGML_API struct ggml_tensor * ggml_sub( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_sub_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_mul( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_mul_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_div( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_div_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_sqr( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sqr_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sqrt( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sqrt_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_log( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_log_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_expm1( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_expm1_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_softplus( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_softplus_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sin( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sin_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_cos( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_cos_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // return scalar + GGML_API struct ggml_tensor * ggml_sum( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // sums along rows, with input shape [a,b,c,d] return shape [1,b,c,d] + GGML_API struct ggml_tensor * ggml_sum_rows( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_cumsum( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // mean along rows + GGML_API struct ggml_tensor * ggml_mean( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // argmax along rows + GGML_API struct ggml_tensor * ggml_argmax( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // count number of equal elements in a and b + GGML_API struct ggml_tensor * ggml_count_equal( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // if a is the same shape as b, and a is not parameter, return a + // otherwise, return a new tensor: repeat(a) to fit in b + GGML_API struct ggml_tensor * ggml_repeat( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // repeat a to the specified shape + GGML_API struct ggml_tensor * ggml_repeat_4d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3); + + // sums repetitions in a into shape of b + GGML_API struct ggml_tensor * ggml_repeat_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); // sum up values that are adjacent in dims > 0 instead of repeated with same stride + + // concat a and b along dim + // used in stable-diffusion + GGML_API struct ggml_tensor * ggml_concat( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int dim); + + GGML_API struct ggml_tensor * ggml_abs( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_abs_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sgn( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sgn_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_neg( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_neg_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_step( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_step_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_tanh( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_tanh_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_elu( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_elu_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_relu( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_leaky_relu( + struct ggml_context * ctx, + struct ggml_tensor * a, float negative_slope, bool inplace); + + GGML_API struct ggml_tensor * ggml_relu_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sigmoid( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_sigmoid_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_gelu( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_gelu_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // GELU using erf (error function) when possible + // some backends may fallback to approximation based on Abramowitz and Stegun formula + GGML_API struct ggml_tensor * ggml_gelu_erf( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_gelu_erf_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_gelu_quick( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_gelu_quick_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_silu( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_silu_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // a - x + // b - dy + GGML_API struct ggml_tensor * ggml_silu_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // hardswish(x) = x * relu6(x + 3) / 6 + GGML_API struct ggml_tensor * ggml_hardswish( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // hardsigmoid(x) = relu6(x + 3) / 6 + GGML_API struct ggml_tensor * ggml_hardsigmoid( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_exp( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_exp_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_floor( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_floor_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_ceil( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_ceil_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_round( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_round_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + /** + * Truncates the fractional part of each element in the tensor (towards zero). + * For example: trunc(3.7) = 3.0, trunc(-2.9) = -2.0 + * Similar to std::trunc in C/C++. + */ + + GGML_API struct ggml_tensor * ggml_trunc( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_trunc_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // Rounds each element to bf16 precision, stored as f32. Equivalent to a + // cast f32 -> bf16 -> f32 round trip, but fused into a single op. + GGML_API struct ggml_tensor * ggml_round_bf16( + struct ggml_context * ctx, + struct ggml_tensor * a); + + + + // xIELU activation function + // x = x * (c_a(alpha_n) + c_b(alpha_p, beta) * sigmoid(beta * x)) + eps * (x > 0) + // where c_a = softplus and c_b(a, b) = softplus(a) + b are constraining functions + // that constrain the positive and negative source alpha values respectively + GGML_API struct ggml_tensor * ggml_xielu( + struct ggml_context * ctx, + struct ggml_tensor * a, + float alpha_n, + float alpha_p, + float beta, + float eps); + + // gated linear unit ops + // A: n columns, r rows, + // result is n / 2 columns, r rows, + // expects gate in second half of row, unless swapped is true + GGML_API struct ggml_tensor * ggml_glu( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_glu_op op, + bool swapped); + + GGML_API struct ggml_tensor * ggml_reglu( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_reglu_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_geglu( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_geglu_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_swiglu( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_swiglu_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_geglu_erf( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_geglu_erf_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_geglu_quick( + struct ggml_context * ctx, + struct ggml_tensor * a); + + GGML_API struct ggml_tensor * ggml_geglu_quick_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // A: n columns, r rows, + // B: n columns, r rows, + GGML_API struct ggml_tensor * ggml_glu_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + enum ggml_glu_op op); + + GGML_API struct ggml_tensor * ggml_reglu_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_geglu_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_swiglu_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_geglu_erf_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_geglu_quick_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + GGML_API struct ggml_tensor * ggml_swiglu_oai( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float alpha, + float limit); + + // normalize along rows + GGML_API struct ggml_tensor * ggml_norm( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps); + + GGML_API struct ggml_tensor * ggml_norm_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps); + + GGML_API struct ggml_tensor * ggml_rms_norm( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps); + + GGML_API struct ggml_tensor * ggml_rms_norm_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps); + + // group normalize along ne0*ne1*n_groups + // used in stable-diffusion + GGML_API struct ggml_tensor * ggml_group_norm( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_groups, + float eps); + + GGML_API struct ggml_tensor * ggml_group_norm_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_groups, + float eps); + + // l2 normalize along rows + // used in rwkv v7 + GGML_API struct ggml_tensor * ggml_l2_norm( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps); + + GGML_API struct ggml_tensor * ggml_l2_norm_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps); + + // a - x + // b - dy + GGML_API struct ggml_tensor * ggml_rms_norm_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float eps); + + // A: k columns, n rows => [ne03, ne02, n, k] + // B: k columns, m rows (i.e. we transpose it internally) => [ne03 * x, ne02 * y, m, k] + // result is n columns, m rows => [ne03 * x, ne02 * y, m, n] + GGML_API struct ggml_tensor * ggml_mul_mat( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // accumulate matrix multiplication in-place: acc += a * b + // result is a view of acc (which must have the shape of a * b), so the + // accumulation lands directly in acc's memory without a separate add pass + GGML_API struct ggml_tensor * ggml_mul_mat_acc( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * acc); + + // fused snake activation: dst = a + sin(a * alpha)^2 / alpha, alpha broadcast per channel + GGML_API struct ggml_tensor * ggml_snake_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * alpha); + + GGML_API struct ggml_tensor * ggml_mul_mat_pack4( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // change the precision of a matrix multiplication + // set to GGML_PREC_F32 for higher precision (useful for phi-2) + GGML_API void ggml_mul_mat_set_prec( + struct ggml_tensor * a, + enum ggml_prec prec); + + // change the hint of a matrix multiplication + GGML_API void ggml_mul_mat_set_hint( + struct ggml_tensor * a, + enum ggml_op_hint hint); + + // indirect matrix multiplication + GGML_API struct ggml_tensor * ggml_mul_mat_id( + struct ggml_context * ctx, + struct ggml_tensor * as, + struct ggml_tensor * b, + struct ggml_tensor * ids); + + // A: m columns, n rows, + // B: p columns, n rows, + // result is m columns, p rows + GGML_API struct ggml_tensor * ggml_out_prod( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // + // operations on tensors without backpropagation + // + + GGML_API struct ggml_tensor * ggml_scale( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s); + + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_scale_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s); + + // x = s * a + b + GGML_API struct ggml_tensor * ggml_scale_bias( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s, + float b); + + GGML_API struct ggml_tensor * ggml_scale_bias_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s, + float b); + + // b -> view(a,offset,nb1,nb2,3), return modified a + GGML_API struct ggml_tensor * ggml_set( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset); // in bytes + + // b -> view(a,offset,nb1,nb2,3), return view(a) + GGML_API struct ggml_tensor * ggml_set_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset); // in bytes + + GGML_API struct ggml_tensor * ggml_set_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t offset); // in bytes + + GGML_API struct ggml_tensor * ggml_set_1d_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t offset); // in bytes + + // b -> view(a,offset,nb1,nb2,3), return modified a + GGML_API struct ggml_tensor * ggml_set_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t offset); // in bytes + + // b -> view(a,offset,nb1,nb2,3), return view(a) + GGML_API struct ggml_tensor * ggml_set_2d_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t offset); // in bytes + + // a -> b, return view(b) + GGML_API struct ggml_tensor * ggml_cpy( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // note: casting from f32 to i32 will discard the fractional part + GGML_API struct ggml_tensor * ggml_cast( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_type type); + + // make contiguous + GGML_API struct ggml_tensor * ggml_cont( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // make contiguous, with new shape + GGML_API struct ggml_tensor * ggml_cont_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0); + + GGML_API struct ggml_tensor * ggml_cont_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1); + + GGML_API struct ggml_tensor * ggml_cont_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2); + + GGML_API struct ggml_tensor * ggml_cont_4d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3); + + // return view(a), b specifies the new shape + // TODO: when we start computing gradient, make a copy instead of view + GGML_API struct ggml_tensor * ggml_reshape( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // return view(a) + // TODO: when we start computing gradient, make a copy instead of view + GGML_API struct ggml_tensor * ggml_reshape_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0); + + GGML_API struct ggml_tensor * ggml_reshape_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1); + + // return view(a) + // TODO: when we start computing gradient, make a copy instead of view + GGML_API struct ggml_tensor * ggml_reshape_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2); + + GGML_API struct ggml_tensor * ggml_reshape_4d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3); + + // offset in bytes + GGML_API struct ggml_tensor * ggml_view_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + size_t offset); + + GGML_API struct ggml_tensor * ggml_view_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + size_t nb1, // row stride in bytes + size_t offset); + + GGML_API struct ggml_tensor * ggml_view_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + size_t nb1, // row stride in bytes + size_t nb2, // slice stride in bytes + size_t offset); + + GGML_API struct ggml_tensor * ggml_view_4d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3, + size_t nb1, // row stride in bytes + size_t nb2, // slice stride in bytes + size_t nb3, + size_t offset); + + GGML_API struct ggml_tensor * ggml_permute( + struct ggml_context * ctx, + struct ggml_tensor * a, + int axis0, + int axis1, + int axis2, + int axis3); + + // alias for ggml_permute(ctx, a, 1, 0, 2, 3) + GGML_API struct ggml_tensor * ggml_transpose( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // supports 4D a: + // a [n_embd, ne1, ne2, ne3] + // b I32 [n_rows, ne2, ne3, 1] + // + // return [n_embd, n_rows, ne2, ne3] + GGML_API struct ggml_tensor * ggml_get_rows( + struct ggml_context * ctx, + struct ggml_tensor * a, // data + struct ggml_tensor * b); // row indices + + GGML_API struct ggml_tensor * ggml_get_rows_back( + struct ggml_context * ctx, + struct ggml_tensor * a, // gradients of ggml_get_rows result + struct ggml_tensor * b, // row indices + struct ggml_tensor * c); // data for ggml_get_rows, only used for its shape + + // a TD [n_embd, ne1, ne2, ne3] + // b TS [n_embd, n_rows, ne02, ne03] | ne02 == ne2, ne03 == ne3 + // c I64 [n_rows, ne11, ne12, 1] | c[i] in [0, ne1) + // + // undefined behavior if destination rows overlap + // + // broadcast: + // ne2 % ne11 == 0 + // ne3 % ne12 == 0 + // + // return view(a) + GGML_API struct ggml_tensor * ggml_set_rows( + struct ggml_context * ctx, + struct ggml_tensor * a, // destination + struct ggml_tensor * b, // source + struct ggml_tensor * c); // row indices + + GGML_API struct ggml_tensor * ggml_diag( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // set elements above the diagonal to -INF + GGML_API struct ggml_tensor * ggml_diag_mask_inf( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past); + + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_diag_mask_inf_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past); + + // set elements above the diagonal to 0 + GGML_API struct ggml_tensor * ggml_diag_mask_zero( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past); + + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_diag_mask_zero_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past); + + GGML_API struct ggml_tensor * ggml_soft_max( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_soft_max_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a); + + // a [ne0, ne01, ne02, ne03] + // mask [ne0, ne11, ne12, ne13] | ne11 >= ne01, F16 or F32, optional + // + // broadcast: + // ne02 % ne12 == 0 + // ne03 % ne13 == 0 + // + // fused soft_max(a*scale + mask*(ALiBi slope)) + // max_bias = 0.0f for no ALiBi + GGML_API struct ggml_tensor * ggml_soft_max_ext( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * mask, + float scale, + float max_bias); + + GGML_API struct ggml_tensor * ggml_soft_max_ext_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * mask, + float scale, + float max_bias); + + GGML_API void ggml_soft_max_add_sinks( + struct ggml_tensor * a, + struct ggml_tensor * sinks); + + GGML_API struct ggml_tensor * ggml_soft_max_ext_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float scale, + float max_bias); + + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_soft_max_ext_back_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float scale, + float max_bias); + + // rotary position embedding + // if (mode & 1) - skip n_past elements (NOT SUPPORTED) + // if (mode & GGML_ROPE_TYPE_NEOX) - GPT-NeoX style + // + // b is an int32 vector with size a->ne[2], it contains the positions + GGML_API struct ggml_tensor * ggml_rope( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int n_dims, + int mode); + + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_rope_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int n_dims, + int mode); + + // RoPE operations with extended options + // a is the input tensor to apply RoPE to, shape [n_embd, n_head, n_token] + // b is an int32 vector with size n_token + // c is freq factors (e.g. phi3-128k), (optional) + // mode can be GGML_ROPE_TYPE_NORMAL or NEOX; for MROPE and VISION mode, use ggml_rope_multi + // + // pseudo-code for computing theta: + // for i in [0, n_dims/2): + // theta[i] = b[i] * powf(freq_base, -2.0 * i / n_dims); + // theta[i] = theta[i] / c[i]; # if c is provided, divide theta by c + // theta[i] = rope_yarn(theta[i], ...); # note: theta = theta * freq_scale is applied here + // + // other params are used by YaRN RoPE scaling, these default values will disable YaRN: + // freq_scale = 1.0f + // ext_factor = 0.0f + // attn_factor = 1.0f + // beta_fast = 0.0f + // beta_slow = 0.0f + // + // example: + // (marking: c = cos, s = sin, 0 = unrotated) + // given a single head with size = 8 --> [00000000] + // GGML_ROPE_TYPE_NORMAL n_dims = 4 --> [cscs0000] + // GGML_ROPE_TYPE_NORMAL n_dims = 8 --> [cscscscs] + // GGML_ROPE_TYPE_NEOX n_dims = 4 --> [ccss0000] + // GGML_ROPE_TYPE_NEOX n_dims = 8 --> [ccccssss] + GGML_API struct ggml_tensor * ggml_rope_ext( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow); + + // multi-dimensional RoPE, for Qwen-VL and similar vision models + // mode can be either VISION, MROPE, IMROPE, cannot be combined with NORMAL or NEOX + // sections specify how many dimensions to rotate in each section: + // section length is equivalent to number of cos/sin pairs, NOT the number of dims + // (i.e. sum of 4 sections are expected to be n_dims/2) + // last sections can be 0, means ignored + // all other options are identical to ggml_rope_ext + // + // important note: + // - NEOX ordering is automatically applied and cannot be disabled for MROPE and VISION + // if you need normal ordering, there are 2 methods: + // (1) split the tensor manually using ggml_view + // (2) permute the weight upon conversion + // - for VISION, n_dims must be head_size/2 + // + // example M-RoPE: + // given sections = [t=4, y=2, x=2, 0] + // given a single head with size = 18 --> [000000000000000000] + // GGML_ROPE_TYPE_MROPE n_dims = 16 --> [ttttyyxxttttyyxx00] (cos/sin are applied in NEOX ordering) + // GGML_ROPE_TYPE_IMROPE n_dims = 16 --> [ttyxttyxttyxttyx00] (interleaved M-RoPE, still NEOX ordering) + // note: the theta for each dim is computed the same way as ggml_rope_ext, no matter the section + // in other words, idx used for theta: [0123456789... until n_dims/2], not reset for each section + // + // example vision RoPE: + // given sections = [y=4, x=4, 0, 0] (last 2 sections are ignored) + // given a single head with size = 8 --> [00000000] + // GGML_ROPE_TYPE_VISION n_dims = 4 --> [yyyyxxxx] + // other values of n_dims are untested and is undefined behavior + // note: unlike MROPE, the theta for each dim is computed differently for each section + // in other words, idx used for theta: [0123] for y section, then [0123] for x section + GGML_API struct ggml_tensor * ggml_rope_multi( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int sections[GGML_MROPE_SECTIONS], + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow); + + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_rope_ext_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow); + + GGML_API struct ggml_tensor * ggml_rope_multi_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int sections[GGML_MROPE_SECTIONS], + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow); + + GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_rope_custom( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow), + "use ggml_rope_ext instead"); - GGML_OP_GLU, - GGML_OP_CONVROT_LINEAR, - - GGML_OP_COUNT, - }; - - enum ggml_unary_op { - GGML_UNARY_OP_ABS, - GGML_UNARY_OP_SGN, - GGML_UNARY_OP_NEG, - GGML_UNARY_OP_STEP, - GGML_UNARY_OP_TANH, - GGML_UNARY_OP_ELU, - GGML_UNARY_OP_RELU, - GGML_UNARY_OP_SIGMOID, - GGML_UNARY_OP_GELU, - GGML_UNARY_OP_GELU_QUICK, - GGML_UNARY_OP_SILU, - GGML_UNARY_OP_HARDSWISH, - GGML_UNARY_OP_HARDSIGMOID, - GGML_UNARY_OP_EXP, - GGML_UNARY_OP_EXPM1, - GGML_UNARY_OP_SOFTPLUS, - GGML_UNARY_OP_GELU_ERF, - GGML_UNARY_OP_XIELU, - GGML_UNARY_OP_FLOOR, - GGML_UNARY_OP_CEIL, - GGML_UNARY_OP_ROUND, - GGML_UNARY_OP_TRUNC, - - GGML_UNARY_OP_COUNT, - }; - - enum ggml_glu_op { - GGML_GLU_OP_REGLU, - GGML_GLU_OP_GEGLU, - GGML_GLU_OP_SWIGLU, - GGML_GLU_OP_SWIGLU_OAI, - GGML_GLU_OP_GEGLU_ERF, - GGML_GLU_OP_GEGLU_QUICK, - - GGML_GLU_OP_COUNT, - }; - - enum ggml_object_type { - GGML_OBJECT_TYPE_TENSOR, - GGML_OBJECT_TYPE_GRAPH, - GGML_OBJECT_TYPE_WORK_BUFFER - }; - - enum ggml_log_level { - GGML_LOG_LEVEL_NONE = 0, - GGML_LOG_LEVEL_DEBUG = 1, - GGML_LOG_LEVEL_INFO = 2, - GGML_LOG_LEVEL_WARN = 3, - GGML_LOG_LEVEL_ERROR = 4, - GGML_LOG_LEVEL_CONT = 5, // continue previous log - }; - - // this tensor... - enum ggml_tensor_flag { - GGML_TENSOR_FLAG_INPUT = 1, // ...is an input for the GGML compute graph - GGML_TENSOR_FLAG_OUTPUT = 2, // ...is an output for the GGML compute graph - GGML_TENSOR_FLAG_PARAM = 4, // ...contains trainable parameters - GGML_TENSOR_FLAG_LOSS = 8, // ...defines loss for numerical optimization (multiple loss tensors add up) - GGML_TENSOR_FLAG_COMPUTE = 16, // ...must be computed - }; - - enum ggml_tri_type { - GGML_TRI_TYPE_UPPER_DIAG = 0, - GGML_TRI_TYPE_UPPER = 1, - GGML_TRI_TYPE_LOWER_DIAG = 2, - GGML_TRI_TYPE_LOWER = 3 - }; - - struct ggml_init_params { - // memory pool - size_t mem_size; // bytes - void * mem_buffer; // if NULL, memory will be allocated internally - bool no_alloc; // don't allocate memory for the tensor data - }; - - // n-dimensional tensor - struct ggml_tensor { - enum ggml_type type; - - struct ggml_backend_buffer * buffer; - - int64_t ne[GGML_MAX_DIMS]; // number of elements - size_t nb[GGML_MAX_DIMS]; // stride in bytes: - // nb[0] = ggml_type_size(type) - // nb[1] = nb[0] * (ne[0] / ggml_blck_size(type)) + padding - // nb[i] = nb[i-1] * ne[i-1] - - // compute data - enum ggml_op op; - - // op params - allocated as int32_t for alignment - int32_t op_params[GGML_MAX_OP_PARAMS / sizeof(int32_t)]; - - int32_t flags; - - struct ggml_tensor * src[GGML_MAX_SRC]; - - // source tensor and offset for views - struct ggml_tensor * view_src; - size_t view_offs; - - void * data; - - char name[GGML_MAX_NAME]; - - void * extra; // extra things e.g. for ggml-cuda.cu - - char padding[8]; - }; - - static const size_t GGML_TENSOR_SIZE = sizeof(struct ggml_tensor); - - // Abort callback - // If not NULL, called before ggml computation - // If it returns true, the computation is aborted - typedef bool (*ggml_abort_callback)(void * data); - - - // - // GUID - // - - // GUID types - typedef uint8_t ggml_guid[16]; - typedef ggml_guid * ggml_guid_t; - - GGML_API bool ggml_guid_matches(ggml_guid_t guid_a, ggml_guid_t guid_b); - - // misc - - GGML_API const char * ggml_version(void); - GGML_API const char * ggml_commit(void); - - GGML_API void ggml_time_init(void); // call this once at the beginning of the program - GGML_API int64_t ggml_time_ms(void); - GGML_API int64_t ggml_time_us(void); - GGML_API int64_t ggml_cycles(void); - GGML_API int64_t ggml_cycles_per_ms(void); - - // accepts a UTF-8 path, even on Windows - GGML_API FILE * ggml_fopen(const char * fname, const char * mode); - - GGML_API void ggml_print_object (const struct ggml_object * obj); - GGML_API void ggml_print_objects(const struct ggml_context * ctx); - - GGML_API int64_t ggml_nelements (const struct ggml_tensor * tensor); - GGML_API int64_t ggml_nrows (const struct ggml_tensor * tensor); - GGML_API size_t ggml_nbytes (const struct ggml_tensor * tensor); - GGML_API size_t ggml_nbytes_pad(const struct ggml_tensor * tensor); // same as ggml_nbytes() but padded to GGML_MEM_ALIGN - - GGML_API int64_t ggml_blck_size(enum ggml_type type); - GGML_API size_t ggml_type_size(enum ggml_type type); // size in bytes for all elements in a block - GGML_API size_t ggml_row_size (enum ggml_type type, int64_t ne); // size in bytes for all elements in a row - - GGML_DEPRECATED( - GGML_API double ggml_type_sizef(enum ggml_type type), // ggml_type_size()/ggml_blck_size() as float - "use ggml_row_size() instead"); - - GGML_API const char * ggml_type_name(enum ggml_type type); - GGML_API const char * ggml_op_name (enum ggml_op op); - GGML_API const char * ggml_op_symbol(enum ggml_op op); - - GGML_API const char * ggml_unary_op_name(enum ggml_unary_op op); - GGML_API const char * ggml_glu_op_name(enum ggml_glu_op op); - GGML_API const char * ggml_op_desc(const struct ggml_tensor * t); // unary or op name - - GGML_API size_t ggml_element_size(const struct ggml_tensor * tensor); - - GGML_API bool ggml_is_quantized(enum ggml_type type); - - // TODO: temporary until model loading of ggml examples is refactored - GGML_API enum ggml_type ggml_ftype_to_ggml_type(enum ggml_ftype ftype); - - GGML_API bool ggml_is_transposed(const struct ggml_tensor * tensor); - GGML_API bool ggml_is_permuted (const struct ggml_tensor * tensor); - GGML_API bool ggml_is_empty (const struct ggml_tensor * tensor); - GGML_API bool ggml_is_view (const struct ggml_tensor * tensor); - GGML_API bool ggml_is_scalar (const struct ggml_tensor * tensor); - GGML_API bool ggml_is_vector (const struct ggml_tensor * tensor); - GGML_API bool ggml_is_matrix (const struct ggml_tensor * tensor); - GGML_API bool ggml_is_3d (const struct ggml_tensor * tensor); - GGML_API int ggml_n_dims (const struct ggml_tensor * tensor); // returns 1 for scalars - - // returns whether the tensor elements can be iterated over with a flattened index (no gaps, no permutation) - GGML_API bool ggml_is_contiguous (const struct ggml_tensor * tensor); - GGML_API bool ggml_is_contiguous_0(const struct ggml_tensor * tensor); // same as ggml_is_contiguous() - GGML_API bool ggml_is_contiguous_1(const struct ggml_tensor * tensor); // contiguous for dims >= 1 - GGML_API bool ggml_is_contiguous_2(const struct ggml_tensor * tensor); // contiguous for dims >= 2 - - // returns whether the tensor elements are allocated as one contiguous block of memory (no gaps, but permutation ok) - GGML_API bool ggml_is_contiguously_allocated(const struct ggml_tensor * tensor); - - // true for tensor that is stored in memory as CxWxHxN and has been permuted to WxHxCxN - GGML_API bool ggml_is_contiguous_channels(const struct ggml_tensor * tensor); - - // true if the elements in dimension 0 are contiguous, or there is just 1 block of elements - GGML_API bool ggml_is_contiguous_rows(const struct ggml_tensor * tensor); - - GGML_API bool ggml_are_same_shape (const struct ggml_tensor * t0, const struct ggml_tensor * t1); - GGML_API bool ggml_are_same_stride(const struct ggml_tensor * t0, const struct ggml_tensor * t1); - - GGML_API bool ggml_can_repeat(const struct ggml_tensor * t0, const struct ggml_tensor * t1); - - // use this to compute the memory overhead of a tensor - GGML_API size_t ggml_tensor_overhead(void); - - GGML_API bool ggml_validate_row_data(enum ggml_type type, const void * data, size_t nbytes); - - // main - - GGML_API struct ggml_context * ggml_init (struct ggml_init_params params); - GGML_API void ggml_reset(struct ggml_context * ctx); - GGML_API void ggml_free (struct ggml_context * ctx); - - GGML_API size_t ggml_used_mem(const struct ggml_context * ctx); - - GGML_API bool ggml_get_no_alloc(struct ggml_context * ctx); - GGML_API void ggml_set_no_alloc(struct ggml_context * ctx, bool no_alloc); - - GGML_API void * ggml_get_mem_buffer (const struct ggml_context * ctx); - GGML_API size_t ggml_get_mem_size (const struct ggml_context * ctx); - GGML_API size_t ggml_get_max_tensor_size(const struct ggml_context * ctx); - - GGML_API struct ggml_tensor * ggml_new_tensor( - struct ggml_context * ctx, - enum ggml_type type, - int n_dims, - const int64_t *ne); - - GGML_API struct ggml_tensor * ggml_new_tensor_1d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0); - - GGML_API struct ggml_tensor * ggml_new_tensor_2d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0, - int64_t ne1); - - GGML_API struct ggml_tensor * ggml_new_tensor_3d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0, - int64_t ne1, - int64_t ne2); - - GGML_API struct ggml_tensor * ggml_new_tensor_4d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3); - - GGML_API void * ggml_new_buffer(struct ggml_context * ctx, size_t nbytes); - - GGML_API struct ggml_tensor * ggml_dup_tensor (struct ggml_context * ctx, const struct ggml_tensor * src); - GGML_API struct ggml_tensor * ggml_view_tensor(struct ggml_context * ctx, struct ggml_tensor * src); - - // Context tensor enumeration and lookup - GGML_API struct ggml_tensor * ggml_get_first_tensor(const struct ggml_context * ctx); - GGML_API struct ggml_tensor * ggml_get_next_tensor (const struct ggml_context * ctx, struct ggml_tensor * tensor); - GGML_API struct ggml_tensor * ggml_get_tensor(struct ggml_context * ctx, const char * name); - - // Converts a flat index into coordinates - GGML_API void ggml_unravel_index(const struct ggml_tensor * tensor, int64_t i, int64_t * i0, int64_t * i1, int64_t * i2, int64_t * i3); - - GGML_API enum ggml_unary_op ggml_get_unary_op(const struct ggml_tensor * tensor); - GGML_API enum ggml_glu_op ggml_get_glu_op(const struct ggml_tensor * tensor); - - GGML_API void * ggml_get_data (const struct ggml_tensor * tensor); - GGML_API float * ggml_get_data_f32(const struct ggml_tensor * tensor); - - GGML_API const char * ggml_get_name (const struct ggml_tensor * tensor); - GGML_API struct ggml_tensor * ggml_set_name ( struct ggml_tensor * tensor, const char * name); - GGML_ATTRIBUTE_FORMAT(2, 3) - GGML_API struct ggml_tensor * ggml_format_name( struct ggml_tensor * tensor, const char * fmt, ...); - - // Tensor flags - GGML_API void ggml_set_input(struct ggml_tensor * tensor); - GGML_API void ggml_set_output(struct ggml_tensor * tensor); - GGML_API void ggml_set_param(struct ggml_tensor * tensor); - GGML_API void ggml_set_loss(struct ggml_tensor * tensor); - - // - // operations on tensors with backpropagation - // - - GGML_API struct ggml_tensor * ggml_dup( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_dup_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_add( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_add_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_add_cast( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - enum ggml_type type); - - // dst[i0, i1, i2] = a[i0, i1, i2] + b[i0, ids[i1, i2]] - GGML_API struct ggml_tensor * ggml_add_id( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * ids); - - GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_add1( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b), - "use ggml_add instead"); - - GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_add1_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b), - "use ggml_add_inplace instead"); - - // dst = a - // view(dst, nb1, nb2, nb3, offset) += b - // return dst - GGML_API struct ggml_tensor * ggml_acc( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset); - - GGML_API struct ggml_tensor * ggml_acc_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset); - - GGML_API struct ggml_tensor * ggml_sub( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_sub_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_mul( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_mul_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_div( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_div_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_sqr( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sqr_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sqrt( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sqrt_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_log( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_log_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_expm1( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_expm1_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_softplus( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_softplus_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sin( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sin_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_cos( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_cos_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // return scalar - GGML_API struct ggml_tensor * ggml_sum( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // sums along rows, with input shape [a,b,c,d] return shape [1,b,c,d] - GGML_API struct ggml_tensor * ggml_sum_rows( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_cumsum( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // mean along rows - GGML_API struct ggml_tensor * ggml_mean( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // argmax along rows - GGML_API struct ggml_tensor * ggml_argmax( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // count number of equal elements in a and b - GGML_API struct ggml_tensor * ggml_count_equal( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - // if a is the same shape as b, and a is not parameter, return a - // otherwise, return a new tensor: repeat(a) to fit in b - GGML_API struct ggml_tensor * ggml_repeat( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - // repeat a to the specified shape - GGML_API struct ggml_tensor * ggml_repeat_4d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3); - - // sums repetitions in a into shape of b - GGML_API struct ggml_tensor * ggml_repeat_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); // sum up values that are adjacent in dims > 0 instead of repeated with same stride - - // concat a and b along dim - // used in stable-diffusion - GGML_API struct ggml_tensor * ggml_concat( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int dim); - - GGML_API struct ggml_tensor * ggml_abs( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_abs_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sgn( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sgn_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_neg( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_neg_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_step( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_step_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_tanh( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_tanh_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_elu( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_elu_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_relu( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_leaky_relu( - struct ggml_context * ctx, - struct ggml_tensor * a, float negative_slope, bool inplace); - - GGML_API struct ggml_tensor * ggml_relu_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sigmoid( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_sigmoid_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_gelu( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_gelu_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // GELU using erf (error function) when possible - // some backends may fallback to approximation based on Abramowitz and Stegun formula - GGML_API struct ggml_tensor * ggml_gelu_erf( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_gelu_erf_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_gelu_quick( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_gelu_quick_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_silu( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_silu_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // a - x - // b - dy - GGML_API struct ggml_tensor * ggml_silu_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - // hardswish(x) = x * relu6(x + 3) / 6 - GGML_API struct ggml_tensor * ggml_hardswish( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // hardsigmoid(x) = relu6(x + 3) / 6 - GGML_API struct ggml_tensor * ggml_hardsigmoid( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_exp( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_exp_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_floor( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_floor_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_ceil( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_ceil_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_round( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_round_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - /** - * Truncates the fractional part of each element in the tensor (towards zero). - * For example: trunc(3.7) = 3.0, trunc(-2.9) = -2.0 - * Similar to std::trunc in C/C++. - */ - - GGML_API struct ggml_tensor * ggml_trunc( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_trunc_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - - - // xIELU activation function - // x = x * (c_a(alpha_n) + c_b(alpha_p, beta) * sigmoid(beta * x)) + eps * (x > 0) - // where c_a = softplus and c_b(a, b) = softplus(a) + b are constraining functions - // that constrain the positive and negative source alpha values respectively - GGML_API struct ggml_tensor * ggml_xielu( - struct ggml_context * ctx, - struct ggml_tensor * a, - float alpha_n, - float alpha_p, - float beta, - float eps); - - // gated linear unit ops - // A: n columns, r rows, - // result is n / 2 columns, r rows, - // expects gate in second half of row, unless swapped is true - GGML_API struct ggml_tensor * ggml_glu( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_glu_op op, - bool swapped); - - GGML_API struct ggml_tensor * ggml_reglu( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_reglu_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_geglu( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_geglu_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_swiglu( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_swiglu_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_geglu_erf( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_geglu_erf_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_geglu_quick( - struct ggml_context * ctx, - struct ggml_tensor * a); - - GGML_API struct ggml_tensor * ggml_geglu_quick_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // A: n columns, r rows, - // B: n columns, r rows, - GGML_API struct ggml_tensor * ggml_glu_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - enum ggml_glu_op op); - - GGML_API struct ggml_tensor * ggml_reglu_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_geglu_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_swiglu_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_geglu_erf_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_geglu_quick_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_swiglu_oai( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float alpha, - float limit); - - // normalize along rows - GGML_API struct ggml_tensor * ggml_norm( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps); - - GGML_API struct ggml_tensor * ggml_norm_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps); - - GGML_API struct ggml_tensor * ggml_rms_norm( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps); - - GGML_API struct ggml_tensor * ggml_rms_norm_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps); - - // group normalize along ne0*ne1*n_groups - // used in stable-diffusion - GGML_API struct ggml_tensor * ggml_group_norm( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_groups, - float eps); - - GGML_API struct ggml_tensor * ggml_group_norm_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_groups, - float eps); - - // l2 normalize along rows - // used in rwkv v7 - GGML_API struct ggml_tensor * ggml_l2_norm( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps); - - GGML_API struct ggml_tensor * ggml_l2_norm_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps); - - // a - x - // b - dy - GGML_API struct ggml_tensor * ggml_rms_norm_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float eps); - - // A: k columns, n rows => [ne03, ne02, n, k] - // B: k columns, m rows (i.e. we transpose it internally) => [ne03 * x, ne02 * y, m, k] - // result is n columns, m rows => [ne03 * x, ne02 * y, m, n] - GGML_API struct ggml_tensor * ggml_mul_mat( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - GGML_API struct ggml_tensor * ggml_mul_mat_pack4( + GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_rope_custom_inplace( struct ggml_context * ctx, struct ggml_tensor * a, - struct ggml_tensor * b); + struct ggml_tensor * b, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow), + "use ggml_rope_ext_inplace instead"); + + // compute correction dims for YaRN RoPE scaling + GGML_API void ggml_rope_yarn_corr_dims( + int n_dims, int n_ctx_orig, float freq_base, float beta_fast, float beta_slow, float dims[2]); + + // rotary position embedding backward, i.e compute dx from dy + // a - dy + GGML_API struct ggml_tensor * ggml_rope_ext_back( + struct ggml_context * ctx, + struct ggml_tensor * a, // gradients of ggml_rope result + struct ggml_tensor * b, // positions + struct ggml_tensor * c, // freq factors + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow); + + GGML_API struct ggml_tensor * ggml_rope_multi_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int sections[4], + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow); + + + // clamp + // in-place, returns view(a) + GGML_API struct ggml_tensor * ggml_clamp( + struct ggml_context * ctx, + struct ggml_tensor * a, + float min, + float max); + + // im2col + // converts data into a format that effectively results in a convolution when combined with matrix multiplication + GGML_API struct ggml_tensor * ggml_im2col( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel + struct ggml_tensor * b, // data + int s0, // stride dimension 0 + int s1, // stride dimension 1 + int p0, // padding dimension 0 + int p1, // padding dimension 1 + int d0, // dilation dimension 0 + int d1, // dilation dimension 1 + bool is_2D, + enum ggml_type dst_type); - // change the precision of a matrix multiplication - // set to GGML_PREC_F32 for higher precision (useful for phi-2) - GGML_API void ggml_mul_mat_set_prec( - struct ggml_tensor * a, - enum ggml_prec prec); - - // change the hint of a matrix multiplication - GGML_API void ggml_mul_mat_set_hint( - struct ggml_tensor * a, - enum ggml_op_hint hint); - - // indirect matrix multiplication - GGML_API struct ggml_tensor * ggml_mul_mat_id( - struct ggml_context * ctx, - struct ggml_tensor * as, - struct ggml_tensor * b, - struct ggml_tensor * ids); - - // A: m columns, n rows, - // B: p columns, n rows, - // result is m columns, p rows - GGML_API struct ggml_tensor * ggml_out_prod( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - // - // operations on tensors without backpropagation - // - - GGML_API struct ggml_tensor * ggml_scale( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s); - - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_scale_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s); - - // x = s * a + b - GGML_API struct ggml_tensor * ggml_scale_bias( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s, - float b); - - GGML_API struct ggml_tensor * ggml_scale_bias_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s, - float b); - - // b -> view(a,offset,nb1,nb2,3), return modified a - GGML_API struct ggml_tensor * ggml_set( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset); // in bytes - - // b -> view(a,offset,nb1,nb2,3), return view(a) - GGML_API struct ggml_tensor * ggml_set_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset); // in bytes - - GGML_API struct ggml_tensor * ggml_set_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t offset); // in bytes - - GGML_API struct ggml_tensor * ggml_set_1d_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t offset); // in bytes - - // b -> view(a,offset,nb1,nb2,3), return modified a - GGML_API struct ggml_tensor * ggml_set_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t offset); // in bytes - - // b -> view(a,offset,nb1,nb2,3), return view(a) - GGML_API struct ggml_tensor * ggml_set_2d_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t offset); // in bytes - - // a -> b, return view(b) - GGML_API struct ggml_tensor * ggml_cpy( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - // note: casting from f32 to i32 will discard the fractional part - GGML_API struct ggml_tensor * ggml_cast( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_type type); - - // make contiguous - GGML_API struct ggml_tensor * ggml_cont( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // make contiguous, with new shape - GGML_API struct ggml_tensor * ggml_cont_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0); - - GGML_API struct ggml_tensor * ggml_cont_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1); - - GGML_API struct ggml_tensor * ggml_cont_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2); - - GGML_API struct ggml_tensor * ggml_cont_4d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3); - - // return view(a), b specifies the new shape - // TODO: when we start computing gradient, make a copy instead of view - GGML_API struct ggml_tensor * ggml_reshape( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - // return view(a) - // TODO: when we start computing gradient, make a copy instead of view - GGML_API struct ggml_tensor * ggml_reshape_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0); - - GGML_API struct ggml_tensor * ggml_reshape_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1); - - // return view(a) - // TODO: when we start computing gradient, make a copy instead of view - GGML_API struct ggml_tensor * ggml_reshape_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2); - - GGML_API struct ggml_tensor * ggml_reshape_4d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3); - - // offset in bytes - GGML_API struct ggml_tensor * ggml_view_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - size_t offset); - - GGML_API struct ggml_tensor * ggml_view_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - size_t nb1, // row stride in bytes - size_t offset); - - GGML_API struct ggml_tensor * ggml_view_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - size_t nb1, // row stride in bytes - size_t nb2, // slice stride in bytes - size_t offset); - - GGML_API struct ggml_tensor * ggml_view_4d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3, - size_t nb1, // row stride in bytes - size_t nb2, // slice stride in bytes - size_t nb3, - size_t offset); - - GGML_API struct ggml_tensor * ggml_permute( - struct ggml_context * ctx, - struct ggml_tensor * a, - int axis0, - int axis1, - int axis2, - int axis3); - - // alias for ggml_permute(ctx, a, 1, 0, 2, 3) - GGML_API struct ggml_tensor * ggml_transpose( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // supports 4D a: - // a [n_embd, ne1, ne2, ne3] - // b I32 [n_rows, ne2, ne3, 1] - // - // return [n_embd, n_rows, ne2, ne3] - GGML_API struct ggml_tensor * ggml_get_rows( - struct ggml_context * ctx, - struct ggml_tensor * a, // data - struct ggml_tensor * b); // row indices - - GGML_API struct ggml_tensor * ggml_get_rows_back( - struct ggml_context * ctx, - struct ggml_tensor * a, // gradients of ggml_get_rows result - struct ggml_tensor * b, // row indices - struct ggml_tensor * c); // data for ggml_get_rows, only used for its shape - - // a TD [n_embd, ne1, ne2, ne3] - // b TS [n_embd, n_rows, ne02, ne03] | ne02 == ne2, ne03 == ne3 - // c I64 [n_rows, ne11, ne12, 1] | c[i] in [0, ne1) - // - // undefined behavior if destination rows overlap - // - // broadcast: - // ne2 % ne11 == 0 - // ne3 % ne12 == 0 - // - // return view(a) - GGML_API struct ggml_tensor * ggml_set_rows( - struct ggml_context * ctx, - struct ggml_tensor * a, // destination - struct ggml_tensor * b, // source - struct ggml_tensor * c); // row indices - - GGML_API struct ggml_tensor * ggml_diag( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // set elements above the diagonal to -INF - GGML_API struct ggml_tensor * ggml_diag_mask_inf( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past); - - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_diag_mask_inf_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past); - - // set elements above the diagonal to 0 - GGML_API struct ggml_tensor * ggml_diag_mask_zero( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past); - - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_diag_mask_zero_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past); - - GGML_API struct ggml_tensor * ggml_soft_max( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_soft_max_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a); - - // a [ne0, ne01, ne02, ne03] - // mask [ne0, ne11, ne12, ne13] | ne11 >= ne01, F16 or F32, optional - // - // broadcast: - // ne02 % ne12 == 0 - // ne03 % ne13 == 0 - // - // fused soft_max(a*scale + mask*(ALiBi slope)) - // max_bias = 0.0f for no ALiBi - GGML_API struct ggml_tensor * ggml_soft_max_ext( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * mask, - float scale, - float max_bias); - - GGML_API struct ggml_tensor * ggml_soft_max_ext_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * mask, - float scale, - float max_bias); - - GGML_API void ggml_soft_max_add_sinks( - struct ggml_tensor * a, - struct ggml_tensor * sinks); - - GGML_API struct ggml_tensor * ggml_soft_max_ext_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float scale, - float max_bias); - - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_soft_max_ext_back_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float scale, - float max_bias); - - // rotary position embedding - // if (mode & 1) - skip n_past elements (NOT SUPPORTED) - // if (mode & GGML_ROPE_TYPE_NEOX) - GPT-NeoX style - // - // b is an int32 vector with size a->ne[2], it contains the positions - GGML_API struct ggml_tensor * ggml_rope( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int n_dims, - int mode); - - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_rope_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int n_dims, - int mode); - - // RoPE operations with extended options - // a is the input tensor to apply RoPE to, shape [n_embd, n_head, n_token] - // b is an int32 vector with size n_token - // c is freq factors (e.g. phi3-128k), (optional) - // mode can be GGML_ROPE_TYPE_NORMAL or NEOX; for MROPE and VISION mode, use ggml_rope_multi - // - // pseudo-code for computing theta: - // for i in [0, n_dims/2): - // theta[i] = b[i] * powf(freq_base, -2.0 * i / n_dims); - // theta[i] = theta[i] / c[i]; # if c is provided, divide theta by c - // theta[i] = rope_yarn(theta[i], ...); # note: theta = theta * freq_scale is applied here - // - // other params are used by YaRN RoPE scaling, these default values will disable YaRN: - // freq_scale = 1.0f - // ext_factor = 0.0f - // attn_factor = 1.0f - // beta_fast = 0.0f - // beta_slow = 0.0f - // - // example: - // (marking: c = cos, s = sin, 0 = unrotated) - // given a single head with size = 8 --> [00000000] - // GGML_ROPE_TYPE_NORMAL n_dims = 4 --> [cscs0000] - // GGML_ROPE_TYPE_NORMAL n_dims = 8 --> [cscscscs] - // GGML_ROPE_TYPE_NEOX n_dims = 4 --> [ccss0000] - // GGML_ROPE_TYPE_NEOX n_dims = 8 --> [ccccssss] - GGML_API struct ggml_tensor * ggml_rope_ext( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow); - - // multi-dimensional RoPE, for Qwen-VL and similar vision models - // mode can be either VISION, MROPE, IMROPE, cannot be combined with NORMAL or NEOX - // sections specify how many dimensions to rotate in each section: - // section length is equivalent to number of cos/sin pairs, NOT the number of dims - // (i.e. sum of 4 sections are expected to be n_dims/2) - // last sections can be 0, means ignored - // all other options are identical to ggml_rope_ext - // - // important note: - // - NEOX ordering is automatically applied and cannot be disabled for MROPE and VISION - // if you need normal ordering, there are 2 methods: - // (1) split the tensor manually using ggml_view - // (2) permute the weight upon conversion - // - for VISION, n_dims must be head_size/2 - // - // example M-RoPE: - // given sections = [t=4, y=2, x=2, 0] - // given a single head with size = 18 --> [000000000000000000] - // GGML_ROPE_TYPE_MROPE n_dims = 16 --> [ttttyyxxttttyyxx00] (cos/sin are applied in NEOX ordering) - // GGML_ROPE_TYPE_IMROPE n_dims = 16 --> [ttyxttyxttyxttyx00] (interleaved M-RoPE, still NEOX ordering) - // note: the theta for each dim is computed the same way as ggml_rope_ext, no matter the section - // in other words, idx used for theta: [0123456789... until n_dims/2], not reset for each section - // - // example vision RoPE: - // given sections = [y=4, x=4, 0, 0] (last 2 sections are ignored) - // given a single head with size = 8 --> [00000000] - // GGML_ROPE_TYPE_VISION n_dims = 4 --> [yyyyxxxx] - // other values of n_dims are untested and is undefined behavior - // note: unlike MROPE, the theta for each dim is computed differently for each section - // in other words, idx used for theta: [0123] for y section, then [0123] for x section - GGML_API struct ggml_tensor * ggml_rope_multi( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int sections[GGML_MROPE_SECTIONS], - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow); - - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_rope_ext_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow); - - GGML_API struct ggml_tensor * ggml_rope_multi_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int sections[GGML_MROPE_SECTIONS], - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow); - - GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_rope_custom( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow), - "use ggml_rope_ext instead"); - - GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_rope_custom_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow), - "use ggml_rope_ext_inplace instead"); - - // compute correction dims for YaRN RoPE scaling - GGML_API void ggml_rope_yarn_corr_dims( - int n_dims, int n_ctx_orig, float freq_base, float beta_fast, float beta_slow, float dims[2]); - - // rotary position embedding backward, i.e compute dx from dy - // a - dy - GGML_API struct ggml_tensor * ggml_rope_ext_back( - struct ggml_context * ctx, - struct ggml_tensor * a, // gradients of ggml_rope result - struct ggml_tensor * b, // positions - struct ggml_tensor * c, // freq factors - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow); - - GGML_API struct ggml_tensor * ggml_rope_multi_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int sections[4], - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow); - - - // clamp - // in-place, returns view(a) - GGML_API struct ggml_tensor * ggml_clamp( - struct ggml_context * ctx, - struct ggml_tensor * a, - float min, - float max); - - // im2col - // converts data into a format that effectively results in a convolution when combined with matrix multiplication - GGML_API struct ggml_tensor * ggml_im2col( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel - struct ggml_tensor * b, // data - int s0, // stride dimension 0 - int s1, // stride dimension 1 - int p0, // padding dimension 0 - int p1, // padding dimension 1 - int d0, // dilation dimension 0 - int d1, // dilation dimension 1 - bool is_2D, - enum ggml_type dst_type); - GGML_API struct ggml_tensor * ggml_im2col_back( struct ggml_context * ctx, struct ggml_tensor * a, // convolution kernel - struct ggml_tensor * b, // gradient of im2col output - int64_t * ne, // shape of im2col input - int s0, // stride dimension 0 - int s1, // stride dimension 1 - int p0, // padding dimension 0 - int p1, // padding dimension 1 - int d0, // dilation dimension 0 + struct ggml_tensor * b, // gradient of im2col output + int64_t * ne, // shape of im2col input + int s0, // stride dimension 0 + int s1, // stride dimension 1 + int p0, // padding dimension 0 + int p1, // padding dimension 1 + int d0, // dilation dimension 0 int d1, // dilation dimension 1 bool is_2D); @@ -2042,385 +2083,385 @@ extern "C" { int d0); // dilation // conv_1d with padding = half - // alias for ggml_conv_1d(a, b, s, a->ne[0]/2, d) - GGML_API struct ggml_tensor* ggml_conv_1d_ph( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel - struct ggml_tensor * b, // data - int s, // stride - int d); // dilation - - // depthwise - // TODO: this is very likely wrong for some cases! - needs more testing - GGML_API struct ggml_tensor * ggml_conv_1d_dw( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel - struct ggml_tensor * b, // data - int s0, // stride - int p0, // padding - int d0); // dilation - - GGML_API struct ggml_tensor * ggml_conv_1d_dw_ph( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel - struct ggml_tensor * b, // data - int s0, // stride - int d0); // dilation - - GGML_API struct ggml_tensor * ggml_conv_transpose_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel - struct ggml_tensor * b, // data - int s0, // stride - int p0, // padding - int d0); // dilation - - GGML_API struct ggml_tensor * ggml_conv_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel - struct ggml_tensor * b, // data - int s0, // stride dimension 0 - int s1, // stride dimension 1 - int p0, // padding dimension 0 - int p1, // padding dimension 1 - int d0, // dilation dimension 0 - int d1); // dilation dimension 1 - - GGML_API struct ggml_tensor * ggml_im2col_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int64_t IC, - int s0, // stride width - int s1, // stride height - int s2, // stride depth - int p0, // padding width - int p1, // padding height - int p2, // padding depth - int d0, // dilation width - int d1, // dilation height - int d2, // dilation depth - enum ggml_type dst_type); - - // a: [OC*IC, KD, KH, KW] - // b: [N*IC, ID, IH, IW] - // result: [N*OC, OD, OH, OW] - GGML_API struct ggml_tensor * ggml_conv_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int64_t IC, - int s0, // stride width - int s1, // stride height - int s2, // stride depth - int p0, // padding width - int p1, // padding height - int p2, // padding depth - int d0, // dilation width - int d1, // dilation height - int d2 // dilation depth - ); - - // kernel size is a->ne[0] x a->ne[1] - // stride is equal to kernel size - // padding is zero - // example: - // a: 16 16 3 768 - // b: 1024 1024 3 1 - // res: 64 64 768 1 - // used in sam - GGML_API struct ggml_tensor * ggml_conv_2d_sk_p0( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - // kernel size is a->ne[0] x a->ne[1] - // stride is 1 - // padding is half - // example: - // a: 3 3 256 256 - // b: 64 64 256 1 - // res: 64 64 256 1 - // used in sam - GGML_API struct ggml_tensor * ggml_conv_2d_s1_ph( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b); - - // depthwise (via im2col and mul_mat) - GGML_API struct ggml_tensor * ggml_conv_2d_dw( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel - struct ggml_tensor * b, // data - int s0, // stride dimension 0 - int s1, // stride dimension 1 - int p0, // padding dimension 0 - int p1, // padding dimension 1 - int d0, // dilation dimension 0 - int d1); // dilation dimension 1 - - // Depthwise 2D convolution - // may be faster than ggml_conv_2d_dw, but not available in all backends - // a: KW KH 1 C convolution kernel - // b: W H C N input data - // res: W_out H_out C N - GGML_API struct ggml_tensor * ggml_conv_2d_dw_direct( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int stride0, - int stride1, - int pad0, - int pad1, - int dilation0, - int dilation1); - - GGML_API struct ggml_tensor * ggml_conv_transpose_2d_p0( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int stride); - - GGML_API struct ggml_tensor * ggml_conv_2d_direct( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel [KW, KH, IC, OC] - struct ggml_tensor * b, // input data [W, H, C, N] - int s0, // stride dimension 0 - int s1, // stride dimension 1 - int p0, // padding dimension 0 - int p1, // padding dimension 1 - int d0, // dilation dimension 0 - int d1); // dilation dimension 1 - - GGML_API struct ggml_tensor * ggml_conv_3d_direct( - struct ggml_context * ctx, - struct ggml_tensor * a, // kernel [KW, KH, KD, IC * OC] - struct ggml_tensor * b, // input [W, H, D, C * N] - int s0, // stride - int s1, - int s2, - int p0, // padding - int p1, - int p2, - int d0, // dilation - int d1, - int d2, - int n_channels, - int n_batch, - int n_channels_out); - - enum ggml_op_pool { - GGML_OP_POOL_MAX, - GGML_OP_POOL_AVG, - GGML_OP_POOL_COUNT, - }; - - GGML_API struct ggml_tensor * ggml_pool_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_op_pool op, - int k0, // kernel size - int s0, // stride - int p0); // padding - - // the result will have 2*p0 padding for the first dimension - // and 2*p1 padding for the second dimension - GGML_API struct ggml_tensor * ggml_pool_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_op_pool op, - int k0, - int k1, - int s0, - int s1, - float p0, - float p1); - - GGML_API struct ggml_tensor * ggml_pool_2d_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * af, // "a"/input used in forward pass - enum ggml_op_pool op, - int k0, - int k1, - int s0, - int s1, - float p0, - float p1); - - enum ggml_scale_mode { - GGML_SCALE_MODE_NEAREST = 0, - GGML_SCALE_MODE_BILINEAR = 1, - GGML_SCALE_MODE_BICUBIC = 2, - - GGML_SCALE_MODE_COUNT - }; - - enum ggml_scale_flag { - GGML_SCALE_FLAG_ALIGN_CORNERS = (1 << 8), - GGML_SCALE_FLAG_ANTIALIAS = (1 << 9), - }; - - // interpolate - // multiplies ne0 and ne1 by scale factor - GGML_API struct ggml_tensor * ggml_upscale( - struct ggml_context * ctx, - struct ggml_tensor * a, - int scale_factor, - enum ggml_scale_mode mode); - - // interpolate - // interpolate scale to specified dimensions - GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_upscale_ext( - struct ggml_context * ctx, - struct ggml_tensor * a, - int ne0, - int ne1, - int ne2, - int ne3, - enum ggml_scale_mode mode), - "use ggml_interpolate instead"); - - // Up- or downsamples the input to the specified size. - // 2D scale modes (eg. bilinear) are applied to the first two dimensions. - GGML_API struct ggml_tensor * ggml_interpolate( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3, - uint32_t mode); // ggml_scale_mode [ | ggml_scale_flag...] - - // pad each dimension with zeros: [x, ..., x] -> [x, ..., x, 0, ..., 0] - GGML_API struct ggml_tensor * ggml_pad( - struct ggml_context * ctx, - struct ggml_tensor * a, - int p0, - int p1, - int p2, - int p3); - - // pad each dimension with values on the other side of the torus (looping around) - GGML_API struct ggml_tensor * ggml_pad_circular( - struct ggml_context * ctx, - struct ggml_tensor * a, - int p0, - int p1, - int p2, - int p3); - - GGML_API struct ggml_tensor * ggml_pad_ext( - struct ggml_context * ctx, - struct ggml_tensor * a, - int lp0, - int rp0, - int lp1, - int rp1, - int lp2, - int rp2, - int lp3, - int rp3 - ); - - // pad each dimension with values on the other side of the torus (looping around) - GGML_API struct ggml_tensor * ggml_pad_ext_circular( - struct ggml_context * ctx, - struct ggml_tensor * a, - int lp0, - int rp0, - int lp1, - int rp1, - int lp2, - int rp2, - int lp3, - int rp3); - - // pad each dimension with reflection: [a, b, c, d] -> [b, a, b, c, d, c] - GGML_API struct ggml_tensor * ggml_pad_reflect_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int p0, - int p1); - - // Move tensor elements by an offset given for each dimension. Elements that - // are shifted beyond the last position are wrapped around to the beginning. - GGML_API struct ggml_tensor * ggml_roll( - struct ggml_context * ctx, - struct ggml_tensor * a, - int shift0, - int shift1, - int shift2, - int shift3); - - // Convert matrix into a triangular one (upper, strict upper, lower or strict lower) by writing - // zeroes everywhere outside the masked area - GGML_API struct ggml_tensor * ggml_tri( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_tri_type type); - - // Fill tensor a with constant c - GGML_API struct ggml_tensor * ggml_fill( - struct ggml_context * ctx, - struct ggml_tensor * a, - float c); - - GGML_API struct ggml_tensor * ggml_fill_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float c); - - // Ref: https://github.com/CompVis/stable-diffusion/blob/main/ldm/modules/diffusionmodules/util.py#L151 - // timesteps: [N,] - // return: [N, dim] - GGML_API struct ggml_tensor * ggml_timestep_embedding( - struct ggml_context * ctx, - struct ggml_tensor * timesteps, - int dim, - int max_period); - - // sort rows - enum ggml_sort_order { - GGML_SORT_ORDER_ASC, - GGML_SORT_ORDER_DESC, - }; - - GGML_API struct ggml_tensor * ggml_argsort( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_sort_order order); - - // similar to ggml_top_k but implemented as `argsort` + `view` - GGML_API struct ggml_tensor * ggml_argsort_top_k( - struct ggml_context * ctx, - struct ggml_tensor * a, - int k); - - // top k elements per row - // note: the resulting top k indices are in no particular order - GGML_API struct ggml_tensor * ggml_top_k( - struct ggml_context * ctx, - struct ggml_tensor * a, - int k); - - GGML_API struct ggml_tensor * ggml_arange( - struct ggml_context * ctx, - float start, - float stop, - float step); - - // q: [n_embd_k, n_batch, n_head, ne3 ] - // k: [n_embd_k, n_kv, n_head_kv, ne3 ] - // v: [n_embd_v, n_kv, n_head_kv, ne3 ] !! not transposed !! - // mask: [n_kv, n_batch, ne32, ne33] - // res: [n_embd_v, n_head, n_batch, ne3 ] !! permuted !! - // - // broadcast: - // n_head % n_head_kv == 0 - // n_head % ne32 == 0 - // ne3 % ne33 == 0 - // + // alias for ggml_conv_1d(a, b, s, a->ne[0]/2, d) + GGML_API struct ggml_tensor* ggml_conv_1d_ph( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel + struct ggml_tensor * b, // data + int s, // stride + int d); // dilation + + // depthwise + // TODO: this is very likely wrong for some cases! - needs more testing + GGML_API struct ggml_tensor * ggml_conv_1d_dw( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel + struct ggml_tensor * b, // data + int s0, // stride + int p0, // padding + int d0); // dilation + + GGML_API struct ggml_tensor * ggml_conv_1d_dw_ph( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel + struct ggml_tensor * b, // data + int s0, // stride + int d0); // dilation + + GGML_API struct ggml_tensor * ggml_conv_transpose_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel + struct ggml_tensor * b, // data + int s0, // stride + int p0, // padding + int d0); // dilation + + GGML_API struct ggml_tensor * ggml_conv_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel + struct ggml_tensor * b, // data + int s0, // stride dimension 0 + int s1, // stride dimension 1 + int p0, // padding dimension 0 + int p1, // padding dimension 1 + int d0, // dilation dimension 0 + int d1); // dilation dimension 1 + + GGML_API struct ggml_tensor * ggml_im2col_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int64_t IC, + int s0, // stride width + int s1, // stride height + int s2, // stride depth + int p0, // padding width + int p1, // padding height + int p2, // padding depth + int d0, // dilation width + int d1, // dilation height + int d2, // dilation depth + enum ggml_type dst_type); + + // a: [OC*IC, KD, KH, KW] + // b: [N*IC, ID, IH, IW] + // result: [N*OC, OD, OH, OW] + GGML_API struct ggml_tensor * ggml_conv_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int64_t IC, + int s0, // stride width + int s1, // stride height + int s2, // stride depth + int p0, // padding width + int p1, // padding height + int p2, // padding depth + int d0, // dilation width + int d1, // dilation height + int d2 // dilation depth + ); + + // kernel size is a->ne[0] x a->ne[1] + // stride is equal to kernel size + // padding is zero + // example: + // a: 16 16 3 768 + // b: 1024 1024 3 1 + // res: 64 64 768 1 + // used in sam + GGML_API struct ggml_tensor * ggml_conv_2d_sk_p0( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // kernel size is a->ne[0] x a->ne[1] + // stride is 1 + // padding is half + // example: + // a: 3 3 256 256 + // b: 64 64 256 1 + // res: 64 64 256 1 + // used in sam + GGML_API struct ggml_tensor * ggml_conv_2d_s1_ph( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b); + + // depthwise (via im2col and mul_mat) + GGML_API struct ggml_tensor * ggml_conv_2d_dw( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel + struct ggml_tensor * b, // data + int s0, // stride dimension 0 + int s1, // stride dimension 1 + int p0, // padding dimension 0 + int p1, // padding dimension 1 + int d0, // dilation dimension 0 + int d1); // dilation dimension 1 + + // Depthwise 2D convolution + // may be faster than ggml_conv_2d_dw, but not available in all backends + // a: KW KH 1 C convolution kernel + // b: W H C N input data + // res: W_out H_out C N + GGML_API struct ggml_tensor * ggml_conv_2d_dw_direct( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int stride0, + int stride1, + int pad0, + int pad1, + int dilation0, + int dilation1); + + GGML_API struct ggml_tensor * ggml_conv_transpose_2d_p0( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int stride); + + GGML_API struct ggml_tensor * ggml_conv_2d_direct( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel [KW, KH, IC, OC] + struct ggml_tensor * b, // input data [W, H, C, N] + int s0, // stride dimension 0 + int s1, // stride dimension 1 + int p0, // padding dimension 0 + int p1, // padding dimension 1 + int d0, // dilation dimension 0 + int d1); // dilation dimension 1 + + GGML_API struct ggml_tensor * ggml_conv_3d_direct( + struct ggml_context * ctx, + struct ggml_tensor * a, // kernel [KW, KH, KD, IC * OC] + struct ggml_tensor * b, // input [W, H, D, C * N] + int s0, // stride + int s1, + int s2, + int p0, // padding + int p1, + int p2, + int d0, // dilation + int d1, + int d2, + int n_channels, + int n_batch, + int n_channels_out); + + enum ggml_op_pool { + GGML_OP_POOL_MAX, + GGML_OP_POOL_AVG, + GGML_OP_POOL_COUNT, + }; + + GGML_API struct ggml_tensor * ggml_pool_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_op_pool op, + int k0, // kernel size + int s0, // stride + int p0); // padding + + // the result will have 2*p0 padding for the first dimension + // and 2*p1 padding for the second dimension + GGML_API struct ggml_tensor * ggml_pool_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_op_pool op, + int k0, + int k1, + int s0, + int s1, + float p0, + float p1); + + GGML_API struct ggml_tensor * ggml_pool_2d_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * af, // "a"/input used in forward pass + enum ggml_op_pool op, + int k0, + int k1, + int s0, + int s1, + float p0, + float p1); + + enum ggml_scale_mode { + GGML_SCALE_MODE_NEAREST = 0, + GGML_SCALE_MODE_BILINEAR = 1, + GGML_SCALE_MODE_BICUBIC = 2, + + GGML_SCALE_MODE_COUNT + }; + + enum ggml_scale_flag { + GGML_SCALE_FLAG_ALIGN_CORNERS = (1 << 8), + GGML_SCALE_FLAG_ANTIALIAS = (1 << 9), + }; + + // interpolate + // multiplies ne0 and ne1 by scale factor + GGML_API struct ggml_tensor * ggml_upscale( + struct ggml_context * ctx, + struct ggml_tensor * a, + int scale_factor, + enum ggml_scale_mode mode); + + // interpolate + // interpolate scale to specified dimensions + GGML_DEPRECATED(GGML_API struct ggml_tensor * ggml_upscale_ext( + struct ggml_context * ctx, + struct ggml_tensor * a, + int ne0, + int ne1, + int ne2, + int ne3, + enum ggml_scale_mode mode), + "use ggml_interpolate instead"); + + // Up- or downsamples the input to the specified size. + // 2D scale modes (eg. bilinear) are applied to the first two dimensions. + GGML_API struct ggml_tensor * ggml_interpolate( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3, + uint32_t mode); // ggml_scale_mode [ | ggml_scale_flag...] + + // pad each dimension with zeros: [x, ..., x] -> [x, ..., x, 0, ..., 0] + GGML_API struct ggml_tensor * ggml_pad( + struct ggml_context * ctx, + struct ggml_tensor * a, + int p0, + int p1, + int p2, + int p3); + + // pad each dimension with values on the other side of the torus (looping around) + GGML_API struct ggml_tensor * ggml_pad_circular( + struct ggml_context * ctx, + struct ggml_tensor * a, + int p0, + int p1, + int p2, + int p3); + + GGML_API struct ggml_tensor * ggml_pad_ext( + struct ggml_context * ctx, + struct ggml_tensor * a, + int lp0, + int rp0, + int lp1, + int rp1, + int lp2, + int rp2, + int lp3, + int rp3 + ); + + // pad each dimension with values on the other side of the torus (looping around) + GGML_API struct ggml_tensor * ggml_pad_ext_circular( + struct ggml_context * ctx, + struct ggml_tensor * a, + int lp0, + int rp0, + int lp1, + int rp1, + int lp2, + int rp2, + int lp3, + int rp3); + + // pad each dimension with reflection: [a, b, c, d] -> [b, a, b, c, d, c] + GGML_API struct ggml_tensor * ggml_pad_reflect_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int p0, + int p1); + + // Move tensor elements by an offset given for each dimension. Elements that + // are shifted beyond the last position are wrapped around to the beginning. + GGML_API struct ggml_tensor * ggml_roll( + struct ggml_context * ctx, + struct ggml_tensor * a, + int shift0, + int shift1, + int shift2, + int shift3); + + // Convert matrix into a triangular one (upper, strict upper, lower or strict lower) by writing + // zeroes everywhere outside the masked area + GGML_API struct ggml_tensor * ggml_tri( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_tri_type type); + + // Fill tensor a with constant c + GGML_API struct ggml_tensor * ggml_fill( + struct ggml_context * ctx, + struct ggml_tensor * a, + float c); + + GGML_API struct ggml_tensor * ggml_fill_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float c); + + // Ref: https://github.com/CompVis/stable-diffusion/blob/main/ldm/modules/diffusionmodules/util.py#L151 + // timesteps: [N,] + // return: [N, dim] + GGML_API struct ggml_tensor * ggml_timestep_embedding( + struct ggml_context * ctx, + struct ggml_tensor * timesteps, + int dim, + int max_period); + + // sort rows + enum ggml_sort_order { + GGML_SORT_ORDER_ASC, + GGML_SORT_ORDER_DESC, + }; + + GGML_API struct ggml_tensor * ggml_argsort( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_sort_order order); + + // similar to ggml_top_k but implemented as `argsort` + `view` + GGML_API struct ggml_tensor * ggml_argsort_top_k( + struct ggml_context * ctx, + struct ggml_tensor * a, + int k); + + // top k elements per row + // note: the resulting top k indices are in no particular order + GGML_API struct ggml_tensor * ggml_top_k( + struct ggml_context * ctx, + struct ggml_tensor * a, + int k); + + GGML_API struct ggml_tensor * ggml_arange( + struct ggml_context * ctx, + float start, + float stop, + float step); + + // q: [n_embd_k, n_batch, n_head, ne3 ] + // k: [n_embd_k, n_kv, n_head_kv, ne3 ] + // v: [n_embd_v, n_kv, n_head_kv, ne3 ] !! not transposed !! + // mask: [n_kv, n_batch, ne32, ne33] + // res: [n_embd_v, n_head, n_batch, ne3 ] !! permuted !! + // + // broadcast: + // n_head % n_head_kv == 0 + // n_head % ne32 == 0 + // ne3 % ne33 == 0 + // GGML_API struct ggml_tensor * ggml_flash_attn_ext( struct ggml_context * ctx, struct ggml_tensor * q, @@ -2472,6 +2513,77 @@ extern "C" { struct ggml_tensor * bias, int group_size); + // VibeASR CPU INT8 pipeline (GGML_TYPE_I8_S / GGML_TYPE_I2_S). + // + // Ported from https://github.com/microsoft/VibeASR.cpp, an end-to-end INT8 + // ASR stack (INT8 VAE encoder, ternary-weight language model) built for CPU + // inference on edge devices. + // + // These are CPU-only, mirroring how ggml_convrot_linear and + // ggml_sage_attn2_i8 above are CUDA-only. + // + // Unlike those two, the scale is NOT a separate F32 src tensor. An I8_S + // activation's scale is recomputed from that activation at run time, and a + // ggml node has exactly one output, so the scale has to travel with the + // data: every I8_S/I2_S tensor stores one F32 immediately after its int8 + // payload (see ggml_type_extra_bytes in ggml.c). Weight scales could have + // used the separate-tensor convention, but sharing one representation with + // activations keeps a single kernel per op instead of two. + + // y = a*scale + b, fusing a ConvNeXt LayerScale into its residual add. + // a and b are I8_S and same-shape, scale is F32 per-channel broadcast on + // ne[0]. Output is I8_S and carries a freshly computed per-tensor scale. + GGML_API struct ggml_tensor * ggml_add_scaled( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * scale); + + // y = rms_norm(a) * scale, fused. a is I8_S, scale is F32 per-channel, + // output is I8_S. Equivalent to ggml_mul(ggml_rms_norm(a), scale) but + // avoids materializing the F32 intermediate. + GGML_API struct ggml_tensor * ggml_rms_norm_scaled( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * scale, + float eps); + + // y = a*b + bias, with a I8_S weights and b I8_S activations. bias is F32 + // and broadcasts on ne[0]. Output is I8_S. The ternary I2_S weights of the + // language model go through plain ggml_mul_mat, which has no bias to fuse. + // + // a with ne[1] == 1 and ne[2] > 1 selects a depthwise contraction: one + // length-ne[0] filter per channel, output indexed channel-major. + GGML_API struct ggml_tensor * ggml_mul_mat_add( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * bias); + + // As ggml_mul_mat_add, with ReLU folded into the epilogue. + GGML_API struct ggml_tensor * ggml_mul_mat_add_relu( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * bias); + + // im2col with independent left/right padding on the width axis. ggml_im2col + // only takes a single symmetric p0, so causal 1D convolutions otherwise need + // a separate ggml_pad_ext node and a full copy of the activation. + GGML_API struct ggml_tensor * ggml_im2col_asym( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int s1, + int lp0, + int rp0, + int p1, + int d0, + int d1, + bool is_2D, + enum ggml_type dst_type); + // MINITTS_FLASH_BIAS_WRAPPER: // Helper for models that already assemble a dense additive attention bias // (for example relative-position scores). The helper expands an optional @@ -2492,314 +2604,314 @@ extern "C" { GGML_API void ggml_flash_attn_ext_set_prec( struct ggml_tensor * a, enum ggml_prec prec); - - GGML_API enum ggml_prec ggml_flash_attn_ext_get_prec( - const struct ggml_tensor * a); - - GGML_API void ggml_flash_attn_ext_add_sinks( - struct ggml_tensor * a, - struct ggml_tensor * sinks); - - // TODO: needs to be adapted to ggml_flash_attn_ext - GGML_API struct ggml_tensor * ggml_flash_attn_back( - struct ggml_context * ctx, - struct ggml_tensor * q, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * d, - bool masked); - - GGML_API struct ggml_tensor * ggml_ssm_conv( - struct ggml_context * ctx, - struct ggml_tensor * sx, - struct ggml_tensor * c); - - GGML_API struct ggml_tensor * ggml_ssm_scan( - struct ggml_context * ctx, - struct ggml_tensor * s, - struct ggml_tensor * x, - struct ggml_tensor * dt, - struct ggml_tensor * A, - struct ggml_tensor * B, - struct ggml_tensor * C, - struct ggml_tensor * ids); - - // partition into non-overlapping windows with padding if needed - // example: - // a: 768 64 64 1 - // w: 14 - // res: 768 14 14 25 - // used in sam - GGML_API struct ggml_tensor * ggml_win_part( - struct ggml_context * ctx, - struct ggml_tensor * a, - int w); - - // reverse of ggml_win_part - // used in sam - GGML_API struct ggml_tensor * ggml_win_unpart( - struct ggml_context * ctx, - struct ggml_tensor * a, - int w0, - int h0, - int w); - - GGML_API struct ggml_tensor * ggml_unary( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_unary_op op); - - GGML_API struct ggml_tensor * ggml_unary_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_unary_op op); - - // used in sam - GGML_API struct ggml_tensor * ggml_get_rel_pos( - struct ggml_context * ctx, - struct ggml_tensor * a, - int qh, - int kh); - - // used in sam - GGML_API struct ggml_tensor * ggml_add_rel_pos( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * pw, - struct ggml_tensor * ph); - - GGML_API struct ggml_tensor * ggml_add_rel_pos_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * pw, - struct ggml_tensor * ph); - - GGML_API struct ggml_tensor * ggml_rwkv_wkv6( - struct ggml_context * ctx, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * r, - struct ggml_tensor * tf, - struct ggml_tensor * td, - struct ggml_tensor * state); - - GGML_API struct ggml_tensor * ggml_gated_linear_attn( - struct ggml_context * ctx, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * q, - struct ggml_tensor * g, - struct ggml_tensor * state, - float scale); - - GGML_API struct ggml_tensor * ggml_rwkv_wkv7( - struct ggml_context * ctx, - struct ggml_tensor * r, - struct ggml_tensor * w, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * state); - - /* Solves a specific equation of the form Ax=B, where A is a triangular matrix - * without zeroes on the diagonal (i.e. invertible). - * B can have any number of columns, but must have the same number of rows as A - * If A is [n, n] and B is [n, m], then the result will be [n, m] as well - * Has O(n^3) complexity (unlike most matrix ops out there), so use on cases - * where n > 100 sparingly, pre-chunk if necessary. - * - * If left = false, solves xA=B instead - * If lower = false, assumes upper triangular instead - * If uni = true, assumes diagonal of A to be all ones (will override actual values) - * - * TODO: currently only lower, right, non-unitriangular variant is implemented - */ - GGML_API struct ggml_tensor * ggml_solve_tri( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - bool left, - bool lower, - bool uni); - - // TODO: add ggml_gated_delta_net_set_bcast() to be able to configure Q, K broadcast type: tiled vs interleaved [TAG_GGML_GDN_BCAST] - // ref: https://github.com/ggml-org/llama.cpp/pull/19468#discussion_r2786394306 - // - // state is a 3D tensor of shape (S_v*S_v*H, K, n_seqs): - // K == 1: output carries the final state only. - // K > 1: output carries K snapshot slots; the kernel writes the last min(n_tokens, K) - // per-token snapshots into the trailing slots - GGML_API struct ggml_tensor * ggml_gated_delta_net( - struct ggml_context * ctx, - struct ggml_tensor * q, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * g, - struct ggml_tensor * beta, - struct ggml_tensor * state); - - // custom operators - - typedef void (*ggml_custom1_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, int ith, int nth, void * userdata); - typedef void (*ggml_custom2_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, const struct ggml_tensor * b, int ith, int nth, void * userdata); - typedef void (*ggml_custom3_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, const struct ggml_tensor * b, const struct ggml_tensor * c, int ith, int nth, void * userdata); - -#define GGML_N_TASKS_MAX (-1) - // n_tasks == GGML_N_TASKS_MAX means to use max number of tasks - - GGML_API struct ggml_tensor * ggml_map_custom1( - struct ggml_context * ctx, - struct ggml_tensor * a, - ggml_custom1_op_t fun, - int n_tasks, - void * userdata); - - GGML_API struct ggml_tensor * ggml_map_custom1_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - ggml_custom1_op_t fun, - int n_tasks, - void * userdata); - - GGML_API struct ggml_tensor * ggml_map_custom2( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - ggml_custom2_op_t fun, - int n_tasks, - void * userdata); - - GGML_API struct ggml_tensor * ggml_map_custom2_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - ggml_custom2_op_t fun, - int n_tasks, - void * userdata); - - GGML_API struct ggml_tensor * ggml_map_custom3( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - ggml_custom3_op_t fun, - int n_tasks, - void * userdata); - - GGML_API struct ggml_tensor * ggml_map_custom3_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - ggml_custom3_op_t fun, - int n_tasks, - void * userdata); - - typedef void (*ggml_custom_op_t)(struct ggml_tensor * dst , int ith, int nth, void * userdata); - - GGML_API struct ggml_tensor * ggml_custom_4d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3, - struct ggml_tensor ** args, - int n_args, - ggml_custom_op_t fun, - int n_tasks, - void * userdata); - - GGML_API struct ggml_tensor * ggml_custom_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor ** args, - int n_args, - ggml_custom_op_t fun, - int n_tasks, - void * userdata); - - // loss function - - GGML_API struct ggml_tensor * ggml_cross_entropy_loss( - struct ggml_context * ctx, - struct ggml_tensor * a, // logits - struct ggml_tensor * b); // labels - - GGML_API struct ggml_tensor * ggml_cross_entropy_loss_back( - struct ggml_context * ctx, - struct ggml_tensor * a, // logits - struct ggml_tensor * b, // labels - struct ggml_tensor * c); // gradients of cross_entropy_loss result - - // AdamW optimizer step - // Paper: https://arxiv.org/pdf/1711.05101v3.pdf - // PyTorch: https://pytorch.org/docs/stable/generated/torch.optim.AdamW.html - GGML_API struct ggml_tensor * ggml_opt_step_adamw( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * grad, - struct ggml_tensor * m, - struct ggml_tensor * v, - struct ggml_tensor * adamw_params); // parameters such as the learning rate - - // stochastic gradient descent step (with weight decay) - GGML_API struct ggml_tensor * ggml_opt_step_sgd( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * grad, - struct ggml_tensor * sgd_params); // alpha, weight decay - - // build forward multiple tensors and select one of them for computing - // this is useful for creating graphs that have constant topology but compute different things based on the input - // ref: https://github.com/ggml-org/llama.cpp/pull/18550 - // - // nodes: - // | - build forward into the graph but do not compute - // c - build forward into the graph and compute - // - // | | ... c ... | - // | | ... c ... | - // | | ... c ... | - // [0 1 ... idx ... n-1] <-- ggml_build_forward_select(..., n, idx) - // c - // c - // - // example: - // struct ggml_tensor * curs[3]; - // - // curs[0] = compute0(...); - // curs[1] = compute1(...); - // curs[2] = compute2(...); - // - // int idx = select_branch(some_input); - // - // struct ggml_tensor * out = ggml_build_forward_select(cgraph, curs, 3, idx); - // - GGML_API struct ggml_tensor * ggml_build_forward_select( - struct ggml_cgraph * cgraph, - struct ggml_tensor ** tensors, - int n_tensors, - int idx); - - GGML_API void ggml_build_forward_expand( - struct ggml_cgraph * cgraph, - struct ggml_tensor * tensor); - - GGML_API void ggml_build_backward_expand( - struct ggml_context * ctx, // context for gradient computation - struct ggml_cgraph * cgraph, - struct ggml_tensor ** grad_accs); - - // graph allocation in a context - GGML_API struct ggml_cgraph * ggml_new_graph (struct ggml_context * ctx); // size = GGML_DEFAULT_GRAPH_SIZE, grads = false - GGML_API struct ggml_cgraph * ggml_new_graph_custom(struct ggml_context * ctx, size_t size, bool grads); - GGML_API struct ggml_cgraph * ggml_graph_dup (struct ggml_context * ctx, struct ggml_cgraph * cgraph, bool force_grads); - GGML_API void ggml_graph_cpy (struct ggml_cgraph * src, struct ggml_cgraph * dst); - GGML_API void ggml_graph_reset (struct ggml_cgraph * cgraph); // set regular grads + optimizer momenta to 0, set loss grad to 1 - GGML_API void ggml_graph_clear (struct ggml_cgraph * cgraph); - + + GGML_API enum ggml_prec ggml_flash_attn_ext_get_prec( + const struct ggml_tensor * a); + + GGML_API void ggml_flash_attn_ext_add_sinks( + struct ggml_tensor * a, + struct ggml_tensor * sinks); + + // TODO: needs to be adapted to ggml_flash_attn_ext + GGML_API struct ggml_tensor * ggml_flash_attn_back( + struct ggml_context * ctx, + struct ggml_tensor * q, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * d, + bool masked); + + GGML_API struct ggml_tensor * ggml_ssm_conv( + struct ggml_context * ctx, + struct ggml_tensor * sx, + struct ggml_tensor * c); + + GGML_API struct ggml_tensor * ggml_ssm_scan( + struct ggml_context * ctx, + struct ggml_tensor * s, + struct ggml_tensor * x, + struct ggml_tensor * dt, + struct ggml_tensor * A, + struct ggml_tensor * B, + struct ggml_tensor * C, + struct ggml_tensor * ids); + + // partition into non-overlapping windows with padding if needed + // example: + // a: 768 64 64 1 + // w: 14 + // res: 768 14 14 25 + // used in sam + GGML_API struct ggml_tensor * ggml_win_part( + struct ggml_context * ctx, + struct ggml_tensor * a, + int w); + + // reverse of ggml_win_part + // used in sam + GGML_API struct ggml_tensor * ggml_win_unpart( + struct ggml_context * ctx, + struct ggml_tensor * a, + int w0, + int h0, + int w); + + GGML_API struct ggml_tensor * ggml_unary( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_unary_op op); + + GGML_API struct ggml_tensor * ggml_unary_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_unary_op op); + + // used in sam + GGML_API struct ggml_tensor * ggml_get_rel_pos( + struct ggml_context * ctx, + struct ggml_tensor * a, + int qh, + int kh); + + // used in sam + GGML_API struct ggml_tensor * ggml_add_rel_pos( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * pw, + struct ggml_tensor * ph); + + GGML_API struct ggml_tensor * ggml_add_rel_pos_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * pw, + struct ggml_tensor * ph); + + GGML_API struct ggml_tensor * ggml_rwkv_wkv6( + struct ggml_context * ctx, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * r, + struct ggml_tensor * tf, + struct ggml_tensor * td, + struct ggml_tensor * state); + + GGML_API struct ggml_tensor * ggml_gated_linear_attn( + struct ggml_context * ctx, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * q, + struct ggml_tensor * g, + struct ggml_tensor * state, + float scale); + + GGML_API struct ggml_tensor * ggml_rwkv_wkv7( + struct ggml_context * ctx, + struct ggml_tensor * r, + struct ggml_tensor * w, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * state); + + /* Solves a specific equation of the form Ax=B, where A is a triangular matrix + * without zeroes on the diagonal (i.e. invertible). + * B can have any number of columns, but must have the same number of rows as A + * If A is [n, n] and B is [n, m], then the result will be [n, m] as well + * Has O(n^3) complexity (unlike most matrix ops out there), so use on cases + * where n > 100 sparingly, pre-chunk if necessary. + * + * If left = false, solves xA=B instead + * If lower = false, assumes upper triangular instead + * If uni = true, assumes diagonal of A to be all ones (will override actual values) + * + * TODO: currently only lower, right, non-unitriangular variant is implemented + */ + GGML_API struct ggml_tensor * ggml_solve_tri( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + bool left, + bool lower, + bool uni); + + // TODO: add ggml_gated_delta_net_set_bcast() to be able to configure Q, K broadcast type: tiled vs interleaved [TAG_GGML_GDN_BCAST] + // ref: https://github.com/ggml-org/llama.cpp/pull/19468#discussion_r2786394306 + // + // state is a 3D tensor of shape (S_v*S_v*H, K, n_seqs): + // K == 1: output carries the final state only. + // K > 1: output carries K snapshot slots; the kernel writes the last min(n_tokens, K) + // per-token snapshots into the trailing slots + GGML_API struct ggml_tensor * ggml_gated_delta_net( + struct ggml_context * ctx, + struct ggml_tensor * q, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * g, + struct ggml_tensor * beta, + struct ggml_tensor * state); + + // custom operators + + typedef void (*ggml_custom1_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, int ith, int nth, void * userdata); + typedef void (*ggml_custom2_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, const struct ggml_tensor * b, int ith, int nth, void * userdata); + typedef void (*ggml_custom3_op_t)(struct ggml_tensor * dst , const struct ggml_tensor * a, const struct ggml_tensor * b, const struct ggml_tensor * c, int ith, int nth, void * userdata); + +#define GGML_N_TASKS_MAX (-1) + // n_tasks == GGML_N_TASKS_MAX means to use max number of tasks + + GGML_API struct ggml_tensor * ggml_map_custom1( + struct ggml_context * ctx, + struct ggml_tensor * a, + ggml_custom1_op_t fun, + int n_tasks, + void * userdata); + + GGML_API struct ggml_tensor * ggml_map_custom1_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + ggml_custom1_op_t fun, + int n_tasks, + void * userdata); + + GGML_API struct ggml_tensor * ggml_map_custom2( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + ggml_custom2_op_t fun, + int n_tasks, + void * userdata); + + GGML_API struct ggml_tensor * ggml_map_custom2_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + ggml_custom2_op_t fun, + int n_tasks, + void * userdata); + + GGML_API struct ggml_tensor * ggml_map_custom3( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + ggml_custom3_op_t fun, + int n_tasks, + void * userdata); + + GGML_API struct ggml_tensor * ggml_map_custom3_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + ggml_custom3_op_t fun, + int n_tasks, + void * userdata); + + typedef void (*ggml_custom_op_t)(struct ggml_tensor * dst , int ith, int nth, void * userdata); + + GGML_API struct ggml_tensor * ggml_custom_4d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3, + struct ggml_tensor ** args, + int n_args, + ggml_custom_op_t fun, + int n_tasks, + void * userdata); + + GGML_API struct ggml_tensor * ggml_custom_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor ** args, + int n_args, + ggml_custom_op_t fun, + int n_tasks, + void * userdata); + + // loss function + + GGML_API struct ggml_tensor * ggml_cross_entropy_loss( + struct ggml_context * ctx, + struct ggml_tensor * a, // logits + struct ggml_tensor * b); // labels + + GGML_API struct ggml_tensor * ggml_cross_entropy_loss_back( + struct ggml_context * ctx, + struct ggml_tensor * a, // logits + struct ggml_tensor * b, // labels + struct ggml_tensor * c); // gradients of cross_entropy_loss result + + // AdamW optimizer step + // Paper: https://arxiv.org/pdf/1711.05101v3.pdf + // PyTorch: https://pytorch.org/docs/stable/generated/torch.optim.AdamW.html + GGML_API struct ggml_tensor * ggml_opt_step_adamw( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * grad, + struct ggml_tensor * m, + struct ggml_tensor * v, + struct ggml_tensor * adamw_params); // parameters such as the learning rate + + // stochastic gradient descent step (with weight decay) + GGML_API struct ggml_tensor * ggml_opt_step_sgd( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * grad, + struct ggml_tensor * sgd_params); // alpha, weight decay + + // build forward multiple tensors and select one of them for computing + // this is useful for creating graphs that have constant topology but compute different things based on the input + // ref: https://github.com/ggml-org/llama.cpp/pull/18550 + // + // nodes: + // | - build forward into the graph but do not compute + // c - build forward into the graph and compute + // + // | | ... c ... | + // | | ... c ... | + // | | ... c ... | + // [0 1 ... idx ... n-1] <-- ggml_build_forward_select(..., n, idx) + // c + // c + // + // example: + // struct ggml_tensor * curs[3]; + // + // curs[0] = compute0(...); + // curs[1] = compute1(...); + // curs[2] = compute2(...); + // + // int idx = select_branch(some_input); + // + // struct ggml_tensor * out = ggml_build_forward_select(cgraph, curs, 3, idx); + // + GGML_API struct ggml_tensor * ggml_build_forward_select( + struct ggml_cgraph * cgraph, + struct ggml_tensor ** tensors, + int n_tensors, + int idx); + + GGML_API void ggml_build_forward_expand( + struct ggml_cgraph * cgraph, + struct ggml_tensor * tensor); + + GGML_API void ggml_build_backward_expand( + struct ggml_context * ctx, // context for gradient computation + struct ggml_cgraph * cgraph, + struct ggml_tensor ** grad_accs); + + // graph allocation in a context + GGML_API struct ggml_cgraph * ggml_new_graph (struct ggml_context * ctx); // size = GGML_DEFAULT_GRAPH_SIZE, grads = false + GGML_API struct ggml_cgraph * ggml_new_graph_custom(struct ggml_context * ctx, size_t size, bool grads); + GGML_API struct ggml_cgraph * ggml_graph_dup (struct ggml_context * ctx, struct ggml_cgraph * cgraph, bool force_grads); + GGML_API void ggml_graph_cpy (struct ggml_cgraph * src, struct ggml_cgraph * dst); + GGML_API void ggml_graph_reset (struct ggml_cgraph * cgraph); // set regular grads + optimizer momenta to 0, set loss grad to 1 + GGML_API void ggml_graph_clear (struct ggml_cgraph * cgraph); + GGML_API int ggml_graph_size (struct ggml_cgraph * cgraph); GGML_API struct ggml_tensor * ggml_graph_node (struct ggml_cgraph * cgraph, int i); // if i < 0, returns nodes[n_nodes + i] GGML_API struct ggml_tensor ** ggml_graph_nodes (struct ggml_cgraph * cgraph); @@ -2807,124 +2919,124 @@ extern "C" { GGML_API void ggml_graph_set_n_nodes(struct ggml_cgraph * cgraph, int n_nodes); GGML_API void ggml_graph_add_node(struct ggml_cgraph * cgraph, struct ggml_tensor * tensor); - - GGML_API size_t ggml_graph_overhead(void); - GGML_API size_t ggml_graph_overhead_custom(size_t size, bool grads); - - GGML_API struct ggml_tensor * ggml_graph_get_tensor (const struct ggml_cgraph * cgraph, const char * name); - GGML_API struct ggml_tensor * ggml_graph_get_grad (const struct ggml_cgraph * cgraph, const struct ggml_tensor * node); - GGML_API struct ggml_tensor * ggml_graph_get_grad_acc(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node); - - // print info and performance information for the graph - GGML_API void ggml_graph_print(const struct ggml_cgraph * cgraph); - - // dump the graph into a file using the dot format - GGML_API void ggml_graph_dump_dot(const struct ggml_cgraph * gb, const struct ggml_cgraph * cgraph, const char * filename); - - // TODO these functions were sandwiched in the old optimization interface, is there a better place for them? - typedef void (*ggml_log_callback)(enum ggml_log_level level, const char * text, void * user_data); - - // Set callback for all future logging events. - // If this is not called, or NULL is supplied, everything is output on stderr. - GGML_API void ggml_log_get(ggml_log_callback * log_callback, void ** user_data); - GGML_API void ggml_log_set(ggml_log_callback log_callback, void * user_data); - - GGML_API struct ggml_tensor * ggml_set_zero(struct ggml_tensor * tensor); - - // - // quantization - // - - // - ggml_quantize_init can be called multiple times with the same type - // it will only initialize the quantization tables for the first call or after ggml_quantize_free - // automatically called by ggml_quantize_chunk for convenience - // - // - ggml_quantize_free will free any memory allocated by ggml_quantize_init - // call this at the end of the program to avoid memory leaks - // - // note: these are thread-safe - // - GGML_API void ggml_quantize_init(enum ggml_type type); - GGML_API void ggml_quantize_free(void); - - // some quantization type cannot be used without an importance matrix - GGML_API bool ggml_quantize_requires_imatrix(enum ggml_type type); - - // calls ggml_quantize_init internally (i.e. can allocate memory) - GGML_API size_t ggml_quantize_chunk( - enum ggml_type type, - const float * src, - void * dst, - int64_t start, - int64_t nrows, - int64_t n_per_row, - const float * imatrix); - -#ifdef __cplusplus - // restrict not standard in C++ -# if defined(__GNUC__) -# define GGML_RESTRICT __restrict__ -# elif defined(__clang__) -# define GGML_RESTRICT __restrict -# elif defined(_MSC_VER) -# define GGML_RESTRICT __restrict -# else -# define GGML_RESTRICT -# endif -#else -# if defined (_MSC_VER) && (__STDC_VERSION__ < 201112L) -# define GGML_RESTRICT __restrict -# else -# define GGML_RESTRICT restrict -# endif -#endif - typedef void (*ggml_to_float_t) (const void * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k); - typedef void (*ggml_from_float_t)(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k); - - struct ggml_type_traits { - const char * type_name; - int64_t blck_size; - int64_t blck_size_interleave; // interleave elements in blocks - size_t type_size; - bool is_quantized; - ggml_to_float_t to_float; - ggml_from_float_t from_float_ref; - }; - - GGML_API const struct ggml_type_traits * ggml_get_type_traits(enum ggml_type type); - - // ggml threadpool - // TODO: currently, only a few functions are in the base ggml API, while the rest are in the CPU backend - // the goal should be to create an API that other backends can use move everything to the ggml base - - // scheduling priorities - enum ggml_sched_priority { - GGML_SCHED_PRIO_LOW = -1, - GGML_SCHED_PRIO_NORMAL, - GGML_SCHED_PRIO_MEDIUM, - GGML_SCHED_PRIO_HIGH, - GGML_SCHED_PRIO_REALTIME - }; - - // threadpool params - // Use ggml_threadpool_params_default() or ggml_threadpool_params_init() to populate the defaults - struct ggml_threadpool_params { - bool cpumask[GGML_MAX_N_THREADS]; // mask of cpu cores (all-zeros means use default affinity settings) - int n_threads; // number of threads - enum ggml_sched_priority prio; // thread priority - uint32_t poll; // polling level (0 - no polling, 100 - aggressive polling) - bool strict_cpu; // strict cpu placement - bool paused; // start in paused state - }; - - struct ggml_threadpool; // forward declaration, see ggml.c - - typedef struct ggml_threadpool * ggml_threadpool_t; - - GGML_API struct ggml_threadpool_params ggml_threadpool_params_default(int n_threads); - GGML_API void ggml_threadpool_params_init (struct ggml_threadpool_params * p, int n_threads); - GGML_API bool ggml_threadpool_params_match (const struct ggml_threadpool_params * p0, const struct ggml_threadpool_params * p1); - -#ifdef __cplusplus -} -#endif + + GGML_API size_t ggml_graph_overhead(void); + GGML_API size_t ggml_graph_overhead_custom(size_t size, bool grads); + + GGML_API struct ggml_tensor * ggml_graph_get_tensor (const struct ggml_cgraph * cgraph, const char * name); + GGML_API struct ggml_tensor * ggml_graph_get_grad (const struct ggml_cgraph * cgraph, const struct ggml_tensor * node); + GGML_API struct ggml_tensor * ggml_graph_get_grad_acc(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node); + + // print info and performance information for the graph + GGML_API void ggml_graph_print(const struct ggml_cgraph * cgraph); + + // dump the graph into a file using the dot format + GGML_API void ggml_graph_dump_dot(const struct ggml_cgraph * gb, const struct ggml_cgraph * cgraph, const char * filename); + + // TODO these functions were sandwiched in the old optimization interface, is there a better place for them? + typedef void (*ggml_log_callback)(enum ggml_log_level level, const char * text, void * user_data); + + // Set callback for all future logging events. + // If this is not called, or NULL is supplied, everything is output on stderr. + GGML_API void ggml_log_get(ggml_log_callback * log_callback, void ** user_data); + GGML_API void ggml_log_set(ggml_log_callback log_callback, void * user_data); + + GGML_API struct ggml_tensor * ggml_set_zero(struct ggml_tensor * tensor); + + // + // quantization + // + + // - ggml_quantize_init can be called multiple times with the same type + // it will only initialize the quantization tables for the first call or after ggml_quantize_free + // automatically called by ggml_quantize_chunk for convenience + // + // - ggml_quantize_free will free any memory allocated by ggml_quantize_init + // call this at the end of the program to avoid memory leaks + // + // note: these are thread-safe + // + GGML_API void ggml_quantize_init(enum ggml_type type); + GGML_API void ggml_quantize_free(void); + + // some quantization type cannot be used without an importance matrix + GGML_API bool ggml_quantize_requires_imatrix(enum ggml_type type); + + // calls ggml_quantize_init internally (i.e. can allocate memory) + GGML_API size_t ggml_quantize_chunk( + enum ggml_type type, + const float * src, + void * dst, + int64_t start, + int64_t nrows, + int64_t n_per_row, + const float * imatrix); + +#ifdef __cplusplus + // restrict not standard in C++ +# if defined(__GNUC__) +# define GGML_RESTRICT __restrict__ +# elif defined(__clang__) +# define GGML_RESTRICT __restrict +# elif defined(_MSC_VER) +# define GGML_RESTRICT __restrict +# else +# define GGML_RESTRICT +# endif +#else +# if defined (_MSC_VER) && (__STDC_VERSION__ < 201112L) +# define GGML_RESTRICT __restrict +# else +# define GGML_RESTRICT restrict +# endif +#endif + typedef void (*ggml_to_float_t) (const void * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k); + typedef void (*ggml_from_float_t)(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t k); + + struct ggml_type_traits { + const char * type_name; + int64_t blck_size; + int64_t blck_size_interleave; // interleave elements in blocks + size_t type_size; + bool is_quantized; + ggml_to_float_t to_float; + ggml_from_float_t from_float_ref; + }; + + GGML_API const struct ggml_type_traits * ggml_get_type_traits(enum ggml_type type); + + // ggml threadpool + // TODO: currently, only a few functions are in the base ggml API, while the rest are in the CPU backend + // the goal should be to create an API that other backends can use move everything to the ggml base + + // scheduling priorities + enum ggml_sched_priority { + GGML_SCHED_PRIO_LOW = -1, + GGML_SCHED_PRIO_NORMAL, + GGML_SCHED_PRIO_MEDIUM, + GGML_SCHED_PRIO_HIGH, + GGML_SCHED_PRIO_REALTIME + }; + + // threadpool params + // Use ggml_threadpool_params_default() or ggml_threadpool_params_init() to populate the defaults + struct ggml_threadpool_params { + bool cpumask[GGML_MAX_N_THREADS]; // mask of cpu cores (all-zeros means use default affinity settings) + int n_threads; // number of threads + enum ggml_sched_priority prio; // thread priority + uint32_t poll; // polling level (0 - no polling, 100 - aggressive polling) + bool strict_cpu; // strict cpu placement + bool paused; // start in paused state + }; + + struct ggml_threadpool; // forward declaration, see ggml.c + + typedef struct ggml_threadpool * ggml_threadpool_t; + + GGML_API struct ggml_threadpool_params ggml_threadpool_params_default(int n_threads); + GGML_API void ggml_threadpool_params_init (struct ggml_threadpool_params * p, int n_threads); + GGML_API bool ggml_threadpool_params_match (const struct ggml_threadpool_params * p0, const struct ggml_threadpool_params * p1); + +#ifdef __cplusplus +} +#endif diff --git a/external/ggml/src/ggml-cpu/ggml-cpu.c b/external/ggml/src/ggml-cpu/ggml-cpu.c index d9ec09939..1d6ac3373 100644 --- a/external/ggml/src/ggml-cpu/ggml-cpu.c +++ b/external/ggml/src/ggml-cpu/ggml-cpu.c @@ -1255,6 +1255,17 @@ void ggml_compute_forward_mul_mat( return; } + // Ternary weights own their activation quantization, so they cannot use the + // vec_dot_type path below: that quantizes src1 one row at a time into a + // fixed row_size and hands the kernel nothing but two row pointers, while + // I2_S needs the per-row activation scale and int8 row sum to survive into + // the epilogue. Branching here rather than adding an op keeps the language + // model graph on plain ggml_mul_mat. + if (src0->type == GGML_TYPE_I2_S) { + ggml_compute_forward_mul_mat_i2_s(params, dst); + return; + } + GGML_TENSOR_BINARY_OP_LOCALS const int ith = params->ith; @@ -1697,6 +1708,93 @@ static void ggml_compute_forward_mul_mat_id( } } +// reference implementation of the accumulate-in-place matmul (dst aliases src[2]): +// dst += src0 * src1. The op is only exercised on Metal; this plain single-threaded +// loop exists so the CPU backend stays correct if a graph containing it is ever run. +static void ggml_compute_forward_mul_mat_acc( + const struct ggml_compute_params * params, + struct ggml_tensor * dst) { + const struct ggml_tensor * src0 = dst->src[0]; // a [K, M] + const struct ggml_tensor * src1 = dst->src[1]; // b [K, N] + + GGML_ASSERT(dst->type == GGML_TYPE_F32); + GGML_ASSERT(src0->type == GGML_TYPE_F32); + GGML_ASSERT(src1->type == GGML_TYPE_F32); + + if (params->ith != 0) { + return; + } + + const int64_t K = src0->ne[0]; + const int64_t M = src0->ne[1]; + const int64_t N = src1->ne[1]; + + GGML_ASSERT(src1->ne[0] == K); + GGML_ASSERT(dst->ne[0] == M && dst->ne[1] == N); + GGML_ASSERT(src0->ne[2] == 1 && src0->ne[3] == 1); + GGML_ASSERT(src1->ne[2] == 1 && src1->ne[3] == 1); + GGML_ASSERT(dst->ne[2] == 1 && dst->ne[3] == 1); + + const char * A = (const char *) src0->data; + const char * B = (const char *) src1->data; + char * C = (char *) dst->data; + + for (int64_t n = 0; n < N; ++n) { + for (int64_t m = 0; m < M; ++m) { + float sum = 0.0f; + for (int64_t k = 0; k < K; ++k) { + const float av = *(const float *) (A + k*src0->nb[0] + m*src0->nb[1]); + const float bv = *(const float *) (B + k*src1->nb[0] + n*src1->nb[1]); + sum += av * bv; + } + float * cv = (float *) (C + m*dst->nb[0] + n*dst->nb[1]); + *cv += sum; + } + } +} + +// ggml_compute_forward_snake_1d +// +// fused snake activation: y = x + sin(x * alpha)^2 / alpha, alpha broadcast per channel. +// naive single-threaded reference so the CPU backend stays correct if a graph containing +// this op is ever run there. +static void ggml_compute_forward_snake_1d( + const struct ggml_compute_params * params, + struct ggml_tensor * dst) { + const struct ggml_tensor * src0 = dst->src[0]; // x [C, T], channels on the fast axis + const struct ggml_tensor * src1 = dst->src[1]; // alpha [C, 1] + + if (params->ith != 0) { + return; + } + + GGML_ASSERT(dst->type == GGML_TYPE_F32); + GGML_ASSERT(src0->type == GGML_TYPE_F32); + GGML_ASSERT(src1->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_contiguous(src0)); + GGML_ASSERT(ggml_is_contiguous(src1)); + GGML_ASSERT(ggml_is_contiguous(dst)); + GGML_ASSERT(src0->ne[2] == 1 && src0->ne[3] == 1); + GGML_ASSERT(src1->ne[0] == src0->ne[0] && src1->ne[1] == 1); + + const int64_t nc = src0->ne[0]; + const int64_t nt = src0->ne[1]; + + const float * x = (const float *) src0->data; + const float * a = (const float *) src1->data; + float * y = (float *) dst->data; + + for (int64_t t = 0; t < nt; t++) { + for (int64_t c = 0; c < nc; c++) { + const float av = a[c]; + const float xv = x[t*nc + c]; + const float ax = xv * av; + const float s = sinf(ax); + y[t*nc + c] = xv + (s*s)/av; + } + } +} + ///////////////////////////////// static void ggml_compute_forward(struct ggml_compute_params * params, struct ggml_tensor * tensor) { @@ -1825,10 +1923,18 @@ static void ggml_compute_forward(struct ggml_compute_params * params, struct ggm ggml_compute_forward_l2_norm(params, tensor); } break; case GGML_OP_MUL_MAT: - case GGML_OP_MUL_MAT_PACK4: + case GGML_OP_MUL_MAT_PACK4: { ggml_compute_forward_mul_mat(params, tensor); } break; + case GGML_OP_MUL_MAT_ACC: + { + ggml_compute_forward_mul_mat_acc(params, tensor); + } break; + case GGML_OP_SNAKE_1D: + { + ggml_compute_forward_snake_1d(params, tensor); + } break; case GGML_OP_MUL_MAT_ID: { ggml_compute_forward_mul_mat_id(params, tensor); @@ -1901,18 +2007,18 @@ static void ggml_compute_forward(struct ggml_compute_params * params, struct ggm { ggml_compute_forward_conv_transpose_1d(params, tensor); } break; - case GGML_OP_IM2COL: - { - ggml_compute_forward_im2col(params, tensor); - } break; - case GGML_OP_IM2COL_FAST_1D: - { - ggml_compute_forward_im2col_fast_1d(params, tensor); - } break; - case GGML_OP_IM2COL_BACK: - { - ggml_compute_forward_im2col_back_f32(params, tensor); - } break; + case GGML_OP_IM2COL: + { + ggml_compute_forward_im2col(params, tensor); + } break; + case GGML_OP_IM2COL_FAST_1D: + { + ggml_compute_forward_im2col_fast_1d(params, tensor); + } break; + case GGML_OP_IM2COL_BACK: + { + ggml_compute_forward_im2col_back_f32(params, tensor); + } break; case GGML_OP_IM2COL_3D: { ggml_compute_forward_im2col_3d(params, tensor); @@ -2092,6 +2198,26 @@ static void ggml_compute_forward(struct ggml_compute_params * params, struct ggm ggml_compute_forward_opt_step_sgd(params, tensor); } break; + case GGML_OP_ADD_SCALED: + { + ggml_compute_forward_add_scaled(params, tensor); + } break; + case GGML_OP_RMS_NORM_SCALED: + { + ggml_compute_forward_rms_norm_scaled(params, tensor); + } break; + case GGML_OP_MUL_MAT_ADD: + { + ggml_compute_forward_mul_mat_add(params, tensor); + } break; + case GGML_OP_MUL_MAT_ADD_RELU: + { + ggml_compute_forward_mul_mat_add_relu(params, tensor); + } break; + case GGML_OP_IM2COL_ASYM: + { + ggml_compute_forward_im2col_asym(params, tensor); + } break; case GGML_OP_NONE: { // nop @@ -2260,6 +2386,7 @@ static int ggml_get_n_tasks(struct ggml_tensor * node, int n_threads) { case GGML_UNARY_OP_CEIL: case GGML_UNARY_OP_ROUND: case GGML_UNARY_OP_TRUNC: + case GGML_UNARY_OP_ROUND_BF16: { n_tasks = 1; } break; @@ -2301,12 +2428,22 @@ static int ggml_get_n_tasks(struct ggml_tensor * node, int n_threads) { case GGML_OP_GROUP_NORM: case GGML_OP_CONCAT: case GGML_OP_MUL_MAT: - case GGML_OP_MUL_MAT_PACK4: + case GGML_OP_MUL_MAT_PACK4: case GGML_OP_MUL_MAT_ID: case GGML_OP_OUT_PROD: { n_tasks = n_threads; } break; + case GGML_OP_MUL_MAT_ACC: + { + // reference implementation is single-threaded + n_tasks = 1; + } break; + case GGML_OP_SNAKE_1D: + { + // reference implementation is single-threaded + n_tasks = 1; + } break; case GGML_OP_GET_ROWS: case GGML_OP_SET_ROWS: { @@ -2343,15 +2480,20 @@ static int ggml_get_n_tasks(struct ggml_tensor * node, int n_threads) { { n_tasks = MIN(n_threads, ggml_nrows(node->src[0])); } break; - case GGML_OP_IM2COL: - case GGML_OP_IM2COL_FAST_1D: - case GGML_OP_IM2COL_BACK: - case GGML_OP_IM2COL_3D: - case GGML_OP_CONV_2D: + case GGML_OP_IM2COL: + case GGML_OP_IM2COL_FAST_1D: + case GGML_OP_IM2COL_ASYM: + case GGML_OP_IM2COL_BACK: + case GGML_OP_IM2COL_3D: + case GGML_OP_CONV_2D: case GGML_OP_CONV_3D: case GGML_OP_CONV_2D_DW: case GGML_OP_CONV_TRANSPOSE_1D: case GGML_OP_CONV_TRANSPOSE_2D: + case GGML_OP_ADD_SCALED: + case GGML_OP_RMS_NORM_SCALED: + case GGML_OP_MUL_MAT_ADD: + case GGML_OP_MUL_MAT_ADD_RELU: { n_tasks = n_threads; } break; @@ -2819,8 +2961,19 @@ struct ggml_cplan ggml_graph_plan( cur = ggml_type_size(node->type)*n_tasks; } break; case GGML_OP_MUL_MAT: - case GGML_OP_MUL_MAT_PACK4: + case GGML_OP_MUL_MAT_PACK4: { + if (node->src[0]->type == GGML_TYPE_I2_S) { + // The int8 activation rows, then one scale and one + // int8 row sum each. I2_S has no vec_dot_type entry + // -- see ggml_compute_forward_mul_mat_i2_s. + const int64_t nrows_y = ggml_nrows(node->src[1]); + + cur = GGML_PAD((size_t) ggml_nelements(node->src[1]), sizeof(float)); + cur += nrows_y*(sizeof(float) + sizeof(int32_t)); + break; + } + const enum ggml_type vec_dot_type = type_traits_cpu[node->src[0]->type].vec_dot_type; if (node->src[1]->type != vec_dot_type) { @@ -2948,6 +3101,18 @@ struct ggml_cplan ggml_graph_plan( { cur = ggml_type_size(node->type)*(n_tasks + node->src[0]->ne[0]*n_tasks); } break; + case GGML_OP_ADD_SCALED: + case GGML_OP_RMS_NORM_SCALED: + case GGML_OP_MUL_MAT_ADD: + case GGML_OP_MUL_MAT_ADD_RELU: + { + // F32 staging for the whole output, plus one absmax per + // thread. The output cannot be quantized in place: its + // scale is only known once every element has been + // computed, so all of them have to be held somewhere + // first. + cur = sizeof(float)*ggml_nelements(node) + sizeof(float)*n_tasks; + } break; case GGML_OP_GATED_DELTA_NET: { const int64_t S_v = node->src[2]->ne[0]; diff --git a/external/ggml/src/ggml-cpu/ops.cpp b/external/ggml/src/ggml-cpu/ops.cpp index 0f0f57399..fcdcd8d8e 100644 --- a/external/ggml/src/ggml-cpu/ops.cpp +++ b/external/ggml/src/ggml-cpu/ops.cpp @@ -7,6 +7,7 @@ #include "ggml.h" #include "unary-ops.h" #include "vec.h" +#include "ggml-quants.h" #include #include @@ -531,6 +532,16 @@ void ggml_compute_forward_dup( if (src0->type == dst->type) { ggml_compute_forward_dup_bytes(params, dst); + // I8_S keeps one scale for the whole tensor, stored past the last + // element: a byte copy moves the payload but leaves that float + // uninitialized, so a cont/cpy of an I8_S view has to carry it over. + if (dst->type == GGML_TYPE_I8_S && params->ith == 0) { + // Reading through to the parent keeps this working for the permuted + // views that ggml_cont() is usually handed, which have no scale of + // their own. + const ggml_tensor * scale_src = src0->view_src ? src0->view_src : src0; + *ggml_inband_scale(dst) = *ggml_inband_scale_const(scale_src); + } return; } @@ -1908,36 +1919,36 @@ static void ggml_compute_forward_concat_any( GGML_TENSOR_BINARY_OP_LOCALS - const int32_t dim = ggml_get_op_params_i32(dst, 0); - - GGML_ASSERT(dim >= 0 && dim < 4); - - // MINITTS_CONCAT_FASTPATH: when concatenating along dim 1 with fully contiguous tensors, - // copy each (ne0 x ne1) plane as two bulk memcpys instead of element-wise indexing. - if (dim == 1 && ggml_is_contiguous(src0) && ggml_is_contiguous(src1) && ggml_is_contiguous(dst)) { - GGML_ASSERT(ne00 == ne10); - GGML_ASSERT(ne02 == ne12 && ne03 == ne13); - const int64_t planes = ne02 * ne03; - const int64_t plane_begin = planes * ith / nth; - const int64_t plane_end = planes * (ith + 1) / nth; - const size_t src0_bytes = (size_t) ne00 * ne01 * len; - const size_t src1_bytes = (size_t) ne10 * ne11 * len; - const size_t dst_plane_bytes = (size_t) ne0 * ne1 * len; - const size_t src0_plane_bytes = (size_t) ne00 * ne01 * len; - const size_t src1_plane_bytes = (size_t) ne10 * ne11 * len; - - for (int64_t plane = plane_begin; plane < plane_end; ++plane) { - char * dst_plane = (char *) dst->data + (size_t) plane * dst_plane_bytes; - const char * src0_plane = (const char *) src0->data + (size_t) plane * src0_plane_bytes; - const char * src1_plane = (const char *) src1->data + (size_t) plane * src1_plane_bytes; - memcpy(dst_plane, src0_plane, src0_bytes); - memcpy(dst_plane + src0_bytes, src1_plane, src1_bytes); - } - return; - } - - int64_t o[4] = {0, 0, 0, 0}; - o[dim] = src0->ne[dim]; + const int32_t dim = ggml_get_op_params_i32(dst, 0); + + GGML_ASSERT(dim >= 0 && dim < 4); + + // MINITTS_CONCAT_FASTPATH: when concatenating along dim 1 with fully contiguous tensors, + // copy each (ne0 x ne1) plane as two bulk memcpys instead of element-wise indexing. + if (dim == 1 && ggml_is_contiguous(src0) && ggml_is_contiguous(src1) && ggml_is_contiguous(dst)) { + GGML_ASSERT(ne00 == ne10); + GGML_ASSERT(ne02 == ne12 && ne03 == ne13); + const int64_t planes = ne02 * ne03; + const int64_t plane_begin = planes * ith / nth; + const int64_t plane_end = planes * (ith + 1) / nth; + const size_t src0_bytes = (size_t) ne00 * ne01 * len; + const size_t src1_bytes = (size_t) ne10 * ne11 * len; + const size_t dst_plane_bytes = (size_t) ne0 * ne1 * len; + const size_t src0_plane_bytes = (size_t) ne00 * ne01 * len; + const size_t src1_plane_bytes = (size_t) ne10 * ne11 * len; + + for (int64_t plane = plane_begin; plane < plane_end; ++plane) { + char * dst_plane = (char *) dst->data + (size_t) plane * dst_plane_bytes; + const char * src0_plane = (const char *) src0->data + (size_t) plane * src0_plane_bytes; + const char * src1_plane = (const char *) src1->data + (size_t) plane * src1_plane_bytes; + memcpy(dst_plane, src0_plane, src0_bytes); + memcpy(dst_plane + src0_bytes, src1_plane, src1_bytes); + } + return; + } + + int64_t o[4] = {0, 0, 0, 0}; + o[dim] = src0->ne[dim]; const char * x; @@ -2054,88 +2065,88 @@ static void ggml_compute_forward_concat_f32( const ggml_tensor * src0 = dst->src[0]; const ggml_tensor * src1 = dst->src[1]; - const size_t len = ggml_type_size(src0->type); - GGML_ASSERT(len == sizeof(float)); + const size_t len = ggml_type_size(src0->type); + GGML_ASSERT(len == sizeof(float)); const int ith = params->ith; const int nth = params->nth; GGML_TENSOR_BINARY_OP_LOCALS - const int32_t dim = ggml_get_op_params_i32(dst, 0); - - GGML_ASSERT(dim >= 0 && dim < 4); - - // MINITTS_CONCAT_FASTPATH: when concatenating along dim 0 with fully contiguous tensors, - // copy each logical row as two bulk memcpys instead of scalar element dispatch. - if (dim == 0 && ggml_is_contiguous(src0) && ggml_is_contiguous(src1) && ggml_is_contiguous(dst)) { - GGML_ASSERT(ne01 == ne11 && ne02 == ne12 && ne03 == ne13); - const int64_t rows = ne1 * ne2 * ne3; - const int64_t row_begin = rows * ith / nth; - const int64_t row_end = rows * (ith + 1) / nth; - const size_t src0_bytes = (size_t) ne00 * len; - const size_t src1_bytes = (size_t) ne10 * len; - const size_t dst_row_bytes = (size_t) ne0 * len; - const size_t src0_row_bytes = (size_t) ne00 * len; - const size_t src1_row_bytes = (size_t) ne10 * len; - - for (int64_t row = row_begin; row < row_end; ++row) { - char * dst_row = (char *) dst->data + (size_t) row * dst_row_bytes; - const char * src0_row = (const char *) src0->data + (size_t) row * src0_row_bytes; - const char * src1_row = (const char *) src1->data + (size_t) row * src1_row_bytes; - memcpy(dst_row, src0_row, src0_bytes); - memcpy(dst_row + src0_bytes, src1_row, src1_bytes); - } - return; - } - - // MINITTS_CONCAT_FASTPATH: the f32 specialization gets the same plane-copy shortcut - // so common contiguous dim-1 concat patterns avoid the scalar fallback below. - if (dim == 1 && ggml_is_contiguous(src0) && ggml_is_contiguous(src1) && ggml_is_contiguous(dst)) { - GGML_ASSERT(ne00 == ne10); - GGML_ASSERT(ne02 == ne12 && ne03 == ne13); - const int64_t planes = ne02 * ne03; - const int64_t plane_begin = planes * ith / nth; - const int64_t plane_end = planes * (ith + 1) / nth; - const size_t src0_bytes = (size_t) ne00 * ne01 * len; - const size_t src1_bytes = (size_t) ne10 * ne11 * len; - const size_t dst_plane_bytes = (size_t) ne0 * ne1 * len; - const size_t src0_plane_bytes = (size_t) ne00 * ne01 * len; - const size_t src1_plane_bytes = (size_t) ne10 * ne11 * len; - - for (int64_t plane = plane_begin; plane < plane_end; ++plane) { - char * dst_plane = (char *) dst->data + (size_t) plane * dst_plane_bytes; - const char * src0_plane = (const char *) src0->data + (size_t) plane * src0_plane_bytes; - const char * src1_plane = (const char *) src1->data + (size_t) plane * src1_plane_bytes; - memcpy(dst_plane, src0_plane, src0_bytes); - memcpy(dst_plane + src0_bytes, src1_plane, src1_bytes); - } - return; - } - - int64_t o[4] = {0, 0, 0, 0}; - o[dim] = src0->ne[dim]; - - const float * x; - - // TODO: smarter multi-theading - for (int i3 = 0; i3 < ne3; i3++) { - for (int i2 = ith; i2 < ne2; i2 += nth) { - for (int i1 = 0; i1 < ne1; i1++) { - for (int i0 = 0; i0 < ne0; i0++) { - if (i0 < ne00 && i1 < ne01 && i2 < ne02 && i3 < ne03) { - x = (const float *) ((const char *)src0->data + (i0 )*nb00 + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03); - } else { - x = (const float *) ((const char *)src1->data + (i0 - o[0])*nb10 + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13); - } - - float * y = (float *)((char *)dst->data + i0*nb0 + i1*nb1 + i2*nb2 + i3*nb3); - - *y = *x; - } - } - } - } + const int32_t dim = ggml_get_op_params_i32(dst, 0); + + GGML_ASSERT(dim >= 0 && dim < 4); + + // MINITTS_CONCAT_FASTPATH: when concatenating along dim 0 with fully contiguous tensors, + // copy each logical row as two bulk memcpys instead of scalar element dispatch. + if (dim == 0 && ggml_is_contiguous(src0) && ggml_is_contiguous(src1) && ggml_is_contiguous(dst)) { + GGML_ASSERT(ne01 == ne11 && ne02 == ne12 && ne03 == ne13); + const int64_t rows = ne1 * ne2 * ne3; + const int64_t row_begin = rows * ith / nth; + const int64_t row_end = rows * (ith + 1) / nth; + const size_t src0_bytes = (size_t) ne00 * len; + const size_t src1_bytes = (size_t) ne10 * len; + const size_t dst_row_bytes = (size_t) ne0 * len; + const size_t src0_row_bytes = (size_t) ne00 * len; + const size_t src1_row_bytes = (size_t) ne10 * len; + + for (int64_t row = row_begin; row < row_end; ++row) { + char * dst_row = (char *) dst->data + (size_t) row * dst_row_bytes; + const char * src0_row = (const char *) src0->data + (size_t) row * src0_row_bytes; + const char * src1_row = (const char *) src1->data + (size_t) row * src1_row_bytes; + memcpy(dst_row, src0_row, src0_bytes); + memcpy(dst_row + src0_bytes, src1_row, src1_bytes); + } + return; + } + + // MINITTS_CONCAT_FASTPATH: the f32 specialization gets the same plane-copy shortcut + // so common contiguous dim-1 concat patterns avoid the scalar fallback below. + if (dim == 1 && ggml_is_contiguous(src0) && ggml_is_contiguous(src1) && ggml_is_contiguous(dst)) { + GGML_ASSERT(ne00 == ne10); + GGML_ASSERT(ne02 == ne12 && ne03 == ne13); + const int64_t planes = ne02 * ne03; + const int64_t plane_begin = planes * ith / nth; + const int64_t plane_end = planes * (ith + 1) / nth; + const size_t src0_bytes = (size_t) ne00 * ne01 * len; + const size_t src1_bytes = (size_t) ne10 * ne11 * len; + const size_t dst_plane_bytes = (size_t) ne0 * ne1 * len; + const size_t src0_plane_bytes = (size_t) ne00 * ne01 * len; + const size_t src1_plane_bytes = (size_t) ne10 * ne11 * len; + + for (int64_t plane = plane_begin; plane < plane_end; ++plane) { + char * dst_plane = (char *) dst->data + (size_t) plane * dst_plane_bytes; + const char * src0_plane = (const char *) src0->data + (size_t) plane * src0_plane_bytes; + const char * src1_plane = (const char *) src1->data + (size_t) plane * src1_plane_bytes; + memcpy(dst_plane, src0_plane, src0_bytes); + memcpy(dst_plane + src0_bytes, src1_plane, src1_bytes); + } + return; + } + + int64_t o[4] = {0, 0, 0, 0}; + o[dim] = src0->ne[dim]; + + const float * x; + + // TODO: smarter multi-theading + for (int i3 = 0; i3 < ne3; i3++) { + for (int i2 = ith; i2 < ne2; i2 += nth) { + for (int i1 = 0; i1 < ne1; i1++) { + for (int i0 = 0; i0 < ne0; i0++) { + if (i0 < ne00 && i1 < ne01 && i2 < ne02 && i3 < ne03) { + x = (const float *) ((const char *)src0->data + (i0 )*nb00 + (i1 )*nb01 + (i2 )*nb02 + (i3 )*nb03); + } else { + x = (const float *) ((const char *)src1->data + (i0 - o[0])*nb10 + (i1 - o[1])*nb11 + (i2 - o[2])*nb12 + (i3 - o[3])*nb13); + } + + float * y = (float *)((char *)dst->data + i0*nb0 + i1*nb1 + i2*nb2 + i3*nb3); + + *y = *x; + } + } + } + } } void ggml_compute_forward_concat( @@ -4501,52 +4512,52 @@ static void ggml_compute_forward_scale_f32( const ggml_compute_params * params, ggml_tensor * dst) { - const ggml_tensor * src0 = dst->src[0]; - - GGML_ASSERT(ggml_is_contiguous(src0)); - GGML_ASSERT(ggml_is_contiguous(dst)); - GGML_ASSERT(ggml_can_repeat(src0, dst)); - - GGML_TENSOR_UNARY_OP_LOCALS - - float s; // scale factor - float b; // bias - + const ggml_tensor * src0 = dst->src[0]; + + GGML_ASSERT(ggml_is_contiguous(src0)); + GGML_ASSERT(ggml_is_contiguous(dst)); + GGML_ASSERT(ggml_can_repeat(src0, dst)); + + GGML_TENSOR_UNARY_OP_LOCALS + + float s; // scale factor + float b; // bias + memcpy(&s, (float *) dst->op_params + 0, sizeof(float)); memcpy(&b, (float *) dst->op_params + 1, sizeof(float)); const int ith = params->ith; const int nth = params->nth; - const int64_t nc = ne0; - const int64_t nr = ggml_nrows(dst); - - // rows per thread - const int64_t dr = (nr + nth - 1)/nth; - - // row range for this thread - const int64_t ir0 = dr*ith; - const int64_t ir1 = MIN(ir0 + dr, nr); - - const bool is_src0_full_shape = ggml_are_same_shape(src0, dst); - - if (!is_src0_full_shape) { - for (int64_t ir = ir0; ir < ir1; ++ir) { - const int64_t i3 = ir/(ne2*ne1); - const int64_t i2 = (ir - i3*ne2*ne1)/ne1; - const int64_t i1 = (ir - i3*ne2*ne1 - i2*ne1); - float * dst_row = (float *) ((char *) dst->data + i3*nb3 + i2*nb2 + i1*nb1); - const char * src0_row = (const char *) src0->data + - (i3 % ne03)*nb03 + (i2 % ne02)*nb02 + (i1 % ne01)*nb01; - for (int64_t i0 = 0; i0 < nc; ++i0) { - const float value = *(const float *) (src0_row + (i0 % ne00)*nb00); - dst_row[i0] = value * s + b; - } - } - return; - } - - if (b == 0.0f) { + const int64_t nc = ne0; + const int64_t nr = ggml_nrows(dst); + + // rows per thread + const int64_t dr = (nr + nth - 1)/nth; + + // row range for this thread + const int64_t ir0 = dr*ith; + const int64_t ir1 = MIN(ir0 + dr, nr); + + const bool is_src0_full_shape = ggml_are_same_shape(src0, dst); + + if (!is_src0_full_shape) { + for (int64_t ir = ir0; ir < ir1; ++ir) { + const int64_t i3 = ir/(ne2*ne1); + const int64_t i2 = (ir - i3*ne2*ne1)/ne1; + const int64_t i1 = (ir - i3*ne2*ne1 - i2*ne1); + float * dst_row = (float *) ((char *) dst->data + i3*nb3 + i2*nb2 + i1*nb1); + const char * src0_row = (const char *) src0->data + + (i3 % ne03)*nb03 + (i2 % ne02)*nb02 + (i1 % ne01)*nb01; + for (int64_t i0 = 0; i0 < nc; ++i0) { + const float value = *(const float *) (src0_row + (i0 % ne00)*nb00); + dst_row[i0] = value * s + b; + } + } + return; + } + + if (b == 0.0f) { for (int i1 = ir0; i1 < ir1; i1++) { if (dst->data != src0->data) { // src0 is same shape as dst => same indices @@ -4557,10 +4568,10 @@ static void ggml_compute_forward_scale_f32( } } else { for (int i1 = ir0; i1 < ir1; i1++) { - ggml_vec_mad1_f32(nc, - (float *) ((char *) dst->data + i1*nb1), - (float *) ((char *) src0->data + i1*nb01), - s, b); + ggml_vec_mad1_f32(nc, + (float *) ((char *) dst->data + i1*nb1), + (float *) ((char *) src0->data + i1*nb01), + s, b); } } } @@ -6420,9 +6431,9 @@ static void ggml_compute_forward_im2col_f16( } } -void ggml_compute_forward_im2col( - const ggml_compute_params * params, - ggml_tensor * dst) { +void ggml_compute_forward_im2col( + const ggml_compute_params * params, + ggml_tensor * dst) { switch (dst->type) { case GGML_TYPE_F16: { @@ -6436,188 +6447,188 @@ void ggml_compute_forward_im2col( { GGML_ABORT("fatal error"); } - } -} - -static inline int64_t ggml_div_ceil_nonneg(int64_t num, int64_t den) { - GGML_ASSERT(num >= 0); - GGML_ASSERT(den > 0); - return (num + den - 1) / den; -} - -static inline int64_t ggml_im2col_1d_valid_start(int64_t base, int64_t step) { - if (base >= 0) { - return 0; - } - return ggml_div_ceil_nonneg(-base, step); -} - -static inline int64_t ggml_im2col_1d_valid_end(int64_t base, int64_t step, int64_t width, int64_t kw) { - if (width <= 0 || base > width - 1) { - return 0; - } - const int64_t max_ikw = (width - 1 - base) / step; - return std::min(kw, max_ikw + 1); -} - -static void ggml_compute_forward_im2col_f32_1d( - const ggml_compute_params * params, - ggml_tensor * dst) { - - const ggml_tensor * src0 = dst->src[0]; - const ggml_tensor * src1 = dst->src[1]; - - GGML_ASSERT(src1->type == GGML_TYPE_F32); - GGML_ASSERT(dst->type == GGML_TYPE_F32); - - GGML_TENSOR_BINARY_OP_LOCALS; - - const int32_t s0 = ((const int32_t *)(dst->op_params))[0]; - const int32_t p0 = ((const int32_t *)(dst->op_params))[2]; - const int32_t d0 = ((const int32_t *)(dst->op_params))[4]; - - const int ith = params->ith; - const int nth = params->nth; - - const int64_t N = ne12; - const int64_t IC = ne11; - const int64_t IW = ne10; - const int64_t KW = ne00; - const int64_t OW = ne1; - - const int ofs0 = nb12; - const int ofs1 = nb11; - - GGML_ASSERT(nb10 == sizeof(float)); - - float * const wdata = (float *) dst->data; - - for (int64_t in = 0; in < N; ++in) { - for (int64_t iow = 0; iow < OW; ++iow) { - const int64_t base = iow*s0 - p0; - const int64_t ikw0 = ggml_im2col_1d_valid_start(base, d0); - const int64_t ikw1 = ggml_im2col_1d_valid_end(base, d0, IW, KW); - - for (int64_t iic = ith; iic < IC; iic += nth) { - float * const dst_row = wdata + (in*OW + iow)*(IC*KW) + iic*KW; - const float * const src_row = (const float *)((const char *) src1->data + in*ofs0 + iic*ofs1); - - if (ikw0 > 0) { - memset(dst_row, 0, ikw0*sizeof(float)); - } - if (ikw1 > ikw0) { - if (d0 == 1) { - memcpy(dst_row + ikw0, src_row + base + ikw0, (ikw1 - ikw0)*sizeof(float)); - } else { - int64_t iiw = base + ikw0*d0; - for (int64_t ikw = ikw0; ikw < ikw1; ++ikw, iiw += d0) { - dst_row[ikw] = src_row[iiw]; - } - } - } - if (ikw1 < KW) { - memset(dst_row + ikw1, 0, (KW - ikw1)*sizeof(float)); - } - } - } - } -} - -static void ggml_compute_forward_im2col_f16_1d( - const ggml_compute_params * params, - ggml_tensor * dst) { - - const ggml_tensor * src0 = dst->src[0]; - const ggml_tensor * src1 = dst->src[1]; - - GGML_ASSERT(src0->type == GGML_TYPE_F16); - GGML_ASSERT(src1->type == GGML_TYPE_F16 || src1->type == GGML_TYPE_F32); - GGML_ASSERT(dst->type == GGML_TYPE_F16); - - GGML_TENSOR_BINARY_OP_LOCALS; - - const int32_t s0 = ((const int32_t *)(dst->op_params))[0]; - const int32_t p0 = ((const int32_t *)(dst->op_params))[2]; - const int32_t d0 = ((const int32_t *)(dst->op_params))[4]; - - const int ith = params->ith; - const int nth = params->nth; - - const int64_t N = ne12; - const int64_t IC = ne11; - const int64_t IW = ne10; - const int64_t KW = ne00; - const int64_t OW = ne1; - - const int ofs0 = nb12; - const int ofs1 = nb11; - - GGML_ASSERT(nb00 == sizeof(ggml_fp16_t)); - GGML_ASSERT(nb10 == ggml_type_size(src1->type)); - - ggml_fp16_t * const wdata = (ggml_fp16_t *) dst->data; - - for (int64_t in = 0; in < N; ++in) { - for (int64_t iow = 0; iow < OW; ++iow) { - const int64_t base = iow*s0 - p0; - const int64_t ikw0 = ggml_im2col_1d_valid_start(base, d0); - const int64_t ikw1 = ggml_im2col_1d_valid_end(base, d0, IW, KW); - - for (int64_t iic = ith; iic < IC; iic += nth) { - ggml_fp16_t * const dst_row = wdata + (in*OW + iow)*(IC*KW) + iic*KW; - const float * const src_row_f32 = src1->type == GGML_TYPE_F32 - ? (const float *)((const char *) src1->data + in*ofs0 + iic*ofs1) - : nullptr; - const ggml_fp16_t * const src_row_f16 = src1->type == GGML_TYPE_F16 - ? (const ggml_fp16_t *)((const char *) src1->data + in*ofs0 + iic*ofs1) - : nullptr; - - if (ikw0 > 0) { - memset(dst_row, 0, ikw0*sizeof(ggml_fp16_t)); - } - if (ikw1 > ikw0) { - if (src_row_f16 != nullptr && d0 == 1) { - memcpy(dst_row + ikw0, src_row_f16 + base + ikw0, (ikw1 - ikw0)*sizeof(ggml_fp16_t)); - } else if (src_row_f16 != nullptr) { - int64_t iiw = base + ikw0*d0; - for (int64_t ikw = ikw0; ikw < ikw1; ++ikw, iiw += d0) { - dst_row[ikw] = src_row_f16[iiw]; - } - } else { - int64_t iiw = base + ikw0*d0; - for (int64_t ikw = ikw0; ikw < ikw1; ++ikw, iiw += d0) { - dst_row[ikw] = GGML_CPU_FP32_TO_FP16(src_row_f32[iiw]); - } - } - } - if (ikw1 < KW) { - memset(dst_row + ikw1, 0, (KW - ikw1)*sizeof(ggml_fp16_t)); - } - } - } - } -} - -void ggml_compute_forward_im2col_fast_1d( - const ggml_compute_params * params, - ggml_tensor * dst) { - switch (dst->type) { - case GGML_TYPE_F16: - { - ggml_compute_forward_im2col_f16_1d(params, dst); - } break; - case GGML_TYPE_F32: - { - ggml_compute_forward_im2col_f32_1d(params, dst); - } break; - default: - { - GGML_ABORT("fatal error"); - } - } -} - -// ggml_compute_forward_im2col_back_f32 + } +} + +static inline int64_t ggml_div_ceil_nonneg(int64_t num, int64_t den) { + GGML_ASSERT(num >= 0); + GGML_ASSERT(den > 0); + return (num + den - 1) / den; +} + +static inline int64_t ggml_im2col_1d_valid_start(int64_t base, int64_t step) { + if (base >= 0) { + return 0; + } + return ggml_div_ceil_nonneg(-base, step); +} + +static inline int64_t ggml_im2col_1d_valid_end(int64_t base, int64_t step, int64_t width, int64_t kw) { + if (width <= 0 || base > width - 1) { + return 0; + } + const int64_t max_ikw = (width - 1 - base) / step; + return std::min(kw, max_ikw + 1); +} + +static void ggml_compute_forward_im2col_f32_1d( + const ggml_compute_params * params, + ggml_tensor * dst) { + + const ggml_tensor * src0 = dst->src[0]; + const ggml_tensor * src1 = dst->src[1]; + + GGML_ASSERT(src1->type == GGML_TYPE_F32); + GGML_ASSERT(dst->type == GGML_TYPE_F32); + + GGML_TENSOR_BINARY_OP_LOCALS; + + const int32_t s0 = ((const int32_t *)(dst->op_params))[0]; + const int32_t p0 = ((const int32_t *)(dst->op_params))[2]; + const int32_t d0 = ((const int32_t *)(dst->op_params))[4]; + + const int ith = params->ith; + const int nth = params->nth; + + const int64_t N = ne12; + const int64_t IC = ne11; + const int64_t IW = ne10; + const int64_t KW = ne00; + const int64_t OW = ne1; + + const int ofs0 = nb12; + const int ofs1 = nb11; + + GGML_ASSERT(nb10 == sizeof(float)); + + float * const wdata = (float *) dst->data; + + for (int64_t in = 0; in < N; ++in) { + for (int64_t iow = 0; iow < OW; ++iow) { + const int64_t base = iow*s0 - p0; + const int64_t ikw0 = ggml_im2col_1d_valid_start(base, d0); + const int64_t ikw1 = ggml_im2col_1d_valid_end(base, d0, IW, KW); + + for (int64_t iic = ith; iic < IC; iic += nth) { + float * const dst_row = wdata + (in*OW + iow)*(IC*KW) + iic*KW; + const float * const src_row = (const float *)((const char *) src1->data + in*ofs0 + iic*ofs1); + + if (ikw0 > 0) { + memset(dst_row, 0, ikw0*sizeof(float)); + } + if (ikw1 > ikw0) { + if (d0 == 1) { + memcpy(dst_row + ikw0, src_row + base + ikw0, (ikw1 - ikw0)*sizeof(float)); + } else { + int64_t iiw = base + ikw0*d0; + for (int64_t ikw = ikw0; ikw < ikw1; ++ikw, iiw += d0) { + dst_row[ikw] = src_row[iiw]; + } + } + } + if (ikw1 < KW) { + memset(dst_row + ikw1, 0, (KW - ikw1)*sizeof(float)); + } + } + } + } +} + +static void ggml_compute_forward_im2col_f16_1d( + const ggml_compute_params * params, + ggml_tensor * dst) { + + const ggml_tensor * src0 = dst->src[0]; + const ggml_tensor * src1 = dst->src[1]; + + GGML_ASSERT(src0->type == GGML_TYPE_F16); + GGML_ASSERT(src1->type == GGML_TYPE_F16 || src1->type == GGML_TYPE_F32); + GGML_ASSERT(dst->type == GGML_TYPE_F16); + + GGML_TENSOR_BINARY_OP_LOCALS; + + const int32_t s0 = ((const int32_t *)(dst->op_params))[0]; + const int32_t p0 = ((const int32_t *)(dst->op_params))[2]; + const int32_t d0 = ((const int32_t *)(dst->op_params))[4]; + + const int ith = params->ith; + const int nth = params->nth; + + const int64_t N = ne12; + const int64_t IC = ne11; + const int64_t IW = ne10; + const int64_t KW = ne00; + const int64_t OW = ne1; + + const int ofs0 = nb12; + const int ofs1 = nb11; + + GGML_ASSERT(nb00 == sizeof(ggml_fp16_t)); + GGML_ASSERT(nb10 == ggml_type_size(src1->type)); + + ggml_fp16_t * const wdata = (ggml_fp16_t *) dst->data; + + for (int64_t in = 0; in < N; ++in) { + for (int64_t iow = 0; iow < OW; ++iow) { + const int64_t base = iow*s0 - p0; + const int64_t ikw0 = ggml_im2col_1d_valid_start(base, d0); + const int64_t ikw1 = ggml_im2col_1d_valid_end(base, d0, IW, KW); + + for (int64_t iic = ith; iic < IC; iic += nth) { + ggml_fp16_t * const dst_row = wdata + (in*OW + iow)*(IC*KW) + iic*KW; + const float * const src_row_f32 = src1->type == GGML_TYPE_F32 + ? (const float *)((const char *) src1->data + in*ofs0 + iic*ofs1) + : nullptr; + const ggml_fp16_t * const src_row_f16 = src1->type == GGML_TYPE_F16 + ? (const ggml_fp16_t *)((const char *) src1->data + in*ofs0 + iic*ofs1) + : nullptr; + + if (ikw0 > 0) { + memset(dst_row, 0, ikw0*sizeof(ggml_fp16_t)); + } + if (ikw1 > ikw0) { + if (src_row_f16 != nullptr && d0 == 1) { + memcpy(dst_row + ikw0, src_row_f16 + base + ikw0, (ikw1 - ikw0)*sizeof(ggml_fp16_t)); + } else if (src_row_f16 != nullptr) { + int64_t iiw = base + ikw0*d0; + for (int64_t ikw = ikw0; ikw < ikw1; ++ikw, iiw += d0) { + dst_row[ikw] = src_row_f16[iiw]; + } + } else { + int64_t iiw = base + ikw0*d0; + for (int64_t ikw = ikw0; ikw < ikw1; ++ikw, iiw += d0) { + dst_row[ikw] = GGML_CPU_FP32_TO_FP16(src_row_f32[iiw]); + } + } + } + if (ikw1 < KW) { + memset(dst_row + ikw1, 0, (KW - ikw1)*sizeof(ggml_fp16_t)); + } + } + } + } +} + +void ggml_compute_forward_im2col_fast_1d( + const ggml_compute_params * params, + ggml_tensor * dst) { + switch (dst->type) { + case GGML_TYPE_F16: + { + ggml_compute_forward_im2col_f16_1d(params, dst); + } break; + case GGML_TYPE_F32: + { + ggml_compute_forward_im2col_f32_1d(params, dst); + } break; + default: + { + GGML_ABORT("fatal error"); + } + } +} + +// ggml_compute_forward_im2col_back_f32 void ggml_compute_forward_im2col_back_f32( const ggml_compute_params * params, @@ -10068,6 +10079,10 @@ void ggml_compute_forward_unary( { ggml_compute_forward_trunc(params, dst); } break; + case GGML_UNARY_OP_ROUND_BF16: + { + ggml_compute_forward_round_bf16(params, dst); + } break; case GGML_UNARY_OP_XIELU: { ggml_compute_forward_xielu(params, dst); @@ -11637,3 +11652,663 @@ void ggml_compute_forward_fwht(const ggml_compute_params * params, ggml_tensor * } } } + +// VibeASR CPU INT8 pipeline +// +// Scalar reference implementations of the five fused I8_S ops. They are written +// to be obviously correct rather than fast: the vectorized kernels live with the +// other SIMD code under arch/, and dispatch to them replaces the inner loops +// here without changing the surrounding structure. +// +// Four of the five produce I8_S, and all four share one shape: +// +// 1. compute the result in F32 into params->wdata, tracking this thread's absmax +// 2. barrier, reduce the per-thread absmaxes to a tensor-wide absmax +// 3. quantize this thread's slice into dst; thread 0 writes the output scale +// +// Step 2 is why these are fused ops at all. The output scale cannot be known +// before the whole output has been computed, so an unfused chain would have to +// materialize each intermediate in F32, rescan it, and write it again. +// +// wdata layout, shared by all four: +// +// [0, n_stage) F32 staging for the result +// [n_stage, n_stage + nth) per-thread absmax +// [n_stage + nth, ...) op-specific scratch (int32 accumulators) + +// Steps 2 and 3. The thread's slice of the output is the rectangle +// {row*row_stride + col : row < n_rows, col0 <= col < col1}; contiguous callers +// pass n_rows = 1, row_stride = 0. Both `stage` and dst use the same indexing. +// +// `relu` clamps after rounding rather than before the absmax scan, so the +// negatives that are about to be zeroed still widen the scale: an output +// spanning [-10, +5] scales by 10, leaving the surviving positives to reach 63 +// of the available 127. That is what the reference implementation does, and +// matching it is what makes a layer-by-layer comparison meaningful, so it is +// reproduced here deliberately rather than improved in passing. +// Scale a contiguous run of staged F32 into I8_S. +// +// relu is folded into the low clamp: clamping to 0 before rounding and rounding +// before clamping to 0 agree on every negative input, and the float clamp is +// free here. The absmax that produced id was taken before the clamp, so a +// channel about to be zeroed still counts towards the scale. +// +// roundf() would be a PLT call per element. rintf() inlines to one instruction +// and rounds ties to even, which is what _mm256_cvtps_epi32 does as well, so the +// vector body and the scalar tail agree - and it matches ggml's own +// nearest_int(). VibeASR rounds ties away from zero in its scalar tail but to +// even in its vector body, so no tie convention reproduces it exactly. +static inline void ggml_i8_s_quantize_range( + int8_t * GGML_RESTRICT dst, + const float * GGML_RESTRICT src, + int64_t n, + float id, + bool relu) { + + int64_t i = 0; + + const float lo = relu ? 0.0f : -127.0f; + +#if defined(__AVX2__) + const __m256 v_id = _mm256_set1_ps(id); + const __m256 v_lo = _mm256_set1_ps(lo); + const __m256 v_hi = _mm256_set1_ps(127.0f); + + for (; i + 8 <= n; i += 8) { + __m256 vf = _mm256_mul_ps(_mm256_loadu_ps(src + i), v_id); + vf = _mm256_min_ps(_mm256_max_ps(vf, v_lo), v_hi); + + const __m256i vi32 = _mm256_cvtps_epi32(vf); + + __m256i vi16 = _mm256_permute4x64_epi64(_mm256_packs_epi32(vi32, vi32), 0xD8); + __m256i vi8 = _mm256_permute4x64_epi64(_mm256_packs_epi16(vi16, vi16), 0xD8); + + _mm_storel_epi64((__m128i *)(dst + i), _mm256_castsi256_si128(vi8)); + } +#elif defined(__ARM_NEON) && defined(__aarch64__) + const float32x4_t v_id = vdupq_n_f32(id); + const float32x4_t v_lo = vdupq_n_f32(lo); + const float32x4_t v_hi = vdupq_n_f32(127.0f); + + for (; i + 8 <= n; i += 8) { + float32x4_t f0 = vmulq_f32(vld1q_f32(src + i ), v_id); + float32x4_t f1 = vmulq_f32(vld1q_f32(src + i + 4), v_id); + + f0 = vminq_f32(vmaxq_f32(f0, v_lo), v_hi); + f1 = vminq_f32(vmaxq_f32(f1, v_lo), v_hi); + + const int16x8_t vi16 = vcombine_s16(vqmovn_s32(vcvtnq_s32_f32(f0)), + vqmovn_s32(vcvtnq_s32_f32(f1))); + + vst1_s8(dst + i, vqmovn_s16(vi16)); + } +#endif + + for (; i < n; ++i) { + float v = src[i] * id; + v = v < lo ? lo : (v > 127.0f ? 127.0f : v); + dst[i] = (int8_t) rintf(v); + } +} + +static void ggml_i8_s_requantize( + const ggml_compute_params * params, + ggml_tensor * dst, + const float * stage, + float * thread_max, + int64_t row_stride, + int64_t n_rows, + int64_t col0, + int64_t col1, + float local_absmax, + bool relu) { + + const int ith = params->ith; + const int nth = params->nth; + + thread_max[ith] = local_absmax; + + ggml_barrier(params->threadpool); + + if (ith == 0) { + float global_max = 0.0f; + for (int t = 0; t < nth; t++) { + if (thread_max[t] > global_max) global_max = thread_max[t]; + } + thread_max[0] = global_max; + } + + ggml_barrier(params->threadpool); + + const float global_max = thread_max[0]; + const float id = global_max != 0.0f ? 127.0f / global_max : 0.0f; + + int8_t * dst_data = (int8_t *) dst->data; + + // Each row's [col0, col1) slice is contiguous, so the vectorized helper runs + // once per row regardless of how the rectangle is strided. + for (int64_t row = 0; row < n_rows; row++) { + const int64_t i = row*row_stride + col0; + + ggml_i8_s_quantize_range(dst_data + i, stage + i, col1 - col0, id, relu); + } + + if (ith == 0) { + // Multiplier, so that dequantizing is q * scale like every other ggml + // quantized type -- see ggml_i8_s_to_float. + *ggml_inband_scale(dst) = global_max != 0.0f ? global_max / 127.0f : 0.0f; + } +} + +// ggml_compute_forward_add_scaled + +void ggml_compute_forward_add_scaled( + const ggml_compute_params * params, + ggml_tensor * dst) { + + const ggml_tensor * src0 = dst->src[0]; // a (I8_S) + const ggml_tensor * src1 = dst->src[1]; // b (I8_S) + const ggml_tensor * src2 = dst->src[2]; // scale (F32, per channel) + + GGML_ASSERT(src0->type == GGML_TYPE_I8_S); + GGML_ASSERT(src1->type == GGML_TYPE_I8_S); + GGML_ASSERT(dst->type == GGML_TYPE_I8_S); + GGML_ASSERT(ggml_are_same_shape(src0, src1)); + GGML_ASSERT(ggml_are_same_shape(src0, dst)); + GGML_ASSERT(ggml_is_contiguous(src0)); + GGML_ASSERT(ggml_is_contiguous(src1)); + + const int ith = params->ith; + const int nth = params->nth; + + const int64_t n = ggml_nelements(src0); + const int64_t ne0 = src0->ne[0]; + + const int8_t * a = (const int8_t *) src0->data; + const int8_t * b = (const int8_t *) src1->data; + + const float a_d = *ggml_inband_scale_const(src0); + const float b_d = *ggml_inband_scale_const(src1); + + // Channel index is i % ne0: the graph is channel-major, so ne[0] is the + // channel axis and one coefficient covers every position of that channel. + const float * gamma = (const float *) src2->data; + + float * stage = (float *) params->wdata; + float * thread_max = stage + n; + + const int64_t dr = (n + nth - 1)/nth; + const int64_t i0 = dr*ith; + const int64_t i1 = MIN(i0 + dr, n); + + float local_absmax = 0.0f; + + for (int64_t i = i0; i < i1; i++) { + const float v = (float) a[i] * a_d * gamma[i % ne0] + (float) b[i] * b_d; + + stage[i] = v; + + const float av = fabsf(v); + if (av > local_absmax) local_absmax = av; + } + + ggml_i8_s_requantize(params, dst, stage, thread_max, 0, 1, i0, i1, local_absmax, false); +} + +// ggml_compute_forward_rms_norm_scaled + +void ggml_compute_forward_rms_norm_scaled( + const ggml_compute_params * params, + ggml_tensor * dst) { + + const ggml_tensor * src0 = dst->src[0]; // a (I8_S) + const ggml_tensor * src1 = dst->src[1]; // scale (F32, per channel) + + GGML_ASSERT(src0->type == GGML_TYPE_I8_S); + GGML_ASSERT(dst->type == GGML_TYPE_I8_S); + GGML_ASSERT(ggml_are_same_shape(src0, dst)); + GGML_ASSERT(ggml_is_contiguous(src0)); + + const int ith = params->ith; + const int nth = params->nth; + + const int64_t ne00 = src0->ne[0]; + const int64_t n = ggml_nelements(src0); + const int64_t nr = n / ne00; + + float eps; + memcpy(&eps, dst->op_params, sizeof(float)); + + const int8_t * x = (const int8_t *) src0->data; + const float * gamma = (const float *) src1->data; + + const float x_d = *ggml_inband_scale_const(src0); + + // The normalization is scale-invariant, so the input scale cancels between + // numerator and denominator and never has to be applied to the int8 values: + // + // real[i] = q[i] * x_d + // out[i] = real[i] / sqrt(mean(real^2) + eps) * gamma[i] + // = q[i] / sqrt(mean(q^2) + eps/x_d^2) * gamma[i] + // + // which keeps the sum of squares in exact int64 arithmetic. Only eps has to + // move into the int8 domain. + const float eps_q = (float) ((double) eps / ((double) x_d * (double) x_d)); + + float * stage = (float *) params->wdata; + float * thread_max = stage + n; + + const int64_t dr = (nr + nth - 1)/nth; + const int64_t ir0 = dr*ith; + const int64_t ir1 = MIN(ir0 + dr, nr); + + float local_absmax = 0.0f; + + for (int64_t ir = ir0; ir < ir1; ir++) { + const int8_t * x_row = x + ir*ne00; + float * stage_row = stage + ir*ne00; + + int64_t sum_sq = 0; + for (int64_t i = 0; i < ne00; i++) { + sum_sq += (int32_t) x_row[i] * (int32_t) x_row[i]; + } + + const float rms_inv = 1.0f/sqrtf((float) sum_sq/(float) ne00 + eps_q); + + for (int64_t i = 0; i < ne00; i++) { + const float v = (float) x_row[i] * rms_inv * gamma[i]; + + stage_row[i] = v; + + const float av = fabsf(v); + if (av > local_absmax) local_absmax = av; + } + } + + ggml_i8_s_requantize(params, dst, stage, thread_max, 0, 1, ir0*ne00, ir1*ne00, local_absmax, false); +} + +// ggml_compute_forward_mul_mat_add + +#define GGML_MUL_MAT_ADD_OC_CHUNK 64 + +// stage[j] = acc[j]*d + bias[j], returning max(|stage[j]|) folded into amax. +// +// This is the second pass over every output, so leaving it scalar costs about as +// much as the dot products do at the small contraction lengths the conv layers +// use. Vectorizing it is exact: max over floats is order-independent, and the +// multiply and add are kept separate so the tail cannot disagree with the body. +static inline float ggml_i8_s_scale_bias_absmax( + float * GGML_RESTRICT stage, + const int32_t * GGML_RESTRICT acc, + const float * GGML_RESTRICT bias, + int64_t n, + float d, + float amax) { + + int64_t i = 0; + +#if defined(__AVX2__) + const __m256 v_d = _mm256_set1_ps(d); + const __m256 v_abs = _mm256_castsi256_ps(_mm256_set1_epi32(0x7fffffff)); + + __m256 v_amax = _mm256_setzero_ps(); + + for (; i + 8 <= n; i += 8) { + const __m256 v = _mm256_add_ps( + _mm256_mul_ps(_mm256_cvtepi32_ps(_mm256_loadu_si256((const __m256i *)(acc + i))), v_d), + _mm256_loadu_ps(bias + i)); + + _mm256_storeu_ps(stage + i, v); + + v_amax = _mm256_max_ps(v_amax, _mm256_and_ps(v, v_abs)); + } + + if (i > 0) { + __m128 r = _mm_max_ps(_mm256_castps256_ps128(v_amax), _mm256_extractf128_ps(v_amax, 1)); + r = _mm_max_ps(r, _mm_movehl_ps(r, r)); + r = _mm_max_ss(r, _mm_shuffle_ps(r, r, 1)); + + const float v = _mm_cvtss_f32(r); + if (v > amax) amax = v; + } +#elif defined(__ARM_NEON) && defined(__aarch64__) + const float32x4_t v_d = vdupq_n_f32(d); + + float32x4_t v_amax = vdupq_n_f32(0.0f); + + for (; i + 4 <= n; i += 4) { + const float32x4_t v = vaddq_f32( + vmulq_f32(vcvtq_f32_s32(vld1q_s32(acc + i)), v_d), + vld1q_f32(bias + i)); + + vst1q_f32(stage + i, v); + + v_amax = vmaxq_f32(v_amax, vabsq_f32(v)); + } + + if (i > 0) { + const float v = vmaxvq_f32(v_amax); + if (v > amax) amax = v; + } +#endif + + for (; i < n; ++i) { + const float v = (float) acc[i]*d + bias[i]; + + stage[i] = v; + + const float av = fabsf(v); + if (av > amax) amax = av; + } + + return amax; +} + +// Shared by GGML_OP_MUL_MAT_ADD and GGML_OP_MUL_MAT_ADD_RELU. +// +// Two shapes are handled. When src0 is [K, 1, C] the op is depthwise: C +// independent length-K dot products per position, with src1 laid out +// [K, N, C] and the output [C, N] channel-major. Otherwise src0 is an ordinary +// [IC, OC] weight matrix, src1 is [IC, N], and the output is [OC, N]. +static void ggml_compute_forward_mul_mat_add_impl( + const ggml_compute_params * params, + ggml_tensor * dst, + bool relu) { + + const ggml_tensor * src0 = dst->src[0]; // weight (I8_S) + const ggml_tensor * src1 = dst->src[1]; // input (I8_S) + const ggml_tensor * src2 = dst->src[2]; // bias (F32) + + GGML_ASSERT(src0->type == GGML_TYPE_I8_S); + GGML_ASSERT(src1->type == GGML_TYPE_I8_S); + GGML_ASSERT(dst->type == GGML_TYPE_I8_S); + GGML_ASSERT(ggml_is_contiguous(src0)); + GGML_ASSERT(ggml_is_contiguous(src1)); + + const int ith = params->ith; + const int nth = params->nth; + + const int64_t ne00 = src0->ne[0]; // IC, or K in the depthwise case + const int64_t ne01 = src0->ne[1]; // OC, or 1 + const int64_t ne02 = src0->ne[2]; // 1, or C + const int64_t ne11 = src1->ne[1]; // N + + const float * bias = (const float *) src2->data; + + // Both scales are multipliers, so they combine by multiplication and the + // int32 accumulator only has to be scaled once. + const float combined_d = *ggml_inband_scale_const(src0) * *ggml_inband_scale_const(src1); + + const int8_t * w = (const int8_t *) src0->data; + const int8_t * x = (const int8_t *) src1->data; + + const bool depthwise = ne01 == 1 && ne02 > 1; + + // Output channel count, and the number of outputs per column. + const int64_t OC = depthwise ? ne02 : ne01; + + // The loops below walk a single batch. Batched matmul has no caller in the + // VibeASR graphs, so refuse it here rather than silently leaving + // dst->ne[2]/ne[3] beyond the first as uninitialized garbage. In the + // depthwise case ne[2] is the channel axis, which is handled. + GGML_ASSERT(dst->ne[3] == 1); + GGML_ASSERT(depthwise || dst->ne[2] == 1); + GGML_ASSERT(ne11*OC == ggml_nelements(dst)); + + float * stage = (float *) params->wdata; + float * thread_max = stage + ne11*OC; + + // Split over columns: every thread owns whole columns of the output, so the + // int32 accumulators need no cross-thread coordination. + const int64_t dr = (ne11 + nth - 1)/nth; + const int64_t col0 = dr*ith; + const int64_t col1 = MIN(col0 + dr, ne11); + + float local_absmax = 0.0f; + + if (depthwise) { + // stage/dst index is ch*ne11 + col, so each channel is a row of length + // ne11 and this thread owns the [col0, col1) band of every row. + // + // The batched roles are inverted here relative to the matmul path: the + // filter is the single row and the positions are the nrc rows, since it is + // the positions that are strided by ne00 and the filter that is reused. + // That also makes the results contiguous, so the scale/bias pass vectorizes. + int32_t acc[GGML_MUL_MAT_ADD_OC_CHUNK]; + float bias_v[GGML_MUL_MAT_ADD_OC_CHUNK]; + + for (int64_t ch = 0; ch < ne02; ch++) { + const int8_t * w_ch = w + ch*ne00; + + // Broadcast once per channel, not per position. + for (int64_t j = 0; j < GGML_MUL_MAT_ADD_OC_CHUNK; j++) { + bias_v[j] = bias[ch]; + } + + for (int64_t col = col0; col < col1; col += GGML_MUL_MAT_ADD_OC_CHUNK) { + const int64_t ncol = MIN((int64_t) GGML_MUL_MAT_ADD_OC_CHUNK, col1 - col); + + // src1 is [K, N, C]: the channel stride is ne11*ne00. + const int8_t * x_col = x + ch*ne11*ne00 + col*ne00; + + ggml_vec_dot_i8_i8(ne00, acc, 1, x_col, ne00, w_ch, ncol); + + local_absmax = ggml_i8_s_scale_bias_absmax( + stage + ch*ne11 + col, acc, bias_v, ncol, combined_d, local_absmax); + } + } + + ggml_i8_s_requantize(params, dst, stage, thread_max, ne11, ne02, col0, col1, local_absmax, relu); + } else { + // stage/dst index is col*ne01 + oc, so this thread owns the contiguous + // block [col0*ne01, col1*ne01). + const size_t nb11 = src1->nb[1]; + + // Output channels are handled a chunk at a time so the int32 + // accumulators fit a fixed stack buffer and params->wdata does not have + // to grow. Within a chunk the weight rows are contiguous and x_col stays + // hot, which is the whole reason for batching them into one call. + int32_t acc[GGML_MUL_MAT_ADD_OC_CHUNK]; + + for (int64_t col = col0; col < col1; col++) { + const int8_t * x_col = (const int8_t *) ((const char *) src1->data + col*nb11); + + for (int64_t oc0 = 0; oc0 < ne01; oc0 += GGML_MUL_MAT_ADD_OC_CHUNK) { + const int64_t noc = MIN((int64_t) GGML_MUL_MAT_ADD_OC_CHUNK, ne01 - oc0); + + ggml_vec_dot_i8_i8(ne00, acc, 1, w + oc0*ne00, ne00, x_col, noc); + + local_absmax = ggml_i8_s_scale_bias_absmax( + stage + col*ne01 + oc0, acc, bias + oc0, noc, combined_d, local_absmax); + } + } + + ggml_i8_s_requantize(params, dst, stage, thread_max, 0, 1, col0*ne01, col1*ne01, local_absmax, relu); + } +} + +void ggml_compute_forward_mul_mat_add( + const ggml_compute_params * params, + ggml_tensor * dst) { + ggml_compute_forward_mul_mat_add_impl(params, dst, false); +} + +void ggml_compute_forward_mul_mat_add_relu( + const ggml_compute_params * params, + ggml_tensor * dst) { + ggml_compute_forward_mul_mat_add_impl(params, dst, true); +} + +// ggml_compute_forward_mul_mat_i2_s + +// Output features per int32 accumulator batch. Batching them means the +// activation row is read once for 64 weight rows instead of once per row, and 64 +// int32s is a small enough stack buffer that params->wdata does not have to +// carry it. +#define GGML_MUL_MAT_I2_S_OC_CHUNK 64 + +void ggml_compute_forward_mul_mat_i2_s( + const ggml_compute_params * params, + ggml_tensor * dst) { + + const ggml_tensor * src0 = dst->src[0]; // weights (I2_S, ternary) + const ggml_tensor * src1 = dst->src[1]; // activations (F32) + + GGML_ASSERT(src0->type == GGML_TYPE_I2_S); + GGML_ASSERT(src1->type == GGML_TYPE_F32); + GGML_ASSERT(dst->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_contiguous(src0)); + + GGML_TENSOR_BINARY_OP_LOCALS + + GGML_ASSERT(ne0 == ne01); + GGML_ASSERT(ne1 == ne11); + GGML_ASSERT(ne2 == ne12); + GGML_ASSERT(ne3 == ne13); + GGML_ASSERT(nb0 == sizeof(float)); + GGML_ASSERT(ne00 % 128 == 0); + + // One scale covers the whole weight tensor, so broadcasting src0 over a + // batch would work, but the language model never has more than a 2D weight + // and silently supporting an untested shape is worse than refusing it. + GGML_ASSERT(ne02 == 1 && ne03 == 1); + + const int ith = params->ith; + const int nth = params->nth; + + const int64_t nrows_y = ne11*ne12*ne13; + + // wdata holds the quantized activation rows, then a sidecar with one scale + // and one int8 row sum each. The sidecar cannot be folded into the rows the + // way I8_S folds its scale in: both values are needed in the epilogue, after + // the kernel has already consumed the row. + int8_t * const qy = (int8_t *) params->wdata; + const size_t qy_bytes = GGML_PAD((size_t) ne10*nrows_y, sizeof(float)); + float * const act_scale = (float *) ((char *) qy + qy_bytes); + int32_t * const act_sum = (int32_t *) (act_scale + nrows_y); + + GGML_ASSERT(params->wsize >= qy_bytes + nrows_y*(sizeof(float) + sizeof(int32_t))); + + // Striped by whole rows: the scale is a row-wide absmax, so a row cannot be + // split across threads. + for (int64_t ir = ith; ir < nrows_y; ir += nth) { + const int64_t i11 = ir % ne11; + const int64_t i12 = (ir / ne11) % ne12; + const int64_t i13 = ir / (ne11*ne12); + + const float * y_row = (const float *) ((const char *) src1->data + i11*nb11 + i12*nb12 + i13*nb13); + + ggml_i8_s_quantize_act(y_row, qy + ir*ne10, ne10, act_scale + ir, act_sum + ir); + } + + ggml_barrier(params->threadpool); + + const float w_scale = *ggml_inband_scale_const(src0); + const size_t w_row = nb01; // bytes per packed weight row + + // Split over output features and sweep the whole batch inside, so a weight + // row is loaded once and reused across every column. Splitting over columns + // instead would re-stream the weights per thread, and the weights are what + // this op is bandwidth-bound on. + const int64_t dr = (ne01 + nth - 1)/nth; + const int64_t oc0 = dr*ith; + const int64_t oc1 = MIN(oc0 + dr, ne01); + + if (oc0 >= oc1) { + return; + } + + int32_t acc[GGML_MUL_MAT_I2_S_OC_CHUNK]; + + for (int64_t ir = 0; ir < nrows_y; ++ir) { + const int64_t i11 = ir % ne11; + const int64_t i12 = (ir / ne11) % ne12; + const int64_t i13 = ir / (ne11*ne12); + + const int8_t * y_row = qy + ir*ne10; + + // The kernel returns sum(code*q) with codes {0,1,2}; subtracting the row + // sum turns that into sum(w*q) with w in {-1,0,+1}. Both scales are + // multipliers, so they combine once at the end. + const float d = w_scale * act_scale[ir]; + const int32_t bias = act_sum[ir]; + + float * dst_row = (float *) ((char *) dst->data + i11*nb1 + i12*nb2 + i13*nb3); + + for (int64_t oc = oc0; oc < oc1; oc += GGML_MUL_MAT_I2_S_OC_CHUNK) { + const int64_t noc = MIN((int64_t) GGML_MUL_MAT_I2_S_OC_CHUNK, oc1 - oc); + + ggml_vec_dot_i2_i8(ne00, acc, 1, + (const uint8_t *) src0->data + oc*w_row, w_row, + y_row, noc); + + for (int64_t j = 0; j < noc; ++j) { + dst_row[oc + j] = (float) (acc[j] - bias) * d; + } + } + } +} + +// ggml_compute_forward_im2col_asym + +void ggml_compute_forward_im2col_asym( + const ggml_compute_params * params, + ggml_tensor * dst) { + + const ggml_tensor * src0 = dst->src[0]; // kernel, for its shape only + const ggml_tensor * src1 = dst->src[1]; // input (I8_S) + + GGML_ASSERT(src1->type == GGML_TYPE_I8_S); + GGML_ASSERT(dst->type == GGML_TYPE_I8_S); + + const int32_t s0 = ggml_get_op_params_i32(dst, 0); + const int32_t lp0 = ggml_get_op_params_i32(dst, 2); + const int32_t d0 = ggml_get_op_params_i32(dst, 4); + const bool is_2D = ggml_get_op_params_i32(dst, 6) == 1; + + // 1D only. The 2D case has no caller, and guessing at its indexing would + // mean shipping an untested path. + GGML_ASSERT(!is_2D && "ggml_im2col_asym: only the 1D case is implemented"); + + const int ith = params->ith; + const int nth = params->nth; + + const int64_t IC = src0->ne[1]; + const int64_t KW = src0->ne[0]; + const int64_t IW = src1->ne[0]; + const int64_t N = src1->ne[2]; + const int64_t OW = dst->ne[1]; + + const int64_t total = N*OW; + const int64_t per_thread = (total + nth - 1)/nth; + const int64_t start = per_thread*ith; + const int64_t end = MIN(start + per_thread, total); + + int8_t * dst_data = (int8_t *) dst->data; + + for (int64_t idx = start; idx < end; idx++) { + const int64_t in = idx/OW; + const int64_t iow = idx%OW; + + int8_t * dst_col = dst_data + idx*(IC*KW); + + for (int64_t iic = 0; iic < IC; iic++) { + const int8_t * src_ch = (const int8_t *) ((const char *) src1->data + + iic*src1->nb[1] + in*src1->nb[2]); + + for (int64_t ikw = 0; ikw < KW; ikw++) { + const int64_t iiw = iow*s0 + ikw*d0 - lp0; + + // Zero is exact in both directions, so padding needs no + // special handling when the scale is applied later. + dst_col[iic*KW + ikw] = (iiw < 0 || iiw >= IW) ? 0 : src_ch[iiw]; + } + } + } + + if (ith == 0) { + // Pure rearrangement: no value changes, so the scale carries over. + *ggml_inband_scale(dst) = *ggml_inband_scale_const(src1); + } +} diff --git a/external/ggml/src/ggml-cpu/ops.h b/external/ggml/src/ggml-cpu/ops.h index e0b3a7bd2..0e3324c97 100644 --- a/external/ggml/src/ggml-cpu/ops.h +++ b/external/ggml/src/ggml-cpu/ops.h @@ -63,11 +63,11 @@ void ggml_compute_forward_soft_max(const struct ggml_compute_params * params, st void ggml_compute_forward_soft_max_ext_back(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_rope(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_rope_back(const struct ggml_compute_params * params, struct ggml_tensor * dst); -void ggml_compute_forward_clamp(const struct ggml_compute_params * params, struct ggml_tensor * dst); -void ggml_compute_forward_conv_transpose_1d(const struct ggml_compute_params * params, struct ggml_tensor * dst); -void ggml_compute_forward_im2col(const struct ggml_compute_params * params, struct ggml_tensor * dst); -void ggml_compute_forward_im2col_fast_1d(const struct ggml_compute_params * params, struct ggml_tensor * dst); -void ggml_compute_forward_im2col_back_f32(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_clamp(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_conv_transpose_1d(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_im2col(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_im2col_fast_1d(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_im2col_back_f32(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_im2col_3d(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_conv_2d(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_conv_3d(const struct ggml_compute_params * params, struct ggml_tensor * dst); @@ -115,6 +115,14 @@ void ggml_compute_forward_opt_step_adamw(const struct ggml_compute_params * para void ggml_compute_forward_mul_mat(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_fwht(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_opt_step_sgd(const struct ggml_compute_params * params, struct ggml_tensor * dst); + +// VibeASR CPU INT8 pipeline +void ggml_compute_forward_add_scaled(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_rms_norm_scaled(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_mul_mat_add(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_mul_mat_add_relu(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_mul_mat_i2_s(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_im2col_asym(const struct ggml_compute_params * params, struct ggml_tensor * dst); #ifdef __cplusplus } #endif diff --git a/external/ggml/src/ggml-cpu/unary-ops.cpp b/external/ggml/src/ggml-cpu/unary-ops.cpp index a82ffc260..4a813271c 100644 --- a/external/ggml/src/ggml-cpu/unary-ops.cpp +++ b/external/ggml/src/ggml-cpu/unary-ops.cpp @@ -97,6 +97,10 @@ static inline float op_trunc(float x) { return truncf(x); } +static inline float op_round_bf16(float x) { + return bf16_to_f32(f32_to_bf16(x)); +} + template static inline void vec_unary_op(int64_t n, dst_t * y, const src0_t * x) { constexpr auto src0_to_f32 = type_conversion_table::to_f32; @@ -322,6 +326,10 @@ void ggml_compute_forward_trunc(const ggml_compute_params * params, ggml_tensor unary_op(params, dst); } +void ggml_compute_forward_round_bf16(const ggml_compute_params * params, ggml_tensor * dst) { + unary_op(params, dst); +} + void ggml_compute_forward_xielu(const ggml_compute_params * params, ggml_tensor * dst) { const float alpha_n = ggml_get_op_params_f32(dst, 1); const float alpha_p = ggml_get_op_params_f32(dst, 2); diff --git a/external/ggml/src/ggml-cpu/unary-ops.h b/external/ggml/src/ggml-cpu/unary-ops.h index d75037369..06f3e1c37 100644 --- a/external/ggml/src/ggml-cpu/unary-ops.h +++ b/external/ggml/src/ggml-cpu/unary-ops.h @@ -28,6 +28,7 @@ void ggml_compute_forward_floor(const struct ggml_compute_params * params, struc void ggml_compute_forward_ceil(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_round(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_trunc(const struct ggml_compute_params * params, struct ggml_tensor * dst); +void ggml_compute_forward_round_bf16(const struct ggml_compute_params * params, struct ggml_tensor * dst); void ggml_compute_forward_xielu(const struct ggml_compute_params * params, struct ggml_tensor * dst); #ifdef __cplusplus diff --git a/external/ggml/src/ggml-cpu/vec.cpp b/external/ggml/src/ggml-cpu/vec.cpp index 7e8135617..0d42f4ede 100644 --- a/external/ggml/src/ggml-cpu/vec.cpp +++ b/external/ggml/src/ggml-cpu/vec.cpp @@ -393,6 +393,238 @@ void ggml_vec_dot_f16(int n, float * GGML_RESTRICT s, size_t bs, ggml_fp16_t * G *s = sumf; } +#if defined(__AVX2__) +static inline int ggml_i8_hsum_i32_4(const __m128i a) { + const __m128i hi64 = _mm_unpackhi_epi64(a, a); + const __m128i sum64 = _mm_add_epi32(hi64, a); + const __m128i hi32 = _mm_shuffle_epi32(sum64, _MM_SHUFFLE(2, 3, 0, 1)); + return _mm_cvtsi128_si32(_mm_add_epi32(sum64, hi32)); +} +#endif + +void ggml_vec_dot_i8_i8(int n, int32_t * GGML_RESTRICT s, size_t bs, const int8_t * GGML_RESTRICT x, size_t bx, const int8_t * GGML_RESTRICT y, int nrc) { + for (int row = 0; row < nrc; ++row) { + const int8_t * xr = x + (size_t)row*bx; + + int i = 0; + int32_t sumi = 0; + +#if defined(__AVX2__) + // The sign trick: maddubs wants the left operand unsigned, so move the + // sign of x onto y and take |x|. Each maddubs lane holds a sum of two + // products, at most 2*128*127 = 32512, which still fits int16 - but a + // second int16 accumulation could not, so widen to int32 every block + // instead of batching in int16. + // + // Everything reduces into one 128-bit accumulator so that short rows + // (the depthwise filters are 4 or 8 long) neither run the 256-bit loop + // nor pay for reducing a register that stayed zero. + __m128i acc = _mm_setzero_si128(); + + if (i + 32 <= n) { + const __m256i one16 = _mm256_set1_epi16(1); + + __m256i acc256 = _mm256_setzero_si256(); + for (; i + 32 <= n; i += 32) { + const __m256i xq = _mm256_loadu_si256((const __m256i *)(xr + i)); + const __m256i yq = _mm256_loadu_si256((const __m256i *)(y + i)); + + const __m256i ax = _mm256_sign_epi8(xq, xq); + const __m256i sy = _mm256_sign_epi8(yq, xq); + const __m256i dot = _mm256_maddubs_epi16(ax, sy); + + acc256 = _mm256_add_epi32(acc256, _mm256_madd_epi16(dot, one16)); + } + acc = _mm_add_epi32(_mm256_castsi256_si128(acc256), + _mm256_extracti128_si256(acc256, 1)); + } + + if (i + 8 <= n) { + const __m128i one16 = _mm_set1_epi16(1); + + for (; i + 16 <= n; i += 16) { + const __m128i xq = _mm_loadu_si128((const __m128i *)(xr + i)); + const __m128i yq = _mm_loadu_si128((const __m128i *)(y + i)); + + const __m128i ax = _mm_sign_epi8(xq, xq); + const __m128i sy = _mm_sign_epi8(yq, xq); + const __m128i dot = _mm_maddubs_epi16(ax, sy); + + acc = _mm_add_epi32(acc, _mm_madd_epi16(dot, one16)); + } + for (; i + 8 <= n; i += 8) { + const __m128i xq = _mm_loadl_epi64((const __m128i *)(xr + i)); + const __m128i yq = _mm_loadl_epi64((const __m128i *)(y + i)); + + const __m128i ax = _mm_sign_epi8(xq, xq); + const __m128i sy = _mm_sign_epi8(yq, xq); + const __m128i dot = _mm_maddubs_epi16(ax, sy); + + acc = _mm_add_epi32(acc, _mm_madd_epi16(dot, one16)); + } + } + + // i > 0 exactly when some vector block ran. Reducing a register that + // stayed zero costs four instructions per row, which is not free when the + // row is a 4-tap depthwise filter and there is one row per output. + if (i > 0) { + sumi = ggml_i8_hsum_i32_4(acc); + } +#elif defined(__ARM_NEON) && defined(__aarch64__) + int32x4_t acc = vdupq_n_s32(0); + for (; i + 16 <= n; i += 16) { + const int8x16_t xv = vld1q_s8(xr + i); + const int8x16_t yv = vld1q_s8(y + i); + #if defined(__ARM_FEATURE_DOTPROD) + acc = vdotq_s32(acc, xv, yv); + #else + // vmull_s8 tops out at 128*128 = 16384, so the int16 products are + // safe; vpadalq_s16 folds them pairwise straight into int32. + acc = vpadalq_s16(acc, vmull_s8(vget_low_s8 (xv), vget_low_s8 (yv))); + acc = vpadalq_s16(acc, vmull_s8(vget_high_s8(xv), vget_high_s8(yv))); + #endif + } + for (; i + 8 <= n; i += 8) { + const int8x8_t xv = vld1_s8(xr + i); + const int8x8_t yv = vld1_s8(y + i); + acc = vpadalq_s16(acc, vmull_s8(xv, yv)); + } + if (i > 0) { + sumi = vaddvq_s32(acc); + } +#endif + + for (; i < n; ++i) { + sumi += (int32_t)xr[i] * (int32_t)y[i]; + } + + s[(size_t)row*bs] = sumi; + } +} + +void ggml_vec_dot_i2_i8(int n, int32_t * GGML_RESTRICT s, size_t bs, const uint8_t * GGML_RESTRICT x, size_t bx, const int8_t * GGML_RESTRICT y, int nrc) { + // 128 values per 32-byte group, and every I2_S row in the language model is + // a multiple of that, so there is no partial-group path to get wrong. + assert(n % 128 == 0); + + const int nb = n / 128; + + for (int row = 0; row < nrc; ++row) { + const uint8_t * xr = x + (size_t)row*bx; + + int b = 0; + int32_t sumi = 0; + +#if defined(__AVX2__) + // Shifting by 16-bit lanes pulls bits down from the neighbouring byte, + // but the mask discards them: after >>6 the low two bits of each byte + // are exactly that byte's top code. + const __m256i mask = _mm256_set1_epi8(0x03); + const __m256i one16 = _mm256_set1_epi16(1); + + __m256i acc = _mm256_setzero_si256(); + + // Each maddubs lane holds a sum of two code*int8 products, at most + // 2*2*127 = 508 in magnitude, and eight groups contribute 32 of them: + // 32*508 = 16256, still inside int16. Widening once per eight groups + // rather than once per group keeps the madd out of the inner loop. + while (b < nb) { + const int bend = b + 8 < nb ? b + 8 : nb; + + __m256i acc16 = _mm256_setzero_si256(); + + for (; b < bend; ++b) { + const __m256i xq = _mm256_loadu_si256((const __m256i *)(xr + (size_t)b*32)); + + const __m256i c0 = _mm256_and_si256(_mm256_srli_epi16(xq, 6), mask); + const __m256i c1 = _mm256_and_si256(_mm256_srli_epi16(xq, 4), mask); + const __m256i c2 = _mm256_and_si256(_mm256_srli_epi16(xq, 2), mask); + const __m256i c3 = _mm256_and_si256(xq, mask); + + const int8_t * py = y + (size_t)b*128; + + const __m256i y0 = _mm256_loadu_si256((const __m256i *)(py + 0)); + const __m256i y1 = _mm256_loadu_si256((const __m256i *)(py + 32)); + const __m256i y2 = _mm256_loadu_si256((const __m256i *)(py + 64)); + const __m256i y3 = _mm256_loadu_si256((const __m256i *)(py + 96)); + + acc16 = _mm256_add_epi16(acc16, _mm256_add_epi16(_mm256_maddubs_epi16(c0, y0), + _mm256_maddubs_epi16(c1, y1))); + acc16 = _mm256_add_epi16(acc16, _mm256_add_epi16(_mm256_maddubs_epi16(c2, y2), + _mm256_maddubs_epi16(c3, y3))); + } + + acc = _mm256_add_epi32(acc, _mm256_madd_epi16(acc16, one16)); + } + + sumi = ggml_i8_hsum_i32_4(_mm_add_epi32(_mm256_castsi256_si128(acc), + _mm256_extracti128_si256(acc, 1))); +#elif defined(__ARM_NEON) && defined(__aarch64__) + // Half a group per iteration, since a NEON register holds 16 of the 32 + // bytes. The four code lanes still map to the four 32-value slices of y, + // offset by which half of the group is loaded. + const uint8x16_t mask = vdupq_n_u8(3); + + int32x4_t acc = vdupq_n_s32(0); + + for (; b < nb; ++b) { + for (int h = 0; h < 2; ++h) { + const uint8x16_t xq = vld1q_u8(xr + (size_t)b*32 + h*16); + + const int8x16_t c0 = vreinterpretq_s8_u8(vandq_u8(vshrq_n_u8(xq, 6), mask)); + const int8x16_t c1 = vreinterpretq_s8_u8(vandq_u8(vshrq_n_u8(xq, 4), mask)); + const int8x16_t c2 = vreinterpretq_s8_u8(vandq_u8(vshrq_n_u8(xq, 2), mask)); + const int8x16_t c3 = vreinterpretq_s8_u8(vandq_u8(xq, mask)); + + const int8_t * py = y + (size_t)b*128 + h*16; + + const int8x16_t y0 = vld1q_s8(py + 0); + const int8x16_t y1 = vld1q_s8(py + 32); + const int8x16_t y2 = vld1q_s8(py + 64); + const int8x16_t y3 = vld1q_s8(py + 96); + + #if defined(__ARM_FEATURE_DOTPROD) + acc = vdotq_s32(acc, c0, y0); + acc = vdotq_s32(acc, c1, y1); + acc = vdotq_s32(acc, c2, y2); + acc = vdotq_s32(acc, c3, y3); + #else + // Products top out at 2*127 = 254, so the int16 halves are safe + // and vpadalq_s16 folds them pairwise straight into int32. + acc = vpadalq_s16(acc, vmull_s8(vget_low_s8 (c0), vget_low_s8 (y0))); + acc = vpadalq_s16(acc, vmull_s8(vget_high_s8(c0), vget_high_s8(y0))); + acc = vpadalq_s16(acc, vmull_s8(vget_low_s8 (c1), vget_low_s8 (y1))); + acc = vpadalq_s16(acc, vmull_s8(vget_high_s8(c1), vget_high_s8(y1))); + acc = vpadalq_s16(acc, vmull_s8(vget_low_s8 (c2), vget_low_s8 (y2))); + acc = vpadalq_s16(acc, vmull_s8(vget_high_s8(c2), vget_high_s8(y2))); + acc = vpadalq_s16(acc, vmull_s8(vget_low_s8 (c3), vget_low_s8 (y3))); + acc = vpadalq_s16(acc, vmull_s8(vget_high_s8(c3), vget_high_s8(y3))); + #endif + } + } + + sumi = vaddvq_s32(acc); +#endif + + // Portable path, and the only path on targets without either ISA. + for (; b < nb; ++b) { + const uint8_t * px = xr + (size_t)b*32; + const int8_t * py = y + (size_t)b*128; + + for (int gp = 0; gp < 32; ++gp) { + const uint8_t v = px[gp]; + + sumi += (int32_t)((v >> 6) & 3) * (int32_t)py[ gp]; + sumi += (int32_t)((v >> 4) & 3) * (int32_t)py[32 + gp]; + sumi += (int32_t)((v >> 2) & 3) * (int32_t)py[64 + gp]; + sumi += (int32_t)( v & 3) * (int32_t)py[96 + gp]; + } + } + + s[(size_t)row*bs] = sumi; + } +} + void ggml_vec_silu_f32(const int n, float * y, const float * x) { int i = 0; #if defined(__AVX512F__) && defined(__AVX512DQ__) diff --git a/external/ggml/src/ggml-cpu/vec.h b/external/ggml/src/ggml-cpu/vec.h index 741902479..8943e86c6 100644 --- a/external/ggml/src/ggml-cpu/vec.h +++ b/external/ggml/src/ggml-cpu/vec.h @@ -43,6 +43,30 @@ void ggml_vec_dot_f32(int n, float * GGML_RESTRICT s, size_t bs, const float * G void ggml_vec_dot_bf16(int n, float * GGML_RESTRICT s, size_t bs, ggml_bf16_t * GGML_RESTRICT x, size_t bx, ggml_bf16_t * GGML_RESTRICT y, size_t by, int nrc); void ggml_vec_dot_f16(int n, float * GGML_RESTRICT s, size_t bs, ggml_fp16_t * GGML_RESTRICT x, size_t bx, ggml_fp16_t * GGML_RESTRICT y, size_t by, int nrc); +// int8 x int8 dot products accumulating in int32, for the GGML_TYPE_I8_S ops. +// The scale is per-tensor and cancels out of the contraction, so these stay +// integral and the caller applies it once. nrc rows of x, each bx bytes apart, +// are contracted against the single row y; result row stride is bs int32s. +// n is arbitrary: whatever the vector width does not cover is done scalar. +void ggml_vec_dot_i8_i8(int n, int32_t * GGML_RESTRICT s, size_t bs, const int8_t * GGML_RESTRICT x, size_t bx, const int8_t * GGML_RESTRICT y, int nrc); + +// Packed ternary x int8 dot products for GGML_TYPE_I2_S, accumulating in int32. +// +// x holds the 2-bit codes as they sit on disk: 128 values per 32-byte group, +// byte gp of a group carrying the values at group-relative positions gp, 32+gp, +// 64+gp and 96+gp in bit pairs 6, 4, 2, 0. y is plain sequential int8, so the +// four code lanes of a group line up with four consecutive 32-value slices of y +// and no shuffling is needed on either side. +// +// The result is sum(code*y), NOT sum(w*y): the codes are {0,1,2} where the +// weights are {-1,0,+1}, so the caller subtracts sum(y) to recover the real +// contraction. Keeping the bias out of the kernel is what lets the unsigned +// multiply-add instructions be used directly. +// +// nrc rows of x, each bx bytes apart, are contracted against the single row y; +// result row stride is bs int32s. n must be a multiple of 128. +void ggml_vec_dot_i2_i8(int n, int32_t * GGML_RESTRICT s, size_t bs, const uint8_t * GGML_RESTRICT x, size_t bx, const int8_t * GGML_RESTRICT y, int nrc); + void ggml_vec_silu_f32(const int n, float * y, const float * x); ggml_float ggml_vec_cvar_f32(const int n, float * y, const float * x, const float mean); //it will also center y ( y = y - mean ) ggml_float ggml_vec_soft_max_f32(const int n, float * y, const float * x, float max); diff --git a/external/ggml/src/ggml-cuda/ggml-cuda.cu b/external/ggml/src/ggml-cuda/ggml-cuda.cu index 8dc80a82d..278a49c43 100644 --- a/external/ggml/src/ggml-cuda/ggml-cuda.cu +++ b/external/ggml/src/ggml-cuda/ggml-cuda.cu @@ -3065,6 +3065,9 @@ static bool ggml_cuda_compute_forward(ggml_backend_cuda_context & ctx, struct gg case GGML_UNARY_OP_TRUNC: ggml_cuda_op_trunc(ctx, dst); break; + case GGML_UNARY_OP_ROUND_BF16: + ggml_cuda_op_round_bf16(ctx, dst); + break; case GGML_UNARY_OP_EXPM1: ggml_cuda_op_expm1(ctx, dst); break; @@ -4670,7 +4673,12 @@ static bool ggml_cuda_graph_set_enabled(ggml_backend_cuda_context * cuda_ctx, co ggml_cuda_graph * graph = cuda_ctx->cuda_graph(graph_key); if (graph->graph == nullptr) { - if (ggml_cuda_info().devices[cuda_ctx->device].cc < GGML_CUDA_CC_AMPERE) { + // CUDA graphs are disabled by default on pre-Ampere GPUs (matching + // upstream, where they regressed on some parts), but can be force + // enabled; decode loops made of many tiny kernels benefit even on + // Turing. + static const bool allow_pre_ampere = getenv("GGML_CUDA_GRAPHS_PRE_AMPERE") != nullptr; + if (!allow_pre_ampere && ggml_cuda_info().devices[cuda_ctx->device].cc < GGML_CUDA_CC_AMPERE) { if (!graph->disable_due_to_gpu_arch) { GGML_LOG_DEBUG("%s: disabling CUDA graphs due to GPU architecture\n", __func__); } @@ -5356,6 +5364,12 @@ static bool ggml_backend_cuda_device_supports_op(ggml_backend_dev_t dev, const g // TODO: should become: //return ggml_is_contiguous_rows(op->src[0]); return ggml_is_contiguous(op->src[0]); + case GGML_UNARY_OP_ROUND_BF16: + // f32/f16/bf16 src with contiguous rows, contiguous f32 dst. + return (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16 || + op->src[0]->type == GGML_TYPE_BF16) && + op->type == GGML_TYPE_F32 && ggml_is_contiguous(op) && + ggml_is_contiguous_rows(op->src[0]); default: return false; } diff --git a/external/ggml/src/ggml-cuda/unary.cu b/external/ggml/src/ggml-cuda/unary.cu index fd3ac7550..213aeb4fe 100644 --- a/external/ggml/src/ggml-cuda/unary.cu +++ b/external/ggml/src/ggml-cuda/unary.cu @@ -114,6 +114,11 @@ static __device__ __forceinline__ float op_trunc(float x) { return trunc(x); } +static __device__ __forceinline__ float op_round_bf16(float x) { + // Matches the f32 -> bf16 -> f32 cpy round trip. + return __bfloat162float(__float2bfloat16(x)); +} + template static __global__ void unary_op_kernel(const T * x, T * dst, const int k) { const int i = blockDim.x*blockIdx.x + threadIdx.x; @@ -125,6 +130,75 @@ static __global__ void unary_op_kernel(const T * x, T * dst, const int k) { dst[i] = (T)op((float)x[i]); } +// Variant for a src with contiguous rows but arbitrary row strides; dst must be contiguous. +template +static __global__ void unary_op_kernel_strided( + const char * cx, T * dst, const int64_t k, + const int64_t ne0, const int64_t ne1, const int64_t ne2, + const int64_t nb01, const int64_t nb02, const int64_t nb03) { + const int64_t i = (int64_t)blockDim.x*blockIdx.x + threadIdx.x; + + if (i >= k) { + return; + } + + const int64_t i0 = i % ne0; + const int64_t i1 = (i / ne0) % ne1; + const int64_t i2 = (i / (ne0*ne1)) % ne2; + const int64_t i3 = i / (ne0*ne1*ne2); + + const T * x = (const T *) (cx + i1*nb01 + i2*nb02 + i3*nb03); + dst[i] = (T)op((float)x[i0]); +} + +// round-to-bf16 kernels: any of f32/f16/bf16 in, always f32 out. +template +static __global__ void round_bf16_kernel(const T * x, float * dst, const int64_t k) { + const int64_t i = (int64_t)blockDim.x*blockIdx.x + threadIdx.x; + + if (i >= k) { + return; + } + + dst[i] = op_round_bf16((float)x[i]); +} + +template +static __global__ void round_bf16_kernel_strided( + const char * cx, float * dst, const int64_t k, + const int64_t ne0, const int64_t ne1, const int64_t ne2, + const int64_t nb01, const int64_t nb02, const int64_t nb03) { + const int64_t i = (int64_t)blockDim.x*blockIdx.x + threadIdx.x; + + if (i >= k) { + return; + } + + const int64_t i0 = i % ne0; + const int64_t i1 = (i / ne0) % ne1; + const int64_t i2 = (i / (ne0*ne1)) % ne2; + const int64_t i3 = i / (ne0*ne1*ne2); + + const T * x = (const T *) (cx + i1*nb01 + i2*nb02 + i3*nb03); + dst[i] = op_round_bf16((float)x[i0]); +} + +template +static void round_bf16_cuda(const ggml_tensor * src0, float * dst, cudaStream_t stream) { + const int64_t k = ggml_nelements(src0); + const int64_t num_blocks = (k + CUDA_NEG_BLOCK_SIZE - 1) / CUDA_NEG_BLOCK_SIZE; + GGML_ASSERT(num_blocks < UINT_MAX); + + if (ggml_is_contiguous(src0)) { + round_bf16_kernel<<<(unsigned int) num_blocks, CUDA_NEG_BLOCK_SIZE, 0, stream>>>( + (const T *) src0->data, dst, k); + } else { + round_bf16_kernel_strided<<<(unsigned int) num_blocks, CUDA_NEG_BLOCK_SIZE, 0, stream>>>( + (const char *) src0->data, dst, k, src0->ne[0], src0->ne[1], src0->ne[2], + src0->nb[1], src0->nb[2], src0->nb[3]); + } +} + template static void unary_cuda(const T * x, T * dst, const int k, cudaStream_t stream) { const int num_blocks = (k + CUDA_NEG_BLOCK_SIZE - 1) / CUDA_NEG_BLOCK_SIZE; @@ -247,6 +321,31 @@ void ggml_cuda_op_trunc(ggml_backend_cuda_context & ctx, ggml_tensor * dst) { ggml_cuda_op_unary(ctx, dst); } +void ggml_cuda_op_round_bf16(ggml_backend_cuda_context & ctx, ggml_tensor * dst) { + const ggml_tensor * src0 = dst->src[0]; + + // ggml_round_bf16 always produces a contiguous f32 dst; src may be + // f32/f16/bf16 with contiguous rows. + GGML_ASSERT(dst->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_contiguous(dst)); + GGML_ASSERT(ggml_is_contiguous_rows(src0)); + + cudaStream_t stream = ctx.stream(); + switch (src0->type) { + case GGML_TYPE_F32: + round_bf16_cuda(src0, (float *) dst->data, stream); + break; + case GGML_TYPE_F16: + round_bf16_cuda(src0, (float *) dst->data, stream); + break; + case GGML_TYPE_BF16: + round_bf16_cuda(src0, (float *) dst->data, stream); + break; + default: + GGML_ABORT("%s: unsupported src type %s", __func__, ggml_type_name(src0->type)); + } +} + void ggml_cuda_op_expm1(ggml_backend_cuda_context & ctx, ggml_tensor * dst) { ggml_cuda_op_unary(ctx, dst); } diff --git a/external/ggml/src/ggml-cuda/unary.cuh b/external/ggml/src/ggml-cuda/unary.cuh index fbb229cb8..06a7e589f 100644 --- a/external/ggml/src/ggml-cuda/unary.cuh +++ b/external/ggml/src/ggml-cuda/unary.cuh @@ -75,6 +75,8 @@ void ggml_cuda_op_round(ggml_backend_cuda_context & ctx, ggml_tensor * dst); void ggml_cuda_op_trunc(ggml_backend_cuda_context & ctx, ggml_tensor * dst); +void ggml_cuda_op_round_bf16(ggml_backend_cuda_context & ctx, ggml_tensor * dst); + void ggml_cuda_op_reglu(ggml_backend_cuda_context & ctx, ggml_tensor * dst); void ggml_cuda_op_geglu(ggml_backend_cuda_context & ctx, ggml_tensor * dst); diff --git a/external/ggml/src/ggml-impl.h b/external/ggml/src/ggml-impl.h index d4cb6c9b3..2f50da73a 100644 --- a/external/ggml/src/ggml-impl.h +++ b/external/ggml/src/ggml-impl.h @@ -354,6 +354,26 @@ struct ggml_cgraph ggml_graph_view(struct ggml_cgraph * cgraph, int i0, int i1); // ggml-alloc.c: true if the operation can reuse memory from its sources GGML_API bool ggml_op_can_inplace(enum ggml_op op); +// Bytes a type needs past its payload. Non-zero only for GGML_TYPE_I8_S and +// GGML_TYPE_I2_S, which append one F32 per-tensor scale; see ggml.c. +GGML_API size_t ggml_type_extra_bytes(enum ggml_type type); + +// The in-band scale of an I8_S or I2_S tensor, which sits immediately after the +// payload. ggml_nbytes already counts the padded scale, so subtracting it back +// out gives the payload end without duplicating either type's row arithmetic. +// +// The scale is a multiplier in both cases: dequantizing is q * scale. +static inline float * ggml_inband_scale(struct ggml_tensor * tensor) { + const size_t extra = ggml_type_extra_bytes(tensor->type); + GGML_ASSERT(extra > 0 && "type has no in-band scale"); + GGML_ASSERT(ggml_is_contiguous(tensor)); + return (float *) ((char *) tensor->data + ggml_nbytes(tensor) - extra); +} + +static inline const float * ggml_inband_scale_const(const struct ggml_tensor * tensor) { + return ggml_inband_scale((struct ggml_tensor *) tensor); +} + // Memory allocation diff --git a/external/ggml/src/ggml-metal/ggml-metal-device.cpp b/external/ggml/src/ggml-metal/ggml-metal-device.cpp index 8f11f92a2..b8c544e60 100644 --- a/external/ggml/src/ggml-metal/ggml-metal-device.cpp +++ b/external/ggml/src/ggml-metal/ggml-metal-device.cpp @@ -352,6 +352,26 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_unary(ggml_metal return res; } +ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_snake_1d(ggml_metal_library_t lib, const ggml_tensor * op) { + GGML_ASSERT(op->op == GGML_OP_SNAKE_1D); + GGML_ASSERT(op->src[0]->type == GGML_TYPE_F32); + GGML_ASSERT(op->src[1]->type == GGML_TYPE_F32); + GGML_ASSERT(op->type == GGML_TYPE_F32); + + char base[256]; + char name[256]; + + snprintf(base, 256, "kernel_snake_1d_f32"); + snprintf(name, 256, "%s", base); + + ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name); + if (!res.pipeline) { + res = ggml_metal_library_compile_pipeline(lib, base, name, nullptr); + } + + return res; +} + ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_glu(ggml_metal_library_t lib, const ggml_tensor * op) { GGML_ASSERT(ggml_is_contiguous_1(op->src[0])); @@ -814,6 +834,72 @@ ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm(ggml_meta return res; } +// accumulate-in-place variant of kernel_mul_mm (tensor-core path only): identical tiling and +// threadgroup usage to ggml_metal_library_get_pipeline_mul_mm, just a different kernel that adds +// the result tile into the destination instead of overwriting it. +ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_acc(ggml_metal_library_t lib, const ggml_tensor * op) { + char base[256]; + char name[256]; + + const ggml_type tsrc0 = op->src[0]->type; + const ggml_type tsrc1 = op->src[1]->type; + + const bool bc_inp = op->src[0]->ne[0] % 32 != 0; + + constexpr int NRA = SZ_SIMDGROUP * N_MM_BLOCK_Y * N_MM_SIMD_GROUP_Y; + constexpr int NRB = SZ_SIMDGROUP * N_MM_BLOCK_X * N_MM_SIMD_GROUP_X; + + const bool bc_out = (op->ne[0] % NRA != 0 || op->ne[1] % NRB != 0); + + GGML_ASSERT(op->src[1]->ne[2] <= INT16_MAX && op->src[1]->ne[3] <= INT16_MAX); + const int16_t ne12 = (int16_t) op->src[1]->ne[2]; + const int16_t ne13 = (int16_t) op->src[1]->ne[3]; + const int16_t r2 = (int16_t) (ne12 / op->src[0]->ne[2]); + const int16_t r3 = (int16_t) (ne13 / op->src[0]->ne[3]); + + snprintf(base, 256, "kernel_mul_mm_acc_%s_%s", ggml_type_name(tsrc0), ggml_type_name(tsrc1)); + snprintf(name, 256, "%s_bci=%d_bco=%d_ne12=%d_ne13=%d_r2=%d_r3=%d", + base, bc_inp, bc_out, ne12, ne13, r2, r3); + + ggml_metal_pipeline_with_params res = ggml_metal_library_get_pipeline(lib, name); + if (!res.pipeline) { + ggml_metal_cv_t cv = ggml_metal_cv_init(); + + ggml_metal_cv_set_bool(cv, bc_inp, FC_MUL_MM + 0); + ggml_metal_cv_set_bool(cv, bc_out, FC_MUL_MM + 1); + ggml_metal_cv_set_int16(cv, ne12, FC_MUL_MM + 2); + ggml_metal_cv_set_int16(cv, ne13, FC_MUL_MM + 3); + ggml_metal_cv_set_int16(cv, r2, FC_MUL_MM + 4); + ggml_metal_cv_set_int16(cv, r3, FC_MUL_MM + 5); + + res = ggml_metal_library_compile_pipeline(lib, base, name, cv); + + ggml_metal_cv_free(cv); + } + + const bool has_tensor = ggml_metal_device_get_props(ggml_metal_library_get_device(lib))->has_tensor; + + if (has_tensor) { + res.nr0 = NRA; + res.nr1 = NRB; + + // threadgroup memory holds the dequantized A tile only (the epilogue accumulates + // through per-thread registers, no extra shared memory) + res.smem = NRA * N_MM_NK_TOTAL * sizeof(ggml_fp16_t); + } else { + res.nr0 = 64; + res.nr1 = 32; + + // the accumulate epilogue always stages the result tile through threadgroup memory + // (NR0 * NR1 floats), which subsumes the sa/sb region + res.smem = 8192; + } + + res.nsg = N_MM_SIMD_GROUP_X * N_MM_SIMD_GROUP_Y; + + return res; +} + ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv(ggml_metal_library_t lib, const ggml_tensor * op) { GGML_TENSOR_LOCALS( int32_t, ne0, op->src[0], ne); GGML_TENSOR_LOCALS( int32_t, ne1, op->src[1], ne); diff --git a/external/ggml/src/ggml-metal/ggml-metal-device.h b/external/ggml/src/ggml-metal/ggml-metal-device.h index 71e7aea5f..16eb89dfc 100644 --- a/external/ggml/src/ggml-metal/ggml-metal-device.h +++ b/external/ggml/src/ggml-metal/ggml-metal-device.h @@ -121,6 +121,7 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_diag struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_diag_mask_inf (ggml_metal_library_t lib, const struct ggml_tensor * op); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_repeat (ggml_metal_library_t lib, enum ggml_type tsrc); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_unary (ggml_metal_library_t lib, const struct ggml_tensor * op); +struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_snake_1d (ggml_metal_library_t lib, const struct ggml_tensor * op); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_glu (ggml_metal_library_t lib, const struct ggml_tensor * op); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_sum (ggml_metal_library_t lib, const struct ggml_tensor * op); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_sum_rows (ggml_metal_library_t lib, const struct ggml_tensor * op); @@ -136,6 +137,7 @@ struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_gated_del struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_solve_tri (ggml_metal_library_t lib, const struct ggml_tensor * op); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv_ext (ggml_metal_library_t lib, const struct ggml_tensor * op, int nsg, int nxpsg, int r1ptg); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm (ggml_metal_library_t lib, const struct ggml_tensor * op); +struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_acc (ggml_metal_library_t lib, const struct ggml_tensor * op); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mv (ggml_metal_library_t lib, const struct ggml_tensor * op); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id_map0 (ggml_metal_library_t lib, int ne02, int ne20); struct ggml_metal_pipeline_with_params ggml_metal_library_get_pipeline_mul_mm_id (ggml_metal_library_t lib, const struct ggml_tensor * op); diff --git a/external/ggml/src/ggml-metal/ggml-metal-device.m b/external/ggml/src/ggml-metal/ggml-metal-device.m index dca96bc5c..773a9b33e 100644 --- a/external/ggml/src/ggml-metal/ggml-metal-device.m +++ b/external/ggml/src/ggml-metal/ggml-metal-device.m @@ -1252,6 +1252,21 @@ bool ggml_metal_device_supports_op(ggml_metal_device_t dev, const struct ggml_te case GGML_OP_MUL_MAT: case GGML_OP_MUL_MAT_ID: return has_simdgroup_reduction && op->src[0]->type != GGML_TYPE_NVFP4; + case GGML_OP_SNAKE_1D: + // fused snake activation: elementwise F32, nothing exotic required + return op->src[0]->type == GGML_TYPE_F32 && op->src[1]->type == GGML_TYPE_F32 && + op->type == GGML_TYPE_F32; + case GGML_OP_MUL_MAT_ACC: + // accumulate-in-place matmul: mirrors the has_simdgroup_mm branch of mul_mat + // (the encode always takes the mm kernel), contiguous F32 x F32 -> F32 + return has_simdgroup_mm && + op->src[0]->type == GGML_TYPE_F32 && + op->src[1]->type == GGML_TYPE_F32 && + op->type == GGML_TYPE_F32 && + op->src[0]->ne[0] >= 64 && + op->src[1]->ne[1] > 8 && + !ggml_is_transposed(op->src[0]) && + !ggml_is_transposed(op->src[1]); case GGML_OP_SET: case GGML_OP_CPY: case GGML_OP_DUP: diff --git a/external/ggml/src/ggml-metal/ggml-metal-impl.h b/external/ggml/src/ggml-metal/ggml-metal-impl.h index a6ad1eec5..b567dd215 100644 --- a/external/ggml/src/ggml-metal/ggml-metal-impl.h +++ b/external/ggml/src/ggml-metal/ggml-metal-impl.h @@ -205,6 +205,17 @@ typedef struct { float max; } ggml_metal_kargs_unary; +typedef struct { + int32_t ne00; + int32_t ne01; + uint64_t nb00; + uint64_t nb01; + int32_t ne0; + int32_t ne1; + uint64_t nb0; + uint64_t nb1; +} ggml_metal_kargs_snake_1d; + typedef struct { int32_t ne00; int32_t ne01; diff --git a/external/ggml/src/ggml-metal/ggml-metal-ops.cpp b/external/ggml/src/ggml-metal/ggml-metal-ops.cpp index 40a5dca40..5395e1d58 100644 --- a/external/ggml/src/ggml-metal/ggml-metal-ops.cpp +++ b/external/ggml/src/ggml-metal/ggml-metal-ops.cpp @@ -390,6 +390,10 @@ static int ggml_metal_op_encode_impl(ggml_metal_op_t ctx, int idx) { { n_fuse = ggml_metal_op_unary(ctx, idx); } break; + case GGML_OP_SNAKE_1D: + { + n_fuse = ggml_metal_op_snake_1d(ctx, idx); + } break; case GGML_OP_GLU: { n_fuse = ggml_metal_op_glu(ctx, idx); @@ -436,6 +440,10 @@ static int ggml_metal_op_encode_impl(ggml_metal_op_t ctx, int idx) { { n_fuse = ggml_metal_op_mul_mat(ctx, idx); } break; + case GGML_OP_MUL_MAT_ACC: + { + n_fuse = ggml_metal_op_mul_mat_acc(ctx, idx); + } break; case GGML_OP_MUL_MAT_ID: { n_fuse = ggml_metal_op_mul_mat_id(ctx, idx); @@ -942,6 +950,51 @@ int ggml_metal_op_unary(ggml_metal_op_t ctx, int idx) { return 1; } +int ggml_metal_op_snake_1d(ggml_metal_op_t ctx, int idx) { + ggml_tensor * op = ctx->node(idx); + + ggml_metal_library_t lib = ctx->lib; + ggml_metal_encoder_t enc = ctx->enc; + + GGML_TENSOR_LOCALS( int32_t, ne0, op->src[0], ne); + GGML_TENSOR_LOCALS(uint64_t, nb0, op->src[0], nb); + GGML_TENSOR_LOCALS( int32_t, ne, op, ne); + GGML_TENSOR_LOCALS(uint64_t, nb, op, nb); + + GGML_ASSERT(ggml_is_contiguous(op->src[0])); + GGML_ASSERT(op->src[1]->ne[1] == 1); + + ggml_metal_kargs_snake_1d args = { + /*.ne00 =*/ ne00, + /*.ne01 =*/ ne01, + /*.nb00 =*/ nb00, + /*.nb01 =*/ nb01, + /*.ne0 =*/ ne0, + /*.ne1 =*/ ne1, + /*.nb0 =*/ nb0, + /*.nb1 =*/ nb1, + }; + + auto pipeline = ggml_metal_library_get_pipeline_snake_1d(lib, op); + + ggml_metal_encoder_set_pipeline(enc, pipeline); + ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0); + ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op->src[0]), 1); + ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op->src[1]), 2); + ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op), 3); + + const int64_t n = int64_t(ne00)*ne01; + + const int nth = MIN(1024, ggml_metal_pipeline_max_theads_per_threadgroup(pipeline)); + + const int nk0 = int((n + nth - 1)/nth); + + ggml_metal_encoder_dispatch_threadgroups(enc, nk0, 1, 1, nth, 1, 1); + + return 1; +} + + int ggml_metal_op_glu(ggml_metal_op_t ctx, int idx) { ggml_tensor * op = ctx->node(idx); @@ -2469,6 +2522,71 @@ int ggml_metal_op_mul_mat(ggml_metal_op_t ctx, int idx) { return 1; } +// accumulate-in-place matmul (dst aliases src[2]): encodes the tensor-core mm kernel that +// adds the product tile into the destination. Mirrors the has_simdgroup_mm branch of +// ggml_metal_op_mul_mat exactly; only the pipeline (accumulate variant) differs. +int ggml_metal_op_mul_mat_acc(ggml_metal_op_t ctx, int idx) { + ggml_tensor * op = ctx->node(idx); + + ggml_metal_library_t lib = ctx->lib; + ggml_metal_encoder_t enc = ctx->enc; + + GGML_TENSOR_LOCALS( int32_t, ne0, op->src[0], ne); + GGML_TENSOR_LOCALS(uint64_t, nb0, op->src[0], nb); + GGML_TENSOR_LOCALS( int32_t, ne1, op->src[1], ne); + GGML_TENSOR_LOCALS(uint64_t, nb1, op->src[1], nb); + GGML_TENSOR_LOCALS( int32_t, ne, op, ne); + GGML_TENSOR_LOCALS(uint64_t, nb, op, nb); + + GGML_ASSERT(ne00 == ne10); + + GGML_ASSERT(ne12 % ne02 == 0); + GGML_ASSERT(ne13 % ne03 == 0); + + // the kernel assumes a contiguous [ne0, ne1] destination tile (dst stride {1, ne0}) + GGML_ASSERT(ggml_is_contiguous(op)); + + const int16_t r2 = ne12/ne02; + const int16_t r3 = ne13/ne03; + + auto pipeline = ggml_metal_library_get_pipeline_mul_mm_acc(lib, op); + + ggml_metal_kargs_mul_mm args = { + /*.ne00 =*/ ne00, + /*.ne02 =*/ ne02, + /*.nb01 =*/ nb01, + /*.nb02 =*/ nb02, + /*.nb03 =*/ nb03, + /*.ne12 =*/ ne12, + /*.nb10 =*/ nb10, + /*.nb11 =*/ nb11, + /*.nb12 =*/ nb12, + /*.nb13 =*/ nb13, + /*.ne0 =*/ ne0, + /*.ne1 =*/ ne1, + /*.r2 =*/ r2, + /*.r3 =*/ r3, + }; + + ggml_metal_encoder_set_pipeline(enc, pipeline); + ggml_metal_encoder_set_bytes (enc, &args, sizeof(args), 0); + ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op->src[0]), 1); + ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op->src[1]), 2); + ggml_metal_encoder_set_buffer (enc, ggml_metal_get_buffer_id(op), 3); + + const size_t smem = pipeline.smem; + + ggml_metal_encoder_set_threadgroup_memory_size(enc, smem, 0); + + const int nr0 = pipeline.nr0; + const int nr1 = pipeline.nr1; + const int nsg = pipeline.nsg; + + ggml_metal_encoder_dispatch_threadgroups(enc, ((ne11 + nr1 - 1) / nr1), ((ne01 + nr0 - 1) / nr0), ne12 * ne13, 32, nsg, 1); + + return 1; +} + size_t ggml_metal_op_mul_mat_id_extra_tpe(const ggml_tensor * op) { assert(op->op == GGML_OP_MUL_MAT_ID); diff --git a/external/ggml/src/ggml-metal/ggml-metal-ops.h b/external/ggml/src/ggml-metal/ggml-metal-ops.h index 5dc229e26..ef1274349 100644 --- a/external/ggml/src/ggml-metal/ggml-metal-ops.h +++ b/external/ggml/src/ggml-metal/ggml-metal-ops.h @@ -47,6 +47,7 @@ int ggml_metal_op_concat (ggml_metal_op_t ctx, int idx); int ggml_metal_op_repeat (ggml_metal_op_t ctx, int idx); int ggml_metal_op_acc (ggml_metal_op_t ctx, int idx); int ggml_metal_op_unary (ggml_metal_op_t ctx, int idx); +int ggml_metal_op_snake_1d (ggml_metal_op_t ctx, int idx); int ggml_metal_op_glu (ggml_metal_op_t ctx, int idx); int ggml_metal_op_sum (ggml_metal_op_t ctx, int idx); int ggml_metal_op_sum_rows (ggml_metal_op_t ctx, int idx); @@ -66,6 +67,7 @@ int ggml_metal_op_cpy (ggml_metal_op_t ctx, int idx); int ggml_metal_op_pool_1d (ggml_metal_op_t ctx, int idx); int ggml_metal_op_pool_2d (ggml_metal_op_t ctx, int idx); int ggml_metal_op_mul_mat (ggml_metal_op_t ctx, int idx); +int ggml_metal_op_mul_mat_acc (ggml_metal_op_t ctx, int idx); int ggml_metal_op_mul_mat_id (ggml_metal_op_t ctx, int idx); int ggml_metal_op_add_id (ggml_metal_op_t ctx, int idx); int ggml_metal_op_flash_attn_ext (ggml_metal_op_t ctx, int idx); diff --git a/external/ggml/src/ggml-metal/ggml-metal.metal b/external/ggml/src/ggml-metal/ggml-metal.metal index b71b83c68..203b67d6a 100644 --- a/external/ggml/src/ggml-metal/ggml-metal.metal +++ b/external/ggml/src/ggml-metal/ggml-metal.metal @@ -1,11403 +1,11804 @@ -#define GGML_COMMON_DECL_METAL -#define GGML_COMMON_IMPL_METAL -#if defined(GGML_METAL_EMBED_LIBRARY) -__embed_ggml-common.h__ -#else -#include "ggml-common.h" -#endif -#include "ggml-metal-impl.h" - -#include - -#ifdef GGML_METAL_HAS_TENSOR -#include - -#include -#endif - -using namespace metal; - -#define MAX(x, y) ((x) > (y) ? (x) : (y)) -#define MIN(x, y) ((x) < (y) ? (x) : (y)) -#define SWAP(x, y) { auto tmp = (x); (x) = (y); (y) = tmp; } - -#define PAD2(x, n) (((x) + (n) - 1) & ~((n) - 1)) - -#define FOR_UNROLL(x) _Pragma("clang loop unroll(full)") for (x) - -#define N_SIMDWIDTH 32 // assuming SIMD group size is 32 - -// ref: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf -// -// cmd: -// .../usr/bin/metal -dM -E -c ggml/src/ggml-metal/ggml-metal.metal -// .../usr/bin/metal -dM -E -c -target air64-apple-ios14.0 ggml/src/ggml-metal/ggml-metal.metal -// -#if __METAL_VERSION__ < 310 && defined(GGML_METAL_HAS_BF16) -#undef GGML_METAL_HAS_BF16 -#endif - -#if defined(GGML_METAL_HAS_BF16) -typedef matrix bfloat4x4; -typedef matrix bfloat2x4; -#endif - -constexpr constant static float kvalues_iq4nl_f[16] = { - -127.f, -104.f, -83.f, -65.f, -49.f, -35.f, -22.f, -10.f, 1.f, 13.f, 25.f, 38.f, 53.f, 69.f, 89.f, 113.f -}; - -constexpr constant static float kvalues_mxfp4_f[16] = { - 0, .5f, 1.f, 1.5f, 2.f, 3.f, 4.f, 6.f, -0, -.5f, -1.f, -1.5f, -2.f, -3.f, -4.f, -6.f -}; - -static inline int best_index_int8(int n, constant float * val, float x) { - if (x <= val[0]) return 0; - if (x >= val[n-1]) return n-1; - int ml = 0, mu = n-1; - while (mu-ml > 1) { - int mav = (ml+mu)/2; - if (x < val[mav]) mu = mav; else ml = mav; - } - return x - val[mu-1] < val[mu] - x ? mu-1 : mu; -} - -static inline float e8m0_to_fp32(uint8_t x) { - uint32_t bits; - - if (x == 0) { - bits = 0x00400000; - } else { - bits = (uint32_t) x << 23; - } - - return as_type(bits); -} - -static inline float dot(float x, float y) { - return x*y; -} - -static inline float sum(float x) { - return x; -} - -static inline float sum(float4 x) { - return x[0] + x[1] + x[2] + x[3]; -} - -// NOTE: this is not dequantizing - we are simply fitting the template -template -void dequantize_f32(device const float4x4 * src, short il, thread type4x4 & reg) { - reg = (type4x4)(*src); -} - -template -void dequantize_f32_t4(device const float4 * src, short il, thread type4 & reg) { - reg = (type4)(*src); -} - -template -void dequantize_f16(device const half4x4 * src, short il, thread type4x4 & reg) { - reg = (type4x4)(*src); -} - -template -void dequantize_f16_t4(device const half4 * src, short il, thread type4 & reg) { - reg = (type4)(*(src)); -} - -#if defined(GGML_METAL_HAS_BF16) -template -void dequantize_bf16(device const bfloat4x4 * src, short il, thread type4x4 & reg) { - reg = (type4x4)(*src); -} - -template -void dequantize_bf16_t4(device const bfloat4 * src, short il, thread type4 & reg) { - reg = (type4)(*(src)); -} -#endif - -template -void dequantize_q1_0(device const block_q1_0 * xb, short il, thread type4x4 & reg) { - device const uint8_t * qs = xb->qs; - const float d = xb->d; - const float neg_d = -d; - - const int byte_offset = il * 2; // il*16 bits = il*2 bytes - const uint8_t b0 = qs[byte_offset]; - const uint8_t b1 = qs[byte_offset + 1]; - - float4x4 reg_f; - - reg_f[0][0] = select(neg_d, d, bool(b0 & 0x01)); - reg_f[0][1] = select(neg_d, d, bool(b0 & 0x02)); - reg_f[0][2] = select(neg_d, d, bool(b0 & 0x04)); - reg_f[0][3] = select(neg_d, d, bool(b0 & 0x08)); - reg_f[1][0] = select(neg_d, d, bool(b0 & 0x10)); - reg_f[1][1] = select(neg_d, d, bool(b0 & 0x20)); - reg_f[1][2] = select(neg_d, d, bool(b0 & 0x40)); - reg_f[1][3] = select(neg_d, d, bool(b0 & 0x80)); - - reg_f[2][0] = select(neg_d, d, bool(b1 & 0x01)); - reg_f[2][1] = select(neg_d, d, bool(b1 & 0x02)); - reg_f[2][2] = select(neg_d, d, bool(b1 & 0x04)); - reg_f[2][3] = select(neg_d, d, bool(b1 & 0x08)); - reg_f[3][0] = select(neg_d, d, bool(b1 & 0x10)); - reg_f[3][1] = select(neg_d, d, bool(b1 & 0x20)); - reg_f[3][2] = select(neg_d, d, bool(b1 & 0x40)); - reg_f[3][3] = select(neg_d, d, bool(b1 & 0x80)); - - reg = (type4x4) reg_f; -} - -template -void dequantize_q1_0_t4(device const block_q1_0 * xb, short il, thread type4 & reg) { - const float d = xb->d; - const float neg_d = -d; - const int base = il * 4; - const uint8_t byte = xb->qs[base / 8]; - const int s = base % 8; - - float4 reg_f; - reg_f[0] = select(neg_d, d, bool((byte >> (s )) & 1)); - reg_f[1] = select(neg_d, d, bool((byte >> (s + 1)) & 1)); - reg_f[2] = select(neg_d, d, bool((byte >> (s + 2)) & 1)); - reg_f[3] = select(neg_d, d, bool((byte >> (s + 3)) & 1)); - - reg = (type4) reg_f; -} - -template -void dequantize_q4_0(device const block_q4_0 * xb, short il, thread type4x4 & reg) { - device const uint16_t * qs = ((device const uint16_t *)xb + 1); - const float d1 = il ? (xb->d / 16.h) : xb->d; - const float d2 = d1 / 256.f; - const float md = -8.h * xb->d; - const ushort mask0 = il ? 0x00F0 : 0x000F; - const ushort mask1 = mask0 << 8; - - float4x4 reg_f; - - for (int i = 0; i < 8; i++) { - reg_f[i/2][2*(i%2) + 0] = d1 * (qs[i] & mask0) + md; - reg_f[i/2][2*(i%2) + 1] = d2 * (qs[i] & mask1) + md; - } - - reg = (type4x4) reg_f; -} - -template -void dequantize_q4_0_t4(device const block_q4_0 * xb, short il, thread type4 & reg) { - device const uint16_t * qs = ((device const uint16_t *)xb + 1); - const float d1 = (il/4) ? (xb->d / 16.h) : xb->d; - const float d2 = d1 / 256.f; - const float md = -8.h * xb->d; - const ushort mask0 = (il/4) ? 0x00F0 : 0x000F; - const ushort mask1 = mask0 << 8; - - for (int i = 0; i < 2; i++) { - reg[2*i + 0] = d1 * (qs[2*(il%4) + i] & mask0) + md; - reg[2*i + 1] = d2 * (qs[2*(il%4) + i] & mask1) + md; - } -} - -void quantize_q1_0(device const float * src, device block_q1_0 & dst) { - float sum_abs = 0.0f; - for (int j = 0; j < QK1_0; j++) { - sum_abs += fabs(src[j]); - } - dst.d = sum_abs / QK1_0; - - for (int j = 0; j < QK1_0 / 8; j++) { - dst.qs[j] = 0; - } - for (int j = 0; j < QK1_0; j++) { - if (src[j] >= 0.0f) { - dst.qs[j / 8] |= (1 << (j % 8)); - } - } -} - -void quantize_q4_0(device const float * src, device block_q4_0 & dst) { -#pragma METAL fp math_mode(safe) - float amax = 0.0f; // absolute max - float max = 0.0f; - - for (int j = 0; j < QK4_0; j++) { - const float v = src[j]; - if (amax < fabs(v)) { - amax = fabs(v); - max = v; - } - } - - const float d = max / -8; - const float id = d ? 1.0f/d : 0.0f; - - dst.d = d; - - for (int j = 0; j < QK4_0/2; ++j) { - const float x0 = src[0 + j]*id; - const float x1 = src[QK4_0/2 + j]*id; - - const uint8_t xi0 = MIN(15, (int8_t)(x0 + 8.5f)); - const uint8_t xi1 = MIN(15, (int8_t)(x1 + 8.5f)); - - dst.qs[j] = xi0; - dst.qs[j] |= xi1 << 4; - } -} - -void quantize_q4_1(device const float * src, device block_q4_1 & dst) { -#pragma METAL fp math_mode(safe) - float min = FLT_MAX; - float max = -FLT_MAX; - - for (int j = 0; j < QK4_1; j++) { - const float v = src[j]; - if (min > v) min = v; - if (max < v) max = v; - } - - const float d = (max - min) / ((1 << 4) - 1); - const float id = d ? 1.0f/d : 0.0f; - - dst.d = d; - dst.m = min; - - for (int j = 0; j < QK4_1/2; ++j) { - const float x0 = (src[0 + j] - min)*id; - const float x1 = (src[QK4_1/2 + j] - min)*id; - - const uint8_t xi0 = MIN(15, (int8_t)(x0 + 0.5f)); - const uint8_t xi1 = MIN(15, (int8_t)(x1 + 0.5f)); - - dst.qs[j] = xi0; - dst.qs[j] |= xi1 << 4; - } -} - -void quantize_q5_0(device const float * src, device block_q5_0 & dst) { -#pragma METAL fp math_mode(safe) - float amax = 0.0f; // absolute max - float max = 0.0f; - - for (int j = 0; j < QK5_0; j++) { - const float v = src[j]; - if (amax < fabs(v)) { - amax = fabs(v); - max = v; - } - } - - const float d = max / -16; - const float id = d ? 1.0f/d : 0.0f; - - dst.d = d; - - uint32_t qh = 0; - for (int j = 0; j < QK5_0/2; ++j) { - const float x0 = src[0 + j]*id; - const float x1 = src[QK5_0/2 + j]*id; - - const uint8_t xi0 = MIN(31, (int8_t)(x0 + 16.5f)); - const uint8_t xi1 = MIN(31, (int8_t)(x1 + 16.5f)); - - dst.qs[j] = (xi0 & 0xf) | ((xi1 & 0xf) << 4); - qh |= ((xi0 & 0x10u) >> 4) << (j + 0); - qh |= ((xi1 & 0x10u) >> 4) << (j + QK5_0/2); - } - - thread const uint8_t * qh8 = (thread const uint8_t *)&qh; - - for (int j = 0; j < 4; ++j) { - dst.qh[j] = qh8[j]; - } -} - -void quantize_q5_1(device const float * src, device block_q5_1 & dst) { -#pragma METAL fp math_mode(safe) - float max = src[0]; - float min = src[0]; - - for (int j = 1; j < QK5_1; j++) { - const float v = src[j]; - min = v < min ? v : min; - max = v > max ? v : max; - } - - const float d = (max - min) / 31; - const float id = d ? 1.0f/d : 0.0f; - - dst.d = d; - dst.m = min; - - uint32_t qh = 0; - for (int j = 0; j < QK5_1/2; ++j) { - const float x0 = (src[0 + j] - min)*id; - const float x1 = (src[QK5_1/2 + j] - min)*id; - - const uint8_t xi0 = (uint8_t)(x0 + 0.5f); - const uint8_t xi1 = (uint8_t)(x1 + 0.5f); - - dst.qs[j] = (xi0 & 0xf) | ((xi1 & 0xf) << 4); - qh |= ((xi0 & 0x10u) >> 4) << (j + 0); - qh |= ((xi1 & 0x10u) >> 4) << (j + QK5_1/2); - } - - thread const uint8_t * qh8 = (thread const uint8_t *)&qh; - - for (int j = 0; j < 4; ++j) { - dst.qh[j] = qh8[j]; - } -} - -void quantize_q8_0(device const float * src, device block_q8_0 & dst) { -#pragma METAL fp math_mode(safe) - float amax = 0.0f; // absolute max - - for (int j = 0; j < QK8_0; j++) { - const float v = src[j]; - amax = MAX(amax, fabs(v)); - } - - const float d = amax / ((1 << 7) - 1); - const float id = d ? 1.0f/d : 0.0f; - - dst.d = d; - - for (int j = 0; j < QK8_0; ++j) { - const float x0 = src[j]*id; - - dst.qs[j] = round(x0); - } -} - -void quantize_iq4_nl(device const float * src, device block_iq4_nl & dst) { -#pragma METAL fp math_mode(safe) - float amax = 0.0f; // absolute max - float max = 0.0f; - - for (int j = 0; j < QK4_NL; j++) { - const float v = src[j]; - if (amax < fabs(v)) { - amax = fabs(v); - max = v; - } - } - - const float d = max / kvalues_iq4nl_f[0]; - const float id = d ? 1.0f/d : 0.0f; - - float sumqx = 0, sumq2 = 0; - for (int j = 0; j < QK4_NL/2; ++j) { - const float x0 = src[0 + j]*id; - const float x1 = src[QK4_NL/2 + j]*id; - - const uint8_t xi0 = best_index_int8(16, kvalues_iq4nl_f, x0); - const uint8_t xi1 = best_index_int8(16, kvalues_iq4nl_f, x1); - - dst.qs[j] = xi0 | (xi1 << 4); - - const float v0 = kvalues_iq4nl_f[xi0]; - const float v1 = kvalues_iq4nl_f[xi1]; - const float w0 = src[0 + j]*src[0 + j]; - const float w1 = src[QK4_NL/2 + j]*src[QK4_NL/2 + j]; - sumqx += w0*v0*src[j] + w1*v1*src[QK4_NL/2 + j]; - sumq2 += w0*v0*v0 + w1*v1*v1; - - } - - dst.d = sumq2 > 0 ? sumqx/sumq2 : d; -} - -template -void dequantize_q4_1(device const block_q4_1 * xb, short il, thread type4x4 & reg) { - device const uint16_t * qs = ((device const uint16_t *)xb + 2); - const float d1 = il ? (xb->d / 16.h) : xb->d; - const float d2 = d1 / 256.f; - const float m = xb->m; - const ushort mask0 = il ? 0x00F0 : 0x000F; - const ushort mask1 = mask0 << 8; - - float4x4 reg_f; - - for (int i = 0; i < 8; i++) { - reg_f[i/2][2*(i%2) + 0] = ((qs[i] & mask0) * d1) + m; - reg_f[i/2][2*(i%2) + 1] = ((qs[i] & mask1) * d2) + m; - } - - reg = (type4x4) reg_f; -} - -template -void dequantize_q4_1_t4(device const block_q4_1 * xb, short il, thread type4 & reg) { - device const uint16_t * qs = ((device const uint16_t *)xb + 2); - const float d1 = (il/4) ? (xb->d / 16.h) : xb->d; - const float d2 = d1 / 256.f; - const float m = xb->m; - const ushort mask0 = (il/4) ? 0x00F0 : 0x000F; - const ushort mask1 = mask0 << 8; - - for (int i = 0; i < 2; i++) { - reg[2*i + 0] = d1 * (qs[2*(il%4) + i] & mask0) + m; - reg[2*i + 1] = d2 * (qs[2*(il%4) + i] & mask1) + m; - } -} - -template -void dequantize_q5_0(device const block_q5_0 * xb, short il, thread type4x4 & reg) { - device const uint16_t * qs = ((device const uint16_t *)xb + 3); - const float d = xb->d; - const float md = -16.h * xb->d; - const ushort mask = il ? 0x00F0 : 0x000F; - - const uint32_t qh = *((device const uint32_t *)xb->qh); - - const int x_mv = il ? 4 : 0; - - const int gh_mv = il ? 12 : 0; - const int gh_bk = il ? 0 : 4; - - float4x4 reg_f; - - for (int i = 0; i < 8; i++) { - // extract the 5-th bits for x0 and x1 - const uint8_t xh_0 = ((qh >> (gh_mv + 2*i )) << gh_bk) & 0x10; - const uint8_t xh_1 = ((qh >> (gh_mv + 2*i+1)) << gh_bk) & 0x10; - - // combine the 4-bits from qs with the 5th bit - const int32_t x0 = ((((qs[i] ) & mask) >> x_mv) | xh_0); - const int32_t x1 = ((((qs[i] >> 8) & mask) >> x_mv) | xh_1); - - reg_f[i/2][2*(i%2) + 0] = d * x0 + md; - reg_f[i/2][2*(i%2) + 1] = d * x1 + md; - } - - reg = (type4x4) reg_f; -} - -template -void dequantize_q5_0_t4(device const block_q5_0 * xb, short il, thread type4 & reg) { - device const uint16_t * qs = ((device const uint16_t *)xb + 3); - const float d = xb->d; - const float md = -16.h * xb->d; - const ushort mask = (il/4) ? 0x00F0 : 0x000F; - - const uint32_t qh = *((device const uint32_t *)xb->qh); - - const int x_mv = (il/4) ? 4 : 0; - - const int gh_mv = (il/4) ? 12 : 0; - const int gh_bk = (il/4) ? 0 : 4; - - for (int ii = 0; ii < 2; ii++) { - int i = 2*(il%4) + ii; - - // extract the 5-th bits for x0 and x1 - const uint8_t xh_0 = ((qh >> (gh_mv + 2*i )) << gh_bk) & 0x10; - const uint8_t xh_1 = ((qh >> (gh_mv + 2*i+1)) << gh_bk) & 0x10; - - // combine the 4-bits from qs with the 5th bit - const int32_t x0 = ((((qs[i] ) & mask) >> x_mv) | xh_0); - const int32_t x1 = ((((qs[i] >> 8) & mask) >> x_mv) | xh_1); - - reg[2*ii + 0] = d * x0 + md; - reg[2*ii + 1] = d * x1 + md; - } -} - -template -void dequantize_q5_1(device const block_q5_1 * xb, short il, thread type4x4 & reg) { - device const uint16_t * qs = ((device const uint16_t *)xb + 4); - const float d = xb->d; - const float m = xb->m; - const ushort mask = il ? 0x00F0 : 0x000F; - - const uint32_t qh = *((device const uint32_t *)xb->qh); - - const int x_mv = il ? 4 : 0; - - const int gh_mv = il ? 12 : 0; - const int gh_bk = il ? 0 : 4; - - float4x4 reg_f; - - for (int i = 0; i < 8; i++) { - // extract the 5-th bits for x0 and x1 - const uint8_t xh_0 = ((qh >> (gh_mv + 2*i )) << gh_bk) & 0x10; - const uint8_t xh_1 = ((qh >> (gh_mv + 2*i+1)) << gh_bk) & 0x10; - - // combine the 4-bits from qs with the 5th bit - const int32_t x0 = ((((qs[i] ) & mask) >> x_mv) | xh_0); - const int32_t x1 = ((((qs[i] >> 8) & mask) >> x_mv) | xh_1); - - reg_f[i/2][2*(i%2) + 0] = d * x0 + m; - reg_f[i/2][2*(i%2) + 1] = d * x1 + m; - } - - reg = (type4x4) reg_f; -} - -template -void dequantize_q5_1_t4(device const block_q5_1 * xb, short il, thread type4 & reg) { - device const uint16_t * qs = ((device const uint16_t *)xb + 4); - const float d = xb->d; - const float m = xb->m; - const ushort mask = (il/4) ? 0x00F0 : 0x000F; - - const uint32_t qh = *((device const uint32_t *)xb->qh); - - const int x_mv = (il/4) ? 4 : 0; - - const int gh_mv = (il/4) ? 12 : 0; - const int gh_bk = (il/4) ? 0 : 4; - - for (int ii = 0; ii < 2; ii++) { - int i = 2*(il%4) + ii; - - // extract the 5-th bits for x0 and x1 - const uint8_t xh_0 = ((qh >> (gh_mv + 2*i )) << gh_bk) & 0x10; - const uint8_t xh_1 = ((qh >> (gh_mv + 2*i+1)) << gh_bk) & 0x10; - - // combine the 4-bits from qs with the 5th bit - const int32_t x0 = ((((qs[i] ) & mask) >> x_mv) | xh_0); - const int32_t x1 = ((((qs[i] >> 8) & mask) >> x_mv) | xh_1); - - reg[2*ii + 0] = d * x0 + m; - reg[2*ii + 1] = d * x1 + m; - } -} - -template -void dequantize_q8_0(device const block_q8_0 *xb, short il, thread type4x4 & reg) { - device const int8_t * qs = ((device const int8_t *)xb->qs); - const float d = xb->d; - - float4x4 reg_f; - - for (int i = 0; i < 16; i++) { - reg_f[i/4][i%4] = (qs[i + 16*il] * d); - } - - reg = (type4x4) reg_f; -} - -template -void dequantize_q8_0_t4(device const block_q8_0 *xb, short il, thread type4 & reg) { - device const int8_t * qs = ((device const int8_t *)xb->qs); - const float d = xb->d; - - for (int i = 0; i < 4; i++) { - reg[i] = (qs[4*(il%4) + i + 16*(il/4)] * d); - } -} - -template -void dequantize_mxfp4(device const block_mxfp4 * xb, short il, thread type4x4 & reg) { - device const uint8_t * q2 = (device const uint8_t *)xb->qs; - - const float d = e8m0_to_fp32(xb->e); - const uint8_t shr = il >= 1 ? 4 : 0; - - for (int i = 0; i < 4; ++i) { - reg[i][0] = d * kvalues_mxfp4_f[(q2[4*i + 0] >> shr) & 0x0F]; - reg[i][1] = d * kvalues_mxfp4_f[(q2[4*i + 1] >> shr) & 0x0F]; - reg[i][2] = d * kvalues_mxfp4_f[(q2[4*i + 2] >> shr) & 0x0F]; - reg[i][3] = d * kvalues_mxfp4_f[(q2[4*i + 3] >> shr) & 0x0F]; - } -} - -template -void dequantize_mxfp4_t4(device const block_mxfp4 * xb, short il, thread type4 & reg) { - device const uint8_t * q2 = (device const uint8_t *)xb->qs; - - const float d = e8m0_to_fp32(xb->e); - const short il4 = il%4; - - const uint8_t shr = il >= 4 ? 4 : 0; - - reg[0] = d * kvalues_mxfp4_f[(q2[4*il4 + 0] >> shr) & 0x0F]; - reg[1] = d * kvalues_mxfp4_f[(q2[4*il4 + 1] >> shr) & 0x0F]; - reg[2] = d * kvalues_mxfp4_f[(q2[4*il4 + 2] >> shr) & 0x0F]; - reg[3] = d * kvalues_mxfp4_f[(q2[4*il4 + 3] >> shr) & 0x0F]; -} - -template -void dequantize_q2_K(device const block_q2_K *xb, short il, thread type4x4 & reg) { - const float d = xb->d; - const float min = xb->dmin; - device const uint8_t * q = (device const uint8_t *)xb->qs; - float dl, ml; - uint8_t sc = xb->scales[il]; - - q = q + 32*(il/8) + 16*(il&1); - il = (il/2)%4; - - half coef = il>1 ? (il>2 ? 1/64.h : 1/16.h) : (il>0 ? 1/4.h : 1.h); - uchar mask = il>1 ? (il>2 ? 192 : 48) : (il>0 ? 12 : 3); - dl = d * (sc & 0xF) * coef, ml = min * (sc >> 4); - for (int i = 0; i < 16; ++i) { - reg[i/4][i%4] = dl * (q[i] & mask) - ml; - } -} - -template -void dequantize_q3_K(device const block_q3_K *xb, short il, thread type4x4 & reg) { - const half d_all = xb->d; - device const uint8_t * q = (device const uint8_t *)xb->qs; - device const uint8_t * h = (device const uint8_t *)xb->hmask; - device const int8_t * scales = (device const int8_t *)xb->scales; - - q = q + 32 * (il/8) + 16 * (il&1); - h = h + 16 * (il&1); - uint8_t m = 1 << (il/2); - uint16_t kmask1 = (il/4)>1 ? ((il/4)>2 ? 192 : 48) : \ - ((il/4)>0 ? 12 : 3); - uint16_t kmask2 = il/8 ? 0xF0 : 0x0F; - uint16_t scale_2 = scales[il%8], scale_1 = scales[8 + il%4]; - int16_t dl_int = (il/4)&1 ? (scale_2&kmask2) | ((scale_1&kmask1) << 2) - : (scale_2&kmask2) | ((scale_1&kmask1) << 4); - float dl = il<8 ? d_all * (dl_int - 32.f) : d_all * (dl_int / 16.f - 32.f); - const float ml = 4.f * dl; - - il = (il/2) & 3; - const half coef = il>1 ? (il>2 ? 1/64.h : 1/16.h) : (il>0 ? 1/4.h : 1.h); - const uint8_t mask = il>1 ? (il>2 ? 192 : 48) : (il>0 ? 12 : 3); - dl *= coef; - - for (int i = 0; i < 16; ++i) { - reg[i/4][i%4] = dl * (q[i] & mask) - (h[i] & m ? 0 : ml); - } -} - -static inline uchar2 get_scale_min_k4_just2(int j, int k, device const uchar * q) { - return j < 4 ? uchar2{uchar(q[j+0+k] & 63), uchar(q[j+4+k] & 63)} - : uchar2{uchar((q[j+4+k] & 0xF) | ((q[j-4+k] & 0xc0) >> 2)), uchar((q[j+4+k] >> 4) | ((q[j-0+k] & 0xc0) >> 2))}; -} - -template -void dequantize_q4_K(device const block_q4_K * xb, short il, thread type4x4 & reg) { - device const uchar * q = xb->qs; - - short is = (il/4) * 2; - q = q + (il/4) * 32 + 16 * (il&1); - il = il & 3; - const uchar2 sc = get_scale_min_k4_just2(is, il/2, xb->scales); - const float d = il < 2 ? xb->d : xb->d / 16.h; - const float min = xb->dmin; - const float dl = d * sc[0]; - const float ml = min * sc[1]; - - const ushort mask = il < 2 ? 0x0F : 0xF0; - for (int i = 0; i < 16; ++i) { - reg[i/4][i%4] = dl * (q[i] & mask) - ml; - } -} - -template -void dequantize_q5_K(device const block_q5_K *xb, short il, thread type4x4 & reg) { - device const uint8_t * q = xb->qs; - device const uint8_t * qh = xb->qh; - - short is = (il/4) * 2; - q = q + 32 * (il/4) + 16 * (il&1); - qh = qh + 16 * (il&1); - uint8_t ul = 1 << (il/2); - il = il & 3; - const uchar2 sc = get_scale_min_k4_just2(is, il/2, xb->scales); - const float d = il < 2 ? xb->d : xb->d / 16.f; - const float min = xb->dmin; - const float dl = d * sc[0]; - const float ml = min * sc[1]; - - const ushort mask = il<2 ? 0x0F : 0xF0; - const float qh_val = il<2 ? 16.f : 256.f; - for (int i = 0; i < 16; ++i) { - reg[i/4][i%4] = dl * ((q[i] & mask) + (qh[i] & ul ? qh_val : 0)) - ml; - } -} - -template -void dequantize_q6_K(device const block_q6_K *xb, short il, thread type4x4 & reg) { - const half d_all = xb->d; - device const uint16_t * ql = (device const uint16_t *)xb->ql; - device const uint16_t * qh = (device const uint16_t *)xb->qh; - device const int8_t * scales = (device const int8_t *)xb->scales; - - ql = ql + 32*(il/8) + 16*((il/2)&1) + 8*(il&1); - qh = qh + 16*(il/8) + 8*(il&1); - float sc = scales[(il%2) + 2 * ((il/2))]; - il = (il/2) & 3; - - const uint32_t kmask1 = il>1 ? (il>2 ? 0xC0C0C0C0 : 0x30303030) : (il>0 ? 0x0C0C0C0C : 0x03030303); - const uint32_t kmask2 = il>1 ? 0xF0F0F0F0 : 0x0F0F0F0F; - const float ml = d_all * sc * 32.f; - const float dl0 = d_all * sc; - const float dl1 = dl0 / 256.f; - const float dl2 = dl0 / (256.f * 256.f); - const float dl3 = dl0 / (256.f * 256.f * 256.f); - const uint8_t shr_h = il>2 ? 2 : 0; - const uint8_t shl_h = il>1 ? 0 : (il>0 ? 2 : 4); - const uint8_t shr_l = il>1 ? 4 : 0; - for (int i = 0; i < 4; ++i) { - const uint32_t low = (ql[2*i] | (uint32_t)(ql[2*i+1] << 16)) & kmask2; - const uint32_t high = (qh[2*i] | (uint32_t)(qh[2*i+1] << 16)) & kmask1; - const uint32_t q = ((high << shl_h) >> shr_h) | (low >> shr_l); - reg[i][0] = dl0 * ((half)(q & 0xFF)) - ml; - reg[i][1] = dl1 * ((float)(q & 0xFF00)) - ml; - reg[i][2] = dl2 * ((float)(q & 0xFF0000)) - ml; - reg[i][3] = dl3 * ((float)(q & 0xFF000000)) - ml; - } -} - -template -void dequantize_iq2_xxs(device const block_iq2_xxs * xb, short il, thread type4x4 & reg) { - // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 - const float d = xb->d; - const int ib32 = il/2; - il = il%2; - // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 - // each block of 32 needs 2 uint32_t's for the quants & scale, so 4 uint16_t's. - device const uint16_t * q2 = xb->qs + 4*ib32; - const uint32_t aux32_g = q2[0] | (q2[1] << 16); - const uint32_t aux32_s = q2[2] | (q2[3] << 16); - thread const uint8_t * aux8 = (thread const uint8_t *)&aux32_g; - const float dl = d * (0.5f + (aux32_s >> 28)) * 0.25f; - constant uint8_t * grid = (constant uint8_t *)(iq2xxs_grid + aux8[2*il+0]); - uint8_t signs = ksigns_iq2xs[(aux32_s >> 14*il) & 127]; - for (int i = 0; i < 8; ++i) { - reg[i/4][i%4] = dl * grid[i] * (signs & kmask_iq2xs[i] ? -1.f : 1.f); - } - grid = (constant uint8_t *)(iq2xxs_grid + aux8[2*il+1]); - signs = ksigns_iq2xs[(aux32_s >> (14*il+7)) & 127]; - for (int i = 0; i < 8; ++i) { - reg[2+i/4][i%4] = dl * grid[i] * (signs & kmask_iq2xs[i] ? -1.f : 1.f); - } -} - -template -void dequantize_iq2_xs(device const block_iq2_xs * xb, short il, thread type4x4 & reg) { - // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 - const float d = xb->d; - const int ib32 = il/2; - il = il%2; - // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 - device const uint16_t * q2 = xb->qs + 4*ib32; - const float dl = d * (0.5f + ((xb->scales[ib32] >> 4*il) & 0xf)) * 0.25f; - constant uint8_t * grid = (constant uint8_t *)(iq2xs_grid + (q2[2*il+0] & 511)); - uint8_t signs = ksigns_iq2xs[q2[2*il+0] >> 9]; - for (int i = 0; i < 8; ++i) { - reg[i/4][i%4] = dl * grid[i] * (signs & kmask_iq2xs[i] ? -1.f : 1.f); - } - grid = (constant uint8_t *)(iq2xs_grid + (q2[2*il+1] & 511)); - signs = ksigns_iq2xs[q2[2*il+1] >> 9]; - for (int i = 0; i < 8; ++i) { - reg[2+i/4][i%4] = dl * grid[i] * (signs & kmask_iq2xs[i] ? -1.f : 1.f); - } -} - -template -void dequantize_iq3_xxs(device const block_iq3_xxs * xb, short il, thread type4x4 & reg) { - // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 - const float d = xb->d; - const int ib32 = il/2; - il = il%2; - // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 - device const uint8_t * q3 = xb->qs + 8*ib32; - device const uint16_t * gas = (device const uint16_t *)(xb->qs + QK_K/4) + 2*ib32; - const uint32_t aux32 = gas[0] | (gas[1] << 16); - const float dl = d * (0.5f + (aux32 >> 28)) * 0.5f; - constant uint8_t * grid1 = (constant uint8_t *)(iq3xxs_grid + q3[4*il+0]); - constant uint8_t * grid2 = (constant uint8_t *)(iq3xxs_grid + q3[4*il+1]); - uint8_t signs = ksigns_iq2xs[(aux32 >> 14*il) & 127]; - for (int i = 0; i < 4; ++i) { - reg[0][i] = dl * grid1[i] * (signs & kmask_iq2xs[i+0] ? -1.f : 1.f); - reg[1][i] = dl * grid2[i] * (signs & kmask_iq2xs[i+4] ? -1.f : 1.f); - } - grid1 = (constant uint8_t *)(iq3xxs_grid + q3[4*il+2]); - grid2 = (constant uint8_t *)(iq3xxs_grid + q3[4*il+3]); - signs = ksigns_iq2xs[(aux32 >> (14*il+7)) & 127]; - for (int i = 0; i < 4; ++i) { - reg[2][i] = dl * grid1[i] * (signs & kmask_iq2xs[i+0] ? -1.f : 1.f); - reg[3][i] = dl * grid2[i] * (signs & kmask_iq2xs[i+4] ? -1.f : 1.f); - } -} - -template -void dequantize_iq3_s(device const block_iq3_s * xb, short il, thread type4x4 & reg) { - // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 - const float d = xb->d; - const int ib32 = il/2; - il = il%2; - // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 - device const uint8_t * qs = xb->qs + 8*ib32; - device const uint8_t * signs = xb->signs + 4*ib32 + 2*il; - const uint8_t qh = xb->qh[ib32] >> 4*il; - const float dl = d * (1 + 2*((xb->scales[ib32/2] >> 4*(ib32%2)) & 0xf)); - constant uint8_t * grid1 = (constant uint8_t *)(iq3s_grid + (qs[4*il+0] | ((qh << 8) & 256))); - constant uint8_t * grid2 = (constant uint8_t *)(iq3s_grid + (qs[4*il+1] | ((qh << 7) & 256))); - for (int i = 0; i < 4; ++i) { - reg[0][i] = dl * grid1[i] * select(1, -1, signs[0] & kmask_iq2xs[i+0]); - reg[1][i] = dl * grid2[i] * select(1, -1, signs[0] & kmask_iq2xs[i+4]); - } - grid1 = (constant uint8_t *)(iq3s_grid + (qs[4*il+2] | ((qh << 6) & 256))); - grid2 = (constant uint8_t *)(iq3s_grid + (qs[4*il+3] | ((qh << 5) & 256))); - for (int i = 0; i < 4; ++i) { - reg[2][i] = dl * grid1[i] * select(1, -1, signs[1] & kmask_iq2xs[i+0]); - reg[3][i] = dl * grid2[i] * select(1, -1, signs[1] & kmask_iq2xs[i+4]); - } -} - -template -void dequantize_iq2_s(device const block_iq2_s * xb, short il, thread type4x4 & reg) { - // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 - const float d = xb->d; - const int ib32 = il/2; - il = il%2; - // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 - device const uint8_t * qs = xb->qs + 4*ib32 + 2*il; - device const uint8_t * signs = qs + QK_K/8; - const uint8_t qh = xb->qh[ib32] >> 4*il; - const float dl = d * (0.5f + ((xb->scales[ib32] >> 4*il) & 0xf)) * 0.25f; - constant uint8_t * grid1 = (constant uint8_t *)(iq2s_grid + (qs[0] | ((qh << 8) & 0x300))); - constant uint8_t * grid2 = (constant uint8_t *)(iq2s_grid + (qs[1] | ((qh << 6) & 0x300))); - for (int i = 0; i < 8; ++i) { - reg[i/4+0][i%4] = dl * grid1[i] * select(1, -1, signs[0] & kmask_iq2xs[i]); - reg[i/4+2][i%4] = dl * grid2[i] * select(1, -1, signs[1] & kmask_iq2xs[i]); - } -} - -template -void dequantize_iq1_s(device const block_iq1_s * xb, short il, thread type4x4 & reg) { - // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 - const int ib32 = il/2; - il = il%2; - const float d = xb->d; - device const uint8_t * qs = xb->qs + 4*ib32 + 2*il; - device const uint16_t * qh = xb->qh; - const float dl = d * (2*((qh[ib32] >> 12) & 7) + 1); - const float ml = dl * (qh[ib32] & 0x8000 ? -1 - IQ1S_DELTA : -1 + IQ1S_DELTA); - const uint16_t h = qh[ib32] >> 6*il; - constant uint8_t * grid1 = (constant uint8_t *)(iq1s_grid_gpu + (qs[0] | ((h << 8) & 0x700))); - constant uint8_t * grid2 = (constant uint8_t *)(iq1s_grid_gpu + (qs[1] | ((h << 5) & 0x700))); - for (int i = 0; i < 4; ++i) { - reg[0][i] = dl * (grid1[i] & 0xf) + ml; - reg[1][i] = dl * (grid1[i] >> 4) + ml; - reg[2][i] = dl * (grid2[i] & 0xf) + ml; - reg[3][i] = dl * (grid2[i] >> 4) + ml; - } -} - -template -void dequantize_iq1_m(device const block_iq1_m * xb, short il, thread type4x4 & reg) { - // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 - const int ib32 = il/2; - il = il%2; - device const uint16_t * sc = (device const uint16_t *)xb->scales; - - iq1m_scale_t scale; - scale.u16 = (sc[0] >> 12) | ((sc[1] >> 8) & 0x00f0) | ((sc[2] >> 4) & 0x0f00) | (sc[3] & 0xf000); - const float d = scale.f16; - - device const uint8_t * qs = xb->qs + 4*ib32 + 2*il; - device const uint8_t * qh = xb->qh + 2*ib32 + il; - - const float dl = d * (2*((sc[ib32/2] >> (6*(ib32%2)+3*il)) & 7) + 1); - const float ml1 = dl * (qh[0] & 0x08 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA); - const float ml2 = dl * (qh[0] & 0x80 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA); - constant uint8_t * grid1 = (constant uint8_t *)(iq1s_grid_gpu + (qs[0] | ((qh[0] << 8) & 0x700))); - constant uint8_t * grid2 = (constant uint8_t *)(iq1s_grid_gpu + (qs[1] | ((qh[0] << 4) & 0x700))); - for (int i = 0; i < 4; ++i) { - reg[0][i] = dl * (grid1[i] & 0xf) + ml1; - reg[1][i] = dl * (grid1[i] >> 4) + ml1; - reg[2][i] = dl * (grid2[i] & 0xf) + ml2; - reg[3][i] = dl * (grid2[i] >> 4) + ml2; - } -} - -template -void dequantize_iq4_nl(device const block_iq4_nl * xb, short il, thread type4x4 & reg) { - device const uint16_t * q4 = (device const uint16_t *)xb->qs; - const float d = xb->d; - uint32_t aux32; - thread const uint8_t * q8 = (thread const uint8_t *)&aux32; - for (int i = 0; i < 4; ++i) { - aux32 = ((q4[2*i] | (q4[2*i+1] << 16)) >> 4*il) & 0x0f0f0f0f; - reg[i][0] = d * kvalues_iq4nl_f[q8[0]]; - reg[i][1] = d * kvalues_iq4nl_f[q8[1]]; - reg[i][2] = d * kvalues_iq4nl_f[q8[2]]; - reg[i][3] = d * kvalues_iq4nl_f[q8[3]]; - } -} - -template -void dequantize_iq4_nl_t4(device const block_iq4_nl * xb, short il, thread type4 & reg) { - device const uint16_t * q4 = (device const uint16_t *)xb->qs; - const float d = xb->d; - uint32_t aux32; - thread const uint8_t * q8 = (thread const uint8_t *)&aux32; - aux32 = ((q4[2*(il%4)] | (q4[2*(il%4)+1] << 16)) >> 4*(il/4)) & 0x0f0f0f0f; - reg[0] = d * kvalues_iq4nl_f[q8[0]]; - reg[1] = d * kvalues_iq4nl_f[q8[1]]; - reg[2] = d * kvalues_iq4nl_f[q8[2]]; - reg[3] = d * kvalues_iq4nl_f[q8[3]]; -} - -template -void dequantize_iq4_xs(device const block_iq4_xs * xb, short il, thread type4x4 & reg) { - // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 - const int ib32 = il/2; - il = il%2; - // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 - device const uint32_t * q4 = (device const uint32_t *)xb->qs + 4*ib32; - const int ls = ((xb->scales_l[ib32/2] >> 4*(ib32%2)) & 0xf) | (((xb->scales_h >> 2*ib32) & 3) << 4); - const float d = (float)xb->d * (ls - 32); - uint32_t aux32; - thread const uint8_t * q8 = (thread const uint8_t *)&aux32; - for (int i = 0; i < 4; ++i) { - aux32 = (q4[i] >> 4*il) & 0x0f0f0f0f; - reg[i][0] = d * kvalues_iq4nl_f[q8[0]]; - reg[i][1] = d * kvalues_iq4nl_f[q8[1]]; - reg[i][2] = d * kvalues_iq4nl_f[q8[2]]; - reg[i][3] = d * kvalues_iq4nl_f[q8[3]]; - } -} - -enum ggml_sort_order { - GGML_SORT_ORDER_ASC, - GGML_SORT_ORDER_DESC, -}; - -constant float GELU_COEF_A = 0.044715f; -constant float GELU_QUICK_COEF = -1.702f; -constant float SQRT_2_OVER_PI = 0.79788456080286535587989211986876f; -constant float SQRT_2_INV = 0.70710678118654752440084436210484f; - -// based on Abramowitz and Stegun formula 7.1.26 or similar Hastings' approximation -// ref: https://www.johndcook.com/blog/python_erf/ -constant float p_erf = 0.3275911f; -constant float a1_erf = 0.254829592f; -constant float a2_erf = -0.284496736f; -constant float a3_erf = 1.421413741f; -constant float a4_erf = -1.453152027f; -constant float a5_erf = 1.061405429f; - -template -inline T erf_approx(T x) { - T sign_x = sign(x); - x = fabs(x); - T t = 1.0f / (1.0f + p_erf * x); - T y = 1.0f - (((((a5_erf * t + a4_erf) * t) + a3_erf) * t + a2_erf) * t + a1_erf) * t * exp(-x * x); - return sign_x * y; -} - -template T elu_approx(T x); - -template<> inline float elu_approx(float x) { - return (x > 0.f) ? x : (exp(x) - 1); -} - -template<> inline float4 elu_approx(float4 x) { - float4 res; - - res[0] = (x[0] > 0.0f) ? x[0] : (exp(x[0]) - 1.0f); - res[1] = (x[1] > 0.0f) ? x[1] : (exp(x[1]) - 1.0f); - res[2] = (x[2] > 0.0f) ? x[2] : (exp(x[2]) - 1.0f); - res[3] = (x[3] > 0.0f) ? x[3] : (exp(x[3]) - 1.0f); - - return res; -} - -constant short FC_unary_op [[function_constant(FC_UNARY + 0)]]; -constant bool FC_unary_cnt[[function_constant(FC_UNARY + 1)]]; - -template -kernel void kernel_unary_impl( - constant ggml_metal_kargs_unary & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { -#define FC_OP FC_unary_op -#define FC_CNT FC_unary_cnt - - device const T0 * src0_ptr; - device T * dst_ptr; - - int i0; - - if (FC_CNT) { - i0 = tgpig.x; - - src0_ptr = (device const T0 *) (src0); - dst_ptr = (device T *) (dst); - } else { - const int i03 = tgpig.z; - const int i02 = tgpig.y; - const int k0 = tgpig.x/args.ne01; - const int i01 = tgpig.x - k0*args.ne01; - - i0 = k0*ntg.x + tpitg.x; - - src0_ptr = (device const T0 *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); - dst_ptr = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1 ); - } - - { - //threadgroup_barrier(mem_flags::mem_none); - - if (!FC_CNT) { - if (i0 >= args.ne0) { - return; - } - } - - const TC x = (TC) src0_ptr[i0]; - - if (FC_OP == OP_UNARY_NUM_SCALE) { - dst_ptr[i0] = (T) (args.scale * x + args.bias); - } - - if (FC_OP == OP_UNARY_NUM_FILL) { - dst_ptr[i0] = (T) args.val; - } - - if (FC_OP == OP_UNARY_NUM_CLAMP) { - dst_ptr[i0] = (T) clamp(x, args.min, args.max); - } - - if (FC_OP == OP_UNARY_NUM_SQR) { - dst_ptr[i0] = (T) (x * x); - } - - if (FC_OP == OP_UNARY_NUM_SQRT) { - dst_ptr[i0] = (T) sqrt(x); - } - - if (FC_OP == OP_UNARY_NUM_SIN) { - dst_ptr[i0] = (T) sin(x); - } - - if (FC_OP == OP_UNARY_NUM_COS) { - dst_ptr[i0] = (T) cos(x); - } - - if (FC_OP == OP_UNARY_NUM_LOG) { - dst_ptr[i0] = (T) log(x); - } - - if (FC_OP == OP_UNARY_NUM_LEAKY_RELU) { - dst_ptr[i0] = (T) (TC(x > 0)*x + TC(x <= 0)*(x * args.slope)); - } - - if (FC_OP == OP_UNARY_NUM_TANH) { - dst_ptr[i0] = (T) precise::tanh(x); - } - - if (FC_OP == OP_UNARY_NUM_RELU) { - dst_ptr[i0] = (T) fmax(0, x); - } - - if (FC_OP == OP_UNARY_NUM_SIGMOID) { - dst_ptr[i0] = (T) (1 / (1 + exp(-x))); - } - - if (FC_OP == OP_UNARY_NUM_GELU) { - dst_ptr[i0] = (T) (0.5*x*(1 + precise::tanh(SQRT_2_OVER_PI*x*(1 + GELU_COEF_A*x*x)))); - } - - if (FC_OP == OP_UNARY_NUM_GELU_ERF) { - dst_ptr[i0] = (T) (0.5*x*(1 + erf_approx(SQRT_2_INV*x))); - } - - if (FC_OP == OP_UNARY_NUM_GELU_QUICK) { - dst_ptr[i0] = (T) (x * (1/(1 + exp(GELU_QUICK_COEF*x)))); - } - - if (FC_OP == OP_UNARY_NUM_SILU) { - dst_ptr[i0] = (T) (x / (1 + exp(-x))); - } - - if (FC_OP == OP_UNARY_NUM_ELU) { - dst_ptr[i0] = (T) elu_approx(x); - } - - if (FC_OP == OP_UNARY_NUM_NEG) { - dst_ptr[i0] = (T) -x; - } - - if (FC_OP == OP_UNARY_NUM_ABS) { - dst_ptr[i0] = (T) fabs(x); - } - - if (FC_OP == OP_UNARY_NUM_SGN) { - dst_ptr[i0] = T(x > 0) - T(x < 0); - } - - if (FC_OP == OP_UNARY_NUM_STEP) { - dst_ptr[i0] = T(x > 0); - } - - if (FC_OP == OP_UNARY_NUM_HARDSWISH) { - dst_ptr[i0] = (T) (x * fmax(0, fmin(1, x/6 + 0.5))); - } - - if (FC_OP == OP_UNARY_NUM_HARDSIGMOID) { - dst_ptr[i0] = (T) fmax(0, fmin(1, x/6 + 0.5)); - } - - if (FC_OP == OP_UNARY_NUM_EXP) { - dst_ptr[i0] = (T) exp(x); - } - - if (FC_OP == OP_UNARY_NUM_SOFTPLUS) { - dst_ptr[i0] = (T) select(log(1 + exp(x)), x, x > 20); - } - - if (FC_OP == OP_UNARY_NUM_EXPM1) { - // TODO: precise implementation - dst_ptr[i0] = (T) (exp(x) - 1); - } - - if (FC_OP == OP_UNARY_NUM_FLOOR) { - dst_ptr[i0] = (T) floor(x); - } - - if (FC_OP == OP_UNARY_NUM_CEIL) { - dst_ptr[i0] = (T) ceil(x); - } - - if (FC_OP == OP_UNARY_NUM_ROUND) { - dst_ptr[i0] = (T) round(x); - } - - if (FC_OP == OP_UNARY_NUM_TRUNC) { - dst_ptr[i0] = (T) trunc(x); - } - - if (FC_OP == OP_UNARY_NUM_XIELU) { - const TC xi = x; - const TC gate = TC(xi > TC(0.0f)); - const TC clamped = fmin(xi, TC(args.val)); - const TC y_pos = TC(args.scale) * xi * xi + TC(args.bias) * xi; - const TC y_neg = (exp(clamped) - TC(1.0f) - xi) * TC(args.slope) + TC(args.bias) * xi; - dst_ptr[i0] = (T) (gate * y_pos + (TC(1.0f) - gate) * y_neg); - } - } - -#undef FC_OP -#undef FC_CNT -} - -typedef decltype(kernel_unary_impl) kernel_unary_t; - -template [[host_name("kernel_unary_f32_f32")]] kernel kernel_unary_t kernel_unary_impl; -template [[host_name("kernel_unary_f32_f32_4")]] kernel kernel_unary_t kernel_unary_impl; -template [[host_name("kernel_unary_f16_f16")]] kernel kernel_unary_t kernel_unary_impl; -template [[host_name("kernel_unary_f16_f16_4")]] kernel kernel_unary_t kernel_unary_impl; - -// OP: 0 - add, 1 - sub, 2 - mul, 3 - div -constant short FC_bin_op [[function_constant(FC_BIN + 0)]]; -constant short FC_bin_f [[function_constant(FC_BIN + 1)]]; -constant bool FC_bin_rb [[function_constant(FC_BIN + 2)]]; -constant bool FC_bin_cb [[function_constant(FC_BIN + 3)]]; - -template -kernel void kernel_bin_fuse_impl( - constant ggml_metal_kargs_bin & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { -#define FC_OP FC_bin_op -#define FC_F FC_bin_f -#define FC_RB FC_bin_rb -#define FC_CB FC_bin_cb - - if (FC_RB) { - // row broadcast - const uint i0 = tgpig.y*args.ne00 + tgpig.x; - const uint i1 = FC_CB ? tgpig.x%args.ne10 : tgpig.x; - - device const T0 * src0_row = (device const T0 *) (src0); - device T * dst_row = (device T *) (dst); - - if (FC_F == 1) { - device const T1 * src1_row = (device const T1 *) (src1 + args.o1[0]); - - if (FC_OP == 0) { - dst_row[i0] = src0_row[i0] + src1_row[i1]; - } - - if (FC_OP == 1) { - dst_row[i0] = src0_row[i0] - src1_row[i1]; - } - - if (FC_OP == 2) { - dst_row[i0] = src0_row[i0] * src1_row[i1]; - } - - if (FC_OP == 3) { - dst_row[i0] = src0_row[i0] / src1_row[i1]; - } - } else { - T0 res = src0_row[i0]; - - if (FC_OP == 0) { - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - res += ((device const T1 *) (src1 + args.o1[j]))[i1]; - } - } - - if (FC_OP == 1) { - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - res -= ((device const T1 *) (src1 + args.o1[j]))[i1]; - } - } - - if (FC_OP == 2) { - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - res *= ((device const T1 *) (src1 + args.o1[j]))[i1]; - } - } - - if (FC_OP == 3) { - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - res /= ((device const T1 *) (src1 + args.o1[j]))[i1]; - } - } - - dst_row[i0] = res; - } - } else { - const int i03 = tgpig.z; - const int i02 = tgpig.y; - const int i01 = tgpig.x; - - if (i01 >= args.ne01) { - return; - } - - const int i13 = i03%args.ne13; - const int i12 = i02%args.ne12; - const int i11 = i01%args.ne11; - - device const T0 * src0_ptr = (device const T0 *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + args.offs); - device T * dst_ptr = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1 + args.offs); - - if (FC_F == 1) { - device const T1 * src1_ptr = (device const T1 *) (src1 + args.o1[0] + i13*args.nb13 + i12*args.nb12 + i11*args.nb11); - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const int i10 = FC_CB ? i0%args.ne10 : i0; - - if (FC_OP == 0) { - dst_ptr[i0] = src0_ptr[i0] + src1_ptr[i10]; - } - - if (FC_OP == 1) { - dst_ptr[i0] = src0_ptr[i0] - src1_ptr[i10]; - } - - if (FC_OP == 2) { - dst_ptr[i0] = src0_ptr[i0] * src1_ptr[i10]; - } - - if (FC_OP == 3) { - dst_ptr[i0] = src0_ptr[i0] / src1_ptr[i10]; - } - } - } else { - device const T1 * src1_ptr[8]; - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - src1_ptr[j] = (device const T1 *) (src1 + args.o1[j] + i13*args.nb13 + i12*args.nb12 + i11*args.nb11); - } - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const int i10 = FC_CB ? i0%args.ne10 : i0; - - T res = src0_ptr[i0]; - - if (FC_OP == 0) { - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - res += src1_ptr[j][i10]; - } - } - - if (FC_OP == 1) { - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - res -= src1_ptr[j][i10]; - } - } - - if (FC_OP == 2) { - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - res *= src1_ptr[j][i10]; - } - } - - if (FC_OP == 3) { - FOR_UNROLL (short j = 0; j < FC_F; ++j) { - res /= src1_ptr[j][i10]; - } - } - - dst_ptr[i0] = res; - } - } - } - -#undef FC_OP -#undef FC_F -#undef FC_RB -#undef FC_CB -} - -typedef decltype(kernel_bin_fuse_impl) kernel_bin_fuse_t; +#define GGML_COMMON_DECL_METAL +#define GGML_COMMON_IMPL_METAL +#if defined(GGML_METAL_EMBED_LIBRARY) +__embed_ggml-common.h__ +#else +#include "ggml-common.h" +#endif +#include "ggml-metal-impl.h" -template [[host_name("kernel_bin_fuse_f32_f32_f32")]] kernel kernel_bin_fuse_t kernel_bin_fuse_impl; -template [[host_name("kernel_bin_fuse_f32_f32_f32_4")]] kernel kernel_bin_fuse_t kernel_bin_fuse_impl; +#include -template -kernel void kernel_bin_bcast_impl( - constant ggml_metal_kargs_bin & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int i0 = tgpig.x*ntg.x + tpitg.x; - const int i1 = tgpig.y; - const int i2 = tgpig.z % args.ne2; - const int i3 = tgpig.z / args.ne2; +#ifdef GGML_METAL_HAS_TENSOR +#include - if (i0 >= args.ne0) { - return; - } +#include +#endif - const int i00 = i0 % args.ne00; - const int i01 = i1 % args.ne01; - const int i02 = i2 % args.ne02; - const int i03 = i3 % args.ne03; +using namespace metal; - const int i10 = i0 % args.ne10; - const int i11 = i1 % args.ne11; - const int i12 = i2 % args.ne12; - const int i13 = i3 % args.ne13; +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define MIN(x, y) ((x) < (y) ? (x) : (y)) +#define SWAP(x, y) { auto tmp = (x); (x) = (y); (y) = tmp; } - device const T * src0_ptr = (device const T *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + i00*args.nb00); - device const T * src1_ptr = (device const T *) (src1 + i13*args.nb13 + i12*args.nb12 + i11*args.nb11 + i10*args.nb10); - device T * dst_ptr = (device T *) (dst + i3 *args.nb3 + i2 *args.nb2 + i1 *args.nb1 + i0 *args.nb0); +#define PAD2(x, n) (((x) + (n) - 1) & ~((n) - 1)) - if (FC_bin_op == 0) { - *dst_ptr = *src0_ptr + *src1_ptr; - } +#define FOR_UNROLL(x) _Pragma("clang loop unroll(full)") for (x) - if (FC_bin_op == 1) { - *dst_ptr = *src0_ptr - *src1_ptr; +#define N_SIMDWIDTH 32 // assuming SIMD group size is 32 + +// ref: https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf +// +// cmd: +// .../usr/bin/metal -dM -E -c ggml/src/ggml-metal/ggml-metal.metal +// .../usr/bin/metal -dM -E -c -target air64-apple-ios14.0 ggml/src/ggml-metal/ggml-metal.metal +// +#if __METAL_VERSION__ < 310 && defined(GGML_METAL_HAS_BF16) +#undef GGML_METAL_HAS_BF16 +#endif + +#if defined(GGML_METAL_HAS_BF16) +typedef matrix bfloat4x4; +typedef matrix bfloat2x4; +#endif + +constexpr constant static float kvalues_iq4nl_f[16] = { + -127.f, -104.f, -83.f, -65.f, -49.f, -35.f, -22.f, -10.f, 1.f, 13.f, 25.f, 38.f, 53.f, 69.f, 89.f, 113.f +}; + +constexpr constant static float kvalues_mxfp4_f[16] = { + 0, .5f, 1.f, 1.5f, 2.f, 3.f, 4.f, 6.f, -0, -.5f, -1.f, -1.5f, -2.f, -3.f, -4.f, -6.f +}; + +static inline int best_index_int8(int n, constant float * val, float x) { + if (x <= val[0]) return 0; + if (x >= val[n-1]) return n-1; + int ml = 0, mu = n-1; + while (mu-ml > 1) { + int mav = (ml+mu)/2; + if (x < val[mav]) mu = mav; else ml = mav; } + return x - val[mu-1] < val[mu] - x ? mu-1 : mu; } -typedef decltype(kernel_bin_bcast_impl) kernel_bin_bcast_f32_t; -typedef decltype(kernel_bin_bcast_impl) kernel_bin_bcast_f16_t; +static inline float e8m0_to_fp32(uint8_t x) { + uint32_t bits; -template [[host_name("kernel_bin_bcast_f32")]] kernel kernel_bin_bcast_f32_t kernel_bin_bcast_impl; -template [[host_name("kernel_bin_bcast_f16")]] kernel kernel_bin_bcast_f16_t kernel_bin_bcast_impl; + if (x == 0) { + bits = 0x00400000; + } else { + bits = (uint32_t) x << 23; + } -kernel void kernel_add_id( - constant ggml_metal_kargs_add_id & args, - device const char * src0, - device const char * src1, - device const char * src2, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int i1 = tgpig.x; - const int i2 = tgpig.y; - - const int i11 = *((device const int32_t *) (src2 + i1*sizeof(int32_t) + i2*args.nb21)); - - const size_t nb1 = args.ne0 * sizeof(float); - const size_t nb2 = args.ne1 * nb1; - - device float * dst_row = (device float *)((device char *)dst + i1*nb1 + i2*nb2); - device const float * src0_row = (device const float *)((device char *)src0 + i1*args.nb01 + i2*args.nb02); - device const float * src1_row = (device const float *)((device char *)src1 + i11*args.nb11); - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - dst_row[i0] = src0_row[i0] + src1_row[i0]; - } -} - -template -kernel void kernel_repeat( - constant ggml_metal_kargs_repeat & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int i3 = tgpig.z; - const int i2 = tgpig.y; - const int i1 = tgpig.x; - - const int i03 = i3%args.ne03; - const int i02 = i2%args.ne02; - const int i01 = i1%args.ne01; - - device const char * src0_ptr = src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01; - device char * dst_ptr = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1; - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const int i00 = i0%args.ne00; - *((device T *)(dst_ptr + i0*args.nb0)) = *((device T *)(src0_ptr + i00*args.nb00)); - } -} - -typedef decltype(kernel_repeat) kernel_repeat_t; - -template [[host_name("kernel_repeat_f32")]] kernel kernel_repeat_t kernel_repeat; -template [[host_name("kernel_repeat_f16")]] kernel kernel_repeat_t kernel_repeat; -template [[host_name("kernel_repeat_i32")]] kernel kernel_repeat_t kernel_repeat; -template [[host_name("kernel_repeat_i16")]] kernel kernel_repeat_t kernel_repeat; - -kernel void kernel_reglu_f32( - constant ggml_metal_kargs_glu & args, - device const char * src0, - device const char * src1, - device char * dst, - uint tgpig[[threadgroup_position_in_grid]], - uint tpitg[[thread_position_in_threadgroup]], - uint ntg[[threads_per_threadgroup]]) { - device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; - device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; - device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); - - for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { - const float x0 = src0_row[i0]; - const float x1 = src1_row[i0]; - - dst_row[i0] = x0*x1*(x0 > 0.0f); - } -} - -kernel void kernel_geglu_f32( - constant ggml_metal_kargs_glu & args, - device const char * src0, - device const char * src1, - device char * dst, - uint tgpig[[threadgroup_position_in_grid]], - uint tpitg[[thread_position_in_threadgroup]], - uint ntg[[threads_per_threadgroup]]) { - device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; - device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; - device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); - - for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { - const float x0 = src0_row[i0]; - const float x1 = src1_row[i0]; - - const float gelu = 0.5f*x0*(1.0f + precise::tanh(SQRT_2_OVER_PI*x0*(1.0f + GELU_COEF_A*x0*x0))); - - dst_row[i0] = gelu*x1; - } -} - -kernel void kernel_swiglu_f32( - constant ggml_metal_kargs_glu & args, - device const char * src0, - device const char * src1, - device char * dst, - uint tgpig[[threadgroup_position_in_grid]], - uint tpitg[[thread_position_in_threadgroup]], - uint ntg[[threads_per_threadgroup]]) { - device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; - device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; - device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); - - for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { - const float x0 = src0_row[i0]; - const float x1 = src1_row[i0]; - - const float silu = x0 / (1.0f + exp(-x0)); - - dst_row[i0] = silu*x1; - } -} - -kernel void kernel_swiglu_oai_f32( - constant ggml_metal_kargs_glu & args, - device const char * src0, - device const char * src1, - device char * dst, - uint tgpig[[threadgroup_position_in_grid]], - uint tpitg[[thread_position_in_threadgroup]], - uint ntg[[threads_per_threadgroup]]) { - device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; - device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; - device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); - - for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { - float x0 = src0_row[i0]; - float x1 = src1_row[i0]; - - x0 = min(x0, args.limit); - x1 = max(min(x1, args.limit), -args.limit); - - float out_glu = x0 / (1.0f + exp(-x0 * args.alpha)); - out_glu = out_glu * (1.0f + x1); - - dst_row[i0] = out_glu; - } -} - -kernel void kernel_geglu_erf_f32( - constant ggml_metal_kargs_glu & args, - device const char * src0, - device const char * src1, - device char * dst, - uint tgpig[[threadgroup_position_in_grid]], - uint tpitg[[thread_position_in_threadgroup]], - uint ntg[[threads_per_threadgroup]]) { - device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; - device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; - device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); - - for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { - const float x0 = src0_row[i0]; - const float x1 = src1_row[i0]; - - const float gelu_erf = 0.5f*x0*(1.0f+erf_approx(x0*SQRT_2_INV)); - - dst_row[i0] = gelu_erf*x1; - } -} - -kernel void kernel_geglu_quick_f32( - constant ggml_metal_kargs_glu & args, - device const char * src0, - device const char * src1, - device char * dst, - uint tgpig[[threadgroup_position_in_grid]], - uint tpitg[[thread_position_in_threadgroup]], - uint ntg[[threads_per_threadgroup]]) { - device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; - device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; - device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); - - for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { - const float x0 = src0_row[i0]; - const float x1 = src1_row[i0]; - - const float gelu_quick = x0*(1.0f/(1.0f+exp(GELU_QUICK_COEF*x0))); - - dst_row[i0] = gelu_quick*x1; - } -} - -kernel void kernel_op_sum_f32( - constant ggml_metal_kargs_sum & args, - device const float * src0, - device float * dst, - threadgroup float * shmem_f32 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - - if (args.np == 0) { - return; - } - - // TODO: become function constant - const uint nsg = (ntg.x + 31) / 32; - - float sumf = 0; - - for (uint64_t i0 = tpitg.x; i0 < args.np; i0 += ntg.x) { - sumf += src0[i0]; - } - - sumf = simd_sum(sumf); - - if (tiisg == 0) { - shmem_f32[sgitg] = sumf; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - float total = 0; - - if (sgitg == 0) { - float v = 0; - - if (tpitg.x < nsg) { - v = shmem_f32[tpitg.x]; - } - - total = simd_sum(v); - - if (tpitg.x == 0) { - dst[0] = total; - } - } -} - -constant short FC_sum_rows_op [[function_constant(FC_SUM_ROWS + 0)]]; - -template -kernel void kernel_sum_rows_impl( - constant ggml_metal_kargs_sum_rows & args, - device const char * src0, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { -#define FC_OP FC_sum_rows_op - - const int i3 = tgpig.z; - const int i2 = tgpig.y; - const int i1 = tgpig.x; - - threadgroup T0 * shmem_t = (threadgroup T0 *) shmem; - - if (sgitg == 0) { - shmem_t[tiisg] = 0.0f; - } - - device const T0 * src_row = (device const T0 *) (src0 + i1*args.nb01 + i2*args.nb02 + i3*args.nb03); - device T * dst_row = (device T *) (dst + i1*args.nb1 + i2*args.nb2 + i3*args.nb3); - - T0 sumf = T0(0.0f); - - for (int64_t i0 = tpitg.x; i0 < args.ne00; i0 += ntg.x) { - sumf += src_row[i0]; - } - - sumf = simd_sum(sumf); - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - shmem_t[sgitg] = sumf; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - sumf = shmem_t[tiisg]; - sumf = simd_sum(sumf); - - if (tpitg.x == 0) { - if (FC_OP == OP_SUM_ROWS_NUM_MEAN) { - if (is_same::value) { - dst_row[0] = sum(sumf) / (4*args.ne00); - } else { - dst_row[0] = sum(sumf) / args.ne00; - } - } else { - dst_row[0] = sum(sumf); - } - } - -#undef FC_OP -} - -typedef decltype(kernel_sum_rows_impl) kernel_sum_rows_t; - -template [[host_name("kernel_sum_rows_f32_f32")]] kernel kernel_sum_rows_t kernel_sum_rows_impl; -template [[host_name("kernel_sum_rows_f32_f32_4")]] kernel kernel_sum_rows_t kernel_sum_rows_impl; - -template -kernel void kernel_cumsum_blk( - constant ggml_metal_kargs_cumsum_blk & args, - device const char * src0, - device char * tmp, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int ib = tgpig[0]/args.ne01; - - const int i00 = ib*ntg.x; - const int i01 = tgpig[0]%args.ne01; - const int i02 = tgpig[1]; - const int i03 = tgpig[2]; - - device const float * src0_row = (device const float *) (src0 + - args.nb01*i01 + - args.nb02*i02 + - args.nb03*i03); - - threadgroup float * shmem_f32 = (threadgroup float *) shmem; - - float v = 0.0f; - - if (i00 + tpitg.x < args.ne00) { - v = src0_row[i00 + tpitg.x]; - } - - float s = simd_prefix_inclusive_sum(v); - - if (tiisg == N_SIMDWIDTH - 1) { - shmem_f32[sgitg] = s; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (sgitg == 0) { - shmem_f32[tiisg] = simd_prefix_exclusive_sum(shmem_f32[tiisg]); - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - s += shmem_f32[sgitg]; - - device float * dst_row = (device float *) dst + - args.ne00*i01 + - args.ne00*args.ne01*i02 + - args.ne00*args.ne01*args.ne02*i03; - - if (i00 + tpitg.x < args.ne00) { - dst_row[i00 + tpitg.x] = s; - } - - if (args.outb && tpitg.x == ntg.x - 1) { - device float * tmp_row = (device float *) tmp + - args.net0*i01 + - args.net0*args.net1*i02 + - args.net0*args.net1*args.net2*i03; - - tmp_row[ib] = s; - } -} - -typedef decltype(kernel_cumsum_blk) kernel_cumsum_blk_t; - -template [[host_name("kernel_cumsum_blk_f32")]] kernel kernel_cumsum_blk_t kernel_cumsum_blk; - -template -kernel void kernel_cumsum_add( - constant ggml_metal_kargs_cumsum_add & args, - device const char * tmp, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int ib = tgpig[0]/args.ne01; - - if (ib == 0) { - return; - } - - const int i00 = ib*ntg.x; - const int i01 = tgpig[0]%args.ne01; - const int i02 = tgpig[1]; - const int i03 = tgpig[2]; - - device const float * tmp_row = (device const float *) (tmp + - args.nbt1*i01 + - args.nbt2*i02 + - args.nbt3*i03); - - device float * dst_row = (device float *) dst + - args.ne00*i01 + - args.ne00*args.ne01*i02 + - args.ne00*args.ne01*args.ne02*i03; - - if (i00 + tpitg.x < args.ne00) { - dst_row[i00 + tpitg.x] += tmp_row[ib - 1]; - } -} - -typedef decltype(kernel_cumsum_add) kernel_cumsum_add_t; - -template [[host_name("kernel_cumsum_add_f32")]] kernel kernel_cumsum_add_t kernel_cumsum_add; - - -template -bool _ggml_vec_tri_cmp(const int i, const int r); - -template<> -bool _ggml_vec_tri_cmp(const int i, const int r) { - return i < r; -} - -template<> -bool _ggml_vec_tri_cmp(const int i, const int r) { - return i <= r; -} - -template<> -bool _ggml_vec_tri_cmp(const int i, const int r) { - return i > r; -} - -template<> -bool _ggml_vec_tri_cmp(const int i, const int r) { - return i >= r; -} - -template -kernel void kernel_tri( - constant ggml_metal_kargs_tri & args, - device const char * src0, - device const char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int i3 = tgpig.z; - const int i2 = tgpig.y; - const int i1 = tgpig.x; - - if (i3 >= args.ne03 || i2 >= args.ne02 || i1 >= args.ne01) { - return; - } - - device const T * src_row = (device const T *) ((device const char *) src0 + i1*args.nb01 + i2*args.nb02 + i3*args.nb03); - device T * dst_row = (device T *) ((device char *) dst + i1*args.nb1 + i2*args.nb2 + i3*args.nb3); - - // Each thread is a single element of the row if ne00 < max threads per - // threadgroup, so this will loop once for each index that this thread is - // responsible for - for (int64_t i0 = tpitg.x; i0 < args.ne00; i0 += ntg.x) { - // Use the comparison as a mask for branchless - dst_row[i0] = static_cast(_ggml_vec_tri_cmp(i0, i1)) * src_row[i0]; - } -} - -typedef decltype(kernel_tri) kernel_tri_t; - -template [[host_name("kernel_tri_f32_0")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_f32_1")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_f32_2")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_f32_3")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_f16_0")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_f16_1")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_f16_2")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_f16_3")]] kernel kernel_tri_t kernel_tri; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_tri_bf16_0")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_bf16_1")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_bf16_2")]] kernel kernel_tri_t kernel_tri; -template [[host_name("kernel_tri_bf16_3")]] kernel kernel_tri_t kernel_tri; -#endif - -template -kernel void kernel_soft_max( - constant ggml_metal_kargs_soft_max & args, - device const char * src0, - device const char * src1, - device const char * src2, - device char * dst, - threadgroup float * buf [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint sgitg[[simdgroup_index_in_threadgroup]], - uint tiisg[[thread_index_in_simdgroup]], - uint3 tptg[[threads_per_threadgroup]]) { - const int32_t i03 = tgpig.z; - const int32_t i02 = tgpig.y; - const int32_t i01 = tgpig.x; - - const int32_t i13 = i03%args.ne13; - const int32_t i12 = i02%args.ne12; - const int32_t i11 = i01; - - device const float * psrc0 = (device const float *) (src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03); - device const T * pmask = src1 != src0 ? (device const T * ) (src1 + i11*args.nb11 + i12*args.nb12 + i13*args.nb13) : nullptr; - device const float * psrc2 = src2 != src0 ? (device const float *) (src2) : nullptr; - device float * pdst = (device float *) (dst + i01*args.nb1 + i02*args.nb2 + i03*args.nb3); - - float slope = 1.0f; - - // ALiBi - if (args.max_bias > 0.0f) { - const int32_t h = i02; - - const float base = h < args.n_head_log2 ? args.m0 : args.m1; - const int exp = h < args.n_head_log2 ? h + 1 : 2*(h - args.n_head_log2) + 1; - - slope = pow(base, exp); - } - - // parallel max - float lmax = psrc2 ? psrc2[i02] : -INFINITY; - - for (int i00 = tpitg.x; i00 < args.ne00; i00 += tptg.x) { - lmax = MAX(lmax, psrc0[i00]*args.scale + (pmask ? slope*pmask[i00] : 0.0f)); - } - - // find the max value in the block - float max_val = simd_max(lmax); - if (tptg.x > N_SIMDWIDTH) { - if (sgitg == 0) { - buf[tiisg] = -INFINITY; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - buf[sgitg] = max_val; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - max_val = buf[tiisg]; - max_val = simd_max(max_val); - } - - // parallel sum - float lsum = 0.0f; - for (int i00 = tpitg.x; i00 < args.ne00; i00 += tptg.x) { - const float exp_psrc0 = exp((psrc0[i00]*args.scale + (pmask ? slope*pmask[i00] : 0.0f)) - max_val); - lsum += exp_psrc0; - pdst[i00] = exp_psrc0; - } - - // This barrier fixes a failing test - // ref: https://github.com/ggml-org/ggml/pull/621#discussion_r1425156335 - threadgroup_barrier(mem_flags::mem_none); - - float sum = simd_sum(lsum); - - if (tptg.x > N_SIMDWIDTH) { - if (sgitg == 0) { - buf[tiisg] = 0.0f; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - buf[sgitg] = sum; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - sum = buf[tiisg]; - sum = simd_sum(sum); - } - - if (psrc2) { - sum += exp(psrc2[i02] - max_val); - } - - const float inv_sum = 1.0f/sum; - - for (int i00 = tpitg.x; i00 < args.ne00; i00 += tptg.x) { - pdst[i00] *= inv_sum; - } -} - -template -kernel void kernel_soft_max_4( - constant ggml_metal_kargs_soft_max & args, - device const char * src0, - device const char * src1, - device const char * src2, - device char * dst, - threadgroup float * buf [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint sgitg[[simdgroup_index_in_threadgroup]], - uint tiisg[[thread_index_in_simdgroup]], - uint3 tptg[[threads_per_threadgroup]]) { - const int32_t i03 = tgpig.z; - const int32_t i02 = tgpig.y; - const int32_t i01 = tgpig.x; - - const int32_t i13 = i03%args.ne13; - const int32_t i12 = i02%args.ne12; - const int32_t i11 = i01; - - device const float4 * psrc4 = (device const float4 *) (src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03); - device const T * pmask = src1 != src0 ? (device const T * ) (src1 + i11*args.nb11 + i12*args.nb12 + i13*args.nb13) : nullptr; - device const float * psrc2 = src2 != src0 ? (device const float * ) (src2) : nullptr; - device float4 * pdst4 = (device float4 *) (dst + i01*args.nb1 + i02*args.nb2 + i03*args.nb3); - - float slope = 1.0f; - - if (args.max_bias > 0.0f) { - const int32_t h = i02; - - const float base = h < args.n_head_log2 ? args.m0 : args.m1; - const int exp = h < args.n_head_log2 ? h + 1 : 2*(h - args.n_head_log2) + 1; - - slope = pow(base, exp); - } - - // parallel max - float4 lmax4 = psrc2 ? psrc2[i02] : -INFINITY; - - for (int i00 = tpitg.x; i00 < args.ne00/4; i00 += tptg.x) { - lmax4 = fmax(lmax4, psrc4[i00]*args.scale + (float4)((pmask ? slope*pmask[i00] : 0.0f))); - } - - const float lmax = MAX(MAX(lmax4[0], lmax4[1]), MAX(lmax4[2], lmax4[3])); - - float max_val = simd_max(lmax); - if (tptg.x > N_SIMDWIDTH) { - if (sgitg == 0) { - buf[tiisg] = -INFINITY; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - buf[sgitg] = max_val; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - max_val = buf[tiisg]; - max_val = simd_max(max_val); - } - - // parallel sum - float4 lsum4 = 0.0f; - for (int i00 = tpitg.x; i00 < args.ne00/4; i00 += tptg.x) { - const float4 exp_psrc4 = exp((psrc4[i00]*args.scale + (float4)((pmask ? slope*pmask[i00] : 0.0f))) - max_val); - lsum4 += exp_psrc4; - pdst4[i00] = exp_psrc4; - } - - const float lsum = lsum4[0] + lsum4[1] + lsum4[2] + lsum4[3]; - - // This barrier fixes a failing test - // ref: https://github.com/ggml-org/ggml/pull/621#discussion_r1425156335 - threadgroup_barrier(mem_flags::mem_none); - - float sum = simd_sum(lsum); - - if (tptg.x > N_SIMDWIDTH) { - if (sgitg == 0) { - buf[tiisg] = 0.0f; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - buf[sgitg] = sum; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - sum = buf[tiisg]; - sum = simd_sum(sum); - } - - if (psrc2) { - sum += exp(psrc2[i02] - max_val); - } - - const float inv_sum = 1.0f/sum; - - for (int i00 = tpitg.x; i00 < args.ne00/4; i00 += tptg.x) { - pdst4[i00] *= inv_sum; - } -} - -typedef decltype(kernel_soft_max) kernel_soft_max_t; -typedef decltype(kernel_soft_max_4) kernel_soft_max_4_t; - -template [[host_name("kernel_soft_max_f16")]] kernel kernel_soft_max_t kernel_soft_max; -template [[host_name("kernel_soft_max_f32")]] kernel kernel_soft_max_t kernel_soft_max; -template [[host_name("kernel_soft_max_f16_4")]] kernel kernel_soft_max_4_t kernel_soft_max_4; -template [[host_name("kernel_soft_max_f32_4")]] kernel kernel_soft_max_4_t kernel_soft_max_4; - -// ref: ggml.c:ggml_compute_forward_ssm_conv_f32 -kernel void kernel_ssm_conv_f32_f32( - constant ggml_metal_kargs_ssm_conv & args, - device const void * src0, - device const void * src1, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - const int64_t ir = tgpig.x; - const int64_t i2 = tgpig.y; - const int64_t i3 = tgpig.z; - - const int64_t nc = args.ne10; - //const int64_t ncs = args.ne00; - //const int64_t nr = args.ne01; - //const int64_t n_t = args.ne1; - //const int64_t n_s = args.ne2; - - device const float * s = (device const float *) ((device const char *) src0 + ir*args.nb01 + i2*args.nb00 + i3*args.nb02); - device const float * c = (device const float *) ((device const char *) src1 + ir*args.nb11); - device float * x = (device float *) ((device char *) dst + ir*args.nb0 + i2*args.nb1 + i3*args.nb2); - - float sumf = 0.0f; - - for (int64_t i0 = 0; i0 < nc; ++i0) { - sumf += s[i0] * c[i0]; - } - - x[0] = sumf; -} - -kernel void kernel_ssm_conv_f32_f32_4( - constant ggml_metal_kargs_ssm_conv & args, - device const void * src0, - device const void * src1, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - const int64_t ir = tgpig.x; - const int64_t i2 = tgpig.y; - const int64_t i3 = tgpig.z; - - const int64_t nc = args.ne10; - //const int64_t ncs = args.ne00; - //const int64_t nr = args.ne01; - //const int64_t n_t = args.ne1; - //const int64_t n_s = args.ne2; - - device const float4 * s = (device const float4 *) ((device const char *) src0 + ir*args.nb01 + i2*args.nb00 + i3*args.nb02); - device const float4 * c = (device const float4 *) ((device const char *) src1 + ir*args.nb11); - device float * x = (device float *) ((device char *) dst + ir*args.nb0 + i2*args.nb1 + i3*args.nb2); - - float sumf = 0.0f; - - for (int64_t i0 = 0; i0 < nc/4; ++i0) { - sumf += dot(s[i0], c[i0]); - } - - x[0] = sumf; -} - -constant short FC_ssm_conv_bs [[function_constant(FC_SSM_CONV + 0)]]; - -// Batched version: each threadgroup processes multiple tokens for better efficiency -// Thread layout: each thread handles one token, threadgroup covers BATCH_SIZE tokens -kernel void kernel_ssm_conv_f32_f32_batched( - constant ggml_metal_kargs_ssm_conv & args, - device const void * src0, - device const void * src1, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - // tgpig.x = row index (ir) - // tgpig.y = batch of tokens (i2_base / BATCH_SIZE) - // tgpig.z = sequence index (i3) - // tpitg.x = thread within batch (0..BATCH_SIZE-1) - const short BATCH_SIZE = FC_ssm_conv_bs; - - const int64_t ir = tgpig.x; - const int64_t i2_base = tgpig.y * BATCH_SIZE; - const int64_t i3 = tgpig.z; - const int64_t i2_off = tpitg.x; - const int64_t i2 = i2_base + i2_off; - - const int64_t nc = args.ne10; // conv kernel size (typically 4) - const int64_t n_t = args.ne1; // number of tokens - - // Bounds check for partial batches at the end - if (i2 >= n_t) { - return; - } - - // Load conv weights (shared across all tokens for this row) - device const float * c = (device const float *) ((device const char *) src1 + ir*args.nb11); - - // Load source for this specific token - device const float * s = (device const float *) ((device const char *) src0 + ir*args.nb01 + i2*args.nb00 + i3*args.nb02); - - // Output location for this token - device float * x = (device float *) ((device char *) dst + ir*args.nb0 + i2*args.nb1 + i3*args.nb2); - - float sumf = 0.0f; - for (int64_t i0 = 0; i0 < nc; ++i0) { - sumf += s[i0] * c[i0]; - } - - x[0] = sumf; -} - -kernel void kernel_ssm_conv_f32_f32_batched_4( - constant ggml_metal_kargs_ssm_conv & args, - device const void * src0, - device const void * src1, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - // tgpig.x = row index (ir) - // tgpig.y = batch of tokens (i2_base / BATCH_SIZE) - // tgpig.z = sequence index (i3) - // tpitg.x = thread within batch (0..BATCH_SIZE-1) - const short BATCH_SIZE = FC_ssm_conv_bs; - - const int64_t ir = tgpig.x; - const int64_t i2_base = tgpig.y * BATCH_SIZE; - const int64_t i3 = tgpig.z; - const int64_t i2_off = tpitg.x; - const int64_t i2 = i2_base + i2_off; - - const int64_t nc = args.ne10; // conv kernel size (typically 4) - const int64_t n_t = args.ne1; // number of tokens - - // Bounds check for partial batches at the end - if (i2 >= n_t) { - return; - } - - // Load conv weights (shared across all tokens for this row) - device const float4 * c = (device const float4 *) ((device const char *) src1 + ir*args.nb11); - - // Load source for this specific token - device const float4 * s = (device const float4 *) ((device const char *) src0 + ir*args.nb01 + i2*args.nb00 + i3*args.nb02); - - // Output location for this token - device float * x = (device float *) ((device char *) dst + ir*args.nb0 + i2*args.nb1 + i3*args.nb2); - - float sumf = 0.0f; - for (int64_t i0 = 0; i0 < nc/4; ++i0) { - sumf += dot(s[i0], c[i0]); - } - - x[0] = sumf; -} - -// ref: ggml.c:ggml_compute_forward_ssm_scan_f32, Mamba-2 part -// Optimized version: reduces redundant memory loads by having one thread load shared values -kernel void kernel_ssm_scan_f32( - constant ggml_metal_kargs_ssm_scan & args, - device const void * src0, - device const void * src1, - device const void * src2, - device const void * src3, - device const void * src4, - device const void * src5, - device const void * src6, - device float * dst, - threadgroup float * shared [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgptg[[simdgroups_per_threadgroup]], - uint3 tgpg[[threadgroups_per_grid]]) { - constexpr short NW = N_SIMDWIDTH; - - // Shared memory layout: - // [0..sgptg*NW-1]: partial sums for reduction (existing) - // [sgptg*NW..sgptg*NW+sgptg-1]: pre-computed x_dt values for each token in batch - // [sgptg*NW+sgptg..sgptg*NW+2*sgptg-1]: pre-computed dA values for each token in batch - threadgroup float * shared_sums = shared; - threadgroup float * shared_x_dt = shared + sgptg * NW; - threadgroup float * shared_dA = shared + sgptg * NW + sgptg; - - shared_sums[tpitg.x] = 0.0f; - - const int32_t i0 = tpitg.x; - const int32_t i1 = tgpig.x; - const int32_t ir = tgpig.y; // current head - const int32_t i3 = tgpig.z; // current seq - - const int32_t nc = args.d_state; - const int32_t nr = args.d_inner; - const int32_t nh = args.n_head; - const int32_t ng = args.n_group; - const int32_t n_t = args.n_seq_tokens; - - const int32_t s_off = args.s_off; - - device const int32_t * ids = (device const int32_t *) src6; - - device const float * s0_buff = (device const float *) ((device const char *) src0 + ir*args.nb02 + ids[i3]*args.nb03); - device float * s_buff = (device float *) ((device char *) dst + ir*args.nb02 + i3*args.nb03 + s_off); - - const int32_t i = i0 + i1*nc; - const int32_t g = ir / (nh / ng); // repeat_interleave - - float s0 = s0_buff[i]; - float s = 0.0f; - - device const float * A = (device const float *) ((device const char *) src3 + ir*args.nb31); // {ne30, nh} - - const float A0 = A[i0%args.ne30]; - - device const float * x = (device const float *)((device const char *) src1 + i1*args.nb10 + ir*args.nb11 + i3*args.nb13); // {dim, nh, nt, ns} - device const float * dt = (device const float *)((device const char *) src2 + ir*args.nb20 + i3*args.nb22); // {nh, nt, ns} - device const float * B = (device const float *)((device const char *) src4 + g*args.nb41 + i3*args.nb43); // {d_state, ng, nt, ns} - device const float * C = (device const float *)((device const char *) src5 + g*args.nb51 + i3*args.nb53); // {d_state, ng, nt, ns} - - device float * y = dst + (i1 + ir*(nr) + i3*(n_t*nh*nr)); // {dim, nh, nt, ns} - - for (int i2 = 0; i2 < n_t; i2 += sgptg) { - threadgroup_barrier(mem_flags::mem_threadgroup); - - // Pre-compute x_dt and dA for this batch of tokens - // Only first sgptg threads do the loads and expensive math - if (i0 < sgptg && i2 + i0 < n_t) { - // ns12 and ns21 are element strides (nb12/nb10, nb21/nb20) - device const float * x_t = x + i0 * args.ns12; - device const float * dt_t = dt + i0 * args.ns21; - - const float dt0 = dt_t[0]; - const float dtsp = dt0 <= 20.0f ? log(1.0f + exp(dt0)) : dt0; - shared_x_dt[i0] = x_t[0] * dtsp; - shared_dA[i0] = dtsp; // Store dtsp, compute exp(dtsp * A0) per-thread since A0 varies - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - for (int t = 0; t < sgptg && i2 + t < n_t; t++) { - const float x_dt = shared_x_dt[t]; - const float dA = exp(shared_dA[t] * A0); - - s = (s0 * dA) + (B[i0] * x_dt); - - const float sumf = simd_sum(s * C[i0]); - - if (tiisg == 0) { - shared_sums[t*NW + sgitg] = sumf; - } - - // recurse - s0 = s; - - B += args.ns42; - C += args.ns52; - } - - // Advance pointers for next batch - x += sgptg * args.ns12; - dt += sgptg * args.ns21; - - threadgroup_barrier(mem_flags::mem_threadgroup); - - const float sumf = simd_sum(shared_sums[sgitg*NW + tiisg]); - - if (tiisg == 0 && i2 + sgitg < n_t) { - y[sgitg*nh*nr] = sumf; - } - - y += sgptg*nh*nr; - } - - s_buff[i] = s; -} - -kernel void kernel_rwkv_wkv6_f32( - device const float * k, - device const float * v, - device const float * r, - device const float * tf, - device const float * td, - device const float * state_in, - device float * dst, - constant uint & B, - constant uint & T, - constant uint & C, - constant uint & H, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const uint head_size = 64; // TODO: support head_size = 128 - const uint batch_id = tgpig.x / H; - const uint head_id = tgpig.x % H; - const uint tid = tpitg.x; - - if (batch_id >= B || head_id >= H) { - return; - } - - const uint state_size = C * head_size; - const uint n_seq_tokens = T / B; - - threadgroup float _k[head_size]; - threadgroup float _r[head_size]; - threadgroup float _tf[head_size]; - threadgroup float _td[head_size]; - - float state[head_size]; - - for (uint i = 0; i < head_size; i++) { - state[i] = state_in[batch_id * state_size + head_id * head_size * head_size - + i * head_size + tid]; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - _tf[tid] = tf[head_id * head_size + tid]; - threadgroup_barrier(mem_flags::mem_threadgroup); - - const uint start_t = batch_id * n_seq_tokens * C + head_id * head_size + tid; - const uint end_t = (batch_id + 1) * n_seq_tokens * C + head_id * head_size + tid; - - for (uint t = start_t; t < end_t; t += C) { - threadgroup_barrier(mem_flags::mem_threadgroup); - _k[tid] = k[t]; - _r[tid] = r[t]; - _td[tid] = td[t]; - threadgroup_barrier(mem_flags::mem_threadgroup); - - const float v_val = v[t]; - float y = 0.0; - - for (uint j = 0; j < head_size; j += 4) { - float4 k_vec = float4(_k[j], _k[j+1], _k[j+2], _k[j+3]); - float4 r_vec = float4(_r[j], _r[j+1], _r[j+2], _r[j+3]); - float4 tf_vec = float4(_tf[j], _tf[j+1], _tf[j+2], _tf[j+3]); - float4 td_vec = float4(_td[j], _td[j+1], _td[j+2], _td[j+3]); - float4 s_vec = float4(state[j], state[j+1], state[j+2], state[j+3]); - - float4 kv = k_vec * v_val; - - float4 temp = tf_vec * kv + s_vec; - y += dot(r_vec, temp); - - s_vec = s_vec * td_vec + kv; - state[j] = s_vec[0]; - state[j+1] = s_vec[1]; - state[j+2] = s_vec[2]; - state[j+3] = s_vec[3]; - } - - dst[t] = y; - } - - for (uint i = 0; i < head_size; i++) { - dst[T * C + batch_id * state_size + head_id * head_size * head_size - + i * head_size + tid] = state[i]; - } -} - -kernel void kernel_rwkv_wkv7_f32( - device const float * r, - device const float * w, - device const float * k, - device const float * v, - device const float * a, - device const float * b, - device const float * state_in, - device float * dst, - constant uint & B, - constant uint & T, - constant uint & C, - constant uint & H, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const uint head_size = 64; // TODO: support head_size = 128 - const uint batch_id = tgpig.x / H; - const uint head_id = tgpig.x % H; - const uint tid = tpitg.x; - - if (batch_id >= B || head_id >= H) { - return; - } - - const uint state_size = C * head_size; - const uint n_seq_tokens = T / B; - - threadgroup float _r[head_size]; - threadgroup float _w[head_size]; - threadgroup float _k[head_size]; - threadgroup float _a[head_size]; - threadgroup float _b[head_size]; - - float state[head_size]; - - for (uint i = 0; i < head_size; i++) { - state[i] = state_in[batch_id * state_size + head_id * head_size * head_size - + tid * head_size + i]; - } - - const uint start_t = batch_id * n_seq_tokens * C + head_id * head_size + tid; - const uint end_t = (batch_id + 1) * n_seq_tokens * C + head_id * head_size + tid; - - for (uint t = start_t; t < end_t; t += C) { - threadgroup_barrier(mem_flags::mem_threadgroup); - _r[tid] = r[t]; - _w[tid] = w[t]; - _k[tid] = k[t]; - _a[tid] = a[t]; - _b[tid] = b[t]; - threadgroup_barrier(mem_flags::mem_threadgroup); - - const float v_val = v[t]; - float y = 0.0, sa = 0.0; - - float4 sa_vec(0.0); - - for (uint j = 0; j < head_size; j += 4) { - float4 a_vec = float4(_a[j], _a[j+1], _a[j+2], _a[j+3]); - float4 s_vec = float4(state[j], state[j+1], state[j+2], state[j+3]); - sa_vec += a_vec * s_vec; - } - sa = sa_vec[0] + sa_vec[1] + sa_vec[2] + sa_vec[3]; - - for (uint j = 0; j < head_size; j += 4) { - float4 r_vec = float4(_r[j], _r[j+1], _r[j+2], _r[j+3]); - float4 w_vec = float4(_w[j], _w[j+1], _w[j+2], _w[j+3]); - float4 k_vec = float4(_k[j], _k[j+1], _k[j+2], _k[j+3]); - float4 b_vec = float4(_b[j], _b[j+1], _b[j+2], _b[j+3]); - float4 s_vec = float4(state[j], state[j+1], state[j+2], state[j+3]); - - float4 kv = k_vec * v_val; - - s_vec = s_vec * w_vec + kv + sa * b_vec; - y += dot(s_vec, r_vec); - - state[j] = s_vec[0]; - state[j+1] = s_vec[1]; - state[j+2] = s_vec[2]; - state[j+3] = s_vec[3]; - } - - dst[t] = y; - } - - for (uint i = 0; i < head_size; i++) { - dst[T * C + batch_id * state_size + head_id * head_size * head_size - + tid * head_size + i] = state[i]; - } -} - -constant short FC_gated_delta_net_ne20 [[function_constant(FC_GATED_DELTA_NET + 0)]]; -constant short FC_gated_delta_net_ne30 [[function_constant(FC_GATED_DELTA_NET + 1)]]; -constant short FC_gated_delta_net_K [[function_constant(FC_GATED_DELTA_NET + 2)]]; - -#if 1 -template -kernel void kernel_gated_delta_net_impl( - constant ggml_metal_kargs_gated_delta_net & args, - device const char * q, - device const char * k, - device const char * v, - device const char * g, - device const char * b, - device const char * s, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { -#define S_v FC_gated_delta_net_ne20 -#define G FC_gated_delta_net_ne30 -#define K FC_gated_delta_net_K - - const uint tx = tpitg.x; - const uint ty = tpitg.y; - - const uint i23 = tgpig.z; // B (n_seqs) - const uint i21 = tgpig.y; // H (head) - const uint i20 = tgpig.x*NSG + ty; // row within S_v - - const uint i01 = i21 % args.ne01; - const uint i11 = i21 % args.ne11; - - const float scale = 1.0f / sqrt((float)S_v); - - // input state layout (D, K, n_seqs): per-seq stride is K*H*D; we read slot 0. - // state is stored transposed: M[i20][is] = S[is][i20], so row i20 is contiguous - const uint state_in_base = (i23*K*args.ne21 + i21)*S_v*S_v + i20*S_v; - device const float * s_ptr = (device const float *) (s) + state_in_base; - - float ls[NSG]; - - FOR_UNROLL (short j = 0; j < NSG; j++) { - const short is = tx*NSG + j; - ls[j] = s_ptr[is]; - } - - device float * dst_attn = (device float *) (dst) + (i23*args.ne22*args.ne21 + i21)*S_v + i20; - - device const float * q_ptr = (device const float *) (q + i23*args.nb03 + i01*args.nb01); - device const float * k_ptr = (device const float *) (k + i23*args.nb13 + i11*args.nb11); - device const float * v_ptr = (device const float *) (v + i23*args.nb23 + i21*args.nb21); - - device const float * b_ptr = (device const float *) (b) + (i23*args.ne22*args.ne21 + i21); - device const float * g_ptr = (device const float *) (g) + (i23*args.ne22*args.ne21 + i21)*G; - - // snapshot slot mapping: target_slot = t - shift. When n_tokens < K, only the last - // n_tokens slots are written; earlier slots are left untouched (caller-owned). - const int shift = (int)args.ne22 - (int)K; - - // output state base offset: after attention scores - const uint attn_size = args.ne22 * args.ne21 * S_v * args.ne23; - // output state per-slot size: S_v * S_v * H * n_seqs - const uint state_size_per_snap = S_v * S_v * args.ne21 * args.ne23; - // per-(seq,head) offset within a slot - const uint state_out_base = (i23*args.ne21 + i21)*S_v*S_v + i20*S_v; - - for (short t = 0; t < args.ne22; t++) { - float s_k = 0.0f; - - if (G == 1) { - const float g_exp = exp(g_ptr[0]); - - FOR_UNROLL (short j = 0; j < NSG; j++) { - const short is = tx*NSG + j; - ls[j] *= g_exp; - - s_k += ls[j]*k_ptr[is]; - } - } else { - // KDA - FOR_UNROLL (short j = 0; j < NSG; j++) { - const short is = tx*NSG + j; - ls[j] *= exp(g_ptr[is]); - - s_k += ls[j]*k_ptr[is]; - } - } - - s_k = simd_sum(s_k); - - const float d = (v_ptr[i20] - s_k)*b_ptr[0]; - - float y = 0.0f; - - FOR_UNROLL (short j = 0; j < NSG; j++) { - const short is = tx*NSG + j; - ls[j] += k_ptr[is]*d; - - y += ls[j]*q_ptr[is]; - } - - y = simd_sum(y); - - if (tx == 0) { - dst_attn[t*args.ne21*S_v] = y*scale; - } - - q_ptr += args.ns02; - k_ptr += args.ns12; - v_ptr += args.ns22; - - b_ptr += args.ne21; - g_ptr += args.ne21*G; - - if (K > 1u) { - const int target_slot = (int)t - shift; - if (target_slot >= 0 && target_slot < (int)K) { - device float * dst_state = (device float *) (dst) + attn_size + (uint)target_slot * state_size_per_snap + state_out_base; - FOR_UNROLL (short j = 0; j < NSG; j++) { - const short is = tx*NSG + j; - dst_state[is] = ls[j]; - } - } - } - } - - if (K == 1u) { - device float * dst_state = (device float *) (dst) + attn_size + state_out_base; - FOR_UNROLL (short j = 0; j < NSG; j++) { - const short is = tx*NSG + j; - dst_state[is] = ls[j]; - } - } - -#undef S_v -#undef G -#undef K -} - -typedef decltype(kernel_gated_delta_net_impl<4>) kernel_gated_delta_net_t; - -template [[host_name("kernel_gated_delta_net_f32_1")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl<1>; -template [[host_name("kernel_gated_delta_net_f32_2")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl<2>; -template [[host_name("kernel_gated_delta_net_f32_4")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl<4>; - -#else -// a simplified version of the above -// no performance improvement, so keep the above version for now - -template -kernel void kernel_gated_delta_net_impl( - constant ggml_metal_kargs_gated_delta_net & args, - device const char * q, - device const char * k, - device const char * v, - device const char * g, - device const char * b, - device const char * s, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { -#define S_v FC_gated_delta_net_ne20 -#define G FC_gated_delta_net_ne30 - - const uint tx = tpitg.x; - const uint ty = tpitg.y; - - const uint i23 = tgpig.z; // B - const uint i21 = tgpig.y; // H - const uint i20 = tgpig.x*NSG + ty; - - const uint i01 = i21 % args.ne01; - const uint i11 = i21 % args.ne11; - - const float scale = 1.0f / sqrt((float)S_v); - - device const float * s_ptr = (device const float *) (s) + (i23*args.ne21 + i21)*S_v*S_v + i20; - - float lsf[NSG]; - - FOR_UNROLL (short j = 0; j < NSG; j++) { - const short is = tx*NSG + j; - lsf[j] = s_ptr[is*S_v]; - } - - thread T * ls = (thread T *) (lsf); - - device float * dst_attn = (device float *) (dst) + (i23*args.ne22*args.ne21 + i21)*S_v + i20; - - device const float * q_ptr = (device const float *) (q + i23*args.nb03 + i01*args.nb01); - device const float * k_ptr = (device const float *) (k + i23*args.nb13 + i11*args.nb11); - device const float * v_ptr = (device const float *) (v + i23*args.nb23 + i21*args.nb21); - - device const float * b_ptr = (device const float *) (b) + (i23*args.ne22*args.ne21 + i21); - device const float * g_ptr = (device const float *) (g) + (i23*args.ne22*args.ne21 + i21)*G; - - for (short t = 0; t < args.ne22; t++) { - device const T * qt_ptr = (device const T *) (q_ptr); - device const T * kt_ptr = (device const T *) (k_ptr); - device const T * gt_ptr = (device const T *) (g_ptr); - - if (G == 1) { - *ls *= exp(g_ptr[0]); - } else { - // KDA - *ls *= exp(gt_ptr[tx]); - } - - const float s_k = simd_sum(dot(*ls, kt_ptr[tx])); - - const float d = (v_ptr[i20] - s_k)*b_ptr[0]; - - *ls += kt_ptr[tx]*d; - - const float y = simd_sum(dot(*ls, qt_ptr[tx])); - - if (tx == 0) { - *dst_attn = y*scale; - } - - q_ptr += args.ns02; - k_ptr += args.ns12; - v_ptr += args.ns22; - - b_ptr += args.ne21; - g_ptr += args.ne21*G; - - dst_attn += args.ne21*S_v; - } - - device float * dst_state = (device float *) (dst) + args.ne23*args.ne22*args.ne21*S_v + (i23*args.ne21 + i21)*S_v*S_v + i20; - device T * dstt_state = (device T *) (dst_state); - - FOR_UNROLL (short j = 0; j < NSG; j++) { - const short is = tx*NSG + j; - dst_state[is*S_v] = lsf[j]; - } - -#undef S_v -#undef G -} - -typedef decltype(kernel_gated_delta_net_impl) kernel_gated_delta_net_t; - -template [[host_name("kernel_gated_delta_net_f32_1")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl; -template [[host_name("kernel_gated_delta_net_f32_2")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl; -template [[host_name("kernel_gated_delta_net_f32_4")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl; -#endif - -constant short FC_solve_tri_nsg [[function_constant(FC_SOLVE_TRI + 0)]]; -constant short FC_solve_tri_n [[function_constant(FC_SOLVE_TRI + 1)]]; -constant short FC_solve_tri_k [[function_constant(FC_SOLVE_TRI + 2)]]; - -kernel void kernel_solve_tri_f32( - constant ggml_metal_kargs_solve_tri & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - ushort3 tgpig[[threadgroup_position_in_grid]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - constexpr short NW = N_SIMDWIDTH; - - const short NSG = FC_solve_tri_nsg; - const short N = FC_solve_tri_n; - const short K = FC_solve_tri_k; - const short NP = PAD2(N, NW); - - const int32_t i03 = tgpig.z; - const int32_t i02 = tgpig.y; - const int32_t i01 = tgpig.x*NSG + sgitg; - - threadgroup float * sh0 = (threadgroup float *) shmem; - - device const float * src0_ptr = (device const float *)(src0 + i02 * args.nb02 + i03 * args.nb03) + sgitg*N; - device const float * src1_ptr = (device const float *)(src1 + i02 * args.nb12 + i03 * args.nb13) + i01; - device float * dst_ptr = (device float *)(dst + i02 * args.nb2 + i03 * args.nb3) + i01; - - for (short rr = 0; rr < N; rr += NSG) { - threadgroup_barrier(mem_flags::mem_threadgroup); - - { - threadgroup float * sh0_cur = sh0 + sgitg*NP; - - for (short t = 0; t*NW < N; ++t) { - const short idx = t*NW + tiisg; - sh0_cur[idx] = src0_ptr[idx]; - } - - src0_ptr += NSG*N; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (i01 >= args.ne10) { - continue; - } - - for (short ir = 0; ir < NSG && rr + ir < N; ++ir) { - const short r = rr + ir; - - threadgroup float * sh0_cur = sh0 + ir*NP; - - float sum = 0.0f; - - for (short t = 0; t*NW < r; ++t) { - const short idx = t*NW + tiisg; - sum += sh0_cur[idx] * dst_ptr[idx*K] * (idx < r); - } - - sum = simd_sum(sum); - - if (tiisg == 0) { - const float diag = sh0_cur[r]; - - dst_ptr[r*K] = (src1_ptr[r*K] - sum) / diag; - } - } - } -} - -kernel void kernel_argmax_f32( - constant ggml_metal_kargs_argmax & args, - device const char * src0, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint tgpig[[threadgroup_position_in_grid]], - uint tpitg[[thread_position_in_threadgroup]], - uint sgitg[[simdgroup_index_in_threadgroup]], - uint tiisg[[thread_index_in_simdgroup]], - uint ntg[[threads_per_threadgroup]]) { - device const float * x_row = (device const float *) ((device const char *) src0 + tgpig * args.nb01); - - float lmax = -INFINITY; - int32_t larg = -1; - - for (int i00 = tpitg; i00 < args.ne00; i00 += ntg) { - if (x_row[i00] > lmax) { - lmax = x_row[i00]; - larg = i00; - } - } - - // find the argmax value in the block - float max_val = simd_max(lmax); - int32_t arg_val = simd_max(select(-1, larg, lmax == max_val)); - - device int32_t * dst_i32 = (device int32_t *) dst; - - threadgroup float * shared_maxval = (threadgroup float *) shmem; - threadgroup int32_t * shared_argmax = (threadgroup int32_t *) shmem + N_SIMDWIDTH; - - if (ntg > N_SIMDWIDTH) { - if (sgitg == 0) { - shared_maxval[tiisg] = -INFINITY; - shared_argmax[tiisg] = -1; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - shared_maxval[sgitg] = max_val; - shared_argmax[sgitg] = arg_val; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - max_val = shared_maxval[tiisg]; - arg_val = shared_argmax[tiisg]; - - float max_val_reduced = simd_max(max_val); - int32_t arg_val_reduced = simd_max(select(-1, arg_val, max_val == max_val_reduced)); - - dst_i32[tgpig] = arg_val_reduced; - - return; - } - - dst_i32[tgpig] = arg_val; -} - -// F == 1 : norm (no fuse) -// F == 2 : norm + mul -// F == 3 : norm + mul + add -template -kernel void kernel_norm_fuse_impl( - constant ggml_metal_kargs_norm & args, - device const char * src0, - device const char * src1_0, - device const char * src1_1, - device char * dst, - threadgroup float * shmem_f32 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - if (sgitg == 0) { - shmem_f32[tiisg] = 0.0f; - } - - const int i01 = tgpig.x; - const int i02 = tgpig.y; - const int i03 = tgpig.z; - - device const T * x = (device const T *) (src0 + i03*args.nbf3[0] + i02*args.nbf2[0] + i01*args.nbf1[0]); - - device const T * f0 = (device const T *) (src1_0 + (i03%args.nef3[1])*args.nbf3[1] + (i02%args.nef2[1])*args.nbf2[1] + (i01%args.nef1[1])*args.nbf1[1]); - device const T * f1 = (device const T *) (src1_1 + (i03%args.nef3[2])*args.nbf3[2] + (i02%args.nef2[2])*args.nbf2[2] + (i01%args.nef1[2])*args.nbf1[2]); - - T sumft(0.0f); - - float sumf = 0.0f; - - for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { - sumft += x[i00]; - } - sumf = dot(sumft, T(1.0f)); - sumf = simd_sum(sumf); - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - shmem_f32[sgitg] = sumf; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - sumf = shmem_f32[tiisg]; - sumf = simd_sum(sumf); - - const float mean = sumf/args.ne00; - - device T * y = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1); - - sumf = 0.0f; - for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { - y[i00] = x[i00] - mean; - sumf += dot(y[i00], y[i00]); - } - sumf = simd_sum(sumf); - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - shmem_f32[sgitg] = sumf; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - sumf = shmem_f32[tiisg]; - sumf = simd_sum(sumf); - - const float variance = sumf/args.ne00; - - const float scale = 1.0f/sqrt(variance + args.eps); - for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { - if (F == 1) { - y[i00] = (y[i00]*scale); - } - if (F == 2) { - y[i00] = (y[i00]*scale)*f0[i00]; - } - if (F == 3) { - y[i00] = (y[i00]*scale)*f0[i00] + f1[i00]; - } - } -} - -typedef decltype(kernel_norm_fuse_impl) kernel_norm_fuse_t; - -template [[host_name("kernel_norm_f32")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; -template [[host_name("kernel_norm_mul_f32")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; -template [[host_name("kernel_norm_mul_add_f32")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; - -template [[host_name("kernel_norm_f32_4")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; -template [[host_name("kernel_norm_mul_f32_4")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; -template [[host_name("kernel_norm_mul_add_f32_4")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; - -// F == 1 : rms_norm (no fuse) -// F == 2 : rms_norm + mul -// F == 3 : rms_norm + mul + add -template -kernel void kernel_rms_norm_fuse_impl( - constant ggml_metal_kargs_norm & args, - device const char * src0, - device const char * src1_0, - device const char * src1_1, - device char * dst, - threadgroup float * shmem_f32 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - if (sgitg == 0) { - shmem_f32[tiisg] = 0.0f; - } - - const int i01 = tgpig.x; - const int i02 = tgpig.y; - const int i03 = tgpig.z; - - device const T * x = (device const T *) (src0 + i03*args.nbf3[0] + i02*args.nbf2[0] + i01*args.nbf1[0]); - - device const T * f0 = (device const T *) (src1_0 + (i03%args.nef3[1])*args.nbf3[1] + (i02%args.nef2[1])*args.nbf2[1] + (i01%args.nef1[1])*args.nbf1[1]); - device const T * f1 = (device const T *) (src1_1 + (i03%args.nef3[2])*args.nbf3[2] + (i02%args.nef2[2])*args.nbf2[2] + (i01%args.nef1[2])*args.nbf1[2]); - - float sumf = 0.0f; - - // parallel sum - for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { - sumf += dot(x[i00], x[i00]); - } - sumf = simd_sum(sumf); - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - shmem_f32[sgitg] = sumf; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - sumf = shmem_f32[tiisg]; - sumf = simd_sum(sumf); - - const float mean = sumf/args.ne00; - const float scale = 1.0f/sqrt(mean + args.eps); - - device T * y = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1); - for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { - if (F == 1) { - y[i00] = (x[i00]*scale); - } - if (F == 2) { - y[i00] = (x[i00]*scale)*f0[i00]; - } - if (F == 3) { - y[i00] = (x[i00]*scale)*f0[i00] + f1[i00]; - } - } -} - -typedef decltype(kernel_rms_norm_fuse_impl) kernel_rms_norm_fuse_t; - -template [[host_name("kernel_rms_norm_f32")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; -template [[host_name("kernel_rms_norm_mul_f32")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; -template [[host_name("kernel_rms_norm_mul_add_f32")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; - -template [[host_name("kernel_rms_norm_f32_4")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; -template [[host_name("kernel_rms_norm_mul_f32_4")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; -template [[host_name("kernel_rms_norm_mul_add_f32_4")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; - -template -kernel void kernel_l2_norm_impl( - constant ggml_metal_kargs_l2_norm & args, - device const char * src0, - device char * dst, - threadgroup float * shmem_f32 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int i03 = tgpig.z; - const int i02 = tgpig.y; - const int i01 = tgpig.x; - - if (sgitg == 0) { - shmem_f32[tiisg] = 0.0f; - } - - device const T0 * x = (device const T0 *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); - device T * y = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1); - - float sumf = 0.0f; - - // parallel sum - for (int i00 = tpitg.x; i00 < args.ne00; i00 += ntg.x) { - sumf += dot(x[i00], x[i00]); - } - sumf = simd_sum(sumf); - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - shmem_f32[sgitg] = sumf; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - sumf = shmem_f32[tiisg]; - sumf = simd_sum(sumf); - - const float scale = 1.0f/max(sqrt(sumf), args.eps); - - for (int i00 = tpitg.x; i00 < args.ne00; i00 += ntg.x) { - y[i00] = x[i00] * scale; - } -} - -typedef decltype(kernel_l2_norm_impl) kernel_l2_norm_t; - -template [[host_name("kernel_l2_norm_f32_f32")]] kernel kernel_l2_norm_t kernel_l2_norm_impl; -template [[host_name("kernel_l2_norm_f32_f32_4")]] kernel kernel_l2_norm_t kernel_l2_norm_impl; - -kernel void kernel_group_norm_f32( - constant ggml_metal_kargs_group_norm & args, - device const float * src0, - device float * dst, - threadgroup float * buf [[threadgroup(0)]], - uint tgpig[[threadgroup_position_in_grid]], - uint tpitg[[thread_position_in_threadgroup]], - uint sgitg[[simdgroup_index_in_threadgroup]], - uint tiisg[[thread_index_in_simdgroup]], - uint ntg[[threads_per_threadgroup]]) { - const int64_t ne = args.ne00*args.ne01*args.ne02; - const int64_t gs = args.ne00*args.ne01*((args.ne02 + args.ngrp - 1) / args.ngrp); - - int start = tgpig * gs; - int end = start + gs; - - start += tpitg; - - if (end >= ne) { - end = ne; - } - - float tmp = 0.0f; // partial sum for thread in warp - - for (int j = start; j < end; j += ntg) { - tmp += src0[j]; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - tmp = simd_sum(tmp); - if (ntg > N_SIMDWIDTH) { - if (sgitg == 0) { - buf[tiisg] = 0.0f; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - buf[sgitg] = tmp; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - tmp = buf[tiisg]; - tmp = simd_sum(tmp); - } - - const float mean = tmp / gs; - tmp = 0.0f; - - for (int j = start; j < end; j += ntg) { - float xi = src0[j] - mean; - dst[j] = xi; - tmp += xi * xi; - } - - tmp = simd_sum(tmp); - if (ntg > N_SIMDWIDTH) { - if (sgitg == 0) { - buf[tiisg] = 0.0f; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tiisg == 0) { - buf[sgitg] = tmp; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - tmp = buf[tiisg]; - tmp = simd_sum(tmp); - } - - const float variance = tmp / gs; - const float scale = 1.0f/sqrt(variance + args.eps); - for (int j = start; j < end; j += ntg) { - dst[j] *= scale; - } -} - -// Q1_0 dot product: dot = d * (2 * Σ(yl[i] where bit=1) - sumy) -inline float block_q_n_dot_y(device const block_q1_0 * qb_curr, float sumy, thread float * yl, int il) { - device const uint8_t * qs = qb_curr->qs + il / 8; - const uint8_t b0 = qs[0]; - const uint8_t b1 = qs[1]; - - float acc = 0.0f; - - acc += select(0.0f, yl[ 0], bool(b0 & 0x01)); - acc += select(0.0f, yl[ 1], bool(b0 & 0x02)); - acc += select(0.0f, yl[ 2], bool(b0 & 0x04)); - acc += select(0.0f, yl[ 3], bool(b0 & 0x08)); - acc += select(0.0f, yl[ 4], bool(b0 & 0x10)); - acc += select(0.0f, yl[ 5], bool(b0 & 0x20)); - acc += select(0.0f, yl[ 6], bool(b0 & 0x40)); - acc += select(0.0f, yl[ 7], bool(b0 & 0x80)); - - acc += select(0.0f, yl[ 8], bool(b1 & 0x01)); - acc += select(0.0f, yl[ 9], bool(b1 & 0x02)); - acc += select(0.0f, yl[10], bool(b1 & 0x04)); - acc += select(0.0f, yl[11], bool(b1 & 0x08)); - acc += select(0.0f, yl[12], bool(b1 & 0x10)); - acc += select(0.0f, yl[13], bool(b1 & 0x20)); - acc += select(0.0f, yl[14], bool(b1 & 0x40)); - acc += select(0.0f, yl[15], bool(b1 & 0x80)); - - return qb_curr->d * (2.0f * acc - sumy); -} - -// function for calculate inner product between half a q4_0 block and 16 floats (yl), sumy is SUM(yl[i]) -// il indicates where the q4 quants begin (0 or QK4_0/4) -// we assume that the yl's have been multiplied with the appropriate scale factor -// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096) -inline float block_q_n_dot_y(device const block_q4_0 * qb_curr, float sumy, thread float * yl, int il) { - float d = qb_curr->d; - - float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - - device const uint16_t * qs = ((device const uint16_t *) qb_curr + 1 + il/2); - - for (int i = 0; i < 8; i += 2) { - acc[0] += yl[i + 0] * (qs[i / 2] & 0x000F); - acc[1] += yl[i + 1] * (qs[i / 2] & 0x0F00); - acc[2] += yl[i + 8] * (qs[i / 2] & 0x00F0); - acc[3] += yl[i + 9] * (qs[i / 2] & 0xF000); - } - - return d * (sumy * -8.f + acc[0] + acc[1] + acc[2] + acc[3]); -} - -// function for calculate inner product between half a q4_1 block and 16 floats (yl), sumy is SUM(yl[i]) -// il indicates where the q4 quants begin (0 or QK4_0/4) -// we assume that the yl's have been multiplied with the appropriate scale factor -// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096) -inline float block_q_n_dot_y(device const block_q4_1 * qb_curr, float sumy, thread float * yl, int il) { - float d = qb_curr->d; - float m = qb_curr->m; - - float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - - device const uint16_t * qs = ((device const uint16_t *) qb_curr + 2 + il/2); - - for (int i = 0; i < 8; i+=2) { - acc[0] += yl[i + 0] * (qs[i / 2] & 0x000F); - acc[1] += yl[i + 1] * (qs[i / 2] & 0x0F00); - acc[2] += yl[i + 8] * (qs[i / 2] & 0x00F0); - acc[3] += yl[i + 9] * (qs[i / 2] & 0xF000); - } - - return d * (acc[0] + acc[1] + acc[2] + acc[3]) + sumy * m; -} - -// function for calculate inner product between half a q5_0 block and 16 floats (yl), sumy is SUM(yl[i]) -// il indicates where the q5 quants begin (0 or QK5_0/4) -// we assume that the yl's have been multiplied with the appropriate scale factor -// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096) -inline float block_q_n_dot_y(device const block_q5_0 * qb_curr, float sumy, thread float * yl, int il) { - float d = qb_curr->d; - - float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - - device const uint16_t * qs = ((device const uint16_t *)qb_curr + 3 + il/2); - const uint32_t qh = *((device const uint32_t *)qb_curr->qh); - - for (int i = 0; i < 8; i+=2) { - acc[0] += yl[i + 0] * ((qs[i / 2] & 0x000F) | ((qh >> (i+0+il ) << 4 ) & 0x00010)); - acc[1] += yl[i + 1] * ((qs[i / 2] & 0x0F00) | ((qh >> (i+1+il ) << 12) & 0x01000)); - acc[2] += yl[i + 8] * ((qs[i / 2] & 0x00F0) | ((qh >> (i+0+il+QK5_0/2) << 8 ) & 0x00100)); - acc[3] += yl[i + 9] * ((qs[i / 2] & 0xF000) | ((qh >> (i+1+il+QK5_0/2) << 16) & 0x10000)); - } - - return d * (sumy * -16.f + acc[0] + acc[1] + acc[2] + acc[3]); -} - -// function for calculate inner product between half a q5_1 block and 16 floats (yl), sumy is SUM(yl[i]) -// il indicates where the q5 quants begin (0 or QK5_1/4) -// we assume that the yl's have been multiplied with the appropriate scale factor -// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096) -inline float block_q_n_dot_y(device const block_q5_1 * qb_curr, float sumy, thread float * yl, int il) { - float d = qb_curr->d; - float m = qb_curr->m; - - float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - - device const uint16_t * qs = ((device const uint16_t *)qb_curr + 4 + il/2); - const uint32_t qh = *((device const uint32_t *)qb_curr->qh); - - for (int i = 0; i < 8; i+=2) { - acc[0] += yl[i + 0] * ((qs[i / 2] & 0x000F) | ((qh >> (i+0+il ) << 4 ) & 0x00010)); - acc[1] += yl[i + 1] * ((qs[i / 2] & 0x0F00) | ((qh >> (i+1+il ) << 12) & 0x01000)); - acc[2] += yl[i + 8] * ((qs[i / 2] & 0x00F0) | ((qh >> (i+0+il+QK5_0/2) << 8 ) & 0x00100)); - acc[3] += yl[i + 9] * ((qs[i / 2] & 0xF000) | ((qh >> (i+1+il+QK5_0/2) << 16) & 0x10000)); - } - - return d * (acc[0] + acc[1] + acc[2] + acc[3]) + sumy * m; -} - -template -static inline void helper_mv_reduce_and_write( - device float * dst_f32, - float sumf[NR0], - const int r0, - const int ne01, - ushort tiisg, - ushort sgitg, - threadgroup char * shmem) { - constexpr short NW = N_SIMDWIDTH; - - threadgroup float * shmem_f32[NR0]; - - for (short row = 0; row < NR0; ++row) { - shmem_f32[row] = (threadgroup float *) shmem + NW*row; - - if (sgitg == 0) { - shmem_f32[row][tiisg] = 0.0f; - } - - sumf[row] = simd_sum(sumf[row]); - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - for (short row = 0; row < NR0; ++row) { - if (tiisg == 0) { - shmem_f32[row][sgitg] = sumf[row]; - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - for (short row = 0; row < NR0 && r0 + row < ne01; ++row) { - float tot = simd_sum(shmem_f32[row][tiisg]); - - if (tiisg == 0 && sgitg == 0) { - dst_f32[r0 + row] = tot; - } - } -} - -constant short FC_mul_mv_nsg [[function_constant(FC_MUL_MV + 0)]]; -constant short FC_mul_mv_nxpsg [[function_constant(FC_MUL_MV + 1)]]; -constant short FC_mul_mv_ne12 [[function_constant(FC_MUL_MV + 2)]]; -constant short FC_mul_mv_r2 [[function_constant(FC_MUL_MV + 3)]]; -constant short FC_mul_mv_r3 [[function_constant(FC_MUL_MV + 4)]]; - -template -void mul_vec_q_n_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - constexpr short NW = N_SIMDWIDTH; - constexpr short NQ = 16; - - const int nb = args.ne00/QK4_0; - - const int r0 = (tgpig.x*NSG + sgitg)*NR0; - //const int r0 = tgpig.x*NR0; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - //const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - //device const block_q_type * x = (device const block_q_type *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - // pointers to src0 rows - device const block_q_type * ax[NR0]; - FOR_UNROLL (int row = 0; row < NR0; ++row) { - const uint64_t offset0 = (r0 + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - - ax[row] = (device const block_q_type *) ((device char *) src0 + offset0); - } - - float sumf[NR0] = {0.f}; - - const short ix = (tiisg/(NW/NQ)); - const short il = (tiisg%(NW/NQ))*8; - - //const int ib0 = sgitg*NQ + ix; - const int ib0 = ix; - - float yl[16]; // src1 vector cache - - //device const float * yb = y + ix*QK4_0 + il; - device const float * yb = y + ib0*QK4_0 + il; - - // each thread in a SIMD group deals with half a block. - //for (int ib = ib0; ib < nb; ib += NSG*NQ) { - for (int ib = ib0; ib < nb; ib += NQ) { - float sumy[2] = { 0.f, 0.f }; - - FOR_UNROLL (short i = 0; i < 8; i += 2) { - sumy[0] += yb[i + 0] + yb[i + 1]; - yl[i + 0] = yb[i + 0]; - yl[i + 1] = yb[i + 1]/256.f; - - sumy[1] += yb[i + 16] + yb[i + 17]; - yl[i + 8] = yb[i + 16]/16.f; - yl[i + 9] = yb[i + 17]/4096.f; - } - - FOR_UNROLL (short row = 0; row < NR0; row++) { - sumf[row] += block_q_n_dot_y(ax[row] + ib, sumy[0] + sumy[1], yl, il); - } - - yb += QK4_0 * 16; - //yb += NSG*NQ*QK4_0; - } - - device float * dst_f32 = (device float *) dst + im*args.ne0*args.ne1 + r1*args.ne0; - - //helper_mv_reduce_and_write(dst_f32, sumf, r0, args.ne01, tiisg, sgitg, shmem); - - for (int row = 0; row < NR0; ++row) { - const float tot = simd_sum(sumf[row]); - - if (tiisg == 0 && r0 + row < args.ne01) { - dst_f32[r0 + row] = tot; - } - } -} - -template -void kernel_mul_mv_q1_0_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK1_0; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset1 = r1*args.nb11 + (i12)*args.nb12 + (i13)*args.nb13; - - device const float * y = (device const float *) (src1 + offset1); - - device const block_q1_0 * ax[nr0]; - for (int row = 0; row < nr0; ++row) { - const uint64_t offset0 = (first_row + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - ax[row] = (device const block_q1_0 *) ((device char *) src0 + offset0); - } - - float yl[16]; - float sumf[nr0] = {0.f}; - - const short ix = (tiisg/8); - const short il = (tiisg%8)*16; - - device const float * yb = y + ix*QK1_0 + il; - - for (int ib = ix; ib < nb; ib += N_SIMDWIDTH/8) { - float sumy = 0.f; - - FOR_UNROLL (short i = 0; i < 16; i++) { - yl[i] = yb[i]; - sumy += yb[i]; - } - - FOR_UNROLL (short row = 0; row < nr0; row++) { - sumf[row] += block_q_n_dot_y(ax[row] + ib, sumy, yl, il); - } - - yb += QK1_0 * (N_SIMDWIDTH/8); - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0; ++row) { - const float tot = simd_sum(sumf[row]); - - if (tiisg == 0 && first_row + row < args.ne01) { - dst_f32[first_row + row] = tot; - } - } -} - -[[host_name("kernel_mul_mv_q1_0_f32")]] -kernel void kernel_mul_mv_q1_0_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - kernel_mul_mv_q1_0_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); -} - -kernel void kernel_mul_mv_q4_0_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - mul_vec_q_n_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -kernel void kernel_mul_mv_q4_1_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - mul_vec_q_n_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -kernel void kernel_mul_mv_q5_0_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - mul_vec_q_n_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -kernel void kernel_mul_mv_q5_1_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - mul_vec_q_n_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_q8_0_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - constexpr short NW = N_SIMDWIDTH; - constexpr short NQ = 8; - - const int nb = args.ne00/QK8_0; - - const int r0 = tgpig.x*NR0; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - //const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - //device const block_q8_0 * x = (device const block_q8_0 *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - // pointers to src0 rows - device const block_q8_0 * ax[NR0]; - FOR_UNROLL (short row = 0; row < NR0; ++row) { - const uint64_t offset0 = (r0 + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - - ax[row] = (device const block_q8_0 *) ((device char *) src0 + offset0); - } - - float sumf[NR0] = { 0.f }; - - const short ix = tiisg/(NW/NQ); - const short il = tiisg%(NW/NQ); - - const int ib0 = sgitg*NQ + ix; - - float yl[NQ]; - - device const float * yb = y + ib0*QK8_0 + il*NQ; - - // each thread in a SIMD group deals with NQ quants at a time - for (int ib = ib0; ib < nb; ib += NSG*NQ) { - for (short i = 0; i < NQ; ++i) { - yl[i] = yb[i]; - } - - for (short row = 0; row < NR0; row++) { - device const int8_t * qs = ax[row][ib].qs + il*NQ; - - float sumq = 0.f; - FOR_UNROLL (short i = 0; i < NQ; ++i) { - sumq += qs[i] * yl[i]; - } - - sumf[row] += sumq*ax[row][ib].d; - } - - yb += NSG*NQ*QK8_0; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - helper_mv_reduce_and_write(dst_f32, sumf, r0, args.ne01, tiisg, sgitg, shmem); -} - -[[host_name("kernel_mul_mv_q8_0_f32")]] -kernel void kernel_mul_mv_q8_0_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - kernel_mul_mv_q8_0_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -// mat-vec kernel processing in chunks of float4 -// chpb - chunks per quantization block -template -void kernel_mul_mv_ext_q4_f32_impl( - constant ggml_metal_kargs_mul_mv_ext & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - const short NSG = FC_mul_mv_nsg; - const short nxpsg = FC_mul_mv_nxpsg; - - const short chpt = 4; // chunks per thread - - //const short nxpsg = (32); - const short nypsg = (32/nxpsg); - - const short tx = tiisg%nxpsg; - const short ty = tiisg/nxpsg; - - const int i01 = tgpig.x*(nypsg*NSG) + nypsg*sgitg + ty; - const int i11 = tgpig.y*r1ptg; - const int i1m = tgpig.z; - - const int i12 = i1m%FC_mul_mv_ne12; - const int i13 = i1m/FC_mul_mv_ne12; - - const uint64_t offset0 = i01*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = i11*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const q_t * xq = (i01 < args.ne01) ? (device const q_t *) (src0 + offset0) + tx/chpb : (device const q_t *) src0; - - device const float4 * y4[r1ptg]; - - for (int ir1 = 0; ir1 < r1ptg; ++ir1) { - y4[ir1] = (i11 + ir1 < args.ne11) ? (device const float4 *) (src1 + offset1 + ir1*args.nb11) + tx : (device const float4 *) src1; - } - - float sumf[r1ptg] = { [ 0 ... r1ptg - 1 ] = 0.0f }; - - short cch = tx%chpb; // current chunk index - - for (int ich = tx; 4*ich < args.ne00; ich += chpt*nxpsg) { - float4 lx[chpt]; - -#pragma unroll(chpt) - for (short ch = 0; ch < chpt; ++ch) { - deq_t4(xq, cch, lx[ch]); - - cch += nxpsg; - if (cch >= chpb) { - xq += cch/chpb; - cch %= chpb; - } - } - -#pragma unroll(chpt) - for (short ch = 0; ch < chpt; ++ch) { -#pragma unroll(r1ptg) - for (short ir1 = 0; ir1 < r1ptg; ++ir1) { - sumf[ir1] += dot(lx[ch], y4[ir1][ch*nxpsg]); - } - } - -#pragma unroll(r1ptg) - for (short ir1 = 0; ir1 < r1ptg; ++ir1) { - y4[ir1] += chpt*nxpsg; - } - } - - // reduce only the threads in each row - for (short ir1 = 0; ir1 < r1ptg; ++ir1) { - if (nxpsg >= 32) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 16); - } - if (nxpsg >= 16) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 8); - } - if (nxpsg >= 8) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 4); - } - if (nxpsg >= 4) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 2); - } - if (nxpsg >= 2) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 1); - } - - //sumf[ir1] = simd_sum(sumf[ir1]); - } - - if (tx == 0) { - for (short ir1 = 0; ir1 < r1ptg && i11 + ir1 < args.ne11; ++ir1) { - device float * dst_f32 = (device float *) dst + (uint64_t)i1m*args.ne0*args.ne1 + (uint64_t)(i11 + ir1)*args.ne0; - - if (i01 < args.ne01) { - dst_f32[i01] = sumf[ir1]; - } - } - } -} - -// mat-vec kernel processing in chunks of float4x4 -template -void kernel_mul_mv_ext_q4x4_f32_impl( - constant ggml_metal_kargs_mul_mv_ext & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - const short NSG = FC_mul_mv_nsg; - const short nxpsg = FC_mul_mv_nxpsg; - - const short chpt = 1; - - //const short nxpsg = (32); - const short nypsg = (32/nxpsg); - - const short tx = tiisg%nxpsg; - const short ty = tiisg/nxpsg; - - const int i01 = tgpig.x*(nypsg*NSG) + nypsg*sgitg + ty; - const int i11 = tgpig.y*r1ptg; - const int i1m = tgpig.z; - - const int i12 = i1m%FC_mul_mv_ne12; - const int i13 = i1m/FC_mul_mv_ne12; - - const uint64_t offset0 = i01*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = i11*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const q_t * xq = (i01 < args.ne01) ? (device const q_t *) (src0 + offset0) + tx/chpb : (device const q_t *) src0; - - device const float4x4 * y4x4[r1ptg]; - - for (int ir1 = 0; ir1 < r1ptg; ++ir1) { - y4x4[ir1] = (i11 + ir1 < args.ne11) ? (device const float4x4 *) (src1 + offset1 + ir1*args.nb11) + tx : (device const float4x4 *) src1; - } - - float sumf[r1ptg] = { [ 0 ... r1ptg - 1 ] = 0.0f }; - - short cch = tx%chpb; - - for (int ich = tx; 16*ich < args.ne00; ich += chpt*nxpsg) { - float4x4 lx[chpt]; - -#pragma unroll(chpt) - for (short ch = 0; ch < chpt; ++ch) { - deq_t4x4(xq, cch, lx[ch]); - - cch += nxpsg; - if (cch >= chpb) { - xq += cch/chpb; - cch %= chpb; - } - } - -#pragma unroll(chpt) - for (short ch = 0; ch < chpt; ++ch) { -#pragma unroll(r1ptg) - for (short ir1 = 0; ir1 < r1ptg; ++ir1) { - sumf[ir1] += - dot(lx[ch][0], y4x4[ir1][ch*nxpsg][0]) + - dot(lx[ch][1], y4x4[ir1][ch*nxpsg][1]) + - dot(lx[ch][2], y4x4[ir1][ch*nxpsg][2]) + - dot(lx[ch][3], y4x4[ir1][ch*nxpsg][3]); - - } - } - -#pragma unroll(r1ptg) - for (short ir1 = 0; ir1 < r1ptg; ++ir1) { - y4x4[ir1] += chpt*nxpsg; - } - } - - for (short ir1 = 0; ir1 < r1ptg; ++ir1) { - if (nxpsg >= 32) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 16); - } - if (nxpsg >= 16) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 8); - } - if (nxpsg >= 8) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 4); - } - if (nxpsg >= 4) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 2); - } - if (nxpsg >= 2) { - sumf[ir1] += simd_shuffle_down(sumf[ir1], 1); - } - - //sumf[ir1] = simd_sum(sumf[ir1]); - } - - if (tx == 0) { - for (short ir1 = 0; ir1 < r1ptg && i11 + ir1 < args.ne11; ++ir1) { - device float * dst_f32 = (device float *) dst + (uint64_t)i1m*args.ne0*args.ne1 + (uint64_t)(i11 + ir1)*args.ne0; - - if (i01 < args.ne01) { - dst_f32[i01] = sumf[ir1]; - } - } - } -} - -// dispatchers needed for compile-time nxpsg -// epb - elements per quantization block -template -kernel void kernel_mul_mv_ext_q4_f32_disp( - constant ggml_metal_kargs_mul_mv_ext & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - kernel_mul_mv_ext_q4_f32_impl(args, src0, src1, dst, tgpig, tiisg, sgitg); -} - -template -kernel void kernel_mul_mv_ext_q4x4_f32_disp( - constant ggml_metal_kargs_mul_mv_ext & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - kernel_mul_mv_ext_q4x4_f32_impl(args, src0, src1, dst, tgpig, tiisg, sgitg); -} - -typedef decltype(kernel_mul_mv_ext_q4_f32_disp <2, block_q8_0, 32, dequantize_q8_0_t4>) mul_mv_ext_q4_f32_t; -typedef decltype(kernel_mul_mv_ext_q4x4_f32_disp<2, block_q4_K, 256, dequantize_q4_K>) mul_mv_ext_q4x4_f32_t; - -template [[host_name("kernel_mul_mv_ext_f32_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, float4, 4, dequantize_f32_t4>; -template [[host_name("kernel_mul_mv_ext_f32_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, float4, 4, dequantize_f32_t4>; -template [[host_name("kernel_mul_mv_ext_f32_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, float4, 4, dequantize_f32_t4>; -template [[host_name("kernel_mul_mv_ext_f32_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, float4, 4, dequantize_f32_t4>; - -template [[host_name("kernel_mul_mv_ext_f16_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, half4, 4, dequantize_f16_t4>; -template [[host_name("kernel_mul_mv_ext_f16_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, half4, 4, dequantize_f16_t4>; -template [[host_name("kernel_mul_mv_ext_f16_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, half4, 4, dequantize_f16_t4>; -template [[host_name("kernel_mul_mv_ext_f16_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, half4, 4, dequantize_f16_t4>; - -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_mul_mv_ext_bf16_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, bfloat4, 4, dequantize_bf16_t4>; -template [[host_name("kernel_mul_mv_ext_bf16_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, bfloat4, 4, dequantize_bf16_t4>; -template [[host_name("kernel_mul_mv_ext_bf16_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, bfloat4, 4, dequantize_bf16_t4>; -template [[host_name("kernel_mul_mv_ext_bf16_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, bfloat4, 4, dequantize_bf16_t4>; -#endif - -template [[host_name("kernel_mul_mv_ext_q1_0_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q1_0, 128, dequantize_q1_0_t4>; -template [[host_name("kernel_mul_mv_ext_q1_0_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q1_0, 128, dequantize_q1_0_t4>; -template [[host_name("kernel_mul_mv_ext_q1_0_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q1_0, 128, dequantize_q1_0_t4>; -template [[host_name("kernel_mul_mv_ext_q1_0_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q1_0, 128, dequantize_q1_0_t4>; - -template [[host_name("kernel_mul_mv_ext_q4_0_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q4_0, 32, dequantize_q4_0_t4>; -template [[host_name("kernel_mul_mv_ext_q4_0_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q4_0, 32, dequantize_q4_0_t4>; -template [[host_name("kernel_mul_mv_ext_q4_0_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q4_0, 32, dequantize_q4_0_t4>; -template [[host_name("kernel_mul_mv_ext_q4_0_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q4_0, 32, dequantize_q4_0_t4>; - -template [[host_name("kernel_mul_mv_ext_q4_1_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q4_1, 32, dequantize_q4_1_t4>; -template [[host_name("kernel_mul_mv_ext_q4_1_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q4_1, 32, dequantize_q4_1_t4>; -template [[host_name("kernel_mul_mv_ext_q4_1_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q4_1, 32, dequantize_q4_1_t4>; -template [[host_name("kernel_mul_mv_ext_q4_1_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q4_1, 32, dequantize_q4_1_t4>; - -template [[host_name("kernel_mul_mv_ext_q5_0_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q5_0, 32, dequantize_q5_0_t4>; -template [[host_name("kernel_mul_mv_ext_q5_0_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q5_0, 32, dequantize_q5_0_t4>; -template [[host_name("kernel_mul_mv_ext_q5_0_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q5_0, 32, dequantize_q5_0_t4>; -template [[host_name("kernel_mul_mv_ext_q5_0_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q5_0, 32, dequantize_q5_0_t4>; - -template [[host_name("kernel_mul_mv_ext_q5_1_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q5_1, 32, dequantize_q5_1_t4>; -template [[host_name("kernel_mul_mv_ext_q5_1_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q5_1, 32, dequantize_q5_1_t4>; -template [[host_name("kernel_mul_mv_ext_q5_1_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q5_1, 32, dequantize_q5_1_t4>; -template [[host_name("kernel_mul_mv_ext_q5_1_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q5_1, 32, dequantize_q5_1_t4>; - -template [[host_name("kernel_mul_mv_ext_q8_0_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q8_0, 32, dequantize_q8_0_t4>; -template [[host_name("kernel_mul_mv_ext_q8_0_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q8_0, 32, dequantize_q8_0_t4>; -template [[host_name("kernel_mul_mv_ext_q8_0_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q8_0, 32, dequantize_q8_0_t4>; -template [[host_name("kernel_mul_mv_ext_q8_0_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q8_0, 32, dequantize_q8_0_t4>; - -template [[host_name("kernel_mul_mv_ext_mxfp4_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_mxfp4, 32, dequantize_mxfp4_t4>; -template [[host_name("kernel_mul_mv_ext_mxfp4_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_mxfp4, 32, dequantize_mxfp4_t4>; -template [[host_name("kernel_mul_mv_ext_mxfp4_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_mxfp4, 32, dequantize_mxfp4_t4>; -template [[host_name("kernel_mul_mv_ext_mxfp4_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_mxfp4, 32, dequantize_mxfp4_t4>; - -template [[host_name("kernel_mul_mv_ext_iq4_nl_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_iq4_nl, 32, dequantize_iq4_nl_t4>; -template [[host_name("kernel_mul_mv_ext_iq4_nl_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_iq4_nl, 32, dequantize_iq4_nl_t4>; -template [[host_name("kernel_mul_mv_ext_iq4_nl_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_iq4_nl, 32, dequantize_iq4_nl_t4>; -template [[host_name("kernel_mul_mv_ext_iq4_nl_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_iq4_nl, 32, dequantize_iq4_nl_t4>; - -template [[host_name("kernel_mul_mv_ext_q4_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q4_K, 256, dequantize_q4_K>; -template [[host_name("kernel_mul_mv_ext_q4_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q4_K, 256, dequantize_q4_K>; -template [[host_name("kernel_mul_mv_ext_q4_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q4_K, 256, dequantize_q4_K>; -template [[host_name("kernel_mul_mv_ext_q4_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q4_K, 256, dequantize_q4_K>; - -template [[host_name("kernel_mul_mv_ext_q5_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q5_K, 256, dequantize_q5_K>; -template [[host_name("kernel_mul_mv_ext_q5_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q5_K, 256, dequantize_q5_K>; -template [[host_name("kernel_mul_mv_ext_q5_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q5_K, 256, dequantize_q5_K>; -template [[host_name("kernel_mul_mv_ext_q5_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q5_K, 256, dequantize_q5_K>; - -template [[host_name("kernel_mul_mv_ext_q6_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q6_K, 256, dequantize_q6_K>; -template [[host_name("kernel_mul_mv_ext_q6_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q6_K, 256, dequantize_q6_K>; -template [[host_name("kernel_mul_mv_ext_q6_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q6_K, 256, dequantize_q6_K>; -template [[host_name("kernel_mul_mv_ext_q6_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q6_K, 256, dequantize_q6_K>; - -template [[host_name("kernel_mul_mv_ext_q2_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q2_K, 256, dequantize_q2_K>; -template [[host_name("kernel_mul_mv_ext_q2_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q2_K, 256, dequantize_q2_K>; -template [[host_name("kernel_mul_mv_ext_q2_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q2_K, 256, dequantize_q2_K>; -template [[host_name("kernel_mul_mv_ext_q2_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q2_K, 256, dequantize_q2_K>; - -template [[host_name("kernel_mul_mv_ext_q3_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q3_K, 256, dequantize_q3_K>; -template [[host_name("kernel_mul_mv_ext_q3_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q3_K, 256, dequantize_q3_K>; -template [[host_name("kernel_mul_mv_ext_q3_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q3_K, 256, dequantize_q3_K>; -template [[host_name("kernel_mul_mv_ext_q3_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q3_K, 256, dequantize_q3_K>; - -template -void kernel_mul_mv_t_t_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - constexpr short NW = N_SIMDWIDTH; - constexpr short NB = 32; - constexpr short NF = 8; - - const int nb = args.ne00/NB; - - const int r0 = tgpig.x*NR0; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - //const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - //device const T0 * x = (device const T0 *) (src0 + offset0); - device const T1 * y = (device const T1 *) (src1 + offset1); - - // pointers to src0 rows - device const T0 * ax [NR0]; - FOR_UNROLL (short row = 0; row < NR0; ++row) { - const uint64_t offset0 = (r0 + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - - ax[row] = (device const T0 *) ((device char *) src0 + offset0); - } - - float sumf[NR0] = { 0.f }; - - const short ix = tiisg/(NW/NF); - const short il = tiisg%(NW/NF); - - const int ib0 = sgitg*NF + ix; - - T1 yl[NF]; - - device const T1 * yb = y + (ib0*NB + il*NF); - - for (int ib = ib0; ib < nb; ib += NSG*NF) { - for (short i = 0; i < NF; ++i) { - yl[i] = yb[i]; - } - - for (short row = 0; row < NR0; row++) { - device const T0 * xb = ax[row] + (ib*NB + il*NF); - - float sumq = 0.f; - FOR_UNROLL (short i = 0; i < NF; ++i) { - sumq += xb[i] * yl[i]; - } - - sumf[row] += sumq; - } - - yb += NSG*NF*NW; - } - - for (int i = nb*NB + sgitg*NW + tiisg; i < args.ne00; i += NW*NSG) { - for (short row = 0; row < NR0; row++) { - sumf[row] += ax[row][i] * y[i]; - } - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - helper_mv_reduce_and_write(dst_f32, sumf, r0, args.ne01, tiisg, sgitg, shmem); -} - -template -void kernel_mul_mv_t_t_disp( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - switch (args.nr0) { - //case 1: kernel_mul_mv_t_t_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; - case 2: kernel_mul_mv_t_t_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; - //case 3: kernel_mul_mv_t_t_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; - //case 4: kernel_mul_mv_t_t_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; - } -} - -template -kernel void kernel_mul_mv_t_t( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - kernel_mul_mv_t_t_disp(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -typedef decltype(kernel_mul_mv_t_t) mul_mv_t_t; - -template [[host_name("kernel_mul_mv_f32_f32")]] kernel mul_mv_t_t kernel_mul_mv_t_t; -template [[host_name("kernel_mul_mv_f16_f32")]] kernel mul_mv_t_t kernel_mul_mv_t_t; -template [[host_name("kernel_mul_mv_f16_f16")]] kernel mul_mv_t_t kernel_mul_mv_t_t; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_mul_mv_bf16_f32")]] kernel mul_mv_t_t kernel_mul_mv_t_t; -template [[host_name("kernel_mul_mv_bf16_bf16")]] kernel mul_mv_t_t kernel_mul_mv_t_t; -#endif - -template -void kernel_mul_mv_t_t_4_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - constexpr short NW = N_SIMDWIDTH; - constexpr short NB = 32; - constexpr short NF = 16; - constexpr short NF4 = NF/4; - - const int nb = args.ne00/NB; - - const int r0 = tgpig.x*NR0; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - //const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const T1 * y = (device const T1 *) (src1 + offset1); - device const T14 * y4 = (device const T14 *) (src1 + offset1); - - // pointers to src0 rows - device const T0 * ax [NR0]; - device const T04 * ax4[NR0]; - FOR_UNROLL (short row = 0; row < NR0; ++row) { - const uint64_t offset0 = (r0 + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - - ax [row] = (device const T0 *) ((device char *) src0 + offset0); - ax4[row] = (device const T04 *) ((device char *) src0 + offset0); - } - - float sumf[NR0] = { 0.f }; - - const short ix = tiisg/(NW/NF); - const short il = tiisg%(NW/NF); - - const int ib0 = sgitg*NF + ix; - - T14 yl4[NF4]; - - device const T14 * yb4 = y4 + (ib0*NB + il*NF)/4; - - for (int ib = ib0; ib < nb; ib += NSG*NF) { - for (short i = 0; i < NF4; ++i) { - yl4[i] = yb4[i]; - } - - for (short row = 0; row < NR0; row++) { - device const T04 * xb4 = ax4[row] + (ib*NB + il*NF)/4; - - float sumq = 0.f; - FOR_UNROLL (short i = 0; i < NF4; ++i) { - sumq += dot(float4(xb4[i]), float4(yl4[i])); - } - - sumf[row] += sumq; - } - - yb4 += NSG*NF*NW/4; - } - - for (int i = nb*NB + sgitg*NW + tiisg; i < args.ne00; i += NW*NSG) { - for (short row = 0; row < NR0; row++) { - sumf[row] += ax[row][i] * y[i]; - } - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - helper_mv_reduce_and_write(dst_f32, sumf, r0, args.ne01, tiisg, sgitg, shmem); -} - -template -void kernel_mul_mv_t_t_4_disp( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - switch (args.nr0) { - //case 1: kernel_mul_mv_t_t_4_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; - case 2: kernel_mul_mv_t_t_4_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; - //case 3: kernel_mul_mv_t_t_4_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; - //case 4: kernel_mul_mv_t_t_4_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; - }; -} - -template -kernel void kernel_mul_mv_t_t_4( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - kernel_mul_mv_t_t_4_disp(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -typedef decltype(kernel_mul_mv_t_t_4) mul_mv_t_t_4; - -template [[host_name("kernel_mul_mv_f32_f32_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; -template [[host_name("kernel_mul_mv_f16_f32_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; -template [[host_name("kernel_mul_mv_f16_f16_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_mul_mv_bf16_f32_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; -template [[host_name("kernel_mul_mv_bf16_bf16_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; -#endif - -template -void kernel_mul_mv_t_t_short_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig, - ushort tiisg) { - const int r0 = tgpig.x*32 + tiisg; - const int r1 = tgpig.y; - const int im = tgpig.z; - - if (r0 >= args.ne01) { - return; - } - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - - device const T0 * x = (device const T0 *) (src0 + offset0); - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1; - - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const T1 * y = (device const T1 *) (src1 + offset1); - - float res = 0.0f; - - for (int i = 0; i < args.ne00; ++i) { - res += (float) x[i] * (float) y[i]; - } - - dst_f32[(uint64_t)r1*args.ne0 + r0] = res; -} - -template -kernel void kernel_mul_mv_t_t_short( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]]) { - kernel_mul_mv_t_t_short_impl( - args, - src0, - src1, - dst, - tgpig, - tiisg); -} - -typedef decltype(kernel_mul_mv_t_t_short) mul_mv_t_t_short_t; - -template [[host_name("kernel_mul_mv_f32_f32_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; -template [[host_name("kernel_mul_mv_f16_f32_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; -template [[host_name("kernel_mul_mv_f16_f16_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_mul_mv_bf16_f32_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; -template [[host_name("kernel_mul_mv_bf16_bf16_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; -#endif - -constant bool FC_rope_is_imrope [[function_constant(FC_ROPE + 0)]]; - -static float rope_yarn_ramp(const float low, const float high, const int i0) { - const float y = (i0 / 2 - low) / max(0.001f, high - low); - return 1.0f - min(1.0f, max(0.0f, y)); -} - -// YaRN algorithm based on LlamaYaRNScaledRotaryEmbedding.py from https://github.com/jquesnelle/yarn -// MIT licensed. Copyright (c) 2023 Jeffrey Quesnelle and Bowen Peng. -static void rope_yarn( - float theta_extrap, float freq_scale, float corr_dims[2], int i0, float ext_factor, float mscale, - thread float * cos_theta, thread float * sin_theta) { - // Get n-d rotational scaling corrected for extrapolation - float theta_interp = freq_scale * theta_extrap; - float theta = theta_interp; - if (ext_factor != 0.0f) { - float ramp_mix = rope_yarn_ramp(corr_dims[0], corr_dims[1], i0) * ext_factor; - theta = theta_interp * (1 - ramp_mix) + theta_extrap * ramp_mix; - - // Get n-d magnitude scaling corrected for interpolation - mscale *= 1.0f + 0.1f * log(1.0f / freq_scale); - } - *cos_theta = cos(theta) * mscale; - *sin_theta = sin(theta) * mscale; -} - -// Apparently solving `n_rot = 2pi * x * base^((2 * max_pos_emb) / n_dims)` for x, we get -// `corr_fac(n_rot) = n_dims * log(max_pos_emb / (n_rot * 2pi)) / (2 * log(base))` -static float rope_yarn_corr_factor(int n_dims, int n_ctx_orig, float n_rot, float base) { - return n_dims * log(n_ctx_orig / (n_rot * 2 * M_PI_F)) / (2 * log(base)); -} - -static void rope_yarn_corr_dims( - int n_dims, int n_ctx_orig, float freq_base, float beta_fast, float beta_slow, float dims[2] -) { - // start and end correction dims - dims[0] = max(0.0f, floor(rope_yarn_corr_factor(n_dims, n_ctx_orig, beta_fast, freq_base))); - dims[1] = min(n_dims - 1.0f, ceil(rope_yarn_corr_factor(n_dims, n_ctx_orig, beta_slow, freq_base))); -} - -template -kernel void kernel_rope_norm( - constant ggml_metal_kargs_rope & args, - device const char * src0, - device const char * src1, - device const char * src2, - device char * dst, - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 tptg [[threads_per_threadgroup]], - uint3 tgpig[[threadgroup_position_in_grid]]) { - const int i3 = tgpig[2]; - const int i2 = tgpig[1]; - const int i1 = tgpig[0]; - - float corr_dims[2]; - rope_yarn_corr_dims(args.n_dims, args.n_ctx_orig, args.freq_base, args.beta_fast, args.beta_slow, corr_dims); - - device const int32_t * pos = (device const int32_t *) src1; - - const float theta_base = (float) pos[i2]; - const float inv_ndims = -1.f/args.n_dims; - - float cos_theta; - float sin_theta; - - for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) { - if (i0 < args.n_dims) { - const int ic = i0/2; - - const float theta = theta_base * pow(args.freq_base, inv_ndims*i0); - - const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f; - - rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta); - - device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); - device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - const float x0 = src[0]; - const float x1 = src[1]; - - dst_data[0] = x0*cos_theta - x1*sin_theta; - dst_data[1] = x0*sin_theta + x1*cos_theta; - } else { - device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); - device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - dst_data[0] = src[0]; - dst_data[1] = src[1]; - } - } -} - -template -kernel void kernel_rope_neox( - constant ggml_metal_kargs_rope & args, - device const char * src0, - device const char * src1, - device const char * src2, - device char * dst, - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 tptg [[threads_per_threadgroup]], - uint3 tgpig[[threadgroup_position_in_grid]]) { - const int i3 = tgpig[2]; - const int i2 = tgpig[1]; - const int i1 = tgpig[0]; - - float corr_dims[2]; - rope_yarn_corr_dims(args.n_dims, args.n_ctx_orig, args.freq_base, args.beta_fast, args.beta_slow, corr_dims); - - device const int32_t * pos = (device const int32_t *) src1; - - const float theta_base = (float) pos[i2]; - const float inv_ndims = -1.f/args.n_dims; - - float cos_theta; - float sin_theta; - - for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) { - if (i0 < args.n_dims) { - const int ic = i0/2; - - const float theta = theta_base * pow(args.freq_base, inv_ndims*i0); - - const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f; - - rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta); - - device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + ic*args.nb00); - device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + ic*args.nb0); - - const float x0 = src[0]; - const float x1 = src[args.n_dims/2]; - - dst_data[0] = x0*cos_theta - x1*sin_theta; - dst_data[args.n_dims/2] = x0*sin_theta + x1*cos_theta; - } else { - device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); - device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - dst_data[0] = src[0]; - dst_data[1] = src[1]; - } - } -} - -template -kernel void kernel_rope_multi( - constant ggml_metal_kargs_rope & args, - device const char * src0, - device const char * src1, - device const char * src2, - device char * dst, - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 tptg [[threads_per_threadgroup]], - uint3 tgpig[[threadgroup_position_in_grid]]) { - const int i3 = tgpig[2]; - const int i2 = tgpig[1]; - const int i1 = tgpig[0]; - - float corr_dims[2]; - rope_yarn_corr_dims(args.n_dims, args.n_ctx_orig, args.freq_base, args.beta_fast, args.beta_slow, corr_dims); - - device const int32_t * pos = (device const int32_t *) src1; - - const float inv_ndims = -1.f/args.n_dims; - - float cos_theta; - float sin_theta; - - for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) { - if (i0 < args.n_dims) { - const int ic = i0/2; - - // mrope theta calculations - // note: the rest is the same as kernel_rope_neox - const int sect_dims = args.sect_0 + args.sect_1 + args.sect_2 + args.sect_3; - const int sec_w01 = args.sect_0 + args.sect_1; // end of section 1 - const int sec_w012 = args.sect_0 + args.sect_1 + args.sect_2; // end of section 2 - const int sector = ic % sect_dims; - - float theta_base; - if (FC_rope_is_imrope) { - if (sector % 3 == 1 && sector < 3 * args.sect_1) { // h - theta_base = (float) pos[i2 + args.ne02 * 1]; - } else if (sector % 3 == 2 && sector < 3 * args.sect_2) { // w - theta_base = (float) pos[i2 + args.ne02 * 2]; - } else if (sector % 3 == 0 && sector < 3 * args.sect_0) { // t - theta_base = (float) pos[i2 + args.ne02 * 0]; - } else { // e - theta_base = (float) pos[i2 + args.ne02 * 3]; - } - } else { - if (sector < args.sect_0) { - theta_base = (float) pos[i2]; - } else if (sector < sec_w01) { - theta_base = (float) pos[i2 + args.ne02 * 1]; - } else if (sector < sec_w012) { - theta_base = (float) pos[i2 + args.ne02 * 2]; - } else { - theta_base = (float) pos[i2 + args.ne02 * 3]; - } - } - // end of mrope - - const float theta = theta_base * pow(args.freq_base, inv_ndims*i0); - - const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f; - - rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta); - - device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + ic*args.nb00); - device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + ic*args.nb0); - - const float x0 = src[0]; - const float x1 = src[args.n_dims/2]; - - dst_data[0] = x0*cos_theta - x1*sin_theta; - dst_data[args.n_dims/2] = x0*sin_theta + x1*cos_theta; - } else { - device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); - device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - dst_data[0] = src[0]; - dst_data[1] = src[1]; - } - } -} - -template -kernel void kernel_rope_vision( - constant ggml_metal_kargs_rope & args, - device const char * src0, - device const char * src1, - device const char * src2, - device char * dst, - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 tptg [[threads_per_threadgroup]], - uint3 tgpig[[threadgroup_position_in_grid]]) { - const int i3 = tgpig[2]; - const int i2 = tgpig[1]; - const int i1 = tgpig[0]; - - float corr_dims[2]; - rope_yarn_corr_dims(args.n_dims, args.n_ctx_orig, args.freq_base, args.beta_fast, args.beta_slow, corr_dims); - - device const int32_t * pos = (device const int32_t *) src1; - - const float inv_ndims = -1.f/args.n_dims; - - float cos_theta; - float sin_theta; - - for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) { - if (i0 < 2*args.n_dims) { // different from kernel_rope_multi - const int ic = i0/2; - - // mrope theta calculations (only support 2 dimensions) - const int sect_dims = args.sect_0 + args.sect_1; - const int sector = ic % sect_dims; - - float p; - float theta_base; - if (sector < args.sect_1) { - p = (float) sector; - theta_base = (float) pos[i2]; - } else { - p = (float) sector - args.sect_0; - theta_base = (float) pos[i2 + args.ne02]; - } - - const float theta = theta_base * pow(args.freq_base, 2.0f * inv_ndims * p); - // end of mrope - - const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f; - - rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta); - - device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + ic*args.nb00); - device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + ic*args.nb0); - - const float x0 = src[0]; - const float x1 = src[args.n_dims]; // different from kernel_rope_multi - - dst_data[0] = x0*cos_theta - x1*sin_theta; - dst_data[args.n_dims] = x0*sin_theta + x1*cos_theta; // different from kernel_rope_multi - } else { - device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); - device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - dst_data[0] = src[0]; - dst_data[1] = src[1]; - } - } -} - -typedef decltype(kernel_rope_norm) kernel_rope_norm_t; -typedef decltype(kernel_rope_neox) kernel_rope_neox_t; -typedef decltype(kernel_rope_multi) kernel_rope_multi_t; -typedef decltype(kernel_rope_vision) kernel_rope_vision_t; - -template [[host_name("kernel_rope_norm_f32")]] kernel kernel_rope_norm_t kernel_rope_norm; -template [[host_name("kernel_rope_norm_f16")]] kernel kernel_rope_norm_t kernel_rope_norm; - -template [[host_name("kernel_rope_neox_f32")]] kernel kernel_rope_neox_t kernel_rope_neox; -template [[host_name("kernel_rope_neox_f16")]] kernel kernel_rope_neox_t kernel_rope_neox; - -template [[host_name("kernel_rope_multi_f32")]] kernel kernel_rope_multi_t kernel_rope_multi; -template [[host_name("kernel_rope_multi_f16")]] kernel kernel_rope_multi_t kernel_rope_multi; - -template [[host_name("kernel_rope_vision_f32")]] kernel kernel_rope_vision_t kernel_rope_vision; -template [[host_name("kernel_rope_vision_f16")]] kernel kernel_rope_vision_t kernel_rope_vision; - -typedef void (im2col_t)( - constant ggml_metal_kargs_im2col & args, - device const float * x, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]); - -template -kernel void kernel_im2col( - constant ggml_metal_kargs_im2col & args, - device const float * x, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { -// const int64_t IC = tgpg[0]; - const int64_t OH = tgpg[1]; - const int64_t OW = tgpg[2]; - - const int64_t KH = ntg[1]; - const int64_t KW = ntg[2]; - - int64_t in = tpitg[0]; - const int64_t ikh = tpitg[1]; - const int64_t ikw = tpitg[2]; - - const int64_t iic = tgpig[0]; - const int64_t ioh = tgpig[1]; - const int64_t iow = tgpig[2]; - - const int64_t iiw = iow*args.s0 + ikw*args.d0 - args.p0; - const int64_t iih = ioh*args.s1 + ikh*args.d1 - args.p1; - - int64_t offset_dst = (in*OH*OW + ioh*OW + iow)*args.CHW + (iic*(KH*KW) + ikh*KW + ikw); - - device T * pdst = (device T *) (dst); - - if (iih < 0 || iih >= args.IH || iiw < 0 || iiw >= args.IW) { - while (in < args.N) { - pdst[offset_dst] = 0.0f; - offset_dst += ntg[0]*args.CHW*OH*OW; - - in += ntg[0]; - } - } else { - int64_t offset_src = in*args.ofs0 + iic*args.ofs1 + iih*args.IW + iiw; - - while (in < args.N) { - pdst[offset_dst] = x[offset_src]; - - offset_dst += ntg[0]*args.CHW*OH*OW; - offset_src += ntg[0]*args.ofs0; - - in += ntg[0]; - } - } -} - -template [[host_name("kernel_im2col_f32")]] kernel im2col_t kernel_im2col; -template [[host_name("kernel_im2col_f16")]] kernel im2col_t kernel_im2col; + return as_type(bits); +} -// TODO: obsolete -- remove -//typedef void (im2col_ext_t)( -// constant ggml_metal_kargs_im2col & args, -// device const float * x, -// device char * dst, -// uint3 tgpig[[threadgroup_position_in_grid]], -// uint3 tgpg[[threadgroups_per_grid]], -// uint3 tpitg[[thread_position_in_threadgroup]], -// uint3 ntg[[threads_per_threadgroup]]); -// -//template -//kernel void kernel_im2col_ext( -// constant ggml_metal_kargs_im2col & args, -// device const float * x, -// device char * dst, -// uint3 tgpig[[threadgroup_position_in_grid]], -// uint3 tgpg[[threadgroups_per_grid]], // tgpg[0] = D x IC x KH x KW, CHW = IC x KH x KW -// uint3 tpitg[[thread_position_in_threadgroup]], -// uint3 ntg[[threads_per_threadgroup]]) { // [M, 1, 1] -// const int64_t KHW = (int64_t)args.KHW; -// -// const int64_t d = tgpig[0] / args.CHW; -// const int64_t chw = tgpig[0] % args.CHW; -// const int64_t tgpig_0 = chw / KHW; // 0 ~ (IC - 1) -// const int64_t HW = tgpig[0] % KHW; -// -// const int64_t tpitg_0 = (d * ntg[0]) + tpitg[0]; -// if (tpitg_0 >= args.N) { -// return; -// } -// -// const int64_t tpitg_1 = HW / args.KW; -// const int64_t tpitg_2 = HW % args.KW; -// -// const int64_t iiw = tgpig[2] * args.s0 + tpitg_2 * args.d0 - args.p0; -// const int64_t iih = tgpig[1] * args.s1 + tpitg_1 * args.d1 - args.p1; -// -// const int64_t offset_dst = -// (tpitg_0 * tgpg[1] * tgpg[2] + tgpig[1] * tgpg[2] + tgpig[2]) * args.CHW + -// (tgpig_0 * KHW + tpitg_1 * args.KW + tpitg_2); -// -// device T * pdst = (device T *) (dst); -// -// if (iih < 0 || iih >= args.IH || iiw < 0 || iiw >= args.IW) { -// pdst[offset_dst] = 0.0f; -// } else { -// const int64_t offset_src = tpitg_0 * args.ofs0 + tgpig_0 * args.ofs1; -// pdst[offset_dst] = x[offset_src + iih * args.IW + iiw]; -// } -//} -// -//template [[host_name("kernel_im2col_ext_f32")]] kernel im2col_ext_t kernel_im2col_ext; -//template [[host_name("kernel_im2col_ext_f16")]] kernel im2col_ext_t kernel_im2col_ext; - -template -kernel void kernel_conv_2d( - constant ggml_metal_kargs_conv_2d & args, - device const char * weights, - device const char * src, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const uint threads_per_tg = ntg.x * ntg.y * ntg.z; - const uint tg_index = (tgpig.z * tgpg.y + tgpig.y) * tgpg.x + tgpig.x; - const uint local_thread = tpitg.z * (ntg.x * ntg.y) + tpitg.y * ntg.x + tpitg.x; - const uint thread_index = tg_index * threads_per_tg + local_thread; - const uint64_t total_threads = (uint64_t) threads_per_tg * tgpg.x * tgpg.y * tgpg.z; - const uint64_t total_outputs = (uint64_t) args.N * args.OC * args.OH * args.OW; - - for (uint64_t index = thread_index; index < total_outputs; index += total_threads) { - uint64_t tmp = index; - - const int32_t ow = tmp % args.OW; tmp /= args.OW; - const int32_t oh = tmp % args.OH; tmp /= args.OH; - const int32_t oc = tmp % args.OC; tmp /= args.OC; - const int32_t n = tmp; - - float acc = 0.0f; - - const int32_t base_x = ow*args.s0 - args.p0; - const int32_t base_y = oh*args.s1 - args.p1; - - int32_t ky_start = 0; - if (base_y < 0) { - ky_start = (-base_y + args.d1 - 1)/args.d1; - } - int32_t ky_end = args.KH; - const int32_t y_max = args.IH - 1 - base_y; - if (y_max < 0) { - ky_end = ky_start; - } else if (base_y + (args.KH - 1)*args.d1 >= args.IH) { - ky_end = min(ky_end, y_max/args.d1 + 1); - } - - int32_t kx_start = 0; - if (base_x < 0) { - kx_start = (-base_x + args.d0 - 1)/args.d0; - } - int32_t kx_end = args.KW; - const int32_t x_max = args.IW - 1 - base_x; - if (x_max < 0) { - kx_end = kx_start; - } else if (base_x + (args.KW - 1)*args.d0 >= args.IW) { - kx_end = min(kx_end, x_max/args.d0 + 1); - } - - if (ky_start < ky_end && kx_start < kx_end) { - const uint64_t src_base_n = (uint64_t) n * args.nb13; - const uint64_t w_base_oc = (uint64_t) oc * args.nb03; - - for (int32_t ic = 0; ic < args.IC; ++ic) { - const uint64_t src_base_nc = src_base_n + (uint64_t) ic * args.nb12; - const uint64_t w_base_ocic = w_base_oc + (uint64_t) ic * args.nb02; - - for (int32_t ky = ky_start; ky < ky_end; ++ky) { - const int32_t iy = base_y + ky*args.d1; - const uint64_t src_base_row = src_base_nc + (uint64_t) iy * args.nb11; - const uint64_t w_base_row = w_base_ocic + (uint64_t) ky * args.nb01; - - for (int32_t kx = kx_start; kx < kx_end; ++kx) { - const int32_t ix = base_x + kx*args.d0; - const uint64_t src_offs = src_base_row + (uint64_t) ix * args.nb10; - const uint64_t w_offs = w_base_row + (uint64_t) kx * args.nb00; - - const float x = *(device const float *)(src + src_offs); - const float w = (float) (*(device const TK *)(weights + w_offs)); - - acc += x * w; - } - } - } - } - - const uint64_t dst_offs = - (uint64_t) n * args.nb3 + - (uint64_t) oc * args.nb2 + - (uint64_t) oh * args.nb1 + - (uint64_t) ow * args.nb0; - - *(device float *)(dst + dst_offs) = acc; - } -} - -template [[host_name("kernel_conv_2d_f32_f32")]] -kernel void kernel_conv_2d( - constant ggml_metal_kargs_conv_2d & args, - device const char * weights, - device const char * src, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]); - -template [[host_name("kernel_conv_2d_f16_f32")]] -kernel void kernel_conv_2d( - constant ggml_metal_kargs_conv_2d & args, - device const char * weights, - device const char * src, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]); +static inline float dot(float x, float y) { + return x*y; +} -static inline float conv_2d_dw_whcn( - constant ggml_metal_kargs_conv_2d_dw & args, - device const float * weights, - device const float * src, - uint idx) { - uint i0 = idx / args.dst_w; - uint dst_x = idx - i0 * args.dst_w; - uint i1 = i0 / args.dst_h; - uint dst_y = i0 - i1 * args.dst_h; - uint n = i1 / args.channels; - uint c = i1 - n * args.channels; +static inline float sum(float x) { + return x; +} - uint src_i = n * args.channels * args.src_h * args.src_w + c * args.src_h * args.src_w; - uint knl_i = c * args.knl_h * args.knl_w; +static inline float sum(float4 x) { + return x[0] + x[1] + x[2] + x[3]; +} - const int y_min = max(0, (args.pad_y - int(dst_y) * args.stride_y + args.dilation_y - 1) / args.dilation_y); - const int y_max = min(args.knl_h, (args.src_h + args.pad_y - int(dst_y) * args.stride_y + args.dilation_y - 1) / args.dilation_y); - const int x_min = max(0, (args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); - const int x_max = min(args.knl_w, (args.src_w + args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); +// NOTE: this is not dequantizing - we are simply fitting the template +template +void dequantize_f32(device const float4x4 * src, short il, thread type4x4 & reg) { + reg = (type4x4)(*src); +} - float sum = 0.0f; - for (int knl_y = y_min; knl_y < y_max; ++knl_y) { - const int src_y = int(dst_y) * args.stride_y + knl_y * args.dilation_y - args.pad_y; - for (int knl_x = x_min; knl_x < x_max; ++knl_x) { - const int src_x = int(dst_x) * args.stride_x + knl_x * args.dilation_x - args.pad_x; - const float v = src[src_i + src_y * args.src_w + src_x]; - const float k = weights[knl_i + knl_y * args.knl_w + knl_x]; - sum = fma(v, k, sum); - } - } - return sum; +template +void dequantize_f32_t4(device const float4 * src, short il, thread type4 & reg) { + reg = (type4)(*src); } -static inline float conv_2d_dw_cwhn( - constant ggml_metal_kargs_conv_2d_dw & args, - device const float * weights, - device const float * src, - uint idx) { - uint i0 = idx / args.channels; - uint c = idx - i0 * args.channels; - uint i1 = i0 / args.dst_w; - uint dst_x = i0 - i1 * args.dst_w; - uint n = i1 / args.dst_h; - uint dst_y = i1 - n * args.dst_h; +template +void dequantize_f16(device const half4x4 * src, short il, thread type4x4 & reg) { + reg = (type4x4)(*src); +} - uint src_i = n * args.channels * args.src_h * args.src_w; - uint src_row = args.src_w * args.channels; - uint knl_row = args.knl_w * args.channels; +template +void dequantize_f16_t4(device const half4 * src, short il, thread type4 & reg) { + reg = (type4)(*(src)); +} - const int y_min = max(0, (args.pad_y - int(dst_y) * args.stride_y + args.dilation_y - 1) / args.dilation_y); - const int y_max = min(args.knl_h, (args.src_h + args.pad_y - int(dst_y) * args.stride_y + args.dilation_y - 1) / args.dilation_y); - const int x_min = max(0, (args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); - const int x_max = min(args.knl_w, (args.src_w + args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); +#if defined(GGML_METAL_HAS_BF16) +template +void dequantize_bf16(device const bfloat4x4 * src, short il, thread type4x4 & reg) { + reg = (type4x4)(*src); +} - float sum = 0.0f; - for (int knl_y = y_min; knl_y < y_max; ++knl_y) { - const int src_y = int(dst_y) * args.stride_y + knl_y * args.dilation_y - args.pad_y; - for (int knl_x = x_min; knl_x < x_max; ++knl_x) { - const int src_x = int(dst_x) * args.stride_x + knl_x * args.dilation_x - args.pad_x; - const float v = src[src_i + src_y * src_row + src_x * args.channels + c]; - const float k = weights[knl_y * knl_row + knl_x * args.channels + c]; - sum = fma(v, k, sum); - } - } - return sum; +template +void dequantize_bf16_t4(device const bfloat4 * src, short il, thread type4 & reg) { + reg = (type4)(*(src)); } +#endif -kernel void kernel_conv_2d_dw_whcn( - constant ggml_metal_kargs_conv_2d_dw & args, - device const float * weights, - device const float * src, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - const uint threads_per_tg = ntg.x * ntg.y * ntg.z; - const uint tg_index = (tgpig.z * tgpg.y + tgpig.y) * tgpg.x + tgpig.x; - const uint local_thread = tpitg.z * (ntg.x * ntg.y) + tpitg.y * ntg.x + tpitg.x; - const uint thread_index = tg_index * threads_per_tg + local_thread; - const uint total_threads = threads_per_tg * tgpg.x * tgpg.y * tgpg.z; +template +void dequantize_q1_0(device const block_q1_0 * xb, short il, thread type4x4 & reg) { + device const uint8_t * qs = xb->qs; + const float d = xb->d; + const float neg_d = -d; - for (uint idx = thread_index; idx < (uint) args.ne; idx += total_threads) { - dst[idx] = conv_2d_dw_whcn(args, weights, src, idx); - } + const int byte_offset = il * 2; // il*16 bits = il*2 bytes + const uint8_t b0 = qs[byte_offset]; + const uint8_t b1 = qs[byte_offset + 1]; + + float4x4 reg_f; + + reg_f[0][0] = select(neg_d, d, bool(b0 & 0x01)); + reg_f[0][1] = select(neg_d, d, bool(b0 & 0x02)); + reg_f[0][2] = select(neg_d, d, bool(b0 & 0x04)); + reg_f[0][3] = select(neg_d, d, bool(b0 & 0x08)); + reg_f[1][0] = select(neg_d, d, bool(b0 & 0x10)); + reg_f[1][1] = select(neg_d, d, bool(b0 & 0x20)); + reg_f[1][2] = select(neg_d, d, bool(b0 & 0x40)); + reg_f[1][3] = select(neg_d, d, bool(b0 & 0x80)); + + reg_f[2][0] = select(neg_d, d, bool(b1 & 0x01)); + reg_f[2][1] = select(neg_d, d, bool(b1 & 0x02)); + reg_f[2][2] = select(neg_d, d, bool(b1 & 0x04)); + reg_f[2][3] = select(neg_d, d, bool(b1 & 0x08)); + reg_f[3][0] = select(neg_d, d, bool(b1 & 0x10)); + reg_f[3][1] = select(neg_d, d, bool(b1 & 0x20)); + reg_f[3][2] = select(neg_d, d, bool(b1 & 0x40)); + reg_f[3][3] = select(neg_d, d, bool(b1 & 0x80)); + + reg = (type4x4) reg_f; } -kernel void kernel_conv_2d_dw_1d_whcn( - constant ggml_metal_kargs_conv_2d_dw & args, - device const float * weights, - device const float * src, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]]) { - const uint dst_x0 = (tgpig.x * 256 + tpitg.x) * 4; - const uint c = tgpig.y; - if (dst_x0 >= (uint) args.dst_w || c >= (uint) args.channels) { - return; - } +template +void dequantize_q1_0_t4(device const block_q1_0 * xb, short il, thread type4 & reg) { + const float d = xb->d; + const float neg_d = -d; + const int base = il * 4; + const uint8_t byte = xb->qs[base / 8]; + const int s = base % 8; - const uint src_base = c * args.src_w; - const uint knl_base = c * args.knl_w; + float4 reg_f; + reg_f[0] = select(neg_d, d, bool((byte >> (s )) & 1)); + reg_f[1] = select(neg_d, d, bool((byte >> (s + 1)) & 1)); + reg_f[2] = select(neg_d, d, bool((byte >> (s + 2)) & 1)); + reg_f[3] = select(neg_d, d, bool((byte >> (s + 3)) & 1)); - for (uint o = 0; o < 4; ++o) { - const uint dst_x = dst_x0 + o; - if (dst_x >= (uint) args.dst_w) { - return; - } + reg = (type4) reg_f; +} - const int base_x = int(dst_x) * args.stride_x - args.pad_x; - float sum = 0.0f; - if (base_x >= 0 && base_x + (args.knl_w - 1) * args.dilation_x < args.src_w) { - int src_x = base_x; - for (int knl_x = 0; knl_x < args.knl_w; ++knl_x, src_x += args.dilation_x) { - sum = fma(src[src_base + uint(src_x)], weights[knl_base + uint(knl_x)], sum); - } - } else { - const int x_min = max(0, (args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); - const int x_max = min(args.knl_w, (args.src_w + args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); - for (int knl_x = x_min; knl_x < x_max; ++knl_x) { - const int src_x = base_x + knl_x * args.dilation_x; - sum = fma(src[src_base + uint(src_x)], weights[knl_base + uint(knl_x)], sum); - } - } +template +void dequantize_q4_0(device const block_q4_0 * xb, short il, thread type4x4 & reg) { + device const uint16_t * qs = ((device const uint16_t *)xb + 1); + const float d1 = il ? (xb->d / 16.h) : xb->d; + const float d2 = d1 / 256.f; + const float md = -8.h * xb->d; + const ushort mask0 = il ? 0x00F0 : 0x000F; + const ushort mask1 = mask0 << 8; - dst[c * args.dst_w + dst_x] = sum; + float4x4 reg_f; + + for (int i = 0; i < 8; i++) { + reg_f[i/2][2*(i%2) + 0] = d1 * (qs[i] & mask0) + md; + reg_f[i/2][2*(i%2) + 1] = d2 * (qs[i] & mask1) + md; } + + reg = (type4x4) reg_f; } -kernel void kernel_conv_2d_dw_cwhn( - constant ggml_metal_kargs_conv_2d_dw & args, - device const float * weights, - device const float * src, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - const uint threads_per_tg = ntg.x * ntg.y * ntg.z; - const uint tg_index = (tgpig.z * tgpg.y + tgpig.y) * tgpg.x + tgpig.x; - const uint local_thread = tpitg.z * (ntg.x * ntg.y) + tpitg.y * ntg.x + tpitg.x; - const uint thread_index = tg_index * threads_per_tg + local_thread; - const uint total_threads = threads_per_tg * tgpg.x * tgpg.y * tgpg.z; +template +void dequantize_q4_0_t4(device const block_q4_0 * xb, short il, thread type4 & reg) { + device const uint16_t * qs = ((device const uint16_t *)xb + 1); + const float d1 = (il/4) ? (xb->d / 16.h) : xb->d; + const float d2 = d1 / 256.f; + const float md = -8.h * xb->d; + const ushort mask0 = (il/4) ? 0x00F0 : 0x000F; + const ushort mask1 = mask0 << 8; - for (uint idx = thread_index; idx < (uint) args.ne; idx += total_threads) { - dst[idx] = conv_2d_dw_cwhn(args, weights, src, idx); + for (int i = 0; i < 2; i++) { + reg[2*i + 0] = d1 * (qs[2*(il%4) + i] & mask0) + md; + reg[2*i + 1] = d2 * (qs[2*(il%4) + i] & mask1) + md; } } -kernel void kernel_conv_2d_dw_1d_cwhn( - constant ggml_metal_kargs_conv_2d_dw & args, - device const float * weights, - device const float * src, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]]) { - const uint dst_x0 = (tgpig.x * 256 + tpitg.x) * 4; - const uint c = tgpig.y; - if (dst_x0 >= (uint) args.dst_w || c >= (uint) args.channels) { - return; +void quantize_q1_0(device const float * src, device block_q1_0 & dst) { + float sum_abs = 0.0f; + for (int j = 0; j < QK1_0; j++) { + sum_abs += fabs(src[j]); } + dst.d = sum_abs / QK1_0; - for (uint o = 0; o < 4; ++o) { - const uint dst_x = dst_x0 + o; - if (dst_x >= (uint) args.dst_w) { - return; + for (int j = 0; j < QK1_0 / 8; j++) { + dst.qs[j] = 0; + } + for (int j = 0; j < QK1_0; j++) { + if (src[j] >= 0.0f) { + dst.qs[j / 8] |= (1 << (j % 8)); } + } +} - const int base_x = int(dst_x) * args.stride_x - args.pad_x; - float sum = 0.0f; - if (base_x >= 0 && base_x + (args.knl_w - 1) * args.dilation_x < args.src_w) { - int src_x = base_x; - for (int knl_x = 0; knl_x < args.knl_w; ++knl_x, src_x += args.dilation_x) { - sum = fma( - src[uint(src_x) * args.channels + c], - weights[uint(knl_x) * args.channels + c], - sum); - } - } else { - const int x_min = max(0, (args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); - const int x_max = min(args.knl_w, (args.src_w + args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); - for (int knl_x = x_min; knl_x < x_max; ++knl_x) { - const int src_x = base_x + knl_x * args.dilation_x; - sum = fma( - src[uint(src_x) * args.channels + c], - weights[uint(knl_x) * args.channels + c], - sum); - } +void quantize_q4_0(device const float * src, device block_q4_0 & dst) { +#pragma METAL fp math_mode(safe) + float amax = 0.0f; // absolute max + float max = 0.0f; + + for (int j = 0; j < QK4_0; j++) { + const float v = src[j]; + if (amax < fabs(v)) { + amax = fabs(v); + max = v; } + } - dst[dst_x * args.channels + c] = sum; + const float d = max / -8; + const float id = d ? 1.0f/d : 0.0f; + + dst.d = d; + + for (int j = 0; j < QK4_0/2; ++j) { + const float x0 = src[0 + j]*id; + const float x1 = src[QK4_0/2 + j]*id; + + const uint8_t xi0 = MIN(15, (int8_t)(x0 + 8.5f)); + const uint8_t xi1 = MIN(15, (int8_t)(x1 + 8.5f)); + + dst.qs[j] = xi0; + dst.qs[j] |= xi1 << 4; } } -typedef void (conv_transpose_1d_t)( - constant ggml_metal_kargs_conv_transpose_1d & args, - device const float * src0, - device const float * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]]); - -template -kernel void kernel_conv_transpose_1d( - constant ggml_metal_kargs_conv_transpose_1d & args, - device const T * src0, - device const float * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg [[threads_per_threadgroup]]) { - - // One thread per output element, grouped ntg.x to a threadgroup so the - // whole SIMD width does useful work (the previous one-thread-per- - // threadgroup dispatch left 31/32 lanes idle). - const int32_t j = tgpig[0] * ntg[0] + tpitg[0]; - if (j >= args.OL) { - return; - } - - // For output position j on the time axis, only input positions - // i such that i*s0 <= j < i*s0 + K - // contribute -- i.e. i in [ceil((j - K + 1)/s0), floor(j/s0)] - // intersected with [0, IL-1]. That's at most ceil(K/s0) values - // (typically 2 for stride==K/2 transposed convs). - const int32_t s0 = args.s0; - const int32_t K = args.K; - const int32_t IL = args.IL; - - int32_t i_min; - { - int32_t a = j - K + 1; - i_min = a <= 0 ? 0 : (a + s0 - 1) / s0; // ceil(a/s0) for a>0 - } - int32_t i_max = j / s0; - if (i_max > IL - 1) i_max = IL - 1; - - float v = 0.0f; - if (i_min <= i_max) { - for (int32_t c = 0; c < args.IC; c++) { - const int32_t kernel_offset = c * args.OC * K + K * tgpig[1]; - const int32_t input_offset = c * IL; - - for (int32_t i = i_min; i <= i_max; i++) { - v += float(src0[kernel_offset + j - i * s0]) * src1[input_offset + i]; - } - } - } - - device float * dst_ptr = (device float *) (dst + j * args.nb0 + tgpig[1] * args.nb1); - - dst_ptr[0] = v; -} - -template [[host_name("kernel_conv_transpose_1d_f32_f32")]] -kernel void kernel_conv_transpose_1d( - constant ggml_metal_kargs_conv_transpose_1d & args, - device const float * src0, - device const float * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg [[threads_per_threadgroup]]); - -template [[host_name("kernel_conv_transpose_1d_f16_f32")]] -kernel void kernel_conv_transpose_1d( - constant ggml_metal_kargs_conv_transpose_1d & args, - device const half * src0, - device const float * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg [[threads_per_threadgroup]]); +void quantize_q4_1(device const float * src, device block_q4_1 & dst) { +#pragma METAL fp math_mode(safe) + float min = FLT_MAX; + float max = -FLT_MAX; + for (int j = 0; j < QK4_1; j++) { + const float v = src[j]; + if (min > v) min = v; + if (max < v) max = v; + } -template -kernel void kernel_col2im_1d( - constant ggml_metal_kargs_col2im_1d & args, - device const T * col, - device T * dst, - uint tgpig [[threadgroup_position_in_grid]], - uint tpitg [[thread_position_in_threadgroup]], - uint ntg [[threads_per_threadgroup]]) { + const float d = (max - min) / ((1 << 4) - 1); + const float id = d ? 1.0f/d : 0.0f; - const int idx = tgpig * ntg + tpitg; - if (idx >= args.T_out * args.OC) { - return; - } + dst.d = d; + dst.m = min; - const int t_out = idx % args.T_out; - const int oc = idx / args.T_out; - const int t_abs = t_out + args.p0; + for (int j = 0; j < QK4_1/2; ++j) { + const float x0 = (src[0 + j] - min)*id; + const float x1 = (src[QK4_1/2 + j] - min)*id; - int t_in_min = (t_abs - args.K + args.s0) / args.s0; - if (t_in_min < 0) { - t_in_min = 0; + const uint8_t xi0 = MIN(15, (int8_t)(x0 + 0.5f)); + const uint8_t xi1 = MIN(15, (int8_t)(x1 + 0.5f)); + + dst.qs[j] = xi0; + dst.qs[j] |= xi1 << 4; } - int t_in_max = t_abs / args.s0; - if (t_in_max >= args.T_in) { - t_in_max = args.T_in - 1; +} + +void quantize_q5_0(device const float * src, device block_q5_0 & dst) { +#pragma METAL fp math_mode(safe) + float amax = 0.0f; // absolute max + float max = 0.0f; + + for (int j = 0; j < QK5_0; j++) { + const float v = src[j]; + if (amax < fabs(v)) { + amax = fabs(v); + max = v; + } } - float sum = 0.0f; - for (int t_in = t_in_min; t_in <= t_in_max; ++t_in) { - const int k = t_abs - t_in * args.s0; - sum += float(col[(oc * args.K + k) + t_in * args.K_OC]); + const float d = max / -16; + const float id = d ? 1.0f/d : 0.0f; + + dst.d = d; + + uint32_t qh = 0; + for (int j = 0; j < QK5_0/2; ++j) { + const float x0 = src[0 + j]*id; + const float x1 = src[QK5_0/2 + j]*id; + + const uint8_t xi0 = MIN(31, (int8_t)(x0 + 16.5f)); + const uint8_t xi1 = MIN(31, (int8_t)(x1 + 16.5f)); + + dst.qs[j] = (xi0 & 0xf) | ((xi1 & 0xf) << 4); + qh |= ((xi0 & 0x10u) >> 4) << (j + 0); + qh |= ((xi1 & 0x10u) >> 4) << (j + QK5_0/2); } - dst[t_out + oc * args.T_out] = T(sum); + thread const uint8_t * qh8 = (thread const uint8_t *)&qh; + + for (int j = 0; j < 4; ++j) { + dst.qh[j] = qh8[j]; + } } -template [[host_name("kernel_col2im_1d_f32")]] -kernel void kernel_col2im_1d( - constant ggml_metal_kargs_col2im_1d & args, - device const float * col, - device float * dst, - uint tgpig [[threadgroup_position_in_grid]], - uint tpitg [[thread_position_in_threadgroup]], - uint ntg [[threads_per_threadgroup]]); +void quantize_q5_1(device const float * src, device block_q5_1 & dst) { +#pragma METAL fp math_mode(safe) + float max = src[0]; + float min = src[0]; -template [[host_name("kernel_col2im_1d_f16")]] -kernel void kernel_col2im_1d( - constant ggml_metal_kargs_col2im_1d & args, - device const half * col, - device half * dst, - uint tgpig [[threadgroup_position_in_grid]], - uint tpitg [[thread_position_in_threadgroup]], - uint ntg [[threads_per_threadgroup]]); + for (int j = 1; j < QK5_1; j++) { + const float v = src[j]; + min = v < min ? v : min; + max = v > max ? v : max; + } -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_col2im_1d_bf16")]] -kernel void kernel_col2im_1d( - constant ggml_metal_kargs_col2im_1d & args, - device const bfloat * col, - device bfloat * dst, - uint tgpig [[threadgroup_position_in_grid]], - uint tpitg [[thread_position_in_threadgroup]], - uint ntg [[threads_per_threadgroup]]); -#endif + const float d = (max - min) / 31; + const float id = d ? 1.0f/d : 0.0f; + dst.d = d; + dst.m = min; -typedef void (conv_transpose_2d_t)( - constant ggml_metal_kargs_conv_transpose_2d & args, - device const float * src0, - device const float * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]]); - -template -kernel void kernel_conv_transpose_2d( - constant ggml_metal_kargs_conv_transpose_2d & args, - device const T * src0, - device const float * src1, - device char * dst, - threadgroup float * shared_sum [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const int64_t out_x = tgpig[0]; - const int64_t out_y = tgpig[1]; - const int64_t out_c = tgpig[2]; - - const int64_t kw = tpitg[0]; - const int64_t kh = tpitg[1]; - - float v = 0.0f; - - for (int64_t in_c = 0; in_c < args.IC; in_c++) { - int64_t in_y = out_y - kh; - - if (in_y < 0 || in_y % args.s0) continue; - - in_y /= args.s0; - - if (in_y >= args.IH) continue; - - int64_t in_x = out_x - kw; - - if (in_x < 0 || in_x % args.s0) continue; - - in_x /= args.s0; - - if (in_x >= args.IW) continue; - - const int64_t input_idx = (args.IW * args.IH) * in_c + (args.IW) * in_y + in_x; - const int64_t kernel_idx = (args.KH * args.KW * args.OC) * in_c + (args.KH * args.KW) * out_c + (args.KW) * kh + kw; - - v += (float)src0[kernel_idx] * src1[input_idx]; - } - - const uint tid = tpitg.y * ntg.x + tpitg.x; - shared_sum[tid] = v; - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (tid == 0) { - float total = 0.0f; - const uint num_threads = ntg.x * ntg.y; - for (uint i = 0; i < num_threads; i++) { - total += shared_sum[i]; - } - - device float * dst_ptr = (device float *) (dst + out_x*args.nb0 + out_y * args.nb1 + out_c*args.nb2); - dst_ptr[0] = total; - } -} - -template [[host_name("kernel_conv_transpose_2d_f32_f32")]] -kernel void kernel_conv_transpose_2d( - constant ggml_metal_kargs_conv_transpose_2d & args, - device const float * src0, - device const float * src1, - device char * dst, - threadgroup float * shared_sum [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]); - -template [[host_name("kernel_conv_transpose_2d_f16_f32")]] -kernel void kernel_conv_transpose_2d( - constant ggml_metal_kargs_conv_transpose_2d & args, - device const half * src0, - device const float * src1, - device char * dst, - threadgroup float * shared_sum [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]); + uint32_t qh = 0; + for (int j = 0; j < QK5_1/2; ++j) { + const float x0 = (src[0 + j] - min)*id; + const float x1 = (src[QK5_1/2 + j] - min)*id; -template -kernel void kernel_conv_transpose_2d_linear( - constant ggml_metal_kargs_conv_transpose_2d_linear & args, - device const T * src0, - device const float * src1, - device float * dst, - uint tgpig [[threadgroup_position_in_grid]], - uint tpitg [[thread_position_in_threadgroup]], - uint ntg [[threads_per_threadgroup]]) { + const uint8_t xi0 = (uint8_t)(x0 + 0.5f); + const uint8_t xi1 = (uint8_t)(x1 + 0.5f); - const int global_idx = tgpig * ntg + tpitg; - if (global_idx >= args.total) { - return; + dst.qs[j] = (xi0 & 0xf) | ((xi1 & 0xf) << 4); + qh |= ((xi0 & 0x10u) >> 4) << (j + 0); + qh |= ((xi1 & 0x10u) >> 4) << (j + QK5_1/2); } - const int out_x = global_idx % args.OW; - const int out_y = (global_idx / args.OW) % args.OH; - const int out_c = (global_idx / (args.OW * args.OH)) % args.OC; - const int out_n = global_idx / (args.OW * args.OH * args.OC); - - float acc = 0.0f; + thread const uint8_t * qh8 = (thread const uint8_t *)&qh; - if (args.IH == 1 && args.OH == 1 && args.KH == 1) { - for (int in_c = 0; in_c < args.IC; ++in_c) { - const int input_base = (args.IW * args.IC) * out_n + args.IW * in_c; - const int kernel_base = (args.KW * args.OC) * in_c + args.KW * out_c; - for (int kw = 0; kw < args.KW; ++kw) { - int in_x = out_x - kw; - if (in_x < 0 || in_x % args.s0) { - continue; - } - in_x /= args.s0; - if (in_x >= args.IW) { - continue; - } + for (int j = 0; j < 4; ++j) { + dst.qh[j] = qh8[j]; + } +} - acc += src1[input_base + in_x] * float(src0[kernel_base + kw]); - } - } +void quantize_q8_0(device const float * src, device block_q8_0 & dst) { +#pragma METAL fp math_mode(safe) + float amax = 0.0f; // absolute max - dst[global_idx] = acc; - return; + for (int j = 0; j < QK8_0; j++) { + const float v = src[j]; + amax = MAX(amax, fabs(v)); } - for (int in_c = 0; in_c < args.IC; ++in_c) { - for (int kh = 0; kh < args.KH; ++kh) { - int in_y = out_y - kh; - if (in_y < 0 || in_y % args.s0) { - continue; - } - in_y /= args.s0; - if (in_y >= args.IH) { - continue; - } + const float d = amax / ((1 << 7) - 1); + const float id = d ? 1.0f/d : 0.0f; - for (int kw = 0; kw < args.KW; ++kw) { - int in_x = out_x - kw; - if (in_x < 0 || in_x % args.s0) { - continue; - } - in_x /= args.s0; - if (in_x >= args.IW) { - continue; - } + dst.d = d; - const int input_idx = - (args.IW * args.IH * args.IC) * out_n + (args.IW * args.IH) * in_c + (args.IW) * in_y + in_x; - const int kernel_idx = - (args.KH * args.KW * args.OC) * in_c + (args.KH * args.KW) * out_c + (args.KW) * kh + kw; + for (int j = 0; j < QK8_0; ++j) { + const float x0 = src[j]*id; - acc += src1[input_idx] * float(src0[kernel_idx]); - } + dst.qs[j] = round(x0); + } +} + +void quantize_iq4_nl(device const float * src, device block_iq4_nl & dst) { +#pragma METAL fp math_mode(safe) + float amax = 0.0f; // absolute max + float max = 0.0f; + + for (int j = 0; j < QK4_NL; j++) { + const float v = src[j]; + if (amax < fabs(v)) { + amax = fabs(v); + max = v; } } - dst[global_idx] = acc; -} + const float d = max / kvalues_iq4nl_f[0]; + const float id = d ? 1.0f/d : 0.0f; -template [[host_name("kernel_conv_transpose_2d_linear_f32_f32")]] -kernel void kernel_conv_transpose_2d_linear( - constant ggml_metal_kargs_conv_transpose_2d_linear & args, - device const float * src0, - device const float * src1, - device float * dst, - uint tgpig [[threadgroup_position_in_grid]], - uint tpitg [[thread_position_in_threadgroup]], - uint ntg [[threads_per_threadgroup]]); + float sumqx = 0, sumq2 = 0; + for (int j = 0; j < QK4_NL/2; ++j) { + const float x0 = src[0 + j]*id; + const float x1 = src[QK4_NL/2 + j]*id; -template [[host_name("kernel_conv_transpose_2d_linear_f16_f32")]] -kernel void kernel_conv_transpose_2d_linear( - constant ggml_metal_kargs_conv_transpose_2d_linear & args, - device const half * src0, - device const float * src1, - device float * dst, - uint tgpig [[threadgroup_position_in_grid]], - uint tpitg [[thread_position_in_threadgroup]], - uint ntg [[threads_per_threadgroup]]); + const uint8_t xi0 = best_index_int8(16, kvalues_iq4nl_f, x0); + const uint8_t xi1 = best_index_int8(16, kvalues_iq4nl_f, x1); -constant bool FC_upscale_aa [[function_constant(FC_UPSCALE + 0)]]; + dst.qs[j] = xi0 | (xi1 << 4); + + const float v0 = kvalues_iq4nl_f[xi0]; + const float v1 = kvalues_iq4nl_f[xi1]; + const float w0 = src[0 + j]*src[0 + j]; + const float w1 = src[QK4_NL/2 + j]*src[QK4_NL/2 + j]; + sumqx += w0*v0*src[j] + w1*v1*src[QK4_NL/2 + j]; + sumq2 += w0*v0*v0 + w1*v1*v1; -kernel void kernel_upscale_nearest_f32( - constant ggml_metal_kargs_upscale & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; - - const int64_t i03 = i3/args.sf3; - const int64_t i02 = i2/args.sf2; - const int64_t i01 = i1/args.sf1; - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const int64_t i00 = i0/args.sf0; - - device const float * src0_ptr = (device const float *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + i00*args.nb00); - device float * dst_ptr = (device float *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - dst_ptr[0] = src0_ptr[0]; - } -} - -static inline float bilinear_tri(float x) { - return MAX(0.0f, 1.0f - fabs(x)); -} - -kernel void kernel_upscale_bilinear_f32( - constant ggml_metal_kargs_upscale & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; - - const int64_t i03 = i3 / args.sf3; - const int64_t i02 = i2 / args.sf2; - - const float f01 = ((float)i1 + args.poffs) / args.sf1 - args.poffs; - const int64_t i01 = MAX(0, MIN(args.ne01 - 1, (int64_t)floor(f01))); - const int64_t i01p = MAX(0, MIN(args.ne01 - 1, i01 + 1)); - const float fd1 = MAX(0.0f, MIN(1.0f, f01 - (float)i01)); - - src0 += i03*args.nb03 + i02*args.nb02; - - device float * dst_ptr = (device float *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1); - - if (FC_upscale_aa) { - const float support0 = MAX(1.0f, 1.0f / args.sf0); - const float invscale0 = 1.0f / support0; - const float support1 = MAX(1.0f, 1.0f / args.sf1); - const float invscale1 = 1.0f / support1; - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const float f00 = ((float)i0 + args.poffs) / args.sf0 - args.poffs; - - int64_t x_min = MAX((int64_t)0, (int64_t)floor(f00 - support0 + args.poffs)); - int64_t x_max = MIN(args.ne00, (int64_t)ceil (f00 + support0 + args.poffs)); - - int64_t y_min = MAX((int64_t)0, (int64_t)floor(f01 - support1 + args.poffs)); - int64_t y_max = MIN(args.ne01, (int64_t)ceil (f01 + support1 + args.poffs)); - - float sum = 0.0f; - float wsum = 0.0f; - - for (int64_t sy = y_min; sy < y_max; ++sy) { - const float wy = MAX(0.0f, 1.0f - fabs((float)sy - f01) * invscale1); - for (int64_t sx = x_min; sx < x_max; ++sx) { - const float wx = MAX(0.0f, 1.0f - fabs((float)sx - f00) * invscale0); - const float w = wx * wy; - const device const float * src_ptr = (device const float *)(src0 + sy*args.nb01 + sx*args.nb00); - sum += (*src_ptr) * w; - wsum += w; - } - } - - const float v = (wsum > 0.0f) ? (sum / wsum) : 0.0f; - dst_ptr[i0] = v; - } - } else { - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const float f00 = ((float)i0 + args.poffs) / args.sf0 - args.poffs; - const int64_t i00 = MAX(0, MIN(args.ne00 - 1, (int64_t)floor(f00))); - const int64_t i00p = MAX(0, MIN(args.ne00 - 1, i00 + 1)); - const float fd0 = MAX(0.0f, MIN(1.0f, f00 - (float)i00)); - - device const float * src00 = (device const float *)(src0 + i01*args.nb01 + i00*args.nb00); - device const float * src10 = (device const float *)(src0 + i01*args.nb01 + i00p*args.nb00); - device const float * src01 = (device const float *)(src0 + i01p*args.nb01 + i00*args.nb00); - device const float * src11 = (device const float *)(src0 + i01p*args.nb01 + i00p*args.nb00); - - const float v = - (*src00) * (1.0f - fd0) * (1.0f - fd1) + - (*src10) * fd0 * (1.0f - fd1) + - (*src01) * (1.0f - fd0) * fd1 + - (*src11) * fd0 * fd1; - - dst_ptr[i0] = v; - } - } -} - -template -kernel void kernel_conv_3d( - constant ggml_metal_kargs_conv_3d & args, - device const char * src0, // Weights [IC * OC, KD, KH, KW] - device const char * src1, // Inputs [IC * N, ID, IH, IW] - device char * dst, // Outputs [OC * N, OD, OH, OW] - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]]) { - - // 1. Un-flatten the spatial dimension from Grid X - int64_t spatial_idx = tgpig.x * 32 + tpitg.x; - - if (spatial_idx >= args.OW * args.OH * args.OD) { - return; // Thread falls outside the spatial volume - } - - int64_t od = spatial_idx / (args.OW * args.OH); - int64_t oh = (spatial_idx / args.OW) % args.OH; - int64_t ow = spatial_idx % args.OW; - - // 2. Map Y to Channels, Z to Batch - int64_t oc = tgpig.y; - int64_t batch_idx = tgpig.z; - - // 3. Calculate anchor coordinates in the Input volume - int64_t i_w_base = ow * args.s0 - args.p0; - int64_t i_h_base = oh * args.s1 - args.p1; - int64_t i_d_base = od * args.s2 - args.p2; - - float sum = 0.0f; - - // 4. Gather Loop (Iterate over Input Channels -> Depth -> Height -> Width) - for (int64_t ic = 0; ic < args.IC; ++ic) { - - // ggml packs batch and channel together in the 4th dimension - int64_t src_cn_idx = batch_idx * args.IC + ic; - int64_t w_cn_idx = oc * args.IC + ic; - - for (int64_t kz = 0; kz < args.KD; ++kz) { - int64_t id = i_d_base + kz * args.d2; - if (id < 0 || id >= args.ID) continue; // Boundary check (Padding) - - for (int64_t ky = 0; ky < args.KH; ++ky) { - int64_t ih = i_h_base + ky * args.d1; - if (ih < 0 || ih >= args.IH) continue; - - for (int64_t kx = 0; kx < args.KW; ++kx) { - int64_t iw = i_w_base + kx * args.d0; - if (iw < 0 || iw >= args.IW) continue; - - // Convert multi-dimensional coordinates to flat byte offsets - int64_t w_idx = kx*args.nb00 + ky*args.nb01 + kz*args.nb02 + w_cn_idx*args.nb03; - int64_t i_idx = iw*args.nb10 + ih*args.nb11 + id*args.nb12 + src_cn_idx*args.nb13; - - // Dereference memory and cast weights to f32 if they were f16 - float w_val = (float)*(device const T*)((device const char*)src0 + w_idx); - float i_val = *(device const float*)((device const char*)src1 + i_idx); - - sum += w_val * i_val; - } - } - } - } - - // 5. Write the accumulated value out to RAM - int64_t dst_cn_idx = batch_idx * args.OC + oc; - int64_t d_idx = ow*args.nb0 + oh*args.nb1 + od*args.nb2 + dst_cn_idx*args.nb3; - - *(device float*)(dst + d_idx) = sum; -} - -// Explicit instantiations so the JIT compiler can find them by name -template [[host_name("kernel_conv_3d_f32_f32")]] -kernel void kernel_conv_3d( - constant ggml_metal_kargs_conv_3d & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]]); - -// Explicit instantiation for f16 weights -template [[host_name("kernel_conv_3d_f16_f32")]] -kernel void kernel_conv_3d( - constant ggml_metal_kargs_conv_3d & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]]); - - -static inline float bicubic_weight1(float x) { - const float a = -0.75f; - return ((a + 2) * x - (a + 3)) * x * x + 1; -} - -static inline float bicubic_weight2(float x) { - const float a = -0.75f; - return ((a * x - 5 * a) * x + 8 * a) * x - 4 * a; -} - -kernel void kernel_upscale_bicubic_f32( - constant ggml_metal_kargs_upscale & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; - - const int64_t i03 = i3 / args.sf3; - const int64_t i02 = i2 / args.sf2; - - const float f01 = ((float)i1 + args.poffs) / args.sf1 - args.poffs; - const int64_t i01 = (int64_t)floor(f01); - const float fd1 = f01 - (float)i01; - - const float w_y0 = bicubic_weight2(fd1 + 1.0f); - const float w_y1 = bicubic_weight1(fd1); - const float w_y2 = bicubic_weight1(1.0f - fd1); - const float w_y3 = bicubic_weight2(2.0f - fd1); - - const device const char * src_slice = src0 + i03 * args.nb03 + i02 * args.nb02; - - device float * dst_ptr = (device float *)(dst + i3 * args.nb3 + i2 * args.nb2 + i1 * args.nb1); - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const float f00 = ((float)i0 + args.poffs) / args.sf0 - args.poffs; - const int64_t i00 = (int64_t)floor(f00); - const float fd0 = f00 - (float)i00; - - const float w_x0 = bicubic_weight2(fd0 + 1.0f); - const float w_x1 = bicubic_weight1(fd0); - const float w_x2 = bicubic_weight1(1.0f - fd0); - const float w_x3 = bicubic_weight2(2.0f - fd0); - - float sum = 0.0f; - - for (int dy = -1; dy <= 2; ++dy) { - const int64_t iy = MAX(0, MIN(args.ne01 - 1, i01 + dy)); - const float wy = (dy == -1) ? w_y0 : (dy == 0) ? w_y1 : (dy == 1) ? w_y2 : w_y3; - - for (int dx = -1; dx <= 2; ++dx) { - const int64_t ix = MAX(0, MIN(args.ne00 - 1, i00 + dx)); - const float wx = (dx == -1) ? w_x0 : (dx == 0) ? w_x1 : (dx == 1) ? w_x2 : w_x3; - - const device const float * src_ptr = (device const float *)(src_slice + iy * args.nb01 + ix * args.nb00); - sum += (*src_ptr) * wx * wy; - } - } - - dst_ptr[i0] = sum; - } -} - -kernel void kernel_roll_f32( - constant ggml_metal_kargs_roll & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; - - device const float * src0_ptr = (device const float *) src0; - device float * dst_ptr = (device float *) dst; - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - // apply shifts and wrap around - int64_t i00 = i0 - args.s0; - int64_t i01 = i1 - args.s1; - int64_t i02 = i2 - args.s2; - int64_t i03 = i3 - args.s3; - - if (i00 < 0) { i00 += args.ne00; } else if (i00 >= args.ne00) { i00 -= args.ne00; } - if (i01 < 0) { i01 += args.ne01; } else if (i01 >= args.ne01) { i01 -= args.ne01; } - if (i02 < 0) { i02 += args.ne02; } else if (i02 >= args.ne02) { i02 -= args.ne02; } - if (i03 < 0) { i03 += args.ne03; } else if (i03 >= args.ne03) { i03 -= args.ne03; } - - int64_t src_idx = i03*args.ne02*args.ne01*args.ne00 + i02*args.ne01*args.ne00 + i01*args.ne00 + i00; - int64_t dst_idx = i3 *args.ne2 *args.ne1 *args.ne0 + i2 *args.ne1 *args.ne0 + i1 *args.ne0 + i0; - - dst_ptr[dst_idx] = src0_ptr[src_idx]; - } -} - -kernel void kernel_pad_f32( - constant ggml_metal_kargs_pad & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; - - const int64_t i03 = i3; - const int64_t i02 = i2; - const int64_t i01 = i1; - - device const float * src0_ptr = (device const float *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); - device float * dst_ptr = (device float *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1); - - if (i1 < args.ne01 && i2 < args.ne02 && i3 < args.ne03) { - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - if (i0 < args.ne00) { - dst_ptr[i0] = src0_ptr[i0]; - } else { - dst_ptr[i0] = 0.0f; - } - } - - return; - } - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - dst_ptr[i0] = 0.0f; } + + dst.d = sumq2 > 0 ? sumqx/sumq2 : d; } -kernel void kernel_pad_left_f32( - constant ggml_metal_kargs_pad_left & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { +template +void dequantize_q4_1(device const block_q4_1 * xb, short il, thread type4x4 & reg) { + device const uint16_t * qs = ((device const uint16_t *)xb + 2); + const float d1 = il ? (xb->d / 16.h) : xb->d; + const float d2 = d1 / 256.f; + const float m = xb->m; + const ushort mask0 = il ? 0x00F0 : 0x000F; + const ushort mask1 = mask0 << 8; - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; + float4x4 reg_f; - device char * dst_row = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1; - const int64_t i03 = i3 - args.lp3; - const int64_t i02 = i2 - args.lp2; - const int64_t i01 = i1 - args.lp1; - const bool in_src_row = i01 >= 0 && i01 < args.ne01 && - i02 >= 0 && i02 < args.ne02 && - i03 >= 0 && i03 < args.ne03; + for (int i = 0; i < 8; i++) { + reg_f[i/2][2*(i%2) + 0] = ((qs[i] & mask0) * d1) + m; + reg_f[i/2][2*(i%2) + 1] = ((qs[i] & mask1) * d2) + m; + } - if (in_src_row) { - device const char * src0_row = src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01; - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const int64_t i00 = i0 - args.lp0; - device float * dst_ptr = (device float *) (dst_row + i0*args.nb0); + reg = (type4x4) reg_f; +} - if (i00 >= 0 && i00 < args.ne00) { - device const float * src0_ptr = (device const float *) (src0_row + i00*args.nb00); - *dst_ptr = *src0_ptr; - } else { - *dst_ptr = 0.0f; - } - } +template +void dequantize_q4_1_t4(device const block_q4_1 * xb, short il, thread type4 & reg) { + device const uint16_t * qs = ((device const uint16_t *)xb + 2); + const float d1 = (il/4) ? (xb->d / 16.h) : xb->d; + const float d2 = d1 / 256.f; + const float m = xb->m; + const ushort mask0 = (il/4) ? 0x00F0 : 0x000F; + const ushort mask1 = mask0 << 8; - return; + for (int i = 0; i < 2; i++) { + reg[2*i + 0] = d1 * (qs[2*(il%4) + i] & mask0) + m; + reg[2*i + 1] = d2 * (qs[2*(il%4) + i] & mask1) + m; } +} - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - device float * dst_ptr = (device float *) (dst_row + i0*args.nb0); - *dst_ptr = 0.0f; +template +void dequantize_q5_0(device const block_q5_0 * xb, short il, thread type4x4 & reg) { + device const uint16_t * qs = ((device const uint16_t *)xb + 3); + const float d = xb->d; + const float md = -16.h * xb->d; + const ushort mask = il ? 0x00F0 : 0x000F; + + const uint32_t qh = *((device const uint32_t *)xb->qh); + + const int x_mv = il ? 4 : 0; + + const int gh_mv = il ? 12 : 0; + const int gh_bk = il ? 0 : 4; + + float4x4 reg_f; + + for (int i = 0; i < 8; i++) { + // extract the 5-th bits for x0 and x1 + const uint8_t xh_0 = ((qh >> (gh_mv + 2*i )) << gh_bk) & 0x10; + const uint8_t xh_1 = ((qh >> (gh_mv + 2*i+1)) << gh_bk) & 0x10; + + // combine the 4-bits from qs with the 5th bit + const int32_t x0 = ((((qs[i] ) & mask) >> x_mv) | xh_0); + const int32_t x1 = ((((qs[i] >> 8) & mask) >> x_mv) | xh_1); + + reg_f[i/2][2*(i%2) + 0] = d * x0 + md; + reg_f[i/2][2*(i%2) + 1] = d * x1 + md; } + + reg = (type4x4) reg_f; } -kernel void kernel_pad_left_f16( - constant ggml_metal_kargs_pad_left & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { +template +void dequantize_q5_0_t4(device const block_q5_0 * xb, short il, thread type4 & reg) { + device const uint16_t * qs = ((device const uint16_t *)xb + 3); + const float d = xb->d; + const float md = -16.h * xb->d; + const ushort mask = (il/4) ? 0x00F0 : 0x000F; - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; + const uint32_t qh = *((device const uint32_t *)xb->qh); - device char * dst_row = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1; - const int64_t i03 = i3 - args.lp3; - const int64_t i02 = i2 - args.lp2; - const int64_t i01 = i1 - args.lp1; - const bool in_src_row = i01 >= 0 && i01 < args.ne01 && - i02 >= 0 && i02 < args.ne02 && - i03 >= 0 && i03 < args.ne03; + const int x_mv = (il/4) ? 4 : 0; - if (in_src_row) { - device const char * src0_row = src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01; - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const int64_t i00 = i0 - args.lp0; - device half * dst_ptr = (device half *) (dst_row + i0*args.nb0); + const int gh_mv = (il/4) ? 12 : 0; + const int gh_bk = (il/4) ? 0 : 4; - if (i00 >= 0 && i00 < args.ne00) { - device const half * src0_ptr = (device const half *) (src0_row + i00*args.nb00); - *dst_ptr = *src0_ptr; - } else { - *dst_ptr = 0.0h; - } - } + for (int ii = 0; ii < 2; ii++) { + int i = 2*(il%4) + ii; - return; - } + // extract the 5-th bits for x0 and x1 + const uint8_t xh_0 = ((qh >> (gh_mv + 2*i )) << gh_bk) & 0x10; + const uint8_t xh_1 = ((qh >> (gh_mv + 2*i+1)) << gh_bk) & 0x10; - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - device half * dst_ptr = (device half *) (dst_row + i0*args.nb0); - *dst_ptr = 0.0h; + // combine the 4-bits from qs with the 5th bit + const int32_t x0 = ((((qs[i] ) & mask) >> x_mv) | xh_0); + const int32_t x1 = ((((qs[i] >> 8) & mask) >> x_mv) | xh_1); + + reg[2*ii + 0] = d * x0 + md; + reg[2*ii + 1] = d * x1 + md; } } -kernel void kernel_pad_left_i32( - constant ggml_metal_kargs_pad_left & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; +template +void dequantize_q5_1(device const block_q5_1 * xb, short il, thread type4x4 & reg) { + device const uint16_t * qs = ((device const uint16_t *)xb + 4); + const float d = xb->d; + const float m = xb->m; + const ushort mask = il ? 0x00F0 : 0x000F; + + const uint32_t qh = *((device const uint32_t *)xb->qh); + + const int x_mv = il ? 4 : 0; + + const int gh_mv = il ? 12 : 0; + const int gh_bk = il ? 0 : 4; + + float4x4 reg_f; + + for (int i = 0; i < 8; i++) { + // extract the 5-th bits for x0 and x1 + const uint8_t xh_0 = ((qh >> (gh_mv + 2*i )) << gh_bk) & 0x10; + const uint8_t xh_1 = ((qh >> (gh_mv + 2*i+1)) << gh_bk) & 0x10; + + // combine the 4-bits from qs with the 5th bit + const int32_t x0 = ((((qs[i] ) & mask) >> x_mv) | xh_0); + const int32_t x1 = ((((qs[i] >> 8) & mask) >> x_mv) | xh_1); + + reg_f[i/2][2*(i%2) + 0] = d * x0 + m; + reg_f[i/2][2*(i%2) + 1] = d * x1 + m; + } + + reg = (type4x4) reg_f; +} + +template +void dequantize_q5_1_t4(device const block_q5_1 * xb, short il, thread type4 & reg) { + device const uint16_t * qs = ((device const uint16_t *)xb + 4); + const float d = xb->d; + const float m = xb->m; + const ushort mask = (il/4) ? 0x00F0 : 0x000F; + + const uint32_t qh = *((device const uint32_t *)xb->qh); + + const int x_mv = (il/4) ? 4 : 0; + + const int gh_mv = (il/4) ? 12 : 0; + const int gh_bk = (il/4) ? 0 : 4; + + for (int ii = 0; ii < 2; ii++) { + int i = 2*(il%4) + ii; + + // extract the 5-th bits for x0 and x1 + const uint8_t xh_0 = ((qh >> (gh_mv + 2*i )) << gh_bk) & 0x10; + const uint8_t xh_1 = ((qh >> (gh_mv + 2*i+1)) << gh_bk) & 0x10; + + // combine the 4-bits from qs with the 5th bit + const int32_t x0 = ((((qs[i] ) & mask) >> x_mv) | xh_0); + const int32_t x1 = ((((qs[i] >> 8) & mask) >> x_mv) | xh_1); + + reg[2*ii + 0] = d * x0 + m; + reg[2*ii + 1] = d * x1 + m; + } +} + +template +void dequantize_q8_0(device const block_q8_0 *xb, short il, thread type4x4 & reg) { + device const int8_t * qs = ((device const int8_t *)xb->qs); + const float d = xb->d; + + float4x4 reg_f; + + for (int i = 0; i < 16; i++) { + reg_f[i/4][i%4] = (qs[i + 16*il] * d); + } + + reg = (type4x4) reg_f; +} + +template +void dequantize_q8_0_t4(device const block_q8_0 *xb, short il, thread type4 & reg) { + device const int8_t * qs = ((device const int8_t *)xb->qs); + const float d = xb->d; + + for (int i = 0; i < 4; i++) { + reg[i] = (qs[4*(il%4) + i + 16*(il/4)] * d); + } +} + +template +void dequantize_mxfp4(device const block_mxfp4 * xb, short il, thread type4x4 & reg) { + device const uint8_t * q2 = (device const uint8_t *)xb->qs; + + const float d = e8m0_to_fp32(xb->e); + const uint8_t shr = il >= 1 ? 4 : 0; + + for (int i = 0; i < 4; ++i) { + reg[i][0] = d * kvalues_mxfp4_f[(q2[4*i + 0] >> shr) & 0x0F]; + reg[i][1] = d * kvalues_mxfp4_f[(q2[4*i + 1] >> shr) & 0x0F]; + reg[i][2] = d * kvalues_mxfp4_f[(q2[4*i + 2] >> shr) & 0x0F]; + reg[i][3] = d * kvalues_mxfp4_f[(q2[4*i + 3] >> shr) & 0x0F]; + } +} + +template +void dequantize_mxfp4_t4(device const block_mxfp4 * xb, short il, thread type4 & reg) { + device const uint8_t * q2 = (device const uint8_t *)xb->qs; + + const float d = e8m0_to_fp32(xb->e); + const short il4 = il%4; + + const uint8_t shr = il >= 4 ? 4 : 0; + + reg[0] = d * kvalues_mxfp4_f[(q2[4*il4 + 0] >> shr) & 0x0F]; + reg[1] = d * kvalues_mxfp4_f[(q2[4*il4 + 1] >> shr) & 0x0F]; + reg[2] = d * kvalues_mxfp4_f[(q2[4*il4 + 2] >> shr) & 0x0F]; + reg[3] = d * kvalues_mxfp4_f[(q2[4*il4 + 3] >> shr) & 0x0F]; +} + +template +void dequantize_q2_K(device const block_q2_K *xb, short il, thread type4x4 & reg) { + const float d = xb->d; + const float min = xb->dmin; + device const uint8_t * q = (device const uint8_t *)xb->qs; + float dl, ml; + uint8_t sc = xb->scales[il]; + + q = q + 32*(il/8) + 16*(il&1); + il = (il/2)%4; + + half coef = il>1 ? (il>2 ? 1/64.h : 1/16.h) : (il>0 ? 1/4.h : 1.h); + uchar mask = il>1 ? (il>2 ? 192 : 48) : (il>0 ? 12 : 3); + dl = d * (sc & 0xF) * coef, ml = min * (sc >> 4); + for (int i = 0; i < 16; ++i) { + reg[i/4][i%4] = dl * (q[i] & mask) - ml; + } +} + +template +void dequantize_q3_K(device const block_q3_K *xb, short il, thread type4x4 & reg) { + const half d_all = xb->d; + device const uint8_t * q = (device const uint8_t *)xb->qs; + device const uint8_t * h = (device const uint8_t *)xb->hmask; + device const int8_t * scales = (device const int8_t *)xb->scales; + + q = q + 32 * (il/8) + 16 * (il&1); + h = h + 16 * (il&1); + uint8_t m = 1 << (il/2); + uint16_t kmask1 = (il/4)>1 ? ((il/4)>2 ? 192 : 48) : \ + ((il/4)>0 ? 12 : 3); + uint16_t kmask2 = il/8 ? 0xF0 : 0x0F; + uint16_t scale_2 = scales[il%8], scale_1 = scales[8 + il%4]; + int16_t dl_int = (il/4)&1 ? (scale_2&kmask2) | ((scale_1&kmask1) << 2) + : (scale_2&kmask2) | ((scale_1&kmask1) << 4); + float dl = il<8 ? d_all * (dl_int - 32.f) : d_all * (dl_int / 16.f - 32.f); + const float ml = 4.f * dl; + + il = (il/2) & 3; + const half coef = il>1 ? (il>2 ? 1/64.h : 1/16.h) : (il>0 ? 1/4.h : 1.h); + const uint8_t mask = il>1 ? (il>2 ? 192 : 48) : (il>0 ? 12 : 3); + dl *= coef; + + for (int i = 0; i < 16; ++i) { + reg[i/4][i%4] = dl * (q[i] & mask) - (h[i] & m ? 0 : ml); + } +} + +static inline uchar2 get_scale_min_k4_just2(int j, int k, device const uchar * q) { + return j < 4 ? uchar2{uchar(q[j+0+k] & 63), uchar(q[j+4+k] & 63)} + : uchar2{uchar((q[j+4+k] & 0xF) | ((q[j-4+k] & 0xc0) >> 2)), uchar((q[j+4+k] >> 4) | ((q[j-0+k] & 0xc0) >> 2))}; +} + +template +void dequantize_q4_K(device const block_q4_K * xb, short il, thread type4x4 & reg) { + device const uchar * q = xb->qs; + + short is = (il/4) * 2; + q = q + (il/4) * 32 + 16 * (il&1); + il = il & 3; + const uchar2 sc = get_scale_min_k4_just2(is, il/2, xb->scales); + const float d = il < 2 ? xb->d : xb->d / 16.h; + const float min = xb->dmin; + const float dl = d * sc[0]; + const float ml = min * sc[1]; + + const ushort mask = il < 2 ? 0x0F : 0xF0; + for (int i = 0; i < 16; ++i) { + reg[i/4][i%4] = dl * (q[i] & mask) - ml; + } +} + +template +void dequantize_q5_K(device const block_q5_K *xb, short il, thread type4x4 & reg) { + device const uint8_t * q = xb->qs; + device const uint8_t * qh = xb->qh; + + short is = (il/4) * 2; + q = q + 32 * (il/4) + 16 * (il&1); + qh = qh + 16 * (il&1); + uint8_t ul = 1 << (il/2); + il = il & 3; + const uchar2 sc = get_scale_min_k4_just2(is, il/2, xb->scales); + const float d = il < 2 ? xb->d : xb->d / 16.f; + const float min = xb->dmin; + const float dl = d * sc[0]; + const float ml = min * sc[1]; + + const ushort mask = il<2 ? 0x0F : 0xF0; + const float qh_val = il<2 ? 16.f : 256.f; + for (int i = 0; i < 16; ++i) { + reg[i/4][i%4] = dl * ((q[i] & mask) + (qh[i] & ul ? qh_val : 0)) - ml; + } +} + +template +void dequantize_q6_K(device const block_q6_K *xb, short il, thread type4x4 & reg) { + const half d_all = xb->d; + device const uint16_t * ql = (device const uint16_t *)xb->ql; + device const uint16_t * qh = (device const uint16_t *)xb->qh; + device const int8_t * scales = (device const int8_t *)xb->scales; + + ql = ql + 32*(il/8) + 16*((il/2)&1) + 8*(il&1); + qh = qh + 16*(il/8) + 8*(il&1); + float sc = scales[(il%2) + 2 * ((il/2))]; + il = (il/2) & 3; + + const uint32_t kmask1 = il>1 ? (il>2 ? 0xC0C0C0C0 : 0x30303030) : (il>0 ? 0x0C0C0C0C : 0x03030303); + const uint32_t kmask2 = il>1 ? 0xF0F0F0F0 : 0x0F0F0F0F; + const float ml = d_all * sc * 32.f; + const float dl0 = d_all * sc; + const float dl1 = dl0 / 256.f; + const float dl2 = dl0 / (256.f * 256.f); + const float dl3 = dl0 / (256.f * 256.f * 256.f); + const uint8_t shr_h = il>2 ? 2 : 0; + const uint8_t shl_h = il>1 ? 0 : (il>0 ? 2 : 4); + const uint8_t shr_l = il>1 ? 4 : 0; + for (int i = 0; i < 4; ++i) { + const uint32_t low = (ql[2*i] | (uint32_t)(ql[2*i+1] << 16)) & kmask2; + const uint32_t high = (qh[2*i] | (uint32_t)(qh[2*i+1] << 16)) & kmask1; + const uint32_t q = ((high << shl_h) >> shr_h) | (low >> shr_l); + reg[i][0] = dl0 * ((half)(q & 0xFF)) - ml; + reg[i][1] = dl1 * ((float)(q & 0xFF00)) - ml; + reg[i][2] = dl2 * ((float)(q & 0xFF0000)) - ml; + reg[i][3] = dl3 * ((float)(q & 0xFF000000)) - ml; + } +} + +template +void dequantize_iq2_xxs(device const block_iq2_xxs * xb, short il, thread type4x4 & reg) { + // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 + const float d = xb->d; + const int ib32 = il/2; + il = il%2; + // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 + // each block of 32 needs 2 uint32_t's for the quants & scale, so 4 uint16_t's. + device const uint16_t * q2 = xb->qs + 4*ib32; + const uint32_t aux32_g = q2[0] | (q2[1] << 16); + const uint32_t aux32_s = q2[2] | (q2[3] << 16); + thread const uint8_t * aux8 = (thread const uint8_t *)&aux32_g; + const float dl = d * (0.5f + (aux32_s >> 28)) * 0.25f; + constant uint8_t * grid = (constant uint8_t *)(iq2xxs_grid + aux8[2*il+0]); + uint8_t signs = ksigns_iq2xs[(aux32_s >> 14*il) & 127]; + for (int i = 0; i < 8; ++i) { + reg[i/4][i%4] = dl * grid[i] * (signs & kmask_iq2xs[i] ? -1.f : 1.f); + } + grid = (constant uint8_t *)(iq2xxs_grid + aux8[2*il+1]); + signs = ksigns_iq2xs[(aux32_s >> (14*il+7)) & 127]; + for (int i = 0; i < 8; ++i) { + reg[2+i/4][i%4] = dl * grid[i] * (signs & kmask_iq2xs[i] ? -1.f : 1.f); + } +} + +template +void dequantize_iq2_xs(device const block_iq2_xs * xb, short il, thread type4x4 & reg) { + // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 + const float d = xb->d; + const int ib32 = il/2; + il = il%2; + // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 + device const uint16_t * q2 = xb->qs + 4*ib32; + const float dl = d * (0.5f + ((xb->scales[ib32] >> 4*il) & 0xf)) * 0.25f; + constant uint8_t * grid = (constant uint8_t *)(iq2xs_grid + (q2[2*il+0] & 511)); + uint8_t signs = ksigns_iq2xs[q2[2*il+0] >> 9]; + for (int i = 0; i < 8; ++i) { + reg[i/4][i%4] = dl * grid[i] * (signs & kmask_iq2xs[i] ? -1.f : 1.f); + } + grid = (constant uint8_t *)(iq2xs_grid + (q2[2*il+1] & 511)); + signs = ksigns_iq2xs[q2[2*il+1] >> 9]; + for (int i = 0; i < 8; ++i) { + reg[2+i/4][i%4] = dl * grid[i] * (signs & kmask_iq2xs[i] ? -1.f : 1.f); + } +} + +template +void dequantize_iq3_xxs(device const block_iq3_xxs * xb, short il, thread type4x4 & reg) { + // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 + const float d = xb->d; + const int ib32 = il/2; + il = il%2; + // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 + device const uint8_t * q3 = xb->qs + 8*ib32; + device const uint16_t * gas = (device const uint16_t *)(xb->qs + QK_K/4) + 2*ib32; + const uint32_t aux32 = gas[0] | (gas[1] << 16); + const float dl = d * (0.5f + (aux32 >> 28)) * 0.5f; + constant uint8_t * grid1 = (constant uint8_t *)(iq3xxs_grid + q3[4*il+0]); + constant uint8_t * grid2 = (constant uint8_t *)(iq3xxs_grid + q3[4*il+1]); + uint8_t signs = ksigns_iq2xs[(aux32 >> 14*il) & 127]; + for (int i = 0; i < 4; ++i) { + reg[0][i] = dl * grid1[i] * (signs & kmask_iq2xs[i+0] ? -1.f : 1.f); + reg[1][i] = dl * grid2[i] * (signs & kmask_iq2xs[i+4] ? -1.f : 1.f); + } + grid1 = (constant uint8_t *)(iq3xxs_grid + q3[4*il+2]); + grid2 = (constant uint8_t *)(iq3xxs_grid + q3[4*il+3]); + signs = ksigns_iq2xs[(aux32 >> (14*il+7)) & 127]; + for (int i = 0; i < 4; ++i) { + reg[2][i] = dl * grid1[i] * (signs & kmask_iq2xs[i+0] ? -1.f : 1.f); + reg[3][i] = dl * grid2[i] * (signs & kmask_iq2xs[i+4] ? -1.f : 1.f); + } +} + +template +void dequantize_iq3_s(device const block_iq3_s * xb, short il, thread type4x4 & reg) { + // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 + const float d = xb->d; + const int ib32 = il/2; + il = il%2; + // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 + device const uint8_t * qs = xb->qs + 8*ib32; + device const uint8_t * signs = xb->signs + 4*ib32 + 2*il; + const uint8_t qh = xb->qh[ib32] >> 4*il; + const float dl = d * (1 + 2*((xb->scales[ib32/2] >> 4*(ib32%2)) & 0xf)); + constant uint8_t * grid1 = (constant uint8_t *)(iq3s_grid + (qs[4*il+0] | ((qh << 8) & 256))); + constant uint8_t * grid2 = (constant uint8_t *)(iq3s_grid + (qs[4*il+1] | ((qh << 7) & 256))); + for (int i = 0; i < 4; ++i) { + reg[0][i] = dl * grid1[i] * select(1, -1, signs[0] & kmask_iq2xs[i+0]); + reg[1][i] = dl * grid2[i] * select(1, -1, signs[0] & kmask_iq2xs[i+4]); + } + grid1 = (constant uint8_t *)(iq3s_grid + (qs[4*il+2] | ((qh << 6) & 256))); + grid2 = (constant uint8_t *)(iq3s_grid + (qs[4*il+3] | ((qh << 5) & 256))); + for (int i = 0; i < 4; ++i) { + reg[2][i] = dl * grid1[i] * select(1, -1, signs[1] & kmask_iq2xs[i+0]); + reg[3][i] = dl * grid2[i] * select(1, -1, signs[1] & kmask_iq2xs[i+4]); + } +} + +template +void dequantize_iq2_s(device const block_iq2_s * xb, short il, thread type4x4 & reg) { + // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 + const float d = xb->d; + const int ib32 = il/2; + il = il%2; + // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 + device const uint8_t * qs = xb->qs + 4*ib32 + 2*il; + device const uint8_t * signs = qs + QK_K/8; + const uint8_t qh = xb->qh[ib32] >> 4*il; + const float dl = d * (0.5f + ((xb->scales[ib32] >> 4*il) & 0xf)) * 0.25f; + constant uint8_t * grid1 = (constant uint8_t *)(iq2s_grid + (qs[0] | ((qh << 8) & 0x300))); + constant uint8_t * grid2 = (constant uint8_t *)(iq2s_grid + (qs[1] | ((qh << 6) & 0x300))); + for (int i = 0; i < 8; ++i) { + reg[i/4+0][i%4] = dl * grid1[i] * select(1, -1, signs[0] & kmask_iq2xs[i]); + reg[i/4+2][i%4] = dl * grid2[i] * select(1, -1, signs[1] & kmask_iq2xs[i]); + } +} + +template +void dequantize_iq1_s(device const block_iq1_s * xb, short il, thread type4x4 & reg) { + // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 + const int ib32 = il/2; + il = il%2; + const float d = xb->d; + device const uint8_t * qs = xb->qs + 4*ib32 + 2*il; + device const uint16_t * qh = xb->qh; + const float dl = d * (2*((qh[ib32] >> 12) & 7) + 1); + const float ml = dl * (qh[ib32] & 0x8000 ? -1 - IQ1S_DELTA : -1 + IQ1S_DELTA); + const uint16_t h = qh[ib32] >> 6*il; + constant uint8_t * grid1 = (constant uint8_t *)(iq1s_grid_gpu + (qs[0] | ((h << 8) & 0x700))); + constant uint8_t * grid2 = (constant uint8_t *)(iq1s_grid_gpu + (qs[1] | ((h << 5) & 0x700))); + for (int i = 0; i < 4; ++i) { + reg[0][i] = dl * (grid1[i] & 0xf) + ml; + reg[1][i] = dl * (grid1[i] >> 4) + ml; + reg[2][i] = dl * (grid2[i] & 0xf) + ml; + reg[3][i] = dl * (grid2[i] >> 4) + ml; + } +} + +template +void dequantize_iq1_m(device const block_iq1_m * xb, short il, thread type4x4 & reg) { + // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 + const int ib32 = il/2; + il = il%2; + device const uint16_t * sc = (device const uint16_t *)xb->scales; + + iq1m_scale_t scale; + scale.u16 = (sc[0] >> 12) | ((sc[1] >> 8) & 0x00f0) | ((sc[2] >> 4) & 0x0f00) | (sc[3] & 0xf000); + const float d = scale.f16; + + device const uint8_t * qs = xb->qs + 4*ib32 + 2*il; + device const uint8_t * qh = xb->qh + 2*ib32 + il; + + const float dl = d * (2*((sc[ib32/2] >> (6*(ib32%2)+3*il)) & 7) + 1); + const float ml1 = dl * (qh[0] & 0x08 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA); + const float ml2 = dl * (qh[0] & 0x80 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA); + constant uint8_t * grid1 = (constant uint8_t *)(iq1s_grid_gpu + (qs[0] | ((qh[0] << 8) & 0x700))); + constant uint8_t * grid2 = (constant uint8_t *)(iq1s_grid_gpu + (qs[1] | ((qh[0] << 4) & 0x700))); + for (int i = 0; i < 4; ++i) { + reg[0][i] = dl * (grid1[i] & 0xf) + ml1; + reg[1][i] = dl * (grid1[i] >> 4) + ml1; + reg[2][i] = dl * (grid2[i] & 0xf) + ml2; + reg[3][i] = dl * (grid2[i] >> 4) + ml2; + } +} + +template +void dequantize_iq4_nl(device const block_iq4_nl * xb, short il, thread type4x4 & reg) { + device const uint16_t * q4 = (device const uint16_t *)xb->qs; + const float d = xb->d; + uint32_t aux32; + thread const uint8_t * q8 = (thread const uint8_t *)&aux32; + for (int i = 0; i < 4; ++i) { + aux32 = ((q4[2*i] | (q4[2*i+1] << 16)) >> 4*il) & 0x0f0f0f0f; + reg[i][0] = d * kvalues_iq4nl_f[q8[0]]; + reg[i][1] = d * kvalues_iq4nl_f[q8[1]]; + reg[i][2] = d * kvalues_iq4nl_f[q8[2]]; + reg[i][3] = d * kvalues_iq4nl_f[q8[3]]; + } +} + +template +void dequantize_iq4_nl_t4(device const block_iq4_nl * xb, short il, thread type4 & reg) { + device const uint16_t * q4 = (device const uint16_t *)xb->qs; + const float d = xb->d; + uint32_t aux32; + thread const uint8_t * q8 = (thread const uint8_t *)&aux32; + aux32 = ((q4[2*(il%4)] | (q4[2*(il%4)+1] << 16)) >> 4*(il/4)) & 0x0f0f0f0f; + reg[0] = d * kvalues_iq4nl_f[q8[0]]; + reg[1] = d * kvalues_iq4nl_f[q8[1]]; + reg[2] = d * kvalues_iq4nl_f[q8[2]]; + reg[3] = d * kvalues_iq4nl_f[q8[3]]; +} + +template +void dequantize_iq4_xs(device const block_iq4_xs * xb, short il, thread type4x4 & reg) { + // il is 0...15 for QK_K = 256 => index of block of 32 is il/2 + const int ib32 = il/2; + il = il%2; + // il = 0 or 1. il = 0 processes the first 16 quants in a block of 32, il = 1 the second 16 + device const uint32_t * q4 = (device const uint32_t *)xb->qs + 4*ib32; + const int ls = ((xb->scales_l[ib32/2] >> 4*(ib32%2)) & 0xf) | (((xb->scales_h >> 2*ib32) & 3) << 4); + const float d = (float)xb->d * (ls - 32); + uint32_t aux32; + thread const uint8_t * q8 = (thread const uint8_t *)&aux32; + for (int i = 0; i < 4; ++i) { + aux32 = (q4[i] >> 4*il) & 0x0f0f0f0f; + reg[i][0] = d * kvalues_iq4nl_f[q8[0]]; + reg[i][1] = d * kvalues_iq4nl_f[q8[1]]; + reg[i][2] = d * kvalues_iq4nl_f[q8[2]]; + reg[i][3] = d * kvalues_iq4nl_f[q8[3]]; + } +} + +enum ggml_sort_order { + GGML_SORT_ORDER_ASC, + GGML_SORT_ORDER_DESC, +}; + +constant float GELU_COEF_A = 0.044715f; +constant float GELU_QUICK_COEF = -1.702f; +constant float SQRT_2_OVER_PI = 0.79788456080286535587989211986876f; +constant float SQRT_2_INV = 0.70710678118654752440084436210484f; + +// based on Abramowitz and Stegun formula 7.1.26 or similar Hastings' approximation +// ref: https://www.johndcook.com/blog/python_erf/ +constant float p_erf = 0.3275911f; +constant float a1_erf = 0.254829592f; +constant float a2_erf = -0.284496736f; +constant float a3_erf = 1.421413741f; +constant float a4_erf = -1.453152027f; +constant float a5_erf = 1.061405429f; + +template +inline T erf_approx(T x) { + T sign_x = sign(x); + x = fabs(x); + T t = 1.0f / (1.0f + p_erf * x); + T y = 1.0f - (((((a5_erf * t + a4_erf) * t) + a3_erf) * t + a2_erf) * t + a1_erf) * t * exp(-x * x); + return sign_x * y; +} + +template T elu_approx(T x); + +template<> inline float elu_approx(float x) { + return (x > 0.f) ? x : (exp(x) - 1); +} + +template<> inline float4 elu_approx(float4 x) { + float4 res; + + res[0] = (x[0] > 0.0f) ? x[0] : (exp(x[0]) - 1.0f); + res[1] = (x[1] > 0.0f) ? x[1] : (exp(x[1]) - 1.0f); + res[2] = (x[2] > 0.0f) ? x[2] : (exp(x[2]) - 1.0f); + res[3] = (x[3] > 0.0f) ? x[3] : (exp(x[3]) - 1.0f); + + return res; +} + +constant short FC_unary_op [[function_constant(FC_UNARY + 0)]]; +constant bool FC_unary_cnt[[function_constant(FC_UNARY + 1)]]; + +template +kernel void kernel_unary_impl( + constant ggml_metal_kargs_unary & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { +#define FC_OP FC_unary_op +#define FC_CNT FC_unary_cnt + + device const T0 * src0_ptr; + device T * dst_ptr; + + int i0; + + if (FC_CNT) { + i0 = tgpig.x; + + src0_ptr = (device const T0 *) (src0); + dst_ptr = (device T *) (dst); + } else { + const int i03 = tgpig.z; + const int i02 = tgpig.y; + const int k0 = tgpig.x/args.ne01; + const int i01 = tgpig.x - k0*args.ne01; + + i0 = k0*ntg.x + tpitg.x; + + src0_ptr = (device const T0 *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); + dst_ptr = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1 ); + } + + { + //threadgroup_barrier(mem_flags::mem_none); + + if (!FC_CNT) { + if (i0 >= args.ne0) { + return; + } + } + + const TC x = (TC) src0_ptr[i0]; + + if (FC_OP == OP_UNARY_NUM_SCALE) { + dst_ptr[i0] = (T) (args.scale * x + args.bias); + } + + if (FC_OP == OP_UNARY_NUM_FILL) { + dst_ptr[i0] = (T) args.val; + } + + if (FC_OP == OP_UNARY_NUM_CLAMP) { + dst_ptr[i0] = (T) clamp(x, args.min, args.max); + } + + if (FC_OP == OP_UNARY_NUM_SQR) { + dst_ptr[i0] = (T) (x * x); + } + + if (FC_OP == OP_UNARY_NUM_SQRT) { + dst_ptr[i0] = (T) sqrt(x); + } + + if (FC_OP == OP_UNARY_NUM_SIN) { + dst_ptr[i0] = (T) sin(x); + } + + if (FC_OP == OP_UNARY_NUM_COS) { + dst_ptr[i0] = (T) cos(x); + } + + if (FC_OP == OP_UNARY_NUM_LOG) { + dst_ptr[i0] = (T) log(x); + } + + if (FC_OP == OP_UNARY_NUM_LEAKY_RELU) { + dst_ptr[i0] = (T) (TC(x > 0)*x + TC(x <= 0)*(x * args.slope)); + } + + if (FC_OP == OP_UNARY_NUM_TANH) { + dst_ptr[i0] = (T) precise::tanh(x); + } + + if (FC_OP == OP_UNARY_NUM_RELU) { + dst_ptr[i0] = (T) fmax(0, x); + } + + if (FC_OP == OP_UNARY_NUM_SIGMOID) { + dst_ptr[i0] = (T) (1 / (1 + exp(-x))); + } + + if (FC_OP == OP_UNARY_NUM_GELU) { + dst_ptr[i0] = (T) (0.5*x*(1 + precise::tanh(SQRT_2_OVER_PI*x*(1 + GELU_COEF_A*x*x)))); + } + + if (FC_OP == OP_UNARY_NUM_GELU_ERF) { + dst_ptr[i0] = (T) (0.5*x*(1 + erf_approx(SQRT_2_INV*x))); + } + + if (FC_OP == OP_UNARY_NUM_GELU_QUICK) { + dst_ptr[i0] = (T) (x * (1/(1 + exp(GELU_QUICK_COEF*x)))); + } + + if (FC_OP == OP_UNARY_NUM_SILU) { + dst_ptr[i0] = (T) (x / (1 + exp(-x))); + } + + if (FC_OP == OP_UNARY_NUM_ELU) { + dst_ptr[i0] = (T) elu_approx(x); + } + + if (FC_OP == OP_UNARY_NUM_NEG) { + dst_ptr[i0] = (T) -x; + } + + if (FC_OP == OP_UNARY_NUM_ABS) { + dst_ptr[i0] = (T) fabs(x); + } + + if (FC_OP == OP_UNARY_NUM_SGN) { + dst_ptr[i0] = T(x > 0) - T(x < 0); + } + + if (FC_OP == OP_UNARY_NUM_STEP) { + dst_ptr[i0] = T(x > 0); + } + + if (FC_OP == OP_UNARY_NUM_HARDSWISH) { + dst_ptr[i0] = (T) (x * fmax(0, fmin(1, x/6 + 0.5))); + } + + if (FC_OP == OP_UNARY_NUM_HARDSIGMOID) { + dst_ptr[i0] = (T) fmax(0, fmin(1, x/6 + 0.5)); + } + + if (FC_OP == OP_UNARY_NUM_EXP) { + dst_ptr[i0] = (T) exp(x); + } + + if (FC_OP == OP_UNARY_NUM_SOFTPLUS) { + dst_ptr[i0] = (T) select(log(1 + exp(x)), x, x > 20); + } + + if (FC_OP == OP_UNARY_NUM_EXPM1) { + // TODO: precise implementation + dst_ptr[i0] = (T) (exp(x) - 1); + } + + if (FC_OP == OP_UNARY_NUM_FLOOR) { + dst_ptr[i0] = (T) floor(x); + } + + if (FC_OP == OP_UNARY_NUM_CEIL) { + dst_ptr[i0] = (T) ceil(x); + } + + if (FC_OP == OP_UNARY_NUM_ROUND) { + dst_ptr[i0] = (T) round(x); + } + + if (FC_OP == OP_UNARY_NUM_TRUNC) { + dst_ptr[i0] = (T) trunc(x); + } + + if (FC_OP == OP_UNARY_NUM_XIELU) { + const TC xi = x; + const TC gate = TC(xi > TC(0.0f)); + const TC clamped = fmin(xi, TC(args.val)); + const TC y_pos = TC(args.scale) * xi * xi + TC(args.bias) * xi; + const TC y_neg = (exp(clamped) - TC(1.0f) - xi) * TC(args.slope) + TC(args.bias) * xi; + dst_ptr[i0] = (T) (gate * y_pos + (TC(1.0f) - gate) * y_neg); + } + } + +#undef FC_OP +#undef FC_CNT +} + +// fused snake activation: y = x + sin(x * alpha[c])^2 / alpha[c], with c = i0 the +// channel (fast axis). One elementwise pass replacing the mul -> sin -> mul -> div +// -> add chain; the per-element op sequence matches the chain bit-for-bit (f32 ops, +// precise sin), so outputs are identical. +kernel void kernel_snake_1d_f32( + constant ggml_metal_kargs_snake_1d & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int n = args.ne00*args.ne01; + + const int ith = tgpig.x*ntg.x + tpitg.x; + + if (ith >= n) { + return; + } + + const int i0 = ith % args.ne00; + const int i1 = ith / args.ne00; + + device const float * x = (device const float *)(src0 + i0*args.nb00 + i1*args.nb01); + device const float * a = (device const float *)(src1 + i0*4); // alpha [C,1] contiguous f32 + device float * y = (device float *)(dst + i0*args.nb0 + i1*args.nb1); + + const float xv = x[0]; + const float av = a[0]; + const float ax = xv * av; + const float s = sin(ax); + const float s2 = s * s; + + y[0] = xv + s2/av; +} + +typedef decltype(kernel_unary_impl) kernel_unary_t; + +template [[host_name("kernel_unary_f32_f32")]] kernel kernel_unary_t kernel_unary_impl; +template [[host_name("kernel_unary_f32_f32_4")]] kernel kernel_unary_t kernel_unary_impl; +template [[host_name("kernel_unary_f16_f16")]] kernel kernel_unary_t kernel_unary_impl; +template [[host_name("kernel_unary_f16_f16_4")]] kernel kernel_unary_t kernel_unary_impl; + +// OP: 0 - add, 1 - sub, 2 - mul, 3 - div +constant short FC_bin_op [[function_constant(FC_BIN + 0)]]; +constant short FC_bin_f [[function_constant(FC_BIN + 1)]]; +constant bool FC_bin_rb [[function_constant(FC_BIN + 2)]]; +constant bool FC_bin_cb [[function_constant(FC_BIN + 3)]]; + +template +kernel void kernel_bin_fuse_impl( + constant ggml_metal_kargs_bin & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { +#define FC_OP FC_bin_op +#define FC_F FC_bin_f +#define FC_RB FC_bin_rb +#define FC_CB FC_bin_cb + + if (FC_RB) { + // row broadcast + const uint i0 = tgpig.y*args.ne00 + tgpig.x; + const uint i1 = FC_CB ? tgpig.x%args.ne10 : tgpig.x; + + device const T0 * src0_row = (device const T0 *) (src0); + device T * dst_row = (device T *) (dst); + + if (FC_F == 1) { + device const T1 * src1_row = (device const T1 *) (src1 + args.o1[0]); + + if (FC_OP == 0) { + dst_row[i0] = src0_row[i0] + src1_row[i1]; + } + + if (FC_OP == 1) { + dst_row[i0] = src0_row[i0] - src1_row[i1]; + } + + if (FC_OP == 2) { + dst_row[i0] = src0_row[i0] * src1_row[i1]; + } + + if (FC_OP == 3) { + dst_row[i0] = src0_row[i0] / src1_row[i1]; + } + } else { + T0 res = src0_row[i0]; + + if (FC_OP == 0) { + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + res += ((device const T1 *) (src1 + args.o1[j]))[i1]; + } + } + + if (FC_OP == 1) { + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + res -= ((device const T1 *) (src1 + args.o1[j]))[i1]; + } + } + + if (FC_OP == 2) { + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + res *= ((device const T1 *) (src1 + args.o1[j]))[i1]; + } + } + + if (FC_OP == 3) { + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + res /= ((device const T1 *) (src1 + args.o1[j]))[i1]; + } + } + + dst_row[i0] = res; + } + } else { + const int i03 = tgpig.z; + const int i02 = tgpig.y; + const int i01 = tgpig.x; + + if (i01 >= args.ne01) { + return; + } + + const int i13 = i03%args.ne13; + const int i12 = i02%args.ne12; + const int i11 = i01%args.ne11; + + device const T0 * src0_ptr = (device const T0 *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + args.offs); + device T * dst_ptr = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1 + args.offs); + + if (FC_F == 1) { + device const T1 * src1_ptr = (device const T1 *) (src1 + args.o1[0] + i13*args.nb13 + i12*args.nb12 + i11*args.nb11); + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const int i10 = FC_CB ? i0%args.ne10 : i0; + + if (FC_OP == 0) { + dst_ptr[i0] = src0_ptr[i0] + src1_ptr[i10]; + } + + if (FC_OP == 1) { + dst_ptr[i0] = src0_ptr[i0] - src1_ptr[i10]; + } + + if (FC_OP == 2) { + dst_ptr[i0] = src0_ptr[i0] * src1_ptr[i10]; + } + + if (FC_OP == 3) { + dst_ptr[i0] = src0_ptr[i0] / src1_ptr[i10]; + } + } + } else { + device const T1 * src1_ptr[8]; + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + src1_ptr[j] = (device const T1 *) (src1 + args.o1[j] + i13*args.nb13 + i12*args.nb12 + i11*args.nb11); + } + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const int i10 = FC_CB ? i0%args.ne10 : i0; + + T res = src0_ptr[i0]; + + if (FC_OP == 0) { + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + res += src1_ptr[j][i10]; + } + } + + if (FC_OP == 1) { + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + res -= src1_ptr[j][i10]; + } + } + + if (FC_OP == 2) { + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + res *= src1_ptr[j][i10]; + } + } + + if (FC_OP == 3) { + FOR_UNROLL (short j = 0; j < FC_F; ++j) { + res /= src1_ptr[j][i10]; + } + } + + dst_ptr[i0] = res; + } + } + } + +#undef FC_OP +#undef FC_F +#undef FC_RB +#undef FC_CB +} + +typedef decltype(kernel_bin_fuse_impl) kernel_bin_fuse_t; + +template [[host_name("kernel_bin_fuse_f32_f32_f32")]] kernel kernel_bin_fuse_t kernel_bin_fuse_impl; +template [[host_name("kernel_bin_fuse_f32_f32_f32_4")]] kernel kernel_bin_fuse_t kernel_bin_fuse_impl; + +template +kernel void kernel_bin_bcast_impl( + constant ggml_metal_kargs_bin & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int i0 = tgpig.x*ntg.x + tpitg.x; + const int i1 = tgpig.y; + const int i2 = tgpig.z % args.ne2; + const int i3 = tgpig.z / args.ne2; + + if (i0 >= args.ne0) { + return; + } + + const int i00 = i0 % args.ne00; + const int i01 = i1 % args.ne01; + const int i02 = i2 % args.ne02; + const int i03 = i3 % args.ne03; + + const int i10 = i0 % args.ne10; + const int i11 = i1 % args.ne11; + const int i12 = i2 % args.ne12; + const int i13 = i3 % args.ne13; + + device const T * src0_ptr = (device const T *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + i00*args.nb00); + device const T * src1_ptr = (device const T *) (src1 + i13*args.nb13 + i12*args.nb12 + i11*args.nb11 + i10*args.nb10); + device T * dst_ptr = (device T *) (dst + i3 *args.nb3 + i2 *args.nb2 + i1 *args.nb1 + i0 *args.nb0); + + if (FC_bin_op == 0) { + *dst_ptr = *src0_ptr + *src1_ptr; + } + + if (FC_bin_op == 1) { + *dst_ptr = *src0_ptr - *src1_ptr; + } +} + +typedef decltype(kernel_bin_bcast_impl) kernel_bin_bcast_f32_t; +typedef decltype(kernel_bin_bcast_impl) kernel_bin_bcast_f16_t; + +template [[host_name("kernel_bin_bcast_f32")]] kernel kernel_bin_bcast_f32_t kernel_bin_bcast_impl; +template [[host_name("kernel_bin_bcast_f16")]] kernel kernel_bin_bcast_f16_t kernel_bin_bcast_impl; + +kernel void kernel_add_id( + constant ggml_metal_kargs_add_id & args, + device const char * src0, + device const char * src1, + device const char * src2, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int i1 = tgpig.x; + const int i2 = tgpig.y; + + const int i11 = *((device const int32_t *) (src2 + i1*sizeof(int32_t) + i2*args.nb21)); + + const size_t nb1 = args.ne0 * sizeof(float); + const size_t nb2 = args.ne1 * nb1; + + device float * dst_row = (device float *)((device char *)dst + i1*nb1 + i2*nb2); + device const float * src0_row = (device const float *)((device char *)src0 + i1*args.nb01 + i2*args.nb02); + device const float * src1_row = (device const float *)((device char *)src1 + i11*args.nb11); + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + dst_row[i0] = src0_row[i0] + src1_row[i0]; + } +} + +template +kernel void kernel_repeat( + constant ggml_metal_kargs_repeat & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int i3 = tgpig.z; + const int i2 = tgpig.y; + const int i1 = tgpig.x; + + const int i03 = i3%args.ne03; + const int i02 = i2%args.ne02; + const int i01 = i1%args.ne01; + + device const char * src0_ptr = src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01; + device char * dst_ptr = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1; + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const int i00 = i0%args.ne00; + *((device T *)(dst_ptr + i0*args.nb0)) = *((device T *)(src0_ptr + i00*args.nb00)); + } +} + +typedef decltype(kernel_repeat) kernel_repeat_t; + +template [[host_name("kernel_repeat_f32")]] kernel kernel_repeat_t kernel_repeat; +template [[host_name("kernel_repeat_f16")]] kernel kernel_repeat_t kernel_repeat; +template [[host_name("kernel_repeat_i32")]] kernel kernel_repeat_t kernel_repeat; +template [[host_name("kernel_repeat_i16")]] kernel kernel_repeat_t kernel_repeat; + +kernel void kernel_reglu_f32( + constant ggml_metal_kargs_glu & args, + device const char * src0, + device const char * src1, + device char * dst, + uint tgpig[[threadgroup_position_in_grid]], + uint tpitg[[thread_position_in_threadgroup]], + uint ntg[[threads_per_threadgroup]]) { + device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; + device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; + device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); + + for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { + const float x0 = src0_row[i0]; + const float x1 = src1_row[i0]; + + dst_row[i0] = x0*x1*(x0 > 0.0f); + } +} + +kernel void kernel_geglu_f32( + constant ggml_metal_kargs_glu & args, + device const char * src0, + device const char * src1, + device char * dst, + uint tgpig[[threadgroup_position_in_grid]], + uint tpitg[[thread_position_in_threadgroup]], + uint ntg[[threads_per_threadgroup]]) { + device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; + device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; + device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); + + for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { + const float x0 = src0_row[i0]; + const float x1 = src1_row[i0]; + + const float gelu = 0.5f*x0*(1.0f + precise::tanh(SQRT_2_OVER_PI*x0*(1.0f + GELU_COEF_A*x0*x0))); + + dst_row[i0] = gelu*x1; + } +} + +kernel void kernel_swiglu_f32( + constant ggml_metal_kargs_glu & args, + device const char * src0, + device const char * src1, + device char * dst, + uint tgpig[[threadgroup_position_in_grid]], + uint tpitg[[thread_position_in_threadgroup]], + uint ntg[[threads_per_threadgroup]]) { + device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; + device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; + device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); + + for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { + const float x0 = src0_row[i0]; + const float x1 = src1_row[i0]; + + const float silu = x0 / (1.0f + exp(-x0)); + + dst_row[i0] = silu*x1; + } +} + +kernel void kernel_swiglu_oai_f32( + constant ggml_metal_kargs_glu & args, + device const char * src0, + device const char * src1, + device char * dst, + uint tgpig[[threadgroup_position_in_grid]], + uint tpitg[[thread_position_in_threadgroup]], + uint ntg[[threads_per_threadgroup]]) { + device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; + device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; + device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); + + for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { + float x0 = src0_row[i0]; + float x1 = src1_row[i0]; + + x0 = min(x0, args.limit); + x1 = max(min(x1, args.limit), -args.limit); + + float out_glu = x0 / (1.0f + exp(-x0 * args.alpha)); + out_glu = out_glu * (1.0f + x1); + + dst_row[i0] = out_glu; + } +} + +kernel void kernel_geglu_erf_f32( + constant ggml_metal_kargs_glu & args, + device const char * src0, + device const char * src1, + device char * dst, + uint tgpig[[threadgroup_position_in_grid]], + uint tpitg[[thread_position_in_threadgroup]], + uint ntg[[threads_per_threadgroup]]) { + device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; + device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; + device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); + + for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { + const float x0 = src0_row[i0]; + const float x1 = src1_row[i0]; + + const float gelu_erf = 0.5f*x0*(1.0f+erf_approx(x0*SQRT_2_INV)); + + dst_row[i0] = gelu_erf*x1; + } +} + +kernel void kernel_geglu_quick_f32( + constant ggml_metal_kargs_glu & args, + device const char * src0, + device const char * src1, + device char * dst, + uint tgpig[[threadgroup_position_in_grid]], + uint tpitg[[thread_position_in_threadgroup]], + uint ntg[[threads_per_threadgroup]]) { + device const float * src0_row = (device const float *) ((device const char *) src0 + tgpig*args.nb01) + args.i00; + device const float * src1_row = (device const float *) ((device const char *) src1 + tgpig*args.nb11) + args.i10; + device float * dst_row = (device float *) ((device char *) dst + tgpig*args.nb1); + + for (int i0 = tpitg; i0 < args.ne0; i0 += ntg) { + const float x0 = src0_row[i0]; + const float x1 = src1_row[i0]; + + const float gelu_quick = x0*(1.0f/(1.0f+exp(GELU_QUICK_COEF*x0))); + + dst_row[i0] = gelu_quick*x1; + } +} + +kernel void kernel_op_sum_f32( + constant ggml_metal_kargs_sum & args, + device const float * src0, + device float * dst, + threadgroup float * shmem_f32 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + + if (args.np == 0) { + return; + } + + // TODO: become function constant + const uint nsg = (ntg.x + 31) / 32; + + float sumf = 0; + + for (uint64_t i0 = tpitg.x; i0 < args.np; i0 += ntg.x) { + sumf += src0[i0]; + } + + sumf = simd_sum(sumf); + + if (tiisg == 0) { + shmem_f32[sgitg] = sumf; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + float total = 0; + + if (sgitg == 0) { + float v = 0; + + if (tpitg.x < nsg) { + v = shmem_f32[tpitg.x]; + } + + total = simd_sum(v); + + if (tpitg.x == 0) { + dst[0] = total; + } + } +} + +constant short FC_sum_rows_op [[function_constant(FC_SUM_ROWS + 0)]]; + +template +kernel void kernel_sum_rows_impl( + constant ggml_metal_kargs_sum_rows & args, + device const char * src0, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { +#define FC_OP FC_sum_rows_op + + const int i3 = tgpig.z; + const int i2 = tgpig.y; + const int i1 = tgpig.x; + + threadgroup T0 * shmem_t = (threadgroup T0 *) shmem; + + if (sgitg == 0) { + shmem_t[tiisg] = 0.0f; + } + + device const T0 * src_row = (device const T0 *) (src0 + i1*args.nb01 + i2*args.nb02 + i3*args.nb03); + device T * dst_row = (device T *) (dst + i1*args.nb1 + i2*args.nb2 + i3*args.nb3); + + T0 sumf = T0(0.0f); + + for (int64_t i0 = tpitg.x; i0 < args.ne00; i0 += ntg.x) { + sumf += src_row[i0]; + } + + sumf = simd_sum(sumf); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + shmem_t[sgitg] = sumf; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + sumf = shmem_t[tiisg]; + sumf = simd_sum(sumf); + + if (tpitg.x == 0) { + if (FC_OP == OP_SUM_ROWS_NUM_MEAN) { + if (is_same::value) { + dst_row[0] = sum(sumf) / (4*args.ne00); + } else { + dst_row[0] = sum(sumf) / args.ne00; + } + } else { + dst_row[0] = sum(sumf); + } + } + +#undef FC_OP +} + +typedef decltype(kernel_sum_rows_impl) kernel_sum_rows_t; + +template [[host_name("kernel_sum_rows_f32_f32")]] kernel kernel_sum_rows_t kernel_sum_rows_impl; +template [[host_name("kernel_sum_rows_f32_f32_4")]] kernel kernel_sum_rows_t kernel_sum_rows_impl; + +template +kernel void kernel_cumsum_blk( + constant ggml_metal_kargs_cumsum_blk & args, + device const char * src0, + device char * tmp, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int ib = tgpig[0]/args.ne01; + + const int i00 = ib*ntg.x; + const int i01 = tgpig[0]%args.ne01; + const int i02 = tgpig[1]; + const int i03 = tgpig[2]; + + device const float * src0_row = (device const float *) (src0 + + args.nb01*i01 + + args.nb02*i02 + + args.nb03*i03); + + threadgroup float * shmem_f32 = (threadgroup float *) shmem; + + float v = 0.0f; + + if (i00 + tpitg.x < args.ne00) { + v = src0_row[i00 + tpitg.x]; + } + + float s = simd_prefix_inclusive_sum(v); + + if (tiisg == N_SIMDWIDTH - 1) { + shmem_f32[sgitg] = s; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (sgitg == 0) { + shmem_f32[tiisg] = simd_prefix_exclusive_sum(shmem_f32[tiisg]); + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + s += shmem_f32[sgitg]; + + device float * dst_row = (device float *) dst + + args.ne00*i01 + + args.ne00*args.ne01*i02 + + args.ne00*args.ne01*args.ne02*i03; + + if (i00 + tpitg.x < args.ne00) { + dst_row[i00 + tpitg.x] = s; + } + + if (args.outb && tpitg.x == ntg.x - 1) { + device float * tmp_row = (device float *) tmp + + args.net0*i01 + + args.net0*args.net1*i02 + + args.net0*args.net1*args.net2*i03; + + tmp_row[ib] = s; + } +} + +typedef decltype(kernel_cumsum_blk) kernel_cumsum_blk_t; + +template [[host_name("kernel_cumsum_blk_f32")]] kernel kernel_cumsum_blk_t kernel_cumsum_blk; + +template +kernel void kernel_cumsum_add( + constant ggml_metal_kargs_cumsum_add & args, + device const char * tmp, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int ib = tgpig[0]/args.ne01; + + if (ib == 0) { + return; + } + + const int i00 = ib*ntg.x; + const int i01 = tgpig[0]%args.ne01; + const int i02 = tgpig[1]; + const int i03 = tgpig[2]; + + device const float * tmp_row = (device const float *) (tmp + + args.nbt1*i01 + + args.nbt2*i02 + + args.nbt3*i03); + + device float * dst_row = (device float *) dst + + args.ne00*i01 + + args.ne00*args.ne01*i02 + + args.ne00*args.ne01*args.ne02*i03; + + if (i00 + tpitg.x < args.ne00) { + dst_row[i00 + tpitg.x] += tmp_row[ib - 1]; + } +} + +typedef decltype(kernel_cumsum_add) kernel_cumsum_add_t; + +template [[host_name("kernel_cumsum_add_f32")]] kernel kernel_cumsum_add_t kernel_cumsum_add; + + +template +bool _ggml_vec_tri_cmp(const int i, const int r); + +template<> +bool _ggml_vec_tri_cmp(const int i, const int r) { + return i < r; +} + +template<> +bool _ggml_vec_tri_cmp(const int i, const int r) { + return i <= r; +} + +template<> +bool _ggml_vec_tri_cmp(const int i, const int r) { + return i > r; +} + +template<> +bool _ggml_vec_tri_cmp(const int i, const int r) { + return i >= r; +} + +template +kernel void kernel_tri( + constant ggml_metal_kargs_tri & args, + device const char * src0, + device const char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int i3 = tgpig.z; + const int i2 = tgpig.y; + const int i1 = tgpig.x; + + if (i3 >= args.ne03 || i2 >= args.ne02 || i1 >= args.ne01) { + return; + } + + device const T * src_row = (device const T *) ((device const char *) src0 + i1*args.nb01 + i2*args.nb02 + i3*args.nb03); + device T * dst_row = (device T *) ((device char *) dst + i1*args.nb1 + i2*args.nb2 + i3*args.nb3); + + // Each thread is a single element of the row if ne00 < max threads per + // threadgroup, so this will loop once for each index that this thread is + // responsible for + for (int64_t i0 = tpitg.x; i0 < args.ne00; i0 += ntg.x) { + // Use the comparison as a mask for branchless + dst_row[i0] = static_cast(_ggml_vec_tri_cmp(i0, i1)) * src_row[i0]; + } +} + +typedef decltype(kernel_tri) kernel_tri_t; + +template [[host_name("kernel_tri_f32_0")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_f32_1")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_f32_2")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_f32_3")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_f16_0")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_f16_1")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_f16_2")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_f16_3")]] kernel kernel_tri_t kernel_tri; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_tri_bf16_0")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_bf16_1")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_bf16_2")]] kernel kernel_tri_t kernel_tri; +template [[host_name("kernel_tri_bf16_3")]] kernel kernel_tri_t kernel_tri; +#endif + +template +kernel void kernel_soft_max( + constant ggml_metal_kargs_soft_max & args, + device const char * src0, + device const char * src1, + device const char * src2, + device char * dst, + threadgroup float * buf [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint sgitg[[simdgroup_index_in_threadgroup]], + uint tiisg[[thread_index_in_simdgroup]], + uint3 tptg[[threads_per_threadgroup]]) { + const int32_t i03 = tgpig.z; + const int32_t i02 = tgpig.y; + const int32_t i01 = tgpig.x; + + const int32_t i13 = i03%args.ne13; + const int32_t i12 = i02%args.ne12; + const int32_t i11 = i01; + + device const float * psrc0 = (device const float *) (src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03); + device const T * pmask = src1 != src0 ? (device const T * ) (src1 + i11*args.nb11 + i12*args.nb12 + i13*args.nb13) : nullptr; + device const float * psrc2 = src2 != src0 ? (device const float *) (src2) : nullptr; + device float * pdst = (device float *) (dst + i01*args.nb1 + i02*args.nb2 + i03*args.nb3); + + float slope = 1.0f; + + // ALiBi + if (args.max_bias > 0.0f) { + const int32_t h = i02; + + const float base = h < args.n_head_log2 ? args.m0 : args.m1; + const int exp = h < args.n_head_log2 ? h + 1 : 2*(h - args.n_head_log2) + 1; + + slope = pow(base, exp); + } + + // parallel max + float lmax = psrc2 ? psrc2[i02] : -INFINITY; + + for (int i00 = tpitg.x; i00 < args.ne00; i00 += tptg.x) { + lmax = MAX(lmax, psrc0[i00]*args.scale + (pmask ? slope*pmask[i00] : 0.0f)); + } + + // find the max value in the block + float max_val = simd_max(lmax); + if (tptg.x > N_SIMDWIDTH) { + if (sgitg == 0) { + buf[tiisg] = -INFINITY; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + buf[sgitg] = max_val; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + max_val = buf[tiisg]; + max_val = simd_max(max_val); + } + + // parallel sum + float lsum = 0.0f; + for (int i00 = tpitg.x; i00 < args.ne00; i00 += tptg.x) { + const float exp_psrc0 = exp((psrc0[i00]*args.scale + (pmask ? slope*pmask[i00] : 0.0f)) - max_val); + lsum += exp_psrc0; + pdst[i00] = exp_psrc0; + } + + // This barrier fixes a failing test + // ref: https://github.com/ggml-org/ggml/pull/621#discussion_r1425156335 + threadgroup_barrier(mem_flags::mem_none); + + float sum = simd_sum(lsum); + + if (tptg.x > N_SIMDWIDTH) { + if (sgitg == 0) { + buf[tiisg] = 0.0f; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + buf[sgitg] = sum; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + sum = buf[tiisg]; + sum = simd_sum(sum); + } + + if (psrc2) { + sum += exp(psrc2[i02] - max_val); + } + + const float inv_sum = 1.0f/sum; + + for (int i00 = tpitg.x; i00 < args.ne00; i00 += tptg.x) { + pdst[i00] *= inv_sum; + } +} + +template +kernel void kernel_soft_max_4( + constant ggml_metal_kargs_soft_max & args, + device const char * src0, + device const char * src1, + device const char * src2, + device char * dst, + threadgroup float * buf [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint sgitg[[simdgroup_index_in_threadgroup]], + uint tiisg[[thread_index_in_simdgroup]], + uint3 tptg[[threads_per_threadgroup]]) { + const int32_t i03 = tgpig.z; + const int32_t i02 = tgpig.y; + const int32_t i01 = tgpig.x; + + const int32_t i13 = i03%args.ne13; + const int32_t i12 = i02%args.ne12; + const int32_t i11 = i01; + + device const float4 * psrc4 = (device const float4 *) (src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03); + device const T * pmask = src1 != src0 ? (device const T * ) (src1 + i11*args.nb11 + i12*args.nb12 + i13*args.nb13) : nullptr; + device const float * psrc2 = src2 != src0 ? (device const float * ) (src2) : nullptr; + device float4 * pdst4 = (device float4 *) (dst + i01*args.nb1 + i02*args.nb2 + i03*args.nb3); + + float slope = 1.0f; + + if (args.max_bias > 0.0f) { + const int32_t h = i02; + + const float base = h < args.n_head_log2 ? args.m0 : args.m1; + const int exp = h < args.n_head_log2 ? h + 1 : 2*(h - args.n_head_log2) + 1; + + slope = pow(base, exp); + } + + // parallel max + float4 lmax4 = psrc2 ? psrc2[i02] : -INFINITY; + + for (int i00 = tpitg.x; i00 < args.ne00/4; i00 += tptg.x) { + lmax4 = fmax(lmax4, psrc4[i00]*args.scale + (float4)((pmask ? slope*pmask[i00] : 0.0f))); + } + + const float lmax = MAX(MAX(lmax4[0], lmax4[1]), MAX(lmax4[2], lmax4[3])); + + float max_val = simd_max(lmax); + if (tptg.x > N_SIMDWIDTH) { + if (sgitg == 0) { + buf[tiisg] = -INFINITY; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + buf[sgitg] = max_val; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + max_val = buf[tiisg]; + max_val = simd_max(max_val); + } + + // parallel sum + float4 lsum4 = 0.0f; + for (int i00 = tpitg.x; i00 < args.ne00/4; i00 += tptg.x) { + const float4 exp_psrc4 = exp((psrc4[i00]*args.scale + (float4)((pmask ? slope*pmask[i00] : 0.0f))) - max_val); + lsum4 += exp_psrc4; + pdst4[i00] = exp_psrc4; + } + + const float lsum = lsum4[0] + lsum4[1] + lsum4[2] + lsum4[3]; + + // This barrier fixes a failing test + // ref: https://github.com/ggml-org/ggml/pull/621#discussion_r1425156335 + threadgroup_barrier(mem_flags::mem_none); + + float sum = simd_sum(lsum); + + if (tptg.x > N_SIMDWIDTH) { + if (sgitg == 0) { + buf[tiisg] = 0.0f; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + buf[sgitg] = sum; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + sum = buf[tiisg]; + sum = simd_sum(sum); + } + + if (psrc2) { + sum += exp(psrc2[i02] - max_val); + } + + const float inv_sum = 1.0f/sum; + + for (int i00 = tpitg.x; i00 < args.ne00/4; i00 += tptg.x) { + pdst4[i00] *= inv_sum; + } +} + +typedef decltype(kernel_soft_max) kernel_soft_max_t; +typedef decltype(kernel_soft_max_4) kernel_soft_max_4_t; + +template [[host_name("kernel_soft_max_f16")]] kernel kernel_soft_max_t kernel_soft_max; +template [[host_name("kernel_soft_max_f32")]] kernel kernel_soft_max_t kernel_soft_max; +template [[host_name("kernel_soft_max_f16_4")]] kernel kernel_soft_max_4_t kernel_soft_max_4; +template [[host_name("kernel_soft_max_f32_4")]] kernel kernel_soft_max_4_t kernel_soft_max_4; + +// ref: ggml.c:ggml_compute_forward_ssm_conv_f32 +kernel void kernel_ssm_conv_f32_f32( + constant ggml_metal_kargs_ssm_conv & args, + device const void * src0, + device const void * src1, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + const int64_t ir = tgpig.x; + const int64_t i2 = tgpig.y; + const int64_t i3 = tgpig.z; + + const int64_t nc = args.ne10; + //const int64_t ncs = args.ne00; + //const int64_t nr = args.ne01; + //const int64_t n_t = args.ne1; + //const int64_t n_s = args.ne2; + + device const float * s = (device const float *) ((device const char *) src0 + ir*args.nb01 + i2*args.nb00 + i3*args.nb02); + device const float * c = (device const float *) ((device const char *) src1 + ir*args.nb11); + device float * x = (device float *) ((device char *) dst + ir*args.nb0 + i2*args.nb1 + i3*args.nb2); + + float sumf = 0.0f; + + for (int64_t i0 = 0; i0 < nc; ++i0) { + sumf += s[i0] * c[i0]; + } + + x[0] = sumf; +} + +kernel void kernel_ssm_conv_f32_f32_4( + constant ggml_metal_kargs_ssm_conv & args, + device const void * src0, + device const void * src1, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + const int64_t ir = tgpig.x; + const int64_t i2 = tgpig.y; + const int64_t i3 = tgpig.z; + + const int64_t nc = args.ne10; + //const int64_t ncs = args.ne00; + //const int64_t nr = args.ne01; + //const int64_t n_t = args.ne1; + //const int64_t n_s = args.ne2; + + device const float4 * s = (device const float4 *) ((device const char *) src0 + ir*args.nb01 + i2*args.nb00 + i3*args.nb02); + device const float4 * c = (device const float4 *) ((device const char *) src1 + ir*args.nb11); + device float * x = (device float *) ((device char *) dst + ir*args.nb0 + i2*args.nb1 + i3*args.nb2); + + float sumf = 0.0f; + + for (int64_t i0 = 0; i0 < nc/4; ++i0) { + sumf += dot(s[i0], c[i0]); + } + + x[0] = sumf; +} + +constant short FC_ssm_conv_bs [[function_constant(FC_SSM_CONV + 0)]]; + +// Batched version: each threadgroup processes multiple tokens for better efficiency +// Thread layout: each thread handles one token, threadgroup covers BATCH_SIZE tokens +kernel void kernel_ssm_conv_f32_f32_batched( + constant ggml_metal_kargs_ssm_conv & args, + device const void * src0, + device const void * src1, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + // tgpig.x = row index (ir) + // tgpig.y = batch of tokens (i2_base / BATCH_SIZE) + // tgpig.z = sequence index (i3) + // tpitg.x = thread within batch (0..BATCH_SIZE-1) + const short BATCH_SIZE = FC_ssm_conv_bs; + + const int64_t ir = tgpig.x; + const int64_t i2_base = tgpig.y * BATCH_SIZE; + const int64_t i3 = tgpig.z; + const int64_t i2_off = tpitg.x; + const int64_t i2 = i2_base + i2_off; + + const int64_t nc = args.ne10; // conv kernel size (typically 4) + const int64_t n_t = args.ne1; // number of tokens + + // Bounds check for partial batches at the end + if (i2 >= n_t) { + return; + } + + // Load conv weights (shared across all tokens for this row) + device const float * c = (device const float *) ((device const char *) src1 + ir*args.nb11); + + // Load source for this specific token + device const float * s = (device const float *) ((device const char *) src0 + ir*args.nb01 + i2*args.nb00 + i3*args.nb02); + + // Output location for this token + device float * x = (device float *) ((device char *) dst + ir*args.nb0 + i2*args.nb1 + i3*args.nb2); + + float sumf = 0.0f; + for (int64_t i0 = 0; i0 < nc; ++i0) { + sumf += s[i0] * c[i0]; + } + + x[0] = sumf; +} + +kernel void kernel_ssm_conv_f32_f32_batched_4( + constant ggml_metal_kargs_ssm_conv & args, + device const void * src0, + device const void * src1, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + // tgpig.x = row index (ir) + // tgpig.y = batch of tokens (i2_base / BATCH_SIZE) + // tgpig.z = sequence index (i3) + // tpitg.x = thread within batch (0..BATCH_SIZE-1) + const short BATCH_SIZE = FC_ssm_conv_bs; + + const int64_t ir = tgpig.x; + const int64_t i2_base = tgpig.y * BATCH_SIZE; + const int64_t i3 = tgpig.z; + const int64_t i2_off = tpitg.x; + const int64_t i2 = i2_base + i2_off; + + const int64_t nc = args.ne10; // conv kernel size (typically 4) + const int64_t n_t = args.ne1; // number of tokens + + // Bounds check for partial batches at the end + if (i2 >= n_t) { + return; + } + + // Load conv weights (shared across all tokens for this row) + device const float4 * c = (device const float4 *) ((device const char *) src1 + ir*args.nb11); + + // Load source for this specific token + device const float4 * s = (device const float4 *) ((device const char *) src0 + ir*args.nb01 + i2*args.nb00 + i3*args.nb02); + + // Output location for this token + device float * x = (device float *) ((device char *) dst + ir*args.nb0 + i2*args.nb1 + i3*args.nb2); + + float sumf = 0.0f; + for (int64_t i0 = 0; i0 < nc/4; ++i0) { + sumf += dot(s[i0], c[i0]); + } + + x[0] = sumf; +} + +// ref: ggml.c:ggml_compute_forward_ssm_scan_f32, Mamba-2 part +// Optimized version: reduces redundant memory loads by having one thread load shared values +kernel void kernel_ssm_scan_f32( + constant ggml_metal_kargs_ssm_scan & args, + device const void * src0, + device const void * src1, + device const void * src2, + device const void * src3, + device const void * src4, + device const void * src5, + device const void * src6, + device float * dst, + threadgroup float * shared [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgptg[[simdgroups_per_threadgroup]], + uint3 tgpg[[threadgroups_per_grid]]) { + constexpr short NW = N_SIMDWIDTH; + + // Shared memory layout: + // [0..sgptg*NW-1]: partial sums for reduction (existing) + // [sgptg*NW..sgptg*NW+sgptg-1]: pre-computed x_dt values for each token in batch + // [sgptg*NW+sgptg..sgptg*NW+2*sgptg-1]: pre-computed dA values for each token in batch + threadgroup float * shared_sums = shared; + threadgroup float * shared_x_dt = shared + sgptg * NW; + threadgroup float * shared_dA = shared + sgptg * NW + sgptg; + + shared_sums[tpitg.x] = 0.0f; + + const int32_t i0 = tpitg.x; + const int32_t i1 = tgpig.x; + const int32_t ir = tgpig.y; // current head + const int32_t i3 = tgpig.z; // current seq + + const int32_t nc = args.d_state; + const int32_t nr = args.d_inner; + const int32_t nh = args.n_head; + const int32_t ng = args.n_group; + const int32_t n_t = args.n_seq_tokens; + + const int32_t s_off = args.s_off; + + device const int32_t * ids = (device const int32_t *) src6; + + device const float * s0_buff = (device const float *) ((device const char *) src0 + ir*args.nb02 + ids[i3]*args.nb03); + device float * s_buff = (device float *) ((device char *) dst + ir*args.nb02 + i3*args.nb03 + s_off); + + const int32_t i = i0 + i1*nc; + const int32_t g = ir / (nh / ng); // repeat_interleave + + float s0 = s0_buff[i]; + float s = 0.0f; + + device const float * A = (device const float *) ((device const char *) src3 + ir*args.nb31); // {ne30, nh} + + const float A0 = A[i0%args.ne30]; + + device const float * x = (device const float *)((device const char *) src1 + i1*args.nb10 + ir*args.nb11 + i3*args.nb13); // {dim, nh, nt, ns} + device const float * dt = (device const float *)((device const char *) src2 + ir*args.nb20 + i3*args.nb22); // {nh, nt, ns} + device const float * B = (device const float *)((device const char *) src4 + g*args.nb41 + i3*args.nb43); // {d_state, ng, nt, ns} + device const float * C = (device const float *)((device const char *) src5 + g*args.nb51 + i3*args.nb53); // {d_state, ng, nt, ns} + + device float * y = dst + (i1 + ir*(nr) + i3*(n_t*nh*nr)); // {dim, nh, nt, ns} + + for (int i2 = 0; i2 < n_t; i2 += sgptg) { + threadgroup_barrier(mem_flags::mem_threadgroup); + + // Pre-compute x_dt and dA for this batch of tokens + // Only first sgptg threads do the loads and expensive math + if (i0 < sgptg && i2 + i0 < n_t) { + // ns12 and ns21 are element strides (nb12/nb10, nb21/nb20) + device const float * x_t = x + i0 * args.ns12; + device const float * dt_t = dt + i0 * args.ns21; + + const float dt0 = dt_t[0]; + const float dtsp = dt0 <= 20.0f ? log(1.0f + exp(dt0)) : dt0; + shared_x_dt[i0] = x_t[0] * dtsp; + shared_dA[i0] = dtsp; // Store dtsp, compute exp(dtsp * A0) per-thread since A0 varies + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + for (int t = 0; t < sgptg && i2 + t < n_t; t++) { + const float x_dt = shared_x_dt[t]; + const float dA = exp(shared_dA[t] * A0); + + s = (s0 * dA) + (B[i0] * x_dt); + + const float sumf = simd_sum(s * C[i0]); + + if (tiisg == 0) { + shared_sums[t*NW + sgitg] = sumf; + } + + // recurse + s0 = s; + + B += args.ns42; + C += args.ns52; + } + + // Advance pointers for next batch + x += sgptg * args.ns12; + dt += sgptg * args.ns21; + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // Each token's full output is the sum over all simdgroups of that token's + // partial sums (shared_sums[t*NW + g] for g in 0..sgptg-1). The previous + // simd_sum(shared_sums[sgitg*NW + tiisg]) read garbage columns whenever + // sgptg < NW (e.g. d_state=64 -> sgptg=2) with few tokens, corrupting the + // SSM state. Compute the token sum redundantly on every thread instead. + float sumf = 0.0f; + if (i2 + sgitg < n_t) { + for (int g = 0; g < sgptg; g++) { + sumf += shared_sums[(i2 + sgitg)*NW + g]; + } + } + + if (tiisg == 0 && i2 + sgitg < n_t) { + y[sgitg*nh*nr] = sumf; + } + + y += sgptg*nh*nr; + } + + s_buff[i] = s; +} + +kernel void kernel_rwkv_wkv6_f32( + device const float * k, + device const float * v, + device const float * r, + device const float * tf, + device const float * td, + device const float * state_in, + device float * dst, + constant uint & B, + constant uint & T, + constant uint & C, + constant uint & H, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const uint head_size = 64; // TODO: support head_size = 128 + const uint batch_id = tgpig.x / H; + const uint head_id = tgpig.x % H; + const uint tid = tpitg.x; + + if (batch_id >= B || head_id >= H) { + return; + } + + const uint state_size = C * head_size; + const uint n_seq_tokens = T / B; + + threadgroup float _k[head_size]; + threadgroup float _r[head_size]; + threadgroup float _tf[head_size]; + threadgroup float _td[head_size]; + + float state[head_size]; + + for (uint i = 0; i < head_size; i++) { + state[i] = state_in[batch_id * state_size + head_id * head_size * head_size + + i * head_size + tid]; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + _tf[tid] = tf[head_id * head_size + tid]; + threadgroup_barrier(mem_flags::mem_threadgroup); + + const uint start_t = batch_id * n_seq_tokens * C + head_id * head_size + tid; + const uint end_t = (batch_id + 1) * n_seq_tokens * C + head_id * head_size + tid; + + for (uint t = start_t; t < end_t; t += C) { + threadgroup_barrier(mem_flags::mem_threadgroup); + _k[tid] = k[t]; + _r[tid] = r[t]; + _td[tid] = td[t]; + threadgroup_barrier(mem_flags::mem_threadgroup); + + const float v_val = v[t]; + float y = 0.0; + + for (uint j = 0; j < head_size; j += 4) { + float4 k_vec = float4(_k[j], _k[j+1], _k[j+2], _k[j+3]); + float4 r_vec = float4(_r[j], _r[j+1], _r[j+2], _r[j+3]); + float4 tf_vec = float4(_tf[j], _tf[j+1], _tf[j+2], _tf[j+3]); + float4 td_vec = float4(_td[j], _td[j+1], _td[j+2], _td[j+3]); + float4 s_vec = float4(state[j], state[j+1], state[j+2], state[j+3]); + + float4 kv = k_vec * v_val; + + float4 temp = tf_vec * kv + s_vec; + y += dot(r_vec, temp); + + s_vec = s_vec * td_vec + kv; + state[j] = s_vec[0]; + state[j+1] = s_vec[1]; + state[j+2] = s_vec[2]; + state[j+3] = s_vec[3]; + } + + dst[t] = y; + } + + for (uint i = 0; i < head_size; i++) { + dst[T * C + batch_id * state_size + head_id * head_size * head_size + + i * head_size + tid] = state[i]; + } +} + +kernel void kernel_rwkv_wkv7_f32( + device const float * r, + device const float * w, + device const float * k, + device const float * v, + device const float * a, + device const float * b, + device const float * state_in, + device float * dst, + constant uint & B, + constant uint & T, + constant uint & C, + constant uint & H, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const uint head_size = 64; // TODO: support head_size = 128 + const uint batch_id = tgpig.x / H; + const uint head_id = tgpig.x % H; + const uint tid = tpitg.x; + + if (batch_id >= B || head_id >= H) { + return; + } + + const uint state_size = C * head_size; + const uint n_seq_tokens = T / B; + + threadgroup float _r[head_size]; + threadgroup float _w[head_size]; + threadgroup float _k[head_size]; + threadgroup float _a[head_size]; + threadgroup float _b[head_size]; + + float state[head_size]; + + for (uint i = 0; i < head_size; i++) { + state[i] = state_in[batch_id * state_size + head_id * head_size * head_size + + tid * head_size + i]; + } + + const uint start_t = batch_id * n_seq_tokens * C + head_id * head_size + tid; + const uint end_t = (batch_id + 1) * n_seq_tokens * C + head_id * head_size + tid; + + for (uint t = start_t; t < end_t; t += C) { + threadgroup_barrier(mem_flags::mem_threadgroup); + _r[tid] = r[t]; + _w[tid] = w[t]; + _k[tid] = k[t]; + _a[tid] = a[t]; + _b[tid] = b[t]; + threadgroup_barrier(mem_flags::mem_threadgroup); + + const float v_val = v[t]; + float y = 0.0, sa = 0.0; + + float4 sa_vec(0.0); + + for (uint j = 0; j < head_size; j += 4) { + float4 a_vec = float4(_a[j], _a[j+1], _a[j+2], _a[j+3]); + float4 s_vec = float4(state[j], state[j+1], state[j+2], state[j+3]); + sa_vec += a_vec * s_vec; + } + sa = sa_vec[0] + sa_vec[1] + sa_vec[2] + sa_vec[3]; + + for (uint j = 0; j < head_size; j += 4) { + float4 r_vec = float4(_r[j], _r[j+1], _r[j+2], _r[j+3]); + float4 w_vec = float4(_w[j], _w[j+1], _w[j+2], _w[j+3]); + float4 k_vec = float4(_k[j], _k[j+1], _k[j+2], _k[j+3]); + float4 b_vec = float4(_b[j], _b[j+1], _b[j+2], _b[j+3]); + float4 s_vec = float4(state[j], state[j+1], state[j+2], state[j+3]); + + float4 kv = k_vec * v_val; + + s_vec = s_vec * w_vec + kv + sa * b_vec; + y += dot(s_vec, r_vec); + + state[j] = s_vec[0]; + state[j+1] = s_vec[1]; + state[j+2] = s_vec[2]; + state[j+3] = s_vec[3]; + } + + dst[t] = y; + } + + for (uint i = 0; i < head_size; i++) { + dst[T * C + batch_id * state_size + head_id * head_size * head_size + + tid * head_size + i] = state[i]; + } +} + +constant short FC_gated_delta_net_ne20 [[function_constant(FC_GATED_DELTA_NET + 0)]]; +constant short FC_gated_delta_net_ne30 [[function_constant(FC_GATED_DELTA_NET + 1)]]; +constant short FC_gated_delta_net_K [[function_constant(FC_GATED_DELTA_NET + 2)]]; + +#if 1 +template +kernel void kernel_gated_delta_net_impl( + constant ggml_metal_kargs_gated_delta_net & args, + device const char * q, + device const char * k, + device const char * v, + device const char * g, + device const char * b, + device const char * s, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { +#define S_v FC_gated_delta_net_ne20 +#define G FC_gated_delta_net_ne30 +#define K FC_gated_delta_net_K + + const uint tx = tpitg.x; + const uint ty = tpitg.y; + + const uint i23 = tgpig.z; // B (n_seqs) + const uint i21 = tgpig.y; // H (head) + const uint i20 = tgpig.x*NSG + ty; // row within S_v + + const uint i01 = i21 % args.ne01; + const uint i11 = i21 % args.ne11; + + const float scale = 1.0f / sqrt((float)S_v); + + // input state layout (D, K, n_seqs): per-seq stride is K*H*D; we read slot 0. + // state is stored transposed: M[i20][is] = S[is][i20], so row i20 is contiguous + const uint state_in_base = (i23*K*args.ne21 + i21)*S_v*S_v + i20*S_v; + device const float * s_ptr = (device const float *) (s) + state_in_base; + + float ls[NSG]; + + FOR_UNROLL (short j = 0; j < NSG; j++) { + const short is = tx*NSG + j; + ls[j] = s_ptr[is]; + } + + device float * dst_attn = (device float *) (dst) + (i23*args.ne22*args.ne21 + i21)*S_v + i20; + + device const float * q_ptr = (device const float *) (q + i23*args.nb03 + i01*args.nb01); + device const float * k_ptr = (device const float *) (k + i23*args.nb13 + i11*args.nb11); + device const float * v_ptr = (device const float *) (v + i23*args.nb23 + i21*args.nb21); + + device const float * b_ptr = (device const float *) (b) + (i23*args.ne22*args.ne21 + i21); + device const float * g_ptr = (device const float *) (g) + (i23*args.ne22*args.ne21 + i21)*G; + + // snapshot slot mapping: target_slot = t - shift. When n_tokens < K, only the last + // n_tokens slots are written; earlier slots are left untouched (caller-owned). + const int shift = (int)args.ne22 - (int)K; + + // output state base offset: after attention scores + const uint attn_size = args.ne22 * args.ne21 * S_v * args.ne23; + // output state per-slot size: S_v * S_v * H * n_seqs + const uint state_size_per_snap = S_v * S_v * args.ne21 * args.ne23; + // per-(seq,head) offset within a slot + const uint state_out_base = (i23*args.ne21 + i21)*S_v*S_v + i20*S_v; + + for (short t = 0; t < args.ne22; t++) { + float s_k = 0.0f; + + if (G == 1) { + const float g_exp = exp(g_ptr[0]); + + FOR_UNROLL (short j = 0; j < NSG; j++) { + const short is = tx*NSG + j; + ls[j] *= g_exp; + + s_k += ls[j]*k_ptr[is]; + } + } else { + // KDA + FOR_UNROLL (short j = 0; j < NSG; j++) { + const short is = tx*NSG + j; + ls[j] *= exp(g_ptr[is]); + + s_k += ls[j]*k_ptr[is]; + } + } + + s_k = simd_sum(s_k); + + const float d = (v_ptr[i20] - s_k)*b_ptr[0]; + + float y = 0.0f; + + FOR_UNROLL (short j = 0; j < NSG; j++) { + const short is = tx*NSG + j; + ls[j] += k_ptr[is]*d; + + y += ls[j]*q_ptr[is]; + } + + y = simd_sum(y); + + if (tx == 0) { + dst_attn[t*args.ne21*S_v] = y*scale; + } + + q_ptr += args.ns02; + k_ptr += args.ns12; + v_ptr += args.ns22; + + b_ptr += args.ne21; + g_ptr += args.ne21*G; + + if (K > 1u) { + const int target_slot = (int)t - shift; + if (target_slot >= 0 && target_slot < (int)K) { + device float * dst_state = (device float *) (dst) + attn_size + (uint)target_slot * state_size_per_snap + state_out_base; + FOR_UNROLL (short j = 0; j < NSG; j++) { + const short is = tx*NSG + j; + dst_state[is] = ls[j]; + } + } + } + } + + if (K == 1u) { + device float * dst_state = (device float *) (dst) + attn_size + state_out_base; + FOR_UNROLL (short j = 0; j < NSG; j++) { + const short is = tx*NSG + j; + dst_state[is] = ls[j]; + } + } + +#undef S_v +#undef G +#undef K +} + +typedef decltype(kernel_gated_delta_net_impl<4>) kernel_gated_delta_net_t; + +template [[host_name("kernel_gated_delta_net_f32_1")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl<1>; +template [[host_name("kernel_gated_delta_net_f32_2")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl<2>; +template [[host_name("kernel_gated_delta_net_f32_4")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl<4>; + +#else +// a simplified version of the above +// no performance improvement, so keep the above version for now + +template +kernel void kernel_gated_delta_net_impl( + constant ggml_metal_kargs_gated_delta_net & args, + device const char * q, + device const char * k, + device const char * v, + device const char * g, + device const char * b, + device const char * s, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { +#define S_v FC_gated_delta_net_ne20 +#define G FC_gated_delta_net_ne30 + + const uint tx = tpitg.x; + const uint ty = tpitg.y; + + const uint i23 = tgpig.z; // B + const uint i21 = tgpig.y; // H + const uint i20 = tgpig.x*NSG + ty; + + const uint i01 = i21 % args.ne01; + const uint i11 = i21 % args.ne11; + + const float scale = 1.0f / sqrt((float)S_v); + + device const float * s_ptr = (device const float *) (s) + (i23*args.ne21 + i21)*S_v*S_v + i20; + + float lsf[NSG]; + + FOR_UNROLL (short j = 0; j < NSG; j++) { + const short is = tx*NSG + j; + lsf[j] = s_ptr[is*S_v]; + } + + thread T * ls = (thread T *) (lsf); + + device float * dst_attn = (device float *) (dst) + (i23*args.ne22*args.ne21 + i21)*S_v + i20; + + device const float * q_ptr = (device const float *) (q + i23*args.nb03 + i01*args.nb01); + device const float * k_ptr = (device const float *) (k + i23*args.nb13 + i11*args.nb11); + device const float * v_ptr = (device const float *) (v + i23*args.nb23 + i21*args.nb21); + + device const float * b_ptr = (device const float *) (b) + (i23*args.ne22*args.ne21 + i21); + device const float * g_ptr = (device const float *) (g) + (i23*args.ne22*args.ne21 + i21)*G; + + for (short t = 0; t < args.ne22; t++) { + device const T * qt_ptr = (device const T *) (q_ptr); + device const T * kt_ptr = (device const T *) (k_ptr); + device const T * gt_ptr = (device const T *) (g_ptr); + + if (G == 1) { + *ls *= exp(g_ptr[0]); + } else { + // KDA + *ls *= exp(gt_ptr[tx]); + } + + const float s_k = simd_sum(dot(*ls, kt_ptr[tx])); + + const float d = (v_ptr[i20] - s_k)*b_ptr[0]; + + *ls += kt_ptr[tx]*d; + + const float y = simd_sum(dot(*ls, qt_ptr[tx])); + + if (tx == 0) { + *dst_attn = y*scale; + } + + q_ptr += args.ns02; + k_ptr += args.ns12; + v_ptr += args.ns22; + + b_ptr += args.ne21; + g_ptr += args.ne21*G; + + dst_attn += args.ne21*S_v; + } + + device float * dst_state = (device float *) (dst) + args.ne23*args.ne22*args.ne21*S_v + (i23*args.ne21 + i21)*S_v*S_v + i20; + device T * dstt_state = (device T *) (dst_state); + + FOR_UNROLL (short j = 0; j < NSG; j++) { + const short is = tx*NSG + j; + dst_state[is*S_v] = lsf[j]; + } + +#undef S_v +#undef G +} + +typedef decltype(kernel_gated_delta_net_impl) kernel_gated_delta_net_t; + +template [[host_name("kernel_gated_delta_net_f32_1")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl; +template [[host_name("kernel_gated_delta_net_f32_2")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl; +template [[host_name("kernel_gated_delta_net_f32_4")]] kernel kernel_gated_delta_net_t kernel_gated_delta_net_impl; +#endif + +constant short FC_solve_tri_nsg [[function_constant(FC_SOLVE_TRI + 0)]]; +constant short FC_solve_tri_n [[function_constant(FC_SOLVE_TRI + 1)]]; +constant short FC_solve_tri_k [[function_constant(FC_SOLVE_TRI + 2)]]; + +kernel void kernel_solve_tri_f32( + constant ggml_metal_kargs_solve_tri & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + ushort3 tgpig[[threadgroup_position_in_grid]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + constexpr short NW = N_SIMDWIDTH; + + const short NSG = FC_solve_tri_nsg; + const short N = FC_solve_tri_n; + const short K = FC_solve_tri_k; + const short NP = PAD2(N, NW); + + const int32_t i03 = tgpig.z; + const int32_t i02 = tgpig.y; + const int32_t i01 = tgpig.x*NSG + sgitg; + + threadgroup float * sh0 = (threadgroup float *) shmem; + + device const float * src0_ptr = (device const float *)(src0 + i02 * args.nb02 + i03 * args.nb03) + sgitg*N; + device const float * src1_ptr = (device const float *)(src1 + i02 * args.nb12 + i03 * args.nb13) + i01; + device float * dst_ptr = (device float *)(dst + i02 * args.nb2 + i03 * args.nb3) + i01; + + for (short rr = 0; rr < N; rr += NSG) { + threadgroup_barrier(mem_flags::mem_threadgroup); + + { + threadgroup float * sh0_cur = sh0 + sgitg*NP; + + for (short t = 0; t*NW < N; ++t) { + const short idx = t*NW + tiisg; + sh0_cur[idx] = src0_ptr[idx]; + } + + src0_ptr += NSG*N; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (i01 >= args.ne10) { + continue; + } + + for (short ir = 0; ir < NSG && rr + ir < N; ++ir) { + const short r = rr + ir; + + threadgroup float * sh0_cur = sh0 + ir*NP; + + float sum = 0.0f; + + for (short t = 0; t*NW < r; ++t) { + const short idx = t*NW + tiisg; + sum += sh0_cur[idx] * dst_ptr[idx*K] * (idx < r); + } + + sum = simd_sum(sum); + + if (tiisg == 0) { + const float diag = sh0_cur[r]; + + dst_ptr[r*K] = (src1_ptr[r*K] - sum) / diag; + } + } + } +} + +kernel void kernel_argmax_f32( + constant ggml_metal_kargs_argmax & args, + device const char * src0, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint tgpig[[threadgroup_position_in_grid]], + uint tpitg[[thread_position_in_threadgroup]], + uint sgitg[[simdgroup_index_in_threadgroup]], + uint tiisg[[thread_index_in_simdgroup]], + uint ntg[[threads_per_threadgroup]]) { + device const float * x_row = (device const float *) ((device const char *) src0 + tgpig * args.nb01); + + float lmax = -INFINITY; + int32_t larg = -1; + + for (int i00 = tpitg; i00 < args.ne00; i00 += ntg) { + if (x_row[i00] > lmax) { + lmax = x_row[i00]; + larg = i00; + } + } + + // find the argmax value in the block + float max_val = simd_max(lmax); + int32_t arg_val = simd_max(select(-1, larg, lmax == max_val)); + + device int32_t * dst_i32 = (device int32_t *) dst; + + threadgroup float * shared_maxval = (threadgroup float *) shmem; + threadgroup int32_t * shared_argmax = (threadgroup int32_t *) shmem + N_SIMDWIDTH; + + if (ntg > N_SIMDWIDTH) { + if (sgitg == 0) { + shared_maxval[tiisg] = -INFINITY; + shared_argmax[tiisg] = -1; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + shared_maxval[sgitg] = max_val; + shared_argmax[sgitg] = arg_val; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + max_val = shared_maxval[tiisg]; + arg_val = shared_argmax[tiisg]; + + float max_val_reduced = simd_max(max_val); + int32_t arg_val_reduced = simd_max(select(-1, arg_val, max_val == max_val_reduced)); + + dst_i32[tgpig] = arg_val_reduced; + + return; + } + + dst_i32[tgpig] = arg_val; +} + +// F == 1 : norm (no fuse) +// F == 2 : norm + mul +// F == 3 : norm + mul + add +template +kernel void kernel_norm_fuse_impl( + constant ggml_metal_kargs_norm & args, + device const char * src0, + device const char * src1_0, + device const char * src1_1, + device char * dst, + threadgroup float * shmem_f32 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + if (sgitg == 0) { + shmem_f32[tiisg] = 0.0f; + } + + const int i01 = tgpig.x; + const int i02 = tgpig.y; + const int i03 = tgpig.z; + + device const T * x = (device const T *) (src0 + i03*args.nbf3[0] + i02*args.nbf2[0] + i01*args.nbf1[0]); + + device const T * f0 = (device const T *) (src1_0 + (i03%args.nef3[1])*args.nbf3[1] + (i02%args.nef2[1])*args.nbf2[1] + (i01%args.nef1[1])*args.nbf1[1]); + device const T * f1 = (device const T *) (src1_1 + (i03%args.nef3[2])*args.nbf3[2] + (i02%args.nef2[2])*args.nbf2[2] + (i01%args.nef1[2])*args.nbf1[2]); + + T sumft(0.0f); + + float sumf = 0.0f; + + for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { + sumft += x[i00]; + } + sumf = dot(sumft, T(1.0f)); + sumf = simd_sum(sumf); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + shmem_f32[sgitg] = sumf; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + sumf = shmem_f32[tiisg]; + sumf = simd_sum(sumf); + + const float mean = sumf/args.ne00; + + device T * y = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1); + + sumf = 0.0f; + for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { + y[i00] = x[i00] - mean; + sumf += dot(y[i00], y[i00]); + } + sumf = simd_sum(sumf); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + shmem_f32[sgitg] = sumf; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + sumf = shmem_f32[tiisg]; + sumf = simd_sum(sumf); + + const float variance = sumf/args.ne00; + + const float scale = 1.0f/sqrt(variance + args.eps); + for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { + if (F == 1) { + y[i00] = (y[i00]*scale); + } + if (F == 2) { + y[i00] = (y[i00]*scale)*f0[i00]; + } + if (F == 3) { + y[i00] = (y[i00]*scale)*f0[i00] + f1[i00]; + } + } +} + +typedef decltype(kernel_norm_fuse_impl) kernel_norm_fuse_t; + +template [[host_name("kernel_norm_f32")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; +template [[host_name("kernel_norm_mul_f32")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; +template [[host_name("kernel_norm_mul_add_f32")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; + +template [[host_name("kernel_norm_f32_4")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; +template [[host_name("kernel_norm_mul_f32_4")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; +template [[host_name("kernel_norm_mul_add_f32_4")]] kernel kernel_norm_fuse_t kernel_norm_fuse_impl; + +// F == 1 : rms_norm (no fuse) +// F == 2 : rms_norm + mul +// F == 3 : rms_norm + mul + add +template +kernel void kernel_rms_norm_fuse_impl( + constant ggml_metal_kargs_norm & args, + device const char * src0, + device const char * src1_0, + device const char * src1_1, + device char * dst, + threadgroup float * shmem_f32 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + if (sgitg == 0) { + shmem_f32[tiisg] = 0.0f; + } + + const int i01 = tgpig.x; + const int i02 = tgpig.y; + const int i03 = tgpig.z; + + device const T * x = (device const T *) (src0 + i03*args.nbf3[0] + i02*args.nbf2[0] + i01*args.nbf1[0]); + + device const T * f0 = (device const T *) (src1_0 + (i03%args.nef3[1])*args.nbf3[1] + (i02%args.nef2[1])*args.nbf2[1] + (i01%args.nef1[1])*args.nbf1[1]); + device const T * f1 = (device const T *) (src1_1 + (i03%args.nef3[2])*args.nbf3[2] + (i02%args.nef2[2])*args.nbf2[2] + (i01%args.nef1[2])*args.nbf1[2]); + + float sumf = 0.0f; + + // parallel sum + for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { + sumf += dot(x[i00], x[i00]); + } + sumf = simd_sum(sumf); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + shmem_f32[sgitg] = sumf; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + sumf = shmem_f32[tiisg]; + sumf = simd_sum(sumf); + + const float mean = sumf/args.ne00; + const float scale = 1.0f/sqrt(mean + args.eps); + + device T * y = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1); + for (int i00 = tpitg.x; i00 < args.ne00_t; i00 += ntg.x) { + if (F == 1) { + y[i00] = (x[i00]*scale); + } + if (F == 2) { + y[i00] = (x[i00]*scale)*f0[i00]; + } + if (F == 3) { + y[i00] = (x[i00]*scale)*f0[i00] + f1[i00]; + } + } +} + +typedef decltype(kernel_rms_norm_fuse_impl) kernel_rms_norm_fuse_t; + +template [[host_name("kernel_rms_norm_f32")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; +template [[host_name("kernel_rms_norm_mul_f32")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; +template [[host_name("kernel_rms_norm_mul_add_f32")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; + +template [[host_name("kernel_rms_norm_f32_4")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; +template [[host_name("kernel_rms_norm_mul_f32_4")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; +template [[host_name("kernel_rms_norm_mul_add_f32_4")]] kernel kernel_rms_norm_fuse_t kernel_rms_norm_fuse_impl; + +template +kernel void kernel_l2_norm_impl( + constant ggml_metal_kargs_l2_norm & args, + device const char * src0, + device char * dst, + threadgroup float * shmem_f32 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int i03 = tgpig.z; + const int i02 = tgpig.y; + const int i01 = tgpig.x; + + if (sgitg == 0) { + shmem_f32[tiisg] = 0.0f; + } + + device const T0 * x = (device const T0 *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); + device T * y = (device T *) (dst + i03*args.nb3 + i02*args.nb2 + i01*args.nb1); + + float sumf = 0.0f; + + // parallel sum + for (int i00 = tpitg.x; i00 < args.ne00; i00 += ntg.x) { + sumf += dot(x[i00], x[i00]); + } + sumf = simd_sum(sumf); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + shmem_f32[sgitg] = sumf; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + sumf = shmem_f32[tiisg]; + sumf = simd_sum(sumf); + + const float scale = 1.0f/max(sqrt(sumf), args.eps); + + for (int i00 = tpitg.x; i00 < args.ne00; i00 += ntg.x) { + y[i00] = x[i00] * scale; + } +} + +typedef decltype(kernel_l2_norm_impl) kernel_l2_norm_t; + +template [[host_name("kernel_l2_norm_f32_f32")]] kernel kernel_l2_norm_t kernel_l2_norm_impl; +template [[host_name("kernel_l2_norm_f32_f32_4")]] kernel kernel_l2_norm_t kernel_l2_norm_impl; + +kernel void kernel_group_norm_f32( + constant ggml_metal_kargs_group_norm & args, + device const float * src0, + device float * dst, + threadgroup float * buf [[threadgroup(0)]], + uint tgpig[[threadgroup_position_in_grid]], + uint tpitg[[thread_position_in_threadgroup]], + uint sgitg[[simdgroup_index_in_threadgroup]], + uint tiisg[[thread_index_in_simdgroup]], + uint ntg[[threads_per_threadgroup]]) { + const int64_t ne = args.ne00*args.ne01*args.ne02; + const int64_t gs = args.ne00*args.ne01*((args.ne02 + args.ngrp - 1) / args.ngrp); + + int start = tgpig * gs; + int end = start + gs; + + start += tpitg; + + if (end >= ne) { + end = ne; + } + + float tmp = 0.0f; // partial sum for thread in warp + + for (int j = start; j < end; j += ntg) { + tmp += src0[j]; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + tmp = simd_sum(tmp); + if (ntg > N_SIMDWIDTH) { + if (sgitg == 0) { + buf[tiisg] = 0.0f; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + buf[sgitg] = tmp; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + tmp = buf[tiisg]; + tmp = simd_sum(tmp); + } + + const float mean = tmp / gs; + tmp = 0.0f; + + for (int j = start; j < end; j += ntg) { + float xi = src0[j] - mean; + dst[j] = xi; + tmp += xi * xi; + } + + tmp = simd_sum(tmp); + if (ntg > N_SIMDWIDTH) { + if (sgitg == 0) { + buf[tiisg] = 0.0f; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tiisg == 0) { + buf[sgitg] = tmp; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + tmp = buf[tiisg]; + tmp = simd_sum(tmp); + } + + const float variance = tmp / gs; + const float scale = 1.0f/sqrt(variance + args.eps); + for (int j = start; j < end; j += ntg) { + dst[j] *= scale; + } +} + +// Q1_0 dot product: dot = d * (2 * Σ(yl[i] where bit=1) - sumy) +inline float block_q_n_dot_y(device const block_q1_0 * qb_curr, float sumy, thread float * yl, int il) { + device const uint8_t * qs = qb_curr->qs + il / 8; + const uint8_t b0 = qs[0]; + const uint8_t b1 = qs[1]; + + float acc = 0.0f; + + acc += select(0.0f, yl[ 0], bool(b0 & 0x01)); + acc += select(0.0f, yl[ 1], bool(b0 & 0x02)); + acc += select(0.0f, yl[ 2], bool(b0 & 0x04)); + acc += select(0.0f, yl[ 3], bool(b0 & 0x08)); + acc += select(0.0f, yl[ 4], bool(b0 & 0x10)); + acc += select(0.0f, yl[ 5], bool(b0 & 0x20)); + acc += select(0.0f, yl[ 6], bool(b0 & 0x40)); + acc += select(0.0f, yl[ 7], bool(b0 & 0x80)); + + acc += select(0.0f, yl[ 8], bool(b1 & 0x01)); + acc += select(0.0f, yl[ 9], bool(b1 & 0x02)); + acc += select(0.0f, yl[10], bool(b1 & 0x04)); + acc += select(0.0f, yl[11], bool(b1 & 0x08)); + acc += select(0.0f, yl[12], bool(b1 & 0x10)); + acc += select(0.0f, yl[13], bool(b1 & 0x20)); + acc += select(0.0f, yl[14], bool(b1 & 0x40)); + acc += select(0.0f, yl[15], bool(b1 & 0x80)); + + return qb_curr->d * (2.0f * acc - sumy); +} + +// function for calculate inner product between half a q4_0 block and 16 floats (yl), sumy is SUM(yl[i]) +// il indicates where the q4 quants begin (0 or QK4_0/4) +// we assume that the yl's have been multiplied with the appropriate scale factor +// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096) +inline float block_q_n_dot_y(device const block_q4_0 * qb_curr, float sumy, thread float * yl, int il) { + float d = qb_curr->d; + + float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + device const uint16_t * qs = ((device const uint16_t *) qb_curr + 1 + il/2); + + for (int i = 0; i < 8; i += 2) { + acc[0] += yl[i + 0] * (qs[i / 2] & 0x000F); + acc[1] += yl[i + 1] * (qs[i / 2] & 0x0F00); + acc[2] += yl[i + 8] * (qs[i / 2] & 0x00F0); + acc[3] += yl[i + 9] * (qs[i / 2] & 0xF000); + } + + return d * (sumy * -8.f + acc[0] + acc[1] + acc[2] + acc[3]); +} + +// function for calculate inner product between half a q4_1 block and 16 floats (yl), sumy is SUM(yl[i]) +// il indicates where the q4 quants begin (0 or QK4_0/4) +// we assume that the yl's have been multiplied with the appropriate scale factor +// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096) +inline float block_q_n_dot_y(device const block_q4_1 * qb_curr, float sumy, thread float * yl, int il) { + float d = qb_curr->d; + float m = qb_curr->m; + + float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + device const uint16_t * qs = ((device const uint16_t *) qb_curr + 2 + il/2); + + for (int i = 0; i < 8; i+=2) { + acc[0] += yl[i + 0] * (qs[i / 2] & 0x000F); + acc[1] += yl[i + 1] * (qs[i / 2] & 0x0F00); + acc[2] += yl[i + 8] * (qs[i / 2] & 0x00F0); + acc[3] += yl[i + 9] * (qs[i / 2] & 0xF000); + } + + return d * (acc[0] + acc[1] + acc[2] + acc[3]) + sumy * m; +} + +// function for calculate inner product between half a q5_0 block and 16 floats (yl), sumy is SUM(yl[i]) +// il indicates where the q5 quants begin (0 or QK5_0/4) +// we assume that the yl's have been multiplied with the appropriate scale factor +// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096) +inline float block_q_n_dot_y(device const block_q5_0 * qb_curr, float sumy, thread float * yl, int il) { + float d = qb_curr->d; + + float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + device const uint16_t * qs = ((device const uint16_t *)qb_curr + 3 + il/2); + const uint32_t qh = *((device const uint32_t *)qb_curr->qh); + + for (int i = 0; i < 8; i+=2) { + acc[0] += yl[i + 0] * ((qs[i / 2] & 0x000F) | ((qh >> (i+0+il ) << 4 ) & 0x00010)); + acc[1] += yl[i + 1] * ((qs[i / 2] & 0x0F00) | ((qh >> (i+1+il ) << 12) & 0x01000)); + acc[2] += yl[i + 8] * ((qs[i / 2] & 0x00F0) | ((qh >> (i+0+il+QK5_0/2) << 8 ) & 0x00100)); + acc[3] += yl[i + 9] * ((qs[i / 2] & 0xF000) | ((qh >> (i+1+il+QK5_0/2) << 16) & 0x10000)); + } + + return d * (sumy * -16.f + acc[0] + acc[1] + acc[2] + acc[3]); +} + +// function for calculate inner product between half a q5_1 block and 16 floats (yl), sumy is SUM(yl[i]) +// il indicates where the q5 quants begin (0 or QK5_1/4) +// we assume that the yl's have been multiplied with the appropriate scale factor +// that corresponds to the missing bit shifts (1, 1/16, 1/256, 1/4096) +inline float block_q_n_dot_y(device const block_q5_1 * qb_curr, float sumy, thread float * yl, int il) { + float d = qb_curr->d; + float m = qb_curr->m; + + float acc[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + + device const uint16_t * qs = ((device const uint16_t *)qb_curr + 4 + il/2); + const uint32_t qh = *((device const uint32_t *)qb_curr->qh); + + for (int i = 0; i < 8; i+=2) { + acc[0] += yl[i + 0] * ((qs[i / 2] & 0x000F) | ((qh >> (i+0+il ) << 4 ) & 0x00010)); + acc[1] += yl[i + 1] * ((qs[i / 2] & 0x0F00) | ((qh >> (i+1+il ) << 12) & 0x01000)); + acc[2] += yl[i + 8] * ((qs[i / 2] & 0x00F0) | ((qh >> (i+0+il+QK5_0/2) << 8 ) & 0x00100)); + acc[3] += yl[i + 9] * ((qs[i / 2] & 0xF000) | ((qh >> (i+1+il+QK5_0/2) << 16) & 0x10000)); + } + + return d * (acc[0] + acc[1] + acc[2] + acc[3]) + sumy * m; +} + +template +static inline void helper_mv_reduce_and_write( + device float * dst_f32, + float sumf[NR0], + const int r0, + const int ne01, + ushort tiisg, + ushort sgitg, + threadgroup char * shmem) { + constexpr short NW = N_SIMDWIDTH; + + threadgroup float * shmem_f32[NR0]; + + for (short row = 0; row < NR0; ++row) { + shmem_f32[row] = (threadgroup float *) shmem + NW*row; + + if (sgitg == 0) { + shmem_f32[row][tiisg] = 0.0f; + } + + sumf[row] = simd_sum(sumf[row]); + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + for (short row = 0; row < NR0; ++row) { + if (tiisg == 0) { + shmem_f32[row][sgitg] = sumf[row]; + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + for (short row = 0; row < NR0 && r0 + row < ne01; ++row) { + float tot = simd_sum(shmem_f32[row][tiisg]); + + if (tiisg == 0 && sgitg == 0) { + dst_f32[r0 + row] = tot; + } + } +} + +constant short FC_mul_mv_nsg [[function_constant(FC_MUL_MV + 0)]]; +constant short FC_mul_mv_nxpsg [[function_constant(FC_MUL_MV + 1)]]; +constant short FC_mul_mv_ne12 [[function_constant(FC_MUL_MV + 2)]]; +constant short FC_mul_mv_r2 [[function_constant(FC_MUL_MV + 3)]]; +constant short FC_mul_mv_r3 [[function_constant(FC_MUL_MV + 4)]]; + +template +void mul_vec_q_n_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + constexpr short NW = N_SIMDWIDTH; + constexpr short NQ = 16; + + const int nb = args.ne00/QK4_0; + + const int r0 = (tgpig.x*NSG + sgitg)*NR0; + //const int r0 = tgpig.x*NR0; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + //const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + //device const block_q_type * x = (device const block_q_type *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + // pointers to src0 rows + device const block_q_type * ax[NR0]; + FOR_UNROLL (int row = 0; row < NR0; ++row) { + const uint64_t offset0 = (r0 + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + + ax[row] = (device const block_q_type *) ((device char *) src0 + offset0); + } + + float sumf[NR0] = {0.f}; + + const short ix = (tiisg/(NW/NQ)); + const short il = (tiisg%(NW/NQ))*8; + + //const int ib0 = sgitg*NQ + ix; + const int ib0 = ix; + + float yl[16]; // src1 vector cache + + //device const float * yb = y + ix*QK4_0 + il; + device const float * yb = y + ib0*QK4_0 + il; + + // each thread in a SIMD group deals with half a block. + //for (int ib = ib0; ib < nb; ib += NSG*NQ) { + for (int ib = ib0; ib < nb; ib += NQ) { + float sumy[2] = { 0.f, 0.f }; + + FOR_UNROLL (short i = 0; i < 8; i += 2) { + sumy[0] += yb[i + 0] + yb[i + 1]; + yl[i + 0] = yb[i + 0]; + yl[i + 1] = yb[i + 1]/256.f; + + sumy[1] += yb[i + 16] + yb[i + 17]; + yl[i + 8] = yb[i + 16]/16.f; + yl[i + 9] = yb[i + 17]/4096.f; + } + + FOR_UNROLL (short row = 0; row < NR0; row++) { + sumf[row] += block_q_n_dot_y(ax[row] + ib, sumy[0] + sumy[1], yl, il); + } + + yb += QK4_0 * 16; + //yb += NSG*NQ*QK4_0; + } + + device float * dst_f32 = (device float *) dst + im*args.ne0*args.ne1 + r1*args.ne0; + + //helper_mv_reduce_and_write(dst_f32, sumf, r0, args.ne01, tiisg, sgitg, shmem); + + for (int row = 0; row < NR0; ++row) { + const float tot = simd_sum(sumf[row]); + + if (tiisg == 0 && r0 + row < args.ne01) { + dst_f32[r0 + row] = tot; + } + } +} + +template +void kernel_mul_mv_q1_0_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK1_0; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset1 = r1*args.nb11 + (i12)*args.nb12 + (i13)*args.nb13; + + device const float * y = (device const float *) (src1 + offset1); + + device const block_q1_0 * ax[nr0]; + for (int row = 0; row < nr0; ++row) { + const uint64_t offset0 = (first_row + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + ax[row] = (device const block_q1_0 *) ((device char *) src0 + offset0); + } + + float yl[16]; + float sumf[nr0] = {0.f}; + + const short ix = (tiisg/8); + const short il = (tiisg%8)*16; + + device const float * yb = y + ix*QK1_0 + il; + + for (int ib = ix; ib < nb; ib += N_SIMDWIDTH/8) { + float sumy = 0.f; + + FOR_UNROLL (short i = 0; i < 16; i++) { + yl[i] = yb[i]; + sumy += yb[i]; + } + + FOR_UNROLL (short row = 0; row < nr0; row++) { + sumf[row] += block_q_n_dot_y(ax[row] + ib, sumy, yl, il); + } + + yb += QK1_0 * (N_SIMDWIDTH/8); + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0; ++row) { + const float tot = simd_sum(sumf[row]); + + if (tiisg == 0 && first_row + row < args.ne01) { + dst_f32[first_row + row] = tot; + } + } +} + +[[host_name("kernel_mul_mv_q1_0_f32")]] +kernel void kernel_mul_mv_q1_0_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + kernel_mul_mv_q1_0_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); +} + +kernel void kernel_mul_mv_q4_0_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + mul_vec_q_n_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +kernel void kernel_mul_mv_q4_1_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + mul_vec_q_n_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +kernel void kernel_mul_mv_q5_0_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + mul_vec_q_n_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +kernel void kernel_mul_mv_q5_1_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + mul_vec_q_n_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_q8_0_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + constexpr short NW = N_SIMDWIDTH; + constexpr short NQ = 8; + + const int nb = args.ne00/QK8_0; + + const int r0 = tgpig.x*NR0; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + //const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + //device const block_q8_0 * x = (device const block_q8_0 *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + // pointers to src0 rows + device const block_q8_0 * ax[NR0]; + FOR_UNROLL (short row = 0; row < NR0; ++row) { + const uint64_t offset0 = (r0 + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + + ax[row] = (device const block_q8_0 *) ((device char *) src0 + offset0); + } + + float sumf[NR0] = { 0.f }; + + const short ix = tiisg/(NW/NQ); + const short il = tiisg%(NW/NQ); + + const int ib0 = sgitg*NQ + ix; + + float yl[NQ]; + + device const float * yb = y + ib0*QK8_0 + il*NQ; + + // each thread in a SIMD group deals with NQ quants at a time + for (int ib = ib0; ib < nb; ib += NSG*NQ) { + for (short i = 0; i < NQ; ++i) { + yl[i] = yb[i]; + } + + for (short row = 0; row < NR0; row++) { + device const int8_t * qs = ax[row][ib].qs + il*NQ; + + float sumq = 0.f; + FOR_UNROLL (short i = 0; i < NQ; ++i) { + sumq += qs[i] * yl[i]; + } + + sumf[row] += sumq*ax[row][ib].d; + } + + yb += NSG*NQ*QK8_0; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + helper_mv_reduce_and_write(dst_f32, sumf, r0, args.ne01, tiisg, sgitg, shmem); +} + +[[host_name("kernel_mul_mv_q8_0_f32")]] +kernel void kernel_mul_mv_q8_0_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + kernel_mul_mv_q8_0_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +// mat-vec kernel processing in chunks of float4 +// chpb - chunks per quantization block +template +void kernel_mul_mv_ext_q4_f32_impl( + constant ggml_metal_kargs_mul_mv_ext & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + const short NSG = FC_mul_mv_nsg; + const short nxpsg = FC_mul_mv_nxpsg; + + const short chpt = 4; // chunks per thread + + //const short nxpsg = (32); + const short nypsg = (32/nxpsg); + + const short tx = tiisg%nxpsg; + const short ty = tiisg/nxpsg; + + const int i01 = tgpig.x*(nypsg*NSG) + nypsg*sgitg + ty; + const int i11 = tgpig.y*r1ptg; + const int i1m = tgpig.z; + + const int i12 = i1m%FC_mul_mv_ne12; + const int i13 = i1m/FC_mul_mv_ne12; + + const uint64_t offset0 = i01*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = i11*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const q_t * xq = (i01 < args.ne01) ? (device const q_t *) (src0 + offset0) + tx/chpb : (device const q_t *) src0; + + device const float4 * y4[r1ptg]; + + for (int ir1 = 0; ir1 < r1ptg; ++ir1) { + y4[ir1] = (i11 + ir1 < args.ne11) ? (device const float4 *) (src1 + offset1 + ir1*args.nb11) + tx : (device const float4 *) src1; + } + + float sumf[r1ptg] = { [ 0 ... r1ptg - 1 ] = 0.0f }; + + short cch = tx%chpb; // current chunk index + + for (int ich = tx; 4*ich < args.ne00; ich += chpt*nxpsg) { + float4 lx[chpt]; + +#pragma unroll(chpt) + for (short ch = 0; ch < chpt; ++ch) { + deq_t4(xq, cch, lx[ch]); + + cch += nxpsg; + if (cch >= chpb) { + xq += cch/chpb; + cch %= chpb; + } + } + +#pragma unroll(chpt) + for (short ch = 0; ch < chpt; ++ch) { +#pragma unroll(r1ptg) + for (short ir1 = 0; ir1 < r1ptg; ++ir1) { + sumf[ir1] += dot(lx[ch], y4[ir1][ch*nxpsg]); + } + } + +#pragma unroll(r1ptg) + for (short ir1 = 0; ir1 < r1ptg; ++ir1) { + y4[ir1] += chpt*nxpsg; + } + } + + // reduce only the threads in each row + for (short ir1 = 0; ir1 < r1ptg; ++ir1) { + if (nxpsg >= 32) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 16); + } + if (nxpsg >= 16) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 8); + } + if (nxpsg >= 8) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 4); + } + if (nxpsg >= 4) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 2); + } + if (nxpsg >= 2) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 1); + } + + //sumf[ir1] = simd_sum(sumf[ir1]); + } + + if (tx == 0) { + for (short ir1 = 0; ir1 < r1ptg && i11 + ir1 < args.ne11; ++ir1) { + device float * dst_f32 = (device float *) dst + (uint64_t)i1m*args.ne0*args.ne1 + (uint64_t)(i11 + ir1)*args.ne0; + + if (i01 < args.ne01) { + dst_f32[i01] = sumf[ir1]; + } + } + } +} + +// mat-vec kernel processing in chunks of float4x4 +template +void kernel_mul_mv_ext_q4x4_f32_impl( + constant ggml_metal_kargs_mul_mv_ext & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + const short NSG = FC_mul_mv_nsg; + const short nxpsg = FC_mul_mv_nxpsg; + + const short chpt = 1; + + //const short nxpsg = (32); + const short nypsg = (32/nxpsg); + + const short tx = tiisg%nxpsg; + const short ty = tiisg/nxpsg; + + const int i01 = tgpig.x*(nypsg*NSG) + nypsg*sgitg + ty; + const int i11 = tgpig.y*r1ptg; + const int i1m = tgpig.z; + + const int i12 = i1m%FC_mul_mv_ne12; + const int i13 = i1m/FC_mul_mv_ne12; + + const uint64_t offset0 = i01*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = i11*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const q_t * xq = (i01 < args.ne01) ? (device const q_t *) (src0 + offset0) + tx/chpb : (device const q_t *) src0; + + device const float4x4 * y4x4[r1ptg]; + + for (int ir1 = 0; ir1 < r1ptg; ++ir1) { + y4x4[ir1] = (i11 + ir1 < args.ne11) ? (device const float4x4 *) (src1 + offset1 + ir1*args.nb11) + tx : (device const float4x4 *) src1; + } + + float sumf[r1ptg] = { [ 0 ... r1ptg - 1 ] = 0.0f }; + + short cch = tx%chpb; + + for (int ich = tx; 16*ich < args.ne00; ich += chpt*nxpsg) { + float4x4 lx[chpt]; + +#pragma unroll(chpt) + for (short ch = 0; ch < chpt; ++ch) { + deq_t4x4(xq, cch, lx[ch]); + + cch += nxpsg; + if (cch >= chpb) { + xq += cch/chpb; + cch %= chpb; + } + } + +#pragma unroll(chpt) + for (short ch = 0; ch < chpt; ++ch) { +#pragma unroll(r1ptg) + for (short ir1 = 0; ir1 < r1ptg; ++ir1) { + sumf[ir1] += + dot(lx[ch][0], y4x4[ir1][ch*nxpsg][0]) + + dot(lx[ch][1], y4x4[ir1][ch*nxpsg][1]) + + dot(lx[ch][2], y4x4[ir1][ch*nxpsg][2]) + + dot(lx[ch][3], y4x4[ir1][ch*nxpsg][3]); + + } + } + +#pragma unroll(r1ptg) + for (short ir1 = 0; ir1 < r1ptg; ++ir1) { + y4x4[ir1] += chpt*nxpsg; + } + } + + for (short ir1 = 0; ir1 < r1ptg; ++ir1) { + if (nxpsg >= 32) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 16); + } + if (nxpsg >= 16) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 8); + } + if (nxpsg >= 8) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 4); + } + if (nxpsg >= 4) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 2); + } + if (nxpsg >= 2) { + sumf[ir1] += simd_shuffle_down(sumf[ir1], 1); + } + + //sumf[ir1] = simd_sum(sumf[ir1]); + } + + if (tx == 0) { + for (short ir1 = 0; ir1 < r1ptg && i11 + ir1 < args.ne11; ++ir1) { + device float * dst_f32 = (device float *) dst + (uint64_t)i1m*args.ne0*args.ne1 + (uint64_t)(i11 + ir1)*args.ne0; + + if (i01 < args.ne01) { + dst_f32[i01] = sumf[ir1]; + } + } + } +} + +// dispatchers needed for compile-time nxpsg +// epb - elements per quantization block +template +kernel void kernel_mul_mv_ext_q4_f32_disp( + constant ggml_metal_kargs_mul_mv_ext & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + kernel_mul_mv_ext_q4_f32_impl(args, src0, src1, dst, tgpig, tiisg, sgitg); +} + +template +kernel void kernel_mul_mv_ext_q4x4_f32_disp( + constant ggml_metal_kargs_mul_mv_ext & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + kernel_mul_mv_ext_q4x4_f32_impl(args, src0, src1, dst, tgpig, tiisg, sgitg); +} + +typedef decltype(kernel_mul_mv_ext_q4_f32_disp <2, block_q8_0, 32, dequantize_q8_0_t4>) mul_mv_ext_q4_f32_t; +typedef decltype(kernel_mul_mv_ext_q4x4_f32_disp<2, block_q4_K, 256, dequantize_q4_K>) mul_mv_ext_q4x4_f32_t; + +template [[host_name("kernel_mul_mv_ext_f32_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, float4, 4, dequantize_f32_t4>; +template [[host_name("kernel_mul_mv_ext_f32_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, float4, 4, dequantize_f32_t4>; +template [[host_name("kernel_mul_mv_ext_f32_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, float4, 4, dequantize_f32_t4>; +template [[host_name("kernel_mul_mv_ext_f32_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, float4, 4, dequantize_f32_t4>; + +template [[host_name("kernel_mul_mv_ext_f16_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, half4, 4, dequantize_f16_t4>; +template [[host_name("kernel_mul_mv_ext_f16_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, half4, 4, dequantize_f16_t4>; +template [[host_name("kernel_mul_mv_ext_f16_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, half4, 4, dequantize_f16_t4>; +template [[host_name("kernel_mul_mv_ext_f16_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, half4, 4, dequantize_f16_t4>; + +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_mul_mv_ext_bf16_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, bfloat4, 4, dequantize_bf16_t4>; +template [[host_name("kernel_mul_mv_ext_bf16_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, bfloat4, 4, dequantize_bf16_t4>; +template [[host_name("kernel_mul_mv_ext_bf16_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, bfloat4, 4, dequantize_bf16_t4>; +template [[host_name("kernel_mul_mv_ext_bf16_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, bfloat4, 4, dequantize_bf16_t4>; +#endif + +template [[host_name("kernel_mul_mv_ext_q1_0_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q1_0, 128, dequantize_q1_0_t4>; +template [[host_name("kernel_mul_mv_ext_q1_0_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q1_0, 128, dequantize_q1_0_t4>; +template [[host_name("kernel_mul_mv_ext_q1_0_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q1_0, 128, dequantize_q1_0_t4>; +template [[host_name("kernel_mul_mv_ext_q1_0_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q1_0, 128, dequantize_q1_0_t4>; + +template [[host_name("kernel_mul_mv_ext_q4_0_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q4_0, 32, dequantize_q4_0_t4>; +template [[host_name("kernel_mul_mv_ext_q4_0_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q4_0, 32, dequantize_q4_0_t4>; +template [[host_name("kernel_mul_mv_ext_q4_0_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q4_0, 32, dequantize_q4_0_t4>; +template [[host_name("kernel_mul_mv_ext_q4_0_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q4_0, 32, dequantize_q4_0_t4>; + +template [[host_name("kernel_mul_mv_ext_q4_1_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q4_1, 32, dequantize_q4_1_t4>; +template [[host_name("kernel_mul_mv_ext_q4_1_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q4_1, 32, dequantize_q4_1_t4>; +template [[host_name("kernel_mul_mv_ext_q4_1_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q4_1, 32, dequantize_q4_1_t4>; +template [[host_name("kernel_mul_mv_ext_q4_1_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q4_1, 32, dequantize_q4_1_t4>; + +template [[host_name("kernel_mul_mv_ext_q5_0_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q5_0, 32, dequantize_q5_0_t4>; +template [[host_name("kernel_mul_mv_ext_q5_0_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q5_0, 32, dequantize_q5_0_t4>; +template [[host_name("kernel_mul_mv_ext_q5_0_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q5_0, 32, dequantize_q5_0_t4>; +template [[host_name("kernel_mul_mv_ext_q5_0_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q5_0, 32, dequantize_q5_0_t4>; + +template [[host_name("kernel_mul_mv_ext_q5_1_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q5_1, 32, dequantize_q5_1_t4>; +template [[host_name("kernel_mul_mv_ext_q5_1_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q5_1, 32, dequantize_q5_1_t4>; +template [[host_name("kernel_mul_mv_ext_q5_1_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q5_1, 32, dequantize_q5_1_t4>; +template [[host_name("kernel_mul_mv_ext_q5_1_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q5_1, 32, dequantize_q5_1_t4>; + +template [[host_name("kernel_mul_mv_ext_q8_0_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_q8_0, 32, dequantize_q8_0_t4>; +template [[host_name("kernel_mul_mv_ext_q8_0_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_q8_0, 32, dequantize_q8_0_t4>; +template [[host_name("kernel_mul_mv_ext_q8_0_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_q8_0, 32, dequantize_q8_0_t4>; +template [[host_name("kernel_mul_mv_ext_q8_0_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_q8_0, 32, dequantize_q8_0_t4>; + +template [[host_name("kernel_mul_mv_ext_mxfp4_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_mxfp4, 32, dequantize_mxfp4_t4>; +template [[host_name("kernel_mul_mv_ext_mxfp4_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_mxfp4, 32, dequantize_mxfp4_t4>; +template [[host_name("kernel_mul_mv_ext_mxfp4_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_mxfp4, 32, dequantize_mxfp4_t4>; +template [[host_name("kernel_mul_mv_ext_mxfp4_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_mxfp4, 32, dequantize_mxfp4_t4>; + +template [[host_name("kernel_mul_mv_ext_iq4_nl_f32_r1_2")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<2, block_iq4_nl, 32, dequantize_iq4_nl_t4>; +template [[host_name("kernel_mul_mv_ext_iq4_nl_f32_r1_3")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<3, block_iq4_nl, 32, dequantize_iq4_nl_t4>; +template [[host_name("kernel_mul_mv_ext_iq4_nl_f32_r1_4")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<4, block_iq4_nl, 32, dequantize_iq4_nl_t4>; +template [[host_name("kernel_mul_mv_ext_iq4_nl_f32_r1_5")]] kernel mul_mv_ext_q4_f32_t kernel_mul_mv_ext_q4_f32_disp<5, block_iq4_nl, 32, dequantize_iq4_nl_t4>; + +template [[host_name("kernel_mul_mv_ext_q4_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q4_K, 256, dequantize_q4_K>; +template [[host_name("kernel_mul_mv_ext_q4_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q4_K, 256, dequantize_q4_K>; +template [[host_name("kernel_mul_mv_ext_q4_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q4_K, 256, dequantize_q4_K>; +template [[host_name("kernel_mul_mv_ext_q4_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q4_K, 256, dequantize_q4_K>; + +template [[host_name("kernel_mul_mv_ext_q5_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q5_K, 256, dequantize_q5_K>; +template [[host_name("kernel_mul_mv_ext_q5_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q5_K, 256, dequantize_q5_K>; +template [[host_name("kernel_mul_mv_ext_q5_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q5_K, 256, dequantize_q5_K>; +template [[host_name("kernel_mul_mv_ext_q5_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q5_K, 256, dequantize_q5_K>; + +template [[host_name("kernel_mul_mv_ext_q6_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q6_K, 256, dequantize_q6_K>; +template [[host_name("kernel_mul_mv_ext_q6_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q6_K, 256, dequantize_q6_K>; +template [[host_name("kernel_mul_mv_ext_q6_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q6_K, 256, dequantize_q6_K>; +template [[host_name("kernel_mul_mv_ext_q6_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q6_K, 256, dequantize_q6_K>; + +template [[host_name("kernel_mul_mv_ext_q2_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q2_K, 256, dequantize_q2_K>; +template [[host_name("kernel_mul_mv_ext_q2_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q2_K, 256, dequantize_q2_K>; +template [[host_name("kernel_mul_mv_ext_q2_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q2_K, 256, dequantize_q2_K>; +template [[host_name("kernel_mul_mv_ext_q2_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q2_K, 256, dequantize_q2_K>; + +template [[host_name("kernel_mul_mv_ext_q3_K_f32_r1_2")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<2, block_q3_K, 256, dequantize_q3_K>; +template [[host_name("kernel_mul_mv_ext_q3_K_f32_r1_3")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<3, block_q3_K, 256, dequantize_q3_K>; +template [[host_name("kernel_mul_mv_ext_q3_K_f32_r1_4")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<4, block_q3_K, 256, dequantize_q3_K>; +template [[host_name("kernel_mul_mv_ext_q3_K_f32_r1_5")]] kernel mul_mv_ext_q4x4_f32_t kernel_mul_mv_ext_q4x4_f32_disp<5, block_q3_K, 256, dequantize_q3_K>; + +template +void kernel_mul_mv_t_t_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + constexpr short NW = N_SIMDWIDTH; + constexpr short NB = 32; + constexpr short NF = 8; + + const int nb = args.ne00/NB; + + const int r0 = tgpig.x*NR0; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + //const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + //device const T0 * x = (device const T0 *) (src0 + offset0); + device const T1 * y = (device const T1 *) (src1 + offset1); + + // pointers to src0 rows + device const T0 * ax [NR0]; + FOR_UNROLL (short row = 0; row < NR0; ++row) { + const uint64_t offset0 = (r0 + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + + ax[row] = (device const T0 *) ((device char *) src0 + offset0); + } + + float sumf[NR0] = { 0.f }; + + const short ix = tiisg/(NW/NF); + const short il = tiisg%(NW/NF); + + const int ib0 = sgitg*NF + ix; + + T1 yl[NF]; + + device const T1 * yb = y + (ib0*NB + il*NF); + + for (int ib = ib0; ib < nb; ib += NSG*NF) { + for (short i = 0; i < NF; ++i) { + yl[i] = yb[i]; + } + + for (short row = 0; row < NR0; row++) { + device const T0 * xb = ax[row] + (ib*NB + il*NF); + + float sumq = 0.f; + FOR_UNROLL (short i = 0; i < NF; ++i) { + sumq += xb[i] * yl[i]; + } + + sumf[row] += sumq; + } + + yb += NSG*NF*NW; + } + + for (int i = nb*NB + sgitg*NW + tiisg; i < args.ne00; i += NW*NSG) { + for (short row = 0; row < NR0; row++) { + sumf[row] += ax[row][i] * y[i]; + } + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + helper_mv_reduce_and_write(dst_f32, sumf, r0, args.ne01, tiisg, sgitg, shmem); +} + +template +void kernel_mul_mv_t_t_disp( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + switch (args.nr0) { + //case 1: kernel_mul_mv_t_t_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; + case 2: kernel_mul_mv_t_t_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; + //case 3: kernel_mul_mv_t_t_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; + //case 4: kernel_mul_mv_t_t_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; + } +} + +template +kernel void kernel_mul_mv_t_t( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + kernel_mul_mv_t_t_disp(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +typedef decltype(kernel_mul_mv_t_t) mul_mv_t_t; + +template [[host_name("kernel_mul_mv_f32_f32")]] kernel mul_mv_t_t kernel_mul_mv_t_t; +template [[host_name("kernel_mul_mv_f16_f32")]] kernel mul_mv_t_t kernel_mul_mv_t_t; +template [[host_name("kernel_mul_mv_f16_f16")]] kernel mul_mv_t_t kernel_mul_mv_t_t; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_mul_mv_bf16_f32")]] kernel mul_mv_t_t kernel_mul_mv_t_t; +template [[host_name("kernel_mul_mv_bf16_bf16")]] kernel mul_mv_t_t kernel_mul_mv_t_t; +#endif + +template +void kernel_mul_mv_t_t_4_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + constexpr short NW = N_SIMDWIDTH; + constexpr short NB = 32; + constexpr short NF = 16; + constexpr short NF4 = NF/4; + + const int nb = args.ne00/NB; + + const int r0 = tgpig.x*NR0; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + //const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const T1 * y = (device const T1 *) (src1 + offset1); + device const T14 * y4 = (device const T14 *) (src1 + offset1); + + // pointers to src0 rows + device const T0 * ax [NR0]; + device const T04 * ax4[NR0]; + FOR_UNROLL (short row = 0; row < NR0; ++row) { + const uint64_t offset0 = (r0 + row)*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + + ax [row] = (device const T0 *) ((device char *) src0 + offset0); + ax4[row] = (device const T04 *) ((device char *) src0 + offset0); + } + + float sumf[NR0] = { 0.f }; + + const short ix = tiisg/(NW/NF); + const short il = tiisg%(NW/NF); + + const int ib0 = sgitg*NF + ix; + + T14 yl4[NF4]; + + device const T14 * yb4 = y4 + (ib0*NB + il*NF)/4; + + for (int ib = ib0; ib < nb; ib += NSG*NF) { + for (short i = 0; i < NF4; ++i) { + yl4[i] = yb4[i]; + } + + for (short row = 0; row < NR0; row++) { + device const T04 * xb4 = ax4[row] + (ib*NB + il*NF)/4; + + float sumq = 0.f; + FOR_UNROLL (short i = 0; i < NF4; ++i) { + sumq += dot(float4(xb4[i]), float4(yl4[i])); + } + + sumf[row] += sumq; + } + + yb4 += NSG*NF*NW/4; + } + + for (int i = nb*NB + sgitg*NW + tiisg; i < args.ne00; i += NW*NSG) { + for (short row = 0; row < NR0; row++) { + sumf[row] += ax[row][i] * y[i]; + } + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + helper_mv_reduce_and_write(dst_f32, sumf, r0, args.ne01, tiisg, sgitg, shmem); +} + +template +void kernel_mul_mv_t_t_4_disp( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + switch (args.nr0) { + //case 1: kernel_mul_mv_t_t_4_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; + case 2: kernel_mul_mv_t_t_4_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; + //case 3: kernel_mul_mv_t_t_4_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; + //case 4: kernel_mul_mv_t_t_4_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); break; + }; +} + +template +kernel void kernel_mul_mv_t_t_4( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + kernel_mul_mv_t_t_4_disp(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +typedef decltype(kernel_mul_mv_t_t_4) mul_mv_t_t_4; + +template [[host_name("kernel_mul_mv_f32_f32_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; +template [[host_name("kernel_mul_mv_f16_f32_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; +template [[host_name("kernel_mul_mv_f16_f16_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_mul_mv_bf16_f32_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; +template [[host_name("kernel_mul_mv_bf16_bf16_4")]] kernel mul_mv_t_t_4 kernel_mul_mv_t_t_4; +#endif + +template +void kernel_mul_mv_t_t_short_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig, + ushort tiisg) { + const int r0 = tgpig.x*32 + tiisg; + const int r1 = tgpig.y; + const int im = tgpig.z; + + if (r0 >= args.ne01) { + return; + } + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = r0*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + + device const T0 * x = (device const T0 *) (src0 + offset0); + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1; + + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const T1 * y = (device const T1 *) (src1 + offset1); + + float res = 0.0f; + + for (int i = 0; i < args.ne00; ++i) { + res += (float) x[i] * (float) y[i]; + } + + dst_f32[(uint64_t)r1*args.ne0 + r0] = res; +} + +template +kernel void kernel_mul_mv_t_t_short( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]]) { + kernel_mul_mv_t_t_short_impl( + args, + src0, + src1, + dst, + tgpig, + tiisg); +} + +typedef decltype(kernel_mul_mv_t_t_short) mul_mv_t_t_short_t; + +template [[host_name("kernel_mul_mv_f32_f32_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; +template [[host_name("kernel_mul_mv_f16_f32_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; +template [[host_name("kernel_mul_mv_f16_f16_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_mul_mv_bf16_f32_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; +template [[host_name("kernel_mul_mv_bf16_bf16_short")]] kernel mul_mv_t_t_short_t kernel_mul_mv_t_t_short; +#endif + +constant bool FC_rope_is_imrope [[function_constant(FC_ROPE + 0)]]; + +static float rope_yarn_ramp(const float low, const float high, const int i0) { + const float y = (i0 / 2 - low) / max(0.001f, high - low); + return 1.0f - min(1.0f, max(0.0f, y)); +} + +// YaRN algorithm based on LlamaYaRNScaledRotaryEmbedding.py from https://github.com/jquesnelle/yarn +// MIT licensed. Copyright (c) 2023 Jeffrey Quesnelle and Bowen Peng. +static void rope_yarn( + float theta_extrap, float freq_scale, float corr_dims[2], int i0, float ext_factor, float mscale, + thread float * cos_theta, thread float * sin_theta) { + // Get n-d rotational scaling corrected for extrapolation + float theta_interp = freq_scale * theta_extrap; + float theta = theta_interp; + if (ext_factor != 0.0f) { + float ramp_mix = rope_yarn_ramp(corr_dims[0], corr_dims[1], i0) * ext_factor; + theta = theta_interp * (1 - ramp_mix) + theta_extrap * ramp_mix; + + // Get n-d magnitude scaling corrected for interpolation + mscale *= 1.0f + 0.1f * log(1.0f / freq_scale); + } + *cos_theta = cos(theta) * mscale; + *sin_theta = sin(theta) * mscale; +} + +// Apparently solving `n_rot = 2pi * x * base^((2 * max_pos_emb) / n_dims)` for x, we get +// `corr_fac(n_rot) = n_dims * log(max_pos_emb / (n_rot * 2pi)) / (2 * log(base))` +static float rope_yarn_corr_factor(int n_dims, int n_ctx_orig, float n_rot, float base) { + return n_dims * log(n_ctx_orig / (n_rot * 2 * M_PI_F)) / (2 * log(base)); +} + +static void rope_yarn_corr_dims( + int n_dims, int n_ctx_orig, float freq_base, float beta_fast, float beta_slow, float dims[2] +) { + // start and end correction dims + dims[0] = max(0.0f, floor(rope_yarn_corr_factor(n_dims, n_ctx_orig, beta_fast, freq_base))); + dims[1] = min(n_dims - 1.0f, ceil(rope_yarn_corr_factor(n_dims, n_ctx_orig, beta_slow, freq_base))); +} + +template +kernel void kernel_rope_norm( + constant ggml_metal_kargs_rope & args, + device const char * src0, + device const char * src1, + device const char * src2, + device char * dst, + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 tptg [[threads_per_threadgroup]], + uint3 tgpig[[threadgroup_position_in_grid]]) { + const int i3 = tgpig[2]; + const int i2 = tgpig[1]; + const int i1 = tgpig[0]; + + float corr_dims[2]; + rope_yarn_corr_dims(args.n_dims, args.n_ctx_orig, args.freq_base, args.beta_fast, args.beta_slow, corr_dims); + + device const int32_t * pos = (device const int32_t *) src1; + + const float theta_base = (float) pos[i2]; + const float inv_ndims = -1.f/args.n_dims; + + float cos_theta; + float sin_theta; + + for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) { + if (i0 < args.n_dims) { + const int ic = i0/2; + + const float theta = theta_base * pow(args.freq_base, inv_ndims*i0); + + const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f; + + rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta); + + device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); + device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + const float x0 = src[0]; + const float x1 = src[1]; + + dst_data[0] = x0*cos_theta - x1*sin_theta; + dst_data[1] = x0*sin_theta + x1*cos_theta; + } else { + device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); + device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + dst_data[0] = src[0]; + dst_data[1] = src[1]; + } + } +} + +template +kernel void kernel_rope_neox( + constant ggml_metal_kargs_rope & args, + device const char * src0, + device const char * src1, + device const char * src2, + device char * dst, + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 tptg [[threads_per_threadgroup]], + uint3 tgpig[[threadgroup_position_in_grid]]) { + const int i3 = tgpig[2]; + const int i2 = tgpig[1]; + const int i1 = tgpig[0]; + + float corr_dims[2]; + rope_yarn_corr_dims(args.n_dims, args.n_ctx_orig, args.freq_base, args.beta_fast, args.beta_slow, corr_dims); + + device const int32_t * pos = (device const int32_t *) src1; + + const float theta_base = (float) pos[i2]; + const float inv_ndims = -1.f/args.n_dims; + + float cos_theta; + float sin_theta; + + for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) { + if (i0 < args.n_dims) { + const int ic = i0/2; + + const float theta = theta_base * pow(args.freq_base, inv_ndims*i0); + + const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f; + + rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta); + + device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + ic*args.nb00); + device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + ic*args.nb0); + + const float x0 = src[0]; + const float x1 = src[args.n_dims/2]; + + dst_data[0] = x0*cos_theta - x1*sin_theta; + dst_data[args.n_dims/2] = x0*sin_theta + x1*cos_theta; + } else { + device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); + device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + dst_data[0] = src[0]; + dst_data[1] = src[1]; + } + } +} + +template +kernel void kernel_rope_multi( + constant ggml_metal_kargs_rope & args, + device const char * src0, + device const char * src1, + device const char * src2, + device char * dst, + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 tptg [[threads_per_threadgroup]], + uint3 tgpig[[threadgroup_position_in_grid]]) { + const int i3 = tgpig[2]; + const int i2 = tgpig[1]; + const int i1 = tgpig[0]; + + float corr_dims[2]; + rope_yarn_corr_dims(args.n_dims, args.n_ctx_orig, args.freq_base, args.beta_fast, args.beta_slow, corr_dims); + + device const int32_t * pos = (device const int32_t *) src1; + + const float inv_ndims = -1.f/args.n_dims; + + float cos_theta; + float sin_theta; + + for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) { + if (i0 < args.n_dims) { + const int ic = i0/2; + + // mrope theta calculations + // note: the rest is the same as kernel_rope_neox + const int sect_dims = args.sect_0 + args.sect_1 + args.sect_2 + args.sect_3; + const int sec_w01 = args.sect_0 + args.sect_1; // end of section 1 + const int sec_w012 = args.sect_0 + args.sect_1 + args.sect_2; // end of section 2 + const int sector = ic % sect_dims; + + float theta_base; + if (FC_rope_is_imrope) { + if (sector % 3 == 1 && sector < 3 * args.sect_1) { // h + theta_base = (float) pos[i2 + args.ne02 * 1]; + } else if (sector % 3 == 2 && sector < 3 * args.sect_2) { // w + theta_base = (float) pos[i2 + args.ne02 * 2]; + } else if (sector % 3 == 0 && sector < 3 * args.sect_0) { // t + theta_base = (float) pos[i2 + args.ne02 * 0]; + } else { // e + theta_base = (float) pos[i2 + args.ne02 * 3]; + } + } else { + if (sector < args.sect_0) { + theta_base = (float) pos[i2]; + } else if (sector < sec_w01) { + theta_base = (float) pos[i2 + args.ne02 * 1]; + } else if (sector < sec_w012) { + theta_base = (float) pos[i2 + args.ne02 * 2]; + } else { + theta_base = (float) pos[i2 + args.ne02 * 3]; + } + } + // end of mrope + + const float theta = theta_base * pow(args.freq_base, inv_ndims*i0); + + const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f; + + rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta); + + device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + ic*args.nb00); + device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + ic*args.nb0); + + const float x0 = src[0]; + const float x1 = src[args.n_dims/2]; + + dst_data[0] = x0*cos_theta - x1*sin_theta; + dst_data[args.n_dims/2] = x0*sin_theta + x1*cos_theta; + } else { + device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); + device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + dst_data[0] = src[0]; + dst_data[1] = src[1]; + } + } +} + +template +kernel void kernel_rope_vision( + constant ggml_metal_kargs_rope & args, + device const char * src0, + device const char * src1, + device const char * src2, + device char * dst, + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 tptg [[threads_per_threadgroup]], + uint3 tgpig[[threadgroup_position_in_grid]]) { + const int i3 = tgpig[2]; + const int i2 = tgpig[1]; + const int i1 = tgpig[0]; + + float corr_dims[2]; + rope_yarn_corr_dims(args.n_dims, args.n_ctx_orig, args.freq_base, args.beta_fast, args.beta_slow, corr_dims); + + device const int32_t * pos = (device const int32_t *) src1; + + const float inv_ndims = -1.f/args.n_dims; + + float cos_theta; + float sin_theta; + + for (int i0 = 2*tiitg; i0 < args.ne0; i0 += 2*tptg.x) { + if (i0 < 2*args.n_dims) { // different from kernel_rope_multi + const int ic = i0/2; + + // mrope theta calculations (only support 2 dimensions) + const int sect_dims = args.sect_0 + args.sect_1; + const int sector = ic % sect_dims; + + float p; + float theta_base; + if (sector < args.sect_1) { + p = (float) sector; + theta_base = (float) pos[i2]; + } else { + p = (float) sector - args.sect_0; + theta_base = (float) pos[i2 + args.ne02]; + } + + const float theta = theta_base * pow(args.freq_base, 2.0f * inv_ndims * p); + // end of mrope + + const float freq_factor = args.src2 ? ((device const float *) src2)[ic] : 1.0f; + + rope_yarn(theta/freq_factor, args.freq_scale, corr_dims, i0, args.ext_factor, args.attn_factor, &cos_theta, &sin_theta); + + device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + ic*args.nb00); + device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + ic*args.nb0); + + const float x0 = src[0]; + const float x1 = src[args.n_dims]; // different from kernel_rope_multi + + dst_data[0] = x0*cos_theta - x1*sin_theta; + dst_data[args.n_dims] = x0*sin_theta + x1*cos_theta; // different from kernel_rope_multi + } else { + device const T * const src = (device T *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); + device T * dst_data = (device T *)( dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + dst_data[0] = src[0]; + dst_data[1] = src[1]; + } + } +} + +typedef decltype(kernel_rope_norm) kernel_rope_norm_t; +typedef decltype(kernel_rope_neox) kernel_rope_neox_t; +typedef decltype(kernel_rope_multi) kernel_rope_multi_t; +typedef decltype(kernel_rope_vision) kernel_rope_vision_t; + +template [[host_name("kernel_rope_norm_f32")]] kernel kernel_rope_norm_t kernel_rope_norm; +template [[host_name("kernel_rope_norm_f16")]] kernel kernel_rope_norm_t kernel_rope_norm; + +template [[host_name("kernel_rope_neox_f32")]] kernel kernel_rope_neox_t kernel_rope_neox; +template [[host_name("kernel_rope_neox_f16")]] kernel kernel_rope_neox_t kernel_rope_neox; + +template [[host_name("kernel_rope_multi_f32")]] kernel kernel_rope_multi_t kernel_rope_multi; +template [[host_name("kernel_rope_multi_f16")]] kernel kernel_rope_multi_t kernel_rope_multi; + +template [[host_name("kernel_rope_vision_f32")]] kernel kernel_rope_vision_t kernel_rope_vision; +template [[host_name("kernel_rope_vision_f16")]] kernel kernel_rope_vision_t kernel_rope_vision; + +typedef void (im2col_t)( + constant ggml_metal_kargs_im2col & args, + device const float * x, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]); + +template +kernel void kernel_im2col( + constant ggml_metal_kargs_im2col & args, + device const float * x, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { +// const int64_t IC = tgpg[0]; + const int64_t OH = tgpg[1]; + const int64_t OW = tgpg[2]; + + const int64_t KH = ntg[1]; + const int64_t KW = ntg[2]; + + int64_t in = tpitg[0]; + const int64_t ikh = tpitg[1]; + const int64_t ikw = tpitg[2]; + + const int64_t iic = tgpig[0]; + const int64_t ioh = tgpig[1]; + const int64_t iow = tgpig[2]; + + const int64_t iiw = iow*args.s0 + ikw*args.d0 - args.p0; + const int64_t iih = ioh*args.s1 + ikh*args.d1 - args.p1; + + int64_t offset_dst = (in*OH*OW + ioh*OW + iow)*args.CHW + (iic*(KH*KW) + ikh*KW + ikw); + + device T * pdst = (device T *) (dst); + + if (iih < 0 || iih >= args.IH || iiw < 0 || iiw >= args.IW) { + while (in < args.N) { + pdst[offset_dst] = 0.0f; + offset_dst += ntg[0]*args.CHW*OH*OW; + + in += ntg[0]; + } + } else { + int64_t offset_src = in*args.ofs0 + iic*args.ofs1 + iih*args.IW + iiw; + + while (in < args.N) { + pdst[offset_dst] = x[offset_src]; + + offset_dst += ntg[0]*args.CHW*OH*OW; + offset_src += ntg[0]*args.ofs0; + + in += ntg[0]; + } + } +} + +template [[host_name("kernel_im2col_f32")]] kernel im2col_t kernel_im2col; +template [[host_name("kernel_im2col_f16")]] kernel im2col_t kernel_im2col; + +// TODO: obsolete -- remove +//typedef void (im2col_ext_t)( +// constant ggml_metal_kargs_im2col & args, +// device const float * x, +// device char * dst, +// uint3 tgpig[[threadgroup_position_in_grid]], +// uint3 tgpg[[threadgroups_per_grid]], +// uint3 tpitg[[thread_position_in_threadgroup]], +// uint3 ntg[[threads_per_threadgroup]]); +// +//template +//kernel void kernel_im2col_ext( +// constant ggml_metal_kargs_im2col & args, +// device const float * x, +// device char * dst, +// uint3 tgpig[[threadgroup_position_in_grid]], +// uint3 tgpg[[threadgroups_per_grid]], // tgpg[0] = D x IC x KH x KW, CHW = IC x KH x KW +// uint3 tpitg[[thread_position_in_threadgroup]], +// uint3 ntg[[threads_per_threadgroup]]) { // [M, 1, 1] +// const int64_t KHW = (int64_t)args.KHW; +// +// const int64_t d = tgpig[0] / args.CHW; +// const int64_t chw = tgpig[0] % args.CHW; +// const int64_t tgpig_0 = chw / KHW; // 0 ~ (IC - 1) +// const int64_t HW = tgpig[0] % KHW; +// +// const int64_t tpitg_0 = (d * ntg[0]) + tpitg[0]; +// if (tpitg_0 >= args.N) { +// return; +// } +// +// const int64_t tpitg_1 = HW / args.KW; +// const int64_t tpitg_2 = HW % args.KW; +// +// const int64_t iiw = tgpig[2] * args.s0 + tpitg_2 * args.d0 - args.p0; +// const int64_t iih = tgpig[1] * args.s1 + tpitg_1 * args.d1 - args.p1; +// +// const int64_t offset_dst = +// (tpitg_0 * tgpg[1] * tgpg[2] + tgpig[1] * tgpg[2] + tgpig[2]) * args.CHW + +// (tgpig_0 * KHW + tpitg_1 * args.KW + tpitg_2); +// +// device T * pdst = (device T *) (dst); +// +// if (iih < 0 || iih >= args.IH || iiw < 0 || iiw >= args.IW) { +// pdst[offset_dst] = 0.0f; +// } else { +// const int64_t offset_src = tpitg_0 * args.ofs0 + tgpig_0 * args.ofs1; +// pdst[offset_dst] = x[offset_src + iih * args.IW + iiw]; +// } +//} +// +//template [[host_name("kernel_im2col_ext_f32")]] kernel im2col_ext_t kernel_im2col_ext; +//template [[host_name("kernel_im2col_ext_f16")]] kernel im2col_ext_t kernel_im2col_ext; + +template +kernel void kernel_conv_2d( + constant ggml_metal_kargs_conv_2d & args, + device const char * weights, + device const char * src, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const uint threads_per_tg = ntg.x * ntg.y * ntg.z; + const uint tg_index = (tgpig.z * tgpg.y + tgpig.y) * tgpg.x + tgpig.x; + const uint local_thread = tpitg.z * (ntg.x * ntg.y) + tpitg.y * ntg.x + tpitg.x; + const uint thread_index = tg_index * threads_per_tg + local_thread; + const uint64_t total_threads = (uint64_t) threads_per_tg * tgpg.x * tgpg.y * tgpg.z; + const uint64_t total_outputs = (uint64_t) args.N * args.OC * args.OH * args.OW; + + for (uint64_t index = thread_index; index < total_outputs; index += total_threads) { + uint64_t tmp = index; + + const int32_t ow = tmp % args.OW; tmp /= args.OW; + const int32_t oh = tmp % args.OH; tmp /= args.OH; + const int32_t oc = tmp % args.OC; tmp /= args.OC; + const int32_t n = tmp; + + float acc = 0.0f; + + const int32_t base_x = ow*args.s0 - args.p0; + const int32_t base_y = oh*args.s1 - args.p1; + + int32_t ky_start = 0; + if (base_y < 0) { + ky_start = (-base_y + args.d1 - 1)/args.d1; + } + int32_t ky_end = args.KH; + const int32_t y_max = args.IH - 1 - base_y; + if (y_max < 0) { + ky_end = ky_start; + } else if (base_y + (args.KH - 1)*args.d1 >= args.IH) { + ky_end = min(ky_end, y_max/args.d1 + 1); + } + + int32_t kx_start = 0; + if (base_x < 0) { + kx_start = (-base_x + args.d0 - 1)/args.d0; + } + int32_t kx_end = args.KW; + const int32_t x_max = args.IW - 1 - base_x; + if (x_max < 0) { + kx_end = kx_start; + } else if (base_x + (args.KW - 1)*args.d0 >= args.IW) { + kx_end = min(kx_end, x_max/args.d0 + 1); + } + + if (ky_start < ky_end && kx_start < kx_end) { + const uint64_t src_base_n = (uint64_t) n * args.nb13; + const uint64_t w_base_oc = (uint64_t) oc * args.nb03; + + for (int32_t ic = 0; ic < args.IC; ++ic) { + const uint64_t src_base_nc = src_base_n + (uint64_t) ic * args.nb12; + const uint64_t w_base_ocic = w_base_oc + (uint64_t) ic * args.nb02; + + for (int32_t ky = ky_start; ky < ky_end; ++ky) { + const int32_t iy = base_y + ky*args.d1; + const uint64_t src_base_row = src_base_nc + (uint64_t) iy * args.nb11; + const uint64_t w_base_row = w_base_ocic + (uint64_t) ky * args.nb01; + + for (int32_t kx = kx_start; kx < kx_end; ++kx) { + const int32_t ix = base_x + kx*args.d0; + const uint64_t src_offs = src_base_row + (uint64_t) ix * args.nb10; + const uint64_t w_offs = w_base_row + (uint64_t) kx * args.nb00; + + const float x = *(device const float *)(src + src_offs); + const float w = (float) (*(device const TK *)(weights + w_offs)); + + acc += x * w; + } + } + } + } + + const uint64_t dst_offs = + (uint64_t) n * args.nb3 + + (uint64_t) oc * args.nb2 + + (uint64_t) oh * args.nb1 + + (uint64_t) ow * args.nb0; + + *(device float *)(dst + dst_offs) = acc; + } +} + +template [[host_name("kernel_conv_2d_f32_f32")]] +kernel void kernel_conv_2d( + constant ggml_metal_kargs_conv_2d & args, + device const char * weights, + device const char * src, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]); + +template [[host_name("kernel_conv_2d_f16_f32")]] +kernel void kernel_conv_2d( + constant ggml_metal_kargs_conv_2d & args, + device const char * weights, + device const char * src, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]); + +static inline float conv_2d_dw_whcn( + constant ggml_metal_kargs_conv_2d_dw & args, + device const float * weights, + device const float * src, + uint idx) { + uint i0 = idx / args.dst_w; + uint dst_x = idx - i0 * args.dst_w; + uint i1 = i0 / args.dst_h; + uint dst_y = i0 - i1 * args.dst_h; + uint n = i1 / args.channels; + uint c = i1 - n * args.channels; + + uint src_i = n * args.channels * args.src_h * args.src_w + c * args.src_h * args.src_w; + uint knl_i = c * args.knl_h * args.knl_w; + + const int y_min = max(0, (args.pad_y - int(dst_y) * args.stride_y + args.dilation_y - 1) / args.dilation_y); + const int y_max = min(args.knl_h, (args.src_h + args.pad_y - int(dst_y) * args.stride_y + args.dilation_y - 1) / args.dilation_y); + const int x_min = max(0, (args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); + const int x_max = min(args.knl_w, (args.src_w + args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); + + float sum = 0.0f; + for (int knl_y = y_min; knl_y < y_max; ++knl_y) { + const int src_y = int(dst_y) * args.stride_y + knl_y * args.dilation_y - args.pad_y; + for (int knl_x = x_min; knl_x < x_max; ++knl_x) { + const int src_x = int(dst_x) * args.stride_x + knl_x * args.dilation_x - args.pad_x; + const float v = src[src_i + src_y * args.src_w + src_x]; + const float k = weights[knl_i + knl_y * args.knl_w + knl_x]; + sum = fma(v, k, sum); + } + } + return sum; +} + +static inline float conv_2d_dw_cwhn( + constant ggml_metal_kargs_conv_2d_dw & args, + device const float * weights, + device const float * src, + uint idx) { + uint i0 = idx / args.channels; + uint c = idx - i0 * args.channels; + uint i1 = i0 / args.dst_w; + uint dst_x = i0 - i1 * args.dst_w; + uint n = i1 / args.dst_h; + uint dst_y = i1 - n * args.dst_h; + + uint src_i = n * args.channels * args.src_h * args.src_w; + uint src_row = args.src_w * args.channels; + uint knl_row = args.knl_w * args.channels; + + const int y_min = max(0, (args.pad_y - int(dst_y) * args.stride_y + args.dilation_y - 1) / args.dilation_y); + const int y_max = min(args.knl_h, (args.src_h + args.pad_y - int(dst_y) * args.stride_y + args.dilation_y - 1) / args.dilation_y); + const int x_min = max(0, (args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); + const int x_max = min(args.knl_w, (args.src_w + args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); + + float sum = 0.0f; + for (int knl_y = y_min; knl_y < y_max; ++knl_y) { + const int src_y = int(dst_y) * args.stride_y + knl_y * args.dilation_y - args.pad_y; + for (int knl_x = x_min; knl_x < x_max; ++knl_x) { + const int src_x = int(dst_x) * args.stride_x + knl_x * args.dilation_x - args.pad_x; + const float v = src[src_i + src_y * src_row + src_x * args.channels + c]; + const float k = weights[knl_y * knl_row + knl_x * args.channels + c]; + sum = fma(v, k, sum); + } + } + return sum; +} + +kernel void kernel_conv_2d_dw_whcn( + constant ggml_metal_kargs_conv_2d_dw & args, + device const float * weights, + device const float * src, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + const uint threads_per_tg = ntg.x * ntg.y * ntg.z; + const uint tg_index = (tgpig.z * tgpg.y + tgpig.y) * tgpg.x + tgpig.x; + const uint local_thread = tpitg.z * (ntg.x * ntg.y) + tpitg.y * ntg.x + tpitg.x; + const uint thread_index = tg_index * threads_per_tg + local_thread; + const uint total_threads = threads_per_tg * tgpg.x * tgpg.y * tgpg.z; + + for (uint idx = thread_index; idx < (uint) args.ne; idx += total_threads) { + dst[idx] = conv_2d_dw_whcn(args, weights, src, idx); + } +} + +kernel void kernel_conv_2d_dw_1d_whcn( + constant ggml_metal_kargs_conv_2d_dw & args, + device const float * weights, + device const float * src, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]]) { + const uint dst_x0 = (tgpig.x * 256 + tpitg.x) * 4; + const uint c = tgpig.y; + if (dst_x0 >= (uint) args.dst_w || c >= (uint) args.channels) { + return; + } + + const uint src_base = c * args.src_w; + const uint knl_base = c * args.knl_w; + + for (uint o = 0; o < 4; ++o) { + const uint dst_x = dst_x0 + o; + if (dst_x >= (uint) args.dst_w) { + return; + } + + const int base_x = int(dst_x) * args.stride_x - args.pad_x; + float sum = 0.0f; + if (base_x >= 0 && base_x + (args.knl_w - 1) * args.dilation_x < args.src_w) { + int src_x = base_x; + for (int knl_x = 0; knl_x < args.knl_w; ++knl_x, src_x += args.dilation_x) { + sum = fma(src[src_base + uint(src_x)], weights[knl_base + uint(knl_x)], sum); + } + } else { + const int x_min = max(0, (args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); + const int x_max = min(args.knl_w, (args.src_w + args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); + for (int knl_x = x_min; knl_x < x_max; ++knl_x) { + const int src_x = base_x + knl_x * args.dilation_x; + sum = fma(src[src_base + uint(src_x)], weights[knl_base + uint(knl_x)], sum); + } + } + + dst[c * args.dst_w + dst_x] = sum; + } +} + +kernel void kernel_conv_2d_dw_cwhn( + constant ggml_metal_kargs_conv_2d_dw & args, + device const float * weights, + device const float * src, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + const uint threads_per_tg = ntg.x * ntg.y * ntg.z; + const uint tg_index = (tgpig.z * tgpg.y + tgpig.y) * tgpg.x + tgpig.x; + const uint local_thread = tpitg.z * (ntg.x * ntg.y) + tpitg.y * ntg.x + tpitg.x; + const uint thread_index = tg_index * threads_per_tg + local_thread; + const uint total_threads = threads_per_tg * tgpg.x * tgpg.y * tgpg.z; + + for (uint idx = thread_index; idx < (uint) args.ne; idx += total_threads) { + dst[idx] = conv_2d_dw_cwhn(args, weights, src, idx); + } +} + +kernel void kernel_conv_2d_dw_1d_cwhn( + constant ggml_metal_kargs_conv_2d_dw & args, + device const float * weights, + device const float * src, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]]) { + const uint dst_x0 = (tgpig.x * 256 + tpitg.x) * 4; + const uint c = tgpig.y; + if (dst_x0 >= (uint) args.dst_w || c >= (uint) args.channels) { + return; + } + + for (uint o = 0; o < 4; ++o) { + const uint dst_x = dst_x0 + o; + if (dst_x >= (uint) args.dst_w) { + return; + } + + const int base_x = int(dst_x) * args.stride_x - args.pad_x; + float sum = 0.0f; + if (base_x >= 0 && base_x + (args.knl_w - 1) * args.dilation_x < args.src_w) { + int src_x = base_x; + for (int knl_x = 0; knl_x < args.knl_w; ++knl_x, src_x += args.dilation_x) { + sum = fma( + src[uint(src_x) * args.channels + c], + weights[uint(knl_x) * args.channels + c], + sum); + } + } else { + const int x_min = max(0, (args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); + const int x_max = min(args.knl_w, (args.src_w + args.pad_x - int(dst_x) * args.stride_x + args.dilation_x - 1) / args.dilation_x); + for (int knl_x = x_min; knl_x < x_max; ++knl_x) { + const int src_x = base_x + knl_x * args.dilation_x; + sum = fma( + src[uint(src_x) * args.channels + c], + weights[uint(knl_x) * args.channels + c], + sum); + } + } + + dst[dst_x * args.channels + c] = sum; + } +} + +typedef void (conv_transpose_1d_t)( + constant ggml_metal_kargs_conv_transpose_1d & args, + device const float * src0, + device const float * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]]); + +template +kernel void kernel_conv_transpose_1d( + constant ggml_metal_kargs_conv_transpose_1d & args, + device const T * src0, + device const float * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg [[threads_per_threadgroup]]) { + + // One thread per output element, grouped ntg.x to a threadgroup so the + // whole SIMD width does useful work (the previous one-thread-per- + // threadgroup dispatch left 31/32 lanes idle). + const int32_t j = tgpig[0] * ntg[0] + tpitg[0]; + if (j >= args.OL) { + return; + } + + // For output position j on the time axis, only input positions + // i such that i*s0 <= j < i*s0 + K + // contribute -- i.e. i in [ceil((j - K + 1)/s0), floor(j/s0)] + // intersected with [0, IL-1]. That's at most ceil(K/s0) values + // (typically 2 for stride==K/2 transposed convs). + const int32_t s0 = args.s0; + const int32_t K = args.K; + const int32_t IL = args.IL; + + int32_t i_min; + { + int32_t a = j - K + 1; + i_min = a <= 0 ? 0 : (a + s0 - 1) / s0; // ceil(a/s0) for a>0 + } + int32_t i_max = j / s0; + if (i_max > IL - 1) i_max = IL - 1; + + float v = 0.0f; + if (i_min <= i_max) { + for (int32_t c = 0; c < args.IC; c++) { + const int32_t kernel_offset = c * args.OC * K + K * tgpig[1]; + const int32_t input_offset = c * IL; + + for (int32_t i = i_min; i <= i_max; i++) { + v += float(src0[kernel_offset + j - i * s0]) * src1[input_offset + i]; + } + } + } + + device float * dst_ptr = (device float *) (dst + j * args.nb0 + tgpig[1] * args.nb1); + + dst_ptr[0] = v; +} + +template [[host_name("kernel_conv_transpose_1d_f32_f32")]] +kernel void kernel_conv_transpose_1d( + constant ggml_metal_kargs_conv_transpose_1d & args, + device const float * src0, + device const float * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg [[threads_per_threadgroup]]); + +template [[host_name("kernel_conv_transpose_1d_f16_f32")]] +kernel void kernel_conv_transpose_1d( + constant ggml_metal_kargs_conv_transpose_1d & args, + device const half * src0, + device const float * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg [[threads_per_threadgroup]]); + + +template +kernel void kernel_col2im_1d( + constant ggml_metal_kargs_col2im_1d & args, + device const T * col, + device T * dst, + uint tgpig [[threadgroup_position_in_grid]], + uint tpitg [[thread_position_in_threadgroup]], + uint ntg [[threads_per_threadgroup]]) { + + const int idx = tgpig * ntg + tpitg; + if (idx >= args.T_out * args.OC) { + return; + } + + const int t_out = idx % args.T_out; + const int oc = idx / args.T_out; + const int t_abs = t_out + args.p0; + + int t_in_min = (t_abs - args.K + args.s0) / args.s0; + if (t_in_min < 0) { + t_in_min = 0; + } + int t_in_max = t_abs / args.s0; + if (t_in_max >= args.T_in) { + t_in_max = args.T_in - 1; + } + + float sum = 0.0f; + for (int t_in = t_in_min; t_in <= t_in_max; ++t_in) { + const int k = t_abs - t_in * args.s0; + sum += float(col[(oc * args.K + k) + t_in * args.K_OC]); + } + + dst[t_out + oc * args.T_out] = T(sum); +} + +template [[host_name("kernel_col2im_1d_f32")]] +kernel void kernel_col2im_1d( + constant ggml_metal_kargs_col2im_1d & args, + device const float * col, + device float * dst, + uint tgpig [[threadgroup_position_in_grid]], + uint tpitg [[thread_position_in_threadgroup]], + uint ntg [[threads_per_threadgroup]]); + +template [[host_name("kernel_col2im_1d_f16")]] +kernel void kernel_col2im_1d( + constant ggml_metal_kargs_col2im_1d & args, + device const half * col, + device half * dst, + uint tgpig [[threadgroup_position_in_grid]], + uint tpitg [[thread_position_in_threadgroup]], + uint ntg [[threads_per_threadgroup]]); + +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_col2im_1d_bf16")]] +kernel void kernel_col2im_1d( + constant ggml_metal_kargs_col2im_1d & args, + device const bfloat * col, + device bfloat * dst, + uint tgpig [[threadgroup_position_in_grid]], + uint tpitg [[thread_position_in_threadgroup]], + uint ntg [[threads_per_threadgroup]]); +#endif + + +typedef void (conv_transpose_2d_t)( + constant ggml_metal_kargs_conv_transpose_2d & args, + device const float * src0, + device const float * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]]); + +template +kernel void kernel_conv_transpose_2d( + constant ggml_metal_kargs_conv_transpose_2d & args, + device const T * src0, + device const float * src1, + device char * dst, + threadgroup float * shared_sum [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t out_x = tgpig[0]; + const int64_t out_y = tgpig[1]; + const int64_t out_c = tgpig[2]; + + const int64_t kw = tpitg[0]; + const int64_t kh = tpitg[1]; + + float v = 0.0f; + + for (int64_t in_c = 0; in_c < args.IC; in_c++) { + int64_t in_y = out_y - kh; + + if (in_y < 0 || in_y % args.s0) continue; + + in_y /= args.s0; + + if (in_y >= args.IH) continue; + + int64_t in_x = out_x - kw; + + if (in_x < 0 || in_x % args.s0) continue; + + in_x /= args.s0; + + if (in_x >= args.IW) continue; + + const int64_t input_idx = (args.IW * args.IH) * in_c + (args.IW) * in_y + in_x; + const int64_t kernel_idx = (args.KH * args.KW * args.OC) * in_c + (args.KH * args.KW) * out_c + (args.KW) * kh + kw; + + v += (float)src0[kernel_idx] * src1[input_idx]; + } + + const uint tid = tpitg.y * ntg.x + tpitg.x; + shared_sum[tid] = v; + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (tid == 0) { + float total = 0.0f; + const uint num_threads = ntg.x * ntg.y; + for (uint i = 0; i < num_threads; i++) { + total += shared_sum[i]; + } + + device float * dst_ptr = (device float *) (dst + out_x*args.nb0 + out_y * args.nb1 + out_c*args.nb2); + dst_ptr[0] = total; + } +} + +template [[host_name("kernel_conv_transpose_2d_f32_f32")]] +kernel void kernel_conv_transpose_2d( + constant ggml_metal_kargs_conv_transpose_2d & args, + device const float * src0, + device const float * src1, + device char * dst, + threadgroup float * shared_sum [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]); + +template [[host_name("kernel_conv_transpose_2d_f16_f32")]] +kernel void kernel_conv_transpose_2d( + constant ggml_metal_kargs_conv_transpose_2d & args, + device const half * src0, + device const float * src1, + device char * dst, + threadgroup float * shared_sum [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]); + +template +kernel void kernel_conv_transpose_2d_linear( + constant ggml_metal_kargs_conv_transpose_2d_linear & args, + device const T * src0, + device const float * src1, + device float * dst, + uint tgpig [[threadgroup_position_in_grid]], + uint tpitg [[thread_position_in_threadgroup]], + uint ntg [[threads_per_threadgroup]]) { + + const int global_idx = tgpig * ntg + tpitg; + if (global_idx >= args.total) { + return; + } + + const int out_x = global_idx % args.OW; + const int out_y = (global_idx / args.OW) % args.OH; + const int out_c = (global_idx / (args.OW * args.OH)) % args.OC; + const int out_n = global_idx / (args.OW * args.OH * args.OC); + + float acc = 0.0f; + + if (args.IH == 1 && args.OH == 1 && args.KH == 1) { + for (int in_c = 0; in_c < args.IC; ++in_c) { + const int input_base = (args.IW * args.IC) * out_n + args.IW * in_c; + const int kernel_base = (args.KW * args.OC) * in_c + args.KW * out_c; + for (int kw = 0; kw < args.KW; ++kw) { + int in_x = out_x - kw; + if (in_x < 0 || in_x % args.s0) { + continue; + } + in_x /= args.s0; + if (in_x >= args.IW) { + continue; + } + + acc += src1[input_base + in_x] * float(src0[kernel_base + kw]); + } + } + + dst[global_idx] = acc; + return; + } + + for (int in_c = 0; in_c < args.IC; ++in_c) { + for (int kh = 0; kh < args.KH; ++kh) { + int in_y = out_y - kh; + if (in_y < 0 || in_y % args.s0) { + continue; + } + in_y /= args.s0; + if (in_y >= args.IH) { + continue; + } + + for (int kw = 0; kw < args.KW; ++kw) { + int in_x = out_x - kw; + if (in_x < 0 || in_x % args.s0) { + continue; + } + in_x /= args.s0; + if (in_x >= args.IW) { + continue; + } + + const int input_idx = + (args.IW * args.IH * args.IC) * out_n + (args.IW * args.IH) * in_c + (args.IW) * in_y + in_x; + const int kernel_idx = + (args.KH * args.KW * args.OC) * in_c + (args.KH * args.KW) * out_c + (args.KW) * kh + kw; + + acc += src1[input_idx] * float(src0[kernel_idx]); + } + } + } + + dst[global_idx] = acc; +} + +template [[host_name("kernel_conv_transpose_2d_linear_f32_f32")]] +kernel void kernel_conv_transpose_2d_linear( + constant ggml_metal_kargs_conv_transpose_2d_linear & args, + device const float * src0, + device const float * src1, + device float * dst, + uint tgpig [[threadgroup_position_in_grid]], + uint tpitg [[thread_position_in_threadgroup]], + uint ntg [[threads_per_threadgroup]]); + +template [[host_name("kernel_conv_transpose_2d_linear_f16_f32")]] +kernel void kernel_conv_transpose_2d_linear( + constant ggml_metal_kargs_conv_transpose_2d_linear & args, + device const half * src0, + device const float * src1, + device float * dst, + uint tgpig [[threadgroup_position_in_grid]], + uint tpitg [[thread_position_in_threadgroup]], + uint ntg [[threads_per_threadgroup]]); + +constant bool FC_upscale_aa [[function_constant(FC_UPSCALE + 0)]]; + +kernel void kernel_upscale_nearest_f32( + constant ggml_metal_kargs_upscale & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + const int64_t i03 = i3/args.sf3; + const int64_t i02 = i2/args.sf2; + const int64_t i01 = i1/args.sf1; + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const int64_t i00 = i0/args.sf0; + + device const float * src0_ptr = (device const float *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + i00*args.nb00); + device float * dst_ptr = (device float *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + dst_ptr[0] = src0_ptr[0]; + } +} + +static inline float bilinear_tri(float x) { + return MAX(0.0f, 1.0f - fabs(x)); +} + +kernel void kernel_upscale_bilinear_f32( + constant ggml_metal_kargs_upscale & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + const int64_t i03 = i3 / args.sf3; + const int64_t i02 = i2 / args.sf2; + + const float f01 = ((float)i1 + args.poffs) / args.sf1 - args.poffs; + const int64_t i01 = MAX(0, MIN(args.ne01 - 1, (int64_t)floor(f01))); + const int64_t i01p = MAX(0, MIN(args.ne01 - 1, i01 + 1)); + const float fd1 = MAX(0.0f, MIN(1.0f, f01 - (float)i01)); + + src0 += i03*args.nb03 + i02*args.nb02; + + device float * dst_ptr = (device float *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1); + + if (FC_upscale_aa) { + const float support0 = MAX(1.0f, 1.0f / args.sf0); + const float invscale0 = 1.0f / support0; + const float support1 = MAX(1.0f, 1.0f / args.sf1); + const float invscale1 = 1.0f / support1; + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const float f00 = ((float)i0 + args.poffs) / args.sf0 - args.poffs; + + int64_t x_min = MAX((int64_t)0, (int64_t)floor(f00 - support0 + args.poffs)); + int64_t x_max = MIN(args.ne00, (int64_t)ceil (f00 + support0 + args.poffs)); + + int64_t y_min = MAX((int64_t)0, (int64_t)floor(f01 - support1 + args.poffs)); + int64_t y_max = MIN(args.ne01, (int64_t)ceil (f01 + support1 + args.poffs)); + + float sum = 0.0f; + float wsum = 0.0f; + + for (int64_t sy = y_min; sy < y_max; ++sy) { + const float wy = MAX(0.0f, 1.0f - fabs((float)sy - f01) * invscale1); + for (int64_t sx = x_min; sx < x_max; ++sx) { + const float wx = MAX(0.0f, 1.0f - fabs((float)sx - f00) * invscale0); + const float w = wx * wy; + const device const float * src_ptr = (device const float *)(src0 + sy*args.nb01 + sx*args.nb00); + sum += (*src_ptr) * w; + wsum += w; + } + } + + const float v = (wsum > 0.0f) ? (sum / wsum) : 0.0f; + dst_ptr[i0] = v; + } + } else { + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const float f00 = ((float)i0 + args.poffs) / args.sf0 - args.poffs; + const int64_t i00 = MAX(0, MIN(args.ne00 - 1, (int64_t)floor(f00))); + const int64_t i00p = MAX(0, MIN(args.ne00 - 1, i00 + 1)); + const float fd0 = MAX(0.0f, MIN(1.0f, f00 - (float)i00)); + + device const float * src00 = (device const float *)(src0 + i01*args.nb01 + i00*args.nb00); + device const float * src10 = (device const float *)(src0 + i01*args.nb01 + i00p*args.nb00); + device const float * src01 = (device const float *)(src0 + i01p*args.nb01 + i00*args.nb00); + device const float * src11 = (device const float *)(src0 + i01p*args.nb01 + i00p*args.nb00); + + const float v = + (*src00) * (1.0f - fd0) * (1.0f - fd1) + + (*src10) * fd0 * (1.0f - fd1) + + (*src01) * (1.0f - fd0) * fd1 + + (*src11) * fd0 * fd1; + + dst_ptr[i0] = v; + } + } +} + +template +kernel void kernel_conv_3d( + constant ggml_metal_kargs_conv_3d & args, + device const char * src0, // Weights [IC * OC, KD, KH, KW] + device const char * src1, // Inputs [IC * N, ID, IH, IW] + device char * dst, // Outputs [OC * N, OD, OH, OW] + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]]) { + + // 1. Un-flatten the spatial dimension from Grid X + int64_t spatial_idx = tgpig.x * 32 + tpitg.x; + + if (spatial_idx >= args.OW * args.OH * args.OD) { + return; // Thread falls outside the spatial volume + } + + int64_t od = spatial_idx / (args.OW * args.OH); + int64_t oh = (spatial_idx / args.OW) % args.OH; + int64_t ow = spatial_idx % args.OW; + + // 2. Map Y to Channels, Z to Batch + int64_t oc = tgpig.y; + int64_t batch_idx = tgpig.z; + + // 3. Calculate anchor coordinates in the Input volume + int64_t i_w_base = ow * args.s0 - args.p0; + int64_t i_h_base = oh * args.s1 - args.p1; + int64_t i_d_base = od * args.s2 - args.p2; + + float sum = 0.0f; + + // 4. Gather Loop (Iterate over Input Channels -> Depth -> Height -> Width) + for (int64_t ic = 0; ic < args.IC; ++ic) { + + // ggml packs batch and channel together in the 4th dimension + int64_t src_cn_idx = batch_idx * args.IC + ic; + int64_t w_cn_idx = oc * args.IC + ic; + + for (int64_t kz = 0; kz < args.KD; ++kz) { + int64_t id = i_d_base + kz * args.d2; + if (id < 0 || id >= args.ID) continue; // Boundary check (Padding) + + for (int64_t ky = 0; ky < args.KH; ++ky) { + int64_t ih = i_h_base + ky * args.d1; + if (ih < 0 || ih >= args.IH) continue; + + for (int64_t kx = 0; kx < args.KW; ++kx) { + int64_t iw = i_w_base + kx * args.d0; + if (iw < 0 || iw >= args.IW) continue; + + // Convert multi-dimensional coordinates to flat byte offsets + int64_t w_idx = kx*args.nb00 + ky*args.nb01 + kz*args.nb02 + w_cn_idx*args.nb03; + int64_t i_idx = iw*args.nb10 + ih*args.nb11 + id*args.nb12 + src_cn_idx*args.nb13; + + // Dereference memory and cast weights to f32 if they were f16 + float w_val = (float)*(device const T*)((device const char*)src0 + w_idx); + float i_val = *(device const float*)((device const char*)src1 + i_idx); + + sum += w_val * i_val; + } + } + } + } + + // 5. Write the accumulated value out to RAM + int64_t dst_cn_idx = batch_idx * args.OC + oc; + int64_t d_idx = ow*args.nb0 + oh*args.nb1 + od*args.nb2 + dst_cn_idx*args.nb3; + + *(device float*)(dst + d_idx) = sum; +} + +// Explicit instantiations so the JIT compiler can find them by name +template [[host_name("kernel_conv_3d_f32_f32")]] +kernel void kernel_conv_3d( + constant ggml_metal_kargs_conv_3d & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]]); + +// Explicit instantiation for f16 weights +template [[host_name("kernel_conv_3d_f16_f32")]] +kernel void kernel_conv_3d( + constant ggml_metal_kargs_conv_3d & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]]); + + +static inline float bicubic_weight1(float x) { + const float a = -0.75f; + return ((a + 2) * x - (a + 3)) * x * x + 1; +} + +static inline float bicubic_weight2(float x) { + const float a = -0.75f; + return ((a * x - 5 * a) * x + 8 * a) * x - 4 * a; +} + +kernel void kernel_upscale_bicubic_f32( + constant ggml_metal_kargs_upscale & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + const int64_t i03 = i3 / args.sf3; + const int64_t i02 = i2 / args.sf2; + + const float f01 = ((float)i1 + args.poffs) / args.sf1 - args.poffs; + const int64_t i01 = (int64_t)floor(f01); + const float fd1 = f01 - (float)i01; + + const float w_y0 = bicubic_weight2(fd1 + 1.0f); + const float w_y1 = bicubic_weight1(fd1); + const float w_y2 = bicubic_weight1(1.0f - fd1); + const float w_y3 = bicubic_weight2(2.0f - fd1); + + const device const char * src_slice = src0 + i03 * args.nb03 + i02 * args.nb02; + + device float * dst_ptr = (device float *)(dst + i3 * args.nb3 + i2 * args.nb2 + i1 * args.nb1); + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const float f00 = ((float)i0 + args.poffs) / args.sf0 - args.poffs; + const int64_t i00 = (int64_t)floor(f00); + const float fd0 = f00 - (float)i00; + + const float w_x0 = bicubic_weight2(fd0 + 1.0f); + const float w_x1 = bicubic_weight1(fd0); + const float w_x2 = bicubic_weight1(1.0f - fd0); + const float w_x3 = bicubic_weight2(2.0f - fd0); + + float sum = 0.0f; + + for (int dy = -1; dy <= 2; ++dy) { + const int64_t iy = MAX(0, MIN(args.ne01 - 1, i01 + dy)); + const float wy = (dy == -1) ? w_y0 : (dy == 0) ? w_y1 : (dy == 1) ? w_y2 : w_y3; + + for (int dx = -1; dx <= 2; ++dx) { + const int64_t ix = MAX(0, MIN(args.ne00 - 1, i00 + dx)); + const float wx = (dx == -1) ? w_x0 : (dx == 0) ? w_x1 : (dx == 1) ? w_x2 : w_x3; + + const device const float * src_ptr = (device const float *)(src_slice + iy * args.nb01 + ix * args.nb00); + sum += (*src_ptr) * wx * wy; + } + } + + dst_ptr[i0] = sum; + } +} + +kernel void kernel_roll_f32( + constant ggml_metal_kargs_roll & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + device const float * src0_ptr = (device const float *) src0; + device float * dst_ptr = (device float *) dst; + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + // apply shifts and wrap around + int64_t i00 = i0 - args.s0; + int64_t i01 = i1 - args.s1; + int64_t i02 = i2 - args.s2; + int64_t i03 = i3 - args.s3; + + if (i00 < 0) { i00 += args.ne00; } else if (i00 >= args.ne00) { i00 -= args.ne00; } + if (i01 < 0) { i01 += args.ne01; } else if (i01 >= args.ne01) { i01 -= args.ne01; } + if (i02 < 0) { i02 += args.ne02; } else if (i02 >= args.ne02) { i02 -= args.ne02; } + if (i03 < 0) { i03 += args.ne03; } else if (i03 >= args.ne03) { i03 -= args.ne03; } + + int64_t src_idx = i03*args.ne02*args.ne01*args.ne00 + i02*args.ne01*args.ne00 + i01*args.ne00 + i00; + int64_t dst_idx = i3 *args.ne2 *args.ne1 *args.ne0 + i2 *args.ne1 *args.ne0 + i1 *args.ne0 + i0; + + dst_ptr[dst_idx] = src0_ptr[src_idx]; + } +} + +kernel void kernel_pad_f32( + constant ggml_metal_kargs_pad & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + const int64_t i03 = i3; + const int64_t i02 = i2; + const int64_t i01 = i1; + + device const float * src0_ptr = (device const float *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); + device float * dst_ptr = (device float *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1); + + if (i1 < args.ne01 && i2 < args.ne02 && i3 < args.ne03) { + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + if (i0 < args.ne00) { + dst_ptr[i0] = src0_ptr[i0]; + } else { + dst_ptr[i0] = 0.0f; + } + } + + return; + } + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + dst_ptr[i0] = 0.0f; + } +} + +kernel void kernel_pad_left_f32( + constant ggml_metal_kargs_pad_left & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + device char * dst_row = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1; + const int64_t i03 = i3 - args.lp3; + const int64_t i02 = i2 - args.lp2; + const int64_t i01 = i1 - args.lp1; + const bool in_src_row = i01 >= 0 && i01 < args.ne01 && + i02 >= 0 && i02 < args.ne02 && + i03 >= 0 && i03 < args.ne03; + + if (in_src_row) { + device const char * src0_row = src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01; + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const int64_t i00 = i0 - args.lp0; + device float * dst_ptr = (device float *) (dst_row + i0*args.nb0); + + if (i00 >= 0 && i00 < args.ne00) { + device const float * src0_ptr = (device const float *) (src0_row + i00*args.nb00); + *dst_ptr = *src0_ptr; + } else { + *dst_ptr = 0.0f; + } + } + + return; + } + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + device float * dst_ptr = (device float *) (dst_row + i0*args.nb0); + *dst_ptr = 0.0f; + } +} + +kernel void kernel_pad_left_f16( + constant ggml_metal_kargs_pad_left & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + device char * dst_row = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1; + const int64_t i03 = i3 - args.lp3; + const int64_t i02 = i2 - args.lp2; + const int64_t i01 = i1 - args.lp1; + const bool in_src_row = i01 >= 0 && i01 < args.ne01 && + i02 >= 0 && i02 < args.ne02 && + i03 >= 0 && i03 < args.ne03; + + if (in_src_row) { + device const char * src0_row = src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01; + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const int64_t i00 = i0 - args.lp0; + device half * dst_ptr = (device half *) (dst_row + i0*args.nb0); + + if (i00 >= 0 && i00 < args.ne00) { + device const half * src0_ptr = (device const half *) (src0_row + i00*args.nb00); + *dst_ptr = *src0_ptr; + } else { + *dst_ptr = 0.0h; + } + } + + return; + } + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + device half * dst_ptr = (device half *) (dst_row + i0*args.nb0); + *dst_ptr = 0.0h; + } +} + +kernel void kernel_pad_left_i32( + constant ggml_metal_kargs_pad_left & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + device char * dst_row = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1; + const int64_t i03 = i3 - args.lp3; + const int64_t i02 = i2 - args.lp2; + const int64_t i01 = i1 - args.lp1; + const bool in_src_row = i01 >= 0 && i01 < args.ne01 && + i02 >= 0 && i02 < args.ne02 && + i03 >= 0 && i03 < args.ne03; + + if (in_src_row) { + device const char * src0_row = src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01; + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + const int64_t i00 = i0 - args.lp0; + device int * dst_ptr = (device int *) (dst_row + i0*args.nb0); + + if (i00 >= 0 && i00 < args.ne00) { + device const int * src0_ptr = (device const int *) (src0_row + i00*args.nb00); + *dst_ptr = *src0_ptr; + } else { + *dst_ptr = 0; + } + } + + return; + } + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + device int * dst_ptr = (device int *) (dst_row + i0*args.nb0); + *dst_ptr = 0; + } +} + +kernel void kernel_pad_reflect_1d_f32( + constant ggml_metal_kargs_pad_reflect_1d & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tgpg[[threadgroups_per_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + const int64_t i3 = tgpig.z; + const int64_t i2 = tgpig.y; + const int64_t i1 = tgpig.x; + + const int64_t i03 = i3; + const int64_t i02 = i2; + const int64_t i01 = i1; + + device const float * src0_ptr = (device const float *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); + device float * dst_ptr = (device float *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1); + + if (i1 < args.ne01 && i2 < args.ne02 && i3 < args.ne03) { + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + if (i0 < args.p0) { + dst_ptr[i0] = src0_ptr[args.p0 - i0]; + } else if (i0 < args.ne0 - args.p1) { + dst_ptr[i0] = src0_ptr[i0 - args.p0]; + } else { + dst_ptr[i0] = src0_ptr[(args.ne0 - args.p1 - args.p0) - (args.p1 + 1 - (args.ne0 - i0)) - 1]; + } + } + } +} + +kernel void kernel_arange_f32( + constant ggml_metal_kargs_arange & args, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + device float * dst_ptr = (device float *) dst; + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + dst_ptr[i0] = args.start + args.step * i0; + } +} + +kernel void kernel_timestep_embedding_f32( + constant ggml_metal_kargs_timestep_embedding & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint3 tpitg[[thread_position_in_threadgroup]], + uint3 ntg[[threads_per_threadgroup]]) { + + int i = tgpig.x; + device float * embed_data = (device float *)(dst + i*args.nb1); + + int half_ = args.dim / 2; + for (int j = tpitg.x; j < half_; j += ntg.x) { + float timestep = ((device float *)src0)[i]; + float freq = (float)exp(-log((float)args.max_period) * j / half_); + float arg = timestep * freq; + embed_data[j ] = cos(arg); + embed_data[j + half_] = sin(arg); + } + + if (args.dim % 2 != 0 && tpitg.x == 0) { + embed_data[2 * half_] = 0.f; + } +} + +// bitonic sort implementation following the CUDA kernels as reference +typedef void (argsort_t)( + constant ggml_metal_kargs_argsort & args, + device const char * src0, + device int32_t * dst, + threadgroup int32_t * shmem_i32 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]); + +template +kernel void kernel_argsort_f32_i32( + constant ggml_metal_kargs_argsort & args, + device const char * src0, + device int32_t * dst, + threadgroup int32_t * shmem_i32 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + // bitonic sort + const int col = tpitg[0]; + const int ib = tgpig[0] / args.ne01; + + const int i00 = ib*ntg.x; + const int i01 = tgpig[0] % args.ne01; + const int i02 = tgpig[1]; + const int i03 = tgpig[2]; + + device const float * src0_row = (device const float *) (src0 + args.nb01*i01 + args.nb02*i02 + args.nb03*i03); + + // initialize indices + shmem_i32[col] = i00 + col; + + threadgroup_barrier(mem_flags::mem_threadgroup); + + for (int k = 2; k <= ntg.x; k *= 2) { + for (int j = k / 2; j > 0; j /= 2) { + int ixj = col ^ j; + if (ixj > col) { + if ((col & k) == 0) { + if (shmem_i32[col] >= args.ne00 || + (shmem_i32[ixj] < args.ne00 && (order == GGML_SORT_ORDER_ASC ? + src0_row[shmem_i32[col]] > src0_row[shmem_i32[ixj]] : + src0_row[shmem_i32[col]] < src0_row[shmem_i32[ixj]])) + ) { + SWAP(shmem_i32[col], shmem_i32[ixj]); + } + } else { + if (shmem_i32[ixj] >= args.ne00 || + (shmem_i32[col] < args.ne00 && (order == GGML_SORT_ORDER_ASC ? + src0_row[shmem_i32[col]] < src0_row[shmem_i32[ixj]] : + src0_row[shmem_i32[col]] > src0_row[shmem_i32[ixj]])) + ) { + SWAP(shmem_i32[col], shmem_i32[ixj]); + } + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + } + } + + const int64_t i0 = ib*args.top_k; + + // copy the result to dst without the padding + if (i0 + col < args.ne0 && col < args.top_k) { + dst += i0 + args.ne0*i01 + args.ne0*args.ne1*i02 + args.ne0*args.ne1*args.ne2*i03; + + dst[col] = shmem_i32[col]; + } +} + +template [[host_name("kernel_argsort_f32_i32_asc")]] kernel argsort_t kernel_argsort_f32_i32; +template [[host_name("kernel_argsort_f32_i32_desc")]] kernel argsort_t kernel_argsort_f32_i32; + +typedef void (argsort_merge_t)( + constant ggml_metal_kargs_argsort_merge & args, + device const char * src0, + device const int32_t * tmp, + device int32_t * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]); + +template +kernel void kernel_argsort_merge_f32_i32( + constant ggml_metal_kargs_argsort_merge & args, + device const char * src0, + device const int32_t * tmp, + device int32_t * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + + const int im = tgpig[0] / args.ne01; + const int i01 = tgpig[0] % args.ne01; + const int i02 = tgpig[1]; + const int i03 = tgpig[2]; + + const int start = im * (2 * args.len); + + const int len0 = MIN(args.len, MAX(0, args.ne0 - (int)(start))); + const int len1 = MIN(args.len, MAX(0, args.ne0 - (int)(start + args.len))); + + const int total = len0 + len1; + + device const int32_t * tmp0 = tmp + start + + i01*args.ne0 + + i02*args.ne0*args.ne01 + + i03*args.ne0*args.ne01*args.ne02; + + device const int32_t * tmp1 = tmp0 + args.len; + + dst += start + + i01*args.top_k + + i02*args.top_k*args.ne01 + + i03*args.top_k*args.ne01*args.ne02; + + device const float * src0_row = (device const float *)(src0 + + args.nb01*i01 + + args.nb02*i02 + + args.nb03*i03); + + if (total == 0) { + return; + } + + const int chunk = (total + ntg.x - 1) / ntg.x; + + const int k0 = tpitg.x * chunk; + const int k1 = MIN(MIN(k0 + chunk, total), args.top_k); + + if (k0 >= args.top_k) { + return; + } + + if (k0 >= total) { + return; + } + + int low = k0 > len1 ? k0 - len1 : 0; + int high = MIN(k0, len0); + + // binary-search partition (i, j) such that i + j = k + while (low < high) { + const int mid = (low + high) >> 1; + + const int32_t idx0 = tmp0[mid]; + const int32_t idx1 = tmp1[k0 - mid - 1]; + + const float val0 = src0_row[idx0]; + const float val1 = src0_row[idx1]; + + bool take_left; + if (order == GGML_SORT_ORDER_ASC) { + take_left = (val0 <= val1); + } else { + take_left = (val0 >= val1); + } + + if (take_left) { + low = mid + 1; + } else { + high = mid; + } + } + + int i = low; + int j = k0 - i; + + // keep the merge fronts into registers + int32_t idx0 = 0; + float val0 = 0.0f; + if (i < len0) { + idx0 = tmp0[i]; + val0 = src0_row[idx0]; + } + + int32_t idx1 = 0; + float val1 = 0.0f; + if (j < len1) { + idx1 = tmp1[j]; + val1 = src0_row[idx1]; + } + + for (int k = k0; k < k1; ++k) { + int32_t out_idx; + + if (i >= len0) { + while (k < k1) { + dst[k++] = tmp1[j++]; + } + break; + } else if (j >= len1) { + while (k < k1) { + dst[k++] = tmp0[i++]; + } + break; + } else { + bool take_left; + + if (order == GGML_SORT_ORDER_ASC) { + take_left = (val0 <= val1); + } else { + take_left = (val0 >= val1); + } + + if (take_left) { + out_idx = idx0; + ++i; + if (i < len0) { + idx0 = tmp0[i]; + val0 = src0_row[idx0]; + } + } else { + out_idx = idx1; + ++j; + if (j < len1) { + idx1 = tmp1[j]; + val1 = src0_row[idx1]; + } + } + } + + dst[k] = out_idx; + } +} + +template [[host_name("kernel_argsort_merge_f32_i32_asc")]] kernel argsort_merge_t kernel_argsort_merge_f32_i32; +template [[host_name("kernel_argsort_merge_f32_i32_desc")]] kernel argsort_merge_t kernel_argsort_merge_f32_i32; + +constant bool FC_flash_attn_ext_pad_has_mask [[function_constant(FC_FLASH_ATTN_EXT_PAD + 0)]]; + +constant int32_t FC_flash_attn_ext_pad_ncpsg [[function_constant(FC_FLASH_ATTN_EXT_PAD + 25)]]; + +// pad the last chunk of C elements of k and v into a an extra pad buffer +kernel void kernel_flash_attn_ext_pad( + constant ggml_metal_kargs_flash_attn_ext_pad & args, + device const char * k, + device const char * v, + device const char * mask, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int32_t C = FC_flash_attn_ext_pad_ncpsg; + + device char * k_pad = dst; + device char * v_pad = k_pad + args.nb11*C*args.ne_12_2*args.ne_12_3; + device char * mask_pad = v_pad + args.nb21*C*args.ne_12_2*args.ne_12_3; + + const int32_t icp = args.ne11 % C; + const int32_t ic0 = args.ne11 - icp; + + const int32_t i1 = tgpig[0]; + const int32_t i2 = tgpig[1]; + const int32_t i3 = tgpig[2]; + + if (i2 < args.ne_12_2 && i3 < args.ne_12_3) { + device const char * k_src = k + args.nb11*(ic0 + i1) + args.nb12*i2 + args.nb13*i3; + device const char * v_src = v + args.nb21*(ic0 + i1) + args.nb22*i2 + args.nb23*i3; + + device char * k_dst = k_pad + args.nb11*i1 + args.nb11*C*i2 + args.nb11*C*args.ne_12_2*i3; + device char * v_dst = v_pad + args.nb21*i1 + args.nb21*C*i2 + args.nb21*C*args.ne_12_2*i3; + + if (i1 >= icp) { + // here it is not important the exact value that will be used as we rely on masking out the scores in the attention + for (uint64_t i = tiitg; i < args.nb11; i += ntg.x) { + k_dst[i] = 0; + } + for (uint64_t i = tiitg; i < args.nb21; i += ntg.x) { + v_dst[i] = 0; + } + } else { + for (uint64_t i = tiitg; i < args.nb11; i += ntg.x) { + k_dst[i] = k_src[i]; + } + for (uint64_t i = tiitg; i < args.nb21; i += ntg.x) { + v_dst[i] = v_src[i]; + } + } + } + + if (FC_flash_attn_ext_pad_has_mask) { + if (i2 < args.ne32 && i3 < args.ne33) { + for (int ib = i1; ib < args.ne31; ib += C) { + device const half * mask_src = (device const half *)(mask + args.nb31*ib + args.nb32*i2 + args.nb33*i3) + ic0; + device half * mask_dst = (device half *)(mask_pad) + C*ib + C*args.ne31*i2 + C*args.ne31*args.ne32*i3; + + for (int i = tiitg; i < C; i += ntg.x) { + if (i >= icp) { + mask_dst[i] = -MAXHALF; + } else { + mask_dst[i] = mask_src[i]; + } + } + } + } + } +} + +constant int32_t FC_flash_attn_ext_blk_nqptg [[function_constant(FC_FLASH_ATTN_EXT_BLK + 24)]]; +constant int32_t FC_flash_attn_ext_blk_ncpsg [[function_constant(FC_FLASH_ATTN_EXT_BLK + 25)]]; + +// scan the blocks of the mask that are not masked +// 0 - masked (i.e. full of -INF, skip) +// 1 - not masked (i.e. at least one element of the mask is not -INF) +// 2 - all zero +kernel void kernel_flash_attn_ext_blk( + constant ggml_metal_kargs_flash_attn_ext_blk & args, + device const char * mask, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]]) { + // block size C x Q + const int32_t Q = FC_flash_attn_ext_blk_nqptg; + const int32_t C = FC_flash_attn_ext_blk_ncpsg; + + constexpr short NW = N_SIMDWIDTH; + + const int32_t i3 = tgpig[2]/args.ne32; + const int32_t i2 = tgpig[2]%args.ne32; + const int32_t i1 = tgpig[1]; + const int32_t i0 = tgpig[0]; + + char res = i0*C + C > args.ne30 ? 1 : 0; + + device const half * mask_src = (device const half *) (mask + (i1*Q)*args.nb31 + i2*args.nb32 + i3*args.nb33) + i0*C + tiisg; + + // detailed check of the elements of the block + if ((C > NW || Q > 1) && res == 0) { + half mmin = MAXHALF; + half mmax = -MAXHALF; + + FOR_UNROLL (short j = 0; j < Q; ++j) { + FOR_UNROLL (short ii = 0; ii < C/NW; ++ii) { + mmin = min(mmin, mask_src[ii*NW]); + mmax = max(mmax, mask_src[ii*NW]); + } + + mask_src += args.nb31/2; + } + + mmin = simd_min(mmin); + mmax = simd_max(mmax); + + if (mmax > -MAXHALF) { + if (mmin == 0.0 && mmax == 0.0) { + res = 2; + } else { + res = 1; + } + } + } + + const int32_t nblk1 = ((args.ne01 + Q - 1)/Q); + const int32_t nblk0 = ((args.ne30 + C - 1)/C); + + if (tiisg == 0) { + dst[((i3*args.ne32 + i2)*nblk1 + i1)*nblk0 + i0] = res; + } +} + +constant bool FC_flash_attn_ext_has_mask [[function_constant(FC_FLASH_ATTN_EXT + 0)]]; +constant bool FC_flash_attn_ext_has_sinks [[function_constant(FC_FLASH_ATTN_EXT + 1)]]; +constant bool FC_flash_attn_ext_has_bias [[function_constant(FC_FLASH_ATTN_EXT + 2)]]; +constant bool FC_flash_attn_ext_has_scap [[function_constant(FC_FLASH_ATTN_EXT + 3)]]; +constant bool FC_flash_attn_ext_has_kvpad [[function_constant(FC_FLASH_ATTN_EXT + 4)]]; + +constant bool FC_flash_attn_ext_bc_mask [[function_constant(FC_FLASH_ATTN_EXT + 10)]]; + +//constant float FC_flash_attn_ext_scale [[function_constant(FC_FLASH_ATTN_EXT + 10)]]; +//constant float FC_flash_attn_ext_max_bias [[function_constant(FC_FLASH_ATTN_EXT + 11)]]; +//constant float FC_flash_attn_ext_logit_softcap [[function_constant(FC_FLASH_ATTN_EXT + 12)]]; + +constant int32_t FC_flash_attn_ext_ns10 [[function_constant(FC_FLASH_ATTN_EXT + 20)]]; +constant int32_t FC_flash_attn_ext_ns20 [[function_constant(FC_FLASH_ATTN_EXT + 21)]]; +constant int32_t FC_flash_attn_ext_nsg [[function_constant(FC_FLASH_ATTN_EXT + 22)]]; + +// ref: https://arxiv.org/pdf/2307.08691.pdf +template< + typename q_t, // query types in shared memory + typename q4_t, + typename q8x8_t, + typename k_t, // key types in shared memory + typename k4x4_t, + typename k8x8_t, + typename v_t, // value types in shared memory + typename v4x4_t, + typename v8x8_t, + typename qk_t, // Q*K types + typename qk8x8_t, + typename s_t, // soft-max types + typename s2_t, + typename s8x8_t, + typename o_t, // attention accumulation types + typename o4_t, + typename o8x8_t, + typename kd4x4_t, // key type in device memory + short nl_k, + void (*deq_k)(device const kd4x4_t *, short, thread k4x4_t &), + typename vd4x4_t, // value type in device memory + short nl_v, + void (*deq_v)(device const vd4x4_t *, short, thread v4x4_t &), + short DK, // K head size + short DV, // V head size + short Q, // queries per threadgroup + short C, // cache items per threadgroup + short NSG> // number of simd groups +void kernel_flash_attn_ext_impl( + constant ggml_metal_kargs_flash_attn_ext & args, + device const char * q, + device const char * k, + device const char * v, + device const char * mask, + device const char * sinks, + device const char * pad, + device const char * blk, + device char * dst, + threadgroup half * shmem_f16, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const ushort iq3 = tgpig[2]; + const ushort iq2 = tgpig[1]; + const ushort iq1 = tgpig[0]*Q; + +#define NS10 (FC_flash_attn_ext_ns10) +#define NS20 (FC_flash_attn_ext_ns20) + + // note: I had some concerns that using this instead of the ugly macros above was affecting performance + // need to re-check carefully and if no regressions are observerd - remove the macros + // the concerns is that maybe using const variables requires extra registers? but not sure if the compiler + // is clever enough to avoid this. unfortunately, using constexpr is not possible with FC + //const short NS10 = FC_flash_attn_ext_ns10; + //const short NS20 = FC_flash_attn_ext_ns20; + + constexpr short KV = 8; + + constexpr short DK4 = DK/4; + constexpr short DK8 = DK/8; + constexpr short DK16 = DK/16; + constexpr short DV4 = DV/4; + //constexpr short DV8 = DV/8; + constexpr short DV16 = DV/16; + + constexpr short PV = PAD2(DV, 64); + constexpr short PV4 = PV/4; + constexpr short PV8 = PV/8; + //constexpr short PV16 = PV/16; + + constexpr short NW = N_SIMDWIDTH; + constexpr short NQ = Q/NSG; + constexpr short SH = 2*C; // shared memory per simdgroup (s_t == float) + + constexpr short TS = 2*SH; + constexpr short T = DK + 2*PV; // shared memory size per query in (half) + + threadgroup q_t * sq = (threadgroup q_t *) (shmem_f16 + 0*T); // holds the query data + threadgroup q4_t * sq4 = (threadgroup q4_t *) (shmem_f16 + 0*T); // same as above but in q4_t + threadgroup o_t * so = (threadgroup o_t *) (shmem_f16 + 0*T + Q*DK); // the result for all queries in 8x8 matrices (the O matrix from the paper) + threadgroup o4_t * so4 = (threadgroup o4_t *) (shmem_f16 + 0*T + Q*DK); + threadgroup s_t * ss = (threadgroup s_t *) (shmem_f16 + Q*T); // scratch buffer for attention, mask and diagonal matrix + threadgroup s2_t * ss2 = (threadgroup s2_t *) (shmem_f16 + Q*T); // same as above but in s2_t + + threadgroup k_t * sk = (threadgroup k_t *) (shmem_f16 + sgitg*(4*16*KV) + Q*T + Q*TS); // scratch buffer to load K in shared memory + threadgroup k4x4_t * sk4x4 = (threadgroup k4x4_t *) (shmem_f16 + sgitg*(4*16*KV) + Q*T + Q*TS); // same as above but in k4x4_t + + threadgroup v_t * sv = (threadgroup v_t *) (shmem_f16 + sgitg*(4*16*KV) + Q*T + Q*TS); // scratch buffer to load V in shared memory + threadgroup v4x4_t * sv4x4 = (threadgroup v4x4_t *) (shmem_f16 + sgitg*(4*16*KV) + Q*T + Q*TS); // same as above but in v4x4_t + + // mask storage in shared mem + threadgroup half2 * sm2 = (threadgroup half2 *) (shmem_f16 + Q*T + 2*C); + + // per-query mask pointers + device const half2 * pm2[NQ]; + + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + + pm2[jj] = (device const half2 *) ((device const char *) mask + (iq1 + j)*args.nb31 + (iq2%args.ne32)*args.nb32 + (iq3%args.ne33)*args.nb33); + } + + { + const int32_t nblk1 = ((args.ne01 + Q - 1)/Q); + const int32_t nblk0 = ((args.ne11 + C - 1)/C); + + blk += (((iq3%args.ne33)*args.ne32 + (iq2%args.ne32))*nblk1 + iq1/Q)*nblk0; + } + + { + q += iq1*args.nb01 + iq2*args.nb02 + iq3*args.nb03; + + const short ikv2 = iq2/(args.ne02/args.ne_12_2); + const short ikv3 = iq3/(args.ne03/args.ne_12_3); + + k += ikv2*args.nb12 + ikv3*args.nb13; + v += ikv2*args.nb22 + ikv3*args.nb23; + } + + // load heads from Q to shared memory + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + + device const float4 * q4 = (device const float4 *) ((device const char *) q + j*args.nb01); + + for (short i = tiisg; i < DK4; i += NW) { + if (iq1 + j < args.ne01) { + sq4[j*DK4 + i] = (q4_t) q4[i]; + } else { + sq4[j*DK4 + i] = 0; + } + } + } + + // zero out + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + + for (short i = tiisg; i < DV4; i += NW) { + so4[j*PV4 + i] = 0; + } + + for (short i = tiisg; i < SH; i += NW) { + ss[j*SH + i] = 0.0f; + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + float S[NQ] = { [0 ... NQ-1] = 0.0f }; + + { + float M[NQ] = { [0 ... NQ-1] = -FLT_MAX/2 }; + + float slope = 1.0f; + + // ALiBi + if (FC_flash_attn_ext_has_bias) { + const short h = iq2; + + const float base = h < args.n_head_log2 ? args.m0 : args.m1; + const short exph = h < args.n_head_log2 ? h + 1 : 2*(h - args.n_head_log2) + 1; + + slope = pow(base, exph); + } + + // loop over the KV cache + // each simdgroup handles blocks of Q rows and C columns + for (int ic0 = 0; ; ++ic0) { + int ic = ic0*C; + if (ic >= args.ne11) { + break; + } + + // the last partial chunk uses the pad buffer as source + if (FC_flash_attn_ext_has_kvpad && ic + C > args.ne11) { + k = pad; + v = k + args.nb11*C*args.ne_12_2*args.ne_12_3; + mask = v + args.nb21*C*args.ne_12_2*args.ne_12_3; + + const short ikv2 = iq2/(args.ne02/args.ne_12_2); + const short ikv3 = iq3/(args.ne03/args.ne_12_3); + + k += (ikv2 + ikv3*args.ne_12_2)*args.nb11*C; + v += (ikv2 + ikv3*args.ne_12_2)*args.nb21*C; + + if (!FC_flash_attn_ext_has_mask) { + threadgroup half * sm = (threadgroup half *) (sm2); + + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + + for (short i = tiisg; i < C; i += NW) { + if (ic + i >= args.ne11) { + sm[2*j*SH + i] = -MAXHALF; + } + } + } + } else { + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + + pm2[jj] = (device const half2 *) ((device const half *) mask + + (iq1 + j)*C + + (iq2%args.ne32)*(C*args.ne31) + + (iq3%args.ne33)*(C*args.ne31*args.ne32)); + } + } + + ic = 0; + } + + char blk_cur = 1; + + // read the mask into shared mem + if (FC_flash_attn_ext_has_mask) { + blk_cur = blk[ic0]; + + if (blk_cur == 0) { + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + pm2[jj] += NW; + } + + continue; + } + + if (blk_cur == 1) { + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + + if (FC_flash_attn_ext_bc_mask) { + sm2[j*SH + tiisg] = (iq1 + j) < args.ne31 ? pm2[jj][tiisg] : half2(-MAXHALF, -MAXHALF); + } else { + sm2[j*SH + tiisg] = pm2[jj][tiisg]; + } + + pm2[jj] += NW; + } + } else if (blk_cur == 2) { + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + pm2[jj] += NW; + } + } + +#if 0 + // note: old -INF block optimization - obsoleted by pre-computing non-masked blocks + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // used to detect blocks full of -INF + // skip only when the entire threadgroup is masked + half2 smax2(-MAXHALF/2, -MAXHALF/2); + + FOR_UNROLL (short j = 0; j < Q; ++j) { + smax2 = max(smax2, sm2[j*SH + tiisg]); + } + + smax2 = simd_max(smax2); + + if (max(smax2[0], smax2[1]) <= -MAXHALF/2) { + // this barrier is important + threadgroup_barrier(mem_flags::mem_threadgroup); + + continue; + } +#endif + } + + // Q*K^T + // this is compile-time check, so it does not have runtime overhead + if (is_same::value) { + // we can read directly from global memory + device const k_t * pk = (device const k_t *) (k + ic*args.nb11); + threadgroup const q_t * pq = sq; + threadgroup s_t * ps = ss; + + pk += sgitg*(8*NS10); + ps += sgitg*(8*1); + + static_assert((C/8) % NSG == 0, ""); + + constexpr short NC = (C/8)/NSG; + + FOR_UNROLL (short cc = 0; cc < NC; ++cc) { + qk8x8_t mqk = make_filled_simdgroup_matrix((qk_t) 0.0f); + + if (DK % 16 != 0) { + k8x8_t mk; + q8x8_t mq; + + FOR_UNROLL (short i = 0; i < DK8; ++i) { + simdgroup_barrier(mem_flags::mem_none); + + simdgroup_load(mk, pk + 8*i, NS10, 0, true); + simdgroup_load(mq, pq + 8*i, DK); + + simdgroup_barrier(mem_flags::mem_none); + + simdgroup_multiply_accumulate(mqk, mq, mk, mqk); + } + } else { + k8x8_t mk[2]; + q8x8_t mq[2]; + + // note: too much unroll can tank the performance for large heads + #pragma unroll (MIN(DK8/2, 4*NSG)) + for (short i = 0; i < DK8/2; ++i) { + simdgroup_barrier(mem_flags::mem_none); + + simdgroup_load(mq[0], pq + 0*8 + 16*i, DK); + simdgroup_load(mq[1], pq + 1*8 + 16*i, DK); + + simdgroup_load(mk[0], pk + 0*8 + 16*i, NS10, 0, true); + simdgroup_load(mk[1], pk + 1*8 + 16*i, NS10, 0, true); + + simdgroup_barrier(mem_flags::mem_none); + + simdgroup_multiply_accumulate(mqk, mq[0], mk[0], mqk); + simdgroup_multiply_accumulate(mqk, mq[1], mk[1], mqk); + } + } + + simdgroup_store(mqk, ps, SH, 0, false); + + pk += 8*(NSG*NS10); + ps += 8*(NSG); + } + } else { + // TODO: this is the quantized K cache branch - not optimized yet + for (short ccc = 0; ccc < (C/8)/NSG; ++ccc) { + const short cc = ccc*NSG + sgitg; + + const short tx = tiisg%4; + const short ty = tiisg/4; + + qk8x8_t mqk = make_filled_simdgroup_matrix((qk_t) 0.0f); + + for (short ii = 0; ii < DK16; ii += 4) { + device const kd4x4_t * pk4x4 = (device const kd4x4_t *) (k + ((ic + 8*cc + ty)*args.nb11)); + + if (DK16%4 == 0) { + // the head is evenly divisible by 4*16 = 64, so no need for bound checks + { + k4x4_t tmp; + deq_k(pk4x4 + (ii + tx)/nl_k, (ii + tx)%nl_k, tmp); + sk4x4[4*ty + tx] = tmp; + } + + simdgroup_barrier(mem_flags::mem_threadgroup); + + FOR_UNROLL (short k = 0; k < 4; ++k) { + k8x8_t mk; + q8x8_t mq; + + simdgroup_load(mk, sk + 16*k + 0*8, 4*16, 0, true); // transpose + simdgroup_load(mq, sq + (2*(ii + k) + 0)*8, DK); + simdgroup_multiply_accumulate(mqk, mq, mk, mqk); + + simdgroup_load(mk, sk + 16*k + 1*8, 4*16, 0, true); // transpose + simdgroup_load(mq, sq + (2*(ii + k) + 1)*8, DK); + simdgroup_multiply_accumulate(mqk, mq, mk, mqk); + } + } else { + if (ii + tx < DK16) { + k4x4_t tmp; + deq_k(pk4x4 + (ii + tx)/nl_k, (ii + tx)%nl_k, tmp); + sk4x4[4*ty + tx] = tmp; + } + + simdgroup_barrier(mem_flags::mem_threadgroup); + + for (short k = 0; k < 4 && ii + k < DK16; ++k) { + k8x8_t mk; + q8x8_t mq; + + simdgroup_load(mk, sk + 16*k + 0*8, 4*16, 0, true); // transpose + simdgroup_load(mq, sq + (2*(ii + k) + 0)*8, DK); + simdgroup_multiply_accumulate(mqk, mq, mk, mqk); + + simdgroup_load(mk, sk + 16*k + 1*8, 4*16, 0, true); // transpose + simdgroup_load(mq, sq + (2*(ii + k) + 1)*8, DK); + simdgroup_multiply_accumulate(mqk, mq, mk, mqk); + } + } + } + + simdgroup_store(mqk, ss + 8*cc, SH, 0, false); + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // online softmax + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + + const float m = M[jj]; + + // scale and apply the logitcap / mask + float2 s2 = ss2[j*SH/2 + tiisg]*args.scale; + + if (FC_flash_attn_ext_has_scap) { + s2 = args.logit_softcap*precise::tanh(s2); + } + + // mqk = mqk + slope*mask + if (blk_cur != 2) { + if (FC_flash_attn_ext_has_bias) { + s2 += s2_t(sm2[j*SH + tiisg])*slope; + } else { + s2 += s2_t(sm2[j*SH + tiisg]); + } + } + + M[jj] = simd_max(max(M[jj], max(s2[0], s2[1]))); + + const float ms = exp(m - M[jj]); + const float2 vs2 = exp(s2 - M[jj]); + + S[jj] = S[jj]*ms + simd_sum(vs2[0] + vs2[1]); + + // the P matrix from the paper (Q rows, C columns) + ss2[j*SH/2 + tiisg] = vs2; + + if (DV4 % NW == 0) { + FOR_UNROLL (short ii = 0; ii < DV4/NW; ++ii) { + const short i = ii*NW + tiisg; + + so4[j*PV4 + i] *= ms; + } + } else { + for (short i = tiisg; i < DV4; i += NW) { + so4[j*PV4 + i] *= ms; + } + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // O = O + (Q*K^T)*V + { + // we can read directly from global memory + if (is_same::value) { + static_assert(PV8 % NSG == 0, ""); + + constexpr short NO = PV8/NSG; + + o8x8_t lo[NO]; + + { + auto sot = so + 8*sgitg; + + FOR_UNROLL (short ii = 0; ii < NO; ++ii) { + simdgroup_load(lo[ii], sot, PV, 0, false); + + sot += 8*NSG; + } + } + + { + device const v_t * pv = (device const v_t *) (v + ic*args.nb21); + + pv += 8*sgitg; + + if (DV <= 64) { + FOR_UNROLL (short cc = 0; cc < C/8; ++cc) { + s8x8_t vs; + simdgroup_load(vs, ss + 8*cc, SH, 0, false); + + FOR_UNROLL (short ii = 0; ii < NO/2; ++ii) { + v8x8_t mv[2]; + + simdgroup_load(mv[0], pv + 0*NSG + 16*ii*NSG, NS20, 0, false); + simdgroup_load(mv[1], pv + 8*NSG + 16*ii*NSG, NS20, 0, false); + + simdgroup_multiply_accumulate(lo[2*ii + 0], vs, mv[0], lo[2*ii + 0]); + simdgroup_multiply_accumulate(lo[2*ii + 1], vs, mv[1], lo[2*ii + 1]); + } + + pv += 8*NS20; + } + } else { + constexpr short NC = (C/8)/2; + + FOR_UNROLL (short cc = 0; cc < NC; ++cc) { + s8x8_t vs[2]; + + simdgroup_load(vs[0], ss + 16*cc + 0, SH, 0, false); + simdgroup_load(vs[1], ss + 16*cc + 8, SH, 0, false); + + FOR_UNROLL (short ii = 0; ii < NO/2; ++ii) { + v8x8_t mv[4]; + + simdgroup_load(mv[0], pv + 0*NSG + 16*ii*NSG + 0*8*NS20, NS20, 0, false); + simdgroup_load(mv[1], pv + 8*NSG + 16*ii*NSG + 0*8*NS20, NS20, 0, false); + simdgroup_load(mv[2], pv + 0*NSG + 16*ii*NSG + 1*8*NS20, NS20, 0, false); + simdgroup_load(mv[3], pv + 8*NSG + 16*ii*NSG + 1*8*NS20, NS20, 0, false); + + simdgroup_multiply_accumulate(lo[2*ii + 0], vs[0], mv[0], lo[2*ii + 0]); + simdgroup_multiply_accumulate(lo[2*ii + 1], vs[0], mv[1], lo[2*ii + 1]); + simdgroup_multiply_accumulate(lo[2*ii + 0], vs[1], mv[2], lo[2*ii + 0]); + simdgroup_multiply_accumulate(lo[2*ii + 1], vs[1], mv[3], lo[2*ii + 1]); + } + + pv += 2*8*NS20; + } + } + } + + { + auto sot = so + 8*sgitg; + + FOR_UNROLL (short ii = 0; ii < NO; ++ii) { + simdgroup_store(lo[ii], sot, PV, 0, false); + + sot += 8*NSG; + } + } + } else { + // TODO: this is the quantized V cache branch - not optimized yet + + const short tx = tiisg%4; + const short ty = tiisg/4; + + for (short cc = 0; cc < C/8; ++cc) { + s8x8_t vs; + simdgroup_load(vs, ss + 8*cc, SH, 0, false); + + for (short ii = 4*sgitg; ii < DV16; ii += 4*NSG) { + device const vd4x4_t * pv4x4 = (device const vd4x4_t *) (v + ((ic + 8*cc + ty)*args.nb21)); + + if (DV16%4 == 0) { + // no need for bound checks + { + v4x4_t tmp; + deq_v(pv4x4 + (ii + tx)/nl_v, (ii + tx)%nl_v, tmp); + sv4x4[4*ty + tx] = tmp; + } + + simdgroup_barrier(mem_flags::mem_threadgroup); + + FOR_UNROLL (short k = 0; k < 4; ++k) { + v8x8_t mv[2]; + o8x8_t lo[2]; + + simdgroup_load(mv[0], sv + 16*k + 0*8, 4*16, 0, false); + simdgroup_load(mv[1], sv + 16*k + 1*8, 4*16, 0, false); + simdgroup_load(lo[0], so + 8*(2*(ii + k) + 0), PV, 0, false); + simdgroup_load(lo[1], so + 8*(2*(ii + k) + 1), PV, 0, false); + + simdgroup_multiply_accumulate(lo[0], vs, mv[0], lo[0]); + simdgroup_multiply_accumulate(lo[1], vs, mv[1], lo[1]); + + simdgroup_store(lo[0], so + 8*(2*(ii + k) + 0), PV, 0, false); + simdgroup_store(lo[1], so + 8*(2*(ii + k) + 1), PV, 0, false); + } + } else { + if (ii + tx < DV16) { + v4x4_t tmp; + deq_v(pv4x4 + (ii + tx)/nl_v, (ii + tx)%nl_v, tmp); + sv4x4[4*ty + tx] = tmp; + } + + simdgroup_barrier(mem_flags::mem_threadgroup); + + for (short k = 0; k < 4 && ii + k < DV16; ++k) { + v8x8_t mv[2]; + o8x8_t lo[2]; + + simdgroup_load(mv[0], sv + 16*k + 0*8, 4*16, 0, false); + simdgroup_load(mv[1], sv + 16*k + 1*8, 4*16, 0, false); + simdgroup_load(lo[0], so + 8*(2*(ii + k) + 0), PV, 0, false); + simdgroup_load(lo[1], so + 8*(2*(ii + k) + 1), PV, 0, false); + + simdgroup_multiply_accumulate(lo[0], vs, mv[0], lo[0]); + simdgroup_multiply_accumulate(lo[1], vs, mv[1], lo[1]); + + simdgroup_store(lo[0], so + 8*(2*(ii + k) + 0), PV, 0, false); + simdgroup_store(lo[1], so + 8*(2*(ii + k) + 1), PV, 0, false); + } + } + } + } + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + if (FC_flash_attn_ext_has_sinks) { + FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + + const float m = M[jj]; + const float s = tiisg == 0 ? ((device const float *) sinks)[iq2] : -FLT_MAX/2; + + M[jj] = simd_max(max(M[jj], s)); + + const float ms = exp(m - M[jj]); + const float vs = exp(s - M[jj]); + + S[jj] = S[jj]*ms + simd_sum(vs); + + for (short i = tiisg; i < DV4; i += NW) { + so4[j*PV4 + i] *= ms; + } + } + } + } + + // store to global memory + for (short jj = 0; jj < NQ; ++jj) { + const short j = jj*NSG + sgitg; + if (iq1 + j >= args.ne01) { + break; + } + + device float4 * dst4 = (device float4 *) dst + ((uint64_t)iq3*args.ne2*args.ne1 + iq2 + (uint64_t)(iq1 + j)*args.ne1)*DV4; + + const float scale = S[jj] == 0.0 ? 0.0f : 1.0f/S[jj]; + + if (DV4 % NW == 0) { + FOR_UNROLL (short ii = 0; ii < DV4/NW; ++ii) { + const short i = ii*NW + tiisg; + + dst4[i] = (float4) so4[j*PV4 + i]*scale; + } + } else { + for (short i = tiisg; i < DV4; i += NW) { + dst4[i] = (float4) so4[j*PV4 + i]*scale; + } + } + } + +#undef NS10 +#undef NS20 +} + +template< + typename q_t, // query types in shared memory + typename q4_t, + typename q8x8_t, + typename k_t, // key types in shared memory + typename k4x4_t, + typename k8x8_t, + typename v_t, // value types in shared memory + typename v4x4_t, + typename v8x8_t, + typename qk_t, // Q*K types + typename qk8x8_t, + typename s_t, // soft-max types + typename s2_t, + typename s8x8_t, + typename o_t, // attention accumulation types + typename o4_t, + typename o8x8_t, + typename kd4x4_t, // key type in device memory + short nl_k, + void (*deq_k)(device const kd4x4_t *, short, thread k4x4_t &), + typename vd4x4_t, // value type in device memory + short nl_v, + void (*deq_v)(device const vd4x4_t *, short, thread v4x4_t &), + short DK, // K head size + short DV, // V head size + short Q = OP_FLASH_ATTN_EXT_NQPSG, // queries per threadgroup + short C = OP_FLASH_ATTN_EXT_NCPSG> // cache items per threadgroup +kernel void kernel_flash_attn_ext( + constant ggml_metal_kargs_flash_attn_ext & args, + device const char * q, + device const char * k, + device const char * v, + device const char * mask, + device const char * sinks, + device const char * pad, + device const char * blk, + device char * dst, + threadgroup half * shmem_f16 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { +#define FWD_TMPL q_t, q4_t, q8x8_t, k_t, k4x4_t, k8x8_t, v_t, v4x4_t, v8x8_t, qk_t, qk8x8_t, s_t, s2_t, s8x8_t, o_t, o4_t, o8x8_t, kd4x4_t, nl_k, deq_k, vd4x4_t, nl_v, deq_v, DK, DV, Q, C +#define FWD_ARGS args, q, k, v, mask, sinks, pad, blk, dst, shmem_f16, tgpig, tiisg, sgitg + switch (FC_flash_attn_ext_nsg) { + // note: disabled cases to reduce library load time + //case 1: kernel_flash_attn_ext_impl(FWD_ARGS); break; + //case 2: kernel_flash_attn_ext_impl(FWD_ARGS); break; + case 4: kernel_flash_attn_ext_impl(FWD_ARGS); break; + case 8: kernel_flash_attn_ext_impl(FWD_ARGS); break; + } +#undef FWD_TMPL +#undef FWD_ARGS +} + +// TODO: this is quite ugly. in the future these types will be hardcoded in the kernel, but for now keep them as +// template to be able to explore different combinations +// +#define FA_TYPES \ + half, half4, simdgroup_half8x8, \ + half, half4x4, simdgroup_half8x8, \ + half, half4x4, simdgroup_half8x8, \ + float, simdgroup_float8x8, \ + float, float2, simdgroup_float8x8, \ + float, float4, simdgroup_float8x8 + //half, half4, simdgroup_half8x8 + +#define FA_TYPES_BF \ + bfloat, bfloat4, simdgroup_bfloat8x8, \ + bfloat, bfloat4x4, simdgroup_bfloat8x8, \ + bfloat, bfloat4x4, simdgroup_bfloat8x8, \ + float, simdgroup_float8x8, \ + float, float2, simdgroup_float8x8, \ + half, half4, simdgroup_half8x8 + //float, float4, simdgroup_float8x8 + +#define FA_TYPES_F32 \ + half, half4, simdgroup_half8x8, \ + float, float4x4, simdgroup_float8x8, \ + float, float4x4, simdgroup_float8x8, \ + float, simdgroup_float8x8, \ + float, float2, simdgroup_float8x8, \ + float, float4, simdgroup_float8x8 + //half, half4, simdgroup_half8x8 + +typedef decltype(kernel_flash_attn_ext) flash_attn_ext_t; + +template [[host_name("kernel_flash_attn_ext_f32_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f32_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; + +template [[host_name("kernel_flash_attn_ext_f16_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_f16_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; + +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_bf16_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_bf16_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +#endif + +template [[host_name("kernel_flash_attn_ext_q4_0_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_0_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; + +template [[host_name("kernel_flash_attn_ext_q4_1_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q4_1_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; + +template [[host_name("kernel_flash_attn_ext_q5_0_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_0_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; + +template [[host_name("kernel_flash_attn_ext_q5_1_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q5_1_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; + +template [[host_name("kernel_flash_attn_ext_q8_0_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; +template [[host_name("kernel_flash_attn_ext_q8_0_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; + +#undef FA_TYPES +#undef FA_TYPES_BF +#undef FA_TYPES_F32 + +constant bool FC_flash_attn_ext_vec_has_mask [[function_constant(FC_FLASH_ATTN_EXT_VEC + 0)]]; +constant bool FC_flash_attn_ext_vec_has_sinks [[function_constant(FC_FLASH_ATTN_EXT_VEC + 1)]]; +constant bool FC_flash_attn_ext_vec_has_bias [[function_constant(FC_FLASH_ATTN_EXT_VEC + 2)]]; +constant bool FC_flash_attn_ext_vec_has_scap [[function_constant(FC_FLASH_ATTN_EXT_VEC + 3)]]; +constant bool FC_flash_attn_ext_vec_has_kvpad [[function_constant(FC_FLASH_ATTN_EXT_VEC + 4)]]; + +//constant float FC_flash_attn_ext_vec_scale [[function_constant(FC_FLASH_ATTN_EXT_VEC + 10)]]; +//constant float FC_flash_attn_ext_vec_max_bias [[function_constant(FC_FLASH_ATTN_EXT_VEC + 11)]]; +//constant float FC_flash_attn_ext_vec_logit_softcap [[function_constant(FC_FLASH_ATTN_EXT_VEC + 12)]]; + +constant int32_t FC_flash_attn_ext_vec_ns10 [[function_constant(FC_FLASH_ATTN_EXT_VEC + 20)]]; +constant int32_t FC_flash_attn_ext_vec_ns20 [[function_constant(FC_FLASH_ATTN_EXT_VEC + 21)]]; +constant int32_t FC_flash_attn_ext_vec_nsg [[function_constant(FC_FLASH_ATTN_EXT_VEC + 22)]]; +constant int32_t FC_flash_attn_ext_vec_nwg [[function_constant(FC_FLASH_ATTN_EXT_VEC + 23)]]; + +template< + typename q4_t, // query types in shared memory + typename k4_t, // key types in shared memory + typename v4_t, // value types in shared memory + typename qk_t, // Q*K types + typename s_t, // soft-max types + typename s4_t, + typename o4_t, // attention accumulation types + typename kd4_t, // key type in device memory + short nl_k, + void (*deq_k_t4)(device const kd4_t *, short, thread k4_t &), + typename vd4_t, // value type in device memory + short nl_v, + void (*deq_v_t4)(device const vd4_t *, short, thread v4_t &), + short DK, // K head size + short DV, // V head size + short NE = 4, // head elements per thread + short Q = OP_FLASH_ATTN_EXT_VEC_NQPSG, // queries per threadgroup + short C = OP_FLASH_ATTN_EXT_VEC_NCPSG> // cache items per threadgroup +kernel void kernel_flash_attn_ext_vec( + constant ggml_metal_kargs_flash_attn_ext_vec & args, + device const char * q, + device const char * k, + device const char * v, + device const char * mask, + device const char * sinks, + device const char * pad, + device char * dst, + threadgroup half * shmem_f16 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + static_assert(DK % 32 == 0, "DK must be divisible by 32"); + static_assert(DV % 32 == 0, "DV must be divisible by 32"); + +#define NWG (FC_flash_attn_ext_vec_nwg) +#define NSG (FC_flash_attn_ext_vec_nsg) + +#define NS10 (FC_flash_attn_ext_vec_ns10) +#define NS20 (FC_flash_attn_ext_vec_ns20) + + const short iwg = tgpig[2]%NWG; + + const ushort iq3 = tgpig[2]/NWG; + const ushort iq2 = tgpig[1]; + const ushort iq1 = tgpig[0]; + + constexpr short DK4 = DK/4; + constexpr short DV4 = DV/4; + + constexpr short PK = PAD2(DK, 128); + constexpr short PK4 = PK/4; + + constexpr short PV = PAD2(DV, 128); + constexpr short PV4 = PV/4; + + constexpr short NW = N_SIMDWIDTH; + constexpr short NL = NW/NE; // note: this can be adjusted to support different head sizes and simdgroup work loads + constexpr short SH = 4*C; // shared memory per simdgroup + + static_assert(DK4 % NL == 0, "DK4 must be divisible by NL"); + static_assert(DV4 % NL == 0, "DV4 must be divisible by NL"); + + //const short T = PK + NSG*SH; // shared memory size per query in (half) + + //threadgroup q_t * sq = (threadgroup q_t *) (shmem_f16 + 0*PK); // holds the query data + threadgroup q4_t * sq4 = (threadgroup q4_t *) (shmem_f16 + 0*PK); // same as above but in q4_t + threadgroup s_t * ss = (threadgroup s_t *) (shmem_f16 + sgitg*SH + NSG*PK); // scratch buffer for attention + threadgroup s4_t * ss4 = (threadgroup s4_t *) (shmem_f16 + sgitg*SH + NSG*PK); // same as above but in s4_t + threadgroup half * sm = (threadgroup half *) (shmem_f16 + sgitg*SH + 2*C + NSG*PK); // scratch buffer for mask + threadgroup o4_t * so4 = (threadgroup o4_t *) (shmem_f16 + 2*sgitg*PV + NSG*PK + NSG*SH); // scratch buffer for the results + + // store the result for all queries in shared memory (the O matrix from the paper) + so4 += tiisg; + + { + q += iq1*args.nb01 + iq2*args.nb02 + iq3*args.nb03; + + const short ikv2 = iq2/(args.ne02/args.ne_12_2); + const short ikv3 = iq3/(args.ne03/args.ne_12_3); + + k += ikv2*args.nb12 + ikv3*args.nb13; + v += ikv2*args.nb22 + ikv3*args.nb23; + } + + // load heads from Q to shared memory + device const float4 * q4 = (device const float4 *) ((device const char *) q); + + if (iq1 < args.ne01) { + for (short i = tiisg; i < PK4; i += NW) { + if (i < DK4) { + sq4[i] = (q4_t) q4[i]; + } else { + sq4[i] = (q4_t) 0.0f; + } + } + } + + // zero out so + for (short i = 0; i < DV4/NL; ++i) { + so4[i*NL] = (o4_t) 0.0f; + } + + // zero out shared memory SH + for (short i = tiisg; i < SH/4; i += NW) { + ss4[i] = (s4_t) 0.0f; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + { + float S = 0.0f; + float M = -FLT_MAX/2; + + // thread indices inside the simdgroup + const short tx = tiisg%NL; + const short ty = tiisg/NL; + + // pointer to the mask + device const half * pm = (device const half *) (mask + iq1*args.nb31 + (iq2%args.ne32)*args.nb32 + (iq3%args.ne33)*args.nb33); + + float slope = 1.0f; + + // ALiBi + if (FC_flash_attn_ext_vec_has_bias) { + const short h = iq2; + + const float base = h < args.n_head_log2 ? args.m0 : args.m1; + const short exph = h < args.n_head_log2 ? h + 1 : 2*(h - args.n_head_log2) + 1; + + slope = pow(base, exph); + } + + // loop over the KV cache + // each simdgroup handles blocks of Q rows and C columns + for (int ic0 = iwg*NSG + sgitg; ; ic0 += NWG*NSG) { + int ic = ic0*C; + if (ic >= args.ne11) { + break; + } + + // the last partial chunk uses the pad buffer as source + if (FC_flash_attn_ext_vec_has_kvpad && ic + C > args.ne11) { + k = pad; + v = k + args.nb11*C*args.ne_12_2*args.ne_12_3; + mask = v + args.nb21*C*args.ne_12_2*args.ne_12_3; + + const short ikv2 = iq2/(args.ne02/args.ne_12_2); + const short ikv3 = iq3/(args.ne03/args.ne_12_3); + + k += (ikv2 + ikv3*args.ne_12_2)*args.nb11*C; + v += (ikv2 + ikv3*args.ne_12_2)*args.nb21*C; + + if (!FC_flash_attn_ext_vec_has_mask) { + if (ic + tiisg >= args.ne11) { + sm[tiisg] = -MAXHALF; + } + } else { + pm = (device const half *) (mask) + + iq1*C + + (iq2%args.ne32)*(C*args.ne31) + + (iq3%args.ne33)*(C*args.ne31*args.ne32); + } + + ic = 0; + } + + if (FC_flash_attn_ext_vec_has_mask) { + sm[tiisg] = pm[ic + tiisg]; + } + + // skip -INF blocks + if (simd_max(sm[tiisg]) <= -MAXHALF) { + continue; + } + + // Q*K^T + { + device const k4_t * pk4 = (device const k4_t *) (k + ic*args.nb11); + threadgroup const q4_t * pq4 = sq4; + + pk4 += ty*NS10/4 + tx; + pq4 += tx; + + qk_t mqk[C/NE] = { [ 0 ... C/NE - 1] = 0.0f }; + + // each simdgroup processes 1 query and NE (NW/NL) cache elements + FOR_UNROLL (short cc = 0; cc < C/NE; ++cc) { + if (is_same::value) { + FOR_UNROLL (short ii = 0; ii < DK4/NL; ++ii) { + mqk[cc] += dot((float4) pk4[cc*NE*NS10/4 + ii*NL], (float4) pq4[ii*NL]); + } + } else { + device const kd4_t * pk = (device const kd4_t *) (k + ((ic + NE*cc + ty)*args.nb11)); + + k4_t mk; + + FOR_UNROLL (short ii = 0; ii < DK4/NL; ++ii) { + const short i = ii*NL + tx; + + deq_k_t4(pk + i/nl_k, i%nl_k, mk); + + mqk[cc] += dot((float4) mk, (float4) sq4[i]); + } + } + + if (NE == 1) { + mqk[cc] = simd_sum(mqk[cc]); + } else { + // simdgroup reduce (NE = 4) + // [ 0 .. 7] -> [ 0] + // [ 8 .. 15] -> [ 8] + // [16 .. 23] -> [16] + // [24 .. 31] -> [24] + if (NE <= 1) { + mqk[cc] += simd_shuffle_down(mqk[cc], 16); + } + if (NE <= 2) { + mqk[cc] += simd_shuffle_down(mqk[cc], 8); + } + if (NE <= 4) { + mqk[cc] += simd_shuffle_down(mqk[cc], 4); + } + if (NE <= 8) { + mqk[cc] += simd_shuffle_down(mqk[cc], 2); + } + if (NE <= 16) { + mqk[cc] += simd_shuffle_down(mqk[cc], 1); + } + + // broadcast + mqk[cc] = simd_shuffle(mqk[cc], NL*ty); + } + } + + if (FC_flash_attn_ext_vec_has_mask && + !FC_flash_attn_ext_vec_has_scap && + !FC_flash_attn_ext_vec_has_bias) { + ss[NE*tx + ty] = fma(mqk[tx], args.scale, (qk_t) sm[NE*tx + ty]); + } else { + mqk[tx] *= args.scale; + + if (FC_flash_attn_ext_vec_has_scap) { + mqk[tx] = args.logit_softcap*precise::tanh(mqk[tx]); + } + + if (FC_flash_attn_ext_vec_has_bias) { + mqk[tx] += (qk_t) sm[NE*tx + ty]*slope; + } else { + mqk[tx] += (qk_t) sm[NE*tx + ty]; + } + + ss[NE*tx + ty] = mqk[tx]; + } + } + + simdgroup_barrier(mem_flags::mem_threadgroup); + + // online softmax + { + const float m = M; + const float s = ss[tiisg]; + + M = simd_max(max(M, s)); + + const float ms = exp(m - M); + const float vs = exp(s - M); + + S = S*ms + simd_sum(vs); + + // the P matrix from the paper (Q rows, C columns) + ss[tiisg] = vs; + + // O = diag(ms)*O + if ((DV4/NL % NW == 0) || ty == 0) { + FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { + so4[ii*NL] *= ms; + } + } + } + + simdgroup_barrier(mem_flags::mem_threadgroup); + + // O = O + (Q*K^T)*V + { + o4_t lo[DV4/NL]; + FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { + lo[ii] = 0.0f; + } + + if (is_same::value) { + device const v4_t * pv4 = (device const v4_t *) (v + ic*args.nb21); + + pv4 += ty*NS20/4 + tx; + + const auto sst = ss + ty; + + FOR_UNROLL (short cc = 0; cc < C/NE; ++cc) { + FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { + lo[ii] += o4_t(float4(pv4[cc*NE*NS20/4 + ii*NL])*float4(sst[cc*NE])); + } + } + } else { + FOR_UNROLL (short cc = 0; cc < C/NE; ++cc) { + device const vd4_t * pv4 = (device const vd4_t *) (v + ((ic + NE*cc + ty)*args.nb21)); + + FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { + const short i = ii*NL + tx; + + v4_t mv; + deq_v_t4(pv4 + i/nl_v, i%nl_v, mv); + + lo[ii] += o4_t(float4(mv)*float4(ss[NE*cc + ty])); + } + } + } + + FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { + if (NE > 1) { + lo[ii][0] += simd_shuffle_down(lo[ii][0], 16); + lo[ii][1] += simd_shuffle_down(lo[ii][1], 16); + lo[ii][2] += simd_shuffle_down(lo[ii][2], 16); + lo[ii][3] += simd_shuffle_down(lo[ii][3], 16); + } + + if (NE > 2) { + lo[ii][0] += simd_shuffle_down(lo[ii][0], 8); + lo[ii][1] += simd_shuffle_down(lo[ii][1], 8); + lo[ii][2] += simd_shuffle_down(lo[ii][2], 8); + lo[ii][3] += simd_shuffle_down(lo[ii][3], 8); + } + + if (NE > 4) { + lo[ii][0] += simd_shuffle_down(lo[ii][0], 4); + lo[ii][1] += simd_shuffle_down(lo[ii][1], 4); + lo[ii][2] += simd_shuffle_down(lo[ii][2], 4); + lo[ii][3] += simd_shuffle_down(lo[ii][3], 4); + } + + if (NE > 8) { + lo[ii][0] += simd_shuffle_down(lo[ii][0], 2); + lo[ii][1] += simd_shuffle_down(lo[ii][1], 2); + lo[ii][2] += simd_shuffle_down(lo[ii][2], 2); + lo[ii][3] += simd_shuffle_down(lo[ii][3], 2); + } + + if (NE > 16) { + lo[ii][0] += simd_shuffle_down(lo[ii][0], 1); + lo[ii][1] += simd_shuffle_down(lo[ii][1], 1); + lo[ii][2] += simd_shuffle_down(lo[ii][2], 1); + lo[ii][3] += simd_shuffle_down(lo[ii][3], 1); + } + } + + if ((DV4/NL % NW == 0) || ty == 0) { + FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { + so4[ii*NL] += lo[ii]; + } + } + } + } + + if (FC_flash_attn_ext_vec_has_sinks && sgitg == 0 && iwg == 0) { + const float m = M; + const float s = tiisg == 0 ? ((device const float *) sinks)[iq2] : -FLT_MAX/2; + + M = simd_max(max(M, s)); + + const float ms = exp(m - M); + const float vs = exp(s - M); + + S = S*ms + simd_sum(vs); + + if ((DV4/NL % NW == 0) || ty == 0) { + FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { + so4[ii*NL] *= ms; + } + } + } + + // these are needed for reducing the results from the simdgroups (reuse the ss buffer) + if (tiisg == 0) { + ss[0] = (s_t) S; + ss[1] = (s_t) M; + } + } + + so4 -= tiisg; + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // parallel reduce + for (short r = NSG/2; r > 0; r >>= 1) { + if (sgitg < r) { + const float S0 = ss[ 0]; + const float S1 = ss[r*(SH/2) + 0]; + + const float M0 = ss[ 1]; + const float M1 = ss[r*(SH/2) + 1]; + + const float M = max(M0, M1); + + const float ms0 = exp(M0 - M); + const float ms1 = exp(M1 - M); + + const float S = S0*ms0 + S1*ms1; + + if (tiisg == 0) { + ss[0] = S; + ss[1] = M; + } + + // O_0 = diag(ms0)*O_0 + diag(ms1)*O_1 + for (short i = tiisg; i < DV4; i += NW) { + so4[i] = so4[i]*ms0 + so4[i + r*PV4]*ms1; + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + // final rescale with 1/S and store to global memory + if (sgitg == 0) { + const int64_t nrows = args.ne3*args.ne2*args.ne1; + const int64_t rid = iq3*args.ne2*args.ne1 + iq2 + iq1*args.ne1; + + device float4 * dst4 = (device float4 *) dst; + device float * dst1 = (device float *) dst + nrows*DV*NWG; // the S and M are stored after the results + + const float S = NWG == 1 ? (ss[0] == 0.0f ? 0.0f : 1.0f/ss[0]) : 1.0f; + + // interleave the workgroup data + for (short i = tiisg; i < DV4; i += NW) { + dst4[rid*DV4*NWG + NWG*i + iwg] = (float4) so4[i]*S; + } + + // store S and M + if (NWG > 1) { + if (tiisg == 0) { + dst1[rid*(2*NWG) + 2*iwg + 0] = ss[0]; + dst1[rid*(2*NWG) + 2*iwg + 1] = ss[1]; + } + } + } + +#undef NWG +#undef NSG +#undef NS10 +#undef NS20 +} + +// note: I think the s_t can be half instead of float, because the Q*K scaling is done before storing to shared mem +// in the other (non-vec) kernel, we need s_t to also be float because we scale during the soft_max +// +#define FA_TYPES \ + half4, \ + half4, \ + half4, \ + float, \ + float, float4, \ + float4 + +#define FA_TYPES_F32 \ + half4, \ + float4, \ + float4, \ + float, \ + float, float4, \ + float4 + +typedef decltype(kernel_flash_attn_ext_vec) flash_attn_ext_vec_t; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +template [[host_name("kernel_flash_attn_ext_vec_f32_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_f16_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_flash_attn_ext_vec_bf16_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +#endif +template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; +template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; + +#undef FA_TYPES +#undef FA_TYPES_F32 + +constant int32_t FC_flash_attn_ext_vec_reduce_DV [[function_constant(FC_FLASH_ATTN_EXT_VEC_REDUCE + 0)]]; +constant int32_t FC_flash_attn_ext_vec_reduce_NWG [[function_constant(FC_FLASH_ATTN_EXT_VEC_REDUCE + 1)]]; + +kernel void kernel_flash_attn_ext_vec_reduce( + constant ggml_metal_kargs_flash_attn_ext_vec_reduce & args, + device const char * htmp, + device char * dst, + uint tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { +#define NWG (FC_flash_attn_ext_vec_reduce_NWG) +#define DV (FC_flash_attn_ext_vec_reduce_DV) + + const uint64_t rid = tgpig; + + const short iwg = tiisg; + + device const float * ss = (device const float *) htmp + (uint64_t)args.nrows*DV*NWG; + + float S = ss[rid*(2*NWG) + 2*iwg + 0]; + float M = ss[rid*(2*NWG) + 2*iwg + 1]; + + const float m = simd_max(M); + const float ms = exp(M - m); + + S = simd_sum(S*ms); + S = S == 0.0f ? 0.0f : 1.0f/S; + + const short DV4 = DV/4; + + device const float4 * htmp4 = (device const float4 *) htmp + rid*DV4*NWG; + device float4 * dst4 = (device float4 *) dst + rid*DV4; + + for (short i = sgitg; i < DV4; i += NWG) { + const float4 v = simd_sum(htmp4[i*NWG + iwg]*ms); + + if (iwg == 0) { + dst4[i] = v*S; + } + } + +#undef NWG +#undef DV +} + +template +kernel void kernel_cpy_t_t( + constant ggml_metal_kargs_cpy & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int i03 = tgpig[2]; + const int i02 = tgpig[1]; + const int i01 = ntg[1] == 1 ? tgpig[0]%args.ne01 : tgpig[0]*ntg[1] + tiitg/ntg[0]; + const int iw0 = ntg[1] == 1 ? tgpig[0]/args.ne01 : 0; + + const int64_t n = i03*args.ne02*args.ne01*args.ne00 + i02*args.ne01*args.ne00 + i01*args.ne00; + + const int64_t i3 = n/(args.ne2*args.ne1*args.ne0); + const int64_t i2 = (n - i3*args.ne2*args.ne1*args.ne0)/(args.ne1*args.ne0); + const int64_t i1 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0)/args.ne0; + const int64_t i0 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0 - i1*args.ne0); + + device T1 * dst_data = (device T1 *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + for (int64_t i00 = iw0*ntg[0] + tiitg%ntg[0]; i00 < args.ne00; ) { + device const T0 * src = (device T0 *)(src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + i00*args.nb00); + dst_data[i00] = (T1) src[0]; + break; + } +} + +typedef decltype(kernel_cpy_t_t) kernel_cpy_t; + +template +kernel void kernel_cpy_contig_t_t( + constant ggml_metal_kargs_cpy & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int64_t i = (int64_t)tgpig.x*ntg.x + tpitg.x; + + if (i >= args.nk0) { + return; + } + + device const T0 * src_data = (device const T0 *) src0; + device T1 * dst_data = (device T1 *) dst; + + dst_data[i] = (T1) src_data[i]; +} + +typedef decltype(kernel_cpy_contig_t_t) kernel_cpy_contig_t; + +template [[host_name("kernel_cpy_contig_f32_f32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_cpy_contig_f32_f16")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_cpy_contig_f32_i32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_cpy_contig_i32_f32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_cpy_contig_i32_i32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_cpy_contig_f32_bf16")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +#endif +template [[host_name("kernel_cpy_contig_f16_f32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_cpy_contig_f16_f16")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_cpy_contig_bf16_f32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_cpy_contig_bf16_bf16")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +#endif + +template +kernel void kernel_cpy_2d_t( + constant ggml_metal_kargs_cpy & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int64_t i0 = (int64_t)tgpig.x*ntg.x + tpitg.x; + const int64_t i1 = (int64_t)tgpig.y*ntg.y + tpitg.y; + const int64_t i23 = tgpig.z; + const int64_t i2 = i23 % args.ne02; + const int64_t i3 = i23 / args.ne02; + + if (i0 >= args.ne00 || i1 >= args.ne01) { + return; + } + + device const T * src_data = (device const T *) (src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); + device T * dst_data = (device T *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + dst_data[0] = src_data[0]; +} + +typedef decltype(kernel_cpy_2d_t) kernel_cpy_2d_tmpl; + +template [[host_name("kernel_cpy_2d_f32")]] kernel kernel_cpy_2d_tmpl kernel_cpy_2d_t; +template [[host_name("kernel_cpy_2d_f16")]] kernel kernel_cpy_2d_tmpl kernel_cpy_2d_t; +template [[host_name("kernel_cpy_2d_i32")]] kernel kernel_cpy_2d_tmpl kernel_cpy_2d_t; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_cpy_2d_bf16")]] kernel kernel_cpy_2d_tmpl kernel_cpy_2d_t; +#endif + +kernel void kernel_cpy_row_f32( + constant ggml_metal_kargs_cpy & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]]) { + const int64_t i0 = ((int64_t)tgpig.x*16 + tpitg.x)*4; + + if (i0 >= args.ne00) { + return; + } + + for (int mat = 0; mat < 4; ++mat) { + const int64_t i23 = (int64_t)tgpig.z*4 + mat; + if (i23 >= args.ne02*args.ne03) { + continue; + } + const int64_t i2 = i23 % args.ne02; + const int64_t i3 = i23 / args.ne02; + + for (int row = 0; row < 2; ++row) { + const int64_t i1 = (int64_t)tgpig.y*16 + tpitg.y + 8*row; + if (i1 >= args.ne01) { + continue; + } + + device const char * src_row = src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00; + device char * dst_row = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0; + + if (i0 + 3 < args.ne00) { + device const float4 * src4 = (device const float4 *) src_row; + device float4 * dst4 = (device float4 *) dst_row; + dst4[0] = src4[0]; + } else { + device const float * src1 = (device const float *) src_row; + device float * dst1 = (device float *) dst_row; + for (int64_t i = i0; i < args.ne00; ++i) { + dst1[i - i0] = src1[i - i0]; + } + } + } + } +} + +kernel void kernel_cpy_transpose_f32( + constant ggml_metal_kargs_cpy & args, + device const char * src0, + device char * dst, + threadgroup float * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]]) { + constexpr int tile_dim = 32; + constexpr int block_rows = 8; + constexpr int tile_stride = tile_dim + 1; + + const int64_t tile_col = tgpig.x; + const int64_t tile_row = tgpig.y; + const int64_t i23 = tgpig.z; + const int64_t i2 = i23 % args.ne02; + const int64_t i3 = i23 / args.ne02; + const int64_t tid_col = tpitg.x; + const int64_t tid_row = tpitg.y; + + for (int y = 0; y < 4; ++y) { + const int64_t i0 = tile_col*tile_dim + tid_row + block_rows*y; + const int64_t i1 = tile_row*tile_dim + tid_col; + if (i0 < args.ne00 && i1 < args.ne01) { + device const float * src = (device const float *) (src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); + shmem[(tid_row + block_rows*y)*tile_stride + tid_col] = src[0]; + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + for (int y = 0; y < 4; ++y) { + const int64_t i0 = tile_col*tile_dim + tid_col; + const int64_t i1 = tile_row*tile_dim + tid_row + block_rows*y; + if (i0 < args.ne0 && i1 < args.ne1) { + device float * dst_data = (device float *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + dst_data[0] = shmem[tid_col*tile_stride + tid_row + block_rows*y]; + } + } +} + +template [[host_name("kernel_cpy_f32_f32")]] kernel kernel_cpy_t kernel_cpy_t_t; +template [[host_name("kernel_cpy_f32_f16")]] kernel kernel_cpy_t kernel_cpy_t_t; +template [[host_name("kernel_cpy_f32_i32")]] kernel kernel_cpy_t kernel_cpy_t_t; +template [[host_name("kernel_cpy_i32_f32")]] kernel kernel_cpy_t kernel_cpy_t_t; +template [[host_name("kernel_cpy_i32_i32")]] kernel kernel_cpy_t kernel_cpy_t_t; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_cpy_f32_bf16")]] kernel kernel_cpy_t kernel_cpy_t_t; +#endif +template [[host_name("kernel_cpy_f16_f32")]] kernel kernel_cpy_t kernel_cpy_t_t; +template [[host_name("kernel_cpy_f16_f16")]] kernel kernel_cpy_t kernel_cpy_t_t; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_cpy_bf16_f32")]] kernel kernel_cpy_t kernel_cpy_t_t; +template [[host_name("kernel_cpy_bf16_bf16")]] kernel kernel_cpy_t kernel_cpy_t_t; +#endif + +template +kernel void kernel_cpy_f32_q( + constant ggml_metal_kargs_cpy & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int i03 = tgpig[2]; + const int i02 = tgpig[1]; + const int i01 = ntg[1] == 1 ? tgpig[0]%args.ne01 : tgpig[0]*ntg[1] + tiitg/ntg[0]; + const int iw0 = ntg[1] == 1 ? tgpig[0]/args.ne01 : 0; + + const int64_t n = i03*args.ne02*args.ne01*args.ne00 + i02*args.ne01*args.ne00 + i01*args.ne00; + + const int64_t i3 = n / (args.ne2*args.ne1*args.ne0); + const int64_t i2 = (n - i3*args.ne2*args.ne1*args.ne0) / (args.ne1*args.ne0); + const int64_t i1 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0) / args.ne0; + const int64_t i0 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0 - i1*args.ne0)/QK; + + device block_q * dst_data = (device block_q *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + for (int64_t i00 = iw0*ntg[0] + tiitg%ntg[0]; i00 < args.nk0; ) { + device const float * src = (device const float *)(src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + (i00*QK)*args.nb00); + + quantize_func(src, dst_data[i00]); + + break; + } +} + +typedef decltype(kernel_cpy_f32_q) cpy_f_q_t; + +template [[host_name("kernel_cpy_f32_q8_0")]] kernel cpy_f_q_t kernel_cpy_f32_q; +template [[host_name("kernel_cpy_f32_q1_0")]] kernel cpy_f_q_t kernel_cpy_f32_q; +template [[host_name("kernel_cpy_f32_q4_0")]] kernel cpy_f_q_t kernel_cpy_f32_q; +template [[host_name("kernel_cpy_f32_q4_1")]] kernel cpy_f_q_t kernel_cpy_f32_q; +template [[host_name("kernel_cpy_f32_q5_0")]] kernel cpy_f_q_t kernel_cpy_f32_q; +template [[host_name("kernel_cpy_f32_q5_1")]] kernel cpy_f_q_t kernel_cpy_f32_q; +template [[host_name("kernel_cpy_f32_iq4_nl")]] kernel cpy_f_q_t kernel_cpy_f32_q; + +template +kernel void kernel_cpy_q_f32( + constant ggml_metal_kargs_cpy & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const int i03 = tgpig[2]; + const int i02 = tgpig[1]; + const int i01 = ntg[1] == 1 ? tgpig[0]%args.ne01 : tgpig[0]*ntg[1] + tiitg/ntg[0]; + const int iw0 = ntg[1] == 1 ? tgpig[0]/args.ne01 : 0; + + const int64_t n = i03*args.ne02*args.ne01*args.ne00 + i02*args.ne01*args.ne00 + i01*args.ne00; + + const int64_t i3 = n/(args.ne2*args.ne1*args.ne0); + const int64_t i2 = (n - i3*args.ne2*args.ne1*args.ne0)/(args.ne1*args.ne0); + const int64_t i1 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0)/args.ne0; + const int64_t i0 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0 - i1*args.ne0); + + device const block_q * src_data = (device const block_q *)(src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); + device T4x4 * dst_data = (device T4x4 *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + for (int64_t i00 = iw0*ntg[0] + tiitg%ntg[0]; i00 < args.nk0; ) { + T4x4 temp; + dequantize_func(src_data + i00/nl, i00%nl, temp); + dst_data[i00] = temp; + + break; + } +} + +typedef decltype(kernel_cpy_q_f32) cpy_q_f_t; + +template [[host_name("kernel_cpy_q1_0_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q4_0_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q4_1_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q5_0_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q5_1_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q8_0_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; + +template [[host_name("kernel_cpy_q1_0_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q4_0_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q4_1_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q5_0_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q5_1_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; +template [[host_name("kernel_cpy_q8_0_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; + +kernel void kernel_concat( + constant ggml_metal_kargs_concat & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + + const int i3 = tgpig.z; + const int i2 = tgpig.y; + const int i1 = ntg.y == 1 ? tgpig.x : tgpig.x*ntg.y + tpitg.y; + + if (i1 >= args.ne1) { + return; + } + + int o[4] = {0, 0, 0, 0}; + o[args.dim] = args.dim == 0 ? args.ne00 : (args.dim == 1 ? args.ne01 : (args.dim == 2 ? args.ne02 : args.ne03)); + + device const float * x; + + for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { + if (i0 < args.ne00 && i1 < args.ne01 && i2 < args.ne02 && i3 < args.ne03) { + x = (device const float *)(src0 + (i3 )*args.nb03 + (i2 )*args.nb02 + (i1 )*args.nb01 + (i0 )*args.nb00); + } else { + x = (device const float *)(src1 + (i3 - o[3])*args.nb13 + (i2 - o[2])*args.nb12 + (i1 - o[1])*args.nb11 + (i0 - o[0])*args.nb10); + } + + device float * y = (device float *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + *y = *x; + } +} + +template +void kernel_mul_mv_q2_K_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_q2_K * x = (device const block_q2_K *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[32]; + float sumf[nr0]={0.f}; + + const short ix = tiisg/8; // 0...3 + const short it = tiisg%8; // 0...7 + const short iq = it/4; // 0 or 1 + const short ir = it%4; // 0...3 + const short is = (8*ir)/16;// 0 or 1 + + device const float * y4 = y + ix * QK_K + 128 * iq + 8 * ir; + + for (int ib = ix; ib < nb; ib += 4) { + float4 sumy = {0.f, 0.f, 0.f, 0.f}; + for (short i = 0; i < 8; ++i) { + yl[i+ 0] = y4[i+ 0]; sumy[0] += yl[i+ 0]; + yl[i+ 8] = y4[i+32]; sumy[1] += yl[i+ 8]; + yl[i+16] = y4[i+64]; sumy[2] += yl[i+16]; + yl[i+24] = y4[i+96]; sumy[3] += yl[i+24]; + } + + device const uint8_t * sc = (device const uint8_t *)x[ib].scales + 8*iq + is; + device const uint16_t * qs = (device const uint16_t *)x[ib].qs + 16 * iq + 4 * ir; + device const half * dh = &x[ib].d; + + for (short row = 0; row < nr0; row++) { + float4 acc1 = {0.f, 0.f, 0.f, 0.f}; + float4 acc2 = {0.f, 0.f, 0.f, 0.f}; + for (int i = 0; i < 8; i += 2) { + acc1[0] += yl[i+ 0] * (qs[i/2] & 0x0003); + acc2[0] += yl[i+ 1] * (qs[i/2] & 0x0300); + acc1[1] += yl[i+ 8] * (qs[i/2] & 0x000c); + acc2[1] += yl[i+ 9] * (qs[i/2] & 0x0c00); + acc1[2] += yl[i+16] * (qs[i/2] & 0x0030); + acc2[2] += yl[i+17] * (qs[i/2] & 0x3000); + acc1[3] += yl[i+24] * (qs[i/2] & 0x00c0); + acc2[3] += yl[i+25] * (qs[i/2] & 0xc000); + } + float dall = dh[0]; + float dmin = dh[1] * 1.f/16.f; + sumf[row] += dall * ((acc1[0] + 1.f/256.f * acc2[0]) * (sc[0] & 0xF) * 1.f/ 1.f + + (acc1[1] + 1.f/256.f * acc2[1]) * (sc[2] & 0xF) * 1.f/ 4.f + + (acc1[2] + 1.f/256.f * acc2[2]) * (sc[4] & 0xF) * 1.f/16.f + + (acc1[3] + 1.f/256.f * acc2[3]) * (sc[6] & 0xF) * 1.f/64.f) - + dmin * (sumy[0] * (sc[0] & 0xF0) + sumy[1] * (sc[2] & 0xF0) + sumy[2] * (sc[4] & 0xF0) + sumy[3] * (sc[6] & 0xF0)); + + qs += args.nb01/2; + sc += args.nb01; + dh += args.nb01/2; + } + + y4 += 4 * QK_K; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_q2_K_f32")]] +kernel void kernel_mul_mv_q2_K_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_q2_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_q3_K_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_q3_K * x = (device const block_q3_K *) (src0 + offset0); + device const float * yy = (device const float *) (src1 + offset1); + + float yl[32]; + + //const uint16_t kmask1 = 0x3030; + //const uint16_t kmask2 = 0x0f0f; + + const short tid = tiisg/4; + const short ix = tiisg%4; + const short ip = tid/4; // 0 or 1 + const short il = 2*((tid%4)/2); // 0 or 2 + const short ir = tid%2; + const short l0 = 8*ir; + + // One would think that the Metal compiler would figure out that ip and il can only have + // 4 possible states, and optimize accordingly. Well, no. It needs help, and we do it + // with these two tales. + // + // Possible masks for the high bit + const ushort4 mm[4] = {{0x0001, 0x0100, 0x0002, 0x0200}, // ip = 0, il = 0 + {0x0004, 0x0400, 0x0008, 0x0800}, // ip = 0, il = 2 + {0x0010, 0x1000, 0x0020, 0x2000}, // ip = 1, il = 0 + {0x0040, 0x4000, 0x0080, 0x8000}}; // ip = 1, il = 2 + + // Possible masks for the low 2 bits + const int4 qm[2] = {{0x0003, 0x0300, 0x000c, 0x0c00}, {0x0030, 0x3000, 0x00c0, 0xc000}}; + + const ushort4 hm = mm[2*ip + il/2]; + + const short shift = 2*il; + + const float v1 = il == 0 ? 4.f : 64.f; + const float v2 = 4.f * v1; + + const uint16_t s_shift1 = 4*ip; + const uint16_t s_shift2 = s_shift1 + il; + + const short q_offset = 32*ip + l0; + const short y_offset = 128*ip + 32*il + l0; + + device const float * y1 = yy + ix*QK_K + y_offset; + + uint32_t scales32, aux32; + thread uint16_t * scales16 = (thread uint16_t *)&scales32; + thread const int8_t * scales = (thread const int8_t *)&scales32; + + float sumf1[nr0] = {0.f}; + float sumf2[nr0] = {0.f}; + + for (int i = ix; i < nb; i += 4) { + for (short l = 0; l < 8; ++l) { + yl[l+ 0] = y1[l+ 0]; + yl[l+ 8] = y1[l+16]; + yl[l+16] = y1[l+32]; + yl[l+24] = y1[l+48]; + } + + device const uint16_t * q = (device const uint16_t *)(x[i].qs + q_offset); + device const uint16_t * h = (device const uint16_t *)(x[i].hmask + l0); + device const uint16_t * a = (device const uint16_t *)(x[i].scales); + device const half * dh = &x[i].d; + + for (short row = 0; row < nr0; ++row) { + const float d_all = (float)dh[0]; + + scales16[0] = a[4]; + scales16[1] = a[5]; + aux32 = ((scales32 >> s_shift2) << 4) & 0x30303030; + scales16[0] = a[il+0]; + scales16[1] = a[il+1]; + scales32 = ((scales32 >> s_shift1) & 0x0f0f0f0f) | aux32; + + float s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, s6 = 0; + for (short l = 0; l < 8; l += 2) { + const int32_t qs = q[l/2]; + s1 += yl[l+0] * (qs & qm[il/2][0]); + s2 += yl[l+1] * (qs & qm[il/2][1]); + s3 += ((h[l/2] & hm[0]) ? 0.f : yl[l+0]) + ((h[l/2] & hm[1]) ? 0.f : yl[l+1]); + s4 += yl[l+16] * (qs & qm[il/2][2]); + s5 += yl[l+17] * (qs & qm[il/2][3]); + s6 += ((h[l/2] & hm[2]) ? 0.f : yl[l+16]) + ((h[l/2] & hm[3]) ? 0.f : yl[l+17]); + } + float d1 = d_all * (s1 + 1.f/256.f * s2 - s3*v1); + float d2 = d_all * (s4 + 1.f/256.f * s5 - s6*v2); + sumf1[row] += d1 * (scales[0] - 32); + sumf2[row] += d2 * (scales[2] - 32); + + s1 = s2 = s3 = s4 = s5 = s6 = 0; + for (short l = 0; l < 8; l += 2) { + const int32_t qs = q[l/2+8]; + s1 += yl[l+8] * (qs & qm[il/2][0]); + s2 += yl[l+9] * (qs & qm[il/2][1]); + s3 += ((h[l/2+8] & hm[0]) ? 0.f : yl[l+8]) + ((h[l/2+8] & hm[1]) ? 0.f : yl[l+9]); + s4 += yl[l+24] * (qs & qm[il/2][2]); + s5 += yl[l+25] * (qs & qm[il/2][3]); + s6 += ((h[l/2+8] & hm[2]) ? 0.f : yl[l+24]) + ((h[l/2+8] & hm[3]) ? 0.f : yl[l+25]); + } + d1 = d_all * (s1 + 1.f/256.f * s2 - s3*v1); + d2 = d_all * (s4 + 1.f/256.f * s5 - s6*v2); + sumf1[row] += d1 * (scales[1] - 32); + sumf2[row] += d2 * (scales[3] - 32); + + q += args.nb01/2; + h += args.nb01/2; + a += args.nb01/2; + dh += args.nb01/2; + } + + y1 += 4 * QK_K; + } + + for (int row = 0; row < nr0; ++row) { + const float sumf = (sumf1[row] + 0.25f * sumf2[row]) / (1 << shift); + sumf1[row] = simd_sum(sumf); + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + if (tiisg == 0) { + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + dst_f32[first_row + row] = sumf1[row]; + } + } +} + +[[host_name("kernel_mul_mv_q3_K_f32")]] +kernel void kernel_mul_mv_q3_K_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_q3_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_q4_K_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + constexpr uint16_t kmask1 = 0x3f3f; + constexpr uint16_t kmask2 = 0x0f0f; + constexpr uint16_t kmask3 = 0xc0c0; + + const short ix = tiisg/8; // 0...3 + const short it = tiisg%8; // 0...7 + const short iq = it/4; // 0 or 1 + const short ir = it%4; // 0...3 + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_q4_K * x = (device const block_q4_K *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[16]; + float yh[16]; + + float sumf[nr0]={0.f}; + + device const float * y4 = y + ix * QK_K + 64 * iq + 8 * ir; + + uint16_t sc16[4]; + thread const uint8_t * sc8 = (thread const uint8_t *)sc16; + + for (int ib = ix; ib < nb; ib += 4) { + float4 sumy = {0.f, 0.f, 0.f, 0.f}; + + for (short i = 0; i < 8; ++i) { + yl[i+0] = y4[i+ 0]; sumy[0] += yl[i+0]; + yl[i+8] = y4[i+ 32]; sumy[1] += yl[i+8]; + yh[i+0] = y4[i+128]; sumy[2] += yh[i+0]; + yh[i+8] = y4[i+160]; sumy[3] += yh[i+8]; + } + + device const uint16_t * sc = (device const uint16_t *)x[ib].scales + iq; + device const uint16_t * q1 = (device const uint16_t *)x[ib].qs + 16 * iq + 4 * ir; + device const half * dh = &x[ib].d; + + for (short row = 0; row < nr0; row++) { + sc16[0] = sc[0] & kmask1; + sc16[1] = sc[2] & kmask1; + sc16[2] = ((sc[4] >> 0) & kmask2) | ((sc[0] & kmask3) >> 2); + sc16[3] = ((sc[4] >> 4) & kmask2) | ((sc[2] & kmask3) >> 2); + + device const uint16_t * q2 = q1 + 32; + + float4 acc1 = {0.f, 0.f, 0.f, 0.f}; + float4 acc2 = {0.f, 0.f, 0.f, 0.f}; + + FOR_UNROLL (short i = 0; i < 4; ++i) { + acc1[0] += yl[2*i + 0] * (q1[i] & 0x000F); + acc1[1] += yl[2*i + 1] * (q1[i] & 0x0F00); + acc1[2] += yl[2*i + 8] * (q1[i] & 0x00F0); + acc1[3] += yl[2*i + 9] * (q1[i] & 0xF000); + acc2[0] += yh[2*i + 0] * (q2[i] & 0x000F); + acc2[1] += yh[2*i + 1] * (q2[i] & 0x0F00); + acc2[2] += yh[2*i + 8] * (q2[i] & 0x00F0); + acc2[3] += yh[2*i + 9] * (q2[i] & 0xF000); + } + + sumf[row] += dh[0] * ((acc1[0] + 1.f/256.f * acc1[1]) * sc8[0] + + (acc1[2] + 1.f/256.f * acc1[3]) * sc8[1] * 1.f/16.f + + (acc2[0] + 1.f/256.f * acc2[1]) * sc8[4] + + (acc2[2] + 1.f/256.f * acc2[3]) * sc8[5] * 1.f/16.f) - + dh[1] * (sumy[0] * sc8[2] + sumy[1] * sc8[3] + sumy[2] * sc8[6] + sumy[3] * sc8[7]); + + q1 += args.nb01/2; + sc += args.nb01/2; + dh += args.nb01/2; + } + + y4 += 4 * QK_K; + } + + device float * dst_f32 = (device float *) dst + (int64_t)im*args.ne0*args.ne1 + (int64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_q4_K_f32")]] +kernel void kernel_mul_mv_q4_K_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_q4_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_q5_K_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_q5_K * x = (device const block_q5_K *) (src0 + offset0); + device const float * yy = (device const float *) (src1 + offset1); + + float sumf[nr0]={0.f}; + + float yl[16], yh[16]; + + constexpr uint16_t kmask1 = 0x3f3f; + constexpr uint16_t kmask2 = 0x0f0f; + constexpr uint16_t kmask3 = 0xc0c0; + + const short tid = tiisg/4; + const short ix = tiisg%4; + const short iq = tid/4; + const short ir = tid%4; + + const short l0 = 8*ir; + const short q_offset = 32*iq + l0; + const short y_offset = 64*iq + l0; + + const uint8_t hm1 = 1u << (2*iq); + const uint8_t hm2 = hm1 << 1; + const uint8_t hm3 = hm1 << 4; + const uint8_t hm4 = hm2 << 4; + + uint16_t sc16[4]; + thread const uint8_t * sc8 = (thread const uint8_t *)sc16; + + device const float * y1 = yy + ix*QK_K + y_offset; + + for (int i = ix; i < nb; i += 4) { + device const uint8_t * q1 = x[i].qs + q_offset; + device const uint8_t * qh = x[i].qh + l0; + device const half * dh = &x[i].d; + device const uint16_t * a = (device const uint16_t *)x[i].scales + iq; + + device const float * y2 = y1 + 128; + float4 sumy = {0.f, 0.f, 0.f, 0.f}; + for (short l = 0; l < 8; ++l) { + yl[l+0] = y1[l+ 0]; sumy[0] += yl[l+0]; + yl[l+8] = y1[l+32]; sumy[1] += yl[l+8]; + yh[l+0] = y2[l+ 0]; sumy[2] += yh[l+0]; + yh[l+8] = y2[l+32]; sumy[3] += yh[l+8]; + } + + for (short row = 0; row < nr0; ++row) { + device const uint8_t * q2 = q1 + 64; + + sc16[0] = a[0] & kmask1; + sc16[1] = a[2] & kmask1; + sc16[2] = ((a[4] >> 0) & kmask2) | ((a[0] & kmask3) >> 2); + sc16[3] = ((a[4] >> 4) & kmask2) | ((a[2] & kmask3) >> 2); + + float4 acc1 = {0.f}; + float4 acc2 = {0.f}; + FOR_UNROLL (short l = 0; l < 8; ++l) { + uint8_t h = qh[l]; + acc1[0] += yl[l+0] * (q1[l] & 0x0F); + acc1[1] += yl[l+8] * (q1[l] & 0xF0); + acc1[2] += yh[l+0] * (q2[l] & 0x0F); + acc1[3] += yh[l+8] * (q2[l] & 0xF0); + acc2[0] += h & hm1 ? yl[l+0] : 0.f; + acc2[1] += h & hm2 ? yl[l+8] : 0.f; + acc2[2] += h & hm3 ? yh[l+0] : 0.f; + acc2[3] += h & hm4 ? yh[l+8] : 0.f; + } + + sumf[row] += dh[0] * (sc8[0] * (acc1[0] + 16.f*acc2[0]) + + sc8[1] * (acc1[1]/16.f + 16.f*acc2[1]) + + sc8[4] * (acc1[2] + 16.f*acc2[2]) + + sc8[5] * (acc1[3]/16.f + 16.f*acc2[3])) - + dh[1] * (sumy[0] * sc8[2] + sumy[1] * sc8[3] + sumy[2] * sc8[6] + sumy[3] * sc8[7]); + + q1 += args.nb01; + qh += args.nb01; + dh += args.nb01/2; + a += args.nb01/2; + } + + y1 += 4 * QK_K; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + const float tot = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = tot; + } + } +} + +[[host_name("kernel_mul_mv_q5_K_f32")]] +kernel void kernel_mul_mv_q5_K_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_q5_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_q6_K_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + constexpr uint8_t kmask1 = 0x03; + constexpr uint8_t kmask2 = 0x0C; + constexpr uint8_t kmask3 = 0x30; + constexpr uint8_t kmask4 = 0xC0; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_q6_K * x = (device const block_q6_K *) (src0 + offset0); + device const float * yy = (device const float *) (src1 + offset1); + + float sumf[nr0] = { 0.f }; + + float yl[16]; + + const short tid = tiisg/2; + const short ix = tiisg%2; + const short ip = tid/8; // 0 or 1 + const short il = tid%8; + const short l0 = 4*il; + const short is = 8*ip + l0/16; + + const short y_offset = 128*ip + l0; + const short q_offset_l = 64*ip + l0; + const short q_offset_h = 32*ip + l0; + + for (int i = ix; i < nb; i += 2) { + device const uint8_t * q1 = x[i].ql + q_offset_l; + device const uint8_t * q2 = q1 + 32; + device const uint8_t * qh = x[i].qh + q_offset_h; + device const int8_t * sc = x[i].scales + is; + device const half * dh = &x[i].d; + + device const float * y = yy + i * QK_K + y_offset; + + for (short l = 0; l < 4; ++l) { + yl[4*l + 0] = y[l + 0]; + yl[4*l + 1] = y[l + 32]; + yl[4*l + 2] = y[l + 64]; + yl[4*l + 3] = y[l + 96]; + } + + for (short row = 0; row < nr0; ++row) { + float4 sums = {0.f, 0.f, 0.f, 0.f}; + + FOR_UNROLL (short l = 0; l < 4; ++l) { + sums[0] += yl[4*l + 0] * ((int8_t)((q1[l] & 0xF) | ((qh[l] & kmask1) << 4)) - 32); + sums[1] += yl[4*l + 1] * ((int8_t)((q2[l] & 0xF) | ((qh[l] & kmask2) << 2)) - 32); + sums[2] += yl[4*l + 2] * ((int8_t)((q1[l] >> 4) | ((qh[l] & kmask3) << 0)) - 32); + sums[3] += yl[4*l + 3] * ((int8_t)((q2[l] >> 4) | ((qh[l] & kmask4) >> 2)) - 32); + } + + sumf[row] += dh[0] * (sums[0] * sc[0] + sums[1] * sc[2] + sums[2] * sc[4] + sums[3] * sc[6]); + + q1 += args.nb01; + q2 += args.nb01; + qh += args.nb01; + sc += args.nb01; + dh += args.nb01/2; + } + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_q6_K_f32")]] +kernel void kernel_mul_mv_q6_K_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_q6_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); +} + +// ======================= "True" 2-bit + +template +void kernel_mul_mv_iq2_xxs_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq2_xxs * x = (device const block_iq2_xxs *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[32]; + float sumf[nr0]={0.f}; + + const int nb32 = nb * (QK_K / 32); + + threadgroup uint64_t * svalues = (threadgroup uint64_t *)(shmem); + threadgroup uint8_t * ssigns = (threadgroup uint8_t *)(svalues + 256); + { + int nval = 4; + int pos = (32*sgitg + tiisg)*nval; + for (int i = 0; i < nval; ++i) svalues[pos + i] = iq2xxs_grid[pos + i]; + nval = 2; + pos = (32*sgitg + tiisg)*nval; + for (int i = 0; i < nval; ++i) ssigns[pos+i] = ksigns_iq2xs[pos+i]; + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + const int ix = tiisg; + + device const float * y4 = y + 32 * ix; + + for (int ib32 = ix; ib32 < nb32; ib32 += 32) { + for (short i = 0; i < 32; ++i) { + yl[i] = y4[i]; + } + + const int ibl = ib32 / (QK_K / 32); + const int ib = ib32 % (QK_K / 32); + + device const block_iq2_xxs * xr = x + ibl; + device const uint16_t * q2 = xr->qs + 4 * ib; + device const half * dh = &xr->d; + + for (short row = 0; row < nr0; row++) { + const float db = dh[0]; + device const uint8_t * aux8 = (device const uint8_t *)q2; + const uint32_t aux32 = q2[2] | (q2[3] << 16); + const float d = db * (0.5f + (aux32 >> 28)); + + float sum = 0; + for (short l = 0; l < 4; ++l) { + const threadgroup uint8_t * grid = (const threadgroup uint8_t *)(svalues + aux8[l]); + const uint8_t signs = ssigns[(aux32 >> 7*l) & 127]; + for (short j = 0; j < 8; ++j) { + sum += yl[8*l + j] * grid[j] * (signs & kmask_iq2xs[j] ? -1.f : 1.f); + } + } + sumf[row] += d * sum; + + dh += args.nb01/2; + q2 += args.nb01/2; + } + + y4 += 32 * 32; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all * 0.25f; + } + } +} + +[[host_name("kernel_mul_mv_iq2_xxs_f32")]] +kernel void kernel_mul_mv_iq2_xxs_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + kernel_mul_mv_iq2_xxs_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_iq2_xs_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq2_xs * x = (device const block_iq2_xs *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[32]; + float sumf[nr0]={0.f}; + + const int nb32 = nb * (QK_K / 32); + + threadgroup uint64_t * svalues = (threadgroup uint64_t *)(shmem); + threadgroup uint8_t * ssigns = (threadgroup uint8_t *)(svalues + 512); + { + int nval = 8; + int pos = (32*sgitg + tiisg)*nval; + for (int i = 0; i < nval; ++i) svalues[pos + i] = iq2xs_grid[pos + i]; + nval = 2; + pos = (32*sgitg + tiisg)*nval; + for (int i = 0; i < nval; ++i) ssigns[pos+i] = ksigns_iq2xs[pos+i]; + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + const int ix = tiisg; + + device const float * y4 = y + 32 * ix; + + for (int ib32 = ix; ib32 < nb32; ib32 += 32) { + for (short i = 0; i < 32; ++i) { + yl[i] = y4[i]; + } + + const int ibl = ib32 / (QK_K / 32); + const int ib = ib32 % (QK_K / 32); + + device const block_iq2_xs * xr = x + ibl; + device const uint16_t * q2 = xr->qs + 4 * ib; + device const uint8_t * sc = xr->scales + ib; + device const half * dh = &xr->d; + + for (short row = 0; row < nr0; row++) { + const float db = dh[0]; + const uint8_t ls1 = sc[0] & 0xf; + const uint8_t ls2 = sc[0] >> 4; + const float d1 = db * (0.5f + ls1); + const float d2 = db * (0.5f + ls2); + + float sum1 = 0, sum2 = 0; + for (short l = 0; l < 2; ++l) { + const threadgroup uint8_t * grid = (const threadgroup uint8_t *)(svalues + (q2[l] & 511)); + const uint8_t signs = ssigns[(q2[l] >> 9)]; + for (short j = 0; j < 8; ++j) { + sum1 += yl[8*l + j] * grid[j] * (signs & kmask_iq2xs[j] ? -1.f : 1.f); + } + } + for (short l = 2; l < 4; ++l) { + const threadgroup uint8_t * grid = (const threadgroup uint8_t *)(svalues + (q2[l] & 511)); + const uint8_t signs = ssigns[(q2[l] >> 9)]; + for (short j = 0; j < 8; ++j) { + sum2 += yl[8*l + j] * grid[j] * (signs & kmask_iq2xs[j] ? -1.f : 1.f); + } + } + sumf[row] += d1 * sum1 + d2 * sum2; + + dh += args.nb01/2; + q2 += args.nb01/2; + sc += args.nb01; + } + + y4 += 32 * 32; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all * 0.25f; + } + } +} + +[[host_name("kernel_mul_mv_iq2_xs_f32")]] +kernel void kernel_mul_mv_iq2_xs_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_iq2_xs_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_iq3_xxs_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq3_xxs * x = (device const block_iq3_xxs *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[32]; + float sumf[nr0]={0.f}; + + const int nb32 = nb * (QK_K / 32); + + threadgroup uint32_t * svalues = (threadgroup uint32_t *)(shmem); + threadgroup uint8_t * ssigns = (threadgroup uint8_t *)(svalues + 256); + { + int nval = 4; + int pos = (32*sgitg + tiisg)*nval; + for (int i = 0; i < nval; ++i) svalues[pos + i] = iq3xxs_grid[pos + i]; + nval = 2; + pos = (32*sgitg + tiisg)*nval; + for (int i = 0; i < nval; ++i) ssigns[pos+i] = ksigns_iq2xs[pos+i]; + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + const int ix = tiisg; + + device const float * y4 = y + 32 * ix; + + for (int ib32 = ix; ib32 < nb32; ib32 += 32) { + for (short i = 0; i < 32; ++i) { + yl[i] = y4[i]; + } + + const int ibl = ib32 / (QK_K / 32); + const int ib = ib32 % (QK_K / 32); + + device const block_iq3_xxs * xr = x + ibl; + device const uint8_t * q3 = xr->qs + 8 * ib; + device const uint16_t * gas = (device const uint16_t *)(xr->qs + QK_K/4) + 2 * ib; + device const half * dh = &xr->d; + + for (short row = 0; row < nr0; row++) { + const float db = dh[0]; + const uint32_t aux32 = gas[0] | (gas[1] << 16); + const float d = db * (0.5f + (aux32 >> 28)); + + float2 sum = {0}; + for (short l = 0; l < 4; ++l) { + const threadgroup uint8_t * grid1 = (const threadgroup uint8_t *)(svalues + q3[2*l+0]); + const threadgroup uint8_t * grid2 = (const threadgroup uint8_t *)(svalues + q3[2*l+1]); + const uint8_t signs = ssigns[(aux32 >> 7*l) & 127]; + for (short j = 0; j < 4; ++j) { + sum[0] += yl[8*l + j + 0] * grid1[j] * (signs & kmask_iq2xs[j+0] ? -1.f : 1.f); + sum[1] += yl[8*l + j + 4] * grid2[j] * (signs & kmask_iq2xs[j+4] ? -1.f : 1.f); + } + } + sumf[row] += d * (sum[0] + sum[1]); + + dh += args.nb01/2; + q3 += args.nb01; + gas += args.nb01/2; + } + + y4 += 32 * 32; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all * 0.5f; + } + } +} + +[[host_name("kernel_mul_mv_iq3_xxs_f32")]] +kernel void kernel_mul_mv_iq3_xxs_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_iq3_xxs_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_iq3_s_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq3_s * x = (device const block_iq3_s *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[32]; + float sumf[nr0]={0.f}; + + const int nb32 = nb * (QK_K / 32); + + threadgroup uint32_t * svalues = (threadgroup uint32_t *) shmem; + { + int nval = 8; + int pos = (32*sgitg + tiisg)*nval; + for (int i = 0; i < nval; ++i) svalues[pos + i] = iq3s_grid[pos + i]; + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + const int ix = tiisg; + + device const float * y4 = y + 32 * ix; + + for (int ib32 = ix; ib32 < nb32; ib32 += 32) { + for (short i = 0; i < 32; ++i) { + yl[i] = y4[i]; + } + + const int ibl = ib32 / (QK_K / 32); + const int ib = ib32 % (QK_K / 32); + + device const block_iq3_s * xr = x + ibl; + device const uint8_t * qs = xr->qs + 8 * ib; + device const uint8_t * qh = xr->qh + ib; + device const uint8_t * sc = xr->scales + (ib/2); + device const uint8_t * signs = xr->signs + 4 * ib; + device const half * dh = &xr->d; + + for (short row = 0; row < nr0; row++) { + const float db = dh[0]; + const float d = db * (1 + 2*((sc[0] >> 4*(ib%2)) & 0xf)); + + float2 sum = {0}; + for (short l = 0; l < 4; ++l) { + const threadgroup uint32_t * table1 = qh[0] & kmask_iq2xs[2*l+0] ? svalues + 256 : svalues; + const threadgroup uint32_t * table2 = qh[0] & kmask_iq2xs[2*l+1] ? svalues + 256 : svalues; + const threadgroup uint8_t * grid1 = (const threadgroup uint8_t *)(table1 + qs[2*l+0]); + const threadgroup uint8_t * grid2 = (const threadgroup uint8_t *)(table2 + qs[2*l+1]); + for (short j = 0; j < 4; ++j) { + sum[0] += yl[8*l + j + 0] * grid1[j] * select(1, -1, signs[l] & kmask_iq2xs[j+0]); + sum[1] += yl[8*l + j + 4] * grid2[j] * select(1, -1, signs[l] & kmask_iq2xs[j+4]); + } + } + sumf[row] += d * (sum[0] + sum[1]); + + dh += args.nb01/2; + qs += args.nb01; + qh += args.nb01; + sc += args.nb01; + signs += args.nb01; + } + + y4 += 32 * 32; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_iq3_s_f32")]] +kernel void kernel_mul_mv_iq3_s_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_iq3_s_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_iq2_s_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq2_s * x = (device const block_iq2_s *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[32]; + float sumf[nr0]={0.f}; + + const int nb32 = nb * (QK_K / 32); + + //threadgroup uint64_t * svalues = (threadgroup uint64_t *) shmem; + //{ + // int nval = 32; + // int pos = (32*sgitg + tiisg)*nval; + // for (int i = 0; i < nval; ++i) svalues[pos + i] = iq2s_grid[pos + i]; + // threadgroup_barrier(mem_flags::mem_threadgroup); + //} + + const short ix = tiisg; + + device const float * y4 = y + 32 * ix; + + for (int ib32 = ix; ib32 < nb32; ib32 += 32) { + for (short i = 0; i < 32; ++i) { + yl[i] = y4[i]; + } + + const int ibl = ib32 / (QK_K / 32); + const int ib = ib32 % (QK_K / 32); + + device const block_iq2_s * xr = x + ibl; + device const uint8_t * qs = xr->qs + 4 * ib; + device const uint8_t * qh = xr->qh + ib; + device const uint8_t * sc = xr->scales + ib; + device const uint8_t * signs = qs + QK_K/8; + device const half * dh = &xr->d; + + for (short row = 0; row < nr0; row++) { + const float db = dh[0]; + const float d1 = db * (0.5f + (sc[0] & 0xf)); + const float d2 = db * (0.5f + (sc[0] >> 4)); + + float2 sum = {0}; + for (short l = 0; l < 2; ++l) { + //const threadgroup uint8_t * grid1 = (const threadgroup uint8_t *)(svalues + (qs[l+0] | ((qh[0] << (8-2*l)) & 0x300))); + //const threadgroup uint8_t * grid2 = (const threadgroup uint8_t *)(svalues + (qs[l+2] | ((qh[0] << (4-2*l)) & 0x300))); + constant uint8_t * grid1 = (constant uint8_t *)(iq2s_grid + (qs[l+0] | ((qh[0] << (8-2*l)) & 0x300))); + constant uint8_t * grid2 = (constant uint8_t *)(iq2s_grid + (qs[l+2] | ((qh[0] << (4-2*l)) & 0x300))); + for (short j = 0; j < 8; ++j) { + sum[0] += yl[8*l + j + 0] * grid1[j] * select(1, -1, signs[l+0] & kmask_iq2xs[j]); + sum[1] += yl[8*l + j + 16] * grid2[j] * select(1, -1, signs[l+2] & kmask_iq2xs[j]); + } + } + sumf[row] += d1 * sum[0] + d2 * sum[1]; + + dh += args.nb01/2; + qs += args.nb01; + qh += args.nb01; + sc += args.nb01; + signs += args.nb01; + } + + y4 += 32 * 32; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all * 0.25f; + } + } +} + +[[host_name("kernel_mul_mv_iq2_s_f32")]] +kernel void kernel_mul_mv_iq2_s_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_iq2_s_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_iq1_s_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq1_s * x = (device const block_iq1_s *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[32]; + float sumf[nr0]={0.f}; + + const int nb32 = nb * (QK_K / 32); + + const short ix = tiisg; + + device const float * y4 = y + 32 * ix; + + for (int ib32 = ix; ib32 < nb32; ib32 += 32) { + float sumy = 0; + for (short i = 0; i < 32; ++i) { + yl[i] = y4[i]; + sumy += yl[i]; + } + + const int ibl = ib32 / (QK_K / 32); + const int ib = ib32 % (QK_K / 32); + + device const block_iq1_s * xr = x + ibl; + device const uint8_t * qs = xr->qs + 4 * ib; + device const uint16_t * qh = xr->qh + ib; + device const half * dh = &xr->d; + + for (short row = 0; row < nr0; row++) { + constant uint8_t * grid1 = (constant uint8_t *)(iq1s_grid_gpu + (qs[0] | ((qh[0] << 8) & 0x700))); + constant uint8_t * grid2 = (constant uint8_t *)(iq1s_grid_gpu + (qs[1] | ((qh[0] << 5) & 0x700))); + constant uint8_t * grid3 = (constant uint8_t *)(iq1s_grid_gpu + (qs[2] | ((qh[0] << 2) & 0x700))); + constant uint8_t * grid4 = (constant uint8_t *)(iq1s_grid_gpu + (qs[3] | ((qh[0] >> 1) & 0x700))); + + float sum = 0; + for (short j = 0; j < 4; ++j) { + sum += yl[j+ 0] * (grid1[j] & 0xf) + yl[j+ 4] * (grid1[j] >> 4) + + yl[j+ 8] * (grid2[j] & 0xf) + yl[j+12] * (grid2[j] >> 4) + + yl[j+16] * (grid3[j] & 0xf) + yl[j+20] * (grid3[j] >> 4) + + yl[j+24] * (grid4[j] & 0xf) + yl[j+28] * (grid4[j] >> 4); + } + sumf[row] += (float)dh[0] * (sum + sumy * (qh[0] & 0x8000 ? -1 - IQ1S_DELTA : -1 + IQ1S_DELTA)) * (2*((qh[0] >> 12) & 7) + 1); + + dh += args.nb01/2; + qs += args.nb01; + qh += args.nb01/2; + } + + y4 += 32 * 32; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_iq1_s_f32")]] +kernel void kernel_mul_mv_iq1_s_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_iq1_s_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_iq1_m_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + const int nb = args.ne00/QK_K; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * nr0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq1_m * x = (device const block_iq1_m *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + float yl[32]; + float sumf[nr0]={0.f}; + + const int nb32 = nb * (QK_K / 32); + + const short ix = tiisg; + + device const float * y4 = y + 32 * ix; + + iq1m_scale_t scale; + + for (int ib32 = ix; ib32 < nb32; ib32 += 32) { + float4 sumy = {0.f}; + for (short i = 0; i < 8; ++i) { + yl[i+ 0] = y4[i+ 0]; sumy[0] += yl[i+ 0]; + yl[i+ 8] = y4[i+ 8]; sumy[1] += yl[i+ 8]; + yl[i+16] = y4[i+16]; sumy[2] += yl[i+16]; + yl[i+24] = y4[i+24]; sumy[3] += yl[i+24]; + } + + const int ibl = ib32 / (QK_K / 32); + const int ib = ib32 % (QK_K / 32); + + device const block_iq1_m * xr = x + ibl; + device const uint8_t * qs = xr->qs + 4 * ib; + device const uint8_t * qh = xr->qh + 2 * ib; + device const uint16_t * sc = (device const uint16_t *)xr->scales; + + for (short row = 0; row < nr0; row++) { + scale.u16 = (sc[0] >> 12) | ((sc[1] >> 8) & 0x00f0) | ((sc[2] >> 4) & 0x0f00) | (sc[3] & 0xf000); + + constant uint8_t * grid1 = (constant uint8_t *)(iq1s_grid_gpu + (qs[0] | ((qh[0] << 8) & 0x700))); + constant uint8_t * grid2 = (constant uint8_t *)(iq1s_grid_gpu + (qs[1] | ((qh[0] << 4) & 0x700))); + constant uint8_t * grid3 = (constant uint8_t *)(iq1s_grid_gpu + (qs[2] | ((qh[1] << 8) & 0x700))); + constant uint8_t * grid4 = (constant uint8_t *)(iq1s_grid_gpu + (qs[3] | ((qh[1] << 4) & 0x700))); + + float2 sum = {0.f}; + for (short j = 0; j < 4; ++j) { + sum[0] += yl[j+ 0] * (grid1[j] & 0xf) + yl[j+ 4] * (grid1[j] >> 4) + + yl[j+ 8] * (grid2[j] & 0xf) + yl[j+12] * (grid2[j] >> 4); + sum[1] += yl[j+16] * (grid3[j] & 0xf) + yl[j+20] * (grid3[j] >> 4) + + yl[j+24] * (grid4[j] & 0xf) + yl[j+28] * (grid4[j] >> 4); + } + const float delta1 = sumy[0] * (qh[0] & 0x08 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA) + sumy[1] * (qh[0] & 0x80 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA); + const float delta2 = sumy[2] * (qh[1] & 0x08 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA) + sumy[3] * (qh[1] & 0x80 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA); + + sumf[row] += (float)scale.f16 * ((sum[0] + delta1) * (2*((sc[ib/2] >> (6*(ib%2)+0)) & 7) + 1) + + (sum[1] + delta2) * (2*((sc[ib/2] >> (6*(ib%2)+3)) & 7) + 1)); + + sc += args.nb01/2; + qs += args.nb01; + qh += args.nb01; + } + + y4 += 32 * 32; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_iq1_m_f32")]] +kernel void kernel_mul_mv_iq1_m_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_iq1_m_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_iq4_nl_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + threadgroup float * shmem_f32 = (threadgroup float *) shmem; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * NR0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq4_nl * x = (device const block_iq4_nl *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + const int nb = args.ne00/QK4_NL; + const int ns01 = args.nb01/args.nb00; + + const short ix = tiisg/2; // 0...15 + const short it = tiisg%2; // 0 or 1 + + shmem_f32[tiisg] = kvalues_iq4nl_f[tiisg%16]; + threadgroup_barrier(mem_flags::mem_threadgroup); + + float4 yl[4]; + float sumf[NR0]={0.f}; + + device const float * yb = y + ix*QK4_NL + it*8; + + uint32_t aux32[2]; + thread const uint8_t * q8 = (thread const uint8_t *)aux32; + + float4 qf1, qf2; + + // [TAG_MUL_MV_WEIRD] + for (int ib = ix; ib < nb && ib < ns01; ib += 16) { + device const float4 * y4 = (device const float4 *)yb; + yl[0] = y4[0]; + yl[1] = y4[4]; + yl[2] = y4[1]; + yl[3] = y4[5]; + + for (short row = 0; row < NR0; row++) { + device const block_iq4_nl & xb = x[row*ns01 + ib]; + device const uint16_t * q4 = (device const uint16_t *)(xb.qs + 8*it); + + float4 acc1 = {0.f}, acc2 = {0.f}; + + aux32[0] = q4[0] | (q4[1] << 16); + aux32[1] = (aux32[0] >> 4) & 0x0f0f0f0f; + aux32[0] &= 0x0f0f0f0f; + qf1 = {shmem_f32[q8[0]], shmem_f32[q8[1]], shmem_f32[q8[2]], shmem_f32[q8[3]]}; + qf2 = {shmem_f32[q8[4]], shmem_f32[q8[5]], shmem_f32[q8[6]], shmem_f32[q8[7]]}; + acc1 += yl[0] * qf1; + acc2 += yl[1] * qf2; + + aux32[0] = q4[2] | (q4[3] << 16); + aux32[1] = (aux32[0] >> 4) & 0x0f0f0f0f; + aux32[0] &= 0x0f0f0f0f; + qf1 = {shmem_f32[q8[0]], shmem_f32[q8[1]], shmem_f32[q8[2]], shmem_f32[q8[3]]}; + qf2 = {shmem_f32[q8[4]], shmem_f32[q8[5]], shmem_f32[q8[6]], shmem_f32[q8[7]]}; + acc1 += yl[2] * qf1; + acc2 += yl[3] * qf2; + + acc1 += acc2; + + sumf[row] += (float)xb.d * (acc1[0] + acc1[1] + acc1[2] + acc1[3]); + } + + yb += 16 * QK4_NL; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < NR0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_iq4_nl_f32")]] +kernel void kernel_mul_mv_iq4_nl_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_iq4_nl_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_iq4_xs_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + threadgroup float * shmem_f32 = (threadgroup float *) shmem; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + const int first_row = (r0 * NSG + sgitg) * NR0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_iq4_xs * x = (device const block_iq4_xs *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + const int nb = args.ne00/QK_K; + const int ns01 = args.nb01/args.nb00; + + const short ix = tiisg/16; // 0 or 1 + const short it = tiisg%16; // 0...15 + const short ib = it/2; + const short il = it%2; + + shmem_f32[tiisg] = kvalues_iq4nl_f[tiisg%16]; + threadgroup_barrier(mem_flags::mem_threadgroup); + + float4 yl[4]; + float sumf[NR0]={0.f}; + + device const float * yb = y + ix * QK_K + ib * 32 + il * 8; + + uint32_t aux32[2]; + thread const uint8_t * q8 = (thread const uint8_t *)aux32; + + float4 qf1, qf2; + + // [TAG_MUL_MV_WEIRD] + for (int ibl = ix; ibl < nb && ibl < ns01; ibl += 2) { + device const float4 * y4 = (device const float4 *)yb; + yl[0] = y4[0]; + yl[1] = y4[4]; + yl[2] = y4[1]; + yl[3] = y4[5]; + + for (short row = 0; row < NR0; ++row) { + device const block_iq4_xs & xb = x[row*ns01 + ibl]; + device const uint32_t * q4 = (device const uint32_t *)(xb.qs + 16*ib + 8*il); + + float4 acc1 = {0.f}, acc2 = {0.f}; + + aux32[0] = (q4[0] ) & 0x0f0f0f0f; + aux32[1] = (q4[0] >> 4) & 0x0f0f0f0f; + qf1 = {shmem_f32[q8[0]], shmem_f32[q8[1]], shmem_f32[q8[2]], shmem_f32[q8[3]]}; + qf2 = {shmem_f32[q8[4]], shmem_f32[q8[5]], shmem_f32[q8[6]], shmem_f32[q8[7]]}; + acc1 += yl[0] * qf1; + acc2 += yl[1] * qf2; + + aux32[0] = (q4[1] ) & 0x0f0f0f0f; + aux32[1] = (q4[1] >> 4) & 0x0f0f0f0f; + qf1 = {shmem_f32[q8[0]], shmem_f32[q8[1]], shmem_f32[q8[2]], shmem_f32[q8[3]]}; + qf2 = {shmem_f32[q8[4]], shmem_f32[q8[5]], shmem_f32[q8[6]], shmem_f32[q8[7]]}; + acc1 += yl[2] * qf1; + acc2 += yl[3] * qf2; + + acc1 += acc2; + + const int ls = (((xb.scales_l[ib/2] >> 4*(ib%2)) & 0xf) | (((xb.scales_h >> 2*ib) & 3) << 4)) - 32; + sumf[row] += (float)xb.d * ls * (acc1[0] + acc1[1] + acc1[2] + acc1[3]); + } + + yb += 2 * QK_K; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < NR0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_iq4_xs_f32")]] +kernel void kernel_mul_mv_iq4_xs_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_iq4_xs_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +void kernel_mul_mv_mxfp4_f32_impl( + args_t args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg) { + const short NSG = FC_mul_mv_nsg; + + threadgroup float * shmem_f32 = (threadgroup float *) shmem; + + const int r0 = tgpig.x; + const int r1 = tgpig.y; + const int im = tgpig.z; + + const int first_row = (r0 * NSG + sgitg) * NR0; + + const uint i12 = im%FC_mul_mv_ne12; + const uint i13 = im/FC_mul_mv_ne12; + + const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; + const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; + + device const block_mxfp4 * x = (device const block_mxfp4 *) (src0 + offset0); + device const float * y = (device const float *) (src1 + offset1); + + const int nb = args.ne00/QK_MXFP4; + const int ns01 = args.nb01/args.nb00; // this can be larger than nb for permuted src0 tensors + + const short ix = tiisg/2; // 0...15 + const short it = tiisg%2; // 0 or 1 + + shmem_f32[tiisg] = kvalues_mxfp4_f[tiisg%16]; + threadgroup_barrier(mem_flags::mem_threadgroup); + + float4 yl[4]; + float sumf[NR0]={0.f}; + + device const float * yb = y + ix*QK_MXFP4 + it*8; + + // note: just the check `ib < nb` is enough, but adding the redundant `&& ib < ns01` check makes the kernel a bit faster + // no idea why that is - needs some deeper investigation [TAG_MUL_MV_WEIRD] + for (int ib = ix; ib < nb && ib < ns01; ib += 16) { + device const float4 * y4 = (device const float4 *) yb; + + yl[0] = y4[0]; + yl[1] = y4[4]; + yl[2] = y4[1]; + yl[3] = y4[5]; + + FOR_UNROLL (short row = 0; row < NR0; row++) { + device const block_mxfp4 & xb = x[row*ns01 + ib]; + device const uint8_t * q2 = (device const uint8_t *)(xb.qs + 8*it); + + float4 acc1 = yl[0]*float4(shmem_f32[q2[0] & 0x0F], shmem_f32[q2[1] & 0x0F], shmem_f32[q2[2] & 0x0F], shmem_f32[q2[3] & 0x0F]); + float4 acc2 = yl[1]*float4(shmem_f32[q2[0] >> 4 ], shmem_f32[q2[1] >> 4 ], shmem_f32[q2[2] >> 4 ], shmem_f32[q2[3] >> 4 ]); + float4 acc3 = yl[2]*float4(shmem_f32[q2[4] & 0x0F], shmem_f32[q2[5] & 0x0F], shmem_f32[q2[6] & 0x0F], shmem_f32[q2[7] & 0x0F]); + float4 acc4 = yl[3]*float4(shmem_f32[q2[4] >> 4 ], shmem_f32[q2[5] >> 4 ], shmem_f32[q2[6] >> 4 ], shmem_f32[q2[7] >> 4 ]); + + acc1 = (acc1 + acc3) + (acc2 + acc4); + + sumf[row] += e8m0_to_fp32(xb.e) * ((acc1[0] + acc1[1]) + (acc1[2] + acc1[3])); + } + + yb += 16 * QK_MXFP4; + } + + device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; + + for (int row = 0; row < NR0 && first_row + row < args.ne0; ++row) { + float sum_all = simd_sum(sumf[row]); + if (tiisg == 0) { + dst_f32[first_row + row] = sum_all; + } + } +} + +[[host_name("kernel_mul_mv_mxfp4_f32")]] +kernel void kernel_mul_mv_mxfp4_f32( + constant ggml_metal_kargs_mul_mv & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + kernel_mul_mv_mxfp4_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} + +template +kernel void kernel_get_rows_q( + constant ggml_metal_kargs_get_rows & args, + device const void * src0, + device const void * src1, + device void * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 ntg [[threads_per_threadgroup]]) { + const int32_t iw0 = tgpig.x/args.ne10; + const int32_t i10 = tgpig.x%args.ne10; + const int32_t i11 = tgpig.y; + const int32_t i12 = tgpig.z; + + const int32_t r = ((const device int32_t *) ((const device char *) src1 + i12*args.nb12 + i11*args.nb11 + i10*args.nb10))[0]; + + const int32_t i02 = i11; + const int32_t i03 = i12; + + auto psrc = (device const block_q *) ((const device char *) src0 + i03*args.nb03 + i02*args.nb02 + r*args.nb01); + auto pdst = (device float4x4 *) (( device char *) dst + i12*args.nb3 + i11*args.nb2 + i10*args.nb1); + + for (int ind = iw0*ntg.x + tiitg; ind < args.ne00t;) { + float4x4 temp; + dequantize_func(psrc + ind/nl, ind%nl, temp); + pdst[ind] = temp; + + break; + } +} + +template +kernel void kernel_get_rows_f( + constant ggml_metal_kargs_get_rows & args, + device const void * src0, + device const void * src1, + device void * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 ntg [[threads_per_threadgroup]]) { + const int32_t iw0 = tgpig.x/args.ne10; + const int32_t i10 = tgpig.x%args.ne10; + const int32_t i11 = tgpig.y; + const int32_t i12 = tgpig.z; + + const int32_t r = ((const device int32_t *) ((const device char *) src1 + i12*args.nb12 + i11*args.nb11 + i10*args.nb10))[0]; + + const int32_t i02 = i11; + const int32_t i03 = i12; + + auto psrc = (const device T0 *) ((const device char *) src0 + i03*args.nb03 + i02*args.nb02 + r*args.nb01); + auto pdst = ( device T *) (( device char *) dst + i12*args.nb3 + i11*args.nb2 + i10*args.nb1); + + for (int ind = iw0*ntg.x + tiitg; ind < args.ne00t;) { + pdst[ind] = psrc[ind]; + + break; + } +} + +template +kernel void kernel_set_rows_q32( + constant ggml_metal_kargs_set_rows & args, + device const void * src0, + device const void * src1, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint tiitg[[thread_index_in_threadgroup]], + uint3 tptg [[threads_per_threadgroup]]) { + const int32_t i03 = tgpig.z; + const int32_t i02 = tgpig.y; + + const int32_t i12 = i03%args.ne12; + const int32_t i11 = i02%args.ne11; + + const int32_t i01 = tgpig.x*tptg.y + tiitg/tptg.x; + if (i01 >= args.ne01) { + return; + } + + const int32_t i10 = i01; + const TI i1 = ((const device TI *) ((const device char *) src1 + i10*args.nb10 + i11*args.nb11 + i12*args.nb12))[0]; + + device block_q * dst_row = ( device block_q *) (( device char *) dst + i1*args.nb1 + i02*args.nb2 + i03*args.nb3); + const device float * src_row = (const device float *) ((const device char *) src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03); + + for (int ind = tiitg%tptg.x; ind < args.nk0; ind += tptg.x) { + quantize_func(src_row + 32*ind, dst_row[ind]); + } +} + +template +kernel void kernel_set_rows_f( + constant ggml_metal_kargs_set_rows & args, + device const void * src0, + device const void * src1, + device float * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + uint tiitg[[thread_index_in_threadgroup]], + uint3 tptg [[threads_per_threadgroup]]) { + const int32_t i03 = tgpig.z; + const int32_t i02 = tgpig.y; + + const int32_t i12 = i03%args.ne12; + const int32_t i11 = i02%args.ne11; + + const int32_t i01 = tgpig.x*tptg.y + tiitg/tptg.x; + if (i01 >= args.ne01) { + return; + } + + const int32_t i10 = i01; + const TI i1 = ((const device TI *) ((const device char *) src1 + i10*args.nb10 + i11*args.nb11 + i12*args.nb12))[0]; + + device T * dst_row = ( device T *) (( device char *) dst + i1*args.nb1 + i02*args.nb2 + i03*args.nb3); + const device float * src_row = (const device float *) ((const device char *) src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03); + + for (int ind = tiitg%tptg.x; ind < args.nk0; ind += tptg.x) { + dst_row[ind] = (T) src_row[ind]; + } +} + +kernel void kernel_diag_f32( + constant ggml_metal_kargs_diag & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]]) { + constexpr short NW = N_SIMDWIDTH; + + const int32_t i3 = tgpig.z; + const int32_t i2 = tgpig.y; + const int32_t i1 = tgpig.x; + + device const float * src0_ptr = (device const float *)(src0 + i2*args.nb02 + i3*args.nb03); + device float * dst_ptr = (device float *)(dst + i1*args.nb01 + i2*args.nb2 + i3*args.nb3); + + for (int i0 = tiitg; i0 < args.ne0; i0 += NW) { + dst_ptr[i0] = i0 == i1 ? src0_ptr[i0] : 0.0f; + } +} + +kernel void kernel_diag_mask_inf_f32( + constant ggml_metal_kargs_diag_mask_inf & args, + device const char * src0, + device char * dst, + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort3 tptg[[threads_per_threadgroup]]) { + const int32_t i0 = tgpig.x*tptg.x + tiitg; + const int32_t i1 = tgpig.y; + const int32_t i2 = tgpig.z % args.ne2; + const int32_t i3 = tgpig.z / args.ne2; + + if (i0 >= args.ne0) { + return; + } + + device const float * src0_ptr = (device const float *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); + device float * dst_ptr = (device float *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + + *dst_ptr = i0 > args.n_past + i1 ? -INFINITY : *src0_ptr; +} + +constant bool FC_mul_mm_bc_inp [[function_constant(FC_MUL_MM + 0)]]; +constant bool FC_mul_mm_bc_out [[function_constant(FC_MUL_MM + 1)]]; +constant short FC_mul_mm_ne12 [[function_constant(FC_MUL_MM + 2)]]; +constant short FC_mul_mm_ne13 [[function_constant(FC_MUL_MM + 3)]]; +constant short FC_mul_mm_r2 [[function_constant(FC_MUL_MM + 4)]]; +constant short FC_mul_mm_r3 [[function_constant(FC_MUL_MM + 5)]]; + +// each block_q contains 16*nl weights +#ifdef GGML_METAL_HAS_TENSOR +template< + typename SA, typename SA_4x4, typename SA_8x8, + typename SB, typename SB_2x4, typename SB_8x8, + typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread SA_4x4 &), + typename T0, typename T0_4x4, typename T1, typename T1_2x4> +kernel void kernel_mul_mm( + constant ggml_metal_kargs_mul_mm & args, + device const char * srcA, + device const char * srcB, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig [[threadgroup_position_in_grid]], + ushort tiitg [[thread_index_in_threadgroup]], + ushort sgitg [[simdgroup_index_in_threadgroup]]) { + (void) sgitg; + + // Matrix dimensions: A(M,K) x B(K,N) -> C(M,N) + const int K = args.ne00; + const int M = args.ne0; + const int N = args.ne1; + + // Batch dimension handling + const int im = tgpig.z; + const int i12 = im % FC_mul_mm_ne12; + const int i13 = im / FC_mul_mm_ne12; + + // Batch offsets for srcA and srcB + const uint64_t offset0 = (i12/FC_mul_mm_r2)*args.nb02 + (i13/FC_mul_mm_r3)*args.nb03; + + // Tile dimensions + constexpr int NRB = SZ_SIMDGROUP * N_MM_BLOCK_X * N_MM_SIMD_GROUP_X; + constexpr int NRA = SZ_SIMDGROUP * N_MM_BLOCK_Y * N_MM_SIMD_GROUP_Y; + + // Tile offsets in output matrix + const int ra = tgpig.y * NRA; + const int rb = tgpig.x * NRB; + + // Threadgroup memory for dequantized A tile only + threadgroup SA * sa = (threadgroup SA *)(shmem); + + // Work-item count for A loading + constexpr int A_WORK_ITEMS = NRA * N_MM_NK; + constexpr int NUM_THREADS = N_SIMDWIDTH * N_MM_SIMD_GROUP_X * N_MM_SIMD_GROUP_Y; + + // tA wraps threadgroup memory + auto tA = tensor(sa, dextents(N_MM_NK_TOTAL, NRA)); + + // tB wraps device memory directly + device T1 * ptrB = (device T1 *)(srcB + args.nb12*i12 + args.nb13*i13); + const int strideB = args.nb11 / sizeof(T1); + auto tB = tensor(ptrB, dextents(K, N), array({1, strideB})); + + // Configure matmul operation + mpp::tensor_ops::matmul2d< + mpp::tensor_ops::matmul2d_descriptor( + NRB, NRA, N_MM_NK_TOTAL, false, true, true, + mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate), + execution_simdgroups> mm; + + auto cT = mm.get_destination_cooperative_tensor(); + + // Accumulate partial results over K dimension + for (int loop_k = 0; loop_k < K; loop_k += N_MM_NK_TOTAL) { + // === PHASE 1: Dequantization of A into threadgroup memory === + for (int work = tiitg; work < A_WORK_ITEMS; work += NUM_THREADS) { + const int row = work / N_MM_NK; + const int k_chunk = work % N_MM_NK; + const int k_pos = loop_k + k_chunk * 16; + const short k_base = k_chunk * 16; + + // Bounds check: skip device read if row is out of matrix bounds + if (ra + row < M) { + if (is_same::value && FC_mul_mm_bc_inp) { + // Element-wise reads when K is not aligned (nb01 not aligned for half4x4/float4x4). + // MSL spec Table 2.5: half4x4 requires 8-byte alignment. When K is odd, + // nb01 = K*2 is not 8-byte aligned, so odd-row pointers are misaligned. + // Mirrors the legacy kernel's existing guard. + device const T0 * row_ptr = (device const T0 *)(srcA + args.nb01 * (ra + row) + offset0); + + FOR_UNROLL (short i = 0; i < 16; i++) { + sa[row * N_MM_NK_TOTAL + (k_base + i)] = (k_pos + i < K) ? (SA) row_ptr[k_pos + i] : (SA)0; + } + } else { + const int block_idx = k_pos / (16 * nl); + const short il = (k_pos / 16) % nl; + + device const block_q * row_ptr = (device const block_q *)(srcA + args.nb01 * (ra + row) + offset0); + + SA_4x4 temp_a; + dequantize_func(row_ptr + block_idx, il, temp_a); + + FOR_UNROLL (short i = 0; i < 16; i++) { + // Zero-pad A for K positions beyond valid range (handles partial K iterations) + sa[row * N_MM_NK_TOTAL + (k_base + i)] = (k_pos + i < K) ? temp_a[i/4][i%4] : (SA)0; + } + } + } else { + // Zero-pad rows beyond matrix bounds + FOR_UNROLL (short i = 0; i < 16; i++) { + sa[row * N_MM_NK_TOTAL + (k_base + i)] = (SA)0; + } + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // === PHASE 2: Tensor matmul === + auto mA = tA.slice(0, 0); + auto mB = tB.slice(loop_k, rb); + + mm.run(mB, mA, cT); + + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + // Store result tile to output matrix (with batch offset) + // cT.store handles bounds checking via tD's extents (M, N) + device float * dstBatch = (device float *)dst + im * N * M; + + auto tD = tensor(dstBatch, dextents(M, N), array({1, M})); + cT.store(tD.slice(ra, rb)); +} + +// Accumulate-in-place variant of kernel_mul_mm: dst += A * B. +// The MMA body is identical to kernel_mul_mm (same K-reduction order, so the partial +// products are bit-identical); only the epilogue differs, folding the result tile into +// the destination instead of overwriting it. Tensor-core path only (GGML_METAL_HAS_TENSOR). + +template< + typename SA, typename SA_4x4, typename SA_8x8, + typename SB, typename SB_2x4, typename SB_8x8, + typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread SA_4x4 &), + typename T0, typename T0_4x4, typename T1, typename T1_2x4> +kernel void kernel_mul_mm_acc( + constant ggml_metal_kargs_mul_mm & args, + device const char * srcA, + device const char * srcB, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig [[threadgroup_position_in_grid]], + ushort tiitg [[thread_index_in_threadgroup]], + ushort sgitg [[simdgroup_index_in_threadgroup]]) { + (void) sgitg; + + // Matrix dimensions: A(M,K) x B(K,N) -> C(M,N) + const int K = args.ne00; + const int M = args.ne0; + const int N = args.ne1; + + // Batch dimension handling + const int im = tgpig.z; + const int i12 = im % FC_mul_mm_ne12; + const int i13 = im / FC_mul_mm_ne12; + + // Batch offsets for srcA and srcB + const uint64_t offset0 = (i12/FC_mul_mm_r2)*args.nb02 + (i13/FC_mul_mm_r3)*args.nb03; + + // Tile dimensions + constexpr int NRB = SZ_SIMDGROUP * N_MM_BLOCK_X * N_MM_SIMD_GROUP_X; + constexpr int NRA = SZ_SIMDGROUP * N_MM_BLOCK_Y * N_MM_SIMD_GROUP_Y; + + // Tile offsets in output matrix + const int ra = tgpig.y * NRA; + const int rb = tgpig.x * NRB; + + // Threadgroup memory for dequantized A tile only + threadgroup SA * sa = (threadgroup SA *)(shmem); + + // Work-item count for A loading + constexpr int A_WORK_ITEMS = NRA * N_MM_NK; + constexpr int NUM_THREADS = N_SIMDWIDTH * N_MM_SIMD_GROUP_X * N_MM_SIMD_GROUP_Y; + + // tA wraps threadgroup memory + auto tA = tensor(sa, dextents(N_MM_NK_TOTAL, NRA)); + + // tB wraps device memory directly + device T1 * ptrB = (device T1 *)(srcB + args.nb12*i12 + args.nb13*i13); + const int strideB = args.nb11 / sizeof(T1); + auto tB = tensor(ptrB, dextents(K, N), array({1, strideB})); + + // Configure matmul operation + mpp::tensor_ops::matmul2d< + mpp::tensor_ops::matmul2d_descriptor( + NRB, NRA, N_MM_NK_TOTAL, false, true, true, + mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate), + execution_simdgroups> mm; + + auto cT = mm.get_destination_cooperative_tensor(); + + // Accumulate partial results over K dimension + for (int loop_k = 0; loop_k < K; loop_k += N_MM_NK_TOTAL) { + // === PHASE 1: Dequantization of A into threadgroup memory === + for (int work = tiitg; work < A_WORK_ITEMS; work += NUM_THREADS) { + const int row = work / N_MM_NK; + const int k_chunk = work % N_MM_NK; + const int k_pos = loop_k + k_chunk * 16; + const short k_base = k_chunk * 16; + + // Bounds check: skip device read if row is out of matrix bounds + if (ra + row < M) { + if (is_same::value && FC_mul_mm_bc_inp) { + // Element-wise reads when K is not aligned (nb01 not aligned for half4x4/float4x4). + // MSL spec Table 2.5: half4x4 requires 8-byte alignment. When K is odd, + // nb01 = K*2 is not 8-byte aligned, so odd-row pointers are misaligned. + // Mirrors the legacy kernel's existing guard. + device const T0 * row_ptr = (device const T0 *)(srcA + args.nb01 * (ra + row) + offset0); + + FOR_UNROLL (short i = 0; i < 16; i++) { + sa[row * N_MM_NK_TOTAL + (k_base + i)] = (k_pos + i < K) ? (SA) row_ptr[k_pos + i] : (SA)0; + } + } else { + const int block_idx = k_pos / (16 * nl); + const short il = (k_pos / 16) % nl; + + device const block_q * row_ptr = (device const block_q *)(srcA + args.nb01 * (ra + row) + offset0); + + SA_4x4 temp_a; + dequantize_func(row_ptr + block_idx, il, temp_a); + + FOR_UNROLL (short i = 0; i < 16; i++) { + // Zero-pad A for K positions beyond valid range (handles partial K iterations) + sa[row * N_MM_NK_TOTAL + (k_base + i)] = (k_pos + i < K) ? temp_a[i/4][i%4] : (SA)0; + } + } + } else { + // Zero-pad rows beyond matrix bounds + FOR_UNROLL (short i = 0; i < 16; i++) { + sa[row * N_MM_NK_TOTAL + (k_base + i)] = (SA)0; + } + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // === PHASE 2: Tensor matmul === + auto mA = tA.slice(0, 0); + auto mB = tB.slice(loop_k, rb); + + mm.run(mB, mA, cT); + + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + // Accumulate the result tile onto the output matrix (with batch offset). + // The dst tile already holds the running sum; load it, add the freshly computed + // tile element-wise (one F32 add per element, matching ggml_add), and store back. + // cAcc shares cT's layout, so per-thread element i maps to the same (row, col) and + // each thread reads/adds/stores only its own elements -- no extra synchronization. + device float * dstBatch = (device float *)dst + im * N * M; + + auto tD = tensor(dstBatch, dextents(M, N), array({1, M})); + auto tDst = tD.slice(ra, rb); + + auto cAcc = mm.get_destination_cooperative_tensor(); + cAcc.load(tDst); + + for (auto it = cT.begin(), jt = cAcc.begin(); it != cT.end(); ++it, ++jt) { + *it += *jt; + } + + cT.store(tDst); +} + +#else + +template< + typename S0, typename S0_4x4, typename S0_8x8, + typename S1, typename S1_2x4, typename S1_8x8, + typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread S0_4x4 &), + typename T0, typename T0_4x4, typename T1, typename T1_2x4> +kernel void kernel_mul_mm( + constant ggml_metal_kargs_mul_mm & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + threadgroup S0 * sa = (threadgroup S0 *)(shmem); + threadgroup S1 * sb = (threadgroup S1 *)(shmem + 4096); + + constexpr int NR0 = 64; + constexpr int NR1 = 32; + + constexpr int NK = 32; + constexpr int NL0 = NK/16; + constexpr int NL1 = NK/8; + + const int im = tgpig.z; + const int r0 = tgpig.y*NR0; + const int r1 = tgpig.x*NR1; + + // if this block is of 64x32 shape or smaller + const short nr0 = (args.ne0 - r0 < NR0) ? (args.ne0 - r0) : NR0; + const short nr1 = (args.ne1 - r1 < NR1) ? (args.ne1 - r1) : NR1; + + // a thread shouldn't load data outside of the matrix + const short lr0 = ((short)tiitg/NL0) < nr0 ? ((short)tiitg/NL0) : nr0 - 1; // 0 .. 63 + const short lr1 = ((short)tiitg/NL1) < nr1 ? ((short)tiitg/NL1) : nr1 - 1; // 0 .. 31 + + const short il0 = (tiitg % NL0); + + short il = il0; + + const int i12 = im % FC_mul_mm_ne12; + const int i13 = im / FC_mul_mm_ne12; + + const uint64_t offset0 = (i12/FC_mul_mm_r2)*args.nb02 + (i13/FC_mul_mm_r3)*args.nb03; + const short offset1 = il0/nl; + + device const block_q * x = (device const block_q *)(src0 + args.nb01*(r0 + lr0) + offset0) + offset1; + + const short iy = 8*(tiitg % NL1); + + device const T1 * y = (device const T1 *)(src1 + + args.nb13*i13 + + args.nb12*i12 + + args.nb11*(r1 + lr1) + + args.nb10*iy); + + S0_8x8 ma[4]; + S1_8x8 mb[2]; + + simdgroup_float8x8 mc[8]; + + for (short i = 0; i < 8; i++){ + mc[i] = make_filled_simdgroup_matrix(0.f); + } + + for (int loop_k = 0; loop_k < args.ne00; loop_k += NK) { + // load data and store to threadgroup memory + if (is_same::value && FC_mul_mm_bc_inp) { + threadgroup_barrier(mem_flags::mem_threadgroup); + + // no need for dequantization + for (short i = 0; i < 16; i++) { + const short sx = 2*il0 + i/8; + const short sy = (tiitg/NL0)/8; + + //const short lx = i%8; + //const short ly = (tiitg/NL0)%8; + const short lx = (tiitg/NL0)%8; + const short ly = i%8; + + const short ib = 8*sx + sy; + + *(sa + 64*ib + 8*ly + lx) = loop_k + 16*il + i < args.ne00 ? *((device T0 *) x + i) : 0; + } + } else { + S0_4x4 temp_a; + dequantize_func(x, il, temp_a); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + FOR_UNROLL (short i = 0; i < 16; i++) { + const short sx = 2*il0 + i/8; + const short sy = (tiitg/NL0)/8; + + //const short lx = i%8; + //const short ly = (tiitg/NL0)%8; + const short lx = (tiitg/NL0)%8; + const short ly = i%8; + + const short ib = 8*sx + sy; + + // NOTE: this is massively slower.. WTF? + //sa[64*ib + 8*ly + lx] = temp_a[i/4][i%4]; + + *(sa + 64*ib + 8*ly + lx) = temp_a[i/4][i%4]; + } + } + + if (FC_mul_mm_bc_inp) { + for (short i = 0; i < 8; ++i) { + const short sx = (tiitg%NL1); + const short sy = (tiitg/NL1)/8; + + const short lx = i; + const short ly = (tiitg/NL1)%8; + //const short lx = (tiitg/NL1)%8; + //const short ly = i; + + const short ib = 4*sx + sy; + + *(sb + 64*ib + 8*ly + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; + } + } else { + const short sx = (tiitg%NL1); + const short sy = (tiitg/NL1)/8; + + //const short dx = sx; + //const short dy = sy; + + const short ly = (tiitg/NL1)%8; + + const short ib = 4*sx + sy; + + *(threadgroup S1_2x4 *)(sb + 64*ib + 8*ly) = (S1_2x4)(*((device T1_2x4 *) y)); + } + + il = (il + 2 < nl) ? il + 2 : il % 2; + x = (il < 2) ? x + (2 + nl - 1)/nl : x; + + y += NK; + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // load matrices from threadgroup memory and conduct outer products + threadgroup const S0 * lsma = (sa + 4*64*(sgitg%2)); + threadgroup const S1 * lsmb = (sb + 2*64*(sgitg/2)); + + FOR_UNROLL (short ik = 0; ik < NK/8; ik++) { + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 4; i++) { + simdgroup_load(ma[i], lsma + 64*i, 8, 0, false); + } + + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 2; i++) { + simdgroup_load(mb[i], lsmb + 64*i, 8, 0, false); + } + + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 8; i++){ + simdgroup_multiply_accumulate(mc[i], mb[i/4], ma[i%4], mc[i]); + } + + lsma += 8*64; + lsmb += 4*64; + } + } + + if (!FC_mul_mm_bc_out || (r0 + NR0 <= args.ne0 && r1 + NR1 <= args.ne1)) { + // if no bounds checks on the output are needed, we can directly write to device memory + device float * C = (device float *) dst + + (r0 + 32*(sgitg & 1)) + \ + (r1 + 16*(sgitg >> 1)) * args.ne0 + im*args.ne1*args.ne0; + + for (short i = 0; i < 8; i++) { + simdgroup_store(mc[i], C + 8*(i%4) + 8*args.ne0*(i/4), args.ne0, 0, false); + } + } else { + // block is smaller than 64x32, we should avoid writing data outside of the matrix + threadgroup_barrier(mem_flags::mem_threadgroup); + + threadgroup float * temp_str = ((threadgroup float *) shmem) + 32*(sgitg&1) + (16*(sgitg >> 1))*NR0; + + for (short i = 0; i < 8; i++) { + simdgroup_store(mc[i], temp_str + 8*(i%4) + 8*NR0*(i/4), NR0, 0, false); + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (sgitg == 0) { + for (int j = tiitg; j < nr1; j += NR1) { + device float * D = (device float *) dst + r0 + (r1 + j)*args.ne0 + im*args.ne1*args.ne0; + device float4 * D4 = (device float4 *) D; + + threadgroup float * C = temp_str + (j*NR0); + threadgroup float4 * C4 = (threadgroup float4 *) C; + + int i = 0; + for (; i < nr0/4; i++) { + *(D4 + i) = *(C4 + i); + } + + i *= 4; + for (; i < nr0; i++) { + *(D + i) = *(C + i); + } + } + } + } +} + +// Accumulate-in-place variant of kernel_mul_mm (simdgroup path): dst += A * B. +// The MMA body is identical to kernel_mul_mm (same K-reduction order, so the partial products +// are bit-identical); only the epilogue differs, folding the result tile into the destination +// via one F32 add per element instead of overwriting it. + +template< + typename S0, typename S0_4x4, typename S0_8x8, + typename S1, typename S1_2x4, typename S1_8x8, + typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread S0_4x4 &), + typename T0, typename T0_4x4, typename T1, typename T1_2x4> +kernel void kernel_mul_mm_acc( + constant ggml_metal_kargs_mul_mm & args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + + threadgroup S0 * sa = (threadgroup S0 *)(shmem); + threadgroup S1 * sb = (threadgroup S1 *)(shmem + 4096); + + constexpr int NR0 = 64; + constexpr int NR1 = 32; + + constexpr int NK = 32; + constexpr int NL0 = NK/16; + constexpr int NL1 = NK/8; + + const int im = tgpig.z; + const int r0 = tgpig.y*NR0; + const int r1 = tgpig.x*NR1; + + // if this block is of 64x32 shape or smaller + const short nr0 = (args.ne0 - r0 < NR0) ? (args.ne0 - r0) : NR0; + const short nr1 = (args.ne1 - r1 < NR1) ? (args.ne1 - r1) : NR1; + + // a thread shouldn't load data outside of the matrix + const short lr0 = ((short)tiitg/NL0) < nr0 ? ((short)tiitg/NL0) : nr0 - 1; // 0 .. 63 + const short lr1 = ((short)tiitg/NL1) < nr1 ? ((short)tiitg/NL1) : nr1 - 1; // 0 .. 31 + + const short il0 = (tiitg % NL0); + + short il = il0; + + const int i12 = im % FC_mul_mm_ne12; + const int i13 = im / FC_mul_mm_ne12; + + const uint64_t offset0 = (i12/FC_mul_mm_r2)*args.nb02 + (i13/FC_mul_mm_r3)*args.nb03; + const short offset1 = il0/nl; + + device const block_q * x = (device const block_q *)(src0 + args.nb01*(r0 + lr0) + offset0) + offset1; + + const short iy = 8*(tiitg % NL1); + + device const T1 * y = (device const T1 *)(src1 + + args.nb13*i13 + + args.nb12*i12 + + args.nb11*(r1 + lr1) + + args.nb10*iy); + + S0_8x8 ma[4]; + S1_8x8 mb[2]; + + simdgroup_float8x8 mc[8]; + + for (short i = 0; i < 8; i++){ + mc[i] = make_filled_simdgroup_matrix(0.f); + } + + for (int loop_k = 0; loop_k < args.ne00; loop_k += NK) { + // load data and store to threadgroup memory + if (is_same::value && FC_mul_mm_bc_inp) { + threadgroup_barrier(mem_flags::mem_threadgroup); + + // no need for dequantization + for (short i = 0; i < 16; i++) { + const short sx = 2*il0 + i/8; + const short sy = (tiitg/NL0)/8; + + //const short lx = i%8; + //const short ly = (tiitg/NL0)%8; + const short lx = (tiitg/NL0)%8; + const short ly = i%8; + + const short ib = 8*sx + sy; + + *(sa + 64*ib + 8*ly + lx) = loop_k + 16*il + i < args.ne00 ? *((device T0 *) x + i) : 0; + } + } else { + S0_4x4 temp_a; + dequantize_func(x, il, temp_a); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + FOR_UNROLL (short i = 0; i < 16; i++) { + const short sx = 2*il0 + i/8; + const short sy = (tiitg/NL0)/8; + + //const short lx = i%8; + //const short ly = (tiitg/NL0)%8; + const short lx = (tiitg/NL0)%8; + const short ly = i%8; + + const short ib = 8*sx + sy; + + // NOTE: this is massively slower.. WTF? + //sa[64*ib + 8*ly + lx] = temp_a[i/4][i%4]; + + *(sa + 64*ib + 8*ly + lx) = temp_a[i/4][i%4]; + } + } + + if (FC_mul_mm_bc_inp) { + for (short i = 0; i < 8; ++i) { + const short sx = (tiitg%NL1); + const short sy = (tiitg/NL1)/8; + + const short lx = i; + const short ly = (tiitg/NL1)%8; + //const short lx = (tiitg/NL1)%8; + //const short ly = i; + + const short ib = 4*sx + sy; + + *(sb + 64*ib + 8*ly + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; + } + } else { + const short sx = (tiitg%NL1); + const short sy = (tiitg/NL1)/8; + + //const short dx = sx; + //const short dy = sy; + + const short ly = (tiitg/NL1)%8; + + const short ib = 4*sx + sy; + + *(threadgroup S1_2x4 *)(sb + 64*ib + 8*ly) = (S1_2x4)(*((device T1_2x4 *) y)); + } + + il = (il + 2 < nl) ? il + 2 : il % 2; + x = (il < 2) ? x + (2 + nl - 1)/nl : x; + + y += NK; + + threadgroup_barrier(mem_flags::mem_threadgroup); + + // load matrices from threadgroup memory and conduct outer products + threadgroup const S0 * lsma = (sa + 4*64*(sgitg%2)); + threadgroup const S1 * lsmb = (sb + 2*64*(sgitg/2)); + + FOR_UNROLL (short ik = 0; ik < NK/8; ik++) { + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 4; i++) { + simdgroup_load(ma[i], lsma + 64*i, 8, 0, false); + } + + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 2; i++) { + simdgroup_load(mb[i], lsmb + 64*i, 8, 0, false); + } + + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 8; i++){ + simdgroup_multiply_accumulate(mc[i], mb[i/4], ma[i%4], mc[i]); + } + + lsma += 8*64; + lsmb += 4*64; + } + } + + // accumulate: stage the result tiles to threadgroup memory, then add onto dst in-place. + // one F32 add per element, matching ggml_add -> bit-identical to mul_mm + add. Always + // staged (no direct-store fast path) so partial tiles are clipped the same way. + threadgroup_barrier(mem_flags::mem_threadgroup); + + threadgroup float * temp_str = ((threadgroup float *) shmem) + 32*(sgitg&1) + (16*(sgitg >> 1))*NR0; + + for (short i = 0; i < 8; i++) { + simdgroup_store(mc[i], temp_str + 8*(i%4) + 8*NR0*(i/4), NR0, 0, false); + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (sgitg == 0) { + for (int j = tiitg; j < nr1; j += NR1) { + device float * D = (device float *) dst + r0 + (r1 + j)*args.ne0 + im*args.ne1*args.ne0; + device float4 * D4 = (device float4 *) D; + + threadgroup float * C = temp_str + (j*NR0); + threadgroup float4 * C4 = (threadgroup float4 *) C; + + int i = 0; + for (; i < nr0/4; i++) { + *(D4 + i) += *(C4 + i); + } + + i *= 4; + for (; i < nr0; i++) { + *(D + i) += *(C + i); + } + } + } +} + +#endif // GGML_METAL_HAS_TENSOR + +template // n_expert_used +kernel void kernel_mul_mm_id_map0( + constant ggml_metal_kargs_mul_mm_id_map0 & args, + device const char * src2, + device char * htpe, + device char * hids, + threadgroup char * shmem [[threadgroup(0)]], + ushort tpitg[[thread_position_in_threadgroup]], + ushort ntg[[threads_per_threadgroup]]) { + const short ide = tpitg; // expert id + + uint32_t n_all = 0; + + device int32_t * ids_i32 = (device int32_t *) hids + ide*args.ne21; + + for (int i21 = 0; i21 < args.ne21; i21 += ntg) { // n_tokens + if (i21 + tpitg < args.ne21) { + device const int32_t * src2_i32 = (device const int32_t *) (src2 + (i21 + tpitg)*args.nb21); + + threadgroup uint16_t * sids = (threadgroup uint16_t *) shmem + tpitg*ne20; + + #pragma unroll(ne20) + for (short i20 = 0; i20 < ne20; i20++) { + sids[i20] = src2_i32[i20]; + } + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + for (short t = 0; t < ntg; t++) { + if (i21 + t >= args.ne21) { + break; + } + + threadgroup const uint16_t * sids = (threadgroup const uint16_t *) shmem + t*ne20; + + short sel = 0; + #pragma unroll(ne20) + for (short i20 = 0; i20 < ne20; i20++) { + sel += (sids[i20] == ide)*(i20 + 1); + } + + ids_i32[n_all] = (i21 + t)*ne20 + sel - 1; + + n_all += sel > 0; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + } + + device uint32_t * tpe_u32 = (device uint32_t *) (htpe); + tpe_u32[ide] = n_all; +} + +typedef decltype(kernel_mul_mm_id_map0<1>) kernel_mul_mm_id_map0_t; + +template [[host_name("kernel_mul_mm_id_map0_ne20_1" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<1>; +template [[host_name("kernel_mul_mm_id_map0_ne20_2" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<2>; +template [[host_name("kernel_mul_mm_id_map0_ne20_4" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<4>; +template [[host_name("kernel_mul_mm_id_map0_ne20_5" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<5>; +template [[host_name("kernel_mul_mm_id_map0_ne20_6" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<6>; +template [[host_name("kernel_mul_mm_id_map0_ne20_8" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<8>; +template [[host_name("kernel_mul_mm_id_map0_ne20_10")]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<10>; +template [[host_name("kernel_mul_mm_id_map0_ne20_16")]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<16>; +template [[host_name("kernel_mul_mm_id_map0_ne20_22")]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<22>; + +template +kernel void kernel_mul_mm_id( + constant ggml_metal_kargs_mul_mm_id & args, + device const char * src0, + device const char * src1, + device const char * htpe, + device const char * hids, + device char * dst, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + threadgroup S0 * sa = (threadgroup S0 *)(shmem); + threadgroup S1 * sb = (threadgroup S1 *)(shmem + 4096); + +#ifdef GGML_METAL_HAS_TENSOR + threadgroup float * sc = (threadgroup float *)(shmem); +#endif + + constexpr int NR0 = 64; + constexpr int NR1 = 32; + + constexpr int NK = 32; + constexpr int NL0 = NK/16; + constexpr int NL1 = NK/8; + + const int im = tgpig.z; // expert + const int r0 = tgpig.y*NR0; + const int r1 = tgpig.x*NR1; + + device const uint32_t * tpe_u32 = (device const uint32_t *) (htpe); + device const int32_t * ids_i32 = (device const int32_t *) (hids); + + const int32_t neh1 = tpe_u32[im]; + + if (r1 >= neh1) { + return; + } + + // if this block is of 64x32 shape or smaller + const short nr0 = (args.ne0 - r0 < NR0) ? (args.ne0 - r0) : NR0; + const short nr1 = ( neh1 - r1 < NR1) ? ( neh1 - r1) : NR1; + + // a thread shouldn't load data outside of the matrix + const short lr0 = ((short)tiitg/NL0) < nr0 ? ((short)tiitg/NL0) : nr0 - 1; // 0 .. 63 + const short lr1 = ((short)tiitg/NL1) < nr1 ? ((short)tiitg/NL1) : nr1 - 1; // 0 .. 31 + + const short il0 = (tiitg % NL0); + + short il = il0; + + const int id = ids_i32[im*args.ne21 + r1 + lr1]; + + const short i11 = (id % args.ne20) % args.ne11; + const short i12 = (id / args.ne20); + const short i13 = 0; + + const uint64_t offset0 = im*args.nb02 + i13*args.nb03; + const short offset1 = il0/nl; + + device const block_q * x = (device const block_q *)(src0 + args.nb01*(r0 + lr0) + offset0) + offset1; + + const short iy = 8*(tiitg % NL1); + + device const T1 * y = (device const T1 *)(src1 + + args.nb13*i13 + + args.nb12*i12 + + args.nb11*i11 + + args.nb10*iy); + +#ifndef GGML_METAL_HAS_TENSOR + S0_8x8 ma[4]; + S1_8x8 mb[2]; + + simdgroup_float8x8 mc[8]; + + for (short i = 0; i < 8; i++){ + mc[i] = make_filled_simdgroup_matrix(0.f); + } +#else + auto tA = tensor, tensor_inline>(sa, dextents(NK, NR0)); + auto tB = tensor, tensor_inline>(sb, dextents(NR1, NK )); + + mpp::tensor_ops::matmul2d< + mpp::tensor_ops::matmul2d_descriptor(NR1, NR0, NK, false, true, false, mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate), + execution_simdgroups<4>> mm; + + auto cT = mm.get_destination_cooperative_tensor(); +#endif + + for (int loop_k = 0; loop_k < args.ne00; loop_k += NK) { +#ifndef GGML_METAL_HAS_TENSOR + // load data and store to threadgroup memory + if (is_same::value && FC_mul_mm_bc_inp) { + threadgroup_barrier(mem_flags::mem_threadgroup); + + // no need for dequantization + for (short i = 0; i < 16; i++) { + const short sx = 2*il0 + i/8; + const short sy = (tiitg/NL0)/8; + + //const short lx = i%8; + //const short ly = (tiitg/NL0)%8; + const short lx = (tiitg/NL0)%8; + const short ly = i%8; + + const short ib = 8*sx + sy; + + *(sa + 64*ib + 8*ly + lx) = loop_k + 16*il + i < args.ne00 ? (S0) *((device T0 *) x + i) : (S0) 0; + } + } else { + S0_4x4 temp_a; + dequantize_func(x, il, temp_a); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + FOR_UNROLL (short i = 0; i < 16; i++) { + const short sx = 2*il0 + i/8; + const short sy = (tiitg/NL0)/8; + + //const short lx = i%8; + //const short ly = (tiitg/NL0)%8; + const short lx = (tiitg/NL0)%8; + const short ly = i%8; + + const short ib = 8*sx + sy; + + // NOTE: this is massively slower.. WTF? + //sa[64*ib + 8*ly + lx] = temp_a[i/4][i%4]; + + *(sa + 64*ib + 8*ly + lx) = temp_a[i/4][i%4]; + } + } + + if (FC_mul_mm_bc_inp) { + for (short i = 0; i < 8; ++i) { + const short sx = (tiitg%NL1); + const short sy = (tiitg/NL1)/8; + + const short lx = i; + const short ly = (tiitg/NL1)%8; + //const short lx = (tiitg/NL1)%8; + //const short ly = i; + + const short ib = 4*sx + sy; + + *(sb + 64*ib + 8*ly + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; + } + } else { + const short sx = (tiitg%NL1); + const short sy = (tiitg/NL1)/8; + + //const short dx = sx; + //const short dy = sy; + + const short ly = (tiitg/NL1)%8; + + const short ib = 4*sx + sy; + + *(threadgroup S1_2x4 *)(sb + 64*ib + 8*ly) = (S1_2x4)(*((device T1_2x4 *) y)); + } +#else + // load data and store to threadgroup memory + if (is_same::value && FC_mul_mm_bc_inp) { + threadgroup_barrier(mem_flags::mem_threadgroup); + + // no need for dequantization + for (short i = 0; i < 16; i++) { + const short sx = 2*il0 + i/8; + const short sy = (tiitg/NL0)/8; + + const short lx = i%8; + const short ly = (tiitg/NL0)%8; + //const short lx = (tiitg/NL0)%8; + //const short ly = i%8; + + *(sa + NK*(8*sy + ly) + 8*sx + lx) = loop_k + 16*il + i < args.ne00 ? *((device T0 *) x + i) : 0; + } + } else { + S0_4x4 temp_a; + dequantize_func(x, il, temp_a); + + threadgroup_barrier(mem_flags::mem_threadgroup); + + FOR_UNROLL (short i = 0; i < 16; i++) { + const short sx = 2*il0 + i/8; + const short sy = (tiitg/NL0)/8; + + const short lx = i%8; + const short ly = (tiitg/NL0)%8; + //const short lx = (tiitg/NL0)%8; + //const short ly = i%8; + + *(sa + NK*(8*sy + ly) + 8*sx + lx) = temp_a[i/4][i%4]; + } + } + + if (FC_mul_mm_bc_inp) { + for (short i = 0; i < 8; ++i) { + const short sx = (tiitg%NL1); + const short sy = (tiitg/NL1)/8; + + const short lx = i; + const short ly = (tiitg/NL1)%8; + //const short lx = (tiitg/NL1)%8; + //const short ly = i; + + *(sb + NK*(8*sy + ly) + 8*sx + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; + } + } else { + const short sx = (tiitg%NL1); + const short sy = (tiitg/NL1)/8; + + //const short lx = i; + const short ly = (tiitg/NL1)%8; + //const short lx = (tiitg/NL1)%8; + //const short ly = i; + + *(threadgroup S1_2x4 *)(sb + NK*(8*sy + ly) + 8*sx) = (S1_2x4)(*((device T1_2x4 *) y)); + } +#endif + + il = (il + 2 < nl) ? il + 2 : il % 2; + x = (il < 2) ? x + (2 + nl - 1)/nl : x; + + y += NK; + + threadgroup_barrier(mem_flags::mem_threadgroup); + +#ifndef GGML_METAL_HAS_TENSOR + // load matrices from threadgroup memory and conduct outer products + threadgroup const S0 * lsma = (sa + 4*64*(sgitg%2)); + threadgroup const S1 * lsmb = (sb + 2*64*(sgitg/2)); + + FOR_UNROLL (short ik = 0; ik < NK/8; ik++) { + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 4; i++) { + simdgroup_load(ma[i], lsma + 64*i, 8, 0, false); + } + + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 2; i++) { + simdgroup_load(mb[i], lsmb + 64*i, 8, 0, false); + } + + simdgroup_barrier(mem_flags::mem_none); + + FOR_UNROLL (short i = 0; i < 8; i++){ + simdgroup_multiply_accumulate(mc[i], mb[i/4], ma[i%4], mc[i]); + } + + lsma += 8*64; + lsmb += 4*64; + } +#else + auto sA = tA.slice(0, 0); + auto sB = tB.slice(0, 0); + + mm.run(sB, sA, cT); +#endif + } + + // block is smaller than 64x32, we should avoid writing data outside of the matrix + threadgroup_barrier(mem_flags::mem_threadgroup); + +#ifdef GGML_METAL_HAS_TENSOR + auto tC = tensor, tensor_inline>(sc, dextents(NR0, NR1)); + cT.store(tC); +#else + threadgroup float * temp_str = ((threadgroup float *) shmem) + 32*(sgitg&1) + (16*(sgitg >> 1))*NR0; + + for (short i = 0; i < 8; i++) { + simdgroup_store(mc[i], temp_str + 8*(i%4) + 8*NR0*(i/4), NR0, 0, false); + } +#endif + + threadgroup_barrier(mem_flags::mem_threadgroup); + + for (short j = sgitg; j < nr1; j += 4) { + const int id = ids_i32[im*args.ne21 + r1 + j]; + + const short ide = id % args.ne20; + const short idt = id / args.ne20; + + device float * D = (device float *) dst + r0 + ide*args.ne0 + idt*args.ne1*args.ne0; + device float4 * D4 = (device float4 *) D; + + threadgroup float * C = (threadgroup float *) shmem + j*NR0; + threadgroup float4 * C4 = (threadgroup float4 *) C; + + int i = tiisg; + for (; i < nr0/4; i += 32) { + *(D4 + i) = *(C4 + i); + } + + i = (4*(nr0/4)) + tiisg; + for (; i < nr0; i += 32) { + *(D + i) = *(C + i); + } + } +} + +#define QK_NL 16 + +// +// get rows +// + +typedef decltype(kernel_get_rows_f) get_rows_f_t; + +template [[host_name("kernel_get_rows_f32")]] kernel get_rows_f_t kernel_get_rows_f; +template [[host_name("kernel_get_rows_f16")]] kernel get_rows_f_t kernel_get_rows_f; +template [[host_name("kernel_get_rows_i32")]] kernel get_rows_f_t kernel_get_rows_f; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_get_rows_bf16")]] kernel get_rows_f_t kernel_get_rows_f; +#endif + +typedef decltype(kernel_get_rows_q) get_rows_q_t; + +template [[host_name("kernel_get_rows_q1_0")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q4_0")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q4_1")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q5_0")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q5_1")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q8_0")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_mxfp4")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q2_K")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q3_K")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q4_K")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q5_K")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_q6_K")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq2_xxs")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq2_xs")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq3_xxs")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq3_s")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq2_s")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq1_s")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq1_m")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq4_nl")]] kernel get_rows_q_t kernel_get_rows_q; +template [[host_name("kernel_get_rows_iq4_xs")]] kernel get_rows_q_t kernel_get_rows_q; + +// +// set rows +// + +typedef decltype(kernel_set_rows_f) set_rows_f_t; + +template [[host_name("kernel_set_rows_f32_i64")]] kernel set_rows_f_t kernel_set_rows_f; +template [[host_name("kernel_set_rows_f32_i32")]] kernel set_rows_f_t kernel_set_rows_f; +template [[host_name("kernel_set_rows_f16_i64")]] kernel set_rows_f_t kernel_set_rows_f; +template [[host_name("kernel_set_rows_f16_i32")]] kernel set_rows_f_t kernel_set_rows_f; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_set_rows_bf16_i64")]] kernel set_rows_f_t kernel_set_rows_f; +template [[host_name("kernel_set_rows_bf16_i32")]] kernel set_rows_f_t kernel_set_rows_f; +#endif + +typedef decltype(kernel_set_rows_q32) set_rows_q32_t; + +template [[host_name("kernel_set_rows_q8_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q8_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q4_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q4_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q4_1_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q4_1_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q5_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q5_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q5_1_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_q5_1_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_iq4_nl_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; +template [[host_name("kernel_set_rows_iq4_nl_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; + +// +// matrix-matrix multiplication +// + +typedef decltype(kernel_mul_mm) mul_mm_t; + +template [[host_name("kernel_mul_mm_f32_f32")]] kernel mul_mm_t kernel_mul_mm; + +typedef decltype(kernel_mul_mm_acc) mul_mm_acc_t; + +template [[host_name("kernel_mul_mm_acc_f32_f32")]] kernel mul_mm_acc_t kernel_mul_mm_acc; +template [[host_name("kernel_mul_mm_f16_f32")]] kernel mul_mm_t kernel_mul_mm; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_mul_mm_bf16_f32")]] kernel mul_mm_t kernel_mul_mm; +#endif +template [[host_name("kernel_mul_mm_q1_0_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q4_0_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q4_1_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q5_0_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q5_1_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q8_0_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_mxfp4_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q2_K_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q3_K_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q4_K_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q5_K_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q6_K_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq2_xxs_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq2_xs_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq3_xxs_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq3_s_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq2_s_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq1_s_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq1_m_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq4_nl_f32")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq4_xs_f32")]] kernel mul_mm_t kernel_mul_mm; + +template [[host_name("kernel_mul_mm_f32_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_f16_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q1_0_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q4_0_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q4_1_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q5_0_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q5_1_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q8_0_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_mxfp4_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q2_K_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q3_K_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q4_K_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q5_K_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_q6_K_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq2_xxs_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq2_xs_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq3_xxs_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq3_s_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq2_s_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq1_s_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq1_m_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq4_nl_f16")]] kernel mul_mm_t kernel_mul_mm; +template [[host_name("kernel_mul_mm_iq4_xs_f16")]] kernel mul_mm_t kernel_mul_mm; + +// +// indirect matrix-matrix multiplication +// + +typedef decltype(kernel_mul_mm_id) mul_mm_id; + +template [[host_name("kernel_mul_mm_id_f32_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_f16_f32")]] kernel mul_mm_id kernel_mul_mm_id; +#if defined(GGML_METAL_HAS_BF16) +template [[host_name("kernel_mul_mm_id_bf16_f32")]] kernel mul_mm_id kernel_mul_mm_id; +#endif +template [[host_name("kernel_mul_mm_id_q1_0_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q4_0_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q4_1_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q5_0_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q5_1_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q8_0_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_mxfp4_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q2_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q3_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q4_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q5_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q6_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq2_xxs_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq2_xs_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq3_xxs_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq3_s_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq2_s_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq1_s_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq1_m_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq4_nl_f32")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq4_xs_f32")]] kernel mul_mm_id kernel_mul_mm_id; + +template [[host_name("kernel_mul_mm_id_f32_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_f16_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q1_0_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q4_0_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q4_1_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q5_0_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q5_1_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q8_0_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_mxfp4_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q2_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q3_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q4_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q5_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_q6_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq2_xxs_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq2_xs_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq3_xxs_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq3_s_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq2_s_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq1_s_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq1_m_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq4_nl_f16")]] kernel mul_mm_id kernel_mul_mm_id; +template [[host_name("kernel_mul_mm_id_iq4_xs_f16")]] kernel mul_mm_id kernel_mul_mm_id; + +// +// matrix-vector multiplication +// + +typedef void (kernel_mul_mv_disp_t)( + ggml_metal_kargs_mul_mv args, + device const char * src0, + device const char * src1, + device char * dst, + uint3 tgpig, + ushort tiisg); + +typedef void (kernel_mul_mv2_disp_t)( + ggml_metal_kargs_mul_mv args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiisg, + ushort sgitg); + +template +void mmv_fn( + ggml_metal_kargs_mul_mv args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiitg, + ushort tiisg, + ushort sgitg) { + disp_fn(args, src0, src1, dst, tgpig, tiisg); +} + +template +void mmv_fn( + ggml_metal_kargs_mul_mv args, + device const char * src0, + device const char * src1, + device char * dst, + threadgroup char * shmem, + uint3 tgpig, + ushort tiitg, + ushort tiisg, + ushort sgitg) { + disp_fn(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); +} - device char * dst_row = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1; - const int64_t i03 = i3 - args.lp3; - const int64_t i02 = i2 - args.lp2; - const int64_t i01 = i1 - args.lp1; - const bool in_src_row = i01 >= 0 && i01 < args.ne01 && - i02 >= 0 && i02 < args.ne02 && - i03 >= 0 && i03 < args.ne03; +typedef decltype(mmv_fn>) mul_mv_disp_fn_t; - if (in_src_row) { - device const char * src0_row = src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01; - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - const int64_t i00 = i0 - args.lp0; - device int * dst_ptr = (device int *) (dst_row + i0*args.nb0); +template +kernel void kernel_mul_mv_id( + constant ggml_metal_kargs_mul_mv_id & args, + device const char * src0s, + device const char * src1, + device char * dst, + device const char * ids, + threadgroup char * shmem [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort tiitg[[thread_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]]) { + const int iid1 = tgpig.z/args.nei0; + const int idx = tgpig.z%args.nei0; - if (i00 >= 0 && i00 < args.ne00) { - device const int * src0_ptr = (device const int *) (src0_row + i00*args.nb00); - *dst_ptr = *src0_ptr; - } else { - *dst_ptr = 0; - } - } + tgpig.z = 0; - return; - } + const int32_t i02 = ((device const int32_t *) (ids + iid1*args.nbi1))[idx]; - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - device int * dst_ptr = (device int *) (dst_row + i0*args.nb0); - *dst_ptr = 0; - } -} + const int64_t i11 = idx % args.ne11; + const int64_t i12 = iid1; -kernel void kernel_pad_reflect_1d_f32( - constant ggml_metal_kargs_pad_reflect_1d & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tgpg[[threadgroups_per_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - const int64_t i3 = tgpig.z; - const int64_t i2 = tgpig.y; - const int64_t i1 = tgpig.x; - - const int64_t i03 = i3; - const int64_t i02 = i2; - const int64_t i01 = i1; - - device const float * src0_ptr = (device const float *) (src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); - device float * dst_ptr = (device float *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1); - - if (i1 < args.ne01 && i2 < args.ne02 && i3 < args.ne03) { - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - if (i0 < args.p0) { - dst_ptr[i0] = src0_ptr[args.p0 - i0]; - } else if (i0 < args.ne0 - args.p1) { - dst_ptr[i0] = src0_ptr[i0 - args.p0]; - } else { - dst_ptr[i0] = src0_ptr[(args.ne0 - args.p1 - args.p0) - (args.p1 + 1 - (args.ne0 - i0)) - 1]; - } - } - } -} - -kernel void kernel_arange_f32( - constant ggml_metal_kargs_arange & args, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - device float * dst_ptr = (device float *) dst; - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - dst_ptr[i0] = args.start + args.step * i0; - } -} - -kernel void kernel_timestep_embedding_f32( - constant ggml_metal_kargs_timestep_embedding & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint3 tpitg[[thread_position_in_threadgroup]], - uint3 ntg[[threads_per_threadgroup]]) { - - int i = tgpig.x; - device float * embed_data = (device float *)(dst + i*args.nb1); - - int half_ = args.dim / 2; - for (int j = tpitg.x; j < half_; j += ntg.x) { - float timestep = ((device float *)src0)[i]; - float freq = (float)exp(-log((float)args.max_period) * j / half_); - float arg = timestep * freq; - embed_data[j ] = cos(arg); - embed_data[j + half_] = sin(arg); - } - - if (args.dim % 2 != 0 && tpitg.x == 0) { - embed_data[2 * half_] = 0.f; - } -} - -// bitonic sort implementation following the CUDA kernels as reference -typedef void (argsort_t)( - constant ggml_metal_kargs_argsort & args, - device const char * src0, - device int32_t * dst, - threadgroup int32_t * shmem_i32 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]); - -template -kernel void kernel_argsort_f32_i32( - constant ggml_metal_kargs_argsort & args, - device const char * src0, - device int32_t * dst, - threadgroup int32_t * shmem_i32 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - // bitonic sort - const int col = tpitg[0]; - const int ib = tgpig[0] / args.ne01; - - const int i00 = ib*ntg.x; - const int i01 = tgpig[0] % args.ne01; - const int i02 = tgpig[1]; - const int i03 = tgpig[2]; - - device const float * src0_row = (device const float *) (src0 + args.nb01*i01 + args.nb02*i02 + args.nb03*i03); - - // initialize indices - shmem_i32[col] = i00 + col; - - threadgroup_barrier(mem_flags::mem_threadgroup); - - for (int k = 2; k <= ntg.x; k *= 2) { - for (int j = k / 2; j > 0; j /= 2) { - int ixj = col ^ j; - if (ixj > col) { - if ((col & k) == 0) { - if (shmem_i32[col] >= args.ne00 || - (shmem_i32[ixj] < args.ne00 && (order == GGML_SORT_ORDER_ASC ? - src0_row[shmem_i32[col]] > src0_row[shmem_i32[ixj]] : - src0_row[shmem_i32[col]] < src0_row[shmem_i32[ixj]])) - ) { - SWAP(shmem_i32[col], shmem_i32[ixj]); - } - } else { - if (shmem_i32[ixj] >= args.ne00 || - (shmem_i32[col] < args.ne00 && (order == GGML_SORT_ORDER_ASC ? - src0_row[shmem_i32[col]] < src0_row[shmem_i32[ixj]] : - src0_row[shmem_i32[col]] > src0_row[shmem_i32[ixj]])) - ) { - SWAP(shmem_i32[col], shmem_i32[ixj]); - } - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - } - } - - const int64_t i0 = ib*args.top_k; - - // copy the result to dst without the padding - if (i0 + col < args.ne0 && col < args.top_k) { - dst += i0 + args.ne0*i01 + args.ne0*args.ne1*i02 + args.ne0*args.ne1*args.ne2*i03; - - dst[col] = shmem_i32[col]; - } -} - -template [[host_name("kernel_argsort_f32_i32_asc")]] kernel argsort_t kernel_argsort_f32_i32; -template [[host_name("kernel_argsort_f32_i32_desc")]] kernel argsort_t kernel_argsort_f32_i32; - -typedef void (argsort_merge_t)( - constant ggml_metal_kargs_argsort_merge & args, - device const char * src0, - device const int32_t * tmp, - device int32_t * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]); - -template -kernel void kernel_argsort_merge_f32_i32( - constant ggml_metal_kargs_argsort_merge & args, - device const char * src0, - device const int32_t * tmp, - device int32_t * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - - const int im = tgpig[0] / args.ne01; - const int i01 = tgpig[0] % args.ne01; - const int i02 = tgpig[1]; - const int i03 = tgpig[2]; - - const int start = im * (2 * args.len); - - const int len0 = MIN(args.len, MAX(0, args.ne0 - (int)(start))); - const int len1 = MIN(args.len, MAX(0, args.ne0 - (int)(start + args.len))); - - const int total = len0 + len1; - - device const int32_t * tmp0 = tmp + start - + i01*args.ne0 - + i02*args.ne0*args.ne01 - + i03*args.ne0*args.ne01*args.ne02; - - device const int32_t * tmp1 = tmp0 + args.len; - - dst += start - + i01*args.top_k - + i02*args.top_k*args.ne01 - + i03*args.top_k*args.ne01*args.ne02; - - device const float * src0_row = (device const float *)(src0 - + args.nb01*i01 - + args.nb02*i02 - + args.nb03*i03); - - if (total == 0) { - return; - } - - const int chunk = (total + ntg.x - 1) / ntg.x; - - const int k0 = tpitg.x * chunk; - const int k1 = MIN(MIN(k0 + chunk, total), args.top_k); - - if (k0 >= args.top_k) { - return; - } - - if (k0 >= total) { - return; - } - - int low = k0 > len1 ? k0 - len1 : 0; - int high = MIN(k0, len0); - - // binary-search partition (i, j) such that i + j = k - while (low < high) { - const int mid = (low + high) >> 1; - - const int32_t idx0 = tmp0[mid]; - const int32_t idx1 = tmp1[k0 - mid - 1]; - - const float val0 = src0_row[idx0]; - const float val1 = src0_row[idx1]; - - bool take_left; - if (order == GGML_SORT_ORDER_ASC) { - take_left = (val0 <= val1); - } else { - take_left = (val0 >= val1); - } - - if (take_left) { - low = mid + 1; - } else { - high = mid; - } - } - - int i = low; - int j = k0 - i; - - // keep the merge fronts into registers - int32_t idx0 = 0; - float val0 = 0.0f; - if (i < len0) { - idx0 = tmp0[i]; - val0 = src0_row[idx0]; - } - - int32_t idx1 = 0; - float val1 = 0.0f; - if (j < len1) { - idx1 = tmp1[j]; - val1 = src0_row[idx1]; - } - - for (int k = k0; k < k1; ++k) { - int32_t out_idx; - - if (i >= len0) { - while (k < k1) { - dst[k++] = tmp1[j++]; - } - break; - } else if (j >= len1) { - while (k < k1) { - dst[k++] = tmp0[i++]; - } - break; - } else { - bool take_left; - - if (order == GGML_SORT_ORDER_ASC) { - take_left = (val0 <= val1); - } else { - take_left = (val0 >= val1); - } - - if (take_left) { - out_idx = idx0; - ++i; - if (i < len0) { - idx0 = tmp0[i]; - val0 = src0_row[idx0]; - } - } else { - out_idx = idx1; - ++j; - if (j < len1) { - idx1 = tmp1[j]; - val1 = src0_row[idx1]; - } - } - } - - dst[k] = out_idx; - } -} - -template [[host_name("kernel_argsort_merge_f32_i32_asc")]] kernel argsort_merge_t kernel_argsort_merge_f32_i32; -template [[host_name("kernel_argsort_merge_f32_i32_desc")]] kernel argsort_merge_t kernel_argsort_merge_f32_i32; - -constant bool FC_flash_attn_ext_pad_has_mask [[function_constant(FC_FLASH_ATTN_EXT_PAD + 0)]]; - -constant int32_t FC_flash_attn_ext_pad_ncpsg [[function_constant(FC_FLASH_ATTN_EXT_PAD + 25)]]; - -// pad the last chunk of C elements of k and v into a an extra pad buffer -kernel void kernel_flash_attn_ext_pad( - constant ggml_metal_kargs_flash_attn_ext_pad & args, - device const char * k, - device const char * v, - device const char * mask, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int32_t C = FC_flash_attn_ext_pad_ncpsg; - - device char * k_pad = dst; - device char * v_pad = k_pad + args.nb11*C*args.ne_12_2*args.ne_12_3; - device char * mask_pad = v_pad + args.nb21*C*args.ne_12_2*args.ne_12_3; - - const int32_t icp = args.ne11 % C; - const int32_t ic0 = args.ne11 - icp; - - const int32_t i1 = tgpig[0]; - const int32_t i2 = tgpig[1]; - const int32_t i3 = tgpig[2]; - - if (i2 < args.ne_12_2 && i3 < args.ne_12_3) { - device const char * k_src = k + args.nb11*(ic0 + i1) + args.nb12*i2 + args.nb13*i3; - device const char * v_src = v + args.nb21*(ic0 + i1) + args.nb22*i2 + args.nb23*i3; - - device char * k_dst = k_pad + args.nb11*i1 + args.nb11*C*i2 + args.nb11*C*args.ne_12_2*i3; - device char * v_dst = v_pad + args.nb21*i1 + args.nb21*C*i2 + args.nb21*C*args.ne_12_2*i3; - - if (i1 >= icp) { - // here it is not important the exact value that will be used as we rely on masking out the scores in the attention - for (uint64_t i = tiitg; i < args.nb11; i += ntg.x) { - k_dst[i] = 0; - } - for (uint64_t i = tiitg; i < args.nb21; i += ntg.x) { - v_dst[i] = 0; - } - } else { - for (uint64_t i = tiitg; i < args.nb11; i += ntg.x) { - k_dst[i] = k_src[i]; - } - for (uint64_t i = tiitg; i < args.nb21; i += ntg.x) { - v_dst[i] = v_src[i]; - } - } - } - - if (FC_flash_attn_ext_pad_has_mask) { - if (i2 < args.ne32 && i3 < args.ne33) { - for (int ib = i1; ib < args.ne31; ib += C) { - device const half * mask_src = (device const half *)(mask + args.nb31*ib + args.nb32*i2 + args.nb33*i3) + ic0; - device half * mask_dst = (device half *)(mask_pad) + C*ib + C*args.ne31*i2 + C*args.ne31*args.ne32*i3; - - for (int i = tiitg; i < C; i += ntg.x) { - if (i >= icp) { - mask_dst[i] = -MAXHALF; - } else { - mask_dst[i] = mask_src[i]; - } - } - } - } - } -} - -constant int32_t FC_flash_attn_ext_blk_nqptg [[function_constant(FC_FLASH_ATTN_EXT_BLK + 24)]]; -constant int32_t FC_flash_attn_ext_blk_ncpsg [[function_constant(FC_FLASH_ATTN_EXT_BLK + 25)]]; - -// scan the blocks of the mask that are not masked -// 0 - masked (i.e. full of -INF, skip) -// 1 - not masked (i.e. at least one element of the mask is not -INF) -// 2 - all zero -kernel void kernel_flash_attn_ext_blk( - constant ggml_metal_kargs_flash_attn_ext_blk & args, - device const char * mask, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]]) { - // block size C x Q - const int32_t Q = FC_flash_attn_ext_blk_nqptg; - const int32_t C = FC_flash_attn_ext_blk_ncpsg; - - constexpr short NW = N_SIMDWIDTH; - - const int32_t i3 = tgpig[2]/args.ne32; - const int32_t i2 = tgpig[2]%args.ne32; - const int32_t i1 = tgpig[1]; - const int32_t i0 = tgpig[0]; - - char res = i0*C + C > args.ne30 ? 1 : 0; - - device const half * mask_src = (device const half *) (mask + (i1*Q)*args.nb31 + i2*args.nb32 + i3*args.nb33) + i0*C + tiisg; - - // detailed check of the elements of the block - if ((C > NW || Q > 1) && res == 0) { - half mmin = MAXHALF; - half mmax = -MAXHALF; - - FOR_UNROLL (short j = 0; j < Q; ++j) { - FOR_UNROLL (short ii = 0; ii < C/NW; ++ii) { - mmin = min(mmin, mask_src[ii*NW]); - mmax = max(mmax, mask_src[ii*NW]); - } - - mask_src += args.nb31/2; - } - - mmin = simd_min(mmin); - mmax = simd_max(mmax); - - if (mmax > -MAXHALF) { - if (mmin == 0.0 && mmax == 0.0) { - res = 2; - } else { - res = 1; - } - } - } - - const int32_t nblk1 = ((args.ne01 + Q - 1)/Q); - const int32_t nblk0 = ((args.ne30 + C - 1)/C); - - if (tiisg == 0) { - dst[((i3*args.ne32 + i2)*nblk1 + i1)*nblk0 + i0] = res; - } -} - -constant bool FC_flash_attn_ext_has_mask [[function_constant(FC_FLASH_ATTN_EXT + 0)]]; -constant bool FC_flash_attn_ext_has_sinks [[function_constant(FC_FLASH_ATTN_EXT + 1)]]; -constant bool FC_flash_attn_ext_has_bias [[function_constant(FC_FLASH_ATTN_EXT + 2)]]; -constant bool FC_flash_attn_ext_has_scap [[function_constant(FC_FLASH_ATTN_EXT + 3)]]; -constant bool FC_flash_attn_ext_has_kvpad [[function_constant(FC_FLASH_ATTN_EXT + 4)]]; - -constant bool FC_flash_attn_ext_bc_mask [[function_constant(FC_FLASH_ATTN_EXT + 10)]]; - -//constant float FC_flash_attn_ext_scale [[function_constant(FC_FLASH_ATTN_EXT + 10)]]; -//constant float FC_flash_attn_ext_max_bias [[function_constant(FC_FLASH_ATTN_EXT + 11)]]; -//constant float FC_flash_attn_ext_logit_softcap [[function_constant(FC_FLASH_ATTN_EXT + 12)]]; - -constant int32_t FC_flash_attn_ext_ns10 [[function_constant(FC_FLASH_ATTN_EXT + 20)]]; -constant int32_t FC_flash_attn_ext_ns20 [[function_constant(FC_FLASH_ATTN_EXT + 21)]]; -constant int32_t FC_flash_attn_ext_nsg [[function_constant(FC_FLASH_ATTN_EXT + 22)]]; - -// ref: https://arxiv.org/pdf/2307.08691.pdf -template< - typename q_t, // query types in shared memory - typename q4_t, - typename q8x8_t, - typename k_t, // key types in shared memory - typename k4x4_t, - typename k8x8_t, - typename v_t, // value types in shared memory - typename v4x4_t, - typename v8x8_t, - typename qk_t, // Q*K types - typename qk8x8_t, - typename s_t, // soft-max types - typename s2_t, - typename s8x8_t, - typename o_t, // attention accumulation types - typename o4_t, - typename o8x8_t, - typename kd4x4_t, // key type in device memory - short nl_k, - void (*deq_k)(device const kd4x4_t *, short, thread k4x4_t &), - typename vd4x4_t, // value type in device memory - short nl_v, - void (*deq_v)(device const vd4x4_t *, short, thread v4x4_t &), - short DK, // K head size - short DV, // V head size - short Q, // queries per threadgroup - short C, // cache items per threadgroup - short NSG> // number of simd groups -void kernel_flash_attn_ext_impl( - constant ggml_metal_kargs_flash_attn_ext & args, - device const char * q, - device const char * k, - device const char * v, - device const char * mask, - device const char * sinks, - device const char * pad, - device const char * blk, - device char * dst, - threadgroup half * shmem_f16, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const ushort iq3 = tgpig[2]; - const ushort iq2 = tgpig[1]; - const ushort iq1 = tgpig[0]*Q; - -#define NS10 (FC_flash_attn_ext_ns10) -#define NS20 (FC_flash_attn_ext_ns20) - - // note: I had some concerns that using this instead of the ugly macros above was affecting performance - // need to re-check carefully and if no regressions are observerd - remove the macros - // the concerns is that maybe using const variables requires extra registers? but not sure if the compiler - // is clever enough to avoid this. unfortunately, using constexpr is not possible with FC - //const short NS10 = FC_flash_attn_ext_ns10; - //const short NS20 = FC_flash_attn_ext_ns20; - - constexpr short KV = 8; - - constexpr short DK4 = DK/4; - constexpr short DK8 = DK/8; - constexpr short DK16 = DK/16; - constexpr short DV4 = DV/4; - //constexpr short DV8 = DV/8; - constexpr short DV16 = DV/16; - - constexpr short PV = PAD2(DV, 64); - constexpr short PV4 = PV/4; - constexpr short PV8 = PV/8; - //constexpr short PV16 = PV/16; - - constexpr short NW = N_SIMDWIDTH; - constexpr short NQ = Q/NSG; - constexpr short SH = 2*C; // shared memory per simdgroup (s_t == float) - - constexpr short TS = 2*SH; - constexpr short T = DK + 2*PV; // shared memory size per query in (half) - - threadgroup q_t * sq = (threadgroup q_t *) (shmem_f16 + 0*T); // holds the query data - threadgroup q4_t * sq4 = (threadgroup q4_t *) (shmem_f16 + 0*T); // same as above but in q4_t - threadgroup o_t * so = (threadgroup o_t *) (shmem_f16 + 0*T + Q*DK); // the result for all queries in 8x8 matrices (the O matrix from the paper) - threadgroup o4_t * so4 = (threadgroup o4_t *) (shmem_f16 + 0*T + Q*DK); - threadgroup s_t * ss = (threadgroup s_t *) (shmem_f16 + Q*T); // scratch buffer for attention, mask and diagonal matrix - threadgroup s2_t * ss2 = (threadgroup s2_t *) (shmem_f16 + Q*T); // same as above but in s2_t - - threadgroup k_t * sk = (threadgroup k_t *) (shmem_f16 + sgitg*(4*16*KV) + Q*T + Q*TS); // scratch buffer to load K in shared memory - threadgroup k4x4_t * sk4x4 = (threadgroup k4x4_t *) (shmem_f16 + sgitg*(4*16*KV) + Q*T + Q*TS); // same as above but in k4x4_t - - threadgroup v_t * sv = (threadgroup v_t *) (shmem_f16 + sgitg*(4*16*KV) + Q*T + Q*TS); // scratch buffer to load V in shared memory - threadgroup v4x4_t * sv4x4 = (threadgroup v4x4_t *) (shmem_f16 + sgitg*(4*16*KV) + Q*T + Q*TS); // same as above but in v4x4_t - - // mask storage in shared mem - threadgroup half2 * sm2 = (threadgroup half2 *) (shmem_f16 + Q*T + 2*C); - - // per-query mask pointers - device const half2 * pm2[NQ]; - - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - - pm2[jj] = (device const half2 *) ((device const char *) mask + (iq1 + j)*args.nb31 + (iq2%args.ne32)*args.nb32 + (iq3%args.ne33)*args.nb33); - } - - { - const int32_t nblk1 = ((args.ne01 + Q - 1)/Q); - const int32_t nblk0 = ((args.ne11 + C - 1)/C); - - blk += (((iq3%args.ne33)*args.ne32 + (iq2%args.ne32))*nblk1 + iq1/Q)*nblk0; - } - - { - q += iq1*args.nb01 + iq2*args.nb02 + iq3*args.nb03; - - const short ikv2 = iq2/(args.ne02/args.ne_12_2); - const short ikv3 = iq3/(args.ne03/args.ne_12_3); - - k += ikv2*args.nb12 + ikv3*args.nb13; - v += ikv2*args.nb22 + ikv3*args.nb23; - } - - // load heads from Q to shared memory - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - - device const float4 * q4 = (device const float4 *) ((device const char *) q + j*args.nb01); - - for (short i = tiisg; i < DK4; i += NW) { - if (iq1 + j < args.ne01) { - sq4[j*DK4 + i] = (q4_t) q4[i]; - } else { - sq4[j*DK4 + i] = 0; - } - } - } - - // zero out - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - - for (short i = tiisg; i < DV4; i += NW) { - so4[j*PV4 + i] = 0; - } - - for (short i = tiisg; i < SH; i += NW) { - ss[j*SH + i] = 0.0f; - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - float S[NQ] = { [0 ... NQ-1] = 0.0f }; - - { - float M[NQ] = { [0 ... NQ-1] = -FLT_MAX/2 }; - - float slope = 1.0f; - - // ALiBi - if (FC_flash_attn_ext_has_bias) { - const short h = iq2; - - const float base = h < args.n_head_log2 ? args.m0 : args.m1; - const short exph = h < args.n_head_log2 ? h + 1 : 2*(h - args.n_head_log2) + 1; - - slope = pow(base, exph); - } - - // loop over the KV cache - // each simdgroup handles blocks of Q rows and C columns - for (int ic0 = 0; ; ++ic0) { - int ic = ic0*C; - if (ic >= args.ne11) { - break; - } - - // the last partial chunk uses the pad buffer as source - if (FC_flash_attn_ext_has_kvpad && ic + C > args.ne11) { - k = pad; - v = k + args.nb11*C*args.ne_12_2*args.ne_12_3; - mask = v + args.nb21*C*args.ne_12_2*args.ne_12_3; - - const short ikv2 = iq2/(args.ne02/args.ne_12_2); - const short ikv3 = iq3/(args.ne03/args.ne_12_3); - - k += (ikv2 + ikv3*args.ne_12_2)*args.nb11*C; - v += (ikv2 + ikv3*args.ne_12_2)*args.nb21*C; - - if (!FC_flash_attn_ext_has_mask) { - threadgroup half * sm = (threadgroup half *) (sm2); - - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - - for (short i = tiisg; i < C; i += NW) { - if (ic + i >= args.ne11) { - sm[2*j*SH + i] = -MAXHALF; - } - } - } - } else { - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - - pm2[jj] = (device const half2 *) ((device const half *) mask + - (iq1 + j)*C + - (iq2%args.ne32)*(C*args.ne31) + - (iq3%args.ne33)*(C*args.ne31*args.ne32)); - } - } - - ic = 0; - } - - char blk_cur = 1; - - // read the mask into shared mem - if (FC_flash_attn_ext_has_mask) { - blk_cur = blk[ic0]; - - if (blk_cur == 0) { - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - pm2[jj] += NW; - } - - continue; - } - - if (blk_cur == 1) { - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - - if (FC_flash_attn_ext_bc_mask) { - sm2[j*SH + tiisg] = (iq1 + j) < args.ne31 ? pm2[jj][tiisg] : half2(-MAXHALF, -MAXHALF); - } else { - sm2[j*SH + tiisg] = pm2[jj][tiisg]; - } - - pm2[jj] += NW; - } - } else if (blk_cur == 2) { - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - pm2[jj] += NW; - } - } - -#if 0 - // note: old -INF block optimization - obsoleted by pre-computing non-masked blocks - - threadgroup_barrier(mem_flags::mem_threadgroup); - - // used to detect blocks full of -INF - // skip only when the entire threadgroup is masked - half2 smax2(-MAXHALF/2, -MAXHALF/2); - - FOR_UNROLL (short j = 0; j < Q; ++j) { - smax2 = max(smax2, sm2[j*SH + tiisg]); - } - - smax2 = simd_max(smax2); - - if (max(smax2[0], smax2[1]) <= -MAXHALF/2) { - // this barrier is important - threadgroup_barrier(mem_flags::mem_threadgroup); - - continue; - } -#endif - } - - // Q*K^T - // this is compile-time check, so it does not have runtime overhead - if (is_same::value) { - // we can read directly from global memory - device const k_t * pk = (device const k_t *) (k + ic*args.nb11); - threadgroup const q_t * pq = sq; - threadgroup s_t * ps = ss; - - pk += sgitg*(8*NS10); - ps += sgitg*(8*1); - - static_assert((C/8) % NSG == 0, ""); - - constexpr short NC = (C/8)/NSG; - - FOR_UNROLL (short cc = 0; cc < NC; ++cc) { - qk8x8_t mqk = make_filled_simdgroup_matrix((qk_t) 0.0f); - - if (DK % 16 != 0) { - k8x8_t mk; - q8x8_t mq; - - FOR_UNROLL (short i = 0; i < DK8; ++i) { - simdgroup_barrier(mem_flags::mem_none); - - simdgroup_load(mk, pk + 8*i, NS10, 0, true); - simdgroup_load(mq, pq + 8*i, DK); - - simdgroup_barrier(mem_flags::mem_none); - - simdgroup_multiply_accumulate(mqk, mq, mk, mqk); - } - } else { - k8x8_t mk[2]; - q8x8_t mq[2]; - - // note: too much unroll can tank the performance for large heads - #pragma unroll (MIN(DK8/2, 4*NSG)) - for (short i = 0; i < DK8/2; ++i) { - simdgroup_barrier(mem_flags::mem_none); - - simdgroup_load(mq[0], pq + 0*8 + 16*i, DK); - simdgroup_load(mq[1], pq + 1*8 + 16*i, DK); - - simdgroup_load(mk[0], pk + 0*8 + 16*i, NS10, 0, true); - simdgroup_load(mk[1], pk + 1*8 + 16*i, NS10, 0, true); - - simdgroup_barrier(mem_flags::mem_none); - - simdgroup_multiply_accumulate(mqk, mq[0], mk[0], mqk); - simdgroup_multiply_accumulate(mqk, mq[1], mk[1], mqk); - } - } - - simdgroup_store(mqk, ps, SH, 0, false); - - pk += 8*(NSG*NS10); - ps += 8*(NSG); - } - } else { - // TODO: this is the quantized K cache branch - not optimized yet - for (short ccc = 0; ccc < (C/8)/NSG; ++ccc) { - const short cc = ccc*NSG + sgitg; - - const short tx = tiisg%4; - const short ty = tiisg/4; - - qk8x8_t mqk = make_filled_simdgroup_matrix((qk_t) 0.0f); - - for (short ii = 0; ii < DK16; ii += 4) { - device const kd4x4_t * pk4x4 = (device const kd4x4_t *) (k + ((ic + 8*cc + ty)*args.nb11)); - - if (DK16%4 == 0) { - // the head is evenly divisible by 4*16 = 64, so no need for bound checks - { - k4x4_t tmp; - deq_k(pk4x4 + (ii + tx)/nl_k, (ii + tx)%nl_k, tmp); - sk4x4[4*ty + tx] = tmp; - } - - simdgroup_barrier(mem_flags::mem_threadgroup); - - FOR_UNROLL (short k = 0; k < 4; ++k) { - k8x8_t mk; - q8x8_t mq; - - simdgroup_load(mk, sk + 16*k + 0*8, 4*16, 0, true); // transpose - simdgroup_load(mq, sq + (2*(ii + k) + 0)*8, DK); - simdgroup_multiply_accumulate(mqk, mq, mk, mqk); - - simdgroup_load(mk, sk + 16*k + 1*8, 4*16, 0, true); // transpose - simdgroup_load(mq, sq + (2*(ii + k) + 1)*8, DK); - simdgroup_multiply_accumulate(mqk, mq, mk, mqk); - } - } else { - if (ii + tx < DK16) { - k4x4_t tmp; - deq_k(pk4x4 + (ii + tx)/nl_k, (ii + tx)%nl_k, tmp); - sk4x4[4*ty + tx] = tmp; - } - - simdgroup_barrier(mem_flags::mem_threadgroup); - - for (short k = 0; k < 4 && ii + k < DK16; ++k) { - k8x8_t mk; - q8x8_t mq; - - simdgroup_load(mk, sk + 16*k + 0*8, 4*16, 0, true); // transpose - simdgroup_load(mq, sq + (2*(ii + k) + 0)*8, DK); - simdgroup_multiply_accumulate(mqk, mq, mk, mqk); - - simdgroup_load(mk, sk + 16*k + 1*8, 4*16, 0, true); // transpose - simdgroup_load(mq, sq + (2*(ii + k) + 1)*8, DK); - simdgroup_multiply_accumulate(mqk, mq, mk, mqk); - } - } - } - - simdgroup_store(mqk, ss + 8*cc, SH, 0, false); - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - // online softmax - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - - const float m = M[jj]; - - // scale and apply the logitcap / mask - float2 s2 = ss2[j*SH/2 + tiisg]*args.scale; - - if (FC_flash_attn_ext_has_scap) { - s2 = args.logit_softcap*precise::tanh(s2); - } - - // mqk = mqk + slope*mask - if (blk_cur != 2) { - if (FC_flash_attn_ext_has_bias) { - s2 += s2_t(sm2[j*SH + tiisg])*slope; - } else { - s2 += s2_t(sm2[j*SH + tiisg]); - } - } - - M[jj] = simd_max(max(M[jj], max(s2[0], s2[1]))); - - const float ms = exp(m - M[jj]); - const float2 vs2 = exp(s2 - M[jj]); - - S[jj] = S[jj]*ms + simd_sum(vs2[0] + vs2[1]); - - // the P matrix from the paper (Q rows, C columns) - ss2[j*SH/2 + tiisg] = vs2; - - if (DV4 % NW == 0) { - FOR_UNROLL (short ii = 0; ii < DV4/NW; ++ii) { - const short i = ii*NW + tiisg; - - so4[j*PV4 + i] *= ms; - } - } else { - for (short i = tiisg; i < DV4; i += NW) { - so4[j*PV4 + i] *= ms; - } - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - // O = O + (Q*K^T)*V - { - // we can read directly from global memory - if (is_same::value) { - static_assert(PV8 % NSG == 0, ""); - - constexpr short NO = PV8/NSG; - - o8x8_t lo[NO]; - - { - auto sot = so + 8*sgitg; - - FOR_UNROLL (short ii = 0; ii < NO; ++ii) { - simdgroup_load(lo[ii], sot, PV, 0, false); - - sot += 8*NSG; - } - } - - { - device const v_t * pv = (device const v_t *) (v + ic*args.nb21); - - pv += 8*sgitg; - - if (DV <= 64) { - FOR_UNROLL (short cc = 0; cc < C/8; ++cc) { - s8x8_t vs; - simdgroup_load(vs, ss + 8*cc, SH, 0, false); - - FOR_UNROLL (short ii = 0; ii < NO/2; ++ii) { - v8x8_t mv[2]; - - simdgroup_load(mv[0], pv + 0*NSG + 16*ii*NSG, NS20, 0, false); - simdgroup_load(mv[1], pv + 8*NSG + 16*ii*NSG, NS20, 0, false); - - simdgroup_multiply_accumulate(lo[2*ii + 0], vs, mv[0], lo[2*ii + 0]); - simdgroup_multiply_accumulate(lo[2*ii + 1], vs, mv[1], lo[2*ii + 1]); - } - - pv += 8*NS20; - } - } else { - constexpr short NC = (C/8)/2; - - FOR_UNROLL (short cc = 0; cc < NC; ++cc) { - s8x8_t vs[2]; - - simdgroup_load(vs[0], ss + 16*cc + 0, SH, 0, false); - simdgroup_load(vs[1], ss + 16*cc + 8, SH, 0, false); - - FOR_UNROLL (short ii = 0; ii < NO/2; ++ii) { - v8x8_t mv[4]; - - simdgroup_load(mv[0], pv + 0*NSG + 16*ii*NSG + 0*8*NS20, NS20, 0, false); - simdgroup_load(mv[1], pv + 8*NSG + 16*ii*NSG + 0*8*NS20, NS20, 0, false); - simdgroup_load(mv[2], pv + 0*NSG + 16*ii*NSG + 1*8*NS20, NS20, 0, false); - simdgroup_load(mv[3], pv + 8*NSG + 16*ii*NSG + 1*8*NS20, NS20, 0, false); - - simdgroup_multiply_accumulate(lo[2*ii + 0], vs[0], mv[0], lo[2*ii + 0]); - simdgroup_multiply_accumulate(lo[2*ii + 1], vs[0], mv[1], lo[2*ii + 1]); - simdgroup_multiply_accumulate(lo[2*ii + 0], vs[1], mv[2], lo[2*ii + 0]); - simdgroup_multiply_accumulate(lo[2*ii + 1], vs[1], mv[3], lo[2*ii + 1]); - } - - pv += 2*8*NS20; - } - } - } - - { - auto sot = so + 8*sgitg; - - FOR_UNROLL (short ii = 0; ii < NO; ++ii) { - simdgroup_store(lo[ii], sot, PV, 0, false); - - sot += 8*NSG; - } - } - } else { - // TODO: this is the quantized V cache branch - not optimized yet - - const short tx = tiisg%4; - const short ty = tiisg/4; - - for (short cc = 0; cc < C/8; ++cc) { - s8x8_t vs; - simdgroup_load(vs, ss + 8*cc, SH, 0, false); - - for (short ii = 4*sgitg; ii < DV16; ii += 4*NSG) { - device const vd4x4_t * pv4x4 = (device const vd4x4_t *) (v + ((ic + 8*cc + ty)*args.nb21)); - - if (DV16%4 == 0) { - // no need for bound checks - { - v4x4_t tmp; - deq_v(pv4x4 + (ii + tx)/nl_v, (ii + tx)%nl_v, tmp); - sv4x4[4*ty + tx] = tmp; - } - - simdgroup_barrier(mem_flags::mem_threadgroup); - - FOR_UNROLL (short k = 0; k < 4; ++k) { - v8x8_t mv[2]; - o8x8_t lo[2]; - - simdgroup_load(mv[0], sv + 16*k + 0*8, 4*16, 0, false); - simdgroup_load(mv[1], sv + 16*k + 1*8, 4*16, 0, false); - simdgroup_load(lo[0], so + 8*(2*(ii + k) + 0), PV, 0, false); - simdgroup_load(lo[1], so + 8*(2*(ii + k) + 1), PV, 0, false); - - simdgroup_multiply_accumulate(lo[0], vs, mv[0], lo[0]); - simdgroup_multiply_accumulate(lo[1], vs, mv[1], lo[1]); - - simdgroup_store(lo[0], so + 8*(2*(ii + k) + 0), PV, 0, false); - simdgroup_store(lo[1], so + 8*(2*(ii + k) + 1), PV, 0, false); - } - } else { - if (ii + tx < DV16) { - v4x4_t tmp; - deq_v(pv4x4 + (ii + tx)/nl_v, (ii + tx)%nl_v, tmp); - sv4x4[4*ty + tx] = tmp; - } - - simdgroup_barrier(mem_flags::mem_threadgroup); - - for (short k = 0; k < 4 && ii + k < DV16; ++k) { - v8x8_t mv[2]; - o8x8_t lo[2]; - - simdgroup_load(mv[0], sv + 16*k + 0*8, 4*16, 0, false); - simdgroup_load(mv[1], sv + 16*k + 1*8, 4*16, 0, false); - simdgroup_load(lo[0], so + 8*(2*(ii + k) + 0), PV, 0, false); - simdgroup_load(lo[1], so + 8*(2*(ii + k) + 1), PV, 0, false); - - simdgroup_multiply_accumulate(lo[0], vs, mv[0], lo[0]); - simdgroup_multiply_accumulate(lo[1], vs, mv[1], lo[1]); - - simdgroup_store(lo[0], so + 8*(2*(ii + k) + 0), PV, 0, false); - simdgroup_store(lo[1], so + 8*(2*(ii + k) + 1), PV, 0, false); - } - } - } - } - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - } - - if (FC_flash_attn_ext_has_sinks) { - FOR_UNROLL (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - - const float m = M[jj]; - const float s = tiisg == 0 ? ((device const float *) sinks)[iq2] : -FLT_MAX/2; - - M[jj] = simd_max(max(M[jj], s)); - - const float ms = exp(m - M[jj]); - const float vs = exp(s - M[jj]); - - S[jj] = S[jj]*ms + simd_sum(vs); - - for (short i = tiisg; i < DV4; i += NW) { - so4[j*PV4 + i] *= ms; - } - } - } - } - - // store to global memory - for (short jj = 0; jj < NQ; ++jj) { - const short j = jj*NSG + sgitg; - if (iq1 + j >= args.ne01) { - break; - } - - device float4 * dst4 = (device float4 *) dst + ((uint64_t)iq3*args.ne2*args.ne1 + iq2 + (uint64_t)(iq1 + j)*args.ne1)*DV4; - - const float scale = S[jj] == 0.0 ? 0.0f : 1.0f/S[jj]; - - if (DV4 % NW == 0) { - FOR_UNROLL (short ii = 0; ii < DV4/NW; ++ii) { - const short i = ii*NW + tiisg; - - dst4[i] = (float4) so4[j*PV4 + i]*scale; - } - } else { - for (short i = tiisg; i < DV4; i += NW) { - dst4[i] = (float4) so4[j*PV4 + i]*scale; - } - } - } - -#undef NS10 -#undef NS20 -} - -template< - typename q_t, // query types in shared memory - typename q4_t, - typename q8x8_t, - typename k_t, // key types in shared memory - typename k4x4_t, - typename k8x8_t, - typename v_t, // value types in shared memory - typename v4x4_t, - typename v8x8_t, - typename qk_t, // Q*K types - typename qk8x8_t, - typename s_t, // soft-max types - typename s2_t, - typename s8x8_t, - typename o_t, // attention accumulation types - typename o4_t, - typename o8x8_t, - typename kd4x4_t, // key type in device memory - short nl_k, - void (*deq_k)(device const kd4x4_t *, short, thread k4x4_t &), - typename vd4x4_t, // value type in device memory - short nl_v, - void (*deq_v)(device const vd4x4_t *, short, thread v4x4_t &), - short DK, // K head size - short DV, // V head size - short Q = OP_FLASH_ATTN_EXT_NQPSG, // queries per threadgroup - short C = OP_FLASH_ATTN_EXT_NCPSG> // cache items per threadgroup -kernel void kernel_flash_attn_ext( - constant ggml_metal_kargs_flash_attn_ext & args, - device const char * q, - device const char * k, - device const char * v, - device const char * mask, - device const char * sinks, - device const char * pad, - device const char * blk, - device char * dst, - threadgroup half * shmem_f16 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { -#define FWD_TMPL q_t, q4_t, q8x8_t, k_t, k4x4_t, k8x8_t, v_t, v4x4_t, v8x8_t, qk_t, qk8x8_t, s_t, s2_t, s8x8_t, o_t, o4_t, o8x8_t, kd4x4_t, nl_k, deq_k, vd4x4_t, nl_v, deq_v, DK, DV, Q, C -#define FWD_ARGS args, q, k, v, mask, sinks, pad, blk, dst, shmem_f16, tgpig, tiisg, sgitg - switch (FC_flash_attn_ext_nsg) { - // note: disabled cases to reduce library load time - //case 1: kernel_flash_attn_ext_impl(FWD_ARGS); break; - //case 2: kernel_flash_attn_ext_impl(FWD_ARGS); break; - case 4: kernel_flash_attn_ext_impl(FWD_ARGS); break; - case 8: kernel_flash_attn_ext_impl(FWD_ARGS); break; - } -#undef FWD_TMPL -#undef FWD_ARGS -} - -// TODO: this is quite ugly. in the future these types will be hardcoded in the kernel, but for now keep them as -// template to be able to explore different combinations -// -#define FA_TYPES \ - half, half4, simdgroup_half8x8, \ - half, half4x4, simdgroup_half8x8, \ - half, half4x4, simdgroup_half8x8, \ - float, simdgroup_float8x8, \ - float, float2, simdgroup_float8x8, \ - float, float4, simdgroup_float8x8 - //half, half4, simdgroup_half8x8 - -#define FA_TYPES_BF \ - bfloat, bfloat4, simdgroup_bfloat8x8, \ - bfloat, bfloat4x4, simdgroup_bfloat8x8, \ - bfloat, bfloat4x4, simdgroup_bfloat8x8, \ - float, simdgroup_float8x8, \ - float, float2, simdgroup_float8x8, \ - half, half4, simdgroup_half8x8 - //float, float4, simdgroup_float8x8 - -#define FA_TYPES_F32 \ - half, half4, simdgroup_half8x8, \ - float, float4x4, simdgroup_float8x8, \ - float, float4x4, simdgroup_float8x8, \ - float, simdgroup_float8x8, \ - float, float2, simdgroup_float8x8, \ - float, float4, simdgroup_float8x8 - //half, half4, simdgroup_half8x8 - -typedef decltype(kernel_flash_attn_ext) flash_attn_ext_t; - -template [[host_name("kernel_flash_attn_ext_f32_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f32_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; - -template [[host_name("kernel_flash_attn_ext_f16_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_f16_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; - -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_bf16_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_bf16_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -#endif - -template [[host_name("kernel_flash_attn_ext_q4_0_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_0_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; - -template [[host_name("kernel_flash_attn_ext_q4_1_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q4_1_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; - -template [[host_name("kernel_flash_attn_ext_q5_0_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_0_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; - -template [[host_name("kernel_flash_attn_ext_q5_1_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q5_1_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; - -template [[host_name("kernel_flash_attn_ext_q8_0_dk32_dv32" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk40_dv40" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk48_dv48" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk64_dv64" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk72_dv72" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk80_dv80" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk96_dv96" )]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk112_dv112")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk128_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk192_dv192")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk192_dv128")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk256_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk320_dv256")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk512_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; -template [[host_name("kernel_flash_attn_ext_q8_0_dk576_dv512")]] kernel flash_attn_ext_t kernel_flash_attn_ext; - -#undef FA_TYPES -#undef FA_TYPES_BF -#undef FA_TYPES_F32 - -constant bool FC_flash_attn_ext_vec_has_mask [[function_constant(FC_FLASH_ATTN_EXT_VEC + 0)]]; -constant bool FC_flash_attn_ext_vec_has_sinks [[function_constant(FC_FLASH_ATTN_EXT_VEC + 1)]]; -constant bool FC_flash_attn_ext_vec_has_bias [[function_constant(FC_FLASH_ATTN_EXT_VEC + 2)]]; -constant bool FC_flash_attn_ext_vec_has_scap [[function_constant(FC_FLASH_ATTN_EXT_VEC + 3)]]; -constant bool FC_flash_attn_ext_vec_has_kvpad [[function_constant(FC_FLASH_ATTN_EXT_VEC + 4)]]; - -//constant float FC_flash_attn_ext_vec_scale [[function_constant(FC_FLASH_ATTN_EXT_VEC + 10)]]; -//constant float FC_flash_attn_ext_vec_max_bias [[function_constant(FC_FLASH_ATTN_EXT_VEC + 11)]]; -//constant float FC_flash_attn_ext_vec_logit_softcap [[function_constant(FC_FLASH_ATTN_EXT_VEC + 12)]]; - -constant int32_t FC_flash_attn_ext_vec_ns10 [[function_constant(FC_FLASH_ATTN_EXT_VEC + 20)]]; -constant int32_t FC_flash_attn_ext_vec_ns20 [[function_constant(FC_FLASH_ATTN_EXT_VEC + 21)]]; -constant int32_t FC_flash_attn_ext_vec_nsg [[function_constant(FC_FLASH_ATTN_EXT_VEC + 22)]]; -constant int32_t FC_flash_attn_ext_vec_nwg [[function_constant(FC_FLASH_ATTN_EXT_VEC + 23)]]; - -template< - typename q4_t, // query types in shared memory - typename k4_t, // key types in shared memory - typename v4_t, // value types in shared memory - typename qk_t, // Q*K types - typename s_t, // soft-max types - typename s4_t, - typename o4_t, // attention accumulation types - typename kd4_t, // key type in device memory - short nl_k, - void (*deq_k_t4)(device const kd4_t *, short, thread k4_t &), - typename vd4_t, // value type in device memory - short nl_v, - void (*deq_v_t4)(device const vd4_t *, short, thread v4_t &), - short DK, // K head size - short DV, // V head size - short NE = 4, // head elements per thread - short Q = OP_FLASH_ATTN_EXT_VEC_NQPSG, // queries per threadgroup - short C = OP_FLASH_ATTN_EXT_VEC_NCPSG> // cache items per threadgroup -kernel void kernel_flash_attn_ext_vec( - constant ggml_metal_kargs_flash_attn_ext_vec & args, - device const char * q, - device const char * k, - device const char * v, - device const char * mask, - device const char * sinks, - device const char * pad, - device char * dst, - threadgroup half * shmem_f16 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - static_assert(DK % 32 == 0, "DK must be divisible by 32"); - static_assert(DV % 32 == 0, "DV must be divisible by 32"); - -#define NWG (FC_flash_attn_ext_vec_nwg) -#define NSG (FC_flash_attn_ext_vec_nsg) - -#define NS10 (FC_flash_attn_ext_vec_ns10) -#define NS20 (FC_flash_attn_ext_vec_ns20) - - const short iwg = tgpig[2]%NWG; - - const ushort iq3 = tgpig[2]/NWG; - const ushort iq2 = tgpig[1]; - const ushort iq1 = tgpig[0]; - - constexpr short DK4 = DK/4; - constexpr short DV4 = DV/4; - - constexpr short PK = PAD2(DK, 128); - constexpr short PK4 = PK/4; - - constexpr short PV = PAD2(DV, 128); - constexpr short PV4 = PV/4; - - constexpr short NW = N_SIMDWIDTH; - constexpr short NL = NW/NE; // note: this can be adjusted to support different head sizes and simdgroup work loads - constexpr short SH = 4*C; // shared memory per simdgroup - - static_assert(DK4 % NL == 0, "DK4 must be divisible by NL"); - static_assert(DV4 % NL == 0, "DV4 must be divisible by NL"); - - //const short T = PK + NSG*SH; // shared memory size per query in (half) - - //threadgroup q_t * sq = (threadgroup q_t *) (shmem_f16 + 0*PK); // holds the query data - threadgroup q4_t * sq4 = (threadgroup q4_t *) (shmem_f16 + 0*PK); // same as above but in q4_t - threadgroup s_t * ss = (threadgroup s_t *) (shmem_f16 + sgitg*SH + NSG*PK); // scratch buffer for attention - threadgroup s4_t * ss4 = (threadgroup s4_t *) (shmem_f16 + sgitg*SH + NSG*PK); // same as above but in s4_t - threadgroup half * sm = (threadgroup half *) (shmem_f16 + sgitg*SH + 2*C + NSG*PK); // scratch buffer for mask - threadgroup o4_t * so4 = (threadgroup o4_t *) (shmem_f16 + 2*sgitg*PV + NSG*PK + NSG*SH); // scratch buffer for the results - - // store the result for all queries in shared memory (the O matrix from the paper) - so4 += tiisg; - - { - q += iq1*args.nb01 + iq2*args.nb02 + iq3*args.nb03; - - const short ikv2 = iq2/(args.ne02/args.ne_12_2); - const short ikv3 = iq3/(args.ne03/args.ne_12_3); - - k += ikv2*args.nb12 + ikv3*args.nb13; - v += ikv2*args.nb22 + ikv3*args.nb23; - } - - // load heads from Q to shared memory - device const float4 * q4 = (device const float4 *) ((device const char *) q); - - if (iq1 < args.ne01) { - for (short i = tiisg; i < PK4; i += NW) { - if (i < DK4) { - sq4[i] = (q4_t) q4[i]; - } else { - sq4[i] = (q4_t) 0.0f; - } - } - } - - // zero out so - for (short i = 0; i < DV4/NL; ++i) { - so4[i*NL] = (o4_t) 0.0f; - } - - // zero out shared memory SH - for (short i = tiisg; i < SH/4; i += NW) { - ss4[i] = (s4_t) 0.0f; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - { - float S = 0.0f; - float M = -FLT_MAX/2; - - // thread indices inside the simdgroup - const short tx = tiisg%NL; - const short ty = tiisg/NL; - - // pointer to the mask - device const half * pm = (device const half *) (mask + iq1*args.nb31 + (iq2%args.ne32)*args.nb32 + (iq3%args.ne33)*args.nb33); - - float slope = 1.0f; - - // ALiBi - if (FC_flash_attn_ext_vec_has_bias) { - const short h = iq2; - - const float base = h < args.n_head_log2 ? args.m0 : args.m1; - const short exph = h < args.n_head_log2 ? h + 1 : 2*(h - args.n_head_log2) + 1; - - slope = pow(base, exph); - } - - // loop over the KV cache - // each simdgroup handles blocks of Q rows and C columns - for (int ic0 = iwg*NSG + sgitg; ; ic0 += NWG*NSG) { - int ic = ic0*C; - if (ic >= args.ne11) { - break; - } - - // the last partial chunk uses the pad buffer as source - if (FC_flash_attn_ext_vec_has_kvpad && ic + C > args.ne11) { - k = pad; - v = k + args.nb11*C*args.ne_12_2*args.ne_12_3; - mask = v + args.nb21*C*args.ne_12_2*args.ne_12_3; - - const short ikv2 = iq2/(args.ne02/args.ne_12_2); - const short ikv3 = iq3/(args.ne03/args.ne_12_3); - - k += (ikv2 + ikv3*args.ne_12_2)*args.nb11*C; - v += (ikv2 + ikv3*args.ne_12_2)*args.nb21*C; - - if (!FC_flash_attn_ext_vec_has_mask) { - if (ic + tiisg >= args.ne11) { - sm[tiisg] = -MAXHALF; - } - } else { - pm = (device const half *) (mask) + - iq1*C + - (iq2%args.ne32)*(C*args.ne31) + - (iq3%args.ne33)*(C*args.ne31*args.ne32); - } - - ic = 0; - } - - if (FC_flash_attn_ext_vec_has_mask) { - sm[tiisg] = pm[ic + tiisg]; - } - - // skip -INF blocks - if (simd_max(sm[tiisg]) <= -MAXHALF) { - continue; - } - - // Q*K^T - { - device const k4_t * pk4 = (device const k4_t *) (k + ic*args.nb11); - threadgroup const q4_t * pq4 = sq4; - - pk4 += ty*NS10/4 + tx; - pq4 += tx; - - qk_t mqk[C/NE] = { [ 0 ... C/NE - 1] = 0.0f }; - - // each simdgroup processes 1 query and NE (NW/NL) cache elements - FOR_UNROLL (short cc = 0; cc < C/NE; ++cc) { - if (is_same::value) { - FOR_UNROLL (short ii = 0; ii < DK4/NL; ++ii) { - mqk[cc] += dot((float4) pk4[cc*NE*NS10/4 + ii*NL], (float4) pq4[ii*NL]); - } - } else { - device const kd4_t * pk = (device const kd4_t *) (k + ((ic + NE*cc + ty)*args.nb11)); - - k4_t mk; - - FOR_UNROLL (short ii = 0; ii < DK4/NL; ++ii) { - const short i = ii*NL + tx; - - deq_k_t4(pk + i/nl_k, i%nl_k, mk); - - mqk[cc] += dot((float4) mk, (float4) sq4[i]); - } - } - - if (NE == 1) { - mqk[cc] = simd_sum(mqk[cc]); - } else { - // simdgroup reduce (NE = 4) - // [ 0 .. 7] -> [ 0] - // [ 8 .. 15] -> [ 8] - // [16 .. 23] -> [16] - // [24 .. 31] -> [24] - if (NE <= 1) { - mqk[cc] += simd_shuffle_down(mqk[cc], 16); - } - if (NE <= 2) { - mqk[cc] += simd_shuffle_down(mqk[cc], 8); - } - if (NE <= 4) { - mqk[cc] += simd_shuffle_down(mqk[cc], 4); - } - if (NE <= 8) { - mqk[cc] += simd_shuffle_down(mqk[cc], 2); - } - if (NE <= 16) { - mqk[cc] += simd_shuffle_down(mqk[cc], 1); - } - - // broadcast - mqk[cc] = simd_shuffle(mqk[cc], NL*ty); - } - } - - if (FC_flash_attn_ext_vec_has_mask && - !FC_flash_attn_ext_vec_has_scap && - !FC_flash_attn_ext_vec_has_bias) { - ss[NE*tx + ty] = fma(mqk[tx], args.scale, (qk_t) sm[NE*tx + ty]); - } else { - mqk[tx] *= args.scale; - - if (FC_flash_attn_ext_vec_has_scap) { - mqk[tx] = args.logit_softcap*precise::tanh(mqk[tx]); - } - - if (FC_flash_attn_ext_vec_has_bias) { - mqk[tx] += (qk_t) sm[NE*tx + ty]*slope; - } else { - mqk[tx] += (qk_t) sm[NE*tx + ty]; - } - - ss[NE*tx + ty] = mqk[tx]; - } - } - - simdgroup_barrier(mem_flags::mem_threadgroup); - - // online softmax - { - const float m = M; - const float s = ss[tiisg]; - - M = simd_max(max(M, s)); - - const float ms = exp(m - M); - const float vs = exp(s - M); - - S = S*ms + simd_sum(vs); - - // the P matrix from the paper (Q rows, C columns) - ss[tiisg] = vs; - - // O = diag(ms)*O - if ((DV4/NL % NW == 0) || ty == 0) { - FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { - so4[ii*NL] *= ms; - } - } - } - - simdgroup_barrier(mem_flags::mem_threadgroup); - - // O = O + (Q*K^T)*V - { - o4_t lo[DV4/NL]; - FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { - lo[ii] = 0.0f; - } - - if (is_same::value) { - device const v4_t * pv4 = (device const v4_t *) (v + ic*args.nb21); - - pv4 += ty*NS20/4 + tx; - - const auto sst = ss + ty; - - FOR_UNROLL (short cc = 0; cc < C/NE; ++cc) { - FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { - lo[ii] += o4_t(float4(pv4[cc*NE*NS20/4 + ii*NL])*float4(sst[cc*NE])); - } - } - } else { - FOR_UNROLL (short cc = 0; cc < C/NE; ++cc) { - device const vd4_t * pv4 = (device const vd4_t *) (v + ((ic + NE*cc + ty)*args.nb21)); - - FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { - const short i = ii*NL + tx; - - v4_t mv; - deq_v_t4(pv4 + i/nl_v, i%nl_v, mv); - - lo[ii] += o4_t(float4(mv)*float4(ss[NE*cc + ty])); - } - } - } - - FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { - if (NE > 1) { - lo[ii][0] += simd_shuffle_down(lo[ii][0], 16); - lo[ii][1] += simd_shuffle_down(lo[ii][1], 16); - lo[ii][2] += simd_shuffle_down(lo[ii][2], 16); - lo[ii][3] += simd_shuffle_down(lo[ii][3], 16); - } - - if (NE > 2) { - lo[ii][0] += simd_shuffle_down(lo[ii][0], 8); - lo[ii][1] += simd_shuffle_down(lo[ii][1], 8); - lo[ii][2] += simd_shuffle_down(lo[ii][2], 8); - lo[ii][3] += simd_shuffle_down(lo[ii][3], 8); - } - - if (NE > 4) { - lo[ii][0] += simd_shuffle_down(lo[ii][0], 4); - lo[ii][1] += simd_shuffle_down(lo[ii][1], 4); - lo[ii][2] += simd_shuffle_down(lo[ii][2], 4); - lo[ii][3] += simd_shuffle_down(lo[ii][3], 4); - } - - if (NE > 8) { - lo[ii][0] += simd_shuffle_down(lo[ii][0], 2); - lo[ii][1] += simd_shuffle_down(lo[ii][1], 2); - lo[ii][2] += simd_shuffle_down(lo[ii][2], 2); - lo[ii][3] += simd_shuffle_down(lo[ii][3], 2); - } - - if (NE > 16) { - lo[ii][0] += simd_shuffle_down(lo[ii][0], 1); - lo[ii][1] += simd_shuffle_down(lo[ii][1], 1); - lo[ii][2] += simd_shuffle_down(lo[ii][2], 1); - lo[ii][3] += simd_shuffle_down(lo[ii][3], 1); - } - } - - if ((DV4/NL % NW == 0) || ty == 0) { - FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { - so4[ii*NL] += lo[ii]; - } - } - } - } - - if (FC_flash_attn_ext_vec_has_sinks && sgitg == 0 && iwg == 0) { - const float m = M; - const float s = tiisg == 0 ? ((device const float *) sinks)[iq2] : -FLT_MAX/2; - - M = simd_max(max(M, s)); - - const float ms = exp(m - M); - const float vs = exp(s - M); - - S = S*ms + simd_sum(vs); - - if ((DV4/NL % NW == 0) || ty == 0) { - FOR_UNROLL (short ii = 0; ii < DV4/NL; ++ii) { - so4[ii*NL] *= ms; - } - } - } - - // these are needed for reducing the results from the simdgroups (reuse the ss buffer) - if (tiisg == 0) { - ss[0] = (s_t) S; - ss[1] = (s_t) M; - } - } - - so4 -= tiisg; - - threadgroup_barrier(mem_flags::mem_threadgroup); - - // parallel reduce - for (short r = NSG/2; r > 0; r >>= 1) { - if (sgitg < r) { - const float S0 = ss[ 0]; - const float S1 = ss[r*(SH/2) + 0]; - - const float M0 = ss[ 1]; - const float M1 = ss[r*(SH/2) + 1]; - - const float M = max(M0, M1); - - const float ms0 = exp(M0 - M); - const float ms1 = exp(M1 - M); - - const float S = S0*ms0 + S1*ms1; - - if (tiisg == 0) { - ss[0] = S; - ss[1] = M; - } - - // O_0 = diag(ms0)*O_0 + diag(ms1)*O_1 - for (short i = tiisg; i < DV4; i += NW) { - so4[i] = so4[i]*ms0 + so4[i + r*PV4]*ms1; - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - } - - // final rescale with 1/S and store to global memory - if (sgitg == 0) { - const int64_t nrows = args.ne3*args.ne2*args.ne1; - const int64_t rid = iq3*args.ne2*args.ne1 + iq2 + iq1*args.ne1; - - device float4 * dst4 = (device float4 *) dst; - device float * dst1 = (device float *) dst + nrows*DV*NWG; // the S and M are stored after the results - - const float S = NWG == 1 ? (ss[0] == 0.0f ? 0.0f : 1.0f/ss[0]) : 1.0f; - - // interleave the workgroup data - for (short i = tiisg; i < DV4; i += NW) { - dst4[rid*DV4*NWG + NWG*i + iwg] = (float4) so4[i]*S; - } - - // store S and M - if (NWG > 1) { - if (tiisg == 0) { - dst1[rid*(2*NWG) + 2*iwg + 0] = ss[0]; - dst1[rid*(2*NWG) + 2*iwg + 1] = ss[1]; - } - } - } - -#undef NWG -#undef NSG -#undef NS10 -#undef NS20 -} - -// note: I think the s_t can be half instead of float, because the Q*K scaling is done before storing to shared mem -// in the other (non-vec) kernel, we need s_t to also be float because we scale during the soft_max -// -#define FA_TYPES \ - half4, \ - half4, \ - half4, \ - float, \ - float, float4, \ - float4 - -#define FA_TYPES_F32 \ - half4, \ - float4, \ - float4, \ - float, \ - float, float4, \ - float4 - -typedef decltype(kernel_flash_attn_ext_vec) flash_attn_ext_vec_t; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk32_dv32")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk64_dv64")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk96_dv96")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk128_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk192_dv192")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk192_dv128")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk256_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk320_dv256")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk512_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -template [[host_name("kernel_flash_attn_ext_vec_f32_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_f16_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_flash_attn_ext_vec_bf16_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -#endif -template [[host_name("kernel_flash_attn_ext_vec_q4_0_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q4_1_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_0_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q5_1_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; -template [[host_name("kernel_flash_attn_ext_vec_q8_0_dk576_dv512")]] kernel flash_attn_ext_vec_t kernel_flash_attn_ext_vec; - -#undef FA_TYPES -#undef FA_TYPES_F32 - -constant int32_t FC_flash_attn_ext_vec_reduce_DV [[function_constant(FC_FLASH_ATTN_EXT_VEC_REDUCE + 0)]]; -constant int32_t FC_flash_attn_ext_vec_reduce_NWG [[function_constant(FC_FLASH_ATTN_EXT_VEC_REDUCE + 1)]]; - -kernel void kernel_flash_attn_ext_vec_reduce( - constant ggml_metal_kargs_flash_attn_ext_vec_reduce & args, - device const char * htmp, - device char * dst, - uint tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { -#define NWG (FC_flash_attn_ext_vec_reduce_NWG) -#define DV (FC_flash_attn_ext_vec_reduce_DV) - - const uint64_t rid = tgpig; - - const short iwg = tiisg; - - device const float * ss = (device const float *) htmp + (uint64_t)args.nrows*DV*NWG; - - float S = ss[rid*(2*NWG) + 2*iwg + 0]; - float M = ss[rid*(2*NWG) + 2*iwg + 1]; - - const float m = simd_max(M); - const float ms = exp(M - m); - - S = simd_sum(S*ms); - S = S == 0.0f ? 0.0f : 1.0f/S; - - const short DV4 = DV/4; - - device const float4 * htmp4 = (device const float4 *) htmp + rid*DV4*NWG; - device float4 * dst4 = (device float4 *) dst + rid*DV4; - - for (short i = sgitg; i < DV4; i += NWG) { - const float4 v = simd_sum(htmp4[i*NWG + iwg]*ms); - - if (iwg == 0) { - dst4[i] = v*S; - } - } - -#undef NWG -#undef DV -} - -template -kernel void kernel_cpy_t_t( - constant ggml_metal_kargs_cpy & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int i03 = tgpig[2]; - const int i02 = tgpig[1]; - const int i01 = ntg[1] == 1 ? tgpig[0]%args.ne01 : tgpig[0]*ntg[1] + tiitg/ntg[0]; - const int iw0 = ntg[1] == 1 ? tgpig[0]/args.ne01 : 0; - - const int64_t n = i03*args.ne02*args.ne01*args.ne00 + i02*args.ne01*args.ne00 + i01*args.ne00; - - const int64_t i3 = n/(args.ne2*args.ne1*args.ne0); - const int64_t i2 = (n - i3*args.ne2*args.ne1*args.ne0)/(args.ne1*args.ne0); - const int64_t i1 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0)/args.ne0; - const int64_t i0 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0 - i1*args.ne0); - - device T1 * dst_data = (device T1 *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - for (int64_t i00 = iw0*ntg[0] + tiitg%ntg[0]; i00 < args.ne00; ) { - device const T0 * src = (device T0 *)(src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + i00*args.nb00); - dst_data[i00] = (T1) src[0]; - break; - } -} - -typedef decltype(kernel_cpy_t_t) kernel_cpy_t; + const int64_t i1 = idx; + const int64_t i2 = i12; -template -kernel void kernel_cpy_contig_t_t( - constant ggml_metal_kargs_cpy & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int64_t i = (int64_t)tgpig.x*ntg.x + tpitg.x; + device const char * src0_cur = src0s + i02*args.nb02; + device const char * src1_cur = src1 + i11*args.nb11 + i12*args.nb12; - if (i >= args.nk0) { - return; - } + device char * dst_cur = dst + (i1*args.ne0 + i2*args.ne1*args.ne0)*sizeof(float); - device const T0 * src_data = (device const T0 *) src0; - device T1 * dst_data = (device T1 *) dst; + ggml_metal_kargs_mul_mv args0 = { + /*.ne00 =*/ args.ne00, + /*.ne01 =*/ args.ne01, + /*.ne02 =*/ 1, // args.ne02, + /*.nb00 =*/ args.nb00, + /*.nb01 =*/ args.nb01, + /*.nb02 =*/ args.nb02, + /*.nb03 =*/ args.nb02, // args.ne02 == 1 + /*.ne10 =*/ args.ne10, + /*.ne11 =*/ 1, // args.ne11, + /*.ne12 =*/ 1, // args.ne12, + /*.nb10 =*/ args.nb10, + /*.nb11 =*/ args.nb11, + /*.nb12 =*/ args.nb12, + /*.nb13 =*/ args.nb12, // ne12 == 1 + /*.ne0 =*/ args.ne0, + /*.ne1 =*/ 1, // args.ne1, + /*.nr0 =*/ args.nr0, + /*.r2 =*/ 1, + /*.r3 =*/ 1, + }; - dst_data[i] = (T1) src_data[i]; + disp_fn( + args0, + /* src0 */ src0_cur, + /* src1 */ src1_cur, + /* dst */ dst_cur, + shmem, + tgpig, + tiitg, + tiisg, + sgitg); } -typedef decltype(kernel_cpy_contig_t_t) kernel_cpy_contig_t; +typedef decltype(kernel_mul_mv_id>>) kernel_mul_mv_id_t; -template [[host_name("kernel_cpy_contig_f32_f32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; -template [[host_name("kernel_cpy_contig_f32_f16")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; -template [[host_name("kernel_cpy_contig_f32_i32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; -template [[host_name("kernel_cpy_contig_i32_f32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; -template [[host_name("kernel_cpy_contig_i32_i32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +typedef decltype(kernel_mul_mv_id>>) kernel_mul_mv_id_4_t; + +template [[host_name("kernel_mul_mv_id_f32_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_f16_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; #if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_cpy_contig_f32_bf16")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_mul_mv_id_bf16_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; #endif -template [[host_name("kernel_cpy_contig_f16_f32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; -template [[host_name("kernel_cpy_contig_f16_f16")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_mul_mv_id_f32_f32_4")]] kernel kernel_mul_mv_id_4_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_f16_f32_4")]] kernel kernel_mul_mv_id_4_t kernel_mul_mv_id>>; #if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_cpy_contig_bf16_f32")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; -template [[host_name("kernel_cpy_contig_bf16_bf16")]] kernel kernel_cpy_contig_t kernel_cpy_contig_t_t; +template [[host_name("kernel_mul_mv_id_bf16_f32_4")]] kernel kernel_mul_mv_id_4_t kernel_mul_mv_id>>; #endif -template -kernel void kernel_cpy_2d_t( - constant ggml_metal_kargs_cpy & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int64_t i0 = (int64_t)tgpig.x*ntg.x + tpitg.x; - const int64_t i1 = (int64_t)tgpig.y*ntg.y + tpitg.y; - const int64_t i23 = tgpig.z; - const int64_t i2 = i23 % args.ne02; - const int64_t i3 = i23 / args.ne02; +template [[host_name("kernel_mul_mv_id_q8_0_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; - if (i0 >= args.ne00 || i1 >= args.ne01) { +template [[host_name("kernel_mul_mv_id_q1_0_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_q4_0_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_q4_1_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_q5_0_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_q5_1_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; + +template [[host_name("kernel_mul_mv_id_mxfp4_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; + +template [[host_name("kernel_mul_mv_id_q2_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_q3_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_q4_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_q5_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_q6_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq1_s_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq1_m_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq2_xxs_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq2_xs_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq3_xxs_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq3_s_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq2_s_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq4_nl_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; +template [[host_name("kernel_mul_mv_id_iq4_xs_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; + +kernel void kernel_pool_2d_max_f32( + constant ggml_metal_kargs_pool_2d & args, + device const float * src0, + device float * dst, + uint gid[[thread_position_in_grid]]) { + + if (gid >= args.np) { return; } - device const T * src_data = (device const T *) (src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); - device T * dst_data = (device T *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + const int idx = gid; + const int I_HW = args.IH * args.IW; + const int O_HW = args.OH * args.OW; + const int nc = idx / O_HW; + const int cur_oh = idx % O_HW / args.OW; + const int cur_ow = idx % O_HW % args.OW; - dst_data[0] = src_data[0]; -} + device const float * i_ptr = src0 + nc * I_HW; + device float * o_ptr = dst + nc * O_HW; -typedef decltype(kernel_cpy_2d_t) kernel_cpy_2d_tmpl; + const int start_h = cur_oh * args.s1 - args.p1; + const int bh = MAX(0, start_h); + const int eh = MIN(args.IH, start_h + args.k1); + const int start_w = cur_ow * args.s0 - args.p0; + const int bw = MAX(0, start_w); + const int ew = MIN(args.IW, start_w + args.k0); -template [[host_name("kernel_cpy_2d_f32")]] kernel kernel_cpy_2d_tmpl kernel_cpy_2d_t; -template [[host_name("kernel_cpy_2d_f16")]] kernel kernel_cpy_2d_tmpl kernel_cpy_2d_t; -template [[host_name("kernel_cpy_2d_i32")]] kernel kernel_cpy_2d_tmpl kernel_cpy_2d_t; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_cpy_2d_bf16")]] kernel kernel_cpy_2d_tmpl kernel_cpy_2d_t; -#endif + float res = -INFINITY; -kernel void kernel_cpy_row_f32( - constant ggml_metal_kargs_cpy & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]]) { - const int64_t i0 = ((int64_t)tgpig.x*16 + tpitg.x)*4; + for (int i = bh; i < eh; i += 1) { + for (int j = bw; j < ew; j += 1) { + res = MAX(res, i_ptr[i * args.IW + j]); + } + } - if (i0 >= args.ne00) { + o_ptr[cur_oh * args.OW + cur_ow] = res; +} + +kernel void kernel_pool_2d_avg_f32( + constant ggml_metal_kargs_pool_2d & args, + device const float * src0, + device float * dst, + uint gid[[thread_position_in_grid]]) { + + if (gid >= args.np) { return; } - for (int mat = 0; mat < 4; ++mat) { - const int64_t i23 = (int64_t)tgpig.z*4 + mat; - if (i23 >= args.ne02*args.ne03) { - continue; - } - const int64_t i2 = i23 % args.ne02; - const int64_t i3 = i23 / args.ne02; + const int idx = gid; + const int I_HW = args.IH * args.IW; + const int O_HW = args.OH * args.OW; + const int nc = idx / O_HW; + const int cur_oh = idx % O_HW / args.OW; + const int cur_ow = idx % O_HW % args.OW; - for (int row = 0; row < 2; ++row) { - const int64_t i1 = (int64_t)tgpig.y*16 + tpitg.y + 8*row; - if (i1 >= args.ne01) { - continue; - } + device const float * i_ptr = src0 + nc * I_HW; + device float * o_ptr = dst + nc * O_HW; - device const char * src_row = src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00; - device char * dst_row = dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0; + const int start_h = cur_oh * args.s1 - args.p1; + const int bh = MAX(0, start_h); + const int eh = MIN(args.IH, start_h + args.k1); + const int start_w = cur_ow * args.s0 - args.p0; + const int bw = MAX(0, start_w); + const int ew = MIN(args.IW, start_w + args.k0); + // const float scale = 1. / ((eh - bh) * (ew - bw)); + const float scale = 1. / (args.k0 * args.k1); - if (i0 + 3 < args.ne00) { - device const float4 * src4 = (device const float4 *) src_row; - device float4 * dst4 = (device float4 *) dst_row; - dst4[0] = src4[0]; - } else { - device const float * src1 = (device const float *) src_row; - device float * dst1 = (device float *) dst_row; - for (int64_t i = i0; i < args.ne00; ++i) { - dst1[i - i0] = src1[i - i0]; - } - } + float res = 0; + + for (int i = bh; i < eh; i += 1) { + for (int j = bw; j < ew; j += 1) { + float cur = i_ptr[i * args.IW + j]; + res += cur * scale; } } + + o_ptr[cur_oh * args.OW + cur_ow] = res; } -kernel void kernel_cpy_transpose_f32( - constant ggml_metal_kargs_cpy & args, - device const char * src0, - device char * dst, - threadgroup float * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]]) { - constexpr int tile_dim = 32; - constexpr int block_rows = 8; - constexpr int tile_stride = tile_dim + 1; - const int64_t tile_col = tgpig.x; - const int64_t tile_row = tgpig.y; - const int64_t i23 = tgpig.z; - const int64_t i2 = i23 % args.ne02; - const int64_t i3 = i23 / args.ne02; - const int64_t tid_col = tpitg.x; - const int64_t tid_row = tpitg.y; +kernel void kernel_pool_1d_max_f32( + constant ggml_metal_kargs_pool_1d & args, + device const float * src, + device float * dst, + uint gid [[thread_position_in_grid]] +) { - for (int y = 0; y < 4; ++y) { - const int64_t i0 = tile_col*tile_dim + tid_row + block_rows*y; - const int64_t i1 = tile_row*tile_dim + tid_col; - if (i0 < args.ne00 && i1 < args.ne01) { - device const float * src = (device const float *) (src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); - shmem[(tid_row + block_rows*y)*tile_stride + tid_col] = src[0]; + if (gid >= args.np) { + return; + } + + const int ow = (int)gid % args.OW; + const int row = (int)gid / args.OW; + + const int base = ow * args.s0 - args.p0; + + float acc = -INFINITY; + + const int src_off = row * args.IW; + const int dst_off = row * args.OW; + + for (int ki = 0; ki < args.k0; ++ki) { + int j = base + ki; + if (j < 0 || j >= args.IW){ + continue; } + float v = src[src_off + j]; + acc = max(acc, v); } - threadgroup_barrier(mem_flags::mem_threadgroup); + dst[dst_off + ow] = acc; +} - for (int y = 0; y < 4; ++y) { - const int64_t i0 = tile_col*tile_dim + tid_col; - const int64_t i1 = tile_row*tile_dim + tid_row + block_rows*y; - if (i0 < args.ne0 && i1 < args.ne1) { - device float * dst_data = (device float *) (dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - dst_data[0] = shmem[tid_col*tile_stride + tid_row + block_rows*y]; +kernel void kernel_pool_1d_avg_f32( + constant ggml_metal_kargs_pool_1d & args, + device const float * src, + device float * dst, + uint gid [[thread_position_in_grid]] +) { + + if (gid >= args.np) { + return; + } + + const int ow = (int)gid % args.OW; + const int row = (int)gid / args.OW; + + const int base = ow * args.s0 - args.p0; + + float acc = 0.0f; + int cnt = 0; + + const int src_off = row * args.IW; + const int dst_off = row * args.OW; + + for (int ki = 0; ki < args.k0; ++ki) { + const int j = base + ki; + if (j < 0 || j >= args.IW) { + continue; } + acc += src[src_off + j]; + cnt += 1; } + + dst[dst_off + ow] = (cnt > 0) ? (acc / (float)cnt) : 0.0f; } -template [[host_name("kernel_cpy_f32_f32")]] kernel kernel_cpy_t kernel_cpy_t_t; -template [[host_name("kernel_cpy_f32_f16")]] kernel kernel_cpy_t kernel_cpy_t_t; -template [[host_name("kernel_cpy_f32_i32")]] kernel kernel_cpy_t kernel_cpy_t_t; -template [[host_name("kernel_cpy_i32_f32")]] kernel kernel_cpy_t kernel_cpy_t_t; -template [[host_name("kernel_cpy_i32_i32")]] kernel kernel_cpy_t kernel_cpy_t_t; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_cpy_f32_bf16")]] kernel kernel_cpy_t kernel_cpy_t_t; -#endif -template [[host_name("kernel_cpy_f16_f32")]] kernel kernel_cpy_t kernel_cpy_t_t; -template [[host_name("kernel_cpy_f16_f16")]] kernel kernel_cpy_t kernel_cpy_t_t; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_cpy_bf16_f32")]] kernel kernel_cpy_t kernel_cpy_t_t; -template [[host_name("kernel_cpy_bf16_bf16")]] kernel kernel_cpy_t kernel_cpy_t_t; -#endif - -template -kernel void kernel_cpy_f32_q( - constant ggml_metal_kargs_cpy & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int i03 = tgpig[2]; - const int i02 = tgpig[1]; - const int i01 = ntg[1] == 1 ? tgpig[0]%args.ne01 : tgpig[0]*ntg[1] + tiitg/ntg[0]; - const int iw0 = ntg[1] == 1 ? tgpig[0]/args.ne01 : 0; - - const int64_t n = i03*args.ne02*args.ne01*args.ne00 + i02*args.ne01*args.ne00 + i01*args.ne00; - - const int64_t i3 = n / (args.ne2*args.ne1*args.ne0); - const int64_t i2 = (n - i3*args.ne2*args.ne1*args.ne0) / (args.ne1*args.ne0); - const int64_t i1 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0) / args.ne0; - const int64_t i0 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0 - i1*args.ne0)/QK; - - device block_q * dst_data = (device block_q *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - for (int64_t i00 = iw0*ntg[0] + tiitg%ntg[0]; i00 < args.nk0; ) { - device const float * src = (device const float *)(src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01 + (i00*QK)*args.nb00); - - quantize_func(src, dst_data[i00]); - - break; - } -} - -typedef decltype(kernel_cpy_f32_q) cpy_f_q_t; - -template [[host_name("kernel_cpy_f32_q8_0")]] kernel cpy_f_q_t kernel_cpy_f32_q; -template [[host_name("kernel_cpy_f32_q1_0")]] kernel cpy_f_q_t kernel_cpy_f32_q; -template [[host_name("kernel_cpy_f32_q4_0")]] kernel cpy_f_q_t kernel_cpy_f32_q; -template [[host_name("kernel_cpy_f32_q4_1")]] kernel cpy_f_q_t kernel_cpy_f32_q; -template [[host_name("kernel_cpy_f32_q5_0")]] kernel cpy_f_q_t kernel_cpy_f32_q; -template [[host_name("kernel_cpy_f32_q5_1")]] kernel cpy_f_q_t kernel_cpy_f32_q; -template [[host_name("kernel_cpy_f32_iq4_nl")]] kernel cpy_f_q_t kernel_cpy_f32_q; - -template -kernel void kernel_cpy_q_f32( - constant ggml_metal_kargs_cpy & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const int i03 = tgpig[2]; - const int i02 = tgpig[1]; - const int i01 = ntg[1] == 1 ? tgpig[0]%args.ne01 : tgpig[0]*ntg[1] + tiitg/ntg[0]; - const int iw0 = ntg[1] == 1 ? tgpig[0]/args.ne01 : 0; - - const int64_t n = i03*args.ne02*args.ne01*args.ne00 + i02*args.ne01*args.ne00 + i01*args.ne00; - - const int64_t i3 = n/(args.ne2*args.ne1*args.ne0); - const int64_t i2 = (n - i3*args.ne2*args.ne1*args.ne0)/(args.ne1*args.ne0); - const int64_t i1 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0)/args.ne0; - const int64_t i0 = (n - i3*args.ne2*args.ne1*args.ne0 - i2*args.ne1*args.ne0 - i1*args.ne0); - - device const block_q * src_data = (device const block_q *)(src0 + i03*args.nb03 + i02*args.nb02 + i01*args.nb01); - device T4x4 * dst_data = (device T4x4 *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - for (int64_t i00 = iw0*ntg[0] + tiitg%ntg[0]; i00 < args.nk0; ) { - T4x4 temp; - dequantize_func(src_data + i00/nl, i00%nl, temp); - dst_data[i00] = temp; - - break; - } -} - -typedef decltype(kernel_cpy_q_f32) cpy_q_f_t; - -template [[host_name("kernel_cpy_q1_0_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q4_0_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q4_1_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q5_0_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q5_1_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q8_0_f32")]] kernel cpy_q_f_t kernel_cpy_q_f32; - -template [[host_name("kernel_cpy_q1_0_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q4_0_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q4_1_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q5_0_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q5_1_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; -template [[host_name("kernel_cpy_q8_0_f16")]] kernel cpy_q_f_t kernel_cpy_q_f32; - -kernel void kernel_concat( - constant ggml_metal_kargs_concat & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - - const int i3 = tgpig.z; - const int i2 = tgpig.y; - const int i1 = ntg.y == 1 ? tgpig.x : tgpig.x*ntg.y + tpitg.y; +kernel void kernel_opt_step_adamw_f32( + constant ggml_metal_kargs_opt_step_adamw & args, + device float * x, + device const float * g, + device float * g_m, + device float * g_v, + device const float * pars, + uint gid[[thread_position_in_grid]]) { - if (i1 >= args.ne1) { + if (gid >= args.np) { return; } - - int o[4] = {0, 0, 0, 0}; - o[args.dim] = args.dim == 0 ? args.ne00 : (args.dim == 1 ? args.ne01 : (args.dim == 2 ? args.ne02 : args.ne03)); - - device const float * x; - - for (int i0 = tpitg.x; i0 < args.ne0; i0 += ntg.x) { - if (i0 < args.ne00 && i1 < args.ne01 && i2 < args.ne02 && i3 < args.ne03) { - x = (device const float *)(src0 + (i3 )*args.nb03 + (i2 )*args.nb02 + (i1 )*args.nb01 + (i0 )*args.nb00); - } else { - x = (device const float *)(src1 + (i3 - o[3])*args.nb13 + (i2 - o[2])*args.nb12 + (i1 - o[1])*args.nb11 + (i0 - o[0])*args.nb10); - } - - device float * y = (device float *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); - - *y = *x; - } -} - -template -void kernel_mul_mv_q2_K_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_q2_K * x = (device const block_q2_K *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[32]; - float sumf[nr0]={0.f}; - - const short ix = tiisg/8; // 0...3 - const short it = tiisg%8; // 0...7 - const short iq = it/4; // 0 or 1 - const short ir = it%4; // 0...3 - const short is = (8*ir)/16;// 0 or 1 - - device const float * y4 = y + ix * QK_K + 128 * iq + 8 * ir; - - for (int ib = ix; ib < nb; ib += 4) { - float4 sumy = {0.f, 0.f, 0.f, 0.f}; - for (short i = 0; i < 8; ++i) { - yl[i+ 0] = y4[i+ 0]; sumy[0] += yl[i+ 0]; - yl[i+ 8] = y4[i+32]; sumy[1] += yl[i+ 8]; - yl[i+16] = y4[i+64]; sumy[2] += yl[i+16]; - yl[i+24] = y4[i+96]; sumy[3] += yl[i+24]; - } - - device const uint8_t * sc = (device const uint8_t *)x[ib].scales + 8*iq + is; - device const uint16_t * qs = (device const uint16_t *)x[ib].qs + 16 * iq + 4 * ir; - device const half * dh = &x[ib].d; - - for (short row = 0; row < nr0; row++) { - float4 acc1 = {0.f, 0.f, 0.f, 0.f}; - float4 acc2 = {0.f, 0.f, 0.f, 0.f}; - for (int i = 0; i < 8; i += 2) { - acc1[0] += yl[i+ 0] * (qs[i/2] & 0x0003); - acc2[0] += yl[i+ 1] * (qs[i/2] & 0x0300); - acc1[1] += yl[i+ 8] * (qs[i/2] & 0x000c); - acc2[1] += yl[i+ 9] * (qs[i/2] & 0x0c00); - acc1[2] += yl[i+16] * (qs[i/2] & 0x0030); - acc2[2] += yl[i+17] * (qs[i/2] & 0x3000); - acc1[3] += yl[i+24] * (qs[i/2] & 0x00c0); - acc2[3] += yl[i+25] * (qs[i/2] & 0xc000); - } - float dall = dh[0]; - float dmin = dh[1] * 1.f/16.f; - sumf[row] += dall * ((acc1[0] + 1.f/256.f * acc2[0]) * (sc[0] & 0xF) * 1.f/ 1.f + - (acc1[1] + 1.f/256.f * acc2[1]) * (sc[2] & 0xF) * 1.f/ 4.f + - (acc1[2] + 1.f/256.f * acc2[2]) * (sc[4] & 0xF) * 1.f/16.f + - (acc1[3] + 1.f/256.f * acc2[3]) * (sc[6] & 0xF) * 1.f/64.f) - - dmin * (sumy[0] * (sc[0] & 0xF0) + sumy[1] * (sc[2] & 0xF0) + sumy[2] * (sc[4] & 0xF0) + sumy[3] * (sc[6] & 0xF0)); - - qs += args.nb01/2; - sc += args.nb01; - dh += args.nb01/2; - } - - y4 += 4 * QK_K; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_q2_K_f32")]] -kernel void kernel_mul_mv_q2_K_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_q2_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_q3_K_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_q3_K * x = (device const block_q3_K *) (src0 + offset0); - device const float * yy = (device const float *) (src1 + offset1); - - float yl[32]; - - //const uint16_t kmask1 = 0x3030; - //const uint16_t kmask2 = 0x0f0f; - - const short tid = tiisg/4; - const short ix = tiisg%4; - const short ip = tid/4; // 0 or 1 - const short il = 2*((tid%4)/2); // 0 or 2 - const short ir = tid%2; - const short l0 = 8*ir; - - // One would think that the Metal compiler would figure out that ip and il can only have - // 4 possible states, and optimize accordingly. Well, no. It needs help, and we do it - // with these two tales. - // - // Possible masks for the high bit - const ushort4 mm[4] = {{0x0001, 0x0100, 0x0002, 0x0200}, // ip = 0, il = 0 - {0x0004, 0x0400, 0x0008, 0x0800}, // ip = 0, il = 2 - {0x0010, 0x1000, 0x0020, 0x2000}, // ip = 1, il = 0 - {0x0040, 0x4000, 0x0080, 0x8000}}; // ip = 1, il = 2 - - // Possible masks for the low 2 bits - const int4 qm[2] = {{0x0003, 0x0300, 0x000c, 0x0c00}, {0x0030, 0x3000, 0x00c0, 0xc000}}; - - const ushort4 hm = mm[2*ip + il/2]; - - const short shift = 2*il; - - const float v1 = il == 0 ? 4.f : 64.f; - const float v2 = 4.f * v1; - - const uint16_t s_shift1 = 4*ip; - const uint16_t s_shift2 = s_shift1 + il; - - const short q_offset = 32*ip + l0; - const short y_offset = 128*ip + 32*il + l0; - - device const float * y1 = yy + ix*QK_K + y_offset; - - uint32_t scales32, aux32; - thread uint16_t * scales16 = (thread uint16_t *)&scales32; - thread const int8_t * scales = (thread const int8_t *)&scales32; - - float sumf1[nr0] = {0.f}; - float sumf2[nr0] = {0.f}; - - for (int i = ix; i < nb; i += 4) { - for (short l = 0; l < 8; ++l) { - yl[l+ 0] = y1[l+ 0]; - yl[l+ 8] = y1[l+16]; - yl[l+16] = y1[l+32]; - yl[l+24] = y1[l+48]; - } - - device const uint16_t * q = (device const uint16_t *)(x[i].qs + q_offset); - device const uint16_t * h = (device const uint16_t *)(x[i].hmask + l0); - device const uint16_t * a = (device const uint16_t *)(x[i].scales); - device const half * dh = &x[i].d; - - for (short row = 0; row < nr0; ++row) { - const float d_all = (float)dh[0]; - - scales16[0] = a[4]; - scales16[1] = a[5]; - aux32 = ((scales32 >> s_shift2) << 4) & 0x30303030; - scales16[0] = a[il+0]; - scales16[1] = a[il+1]; - scales32 = ((scales32 >> s_shift1) & 0x0f0f0f0f) | aux32; - - float s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, s6 = 0; - for (short l = 0; l < 8; l += 2) { - const int32_t qs = q[l/2]; - s1 += yl[l+0] * (qs & qm[il/2][0]); - s2 += yl[l+1] * (qs & qm[il/2][1]); - s3 += ((h[l/2] & hm[0]) ? 0.f : yl[l+0]) + ((h[l/2] & hm[1]) ? 0.f : yl[l+1]); - s4 += yl[l+16] * (qs & qm[il/2][2]); - s5 += yl[l+17] * (qs & qm[il/2][3]); - s6 += ((h[l/2] & hm[2]) ? 0.f : yl[l+16]) + ((h[l/2] & hm[3]) ? 0.f : yl[l+17]); - } - float d1 = d_all * (s1 + 1.f/256.f * s2 - s3*v1); - float d2 = d_all * (s4 + 1.f/256.f * s5 - s6*v2); - sumf1[row] += d1 * (scales[0] - 32); - sumf2[row] += d2 * (scales[2] - 32); - - s1 = s2 = s3 = s4 = s5 = s6 = 0; - for (short l = 0; l < 8; l += 2) { - const int32_t qs = q[l/2+8]; - s1 += yl[l+8] * (qs & qm[il/2][0]); - s2 += yl[l+9] * (qs & qm[il/2][1]); - s3 += ((h[l/2+8] & hm[0]) ? 0.f : yl[l+8]) + ((h[l/2+8] & hm[1]) ? 0.f : yl[l+9]); - s4 += yl[l+24] * (qs & qm[il/2][2]); - s5 += yl[l+25] * (qs & qm[il/2][3]); - s6 += ((h[l/2+8] & hm[2]) ? 0.f : yl[l+24]) + ((h[l/2+8] & hm[3]) ? 0.f : yl[l+25]); - } - d1 = d_all * (s1 + 1.f/256.f * s2 - s3*v1); - d2 = d_all * (s4 + 1.f/256.f * s5 - s6*v2); - sumf1[row] += d1 * (scales[1] - 32); - sumf2[row] += d2 * (scales[3] - 32); - - q += args.nb01/2; - h += args.nb01/2; - a += args.nb01/2; - dh += args.nb01/2; - } - - y1 += 4 * QK_K; - } - - for (int row = 0; row < nr0; ++row) { - const float sumf = (sumf1[row] + 0.25f * sumf2[row]) / (1 << shift); - sumf1[row] = simd_sum(sumf); - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - if (tiisg == 0) { - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - dst_f32[first_row + row] = sumf1[row]; - } - } -} - -[[host_name("kernel_mul_mv_q3_K_f32")]] -kernel void kernel_mul_mv_q3_K_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_q3_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_q4_K_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - constexpr uint16_t kmask1 = 0x3f3f; - constexpr uint16_t kmask2 = 0x0f0f; - constexpr uint16_t kmask3 = 0xc0c0; - - const short ix = tiisg/8; // 0...3 - const short it = tiisg%8; // 0...7 - const short iq = it/4; // 0 or 1 - const short ir = it%4; // 0...3 - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_q4_K * x = (device const block_q4_K *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[16]; - float yh[16]; - - float sumf[nr0]={0.f}; - - device const float * y4 = y + ix * QK_K + 64 * iq + 8 * ir; - - uint16_t sc16[4]; - thread const uint8_t * sc8 = (thread const uint8_t *)sc16; - - for (int ib = ix; ib < nb; ib += 4) { - float4 sumy = {0.f, 0.f, 0.f, 0.f}; - - for (short i = 0; i < 8; ++i) { - yl[i+0] = y4[i+ 0]; sumy[0] += yl[i+0]; - yl[i+8] = y4[i+ 32]; sumy[1] += yl[i+8]; - yh[i+0] = y4[i+128]; sumy[2] += yh[i+0]; - yh[i+8] = y4[i+160]; sumy[3] += yh[i+8]; - } - - device const uint16_t * sc = (device const uint16_t *)x[ib].scales + iq; - device const uint16_t * q1 = (device const uint16_t *)x[ib].qs + 16 * iq + 4 * ir; - device const half * dh = &x[ib].d; - - for (short row = 0; row < nr0; row++) { - sc16[0] = sc[0] & kmask1; - sc16[1] = sc[2] & kmask1; - sc16[2] = ((sc[4] >> 0) & kmask2) | ((sc[0] & kmask3) >> 2); - sc16[3] = ((sc[4] >> 4) & kmask2) | ((sc[2] & kmask3) >> 2); - - device const uint16_t * q2 = q1 + 32; - - float4 acc1 = {0.f, 0.f, 0.f, 0.f}; - float4 acc2 = {0.f, 0.f, 0.f, 0.f}; - - FOR_UNROLL (short i = 0; i < 4; ++i) { - acc1[0] += yl[2*i + 0] * (q1[i] & 0x000F); - acc1[1] += yl[2*i + 1] * (q1[i] & 0x0F00); - acc1[2] += yl[2*i + 8] * (q1[i] & 0x00F0); - acc1[3] += yl[2*i + 9] * (q1[i] & 0xF000); - acc2[0] += yh[2*i + 0] * (q2[i] & 0x000F); - acc2[1] += yh[2*i + 1] * (q2[i] & 0x0F00); - acc2[2] += yh[2*i + 8] * (q2[i] & 0x00F0); - acc2[3] += yh[2*i + 9] * (q2[i] & 0xF000); - } - - sumf[row] += dh[0] * ((acc1[0] + 1.f/256.f * acc1[1]) * sc8[0] + - (acc1[2] + 1.f/256.f * acc1[3]) * sc8[1] * 1.f/16.f + - (acc2[0] + 1.f/256.f * acc2[1]) * sc8[4] + - (acc2[2] + 1.f/256.f * acc2[3]) * sc8[5] * 1.f/16.f) - - dh[1] * (sumy[0] * sc8[2] + sumy[1] * sc8[3] + sumy[2] * sc8[6] + sumy[3] * sc8[7]); - - q1 += args.nb01/2; - sc += args.nb01/2; - dh += args.nb01/2; - } - - y4 += 4 * QK_K; - } - - device float * dst_f32 = (device float *) dst + (int64_t)im*args.ne0*args.ne1 + (int64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_q4_K_f32")]] -kernel void kernel_mul_mv_q4_K_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_q4_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_q5_K_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_q5_K * x = (device const block_q5_K *) (src0 + offset0); - device const float * yy = (device const float *) (src1 + offset1); - - float sumf[nr0]={0.f}; - - float yl[16], yh[16]; - - constexpr uint16_t kmask1 = 0x3f3f; - constexpr uint16_t kmask2 = 0x0f0f; - constexpr uint16_t kmask3 = 0xc0c0; - - const short tid = tiisg/4; - const short ix = tiisg%4; - const short iq = tid/4; - const short ir = tid%4; - - const short l0 = 8*ir; - const short q_offset = 32*iq + l0; - const short y_offset = 64*iq + l0; - - const uint8_t hm1 = 1u << (2*iq); - const uint8_t hm2 = hm1 << 1; - const uint8_t hm3 = hm1 << 4; - const uint8_t hm4 = hm2 << 4; - - uint16_t sc16[4]; - thread const uint8_t * sc8 = (thread const uint8_t *)sc16; - - device const float * y1 = yy + ix*QK_K + y_offset; - - for (int i = ix; i < nb; i += 4) { - device const uint8_t * q1 = x[i].qs + q_offset; - device const uint8_t * qh = x[i].qh + l0; - device const half * dh = &x[i].d; - device const uint16_t * a = (device const uint16_t *)x[i].scales + iq; - - device const float * y2 = y1 + 128; - float4 sumy = {0.f, 0.f, 0.f, 0.f}; - for (short l = 0; l < 8; ++l) { - yl[l+0] = y1[l+ 0]; sumy[0] += yl[l+0]; - yl[l+8] = y1[l+32]; sumy[1] += yl[l+8]; - yh[l+0] = y2[l+ 0]; sumy[2] += yh[l+0]; - yh[l+8] = y2[l+32]; sumy[3] += yh[l+8]; - } - - for (short row = 0; row < nr0; ++row) { - device const uint8_t * q2 = q1 + 64; - - sc16[0] = a[0] & kmask1; - sc16[1] = a[2] & kmask1; - sc16[2] = ((a[4] >> 0) & kmask2) | ((a[0] & kmask3) >> 2); - sc16[3] = ((a[4] >> 4) & kmask2) | ((a[2] & kmask3) >> 2); - - float4 acc1 = {0.f}; - float4 acc2 = {0.f}; - FOR_UNROLL (short l = 0; l < 8; ++l) { - uint8_t h = qh[l]; - acc1[0] += yl[l+0] * (q1[l] & 0x0F); - acc1[1] += yl[l+8] * (q1[l] & 0xF0); - acc1[2] += yh[l+0] * (q2[l] & 0x0F); - acc1[3] += yh[l+8] * (q2[l] & 0xF0); - acc2[0] += h & hm1 ? yl[l+0] : 0.f; - acc2[1] += h & hm2 ? yl[l+8] : 0.f; - acc2[2] += h & hm3 ? yh[l+0] : 0.f; - acc2[3] += h & hm4 ? yh[l+8] : 0.f; - } - - sumf[row] += dh[0] * (sc8[0] * (acc1[0] + 16.f*acc2[0]) + - sc8[1] * (acc1[1]/16.f + 16.f*acc2[1]) + - sc8[4] * (acc1[2] + 16.f*acc2[2]) + - sc8[5] * (acc1[3]/16.f + 16.f*acc2[3])) - - dh[1] * (sumy[0] * sc8[2] + sumy[1] * sc8[3] + sumy[2] * sc8[6] + sumy[3] * sc8[7]); - - q1 += args.nb01; - qh += args.nb01; - dh += args.nb01/2; - a += args.nb01/2; - } - - y1 += 4 * QK_K; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - const float tot = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = tot; - } - } -} - -[[host_name("kernel_mul_mv_q5_K_f32")]] -kernel void kernel_mul_mv_q5_K_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_q5_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_q6_K_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - constexpr uint8_t kmask1 = 0x03; - constexpr uint8_t kmask2 = 0x0C; - constexpr uint8_t kmask3 = 0x30; - constexpr uint8_t kmask4 = 0xC0; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_q6_K * x = (device const block_q6_K *) (src0 + offset0); - device const float * yy = (device const float *) (src1 + offset1); - - float sumf[nr0] = { 0.f }; - - float yl[16]; - - const short tid = tiisg/2; - const short ix = tiisg%2; - const short ip = tid/8; // 0 or 1 - const short il = tid%8; - const short l0 = 4*il; - const short is = 8*ip + l0/16; - - const short y_offset = 128*ip + l0; - const short q_offset_l = 64*ip + l0; - const short q_offset_h = 32*ip + l0; - - for (int i = ix; i < nb; i += 2) { - device const uint8_t * q1 = x[i].ql + q_offset_l; - device const uint8_t * q2 = q1 + 32; - device const uint8_t * qh = x[i].qh + q_offset_h; - device const int8_t * sc = x[i].scales + is; - device const half * dh = &x[i].d; - - device const float * y = yy + i * QK_K + y_offset; - - for (short l = 0; l < 4; ++l) { - yl[4*l + 0] = y[l + 0]; - yl[4*l + 1] = y[l + 32]; - yl[4*l + 2] = y[l + 64]; - yl[4*l + 3] = y[l + 96]; - } - - for (short row = 0; row < nr0; ++row) { - float4 sums = {0.f, 0.f, 0.f, 0.f}; - - FOR_UNROLL (short l = 0; l < 4; ++l) { - sums[0] += yl[4*l + 0] * ((int8_t)((q1[l] & 0xF) | ((qh[l] & kmask1) << 4)) - 32); - sums[1] += yl[4*l + 1] * ((int8_t)((q2[l] & 0xF) | ((qh[l] & kmask2) << 2)) - 32); - sums[2] += yl[4*l + 2] * ((int8_t)((q1[l] >> 4) | ((qh[l] & kmask3) << 0)) - 32); - sums[3] += yl[4*l + 3] * ((int8_t)((q2[l] >> 4) | ((qh[l] & kmask4) >> 2)) - 32); - } - - sumf[row] += dh[0] * (sums[0] * sc[0] + sums[1] * sc[2] + sums[2] * sc[4] + sums[3] * sc[6]); - - q1 += args.nb01; - q2 += args.nb01; - qh += args.nb01; - sc += args.nb01; - dh += args.nb01/2; - } - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_q6_K_f32")]] -kernel void kernel_mul_mv_q6_K_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_q6_K_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); -} - -// ======================= "True" 2-bit - -template -void kernel_mul_mv_iq2_xxs_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq2_xxs * x = (device const block_iq2_xxs *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[32]; - float sumf[nr0]={0.f}; - - const int nb32 = nb * (QK_K / 32); - - threadgroup uint64_t * svalues = (threadgroup uint64_t *)(shmem); - threadgroup uint8_t * ssigns = (threadgroup uint8_t *)(svalues + 256); - { - int nval = 4; - int pos = (32*sgitg + tiisg)*nval; - for (int i = 0; i < nval; ++i) svalues[pos + i] = iq2xxs_grid[pos + i]; - nval = 2; - pos = (32*sgitg + tiisg)*nval; - for (int i = 0; i < nval; ++i) ssigns[pos+i] = ksigns_iq2xs[pos+i]; - threadgroup_barrier(mem_flags::mem_threadgroup); - } - - const int ix = tiisg; - - device const float * y4 = y + 32 * ix; - - for (int ib32 = ix; ib32 < nb32; ib32 += 32) { - for (short i = 0; i < 32; ++i) { - yl[i] = y4[i]; - } - - const int ibl = ib32 / (QK_K / 32); - const int ib = ib32 % (QK_K / 32); - - device const block_iq2_xxs * xr = x + ibl; - device const uint16_t * q2 = xr->qs + 4 * ib; - device const half * dh = &xr->d; - - for (short row = 0; row < nr0; row++) { - const float db = dh[0]; - device const uint8_t * aux8 = (device const uint8_t *)q2; - const uint32_t aux32 = q2[2] | (q2[3] << 16); - const float d = db * (0.5f + (aux32 >> 28)); - - float sum = 0; - for (short l = 0; l < 4; ++l) { - const threadgroup uint8_t * grid = (const threadgroup uint8_t *)(svalues + aux8[l]); - const uint8_t signs = ssigns[(aux32 >> 7*l) & 127]; - for (short j = 0; j < 8; ++j) { - sum += yl[8*l + j] * grid[j] * (signs & kmask_iq2xs[j] ? -1.f : 1.f); - } - } - sumf[row] += d * sum; - - dh += args.nb01/2; - q2 += args.nb01/2; - } - - y4 += 32 * 32; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all * 0.25f; - } - } -} - -[[host_name("kernel_mul_mv_iq2_xxs_f32")]] -kernel void kernel_mul_mv_iq2_xxs_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - kernel_mul_mv_iq2_xxs_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_iq2_xs_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq2_xs * x = (device const block_iq2_xs *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[32]; - float sumf[nr0]={0.f}; - - const int nb32 = nb * (QK_K / 32); - - threadgroup uint64_t * svalues = (threadgroup uint64_t *)(shmem); - threadgroup uint8_t * ssigns = (threadgroup uint8_t *)(svalues + 512); - { - int nval = 8; - int pos = (32*sgitg + tiisg)*nval; - for (int i = 0; i < nval; ++i) svalues[pos + i] = iq2xs_grid[pos + i]; - nval = 2; - pos = (32*sgitg + tiisg)*nval; - for (int i = 0; i < nval; ++i) ssigns[pos+i] = ksigns_iq2xs[pos+i]; - threadgroup_barrier(mem_flags::mem_threadgroup); - } - - const int ix = tiisg; - - device const float * y4 = y + 32 * ix; - - for (int ib32 = ix; ib32 < nb32; ib32 += 32) { - for (short i = 0; i < 32; ++i) { - yl[i] = y4[i]; - } - - const int ibl = ib32 / (QK_K / 32); - const int ib = ib32 % (QK_K / 32); - - device const block_iq2_xs * xr = x + ibl; - device const uint16_t * q2 = xr->qs + 4 * ib; - device const uint8_t * sc = xr->scales + ib; - device const half * dh = &xr->d; - - for (short row = 0; row < nr0; row++) { - const float db = dh[0]; - const uint8_t ls1 = sc[0] & 0xf; - const uint8_t ls2 = sc[0] >> 4; - const float d1 = db * (0.5f + ls1); - const float d2 = db * (0.5f + ls2); - - float sum1 = 0, sum2 = 0; - for (short l = 0; l < 2; ++l) { - const threadgroup uint8_t * grid = (const threadgroup uint8_t *)(svalues + (q2[l] & 511)); - const uint8_t signs = ssigns[(q2[l] >> 9)]; - for (short j = 0; j < 8; ++j) { - sum1 += yl[8*l + j] * grid[j] * (signs & kmask_iq2xs[j] ? -1.f : 1.f); - } - } - for (short l = 2; l < 4; ++l) { - const threadgroup uint8_t * grid = (const threadgroup uint8_t *)(svalues + (q2[l] & 511)); - const uint8_t signs = ssigns[(q2[l] >> 9)]; - for (short j = 0; j < 8; ++j) { - sum2 += yl[8*l + j] * grid[j] * (signs & kmask_iq2xs[j] ? -1.f : 1.f); - } - } - sumf[row] += d1 * sum1 + d2 * sum2; - - dh += args.nb01/2; - q2 += args.nb01/2; - sc += args.nb01; - } - - y4 += 32 * 32; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all * 0.25f; - } - } -} - -[[host_name("kernel_mul_mv_iq2_xs_f32")]] -kernel void kernel_mul_mv_iq2_xs_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_iq2_xs_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_iq3_xxs_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq3_xxs * x = (device const block_iq3_xxs *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[32]; - float sumf[nr0]={0.f}; - - const int nb32 = nb * (QK_K / 32); - - threadgroup uint32_t * svalues = (threadgroup uint32_t *)(shmem); - threadgroup uint8_t * ssigns = (threadgroup uint8_t *)(svalues + 256); - { - int nval = 4; - int pos = (32*sgitg + tiisg)*nval; - for (int i = 0; i < nval; ++i) svalues[pos + i] = iq3xxs_grid[pos + i]; - nval = 2; - pos = (32*sgitg + tiisg)*nval; - for (int i = 0; i < nval; ++i) ssigns[pos+i] = ksigns_iq2xs[pos+i]; - threadgroup_barrier(mem_flags::mem_threadgroup); - } - - const int ix = tiisg; - - device const float * y4 = y + 32 * ix; - - for (int ib32 = ix; ib32 < nb32; ib32 += 32) { - for (short i = 0; i < 32; ++i) { - yl[i] = y4[i]; - } - - const int ibl = ib32 / (QK_K / 32); - const int ib = ib32 % (QK_K / 32); - - device const block_iq3_xxs * xr = x + ibl; - device const uint8_t * q3 = xr->qs + 8 * ib; - device const uint16_t * gas = (device const uint16_t *)(xr->qs + QK_K/4) + 2 * ib; - device const half * dh = &xr->d; - - for (short row = 0; row < nr0; row++) { - const float db = dh[0]; - const uint32_t aux32 = gas[0] | (gas[1] << 16); - const float d = db * (0.5f + (aux32 >> 28)); - - float2 sum = {0}; - for (short l = 0; l < 4; ++l) { - const threadgroup uint8_t * grid1 = (const threadgroup uint8_t *)(svalues + q3[2*l+0]); - const threadgroup uint8_t * grid2 = (const threadgroup uint8_t *)(svalues + q3[2*l+1]); - const uint8_t signs = ssigns[(aux32 >> 7*l) & 127]; - for (short j = 0; j < 4; ++j) { - sum[0] += yl[8*l + j + 0] * grid1[j] * (signs & kmask_iq2xs[j+0] ? -1.f : 1.f); - sum[1] += yl[8*l + j + 4] * grid2[j] * (signs & kmask_iq2xs[j+4] ? -1.f : 1.f); - } - } - sumf[row] += d * (sum[0] + sum[1]); - - dh += args.nb01/2; - q3 += args.nb01; - gas += args.nb01/2; - } - - y4 += 32 * 32; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all * 0.5f; - } - } -} - -[[host_name("kernel_mul_mv_iq3_xxs_f32")]] -kernel void kernel_mul_mv_iq3_xxs_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_iq3_xxs_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_iq3_s_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq3_s * x = (device const block_iq3_s *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[32]; - float sumf[nr0]={0.f}; - - const int nb32 = nb * (QK_K / 32); - - threadgroup uint32_t * svalues = (threadgroup uint32_t *) shmem; - { - int nval = 8; - int pos = (32*sgitg + tiisg)*nval; - for (int i = 0; i < nval; ++i) svalues[pos + i] = iq3s_grid[pos + i]; - threadgroup_barrier(mem_flags::mem_threadgroup); - } - - const int ix = tiisg; - - device const float * y4 = y + 32 * ix; - - for (int ib32 = ix; ib32 < nb32; ib32 += 32) { - for (short i = 0; i < 32; ++i) { - yl[i] = y4[i]; - } - - const int ibl = ib32 / (QK_K / 32); - const int ib = ib32 % (QK_K / 32); - - device const block_iq3_s * xr = x + ibl; - device const uint8_t * qs = xr->qs + 8 * ib; - device const uint8_t * qh = xr->qh + ib; - device const uint8_t * sc = xr->scales + (ib/2); - device const uint8_t * signs = xr->signs + 4 * ib; - device const half * dh = &xr->d; - - for (short row = 0; row < nr0; row++) { - const float db = dh[0]; - const float d = db * (1 + 2*((sc[0] >> 4*(ib%2)) & 0xf)); - - float2 sum = {0}; - for (short l = 0; l < 4; ++l) { - const threadgroup uint32_t * table1 = qh[0] & kmask_iq2xs[2*l+0] ? svalues + 256 : svalues; - const threadgroup uint32_t * table2 = qh[0] & kmask_iq2xs[2*l+1] ? svalues + 256 : svalues; - const threadgroup uint8_t * grid1 = (const threadgroup uint8_t *)(table1 + qs[2*l+0]); - const threadgroup uint8_t * grid2 = (const threadgroup uint8_t *)(table2 + qs[2*l+1]); - for (short j = 0; j < 4; ++j) { - sum[0] += yl[8*l + j + 0] * grid1[j] * select(1, -1, signs[l] & kmask_iq2xs[j+0]); - sum[1] += yl[8*l + j + 4] * grid2[j] * select(1, -1, signs[l] & kmask_iq2xs[j+4]); - } - } - sumf[row] += d * (sum[0] + sum[1]); - - dh += args.nb01/2; - qs += args.nb01; - qh += args.nb01; - sc += args.nb01; - signs += args.nb01; - } - - y4 += 32 * 32; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_iq3_s_f32")]] -kernel void kernel_mul_mv_iq3_s_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_iq3_s_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_iq2_s_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq2_s * x = (device const block_iq2_s *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[32]; - float sumf[nr0]={0.f}; - - const int nb32 = nb * (QK_K / 32); - - //threadgroup uint64_t * svalues = (threadgroup uint64_t *) shmem; - //{ - // int nval = 32; - // int pos = (32*sgitg + tiisg)*nval; - // for (int i = 0; i < nval; ++i) svalues[pos + i] = iq2s_grid[pos + i]; - // threadgroup_barrier(mem_flags::mem_threadgroup); - //} - - const short ix = tiisg; - - device const float * y4 = y + 32 * ix; - - for (int ib32 = ix; ib32 < nb32; ib32 += 32) { - for (short i = 0; i < 32; ++i) { - yl[i] = y4[i]; - } - - const int ibl = ib32 / (QK_K / 32); - const int ib = ib32 % (QK_K / 32); - - device const block_iq2_s * xr = x + ibl; - device const uint8_t * qs = xr->qs + 4 * ib; - device const uint8_t * qh = xr->qh + ib; - device const uint8_t * sc = xr->scales + ib; - device const uint8_t * signs = qs + QK_K/8; - device const half * dh = &xr->d; - - for (short row = 0; row < nr0; row++) { - const float db = dh[0]; - const float d1 = db * (0.5f + (sc[0] & 0xf)); - const float d2 = db * (0.5f + (sc[0] >> 4)); - - float2 sum = {0}; - for (short l = 0; l < 2; ++l) { - //const threadgroup uint8_t * grid1 = (const threadgroup uint8_t *)(svalues + (qs[l+0] | ((qh[0] << (8-2*l)) & 0x300))); - //const threadgroup uint8_t * grid2 = (const threadgroup uint8_t *)(svalues + (qs[l+2] | ((qh[0] << (4-2*l)) & 0x300))); - constant uint8_t * grid1 = (constant uint8_t *)(iq2s_grid + (qs[l+0] | ((qh[0] << (8-2*l)) & 0x300))); - constant uint8_t * grid2 = (constant uint8_t *)(iq2s_grid + (qs[l+2] | ((qh[0] << (4-2*l)) & 0x300))); - for (short j = 0; j < 8; ++j) { - sum[0] += yl[8*l + j + 0] * grid1[j] * select(1, -1, signs[l+0] & kmask_iq2xs[j]); - sum[1] += yl[8*l + j + 16] * grid2[j] * select(1, -1, signs[l+2] & kmask_iq2xs[j]); - } - } - sumf[row] += d1 * sum[0] + d2 * sum[1]; - - dh += args.nb01/2; - qs += args.nb01; - qh += args.nb01; - sc += args.nb01; - signs += args.nb01; - } - - y4 += 32 * 32; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all * 0.25f; - } - } -} - -[[host_name("kernel_mul_mv_iq2_s_f32")]] -kernel void kernel_mul_mv_iq2_s_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_iq2_s_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_iq1_s_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq1_s * x = (device const block_iq1_s *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[32]; - float sumf[nr0]={0.f}; - - const int nb32 = nb * (QK_K / 32); - - const short ix = tiisg; - - device const float * y4 = y + 32 * ix; - - for (int ib32 = ix; ib32 < nb32; ib32 += 32) { - float sumy = 0; - for (short i = 0; i < 32; ++i) { - yl[i] = y4[i]; - sumy += yl[i]; - } - - const int ibl = ib32 / (QK_K / 32); - const int ib = ib32 % (QK_K / 32); - - device const block_iq1_s * xr = x + ibl; - device const uint8_t * qs = xr->qs + 4 * ib; - device const uint16_t * qh = xr->qh + ib; - device const half * dh = &xr->d; - - for (short row = 0; row < nr0; row++) { - constant uint8_t * grid1 = (constant uint8_t *)(iq1s_grid_gpu + (qs[0] | ((qh[0] << 8) & 0x700))); - constant uint8_t * grid2 = (constant uint8_t *)(iq1s_grid_gpu + (qs[1] | ((qh[0] << 5) & 0x700))); - constant uint8_t * grid3 = (constant uint8_t *)(iq1s_grid_gpu + (qs[2] | ((qh[0] << 2) & 0x700))); - constant uint8_t * grid4 = (constant uint8_t *)(iq1s_grid_gpu + (qs[3] | ((qh[0] >> 1) & 0x700))); - - float sum = 0; - for (short j = 0; j < 4; ++j) { - sum += yl[j+ 0] * (grid1[j] & 0xf) + yl[j+ 4] * (grid1[j] >> 4) - + yl[j+ 8] * (grid2[j] & 0xf) + yl[j+12] * (grid2[j] >> 4) - + yl[j+16] * (grid3[j] & 0xf) + yl[j+20] * (grid3[j] >> 4) - + yl[j+24] * (grid4[j] & 0xf) + yl[j+28] * (grid4[j] >> 4); - } - sumf[row] += (float)dh[0] * (sum + sumy * (qh[0] & 0x8000 ? -1 - IQ1S_DELTA : -1 + IQ1S_DELTA)) * (2*((qh[0] >> 12) & 7) + 1); - - dh += args.nb01/2; - qs += args.nb01; - qh += args.nb01/2; - } - - y4 += 32 * 32; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_iq1_s_f32")]] -kernel void kernel_mul_mv_iq1_s_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_iq1_s_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_iq1_m_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - const int nb = args.ne00/QK_K; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * nr0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq1_m * x = (device const block_iq1_m *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - float yl[32]; - float sumf[nr0]={0.f}; - - const int nb32 = nb * (QK_K / 32); - - const short ix = tiisg; - - device const float * y4 = y + 32 * ix; - - iq1m_scale_t scale; - - for (int ib32 = ix; ib32 < nb32; ib32 += 32) { - float4 sumy = {0.f}; - for (short i = 0; i < 8; ++i) { - yl[i+ 0] = y4[i+ 0]; sumy[0] += yl[i+ 0]; - yl[i+ 8] = y4[i+ 8]; sumy[1] += yl[i+ 8]; - yl[i+16] = y4[i+16]; sumy[2] += yl[i+16]; - yl[i+24] = y4[i+24]; sumy[3] += yl[i+24]; - } - - const int ibl = ib32 / (QK_K / 32); - const int ib = ib32 % (QK_K / 32); - - device const block_iq1_m * xr = x + ibl; - device const uint8_t * qs = xr->qs + 4 * ib; - device const uint8_t * qh = xr->qh + 2 * ib; - device const uint16_t * sc = (device const uint16_t *)xr->scales; - - for (short row = 0; row < nr0; row++) { - scale.u16 = (sc[0] >> 12) | ((sc[1] >> 8) & 0x00f0) | ((sc[2] >> 4) & 0x0f00) | (sc[3] & 0xf000); - - constant uint8_t * grid1 = (constant uint8_t *)(iq1s_grid_gpu + (qs[0] | ((qh[0] << 8) & 0x700))); - constant uint8_t * grid2 = (constant uint8_t *)(iq1s_grid_gpu + (qs[1] | ((qh[0] << 4) & 0x700))); - constant uint8_t * grid3 = (constant uint8_t *)(iq1s_grid_gpu + (qs[2] | ((qh[1] << 8) & 0x700))); - constant uint8_t * grid4 = (constant uint8_t *)(iq1s_grid_gpu + (qs[3] | ((qh[1] << 4) & 0x700))); - - float2 sum = {0.f}; - for (short j = 0; j < 4; ++j) { - sum[0] += yl[j+ 0] * (grid1[j] & 0xf) + yl[j+ 4] * (grid1[j] >> 4) - + yl[j+ 8] * (grid2[j] & 0xf) + yl[j+12] * (grid2[j] >> 4); - sum[1] += yl[j+16] * (grid3[j] & 0xf) + yl[j+20] * (grid3[j] >> 4) - + yl[j+24] * (grid4[j] & 0xf) + yl[j+28] * (grid4[j] >> 4); - } - const float delta1 = sumy[0] * (qh[0] & 0x08 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA) + sumy[1] * (qh[0] & 0x80 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA); - const float delta2 = sumy[2] * (qh[1] & 0x08 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA) + sumy[3] * (qh[1] & 0x80 ? -1 - IQ1M_DELTA : -1 + IQ1M_DELTA); - - sumf[row] += (float)scale.f16 * ((sum[0] + delta1) * (2*((sc[ib/2] >> (6*(ib%2)+0)) & 7) + 1) + - (sum[1] + delta2) * (2*((sc[ib/2] >> (6*(ib%2)+3)) & 7) + 1)); - - sc += args.nb01/2; - qs += args.nb01; - qh += args.nb01; - } - - y4 += 32 * 32; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < nr0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_iq1_m_f32")]] -kernel void kernel_mul_mv_iq1_m_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_iq1_m_f32_impl(args, src0, src1, dst, nullptr, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_iq4_nl_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - threadgroup float * shmem_f32 = (threadgroup float *) shmem; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * NR0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq4_nl * x = (device const block_iq4_nl *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - const int nb = args.ne00/QK4_NL; - const int ns01 = args.nb01/args.nb00; - - const short ix = tiisg/2; // 0...15 - const short it = tiisg%2; // 0 or 1 - - shmem_f32[tiisg] = kvalues_iq4nl_f[tiisg%16]; - threadgroup_barrier(mem_flags::mem_threadgroup); - - float4 yl[4]; - float sumf[NR0]={0.f}; - - device const float * yb = y + ix*QK4_NL + it*8; - - uint32_t aux32[2]; - thread const uint8_t * q8 = (thread const uint8_t *)aux32; - - float4 qf1, qf2; - - // [TAG_MUL_MV_WEIRD] - for (int ib = ix; ib < nb && ib < ns01; ib += 16) { - device const float4 * y4 = (device const float4 *)yb; - yl[0] = y4[0]; - yl[1] = y4[4]; - yl[2] = y4[1]; - yl[3] = y4[5]; - - for (short row = 0; row < NR0; row++) { - device const block_iq4_nl & xb = x[row*ns01 + ib]; - device const uint16_t * q4 = (device const uint16_t *)(xb.qs + 8*it); - - float4 acc1 = {0.f}, acc2 = {0.f}; - - aux32[0] = q4[0] | (q4[1] << 16); - aux32[1] = (aux32[0] >> 4) & 0x0f0f0f0f; - aux32[0] &= 0x0f0f0f0f; - qf1 = {shmem_f32[q8[0]], shmem_f32[q8[1]], shmem_f32[q8[2]], shmem_f32[q8[3]]}; - qf2 = {shmem_f32[q8[4]], shmem_f32[q8[5]], shmem_f32[q8[6]], shmem_f32[q8[7]]}; - acc1 += yl[0] * qf1; - acc2 += yl[1] * qf2; - - aux32[0] = q4[2] | (q4[3] << 16); - aux32[1] = (aux32[0] >> 4) & 0x0f0f0f0f; - aux32[0] &= 0x0f0f0f0f; - qf1 = {shmem_f32[q8[0]], shmem_f32[q8[1]], shmem_f32[q8[2]], shmem_f32[q8[3]]}; - qf2 = {shmem_f32[q8[4]], shmem_f32[q8[5]], shmem_f32[q8[6]], shmem_f32[q8[7]]}; - acc1 += yl[2] * qf1; - acc2 += yl[3] * qf2; - - acc1 += acc2; - - sumf[row] += (float)xb.d * (acc1[0] + acc1[1] + acc1[2] + acc1[3]); - } - - yb += 16 * QK4_NL; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < NR0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_iq4_nl_f32")]] -kernel void kernel_mul_mv_iq4_nl_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_iq4_nl_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_iq4_xs_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - threadgroup float * shmem_f32 = (threadgroup float *) shmem; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - const int first_row = (r0 * NSG + sgitg) * NR0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_iq4_xs * x = (device const block_iq4_xs *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - const int nb = args.ne00/QK_K; - const int ns01 = args.nb01/args.nb00; - - const short ix = tiisg/16; // 0 or 1 - const short it = tiisg%16; // 0...15 - const short ib = it/2; - const short il = it%2; - - shmem_f32[tiisg] = kvalues_iq4nl_f[tiisg%16]; - threadgroup_barrier(mem_flags::mem_threadgroup); - - float4 yl[4]; - float sumf[NR0]={0.f}; - - device const float * yb = y + ix * QK_K + ib * 32 + il * 8; - - uint32_t aux32[2]; - thread const uint8_t * q8 = (thread const uint8_t *)aux32; - - float4 qf1, qf2; - - // [TAG_MUL_MV_WEIRD] - for (int ibl = ix; ibl < nb && ibl < ns01; ibl += 2) { - device const float4 * y4 = (device const float4 *)yb; - yl[0] = y4[0]; - yl[1] = y4[4]; - yl[2] = y4[1]; - yl[3] = y4[5]; - - for (short row = 0; row < NR0; ++row) { - device const block_iq4_xs & xb = x[row*ns01 + ibl]; - device const uint32_t * q4 = (device const uint32_t *)(xb.qs + 16*ib + 8*il); - - float4 acc1 = {0.f}, acc2 = {0.f}; - - aux32[0] = (q4[0] ) & 0x0f0f0f0f; - aux32[1] = (q4[0] >> 4) & 0x0f0f0f0f; - qf1 = {shmem_f32[q8[0]], shmem_f32[q8[1]], shmem_f32[q8[2]], shmem_f32[q8[3]]}; - qf2 = {shmem_f32[q8[4]], shmem_f32[q8[5]], shmem_f32[q8[6]], shmem_f32[q8[7]]}; - acc1 += yl[0] * qf1; - acc2 += yl[1] * qf2; - - aux32[0] = (q4[1] ) & 0x0f0f0f0f; - aux32[1] = (q4[1] >> 4) & 0x0f0f0f0f; - qf1 = {shmem_f32[q8[0]], shmem_f32[q8[1]], shmem_f32[q8[2]], shmem_f32[q8[3]]}; - qf2 = {shmem_f32[q8[4]], shmem_f32[q8[5]], shmem_f32[q8[6]], shmem_f32[q8[7]]}; - acc1 += yl[2] * qf1; - acc2 += yl[3] * qf2; - - acc1 += acc2; - - const int ls = (((xb.scales_l[ib/2] >> 4*(ib%2)) & 0xf) | (((xb.scales_h >> 2*ib) & 3) << 4)) - 32; - sumf[row] += (float)xb.d * ls * (acc1[0] + acc1[1] + acc1[2] + acc1[3]); - } - - yb += 2 * QK_K; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < NR0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_iq4_xs_f32")]] -kernel void kernel_mul_mv_iq4_xs_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_iq4_xs_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -void kernel_mul_mv_mxfp4_f32_impl( - args_t args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg) { - const short NSG = FC_mul_mv_nsg; - - threadgroup float * shmem_f32 = (threadgroup float *) shmem; - - const int r0 = tgpig.x; - const int r1 = tgpig.y; - const int im = tgpig.z; - - const int first_row = (r0 * NSG + sgitg) * NR0; - - const uint i12 = im%FC_mul_mv_ne12; - const uint i13 = im/FC_mul_mv_ne12; - - const uint64_t offset0 = first_row*args.nb01 + (i12/FC_mul_mv_r2)*args.nb02 + (i13/FC_mul_mv_r3)*args.nb03; - const uint64_t offset1 = r1*args.nb11 + (i12 )*args.nb12 + (i13 )*args.nb13; - - device const block_mxfp4 * x = (device const block_mxfp4 *) (src0 + offset0); - device const float * y = (device const float *) (src1 + offset1); - - const int nb = args.ne00/QK_MXFP4; - const int ns01 = args.nb01/args.nb00; // this can be larger than nb for permuted src0 tensors - - const short ix = tiisg/2; // 0...15 - const short it = tiisg%2; // 0 or 1 - - shmem_f32[tiisg] = kvalues_mxfp4_f[tiisg%16]; - threadgroup_barrier(mem_flags::mem_threadgroup); - - float4 yl[4]; - float sumf[NR0]={0.f}; - - device const float * yb = y + ix*QK_MXFP4 + it*8; - - // note: just the check `ib < nb` is enough, but adding the redundant `&& ib < ns01` check makes the kernel a bit faster - // no idea why that is - needs some deeper investigation [TAG_MUL_MV_WEIRD] - for (int ib = ix; ib < nb && ib < ns01; ib += 16) { - device const float4 * y4 = (device const float4 *) yb; - - yl[0] = y4[0]; - yl[1] = y4[4]; - yl[2] = y4[1]; - yl[3] = y4[5]; - - FOR_UNROLL (short row = 0; row < NR0; row++) { - device const block_mxfp4 & xb = x[row*ns01 + ib]; - device const uint8_t * q2 = (device const uint8_t *)(xb.qs + 8*it); - - float4 acc1 = yl[0]*float4(shmem_f32[q2[0] & 0x0F], shmem_f32[q2[1] & 0x0F], shmem_f32[q2[2] & 0x0F], shmem_f32[q2[3] & 0x0F]); - float4 acc2 = yl[1]*float4(shmem_f32[q2[0] >> 4 ], shmem_f32[q2[1] >> 4 ], shmem_f32[q2[2] >> 4 ], shmem_f32[q2[3] >> 4 ]); - float4 acc3 = yl[2]*float4(shmem_f32[q2[4] & 0x0F], shmem_f32[q2[5] & 0x0F], shmem_f32[q2[6] & 0x0F], shmem_f32[q2[7] & 0x0F]); - float4 acc4 = yl[3]*float4(shmem_f32[q2[4] >> 4 ], shmem_f32[q2[5] >> 4 ], shmem_f32[q2[6] >> 4 ], shmem_f32[q2[7] >> 4 ]); - - acc1 = (acc1 + acc3) + (acc2 + acc4); - - sumf[row] += e8m0_to_fp32(xb.e) * ((acc1[0] + acc1[1]) + (acc1[2] + acc1[3])); - } - - yb += 16 * QK_MXFP4; - } - - device float * dst_f32 = (device float *) dst + (uint64_t)im*args.ne0*args.ne1 + (uint64_t)r1*args.ne0; - - for (int row = 0; row < NR0 && first_row + row < args.ne0; ++row) { - float sum_all = simd_sum(sumf[row]); - if (tiisg == 0) { - dst_f32[first_row + row] = sum_all; - } - } -} - -[[host_name("kernel_mul_mv_mxfp4_f32")]] -kernel void kernel_mul_mv_mxfp4_f32( - constant ggml_metal_kargs_mul_mv & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - kernel_mul_mv_mxfp4_f32_impl(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -template -kernel void kernel_get_rows_q( - constant ggml_metal_kargs_get_rows & args, - device const void * src0, - device const void * src1, - device void * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 ntg [[threads_per_threadgroup]]) { - const int32_t iw0 = tgpig.x/args.ne10; - const int32_t i10 = tgpig.x%args.ne10; - const int32_t i11 = tgpig.y; - const int32_t i12 = tgpig.z; - - const int32_t r = ((const device int32_t *) ((const device char *) src1 + i12*args.nb12 + i11*args.nb11 + i10*args.nb10))[0]; - - const int32_t i02 = i11; - const int32_t i03 = i12; - - auto psrc = (device const block_q *) ((const device char *) src0 + i03*args.nb03 + i02*args.nb02 + r*args.nb01); - auto pdst = (device float4x4 *) (( device char *) dst + i12*args.nb3 + i11*args.nb2 + i10*args.nb1); - - for (int ind = iw0*ntg.x + tiitg; ind < args.ne00t;) { - float4x4 temp; - dequantize_func(psrc + ind/nl, ind%nl, temp); - pdst[ind] = temp; - - break; - } -} - -template -kernel void kernel_get_rows_f( - constant ggml_metal_kargs_get_rows & args, - device const void * src0, - device const void * src1, - device void * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 ntg [[threads_per_threadgroup]]) { - const int32_t iw0 = tgpig.x/args.ne10; - const int32_t i10 = tgpig.x%args.ne10; - const int32_t i11 = tgpig.y; - const int32_t i12 = tgpig.z; - - const int32_t r = ((const device int32_t *) ((const device char *) src1 + i12*args.nb12 + i11*args.nb11 + i10*args.nb10))[0]; - - const int32_t i02 = i11; - const int32_t i03 = i12; - - auto psrc = (const device T0 *) ((const device char *) src0 + i03*args.nb03 + i02*args.nb02 + r*args.nb01); - auto pdst = ( device T *) (( device char *) dst + i12*args.nb3 + i11*args.nb2 + i10*args.nb1); - - for (int ind = iw0*ntg.x + tiitg; ind < args.ne00t;) { - pdst[ind] = psrc[ind]; - - break; - } -} - -template -kernel void kernel_set_rows_q32( - constant ggml_metal_kargs_set_rows & args, - device const void * src0, - device const void * src1, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint tiitg[[thread_index_in_threadgroup]], - uint3 tptg [[threads_per_threadgroup]]) { - const int32_t i03 = tgpig.z; - const int32_t i02 = tgpig.y; - - const int32_t i12 = i03%args.ne12; - const int32_t i11 = i02%args.ne11; - - const int32_t i01 = tgpig.x*tptg.y + tiitg/tptg.x; - if (i01 >= args.ne01) { - return; - } - - const int32_t i10 = i01; - const TI i1 = ((const device TI *) ((const device char *) src1 + i10*args.nb10 + i11*args.nb11 + i12*args.nb12))[0]; - - device block_q * dst_row = ( device block_q *) (( device char *) dst + i1*args.nb1 + i02*args.nb2 + i03*args.nb3); - const device float * src_row = (const device float *) ((const device char *) src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03); - - for (int ind = tiitg%tptg.x; ind < args.nk0; ind += tptg.x) { - quantize_func(src_row + 32*ind, dst_row[ind]); - } -} - -template -kernel void kernel_set_rows_f( - constant ggml_metal_kargs_set_rows & args, - device const void * src0, - device const void * src1, - device float * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - uint tiitg[[thread_index_in_threadgroup]], - uint3 tptg [[threads_per_threadgroup]]) { - const int32_t i03 = tgpig.z; - const int32_t i02 = tgpig.y; - - const int32_t i12 = i03%args.ne12; - const int32_t i11 = i02%args.ne11; - - const int32_t i01 = tgpig.x*tptg.y + tiitg/tptg.x; - if (i01 >= args.ne01) { - return; - } - - const int32_t i10 = i01; - const TI i1 = ((const device TI *) ((const device char *) src1 + i10*args.nb10 + i11*args.nb11 + i12*args.nb12))[0]; - - device T * dst_row = ( device T *) (( device char *) dst + i1*args.nb1 + i02*args.nb2 + i03*args.nb3); - const device float * src_row = (const device float *) ((const device char *) src0 + i01*args.nb01 + i02*args.nb02 + i03*args.nb03); - - for (int ind = tiitg%tptg.x; ind < args.nk0; ind += tptg.x) { - dst_row[ind] = (T) src_row[ind]; - } -} - -kernel void kernel_diag_f32( - constant ggml_metal_kargs_diag & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]]) { - constexpr short NW = N_SIMDWIDTH; - - const int32_t i3 = tgpig.z; - const int32_t i2 = tgpig.y; - const int32_t i1 = tgpig.x; - - device const float * src0_ptr = (device const float *)(src0 + i2*args.nb02 + i3*args.nb03); - device float * dst_ptr = (device float *)(dst + i1*args.nb01 + i2*args.nb2 + i3*args.nb3); - - for (int i0 = tiitg; i0 < args.ne0; i0 += NW) { - dst_ptr[i0] = i0 == i1 ? src0_ptr[i0] : 0.0f; + + const float alpha = pars[0]; + const float beta1 = pars[1]; + const float beta2 = pars[2]; + const float eps = pars[3]; + const float wd = pars[4]; + const float beta1h = pars[5]; + const float beta2h = pars[6]; + + const float gi = g[gid]; + const float gmi = g_m[gid] * beta1 + gi * (1.0f - beta1); + const float gvi = g_v[gid] * beta2 + gi * gi * (1.0f - beta2); + + g_m[gid] = gmi; + g_v[gid] = gvi; + + const float mh = gmi * beta1h; + const float vh = sqrt(gvi * beta2h) + eps; + + x[gid] = x[gid] * (1.0f - alpha * wd) - alpha * mh / vh; +} + +kernel void kernel_opt_step_sgd_f32( + constant ggml_metal_kargs_opt_step_sgd & args, + device float * x, + device const float * g, + device const float * pars, + uint gid[[thread_position_in_grid]]) { + + if (gid >= args.np) { + return; } + + x[gid] = x[gid] * (1.0f - pars[0] * pars[1]) - pars[0] * g[gid]; } -kernel void kernel_diag_mask_inf_f32( - constant ggml_metal_kargs_diag_mask_inf & args, - device const char * src0, - device char * dst, - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort3 tptg[[threads_per_threadgroup]]) { - const int32_t i0 = tgpig.x*tptg.x + tiitg; - const int32_t i1 = tgpig.y; - const int32_t i2 = tgpig.z % args.ne2; - const int32_t i3 = tgpig.z / args.ne2; +template +kernel void kernel_memset( + constant ggml_metal_kargs_memset & args, + device T * dst, + uint tpig[[thread_position_in_grid]]) { + dst[tpig] = args.val; +} - if (i0 >= args.ne0) { +typedef decltype(kernel_memset) kernel_memset_t; + +template [[host_name("kernel_memset_i64")]] kernel kernel_memset_t kernel_memset; + +constant short FC_count_equal_nsg [[function_constant(FC_COUNT_EQUAL + 0)]]; + +template +kernel void kernel_count_equal( + constant ggml_metal_kargs_count_equal & args, + device const char * src0, + device const char * src1, + device atomic_int * dst, + threadgroup int32_t * shmem_i32 [[threadgroup(0)]], + uint3 tgpig[[threadgroup_position_in_grid]], + ushort3 tpitg[[thread_position_in_threadgroup]], + ushort sgitg[[simdgroup_index_in_threadgroup]], + ushort tiisg[[thread_index_in_simdgroup]], + ushort3 ntg[[threads_per_threadgroup]]) { + const short NSG = FC_count_equal_nsg; + + const int i3 = tgpig.z; + const int i2 = tgpig.y; + const int i1 = tgpig.x; + + if (i3 >= args.ne03 || i2 >= args.ne02 || i1 >= args.ne01) { return; } - device const float * src0_ptr = (device const float *)(src0 + i3*args.nb03 + i2*args.nb02 + i1*args.nb01 + i0*args.nb00); - device float * dst_ptr = (device float *)(dst + i3*args.nb3 + i2*args.nb2 + i1*args.nb1 + i0*args.nb0); + int sum = 0; - *dst_ptr = i0 > args.n_past + i1 ? -INFINITY : *src0_ptr; + device const char * base0 = src0 + i1*args.nb01 + i2*args.nb02 + i3*args.nb03; + device const char * base1 = src1 + i1*args.nb11 + i2*args.nb12 + i3*args.nb13; + + for (int64_t i0 = tpitg.x; i0 < args.ne00; i0 += ntg.x) { + const T v0 = *(device const T *)(base0 + i0*args.nb00); + const T v1 = *(device const T *)(base1 + i0*args.nb10); + sum += (v0 == v1); + } + + sum = simd_sum(sum); + + if (tiisg == 0) { + shmem_i32[sgitg] = sum; + } + + threadgroup_barrier(mem_flags::mem_threadgroup); + + if (sgitg == 0) { + float v = 0.0f; + if (tpitg.x < NSG) { + v = shmem_i32[tpitg.x]; + } + + float total = simd_sum(v); + if (tpitg.x == 0) { + atomic_fetch_add_explicit(dst, (int32_t) total, memory_order_relaxed); + } + } } -constant bool FC_mul_mm_bc_inp [[function_constant(FC_MUL_MM + 0)]]; -constant bool FC_mul_mm_bc_out [[function_constant(FC_MUL_MM + 1)]]; -constant short FC_mul_mm_ne12 [[function_constant(FC_MUL_MM + 2)]]; -constant short FC_mul_mm_ne13 [[function_constant(FC_MUL_MM + 3)]]; -constant short FC_mul_mm_r2 [[function_constant(FC_MUL_MM + 4)]]; -constant short FC_mul_mm_r3 [[function_constant(FC_MUL_MM + 5)]]; - -// each block_q contains 16*nl weights -#ifdef GGML_METAL_HAS_TENSOR -template< - typename SA, typename SA_4x4, typename SA_8x8, - typename SB, typename SB_2x4, typename SB_8x8, - typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread SA_4x4 &), - typename T0, typename T0_4x4, typename T1, typename T1_2x4> -kernel void kernel_mul_mm( - constant ggml_metal_kargs_mul_mm & args, - device const char * srcA, - device const char * srcB, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig [[threadgroup_position_in_grid]], - ushort tiitg [[thread_index_in_threadgroup]], - ushort sgitg [[simdgroup_index_in_threadgroup]]) { - (void) sgitg; - - // Matrix dimensions: A(M,K) x B(K,N) -> C(M,N) - const int K = args.ne00; - const int M = args.ne0; - const int N = args.ne1; - - // Batch dimension handling - const int im = tgpig.z; - const int i12 = im % FC_mul_mm_ne12; - const int i13 = im / FC_mul_mm_ne12; - - // Batch offsets for srcA and srcB - const uint64_t offset0 = (i12/FC_mul_mm_r2)*args.nb02 + (i13/FC_mul_mm_r3)*args.nb03; - - // Tile dimensions - constexpr int NRB = SZ_SIMDGROUP * N_MM_BLOCK_X * N_MM_SIMD_GROUP_X; - constexpr int NRA = SZ_SIMDGROUP * N_MM_BLOCK_Y * N_MM_SIMD_GROUP_Y; - - // Tile offsets in output matrix - const int ra = tgpig.y * NRA; - const int rb = tgpig.x * NRB; - - // Threadgroup memory for dequantized A tile only - threadgroup SA * sa = (threadgroup SA *)(shmem); - - // Work-item count for A loading - constexpr int A_WORK_ITEMS = NRA * N_MM_NK; - constexpr int NUM_THREADS = N_SIMDWIDTH * N_MM_SIMD_GROUP_X * N_MM_SIMD_GROUP_Y; - - // tA wraps threadgroup memory - auto tA = tensor(sa, dextents(N_MM_NK_TOTAL, NRA)); - - // tB wraps device memory directly - device T1 * ptrB = (device T1 *)(srcB + args.nb12*i12 + args.nb13*i13); - const int strideB = args.nb11 / sizeof(T1); - auto tB = tensor(ptrB, dextents(K, N), array({1, strideB})); - - // Configure matmul operation - mpp::tensor_ops::matmul2d< - mpp::tensor_ops::matmul2d_descriptor( - NRB, NRA, N_MM_NK_TOTAL, false, true, true, - mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate), - execution_simdgroups> mm; - - auto cT = mm.get_destination_cooperative_tensor(); - - // Accumulate partial results over K dimension - for (int loop_k = 0; loop_k < K; loop_k += N_MM_NK_TOTAL) { - // === PHASE 1: Dequantization of A into threadgroup memory === - for (int work = tiitg; work < A_WORK_ITEMS; work += NUM_THREADS) { - const int row = work / N_MM_NK; - const int k_chunk = work % N_MM_NK; - const int k_pos = loop_k + k_chunk * 16; - const short k_base = k_chunk * 16; - - // Bounds check: skip device read if row is out of matrix bounds - if (ra + row < M) { - if (is_same::value && FC_mul_mm_bc_inp) { - // Element-wise reads when K is not aligned (nb01 not aligned for half4x4/float4x4). - // MSL spec Table 2.5: half4x4 requires 8-byte alignment. When K is odd, - // nb01 = K*2 is not 8-byte aligned, so odd-row pointers are misaligned. - // Mirrors the legacy kernel's existing guard. - device const T0 * row_ptr = (device const T0 *)(srcA + args.nb01 * (ra + row) + offset0); - - FOR_UNROLL (short i = 0; i < 16; i++) { - sa[row * N_MM_NK_TOTAL + (k_base + i)] = (k_pos + i < K) ? (SA) row_ptr[k_pos + i] : (SA)0; - } - } else { - const int block_idx = k_pos / (16 * nl); - const short il = (k_pos / 16) % nl; - - device const block_q * row_ptr = (device const block_q *)(srcA + args.nb01 * (ra + row) + offset0); - - SA_4x4 temp_a; - dequantize_func(row_ptr + block_idx, il, temp_a); - - FOR_UNROLL (short i = 0; i < 16; i++) { - // Zero-pad A for K positions beyond valid range (handles partial K iterations) - sa[row * N_MM_NK_TOTAL + (k_base + i)] = (k_pos + i < K) ? temp_a[i/4][i%4] : (SA)0; - } - } - } else { - // Zero-pad rows beyond matrix bounds - FOR_UNROLL (short i = 0; i < 16; i++) { - sa[row * N_MM_NK_TOTAL + (k_base + i)] = (SA)0; - } - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - // === PHASE 2: Tensor matmul === - auto mA = tA.slice(0, 0); - auto mB = tB.slice(loop_k, rb); - - mm.run(mB, mA, cT); - - threadgroup_barrier(mem_flags::mem_threadgroup); - } - - // Store result tile to output matrix (with batch offset) - // cT.store handles bounds checking via tD's extents (M, N) - device float * dstBatch = (device float *)dst + im * N * M; - - auto tD = tensor(dstBatch, dextents(M, N), array({1, M})); - cT.store(tD.slice(ra, rb)); -} - -#else - -template< - typename S0, typename S0_4x4, typename S0_8x8, - typename S1, typename S1_2x4, typename S1_8x8, - typename block_q, short nl, void (*dequantize_func)(device const block_q *, short, thread S0_4x4 &), - typename T0, typename T0_4x4, typename T1, typename T1_2x4> -kernel void kernel_mul_mm( - constant ggml_metal_kargs_mul_mm & args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - - threadgroup S0 * sa = (threadgroup S0 *)(shmem); - threadgroup S1 * sb = (threadgroup S1 *)(shmem + 4096); - - constexpr int NR0 = 64; - constexpr int NR1 = 32; - - constexpr int NK = 32; - constexpr int NL0 = NK/16; - constexpr int NL1 = NK/8; - - const int im = tgpig.z; - const int r0 = tgpig.y*NR0; - const int r1 = tgpig.x*NR1; - - // if this block is of 64x32 shape or smaller - const short nr0 = (args.ne0 - r0 < NR0) ? (args.ne0 - r0) : NR0; - const short nr1 = (args.ne1 - r1 < NR1) ? (args.ne1 - r1) : NR1; - - // a thread shouldn't load data outside of the matrix - const short lr0 = ((short)tiitg/NL0) < nr0 ? ((short)tiitg/NL0) : nr0 - 1; // 0 .. 63 - const short lr1 = ((short)tiitg/NL1) < nr1 ? ((short)tiitg/NL1) : nr1 - 1; // 0 .. 31 - - const short il0 = (tiitg % NL0); - - short il = il0; - - const int i12 = im % FC_mul_mm_ne12; - const int i13 = im / FC_mul_mm_ne12; - - const uint64_t offset0 = (i12/FC_mul_mm_r2)*args.nb02 + (i13/FC_mul_mm_r3)*args.nb03; - const short offset1 = il0/nl; - - device const block_q * x = (device const block_q *)(src0 + args.nb01*(r0 + lr0) + offset0) + offset1; - - const short iy = 8*(tiitg % NL1); - - device const T1 * y = (device const T1 *)(src1 - + args.nb13*i13 - + args.nb12*i12 - + args.nb11*(r1 + lr1) - + args.nb10*iy); - - S0_8x8 ma[4]; - S1_8x8 mb[2]; - - simdgroup_float8x8 mc[8]; - - for (short i = 0; i < 8; i++){ - mc[i] = make_filled_simdgroup_matrix(0.f); - } - - for (int loop_k = 0; loop_k < args.ne00; loop_k += NK) { - // load data and store to threadgroup memory - if (is_same::value && FC_mul_mm_bc_inp) { - threadgroup_barrier(mem_flags::mem_threadgroup); - - // no need for dequantization - for (short i = 0; i < 16; i++) { - const short sx = 2*il0 + i/8; - const short sy = (tiitg/NL0)/8; - - //const short lx = i%8; - //const short ly = (tiitg/NL0)%8; - const short lx = (tiitg/NL0)%8; - const short ly = i%8; - - const short ib = 8*sx + sy; - - *(sa + 64*ib + 8*ly + lx) = loop_k + 16*il + i < args.ne00 ? *((device T0 *) x + i) : 0; - } - } else { - S0_4x4 temp_a; - dequantize_func(x, il, temp_a); - - threadgroup_barrier(mem_flags::mem_threadgroup); - - FOR_UNROLL (short i = 0; i < 16; i++) { - const short sx = 2*il0 + i/8; - const short sy = (tiitg/NL0)/8; - - //const short lx = i%8; - //const short ly = (tiitg/NL0)%8; - const short lx = (tiitg/NL0)%8; - const short ly = i%8; - - const short ib = 8*sx + sy; - - // NOTE: this is massively slower.. WTF? - //sa[64*ib + 8*ly + lx] = temp_a[i/4][i%4]; - - *(sa + 64*ib + 8*ly + lx) = temp_a[i/4][i%4]; - } - } - - if (FC_mul_mm_bc_inp) { - for (short i = 0; i < 8; ++i) { - const short sx = (tiitg%NL1); - const short sy = (tiitg/NL1)/8; - - const short lx = i; - const short ly = (tiitg/NL1)%8; - //const short lx = (tiitg/NL1)%8; - //const short ly = i; - - const short ib = 4*sx + sy; - - *(sb + 64*ib + 8*ly + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; - } - } else { - const short sx = (tiitg%NL1); - const short sy = (tiitg/NL1)/8; - - //const short dx = sx; - //const short dy = sy; - - const short ly = (tiitg/NL1)%8; - - const short ib = 4*sx + sy; - - *(threadgroup S1_2x4 *)(sb + 64*ib + 8*ly) = (S1_2x4)(*((device T1_2x4 *) y)); - } - - il = (il + 2 < nl) ? il + 2 : il % 2; - x = (il < 2) ? x + (2 + nl - 1)/nl : x; - - y += NK; - - threadgroup_barrier(mem_flags::mem_threadgroup); - - // load matrices from threadgroup memory and conduct outer products - threadgroup const S0 * lsma = (sa + 4*64*(sgitg%2)); - threadgroup const S1 * lsmb = (sb + 2*64*(sgitg/2)); - - FOR_UNROLL (short ik = 0; ik < NK/8; ik++) { - simdgroup_barrier(mem_flags::mem_none); - - FOR_UNROLL (short i = 0; i < 4; i++) { - simdgroup_load(ma[i], lsma + 64*i, 8, 0, false); - } - - simdgroup_barrier(mem_flags::mem_none); - - FOR_UNROLL (short i = 0; i < 2; i++) { - simdgroup_load(mb[i], lsmb + 64*i, 8, 0, false); - } - - simdgroup_barrier(mem_flags::mem_none); - - FOR_UNROLL (short i = 0; i < 8; i++){ - simdgroup_multiply_accumulate(mc[i], mb[i/4], ma[i%4], mc[i]); - } - - lsma += 8*64; - lsmb += 4*64; - } - } - - if (!FC_mul_mm_bc_out || (r0 + NR0 <= args.ne0 && r1 + NR1 <= args.ne1)) { - // if no bounds checks on the output are needed, we can directly write to device memory - device float * C = (device float *) dst + - (r0 + 32*(sgitg & 1)) + \ - (r1 + 16*(sgitg >> 1)) * args.ne0 + im*args.ne1*args.ne0; - - for (short i = 0; i < 8; i++) { - simdgroup_store(mc[i], C + 8*(i%4) + 8*args.ne0*(i/4), args.ne0, 0, false); - } - } else { - // block is smaller than 64x32, we should avoid writing data outside of the matrix - threadgroup_barrier(mem_flags::mem_threadgroup); - - threadgroup float * temp_str = ((threadgroup float *) shmem) + 32*(sgitg&1) + (16*(sgitg >> 1))*NR0; - - for (short i = 0; i < 8; i++) { - simdgroup_store(mc[i], temp_str + 8*(i%4) + 8*NR0*(i/4), NR0, 0, false); - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (sgitg == 0) { - for (int j = tiitg; j < nr1; j += NR1) { - device float * D = (device float *) dst + r0 + (r1 + j)*args.ne0 + im*args.ne1*args.ne0; - device float4 * D4 = (device float4 *) D; - - threadgroup float * C = temp_str + (j*NR0); - threadgroup float4 * C4 = (threadgroup float4 *) C; - - int i = 0; - for (; i < nr0/4; i++) { - *(D4 + i) = *(C4 + i); - } - - i *= 4; - for (; i < nr0; i++) { - *(D + i) = *(C + i); - } - } - } - } -} - -#endif // GGML_METAL_HAS_TENSOR - -template // n_expert_used -kernel void kernel_mul_mm_id_map0( - constant ggml_metal_kargs_mul_mm_id_map0 & args, - device const char * src2, - device char * htpe, - device char * hids, - threadgroup char * shmem [[threadgroup(0)]], - ushort tpitg[[thread_position_in_threadgroup]], - ushort ntg[[threads_per_threadgroup]]) { - const short ide = tpitg; // expert id - - uint32_t n_all = 0; - - device int32_t * ids_i32 = (device int32_t *) hids + ide*args.ne21; - - for (int i21 = 0; i21 < args.ne21; i21 += ntg) { // n_tokens - if (i21 + tpitg < args.ne21) { - device const int32_t * src2_i32 = (device const int32_t *) (src2 + (i21 + tpitg)*args.nb21); - - threadgroup uint16_t * sids = (threadgroup uint16_t *) shmem + tpitg*ne20; - - #pragma unroll(ne20) - for (short i20 = 0; i20 < ne20; i20++) { - sids[i20] = src2_i32[i20]; - } - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - for (short t = 0; t < ntg; t++) { - if (i21 + t >= args.ne21) { - break; - } - - threadgroup const uint16_t * sids = (threadgroup const uint16_t *) shmem + t*ne20; - - short sel = 0; - #pragma unroll(ne20) - for (short i20 = 0; i20 < ne20; i20++) { - sel += (sids[i20] == ide)*(i20 + 1); - } - - ids_i32[n_all] = (i21 + t)*ne20 + sel - 1; - - n_all += sel > 0; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - } - - device uint32_t * tpe_u32 = (device uint32_t *) (htpe); - tpe_u32[ide] = n_all; -} - -typedef decltype(kernel_mul_mm_id_map0<1>) kernel_mul_mm_id_map0_t; - -template [[host_name("kernel_mul_mm_id_map0_ne20_1" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<1>; -template [[host_name("kernel_mul_mm_id_map0_ne20_2" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<2>; -template [[host_name("kernel_mul_mm_id_map0_ne20_4" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<4>; -template [[host_name("kernel_mul_mm_id_map0_ne20_5" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<5>; -template [[host_name("kernel_mul_mm_id_map0_ne20_6" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<6>; -template [[host_name("kernel_mul_mm_id_map0_ne20_8" )]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<8>; -template [[host_name("kernel_mul_mm_id_map0_ne20_10")]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<10>; -template [[host_name("kernel_mul_mm_id_map0_ne20_16")]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<16>; -template [[host_name("kernel_mul_mm_id_map0_ne20_22")]] kernel kernel_mul_mm_id_map0_t kernel_mul_mm_id_map0<22>; - -template -kernel void kernel_mul_mm_id( - constant ggml_metal_kargs_mul_mm_id & args, - device const char * src0, - device const char * src1, - device const char * htpe, - device const char * hids, - device char * dst, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - threadgroup S0 * sa = (threadgroup S0 *)(shmem); - threadgroup S1 * sb = (threadgroup S1 *)(shmem + 4096); - -#ifdef GGML_METAL_HAS_TENSOR - threadgroup float * sc = (threadgroup float *)(shmem); -#endif - - constexpr int NR0 = 64; - constexpr int NR1 = 32; - - constexpr int NK = 32; - constexpr int NL0 = NK/16; - constexpr int NL1 = NK/8; - - const int im = tgpig.z; // expert - const int r0 = tgpig.y*NR0; - const int r1 = tgpig.x*NR1; - - device const uint32_t * tpe_u32 = (device const uint32_t *) (htpe); - device const int32_t * ids_i32 = (device const int32_t *) (hids); - - const int32_t neh1 = tpe_u32[im]; - - if (r1 >= neh1) { - return; - } - - // if this block is of 64x32 shape or smaller - const short nr0 = (args.ne0 - r0 < NR0) ? (args.ne0 - r0) : NR0; - const short nr1 = ( neh1 - r1 < NR1) ? ( neh1 - r1) : NR1; - - // a thread shouldn't load data outside of the matrix - const short lr0 = ((short)tiitg/NL0) < nr0 ? ((short)tiitg/NL0) : nr0 - 1; // 0 .. 63 - const short lr1 = ((short)tiitg/NL1) < nr1 ? ((short)tiitg/NL1) : nr1 - 1; // 0 .. 31 - - const short il0 = (tiitg % NL0); - - short il = il0; - - const int id = ids_i32[im*args.ne21 + r1 + lr1]; - - const short i11 = (id % args.ne20) % args.ne11; - const short i12 = (id / args.ne20); - const short i13 = 0; - - const uint64_t offset0 = im*args.nb02 + i13*args.nb03; - const short offset1 = il0/nl; - - device const block_q * x = (device const block_q *)(src0 + args.nb01*(r0 + lr0) + offset0) + offset1; - - const short iy = 8*(tiitg % NL1); - - device const T1 * y = (device const T1 *)(src1 - + args.nb13*i13 - + args.nb12*i12 - + args.nb11*i11 - + args.nb10*iy); - -#ifndef GGML_METAL_HAS_TENSOR - S0_8x8 ma[4]; - S1_8x8 mb[2]; - - simdgroup_float8x8 mc[8]; - - for (short i = 0; i < 8; i++){ - mc[i] = make_filled_simdgroup_matrix(0.f); - } -#else - auto tA = tensor, tensor_inline>(sa, dextents(NK, NR0)); - auto tB = tensor, tensor_inline>(sb, dextents(NR1, NK )); - - mpp::tensor_ops::matmul2d< - mpp::tensor_ops::matmul2d_descriptor(NR1, NR0, NK, false, true, false, mpp::tensor_ops::matmul2d_descriptor::mode::multiply_accumulate), - execution_simdgroups<4>> mm; - - auto cT = mm.get_destination_cooperative_tensor(); -#endif - - for (int loop_k = 0; loop_k < args.ne00; loop_k += NK) { -#ifndef GGML_METAL_HAS_TENSOR - // load data and store to threadgroup memory - if (is_same::value && FC_mul_mm_bc_inp) { - threadgroup_barrier(mem_flags::mem_threadgroup); - - // no need for dequantization - for (short i = 0; i < 16; i++) { - const short sx = 2*il0 + i/8; - const short sy = (tiitg/NL0)/8; - - //const short lx = i%8; - //const short ly = (tiitg/NL0)%8; - const short lx = (tiitg/NL0)%8; - const short ly = i%8; - - const short ib = 8*sx + sy; - - *(sa + 64*ib + 8*ly + lx) = loop_k + 16*il + i < args.ne00 ? (S0) *((device T0 *) x + i) : (S0) 0; - } - } else { - S0_4x4 temp_a; - dequantize_func(x, il, temp_a); - - threadgroup_barrier(mem_flags::mem_threadgroup); - - FOR_UNROLL (short i = 0; i < 16; i++) { - const short sx = 2*il0 + i/8; - const short sy = (tiitg/NL0)/8; - - //const short lx = i%8; - //const short ly = (tiitg/NL0)%8; - const short lx = (tiitg/NL0)%8; - const short ly = i%8; - - const short ib = 8*sx + sy; - - // NOTE: this is massively slower.. WTF? - //sa[64*ib + 8*ly + lx] = temp_a[i/4][i%4]; - - *(sa + 64*ib + 8*ly + lx) = temp_a[i/4][i%4]; - } - } - - if (FC_mul_mm_bc_inp) { - for (short i = 0; i < 8; ++i) { - const short sx = (tiitg%NL1); - const short sy = (tiitg/NL1)/8; - - const short lx = i; - const short ly = (tiitg/NL1)%8; - //const short lx = (tiitg/NL1)%8; - //const short ly = i; - - const short ib = 4*sx + sy; - - *(sb + 64*ib + 8*ly + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; - } - } else { - const short sx = (tiitg%NL1); - const short sy = (tiitg/NL1)/8; - - //const short dx = sx; - //const short dy = sy; - - const short ly = (tiitg/NL1)%8; - - const short ib = 4*sx + sy; - - *(threadgroup S1_2x4 *)(sb + 64*ib + 8*ly) = (S1_2x4)(*((device T1_2x4 *) y)); - } -#else - // load data and store to threadgroup memory - if (is_same::value && FC_mul_mm_bc_inp) { - threadgroup_barrier(mem_flags::mem_threadgroup); - - // no need for dequantization - for (short i = 0; i < 16; i++) { - const short sx = 2*il0 + i/8; - const short sy = (tiitg/NL0)/8; - - const short lx = i%8; - const short ly = (tiitg/NL0)%8; - //const short lx = (tiitg/NL0)%8; - //const short ly = i%8; - - *(sa + NK*(8*sy + ly) + 8*sx + lx) = loop_k + 16*il + i < args.ne00 ? *((device T0 *) x + i) : 0; - } - } else { - S0_4x4 temp_a; - dequantize_func(x, il, temp_a); - - threadgroup_barrier(mem_flags::mem_threadgroup); - - FOR_UNROLL (short i = 0; i < 16; i++) { - const short sx = 2*il0 + i/8; - const short sy = (tiitg/NL0)/8; - - const short lx = i%8; - const short ly = (tiitg/NL0)%8; - //const short lx = (tiitg/NL0)%8; - //const short ly = i%8; - - *(sa + NK*(8*sy + ly) + 8*sx + lx) = temp_a[i/4][i%4]; - } - } - - if (FC_mul_mm_bc_inp) { - for (short i = 0; i < 8; ++i) { - const short sx = (tiitg%NL1); - const short sy = (tiitg/NL1)/8; - - const short lx = i; - const short ly = (tiitg/NL1)%8; - //const short lx = (tiitg/NL1)%8; - //const short ly = i; - - *(sb + NK*(8*sy + ly) + 8*sx + lx) = loop_k + iy + i < args.ne00 ? (S1) *((device T1 *) y + i) : 0; - } - } else { - const short sx = (tiitg%NL1); - const short sy = (tiitg/NL1)/8; - - //const short lx = i; - const short ly = (tiitg/NL1)%8; - //const short lx = (tiitg/NL1)%8; - //const short ly = i; - - *(threadgroup S1_2x4 *)(sb + NK*(8*sy + ly) + 8*sx) = (S1_2x4)(*((device T1_2x4 *) y)); - } -#endif - - il = (il + 2 < nl) ? il + 2 : il % 2; - x = (il < 2) ? x + (2 + nl - 1)/nl : x; - - y += NK; - - threadgroup_barrier(mem_flags::mem_threadgroup); - -#ifndef GGML_METAL_HAS_TENSOR - // load matrices from threadgroup memory and conduct outer products - threadgroup const S0 * lsma = (sa + 4*64*(sgitg%2)); - threadgroup const S1 * lsmb = (sb + 2*64*(sgitg/2)); - - FOR_UNROLL (short ik = 0; ik < NK/8; ik++) { - simdgroup_barrier(mem_flags::mem_none); - - FOR_UNROLL (short i = 0; i < 4; i++) { - simdgroup_load(ma[i], lsma + 64*i, 8, 0, false); - } - - simdgroup_barrier(mem_flags::mem_none); - - FOR_UNROLL (short i = 0; i < 2; i++) { - simdgroup_load(mb[i], lsmb + 64*i, 8, 0, false); - } - - simdgroup_barrier(mem_flags::mem_none); - - FOR_UNROLL (short i = 0; i < 8; i++){ - simdgroup_multiply_accumulate(mc[i], mb[i/4], ma[i%4], mc[i]); - } - - lsma += 8*64; - lsmb += 4*64; - } -#else - auto sA = tA.slice(0, 0); - auto sB = tB.slice(0, 0); - - mm.run(sB, sA, cT); -#endif - } - - // block is smaller than 64x32, we should avoid writing data outside of the matrix - threadgroup_barrier(mem_flags::mem_threadgroup); - -#ifdef GGML_METAL_HAS_TENSOR - auto tC = tensor, tensor_inline>(sc, dextents(NR0, NR1)); - cT.store(tC); -#else - threadgroup float * temp_str = ((threadgroup float *) shmem) + 32*(sgitg&1) + (16*(sgitg >> 1))*NR0; - - for (short i = 0; i < 8; i++) { - simdgroup_store(mc[i], temp_str + 8*(i%4) + 8*NR0*(i/4), NR0, 0, false); - } -#endif - - threadgroup_barrier(mem_flags::mem_threadgroup); - - for (short j = sgitg; j < nr1; j += 4) { - const int id = ids_i32[im*args.ne21 + r1 + j]; - - const short ide = id % args.ne20; - const short idt = id / args.ne20; - - device float * D = (device float *) dst + r0 + ide*args.ne0 + idt*args.ne1*args.ne0; - device float4 * D4 = (device float4 *) D; - - threadgroup float * C = (threadgroup float *) shmem + j*NR0; - threadgroup float4 * C4 = (threadgroup float4 *) C; - - int i = tiisg; - for (; i < nr0/4; i += 32) { - *(D4 + i) = *(C4 + i); - } - - i = (4*(nr0/4)) + tiisg; - for (; i < nr0; i += 32) { - *(D + i) = *(C + i); - } - } -} - -#define QK_NL 16 - -// -// get rows -// - -typedef decltype(kernel_get_rows_f) get_rows_f_t; - -template [[host_name("kernel_get_rows_f32")]] kernel get_rows_f_t kernel_get_rows_f; -template [[host_name("kernel_get_rows_f16")]] kernel get_rows_f_t kernel_get_rows_f; -template [[host_name("kernel_get_rows_i32")]] kernel get_rows_f_t kernel_get_rows_f; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_get_rows_bf16")]] kernel get_rows_f_t kernel_get_rows_f; -#endif - -typedef decltype(kernel_get_rows_q) get_rows_q_t; - -template [[host_name("kernel_get_rows_q1_0")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q4_0")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q4_1")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q5_0")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q5_1")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q8_0")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_mxfp4")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q2_K")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q3_K")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q4_K")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q5_K")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_q6_K")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq2_xxs")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq2_xs")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq3_xxs")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq3_s")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq2_s")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq1_s")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq1_m")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq4_nl")]] kernel get_rows_q_t kernel_get_rows_q; -template [[host_name("kernel_get_rows_iq4_xs")]] kernel get_rows_q_t kernel_get_rows_q; - -// -// set rows -// - -typedef decltype(kernel_set_rows_f) set_rows_f_t; - -template [[host_name("kernel_set_rows_f32_i64")]] kernel set_rows_f_t kernel_set_rows_f; -template [[host_name("kernel_set_rows_f32_i32")]] kernel set_rows_f_t kernel_set_rows_f; -template [[host_name("kernel_set_rows_f16_i64")]] kernel set_rows_f_t kernel_set_rows_f; -template [[host_name("kernel_set_rows_f16_i32")]] kernel set_rows_f_t kernel_set_rows_f; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_set_rows_bf16_i64")]] kernel set_rows_f_t kernel_set_rows_f; -template [[host_name("kernel_set_rows_bf16_i32")]] kernel set_rows_f_t kernel_set_rows_f; -#endif - -typedef decltype(kernel_set_rows_q32) set_rows_q32_t; - -template [[host_name("kernel_set_rows_q8_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q8_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q4_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q4_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q4_1_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q4_1_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q5_0_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q5_0_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q5_1_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_q5_1_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_iq4_nl_i64")]] kernel set_rows_q32_t kernel_set_rows_q32; -template [[host_name("kernel_set_rows_iq4_nl_i32")]] kernel set_rows_q32_t kernel_set_rows_q32; - -// -// matrix-matrix multiplication -// - -typedef decltype(kernel_mul_mm) mul_mm_t; - -template [[host_name("kernel_mul_mm_f32_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_f16_f32")]] kernel mul_mm_t kernel_mul_mm; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_mul_mm_bf16_f32")]] kernel mul_mm_t kernel_mul_mm; -#endif -template [[host_name("kernel_mul_mm_q1_0_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q4_0_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q4_1_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q5_0_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q5_1_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q8_0_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_mxfp4_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q2_K_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q3_K_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q4_K_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q5_K_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q6_K_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq2_xxs_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq2_xs_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq3_xxs_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq3_s_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq2_s_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq1_s_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq1_m_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq4_nl_f32")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq4_xs_f32")]] kernel mul_mm_t kernel_mul_mm; - -template [[host_name("kernel_mul_mm_f32_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_f16_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q1_0_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q4_0_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q4_1_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q5_0_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q5_1_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q8_0_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_mxfp4_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q2_K_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q3_K_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q4_K_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q5_K_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_q6_K_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq2_xxs_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq2_xs_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq3_xxs_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq3_s_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq2_s_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq1_s_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq1_m_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq4_nl_f16")]] kernel mul_mm_t kernel_mul_mm; -template [[host_name("kernel_mul_mm_iq4_xs_f16")]] kernel mul_mm_t kernel_mul_mm; - -// -// indirect matrix-matrix multiplication -// - -typedef decltype(kernel_mul_mm_id) mul_mm_id; - -template [[host_name("kernel_mul_mm_id_f32_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_f16_f32")]] kernel mul_mm_id kernel_mul_mm_id; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_mul_mm_id_bf16_f32")]] kernel mul_mm_id kernel_mul_mm_id; -#endif -template [[host_name("kernel_mul_mm_id_q1_0_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q4_0_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q4_1_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q5_0_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q5_1_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q8_0_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_mxfp4_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q2_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q3_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q4_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q5_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q6_K_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq2_xxs_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq2_xs_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq3_xxs_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq3_s_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq2_s_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq1_s_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq1_m_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq4_nl_f32")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq4_xs_f32")]] kernel mul_mm_id kernel_mul_mm_id; - -template [[host_name("kernel_mul_mm_id_f32_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_f16_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q1_0_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q4_0_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q4_1_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q5_0_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q5_1_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q8_0_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_mxfp4_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q2_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q3_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q4_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q5_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_q6_K_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq2_xxs_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq2_xs_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq3_xxs_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq3_s_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq2_s_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq1_s_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq1_m_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq4_nl_f16")]] kernel mul_mm_id kernel_mul_mm_id; -template [[host_name("kernel_mul_mm_id_iq4_xs_f16")]] kernel mul_mm_id kernel_mul_mm_id; - -// -// matrix-vector multiplication -// - -typedef void (kernel_mul_mv_disp_t)( - ggml_metal_kargs_mul_mv args, - device const char * src0, - device const char * src1, - device char * dst, - uint3 tgpig, - ushort tiisg); - -typedef void (kernel_mul_mv2_disp_t)( - ggml_metal_kargs_mul_mv args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiisg, - ushort sgitg); - -template -void mmv_fn( - ggml_metal_kargs_mul_mv args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiitg, - ushort tiisg, - ushort sgitg) { - disp_fn(args, src0, src1, dst, tgpig, tiisg); -} - -template -void mmv_fn( - ggml_metal_kargs_mul_mv args, - device const char * src0, - device const char * src1, - device char * dst, - threadgroup char * shmem, - uint3 tgpig, - ushort tiitg, - ushort tiisg, - ushort sgitg) { - disp_fn(args, src0, src1, dst, shmem, tgpig, tiisg, sgitg); -} - -typedef decltype(mmv_fn>) mul_mv_disp_fn_t; - -template -kernel void kernel_mul_mv_id( - constant ggml_metal_kargs_mul_mv_id & args, - device const char * src0s, - device const char * src1, - device char * dst, - device const char * ids, - threadgroup char * shmem [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort tiitg[[thread_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]]) { - const int iid1 = tgpig.z/args.nei0; - const int idx = tgpig.z%args.nei0; - - tgpig.z = 0; - - const int32_t i02 = ((device const int32_t *) (ids + iid1*args.nbi1))[idx]; - - const int64_t i11 = idx % args.ne11; - const int64_t i12 = iid1; - - const int64_t i1 = idx; - const int64_t i2 = i12; - - device const char * src0_cur = src0s + i02*args.nb02; - device const char * src1_cur = src1 + i11*args.nb11 + i12*args.nb12; - - device char * dst_cur = dst + (i1*args.ne0 + i2*args.ne1*args.ne0)*sizeof(float); - - ggml_metal_kargs_mul_mv args0 = { - /*.ne00 =*/ args.ne00, - /*.ne01 =*/ args.ne01, - /*.ne02 =*/ 1, // args.ne02, - /*.nb00 =*/ args.nb00, - /*.nb01 =*/ args.nb01, - /*.nb02 =*/ args.nb02, - /*.nb03 =*/ args.nb02, // args.ne02 == 1 - /*.ne10 =*/ args.ne10, - /*.ne11 =*/ 1, // args.ne11, - /*.ne12 =*/ 1, // args.ne12, - /*.nb10 =*/ args.nb10, - /*.nb11 =*/ args.nb11, - /*.nb12 =*/ args.nb12, - /*.nb13 =*/ args.nb12, // ne12 == 1 - /*.ne0 =*/ args.ne0, - /*.ne1 =*/ 1, // args.ne1, - /*.nr0 =*/ args.nr0, - /*.r2 =*/ 1, - /*.r3 =*/ 1, - }; - - disp_fn( - args0, - /* src0 */ src0_cur, - /* src1 */ src1_cur, - /* dst */ dst_cur, - shmem, - tgpig, - tiitg, - tiisg, - sgitg); -} - -typedef decltype(kernel_mul_mv_id>>) kernel_mul_mv_id_t; - -typedef decltype(kernel_mul_mv_id>>) kernel_mul_mv_id_4_t; - -template [[host_name("kernel_mul_mv_id_f32_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_f16_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_mul_mv_id_bf16_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -#endif -template [[host_name("kernel_mul_mv_id_f32_f32_4")]] kernel kernel_mul_mv_id_4_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_f16_f32_4")]] kernel kernel_mul_mv_id_4_t kernel_mul_mv_id>>; -#if defined(GGML_METAL_HAS_BF16) -template [[host_name("kernel_mul_mv_id_bf16_f32_4")]] kernel kernel_mul_mv_id_4_t kernel_mul_mv_id>>; -#endif - -template [[host_name("kernel_mul_mv_id_q8_0_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; - -template [[host_name("kernel_mul_mv_id_q1_0_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_q4_0_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_q4_1_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_q5_0_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_q5_1_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; - -template [[host_name("kernel_mul_mv_id_mxfp4_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; - -template [[host_name("kernel_mul_mv_id_q2_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_q3_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_q4_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_q5_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_q6_K_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq1_s_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq1_m_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq2_xxs_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq2_xs_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq3_xxs_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq3_s_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq2_s_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq4_nl_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; -template [[host_name("kernel_mul_mv_id_iq4_xs_f32")]] kernel kernel_mul_mv_id_t kernel_mul_mv_id>>; - -kernel void kernel_pool_2d_max_f32( - constant ggml_metal_kargs_pool_2d & args, - device const float * src0, - device float * dst, - uint gid[[thread_position_in_grid]]) { - - if (gid >= args.np) { - return; - } - - const int idx = gid; - const int I_HW = args.IH * args.IW; - const int O_HW = args.OH * args.OW; - const int nc = idx / O_HW; - const int cur_oh = idx % O_HW / args.OW; - const int cur_ow = idx % O_HW % args.OW; - - device const float * i_ptr = src0 + nc * I_HW; - device float * o_ptr = dst + nc * O_HW; - - const int start_h = cur_oh * args.s1 - args.p1; - const int bh = MAX(0, start_h); - const int eh = MIN(args.IH, start_h + args.k1); - const int start_w = cur_ow * args.s0 - args.p0; - const int bw = MAX(0, start_w); - const int ew = MIN(args.IW, start_w + args.k0); - - float res = -INFINITY; - - for (int i = bh; i < eh; i += 1) { - for (int j = bw; j < ew; j += 1) { - res = MAX(res, i_ptr[i * args.IW + j]); - } - } - - o_ptr[cur_oh * args.OW + cur_ow] = res; -} - -kernel void kernel_pool_2d_avg_f32( - constant ggml_metal_kargs_pool_2d & args, - device const float * src0, - device float * dst, - uint gid[[thread_position_in_grid]]) { - - if (gid >= args.np) { - return; - } - - const int idx = gid; - const int I_HW = args.IH * args.IW; - const int O_HW = args.OH * args.OW; - const int nc = idx / O_HW; - const int cur_oh = idx % O_HW / args.OW; - const int cur_ow = idx % O_HW % args.OW; - - device const float * i_ptr = src0 + nc * I_HW; - device float * o_ptr = dst + nc * O_HW; - - const int start_h = cur_oh * args.s1 - args.p1; - const int bh = MAX(0, start_h); - const int eh = MIN(args.IH, start_h + args.k1); - const int start_w = cur_ow * args.s0 - args.p0; - const int bw = MAX(0, start_w); - const int ew = MIN(args.IW, start_w + args.k0); - // const float scale = 1. / ((eh - bh) * (ew - bw)); - const float scale = 1. / (args.k0 * args.k1); - - float res = 0; - - for (int i = bh; i < eh; i += 1) { - for (int j = bw; j < ew; j += 1) { - float cur = i_ptr[i * args.IW + j]; - res += cur * scale; - } - } - - o_ptr[cur_oh * args.OW + cur_ow] = res; -} - - -kernel void kernel_pool_1d_max_f32( - constant ggml_metal_kargs_pool_1d & args, - device const float * src, - device float * dst, - uint gid [[thread_position_in_grid]] -) { - - if (gid >= args.np) { - return; - } - - const int ow = (int)gid % args.OW; - const int row = (int)gid / args.OW; - - const int base = ow * args.s0 - args.p0; - - float acc = -INFINITY; - - const int src_off = row * args.IW; - const int dst_off = row * args.OW; - - for (int ki = 0; ki < args.k0; ++ki) { - int j = base + ki; - if (j < 0 || j >= args.IW){ - continue; - } - float v = src[src_off + j]; - acc = max(acc, v); - } - - dst[dst_off + ow] = acc; -} - -kernel void kernel_pool_1d_avg_f32( - constant ggml_metal_kargs_pool_1d & args, - device const float * src, - device float * dst, - uint gid [[thread_position_in_grid]] -) { - - if (gid >= args.np) { - return; - } - - const int ow = (int)gid % args.OW; - const int row = (int)gid / args.OW; - - const int base = ow * args.s0 - args.p0; - - float acc = 0.0f; - int cnt = 0; - - const int src_off = row * args.IW; - const int dst_off = row * args.OW; - - for (int ki = 0; ki < args.k0; ++ki) { - const int j = base + ki; - if (j < 0 || j >= args.IW) { - continue; - } - acc += src[src_off + j]; - cnt += 1; - } - - dst[dst_off + ow] = (cnt > 0) ? (acc / (float)cnt) : 0.0f; -} - -kernel void kernel_opt_step_adamw_f32( - constant ggml_metal_kargs_opt_step_adamw & args, - device float * x, - device const float * g, - device float * g_m, - device float * g_v, - device const float * pars, - uint gid[[thread_position_in_grid]]) { - - if (gid >= args.np) { - return; - } - - const float alpha = pars[0]; - const float beta1 = pars[1]; - const float beta2 = pars[2]; - const float eps = pars[3]; - const float wd = pars[4]; - const float beta1h = pars[5]; - const float beta2h = pars[6]; - - const float gi = g[gid]; - const float gmi = g_m[gid] * beta1 + gi * (1.0f - beta1); - const float gvi = g_v[gid] * beta2 + gi * gi * (1.0f - beta2); - - g_m[gid] = gmi; - g_v[gid] = gvi; - - const float mh = gmi * beta1h; - const float vh = sqrt(gvi * beta2h) + eps; - - x[gid] = x[gid] * (1.0f - alpha * wd) - alpha * mh / vh; -} - -kernel void kernel_opt_step_sgd_f32( - constant ggml_metal_kargs_opt_step_sgd & args, - device float * x, - device const float * g, - device const float * pars, - uint gid[[thread_position_in_grid]]) { - - if (gid >= args.np) { - return; - } - - x[gid] = x[gid] * (1.0f - pars[0] * pars[1]) - pars[0] * g[gid]; -} - -template -kernel void kernel_memset( - constant ggml_metal_kargs_memset & args, - device T * dst, - uint tpig[[thread_position_in_grid]]) { - dst[tpig] = args.val; -} - -typedef decltype(kernel_memset) kernel_memset_t; - -template [[host_name("kernel_memset_i64")]] kernel kernel_memset_t kernel_memset; - -constant short FC_count_equal_nsg [[function_constant(FC_COUNT_EQUAL + 0)]]; - -template -kernel void kernel_count_equal( - constant ggml_metal_kargs_count_equal & args, - device const char * src0, - device const char * src1, - device atomic_int * dst, - threadgroup int32_t * shmem_i32 [[threadgroup(0)]], - uint3 tgpig[[threadgroup_position_in_grid]], - ushort3 tpitg[[thread_position_in_threadgroup]], - ushort sgitg[[simdgroup_index_in_threadgroup]], - ushort tiisg[[thread_index_in_simdgroup]], - ushort3 ntg[[threads_per_threadgroup]]) { - const short NSG = FC_count_equal_nsg; - - const int i3 = tgpig.z; - const int i2 = tgpig.y; - const int i1 = tgpig.x; - - if (i3 >= args.ne03 || i2 >= args.ne02 || i1 >= args.ne01) { - return; - } - - int sum = 0; - - device const char * base0 = src0 + i1*args.nb01 + i2*args.nb02 + i3*args.nb03; - device const char * base1 = src1 + i1*args.nb11 + i2*args.nb12 + i3*args.nb13; - - for (int64_t i0 = tpitg.x; i0 < args.ne00; i0 += ntg.x) { - const T v0 = *(device const T *)(base0 + i0*args.nb00); - const T v1 = *(device const T *)(base1 + i0*args.nb10); - sum += (v0 == v1); - } - - sum = simd_sum(sum); - - if (tiisg == 0) { - shmem_i32[sgitg] = sum; - } - - threadgroup_barrier(mem_flags::mem_threadgroup); - - if (sgitg == 0) { - float v = 0.0f; - if (tpitg.x < NSG) { - v = shmem_i32[tpitg.x]; - } - - float total = simd_sum(v); - if (tpitg.x == 0) { - atomic_fetch_add_explicit(dst, (int32_t) total, memory_order_relaxed); - } - } -} - -typedef decltype(kernel_count_equal) kernel_count_equal_t; - -template [[host_name("kernel_count_equal_i32")]] kernel kernel_count_equal_t kernel_count_equal; +typedef decltype(kernel_count_equal) kernel_count_equal_t; + +template [[host_name("kernel_count_equal_i32")]] kernel kernel_count_equal_t kernel_count_equal; diff --git a/external/ggml/src/ggml-quants.c b/external/ggml/src/ggml-quants.c index 51f08fa3b..facdc2473 100644 --- a/external/ggml/src/ggml-quants.c +++ b/external/ggml/src/ggml-quants.c @@ -2407,6 +2407,163 @@ void dequantize_row_tq2_0(const block_tq2_0 * GGML_RESTRICT x, float * GGML_REST } } +// ====================== GGML_TYPE_I8_S / GGML_TYPE_I2_S +// +// Unlike every block-quantized type above, these two carry a single F32 scale +// for the whole tensor rather than one per block, stored immediately after the +// payload (ggml_type_extra_bytes reserves the room). The four functions below +// are therefore whole-tensor: `n` is ggml_nelements(), and the scale lives at +// byte offset ggml_row_size(type, n) rounded down to the payload end. +// +// The scale is a multiplier in both directions -- real = q * scale -- matching +// the `d` of every other ggml quant type. VibeASR's own fork uses a multiplier +// for weights but stores the reciprocal for op-produced activations; the two +// are reconciled by a division deep inside each kernel. Only the multiplier +// convention is used here. Activation scales never reach a file, so this +// changes nothing about how an existing GGUF is read. + +#define I2_S_GROUP 128 // ternary values per packed group +#define I2_S_BYTES 32 // bytes those 128 values occupy + +// Scale slot: the F32 sits directly after the payload. Kept as helpers so the +// offset arithmetic is not repeated in four places. The payload size is the +// only difference between the two types -- I8_S is one byte per value, I2_S +// packs 128 values into 32 bytes. +static inline size_t i8_s_payload_bytes(int64_t n) { return (size_t)n; } +static inline size_t i2_s_payload_bytes(int64_t n) { return (size_t)(n / I2_S_GROUP) * I2_S_BYTES; } + +static inline float * i8_s_scale_ptr (void * x, int64_t n) { return (float *)((char *)x + i8_s_payload_bytes(n)); } +static inline const float * i8_s_scale_ptr_const(const void * x, int64_t n) { return (const float *)((const char *)x + i8_s_payload_bytes(n)); } +static inline float * i2_s_scale_ptr (void * x, int64_t n) { return (float *)((char *)x + i2_s_payload_bytes(n)); } +static inline const float * i2_s_scale_ptr_const(const void * x, int64_t n) { return (const float *)((const char *)x + i2_s_payload_bytes(n)); } + +void ggml_i8_s_to_float(const void * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t n) { + const int8_t * q = (const int8_t *)x; + const float d = *i8_s_scale_ptr_const(x, n); + + for (int64_t i = 0; i < n; ++i) { + y[i] = (float)q[i] * d; + } +} + +size_t ggml_i8_s_from_float(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t n) { + int8_t * q = (int8_t *)y; + + float amax = 0.0f; + for (int64_t i = 0; i < n; ++i) { + const float a = fabsf(x[i]); + if (a > amax) amax = a; + } + + // Symmetric range: -127..127 rather than -128..127, so that negating a + // tensor is exactly representable and the kernels can widen to int16 + // without a special case for the one asymmetric value. + const float d = amax / 127.0f; + const float id = d != 0.0f ? 1.0f / d : 0.0f; + + for (int64_t i = 0; i < n; ++i) { + int v = nearest_int(x[i] * id); + if (v > 127) v = 127; + if (v < -127) v = -127; + q[i] = (int8_t)v; + } + + *i8_s_scale_ptr(y, n) = d; + + return i8_s_payload_bytes(n) + ggml_type_extra_bytes(GGML_TYPE_I8_S); +} + +// Bit pair -> ternary value. Codes 1 and 3 both mean zero; the packer only ever +// emits 1, but 3 is accepted so a stray pattern dequantizes to 0 instead of +// reading out of bounds. +static const float i2_s_map[4] = { -1.0f, 0.0f, +1.0f, 0.0f }; + +void ggml_i2_s_to_float(const void * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t n) { + assert(n % I2_S_GROUP == 0); + + const uint8_t * q = (const uint8_t *)x; + const float d = *i2_s_scale_ptr_const(x, n); + + for (int64_t base = 0; base < n; base += I2_S_GROUP) { + // Byte gp holds the values at base+gp, base+32+gp, base+64+gp and + // base+96+gp, in bit pairs 6, 4, 2, 0 -- strided, not consecutive, so + // that a SIMD load of 32 bytes yields 4 aligned lanes of 32 values. + for (int gp = 0; gp < I2_S_BYTES; ++gp) { + const uint8_t b = q[gp]; + + y[base + 0 + gp] = d * i2_s_map[(b >> 6) & 3]; + y[base + 32 + gp] = d * i2_s_map[(b >> 4) & 3]; + y[base + 64 + gp] = d * i2_s_map[(b >> 2) & 3]; + y[base + 96 + gp] = d * i2_s_map[(b >> 0) & 3]; + } + + q += I2_S_BYTES; + } +} + +size_t ggml_i2_s_from_float(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t n) { + assert(n % I2_S_GROUP == 0); + + uint8_t * q = (uint8_t *)y; + + float amax = 0.0f; + for (int64_t i = 0; i < n; ++i) { + const float a = fabsf(x[i]); + if (a > amax) amax = a; + } + + // The type stores {-1, 0, +1} * d, so d is the absmax and a ternary input + // round-trips exactly. Non-ternary input is rounded to the nearest of the + // three representable values, which is all this type can express. + const float d = amax; + const float id = d != 0.0f ? 1.0f / d : 0.0f; + + memset(q, 0, i2_s_payload_bytes(n)); + + for (int64_t base = 0; base < n; base += I2_S_GROUP) { + for (int j = 0; j < I2_S_GROUP; ++j) { + int v = nearest_int(x[base + j] * id); + if (v > 1) v = 1; + if (v < -1) v = -1; + + const uint8_t code = (uint8_t)(v + 1); // -1,0,1 -> 0,1,2 + + q[j % I2_S_BYTES] |= (uint8_t)(code << (6 - 2*(j / I2_S_BYTES))); + } + + q += I2_S_BYTES; + } + + *i2_s_scale_ptr(y, n) = d; + + return i2_s_payload_bytes(n) + ggml_type_extra_bytes(GGML_TYPE_I2_S); +} + +void ggml_i8_s_quantize_act(const float * GGML_RESTRICT x, int8_t * GGML_RESTRICT q, int64_t n, + float * GGML_RESTRICT scale, int32_t * GGML_RESTRICT sum) { + float amax = 0.0f; + for (int64_t i = 0; i < n; ++i) { + const float a = fabsf(x[i]); + if (a > amax) amax = a; + } + + // -127..127 rather than -128..127, as in ggml_i8_s_from_float. + const float d = amax / 127.0f; + const float id = d != 0.0f ? 1.0f / d : 0.0f; + + int32_t s = 0; + for (int64_t i = 0; i < n; ++i) { + int v = nearest_int(x[i] * id); + if (v > 127) v = 127; + if (v < -127) v = -127; + q[i] = (int8_t)v; + s += v; + } + + *scale = d; + *sum = s; +} + // ====================== "True" 2-bit (de)-quantization void dequantize_row_iq2_xxs(const block_iq2_xxs * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k) { diff --git a/external/ggml/src/ggml-quants.h b/external/ggml/src/ggml-quants.h index 3fb6140b7..cda127695 100644 --- a/external/ggml/src/ggml-quants.h +++ b/external/ggml/src/ggml-quants.h @@ -102,6 +102,36 @@ GGML_API size_t quantize_q8_0(const float * GGML_RESTRICT src, void * GGML_RESTR GGML_API size_t quantize_mxfp4(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix); GGML_API size_t quantize_nvfp4(const float * GGML_RESTRICT src, void * GGML_RESTRICT dst, int64_t nrows, int64_t n_per_row, const float * imatrix); +// GGML_TYPE_I8_S / GGML_TYPE_I2_S conversions. +// +// These are whole-tensor, not per-row: both types carry a single F32 scale for +// the entire tensor, stored immediately after the payload (see +// ggml_type_extra_bytes), so `n` is ggml_nelements() and a row pointer alone +// cannot locate the scale. That is also why the two traits entries leave +// .to_float / .from_float_ref NULL instead of pointing here, and why +// ggml_quantize_chunk does not list these types -- its +// `result == nrows * row_size` invariant cannot hold for a per-tensor scale. +// +// The from_float direction returns the number of bytes written, payload plus +// the padded scale. +GGML_API void ggml_i8_s_to_float (const void * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t n); +GGML_API size_t ggml_i8_s_from_float(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t n); +GGML_API void ggml_i2_s_to_float (const void * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t n); +GGML_API size_t ggml_i2_s_from_float(const float * GGML_RESTRICT x, void * GGML_RESTRICT y, int64_t n); + +// Activation quantizer for the I2_S matmul: one row in, int8 payload out, with +// the scale and the row's int8 sum handed back out of band. +// +// Out of band because neither fits the in-band convention above. The scale is +// per row here, not per tensor, since a language model activation row is a +// single token and its dynamic range has nothing to do with its neighbours'. +// The sum is needed because I2_S stores the ternary values as the codes +// {0, 1, 2} rather than {-1, 0, +1}: an integer dot against the codes gives +// sum(w*q) + sum(q), so the row sum has to be subtracted back out. Computing it +// here costs nothing -- the values are already in registers. +GGML_API void ggml_i8_s_quantize_act(const float * GGML_RESTRICT x, int8_t * GGML_RESTRICT q, int64_t n, + float * GGML_RESTRICT scale, int32_t * GGML_RESTRICT sum); + GGML_API void iq2xs_init_impl(enum ggml_type type); GGML_API void iq2xs_free_impl(enum ggml_type type); GGML_API void iq3xs_init_impl(int grid_size); diff --git a/external/ggml/src/ggml-vulkan/ggml-vulkan.cpp b/external/ggml/src/ggml-vulkan/ggml-vulkan.cpp index 334651a11..120f484e5 100644 --- a/external/ggml/src/ggml-vulkan/ggml-vulkan.cpp +++ b/external/ggml/src/ggml-vulkan/ggml-vulkan.cpp @@ -774,8 +774,8 @@ struct vk_device_struct { vk_pipeline pipeline_pad_reflect_1d_f32; vk_pipeline pipeline_roll_f32; vk_pipeline pipeline_repeat_f32, pipeline_repeat_back_f32; - vk_pipeline pipeline_cpy_f32_f32, pipeline_cpy_f32_f16, pipeline_cpy_f16_f16, pipeline_cpy_f16_f32, pipeline_cpy_f32_bf16, pipeline_cpy_f32_i32, pipeline_cpy_i32_f32; - vk_pipeline pipeline_contig_cpy_f32_f32, pipeline_contig_cpy_f32_f16, pipeline_contig_cpy_f16_f16, pipeline_contig_cpy_f16_f32, pipeline_contig_cpy_f32_bf16, pipeline_contig_cpy_f32_i32, pipeline_contig_cpy_i32_f32; + vk_pipeline pipeline_cpy_f32_f32, pipeline_cpy_f32_f16, pipeline_cpy_f16_f16, pipeline_cpy_f16_f32, pipeline_cpy_f32_bf16, pipeline_cpy_bf16_f32, pipeline_cpy_f16_bf16, pipeline_cpy_bf16_f16, pipeline_cpy_f32_i32, pipeline_cpy_i32_f32; + vk_pipeline pipeline_contig_cpy_f32_f32, pipeline_contig_cpy_f32_f16, pipeline_contig_cpy_f16_f16, pipeline_contig_cpy_f16_f32, pipeline_contig_cpy_f32_bf16, pipeline_contig_cpy_bf16_f32, pipeline_contig_cpy_f16_bf16, pipeline_contig_cpy_bf16_f16, pipeline_contig_cpy_f32_i32, pipeline_contig_cpy_i32_f32; vk_pipeline pipeline_cpy_f32_quant[GGML_TYPE_COUNT]; vk_pipeline pipeline_cpy_quant_f32[GGML_TYPE_COUNT]; vk_pipeline pipeline_cpy_transpose_16, pipeline_cpy_transpose_32; @@ -811,6 +811,8 @@ struct vk_device_struct { vk_pipeline pipeline_softplus[2]; vk_pipeline pipeline_step[2]; vk_pipeline pipeline_round[2]; + vk_pipeline pipeline_round_bf16[3]; + vk_pipeline pipeline_round_bf16_strided[3]; vk_pipeline pipeline_ceil[2]; vk_pipeline pipeline_floor[2]; vk_pipeline pipeline_trunc[2]; @@ -4594,7 +4596,10 @@ static void ggml_vk_load_shaders(vk_device& device) { ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_f16, "cpy_f32_f16", cpy_f32_f16_len, cpy_f32_f16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_cpy_f16_f16, "cpy_f16_f16", cpy_f16_f16_len, cpy_f16_f16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_cpy_f16_f32, "cpy_f16_f32", cpy_f16_f32_len, cpy_f16_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); - ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_bf16,"cpy_f32_bf16",cpy_f32_bf16_len,cpy_f32_bf16_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_bf16,"cpy_f32_bf16",cpy_f32_bf16_len,cpy_f32_bf16_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_cpy_bf16_f32,"cpy_bf16_f32",cpy_bf16_f32_len,cpy_bf16_f32_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_cpy_f16_bf16,"cpy_f16_bf16",cpy_f16_bf16_len,cpy_f16_bf16_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_cpy_bf16_f16,"cpy_bf16_f16",cpy_bf16_f16_len,cpy_bf16_f16_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_cpy_i32_f32, "cpy_i32_f32", cpy_i32_f32_len, cpy_i32_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_cpy_f32_i32, "cpy_f32_i32", cpy_f32_i32_len, cpy_f32_i32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); @@ -4602,7 +4607,10 @@ static void ggml_vk_load_shaders(vk_device& device) { ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_f32_f16, "contig_cpy_f32_f16", contig_cpy_f32_f16_len, contig_cpy_f32_f16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_f16_f16, "contig_cpy_f16_f16", contig_cpy_f16_f16_len, contig_cpy_f16_f16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_f16_f32, "contig_cpy_f16_f32", contig_cpy_f16_f32_len, contig_cpy_f16_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); - ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_f32_bf16,"contig_cpy_f32_bf16",contig_cpy_f32_bf16_len,contig_cpy_f32_bf16_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_f32_bf16,"contig_cpy_f32_bf16",contig_cpy_f32_bf16_len,contig_cpy_f32_bf16_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_bf16_f32,"contig_cpy_bf16_f32",contig_cpy_bf16_f32_len,contig_cpy_bf16_f32_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_f16_bf16,"contig_cpy_f16_bf16",contig_cpy_f16_bf16_len,contig_cpy_f16_bf16_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_bf16_f16,"contig_cpy_bf16_f16",contig_cpy_bf16_f16_len,contig_cpy_bf16_f16_data,"main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_i32_f32, "contig_cpy_i32_f32", contig_cpy_i32_f32_len, contig_cpy_i32_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_contig_cpy_f32_i32, "contig_cpy_f32_i32", contig_cpy_f32_i32_len, contig_cpy_f32_i32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); @@ -4744,6 +4752,15 @@ static void ggml_vk_load_shaders(vk_device& device) { CREATE_UNARY(exp) #undef CREATE_UNARY + // round-to-bf16: f32/f16/bf16 in, always f32 out (index by src type). + ggml_vk_create_pipeline(device, device->pipeline_round_bf16[0], "round_bf16_f32", round_bf16_f32_len, round_bf16_f32_data, "main", 2, sizeof(vk_op_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_round_bf16[1], "round_bf16_f16", round_bf16_f16_len, round_bf16_f16_data, "main", 2, sizeof(vk_op_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_round_bf16[2], "round_bf16_bf16", round_bf16_bf16_len, round_bf16_bf16_data, "main", 2, sizeof(vk_op_push_constants), {512, 1, 1}, {}, 1); + // strided variant for non-contiguous (e.g. row-strided view) inputs. + ggml_vk_create_pipeline(device, device->pipeline_round_bf16_strided[0], "round_bf16_strided_f32", round_bf16_strided_f32_len, round_bf16_strided_f32_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_round_bf16_strided[1], "round_bf16_strided_f16", round_bf16_strided_f16_len, round_bf16_strided_f16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_round_bf16_strided[2], "round_bf16_strided_bf16", round_bf16_strided_bf16_len, round_bf16_strided_bf16_data, "main", 2, sizeof(vk_op_unary_push_constants), {512, 1, 1}, {}, 1); + ggml_vk_create_pipeline(device, device->pipeline_add1_f16_f16, "add1_f16_f16", add1_f16_f16_len, add1_f16_f16_data, "main", 3, sizeof(vk_op_binary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_add1_f16_f32, "add1_f16_f32", add1_f16_f32_len, add1_f16_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {512, 1, 1}, {}, 1); ggml_vk_create_pipeline(device, device->pipeline_add1_f32_f32, "add1_f32_f32", add1_f32_f32_len, add1_f32_f32_data, "main", 3, sizeof(vk_op_binary_push_constants), {512, 1, 1}, {}, 1); @@ -7578,6 +7595,27 @@ static vk_pipeline ggml_vk_get_cpy_pipeline(ggml_backend_vk_context * ctx, const return ctx->device->pipeline_cpy_f32_bf16; } } + if (src->type == GGML_TYPE_BF16 && to == GGML_TYPE_F32) { + if (contig) { + return ctx->device->pipeline_contig_cpy_bf16_f32; + } else { + return ctx->device->pipeline_cpy_bf16_f32; + } + } + if (src->type == GGML_TYPE_F16 && to == GGML_TYPE_BF16) { + if (contig) { + return ctx->device->pipeline_contig_cpy_f16_bf16; + } else { + return ctx->device->pipeline_cpy_f16_bf16; + } + } + if (src->type == GGML_TYPE_BF16 && to == GGML_TYPE_F16) { + if (contig) { + return ctx->device->pipeline_contig_cpy_bf16_f16; + } else { + return ctx->device->pipeline_cpy_bf16_f16; + } + } if (src->type == GGML_TYPE_F32 && to == GGML_TYPE_I32) { if (contig) { return ctx->device->pipeline_contig_cpy_f32_i32; @@ -9713,6 +9751,19 @@ static vk_pipeline ggml_vk_op_get_pipeline(ggml_backend_vk_context * ctx, const } return nullptr; case GGML_OP_UNARY: + // ROUND_BF16 widens to f32: src may be f32/f16/bf16 while dst is f32. + if (ggml_get_unary_op(dst) == GGML_UNARY_OP_ROUND_BF16) { + if (dst->type != GGML_TYPE_F32) { + return nullptr; + } + const bool strided = !ggml_is_contiguous(src0) || !ggml_is_contiguous(dst); + switch (src0->type) { + case GGML_TYPE_F32: return strided ? ctx->device->pipeline_round_bf16_strided[0] : ctx->device->pipeline_round_bf16[0]; + case GGML_TYPE_F16: return strided ? ctx->device->pipeline_round_bf16_strided[1] : ctx->device->pipeline_round_bf16[1]; + case GGML_TYPE_BF16: return strided ? ctx->device->pipeline_round_bf16_strided[2] : ctx->device->pipeline_round_bf16[2]; + default: return nullptr; + } + } if ((src0->type != GGML_TYPE_F32 && src0->type != GGML_TYPE_F16) || (dst->type != GGML_TYPE_F32 && dst->type != GGML_TYPE_F16) || (src0->type != dst->type)) { @@ -11454,6 +11505,11 @@ static void ggml_vk_sigmoid_strided(ggml_backend_vk_context * ctx, vk_context& s ggml_vk_op_f32(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_UNARY, std::move(p)); } +static void ggml_vk_round_bf16_strided(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) { + vk_op_unary_push_constants p = vk_op_unary_push_constants_init(src0, dst); + ggml_vk_op_f32(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_UNARY, std::move(p)); +} + static void ggml_vk_xielu(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) { float * op_params = (float *)dst->op_params; ggml_vk_op_f32(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_UNARY, @@ -13495,6 +13551,13 @@ static bool ggml_vk_build_graph(ggml_backend_vk_context * ctx, ggml_cgraph * cgr case GGML_UNARY_OP_SGN: ggml_vk_unary(ctx, compute_ctx, src0, node); break; + case GGML_UNARY_OP_ROUND_BF16: + if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(node)) { + ggml_vk_round_bf16_strided(ctx, compute_ctx, src0, node); + break; + } + ggml_vk_unary(ctx, compute_ctx, src0, node); + break; case GGML_UNARY_OP_SIGMOID: if (!ggml_is_contiguous(src0) || !ggml_is_contiguous(node)) { ggml_vk_sigmoid_strided(ctx, compute_ctx, src0, node); @@ -15745,6 +15808,9 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16) && (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && (op->src[0]->type == op->type); + case GGML_UNARY_OP_ROUND_BF16: + return (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16 || op->src[0]->type == GGML_TYPE_BF16) && + (op->type == GGML_TYPE_F32); case GGML_UNARY_OP_SIGMOID: return (op->src[0]->type == GGML_TYPE_F32 || op->src[0]->type == GGML_TYPE_F16) && (op->type == GGML_TYPE_F32 || op->type == GGML_TYPE_F16) && diff --git a/external/ggml/src/ggml-vulkan/vulkan-shaders/contig_copy.comp b/external/ggml/src/ggml-vulkan/vulkan-shaders/contig_copy.comp index 066b27ca0..cacddbdf7 100644 --- a/external/ggml/src/ggml-vulkan/vulkan-shaders/contig_copy.comp +++ b/external/ggml/src/ggml-vulkan/vulkan-shaders/contig_copy.comp @@ -19,7 +19,10 @@ void main() { if (idx + (num_iter-1)*num_threads < p.ne) { [[unroll]] for (uint i = 0; i < num_iter; ++i) { -#if defined(DATA_D_BF16) +#if defined(DATA_A_BF16) + float f = bf16_to_fp32(uint32_t(data_a[get_aoffset() + idx])); + data_d[get_doffset() + idx] = D_TYPE(f); +#elif defined(DATA_D_BF16) float f = float(data_a[get_aoffset() + idx]); data_d[get_doffset() + idx] = D_TYPE(fp32_to_bf16(f)); #elif !defined(OPTIMIZATION_ERROR_WORKAROUND) @@ -35,7 +38,10 @@ void main() { continue; } -#if defined(DATA_D_BF16) +#if defined(DATA_A_BF16) + float f = bf16_to_fp32(uint32_t(data_a[get_aoffset() + idx])); + data_d[get_doffset() + idx] = D_TYPE(f); +#elif defined(DATA_D_BF16) float f = float(data_a[get_aoffset() + idx]); data_d[get_doffset() + idx] = D_TYPE(fp32_to_bf16(f)); #elif !defined(OPTIMIZATION_ERROR_WORKAROUND) diff --git a/external/ggml/src/ggml-vulkan/vulkan-shaders/copy.comp b/external/ggml/src/ggml-vulkan/vulkan-shaders/copy.comp index a1ba96702..81cce864e 100644 --- a/external/ggml/src/ggml-vulkan/vulkan-shaders/copy.comp +++ b/external/ggml/src/ggml-vulkan/vulkan-shaders/copy.comp @@ -12,7 +12,10 @@ void main() { return; } -#if defined(DATA_D_BF16) +#if defined(DATA_A_BF16) + float f = bf16_to_fp32(uint32_t(data_a[get_aoffset() + src0_idx(idx)])); + data_d[get_doffset() + dst_idx(idx)] = D_TYPE(f); +#elif defined(DATA_D_BF16) float f = float(data_a[get_aoffset() + src0_idx(idx)]); data_d[get_doffset() + dst_idx(idx)] = D_TYPE(fp32_to_bf16(f)); #elif !defined(OPTIMIZATION_ERROR_WORKAROUND) diff --git a/external/ggml/src/ggml-vulkan/vulkan-shaders/round_bf16.comp b/external/ggml/src/ggml-vulkan/vulkan-shaders/round_bf16.comp new file mode 100644 index 000000000..b92937bec --- /dev/null +++ b/external/ggml/src/ggml-vulkan/vulkan-shaders/round_bf16.comp @@ -0,0 +1,26 @@ +#version 450 + +#include "generic_head.glsl" +#include "types.glsl" + +layout(local_size_x = 512, local_size_y = 1, local_size_z = 1) in; + +layout (binding = 0) readonly buffer X {A_TYPE data_a[];}; +layout (binding = 1) writeonly buffer D {D_TYPE data_d[];}; + +void main() { + const uint i = gl_GlobalInvocationID.z * 262144 + gl_GlobalInvocationID.y * 512 + gl_GlobalInvocationID.x; + + if (i >= p.KX) { + return; + } + +#if defined(DATA_A_BF16) + const float x = bf16_to_fp32(uint32_t(data_a[i])); +#else + const float x = float(data_a[i]); +#endif + // Round to bf16 precision and widen back to f32, matching the + // f32 -> bf16 -> f32 cast round trip. + data_d[i] = D_TYPE(bf16_to_fp32(fp32_to_bf16(x))); +} diff --git a/external/ggml/src/ggml-vulkan/vulkan-shaders/round_bf16_strided.comp b/external/ggml/src/ggml-vulkan/vulkan-shaders/round_bf16_strided.comp new file mode 100644 index 000000000..d5eaa5087 --- /dev/null +++ b/external/ggml/src/ggml-vulkan/vulkan-shaders/round_bf16_strided.comp @@ -0,0 +1,23 @@ +#version 450 + +#include "types.glsl" +#include "generic_unary_head.glsl" + +layout(local_size_x = 512, local_size_y = 1, local_size_z = 1) in; + +void main() { + const uint idx = get_idx(); + + if (idx >= p.ne) { + return; + } + +#if defined(DATA_A_BF16) + const float x = bf16_to_fp32(uint32_t(data_a[get_aoffset() + src0_idx(idx)])); +#else + const float x = float(data_a[get_aoffset() + src0_idx(idx)]); +#endif + // Round to bf16 precision and widen back to f32, matching the + // f32 -> bf16 -> f32 cast round trip. + data_d[get_doffset() + dst_idx(idx)] = D_TYPE(bf16_to_fp32(fp32_to_bf16(x))); +} diff --git a/external/ggml/src/ggml-vulkan/vulkan-shaders/vulkan-shaders-gen.cpp b/external/ggml/src/ggml-vulkan/vulkan-shaders/vulkan-shaders-gen.cpp index 7295ef107..40c223470 100644 --- a/external/ggml/src/ggml-vulkan/vulkan-shaders/vulkan-shaders-gen.cpp +++ b/external/ggml/src/ggml-vulkan/vulkan-shaders/vulkan-shaders-gen.cpp @@ -730,14 +730,20 @@ void process_shaders() { string_to_spv("cpy_f32_f16", "copy.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float16_t"}}); string_to_spv("cpy_f16_f16", "copy.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float16_t"}, {"OPTIMIZATION_ERROR_WORKAROUND", "1"}}); string_to_spv("cpy_f16_f32", "copy.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float"}, {"OPTIMIZATION_ERROR_WORKAROUND", "1"}}); - string_to_spv("cpy_f32_bf16","copy.comp", {{"A_TYPE", "float"}, {"D_TYPE", "uint16_t"}, {"DATA_D_BF16", "1"}}); + string_to_spv("cpy_f32_bf16","copy.comp", {{"A_TYPE", "float"}, {"D_TYPE", "uint16_t"}, {"DATA_D_BF16", "1"}}); + string_to_spv("cpy_bf16_f32","copy.comp", {{"A_TYPE", "uint16_t"}, {"D_TYPE", "float"}, {"DATA_A_BF16", "1"}}); + string_to_spv("cpy_f16_bf16","copy.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "uint16_t"}, {"DATA_D_BF16", "1"}}); + string_to_spv("cpy_bf16_f16","copy.comp", {{"A_TYPE", "uint16_t"}, {"D_TYPE", "float16_t"}, {"DATA_A_BF16", "1"}}); string_to_spv("contig_cpy_f32_f32", "contig_copy.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}}); string_to_spv("contig_cpy_f32_i32", "contig_copy.comp", {{"A_TYPE", "float"}, {"D_TYPE", "int"}}); string_to_spv("contig_cpy_i32_f32", "contig_copy.comp", {{"A_TYPE", "int"}, {"D_TYPE", "float"}}); string_to_spv("contig_cpy_f32_f16", "contig_copy.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float16_t"}}); string_to_spv("contig_cpy_f16_f16", "contig_copy.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float16_t"}, {"OPTIMIZATION_ERROR_WORKAROUND", "1"}}); string_to_spv("contig_cpy_f16_f32", "contig_copy.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float"}, {"OPTIMIZATION_ERROR_WORKAROUND", "1"}}); - string_to_spv("contig_cpy_f32_bf16","contig_copy.comp",{{"A_TYPE", "float"}, {"D_TYPE", "uint16_t"}, {"DATA_D_BF16", "1"}}); + string_to_spv("contig_cpy_f32_bf16","contig_copy.comp",{{"A_TYPE", "float"}, {"D_TYPE", "uint16_t"}, {"DATA_D_BF16", "1"}}); + string_to_spv("contig_cpy_bf16_f32","contig_copy.comp",{{"A_TYPE", "uint16_t"}, {"D_TYPE", "float"}, {"DATA_A_BF16", "1"}}); + string_to_spv("contig_cpy_f16_bf16","contig_copy.comp",{{"A_TYPE", "float16_t"}, {"D_TYPE", "uint16_t"}, {"DATA_D_BF16", "1"}}); + string_to_spv("contig_cpy_bf16_f16","contig_copy.comp",{{"A_TYPE", "uint16_t"}, {"D_TYPE", "float16_t"}, {"DATA_A_BF16", "1"}}); string_to_spv("cpy_f32_i32", "copy.comp", {{"A_TYPE", "float"}, {"D_TYPE", "int"}}); string_to_spv("cpy_i32_f32", "copy.comp", {{"A_TYPE", "int"}, {"D_TYPE", "float"}}); @@ -874,6 +880,12 @@ void process_shaders() { string_to_spv("step_f32", "step.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}}); string_to_spv("round_f16", "round.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float16_t"}}); string_to_spv("round_f32", "round.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}}); + string_to_spv("round_bf16_f32", "round_bf16.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}}); + string_to_spv("round_bf16_f16", "round_bf16.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float"}}); + string_to_spv("round_bf16_bf16", "round_bf16.comp", {{"A_TYPE", "uint16_t"}, {"D_TYPE", "float"}, {"DATA_A_BF16", "1"}}); + string_to_spv("round_bf16_strided_f32", "round_bf16_strided.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}}); + string_to_spv("round_bf16_strided_f16", "round_bf16_strided.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float"}}); + string_to_spv("round_bf16_strided_bf16", "round_bf16_strided.comp", {{"A_TYPE", "uint16_t"}, {"D_TYPE", "float"}, {"DATA_A_BF16", "1"}}); string_to_spv("ceil_f16", "ceil.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float16_t"}}); string_to_spv("ceil_f32", "ceil.comp", {{"A_TYPE", "float"}, {"D_TYPE", "float"}}); string_to_spv("floor_f16", "floor.comp", {{"A_TYPE", "float16_t"}, {"D_TYPE", "float16_t"}}); diff --git a/external/ggml/src/ggml.c b/external/ggml/src/ggml.c index 0ba560368..4cdf12448 100644 --- a/external/ggml/src/ggml.c +++ b/external/ggml/src/ggml.c @@ -1,1031 +1,1072 @@ -#define _CRT_SECURE_NO_DEPRECATE // Disables "unsafe" warnings on Windows -#define _USE_MATH_DEFINES // For M_PI on MSVC - -#include "ggml-backend.h" -#include "ggml-impl.h" -#include "ggml-threading.h" -#include "ggml-cpu.h" -#include "ggml.h" - -// FIXME: required here for quantization functions -#include "ggml-quants.h" - -#ifdef GGML_USE_CPU_HBM -#include -#endif - -#if defined(_MSC_VER) || defined(__MINGW32__) -#include // using malloc.h with MSC/MINGW -#elif !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) -#include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(__gnu_linux__) -#include -#endif - -#if defined(__APPLE__) -#include -#include -#include -#endif - -#if defined(_WIN32) -#define WIN32_LEAN_AND_MEAN -#ifndef NOMINMAX - #define NOMINMAX -#endif -#include -#endif - -#define UNUSED GGML_UNUSED - -uint64_t ggml_graph_next_uid(void) { -#ifdef _MSC_VER -#if defined(_WIN32) - static volatile LONG counter = 1; - return (uint64_t) InterlockedIncrement(&counter) - 1; -#else - static volatile long long counter = 1; - return (uint64_t) _InterlockedIncrement64(&counter) - 1; -#endif -#else - static uint64_t counter = 1; - return __atomic_fetch_add(&counter, 1, __ATOMIC_RELAXED); -#endif -} - -// Needed for ggml_fp32_to_bf16_row() -#if defined(__AVX512BF16__) -#if defined(_MSC_VER) -#define m512i(p) p -#else -#include -#define m512i(p) (__m512i)(p) -#endif // defined(_MSC_VER) -#endif // defined(__AVX512BF16__) - -#if defined(__linux__) || \ - defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ - (defined(__APPLE__) && !TARGET_OS_TV && !TARGET_OS_WATCH) - -#include -#include -#include -#include -#if defined(__linux__) -#include -#endif - -#if defined(__ANDROID__) -#include -#include -#include - -struct backtrace_state { - void ** current; - void ** end; -}; - -static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context, void* arg) { - struct backtrace_state * state = (struct backtrace_state *)arg; - uintptr_t pc = _Unwind_GetIP(context); - if (pc) { - if (state->current == state->end) { - return _URC_END_OF_STACK; - } else { - *state->current++ = (void*)pc; - } - } - return _URC_NO_REASON; -} - -static void ggml_print_backtrace_symbols(void) { - const int max = 100; - void* buffer[max]; - - struct backtrace_state state = {buffer, buffer + max}; - _Unwind_Backtrace(unwind_callback, &state); - - int count = state.current - buffer; - - for (int idx = 0; idx < count; ++idx) { - const void * addr = buffer[idx]; - const char * symbol = ""; - - Dl_info info; - if (dladdr(addr, &info) && info.dli_sname) { - symbol = info.dli_sname; - } - - fprintf(stderr, "%d: %p %s\n", idx, addr, symbol); - } -} -#elif defined(__linux__) && defined(__GLIBC__) -#include -static void ggml_print_backtrace_symbols(void) { - void * trace[100]; - int nptrs = backtrace(trace, sizeof(trace)/sizeof(trace[0])); - backtrace_symbols_fd(trace, nptrs, STDERR_FILENO); -} -#elif defined(__APPLE__) -#include -static void ggml_print_backtrace_symbols(void) { - void * trace[100]; - int nptrs = backtrace(trace, sizeof(trace)/sizeof(trace[0])); - backtrace_symbols_fd(trace, nptrs, STDERR_FILENO); -} -#else -static void ggml_print_backtrace_symbols(void) { - // platform not supported -} -#endif - -void ggml_print_backtrace(void) { - const char * GGML_NO_BACKTRACE = getenv("GGML_NO_BACKTRACE"); - if (GGML_NO_BACKTRACE) { - return; - } -#if defined(__APPLE__) - // On macOS, fork+debugger attachment is problematic due to: - // 1. libdispatch "poisons" forked child processes - // 2. lldb has issues attaching to parent from forked child - // Use simple backtrace() instead to avoid Terminal.app crashes - const char * GGML_BACKTRACE_LLDB = getenv("GGML_BACKTRACE_LLDB"); - if (!GGML_BACKTRACE_LLDB) { - fprintf(stderr, "WARNING: Using native backtrace. Set GGML_BACKTRACE_LLDB for more info.\n"); - fprintf(stderr, "WARNING: GGML_BACKTRACE_LLDB may cause native MacOS Terminal.app to crash.\n"); - fprintf(stderr, "See: https://github.com/ggml-org/llama.cpp/pull/17869\n"); - ggml_print_backtrace_symbols(); - return; - } -#endif -#if defined(__linux__) - FILE * f = fopen("/proc/self/status", "r"); - size_t size = 0; - char * line = NULL; - ssize_t length = 0; - while ((length = getline(&line, &size, f)) > 0) { - if (!strncmp(line, "TracerPid:", sizeof("TracerPid:") - 1) && - (length != sizeof("TracerPid:\t0\n") - 1 || line[length - 2] != '0')) { - // Already being debugged, and the breakpoint is the later abort() - free(line); - fclose(f); - return; - } - } - free(line); - fclose(f); - int lock[2] = { -1, -1 }; - (void) !pipe(lock); // Don't start gdb until after PR_SET_PTRACER -#endif - const int parent_pid = getpid(); - const int child_pid = fork(); - if (child_pid < 0) { // error -#if defined(__linux__) - close(lock[1]); - close(lock[0]); -#endif - return; - } else if (child_pid == 0) { // child - char attach[32]; - snprintf(attach, sizeof(attach), "attach %d", parent_pid); -#if defined(__linux__) - close(lock[1]); - (void) !read(lock[0], lock, 1); - close(lock[0]); -#endif - // try gdb - execlp("gdb", "gdb", "--batch", - "-ex", "set style enabled on", - "-ex", attach, - "-ex", "bt -frame-info source-and-location", - "-ex", "detach", - "-ex", "quit", - (char *) NULL); - // try lldb - execlp("lldb", "lldb", "--batch", - "-o", "bt", - "-o", "quit", - "-p", &attach[sizeof("attach ") - 1], - (char *) NULL); - // gdb failed, fallback to backtrace_symbols - ggml_print_backtrace_symbols(); - _Exit(0); - } else { // parent -#if defined(__linux__) - prctl(PR_SET_PTRACER, child_pid); - close(lock[1]); - close(lock[0]); -#endif - waitpid(child_pid, NULL, 0); - } -} -#else -void ggml_print_backtrace(void) { - // platform not supported -} -#endif - -static ggml_abort_callback_t g_abort_callback = NULL; - -// Set the abort callback (passing null will restore original abort functionality: printing a message to stdout) -GGML_API ggml_abort_callback_t ggml_set_abort_callback(ggml_abort_callback_t callback) { - ggml_abort_callback_t ret_val = g_abort_callback; - g_abort_callback = callback; - return ret_val; -} - -void ggml_abort(const char * file, int line, const char * fmt, ...) { - fflush(stdout); - - char message[2048]; - int offset = snprintf(message, sizeof(message), "%s:%d: ", file, line); - - va_list args; - va_start(args, fmt); - vsnprintf(message + offset, sizeof(message) - offset, fmt, args); - va_end(args); - - if (g_abort_callback) { - g_abort_callback(message); - } else { - // default: print error and backtrace to stderr - fprintf(stderr, "%s\n", message); - ggml_print_backtrace(); - } - - abort(); -} - -// ggml_print_backtrace is registered with std::set_terminate by ggml.cpp - -// -// logging -// - -struct ggml_logger_state { - ggml_log_callback log_callback; - void * log_callback_user_data; -}; -static struct ggml_logger_state g_logger_state = {ggml_log_callback_default, NULL}; - -static void ggml_log_internal_v(enum ggml_log_level level, const char * format, va_list args) { - if (format == NULL) { - return; - } - va_list args_copy; - va_copy(args_copy, args); - char buffer[128]; - int len = vsnprintf(buffer, 128, format, args); - if (len < 128) { - g_logger_state.log_callback(level, buffer, g_logger_state.log_callback_user_data); - } else { - char * buffer2 = (char *) calloc(len + 1, sizeof(char)); - vsnprintf(buffer2, len + 1, format, args_copy); - buffer2[len] = 0; - g_logger_state.log_callback(level, buffer2, g_logger_state.log_callback_user_data); - free(buffer2); - } - va_end(args_copy); -} - -void ggml_log_internal(enum ggml_log_level level, const char * format, ...) { - va_list args; - va_start(args, format); - ggml_log_internal_v(level, format, args); - va_end(args); -} - -void ggml_log_callback_default(enum ggml_log_level level, const char * text, void * user_data) { - (void) level; - (void) user_data; - fputs(text, stderr); - fflush(stderr); -} - -// -// end of logging block -// - -#ifdef GGML_USE_ACCELERATE -// uncomment to use vDSP for soft max computation -// note: not sure if it is actually faster -//#define GGML_SOFT_MAX_ACCELERATE -#endif - - -void * ggml_aligned_malloc(size_t size) { -#if defined(__s390x__) - const int alignment = 256; -#else - const int alignment = 64; -#endif - -#if defined(_MSC_VER) || defined(__MINGW32__) - return _aligned_malloc(size, alignment); -#else - if (size == 0) { - GGML_LOG_WARN("Behavior may be unexpected when allocating 0 bytes for ggml_aligned_malloc!\n"); - return NULL; - } - void * aligned_memory = NULL; - #ifdef GGML_USE_CPU_HBM - int result = hbw_posix_memalign(&aligned_memory, alignment, size); - #elif TARGET_OS_OSX - GGML_UNUSED(alignment); - kern_return_t alloc_status = vm_allocate((vm_map_t) mach_task_self(), (vm_address_t *) &aligned_memory, size, VM_FLAGS_ANYWHERE); - int result = EFAULT; - switch (alloc_status) { - case KERN_SUCCESS: - result = 0; - break; - case KERN_INVALID_ADDRESS: - result = EINVAL; - break; - case KERN_NO_SPACE: - result = ENOMEM; - break; - default: - result = EFAULT; - break; - } - #else - int result = posix_memalign(&aligned_memory, alignment, size); - #endif - if (result != 0) { - // Handle allocation failure - const char *error_desc = "unknown allocation error"; - switch (result) { - case EINVAL: - error_desc = "invalid alignment value"; - break; - case ENOMEM: - error_desc = "insufficient memory"; - break; - } - GGML_LOG_ERROR("%s: %s (attempted to allocate %6.2f MB)\n", __func__, error_desc, size/(1024.0*1024.0)); - return NULL; - } - return aligned_memory; -#endif -} - -void ggml_aligned_free(void * ptr, size_t size) { - GGML_UNUSED(size); -#if defined(_MSC_VER) || defined(__MINGW32__) - _aligned_free(ptr); -#elif GGML_USE_CPU_HBM - if (ptr != NULL) { - hbw_free(ptr); - } -#elif TARGET_OS_OSX - if (ptr != NULL) { - vm_deallocate((vm_map_t)mach_task_self(), (vm_address_t)ptr, size); - } -#else - free(ptr); -#endif -} - - -inline static void * ggml_malloc(size_t size) { - if (size == 0) { - GGML_LOG_WARN("Behavior may be unexpected when allocating 0 bytes for ggml_malloc!\n"); - return NULL; - } - void * result = malloc(size); - if (result == NULL) { - GGML_LOG_ERROR("%s: failed to allocate %6.2f MB\n", __func__, size/(1024.0*1024.0)); - GGML_ABORT("fatal error"); - } - return result; -} - -// calloc -inline static void * ggml_calloc(size_t num, size_t size) { - if (num == 0 || size == 0) { - GGML_LOG_WARN("Behavior may be unexpected when allocating 0 bytes for ggml_calloc!\n"); - return NULL; - } - void * result = calloc(num, size); - if (result == NULL) { - GGML_LOG_ERROR("%s: failed to allocate %6.2f MB\n", __func__, size/(1024.0*1024.0)); - GGML_ABORT("fatal error"); - } - return result; -} - -#define GGML_MALLOC(size) ggml_malloc(size) -#define GGML_CALLOC(num, size) ggml_calloc(num, size) - -#define GGML_FREE(ptr) free(ptr) - -const char * ggml_status_to_string(enum ggml_status status) { - switch (status) { - case GGML_STATUS_ALLOC_FAILED: return "GGML status: error (failed to allocate memory)"; - case GGML_STATUS_FAILED: return "GGML status: error (operation failed)"; - case GGML_STATUS_SUCCESS: return "GGML status: success"; - case GGML_STATUS_ABORTED: return "GGML status: warning (operation aborted)"; - } - - return "GGML status: unknown"; -} - -float ggml_fp16_to_fp32(ggml_fp16_t x) { -#define ggml_fp16_to_fp32 do_not_use__ggml_fp16_to_fp32__in_ggml - return GGML_FP16_TO_FP32(x); -} - -ggml_fp16_t ggml_fp32_to_fp16(float x) { -#define ggml_fp32_to_fp16 do_not_use__ggml_fp32_to_fp16__in_ggml - return GGML_FP32_TO_FP16(x); -} - -float ggml_bf16_to_fp32(ggml_bf16_t x) { -#define ggml_bf16_to_fp32 do_not_use__ggml_bf16_to_fp32__in_ggml - return GGML_BF16_TO_FP32(x); // it just left shifts -} - -ggml_bf16_t ggml_fp32_to_bf16(float x) { -#define ggml_fp32_to_bf16 do_not_use__ggml_fp32_to_bf16__in_ggml - return GGML_FP32_TO_BF16(x); -} - -void ggml_fp16_to_fp32_row(const ggml_fp16_t * x, float * y, int64_t n) { - for (int64_t i = 0; i < n; i++) { - y[i] = GGML_FP16_TO_FP32(x[i]); - } -} - -void ggml_fp32_to_fp16_row(const float * x, ggml_fp16_t * y, int64_t n) { - int i = 0; - for (; i < n; ++i) { - y[i] = GGML_FP32_TO_FP16(x[i]); - } -} - -void ggml_bf16_to_fp32_row(const ggml_bf16_t * x, float * y, int64_t n) { - int i = 0; - for (; i < n; ++i) { - y[i] = GGML_BF16_TO_FP32(x[i]); - } -} - -void ggml_fp32_to_bf16_row_ref(const float * x, ggml_bf16_t * y, int64_t n) { - for (int i = 0; i < n; i++) { - y[i] = ggml_compute_fp32_to_bf16(x[i]); - } -} - -void ggml_fp32_to_bf16_row(const float * x, ggml_bf16_t * y, int64_t n) { - int i = 0; -#if defined(__AVX512BF16__) - // subnormals are flushed to zero on this platform - for (; i + 32 <= n; i += 32) { - _mm512_storeu_si512( - (__m512i *)(y + i), - m512i(_mm512_cvtne2ps_pbh(_mm512_loadu_ps(x + i + 16), - _mm512_loadu_ps(x + i)))); - } -#endif - for (; i < n; i++) { - y[i] = GGML_FP32_TO_BF16(x[i]); - } -} - -bool ggml_guid_matches(ggml_guid_t guid_a, ggml_guid_t guid_b) { - return memcmp(guid_a, guid_b, sizeof(ggml_guid)) == 0; -} - -const char * ggml_version(void) { - return GGML_VERSION; -} - -const char * ggml_commit(void) { - return GGML_COMMIT; -} - -// -// timing -// - -#if defined(_MSC_VER) || defined(__MINGW32__) -static int64_t timer_freq, timer_start; -void ggml_time_init(void) { - LARGE_INTEGER t; - QueryPerformanceFrequency(&t); - timer_freq = t.QuadPart; - - // The multiplication by 1000 or 1000000 below can cause an overflow if timer_freq - // and the uptime is high enough. - // We subtract the program start time to reduce the likelihood of that happening. - QueryPerformanceCounter(&t); - timer_start = t.QuadPart; -} -int64_t ggml_time_ms(void) { - LARGE_INTEGER t; - QueryPerformanceCounter(&t); - return ((t.QuadPart-timer_start) * 1000) / timer_freq; -} -int64_t ggml_time_us(void) { - LARGE_INTEGER t; - QueryPerformanceCounter(&t); - return ((t.QuadPart-timer_start) * 1000000) / timer_freq; -} -#else -void ggml_time_init(void) {} -int64_t ggml_time_ms(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (int64_t)ts.tv_sec*1000 + (int64_t)ts.tv_nsec/1000000; -} - -int64_t ggml_time_us(void) { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (int64_t)ts.tv_sec*1000000 + (int64_t)ts.tv_nsec/1000; -} -#endif - -int64_t ggml_cycles(void) { - return clock(); -} - -int64_t ggml_cycles_per_ms(void) { - return CLOCKS_PER_SEC/1000; -} - -// -// cross-platform UTF-8 file paths -// - -#ifdef _WIN32 -static wchar_t * ggml_mbstowcs(const char * mbs) { - int wlen = MultiByteToWideChar(CP_UTF8, 0, mbs, -1, NULL, 0); - if (!wlen) { - errno = EINVAL; - return NULL; - } - - wchar_t * wbuf = GGML_MALLOC(wlen * sizeof(wchar_t)); - wlen = MultiByteToWideChar(CP_UTF8, 0, mbs, -1, wbuf, wlen); - if (!wlen) { - GGML_FREE(wbuf); - errno = EINVAL; - return NULL; - } - - return wbuf; -} -#endif - -FILE * ggml_fopen(const char * fname, const char * mode) { -#ifdef _WIN32 - FILE * file = NULL; - - // convert fname (UTF-8) - wchar_t * wfname = ggml_mbstowcs(fname); - if (wfname) { - // convert mode (ANSI) - wchar_t * wmode = GGML_MALLOC((strlen(mode) + 1) * sizeof(wchar_t)); - wchar_t * wmode_p = wmode; - do { - *wmode_p++ = (wchar_t)*mode; - } while (*mode++); - - // open file - file = _wfopen(wfname, wmode); - - GGML_FREE(wfname); - GGML_FREE(wmode); - } - - return file; -#else - return fopen(fname, mode); -#endif - -} - -static const struct ggml_type_traits type_traits[GGML_TYPE_COUNT] = { - [GGML_TYPE_I8] = { - .type_name = "i8", - .blck_size = 1, - .type_size = sizeof(int8_t), - .is_quantized = false, - }, - [GGML_TYPE_I16] = { - .type_name = "i16", - .blck_size = 1, - .type_size = sizeof(int16_t), - .is_quantized = false, - }, - [GGML_TYPE_I32] = { - .type_name = "i32", - .blck_size = 1, - .type_size = sizeof(int32_t), - .is_quantized = false, - }, - [GGML_TYPE_I64] = { - .type_name = "i64", - .blck_size = 1, - .type_size = sizeof(int64_t), - .is_quantized = false, - }, - [GGML_TYPE_F64] = { - .type_name = "f64", - .blck_size = 1, - .type_size = sizeof(double), - .is_quantized = false, - }, - [GGML_TYPE_F32] = { - .type_name = "f32", - .blck_size = 1, - .type_size = sizeof(float), - .is_quantized = false, - }, - [GGML_TYPE_F16] = { - .type_name = "f16", - .blck_size = 1, - .type_size = sizeof(ggml_fp16_t), - .is_quantized = false, - .to_float = (ggml_to_float_t) ggml_fp16_to_fp32_row, - .from_float_ref = (ggml_from_float_t) ggml_fp32_to_fp16_row, - }, - [GGML_TYPE_Q1_0] = { - .type_name = "q1_0", - .blck_size = QK1_0, - .type_size = sizeof(block_q1_0), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q1_0, - .from_float_ref = (ggml_from_float_t) quantize_row_q1_0_ref, - }, - [GGML_TYPE_Q4_0] = { - .type_name = "q4_0", - .blck_size = QK4_0, - .type_size = sizeof(block_q4_0), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q4_0, - .from_float_ref = (ggml_from_float_t) quantize_row_q4_0_ref, - }, - [GGML_TYPE_Q4_1] = { - .type_name = "q4_1", - .blck_size = QK4_1, - .type_size = sizeof(block_q4_1), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q4_1, - .from_float_ref = (ggml_from_float_t) quantize_row_q4_1_ref, - }, - [4] = { // GGML_TYPE_Q4_2 - .type_name = "DEPRECATED", - .blck_size = 0, - .type_size = 0, - .is_quantized = false, - }, - [5] = { // GGML_TYPE_Q4_3 - .type_name = "DEPRECATED", - .blck_size = 0, - .type_size = 0, - .is_quantized = false, - }, - [GGML_TYPE_Q5_0] = { - .type_name = "q5_0", - .blck_size = QK5_0, - .type_size = sizeof(block_q5_0), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q5_0, - .from_float_ref = (ggml_from_float_t) quantize_row_q5_0_ref, - }, - [GGML_TYPE_Q5_1] = { - .type_name = "q5_1", - .blck_size = QK5_1, - .type_size = sizeof(block_q5_1), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q5_1, - .from_float_ref = (ggml_from_float_t) quantize_row_q5_1_ref, - }, - [GGML_TYPE_Q8_0] = { - .type_name = "q8_0", - .blck_size = QK8_0, - .type_size = sizeof(block_q8_0), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q8_0, - .from_float_ref = (ggml_from_float_t) quantize_row_q8_0_ref, - }, - [GGML_TYPE_Q8_1] = { - .type_name = "q8_1", - .blck_size = QK8_1, - .type_size = sizeof(block_q8_1), - .is_quantized = true, - .from_float_ref = (ggml_from_float_t) quantize_row_q8_1_ref, - }, - [GGML_TYPE_MXFP4] = { - .type_name = "mxfp4", - .blck_size = QK_MXFP4, - .type_size = sizeof(block_mxfp4), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_mxfp4, - .from_float_ref = (ggml_from_float_t)quantize_row_mxfp4_ref, - }, - [GGML_TYPE_NVFP4] = { - .type_name = "nvfp4", - .blck_size = QK_NVFP4, - .type_size = sizeof(block_nvfp4), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_nvfp4, - .from_float_ref = (ggml_from_float_t)quantize_row_nvfp4_ref, - }, - [GGML_TYPE_Q2_K] = { - .type_name = "q2_K", - .blck_size = QK_K, - .type_size = sizeof(block_q2_K), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q2_K, - .from_float_ref = (ggml_from_float_t) quantize_row_q2_K_ref, - }, - [GGML_TYPE_Q3_K] = { - .type_name = "q3_K", - .blck_size = QK_K, - .type_size = sizeof(block_q3_K), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q3_K, - .from_float_ref = (ggml_from_float_t) quantize_row_q3_K_ref, - }, - [GGML_TYPE_Q4_K] = { - .type_name = "q4_K", - .blck_size = QK_K, - .type_size = sizeof(block_q4_K), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q4_K, - .from_float_ref = (ggml_from_float_t) quantize_row_q4_K_ref, - }, - [GGML_TYPE_Q5_K] = { - .type_name = "q5_K", - .blck_size = QK_K, - .type_size = sizeof(block_q5_K), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q5_K, - .from_float_ref = (ggml_from_float_t) quantize_row_q5_K_ref, - }, - [GGML_TYPE_Q6_K] = { - .type_name = "q6_K", - .blck_size = QK_K, - .type_size = sizeof(block_q6_K), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_q6_K, - .from_float_ref = (ggml_from_float_t) quantize_row_q6_K_ref, - }, - [GGML_TYPE_IQ2_XXS] = { - .type_name = "iq2_xxs", - .blck_size = QK_K, - .type_size = sizeof(block_iq2_xxs), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq2_xxs, - .from_float_ref = NULL, - }, - [GGML_TYPE_IQ2_XS] = { - .type_name = "iq2_xs", - .blck_size = QK_K, - .type_size = sizeof(block_iq2_xs), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq2_xs, - .from_float_ref = NULL, - }, - [GGML_TYPE_IQ3_XXS] = { - .type_name = "iq3_xxs", - .blck_size = QK_K, - .type_size = sizeof(block_iq3_xxs), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq3_xxs, - .from_float_ref = (ggml_from_float_t)quantize_row_iq3_xxs_ref, - }, - [GGML_TYPE_IQ3_S] = { - .type_name = "iq3_s", - .blck_size = QK_K, - .type_size = sizeof(block_iq3_s), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq3_s, - .from_float_ref = (ggml_from_float_t)quantize_row_iq3_s_ref, - }, - [GGML_TYPE_IQ2_S] = { - .type_name = "iq2_s", - .blck_size = QK_K, - .type_size = sizeof(block_iq2_s), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq2_s, - .from_float_ref = (ggml_from_float_t)quantize_row_iq2_s_ref, - }, - [GGML_TYPE_IQ1_S] = { - .type_name = "iq1_s", - .blck_size = QK_K, - .type_size = sizeof(block_iq1_s), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq1_s, - .from_float_ref = NULL, - }, - [GGML_TYPE_IQ1_M] = { - .type_name = "iq1_m", - .blck_size = QK_K, - .type_size = sizeof(block_iq1_m), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq1_m, - .from_float_ref = NULL, - }, - [GGML_TYPE_IQ4_NL] = { - .type_name = "iq4_nl", - .blck_size = QK4_NL, - .type_size = sizeof(block_iq4_nl), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq4_nl, - .from_float_ref = (ggml_from_float_t)quantize_row_iq4_nl_ref, - }, - [GGML_TYPE_IQ4_XS] = { - .type_name = "iq4_xs", - .blck_size = QK_K, - .type_size = sizeof(block_iq4_xs), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_iq4_xs, - .from_float_ref = (ggml_from_float_t)quantize_row_iq4_xs_ref, - }, - [GGML_TYPE_Q8_K] = { - .type_name = "q8_K", - .blck_size = QK_K, - .type_size = sizeof(block_q8_K), - .is_quantized = true, - }, - [GGML_TYPE_BF16] = { - .type_name = "bf16", - .blck_size = 1, - .type_size = sizeof(ggml_bf16_t), - .is_quantized = false, - .to_float = (ggml_to_float_t) ggml_bf16_to_fp32_row, - .from_float_ref = (ggml_from_float_t) ggml_fp32_to_bf16_row_ref, - }, - [31] = { // GGML_TYPE_Q4_0_4_4 - .type_name = "TYPE_Q4_0_4_4 REMOVED, use Q4_0 with runtime repacking", - .blck_size = 0, - .type_size = 0, - .is_quantized = false, - }, - [32] = { // GGML_TYPE_Q4_0_4_8 - .type_name = "TYPE_Q4_0_4_8 REMOVED, use Q4_0 with runtime repacking", - .blck_size = 0, - .type_size = 0, - .is_quantized = false, - }, - [33] = { // GGML_TYPE_Q4_0_8_8 - .type_name = "TYPE_Q4_0_8_8 REMOVED, use Q4_0 with runtime repacking", - .blck_size = 0, - .type_size = 0, - .is_quantized = false, - }, - [GGML_TYPE_TQ1_0] = { - .type_name = "tq1_0", - .blck_size = QK_K, - .type_size = sizeof(block_tq1_0), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_tq1_0, - .from_float_ref = (ggml_from_float_t) quantize_row_tq1_0_ref, - }, - [GGML_TYPE_TQ2_0] = { - .type_name = "tq2_0", - .blck_size = QK_K, - .type_size = sizeof(block_tq2_0), - .is_quantized = true, - .to_float = (ggml_to_float_t) dequantize_row_tq2_0, - .from_float_ref = (ggml_from_float_t) quantize_row_tq2_0_ref, - }, - [36] = { // GGML_TYPE_IQ4_NL_4_4 - .type_name = "TYPE_IQ4_NL_4_4 REMOVED, use IQ4_NL with runtime repacking", - .blck_size = 0, - .type_size = 0, - .is_quantized = false, - }, - [37] = { // GGML_TYPE_IQ4_NL_4_8 - .type_name = "TYPE_IQ4_NL_4_8 REMOVED, use IQ4_NL with runtime repacking", - .blck_size = 0, - .type_size = 0, - .is_quantized = false, - }, - [38] = { // GGML_TYPE_IQ4_NL_8_8 - .type_name = "TYPE_IQ4_NL_8_8 REMOVED, use IQ4_NL with runtime repacking", - .blck_size = 0, - .type_size = 0, - .is_quantized = false, - }, -}; - -const struct ggml_type_traits * ggml_get_type_traits(enum ggml_type type) { - assert(type >= 0); - assert(type < GGML_TYPE_COUNT); - return &type_traits[type]; -} - -// -// ggml object -// - -struct ggml_object { - size_t offs; - size_t size; - - struct ggml_object * next; - - enum ggml_object_type type; - - char padding[4]; -}; - -static const size_t GGML_OBJECT_SIZE = sizeof(struct ggml_object); - -// -// ggml context -// - -struct ggml_context { - size_t mem_size; - void * mem_buffer; - bool mem_buffer_owned; - bool no_alloc; - - int n_objects; - - struct ggml_object * objects_begin; - struct ggml_object * objects_end; -}; - -// -// data types -// - -static const char * GGML_OP_NAME[GGML_OP_COUNT] = { - "NONE", - - "DUP", - "ADD", - "ADD_ID", - "ADD1", - "ACC", - "SUB", - "MUL", - "DIV", - "SQR", - "SQRT", - "LOG", - "SIN", - "COS", - "SUM", - "SUM_ROWS", - "CUMSUM", - "MEAN", - "ARGMAX", - "COUNT_EQUAL", - "REPEAT", - "REPEAT_BACK", - "CONCAT", - "SILU_BACK", - "NORM", - "RMS_NORM", - "RMS_NORM_BACK", - "GROUP_NORM", - "L2_NORM", - - "MUL_MAT", +#define _CRT_SECURE_NO_DEPRECATE // Disables "unsafe" warnings on Windows +#define _USE_MATH_DEFINES // For M_PI on MSVC + +#include "ggml-backend.h" +#include "ggml-impl.h" +#include "ggml-threading.h" +#include "ggml-cpu.h" +#include "ggml.h" + +// FIXME: required here for quantization functions +#include "ggml-quants.h" + +#ifdef GGML_USE_CPU_HBM +#include +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) +#include // using malloc.h with MSC/MINGW +#elif !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__gnu_linux__) +#include +#endif + +#if defined(__APPLE__) +#include +#include +#include +#endif + +#if defined(_WIN32) +#define WIN32_LEAN_AND_MEAN +#ifndef NOMINMAX + #define NOMINMAX +#endif +#include +#endif + +#define UNUSED GGML_UNUSED + +uint64_t ggml_graph_next_uid(void) { +#ifdef _MSC_VER +#if defined(_WIN32) + static volatile LONG counter = 1; + return (uint64_t) InterlockedIncrement(&counter) - 1; +#else + static volatile long long counter = 1; + return (uint64_t) _InterlockedIncrement64(&counter) - 1; +#endif +#else + static uint64_t counter = 1; + return __atomic_fetch_add(&counter, 1, __ATOMIC_RELAXED); +#endif +} + +// Needed for ggml_fp32_to_bf16_row() +#if defined(__AVX512BF16__) +#if defined(_MSC_VER) +#define m512i(p) p +#else +#include +#define m512i(p) (__m512i)(p) +#endif // defined(_MSC_VER) +#endif // defined(__AVX512BF16__) + +#if defined(__linux__) || \ + defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || \ + (defined(__APPLE__) && !TARGET_OS_TV && !TARGET_OS_WATCH) + +#include +#include +#include +#include +#if defined(__linux__) +#include +#endif + +#if defined(__ANDROID__) +#include +#include +#include + +struct backtrace_state { + void ** current; + void ** end; +}; + +static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context* context, void* arg) { + struct backtrace_state * state = (struct backtrace_state *)arg; + uintptr_t pc = _Unwind_GetIP(context); + if (pc) { + if (state->current == state->end) { + return _URC_END_OF_STACK; + } else { + *state->current++ = (void*)pc; + } + } + return _URC_NO_REASON; +} + +static void ggml_print_backtrace_symbols(void) { + const int max = 100; + void* buffer[max]; + + struct backtrace_state state = {buffer, buffer + max}; + _Unwind_Backtrace(unwind_callback, &state); + + int count = state.current - buffer; + + for (int idx = 0; idx < count; ++idx) { + const void * addr = buffer[idx]; + const char * symbol = ""; + + Dl_info info; + if (dladdr(addr, &info) && info.dli_sname) { + symbol = info.dli_sname; + } + + fprintf(stderr, "%d: %p %s\n", idx, addr, symbol); + } +} +#elif defined(__linux__) && defined(__GLIBC__) +#include +static void ggml_print_backtrace_symbols(void) { + void * trace[100]; + int nptrs = backtrace(trace, sizeof(trace)/sizeof(trace[0])); + backtrace_symbols_fd(trace, nptrs, STDERR_FILENO); +} +#elif defined(__APPLE__) +#include +static void ggml_print_backtrace_symbols(void) { + void * trace[100]; + int nptrs = backtrace(trace, sizeof(trace)/sizeof(trace[0])); + backtrace_symbols_fd(trace, nptrs, STDERR_FILENO); +} +#else +static void ggml_print_backtrace_symbols(void) { + // platform not supported +} +#endif + +void ggml_print_backtrace(void) { + const char * GGML_NO_BACKTRACE = getenv("GGML_NO_BACKTRACE"); + if (GGML_NO_BACKTRACE) { + return; + } +#if defined(__APPLE__) + // On macOS, fork+debugger attachment is problematic due to: + // 1. libdispatch "poisons" forked child processes + // 2. lldb has issues attaching to parent from forked child + // Use simple backtrace() instead to avoid Terminal.app crashes + const char * GGML_BACKTRACE_LLDB = getenv("GGML_BACKTRACE_LLDB"); + if (!GGML_BACKTRACE_LLDB) { + fprintf(stderr, "WARNING: Using native backtrace. Set GGML_BACKTRACE_LLDB for more info.\n"); + fprintf(stderr, "WARNING: GGML_BACKTRACE_LLDB may cause native MacOS Terminal.app to crash.\n"); + fprintf(stderr, "See: https://github.com/ggml-org/llama.cpp/pull/17869\n"); + ggml_print_backtrace_symbols(); + return; + } +#endif +#if defined(__linux__) + FILE * f = fopen("/proc/self/status", "r"); + size_t size = 0; + char * line = NULL; + ssize_t length = 0; + while ((length = getline(&line, &size, f)) > 0) { + if (!strncmp(line, "TracerPid:", sizeof("TracerPid:") - 1) && + (length != sizeof("TracerPid:\t0\n") - 1 || line[length - 2] != '0')) { + // Already being debugged, and the breakpoint is the later abort() + free(line); + fclose(f); + return; + } + } + free(line); + fclose(f); + int lock[2] = { -1, -1 }; + (void) !pipe(lock); // Don't start gdb until after PR_SET_PTRACER +#endif + const int parent_pid = getpid(); + const int child_pid = fork(); + if (child_pid < 0) { // error +#if defined(__linux__) + close(lock[1]); + close(lock[0]); +#endif + return; + } else if (child_pid == 0) { // child + char attach[32]; + snprintf(attach, sizeof(attach), "attach %d", parent_pid); +#if defined(__linux__) + close(lock[1]); + (void) !read(lock[0], lock, 1); + close(lock[0]); +#endif + // try gdb + execlp("gdb", "gdb", "--batch", + "-ex", "set style enabled on", + "-ex", attach, + "-ex", "bt -frame-info source-and-location", + "-ex", "detach", + "-ex", "quit", + (char *) NULL); + // try lldb + execlp("lldb", "lldb", "--batch", + "-o", "bt", + "-o", "quit", + "-p", &attach[sizeof("attach ") - 1], + (char *) NULL); + // gdb failed, fallback to backtrace_symbols + ggml_print_backtrace_symbols(); + _Exit(0); + } else { // parent +#if defined(__linux__) + prctl(PR_SET_PTRACER, child_pid); + close(lock[1]); + close(lock[0]); +#endif + waitpid(child_pid, NULL, 0); + } +} +#else +void ggml_print_backtrace(void) { + // platform not supported +} +#endif + +static ggml_abort_callback_t g_abort_callback = NULL; + +// Set the abort callback (passing null will restore original abort functionality: printing a message to stdout) +GGML_API ggml_abort_callback_t ggml_set_abort_callback(ggml_abort_callback_t callback) { + ggml_abort_callback_t ret_val = g_abort_callback; + g_abort_callback = callback; + return ret_val; +} + +void ggml_abort(const char * file, int line, const char * fmt, ...) { + fflush(stdout); + + char message[2048]; + int offset = snprintf(message, sizeof(message), "%s:%d: ", file, line); + + va_list args; + va_start(args, fmt); + vsnprintf(message + offset, sizeof(message) - offset, fmt, args); + va_end(args); + + if (g_abort_callback) { + g_abort_callback(message); + } else { + // default: print error and backtrace to stderr + fprintf(stderr, "%s\n", message); + ggml_print_backtrace(); + } + + abort(); +} + +// ggml_print_backtrace is registered with std::set_terminate by ggml.cpp + +// +// logging +// + +struct ggml_logger_state { + ggml_log_callback log_callback; + void * log_callback_user_data; +}; +static struct ggml_logger_state g_logger_state = {ggml_log_callback_default, NULL}; + +static void ggml_log_internal_v(enum ggml_log_level level, const char * format, va_list args) { + if (format == NULL) { + return; + } + va_list args_copy; + va_copy(args_copy, args); + char buffer[128]; + int len = vsnprintf(buffer, 128, format, args); + if (len < 128) { + g_logger_state.log_callback(level, buffer, g_logger_state.log_callback_user_data); + } else { + char * buffer2 = (char *) calloc(len + 1, sizeof(char)); + vsnprintf(buffer2, len + 1, format, args_copy); + buffer2[len] = 0; + g_logger_state.log_callback(level, buffer2, g_logger_state.log_callback_user_data); + free(buffer2); + } + va_end(args_copy); +} + +void ggml_log_internal(enum ggml_log_level level, const char * format, ...) { + va_list args; + va_start(args, format); + ggml_log_internal_v(level, format, args); + va_end(args); +} + +void ggml_log_callback_default(enum ggml_log_level level, const char * text, void * user_data) { + (void) level; + (void) user_data; + fputs(text, stderr); + fflush(stderr); +} + +// +// end of logging block +// + +#ifdef GGML_USE_ACCELERATE +// uncomment to use vDSP for soft max computation +// note: not sure if it is actually faster +//#define GGML_SOFT_MAX_ACCELERATE +#endif + + +void * ggml_aligned_malloc(size_t size) { +#if defined(__s390x__) + const int alignment = 256; +#else + const int alignment = 64; +#endif + +#if defined(_MSC_VER) || defined(__MINGW32__) + return _aligned_malloc(size, alignment); +#else + if (size == 0) { + GGML_LOG_WARN("Behavior may be unexpected when allocating 0 bytes for ggml_aligned_malloc!\n"); + return NULL; + } + void * aligned_memory = NULL; + #ifdef GGML_USE_CPU_HBM + int result = hbw_posix_memalign(&aligned_memory, alignment, size); + #elif TARGET_OS_OSX + GGML_UNUSED(alignment); + kern_return_t alloc_status = vm_allocate((vm_map_t) mach_task_self(), (vm_address_t *) &aligned_memory, size, VM_FLAGS_ANYWHERE); + int result = EFAULT; + switch (alloc_status) { + case KERN_SUCCESS: + result = 0; + break; + case KERN_INVALID_ADDRESS: + result = EINVAL; + break; + case KERN_NO_SPACE: + result = ENOMEM; + break; + default: + result = EFAULT; + break; + } + #else + int result = posix_memalign(&aligned_memory, alignment, size); + #endif + if (result != 0) { + // Handle allocation failure + const char *error_desc = "unknown allocation error"; + switch (result) { + case EINVAL: + error_desc = "invalid alignment value"; + break; + case ENOMEM: + error_desc = "insufficient memory"; + break; + } + GGML_LOG_ERROR("%s: %s (attempted to allocate %6.2f MB)\n", __func__, error_desc, size/(1024.0*1024.0)); + return NULL; + } + return aligned_memory; +#endif +} + +void ggml_aligned_free(void * ptr, size_t size) { + GGML_UNUSED(size); +#if defined(_MSC_VER) || defined(__MINGW32__) + _aligned_free(ptr); +#elif GGML_USE_CPU_HBM + if (ptr != NULL) { + hbw_free(ptr); + } +#elif TARGET_OS_OSX + if (ptr != NULL) { + vm_deallocate((vm_map_t)mach_task_self(), (vm_address_t)ptr, size); + } +#else + free(ptr); +#endif +} + + +inline static void * ggml_malloc(size_t size) { + if (size == 0) { + GGML_LOG_WARN("Behavior may be unexpected when allocating 0 bytes for ggml_malloc!\n"); + return NULL; + } + void * result = malloc(size); + if (result == NULL) { + GGML_LOG_ERROR("%s: failed to allocate %6.2f MB\n", __func__, size/(1024.0*1024.0)); + GGML_ABORT("fatal error"); + } + return result; +} + +// calloc +inline static void * ggml_calloc(size_t num, size_t size) { + if (num == 0 || size == 0) { + GGML_LOG_WARN("Behavior may be unexpected when allocating 0 bytes for ggml_calloc!\n"); + return NULL; + } + void * result = calloc(num, size); + if (result == NULL) { + GGML_LOG_ERROR("%s: failed to allocate %6.2f MB\n", __func__, size/(1024.0*1024.0)); + GGML_ABORT("fatal error"); + } + return result; +} + +#define GGML_MALLOC(size) ggml_malloc(size) +#define GGML_CALLOC(num, size) ggml_calloc(num, size) + +#define GGML_FREE(ptr) free(ptr) + +const char * ggml_status_to_string(enum ggml_status status) { + switch (status) { + case GGML_STATUS_ALLOC_FAILED: return "GGML status: error (failed to allocate memory)"; + case GGML_STATUS_FAILED: return "GGML status: error (operation failed)"; + case GGML_STATUS_SUCCESS: return "GGML status: success"; + case GGML_STATUS_ABORTED: return "GGML status: warning (operation aborted)"; + } + + return "GGML status: unknown"; +} + +float ggml_fp16_to_fp32(ggml_fp16_t x) { +#define ggml_fp16_to_fp32 do_not_use__ggml_fp16_to_fp32__in_ggml + return GGML_FP16_TO_FP32(x); +} + +ggml_fp16_t ggml_fp32_to_fp16(float x) { +#define ggml_fp32_to_fp16 do_not_use__ggml_fp32_to_fp16__in_ggml + return GGML_FP32_TO_FP16(x); +} + +float ggml_bf16_to_fp32(ggml_bf16_t x) { +#define ggml_bf16_to_fp32 do_not_use__ggml_bf16_to_fp32__in_ggml + return GGML_BF16_TO_FP32(x); // it just left shifts +} + +ggml_bf16_t ggml_fp32_to_bf16(float x) { +#define ggml_fp32_to_bf16 do_not_use__ggml_fp32_to_bf16__in_ggml + return GGML_FP32_TO_BF16(x); +} + +void ggml_fp16_to_fp32_row(const ggml_fp16_t * x, float * y, int64_t n) { + for (int64_t i = 0; i < n; i++) { + y[i] = GGML_FP16_TO_FP32(x[i]); + } +} + +void ggml_fp32_to_fp16_row(const float * x, ggml_fp16_t * y, int64_t n) { + int i = 0; + for (; i < n; ++i) { + y[i] = GGML_FP32_TO_FP16(x[i]); + } +} + +void ggml_bf16_to_fp32_row(const ggml_bf16_t * x, float * y, int64_t n) { + int i = 0; + for (; i < n; ++i) { + y[i] = GGML_BF16_TO_FP32(x[i]); + } +} + +void ggml_fp32_to_bf16_row_ref(const float * x, ggml_bf16_t * y, int64_t n) { + for (int i = 0; i < n; i++) { + y[i] = ggml_compute_fp32_to_bf16(x[i]); + } +} + +void ggml_fp32_to_bf16_row(const float * x, ggml_bf16_t * y, int64_t n) { + int i = 0; +#if defined(__AVX512BF16__) + // subnormals are flushed to zero on this platform + for (; i + 32 <= n; i += 32) { + _mm512_storeu_si512( + (__m512i *)(y + i), + m512i(_mm512_cvtne2ps_pbh(_mm512_loadu_ps(x + i + 16), + _mm512_loadu_ps(x + i)))); + } +#endif + for (; i < n; i++) { + y[i] = GGML_FP32_TO_BF16(x[i]); + } +} + +bool ggml_guid_matches(ggml_guid_t guid_a, ggml_guid_t guid_b) { + return memcmp(guid_a, guid_b, sizeof(ggml_guid)) == 0; +} + +const char * ggml_version(void) { + return GGML_VERSION; +} + +const char * ggml_commit(void) { + return GGML_COMMIT; +} + +// +// timing +// + +#if defined(_MSC_VER) || defined(__MINGW32__) +static int64_t timer_freq, timer_start; +void ggml_time_init(void) { + LARGE_INTEGER t; + QueryPerformanceFrequency(&t); + timer_freq = t.QuadPart; + + // The multiplication by 1000 or 1000000 below can cause an overflow if timer_freq + // and the uptime is high enough. + // We subtract the program start time to reduce the likelihood of that happening. + QueryPerformanceCounter(&t); + timer_start = t.QuadPart; +} +int64_t ggml_time_ms(void) { + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart-timer_start) * 1000) / timer_freq; +} +int64_t ggml_time_us(void) { + LARGE_INTEGER t; + QueryPerformanceCounter(&t); + return ((t.QuadPart-timer_start) * 1000000) / timer_freq; +} +#else +void ggml_time_init(void) {} +int64_t ggml_time_ms(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec*1000 + (int64_t)ts.tv_nsec/1000000; +} + +int64_t ggml_time_us(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (int64_t)ts.tv_sec*1000000 + (int64_t)ts.tv_nsec/1000; +} +#endif + +int64_t ggml_cycles(void) { + return clock(); +} + +int64_t ggml_cycles_per_ms(void) { + return CLOCKS_PER_SEC/1000; +} + +// +// cross-platform UTF-8 file paths +// + +#ifdef _WIN32 +static wchar_t * ggml_mbstowcs(const char * mbs) { + int wlen = MultiByteToWideChar(CP_UTF8, 0, mbs, -1, NULL, 0); + if (!wlen) { + errno = EINVAL; + return NULL; + } + + wchar_t * wbuf = GGML_MALLOC(wlen * sizeof(wchar_t)); + wlen = MultiByteToWideChar(CP_UTF8, 0, mbs, -1, wbuf, wlen); + if (!wlen) { + GGML_FREE(wbuf); + errno = EINVAL; + return NULL; + } + + return wbuf; +} +#endif + +FILE * ggml_fopen(const char * fname, const char * mode) { +#ifdef _WIN32 + FILE * file = NULL; + + // convert fname (UTF-8) + wchar_t * wfname = ggml_mbstowcs(fname); + if (wfname) { + // convert mode (ANSI) + wchar_t * wmode = GGML_MALLOC((strlen(mode) + 1) * sizeof(wchar_t)); + wchar_t * wmode_p = wmode; + do { + *wmode_p++ = (wchar_t)*mode; + } while (*mode++); + + // open file + file = _wfopen(wfname, wmode); + + GGML_FREE(wfname); + GGML_FREE(wmode); + } + + return file; +#else + return fopen(fname, mode); +#endif + +} + +static const struct ggml_type_traits type_traits[GGML_TYPE_COUNT] = { + [GGML_TYPE_I8] = { + .type_name = "i8", + .blck_size = 1, + .type_size = sizeof(int8_t), + .is_quantized = false, + }, + [GGML_TYPE_I16] = { + .type_name = "i16", + .blck_size = 1, + .type_size = sizeof(int16_t), + .is_quantized = false, + }, + [GGML_TYPE_I32] = { + .type_name = "i32", + .blck_size = 1, + .type_size = sizeof(int32_t), + .is_quantized = false, + }, + [GGML_TYPE_I64] = { + .type_name = "i64", + .blck_size = 1, + .type_size = sizeof(int64_t), + .is_quantized = false, + }, + [GGML_TYPE_F64] = { + .type_name = "f64", + .blck_size = 1, + .type_size = sizeof(double), + .is_quantized = false, + }, + [GGML_TYPE_F32] = { + .type_name = "f32", + .blck_size = 1, + .type_size = sizeof(float), + .is_quantized = false, + }, + [GGML_TYPE_F16] = { + .type_name = "f16", + .blck_size = 1, + .type_size = sizeof(ggml_fp16_t), + .is_quantized = false, + .to_float = (ggml_to_float_t) ggml_fp16_to_fp32_row, + .from_float_ref = (ggml_from_float_t) ggml_fp32_to_fp16_row, + }, + [GGML_TYPE_Q1_0] = { + .type_name = "q1_0", + .blck_size = QK1_0, + .type_size = sizeof(block_q1_0), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q1_0, + .from_float_ref = (ggml_from_float_t) quantize_row_q1_0_ref, + }, + [GGML_TYPE_Q4_0] = { + .type_name = "q4_0", + .blck_size = QK4_0, + .type_size = sizeof(block_q4_0), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q4_0, + .from_float_ref = (ggml_from_float_t) quantize_row_q4_0_ref, + }, + [GGML_TYPE_Q4_1] = { + .type_name = "q4_1", + .blck_size = QK4_1, + .type_size = sizeof(block_q4_1), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q4_1, + .from_float_ref = (ggml_from_float_t) quantize_row_q4_1_ref, + }, + [4] = { // GGML_TYPE_Q4_2 + .type_name = "DEPRECATED", + .blck_size = 0, + .type_size = 0, + .is_quantized = false, + }, + [5] = { // GGML_TYPE_Q4_3 + .type_name = "DEPRECATED", + .blck_size = 0, + .type_size = 0, + .is_quantized = false, + }, + [GGML_TYPE_Q5_0] = { + .type_name = "q5_0", + .blck_size = QK5_0, + .type_size = sizeof(block_q5_0), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q5_0, + .from_float_ref = (ggml_from_float_t) quantize_row_q5_0_ref, + }, + [GGML_TYPE_Q5_1] = { + .type_name = "q5_1", + .blck_size = QK5_1, + .type_size = sizeof(block_q5_1), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q5_1, + .from_float_ref = (ggml_from_float_t) quantize_row_q5_1_ref, + }, + [GGML_TYPE_Q8_0] = { + .type_name = "q8_0", + .blck_size = QK8_0, + .type_size = sizeof(block_q8_0), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q8_0, + .from_float_ref = (ggml_from_float_t) quantize_row_q8_0_ref, + }, + [GGML_TYPE_Q8_1] = { + .type_name = "q8_1", + .blck_size = QK8_1, + .type_size = sizeof(block_q8_1), + .is_quantized = true, + .from_float_ref = (ggml_from_float_t) quantize_row_q8_1_ref, + }, + [GGML_TYPE_MXFP4] = { + .type_name = "mxfp4", + .blck_size = QK_MXFP4, + .type_size = sizeof(block_mxfp4), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_mxfp4, + .from_float_ref = (ggml_from_float_t)quantize_row_mxfp4_ref, + }, + [GGML_TYPE_NVFP4] = { + .type_name = "nvfp4", + .blck_size = QK_NVFP4, + .type_size = sizeof(block_nvfp4), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_nvfp4, + .from_float_ref = (ggml_from_float_t)quantize_row_nvfp4_ref, + }, + [GGML_TYPE_Q2_K] = { + .type_name = "q2_K", + .blck_size = QK_K, + .type_size = sizeof(block_q2_K), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q2_K, + .from_float_ref = (ggml_from_float_t) quantize_row_q2_K_ref, + }, + [GGML_TYPE_Q3_K] = { + .type_name = "q3_K", + .blck_size = QK_K, + .type_size = sizeof(block_q3_K), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q3_K, + .from_float_ref = (ggml_from_float_t) quantize_row_q3_K_ref, + }, + [GGML_TYPE_Q4_K] = { + .type_name = "q4_K", + .blck_size = QK_K, + .type_size = sizeof(block_q4_K), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q4_K, + .from_float_ref = (ggml_from_float_t) quantize_row_q4_K_ref, + }, + [GGML_TYPE_Q5_K] = { + .type_name = "q5_K", + .blck_size = QK_K, + .type_size = sizeof(block_q5_K), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q5_K, + .from_float_ref = (ggml_from_float_t) quantize_row_q5_K_ref, + }, + [GGML_TYPE_Q6_K] = { + .type_name = "q6_K", + .blck_size = QK_K, + .type_size = sizeof(block_q6_K), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_q6_K, + .from_float_ref = (ggml_from_float_t) quantize_row_q6_K_ref, + }, + [GGML_TYPE_IQ2_XXS] = { + .type_name = "iq2_xxs", + .blck_size = QK_K, + .type_size = sizeof(block_iq2_xxs), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq2_xxs, + .from_float_ref = NULL, + }, + [GGML_TYPE_IQ2_XS] = { + .type_name = "iq2_xs", + .blck_size = QK_K, + .type_size = sizeof(block_iq2_xs), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq2_xs, + .from_float_ref = NULL, + }, + [GGML_TYPE_IQ3_XXS] = { + .type_name = "iq3_xxs", + .blck_size = QK_K, + .type_size = sizeof(block_iq3_xxs), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq3_xxs, + .from_float_ref = (ggml_from_float_t)quantize_row_iq3_xxs_ref, + }, + [GGML_TYPE_IQ3_S] = { + .type_name = "iq3_s", + .blck_size = QK_K, + .type_size = sizeof(block_iq3_s), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq3_s, + .from_float_ref = (ggml_from_float_t)quantize_row_iq3_s_ref, + }, + [GGML_TYPE_IQ2_S] = { + .type_name = "iq2_s", + .blck_size = QK_K, + .type_size = sizeof(block_iq2_s), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq2_s, + .from_float_ref = (ggml_from_float_t)quantize_row_iq2_s_ref, + }, + [GGML_TYPE_IQ1_S] = { + .type_name = "iq1_s", + .blck_size = QK_K, + .type_size = sizeof(block_iq1_s), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq1_s, + .from_float_ref = NULL, + }, + [GGML_TYPE_IQ1_M] = { + .type_name = "iq1_m", + .blck_size = QK_K, + .type_size = sizeof(block_iq1_m), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq1_m, + .from_float_ref = NULL, + }, + [GGML_TYPE_IQ4_NL] = { + .type_name = "iq4_nl", + .blck_size = QK4_NL, + .type_size = sizeof(block_iq4_nl), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq4_nl, + .from_float_ref = (ggml_from_float_t)quantize_row_iq4_nl_ref, + }, + [GGML_TYPE_IQ4_XS] = { + .type_name = "iq4_xs", + .blck_size = QK_K, + .type_size = sizeof(block_iq4_xs), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_iq4_xs, + .from_float_ref = (ggml_from_float_t)quantize_row_iq4_xs_ref, + }, + [GGML_TYPE_Q8_K] = { + .type_name = "q8_K", + .blck_size = QK_K, + .type_size = sizeof(block_q8_K), + .is_quantized = true, + }, + [GGML_TYPE_BF16] = { + .type_name = "bf16", + .blck_size = 1, + .type_size = sizeof(ggml_bf16_t), + .is_quantized = false, + .to_float = (ggml_to_float_t) ggml_bf16_to_fp32_row, + .from_float_ref = (ggml_from_float_t) ggml_fp32_to_bf16_row_ref, + }, + [31] = { // GGML_TYPE_Q4_0_4_4 + .type_name = "TYPE_Q4_0_4_4 REMOVED, use Q4_0 with runtime repacking", + .blck_size = 0, + .type_size = 0, + .is_quantized = false, + }, + [32] = { // GGML_TYPE_Q4_0_4_8 + .type_name = "TYPE_Q4_0_4_8 REMOVED, use Q4_0 with runtime repacking", + .blck_size = 0, + .type_size = 0, + .is_quantized = false, + }, + [33] = { // GGML_TYPE_Q4_0_8_8 + .type_name = "TYPE_Q4_0_8_8 REMOVED, use Q4_0 with runtime repacking", + .blck_size = 0, + .type_size = 0, + .is_quantized = false, + }, + [GGML_TYPE_TQ1_0] = { + .type_name = "tq1_0", + .blck_size = QK_K, + .type_size = sizeof(block_tq1_0), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_tq1_0, + .from_float_ref = (ggml_from_float_t) quantize_row_tq1_0_ref, + }, + [GGML_TYPE_TQ2_0] = { + .type_name = "tq2_0", + .blck_size = QK_K, + .type_size = sizeof(block_tq2_0), + .is_quantized = true, + .to_float = (ggml_to_float_t) dequantize_row_tq2_0, + .from_float_ref = (ggml_from_float_t) quantize_row_tq2_0_ref, + }, + [36] = { // GGML_TYPE_IQ4_NL_4_4 + .type_name = "TYPE_IQ4_NL_4_4 REMOVED, use IQ4_NL with runtime repacking", + .blck_size = 0, + .type_size = 0, + .is_quantized = false, + }, + [37] = { // GGML_TYPE_IQ4_NL_4_8 + .type_name = "TYPE_IQ4_NL_4_8 REMOVED, use IQ4_NL with runtime repacking", + .blck_size = 0, + .type_size = 0, + .is_quantized = false, + }, + [38] = { // GGML_TYPE_IQ4_NL_8_8 + .type_name = "TYPE_IQ4_NL_8_8 REMOVED, use IQ4_NL with runtime repacking", + .blck_size = 0, + .type_size = 0, + .is_quantized = false, + }, + // .to_float / .from_float_ref are deliberately left NULL for the two types + // below. Both carry a single F32 scale for the whole tensor rather than one + // per block, so a row-scoped callback has no way to find it: the scale is + // past the end of the last row, not the row it is handed. Use + // ggml_i8_s_to_float / ggml_i2_s_to_float (ggml-quants.h), which take an + // element count of ggml_nelements(). For the same reason neither type + // appears in ggml_quantize_chunk -- its `result == nrows * row_size` + // invariant cannot hold when a per-tensor scale is in play. + [GGML_TYPE_I8_S] = { + .type_name = "i8_s", + .blck_size = 1, + .type_size = sizeof(int8_t), + .is_quantized = true, + }, + [GGML_TYPE_I2_S] = { + .type_name = "i2_s", + // 128 ternary values packed into 32 bytes: byte gp holds the values at + // positions gp, 32+gp, 64+gp and 96+gp in bit pairs 6,4,2,0. A partial + // group still consumes a full 32 bytes, so the row size is + // ceil(ne0/128)*32 -- declaring the block as 128/32 makes ggml_row_size + // compute exactly that and assert the ne0 % 128 == 0 the packing needs. + .blck_size = 128, + .type_size = 32, + .is_quantized = true, + }, +}; + +// I8_S and I2_S append one F32 per-tensor scale after the payload. Padded to 32 +// bytes so the next tensor in an arena stays aligned. +// +// This is the only place the layout is encoded; ggml_nbytes and +// ggml_new_tensor_impl both go through it. Returns 0 for every other type, so +// no existing type changes size. +size_t ggml_type_extra_bytes(enum ggml_type type) { + switch (type) { + case GGML_TYPE_I8_S: + case GGML_TYPE_I2_S: + return 32; + default: + return 0; + } +} + +const struct ggml_type_traits * ggml_get_type_traits(enum ggml_type type) { + assert(type >= 0); + assert(type < GGML_TYPE_COUNT); + return &type_traits[type]; +} + +// +// ggml object +// + +struct ggml_object { + size_t offs; + size_t size; + + struct ggml_object * next; + + enum ggml_object_type type; + + char padding[4]; +}; + +static const size_t GGML_OBJECT_SIZE = sizeof(struct ggml_object); + +// +// ggml context +// + +struct ggml_context { + size_t mem_size; + void * mem_buffer; + bool mem_buffer_owned; + bool no_alloc; + + int n_objects; + + struct ggml_object * objects_begin; + struct ggml_object * objects_end; +}; + +// +// data types +// + +static const char * GGML_OP_NAME[GGML_OP_COUNT] = { + "NONE", + + "DUP", + "ADD", + "ADD_ID", + "ADD1", + "ACC", + "SUB", + "MUL", + "DIV", + "SQR", + "SQRT", + "LOG", + "SIN", + "COS", + "SUM", + "SUM_ROWS", + "CUMSUM", + "MEAN", + "ARGMAX", + "COUNT_EQUAL", + "REPEAT", + "REPEAT_BACK", + "CONCAT", + "SILU_BACK", + "NORM", + "RMS_NORM", + "RMS_NORM_BACK", + "GROUP_NORM", + "L2_NORM", + + "MUL_MAT", "MUL_MAT_PACK4", - "MUL_MAT_ID", - "OUT_PROD", - - "SCALE", - "SET", - "CPY", - "CONT", - "RESHAPE", - "VIEW", - "PERMUTE", - "TRANSPOSE", - "GET_ROWS", - "GET_ROWS_BACK", - "SET_ROWS", - "DIAG", - "DIAG_MASK_INF", - "DIAG_MASK_ZERO", - "SOFT_MAX", - "SOFT_MAX_BACK", - "ROPE", + "MUL_MAT_ID", + "OUT_PROD", + + "SCALE", + "SET", + "CPY", + "CONT", + "RESHAPE", + "VIEW", + "PERMUTE", + "TRANSPOSE", + "GET_ROWS", + "GET_ROWS_BACK", + "SET_ROWS", + "DIAG", + "DIAG_MASK_INF", + "DIAG_MASK_ZERO", + "SOFT_MAX", + "SOFT_MAX_BACK", + "ROPE", "ROPE_BACK", "CLAMP", "CONV_TRANSPOSE_1D", @@ -1035,22 +1076,22 @@ static const char * GGML_OP_NAME[GGML_OP_COUNT] = { "IM2COL_3D", "COL2IM_1D", "CONV_2D", - "CONV_3D", - "CONV_2D_DW", - "CONV_TRANSPOSE_2D", - "POOL_1D", - "POOL_2D", - "POOL_2D_BACK", - "UPSCALE", - "PAD", - "PAD_REFLECT_1D", - "ROLL", - "ARANGE", - "TIMESTEP_EMBEDDING", - "ARGSORT", - "TOP_K", - "LEAKY_RELU", - "TRI", + "CONV_3D", + "CONV_2D_DW", + "CONV_TRANSPOSE_2D", + "POOL_1D", + "POOL_2D", + "POOL_2D_BACK", + "UPSCALE", + "PAD", + "PAD_REFLECT_1D", + "ROLL", + "ARANGE", + "TIMESTEP_EMBEDDING", + "ARGSORT", + "TOP_K", + "LEAKY_RELU", + "TRI", "FILL", "FLASH_ATTN_EXT", @@ -1059,114 +1100,122 @@ static const char * GGML_OP_NAME[GGML_OP_COUNT] = { "FLASH_ATTN_BACK", "SSM_CONV", "SSM_SCAN", - "WIN_PART", - "WIN_UNPART", - "GET_REL_POS", - "ADD_REL_POS", - "RWKV_WKV6", - "GATED_LINEAR_ATTN", - "RWKV_WKV7", - "SOLVE_TRI", - "GATED_DELTA_NET", - - "UNARY", - - "MAP_CUSTOM1", - "MAP_CUSTOM2", - "MAP_CUSTOM3", - - "CUSTOM", - - "CROSS_ENTROPY_LOSS", - "CROSS_ENTROPY_LOSS_BACK", - "OPT_STEP_ADAMW", + "WIN_PART", + "WIN_UNPART", + "GET_REL_POS", + "ADD_REL_POS", + "RWKV_WKV6", + "GATED_LINEAR_ATTN", + "RWKV_WKV7", + "SOLVE_TRI", + "GATED_DELTA_NET", + + "UNARY", + + "MAP_CUSTOM1", + "MAP_CUSTOM2", + "MAP_CUSTOM3", + + "CUSTOM", + + "CROSS_ENTROPY_LOSS", + "CROSS_ENTROPY_LOSS_BACK", + "OPT_STEP_ADAMW", "OPT_STEP_SGD", "GLU", "CONVROT_LINEAR", + + "ADD_SCALED", + "RMS_NORM_SCALED", + "MUL_MAT_ADD", + "MUL_MAT_ADD_RELU", + "IM2COL_ASYM", + "MUL_MAT_ACC", + "SNAKE_1D", }; - -static_assert(GGML_OP_COUNT == 102, "GGML_OP_COUNT != 102"); - -static const char * GGML_OP_SYMBOL[GGML_OP_COUNT] = { - "none", - - "x", - "x+y", - "x[i]+y", - "x+y", - "view(x,nb,offset)+=y->x", - "x-y", - "x*y", - "x/y", - "x^2", - "√x", - "log(x)", - "sin(x)", - "cos(x)", - "Σx", - "Σx_k", - "cumsum(x)", - "Σx/n", - "argmax(x)", - "count_equal(x)", - "repeat(x)", - "repeat_back(x)", - "concat(x, y)", - "silu_back(x)", - "norm(x)", - "rms_norm(x)", - "rms_norm_back(x)", - "group_norm(x)", - "l2_norm(x)", - + +static_assert(GGML_OP_COUNT == 109, "GGML_OP_COUNT != 109"); + +static const char * GGML_OP_SYMBOL[GGML_OP_COUNT] = { + "none", + + "x", + "x+y", + "x[i]+y", + "x+y", + "view(x,nb,offset)+=y->x", + "x-y", + "x*y", + "x/y", + "x^2", + "√x", + "log(x)", + "sin(x)", + "cos(x)", + "Σx", + "Σx_k", + "cumsum(x)", + "Σx/n", + "argmax(x)", + "count_equal(x)", + "repeat(x)", + "repeat_back(x)", + "concat(x, y)", + "silu_back(x)", + "norm(x)", + "rms_norm(x)", + "rms_norm_back(x)", + "group_norm(x)", + "l2_norm(x)", + + "X*Y", "X*Y", - "X*Y", - "X[i]*Y", - "X*Y", - - "x*v", - "y-\\>view(x)", - "x-\\>y", - "cont(x)", - "reshape(x)", - "view(x)", - "permute(x)", - "transpose(x)", - "get_rows(x)", - "get_rows_back(x)", - "set_rows(x)", - "diag(x)", - "diag_mask_inf(x)", - "diag_mask_zero(x)", - "soft_max(x)", - "soft_max_back(x)", - "rope(x)", - "rope_back(x)", - "clamp(x)", - "conv_transpose_1d(x)", + "X[i]*Y", + "X*Y", + + "x*v", + "y-\\>view(x)", + "x-\\>y", + "cont(x)", + "reshape(x)", + "view(x)", + "permute(x)", + "transpose(x)", + "get_rows(x)", + "get_rows_back(x)", + "set_rows(x)", + "diag(x)", + "diag_mask_inf(x)", + "diag_mask_zero(x)", + "soft_max(x)", + "soft_max_back(x)", + "rope(x)", + "rope_back(x)", + "clamp(x)", + "conv_transpose_1d(x)", "im2col(x)", "im2col(x)", "im2col_back(x)", "im2col_3d(x)", "col2im_1d(x)", "conv_2d(x)", - "conv_3d(x)", - "conv_2d_dw(x)", - "conv_transpose_2d(x)", - "pool_1d(x)", - "pool_2d(x)", - "pool_2d_back(x)", - "upscale(x)", - "pad(x)", - "pad_reflect_1d(x)", - "roll(x)", - "arange(start, stop, step)", - "timestep_embedding(timesteps, dim, max_period)", - "argsort(x)", - "top_k(x)", - "leaky_relu(x)", - "tri(x)", + "conv_3d(x)", + "conv_2d_dw(x)", + "conv_transpose_2d(x)", + "pool_1d(x)", + "pool_2d(x)", + "pool_2d_back(x)", + "upscale(x)", + "pad(x)", + "pad_reflect_1d(x)", + "roll(x)", + "arange(start, stop, step)", + "timestep_embedding(timesteps, dim, max_period)", + "argsort(x)", + "top_k(x)", + "leaky_relu(x)", + "tri(x)", "fill(x, c)", "flash_attn_ext(x)", @@ -1175,6901 +1224,7131 @@ static const char * GGML_OP_SYMBOL[GGML_OP_COUNT] = { "flash_attn_back(x)", "ssm_conv(x)", "ssm_scan(x)", - "win_part(x)", - "win_unpart(x)", - "get_rel_pos(x)", - "add_rel_pos(x)", - "rwkv_wkv6(k, v, r, tf, td, s)", - "gated_linear_attn(k, v, q, gate, s)", - "rwkv_wkv7(r, w, k, v, a, b, s)", - "A X = B, A triangular, solve X", - "gated_delta_net(q, k, v, g, beta, s)", - - "unary(x)", - - "map_custom(x)", - "map_custom(x,y)", - "map_custom(x,y,z)", - - "custom(x)", - - "cross_entropy_loss(x,y)", - "cross_entropy_loss_back(x,y)", - "adamw(x)", + "win_part(x)", + "win_unpart(x)", + "get_rel_pos(x)", + "add_rel_pos(x)", + "rwkv_wkv6(k, v, r, tf, td, s)", + "gated_linear_attn(k, v, q, gate, s)", + "rwkv_wkv7(r, w, k, v, a, b, s)", + "A X = B, A triangular, solve X", + "gated_delta_net(q, k, v, g, beta, s)", + + "unary(x)", + + "map_custom(x)", + "map_custom(x,y)", + "map_custom(x,y,z)", + + "custom(x)", + + "cross_entropy_loss(x,y)", + "cross_entropy_loss_back(x,y)", + "adamw(x)", "sgd(x)", "glu(x)", "convrot_linear(weight_i8, input, weight_scale, bias)", + + "a*scale+b", + "rms_norm(x)*scale", + "a*b+bias", + "relu(a*b+bias)", + "im2col_asym(x)", + "mul_mat_acc(a, b, acc)", + "snake_1d(a, alpha)", }; - -static_assert(GGML_OP_COUNT == 102, "GGML_OP_COUNT != 102"); - -static_assert(GGML_OP_POOL_COUNT == 2, "GGML_OP_POOL_COUNT != 2"); - -static const char * GGML_UNARY_OP_NAME[GGML_UNARY_OP_COUNT] = { - "ABS", - "SGN", - "NEG", - "STEP", - "TANH", - "ELU", - "RELU", - "SIGMOID", - "GELU", - "GELU_QUICK", - "SILU", - "HARDSWISH", - "HARDSIGMOID", - "EXP", - "EXPM1", - "SOFTPLUS", - "GELU_ERF", - "XIELU", - "FLOOR", - "CEIL", - "ROUND", - "TRUNC", -}; - -static_assert(GGML_UNARY_OP_COUNT == 22, "GGML_UNARY_OP_COUNT != 22"); - -static const char * GGML_GLU_OP_NAME[GGML_GLU_OP_COUNT] = { - "REGLU", - "GEGLU", - "SWIGLU", - "SWIGLU_OAI", - "GEGLU_ERF", - "GEGLU_QUICK", -}; - -static_assert(GGML_GLU_OP_COUNT == 6, "GGML_GLU_OP_COUNT != 6"); - - -static_assert(sizeof(struct ggml_object)%GGML_MEM_ALIGN == 0, "ggml_object size must be a multiple of GGML_MEM_ALIGN"); -static_assert(sizeof(struct ggml_tensor)%GGML_MEM_ALIGN == 0, "ggml_tensor size must be a multiple of GGML_MEM_ALIGN"); - - -//////////////////////////////////////////////////////////////////////////////// - -void ggml_print_object(const struct ggml_object * obj) { - GGML_LOG_INFO(" - ggml_object: type = %d, offset = %zu, size = %zu, next = %p\n", - obj->type, obj->offs, obj->size, (const void *) obj->next); -} - -void ggml_print_objects(const struct ggml_context * ctx) { - struct ggml_object * obj = ctx->objects_begin; - - GGML_LOG_INFO("%s: objects in context %p:\n", __func__, (const void *) ctx); - - while (obj != NULL) { - ggml_print_object(obj); - obj = obj->next; - } - - GGML_LOG_INFO("%s: --- end ---\n", __func__); -} - -int64_t ggml_nelements(const struct ggml_tensor * tensor) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return tensor->ne[0]*tensor->ne[1]*tensor->ne[2]*tensor->ne[3]; -} - -int64_t ggml_nrows(const struct ggml_tensor * tensor) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return tensor->ne[1]*tensor->ne[2]*tensor->ne[3]; -} - -size_t ggml_nbytes(const struct ggml_tensor * tensor) { - for (int i = 0; i < GGML_MAX_DIMS; ++i) { - if (tensor->ne[i] <= 0) { - return 0; - } - } - - size_t nbytes; - const size_t blck_size = ggml_blck_size(tensor->type); - if (blck_size == 1) { - nbytes = ggml_type_size(tensor->type); - for (int i = 0; i < GGML_MAX_DIMS; ++i) { - nbytes += (tensor->ne[i] - 1)*tensor->nb[i]; - } - } - else { - nbytes = tensor->ne[0]*tensor->nb[0]/blck_size; - for (int i = 1; i < GGML_MAX_DIMS; ++i) { - nbytes += (tensor->ne[i] - 1)*tensor->nb[i]; - } - } - - return nbytes; -} - -size_t ggml_nbytes_pad(const struct ggml_tensor * tensor) { - return GGML_PAD(ggml_nbytes(tensor), GGML_MEM_ALIGN); -} - -int64_t ggml_blck_size(enum ggml_type type) { - assert(type >= 0); - assert(type < GGML_TYPE_COUNT); - return type_traits[type].blck_size; -} - -size_t ggml_type_size(enum ggml_type type) { - assert(type >= 0); - assert(type < GGML_TYPE_COUNT); - return type_traits[type].type_size; -} - -size_t ggml_row_size(enum ggml_type type, int64_t ne) { - assert(type >= 0); - assert(type < GGML_TYPE_COUNT); - assert(ne % ggml_blck_size(type) == 0); - return ggml_type_size(type)*ne/ggml_blck_size(type); -} - -double ggml_type_sizef(enum ggml_type type) { - assert(type >= 0); - assert(type < GGML_TYPE_COUNT); - return ((double)(type_traits[type].type_size))/type_traits[type].blck_size; -} - -const char * ggml_type_name(enum ggml_type type) { - assert(type >= 0); - assert(type < GGML_TYPE_COUNT); - return type_traits[type].type_name; -} - -bool ggml_is_quantized(enum ggml_type type) { - assert(type >= 0); - assert(type < GGML_TYPE_COUNT); - return type_traits[type].is_quantized; -} - -const char * ggml_op_name(enum ggml_op op) { - return GGML_OP_NAME[op]; -} - -const char * ggml_op_symbol(enum ggml_op op) { - return GGML_OP_SYMBOL[op]; -} - -const char * ggml_unary_op_name(enum ggml_unary_op op) { - return GGML_UNARY_OP_NAME[op]; -} - -const char * ggml_glu_op_name(enum ggml_glu_op op) { - return GGML_GLU_OP_NAME[op]; -} - -const char * ggml_op_desc(const struct ggml_tensor * t) { - if (t->op == GGML_OP_UNARY) { - enum ggml_unary_op uop = ggml_get_unary_op(t); - return ggml_unary_op_name(uop); - } - if (t->op == GGML_OP_GLU) { - enum ggml_glu_op gop = ggml_get_glu_op(t); - return ggml_glu_op_name(gop); - } - return ggml_op_name(t->op); -} - -size_t ggml_element_size(const struct ggml_tensor * tensor) { - return ggml_type_size(tensor->type); -} - -bool ggml_is_scalar(const struct ggml_tensor * tensor) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return tensor->ne[0] == 1 && tensor->ne[1] == 1 && tensor->ne[2] == 1 && tensor->ne[3] == 1; -} - -bool ggml_is_vector(const struct ggml_tensor * tensor) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return tensor->ne[1] == 1 && tensor->ne[2] == 1 && tensor->ne[3] == 1; -} - -bool ggml_is_matrix(const struct ggml_tensor * tensor) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return tensor->ne[2] == 1 && tensor->ne[3] == 1; -} - -bool ggml_is_3d(const struct ggml_tensor * tensor) { - return tensor->ne[3] == 1; -} - -int ggml_n_dims(const struct ggml_tensor * tensor) { - for (int i = GGML_MAX_DIMS - 1; i >= 1; --i) { - if (tensor->ne[i] > 1) { - return i + 1; - } - } - return 1; -} - -enum ggml_type ggml_ftype_to_ggml_type(enum ggml_ftype ftype) { - enum ggml_type wtype = GGML_TYPE_COUNT; - - switch (ftype) { - case GGML_FTYPE_ALL_F32: wtype = GGML_TYPE_F32; break; - case GGML_FTYPE_MOSTLY_F16: wtype = GGML_TYPE_F16; break; - case GGML_FTYPE_MOSTLY_BF16: wtype = GGML_TYPE_BF16; break; - case GGML_FTYPE_MOSTLY_Q4_0: wtype = GGML_TYPE_Q4_0; break; - case GGML_FTYPE_MOSTLY_Q4_1: wtype = GGML_TYPE_Q4_1; break; - case GGML_FTYPE_MOSTLY_Q1_0: wtype = GGML_TYPE_Q1_0; break; - case GGML_FTYPE_MOSTLY_Q5_0: wtype = GGML_TYPE_Q5_0; break; - case GGML_FTYPE_MOSTLY_Q5_1: wtype = GGML_TYPE_Q5_1; break; - case GGML_FTYPE_MOSTLY_Q8_0: wtype = GGML_TYPE_Q8_0; break; - case GGML_FTYPE_MOSTLY_MXFP4: wtype = GGML_TYPE_MXFP4; break; - case GGML_FTYPE_MOSTLY_NVFP4: wtype = GGML_TYPE_NVFP4; break; - case GGML_FTYPE_MOSTLY_Q2_K: wtype = GGML_TYPE_Q2_K; break; - case GGML_FTYPE_MOSTLY_Q3_K: wtype = GGML_TYPE_Q3_K; break; - case GGML_FTYPE_MOSTLY_Q4_K: wtype = GGML_TYPE_Q4_K; break; - case GGML_FTYPE_MOSTLY_Q5_K: wtype = GGML_TYPE_Q5_K; break; - case GGML_FTYPE_MOSTLY_Q6_K: wtype = GGML_TYPE_Q6_K; break; - case GGML_FTYPE_MOSTLY_IQ2_XXS: wtype = GGML_TYPE_IQ2_XXS; break; - case GGML_FTYPE_MOSTLY_IQ2_XS: wtype = GGML_TYPE_IQ2_XS; break; - case GGML_FTYPE_MOSTLY_IQ3_XXS: wtype = GGML_TYPE_IQ3_XXS; break; - case GGML_FTYPE_MOSTLY_IQ1_S: wtype = GGML_TYPE_IQ1_S; break; - case GGML_FTYPE_MOSTLY_IQ1_M: wtype = GGML_TYPE_IQ1_M; break; - case GGML_FTYPE_MOSTLY_IQ4_NL: wtype = GGML_TYPE_IQ4_NL; break; - case GGML_FTYPE_MOSTLY_IQ4_XS: wtype = GGML_TYPE_IQ4_XS; break; - case GGML_FTYPE_MOSTLY_IQ3_S: wtype = GGML_TYPE_IQ3_S; break; - case GGML_FTYPE_MOSTLY_IQ2_S: wtype = GGML_TYPE_IQ2_S; break; - case GGML_FTYPE_UNKNOWN: wtype = GGML_TYPE_COUNT; break; - case GGML_FTYPE_MOSTLY_Q4_1_SOME_F16: wtype = GGML_TYPE_COUNT; break; - } - - GGML_ASSERT(wtype != GGML_TYPE_COUNT); - - return wtype; -} - -size_t ggml_tensor_overhead(void) { - return GGML_OBJECT_SIZE + GGML_TENSOR_SIZE; -} - -bool ggml_is_transposed(const struct ggml_tensor * tensor) { - return tensor->nb[0] > tensor->nb[1]; -} - -static bool ggml_is_contiguous_n(const struct ggml_tensor * tensor, int n) { - size_t next_nb = ggml_type_size(tensor->type); - if (tensor->ne[0] != ggml_blck_size(tensor->type) && tensor->nb[0] != next_nb) { - return false; - } - next_nb *= tensor->ne[0]/ggml_blck_size(tensor->type); - for (int i = 1; i < GGML_MAX_DIMS; i++) { - if (i > n) { - if (tensor->ne[i] != 1 && tensor->nb[i] != next_nb) { - return false; - } - next_nb *= tensor->ne[i]; - } else { - // this dimension does not need to be contiguous - next_nb = tensor->ne[i]*tensor->nb[i]; - } - } - return true; -} - -bool ggml_is_contiguous(const struct ggml_tensor * tensor) { - return ggml_is_contiguous_0(tensor); -} - -bool ggml_is_contiguous_0(const struct ggml_tensor * tensor) { - return ggml_is_contiguous_n(tensor, 0); -} - -bool ggml_is_contiguous_1(const struct ggml_tensor * tensor) { - return ggml_is_contiguous_n(tensor, 1); -} - -bool ggml_is_contiguous_2(const struct ggml_tensor * tensor) { - return ggml_is_contiguous_n(tensor, 2); -} - -bool ggml_is_contiguously_allocated(const struct ggml_tensor * tensor) { - return ggml_nbytes(tensor) == ggml_nelements(tensor) * ggml_type_size(tensor->type)/ggml_blck_size(tensor->type); -} - -bool ggml_is_permuted(const struct ggml_tensor * tensor) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return tensor->nb[0] > tensor->nb[1] || tensor->nb[1] > tensor->nb[2] || tensor->nb[2] > tensor->nb[3]; -} - -bool ggml_is_contiguous_channels(const struct ggml_tensor * tensor) { - return - tensor->nb[0] > tensor->nb[2] && - tensor->nb[1] > tensor->nb[0] && - tensor->nb[2] == ggml_type_size(tensor->type); -} - -bool ggml_is_contiguous_rows(const struct ggml_tensor * tensor) { - return - tensor->ne[0] == ggml_blck_size(tensor->type) || - tensor->nb[0] == ggml_type_size(tensor->type); -} - -static inline bool ggml_is_padded_1d(const struct ggml_tensor * tensor) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return - tensor->nb[0] == ggml_type_size(tensor->type) && - tensor->nb[2] == tensor->nb[1]*tensor->ne[1] && - tensor->nb[3] == tensor->nb[2]*tensor->ne[2]; -} - -bool ggml_is_empty(const struct ggml_tensor * tensor) { - for (int i = 0; i < GGML_MAX_DIMS; ++i) { - if (tensor->ne[i] == 0) { - // empty if any dimension has no elements - return true; - } - } - return false; -} - -bool ggml_are_same_shape(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return - (t0->ne[0] == t1->ne[0]) && - (t0->ne[1] == t1->ne[1]) && - (t0->ne[2] == t1->ne[2]) && - (t0->ne[3] == t1->ne[3]); -} - -bool ggml_are_same_stride(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return - (t0->nb[0] == t1->nb[0]) && - (t0->nb[1] == t1->nb[1]) && - (t0->nb[2] == t1->nb[2]) && - (t0->nb[3] == t1->nb[3]); -} - -bool ggml_is_view(const struct ggml_tensor * t) { - return ggml_impl_is_view(t); -} - -// check if t1 can be represented as a repetition of t0 -bool ggml_can_repeat(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return ggml_is_empty(t0) ? ggml_is_empty(t1) : - (t1->ne[0]%t0->ne[0] == 0) && - (t1->ne[1]%t0->ne[1] == 0) && - (t1->ne[2]%t0->ne[2] == 0) && - (t1->ne[3]%t0->ne[3] == 0); -} - -static inline bool ggml_can_repeat_rows(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return (t0->ne[0] == t1->ne[0]) && ggml_can_repeat(t0, t1); -} - -// assert that pointer is aligned to GGML_MEM_ALIGN -#define GGML_ASSERT_ALIGNED(ptr) \ - GGML_ASSERT(((uintptr_t) (ptr))%GGML_MEM_ALIGN == 0) - -//////////////////////////////////////////////////////////////////////////////// - -struct ggml_context * ggml_init(struct ggml_init_params params) { - static bool is_first_call = true; - - ggml_critical_section_start(); - - if (is_first_call) { - // initialize time system (required on Windows) - ggml_time_init(); - - is_first_call = false; - } - - ggml_critical_section_end(); - - struct ggml_context * ctx = GGML_MALLOC(sizeof(struct ggml_context)); - - // allow to call ggml_init with 0 size - if (params.mem_size == 0) { - params.mem_size = GGML_MEM_ALIGN; - } - - const size_t mem_size = params.mem_buffer ? params.mem_size : GGML_PAD(params.mem_size, GGML_MEM_ALIGN); - - *ctx = (struct ggml_context) { - /*.mem_size =*/ mem_size, - /*.mem_buffer =*/ params.mem_buffer ? params.mem_buffer : ggml_aligned_malloc(mem_size), - /*.mem_buffer_owned =*/ params.mem_buffer ? false : true, - /*.no_alloc =*/ params.no_alloc, - /*.n_objects =*/ 0, - /*.objects_begin =*/ NULL, - /*.objects_end =*/ NULL, - }; - - GGML_ASSERT(ctx->mem_buffer != NULL); - - GGML_ASSERT_ALIGNED(ctx->mem_buffer); - - GGML_PRINT_DEBUG("%s: context initialized\n", __func__); - - return ctx; -} - -void ggml_reset(struct ggml_context * ctx) { - if (ctx == NULL) { - return; - } - - ctx->n_objects = 0; - ctx->objects_begin = NULL; - ctx->objects_end = NULL; -} - -void ggml_free(struct ggml_context * ctx) { - if (ctx == NULL) { - return; - } - - if (ctx->mem_buffer_owned) { - ggml_aligned_free(ctx->mem_buffer, ctx->mem_size); - } - - GGML_FREE(ctx); -} - -size_t ggml_used_mem(const struct ggml_context * ctx) { - return ctx->objects_end == NULL ? 0 : ctx->objects_end->offs + ctx->objects_end->size; -} - -bool ggml_get_no_alloc(struct ggml_context * ctx) { - return ctx->no_alloc; -} - -void ggml_set_no_alloc(struct ggml_context * ctx, bool no_alloc) { - ctx->no_alloc = no_alloc; -} - -void * ggml_get_mem_buffer(const struct ggml_context * ctx) { - return ctx->mem_buffer; -} - -size_t ggml_get_mem_size(const struct ggml_context * ctx) { - return ctx->mem_size; -} - -size_t ggml_get_max_tensor_size(const struct ggml_context * ctx) { - size_t max_size = 0; - - for (struct ggml_tensor * tensor = ggml_get_first_tensor(ctx); tensor != NULL; tensor = ggml_get_next_tensor(ctx, tensor)) { - size_t bytes = ggml_nbytes(tensor); - max_size = MAX(max_size, bytes); - } - - return max_size; -} - -//////////////////////////////////////////////////////////////////////////////// - -static struct ggml_object * ggml_new_object(struct ggml_context * ctx, enum ggml_object_type type, size_t size) { - // always insert objects at the end of the context's memory pool - struct ggml_object * obj_cur = ctx->objects_end; - - const size_t cur_offs = obj_cur == NULL ? 0 : obj_cur->offs; - const size_t cur_size = obj_cur == NULL ? 0 : obj_cur->size; - const size_t cur_end = cur_offs + cur_size; - - // align to GGML_MEM_ALIGN - GGML_ASSERT(size <= SIZE_MAX - (GGML_MEM_ALIGN - 1)); - size_t size_needed = GGML_PAD(size, GGML_MEM_ALIGN); - - char * const mem_buffer = ctx->mem_buffer; - struct ggml_object * const obj_new = (struct ggml_object *)(mem_buffer + cur_end); - - // integer overflow checks - if (cur_end > SIZE_MAX - size_needed) { - GGML_LOG_WARN("%s: overflow detected in cur_end (%zu) + size_needed (%zu)\n", __func__, cur_end, size_needed); - return NULL; - } - if (cur_end + size_needed > SIZE_MAX - GGML_OBJECT_SIZE) { - GGML_LOG_WARN("%s: overflow detected in cur_end (%zu) + size_needed (%zu) + GGML_OBJECT_SIZE (%zu)\n", __func__, - cur_end, size_needed, (size_t) GGML_OBJECT_SIZE); - return NULL; - } - - if (cur_end + size_needed + GGML_OBJECT_SIZE > ctx->mem_size) { - GGML_LOG_WARN("%s: not enough space in the context's memory pool (needed %zu, available %zu)\n", - __func__, cur_end + size_needed + GGML_OBJECT_SIZE, ctx->mem_size); -#ifndef NDEBUG - GGML_ABORT("not enough space in the context's memory pool"); -#endif - return NULL; - } - - *obj_new = (struct ggml_object) { - .offs = cur_end + GGML_OBJECT_SIZE, - .size = size_needed, - .next = NULL, - .type = type, - }; - - GGML_ASSERT_ALIGNED(mem_buffer + obj_new->offs); - - if (obj_cur != NULL) { - obj_cur->next = obj_new; - } else { - // this is the first object in this context - ctx->objects_begin = obj_new; - } - - ctx->objects_end = obj_new; - - //printf("%s: inserted new object at %zu, size = %zu\n", __func__, cur_end, obj_new->size); - - return obj_new; -} - -static struct ggml_tensor * ggml_new_tensor_impl( - struct ggml_context * ctx, - enum ggml_type type, - int n_dims, - const int64_t * ne, - struct ggml_tensor * view_src, - size_t view_offs) { - - GGML_ASSERT(type >= 0 && type < GGML_TYPE_COUNT); - GGML_ASSERT(n_dims >= 1 && n_dims <= GGML_MAX_DIMS); - - // find the base tensor and absolute offset - if (view_src != NULL && view_src->view_src != NULL) { - view_offs += view_src->view_offs; - view_src = view_src->view_src; - } - - size_t data_size = ggml_row_size(type, ne[0]); - for (int i = 1; i < n_dims; i++) { - data_size *= ne[i]; - } - - GGML_ASSERT(view_src == NULL || data_size == 0 || data_size + view_offs <= ggml_nbytes(view_src)); - - void * data = view_src != NULL ? view_src->data : NULL; - if (data != NULL) { - data = (char *) data + view_offs; - } - - size_t obj_alloc_size = 0; - - if (view_src == NULL && !ctx->no_alloc) { - // allocate tensor data in the context's memory pool - obj_alloc_size = data_size; - } - - GGML_ASSERT(GGML_TENSOR_SIZE <= SIZE_MAX - obj_alloc_size); - - struct ggml_object * const obj_new = ggml_new_object(ctx, GGML_OBJECT_TYPE_TENSOR, GGML_TENSOR_SIZE + obj_alloc_size); - GGML_ASSERT(obj_new); - - struct ggml_tensor * const result = (struct ggml_tensor *)((char *)ctx->mem_buffer + obj_new->offs); - - *result = (struct ggml_tensor) { - /*.type =*/ type, - /*.buffer =*/ NULL, - /*.ne =*/ { 1, 1, 1, 1 }, - /*.nb =*/ { 0, 0, 0, 0 }, - /*.op =*/ GGML_OP_NONE, - /*.op_params =*/ { 0 }, - /*.flags =*/ 0, - /*.src =*/ { NULL }, - /*.view_src =*/ view_src, - /*.view_offs =*/ view_offs, - /*.data =*/ obj_alloc_size > 0 ? (void *)(result + 1) : data, - /*.name =*/ { 0 }, - /*.extra =*/ NULL, - /*.padding =*/ { 0 }, - }; - - // TODO: this should not be needed as long as we don't rely on aligned SIMD loads - //GGML_ASSERT_ALIGNED(result->data); - - for (int i = 0; i < n_dims; i++) { - result->ne[i] = ne[i]; - } - - result->nb[0] = ggml_type_size(type); - result->nb[1] = result->nb[0]*(result->ne[0]/ggml_blck_size(type)); - for (int i = 2; i < GGML_MAX_DIMS; i++) { - result->nb[i] = result->nb[i - 1]*result->ne[i - 1]; - } - - ctx->n_objects++; - - return result; -} - -struct ggml_tensor * ggml_new_tensor( - struct ggml_context * ctx, - enum ggml_type type, - int n_dims, - const int64_t * ne) { - return ggml_new_tensor_impl(ctx, type, n_dims, ne, NULL, 0); -} - -struct ggml_tensor * ggml_new_tensor_1d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0) { - return ggml_new_tensor(ctx, type, 1, &ne0); -} - -struct ggml_tensor * ggml_new_tensor_2d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0, - int64_t ne1) { - const int64_t ne[2] = { ne0, ne1 }; - return ggml_new_tensor(ctx, type, 2, ne); -} - -struct ggml_tensor * ggml_new_tensor_3d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0, - int64_t ne1, - int64_t ne2) { - const int64_t ne[3] = { ne0, ne1, ne2 }; - return ggml_new_tensor(ctx, type, 3, ne); -} - -struct ggml_tensor * ggml_new_tensor_4d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3) { - const int64_t ne[4] = { ne0, ne1, ne2, ne3 }; - return ggml_new_tensor(ctx, type, 4, ne); -} - -void * ggml_new_buffer(struct ggml_context * ctx, size_t nbytes) { - struct ggml_object * obj = ggml_new_object(ctx, GGML_OBJECT_TYPE_WORK_BUFFER, nbytes); - - return (uint8_t *)ctx->mem_buffer + obj->offs; -} - -struct ggml_tensor * ggml_dup_tensor(struct ggml_context * ctx, const struct ggml_tensor * src) { - return ggml_new_tensor(ctx, src->type, GGML_MAX_DIMS, src->ne); -} - -void ggml_unravel_index(const struct ggml_tensor * tensor, int64_t i, int64_t * i0, int64_t * i1, int64_t * i2, int64_t * i3) { - const int64_t ne2 = tensor->ne[2]; - const int64_t ne1 = tensor->ne[1]; - const int64_t ne0 = tensor->ne[0]; - - const int64_t i3_ = (i/(ne2*ne1*ne0)); - const int64_t i2_ = (i - i3_*ne2*ne1*ne0)/(ne1*ne0); - const int64_t i1_ = (i - i3_*ne2*ne1*ne0 - i2_*ne1*ne0)/ne0; - const int64_t i0_ = (i - i3_*ne2*ne1*ne0 - i2_*ne1*ne0 - i1_*ne0); - - if (i0) { - * i0 = i0_; - } - if (i1) { - * i1 = i1_; - } - if (i2) { - * i2 = i2_; - } - if (i3) { - * i3 = i3_; - } -} - -void * ggml_get_data(const struct ggml_tensor * tensor) { - return tensor->data; -} - -float * ggml_get_data_f32(const struct ggml_tensor * tensor) { - assert(tensor->type == GGML_TYPE_F32); - return (float *)(tensor->data); -} - -enum ggml_unary_op ggml_get_unary_op(const struct ggml_tensor * tensor) { - GGML_ASSERT(tensor->op == GGML_OP_UNARY); - return (enum ggml_unary_op) ggml_get_op_params_i32(tensor, 0); -} - -enum ggml_glu_op ggml_get_glu_op(const struct ggml_tensor * tensor) { - GGML_ASSERT(tensor->op == GGML_OP_GLU); - return (enum ggml_glu_op) ggml_get_op_params_i32(tensor, 0); -} - -const char * ggml_get_name(const struct ggml_tensor * tensor) { - return tensor->name; -} - -struct ggml_tensor * ggml_set_name(struct ggml_tensor * tensor, const char * name) { - size_t i; - for (i = 0; i < sizeof(tensor->name) - 1 && name[i] != '\0'; i++) { - tensor->name[i] = name[i]; - } - tensor->name[i] = '\0'; - return tensor; -} - -struct ggml_tensor * ggml_format_name(struct ggml_tensor * tensor, const char * fmt, ...) { - va_list args; - va_start(args, fmt); - vsnprintf(tensor->name, sizeof(tensor->name), fmt, args); - va_end(args); - return tensor; -} - -struct ggml_tensor * ggml_view_tensor( - struct ggml_context * ctx, - struct ggml_tensor * src) { - struct ggml_tensor * result = ggml_new_tensor_impl(ctx, src->type, GGML_MAX_DIMS, src->ne, src, 0); - ggml_format_name(result, "%s (view)", src->name); - - for (int i = 0; i < GGML_MAX_DIMS; i++) { - result->nb[i] = src->nb[i]; - } - - return result; -} - -struct ggml_tensor * ggml_get_first_tensor(const struct ggml_context * ctx) { - struct ggml_object * obj = ctx->objects_begin; - - char * const mem_buffer = ctx->mem_buffer; - - while (obj != NULL) { - if (obj->type == GGML_OBJECT_TYPE_TENSOR) { - return (struct ggml_tensor *)(mem_buffer + obj->offs); - } - - obj = obj->next; - } - - return NULL; -} - -struct ggml_tensor * ggml_get_next_tensor(const struct ggml_context * ctx, struct ggml_tensor * tensor) { - struct ggml_object * obj = (struct ggml_object *) ((char *)tensor - GGML_OBJECT_SIZE); - obj = obj->next; - - char * const mem_buffer = ctx->mem_buffer; - - while (obj != NULL) { - if (obj->type == GGML_OBJECT_TYPE_TENSOR) { - return (struct ggml_tensor *)(mem_buffer + obj->offs); - } - - obj = obj->next; - } - - return NULL; -} - -struct ggml_tensor * ggml_get_tensor(struct ggml_context * ctx, const char * name) { - struct ggml_object * obj = ctx->objects_begin; - - char * const mem_buffer = ctx->mem_buffer; - - while (obj != NULL) { - if (obj->type == GGML_OBJECT_TYPE_TENSOR) { - struct ggml_tensor * cur = (struct ggml_tensor *)(mem_buffer + obj->offs); - if (strcmp(cur->name, name) == 0) { - return cur; - } - } - - obj = obj->next; - } - - return NULL; -} - -//////////////////////////////////////////////////////////////////////////////// - -// ggml_dup - -static struct ggml_tensor * ggml_dup_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_DUP; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_dup( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_dup_impl(ctx, a, false); -} - -struct ggml_tensor * ggml_dup_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_dup_impl(ctx, a, true); -} - -// ggml_add - -static struct ggml_tensor * ggml_add_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - bool inplace) { - GGML_ASSERT(ggml_can_repeat(b, a)); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_ADD; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_add( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_add_impl(ctx, a, b, false); -} - -struct ggml_tensor * ggml_add_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_add_impl(ctx, a, b, true); -} - -// ggml_add_cast - -static struct ggml_tensor * ggml_add_cast_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - enum ggml_type type) { - // TODO: support less-strict constraint - // GGML_ASSERT(ggml_can_repeat(b, a)); - GGML_ASSERT(ggml_can_repeat_rows(b, a)); - - // currently only supported for quantized input and f16 - GGML_ASSERT(ggml_is_quantized(a->type) || - a->type == GGML_TYPE_F16 || - a->type == GGML_TYPE_BF16); - - struct ggml_tensor * result = ggml_new_tensor(ctx, type, GGML_MAX_DIMS, a->ne); - - result->op = GGML_OP_ADD; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_add_cast( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - enum ggml_type type) { - return ggml_add_cast_impl(ctx, a, b, type); -} - -struct ggml_tensor * ggml_add_id( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * ids) { - - GGML_ASSERT(a->ne[0] == b->ne[0]); - GGML_ASSERT(a->ne[1] == ids->ne[0]); - GGML_ASSERT(a->ne[2] == ids->ne[1]); - GGML_ASSERT(ids->type == GGML_TYPE_I32); - - struct ggml_tensor * result = ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_ADD_ID; - result->src[0] = a; - result->src[1] = b; - result->src[2] = ids; - - return result; -} - -// ggml_add1 - -static struct ggml_tensor * ggml_add1_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - bool inplace) { - GGML_ASSERT(ggml_is_scalar(b)); - GGML_ASSERT(ggml_is_padded_1d(a)); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_ADD1; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_add1( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_add1_impl(ctx, a, b, false); -} - -struct ggml_tensor * ggml_add1_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_add1_impl(ctx, a, b, true); -} - -// ggml_acc - -static struct ggml_tensor * ggml_acc_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset, - bool inplace) { - GGML_ASSERT(ggml_nelements(b) <= ggml_nelements(a)); - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(a->type == GGML_TYPE_F32); - GGML_ASSERT(b->type == GGML_TYPE_F32); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - int32_t params[] = { nb1, nb2, nb3, offset, inplace ? 1 : 0 }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_ACC; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_acc( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset) { - return ggml_acc_impl(ctx, a, b, nb1, nb2, nb3, offset, false); -} - -struct ggml_tensor * ggml_acc_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset) { - return ggml_acc_impl(ctx, a, b, nb1, nb2, nb3, offset, true); -} - -// ggml_sub - -static struct ggml_tensor * ggml_sub_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - bool inplace) { - GGML_ASSERT(ggml_can_repeat(b, a)); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_SUB; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_sub( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_sub_impl(ctx, a, b, false); -} - -struct ggml_tensor * ggml_sub_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_sub_impl(ctx, a, b, true); -} - -// ggml_mul - -static struct ggml_tensor * ggml_mul_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - bool inplace) { - GGML_ASSERT(ggml_can_repeat(b, a)); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_MUL; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_mul( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_mul_impl(ctx, a, b, false); -} - -struct ggml_tensor * ggml_mul_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_mul_impl(ctx, a, b, true); -} - -// ggml_div - -static struct ggml_tensor * ggml_div_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - bool inplace) { - GGML_ASSERT(ggml_can_repeat(b, a)); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_DIV; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_div( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_div_impl(ctx, a, b, false); -} - -struct ggml_tensor * ggml_div_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_div_impl(ctx, a, b, true); -} - -// ggml_sqr - -static struct ggml_tensor * ggml_sqr_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_SQR; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_sqr( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_sqr_impl(ctx, a, false); -} - -struct ggml_tensor * ggml_sqr_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_sqr_impl(ctx, a, true); -} - -// ggml_sqrt - -static struct ggml_tensor * ggml_sqrt_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_SQRT; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_sqrt( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_sqrt_impl(ctx, a, false); -} - -struct ggml_tensor * ggml_sqrt_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_sqrt_impl(ctx, a, true); -} - -// ggml_log - -static struct ggml_tensor * ggml_log_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_LOG; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_log( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_log_impl(ctx, a, false); -} - -struct ggml_tensor * ggml_log_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_log_impl(ctx, a, true); -} - -struct ggml_tensor * ggml_expm1( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_EXPM1); -} - -struct ggml_tensor * ggml_expm1_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_EXPM1); -} - -struct ggml_tensor * ggml_softplus( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_SOFTPLUS); -} - -struct ggml_tensor * ggml_softplus_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_SOFTPLUS); -} - -// ggml_sin - -static struct ggml_tensor * ggml_sin_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_SIN; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_sin( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_sin_impl(ctx, a, false); -} - -struct ggml_tensor * ggml_sin_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_sin_impl(ctx, a, true); -} - -// ggml_cos - -static struct ggml_tensor * ggml_cos_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_COS; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_cos( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_cos_impl(ctx, a, false); -} - -struct ggml_tensor * ggml_cos_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_cos_impl(ctx, a, true); -} - -// ggml_sum - -struct ggml_tensor * ggml_sum( - struct ggml_context * ctx, - struct ggml_tensor * a) { - struct ggml_tensor * result = ggml_new_tensor_1d(ctx, a->type, 1); - - result->op = GGML_OP_SUM; - result->src[0] = a; - - return result; -} - -// ggml_sum_rows - -struct ggml_tensor * ggml_sum_rows( - struct ggml_context * ctx, - struct ggml_tensor * a) { - int64_t ne[GGML_MAX_DIMS] = { 1 }; - for (int i = 1; i < GGML_MAX_DIMS; ++i) { - ne[i] = a->ne[i]; - } - - struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, GGML_MAX_DIMS, ne); - - result->op = GGML_OP_SUM_ROWS; - result->src[0] = a; - - return result; -} - -// ggml_cumsum - -struct ggml_tensor * ggml_cumsum( - struct ggml_context * ctx, - struct ggml_tensor * a) { - GGML_ASSERT(a->type == GGML_TYPE_F32); - - struct ggml_tensor * result = ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_CUMSUM; - result->src[0] = a; - - return result; -} - -// ggml_mean - -struct ggml_tensor * ggml_mean( - struct ggml_context * ctx, - struct ggml_tensor * a) { - int64_t ne[4] = { 1, a->ne[1], a->ne[2], a->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - result->op = GGML_OP_MEAN; - result->src[0] = a; - - return result; -} - -// ggml_argmax - -struct ggml_tensor * ggml_argmax( - struct ggml_context * ctx, - struct ggml_tensor * a) { - GGML_ASSERT(ggml_is_matrix(a)); - GGML_ASSERT(a->ne[0] <= INT32_MAX); - - struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, a->ne[1]); - - result->op = GGML_OP_ARGMAX; - result->src[0] = a; - - return result; -} - -// ggml_count_equal - -struct ggml_tensor * ggml_count_equal( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_are_same_shape(a, b)); - - struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_I64, 1); - - result->op = GGML_OP_COUNT_EQUAL; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_repeat - -struct ggml_tensor * ggml_repeat( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_can_repeat(a, b)); - - struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, GGML_MAX_DIMS, b->ne); - - result->op = GGML_OP_REPEAT; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_repeat_4d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, int64_t ne1, int64_t ne2, int64_t ne3) { - const bool can_repeat = ggml_is_empty(a) || ( - (ne0 % a->ne[0] == 0) && - (ne1 % a->ne[1] == 0) && - (ne2 % a->ne[2] == 0) && - (ne3 % a->ne[3] == 0) - ); - GGML_ASSERT(can_repeat); - - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, ne0, ne1, ne2, ne3); - - result->op = GGML_OP_REPEAT; - result->src[0] = a; - - return result; -} - -// ggml_repeat_back - -struct ggml_tensor * ggml_repeat_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_can_repeat(b, a)); - - struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, GGML_MAX_DIMS, b->ne); - - result->op = GGML_OP_REPEAT_BACK; - result->src[0] = a; - - return result; -} - -// ggml_concat - -struct ggml_tensor * ggml_concat( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int dim) { - GGML_ASSERT(dim >= 0 && dim < GGML_MAX_DIMS); - GGML_ASSERT(a->type == b->type); - - int64_t ne[GGML_MAX_DIMS]; - for (int d = 0; d < GGML_MAX_DIMS; ++d) { - if (d == dim) { - ne[d] = a->ne[d] + b->ne[d]; - continue; - } - GGML_ASSERT(a->ne[d] == b->ne[d]); - ne[d] = a->ne[d]; - } - - struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, GGML_MAX_DIMS, ne); - - ggml_set_op_params_i32(result, 0, dim); - - result->op = GGML_OP_CONCAT; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_abs - -struct ggml_tensor * ggml_abs( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_ABS); -} - -struct ggml_tensor * ggml_abs_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_ABS); -} - -// ggml_sgn - -struct ggml_tensor * ggml_sgn( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_SGN); -} - -struct ggml_tensor * ggml_sgn_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_SGN); -} - -// ggml_neg - -struct ggml_tensor * ggml_neg( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_NEG); -} - -struct ggml_tensor * ggml_neg_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_NEG); -} - -// ggml_step - -struct ggml_tensor * ggml_step( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_STEP); -} - -struct ggml_tensor * ggml_step_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_STEP); -} - -// ggml_tanh - -struct ggml_tensor * ggml_tanh( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_TANH); -} - -struct ggml_tensor * ggml_tanh_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_TANH); -} - -// ggml_elu - -struct ggml_tensor * ggml_elu( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_ELU); -} - -struct ggml_tensor * ggml_elu_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_ELU); -} - -// ggml_relu - -struct ggml_tensor * ggml_relu( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_RELU); -} - -struct ggml_tensor * ggml_relu_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_RELU); -} - -// ggml_leaky_relu - -struct ggml_tensor * ggml_leaky_relu( - struct ggml_context * ctx, - struct ggml_tensor * a, - float negative_slope, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - ggml_set_op_params(result, &negative_slope, sizeof(negative_slope)); - - result->op = GGML_OP_LEAKY_RELU; - result->src[0] = a; - - return result; -} - -// ggml_sigmoid - -struct ggml_tensor * ggml_sigmoid( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_SIGMOID); -} - -struct ggml_tensor * ggml_sigmoid_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_SIGMOID); -} - -// ggml_gelu - -struct ggml_tensor * ggml_gelu( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_GELU); -} - -struct ggml_tensor * ggml_gelu_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_GELU); -} - -// ggml_gelu_erf - -struct ggml_tensor * ggml_gelu_erf( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_GELU_ERF); -} - -struct ggml_tensor * ggml_gelu_erf_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_GELU_ERF); -} - -// ggml_gelu_quick - -struct ggml_tensor * ggml_gelu_quick( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_GELU_QUICK); -} - -struct ggml_tensor * ggml_gelu_quick_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_GELU_QUICK); -} - -// ggml_silu - -struct ggml_tensor * ggml_silu( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_SILU); -} - -struct ggml_tensor * ggml_silu_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_SILU); -} - -// ggml_xielu - -struct ggml_tensor * ggml_xielu( - struct ggml_context * ctx, - struct ggml_tensor * a, - float alpha_n, - float alpha_p, - float beta, - float eps) { - struct ggml_tensor * result = ggml_dup_tensor(ctx, a); - - ggml_set_op_params_i32(result, 0, (int32_t) GGML_UNARY_OP_XIELU); - ggml_set_op_params_f32(result, 1, beta + ggml_compute_softplus_f32(alpha_n)); - ggml_set_op_params_f32(result, 2, ggml_compute_softplus_f32(alpha_p)); - ggml_set_op_params_f32(result, 3, beta); - ggml_set_op_params_f32(result, 4, eps); - - result->op = GGML_OP_UNARY; - result->src[0] = a; - - return result; -} - -// ggml_silu_back - -struct ggml_tensor * ggml_silu_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - struct ggml_tensor * result = ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_SILU_BACK; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml hardswish - -struct ggml_tensor * ggml_hardswish( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_HARDSWISH); -} - -// ggml hardsigmoid - -struct ggml_tensor * ggml_hardsigmoid( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_HARDSIGMOID); -} - -// ggml exp - -struct ggml_tensor * ggml_exp( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_EXP); -} - -struct ggml_tensor * ggml_exp_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_EXP); -} - -// ggml_glu - -static struct ggml_tensor * ggml_glu_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - enum ggml_glu_op op, - bool swapped) { - GGML_ASSERT(ggml_is_contiguous_1(a)); - - if (b) { - GGML_ASSERT(ggml_is_contiguous_1(b)); - GGML_ASSERT(ggml_are_same_shape(a, b)); - GGML_ASSERT(a->type == b->type); - } - - int64_t ne[GGML_MAX_DIMS] = { a->ne[0] / 2 }; for (int i = 1; i < GGML_MAX_DIMS; i++) ne[i] = a->ne[i]; - struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, GGML_MAX_DIMS, b ? a->ne : ne, NULL, 0); - - ggml_set_op_params_i32(result, 0, (int32_t) op); - ggml_set_op_params_i32(result, 1, (int32_t) swapped); - - result->op = GGML_OP_GLU; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_floor - -struct ggml_tensor * ggml_floor( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_FLOOR); -} - -struct ggml_tensor * ggml_floor_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_FLOOR); -} - -// ggml_ceil - -struct ggml_tensor * ggml_ceil( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_CEIL); -} - -struct ggml_tensor * ggml_ceil_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_CEIL); -} - -//ggml_round - -struct ggml_tensor * ggml_round( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_ROUND); -} - -struct ggml_tensor * ggml_round_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_ROUND); -} - -//ggml_trunc - -struct ggml_tensor * ggml_trunc( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary(ctx, a, GGML_UNARY_OP_TRUNC); -} - -struct ggml_tensor * ggml_trunc_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_TRUNC); -} - -struct ggml_tensor * ggml_glu( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_glu_op op, - bool swapped) { - return ggml_glu_impl(ctx, a, NULL, op, swapped); -} - -struct ggml_tensor * ggml_glu_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - enum ggml_glu_op op) { - return ggml_glu_impl(ctx, a, b, op, false); -} - -// ggml_reglu - -struct ggml_tensor * ggml_reglu( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_REGLU, false); -} - -struct ggml_tensor * ggml_reglu_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_REGLU, true); -} - -struct ggml_tensor * ggml_reglu_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_REGLU, false); -} - -// ggml_geglu - -struct ggml_tensor * ggml_geglu( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU, false); -} - -struct ggml_tensor * ggml_geglu_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU, true); -} - -struct ggml_tensor * ggml_geglu_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_GEGLU, false); -} - -// ggml_swiglu - -struct ggml_tensor * ggml_swiglu( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_SWIGLU, false); -} - -struct ggml_tensor * ggml_swiglu_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_SWIGLU, true); -} - -struct ggml_tensor * ggml_swiglu_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_SWIGLU, false); -} - -// ggml_geglu_erf - -struct ggml_tensor * ggml_geglu_erf( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU_ERF, false); -} - -struct ggml_tensor * ggml_geglu_erf_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU_ERF, true); -} - -struct ggml_tensor * ggml_geglu_erf_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_GEGLU_ERF, false); -} - -// ggml_geglu_quick - -struct ggml_tensor * ggml_geglu_quick( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU_QUICK, false); -} - -struct ggml_tensor * ggml_geglu_quick_swapped( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU_QUICK, true); -} - -struct ggml_tensor * ggml_geglu_quick_split( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_GEGLU_QUICK, false); -} - -struct ggml_tensor * ggml_swiglu_oai( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float alpha, - float limit) { - struct ggml_tensor * result = ggml_glu_impl(ctx, a, b, GGML_GLU_OP_SWIGLU_OAI, false); - ggml_set_op_params_f32(result, 2, alpha); - ggml_set_op_params_f32(result, 3, limit); - - return result; -} - -// ggml_norm - -static struct ggml_tensor * ggml_norm_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - ggml_set_op_params(result, &eps, sizeof(eps)); - - result->op = GGML_OP_NORM; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_norm( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps) { - return ggml_norm_impl(ctx, a, eps, false); -} - -struct ggml_tensor * ggml_norm_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps) { - return ggml_norm_impl(ctx, a, eps, true); -} - -// ggml_rms_norm - -static struct ggml_tensor * ggml_rms_norm_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - ggml_set_op_params(result, &eps, sizeof(eps)); - - result->op = GGML_OP_RMS_NORM; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_rms_norm( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps) { - return ggml_rms_norm_impl(ctx, a, eps, false); -} - -struct ggml_tensor * ggml_rms_norm_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps) { - return ggml_rms_norm_impl(ctx, a, eps, true); -} - -// ggml_rms_norm_back - -struct ggml_tensor * ggml_rms_norm_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float eps) { - struct ggml_tensor * result = ggml_dup_tensor(ctx, a); - - ggml_set_op_params(result, &eps, sizeof(eps)); - - result->op = GGML_OP_RMS_NORM_BACK; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_group_norm - -static struct ggml_tensor * ggml_group_norm_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_groups, - float eps, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - ggml_set_op_params_i32(result, 0, n_groups); - ggml_set_op_params_f32(result, 1, eps); - - result->op = GGML_OP_GROUP_NORM; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_group_norm( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_groups, - float eps) { - return ggml_group_norm_impl(ctx, a, n_groups, eps, false); -} - -struct ggml_tensor * ggml_group_norm_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_groups, - float eps) { - return ggml_group_norm_impl(ctx, a, n_groups, eps, true); -} - -// ggml_l2_norm - -static struct ggml_tensor * ggml_l2_norm_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - ggml_set_op_params_f32(result, 0, eps); - - result->op = GGML_OP_L2_NORM; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_l2_norm( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps) { - return ggml_l2_norm_impl(ctx, a, eps, false); -} - -struct ggml_tensor * ggml_l2_norm_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float eps) { - return ggml_l2_norm_impl(ctx, a, eps, true); -} - -// ggml_mul_mat - -static inline bool ggml_can_mul_mat(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return (t0->ne[0] == t1->ne[0]) && - (t1->ne[2]%t0->ne[2] == 0) && // verify t0 is broadcastable - (t1->ne[3]%t0->ne[3] == 0); -} - -struct ggml_tensor * ggml_mul_mat( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_can_mul_mat(a, b)); - GGML_ASSERT(!ggml_is_transposed(a)); - - const int64_t ne[4] = { a->ne[1], b->ne[1], b->ne[2], b->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - result->op = GGML_OP_MUL_MAT; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_mul_mat_pack4( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_can_mul_mat(a, b)); - GGML_ASSERT(!ggml_is_transposed(a)); - GGML_ASSERT(a->ne[1] % 4 == 0); - const int64_t ne[4] = { a->ne[1], b->ne[1], b->ne[2], b->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); +static_assert(GGML_OP_COUNT == 109, "GGML_OP_COUNT != 109"); - result->op = GGML_OP_MUL_MAT_PACK4; - result->src[0] = a; - result->src[1] = b; +static_assert(GGML_OP_POOL_COUNT == 2, "GGML_OP_POOL_COUNT != 2"); - return result; -} +static const char * GGML_UNARY_OP_NAME[GGML_UNARY_OP_COUNT] = { + "ABS", + "SGN", + "NEG", + "STEP", + "TANH", + "ELU", + "RELU", + "SIGMOID", + "GELU", + "GELU_QUICK", + "SILU", + "HARDSWISH", + "HARDSIGMOID", + "EXP", + "EXPM1", + "SOFTPLUS", + "GELU_ERF", + "XIELU", + "FLOOR", + "CEIL", + "ROUND", + "TRUNC", + "ROUND_BF16", +}; -void ggml_mul_mat_set_prec( - struct ggml_tensor * a, - enum ggml_prec prec) { - GGML_ASSERT(a->op == GGML_OP_MUL_MAT || a->op == GGML_OP_MUL_MAT_PACK4); - - const int32_t prec_i32 = (int32_t) prec; - - ggml_set_op_params_i32(a, 0, prec_i32); -} - -void ggml_mul_mat_set_hint( - struct ggml_tensor * a, - enum ggml_op_hint hint) { - GGML_ASSERT(a->op == GGML_OP_MUL_MAT || a->op == GGML_OP_MUL_MAT_PACK4); - - const int32_t hint_i32 = (int32_t) hint; - - ggml_set_op_params_i32(a, 1, hint_i32); -} - -// ggml_mul_mat_id - -/* - c = ggml_mul_mat_id(ctx, as, b, ids); - - as -> [cols, rows, n_expert] - b -> [cols, n_expert_used, n_tokens] - ids -> [n_expert_used, n_tokens] (i32) - c -> [rows, n_expert_used, n_tokens] - - in b, n_expert_used can be broadcasted to match the n_expert_used of ids - - c ~= as[:,:,i] @ b[:,i%r,t], i = ids[e,t] for all e,t in ids -*/ -struct ggml_tensor * ggml_mul_mat_id( - struct ggml_context * ctx, - struct ggml_tensor * as, - struct ggml_tensor * b, - struct ggml_tensor * ids) { - GGML_ASSERT(!ggml_is_transposed(as)); - GGML_ASSERT(ids->type == GGML_TYPE_I32); - - GGML_ASSERT(as->ne[3] == 1); // as is 3d (one matrix per expert) - GGML_ASSERT(b->ne[3] == 1); // b is 3d - GGML_ASSERT(ids->ne[2] == 1 && ids->ne[3] == 1); // ids is 2d - GGML_ASSERT(ids->ne[1] == b->ne[2]); // must have an expert list per b row - GGML_ASSERT(as->ne[0] == b->ne[0]); // can_mul_mat - GGML_ASSERT(ids->ne[0] % b->ne[1] == 0); // can broadcast - - const int64_t ne[4] = { as->ne[1], ids->ne[0], b->ne[2], 1 }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - result->op = GGML_OP_MUL_MAT_ID; - result->src[0] = as; - result->src[1] = b; - result->src[2] = ids; - - return result; -} - -// ggml_out_prod - -static inline bool ggml_can_out_prod(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { - static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - - return (t0->ne[1] == t1->ne[1]) && - (t1->ne[2]%t0->ne[2] == 0) && // verify t0 is broadcastable - (t1->ne[3]%t0->ne[3] == 0); -} - -struct ggml_tensor * ggml_out_prod( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_can_out_prod(a, b)); - GGML_ASSERT(!ggml_is_transposed(a)); - - // a is broadcastable to b for ne[2] and ne[3] -> use b->ne[2] and b->ne[3] - const int64_t ne[4] = { a->ne[0], b->ne[0], b->ne[2], b->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - result->op = GGML_OP_OUT_PROD; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_scale - -static struct ggml_tensor * ggml_scale_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s, - float b, - bool inplace) { - GGML_ASSERT(ggml_is_padded_1d(a)); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - float params[2] = { s, b }; - ggml_set_op_params(result, ¶ms, sizeof(params)); - - result->op = GGML_OP_SCALE; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_scale( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s) { - return ggml_scale_impl(ctx, a, s, 0.0, false); -} - -struct ggml_tensor * ggml_scale_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s) { - return ggml_scale_impl(ctx, a, s, 0.0, true); -} - -struct ggml_tensor * ggml_scale_bias( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s, - float b) { - return ggml_scale_impl(ctx, a, s, b, false); -} - -struct ggml_tensor * ggml_scale_bias_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float s, - float b) { - return ggml_scale_impl(ctx, a, s, b, true); -} - -// ggml_set - -static struct ggml_tensor * ggml_set_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset, - bool inplace) { - GGML_ASSERT(ggml_nelements(a) >= ggml_nelements(b)); - - // make a view of the destination - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - GGML_ASSERT(offset < (size_t)(1 << 30)); - int32_t params[] = { nb1, nb2, nb3, offset, inplace ? 1 : 0 }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_SET; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_set( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset) { - return ggml_set_impl(ctx, a, b, nb1, nb2, nb3, offset, false); -} - -struct ggml_tensor * ggml_set_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset) { - return ggml_set_impl(ctx, a, b, nb1, nb2, nb3, offset, true); -} - -struct ggml_tensor * ggml_set_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t offset) { - return ggml_set_impl(ctx, a, b, a->nb[1], a->nb[2], a->nb[3], offset, false); -} - -struct ggml_tensor * ggml_set_1d_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t offset) { - return ggml_set_impl(ctx, a, b, a->nb[1], a->nb[2], a->nb[3], offset, true); -} - -struct ggml_tensor * ggml_set_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t offset) { - return ggml_set_impl(ctx, a, b, nb1, a->nb[2], a->nb[3], offset, false); -} - -struct ggml_tensor * ggml_set_2d_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - size_t nb1, - size_t offset) { - return ggml_set_impl(ctx, a, b, nb1, a->nb[2], a->nb[3], offset, true); -} - -// ggml_cpy - -static struct ggml_tensor * ggml_cpy_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_nelements(a) == ggml_nelements(b)); - - // make a view of the destination - struct ggml_tensor * result = ggml_view_tensor(ctx, b); - if (strlen(b->name) > 0) { - ggml_format_name(result, "%s (copy of %s)", b->name, a->name); - } else { - ggml_format_name(result, "%s (copy)", a->name); - } - - result->op = GGML_OP_CPY; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_cpy( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_cpy_impl(ctx, a, b); -} - -struct ggml_tensor * ggml_cast( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_type type) { - struct ggml_tensor * result = ggml_new_tensor(ctx, type, GGML_MAX_DIMS, a->ne); - ggml_format_name(result, "%s (copy)", a->name); - - result->op = GGML_OP_CPY; - result->src[0] = a; - result->src[1] = result; // note: this self-reference might seem redundant, but it's actually needed by some - // backends for consistency with ggml_cpy_impl() above - - return result; -} - -// ggml_cont - -static struct ggml_tensor * ggml_cont_impl( - struct ggml_context * ctx, - struct ggml_tensor * a) { - struct ggml_tensor * result = ggml_dup_tensor(ctx, a); - ggml_format_name(result, "%s (cont)", a->name); - - result->op = GGML_OP_CONT; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_cont( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_cont_impl(ctx, a); -} - -// make contiguous, with new shape -GGML_API struct ggml_tensor * ggml_cont_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0) { - return ggml_cont_4d(ctx, a, ne0, 1, 1, 1); -} - -GGML_API struct ggml_tensor * ggml_cont_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1) { - return ggml_cont_4d(ctx, a, ne0, ne1, 1, 1); -} - -GGML_API struct ggml_tensor * ggml_cont_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2) { - return ggml_cont_4d(ctx, a, ne0, ne1, ne2, 1); -} - -struct ggml_tensor * ggml_cont_4d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3) { - GGML_ASSERT(ggml_nelements(a) == (ne0*ne1*ne2*ne3)); - - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, ne0, ne1, ne2, ne3); - ggml_format_name(result, "%s (cont)", a->name); - - result->op = GGML_OP_CONT; - result->src[0] = a; - - return result; -} - -// ggml_reshape - -struct ggml_tensor * ggml_reshape( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_is_contiguous(a)); - // as only the shape of b is relevant, and not its memory layout, b is allowed to be non contiguous. - GGML_ASSERT(ggml_nelements(a) == ggml_nelements(b)); - - struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, GGML_MAX_DIMS, b->ne, a, 0); - ggml_format_name(result, "%s (reshaped)", a->name); - - result->op = GGML_OP_RESHAPE; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_reshape_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0) { - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(ggml_nelements(a) == ne0); - - const int64_t ne[1] = { ne0 }; - struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, 1, ne, a, 0); - ggml_format_name(result, "%s (reshaped)", a->name); - - result->op = GGML_OP_RESHAPE; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_reshape_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1) { - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(ggml_nelements(a) == ne0*ne1); - - const int64_t ne[2] = { ne0, ne1 }; - struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, 2, ne, a, 0); - ggml_format_name(result, "%s (reshaped)", a->name); - - result->op = GGML_OP_RESHAPE; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_reshape_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2) { - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(ggml_nelements(a) == ne0*ne1*ne2); - - const int64_t ne[3] = { ne0, ne1, ne2 }; - struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, 3, ne, a, 0); - ggml_format_name(result, "%s (reshaped)", a->name); - - result->op = GGML_OP_RESHAPE; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_reshape_4d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3) { - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(ggml_nelements(a) == ne0*ne1*ne2*ne3); - - const int64_t ne[4] = { ne0, ne1, ne2, ne3 }; - struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, 4, ne, a, 0); - ggml_format_name(result, "%s (reshaped)", a->name); - - result->op = GGML_OP_RESHAPE; - result->src[0] = a; - - return result; -} - -static struct ggml_tensor * ggml_view_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_dims, - const int64_t * ne, - size_t offset) { - struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, n_dims, ne, a, offset); - ggml_format_name(result, "%s (view)", a->name); - - ggml_set_op_params(result, &offset, sizeof(offset)); - - result->op = GGML_OP_VIEW; - result->src[0] = a; - - return result; -} - -// ggml_view_1d - -struct ggml_tensor * ggml_view_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - size_t offset) { - struct ggml_tensor * result = ggml_view_impl(ctx, a, 1, &ne0, offset); - - return result; -} - -// ggml_view_2d - -struct ggml_tensor * ggml_view_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - size_t nb1, - size_t offset) { - const int64_t ne[2] = { ne0, ne1 }; - - struct ggml_tensor * result = ggml_view_impl(ctx, a, 2, ne, offset); - - result->nb[1] = nb1; - result->nb[2] = result->nb[1]*ne1; - result->nb[3] = result->nb[2]; - - return result; -} - -// ggml_view_3d - -struct ggml_tensor * ggml_view_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - size_t nb1, - size_t nb2, - size_t offset) { - const int64_t ne[3] = { ne0, ne1, ne2 }; - - struct ggml_tensor * result = ggml_view_impl(ctx, a, 3, ne, offset); - - result->nb[1] = nb1; - result->nb[2] = nb2; - result->nb[3] = result->nb[2]*ne2; - - return result; -} - -// ggml_view_4d - -struct ggml_tensor * ggml_view_4d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3, - size_t nb1, - size_t nb2, - size_t nb3, - size_t offset) { - const int64_t ne[4] = { ne0, ne1, ne2, ne3 }; - - struct ggml_tensor * result = ggml_view_impl(ctx, a, 4, ne, offset); - - result->nb[1] = nb1; - result->nb[2] = nb2; - result->nb[3] = nb3; - - return result; -} - -// ggml_permute - -struct ggml_tensor * ggml_permute( - struct ggml_context * ctx, - struct ggml_tensor * a, - int axis0, - int axis1, - int axis2, - int axis3) { - GGML_ASSERT(axis0 >= 0 && axis0 < GGML_MAX_DIMS); - GGML_ASSERT(axis1 >= 0 && axis1 < GGML_MAX_DIMS); - GGML_ASSERT(axis2 >= 0 && axis2 < GGML_MAX_DIMS); - GGML_ASSERT(axis3 >= 0 && axis3 < GGML_MAX_DIMS); - - GGML_ASSERT(axis0 != axis1); - GGML_ASSERT(axis0 != axis2); - GGML_ASSERT(axis0 != axis3); - GGML_ASSERT(axis1 != axis2); - GGML_ASSERT(axis1 != axis3); - GGML_ASSERT(axis2 != axis3); - - struct ggml_tensor * result = ggml_view_tensor(ctx, a); - ggml_format_name(result, "%s (permuted)", a->name); - - int ne[GGML_MAX_DIMS]; - int nb[GGML_MAX_DIMS]; - - ne[axis0] = a->ne[0]; - ne[axis1] = a->ne[1]; - ne[axis2] = a->ne[2]; - ne[axis3] = a->ne[3]; - - nb[axis0] = a->nb[0]; - nb[axis1] = a->nb[1]; - nb[axis2] = a->nb[2]; - nb[axis3] = a->nb[3]; - - result->ne[0] = ne[0]; - result->ne[1] = ne[1]; - result->ne[2] = ne[2]; - result->ne[3] = ne[3]; - - result->nb[0] = nb[0]; - result->nb[1] = nb[1]; - result->nb[2] = nb[2]; - result->nb[3] = nb[3]; - - result->op = GGML_OP_PERMUTE; - result->src[0] = a; - - int32_t params[] = { axis0, axis1, axis2, axis3 }; - ggml_set_op_params(result, params, sizeof(params)); - - return result; -} - -// ggml_transpose - -struct ggml_tensor * ggml_transpose( - struct ggml_context * ctx, - struct ggml_tensor * a) { - struct ggml_tensor * result = ggml_view_tensor(ctx, a); - ggml_format_name(result, "%s (transposed)", a->name); - - result->ne[0] = a->ne[1]; - result->ne[1] = a->ne[0]; - - result->nb[0] = a->nb[1]; - result->nb[1] = a->nb[0]; - - result->op = GGML_OP_TRANSPOSE; - result->src[0] = a; - - return result; -} - -// ggml_get_rows - -struct ggml_tensor * ggml_get_rows( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(a->ne[2] == b->ne[1]); - GGML_ASSERT(a->ne[3] == b->ne[2]); - GGML_ASSERT(b->ne[3] == 1); - GGML_ASSERT(b->type == GGML_TYPE_I32); - - // TODO: implement non F32 return - enum ggml_type type = GGML_TYPE_F32; - if (a->type == GGML_TYPE_I32) { - type = a->type; - } - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, type, a->ne[0], b->ne[0], b->ne[1], b->ne[2]); - - result->op = GGML_OP_GET_ROWS; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_get_rows_back - -struct ggml_tensor * ggml_get_rows_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c) { - GGML_ASSERT(ggml_is_matrix(a) && ggml_is_vector(b) && b->type == GGML_TYPE_I32); - GGML_ASSERT(ggml_is_matrix(c) && (a->ne[0] == c->ne[0])); - - // TODO: implement non F32 return - //struct ggml_tensor * result = ggml_new_tensor_2d(ctx, a->type, a->ne[0], b->ne[0]); - struct ggml_tensor * result = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, c->ne[0], c->ne[1]); - - result->op = GGML_OP_GET_ROWS_BACK; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_set_rows - -struct ggml_tensor * ggml_set_rows( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c) { - GGML_ASSERT(a->ne[0] == b->ne[0]); - GGML_ASSERT(a->ne[2] == b->ne[2]); - GGML_ASSERT(a->ne[3] == b->ne[3]); - GGML_ASSERT(b->ne[1] == c->ne[0]); - GGML_ASSERT(b->ne[2] % c->ne[1] == 0); - GGML_ASSERT(b->ne[3] % c->ne[2] == 0); - GGML_ASSERT(c->ne[3] == 1); - GGML_ASSERT(b->type == GGML_TYPE_F32); - GGML_ASSERT(c->type == GGML_TYPE_I64 || c->type == GGML_TYPE_I32); - - GGML_ASSERT(ggml_is_contiguous_rows(a)); - GGML_ASSERT(ggml_is_contiguous_rows(b)); - - struct ggml_tensor * result = ggml_view_tensor(ctx, a); - - result->op = GGML_OP_SET_ROWS; - result->src[0] = b; - result->src[1] = c; - result->src[2] = a; // note: order is weird due to legacy reasons (https://github.com/ggml-org/llama.cpp/pull/16063#discussion_r2385795931) - - return result; -} - -// ggml_diag - -struct ggml_tensor * ggml_diag( - struct ggml_context * ctx, - struct ggml_tensor * a) { - GGML_ASSERT(a->ne[1] == 1); - - const int64_t ne[4] = { a->ne[0], a->ne[0], a->ne[2], a->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, 4, ne); - - result->op = GGML_OP_DIAG; - result->src[0] = a; - - return result; -} - -// ggml_diag_mask_inf - -static struct ggml_tensor * ggml_diag_mask_inf_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - int32_t params[] = { n_past }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_DIAG_MASK_INF; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_diag_mask_inf( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past) { - return ggml_diag_mask_inf_impl(ctx, a, n_past, false); -} - -struct ggml_tensor * ggml_diag_mask_inf_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past) { - return ggml_diag_mask_inf_impl(ctx, a, n_past, true); -} - -// ggml_diag_mask_zero - -static struct ggml_tensor * ggml_diag_mask_zero_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - int32_t params[] = { n_past }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_DIAG_MASK_ZERO; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_diag_mask_zero( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past) { - return ggml_diag_mask_zero_impl(ctx, a, n_past, false); -} - -struct ggml_tensor * ggml_diag_mask_zero_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - int n_past) { - return ggml_diag_mask_zero_impl(ctx, a, n_past, true); -} - -// ggml_soft_max - -static struct ggml_tensor * ggml_soft_max_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * mask, - float scale, - float max_bias, - bool inplace) { - GGML_ASSERT(ggml_is_contiguous(a)); - - if (mask) { - GGML_ASSERT(mask->type == GGML_TYPE_F16 || mask->type == GGML_TYPE_F32); - GGML_ASSERT(ggml_is_contiguous(mask)); - GGML_ASSERT(mask->ne[0] == a->ne[0]); - GGML_ASSERT(mask->ne[1] >= a->ne[1]); - GGML_ASSERT(a->ne[2]%mask->ne[2] == 0); - GGML_ASSERT(a->ne[3]%mask->ne[3] == 0); - } - - if (max_bias > 0.0f) { - GGML_ASSERT(mask); - } - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - float params[] = { scale, max_bias }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_SOFT_MAX; - result->src[0] = a; - result->src[1] = mask; - - return result; -} - -struct ggml_tensor * ggml_soft_max( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_soft_max_impl(ctx, a, NULL, 1.0f, 0.0f, false); -} - -struct ggml_tensor * ggml_soft_max_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a) { - return ggml_soft_max_impl(ctx, a, NULL, 1.0f, 0.0f, true); -} - -struct ggml_tensor * ggml_soft_max_ext( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * mask, - float scale, - float max_bias) { - return ggml_soft_max_impl(ctx, a, mask, scale, max_bias, false); -} - -struct ggml_tensor * ggml_soft_max_ext_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * mask, - float scale, - float max_bias) { - return ggml_soft_max_impl(ctx, a, mask, scale, max_bias, true); -} - -void ggml_soft_max_add_sinks( - struct ggml_tensor * a, - struct ggml_tensor * sinks) { - if (!sinks) { - a->src[2] = NULL; - return; - } - - GGML_ASSERT(a->op == GGML_OP_SOFT_MAX); - GGML_ASSERT(a->src[2] == NULL); - GGML_ASSERT(a->src[0]->ne[2] == sinks->ne[0]); - GGML_ASSERT(sinks->type == GGML_TYPE_F32); - - a->src[2] = sinks; -} - -// ggml_soft_max_ext_back - -static struct ggml_tensor * ggml_soft_max_ext_back_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float scale, - float max_bias, - bool inplace) { - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - result->op = GGML_OP_SOFT_MAX_BACK; - result->src[0] = a; - result->src[1] = b; - - memcpy((float *) result->op_params + 0, &scale, sizeof(float)); - memcpy((float *) result->op_params + 1, &max_bias, sizeof(float)); - - return result; -} - -struct ggml_tensor * ggml_soft_max_ext_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float scale, - float max_bias) { - return ggml_soft_max_ext_back_impl(ctx, a, b, scale, max_bias, false); -} - -struct ggml_tensor * ggml_soft_max_ext_back_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - float scale, - float max_bias) { - return ggml_soft_max_ext_back_impl(ctx, a, b, scale, max_bias, true); -} - -// ggml_rope - -static struct ggml_tensor * ggml_rope_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int sections[GGML_MROPE_SECTIONS], - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow, - bool inplace) { - GGML_ASSERT((mode & 1) == 0 && "mode & 1 == 1 is no longer supported"); - - GGML_ASSERT(ggml_is_vector(b)); - GGML_ASSERT(b->type == GGML_TYPE_I32); - - bool mrope_used = mode & GGML_ROPE_TYPE_MROPE; - if (mrope_used) { - GGML_ASSERT(a->ne[2] * 4 == b->ne[0]); // mrope expecting 4 position ids per token - } else { - GGML_ASSERT(a->ne[2] == b->ne[0]); - } - - if (c) { - GGML_ASSERT(c->type == GGML_TYPE_F32); - GGML_ASSERT(c->ne[0] >= n_dims / 2); - } - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - int32_t params[15] = { /*n_past*/ 0, n_dims, mode, /*n_ctx*/ 0, n_ctx_orig }; - memcpy(params + 5, &freq_base, sizeof(float)); - memcpy(params + 6, &freq_scale, sizeof(float)); - memcpy(params + 7, &ext_factor, sizeof(float)); - memcpy(params + 8, &attn_factor, sizeof(float)); - memcpy(params + 9, &beta_fast, sizeof(float)); - memcpy(params + 10, &beta_slow, sizeof(float)); - if (mrope_used && sections) { - memcpy(params + 11, sections, sizeof(int32_t) * GGML_MROPE_SECTIONS); - } else { - memset(params + 11, 0, sizeof(int32_t) * GGML_MROPE_SECTIONS); - } - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_ROPE; - result->src[0] = a; - result->src[1] = b; - result->src[2] = c; - - return result; -} - -struct ggml_tensor * ggml_rope( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int n_dims, - int mode) { - return ggml_rope_impl( - ctx, a, b, NULL, n_dims, NULL, mode, 0, 10000.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, false - ); -} - -struct ggml_tensor * ggml_rope_multi( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int sections[GGML_MROPE_SECTIONS], - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow) { - return ggml_rope_impl( - ctx, a, b, c, n_dims, sections, mode, n_ctx_orig, freq_base, freq_scale, - ext_factor, attn_factor, beta_fast, beta_slow, false - ); -} - -struct ggml_tensor * ggml_rope_multi_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int sections[GGML_MROPE_SECTIONS], - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow) { - return ggml_rope_impl( - ctx, a, b, c, n_dims, sections, mode, n_ctx_orig, freq_base, freq_scale, - ext_factor, attn_factor, beta_fast, beta_slow, true - ); -} - -struct ggml_tensor * ggml_rope_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int n_dims, - int mode) { - return ggml_rope_impl( - ctx, a, b, NULL, n_dims, NULL, mode, 0, 10000.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, true - ); -} - -struct ggml_tensor * ggml_rope_ext( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow) { - return ggml_rope_impl( - ctx, a, b, c, n_dims, NULL, mode, n_ctx_orig, freq_base, freq_scale, - ext_factor, attn_factor, beta_fast, beta_slow, false - ); -} - -struct ggml_tensor * ggml_rope_ext_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow) { - return ggml_rope_impl( - ctx, a, b, c, n_dims, NULL, mode, n_ctx_orig, freq_base, freq_scale, - ext_factor, attn_factor, beta_fast, beta_slow, true - ); -} - -struct ggml_tensor * ggml_rope_custom( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow) { - return ggml_rope_impl( - ctx, a, b, NULL, n_dims, NULL, mode, n_ctx_orig, freq_base, freq_scale, - ext_factor, attn_factor, beta_fast, beta_slow, false - ); -} - -struct ggml_tensor * ggml_rope_custom_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow) { - return ggml_rope_impl( - ctx, a, b, NULL, n_dims, NULL, mode, n_ctx_orig, freq_base, freq_scale, - ext_factor, attn_factor, beta_fast, beta_slow, true - ); -} - -// Apparently solving `n_rot = 2pi * x * base^((2 * max_pos_emb) / n_dims)` for x, we get -// `corr_dim(n_rot) = n_dims * log(max_pos_emb / (n_rot * 2pi)) / (2 * log(base))` -static float ggml_rope_yarn_corr_dim(int n_dims, int n_ctx_orig, float n_rot, float base) { - return n_dims * logf(n_ctx_orig / (n_rot * 2 * (float)M_PI)) / (2 * logf(base)); -} - -void ggml_rope_yarn_corr_dims( - int n_dims, int n_ctx_orig, float freq_base, float beta_fast, float beta_slow, float dims[2] -) { - // start and end correction dims - float start = floorf(ggml_rope_yarn_corr_dim(n_dims, n_ctx_orig, beta_fast, freq_base)); - float end = ceilf(ggml_rope_yarn_corr_dim(n_dims, n_ctx_orig, beta_slow, freq_base)); - dims[0] = MAX(0, start); - dims[1] = MIN(n_dims - 1, end); -} - -// ggml_rope_back - -struct ggml_tensor * ggml_rope_ext_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow) { - struct ggml_tensor * result = ggml_rope_ext( - ctx, a, b, c, n_dims, mode, n_ctx_orig, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow); - result->op = GGML_OP_ROPE_BACK; - return result; -} - -struct ggml_tensor * ggml_rope_multi_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - int n_dims, - int sections[4], - int mode, - int n_ctx_orig, - float freq_base, - float freq_scale, - float ext_factor, - float attn_factor, - float beta_fast, - float beta_slow) { - struct ggml_tensor * result = ggml_rope_multi( - ctx, a, b, c, n_dims, sections, mode, n_ctx_orig, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow); - result->op = GGML_OP_ROPE_BACK; - return result; -} -// ggml_clamp - -struct ggml_tensor * ggml_clamp( - struct ggml_context * ctx, - struct ggml_tensor * a, - float min, - float max) { - // TODO: when implement backward, fix this: - struct ggml_tensor * result = ggml_view_tensor(ctx, a); - - float params[] = { min, max }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_CLAMP; - result->src[0] = a; - - return result; -} - -static int64_t ggml_calc_conv_output_size(int64_t ins, int64_t ks, int s, int p, int d) { - return (ins + 2 * p - d * (ks - 1) - 1) / s + 1; -} - -// im2col: [N, IC, IH, IW] => [N, OH, OW, IC*KH*KW] -// a: [OC,IC, KH, KW] -// b: [N, IC, IH, IW] -// result: [N, OH, OW, IC*KH*KW] -struct ggml_tensor * ggml_im2col( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int s1, - int p0, - int p1, - int d0, - int d1, - bool is_2D, - enum ggml_type dst_type) { - if (is_2D) { - GGML_ASSERT(a->ne[2] == b->ne[2]); - } else { - //GGML_ASSERT(b->ne[1] % a->ne[1] == 0); - GGML_ASSERT(b->ne[1] == a->ne[1]); - GGML_ASSERT(b->ne[3] == 1); - } - - const int64_t OH = is_2D ? ggml_calc_conv_output_size(b->ne[1], a->ne[1], s1, p1, d1) : 0; - const int64_t OW = ggml_calc_conv_output_size(b->ne[0], a->ne[0], s0, p0, d0); - - GGML_ASSERT((!is_2D || OH > 0) && "b too small compared to a"); - GGML_ASSERT((OW > 0) && "b too small compared to a"); - - const int64_t ne[4] = { - is_2D ? (a->ne[2] * a->ne[1] * a->ne[0]) : a->ne[1] * a->ne[0], - OW, - is_2D ? OH : b->ne[2], - is_2D ? b->ne[3] : 1, - }; - - struct ggml_tensor * result = ggml_new_tensor(ctx, dst_type, 4, ne); - int32_t params[] = { s0, s1, p0, p1, d0, d1, (is_2D ? 1 : 0) }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_IM2COL; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_im2col_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int64_t * ne, - int s0, - int s1, - int p0, - int p1, - int d0, - int d1, - bool is_2D) { - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - int32_t params[] = { s0, s1, p0, p1, d0, d1, (is_2D ? 1 : 0) }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_IM2COL_BACK; - result->src[0] = a; - result->src[1] = b; +static_assert(GGML_UNARY_OP_COUNT == 23, "GGML_UNARY_OP_COUNT != 23"); - return result; -} +static const char * GGML_GLU_OP_NAME[GGML_GLU_OP_COUNT] = { + "REGLU", + "GEGLU", + "SWIGLU", + "SWIGLU_OAI", + "GEGLU_ERF", + "GEGLU_QUICK", +}; -struct ggml_tensor * ggml_col2im_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int s0, - int oc, - int p0) { - GGML_ASSERT(ggml_is_matrix(a)); - GGML_ASSERT(s0 > 0); - GGML_ASSERT(oc > 0); - GGML_ASSERT(a->ne[0] % oc == 0); +static_assert(GGML_GLU_OP_COUNT == 6, "GGML_GLU_OP_COUNT != 6"); - const int64_t k = a->ne[0] / oc; - const int64_t ne[4] = { - (a->ne[1] - 1) * s0 + k - 2 * p0, - oc, - 1, - 1, - }; - GGML_ASSERT(ne[0] > 0); - struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, 2, ne); - int32_t params[] = {s0, oc, p0}; - ggml_set_op_params(result, params, sizeof(params)); +static_assert(sizeof(struct ggml_object)%GGML_MEM_ALIGN == 0, "ggml_object size must be a multiple of GGML_MEM_ALIGN"); +static_assert(sizeof(struct ggml_tensor)%GGML_MEM_ALIGN == 0, "ggml_tensor size must be a multiple of GGML_MEM_ALIGN"); - result->op = GGML_OP_COL2IM_1D; - result->src[0] = a; - return result; -} +//////////////////////////////////////////////////////////////////////////////// -static struct ggml_tensor * ggml_im2col_fast_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int s1, - int p0, - int p1, - int d0, - int d1, - bool is_2D, - enum ggml_type dst_type) { - struct ggml_tensor * result = ggml_im2col(ctx, a, b, s0, s1, p0, p1, d0, d1, is_2D, dst_type); - result->op = GGML_OP_IM2COL_FAST_1D; - return result; +void ggml_print_object(const struct ggml_object * obj) { + GGML_LOG_INFO(" - ggml_object: type = %d, offset = %zu, size = %zu, next = %p\n", + obj->type, obj->offs, obj->size, (const void *) obj->next); } -// ggml_conv_1d - -struct ggml_tensor * ggml_conv_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int p0, - int d0) { - struct ggml_tensor * im2col = ggml_im2col(ctx, a, b, s0, 0, p0, 0, d0, 0, false, a->type); // [N, OL, IC * K] - - struct ggml_tensor * result = - ggml_mul_mat(ctx, - ggml_reshape_2d(ctx, im2col, im2col->ne[0], (im2col->ne[2] * im2col->ne[1])), // [N, OL, IC * K] => [N*OL, IC * K] - ggml_reshape_2d(ctx, a, (a->ne[0] * a->ne[1]), a->ne[2])); // [OC,IC, K] => [OC, IC * K] - - result = ggml_reshape_3d(ctx, result, im2col->ne[1], a->ne[2], im2col->ne[2]); // [N, OC, OL] +void ggml_print_objects(const struct ggml_context * ctx) { + struct ggml_object * obj = ctx->objects_begin; - return result; -} + GGML_LOG_INFO("%s: objects in context %p:\n", __func__, (const void *) ctx); -struct ggml_tensor * ggml_conv_1d_fast_1d_im2col( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int p0, - int d0) { - struct ggml_tensor * im2col = ggml_im2col_fast_1d(ctx, a, b, s0, 0, p0, 0, d0, 0, false, a->type); // [N, OL, IC * K] + while (obj != NULL) { + ggml_print_object(obj); + obj = obj->next; + } - struct ggml_tensor * result = - ggml_mul_mat(ctx, - ggml_reshape_2d(ctx, im2col, im2col->ne[0], (im2col->ne[2] * im2col->ne[1])), // [N, OL, IC * K] => [N*OL, IC * K] - ggml_reshape_2d(ctx, a, (a->ne[0] * a->ne[1]), a->ne[2])); // [OC,IC, K] => [OC, IC * K] + GGML_LOG_INFO("%s: --- end ---\n", __func__); +} - result = ggml_reshape_3d(ctx, result, im2col->ne[1], a->ne[2], im2col->ne[2]); // [N, OC, OL] +int64_t ggml_nelements(const struct ggml_tensor * tensor) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - return result; + return tensor->ne[0]*tensor->ne[1]*tensor->ne[2]*tensor->ne[3]; } -// ggml_conv_1d_ph - -struct ggml_tensor* ggml_conv_1d_ph( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s, - int d) { - return ggml_conv_1d(ctx, a, b, s, a->ne[0] / 2, d); -} - -// ggml_conv_1d_dw - -struct ggml_tensor * ggml_conv_1d_dw( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int p0, - int d0) { - struct ggml_tensor * new_b = ggml_reshape_4d(ctx, b, b->ne[0], 1, b->ne[1], b->ne[2]); - - struct ggml_tensor * im2col = ggml_im2col(ctx, a, new_b, s0, 0, p0, 0, d0, 0, false, a->type); - - struct ggml_tensor * result = ggml_mul_mat(ctx, im2col, a); - - result = ggml_reshape_3d(ctx, result, result->ne[0], result->ne[2], 1); - - return result; -} - -// ggml_conv_1d_dw_ph - -struct ggml_tensor * ggml_conv_1d_dw_ph( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int d0) { - return ggml_conv_1d_dw(ctx, a, b, s0, a->ne[0] / 2, d0); -} - -// ggml_conv_transpose_1d - -static int64_t ggml_calc_conv_transpose_1d_output_size(int64_t ins, int64_t ks, int s, int p, int d) { - return (ins - 1) * s - 2 * p + d * (ks - 1) + 1; -} - -GGML_API struct ggml_tensor * ggml_conv_transpose_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int p0, - int d0) { - GGML_ASSERT(ggml_is_matrix(b)); - GGML_ASSERT(a->ne[2] == b->ne[1]); - GGML_ASSERT(a->ne[3] == 1); - - GGML_ASSERT(p0 == 0); - GGML_ASSERT(d0 == 1); - - const int64_t ne[4] = { - ggml_calc_conv_transpose_1d_output_size(b->ne[0], a->ne[0], s0, 0 /*p0*/, 1 /*d0*/), - a->ne[1], b->ne[2], 1, - }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - int32_t params[] = { s0, p0, d0 }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_CONV_TRANSPOSE_1D; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_conv_2d - -// a: [OC,IC, KH, KW] -// b: [N, IC, IH, IW] -// result: [N, OC, OH, OW] -struct ggml_tensor * ggml_conv_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int s1, - int p0, - int p1, - int d0, - int d1) { - struct ggml_tensor * im2col = ggml_im2col(ctx, a, b, s0, s1, p0, p1, d0, d1, true, a->type); // [N, OH, OW, IC * KH * KW] - - struct ggml_tensor * result = - ggml_mul_mat(ctx, - ggml_reshape_2d(ctx, im2col, im2col->ne[0], im2col->ne[3] * im2col->ne[2] * im2col->ne[1]), // [N, OH, OW, IC * KH * KW] => [N*OH*OW, IC * KH * KW] - ggml_reshape_2d(ctx, a, (a->ne[0] * a->ne[1] * a->ne[2]), a->ne[3])); // [OC,IC, KH, KW] => [OC, IC * KH * KW] - - result = ggml_reshape_4d(ctx, result, im2col->ne[1], im2col->ne[2], im2col->ne[3], a->ne[3]); // [OC, N, OH, OW] - result = ggml_cont(ctx, ggml_permute(ctx, result, 0, 1, 3, 2)); // [N, OC, OH, OW] - - - return result; -} - -// a: [OC*IC, KD, KH, KW] -// b: [N*IC, ID, IH, IW] -// result: [N*OD, OH, OW, IC * KD * KH * KW] -struct ggml_tensor * ggml_im2col_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int64_t IC, - int s0, // stride width - int s1, // stride height - int s2, // stride depth - int p0, // padding width - int p1, // padding height - int p2, // padding depth - int d0, // dilation width - int d1, // dilation height - int d2, // dilation depth - enum ggml_type dst_type) { - const int64_t N = b->ne[3] / IC; - const int64_t ID = b->ne[2]; - const int64_t IH = b->ne[1]; - const int64_t IW = b->ne[0]; - - const int64_t OC = a->ne[3] / IC; - UNUSED(OC); - const int64_t KD = a->ne[2]; - const int64_t KH = a->ne[1]; - const int64_t KW = a->ne[0]; - const int64_t OD = ggml_calc_conv_output_size(ID, KD, s2, p2, d2); - const int64_t OH = ggml_calc_conv_output_size(IH, KH, s1, p1, d1); - const int64_t OW = ggml_calc_conv_output_size(IW, KW, s0, p0, d0); - - GGML_ASSERT((OD > 0) && "b too small compared to a"); - GGML_ASSERT((OH > 0) && "b too small compared to a"); - GGML_ASSERT((OW > 0) && "b too small compared to a"); - - - const int64_t ne[4] = {KW*KH*KD*IC, OW, OH, OD*N}; - - struct ggml_tensor * result = ggml_new_tensor(ctx, dst_type, 4, ne); - int32_t params[] = { s0, s1, s2, p0, p1, p2, d0, d1, d2, (int32_t)IC}; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_IM2COL_3D; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// a: [OC*IC, KD, KH, KW] -// b: [N*IC, ID, IH, IW] -// result: [N*OC, OD, OH, OW] -struct ggml_tensor * ggml_conv_3d( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int64_t IC, - int s0, // stride width - int s1, // stride height - int s2, // stride depth - int p0, // padding width - int p1, // padding height - int p2, // padding depth - int d0, // dilation width - int d1, // dilation height - int d2 // dilation depth - ) { - struct ggml_tensor * im2col = ggml_im2col_3d(ctx, a, b, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2, a->type); // [N*OD, OH, OW, IC * KD * KH * KW] - - int64_t OC = a->ne[3] / IC; - int64_t N = b->ne[3] / IC; - struct ggml_tensor * result = - ggml_mul_mat(ctx, - ggml_reshape_2d(ctx, im2col, im2col->ne[0], im2col->ne[3] * im2col->ne[2] * im2col->ne[1]), // [N*OD, OH, OW, IC * KD * KH * KW] => [N*OD*OH*OW, IC * KD * KH * KW] - ggml_reshape_2d(ctx, a, (a->ne[0] * a->ne[1] * a->ne[2] * IC), OC)); // [OC*IC, KD, KH, KW] => [OC, IC * KD * KH * KW] - - int64_t OD = im2col->ne[3] / N; - result = ggml_reshape_4d(ctx, result, im2col->ne[1]*im2col->ne[2], OD, N, OC); // [OC, N*OD*OH*OW] => [OC, N, OD, OH*OW] - result = ggml_cont(ctx, ggml_permute(ctx, result, 0, 1, 3, 2)); // [N, OC, OD, OH*OW] - result = ggml_reshape_4d(ctx, result, im2col->ne[1], im2col->ne[2], OD, OC * N); // [N*OC, OD, OH, OW] - - return result; -} - -// ggml_conv_2d_sk_p0 - -struct ggml_tensor * ggml_conv_2d_sk_p0( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_conv_2d(ctx, a, b, a->ne[0], a->ne[1], 0, 0, 1, 1); -} - -// ggml_conv_2d_s1_ph - -struct ggml_tensor * ggml_conv_2d_s1_ph( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - return ggml_conv_2d(ctx, a, b, 1, 1, a->ne[0] / 2, a->ne[1] / 2, 1, 1); -} - -// ggml_conv_2d_dw - -struct ggml_tensor * ggml_conv_2d_dw( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int s1, - int p0, - int p1, - int d0, - int d1) { - struct ggml_tensor * new_a = ggml_reshape_4d(ctx, a, a->ne[0], a->ne[1], 1, a->ne[2] * a->ne[3]); - struct ggml_tensor * im2col = ggml_im2col(ctx, new_a, - ggml_reshape_4d(ctx, b, b->ne[0], b->ne[1], 1, b->ne[2] * b->ne[3]), - s0, s1, p0, p1, d0, d1, true, GGML_TYPE_F16); // [N * IC, OH, OW, KH * KW] - struct ggml_tensor * new_b = ggml_reshape_4d(ctx, im2col, im2col->ne[0], im2col->ne[2] * im2col->ne[1], b->ne[2], b->ne[3]); // [N * IC, OH, OW, KH * KW] => [N, IC, OH * OW, KH * KW] - - new_a = ggml_reshape_4d(ctx, new_a, (new_a->ne[0] * new_a->ne[1]), new_a->ne[2], new_a->ne[3], 1); // [OC,1, KH, KW] => [1, OC, 1, KH * KW] - struct ggml_tensor * result = ggml_mul_mat(ctx, new_a, new_b); - result = ggml_reshape_4d(ctx, result, im2col->ne[1], im2col->ne[2], b->ne[2], b->ne[3]); // [N, OC, OH, OW] - - return result; -} - -// ggml_conv_2d_dw_direct - -struct ggml_tensor * ggml_conv_2d_dw_direct( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int stride0, - int stride1, - int pad0, - int pad1, - int dilation0, - int dilation1) { - GGML_ASSERT(a->ne[2] == 1); - GGML_ASSERT(a->ne[3] == b->ne[2]); - int64_t ne[4]; - ne[0] = ggml_calc_conv_output_size(b->ne[0], a->ne[0], stride0, pad0, dilation0); - ne[1] = ggml_calc_conv_output_size(b->ne[1], a->ne[1], stride1, pad1, dilation1); - ne[2] = b->ne[2]; - ne[3] = b->ne[3]; - - struct ggml_tensor * result = ggml_new_tensor(ctx, b->type, 4, ne); - - if (ggml_is_contiguous_channels(b)) { - // Result will be permuted the same way as input (CWHN order) - const int64_t type_size = ggml_type_size(result->type); - GGML_ASSERT(ggml_blck_size(result->type) == 1); - result->nb[0] = result->ne[2] * type_size; - result->nb[1] = result->ne[0] * result->nb[0]; - result->nb[2] = type_size; - } - - int32_t params[] = { stride0, stride1, pad0, pad1, dilation0, dilation1 }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_CONV_2D_DW; - result->src[0] = a; - result->src[1] = b; - return result; -} - -// ggml_conv_2d_direct - -struct ggml_tensor * ggml_conv_2d_direct( - struct ggml_context * ctx, - struct ggml_tensor * a, // convolution kernel [KW, KH, IC, OC] - struct ggml_tensor * b, // input data [W, H, C, N] - int s0, // stride dimension 0 - int s1, // stride dimension 1 - int p0, // padding dimension 0 - int p1, // padding dimension 1 - int d0, // dilation dimension 0 - int d1) {// dilation dimension 1 - - GGML_ASSERT(a->ne[2] == b->ne[2]); - //GGML_ASSERT(a->type == b->type); - - int64_t ne[4]; - ne[0] = ggml_calc_conv_output_size(b->ne[0], a->ne[0], s0, p0, d0); - ne[1] = ggml_calc_conv_output_size(b->ne[1], a->ne[1], s1, p1, d1); - ne[2] = a->ne[3]; - ne[3] = b->ne[3]; - - struct ggml_tensor * result = ggml_new_tensor(ctx, b->type, 4, ne); - - ggml_set_op_params_i32(result, 0, s0); - ggml_set_op_params_i32(result, 1, s1); - ggml_set_op_params_i32(result, 2, p0); - ggml_set_op_params_i32(result, 3, p1); - ggml_set_op_params_i32(result, 4, d0); - ggml_set_op_params_i32(result, 5, d1); - - result->op = GGML_OP_CONV_2D; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_conv_3d_direct - -struct ggml_tensor * ggml_conv_3d_direct( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int s0, - int s1, - int s2, - int p0, - int p1, - int p2, - int d0, - int d1, - int d2, - int c, - int n, - int oc) { - - GGML_ASSERT(a->ne[3] == (int64_t) c * oc); - GGML_ASSERT(b->ne[3] == (int64_t) c * n); - - int64_t ne[4]; - ne[0] = ggml_calc_conv_output_size(b->ne[0], a->ne[0], s0, p0, d0); - ne[1] = ggml_calc_conv_output_size(b->ne[1], a->ne[1], s1, p1, d1); - ne[2] = ggml_calc_conv_output_size(b->ne[2], a->ne[2], s2, p2, d2); - ne[3] = (int64_t) oc * n; - - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - ggml_set_op_params_i32(result, 0, s0); - ggml_set_op_params_i32(result, 1, s1); - ggml_set_op_params_i32(result, 2, s2); - ggml_set_op_params_i32(result, 3, p0); - ggml_set_op_params_i32(result, 4, p1); - ggml_set_op_params_i32(result, 5, p2); - ggml_set_op_params_i32(result, 6, d0); - ggml_set_op_params_i32(result, 7, d1); - ggml_set_op_params_i32(result, 8, d2); - ggml_set_op_params_i32(result, 9, c); - ggml_set_op_params_i32(result, 10, n); - ggml_set_op_params_i32(result, 11, oc); - - result->op = GGML_OP_CONV_3D; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_conv_transpose_2d_p0 - -static int64_t ggml_calc_conv_transpose_output_size(int64_t ins, int64_t ks, int s, int p) { - return (ins - 1) * s - 2 * p + ks; -} - -struct ggml_tensor * ggml_conv_transpose_2d_p0( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - int stride) { - GGML_ASSERT(a->ne[3] == b->ne[2]); - - const int64_t ne[4] = { - ggml_calc_conv_transpose_output_size(b->ne[0], a->ne[0], stride, 0 /*p0*/), - ggml_calc_conv_transpose_output_size(b->ne[1], a->ne[1], stride, 0 /*p1*/), - a->ne[2], b->ne[3], - }; - - struct ggml_tensor* result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - ggml_set_op_params_i32(result, 0, stride); - - result->op = GGML_OP_CONV_TRANSPOSE_2D; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_pool_* - -static int64_t ggml_calc_pool_output_size(int64_t ins, int ks, int s, float p) { - return (ins + 2 * p - ks) / s + 1; -} - -// ggml_pool_1d - -struct ggml_tensor * ggml_pool_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_op_pool op, - int k0, - int s0, - int p0) { - const int64_t ne[4] = { - ggml_calc_pool_output_size(a->ne[0], k0, s0, p0), - a->ne[1], - a->ne[2], - a->ne[3], - }; - GGML_ASSERT(ne[0] > 0); - - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - int32_t params[] = { op, k0, s0, p0 }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_POOL_1D; - result->src[0] = a; - - return result; -} - -// ggml_pool_2d - -struct ggml_tensor * ggml_pool_2d( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_op_pool op, - int k0, - int k1, - int s0, - int s1, - float p0, - float p1) { - struct ggml_tensor * result; - const int64_t ne[4] = { - ggml_calc_pool_output_size(a->ne[0], k0, s0, p0), - ggml_calc_pool_output_size(a->ne[1], k1, s1, p1), - a->ne[2], - a->ne[3], - }; - GGML_ASSERT(ne[0] > 0); - GGML_ASSERT(ne[1] > 0); - - result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - int32_t params[] = { op, k0, k1, s0, s1, p0, p1 }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_POOL_2D; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_pool_2d_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * af, - enum ggml_op_pool op, - int k0, - int k1, - int s0, - int s1, - float p0, - float p1) { - struct ggml_tensor * result; - result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, af->ne); - - int32_t params[] = { op, k0, k1, s0, s1, p0, p1 }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_POOL_2D_BACK; - result->src[0] = a; - result->src[1] = af; - - return result; -} - -// ggml_upscale / ggml_interpolate - -static struct ggml_tensor * ggml_interpolate_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3, - uint32_t mode) { - GGML_ASSERT((mode & 0xFF) < GGML_SCALE_MODE_COUNT); - // TODO: implement antialias for modes other than bilinear - GGML_ASSERT(!(mode & GGML_SCALE_FLAG_ANTIALIAS) || (mode & 0xFF) == GGML_SCALE_MODE_BILINEAR); - GGML_ASSERT(a->type == GGML_TYPE_F32); - - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, ne0, ne1, ne2, ne3); - - ggml_set_op_params_i32(result, 0, (int32_t)mode); - - result->op = GGML_OP_UPSCALE; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_upscale( - struct ggml_context * ctx, - struct ggml_tensor * a, - int scale_factor, - enum ggml_scale_mode mode) { - GGML_ASSERT(scale_factor > 1); - return ggml_interpolate_impl(ctx, a, a->ne[0] * scale_factor, a->ne[1] * scale_factor, a->ne[2], a->ne[3], mode); -} - -struct ggml_tensor * ggml_upscale_ext( - struct ggml_context * ctx, - struct ggml_tensor * a, - int ne0, - int ne1, - int ne2, - int ne3, - enum ggml_scale_mode mode) { - return ggml_interpolate_impl(ctx, a, ne0, ne1, ne2, ne3, mode); -} - -struct ggml_tensor * ggml_interpolate( - struct ggml_context * ctx, - struct ggml_tensor * a, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3, - uint32_t mode) { - return ggml_interpolate_impl(ctx, a, ne0, ne1, ne2, ne3, mode); -} - -// ggml_pad - -struct ggml_tensor * ggml_pad( - struct ggml_context * ctx, - struct ggml_tensor * a, - int p0, - int p1, - int p2, - int p3) { - return ggml_pad_ext(ctx, a, 0, p0, 0, p1, 0, p2, 0, p3); -} - -// ggml_pad_circular - -struct ggml_tensor * ggml_pad_circular( - struct ggml_context * ctx, - struct ggml_tensor * a, - int p0, - int p1, - int p2, - int p3) { - return ggml_pad_ext_circular(ctx, a, 0, p0, 0, p1, 0, p2, 0, p3); -} - -struct ggml_tensor * ggml_pad_ext( - struct ggml_context * ctx, - struct ggml_tensor * a, - int lp0, - int rp0, - int lp1, - int rp1, - int lp2, - int rp2, - int lp3, - int rp3 - ) { - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, - a->ne[0] + lp0 + rp0, - a->ne[1] + lp1 + rp1, - a->ne[2] + lp2 + rp2, - a->ne[3] + lp3 + rp3); - - ggml_set_op_params_i32(result, 0, lp0); - ggml_set_op_params_i32(result, 1, rp0); - ggml_set_op_params_i32(result, 2, lp1); - ggml_set_op_params_i32(result, 3, rp1); - ggml_set_op_params_i32(result, 4, lp2); - ggml_set_op_params_i32(result, 5, rp2); - ggml_set_op_params_i32(result, 6, lp3); - ggml_set_op_params_i32(result, 7, rp3); - ggml_set_op_params_i32(result, 8, 0); // not circular by default - - - result->op = GGML_OP_PAD; - result->src[0] = a; - - return result; -} - -// ggml_pad_ext_circular - -struct ggml_tensor * ggml_pad_ext_circular( - struct ggml_context * ctx, - struct ggml_tensor * a, - int lp0, - int rp0, - int lp1, - int rp1, - int lp2, - int rp2, - int lp3, - int rp3 - ) { - struct ggml_tensor * result = ggml_pad_ext(ctx, a, lp0, rp0, lp1, rp1, lp2, rp2, lp3, rp3); - ggml_set_op_params_i32(result, 8, 1); // circular - return result; -} - -// ggml_pad_reflect_1d - -struct ggml_tensor * ggml_pad_reflect_1d( - struct ggml_context * ctx, - struct ggml_tensor * a, - int p0, - int p1) { - GGML_ASSERT(p0 >= 0); - GGML_ASSERT(p1 >= 0); - - GGML_ASSERT(p0 < a->ne[0]); // padding length on each size must be less than the - GGML_ASSERT(p1 < a->ne[0]); // existing length of the dimension being padded - - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(a->type == GGML_TYPE_F32); - - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, - a->ne[0] + p0 + p1, - a->ne[1], - a->ne[2], - a->ne[3]); - - int32_t params[] = { p0, p1 }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_PAD_REFLECT_1D; - result->src[0] = a; - - return result; -} - -// ggml_roll - -struct ggml_tensor * ggml_roll( - struct ggml_context * ctx, - struct ggml_tensor * a, - int shift0, - int shift1, - int shift2, - int shift3) { - GGML_ASSERT(a->nb[0] == ggml_type_size(a->type)); - GGML_ASSERT(abs(shift0) < a->ne[0]); - GGML_ASSERT(abs(shift1) < a->ne[1]); - GGML_ASSERT(abs(shift2) < a->ne[2]); - GGML_ASSERT(abs(shift3) < a->ne[3]); - - struct ggml_tensor * result = ggml_dup_tensor(ctx, a); - - ggml_set_op_params_i32(result, 0, shift0); - ggml_set_op_params_i32(result, 1, shift1); - ggml_set_op_params_i32(result, 2, shift2); - ggml_set_op_params_i32(result, 3, shift3); - - result->op = GGML_OP_ROLL; - result->src[0] = a; - - return result; -} - -// ggml_timestep_embedding - -struct ggml_tensor * ggml_timestep_embedding( - struct ggml_context * ctx, - struct ggml_tensor * timesteps, - int dim, - int max_period) { - - struct ggml_tensor * result = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, dim, timesteps->ne[0]); - - ggml_set_op_params_i32(result, 0, dim); - ggml_set_op_params_i32(result, 1, max_period); - - result->op = GGML_OP_TIMESTEP_EMBEDDING; - result->src[0] = timesteps; - - return result; -} - -// ggml_tri - -struct ggml_tensor * ggml_tri( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_tri_type type) { - GGML_ASSERT(a->type == GGML_TYPE_F32); - - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(a->ne[0] == a->ne[1]); - - struct ggml_tensor * result = ggml_dup_tensor(ctx, a); - - ggml_set_op_params_i32(result, 0, type); - - result->op = GGML_OP_TRI; - result->src[0] = a; - - return result; -} - -// ggml_fill - -static struct ggml_tensor * ggml_fill_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - float c, - bool inplace) { - GGML_ASSERT(a->type == GGML_TYPE_F32); - GGML_ASSERT(ggml_is_contiguous(a)); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - ggml_set_op_params_f32(result, 0, c); - - result->op = GGML_OP_FILL; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_fill( - struct ggml_context * ctx, - struct ggml_tensor * a, - float c) { - return ggml_fill_impl(ctx, a, c, false); -} - -struct ggml_tensor * ggml_fill_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - float c) { - return ggml_fill_impl(ctx, a, c, true); -} - -// ggml_argsort - -struct ggml_tensor * ggml_argsort( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_sort_order order) { - GGML_ASSERT(a->ne[0] <= INT32_MAX); - - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_I32, GGML_MAX_DIMS, a->ne); - - ggml_set_op_params_i32(result, 0, (int32_t) order); - - result->op = GGML_OP_ARGSORT; - result->src[0] = a; - - return result; -} - -// ggml_argsort_top_k - -struct ggml_tensor * ggml_argsort_top_k( - struct ggml_context * ctx, - struct ggml_tensor * a, - int k) { - GGML_ASSERT(a->ne[0] >= k); - - struct ggml_tensor * result = ggml_argsort(ctx, a, GGML_SORT_ORDER_DESC); - - result = ggml_view_4d(ctx, result, - k, result->ne[1], result->ne[2], result->ne[3], - result->nb[1], result->nb[2], result->nb[3], - 0); - - return result; -} - -// ggml_top_k - -struct ggml_tensor * ggml_top_k( - struct ggml_context * ctx, - struct ggml_tensor * a, - int k) { - GGML_ASSERT(a->ne[0] >= k); - - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, GGML_TYPE_I32, k, a->ne[1], a->ne[2], a->ne[3]); - - result->op = GGML_OP_TOP_K; - result->src[0] = a; - - return result; -} - -// ggml_arange - -struct ggml_tensor * ggml_arange( - struct ggml_context * ctx, - float start, - float stop, - float step) { - GGML_ASSERT(stop > start); - - const int64_t steps = (int64_t) ceilf((stop - start) / step); - - struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, steps); - - ggml_set_op_params_f32(result, 0, start); - ggml_set_op_params_f32(result, 1, stop); - ggml_set_op_params_f32(result, 2, step); - - result->op = GGML_OP_ARANGE; - - return result; -} - -// ggml_flash_attn_ext - -struct ggml_tensor * ggml_flash_attn_ext( - struct ggml_context * ctx, - struct ggml_tensor * q, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * mask, - float scale, - float max_bias, - float logit_softcap) { - GGML_ASSERT(ggml_can_mul_mat(k, q)); - // TODO: check if vT can be multiplied by (k*qT) - - GGML_ASSERT(q->ne[3] == k->ne[3]); - GGML_ASSERT(q->ne[3] == v->ne[3]); - - if (mask) { - GGML_ASSERT(mask->type == GGML_TYPE_F16); - GGML_ASSERT(ggml_is_contiguous(mask)); - //GGML_ASSERT(ggml_can_repeat_rows(mask, qk)); - - GGML_ASSERT(q->ne[2] % mask->ne[2] == 0); - GGML_ASSERT(q->ne[3] % mask->ne[3] == 0); - } - - if (max_bias > 0.0f) { - GGML_ASSERT(mask); - } - - // permute(0, 2, 1, 3) - int64_t ne[4] = { v->ne[0], q->ne[2], q->ne[1], q->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - float params[] = { scale, max_bias, logit_softcap }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_FLASH_ATTN_EXT; - result->src[0] = q; - result->src[1] = k; - result->src[2] = v; - result->src[3] = mask; +int64_t ggml_nrows(const struct ggml_tensor * tensor) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - return result; + return tensor->ne[1]*tensor->ne[2]*tensor->ne[3]; } -struct ggml_tensor * ggml_sage_attn2( - struct ggml_context * ctx, - struct ggml_tensor * q, - struct ggml_tensor * k, - struct ggml_tensor * v, - float scale, - bool causal) { - GGML_ASSERT(q->type == GGML_TYPE_F16); - GGML_ASSERT(k->type == GGML_TYPE_F16); - GGML_ASSERT(v->type == GGML_TYPE_F16); - GGML_ASSERT(ggml_is_contiguous(q)); - GGML_ASSERT(ggml_is_contiguous(k)); - GGML_ASSERT(ggml_is_contiguous(v)); +size_t ggml_nbytes(const struct ggml_tensor * tensor) { + for (int i = 0; i < GGML_MAX_DIMS; ++i) { + if (tensor->ne[i] <= 0) { + return 0; + } + } - GGML_ASSERT(q->ne[0] == k->ne[0]); - GGML_ASSERT(q->ne[0] == v->ne[0]); - GGML_ASSERT(k->ne[1] == v->ne[1]); - GGML_ASSERT(k->ne[2] == v->ne[2]); - GGML_ASSERT(q->ne[3] == k->ne[3]); - GGML_ASSERT(q->ne[3] == v->ne[3]); - GGML_ASSERT(q->ne[2] % k->ne[2] == 0); - GGML_ASSERT(q->ne[0] == 64 || q->ne[0] == 128); - GGML_ASSERT(scale > 0.0f); + size_t nbytes; + const size_t blck_size = ggml_blck_size(tensor->type); + if (blck_size == 1) { + nbytes = ggml_type_size(tensor->type); + for (int i = 0; i < GGML_MAX_DIMS; ++i) { + nbytes += (tensor->ne[i] - 1)*tensor->nb[i]; + } + } + else { + nbytes = tensor->ne[0]*tensor->nb[0]/blck_size; + for (int i = 1; i < GGML_MAX_DIMS; ++i) { + nbytes += (tensor->ne[i] - 1)*tensor->nb[i]; + } + } - int64_t ne[4] = { v->ne[0], q->ne[2], q->ne[1], q->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F16, 4, ne); + return nbytes + ggml_type_extra_bytes(tensor->type); +} - ggml_set_op_params_f32(result, 0, scale); - ggml_set_op_params_i32(result, 1, causal ? 1 : 0); +size_t ggml_nbytes_pad(const struct ggml_tensor * tensor) { + return GGML_PAD(ggml_nbytes(tensor), GGML_MEM_ALIGN); +} - result->op = GGML_OP_SAGE_ATTN2; - result->src[0] = q; - result->src[1] = k; - result->src[2] = v; +int64_t ggml_blck_size(enum ggml_type type) { + assert(type >= 0); + assert(type < GGML_TYPE_COUNT); + return type_traits[type].blck_size; +} - return result; +size_t ggml_type_size(enum ggml_type type) { + assert(type >= 0); + assert(type < GGML_TYPE_COUNT); + return type_traits[type].type_size; } -struct ggml_tensor * ggml_sage_attn2_i8( - struct ggml_context * ctx, - struct ggml_tensor * q_i8, - struct ggml_tensor * k_i8, - struct ggml_tensor * v, - struct ggml_tensor * q_scale, - struct ggml_tensor * k_scale, - float scale, - bool causal) { - GGML_ASSERT(q_i8->type == GGML_TYPE_I8); - GGML_ASSERT(k_i8->type == GGML_TYPE_I8); - GGML_ASSERT(v->type == GGML_TYPE_F16); - GGML_ASSERT(q_scale->type == GGML_TYPE_F32); - GGML_ASSERT(k_scale->type == GGML_TYPE_F32); - GGML_ASSERT(ggml_is_contiguous(q_i8)); - GGML_ASSERT(ggml_is_contiguous(k_i8)); - GGML_ASSERT(ggml_is_contiguous(v)); - GGML_ASSERT(ggml_is_contiguous(q_scale)); - GGML_ASSERT(ggml_is_contiguous(k_scale)); +size_t ggml_row_size(enum ggml_type type, int64_t ne) { + assert(type >= 0); + assert(type < GGML_TYPE_COUNT); + assert(ne % ggml_blck_size(type) == 0); + return ggml_type_size(type)*ne/ggml_blck_size(type); +} - GGML_ASSERT(q_i8->ne[0] == k_i8->ne[0]); - GGML_ASSERT(q_i8->ne[0] == v->ne[0]); - GGML_ASSERT(k_i8->ne[1] == v->ne[1]); - GGML_ASSERT(k_i8->ne[2] == v->ne[2]); - GGML_ASSERT(q_i8->ne[3] == k_i8->ne[3]); - GGML_ASSERT(q_i8->ne[3] == v->ne[3]); - GGML_ASSERT(q_i8->ne[2] % k_i8->ne[2] == 0); - GGML_ASSERT(q_i8->ne[0] == 64 || q_i8->ne[0] == 128); - GGML_ASSERT(q_scale->ne[0] == ((q_i8->ne[1] + 127) / 128) * 4); - GGML_ASSERT(q_scale->ne[1] == q_i8->ne[2]); - GGML_ASSERT(q_scale->ne[2] == q_i8->ne[3]); - GGML_ASSERT(k_scale->ne[0] == (k_i8->ne[1] + 63) / 64); - GGML_ASSERT(k_scale->ne[1] == k_i8->ne[2]); - GGML_ASSERT(k_scale->ne[2] == k_i8->ne[3]); - GGML_ASSERT(scale > 0.0f); +double ggml_type_sizef(enum ggml_type type) { + assert(type >= 0); + assert(type < GGML_TYPE_COUNT); + return ((double)(type_traits[type].type_size))/type_traits[type].blck_size; +} - int64_t ne[4] = { v->ne[0], q_i8->ne[2], q_i8->ne[1], q_i8->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F16, 4, ne); +const char * ggml_type_name(enum ggml_type type) { + assert(type >= 0); + assert(type < GGML_TYPE_COUNT); + return type_traits[type].type_name; +} - ggml_set_op_params_f32(result, 0, scale); - ggml_set_op_params_i32(result, 1, causal ? 1 : 0); +bool ggml_is_quantized(enum ggml_type type) { + assert(type >= 0); + assert(type < GGML_TYPE_COUNT); + return type_traits[type].is_quantized; +} - result->op = GGML_OP_SAGE_ATTN2_I8; - result->src[0] = q_i8; - result->src[1] = k_i8; - result->src[2] = v; - result->src[3] = q_scale; - result->src[4] = k_scale; +const char * ggml_op_name(enum ggml_op op) { + return GGML_OP_NAME[op]; +} - return result; +const char * ggml_op_symbol(enum ggml_op op) { + return GGML_OP_SYMBOL[op]; } -struct ggml_tensor * ggml_convrot_linear( - struct ggml_context * ctx, - struct ggml_tensor * weight_i8, - struct ggml_tensor * input, - struct ggml_tensor * weight_scale, - struct ggml_tensor * bias, - int group_size) { - GGML_ASSERT(weight_i8->type == GGML_TYPE_I8); - GGML_ASSERT(input->type == GGML_TYPE_F32); - GGML_ASSERT(weight_scale->type == GGML_TYPE_F32); - GGML_ASSERT(bias == NULL || bias->type == GGML_TYPE_F32); - GGML_ASSERT(ggml_is_contiguous(weight_i8)); - GGML_ASSERT(ggml_is_contiguous(input)); - GGML_ASSERT(ggml_is_contiguous(weight_scale)); - GGML_ASSERT(bias == NULL || ggml_is_contiguous(bias)); - GGML_ASSERT(group_size > 0); +const char * ggml_unary_op_name(enum ggml_unary_op op) { + return GGML_UNARY_OP_NAME[op]; +} - const int64_t in_features = weight_i8->ne[0]; - const int64_t out_features = weight_i8->ne[1]; - GGML_ASSERT(input->ne[0] == in_features); - GGML_ASSERT(in_features % group_size == 0); - GGML_ASSERT(ggml_nelements(weight_scale) == out_features); - GGML_ASSERT(bias == NULL || bias->ne[0] == out_features); +const char * ggml_glu_op_name(enum ggml_glu_op op) { + return GGML_GLU_OP_NAME[op]; +} - int64_t ne[4] = { out_features, input->ne[1], input->ne[2], input->ne[3] }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, ggml_n_dims(input), ne); +const char * ggml_op_desc(const struct ggml_tensor * t) { + if (t->op == GGML_OP_UNARY) { + enum ggml_unary_op uop = ggml_get_unary_op(t); + return ggml_unary_op_name(uop); + } + if (t->op == GGML_OP_GLU) { + enum ggml_glu_op gop = ggml_get_glu_op(t); + return ggml_glu_op_name(gop); + } + return ggml_op_name(t->op); +} - ggml_set_op_params_i32(result, 0, group_size); +size_t ggml_element_size(const struct ggml_tensor * tensor) { + return ggml_type_size(tensor->type); +} - result->op = GGML_OP_CONVROT_LINEAR; - result->src[0] = weight_i8; - result->src[1] = input; - result->src[2] = weight_scale; - result->src[3] = bias; +bool ggml_is_scalar(const struct ggml_tensor * tensor) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - return result; + return tensor->ne[0] == 1 && tensor->ne[1] == 1 && tensor->ne[2] == 1 && tensor->ne[3] == 1; } -struct ggml_tensor * ggml_flash_attn_ext_with_bias_mask( - struct ggml_context * ctx, - struct ggml_tensor * q, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * bias, - struct ggml_tensor * mask, - float scale, - float max_bias, - float logit_softcap) { - GGML_ASSERT(bias); - GGML_ASSERT(q && k && v); - GGML_ASSERT(q->ne[1] == bias->ne[1]); - GGML_ASSERT(k->ne[1] == bias->ne[0]); - GGML_ASSERT(q->ne[2] % bias->ne[2] == 0); - GGML_ASSERT(q->ne[3] % bias->ne[3] == 0); - if (mask) { - GGML_ASSERT(mask->type == GGML_TYPE_F16 || mask->type == GGML_TYPE_F32); - GGML_ASSERT(mask->ne[0] == bias->ne[0]); - GGML_ASSERT(mask->ne[1] == bias->ne[1]); - GGML_ASSERT(bias->ne[2] % mask->ne[2] == 0); - GGML_ASSERT(bias->ne[3] % mask->ne[3] == 0); - } +bool ggml_is_vector(const struct ggml_tensor * tensor) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); - // MINITTS_FLASH_BIAS_WRAPPER: - // Relative-position scores are added to QK before the common softmax scale - // in the reference path. Flash attention applies scale only to QK, so - // pre-scale the dense bias before folding it into the additive mask. - if (!ggml_is_contiguous(bias)) { - bias = ggml_cont(ctx, bias); - } + return tensor->ne[1] == 1 && tensor->ne[2] == 1 && tensor->ne[3] == 1; +} - struct ggml_tensor * effective_mask = ggml_scale(ctx, bias, scale); - if (mask) { - if (!ggml_are_same_shape(mask, effective_mask)) { - mask = ggml_repeat(ctx, mask, effective_mask); +bool ggml_is_matrix(const struct ggml_tensor * tensor) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return tensor->ne[2] == 1 && tensor->ne[3] == 1; +} + +bool ggml_is_3d(const struct ggml_tensor * tensor) { + return tensor->ne[3] == 1; +} + +int ggml_n_dims(const struct ggml_tensor * tensor) { + for (int i = GGML_MAX_DIMS - 1; i >= 1; --i) { + if (tensor->ne[i] > 1) { + return i + 1; } - effective_mask = ggml_add(ctx, effective_mask, mask); } + return 1; +} - // MINITTS_FLASH_BIAS_WRAPPER: - // The flash-attention op expects a contiguous F16 additive mask. The - // relative-position branch already materializes the per-head bias in F32, - // so normalize that here before dispatching to the existing kernel. - if (!ggml_is_contiguous(effective_mask)) { - effective_mask = ggml_cont(ctx, effective_mask); +enum ggml_type ggml_ftype_to_ggml_type(enum ggml_ftype ftype) { + enum ggml_type wtype = GGML_TYPE_COUNT; + + switch (ftype) { + case GGML_FTYPE_ALL_F32: wtype = GGML_TYPE_F32; break; + case GGML_FTYPE_MOSTLY_F16: wtype = GGML_TYPE_F16; break; + case GGML_FTYPE_MOSTLY_BF16: wtype = GGML_TYPE_BF16; break; + case GGML_FTYPE_MOSTLY_Q4_0: wtype = GGML_TYPE_Q4_0; break; + case GGML_FTYPE_MOSTLY_Q4_1: wtype = GGML_TYPE_Q4_1; break; + case GGML_FTYPE_MOSTLY_Q1_0: wtype = GGML_TYPE_Q1_0; break; + case GGML_FTYPE_MOSTLY_Q5_0: wtype = GGML_TYPE_Q5_0; break; + case GGML_FTYPE_MOSTLY_Q5_1: wtype = GGML_TYPE_Q5_1; break; + case GGML_FTYPE_MOSTLY_Q8_0: wtype = GGML_TYPE_Q8_0; break; + case GGML_FTYPE_MOSTLY_MXFP4: wtype = GGML_TYPE_MXFP4; break; + case GGML_FTYPE_MOSTLY_NVFP4: wtype = GGML_TYPE_NVFP4; break; + case GGML_FTYPE_MOSTLY_Q2_K: wtype = GGML_TYPE_Q2_K; break; + case GGML_FTYPE_MOSTLY_Q3_K: wtype = GGML_TYPE_Q3_K; break; + case GGML_FTYPE_MOSTLY_Q4_K: wtype = GGML_TYPE_Q4_K; break; + case GGML_FTYPE_MOSTLY_Q5_K: wtype = GGML_TYPE_Q5_K; break; + case GGML_FTYPE_MOSTLY_Q6_K: wtype = GGML_TYPE_Q6_K; break; + case GGML_FTYPE_MOSTLY_IQ2_XXS: wtype = GGML_TYPE_IQ2_XXS; break; + case GGML_FTYPE_MOSTLY_IQ2_XS: wtype = GGML_TYPE_IQ2_XS; break; + case GGML_FTYPE_MOSTLY_IQ3_XXS: wtype = GGML_TYPE_IQ3_XXS; break; + case GGML_FTYPE_MOSTLY_IQ1_S: wtype = GGML_TYPE_IQ1_S; break; + case GGML_FTYPE_MOSTLY_IQ1_M: wtype = GGML_TYPE_IQ1_M; break; + case GGML_FTYPE_MOSTLY_IQ4_NL: wtype = GGML_TYPE_IQ4_NL; break; + case GGML_FTYPE_MOSTLY_IQ4_XS: wtype = GGML_TYPE_IQ4_XS; break; + case GGML_FTYPE_MOSTLY_IQ3_S: wtype = GGML_TYPE_IQ3_S; break; + case GGML_FTYPE_MOSTLY_IQ2_S: wtype = GGML_TYPE_IQ2_S; break; + case GGML_FTYPE_UNKNOWN: wtype = GGML_TYPE_COUNT; break; + case GGML_FTYPE_MOSTLY_Q4_1_SOME_F16: wtype = GGML_TYPE_COUNT; break; } - if (effective_mask->type != GGML_TYPE_F16) { - effective_mask = ggml_cast(ctx, effective_mask, GGML_TYPE_F16); + + GGML_ASSERT(wtype != GGML_TYPE_COUNT); + + return wtype; +} + +size_t ggml_tensor_overhead(void) { + return GGML_OBJECT_SIZE + GGML_TENSOR_SIZE; +} + +bool ggml_is_transposed(const struct ggml_tensor * tensor) { + return tensor->nb[0] > tensor->nb[1]; +} + +static bool ggml_is_contiguous_n(const struct ggml_tensor * tensor, int n) { + size_t next_nb = ggml_type_size(tensor->type); + if (tensor->ne[0] != ggml_blck_size(tensor->type) && tensor->nb[0] != next_nb) { + return false; } - if (!ggml_is_contiguous(effective_mask)) { - effective_mask = ggml_cont(ctx, effective_mask); + next_nb *= tensor->ne[0]/ggml_blck_size(tensor->type); + for (int i = 1; i < GGML_MAX_DIMS; i++) { + if (i > n) { + if (tensor->ne[i] != 1 && tensor->nb[i] != next_nb) { + return false; + } + next_nb *= tensor->ne[i]; + } else { + // this dimension does not need to be contiguous + next_nb = tensor->ne[i]*tensor->nb[i]; + } } + return true; +} - return ggml_flash_attn_ext( - ctx, - q, - k, - v, - effective_mask, - scale, - max_bias, - logit_softcap); +bool ggml_is_contiguous(const struct ggml_tensor * tensor) { + return ggml_is_contiguous_0(tensor); } -void ggml_flash_attn_ext_set_prec( - struct ggml_tensor * a, - enum ggml_prec prec) { - GGML_ASSERT(a->op == GGML_OP_FLASH_ATTN_EXT); - - const int32_t prec_i32 = (int32_t) prec; - - ggml_set_op_params_i32(a, 3, prec_i32); // scale is on first pos, max_bias on second -} - -enum ggml_prec ggml_flash_attn_ext_get_prec( - const struct ggml_tensor * a) { - GGML_ASSERT(a->op == GGML_OP_FLASH_ATTN_EXT); - - const int32_t prec_i32 = ggml_get_op_params_i32(a, 3); - - return (enum ggml_prec) prec_i32; -} - -void ggml_flash_attn_ext_add_sinks( - struct ggml_tensor * a, - struct ggml_tensor * sinks) { - if (!sinks) { - a->src[4] = NULL; - return; - } - - GGML_ASSERT(a->op == GGML_OP_FLASH_ATTN_EXT); - GGML_ASSERT(a->src[4] == NULL); - GGML_ASSERT(a->src[0]->ne[2] == sinks->ne[0]); - GGML_ASSERT(sinks->type == GGML_TYPE_F32); - - a->src[4] = sinks; -} - -// ggml_flash_attn_back - -struct ggml_tensor * ggml_flash_attn_back( - struct ggml_context * ctx, - struct ggml_tensor * q, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * d, - bool masked) { - GGML_ABORT("TODO: adapt to ggml_flash_attn_ext() changes"); - - GGML_ASSERT(ggml_can_mul_mat(k, q)); - // TODO: check if vT can be multiplied by (k*qT) - - // d shape [D,N,ne2,ne3] - // q shape [D,N,ne2,ne3] - // k shape [D,M,kvne2,ne3] - // v shape [M,D,kvne2,ne3] - - const int64_t D = q->ne[0]; - const int64_t N = q->ne[1]; - const int64_t M = k->ne[1]; - const int64_t ne2 = q->ne[2]; - const int64_t ne3 = q->ne[3]; - const int64_t kvne2 = k->ne[2]; - - GGML_ASSERT(k->ne[0] == D); - GGML_ASSERT(v->ne[0] == M); - GGML_ASSERT(v->ne[1] == D); - GGML_ASSERT(d->ne[0] == D); - GGML_ASSERT(d->ne[1] == N); - GGML_ASSERT(k->ne[2] == kvne2); - GGML_ASSERT(k->ne[3] == ne3); - GGML_ASSERT(v->ne[2] == kvne2); - GGML_ASSERT(v->ne[3] == ne3); - GGML_ASSERT(d->ne[2] == ne2); - GGML_ASSERT(d->ne[3] == ne3); - - GGML_ASSERT(ne2 % kvne2 == 0); - - // store gradients of q, k and v as continuous tensors concatenated in result. - // note: v and gradv are actually transposed, i.e. v->ne[0] != D. - const int64_t elem_q = ggml_nelements(q); - const int64_t elem_k = ggml_nelements(k); - const int64_t elem_v = ggml_nelements(v); - - enum ggml_type result_type = GGML_TYPE_F32; - GGML_ASSERT(ggml_blck_size(result_type) == 1); - const size_t tsize = ggml_type_size(result_type); - - const size_t offs_q = 0; - const size_t offs_k = offs_q + GGML_PAD(elem_q * tsize, GGML_MEM_ALIGN); - const size_t offs_v = offs_k + GGML_PAD(elem_k * tsize, GGML_MEM_ALIGN); - const size_t end = offs_v + GGML_PAD(elem_v * tsize, GGML_MEM_ALIGN); - - const size_t nelements = (end + tsize - 1)/tsize; - - struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, nelements); - - int32_t masked_i = masked ? 1 : 0; - ggml_set_op_params(result, &masked_i, sizeof(masked_i)); - - result->op = GGML_OP_FLASH_ATTN_BACK; - result->src[0] = q; - result->src[1] = k; - result->src[2] = v; - result->src[3] = d; - - return result; -} - -// ggml_ssm_conv - -struct ggml_tensor * ggml_ssm_conv( - struct ggml_context * ctx, - struct ggml_tensor * sx, - struct ggml_tensor * c) { - GGML_ASSERT(ggml_is_3d(sx)); - GGML_ASSERT(ggml_is_matrix(c)); - - const int64_t d_conv = c->ne[0]; - const int64_t d_inner = c->ne[1]; - const int64_t n_t = sx->ne[0] - d_conv + 1; // tokens per sequence - const int64_t n_s = sx->ne[2]; - - // TODO: maybe support other strides than 1? - GGML_ASSERT(sx->ne[0] == d_conv - 1 + n_t); - GGML_ASSERT(sx->ne[1] == d_inner); - GGML_ASSERT(n_t >= 0); - - struct ggml_tensor * result = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, d_inner, n_t, n_s); - - result->op = GGML_OP_SSM_CONV; - result->src[0] = sx; - result->src[1] = c; - - return result; -} - -// ggml_ssm_scan - -struct ggml_tensor * ggml_ssm_scan( - struct ggml_context * ctx, - struct ggml_tensor * s, - struct ggml_tensor * x, - struct ggml_tensor * dt, - struct ggml_tensor * A, - struct ggml_tensor * B, - struct ggml_tensor * C, - struct ggml_tensor * ids) { - GGML_ASSERT(ggml_is_contiguous(s)); - GGML_ASSERT(ggml_is_contiguous(dt)); - GGML_ASSERT(ggml_is_contiguous(A)); - GGML_ASSERT(x->nb[0] == ggml_type_size(x->type)); - GGML_ASSERT(B->nb[0] == ggml_type_size(B->type)); - GGML_ASSERT(C->nb[0] == ggml_type_size(C->type)); - GGML_ASSERT(x->nb[1] == x->ne[0]*x->nb[0]); - GGML_ASSERT(B->nb[1] == B->ne[0]*B->nb[0]); - GGML_ASSERT(C->nb[1] == C->ne[0]*C->nb[0]); - GGML_ASSERT(ggml_are_same_shape(B, C)); - GGML_ASSERT(ids->type == GGML_TYPE_I32); - - { - const int64_t d_state = s->ne[0]; - const int64_t head_dim = x->ne[0]; - const int64_t n_head = x->ne[1]; - const int64_t n_seq_tokens = x->ne[2]; - const int64_t n_seqs = x->ne[3]; - - GGML_ASSERT(dt->ne[0] == n_head); - GGML_ASSERT(dt->ne[1] == n_seq_tokens); - GGML_ASSERT(dt->ne[2] == n_seqs); - GGML_ASSERT(ggml_is_3d(dt)); - GGML_ASSERT(s->ne[1] == head_dim); - GGML_ASSERT(s->ne[2] == n_head); - GGML_ASSERT(B->ne[0] == d_state); - GGML_ASSERT(B->ne[2] == n_seq_tokens); - GGML_ASSERT(B->ne[3] == n_seqs); - GGML_ASSERT(ids->ne[0] == n_seqs); - GGML_ASSERT(ggml_is_vector(ids)); - GGML_ASSERT(A->ne[1] == n_head); - GGML_ASSERT(ggml_is_matrix(A)); - - if (A->ne[0] != 1) { - // Mamba-1 has more granular decay factors - GGML_ASSERT(A->ne[0] == d_state); - } - } - - // concatenated y + ssm_states - struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, ggml_nelements(x) + s->ne[0]*s->ne[1]*s->ne[2]*ids->ne[0]); - - result->op = GGML_OP_SSM_SCAN; - result->src[0] = s; - result->src[1] = x; - result->src[2] = dt; - result->src[3] = A; - result->src[4] = B; - result->src[5] = C; - result->src[6] = ids; - - return result; -} - -// ggml_win_part - -struct ggml_tensor * ggml_win_part( - struct ggml_context * ctx, - struct ggml_tensor * a, - int w) { - GGML_ASSERT(a->ne[3] == 1); - GGML_ASSERT(a->type == GGML_TYPE_F32); - - // padding - const int px = (w - a->ne[1]%w)%w; - const int py = (w - a->ne[2]%w)%w; - - const int npx = (px + a->ne[1])/w; - const int npy = (py + a->ne[2])/w; - const int np = npx*npy; - - const int64_t ne[4] = { a->ne[0], w, w, np, }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - int32_t params[] = { npx, npy, w }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_WIN_PART; - result->src[0] = a; - - return result; -} - -// ggml_win_unpart - -struct ggml_tensor * ggml_win_unpart( - struct ggml_context * ctx, - struct ggml_tensor * a, - int w0, - int h0, - int w) { - GGML_ASSERT(a->type == GGML_TYPE_F32); - - const int64_t ne[4] = { a->ne[0], w0, h0, 1, }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 3, ne); - - int32_t params[] = { w }; - ggml_set_op_params(result, params, sizeof(params)); - - result->op = GGML_OP_WIN_UNPART; - result->src[0] = a; - - return result; -} - -// ggml_get_rel_pos - -struct ggml_tensor * ggml_get_rel_pos( - struct ggml_context * ctx, - struct ggml_tensor * a, - int qh, - int kh) { - GGML_ASSERT(qh == kh); - GGML_ASSERT(2*MAX(qh, kh) - 1 == a->ne[1]); - - const int64_t ne[4] = { a->ne[0], kh, qh, 1, }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F16, 3, ne); - - result->op = GGML_OP_GET_REL_POS; - result->src[0] = a; - - return result; -} - -// ggml_add_rel_pos - -static struct ggml_tensor * ggml_add_rel_pos_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * pw, - struct ggml_tensor * ph, - bool inplace) { - GGML_ASSERT(ggml_are_same_shape(pw, ph)); - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(ggml_is_contiguous(pw)); - GGML_ASSERT(ggml_is_contiguous(ph)); - GGML_ASSERT(ph->type == GGML_TYPE_F32); - GGML_ASSERT(pw->type == GGML_TYPE_F32); - GGML_ASSERT(pw->ne[3] == a->ne[2]); - GGML_ASSERT(pw->ne[0]*pw->ne[0] == a->ne[0]); - GGML_ASSERT(pw->ne[1]*pw->ne[2] == a->ne[1]); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - ggml_set_op_params_i32(result, 0, inplace ? 1 : 0); - - result->op = GGML_OP_ADD_REL_POS; - result->src[0] = a; - result->src[1] = pw; - result->src[2] = ph; - - return result; -} - -struct ggml_tensor * ggml_add_rel_pos( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * pw, - struct ggml_tensor * ph) { - return ggml_add_rel_pos_impl(ctx, a, pw, ph, false); -} - -struct ggml_tensor * ggml_add_rel_pos_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * pw, - struct ggml_tensor * ph) { - return ggml_add_rel_pos_impl(ctx, a, pw, ph, true); -} - -// ggml_rwkv_wkv6 - -struct ggml_tensor * ggml_rwkv_wkv6( - struct ggml_context * ctx, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * r, - struct ggml_tensor * tf, - struct ggml_tensor * td, - struct ggml_tensor * state) { - GGML_ASSERT(ggml_is_contiguous(k)); - GGML_ASSERT(ggml_is_contiguous(v)); - GGML_ASSERT(ggml_is_contiguous(r)); - GGML_ASSERT(ggml_is_contiguous(tf)); - GGML_ASSERT(ggml_is_contiguous(td)); - GGML_ASSERT(ggml_is_contiguous(state)); - - const int64_t S = k->ne[0]; - const int64_t H = k->ne[1]; - const int64_t n_tokens = k->ne[2]; - const int64_t n_seqs = state->ne[1]; - { - GGML_ASSERT(v->ne[0] == S && v->ne[1] == H && v->ne[2] == n_tokens); - GGML_ASSERT(r->ne[0] == S && r->ne[1] == H && r->ne[2] == n_tokens); - GGML_ASSERT(td->ne[0] == S && td->ne[1] == H && td->ne[2] == n_tokens); - GGML_ASSERT(ggml_nelements(state) == S * S * H * n_seqs); - } - - // concat output and new_state - const int64_t ne[4] = { S * H, n_tokens + S * n_seqs, 1, 1 }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - result->op = GGML_OP_RWKV_WKV6; - result->src[0] = k; - result->src[1] = v; - result->src[2] = r; - result->src[3] = tf; - result->src[4] = td; - result->src[5] = state; - - return result; -} - -// ggml_gated_linear_attn - -struct ggml_tensor * ggml_gated_linear_attn( - struct ggml_context * ctx, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * q, - struct ggml_tensor * g, - struct ggml_tensor * state, - float scale) { - GGML_ASSERT(ggml_is_contiguous(k)); - GGML_ASSERT(ggml_is_contiguous(v)); - GGML_ASSERT(ggml_is_contiguous(q)); - GGML_ASSERT(ggml_is_contiguous(g)); - GGML_ASSERT(ggml_is_contiguous(state)); - - const int64_t S = k->ne[0]; - const int64_t H = k->ne[1]; - const int64_t n_tokens = k->ne[2]; - const int64_t n_seqs = state->ne[1]; - { - GGML_ASSERT(v->ne[0] == S && v->ne[1] == H && v->ne[2] == n_tokens); - GGML_ASSERT(q->ne[0] == S && q->ne[1] == H && q->ne[2] == n_tokens); - GGML_ASSERT(g->ne[0] == S && g->ne[1] == H && g->ne[2] == n_tokens); - GGML_ASSERT(ggml_nelements(state) == S * S * H * n_seqs); - } - - // concat output and new_state - const int64_t ne[4] = { S * H, n_tokens + S * n_seqs, 1, 1 }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - ggml_set_op_params_f32(result, 0, scale); - - result->op = GGML_OP_GATED_LINEAR_ATTN; - result->src[0] = k; - result->src[1] = v; - result->src[2] = q; - result->src[3] = g; - result->src[4] = state; - - return result; -} - -// ggml_rwkv_wkv7 - -struct ggml_tensor * ggml_rwkv_wkv7( - struct ggml_context * ctx, - struct ggml_tensor * r, - struct ggml_tensor * w, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * state) { - GGML_ASSERT(ggml_is_contiguous(r)); - GGML_ASSERT(ggml_is_contiguous(w)); - GGML_ASSERT(ggml_is_contiguous(k)); - GGML_ASSERT(ggml_is_contiguous(v)); - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(ggml_is_contiguous(b)); - GGML_ASSERT(ggml_is_contiguous(state)); - - const int64_t S = k->ne[0]; - const int64_t H = k->ne[1]; - const int64_t n_tokens = k->ne[2]; - const int64_t n_seqs = state->ne[1]; - { - GGML_ASSERT(w->ne[0] == S && w->ne[1] == H && w->ne[2] == n_tokens); - GGML_ASSERT(k->ne[0] == S && k->ne[1] == H && k->ne[2] == n_tokens); - GGML_ASSERT(v->ne[0] == S && v->ne[1] == H && v->ne[2] == n_tokens); - GGML_ASSERT(a->ne[0] == S && a->ne[1] == H && a->ne[2] == n_tokens); - GGML_ASSERT(b->ne[0] == S && b->ne[1] == H && b->ne[2] == n_tokens); - GGML_ASSERT(ggml_nelements(state) == S * S * H * n_seqs); - } - - // concat output and new_state - const int64_t ne[4] = { S * H, n_tokens + S * n_seqs, 1, 1 }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - result->op = GGML_OP_RWKV_WKV7; - result->src[0] = r; - result->src[1] = w; - result->src[2] = k; - result->src[3] = v; - result->src[4] = a; - result->src[5] = b; - result->src[6] = state; - - return result; -} - -// ggml_unary - -static struct ggml_tensor * ggml_unary_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_unary_op op, - bool inplace) { - GGML_ASSERT(ggml_is_contiguous_rows(a)); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - ggml_set_op_params_i32(result, 0, (int32_t) op); - - result->op = GGML_OP_UNARY; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_unary( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_unary_op op) { - return ggml_unary_impl(ctx, a, op, false); -} - -struct ggml_tensor * ggml_unary_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - enum ggml_unary_op op) { - return ggml_unary_impl(ctx, a, op, true); -} - -// ggml_map_custom1 - -static struct ggml_tensor * ggml_map_custom1_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - const ggml_custom1_op_t fun, - int n_tasks, - void * userdata, - bool inplace) { - GGML_ASSERT(n_tasks == GGML_N_TASKS_MAX || n_tasks > 0); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - struct ggml_map_custom1_op_params params = { - /*.fun =*/ fun, - /*.n_tasks =*/ n_tasks, - /*.userdata =*/ userdata - }; - ggml_set_op_params(result, ¶ms, sizeof(params)); - - result->op = GGML_OP_MAP_CUSTOM1; - result->src[0] = a; - - return result; -} - -struct ggml_tensor * ggml_map_custom1( - struct ggml_context * ctx, - struct ggml_tensor * a, - const ggml_custom1_op_t fun, - int n_tasks, - void * userdata) { - return ggml_map_custom1_impl(ctx, a, fun, n_tasks, userdata, false); -} - -struct ggml_tensor * ggml_map_custom1_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - const ggml_custom1_op_t fun, - int n_tasks, - void * userdata) { - return ggml_map_custom1_impl(ctx, a, fun, n_tasks, userdata, true); -} - -// ggml_map_custom2 - -static struct ggml_tensor * ggml_map_custom2_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - const ggml_custom2_op_t fun, - int n_tasks, - void * userdata, - bool inplace) { - GGML_ASSERT(n_tasks == GGML_N_TASKS_MAX || n_tasks > 0); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - struct ggml_map_custom2_op_params params = { - /*.fun =*/ fun, - /*.n_tasks =*/ n_tasks, - /*.userdata =*/ userdata - }; - ggml_set_op_params(result, ¶ms, sizeof(params)); - - result->op = GGML_OP_MAP_CUSTOM2; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -struct ggml_tensor * ggml_map_custom2( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - const ggml_custom2_op_t fun, - int n_tasks, - void * userdata) { - return ggml_map_custom2_impl(ctx, a, b, fun, n_tasks, userdata, false); -} - -struct ggml_tensor * ggml_map_custom2_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - const ggml_custom2_op_t fun, - int n_tasks, - void * userdata) { - return ggml_map_custom2_impl(ctx, a, b, fun, n_tasks, userdata, true); -} - -// ggml_map_custom3 - -static struct ggml_tensor * ggml_map_custom3_impl( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - const ggml_custom3_op_t fun, - int n_tasks, - void * userdata, - bool inplace) { - GGML_ASSERT(n_tasks == GGML_N_TASKS_MAX || n_tasks > 0); - - struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); - - struct ggml_map_custom3_op_params params = { - /*.fun =*/ fun, - /*.n_tasks =*/ n_tasks, - /*.userdata =*/ userdata - }; - ggml_set_op_params(result, ¶ms, sizeof(params)); - - result->op = GGML_OP_MAP_CUSTOM3; - result->src[0] = a; - result->src[1] = b; - result->src[2] = c; - - return result; -} - -struct ggml_tensor * ggml_map_custom3( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - const ggml_custom3_op_t fun, - int n_tasks, - void * userdata) { - return ggml_map_custom3_impl(ctx, a, b, c, fun, n_tasks, userdata, false); -} - -struct ggml_tensor * ggml_map_custom3_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c, - const ggml_custom3_op_t fun, - int n_tasks, - void * userdata) { - return ggml_map_custom3_impl(ctx, a, b, c, fun, n_tasks, userdata, true); -} - -struct ggml_tensor * ggml_custom_4d( - struct ggml_context * ctx, - enum ggml_type type, - int64_t ne0, - int64_t ne1, - int64_t ne2, - int64_t ne3, - struct ggml_tensor ** args, - int n_args, - ggml_custom_op_t fun, - int n_tasks, - void * userdata) { - - GGML_ASSERT(n_args < GGML_MAX_SRC); - - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, type, ne0, ne1, ne2, ne3); - - struct ggml_custom_op_params params = { - /*.fun =*/ fun, - /*.n_tasks =*/ n_tasks, - /*.userdata =*/ userdata - }; - ggml_set_op_params(result, ¶ms, sizeof(params)); - - result->op = GGML_OP_CUSTOM; - for (int i = 0; i < n_args; i++) { - result->src[i] = args[i]; - } - - return result; -} - -struct ggml_tensor * ggml_custom_inplace( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor ** args, - int n_args, - ggml_custom_op_t fun, - int n_tasks, - void * userdata) { - - GGML_ASSERT(n_args < GGML_MAX_SRC - 1); - - struct ggml_tensor * result = ggml_view_tensor(ctx, a); - - struct ggml_custom_op_params params = { - /*.fun =*/ fun, - /*.n_tasks =*/ n_tasks, - /*.userdata =*/ userdata - }; - ggml_set_op_params(result, ¶ms, sizeof(params)); - - result->op = GGML_OP_CUSTOM; - result->src[0] = a; - for (int i = 0; i < n_args; i++) { - result->src[i + 1] = args[i]; - } - - return result; -} -// ggml_cross_entropy_loss - -struct ggml_tensor * ggml_cross_entropy_loss( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b) { - GGML_ASSERT(ggml_are_same_shape(a, b)); - - struct ggml_tensor * result = ggml_new_tensor_1d(ctx, a->type, 1); - - result->op = GGML_OP_CROSS_ENTROPY_LOSS; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_cross_entropy_loss_back - -struct ggml_tensor * ggml_cross_entropy_loss_back( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - struct ggml_tensor * c) { - GGML_ASSERT(ggml_is_scalar(a)); - GGML_ASSERT(ggml_are_same_shape(b, c)); - - struct ggml_tensor * result = ggml_dup_tensor(ctx, b); - - result->op = GGML_OP_CROSS_ENTROPY_LOSS_BACK; - result->src[0] = a; - result->src[1] = b; - result->src[2] = c; - - return result; -} - -// opt_step_adamw - -struct ggml_tensor * ggml_opt_step_adamw( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * grad, - struct ggml_tensor * m, - struct ggml_tensor * v, - struct ggml_tensor * adamw_params) { - GGML_ASSERT(a->flags & GGML_TENSOR_FLAG_PARAM); - GGML_ASSERT(ggml_are_same_shape(a, grad)); - GGML_ASSERT(ggml_are_same_shape(a, m)); - GGML_ASSERT(ggml_are_same_shape(a, v)); - GGML_ASSERT(adamw_params->type == GGML_TYPE_F32); - GGML_ASSERT(ggml_nelements(adamw_params) == 7); - - struct ggml_tensor * result = ggml_view_tensor(ctx, a); - - result->op = GGML_OP_OPT_STEP_ADAMW; - result->src[0] = a; - result->src[1] = grad; - result->src[2] = m; - result->src[3] = v; - result->src[4] = adamw_params; - - return result; -} - -// opt_step_sgd - -struct ggml_tensor * ggml_opt_step_sgd( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * grad, - struct ggml_tensor * params) { - GGML_ASSERT(a->flags & GGML_TENSOR_FLAG_PARAM); - GGML_ASSERT(ggml_are_same_shape(a, grad)); - GGML_ASSERT(params->type == GGML_TYPE_F32); - GGML_ASSERT(ggml_nelements(params) == 2); - - struct ggml_tensor * result = ggml_view_tensor(ctx, a); - - result->op = GGML_OP_OPT_STEP_SGD; - result->src[0] = a; - result->src[1] = grad; - result->src[2] = params; - - return result; -} - -// solve_tri - -struct ggml_tensor * ggml_solve_tri( - struct ggml_context * ctx, - struct ggml_tensor * a, - struct ggml_tensor * b, - bool left, - bool lower, - bool uni) { - GGML_ASSERT(a->type == GGML_TYPE_F32); - GGML_ASSERT(b->type == GGML_TYPE_F32); - - // A must be square and lower diagonal - GGML_ASSERT(a->ne[0] == a->ne[1]); - // B must have same outer dimension as A - GGML_ASSERT(a->ne[1] == b->ne[1]); - - // batch dimensions must be equal - GGML_ASSERT(a->ne[2] == b->ne[2]); - GGML_ASSERT(a->ne[3] == b->ne[3]); - - GGML_ASSERT(ggml_is_contiguous(a)); - GGML_ASSERT(ggml_is_contiguous(b)); - - GGML_ASSERT(lower && left && !uni); // TODO: support other variants - - struct ggml_tensor * result = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, b->ne[0], b->ne[1], b->ne[2], b->ne[3]); - - result->op = GGML_OP_SOLVE_TRI; - result->src[0] = a; - result->src[1] = b; - - return result; -} - -// ggml_gated_delta_net - -struct ggml_tensor * ggml_gated_delta_net( - struct ggml_context * ctx, - struct ggml_tensor * q, - struct ggml_tensor * k, - struct ggml_tensor * v, - struct ggml_tensor * g, - struct ggml_tensor * beta, - struct ggml_tensor * state) { - GGML_ASSERT(ggml_is_contiguous_rows(q)); - GGML_ASSERT(ggml_is_contiguous_rows(k)); - GGML_ASSERT(ggml_is_contiguous_rows(v)); - GGML_ASSERT(ggml_is_contiguous(g)); - GGML_ASSERT(ggml_is_contiguous(beta)); - GGML_ASSERT(ggml_is_contiguous(state)); - - GGML_ASSERT(q->type == GGML_TYPE_F32); - GGML_ASSERT(k->type == GGML_TYPE_F32); - GGML_ASSERT(v->type == GGML_TYPE_F32); - GGML_ASSERT(g->type == GGML_TYPE_F32); - GGML_ASSERT(beta->type == GGML_TYPE_F32); - GGML_ASSERT(state->type == GGML_TYPE_F32); - - const int64_t S_v = v->ne[0]; - const int64_t H = v->ne[1]; - const int64_t n_tokens = v->ne[2]; - const int64_t n_seqs = v->ne[3]; - - // gate: scalar [1, H, T, B] or vector [S_v, H, T, B] (KDA) - GGML_ASSERT(g->ne[0] == 1 || g->ne[0] == S_v); - GGML_ASSERT(beta->ne[0] == 1); - - // state is a 3D tensor (S_v*S_v*H, K, n_seqs). K is the snapshot slot count. - GGML_ASSERT(state->ne[0] == S_v * S_v * H); - GGML_ASSERT(state->ne[2] == n_seqs); - GGML_ASSERT(state->ne[3] == 1); - const int64_t K = state->ne[1]; - const int64_t state_rows = K * S_v * n_seqs; - const int64_t ne[4] = { S_v * H, n_tokens * n_seqs + state_rows, 1, 1 }; - struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); - - result->op = GGML_OP_GATED_DELTA_NET; - result->src[0] = q; - result->src[1] = k; - result->src[2] = v; - result->src[3] = g; - result->src[4] = beta; - result->src[5] = state; - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// - -struct ggml_hash_set ggml_hash_set_new(size_t size) { - size = ggml_hash_size(size); - struct ggml_hash_set result; - result.size = size; - result.keys = GGML_MALLOC(sizeof(struct ggml_tensor *) * size); - result.used = GGML_CALLOC(ggml_bitset_size(size), sizeof(ggml_bitset_t)); - return result; -} - -void ggml_hash_set_reset(struct ggml_hash_set * hash_set) { - memset(hash_set->used, 0, sizeof(ggml_bitset_t) * ggml_bitset_size(hash_set->size)); -} - -void ggml_hash_set_free(struct ggml_hash_set * hash_set) { - GGML_FREE(hash_set->used); - GGML_FREE(hash_set->keys); -} - -size_t ggml_hash_size(size_t min_sz) { - // next primes after powers of two - static const size_t primes[] = { - 2, 3, 5, 11, 17, 37, 67, 131, 257, 521, 1031, - 2053, 4099, 8209, 16411, 32771, 65537, 131101, - 262147, 524309, 1048583, 2097169, 4194319, 8388617, - 16777259, 33554467, 67108879, 134217757, 268435459, - 536870923, 1073741827, 2147483659 - }; - static const size_t n_primes = sizeof(primes)/sizeof(primes[0]); - - // find the smallest prime that is larger or equal than min_sz - size_t l = 0; - size_t r = n_primes; - while (l < r) { - size_t m = (l + r)/2; - if (primes[m] < min_sz) { - l = m + 1; - } else { - r = m; - } - } - size_t sz = l < n_primes ? primes[l] : min_sz | 1; - return sz; -} - -struct hash_map { - struct ggml_hash_set set; - struct ggml_tensor ** vals; -}; - -static struct hash_map * ggml_new_hash_map(size_t size) { - struct hash_map * result = GGML_MALLOC(sizeof(struct hash_map)); - result->set = ggml_hash_set_new(size); - result->vals = GGML_CALLOC(result->set.size, sizeof(struct ggml_tensor *)); - return result; -} - -static void ggml_hash_map_free(struct hash_map * map) { - ggml_hash_set_free(&map->set); - GGML_FREE(map->vals); - GGML_FREE(map); -} - -// utility functions to change gradients -// isrc is the index of tensor in cgraph->visited_has_set.keys -// the corresponding gradient (accumulators) are also at position isrc -// if tensor has a gradient accumulator, modify that accumulator in-place -// else if there is no gradient for tensor, set the corresponding value -// else, just add/subtract/etc. the gradients - -static void ggml_add_or_set( - struct ggml_context * ctx, - struct ggml_cgraph * cgraph, - size_t isrc, - struct ggml_tensor * tensor) { - struct ggml_tensor * src = cgraph->visited_hash_set.keys[isrc]; - GGML_ASSERT(src); - if (cgraph->grads[isrc]) { - cgraph->grads[isrc] = ggml_add_impl(ctx, cgraph->grads[isrc], tensor, /*inplace =*/ cgraph->grad_accs[isrc]); - } else { - cgraph->grads[isrc] = tensor; - } - ggml_format_name(cgraph->grads[isrc], "grad for %s", src->name); - ggml_build_forward_expand(cgraph, cgraph->grads[isrc]); -} - -static void ggml_acc_or_set( - struct ggml_context * ctx, - struct ggml_cgraph * cgraph, - size_t isrc, - struct ggml_tensor * tensor, - const size_t nb1, - const size_t nb2, - const size_t nb3, - const size_t offset) { - struct ggml_tensor * src = cgraph->visited_hash_set.keys[isrc]; - GGML_ASSERT(src); - if (cgraph->grads[isrc]) { - cgraph->grads[isrc] = ggml_acc_impl(ctx, cgraph->grads[isrc], tensor, nb1, nb2, nb3, offset, cgraph->grad_accs[isrc]); - } else { - struct ggml_tensor * a_zero = ggml_scale(ctx, src, 0.0f); // FIXME this is going to produce NaN if a contains inf/NaN - cgraph->grads[isrc] = ggml_acc_impl(ctx, a_zero, tensor, nb1, nb2, nb3, offset, false); - } - ggml_format_name(cgraph->grads[isrc], "grad for %s", cgraph->visited_hash_set.keys[isrc]->name); - ggml_build_forward_expand(cgraph, cgraph->grads[isrc]); -} - -static void ggml_add1_or_set( - struct ggml_context * ctx, - struct ggml_cgraph * cgraph, - size_t isrc, - struct ggml_tensor * tensor) { - struct ggml_tensor * src = cgraph->visited_hash_set.keys[isrc]; - GGML_ASSERT(src); - if (cgraph->grads[isrc]) { - cgraph->grads[isrc] = ggml_add1_impl(ctx, cgraph->grads[isrc], tensor, cgraph->grad_accs[isrc]); - } else { - cgraph->grads[isrc] = ggml_repeat(ctx, tensor, src); - } - ggml_format_name(cgraph->grads[isrc], "grad for %s", src->name); - ggml_build_forward_expand(cgraph, cgraph->grads[isrc]); -} - -static void ggml_sub_or_set( - struct ggml_context * ctx, - struct ggml_cgraph * cgraph, - size_t isrc, - struct ggml_tensor * tensor) { - struct ggml_tensor * src = cgraph->visited_hash_set.keys[isrc]; - GGML_ASSERT(src); - if (cgraph->grads[isrc]) { - cgraph->grads[isrc] = ggml_sub_impl(ctx, cgraph->grads[isrc], tensor, cgraph->grad_accs[isrc]); - } else { - cgraph->grads[isrc] = ggml_neg(ctx, tensor); - } - ggml_format_name(cgraph->grads[isrc], "grad for %s", src->name); - ggml_build_forward_expand(cgraph, cgraph->grads[isrc]); -} - -static void ggml_compute_backward( - struct ggml_context * ctx, struct ggml_cgraph * cgraph, int i, const bool * grads_needed) { - struct ggml_tensor * tensor = cgraph->nodes[i]; - struct ggml_tensor * grad = ggml_graph_get_grad(cgraph, tensor); - - if (!grad) { - return; - } - - struct ggml_tensor * src0 = tensor->src[0]; - struct ggml_tensor * src1 = tensor->src[1]; - struct ggml_tensor * src2 = tensor->src[2]; - struct ggml_hash_set * hash_set = &cgraph->visited_hash_set; - const size_t isrc0 = src0 ? ggml_hash_find(hash_set, src0) : (size_t) -1; - const size_t isrc1 = src1 ? ggml_hash_find(hash_set, src1) : (size_t) -1; - const size_t isrc2 = src2 ? ggml_hash_find(hash_set, src2) : (size_t) -1; - const bool src0_needs_grads = src0 && isrc0 != GGML_HASHSET_FULL && ggml_bitset_get(hash_set->used, isrc0) && grads_needed[isrc0]; - const bool src1_needs_grads = src1 && isrc1 != GGML_HASHSET_FULL && ggml_bitset_get(hash_set->used, isrc1) && grads_needed[isrc1]; - const bool src2_needs_grads = src2 && isrc2 != GGML_HASHSET_FULL && ggml_bitset_get(hash_set->used, isrc2) && grads_needed[isrc2]; - - switch (tensor->op) { - case GGML_OP_DUP: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, grad); - } - } break; - case GGML_OP_ADD: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, grad); - } - if (src1_needs_grads) { - struct ggml_tensor * tmp = grad; - if (!ggml_are_same_shape(src0, src1)) { - tmp = ggml_repeat_back(ctx, tmp, src1); - } - ggml_add_or_set(ctx, cgraph, isrc1, tmp); - } - } break; - case GGML_OP_ADD1: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, grad); - } - if (src1_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc1, ggml_mean(ctx, grad)); // TODO: should probably be sum instead of mean - } - } break; - case GGML_OP_ACC: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, grad); - } - if (src1_needs_grads) { - const size_t nb1 = ((int32_t *) tensor->op_params)[0]; - const size_t nb2 = ((int32_t *) tensor->op_params)[1]; - const size_t nb3 = ((int32_t *) tensor->op_params)[2]; - const size_t offset = ((int32_t *) tensor->op_params)[3]; - - struct ggml_tensor * tensor_grad_view = ggml_view_4d(ctx, - grad, src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3], - nb1, nb2, nb3, offset); - - ggml_add_or_set(ctx, cgraph, isrc1, ggml_reshape(ctx, ggml_cont(ctx, tensor_grad_view), src1)); - } - } break; - case GGML_OP_SUB: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, grad); - } - if (src1_needs_grads) { - ggml_sub_or_set(ctx, cgraph, isrc1, grad); - } - } break; - case GGML_OP_MUL: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, src1)); - } - if (src1_needs_grads) { - struct ggml_tensor * tmp = ggml_mul(ctx, src0, grad); - if (!ggml_are_same_shape(src0, src1)) { - tmp = ggml_repeat_back(ctx, tmp, src1); - } - ggml_add_or_set(ctx, cgraph, isrc1, tmp); - } - } break; - case GGML_OP_DIV: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_div(ctx, grad, src1)); - } - if (src1_needs_grads) { - ggml_sub_or_set(ctx, cgraph, isrc1, ggml_mul(ctx, grad, ggml_div(ctx, tensor, src1))); - } - } break; - case GGML_OP_SQR: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_scale(ctx, ggml_mul(ctx, src0, grad), 2.0f)); - } - } break; - case GGML_OP_SQRT: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_scale(ctx, ggml_div(ctx, grad, tensor), 0.5f)); - } - } break; - case GGML_OP_LOG: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_div(ctx, grad, src0)); - } - } break; - case GGML_OP_SIN: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, ggml_cos(ctx, src0))); - } - } break; - case GGML_OP_COS: { - if (src0_needs_grads) { - ggml_sub_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, ggml_sin(ctx, src0))); - } - } break; - case GGML_OP_SUM: { - if (src0_needs_grads) { - ggml_add1_or_set(ctx, cgraph, isrc0, grad); - } - } break; - case GGML_OP_SUM_ROWS: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_repeat(ctx, grad, src0)); - } - } break; - case GGML_OP_MEAN: { - if (src0_needs_grads) { - ggml_add1_or_set(ctx, cgraph, isrc0, ggml_scale_impl(ctx, grad, 1.0f/src0->ne[0], 0.0, false)); - } - } break; - case GGML_OP_REPEAT: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_repeat_back(ctx, grad, src0)); - } - } break; - case GGML_OP_REPEAT_BACK: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_repeat(ctx, grad, src0)); - } - } break; - case GGML_OP_RMS_NORM: { - if (src0_needs_grads) { - float eps; - memcpy(&eps, tensor->op_params, sizeof(float)); - ggml_add_or_set(ctx, cgraph, isrc0, ggml_rms_norm_back(ctx, grad, src0, eps)); - } - } break; - case GGML_OP_MUL_MAT: - case GGML_OP_MUL_MAT_PACK4: { - // https://cs231n.github.io/optimization-2/#staged - // # forward pass - // s0 = np.random.randn(5, 10) - // s1 = np.random.randn(10, 3) - // t = s0.dot(s1) - - // # now suppose we had the gradient on t from above in the circuit - // dt = np.random.randn(*t.shape) # same shape as t - // ds0 = dt.dot(s1.T) #.T gives the transpose of the matrix - // ds1 = t.T.dot(dt) - - // tensor.shape [m,p,qq,rr] - // src0.shape [n,m,q1,r1] - // src1.shape [n,p,qq,rr] - - if (src0_needs_grads) { - GGML_ASSERT(grad->ne[2] == src1->ne[2]); - GGML_ASSERT(grad->ne[3] == src1->ne[3]); - struct ggml_tensor * tmp = - ggml_out_prod(ctx, // [n,m,qq,rr] - src1, // [n,p,qq,rr] - grad); // [m,p,qq,rr] - if (!ggml_are_same_shape(tmp, src0)) { - GGML_ASSERT(tmp->ne[0] == src0->ne[0]); - GGML_ASSERT(tmp->ne[1] == src0->ne[1]); - GGML_ASSERT(tmp->ne[3] == 1); - - const int64_t nr2 = tmp->ne[2] / src0->ne[2]; - const size_t nb2 = tmp->nb[2] * nr2; - const size_t nb3 = tmp->nb[2]; - - tmp = ggml_view_4d(ctx, tmp, src0->ne[0], src0->ne[1], src0->ne[2], nr2, tmp->nb[1], nb2, nb3, 0); - tmp = ggml_repeat_back(ctx, tmp, src0); - } - ggml_add_or_set(ctx, cgraph, isrc0, tmp); - } - if (src1_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc1, - // ggml_mul_mat(ctx, // [n,p,qq,rr] - // ggml_cont(ctx, // [m,n,q1,r1] - // ggml_transpose(ctx, src0)), // [m,n,q1,r1] - // grad), // [m,p,qq,rr] - - // when src0 is bigger than tensor->grad (this is mostly the case in llama), - // avoid transpose of src0, rather transpose smaller tensor->grad - // and then use ggml_out_prod - ggml_out_prod(ctx, // [n,p,qq,rr] - src0, // [n,m,q1,r1] - ggml_transpose(ctx, // [p,m,qq,rr] - grad))); // [m,p,qq,rr] - } - } break; - case GGML_OP_SCALE: { - if (src0_needs_grads) { - float s; - memcpy(&s, tensor->op_params, sizeof(float)); - ggml_add_or_set(ctx, cgraph, isrc0, ggml_scale_impl(ctx, grad, s, 0.0, false)); - } - } break; - case GGML_OP_SET: { - const size_t nb1 = ((const int32_t *) tensor->op_params)[0]; - const size_t nb2 = ((const int32_t *) tensor->op_params)[1]; - const size_t nb3 = ((const int32_t *) tensor->op_params)[2]; - const size_t offset = ((const int32_t *) tensor->op_params)[3]; - - struct ggml_tensor * tensor_grad_view = NULL; - - if (src0_needs_grads || src1_needs_grads) { - GGML_ASSERT(src0->type == tensor->type); - GGML_ASSERT(!cgraph->grads[isrc0] || cgraph->grads[isrc0]->type == grad->type); - GGML_ASSERT(!cgraph->grads[isrc1] || !src1_needs_grads || cgraph->grads[isrc1]->type == grad->type); - - tensor_grad_view = ggml_view_4d(ctx, - grad, src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3], - nb1, nb2, nb3, offset); - } - - if (src0_needs_grads) { - struct ggml_tensor * tmp = ggml_neg(ctx, tensor_grad_view); - ggml_add_or_set(ctx, cgraph, isrc0, ggml_acc_impl(ctx, grad, tmp, nb1, nb2, nb3, offset, false)); - } - - if (src1_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc1, ggml_reshape(ctx, ggml_cont(ctx, tensor_grad_view), src1)); - } - } break; - case GGML_OP_CPY: { - // cpy overwrites value of src1 by src0 and returns view(src1) - // the overwriting is mathematically equivalent to: - // tensor = src0 * 1 + src1 * 0 - if (src0_needs_grads) { - // dsrc0 = dtensor * 1 - ggml_add_or_set(ctx, cgraph, isrc0, ggml_reshape(ctx, grad, src0)); - } - if (src1_needs_grads) { - // dsrc1 = dtensor * 0 -> noop - } - } break; - case GGML_OP_CONT: { - // same as cpy - if (src0_needs_grads) { - GGML_ASSERT(!cgraph->grads[isrc0] || ggml_is_contiguous(cgraph->grads[isrc0])); - GGML_ASSERT(ggml_is_contiguous(grad)); - GGML_ASSERT(ggml_nelements(tensor) == ggml_nelements(src0)); - ggml_add_or_set(ctx, cgraph, isrc0, - ggml_are_same_shape(tensor, src0) ? grad : ggml_reshape(ctx, grad, src0)); - } - } break; - case GGML_OP_RESHAPE: { - if (src0_needs_grads) { - struct ggml_tensor * grad_cont = ggml_is_contiguous(grad) ? grad : ggml_cont(ctx, grad); - ggml_add_or_set(ctx, cgraph, isrc0, ggml_reshape(ctx, grad_cont, src0)); - } - } break; - case GGML_OP_VIEW: { - if (src0_needs_grads) { - size_t offset; - - memcpy(&offset, tensor->op_params, sizeof(offset)); - - size_t nb1 = tensor->nb[1]; - size_t nb2 = tensor->nb[2]; - size_t nb3 = tensor->nb[3]; - - if (cgraph->grads[isrc0] && src0->type != cgraph->grads[isrc0]->type) { - // gradient is typically F32, but src0 could be other type - size_t ng = ggml_element_size(cgraph->grads[isrc0]); - size_t n0 = ggml_element_size(src0); - GGML_ASSERT(offset % n0 == 0); - GGML_ASSERT(nb1 % n0 == 0); - GGML_ASSERT(nb2 % n0 == 0); - GGML_ASSERT(nb3 % n0 == 0); - offset = (offset / n0) * ng; - nb1 = (nb1 / n0) * ng; - nb2 = (nb2 / n0) * ng; - nb3 = (nb3 / n0) * ng; - } - - ggml_acc_or_set(ctx, cgraph, isrc0, grad, nb1, nb2, nb3, offset); - } - } break; - case GGML_OP_PERMUTE: { - if (src0_needs_grads) { - const int32_t * axes = (const int32_t *) tensor->op_params; - const int axis0 = axes[0] & 0x3; - const int axis1 = axes[1] & 0x3; - const int axis2 = axes[2] & 0x3; - const int axis3 = axes[3] & 0x3; - int axb[4] = {0,0,0,0}; // axes backward - axb[axis0] = 0; - axb[axis1] = 1; - axb[axis2] = 2; - axb[axis3] = 3; - ggml_add_or_set(ctx, cgraph, isrc0, ggml_permute(ctx, grad, axb[0], axb[1], axb[2], axb[3])); - } - } break; - case GGML_OP_TRANSPOSE: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_transpose(ctx, grad)); - } - } break; - case GGML_OP_GET_ROWS: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_get_rows_back(ctx, grad, src1, src0)); - } - if (src1_needs_grads) { - // noop - } - } break; - case GGML_OP_DIAG_MASK_INF: { - if (src0_needs_grads) { - /* ggml_diag_mask_inf_impl() shouldn't be here */ - /* ref: https://github.com/ggml-org/llama.cpp/pull/4203#discussion_r1412377992 */ - const int n_past = ((const int32_t *) tensor->op_params)[0]; - ggml_add_or_set(ctx, cgraph, isrc0, ggml_diag_mask_zero_impl(ctx, grad, n_past, false)); - } - } break; - case GGML_OP_DIAG_MASK_ZERO: { - if (src0_needs_grads) { - const int n_past = ((const int32_t *) tensor->op_params)[0]; - ggml_add_or_set(ctx, cgraph, isrc0, ggml_diag_mask_zero_impl(ctx, grad, n_past, false)); - } - } break; - case GGML_OP_SOFT_MAX: { - if (src0_needs_grads) { - float scale = 1.0f; - float max_bias = 0.0f; - - memcpy(&scale, (const float *) tensor->op_params + 0, sizeof(float)); - memcpy(&max_bias, (const float *) tensor->op_params + 1, sizeof(float)); - - ggml_add_or_set(ctx, cgraph, isrc0, ggml_soft_max_ext_back(ctx, grad, tensor, scale, max_bias)); - } - GGML_ASSERT((!src1 || !src1_needs_grads) && "backward pass for softmax mask not implemented"); - } break; - case GGML_OP_ROPE: { - if (src0_needs_grads) { - //const int n_past = ((int32_t *) tensor->op_params)[0]; - const int n_dims = ((const int32_t *) tensor->op_params)[1]; - const int mode = ((const int32_t *) tensor->op_params)[2]; - //const int n_ctx = ((int32_t *) tensor->op_params)[3]; - const int n_ctx_orig = ((const int32_t *) tensor->op_params)[4]; - float freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow; - int sections[4] = {0, 0, 0, 0}; - - memcpy(&freq_base, (const float *) tensor->op_params + 5, sizeof(float)); - memcpy(&freq_scale, (const float *) tensor->op_params + 6, sizeof(float)); - memcpy(&ext_factor, (const float *) tensor->op_params + 7, sizeof(float)); - memcpy(&attn_factor, (const float *) tensor->op_params + 8, sizeof(float)); - memcpy(&beta_fast, (const float *) tensor->op_params + 9, sizeof(float)); - memcpy(&beta_slow, (const float *) tensor->op_params + 10, sizeof(float)); - memcpy(§ions, tensor->op_params + 11, sizeof(sections)); - - struct ggml_tensor * rope_back = grad->ne[2] == src1->ne[0] ? - ggml_rope_ext_back(ctx, grad, src1, src2, n_dims, - mode, n_ctx_orig, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow) : - ggml_rope_multi_back(ctx, grad, src1, src2, n_dims, sections, - mode, n_ctx_orig, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow); - ggml_add_or_set(ctx, cgraph, isrc0, rope_back); - } - GGML_ASSERT((!src2 || !src2_needs_grads) && "gradients for freq factors not implemented"); - } break; - case GGML_OP_IM2COL: - case GGML_OP_IM2COL_FAST_1D: { - if (src1_needs_grads) { - const int32_t s0 = ggml_get_op_params_i32(tensor, 0); - const int32_t s1 = ggml_get_op_params_i32(tensor, 1); - const int32_t p0 = ggml_get_op_params_i32(tensor, 2); - const int32_t p1 = ggml_get_op_params_i32(tensor, 3); - const int32_t d0 = ggml_get_op_params_i32(tensor, 4); - const int32_t d1 = ggml_get_op_params_i32(tensor, 5); - const bool is_2D = ggml_get_op_params_i32(tensor, 6) == 1; - - ggml_add_or_set(ctx, cgraph, isrc1, ggml_im2col_back(ctx, grad, src0, src1->ne, s0, s1, p0, p1, d0, d1, is_2D)); - } - } break; - case GGML_OP_POOL_2D: { - if (src0_needs_grads) { - const enum ggml_op_pool op = ggml_get_op_params_i32(tensor, 0); - const int32_t k0 = ggml_get_op_params_i32(tensor, 1); - const int32_t k1 = ggml_get_op_params_i32(tensor, 2); - const int32_t s0 = ggml_get_op_params_i32(tensor, 3); - const int32_t s1 = ggml_get_op_params_i32(tensor, 4); - const int32_t p0 = ggml_get_op_params_i32(tensor, 5); - const int32_t p1 = ggml_get_op_params_i32(tensor, 6); - - ggml_add_or_set(ctx, cgraph, isrc0, ggml_pool_2d_back(ctx, grad, src0, op, k0, k1, s0, s1, p0, p1)); - } - } break; - case GGML_OP_WIN_PART: - case GGML_OP_WIN_UNPART: - case GGML_OP_UNARY: { - switch (ggml_get_unary_op(tensor)) { - case GGML_UNARY_OP_ABS: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, ggml_sgn(ctx, src0), grad)); - } - } break; - case GGML_UNARY_OP_SGN: { - // noop - } break; - case GGML_UNARY_OP_NEG: { - if (src0_needs_grads) { - ggml_sub_or_set(ctx, cgraph, isrc0, grad); - } - } break; - case GGML_UNARY_OP_STEP: { - // noop - } break; - case GGML_UNARY_OP_RELU: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, ggml_step(ctx, src0), grad)); - } - } break; - case GGML_UNARY_OP_SILU: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_silu_back(ctx, grad, src0)); - } - } break; - case GGML_UNARY_OP_EXP: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, tensor, grad)); - } - } break; - case GGML_UNARY_OP_EXPM1: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, ggml_exp(ctx, src0))); - } - } break; - case GGML_UNARY_OP_SOFTPLUS: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, ggml_sigmoid(ctx, src0))); - } - } break; - default: { - fprintf(stderr, "%s: unsupported unary op for backward pass: %s\n", - __func__, ggml_unary_op_name(ggml_get_unary_op(tensor))); - GGML_ABORT("fatal error"); - } //break; - } - } break; - case GGML_OP_CROSS_ENTROPY_LOSS: { - if (src0_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc0, ggml_cross_entropy_loss_back(ctx, grad, src0, src1)); - } - GGML_ASSERT(!src1_needs_grads && "backward pass for labels not implemented"); - } break; - case GGML_OP_GLU: { - switch (ggml_get_glu_op(tensor)) { - case GGML_GLU_OP_SWIGLU: { - if (src0_needs_grads) { - GGML_ASSERT(src1 && "backward pass only implemented for split swiglu"); - ggml_add_or_set(ctx, cgraph, isrc0, ggml_silu_back(ctx, ggml_mul(ctx, grad, src1), src0)); - } - if (src1_needs_grads) { - ggml_add_or_set(ctx, cgraph, isrc1, ggml_mul(ctx, ggml_silu(ctx, src0), grad)); - } - } break; - default: { - GGML_ABORT("unsupported glu op for backward pass: %s", ggml_glu_op_name(ggml_get_glu_op(tensor))); - } //break; - } - } break; - case GGML_OP_NONE: { - // noop - } break; - case GGML_OP_COUNT: - default: { - GGML_ABORT("%s: unsupported ggml op for backward pass: %s\n", __func__, ggml_op_name(tensor->op)); - } //break; - } - - GGML_ASSERT(!src0_needs_grads || ggml_are_same_shape(src0, cgraph->grads[isrc0])); - GGML_ASSERT(!src1_needs_grads || ggml_are_same_shape(src1, cgraph->grads[isrc1])); - GGML_ASSERT(!src2_needs_grads || ggml_are_same_shape(src2, cgraph->grads[isrc2])); -} - -static size_t ggml_visit_parents_graph(struct ggml_cgraph * cgraph, struct ggml_tensor * node, bool compute) { - if (node->op != GGML_OP_NONE && compute) { - node->flags |= GGML_TENSOR_FLAG_COMPUTE; - } - - const size_t node_hash_pos = ggml_hash_find(&cgraph->visited_hash_set, node); - GGML_ASSERT(node_hash_pos != GGML_HASHSET_FULL); - - if (ggml_bitset_get(cgraph->visited_hash_set.used, node_hash_pos)) { - // already visited - - if (compute) { - // update the compute flag regardless - for (int i = 0; i < GGML_MAX_SRC; ++i) { - struct ggml_tensor * src = node->src[i]; - if (src && ((src->flags & GGML_TENSOR_FLAG_COMPUTE) == 0)) { - ggml_visit_parents_graph(cgraph, src, true); - } - } - } - - return node_hash_pos; - } - - // This is the first time we see this node in the current graph. - cgraph->visited_hash_set.keys[node_hash_pos] = node; - ggml_bitset_set(cgraph->visited_hash_set.used, node_hash_pos); - cgraph->use_counts[node_hash_pos] = 0; - - for (int i = 0; i < GGML_MAX_SRC; ++i) { - const int k = - (cgraph->order == GGML_CGRAPH_EVAL_ORDER_LEFT_TO_RIGHT) ? i : - (cgraph->order == GGML_CGRAPH_EVAL_ORDER_RIGHT_TO_LEFT) ? (GGML_MAX_SRC-1-i) : - /* unknown order, just fall back to using i */ i; - - struct ggml_tensor * src = node->src[k]; - if (src) { - const size_t src_hash_pos = ggml_visit_parents_graph(cgraph, src, compute); - - // Update the use count for this operand. - cgraph->use_counts[src_hash_pos]++; - } - } - - if (node->op == GGML_OP_NONE && !(node->flags & GGML_TENSOR_FLAG_PARAM)) { - // reached a leaf node, not part of the gradient graph (e.g. a constant) - GGML_ASSERT(cgraph->n_leafs < cgraph->size); - - if (strlen(node->name) == 0) { - ggml_format_name(node, "leaf_%d", cgraph->n_leafs); - } - - cgraph->leafs[cgraph->n_leafs] = node; - cgraph->n_leafs++; - } else { - GGML_ASSERT(cgraph->n_nodes < cgraph->size); - - if (strlen(node->name) == 0) { - ggml_format_name(node, "node_%d", cgraph->n_nodes); - } - - cgraph->nodes[cgraph->n_nodes] = node; - cgraph->n_nodes++; - } - - return node_hash_pos; -} - -static void ggml_build_forward_impl(struct ggml_cgraph * cgraph, struct ggml_tensor * tensor, bool expand, bool compute) { - if (!expand) { - // TODO: this branch isn't accessible anymore, maybe move this to ggml_build_forward_expand - ggml_graph_clear(cgraph); - } - - const int n_old = cgraph->n_nodes; - - ggml_visit_parents_graph(cgraph, tensor, compute); - - const int n_new = cgraph->n_nodes - n_old; - GGML_PRINT_DEBUG("%s: visited %d new nodes\n", __func__, n_new); - - if (n_new > 0) { - // the last added node should always be starting point - GGML_ASSERT(cgraph->nodes[cgraph->n_nodes - 1] == tensor); - } -} - -struct ggml_tensor * ggml_build_forward_select( - struct ggml_cgraph * cgraph, - struct ggml_tensor ** tensors, - int n_tensors, - int idx) { - GGML_ASSERT(idx >= 0 && idx < n_tensors); - - for (int i = 0; i < n_tensors; i++) { - ggml_build_forward_impl(cgraph, tensors[i], true, i == idx ? true : false); - } - - return tensors[idx]; -} - -void ggml_build_forward_expand(struct ggml_cgraph * cgraph, struct ggml_tensor * tensor) { - ggml_build_forward_impl(cgraph, tensor, true, true); -} - -void ggml_build_backward_expand( - struct ggml_context * ctx, - struct ggml_cgraph * cgraph, - struct ggml_tensor ** grad_accs) { - GGML_ASSERT(cgraph->n_nodes > 0); - GGML_ASSERT(cgraph->grads); - GGML_ASSERT(cgraph->grad_accs); - - const int n_nodes_f = cgraph->n_nodes; - - memset(cgraph->grads, 0, cgraph->visited_hash_set.size*sizeof(struct ggml_tensor *)); - memset(cgraph->grad_accs, 0, cgraph->visited_hash_set.size*sizeof(struct ggml_tensor *)); - bool * grads_needed = calloc(cgraph->visited_hash_set.size, sizeof(bool)); - - { - bool any_params = false; - bool any_loss = false; - for (int i = 0; i < n_nodes_f; ++i) { - struct ggml_tensor * node = cgraph->nodes[i]; - any_params = any_params || (node->flags & GGML_TENSOR_FLAG_PARAM); - any_loss = any_loss || (node->flags & GGML_TENSOR_FLAG_LOSS); - } - GGML_ASSERT(any_params && "no trainable parameters found, did you forget to call ggml_set_param?"); - GGML_ASSERT(any_loss && "no training loss found, did you forget to call ggml_set_loss?"); - } - - for (int i = 0; i < n_nodes_f; ++i) { - struct ggml_tensor * node = cgraph->nodes[i]; - - if (node->type == GGML_TYPE_I32) { - continue; - } - - bool node_needs_grad = (node->flags & GGML_TENSOR_FLAG_PARAM) || (node->flags & GGML_TENSOR_FLAG_LOSS); - bool ignore_src[GGML_MAX_SRC] = {false}; - switch (node->op) { - // gradients in node->src[0] for one reason or another have no effect on output gradients - case GGML_OP_IM2COL: // only used for its shape - case GGML_OP_IM2COL_FAST_1D: - case GGML_OP_IM2COL_BACK: // same as IM2COL - ignore_src[0] = true; - break; - case GGML_OP_UNARY: { - const enum ggml_unary_op uop = ggml_get_unary_op(node); - // SGN and STEP unary ops are piecewise constant - if (uop == GGML_UNARY_OP_SGN || uop == GGML_UNARY_OP_STEP) { - ignore_src[0] = true; - } - } break; - - // gradients in node->src[1] for one reason or another have no effect on output gradients - case GGML_OP_CPY: // gradients in CPY target are irrelevant - case GGML_OP_GET_ROWS: // row indices not differentiable - case GGML_OP_GET_ROWS_BACK: // same as for GET_ROWS - case GGML_OP_ROPE: // positions not differentiable - ignore_src[1] = true; - break; - - default: - break; - } - for (int j = 0; j < GGML_MAX_SRC; ++j) { - if (!node->src[j] || ignore_src[j] || !grads_needed[ggml_hash_find(&cgraph->visited_hash_set, node->src[j])]) { - continue; - } - GGML_ASSERT(node->src[j]->type == GGML_TYPE_F32 || node->src[j]->type == GGML_TYPE_F16); - node_needs_grad = true; - break; - } - if (!node_needs_grad) { - continue; - } - - // inplace operations are currently not supported - GGML_ASSERT(!node->view_src || node->op == GGML_OP_CPY || node->op == GGML_OP_VIEW || - node->op == GGML_OP_RESHAPE || node->op == GGML_OP_PERMUTE || node->op == GGML_OP_TRANSPOSE); - - const size_t ihash = ggml_hash_find(&cgraph->visited_hash_set, node); - GGML_ASSERT(ihash != GGML_HASHSET_FULL); - GGML_ASSERT(ggml_bitset_get(cgraph->visited_hash_set.used, ihash)); - if (grad_accs && grad_accs[i]) { - cgraph->grad_accs[ihash] = grad_accs[i]; - cgraph->grads[ihash] = cgraph->grad_accs[ihash]; - } else if (node->flags & GGML_TENSOR_FLAG_LOSS) { - // loss tensors always need a gradient accumulator - cgraph->grad_accs[ihash] = ggml_new_tensor(ctx, GGML_TYPE_F32, GGML_MAX_DIMS, node->ne); - cgraph->grads[ihash] = cgraph->grad_accs[ihash]; - } - grads_needed[ihash] = true; - } - - for (int i = n_nodes_f - 1; i >= 0; --i) { - // inplace operations to add gradients are not created by ggml_compute_backward except for gradient accumulation - // use allocator to automatically make inplace operations - ggml_compute_backward(ctx, cgraph, i, grads_needed); - } - - free(grads_needed); -} - -static void * incr_ptr_aligned(void ** p, size_t size, size_t align) { - void * ptr = *p; - ptr = (void *) GGML_PAD((uintptr_t) ptr, align); - *p = (void *) ((char *) ptr + size); - return ptr; -} - -static size_t ggml_graph_nbytes(size_t size, bool grads) { - size_t hash_size = ggml_hash_size(size * 2); - void * p = 0; - incr_ptr_aligned(&p, sizeof(struct ggml_cgraph), 1); - incr_ptr_aligned(&p, size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // nodes - incr_ptr_aligned(&p, size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // leafs - incr_ptr_aligned(&p, hash_size * sizeof(int32_t), sizeof(int32_t)); // use_counts - incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // hash keys - if (grads) { - incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // grads - incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // grad_accs - } - incr_ptr_aligned(&p, ggml_bitset_size(hash_size) * sizeof(ggml_bitset_t), sizeof(ggml_bitset_t)); - - size_t nbytes = (size_t) p; - return nbytes; -} - -size_t ggml_graph_overhead_custom(size_t size, bool grads) { - return GGML_OBJECT_SIZE + GGML_PAD(ggml_graph_nbytes(size, grads), GGML_MEM_ALIGN); -} - -size_t ggml_graph_overhead(void) { - return ggml_graph_overhead_custom(GGML_DEFAULT_GRAPH_SIZE, false); -} - -struct ggml_cgraph * ggml_new_graph_custom(struct ggml_context * ctx, size_t size, bool grads) { - const size_t obj_size = ggml_graph_nbytes(size, grads); - struct ggml_object * obj = ggml_new_object(ctx, GGML_OBJECT_TYPE_GRAPH, obj_size); - struct ggml_cgraph * cgraph = (struct ggml_cgraph *) ((char *) ctx->mem_buffer + obj->offs); - - // the size of the hash table is doubled since it needs to hold both nodes and leafs - size_t hash_size = ggml_hash_size(size * 2); - - void * p = cgraph + 1; - - struct ggml_tensor ** nodes_ptr = incr_ptr_aligned(&p, size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); - struct ggml_tensor ** leafs_ptr = incr_ptr_aligned(&p, size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); - int32_t * use_counts_ptr = incr_ptr_aligned(&p, hash_size * sizeof(int32_t), sizeof(int32_t)); - struct ggml_tensor ** hash_keys_ptr = incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); - struct ggml_tensor ** grads_ptr = grads ? incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)) : NULL; - struct ggml_tensor ** grad_accs_ptr = grads ? incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)) : NULL; - - ggml_bitset_t * hash_used = incr_ptr_aligned(&p, ggml_bitset_size(hash_size) * sizeof(ggml_bitset_t), sizeof(ggml_bitset_t)); - - // check that we allocated the correct amount of memory - assert(obj_size == (size_t)((char *)p - (char *)cgraph)); - - *cgraph = (struct ggml_cgraph) { - /*.size =*/ size, - /*.n_nodes =*/ 0, - /*.n_leafs =*/ 0, - /*.nodes =*/ nodes_ptr, - /*.grads =*/ grads_ptr, - /*.grad_accs =*/ grad_accs_ptr, - /*.leafs =*/ leafs_ptr, - /*.use_counts =*/ use_counts_ptr, - /*.hash_table =*/ { hash_size, hash_used, hash_keys_ptr }, - /*.order =*/ GGML_CGRAPH_EVAL_ORDER_LEFT_TO_RIGHT, - /*.uid =*/ 0, - }; - - ggml_hash_set_reset(&cgraph->visited_hash_set); - if (grads) { - memset(cgraph->grads, 0, hash_size*sizeof(struct ggml_tensor *)); - memset(cgraph->grad_accs, 0, hash_size*sizeof(struct ggml_tensor *)); - } - - return cgraph; -} - -struct ggml_cgraph * ggml_new_graph(struct ggml_context * ctx) { - return ggml_new_graph_custom(ctx, GGML_DEFAULT_GRAPH_SIZE, false); -} - -struct ggml_cgraph ggml_graph_view(struct ggml_cgraph * cgraph0, int i0, int i1) { - struct ggml_cgraph cgraph = { - /*.size =*/ 0, - /*.n_nodes =*/ i1 - i0, - /*.n_leafs =*/ 0, - /*.nodes =*/ cgraph0->nodes + i0, - /*.grads =*/ NULL, // gradients would need visited_hash_set - /*.grad_accs =*/ NULL, - /*.leafs =*/ NULL, - /*.use_counts =*/ cgraph0->use_counts, - /*.visited_hash_set =*/ cgraph0->visited_hash_set, - /*.order =*/ cgraph0->order, - /*.uid =*/ 0 - }; - - return cgraph; -} - -void ggml_graph_cpy(struct ggml_cgraph * src, struct ggml_cgraph * dst) { - GGML_ASSERT(dst->size >= src->n_leafs); - GGML_ASSERT(dst->size >= src->n_nodes); - GGML_ASSERT(dst->visited_hash_set.size >= src->visited_hash_set.size); - - dst->n_leafs = src->n_leafs; - dst->n_nodes = src->n_nodes; - dst->order = src->order; - - for (int i = 0; i < src->n_leafs; ++i) { - dst->leafs[i] = src->leafs[i]; - } - - for (int i = 0; i < src->n_nodes; ++i) { - dst->nodes[i] = src->nodes[i]; - } - - for (size_t i = 0; i < src->visited_hash_set.size; ++i) { - // copy all hashset keys (tensors) that are in use - if (ggml_bitset_get(src->visited_hash_set.used, i)) { - size_t new_hash_pos = ggml_hash_insert(&dst->visited_hash_set, src->visited_hash_set.keys[i]); - dst->use_counts[new_hash_pos] = src->use_counts[i]; - } - } - - if (dst->grads) { - memset(dst->grads, 0, dst->visited_hash_set.size*sizeof(struct ggml_tensor *)); - memset(dst->grad_accs, 0, dst->visited_hash_set.size*sizeof(struct ggml_tensor *)); - } - if (src->grads) { - GGML_ASSERT(dst->grads != NULL); - GGML_ASSERT(dst->grad_accs != NULL); - for (int i = 0; i < src->n_nodes; ++i) { - const size_t igrad_src = ggml_hash_find(&src->visited_hash_set, src->nodes[i]); - const size_t igrad_dst = ggml_hash_find(&dst->visited_hash_set, dst->nodes[i]); - - GGML_ASSERT(igrad_src != GGML_HASHSET_FULL); - GGML_ASSERT(ggml_bitset_get(src->visited_hash_set.used, igrad_src)); - GGML_ASSERT(igrad_dst != GGML_HASHSET_FULL); - GGML_ASSERT(ggml_bitset_get(dst->visited_hash_set.used, igrad_dst)); - - dst->grads[igrad_dst] = src->grads[igrad_src]; - dst->grad_accs[igrad_dst] = src->grad_accs[igrad_src]; - } - } -} - -struct ggml_cgraph * ggml_graph_dup(struct ggml_context * ctx, struct ggml_cgraph * cgraph, bool force_grads) { - struct ggml_cgraph * result = ggml_new_graph_custom(ctx, cgraph->size, cgraph->grads || force_grads); - ggml_graph_cpy(cgraph, result); - return result; -} - -struct ggml_tensor * ggml_set_zero(struct ggml_tensor * tensor) { - if (ggml_is_empty(tensor)) { - return tensor; - } - if (tensor->buffer) { - ggml_backend_tensor_memset(tensor, 0, 0, ggml_nbytes(tensor)); - } else { - GGML_ASSERT(tensor->data); - memset(tensor->data, 0, ggml_nbytes(tensor)); - } - return tensor; -} - -void ggml_graph_reset(struct ggml_cgraph * cgraph) { - if (!cgraph) { - return; - } - GGML_ASSERT(cgraph->grads != NULL); - - for (int i = 0; i < cgraph->n_nodes; i++) { - struct ggml_tensor * node = cgraph->nodes[i]; - struct ggml_tensor * grad_acc = ggml_graph_get_grad_acc(cgraph, node); - - if (node->op == GGML_OP_OPT_STEP_ADAMW) { - // clear momenta - ggml_set_zero(node->src[2]); - ggml_set_zero(node->src[3]); - } - - // initial gradients of loss should be 1, 0 otherwise - if (grad_acc) { - if (node->flags & GGML_TENSOR_FLAG_LOSS) { - GGML_ASSERT(grad_acc->type == GGML_TYPE_F32); - GGML_ASSERT(ggml_is_scalar(grad_acc)); - - const float onef = 1.0f; - if (grad_acc->buffer) { - ggml_backend_tensor_set(grad_acc, &onef, 0, sizeof(float)); - } else { - GGML_ASSERT(grad_acc->data); - *((float *) grad_acc->data) = onef; - } - } else { - ggml_set_zero(grad_acc); - } - } - } -} - -void ggml_graph_clear(struct ggml_cgraph * cgraph) { - cgraph->n_leafs = 0; - cgraph->n_nodes = 0; - ggml_hash_set_reset(&cgraph->visited_hash_set); -} - -int ggml_graph_size(struct ggml_cgraph * cgraph) { - return cgraph->size; -} - -struct ggml_tensor * ggml_graph_node(struct ggml_cgraph * cgraph, int i) { - if (i < 0) { - GGML_ASSERT(cgraph->n_nodes + i >= 0); - return cgraph->nodes[cgraph->n_nodes + i]; - } - - GGML_ASSERT(i < cgraph->n_nodes); - return cgraph->nodes[i]; -} - -struct ggml_tensor ** ggml_graph_nodes(struct ggml_cgraph * cgraph) { - return cgraph->nodes; -} - -int ggml_graph_n_nodes(struct ggml_cgraph * cgraph) { - return cgraph->n_nodes; +bool ggml_is_contiguous_0(const struct ggml_tensor * tensor) { + return ggml_is_contiguous_n(tensor, 0); } -void ggml_graph_set_n_nodes(struct ggml_cgraph * cgraph, int n_nodes) { - GGML_ASSERT(n_nodes >= 0); - GGML_ASSERT(n_nodes <= cgraph->size); - cgraph->n_nodes = n_nodes; +bool ggml_is_contiguous_1(const struct ggml_tensor * tensor) { + return ggml_is_contiguous_n(tensor, 1); } -void ggml_graph_add_node(struct ggml_cgraph * cgraph, struct ggml_tensor * tensor) { - GGML_ASSERT(cgraph->size > cgraph->n_nodes); - cgraph->nodes[cgraph->n_nodes] = tensor; - cgraph->n_nodes++; -} - -struct ggml_tensor * ggml_graph_get_tensor(const struct ggml_cgraph * cgraph, const char * name) { - for (int i = 0; i < cgraph->n_leafs; i++) { - struct ggml_tensor * leaf = cgraph->leafs[i]; - - if (strcmp(leaf->name, name) == 0) { - return leaf; - } - } - - for (int i = 0; i < cgraph->n_nodes; i++) { - struct ggml_tensor * node = cgraph->nodes[i]; - - if (strcmp(node->name, name) == 0) { - return node; - } - } - - return NULL; -} - -struct ggml_tensor * ggml_graph_get_grad(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node) { - const size_t igrad = ggml_hash_find(&cgraph->visited_hash_set, node); - return igrad != GGML_HASHSET_FULL && ggml_bitset_get(cgraph->visited_hash_set.used, igrad) && cgraph->grads ? cgraph->grads[igrad] : NULL; -} - -struct ggml_tensor * ggml_graph_get_grad_acc(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node) { - const size_t igrad = ggml_hash_find(&cgraph->visited_hash_set, node); - return igrad != GGML_HASHSET_FULL && ggml_bitset_get(cgraph->visited_hash_set.used, igrad) && cgraph->grad_accs ? cgraph->grad_accs[igrad] : NULL; -} - -void ggml_graph_print(const struct ggml_cgraph * cgraph) { - GGML_LOG_INFO("=== GRAPH ===\n"); - - GGML_LOG_INFO("n_nodes = %d\n", cgraph->n_nodes); - for (int i = 0; i < cgraph->n_nodes; i++) { - struct ggml_tensor * node = cgraph->nodes[i]; - - GGML_LOG_INFO(" - %3d: [ %5" PRId64 ", %5" PRId64 ", %5" PRId64 "] %16s %s\n", - i, - node->ne[0], node->ne[1], node->ne[2], - ggml_op_name(node->op), (node->flags & GGML_TENSOR_FLAG_PARAM) ? "x" : - ggml_graph_get_grad(cgraph, node) ? "g" : " "); - } - - GGML_LOG_INFO("n_leafs = %d\n", cgraph->n_leafs); - for (int i = 0; i < cgraph->n_leafs; i++) { - struct ggml_tensor * node = cgraph->leafs[i]; - - GGML_LOG_INFO(" - %3d: [ %5" PRId64 ", %5" PRId64 "] %8s %16s\n", - i, - node->ne[0], node->ne[1], - ggml_op_name(node->op), - ggml_get_name(node)); - } - - GGML_LOG_INFO("========================================\n"); -} - -static int ggml_node_list_find_tensor(const struct ggml_cgraph * cgraph, - const int * idxs, - int count, - const struct ggml_tensor * tensor) { - GGML_ASSERT(cgraph && idxs); - for (int i = 0; i < count; ++i) { - const int node_idx = idxs[i]; - - if (node_idx >= cgraph->n_nodes) { - return -1; - } - if (cgraph->nodes[node_idx] == tensor) { - return i; - } - } - return -1; -} - -bool ggml_can_fuse_subgraph_ext(const struct ggml_cgraph * cgraph, - const int * node_idxs, - int count, - const enum ggml_op * ops, - const int * outputs, - int num_outputs) { - GGML_ASSERT(outputs && num_outputs > 0); - - for (int i = 0; i < count; ++i) { - if (node_idxs[i] >= cgraph->n_nodes) { - return false; - } - - const struct ggml_tensor * node = cgraph->nodes[node_idxs[i]]; - - if (node->op != ops[i]) { - return false; - } - - if ((node->flags & GGML_TENSOR_FLAG_COMPUTE) == 0) { - return false; - } - - if (ggml_node_list_find_tensor(cgraph, outputs, num_outputs, node) != -1) { - continue; - } - - if (node->flags & GGML_TENSOR_FLAG_OUTPUT) { - return false; - } - - int subgraph_uses = 0; - for (int j = i + 1; j < count; ++j) { - const struct ggml_tensor * other_node = cgraph->nodes[node_idxs[j]]; - for (int src_idx = 0; src_idx < GGML_MAX_SRC; src_idx++) { - if (other_node->src[src_idx] == node) { - subgraph_uses++; - } - } - } - - if (subgraph_uses != ggml_node_get_use_count(cgraph, node_idxs[i])) { - return false; - } - - // if node is a view, check if the view_src and all it's parent view_srcs are within the subgraph - struct ggml_tensor * view_src = node->view_src; - while (view_src) { - if (ggml_node_list_find_tensor(cgraph, node_idxs, count, view_src) == -1) { - return false; - } - view_src = view_src->view_src; - } - } - - return true; -} - -// check if node is part of the graph -static bool ggml_graph_find(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node) { - if (cgraph == NULL) { - return true; - } - - for (int i = 0; i < cgraph->n_nodes; i++) { - if (cgraph->nodes[i] == node) { - return true; - } - } - - return false; -} - -static struct ggml_tensor * ggml_graph_get_parent(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node) { - for (int i = 0; i < cgraph->n_nodes; i++) { - struct ggml_tensor * parent = cgraph->nodes[i]; - struct ggml_tensor * grad = ggml_graph_get_grad(cgraph, parent); - - if (grad == node) { - return parent; - } - } - - return NULL; -} - -static void ggml_graph_dump_dot_node_edge(FILE * fp, const struct ggml_cgraph * gb, struct ggml_tensor * node, struct ggml_tensor * parent, const char * label) { - struct ggml_tensor * gparent = ggml_graph_get_parent(gb, node); - struct ggml_tensor * gparent0 = ggml_graph_get_parent(gb, parent); - fprintf(fp, " \"%p\" -> \"%p\" [ arrowhead = %s; style = %s; label = \"%s\"; ]\n", - gparent0 ? (void *) gparent0 : (void *) parent, - gparent ? (void *) gparent : (void *) node, - gparent ? "empty" : "vee", - gparent ? "dashed" : "solid", - label); -} - -static void ggml_graph_dump_dot_leaf_edge(FILE * fp, struct ggml_tensor * node, struct ggml_tensor * parent, const char * label) { - fprintf(fp, " \"%p\" -> \"%p\" [ label = \"%s\"; ]\n", - (void *) parent, - (void *) node, - label); -} - -void ggml_graph_dump_dot(const struct ggml_cgraph * gb, const struct ggml_cgraph * cgraph, const char * filename) { - char color[16]; - - FILE * fp = ggml_fopen(filename, "w"); - GGML_ASSERT(fp); - - fprintf(fp, "digraph G {\n"); - fprintf(fp, " newrank = true;\n"); - fprintf(fp, " rankdir = TB;\n"); - - for (int i = 0; i < gb->n_nodes; i++) { - struct ggml_tensor * node = gb->nodes[i]; - struct ggml_tensor * grad = ggml_graph_get_grad(gb, node); - - if (ggml_graph_get_parent(gb, node) != NULL) { - continue; - } - - if (node->flags & GGML_TENSOR_FLAG_PARAM) { - snprintf(color, sizeof(color), "yellow"); - } else if (grad) { - if (ggml_graph_find(cgraph, node)) { - snprintf(color, sizeof(color), "green"); - } else { - snprintf(color, sizeof(color), "lightblue"); - } - } else { - snprintf(color, sizeof(color), "white"); - } - - fprintf(fp, " \"%p\" [ " - "style = filled; fillcolor = %s; shape = record; " - "label=\"", - (void *) node, color); - - if (strlen(node->name) > 0) { - fprintf(fp, "%s (%s)|", node->name, ggml_type_name(node->type)); - } else { - fprintf(fp, "(%s)|", ggml_type_name(node->type)); - } - - if (ggml_is_matrix(node)) { - fprintf(fp, "%d [%" PRId64 ", %" PRId64 "] | %s", i, node->ne[0], node->ne[1], ggml_op_symbol(node->op)); - } else { - fprintf(fp, "%d [%" PRId64 ", %" PRId64 ", %" PRId64 "] | %s", i, node->ne[0], node->ne[1], node->ne[2], ggml_op_symbol(node->op)); - } - - if (grad) { - fprintf(fp, " | %s\"; ]\n", ggml_op_symbol(grad->op)); - } else { - fprintf(fp, "\"; ]\n"); - } - } - - for (int i = 0; i < gb->n_leafs; i++) { - struct ggml_tensor * node = gb->leafs[i]; - - snprintf(color, sizeof(color), "pink"); - - fprintf(fp, " \"%p\" [ " - "style = filled; fillcolor = %s; shape = record; " - "label=\"", - (void *) node, color); - - if (strlen(node->name) > 0) { - fprintf(fp, "%s (%s)|", node->name, ggml_type_name(node->type)); - } else { - fprintf(fp, "(%s)|", ggml_type_name(node->type)); - } - - fprintf(fp, "CONST %d [%" PRId64 ", %" PRId64 "]", i, node->ne[0], node->ne[1]); - if (ggml_nelements(node) < 5 && node->data != NULL) { - fprintf(fp, " | ("); - for (int j = 0; j < ggml_nelements(node); j++) { - // FIXME: use ggml-backend to obtain the tensor data - //if (node->type == GGML_TYPE_I8 || node->type == GGML_TYPE_I16 || node->type == GGML_TYPE_I32) { - // fprintf(fp, "%d", ggml_get_i32_1d(node, j)); - //} - //else if (node->type == GGML_TYPE_F32 || - // node->type == GGML_TYPE_F16 || - // node->type == GGML_TYPE_BF16) { - // fprintf(fp, "%.1e", (double)ggml_get_f32_1d(node, j)); - //} - //else - { - fprintf(fp, "#"); - } - if (j < ggml_nelements(node) - 1) { - fprintf(fp, ", "); - } - } - fprintf(fp, ")"); - } - fprintf(fp, "\"; ]\n"); - } - - for (int i = 0; i < gb->n_nodes; i++) { - struct ggml_tensor * node = gb->nodes[i]; - - for (int j = 0; j < GGML_MAX_SRC; j++) { - if (node->src[j]) { - char label[16]; - snprintf(label, sizeof(label), "src %d", j); - ggml_graph_dump_dot_node_edge(fp, gb, node, node->src[j], label); - } - } - } - - for (int i = 0; i < gb->n_leafs; i++) { - struct ggml_tensor * node = gb->leafs[i]; - - for (int j = 0; j < GGML_MAX_SRC; j++) { - if (node->src[j]) { - char label[16]; - snprintf(label, sizeof(label), "src %d", j); - ggml_graph_dump_dot_leaf_edge(fp, node, node->src[j], label); - } - } - } - - fprintf(fp, "}\n"); - - fclose(fp); - - GGML_LOG_INFO("%s: dot -Tpng %s -o %s.png && open %s.png\n", __func__, filename, filename, filename); -} - -//////////////////////////////////////////////////////////////////////////////// - -void ggml_set_input(struct ggml_tensor * tensor) { - tensor->flags |= GGML_TENSOR_FLAG_INPUT; -} - -void ggml_set_output(struct ggml_tensor * tensor) { - tensor->flags |= GGML_TENSOR_FLAG_OUTPUT; -} - -void ggml_set_param(struct ggml_tensor * tensor) { - GGML_ASSERT(tensor->op == GGML_OP_NONE); - tensor->flags |= GGML_TENSOR_FLAG_PARAM; -} - -void ggml_set_loss(struct ggml_tensor * tensor) { - GGML_ASSERT(ggml_is_scalar(tensor)); - GGML_ASSERT(tensor->type == GGML_TYPE_F32); - tensor->flags |= GGML_TENSOR_FLAG_LOSS; -} - -//////////////////////////////////////////////////////////////////////////////// - -void ggml_quantize_init(enum ggml_type type) { - ggml_critical_section_start(); - - switch (type) { - case GGML_TYPE_IQ2_XXS: - case GGML_TYPE_IQ2_XS: - case GGML_TYPE_IQ2_S: - case GGML_TYPE_IQ1_S: - case GGML_TYPE_IQ1_M: iq2xs_init_impl(type); break; - case GGML_TYPE_IQ3_XXS: iq3xs_init_impl(256); break; - case GGML_TYPE_IQ3_S: iq3xs_init_impl(512); break; - default: // nothing - break; - } - - ggml_critical_section_end(); -} - -void ggml_quantize_free(void) { - ggml_critical_section_start(); - - iq2xs_free_impl(GGML_TYPE_IQ2_XXS); - iq2xs_free_impl(GGML_TYPE_IQ2_XS); - iq2xs_free_impl(GGML_TYPE_IQ2_S); - iq2xs_free_impl(GGML_TYPE_IQ1_S); - iq2xs_free_impl(GGML_TYPE_IQ1_M); - iq3xs_free_impl(256); - iq3xs_free_impl(512); - - ggml_critical_section_end(); -} - -bool ggml_quantize_requires_imatrix(enum ggml_type type) { - return - type == GGML_TYPE_IQ2_XXS || - type == GGML_TYPE_IQ2_XS || - type == GGML_TYPE_IQ1_S;// || - //type == GGML_TYPE_IQ1_M; -} - -size_t ggml_quantize_chunk( - enum ggml_type type, - const float * src, - void * dst, - int64_t start, - int64_t nrows, - int64_t n_per_row, - const float * imatrix) { - const int64_t n = nrows * n_per_row; - - if (ggml_quantize_requires_imatrix(type)) { - GGML_ASSERT(imatrix != NULL); - } - - GGML_ASSERT(start % type_traits[type].blck_size == 0); - GGML_ASSERT(start % n_per_row == 0); - - ggml_quantize_init(type); // this is noop if already initialized - - const size_t start_row = start / n_per_row; - const size_t row_size = ggml_row_size(type, n_per_row); - - size_t result = 0; - - switch (type) { - case GGML_TYPE_Q1_0: result = quantize_q1_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q4_0: result = quantize_q4_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q4_1: result = quantize_q4_1 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q5_0: result = quantize_q5_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q5_1: result = quantize_q5_1 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q8_0: result = quantize_q8_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_MXFP4: result = quantize_mxfp4 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_NVFP4: result = quantize_nvfp4 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q2_K: result = quantize_q2_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q3_K: result = quantize_q3_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q4_K: result = quantize_q4_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q5_K: result = quantize_q5_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_Q6_K: result = quantize_q6_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_TQ1_0: result = quantize_tq1_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_TQ2_0: result = quantize_tq2_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ2_XXS: result = quantize_iq2_xxs(src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ2_XS: result = quantize_iq2_xs (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ3_XXS: result = quantize_iq3_xxs(src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ3_S: result = quantize_iq3_s (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ2_S: result = quantize_iq2_s (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ1_S: result = quantize_iq1_s (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ1_M: result = quantize_iq1_m (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ4_NL: result = quantize_iq4_nl (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_IQ4_XS: result = quantize_iq4_xs (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; - case GGML_TYPE_F16: - { - size_t elemsize = sizeof(ggml_fp16_t); - ggml_fp32_to_fp16_row(src + start, (ggml_fp16_t *)dst + start, n); - result = n * elemsize; - } break; - case GGML_TYPE_BF16: - { - size_t elemsize = sizeof(ggml_bf16_t); - ggml_fp32_to_bf16_row_ref(src + start, (ggml_bf16_t *)dst + start, n); - result = n * elemsize; - } break; - case GGML_TYPE_F32: - { - size_t elemsize = sizeof(float); - result = n * elemsize; - memcpy((uint8_t *)dst + start * elemsize, src + start, result); - } break; - default: - assert(false); - } - - GGML_ASSERT(result == nrows * row_size); - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// - -void ggml_log_get(ggml_log_callback * log_callback, void ** user_data) { - *log_callback = g_logger_state.log_callback; - *user_data = g_logger_state.log_callback_user_data; -} - -void ggml_log_set(ggml_log_callback log_callback, void * user_data) { - g_logger_state.log_callback = log_callback ? log_callback : ggml_log_callback_default; - g_logger_state.log_callback_user_data = user_data; -} - -void ggml_threadpool_params_init(struct ggml_threadpool_params * p, int n_threads) { - p->n_threads = n_threads; - p->prio = 0; // default priority (usually means normal or inherited) - p->poll = 50; // hybrid-polling enabled - p->strict_cpu = false; // no strict placement (all threads share same cpumask) - p->paused = false; // threads are ready to go - memset(p->cpumask, 0, GGML_MAX_N_THREADS); // all-zero means use the default affinity (usually inherited) -} - -struct ggml_threadpool_params ggml_threadpool_params_default(int n_threads) { - struct ggml_threadpool_params p; - ggml_threadpool_params_init(&p, n_threads); - return p; -} - -bool ggml_threadpool_params_match(const struct ggml_threadpool_params * p0, const struct ggml_threadpool_params * p1) { - if (p0->n_threads != p1->n_threads ) return false; - if (p0->prio != p1->prio ) return false; - if (p0->poll != p1->poll ) return false; - if (p0->strict_cpu != p1->strict_cpu ) return false; - return memcmp(p0->cpumask, p1->cpumask, GGML_MAX_N_THREADS) == 0; -} +bool ggml_is_contiguous_2(const struct ggml_tensor * tensor) { + return ggml_is_contiguous_n(tensor, 2); +} + +bool ggml_is_contiguously_allocated(const struct ggml_tensor * tensor) { + return ggml_nbytes(tensor) == ggml_nelements(tensor) * ggml_type_size(tensor->type)/ggml_blck_size(tensor->type); +} + +bool ggml_is_permuted(const struct ggml_tensor * tensor) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return tensor->nb[0] > tensor->nb[1] || tensor->nb[1] > tensor->nb[2] || tensor->nb[2] > tensor->nb[3]; +} + +bool ggml_is_contiguous_channels(const struct ggml_tensor * tensor) { + return + tensor->nb[0] > tensor->nb[2] && + tensor->nb[1] > tensor->nb[0] && + tensor->nb[2] == ggml_type_size(tensor->type); +} + +bool ggml_is_contiguous_rows(const struct ggml_tensor * tensor) { + return + tensor->ne[0] == ggml_blck_size(tensor->type) || + tensor->nb[0] == ggml_type_size(tensor->type); +} + +static inline bool ggml_is_padded_1d(const struct ggml_tensor * tensor) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return + tensor->nb[0] == ggml_type_size(tensor->type) && + tensor->nb[2] == tensor->nb[1]*tensor->ne[1] && + tensor->nb[3] == tensor->nb[2]*tensor->ne[2]; +} + +bool ggml_is_empty(const struct ggml_tensor * tensor) { + for (int i = 0; i < GGML_MAX_DIMS; ++i) { + if (tensor->ne[i] == 0) { + // empty if any dimension has no elements + return true; + } + } + return false; +} + +bool ggml_are_same_shape(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return + (t0->ne[0] == t1->ne[0]) && + (t0->ne[1] == t1->ne[1]) && + (t0->ne[2] == t1->ne[2]) && + (t0->ne[3] == t1->ne[3]); +} + +bool ggml_are_same_stride(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return + (t0->nb[0] == t1->nb[0]) && + (t0->nb[1] == t1->nb[1]) && + (t0->nb[2] == t1->nb[2]) && + (t0->nb[3] == t1->nb[3]); +} + +bool ggml_is_view(const struct ggml_tensor * t) { + return ggml_impl_is_view(t); +} + +// check if t1 can be represented as a repetition of t0 +bool ggml_can_repeat(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return ggml_is_empty(t0) ? ggml_is_empty(t1) : + (t1->ne[0]%t0->ne[0] == 0) && + (t1->ne[1]%t0->ne[1] == 0) && + (t1->ne[2]%t0->ne[2] == 0) && + (t1->ne[3]%t0->ne[3] == 0); +} + +static inline bool ggml_can_repeat_rows(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return (t0->ne[0] == t1->ne[0]) && ggml_can_repeat(t0, t1); +} + +// assert that pointer is aligned to GGML_MEM_ALIGN +#define GGML_ASSERT_ALIGNED(ptr) \ + GGML_ASSERT(((uintptr_t) (ptr))%GGML_MEM_ALIGN == 0) + +//////////////////////////////////////////////////////////////////////////////// + +struct ggml_context * ggml_init(struct ggml_init_params params) { + static bool is_first_call = true; + + ggml_critical_section_start(); + + if (is_first_call) { + // initialize time system (required on Windows) + ggml_time_init(); + + is_first_call = false; + } + + ggml_critical_section_end(); + + struct ggml_context * ctx = GGML_MALLOC(sizeof(struct ggml_context)); + + // allow to call ggml_init with 0 size + if (params.mem_size == 0) { + params.mem_size = GGML_MEM_ALIGN; + } + + const size_t mem_size = params.mem_buffer ? params.mem_size : GGML_PAD(params.mem_size, GGML_MEM_ALIGN); + + *ctx = (struct ggml_context) { + /*.mem_size =*/ mem_size, + /*.mem_buffer =*/ params.mem_buffer ? params.mem_buffer : ggml_aligned_malloc(mem_size), + /*.mem_buffer_owned =*/ params.mem_buffer ? false : true, + /*.no_alloc =*/ params.no_alloc, + /*.n_objects =*/ 0, + /*.objects_begin =*/ NULL, + /*.objects_end =*/ NULL, + }; + + GGML_ASSERT(ctx->mem_buffer != NULL); + + GGML_ASSERT_ALIGNED(ctx->mem_buffer); + + GGML_PRINT_DEBUG("%s: context initialized\n", __func__); + + return ctx; +} + +void ggml_reset(struct ggml_context * ctx) { + if (ctx == NULL) { + return; + } + + ctx->n_objects = 0; + ctx->objects_begin = NULL; + ctx->objects_end = NULL; +} + +void ggml_free(struct ggml_context * ctx) { + if (ctx == NULL) { + return; + } + + if (ctx->mem_buffer_owned) { + ggml_aligned_free(ctx->mem_buffer, ctx->mem_size); + } + + GGML_FREE(ctx); +} + +size_t ggml_used_mem(const struct ggml_context * ctx) { + return ctx->objects_end == NULL ? 0 : ctx->objects_end->offs + ctx->objects_end->size; +} + +bool ggml_get_no_alloc(struct ggml_context * ctx) { + return ctx->no_alloc; +} + +void ggml_set_no_alloc(struct ggml_context * ctx, bool no_alloc) { + ctx->no_alloc = no_alloc; +} + +void * ggml_get_mem_buffer(const struct ggml_context * ctx) { + return ctx->mem_buffer; +} + +size_t ggml_get_mem_size(const struct ggml_context * ctx) { + return ctx->mem_size; +} + +size_t ggml_get_max_tensor_size(const struct ggml_context * ctx) { + size_t max_size = 0; + + for (struct ggml_tensor * tensor = ggml_get_first_tensor(ctx); tensor != NULL; tensor = ggml_get_next_tensor(ctx, tensor)) { + size_t bytes = ggml_nbytes(tensor); + max_size = MAX(max_size, bytes); + } + + return max_size; +} + +//////////////////////////////////////////////////////////////////////////////// + +static struct ggml_object * ggml_new_object(struct ggml_context * ctx, enum ggml_object_type type, size_t size) { + // always insert objects at the end of the context's memory pool + struct ggml_object * obj_cur = ctx->objects_end; + + const size_t cur_offs = obj_cur == NULL ? 0 : obj_cur->offs; + const size_t cur_size = obj_cur == NULL ? 0 : obj_cur->size; + const size_t cur_end = cur_offs + cur_size; + + // align to GGML_MEM_ALIGN + GGML_ASSERT(size <= SIZE_MAX - (GGML_MEM_ALIGN - 1)); + size_t size_needed = GGML_PAD(size, GGML_MEM_ALIGN); + + char * const mem_buffer = ctx->mem_buffer; + struct ggml_object * const obj_new = (struct ggml_object *)(mem_buffer + cur_end); + + // integer overflow checks + if (cur_end > SIZE_MAX - size_needed) { + GGML_LOG_WARN("%s: overflow detected in cur_end (%zu) + size_needed (%zu)\n", __func__, cur_end, size_needed); + return NULL; + } + if (cur_end + size_needed > SIZE_MAX - GGML_OBJECT_SIZE) { + GGML_LOG_WARN("%s: overflow detected in cur_end (%zu) + size_needed (%zu) + GGML_OBJECT_SIZE (%zu)\n", __func__, + cur_end, size_needed, (size_t) GGML_OBJECT_SIZE); + return NULL; + } + + if (cur_end + size_needed + GGML_OBJECT_SIZE > ctx->mem_size) { + GGML_LOG_WARN("%s: not enough space in the context's memory pool (needed %zu, available %zu)\n", + __func__, cur_end + size_needed + GGML_OBJECT_SIZE, ctx->mem_size); +#ifndef NDEBUG + GGML_ABORT("not enough space in the context's memory pool"); +#endif + return NULL; + } + + *obj_new = (struct ggml_object) { + .offs = cur_end + GGML_OBJECT_SIZE, + .size = size_needed, + .next = NULL, + .type = type, + }; + + GGML_ASSERT_ALIGNED(mem_buffer + obj_new->offs); + + if (obj_cur != NULL) { + obj_cur->next = obj_new; + } else { + // this is the first object in this context + ctx->objects_begin = obj_new; + } + + ctx->objects_end = obj_new; + + //printf("%s: inserted new object at %zu, size = %zu\n", __func__, cur_end, obj_new->size); + + return obj_new; +} + +static struct ggml_tensor * ggml_new_tensor_impl( + struct ggml_context * ctx, + enum ggml_type type, + int n_dims, + const int64_t * ne, + struct ggml_tensor * view_src, + size_t view_offs) { + + GGML_ASSERT(type >= 0 && type < GGML_TYPE_COUNT); + GGML_ASSERT(n_dims >= 1 && n_dims <= GGML_MAX_DIMS); + + // find the base tensor and absolute offset + if (view_src != NULL && view_src->view_src != NULL) { + view_offs += view_src->view_offs; + view_src = view_src->view_src; + } + + size_t data_size = ggml_row_size(type, ne[0]); + for (int i = 1; i < n_dims; i++) { + data_size *= ne[i]; + } + data_size += ggml_type_extra_bytes(type); + + GGML_ASSERT(view_src == NULL || data_size == 0 || data_size + view_offs <= ggml_nbytes(view_src)); + + void * data = view_src != NULL ? view_src->data : NULL; + if (data != NULL) { + data = (char *) data + view_offs; + } + + size_t obj_alloc_size = 0; + + if (view_src == NULL && !ctx->no_alloc) { + // allocate tensor data in the context's memory pool + obj_alloc_size = data_size; + } + + GGML_ASSERT(GGML_TENSOR_SIZE <= SIZE_MAX - obj_alloc_size); + + struct ggml_object * const obj_new = ggml_new_object(ctx, GGML_OBJECT_TYPE_TENSOR, GGML_TENSOR_SIZE + obj_alloc_size); + GGML_ASSERT(obj_new); + + struct ggml_tensor * const result = (struct ggml_tensor *)((char *)ctx->mem_buffer + obj_new->offs); + + *result = (struct ggml_tensor) { + /*.type =*/ type, + /*.buffer =*/ NULL, + /*.ne =*/ { 1, 1, 1, 1 }, + /*.nb =*/ { 0, 0, 0, 0 }, + /*.op =*/ GGML_OP_NONE, + /*.op_params =*/ { 0 }, + /*.flags =*/ 0, + /*.src =*/ { NULL }, + /*.view_src =*/ view_src, + /*.view_offs =*/ view_offs, + /*.data =*/ obj_alloc_size > 0 ? (void *)(result + 1) : data, + /*.name =*/ { 0 }, + /*.extra =*/ NULL, + /*.padding =*/ { 0 }, + }; + + // TODO: this should not be needed as long as we don't rely on aligned SIMD loads + //GGML_ASSERT_ALIGNED(result->data); + + for (int i = 0; i < n_dims; i++) { + result->ne[i] = ne[i]; + } + + result->nb[0] = ggml_type_size(type); + result->nb[1] = result->nb[0]*(result->ne[0]/ggml_blck_size(type)); + for (int i = 2; i < GGML_MAX_DIMS; i++) { + result->nb[i] = result->nb[i - 1]*result->ne[i - 1]; + } + + ctx->n_objects++; + + return result; +} + +struct ggml_tensor * ggml_new_tensor( + struct ggml_context * ctx, + enum ggml_type type, + int n_dims, + const int64_t * ne) { + return ggml_new_tensor_impl(ctx, type, n_dims, ne, NULL, 0); +} + +struct ggml_tensor * ggml_new_tensor_1d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0) { + return ggml_new_tensor(ctx, type, 1, &ne0); +} + +struct ggml_tensor * ggml_new_tensor_2d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0, + int64_t ne1) { + const int64_t ne[2] = { ne0, ne1 }; + return ggml_new_tensor(ctx, type, 2, ne); +} + +struct ggml_tensor * ggml_new_tensor_3d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0, + int64_t ne1, + int64_t ne2) { + const int64_t ne[3] = { ne0, ne1, ne2 }; + return ggml_new_tensor(ctx, type, 3, ne); +} + +struct ggml_tensor * ggml_new_tensor_4d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3) { + const int64_t ne[4] = { ne0, ne1, ne2, ne3 }; + return ggml_new_tensor(ctx, type, 4, ne); +} + +void * ggml_new_buffer(struct ggml_context * ctx, size_t nbytes) { + struct ggml_object * obj = ggml_new_object(ctx, GGML_OBJECT_TYPE_WORK_BUFFER, nbytes); + + return (uint8_t *)ctx->mem_buffer + obj->offs; +} + +struct ggml_tensor * ggml_dup_tensor(struct ggml_context * ctx, const struct ggml_tensor * src) { + return ggml_new_tensor(ctx, src->type, GGML_MAX_DIMS, src->ne); +} + +void ggml_unravel_index(const struct ggml_tensor * tensor, int64_t i, int64_t * i0, int64_t * i1, int64_t * i2, int64_t * i3) { + const int64_t ne2 = tensor->ne[2]; + const int64_t ne1 = tensor->ne[1]; + const int64_t ne0 = tensor->ne[0]; + + const int64_t i3_ = (i/(ne2*ne1*ne0)); + const int64_t i2_ = (i - i3_*ne2*ne1*ne0)/(ne1*ne0); + const int64_t i1_ = (i - i3_*ne2*ne1*ne0 - i2_*ne1*ne0)/ne0; + const int64_t i0_ = (i - i3_*ne2*ne1*ne0 - i2_*ne1*ne0 - i1_*ne0); + + if (i0) { + * i0 = i0_; + } + if (i1) { + * i1 = i1_; + } + if (i2) { + * i2 = i2_; + } + if (i3) { + * i3 = i3_; + } +} + +void * ggml_get_data(const struct ggml_tensor * tensor) { + return tensor->data; +} + +float * ggml_get_data_f32(const struct ggml_tensor * tensor) { + assert(tensor->type == GGML_TYPE_F32); + return (float *)(tensor->data); +} + +enum ggml_unary_op ggml_get_unary_op(const struct ggml_tensor * tensor) { + GGML_ASSERT(tensor->op == GGML_OP_UNARY); + return (enum ggml_unary_op) ggml_get_op_params_i32(tensor, 0); +} + +enum ggml_glu_op ggml_get_glu_op(const struct ggml_tensor * tensor) { + GGML_ASSERT(tensor->op == GGML_OP_GLU); + return (enum ggml_glu_op) ggml_get_op_params_i32(tensor, 0); +} + +const char * ggml_get_name(const struct ggml_tensor * tensor) { + return tensor->name; +} + +struct ggml_tensor * ggml_set_name(struct ggml_tensor * tensor, const char * name) { + size_t i; + for (i = 0; i < sizeof(tensor->name) - 1 && name[i] != '\0'; i++) { + tensor->name[i] = name[i]; + } + tensor->name[i] = '\0'; + return tensor; +} + +struct ggml_tensor * ggml_format_name(struct ggml_tensor * tensor, const char * fmt, ...) { + va_list args; + va_start(args, fmt); + vsnprintf(tensor->name, sizeof(tensor->name), fmt, args); + va_end(args); + return tensor; +} + +struct ggml_tensor * ggml_view_tensor( + struct ggml_context * ctx, + struct ggml_tensor * src) { + struct ggml_tensor * result = ggml_new_tensor_impl(ctx, src->type, GGML_MAX_DIMS, src->ne, src, 0); + ggml_format_name(result, "%s (view)", src->name); + + for (int i = 0; i < GGML_MAX_DIMS; i++) { + result->nb[i] = src->nb[i]; + } + + return result; +} + +struct ggml_tensor * ggml_get_first_tensor(const struct ggml_context * ctx) { + struct ggml_object * obj = ctx->objects_begin; + + char * const mem_buffer = ctx->mem_buffer; + + while (obj != NULL) { + if (obj->type == GGML_OBJECT_TYPE_TENSOR) { + return (struct ggml_tensor *)(mem_buffer + obj->offs); + } + + obj = obj->next; + } + + return NULL; +} + +struct ggml_tensor * ggml_get_next_tensor(const struct ggml_context * ctx, struct ggml_tensor * tensor) { + struct ggml_object * obj = (struct ggml_object *) ((char *)tensor - GGML_OBJECT_SIZE); + obj = obj->next; + + char * const mem_buffer = ctx->mem_buffer; + + while (obj != NULL) { + if (obj->type == GGML_OBJECT_TYPE_TENSOR) { + return (struct ggml_tensor *)(mem_buffer + obj->offs); + } + + obj = obj->next; + } + + return NULL; +} + +struct ggml_tensor * ggml_get_tensor(struct ggml_context * ctx, const char * name) { + struct ggml_object * obj = ctx->objects_begin; + + char * const mem_buffer = ctx->mem_buffer; + + while (obj != NULL) { + if (obj->type == GGML_OBJECT_TYPE_TENSOR) { + struct ggml_tensor * cur = (struct ggml_tensor *)(mem_buffer + obj->offs); + if (strcmp(cur->name, name) == 0) { + return cur; + } + } + + obj = obj->next; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// + +// ggml_dup + +static struct ggml_tensor * ggml_dup_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_DUP; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_dup( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_dup_impl(ctx, a, false); +} + +struct ggml_tensor * ggml_dup_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_dup_impl(ctx, a, true); +} + +// ggml_add + +static struct ggml_tensor * ggml_add_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + bool inplace) { + GGML_ASSERT(ggml_can_repeat(b, a)); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_ADD; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_add( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_add_impl(ctx, a, b, false); +} + +struct ggml_tensor * ggml_add_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_add_impl(ctx, a, b, true); +} + +// ggml_add_cast + +static struct ggml_tensor * ggml_add_cast_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + enum ggml_type type) { + // TODO: support less-strict constraint + // GGML_ASSERT(ggml_can_repeat(b, a)); + GGML_ASSERT(ggml_can_repeat_rows(b, a)); + + // currently only supported for quantized input and f16 + GGML_ASSERT(ggml_is_quantized(a->type) || + a->type == GGML_TYPE_F16 || + a->type == GGML_TYPE_BF16); + + struct ggml_tensor * result = ggml_new_tensor(ctx, type, GGML_MAX_DIMS, a->ne); + + result->op = GGML_OP_ADD; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_add_cast( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + enum ggml_type type) { + return ggml_add_cast_impl(ctx, a, b, type); +} + +struct ggml_tensor * ggml_add_id( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * ids) { + + GGML_ASSERT(a->ne[0] == b->ne[0]); + GGML_ASSERT(a->ne[1] == ids->ne[0]); + GGML_ASSERT(a->ne[2] == ids->ne[1]); + GGML_ASSERT(ids->type == GGML_TYPE_I32); + + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_ADD_ID; + result->src[0] = a; + result->src[1] = b; + result->src[2] = ids; + + return result; +} + +// ggml_add1 + +static struct ggml_tensor * ggml_add1_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + bool inplace) { + GGML_ASSERT(ggml_is_scalar(b)); + GGML_ASSERT(ggml_is_padded_1d(a)); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_ADD1; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_add1( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_add1_impl(ctx, a, b, false); +} + +struct ggml_tensor * ggml_add1_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_add1_impl(ctx, a, b, true); +} + +// ggml_acc + +static struct ggml_tensor * ggml_acc_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset, + bool inplace) { + GGML_ASSERT(ggml_nelements(b) <= ggml_nelements(a)); + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(a->type == GGML_TYPE_F32); + GGML_ASSERT(b->type == GGML_TYPE_F32); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + int32_t params[] = { nb1, nb2, nb3, offset, inplace ? 1 : 0 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_ACC; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_acc( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset) { + return ggml_acc_impl(ctx, a, b, nb1, nb2, nb3, offset, false); +} + +struct ggml_tensor * ggml_acc_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset) { + return ggml_acc_impl(ctx, a, b, nb1, nb2, nb3, offset, true); +} + +// ggml_sub + +static struct ggml_tensor * ggml_sub_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + bool inplace) { + GGML_ASSERT(ggml_can_repeat(b, a)); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_SUB; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_sub( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_sub_impl(ctx, a, b, false); +} + +struct ggml_tensor * ggml_sub_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_sub_impl(ctx, a, b, true); +} + +// ggml_mul + +static struct ggml_tensor * ggml_mul_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + bool inplace) { + GGML_ASSERT(ggml_can_repeat(b, a)); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_MUL; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_mul( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_mul_impl(ctx, a, b, false); +} + +struct ggml_tensor * ggml_mul_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_mul_impl(ctx, a, b, true); +} + +// ggml_div + +static struct ggml_tensor * ggml_div_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + bool inplace) { + GGML_ASSERT(ggml_can_repeat(b, a)); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_DIV; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_div( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_div_impl(ctx, a, b, false); +} + +struct ggml_tensor * ggml_div_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_div_impl(ctx, a, b, true); +} + +// ggml_sqr + +static struct ggml_tensor * ggml_sqr_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_SQR; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_sqr( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_sqr_impl(ctx, a, false); +} + +struct ggml_tensor * ggml_sqr_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_sqr_impl(ctx, a, true); +} + +// ggml_sqrt + +static struct ggml_tensor * ggml_sqrt_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_SQRT; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_sqrt( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_sqrt_impl(ctx, a, false); +} + +struct ggml_tensor * ggml_sqrt_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_sqrt_impl(ctx, a, true); +} + +// ggml_log + +static struct ggml_tensor * ggml_log_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_LOG; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_log( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_log_impl(ctx, a, false); +} + +struct ggml_tensor * ggml_log_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_log_impl(ctx, a, true); +} + +struct ggml_tensor * ggml_expm1( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_EXPM1); +} + +struct ggml_tensor * ggml_expm1_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_EXPM1); +} + +struct ggml_tensor * ggml_softplus( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_SOFTPLUS); +} + +struct ggml_tensor * ggml_softplus_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_SOFTPLUS); +} + +// ggml_sin + +static struct ggml_tensor * ggml_sin_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_SIN; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_sin( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_sin_impl(ctx, a, false); +} + +struct ggml_tensor * ggml_sin_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_sin_impl(ctx, a, true); +} + +// ggml_cos + +static struct ggml_tensor * ggml_cos_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_COS; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_cos( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_cos_impl(ctx, a, false); +} + +struct ggml_tensor * ggml_cos_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_cos_impl(ctx, a, true); +} + +// ggml_sum + +struct ggml_tensor * ggml_sum( + struct ggml_context * ctx, + struct ggml_tensor * a) { + struct ggml_tensor * result = ggml_new_tensor_1d(ctx, a->type, 1); + + result->op = GGML_OP_SUM; + result->src[0] = a; + + return result; +} + +// ggml_sum_rows + +struct ggml_tensor * ggml_sum_rows( + struct ggml_context * ctx, + struct ggml_tensor * a) { + int64_t ne[GGML_MAX_DIMS] = { 1 }; + for (int i = 1; i < GGML_MAX_DIMS; ++i) { + ne[i] = a->ne[i]; + } + + struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, GGML_MAX_DIMS, ne); + + result->op = GGML_OP_SUM_ROWS; + result->src[0] = a; + + return result; +} + +// ggml_cumsum + +struct ggml_tensor * ggml_cumsum( + struct ggml_context * ctx, + struct ggml_tensor * a) { + GGML_ASSERT(a->type == GGML_TYPE_F32); + + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_CUMSUM; + result->src[0] = a; + + return result; +} + +// ggml_mean + +struct ggml_tensor * ggml_mean( + struct ggml_context * ctx, + struct ggml_tensor * a) { + int64_t ne[4] = { 1, a->ne[1], a->ne[2], a->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + result->op = GGML_OP_MEAN; + result->src[0] = a; + + return result; +} + +// ggml_argmax + +struct ggml_tensor * ggml_argmax( + struct ggml_context * ctx, + struct ggml_tensor * a) { + GGML_ASSERT(ggml_is_matrix(a)); + GGML_ASSERT(a->ne[0] <= INT32_MAX); + + struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, a->ne[1]); + + result->op = GGML_OP_ARGMAX; + result->src[0] = a; + + return result; +} + +// ggml_count_equal + +struct ggml_tensor * ggml_count_equal( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_are_same_shape(a, b)); + + struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_I64, 1); + + result->op = GGML_OP_COUNT_EQUAL; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_repeat + +struct ggml_tensor * ggml_repeat( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_can_repeat(a, b)); + + struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, GGML_MAX_DIMS, b->ne); + + result->op = GGML_OP_REPEAT; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_repeat_4d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, int64_t ne1, int64_t ne2, int64_t ne3) { + const bool can_repeat = ggml_is_empty(a) || ( + (ne0 % a->ne[0] == 0) && + (ne1 % a->ne[1] == 0) && + (ne2 % a->ne[2] == 0) && + (ne3 % a->ne[3] == 0) + ); + GGML_ASSERT(can_repeat); + + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, ne0, ne1, ne2, ne3); + + result->op = GGML_OP_REPEAT; + result->src[0] = a; + + return result; +} + +// ggml_repeat_back + +struct ggml_tensor * ggml_repeat_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_can_repeat(b, a)); + + struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, GGML_MAX_DIMS, b->ne); + + result->op = GGML_OP_REPEAT_BACK; + result->src[0] = a; + + return result; +} + +// ggml_concat + +struct ggml_tensor * ggml_concat( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int dim) { + GGML_ASSERT(dim >= 0 && dim < GGML_MAX_DIMS); + GGML_ASSERT(a->type == b->type); + + int64_t ne[GGML_MAX_DIMS]; + for (int d = 0; d < GGML_MAX_DIMS; ++d) { + if (d == dim) { + ne[d] = a->ne[d] + b->ne[d]; + continue; + } + GGML_ASSERT(a->ne[d] == b->ne[d]); + ne[d] = a->ne[d]; + } + + struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, GGML_MAX_DIMS, ne); + + ggml_set_op_params_i32(result, 0, dim); + + result->op = GGML_OP_CONCAT; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_abs + +struct ggml_tensor * ggml_abs( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_ABS); +} + +struct ggml_tensor * ggml_abs_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_ABS); +} + +// ggml_sgn + +struct ggml_tensor * ggml_sgn( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_SGN); +} + +struct ggml_tensor * ggml_sgn_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_SGN); +} + +// ggml_neg + +struct ggml_tensor * ggml_neg( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_NEG); +} + +struct ggml_tensor * ggml_neg_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_NEG); +} + +// ggml_step + +struct ggml_tensor * ggml_step( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_STEP); +} + +struct ggml_tensor * ggml_step_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_STEP); +} + +// ggml_tanh + +struct ggml_tensor * ggml_tanh( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_TANH); +} + +struct ggml_tensor * ggml_tanh_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_TANH); +} + +// ggml_elu + +struct ggml_tensor * ggml_elu( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_ELU); +} + +struct ggml_tensor * ggml_elu_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_ELU); +} + +// ggml_relu + +struct ggml_tensor * ggml_relu( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_RELU); +} + +struct ggml_tensor * ggml_relu_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_RELU); +} + +// ggml_leaky_relu + +struct ggml_tensor * ggml_leaky_relu( + struct ggml_context * ctx, + struct ggml_tensor * a, + float negative_slope, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + ggml_set_op_params(result, &negative_slope, sizeof(negative_slope)); + + result->op = GGML_OP_LEAKY_RELU; + result->src[0] = a; + + return result; +} + +// ggml_sigmoid + +struct ggml_tensor * ggml_sigmoid( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_SIGMOID); +} + +struct ggml_tensor * ggml_sigmoid_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_SIGMOID); +} + +// ggml_gelu + +struct ggml_tensor * ggml_gelu( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_GELU); +} + +struct ggml_tensor * ggml_gelu_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_GELU); +} + +// ggml_gelu_erf + +struct ggml_tensor * ggml_gelu_erf( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_GELU_ERF); +} + +struct ggml_tensor * ggml_gelu_erf_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_GELU_ERF); +} + +// ggml_gelu_quick + +struct ggml_tensor * ggml_gelu_quick( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_GELU_QUICK); +} + +struct ggml_tensor * ggml_gelu_quick_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_GELU_QUICK); +} + +// ggml_silu + +struct ggml_tensor * ggml_silu( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_SILU); +} + +struct ggml_tensor * ggml_silu_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_SILU); +} + +// ggml_xielu + +struct ggml_tensor * ggml_xielu( + struct ggml_context * ctx, + struct ggml_tensor * a, + float alpha_n, + float alpha_p, + float beta, + float eps) { + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + ggml_set_op_params_i32(result, 0, (int32_t) GGML_UNARY_OP_XIELU); + ggml_set_op_params_f32(result, 1, beta + ggml_compute_softplus_f32(alpha_n)); + ggml_set_op_params_f32(result, 2, ggml_compute_softplus_f32(alpha_p)); + ggml_set_op_params_f32(result, 3, beta); + ggml_set_op_params_f32(result, 4, eps); + + result->op = GGML_OP_UNARY; + result->src[0] = a; + + return result; +} + +// ggml_silu_back + +struct ggml_tensor * ggml_silu_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_SILU_BACK; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml hardswish + +struct ggml_tensor * ggml_hardswish( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_HARDSWISH); +} + +// ggml hardsigmoid + +struct ggml_tensor * ggml_hardsigmoid( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_HARDSIGMOID); +} + +// ggml exp + +struct ggml_tensor * ggml_exp( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_EXP); +} + +struct ggml_tensor * ggml_exp_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_EXP); +} + +// ggml_glu + +static struct ggml_tensor * ggml_glu_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + enum ggml_glu_op op, + bool swapped) { + GGML_ASSERT(ggml_is_contiguous_1(a)); + + if (b) { + GGML_ASSERT(ggml_is_contiguous_1(b)); + GGML_ASSERT(ggml_are_same_shape(a, b)); + GGML_ASSERT(a->type == b->type); + } + + int64_t ne[GGML_MAX_DIMS] = { a->ne[0] / 2 }; for (int i = 1; i < GGML_MAX_DIMS; i++) ne[i] = a->ne[i]; + struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, GGML_MAX_DIMS, b ? a->ne : ne, NULL, 0); + + ggml_set_op_params_i32(result, 0, (int32_t) op); + ggml_set_op_params_i32(result, 1, (int32_t) swapped); + + result->op = GGML_OP_GLU; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_floor + +struct ggml_tensor * ggml_floor( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_FLOOR); +} + +struct ggml_tensor * ggml_floor_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_FLOOR); +} + +// ggml_ceil + +struct ggml_tensor * ggml_ceil( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_CEIL); +} + +struct ggml_tensor * ggml_ceil_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_CEIL); +} + +//ggml_round + +struct ggml_tensor * ggml_round( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_ROUND); +} + +struct ggml_tensor * ggml_round_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_ROUND); +} + +//ggml_trunc + +struct ggml_tensor * ggml_trunc( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary(ctx, a, GGML_UNARY_OP_TRUNC); +} + +struct ggml_tensor * ggml_trunc_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_unary_inplace(ctx, a, GGML_UNARY_OP_TRUNC); +} + +//ggml_round_bf16 + +struct ggml_tensor * ggml_round_bf16( + struct ggml_context * ctx, + struct ggml_tensor * a) { + GGML_ASSERT(a->type == GGML_TYPE_F32 || a->type == GGML_TYPE_F16 || a->type == GGML_TYPE_BF16); + GGML_ASSERT(ggml_is_contiguous_rows(a)); + + // Unlike ggml_unary, the result is always f32: bf16/f16 inputs are widened + // while rounding, matching an f32 -> bf16 -> f32 cast round trip. + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, GGML_MAX_DIMS, a->ne); + + ggml_set_op_params_i32(result, 0, (int32_t) GGML_UNARY_OP_ROUND_BF16); + + result->op = GGML_OP_UNARY; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_glu( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_glu_op op, + bool swapped) { + return ggml_glu_impl(ctx, a, NULL, op, swapped); +} + +struct ggml_tensor * ggml_glu_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + enum ggml_glu_op op) { + return ggml_glu_impl(ctx, a, b, op, false); +} + +// ggml_reglu + +struct ggml_tensor * ggml_reglu( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_REGLU, false); +} + +struct ggml_tensor * ggml_reglu_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_REGLU, true); +} + +struct ggml_tensor * ggml_reglu_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_REGLU, false); +} + +// ggml_geglu + +struct ggml_tensor * ggml_geglu( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU, false); +} + +struct ggml_tensor * ggml_geglu_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU, true); +} + +struct ggml_tensor * ggml_geglu_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_GEGLU, false); +} + +// ggml_swiglu + +struct ggml_tensor * ggml_swiglu( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_SWIGLU, false); +} + +struct ggml_tensor * ggml_swiglu_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_SWIGLU, true); +} + +struct ggml_tensor * ggml_swiglu_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_SWIGLU, false); +} + +// ggml_geglu_erf + +struct ggml_tensor * ggml_geglu_erf( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU_ERF, false); +} + +struct ggml_tensor * ggml_geglu_erf_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU_ERF, true); +} + +struct ggml_tensor * ggml_geglu_erf_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_GEGLU_ERF, false); +} + +// ggml_geglu_quick + +struct ggml_tensor * ggml_geglu_quick( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU_QUICK, false); +} + +struct ggml_tensor * ggml_geglu_quick_swapped( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_glu_impl(ctx, a, NULL, GGML_GLU_OP_GEGLU_QUICK, true); +} + +struct ggml_tensor * ggml_geglu_quick_split( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_glu_impl(ctx, a, b, GGML_GLU_OP_GEGLU_QUICK, false); +} + +struct ggml_tensor * ggml_swiglu_oai( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float alpha, + float limit) { + struct ggml_tensor * result = ggml_glu_impl(ctx, a, b, GGML_GLU_OP_SWIGLU_OAI, false); + ggml_set_op_params_f32(result, 2, alpha); + ggml_set_op_params_f32(result, 3, limit); + + return result; +} + +// ggml_norm + +static struct ggml_tensor * ggml_norm_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + ggml_set_op_params(result, &eps, sizeof(eps)); + + result->op = GGML_OP_NORM; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_norm( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps) { + return ggml_norm_impl(ctx, a, eps, false); +} + +struct ggml_tensor * ggml_norm_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps) { + return ggml_norm_impl(ctx, a, eps, true); +} + +// ggml_rms_norm + +static struct ggml_tensor * ggml_rms_norm_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + ggml_set_op_params(result, &eps, sizeof(eps)); + + result->op = GGML_OP_RMS_NORM; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_rms_norm( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps) { + return ggml_rms_norm_impl(ctx, a, eps, false); +} + +struct ggml_tensor * ggml_rms_norm_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps) { + return ggml_rms_norm_impl(ctx, a, eps, true); +} + +// ggml_rms_norm_back + +struct ggml_tensor * ggml_rms_norm_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float eps) { + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + ggml_set_op_params(result, &eps, sizeof(eps)); + + result->op = GGML_OP_RMS_NORM_BACK; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_group_norm + +static struct ggml_tensor * ggml_group_norm_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_groups, + float eps, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + ggml_set_op_params_i32(result, 0, n_groups); + ggml_set_op_params_f32(result, 1, eps); + + result->op = GGML_OP_GROUP_NORM; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_group_norm( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_groups, + float eps) { + return ggml_group_norm_impl(ctx, a, n_groups, eps, false); +} + +struct ggml_tensor * ggml_group_norm_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_groups, + float eps) { + return ggml_group_norm_impl(ctx, a, n_groups, eps, true); +} + +// ggml_l2_norm + +static struct ggml_tensor * ggml_l2_norm_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + ggml_set_op_params_f32(result, 0, eps); + + result->op = GGML_OP_L2_NORM; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_l2_norm( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps) { + return ggml_l2_norm_impl(ctx, a, eps, false); +} + +struct ggml_tensor * ggml_l2_norm_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float eps) { + return ggml_l2_norm_impl(ctx, a, eps, true); +} + +// ggml_mul_mat + +static inline bool ggml_can_mul_mat(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return (t0->ne[0] == t1->ne[0]) && + (t1->ne[2]%t0->ne[2] == 0) && // verify t0 is broadcastable + (t1->ne[3]%t0->ne[3] == 0); +} + +struct ggml_tensor * ggml_mul_mat( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_can_mul_mat(a, b)); + GGML_ASSERT(!ggml_is_transposed(a)); + + const int64_t ne[4] = { a->ne[1], b->ne[1], b->ne[2], b->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + result->op = GGML_OP_MUL_MAT; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_mul_mat_acc( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * acc) { + GGML_ASSERT(ggml_can_mul_mat(a, b)); + GGML_ASSERT(!ggml_is_transposed(a)); + GGML_ASSERT(acc->type == GGML_TYPE_F32); + // acc must have the shape of the a * b product + GGML_ASSERT(acc->ne[0] == a->ne[1] && acc->ne[1] == b->ne[1] && + acc->ne[2] == b->ne[2] && acc->ne[3] == b->ne[3]); + + // result is a view of acc: the accumulation is written in-place into acc's memory + struct ggml_tensor * result = ggml_view_tensor(ctx, acc); + + result->op = GGML_OP_MUL_MAT_ACC; + result->src[0] = a; + result->src[1] = b; + result->src[2] = acc; + + return result; +} + +// ggml_snake_1d + +struct ggml_tensor * ggml_snake_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * alpha) { + GGML_ASSERT(a->type == GGML_TYPE_F32); + GGML_ASSERT(alpha->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(ggml_is_contiguous(alpha)); + GGML_ASSERT(alpha->ne[0] == a->ne[0]); + GGML_ASSERT(alpha->ne[1] == 1 && alpha->ne[2] == 1 && alpha->ne[3] == 1); + + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_SNAKE_1D; + result->src[0] = a; + result->src[1] = alpha; + + return result; +} + +struct ggml_tensor * ggml_mul_mat_pack4( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_can_mul_mat(a, b)); + GGML_ASSERT(!ggml_is_transposed(a)); + GGML_ASSERT(a->ne[1] % 4 == 0); + + const int64_t ne[4] = { a->ne[1], b->ne[1], b->ne[2], b->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + result->op = GGML_OP_MUL_MAT_PACK4; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +void ggml_mul_mat_set_prec( + struct ggml_tensor * a, + enum ggml_prec prec) { + GGML_ASSERT(a->op == GGML_OP_MUL_MAT || a->op == GGML_OP_MUL_MAT_PACK4); + + const int32_t prec_i32 = (int32_t) prec; + + ggml_set_op_params_i32(a, 0, prec_i32); +} + +void ggml_mul_mat_set_hint( + struct ggml_tensor * a, + enum ggml_op_hint hint) { + GGML_ASSERT(a->op == GGML_OP_MUL_MAT || a->op == GGML_OP_MUL_MAT_PACK4); + + const int32_t hint_i32 = (int32_t) hint; + + ggml_set_op_params_i32(a, 1, hint_i32); +} + +// ggml_mul_mat_id + +/* + c = ggml_mul_mat_id(ctx, as, b, ids); + + as -> [cols, rows, n_expert] + b -> [cols, n_expert_used, n_tokens] + ids -> [n_expert_used, n_tokens] (i32) + c -> [rows, n_expert_used, n_tokens] + + in b, n_expert_used can be broadcasted to match the n_expert_used of ids + + c ~= as[:,:,i] @ b[:,i%r,t], i = ids[e,t] for all e,t in ids +*/ +struct ggml_tensor * ggml_mul_mat_id( + struct ggml_context * ctx, + struct ggml_tensor * as, + struct ggml_tensor * b, + struct ggml_tensor * ids) { + GGML_ASSERT(!ggml_is_transposed(as)); + GGML_ASSERT(ids->type == GGML_TYPE_I32); + + GGML_ASSERT(as->ne[3] == 1); // as is 3d (one matrix per expert) + GGML_ASSERT(b->ne[3] == 1); // b is 3d + GGML_ASSERT(ids->ne[2] == 1 && ids->ne[3] == 1); // ids is 2d + GGML_ASSERT(ids->ne[1] == b->ne[2]); // must have an expert list per b row + GGML_ASSERT(as->ne[0] == b->ne[0]); // can_mul_mat + GGML_ASSERT(ids->ne[0] % b->ne[1] == 0); // can broadcast + + const int64_t ne[4] = { as->ne[1], ids->ne[0], b->ne[2], 1 }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + result->op = GGML_OP_MUL_MAT_ID; + result->src[0] = as; + result->src[1] = b; + result->src[2] = ids; + + return result; +} + +// ggml_out_prod + +static inline bool ggml_can_out_prod(const struct ggml_tensor * t0, const struct ggml_tensor * t1) { + static_assert(GGML_MAX_DIMS == 4, "GGML_MAX_DIMS is not 4 - update this function"); + + return (t0->ne[1] == t1->ne[1]) && + (t1->ne[2]%t0->ne[2] == 0) && // verify t0 is broadcastable + (t1->ne[3]%t0->ne[3] == 0); +} + +struct ggml_tensor * ggml_out_prod( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_can_out_prod(a, b)); + GGML_ASSERT(!ggml_is_transposed(a)); + + // a is broadcastable to b for ne[2] and ne[3] -> use b->ne[2] and b->ne[3] + const int64_t ne[4] = { a->ne[0], b->ne[0], b->ne[2], b->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + result->op = GGML_OP_OUT_PROD; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_scale + +static struct ggml_tensor * ggml_scale_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s, + float b, + bool inplace) { + GGML_ASSERT(ggml_is_padded_1d(a)); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + float params[2] = { s, b }; + ggml_set_op_params(result, ¶ms, sizeof(params)); + + result->op = GGML_OP_SCALE; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_scale( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s) { + return ggml_scale_impl(ctx, a, s, 0.0, false); +} + +struct ggml_tensor * ggml_scale_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s) { + return ggml_scale_impl(ctx, a, s, 0.0, true); +} + +struct ggml_tensor * ggml_scale_bias( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s, + float b) { + return ggml_scale_impl(ctx, a, s, b, false); +} + +struct ggml_tensor * ggml_scale_bias_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float s, + float b) { + return ggml_scale_impl(ctx, a, s, b, true); +} + +// ggml_set + +static struct ggml_tensor * ggml_set_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset, + bool inplace) { + GGML_ASSERT(ggml_nelements(a) >= ggml_nelements(b)); + + // make a view of the destination + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + GGML_ASSERT(offset < (size_t)(1 << 30)); + int32_t params[] = { nb1, nb2, nb3, offset, inplace ? 1 : 0 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_SET; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_set( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset) { + return ggml_set_impl(ctx, a, b, nb1, nb2, nb3, offset, false); +} + +struct ggml_tensor * ggml_set_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset) { + return ggml_set_impl(ctx, a, b, nb1, nb2, nb3, offset, true); +} + +struct ggml_tensor * ggml_set_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t offset) { + return ggml_set_impl(ctx, a, b, a->nb[1], a->nb[2], a->nb[3], offset, false); +} + +struct ggml_tensor * ggml_set_1d_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t offset) { + return ggml_set_impl(ctx, a, b, a->nb[1], a->nb[2], a->nb[3], offset, true); +} + +struct ggml_tensor * ggml_set_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t offset) { + return ggml_set_impl(ctx, a, b, nb1, a->nb[2], a->nb[3], offset, false); +} + +struct ggml_tensor * ggml_set_2d_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + size_t nb1, + size_t offset) { + return ggml_set_impl(ctx, a, b, nb1, a->nb[2], a->nb[3], offset, true); +} + +// ggml_cpy + +static struct ggml_tensor * ggml_cpy_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_nelements(a) == ggml_nelements(b)); + + // make a view of the destination + struct ggml_tensor * result = ggml_view_tensor(ctx, b); + if (strlen(b->name) > 0) { + ggml_format_name(result, "%s (copy of %s)", b->name, a->name); + } else { + ggml_format_name(result, "%s (copy)", a->name); + } + + result->op = GGML_OP_CPY; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_cpy( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_cpy_impl(ctx, a, b); +} + +struct ggml_tensor * ggml_cast( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_type type) { + struct ggml_tensor * result = ggml_new_tensor(ctx, type, GGML_MAX_DIMS, a->ne); + ggml_format_name(result, "%s (copy)", a->name); + + result->op = GGML_OP_CPY; + result->src[0] = a; + result->src[1] = result; // note: this self-reference might seem redundant, but it's actually needed by some + // backends for consistency with ggml_cpy_impl() above + + return result; +} + +// ggml_cont + +static struct ggml_tensor * ggml_cont_impl( + struct ggml_context * ctx, + struct ggml_tensor * a) { + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + ggml_format_name(result, "%s (cont)", a->name); + + result->op = GGML_OP_CONT; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_cont( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_cont_impl(ctx, a); +} + +// make contiguous, with new shape +GGML_API struct ggml_tensor * ggml_cont_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0) { + return ggml_cont_4d(ctx, a, ne0, 1, 1, 1); +} + +GGML_API struct ggml_tensor * ggml_cont_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1) { + return ggml_cont_4d(ctx, a, ne0, ne1, 1, 1); +} + +GGML_API struct ggml_tensor * ggml_cont_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2) { + return ggml_cont_4d(ctx, a, ne0, ne1, ne2, 1); +} + +struct ggml_tensor * ggml_cont_4d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3) { + GGML_ASSERT(ggml_nelements(a) == (ne0*ne1*ne2*ne3)); + + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, ne0, ne1, ne2, ne3); + ggml_format_name(result, "%s (cont)", a->name); + + result->op = GGML_OP_CONT; + result->src[0] = a; + + return result; +} + +// ggml_reshape + +struct ggml_tensor * ggml_reshape( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_is_contiguous(a)); + // as only the shape of b is relevant, and not its memory layout, b is allowed to be non contiguous. + GGML_ASSERT(ggml_nelements(a) == ggml_nelements(b)); + + struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, GGML_MAX_DIMS, b->ne, a, 0); + ggml_format_name(result, "%s (reshaped)", a->name); + + result->op = GGML_OP_RESHAPE; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_reshape_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0) { + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(ggml_nelements(a) == ne0); + + const int64_t ne[1] = { ne0 }; + struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, 1, ne, a, 0); + ggml_format_name(result, "%s (reshaped)", a->name); + + result->op = GGML_OP_RESHAPE; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_reshape_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1) { + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(ggml_nelements(a) == ne0*ne1); + + const int64_t ne[2] = { ne0, ne1 }; + struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, 2, ne, a, 0); + ggml_format_name(result, "%s (reshaped)", a->name); + + result->op = GGML_OP_RESHAPE; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_reshape_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2) { + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(ggml_nelements(a) == ne0*ne1*ne2); + + const int64_t ne[3] = { ne0, ne1, ne2 }; + struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, 3, ne, a, 0); + ggml_format_name(result, "%s (reshaped)", a->name); + + result->op = GGML_OP_RESHAPE; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_reshape_4d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3) { + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(ggml_nelements(a) == ne0*ne1*ne2*ne3); + + const int64_t ne[4] = { ne0, ne1, ne2, ne3 }; + struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, 4, ne, a, 0); + ggml_format_name(result, "%s (reshaped)", a->name); + + result->op = GGML_OP_RESHAPE; + result->src[0] = a; + + return result; +} + +static struct ggml_tensor * ggml_view_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_dims, + const int64_t * ne, + size_t offset) { + struct ggml_tensor * result = ggml_new_tensor_impl(ctx, a->type, n_dims, ne, a, offset); + ggml_format_name(result, "%s (view)", a->name); + + ggml_set_op_params(result, &offset, sizeof(offset)); + + result->op = GGML_OP_VIEW; + result->src[0] = a; + + return result; +} + +// ggml_view_1d + +struct ggml_tensor * ggml_view_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + size_t offset) { + struct ggml_tensor * result = ggml_view_impl(ctx, a, 1, &ne0, offset); + + return result; +} + +// ggml_view_2d + +struct ggml_tensor * ggml_view_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + size_t nb1, + size_t offset) { + const int64_t ne[2] = { ne0, ne1 }; + + struct ggml_tensor * result = ggml_view_impl(ctx, a, 2, ne, offset); + + result->nb[1] = nb1; + result->nb[2] = result->nb[1]*ne1; + result->nb[3] = result->nb[2]; + + return result; +} + +// ggml_view_3d + +struct ggml_tensor * ggml_view_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + size_t nb1, + size_t nb2, + size_t offset) { + const int64_t ne[3] = { ne0, ne1, ne2 }; + + struct ggml_tensor * result = ggml_view_impl(ctx, a, 3, ne, offset); + + result->nb[1] = nb1; + result->nb[2] = nb2; + result->nb[3] = result->nb[2]*ne2; + + return result; +} + +// ggml_view_4d + +struct ggml_tensor * ggml_view_4d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3, + size_t nb1, + size_t nb2, + size_t nb3, + size_t offset) { + const int64_t ne[4] = { ne0, ne1, ne2, ne3 }; + + struct ggml_tensor * result = ggml_view_impl(ctx, a, 4, ne, offset); + + result->nb[1] = nb1; + result->nb[2] = nb2; + result->nb[3] = nb3; + + return result; +} + +// ggml_permute + +struct ggml_tensor * ggml_permute( + struct ggml_context * ctx, + struct ggml_tensor * a, + int axis0, + int axis1, + int axis2, + int axis3) { + GGML_ASSERT(axis0 >= 0 && axis0 < GGML_MAX_DIMS); + GGML_ASSERT(axis1 >= 0 && axis1 < GGML_MAX_DIMS); + GGML_ASSERT(axis2 >= 0 && axis2 < GGML_MAX_DIMS); + GGML_ASSERT(axis3 >= 0 && axis3 < GGML_MAX_DIMS); + + GGML_ASSERT(axis0 != axis1); + GGML_ASSERT(axis0 != axis2); + GGML_ASSERT(axis0 != axis3); + GGML_ASSERT(axis1 != axis2); + GGML_ASSERT(axis1 != axis3); + GGML_ASSERT(axis2 != axis3); + + struct ggml_tensor * result = ggml_view_tensor(ctx, a); + ggml_format_name(result, "%s (permuted)", a->name); + + int ne[GGML_MAX_DIMS]; + int nb[GGML_MAX_DIMS]; + + ne[axis0] = a->ne[0]; + ne[axis1] = a->ne[1]; + ne[axis2] = a->ne[2]; + ne[axis3] = a->ne[3]; + + nb[axis0] = a->nb[0]; + nb[axis1] = a->nb[1]; + nb[axis2] = a->nb[2]; + nb[axis3] = a->nb[3]; + + result->ne[0] = ne[0]; + result->ne[1] = ne[1]; + result->ne[2] = ne[2]; + result->ne[3] = ne[3]; + + result->nb[0] = nb[0]; + result->nb[1] = nb[1]; + result->nb[2] = nb[2]; + result->nb[3] = nb[3]; + + result->op = GGML_OP_PERMUTE; + result->src[0] = a; + + int32_t params[] = { axis0, axis1, axis2, axis3 }; + ggml_set_op_params(result, params, sizeof(params)); + + return result; +} + +// ggml_transpose + +struct ggml_tensor * ggml_transpose( + struct ggml_context * ctx, + struct ggml_tensor * a) { + struct ggml_tensor * result = ggml_view_tensor(ctx, a); + ggml_format_name(result, "%s (transposed)", a->name); + + result->ne[0] = a->ne[1]; + result->ne[1] = a->ne[0]; + + result->nb[0] = a->nb[1]; + result->nb[1] = a->nb[0]; + + result->op = GGML_OP_TRANSPOSE; + result->src[0] = a; + + return result; +} + +// ggml_get_rows + +struct ggml_tensor * ggml_get_rows( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(a->ne[2] == b->ne[1]); + GGML_ASSERT(a->ne[3] == b->ne[2]); + GGML_ASSERT(b->ne[3] == 1); + GGML_ASSERT(b->type == GGML_TYPE_I32); + + // TODO: implement non F32 return + enum ggml_type type = GGML_TYPE_F32; + if (a->type == GGML_TYPE_I32) { + type = a->type; + } + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, type, a->ne[0], b->ne[0], b->ne[1], b->ne[2]); + + result->op = GGML_OP_GET_ROWS; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_get_rows_back + +struct ggml_tensor * ggml_get_rows_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c) { + GGML_ASSERT(ggml_is_matrix(a) && ggml_is_vector(b) && b->type == GGML_TYPE_I32); + GGML_ASSERT(ggml_is_matrix(c) && (a->ne[0] == c->ne[0])); + + // TODO: implement non F32 return + //struct ggml_tensor * result = ggml_new_tensor_2d(ctx, a->type, a->ne[0], b->ne[0]); + struct ggml_tensor * result = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, c->ne[0], c->ne[1]); + + result->op = GGML_OP_GET_ROWS_BACK; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_set_rows + +struct ggml_tensor * ggml_set_rows( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c) { + GGML_ASSERT(a->ne[0] == b->ne[0]); + GGML_ASSERT(a->ne[2] == b->ne[2]); + GGML_ASSERT(a->ne[3] == b->ne[3]); + GGML_ASSERT(b->ne[1] == c->ne[0]); + GGML_ASSERT(b->ne[2] % c->ne[1] == 0); + GGML_ASSERT(b->ne[3] % c->ne[2] == 0); + GGML_ASSERT(c->ne[3] == 1); + GGML_ASSERT(b->type == GGML_TYPE_F32); + GGML_ASSERT(c->type == GGML_TYPE_I64 || c->type == GGML_TYPE_I32); + + GGML_ASSERT(ggml_is_contiguous_rows(a)); + GGML_ASSERT(ggml_is_contiguous_rows(b)); + + struct ggml_tensor * result = ggml_view_tensor(ctx, a); + + result->op = GGML_OP_SET_ROWS; + result->src[0] = b; + result->src[1] = c; + result->src[2] = a; // note: order is weird due to legacy reasons (https://github.com/ggml-org/llama.cpp/pull/16063#discussion_r2385795931) + + return result; +} + +// ggml_diag + +struct ggml_tensor * ggml_diag( + struct ggml_context * ctx, + struct ggml_tensor * a) { + GGML_ASSERT(a->ne[1] == 1); + + const int64_t ne[4] = { a->ne[0], a->ne[0], a->ne[2], a->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, 4, ne); + + result->op = GGML_OP_DIAG; + result->src[0] = a; + + return result; +} + +// ggml_diag_mask_inf + +static struct ggml_tensor * ggml_diag_mask_inf_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + int32_t params[] = { n_past }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_DIAG_MASK_INF; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_diag_mask_inf( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past) { + return ggml_diag_mask_inf_impl(ctx, a, n_past, false); +} + +struct ggml_tensor * ggml_diag_mask_inf_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past) { + return ggml_diag_mask_inf_impl(ctx, a, n_past, true); +} + +// ggml_diag_mask_zero + +static struct ggml_tensor * ggml_diag_mask_zero_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + int32_t params[] = { n_past }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_DIAG_MASK_ZERO; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_diag_mask_zero( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past) { + return ggml_diag_mask_zero_impl(ctx, a, n_past, false); +} + +struct ggml_tensor * ggml_diag_mask_zero_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + int n_past) { + return ggml_diag_mask_zero_impl(ctx, a, n_past, true); +} + +// ggml_soft_max + +static struct ggml_tensor * ggml_soft_max_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * mask, + float scale, + float max_bias, + bool inplace) { + GGML_ASSERT(ggml_is_contiguous(a)); + + if (mask) { + GGML_ASSERT(mask->type == GGML_TYPE_F16 || mask->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_contiguous(mask)); + GGML_ASSERT(mask->ne[0] == a->ne[0]); + GGML_ASSERT(mask->ne[1] >= a->ne[1]); + GGML_ASSERT(a->ne[2]%mask->ne[2] == 0); + GGML_ASSERT(a->ne[3]%mask->ne[3] == 0); + } + + if (max_bias > 0.0f) { + GGML_ASSERT(mask); + } + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + float params[] = { scale, max_bias }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_SOFT_MAX; + result->src[0] = a; + result->src[1] = mask; + + return result; +} + +struct ggml_tensor * ggml_soft_max( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_soft_max_impl(ctx, a, NULL, 1.0f, 0.0f, false); +} + +struct ggml_tensor * ggml_soft_max_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a) { + return ggml_soft_max_impl(ctx, a, NULL, 1.0f, 0.0f, true); +} + +struct ggml_tensor * ggml_soft_max_ext( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * mask, + float scale, + float max_bias) { + return ggml_soft_max_impl(ctx, a, mask, scale, max_bias, false); +} + +struct ggml_tensor * ggml_soft_max_ext_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * mask, + float scale, + float max_bias) { + return ggml_soft_max_impl(ctx, a, mask, scale, max_bias, true); +} + +void ggml_soft_max_add_sinks( + struct ggml_tensor * a, + struct ggml_tensor * sinks) { + if (!sinks) { + a->src[2] = NULL; + return; + } + + GGML_ASSERT(a->op == GGML_OP_SOFT_MAX); + GGML_ASSERT(a->src[2] == NULL); + GGML_ASSERT(a->src[0]->ne[2] == sinks->ne[0]); + GGML_ASSERT(sinks->type == GGML_TYPE_F32); + + a->src[2] = sinks; +} + +// ggml_soft_max_ext_back + +static struct ggml_tensor * ggml_soft_max_ext_back_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float scale, + float max_bias, + bool inplace) { + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_SOFT_MAX_BACK; + result->src[0] = a; + result->src[1] = b; + + memcpy((float *) result->op_params + 0, &scale, sizeof(float)); + memcpy((float *) result->op_params + 1, &max_bias, sizeof(float)); + + return result; +} + +struct ggml_tensor * ggml_soft_max_ext_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float scale, + float max_bias) { + return ggml_soft_max_ext_back_impl(ctx, a, b, scale, max_bias, false); +} + +struct ggml_tensor * ggml_soft_max_ext_back_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + float scale, + float max_bias) { + return ggml_soft_max_ext_back_impl(ctx, a, b, scale, max_bias, true); +} + +// ggml_rope + +static struct ggml_tensor * ggml_rope_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int sections[GGML_MROPE_SECTIONS], + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow, + bool inplace) { + GGML_ASSERT((mode & 1) == 0 && "mode & 1 == 1 is no longer supported"); + + GGML_ASSERT(ggml_is_vector(b)); + GGML_ASSERT(b->type == GGML_TYPE_I32); + + bool mrope_used = mode & GGML_ROPE_TYPE_MROPE; + if (mrope_used) { + GGML_ASSERT(a->ne[2] * 4 == b->ne[0]); // mrope expecting 4 position ids per token + } else { + GGML_ASSERT(a->ne[2] == b->ne[0]); + } + + if (c) { + GGML_ASSERT(c->type == GGML_TYPE_F32); + GGML_ASSERT(c->ne[0] >= n_dims / 2); + } + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + int32_t params[15] = { /*n_past*/ 0, n_dims, mode, /*n_ctx*/ 0, n_ctx_orig }; + memcpy(params + 5, &freq_base, sizeof(float)); + memcpy(params + 6, &freq_scale, sizeof(float)); + memcpy(params + 7, &ext_factor, sizeof(float)); + memcpy(params + 8, &attn_factor, sizeof(float)); + memcpy(params + 9, &beta_fast, sizeof(float)); + memcpy(params + 10, &beta_slow, sizeof(float)); + if (mrope_used && sections) { + memcpy(params + 11, sections, sizeof(int32_t) * GGML_MROPE_SECTIONS); + } else { + memset(params + 11, 0, sizeof(int32_t) * GGML_MROPE_SECTIONS); + } + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_ROPE; + result->src[0] = a; + result->src[1] = b; + result->src[2] = c; + + return result; +} + +struct ggml_tensor * ggml_rope( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int n_dims, + int mode) { + return ggml_rope_impl( + ctx, a, b, NULL, n_dims, NULL, mode, 0, 10000.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, false + ); +} + +struct ggml_tensor * ggml_rope_multi( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int sections[GGML_MROPE_SECTIONS], + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow) { + return ggml_rope_impl( + ctx, a, b, c, n_dims, sections, mode, n_ctx_orig, freq_base, freq_scale, + ext_factor, attn_factor, beta_fast, beta_slow, false + ); +} + +struct ggml_tensor * ggml_rope_multi_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int sections[GGML_MROPE_SECTIONS], + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow) { + return ggml_rope_impl( + ctx, a, b, c, n_dims, sections, mode, n_ctx_orig, freq_base, freq_scale, + ext_factor, attn_factor, beta_fast, beta_slow, true + ); +} + +struct ggml_tensor * ggml_rope_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int n_dims, + int mode) { + return ggml_rope_impl( + ctx, a, b, NULL, n_dims, NULL, mode, 0, 10000.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, true + ); +} + +struct ggml_tensor * ggml_rope_ext( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow) { + return ggml_rope_impl( + ctx, a, b, c, n_dims, NULL, mode, n_ctx_orig, freq_base, freq_scale, + ext_factor, attn_factor, beta_fast, beta_slow, false + ); +} + +struct ggml_tensor * ggml_rope_ext_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow) { + return ggml_rope_impl( + ctx, a, b, c, n_dims, NULL, mode, n_ctx_orig, freq_base, freq_scale, + ext_factor, attn_factor, beta_fast, beta_slow, true + ); +} + +struct ggml_tensor * ggml_rope_custom( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow) { + return ggml_rope_impl( + ctx, a, b, NULL, n_dims, NULL, mode, n_ctx_orig, freq_base, freq_scale, + ext_factor, attn_factor, beta_fast, beta_slow, false + ); +} + +struct ggml_tensor * ggml_rope_custom_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow) { + return ggml_rope_impl( + ctx, a, b, NULL, n_dims, NULL, mode, n_ctx_orig, freq_base, freq_scale, + ext_factor, attn_factor, beta_fast, beta_slow, true + ); +} + +// Apparently solving `n_rot = 2pi * x * base^((2 * max_pos_emb) / n_dims)` for x, we get +// `corr_dim(n_rot) = n_dims * log(max_pos_emb / (n_rot * 2pi)) / (2 * log(base))` +static float ggml_rope_yarn_corr_dim(int n_dims, int n_ctx_orig, float n_rot, float base) { + return n_dims * logf(n_ctx_orig / (n_rot * 2 * (float)M_PI)) / (2 * logf(base)); +} + +void ggml_rope_yarn_corr_dims( + int n_dims, int n_ctx_orig, float freq_base, float beta_fast, float beta_slow, float dims[2] +) { + // start and end correction dims + float start = floorf(ggml_rope_yarn_corr_dim(n_dims, n_ctx_orig, beta_fast, freq_base)); + float end = ceilf(ggml_rope_yarn_corr_dim(n_dims, n_ctx_orig, beta_slow, freq_base)); + dims[0] = MAX(0, start); + dims[1] = MIN(n_dims - 1, end); +} + +// ggml_rope_back + +struct ggml_tensor * ggml_rope_ext_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow) { + struct ggml_tensor * result = ggml_rope_ext( + ctx, a, b, c, n_dims, mode, n_ctx_orig, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow); + result->op = GGML_OP_ROPE_BACK; + return result; +} + +struct ggml_tensor * ggml_rope_multi_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + int n_dims, + int sections[4], + int mode, + int n_ctx_orig, + float freq_base, + float freq_scale, + float ext_factor, + float attn_factor, + float beta_fast, + float beta_slow) { + struct ggml_tensor * result = ggml_rope_multi( + ctx, a, b, c, n_dims, sections, mode, n_ctx_orig, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow); + result->op = GGML_OP_ROPE_BACK; + return result; +} +// ggml_clamp + +struct ggml_tensor * ggml_clamp( + struct ggml_context * ctx, + struct ggml_tensor * a, + float min, + float max) { + // TODO: when implement backward, fix this: + struct ggml_tensor * result = ggml_view_tensor(ctx, a); + + float params[] = { min, max }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_CLAMP; + result->src[0] = a; + + return result; +} + +static int64_t ggml_calc_conv_output_size(int64_t ins, int64_t ks, int s, int p, int d) { + return (ins + 2 * p - d * (ks - 1) - 1) / s + 1; +} + +// im2col: [N, IC, IH, IW] => [N, OH, OW, IC*KH*KW] +// a: [OC,IC, KH, KW] +// b: [N, IC, IH, IW] +// result: [N, OH, OW, IC*KH*KW] +struct ggml_tensor * ggml_im2col( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int s1, + int p0, + int p1, + int d0, + int d1, + bool is_2D, + enum ggml_type dst_type) { + if (is_2D) { + GGML_ASSERT(a->ne[2] == b->ne[2]); + } else { + //GGML_ASSERT(b->ne[1] % a->ne[1] == 0); + GGML_ASSERT(b->ne[1] == a->ne[1]); + GGML_ASSERT(b->ne[3] == 1); + } + + const int64_t OH = is_2D ? ggml_calc_conv_output_size(b->ne[1], a->ne[1], s1, p1, d1) : 0; + const int64_t OW = ggml_calc_conv_output_size(b->ne[0], a->ne[0], s0, p0, d0); + + GGML_ASSERT((!is_2D || OH > 0) && "b too small compared to a"); + GGML_ASSERT((OW > 0) && "b too small compared to a"); + + const int64_t ne[4] = { + is_2D ? (a->ne[2] * a->ne[1] * a->ne[0]) : a->ne[1] * a->ne[0], + OW, + is_2D ? OH : b->ne[2], + is_2D ? b->ne[3] : 1, + }; + + struct ggml_tensor * result = ggml_new_tensor(ctx, dst_type, 4, ne); + int32_t params[] = { s0, s1, p0, p1, d0, d1, (is_2D ? 1 : 0) }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_IM2COL; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_im2col_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int64_t * ne, + int s0, + int s1, + int p0, + int p1, + int d0, + int d1, + bool is_2D) { + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + int32_t params[] = { s0, s1, p0, p1, d0, d1, (is_2D ? 1 : 0) }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_IM2COL_BACK; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_col2im_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int s0, + int oc, + int p0) { + GGML_ASSERT(ggml_is_matrix(a)); + GGML_ASSERT(s0 > 0); + GGML_ASSERT(oc > 0); + GGML_ASSERT(a->ne[0] % oc == 0); + + const int64_t k = a->ne[0] / oc; + const int64_t ne[4] = { + (a->ne[1] - 1) * s0 + k - 2 * p0, + oc, + 1, + 1, + }; + GGML_ASSERT(ne[0] > 0); + struct ggml_tensor * result = ggml_new_tensor(ctx, a->type, 2, ne); + + int32_t params[] = {s0, oc, p0}; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_COL2IM_1D; + result->src[0] = a; + + return result; +} + +static struct ggml_tensor * ggml_im2col_fast_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int s1, + int p0, + int p1, + int d0, + int d1, + bool is_2D, + enum ggml_type dst_type) { + struct ggml_tensor * result = ggml_im2col(ctx, a, b, s0, s1, p0, p1, d0, d1, is_2D, dst_type); + result->op = GGML_OP_IM2COL_FAST_1D; + return result; +} + +// ggml_conv_1d + +struct ggml_tensor * ggml_conv_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int p0, + int d0) { + struct ggml_tensor * im2col = ggml_im2col(ctx, a, b, s0, 0, p0, 0, d0, 0, false, a->type); // [N, OL, IC * K] + + struct ggml_tensor * result = + ggml_mul_mat(ctx, + ggml_reshape_2d(ctx, im2col, im2col->ne[0], (im2col->ne[2] * im2col->ne[1])), // [N, OL, IC * K] => [N*OL, IC * K] + ggml_reshape_2d(ctx, a, (a->ne[0] * a->ne[1]), a->ne[2])); // [OC,IC, K] => [OC, IC * K] + + result = ggml_reshape_3d(ctx, result, im2col->ne[1], a->ne[2], im2col->ne[2]); // [N, OC, OL] + + return result; +} + +struct ggml_tensor * ggml_conv_1d_fast_1d_im2col( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int p0, + int d0) { + struct ggml_tensor * im2col = ggml_im2col_fast_1d(ctx, a, b, s0, 0, p0, 0, d0, 0, false, a->type); // [N, OL, IC * K] + + struct ggml_tensor * result = + ggml_mul_mat(ctx, + ggml_reshape_2d(ctx, im2col, im2col->ne[0], (im2col->ne[2] * im2col->ne[1])), // [N, OL, IC * K] => [N*OL, IC * K] + ggml_reshape_2d(ctx, a, (a->ne[0] * a->ne[1]), a->ne[2])); // [OC,IC, K] => [OC, IC * K] + + result = ggml_reshape_3d(ctx, result, im2col->ne[1], a->ne[2], im2col->ne[2]); // [N, OC, OL] + + return result; +} + +// ggml_conv_1d_ph + +struct ggml_tensor* ggml_conv_1d_ph( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s, + int d) { + return ggml_conv_1d(ctx, a, b, s, a->ne[0] / 2, d); +} + +// ggml_conv_1d_dw + +struct ggml_tensor * ggml_conv_1d_dw( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int p0, + int d0) { + struct ggml_tensor * new_b = ggml_reshape_4d(ctx, b, b->ne[0], 1, b->ne[1], b->ne[2]); + + struct ggml_tensor * im2col = ggml_im2col(ctx, a, new_b, s0, 0, p0, 0, d0, 0, false, a->type); + + struct ggml_tensor * result = ggml_mul_mat(ctx, im2col, a); + + result = ggml_reshape_3d(ctx, result, result->ne[0], result->ne[2], 1); + + return result; +} + +// ggml_conv_1d_dw_ph + +struct ggml_tensor * ggml_conv_1d_dw_ph( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int d0) { + return ggml_conv_1d_dw(ctx, a, b, s0, a->ne[0] / 2, d0); +} + +// ggml_conv_transpose_1d + +static int64_t ggml_calc_conv_transpose_1d_output_size(int64_t ins, int64_t ks, int s, int p, int d) { + return (ins - 1) * s - 2 * p + d * (ks - 1) + 1; +} + +GGML_API struct ggml_tensor * ggml_conv_transpose_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int p0, + int d0) { + GGML_ASSERT(ggml_is_matrix(b)); + GGML_ASSERT(a->ne[2] == b->ne[1]); + GGML_ASSERT(a->ne[3] == 1); + + GGML_ASSERT(p0 == 0); + GGML_ASSERT(d0 == 1); + + const int64_t ne[4] = { + ggml_calc_conv_transpose_1d_output_size(b->ne[0], a->ne[0], s0, 0 /*p0*/, 1 /*d0*/), + a->ne[1], b->ne[2], 1, + }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + int32_t params[] = { s0, p0, d0 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_CONV_TRANSPOSE_1D; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_conv_2d + +// a: [OC,IC, KH, KW] +// b: [N, IC, IH, IW] +// result: [N, OC, OH, OW] +struct ggml_tensor * ggml_conv_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int s1, + int p0, + int p1, + int d0, + int d1) { + struct ggml_tensor * im2col = ggml_im2col(ctx, a, b, s0, s1, p0, p1, d0, d1, true, a->type); // [N, OH, OW, IC * KH * KW] + + struct ggml_tensor * result = + ggml_mul_mat(ctx, + ggml_reshape_2d(ctx, im2col, im2col->ne[0], im2col->ne[3] * im2col->ne[2] * im2col->ne[1]), // [N, OH, OW, IC * KH * KW] => [N*OH*OW, IC * KH * KW] + ggml_reshape_2d(ctx, a, (a->ne[0] * a->ne[1] * a->ne[2]), a->ne[3])); // [OC,IC, KH, KW] => [OC, IC * KH * KW] + + result = ggml_reshape_4d(ctx, result, im2col->ne[1], im2col->ne[2], im2col->ne[3], a->ne[3]); // [OC, N, OH, OW] + result = ggml_cont(ctx, ggml_permute(ctx, result, 0, 1, 3, 2)); // [N, OC, OH, OW] + + + return result; +} + +// a: [OC*IC, KD, KH, KW] +// b: [N*IC, ID, IH, IW] +// result: [N*OD, OH, OW, IC * KD * KH * KW] +struct ggml_tensor * ggml_im2col_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int64_t IC, + int s0, // stride width + int s1, // stride height + int s2, // stride depth + int p0, // padding width + int p1, // padding height + int p2, // padding depth + int d0, // dilation width + int d1, // dilation height + int d2, // dilation depth + enum ggml_type dst_type) { + const int64_t N = b->ne[3] / IC; + const int64_t ID = b->ne[2]; + const int64_t IH = b->ne[1]; + const int64_t IW = b->ne[0]; + + const int64_t OC = a->ne[3] / IC; + UNUSED(OC); + const int64_t KD = a->ne[2]; + const int64_t KH = a->ne[1]; + const int64_t KW = a->ne[0]; + const int64_t OD = ggml_calc_conv_output_size(ID, KD, s2, p2, d2); + const int64_t OH = ggml_calc_conv_output_size(IH, KH, s1, p1, d1); + const int64_t OW = ggml_calc_conv_output_size(IW, KW, s0, p0, d0); + + GGML_ASSERT((OD > 0) && "b too small compared to a"); + GGML_ASSERT((OH > 0) && "b too small compared to a"); + GGML_ASSERT((OW > 0) && "b too small compared to a"); + + + const int64_t ne[4] = {KW*KH*KD*IC, OW, OH, OD*N}; + + struct ggml_tensor * result = ggml_new_tensor(ctx, dst_type, 4, ne); + int32_t params[] = { s0, s1, s2, p0, p1, p2, d0, d1, d2, (int32_t)IC}; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_IM2COL_3D; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// a: [OC*IC, KD, KH, KW] +// b: [N*IC, ID, IH, IW] +// result: [N*OC, OD, OH, OW] +struct ggml_tensor * ggml_conv_3d( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int64_t IC, + int s0, // stride width + int s1, // stride height + int s2, // stride depth + int p0, // padding width + int p1, // padding height + int p2, // padding depth + int d0, // dilation width + int d1, // dilation height + int d2 // dilation depth + ) { + struct ggml_tensor * im2col = ggml_im2col_3d(ctx, a, b, IC, s0, s1, s2, p0, p1, p2, d0, d1, d2, a->type); // [N*OD, OH, OW, IC * KD * KH * KW] + + int64_t OC = a->ne[3] / IC; + int64_t N = b->ne[3] / IC; + struct ggml_tensor * result = + ggml_mul_mat(ctx, + ggml_reshape_2d(ctx, im2col, im2col->ne[0], im2col->ne[3] * im2col->ne[2] * im2col->ne[1]), // [N*OD, OH, OW, IC * KD * KH * KW] => [N*OD*OH*OW, IC * KD * KH * KW] + ggml_reshape_2d(ctx, a, (a->ne[0] * a->ne[1] * a->ne[2] * IC), OC)); // [OC*IC, KD, KH, KW] => [OC, IC * KD * KH * KW] + + int64_t OD = im2col->ne[3] / N; + result = ggml_reshape_4d(ctx, result, im2col->ne[1]*im2col->ne[2], OD, N, OC); // [OC, N*OD*OH*OW] => [OC, N, OD, OH*OW] + result = ggml_cont(ctx, ggml_permute(ctx, result, 0, 1, 3, 2)); // [N, OC, OD, OH*OW] + result = ggml_reshape_4d(ctx, result, im2col->ne[1], im2col->ne[2], OD, OC * N); // [N*OC, OD, OH, OW] + + return result; +} + +// ggml_conv_2d_sk_p0 + +struct ggml_tensor * ggml_conv_2d_sk_p0( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_conv_2d(ctx, a, b, a->ne[0], a->ne[1], 0, 0, 1, 1); +} + +// ggml_conv_2d_s1_ph + +struct ggml_tensor * ggml_conv_2d_s1_ph( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + return ggml_conv_2d(ctx, a, b, 1, 1, a->ne[0] / 2, a->ne[1] / 2, 1, 1); +} + +// ggml_conv_2d_dw + +struct ggml_tensor * ggml_conv_2d_dw( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int s1, + int p0, + int p1, + int d0, + int d1) { + struct ggml_tensor * new_a = ggml_reshape_4d(ctx, a, a->ne[0], a->ne[1], 1, a->ne[2] * a->ne[3]); + struct ggml_tensor * im2col = ggml_im2col(ctx, new_a, + ggml_reshape_4d(ctx, b, b->ne[0], b->ne[1], 1, b->ne[2] * b->ne[3]), + s0, s1, p0, p1, d0, d1, true, GGML_TYPE_F16); // [N * IC, OH, OW, KH * KW] + struct ggml_tensor * new_b = ggml_reshape_4d(ctx, im2col, im2col->ne[0], im2col->ne[2] * im2col->ne[1], b->ne[2], b->ne[3]); // [N * IC, OH, OW, KH * KW] => [N, IC, OH * OW, KH * KW] + + new_a = ggml_reshape_4d(ctx, new_a, (new_a->ne[0] * new_a->ne[1]), new_a->ne[2], new_a->ne[3], 1); // [OC,1, KH, KW] => [1, OC, 1, KH * KW] + struct ggml_tensor * result = ggml_mul_mat(ctx, new_a, new_b); + result = ggml_reshape_4d(ctx, result, im2col->ne[1], im2col->ne[2], b->ne[2], b->ne[3]); // [N, OC, OH, OW] + + return result; +} + +// ggml_conv_2d_dw_direct + +struct ggml_tensor * ggml_conv_2d_dw_direct( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int stride0, + int stride1, + int pad0, + int pad1, + int dilation0, + int dilation1) { + GGML_ASSERT(a->ne[2] == 1); + GGML_ASSERT(a->ne[3] == b->ne[2]); + int64_t ne[4]; + ne[0] = ggml_calc_conv_output_size(b->ne[0], a->ne[0], stride0, pad0, dilation0); + ne[1] = ggml_calc_conv_output_size(b->ne[1], a->ne[1], stride1, pad1, dilation1); + ne[2] = b->ne[2]; + ne[3] = b->ne[3]; + + struct ggml_tensor * result = ggml_new_tensor(ctx, b->type, 4, ne); + + if (ggml_is_contiguous_channels(b)) { + // Result will be permuted the same way as input (CWHN order) + const int64_t type_size = ggml_type_size(result->type); + GGML_ASSERT(ggml_blck_size(result->type) == 1); + result->nb[0] = result->ne[2] * type_size; + result->nb[1] = result->ne[0] * result->nb[0]; + result->nb[2] = type_size; + } + + int32_t params[] = { stride0, stride1, pad0, pad1, dilation0, dilation1 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_CONV_2D_DW; + result->src[0] = a; + result->src[1] = b; + return result; +} + +// ggml_conv_2d_direct + +struct ggml_tensor * ggml_conv_2d_direct( + struct ggml_context * ctx, + struct ggml_tensor * a, // convolution kernel [KW, KH, IC, OC] + struct ggml_tensor * b, // input data [W, H, C, N] + int s0, // stride dimension 0 + int s1, // stride dimension 1 + int p0, // padding dimension 0 + int p1, // padding dimension 1 + int d0, // dilation dimension 0 + int d1) {// dilation dimension 1 + + GGML_ASSERT(a->ne[2] == b->ne[2]); + //GGML_ASSERT(a->type == b->type); + + int64_t ne[4]; + ne[0] = ggml_calc_conv_output_size(b->ne[0], a->ne[0], s0, p0, d0); + ne[1] = ggml_calc_conv_output_size(b->ne[1], a->ne[1], s1, p1, d1); + ne[2] = a->ne[3]; + ne[3] = b->ne[3]; + + struct ggml_tensor * result = ggml_new_tensor(ctx, b->type, 4, ne); + + ggml_set_op_params_i32(result, 0, s0); + ggml_set_op_params_i32(result, 1, s1); + ggml_set_op_params_i32(result, 2, p0); + ggml_set_op_params_i32(result, 3, p1); + ggml_set_op_params_i32(result, 4, d0); + ggml_set_op_params_i32(result, 5, d1); + + result->op = GGML_OP_CONV_2D; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_conv_3d_direct + +struct ggml_tensor * ggml_conv_3d_direct( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int s1, + int s2, + int p0, + int p1, + int p2, + int d0, + int d1, + int d2, + int c, + int n, + int oc) { + + GGML_ASSERT(a->ne[3] == (int64_t) c * oc); + GGML_ASSERT(b->ne[3] == (int64_t) c * n); + + int64_t ne[4]; + ne[0] = ggml_calc_conv_output_size(b->ne[0], a->ne[0], s0, p0, d0); + ne[1] = ggml_calc_conv_output_size(b->ne[1], a->ne[1], s1, p1, d1); + ne[2] = ggml_calc_conv_output_size(b->ne[2], a->ne[2], s2, p2, d2); + ne[3] = (int64_t) oc * n; + + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + ggml_set_op_params_i32(result, 0, s0); + ggml_set_op_params_i32(result, 1, s1); + ggml_set_op_params_i32(result, 2, s2); + ggml_set_op_params_i32(result, 3, p0); + ggml_set_op_params_i32(result, 4, p1); + ggml_set_op_params_i32(result, 5, p2); + ggml_set_op_params_i32(result, 6, d0); + ggml_set_op_params_i32(result, 7, d1); + ggml_set_op_params_i32(result, 8, d2); + ggml_set_op_params_i32(result, 9, c); + ggml_set_op_params_i32(result, 10, n); + ggml_set_op_params_i32(result, 11, oc); + + result->op = GGML_OP_CONV_3D; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_conv_transpose_2d_p0 + +static int64_t ggml_calc_conv_transpose_output_size(int64_t ins, int64_t ks, int s, int p) { + return (ins - 1) * s - 2 * p + ks; +} + +struct ggml_tensor * ggml_conv_transpose_2d_p0( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int stride) { + GGML_ASSERT(a->ne[3] == b->ne[2]); + + const int64_t ne[4] = { + ggml_calc_conv_transpose_output_size(b->ne[0], a->ne[0], stride, 0 /*p0*/), + ggml_calc_conv_transpose_output_size(b->ne[1], a->ne[1], stride, 0 /*p1*/), + a->ne[2], b->ne[3], + }; + + struct ggml_tensor* result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + ggml_set_op_params_i32(result, 0, stride); + + result->op = GGML_OP_CONV_TRANSPOSE_2D; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_pool_* + +static int64_t ggml_calc_pool_output_size(int64_t ins, int ks, int s, float p) { + return (ins + 2 * p - ks) / s + 1; +} + +// ggml_pool_1d + +struct ggml_tensor * ggml_pool_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_op_pool op, + int k0, + int s0, + int p0) { + const int64_t ne[4] = { + ggml_calc_pool_output_size(a->ne[0], k0, s0, p0), + a->ne[1], + a->ne[2], + a->ne[3], + }; + GGML_ASSERT(ne[0] > 0); + + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + int32_t params[] = { op, k0, s0, p0 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_POOL_1D; + result->src[0] = a; + + return result; +} + +// ggml_pool_2d + +struct ggml_tensor * ggml_pool_2d( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_op_pool op, + int k0, + int k1, + int s0, + int s1, + float p0, + float p1) { + struct ggml_tensor * result; + const int64_t ne[4] = { + ggml_calc_pool_output_size(a->ne[0], k0, s0, p0), + ggml_calc_pool_output_size(a->ne[1], k1, s1, p1), + a->ne[2], + a->ne[3], + }; + GGML_ASSERT(ne[0] > 0); + GGML_ASSERT(ne[1] > 0); + + result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + int32_t params[] = { op, k0, k1, s0, s1, p0, p1 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_POOL_2D; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_pool_2d_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * af, + enum ggml_op_pool op, + int k0, + int k1, + int s0, + int s1, + float p0, + float p1) { + struct ggml_tensor * result; + result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, af->ne); + + int32_t params[] = { op, k0, k1, s0, s1, p0, p1 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_POOL_2D_BACK; + result->src[0] = a; + result->src[1] = af; + + return result; +} + +// ggml_upscale / ggml_interpolate + +static struct ggml_tensor * ggml_interpolate_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3, + uint32_t mode) { + GGML_ASSERT((mode & 0xFF) < GGML_SCALE_MODE_COUNT); + // TODO: implement antialias for modes other than bilinear + GGML_ASSERT(!(mode & GGML_SCALE_FLAG_ANTIALIAS) || (mode & 0xFF) == GGML_SCALE_MODE_BILINEAR); + GGML_ASSERT(a->type == GGML_TYPE_F32); + + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, ne0, ne1, ne2, ne3); + + ggml_set_op_params_i32(result, 0, (int32_t)mode); + + result->op = GGML_OP_UPSCALE; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_upscale( + struct ggml_context * ctx, + struct ggml_tensor * a, + int scale_factor, + enum ggml_scale_mode mode) { + GGML_ASSERT(scale_factor > 1); + return ggml_interpolate_impl(ctx, a, a->ne[0] * scale_factor, a->ne[1] * scale_factor, a->ne[2], a->ne[3], mode); +} + +struct ggml_tensor * ggml_upscale_ext( + struct ggml_context * ctx, + struct ggml_tensor * a, + int ne0, + int ne1, + int ne2, + int ne3, + enum ggml_scale_mode mode) { + return ggml_interpolate_impl(ctx, a, ne0, ne1, ne2, ne3, mode); +} + +struct ggml_tensor * ggml_interpolate( + struct ggml_context * ctx, + struct ggml_tensor * a, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3, + uint32_t mode) { + return ggml_interpolate_impl(ctx, a, ne0, ne1, ne2, ne3, mode); +} + +// ggml_pad + +struct ggml_tensor * ggml_pad( + struct ggml_context * ctx, + struct ggml_tensor * a, + int p0, + int p1, + int p2, + int p3) { + return ggml_pad_ext(ctx, a, 0, p0, 0, p1, 0, p2, 0, p3); +} + +// ggml_pad_circular + +struct ggml_tensor * ggml_pad_circular( + struct ggml_context * ctx, + struct ggml_tensor * a, + int p0, + int p1, + int p2, + int p3) { + return ggml_pad_ext_circular(ctx, a, 0, p0, 0, p1, 0, p2, 0, p3); +} + +struct ggml_tensor * ggml_pad_ext( + struct ggml_context * ctx, + struct ggml_tensor * a, + int lp0, + int rp0, + int lp1, + int rp1, + int lp2, + int rp2, + int lp3, + int rp3 + ) { + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, + a->ne[0] + lp0 + rp0, + a->ne[1] + lp1 + rp1, + a->ne[2] + lp2 + rp2, + a->ne[3] + lp3 + rp3); + + ggml_set_op_params_i32(result, 0, lp0); + ggml_set_op_params_i32(result, 1, rp0); + ggml_set_op_params_i32(result, 2, lp1); + ggml_set_op_params_i32(result, 3, rp1); + ggml_set_op_params_i32(result, 4, lp2); + ggml_set_op_params_i32(result, 5, rp2); + ggml_set_op_params_i32(result, 6, lp3); + ggml_set_op_params_i32(result, 7, rp3); + ggml_set_op_params_i32(result, 8, 0); // not circular by default + + + result->op = GGML_OP_PAD; + result->src[0] = a; + + return result; +} + +// ggml_pad_ext_circular + +struct ggml_tensor * ggml_pad_ext_circular( + struct ggml_context * ctx, + struct ggml_tensor * a, + int lp0, + int rp0, + int lp1, + int rp1, + int lp2, + int rp2, + int lp3, + int rp3 + ) { + struct ggml_tensor * result = ggml_pad_ext(ctx, a, lp0, rp0, lp1, rp1, lp2, rp2, lp3, rp3); + ggml_set_op_params_i32(result, 8, 1); // circular + return result; +} + +// ggml_pad_reflect_1d + +struct ggml_tensor * ggml_pad_reflect_1d( + struct ggml_context * ctx, + struct ggml_tensor * a, + int p0, + int p1) { + GGML_ASSERT(p0 >= 0); + GGML_ASSERT(p1 >= 0); + + GGML_ASSERT(p0 < a->ne[0]); // padding length on each size must be less than the + GGML_ASSERT(p1 < a->ne[0]); // existing length of the dimension being padded + + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(a->type == GGML_TYPE_F32); + + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, a->type, + a->ne[0] + p0 + p1, + a->ne[1], + a->ne[2], + a->ne[3]); + + int32_t params[] = { p0, p1 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_PAD_REFLECT_1D; + result->src[0] = a; + + return result; +} + +// ggml_roll + +struct ggml_tensor * ggml_roll( + struct ggml_context * ctx, + struct ggml_tensor * a, + int shift0, + int shift1, + int shift2, + int shift3) { + GGML_ASSERT(a->nb[0] == ggml_type_size(a->type)); + GGML_ASSERT(abs(shift0) < a->ne[0]); + GGML_ASSERT(abs(shift1) < a->ne[1]); + GGML_ASSERT(abs(shift2) < a->ne[2]); + GGML_ASSERT(abs(shift3) < a->ne[3]); + + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + ggml_set_op_params_i32(result, 0, shift0); + ggml_set_op_params_i32(result, 1, shift1); + ggml_set_op_params_i32(result, 2, shift2); + ggml_set_op_params_i32(result, 3, shift3); + + result->op = GGML_OP_ROLL; + result->src[0] = a; + + return result; +} + +// ggml_timestep_embedding + +struct ggml_tensor * ggml_timestep_embedding( + struct ggml_context * ctx, + struct ggml_tensor * timesteps, + int dim, + int max_period) { + + struct ggml_tensor * result = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, dim, timesteps->ne[0]); + + ggml_set_op_params_i32(result, 0, dim); + ggml_set_op_params_i32(result, 1, max_period); + + result->op = GGML_OP_TIMESTEP_EMBEDDING; + result->src[0] = timesteps; + + return result; +} + +// ggml_tri + +struct ggml_tensor * ggml_tri( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_tri_type type) { + GGML_ASSERT(a->type == GGML_TYPE_F32); + + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(a->ne[0] == a->ne[1]); + + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + ggml_set_op_params_i32(result, 0, type); + + result->op = GGML_OP_TRI; + result->src[0] = a; + + return result; +} + +// ggml_fill + +static struct ggml_tensor * ggml_fill_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + float c, + bool inplace) { + GGML_ASSERT(a->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_contiguous(a)); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + ggml_set_op_params_f32(result, 0, c); + + result->op = GGML_OP_FILL; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_fill( + struct ggml_context * ctx, + struct ggml_tensor * a, + float c) { + return ggml_fill_impl(ctx, a, c, false); +} + +struct ggml_tensor * ggml_fill_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + float c) { + return ggml_fill_impl(ctx, a, c, true); +} + +// ggml_argsort + +struct ggml_tensor * ggml_argsort( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_sort_order order) { + GGML_ASSERT(a->ne[0] <= INT32_MAX); + + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_I32, GGML_MAX_DIMS, a->ne); + + ggml_set_op_params_i32(result, 0, (int32_t) order); + + result->op = GGML_OP_ARGSORT; + result->src[0] = a; + + return result; +} + +// ggml_argsort_top_k + +struct ggml_tensor * ggml_argsort_top_k( + struct ggml_context * ctx, + struct ggml_tensor * a, + int k) { + GGML_ASSERT(a->ne[0] >= k); + + struct ggml_tensor * result = ggml_argsort(ctx, a, GGML_SORT_ORDER_DESC); + + result = ggml_view_4d(ctx, result, + k, result->ne[1], result->ne[2], result->ne[3], + result->nb[1], result->nb[2], result->nb[3], + 0); + + return result; +} + +// ggml_top_k + +struct ggml_tensor * ggml_top_k( + struct ggml_context * ctx, + struct ggml_tensor * a, + int k) { + GGML_ASSERT(a->ne[0] >= k); + + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, GGML_TYPE_I32, k, a->ne[1], a->ne[2], a->ne[3]); + + result->op = GGML_OP_TOP_K; + result->src[0] = a; + + return result; +} + +// ggml_arange + +struct ggml_tensor * ggml_arange( + struct ggml_context * ctx, + float start, + float stop, + float step) { + GGML_ASSERT(stop > start); + + const int64_t steps = (int64_t) ceilf((stop - start) / step); + + struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, steps); + + ggml_set_op_params_f32(result, 0, start); + ggml_set_op_params_f32(result, 1, stop); + ggml_set_op_params_f32(result, 2, step); + + result->op = GGML_OP_ARANGE; + + return result; +} + +// ggml_flash_attn_ext + +struct ggml_tensor * ggml_flash_attn_ext( + struct ggml_context * ctx, + struct ggml_tensor * q, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * mask, + float scale, + float max_bias, + float logit_softcap) { + GGML_ASSERT(ggml_can_mul_mat(k, q)); + // TODO: check if vT can be multiplied by (k*qT) + + GGML_ASSERT(q->ne[3] == k->ne[3]); + GGML_ASSERT(q->ne[3] == v->ne[3]); + + if (mask) { + GGML_ASSERT(mask->type == GGML_TYPE_F16); + GGML_ASSERT(ggml_is_contiguous(mask)); + //GGML_ASSERT(ggml_can_repeat_rows(mask, qk)); + + GGML_ASSERT(q->ne[2] % mask->ne[2] == 0); + GGML_ASSERT(q->ne[3] % mask->ne[3] == 0); + } + + if (max_bias > 0.0f) { + GGML_ASSERT(mask); + } + + // permute(0, 2, 1, 3) + int64_t ne[4] = { v->ne[0], q->ne[2], q->ne[1], q->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + float params[] = { scale, max_bias, logit_softcap }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_FLASH_ATTN_EXT; + result->src[0] = q; + result->src[1] = k; + result->src[2] = v; + result->src[3] = mask; + + return result; +} + +struct ggml_tensor * ggml_sage_attn2( + struct ggml_context * ctx, + struct ggml_tensor * q, + struct ggml_tensor * k, + struct ggml_tensor * v, + float scale, + bool causal) { + GGML_ASSERT(q->type == GGML_TYPE_F16); + GGML_ASSERT(k->type == GGML_TYPE_F16); + GGML_ASSERT(v->type == GGML_TYPE_F16); + GGML_ASSERT(ggml_is_contiguous(q)); + GGML_ASSERT(ggml_is_contiguous(k)); + GGML_ASSERT(ggml_is_contiguous(v)); + + GGML_ASSERT(q->ne[0] == k->ne[0]); + GGML_ASSERT(q->ne[0] == v->ne[0]); + GGML_ASSERT(k->ne[1] == v->ne[1]); + GGML_ASSERT(k->ne[2] == v->ne[2]); + GGML_ASSERT(q->ne[3] == k->ne[3]); + GGML_ASSERT(q->ne[3] == v->ne[3]); + GGML_ASSERT(q->ne[2] % k->ne[2] == 0); + GGML_ASSERT(q->ne[0] == 64 || q->ne[0] == 128); + GGML_ASSERT(scale > 0.0f); + + int64_t ne[4] = { v->ne[0], q->ne[2], q->ne[1], q->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F16, 4, ne); + + ggml_set_op_params_f32(result, 0, scale); + ggml_set_op_params_i32(result, 1, causal ? 1 : 0); + + result->op = GGML_OP_SAGE_ATTN2; + result->src[0] = q; + result->src[1] = k; + result->src[2] = v; + + return result; +} + +struct ggml_tensor * ggml_sage_attn2_i8( + struct ggml_context * ctx, + struct ggml_tensor * q_i8, + struct ggml_tensor * k_i8, + struct ggml_tensor * v, + struct ggml_tensor * q_scale, + struct ggml_tensor * k_scale, + float scale, + bool causal) { + GGML_ASSERT(q_i8->type == GGML_TYPE_I8); + GGML_ASSERT(k_i8->type == GGML_TYPE_I8); + GGML_ASSERT(v->type == GGML_TYPE_F16); + GGML_ASSERT(q_scale->type == GGML_TYPE_F32); + GGML_ASSERT(k_scale->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_contiguous(q_i8)); + GGML_ASSERT(ggml_is_contiguous(k_i8)); + GGML_ASSERT(ggml_is_contiguous(v)); + GGML_ASSERT(ggml_is_contiguous(q_scale)); + GGML_ASSERT(ggml_is_contiguous(k_scale)); + + GGML_ASSERT(q_i8->ne[0] == k_i8->ne[0]); + GGML_ASSERT(q_i8->ne[0] == v->ne[0]); + GGML_ASSERT(k_i8->ne[1] == v->ne[1]); + GGML_ASSERT(k_i8->ne[2] == v->ne[2]); + GGML_ASSERT(q_i8->ne[3] == k_i8->ne[3]); + GGML_ASSERT(q_i8->ne[3] == v->ne[3]); + GGML_ASSERT(q_i8->ne[2] % k_i8->ne[2] == 0); + GGML_ASSERT(q_i8->ne[0] == 64 || q_i8->ne[0] == 128); + GGML_ASSERT(q_scale->ne[0] == ((q_i8->ne[1] + 127) / 128) * 4); + GGML_ASSERT(q_scale->ne[1] == q_i8->ne[2]); + GGML_ASSERT(q_scale->ne[2] == q_i8->ne[3]); + GGML_ASSERT(k_scale->ne[0] == (k_i8->ne[1] + 63) / 64); + GGML_ASSERT(k_scale->ne[1] == k_i8->ne[2]); + GGML_ASSERT(k_scale->ne[2] == k_i8->ne[3]); + GGML_ASSERT(scale > 0.0f); + + int64_t ne[4] = { v->ne[0], q_i8->ne[2], q_i8->ne[1], q_i8->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F16, 4, ne); + + ggml_set_op_params_f32(result, 0, scale); + ggml_set_op_params_i32(result, 1, causal ? 1 : 0); + + result->op = GGML_OP_SAGE_ATTN2_I8; + result->src[0] = q_i8; + result->src[1] = k_i8; + result->src[2] = v; + result->src[3] = q_scale; + result->src[4] = k_scale; + + return result; +} + +struct ggml_tensor * ggml_convrot_linear( + struct ggml_context * ctx, + struct ggml_tensor * weight_i8, + struct ggml_tensor * input, + struct ggml_tensor * weight_scale, + struct ggml_tensor * bias, + int group_size) { + GGML_ASSERT(weight_i8->type == GGML_TYPE_I8); + GGML_ASSERT(input->type == GGML_TYPE_F32); + GGML_ASSERT(weight_scale->type == GGML_TYPE_F32); + GGML_ASSERT(bias == NULL || bias->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_contiguous(weight_i8)); + GGML_ASSERT(ggml_is_contiguous(input)); + GGML_ASSERT(ggml_is_contiguous(weight_scale)); + GGML_ASSERT(bias == NULL || ggml_is_contiguous(bias)); + GGML_ASSERT(group_size > 0); + + const int64_t in_features = weight_i8->ne[0]; + const int64_t out_features = weight_i8->ne[1]; + GGML_ASSERT(input->ne[0] == in_features); + GGML_ASSERT(in_features % group_size == 0); + GGML_ASSERT(ggml_nelements(weight_scale) == out_features); + GGML_ASSERT(bias == NULL || bias->ne[0] == out_features); + + int64_t ne[4] = { out_features, input->ne[1], input->ne[2], input->ne[3] }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, ggml_n_dims(input), ne); + + ggml_set_op_params_i32(result, 0, group_size); + + result->op = GGML_OP_CONVROT_LINEAR; + result->src[0] = weight_i8; + result->src[1] = input; + result->src[2] = weight_scale; + result->src[3] = bias; + + return result; +} + +// VibeASR CPU INT8 pipeline +// +// Five fused ops that keep an activation chain entirely in GGML_TYPE_I8_S: each +// one consumes int8 with a per-tensor scale, does its arithmetic in int32/F32, +// and re-quantizes its own output to int8 with a freshly measured scale. Fusing +// matters because the requantization needs the output absmax, so an unfused +// chain would have to write, read back and rescan every intermediate. +// +// That per-tensor output scale is why these need GGML_TYPE_I8_S rather than the +// GGML_TYPE_I8-plus-separate-scale-tensor pair used by ggml_convrot_linear +// above: a scale computed from the op's own output has nowhere to live but with +// the data, since a node has exactly one output tensor. + +struct ggml_tensor * ggml_add_scaled( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * scale) { + GGML_ASSERT(ggml_are_same_shape(a, b)); + GGML_ASSERT(scale->type == GGML_TYPE_F32); + // Broadcast along ne[0], the channel axis: one LayerScale coefficient per + // channel, applied to every position. + GGML_ASSERT(scale->ne[0] == a->ne[0]); + + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + result->op = GGML_OP_ADD_SCALED; + result->src[0] = a; + result->src[1] = b; + result->src[2] = scale; + + return result; +} + +struct ggml_tensor * ggml_rms_norm_scaled( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * scale, + float eps) { + GGML_ASSERT(a->type == GGML_TYPE_I8_S); + GGML_ASSERT(scale->type == GGML_TYPE_F32); + GGML_ASSERT(scale->ne[0] == a->ne[0]); + GGML_ASSERT(eps > 0.0f); + + struct ggml_tensor * result = ggml_dup_tensor(ctx, a); + + ggml_set_op_params(result, &eps, sizeof(eps)); + + result->op = GGML_OP_RMS_NORM_SCALED; + result->src[0] = a; + result->src[1] = scale; + + return result; +} + +// Shared by ggml_mul_mat_add and ggml_mul_mat_add_relu: identical shape rules, +// only the epilogue differs. +static struct ggml_tensor * ggml_mul_mat_add_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * bias, + enum ggml_op op) { + GGML_ASSERT(ggml_can_mul_mat(a, b)); + // I8_S weights only. The ternary I2_S weights of the language model go + // through plain ggml_mul_mat, which has no bias to fuse. + GGML_ASSERT(a->type == GGML_TYPE_I8_S); + GGML_ASSERT(b->type == GGML_TYPE_I8_S); + GGML_ASSERT(bias->type == GGML_TYPE_F32); + + // Bias is per output channel, and which ne holds the output channels + // depends on the shape of a: a [IC, OC] is an ordinary matmul, whereas + // a [K, 1, C] is the depthwise case where the channels sit in ne[2]. + GGML_ASSERT(bias->ne[0] == (a->ne[1] == 1 && a->ne[2] > 1 ? a->ne[2] : a->ne[1])); + + const int64_t ne[4] = { a->ne[1], b->ne[1], b->ne[2], b->ne[3] }; + + // Output is int8 with its own scale, so the chain continues without a + // detour through F32. + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_I8_S, 4, ne); + + result->op = op; + result->src[0] = a; + result->src[1] = b; + result->src[2] = bias; + + return result; +} + +struct ggml_tensor * ggml_mul_mat_add( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * bias) { + return ggml_mul_mat_add_impl(ctx, a, b, bias, GGML_OP_MUL_MAT_ADD); +} + +struct ggml_tensor * ggml_mul_mat_add_relu( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * bias) { + return ggml_mul_mat_add_impl(ctx, a, b, bias, GGML_OP_MUL_MAT_ADD_RELU); +} + +struct ggml_tensor * ggml_im2col_asym( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + int s0, + int s1, + int lp0, + int rp0, + int p1, + int d0, + int d1, + bool is_2D, + enum ggml_type dst_type) { + if (is_2D) { + GGML_ASSERT(a->ne[2] == b->ne[2]); + } else { + GGML_ASSERT(a->ne[1] == b->ne[1]); + GGML_ASSERT(b->ne[3] == 1); + } + + const int64_t OH = is_2D ? ggml_calc_conv_output_size(b->ne[1], a->ne[1], s1, p1, d1) : 0; + // Same formula as ggml_calc_conv_output_size but with the two width pads + // counted separately instead of as 2*p0. + const int64_t OW = (b->ne[0] + lp0 + rp0 - d0 * (a->ne[0] - 1) - 1) / s0 + 1; + + GGML_ASSERT((!is_2D || OH > 0) && "b too small compared to a"); + GGML_ASSERT((OW > 0) && "b too small compared to a"); + + const int64_t ne[4] = { + is_2D ? (a->ne[2] * a->ne[1] * a->ne[0]) : a->ne[1] * a->ne[0], + OW, + is_2D ? OH : b->ne[2], + is_2D ? b->ne[3] : 1, + }; + + struct ggml_tensor * result = ggml_new_tensor(ctx, dst_type, 4, ne); + + // Note the order: lp0 and rp0 occupy slots 2 and 3, where GGML_OP_IM2COL + // keeps p0 and p1. The two ops read their own params and never share a + // forward, so the layouts do not have to agree. + int32_t params[] = { s0, s1, lp0, rp0, d0, d1, (is_2D ? 1 : 0), p1 }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_IM2COL_ASYM; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_flash_attn_ext_with_bias_mask( + struct ggml_context * ctx, + struct ggml_tensor * q, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * bias, + struct ggml_tensor * mask, + float scale, + float max_bias, + float logit_softcap) { + GGML_ASSERT(bias); + GGML_ASSERT(q && k && v); + GGML_ASSERT(q->ne[1] == bias->ne[1]); + GGML_ASSERT(k->ne[1] == bias->ne[0]); + GGML_ASSERT(q->ne[2] % bias->ne[2] == 0); + GGML_ASSERT(q->ne[3] % bias->ne[3] == 0); + if (mask) { + GGML_ASSERT(mask->type == GGML_TYPE_F16 || mask->type == GGML_TYPE_F32); + GGML_ASSERT(mask->ne[0] == bias->ne[0]); + GGML_ASSERT(mask->ne[1] == bias->ne[1]); + GGML_ASSERT(bias->ne[2] % mask->ne[2] == 0); + GGML_ASSERT(bias->ne[3] % mask->ne[3] == 0); + } + + // MINITTS_FLASH_BIAS_WRAPPER: + // Relative-position scores are added to QK before the common softmax scale + // in the reference path. Flash attention applies scale only to QK, so + // pre-scale the dense bias before folding it into the additive mask. + if (!ggml_is_contiguous(bias)) { + bias = ggml_cont(ctx, bias); + } + + struct ggml_tensor * effective_mask = ggml_scale(ctx, bias, scale); + if (mask) { + if (!ggml_are_same_shape(mask, effective_mask)) { + mask = ggml_repeat(ctx, mask, effective_mask); + } + effective_mask = ggml_add(ctx, effective_mask, mask); + } + + // MINITTS_FLASH_BIAS_WRAPPER: + // The flash-attention op expects a contiguous F16 additive mask. The + // relative-position branch already materializes the per-head bias in F32, + // so normalize that here before dispatching to the existing kernel. + if (!ggml_is_contiguous(effective_mask)) { + effective_mask = ggml_cont(ctx, effective_mask); + } + if (effective_mask->type != GGML_TYPE_F16) { + effective_mask = ggml_cast(ctx, effective_mask, GGML_TYPE_F16); + } + if (!ggml_is_contiguous(effective_mask)) { + effective_mask = ggml_cont(ctx, effective_mask); + } + + return ggml_flash_attn_ext( + ctx, + q, + k, + v, + effective_mask, + scale, + max_bias, + logit_softcap); +} + +void ggml_flash_attn_ext_set_prec( + struct ggml_tensor * a, + enum ggml_prec prec) { + GGML_ASSERT(a->op == GGML_OP_FLASH_ATTN_EXT); + + const int32_t prec_i32 = (int32_t) prec; + + ggml_set_op_params_i32(a, 3, prec_i32); // scale is on first pos, max_bias on second +} + +enum ggml_prec ggml_flash_attn_ext_get_prec( + const struct ggml_tensor * a) { + GGML_ASSERT(a->op == GGML_OP_FLASH_ATTN_EXT); + + const int32_t prec_i32 = ggml_get_op_params_i32(a, 3); + + return (enum ggml_prec) prec_i32; +} + +void ggml_flash_attn_ext_add_sinks( + struct ggml_tensor * a, + struct ggml_tensor * sinks) { + if (!sinks) { + a->src[4] = NULL; + return; + } + + GGML_ASSERT(a->op == GGML_OP_FLASH_ATTN_EXT); + GGML_ASSERT(a->src[4] == NULL); + GGML_ASSERT(a->src[0]->ne[2] == sinks->ne[0]); + GGML_ASSERT(sinks->type == GGML_TYPE_F32); + + a->src[4] = sinks; +} + +// ggml_flash_attn_back + +struct ggml_tensor * ggml_flash_attn_back( + struct ggml_context * ctx, + struct ggml_tensor * q, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * d, + bool masked) { + GGML_ABORT("TODO: adapt to ggml_flash_attn_ext() changes"); + + GGML_ASSERT(ggml_can_mul_mat(k, q)); + // TODO: check if vT can be multiplied by (k*qT) + + // d shape [D,N,ne2,ne3] + // q shape [D,N,ne2,ne3] + // k shape [D,M,kvne2,ne3] + // v shape [M,D,kvne2,ne3] + + const int64_t D = q->ne[0]; + const int64_t N = q->ne[1]; + const int64_t M = k->ne[1]; + const int64_t ne2 = q->ne[2]; + const int64_t ne3 = q->ne[3]; + const int64_t kvne2 = k->ne[2]; + + GGML_ASSERT(k->ne[0] == D); + GGML_ASSERT(v->ne[0] == M); + GGML_ASSERT(v->ne[1] == D); + GGML_ASSERT(d->ne[0] == D); + GGML_ASSERT(d->ne[1] == N); + GGML_ASSERT(k->ne[2] == kvne2); + GGML_ASSERT(k->ne[3] == ne3); + GGML_ASSERT(v->ne[2] == kvne2); + GGML_ASSERT(v->ne[3] == ne3); + GGML_ASSERT(d->ne[2] == ne2); + GGML_ASSERT(d->ne[3] == ne3); + + GGML_ASSERT(ne2 % kvne2 == 0); + + // store gradients of q, k and v as continuous tensors concatenated in result. + // note: v and gradv are actually transposed, i.e. v->ne[0] != D. + const int64_t elem_q = ggml_nelements(q); + const int64_t elem_k = ggml_nelements(k); + const int64_t elem_v = ggml_nelements(v); + + enum ggml_type result_type = GGML_TYPE_F32; + GGML_ASSERT(ggml_blck_size(result_type) == 1); + const size_t tsize = ggml_type_size(result_type); + + const size_t offs_q = 0; + const size_t offs_k = offs_q + GGML_PAD(elem_q * tsize, GGML_MEM_ALIGN); + const size_t offs_v = offs_k + GGML_PAD(elem_k * tsize, GGML_MEM_ALIGN); + const size_t end = offs_v + GGML_PAD(elem_v * tsize, GGML_MEM_ALIGN); + + const size_t nelements = (end + tsize - 1)/tsize; + + struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, nelements); + + int32_t masked_i = masked ? 1 : 0; + ggml_set_op_params(result, &masked_i, sizeof(masked_i)); + + result->op = GGML_OP_FLASH_ATTN_BACK; + result->src[0] = q; + result->src[1] = k; + result->src[2] = v; + result->src[3] = d; + + return result; +} + +// ggml_ssm_conv + +struct ggml_tensor * ggml_ssm_conv( + struct ggml_context * ctx, + struct ggml_tensor * sx, + struct ggml_tensor * c) { + GGML_ASSERT(ggml_is_3d(sx)); + GGML_ASSERT(ggml_is_matrix(c)); + + const int64_t d_conv = c->ne[0]; + const int64_t d_inner = c->ne[1]; + const int64_t n_t = sx->ne[0] - d_conv + 1; // tokens per sequence + const int64_t n_s = sx->ne[2]; + + // TODO: maybe support other strides than 1? + GGML_ASSERT(sx->ne[0] == d_conv - 1 + n_t); + GGML_ASSERT(sx->ne[1] == d_inner); + GGML_ASSERT(n_t >= 0); + + struct ggml_tensor * result = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, d_inner, n_t, n_s); + + result->op = GGML_OP_SSM_CONV; + result->src[0] = sx; + result->src[1] = c; + + return result; +} + +// ggml_ssm_scan + +struct ggml_tensor * ggml_ssm_scan( + struct ggml_context * ctx, + struct ggml_tensor * s, + struct ggml_tensor * x, + struct ggml_tensor * dt, + struct ggml_tensor * A, + struct ggml_tensor * B, + struct ggml_tensor * C, + struct ggml_tensor * ids) { + GGML_ASSERT(ggml_is_contiguous(s)); + GGML_ASSERT(ggml_is_contiguous(dt)); + GGML_ASSERT(ggml_is_contiguous(A)); + GGML_ASSERT(x->nb[0] == ggml_type_size(x->type)); + GGML_ASSERT(B->nb[0] == ggml_type_size(B->type)); + GGML_ASSERT(C->nb[0] == ggml_type_size(C->type)); + GGML_ASSERT(x->nb[1] == x->ne[0]*x->nb[0]); + GGML_ASSERT(B->nb[1] == B->ne[0]*B->nb[0]); + GGML_ASSERT(C->nb[1] == C->ne[0]*C->nb[0]); + GGML_ASSERT(ggml_are_same_shape(B, C)); + GGML_ASSERT(ids->type == GGML_TYPE_I32); + + { + const int64_t d_state = s->ne[0]; + const int64_t head_dim = x->ne[0]; + const int64_t n_head = x->ne[1]; + const int64_t n_seq_tokens = x->ne[2]; + const int64_t n_seqs = x->ne[3]; + + GGML_ASSERT(dt->ne[0] == n_head); + GGML_ASSERT(dt->ne[1] == n_seq_tokens); + GGML_ASSERT(dt->ne[2] == n_seqs); + GGML_ASSERT(ggml_is_3d(dt)); + GGML_ASSERT(s->ne[1] == head_dim); + GGML_ASSERT(s->ne[2] == n_head); + GGML_ASSERT(B->ne[0] == d_state); + GGML_ASSERT(B->ne[2] == n_seq_tokens); + GGML_ASSERT(B->ne[3] == n_seqs); + GGML_ASSERT(ids->ne[0] == n_seqs); + GGML_ASSERT(ggml_is_vector(ids)); + GGML_ASSERT(A->ne[1] == n_head); + GGML_ASSERT(ggml_is_matrix(A)); + + if (A->ne[0] != 1) { + // Mamba-1 has more granular decay factors + GGML_ASSERT(A->ne[0] == d_state); + } + } + + // concatenated y + ssm_states + struct ggml_tensor * result = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, ggml_nelements(x) + s->ne[0]*s->ne[1]*s->ne[2]*ids->ne[0]); + + result->op = GGML_OP_SSM_SCAN; + result->src[0] = s; + result->src[1] = x; + result->src[2] = dt; + result->src[3] = A; + result->src[4] = B; + result->src[5] = C; + result->src[6] = ids; + + return result; +} + +// ggml_win_part + +struct ggml_tensor * ggml_win_part( + struct ggml_context * ctx, + struct ggml_tensor * a, + int w) { + GGML_ASSERT(a->ne[3] == 1); + GGML_ASSERT(a->type == GGML_TYPE_F32); + + // padding + const int px = (w - a->ne[1]%w)%w; + const int py = (w - a->ne[2]%w)%w; + + const int npx = (px + a->ne[1])/w; + const int npy = (py + a->ne[2])/w; + const int np = npx*npy; + + const int64_t ne[4] = { a->ne[0], w, w, np, }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + int32_t params[] = { npx, npy, w }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_WIN_PART; + result->src[0] = a; + + return result; +} + +// ggml_win_unpart + +struct ggml_tensor * ggml_win_unpart( + struct ggml_context * ctx, + struct ggml_tensor * a, + int w0, + int h0, + int w) { + GGML_ASSERT(a->type == GGML_TYPE_F32); + + const int64_t ne[4] = { a->ne[0], w0, h0, 1, }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 3, ne); + + int32_t params[] = { w }; + ggml_set_op_params(result, params, sizeof(params)); + + result->op = GGML_OP_WIN_UNPART; + result->src[0] = a; + + return result; +} + +// ggml_get_rel_pos + +struct ggml_tensor * ggml_get_rel_pos( + struct ggml_context * ctx, + struct ggml_tensor * a, + int qh, + int kh) { + GGML_ASSERT(qh == kh); + GGML_ASSERT(2*MAX(qh, kh) - 1 == a->ne[1]); + + const int64_t ne[4] = { a->ne[0], kh, qh, 1, }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F16, 3, ne); + + result->op = GGML_OP_GET_REL_POS; + result->src[0] = a; + + return result; +} + +// ggml_add_rel_pos + +static struct ggml_tensor * ggml_add_rel_pos_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * pw, + struct ggml_tensor * ph, + bool inplace) { + GGML_ASSERT(ggml_are_same_shape(pw, ph)); + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(ggml_is_contiguous(pw)); + GGML_ASSERT(ggml_is_contiguous(ph)); + GGML_ASSERT(ph->type == GGML_TYPE_F32); + GGML_ASSERT(pw->type == GGML_TYPE_F32); + GGML_ASSERT(pw->ne[3] == a->ne[2]); + GGML_ASSERT(pw->ne[0]*pw->ne[0] == a->ne[0]); + GGML_ASSERT(pw->ne[1]*pw->ne[2] == a->ne[1]); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + ggml_set_op_params_i32(result, 0, inplace ? 1 : 0); + + result->op = GGML_OP_ADD_REL_POS; + result->src[0] = a; + result->src[1] = pw; + result->src[2] = ph; + + return result; +} + +struct ggml_tensor * ggml_add_rel_pos( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * pw, + struct ggml_tensor * ph) { + return ggml_add_rel_pos_impl(ctx, a, pw, ph, false); +} + +struct ggml_tensor * ggml_add_rel_pos_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * pw, + struct ggml_tensor * ph) { + return ggml_add_rel_pos_impl(ctx, a, pw, ph, true); +} + +// ggml_rwkv_wkv6 + +struct ggml_tensor * ggml_rwkv_wkv6( + struct ggml_context * ctx, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * r, + struct ggml_tensor * tf, + struct ggml_tensor * td, + struct ggml_tensor * state) { + GGML_ASSERT(ggml_is_contiguous(k)); + GGML_ASSERT(ggml_is_contiguous(v)); + GGML_ASSERT(ggml_is_contiguous(r)); + GGML_ASSERT(ggml_is_contiguous(tf)); + GGML_ASSERT(ggml_is_contiguous(td)); + GGML_ASSERT(ggml_is_contiguous(state)); + + const int64_t S = k->ne[0]; + const int64_t H = k->ne[1]; + const int64_t n_tokens = k->ne[2]; + const int64_t n_seqs = state->ne[1]; + { + GGML_ASSERT(v->ne[0] == S && v->ne[1] == H && v->ne[2] == n_tokens); + GGML_ASSERT(r->ne[0] == S && r->ne[1] == H && r->ne[2] == n_tokens); + GGML_ASSERT(td->ne[0] == S && td->ne[1] == H && td->ne[2] == n_tokens); + GGML_ASSERT(ggml_nelements(state) == S * S * H * n_seqs); + } + + // concat output and new_state + const int64_t ne[4] = { S * H, n_tokens + S * n_seqs, 1, 1 }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + result->op = GGML_OP_RWKV_WKV6; + result->src[0] = k; + result->src[1] = v; + result->src[2] = r; + result->src[3] = tf; + result->src[4] = td; + result->src[5] = state; + + return result; +} + +// ggml_gated_linear_attn + +struct ggml_tensor * ggml_gated_linear_attn( + struct ggml_context * ctx, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * q, + struct ggml_tensor * g, + struct ggml_tensor * state, + float scale) { + GGML_ASSERT(ggml_is_contiguous(k)); + GGML_ASSERT(ggml_is_contiguous(v)); + GGML_ASSERT(ggml_is_contiguous(q)); + GGML_ASSERT(ggml_is_contiguous(g)); + GGML_ASSERT(ggml_is_contiguous(state)); + + const int64_t S = k->ne[0]; + const int64_t H = k->ne[1]; + const int64_t n_tokens = k->ne[2]; + const int64_t n_seqs = state->ne[1]; + { + GGML_ASSERT(v->ne[0] == S && v->ne[1] == H && v->ne[2] == n_tokens); + GGML_ASSERT(q->ne[0] == S && q->ne[1] == H && q->ne[2] == n_tokens); + GGML_ASSERT(g->ne[0] == S && g->ne[1] == H && g->ne[2] == n_tokens); + GGML_ASSERT(ggml_nelements(state) == S * S * H * n_seqs); + } + + // concat output and new_state + const int64_t ne[4] = { S * H, n_tokens + S * n_seqs, 1, 1 }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + ggml_set_op_params_f32(result, 0, scale); + + result->op = GGML_OP_GATED_LINEAR_ATTN; + result->src[0] = k; + result->src[1] = v; + result->src[2] = q; + result->src[3] = g; + result->src[4] = state; + + return result; +} + +// ggml_rwkv_wkv7 + +struct ggml_tensor * ggml_rwkv_wkv7( + struct ggml_context * ctx, + struct ggml_tensor * r, + struct ggml_tensor * w, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * state) { + GGML_ASSERT(ggml_is_contiguous(r)); + GGML_ASSERT(ggml_is_contiguous(w)); + GGML_ASSERT(ggml_is_contiguous(k)); + GGML_ASSERT(ggml_is_contiguous(v)); + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(ggml_is_contiguous(b)); + GGML_ASSERT(ggml_is_contiguous(state)); + + const int64_t S = k->ne[0]; + const int64_t H = k->ne[1]; + const int64_t n_tokens = k->ne[2]; + const int64_t n_seqs = state->ne[1]; + { + GGML_ASSERT(w->ne[0] == S && w->ne[1] == H && w->ne[2] == n_tokens); + GGML_ASSERT(k->ne[0] == S && k->ne[1] == H && k->ne[2] == n_tokens); + GGML_ASSERT(v->ne[0] == S && v->ne[1] == H && v->ne[2] == n_tokens); + GGML_ASSERT(a->ne[0] == S && a->ne[1] == H && a->ne[2] == n_tokens); + GGML_ASSERT(b->ne[0] == S && b->ne[1] == H && b->ne[2] == n_tokens); + GGML_ASSERT(ggml_nelements(state) == S * S * H * n_seqs); + } + + // concat output and new_state + const int64_t ne[4] = { S * H, n_tokens + S * n_seqs, 1, 1 }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + result->op = GGML_OP_RWKV_WKV7; + result->src[0] = r; + result->src[1] = w; + result->src[2] = k; + result->src[3] = v; + result->src[4] = a; + result->src[5] = b; + result->src[6] = state; + + return result; +} + +// ggml_unary + +static struct ggml_tensor * ggml_unary_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_unary_op op, + bool inplace) { + GGML_ASSERT(ggml_is_contiguous_rows(a)); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + ggml_set_op_params_i32(result, 0, (int32_t) op); + + result->op = GGML_OP_UNARY; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_unary( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_unary_op op) { + return ggml_unary_impl(ctx, a, op, false); +} + +struct ggml_tensor * ggml_unary_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + enum ggml_unary_op op) { + return ggml_unary_impl(ctx, a, op, true); +} + +// ggml_map_custom1 + +static struct ggml_tensor * ggml_map_custom1_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + const ggml_custom1_op_t fun, + int n_tasks, + void * userdata, + bool inplace) { + GGML_ASSERT(n_tasks == GGML_N_TASKS_MAX || n_tasks > 0); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + struct ggml_map_custom1_op_params params = { + /*.fun =*/ fun, + /*.n_tasks =*/ n_tasks, + /*.userdata =*/ userdata + }; + ggml_set_op_params(result, ¶ms, sizeof(params)); + + result->op = GGML_OP_MAP_CUSTOM1; + result->src[0] = a; + + return result; +} + +struct ggml_tensor * ggml_map_custom1( + struct ggml_context * ctx, + struct ggml_tensor * a, + const ggml_custom1_op_t fun, + int n_tasks, + void * userdata) { + return ggml_map_custom1_impl(ctx, a, fun, n_tasks, userdata, false); +} + +struct ggml_tensor * ggml_map_custom1_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + const ggml_custom1_op_t fun, + int n_tasks, + void * userdata) { + return ggml_map_custom1_impl(ctx, a, fun, n_tasks, userdata, true); +} + +// ggml_map_custom2 + +static struct ggml_tensor * ggml_map_custom2_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + const ggml_custom2_op_t fun, + int n_tasks, + void * userdata, + bool inplace) { + GGML_ASSERT(n_tasks == GGML_N_TASKS_MAX || n_tasks > 0); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + struct ggml_map_custom2_op_params params = { + /*.fun =*/ fun, + /*.n_tasks =*/ n_tasks, + /*.userdata =*/ userdata + }; + ggml_set_op_params(result, ¶ms, sizeof(params)); + + result->op = GGML_OP_MAP_CUSTOM2; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +struct ggml_tensor * ggml_map_custom2( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + const ggml_custom2_op_t fun, + int n_tasks, + void * userdata) { + return ggml_map_custom2_impl(ctx, a, b, fun, n_tasks, userdata, false); +} + +struct ggml_tensor * ggml_map_custom2_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + const ggml_custom2_op_t fun, + int n_tasks, + void * userdata) { + return ggml_map_custom2_impl(ctx, a, b, fun, n_tasks, userdata, true); +} + +// ggml_map_custom3 + +static struct ggml_tensor * ggml_map_custom3_impl( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + const ggml_custom3_op_t fun, + int n_tasks, + void * userdata, + bool inplace) { + GGML_ASSERT(n_tasks == GGML_N_TASKS_MAX || n_tasks > 0); + + struct ggml_tensor * result = inplace ? ggml_view_tensor(ctx, a) : ggml_dup_tensor(ctx, a); + + struct ggml_map_custom3_op_params params = { + /*.fun =*/ fun, + /*.n_tasks =*/ n_tasks, + /*.userdata =*/ userdata + }; + ggml_set_op_params(result, ¶ms, sizeof(params)); + + result->op = GGML_OP_MAP_CUSTOM3; + result->src[0] = a; + result->src[1] = b; + result->src[2] = c; + + return result; +} + +struct ggml_tensor * ggml_map_custom3( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + const ggml_custom3_op_t fun, + int n_tasks, + void * userdata) { + return ggml_map_custom3_impl(ctx, a, b, c, fun, n_tasks, userdata, false); +} + +struct ggml_tensor * ggml_map_custom3_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c, + const ggml_custom3_op_t fun, + int n_tasks, + void * userdata) { + return ggml_map_custom3_impl(ctx, a, b, c, fun, n_tasks, userdata, true); +} + +struct ggml_tensor * ggml_custom_4d( + struct ggml_context * ctx, + enum ggml_type type, + int64_t ne0, + int64_t ne1, + int64_t ne2, + int64_t ne3, + struct ggml_tensor ** args, + int n_args, + ggml_custom_op_t fun, + int n_tasks, + void * userdata) { + + GGML_ASSERT(n_args < GGML_MAX_SRC); + + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, type, ne0, ne1, ne2, ne3); + + struct ggml_custom_op_params params = { + /*.fun =*/ fun, + /*.n_tasks =*/ n_tasks, + /*.userdata =*/ userdata + }; + ggml_set_op_params(result, ¶ms, sizeof(params)); + + result->op = GGML_OP_CUSTOM; + for (int i = 0; i < n_args; i++) { + result->src[i] = args[i]; + } + + return result; +} + +struct ggml_tensor * ggml_custom_inplace( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor ** args, + int n_args, + ggml_custom_op_t fun, + int n_tasks, + void * userdata) { + + GGML_ASSERT(n_args < GGML_MAX_SRC - 1); + + struct ggml_tensor * result = ggml_view_tensor(ctx, a); + + struct ggml_custom_op_params params = { + /*.fun =*/ fun, + /*.n_tasks =*/ n_tasks, + /*.userdata =*/ userdata + }; + ggml_set_op_params(result, ¶ms, sizeof(params)); + + result->op = GGML_OP_CUSTOM; + result->src[0] = a; + for (int i = 0; i < n_args; i++) { + result->src[i + 1] = args[i]; + } + + return result; +} +// ggml_cross_entropy_loss + +struct ggml_tensor * ggml_cross_entropy_loss( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b) { + GGML_ASSERT(ggml_are_same_shape(a, b)); + + struct ggml_tensor * result = ggml_new_tensor_1d(ctx, a->type, 1); + + result->op = GGML_OP_CROSS_ENTROPY_LOSS; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_cross_entropy_loss_back + +struct ggml_tensor * ggml_cross_entropy_loss_back( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + struct ggml_tensor * c) { + GGML_ASSERT(ggml_is_scalar(a)); + GGML_ASSERT(ggml_are_same_shape(b, c)); + + struct ggml_tensor * result = ggml_dup_tensor(ctx, b); + + result->op = GGML_OP_CROSS_ENTROPY_LOSS_BACK; + result->src[0] = a; + result->src[1] = b; + result->src[2] = c; + + return result; +} + +// opt_step_adamw + +struct ggml_tensor * ggml_opt_step_adamw( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * grad, + struct ggml_tensor * m, + struct ggml_tensor * v, + struct ggml_tensor * adamw_params) { + GGML_ASSERT(a->flags & GGML_TENSOR_FLAG_PARAM); + GGML_ASSERT(ggml_are_same_shape(a, grad)); + GGML_ASSERT(ggml_are_same_shape(a, m)); + GGML_ASSERT(ggml_are_same_shape(a, v)); + GGML_ASSERT(adamw_params->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_nelements(adamw_params) == 7); + + struct ggml_tensor * result = ggml_view_tensor(ctx, a); + + result->op = GGML_OP_OPT_STEP_ADAMW; + result->src[0] = a; + result->src[1] = grad; + result->src[2] = m; + result->src[3] = v; + result->src[4] = adamw_params; + + return result; +} + +// opt_step_sgd + +struct ggml_tensor * ggml_opt_step_sgd( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * grad, + struct ggml_tensor * params) { + GGML_ASSERT(a->flags & GGML_TENSOR_FLAG_PARAM); + GGML_ASSERT(ggml_are_same_shape(a, grad)); + GGML_ASSERT(params->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_nelements(params) == 2); + + struct ggml_tensor * result = ggml_view_tensor(ctx, a); + + result->op = GGML_OP_OPT_STEP_SGD; + result->src[0] = a; + result->src[1] = grad; + result->src[2] = params; + + return result; +} + +// solve_tri + +struct ggml_tensor * ggml_solve_tri( + struct ggml_context * ctx, + struct ggml_tensor * a, + struct ggml_tensor * b, + bool left, + bool lower, + bool uni) { + GGML_ASSERT(a->type == GGML_TYPE_F32); + GGML_ASSERT(b->type == GGML_TYPE_F32); + + // A must be square and lower diagonal + GGML_ASSERT(a->ne[0] == a->ne[1]); + // B must have same outer dimension as A + GGML_ASSERT(a->ne[1] == b->ne[1]); + + // batch dimensions must be equal + GGML_ASSERT(a->ne[2] == b->ne[2]); + GGML_ASSERT(a->ne[3] == b->ne[3]); + + GGML_ASSERT(ggml_is_contiguous(a)); + GGML_ASSERT(ggml_is_contiguous(b)); + + GGML_ASSERT(lower && left && !uni); // TODO: support other variants + + struct ggml_tensor * result = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, b->ne[0], b->ne[1], b->ne[2], b->ne[3]); + + result->op = GGML_OP_SOLVE_TRI; + result->src[0] = a; + result->src[1] = b; + + return result; +} + +// ggml_gated_delta_net + +struct ggml_tensor * ggml_gated_delta_net( + struct ggml_context * ctx, + struct ggml_tensor * q, + struct ggml_tensor * k, + struct ggml_tensor * v, + struct ggml_tensor * g, + struct ggml_tensor * beta, + struct ggml_tensor * state) { + GGML_ASSERT(ggml_is_contiguous_rows(q)); + GGML_ASSERT(ggml_is_contiguous_rows(k)); + GGML_ASSERT(ggml_is_contiguous_rows(v)); + GGML_ASSERT(ggml_is_contiguous(g)); + GGML_ASSERT(ggml_is_contiguous(beta)); + GGML_ASSERT(ggml_is_contiguous(state)); + + GGML_ASSERT(q->type == GGML_TYPE_F32); + GGML_ASSERT(k->type == GGML_TYPE_F32); + GGML_ASSERT(v->type == GGML_TYPE_F32); + GGML_ASSERT(g->type == GGML_TYPE_F32); + GGML_ASSERT(beta->type == GGML_TYPE_F32); + GGML_ASSERT(state->type == GGML_TYPE_F32); + + const int64_t S_v = v->ne[0]; + const int64_t H = v->ne[1]; + const int64_t n_tokens = v->ne[2]; + const int64_t n_seqs = v->ne[3]; + + // gate: scalar [1, H, T, B] or vector [S_v, H, T, B] (KDA) + GGML_ASSERT(g->ne[0] == 1 || g->ne[0] == S_v); + GGML_ASSERT(beta->ne[0] == 1); + + // state is a 3D tensor (S_v*S_v*H, K, n_seqs). K is the snapshot slot count. + GGML_ASSERT(state->ne[0] == S_v * S_v * H); + GGML_ASSERT(state->ne[2] == n_seqs); + GGML_ASSERT(state->ne[3] == 1); + const int64_t K = state->ne[1]; + const int64_t state_rows = K * S_v * n_seqs; + const int64_t ne[4] = { S_v * H, n_tokens * n_seqs + state_rows, 1, 1 }; + struct ggml_tensor * result = ggml_new_tensor(ctx, GGML_TYPE_F32, 4, ne); + + result->op = GGML_OP_GATED_DELTA_NET; + result->src[0] = q; + result->src[1] = k; + result->src[2] = v; + result->src[3] = g; + result->src[4] = beta; + result->src[5] = state; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +struct ggml_hash_set ggml_hash_set_new(size_t size) { + size = ggml_hash_size(size); + struct ggml_hash_set result; + result.size = size; + result.keys = GGML_MALLOC(sizeof(struct ggml_tensor *) * size); + result.used = GGML_CALLOC(ggml_bitset_size(size), sizeof(ggml_bitset_t)); + return result; +} + +void ggml_hash_set_reset(struct ggml_hash_set * hash_set) { + memset(hash_set->used, 0, sizeof(ggml_bitset_t) * ggml_bitset_size(hash_set->size)); +} + +void ggml_hash_set_free(struct ggml_hash_set * hash_set) { + GGML_FREE(hash_set->used); + GGML_FREE(hash_set->keys); +} + +size_t ggml_hash_size(size_t min_sz) { + // next primes after powers of two + static const size_t primes[] = { + 2, 3, 5, 11, 17, 37, 67, 131, 257, 521, 1031, + 2053, 4099, 8209, 16411, 32771, 65537, 131101, + 262147, 524309, 1048583, 2097169, 4194319, 8388617, + 16777259, 33554467, 67108879, 134217757, 268435459, + 536870923, 1073741827, 2147483659 + }; + static const size_t n_primes = sizeof(primes)/sizeof(primes[0]); + + // find the smallest prime that is larger or equal than min_sz + size_t l = 0; + size_t r = n_primes; + while (l < r) { + size_t m = (l + r)/2; + if (primes[m] < min_sz) { + l = m + 1; + } else { + r = m; + } + } + size_t sz = l < n_primes ? primes[l] : min_sz | 1; + return sz; +} + +struct hash_map { + struct ggml_hash_set set; + struct ggml_tensor ** vals; +}; + +static struct hash_map * ggml_new_hash_map(size_t size) { + struct hash_map * result = GGML_MALLOC(sizeof(struct hash_map)); + result->set = ggml_hash_set_new(size); + result->vals = GGML_CALLOC(result->set.size, sizeof(struct ggml_tensor *)); + return result; +} + +static void ggml_hash_map_free(struct hash_map * map) { + ggml_hash_set_free(&map->set); + GGML_FREE(map->vals); + GGML_FREE(map); +} + +// utility functions to change gradients +// isrc is the index of tensor in cgraph->visited_has_set.keys +// the corresponding gradient (accumulators) are also at position isrc +// if tensor has a gradient accumulator, modify that accumulator in-place +// else if there is no gradient for tensor, set the corresponding value +// else, just add/subtract/etc. the gradients + +static void ggml_add_or_set( + struct ggml_context * ctx, + struct ggml_cgraph * cgraph, + size_t isrc, + struct ggml_tensor * tensor) { + struct ggml_tensor * src = cgraph->visited_hash_set.keys[isrc]; + GGML_ASSERT(src); + if (cgraph->grads[isrc]) { + cgraph->grads[isrc] = ggml_add_impl(ctx, cgraph->grads[isrc], tensor, /*inplace =*/ cgraph->grad_accs[isrc]); + } else { + cgraph->grads[isrc] = tensor; + } + ggml_format_name(cgraph->grads[isrc], "grad for %s", src->name); + ggml_build_forward_expand(cgraph, cgraph->grads[isrc]); +} + +static void ggml_acc_or_set( + struct ggml_context * ctx, + struct ggml_cgraph * cgraph, + size_t isrc, + struct ggml_tensor * tensor, + const size_t nb1, + const size_t nb2, + const size_t nb3, + const size_t offset) { + struct ggml_tensor * src = cgraph->visited_hash_set.keys[isrc]; + GGML_ASSERT(src); + if (cgraph->grads[isrc]) { + cgraph->grads[isrc] = ggml_acc_impl(ctx, cgraph->grads[isrc], tensor, nb1, nb2, nb3, offset, cgraph->grad_accs[isrc]); + } else { + struct ggml_tensor * a_zero = ggml_scale(ctx, src, 0.0f); // FIXME this is going to produce NaN if a contains inf/NaN + cgraph->grads[isrc] = ggml_acc_impl(ctx, a_zero, tensor, nb1, nb2, nb3, offset, false); + } + ggml_format_name(cgraph->grads[isrc], "grad for %s", cgraph->visited_hash_set.keys[isrc]->name); + ggml_build_forward_expand(cgraph, cgraph->grads[isrc]); +} + +static void ggml_add1_or_set( + struct ggml_context * ctx, + struct ggml_cgraph * cgraph, + size_t isrc, + struct ggml_tensor * tensor) { + struct ggml_tensor * src = cgraph->visited_hash_set.keys[isrc]; + GGML_ASSERT(src); + if (cgraph->grads[isrc]) { + cgraph->grads[isrc] = ggml_add1_impl(ctx, cgraph->grads[isrc], tensor, cgraph->grad_accs[isrc]); + } else { + cgraph->grads[isrc] = ggml_repeat(ctx, tensor, src); + } + ggml_format_name(cgraph->grads[isrc], "grad for %s", src->name); + ggml_build_forward_expand(cgraph, cgraph->grads[isrc]); +} + +static void ggml_sub_or_set( + struct ggml_context * ctx, + struct ggml_cgraph * cgraph, + size_t isrc, + struct ggml_tensor * tensor) { + struct ggml_tensor * src = cgraph->visited_hash_set.keys[isrc]; + GGML_ASSERT(src); + if (cgraph->grads[isrc]) { + cgraph->grads[isrc] = ggml_sub_impl(ctx, cgraph->grads[isrc], tensor, cgraph->grad_accs[isrc]); + } else { + cgraph->grads[isrc] = ggml_neg(ctx, tensor); + } + ggml_format_name(cgraph->grads[isrc], "grad for %s", src->name); + ggml_build_forward_expand(cgraph, cgraph->grads[isrc]); +} + +static void ggml_compute_backward( + struct ggml_context * ctx, struct ggml_cgraph * cgraph, int i, const bool * grads_needed) { + struct ggml_tensor * tensor = cgraph->nodes[i]; + struct ggml_tensor * grad = ggml_graph_get_grad(cgraph, tensor); + + if (!grad) { + return; + } + + struct ggml_tensor * src0 = tensor->src[0]; + struct ggml_tensor * src1 = tensor->src[1]; + struct ggml_tensor * src2 = tensor->src[2]; + struct ggml_hash_set * hash_set = &cgraph->visited_hash_set; + const size_t isrc0 = src0 ? ggml_hash_find(hash_set, src0) : (size_t) -1; + const size_t isrc1 = src1 ? ggml_hash_find(hash_set, src1) : (size_t) -1; + const size_t isrc2 = src2 ? ggml_hash_find(hash_set, src2) : (size_t) -1; + const bool src0_needs_grads = src0 && isrc0 != GGML_HASHSET_FULL && ggml_bitset_get(hash_set->used, isrc0) && grads_needed[isrc0]; + const bool src1_needs_grads = src1 && isrc1 != GGML_HASHSET_FULL && ggml_bitset_get(hash_set->used, isrc1) && grads_needed[isrc1]; + const bool src2_needs_grads = src2 && isrc2 != GGML_HASHSET_FULL && ggml_bitset_get(hash_set->used, isrc2) && grads_needed[isrc2]; + + switch (tensor->op) { + case GGML_OP_DUP: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, grad); + } + } break; + case GGML_OP_ADD: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, grad); + } + if (src1_needs_grads) { + struct ggml_tensor * tmp = grad; + if (!ggml_are_same_shape(src0, src1)) { + tmp = ggml_repeat_back(ctx, tmp, src1); + } + ggml_add_or_set(ctx, cgraph, isrc1, tmp); + } + } break; + case GGML_OP_ADD1: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, grad); + } + if (src1_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc1, ggml_mean(ctx, grad)); // TODO: should probably be sum instead of mean + } + } break; + case GGML_OP_ACC: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, grad); + } + if (src1_needs_grads) { + const size_t nb1 = ((int32_t *) tensor->op_params)[0]; + const size_t nb2 = ((int32_t *) tensor->op_params)[1]; + const size_t nb3 = ((int32_t *) tensor->op_params)[2]; + const size_t offset = ((int32_t *) tensor->op_params)[3]; + + struct ggml_tensor * tensor_grad_view = ggml_view_4d(ctx, + grad, src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3], + nb1, nb2, nb3, offset); + + ggml_add_or_set(ctx, cgraph, isrc1, ggml_reshape(ctx, ggml_cont(ctx, tensor_grad_view), src1)); + } + } break; + case GGML_OP_SUB: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, grad); + } + if (src1_needs_grads) { + ggml_sub_or_set(ctx, cgraph, isrc1, grad); + } + } break; + case GGML_OP_MUL: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, src1)); + } + if (src1_needs_grads) { + struct ggml_tensor * tmp = ggml_mul(ctx, src0, grad); + if (!ggml_are_same_shape(src0, src1)) { + tmp = ggml_repeat_back(ctx, tmp, src1); + } + ggml_add_or_set(ctx, cgraph, isrc1, tmp); + } + } break; + case GGML_OP_DIV: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_div(ctx, grad, src1)); + } + if (src1_needs_grads) { + ggml_sub_or_set(ctx, cgraph, isrc1, ggml_mul(ctx, grad, ggml_div(ctx, tensor, src1))); + } + } break; + case GGML_OP_SQR: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_scale(ctx, ggml_mul(ctx, src0, grad), 2.0f)); + } + } break; + case GGML_OP_SQRT: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_scale(ctx, ggml_div(ctx, grad, tensor), 0.5f)); + } + } break; + case GGML_OP_LOG: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_div(ctx, grad, src0)); + } + } break; + case GGML_OP_SIN: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, ggml_cos(ctx, src0))); + } + } break; + case GGML_OP_COS: { + if (src0_needs_grads) { + ggml_sub_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, ggml_sin(ctx, src0))); + } + } break; + case GGML_OP_SUM: { + if (src0_needs_grads) { + ggml_add1_or_set(ctx, cgraph, isrc0, grad); + } + } break; + case GGML_OP_SUM_ROWS: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_repeat(ctx, grad, src0)); + } + } break; + case GGML_OP_MEAN: { + if (src0_needs_grads) { + ggml_add1_or_set(ctx, cgraph, isrc0, ggml_scale_impl(ctx, grad, 1.0f/src0->ne[0], 0.0, false)); + } + } break; + case GGML_OP_REPEAT: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_repeat_back(ctx, grad, src0)); + } + } break; + case GGML_OP_REPEAT_BACK: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_repeat(ctx, grad, src0)); + } + } break; + case GGML_OP_RMS_NORM: { + if (src0_needs_grads) { + float eps; + memcpy(&eps, tensor->op_params, sizeof(float)); + ggml_add_or_set(ctx, cgraph, isrc0, ggml_rms_norm_back(ctx, grad, src0, eps)); + } + } break; + case GGML_OP_MUL_MAT: + case GGML_OP_MUL_MAT_PACK4: { + // https://cs231n.github.io/optimization-2/#staged + // # forward pass + // s0 = np.random.randn(5, 10) + // s1 = np.random.randn(10, 3) + // t = s0.dot(s1) + + // # now suppose we had the gradient on t from above in the circuit + // dt = np.random.randn(*t.shape) # same shape as t + // ds0 = dt.dot(s1.T) #.T gives the transpose of the matrix + // ds1 = t.T.dot(dt) + + // tensor.shape [m,p,qq,rr] + // src0.shape [n,m,q1,r1] + // src1.shape [n,p,qq,rr] + + if (src0_needs_grads) { + GGML_ASSERT(grad->ne[2] == src1->ne[2]); + GGML_ASSERT(grad->ne[3] == src1->ne[3]); + struct ggml_tensor * tmp = + ggml_out_prod(ctx, // [n,m,qq,rr] + src1, // [n,p,qq,rr] + grad); // [m,p,qq,rr] + if (!ggml_are_same_shape(tmp, src0)) { + GGML_ASSERT(tmp->ne[0] == src0->ne[0]); + GGML_ASSERT(tmp->ne[1] == src0->ne[1]); + GGML_ASSERT(tmp->ne[3] == 1); + + const int64_t nr2 = tmp->ne[2] / src0->ne[2]; + const size_t nb2 = tmp->nb[2] * nr2; + const size_t nb3 = tmp->nb[2]; + + tmp = ggml_view_4d(ctx, tmp, src0->ne[0], src0->ne[1], src0->ne[2], nr2, tmp->nb[1], nb2, nb3, 0); + tmp = ggml_repeat_back(ctx, tmp, src0); + } + ggml_add_or_set(ctx, cgraph, isrc0, tmp); + } + if (src1_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc1, + // ggml_mul_mat(ctx, // [n,p,qq,rr] + // ggml_cont(ctx, // [m,n,q1,r1] + // ggml_transpose(ctx, src0)), // [m,n,q1,r1] + // grad), // [m,p,qq,rr] + + // when src0 is bigger than tensor->grad (this is mostly the case in llama), + // avoid transpose of src0, rather transpose smaller tensor->grad + // and then use ggml_out_prod + ggml_out_prod(ctx, // [n,p,qq,rr] + src0, // [n,m,q1,r1] + ggml_transpose(ctx, // [p,m,qq,rr] + grad))); // [m,p,qq,rr] + } + } break; + case GGML_OP_SCALE: { + if (src0_needs_grads) { + float s; + memcpy(&s, tensor->op_params, sizeof(float)); + ggml_add_or_set(ctx, cgraph, isrc0, ggml_scale_impl(ctx, grad, s, 0.0, false)); + } + } break; + case GGML_OP_SET: { + const size_t nb1 = ((const int32_t *) tensor->op_params)[0]; + const size_t nb2 = ((const int32_t *) tensor->op_params)[1]; + const size_t nb3 = ((const int32_t *) tensor->op_params)[2]; + const size_t offset = ((const int32_t *) tensor->op_params)[3]; + + struct ggml_tensor * tensor_grad_view = NULL; + + if (src0_needs_grads || src1_needs_grads) { + GGML_ASSERT(src0->type == tensor->type); + GGML_ASSERT(!cgraph->grads[isrc0] || cgraph->grads[isrc0]->type == grad->type); + GGML_ASSERT(!cgraph->grads[isrc1] || !src1_needs_grads || cgraph->grads[isrc1]->type == grad->type); + + tensor_grad_view = ggml_view_4d(ctx, + grad, src1->ne[0], src1->ne[1], src1->ne[2], src1->ne[3], + nb1, nb2, nb3, offset); + } + + if (src0_needs_grads) { + struct ggml_tensor * tmp = ggml_neg(ctx, tensor_grad_view); + ggml_add_or_set(ctx, cgraph, isrc0, ggml_acc_impl(ctx, grad, tmp, nb1, nb2, nb3, offset, false)); + } + + if (src1_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc1, ggml_reshape(ctx, ggml_cont(ctx, tensor_grad_view), src1)); + } + } break; + case GGML_OP_CPY: { + // cpy overwrites value of src1 by src0 and returns view(src1) + // the overwriting is mathematically equivalent to: + // tensor = src0 * 1 + src1 * 0 + if (src0_needs_grads) { + // dsrc0 = dtensor * 1 + ggml_add_or_set(ctx, cgraph, isrc0, ggml_reshape(ctx, grad, src0)); + } + if (src1_needs_grads) { + // dsrc1 = dtensor * 0 -> noop + } + } break; + case GGML_OP_CONT: { + // same as cpy + if (src0_needs_grads) { + GGML_ASSERT(!cgraph->grads[isrc0] || ggml_is_contiguous(cgraph->grads[isrc0])); + GGML_ASSERT(ggml_is_contiguous(grad)); + GGML_ASSERT(ggml_nelements(tensor) == ggml_nelements(src0)); + ggml_add_or_set(ctx, cgraph, isrc0, + ggml_are_same_shape(tensor, src0) ? grad : ggml_reshape(ctx, grad, src0)); + } + } break; + case GGML_OP_RESHAPE: { + if (src0_needs_grads) { + struct ggml_tensor * grad_cont = ggml_is_contiguous(grad) ? grad : ggml_cont(ctx, grad); + ggml_add_or_set(ctx, cgraph, isrc0, ggml_reshape(ctx, grad_cont, src0)); + } + } break; + case GGML_OP_VIEW: { + if (src0_needs_grads) { + size_t offset; + + memcpy(&offset, tensor->op_params, sizeof(offset)); + + size_t nb1 = tensor->nb[1]; + size_t nb2 = tensor->nb[2]; + size_t nb3 = tensor->nb[3]; + + if (cgraph->grads[isrc0] && src0->type != cgraph->grads[isrc0]->type) { + // gradient is typically F32, but src0 could be other type + size_t ng = ggml_element_size(cgraph->grads[isrc0]); + size_t n0 = ggml_element_size(src0); + GGML_ASSERT(offset % n0 == 0); + GGML_ASSERT(nb1 % n0 == 0); + GGML_ASSERT(nb2 % n0 == 0); + GGML_ASSERT(nb3 % n0 == 0); + offset = (offset / n0) * ng; + nb1 = (nb1 / n0) * ng; + nb2 = (nb2 / n0) * ng; + nb3 = (nb3 / n0) * ng; + } + + ggml_acc_or_set(ctx, cgraph, isrc0, grad, nb1, nb2, nb3, offset); + } + } break; + case GGML_OP_PERMUTE: { + if (src0_needs_grads) { + const int32_t * axes = (const int32_t *) tensor->op_params; + const int axis0 = axes[0] & 0x3; + const int axis1 = axes[1] & 0x3; + const int axis2 = axes[2] & 0x3; + const int axis3 = axes[3] & 0x3; + int axb[4] = {0,0,0,0}; // axes backward + axb[axis0] = 0; + axb[axis1] = 1; + axb[axis2] = 2; + axb[axis3] = 3; + ggml_add_or_set(ctx, cgraph, isrc0, ggml_permute(ctx, grad, axb[0], axb[1], axb[2], axb[3])); + } + } break; + case GGML_OP_TRANSPOSE: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_transpose(ctx, grad)); + } + } break; + case GGML_OP_GET_ROWS: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_get_rows_back(ctx, grad, src1, src0)); + } + if (src1_needs_grads) { + // noop + } + } break; + case GGML_OP_DIAG_MASK_INF: { + if (src0_needs_grads) { + /* ggml_diag_mask_inf_impl() shouldn't be here */ + /* ref: https://github.com/ggml-org/llama.cpp/pull/4203#discussion_r1412377992 */ + const int n_past = ((const int32_t *) tensor->op_params)[0]; + ggml_add_or_set(ctx, cgraph, isrc0, ggml_diag_mask_zero_impl(ctx, grad, n_past, false)); + } + } break; + case GGML_OP_DIAG_MASK_ZERO: { + if (src0_needs_grads) { + const int n_past = ((const int32_t *) tensor->op_params)[0]; + ggml_add_or_set(ctx, cgraph, isrc0, ggml_diag_mask_zero_impl(ctx, grad, n_past, false)); + } + } break; + case GGML_OP_SOFT_MAX: { + if (src0_needs_grads) { + float scale = 1.0f; + float max_bias = 0.0f; + + memcpy(&scale, (const float *) tensor->op_params + 0, sizeof(float)); + memcpy(&max_bias, (const float *) tensor->op_params + 1, sizeof(float)); + + ggml_add_or_set(ctx, cgraph, isrc0, ggml_soft_max_ext_back(ctx, grad, tensor, scale, max_bias)); + } + GGML_ASSERT((!src1 || !src1_needs_grads) && "backward pass for softmax mask not implemented"); + } break; + case GGML_OP_ROPE: { + if (src0_needs_grads) { + //const int n_past = ((int32_t *) tensor->op_params)[0]; + const int n_dims = ((const int32_t *) tensor->op_params)[1]; + const int mode = ((const int32_t *) tensor->op_params)[2]; + //const int n_ctx = ((int32_t *) tensor->op_params)[3]; + const int n_ctx_orig = ((const int32_t *) tensor->op_params)[4]; + float freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow; + int sections[4] = {0, 0, 0, 0}; + + memcpy(&freq_base, (const float *) tensor->op_params + 5, sizeof(float)); + memcpy(&freq_scale, (const float *) tensor->op_params + 6, sizeof(float)); + memcpy(&ext_factor, (const float *) tensor->op_params + 7, sizeof(float)); + memcpy(&attn_factor, (const float *) tensor->op_params + 8, sizeof(float)); + memcpy(&beta_fast, (const float *) tensor->op_params + 9, sizeof(float)); + memcpy(&beta_slow, (const float *) tensor->op_params + 10, sizeof(float)); + memcpy(§ions, tensor->op_params + 11, sizeof(sections)); + + struct ggml_tensor * rope_back = grad->ne[2] == src1->ne[0] ? + ggml_rope_ext_back(ctx, grad, src1, src2, n_dims, + mode, n_ctx_orig, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow) : + ggml_rope_multi_back(ctx, grad, src1, src2, n_dims, sections, + mode, n_ctx_orig, freq_base, freq_scale, ext_factor, attn_factor, beta_fast, beta_slow); + ggml_add_or_set(ctx, cgraph, isrc0, rope_back); + } + GGML_ASSERT((!src2 || !src2_needs_grads) && "gradients for freq factors not implemented"); + } break; + case GGML_OP_IM2COL: + case GGML_OP_IM2COL_FAST_1D: { + if (src1_needs_grads) { + const int32_t s0 = ggml_get_op_params_i32(tensor, 0); + const int32_t s1 = ggml_get_op_params_i32(tensor, 1); + const int32_t p0 = ggml_get_op_params_i32(tensor, 2); + const int32_t p1 = ggml_get_op_params_i32(tensor, 3); + const int32_t d0 = ggml_get_op_params_i32(tensor, 4); + const int32_t d1 = ggml_get_op_params_i32(tensor, 5); + const bool is_2D = ggml_get_op_params_i32(tensor, 6) == 1; + + ggml_add_or_set(ctx, cgraph, isrc1, ggml_im2col_back(ctx, grad, src0, src1->ne, s0, s1, p0, p1, d0, d1, is_2D)); + } + } break; + case GGML_OP_POOL_2D: { + if (src0_needs_grads) { + const enum ggml_op_pool op = ggml_get_op_params_i32(tensor, 0); + const int32_t k0 = ggml_get_op_params_i32(tensor, 1); + const int32_t k1 = ggml_get_op_params_i32(tensor, 2); + const int32_t s0 = ggml_get_op_params_i32(tensor, 3); + const int32_t s1 = ggml_get_op_params_i32(tensor, 4); + const int32_t p0 = ggml_get_op_params_i32(tensor, 5); + const int32_t p1 = ggml_get_op_params_i32(tensor, 6); + + ggml_add_or_set(ctx, cgraph, isrc0, ggml_pool_2d_back(ctx, grad, src0, op, k0, k1, s0, s1, p0, p1)); + } + } break; + case GGML_OP_WIN_PART: + case GGML_OP_WIN_UNPART: + case GGML_OP_UNARY: { + switch (ggml_get_unary_op(tensor)) { + case GGML_UNARY_OP_ABS: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, ggml_sgn(ctx, src0), grad)); + } + } break; + case GGML_UNARY_OP_SGN: { + // noop + } break; + case GGML_UNARY_OP_NEG: { + if (src0_needs_grads) { + ggml_sub_or_set(ctx, cgraph, isrc0, grad); + } + } break; + case GGML_UNARY_OP_STEP: { + // noop + } break; + case GGML_UNARY_OP_RELU: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, ggml_step(ctx, src0), grad)); + } + } break; + case GGML_UNARY_OP_SILU: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_silu_back(ctx, grad, src0)); + } + } break; + case GGML_UNARY_OP_EXP: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, tensor, grad)); + } + } break; + case GGML_UNARY_OP_EXPM1: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, ggml_exp(ctx, src0))); + } + } break; + case GGML_UNARY_OP_SOFTPLUS: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_mul(ctx, grad, ggml_sigmoid(ctx, src0))); + } + } break; + default: { + fprintf(stderr, "%s: unsupported unary op for backward pass: %s\n", + __func__, ggml_unary_op_name(ggml_get_unary_op(tensor))); + GGML_ABORT("fatal error"); + } //break; + } + } break; + case GGML_OP_CROSS_ENTROPY_LOSS: { + if (src0_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc0, ggml_cross_entropy_loss_back(ctx, grad, src0, src1)); + } + GGML_ASSERT(!src1_needs_grads && "backward pass for labels not implemented"); + } break; + case GGML_OP_GLU: { + switch (ggml_get_glu_op(tensor)) { + case GGML_GLU_OP_SWIGLU: { + if (src0_needs_grads) { + GGML_ASSERT(src1 && "backward pass only implemented for split swiglu"); + ggml_add_or_set(ctx, cgraph, isrc0, ggml_silu_back(ctx, ggml_mul(ctx, grad, src1), src0)); + } + if (src1_needs_grads) { + ggml_add_or_set(ctx, cgraph, isrc1, ggml_mul(ctx, ggml_silu(ctx, src0), grad)); + } + } break; + default: { + GGML_ABORT("unsupported glu op for backward pass: %s", ggml_glu_op_name(ggml_get_glu_op(tensor))); + } //break; + } + } break; + case GGML_OP_NONE: { + // noop + } break; + case GGML_OP_COUNT: + default: { + GGML_ABORT("%s: unsupported ggml op for backward pass: %s\n", __func__, ggml_op_name(tensor->op)); + } //break; + } + + GGML_ASSERT(!src0_needs_grads || ggml_are_same_shape(src0, cgraph->grads[isrc0])); + GGML_ASSERT(!src1_needs_grads || ggml_are_same_shape(src1, cgraph->grads[isrc1])); + GGML_ASSERT(!src2_needs_grads || ggml_are_same_shape(src2, cgraph->grads[isrc2])); +} + +static size_t ggml_visit_parents_graph(struct ggml_cgraph * cgraph, struct ggml_tensor * node, bool compute) { + if (node->op != GGML_OP_NONE && compute) { + node->flags |= GGML_TENSOR_FLAG_COMPUTE; + } + + const size_t node_hash_pos = ggml_hash_find(&cgraph->visited_hash_set, node); + GGML_ASSERT(node_hash_pos != GGML_HASHSET_FULL); + + if (ggml_bitset_get(cgraph->visited_hash_set.used, node_hash_pos)) { + // already visited + + if (compute) { + // update the compute flag regardless + for (int i = 0; i < GGML_MAX_SRC; ++i) { + struct ggml_tensor * src = node->src[i]; + if (src && ((src->flags & GGML_TENSOR_FLAG_COMPUTE) == 0)) { + ggml_visit_parents_graph(cgraph, src, true); + } + } + } + + return node_hash_pos; + } + + // This is the first time we see this node in the current graph. + cgraph->visited_hash_set.keys[node_hash_pos] = node; + ggml_bitset_set(cgraph->visited_hash_set.used, node_hash_pos); + cgraph->use_counts[node_hash_pos] = 0; + + for (int i = 0; i < GGML_MAX_SRC; ++i) { + const int k = + (cgraph->order == GGML_CGRAPH_EVAL_ORDER_LEFT_TO_RIGHT) ? i : + (cgraph->order == GGML_CGRAPH_EVAL_ORDER_RIGHT_TO_LEFT) ? (GGML_MAX_SRC-1-i) : + /* unknown order, just fall back to using i */ i; + + struct ggml_tensor * src = node->src[k]; + if (src) { + const size_t src_hash_pos = ggml_visit_parents_graph(cgraph, src, compute); + + // Update the use count for this operand. + cgraph->use_counts[src_hash_pos]++; + } + } + + if (node->op == GGML_OP_NONE && !(node->flags & GGML_TENSOR_FLAG_PARAM)) { + // reached a leaf node, not part of the gradient graph (e.g. a constant) + GGML_ASSERT(cgraph->n_leafs < cgraph->size); + + if (strlen(node->name) == 0) { + ggml_format_name(node, "leaf_%d", cgraph->n_leafs); + } + + cgraph->leafs[cgraph->n_leafs] = node; + cgraph->n_leafs++; + } else { + GGML_ASSERT(cgraph->n_nodes < cgraph->size); + + if (strlen(node->name) == 0) { + ggml_format_name(node, "node_%d", cgraph->n_nodes); + } + + cgraph->nodes[cgraph->n_nodes] = node; + cgraph->n_nodes++; + } + + return node_hash_pos; +} + +static void ggml_build_forward_impl(struct ggml_cgraph * cgraph, struct ggml_tensor * tensor, bool expand, bool compute) { + if (!expand) { + // TODO: this branch isn't accessible anymore, maybe move this to ggml_build_forward_expand + ggml_graph_clear(cgraph); + } + + const int n_old = cgraph->n_nodes; + + ggml_visit_parents_graph(cgraph, tensor, compute); + + const int n_new = cgraph->n_nodes - n_old; + GGML_PRINT_DEBUG("%s: visited %d new nodes\n", __func__, n_new); + + if (n_new > 0) { + // the last added node should always be starting point + GGML_ASSERT(cgraph->nodes[cgraph->n_nodes - 1] == tensor); + } +} + +struct ggml_tensor * ggml_build_forward_select( + struct ggml_cgraph * cgraph, + struct ggml_tensor ** tensors, + int n_tensors, + int idx) { + GGML_ASSERT(idx >= 0 && idx < n_tensors); + + for (int i = 0; i < n_tensors; i++) { + ggml_build_forward_impl(cgraph, tensors[i], true, i == idx ? true : false); + } + + return tensors[idx]; +} + +void ggml_build_forward_expand(struct ggml_cgraph * cgraph, struct ggml_tensor * tensor) { + ggml_build_forward_impl(cgraph, tensor, true, true); +} + +void ggml_build_backward_expand( + struct ggml_context * ctx, + struct ggml_cgraph * cgraph, + struct ggml_tensor ** grad_accs) { + GGML_ASSERT(cgraph->n_nodes > 0); + GGML_ASSERT(cgraph->grads); + GGML_ASSERT(cgraph->grad_accs); + + const int n_nodes_f = cgraph->n_nodes; + + memset(cgraph->grads, 0, cgraph->visited_hash_set.size*sizeof(struct ggml_tensor *)); + memset(cgraph->grad_accs, 0, cgraph->visited_hash_set.size*sizeof(struct ggml_tensor *)); + bool * grads_needed = calloc(cgraph->visited_hash_set.size, sizeof(bool)); + + { + bool any_params = false; + bool any_loss = false; + for (int i = 0; i < n_nodes_f; ++i) { + struct ggml_tensor * node = cgraph->nodes[i]; + any_params = any_params || (node->flags & GGML_TENSOR_FLAG_PARAM); + any_loss = any_loss || (node->flags & GGML_TENSOR_FLAG_LOSS); + } + GGML_ASSERT(any_params && "no trainable parameters found, did you forget to call ggml_set_param?"); + GGML_ASSERT(any_loss && "no training loss found, did you forget to call ggml_set_loss?"); + } + + for (int i = 0; i < n_nodes_f; ++i) { + struct ggml_tensor * node = cgraph->nodes[i]; + + if (node->type == GGML_TYPE_I32) { + continue; + } + + bool node_needs_grad = (node->flags & GGML_TENSOR_FLAG_PARAM) || (node->flags & GGML_TENSOR_FLAG_LOSS); + bool ignore_src[GGML_MAX_SRC] = {false}; + switch (node->op) { + // gradients in node->src[0] for one reason or another have no effect on output gradients + case GGML_OP_IM2COL: // only used for its shape + case GGML_OP_IM2COL_FAST_1D: + case GGML_OP_IM2COL_BACK: // same as IM2COL + ignore_src[0] = true; + break; + case GGML_OP_UNARY: { + const enum ggml_unary_op uop = ggml_get_unary_op(node); + // SGN and STEP unary ops are piecewise constant + if (uop == GGML_UNARY_OP_SGN || uop == GGML_UNARY_OP_STEP) { + ignore_src[0] = true; + } + } break; + + // gradients in node->src[1] for one reason or another have no effect on output gradients + case GGML_OP_CPY: // gradients in CPY target are irrelevant + case GGML_OP_GET_ROWS: // row indices not differentiable + case GGML_OP_GET_ROWS_BACK: // same as for GET_ROWS + case GGML_OP_ROPE: // positions not differentiable + ignore_src[1] = true; + break; + + default: + break; + } + for (int j = 0; j < GGML_MAX_SRC; ++j) { + if (!node->src[j] || ignore_src[j] || !grads_needed[ggml_hash_find(&cgraph->visited_hash_set, node->src[j])]) { + continue; + } + GGML_ASSERT(node->src[j]->type == GGML_TYPE_F32 || node->src[j]->type == GGML_TYPE_F16); + node_needs_grad = true; + break; + } + if (!node_needs_grad) { + continue; + } + + // inplace operations are currently not supported + GGML_ASSERT(!node->view_src || node->op == GGML_OP_CPY || node->op == GGML_OP_VIEW || + node->op == GGML_OP_RESHAPE || node->op == GGML_OP_PERMUTE || node->op == GGML_OP_TRANSPOSE); + + const size_t ihash = ggml_hash_find(&cgraph->visited_hash_set, node); + GGML_ASSERT(ihash != GGML_HASHSET_FULL); + GGML_ASSERT(ggml_bitset_get(cgraph->visited_hash_set.used, ihash)); + if (grad_accs && grad_accs[i]) { + cgraph->grad_accs[ihash] = grad_accs[i]; + cgraph->grads[ihash] = cgraph->grad_accs[ihash]; + } else if (node->flags & GGML_TENSOR_FLAG_LOSS) { + // loss tensors always need a gradient accumulator + cgraph->grad_accs[ihash] = ggml_new_tensor(ctx, GGML_TYPE_F32, GGML_MAX_DIMS, node->ne); + cgraph->grads[ihash] = cgraph->grad_accs[ihash]; + } + grads_needed[ihash] = true; + } + + for (int i = n_nodes_f - 1; i >= 0; --i) { + // inplace operations to add gradients are not created by ggml_compute_backward except for gradient accumulation + // use allocator to automatically make inplace operations + ggml_compute_backward(ctx, cgraph, i, grads_needed); + } + + free(grads_needed); +} + +static void * incr_ptr_aligned(void ** p, size_t size, size_t align) { + void * ptr = *p; + ptr = (void *) GGML_PAD((uintptr_t) ptr, align); + *p = (void *) ((char *) ptr + size); + return ptr; +} + +static size_t ggml_graph_nbytes(size_t size, bool grads) { + size_t hash_size = ggml_hash_size(size * 2); + void * p = 0; + incr_ptr_aligned(&p, sizeof(struct ggml_cgraph), 1); + incr_ptr_aligned(&p, size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // nodes + incr_ptr_aligned(&p, size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // leafs + incr_ptr_aligned(&p, hash_size * sizeof(int32_t), sizeof(int32_t)); // use_counts + incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // hash keys + if (grads) { + incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // grads + incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); // grad_accs + } + incr_ptr_aligned(&p, ggml_bitset_size(hash_size) * sizeof(ggml_bitset_t), sizeof(ggml_bitset_t)); + + size_t nbytes = (size_t) p; + return nbytes; +} + +size_t ggml_graph_overhead_custom(size_t size, bool grads) { + return GGML_OBJECT_SIZE + GGML_PAD(ggml_graph_nbytes(size, grads), GGML_MEM_ALIGN); +} + +size_t ggml_graph_overhead(void) { + return ggml_graph_overhead_custom(GGML_DEFAULT_GRAPH_SIZE, false); +} + +struct ggml_cgraph * ggml_new_graph_custom(struct ggml_context * ctx, size_t size, bool grads) { + const size_t obj_size = ggml_graph_nbytes(size, grads); + struct ggml_object * obj = ggml_new_object(ctx, GGML_OBJECT_TYPE_GRAPH, obj_size); + struct ggml_cgraph * cgraph = (struct ggml_cgraph *) ((char *) ctx->mem_buffer + obj->offs); + + // the size of the hash table is doubled since it needs to hold both nodes and leafs + size_t hash_size = ggml_hash_size(size * 2); + + void * p = cgraph + 1; + + struct ggml_tensor ** nodes_ptr = incr_ptr_aligned(&p, size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); + struct ggml_tensor ** leafs_ptr = incr_ptr_aligned(&p, size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); + int32_t * use_counts_ptr = incr_ptr_aligned(&p, hash_size * sizeof(int32_t), sizeof(int32_t)); + struct ggml_tensor ** hash_keys_ptr = incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)); + struct ggml_tensor ** grads_ptr = grads ? incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)) : NULL; + struct ggml_tensor ** grad_accs_ptr = grads ? incr_ptr_aligned(&p, hash_size * sizeof(struct ggml_tensor *), sizeof(struct ggml_tensor *)) : NULL; + + ggml_bitset_t * hash_used = incr_ptr_aligned(&p, ggml_bitset_size(hash_size) * sizeof(ggml_bitset_t), sizeof(ggml_bitset_t)); + + // check that we allocated the correct amount of memory + assert(obj_size == (size_t)((char *)p - (char *)cgraph)); + + *cgraph = (struct ggml_cgraph) { + /*.size =*/ size, + /*.n_nodes =*/ 0, + /*.n_leafs =*/ 0, + /*.nodes =*/ nodes_ptr, + /*.grads =*/ grads_ptr, + /*.grad_accs =*/ grad_accs_ptr, + /*.leafs =*/ leafs_ptr, + /*.use_counts =*/ use_counts_ptr, + /*.hash_table =*/ { hash_size, hash_used, hash_keys_ptr }, + /*.order =*/ GGML_CGRAPH_EVAL_ORDER_LEFT_TO_RIGHT, + /*.uid =*/ 0, + }; + + ggml_hash_set_reset(&cgraph->visited_hash_set); + if (grads) { + memset(cgraph->grads, 0, hash_size*sizeof(struct ggml_tensor *)); + memset(cgraph->grad_accs, 0, hash_size*sizeof(struct ggml_tensor *)); + } + + return cgraph; +} + +struct ggml_cgraph * ggml_new_graph(struct ggml_context * ctx) { + return ggml_new_graph_custom(ctx, GGML_DEFAULT_GRAPH_SIZE, false); +} + +struct ggml_cgraph ggml_graph_view(struct ggml_cgraph * cgraph0, int i0, int i1) { + struct ggml_cgraph cgraph = { + /*.size =*/ 0, + /*.n_nodes =*/ i1 - i0, + /*.n_leafs =*/ 0, + /*.nodes =*/ cgraph0->nodes + i0, + /*.grads =*/ NULL, // gradients would need visited_hash_set + /*.grad_accs =*/ NULL, + /*.leafs =*/ NULL, + /*.use_counts =*/ cgraph0->use_counts, + /*.visited_hash_set =*/ cgraph0->visited_hash_set, + /*.order =*/ cgraph0->order, + /*.uid =*/ 0 + }; + + return cgraph; +} + +void ggml_graph_cpy(struct ggml_cgraph * src, struct ggml_cgraph * dst) { + GGML_ASSERT(dst->size >= src->n_leafs); + GGML_ASSERT(dst->size >= src->n_nodes); + GGML_ASSERT(dst->visited_hash_set.size >= src->visited_hash_set.size); + + dst->n_leafs = src->n_leafs; + dst->n_nodes = src->n_nodes; + dst->order = src->order; + + for (int i = 0; i < src->n_leafs; ++i) { + dst->leafs[i] = src->leafs[i]; + } + + for (int i = 0; i < src->n_nodes; ++i) { + dst->nodes[i] = src->nodes[i]; + } + + for (size_t i = 0; i < src->visited_hash_set.size; ++i) { + // copy all hashset keys (tensors) that are in use + if (ggml_bitset_get(src->visited_hash_set.used, i)) { + size_t new_hash_pos = ggml_hash_insert(&dst->visited_hash_set, src->visited_hash_set.keys[i]); + dst->use_counts[new_hash_pos] = src->use_counts[i]; + } + } + + if (dst->grads) { + memset(dst->grads, 0, dst->visited_hash_set.size*sizeof(struct ggml_tensor *)); + memset(dst->grad_accs, 0, dst->visited_hash_set.size*sizeof(struct ggml_tensor *)); + } + if (src->grads) { + GGML_ASSERT(dst->grads != NULL); + GGML_ASSERT(dst->grad_accs != NULL); + for (int i = 0; i < src->n_nodes; ++i) { + const size_t igrad_src = ggml_hash_find(&src->visited_hash_set, src->nodes[i]); + const size_t igrad_dst = ggml_hash_find(&dst->visited_hash_set, dst->nodes[i]); + + GGML_ASSERT(igrad_src != GGML_HASHSET_FULL); + GGML_ASSERT(ggml_bitset_get(src->visited_hash_set.used, igrad_src)); + GGML_ASSERT(igrad_dst != GGML_HASHSET_FULL); + GGML_ASSERT(ggml_bitset_get(dst->visited_hash_set.used, igrad_dst)); + + dst->grads[igrad_dst] = src->grads[igrad_src]; + dst->grad_accs[igrad_dst] = src->grad_accs[igrad_src]; + } + } +} + +struct ggml_cgraph * ggml_graph_dup(struct ggml_context * ctx, struct ggml_cgraph * cgraph, bool force_grads) { + struct ggml_cgraph * result = ggml_new_graph_custom(ctx, cgraph->size, cgraph->grads || force_grads); + ggml_graph_cpy(cgraph, result); + return result; +} + +struct ggml_tensor * ggml_set_zero(struct ggml_tensor * tensor) { + if (ggml_is_empty(tensor)) { + return tensor; + } + if (tensor->buffer) { + ggml_backend_tensor_memset(tensor, 0, 0, ggml_nbytes(tensor)); + } else { + GGML_ASSERT(tensor->data); + memset(tensor->data, 0, ggml_nbytes(tensor)); + } + return tensor; +} + +void ggml_graph_reset(struct ggml_cgraph * cgraph) { + if (!cgraph) { + return; + } + GGML_ASSERT(cgraph->grads != NULL); + + for (int i = 0; i < cgraph->n_nodes; i++) { + struct ggml_tensor * node = cgraph->nodes[i]; + struct ggml_tensor * grad_acc = ggml_graph_get_grad_acc(cgraph, node); + + if (node->op == GGML_OP_OPT_STEP_ADAMW) { + // clear momenta + ggml_set_zero(node->src[2]); + ggml_set_zero(node->src[3]); + } + + // initial gradients of loss should be 1, 0 otherwise + if (grad_acc) { + if (node->flags & GGML_TENSOR_FLAG_LOSS) { + GGML_ASSERT(grad_acc->type == GGML_TYPE_F32); + GGML_ASSERT(ggml_is_scalar(grad_acc)); + + const float onef = 1.0f; + if (grad_acc->buffer) { + ggml_backend_tensor_set(grad_acc, &onef, 0, sizeof(float)); + } else { + GGML_ASSERT(grad_acc->data); + *((float *) grad_acc->data) = onef; + } + } else { + ggml_set_zero(grad_acc); + } + } + } +} + +void ggml_graph_clear(struct ggml_cgraph * cgraph) { + cgraph->n_leafs = 0; + cgraph->n_nodes = 0; + ggml_hash_set_reset(&cgraph->visited_hash_set); +} + +int ggml_graph_size(struct ggml_cgraph * cgraph) { + return cgraph->size; +} + +struct ggml_tensor * ggml_graph_node(struct ggml_cgraph * cgraph, int i) { + if (i < 0) { + GGML_ASSERT(cgraph->n_nodes + i >= 0); + return cgraph->nodes[cgraph->n_nodes + i]; + } + + GGML_ASSERT(i < cgraph->n_nodes); + return cgraph->nodes[i]; +} + +struct ggml_tensor ** ggml_graph_nodes(struct ggml_cgraph * cgraph) { + return cgraph->nodes; +} + +int ggml_graph_n_nodes(struct ggml_cgraph * cgraph) { + return cgraph->n_nodes; +} + +void ggml_graph_set_n_nodes(struct ggml_cgraph * cgraph, int n_nodes) { + GGML_ASSERT(n_nodes >= 0); + GGML_ASSERT(n_nodes <= cgraph->size); + cgraph->n_nodes = n_nodes; +} + +void ggml_graph_add_node(struct ggml_cgraph * cgraph, struct ggml_tensor * tensor) { + GGML_ASSERT(cgraph->size > cgraph->n_nodes); + cgraph->nodes[cgraph->n_nodes] = tensor; + cgraph->n_nodes++; +} + +struct ggml_tensor * ggml_graph_get_tensor(const struct ggml_cgraph * cgraph, const char * name) { + for (int i = 0; i < cgraph->n_leafs; i++) { + struct ggml_tensor * leaf = cgraph->leafs[i]; + + if (strcmp(leaf->name, name) == 0) { + return leaf; + } + } + + for (int i = 0; i < cgraph->n_nodes; i++) { + struct ggml_tensor * node = cgraph->nodes[i]; + + if (strcmp(node->name, name) == 0) { + return node; + } + } + + return NULL; +} + +struct ggml_tensor * ggml_graph_get_grad(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node) { + const size_t igrad = ggml_hash_find(&cgraph->visited_hash_set, node); + return igrad != GGML_HASHSET_FULL && ggml_bitset_get(cgraph->visited_hash_set.used, igrad) && cgraph->grads ? cgraph->grads[igrad] : NULL; +} + +struct ggml_tensor * ggml_graph_get_grad_acc(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node) { + const size_t igrad = ggml_hash_find(&cgraph->visited_hash_set, node); + return igrad != GGML_HASHSET_FULL && ggml_bitset_get(cgraph->visited_hash_set.used, igrad) && cgraph->grad_accs ? cgraph->grad_accs[igrad] : NULL; +} + +void ggml_graph_print(const struct ggml_cgraph * cgraph) { + GGML_LOG_INFO("=== GRAPH ===\n"); + + GGML_LOG_INFO("n_nodes = %d\n", cgraph->n_nodes); + for (int i = 0; i < cgraph->n_nodes; i++) { + struct ggml_tensor * node = cgraph->nodes[i]; + + GGML_LOG_INFO(" - %3d: [ %5" PRId64 ", %5" PRId64 ", %5" PRId64 "] %16s %s\n", + i, + node->ne[0], node->ne[1], node->ne[2], + ggml_op_name(node->op), (node->flags & GGML_TENSOR_FLAG_PARAM) ? "x" : + ggml_graph_get_grad(cgraph, node) ? "g" : " "); + } + + GGML_LOG_INFO("n_leafs = %d\n", cgraph->n_leafs); + for (int i = 0; i < cgraph->n_leafs; i++) { + struct ggml_tensor * node = cgraph->leafs[i]; + + GGML_LOG_INFO(" - %3d: [ %5" PRId64 ", %5" PRId64 "] %8s %16s\n", + i, + node->ne[0], node->ne[1], + ggml_op_name(node->op), + ggml_get_name(node)); + } + + GGML_LOG_INFO("========================================\n"); +} + +static int ggml_node_list_find_tensor(const struct ggml_cgraph * cgraph, + const int * idxs, + int count, + const struct ggml_tensor * tensor) { + GGML_ASSERT(cgraph && idxs); + for (int i = 0; i < count; ++i) { + const int node_idx = idxs[i]; + + if (node_idx >= cgraph->n_nodes) { + return -1; + } + if (cgraph->nodes[node_idx] == tensor) { + return i; + } + } + return -1; +} + +bool ggml_can_fuse_subgraph_ext(const struct ggml_cgraph * cgraph, + const int * node_idxs, + int count, + const enum ggml_op * ops, + const int * outputs, + int num_outputs) { + GGML_ASSERT(outputs && num_outputs > 0); + + for (int i = 0; i < count; ++i) { + if (node_idxs[i] >= cgraph->n_nodes) { + return false; + } + + const struct ggml_tensor * node = cgraph->nodes[node_idxs[i]]; + + if (node->op != ops[i]) { + return false; + } + + if ((node->flags & GGML_TENSOR_FLAG_COMPUTE) == 0) { + return false; + } + + if (ggml_node_list_find_tensor(cgraph, outputs, num_outputs, node) != -1) { + continue; + } + + if (node->flags & GGML_TENSOR_FLAG_OUTPUT) { + return false; + } + + int subgraph_uses = 0; + for (int j = i + 1; j < count; ++j) { + const struct ggml_tensor * other_node = cgraph->nodes[node_idxs[j]]; + for (int src_idx = 0; src_idx < GGML_MAX_SRC; src_idx++) { + if (other_node->src[src_idx] == node) { + subgraph_uses++; + } + } + } + + if (subgraph_uses != ggml_node_get_use_count(cgraph, node_idxs[i])) { + return false; + } + + // if node is a view, check if the view_src and all it's parent view_srcs are within the subgraph + struct ggml_tensor * view_src = node->view_src; + while (view_src) { + if (ggml_node_list_find_tensor(cgraph, node_idxs, count, view_src) == -1) { + return false; + } + view_src = view_src->view_src; + } + } + + return true; +} + +// check if node is part of the graph +static bool ggml_graph_find(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node) { + if (cgraph == NULL) { + return true; + } + + for (int i = 0; i < cgraph->n_nodes; i++) { + if (cgraph->nodes[i] == node) { + return true; + } + } + + return false; +} + +static struct ggml_tensor * ggml_graph_get_parent(const struct ggml_cgraph * cgraph, const struct ggml_tensor * node) { + for (int i = 0; i < cgraph->n_nodes; i++) { + struct ggml_tensor * parent = cgraph->nodes[i]; + struct ggml_tensor * grad = ggml_graph_get_grad(cgraph, parent); + + if (grad == node) { + return parent; + } + } + + return NULL; +} + +static void ggml_graph_dump_dot_node_edge(FILE * fp, const struct ggml_cgraph * gb, struct ggml_tensor * node, struct ggml_tensor * parent, const char * label) { + struct ggml_tensor * gparent = ggml_graph_get_parent(gb, node); + struct ggml_tensor * gparent0 = ggml_graph_get_parent(gb, parent); + fprintf(fp, " \"%p\" -> \"%p\" [ arrowhead = %s; style = %s; label = \"%s\"; ]\n", + gparent0 ? (void *) gparent0 : (void *) parent, + gparent ? (void *) gparent : (void *) node, + gparent ? "empty" : "vee", + gparent ? "dashed" : "solid", + label); +} + +static void ggml_graph_dump_dot_leaf_edge(FILE * fp, struct ggml_tensor * node, struct ggml_tensor * parent, const char * label) { + fprintf(fp, " \"%p\" -> \"%p\" [ label = \"%s\"; ]\n", + (void *) parent, + (void *) node, + label); +} + +void ggml_graph_dump_dot(const struct ggml_cgraph * gb, const struct ggml_cgraph * cgraph, const char * filename) { + char color[16]; + + FILE * fp = ggml_fopen(filename, "w"); + GGML_ASSERT(fp); + + fprintf(fp, "digraph G {\n"); + fprintf(fp, " newrank = true;\n"); + fprintf(fp, " rankdir = TB;\n"); + + for (int i = 0; i < gb->n_nodes; i++) { + struct ggml_tensor * node = gb->nodes[i]; + struct ggml_tensor * grad = ggml_graph_get_grad(gb, node); + + if (ggml_graph_get_parent(gb, node) != NULL) { + continue; + } + + if (node->flags & GGML_TENSOR_FLAG_PARAM) { + snprintf(color, sizeof(color), "yellow"); + } else if (grad) { + if (ggml_graph_find(cgraph, node)) { + snprintf(color, sizeof(color), "green"); + } else { + snprintf(color, sizeof(color), "lightblue"); + } + } else { + snprintf(color, sizeof(color), "white"); + } + + fprintf(fp, " \"%p\" [ " + "style = filled; fillcolor = %s; shape = record; " + "label=\"", + (void *) node, color); + + if (strlen(node->name) > 0) { + fprintf(fp, "%s (%s)|", node->name, ggml_type_name(node->type)); + } else { + fprintf(fp, "(%s)|", ggml_type_name(node->type)); + } + + if (ggml_is_matrix(node)) { + fprintf(fp, "%d [%" PRId64 ", %" PRId64 "] | %s", i, node->ne[0], node->ne[1], ggml_op_symbol(node->op)); + } else { + fprintf(fp, "%d [%" PRId64 ", %" PRId64 ", %" PRId64 "] | %s", i, node->ne[0], node->ne[1], node->ne[2], ggml_op_symbol(node->op)); + } + + if (grad) { + fprintf(fp, " | %s\"; ]\n", ggml_op_symbol(grad->op)); + } else { + fprintf(fp, "\"; ]\n"); + } + } + + for (int i = 0; i < gb->n_leafs; i++) { + struct ggml_tensor * node = gb->leafs[i]; + + snprintf(color, sizeof(color), "pink"); + + fprintf(fp, " \"%p\" [ " + "style = filled; fillcolor = %s; shape = record; " + "label=\"", + (void *) node, color); + + if (strlen(node->name) > 0) { + fprintf(fp, "%s (%s)|", node->name, ggml_type_name(node->type)); + } else { + fprintf(fp, "(%s)|", ggml_type_name(node->type)); + } + + fprintf(fp, "CONST %d [%" PRId64 ", %" PRId64 "]", i, node->ne[0], node->ne[1]); + if (ggml_nelements(node) < 5 && node->data != NULL) { + fprintf(fp, " | ("); + for (int j = 0; j < ggml_nelements(node); j++) { + // FIXME: use ggml-backend to obtain the tensor data + //if (node->type == GGML_TYPE_I8 || node->type == GGML_TYPE_I16 || node->type == GGML_TYPE_I32) { + // fprintf(fp, "%d", ggml_get_i32_1d(node, j)); + //} + //else if (node->type == GGML_TYPE_F32 || + // node->type == GGML_TYPE_F16 || + // node->type == GGML_TYPE_BF16) { + // fprintf(fp, "%.1e", (double)ggml_get_f32_1d(node, j)); + //} + //else + { + fprintf(fp, "#"); + } + if (j < ggml_nelements(node) - 1) { + fprintf(fp, ", "); + } + } + fprintf(fp, ")"); + } + fprintf(fp, "\"; ]\n"); + } + + for (int i = 0; i < gb->n_nodes; i++) { + struct ggml_tensor * node = gb->nodes[i]; + + for (int j = 0; j < GGML_MAX_SRC; j++) { + if (node->src[j]) { + char label[16]; + snprintf(label, sizeof(label), "src %d", j); + ggml_graph_dump_dot_node_edge(fp, gb, node, node->src[j], label); + } + } + } + + for (int i = 0; i < gb->n_leafs; i++) { + struct ggml_tensor * node = gb->leafs[i]; + + for (int j = 0; j < GGML_MAX_SRC; j++) { + if (node->src[j]) { + char label[16]; + snprintf(label, sizeof(label), "src %d", j); + ggml_graph_dump_dot_leaf_edge(fp, node, node->src[j], label); + } + } + } + + fprintf(fp, "}\n"); + + fclose(fp); + + GGML_LOG_INFO("%s: dot -Tpng %s -o %s.png && open %s.png\n", __func__, filename, filename, filename); +} + +//////////////////////////////////////////////////////////////////////////////// + +void ggml_set_input(struct ggml_tensor * tensor) { + tensor->flags |= GGML_TENSOR_FLAG_INPUT; +} + +void ggml_set_output(struct ggml_tensor * tensor) { + tensor->flags |= GGML_TENSOR_FLAG_OUTPUT; +} + +void ggml_set_param(struct ggml_tensor * tensor) { + GGML_ASSERT(tensor->op == GGML_OP_NONE); + tensor->flags |= GGML_TENSOR_FLAG_PARAM; +} + +void ggml_set_loss(struct ggml_tensor * tensor) { + GGML_ASSERT(ggml_is_scalar(tensor)); + GGML_ASSERT(tensor->type == GGML_TYPE_F32); + tensor->flags |= GGML_TENSOR_FLAG_LOSS; +} + +//////////////////////////////////////////////////////////////////////////////// + +void ggml_quantize_init(enum ggml_type type) { + ggml_critical_section_start(); + + switch (type) { + case GGML_TYPE_IQ2_XXS: + case GGML_TYPE_IQ2_XS: + case GGML_TYPE_IQ2_S: + case GGML_TYPE_IQ1_S: + case GGML_TYPE_IQ1_M: iq2xs_init_impl(type); break; + case GGML_TYPE_IQ3_XXS: iq3xs_init_impl(256); break; + case GGML_TYPE_IQ3_S: iq3xs_init_impl(512); break; + default: // nothing + break; + } + + ggml_critical_section_end(); +} + +void ggml_quantize_free(void) { + ggml_critical_section_start(); + + iq2xs_free_impl(GGML_TYPE_IQ2_XXS); + iq2xs_free_impl(GGML_TYPE_IQ2_XS); + iq2xs_free_impl(GGML_TYPE_IQ2_S); + iq2xs_free_impl(GGML_TYPE_IQ1_S); + iq2xs_free_impl(GGML_TYPE_IQ1_M); + iq3xs_free_impl(256); + iq3xs_free_impl(512); + + ggml_critical_section_end(); +} + +bool ggml_quantize_requires_imatrix(enum ggml_type type) { + return + type == GGML_TYPE_IQ2_XXS || + type == GGML_TYPE_IQ2_XS || + type == GGML_TYPE_IQ1_S;// || + //type == GGML_TYPE_IQ1_M; +} + +size_t ggml_quantize_chunk( + enum ggml_type type, + const float * src, + void * dst, + int64_t start, + int64_t nrows, + int64_t n_per_row, + const float * imatrix) { + const int64_t n = nrows * n_per_row; + + if (ggml_quantize_requires_imatrix(type)) { + GGML_ASSERT(imatrix != NULL); + } + + GGML_ASSERT(start % type_traits[type].blck_size == 0); + GGML_ASSERT(start % n_per_row == 0); + + ggml_quantize_init(type); // this is noop if already initialized + + const size_t start_row = start / n_per_row; + const size_t row_size = ggml_row_size(type, n_per_row); + + size_t result = 0; + + switch (type) { + case GGML_TYPE_Q1_0: result = quantize_q1_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q4_0: result = quantize_q4_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q4_1: result = quantize_q4_1 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q5_0: result = quantize_q5_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q5_1: result = quantize_q5_1 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q8_0: result = quantize_q8_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_MXFP4: result = quantize_mxfp4 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_NVFP4: result = quantize_nvfp4 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q2_K: result = quantize_q2_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q3_K: result = quantize_q3_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q4_K: result = quantize_q4_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q5_K: result = quantize_q5_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_Q6_K: result = quantize_q6_K (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_TQ1_0: result = quantize_tq1_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_TQ2_0: result = quantize_tq2_0 (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ2_XXS: result = quantize_iq2_xxs(src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ2_XS: result = quantize_iq2_xs (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ3_XXS: result = quantize_iq3_xxs(src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ3_S: result = quantize_iq3_s (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ2_S: result = quantize_iq2_s (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ1_S: result = quantize_iq1_s (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ1_M: result = quantize_iq1_m (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ4_NL: result = quantize_iq4_nl (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_IQ4_XS: result = quantize_iq4_xs (src + start, (char *) dst + start_row * row_size, nrows, n_per_row, imatrix); break; + case GGML_TYPE_F16: + { + size_t elemsize = sizeof(ggml_fp16_t); + ggml_fp32_to_fp16_row(src + start, (ggml_fp16_t *)dst + start, n); + result = n * elemsize; + } break; + case GGML_TYPE_BF16: + { + size_t elemsize = sizeof(ggml_bf16_t); + ggml_fp32_to_bf16_row_ref(src + start, (ggml_bf16_t *)dst + start, n); + result = n * elemsize; + } break; + case GGML_TYPE_F32: + { + size_t elemsize = sizeof(float); + result = n * elemsize; + memcpy((uint8_t *)dst + start * elemsize, src + start, result); + } break; + default: + assert(false); + } + + GGML_ASSERT(result == nrows * row_size); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +void ggml_log_get(ggml_log_callback * log_callback, void ** user_data) { + *log_callback = g_logger_state.log_callback; + *user_data = g_logger_state.log_callback_user_data; +} + +void ggml_log_set(ggml_log_callback log_callback, void * user_data) { + g_logger_state.log_callback = log_callback ? log_callback : ggml_log_callback_default; + g_logger_state.log_callback_user_data = user_data; +} + +void ggml_threadpool_params_init(struct ggml_threadpool_params * p, int n_threads) { + p->n_threads = n_threads; + p->prio = 0; // default priority (usually means normal or inherited) + p->poll = 50; // hybrid-polling enabled + p->strict_cpu = false; // no strict placement (all threads share same cpumask) + p->paused = false; // threads are ready to go + memset(p->cpumask, 0, GGML_MAX_N_THREADS); // all-zero means use the default affinity (usually inherited) +} + +struct ggml_threadpool_params ggml_threadpool_params_default(int n_threads) { + struct ggml_threadpool_params p; + ggml_threadpool_params_init(&p, n_threads); + return p; +} + +bool ggml_threadpool_params_match(const struct ggml_threadpool_params * p0, const struct ggml_threadpool_params * p1) { + if (p0->n_threads != p1->n_threads ) return false; + if (p0->prio != p1->prio ) return false; + if (p0->poll != p1->poll ) return false; + if (p0->strict_cpu != p1->strict_cpu ) return false; + return memcmp(p0->cpumask, p1->cpumask, GGML_MAX_N_THREADS) == 0; +} diff --git a/include/engine/community_models/mira_tts/assets.h b/include/engine/community_models/mira_tts/assets.h new file mode 100644 index 000000000..47a0ffddb --- /dev/null +++ b/include/engine/community_models/mira_tts/assets.h @@ -0,0 +1,56 @@ +#pragma once + +#include "engine/framework/assets/resource_bundle.h" +#include "engine/framework/assets/tensor_source.h" + +#include +#include +#include + +namespace engine::community_models::mira_tts { + +struct MiraTTSConfig { + int64_t hidden_size = 896; + int64_t intermediate_size = 4864; + int64_t layers = 24; + int64_t attention_heads = 14; + int64_t kv_heads = 2; + int64_t head_dim = 64; + int64_t vocab_size = 166000; + int64_t max_position_embeddings = 32768; + float rms_norm_eps = 1.0e-6F; + float rope_theta = 1.0e6F; + int32_t bos_token_id = 151643; + int32_t eos_token_id = 151645; + int32_t speech_token_start = 155761; + int32_t speech_token_end = 163952; + int32_t prompt_speech_start = 165151; + int32_t sample_rate = 16000; + int32_t output_sample_rate = 48000; +}; + +struct MiraTTSAssets { + assets::ResourceBundle resources; + MiraTTSConfig config; + std::shared_ptr language_model_weights; + std::shared_ptr speaker_encoder_weights; + std::shared_ptr processor_weights; + std::shared_ptr decoder_weights; + std::shared_ptr upsampler_weights; +}; + +struct MiraGenerationOptions { + int64_t max_new_tokens = 1024; + int64_t top_k = 50; + float top_p = 0.95F; + float min_p = 0.05F; + float temperature = 0.8F; + float repetition_penalty = 1.2F; + uint64_t seed = 0; + bool has_seed = false; +}; + +std::shared_ptr load_mira_tts_assets( + const std::filesystem::path & model_path); + +} // namespace engine::community_models::mira_tts diff --git a/include/engine/community_models/mira_tts/decoder.h b/include/engine/community_models/mira_tts/decoder.h new file mode 100644 index 000000000..d9e6bc656 --- /dev/null +++ b/include/engine/community_models/mira_tts/decoder.h @@ -0,0 +1,33 @@ +#pragma once + +#include "engine/community_models/mira_tts/assets.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/runtime/session.h" + +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { + +class MiraDecoder final { +public: + MiraDecoder( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + size_t graph_context_bytes, + assets::TensorStorageType storage_type); + ~MiraDecoder(); + + runtime::AudioBuffer decode( + const std::vector & latents, + int64_t frames); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::community_models::mira_tts diff --git a/include/engine/community_models/mira_tts/generator.h b/include/engine/community_models/mira_tts/generator.h new file mode 100644 index 000000000..2b253bd3c --- /dev/null +++ b/include/engine/community_models/mira_tts/generator.h @@ -0,0 +1,34 @@ +#pragma once + +#include "engine/community_models/mira_tts/assets.h" +#include "engine/framework/core/execution_context.h" + +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { + +class MiraGenerator final { +public: + MiraGenerator( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t prefill_graph_arena_bytes, + size_t decode_graph_arena_bytes, + size_t weight_context_bytes, + assets::TensorStorageType weight_storage_type); + ~MiraGenerator(); + + std::vector generate( + const std::vector & prompt_ids, + const MiraGenerationOptions & options); + void release_runtime_graphs(); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::community_models::mira_tts diff --git a/include/engine/community_models/mira_tts/processor.h b/include/engine/community_models/mira_tts/processor.h new file mode 100644 index 000000000..2caf27a8a --- /dev/null +++ b/include/engine/community_models/mira_tts/processor.h @@ -0,0 +1,33 @@ +#pragma once + +#include "engine/community_models/mira_tts/assets.h" +#include "engine/framework/core/execution_context.h" + +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { + +class MiraAcousticProcessor final { +public: + MiraAcousticProcessor( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + size_t graph_context_bytes, + assets::TensorStorageType linear_storage_type, + assets::TensorStorageType conv_storage_type); + ~MiraAcousticProcessor(); + + std::vector process( + const std::vector & speech_codes, + const std::vector & context_codes); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::community_models::mira_tts diff --git a/include/engine/community_models/mira_tts/prompt.h b/include/engine/community_models/mira_tts/prompt.h new file mode 100644 index 000000000..188b38f41 --- /dev/null +++ b/include/engine/community_models/mira_tts/prompt.h @@ -0,0 +1,26 @@ +#pragma once + +#include "engine/community_models/mira_tts/assets.h" + +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { + +class MiraPromptBuilder final { +public: + explicit MiraPromptBuilder(std::shared_ptr assets); + ~MiraPromptBuilder(); + + std::vector build( + const std::string & text, + const std::vector & context_codes) const; + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::community_models::mira_tts diff --git a/include/engine/community_models/mira_tts/session.h b/include/engine/community_models/mira_tts/session.h new file mode 100644 index 000000000..c5998fd8f --- /dev/null +++ b/include/engine/community_models/mira_tts/session.h @@ -0,0 +1,96 @@ +#pragma once + +#include "engine/community_models/mira_tts/assets.h" +#include "engine/framework/model_spec/metadata.h" +#include "engine/framework/runtime/cache_slots.h" +#include "engine/framework/runtime/model.h" +#include "engine/framework/runtime/session_base.h" + +#include +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { + +std::shared_ptr make_mira_tts_loader(); + +class MiraPromptBuilder; +class MiraSpeakerEncoder; +class MiraGenerator; +class MiraAcousticProcessor; +class MiraDecoder; + +class MiraTTSOfflineSession final : public runtime::RuntimeSessionBase, + public runtime::IOfflineVoiceTaskSession, + public runtime::IStreamingVoiceTaskSession { +public: + MiraTTSOfflineSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract); + ~MiraTTSOfflineSession() override; + + std::string family() const override; + runtime::VoiceTaskKind task_kind() const override; + runtime::RunMode run_mode() const override; + void prepare(const runtime::SessionPreparationRequest & request) override; + runtime::TaskResult run(const runtime::TaskRequest & request) override; + runtime::StreamingPolicy streaming_policy() const override; + void start_stream(const runtime::TaskRequest & request) override; + std::optional next_stream_event() override; + void set_stream_event_sink(runtime::StreamEventCallback sink) override; + runtime::TaskResult finish_stream() override; + void reset() override; + runtime::StreamEvent process_audio_chunk(const runtime::AudioChunk & chunk) override; + runtime::TaskResult finalize() override; + +private: + struct ReferenceCacheKey { + int sample_rate = 0; + int channels = 0; + uint64_t sample_count = 0; + uint64_t sample_hash = 0; + }; + + struct ReferenceCacheKeyEqual { + bool operator()( + const ReferenceCacheKey & lhs, + const ReferenceCacheKey & rhs) const noexcept; + }; + + static ReferenceCacheKey make_reference_cache_key( + const runtime::AudioBuffer & audio); + const runtime::AudioBuffer & reference_audio( + const runtime::TaskRequest & request) const; + const std::vector & context_codes( + const runtime::AudioBuffer & reference); + runtime::AudioBuffer synthesize_text( + const std::string & text, + const std::vector & context_codes, + const MiraGenerationOptions & options); + + runtime::TaskSpec task_; + std::shared_ptr assets_; + std::shared_ptr contract_; + std::optional prepared_reference_; + std::unique_ptr prompt_; + std::unique_ptr speaker_encoder_; + std::unique_ptr generator_; + std::unique_ptr processor_; + std::unique_ptr decoder_; + runtime::CacheSlots, ReferenceCacheKeyEqual> + reference_cache_; + std::optional> uncached_context_codes_; + std::vector streaming_context_codes_; + std::vector streaming_text_chunks_; + std::vector streaming_audio_chunks_; + std::optional streaming_generation_; + runtime::StreamEventCallback stream_sink_; + size_t streaming_chunk_index_ = 0; + bool streaming_started_ = false; +}; + +} // namespace engine::community_models::mira_tts diff --git a/include/engine/community_models/mira_tts/speaker_encoder.h b/include/engine/community_models/mira_tts/speaker_encoder.h new file mode 100644 index 000000000..fc58b5055 --- /dev/null +++ b/include/engine/community_models/mira_tts/speaker_encoder.h @@ -0,0 +1,34 @@ +#pragma once + +#include "engine/community_models/mira_tts/assets.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/runtime/model.h" + +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { + +class MiraSpeakerEncoder final { +public: + MiraSpeakerEncoder( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + size_t graph_context_bytes, + assets::TensorStorageType linear_storage_type, + assets::TensorStorageType conv_storage_type); + ~MiraSpeakerEncoder(); + + // Returns the 32 discrete context codes consumed by Mira's prompt and + // acoustic processor. + std::vector encode(const runtime::AudioBuffer & reference_audio); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::community_models::mira_tts diff --git a/include/engine/community_models/sanotts/assets.h b/include/engine/community_models/sanotts/assets.h new file mode 100644 index 000000000..144bf9939 --- /dev/null +++ b/include/engine/community_models/sanotts/assets.h @@ -0,0 +1,91 @@ +#pragma once + +#include "engine/framework/assets/resource_bundle.h" +#include "engine/framework/assets/tensor_source.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::sanotts { + +struct SanoTtsConfig { + int64_t vocab_size = 62; + int64_t sample_rate = 24000; + int64_t hop_length = 256; + int64_t n_fft = 1024; + int64_t mels = 100; + int64_t dim = 0; + int64_t blocks = 0; + int64_t pw_hidden = 0; + int64_t noise_channels = 4; + int64_t dw_kernel = 7; + int64_t embed_kernel = 7; + + int64_t duration_hidden = 0; + int64_t duration_depth = 0; + int64_t duration_kernel = 5; + int64_t duration_max_tokens = 207; + int64_t duration_max_frames = 80; + + int64_t acoustic_hidden = 0; + int64_t acoustic_token_depth = 0; + int64_t acoustic_depth = 0; + int64_t acoustic_kernel = 5; + + std::string voice; +}; + +enum class SanoTtsGraph { + Nano, // mel-100 -> ConvNeXt-1D -> iSTFT, noise-fed (heart, heart-nano) + Piperlite, // 192-ch latent -> 3-stage ConvTranspose1d, deterministic (amy, ...) +}; + +struct SanoTtsPiperConfig { + std::string voice; + std::string language; // short code the session validates against: en, vi, id + std::string espeak_voice; + int64_t sample_rate = 22050; + double duration_length_scale = 1.0; + + int64_t duration_vocab = 0; + int64_t duration_hidden = 0; + int64_t duration_depth = 0; + int64_t duration_kernel = 5; + int64_t duration_max_tokens = 0; + int64_t duration_max_frames = 0; + + int64_t acoustic_vocab = 0; + int64_t acoustic_hidden = 0; + int64_t acoustic_depth = 0; + int64_t acoustic_token_depth = 0; + int64_t acoustic_kernel = 5; + int64_t acoustic_out_channels = 0; + + std::array channels = {0, 0, 0, 0}; + std::array, 3> stage_branches; + int64_t post_filter_channels = 0; + int64_t post_filter_layers = 0; + int64_t post_filter_kernel = 9; + double post_filter_scale = 0.0; + + /** Piper phoneme_id_map: one UTF-8 codepoint -> id. */ + std::unordered_map phoneme_id_map; +}; + +struct SanoTtsAssets { + assets::ResourceBundle resources; + SanoTtsGraph graph = SanoTtsGraph::Nano; + SanoTtsConfig config; // valid when graph == Nano + SanoTtsPiperConfig piper; // valid when graph == Piperlite + std::shared_ptr weights; +}; + +std::shared_ptr load_sanotts_assets( + const std::filesystem::path & model_path); + +} // namespace engine::models::sanotts diff --git a/include/engine/community_models/sanotts/frontend.h b/include/engine/community_models/sanotts/frontend.h new file mode 100644 index 000000000..9ce91e51d --- /dev/null +++ b/include/engine/community_models/sanotts/frontend.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::sanotts { + +/** Thrown by encode() when a chunk phonemizes past the duration model's + * token limit; the session responds by bisecting the chunk. */ +struct SanoTtsTooLongError : std::runtime_error { + using std::runtime_error::runtime_error; +}; + +struct SanoTtsEncoded { + std::vector token_ids; + std::string dropped; // symbols outside the vocabulary, for tracing +}; + +/** + * Text -> the 62-symbol phoneme ids the sanoTTS front end was trained on. + * + * eSpeak-ng produces the IPA; misaki's E2M table then rewrites it into the + * character-level inventory this model uses. Both steps are reproduced from + * the project's own JavaScript and Python front ends so the three agree + * symbol for symbol. + * + * eSpeak-ng is opened at runtime and never linked, matching how inflect_v2 + * treats it: it is GPL-3.0 and must not be embedded in this project. + */ +class SanoTtsFrontend { +public: + SanoTtsFrontend( + std::filesystem::path espeak_library_path, + std::filesystem::path espeak_data_path, + int64_t max_tokens); + ~SanoTtsFrontend(); + + [[nodiscard]] SanoTtsEncoded encode(const std::string & text) const; + + /** Long-form splitting on sentence punctuation, then a codepoint budget. */ + [[nodiscard]] static std::vector split_text( + const std::string & text, + int64_t max_codepoints); + + /** Pause inserted between chunks, longer after a sentence end. */ + [[nodiscard]] static double boundary_pause_seconds(const std::string & chunk); + +private: + struct Impl; + std::unique_ptr impl_; + int64_t max_tokens_; +}; + +/** + * Text -> Piper phoneme ids for the piperlite voices. + * + * Reproduces piper's phonemize_espeak / phonemes_to_ids convention through + * the same eSpeak-ng library: phonemizer-style punctuation preservation, + * NFD decomposition to single codepoints, then the voice's phoneme_id_map + * with [BOS, PAD, (id, PAD)..., EOS] framing. Deterministic per voice. + */ +class SanoTtsPiperFrontend { +public: + SanoTtsPiperFrontend( + std::filesystem::path espeak_library_path, + std::filesystem::path espeak_data_path, + std::string espeak_voice, + std::unordered_map phoneme_id_map, + int64_t max_tokens); + ~SanoTtsPiperFrontend(); + + [[nodiscard]] SanoTtsEncoded encode(const std::string & text) const; + +private: + struct Impl; + std::unique_ptr impl_; + std::unordered_map id_map_; + int64_t max_tokens_; +}; + +} // namespace engine::models::sanotts diff --git a/include/engine/community_models/sanotts/piper_runtime.h b/include/engine/community_models/sanotts/piper_runtime.h new file mode 100644 index 000000000..77d08a01e --- /dev/null +++ b/include/engine/community_models/sanotts/piper_runtime.h @@ -0,0 +1,34 @@ +#pragma once + +#include "engine/community_models/sanotts/assets.h" +#include "engine/framework/runtime/session.h" + +#include +#include +#include + +namespace engine::models::sanotts { + +struct SanoTtsPiperGenerationOptions { + /** Multiplier on the voice's tuned duration_length_scale; larger is + * slower. The decoder is deterministic -- there is no seed. */ + float speaking_rate = 1.0F; +}; + +class SanoTtsPiperRuntime { +public: + SanoTtsPiperRuntime( + std::shared_ptr assets, + core::BackendConfig backend_config); + ~SanoTtsPiperRuntime(); + + runtime::AudioBuffer synthesize( + const std::vector & token_ids, + const SanoTtsPiperGenerationOptions & options); + +private: + struct State; + std::unique_ptr state_; +}; + +} // namespace engine::models::sanotts diff --git a/include/engine/community_models/sanotts/runtime.h b/include/engine/community_models/sanotts/runtime.h new file mode 100644 index 000000000..750cb8759 --- /dev/null +++ b/include/engine/community_models/sanotts/runtime.h @@ -0,0 +1,41 @@ +#pragma once + +#include "engine/community_models/sanotts/assets.h" +#include "engine/framework/runtime/session.h" + +#include +#include +#include +#include + +namespace engine::models::sanotts { + +struct SanoTtsGenerationOptions { + /** Duration multiplier applied before rounding; larger is slower. */ + float speaking_rate = 1.0F; + /** Decoder noise seed; the session resolves the derive-from-text default + * before calling the runtime, so this is always the final seed. */ + uint64_t seed = 0; +}; + +class SanoTtsNativeRuntime { +public: + SanoTtsNativeRuntime( + std::shared_ptr assets, + core::BackendConfig backend_config); + ~SanoTtsNativeRuntime(); + + runtime::AudioBuffer synthesize( + const std::vector & token_ids, + const SanoTtsGenerationOptions & options); + +private: + struct State; + std::unique_ptr state_; +}; + +/** int.from_bytes(sha256(text).digest()[:8], "big") -- the seed the reference + * implementations derive when the caller does not pass one. */ +uint64_t sanotts_text_seed(const std::string & text); + +} // namespace engine::models::sanotts diff --git a/include/engine/community_models/sanotts/session.h b/include/engine/community_models/sanotts/session.h new file mode 100644 index 000000000..464737ad6 --- /dev/null +++ b/include/engine/community_models/sanotts/session.h @@ -0,0 +1,43 @@ +#pragma once + +#include "engine/community_models/sanotts/assets.h" +#include "engine/community_models/sanotts/frontend.h" +#include "engine/community_models/sanotts/piper_runtime.h" +#include "engine/community_models/sanotts/runtime.h" +#include "engine/framework/model_spec/metadata.h" +#include "engine/framework/runtime/session_base.h" + +#include + +namespace engine::models::sanotts { + +std::shared_ptr make_sanotts_loader(); + +class SanoTtsSession final + : public runtime::RuntimeSessionBase + , public runtime::IOfflineVoiceTaskSession { +public: + SanoTtsSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract); + ~SanoTtsSession() override; + + std::string family() const override; + runtime::VoiceTaskKind task_kind() const override; + runtime::RunMode run_mode() const override; + void prepare(const runtime::SessionPreparationRequest & request) override; + runtime::TaskResult run(const runtime::TaskRequest & request) override; + +private: + runtime::TaskSpec task_; + std::shared_ptr assets_; + std::shared_ptr contract_; + std::unique_ptr frontend_; // nano graph + std::unique_ptr runtime_; // nano graph + std::unique_ptr piper_frontend_; // piperlite graph + std::unique_ptr piper_runtime_; // piperlite graph +}; + +} // namespace engine::models::sanotts diff --git a/include/engine/community_models/sopro_tts/acoustic.h b/include/engine/community_models/sopro_tts/acoustic.h new file mode 100644 index 000000000..44510c202 --- /dev/null +++ b/include/engine/community_models/sopro_tts/acoustic.h @@ -0,0 +1,69 @@ +#pragma once + +#include "engine/community_models/sopro_tts/assets.h" + +#include +#include +#include +#include + +namespace engine::core { +class ExecutionContext; +} +namespace engine::assets { +enum class TensorStorageType; +} + +namespace engine::community_models::sopro_tts { + +struct SoproAcousticWeights; +struct SoproAcousticGraphs; + +struct SoproAcousticRequest { + // Reference tokens followed by the generated ones; the acoustic head sees + // the whole span so the prompt mel and the new audio stay phase-coherent. + std::vector semantic_tokens; + std::vector cond_vec; // [cond_hidden_dim] + std::vector prompt_mel; // [n_mels, prompt_frames], normalised + int64_t prompt_frames = 0; + int64_t total_frames = 0; + int64_t steps = 2; + uint64_t seed = 0; +}; + +// sopro/nn/acoustic.py AcousticHead.solve, offline (unchunked) path: a +// rectified-flow DiT with adaptive layer norm conditioning, solved with the +// Euler steps of a sway-sampled time grid while the prompt frames are pinned +// to the reference mel at every step. +class SoproAcousticRuntime final { +public: + SoproAcousticRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t weight_context_bytes, + size_t graph_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type); + ~SoproAcousticRuntime(); + + SoproAcousticRuntime(const SoproAcousticRuntime &) = delete; + SoproAcousticRuntime & operator=(const SoproAcousticRuntime &) = delete; + + // Returns the normalised mel [n_mels, total_frames], channel-major. + std::vector solve(const SoproAcousticRequest & request) const; + +private: + const SoproModelConfig & config_; + engine::core::ExecutionContext & execution_context_; + size_t graph_context_bytes_ = 0; + std::shared_ptr weights_; + mutable std::unique_ptr graphs_; +}; + +// build_time_grid: linspace(0, 1, steps + 1) warped by the sway coefficient. +std::vector build_time_grid(int64_t steps, float sway_coefficient); + +// sinusoidal_time_embedding(t, dim, scale=1000). +std::vector sinusoidal_time_embedding(float t, int64_t dim, float scale = 1000.0F); + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/sopro_tts/assets.h b/include/engine/community_models/sopro_tts/assets.h new file mode 100644 index 000000000..57b222734 --- /dev/null +++ b/include/engine/community_models/sopro_tts/assets.h @@ -0,0 +1,196 @@ +#pragma once + +#include "engine/framework/assets/resource_bundle.h" +#include "engine/framework/assets/tensor_source.h" + +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { + +// samuel-vitorino/sopro-v2-turbo. Four stages run per request: +// speaker encoder (16 kHz reference -> id/style/style-ctrl embeddings) +// semantic encoder (24 kHz reference -> FSQ semantic token ids) +// semantic LM (text + style prefix + prompt tokens -> semantic tokens) +// acoustic head (semantic tokens -> mel, rectified-flow Euler solve) +// vocoder (mel -> 24 kHz waveform, Vocos ConvNeXt + ISTFT head) +// Every field below mirrors one key of the checkpoint's config.json; nothing +// about the architecture is hardcoded so a retrained variant loads unchanged. + +// config.json -> "model" +struct SoproModelConfig { + int64_t latent_dim = 1280; + int64_t semantic_vocab_size = 4375; + int64_t text_vocab_size = 8192; + int64_t max_text_len = 2048; + int64_t cond_in_dim = 328; // id_emb + style_emb + style_ctrl + int64_t cond_hidden_dim = 512; + + int64_t ar_model_dim = 512; + int64_t ar_blocks = 12; + int64_t ar_heads = 8; + int64_t ar_kv_heads = 8; // defaults to ar_heads when absent + float ar_ffn_mult = 4.0F; + bool ar_qk_rms_norm = true; + int64_t style_prefix_tokens = 8; + + int64_t acoustic_time_embed_dim = 256; + float acoustic_sway_sampling_coef = -1.0F; + int64_t acoustic_upsampler_kernel_size = 3; + int64_t acoustic_dit_dim = 512; + int64_t acoustic_dit_depth = 8; + int64_t acoustic_dit_heads = 8; + int64_t acoustic_dit_dim_head = 64; + float acoustic_dit_ff_mult = 2.0F; + int64_t acoustic_mu_dim = 100; // defaults to acoustic_mel_n_mels + int64_t acoustic_spk_dim = 80; + int64_t acoustic_pre_lookahead_frames = 3; + int64_t acoustic_pos_kernel_size = 31; + float acoustic_sigma_min = 1.0e-6F; + int64_t acoustic_num_left_chunks = -1; + int64_t acoustic_mel_n_mels = 100; + int64_t acoustic_mel_hop_length = 256; + std::vector acoustic_mel_mean; + std::vector acoustic_mel_std; + + // BOS/EOS live just past the FSQ codebook; the AR head is + // Linear(dim -> semantic_vocab_size + 2). + int64_t semantic_bos_id() const noexcept { return semantic_vocab_size; } + int64_t semantic_eos_id() const noexcept { return semantic_vocab_size + 1; } + int64_t ar_head_dim() const noexcept { return ar_model_dim / ar_heads; } + int64_t ar_ffn_dim() const noexcept; + int64_t acoustic_dit_ff_dim() const noexcept; +}; + +// config.json -> "semantic_encoder" +struct SoproSemanticEncoderConfig { + int64_t n_mels = 80; + int64_t d_model = 512; + int64_t layers = 6; + int64_t heads = 8; + int64_t ffn_dim = 2048; + int64_t max_positions = 1500; + std::vector fsq_levels{7, 5, 5, 5, 5}; + int64_t sample_rate = 16000; + int64_t n_fft = 400; + int64_t hop_length = 160; + int64_t token_samples_24k = 1024; + + int64_t head_dim() const noexcept { return d_model / heads; } + int64_t digit_dim() const noexcept; // sum(fsq_levels) + int64_t codebook_size() const noexcept; // prod(fsq_levels) +}; + +// config.json -> "speaker_encoder" +struct SoproSpeakerEncoderConfig { + int64_t sample_rate = 16000; + int64_t n_mels = 80; + int64_t n_fft = 1024; + int64_t win_length = 400; + int64_t hop_length = 160; + float f_min = 20.0F; + float f_max = 7600.0F; + float mel_log_floor = 1.0e-5F; + int64_t stem_channels = 128; + std::vector stage_channels{160, 192, 224}; + std::vector blocks_per_stage{4, 4, 4}; + std::vector dilation_cycle{1, 2, 4, 8}; + int64_t depthwise_kernel_size = 5; + int64_t se_reduction = 8; + int64_t id_emb_dim = 192; + int64_t style_emb_dim = 128; + int64_t style_ctrl_dim = 8; + int64_t id_head_hidden = 256; + int64_t style_head_hidden = 256; + int64_t attn_hidden = 128; +}; + +// config.json -> "vocoder" / "vocoder_streaming" +struct SoproVocoderConfig { + int64_t sample_rate = 24000; + int64_t n_fft = 1024; + int64_t hop_length = 256; + int64_t n_mels = 100; + int64_t dim = 512; + int64_t intermediate_dim = 1536; + int64_t num_layers = 14; + float max_magnitude = 100.0F; + // sopro/config.py VocoderConfig.band_limit_hz. Zero (or a negative value) + // disables the cut; the published checkpoints do not carry the key, so the + // default has to match the reference dataclass. + float band_limit_hz = 10900.0F; + bool causal = false; + int64_t lookahead_frames = 0; + std::vector block_lookaheads; +}; + +// config.json -> "generation" +struct SoproGenerationConfig { + float temperature = 0.8F; + float top_p = 0.9F; + int64_t top_k = 25; + int64_t steps = 2; + float max_seconds = 30.0F; + float min_seconds = 0.4F; + int64_t max_segment_chars = 300; + float ref_seconds = 10.0F; + int64_t style_tokens = 160; + int64_t prompt_tokens = 120; + int64_t stream_chunk_frames = 64; +}; + +struct SoproTTSConfig { + int64_t sample_rate = 24000; + SoproModelConfig model; + SoproSemanticEncoderConfig semantic_encoder; + SoproSpeakerEncoderConfig speaker_encoder; + SoproVocoderConfig vocoder; + SoproVocoderConfig vocoder_streaming; + SoproGenerationConfig generation; + + // Mel frames produced per semantic token (token_samples_24k / mel hop). + int64_t hop_ratio() const noexcept; +}; + +struct SoproTTSAssets { + assets::ResourceBundle resources; + SoproTTSConfig config; + std::shared_ptr model_weights; + std::shared_ptr semantic_encoder_weights; + std::shared_ptr speaker_encoder_weights; + std::shared_ptr vocoder_weights; + std::filesystem::path tokenizer_path; +}; + +// Per-request knobs; defaults come from config.json "generation". +struct SoproRequestOptions { + std::string language; // "", en, pt, fr, de + float temperature = 0.8F; + float top_p = 0.9F; + int64_t top_k = 25; + int64_t steps = 2; + float max_seconds = 30.0F; + float min_seconds = 0.4F; + int64_t max_segment_chars = 300; + float ref_seconds = 10.0F; + uint64_t seed = 0; + bool has_seed = false; +}; + +std::shared_ptr load_sopro_tts_assets( + const std::filesystem::path & model_path); + +// The front ends reuse the analysis window and mel filterbank that torchaudio +// stores as persistent buffers instead of rebuilding them, which is what keeps +// them comparable with the reference pipeline. Fail early and say why when a +// checkpoint was exported without them. +void require_frontend_buffers( + const assets::TensorSource & source, + const char * stage, + std::initializer_list tensor_names); + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/sopro_tts/reference.h b/include/engine/community_models/sopro_tts/reference.h new file mode 100644 index 000000000..4e75195e3 --- /dev/null +++ b/include/engine/community_models/sopro_tts/reference.h @@ -0,0 +1,105 @@ +#pragma once + +#include "engine/community_models/sopro_tts/assets.h" + +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { + +class SoproSpeakerEncoderRuntime; +class SoproSemanticEncoderRuntime; +class SoproVocoderRuntime; + +// Ports of sopro/audio.py. All of them operate on mono float waveforms. +namespace audio_ops { + +constexpr float kPromptLevelDb = -19.8F; +constexpr float kOutputLevelDb = -23.0F; +constexpr float kLimiterKnee = 0.9F; +constexpr float kLeadInSeconds = 0.08F; +constexpr float kSegmentLeadSeconds = 0.30F; +constexpr float kSegmentSkipSeconds = 0.10F; +constexpr float kTrailSeconds = 0.30F; +constexpr float kJoinFadeSeconds = 0.01F; +constexpr float kFinalFadeSeconds = 0.08F; + +struct SpeechLevel { + float level_db = 0.0F; + float active_seconds = 0.0F; +}; + +// normalize_reference returns the boosted waveform together with the speech +// level it ends up at, so the output gain can be derived from the reference +// the model actually heard. +struct NormalizedReference { + std::vector wav; + float level_db = kPromptLevelDb; +}; + +std::vector crop_on_pause( + const std::vector & wav, float target_seconds, int sample_rate, std::mt19937_64 & rng); +SpeechLevel speech_level_db(const std::vector & wav, int sample_rate); +// Boost-only and peak-guarded: a reference already at or above the prompt level +// is left alone, and the boost never pushes the peak past 0.95. +NormalizedReference normalize_reference(const std::vector & wav, int sample_rate); +float output_gain(float prompt_level_db = kPromptLevelDb); +float match_gain( + const std::vector & wav, int sample_rate, float target_db = kOutputLevelDb, + float prompt_level_db = kPromptLevelDb); +void soft_limit(std::vector & wav, float knee = kLimiterKnee); +std::optional speech_onset(const std::vector & wav, int sample_rate); +std::vector trim_lead( + const std::vector & wav, int sample_rate, + float lead = kLeadInSeconds, float skip = 0.0F); +std::vector trim_trail( + const std::vector & wav, int sample_rate, float trail = kTrailSeconds); +void fade_edges( + std::vector & wav, int sample_rate, bool fade_in, bool fade_out, + float fade_seconds = kJoinFadeSeconds); +std::vector join_segments(std::vector> parts, int sample_rate); + +} // namespace audio_ops + +// The per-voice state the semantic LM and the acoustic head both condition on. +struct SoproReference { + std::vector cond_vec; // [cond_hidden_dim] + std::vector semantic_tokens; // one id per 1024 reference samples + std::vector mel; // [n_mels, mel_frames], normalised + int64_t mel_frames = 0; + // Speech level of the normalised reference, i.e. what the output gain is + // derived from (Reference.level_db). + float level_db = audio_ops::kPromptLevelDb; +}; + +// SoproTTS.prepare_reference: crop on a pause, level-normalise, then run the +// speaker encoder, the semantic encoder and the analysis mel in one pass. +class SoproReferenceBuilder final { +public: + SoproReferenceBuilder( + const SoproTTSAssets & assets, + const SoproSpeakerEncoderRuntime & speaker_encoder, + const SoproSemanticEncoderRuntime & semantic_encoder, + const SoproVocoderRuntime & vocoder); + + // audio24: mono 24 kHz reference waveform. + SoproReference build( + const std::vector & audio24, float ref_seconds, std::mt19937_64 & rng) const; + +private: + const SoproTTSConfig & config_; + const SoproSpeakerEncoderRuntime & speaker_encoder_; + const SoproSemanticEncoderRuntime & semantic_encoder_; + const SoproVocoderRuntime & vocoder_; + // SoproModel.cond_proj = Sequential(Linear, SiLU, Identity, Linear). + std::vector cond_proj_w0; + std::vector cond_proj_b0; + std::vector cond_proj_w3; + std::vector cond_proj_b3; + std::vector mel_mean_; + std::vector mel_std_; +}; + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/sopro_tts/semantic_encoder.h b/include/engine/community_models/sopro_tts/semantic_encoder.h new file mode 100644 index 000000000..4a386ebda --- /dev/null +++ b/include/engine/community_models/sopro_tts/semantic_encoder.h @@ -0,0 +1,54 @@ +#pragma once + +#include "engine/community_models/sopro_tts/assets.h" + +#include +#include +#include +#include + +namespace engine::core { +class ExecutionContext; +} +namespace engine::assets { +enum class TensorStorageType; +} + +namespace engine::community_models::sopro_tts { + +struct SoproSemanticEncoderWeights; +struct SoproSemanticEncoderGraph; + +// sopro/encoders/semantic.py. A Whisper-style log-mel front end and two +// striding convolutions feed six non-causal transformer layers; the result is +// resampled to one frame per 1024 output samples and quantised by a finite +// scalar quantiser whose per-level arg-maxes are packed into a single id. +class SoproSemanticEncoderRuntime final { +public: + SoproSemanticEncoderRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t weight_context_bytes, + size_t graph_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type); + ~SoproSemanticEncoderRuntime(); + + SoproSemanticEncoderRuntime(const SoproSemanticEncoderRuntime &) = delete; + SoproSemanticEncoderRuntime & operator=(const SoproSemanticEncoderRuntime &) = delete; + + // audio24: mono 24 kHz reference waveform. Returns ceil(n / 1024) token ids. + std::vector encode(const std::vector & audio24) const; + +private: + const SoproSemanticEncoderConfig & config_; + // Rate the caller's reference waveform arrives at (config.json sample_rate), + // kept so the resample and its pinned length are not tied to 24 kHz. + int64_t source_sample_rate_ = 0; + engine::core::ExecutionContext & execution_context_; + size_t graph_context_bytes_ = 0; + std::shared_ptr weights_; + mutable std::unique_ptr graph_; +}; + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/sopro_tts/semantic_lm.h b/include/engine/community_models/sopro_tts/semantic_lm.h new file mode 100644 index 000000000..fb91a3ad0 --- /dev/null +++ b/include/engine/community_models/sopro_tts/semantic_lm.h @@ -0,0 +1,75 @@ +#pragma once + +#include "engine/community_models/sopro_tts/assets.h" + +#include +#include +#include +#include +#include + +namespace engine::core { +class ExecutionContext; +} +namespace engine::assets { +enum class TensorStorageType; +} + +namespace engine::community_models::sopro_tts { + +struct SoproSemanticLMOptions { + int64_t max_steps = 1; + int64_t min_steps = 1; + float temperature = 0.8F; + float top_p = 0.9F; + int64_t top_k = 25; +}; + +// sopro/nn/ar.py + SoproModel.stream_semantic_tokens. The prompt is +// [style prefix | text | carried semantic tokens | BOS] and the model +// autoregresses semantic ids until EOS or the step budget. +// +// The stack is a standard pre-norm transformer with QK RMS-norm, SwiGLU and +// half-rotation RoPE, so it maps onto the shared Qwen decoder runtime once the +// per-branch LayerScale vectors are folded into the output projections. +class SoproSemanticLMRuntime final { +public: + SoproSemanticLMRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t prefill_graph_arena_bytes, + size_t decode_graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType weight_storage_type); + ~SoproSemanticLMRuntime(); + + SoproSemanticLMRuntime(const SoproSemanticLMRuntime &) = delete; + SoproSemanticLMRuntime & operator=(const SoproSemanticLMRuntime &) = delete; + + std::vector generate( + const std::vector & text_ids, + const std::vector & style_tokens, + const std::vector & prompt_tokens, + const SoproSemanticLMOptions & options, + std::mt19937_64 & rng) const; + + void release_runtime_graphs(); + +private: + class Impl; + std::unique_ptr impl_; +}; + +// sopro/sampling.py sample_next_token, exposed for testing. `logits` is +// modified in place. +int32_t sample_next_token( + std::vector & logits, + float temperature, + float top_p, + int64_t top_k, + int32_t bos_id, + int32_t eos_id, + bool allow_eos, + std::mt19937_64 & rng); + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/sopro_tts/session.h b/include/engine/community_models/sopro_tts/session.h new file mode 100644 index 000000000..44f5a5bc4 --- /dev/null +++ b/include/engine/community_models/sopro_tts/session.h @@ -0,0 +1,83 @@ +#pragma once + +#include "engine/community_models/sopro_tts/assets.h" +#include "engine/framework/model_spec/metadata.h" +#include "engine/framework/runtime/model.h" +#include "engine/framework/runtime/session_base.h" + +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { + +std::shared_ptr make_sopro_tts_loader(); + +class SoproAcousticRuntime; +class SoproReferenceBuilder; +class SoproSemanticEncoderRuntime; +class SoproSemanticLMRuntime; +class SoproSpeakerEncoderRuntime; +class SoproTextTokenizer; +class SoproVocoderRuntime; + +// Everything one synthesis run carries between its text segments: the parsed +// options, the encoded reference voice, the LM carry-over prompt and the RNG. +// Offline builds one and drains it in a loop; streaming keeps it alive across +// next_stream_event calls, so both paths consume the seed in the same order. +struct SoproSynthesisState; + +class SoproTTSSession final : public runtime::RuntimeSessionBase, + public runtime::IOfflineVoiceTaskSession, + public runtime::IStreamingVoiceTaskSession { +public: + SoproTTSSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract); + ~SoproTTSSession() override; + + std::string family() const override; + runtime::VoiceTaskKind task_kind() const override; + runtime::RunMode run_mode() const override; + void prepare(const runtime::SessionPreparationRequest & request) override; + runtime::TaskResult run(const runtime::TaskRequest & request) override; + + runtime::StreamingPolicy streaming_policy() const override; + void start_stream(const runtime::TaskRequest & request) override; + std::optional next_stream_event() override; + void set_stream_event_sink(runtime::StreamEventCallback sink) override; + runtime::TaskResult finish_stream() override; + void reset() override; + runtime::StreamEvent process_audio_chunk(const runtime::AudioChunk & chunk) override; + runtime::TaskResult finalize() override; + +private: + SoproRequestOptions parse_options(const runtime::TaskRequest & request) const; + // Validates the request, encodes the reference voice and splits the text. + std::unique_ptr begin_synthesis(const runtime::TaskRequest & request); + // Runs one text segment through the LM, the acoustic head and the vocoder. + // Returns the raw 24 kHz waveform before any levelling, or an empty vector + // when the segment generated nothing. + std::vector synthesize_segment(SoproSynthesisState & state); + + runtime::TaskSpec task_; + std::shared_ptr assets_; + std::shared_ptr contract_; + std::string default_language_; + + std::unique_ptr tokenizer_; + std::unique_ptr speaker_encoder_; + std::unique_ptr semantic_encoder_; + std::unique_ptr vocoder_; + std::unique_ptr semantic_lm_; + std::unique_ptr acoustic_; + std::unique_ptr reference_builder_; + + std::unique_ptr stream_state_; + std::vector stream_chunks_; +}; + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/sopro_tts/speaker_encoder.h b/include/engine/community_models/sopro_tts/speaker_encoder.h new file mode 100644 index 000000000..07ba07346 --- /dev/null +++ b/include/engine/community_models/sopro_tts/speaker_encoder.h @@ -0,0 +1,63 @@ +#pragma once + +#include "engine/community_models/sopro_tts/assets.h" + +#include +#include +#include +#include + +namespace engine::core { +class ExecutionContext; +} +namespace engine::assets { +enum class TensorStorageType; +} + +namespace engine::community_models::sopro_tts { + +struct SoproSpeakerWeights; +struct SoproSpeakerGraph; + +struct SoproSpeakerEmbeddings { + std::vector id_emb; // id_emb_dim, L2-normalised + std::vector style_emb; // style_emb_dim + std::vector style_ctrl; // style_ctrl_dim +}; + +// sopro/encoders/speaker.py. A log-mel front end feeding a three-stage +// gated depthwise ResNet with squeeze-excite, then two pooling heads: +// attentive statistics for speaker identity and multi-scale mean/std for +// style. The convolution trunk runs on the backend; the pooling heads and +// their small MLPs run on the host, where they cost a few hundred kFLOP. +class SoproSpeakerEncoderRuntime final { +public: + SoproSpeakerEncoderRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t weight_context_bytes, + size_t graph_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type); + ~SoproSpeakerEncoderRuntime(); + + SoproSpeakerEncoderRuntime(const SoproSpeakerEncoderRuntime &) = delete; + SoproSpeakerEncoderRuntime & operator=(const SoproSpeakerEncoderRuntime &) = delete; + + // audio16: mono 16 kHz reference waveform. + SoproSpeakerEmbeddings encode(const std::vector & audio16) const; + + int64_t sample_rate() const noexcept; + +private: + // Returns the fused trunk features [channels, frames], channel-major. + std::vector trunk(const std::vector & log_mel, int64_t frames, int64_t & out_frames) const; + + const SoproSpeakerEncoderConfig & config_; + engine::core::ExecutionContext & execution_context_; + size_t graph_context_bytes_ = 0; + std::shared_ptr weights_; + mutable std::unique_ptr graph_; +}; + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/sopro_tts/text_tokenizer.h b/include/engine/community_models/sopro_tts/text_tokenizer.h new file mode 100644 index 000000000..e270588f1 --- /dev/null +++ b/include/engine/community_models/sopro_tts/text_tokenizer.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { + +// Mirrors sopro/text.py. The reference pipeline is deliberately minimal: no +// grapheme-to-phoneme stage, just light punctuation clean-up, an optional +// language tag and a SentencePiece unigram model with 8192 pieces. + +// sopro.text.split_text: sentence -> clause -> word packing, codepoint budget. +std::vector split_text(const std::string & text, int64_t max_chars); + +// sopro.text.normalize_text. +std::string normalize_text(const std::string & text); + +// sopro.text.language_tag; throws for anything outside {en, pt, fr, de}. +// An empty language yields an empty tag. +std::string language_tag(const std::string & language); + +class SoproTextTokenizer { +public: + explicit SoproTextTokenizer(const std::filesystem::path & model_path, int64_t max_length = 512); + ~SoproTextTokenizer(); + + SoproTextTokenizer(const SoproTextTokenizer &) = delete; + SoproTextTokenizer & operator=(const SoproTextTokenizer &) = delete; + + // [bos] + pieces(normalize_text(tag + text)) + [eos], truncated to + // max_length; never empty (falls back to the unk id). + std::vector encode(const std::string & text, const std::string & language) const; + + int32_t bos_id() const noexcept; + int32_t eos_id() const noexcept; + int32_t unk_id() const noexcept; + int64_t vocab_size() const noexcept; + +private: + class Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/sopro_tts/vocoder.h b/include/engine/community_models/sopro_tts/vocoder.h new file mode 100644 index 000000000..91b0e33d6 --- /dev/null +++ b/include/engine/community_models/sopro_tts/vocoder.h @@ -0,0 +1,70 @@ +#pragma once + +#include "engine/community_models/sopro_tts/assets.h" + +#include +#include +#include +#include + +namespace engine::core { +class ExecutionContext; +} +namespace engine::assets { +enum class TensorStorageType; +} + +namespace engine::community_models::sopro_tts { + +struct SoproVocoderWeights; +struct SoproVocoderGraph; + +// ISTFTHead band limit (sopro/vocoder.py band_limit_bin): the first FFT bin the +// head zeroes, i.e. how many of the n_fft/2 + 1 bins it actually synthesises. +// A band_limit_hz of zero or less keeps every bin. +int64_t band_limit_bin(const SoproVocoderConfig & config); + +// sopro/vocoder.py, offline path. A Vocos backbone (Conv1d embed, 14 ConvNeXt +// blocks with per-channel gamma, final LayerNorm) feeding one ISTFT head: +// Linear(dim -> n_fft + 2) split into log-magnitude and phase, then a single +// centred inverse STFT with the checkpoint's Hann window. +// +// The same object also owns the analysis mel filterbank, because the acoustic +// stage conditions on the reference mel produced by exactly this extractor. +class SoproVocoderRuntime final { +public: + SoproVocoderRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t weight_context_bytes, + size_t graph_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type); + ~SoproVocoderRuntime(); + + SoproVocoderRuntime(const SoproVocoderRuntime &) = delete; + SoproVocoderRuntime & operator=(const SoproVocoderRuntime &) = delete; + + // mel: [n_mels, frames], channel-major (mel[c * frames + t]). + // Returns (frames - 1) * hop_length mono samples at config.sample_rate. + std::vector decode(const std::vector & mel, int64_t frames) const; + + // MelFeatures.forward: log(clamp(|STFT|, min=1e-7)) with the torchaudio + // MelSpectrogram buffers stored in the checkpoint (power=1, centred). + // Returns [n_mels, frames] channel-major. + std::vector log_mel(const std::vector & audio) const; + + int64_t mel_frames(int64_t samples) const noexcept; + int64_t hop_length() const noexcept; + int64_t n_mels() const noexcept; + int sample_rate() const noexcept; + +private: + const SoproVocoderConfig & config_; + engine::core::ExecutionContext & execution_context_; + size_t graph_context_bytes_ = 0; + std::shared_ptr weights_; + mutable std::unique_ptr graph_; +}; + +} // namespace engine::community_models::sopro_tts diff --git a/include/engine/community_models/vibeasr/assets.h b/include/engine/community_models/vibeasr/assets.h new file mode 100644 index 000000000..d7b77d580 --- /dev/null +++ b/include/engine/community_models/vibeasr/assets.h @@ -0,0 +1,109 @@ +#pragma once + +// VibeASR assets: the I8_S audio VAE encoder and the ternary I2_S Qwen2 decoder. +// +// Ported from https://github.com/microsoft/VibeASR.cpp (src/vae.cpp, src/lm.cpp). + +#include "engine/framework/assets/resource_bundle.h" +#include "engine/framework/assets/tensor_source.h" + +#include +#include +#include +#include +#include + +namespace engine::community_models::vibeasr { + +// One ConvNeXt-style block inside a stage. +struct VaeBlockConfig { + int64_t channels = 0; + // Padded depthwise kernel width. The converter left-pads the real kernel + // (7 taps) up to a SIMD-friendly width with leading zeros, so convolving + // with the padded width and a matching causal left pad is bit-exact with + // convolving the unpadded kernel. + int64_t kernel_size = 0; + int64_t ffn_hidden = 0; +}; + +struct VaeStageConfig { + // Strided causal conv that enters the stage. + int64_t downsample_kernel_size = 0; + int64_t downsample_stride = 0; + int64_t in_channels = 0; + int64_t out_channels = 0; + std::vector blocks; +}; + +// One of the two encoder branches (acoustic / semantic). Both share the layout +// and differ only in latent width and stage depths. +struct VaeBranchConfig { + std::string prefix; // "acoustic" or "semantic" + std::vector stages; + int64_t head_kernel_size = 0; // padded causal kernel of the latent head + int64_t latent_dim = 0; // head output width + int64_t connector_hidden = 0; // connector output width, i.e. LM hidden size + int64_t total_stride = 0; // product of the stage strides + + // Downsampling factor from waveform samples to encoder frames. + [[nodiscard]] int64_t frames_for_samples(int64_t num_samples) const; +}; + +struct VibeASRVaeConfig { + VaeBranchConfig acoustic; + VaeBranchConfig semantic; + // VibeASR's graph hardcodes 1e-5 for every RMS norm, including the ones the + // checkpoint metadata labels 1e-6. The published weights were validated + // against the hardcoded value, so the port keeps it. + float rms_norm_eps = 1e-5f; +}; + +struct VibeASRVaeAssets { + std::shared_ptr source; + VibeASRVaeConfig config; +}; + +// Derives the encoder geometry from the tensor table instead of GGUF metadata: +// stage depths come from which block tensors are present, channel counts and +// kernel widths from the weight shapes. That keeps the loader working for any +// VibeASR VAE checkpoint with this topology, and avoids trusting metadata the +// reference implementation itself ignores. +VibeASRVaeConfig derive_vae_config(const assets::TensorSource & source); + +std::shared_ptr load_vibeasr_vae_assets(const std::filesystem::path & model_path); + +// Same, for a tensor source already opened from a resource bundle. +std::shared_ptr make_vibeasr_vae_assets( + std::shared_ptr source); + +// Decoder geometry. Unlike the encoder, none of this is recoverable from the +// tensor shapes alone -- head_dim, rope_theta and the RMS norm epsilon are not +// implied by any weight -- so it comes from the GGUF qwen2.* metadata block. +struct VibeASRLmConfig { + int64_t vocab_size = 0; + int64_t hidden_size = 0; + int64_t intermediate_size = 0; + int64_t num_hidden_layers = 0; + int64_t num_attention_heads = 0; + int64_t num_key_value_heads = 0; + int64_t head_dim = 0; + int64_t max_position_embeddings = 0; + // 1e-6 for the published checkpoint. Note this is *not* the encoder's + // epsilon: the VAE graph hardcodes 1e-5 (see VibeASRVaeConfig). + float rms_norm_eps = 1e-6f; + float rope_theta = 1e6f; +}; + +// The two GGUF halves plus the tokenizer files, as named by model_specs/vibeasr.json. +struct VibeASRAssets { + assets::ResourceBundle resources; + std::shared_ptr vae; + std::shared_ptr lm_weights; + VibeASRLmConfig lm; +}; + +VibeASRLmConfig derive_lm_config(const assets::TensorSource & source); + +std::shared_ptr load_vibeasr_assets(const std::filesystem::path & model_path); + +} // namespace engine::community_models::vibeasr diff --git a/include/engine/community_models/vibeasr/lm_decoder.h b/include/engine/community_models/vibeasr/lm_decoder.h new file mode 100644 index 000000000..7080f4b1d --- /dev/null +++ b/include/engine/community_models/vibeasr/lm_decoder.h @@ -0,0 +1,62 @@ +#pragma once + +// VibeASR language model: the Qwen2 causal decoder whose projections are stored +// as ternary GGML_TYPE_I2_S. Speech features from the VAE encoder replace the +// prompt's <|speech_pad|> placeholders before prefill. +// +// Ported from https://github.com/microsoft/VibeASR.cpp (src/lm.cpp). + +#include "engine/community_models/vibeasr/assets.h" +#include "engine/framework/core/execution_context.h" + +#include +#include +#include +#include + +namespace engine::community_models::vibeasr { + +struct VibeASRLmPrompt { + std::vector input_ids; + // Positions in input_ids occupied by <|speech_pad|>, in order. + std::vector speech_positions; +}; + +// Summed acoustic + semantic connector output, row-major [tokens][hidden_size]. +struct VibeASRSpeechEmbeddings { + int64_t tokens = 0; + int64_t hidden_size = 0; + std::vector values; +}; + +struct VibeASRGenerationOptions { + int64_t max_new_tokens = 1024; + std::vector eos_token_ids; +}; + +class VibeASRLmRuntime { +public: + VibeASRLmRuntime( + std::shared_ptr weights_source, + const VibeASRLmConfig & config, + core::ExecutionContext & execution, + size_t prefill_graph_arena_bytes, + size_t decode_graph_arena_bytes, + size_t weight_context_bytes); + ~VibeASRLmRuntime(); + + VibeASRLmRuntime(const VibeASRLmRuntime &) = delete; + VibeASRLmRuntime & operator=(const VibeASRLmRuntime &) = delete; + + // Greedy decode. Stops at any eos id or after max_new_tokens. + std::vector generate( + const VibeASRLmPrompt & prompt, + const VibeASRSpeechEmbeddings & speech, + const VibeASRGenerationOptions & options); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::community_models::vibeasr diff --git a/include/engine/community_models/vibeasr/session.h b/include/engine/community_models/vibeasr/session.h new file mode 100644 index 000000000..e35ef8f8a --- /dev/null +++ b/include/engine/community_models/vibeasr/session.h @@ -0,0 +1,60 @@ +#pragma once + +// Offline ASR session for the VibeASR package: I8_S VAE encoder -> ternary I2_S +// Qwen2 decoder, with VibeASR.cpp's ChatML prompt around the speech features. +// +// Ported from https://github.com/microsoft/VibeASR.cpp (src/asr_server.cpp, +// utils/prompt_builder.h). + +#include "engine/community_models/vibeasr/assets.h" +#include "engine/community_models/vibeasr/lm_decoder.h" +#include "engine/community_models/vibeasr/vae_encoder.h" +#include "engine/framework/model_spec/metadata.h" +#include "engine/framework/runtime/model.h" +#include "engine/framework/runtime/session_base.h" +#include "engine/framework/tokenizers/llama_bpe.h" + +#include +#include + +namespace engine::community_models::vibeasr { + +std::shared_ptr make_vibeasr_loader(); + +class VibeASRSession final : public runtime::RuntimeSessionBase, public runtime::IOfflineVoiceTaskSession { +public: + VibeASRSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract); + ~VibeASRSession() override; + + std::string family() const override; + runtime::VoiceTaskKind task_kind() const override; + runtime::RunMode run_mode() const override; + void prepare(const runtime::SessionPreparationRequest & request) override; + runtime::TaskResult run(const runtime::TaskRequest & request) override; + +private: + struct RequestOptions { + std::string output_format = "text"; + std::string context; + int64_t max_new_tokens = 1024; + }; + + RequestOptions parse_request_options(const runtime::TaskRequest & request) const; + runtime::AudioBuffer normalize(const runtime::AudioBuffer & audio) const; + VibeASRSpeechEmbeddings encode_speech(const std::vector & samples); + VibeASRLmPrompt build_prompt(int64_t speech_tokens, float duration_seconds, const RequestOptions & options) const; + std::string decode_tokens(const std::vector & token_ids) const; + + runtime::TaskSpec task_; + std::shared_ptr assets_; + std::shared_ptr contract_; + std::shared_ptr tokenizer_; + VibeASRVaeEncoderRuntime encoder_; + VibeASRLmRuntime lm_; +}; + +} // namespace engine::community_models::vibeasr diff --git a/include/engine/community_models/vibeasr/vae_encoder.h b/include/engine/community_models/vibeasr/vae_encoder.h new file mode 100644 index 000000000..dd4021dfe --- /dev/null +++ b/include/engine/community_models/vibeasr/vae_encoder.h @@ -0,0 +1,88 @@ +#pragma once + +// VibeASR audio VAE encoder: a ConvNeXt-style causal encoder that turns a mono +// waveform into LM-width features, running end to end in GGML_TYPE_I8_S. +// +// Ported from https://github.com/microsoft/VibeASR.cpp (src/vae.cpp). + +#include "engine/community_models/vibeasr/assets.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/core/module.h" + +#include +#include +#include + +namespace engine::community_models::vibeasr { + +struct VaeBlockWeights { + core::TensorValue mixer_norm; // [channels] + core::TensorValue mixer_conv_weight; // [channels, 1, kernel_size], I8_S + core::TensorValue mixer_conv_bias; // [channels] + core::TensorValue mixer_gamma; // [channels] + core::TensorValue ffn_norm; // [channels] + core::TensorValue ffn_fc1_weight; // [ffn_hidden, channels], I8_S + core::TensorValue ffn_fc1_bias; // [ffn_hidden] + core::TensorValue ffn_fc2_weight; // [channels, ffn_hidden], I8_S + core::TensorValue ffn_fc2_bias; // [channels] + core::TensorValue ffn_gamma; // [channels] +}; + +struct VaeStageWeights { + core::TensorValue downsample_weight; // [out_channels, in_channels, kernel_size], I8_S + core::TensorValue downsample_bias; // [out_channels] + std::vector blocks; +}; + +struct VaeBranchWeights { + std::vector stages; + core::TensorValue head_weight; // [latent_dim, channels, kernel_size], I8_S + core::TensorValue head_bias; // [latent_dim] + core::TensorValue connector_fc1_weight; // [connector_hidden, latent_dim], I8_S + core::TensorValue connector_fc1_bias; // [connector_hidden] + core::TensorValue connector_norm; // [connector_hidden] + core::TensorValue connector_fc2_weight; // [connector_hidden, connector_hidden], I8_S + core::TensorValue connector_fc2_bias; // [connector_hidden] +}; + +struct VibeASRVaeEncoderWeights { + VaeBranchWeights acoustic; + VaeBranchWeights semantic; +}; + +// Encoder output, row-major [frames][dim]. +struct VaeEncoderFeatures { + int64_t frames = 0; + int64_t dim = 0; + std::vector values; +}; + +class VibeASRVaeEncoderRuntime { +public: + VibeASRVaeEncoderRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution_context, + size_t graph_arena_bytes = 64ull * 1024ull * 1024ull); + + // Both branches consume the same waveform, sampled at 24 kHz and scaled to + // [-1, 1], and produce connector_hidden-wide features. + VaeEncoderFeatures encode_acoustic(const std::vector & samples); + VaeEncoderFeatures encode_semantic(const std::vector & samples); + + const VibeASRVaeAssets & assets() const noexcept { return *assets_; } + +private: + VaeEncoderFeatures encode( + const VaeBranchConfig & config, + const VaeBranchWeights & weights, + const std::vector & samples); + + std::shared_ptr assets_; + engine::core::ExecutionContext * execution_context_ = nullptr; + engine::core::BackendWeightStore weight_store_; + VibeASRVaeEncoderWeights weights_; + size_t graph_arena_bytes_; +}; + +} // namespace engine::community_models::vibeasr diff --git a/include/engine/framework/audio/flashsr.h b/include/engine/framework/audio/flashsr.h index 2b5afa519..745414c4e 100644 --- a/include/engine/framework/audio/flashsr.h +++ b/include/engine/framework/audio/flashsr.h @@ -7,10 +7,15 @@ #include #include +namespace engine::assets { +class TensorSource; +} + namespace engine::audio { struct FlashSrWeights; class FlashSrGraph; +class FlashSrDsp; struct FlashSrOutput { int sample_rate = 48000; @@ -21,6 +26,9 @@ class FlashSrModel { public: static FlashSrModel load_from_directory(const std::filesystem::path & model_dir); static FlashSrModel load_from_directory(const std::filesystem::path & model_dir, const core::BackendConfig & backend_config); + static FlashSrModel load_from_tensor_source( + std::shared_ptr source, + const core::BackendConfig & backend_config); FlashSrModel(); ~FlashSrModel(); @@ -36,6 +44,7 @@ class FlashSrModel { std::shared_ptr weights_; mutable std::unique_ptr graph_; + mutable std::unique_ptr dsp_; }; } // namespace engine::audio diff --git a/include/engine/framework/core/attention_fallback.h b/include/engine/framework/core/attention_fallback.h new file mode 100644 index 000000000..0a238aadf --- /dev/null +++ b/include/engine/framework/core/attention_fallback.h @@ -0,0 +1,44 @@ +#pragma once + +#include "ggml.h" +#include "ggml-backend.h" + +#include +#include + +namespace engine::core { + +// Session-option vocabulary shared by families that lower attention with +// ggml_flash_attn_ext: "auto" (default), "flash", or "eager". +// +// Background: ggml-cuda only instantiates the MMA/wmma flash-attention kernels +// for compute capability >= 8.0 (Ampere). On older GPUs such as Volta/sm70 +// (e.g. Tesla V100) a graph containing GGML_OP_FLASH_ATTN_EXT fails at compute +// time with "no device code compatible with CUDA arch 700", even when ggml was +// built with that arch enabled. The eager (explicit matmul + softmax) lowering +// computes the same operation with generic ops and runs everywhere (output +// logits may differ at ulp level, as with any kernel change). +enum class AttentionPreference { + Auto, + Flash, + Eager, +}; + +// Parses a ".attention" session-option value. Throws std::runtime_error +// naming option_name on invalid input. +AttentionPreference parse_attention_preference(const std::string & value, const char * option_name); + +// Resolves whether flash attention may be used for the given backend and head +// dimension. Flash forces true, Eager forces false, Auto gates on the CUDA +// device compute capability (Volta/Turing resolve to eager; see the .cpp for +// why supports_op cannot be used). A null or non-CUDA backend, or a device +// query failure, preserves historical behavior (true). +// +// Adopting in other families (currently wired for higgs_audio_tts and +// breeze_tts only): resolve once per runtime with the model's head_dim and +// the family's ".attention" session option, then switch the +// QwenDecoder prefill/static modes (or SDPA/GQA lowerings) between flash and +// their ManualRepeat/Explicit equivalents based on the result. +bool resolve_flash_attention(ggml_backend_t backend, int64_t head_dim, AttentionPreference preference); + +} // namespace engine::core diff --git a/include/engine/framework/modules/conv_modules.h b/include/engine/framework/modules/conv_modules.h index 0a04766ae..df19a03f1 100644 --- a/include/engine/framework/modules/conv_modules.h +++ b/include/engine/framework/modules/conv_modules.h @@ -40,6 +40,20 @@ class Conv1dModule { Conv1dConfig config_; }; +// Raw Metal fast paths on channel-fast activations (ne = [channels, frames], contiguous +// F32) for the audio codec decoder's chained regions. Unlike the module build() methods +// these take and return raw ggml tensors without the canonical [frames, channels] +// orientation, so consecutive convolutions chain without paying two transposes per conv. +// The caller owns layout conversion at region edges and causal padding. +// +// conv1d_pertap_channel_fast: requires padding=0, stride=1; per-tap GEMM decomposition; +// returns [out_channels, output_frames] with bias broadcast-added when use_bias. +ggml_tensor * conv1d_pertap_channel_fast( + core::ModuleBuildContext & ctx, + const Conv1dWeights & weights, + ggml_tensor * input_cf, + const Conv1dConfig & config); + struct Conv2dConfig { int64_t in_channels = 0; int64_t out_channels = 0; @@ -278,6 +292,16 @@ bool is_conv_transpose1d_col2im_fast_path_eligible( const core::ModuleBuildContext & ctx, const ConvTranspose1dConfig & config) noexcept; +// conv_transpose1d_col2im_channel_fast: same col2im math as ConvTranspose1dModule's fast +// path but consumes channel-fast input directly (skipping its internal transpose) and +// returns the raw time-fast [frames_out, out_channels] tensor with bias included; the +// caller owns causal trimming and layout conversion. +ggml_tensor * conv_transpose1d_col2im_channel_fast( + core::ModuleBuildContext & ctx, + const ConvTranspose1dWeights & weights, + ggml_tensor * input_cf, + const ConvTranspose1dConfig & config); + class ConvTranspose1dModule { public: explicit ConvTranspose1dModule(ConvTranspose1dConfig config); diff --git a/include/engine/framework/modules/text_encoders/t5_gemma_encoder.h b/include/engine/framework/modules/text_encoders/t5_gemma_encoder.h index 23f612ddc..df17e2e3c 100644 --- a/include/engine/framework/modules/text_encoders/t5_gemma_encoder.h +++ b/include/engine/framework/modules/text_encoders/t5_gemma_encoder.h @@ -2,24 +2,36 @@ #include "engine/framework/core/module.h" #include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/norm_modules.h" #include namespace engine::modules { +enum class T5GemmaRMSNormStyle { + Gemma, + Direct, +}; + struct T5GemmaEncoderConfig { int64_t hidden_size = 0; int64_t layers = 0; int64_t attention_heads = 0; int64_t kv_heads = 0; int64_t head_dim = 0; + int64_t attention_size = 0; int64_t intermediate_size = 0; int64_t vocab_size = 0; float rope_theta = 10000.0F; float rms_norm_eps = 1.0e-6F; float attn_logit_softcap = 50.0F; float query_pre_attn_scalar = 64.0F; + float rope_freq_scale = 1.0F; + std::vector layer_rope_theta; + std::vector layer_rope_freq_scale; bool scale_embeddings = true; + bool use_qk_norm = false; + T5GemmaRMSNormStyle rms_norm_style = T5GemmaRMSNormStyle::Gemma; }; struct T5GemmaEncoderLayerWeights { @@ -31,6 +43,8 @@ struct T5GemmaEncoderLayerWeights { LinearWeights k_proj; LinearWeights v_proj; LinearWeights o_proj; + NormWeights q_norm; + NormWeights k_norm; LinearWeights gate_proj; LinearWeights up_proj; LinearWeights down_proj; diff --git a/include/engine/framework/modules/transformers/qwen_decoder.h b/include/engine/framework/modules/transformers/qwen_decoder.h index 3b43a744b..fa1c5ac99 100644 --- a/include/engine/framework/modules/transformers/qwen_decoder.h +++ b/include/engine/framework/modules/transformers/qwen_decoder.h @@ -53,6 +53,10 @@ enum class QwenDecoderPositionEncoding { struct QwenDecoderActivationCastPolicy { bool enabled = false; ggml_type type = GGML_TYPE_BF16; + // Use the fused single-kernel round-to-bf16 op instead of a + // cast -> bf16 -> cast -> f32 round trip. Only valid on backends that + // implement GGML_UNARY_OP_ROUND_BF16 (CUDA/HIP, CPU fallback). + bool fused_round = false; bool after_input_norm = false; bool after_qkv_projection = false; bool after_qk_norm = false; @@ -73,6 +77,9 @@ struct QwenDecoderAttentionPolicy { QwenDecoderAttentionMode static_mode = QwenDecoderAttentionMode::FlashGrouped; QwenDecoderPrefixAttentionMode prefix_mode = QwenDecoderPrefixAttentionMode::Exact; int64_t grouped_query_min_steps = 0; + // False routes flash branches through repeat-KV + matmul/softmax for GPUs + // without a flash kernel (e.g. CUDA sm70). True preserves historical behavior. + bool allow_flash_attention = true; }; struct QwenDecoderStaticCachePolicy { diff --git a/include/engine/framework/sampling/hf_sampler.h b/include/engine/framework/sampling/hf_sampler.h index d4c7ebb5c..21784c184 100644 --- a/include/engine/framework/sampling/hf_sampler.h +++ b/include/engine/framework/sampling/hf_sampler.h @@ -15,6 +15,7 @@ struct HfSamplingOptions { float temperature = 1.0F; int64_t top_k = 0; float top_p = 1.0F; + float min_p = 0.0F; int64_t min_tokens_to_keep = 1; float repetition_penalty = 1.0F; }; @@ -75,6 +76,12 @@ class HfLogitsProcessor { int64_t min_tokens_to_keep, HfSamplerScratch & scratch); + static void apply_min_p( + std::vector & scores, + float min_p, + int64_t min_tokens_to_keep, + HfSamplerScratch & scratch); + static void apply_temperature(std::vector & scores, float temperature); static void build_candidates( diff --git a/include/engine/framework/tokenizers/llama_bpe.h b/include/engine/framework/tokenizers/llama_bpe.h index cd1bb0070..9a048f55f 100644 --- a/include/engine/framework/tokenizers/llama_bpe.h +++ b/include/engine/framework/tokenizers/llama_bpe.h @@ -96,13 +96,15 @@ struct LlamaBpeTokenizerSpec { std::filesystem::path tokenizer_config_path_ = {}, std::optional tokenizer_json_path_ = std::nullopt, LlamaBpePreTokenizer pre_type_ = LlamaBpePreTokenizer::Gpt2, - std::vector additional_special_tokens_ = {}) + std::vector additional_special_tokens_ = {}, + std::string normalizer_space_replacement_ = {}) : vocab_path(std::move(vocab_path_)), merges_path(std::move(merges_path_)), tokenizer_config_path(std::move(tokenizer_config_path_)), tokenizer_json_path(std::move(tokenizer_json_path_)), pre_type(pre_type_), - additional_special_tokens(std::move(additional_special_tokens_)) {} + additional_special_tokens(std::move(additional_special_tokens_)), + normalizer_space_replacement(std::move(normalizer_space_replacement_)) {} std::filesystem::path vocab_path; std::filesystem::path merges_path; @@ -110,6 +112,7 @@ struct LlamaBpeTokenizerSpec { std::optional tokenizer_json_path; LlamaBpePreTokenizer pre_type = LlamaBpePreTokenizer::Gpt2; std::vector additional_special_tokens; + std::string normalizer_space_replacement; }; class LlamaBpeTokenizer final : public ITokenizer { diff --git a/include/engine/models/breeze_tts/assets.h b/include/engine/models/breeze_tts/assets.h new file mode 100644 index 000000000..f675c341d --- /dev/null +++ b/include/engine/models/breeze_tts/assets.h @@ -0,0 +1,76 @@ +#pragma once + +#include "engine/framework/assets/resource_bundle.h" +#include "engine/framework/assets/tensor_source.h" + +#include +#include +#include +#include + +namespace engine::models::breeze_tts { + +struct BreezeTTSConfig { + int sample_rate = 24000; + int64_t hidden_size = 2048; + int64_t intermediate_size = 6144; + int64_t layers = 28; + int64_t heads = 16; + int64_t kv_heads = 8; + int64_t head_dim = 128; + int64_t vocab_size = 2051; + int64_t lm_head_size = 2052; + int64_t text_vocab_size = 262158; + int64_t num_codebooks = 16; + int64_t max_position_embeddings = 2048; + float rms_norm_eps = 1.0e-5F; + float rope_theta = 500000.0F; + float rope_scaling_factor = 32.0F; + float rope_low_freq_factor = 0.125F; + float rope_high_freq_factor = 0.5F; + int64_t rope_original_max_position_embeddings = 1024; + bool rope_scaling_enabled = false; + int64_t audio_token_id = 262144; + int64_t audio_eos_token_id = 262145; + int64_t codebook_pad_token_id = 2050; + int64_t codebook_eos_token_id = 0; + + int64_t text_hidden_size = 1152; + int64_t text_intermediate_size = 6912; + int64_t text_layers = 26; + int64_t text_heads = 4; + int64_t text_kv_heads = 1; + int64_t text_head_dim = 256; + int64_t text_max_position_embeddings = 32768; + float text_rms_norm_eps = 1.0e-6F; + float text_rope_theta = 1000000.0F; + float text_rope_linear_factor = 8.0F; + float text_query_pre_attn_scalar = 256.0F; + std::vector text_layer_rope_theta; + std::vector text_layer_rope_freq_scale; + + int64_t depth_hidden_size = 1024; + int64_t depth_intermediate_size = 4096; + int64_t depth_layers = 4; + int64_t depth_heads = 8; + int64_t depth_kv_heads = 2; + int64_t depth_head_dim = 128; + float depth_rms_norm_eps = 1.0e-5F; + float depth_rope_theta = 500000.0F; + float depth_rope_scaling_factor = 32.0F; + float depth_rope_low_freq_factor = 0.001953125F; + float depth_rope_high_freq_factor = 0.0078125F; + int64_t depth_rope_original_max_position_embeddings = 16; + bool depth_rope_scaling_enabled = false; +}; + +struct BreezeTTSAssets { + std::filesystem::path model_root; + engine::assets::ResourceBundle resources; + BreezeTTSConfig config; + std::shared_ptr weights; +}; + +std::shared_ptr load_breeze_tts_assets(const std::filesystem::path & model_path); + +} // namespace engine::models::breeze_tts diff --git a/include/engine/models/breeze_tts/generator.h b/include/engine/models/breeze_tts/generator.h new file mode 100644 index 000000000..7156ea6b5 --- /dev/null +++ b/include/engine/models/breeze_tts/generator.h @@ -0,0 +1,51 @@ +#pragma once + +#include "engine/framework/core/attention_fallback.h" +#include "engine/framework/runtime/session.h" +#include "engine/models/breeze_tts/assets.h" +#include "engine/models/breeze_tts/speech_decoder.h" +#include "engine/models/breeze_tts/text_encoder.h" +#include "engine/models/breeze_tts/tokenizer_text.h" + +#include +#include +#include +#include + +namespace engine::models::breeze_tts { + +struct BreezeGenerationRequest { + std::string text; + std::string instruction; + std::string reference_text; + std::optional reference_audio; + std::optional reference_codes; + float guidance_scale = 1.0F; + float temperature = 0.9F; + float depth_temperature = 0.9F; + int64_t top_k = 50; + float top_p = 1.0F; + int64_t max_tokens = 1500; + uint64_t seed = 0; +}; + +class BreezeGeneratorRuntime { +public: + BreezeGeneratorRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type, + engine::core::AttentionPreference attention_preference = engine::core::AttentionPreference::Auto); + ~BreezeGeneratorRuntime(); + + engine::runtime::AudioBuffer generate(const BreezeGenerationRequest & request); + BreezeSpeechCodes encode_reference(const engine::runtime::AudioBuffer & audio) const; + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::models::breeze_tts diff --git a/include/engine/models/breeze_tts/session.h b/include/engine/models/breeze_tts/session.h new file mode 100644 index 000000000..8c839da0a --- /dev/null +++ b/include/engine/models/breeze_tts/session.h @@ -0,0 +1,84 @@ +#pragma once + +#include "engine/framework/model_spec/metadata.h" +#include "engine/framework/runtime/cache_slots.h" +#include "engine/framework/runtime/model.h" +#include "engine/framework/runtime/session_base.h" +#include "engine/models/breeze_tts/assets.h" +#include "engine/models/breeze_tts/speech_decoder.h" + +#include +#include +#include +#include +#include + +namespace engine::models::breeze_tts { + +class BreezeGeneratorRuntime; +struct BreezeGenerationRequest; + +std::shared_ptr make_breeze_tts_loader(); + +class BreezeTTSSession final + : public engine::runtime::RuntimeSessionBase + , public engine::runtime::IOfflineVoiceTaskSession + , public engine::runtime::IStreamingVoiceTaskSession { +public: + BreezeTTSSession( + engine::runtime::TaskSpec task, + engine::runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract); + ~BreezeTTSSession() override; + + std::string family() const override; + engine::runtime::VoiceTaskKind task_kind() const override; + engine::runtime::RunMode run_mode() const override; + void prepare(const engine::runtime::SessionPreparationRequest & request) override; + engine::runtime::TaskResult run(const engine::runtime::TaskRequest & request) override; + engine::runtime::StreamingPolicy streaming_policy() const override; + void start_stream(const engine::runtime::TaskRequest & request) override; + std::optional next_stream_event() override; + void set_stream_event_sink(engine::runtime::StreamEventCallback sink) override; + engine::runtime::TaskResult finish_stream() override; + void reset() override; + engine::runtime::StreamEvent process_audio_chunk(const engine::runtime::AudioChunk & chunk) override; + engine::runtime::TaskResult finalize() override; + +private: + struct ReferenceCacheKey { + int sample_rate = 0; + int channels = 0; + uint64_t sample_count = 0; + uint64_t sample_hash = 0; + }; + + struct ReferenceCacheKeyEqual { + bool operator()(const ReferenceCacheKey & lhs, const ReferenceCacheKey & rhs) const noexcept; + }; + + struct ReferenceCacheEntry { + BreezeSpeechCodes codes; + }; + + BreezeSpeechCodes resolve_reference_codes(const engine::runtime::AudioBuffer & audio); + BreezeGenerationRequest build_generation_request( + const engine::runtime::TaskRequest & request, + const std::optional & reference_codes, + size_t chunk_index) const; + + engine::runtime::TaskSpec task_; + std::shared_ptr assets_; + std::shared_ptr contract_; + std::unique_ptr generator_; + engine::runtime::CacheSlots reference_cache_; + std::optional uncached_reference_; + std::vector stream_chunk_requests_; + std::optional stream_reference_codes_; + engine::runtime::AudioBuffer stream_merged_audio_; + size_t stream_chunk_index_ = 0; + bool stream_started_ = false; +}; + +} // namespace engine::models::breeze_tts diff --git a/include/engine/models/breeze_tts/speech_decoder.h b/include/engine/models/breeze_tts/speech_decoder.h new file mode 100644 index 000000000..a5c269c6d --- /dev/null +++ b/include/engine/models/breeze_tts/speech_decoder.h @@ -0,0 +1,64 @@ +#pragma once + +#include "engine/framework/core/attention_fallback.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/runtime/session.h" +#include "engine/models/breeze_tts/assets.h" + +#include +#include +#include +#include +#include + +namespace engine::core { +class ConstantTensorCache; +} + +namespace engine::models { + +namespace breeze_tts { + +struct BreezeSpeechCodes { + std::vector codes; + int64_t frames = 0; + int64_t code_groups = 0; +}; + +struct BreezeSpeechDecoderWeights; +class BreezeSpeechDecoderGraph; + +class BreezeSpeechDecoderRuntime { +public: + BreezeSpeechDecoderRuntime( + std::shared_ptr assets, + core::ExecutionContext & execution_context, + size_t graph_arena_bytes, + size_t constant_context_bytes, + engine::assets::TensorStorageType linear_weight_storage_type, + engine::assets::TensorStorageType conv_weight_storage_type, + core::AttentionPreference attention_preference = core::AttentionPreference::Auto); + ~BreezeSpeechDecoderRuntime(); + + runtime::AudioBuffer decode(const BreezeSpeechCodes & codec_codes) const; + runtime::AudioBuffer decode_and_trim_reference( + const BreezeSpeechCodes & reference_codes, + const BreezeSpeechCodes & generated_codes) const; + void release_runtime_graphs() const; + +private: + std::shared_ptr assets_; + core::ExecutionContext * execution_context_ = nullptr; + std::shared_ptr weights_; + size_t graph_arena_bytes_ = 0; + bool allow_flash_attention_ = true; + std::unique_ptr constants_; + mutable std::unique_ptr graph_; + // Always present to keep this public class layout identical when the private + // Strix Halo compile definition differs between translation units. + mutable std::array, 2> optimized_graphs_; +}; + +} // namespace breeze_tts +} // namespace engine::models diff --git a/include/engine/models/breeze_tts/speech_encoder.h b/include/engine/models/breeze_tts/speech_encoder.h new file mode 100644 index 000000000..818a8cb2a --- /dev/null +++ b/include/engine/models/breeze_tts/speech_encoder.h @@ -0,0 +1,58 @@ +#pragma once + +#include "engine/framework/core/attention_fallback.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/runtime/session.h" +#include "engine/models/breeze_tts/assets.h" +#include "engine/models/breeze_tts/speech_decoder.h" + +#include +#include +#include + +namespace engine::core { +class ConstantTensorCache; +} + +namespace engine::models { + +namespace breeze_tts { + +struct BreezeSpeechEncoderWeights; +class BreezeSpeechEncoderConvGraph; +class BreezeSpeechEncoderTransformerGraph; + +struct BreezeSpeechEncoderOutput { + BreezeSpeechCodes codes; + std::vector semantic_projected; + std::vector acoustic_projected; +}; + +class BreezeSpeechEncoderRuntime { +public: + BreezeSpeechEncoderRuntime( + std::shared_ptr assets, + core::ExecutionContext & execution_context, + size_t graph_arena_bytes, + engine::assets::TensorStorageType linear_weight_storage_type, + engine::assets::TensorStorageType conv_weight_storage_type, + core::AttentionPreference attention_preference = core::AttentionPreference::Auto); + ~BreezeSpeechEncoderRuntime(); + + BreezeSpeechCodes encode(const runtime::AudioBuffer & audio) const; + void release_runtime_graphs() const; + +private: + std::shared_ptr assets_; + std::shared_ptr weights_; + core::ExecutionContext * execution_context_ = nullptr; + size_t graph_arena_bytes_ = 0; + bool allow_flash_attention_ = true; + std::unique_ptr constants_; + mutable std::unique_ptr conv_graph_; + mutable std::unique_ptr transformer_graph_; +}; + +} // namespace breeze_tts +} // namespace engine::models diff --git a/include/engine/models/breeze_tts/text_encoder.h b/include/engine/models/breeze_tts/text_encoder.h new file mode 100644 index 000000000..35ac17338 --- /dev/null +++ b/include/engine/models/breeze_tts/text_encoder.h @@ -0,0 +1,36 @@ +#pragma once + +#include "engine/framework/core/execution_context.h" +#include "engine/framework/assets/tensor_source.h" +#include "engine/models/breeze_tts/assets.h" +#include "engine/models/breeze_tts/tokenizer_text.h" + +#include +#include + +namespace engine::models::breeze_tts { + +struct BreezeProjectedText { + int64_t tokens = 0; + std::vector values; +}; + +class BreezeTextEncoderRuntime { +public: + BreezeTextEncoderRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type); + ~BreezeTextEncoderRuntime(); + + BreezeProjectedText encode(const std::vector & input_ids); + void release_runtime_graphs(); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::models::breeze_tts diff --git a/include/engine/models/breeze_tts/tokenizer_text.h b/include/engine/models/breeze_tts/tokenizer_text.h new file mode 100644 index 000000000..718693378 --- /dev/null +++ b/include/engine/models/breeze_tts/tokenizer_text.h @@ -0,0 +1,44 @@ +#pragma once + +#include "engine/models/breeze_tts/assets.h" + +#include +#include +#include +#include + +namespace engine::models::breeze_tts { + +struct BreezePromptBranch { + std::vector input_ids; + std::vector text_mask; + std::vector text_segment_lengths; + std::vector> text_segments; +}; + +class BreezeTextTokenizer { +public: + explicit BreezeTextTokenizer(std::shared_ptr assets); + ~BreezeTextTokenizer(); + + BreezePromptBranch build_tts_instruction(const std::string & text, const std::string & instruction) const; + BreezePromptBranch build_tts_plain(const std::string & text) const; + BreezePromptBranch build_clone( + const std::string & text, + const std::string & instruction, + const std::string & reference_text, + int64_t reference_audio_frames) const; + BreezePromptBranch build_clone_negative( + const std::string & text, + const std::string & reference_text, + int64_t reference_audio_frames) const; + + int32_t audio_token_id() const noexcept; + int32_t audio_eos_token_id() const noexcept; + +private: + struct Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::models::breeze_tts diff --git a/include/engine/models/cosyvoice3/ar.h b/include/engine/models/cosyvoice3/ar.h new file mode 100644 index 000000000..589246b30 --- /dev/null +++ b/include/engine/models/cosyvoice3/ar.h @@ -0,0 +1,48 @@ +#pragma once + +#include "engine/framework/core/execution_context.h" +#include "engine/framework/sampling/torch_random.h" +#include "engine/models/cosyvoice3/assets.h" + +#include +#include +#include + +namespace engine::models::cosyvoice3 { + +struct CosyVoice3ArRequest { + std::vector prompt_text_tokens; + std::vector target_text_tokens; + std::vector prompt_speech_tokens; + uint32_t seed = 1986; + int64_t top_k = 25; + int64_t min_tokens = -1; + int64_t max_tokens = -1; +}; + +struct CosyVoice3ArOutput { + std::vector speech_tokens; +}; + +class CosyVoice3ArRuntime { +public: + CosyVoice3ArRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type); + ~CosyVoice3ArRuntime(); + + CosyVoice3ArRuntime(const CosyVoice3ArRuntime &) = delete; + CosyVoice3ArRuntime & operator=(const CosyVoice3ArRuntime &) = delete; + + CosyVoice3ArOutput generate(const CosyVoice3ArRequest & request); + void release_graphs(); + +private: + class Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::models::cosyvoice3 diff --git a/include/engine/models/cosyvoice3/assets.h b/include/engine/models/cosyvoice3/assets.h new file mode 100644 index 000000000..777ecc5d3 --- /dev/null +++ b/include/engine/models/cosyvoice3/assets.h @@ -0,0 +1,51 @@ +#pragma once + +#include "engine/framework/assets/resource_bundle.h" +#include "engine/framework/assets/tensor_source.h" + +#include +#include +#include + +namespace engine::models::cosyvoice3 { + +struct CosyVoice3Config { + int64_t sample_rate = 24000; + int64_t text_vocab_size = 151936; + int64_t hidden_size = 896; + int64_t intermediate_size = 4864; + int64_t layers = 24; + int64_t heads = 14; + int64_t kv_heads = 2; + int64_t head_dim = 64; + int64_t speech_token_size = 6561; + int64_t speech_reserved_tokens = 200; + int64_t flow_mel_channels = 80; + int64_t flow_hidden_size = 1024; + int64_t flow_layers = 22; + int64_t flow_heads = 16; + int64_t flow_head_dim = 64; + int64_t flow_ff_mult = 2; + int64_t flow_input_channels = 240; + int64_t flow_static_chunk_size = 50; + int64_t token_mel_ratio = 2; + int64_t pre_lookahead_len = 3; + int64_t speaker_dim = 192; +}; + +struct CosyVoice3Assets { + std::filesystem::path model_root; + std::filesystem::path gguf_path; + engine::assets::ResourceBundle resources; + CosyVoice3Config config; + std::shared_ptr llm_weights; + std::shared_ptr flow_weights; + std::shared_ptr hift_weights; + std::shared_ptr campplus_weights; + std::shared_ptr speech_tokenizer_weights; + std::shared_ptr blank_en_weights; +}; + +std::shared_ptr load_cosyvoice3_assets(const std::filesystem::path & model_path); + +} // namespace engine::models::cosyvoice3 diff --git a/include/engine/models/cosyvoice3/flow.h b/include/engine/models/cosyvoice3/flow.h new file mode 100644 index 000000000..d5211e4d3 --- /dev/null +++ b/include/engine/models/cosyvoice3/flow.h @@ -0,0 +1,48 @@ +#pragma once + +#include "engine/framework/core/execution_context.h" +#include "engine/models/cosyvoice3/assets.h" + +#include +#include +#include + +namespace engine::models::cosyvoice3 { + +struct CosyVoice3FlowRequest { + std::vector speech_tokens; + std::vector prompt_speech_tokens; + std::vector prompt_mel; + int64_t prompt_mel_frames = 0; + std::vector speaker_embedding; + uint32_t seed = 1986; + int64_t num_inference_steps = 10; +}; + +struct CosyVoice3FlowOutput { + std::vector mel; + int64_t frames = 0; +}; + +class CosyVoice3FlowRuntime { +public: + CosyVoice3FlowRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type); + ~CosyVoice3FlowRuntime(); + + CosyVoice3FlowRuntime(const CosyVoice3FlowRuntime &) = delete; + CosyVoice3FlowRuntime & operator=(const CosyVoice3FlowRuntime &) = delete; + + CosyVoice3FlowOutput generate(const CosyVoice3FlowRequest & request); + void release_graphs(); + +private: + class Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::models::cosyvoice3 diff --git a/include/engine/models/cosyvoice3/frontend.h b/include/engine/models/cosyvoice3/frontend.h new file mode 100644 index 000000000..4de239aa9 --- /dev/null +++ b/include/engine/models/cosyvoice3/frontend.h @@ -0,0 +1,42 @@ +#pragma once + +#include "engine/framework/core/execution_context.h" +#include "engine/framework/runtime/model.h" +#include "engine/models/cosyvoice3/assets.h" + +#include +#include + +namespace engine::models::cosyvoice3 { + +struct CosyVoice3ReferenceFeatures { + std::vector speech_tokens; + int64_t speech_token_count = 0; + std::vector prompt_mel; + int64_t prompt_mel_frames = 0; + std::vector speaker_embedding; +}; + +class CosyVoice3Frontend { +public: + CosyVoice3Frontend( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type, + size_t reference_cache_slots); + ~CosyVoice3Frontend(); + + CosyVoice3Frontend(const CosyVoice3Frontend &) = delete; + CosyVoice3Frontend & operator=(const CosyVoice3Frontend &) = delete; + + const CosyVoice3ReferenceFeatures & prepare_reference(const engine::runtime::AudioBuffer & audio); + void release_graphs(); + +private: + class Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::models::cosyvoice3 diff --git a/include/engine/models/cosyvoice3/hift.h b/include/engine/models/cosyvoice3/hift.h new file mode 100644 index 000000000..03454bc2b --- /dev/null +++ b/include/engine/models/cosyvoice3/hift.h @@ -0,0 +1,34 @@ +#pragma once + +#include "engine/framework/core/execution_context.h" +#include "engine/framework/runtime/session.h" +#include "engine/models/cosyvoice3/assets.h" + +#include +#include + +namespace engine::models::cosyvoice3 { + +class CosyVoice3HiftRuntime { +public: + CosyVoice3HiftRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + engine::assets::TensorStorageType storage_type); + ~CosyVoice3HiftRuntime(); + + CosyVoice3HiftRuntime(const CosyVoice3HiftRuntime &) = delete; + CosyVoice3HiftRuntime & operator=(const CosyVoice3HiftRuntime &) = delete; + + engine::runtime::AudioBuffer synthesize( + const std::vector & mel, + int64_t frames, + uint64_t seed); + void release_graphs(); + +private: + class Impl; + std::unique_ptr impl_; +}; + +} // namespace engine::models::cosyvoice3 diff --git a/include/engine/models/cosyvoice3/session.h b/include/engine/models/cosyvoice3/session.h new file mode 100644 index 000000000..f4e40c255 --- /dev/null +++ b/include/engine/models/cosyvoice3/session.h @@ -0,0 +1,49 @@ +#pragma once + +#include "engine/framework/model_spec/metadata.h" +#include "engine/framework/runtime/model.h" +#include "engine/framework/runtime/session_base.h" +#include "engine/models/cosyvoice3/assets.h" +#include "engine/models/cosyvoice3/tokenizer_text.h" + +#include + +namespace engine::models::cosyvoice3 { + +class CosyVoice3Frontend; +class CosyVoice3ArRuntime; +class CosyVoice3FlowRuntime; +class CosyVoice3HiftRuntime; + +std::shared_ptr make_cosyvoice3_loader(); + +class CosyVoice3Session final + : public engine::runtime::RuntimeSessionBase + , public engine::runtime::IOfflineVoiceTaskSession { +public: + CosyVoice3Session( + engine::runtime::TaskSpec task, + engine::runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract); + ~CosyVoice3Session() override; + + std::string family() const override; + engine::runtime::VoiceTaskKind task_kind() const override; + engine::runtime::RunMode run_mode() const override; + void prepare(const engine::runtime::SessionPreparationRequest & request) override; + engine::runtime::TaskResult run(const engine::runtime::TaskRequest & request) override; + +private: + engine::runtime::TaskSpec task_; + std::shared_ptr assets_; + std::shared_ptr contract_; + std::unique_ptr tokenizer_; + std::unique_ptr frontend_; + std::unique_ptr ar_; + std::unique_ptr flow_; + std::unique_ptr hift_; + bool mem_saver_ = false; +}; + +} // namespace engine::models::cosyvoice3 diff --git a/include/engine/models/cosyvoice3/tokenizer_text.h b/include/engine/models/cosyvoice3/tokenizer_text.h new file mode 100644 index 000000000..dc08d6142 --- /dev/null +++ b/include/engine/models/cosyvoice3/tokenizer_text.h @@ -0,0 +1,32 @@ +#pragma once + +#include "engine/models/cosyvoice3/assets.h" + +#include +#include +#include +#include +#include + +namespace engine::models::cosyvoice3 { + +struct CosyVoice3TextTokens { + std::vector prompt; + std::vector target; +}; + +class CosyVoice3TextTokenizer { +public: + explicit CosyVoice3TextTokenizer(std::shared_ptr assets); + ~CosyVoice3TextTokenizer(); + + CosyVoice3TextTokens encode_zero_shot(std::string_view text, std::string_view prompt_text) const; + CosyVoice3TextTokens encode_cross_lingual(std::string_view text) const; + CosyVoice3TextTokens encode_instruct(std::string_view text, std::string_view instruction) const; + +private: + class Impl; + std::shared_ptr impl_; +}; + +} // namespace engine::models::cosyvoice3 diff --git a/include/engine/models/higgs_audio_tts/ar.h b/include/engine/models/higgs_audio_tts/ar.h index 77549e596..d91818f81 100644 --- a/include/engine/models/higgs_audio_tts/ar.h +++ b/include/engine/models/higgs_audio_tts/ar.h @@ -1,6 +1,7 @@ #pragma once #include "engine/framework/assets/tensor_source.h" +#include "engine/framework/core/attention_fallback.h" #include "engine/framework/core/execution_context.h" #include "engine/framework/core/module.h" #include "engine/framework/modules/transformers/qwen_decoder.h" @@ -44,7 +45,8 @@ class HiggsARRuntime { std::shared_ptr assets, core::ExecutionContext & execution, size_t weight_context_bytes, - assets::TensorStorageType weight_storage_type); + assets::TensorStorageType weight_storage_type, + core::AttentionPreference attention_preference = core::AttentionPreference::Auto); const HiggsAssets & assets() const noexcept; const HiggsARWeights & weights() const noexcept; @@ -52,6 +54,7 @@ class HiggsARRuntime { core::BackendType backend_type() const noexcept; int device() const noexcept; int threads() const noexcept; + bool allow_flash_attention() const noexcept; private: std::shared_ptr assets_; @@ -59,6 +62,7 @@ class HiggsARRuntime { core::BackendType backend_type_ = core::BackendType::Cpu; int device_ = 0; int threads_ = 1; + bool allow_flash_attention_ = true; std::shared_ptr weights_; }; diff --git a/include/engine/models/higgs_audio_tts/codec.h b/include/engine/models/higgs_audio_tts/codec.h index 28516eea5..88a7bcd92 100644 --- a/include/engine/models/higgs_audio_tts/codec.h +++ b/include/engine/models/higgs_audio_tts/codec.h @@ -117,6 +117,11 @@ class HiggsCodecRuntime { void release_runtime_graphs(); private: + HiggsCodecDecodeOutput decode_codes_impl( + const std::vector & codes, + int64_t frames, + int64_t codebooks) const; + std::shared_ptr assets_; ggml_backend_t backend_ = nullptr; core::BackendType backend_type_ = core::BackendType::Cpu; diff --git a/include/engine/models/pocket_tts/acoustic_model.h b/include/engine/models/pocket_tts/acoustic_model.h index 3503b13e7..3f8ad0d3e 100644 --- a/include/engine/models/pocket_tts/acoustic_model.h +++ b/include/engine/models/pocket_tts/acoustic_model.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include namespace engine::models::pocket_tts { @@ -35,6 +37,17 @@ struct AcousticPreparedRuntime { std::shared_ptr step_runtime; }; +struct AcousticStreamState { + AcousticPreparedRuntime runtime; + AcousticGenerationConfig config; + std::vector current_input; + std::mt19937 rng; + int step = 0; + int eos_step = -1; + int generated_steps = 0; + bool done = false; +}; + class AcousticModel { public: explicit AcousticModel(FlowLMConfig config = {}); @@ -63,6 +76,16 @@ class AcousticModel { const FlowLMState & initial_state, const AcousticGenerationConfig & config) const; + AcousticStreamState start_stream( + const AcousticPreparedRuntime & runtime, + const PocketTTSAssets & manifest, + const PocketTTSBackendWeights & weights, + const std::vector & text_embeddings, + const FlowLMState & initial_state, + const AcousticGenerationConfig & config) const; + + std::optional next_stream_step(AcousticStreamState & state) const; + void clear_runtime_cache() const noexcept; int64_t prepared_prompt_capacity() const noexcept; int prepared_max_steps_capacity() const noexcept; diff --git a/include/engine/models/pocket_tts/audio_decoder.h b/include/engine/models/pocket_tts/audio_decoder.h index dc8620181..c5bd3408c 100644 --- a/include/engine/models/pocket_tts/audio_decoder.h +++ b/include/engine/models/pocket_tts/audio_decoder.h @@ -31,6 +31,18 @@ class AudioDecoder { int64_t stage2_chunk_frames, bool use_full_sequence_path) const; + void reset_streaming_state() const; + + std::vector decode_streaming_step( + ggml_backend_t backend, + int threads, + const PocketTTSAssets & manifest, + const PocketTTSBackendWeights & weights, + const std::vector & normalized_latent, + size_t conv_graph_context_bytes, + size_t transformer_graph_context_bytes, + size_t tail_graph_context_bytes) const; + void clear_runtime_cache() const noexcept; private: diff --git a/include/engine/models/pocket_tts/mimi_decoder.h b/include/engine/models/pocket_tts/mimi_decoder.h index 22a540666..eb8c7eaeb 100644 --- a/include/engine/models/pocket_tts/mimi_decoder.h +++ b/include/engine/models/pocket_tts/mimi_decoder.h @@ -42,12 +42,26 @@ class MimiDecoder { int64_t stage2_chunk_frames, bool use_full_sequence_path) const; + void reset_streaming_state() const; + + std::vector decode_streaming_step( + ggml_backend_t backend, + int threads, + const PocketTTSAssets & manifest, + const PocketTTSBackendWeights & weights, + const std::vector & latent, + size_t conv_graph_context_bytes, + size_t transformer_graph_context_bytes, + size_t tail_graph_context_bytes) const; + void clear_runtime_cache() const noexcept; private: struct RuntimeCache; + struct StreamingState; MimiDecoderConfig config_; mutable std::unique_ptr runtime_cache_; + mutable std::unique_ptr streaming_state_; }; } // namespace engine::models::pocket_tts diff --git a/include/engine/models/pocket_tts/session.h b/include/engine/models/pocket_tts/session.h index 165da8657..87841942c 100644 --- a/include/engine/models/pocket_tts/session.h +++ b/include/engine/models/pocket_tts/session.h @@ -9,6 +9,7 @@ #include "engine/models/pocket_tts/text_conditioner.h" #include "engine/models/pocket_tts/voice_conditioner.h" +#include #include #include #include @@ -38,7 +39,8 @@ struct PocketTTSGraphCapacityConfig { class PocketTTSSession final : public runtime::RuntimeSessionBase - , public runtime::IOfflineVoiceTaskSession { + , public runtime::IOfflineVoiceTaskSession + , public runtime::IStreamingVoiceTaskSession { public: PocketTTSSession( runtime::TaskSpec task, @@ -55,6 +57,14 @@ class PocketTTSSession final runtime::RunMode run_mode() const override; void prepare(const runtime::SessionPreparationRequest & request) override; runtime::TaskResult run(const runtime::TaskRequest & request) override; + runtime::StreamingPolicy streaming_policy() const override; + void start_stream(const runtime::TaskRequest & request) override; + std::optional next_stream_event() override; + void set_stream_event_sink(runtime::StreamEventCallback sink) override; + runtime::TaskResult finish_stream() override; + void reset() override; + runtime::StreamEvent process_audio_chunk(const runtime::AudioChunk & chunk) override; + runtime::TaskResult finalize() override; void prepare_generation(const GenerationRequest & request); GenerationResult generate(const GenerationRequest & request); @@ -72,6 +82,8 @@ class PocketTTSSession final runtime::MappedGraphCapacityAdapter make_prompt_capacity_adapter() const; runtime::MappedGraphCapacityAdapter make_generation_capacity_adapter() const; AcousticCapacitySelection select_acoustic_capacities(int64_t prompt_steps, int max_steps) const; + GenerationRequest effective_request_for_run(const runtime::TaskRequest & request) const; + bool start_next_stream_text_chunk(); std::vector prepared_prompt_capacities() const; std::vector prepared_generation_capacities() const; @@ -88,6 +100,17 @@ class PocketTTSSession final GenerationRequest prepared_session_request_; runtime::GraphCapacityController prompt_capacity_controller_; runtime::GraphCapacityController generation_capacity_controller_; + + GenerationRequest stream_request_; + FlowLMState stream_voice_state_; + std::vector stream_text_chunks_; + size_t stream_text_chunk_index_ = 0; + std::optional stream_acoustic_state_; + runtime::AudioBuffer stream_merged_audio_; + runtime::StreamEventCallback stream_event_sink_; + std::chrono::steady_clock::time_point stream_started_at_; + size_t stream_audio_chunk_index_ = 0; + bool stream_started_ = false; }; } // namespace engine::models::pocket_tts diff --git a/model_specs/ace_step.json b/model_specs/ace_step.json index b06e42014..b1c094c68 100644 --- a/model_specs/ace_step.json +++ b/model_specs/ace_step.json @@ -123,6 +123,36 @@ "kind": "huggingface_snapshot", "repo": "CaptainArni/audio.cpp-gguf" } + }, + { + "id": "ace_step_xl_turbo_q8dit", + "display_name": "ACE-Step 1.5 XL Turbo Q8_0 DiT GGUF (BF16 planner)", + "format": "gguf", + "precision": "q8_0", + "target_directory": "ACE-Step1.5-GGUF", + "files": [ + "ACE-Step1.5-GGUF/xl-turbo-q8dit/ace-step-1.5-xl-turbo-q8dit.gguf" + ], + "strip_prefix": "ACE-Step1.5-GGUF", + "download": { + "kind": "huggingface_snapshot", + "repo": "CaptainArni/audio.cpp-gguf" + } + }, + { + "id": "ace_step_xl_sft_q8dit", + "display_name": "ACE-Step 1.5 XL SFT Q8_0 DiT GGUF (BF16 planner)", + "format": "gguf", + "precision": "q8_0", + "target_directory": "ACE-Step1.5-GGUF", + "files": [ + "ACE-Step1.5-GGUF/xl-sft-q8dit/ace-step-1.5-xl-sft-q8dit.gguf" + ], + "strip_prefix": "ACE-Step1.5-GGUF", + "download": { + "kind": "huggingface_snapshot", + "repo": "CaptainArni/audio.cpp-gguf" + } } ], "sources": [ diff --git a/model_specs/breeze_tts.json b/model_specs/breeze_tts.json new file mode 100644 index 000000000..d94e62cbf --- /dev/null +++ b/model_specs/breeze_tts.json @@ -0,0 +1,248 @@ +{ + "schema_version": 1, + "family": "breeze_tts", + "display_name": "BreezeTTS 2", + "description": "BreezeTTS 2 native GGUF package for instruction-conditioned text-to-speech and prompt-audio voice cloning with T5Gemma text conditioning, Qwen-style acoustic code generation, depth codebook decoding, and Mimi waveform decoding.", + "category": "tts", + "status": "supported", + "tasks": [ + "tts", + "clone", + "design" + ], + "modes": [ + "offline", + "streaming" + ], + "languages": [ + "zh", + "en" + ], + "runtime": { + "tags": [ + "gguf" + ] + }, + "dependencies": [], + "capabilities": { + "design": [ + "voice_design" + ], + "tts": [ + "style_control" + ], + "clone": [ + "speaker_reference" + ] + }, + "options": { + "request": [ + { + "name": "instruction", + "type": "string", + "description": "BreezeTTS generation instruction.", + "required": false, + "default": "Speak clearly and naturally." + }, + { + "name": "reference_text", + "type": "string", + "description": "Transcript for the prompt audio when cloning.", + "required": false + }, + { + "name": "text_chunk_size", + "type": "int", + "description": "Maximum Unicode codepoints per long-form text chunk; default 600.", + "required": false, + "min": 1, + "default": 600 + }, + { + "name": "text_chunk_mode", + "type": "enum", + "description": "Framework text chunking mode.", + "values": [ + "default", + "tag_aware", + "japanese", + "endline" + ], + "required": false, + "default": "default" + }, + { + "name": "max_tokens", + "type": "int", + "description": "Maximum generated BreezeTTS acoustic frames.", + "required": false, + "min": 1, + "default": 1500 + }, + { + "name": "guidance_scale", + "type": "float", + "description": "Classifier-free guidance scale.", + "required": false, + "min": 0.0, + "default": 1.0 + }, + { + "name": "temperature", + "type": "float", + "description": "Backbone first-codebook sampling temperature.", + "required": false, + "min": 0.0, + "default": 0.9 + }, + { + "name": "depth_temperature", + "type": "float", + "description": "Depth decoder codebook sampling temperature.", + "required": false, + "min": 0.0, + "default": 0.9 + }, + { + "name": "top_k", + "type": "int", + "description": "Top-k sampling limit; 0 disables top-k filtering.", + "required": false, + "min": 0, + "default": 50 + }, + { + "name": "top_p", + "type": "float", + "description": "Top-p sampling limit.", + "required": false, + "min": 0.0, + "max": 1.0, + "default": 1.0 + }, + { + "name": "seed", + "type": "int", + "description": "Generation seed.", + "required": false, + "min": 0, + "default": 0 + } + ], + "session": [ + { + "name": "weight_type", + "type": "enum", + "description": "BreezeTTS matmul weight storage type; default native.", + "preset": "weight_type_full", + "required": false, + "default": "native" + }, + { + "name": "graph_arena_mb", + "type": "int", + "description": "Reusable runtime graph arena size in MiB; default 1024.", + "required": false, + "min": 1, + "default": 1024 + }, + { + "name": "weight_context_mb", + "type": "int", + "description": "Weight loading context size in MiB; default 2048.", + "required": false, + "min": 1, + "default": 2048 + }, + { + "name": "reference_cache_slots", + "type": "int", + "description": "Prepared reference-audio cache slots; set 0 to disable reuse.", + "required": false, + "min": 0, + "default": 1 + }, + { + "name": "attention", + "type": "enum", + "description": "Attention lowering; auto probes the backend and falls back to eager on GPUs without a flash kernel (e.g. sm70); default auto.", + "required": false, + "values": ["auto", "flash", "eager"], + "default": "auto" + } + ], + "load": [] + }, + "packages": [ + { + "id": "breeze_tts_2_q8_0", + "display_name": "BreezeTTS 2 Q8_0 GGUF", + "default": true, + "format": "gguf", + "precision": "q8_0", + "target_directory": "Breeze-TTS-2-GGUF", + "files": [ + "Breeze-TTS-2-GGUF/breeze-tts-2-q8_0.gguf" + ], + "strip_prefix": "Breeze-TTS-2-GGUF", + "download": { + "kind": "huggingface_snapshot", + "repo": "audio-cpp/audio.cpp-gguf", + "revision": "main", + "gated": false + } + }, + { + "id": "breeze_tts_2_bf16", + "display_name": "BreezeTTS 2 BF16 GGUF", + "default": false, + "format": "gguf", + "precision": "bf16", + "target_directory": "Breeze-TTS-2-GGUF", + "files": [ + "Breeze-TTS-2-GGUF/breeze-tts-2-bf16.gguf" + ], + "strip_prefix": "Breeze-TTS-2-GGUF", + "download": { + "kind": "huggingface_snapshot", + "repo": "audio-cpp/audio.cpp-gguf", + "revision": "main", + "gated": false + } + } + ], + "ui": { + "recommended_package": "breeze_tts_2_q8_0", + "tags": [ + "TTS", + "Clone", + "Design", + "GGUF" + ], + "docs": [ + "docs/tts.md", + "docs/gguf.md" + ] + }, + "sources": [ + { + "format": "gguf", + "roots": { + "model": ".", + "weights": "$gguf" + }, + "files": { + "config_json": "model:config.json", + "audio_tokenizer_config_json": "model:audio_tokenizer/config.json", + "tokenizer_config_json": "model:tokenizer_config.json", + "tokenizer_json": "model:tokenizer.json" + }, + "tensors": { + "model_weights": { + "source": "weights:", + "prefix": "model" + } + } + } + ] +} diff --git a/model_specs/cosyvoice3.json b/model_specs/cosyvoice3.json new file mode 100644 index 000000000..ed01ff8e6 --- /dev/null +++ b/model_specs/cosyvoice3.json @@ -0,0 +1,273 @@ +{ + "schema_version": 1, + "family": "cosyvoice3", + "display_name": "CosyVoice3", + "description": "Fun-CosyVoice3 native GGUF package for zero-shot, cross-lingual, and instruction-conditioned text-to-speech using CosyVoice3 speech-token AR generation, causal masked flow mel decoding, CAM++ speaker conditioning, and causal HiFT waveform decoding.", + "category": "tts", + "status": "supported", + "tasks": [ + "tts", + "clone" + ], + "modes": [ + "offline" + ], + "languages": [ + "zh", + "en", + "ja", + "ko", + "de", + "es", + "fr", + "it", + "ru", + "yue" + ], + "runtime": { + "tags": [ + "gguf" + ] + }, + "dependencies": [], + "capabilities": { + "tts": [ + "speaker_reference", + "style_control" + ], + "clone": [ + "speaker_reference" + ] + }, + "options": { + "request": [ + { + "name": "template_name", + "type": "enum", + "description": "CosyVoice3 request template.", + "required": false, + "values": [ + "zero_shot", + "cross_lingual", + "instruct" + ], + "default": "zero_shot" + }, + { + "name": "reference_text", + "type": "string", + "description": "Transcript for the prompt audio.", + "required": false + }, + { + "name": "instruction", + "type": "string", + "description": "Instruction text for instruct mode.", + "required": false + }, + { + "name": "text_chunk_size", + "type": "int", + "description": "Maximum Unicode codepoints per long-form text chunk; default 600.", + "required": false, + "min": 1, + "default": 600 + }, + { + "name": "text_chunk_mode", + "type": "enum", + "description": "Framework text chunking mode.", + "values": [ + "default", + "tag_aware", + "japanese", + "endline" + ], + "required": false, + "default": "default" + }, + { + "name": "max_tokens", + "type": "int", + "description": "Maximum generated CosyVoice3 speech tokens.", + "required": false, + "min": 1, + "default": 1600 + }, + { + "name": "min_tokens", + "type": "int", + "description": "Minimum generated CosyVoice3 speech tokens before stop tokens are accepted.", + "required": false, + "min": 0, + "default": 0 + }, + { + "name": "top_k", + "type": "int", + "description": "AR speech-token top-k sampling limit; default 25.", + "required": false, + "min": 1, + "default": 25 + }, + { + "name": "num_inference_steps", + "type": "int", + "description": "Flow decoder Euler steps; default 10.", + "required": false, + "min": 1, + "default": 10 + }, + { + "name": "seed", + "type": "int", + "description": "Generation seed.", + "required": false, + "min": 0, + "default": 1986 + } + ], + "session": [ + { + "name": "weight_type", + "type": "enum", + "description": "CosyVoice3 matmul weight storage type; default native.", + "preset": "weight_type_full", + "required": false, + "default": "native" + }, + { + "name": "conv_weight_type", + "type": "enum", + "description": "CosyVoice3 convolution weight storage type; default native.", + "preset": "weight_type_conv", + "required": false, + "default": "native" + }, + { + "name": "graph_arena_mb", + "type": "int", + "description": "Reusable runtime graph arena size in MiB; default 1024.", + "required": false, + "min": 1, + "default": 1024 + }, + { + "name": "weight_context_mb", + "type": "int", + "description": "Weight loading context size in MiB; default 2048.", + "required": false, + "min": 1, + "default": 2048 + }, + { + "name": "reference_cache_slots", + "type": "int", + "description": "Prepared reference-audio cache slots; set 0 to disable reuse.", + "required": false, + "min": 0, + "default": 4 + }, + { + "name": "mem_saver", + "type": "bool", + "description": "Release cached runtime graphs after each request to reduce peak VRAM; default false.", + "required": false, + "default": false + } + ], + "load": [] + }, + "packages": [ + { + "id": "cosyvoice3_q8_0", + "display_name": "CosyVoice3 Q8_0 GGUF", + "default": true, + "format": "gguf", + "precision": "q8_0", + "target_directory": "CosyVoice3-GGUF", + "files": [ + "CosyVoice3-GGUF/cosyvoice3-q8_0.gguf" + ], + "strip_prefix": "CosyVoice3-GGUF", + "download": { + "kind": "huggingface_snapshot", + "repo": "audio-cpp/audio.cpp-gguf", + "revision": "main", + "gated": false + } + }, + { + "id": "cosyvoice3_f32", + "display_name": "CosyVoice3 F32 GGUF", + "default": false, + "format": "gguf", + "precision": "f32", + "target_directory": "CosyVoice3-GGUF", + "files": [ + "CosyVoice3-GGUF/cosyvoice3-f32.gguf" + ], + "strip_prefix": "CosyVoice3-GGUF", + "download": { + "kind": "huggingface_snapshot", + "repo": "audio-cpp/audio.cpp-gguf", + "revision": "main", + "gated": false + } + } + ], + "ui": { + "recommended_package": "cosyvoice3_q8_0", + "tags": [ + "TTS", + "Clone", + "GGUF" + ], + "docs": [ + "docs/tts.md", + "docs/gguf.md" + ] + }, + "sources": [ + { + "format": "gguf", + "roots": { + "model": ".", + "weights": "$gguf" + }, + "files": { + "cosyvoice3_yaml": "model:cosyvoice3.yaml", + "qwen_config": "model:CosyVoice-BlankEN/config.json", + "tokenizer_config": "model:CosyVoice-BlankEN/tokenizer_config.json", + "vocab_json": "model:CosyVoice-BlankEN/vocab.json", + "merges_txt": "model:CosyVoice-BlankEN/merges.txt" + }, + "tensors": { + "llm_weights": { + "source": "weights:", + "prefix": "llm" + }, + "flow_weights": { + "source": "weights:", + "prefix": "flow" + }, + "hift_weights": { + "source": "weights:", + "prefix": "hift" + }, + "campplus_weights": { + "source": "weights:", + "prefix": "campplus" + }, + "speech_tokenizer_weights": { + "source": "weights:", + "prefix": "speech_tokenizer" + }, + "blank_en_weights": { + "source": "weights:", + "prefix": "blank_en" + } + } + } + ] +} diff --git a/model_specs/mira_tts.json b/model_specs/mira_tts.json new file mode 100644 index 000000000..9ad42c8d2 --- /dev/null +++ b/model_specs/mira_tts.json @@ -0,0 +1,215 @@ +{ + "schema_version": 1, + "family": "mira_tts", + "display_name": "MiraTTS", + "description": "MiraTTS is a community voice-cloning TTS model with a Qwen2 autoregressive speech-token generator, ECAPA/Perceiver speaker tokenizer, conditional acoustic processor, and DAC decoder.", + "category": "tts", + "status": "experimental", + "tasks": [ + "tts", + "clone" + ], + "modes": [ + "offline", + "streaming" + ], + "languages": [ + "en" + ], + "runtime": { + "tags": [ + "gguf" + ] + }, + "capabilities": { + "tts": [ + "speaker_reference" + ], + "clone": [ + "speaker_reference" + ] + }, + "options": { + "request": [ + { + "name": "max_tokens", + "type": "int", + "description": "Maximum autoregressive speech tokens; default 1024.", + "required": false, + "min": 1, + "default": 1024 + }, + { + "name": "temperature", + "type": "float", + "description": "Sampling temperature; default 0.8.", + "required": false, + "min": 0.000001, + "default": 0.8 + }, + { + "name": "top_k", + "type": "int", + "description": "Top-k sampling cutoff; default 50.", + "required": false, + "min": 1, + "default": 50 + }, + { + "name": "top_p", + "type": "float", + "description": "Nucleus sampling probability; default 0.95.", + "required": false, + "min": 0.000001, + "max": 1.0, + "default": 0.95 + }, + { + "name": "min_p", + "type": "float", + "description": "Minimum probability relative to the most likely token; default 0.05.", + "required": false, + "min": 0.0, + "max": 1.0, + "default": 0.05 + }, + { + "name": "repetition_penalty", + "type": "float", + "description": "Autoregressive repetition penalty; default 1.2.", + "required": false, + "min": 1.0, + "default": 1.2 + }, + { + "name": "text_chunk_size", + "type": "int", + "description": "Maximum codepoints per progressively emitted streaming segment; default 160.", + "required": false, + "min": 1, + "default": 160 + }, + { + "name": "text_chunk_mode", + "type": "enum", + "description": "Framework text chunking mode used by streaming synthesis.", + "values": [ + "default", + "tag_aware", + "japanese", + "endline" + ], + "required": false, + "default": "default" + }, + { + "name": "seed", + "type": "int", + "description": "Sampling seed; omitted requests choose a random seed.", + "required": false, + "min": 0 + } + ], + "session": [ + { + "name": "reference_cache_slots", + "type": "int", + "description": "Encoded reference-voice cache slots; default 1. Set to 0 to disable reuse.", + "required": false, + "min": 0, + "default": 1 + } + ], + "load": [ + { + "name": "backbone_weight_type", + "type": "enum", + "preset": "weight_type_full", + "required": false, + "default": "native", + "description": "Storage type for the Qwen2 language-model weights." + }, + { + "name": "linear_weight_type", + "type": "enum", + "preset": "weight_type_full", + "required": false, + "default": "native", + "description": "Storage type for non-convolutional speaker, processor, and decoder weights." + }, + { + "name": "conv_weight_type", + "type": "enum", + "preset": "weight_type_conv", + "required": false, + "default": "f32", + "description": "Storage type for speaker and processor convolution weights. The DAC transposed-convolution decoder remains F32 for CUDA compatibility." + } + ] + }, + "packages": [], + "dependencies": [], + "ui": { + "tags": [ + "TTS", + "Clone" + ], + "docs": [ + "docs/community_models/mira_tts.md" + ] + }, + "sources": [ + { + "format": "gguf", + "roots": { + "model": ".", + "weights": "$gguf" + }, + "files": { + "config": "model:config.json", + "tokenizer_config": "model:tokenizer_config.json", + "tokenizer_json": "model:tokenizer.json" + }, + "tensors": { + "language_model": { + "source": "weights:", + "prefix": "language_model" + }, + "speaker_encoder": { + "source": "weights:", + "prefix": "speaker_encoder" + }, + "processor": { + "source": "weights:", + "prefix": "processor" + }, + "decoder": { + "source": "weights:", + "prefix": "decoder" + }, + "upsampler": { + "source": "weights:", + "prefix": "upsampler" + } + } + }, + { + "format": "safetensors", + "roots": { + "model": "." + }, + "files": { + "config": "model:config.json", + "tokenizer_config": "model:tokenizer_config.json", + "tokenizer_json": "model:tokenizer.json" + }, + "tensors": { + "language_model": "model:language_model.safetensors", + "speaker_encoder": "model:speaker_encoder.safetensors", + "processor": "model:processor.safetensors", + "decoder": "model:decoder.safetensors", + "upsampler": "model:upsampler.safetensors" + } + } + ] +} diff --git a/model_specs/pocket_tts.json b/model_specs/pocket_tts.json index 522467eb5..cffc2f6c3 100644 --- a/model_specs/pocket_tts.json +++ b/model_specs/pocket_tts.json @@ -9,7 +9,8 @@ "clone" ], "modes": [ - "offline" + "offline", + "streaming" ], "languages": [ "en", diff --git a/model_specs/sanotts.json b/model_specs/sanotts.json new file mode 100644 index 000000000..8d98fa9e1 --- /dev/null +++ b/model_specs/sanotts.json @@ -0,0 +1,212 @@ +{ + "schema_version": 1, + "family": "sanotts", + "display_name": "sanoTTS Nano", + "description": "Very small English, Vietnamese and Indonesian text-to-speech. Two graphs: the nano lineage (duration student, contextual acoustic student to mel-100, noise-fed ConvNeXt-1D decoder with an iSTFT head; voices heart 2.27M and heart-nano 294k, 24 kHz) and the deterministic piperlite lineage (duration student, acoustic student to a 192-channel latent, 3-stage ConvTranspose1d decoder with dilated residual banks; voices amy, hfc, kristin, vi, id at 1.1-1.8M parameters, 22.05 kHz). Uses an external eSpeak-ng phonemizer.", + "category": "tts", + "status": "community", + "tasks": [ + "tts" + ], + "modes": [ + "offline" + ], + "languages": [ + "en", + "vi", + "id" + ], + "runtime": { + "tags": [ + "gguf" + ] + }, + "capabilities": { + "tts": [ + "long_form" + ] + }, + "options": { + "request": [ + { + "name": "speaking_rate", + "type": "float", + "description": "Duration multiplier on the voice's tuned length scale; larger is slower. Applied before the per-token clamp.", + "required": false, + "min": 0.5, + "max": 2.0, + "default": 1.0 + }, + { + "name": "seed", + "type": "int", + "description": "Decoder noise seed. The decoder is noise-fed, so a given seed picks one of many valid renderings; 0 derives it from the text as sha256(text)[:8], which is what the reference implementations do. Piperlite voices are deterministic and ignore the seed.", + "required": false, + "min": 0, + "default": 0 + }, + { + "name": "text_chunk_mode", + "type": "enum", + "description": "Long-form text chunking mode.", + "values": [ + "word_budget" + ], + "required": false, + "default": "word_budget" + }, + { + "name": "text_chunk_size", + "type": "int", + "description": "Maximum Unicode codepoints per long-form text chunk; default 280.", + "required": false, + "min": 1, + "default": 280 + } + ], + "session": [ + { + "name": "espeak_library_path", + "type": "path", + "description": "Optional explicit path to the eSpeak-ng shared library.", + "required": false + }, + { + "name": "espeak_data_path", + "type": "path", + "description": "Optional explicit path to the directory containing espeak-ng-data.", + "required": false + } + ], + "load": [] + }, + "package_defaults": { + "download": { + "kind": "huggingface_snapshot", + "repo": "ampixa/sanoTTS", + "revision": "main", + "gated": false + } + }, + "packages": [ + { + "id": "sanotts_heart_nano_orig", + "display_name": "sanoTTS heart-nano 294k FP32 GGUF", + "default": true, + "format": "gguf", + "precision": "orig", + "target_directory": "sanoTTS-heart-nano-GGUF", + "files": [ + "gguf/heart-nano-f32.gguf", + "gguf/config.json" + ], + "strip_prefix": "gguf" + }, + { + "id": "sanotts_heart_orig", + "display_name": "sanoTTS heart 2.27M FP32 GGUF", + "default": false, + "format": "gguf", + "precision": "orig", + "target_directory": "sanoTTS-heart-GGUF", + "files": [ + "gguf/heart/heart-f32.gguf", + "gguf/heart/config.json" + ], + "strip_prefix": "gguf/heart" + }, + { + "id": "sanotts_amy_orig", + "display_name": "sanoTTS amy 1.46M FP32 GGUF (English, piperlite)", + "default": false, + "format": "gguf", + "precision": "orig", + "target_directory": "sanoTTS-amy-GGUF", + "files": [ + "gguf/amy/amy-f32.gguf", + "gguf/amy/config.json" + ], + "strip_prefix": "gguf/amy" + }, + { + "id": "sanotts_hfc_orig", + "display_name": "sanoTTS hfc 1.83M FP32 GGUF (English, piperlite)", + "default": false, + "format": "gguf", + "precision": "orig", + "target_directory": "sanoTTS-hfc-GGUF", + "files": [ + "gguf/hfc/hfc-f32.gguf", + "gguf/hfc/config.json" + ], + "strip_prefix": "gguf/hfc" + }, + { + "id": "sanotts_kristin_orig", + "display_name": "sanoTTS kristin 1.40M FP32 GGUF (English, piperlite)", + "default": false, + "format": "gguf", + "precision": "orig", + "target_directory": "sanoTTS-kristin-GGUF", + "files": [ + "gguf/kristin/kristin-f32.gguf", + "gguf/kristin/config.json" + ], + "strip_prefix": "gguf/kristin" + }, + { + "id": "sanotts_vi_orig", + "display_name": "sanoTTS vi 1.57M FP32 GGUF (Vietnamese, piperlite)", + "default": false, + "format": "gguf", + "precision": "orig", + "target_directory": "sanoTTS-vi-GGUF", + "files": [ + "gguf/vi/vi-f32.gguf", + "gguf/vi/config.json" + ], + "strip_prefix": "gguf/vi" + }, + { + "id": "sanotts_id_orig", + "display_name": "sanoTTS id 1.56M FP32 GGUF (Indonesian, piperlite)", + "default": false, + "format": "gguf", + "precision": "orig", + "target_directory": "sanoTTS-id-GGUF", + "files": [ + "gguf/id/id-f32.gguf", + "gguf/id/config.json" + ], + "strip_prefix": "gguf/id" + } + ], + "dependencies": [], + "ui": { + "recommended_package": "sanotts_heart_nano_orig", + "tags": [ + "TTS", + "GGUF" + ], + "docs": [ + "docs/tts.md", + "docs/community_models/sanotts.md", + "docs/gguf.md" + ] + }, + "sources": [ + { + "format": "gguf", + "roots": { + "model": ".", + "weights": "$gguf" + }, + "files": { + "config": "model:config.json" + }, + "tensors": { + "weights": "weights:" + } + } + ] +} diff --git a/model_specs/sopro_tts.json b/model_specs/sopro_tts.json new file mode 100644 index 000000000..3b37853a9 --- /dev/null +++ b/model_specs/sopro_tts.json @@ -0,0 +1,257 @@ +{ + "schema_version": 1, + "family": "sopro_tts", + "display_name": "Sopro V2 Turbo", + "description": "Community Sopro V2 Turbo (samuel-vitorino/sopro-v2-turbo): a 120M zero-shot voice-cloning TTS. SentencePiece text tokenizer, style-prefix conditioned autoregressive semantic LM over FSQ tokens, rectified-flow acoustic DiT and a Vocos ISTFT vocoder at 24 kHz.", + "category": "tts", + "status": "community", + "tasks": [ + "tts", + "clone" + ], + "modes": [ + "offline", + "streaming" + ], + "languages": [ + "en", + "pt", + "fr", + "de" + ], + "runtime": { + "tags": [ + "server" + ] + }, + "capabilities": { + "clone": [ + "speaker_reference" + ] + }, + "options": { + "request": [ + { + "name": "language", + "type": "string", + "description": "Language tag prepended to the prompt (en, pt, fr, de). Optional; helps pronunciation on ambiguous text.", + "required": false, + "default": "" + }, + { + "name": "temperature", + "type": "float", + "description": "Semantic LM sampling temperature; default 0.8.", + "required": false, + "min": 0.0, + "max": 2.0, + "default": 0.8 + }, + { + "name": "top_p", + "type": "float", + "description": "Nucleus sampling threshold for the semantic LM; default 0.9.", + "required": false, + "min": 0.0, + "max": 1.0, + "default": 0.9 + }, + { + "name": "top_k", + "type": "int", + "description": "Top-k truncation for the semantic LM; 0 disables. Default 25.", + "required": false, + "min": 0, + "default": 25 + }, + { + "name": "num_inference_steps", + "type": "int", + "description": "Acoustic rectified-flow Euler steps; default 2.", + "required": false, + "min": 1, + "max": 32, + "default": 2 + }, + { + "name": "max_seconds", + "type": "float", + "description": "Cap on generated audio per segment; long text is split into segments so total length is unbounded. Default 30.", + "required": false, + "min": 1.0, + "max": 60.0, + "default": 30.0 + }, + { + "name": "min_seconds", + "type": "float", + "description": "Minimum audio per segment before the semantic LM may emit EOS; default 0.4.", + "required": false, + "min": 0.0, + "max": 10.0, + "default": 0.4 + }, + { + "name": "ref_seconds", + "type": "float", + "description": "Reference audio window used for cloning; default 10.", + "required": false, + "min": 1.0, + "max": 30.0, + "default": 10.0 + }, + { + "name": "text_chunk_size", + "type": "int", + "description": "Maximum codepoints per synthesis segment; default 300 (config.json generation.max_segment_chars).", + "required": false, + "min": 20, + "max": 2000, + "default": 300 + }, + { + "name": "seed", + "type": "int", + "description": "Non-negative seed for semantic sampling and the acoustic noise prior; omit for a random seed.", + "required": false, + "min": 0 + } + ], + "session": [ + { + "name": "language", + "type": "string", + "description": "Default language tag for requests that do not set one.", + "required": false, + "default": "" + } + ], + "load": [ + { + "name": "matmul_weight_type", + "type": "enum", + "preset": "weight_type_full", + "description": "Storage type for the matmul weights of every stage (semantic LM, acoustic DiT, encoders, vocoder head).", + "required": false, + "default": "f32" + }, + { + "name": "conv_weight_type", + "type": "enum", + "preset": "weight_type_conv", + "description": "Storage type for convolution weights (speaker/semantic encoders and the Vocos backbone).", + "required": false, + "default": "f32" + } + ] + }, + "package_defaults": { + "download": { + "kind": "unsupported", + "reason": "No audio.cpp GGUF build of sopro-v2-turbo is published yet: install the sopro_v2_turbo_safetensors package and run from safetensors, or pack one locally with audiocpp_gguf (one --input namespace per stage: model, semantic_encoder, speaker_encoder, vocoder)." + } + }, + "packages": [ + { + "id": "sopro_v2_turbo_f16", + "display_name": "Sopro V2 Turbo F16 GGUF", + "description": "Locally packed GGUF holding all four stages plus the embedded config and tokenizer sidecars. Produced with audiocpp_gguf --family sopro_tts.", + "default": true, + "format": "gguf", + "precision": "f16", + "target_directory": "sopro-v2-turbo-GGUF", + "files": [ + "sopro-v2-turbo-GGUF/sopro-v2-turbo-f16.gguf" + ], + "strip_prefix": "sopro-v2-turbo-GGUF" + }, + { + "id": "sopro_v2_turbo_safetensors", + "display_name": "Sopro V2 Turbo (upstream safetensors)", + "description": "Upstream checkpoint from samuel-vitorino/sopro-v2-turbo: config.json, tokenizer.model and the four safetensors stages. Runs directly, no conversion needed.", + "format": "safetensors", + "precision": "orig", + "target_directory": "sopro-v2-turbo", + "files": [ + "config.json", + "tokenizer.model", + "model.safetensors", + "semantic_encoder.safetensors", + "speaker_encoder.safetensors", + "vocoder.safetensors" + ], + "download": { + "kind": "huggingface_snapshot", + "repo": "samuel-vitorino/sopro-v2-turbo", + "revision": "main", + "gated": false + } + } + ], + "dependencies": [], + "ui": { + "recommended_package": "sopro_v2_turbo_safetensors", + "tags": [ + "TTS", + "Clone" + ], + "docs": [ + "docs/community_models/sopro_tts.md" + ] + }, + "sources": [ + { + "format": "gguf", + "roots": { + "model": ".", + "weights": "$gguf" + }, + "files": { + "config": "model:config.json", + "tokenizer": "model:tokenizer.model" + }, + "tensors": { + "model": { + "source": "weights:", + "prefix": "model" + }, + "semantic_encoder": { + "source": "weights:", + "prefix": "semantic_encoder" + }, + "speaker_encoder": { + "source": "weights:", + "prefix": "speaker_encoder" + }, + "vocoder": { + "source": "weights:", + "prefix": "vocoder" + } + } + }, + { + "format": "safetensors", + "roots": { + "model": "." + }, + "files": { + "config": "model:config.json", + "tokenizer": "model:tokenizer.model" + }, + "tensors": { + "model": { + "source": "model:model.safetensors" + }, + "semantic_encoder": { + "source": "model:semantic_encoder.safetensors" + }, + "speaker_encoder": { + "source": "model:speaker_encoder.safetensors" + }, + "vocoder": { + "source": "model:vocoder.safetensors" + } + } + } + ] +} diff --git a/model_specs/vibeasr.json b/model_specs/vibeasr.json new file mode 100644 index 000000000..4c25db9c8 --- /dev/null +++ b/model_specs/vibeasr.json @@ -0,0 +1,139 @@ +{ + "schema_version": 1, + "family": "vibeasr", + "display_name": "VibeVoice-ASR-BitNet", + "description": "VibeASR.cpp's CPU-first VibeVoice ASR port: an INT8 (I8_S) audio VAE encoder feeding a ternary (I2_S) Qwen2 decoder, ported to audio.cpp.", + "category": "asr", + "status": "community", + "tasks": [ + "asr" + ], + "modes": [ + "offline" + ], + "languages": [ + "en", + "zh", + "fr", + "it", + "ko", + "pt", + "vi" + ], + "capabilities": {}, + "options": { + "request": [ + { + "name": "output_format", + "type": "enum", + "description": "Prompt suffix asked of the decoder: plain transcription text, or JSON rows with Start/End/Speaker/Content.", + "values": [ + "text", + "json" + ], + "required": false, + "default": "text" + }, + { + "name": "context", + "type": "string", + "description": "Extra context injected into the prompt (names, jargon) to bias the transcription.", + "required": false, + "default": "" + }, + { + "name": "max_new_tokens", + "type": "int", + "description": "Cap on decoded tokens for one request.", + "required": false, + "min": 1, + "default": 1024 + } + ], + "session": [ + { + "name": "encoder_graph_arena_mb", + "type": "int", + "description": "VAE encoder graph arena size in MB.", + "required": false, + "min": 16, + "default": 64 + }, + { + "name": "prefill_graph_arena_mb", + "type": "int", + "description": "Decoder prefill graph arena size in MB.", + "required": false, + "min": 16, + "default": 256 + }, + { + "name": "decode_graph_arena_mb", + "type": "int", + "description": "Decoder single-step graph arena size in MB.", + "required": false, + "min": 16, + "default": 256 + } + ], + "load": [] + }, + "runtime": { + "tags": [ + "gguf", + "cpu" + ] + }, + "packages": [ + { + "id": "vibeasr_bitnet_i2_s", + "display_name": "VibeVoice-ASR-BitNet I8_S encoder + I2_S decoder", + "description": "Upstream VibeASR.cpp GGUF package. The two GGUFs carry the VibeASR ggml fork's type ids and need one pass of tools/community_models/convert_vibeasr_gguf.py --in-place before audio.cpp can load them.", + "default": true, + "format": "gguf", + "precision": "native", + "target_directory": "VibeVoice-ASR-BitNet", + "files": [ + "vibeasr-vae-encoder-i8_s.gguf", + "vibeasr-lm-i2_s-embed-q6_k.gguf", + "tokenizer.json", + "tokenizer_config.json" + ], + "download": { + "kind": "huggingface_snapshot", + "repo": "microsoft/VibeVoice-ASR-BitNet", + "revision": "main", + "gated": false + } + } + ], + "dependencies": [], + "ui": { + "recommended_package": "vibeasr_bitnet_i2_s", + "tags": [ + "ASR", + "GGUF" + ], + "docs": [ + "docs/community_models/vibeasr.md" + ], + "summary": "INT8 encoder plus ternary Qwen2 decoder transcription on CPU." + }, + "sources": [ + { + "format": "gguf", + "roots": { + "model": "." + }, + "files": { + "tokenizer_json": "model:tokenizer.json", + "tokenizer_config": "model:tokenizer_config.json" + }, + "optional_files": {}, + "tensors": { + "vae_weights": "model:vibeasr-vae-encoder-i8_s.gguf", + "lm_weights": "model:vibeasr-lm-i2_s-embed-q6_k.gguf" + } + } + ] +} diff --git a/scripts/build_windows.ps1 b/scripts/build_windows.ps1 index 527cef205..53790d3e0 100644 --- a/scripts/build_windows.ps1 +++ b/scripts/build_windows.ps1 @@ -19,6 +19,7 @@ param( [ValidateSet("full", "core", "custom")] [string]$ModelSet = "full", [string]$Models = "", + [string]$Version = "dev", [string]$VsInstall = "" ) @@ -551,6 +552,7 @@ Write-Host "Model composite: $ModelSet" if ($Models -ne "") { Write-Host "Selected models: $Models" } +Write-Host "audio.cpp version: $Version" if ($Clean) { $buildDirForClean = Join-Path (Join-Path (Split-Path $PSScriptRoot -Parent) "build") $Preset @@ -587,6 +589,7 @@ $configureArgs = @( "-DAUDIOCPP_DEPLOYMENT_BUILD=$deploymentBuildValue", "-DAUDIOCPP_BUILD_NATIVE_MODEL_MANAGER=$nativeModelManagerValue", "-DAUDIOCPP_USE_SYSTEM_OPENSSL=$systemOpenSslValue", + "-DAUDIOCPP_VERSION=$Version", "-U", "AUDIOCPP_BORINGSSL_ARCHIVE", "-DAUDIOCPP_MODEL_SET=$ModelSet", "-DAUDIOCPP_MODELS=$Models" diff --git a/src/community_models/audio8_tts/ar.cpp b/src/community_models/audio8_tts/ar.cpp index bdd03d2b2..ba11865ca 100644 --- a/src/community_models/audio8_tts/ar.cpp +++ b/src/community_models/audio8_tts/ar.cpp @@ -1,5 +1,7 @@ #include "engine/community_models/audio8_tts/ar.h" +#include "falcon_kv_cache.h" + #include "engine/framework/core/backend_weight_store.h" #include "engine/framework/core/backend.h" #include "engine/framework/debug/profiler.h" @@ -61,6 +63,13 @@ struct ArkttsARProfile { double sample_main_ms = 0.0; double sample_high_ms = 0.0; double sample_fast_ms = 0.0; + double falcon_step_init_ms = 0.0; + double falcon_step_build_ms = 0.0; + double falcon_step_gallocr_ms = 0.0; + double falcon_step_upload_ms = 0.0; + double falcon_step_compute_ms = 0.0; + double falcon_step_download_ms = 0.0; + int64_t falcon_step_runs = 0; int64_t prefill_runs = 0; int64_t step_runs = 0; int64_t fast_runs = 0; @@ -103,6 +112,7 @@ struct FalconH1LayerWeights { assets::TensorDataF32 input_layernorm; // slow.layers.*.input_layernorm.weight [512] core::TensorValue ssm_in; // slow.layers.*.mamba.in_proj.weight [1688,512] core::TensorValue ssm_conv1d; // slow.layers.*.mamba.conv1d.weight [896,1,4] -> [4,896] after convert + assets::TensorDataF32 conv1d_kernel; // host [d_conv, conv_dim] GGUF layout for ggml ssm_conv assets::TensorDataF32 ssm_conv1d_b; // slow.layers.*.mamba.conv1d.bias [896] core::TensorValue ssm_dt_b; // slow.layers.*.mamba.dt_bias [24] core::TensorValue ssm_A; // slow.layers.*.mamba.A_log [24] -> [1,24] @@ -431,21 +441,40 @@ FalconH1LayerWeights load_falcon_layer( w.ssm_in = store.load_tensor(source, prefix + ".mamba.in_proj.weight", storage_type, meta.shape); } { + // ssm_conv Metal pipeline requires contiguous F32 conv weights. auto meta = source.require_metadata(prefix + ".mamba.conv1d.weight"); - w.ssm_conv1d = store.load_tensor(source, prefix + ".mamba.conv1d.weight", storage_type, meta.shape); + w.ssm_conv1d = store.load_tensor(source, prefix + ".mamba.conv1d.weight", assets::TensorStorageType::F32, meta.shape); + { + // ggml ssm_conv computes y[c] = sum_k w[k,c]*window[k,c] with window[0] + // the OLDEST frame. HF (both nn.Conv1d prefill and the cached + // torch.sum(conv_states * w, dim=-1) decode) uses the identical + // orientation: w[...,0] multiplies the oldest frame. The GGUF tensor + // is the HF [conv_dim,1,d_conv] weight with reversed dims + // [d_conv,1,conv_dim] and unchanged flat bytes, i.e. + // raw[k + d_conv*c] == hf_w[c,k] — exactly what ssm_conv wants. + // Feed it through UNFLIPPED (an earlier kernel flip here reversed the + // tap order and corrupted the x/B/C split on every step). + auto raw = source.require_f32_tensor(prefix + ".mamba.conv1d.weight"); + const int64_t d_conv = raw.shape.dims[0]; + const int64_t conv_dim = raw.shape.dims[2]; + w.conv1d_kernel.shape = core::TensorShape::from_dims({d_conv, conv_dim}); + w.conv1d_kernel.values = raw.values; + } } w.ssm_conv1d_b = source.require_f32_tensor(prefix + ".mamba.conv1d.bias"); + // Per-head Mamba params must stay unquantized (Native): they are consumed as + // raw F32 scalars by the SSM path (A = -exp(A_log), D, dt bias). { auto meta = source.require_metadata(prefix + ".mamba.dt_bias"); - w.ssm_dt_b = store.load_tensor(source, prefix + ".mamba.dt_bias", storage_type, meta.shape); + w.ssm_dt_b = store.load_tensor(source, prefix + ".mamba.dt_bias", assets::TensorStorageType::F32, meta.shape); } { auto meta = source.require_metadata(prefix + ".mamba.A_log"); - w.ssm_A = store.load_tensor(source, prefix + ".mamba.A_log", storage_type, meta.shape); + w.ssm_A = store.load_tensor(source, prefix + ".mamba.A_log", assets::TensorStorageType::F32, meta.shape); } { auto meta = source.require_metadata(prefix + ".mamba.D"); - w.ssm_D = store.load_tensor(source, prefix + ".mamba.D", storage_type, meta.shape); + w.ssm_D = store.load_tensor(source, prefix + ".mamba.D", assets::TensorStorageType::F32, meta.shape); } { auto meta = source.require_metadata(prefix + ".mamba.out_proj.weight"); @@ -846,135 +875,1137 @@ std::vector build_falcon_embeddings( for (int64_t step = 0; step < steps; ++step) { const int32_t token = matrix[step]; auto row = lookup_row(weights.text_embedding_host, token, hidden); - for (auto & v : row) v *= config.text.embedding_multiplier; if (is_semantic_token(config, token)) { for (int64_t codebook = 0; codebook < config.fast.num_codebooks; ++codebook) { const int32_t code = matrix[(codebook + 1) * steps + step]; add_row(weights.codebook_embedding_host, codebook * config.fast.vocab_size + code, hidden, row); } } + for (auto & v : row) v *= config.text.embedding_multiplier; std::copy(row.begin(), row.end(), out.begin() + static_cast(step * hidden)); } return out; } -// TODO(Falcon-H1): Replace with full Mamba2 port (ggml_ssm_conv + B/C/dt/A/D -// + ggml_ssm_scan + recurrent conv/ssm state + hybrid attention). -// See docs/FALCON_H1_0.1B_PORT_PLAN.md M2/M3 and -// ../llama.cpp/src/models/mamba-base.cpp:151 / falcon-h1.cpp:132. -// Current stub keeps weight loading native but omits the SSM core and -// hybrid attention (attn_out = 0), recomputes full sequence each step, -// and only applies ssm_out/lm_head multipliers — tracked for follow-up. -SlowForwardOutput falcon_forward_stateless( +// ============================================================================ +// Falcon-H1 (Mamba2 + hybrid GQA attention) stateful single-token forward. +// Replaces the documented stub (TODO(Falcon-H1)) with a full Mamba2 port +// mirroring transformers.models.falcon_h1 FalconH1DecoderLayer and +// llama.cpp mamba-base.cpp build_mamba2_layer. Verified shapes against +// Audio8-TTS-Preview-0.1b (dim 512, d_ssm 768, d_state 64, d_conv 4, +// mamba heads 24 x head 32, GQA 8/2 x 64, RoPE NEOX base 1e11). +// ============================================================================ + +// One baked Falcon-H1 step graph (zero-copy path), valid for a fixed KV +// capacity bucket. All weights and recurrent state are bound as external views +// of host memory owned by the weights/state structs, so a plan can be reused +// for every step whose sequence fits the bucket. +struct FalconStepPlan { + int64_t cap = 0; + std::unique_ptr ctx; + ggml_cgraph * gf = nullptr; // owned by ctx + ggml_gallocr_t gallocr = nullptr; + ggml_tensor * logits_out = nullptr; + ggml_tensor * hidden_out = nullptr; + ggml_backend_t backend = nullptr; // borrowed; the runtime outlives any generation + ~FalconStepPlan() { + if (backend != nullptr && gf != nullptr) { + core::release_backend_graph_resources(backend, gf); + } + if (gallocr != nullptr) { + ggml_gallocr_free(gallocr); + } + } + FalconStepPlan() = default; + FalconStepPlan(const FalconStepPlan &) = delete; + FalconStepPlan & operator=(const FalconStepPlan &) = delete; +}; + +struct FalconH1StepState { + int64_t n_layer = 0; + int64_t d_inner = 0; // mamba_d_ssm (768) + int64_t d_state = 0; // mamba_d_state (64) + int64_t d_conv = 0; // mamba_d_conv (4) + int64_t n_groups = 0; // mamba_n_groups (1) + int64_t n_mamba_heads = 0; // mamba_n_heads (24) + int64_t conv_dim = 0; // d_inner + 2*ng*d_state (896) + int64_t kv_dim = 0; // n_local_heads * head_dim (128) + std::vector> conv_states; // [layer][(d_conv-1)*conv_dim] + std::vector> ssm_states; // [layer][d_state*d_inner] + std::vector> k_cache; // [layer][seq*kv_dim] + std::vector> v_cache; // [layer][seq*kv_dim] + int64_t seq_len = 0; + // Padded KV caches for the zero-copy path: flat [head_dim, kv_cap, n_kv] + // (head stride = head_dim*kv_cap), grown geometrically so each step's graph + // can write the new token's k/v in-place and flash-attend over a strided + // prefix view — no per-step host upload, read-back, or concat copy. The + // exact-size k_cache/v_cache vectors above are only used on the fallback + // (non-host backend) path. + std::vector> k_pad; + std::vector> v_pad; + int64_t kv_cap = 0; + // Zero-copy constant cache (filled once per generation on host backends): + // A = -exp(A_log) per mamba head, D expanded per channel, plus fallback + // buffers for absent norm/bias weights and the scalar graph inputs. + std::vector> pre_A; // [layer][n_mamba_heads] + std::vector> pre_D; // [layer][d_inner] + std::vector ones_dim; // [dim] + std::vector zeros_conv; // [conv_dim] + std::vector zeros_conv_kernel; // [d_conv*conv_dim] + int32_t ids_value = 0; + int32_t pos_value = 0; + bool constants_ready = false; + // Bucketed plan reuse (zero-copy path): staging buffer for the per-step + // embedding (the graph input tensor is baked to its address), the flash + // mask scratch (slot seq is the last visible position), the dynamic + // set_rows slot index, and the baked graph itself. + std::vector emb_stage; // [dim] + std::vector kv_mask; // [kv_cap] + int32_t kv_slot = 0; + std::unique_ptr plan; +}; + +FalconH1StepState init_falcon_step_state(const Audio8TtsConfig & config) { + FalconH1StepState st; + st.n_layer = config.text.n_layer; + st.d_inner = config.text.mamba_d_ssm; + st.d_state = config.text.mamba_d_state; + st.d_conv = config.text.mamba_d_conv; + st.n_groups = config.text.mamba_n_groups; + st.n_mamba_heads = config.text.mamba_n_heads; + st.conv_dim = st.d_inner + 2 * st.n_groups * st.d_state; + st.kv_dim = config.text.n_local_heads * config.text.head_dim; + st.conv_states.resize(static_cast(st.n_layer)); + st.ssm_states.resize(static_cast(st.n_layer)); + st.k_cache.resize(static_cast(st.n_layer)); + st.v_cache.resize(static_cast(st.n_layer)); + st.k_pad.resize(static_cast(st.n_layer)); + st.v_pad.resize(static_cast(st.n_layer)); + const size_t conv_sz = static_cast((st.d_conv - 1) * st.conv_dim); + const size_t ssm_sz = static_cast(st.d_state * st.d_inner); + for (int64_t i = 0; i < st.n_layer; ++i) { + st.conv_states[static_cast(i)].assign(conv_sz, 0.0F); + st.ssm_states[static_cast(i)].assign(ssm_sz, 0.0F); + } + st.seq_len = 0; + return st; +} + +// Grow the zero-copy padded KV caches. The head stride changes with capacity, +// so live slots are re-laid-out per head; called between steps, and per-step +// graphs re-bind their views from scratch afterwards. +void grow_falcon_kv_pad(FalconH1StepState & state, int64_t head_dim, int64_t n_kv, int64_t need) { + if (need <= state.kv_cap) return; + // 128-slot buckets: fine-grained enough to keep padded flash cheap, and + // they keep nek1 below the CPU flash kernel's split-KV threshold (512) for + // as long as possible so the masked padded reduction stays bitwise equal + // to the exact-prefix one. + int64_t new_cap = std::max(64, ((need + 127) / 128) * 128); + const size_t live = static_cast(head_dim * state.seq_len); // valid floats per head + for (auto * caches : {&state.k_pad, &state.v_pad}) { + for (auto & cache : *caches) { + std::vector next(static_cast(head_dim * new_cap * n_kv), 0.0F); + if (!cache.empty() && live > 0) { + const size_t old_stride = static_cast(head_dim * state.kv_cap); + const size_t new_stride = static_cast(head_dim * new_cap); + for (int64_t h = 0; h < n_kv; ++h) { + std::memcpy(next.data() + h * new_stride, + cache.data() + h * old_stride, + live * sizeof(float)); + } + } + cache.swap(next); + } + } + state.kv_cap = new_cap; +} + +// Loop-invariant SSM constants, resolved once per generation: A = -exp(A_log) +// per mamba head, D expanded per channel. +void precompute_falcon_constants( + const Audio8TtsConfig & config, + const ArkttsARWeights & weights, + FalconH1StepState & state) { + if (state.constants_ready) return; + const int64_t n_layer = config.text.n_layer; + const int64_t d_inner = config.text.mamba_d_ssm; + const int64_t n_mamba_heads = config.text.mamba_n_heads; + const int64_t mamba_head_dim = config.text.mamba_d_head; + state.pre_A.resize(static_cast(n_layer)); + state.pre_D.resize(static_cast(n_layer)); + for (int64_t li = 0; li < n_layer; ++li) { + const auto & layer = weights.falcon_layers[static_cast(li)]; + auto & a_vals = state.pre_A[static_cast(li)]; + a_vals.resize(static_cast(n_mamba_heads)); + std::vector a_log(static_cast(n_mamba_heads)); + ggml_backend_tensor_get(layer.ssm_A.tensor, a_log.data(), 0, a_log.size() * sizeof(float)); + for (int64_t h = 0; h < n_mamba_heads; ++h) { + a_vals[static_cast(h)] = -std::exp(a_log[static_cast(h)]); + } + auto & d_vals = state.pre_D[static_cast(li)]; + d_vals.resize(static_cast(d_inner)); + std::vector d_raw(static_cast(n_mamba_heads)); + ggml_backend_tensor_get(layer.ssm_D.tensor, d_raw.data(), 0, d_raw.size() * sizeof(float)); + for (int64_t h = 0; h < n_mamba_heads; ++h) { + for (int64_t d = 0; d < mamba_head_dim; ++d) { + d_vals[static_cast(d + h * mamba_head_dim)] = d_raw[static_cast(h)]; + } + } + } + state.constants_ready = true; +} + +// Builds the reusable per-bucket Falcon step graph for the zero-copy path. +// Every weight and recurrent-state tensor is bound as an external view of host +// memory owned by `weights`/`state` (gallocr skips tensors whose data is set +// externally), conv/ssm state write-backs and the KV slot writes run in-graph +// (ggml_cpy / ggml_set_rows), and flash attention reads the full padded cache +// under a mask so the graph topology does not depend on the sequence length. +std::unique_ptr build_falcon_step_plan( + ggml_backend_t backend, + size_t arena_bytes, + const Audio8TtsConfig & config, + const ArkttsARWeights & weights, + FalconH1StepState & state, + ArkttsARProfile * profile) { + const int64_t dim = config.text.dim; + const int64_t n_layer = config.text.n_layer; + const int64_t d_inner = config.text.mamba_d_ssm; + const int64_t d_state = config.text.mamba_d_state; + const int64_t d_conv = config.text.mamba_d_conv; + const int64_t n_groups = config.text.mamba_n_groups; + const int64_t n_mamba_heads = config.text.mamba_n_heads; + const int64_t mamba_head_dim = config.text.mamba_d_head; + const int64_t conv_dim = d_inner + 2 * n_groups * d_state; + const int64_t n_head = config.text.n_head; + const int64_t n_kv = config.text.n_local_heads; + const int64_t head_dim = config.text.head_dim; + const float norm_eps = config.text.norm_eps; + const float rope_base = config.text.rope_base; + const int64_t cap = state.kv_cap; + + auto plan = std::make_unique(); + plan->cap = cap; + plan->backend = backend; + + auto t_init = Clock::now(); + ggml_init_params params{arena_bytes, nullptr, true}; + plan->ctx.reset(ggml_init(params)); + if (!plan->ctx) throw std::runtime_error("build_falcon_step_plan: ggml_init failed"); + ggml_context * ctx = plan->ctx.get(); + auto t_build = Clock::now(); + + // Mask scratch: slots [0, seq_len] visible, the rest -inf; the runner + // reveals one more slot per step. + state.kv_mask.assign(static_cast(cap), ggml_fp32_to_fp16(-INFINITY)); + for (int64_t i = 0; i <= state.seq_len && i < cap; ++i) { + state.kv_mask[static_cast(i)] = ggml_fp32_to_fp16(0.0F); + } + ggml_tensor * mask_t = ggml_new_tensor_4d(ctx, GGML_TYPE_F16, cap, 1, 1, 1); + mask_t->data = state.kv_mask.data(); + + auto bind_const = [&](ggml_tensor * t, const std::vector & values, + std::vector & fallback, float fallback_fill) { + if (!values.empty()) { + t->data = const_cast(values.data()); + return; + } + if (fallback.empty()) { + fallback.assign(static_cast(ggml_nelements(t)), fallback_fill); + } + t->data = fallback.data(); + }; + + ggml_tensor * cur = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, dim); + ggml_set_name(cur, "falcon_input"); + cur->data = state.emb_stage.data(); + + std::vector writebacks; // conv/ssm state tails (dependency-ordered) + std::vector kv_writebacks; // KV slot writes alias the flash reads: expanded first + writebacks.reserve(static_cast(n_layer) * 2); + kv_writebacks.reserve(static_cast(n_layer) * 2); + + for (int64_t li = 0; li < n_layer; ++li) { + const auto & layer = weights.falcon_layers[static_cast(li)]; + + // input_layernorm (RMS) + ggml_tensor * ln_w = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, dim); + bind_const(ln_w, layer.input_layernorm.values, state.ones_dim, 1.0F); + ggml_tensor * normed = ggml_rms_norm(ctx, cur, norm_eps); + normed = ggml_mul(ctx, normed, ln_w); + + // ---- Mamba2 branch ---- + // zxBCdt = in_proj(normed) -> [d_inner + conv_dim + n_mamba_heads] + ggml_tensor * zxBCdt = ggml_mul_mat(ctx, layer.ssm_in.tensor, normed); + ggml_tensor * z = ggml_view_1d(ctx, zxBCdt, d_inner, 0); + ggml_tensor * xBC = ggml_view_1d(ctx, zxBCdt, conv_dim, d_inner * ggml_element_size(zxBCdt)); + ggml_tensor * dt = ggml_view_1d(ctx, zxBCdt, n_mamba_heads, (d_inner + conv_dim) * ggml_element_size(zxBCdt)); + + // conv: state (d_conv-1 rows) + current xBC -> [d_conv, conv_dim, 1] + ggml_tensor * st_t = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, conv_dim, d_conv - 1); + st_t->data = state.conv_states[static_cast(li)].data(); + ggml_tensor * stT = ggml_cont(ctx, ggml_transpose(ctx, st_t)); // [d_conv-1, conv_dim] + ggml_tensor * xBC_r = ggml_cont(ctx, ggml_transpose(ctx, ggml_reshape_2d(ctx, xBC, conv_dim, 1))); // [1, conv_dim] + ggml_tensor * sx = ggml_concat(ctx, stT, xBC_r, 0); // [d_conv, conv_dim] + // Next conv state = sx rows 1..d_conv-1, written back into the host + // state vector in-graph (the cpy depends on sx, hence on the cont() + // that consumed st_t, so the old state is read before it is + // overwritten). + ggml_tensor * tail = ggml_view_2d(ctx, sx, d_conv - 1, conv_dim, + sx->nb[1], ggml_element_size(sx)); + writebacks.push_back(ggml_cpy(ctx, ggml_transpose(ctx, tail), st_t)); + ggml_tensor * sx3 = ggml_reshape_3d(ctx, sx, d_conv, conv_dim, 1); + // ggml ssm_conv computes y[c] = sum_k w[k,c]*sx[k,c] with sx row 0 the + // oldest frame — the same orientation as the HF conv1d/cached decode. + ggml_tensor * conv_w2 = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, d_conv, conv_dim); + bind_const(conv_w2, layer.conv1d_kernel.values, state.zeros_conv_kernel, 0.0F); + ggml_tensor * xBC_conv = ggml_ssm_conv(ctx, sx3, conv_w2); // [conv_dim, 1, 1] + ggml_tensor * conv_b = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, conv_dim); + bind_const(conv_b, layer.ssm_conv1d_b.values, state.zeros_conv, 0.0F); + xBC_conv = ggml_add(ctx, xBC_conv, ggml_reshape_3d(ctx, conv_b, conv_dim, 1, 1)); + xBC_conv = ggml_silu(ctx, xBC_conv); + + // split x / B / C (conv output is contiguous [conv_dim,1,1]) + ggml_tensor * x = ggml_view_1d(ctx, xBC_conv, d_inner, 0); + ggml_tensor * B = ggml_view_1d(ctx, xBC_conv, d_state * n_groups, d_inner * ggml_element_size(xBC_conv)); + ggml_tensor * C = ggml_view_1d(ctx, xBC_conv, d_state * n_groups, (d_inner + d_state * n_groups) * ggml_element_size(xBC_conv)); + + // x -> [head_dim, n_mamba_heads, 1, 1] + ggml_tensor * x4 = ggml_view_4d(ctx, x, mamba_head_dim, n_mamba_heads, 1, 1, + mamba_head_dim * ggml_element_size(x), + mamba_head_dim * n_mamba_heads * ggml_element_size(x), + mamba_head_dim * n_mamba_heads * ggml_element_size(x), 0); + ggml_tensor * B4 = ggml_view_4d(ctx, B, d_state, n_groups, 1, 1, + d_state * ggml_element_size(B), + d_state * n_groups * ggml_element_size(B), + d_state * n_groups * ggml_element_size(B), 0); + ggml_tensor * C4 = ggml_view_4d(ctx, C, d_state, n_groups, 1, 1, + d_state * ggml_element_size(C), + d_state * n_groups * ggml_element_size(C), + d_state * n_groups * ggml_element_size(C), 0); + + // dt = dt + dt_bias -> [n_mamba_heads, 1, 1] + ggml_tensor * dt_eff = ggml_add(ctx, dt, layer.ssm_dt_b.tensor); + ggml_tensor * dt3 = ggml_view_3d(ctx, dt_eff, n_mamba_heads, 1, 1, + n_mamba_heads * ggml_element_size(dt_eff), + n_mamba_heads * ggml_element_size(dt_eff), 0); + + // A = -exp(A_log), precomputed: [1, n_mamba_heads] + ggml_tensor * A_t = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, 1, n_mamba_heads); + A_t->data = state.pre_A[static_cast(li)].data(); + + // ssm state: [d_state, mamba_head_dim, n_mamba_heads] + ggml_tensor * ssm_t = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, d_state, mamba_head_dim, n_mamba_heads); + ssm_t->data = state.ssm_states[static_cast(li)].data(); + + // ids for scan (1 sequence) + ggml_tensor * ids = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, 1); + ids->data = &state.ids_value; + + ggml_tensor * scan = ggml_ssm_scan(ctx, ssm_t, x4, dt3, A_t, B4, C4, ids); + // New ssm state = scan tail (after the d_inner y values), written back + // into the host state vector in-graph (ordered after the scan read). + ggml_tensor * next_state = ggml_view_3d(ctx, scan, + d_state, mamba_head_dim, n_mamba_heads, + d_state * ggml_element_size(scan), + d_state * mamba_head_dim * ggml_element_size(scan), + d_inner * ggml_element_size(scan)); + writebacks.push_back(ggml_cpy(ctx, next_state, ssm_t)); + ggml_tensor * y = ggml_view_1d(ctx, scan, d_inner, 0); + + // y += x * D (D precomputed) + ggml_tensor * D_t = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, d_inner); + D_t->data = state.pre_D[static_cast(li)].data(); + y = ggml_add(ctx, y, ggml_mul(ctx, ggml_view_1d(ctx, x4, d_inner, 0), D_t)); + + // z gate: y *= silu(z) + y = ggml_mul(ctx, y, ggml_silu(ctx, z)); + + ggml_tensor * out_mamba = ggml_mul_mat(ctx, layer.ssm_out.tensor, y); // [dim] + + // ---- GQA attention branch ---- + // ggml_flash_attn_ext layout: [head_dim, n_tokens, n_head, batch]. + ggml_tensor * q = ggml_mul_mat(ctx, layer.attn_q_proj.tensor, normed); // [n_head*head_dim] + ggml_tensor * k = ggml_mul_mat(ctx, layer.attn_k_proj.tensor, normed); // [n_kv*head_dim] + ggml_tensor * v = ggml_mul_mat(ctx, layer.attn_v_proj.tensor, normed); // [n_kv*head_dim] + + ggml_tensor * q4 = ggml_view_4d(ctx, q, head_dim, n_head, 1, 1, + head_dim * ggml_element_size(q), + head_dim * n_head * ggml_element_size(q), + head_dim * n_head * ggml_element_size(q), 0); + ggml_tensor * k4 = ggml_view_4d(ctx, k, head_dim, n_kv, 1, 1, + head_dim * ggml_element_size(k), + head_dim * n_kv * ggml_element_size(k), + head_dim * n_kv * ggml_element_size(k), 0); + ggml_tensor * v4 = ggml_view_4d(ctx, v, head_dim, n_kv, 1, 1, + head_dim * ggml_element_size(v), + head_dim * n_kv * ggml_element_size(v), + head_dim * n_kv * ggml_element_size(v), 0); + + // RoPE (NEOX / HF default half rotation), base 1e11; ne2 = n_tokens = 1. + ggml_tensor * pos_t = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, 1); + pos_t->data = &state.pos_value; + ggml_tensor * q_r = ggml_rope_ext(ctx, q4, pos_t, nullptr, head_dim, + GGML_ROPE_TYPE_NEOX, config.text.max_seq_len, + rope_base, 1.0F, 1.0F, 1.0F, 32.0F, 1.0F); + ggml_tensor * k_r = ggml_rope_ext(ctx, k4, pos_t, nullptr, head_dim, + GGML_ROPE_TYPE_NEOX, config.text.max_seq_len, + rope_base, 1.0F, 1.0F, 1.0F, 32.0F, 1.0F); + + // [head_dim, n_head, 1, 1] -> permute(0,2,1,3) -> [head_dim, 1, n_head, 1] + ggml_tensor * q_p = ggml_permute(ctx, q_r, 0, 2, 1, 3); + ggml_tensor * k_p = ggml_permute(ctx, k_r, 0, 2, 1, 3); + ggml_tensor * v_p = ggml_permute(ctx, v4, 0, 2, 1, 3); + + // Padded caches as external leaves [head_dim, cap, n_kv, 1]; the fresh + // k/v land in slot `kv_slot` via in-graph set_rows, and flash attends + // the whole padded cache under the mask (slots > seq are -inf, which + // contributes exactly zero to the softmax). + ggml_tensor * k_pad_t = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, head_dim, cap, n_kv, 1); + ggml_tensor * v_pad_t = ggml_new_tensor_4d(ctx, GGML_TYPE_F32, head_dim, cap, n_kv, 1); + k_pad_t->data = state.k_pad[static_cast(li)].data(); + v_pad_t->data = state.v_pad[static_cast(li)].data(); + ggml_tensor * slot_t = ggml_new_tensor_1d(ctx, GGML_TYPE_I32, 1); + slot_t->data = &state.kv_slot; + kv_writebacks.push_back(ggml_set_rows(ctx, k_pad_t, k_p, slot_t)); + kv_writebacks.push_back(ggml_set_rows(ctx, v_pad_t, v_p, slot_t)); + + // Single-token causal: all unmasked cache slots are visible. + ggml_tensor * attn = ggml_flash_attn_ext(ctx, q_p, k_pad_t, v_pad_t, mask_t, + 1.0F / std::sqrt(static_cast(head_dim)), + 0.0F, 0.0F); + ggml_flash_attn_ext_set_prec(attn, GGML_PREC_F32); + ggml_tensor * attn_flat = ggml_cont(ctx, ggml_reshape_1d(ctx, attn, n_head * head_dim)); + ggml_tensor * attn_out = ggml_mul_mat(ctx, layer.attn_o_proj.tensor, attn_flat); // [dim] + + // ---- merge + residual ---- + ggml_tensor * h = ggml_add(ctx, out_mamba, attn_out); + h = ggml_add(ctx, cur, h); + + // ---- FFN ---- + ggml_tensor * pre_w = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, dim); + bind_const(pre_w, layer.pre_ff_layernorm.values, state.ones_dim, 1.0F); + ggml_tensor * h2 = ggml_rms_norm(ctx, h, norm_eps); + h2 = ggml_mul(ctx, h2, pre_w); + ggml_tensor * gate_ff = ggml_mul_mat(ctx, layer.ffn_gate.tensor, h2); + ggml_tensor * up_ff = ggml_mul_mat(ctx, layer.ffn_up.tensor, h2); + ggml_tensor * gated = ggml_mul(ctx, ggml_silu(ctx, gate_ff), up_ff); + ggml_tensor * down = ggml_mul_mat(ctx, layer.ffn_down.tensor, gated); + cur = ggml_add(ctx, h, down); + } + + // final norm + semantic head + ggml_tensor * final_w = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, dim); + bind_const(final_w, weights.slow_norm.values, state.ones_dim, 1.0F); + ggml_tensor * final_norm = ggml_rms_norm(ctx, cur, norm_eps); + final_norm = ggml_mul(ctx, final_norm, final_w); + ggml_tensor * logits = ggml_mul_mat(ctx, weights.falcon_lm_head.tensor, final_norm); // [vocab] + // NOTE: lm_head_multiplier applies only to FalconH1ForCausalLM's full-vocab head. + // ArkttsModel uses the compact semantic_output head and does NOT scale logits. + plan->logits_out = ggml_dup(ctx, logits); + plan->hidden_out = ggml_dup(ctx, final_norm); + ggml_set_name(plan->logits_out, "logits_out"); + ggml_set_name(plan->hidden_out, "hidden_out"); + // Pin the outputs: the writeback nodes expanded after them allocate no + // memory of their own, but the flag keeps gallocr from ever reusing the + // output buffers while the plan is reused across steps. + ggml_set_output(plan->logits_out); + ggml_set_output(plan->hidden_out); + + ggml_cgraph * gf = ggml_new_graph_custom(ctx, 8192, false); + // KV slot writes first: they alias the cache the attention reads, so they + // must precede those nodes in graph order (backends execute sequentially). + for (ggml_tensor * wb : kv_writebacks) { + ggml_build_forward_expand(gf, wb); + } + ggml_build_forward_expand(gf, plan->logits_out); + ggml_build_forward_expand(gf, plan->hidden_out); + for (ggml_tensor * wb : writebacks) { + ggml_build_forward_expand(gf, wb); + } + plan->gf = gf; + auto t_graph = Clock::now(); + plan->gallocr = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend)); + if (!plan->gallocr || !ggml_gallocr_reserve(plan->gallocr, gf) || !ggml_gallocr_alloc_graph(plan->gallocr, gf)) { + throw std::runtime_error("build_falcon_step_plan: gallocr failed"); + } + auto t_alloc = Clock::now(); + if (profile != nullptr) { + profile->falcon_step_init_ms += engine::debug::elapsed_ms(t_init, t_build); + profile->falcon_step_build_ms += engine::debug::elapsed_ms(t_build, t_graph); + profile->falcon_step_gallocr_ms += engine::debug::elapsed_ms(t_graph, t_alloc); + } + return plan; +} + +// Zero-copy runner: feed the staged inputs, run the baked bucket graph, read +// logits/hidden. No per-step graph construction, allocation, or state I/O. +SlowForwardOutput falcon_forward_step_zero_copy( ggml_backend_t backend, int threads, size_t arena_bytes, const Audio8TtsConfig & config, const ArkttsARWeights & weights, - const std::vector & embeddings, - int64_t seq_len) { - if (seq_len <= 0) throw std::runtime_error("falcon_forward: zero seq"); - if (weights.falcon_layers.empty()) throw std::runtime_error("falcon_forward: no falcon layers"); + const std::vector & embedding, + FalconH1StepState & state, + int64_t position, + ArkttsARProfile * profile) { const int64_t dim = config.text.dim; - const float eps = config.text.norm_eps; - const float lm_mult = config.text.lm_head_multiplier; + const int64_t head_dim = config.text.head_dim; + const int64_t n_kv = config.text.n_local_heads; + const int64_t vocab = config.fast.vocab_size + 1; + const int64_t seq = state.seq_len; + + precompute_falcon_constants(config, weights, state); + grow_falcon_kv_pad(state, head_dim, n_kv, seq + 1); + if (state.emb_stage.empty()) { + state.emb_stage.assign(static_cast(dim), 0.0F); + } + if (!state.plan || state.plan->cap != state.kv_cap) { + state.plan = build_falcon_step_plan(backend, arena_bytes, config, weights, state, profile); + } + auto t_feed = Clock::now(); + std::memcpy(state.emb_stage.data(), embedding.data(), embedding.size() * sizeof(float)); + state.pos_value = static_cast(position); + state.kv_slot = static_cast(seq); + state.kv_mask[static_cast(seq)] = ggml_fp32_to_fp16(0.0F); + auto t_compute = Clock::now(); + core::set_backend_threads(backend, threads); + const ggml_status status = core::compute_backend_graph(backend, state.plan->gf, nullptr, "falcon_forward_step"); + ggml_backend_synchronize(backend); + auto t_read = Clock::now(); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("falcon_forward_step compute failed"); + } + SlowForwardOutput out; + out.logits.resize(static_cast(vocab)); + out.hidden.resize(static_cast(dim)); + ggml_backend_tensor_get(state.plan->logits_out, out.logits.data(), 0, out.logits.size() * sizeof(float)); + ggml_backend_tensor_get(state.plan->hidden_out, out.hidden.data(), 0, out.hidden.size() * sizeof(float)); + state.seq_len = seq + 1; + if (profile != nullptr) { + profile->falcon_step_upload_ms += engine::debug::elapsed_ms(t_feed, t_compute); + profile->falcon_step_compute_ms += engine::debug::elapsed_ms(t_compute, t_read); + profile->falcon_step_download_ms += engine::debug::elapsed_ms(t_read, Clock::now()); + profile->falcon_step_runs += 1; + } + return out; +} + +// Single-token Falcon-H1 forward. `embedding` is the pre-multiplied token +// embedding (text embedding * embedding_multiplier + codebook sum). +SlowForwardOutput falcon_forward_step( + ggml_backend_t backend, + int threads, + size_t arena_bytes, + const Audio8TtsConfig & config, + const ArkttsARWeights & weights, + const std::vector & embedding, // [dim] + FalconH1StepState & state, + int64_t position, + ArkttsARProfile * profile = nullptr) { + const int64_t dim = config.text.dim; + const int64_t n_layer = config.text.n_layer; + const int64_t d_inner = config.text.mamba_d_ssm; + const int64_t d_state = config.text.mamba_d_state; + const int64_t d_conv = config.text.mamba_d_conv; + const int64_t n_groups = config.text.mamba_n_groups; + const int64_t n_mamba_heads = config.text.mamba_n_heads; + const int64_t mamba_head_dim = config.text.mamba_d_head; + const int64_t conv_dim = d_inner + 2 * n_groups * d_state; + const int64_t n_head = config.text.n_head; + const int64_t n_kv = config.text.n_local_heads; + const int64_t head_dim = config.text.head_dim; + const float norm_eps = config.text.norm_eps; + const float rope_base = config.text.rope_base; + const int64_t vocab = config.fast.vocab_size + 1; + const int64_t seq = state.seq_len; + + if (embedding.size() != static_cast(dim)) { + throw std::runtime_error("falcon_forward_step: embedding size mismatch"); + } + + // Host backends run the reusable zero-copy plan graph (one baked graph per + // KV capacity bucket; weights/state bound as external host views, state + // write-backs in-graph). Other backends keep the per-call explicit + // upload/download path below. + const bool zero_copy = core::backend_type(backend) == core::BackendType::Cpu; + if (zero_copy) { + return falcon_forward_step_zero_copy(backend, threads, arena_bytes, config, weights, + embedding, state, position, profile); + } + + auto t_init = Clock::now(); ggml_init_params params{arena_bytes, nullptr, true}; std::unique_ptr ctx(ggml_init(params)); - if (!ctx) throw std::runtime_error("falcon_forward: ggml_init failed"); - ggml_tensor * cur = ggml_new_tensor_2d(ctx.get(), GGML_TYPE_F32, dim, seq_len); + if (!ctx) throw std::runtime_error("falcon_forward_step: ggml_init failed"); + auto t_build = Clock::now(); + + ggml_tensor * cur = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_F32, dim); ggml_set_name(cur, "falcon_input"); - std::vector ln_ws; - std::vector bias_ws; - std::vector pre_ws; - ln_ws.reserve(weights.falcon_layers.size()); - bias_ws.reserve(weights.falcon_layers.size()); - pre_ws.reserve(weights.falcon_layers.size()); - for (size_t li = 0; li < weights.falcon_layers.size(); ++li) { - const auto & layer = weights.falcon_layers[li]; + if (zero_copy) { + cur->data = const_cast(embedding.data()); + } else { + ggml_set_input(cur); + } + + // Bind a loop-invariant F32 tensor to its host values with zero copies when + // the backend reads host memory directly; otherwise mark it for the upload + // pass below. `fallback` (filled once with `fallback_fill`) covers weights + // that are absent from the checkpoint. + auto bind_const = [&](ggml_tensor * t, const std::vector & values, + std::vector & fallback, float fallback_fill) { + if (!zero_copy) { + ggml_set_input(t); + return; + } + if (!values.empty()) { + t->data = const_cast(values.data()); + return; + } + if (fallback.empty()) { + fallback.assign(static_cast(ggml_nelements(t)), fallback_fill); + } + t->data = fallback.data(); + }; + auto bind_state = [&](ggml_tensor * t, const std::vector & values) { + if (!zero_copy) { + ggml_set_input(t); + return; + } + t->data = const_cast(values.data()); + }; + std::vector writebacks; + writebacks.reserve(static_cast(n_layer) * 2); + // KV slot writes have no data dependency protecting them from the + // attention read of the same cache, so they are expanded into the graph + // FIRST below — backends execute nodes sequentially in graph order. + std::vector kv_writebacks; + kv_writebacks.reserve(static_cast(n_layer) * 2); + + std::vector ln_w_ts; + std::vector pre_w_ts; + std::vector conv_st_ts; + std::vector ssm_st_ts; + std::vector k_cur_ts; + std::vector v_cur_ts; + std::vector conv_b_ts; + std::vector conv_w2_ts; + std::vector k_cache_ts; + std::vector v_cache_ts; + std::vector sx_ts; + std::vector scan_ts; + std::vector ids_ts; + std::vector pos_ts; + ln_w_ts.reserve(static_cast(n_layer)); + pre_w_ts.reserve(static_cast(n_layer)); + conv_st_ts.reserve(static_cast(n_layer)); + ssm_st_ts.reserve(static_cast(n_layer)); + k_cur_ts.reserve(static_cast(n_layer)); + v_cur_ts.reserve(static_cast(n_layer)); + conv_b_ts.reserve(static_cast(n_layer)); + conv_w2_ts.reserve(static_cast(n_layer)); + k_cache_ts.reserve(static_cast(n_layer)); + v_cache_ts.reserve(static_cast(n_layer)); + sx_ts.reserve(static_cast(n_layer)); + scan_ts.reserve(static_cast(n_layer)); + ids_ts.reserve(static_cast(n_layer)); + pos_ts.reserve(static_cast(n_layer)); + + // A = -exp(A_log) per layer + std::vector A_ts; + A_ts.reserve(static_cast(n_layer)); + // D expanded to [d_inner]: D[h] repeated mamba_head_dim times + std::vector D_ts; + + for (int64_t li = 0; li < n_layer; ++li) { + const auto & layer = weights.falcon_layers[static_cast(li)]; + + // input_layernorm (RMS) ggml_tensor * ln_w = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_F32, dim); - ln_ws.push_back(ln_w); - ggml_tensor * normed = ggml_rms_norm(ctx.get(), cur, eps); + bind_const(ln_w, layer.input_layernorm.values, state.ones_dim, 1.0F); + ln_w_ts.push_back(ln_w); + ggml_tensor * normed = ggml_rms_norm(ctx.get(), cur, norm_eps); normed = ggml_mul(ctx.get(), normed, ln_w); - ggml_tensor * proj = ggml_mul_mat(ctx.get(), layer.ssm_in.tensor, normed); - ggml_tensor * gate = ggml_view_2d(ctx.get(), proj, 768, seq_len, proj->nb[1], 0); - ggml_tensor * xBC = ggml_view_2d(ctx.get(), proj, 896, seq_len, proj->nb[1], 768 * sizeof(float)); - ggml_tensor * bias = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_F32, 896); - bias_ws.push_back(bias); - ggml_tensor * bias_bcast = ggml_repeat(ctx.get(), bias, xBC); - ggml_tensor * xBC_b = ggml_add(ctx.get(), xBC, bias_bcast); - ggml_tensor * xBC_silu = ggml_silu(ctx.get(), xBC_b); - ggml_tensor * x = ggml_view_2d(ctx.get(), xBC_silu, 768, seq_len, xBC_silu->nb[1], 0); - ggml_tensor * gate_silu = ggml_silu(ctx.get(), gate); - ggml_tensor * y_gated = ggml_mul(ctx.get(), x, gate_silu); - ggml_tensor * out_mamba = ggml_mul_mat(ctx.get(), layer.ssm_out.tensor, y_gated); - if (std::abs(config.text.ssm_out_multiplier - 1.0f) > 1e-6) out_mamba = ggml_scale(ctx.get(), out_mamba, config.text.ssm_out_multiplier); - ggml_tensor * attn_out = ggml_scale(ctx.get(), cur, 0.0f); - ggml_tensor * hybrid = ggml_add(ctx.get(), out_mamba, attn_out); - ggml_tensor * cur_res = ggml_add(ctx.get(), cur, hybrid); + + // ---- Mamba2 branch ---- + // zxBCdt = in_proj(normed) -> [d_inner + conv_dim + n_mamba_heads] + ggml_tensor * zxBCdt = ggml_mul_mat(ctx.get(), layer.ssm_in.tensor, normed); + ggml_tensor * z = ggml_view_1d(ctx.get(), zxBCdt, d_inner, 0); + ggml_tensor * xBC = ggml_view_1d(ctx.get(), zxBCdt, conv_dim, d_inner * ggml_element_size(zxBCdt)); + ggml_tensor * dt = ggml_view_1d(ctx.get(), zxBCdt, n_mamba_heads, (d_inner + conv_dim) * ggml_element_size(zxBCdt)); + + // conv: state (d_conv-1 rows) + current xBC -> [d_conv, conv_dim, 1] + ggml_tensor * st_t = ggml_new_tensor_2d(ctx.get(), GGML_TYPE_F32, conv_dim, d_conv - 1); + bind_state(st_t, state.conv_states[static_cast(li)]); + conv_st_ts.push_back(st_t); + ggml_tensor * stT = ggml_cont(ctx.get(), ggml_transpose(ctx.get(), st_t)); // [d_conv-1, conv_dim] + ggml_tensor * xBC_r = ggml_cont(ctx.get(), ggml_transpose(ctx.get(), ggml_reshape_2d(ctx.get(), xBC, conv_dim, 1))); // [1, conv_dim] + ggml_tensor * sx = ggml_concat(ctx.get(), stT, xBC_r, 0); // [d_conv, conv_dim] + sx_ts.push_back(sx); + if (zero_copy) { + // Next conv state = sx rows 1..d_conv-1, written back into the host + // state vector in-graph. The cpy depends on sx (hence on the cont() + // that consumed st_t), so the old state is read before it is + // overwritten on every backend. + ggml_tensor * tail = ggml_view_2d(ctx.get(), sx, d_conv - 1, conv_dim, + sx->nb[1], ggml_element_size(sx)); + writebacks.push_back(ggml_cpy(ctx.get(), ggml_transpose(ctx.get(), tail), st_t)); + } else { + ggml_set_output(sx); // host reads the conv window back — pin the buffer + } + ggml_tensor * sx3 = ggml_reshape_3d(ctx.get(), sx, d_conv, conv_dim, 1); + // ggml ssm_conv computes y[c] = sum_k w[k,c]*sx[k,c] with sx row 0 the oldest + // frame — the same orientation as the HF conv1d/cached decode, so the GGUF + // kernel is fed as loaded (see load_falcon_layer). + ggml_tensor * conv_w2 = ggml_new_tensor_2d(ctx.get(), GGML_TYPE_F32, d_conv, conv_dim); + bind_const(conv_w2, layer.conv1d_kernel.values, state.zeros_conv_kernel, 0.0F); + conv_w2_ts.push_back(conv_w2); + ggml_tensor * xBC_conv = ggml_ssm_conv(ctx.get(), sx3, conv_w2); // [conv_dim, 1, 1] + ggml_tensor * conv_b = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_F32, conv_dim); + bind_const(conv_b, layer.ssm_conv1d_b.values, state.zeros_conv, 0.0F); + conv_b_ts.push_back(conv_b); + xBC_conv = ggml_add(ctx.get(), xBC_conv, ggml_reshape_3d(ctx.get(), conv_b, conv_dim, 1, 1)); + xBC_conv = ggml_silu(ctx.get(), xBC_conv); + + // split x / B / C (conv output is contiguous [conv_dim,1,1]) + ggml_tensor * x = ggml_view_1d(ctx.get(), xBC_conv, d_inner, 0); + ggml_tensor * B = ggml_view_1d(ctx.get(), xBC_conv, d_state * n_groups, d_inner * ggml_element_size(xBC_conv)); + ggml_tensor * C = ggml_view_1d(ctx.get(), xBC_conv, d_state * n_groups, (d_inner + d_state * n_groups) * ggml_element_size(xBC_conv)); + + // x -> [head_dim, n_mamba_heads, 1, 1] + ggml_tensor * x4 = ggml_view_4d(ctx.get(), x, mamba_head_dim, n_mamba_heads, 1, 1, + mamba_head_dim * ggml_element_size(x), + mamba_head_dim * n_mamba_heads * ggml_element_size(x), + mamba_head_dim * n_mamba_heads * ggml_element_size(x), 0); + ggml_tensor * B4 = ggml_view_4d(ctx.get(), B, d_state, n_groups, 1, 1, + d_state * ggml_element_size(B), + d_state * n_groups * ggml_element_size(B), + d_state * n_groups * ggml_element_size(B), 0); + ggml_tensor * C4 = ggml_view_4d(ctx.get(), C, d_state, n_groups, 1, 1, + d_state * ggml_element_size(C), + d_state * n_groups * ggml_element_size(C), + d_state * n_groups * ggml_element_size(C), 0); + + // dt = dt + dt_bias -> [n_mamba_heads, 1, 1] + ggml_tensor * dt_eff = ggml_add(ctx.get(), dt, layer.ssm_dt_b.tensor); + ggml_tensor * dt3 = ggml_view_3d(ctx.get(), dt_eff, n_mamba_heads, 1, 1, + n_mamba_heads * ggml_element_size(dt_eff), + n_mamba_heads * ggml_element_size(dt_eff), 0); + + // A = -exp(A_log): [1, n_mamba_heads] — precomputed once per generation + // into state.pre_A (zero-copy) or uploaded per step below. + ggml_tensor * A_t = ggml_new_tensor_2d(ctx.get(), GGML_TYPE_F32, 1, n_mamba_heads); + if (zero_copy) { + A_t->data = state.pre_A[static_cast(li)].data(); + } else { + ggml_set_input(A_t); + } + A_ts.push_back(A_t); + + // ssm state: [d_state, mamba_head_dim, n_mamba_heads] + ggml_tensor * ssm_t = ggml_new_tensor_3d(ctx.get(), GGML_TYPE_F32, d_state, mamba_head_dim, n_mamba_heads); + bind_state(ssm_t, state.ssm_states[static_cast(li)]); + ssm_st_ts.push_back(ssm_t); + + // ids for scan (1 sequence) + ggml_tensor * ids = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_I32, 1); + if (zero_copy) { + ids->data = &state.ids_value; + } else { + ggml_set_input(ids); + } + ids_ts.push_back(ids); + + ggml_tensor * scan = ggml_ssm_scan(ctx.get(), ssm_t, x4, dt3, A_t, B4, C4, ids); + scan_ts.push_back(scan); + if (zero_copy) { + // New ssm state = scan tail (after the d_inner y values), written + // back into the host state vector in-graph. The cpy depends on the + // scan that read ssm_t, so the old state is fully consumed first. + ggml_tensor * next_state = ggml_view_3d(ctx.get(), scan, + d_state, mamba_head_dim, n_mamba_heads, + d_state * ggml_element_size(scan), + d_state * mamba_head_dim * ggml_element_size(scan), + d_inner * ggml_element_size(scan)); + writebacks.push_back(ggml_cpy(ctx.get(), next_state, ssm_t)); + } else { + ggml_set_output(scan); // keep state tail alive for host read-back + } + ggml_tensor * y = ggml_view_1d(ctx.get(), scan, d_inner, 0); + + // y += x * D (D precomputed per generation / uploaded per step) + ggml_tensor * D_t = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_F32, d_inner); + if (zero_copy) { + D_t->data = state.pre_D[static_cast(li)].data(); + } else { + ggml_set_input(D_t); + } + D_ts.push_back(D_t); + y = ggml_add(ctx.get(), y, ggml_mul(ctx.get(), ggml_view_1d(ctx.get(), x4, d_inner, 0), D_t)); + + // z gate: y *= silu(z) + y = ggml_mul(ctx.get(), y, ggml_silu(ctx.get(), z)); + + ggml_tensor * out_mamba = ggml_mul_mat(ctx.get(), layer.ssm_out.tensor, y); // [dim] + + // ---- GQA attention branch ---- + // ggml_flash_attn_ext layout: [head_dim, n_tokens, n_head, batch]. + // Project -> [head_dim, n_head, 1, 1] -> RoPE (ne2 = tokens) -> permute(0,2,1,3) + // -> [head_dim, 1, n_head, 1]; KV cache kept in the same layout. + ggml_tensor * q = ggml_mul_mat(ctx.get(), layer.attn_q_proj.tensor, normed); // [n_head*head_dim] + ggml_tensor * k = ggml_mul_mat(ctx.get(), layer.attn_k_proj.tensor, normed); // [n_kv*head_dim] + ggml_tensor * v = ggml_mul_mat(ctx.get(), layer.attn_v_proj.tensor, normed); // [n_kv*head_dim] + + ggml_tensor * q4 = ggml_view_4d(ctx.get(), q, head_dim, n_head, 1, 1, + head_dim * ggml_element_size(q), + head_dim * n_head * ggml_element_size(q), + head_dim * n_head * ggml_element_size(q), 0); + ggml_tensor * k4 = ggml_view_4d(ctx.get(), k, head_dim, n_kv, 1, 1, + head_dim * ggml_element_size(k), + head_dim * n_kv * ggml_element_size(k), + head_dim * n_kv * ggml_element_size(k), 0); + ggml_tensor * v4 = ggml_view_4d(ctx.get(), v, head_dim, n_kv, 1, 1, + head_dim * ggml_element_size(v), + head_dim * n_kv * ggml_element_size(v), + head_dim * n_kv * ggml_element_size(v), 0); + + // RoPE (NEOX / HF default half rotation), base 1e11; ne2 = n_tokens = 1. + ggml_tensor * pos_t = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_I32, 1); + if (zero_copy) { + pos_t->data = &state.pos_value; + } else { + ggml_set_input(pos_t); + } + pos_ts.push_back(pos_t); + ggml_tensor * q_r = ggml_rope_ext(ctx.get(), q4, pos_t, nullptr, head_dim, + GGML_ROPE_TYPE_NEOX, config.text.max_seq_len, + rope_base, 1.0F, 1.0F, 1.0F, 32.0F, 1.0F); + ggml_tensor * k_r = ggml_rope_ext(ctx.get(), k4, pos_t, nullptr, head_dim, + GGML_ROPE_TYPE_NEOX, config.text.max_seq_len, + rope_base, 1.0F, 1.0F, 1.0F, 32.0F, 1.0F); + // k_p/v_p (and their k_r/v bases) are read back on the host for the KV + // cache on the fallback path — pin the underlying buffers there. + if (!zero_copy) { + ggml_set_output(k_r); + ggml_set_output(v); + } + + // [head_dim, n_head, 1, 1] -> permute(0,2,1,3) -> [head_dim, 1, n_head, 1] + ggml_tensor * q_p = ggml_permute(ctx.get(), q_r, 0, 2, 1, 3); + ggml_tensor * k_p = ggml_permute(ctx.get(), k_r, 0, 2, 1, 3); + ggml_tensor * v_p = ggml_permute(ctx.get(), v4, 0, 2, 1, 3); + k_cur_ts.push_back(k_p); + v_cur_ts.push_back(v_p); + + // KV cache in flash layout: [head_dim, n_tokens, n_kv, 1] + ggml_tensor * K_all = nullptr; + ggml_tensor * V_all = nullptr; + if (zero_copy) { + // Padded caches bound as external leaves; the fresh k/v are written + // into slot `seq` in-graph (kv_writebacks are expanded before the + // attention nodes, ordering the writes first), and flash attends + // over a strided prefix view of slots [0, seq+1). CPU + // flash_attn_ext only requires contiguous rows (nb0), so the + // padded head stride is fine. + ggml_tensor * k_pad_t = ggml_new_tensor_4d(ctx.get(), GGML_TYPE_F32, head_dim, state.kv_cap, n_kv, 1); + ggml_tensor * v_pad_t = ggml_new_tensor_4d(ctx.get(), GGML_TYPE_F32, head_dim, state.kv_cap, n_kv, 1); + k_pad_t->data = state.k_pad[static_cast(li)].data(); + v_pad_t->data = state.v_pad[static_cast(li)].data(); + const size_t slot_off = static_cast(seq * head_dim) * ggml_element_size(k_pad_t); + kv_writebacks.push_back(ggml_cpy(ctx.get(), k_p, + ggml_view_4d(ctx.get(), k_pad_t, head_dim, 1, n_kv, 1, + k_pad_t->nb[1], k_pad_t->nb[2], k_pad_t->nb[3], slot_off))); + kv_writebacks.push_back(ggml_cpy(ctx.get(), v_p, + ggml_view_4d(ctx.get(), v_pad_t, head_dim, 1, n_kv, 1, + v_pad_t->nb[1], v_pad_t->nb[2], v_pad_t->nb[3], slot_off))); + K_all = ggml_view_4d(ctx.get(), k_pad_t, head_dim, seq + 1, n_kv, 1, + k_pad_t->nb[1], k_pad_t->nb[2], k_pad_t->nb[3], 0); + V_all = ggml_view_4d(ctx.get(), v_pad_t, head_dim, seq + 1, n_kv, 1, + v_pad_t->nb[1], v_pad_t->nb[2], v_pad_t->nb[3], 0); + } else { + ggml_tensor * k_cache_t = ggml_new_tensor_4d(ctx.get(), GGML_TYPE_F32, head_dim, seq, n_kv, 1); + ggml_tensor * v_cache_t = ggml_new_tensor_4d(ctx.get(), GGML_TYPE_F32, head_dim, seq, n_kv, 1); + ggml_set_input(k_cache_t); + ggml_set_input(v_cache_t); + k_cache_ts.push_back(k_cache_t); + v_cache_ts.push_back(v_cache_t); + K_all = ggml_concat(ctx.get(), k_cache_t, k_p, 1); // [head_dim, seq+1, n_kv, 1] + V_all = ggml_concat(ctx.get(), v_cache_t, v_p, 1); + } + + // Single-token causal: current query attends to all cached keys (all visible). + ggml_tensor * attn = ggml_flash_attn_ext(ctx.get(), q_p, K_all, V_all, nullptr, + 1.0F / std::sqrt(static_cast(head_dim)), + 0.0F, 0.0F); + ggml_flash_attn_ext_set_prec(attn, GGML_PREC_F32); + ggml_tensor * attn_flat = ggml_cont(ctx.get(), ggml_reshape_1d(ctx.get(), attn, n_head * head_dim)); + ggml_tensor * attn_out = ggml_mul_mat(ctx.get(), layer.attn_o_proj.tensor, attn_flat); // [dim] + + // ---- merge + residual ---- + ggml_tensor * h = ggml_add(ctx.get(), out_mamba, attn_out); + h = ggml_add(ctx.get(), cur, h); + + // ---- FFN ---- ggml_tensor * pre_w = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_F32, dim); - pre_ws.push_back(pre_w); - ggml_tensor * pre_norm = ggml_rms_norm(ctx.get(), cur_res, eps); - pre_norm = ggml_mul(ctx.get(), pre_norm, pre_w); - ggml_tensor * gate_ff = ggml_mul_mat(ctx.get(), layer.ffn_gate.tensor, pre_norm); - ggml_tensor * up_ff = ggml_mul_mat(ctx.get(), layer.ffn_up.tensor, pre_norm); - ggml_tensor * gate_silu2 = ggml_silu(ctx.get(), gate_ff); - ggml_tensor * gated = ggml_mul(ctx.get(), gate_silu2, up_ff); + bind_const(pre_w, layer.pre_ff_layernorm.values, state.ones_dim, 1.0F); + pre_w_ts.push_back(pre_w); + ggml_tensor * h2 = ggml_rms_norm(ctx.get(), h, norm_eps); + h2 = ggml_mul(ctx.get(), h2, pre_w); + ggml_tensor * gate_ff = ggml_mul_mat(ctx.get(), layer.ffn_gate.tensor, h2); + ggml_tensor * up_ff = ggml_mul_mat(ctx.get(), layer.ffn_up.tensor, h2); + ggml_tensor * gated = ggml_mul(ctx.get(), ggml_silu(ctx.get(), gate_ff), up_ff); ggml_tensor * down = ggml_mul_mat(ctx.get(), layer.ffn_down.tensor, gated); - cur = ggml_add(ctx.get(), cur_res, down); + cur = ggml_add(ctx.get(), h, down); } + + // final norm + semantic head ggml_tensor * final_w = ggml_new_tensor_1d(ctx.get(), GGML_TYPE_F32, dim); - ggml_tensor * final_norm = ggml_rms_norm(ctx.get(), cur, eps); + bind_const(final_w, weights.slow_norm.values, state.ones_dim, 1.0F); + ggml_tensor * final_norm = ggml_rms_norm(ctx.get(), cur, norm_eps); final_norm = ggml_mul(ctx.get(), final_norm, final_w); - ggml_tensor * last_hidden = ggml_view_2d(ctx.get(), final_norm, dim, 1, final_norm->nb[1], (seq_len - 1) * final_norm->nb[1]); - ggml_tensor * logits = ggml_mul_mat(ctx.get(), weights.falcon_lm_head.tensor, last_hidden); - if (std::abs(lm_mult - 1.0f) > 1e-6) logits = ggml_scale(ctx.get(), logits, lm_mult); + ggml_tensor * logits = ggml_mul_mat(ctx.get(), weights.falcon_lm_head.tensor, final_norm); // [vocab] + // NOTE: lm_head_multiplier applies only to FalconH1ForCausalLM's full-vocab head. + // ArkttsModel uses the compact semantic_output head and does NOT scale logits. ggml_tensor * logits_out = ggml_dup(ctx.get(), logits); - ggml_tensor * hidden_out = ggml_dup(ctx.get(), last_hidden); + ggml_tensor * hidden_out = ggml_dup(ctx.get(), final_norm); ggml_set_name(logits_out, "logits_out"); ggml_set_name(hidden_out, "hidden_out"); + ggml_cgraph * gf = ggml_new_graph_custom(ctx.get(), 8192, false); + // KV slot writes first: they alias the cache views the attention reads, so + // they must precede those nodes in graph order. + for (ggml_tensor * wb : kv_writebacks) { + ggml_build_forward_expand(gf, wb); + } ggml_build_forward_expand(gf, logits_out); ggml_build_forward_expand(gf, hidden_out); + for (ggml_tensor * wb : writebacks) { + ggml_build_forward_expand(gf, wb); + } + auto t_graph = Clock::now(); ggml_gallocr_t gallocr = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend)); - if (!gallocr || !ggml_gallocr_reserve(gallocr, gf) || !ggml_gallocr_alloc_graph(gallocr, gf)) throw std::runtime_error("falcon_forward: gallocr failed"); - for (size_t i = 0; i < ln_ws.size(); ++i) { - const auto & vals = weights.falcon_layers[i].input_layernorm.values; - if (!vals.empty()) ggml_backend_tensor_set(ln_ws[i], vals.data(), 0, vals.size() * sizeof(float)); - else { std::vector ones(static_cast(dim), 1.0f); ggml_backend_tensor_set(ln_ws[i], ones.data(), 0, ones.size() * sizeof(float)); } - } - for (size_t i = 0; i < bias_ws.size(); ++i) { - const auto & vals = weights.falcon_layers[i].ssm_conv1d_b.values; - if (!vals.empty()) ggml_backend_tensor_set(bias_ws[i], vals.data(), 0, vals.size() * sizeof(float)); - else { std::vector zeros(896, 0.0f); ggml_backend_tensor_set(bias_ws[i], zeros.data(), 0, zeros.size() * sizeof(float)); } - } - for (size_t i = 0; i < pre_ws.size(); ++i) { - const auto & vals = weights.falcon_layers[i].pre_ff_layernorm.values; - if (!vals.empty()) ggml_backend_tensor_set(pre_ws[i], vals.data(), 0, vals.size() * sizeof(float)); - else { std::vector ones(static_cast(dim), 1.0f); ggml_backend_tensor_set(pre_ws[i], ones.data(), 0, ones.size() * sizeof(float)); } - } - if (!weights.slow_norm.values.empty()) ggml_backend_tensor_set(final_w, weights.slow_norm.values.data(), 0, weights.slow_norm.values.size() * sizeof(float)); - else { std::vector ones(static_cast(dim), 1.0f); ggml_backend_tensor_set(final_w, ones.data(), 0, ones.size() * sizeof(float)); } - std::vector cur_data(static_cast(dim * seq_len)); - for (int64_t s = 0; s < seq_len; ++s) for (int64_t d = 0; d < dim; ++d) cur_data[static_cast(d + s * dim)] = embeddings[static_cast(s * dim + d)]; - ggml_backend_tensor_set(cur, cur_data.data(), 0, cur_data.size() * sizeof(float)); + if (!gallocr || !ggml_gallocr_reserve(gallocr, gf) || !ggml_gallocr_alloc_graph(gallocr, gf)) { + throw std::runtime_error("falcon_forward_step: gallocr failed"); + } + auto t_alloc = Clock::now(); + + // ---- feed host constants ---- + if (zero_copy) { + // Everything except the rope position is bound to host memory already; + // the graph reads the position straight from the state struct. + state.pos_value = static_cast(position); + } else { + ggml_backend_tensor_set(cur, embedding.data(), 0, embedding.size() * sizeof(float)); + { + const int32_t ids0 = 0; + const int32_t posv = static_cast(position); + for (int64_t li = 0; li < n_layer; ++li) { + ggml_backend_tensor_set(ids_ts[static_cast(li)], &ids0, 0, sizeof(int32_t)); + ggml_backend_tensor_set(pos_ts[static_cast(li)], &posv, 0, sizeof(int32_t)); + } + } + for (int64_t li = 0; li < n_layer; ++li) { + const auto & layer = weights.falcon_layers[static_cast(li)]; + if (!layer.input_layernorm.values.empty()) { + ggml_backend_tensor_set(ln_w_ts[static_cast(li)], layer.input_layernorm.values.data(), 0, + layer.input_layernorm.values.size() * sizeof(float)); + } else { + std::vector ones(static_cast(dim), 1.0F); + ggml_backend_tensor_set(ln_w_ts[static_cast(li)], ones.data(), 0, ones.size() * sizeof(float)); + } + if (!layer.pre_ff_layernorm.values.empty()) { + ggml_backend_tensor_set(pre_w_ts[static_cast(li)], layer.pre_ff_layernorm.values.data(), 0, + layer.pre_ff_layernorm.values.size() * sizeof(float)); + } else { + std::vector ones(static_cast(dim), 1.0F); + ggml_backend_tensor_set(pre_w_ts[static_cast(li)], ones.data(), 0, ones.size() * sizeof(float)); + } + // conv state [d_conv-1, conv_dim] (col-major: element (c,r) at r*conv_dim + c) + const auto & cstate = state.conv_states[static_cast(li)]; + ggml_backend_tensor_set(conv_st_ts[static_cast(li)], cstate.data(), 0, cstate.size() * sizeof(float)); + // ssm state [d_state, mamba_head_dim, n_mamba_heads] + const auto & sstate = state.ssm_states[static_cast(li)]; + ggml_backend_tensor_set(ssm_st_ts[static_cast(li)], sstate.data(), 0, sstate.size() * sizeof(float)); + // A = -exp(A_log) + { + std::vector a_log(static_cast(n_mamba_heads)); + ggml_backend_tensor_get(layer.ssm_A.tensor, a_log.data(), 0, a_log.size() * sizeof(float)); + std::vector av(static_cast(n_mamba_heads)); + for (int64_t h = 0; h < n_mamba_heads; ++h) { + av[static_cast(h)] = -std::exp(a_log[static_cast(h)]); + } + ggml_backend_tensor_set(A_ts[static_cast(li)], av.data(), 0, av.size() * sizeof(float)); + } + // D expanded + { + std::vector d_raw(static_cast(n_mamba_heads)); + ggml_backend_tensor_get(layer.ssm_D.tensor, d_raw.data(), 0, d_raw.size() * sizeof(float)); + std::vector dv(static_cast(d_inner)); + for (int64_t h = 0; h < n_mamba_heads; ++h) { + for (int64_t d = 0; d < mamba_head_dim; ++d) { + dv[static_cast(d + h * mamba_head_dim)] = d_raw[static_cast(h)]; + } + } + ggml_backend_tensor_set(D_ts[static_cast(li)], dv.data(), 0, dv.size() * sizeof(float)); + } + // conv bias + if (!layer.ssm_conv1d_b.values.empty()) { + ggml_backend_tensor_set(conv_b_ts[static_cast(li)], layer.ssm_conv1d_b.values.data(), 0, + layer.ssm_conv1d_b.values.size() * sizeof(float)); + } else { + std::vector zeros(static_cast(conv_dim), 0.0F); + ggml_backend_tensor_set(conv_b_ts[static_cast(li)], zeros.data(), 0, zeros.size() * sizeof(float)); + } + // conv1d weight: host [d_conv, conv_dim] kernel (GGUF layout, no flip) + { + const auto & cw = layer.conv1d_kernel; + ggml_backend_tensor_set(conv_w2_ts[static_cast(li)], cw.values.data(), 0, cw.values.size() * sizeof(float)); + } + // KV cache + const auto & kc = state.k_cache[static_cast(li)]; + const auto & vc = state.v_cache[static_cast(li)]; + if (!kc.empty()) ggml_backend_tensor_set(k_cache_ts[static_cast(li)], kc.data(), 0, kc.size() * sizeof(float)); + if (!vc.empty()) ggml_backend_tensor_set(v_cache_ts[static_cast(li)], vc.data(), 0, vc.size() * sizeof(float)); + } + if (!weights.slow_norm.values.empty()) { + ggml_backend_tensor_set(final_w, weights.slow_norm.values.data(), 0, weights.slow_norm.values.size() * sizeof(float)); + } else { + std::vector ones(static_cast(dim), 1.0F); + ggml_backend_tensor_set(final_w, ones.data(), 0, ones.size() * sizeof(float)); + } + } + core::set_backend_threads(backend, threads); - ggml_status status = core::compute_backend_graph(backend, gf, nullptr, "falcon_forward"); + auto t_upload = Clock::now(); + ggml_status status = core::compute_backend_graph(backend, gf, nullptr, "falcon_forward_step"); ggml_backend_synchronize(backend); - if (status != GGML_STATUS_SUCCESS) throw std::runtime_error("falcon_forward compute failed"); + auto t_compute = Clock::now(); + if (status != GGML_STATUS_SUCCESS) { + ggml_gallocr_free(gallocr); + throw std::runtime_error("falcon_forward_step compute failed"); + } + + // ---- read outputs + update states ---- SlowForwardOutput out; - size_t vocab = static_cast(logits_out->ne[0]); - if (vocab == 0) vocab = 4097; - out.logits.resize(vocab); + out.logits.resize(static_cast(vocab)); out.hidden.resize(static_cast(dim)); - ggml_backend_tensor_get(logits_out, out.logits.data(), 0, vocab * sizeof(float)); + ggml_backend_tensor_get(logits_out, out.logits.data(), 0, static_cast(vocab) * sizeof(float)); ggml_backend_tensor_get(hidden_out, out.hidden.data(), 0, static_cast(dim) * sizeof(float)); + + // conv/ssm states: updated in-graph on host backends; on GPU backends the + // host reads the new state tails back and shifts them into the vectors. + if (!zero_copy) { + // conv state: last d_conv-1 kernel rows of sx (per layer). sx is col-major + // [d_conv, conv_dim], element (k, c) at k + d_conv*c. + { + std::vector sx_vals(static_cast(d_conv * conv_dim)); + for (int64_t li = 0; li < n_layer; ++li) { + ggml_backend_tensor_get(sx_ts[static_cast(li)], sx_vals.data(), 0, sx_vals.size() * sizeof(float)); + auto & cstate = state.conv_states[static_cast(li)]; + for (int64_t r = 0; r < d_conv - 1; ++r) { + for (int64_t c = 0; c < conv_dim; ++c) { + cstate[r * conv_dim + c] = sx_vals[(r + 1) + d_conv * c]; + } + } + } + } + + // ssm state: tail of scan output (d_state*d_inner per layer) — per-layer tensors! + { + const size_t y_sz = static_cast(d_inner); + const size_t s_sz = static_cast(d_state * d_inner); + std::vector scan_vals(y_sz + s_sz); + for (int64_t li = 0; li < n_layer; ++li) { + ggml_backend_tensor_get(scan_ts[static_cast(li)], scan_vals.data(), 0, scan_vals.size() * sizeof(float)); + auto & sstate = state.ssm_states[static_cast(li)]; + for (size_t i = 0; i < s_sz; ++i) { + sstate[i] = scan_vals[y_sz + i]; + } + } + } + } + + // KV cache append (fallback path only; the zero-copy path wrote the fresh + // k/v into the padded caches in-graph). ggml col-major layout + // [head_dim, seq, n_kv, 1]: element (d, t, h) at + // d + head_dim*(t + new_seq_len*h). The freshly projected/roped k/v read + // back as [d + head_dim*h] (128 values). + if (!zero_copy) { + std::vector kv(static_cast(n_kv * head_dim)); + for (int64_t li = 0; li < n_layer; ++li) { + ggml_backend_tensor_get(k_cur_ts[static_cast(li)], kv.data(), 0, kv.size() * sizeof(float)); + append_falcon_kv_token(state.k_cache[static_cast(li)], seq, n_kv, head_dim, kv.data()); + ggml_backend_tensor_get(v_cur_ts[static_cast(li)], kv.data(), 0, kv.size() * sizeof(float)); + append_falcon_kv_token(state.v_cache[static_cast(li)], seq, n_kv, head_dim, kv.data()); + } + } + state.seq_len = seq + 1; + + if (profile != nullptr) { + profile->falcon_step_init_ms += engine::debug::elapsed_ms(t_init, t_build); + profile->falcon_step_build_ms += engine::debug::elapsed_ms(t_build, t_graph); + profile->falcon_step_gallocr_ms += engine::debug::elapsed_ms(t_graph, t_alloc); + profile->falcon_step_upload_ms += engine::debug::elapsed_ms(t_alloc, t_upload); + profile->falcon_step_compute_ms += engine::debug::elapsed_ms(t_upload, t_compute); + profile->falcon_step_download_ms += engine::debug::elapsed_ms(t_compute, Clock::now()); + profile->falcon_step_runs += 1; + } + ggml_gallocr_free(gallocr); core::release_backend_graph_resources(backend, gf); return out; } +// Single-token Falcon embedding: text_embedding(semantic) * embedding_multiplier +// + sum(codebook_embeddings) for semantic tokens. `matrix` is [codebook_rows][steps]. +std::vector build_falcon_embedding_step( + const Audio8TtsConfig & config, + const ArkttsARWeights & weights, + const int32_t * matrix, + int64_t steps, + int64_t step) { + const int64_t hidden = config.text.dim; + const int64_t codebook_rows = config.fast.num_codebooks + 1; + const int32_t token = matrix[step]; + std::vector out(static_cast(hidden), 0.0F); + auto row = lookup_row(weights.text_embedding_host, token, hidden); + if (token >= config.semantic_start_token_id && token <= config.semantic_end_token_id) { + for (int64_t cb = 0; cb < config.fast.num_codebooks; ++cb) { + const int32_t code = matrix[(cb + 1) * steps + step]; + add_row(weights.codebook_embedding_host, cb * config.fast.vocab_size + code, hidden, row); + } + } + // HF _embed + _slow_backbone: (text_emb + codebook_sum) * embedding_multiplier. + for (auto & v : row) v *= config.text.embedding_multiplier; + std::copy(row.begin(), row.end(), out.begin()); + return out; +} + + } // namespace +// Copies a backend-resident weight tensor byte-for-byte (same ggml type and +// dimensions) so it can live on a second backend: the fast AR graph runs on a +// dedicated CPU backend while the slow path and codec stay on the GPU (the +// per-step fast AR submit+sync latency dominates on GPU backends, while CPU +// computes the same graph several times faster). q8_0/f32/f16 all copy +// losslessly — the point is backend placement, not conversion. +core::TensorValue schedule_tensor_copy( + ggml_context * dst_ctx, + const core::TensorValue & src) { + ggml_tensor * dst = ggml_new_tensor( + dst_ctx, src.tensor->type, ggml_n_dims(src.tensor), src.tensor->ne); + return core::wrap_tensor(dst, src.shape, src.type); +} + +void copy_tensor_bytes(const core::TensorValue & src, const core::TensorValue & dst) { + const size_t bytes = static_cast(ggml_nbytes(src.tensor)); + std::vector host(bytes); + ggml_backend_tensor_get(src.tensor, host.data(), 0, bytes); + ggml_backend_tensor_set(dst.tensor, host.data(), 0, bytes); +} + class ArkttsARWeightsRuntime { public: ArkttsARWeightsRuntime( @@ -993,15 +2024,35 @@ class ArkttsARWeightsRuntime { backend_config.threads = threads_; backend_ = core::init_backend(backend_config); backend_type_ = core::backend_type(backend_); - weights_ = std::make_shared( - load_ar_weights(*assets_, backend_, backend_type_, weight_context_bytes, weight_storage_type)); + ArkttsARWeights loaded = + load_ar_weights(*assets_, backend_, backend_type_, weight_context_bytes, weight_storage_type); + if (backend_type_ != core::BackendType::Cpu) { + // Fast AR is submit+sync latency bound on GPU backends (one graph + // submission per generated codebook token); the same graph computes + // several times faster on CPU. Give it a dedicated CPU backend and + // move the fast-layer weights over, leaving slow path + codec on + // the GPU backend. + core::BackendConfig fast_backend_config; + fast_backend_config.type = core::BackendType::Cpu; + fast_backend_config.threads = threads_; + fast_backend_ = core::init_backend(fast_backend_config); + fast_backend_type_ = core::backend_type(fast_backend_); + retarget_fast_weights(loaded); + if (!loaded.falcon_layers.empty()) { + // The Falcon-H1 per-token step graph is ~600 tiny nodes; on Metal it + // is dispatch-latency bound (measured 5.9 ms/step vs 2.0 ms on CPU, + // same weights). Run it on the same dedicated CPU backend. + retarget_falcon_weights(loaded); + } + } + weights_ = std::make_shared(std::move(loaded)); slow_step_constants_ = std::make_unique( backend_, threads_, "audio8_tts.ar.step.constants", 256ull * 1024ull * 1024ull); fast_constants_ = std::make_unique( - backend_, + fast_backend(), threads_, "audio8_tts.ar.fast.constants", 256ull * 1024ull * 1024ull); @@ -1011,6 +2062,17 @@ class ArkttsARWeightsRuntime { fast_constants_.reset(); slow_step_constants_.reset(); weights_.reset(); + if (falcon_weight_buffer_ != nullptr) { + ggml_backend_buffer_free(falcon_weight_buffer_); + } + falcon_weight_ctx_.reset(); + if (fast_weight_buffer_ != nullptr) { + ggml_backend_buffer_free(fast_weight_buffer_); + } + fast_weight_ctx_.reset(); + if (fast_backend_ != nullptr) { + ggml_backend_free(fast_backend_); + } if (backend_ != nullptr) { ggml_backend_free(backend_); } @@ -1043,6 +2105,23 @@ class ArkttsARWeightsRuntime { return backend_type_; } + // Backend hosting the fast AR graph: a dedicated CPU backend when the main + // backend is a GPU, otherwise the main backend itself. + ggml_backend_t fast_backend() const noexcept { + return fast_backend_ != nullptr ? fast_backend_ : backend_; + } + + // Falcon-H1 per-token steps run on the dedicated CPU backend when the main + // backend is a GPU one (dispatch-latency bound there); identical tensors + // otherwise, so this is always the right backend for falcon_forward_step. + ggml_backend_t falcon_step_backend() const noexcept { + return fast_backend_ != nullptr ? fast_backend_ : backend_; + } + + core::BackendType fast_backend_type() const noexcept { + return fast_backend_ != nullptr ? fast_backend_type_ : backend_type_; + } + core::ConstantTensorCache & slow_step_constants() const noexcept { return *slow_step_constants_; } @@ -1052,12 +2131,143 @@ class ArkttsARWeightsRuntime { } private: + // Re-binds the fast AR layer weights (and fast_output) onto the dedicated + // CPU fast backend. The projections are byte copies of the tensors the + // weight store uploaded to the main backend; norms stay host TensorData + // and upload through the (CPU-backed) fast constants cache at graph build. + void retarget_fast_weights(ArkttsARWeights & weights) { + ggml_init_params params{8ull * 1024ull * 1024ull, nullptr, true}; + fast_weight_ctx_.reset(ggml_init(params)); + if (fast_weight_ctx_ == nullptr) { + throw std::runtime_error("failed to initialize Audio8 TTS fast AR CPU weight context"); + } + struct ScheduledCopy { + const core::TensorValue * source; + core::TensorValue target; + }; + std::vector copies; + copies.reserve(weights.fast_layers.size() * 5 + 1); + auto schedule = [&](const core::TensorValue & value) { + if (!value.valid()) { + throw std::runtime_error("Audio8 TTS fast AR weight tensor is missing"); + } + copies.push_back({&value, schedule_tensor_copy(fast_weight_ctx_.get(), value)}); + }; + for (const auto & layer : weights.fast_layers) { + schedule(layer.qkv_proj); + if (layer.qkv_bias.has_value()) { + schedule(*layer.qkv_bias); + } + schedule(layer.o_proj); + schedule(layer.gate_up_proj); + schedule(layer.down_proj); + } + schedule(weights.fast_output); + fast_weight_buffer_ = ggml_backend_alloc_ctx_tensors(fast_weight_ctx_.get(), fast_backend_); + if (fast_weight_buffer_ == nullptr) { + throw std::runtime_error("failed to allocate Audio8 TTS fast AR CPU weights"); + } + for (auto & copy : copies) { + copy_tensor_bytes(*copy.source, copy.target); + } + size_t index = 0; + auto commit = [&](core::TensorValue & value) { + if (!value.valid()) { + return; + } + value = std::move(copies[index].target); + ++index; + }; + for (auto & layer : weights.fast_layers) { + commit(layer.qkv_proj); + if (layer.qkv_bias.has_value()) { + commit(*layer.qkv_bias); + } + commit(layer.o_proj); + commit(layer.gate_up_proj); + commit(layer.down_proj); + } + commit(weights.fast_output); + } + + // Re-binds the Falcon-H1 layer weights (and the semantic head) onto the + // dedicated CPU backend, mirroring retarget_fast_weights. ssm_A / ssm_D are + // included so the per-step A=-exp(A_log) / D-expansion reads become plain + // CPU memcpys instead of GPU->host syncs. + void retarget_falcon_weights(ArkttsARWeights & weights) { + ggml_init_params params{8ull * 1024ull * 1024ull, nullptr, true}; + falcon_weight_ctx_.reset(ggml_init(params)); + if (falcon_weight_ctx_ == nullptr) { + throw std::runtime_error("failed to initialize Audio8 TTS Falcon step CPU weight context"); + } + struct ScheduledCopy { + const core::TensorValue * source; + core::TensorValue target; + }; + std::vector copies; + copies.reserve(weights.falcon_layers.size() * 12 + 1); + auto schedule = [&](const core::TensorValue & value) { + if (!value.valid()) { + throw std::runtime_error("Audio8 TTS Falcon step weight tensor is missing"); + } + copies.push_back({&value, schedule_tensor_copy(falcon_weight_ctx_.get(), value)}); + }; + for (const auto & layer : weights.falcon_layers) { + schedule(layer.ssm_in); + schedule(layer.ssm_dt_b); + schedule(layer.ssm_A); + schedule(layer.ssm_D); + schedule(layer.ssm_out); + schedule(layer.attn_q_proj); + schedule(layer.attn_k_proj); + schedule(layer.attn_v_proj); + schedule(layer.attn_o_proj); + schedule(layer.ffn_gate); + schedule(layer.ffn_up); + schedule(layer.ffn_down); + } + schedule(weights.falcon_lm_head); + falcon_weight_buffer_ = ggml_backend_alloc_ctx_tensors(falcon_weight_ctx_.get(), fast_backend_); + if (falcon_weight_buffer_ == nullptr) { + throw std::runtime_error("failed to allocate Audio8 TTS Falcon step CPU weights"); + } + for (auto & copy : copies) { + copy_tensor_bytes(*copy.source, copy.target); + } + size_t index = 0; + auto commit = [&](core::TensorValue & value) { + value = std::move(copies[index].target); + ++index; + }; + for (auto & layer : weights.falcon_layers) { + commit(layer.ssm_in); + commit(layer.ssm_dt_b); + commit(layer.ssm_A); + commit(layer.ssm_D); + commit(layer.ssm_out); + commit(layer.attn_q_proj); + commit(layer.attn_k_proj); + commit(layer.attn_v_proj); + commit(layer.attn_o_proj); + commit(layer.ffn_gate); + commit(layer.ffn_up); + commit(layer.ffn_down); + } + commit(weights.falcon_lm_head); + } + std::shared_ptr assets_; std::shared_ptr weights_; int threads_ = 1; size_t graph_arena_bytes_ = 0; ggml_backend_t backend_ = nullptr; core::BackendType backend_type_ = core::BackendType::Cpu; + ggml_backend_t fast_backend_ = nullptr; + core::BackendType fast_backend_type_ = core::BackendType::Cpu; + std::unique_ptr fast_weight_ctx_; + ggml_backend_buffer_t fast_weight_buffer_ = nullptr; + std::unique_ptr falcon_weight_ctx_; + ggml_backend_buffer_t falcon_weight_buffer_ = nullptr; std::unique_ptr slow_step_constants_; std::unique_ptr fast_constants_; }; @@ -1099,18 +2309,13 @@ class Audio8TtsARRuntime::Impl { const bool is_falcon = assets.config.text.slow_backbone == "falcon_h1" || assets.model_weights->has_tensor("slow.embed_tokens.weight"); if (is_falcon) { - // Falcon-H1 0.1B — native ggml path (see docs/FALCON_H1_0.1B_PORT_PLAN.md). - // Current limitation (drawback stub): falcon_forward_stateless is a - // simplified forward that implements RMSNorm + Mamba in_proj split - // (gate/xBC) + conv bias SiLU + gated out_proj + FFN, but stubs the - // SSM core (no ggml_ssm_conv / B/C / dt / A / D / ggml_ssm_scan / - // recurrent conv/ssm state, no hybrid attention). It recomputes the - // full sequence each step O(N^2) and only applies ssm_out/lm_head - // multipliers. This produces prompt-invariant logits and fails STT - // without the full Mamba2 port (see mamba-base.cpp:151, - // falcon-h1.cpp:132). The full port is tracked in the plan file and - // reuses vendored external/ggml ssm backends (cpu/cuda/metal/vulkan) - // — no Python dependency, no /tmp or system() calls. + // Falcon-H1 0.1B — native ggml path. Stateful Mamba2 + hybrid GQA + // attention single-token forward (falcon_forward_step), mirroring + // transformers.models.falcon_h1 FalconH1DecoderLayer and llama.cpp + // mamba-base.cpp build_mamba2_layer. Prefill runs each prompt token + // through the step graph to populate conv/ssm states and the KV + // cache; generation continues token by token (O(N) per step instead + // of the former O(N^2) stateless recompute). if (prompt.codebook_rows != assets.config.fast.num_codebooks + 1 || static_cast(prompt.matrix.size()) != prompt.codebook_rows * prompt.steps) { throw std::runtime_error("Audio8 TTS AR prompt shape mismatch"); @@ -1138,8 +2343,13 @@ class Audio8TtsARRuntime::Impl { } return full; }; - auto pre_emb = build_falcon_embeddings(assets.config, weights, full_matrix.data(), cur_steps); - auto pre_out = falcon_forward_stateless(runtime_->backend(), runtime_->threads(), runtime_->graph_arena_bytes(), assets.config, weights, pre_emb, cur_steps); + FalconH1StepState fstate = init_falcon_step_state(assets.config); + SlowForwardOutput pre_out; + for (int64_t p = 0; p < cur_steps; ++p) { + auto p_emb = build_falcon_embedding_step(assets.config, weights, full_matrix.data(), cur_steps, p); + pre_out = falcon_forward_step(runtime_->falcon_step_backend(), runtime_->threads(), runtime_->graph_arena_bytes(), + assets.config, weights, p_emb, fstate, p, &profile); + } auto pre_logits_full = expand_compact(pre_out.logits); auto frame = sample_frame(pre_logits_full, pre_out.hidden, options, sample, false, profile); if (frame.front() == im_end_id()) { @@ -1161,8 +2371,10 @@ class Audio8TtsARRuntime::Impl { } bool ended_by_im_end = false; for (int64_t step = 1; step < max_new_tokens; ++step) { - auto emb = build_falcon_embeddings(assets.config, weights, full_matrix.data(), cur_steps); - auto out = falcon_forward_stateless(runtime_->backend(), runtime_->threads(), runtime_->graph_arena_bytes(), assets.config, weights, emb, cur_steps); + const int64_t pos = cur_steps - 1; + auto emb = build_falcon_embedding_step(assets.config, weights, full_matrix.data(), cur_steps, pos); + auto out = falcon_forward_step(runtime_->falcon_step_backend(), runtime_->threads(), runtime_->graph_arena_bytes(), + assets.config, weights, emb, fstate, pos, &profile); auto logits_full = expand_compact(out.logits); auto next_frame = sample_frame(logits_full, out.hidden, options, sample, true, profile); if (next_frame.front() == im_end_id()) { ended_by_im_end = true; break; } @@ -1607,7 +2819,7 @@ class Audio8TtsARRuntime::Impl { cache_keys.reserve(weights.fast_layers.size()); cache_values.reserve(weights.fast_layers.size()); const ggml_type cache_type = - runtime_->backend_type() == core::BackendType::Vulkan ? GGML_TYPE_F32 : GGML_TYPE_BF16; + runtime_->fast_backend_type() == core::BackendType::Vulkan ? GGML_TYPE_F32 : GGML_TYPE_BF16; for (size_t layer = 0; layer < weights.fast_layers.size(); ++layer) { cache_keys.push_back(core::wrap_tensor( ggml_new_tensor_4d( @@ -1630,7 +2842,7 @@ class Audio8TtsARRuntime::Impl { core::TensorShape::from_dims({1, config.num_codebooks, config.n_local_heads, config.head_dim}), cache_type)); } - state_buffer_ = ggml_backend_alloc_ctx_tensors(state_ctx_.get(), runtime_->backend()); + state_buffer_ = ggml_backend_alloc_ctx_tensors(state_ctx_.get(), runtime_->fast_backend()); if (state_buffer_ == nullptr) { throw std::runtime_error("failed to allocate Audio8 TTS fast AR state tensors"); } @@ -1643,7 +2855,7 @@ class Audio8TtsARRuntime::Impl { ggml_backend_tensor_set(cache.tensor, zeros.data(), 0, zeros.size()); } - core::ModuleBuildContext ctx{graph_ctx_.get(), "audio8_tts.ar.fast", runtime_->backend_type()}; + core::ModuleBuildContext ctx{graph_ctx_.get(), "audio8_tts.ar.fast", runtime_->fast_backend_type()}; auto input = core::make_tensor(ctx, GGML_TYPE_F32, core::TensorShape::from_dims({1, 1, config.dim})); input = core::wrap_tensor(ggml_cpy(ctx.ggml, input_, input.tensor), input.shape, input.type); auto position_value = core::wrap_tensor(position_, core::TensorShape::from_dims({1}), GGML_TYPE_I32); @@ -1664,7 +2876,7 @@ class Audio8TtsARRuntime::Impl { input, position_value, decoder_weights, - make_fast_decoder_config(config, runtime_->backend_type()), + make_fast_decoder_config(config, runtime_->fast_backend_type()), config.num_codebooks, mask_value, position_value, @@ -1676,7 +2888,7 @@ class Audio8TtsARRuntime::Impl { ggml_build_forward_expand(graph_, logits_); constants.finish_graph(); constants.ensure_uploaded(); - gallocr_ = ggml_gallocr_new(ggml_backend_get_default_buffer_type(runtime_->backend())); + gallocr_ = ggml_gallocr_new(ggml_backend_get_default_buffer_type(runtime_->fast_backend())); if (gallocr_ == nullptr || !ggml_gallocr_reserve(gallocr_, graph_) || !ggml_gallocr_alloc_graph(gallocr_, graph_)) { @@ -1686,7 +2898,7 @@ class Audio8TtsARRuntime::Impl { } ~FastGraph() { - core::release_backend_graph_resources(runtime_->backend(), graph_); + core::release_backend_graph_resources(runtime_->fast_backend(), graph_); if (gallocr_ != nullptr) { ggml_gallocr_free(gallocr_); } @@ -1721,10 +2933,10 @@ class Audio8TtsARRuntime::Impl { timing_start = Clock::now(); ggml_backend_tensor_set(input_, input.data(), 0, input.size() * sizeof(float)); profile.fast_input_upload_ms += engine::debug::elapsed_ms(timing_start, Clock::now()); - core::set_backend_threads(runtime_->backend(), runtime_->threads()); + core::set_backend_threads(runtime_->fast_backend(), runtime_->threads()); timing_start = Clock::now(); - const ggml_status status = core::compute_backend_graph(runtime_->backend(), graph_, nullptr, "audio8_tts.ar.fast"); - ggml_backend_synchronize(runtime_->backend()); + const ggml_status status = core::compute_backend_graph(runtime_->fast_backend(), graph_, nullptr, "audio8_tts.ar.fast"); + ggml_backend_synchronize(runtime_->fast_backend()); profile.fast_graph_ms += engine::debug::elapsed_ms(timing_start, Clock::now()); if (status != GGML_STATUS_SUCCESS) { throw std::runtime_error("Audio8 TTS fast AR graph compute failed"); @@ -1881,6 +3093,13 @@ class Audio8TtsARRuntime::Impl { engine::debug::timing_log_scalar("audio8_tts.ar.profile.sample_main_ms", profile.sample_main_ms); engine::debug::timing_log_scalar("audio8_tts.ar.profile.sample_high_ms", profile.sample_high_ms); engine::debug::timing_log_scalar("audio8_tts.ar.profile.sample_fast_ms", profile.sample_fast_ms); + engine::debug::timing_log_scalar("audio8_tts.ar.profile.falcon_step_init_ms", profile.falcon_step_init_ms); + engine::debug::timing_log_scalar("audio8_tts.ar.profile.falcon_step_build_ms", profile.falcon_step_build_ms); + engine::debug::timing_log_scalar("audio8_tts.ar.profile.falcon_step_gallocr_ms", profile.falcon_step_gallocr_ms); + engine::debug::timing_log_scalar("audio8_tts.ar.profile.falcon_step_upload_ms", profile.falcon_step_upload_ms); + engine::debug::timing_log_scalar("audio8_tts.ar.profile.falcon_step_compute_ms", profile.falcon_step_compute_ms); + engine::debug::timing_log_scalar("audio8_tts.ar.profile.falcon_step_download_ms", profile.falcon_step_download_ms); + engine::debug::timing_log_scalar("audio8_tts.ar.profile.falcon_step_runs", profile.falcon_step_runs); engine::debug::trace_log_scalar("audio8_tts.ar.profile.prefill_runs", profile.prefill_runs); engine::debug::trace_log_scalar("audio8_tts.ar.profile.step_runs", profile.step_runs); engine::debug::trace_log_scalar("audio8_tts.ar.profile.fast_runs", profile.fast_runs); diff --git a/src/community_models/audio8_tts/codec.cpp b/src/community_models/audio8_tts/codec.cpp index fd43ed19a..03c68d396 100644 --- a/src/community_models/audio8_tts/codec.cpp +++ b/src/community_models/audio8_tts/codec.cpp @@ -1,9 +1,12 @@ +#include + #include "engine/community_models/audio8_tts/codec.h" #include "engine/framework/audio/conversion.h" #include "engine/framework/audio/resampling.h" #include "engine/framework/core/backend.h" #include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" #include "engine/framework/debug/trace.h" #include "engine/framework/core/execution_context.h" #include "engine/framework/modules/activation_modules.h" @@ -463,6 +466,131 @@ core::TensorValue build_window_transformer( return modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, x); } +// ---- Channel-fast decoder region (Metal only) -------------------------------------- +// The decoder's residual units are back-to-back stride-1 convolutions separated only by +// snake activations and residual adds -- all elementwise, hence layout-agnostic. Running +// a whole block (snake -> convT upsample -> 3 residual units) in channel-fast +// [channels, frames] layout avoids the two transposes per conv that the module-level +// fast path pays at every boundary, and the convT's own internal transpose. Numerics +// are unchanged: identical GEMMs, identical elementwise ops, same accumulation order. + +bool codec_channel_fast_decoder_enabled() { + static const bool enabled = [] { + const char * value = std::getenv("AUDIO8_TTS_CODEC_CHANNEL_FAST"); + return value == nullptr || value[0] != '0'; + }(); + return enabled; +} + +ggml_tensor * channel_fast_in(core::ModuleBuildContext & ctx, const core::TensorValue & x) { + const auto contiguous = core::ensure_backend_addressable_layout(ctx, x); + return ggml_cont(ctx.ggml, ggml_transpose(ctx.ggml, contiguous.tensor)); +} + +core::TensorValue channel_fast_out(core::ModuleBuildContext & ctx, ggml_tensor * x_cf, int64_t channels) { + return core::wrap_tensor( + ggml_cont(ctx.ggml, ggml_transpose(ctx.ggml, x_cf)), + core::TensorShape::from_dims({1, channels, x_cf->ne[1]}), + GGML_TYPE_F32); +} + +// snake(x)[c, t] = x + sin^2(alpha_c * x) / alpha_c, alpha broadcast along frames. +ggml_tensor * snake1d_channel_fast( + core::ModuleBuildContext & ctx, + ggml_tensor * x_cf, + const core::TensorValue & alpha) { + auto * alpha_cf = ggml_reshape_2d(ctx.ggml, alpha.tensor, alpha.tensor->ne[0], 1); + // Fused snake op: one elementwise pass instead of a 5-kernel chain (mul -> sin -> + // mul -> div -> add) over the largest decoder tensors. Per-element op order matches + // the chain; residual differences are metal sin ulp-level only (max int16 delta 12 + // over a full utterance vs the chain). Set AUDIO8_TTS_CODEC_SNAKE_FUSED=0 to fall + // back to the explicit chain. + static const bool fused_disabled = [] { + const char * e = std::getenv("AUDIO8_TTS_CODEC_SNAKE_FUSED"); + return e && e[0] == '0' && e[1] == '\0'; + }(); + if (!fused_disabled) { + ggml_tensor * alpha_f32 = alpha_cf; + if (alpha_f32->type != GGML_TYPE_F32) { + alpha_f32 = ggml_cast(ctx.ggml, alpha_f32, GGML_TYPE_F32); + } + return ggml_snake_1d(ctx.ggml, x_cf, alpha_f32); + } + auto * ax = ggml_mul(ctx.ggml, x_cf, alpha_cf); + auto * s = ggml_sin(ctx.ggml, ax); + auto * s2 = ggml_mul(ctx.ggml, s, s); + return ggml_add(ctx.ggml, x_cf, ggml_div(ctx.ggml, s2, alpha_cf)); +} + +// Causal left pad built from scaled-to-zero columns of the input itself: activations are +// finite so x * 0 is a bitwise zero, and snake(+-0) = +0 keeps the pad region exact. +ggml_tensor * channel_fast_causal_pad( + core::ModuleBuildContext & ctx, + ggml_tensor * x_cf, + int64_t channels, + int64_t left_pad) { + if (left_pad <= 0) { + return x_cf; + } + auto * head = ggml_view_2d(ctx.ggml, x_cf, channels, left_pad, x_cf->nb[1], 0); + auto * zeros = ggml_scale(ctx.ggml, head, 0.0f); + return ggml_concat(ctx.ggml, zeros, x_cf, 1); +} + +ggml_tensor * causal_conv1d_channel_fast( + core::ModuleBuildContext & ctx, + ggml_tensor * x_cf, + const modules::Conv1dWeights & weights, + int64_t channels, + int64_t kernel, + int dilation) { + const int64_t left_pad = (kernel - 1) * dilation; // stride == 1 + auto * padded = channel_fast_causal_pad(ctx, x_cf, channels, left_pad); + return modules::conv1d_pertap_channel_fast( + ctx, + weights, + padded, + modules::Conv1dConfig{channels, channels, kernel, 1, 0, dilation, true}); +} + +ggml_tensor * residual_unit_channel_fast( + core::ModuleBuildContext & ctx, + ggml_tensor * x_cf, + const ResidualUnitWeights & weights, + int64_t channels, + int dilation) { + const int64_t frames = x_cf->ne[1]; + auto * y = snake1d_channel_fast(ctx, x_cf, weights.snake1.alpha); + y = causal_conv1d_channel_fast(ctx, y, weights.conv1, channels, 7, dilation); + y = snake1d_channel_fast(ctx, y, weights.snake2.alpha); + y = causal_conv1d_channel_fast(ctx, y, weights.conv2, channels, 1, 1); + ggml_tensor * residual = x_cf; + if (y->ne[1] != frames) { + residual = ggml_view_2d(ctx.ggml, x_cf, channels, y->ne[1], x_cf->nb[1], 0); + } + return ggml_add(ctx.ggml, residual, y); +} + +// Transposed-conv upsample on channel-fast input; the col2im output is time-fast, so +// the causal trim and the transpose back happen here. +ggml_tensor * causal_conv_transpose1d_channel_fast( + core::ModuleBuildContext & ctx, + ggml_tensor * x_cf, + const modules::ConvTranspose1dWeights & weights, + int64_t in_channels, + int64_t out_channels, + int64_t kernel, + int stride) { + auto * out_tf = modules::conv_transpose1d_col2im_channel_fast( + ctx, + weights, + x_cf, + modules::ConvTranspose1dConfig{in_channels, out_channels, kernel, stride, 0, 1, true}); + const int64_t pad = kernel - stride; // padding_left == 0; drop the trailing frames + auto * trimmed = ggml_view_2d(ctx.ggml, out_tf, out_tf->ne[0] - pad, out_channels, out_tf->nb[1], 0); + return ggml_cont(ctx.ggml, ggml_transpose(ctx.ggml, trimmed)); +} + core::TensorValue build_residual_unit( core::ModuleBuildContext & ctx, const core::TensorValue & input, @@ -531,14 +659,29 @@ core::TensorValue build_decoder( auto x = causal_conv1d(ctx, input, weights.decoder_first, kCodecDim, 1536, 7, 1, 1, true); int64_t channels = 1536; const int strides[] = {8, 8, 4, 2}; - for (size_t index = 0; index < weights.decoder_blocks.size(); ++index) { - const auto & block = weights.decoder_blocks[index]; - x = modules::Snake1dModule({channels}).build(ctx, x, block.snake); - x = causal_conv_transpose1d(ctx, x, block.conv, channels, channels / 2, 2 * strides[index], strides[index], true); - channels /= 2; - x = build_residual_unit(ctx, x, block.residual1, channels, 1); - x = build_residual_unit(ctx, x, block.residual3, channels, 3); - x = build_residual_unit(ctx, x, block.residual9, channels, 9); + if (ctx.backend_type == core::BackendType::Metal && codec_channel_fast_decoder_enabled()) { + ggml_tensor * x_cf = channel_fast_in(ctx, x); + for (size_t index = 0; index < weights.decoder_blocks.size(); ++index) { + const auto & block = weights.decoder_blocks[index]; + x_cf = snake1d_channel_fast(ctx, x_cf, block.snake.alpha); + x_cf = causal_conv_transpose1d_channel_fast( + ctx, x_cf, block.conv, channels, channels / 2, 2 * strides[index], strides[index]); + channels /= 2; + x_cf = residual_unit_channel_fast(ctx, x_cf, block.residual1, channels, 1); + x_cf = residual_unit_channel_fast(ctx, x_cf, block.residual3, channels, 3); + x_cf = residual_unit_channel_fast(ctx, x_cf, block.residual9, channels, 9); + } + x = channel_fast_out(ctx, x_cf, channels); + } else { + for (size_t index = 0; index < weights.decoder_blocks.size(); ++index) { + const auto & block = weights.decoder_blocks[index]; + x = modules::Snake1dModule({channels}).build(ctx, x, block.snake); + x = causal_conv_transpose1d(ctx, x, block.conv, channels, channels / 2, 2 * strides[index], strides[index], true); + channels /= 2; + x = build_residual_unit(ctx, x, block.residual1, channels, 1); + x = build_residual_unit(ctx, x, block.residual3, channels, 3); + x = build_residual_unit(ctx, x, block.residual9, channels, 9); + } } x = modules::Snake1dModule({channels}).build(ctx, x, weights.decoder_final_snake); x = causal_conv1d(ctx, x, weights.decoder_final, channels, 1, 7, 1, 1, true); @@ -884,6 +1027,7 @@ struct DecodeGraph { throw std::runtime_error("failed to initialize Audio8 TTS codec decode graph context"); } core::ModuleBuildContext ctx{ctx_.get(), "audio8_tts.codec.decode", backend_type_}; + const auto build_start = std::chrono::steady_clock::now(); constants_.begin_graph(); for (int64_t codebook = 0; codebook < assets_->config.codec.total_codebooks; ++codebook) { auto ids = core::make_tensor(ctx, GGML_TYPE_I32, core::TensorShape::from_dims({1, frame_capacity_})); @@ -902,6 +1046,10 @@ struct DecodeGraph { if (gallocr_ == nullptr || !ggml_gallocr_alloc_graph(gallocr_.get(), graph_)) { throw std::runtime_error("failed to allocate Audio8 TTS codec decode graph"); } + engine::debug::timing_log_scalar( + "audio8_tts.codec.graph_build_ms", + engine::debug::elapsed_ms(build_start, std::chrono::steady_clock::now())); + engine::debug::trace_log_scalar("audio8_tts.codec.graph_nodes", static_cast(ggml_graph_n_nodes(graph_))); } ~DecodeGraph() { @@ -941,8 +1089,12 @@ struct DecodeGraph { core::write_tensor_i32(code_inputs_[static_cast(codebook)], padded); } core::set_backend_threads(backend_, threads_); + const auto compute_start = std::chrono::steady_clock::now(); const ggml_status status = engine::core::compute_backend_graph(backend_, graph_); ggml_backend_synchronize(backend_); + engine::debug::timing_log_scalar( + "audio8_tts.codec.graph_compute_ms", + engine::debug::elapsed_ms(compute_start, std::chrono::steady_clock::now())); if (status != GGML_STATUS_SUCCESS) { throw std::runtime_error("Audio8 TTS codec decode graph compute failed"); } diff --git a/src/community_models/audio8_tts/falcon_kv_cache.h b/src/community_models/audio8_tts/falcon_kv_cache.h new file mode 100644 index 000000000..a03090cd2 --- /dev/null +++ b/src/community_models/audio8_tts/falcon_kv_cache.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +namespace engine::models::audio8_tts { + +// Appends one token's K (or V) vectors to a host-side KV cache stored in +// ggml's col-major [head_dim, seq, n_kv] layout: element (d, t, h) lives at +// d + head_dim*(t + seq*h), i.e. the per-head stride is the CURRENT sequence +// length. Because that stride grows with every appended token, the cached +// tokens must be re-laid into the new stride before `fresh` (the new token's +// n_kv*head_dim values, head-major) is written at t = seq. Appending without +// the re-layout makes the new token overwrite the previous head blocks and +// silently corrupts attention context from the second token on. +inline void append_falcon_kv_token( + std::vector & cache, + int64_t seq, + int64_t n_kv, + int64_t head_dim, + const float * fresh) { + const int64_t new_seq_len = seq + 1; + std::vector old; + old.swap(cache); + cache.assign(static_cast(new_seq_len * n_kv * head_dim), 0.0F); + for (int64_t h = 0; h < n_kv; ++h) { + for (int64_t t = 0; t < seq; ++t) { + std::copy_n(old.data() + head_dim * (t + seq * h), + static_cast(head_dim), + cache.data() + head_dim * (t + new_seq_len * h)); + } + std::copy_n(fresh + head_dim * h, + static_cast(head_dim), + cache.data() + head_dim * (seq + new_seq_len * h)); + } +} + +} // namespace engine::models::audio8_tts diff --git a/src/community_models/mira_tts/assets.cpp b/src/community_models/mira_tts/assets.cpp new file mode 100644 index 000000000..8f7f69c92 --- /dev/null +++ b/src/community_models/mira_tts/assets.cpp @@ -0,0 +1,54 @@ +#include "engine/community_models/mira_tts/assets.h" + +#include "engine/framework/io/json.h" +#include "engine/framework/model_spec/package.h" + +#include + +namespace engine::community_models::mira_tts { +namespace { + +namespace json = engine::io::json; +constexpr const char * kFamily = "mira_tts"; + +MiraTTSConfig parse_config(const assets::ResourceBundle & resources) { + const auto root = resources.parse_json("config"); + if (json::require_string(root, "model_type") != "qwen2") { + throw std::runtime_error("MiraTTS config must use model_type qwen2"); + } + MiraTTSConfig out; + out.hidden_size = json::require_i64(root, "hidden_size"); + out.intermediate_size = json::require_i64(root, "intermediate_size"); + out.layers = json::require_i64(root, "num_hidden_layers"); + out.attention_heads = json::require_i64(root, "num_attention_heads"); + out.kv_heads = json::require_i64(root, "num_key_value_heads"); + out.head_dim = out.hidden_size / out.attention_heads; + out.vocab_size = json::require_i64(root, "vocab_size"); + out.max_position_embeddings = json::optional_i64( + root, "max_position_embeddings", out.max_position_embeddings); + out.rms_norm_eps = json::optional_f32(root, "rms_norm_eps", out.rms_norm_eps); + out.rope_theta = json::optional_f32(root, "rope_theta", out.rope_theta); + out.bos_token_id = static_cast(json::optional_i64( + root, "bos_token_id", out.bos_token_id)); + out.eos_token_id = static_cast(json::optional_i64( + root, "eos_token_id", out.eos_token_id)); + return out; +} + +} // namespace + +std::shared_ptr load_mira_tts_assets( + const std::filesystem::path & model_path) { + auto out = std::make_shared(); + out->resources = engine::model_spec::load_resource_bundle( + model_path, engine::model_spec::default_spec_path(kFamily)); + out->config = parse_config(out->resources); + out->language_model_weights = out->resources.open_tensor_source("language_model"); + out->speaker_encoder_weights = out->resources.open_tensor_source("speaker_encoder"); + out->processor_weights = out->resources.open_tensor_source("processor"); + out->decoder_weights = out->resources.open_tensor_source("decoder"); + out->upsampler_weights = out->resources.open_tensor_source("upsampler"); + return out; +} + +} // namespace engine::community_models::mira_tts diff --git a/src/community_models/mira_tts/decoder.cpp b/src/community_models/mira_tts/decoder.cpp new file mode 100644 index 000000000..b33ee2587 --- /dev/null +++ b/src/community_models/mira_tts/decoder.cpp @@ -0,0 +1,323 @@ +#include "engine/community_models/mira_tts/decoder.h" + +#include "engine/framework/audio/flashsr.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/structural_modules.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { +namespace { + +using Clock = std::chrono::steady_clock; + +struct ContextDeleter { + void operator()(ggml_context * context) const noexcept { + if (context != nullptr) ggml_free(context); + } +}; + +struct SnakeWeights { core::TensorValue alpha; }; +struct ConvWeights { + modules::Conv1dWeights value; + int64_t in_channels = 0; + int64_t out_channels = 0; + int64_t kernel = 0; +}; +struct UpWeights { + modules::ConvTranspose1dWeights value; + int64_t in_channels = 0; + int64_t out_channels = 0; + int64_t kernel = 0; +}; +struct ResidualWeights { + SnakeWeights snake1; + ConvWeights conv1; + SnakeWeights snake2; + ConvWeights conv2; +}; +struct BlockWeights { + SnakeWeights snake; + UpWeights up; + std::vector residuals; + int stride = 1; +}; +struct Weights { + std::shared_ptr store; + ConvWeights first; + std::vector blocks; + SnakeWeights final_snake; + ConvWeights final_conv; +}; + +ConvWeights load_conv( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + int64_t out_channels, + int64_t in_channels, + int64_t kernel, + assets::TensorStorageType storage_type) { + ConvWeights out; + out.in_channels = in_channels; + out.out_channels = out_channels; + out.kernel = kernel; + out.value.weight = store.load_tensor( + source, prefix + ".weight", storage_type, + {out_channels, in_channels, kernel}); + out.value.bias = store.load_f32_tensor( + source, prefix + ".bias", {out_channels}); + return out; +} + +UpWeights load_up( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + int64_t in_channels, + int64_t out_channels, + int64_t kernel, + assets::TensorStorageType storage_type) { + UpWeights out; + out.in_channels = in_channels; + out.out_channels = out_channels; + out.kernel = kernel; + out.value.weight = store.load_tensor( + source, prefix + ".weight", storage_type, + {in_channels, out_channels, kernel}); + out.value.bias = store.load_f32_tensor( + source, prefix + ".bias", {out_channels}); + return out; +} + +SnakeWeights load_snake( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & name, + int64_t channels) { + return {store.make_from_f32( + core::TensorShape::from_dims({channels}), + assets::TensorStorageType::F32, + source.require_f32(name, {1, channels, 1}))}; +} + +ResidualWeights load_residual( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + int64_t channels, + assets::TensorStorageType storage_type) { + ResidualWeights out; + out.snake1 = load_snake(store, source, prefix + ".block.0.alpha", channels); + out.conv1 = load_conv(store, source, prefix + ".block.1", channels, channels, 7, storage_type); + out.snake2 = load_snake(store, source, prefix + ".block.2.alpha", channels); + out.conv2 = load_conv(store, source, prefix + ".block.3", channels, channels, 1, storage_type); + return out; +} + +Weights load_weights( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t context_bytes, + assets::TensorStorageType storage_type) { + Weights out; + out.store = std::make_shared( + execution.backend(), execution.backend_type(), + "mira_tts.decoder.weights", context_bytes); + const auto & source = *assets.decoder_weights; + out.first = load_conv(*out.store, source, "model.0", 1536, 1024, 7, storage_type); + const int strides[] = {8, 5, 4, 2}; + const int kernels[] = {16, 11, 8, 4}; + int64_t channels = 1536; + for (int stage = 0; stage < 4; ++stage) { + const int64_t out_channels = channels / 2; + const std::string prefix = "model." + std::to_string(stage + 1) + ".block"; + BlockWeights block; + block.stride = strides[stage]; + block.snake = load_snake(*out.store, source, prefix + ".0.alpha", channels); + block.up = load_up( + *out.store, source, prefix + ".1", channels, out_channels, + kernels[stage], storage_type); + for (int residual = 0; residual < 3; ++residual) { + block.residuals.push_back(load_residual( + *out.store, source, + prefix + "." + std::to_string(residual + 2), + out_channels, storage_type)); + } + out.blocks.push_back(std::move(block)); + channels = out_channels; + } + out.final_snake = load_snake(*out.store, source, "model.5.alpha", 96); + out.final_conv = load_conv(*out.store, source, "model.6", 1, 96, 7, storage_type); + out.store->upload(); + return out; +} + +core::TensorValue conv( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const ConvWeights & weights, + int padding, + int dilation = 1) { + return modules::Conv1dModule({ + weights.in_channels, weights.out_channels, weights.kernel, + 1, padding, dilation, true}).build(ctx, input, weights.value); +} + +core::TensorValue snake( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const SnakeWeights & weights) { + return modules::Snake1dModule({input.shape.dims[1]}).build( + ctx, input, {weights.alpha}); +} + +core::TensorValue residual( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const ResidualWeights & weights, + int dilation) { + auto x = snake(ctx, input, weights.snake1); + x = conv(ctx, x, weights.conv1, 3 * dilation, dilation); + x = snake(ctx, x, weights.snake2); + x = conv(ctx, x, weights.conv2, 0); + return modules::AddModule{}.build(ctx, input, x); +} + +} // namespace + +struct MiraDecoder::Impl { + Impl( + const MiraTTSAssets & assets, + core::ExecutionContext & execution_in, + size_t weight_bytes, + size_t graph_bytes, + assets::TensorStorageType storage_type) + : execution(execution_in), + graph_context_bytes(graph_bytes), + weights(load_weights(assets, execution_in, weight_bytes, storage_type)), + upsampler(audio::FlashSrModel::load_from_tensor_source( + assets.upsampler_weights, execution_in.config())) {} + + runtime::AudioBuffer decode(const std::vector & latents, int64_t frames) { + if (frames <= 0 || latents.size() != static_cast(frames * 1024)) { + throw std::runtime_error("MiraTTS decoder expects [1024, frames] latents"); + } + const auto build_start = Clock::now(); + ggml_init_params params{graph_context_bytes, nullptr, true}; + std::unique_ptr context(ggml_init(params)); + if (!context) throw std::runtime_error("failed to create MiraTTS decoder graph context"); + core::ModuleBuildContext build{ + context.get(), "mira_tts.decoder", execution.backend_type()}; + auto * input = ggml_new_tensor_3d(context.get(), GGML_TYPE_F32, frames, 1024, 1); + ggml_set_input(input); + auto x = core::wrap_tensor( + input, core::TensorShape::from_dims({1, 1024, frames}), GGML_TYPE_F32); + x = conv(build, x, weights.first, 3); + for (const auto & block : weights.blocks) { + x = snake(build, x, block.snake); + auto upsampled = modules::ConvTranspose1dModule({ + block.up.in_channels, block.up.out_channels, block.up.kernel, + block.stride, 0, 1, true}).build(build, x, block.up.value); + const int padding = static_cast(std::ceil(block.stride / 2.0)); + x = modules::SliceModule({2, padding, upsampled.shape.dims[2] - 2 * padding}) + .build(build, upsampled); + x = residual(build, x, block.residuals[0], 1); + x = residual(build, x, block.residuals[1], 3); + x = residual(build, x, block.residuals[2], 9); + } + x = snake(build, x, weights.final_snake); + x = conv(build, x, weights.final_conv, 3); + x = modules::TanhModule{}.build(build, x); + x = core::ensure_backend_addressable_layout(build, x); + ggml_set_output(x.tensor); + auto * graph = ggml_new_graph_custom(context.get(), 65536, false); + ggml_build_forward_expand(graph, x.tensor); + ggml_gallocr_t allocator = ggml_gallocr_new( + ggml_backend_get_default_buffer_type(execution.backend())); + if (allocator == nullptr || + !ggml_gallocr_reserve(allocator, graph) || + !ggml_gallocr_alloc_graph(allocator, graph)) { + if (allocator != nullptr) ggml_gallocr_free(allocator); + throw std::runtime_error("failed to allocate MiraTTS decoder graph"); + } + engine::debug::timing_log_scalar( + "mira_tts.decoder.dac.build_allocate_ms", + engine::debug::elapsed_ms(build_start, Clock::now())); + const auto upload_start = Clock::now(); + ggml_backend_tensor_set( + input, latents.data(), 0, latents.size() * sizeof(float)); + engine::debug::timing_log_scalar( + "mira_tts.decoder.dac.upload_ms", + engine::debug::elapsed_ms(upload_start, Clock::now())); + core::set_backend_threads(execution.backend(), std::max(1, execution.config().threads)); + const auto compute_start = Clock::now(); + const auto status = core::compute_backend_graph(execution.backend(), graph); + ggml_backend_synchronize(execution.backend()); + engine::debug::timing_log_scalar( + "mira_tts.decoder.dac.compute_ms", + engine::debug::elapsed_ms(compute_start, Clock::now())); + if (status != GGML_STATUS_SUCCESS) { + core::release_backend_graph_resources(execution.backend(), graph); + ggml_gallocr_free(allocator); + throw std::runtime_error("MiraTTS decoder graph compute failed"); + } + const auto readback_start = Clock::now(); + std::vector decoded(static_cast(x.shape.dims[2])); + ggml_backend_tensor_get(x.tensor, decoded.data(), 0, decoded.size() * sizeof(float)); + engine::debug::timing_log_scalar( + "mira_tts.decoder.dac.readback_ms", + engine::debug::elapsed_ms(readback_start, Clock::now())); + core::release_backend_graph_resources(execution.backend(), graph); + ggml_gallocr_free(allocator); + const auto upsample_start = Clock::now(); + const auto enhanced = upsampler.super_resolve_mono_16k(decoded); + engine::debug::timing_log_scalar( + "mira_tts.decoder.flashsr_ms", + engine::debug::elapsed_ms(upsample_start, Clock::now())); + runtime::AudioBuffer audio; + audio.samples = enhanced.samples; + audio.sample_rate = enhanced.sample_rate; + audio.channels = 1; + return audio; + } + + core::ExecutionContext & execution; + size_t graph_context_bytes; + Weights weights; + audio::FlashSrModel upsampler; +}; + +MiraDecoder::MiraDecoder( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + size_t graph_context_bytes, + assets::TensorStorageType storage_type) + : impl_(std::make_unique( + assets, execution, weight_context_bytes, graph_context_bytes, storage_type)) {} + +MiraDecoder::~MiraDecoder() = default; + +runtime::AudioBuffer MiraDecoder::decode( + const std::vector & latents, + int64_t frames) { + return impl_->decode(latents, frames); +} + +} // namespace engine::community_models::mira_tts diff --git a/src/community_models/mira_tts/generator.cpp b/src/community_models/mira_tts/generator.cpp new file mode 100644 index 000000000..bf30e2ba8 --- /dev/null +++ b/src/community_models/mira_tts/generator.cpp @@ -0,0 +1,366 @@ +#include "engine/community_models/mira_tts/generator.h" + +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/modules/transformers/qwen_causal_decode_runtime.h" +#include "engine/framework/modules/weight_binding.h" +#include "engine/framework/sampling/hf_sampler.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { +namespace { + +namespace binding = engine::modules::binding; + +struct MiraQwenWeights { + std::shared_ptr store; + // This context owns only the sparse head's tensor metadata. The tied + // embedding storage outlives it, and both outlive the decoder runtime. + std::shared_ptr head_context; + core::TensorValue token_embedding; + core::TensorValue lm_head; + int64_t lm_head_row_offset = 0; + modules::QwenDecoderStackWeights stack; + modules::NormWeights final_norm; +}; + +modules::QwenDecoderLayerWeights load_layer( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const MiraTTSConfig & config, + assets::TensorStorageType storage_type, + int64_t layer) { + const std::string prefix = "model.layers." + std::to_string(layer); + modules::QwenDecoderLayerWeights out; + out.input_norm = binding::norm_weight_from_source( + store, source, prefix + ".input_layernorm", config.hidden_size); + + const int64_t q_out = config.attention_heads * config.head_dim; + const int64_t kv_out = config.kv_heads * config.head_dim; + std::vector qkv = source.require_f32( + prefix + ".self_attn.q_proj.weight", {q_out, config.hidden_size}); + const auto k = source.require_f32( + prefix + ".self_attn.k_proj.weight", {kv_out, config.hidden_size}); + const auto v = source.require_f32( + prefix + ".self_attn.v_proj.weight", {kv_out, config.hidden_size}); + qkv.insert(qkv.end(), k.begin(), k.end()); + qkv.insert(qkv.end(), v.begin(), v.end()); + out.self_attention.qkv_weight = store.make_from_f32( + core::TensorShape::from_dims({q_out + 2 * kv_out, config.hidden_size}), + storage_type, + std::move(qkv)); + std::vector qkv_bias = source.require_f32( + prefix + ".self_attn.q_proj.bias", {q_out}); + const auto k_bias = source.require_f32( + prefix + ".self_attn.k_proj.bias", {kv_out}); + const auto v_bias = source.require_f32( + prefix + ".self_attn.v_proj.bias", {kv_out}); + qkv_bias.insert(qkv_bias.end(), k_bias.begin(), k_bias.end()); + qkv_bias.insert(qkv_bias.end(), v_bias.begin(), v_bias.end()); + out.self_attention.qkv_bias = store.make_f32( + core::TensorShape::from_dims({q_out + 2 * kv_out}), + qkv_bias); + out.self_attention.out_weight = store.load_tensor( + source, + prefix + ".self_attn.o_proj.weight", + storage_type, + {config.hidden_size, q_out}); + out.post_norm = binding::norm_weight_from_source( + store, source, prefix + ".post_attention_layernorm", config.hidden_size); + + std::vector gate_up = source.require_f32( + prefix + ".mlp.gate_proj.weight", + {config.intermediate_size, config.hidden_size}); + const auto up = source.require_f32( + prefix + ".mlp.up_proj.weight", + {config.intermediate_size, config.hidden_size}); + gate_up.insert(gate_up.end(), up.begin(), up.end()); + out.mlp.gate_up_proj = modules::LinearWeights{ + store.make_from_f32( + core::TensorShape::from_dims( + {2 * config.intermediate_size, config.hidden_size}), + storage_type, + std::move(gate_up)), + std::nullopt}; + out.mlp.down_proj = binding::linear_from_source( + store, + source, + prefix + ".mlp.down_proj", + storage_type, + config.hidden_size, + config.intermediate_size, + false); + return out; +} + +modules::QwenCausalDecoderConfig decoder_config( + const MiraTTSConfig & config, + core::BackendType backend_type) { + modules::QwenCausalDecoderConfig out; + out.stack.hidden_size = config.hidden_size; + out.stack.num_attention_heads = config.attention_heads; + out.stack.num_key_value_heads = config.kv_heads; + out.stack.head_dim = config.head_dim; + out.stack.intermediate_size = config.intermediate_size; + out.stack.layers = config.layers; + out.stack.rms_norm_eps = config.rms_norm_eps; + out.stack.rope_theta = config.rope_theta; + out.stack.rope_type = GGML_ROPE_TYPE_NEOX; + out.stack.use_qk_norm = false; + out.stack.qkv_layout = modules::QwenDecoderQKVLayout::PackedQKV; + out.stack.runtime.mlp.mode = modules::QwenDecoderMLPMode::PackedGateUp; + out.stack.runtime.attention.prefill_mode = + modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.stack.runtime.attention.static_mode = + modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.stack.runtime.static_cache.update_mode = + modules::QwenDecoderStaticCacheUpdateMode::DirectSetRows; + if (backend_type == core::BackendType::Vulkan) { + // Mira's projections are sensitive to Vulkan's default reduced + // precision. Materialize grouped K/V heads for attention as well: + // the strided-view path diverges during prompt evaluation. + out.stack.projection_precision = GGML_PREC_F32; + out.stack.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::FlashGrouped; + out.stack.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::FlashGrouped; + } + out.logits_size = config.vocab_size; + out.logits_mode = modules::QwenCausalDecoderLogitsMode::LastStep; + out.use_lm_head_bias = false; + if (backend_type == core::BackendType::Metal) { + out.lm_head_input_type = GGML_TYPE_F16; + } else if (backend_type != core::BackendType::Cpu && + backend_type != core::BackendType::Vulkan) { + out.lm_head_input_type = GGML_TYPE_BF16; + } + return out; +} + +std::vector generation_token_ids(const MiraTTSConfig & config) { + std::vector out; + out.reserve(static_cast( + config.speech_token_end - config.speech_token_start + 2)); + for (int32_t token = config.speech_token_start; + token <= config.speech_token_end; + ++token) { + out.push_back(token); + } + out.push_back(config.eos_token_id); + return out; +} + +std::shared_ptr load_weights( + const MiraTTSAssets & assets, + ggml_backend_t backend, + core::BackendType backend_type, + size_t context_bytes, + assets::TensorStorageType storage_type) { + auto out = std::make_shared(); + out->store = std::make_shared( + backend, backend_type, "mira_tts.lm.weights", context_bytes); + const auto & config = assets.config; + const auto & source = *assets.language_model_weights; + out->token_embedding = out->store->load_tensor( + source, + "model.embed_tokens.weight", + storage_type, + {config.vocab_size, config.hidden_size}); + out->stack.layers.reserve(static_cast(config.layers)); + for (int64_t layer = 0; layer < config.layers; ++layer) { + out->stack.layers.push_back(load_layer( + *out->store, source, config, storage_type, layer)); + } + out->final_norm = binding::norm_weight_from_source( + *out->store, source, "model.norm", config.hidden_size); + out->store->upload(); + out->lm_head = out->token_embedding; + const char * sparse_head = std::getenv("AUDIOCPP_MIRA_TTS_SPARSE_HEAD"); + if (backend_type == core::BackendType::Cpu && + !(sparse_head != nullptr && sparse_head[0] == '0')) { + // Only MiraTTS knows its speech/EOS alphabet. Keep its weight window + // here, presenting an ordinary, correctly sized head to shared Qwen. + const int64_t offset = config.eos_token_id; + if (offset < 0 || offset >= config.vocab_size || + config.speech_token_start < offset || + config.speech_token_end < config.speech_token_start || + config.speech_token_end >= config.vocab_size) { + throw std::runtime_error("MiraTTS sparse head does not cover its generation alphabet"); + } + const int64_t rows = config.vocab_size - offset; + out->head_context = std::shared_ptr( + ggml_init({ggml_tensor_overhead(), nullptr, true}), ggml_free); + if (!out->head_context) { + throw std::runtime_error("failed to initialize MiraTTS sparse head context"); + } + auto * base = out->token_embedding.tensor; + auto * view = ggml_view_2d( + out->head_context.get(), base, base->ne[0], rows, base->nb[1], + static_cast(offset) * base->nb[1]); + if (ggml_backend_view_init(view) != GGML_STATUS_SUCCESS) { + throw std::runtime_error("failed to initialize MiraTTS sparse head view"); + } + out->lm_head = core::wrap_tensor( + view, core::TensorShape::from_dims({rows, config.hidden_size}), + out->token_embedding.type); + out->lm_head_row_offset = offset; + } + return out; +} + +modules::QwenCausalDecodeRuntimeConfig runtime_config( + const MiraTTSConfig & config, + const MiraQwenWeights & weights, + core::BackendType backend_type, + size_t prefill_bytes, + size_t decode_bytes) { + modules::QwenCausalDecodeRuntimeConfig out; + out.trace_name = "mira_tts.lm"; + out.decoder = decoder_config(config, backend_type); + out.decoder.logits_size = weights.lm_head.shape.dims[0]; + out.decoder.logits_mode = modules::QwenCausalDecoderLogitsMode::LastStep; + out.decoder.use_lm_head_bias = false; + out.logits_readback_token_ids = generation_token_ids(config); + // Readback indices address our local head. Prompt/decode token IDs still + // address the full embedding vocabulary and are never rebased. + for (auto & token : out.logits_readback_token_ids) { + token -= static_cast(weights.lm_head_row_offset); + } + out.prefill_graph_arena_bytes = prefill_bytes; + out.decode_graph_arena_bytes = decode_bytes; + return out; +} + +modules::QwenCausalDecodeRuntimeWeights runtime_weights( + const MiraQwenWeights & weights) { + modules::QwenCausalDecodeRuntimeWeights out; + out.token_embedding = weights.token_embedding; + out.stack = weights.stack; + out.final_norm = weights.final_norm; + out.lm_head = modules::LinearWeights{weights.lm_head, std::nullopt}; + return out; +} + +} // namespace + +struct MiraGenerator::Impl { + Impl( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t prefill_bytes, + size_t decode_bytes, + size_t weight_bytes, + assets::TensorStorageType storage_type) + : config(assets.config), + weights(load_weights( + assets, + execution.backend(), + execution.backend_type(), + weight_bytes, + storage_type)), + runtime(std::make_unique( + execution, + runtime_config(config, *weights, execution.backend_type(), prefill_bytes, decode_bytes), + runtime_weights(*weights))) {} + + std::vector generate( + const std::vector & prompt, + const MiraGenerationOptions & options) { + if (prompt.empty()) { + throw std::runtime_error("MiraTTS LM prompt is empty"); + } + const int64_t room = config.max_position_embeddings - + static_cast(prompt.size()); + const int64_t max_tokens = std::min(options.max_new_tokens, room); + if (max_tokens <= 0) { + throw std::runtime_error("MiraTTS LM prompt exceeds its context window"); + } + auto prefill = runtime->prefill_tokens(prompt); + runtime->start_decode_tokens( + prefill.state, static_cast(prompt.size()) + max_tokens); + + sampling::HfSamplingOptions sampling_options; + sampling_options.do_sample = true; + sampling_options.temperature = options.temperature; + sampling_options.top_k = options.top_k; + sampling_options.top_p = options.top_p; + sampling_options.min_p = options.min_p; + sampling_options.repetition_penalty = options.repetition_penalty; + sampling_options.min_tokens_to_keep = 1; + sampling::HfSampler sampler; + sampling::HfSamplerScratch scratch; + scratch.reserve_vocab(static_cast(config.vocab_size)); + std::mt19937 rng(static_cast(options.seed)); + // Logits are compacted to [speech codes..., EOS]. Prompt tokens do not + // overlap this alphabet, so only generated compact ids participate in + // repetition penalty bookkeeping. + std::vector history; + std::vector codes; + auto logits = std::move(prefill.logits); + for (int64_t step = 0; step < max_tokens; ++step) { + const int32_t compact_token = sampler.sample( + logits, + history, + sampling_options, + scratch, + rng, + nullptr, + "MiraTTS LM"); + const int32_t token = compact_token == + static_cast(config.speech_token_end - + config.speech_token_start + 1) + ? config.eos_token_id + : config.speech_token_start + compact_token; + if (token == config.eos_token_id) { + break; + } + history.push_back(compact_token); + if (token >= config.speech_token_start && token <= config.speech_token_end) { + codes.push_back(token - config.speech_token_start); + } + logits = runtime->decode_token(token).logits; + } + if (codes.empty()) { + throw std::runtime_error("MiraTTS LM produced no speech tokens"); + } + return codes; + } + + MiraTTSConfig config; + std::shared_ptr weights; + std::unique_ptr runtime; +}; + +MiraGenerator::MiraGenerator( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t prefill_graph_arena_bytes, + size_t decode_graph_arena_bytes, + size_t weight_context_bytes, + assets::TensorStorageType weight_storage_type) + : impl_(std::make_unique( + assets, + execution, + prefill_graph_arena_bytes, + decode_graph_arena_bytes, + weight_context_bytes, + weight_storage_type)) {} + +MiraGenerator::~MiraGenerator() = default; + +std::vector MiraGenerator::generate( + const std::vector & prompt_ids, + const MiraGenerationOptions & options) { + return impl_->generate(prompt_ids, options); +} + +void MiraGenerator::release_runtime_graphs() { + impl_->runtime->release_runtime_graphs(); +} + +} // namespace engine::community_models::mira_tts diff --git a/src/community_models/mira_tts/processor.cpp b/src/community_models/mira_tts/processor.cpp new file mode 100644 index 000000000..046b10f40 --- /dev/null +++ b/src/community_models/mira_tts/processor.cpp @@ -0,0 +1,429 @@ +#include "engine/community_models/mira_tts/processor.h" + +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/lookup_modules.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/streaming_conv_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { +namespace { + +namespace binding = modules::binding; + +struct ContextDeleter { + void operator()(ggml_context * context) const noexcept { + if (context != nullptr) ggml_free(context); + } +}; + +struct ConvNeXtWeights { + modules::Conv1dWeights depthwise; + modules::NormWeights norm; + modules::LinearWeights first; + modules::LinearWeights second; + core::TensorValue gamma; +}; + +struct PlainStageWeights { + modules::Conv1dWeights embed; + modules::NormWeights norm; + std::vector blocks; + modules::NormWeights final_norm; +}; + +struct ConditionalNormWeights { + modules::LinearWeights scale; + modules::LinearWeights shift; +}; + +struct ConditionalBlockWeights { + modules::Conv1dWeights depthwise; + ConditionalNormWeights norm; + modules::LinearWeights first; + modules::LinearWeights second; + core::TensorValue gamma; +}; + +struct ProcessorWeights { + std::shared_ptr store; + core::TensorValue speech_codebook; + modules::Conv1dWeights speech_projection; + modules::LinearWeights speech_linear; + core::TensorValue context_codebook; + modules::LinearWeights context_project_out; + modules::LinearWeights speaker_project; + std::vector downsample; + modules::Conv1dWeights backbone_embed; + ConditionalNormWeights backbone_norm; + std::vector backbone_blocks; + modules::NormWeights final_norm; + modules::LinearWeights output_linear; +}; + +modules::Conv1dWeights load_conv( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + assets::TensorStorageType storage_type, + int64_t out_channels, + int64_t in_channels, + int64_t kernel, + int64_t groups = 1) { + modules::Conv1dWeights out; + out.weight = store.load_tensor( + source, prefix + ".weight", storage_type, + {out_channels, in_channels / groups, kernel}); + out.bias = store.load_f32_tensor(source, prefix + ".bias", {out_channels}); + return out; +} + +modules::LinearWeights load_linear( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + assets::TensorStorageType storage_type, + int64_t out_features, + int64_t in_features, + bool bias = true) { + return binding::linear_from_source( + store, source, prefix, storage_type, out_features, in_features, bias); +} + +modules::NormWeights load_norm( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + int64_t hidden) { + return { + store.load_f32_tensor(source, prefix + ".weight", {hidden}), + store.load_f32_tensor(source, prefix + ".bias", {hidden})}; +} + +ConvNeXtWeights load_plain_block( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + assets::TensorStorageType linear_type, + assets::TensorStorageType conv_type) { + ConvNeXtWeights out; + out.depthwise = load_conv(store, source, prefix + ".dwconv", conv_type, 384, 384, 7, 384); + out.norm = load_norm(store, source, prefix + ".norm", 384); + out.first = load_linear(store, source, prefix + ".pwconv1", linear_type, 2048, 384); + out.second = load_linear(store, source, prefix + ".pwconv2", linear_type, 384, 2048); + out.gamma = store.load_f32_tensor(source, prefix + ".gamma", {384}); + return out; +} + +ConditionalNormWeights load_cond_norm( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + assets::TensorStorageType storage_type) { + return { + load_linear(store, source, prefix + ".scale", storage_type, 384, 1024), + load_linear(store, source, prefix + ".shift", storage_type, 384, 1024)}; +} + +ProcessorWeights load_weights( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t context_bytes, + assets::TensorStorageType linear_type, + assets::TensorStorageType conv_type) { + ProcessorWeights out; + out.store = std::make_shared( + execution.backend(), execution.backend_type(), + "mira_tts.processor.weights", context_bytes); + const auto & source = *assets.processor_weights; + out.speech_codebook = out.store->load_tensor( + source, "quantizer.codebook.weight", assets::TensorStorageType::F32, {8192, 8}); + out.speech_projection = load_conv( + *out.store, source, "quantizer.out_project", conv_type, 1024, 8, 1); + out.speech_linear = load_linear( + *out.store, source, "prenet.linear_pre", linear_type, 384, 1024); + out.context_codebook = out.store->load_tensor( + source, "speaker_encoder.context_codebook", assets::TensorStorageType::F32, + {4096, 6}); + out.context_project_out = load_linear( + *out.store, source, "speaker_encoder.quantizer.project_out", + linear_type, 128, 6); + out.speaker_project = load_linear( + *out.store, source, "speaker_encoder.project", linear_type, 1024, 4096); + for (int stage = 0; stage < 2; ++stage) { + const std::string prefix = "prenet.downsample." + std::to_string(stage) + ".1"; + PlainStageWeights item; + item.embed = load_conv(*out.store, source, prefix + ".embed", conv_type, 384, 384, 7); + item.norm = load_norm(*out.store, source, prefix + ".norm", 384); + for (int block = 0; block < 2; ++block) { + item.blocks.push_back(load_plain_block( + *out.store, source, + prefix + ".convnext." + std::to_string(block), + linear_type, conv_type)); + } + item.final_norm = load_norm(*out.store, source, prefix + ".final_layer_norm", 384); + out.downsample.push_back(std::move(item)); + } + out.backbone_embed = load_conv( + *out.store, source, "prenet.vocos_backbone.embed", conv_type, 384, 384, 7); + out.backbone_norm = load_cond_norm( + *out.store, source, "prenet.vocos_backbone.norm", linear_type); + for (int block = 0; block < 12; ++block) { + const std::string prefix = + "prenet.vocos_backbone.convnext." + std::to_string(block); + ConditionalBlockWeights item; + item.depthwise = load_conv( + *out.store, source, prefix + ".dwconv", conv_type, 384, 384, 7, 384); + item.norm = load_cond_norm(*out.store, source, prefix + ".norm", linear_type); + item.first = load_linear(*out.store, source, prefix + ".pwconv1", linear_type, 2048, 384); + item.second = load_linear(*out.store, source, prefix + ".pwconv2", linear_type, 384, 2048); + item.gamma = out.store->load_f32_tensor(source, prefix + ".gamma", {384}); + out.backbone_blocks.push_back(std::move(item)); + } + out.final_norm = load_norm( + *out.store, source, "prenet.vocos_backbone.final_layer_norm", 384); + out.output_linear = load_linear( + *out.store, source, "prenet.linear", linear_type, 1024, 384); + out.store->upload(); + return out; +} + +core::TensorValue linear( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const modules::LinearWeights & weights, + int64_t in_features, + int64_t out_features) { + return modules::LinearModule({in_features, out_features, weights.bias.has_value()}) + .build(ctx, input, weights); +} + +core::TensorValue conv( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const modules::Conv1dWeights & weights, + int64_t channels, + int64_t kernel, + int64_t groups = 1) { + if (groups == channels) { + return modules::DepthwiseConv1dModule({ + channels, kernel, 1, static_cast(kernel / 2), 1, true}) + .build(ctx, input, {weights.weight, weights.bias}); + } + if (groups != 1) { + throw std::runtime_error("MiraTTS processor only supports regular or depthwise convolution"); + } + return modules::Conv1dModule({ + channels, channels, kernel, 1, static_cast(kernel / 2), 1, true}) + .build(ctx, input, weights); +} + +core::TensorValue layer_norm( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const modules::NormWeights & weights) { + return modules::LayerNormModule({384, 1.0e-5F, true, true}) + .build(ctx, input, weights); +} + +core::TensorValue scale_last( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const core::TensorValue & scale) { + auto shaped = core::reshape_tensor( + ctx, scale, core::TensorShape::from_dims({1, 1, 384})); + auto repeated = core::wrap_tensor( + ggml_repeat(ctx.ggml, shaped.tensor, input.tensor), input.shape, GGML_TYPE_F32); + return modules::MulModule{}.build(ctx, input, repeated); +} + +core::TensorValue plain_block( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const ConvNeXtWeights & weights) { + auto x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, input); + x = conv(ctx, x, weights.depthwise, 384, 7, 384); + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, x); + x = layer_norm(ctx, x, weights.norm); + x = linear(ctx, x, weights.first, 384, 2048); + x = modules::GeluModule({modules::GeluApproximation::ExactErf}).build(ctx, x); + x = linear(ctx, x, weights.second, 2048, 384); + x = scale_last(ctx, x, weights.gamma); + return modules::AddModule{}.build(ctx, input, x); +} + +core::TensorValue conditional_norm( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const core::TensorValue & condition, + const ConditionalNormWeights & weights) { + auto normalized = modules::LayerNormModule({384, 1.0e-5F, false, false}) + .build(ctx, input, {}); + auto scale = linear(ctx, condition, weights.scale, 1024, 384); + auto shift = linear(ctx, condition, weights.shift, 1024, 384); + scale = core::reshape_tensor(ctx, scale, core::TensorShape::from_dims({1, 1, 384})); + shift = core::reshape_tensor(ctx, shift, core::TensorShape::from_dims({1, 1, 384})); + auto scale_rep = core::wrap_tensor( + ggml_repeat(ctx.ggml, scale.tensor, normalized.tensor), normalized.shape, GGML_TYPE_F32); + auto shift_rep = core::wrap_tensor( + ggml_repeat(ctx.ggml, shift.tensor, normalized.tensor), normalized.shape, GGML_TYPE_F32); + auto x = modules::MulModule{}.build(ctx, normalized, scale_rep); + return modules::AddModule{}.build(ctx, x, shift_rep); +} + +} // namespace + +struct MiraAcousticProcessor::Impl { + Impl( + const MiraTTSAssets & assets, + core::ExecutionContext & execution_in, + size_t weight_bytes, + size_t graph_bytes, + assets::TensorStorageType linear_type, + assets::TensorStorageType conv_type) + : execution(execution_in), + graph_context_bytes(graph_bytes), + weights(load_weights( + assets, execution_in, weight_bytes, linear_type, conv_type)) {} + + std::vector process( + const std::vector & speech_codes, + const std::vector & context_codes) { + if (speech_codes.empty()) throw std::runtime_error("MiraTTS processor requires speech codes"); + if (context_codes.size() != 32) throw std::runtime_error("MiraTTS processor requires 32 context codes"); + const int64_t frames = static_cast(speech_codes.size()); + ggml_init_params params{graph_context_bytes, nullptr, true}; + std::unique_ptr context(ggml_init(params)); + if (!context) throw std::runtime_error("failed to create MiraTTS processor graph context"); + auto * speech = ggml_new_tensor_2d(context.get(), GGML_TYPE_I32, frames, 1); + auto * speaker = ggml_new_tensor_2d(context.get(), GGML_TYPE_I32, 32, 1); + ggml_set_input(speech); + ggml_set_input(speaker); + core::ModuleBuildContext build{context.get(), "mira_tts.processor"}; + + auto speech_ids = core::wrap_tensor( + speech, core::TensorShape::from_dims({1, frames}), GGML_TYPE_I32); + auto x = modules::EmbeddingModule({8192, 8}).build( + build, speech_ids, weights.speech_codebook); + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, x); + x = modules::Conv1dModule({8, 1024, 1, 1, 0, 1, true}) + .build(build, x, weights.speech_projection); + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, x); + x = linear(build, x, weights.speech_linear, 1024, 384); + + auto context_ids = core::wrap_tensor( + speaker, core::TensorShape::from_dims({1, 32}), GGML_TYPE_I32); + auto condition = modules::EmbeddingModule({4096, 6}).build( + build, context_ids, weights.context_codebook); + condition = linear(build, condition, weights.context_project_out, 6, 128); + // The exported processor flattens [B, 128, 32], not [B, 32, 128]. + // Preserve that channel-major speaker-conditioning order. + condition = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, condition); + condition = core::reshape_tensor( + build, + core::ensure_backend_addressable_layout(build, condition), + core::TensorShape::from_dims({1, 4096})); + condition = linear(build, condition, weights.speaker_project, 4096, 1024); + + for (const auto & stage : weights.downsample) { + x = core::wrap_tensor(ggml_scale(build.ggml, x.tensor, 3.0F), x.shape, x.type); + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, x); + x = conv(build, x, stage.embed, 384, 7); + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, x); + x = layer_norm(build, x, stage.norm); + for (const auto & block : stage.blocks) x = plain_block(build, x, block); + x = layer_norm(build, x, stage.final_norm); + } + + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, x); + x = conv(build, x, weights.backbone_embed, 384, 7); + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, x); + x = conditional_norm(build, x, condition, weights.backbone_norm); + for (const auto & block : weights.backbone_blocks) { + auto residual = x; + auto hidden = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, x); + hidden = conv(build, hidden, block.depthwise, 384, 7, 384); + hidden = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, hidden); + hidden = conditional_norm(build, hidden, condition, block.norm); + hidden = linear(build, hidden, block.first, 384, 2048); + hidden = modules::GeluModule({modules::GeluApproximation::ExactErf}).build(build, hidden); + hidden = linear(build, hidden, block.second, 2048, 384); + hidden = scale_last(build, hidden, block.gamma); + x = modules::AddModule{}.build(build, residual, hidden); + } + x = layer_norm(build, x, weights.final_norm); + x = linear(build, x, weights.output_linear, 384, 1024); + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build, x); + auto cond_bct = core::reshape_tensor( + build, condition, core::TensorShape::from_dims({1, 1024, 1})); + cond_bct = core::wrap_tensor( + ggml_repeat(build.ggml, cond_bct.tensor, x.tensor), x.shape, GGML_TYPE_F32); + x = modules::AddModule{}.build(build, x, cond_bct); + x = core::ensure_backend_addressable_layout(build, x); + ggml_set_output(x.tensor); + auto * graph = ggml_new_graph_custom(context.get(), 65536, false); + ggml_build_forward_expand(graph, x.tensor); + auto allocator = ggml_gallocr_new( + ggml_backend_get_default_buffer_type(execution.backend())); + if (allocator == nullptr || !ggml_gallocr_alloc_graph(allocator, graph)) { + if (allocator != nullptr) ggml_gallocr_free(allocator); + throw std::runtime_error("failed to allocate MiraTTS processor graph"); + } + ggml_backend_tensor_set(speech, speech_codes.data(), 0, speech_codes.size() * sizeof(int32_t)); + ggml_backend_tensor_set(speaker, context_codes.data(), 0, context_codes.size() * sizeof(int32_t)); + core::set_backend_threads(execution.backend(), std::max(1, execution.config().threads)); + const auto status = core::compute_backend_graph(execution.backend(), graph); + ggml_backend_synchronize(execution.backend()); + if (status != GGML_STATUS_SUCCESS) { + core::release_backend_graph_resources(execution.backend(), graph); + ggml_gallocr_free(allocator); + throw std::runtime_error("MiraTTS processor graph compute failed"); + } + std::vector output(static_cast(1024 * frames)); + ggml_backend_tensor_get(x.tensor, output.data(), 0, output.size() * sizeof(float)); + core::release_backend_graph_resources(execution.backend(), graph); + ggml_gallocr_free(allocator); + return output; + } + + core::ExecutionContext & execution; + size_t graph_context_bytes; + ProcessorWeights weights; +}; + +MiraAcousticProcessor::MiraAcousticProcessor( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + size_t graph_context_bytes, + assets::TensorStorageType linear_storage_type, + assets::TensorStorageType conv_storage_type) + : impl_(std::make_unique( + assets, execution, weight_context_bytes, graph_context_bytes, + linear_storage_type, conv_storage_type)) {} + +MiraAcousticProcessor::~MiraAcousticProcessor() = default; + +std::vector MiraAcousticProcessor::process( + const std::vector & speech_codes, + const std::vector & context_codes) { + return impl_->process(speech_codes, context_codes); +} + +} // namespace engine::community_models::mira_tts diff --git a/src/community_models/mira_tts/prompt.cpp b/src/community_models/mira_tts/prompt.cpp new file mode 100644 index 000000000..1cfec568b --- /dev/null +++ b/src/community_models/mira_tts/prompt.cpp @@ -0,0 +1,87 @@ +#include "engine/community_models/mira_tts/prompt.h" + +#include "engine/framework/tokenizers/llama_bpe.h" + +#include +#include + +namespace engine::community_models::mira_tts { +namespace { + +int32_t require_token_id( + const tokenizers::LlamaBpeTokenizer & tokenizer, + const std::string & token) { + const auto id = tokenizer.find_token_id(token); + if (!id.has_value()) { + throw std::runtime_error("MiraTTS tokenizer is missing token " + token); + } + return *id; +} + +} // namespace + +struct MiraPromptBuilder::Impl { + explicit Impl(std::shared_ptr input_assets) + : assets(std::move(input_assets)) { + if (assets == nullptr) { + throw std::runtime_error("MiraTTS prompt builder requires assets"); + } + tokenizers::LlamaBpeTokenizerSpec spec; + spec.tokenizer_json_path = assets->resources.require_file("tokenizer_json"); + spec.tokenizer_config_path = assets->resources.require_file("tokenizer_config"); + spec.pre_type = tokenizers::LlamaBpePreTokenizer::Qwen2; + tokenizer = tokenizers::load_llama_bpe_tokenizer(spec); + task_tts = require_token_id(*tokenizer, "<|task_tts|>"); + start_text = require_token_id(*tokenizer, "<|start_text|>"); + end_text = require_token_id(*tokenizer, "<|end_text|>"); + context_start = require_token_id(*tokenizer, "<|context_audio_start|>"); + context_end = require_token_id(*tokenizer, "<|context_audio_end|>"); + speech_start = require_token_id(*tokenizer, "<|prompt_speech_start|>"); + context_token_start = require_token_id(*tokenizer, "<|context_token_0|>"); + } + + std::shared_ptr assets; + std::shared_ptr tokenizer; + int32_t task_tts = 0; + int32_t start_text = 0; + int32_t end_text = 0; + int32_t context_start = 0; + int32_t context_end = 0; + int32_t speech_start = 0; + int32_t context_token_start = 0; +}; + +MiraPromptBuilder::MiraPromptBuilder(std::shared_ptr assets) + : impl_(std::make_unique(std::move(assets))) {} + +MiraPromptBuilder::~MiraPromptBuilder() = default; + +std::vector MiraPromptBuilder::build( + const std::string & text, + const std::vector & context_codes) const { + if (text.empty()) { + throw std::runtime_error("MiraTTS requires non-empty text"); + } + if (context_codes.size() != 32) { + throw std::runtime_error("MiraTTS speaker encoder must produce 32 context codes"); + } + auto text_ids = impl_->tokenizer->encode(text, false); + std::vector out; + out.reserve(text_ids.size() + context_codes.size() + 6); + out.push_back(impl_->task_tts); + out.push_back(impl_->start_text); + out.insert(out.end(), text_ids.begin(), text_ids.end()); + out.push_back(impl_->end_text); + out.push_back(impl_->context_start); + for (const int32_t code : context_codes) { + if (code < 0 || code >= 4096) { + throw std::runtime_error("MiraTTS context code is outside [0, 4096)"); + } + out.push_back(impl_->context_token_start + code); + } + out.push_back(impl_->context_end); + out.push_back(impl_->speech_start); + return out; +} + +} // namespace engine::community_models::mira_tts diff --git a/src/community_models/mira_tts/session.cpp b/src/community_models/mira_tts/session.cpp new file mode 100644 index 000000000..97d5b310f --- /dev/null +++ b/src/community_models/mira_tts/session.cpp @@ -0,0 +1,399 @@ +#include "engine/community_models/mira_tts/session.h" + +#include "engine/community_models/mira_tts/decoder.h" +#include "engine/community_models/mira_tts/generator.h" +#include "engine/community_models/mira_tts/processor.h" +#include "engine/community_models/mira_tts/prompt.h" +#include "engine/community_models/mira_tts/speaker_encoder.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/runtime/options.h" +#include "engine/framework/runtime/spec_backed_model.h" +#include "engine/framework/text/chunking.h" + +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { +namespace { + +constexpr const char * kFamily = "mira_tts"; +constexpr size_t kGraphBytes = 512ull * 1024ull * 1024ull; +constexpr size_t kWeightBytes = 256ull * 1024ull * 1024ull; +constexpr size_t kDefaultReferenceCacheSlots = 1; + +using Clock = std::chrono::steady_clock; + +std::shared_ptr require_assets( + std::shared_ptr value) { + if (value == nullptr) throw std::runtime_error("MiraTTS session requires assets"); + return value; +} + +std::shared_ptr require_contract( + std::shared_ptr value) { + if (value == nullptr) throw std::runtime_error("MiraTTS session requires a model contract"); + return value; +} + +MiraGenerationOptions generation_options(const runtime::TaskRequest & request) { + MiraGenerationOptions out; + if (const auto value = runtime::parse_i64_option(request.options, {"max_tokens"})) { + out.max_new_tokens = *value; + } + if (const auto value = runtime::parse_i64_option(request.options, {"top_k"})) { + out.top_k = *value; + } + if (const auto value = runtime::parse_finite_float_option(request.options, {"top_p"})) { + out.top_p = *value; + } + if (const auto value = runtime::parse_finite_float_option(request.options, {"min_p"})) { + out.min_p = *value; + } + if (const auto value = runtime::parse_finite_float_option(request.options, {"temperature"})) { + out.temperature = *value; + } + if (const auto value = runtime::parse_finite_float_option( + request.options, {"repetition_penalty"})) { + out.repetition_penalty = *value; + } + if (const auto value = runtime::parse_u64_option(request.options, {"seed"})) { + out.seed = *value; + out.has_seed = true; + } + if (!out.has_seed) out.seed = runtime::random_u64_seed(); + if (out.max_new_tokens < 1 || out.top_k < 1 || out.temperature <= 0.0F || + out.top_p <= 0.0F || out.top_p > 1.0F || out.min_p < 0.0F || + out.min_p > 1.0F || out.repetition_penalty < 1.0F) { + throw std::runtime_error("MiraTTS generation options are outside their valid ranges"); + } + return out; +} + +size_t reference_cache_slots(const runtime::SessionOptions & options) { + const int64_t slots = runtime::parse_i64_option( + options.options, + {"mira_tts.reference_cache_slots", "reference_cache_slots"}) + .value_or(static_cast(kDefaultReferenceCacheSlots)); + if (slots < 0) { + throw std::runtime_error( + "mira_tts.reference_cache_slots must be non-negative"); + } + if (static_cast(slots) > + static_cast(std::numeric_limits::max())) { + throw std::runtime_error( + "mira_tts.reference_cache_slots is too large"); + } + return static_cast(slots); +} + +uint64_t mix_reference_hash(uint64_t hash, uint64_t value) { + hash ^= value; + hash *= 1099511628211ull; + return hash; +} + +std::unique_ptr create_session( + const runtime::TaskSpec & task, + const runtime::SessionOptions & options, + std::shared_ptr assets, + std::shared_ptr contract) { + return std::make_unique( + task, options, std::move(assets), std::move(contract)); +} + +} // namespace + +MiraTTSOfflineSession::MiraTTSOfflineSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract) + : RuntimeSessionBase(options), + task_(task), + assets_(require_assets(std::move(assets))), + contract_(require_contract(std::move(contract))), + reference_cache_(reference_cache_slots(this->options())) { + runtime::validate_spec_backed_session_options( + options, *contract_, kFamily, "MiraTTS"); + if ((task.mode != runtime::RunMode::Offline && + task.mode != runtime::RunMode::Streaming) || + (task.task != runtime::VoiceTaskKind::Tts && + task.task != runtime::VoiceTaskKind::VoiceCloning)) { + throw std::runtime_error( + "MiraTTS supports offline and streaming TTS/voice cloning only"); + } + const auto lm_type = runtime::parse_tensor_storage_option( + options.options, "backbone_weight_type", assets::TensorStorageType::Native, + {assets::TensorStorageType::Native, assets::TensorStorageType::F32, + assets::TensorStorageType::F16, assets::TensorStorageType::BF16, + assets::TensorStorageType::Q8_0}); + const auto linear_type = runtime::parse_tensor_storage_option( + options.options, "linear_weight_type", assets::TensorStorageType::Native, + {assets::TensorStorageType::Native, assets::TensorStorageType::F32, + assets::TensorStorageType::F16, assets::TensorStorageType::BF16, + assets::TensorStorageType::Q8_0}); + const auto conv_type = runtime::parse_tensor_storage_option( + options.options, "conv_weight_type", assets::TensorStorageType::F32, + {assets::TensorStorageType::Native, assets::TensorStorageType::F32, + assets::TensorStorageType::F16, assets::TensorStorageType::BF16}); + auto & execution = execution_context(); + prompt_ = std::make_unique(assets_); + speaker_encoder_ = std::make_unique( + *assets_, execution, kWeightBytes, kGraphBytes, linear_type, conv_type); + generator_ = std::make_unique( + *assets_, execution, kGraphBytes, kGraphBytes, kWeightBytes, lm_type); + processor_ = std::make_unique( + *assets_, execution, kWeightBytes, kGraphBytes, linear_type, conv_type); + // ggml's current CUDA ConvTranspose1d kernel requires F32 weights. + decoder_ = std::make_unique( + *assets_, execution, kWeightBytes, kGraphBytes, + assets::TensorStorageType::F32); +} + +MiraTTSOfflineSession::~MiraTTSOfflineSession() = default; + +std::string MiraTTSOfflineSession::family() const { return kFamily; } + +runtime::VoiceTaskKind MiraTTSOfflineSession::task_kind() const { + return task_.task; +} + +runtime::RunMode MiraTTSOfflineSession::run_mode() const { return task_.mode; } + +void MiraTTSOfflineSession::prepare( + const runtime::SessionPreparationRequest & request) { + runtime::validate_spec_backed_request_options( + request.options, *contract_, "MiraTTS"); + prepared_reference_.reset(); + if (request.voice.has_value() && request.voice->speaker.has_value() && + request.voice->speaker->audio.has_value()) { + prepared_reference_ = *request.voice->speaker->audio; + (void)context_codes(*prepared_reference_); + } + mark_prepared(); +} + +bool MiraTTSOfflineSession::ReferenceCacheKeyEqual::operator()( + const ReferenceCacheKey & lhs, + const ReferenceCacheKey & rhs) const noexcept { + return lhs.sample_rate == rhs.sample_rate && + lhs.channels == rhs.channels && + lhs.sample_count == rhs.sample_count && + lhs.sample_hash == rhs.sample_hash; +} + +MiraTTSOfflineSession::ReferenceCacheKey +MiraTTSOfflineSession::make_reference_cache_key( + const runtime::AudioBuffer & audio) { + uint64_t hash = 1469598103934665603ull; + for (const float sample : audio.samples) { + uint32_t bits = 0; + std::memcpy(&bits, &sample, sizeof(bits)); + hash = mix_reference_hash(hash, static_cast(bits)); + } + return ReferenceCacheKey{ + audio.sample_rate, + audio.channels, + static_cast(audio.samples.size()), + hash, + }; +} + +const runtime::AudioBuffer & MiraTTSOfflineSession::reference_audio( + const runtime::TaskRequest & request) const { + if (request.voice.has_value() && request.voice->speaker.has_value() && + request.voice->speaker->audio.has_value()) { + return *request.voice->speaker->audio; + } + if (request.audio_input.has_value()) return *request.audio_input; + if (prepared_reference_.has_value()) return *prepared_reference_; + throw std::runtime_error( + "MiraTTS requires a reference voice in voice.speaker.audio or audio_input"); +} + +const std::vector & MiraTTSOfflineSession::context_codes( + const runtime::AudioBuffer & reference) { + const auto key_start = Clock::now(); + auto key = make_reference_cache_key(reference); + engine::debug::timing_log_scalar( + "mira_tts.reference.hash_ms", engine::debug::elapsed_ms(key_start)); + if (const auto * cached = reference_cache_.find(key)) { + engine::debug::trace_log_scalar("mira_tts.reference.cache_hit", true); + return *cached; + } + + engine::debug::trace_log_scalar("mira_tts.reference.cache_hit", false); + const auto encode_start = Clock::now(); + auto encoded = speaker_encoder_->encode(reference); + engine::debug::timing_log_scalar( + "mira_tts.reference.encode_ms", engine::debug::elapsed_ms(encode_start)); + if (reference_cache_.capacity() == 0) { + uncached_context_codes_ = std::move(encoded); + return *uncached_context_codes_; + } + reference_cache_.put(key, std::move(encoded)); + return *reference_cache_.find(key); +} + +runtime::TaskResult MiraTTSOfflineSession::run( + const runtime::TaskRequest & request) { + require_prepared("MiraTTS run"); + runtime::validate_spec_backed_request_options( + request.options, *contract_, "MiraTTS"); + if (!request.text_input.has_value() || request.text_input->text.empty()) { + throw std::runtime_error("MiraTTS requires non-empty text input"); + } + if (task_.mode != runtime::RunMode::Offline) { + throw std::runtime_error("MiraTTS run requires an offline session"); + } + const auto & codes = context_codes(reference_audio(request)); + runtime::TaskResult result; + result.audio_output = synthesize_text( + request.text_input->text, codes, generation_options(request)); + return result; +} + +runtime::AudioBuffer MiraTTSOfflineSession::synthesize_text( + const std::string & text, + const std::vector & context_codes, + const MiraGenerationOptions & options) { + const auto prompt_start = Clock::now(); + const auto prompt_ids = prompt_->build(text, context_codes); + engine::debug::timing_log_scalar( + "mira_tts.prompt_ms", engine::debug::elapsed_ms(prompt_start)); + if (execution_context().backend_type() == core::BackendType::Cpu) { + // The CPU decode graph may otherwise reuse a larger cache allocation + // after a long request. Rebuilding its graph preserves deterministic + // seeded output; model weights remain resident across this reset. + generator_->release_runtime_graphs(); + } + const auto generator_start = Clock::now(); + const auto speech_codes = generator_->generate( + prompt_ids, options); + engine::debug::timing_log_scalar( + "mira_tts.generator_ms", engine::debug::elapsed_ms(generator_start)); + if (speech_codes.empty()) { + throw std::runtime_error("MiraTTS generated no speech tokens"); + } + const auto processor_start = Clock::now(); + const auto latents = processor_->process(speech_codes, context_codes); + engine::debug::timing_log_scalar( + "mira_tts.processor_ms", engine::debug::elapsed_ms(processor_start)); + const auto decoder_start = Clock::now(); + auto audio = decoder_->decode( + latents, static_cast(speech_codes.size())); + engine::debug::timing_log_scalar( + "mira_tts.decoder_ms", engine::debug::elapsed_ms(decoder_start)); + return audio; +} + +runtime::StreamingPolicy MiraTTSOfflineSession::streaming_policy() const { + runtime::StreamingPolicy policy; + policy.input = runtime::StreamingInputKind::None; + policy.output = runtime::StreamingOutputKind::PullEvents; + return policy; +} + +void MiraTTSOfflineSession::start_stream( + const runtime::TaskRequest & request) { + require_prepared("MiraTTS streaming"); + runtime::validate_spec_backed_request_options( + request.options, *contract_, "MiraTTS"); + if (task_.mode != runtime::RunMode::Streaming) { + throw std::runtime_error("MiraTTS start_stream requires a streaming session"); + } + if (!request.text_input.has_value() || request.text_input->text.empty()) { + throw std::runtime_error("MiraTTS streaming requires non-empty text input"); + } + reset(); + const int64_t chunk_size = engine::text::parse_text_chunk_size_override( + request.options).value_or(160); + const auto chunk_mode = engine::text::parse_text_chunk_mode_override( + request.options).value_or(engine::text::TextChunkMode::Default); + streaming_text_chunks_ = engine::text::split_text_chunks( + request.text_input->text, chunk_size, chunk_mode); + if (streaming_text_chunks_.empty()) { + throw std::runtime_error("MiraTTS streaming text chunking produced no segments"); + } + streaming_context_codes_ = context_codes(reference_audio(request)); + streaming_generation_ = generation_options(request); + streaming_started_ = true; +} + +std::optional +MiraTTSOfflineSession::next_stream_event() { + if (!streaming_started_ || !streaming_generation_.has_value()) { + throw std::runtime_error("MiraTTS streaming has not been started"); + } + if (streaming_chunk_index_ >= streaming_text_chunks_.size()) { + return std::nullopt; + } + const size_t index = streaming_chunk_index_++; + auto options = *streaming_generation_; + options.seed += index; + auto audio = synthesize_text( + streaming_text_chunks_[index], streaming_context_codes_, options); + streaming_audio_chunks_.push_back(audio); + runtime::StreamEvent event; + event.audio_output = std::move(audio); + if (stream_sink_) { + stream_sink_(event); + } + return event; +} + +void MiraTTSOfflineSession::set_stream_event_sink( + runtime::StreamEventCallback sink) { + stream_sink_ = std::move(sink); +} + +runtime::TaskResult MiraTTSOfflineSession::finish_stream() { + if (!streaming_started_) { + throw std::runtime_error("MiraTTS streaming has not been started"); + } + runtime::TaskResult result; + runtime::AudioBuffer merged; + for (const auto & chunk : streaming_audio_chunks_) { + runtime::append_audio_buffer(merged, chunk); + } + if (merged.sample_rate == 0) { + throw std::runtime_error("MiraTTS streaming produced no audio chunks"); + } + result.audio_output = std::move(merged); + reset(); + return result; +} + +void MiraTTSOfflineSession::reset() { + streaming_context_codes_.clear(); + streaming_text_chunks_.clear(); + streaming_audio_chunks_.clear(); + streaming_generation_.reset(); + streaming_chunk_index_ = 0; + streaming_started_ = false; +} + +runtime::StreamEvent MiraTTSOfflineSession::process_audio_chunk( + const runtime::AudioChunk & chunk) { + (void)chunk; + throw std::runtime_error("MiraTTS streaming does not consume audio chunks"); +} + +runtime::TaskResult MiraTTSOfflineSession::finalize() { + return finish_stream(); +} + +std::shared_ptr make_mira_tts_loader() { + runtime::SpecBackedVoiceModelConfig config; + config.family = kFamily; + config.aliases = {"mira", "MiraTTS"}; + config.load_assets = load_mira_tts_assets; + config.create_session = create_session; + return runtime::make_spec_backed_voice_loader(std::move(config)); +} + +} // namespace engine::community_models::mira_tts diff --git a/src/community_models/mira_tts/speaker_encoder.cpp b/src/community_models/mira_tts/speaker_encoder.cpp new file mode 100644 index 000000000..53b6e4206 --- /dev/null +++ b/src/community_models/mira_tts/speaker_encoder.cpp @@ -0,0 +1,588 @@ +#include "engine/community_models/mira_tts/speaker_encoder.h" + +#include "engine/framework/audio/conversion.h" +#include "engine/framework/audio/dsp.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/conditioning_modules.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/optimizations/fast_conv_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::mira_tts { +namespace { + +namespace binding = modules::binding; + +constexpr int kSampleRate = 16000; +constexpr int64_t kReferenceSamples = 96000; +constexpr int64_t kMelBins = 128; +constexpr int64_t kEcapaChannels = 512; +constexpr int64_t kRes2Width = 64; +constexpr int64_t kPerceiverDim = 128; +constexpr int64_t kPerceiverLatents = 32; +constexpr int64_t kPerceiverHeads = 8; +constexpr int64_t kPerceiverInner = 512; +constexpr float kBatchNormEps = 1.0e-5F; + +struct ContextDeleter { + void operator()(ggml_context * context) const noexcept { + if (context != nullptr) ggml_free(context); + } +}; + +struct ConvWeights { + modules::Conv1dWeights value; + int64_t in_channels = 0; + int64_t out_channels = 0; + int64_t kernel = 1; + int64_t padding = 0; + int64_t dilation = 1; +}; + +struct TdnnWeights { + ConvWeights conv; + modules::BatchNorm1dEvalWeights norm; +}; + +struct SeRes2Weights { + TdnnWeights first; + std::vector res2; + TdnnWeights second; + modules::LinearWeights se_first; + modules::LinearWeights se_second; +}; + +struct PerceiverLayerWeights { + modules::LinearWeights q; + modules::LinearWeights kv; + modules::LinearWeights out; + modules::LinearWeights ff_in; + modules::LinearWeights ff_out; +}; + +struct SpeakerWeights { + std::shared_ptr store; + TdnnWeights input; + std::vector blocks; + ConvWeights mfa; + modules::LinearWeights project_context; + core::TensorValue latents; + std::vector perceiver; + core::TensorValue norm_gamma; + modules::LinearWeights quant_project; +}; + +std::vector require_values( + const assets::TensorSource & source, + const std::string & name, + int64_t size) { + auto tensor = source.require_f32_tensor(name); + if (static_cast(tensor.values.size()) != size) { + throw std::runtime_error("MiraTTS tensor size mismatch: " + name); + } + return std::move(tensor.values); +} + +modules::BatchNorm1dEvalWeights load_batch_norm( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + int64_t channels) { + const auto gamma = require_values(source, prefix + ".weight", channels); + const auto beta = require_values(source, prefix + ".bias", channels); + const auto mean = require_values(source, prefix + ".running_mean", channels); + const auto variance = require_values(source, prefix + ".running_var", channels); + std::vector scale(static_cast(channels)); + std::vector bias(static_cast(channels)); + for (int64_t i = 0; i < channels; ++i) { + scale[static_cast(i)] = gamma[static_cast(i)] / + std::sqrt(variance[static_cast(i)] + kBatchNormEps); + bias[static_cast(i)] = beta[static_cast(i)] - + mean[static_cast(i)] * scale[static_cast(i)]; + } + return { + store.make_f32(core::TensorShape::from_dims({channels}), scale), + store.make_f32(core::TensorShape::from_dims({channels}), bias)}; +} + +ConvWeights load_conv( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + assets::TensorStorageType storage_type, + int64_t out_channels, + int64_t in_channels, + int64_t kernel, + int64_t padding, + int64_t dilation) { + ConvWeights out; + out.in_channels = in_channels; + out.out_channels = out_channels; + out.kernel = kernel; + out.padding = padding; + out.dilation = dilation; + out.value.weight = store.load_tensor( + source, prefix + ".weight", storage_type, + {out_channels, in_channels, kernel}); + out.value.bias = store.load_f32_tensor(source, prefix + ".bias", {out_channels}); + return out; +} + +TdnnWeights load_tdnn( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + assets::TensorStorageType storage_type, + int64_t out_channels, + int64_t in_channels, + int64_t kernel, + int64_t padding, + int64_t dilation) { + return { + load_conv(store, source, prefix + ".conv", storage_type, + out_channels, in_channels, kernel, padding, dilation), + load_batch_norm(store, source, prefix + ".bn", out_channels)}; +} + +modules::LinearWeights load_linear( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + assets::TensorStorageType storage_type, + int64_t out_features, + int64_t in_features, + bool bias) { + return binding::linear_from_source( + store, source, prefix, storage_type, out_features, in_features, bias); +} + +SpeakerWeights load_weights( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t context_bytes, + assets::TensorStorageType linear_type, + assets::TensorStorageType conv_type) { + SpeakerWeights out; + out.store = std::make_shared( + execution.backend(), execution.backend_type(), + "mira_tts.speaker_encoder.weights", context_bytes); + const auto & source = *assets.speaker_encoder_weights; + const std::string root = "speaker_encoder.speaker_encoder."; + out.input = load_tdnn( + *out.store, source, root + "layer1", conv_type, + kEcapaChannels, kMelBins, 5, 2, 1); + for (int layer = 2; layer <= 4; ++layer) { + const std::string prefix = root + "layer" + std::to_string(layer) + ".se_res2block."; + SeRes2Weights block; + block.first = load_tdnn( + *out.store, source, prefix + "0", conv_type, + kEcapaChannels, kEcapaChannels, 1, 0, 1); + const int64_t dilation = layer; + for (int branch = 0; branch < 7; ++branch) { + TdnnWeights branch_weights; + branch_weights.conv = load_conv( + *out.store, source, + prefix + "1.convs." + std::to_string(branch), conv_type, + kRes2Width, kRes2Width, 3, dilation, dilation); + branch_weights.norm = load_batch_norm( + *out.store, source, + prefix + "1.bns." + std::to_string(branch), kRes2Width); + block.res2.push_back(std::move(branch_weights)); + } + block.second = load_tdnn( + *out.store, source, prefix + "2", conv_type, + kEcapaChannels, kEcapaChannels, 1, 0, 1); + block.se_first = load_linear( + *out.store, source, prefix + "3.linear1", linear_type, 128, 512, true); + block.se_second = load_linear( + *out.store, source, prefix + "3.linear2", linear_type, 512, 128, true); + out.blocks.push_back(std::move(block)); + } + out.mfa = load_conv( + *out.store, source, root + "conv", conv_type, + 1536, 1536, 1, 0, 1); + out.project_context.weight = out.store->load_tensor( + source, "perceiver.proj_context.weight", linear_type, {128, 1536}); + out.project_context.bias = out.store->load_f32_tensor( + source, "speaker_encoder.perceiver_sampler.proj_context.bias", {128}); + out.latents = out.store->load_f32_tensor( + source, "perceiver.latents", {1, kPerceiverLatents, kPerceiverDim}); + for (int layer = 0; layer < 2; ++layer) { + const std::string prefix = "perceiver.layers." + std::to_string(layer); + const std::string bias_prefix = "speaker_encoder.perceiver_sampler.layers." + + std::to_string(layer) + ".1."; + PerceiverLayerWeights item; + item.q = load_linear(*out.store, source, prefix + ".attn.q", linear_type, 512, 128, false); + item.kv = load_linear(*out.store, source, prefix + ".attn.kv", linear_type, 1024, 128, false); + item.out = load_linear(*out.store, source, prefix + ".attn.out", linear_type, 128, 512, false); + item.ff_in.weight = out.store->load_tensor( + source, prefix + ".ff.in.weight", linear_type, {682, 128}); + item.ff_in.bias = out.store->load_f32_tensor(source, bias_prefix + "0.bias", {682}); + item.ff_out.weight = out.store->load_tensor( + source, prefix + ".ff.out.weight", linear_type, {128, 341}); + item.ff_out.bias = out.store->load_f32_tensor(source, bias_prefix + "2.bias", {128}); + out.perceiver.push_back(std::move(item)); + } + out.norm_gamma = out.store->load_f32_tensor( + source, "speaker_encoder.perceiver_sampler.norm.gamma", {128}); + out.quant_project.weight = out.store->load_tensor( + source, "quantizer.project_in.weight", linear_type, {6, 128}); + out.quant_project.bias = out.store->load_f32_tensor( + source, "speaker_encoder.quantizer.project_in.bias", {6}); + out.store->upload(); + return out; +} + +core::TensorValue conv1d( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const ConvWeights & weights) { + return modules::FastConv1dModule({ + weights.in_channels, weights.out_channels, weights.kernel, 1, + static_cast(weights.padding), static_cast(weights.dilation), true}, + modules::FastConv1dKind::MinittsFast1dIm2col) + .build(ctx, input, weights.value); +} + +core::TensorValue tdnn( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const TdnnWeights & weights) { + auto x = conv1d(ctx, input, weights.conv); + x = modules::ReluModule{}.build(ctx, x); + return modules::BatchNorm1dEvalModule({weights.conv.out_channels}) + .build(ctx, x, weights.norm); +} + +core::TensorValue se_res2( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const SeRes2Weights & weights) { + auto x = tdnn(ctx, input, weights.first); + core::TensorValue merged; + core::TensorValue previous; + for (int branch = 0; branch < 8; ++branch) { + auto chunk = modules::SliceModule({1, branch * kRes2Width, kRes2Width}) + .build(ctx, x); + core::TensorValue current; + if (branch == 7) { + current = chunk; + } else { + if (branch > 0) { + chunk = modules::AddModule{}.build(ctx, chunk, previous); + } + current = tdnn(ctx, chunk, weights.res2[static_cast(branch)]); + previous = current; + } + merged = merged.valid() + ? modules::ConcatModule({1}).build(ctx, merged, current) + : current; + } + x = tdnn(ctx, merged, weights.second); + auto pooled = modules::ReduceMeanModule({2}).build(ctx, x); + pooled = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, pooled); + auto gate = modules::LinearModule({512, 128, true}).build(ctx, pooled, weights.se_first); + gate = modules::ReluModule{}.build(ctx, gate); + gate = modules::LinearModule({128, 512, true}).build(ctx, gate, weights.se_second); + gate = modules::SigmoidModule{}.build(ctx, gate); + // [B, 1, C] -> [B, C, 1]. The singleton-axis transpose is only a + // strided view whose ggml dim 0 has a non-unit stride. CPU repeat requires + // dim 0 to be contiguous, so reshape the already contiguous values instead. + gate = core::reshape_tensor( + ctx, gate, core::TensorShape::from_dims({1, kEcapaChannels, 1})); + gate = modules::RepeatModule({x.shape}).build(ctx, gate); + return modules::AddModule{}.build( + ctx, input, modules::MulModule{}.build(ctx, x, gate)); +} + +core::TensorValue reshape_heads( + core::ModuleBuildContext & ctx, + const core::TensorValue & input) { + auto x = core::ensure_backend_addressable_layout(ctx, input); + x = core::reshape_tensor(ctx, x, core::TensorShape::from_dims( + {input.shape.dims[0], input.shape.dims[1], kPerceiverHeads, + kPerceiverInner / kPerceiverHeads})); + return modules::TransposeModule({{0, 2, 1, 3}, 4}).build(ctx, x); +} + +core::TensorValue scale( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + float value) { + return core::wrap_tensor( + ggml_scale(ctx.ggml, input.tensor, value), input.shape, GGML_TYPE_F32); +} + +core::TensorValue perceiver_attention( + core::ModuleBuildContext & ctx, + const core::TensorValue & latents, + const core::TensorValue & context, + const PerceiverLayerWeights & weights) { + const auto full_context = modules::ConcatModule({1}).build(ctx, latents, context); + auto q = modules::LinearModule({128, 512, false}).build(ctx, latents, weights.q); + auto kv = modules::LinearModule({128, 1024, false}).build(ctx, full_context, weights.kv); + auto k = modules::SliceModule({2, 0, 512}).build(ctx, kv); + auto v = modules::SliceModule({2, 512, 512}).build(ctx, kv); + q = reshape_heads(ctx, q); + k = reshape_heads(ctx, k); + v = reshape_heads(ctx, v); + auto scores = modules::MatMulModule{}.build( + ctx, q, modules::TransposeModule({{0, 1, 3, 2}, 4}).build(ctx, k)); + scores = scale(ctx, scores, 1.0F / std::sqrt(64.0F)); + auto attention = core::wrap_tensor( + ggml_soft_max(ctx.ggml, + core::ensure_backend_addressable_layout(ctx, scores).tensor), + scores.shape, GGML_TYPE_F32); + auto x = modules::MatMulModule{}.build(ctx, attention, v); + x = modules::TransposeModule({{0, 2, 1, 3}, 4}).build(ctx, x); + x = core::ensure_backend_addressable_layout(ctx, x); + x = core::reshape_tensor(ctx, x, core::TensorShape::from_dims({1, 32, 512})); + return modules::LinearModule({512, 128, false}).build(ctx, x, weights.out); +} + +core::TensorValue perceiver_ff( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const PerceiverLayerWeights & weights) { + auto x = modules::LinearModule({128, 682, true}).build(ctx, input, weights.ff_in); + auto value = modules::SliceModule({2, 0, 341}).build(ctx, x); + auto gate = modules::SliceModule({2, 341, 341}).build(ctx, x); + gate = modules::GeluModule({modules::GeluApproximation::ExactErf}).build(ctx, gate); + x = modules::MulModule{}.build(ctx, value, gate); + return modules::LinearModule({341, 128, true}).build(ctx, x, weights.ff_out); +} + +core::TensorValue rms_norm( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const core::TensorValue & gamma) { + auto x = modules::RMSNormModule({128, 1.0e-5F, true, false}) + .build(ctx, input, {gamma, std::nullopt}); + return x; +} + +core::TensorValue build_graph( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const SpeakerWeights & weights) { + auto x = tdnn(ctx, input, weights.input); + std::vector outputs; + for (const auto & block : weights.blocks) { + x = se_res2(ctx, x, block); + outputs.push_back(x); + } + x = modules::ConcatModule({1}).build(ctx, outputs[0], outputs[1]); + x = modules::ConcatModule({1}).build(ctx, x, outputs[2]); + x = modules::ReluModule{}.build(ctx, conv1d(ctx, x, weights.mfa)); + x = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, x); + x = modules::LinearModule({1536, 128, true}) + .build(ctx, x, weights.project_context); + auto latents = modules::RepeatModule({core::TensorShape::from_dims({1, 32, 128})}) + .build(ctx, weights.latents); + for (const auto & layer : weights.perceiver) { + latents = modules::AddModule{}.build( + ctx, latents, perceiver_attention(ctx, latents, x, layer)); + latents = modules::AddModule{}.build( + ctx, latents, perceiver_ff(ctx, latents, layer)); + } + latents = rms_norm(ctx, latents, weights.norm_gamma); + return modules::LinearModule({128, 6, true}) + .build(ctx, latents, weights.quant_project); +} + +std::vector prepare_reference(const runtime::AudioBuffer & audio) { + if (audio.sample_rate <= 0 || audio.channels <= 0 || audio.samples.empty()) { + throw std::runtime_error("MiraTTS requires non-empty reference audio"); + } + auto mono = engine::audio::convert_interleaved_audio_to_mono_linear_resampled( + audio.samples, audio.sample_rate, audio.channels, kSampleRate); + if (mono.empty()) { + throw std::runtime_error("MiraTTS reference audio contains no samples"); + } + // librosa.load(..., duration=8, sr=16000) in the upstream encoder limits + // the signal before volume normalization and six-second tiling/truncation. + mono.resize(std::min(mono.size(), 8 * kSampleRate)); + std::vector magnitudes; + magnitudes.reserve(mono.size()); + for (float sample : mono) magnitudes.push_back(std::abs(sample)); + std::sort(magnitudes.begin(), magnitudes.end()); + if (magnitudes.back() < 0.1F) { + const float divisor = std::max(magnitudes.back(), 1.0e-3F); + for (float & sample : mono) sample = sample / divisor * 0.1F; + for (float & magnitude : magnitudes) magnitude = magnitude / divisor * 0.1F; + } + const auto first_significant = std::upper_bound( + magnitudes.begin(), magnitudes.end(), 0.01F); + const size_t significant = static_cast(magnitudes.end() - first_significant); + if (significant > 10) { + const size_t begin = static_cast(0.90 * significant); + const size_t end = static_cast(0.99 * significant); + float sum = 0.0F; + for (size_t i = begin; i < end; ++i) { + sum += *(first_significant + static_cast(i)); + } + const float volume = sum / static_cast(std::max(1, end - begin)); + const float gain = std::clamp(0.2F / volume, 0.1F, 10.0F); + for (float & sample : mono) sample *= gain; + } + float peak = 0.0F; + for (float sample : mono) peak = std::max(peak, std::abs(sample)); + if (peak > 1.0F) { + for (float & sample : mono) sample /= peak; + } + std::vector fixed(static_cast(kReferenceSamples)); + for (int64_t i = 0; i < kReferenceSamples; ++i) { + fixed[static_cast(i)] = mono[static_cast(i) % mono.size()]; + } + return fixed; +} + +std::vector extract_mel(const runtime::AudioBuffer & audio, size_t threads) { + const auto waveform = prepare_reference(audio); + const engine::audio::STFTConfig stft{ + 1024, 320, 640, true, + engine::audio::STFTPadMode::Reflect, + // torch.hann_window defaults to periodic=true in upstream MiraTTS. + engine::audio::STFTFamily::Kokoro}; + const auto & window = engine::audio::get_cached_stft_window(stft); + const auto magnitude = engine::audio::STFT().compute_magnitude( + waveform, window, 1, kReferenceSamples, stft, threads); + const int64_t frames = magnitude.shape.at(2); + auto mel = engine::audio::MelFilterbank().compute( + magnitude.values, 1, 513, frames, + engine::audio::MelFilterbankConfig{ + 16000, 1024, 128, 10.0F, 8000.0F, true}); + // AudioTensor is [B, mel, frames], which is already the graph's BCT layout. + return std::move(mel.values); +} + +} // namespace + +struct MiraSpeakerEncoder::Impl { + Impl( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + size_t graph_context_bytes, + assets::TensorStorageType linear_type, + assets::TensorStorageType conv_type) + : execution(execution), + weights(load_weights( + assets, execution, weight_context_bytes, linear_type, conv_type)), + graph_context_bytes(graph_context_bytes) {} + + ~Impl() { + if (gallocr != nullptr) ggml_gallocr_free(gallocr); + } + + void ensure_graph(int64_t frames) { + if (ctx != nullptr && frames == graph_frames) return; + if (gallocr != nullptr) { + ggml_gallocr_free(gallocr); + gallocr = nullptr; + } + ctx.reset(); + ggml_init_params params{graph_context_bytes, nullptr, true}; + ctx.reset(ggml_init(params)); + if (!ctx) throw std::runtime_error("MiraTTS failed to initialize speaker graph"); + core::ModuleBuildContext build_ctx{ + ctx.get(), "mira_tts.speaker_encoder", execution.backend_type()}; + auto input = core::make_tensor( + build_ctx, GGML_TYPE_F32, + core::TensorShape::from_dims({1, 128, frames})); + input_tensor = input.tensor; + output_tensor = build_graph(build_ctx, input, weights).tensor; + ggml_set_output(output_tensor); + graph = ggml_new_graph_custom(ctx.get(), 65536, false); + ggml_build_forward_expand(graph, output_tensor); + gallocr = ggml_gallocr_new( + ggml_backend_get_default_buffer_type(execution.backend())); + if (gallocr == nullptr || !ggml_gallocr_reserve(gallocr, graph) || + !ggml_gallocr_alloc_graph(gallocr, graph)) { + throw std::runtime_error("MiraTTS failed to allocate speaker graph"); + } + graph_frames = frames; + } + + std::vector encode(const runtime::AudioBuffer & audio) { + auto mel = extract_mel(audio, static_cast( + std::max(1, execution.config().threads))); + if (mel.size() % 128 != 0) { + throw std::runtime_error("MiraTTS mel feature shape mismatch"); + } + const int64_t frames = static_cast(mel.size() / 128); + ensure_graph(frames); + ggml_backend_tensor_set( + input_tensor, mel.data(), 0, mel.size() * sizeof(float)); + if (ggml_backend_graph_compute(execution.backend(), graph) != GGML_STATUS_SUCCESS) { + throw std::runtime_error("MiraTTS speaker graph execution failed"); + } + std::vector projected(32 * 6); + ggml_backend_tensor_get( + output_tensor, projected.data(), 0, + projected.size() * sizeof(float)); + std::vector codes(32, 0); + for (int row = 0; row < 32; ++row) { + int32_t code = 0; + int32_t radix = 1; + for (int dim = 0; dim < 6; ++dim) { + const float value = projected[static_cast(row * 6 + dim)]; + const float bounded = std::tanh(value + 0.3461989760398865F) * + 1.501500129699707F - 0.5F; + const int32_t digit = static_cast(std::nearbyint(bounded)) + 2; + code += std::clamp(digit, 0, 3) * radix; + radix *= 4; + } + codes[static_cast(row)] = code; + } + return codes; + } + + core::ExecutionContext & execution; + SpeakerWeights weights; + size_t graph_context_bytes = 0; + int64_t graph_frames = 0; + std::unique_ptr ctx; + ggml_cgraph * graph = nullptr; + ggml_gallocr_t gallocr = nullptr; + ggml_tensor * input_tensor = nullptr; + ggml_tensor * output_tensor = nullptr; +}; + +MiraSpeakerEncoder::MiraSpeakerEncoder( + const MiraTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + size_t graph_context_bytes, + assets::TensorStorageType linear_storage_type, + assets::TensorStorageType conv_storage_type) + : impl_(std::make_unique( + assets, execution, weight_context_bytes, graph_context_bytes, + linear_storage_type, conv_storage_type)) {} + +MiraSpeakerEncoder::~MiraSpeakerEncoder() = default; + +std::vector MiraSpeakerEncoder::encode( + const runtime::AudioBuffer & reference_audio) { + return impl_->encode(reference_audio); +} + +} // namespace engine::community_models::mira_tts diff --git a/src/community_models/sanotts/assets.cpp b/src/community_models/sanotts/assets.cpp new file mode 100644 index 000000000..a20abb77f --- /dev/null +++ b/src/community_models/sanotts/assets.cpp @@ -0,0 +1,343 @@ +#include "engine/community_models/sanotts/assets.h" + +#include "engine/framework/io/json.h" +#include "engine/framework/io/config.h" +#include "engine/framework/model_spec/package.h" + +#include +#include +#include + +namespace engine::models::sanotts { +namespace { + +namespace json = engine::io::json; + +constexpr const char * kFamily = "sanotts"; + +SanoTtsConfig parse_nano_config(const engine::io::json::Value & root) { + SanoTtsConfig out; + out.voice = root.require("voice").as_string(); + out.vocab_size = root.require("vocab_size").as_i64(); + out.sample_rate = root.require("sample_rate").as_i64(); + out.hop_length = root.require("hop_length").as_i64(); + out.n_fft = root.require("n_fft").as_i64(); + out.mels = root.require("mels").as_i64(); + out.dim = root.require("dim").as_i64(); + out.blocks = root.require("blocks").as_i64(); + out.pw_hidden = root.require("pw_hidden").as_i64(); + out.noise_channels = root.require("noise_channels").as_i64(); + out.dw_kernel = root.require("dw_kernel").as_i64(); + out.embed_kernel = root.require("embed_kernel").as_i64(); + + const auto & duration = root.require("duration"); + out.duration_hidden = duration.require("hidden").as_i64(); + out.duration_depth = duration.require("depth").as_i64(); + out.duration_kernel = duration.require("kernel").as_i64(); + out.duration_max_tokens = duration.require("max_tokens").as_i64(); + out.duration_max_frames = duration.require("max_duration").as_i64(); + + const auto & acoustic = root.require("acoustic"); + out.acoustic_hidden = acoustic.require("hidden").as_i64(); + out.acoustic_token_depth = acoustic.require("token_depth").as_i64(); + out.acoustic_depth = acoustic.require("depth").as_i64(); + out.acoustic_kernel = acoustic.require("kernel").as_i64(); + + for (const auto & [label, value] : std::initializer_list>{ + {"sanoTTS dim", out.dim}, + {"sanoTTS blocks", out.blocks}, + {"sanoTTS pw_hidden", out.pw_hidden}, + {"sanoTTS duration hidden", out.duration_hidden}, + {"sanoTTS acoustic hidden", out.acoustic_hidden}, + {"sanoTTS sample_rate", out.sample_rate}, + }) { + engine::io::require_positive(value, label); + } + return out; +} + +/** + * Fail on a missing or wrongly-shaped tensor at load, not mid-graph. + * + * The decoder is noise-fed and ends in an iSTFT, so a weight that is present + * but wrong in shape tends to produce plausible-sounding audio rather than an + * obvious failure. Checking the whole inventory up front is what keeps a + * packaging mistake loud. + */ +void validate_nano_tensors(const SanoTtsAssets & assets) { + const auto & c = assets.config; + const auto & weights = *assets.weights; + + std::vector>> expected; + const auto conv = [&](const std::string & name, int64_t out_ch, int64_t in_ch, int64_t k) { + expected.emplace_back(name + ".weight", std::vector{out_ch, in_ch, k}); + expected.emplace_back(name + ".bias", std::vector{out_ch}); + }; + const auto linear = [&](const std::string & name, int64_t out_ch, int64_t in_ch) { + expected.emplace_back(name + ".weight", std::vector{out_ch, in_ch}); + expected.emplace_back(name + ".bias", std::vector{out_ch}); + }; + + expected.emplace_back("duration.embedding.weight", + std::vector{c.vocab_size, c.duration_hidden}); + conv("duration.input_proj", c.duration_hidden, c.duration_hidden + 3, 1); + for (int64_t b = 0; b < c.duration_depth; ++b) { + const std::string prefix = "duration.blocks." + std::to_string(b); + conv(prefix + ".net.0", c.duration_hidden, c.duration_hidden, c.duration_kernel); + conv(prefix + ".net.2", c.duration_hidden, c.duration_hidden, c.duration_kernel); + expected.emplace_back(prefix + ".scale", std::vector{1}); + } + conv("duration.output", 1, c.duration_hidden, 1); + + expected.emplace_back("acoustic.embedding.weight", + std::vector{c.vocab_size, c.acoustic_hidden}); + conv("acoustic.token_input_proj", c.acoustic_hidden, c.acoustic_hidden + 2, 1); + for (int64_t b = 0; b < c.acoustic_token_depth; ++b) { + const std::string prefix = "acoustic.token_blocks." + std::to_string(b); + conv(prefix + ".net.0", c.acoustic_hidden, c.acoustic_hidden, c.acoustic_kernel); + conv(prefix + ".net.2", c.acoustic_hidden, c.acoustic_hidden, c.acoustic_kernel); + expected.emplace_back(prefix + ".scale", std::vector{1}); + } + conv("acoustic.frame_input_proj", c.acoustic_hidden, c.acoustic_hidden + 3, 1); + for (int64_t b = 0; b < c.acoustic_depth; ++b) { + const std::string prefix = "acoustic.frame_blocks." + std::to_string(b); + conv(prefix + ".net.0", c.acoustic_hidden, c.acoustic_hidden, c.acoustic_kernel); + conv(prefix + ".net.2", c.acoustic_hidden, c.acoustic_hidden, c.acoustic_kernel); + expected.emplace_back(prefix + ".scale", std::vector{1}); + } + conv("acoustic.output", c.mels, c.acoustic_hidden, 1); + + conv("decoder.embed", c.dim, c.mels, c.embed_kernel); + conv("decoder.noise_adapter", c.dim, c.noise_channels, c.embed_kernel); + expected.emplace_back("decoder.norm.weight", std::vector{c.dim}); + expected.emplace_back("decoder.norm.bias", std::vector{c.dim}); + for (int64_t b = 0; b < c.blocks; ++b) { + const std::string prefix = "decoder.blocks." + std::to_string(b); + conv(prefix + ".dwconv", c.dim, 1, c.dw_kernel); // groups == dim + expected.emplace_back(prefix + ".norm.weight", std::vector{c.dim}); + expected.emplace_back(prefix + ".norm.bias", std::vector{c.dim}); + linear(prefix + ".pwconv1", c.pw_hidden, c.dim); + linear(prefix + ".pwconv2", c.dim, c.pw_hidden); + expected.emplace_back(prefix + ".gamma", std::vector{c.dim}); + } + expected.emplace_back("decoder.final_norm.weight", std::vector{c.dim}); + expected.emplace_back("decoder.final_norm.bias", std::vector{c.dim}); + linear("decoder.head", c.n_fft + 2, c.dim); + + for (const auto & [name, shape] : expected) { + if (!weights.has_tensor(name)) { + throw std::runtime_error("sanoTTS missing tensor: " + name); + } + assets::require_tensor_shape(weights, name, shape); + } +} + + +SanoTtsPiperConfig parse_piper_config(const engine::io::json::Value & root) { + SanoTtsPiperConfig out; + out.voice = root.require("voice").as_string(); + out.language = root.require("language").as_string(); + out.espeak_voice = root.require("espeak_voice").as_string(); + out.sample_rate = root.require("sample_rate").as_i64(); + out.duration_length_scale = + static_cast(root.require("duration_length_scale").as_f32()); + + const auto & duration = root.require("duration"); + out.duration_vocab = duration.require("vocab_size").as_i64(); + out.duration_hidden = duration.require("hidden").as_i64(); + out.duration_depth = duration.require("depth").as_i64(); + out.duration_kernel = duration.require("kernel").as_i64(); + out.duration_max_tokens = duration.require("max_tokens").as_i64(); + out.duration_max_frames = duration.require("max_duration").as_i64(); + + const auto & acoustic = root.require("acoustic"); + out.acoustic_vocab = acoustic.require("vocab_size").as_i64(); + out.acoustic_hidden = acoustic.require("hidden").as_i64(); + out.acoustic_depth = acoustic.require("depth").as_i64(); + out.acoustic_token_depth = acoustic.require("token_depth").as_i64(); + out.acoustic_kernel = acoustic.require("kernel").as_i64(); + out.acoustic_out_channels = acoustic.require("out_channels").as_i64(); + + const auto & decoder = root.require("decoder"); + const auto channels = decoder.require("channels").as_array(); + if (channels.size() != 4) { + throw std::runtime_error("sanoTTS piperlite decoder.channels must have 4 entries"); + } + for (size_t stage = 0; stage < 4; ++stage) { + out.channels[stage] = channels[stage].as_i64(); + } + for (size_t stage = 0; stage < 3; ++stage) { + const auto & branches = + decoder.require("stage" + std::to_string(stage) + "_branches").as_array(); + for (const auto & branch : branches) { + const int64_t index = branch.as_i64(); + if (index < 0 || index > 2) { + throw std::runtime_error("sanoTTS piperlite branch index out of range"); + } + out.stage_branches[stage].push_back(index); + } + if (out.stage_branches[stage].empty()) { + throw std::runtime_error("sanoTTS piperlite stage has no branches"); + } + } + out.post_filter_channels = decoder.require("post_filter_channels").as_i64(); + out.post_filter_layers = decoder.require("post_filter_layers").as_i64(); + out.post_filter_kernel = decoder.require("post_filter_kernel").as_i64(); + out.post_filter_scale = + static_cast(decoder.require("post_filter_scale").as_f32()); + + for (const auto & [symbol, id] : root.require("phoneme_id_map").as_object()) { + out.phoneme_id_map.emplace(symbol, static_cast(id.as_i64())); + } + if (out.phoneme_id_map.empty()) { + throw std::runtime_error("sanoTTS piperlite config has an empty phoneme_id_map"); + } + + for (const auto & [label, value] : std::initializer_list>{ + {"sanoTTS piperlite duration vocab", out.duration_vocab}, + {"sanoTTS piperlite duration hidden", out.duration_hidden}, + {"sanoTTS piperlite duration max_tokens", out.duration_max_tokens}, + {"sanoTTS piperlite acoustic vocab", out.acoustic_vocab}, + {"sanoTTS piperlite acoustic hidden", out.acoustic_hidden}, + {"sanoTTS piperlite latent channels", out.acoustic_out_channels}, + {"sanoTTS piperlite sample_rate", out.sample_rate}, + }) { + engine::io::require_positive(value, label); + } + if (out.duration_length_scale <= 0.0) { + throw std::runtime_error("sanoTTS piperlite duration_length_scale must be positive"); + } + return out; +} + +// PiperResidualBank geometry, fixed by the training code. +constexpr int64_t kBankKernels[3] = {3, 5, 7}; + +/** + * The piperlite inventory. Kernel sizes the reference reads from the tensors + * themselves (decoder pre/post convs) are validated as rank/channels only, + * by looking the actual kernel up from the source. + */ +void validate_piper_tensors(const SanoTtsAssets & assets) { + const auto & c = assets.piper; + const auto & weights = *assets.weights; + + std::vector>> expected; + const auto conv = [&](const std::string & name, int64_t out_ch, int64_t in_ch, int64_t k) { + expected.emplace_back(name + ".weight", std::vector{out_ch, in_ch, k}); + expected.emplace_back(name + ".bias", std::vector{out_ch}); + }; + const auto conv_any_kernel = [&](const std::string & name, int64_t out_ch, int64_t in_ch) { + const auto metadata = weights.require_metadata(name + ".weight"); + if (metadata.shape.size() != 3 || metadata.shape[0] != out_ch || + metadata.shape[1] != in_ch || metadata.shape[2] < 1 || + metadata.shape[2] % 2 == 0) { + throw std::runtime_error("sanoTTS unexpected shape for tensor: " + name + ".weight"); + } + expected.emplace_back(name + ".bias", std::vector{out_ch}); + }; + + expected.emplace_back("duration.embedding.weight", + std::vector{c.duration_vocab, c.duration_hidden}); + conv("duration.input_proj", c.duration_hidden, c.duration_hidden + 3, 1); + for (int64_t b = 0; b < c.duration_depth; ++b) { + const std::string prefix = "duration.blocks." + std::to_string(b); + conv(prefix + ".net.0", c.duration_hidden, c.duration_hidden, c.duration_kernel); + conv(prefix + ".net.2", c.duration_hidden, c.duration_hidden, c.duration_kernel); + expected.emplace_back(prefix + ".scale", std::vector{1}); + } + conv("duration.output", 1, c.duration_hidden, 1); + + expected.emplace_back("acoustic.embedding.weight", + std::vector{c.acoustic_vocab, c.acoustic_hidden}); + conv("acoustic.token_input_proj", c.acoustic_hidden, c.acoustic_hidden + 2, 1); + for (int64_t b = 0; b < c.acoustic_token_depth; ++b) { + const std::string prefix = "acoustic.token_blocks." + std::to_string(b); + conv(prefix + ".net.0", c.acoustic_hidden, c.acoustic_hidden, c.acoustic_kernel); + conv(prefix + ".net.2", c.acoustic_hidden, c.acoustic_hidden, c.acoustic_kernel); + expected.emplace_back(prefix + ".scale", std::vector{1}); + } + conv("acoustic.frame_input_proj", c.acoustic_hidden, c.acoustic_hidden + 3, 1); + for (int64_t b = 0; b < c.acoustic_depth; ++b) { + const std::string prefix = "acoustic.frame_blocks." + std::to_string(b); + conv(prefix + ".net.0", c.acoustic_hidden, c.acoustic_hidden, c.acoustic_kernel); + conv(prefix + ".net.2", c.acoustic_hidden, c.acoustic_hidden, c.acoustic_kernel); + expected.emplace_back(prefix + ".scale", std::vector{1}); + } + conv("acoustic.output", c.acoustic_out_channels, c.acoustic_hidden, 1); + + conv_any_kernel("decoder.pre", c.channels[0], c.acoustic_out_channels); + const int64_t up_kernels[3] = {16, 16, 8}; + for (size_t stage = 0; stage < 3; ++stage) { + const int64_t in_ch = c.channels[stage]; + const int64_t out_ch = c.channels[stage + 1]; + // ConvTranspose1d stores [in, out, K] + expected.emplace_back( + "decoder.up" + std::to_string(stage) + ".weight", + std::vector{in_ch, out_ch, up_kernels[stage]}); + expected.emplace_back( + "decoder.up" + std::to_string(stage) + ".bias", + std::vector{out_ch}); + for (const int64_t branch : c.stage_branches[stage]) { + const std::string prefix = "decoder.res" + std::to_string(stage) + ".0.blocks." + + std::to_string(branch); + conv(prefix + ".conv1", out_ch, out_ch, kBankKernels[branch]); + conv(prefix + ".conv2", out_ch, out_ch, kBankKernels[branch]); + } + } + conv_any_kernel("decoder.post", 1, c.channels[3]); + if (c.post_filter_channels > 0) { + conv_any_kernel("decoder.post_filter.in_conv", c.post_filter_channels, 1); + for (int64_t layer = 0; layer < c.post_filter_layers; ++layer) { + const std::string prefix = "decoder.post_filter.units." + std::to_string(layer); + // Unit conv kernels are a property of the checkpoint, not of + // post_filter_kernel (which sizes only the in/out convs). + conv_any_kernel(prefix + ".conv1", c.post_filter_channels, c.post_filter_channels); + conv_any_kernel(prefix + ".conv2", c.post_filter_channels, c.post_filter_channels); + expected.emplace_back(prefix + ".scale", std::vector{1}); + } + conv_any_kernel("decoder.post_filter.out_conv", 1, c.post_filter_channels); + } + + for (const auto & [name, shape] : expected) { + if (!weights.has_tensor(name)) { + throw std::runtime_error("sanoTTS missing tensor: " + name); + } + assets::require_tensor_shape(weights, name, shape); + } +} + +} // namespace + +std::shared_ptr load_sanotts_assets( + const std::filesystem::path & model_path) { + auto resources = engine::model_spec::load_resource_bundle_for_family(model_path, kFamily); + SanoTtsAssets out; + const auto root = resources.parse_json("config"); + const auto architecture = root.require("architecture").as_string(); + if (architecture != kFamily) { + throw std::runtime_error( + "sanoTTS config.json architecture is '" + architecture + "', expected 'sanotts'"); + } + const auto * graph = root.find("graph"); + const auto graph_name = graph == nullptr ? std::string("nano") : graph->as_string(); + if (graph_name == "nano") { + out.graph = SanoTtsGraph::Nano; + out.config = parse_nano_config(root); + } else if (graph_name == "piperlite") { + out.graph = SanoTtsGraph::Piperlite; + out.piper = parse_piper_config(root); + } else { + throw std::runtime_error("sanoTTS config.json has unknown graph '" + graph_name + "'"); + } + out.weights = resources.open_tensor_source("weights"); + out.resources = std::move(resources); + if (out.graph == SanoTtsGraph::Nano) { + validate_nano_tensors(out); + } else { + validate_piper_tensors(out); + } + return std::make_shared(std::move(out)); +} + +} // namespace engine::models::sanotts diff --git a/src/community_models/sanotts/frontend.cpp b/src/community_models/sanotts/frontend.cpp new file mode 100644 index 000000000..547eabeeb --- /dev/null +++ b/src/community_models/sanotts/frontend.cpp @@ -0,0 +1,1182 @@ +#include "engine/community_models/sanotts/frontend.h" + +#include "engine/framework/io/dynamic_library.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::sanotts { +namespace { + +using InitializeFn = int (*)(int, int, const char *, int); +using SetVoiceFn = int (*)(const char *); +using TextToPhonemesFn = const char * (*)(const void **, int, int); +using TerminateFn = int (*)(); + +constexpr int kEspeakSynchronous = 2; +constexpr int kEspeakCharsUtf8 = 1; +// IPA output (0x02), tie flag (bit 7), and U+0361 COMBINING DOUBLE INVERTED +// BREVE in bits 8..23 as the tie character -- exactly the phonemes_mode +// phonemizer computes, so the E2M diphthong patterns ("a͡ɪ" -> "I") can match. +constexpr int kEspeakPhonemesIpaTie = 0x02 | (0x01 << 7) | (0x0361 << 8); +// The piperlite voices phonemize with tie=False: IPA with '_' as the phoneme +// separator in bits 8..23, exactly phonemizer's untied phonemes_mode. +constexpr int kEspeakPhonemesIpaUnderscore = 0x02 | ('_' << 8); + +constexpr std::string_view kTieDefault = "͡"; // COMBINING DOUBLE INVERTED BREVE +constexpr std::string_view kTieMisaki = "^"; +constexpr std::string_view kSyllabic = "̩"; // COMBINING VERTICAL LINE BELOW +constexpr std::string_view kNasal = "̃"; // COMBINING TILDE + +/** + * The frozen 62-symbol inventory, exactly as the trained package records it. + * + * Ids are positional and contiguous from zero; 0..2 are , , + * and never appear in text. + */ +const std::unordered_map & vocabulary() { + static const std::unordered_map table = [] { + static constexpr std::array symbols = { + " ", "!", "\"", "(", ")", ",", ".", ":", ";", + "?", "A", "I", "O", "T", "W", "Y", "b", + "d", "f", "h", "i", "j", "k", "l", "m", + "n", "p", "s", "t", "u", "v", "w", "z", + "æ", "ð", "ŋ", "ɐ", "ɑ", + "ɔ", "ə", "ɛ", "ɜ", "ɡ", + "ɪ", "ɹ", "ʃ", "ʊ", "ʌ", + "ʒ", "ʤ", "ʧ", "ˈ", "ˌ", + "θ", "ᵊ", "ᵻ", "—", "“", + "”", + }; + std::unordered_map out; + int32_t id = 3; // 0..2 are the specials + for (const char * symbol : symbols) { + out.emplace(symbol, id++); + } + if (out.size() + 3 != 62) { + throw std::runtime_error("sanoTTS compiled symbol inventory is invalid"); + } + return out; + }(); + return table; +} + +void replace_all(std::string & value, std::string_view from, std::string_view to) { + if (from.empty()) { + return; + } + size_t position = 0; + while ((position = value.find(from, position)) != std::string::npos) { + value.replace(position, from.size(), to); + position += to.size(); + } +} + +/** + * misaki EspeakFallback's E2M rewrite, british=false. + * + * Order is load-bearing and matches the Python source, which sorts by + * descending key length: "e͡ɪ" must be tried before the bare "e", + * or every diphthong collapses to the wrong symbol. + */ +std::string apply_e2m(std::string ps) { + static const std::array, 20> kE2M = {{ + {"ʔˌn̩", "ʔn"}, + {"ʔn̩", "ʔn"}, + {"a^ɪ", "I"}, {"a^ʊ", "W"}, {"d^ʒ", "ʤ"}, + {"e^ɪ", "A"}, {"t^ʃ", "ʧ"}, {"ɔ^ɪ", "Y"}, + {"ə^l", "ᵊl"}, + {"ʲo", "jo"}, {"ʲə", "jə"}, {"e", "A"}, {"ʲ", ""}, + {"ɚ", "əɹ"}, {"r", "ɹ"}, {"x", "k"}, {"ç", "k"}, + {"ɐ", "ə"}, {"ɬ", "l"}, {"̃", ""}, + }}; + // trim + const auto not_space = [](unsigned char ch) { return std::isspace(ch) == 0; }; + ps.erase(ps.begin(), std::find_if(ps.begin(), ps.end(), not_space)); + ps.erase(std::find_if(ps.rbegin(), ps.rend(), not_space).base(), ps.end()); + + for (const auto & [from, to] : kE2M) { + replace_all(ps, from, to); + } + // re.sub(r'(\S)̩', r'ᵊ\1', ps) then drop any remaining syllabic + size_t at = 0; + while ((at = ps.find(kSyllabic, at)) != std::string::npos) { + size_t start = at; + while (start > 0 && (static_cast(ps[start - 1]) & 0xC0U) == 0x80U) { + --start; // walk back over the UTF-8 continuation bytes + } + if (start == at || std::isspace(static_cast(ps[start])) != 0) { + ps.erase(at, kSyllabic.size()); + continue; + } + ps.erase(at, kSyllabic.size()); + ps.insert(start, "ᵊ"); + at = start + std::strlen("ᵊ"); + } + replace_all(ps, "o^ʊ", "O"); + replace_all(ps, "ɜːɹ", "ɜɹ"); + replace_all(ps, "ɜː", "ɜɹ"); + replace_all(ps, "ɪə", "iə"); + replace_all(ps, "ː", ""); + replace_all(ps, "o", "ɔ"); // espeak < 1.52 + replace_all(ps, "ɾ", "T"); // version != '2.0' + replace_all(ps, "ʔ", "t"); + replace_all(ps, "^", ""); + return ps; +} + + +// ---- phonemizer-fork punctuation preserve/restore ------------------------ +// +// The reference front ends run eSpeak through phonemizer with +// preserve_punctuation=True: punctuation is cut out before phonemization and +// spliced back afterwards, so marks like "," and "." survive as tokens (the +// model was trained with them). This reproduces phonemizer's Punctuation +// class for the fixed marks and separator this model uses. + +constexpr std::string_view kPunctuationMarks = "!'(),-.:;?\""; + +bool is_punctuation_mark(char ch) { + return kPunctuationMarks.find(ch) != std::string_view::npos; +} + +bool is_ascii_space(char ch) { + return std::isspace(static_cast(ch)) != 0; +} + +struct MarkIndex { + std::string mark; + char position = 'I'; // B(egin), E(nd), I(nside), A(lone) +}; + +/** Matches of phonemizer's (\s*[marks]+\s*)+ -- maximal runs of spaces and + * marks that contain at least one mark. */ +std::vector> find_mark_runs(const std::string & line) { + std::vector> runs; + size_t i = 0; + while (i < line.size()) { + if (!is_ascii_space(line[i]) && !is_punctuation_mark(line[i])) { + ++i; + continue; + } + size_t end = i; + bool has_mark = false; + while (end < line.size() && + (is_ascii_space(line[end]) || is_punctuation_mark(line[end]))) { + has_mark = has_mark || is_punctuation_mark(line[end]); + ++end; + } + if (has_mark) { + runs.emplace_back(i, end); + } + i = end; + } + return runs; +} + +/** Punctuation._preserve_line: chunks without punctuation + ordered marks. + * Empty chunks are filtered, as Punctuation.preserve() does. */ +std::pair, std::vector> preserve_punctuation( + const std::string & line) { + const auto runs = find_mark_runs(line); + if (runs.empty()) { + return {{line}, {}}; + } + if (runs.size() == 1 && runs[0].first == 0 && runs[0].second == line.size()) { + return {{}, {{line, 'A'}}}; + } + std::vector marks; + marks.reserve(runs.size()); + for (size_t index = 0; index < runs.size(); ++index) { + const auto & run = runs[index]; + char position = 'I'; + if (index == 0 && run.first == 0) { + position = 'B'; + } else if (index + 1 == runs.size() && run.second == line.size()) { + position = 'E'; + } + marks.push_back({line.substr(run.first, run.second - run.first), position}); + } + // The find-first split dance, exactly as the Python does it. + std::vector chunks; + std::string rest = line; + for (const auto & mark : marks) { + const size_t at = rest.find(mark.mark); + if (at == std::string::npos) { + chunks.push_back(rest); + rest.clear(); + continue; + } + chunks.push_back(rest.substr(0, at)); + rest.erase(0, at + mark.mark.size()); + } + chunks.push_back(rest); + chunks.erase( + std::remove_if(chunks.begin(), chunks.end(), + [](const std::string & chunk) { return chunk.empty(); }), + chunks.end()); + return {std::move(chunks), std::move(marks)}; +} + +/** Punctuation.restore for a single line, sep.word = " ", strip = False. */ +std::string restore_punctuation( + std::vector chunk_phonemes, + std::vector marks) { + std::deque text(chunk_phonemes.begin(), chunk_phonemes.end()); + std::deque pending(marks.begin(), marks.end()); + std::vector out; + size_t pos = 0; + while (!text.empty() || !pending.empty()) { + if (pending.empty()) { + for (auto & line : text) { + if (line.empty() || line.back() != ' ') { + line.push_back(' '); + } + out.push_back(std::move(line)); + } + text.clear(); + } else if (text.empty()) { + std::string joined; + for (const auto & mark : pending) { + joined += mark.mark; + } + out.push_back(std::move(joined)); + pending.clear(); + } else if (pos == 0) { // single line: every mark carries index 0 + const auto current = pending.front(); + pending.pop_front(); + if (!text.front().empty() && text.front().back() == ' ') { + text.front().pop_back(); + } + const bool mark_ends_with_sep = + !current.mark.empty() && current.mark.back() == ' '; + if (current.position == 'B') { + text.front() = current.mark + text.front(); + } else if (current.position == 'E') { + out.push_back(text.front() + current.mark + (mark_ends_with_sep ? "" : " ")); + text.pop_front(); + ++pos; + } else if (current.position == 'A') { + out.push_back(current.mark + (mark_ends_with_sep ? "" : " ")); + ++pos; + } else { // 'I' + if (text.size() == 1) { + text.front() += current.mark; + } else { + auto first = std::move(text.front()); + text.pop_front(); + text.front() = first + current.mark + text.front(); + } + } + } else { + auto & line = text.front(); + if (line.empty() || line.back() != ' ') { + line.push_back(' '); + } + out.push_back(std::move(line)); + text.pop_front(); + ++pos; + } + } + // phonemizer would return these as separate lines and the reference + // takes the first; a single input line produces one in practice. + std::string result; + for (const auto & line : out) { + result += line; + } + return result; +} + +/** phonemizer EspeakBackend._postprocess_line with tie enabled, + * with_stress=True, strip=False, word separator " ", phone separator "". */ +std::string postprocess_espeak_line(std::string line) { + const auto not_space = [](unsigned char ch) { return std::isspace(ch) == 0; }; + line.erase(line.begin(), std::find_if(line.begin(), line.end(), not_space)); + line.erase(std::find_if(line.rbegin(), line.rend(), not_space).base(), line.end()); + std::replace(line.begin(), line.end(), '\n', ' '); + replace_all(line, " ", " "); + // espeak-ng#694: stray '_' separators at word ends + std::string squeezed; + squeezed.reserve(line.size()); + for (const char ch : line) { + if (ch == '_' && !squeezed.empty() && squeezed.back() == '_') { + continue; + } + squeezed.push_back(ch); + } + line = std::move(squeezed); + replace_all(line, "_ ", " "); + // language_switch="remove-flags": strip espeak's (lang) switch flags + if (line.find('(') != std::string::npos) { + std::string unflagged; + size_t at = 0; + while (at < line.size()) { + if (line[at] == '(') { + const size_t close = line.find(')', at + 1); + if (close != std::string::npos) { + at = close + 1; + continue; + } + } + unflagged.push_back(line[at++]); + } + line = std::move(unflagged); + } + if (line.empty()) { + return line; + } + // per word: strip, drop in-word '_' (phone separator is empty), append " " + std::string out; + size_t start = 0; + while (start <= line.size()) { + size_t end = line.find(' ', start); + if (end == std::string::npos) { + end = line.size(); + } + std::string word = line.substr(start, end - start); + word.erase(std::remove(word.begin(), word.end(), '_'), word.end()); + out += word; + out.push_back(' '); + if (end == line.size()) { + break; + } + start = end + 1; + } + return out; +} + +/** The reference front end's own line post-processing: rewrite eSpeak's tie + * to '^' per word so the E2M diphthong patterns can match. */ +std::string rewrite_ties_per_word(const std::string & line_in) { + std::string line = line_in; + const auto not_space = [](unsigned char ch) { return std::isspace(ch) == 0; }; + line.erase(line.begin(), std::find_if(line.begin(), line.end(), not_space)); + line.erase(std::find_if(line.rbegin(), line.rend(), not_space).base(), line.end()); + std::replace(line.begin(), line.end(), '\n', ' '); + replace_all(line, " ", " "); + if (line.empty()) { + return line; + } + std::string out; + size_t start = 0; + while (start <= line.size()) { + size_t end = line.find(' ', start); + if (end == std::string::npos) { + end = line.size(); + } + std::string word = line.substr(start, end - start); + replace_all(word, kTieDefault, kTieMisaki); + out += word; + out.push_back(' '); + if (end == line.size()) { + break; + } + start = end + 1; + } + return out; +} + + +// ---- NFD decomposition for the Piper codepoint mapping ------------------- +// +// piper normalizes the phonemized string with NFD before mapping each +// codepoint through phoneme_id_map. eSpeak-ng's IPA output for the shipped +// languages is already NFD-normal (probed over en/vi/id corpora), so this +// table only has to cover precomposed Latin letters that could slip through; +// characters outside it pass through unchanged, and an unmapped codepoint is +// skipped exactly as piper skips it. The framework's NFKD normalizer is NOT +// usable here: compatibility decomposition rewrites IPA modifier letters. + +// Canonical (NFD) decompositions for Latin-1/Extended and Vietnamese +// precomposed letters -- everything eSpeak-ng can plausibly emit in IPA +// mode. Generated from Python unicodedata (Unicode 15); canonical +// decompositions are stable across Unicode versions by policy. +struct NfdEntry { uint32_t composed; uint32_t parts[3]; }; +constexpr NfdEntry kNfdEntries[] = { + {0x00C0, {0x0041, 0x0300, 0x0000}}, + {0x00C1, {0x0041, 0x0301, 0x0000}}, + {0x00C2, {0x0041, 0x0302, 0x0000}}, + {0x00C3, {0x0041, 0x0303, 0x0000}}, + {0x00C4, {0x0041, 0x0308, 0x0000}}, + {0x00C5, {0x0041, 0x030A, 0x0000}}, + {0x00C7, {0x0043, 0x0327, 0x0000}}, + {0x00C8, {0x0045, 0x0300, 0x0000}}, + {0x00C9, {0x0045, 0x0301, 0x0000}}, + {0x00CA, {0x0045, 0x0302, 0x0000}}, + {0x00CB, {0x0045, 0x0308, 0x0000}}, + {0x00CC, {0x0049, 0x0300, 0x0000}}, + {0x00CD, {0x0049, 0x0301, 0x0000}}, + {0x00CE, {0x0049, 0x0302, 0x0000}}, + {0x00CF, {0x0049, 0x0308, 0x0000}}, + {0x00D1, {0x004E, 0x0303, 0x0000}}, + {0x00D2, {0x004F, 0x0300, 0x0000}}, + {0x00D3, {0x004F, 0x0301, 0x0000}}, + {0x00D4, {0x004F, 0x0302, 0x0000}}, + {0x00D5, {0x004F, 0x0303, 0x0000}}, + {0x00D6, {0x004F, 0x0308, 0x0000}}, + {0x00D9, {0x0055, 0x0300, 0x0000}}, + {0x00DA, {0x0055, 0x0301, 0x0000}}, + {0x00DB, {0x0055, 0x0302, 0x0000}}, + {0x00DC, {0x0055, 0x0308, 0x0000}}, + {0x00DD, {0x0059, 0x0301, 0x0000}}, + {0x00E0, {0x0061, 0x0300, 0x0000}}, + {0x00E1, {0x0061, 0x0301, 0x0000}}, + {0x00E2, {0x0061, 0x0302, 0x0000}}, + {0x00E3, {0x0061, 0x0303, 0x0000}}, + {0x00E4, {0x0061, 0x0308, 0x0000}}, + {0x00E5, {0x0061, 0x030A, 0x0000}}, + {0x00E7, {0x0063, 0x0327, 0x0000}}, + {0x00E8, {0x0065, 0x0300, 0x0000}}, + {0x00E9, {0x0065, 0x0301, 0x0000}}, + {0x00EA, {0x0065, 0x0302, 0x0000}}, + {0x00EB, {0x0065, 0x0308, 0x0000}}, + {0x00EC, {0x0069, 0x0300, 0x0000}}, + {0x00ED, {0x0069, 0x0301, 0x0000}}, + {0x00EE, {0x0069, 0x0302, 0x0000}}, + {0x00EF, {0x0069, 0x0308, 0x0000}}, + {0x00F1, {0x006E, 0x0303, 0x0000}}, + {0x00F2, {0x006F, 0x0300, 0x0000}}, + {0x00F3, {0x006F, 0x0301, 0x0000}}, + {0x00F4, {0x006F, 0x0302, 0x0000}}, + {0x00F5, {0x006F, 0x0303, 0x0000}}, + {0x00F6, {0x006F, 0x0308, 0x0000}}, + {0x00F9, {0x0075, 0x0300, 0x0000}}, + {0x00FA, {0x0075, 0x0301, 0x0000}}, + {0x00FB, {0x0075, 0x0302, 0x0000}}, + {0x00FC, {0x0075, 0x0308, 0x0000}}, + {0x00FD, {0x0079, 0x0301, 0x0000}}, + {0x00FF, {0x0079, 0x0308, 0x0000}}, + {0x0100, {0x0041, 0x0304, 0x0000}}, + {0x0101, {0x0061, 0x0304, 0x0000}}, + {0x0102, {0x0041, 0x0306, 0x0000}}, + {0x0103, {0x0061, 0x0306, 0x0000}}, + {0x0104, {0x0041, 0x0328, 0x0000}}, + {0x0105, {0x0061, 0x0328, 0x0000}}, + {0x0106, {0x0043, 0x0301, 0x0000}}, + {0x0107, {0x0063, 0x0301, 0x0000}}, + {0x0108, {0x0043, 0x0302, 0x0000}}, + {0x0109, {0x0063, 0x0302, 0x0000}}, + {0x010A, {0x0043, 0x0307, 0x0000}}, + {0x010B, {0x0063, 0x0307, 0x0000}}, + {0x010C, {0x0043, 0x030C, 0x0000}}, + {0x010D, {0x0063, 0x030C, 0x0000}}, + {0x010E, {0x0044, 0x030C, 0x0000}}, + {0x010F, {0x0064, 0x030C, 0x0000}}, + {0x0112, {0x0045, 0x0304, 0x0000}}, + {0x0113, {0x0065, 0x0304, 0x0000}}, + {0x0114, {0x0045, 0x0306, 0x0000}}, + {0x0115, {0x0065, 0x0306, 0x0000}}, + {0x0116, {0x0045, 0x0307, 0x0000}}, + {0x0117, {0x0065, 0x0307, 0x0000}}, + {0x0118, {0x0045, 0x0328, 0x0000}}, + {0x0119, {0x0065, 0x0328, 0x0000}}, + {0x011A, {0x0045, 0x030C, 0x0000}}, + {0x011B, {0x0065, 0x030C, 0x0000}}, + {0x011C, {0x0047, 0x0302, 0x0000}}, + {0x011D, {0x0067, 0x0302, 0x0000}}, + {0x011E, {0x0047, 0x0306, 0x0000}}, + {0x011F, {0x0067, 0x0306, 0x0000}}, + {0x0120, {0x0047, 0x0307, 0x0000}}, + {0x0121, {0x0067, 0x0307, 0x0000}}, + {0x0122, {0x0047, 0x0327, 0x0000}}, + {0x0123, {0x0067, 0x0327, 0x0000}}, + {0x0124, {0x0048, 0x0302, 0x0000}}, + {0x0125, {0x0068, 0x0302, 0x0000}}, + {0x0128, {0x0049, 0x0303, 0x0000}}, + {0x0129, {0x0069, 0x0303, 0x0000}}, + {0x012A, {0x0049, 0x0304, 0x0000}}, + {0x012B, {0x0069, 0x0304, 0x0000}}, + {0x012C, {0x0049, 0x0306, 0x0000}}, + {0x012D, {0x0069, 0x0306, 0x0000}}, + {0x012E, {0x0049, 0x0328, 0x0000}}, + {0x012F, {0x0069, 0x0328, 0x0000}}, + {0x0130, {0x0049, 0x0307, 0x0000}}, + {0x0134, {0x004A, 0x0302, 0x0000}}, + {0x0135, {0x006A, 0x0302, 0x0000}}, + {0x0136, {0x004B, 0x0327, 0x0000}}, + {0x0137, {0x006B, 0x0327, 0x0000}}, + {0x0139, {0x004C, 0x0301, 0x0000}}, + {0x013A, {0x006C, 0x0301, 0x0000}}, + {0x013B, {0x004C, 0x0327, 0x0000}}, + {0x013C, {0x006C, 0x0327, 0x0000}}, + {0x013D, {0x004C, 0x030C, 0x0000}}, + {0x013E, {0x006C, 0x030C, 0x0000}}, + {0x0143, {0x004E, 0x0301, 0x0000}}, + {0x0144, {0x006E, 0x0301, 0x0000}}, + {0x0145, {0x004E, 0x0327, 0x0000}}, + {0x0146, {0x006E, 0x0327, 0x0000}}, + {0x0147, {0x004E, 0x030C, 0x0000}}, + {0x0148, {0x006E, 0x030C, 0x0000}}, + {0x014C, {0x004F, 0x0304, 0x0000}}, + {0x014D, {0x006F, 0x0304, 0x0000}}, + {0x014E, {0x004F, 0x0306, 0x0000}}, + {0x014F, {0x006F, 0x0306, 0x0000}}, + {0x0150, {0x004F, 0x030B, 0x0000}}, + {0x0151, {0x006F, 0x030B, 0x0000}}, + {0x0154, {0x0052, 0x0301, 0x0000}}, + {0x0155, {0x0072, 0x0301, 0x0000}}, + {0x0156, {0x0052, 0x0327, 0x0000}}, + {0x0157, {0x0072, 0x0327, 0x0000}}, + {0x0158, {0x0052, 0x030C, 0x0000}}, + {0x0159, {0x0072, 0x030C, 0x0000}}, + {0x015A, {0x0053, 0x0301, 0x0000}}, + {0x015B, {0x0073, 0x0301, 0x0000}}, + {0x015C, {0x0053, 0x0302, 0x0000}}, + {0x015D, {0x0073, 0x0302, 0x0000}}, + {0x015E, {0x0053, 0x0327, 0x0000}}, + {0x015F, {0x0073, 0x0327, 0x0000}}, + {0x0160, {0x0053, 0x030C, 0x0000}}, + {0x0161, {0x0073, 0x030C, 0x0000}}, + {0x0162, {0x0054, 0x0327, 0x0000}}, + {0x0163, {0x0074, 0x0327, 0x0000}}, + {0x0164, {0x0054, 0x030C, 0x0000}}, + {0x0165, {0x0074, 0x030C, 0x0000}}, + {0x0168, {0x0055, 0x0303, 0x0000}}, + {0x0169, {0x0075, 0x0303, 0x0000}}, + {0x016A, {0x0055, 0x0304, 0x0000}}, + {0x016B, {0x0075, 0x0304, 0x0000}}, + {0x016C, {0x0055, 0x0306, 0x0000}}, + {0x016D, {0x0075, 0x0306, 0x0000}}, + {0x016E, {0x0055, 0x030A, 0x0000}}, + {0x016F, {0x0075, 0x030A, 0x0000}}, + {0x0170, {0x0055, 0x030B, 0x0000}}, + {0x0171, {0x0075, 0x030B, 0x0000}}, + {0x0172, {0x0055, 0x0328, 0x0000}}, + {0x0173, {0x0075, 0x0328, 0x0000}}, + {0x0174, {0x0057, 0x0302, 0x0000}}, + {0x0175, {0x0077, 0x0302, 0x0000}}, + {0x0176, {0x0059, 0x0302, 0x0000}}, + {0x0177, {0x0079, 0x0302, 0x0000}}, + {0x0178, {0x0059, 0x0308, 0x0000}}, + {0x0179, {0x005A, 0x0301, 0x0000}}, + {0x017A, {0x007A, 0x0301, 0x0000}}, + {0x017B, {0x005A, 0x0307, 0x0000}}, + {0x017C, {0x007A, 0x0307, 0x0000}}, + {0x017D, {0x005A, 0x030C, 0x0000}}, + {0x017E, {0x007A, 0x030C, 0x0000}}, + {0x1E00, {0x0041, 0x0325, 0x0000}}, + {0x1E01, {0x0061, 0x0325, 0x0000}}, + {0x1E02, {0x0042, 0x0307, 0x0000}}, + {0x1E03, {0x0062, 0x0307, 0x0000}}, + {0x1E04, {0x0042, 0x0323, 0x0000}}, + {0x1E05, {0x0062, 0x0323, 0x0000}}, + {0x1E06, {0x0042, 0x0331, 0x0000}}, + {0x1E07, {0x0062, 0x0331, 0x0000}}, + {0x1E08, {0x0043, 0x0327, 0x0301}}, + {0x1E09, {0x0063, 0x0327, 0x0301}}, + {0x1E0A, {0x0044, 0x0307, 0x0000}}, + {0x1E0B, {0x0064, 0x0307, 0x0000}}, + {0x1E0C, {0x0044, 0x0323, 0x0000}}, + {0x1E0D, {0x0064, 0x0323, 0x0000}}, + {0x1E0E, {0x0044, 0x0331, 0x0000}}, + {0x1E0F, {0x0064, 0x0331, 0x0000}}, + {0x1E10, {0x0044, 0x0327, 0x0000}}, + {0x1E11, {0x0064, 0x0327, 0x0000}}, + {0x1E12, {0x0044, 0x032D, 0x0000}}, + {0x1E13, {0x0064, 0x032D, 0x0000}}, + {0x1E14, {0x0045, 0x0304, 0x0300}}, + {0x1E15, {0x0065, 0x0304, 0x0300}}, + {0x1E16, {0x0045, 0x0304, 0x0301}}, + {0x1E17, {0x0065, 0x0304, 0x0301}}, + {0x1E18, {0x0045, 0x032D, 0x0000}}, + {0x1E19, {0x0065, 0x032D, 0x0000}}, + {0x1E1A, {0x0045, 0x0330, 0x0000}}, + {0x1E1B, {0x0065, 0x0330, 0x0000}}, + {0x1E1C, {0x0045, 0x0327, 0x0306}}, + {0x1E1D, {0x0065, 0x0327, 0x0306}}, + {0x1E1E, {0x0046, 0x0307, 0x0000}}, + {0x1E1F, {0x0066, 0x0307, 0x0000}}, + {0x1E20, {0x0047, 0x0304, 0x0000}}, + {0x1E21, {0x0067, 0x0304, 0x0000}}, + {0x1E22, {0x0048, 0x0307, 0x0000}}, + {0x1E23, {0x0068, 0x0307, 0x0000}}, + {0x1E24, {0x0048, 0x0323, 0x0000}}, + {0x1E25, {0x0068, 0x0323, 0x0000}}, + {0x1E26, {0x0048, 0x0308, 0x0000}}, + {0x1E27, {0x0068, 0x0308, 0x0000}}, + {0x1E28, {0x0048, 0x0327, 0x0000}}, + {0x1E29, {0x0068, 0x0327, 0x0000}}, + {0x1E2A, {0x0048, 0x032E, 0x0000}}, + {0x1E2B, {0x0068, 0x032E, 0x0000}}, + {0x1E2C, {0x0049, 0x0330, 0x0000}}, + {0x1E2D, {0x0069, 0x0330, 0x0000}}, + {0x1E2E, {0x0049, 0x0308, 0x0301}}, + {0x1E2F, {0x0069, 0x0308, 0x0301}}, + {0x1E30, {0x004B, 0x0301, 0x0000}}, + {0x1E31, {0x006B, 0x0301, 0x0000}}, + {0x1E32, {0x004B, 0x0323, 0x0000}}, + {0x1E33, {0x006B, 0x0323, 0x0000}}, + {0x1E34, {0x004B, 0x0331, 0x0000}}, + {0x1E35, {0x006B, 0x0331, 0x0000}}, + {0x1E36, {0x004C, 0x0323, 0x0000}}, + {0x1E37, {0x006C, 0x0323, 0x0000}}, + {0x1E38, {0x004C, 0x0323, 0x0304}}, + {0x1E39, {0x006C, 0x0323, 0x0304}}, + {0x1E3A, {0x004C, 0x0331, 0x0000}}, + {0x1E3B, {0x006C, 0x0331, 0x0000}}, + {0x1E3C, {0x004C, 0x032D, 0x0000}}, + {0x1E3D, {0x006C, 0x032D, 0x0000}}, + {0x1E3E, {0x004D, 0x0301, 0x0000}}, + {0x1E3F, {0x006D, 0x0301, 0x0000}}, + {0x1E40, {0x004D, 0x0307, 0x0000}}, + {0x1E41, {0x006D, 0x0307, 0x0000}}, + {0x1E42, {0x004D, 0x0323, 0x0000}}, + {0x1E43, {0x006D, 0x0323, 0x0000}}, + {0x1E44, {0x004E, 0x0307, 0x0000}}, + {0x1E45, {0x006E, 0x0307, 0x0000}}, + {0x1E46, {0x004E, 0x0323, 0x0000}}, + {0x1E47, {0x006E, 0x0323, 0x0000}}, + {0x1E48, {0x004E, 0x0331, 0x0000}}, + {0x1E49, {0x006E, 0x0331, 0x0000}}, + {0x1E4A, {0x004E, 0x032D, 0x0000}}, + {0x1E4B, {0x006E, 0x032D, 0x0000}}, + {0x1E4C, {0x004F, 0x0303, 0x0301}}, + {0x1E4D, {0x006F, 0x0303, 0x0301}}, + {0x1E4E, {0x004F, 0x0303, 0x0308}}, + {0x1E4F, {0x006F, 0x0303, 0x0308}}, + {0x1E50, {0x004F, 0x0304, 0x0300}}, + {0x1E51, {0x006F, 0x0304, 0x0300}}, + {0x1E52, {0x004F, 0x0304, 0x0301}}, + {0x1E53, {0x006F, 0x0304, 0x0301}}, + {0x1E54, {0x0050, 0x0301, 0x0000}}, + {0x1E55, {0x0070, 0x0301, 0x0000}}, + {0x1E56, {0x0050, 0x0307, 0x0000}}, + {0x1E57, {0x0070, 0x0307, 0x0000}}, + {0x1E58, {0x0052, 0x0307, 0x0000}}, + {0x1E59, {0x0072, 0x0307, 0x0000}}, + {0x1E5A, {0x0052, 0x0323, 0x0000}}, + {0x1E5B, {0x0072, 0x0323, 0x0000}}, + {0x1E5C, {0x0052, 0x0323, 0x0304}}, + {0x1E5D, {0x0072, 0x0323, 0x0304}}, + {0x1E5E, {0x0052, 0x0331, 0x0000}}, + {0x1E5F, {0x0072, 0x0331, 0x0000}}, + {0x1E60, {0x0053, 0x0307, 0x0000}}, + {0x1E61, {0x0073, 0x0307, 0x0000}}, + {0x1E62, {0x0053, 0x0323, 0x0000}}, + {0x1E63, {0x0073, 0x0323, 0x0000}}, + {0x1E64, {0x0053, 0x0301, 0x0307}}, + {0x1E65, {0x0073, 0x0301, 0x0307}}, + {0x1E66, {0x0053, 0x030C, 0x0307}}, + {0x1E67, {0x0073, 0x030C, 0x0307}}, + {0x1E68, {0x0053, 0x0323, 0x0307}}, + {0x1E69, {0x0073, 0x0323, 0x0307}}, + {0x1E6A, {0x0054, 0x0307, 0x0000}}, + {0x1E6B, {0x0074, 0x0307, 0x0000}}, + {0x1E6C, {0x0054, 0x0323, 0x0000}}, + {0x1E6D, {0x0074, 0x0323, 0x0000}}, + {0x1E6E, {0x0054, 0x0331, 0x0000}}, + {0x1E6F, {0x0074, 0x0331, 0x0000}}, + {0x1E70, {0x0054, 0x032D, 0x0000}}, + {0x1E71, {0x0074, 0x032D, 0x0000}}, + {0x1E72, {0x0055, 0x0324, 0x0000}}, + {0x1E73, {0x0075, 0x0324, 0x0000}}, + {0x1E74, {0x0055, 0x0330, 0x0000}}, + {0x1E75, {0x0075, 0x0330, 0x0000}}, + {0x1E76, {0x0055, 0x032D, 0x0000}}, + {0x1E77, {0x0075, 0x032D, 0x0000}}, + {0x1E78, {0x0055, 0x0303, 0x0301}}, + {0x1E79, {0x0075, 0x0303, 0x0301}}, + {0x1E7A, {0x0055, 0x0304, 0x0308}}, + {0x1E7B, {0x0075, 0x0304, 0x0308}}, + {0x1E7C, {0x0056, 0x0303, 0x0000}}, + {0x1E7D, {0x0076, 0x0303, 0x0000}}, + {0x1E7E, {0x0056, 0x0323, 0x0000}}, + {0x1E7F, {0x0076, 0x0323, 0x0000}}, + {0x1E80, {0x0057, 0x0300, 0x0000}}, + {0x1E81, {0x0077, 0x0300, 0x0000}}, + {0x1E82, {0x0057, 0x0301, 0x0000}}, + {0x1E83, {0x0077, 0x0301, 0x0000}}, + {0x1E84, {0x0057, 0x0308, 0x0000}}, + {0x1E85, {0x0077, 0x0308, 0x0000}}, + {0x1E86, {0x0057, 0x0307, 0x0000}}, + {0x1E87, {0x0077, 0x0307, 0x0000}}, + {0x1E88, {0x0057, 0x0323, 0x0000}}, + {0x1E89, {0x0077, 0x0323, 0x0000}}, + {0x1E8A, {0x0058, 0x0307, 0x0000}}, + {0x1E8B, {0x0078, 0x0307, 0x0000}}, + {0x1E8C, {0x0058, 0x0308, 0x0000}}, + {0x1E8D, {0x0078, 0x0308, 0x0000}}, + {0x1E8E, {0x0059, 0x0307, 0x0000}}, + {0x1E8F, {0x0079, 0x0307, 0x0000}}, + {0x1E90, {0x005A, 0x0302, 0x0000}}, + {0x1E91, {0x007A, 0x0302, 0x0000}}, + {0x1E92, {0x005A, 0x0323, 0x0000}}, + {0x1E93, {0x007A, 0x0323, 0x0000}}, + {0x1E94, {0x005A, 0x0331, 0x0000}}, + {0x1E95, {0x007A, 0x0331, 0x0000}}, + {0x1E96, {0x0068, 0x0331, 0x0000}}, + {0x1E97, {0x0074, 0x0308, 0x0000}}, + {0x1E98, {0x0077, 0x030A, 0x0000}}, + {0x1E99, {0x0079, 0x030A, 0x0000}}, + {0x1E9B, {0x017F, 0x0307, 0x0000}}, + {0x1EA0, {0x0041, 0x0323, 0x0000}}, + {0x1EA1, {0x0061, 0x0323, 0x0000}}, + {0x1EA2, {0x0041, 0x0309, 0x0000}}, + {0x1EA3, {0x0061, 0x0309, 0x0000}}, + {0x1EA4, {0x0041, 0x0302, 0x0301}}, + {0x1EA5, {0x0061, 0x0302, 0x0301}}, + {0x1EA6, {0x0041, 0x0302, 0x0300}}, + {0x1EA7, {0x0061, 0x0302, 0x0300}}, + {0x1EA8, {0x0041, 0x0302, 0x0309}}, + {0x1EA9, {0x0061, 0x0302, 0x0309}}, + {0x1EAA, {0x0041, 0x0302, 0x0303}}, + {0x1EAB, {0x0061, 0x0302, 0x0303}}, + {0x1EAC, {0x0041, 0x0323, 0x0302}}, + {0x1EAD, {0x0061, 0x0323, 0x0302}}, + {0x1EAE, {0x0041, 0x0306, 0x0301}}, + {0x1EAF, {0x0061, 0x0306, 0x0301}}, + {0x1EB0, {0x0041, 0x0306, 0x0300}}, + {0x1EB1, {0x0061, 0x0306, 0x0300}}, + {0x1EB2, {0x0041, 0x0306, 0x0309}}, + {0x1EB3, {0x0061, 0x0306, 0x0309}}, + {0x1EB4, {0x0041, 0x0306, 0x0303}}, + {0x1EB5, {0x0061, 0x0306, 0x0303}}, + {0x1EB6, {0x0041, 0x0323, 0x0306}}, + {0x1EB7, {0x0061, 0x0323, 0x0306}}, + {0x1EB8, {0x0045, 0x0323, 0x0000}}, + {0x1EB9, {0x0065, 0x0323, 0x0000}}, + {0x1EBA, {0x0045, 0x0309, 0x0000}}, + {0x1EBB, {0x0065, 0x0309, 0x0000}}, + {0x1EBC, {0x0045, 0x0303, 0x0000}}, + {0x1EBD, {0x0065, 0x0303, 0x0000}}, + {0x1EBE, {0x0045, 0x0302, 0x0301}}, + {0x1EBF, {0x0065, 0x0302, 0x0301}}, + {0x1EC0, {0x0045, 0x0302, 0x0300}}, + {0x1EC1, {0x0065, 0x0302, 0x0300}}, + {0x1EC2, {0x0045, 0x0302, 0x0309}}, + {0x1EC3, {0x0065, 0x0302, 0x0309}}, + {0x1EC4, {0x0045, 0x0302, 0x0303}}, + {0x1EC5, {0x0065, 0x0302, 0x0303}}, + {0x1EC6, {0x0045, 0x0323, 0x0302}}, + {0x1EC7, {0x0065, 0x0323, 0x0302}}, + {0x1EC8, {0x0049, 0x0309, 0x0000}}, + {0x1EC9, {0x0069, 0x0309, 0x0000}}, + {0x1ECA, {0x0049, 0x0323, 0x0000}}, + {0x1ECB, {0x0069, 0x0323, 0x0000}}, + {0x1ECC, {0x004F, 0x0323, 0x0000}}, + {0x1ECD, {0x006F, 0x0323, 0x0000}}, + {0x1ECE, {0x004F, 0x0309, 0x0000}}, + {0x1ECF, {0x006F, 0x0309, 0x0000}}, + {0x1ED0, {0x004F, 0x0302, 0x0301}}, + {0x1ED1, {0x006F, 0x0302, 0x0301}}, + {0x1ED2, {0x004F, 0x0302, 0x0300}}, + {0x1ED3, {0x006F, 0x0302, 0x0300}}, + {0x1ED4, {0x004F, 0x0302, 0x0309}}, + {0x1ED5, {0x006F, 0x0302, 0x0309}}, + {0x1ED6, {0x004F, 0x0302, 0x0303}}, + {0x1ED7, {0x006F, 0x0302, 0x0303}}, + {0x1ED8, {0x004F, 0x0323, 0x0302}}, + {0x1ED9, {0x006F, 0x0323, 0x0302}}, + {0x1EDA, {0x004F, 0x031B, 0x0301}}, + {0x1EDB, {0x006F, 0x031B, 0x0301}}, + {0x1EDC, {0x004F, 0x031B, 0x0300}}, + {0x1EDD, {0x006F, 0x031B, 0x0300}}, + {0x1EDE, {0x004F, 0x031B, 0x0309}}, + {0x1EDF, {0x006F, 0x031B, 0x0309}}, + {0x1EE0, {0x004F, 0x031B, 0x0303}}, + {0x1EE1, {0x006F, 0x031B, 0x0303}}, + {0x1EE2, {0x004F, 0x031B, 0x0323}}, + {0x1EE3, {0x006F, 0x031B, 0x0323}}, + {0x1EE4, {0x0055, 0x0323, 0x0000}}, + {0x1EE5, {0x0075, 0x0323, 0x0000}}, + {0x1EE6, {0x0055, 0x0309, 0x0000}}, + {0x1EE7, {0x0075, 0x0309, 0x0000}}, + {0x1EE8, {0x0055, 0x031B, 0x0301}}, + {0x1EE9, {0x0075, 0x031B, 0x0301}}, + {0x1EEA, {0x0055, 0x031B, 0x0300}}, + {0x1EEB, {0x0075, 0x031B, 0x0300}}, + {0x1EEC, {0x0055, 0x031B, 0x0309}}, + {0x1EED, {0x0075, 0x031B, 0x0309}}, + {0x1EEE, {0x0055, 0x031B, 0x0303}}, + {0x1EEF, {0x0075, 0x031B, 0x0303}}, + {0x1EF0, {0x0055, 0x031B, 0x0323}}, + {0x1EF1, {0x0075, 0x031B, 0x0323}}, + {0x1EF2, {0x0059, 0x0300, 0x0000}}, + {0x1EF3, {0x0079, 0x0300, 0x0000}}, + {0x1EF4, {0x0059, 0x0323, 0x0000}}, + {0x1EF5, {0x0079, 0x0323, 0x0000}}, + {0x1EF6, {0x0059, 0x0309, 0x0000}}, + {0x1EF7, {0x0079, 0x0309, 0x0000}}, + {0x1EF8, {0x0059, 0x0303, 0x0000}}, + {0x1EF9, {0x0079, 0x0303, 0x0000}}, +}; + + +void append_codepoint_utf8(uint32_t codepoint, std::string & out) { + if (codepoint < 0x80U) { + out.push_back(static_cast(codepoint)); + } else if (codepoint < 0x800U) { + out.push_back(static_cast(0xC0U | (codepoint >> 6))); + out.push_back(static_cast(0x80U | (codepoint & 0x3FU))); + } else if (codepoint < 0x10000U) { + out.push_back(static_cast(0xE0U | (codepoint >> 12))); + out.push_back(static_cast(0x80U | ((codepoint >> 6) & 0x3FU))); + out.push_back(static_cast(0x80U | (codepoint & 0x3FU))); + } else { + out.push_back(static_cast(0xF0U | (codepoint >> 18))); + out.push_back(static_cast(0x80U | ((codepoint >> 12) & 0x3FU))); + out.push_back(static_cast(0x80U | ((codepoint >> 6) & 0x3FU))); + out.push_back(static_cast(0x80U | (codepoint & 0x3FU))); + } +} + +/** Iterates whole UTF-8 codepoints; invalid lead bytes pass through as one. */ +size_t utf8_sequence_length(const std::string & text, size_t at) { + const auto lead = static_cast(text[at]); + size_t len = 1; + if ((lead & 0xF8U) == 0xF0U) { len = 4; } + else if ((lead & 0xF0U) == 0xE0U) { len = 3; } + else if ((lead & 0xE0U) == 0xC0U) { len = 2; } + return std::min(len, text.size() - at); +} + +uint32_t decode_codepoint_utf8(const std::string & text, size_t at, size_t len) { + const auto lead = static_cast(text[at]); + if (len == 1) { + return lead; + } + uint32_t value = lead & (0x7FU >> len); + for (size_t i = 1; i < len; ++i) { + value = (value << 6) | (static_cast(text[at + i]) & 0x3FU); + } + return value; +} + +std::string nfd_decompose(const std::string & text) { + std::string out; + out.reserve(text.size()); + for (size_t i = 0; i < text.size();) { + const size_t len = utf8_sequence_length(text, i); + const uint32_t codepoint = decode_codepoint_utf8(text, i, len); + const auto * entry = std::lower_bound( + std::begin(kNfdEntries), + std::end(kNfdEntries), + codepoint, + [](const NfdEntry & candidate, uint32_t value) { + return candidate.composed < value; + }); + if (entry != std::end(kNfdEntries) && entry->composed == codepoint) { + for (const uint32_t part : entry->parts) { + if (part != 0) { + append_codepoint_utf8(part, out); + } + } + } else { + out.append(text, i, len); + } + i += len; + } + return out; +} + +struct EspeakApi { + io::DynamicLibraryHandle library = nullptr; + InitializeFn initialize = nullptr; + SetVoiceFn set_voice = nullptr; + TextToPhonemesFn text_to_phonemes = nullptr; + TerminateFn terminate = nullptr; + mutable std::mutex call_mutex; + + EspeakApi(const std::filesystem::path & requested_library, + const std::filesystem::path & requested_data, + const std::string & voice) { + if (!requested_library.empty() && + !std::filesystem::is_regular_file(requested_library)) { + throw std::runtime_error( + "sanoTTS eSpeak-ng library does not exist: " + requested_library.string()); + } + if (!requested_data.empty() && + (!std::filesystem::is_directory(requested_data) || + !std::filesystem::is_regular_file(requested_data / "phontab"))) { + throw std::runtime_error( + "sanoTTS eSpeak-ng data path is invalid; expected the espeak-ng-data " + "directory containing phontab: " + requested_data.string()); + } + if (!requested_library.empty()) { + library = io::open_dynamic_library(requested_library.string()); + } else { + library = io::open_dynamic_library({ +#ifdef _WIN32 + "espeak-ng.dll", "libespeak-ng.dll", +#elif defined(__APPLE__) + "libespeak-ng.dylib", "libespeak-ng.1.dylib", +#else + "libespeak-ng.so.1", "libespeak-ng.so", +#endif + }); + } + if (library == nullptr) { + throw std::runtime_error( + "sanoTTS could not load eSpeak-ng. Install it (apt install espeak-ng, " + "brew install espeak-ng) or pass " + "--session-option sanotts.espeak_library_path=/path/to/libespeak-ng.so"); + } + initialize = reinterpret_cast( + io::dynamic_library_symbol(library, "espeak_Initialize")); + set_voice = reinterpret_cast( + io::dynamic_library_symbol(library, "espeak_SetVoiceByName")); + text_to_phonemes = reinterpret_cast( + io::dynamic_library_symbol(library, "espeak_TextToPhonemes")); + terminate = reinterpret_cast( + io::dynamic_library_symbol(library, "espeak_Terminate")); + if (initialize == nullptr || set_voice == nullptr || text_to_phonemes == nullptr) { + throw std::runtime_error("sanoTTS eSpeak-ng is missing required symbols"); + } + // espeak appends "/espeak-ng-data" to the path it is given, so the + // PARENT of the data directory is what it wants. Handing it the data + // directory itself makes it fall back to its compiled-in default. + const std::string data = + requested_data.empty() ? std::string() : requested_data.parent_path().string(); + if (initialize(kEspeakSynchronous, 0, data.empty() ? nullptr : data.c_str(), 0) <= 0) { + throw std::runtime_error( + "sanoTTS eSpeak-ng failed to initialize; pass " + "--session-option sanotts.espeak_data_path=/path/to/espeak-ng-data"); + } + // Some packages name a bare language code ("en"). phonemizer, which + // the reference front end drives, rejects bare codes on every + // espeak-ng >= 1.49 and falls back to the regional variant, even + // though espeak_SetVoiceByName itself would accept "en" (and select + // a different accent). Prefer the regional variants first so both + // stacks phonemize identically; a code with no variant (vi, id) + // falls through to itself. + std::vector candidates; + if (voice.find('-') == std::string::npos) { + candidates.push_back(voice + "-us"); + candidates.push_back(voice + "-gb"); + } + candidates.push_back(voice); + bool selected = false; + for (const auto & candidate : candidates) { + if (set_voice(candidate.c_str()) == 0) { + selected = true; + break; + } + } + if (!selected) { + throw std::runtime_error("sanoTTS eSpeak-ng has no voice matching '" + voice + "'"); + } + } + + ~EspeakApi() { + if (terminate != nullptr) { + terminate(); + } + if (library != nullptr) { + io::close_dynamic_library(library); + } + } + + [[nodiscard]] std::string phonemize(const std::string & text, int phonemes_mode) const { + const std::lock_guard guard(call_mutex); + std::string out; + const char * cursor = text.c_str(); + const void * position = cursor; + // espeak consumes one clause per call and advances the pointer; it + // returns null when the input is spent. + while (position != nullptr) { + const char * clause = + text_to_phonemes(&position, kEspeakCharsUtf8, phonemes_mode); + if (clause == nullptr) { + break; + } + if (!out.empty()) { + out.push_back(' '); + } + out.append(clause); + } + return out; + } +}; + +} // namespace + +struct SanoTtsFrontend::Impl { + EspeakApi espeak; + Impl(const std::filesystem::path & library, const std::filesystem::path & data) + : espeak(library, data, "en-us") {} +}; + +SanoTtsFrontend::SanoTtsFrontend( + std::filesystem::path espeak_library_path, + std::filesystem::path espeak_data_path, + int64_t max_tokens) + : impl_(std::make_unique(espeak_library_path, espeak_data_path)), + max_tokens_(max_tokens > 2 ? max_tokens : 207) {} + +SanoTtsFrontend::~SanoTtsFrontend() = default; + +SanoTtsEncoded SanoTtsFrontend::encode(const std::string & text) const { + auto [chunks, marks] = preserve_punctuation(text); + std::vector chunk_phonemes; + chunk_phonemes.reserve(chunks.size()); + for (const auto & chunk : chunks) { + chunk_phonemes.push_back(postprocess_espeak_line( + impl_->espeak.phonemize(chunk, kEspeakPhonemesIpaTie))); + } + const std::string restored = + restore_punctuation(std::move(chunk_phonemes), std::move(marks)); + // Ties become '^' BEFORE E2M runs: every diphthong pattern in the table + // ("o^ʊ" -> "O", "t^ʃ" -> "ʧ", ...) matches on the rewritten form. + std::string ipa = apply_e2m(rewrite_ties_per_word(restored)); + + const auto & vocab = vocabulary(); + SanoTtsEncoded out; + out.token_ids.push_back(1); // + // Iterate whole UTF-8 codepoints: every vocabulary symbol is one + // codepoint, so anything else can only be dropped -- which is what the + // reference front ends do with unknown symbols. + for (size_t i = 0; i < ipa.size();) { + size_t len = 1; + const auto lead = static_cast(ipa[i]); + if ((lead & 0xF8U) == 0xF0U) { len = 4; } + else if ((lead & 0xF0U) == 0xE0U) { len = 3; } + else if ((lead & 0xE0U) == 0xC0U) { len = 2; } + len = std::min(len, ipa.size() - i); + const std::string symbol = ipa.substr(i, len); + i += len; + const auto found = vocab.find(symbol); + if (found != vocab.end()) { + out.token_ids.push_back(found->second); + } else { + out.dropped.append(symbol); + } + } + if (out.token_ids.size() == 1) { + throw std::runtime_error( + "sanoTTS phonemization produced no symbols in the packaged vocabulary"); + } + out.token_ids.push_back(2); // + if (static_cast(out.token_ids.size()) > max_tokens_) { + throw SanoTtsTooLongError( + "sanoTTS phoneme sequence has " + std::to_string(out.token_ids.size()) + + " tokens including BOS/EOS; the duration model was trained for at most " + + std::to_string(max_tokens_) + "."); + } + return out; +} + +std::vector SanoTtsFrontend::split_text( + const std::string & text, + int64_t max_codepoints) { + const size_t budget = max_codepoints > 0 ? static_cast(max_codepoints) : 280U; + std::vector chunks; + std::string current; + size_t codepoints = 0; + for (size_t i = 0; i < text.size();) { + size_t len = 1; + const auto lead = static_cast(text[i]); + if ((lead & 0xF8U) == 0xF0U) { len = 4; } + else if ((lead & 0xF0U) == 0xE0U) { len = 3; } + else if ((lead & 0xE0U) == 0xC0U) { len = 2; } + len = std::min(len, text.size() - i); + current.append(text, i, len); + i += len; + ++codepoints; + const bool sentence_end = len == 1 && (text[i - 1] == '.' || text[i - 1] == '!' || + text[i - 1] == '?'); + if ((sentence_end && codepoints >= budget / 4) || codepoints >= budget) { + chunks.push_back(current); + current.clear(); + codepoints = 0; + } + } + if (!current.empty()) { + chunks.push_back(current); + } + if (chunks.empty()) { + chunks.push_back(text); + } + return chunks; +} + +double SanoTtsFrontend::boundary_pause_seconds(const std::string & chunk) { + for (auto it = chunk.rbegin(); it != chunk.rend(); ++it) { + if (std::isspace(static_cast(*it)) != 0) { + continue; + } + return (*it == '.' || *it == '!' || *it == '?') ? 0.20 : 0.08; + } + return 0.08; +} + +// ---- piperlite front end ------------------------------------------------- + +struct SanoTtsPiperFrontend::Impl { + EspeakApi espeak; + Impl(const std::filesystem::path & library, + const std::filesystem::path & data, + const std::string & voice) + : espeak(library, data, voice) {} +}; + +SanoTtsPiperFrontend::SanoTtsPiperFrontend( + std::filesystem::path espeak_library_path, + std::filesystem::path espeak_data_path, + std::string espeak_voice, + std::unordered_map phoneme_id_map, + int64_t max_tokens) + : impl_(std::make_unique(espeak_library_path, espeak_data_path, espeak_voice)), + id_map_(std::move(phoneme_id_map)), + max_tokens_(max_tokens > 3 ? max_tokens : 3) {} + +SanoTtsPiperFrontend::~SanoTtsPiperFrontend() = default; + +SanoTtsEncoded SanoTtsPiperFrontend::encode(const std::string & text) const { + auto [chunks, marks] = preserve_punctuation(text); + std::vector chunk_phonemes; + chunk_phonemes.reserve(chunks.size()); + for (const auto & chunk : chunks) { + chunk_phonemes.push_back(postprocess_espeak_line( + impl_->espeak.phonemize(chunk, kEspeakPhonemesIpaUnderscore))); + } + std::string restored = + restore_punctuation(std::move(chunk_phonemes), std::move(marks)); + // phonemizer leaves a trailing word separator that piper's own bridge + // does not emit at true end of input; the reference rstrips before NFD. + while (!restored.empty() && + std::isspace(static_cast(restored.back())) != 0) { + restored.pop_back(); + } + const std::string decomposed = nfd_decompose(restored); + + // piper.phoneme_ids.phonemes_to_ids: bos, pad, then (id, pad) per + // codepoint, then eos. The exporter validated the framing symbols + // ('_' -> 0, '^' -> 1, '$' -> 2) against the voice's own map. + SanoTtsEncoded out; + out.token_ids.push_back(1); // + out.token_ids.push_back(0); // + for (size_t i = 0; i < decomposed.size();) { + const size_t len = utf8_sequence_length(decomposed, i); + const std::string symbol = decomposed.substr(i, len); + i += len; + const auto found = id_map_.find(symbol); + if (found != id_map_.end()) { + out.token_ids.push_back(found->second); + out.token_ids.push_back(0); + } else { + // skipped with a note, exactly as piper skips unmapped phonemes + out.dropped.append(symbol); + } + } + if (out.token_ids.size() <= 2) { + throw std::runtime_error( + "sanoTTS phonemization produced no symbols in the voice's phoneme_id_map"); + } + out.token_ids.push_back(2); // + if (static_cast(out.token_ids.size()) > max_tokens_) { + throw SanoTtsTooLongError( + "sanoTTS phoneme sequence has " + std::to_string(out.token_ids.size()) + + " ids including framing; the duration model was trained for at most " + + std::to_string(max_tokens_) + "."); + } + return out; +} + +} // namespace engine::models::sanotts diff --git a/src/community_models/sanotts/graph_common.h b/src/community_models/sanotts/graph_common.h new file mode 100644 index 000000000..4dc0e24d1 --- /dev/null +++ b/src/community_models/sanotts/graph_common.h @@ -0,0 +1,635 @@ +#pragma once + +// Internal helpers shared by the two sanoTTS runtimes (the nano lineage in +// runtime.cpp and the piperlite lineage in piper_runtime.cpp). Both lineages +// share the same convolutional front-end structure -- residual conv blocks +// over channel-major [1, C, T] values -- and the same graph plumbing. + +#include "engine/community_models/sanotts/assets.h" +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/lookup_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/runtime/cache_slots.h" + +#include "ggml-alloc.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::sanotts::graph { + +struct GgmlContextDeleter { + void operator()(ggml_context * context) const noexcept { + if (context != nullptr) { + ggml_free(context); + } + } +}; + +inline core::TensorValue contiguous( + core::ModuleBuildContext & ctx, + const core::TensorValue & value) { + if (core::has_backend_addressable_layout(value.tensor)) { + return value; + } + return core::wrap_tensor( + ggml_cont(ctx.ggml, value.tensor), + value.shape, + value.type); +} + +inline core::TensorValue add( + core::ModuleBuildContext & ctx, + const core::TensorValue & lhs, + const core::TensorValue & rhs) { + return modules::AddModule().build(ctx, lhs, rhs); +} + +struct SanoTtsBackendWeights { + std::shared_ptr store; + std::unordered_map tensors; +}; + +inline const core::TensorValue & weight( + const SanoTtsBackendWeights & weights, + const std::string & name) { + const auto found = weights.tensors.find(name); + if (found == weights.tensors.end()) { + throw std::runtime_error("sanoTTS missing tensor: " + name); + } + return found->second; +} + +inline std::shared_ptr load_weights( + const std::shared_ptr & assets, + ggml_backend_t backend, + core::BackendType backend_type, + size_t expected_tensors, + size_t weight_arena_bytes) { + auto out = std::make_shared(); + out->store = std::make_shared( + backend, + backend_type, + "sanotts.weights", + weight_arena_bytes); + const auto metadata = assets->weights->tensors(); + if (metadata.size() != expected_tensors) { + throw std::runtime_error( + "sanoTTS expects exactly " + std::to_string(expected_tensors) + + " tensors for this config, found " + std::to_string(metadata.size())); + } + out->tensors.reserve(metadata.size()); + for (const auto & tensor : metadata) { + if (assets::ggml_type_for_tensor_dtype(tensor.dtype) != GGML_TYPE_F32) { + throw std::runtime_error( + "sanoTTS supports FP32 weights only: " + tensor.name); + } + out->tensors.emplace( + tensor.name, + out->store->load_tensor( + *assets->weights, + tensor.name, + assets::TensorStorageType::F32, + tensor.shape)); + } + out->store->upload(); + assets->weights->release_storage(); + return out; +} + +/** Conv1d over channel-major [1, C, T] with explicit padding and dilation. + * A kernel-1 conv lowers to a matmul. */ +inline core::TensorValue conv1d( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const core::TensorValue & input, + const std::string & prefix, + int64_t out_channels, + int64_t kernel, + int padding, + int dilation = 1) { + const int64_t in_channels = input.shape.dims[1]; + const int64_t input_frames = input.shape.dims[2]; + const int64_t output_frames = + input_frames + 2 * padding - static_cast(dilation) * (kernel - 1); + const auto source = contiguous(ctx, input); + auto * input_2d = ggml_reshape_2d( + ctx.ggml, + source.tensor, + input_frames, + in_channels); + auto * kernel_tensor = weight(weights, prefix + ".weight").tensor; + ggml_tensor * output = nullptr; + if (kernel == 1 && padding == 0) { + auto * kernel_2d = ggml_reshape_2d( + ctx.ggml, + kernel_tensor, + in_channels, + out_channels); + auto * input_channels_first = ggml_cont( + ctx.ggml, + ggml_permute(ctx.ggml, input_2d, 1, 0, 2, 3)); + auto * output_channels_first = + ggml_mul_mat(ctx.ggml, kernel_2d, input_channels_first); + output = ggml_reshape_2d( + ctx.ggml, + ggml_cont( + ctx.ggml, + ggml_permute(ctx.ggml, output_channels_first, 1, 0, 2, 3)), + output_frames, + out_channels); + } else { + auto * kernel_3d = ggml_reshape_3d( + ctx.ggml, + kernel_tensor, + kernel, + in_channels, + out_channels); + auto * input_3d = ggml_reshape_3d( + ctx.ggml, + input_2d, + input_frames, + in_channels, + 1); + auto * output_3d = ggml_conv_1d( + ctx.ggml, + kernel_3d, + input_3d, + 1, + padding, + dilation); + output = ggml_reshape_2d( + ctx.ggml, + output_3d, + output_frames, + out_channels); + } + auto * bias = ggml_reshape_2d( + ctx.ggml, + weight(weights, prefix + ".bias").tensor, + 1, + out_channels); + output = ggml_add(ctx.ggml, output, bias); + return core::wrap_tensor( + ggml_reshape_3d(ctx.ggml, output, output_frames, out_channels, 1), + core::TensorShape::from_dims({1, out_channels, output_frames}), + GGML_TYPE_F32); +} + +/** x + scale * conv2(silu(conv1(x))) -- the front ends' ResidualConvBlock. + * `scale` is a learned one-element tensor, broadcast by ggml_mul. */ +inline core::TensorValue residual_block( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const core::TensorValue & input, + const std::string & prefix, + int64_t hidden, + int64_t kernel) { + const int padding = static_cast(kernel / 2); + auto h = conv1d(ctx, weights, input, prefix + ".net.0", hidden, kernel, padding); + h = modules::SiluModule().build(ctx, h); + h = conv1d(ctx, weights, h, prefix + ".net.2", hidden, kernel, padding); + const auto h_source = contiguous(ctx, h); + const auto scaled_h = core::wrap_tensor( + ggml_mul(ctx.ggml, h_source.tensor, weight(weights, prefix + ".scale").tensor), + h.shape, + GGML_TYPE_F32); + return add(ctx, input, scaled_h); +} + +inline core::TensorValue embed_tokens( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const core::TensorValue & tokens, + const std::string & name, + int64_t vocab, + int64_t hidden) { + auto embedded = modules::EmbeddingModule({vocab, hidden}).build( + ctx, + tokens, + weight(weights, name)); + return modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, embedded); +} + +struct GraphResources { + ~GraphResources() { + core::free_backend_graph_plan(backend, plan); + core::release_backend_graph_resources(backend, graph); + if (allocator != nullptr) { + ggml_gallocr_free(allocator); + } + if (io_buffer != nullptr) { + ggml_backend_buffer_free(io_buffer); + } + } + + std::unique_ptr io_context; + std::unique_ptr graph_context; + ggml_backend_buffer_t io_buffer = nullptr; + ggml_gallocr_t allocator = nullptr; + ggml_backend_t backend = nullptr; + ggml_backend_graph_plan_t plan = nullptr; + ggml_cgraph * graph = nullptr; +}; + +inline void allocate_graph(GraphResources & resources) { + resources.io_buffer = + ggml_backend_alloc_ctx_tensors(resources.io_context.get(), resources.backend); + if (resources.io_buffer == nullptr) { + throw std::runtime_error("sanoTTS failed to allocate graph input buffer"); + } + resources.allocator = + ggml_gallocr_new(ggml_backend_get_default_buffer_type(resources.backend)); + if (resources.allocator == nullptr || + !ggml_gallocr_reserve(resources.allocator, resources.graph) || + !ggml_gallocr_alloc_graph(resources.allocator, resources.graph)) { + throw std::runtime_error("sanoTTS failed to allocate backend graph"); + } + core::validate_backend_graph_supported( + resources.backend, + resources.graph, + "sanoTTS"); + resources.plan = + core::create_backend_graph_plan_if_host(resources.backend, resources.graph); +} + +inline void compute_graph(GraphResources & resources, const char * label) { + const auto status = core::compute_backend_graph( + resources.backend, + resources.graph, + resources.plan, + label); + ggml_backend_synchronize(resources.backend); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error(std::string(label) + " graph compute failed"); + } +} + +/** torch.linspace(0, 1, n) with exact CPU-kernel float semantics: step in + * fp32, the first half filled as step*i, the second as fma(-step, n-1-i, 1). */ +inline void linspace01(float * dst, int64_t n) { + if (n <= 0) { + return; + } + if (n == 1) { + dst[0] = 0.0F; + return; + } + const auto step = 1.0F / static_cast(n - 1); + const int64_t half = n / 2; + for (int64_t i = 0; i < half; ++i) { + dst[i] = step * static_cast(i); + } + for (int64_t i = half; i < n; ++i) { + dst[i] = std::fma(-step, static_cast(n - 1 - i), 1.0F); + } +} + + +// ---- shared front-end graphs --------------------------------------------- +// +// Both lineages run the same two token-level stages -- embed + positional +// features + input projection + residual blocks (+ optional 1x1 output conv) +// -- under the same tensor names; only the dimensions differ per package. + +struct FrontStageSpec { + const char * embedding = nullptr; // "duration.embedding.weight" + const char * input_proj = nullptr; // "duration.input_proj" + const char * block_prefix = nullptr; // "duration.blocks." + const char * output_conv = nullptr; // "duration.output", or nullptr + const char * label = nullptr; // graph label for contexts/tracing + int64_t feat_rows = 3; + int64_t vocab = 0; + int64_t hidden = 0; + int64_t depth = 0; + int64_t kernel = 0; + int64_t out_channels = 1; // used when output_conv is set +}; + +inline FrontStageSpec duration_stage_spec(int64_t vocab, int64_t hidden, int64_t depth, int64_t kernel) { + return {"duration.embedding.weight", "duration.input_proj", "duration.blocks.", + "duration.output", "sanotts.duration", 3, vocab, hidden, depth, kernel, 1}; +} + +inline FrontStageSpec token_stage_spec(int64_t vocab, int64_t hidden, int64_t depth, int64_t kernel) { + return {"acoustic.embedding.weight", "acoustic.token_input_proj", "acoustic.token_blocks.", + nullptr, "sanotts.token", 2, vocab, hidden, depth, kernel, 1}; +} + +struct FrontGraph : GraphResources { + int64_t token_count = 0; + ggml_tensor * tokens = nullptr; + ggml_tensor * feats = nullptr; + ggml_tensor * output = nullptr; // log-durations or token context +}; + +inline std::unique_ptr build_front_graph( + const SanoTtsBackendWeights & weights, + const FrontStageSpec & spec, + ggml_backend_t backend, + core::BackendType backend_type, + int64_t token_count, + size_t io_arena_bytes, + size_t graph_arena_bytes) { + auto out = std::make_unique(); + out->backend = backend; + out->token_count = token_count; + out->io_context.reset(ggml_init({io_arena_bytes, nullptr, true})); + out->graph_context.reset(ggml_init({graph_arena_bytes, nullptr, true})); + if (out->io_context == nullptr || out->graph_context == nullptr) { + throw std::runtime_error("sanoTTS failed to create graph contexts"); + } + core::ModuleBuildContext io_ctx{out->io_context.get(), spec.label, backend_type}; + core::ModuleBuildContext ctx{out->graph_context.get(), spec.label, backend_type}; + auto tokens = core::make_tensor( + io_ctx, + GGML_TYPE_I32, + core::TensorShape::from_dims({1, token_count})); + auto feats = core::make_tensor( + io_ctx, + GGML_TYPE_F32, + core::TensorShape::from_dims({1, spec.feat_rows, token_count})); + ggml_set_input(tokens.tensor); + ggml_set_input(feats.tensor); + + auto hidden = embed_tokens(ctx, weights, tokens, spec.embedding, spec.vocab, spec.hidden); + hidden = modules::ConcatModule({1}).build(ctx, hidden, feats); + hidden = conv1d(ctx, weights, hidden, spec.input_proj, spec.hidden, 1, 0); + for (int64_t block = 0; block < spec.depth; ++block) { + hidden = residual_block( + ctx, + weights, + hidden, + spec.block_prefix + std::to_string(block), + spec.hidden, + spec.kernel); + } + if (spec.output_conv != nullptr) { + hidden = conv1d(ctx, weights, hidden, spec.output_conv, spec.out_channels, 1, 0); + } + hidden = contiguous(ctx, hidden); + out->tokens = tokens.tensor; + out->feats = feats.tensor; + out->output = hidden.tensor; + ggml_set_output(out->output); + out->graph = ggml_new_graph_custom(ctx.ggml, 16384, false); + ggml_build_forward_expand(out->graph, out->output); + allocate_graph(*out); + return out; +} + +/** The frame-level acoustic stage both decoder graphs open with: expanded + * token context + [frame_pos, token_pos, duration_pos] -> residual blocks + * -> 1x1 output conv (mel-100 for nano, the 192-ch latent for piperlite). */ +inline core::TensorValue acoustic_frame_stage( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const core::TensorValue & context, + const core::TensorValue & feats, + int64_t hidden, + int64_t depth, + int64_t kernel, + int64_t out_channels) { + auto value = modules::ConcatModule({1}).build(ctx, context, feats); + value = conv1d(ctx, weights, value, "acoustic.frame_input_proj", hidden, 1, 0); + for (int64_t block = 0; block < depth; ++block) { + value = residual_block( + ctx, + weights, + value, + "acoustic.frame_blocks." + std::to_string(block), + hidden, + kernel); + } + return conv1d(ctx, weights, value, "acoustic.output", out_channels, 1, 0); +} + +// ---- shared runtime state ------------------------------------------------ + +struct BackendOwner { + ggml_backend_t value = nullptr; + BackendOwner() = default; + BackendOwner(const BackendOwner &) = delete; + BackendOwner & operator=(const BackendOwner &) = delete; + ~BackendOwner() { + if (value != nullptr) { + ggml_backend_free(value); + } + } +}; + +/** Backend + uploaded weights, with the tiny duration model mirrored to the + * host on CUDA builds: durations round to integers and gate the whole frame + * layout, so small TF32 differences must not move them (the inflect_v2 + * rationale). */ +struct BackendState { + std::shared_ptr assets; + int threads = 1; + core::BackendType backend_type = core::BackendType::Cpu; + BackendOwner backend; + std::shared_ptr weights; + BackendOwner duration_backend; + std::shared_ptr duration_weights; + + BackendState( + std::shared_ptr assets_in, + core::BackendConfig backend_config, + size_t expected_tensors, + size_t weight_arena_bytes) + : assets(std::move(assets_in)), + threads(std::max(1, backend_config.threads)) { + if (assets == nullptr) { + throw std::runtime_error("sanoTTS runtime requires assets"); + } + backend_config.threads = threads; + backend.value = core::init_backend(backend_config); + backend_type = core::backend_type(backend.value); + core::set_backend_threads(backend.value, threads); + weights = load_weights( + assets, backend.value, backend_type, expected_tensors, weight_arena_bytes); + if (backend_type == core::BackendType::Cuda) { + core::BackendConfig duration_config{core::BackendType::Cpu, 0, threads}; + duration_backend.value = core::init_backend(duration_config); + core::set_backend_threads(duration_backend.value, threads); + duration_weights = load_weights( + assets, + duration_backend.value, + core::BackendType::Cpu, + expected_tensors, + weight_arena_bytes); + } + } + + ggml_backend_t duration_backend_value() const { + return duration_backend.value != nullptr ? duration_backend.value : backend.value; + } + core::BackendType duration_backend_type() const { + return duration_backend.value != nullptr ? core::BackendType::Cpu : backend_type; + } + const SanoTtsBackendWeights & duration_weights_ref() const { + return duration_weights != nullptr ? *duration_weights : *weights; + } +}; + +template +Graph & cached_graph( + runtime::CacheSlots> & slots, + int64_t key, + const char * trace_name, + Build && build) { + if (auto * found = slots.find(key)) { + engine::debug::trace_log_scalar(trace_name, true); + return **found; + } + engine::debug::trace_log_scalar(trace_name, false); + slots.put(key, build()); + auto * created = slots.find(key); + if (created == nullptr) { + throw std::runtime_error("sanoTTS graph cache insert failed"); + } + return **created; +} + +// ---- shared host-side pieces --------------------------------------------- +// +// Hints are computed in double and cast to fp32, matching the numpy +// references both runtimes are gated against. + +/** [positions, length_hint, valid=1] rows for the duration stage. */ +inline std::vector duration_features(int64_t token_count, int64_t max_tokens) { + std::vector feats(static_cast(3 * token_count)); + linspace01(feats.data(), token_count); + const auto length_hint = static_cast( + std::log1p(static_cast(token_count)) / + std::log1p(static_cast(max_tokens))); + std::fill_n(feats.begin() + token_count, token_count, length_hint); + std::fill_n(feats.begin() + 2 * token_count, token_count, 1.0F); + return feats; +} + +/** [token_pos, duration_hint] rows for the token stage. */ +inline std::vector token_features( + int64_t token_count, + const std::vector & durations) { + std::vector feats(static_cast(2 * token_count)); + linspace01(feats.data(), token_count); + double max_duration = 1.0; + for (const int64_t duration : durations) { + max_duration = std::max(max_duration, static_cast(duration)); + } + const double log_max_duration = std::log1p(max_duration); + for (int64_t token = 0; token < token_count; ++token) { + feats[static_cast(token_count + token)] = static_cast( + std::log1p(static_cast(durations[static_cast(token)])) / + log_max_duration); + } + return feats; +} + +/** exp -> clamp_min(1) -> *scale -> round (ties to even) -> clamp, in + * double, exactly as the references compute it. Returns per-token frame + * counts and validates the total against the trained limits. */ +inline std::vector round_durations( + const std::vector & log_duration, + double scale, + int64_t max_frames_per_token, + int64_t max_total_frames, + int64_t & total_frames) { + std::vector durations(log_duration.size()); + total_frames = 0; + for (size_t token = 0; token < log_duration.size(); ++token) { + double value = std::exp(static_cast(log_duration[token])); + if (!std::isfinite(value)) { + throw std::runtime_error("sanoTTS duration predictor produced a non-finite duration"); + } + if (value < 1.0) { + value = 1.0; + } + value = std::rint(value * scale); + if (value < 1.0) { + value = 1.0; + } + if (value > static_cast(max_frames_per_token)) { + value = static_cast(max_frames_per_token); + } + durations[token] = static_cast(value); + total_frames += durations[token]; + } + if (total_frames < 1 || total_frames > max_total_frames) { + throw std::runtime_error( + "sanoTTS expanded to " + std::to_string(total_frames) + + " frames, outside the supported range"); + } + return durations; +} + +/** Repeat each token's context vector across its own frames. */ +inline std::vector expand_context( + const std::vector & token_context, + const std::vector & durations, + int64_t hidden, + int64_t token_count, + int64_t frames) { + std::vector expanded(static_cast(hidden * frames)); + for (int64_t channel = 0; channel < hidden; ++channel) { + const float * row = token_context.data() + channel * token_count; + float * out_row = expanded.data() + channel * frames; + int64_t at = 0; + for (int64_t token = 0; token < token_count; ++token) { + const float value = row[token]; + for (int64_t j = 0; j < durations[static_cast(token)]; ++j) { + out_row[at++] = value; + } + } + } + return expanded; +} + +/** [frame_pos, token_pos, duration_pos] rows -- expand_features' documented + * float semantics: doubles cast to fp32, a duration of 1 contributing 0. */ +inline std::vector frame_features( + int64_t token_count, + const std::vector & durations, + int64_t frames) { + std::vector feats(static_cast(3 * frames)); + linspace01(feats.data(), frames); + const int64_t denominator = token_count > 1 ? token_count - 1 : 1; + float * token_pos = feats.data() + frames; + float * duration_pos = feats.data() + 2 * frames; + int64_t at = 0; + for (int64_t token = 0; token < token_count; ++token) { + const int64_t count = durations[static_cast(token)]; + const auto position = static_cast( + static_cast(token) / static_cast(denominator)); + for (int64_t j = 0; j < count; ++j) { + token_pos[at] = position; + duration_pos[at] = count == 1 + ? 0.0F + : static_cast( + static_cast(j) / static_cast(count - 1)); + ++at; + } + } + return feats; +} + +inline void write_i32_input(ggml_tensor * tensor, const core::TensorShape & shape, const std::vector & values) { + core::write_tensor_i32(core::wrap_tensor(tensor, shape, GGML_TYPE_I32), values); +} + +inline void write_f32_input(ggml_tensor * tensor, const core::TensorShape & shape, const std::vector & values) { + core::write_tensor_f32(core::wrap_tensor(tensor, shape, GGML_TYPE_F32), values); +} + +} // namespace engine::models::sanotts::graph diff --git a/src/community_models/sanotts/piper_runtime.cpp b/src/community_models/sanotts/piper_runtime.cpp new file mode 100644 index 000000000..4b80ef346 --- /dev/null +++ b/src/community_models/sanotts/piper_runtime.cpp @@ -0,0 +1,552 @@ +#include "engine/community_models/sanotts/piper_runtime.h" + +#include "graph_common.h" + +#include "engine/framework/modules/conv_modules.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::sanotts { +namespace { + +namespace core = engine::core; +namespace modules = engine::modules; +using namespace engine::models::sanotts::graph; // shared graph helpers + +constexpr size_t kIoArenaBytes = 8ULL * 1024ULL * 1024ULL; +constexpr size_t kGraphArenaBytes = 128ULL * 1024ULL * 1024ULL; +constexpr size_t kWeightArenaBytes = 32ULL * 1024ULL * 1024ULL; +constexpr float kLeakyReluSlope = 0.1F; + +// PiperResidualBank geometry, fixed by the training code: branch b uses +// kernel kBankKernels[b] with dilation kBankDilations1[b] on its first conv +// and kBankDilations2[b] on its second. +constexpr int64_t kBankKernels[3] = {3, 5, 7}; +constexpr int kBankDilations1[3] = {1, 2, 3}; +constexpr int kBankDilations2[3] = {2, 6, 12}; + +// The three ConvTranspose1d stages: stride and PyTorch padding. Kernel sizes +// are read from the weights themselves. +constexpr int kUpStrides[3] = {8, 8, 4}; +constexpr int64_t kUpPaddings[3] = {4, 4, 2}; + +/** Ids outside a component's trained vocab remap to schwa -- the same + * fallback the reference runtimes use for the shared-frontend/table + * vocab-size mismatch (e.g. duration vocab 127 vs table ids to 156). */ +constexpr int32_t kSchwaFallbackId = 59; + +size_t expected_tensor_count(const SanoTtsPiperConfig & config) { + const auto duration = 5 + 5 * config.duration_depth; + const auto acoustic = + 7 + 5 * (config.acoustic_token_depth + config.acoustic_depth); + int64_t decoder = 4; // pre + post + for (const auto & branches : config.stage_branches) { + decoder += 2 + 4 * static_cast(branches.size()); + } + if (config.post_filter_channels > 0) { + decoder += 4 + 5 * config.post_filter_layers; + } + return static_cast(duration + acoustic + decoder); +} + +std::vector clamp_ids_to_vocab( + const std::vector & ids, + int64_t vocab_size) { + const int32_t fallback = + kSchwaFallbackId < vocab_size ? kSchwaFallbackId : 0; + std::vector out(ids.size()); + for (size_t i = 0; i < ids.size(); ++i) { + const int32_t id = ids[i]; + out[i] = (id < 0 || id >= vocab_size) ? fallback : id; + } + return out; +} + +int64_t kernel_of(const SanoTtsBackendWeights & weights, const std::string & prefix) { + return weight(weights, prefix + ".weight").shape.dims[2]; +} + +core::TensorValue leaky_relu( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + float slope) { + const auto source = contiguous(ctx, input); + return core::wrap_tensor( + ggml_leaky_relu(ctx.ggml, source.tensor, slope, false), + input.shape, + GGML_TYPE_F32); +} + +core::TensorValue scaled( + core::ModuleBuildContext & ctx, + const core::TensorValue & value, + float scale) { + const auto source = contiguous(ctx, value); + return core::wrap_tensor( + ggml_scale(ctx.ggml, source.tensor, scale), + value.shape, + GGML_TYPE_F32); +} + +/** 'same'-padded conv: pad = dilation * (kernel / 2). */ +core::TensorValue conv1d_same( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const core::TensorValue & input, + const std::string & prefix, + int64_t out_channels, + int64_t kernel, + int dilation = 1) { + return conv1d( + ctx, + weights, + input, + prefix, + out_channels, + kernel, + dilation * static_cast(kernel / 2), + dilation); +} + +/** ConvTranspose1d producing stride * input_frames samples, PyTorch padding + * semantics -- the same construction inflect_v2 uses. */ +core::TensorValue conv_transpose1d( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const core::TensorValue & input, + const std::string & prefix, + int64_t out_channels, + int64_t kernel, + int stride, + int64_t padding) { + const int64_t input_frames = input.shape.dims[2]; + const int64_t output_frames = input_frames * stride; + if (ctx.backend_type == core::BackendType::Cpu && padding > 0) { + const auto source = contiguous(ctx, input); + auto * input_2d = ggml_reshape_2d( + ctx.ggml, + source.tensor, + input_frames, + input.shape.dims[1]); + auto * output = ggml_conv_transpose_1d( + ctx.ggml, + weight(weights, prefix + ".weight").tensor, + input_2d, + stride, + 0, + 1); + const int64_t full_frames = (input_frames - 1) * stride + kernel; + output = ggml_reshape_2d(ctx.ggml, output, full_frames, out_channels); + output = ggml_view_2d( + ctx.ggml, + output, + output_frames, + out_channels, + ggml_row_size(output->type, full_frames), + ggml_row_size(output->type, padding)); + output = ggml_cont(ctx.ggml, output); + auto * bias = ggml_reshape_2d( + ctx.ggml, + weight(weights, prefix + ".bias").tensor, + 1, + out_channels); + output = ggml_add(ctx.ggml, output, bias); + return core::wrap_tensor( + ggml_reshape_3d(ctx.ggml, output, output_frames, out_channels, 1), + core::TensorShape::from_dims({1, out_channels, output_frames}), + GGML_TYPE_F32); + } + auto output = modules::ConvTranspose1dModule({ + input.shape.dims[1], + out_channels, + kernel, + stride, + 0, + 1, + true, + }).build( + ctx, + input, + { + weight(weights, prefix + ".weight"), + weight(weights, prefix + ".bias"), + }); + return modules::SliceModule({2, padding, output_frames}).build(ctx, output); +} + +/** PiperResidualBank: mean over active branches of + * y2 = conv2(lrelu(y1)) + y1, y1 = conv1(lrelu(x)) + x. */ +core::TensorValue residual_bank( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const core::TensorValue & input, + const std::string & prefix, + const std::vector & branches) { + core::TensorValue sum; + for (const int64_t branch : branches) { + const std::string branch_prefix = + prefix + ".blocks." + std::to_string(branch); + const int64_t kernel = kBankKernels[branch]; + auto value = leaky_relu(ctx, input, kLeakyReluSlope); + value = conv1d_same( + ctx, + weights, + value, + branch_prefix + ".conv1", + input.shape.dims[1], + kernel, + kBankDilations1[branch]); + const auto y1 = add(ctx, value, input); + value = leaky_relu(ctx, y1, kLeakyReluSlope); + value = conv1d_same( + ctx, + weights, + value, + branch_prefix + ".conv2", + input.shape.dims[1], + kernel, + kBankDilations2[branch]); + const auto y2 = add(ctx, value, y1); + sum = sum.valid() ? add(ctx, sum, y2) : y2; + } + return scaled(ctx, sum, 1.0F / static_cast(branches.size())); +} + +core::TensorValue post_filter( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const SanoTtsPiperConfig & config, + const core::TensorValue & audio) { + auto value = conv1d_same( + ctx, + weights, + audio, + "decoder.post_filter.in_conv", + config.post_filter_channels, + kernel_of(weights, "decoder.post_filter.in_conv")); + for (int64_t layer = 0; layer < config.post_filter_layers; ++layer) { + const std::string prefix = + "decoder.post_filter.units." + std::to_string(layer); + auto branch = leaky_relu(ctx, value, kLeakyReluSlope); + branch = conv1d_same( + ctx, + weights, + branch, + prefix + ".conv1", + config.post_filter_channels, + kernel_of(weights, prefix + ".conv1"), + static_cast(1 + layer)); + branch = leaky_relu(ctx, branch, kLeakyReluSlope); + branch = conv1d_same( + ctx, + weights, + branch, + prefix + ".conv2", + config.post_filter_channels, + kernel_of(weights, prefix + ".conv2")); + const auto branch_source = contiguous(ctx, branch); + branch = core::wrap_tensor( + ggml_mul( + ctx.ggml, + branch_source.tensor, + weight(weights, prefix + ".scale").tensor), + branch.shape, + GGML_TYPE_F32); + value = add(ctx, value, branch); + } + auto correction = conv1d_same( + ctx, + weights, + value, + "decoder.post_filter.out_conv", + 1, + kernel_of(weights, "decoder.post_filter.out_conv")); + correction = scaled(ctx, correction, static_cast(config.post_filter_scale)); + auto mixed = add(ctx, audio, correction); + const auto mixed_source = contiguous(ctx, mixed); + return core::wrap_tensor( + ggml_tanh(ctx.ggml, mixed_source.tensor), + mixed.shape, + GGML_TYPE_F32); +} + +struct DecoderGraph : GraphResources { + int64_t frames = 0; + ggml_tensor * context = nullptr; + ggml_tensor * feats = nullptr; + ggml_tensor * waveform = nullptr; +}; + +/** Frame-stage acoustic blocks -> latent -> 3-stage ConvTranspose decoder + * with dilated residual banks -> tanh waveform (plus kristin's post + * filter when the config carries one). */ +std::unique_ptr build_decoder_graph( + const SanoTtsBackendWeights & weights, + const SanoTtsPiperConfig & config, + ggml_backend_t backend, + core::BackendType backend_type, + int64_t frames) { + auto out = std::make_unique(); + out->backend = backend; + out->frames = frames; + out->io_context.reset(ggml_init({kIoArenaBytes, nullptr, true})); + out->graph_context.reset(ggml_init({kGraphArenaBytes, nullptr, true})); + if (out->io_context == nullptr || out->graph_context == nullptr) { + throw std::runtime_error("sanoTTS failed to create decoder graph contexts"); + } + core::ModuleBuildContext io_ctx{ + out->io_context.get(), + "sanotts.piper.decoder.io", + backend_type, + }; + core::ModuleBuildContext ctx{ + out->graph_context.get(), + "sanotts.piper.decoder", + backend_type, + }; + auto context = core::make_tensor( + io_ctx, + GGML_TYPE_F32, + core::TensorShape::from_dims({1, config.acoustic_hidden, frames})); + auto feats = core::make_tensor( + io_ctx, + GGML_TYPE_F32, + core::TensorShape::from_dims({1, 3, frames})); + ggml_set_input(context.tensor); + ggml_set_input(feats.tensor); + + auto latent = acoustic_frame_stage( + ctx, + weights, + context, + feats, + config.acoustic_hidden, + config.acoustic_depth, + config.acoustic_kernel, + config.acoustic_out_channels); + + auto value = conv1d_same( + ctx, + weights, + latent, + "decoder.pre", + config.channels[0], + kernel_of(weights, "decoder.pre")); + for (size_t stage = 0; stage < 3; ++stage) { + const std::string up_name = "decoder.up" + std::to_string(stage); + value = leaky_relu(ctx, value, kLeakyReluSlope); + value = conv_transpose1d( + ctx, + weights, + value, + up_name, + config.channels[stage + 1], + kernel_of(weights, up_name), + kUpStrides[stage], + kUpPaddings[stage]); + value = residual_bank( + ctx, + weights, + value, + "decoder.res" + std::to_string(stage) + ".0", + config.stage_branches[stage]); + } + value = leaky_relu(ctx, value, 0.01F); + auto audio = conv1d_same( + ctx, + weights, + value, + "decoder.post", + 1, + kernel_of(weights, "decoder.post")); + { + const auto audio_source = contiguous(ctx, audio); + audio = core::wrap_tensor( + ggml_tanh(ctx.ggml, audio_source.tensor), + audio.shape, + GGML_TYPE_F32); + } + if (config.post_filter_channels > 0) { + audio = post_filter(ctx, weights, config, audio); + } + audio = contiguous(ctx, audio); + out->context = context.tensor; + out->feats = feats.tensor; + out->waveform = audio.tensor; + ggml_set_output(out->waveform); + out->graph = ggml_new_graph_custom(ctx.ggml, 16384, false); + ggml_build_forward_expand(out->graph, out->waveform); + allocate_graph(*out); + return out; +} + +} // namespace + +struct SanoTtsPiperRuntime::State : BackendState { + State(const std::shared_ptr & assets_in, + core::BackendConfig backend_config) + : BackendState( + assets_in, + backend_config, + assets_in == nullptr ? 0 : expected_tensor_count(assets_in->piper), + kWeightArenaBytes) {} + + runtime::CacheSlots> duration_graphs{4}; + runtime::CacheSlots> token_graphs{4}; + runtime::CacheSlots> decoder_graphs{2}; +}; + +SanoTtsPiperRuntime::SanoTtsPiperRuntime( + std::shared_ptr assets, + core::BackendConfig backend_config) + : state_(std::make_unique(std::move(assets), backend_config)) {} + +SanoTtsPiperRuntime::~SanoTtsPiperRuntime() = default; + +runtime::AudioBuffer SanoTtsPiperRuntime::synthesize( + const std::vector & token_ids, + const SanoTtsPiperGenerationOptions & options) { + const auto & config = state_->assets->piper; + const auto token_count = static_cast(token_ids.size()); + if (token_count <= 0) { + throw std::runtime_error("sanoTTS requires at least one phoneme token"); + } + const auto total_start = std::chrono::steady_clock::now(); + + // -- durations --------------------------------------------------------- + const auto duration_start = std::chrono::steady_clock::now(); + auto & duration = cached_graph( + state_->duration_graphs, + token_count, + "sanotts.duration_graph.cache_hit", + [&] { + return build_front_graph( + state_->duration_weights_ref(), + duration_stage_spec( + config.duration_vocab, + config.duration_hidden, + config.duration_depth, + config.duration_kernel), + state_->duration_backend_value(), + state_->duration_backend_type(), + token_count, + kIoArenaBytes, + kGraphArenaBytes); + }); + write_i32_input( + duration.tokens, + core::TensorShape::from_dims({1, token_count}), + clamp_ids_to_vocab(token_ids, config.duration_vocab)); + write_f32_input( + duration.feats, + core::TensorShape::from_dims({1, 3, token_count}), + duration_features(token_count, config.duration_max_tokens)); + compute_graph(duration, "sanoTTS piper duration"); + const auto log_duration = core::read_tensor_f32(duration.output); + if (static_cast(log_duration.size()) != token_count) { + throw std::runtime_error("sanoTTS duration graph returned invalid output"); + } + // The user's speaking_rate multiplies the voice's tuned length scale. + int64_t frames = 0; + const auto durations = round_durations( + log_duration, + config.duration_length_scale * static_cast(options.speaking_rate), + config.duration_max_frames, + config.duration_max_tokens * config.duration_max_frames, + frames); + engine::debug::timing_log_scalar( + "sanotts.duration_ms", + engine::debug::elapsed_ms(duration_start)); + + // -- token-stage acoustic context -------------------------------------- + const auto acoustic_start = std::chrono::steady_clock::now(); + auto & token_graph = cached_graph( + state_->token_graphs, + token_count, + "sanotts.token_graph.cache_hit", + [&] { + return build_front_graph( + *state_->weights, + token_stage_spec( + config.acoustic_vocab, + config.acoustic_hidden, + config.acoustic_token_depth, + config.acoustic_kernel), + state_->backend.value, + state_->backend_type, + token_count, + kIoArenaBytes, + kGraphArenaBytes); + }); + write_i32_input( + token_graph.tokens, + core::TensorShape::from_dims({1, token_count}), + clamp_ids_to_vocab(token_ids, config.acoustic_vocab)); + write_f32_input( + token_graph.feats, + core::TensorShape::from_dims({1, 2, token_count}), + token_features(token_count, durations)); + compute_graph(token_graph, "sanoTTS piper token context"); + const auto token_context = core::read_tensor_f32(token_graph.output); + if (static_cast(token_context.size()) != + config.acoustic_hidden * token_count) { + throw std::runtime_error("sanoTTS token graph returned invalid output"); + } + engine::debug::timing_log_scalar( + "sanotts.acoustic_ms", + engine::debug::elapsed_ms(acoustic_start)); + + // -- frame stage + decoder --------------------------------------------- + const auto decoder_start = std::chrono::steady_clock::now(); + auto & decoder = cached_graph( + state_->decoder_graphs, + frames, + "sanotts.decoder_graph.cache_hit", + [&] { + return build_decoder_graph( + *state_->weights, + config, + state_->backend.value, + state_->backend_type, + frames); + }); + write_f32_input( + decoder.context, + core::TensorShape::from_dims({1, config.acoustic_hidden, frames}), + expand_context(token_context, durations, config.acoustic_hidden, token_count, frames)); + write_f32_input( + decoder.feats, + core::TensorShape::from_dims({1, 3, frames}), + frame_features(token_count, durations, frames)); + compute_graph(decoder, "sanoTTS piper decoder"); + runtime::AudioBuffer out; + out.sample_rate = static_cast(config.sample_rate); + out.channels = 1; + out.samples = core::read_tensor_f32(decoder.waveform); + const auto expected_samples = static_cast(frames) * 256U; + if (out.samples.size() != expected_samples) { + throw std::runtime_error("sanoTTS decoder returned an unexpected sample count"); + } + engine::debug::timing_log_scalar( + "sanotts.decoder_ms", + engine::debug::elapsed_ms(decoder_start)); + engine::debug::trace_log_scalar("sanotts.token_count", token_count); + engine::debug::trace_log_scalar("sanotts.frames", frames); + engine::debug::trace_log_scalar( + "sanotts.output_samples", + static_cast(out.samples.size())); + engine::debug::timing_log_scalar( + "session.wall_ms", + engine::debug::elapsed_ms(total_start)); + return out; +} + +} // namespace engine::models::sanotts diff --git a/src/community_models/sanotts/runtime.cpp b/src/community_models/sanotts/runtime.cpp new file mode 100644 index 000000000..64efa1ba0 --- /dev/null +++ b/src/community_models/sanotts/runtime.cpp @@ -0,0 +1,658 @@ +#include "engine/community_models/sanotts/runtime.h" + +#include "graph_common.h" + +#include "engine/framework/audio/istft_graph.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/norm_modules.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::sanotts { +namespace { + +namespace core = engine::core; +namespace modules = engine::modules; +using namespace engine::models::sanotts::graph; // shared graph helpers + +constexpr size_t kIoArenaBytes = 8ULL * 1024ULL * 1024ULL; +constexpr size_t kGraphArenaBytes = 128ULL * 1024ULL * 1024ULL; +constexpr size_t kWeightArenaBytes = 32ULL * 1024ULL * 1024ULL; + +// The decoder's norms are nn.LayerNorm(eps=1e-6), NOT torch's 1e-5 default. +// The difference compounds through the ConvNeXt blocks and is then amplified +// by the exp() in the magnitude head; the reference implementations document +// losing 0.06 of correlation and a third of the output amplitude to exactly +// this constant. +constexpr float kLayerNormEps = 1.0e-6F; +constexpr float kDcBlockPole = 0.9973F; +constexpr double kPi = 3.14159265358979323846; + +/** Tensor count implied by the config -- 103 for heart-nano, 117 for heart. + * Kept in lockstep with the inventory validate_nano_tensors() builds. */ +size_t expected_tensor_count(const SanoTtsConfig & config) { + const auto duration = 5 + 5 * config.duration_depth; + const auto acoustic = + 7 + 5 * (config.acoustic_token_depth + config.acoustic_depth); + const auto decoder = 10 + 9 * config.blocks; + return static_cast(duration + acoustic + decoder); +} + +core::TensorValue channel_last_layer_norm( + core::ModuleBuildContext & ctx, + const SanoTtsBackendWeights & weights, + const core::TensorValue & input, + const std::string & prefix, + int64_t channels) { + return modules::LayerNormModule({channels, kLayerNormEps, true, true}).build( + ctx, + input, + { + weight(weights, prefix + ".weight"), + weight(weights, prefix + ".bias"), + }); +} + +// ---- ATen-compatible noise ---------------------------------------------- +// +// The decoder is noise-fed, so a rendering is only reproducible if the noise +// stream is. This is PyTorch's CPU path exactly: MT19937 seeded from the low +// 32 bits, a 24-bit float uniform, and Box-Muller in blocks of 16 +// (aten/src/ATen/native/DistributionTemplates.h). The integer half is +// bit-exact against torch.rand; the Gaussian half goes through logf/cosf/ +// sinf, which differ by a few ulp across libm builds -- the reference +// implementations document the same bound (at most ~2e-6 per draw). + +constexpr int kMtN = 624; +constexpr int kMtM = 397; + +struct AtenMt19937 { + uint32_t state[kMtN]; + int left = 1; + int next = 0; + + explicit AtenMt19937(uint64_t seed) { + // ATen mt19937_engine::init_with_uint32 -- only the low 32 bits used. + state[0] = static_cast(seed & 0xFFFFFFFFULL); + for (int i = 1; i < kMtN; ++i) { + state[i] = 1812433253U * (state[i - 1] ^ (state[i - 1] >> 30)) + + static_cast(i); + } + } + + static uint32_t twist(uint32_t u, uint32_t v) { + const uint32_t mixed = (u & 0x80000000U) | (v & 0x7FFFFFFFU); + return (mixed >> 1) ^ ((v & 1U) != 0U ? 0x9908B0DFU : 0U); + } + + void next_state() { + left = kMtN; + next = 0; + int i = 0; + for (; i < kMtN - kMtM; ++i) { + state[i] = state[i + kMtM] ^ twist(state[i], state[i + 1]); + } + for (; i < kMtN - 1; ++i) { + state[i] = state[i + kMtM - kMtN] ^ twist(state[i], state[i + 1]); + } + state[kMtN - 1] = state[kMtM - 1] ^ twist(state[kMtN - 1], state[0]); + } + + uint32_t random() { + if (--left <= 0) { + next_state(); + } + uint32_t y = state[next++]; + y ^= (y >> 11); + y ^= (y << 7) & 0x9D2C5680U; + y ^= (y << 15) & 0xEFC60000U; + y ^= (y >> 18); + return y; + } + + /** at::uniform_real_distribution: (raw & (2^24 - 1)) * 2^-24. */ + float uniform() { + return static_cast(random() & 0xFFFFFFU) * (1.0F / 16777216.0F); + } +}; + +/** ATen normal_fill_16, mean 0 std 1, float32 throughout. In place. */ +void normal_fill_16(float * d) { + for (int j = 0; j < 8; ++j) { + const float u1 = 1.0F - d[j]; + const float u2 = d[j + 8]; + const float radius = std::sqrt(-2.0F * std::log(u1)); + const float theta = static_cast(2.0 * kPi) * u2; + d[j] = radius * std::cos(theta); + d[j + 8] = radius * std::sin(theta); + } +} + +std::vector seeded_noise(uint64_t seed, int64_t channels, int64_t frames) { + const int64_t size = channels * frames; + if (size < 16) { + // torch dispatches sizes under 16 to a scalar path this does not + // implement; the decoder always asks for channels*frames above that. + throw std::runtime_error("sanoTTS seeded noise needs at least 16 values"); + } + AtenMt19937 gen(seed); + std::vector out(static_cast(size)); + for (auto & value : out) { + value = gen.uniform(); + } + int64_t i = 0; + for (; i + 16 <= size; i += 16) { + normal_fill_16(out.data() + i); + } + if (size % 16 != 0) { + // Torch draws a FRESH block of 16 (continuing the same stream) and + // overwrites the last 16 values with it; the loop's remainder is + // discarded, so the tail is not simply its leftover. + float tail[16]; + for (float & value : tail) { + value = gen.uniform(); + } + normal_fill_16(tail); + std::memcpy(out.data() + size - 16, tail, sizeof(tail)); + } + return out; +} + +// ---- decoder graph ------------------------------------------------------- + +struct DecoderGraph : GraphResources { + int64_t frames = 0; + ggml_tensor * context = nullptr; + ggml_tensor * feats = nullptr; + ggml_tensor * noise = nullptr; + ggml_tensor * spectrum = nullptr; +}; + +/** Frame-stage acoustic blocks -> mel-100 -> ConvNeXt decoder -> the + * [log-magnitude | phase] spectrum rows the host iSTFT consumes. */ +std::unique_ptr build_decoder_graph( + const SanoTtsBackendWeights & weights, + const SanoTtsConfig & config, + ggml_backend_t backend, + core::BackendType backend_type, + int64_t frames) { + auto out = std::make_unique(); + out->backend = backend; + out->frames = frames; + out->io_context.reset(ggml_init({kIoArenaBytes, nullptr, true})); + out->graph_context.reset(ggml_init({kGraphArenaBytes, nullptr, true})); + if (out->io_context == nullptr || out->graph_context == nullptr) { + throw std::runtime_error("sanoTTS failed to create decoder graph contexts"); + } + core::ModuleBuildContext io_ctx{ + out->io_context.get(), + "sanotts.decoder.io", + backend_type, + }; + core::ModuleBuildContext ctx{ + out->graph_context.get(), + "sanotts.decoder", + backend_type, + }; + auto context = core::make_tensor( + io_ctx, + GGML_TYPE_F32, + core::TensorShape::from_dims({1, config.acoustic_hidden, frames})); + auto feats = core::make_tensor( + io_ctx, + GGML_TYPE_F32, + core::TensorShape::from_dims({1, 3, frames})); + auto noise = core::make_tensor( + io_ctx, + GGML_TYPE_F32, + core::TensorShape::from_dims({1, config.noise_channels, frames})); + ggml_set_input(context.tensor); + ggml_set_input(feats.tensor); + ggml_set_input(noise.tensor); + + auto mel = acoustic_frame_stage( + ctx, + weights, + context, + feats, + config.acoustic_hidden, + config.acoustic_depth, + config.acoustic_kernel, + config.mels); + + // ConvNeXt decoder. Noise-fed: the noise adapter's output is added to the + // mel embedding before the first norm. + const int embed_padding = static_cast(config.embed_kernel / 2); + auto value = conv1d( + ctx, + weights, + mel, + "decoder.embed", + config.dim, + config.embed_kernel, + embed_padding); + value = add( + ctx, + value, + conv1d( + ctx, + weights, + noise, + "decoder.noise_adapter", + config.dim, + config.embed_kernel, + embed_padding)); + + // Channel-last from here: LayerNorm and the pointwise projections act on + // the channel axis, exactly as the PyTorch modules do. + auto value_cl = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, value); + value_cl = channel_last_layer_norm(ctx, weights, value_cl, "decoder.norm", config.dim); + for (int64_t block = 0; block < config.blocks; ++block) { + const std::string prefix = "decoder.blocks." + std::to_string(block); + const auto residual = value_cl; + auto branch_cm = contiguous( + ctx, + modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, value_cl)); + auto branch = core::wrap_tensor( + ggml_conv_1d_dw( + ctx.ggml, + contiguous(ctx, weight(weights, prefix + ".dwconv.weight")).tensor, + branch_cm.tensor, + 1, + static_cast(config.dw_kernel / 2), + 1), + core::TensorShape::from_dims({1, config.dim, frames}), + GGML_TYPE_F32); + auto * dw_bias = ggml_reshape_3d( + ctx.ggml, + weight(weights, prefix + ".dwconv.bias").tensor, + 1, + config.dim, + 1); + branch = core::wrap_tensor( + ggml_add(ctx.ggml, branch.tensor, dw_bias), + branch.shape, + GGML_TYPE_F32); + auto branch_cl = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(ctx, branch); + branch_cl = channel_last_layer_norm(ctx, weights, branch_cl, prefix + ".norm", config.dim); + branch_cl = modules::LinearModule({config.dim, config.pw_hidden, true}).build( + ctx, + branch_cl, + { + weight(weights, prefix + ".pwconv1.weight"), + weight(weights, prefix + ".pwconv1.bias"), + }); + // Exact erf GELU -- what nn.GELU() computes by default, NOT the tanh + // approximation. + branch_cl = modules::GeluModule({modules::GeluApproximation::ExactErf}) + .build(ctx, branch_cl); + branch_cl = modules::LinearModule({config.pw_hidden, config.dim, true}).build( + ctx, + branch_cl, + { + weight(weights, prefix + ".pwconv2.weight"), + weight(weights, prefix + ".pwconv2.bias"), + }); + const auto branch_source = contiguous(ctx, branch_cl); + branch_cl = core::wrap_tensor( + ggml_mul( + ctx.ggml, + branch_source.tensor, + weight(weights, prefix + ".gamma").tensor), + branch_cl.shape, + GGML_TYPE_F32); + value_cl = add(ctx, residual, branch_cl); + } + value_cl = channel_last_layer_norm(ctx, weights, value_cl, "decoder.final_norm", config.dim); + auto spectrum = modules::LinearModule({config.dim, config.n_fft + 2, true}).build( + ctx, + value_cl, + { + weight(weights, "decoder.head.weight"), + weight(weights, "decoder.head.bias"), + }); + spectrum = contiguous(ctx, spectrum); + out->context = context.tensor; + out->feats = feats.tensor; + out->noise = noise.tensor; + out->spectrum = spectrum.tensor; + ggml_set_output(out->spectrum); + out->graph = ggml_new_graph_custom(ctx.ggml, 16384, false); + ggml_build_forward_expand(out->graph, out->spectrum); + allocate_graph(*out); + return out; +} + +// ---- SHA-256 for the derive-from-text seed ------------------------------- + +constexpr uint32_t kShaK[64] = { + 0x428A2F98U, 0x71374491U, 0xB5C0FBCFU, 0xE9B5DBA5U, 0x3956C25BU, 0x59F111F1U, + 0x923F82A4U, 0xAB1C5ED5U, 0xD807AA98U, 0x12835B01U, 0x243185BEU, 0x550C7DC3U, + 0x72BE5D74U, 0x80DEB1FEU, 0x9BDC06A7U, 0xC19BF174U, 0xE49B69C1U, 0xEFBE4786U, + 0x0FC19DC6U, 0x240CA1CCU, 0x2DE92C6FU, 0x4A7484AAU, 0x5CB0A9DCU, 0x76F988DAU, + 0x983E5152U, 0xA831C66DU, 0xB00327C8U, 0xBF597FC7U, 0xC6E00BF3U, 0xD5A79147U, + 0x06CA6351U, 0x14292967U, 0x27B70A85U, 0x2E1B2138U, 0x4D2C6DFCU, 0x53380D13U, + 0x650A7354U, 0x766A0ABBU, 0x81C2C92EU, 0x92722C85U, 0xA2BFE8A1U, 0xA81A664BU, + 0xC24B8B70U, 0xC76C51A3U, 0xD192E819U, 0xD6990624U, 0xF40E3585U, 0x106AA070U, + 0x19A4C116U, 0x1E376C08U, 0x2748774CU, 0x34B0BCB5U, 0x391C0CB3U, 0x4ED8AA4AU, + 0x5B9CCA4FU, 0x682E6FF3U, 0x748F82EEU, 0x78A5636FU, 0x84C87814U, 0x8CC70208U, + 0x90BEFFFAU, 0xA4506CEBU, 0xBEF9A3F7U, 0xC67178F2U, +}; + +uint32_t rotr32(uint32_t v, int n) { + return (v >> n) | (v << (32 - n)); +} + +void sha256_block(uint32_t * h, const unsigned char * block) { + uint32_t w[64]; + for (int i = 0; i < 16; ++i) { + w[i] = (static_cast(block[4 * i]) << 24) | + (static_cast(block[4 * i + 1]) << 16) | + (static_cast(block[4 * i + 2]) << 8) | + static_cast(block[4 * i + 3]); + } + for (int i = 16; i < 64; ++i) { + const uint32_t s0 = rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ (w[i - 15] >> 3); + const uint32_t s1 = rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ (w[i - 2] >> 10); + w[i] = w[i - 16] + s0 + w[i - 7] + s1; + } + uint32_t a = h[0]; + uint32_t b = h[1]; + uint32_t c = h[2]; + uint32_t d = h[3]; + uint32_t e = h[4]; + uint32_t f = h[5]; + uint32_t g = h[6]; + uint32_t hh = h[7]; + for (int i = 0; i < 64; ++i) { + const uint32_t s1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25); + const uint32_t ch = (e & f) ^ ((~e) & g); + const uint32_t s0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22); + const uint32_t maj = (a & b) ^ (a & c) ^ (b & c); + const uint32_t t1 = hh + s1 + ch + kShaK[i] + w[i]; + const uint32_t t2 = s0 + maj; + hh = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + h[0] += a; + h[1] += b; + h[2] += c; + h[3] += d; + h[4] += e; + h[5] += f; + h[6] += g; + h[7] += hh; +} + +// ---- host post-processing ------------------------------------------------ + +std::vector periodic_hann_window(int64_t n_fft) { + std::vector window(static_cast(n_fft)); + for (int64_t i = 0; i < n_fft; ++i) { + window[static_cast(i)] = static_cast( + 0.5 - 0.5 * std::cos(2.0 * kPi * static_cast(i) / + static_cast(n_fft))); + } + return window; +} + +/** H(z) = (1 - z^-1) / (1 - R z^-1), 2 MAC/sample, zero initial state -- + * the same DC blocker the reference runtimes apply after the iSTFT. */ +void dc_block_in_place(std::vector & samples) { + float x1 = 0.0F; + float y1 = 0.0F; + for (float & sample : samples) { + const float x = sample; + const float y = x - x1 + kDcBlockPole * y1; + x1 = x; + y1 = y; + sample = y; + } +} + +} // namespace + +uint64_t sanotts_text_seed(const std::string & text) { + uint32_t h[8] = {0x6A09E667U, 0xBB67AE85U, 0x3C6EF372U, 0xA54FF53AU, + 0x510E527FU, 0x9B05688CU, 0x1F83D9ABU, 0x5BE0CD19U}; + const auto * data = reinterpret_cast(text.data()); + const size_t len = text.size(); + const size_t full = len / 64; + const size_t rem = len % 64; + for (size_t i = 0; i < full; ++i) { + sha256_block(h, data + i * 64); + } + unsigned char tail[128] = {0}; + std::memcpy(tail, data + full * 64, rem); + tail[rem] = 0x80; + const size_t tail_len = rem < 56 ? 64 : 128; + const uint64_t bits = static_cast(len) * 8U; + for (int i = 0; i < 8; ++i) { + tail[tail_len - 1 - static_cast(i)] = + static_cast((bits >> (8 * i)) & 0xFFU); + } + sha256_block(h, tail); + if (tail_len == 128) { + sha256_block(h, tail + 64); + } + // == int.from_bytes(sha256(text).digest()[:8], "big"); ATen seeding then + // keeps only the low 32 bits, exactly as the reference runtimes do. + return (static_cast(h[0]) << 32) | static_cast(h[1]); +} + +struct SanoTtsNativeRuntime::State : BackendState { + State(const std::shared_ptr & assets_in, + core::BackendConfig backend_config) + : BackendState( + assets_in, + backend_config, + assets_in == nullptr ? 0 : expected_tensor_count(assets_in->config), + kWeightArenaBytes) {} + + runtime::CacheSlots> duration_graphs{4}; + runtime::CacheSlots> token_graphs{4}; + runtime::CacheSlots> decoder_graphs{2}; +}; + +SanoTtsNativeRuntime::SanoTtsNativeRuntime( + std::shared_ptr assets, + core::BackendConfig backend_config) + : state_(std::make_unique(std::move(assets), backend_config)) {} + +SanoTtsNativeRuntime::~SanoTtsNativeRuntime() = default; + +runtime::AudioBuffer SanoTtsNativeRuntime::synthesize( + const std::vector & token_ids, + const SanoTtsGenerationOptions & options) { + const auto & config = state_->assets->config; + const auto token_count = static_cast(token_ids.size()); + if (token_count <= 0) { + throw std::runtime_error("sanoTTS requires at least one phoneme token"); + } + const auto total_start = std::chrono::steady_clock::now(); + + // -- durations --------------------------------------------------------- + const auto duration_start = std::chrono::steady_clock::now(); + auto & duration = cached_graph( + state_->duration_graphs, + token_count, + "sanotts.duration_graph.cache_hit", + [&] { + return build_front_graph( + state_->duration_weights_ref(), + duration_stage_spec( + config.vocab_size, + config.duration_hidden, + config.duration_depth, + config.duration_kernel), + state_->duration_backend_value(), + state_->duration_backend_type(), + token_count, + kIoArenaBytes, + kGraphArenaBytes); + }); + write_i32_input( + duration.tokens, core::TensorShape::from_dims({1, token_count}), token_ids); + write_f32_input( + duration.feats, + core::TensorShape::from_dims({1, 3, token_count}), + duration_features(token_count, config.duration_max_tokens)); + compute_graph(duration, "sanoTTS duration"); + const auto log_duration = core::read_tensor_f32(duration.output); + if (static_cast(log_duration.size()) != token_count) { + throw std::runtime_error("sanoTTS duration graph returned invalid output"); + } + int64_t frames = 0; + const auto durations = round_durations( + log_duration, + static_cast(options.speaking_rate), + config.duration_max_frames, + config.duration_max_tokens * config.duration_max_frames, + frames); + if (frames < 2) { + throw std::runtime_error("sanoTTS duration predictor produced too few frames"); + } + engine::debug::timing_log_scalar( + "sanotts.duration_ms", + engine::debug::elapsed_ms(duration_start)); + + // -- token-stage acoustic context -------------------------------------- + const auto acoustic_start = std::chrono::steady_clock::now(); + auto & token_graph = cached_graph( + state_->token_graphs, + token_count, + "sanotts.token_graph.cache_hit", + [&] { + return build_front_graph( + *state_->weights, + token_stage_spec( + config.vocab_size, + config.acoustic_hidden, + config.acoustic_token_depth, + config.acoustic_kernel), + state_->backend.value, + state_->backend_type, + token_count, + kIoArenaBytes, + kGraphArenaBytes); + }); + write_i32_input( + token_graph.tokens, core::TensorShape::from_dims({1, token_count}), token_ids); + write_f32_input( + token_graph.feats, + core::TensorShape::from_dims({1, 2, token_count}), + token_features(token_count, durations)); + compute_graph(token_graph, "sanoTTS token context"); + const auto token_context = core::read_tensor_f32(token_graph.output); + if (static_cast(token_context.size()) != + config.acoustic_hidden * token_count) { + throw std::runtime_error("sanoTTS token graph returned invalid output"); + } + const auto noise = seeded_noise(options.seed, config.noise_channels, frames); + engine::debug::timing_log_scalar( + "sanotts.acoustic_ms", + engine::debug::elapsed_ms(acoustic_start)); + + // -- frame stage + decoder --------------------------------------------- + const auto decoder_start = std::chrono::steady_clock::now(); + auto & decoder = cached_graph( + state_->decoder_graphs, + frames, + "sanotts.decoder_graph.cache_hit", + [&] { + return build_decoder_graph( + *state_->weights, + config, + state_->backend.value, + state_->backend_type, + frames); + }); + write_f32_input( + decoder.context, + core::TensorShape::from_dims({1, config.acoustic_hidden, frames}), + expand_context(token_context, durations, config.acoustic_hidden, token_count, frames)); + write_f32_input( + decoder.feats, + core::TensorShape::from_dims({1, 3, frames}), + frame_features(token_count, durations, frames)); + write_f32_input( + decoder.noise, + core::TensorShape::from_dims({1, config.noise_channels, frames}), + noise); + compute_graph(decoder, "sanoTTS decoder"); + auto spectrum = core::read_tensor_f32(decoder.spectrum); + const int64_t out_dim = config.n_fft + 2; + if (static_cast(spectrum.size()) != frames * out_dim) { + throw std::runtime_error("sanoTTS decoder graph returned invalid output"); + } + engine::debug::timing_log_scalar( + "sanotts.decoder_ms", + engine::debug::elapsed_ms(decoder_start)); + + // -- iSTFT + DC block --------------------------------------------------- + const auto istft_start = std::chrono::steady_clock::now(); + // Bin 0 and Nyquist stay zeroed: the mag*(cos,sin) parametrisation + // phase-collapses at bin 0, which is where the frame-DC artefact came + // from. -inf log-magnitude exps to exactly 0. + const int64_t bins = config.n_fft / 2 + 1; + for (int64_t frame = 0; frame < frames; ++frame) { + float * row = spectrum.data() + frame * out_dim; + row[0] = -std::numeric_limits::infinity(); + row[bins - 1] = -std::numeric_limits::infinity(); + } + engine::audio::HostLogMagnitudePhaseISTFTConfig istft_config; + istft_config.frames = frames; + istft_config.n_fft = config.n_fft; + istft_config.hop_length = config.hop_length; + istft_config.out_dim = out_dim; + istft_config.threads = static_cast(state_->threads); + engine::audio::HostLogMagnitudePhaseISTFT istft(istft_config); + auto istft_result = istft.compute(spectrum, periodic_hann_window(config.n_fft)); + // The framework trims (n_fft - hop)/2 per side; torch.istft(center=True) + // trims n_fft/2. Drop the extra hop/2 per side so lengths and content + // match the reference exactly: (frames - 1) * hop samples. + const auto edge = static_cast(config.hop_length / 2); + const size_t expected = static_cast(frames) * static_cast(config.hop_length); + if (istft_result.audio.size() != expected || istft_result.audio.size() < 2 * edge) { + throw std::runtime_error("sanoTTS iSTFT returned an unexpected sample count"); + } + std::vector samples( + istft_result.audio.begin() + static_cast(edge), + istft_result.audio.end() - static_cast(edge)); + dc_block_in_place(samples); + engine::debug::timing_log_scalar( + "sanotts.istft_ms", + engine::debug::elapsed_ms(istft_start)); + + runtime::AudioBuffer out; + out.sample_rate = static_cast(config.sample_rate); + out.channels = 1; + out.samples = std::move(samples); + engine::debug::trace_log_scalar("sanotts.token_count", token_count); + engine::debug::trace_log_scalar("sanotts.frames", frames); + engine::debug::trace_log_scalar( + "sanotts.output_samples", + static_cast(out.samples.size())); + engine::debug::timing_log_scalar( + "session.wall_ms", + engine::debug::elapsed_ms(total_start)); + return out; +} + +} // namespace engine::models::sanotts diff --git a/src/community_models/sanotts/session.cpp b/src/community_models/sanotts/session.cpp new file mode 100644 index 000000000..68e50d23f --- /dev/null +++ b/src/community_models/sanotts/session.cpp @@ -0,0 +1,282 @@ +#include "engine/community_models/sanotts/session.h" + +#include "engine/framework/debug/profiler.h" +#include "engine/framework/runtime/options.h" +#include "engine/framework/runtime/spec_backed_model.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::sanotts { +namespace { + +constexpr const char * kFamily = "sanotts"; + +std::shared_ptr require_assets( + std::shared_ptr assets) { + if (assets == nullptr) { + throw std::runtime_error("sanoTTS session requires assets"); + } + return assets; +} + +std::shared_ptr require_contract( + std::shared_ptr contract) { + if (contract == nullptr) { + throw std::runtime_error("sanoTTS session requires a model contract"); + } + return contract; +} + +std::filesystem::path session_path( + const runtime::SessionOptions & options, + const char * key) { + const auto found = options.options.find(key); + return found == options.options.end() + ? std::filesystem::path{} + : std::filesystem::path(found->second); +} + +void validate_session_options( + const runtime::SessionOptions & options, + const engine::model_spec::ModelContract & contract) { + const std::string family_prefix = std::string(kFamily) + "."; + for (const auto & [key, _] : options.options) { + if (key.rfind(family_prefix, 0) == 0 && + contract.session_option_keys.find(key) == + contract.session_option_keys.end()) { + throw std::runtime_error("unknown sanoTTS session option: " + key); + } + } +} + +int64_t chunk_size_from_request(const runtime::TaskRequest & request) { + const auto value = runtime::parse_i64_option( + request.options, + {"text_chunk_size", "chunk_size"}); + const int64_t chunk_size = value.value_or(280); + if (chunk_size <= 0) { + throw std::runtime_error("sanoTTS text_chunk_size must be positive"); + } + return chunk_size; +} + +void validate_chunk_mode(const runtime::TaskRequest & request) { + if (const auto value = runtime::find_option( + request.options, + {"text_chunk_mode", "chunk_mode"})) { + if (*value != "word_budget" && *value != "default") { + throw std::runtime_error("sanoTTS text_chunk_mode must be word_budget"); + } + } +} + +struct RequestOptions { + float speaking_rate = 1.0F; + uint64_t seed = 0; + bool seed_from_text = true; +}; + +RequestOptions parse_request_options(const runtime::TaskRequest & request) { + RequestOptions out; + if (const auto value = runtime::parse_finite_float_option( + request.options, + {"speaking_rate"})) { + out.speaking_rate = *value; + } + if (out.speaking_rate < 0.5F || out.speaking_rate > 2.0F) { + throw std::runtime_error("sanoTTS speaking_rate must be between 0.5 and 2.0"); + } + if (const auto value = runtime::parse_i64_option(request.options, {"seed"})) { + if (*value < 0) { + throw std::runtime_error("sanoTTS seed must not be negative"); + } + out.seed = static_cast(*value); + out.seed_from_text = *value == 0; + } + return out; +} + +void append_pause(runtime::AudioBuffer & output, double seconds) { + if (output.sample_rate <= 0 || seconds <= 0.0) { + return; + } + const auto count = static_cast( + std::llround(seconds * static_cast(output.sample_rate))); + output.samples.insert(output.samples.end(), count, 0.0F); +} + +} // namespace + +SanoTtsSession::SanoTtsSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract) + : RuntimeSessionBase(options), + task_(task), + assets_(require_assets(std::move(assets))), + contract_(require_contract(std::move(contract))) { + if (task_.task != runtime::VoiceTaskKind::Tts || + task_.mode != runtime::RunMode::Offline) { + throw std::runtime_error("sanoTTS only supports offline TTS"); + } + validate_session_options(options, *contract_); + if (assets_->graph == SanoTtsGraph::Nano) { + frontend_ = std::make_unique( + session_path(options, "sanotts.espeak_library_path"), + session_path(options, "sanotts.espeak_data_path"), + assets_->config.duration_max_tokens); + runtime_ = std::make_unique(assets_, options.backend); + } else { + piper_frontend_ = std::make_unique( + session_path(options, "sanotts.espeak_library_path"), + session_path(options, "sanotts.espeak_data_path"), + assets_->piper.espeak_voice, + assets_->piper.phoneme_id_map, + assets_->piper.duration_max_tokens); + piper_runtime_ = std::make_unique(assets_, options.backend); + } +} + +SanoTtsSession::~SanoTtsSession() = default; + +std::string SanoTtsSession::family() const { return kFamily; } +runtime::VoiceTaskKind SanoTtsSession::task_kind() const { return task_.task; } +runtime::RunMode SanoTtsSession::run_mode() const { return task_.mode; } + +void SanoTtsSession::prepare(const runtime::SessionPreparationRequest & request) { + (void)request; + mark_prepared(); +} + +runtime::TaskResult SanoTtsSession::run(const runtime::TaskRequest & request) { + require_prepared("sanoTTS run"); + if (!request.text_input.has_value() || request.text_input->text.empty()) { + throw std::runtime_error("sanoTTS requires --text input"); + } + if (request.audio_input.has_value()) { + throw std::runtime_error("sanoTTS does not accept audio input"); + } + const std::string voice_language = + assets_->graph == SanoTtsGraph::Nano ? "en" : assets_->piper.language; + if (!request.text_input->language.empty() && + request.text_input->language != voice_language && + !(voice_language == "en" && + (request.text_input->language == "en-us" || + request.text_input->language == "English"))) { + throw std::runtime_error( + "this sanoTTS voice supports language '" + voice_language + "' only"); + } + validate_chunk_mode(request); + const int64_t chunk_size = chunk_size_from_request(request); + auto chunks = SanoTtsFrontend::split_text(request.text_input->text, chunk_size); + if (chunks.empty()) { + throw std::runtime_error("sanoTTS text must not be empty"); + } + + const auto request_options = parse_request_options(request); + runtime::AudioBuffer merged; + uint64_t rendered_chunks = 0; + // Renders one chunk, bisecting at whitespace when it phonemizes past the + // duration model's token limit -- the codepoint budget cannot see phoneme + // counts, so a dense 280-codepoint chunk can exceed 207 tokens. + const std::function render_chunk = + [&](const std::string & chunk, int depth) { + SanoTtsEncoded encoded; + try { + encoded = frontend_ != nullptr + ? frontend_->encode(chunk) + : piper_frontend_->encode(chunk); + } catch (const SanoTtsTooLongError &) { + const size_t middle = chunk.size() / 2; + size_t split = std::string::npos; + for (size_t offset = 0; offset < chunk.size(); ++offset) { + const size_t after = middle + offset; + if (after < chunk.size() && chunk[after] == ' ') { + split = after; + break; + } + if (offset <= middle && chunk[middle - offset] == ' ') { + split = middle - offset; + break; + } + } + if (depth >= 8 || split == std::string::npos) { + throw; + } + const std::string left = chunk.substr(0, split); + const std::string right = chunk.substr(split + 1); + render_chunk(left, depth + 1); + append_pause(merged, SanoTtsFrontend::boundary_pause_seconds(left)); + render_chunk(right, depth + 1); + return; + } + runtime::AudioBuffer audio; + if (runtime_ != nullptr) { + SanoTtsGenerationOptions chunk_options; + chunk_options.speaking_rate = request_options.speaking_rate; + // The default seed is derived from the chunk's own text -- + // the reference implementations' sha256(text)[:8] convention + // -- so a given sentence renders identically wherever it + // appears. An explicit seed advances per chunk instead, so + // long-form noise is not reused across chunks. + chunk_options.seed = request_options.seed_from_text + ? sanotts_text_seed(chunk) + : request_options.seed + rendered_chunks; + audio = runtime_->synthesize(encoded.token_ids, chunk_options); + } else { + // The piperlite decoder is deterministic; the seed option is + // documented as ignored for these voices. + SanoTtsPiperGenerationOptions chunk_options; + chunk_options.speaking_rate = request_options.speaking_rate; + audio = piper_runtime_->synthesize(encoded.token_ids, chunk_options); + } + ++rendered_chunks; + runtime::append_audio_buffer(merged, audio); + }; + for (size_t index = 0; index < chunks.size(); ++index) { + if (index != 0) { + append_pause( + merged, + SanoTtsFrontend::boundary_pause_seconds(chunks[index - 1])); + } + render_chunk(chunks[index], 0); + } + for (float & sample : merged.samples) { + sample = std::clamp(sample, -1.0F, 1.0F); + } + engine::debug::trace_log_scalar("sanotts.text_chunk_size", chunk_size); + engine::debug::trace_log_scalar( + "sanotts.text_chunk_count", + static_cast(chunks.size())); + runtime::TaskResult result; + result.audio_output = std::move(merged); + return result; +} + +std::shared_ptr make_sanotts_loader() { + runtime::SpecBackedVoiceModelConfig config; + config.family = kFamily; + config.load_assets = load_sanotts_assets; + config.create_session = []( + const runtime::TaskSpec & task, + const runtime::SessionOptions & options, + std::shared_ptr assets, + std::shared_ptr contract) { + return std::make_unique( + task, + options, + std::move(assets), + std::move(contract)); + }; + return runtime::make_spec_backed_voice_loader(std::move(config)); +} + +} // namespace engine::models::sanotts diff --git a/src/community_models/sopro_tts/acoustic.cpp b/src/community_models/sopro_tts/acoustic.cpp new file mode 100644 index 000000000..6abf24ac9 --- /dev/null +++ b/src/community_models/sopro_tts/acoustic.cpp @@ -0,0 +1,903 @@ +#include "engine/community_models/sopro_tts/acoustic.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/attention/scaled_dot_product_attention.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/positional_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { + +struct SoproDiTBlockWeights { + engine::modules::LinearWeights modulation; // attn_norm.mod.1: dim -> 6 * dim + engine::modules::LinearWeights to_q; + engine::modules::LinearWeights to_k; + engine::modules::LinearWeights to_v; + engine::modules::LinearWeights to_out; + engine::modules::LinearWeights ff_in; + engine::modules::LinearWeights ff_out; +}; + +// A Conv1d with `groups` > 1 and groups != channels, expressed as one +// independent convolution per group (ggml has no grouped conv1d primitive). +struct SoproGroupedConvWeights { + std::vector groups; + int64_t group_in_channels = 0; + int64_t group_out_channels = 0; + int64_t kernel_size = 0; +}; + +struct SoproAcousticWeights { + std::shared_ptr store; + engine::core::TensorValue semantic_token_emb; // [semantic_vocab, latent_dim] + engine::modules::Conv1dWeights prelook_conv1; + engine::modules::Conv1dWeights prelook_conv2; + engine::modules::Conv1dWeights upsampler_in; + engine::modules::Conv1dWeights upsampler_mix; + engine::modules::Conv1dWeights upsampler_out; + engine::modules::Conv1dWeights mu_proj; + engine::modules::LinearWeights input_proj; + engine::modules::LinearWeights cond_mask_proj; + SoproGroupedConvWeights pos_conv1; + SoproGroupedConvWeights pos_conv2; + std::vector blocks; + engine::modules::LinearWeights out_modulation; // out_norm.mod.1: dim -> 2 * dim + engine::modules::LinearWeights out_proj; + // Host-side conditioning projections. + std::vector time_mlp_w0; // [dim, time_embed_dim] + std::vector time_mlp_b0; + std::vector time_mlp_w2; // [dim, dim] + std::vector time_mlp_b2; + std::vector spk_proj_w; // [spk_dim, cond_hidden_dim] + std::vector spk_proj_b; +}; + +namespace { + +// MSVC does not define M_PI; use our own constant, as f5_tts does for the +// same sway-time grid. +constexpr float kPi = 3.14159265358979323846F; + +namespace binding = engine::modules::binding; +namespace mod = engine::modules; + +constexpr float kDiTLayerNormEps = 1.0e-6F; + +// Set SOPRO_DUMP_DIR to write the solver's intermediates as raw f32 for +// stage-by-stage comparison against the reference implementation. +void dump(const std::string & name, const std::vector & values) { + const char * dir = std::getenv("SOPRO_DUMP_DIR"); + if (dir == nullptr) { + return; + } + const std::string path = std::string(dir) + "/" + name + ".f32"; + std::FILE * fh = std::fopen(path.c_str(), "wb"); + if (fh != nullptr) { + std::fwrite(values.data(), sizeof(float), values.size(), fh); + std::fclose(fh); + } +} + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +engine::core::TensorValue dense( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & value) { + return engine::core::wrap_tensor(ggml_cont(ctx.ggml, value.tensor), value.shape, GGML_TYPE_F32); +} + +mod::TransposeConfig swap_channel_time() { + return mod::TransposeConfig{{0, 2, 1, 3}, 3}; +} + +// Zero-pad the time axis of a [B, C, T] tensor. +engine::core::TensorValue pad_time( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & value, + int left, + int right) { + if (left == 0 && right == 0) { + return value; + } + auto contiguous = engine::core::ensure_backend_addressable_layout(ctx, value); + auto shape = contiguous.shape; + shape.dims[2] += left + right; + return engine::core::wrap_tensor( + ggml_pad_ext(ctx.ggml, contiguous.tensor, left, right, 0, 0, 0, 0, 0, 0), + shape, + GGML_TYPE_F32); +} + +// mish(x) = x * tanh(softplus(x)) +engine::core::TensorValue mish( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & value) { + auto * gate = ggml_tanh(ctx.ggml, ggml_softplus(ctx.ggml, value.tensor)); + return engine::core::wrap_tensor( + ggml_mul(ctx.ggml, value.tensor, gate), value.shape, GGML_TYPE_F32); +} + +// x * (1 + scale) + shift, with scale/shift broadcast over the time axis. +engine::core::TensorValue modulate( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & x, + const engine::core::TensorValue & scale, + const engine::core::TensorValue & shift) { + auto scaled = mod::MulModule{}.build(ctx, x, mod::RepeatModule({x.shape}).build(ctx, scale)); + auto sum = mod::AddModule{}.build(ctx, x, scaled); + return mod::AddModule{}.build(ctx, sum, mod::RepeatModule({x.shape}).build(ctx, shift)); +} + +SoproGroupedConvWeights load_grouped_conv( + engine::core::BackendWeightStore & store, + const engine::assets::TensorSource & source, + const std::string & prefix, + engine::assets::TensorStorageType storage_type, + int64_t channels, + int64_t kernel_size, + int64_t groups) { + if (channels % groups != 0) { + throw std::runtime_error(prefix + ": channel count is not divisible by the group count"); + } + SoproGroupedConvWeights out; + out.group_in_channels = channels / groups; + out.group_out_channels = channels / groups; + out.kernel_size = kernel_size; + const auto weight = source.require_f32( + prefix + ".weight", {channels, out.group_in_channels, kernel_size}); + const auto bias = source.require_f32(prefix + ".bias", {channels}); + const auto group_weight_elements = + static_cast(out.group_out_channels * out.group_in_channels * kernel_size); + out.groups.reserve(static_cast(groups)); + for (int64_t group = 0; group < groups; ++group) { + const size_t weight_offset = static_cast(group) * group_weight_elements; + std::vector group_weight( + weight.begin() + static_cast(weight_offset), + weight.begin() + static_cast(weight_offset + group_weight_elements)); + const size_t bias_offset = static_cast(group * out.group_out_channels); + std::vector group_bias( + bias.begin() + static_cast(bias_offset), + bias.begin() + static_cast(bias_offset + out.group_out_channels)); + engine::modules::Conv1dWeights conv; + conv.weight = store.make_from_f32( + engine::core::TensorShape::from_dims( + {out.group_out_channels, out.group_in_channels, kernel_size}), + storage_type, + std::move(group_weight)); + conv.bias = store.make_from_f32( + engine::core::TensorShape::from_dims({out.group_out_channels}), + engine::assets::TensorStorageType::F32, + std::move(group_bias)); + out.groups.push_back(std::move(conv)); + } + return out; +} + +engine::core::TensorValue build_grouped_conv( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & input, + const SoproGroupedConvWeights & weights) { + engine::core::TensorValue result; + for (size_t group = 0; group < weights.groups.size(); ++group) { + auto slice = mod::SliceModule({ + 1, + static_cast(group) * weights.group_in_channels, + weights.group_in_channels, + }).build(ctx, input); + auto convolved = mod::Conv1dModule({ + weights.group_in_channels, weights.group_out_channels, weights.kernel_size, + 1, 0, 1, true, + }).build(ctx, dense(ctx, slice), weights.groups[group]); + result = group == 0 ? convolved : mod::ConcatModule({1}).build(ctx, result, convolved); + } + return result; +} + +std::shared_ptr load_acoustic_weights( + ggml_backend_t backend, + engine::core::BackendType backend_type, + const engine::assets::TensorSource & source, + const SoproModelConfig & config, + size_t weight_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type) { + auto weights = std::make_shared(); + weights->store = std::make_shared( + backend, backend_type, "sopro_tts.acoustic.weights", weight_context_bytes); + auto & store = *weights->store; + const std::string root = "acoustic_head."; + const int64_t latent = config.latent_dim; + const int64_t dim = config.acoustic_dit_dim; + const int64_t mel = config.acoustic_mel_n_mels; + + weights->semantic_token_emb = store.load_f32_tensor( + source, root + "semantic_token_emb.weight", {config.semantic_vocab_size, latent}); + weights->prelook_conv1 = binding::conv1d_from_source( + store, source, root + "semantic_prelook.conv1", conv_storage_type, + latent, latent, config.acoustic_pre_lookahead_frames + 1, true); + weights->prelook_conv2 = binding::conv1d_from_source( + store, source, root + "semantic_prelook.conv2", conv_storage_type, latent, latent, 3, true); + // LearnedCausalUpsampler hidden width is max(8, channels). + const int64_t upsampler_hidden = std::max(8, latent); + weights->upsampler_in = binding::conv1d_from_source( + store, source, root + "semantic_upsampler.in_proj", conv_storage_type, + upsampler_hidden, latent, 1, true); + weights->upsampler_mix = binding::conv1d_from_source( + store, source, root + "semantic_upsampler.mix.conv", conv_storage_type, + upsampler_hidden, upsampler_hidden, config.acoustic_upsampler_kernel_size, true); + weights->upsampler_out = binding::conv1d_from_source( + store, source, root + "semantic_upsampler.out_proj", conv_storage_type, + latent, upsampler_hidden, 1, true); + weights->mu_proj = binding::conv1d_from_source( + store, source, root + "mu_proj", conv_storage_type, config.acoustic_mu_dim, latent, 1, true); + + const int64_t proj_in = mel * 2 + config.acoustic_mu_dim + config.acoustic_spk_dim; + weights->input_proj = binding::linear_from_source( + store, source, root + "input_embed.proj", matmul_storage_type, dim, proj_in, true); + weights->cond_mask_proj = binding::linear_from_source( + store, source, root + "input_embed.cond_mask_proj", matmul_storage_type, dim, 1, false); + weights->pos_conv1 = load_grouped_conv( + store, source, root + "input_embed.pos.conv1", conv_storage_type, + dim, config.acoustic_pos_kernel_size, 16); + weights->pos_conv2 = load_grouped_conv( + store, source, root + "input_embed.pos.conv2", conv_storage_type, + dim, config.acoustic_pos_kernel_size, 16); + + const int64_t inner = config.acoustic_dit_heads * config.acoustic_dit_dim_head; + const int64_t ff_dim = config.acoustic_dit_ff_dim(); + weights->blocks.reserve(static_cast(config.acoustic_dit_depth)); + for (int64_t index = 0; index < config.acoustic_dit_depth; ++index) { + const std::string prefix = root + "blocks." + std::to_string(index); + SoproDiTBlockWeights block; + block.modulation = binding::linear_from_source( + store, source, prefix + ".attn_norm.mod.1", matmul_storage_type, dim * 6, dim, true); + block.to_q = binding::linear_from_source( + store, source, prefix + ".attn.to_q", matmul_storage_type, inner, dim, true); + block.to_k = binding::linear_from_source( + store, source, prefix + ".attn.to_k", matmul_storage_type, inner, dim, true); + block.to_v = binding::linear_from_source( + store, source, prefix + ".attn.to_v", matmul_storage_type, inner, dim, true); + block.to_out = binding::linear_from_source( + store, source, prefix + ".attn.to_out.0", matmul_storage_type, dim, inner, true); + block.ff_in = binding::linear_from_source( + store, source, prefix + ".ff.0", matmul_storage_type, ff_dim, dim, true); + block.ff_out = binding::linear_from_source( + store, source, prefix + ".ff.3", matmul_storage_type, dim, ff_dim, true); + weights->blocks.push_back(std::move(block)); + } + weights->out_modulation = binding::linear_from_source( + store, source, root + "out_norm.mod.1", matmul_storage_type, dim * 2, dim, true); + weights->out_proj = binding::linear_from_source( + store, source, root + "out_proj", matmul_storage_type, mel, dim, true); + + weights->time_mlp_w0 = source.require_f32( + root + "time_mlp.0.weight", {dim, config.acoustic_time_embed_dim}); + weights->time_mlp_b0 = source.require_f32(root + "time_mlp.0.bias", {dim}); + weights->time_mlp_w2 = source.require_f32(root + "time_mlp.2.weight", {dim, dim}); + weights->time_mlp_b2 = source.require_f32(root + "time_mlp.2.bias", {dim}); + weights->spk_proj_w = source.require_f32( + root + "spk_proj.weight", {config.acoustic_spk_dim, config.cond_hidden_dim}); + weights->spk_proj_b = source.require_f32(root + "spk_proj.bias", {config.acoustic_spk_dim}); + + store.upload(); + return weights; +} + +std::vector affine( + const std::vector & weight, + const std::vector & bias, + const std::vector & input, + int64_t in_dim, + int64_t out_dim) { + std::vector out(static_cast(out_dim), 0.0F); + for (int64_t o = 0; o < out_dim; ++o) { + const float * row = weight.data() + static_cast(o * in_dim); + double sum = bias[static_cast(o)]; + for (int64_t i = 0; i < in_dim; ++i) { + sum += static_cast(row[i]) * static_cast(input[static_cast(i)]); + } + out[static_cast(o)] = static_cast(sum); + } + return out; +} + +} // namespace + +std::vector build_time_grid(int64_t steps, float sway_coefficient) { + if (steps < 1) { + throw std::runtime_error("Sopro acoustic solver requires at least one step"); + } + std::vector times(static_cast(steps + 1), 0.0F); + for (int64_t i = 0; i <= steps; ++i) { + times[static_cast(i)] = static_cast(i) / static_cast(steps); + } + if (std::fabs(sway_coefficient) <= 1.0e-8F) { + return times; + } + for (auto & value : times) { + value += sway_coefficient * + (std::cos(0.5F * kPi * value) - 1.0F + value); + } + return times; +} + +std::vector sinusoidal_time_embedding(float t, int64_t dim, float scale) { + const int64_t half = std::max(1, dim / 2); + const int64_t denominator = std::max(1, half - 1); + std::vector out(static_cast(half * 2), 0.0F); + for (int64_t i = 0; i < half; ++i) { + const float frequency = std::exp( + static_cast(i) * (-(std::log(10000.0F) / static_cast(denominator)))); + const float argument = scale * t * frequency; + out[static_cast(i)] = std::sin(argument); + out[static_cast(half + i)] = std::cos(argument); + } + return out; +} + +// One graph produces mu (constant across solver steps); the other evaluates the +// DiT velocity field and is replayed once per Euler step. +struct SoproAcousticGraphs { + SoproAcousticGraphs( + ggml_backend_t backend_in, + engine::core::BackendType backend_type, + size_t graph_context_bytes, + const SoproModelConfig & config_in, + std::shared_ptr weights_in, + int64_t token_count, + int64_t frame_count) + : backend(backend_in), + weights(std::move(weights_in)), + tokens(token_count), + frames(frame_count), + config(&config_in) { + if (backend == nullptr || weights == nullptr) { + throw std::runtime_error("Sopro acoustic graphs require a backend and weights"); + } + if (tokens <= 0 || frames <= 0) { + throw std::runtime_error("Sopro acoustic graphs require positive lengths"); + } + build_conditioning(backend_type, graph_context_bytes, config_in); + build_velocity(backend_type, graph_context_bytes, config_in); + } + + ~SoproAcousticGraphs() { + if (conditioning_allocr != nullptr) { + ggml_gallocr_free(conditioning_allocr); + } + if (velocity_allocr != nullptr) { + ggml_gallocr_free(velocity_allocr); + } + } + + bool matches(const SoproAcousticWeights & other, int64_t token_count, int64_t frame_count) const noexcept { + return weights.get() == &other && tokens == token_count && frames == frame_count; + } + + void build_conditioning( + engine::core::BackendType backend_type, + size_t graph_context_bytes, + const SoproModelConfig & config_in) { + ggml_init_params params{graph_context_bytes, nullptr, true}; + conditioning_ctx.reset(ggml_init(params)); + if (conditioning_ctx == nullptr) { + throw std::runtime_error("failed to initialize the Sopro conditioning graph context"); + } + engine::core::ModuleBuildContext ctx{ + conditioning_ctx.get(), "sopro_tts.acoustic.conditioning", backend_type}; + const int64_t latent = config_in.latent_dim; + const int64_t upsampler_hidden = std::max(8, latent); + + token_input = ggml_new_tensor_1d(ctx.ggml, GGML_TYPE_I32, tokens); + ggml_set_input(token_input); + expand_index = ggml_new_tensor_1d(ctx.ggml, GGML_TYPE_I32, frames); + ggml_set_input(expand_index); + + // semantic_latents: [1, latent, tokens] + auto * rows = ggml_get_rows(ctx.ggml, weights->semantic_token_emb.tensor, token_input); + auto latents = engine::core::wrap_tensor( + rows, engine::core::TensorShape::from_dims({1, tokens, latent}), GGML_TYPE_F32); + latents = dense(ctx, mod::TransposeModule(swap_channel_time()).build(ctx, latents)); + + // PreLookahead: right-pad the lookahead, conv, then a causal 3-tap conv. + { + const auto lookahead = static_cast(config_in.acoustic_pre_lookahead_frames); + auto y = pad_time(ctx, latents, 0, lookahead); + y = mod::Conv1dModule({latent, latent, lookahead + 1, 1, 0, 1, true}) + .build(ctx, y, weights->prelook_conv1); + y = mod::LeakyReluModule({0.1F}).build(ctx, y); + y = pad_time(ctx, y, 2, 0); + y = mod::Conv1dModule({latent, latent, 3, 1, 0, 1, true}) + .build(ctx, y, weights->prelook_conv2); + latents = mod::AddModule{}.build(ctx, latents, y); + } + + // LearnedCausalUpsampler: nearest-index expansion onto the mel grid, + // then a residual causal mixer. + engine::core::TensorValue expanded; + { + auto btc = dense(ctx, mod::TransposeModule(swap_channel_time()).build(ctx, latents)); + auto * gathered = ggml_get_rows( + ctx.ggml, + ggml_reshape_2d(ctx.ggml, btc.tensor, latent, tokens), + expand_index); + expanded = dense(ctx, mod::TransposeModule(swap_channel_time()).build( + ctx, + engine::core::wrap_tensor( + gathered, engine::core::TensorShape::from_dims({1, frames, latent}), + GGML_TYPE_F32))); + } + const auto mix_kernel = static_cast(config_in.acoustic_upsampler_kernel_size); + auto hidden = mod::Conv1dModule({latent, upsampler_hidden, 1, 1, 0, 1, true}) + .build(ctx, expanded, weights->upsampler_in); + hidden = mod::SiluModule{}.build(ctx, hidden); + hidden = pad_time(ctx, hidden, mix_kernel - 1, 0); + hidden = mod::Conv1dModule({upsampler_hidden, upsampler_hidden, mix_kernel, 1, 0, 1, true}) + .build(ctx, hidden, weights->upsampler_mix); + hidden = mod::SiluModule{}.build(ctx, hidden); + hidden = mod::Conv1dModule({upsampler_hidden, latent, 1, 1, 0, 1, true}) + .build(ctx, hidden, weights->upsampler_out); + hidden = mod::AddModule{}.build(ctx, expanded, hidden); + hidden = mod::Conv1dModule({latent, config_in.acoustic_mu_dim, 1, 1, 0, 1, true}) + .build(ctx, hidden, weights->mu_proj); + hidden = engine::core::ensure_backend_addressable_layout(ctx, hidden); + mu_output = hidden.tensor; + ggml_set_output(mu_output); + conditioning_graph = ggml_new_graph_custom(conditioning_ctx.get(), 65536, false); + ggml_build_forward_expand(conditioning_graph, mu_output); + conditioning_allocr = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend)); + if (conditioning_allocr == nullptr || + !ggml_gallocr_reserve(conditioning_allocr, conditioning_graph) || + !ggml_gallocr_alloc_graph(conditioning_allocr, conditioning_graph)) { + throw std::runtime_error("failed to allocate the Sopro conditioning graph"); + } + } + + void build_velocity( + engine::core::BackendType backend_type, + size_t graph_context_bytes, + const SoproModelConfig & config_in) { + ggml_init_params params{graph_context_bytes, nullptr, true}; + velocity_ctx.reset(ggml_init(params)); + if (velocity_ctx == nullptr) { + throw std::runtime_error("failed to initialize the Sopro velocity graph context"); + } + engine::core::ModuleBuildContext ctx{ + velocity_ctx.get(), "sopro_tts.acoustic.velocity", backend_type}; + const int64_t mel = config_in.acoustic_mel_n_mels; + const int64_t mu_dim = config_in.acoustic_mu_dim; + const int64_t spk_dim = config_in.acoustic_spk_dim; + const int64_t dim = config_in.acoustic_dit_dim; + const int64_t heads = config_in.acoustic_dit_heads; + const int64_t head_dim = config_in.acoustic_dit_dim_head; + const int64_t inner = heads * head_dim; + const int64_t ff_dim = config_in.acoustic_dit_ff_dim(); + + const auto mel_shape = engine::core::TensorShape::from_dims({1, mel, frames}); + const auto mu_shape = engine::core::TensorShape::from_dims({1, mu_dim, frames}); + const auto mask_shape = engine::core::TensorShape::from_dims({1, 1, frames}); + x_input = engine::core::make_tensor(ctx, GGML_TYPE_F32, mel_shape).tensor; + cond_mel_input = engine::core::make_tensor(ctx, GGML_TYPE_F32, mel_shape).tensor; + cond_mask_input = engine::core::make_tensor(ctx, GGML_TYPE_F32, mask_shape).tensor; + mu_input = engine::core::make_tensor(ctx, GGML_TYPE_F32, mu_shape).tensor; + spk_input = engine::core::make_tensor( + ctx, GGML_TYPE_F32, engine::core::TensorShape::from_dims({1, 1, spk_dim})).tensor; + emb_input = engine::core::make_tensor( + ctx, GGML_TYPE_F32, engine::core::TensorShape::from_dims({1, 1, dim})).tensor; + // The solver replays this graph once per Euler step and only re-uploads + // x_t and the time embedding, so every leaf must survive the allocator. + for (ggml_tensor * leaf : + {x_input, cond_mel_input, cond_mask_input, mu_input, spk_input, emb_input}) { + ggml_set_input(leaf); + } + positions = ggml_new_tensor_1d(ctx.ggml, GGML_TYPE_I32, frames); + ggml_set_input(positions); + + auto to_btc = [&](ggml_tensor * tensor, int64_t channels) { + auto value = engine::core::wrap_tensor( + tensor, engine::core::TensorShape::from_dims({1, channels, frames}), GGML_TYPE_F32); + return dense(ctx, mod::TransposeModule(swap_channel_time()).build(ctx, value)); + }; + auto emb = engine::core::wrap_tensor( + emb_input, engine::core::TensorShape::from_dims({1, 1, dim}), GGML_TYPE_F32); + + // InputEmbedding.proj over [x_t | cond_mel | mu | spk]. + auto features = mod::ConcatModule({2}).build(ctx, to_btc(x_input, mel), to_btc(cond_mel_input, mel)); + features = mod::ConcatModule({2}).build(ctx, features, to_btc(mu_input, mu_dim)); + { + auto spk = engine::core::wrap_tensor( + spk_input, engine::core::TensorShape::from_dims({1, 1, spk_dim}), GGML_TYPE_F32); + auto broadcast = mod::RepeatModule({ + engine::core::TensorShape::from_dims({1, frames, spk_dim})}).build(ctx, spk); + features = mod::ConcatModule({2}).build(ctx, features, broadcast); + } + const int64_t proj_in = mel * 2 + mu_dim + spk_dim; + auto hidden = mod::LinearModule({proj_in, dim, true, GGML_PREC_F32}) + .build(ctx, dense(ctx, features), weights->input_proj); + hidden = mod::AddModule{}.build( + ctx, hidden, + mod::LinearModule({1, dim, false, GGML_PREC_F32}) + .build(ctx, to_btc(cond_mask_input, 1), weights->cond_mask_proj)); + { + // CausalConvPositionEmbedding: two causal grouped convolutions. + const auto kernel = static_cast(config_in.acoustic_pos_kernel_size); + auto y = dense(ctx, mod::TransposeModule(swap_channel_time()).build(ctx, hidden)); + y = build_grouped_conv(ctx, pad_time(ctx, y, kernel - 1, 0), weights->pos_conv1); + y = mish(ctx, y); + y = build_grouped_conv(ctx, pad_time(ctx, y, kernel - 1, 0), weights->pos_conv2); + y = mish(ctx, y); + y = dense(ctx, mod::TransposeModule(swap_channel_time()).build(ctx, y)); + hidden = mod::AddModule{}.build(ctx, hidden, y); + } + + auto silu_emb = mod::SiluModule{}.build(ctx, emb); + for (const auto & block : weights->blocks) { + auto modulation = mod::LinearModule({dim, dim * 6, true, GGML_PREC_F32}) + .build(ctx, silu_emb, block.modulation); + auto chunk = [&](int64_t index) { + return mod::SliceModule({2, index * dim, dim}).build(ctx, modulation); + }; + const auto shift_msa = chunk(0); + const auto scale_msa = chunk(1); + const auto gate_msa = chunk(2); + const auto shift_mlp = chunk(3); + const auto scale_mlp = chunk(4); + const auto gate_mlp = chunk(5); + + auto norm = modulate( + ctx, + mod::LayerNormModule({dim, kDiTLayerNormEps, false, false}) + .build(ctx, hidden, mod::NormWeights{}), + scale_msa, shift_msa); + auto q = mod::LinearModule({dim, inner, true, GGML_PREC_F32}).build(ctx, norm, block.to_q); + auto k = mod::LinearModule({dim, inner, true, GGML_PREC_F32}).build(ctx, norm, block.to_k); + auto v = mod::LinearModule({dim, inner, true, GGML_PREC_F32}).build(ctx, norm, block.to_v); + const auto head_shape = engine::core::TensorShape::from_dims({1, frames, heads, head_dim}); + auto reshape_heads = [&](const engine::core::TensorValue & value) { + return dense(ctx, engine::core::reshape_tensor( + ctx, engine::core::ensure_backend_addressable_layout(ctx, value), head_shape)); + }; + // Half-rotation RoPE over the frame index, applied per head. + auto rope = [&](engine::core::TensorValue value) { + return mod::RoPEModule({head_dim, GGML_ROPE_TYPE_NEOX, 10000.0F}) + .build(ctx, value, engine::core::wrap_tensor( + positions, engine::core::TensorShape::from_dims({frames}), GGML_TYPE_I32)); + }; + auto to_flash = [&](const engine::core::TensorValue & value) { + return dense(ctx, mod::TransposeModule({{0, 2, 1, 3}, 4}).build(ctx, value)); + }; + auto q_heads = to_flash(rope(reshape_heads(q))); + auto k_heads = to_flash(rope(reshape_heads(k))); + auto v_heads = to_flash(reshape_heads(v)); + auto attention = mod::ScaledDotProductAttentionModule({ + head_dim, + mod::ScaledDotProductAttentionLowering::Flash, + GGML_PREC_F32, + mod::AttentionCausality::NonCausal, + }).build(ctx, q_heads, k_heads, v_heads); + auto flat = engine::core::reshape_tensor( + ctx, engine::core::ensure_backend_addressable_layout(ctx, attention), + engine::core::TensorShape::from_dims({1, frames, inner})); + auto projected = mod::LinearModule({inner, dim, true, GGML_PREC_F32}) + .build(ctx, flat, block.to_out); + hidden = mod::AddModule{}.build( + ctx, hidden, + mod::MulModule{}.build( + ctx, projected, mod::RepeatModule({projected.shape}).build(ctx, gate_msa))); + + auto feed = modulate( + ctx, + mod::LayerNormModule({dim, kDiTLayerNormEps, false, false}) + .build(ctx, hidden, mod::NormWeights{}), + scale_mlp, shift_mlp); + feed = mod::LinearModule({dim, ff_dim, true, GGML_PREC_F32}).build(ctx, feed, block.ff_in); + feed = mod::GeluModule({mod::GeluApproximation::Tanh}).build(ctx, feed); + feed = mod::LinearModule({ff_dim, dim, true, GGML_PREC_F32}).build(ctx, feed, block.ff_out); + hidden = mod::AddModule{}.build( + ctx, hidden, + mod::MulModule{}.build( + ctx, feed, mod::RepeatModule({feed.shape}).build(ctx, gate_mlp))); + } + + { + // AdaLayerNormFinal emits (scale, shift) in that order. + auto modulation = mod::LinearModule({dim, dim * 2, true, GGML_PREC_F32}) + .build(ctx, silu_emb, weights->out_modulation); + const auto scale = mod::SliceModule({2, 0, dim}).build(ctx, modulation); + const auto shift = mod::SliceModule({2, dim, dim}).build(ctx, modulation); + hidden = modulate( + ctx, + mod::LayerNormModule({dim, kDiTLayerNormEps, false, false}) + .build(ctx, hidden, mod::NormWeights{}), + scale, shift); + } + hidden = mod::LinearModule({dim, mel, true, GGML_PREC_F32}).build(ctx, hidden, weights->out_proj); + // Back to [1, mel, frames] so the Euler update sees the solver layout. + hidden = dense(ctx, mod::TransposeModule(swap_channel_time()).build(ctx, hidden)); + velocity_output = hidden.tensor; + ggml_set_output(velocity_output); + velocity_graph = ggml_new_graph_custom(velocity_ctx.get(), 262144, false); + ggml_build_forward_expand(velocity_graph, velocity_output); + velocity_allocr = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend)); + if (velocity_allocr == nullptr || + !ggml_gallocr_reserve(velocity_allocr, velocity_graph) || + !ggml_gallocr_alloc_graph(velocity_allocr, velocity_graph)) { + throw std::runtime_error("failed to allocate the Sopro velocity graph"); + } + } + + std::vector run_conditioning(const std::vector & token_ids) { + std::vector index(static_cast(frames), 0); + for (int64_t frame = 0; frame < frames; ++frame) { + index[static_cast(frame)] = static_cast( + std::min(frame * tokens / frames, tokens - 1)); + } + ggml_backend_tensor_set(token_input, token_ids.data(), 0, token_ids.size() * sizeof(int32_t)); + ggml_backend_tensor_set(expand_index, index.data(), 0, index.size() * sizeof(int32_t)); + const ggml_status status = engine::core::compute_backend_graph(backend, conditioning_graph); + ggml_backend_synchronize(backend); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Sopro acoustic conditioning graph compute failed"); + } + std::vector mu(static_cast(config->acoustic_mu_dim * frames), 0.0F); + ggml_backend_tensor_get(mu_output, mu.data(), 0, mu.size() * sizeof(float)); + return mu; + } + + // ggml_gallocr only exempts GGML_TENSOR_FLAG_OUTPUT tensors from being + // freed and reused (ggml-alloc.c, ggml_gallocr_free_node); an input leaf's + // arena space is handed to a later intermediate once its last consumer has + // run. That is fine for a one-shot graph, but the solver replays this one + // per Euler step, so every leaf has to be re-uploaded before each compute + // rather than staged once. + void set_constants( + std::vector cond_mel, + std::vector cond_mask, + std::vector mu, + std::vector spk) { + cond_mel_host = std::move(cond_mel); + cond_mask_host = std::move(cond_mask); + mu_host = std::move(mu); + spk_host = std::move(spk); + position_host.assign(static_cast(frames), 0); + for (int64_t frame = 0; frame < frames; ++frame) { + position_host[static_cast(frame)] = static_cast(frame); + } + } + + void upload_constants() { + ggml_backend_tensor_set(positions, position_host.data(), 0, + position_host.size() * sizeof(int32_t)); + ggml_backend_tensor_set(cond_mel_input, cond_mel_host.data(), 0, + cond_mel_host.size() * sizeof(float)); + ggml_backend_tensor_set(cond_mask_input, cond_mask_host.data(), 0, + cond_mask_host.size() * sizeof(float)); + ggml_backend_tensor_set(mu_input, mu_host.data(), 0, mu_host.size() * sizeof(float)); + ggml_backend_tensor_set(spk_input, spk_host.data(), 0, spk_host.size() * sizeof(float)); + } + + std::vector run_velocity(const std::vector & x, const std::vector & emb) { + upload_constants(); + ggml_backend_tensor_set(x_input, x.data(), 0, x.size() * sizeof(float)); + ggml_backend_tensor_set(emb_input, emb.data(), 0, emb.size() * sizeof(float)); + const ggml_status status = engine::core::compute_backend_graph(backend, velocity_graph); + ggml_backend_synchronize(backend); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Sopro acoustic velocity graph compute failed"); + } + std::vector velocity(x.size(), 0.0F); + ggml_backend_tensor_get(velocity_output, velocity.data(), 0, velocity.size() * sizeof(float)); + return velocity; + } + + ggml_backend_t backend = nullptr; + std::shared_ptr weights; + int64_t tokens = 0; + int64_t frames = 0; + const SoproModelConfig * config = nullptr; + + std::unique_ptr conditioning_ctx; + ggml_tensor * token_input = nullptr; + ggml_tensor * expand_index = nullptr; + ggml_tensor * mu_output = nullptr; + ggml_cgraph * conditioning_graph = nullptr; + ggml_gallocr_t conditioning_allocr = nullptr; + + std::unique_ptr velocity_ctx; + ggml_tensor * x_input = nullptr; + ggml_tensor * cond_mel_input = nullptr; + ggml_tensor * cond_mask_input = nullptr; + ggml_tensor * mu_input = nullptr; + ggml_tensor * spk_input = nullptr; + ggml_tensor * emb_input = nullptr; + ggml_tensor * positions = nullptr; + ggml_tensor * velocity_output = nullptr; + std::vector cond_mel_host; + std::vector cond_mask_host; + std::vector mu_host; + std::vector spk_host; + std::vector position_host; + ggml_cgraph * velocity_graph = nullptr; + ggml_gallocr_t velocity_allocr = nullptr; +}; + +SoproAcousticRuntime::SoproAcousticRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t weight_context_bytes, + size_t graph_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type) + : config_(assets.config.model), + execution_context_(execution_context), + graph_context_bytes_(graph_context_bytes), + weights_(load_acoustic_weights( + execution_context.backend(), + execution_context.backend_type(), + *assets.model_weights, + assets.config.model, + weight_context_bytes, + matmul_storage_type, + conv_storage_type)) {} + +SoproAcousticRuntime::~SoproAcousticRuntime() = default; + +std::vector SoproAcousticRuntime::solve(const SoproAcousticRequest & request) const { + const int64_t mel = config_.acoustic_mel_n_mels; + const int64_t frames = request.total_frames; + const auto tokens = static_cast(request.semantic_tokens.size()); + if (frames <= 0 || tokens <= 0) { + throw std::runtime_error("Sopro acoustic solve requires tokens and frames"); + } + if (request.prompt_frames < 0 || request.prompt_frames > frames) { + throw std::runtime_error("Sopro acoustic prompt frame count is out of range"); + } + if (static_cast(request.prompt_mel.size()) != mel * request.prompt_frames) { + throw std::runtime_error("Sopro acoustic prompt mel shape mismatch"); + } + if (static_cast(request.cond_vec.size()) != config_.cond_hidden_dim) { + throw std::runtime_error("Sopro acoustic conditioning vector shape mismatch"); + } + const int64_t steps = std::max(1, request.steps); + + if (graphs_ == nullptr || !graphs_->matches(*weights_, tokens, frames)) { + // Free the previous arena first; otherwise both are resident while the + // replacement is allocated, and every segment rebuilds this graph. + graphs_.reset(); + graphs_ = std::make_unique( + execution_context_.backend(), + execution_context_.backend_type(), + graph_context_bytes_, + config_, + weights_, + tokens, + frames); + } + + // _row_has_signal: an all-zero conditioning vector disables the speaker + // branch entirely (the reference uses it for unconditional batches). + float cond_peak = 0.0F; + for (const float value : request.cond_vec) { + cond_peak = std::max(cond_peak, std::fabs(value)); + } + std::vector spk(static_cast(config_.acoustic_spk_dim), 0.0F); + if (cond_peak > 0.0F) { + double norm = 0.0; + for (const float value : request.cond_vec) { + norm += static_cast(value) * static_cast(value); + } + // F.normalize uses an epsilon floor rather than a plain division. + const auto inv = static_cast(1.0 / std::max(std::sqrt(norm), 1.0e-12)); + std::vector normalised(request.cond_vec); + for (auto & value : normalised) { + value *= inv; + } + spk = affine( + weights_->spk_proj_w, weights_->spk_proj_b, normalised, + config_.cond_hidden_dim, config_.acoustic_spk_dim); + } + + auto mu = graphs_->run_conditioning(request.semantic_tokens); + dump("mu", mu); + dump("spk", spk); + + std::vector cond_mel(static_cast(mel * frames), 0.0F); + std::vector cond_mask(static_cast(frames), 0.0F); + for (int64_t c = 0; c < mel; ++c) { + std::copy( + request.prompt_mel.begin() + static_cast(c * request.prompt_frames), + request.prompt_mel.begin() + static_cast((c + 1) * request.prompt_frames), + cond_mel.begin() + static_cast(c * frames)); + } + std::fill(cond_mask.begin(), cond_mask.begin() + static_cast(request.prompt_frames), 1.0F); + graphs_->set_constants(cond_mel, cond_mask, mu, spk); + + std::mt19937_64 rng(request.seed); + std::normal_distribution normal(0.0F, 1.0F); + std::vector x_init(static_cast(mel * frames), 0.0F); + for (auto & value : x_init) { + value = normal(rng); + } + std::vector x(x_init); + dump("x_init", x_init); + dump("cond_mel", cond_mel); + + const auto grid = build_time_grid(steps, config_.acoustic_sway_sampling_coef); + const float sigma_min = config_.acoustic_sigma_min; + for (int64_t step = 0; step < steps; ++step) { + const float t0 = grid[static_cast(step)]; + const float t1 = grid[static_cast(step + 1)]; + const auto raw = sinusoidal_time_embedding(t0, config_.acoustic_time_embed_dim); + auto emb = affine( + weights_->time_mlp_w0, weights_->time_mlp_b0, raw, + config_.acoustic_time_embed_dim, config_.acoustic_dit_dim); + for (auto & value : emb) { + value = value / (1.0F + std::exp(-value)); // SiLU + } + emb = affine( + weights_->time_mlp_w2, weights_->time_mlp_b2, emb, + config_.acoustic_dit_dim, config_.acoustic_dit_dim); + + const std::string tag = std::to_string(step); + if (step == 0) { + dump("emb0", emb); + dump("x_step0", x); + } + dump(("traj_x_" + tag).c_str(), x); + const auto velocity = graphs_->run_velocity(x, emb); + if (step == 0) { + dump("velocity0", velocity); + } + dump(("traj_v_" + tag).c_str(), velocity); + const float dt = t1 - t0; + for (size_t i = 0; i < x.size(); ++i) { + x[i] += dt * velocity[i]; + } + // Re-pin the prompt span to the reference mel's flow at t1. + const float prompt_scale = 1.0F - (1.0F - sigma_min) * t1; + for (int64_t c = 0; c < mel; ++c) { + const size_t base = static_cast(c * frames); + for (int64_t t = 0; t < request.prompt_frames; ++t) { + const size_t index = base + static_cast(t); + x[index] = prompt_scale * x_init[index] + t1 * cond_mel[index]; + } + } + } + for (int64_t c = 0; c < mel; ++c) { + const size_t base = static_cast(c * frames); + for (int64_t t = 0; t < request.prompt_frames; ++t) { + x[base + static_cast(t)] = cond_mel[base + static_cast(t)]; + } + } + dump("solved", x); + return x; +} + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/sopro_tts/assets.cpp b/src/community_models/sopro_tts/assets.cpp new file mode 100644 index 000000000..b0500b4d2 --- /dev/null +++ b/src/community_models/sopro_tts/assets.cpp @@ -0,0 +1,263 @@ +#include "engine/community_models/sopro_tts/assets.h" + +#include "engine/framework/io/json.h" +#include "engine/framework/model_spec/package.h" + +#include +#include +#include + +namespace engine::community_models::sopro_tts { +namespace { + +namespace json = engine::io::json; + +constexpr const char * kFamily = "sopro_tts"; + +SoproModelConfig parse_model(const json::Value & root) { + SoproModelConfig out; + const auto * node = root.find("model"); + if (node == nullptr) { + throw std::runtime_error("Sopro config.json is missing the \"model\" section"); + } + const auto & model = *node; + out.latent_dim = json::optional_i64(model, "latent_dim", out.latent_dim); + out.semantic_vocab_size = json::optional_i64(model, "semantic_vocab_size", out.semantic_vocab_size); + out.text_vocab_size = json::optional_i64(model, "text_vocab_size", out.text_vocab_size); + out.max_text_len = json::optional_i64(model, "max_text_len", out.max_text_len); + out.cond_in_dim = json::optional_i64(model, "cond_in_dim", out.cond_in_dim); + out.cond_hidden_dim = json::optional_i64(model, "cond_hidden_dim", out.cond_hidden_dim); + out.ar_model_dim = json::optional_i64(model, "ar_model_dim", out.ar_model_dim); + out.ar_blocks = json::optional_i64(model, "ar_blocks", out.ar_blocks); + out.ar_heads = json::optional_i64(model, "ar_heads", out.ar_heads); + // ModelConfig.__post_init__: ar_kv_heads defaults to ar_heads. + out.ar_kv_heads = json::optional_nullable_i64(model, "ar_kv_heads", out.ar_heads); + out.ar_ffn_mult = json::optional_f32(model, "ar_ffn_mult", out.ar_ffn_mult); + out.ar_qk_rms_norm = json::optional_bool(model, "ar_qk_rms_norm", out.ar_qk_rms_norm); + out.style_prefix_tokens = json::optional_i64(model, "style_prefix_tokens", out.style_prefix_tokens); + out.acoustic_time_embed_dim = json::optional_i64(model, "acoustic_time_embed_dim", out.acoustic_time_embed_dim); + out.acoustic_sway_sampling_coef = json::optional_f32(model, "acoustic_sway_sampling_coef", out.acoustic_sway_sampling_coef); + out.acoustic_upsampler_kernel_size = json::optional_i64(model, "acoustic_upsampler_kernel_size", out.acoustic_upsampler_kernel_size); + out.acoustic_dit_dim = json::optional_i64(model, "acoustic_dit_dim", out.acoustic_dit_dim); + out.acoustic_dit_depth = json::optional_i64(model, "acoustic_dit_depth", out.acoustic_dit_depth); + out.acoustic_dit_heads = json::optional_i64(model, "acoustic_dit_heads", out.acoustic_dit_heads); + out.acoustic_dit_dim_head = json::optional_i64(model, "acoustic_dit_dim_head", out.acoustic_dit_dim_head); + out.acoustic_dit_ff_mult = json::optional_f32(model, "acoustic_dit_ff_mult", out.acoustic_dit_ff_mult); + out.acoustic_spk_dim = json::optional_i64(model, "acoustic_spk_dim", out.acoustic_spk_dim); + out.acoustic_pre_lookahead_frames = json::optional_i64(model, "acoustic_pre_lookahead_frames", out.acoustic_pre_lookahead_frames); + out.acoustic_pos_kernel_size = json::optional_i64(model, "acoustic_pos_kernel_size", out.acoustic_pos_kernel_size); + out.acoustic_sigma_min = json::optional_f32(model, "acoustic_sigma_min", out.acoustic_sigma_min); + out.acoustic_num_left_chunks = json::optional_i64(model, "acoustic_num_left_chunks", out.acoustic_num_left_chunks); + out.acoustic_mel_n_mels = json::optional_i64(model, "acoustic_mel_n_mels", out.acoustic_mel_n_mels); + out.acoustic_mel_hop_length = json::optional_i64(model, "acoustic_mel_hop_length", out.acoustic_mel_hop_length); + // ModelConfig.__post_init__: acoustic_mu_dim defaults to acoustic_mel_n_mels. + out.acoustic_mu_dim = json::optional_nullable_i64(model, "acoustic_mu_dim", out.acoustic_mel_n_mels); + out.acoustic_mel_mean = json::optional_f32_array(model, "acoustic_mel_mean"); + out.acoustic_mel_std = json::optional_f32_array(model, "acoustic_mel_std"); + if (static_cast(out.acoustic_mel_mean.size()) != out.acoustic_mel_n_mels || + static_cast(out.acoustic_mel_std.size()) != out.acoustic_mel_n_mels) { + throw std::runtime_error( + "Sopro config.json must provide acoustic_mel_mean/acoustic_mel_std with " + "acoustic_mel_n_mels entries"); + } + if (out.ar_model_dim % out.ar_heads != 0) { + throw std::runtime_error("Sopro ar_model_dim must be divisible by ar_heads"); + } + return out; +} + +SoproSemanticEncoderConfig parse_semantic_encoder(const json::Value & root) { + SoproSemanticEncoderConfig out; + const auto * node = root.find("semantic_encoder"); + if (node == nullptr) { + return out; + } + const auto & cfg = *node; + out.n_mels = json::optional_i64(cfg, "n_mels", out.n_mels); + out.d_model = json::optional_i64(cfg, "d_model", out.d_model); + out.layers = json::optional_i64(cfg, "layers", out.layers); + out.heads = json::optional_i64(cfg, "heads", out.heads); + out.ffn_dim = json::optional_i64(cfg, "ffn_dim", out.ffn_dim); + out.max_positions = json::optional_i64(cfg, "max_positions", out.max_positions); + out.fsq_levels = json::optional_i64_array(cfg, "fsq_levels", out.fsq_levels); + out.sample_rate = json::optional_i64(cfg, "sample_rate", out.sample_rate); + out.n_fft = json::optional_i64(cfg, "n_fft", out.n_fft); + out.hop_length = json::optional_i64(cfg, "hop_length", out.hop_length); + out.token_samples_24k = json::optional_i64(cfg, "token_samples_24k", out.token_samples_24k); + if (out.fsq_levels.empty()) { + throw std::runtime_error("Sopro semantic_encoder.fsq_levels must not be empty"); + } + return out; +} + +SoproSpeakerEncoderConfig parse_speaker_encoder(const json::Value & root) { + SoproSpeakerEncoderConfig out; + const auto * node = root.find("speaker_encoder"); + if (node == nullptr) { + return out; + } + const auto & cfg = *node; + out.sample_rate = json::optional_i64(cfg, "sample_rate", out.sample_rate); + out.n_mels = json::optional_i64(cfg, "n_mels", out.n_mels); + out.n_fft = json::optional_i64(cfg, "n_fft", out.n_fft); + out.win_length = json::optional_i64(cfg, "win_length", out.win_length); + out.hop_length = json::optional_i64(cfg, "hop_length", out.hop_length); + out.f_min = json::optional_f32(cfg, "f_min", out.f_min); + out.f_max = json::optional_f32(cfg, "f_max", out.f_max); + out.mel_log_floor = json::optional_f32(cfg, "mel_log_floor", out.mel_log_floor); + out.stem_channels = json::optional_i64(cfg, "stem_channels", out.stem_channels); + out.stage_channels = json::optional_i64_array(cfg, "stage_channels", out.stage_channels); + out.blocks_per_stage = json::optional_i64_array(cfg, "blocks_per_stage", out.blocks_per_stage); + out.dilation_cycle = json::optional_i64_array(cfg, "dilation_cycle", out.dilation_cycle); + out.depthwise_kernel_size = json::optional_i64(cfg, "depthwise_kernel_size", out.depthwise_kernel_size); + out.se_reduction = json::optional_i64(cfg, "se_reduction", out.se_reduction); + out.id_emb_dim = json::optional_i64(cfg, "id_emb_dim", out.id_emb_dim); + out.style_emb_dim = json::optional_i64(cfg, "style_emb_dim", out.style_emb_dim); + out.style_ctrl_dim = json::optional_i64(cfg, "style_ctrl_dim", out.style_ctrl_dim); + out.id_head_hidden = json::optional_i64(cfg, "id_head_hidden", out.id_head_hidden); + out.style_head_hidden = json::optional_i64(cfg, "style_head_hidden", out.style_head_hidden); + out.attn_hidden = json::optional_i64(cfg, "attn_hidden", out.attn_hidden); + if (out.stage_channels.size() != out.blocks_per_stage.size() || out.stage_channels.empty()) { + throw std::runtime_error( + "Sopro speaker_encoder.stage_channels and blocks_per_stage must be " + "non-empty and the same length"); + } + if (out.dilation_cycle.empty()) { + throw std::runtime_error("Sopro speaker_encoder.dilation_cycle must not be empty"); + } + return out; +} + +SoproVocoderConfig parse_vocoder(const json::Value & root, const std::string & key) { + SoproVocoderConfig out; + const auto * node = root.find(key); + if (node == nullptr) { + return out; + } + const auto & cfg = *node; + out.sample_rate = json::optional_i64(cfg, "sample_rate", out.sample_rate); + out.n_fft = json::optional_i64(cfg, "n_fft", out.n_fft); + out.hop_length = json::optional_i64(cfg, "hop_length", out.hop_length); + out.n_mels = json::optional_i64(cfg, "n_mels", out.n_mels); + out.dim = json::optional_i64(cfg, "dim", out.dim); + out.intermediate_dim = json::optional_i64(cfg, "intermediate_dim", out.intermediate_dim); + out.num_layers = json::optional_i64(cfg, "num_layers", out.num_layers); + out.max_magnitude = json::optional_f32(cfg, "max_magnitude", out.max_magnitude); + out.band_limit_hz = json::optional_f32(cfg, "band_limit_hz", out.band_limit_hz); + out.causal = json::optional_bool(cfg, "causal", out.causal); + out.lookahead_frames = json::optional_nullable_i64(cfg, "lookahead_frames", out.lookahead_frames); + out.block_lookaheads = json::optional_i64_array(cfg, "block_lookaheads"); + return out; +} + +SoproGenerationConfig parse_generation(const json::Value & root) { + SoproGenerationConfig out; + const auto * node = root.find("generation"); + if (node == nullptr) { + return out; + } + const auto & cfg = *node; + out.temperature = json::optional_f32(cfg, "temperature", out.temperature); + out.top_p = json::optional_f32(cfg, "top_p", out.top_p); + out.top_k = json::optional_i64(cfg, "top_k", out.top_k); + out.steps = json::optional_i64(cfg, "steps", out.steps); + out.max_seconds = json::optional_f32(cfg, "max_seconds", out.max_seconds); + out.min_seconds = json::optional_f32(cfg, "min_seconds", out.min_seconds); + out.max_segment_chars = json::optional_i64(cfg, "max_segment_chars", out.max_segment_chars); + out.ref_seconds = json::optional_f32(cfg, "ref_seconds", out.ref_seconds); + out.style_tokens = json::optional_i64(cfg, "style_tokens", out.style_tokens); + out.prompt_tokens = json::optional_i64(cfg, "prompt_tokens", out.prompt_tokens); + out.stream_chunk_frames = json::optional_i64(cfg, "stream_chunk_frames", out.stream_chunk_frames); + return out; +} + +SoproTTSConfig parse_config(const assets::ResourceBundle & resources) { + const auto root = resources.parse_json("config"); + SoproTTSConfig out; + out.sample_rate = json::optional_i64(root, "sample_rate", out.sample_rate); + out.model = parse_model(root); + out.semantic_encoder = parse_semantic_encoder(root); + out.speaker_encoder = parse_speaker_encoder(root); + out.vocoder = parse_vocoder(root, "vocoder"); + out.vocoder_streaming = parse_vocoder(root, "vocoder_streaming"); + out.generation = parse_generation(root); + const int64_t codebook = out.semantic_encoder.codebook_size(); + if (codebook != out.model.semantic_vocab_size) { + throw std::runtime_error( + "Sopro config mismatch: prod(semantic_encoder.fsq_levels) = " + + std::to_string(codebook) + " but model.semantic_vocab_size = " + + std::to_string(out.model.semantic_vocab_size)); + } + if (out.semantic_encoder.token_samples_24k % out.model.acoustic_mel_hop_length != 0) { + throw std::runtime_error( + "Sopro config mismatch: semantic_encoder.token_samples_24k must be a " + "multiple of model.acoustic_mel_hop_length"); + } + return out; +} + +} // namespace + +int64_t SoproModelConfig::ar_ffn_dim() const noexcept { + // SwiGLUFeedForward: hidden = max(1, round(mult * dim)). + const auto hidden = static_cast( + std::llround(static_cast(ar_ffn_mult) * static_cast(ar_model_dim))); + return hidden < 1 ? 1 : hidden; +} + +int64_t SoproModelConfig::acoustic_dit_ff_dim() const noexcept { + const auto hidden = static_cast( + std::llround(static_cast(acoustic_dit_ff_mult) * static_cast(acoustic_dit_dim))); + return hidden < 1 ? 1 : hidden; +} + +int64_t SoproSemanticEncoderConfig::digit_dim() const noexcept { + int64_t sum = 0; + for (const int64_t level : fsq_levels) { + sum += level; + } + return sum; +} + +int64_t SoproSemanticEncoderConfig::codebook_size() const noexcept { + int64_t product = 1; + for (const int64_t level : fsq_levels) { + product *= level; + } + return product; +} + +int64_t SoproTTSConfig::hop_ratio() const noexcept { + return semantic_encoder.token_samples_24k / model.acoustic_mel_hop_length; +} + +void require_frontend_buffers( + const assets::TensorSource & source, + const char * stage, + std::initializer_list tensor_names) { + for (const char * name : tensor_names) { + if (!source.has_tensor(name)) { + throw std::runtime_error( + std::string("Sopro ") + stage + " checkpoint is missing '" + name + + "'. audio.cpp reuses torchaudio's stored analysis window and mel " + "filterbank rather than rebuilding them; re-export the checkpoint " + "with persistent buffers."); + } + } +} + +std::shared_ptr load_sopro_tts_assets( + const std::filesystem::path & model_path) { + auto assets = std::make_shared(); + assets->resources = engine::model_spec::load_resource_bundle( + model_path, engine::model_spec::default_spec_path(kFamily)); + assets->config = parse_config(assets->resources); + assets->model_weights = assets->resources.open_tensor_source("model"); + assets->semantic_encoder_weights = assets->resources.open_tensor_source("semantic_encoder"); + assets->speaker_encoder_weights = assets->resources.open_tensor_source("speaker_encoder"); + assets->vocoder_weights = assets->resources.open_tensor_source("vocoder"); + assets->tokenizer_path = assets->resources.require_file("tokenizer"); + return assets; +} + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/sopro_tts/reference.cpp b/src/community_models/sopro_tts/reference.cpp new file mode 100644 index 000000000..b0d74e59f --- /dev/null +++ b/src/community_models/sopro_tts/reference.cpp @@ -0,0 +1,490 @@ +#include "engine/community_models/sopro_tts/reference.h" + +#include "engine/community_models/sopro_tts/semantic_encoder.h" +#include "engine/community_models/sopro_tts/speaker_encoder.h" +#include "engine/community_models/sopro_tts/vocoder.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/audio/resampling.h" + +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { +namespace audio_ops { +namespace { + +constexpr float kRefGainLimitDb = 30.0F; +constexpr float kRefPeakCeiling = 0.95F; +constexpr float kPauseMinSeconds = 0.10F; +constexpr float kPauseKeepSeconds = 0.15F; +constexpr float kCropForwardSeconds = 5.0F; +constexpr float kCropBackwardSeconds = 5.0F; +constexpr float kMinKeepFraction = 0.75F; +constexpr float kRoomToneSeconds = 0.25F; +constexpr float kFadeSeconds = 0.02F; +constexpr float kOnsetThresholdDb = -45.0F; +constexpr float kOnsetOverFloorDb = 15.0F; +constexpr int64_t kOnsetWindowFrames = 6; +constexpr int64_t kOnsetMinFrames = 5; +constexpr float kMinActiveSeconds = 0.4F; + +// torch.unfold + RMS: floor((n - win) / hop) + 1 frames, floored at 1e-6. +std::vector frame_rms( + const std::vector & wav, int64_t window, int64_t hop) { + const auto total = static_cast(wav.size()); + std::vector out; + if (window <= 0 || hop <= 0 || total < window) { + return out; + } + const int64_t frames = (total - window) / hop + 1; + out.resize(static_cast(frames), 0.0F); + for (int64_t frame = 0; frame < frames; ++frame) { + const float * values = wav.data() + static_cast(frame * hop); + double sum = 0.0; + for (int64_t i = 0; i < window; ++i) { + sum += static_cast(values[i]) * static_cast(values[i]); + } + out[static_cast(frame)] = std::max( + static_cast(std::sqrt(sum / static_cast(window))), 1.0e-6F); + } + return out; +} + +// torch.quantile's default "linear" interpolation. +float quantile(std::vector values, float q) { + if (values.empty()) { + return 0.0F; + } + std::sort(values.begin(), values.end()); + const double position = static_cast(q) * static_cast(values.size() - 1); + const auto lower = static_cast(std::floor(position)); + const size_t upper = std::min(lower + 1, values.size() - 1); + const double weight = position - static_cast(lower); + return static_cast(values[lower] * (1.0 - weight) + values[upper] * weight); +} + +// torch.median returns the lower of the two middle elements for even counts. +float lower_median(std::vector values) { + if (values.empty()) { + return 0.0F; + } + const size_t index = (values.size() - 1) / 2; + std::nth_element(values.begin(), values.begin() + static_cast(index), values.end()); + return values[index]; +} + +struct PauseRun { + int64_t begin = 0; + int64_t end = 0; +}; + +std::vector pause_runs(const std::vector & quiet, int64_t min_run) { + std::vector runs; + const auto count = static_cast(quiet.size()); + int64_t index = 0; + while (index < count) { + if (quiet[static_cast(index)] == 0) { + ++index; + continue; + } + int64_t end = index; + while (end < count && quiet[static_cast(end)] != 0) { + ++end; + } + if (end - index >= min_run) { + runs.push_back({index, end}); + } + index = end; + } + return runs; +} + +float onset_threshold(const std::vector & rms) { + float threshold = std::pow(10.0F, kOnsetThresholdDb / 20.0F); + if (rms.size() >= 30) { + threshold = std::max( + threshold, quantile(rms, 0.1F) * std::pow(10.0F, kOnsetOverFloorDb / 20.0F)); + } + return threshold; +} + +// x[:(n // win) * win].view(-1, win) RMS, i.e. non-overlapping windows. +std::vector block_rms(const std::vector & wav, int64_t window) { + std::vector out; + if (window <= 0) { + return out; + } + const int64_t frames = static_cast(wav.size()) / window; + out.resize(static_cast(frames), 0.0F); + for (int64_t frame = 0; frame < frames; ++frame) { + const float * values = wav.data() + static_cast(frame * window); + double sum = 0.0; + for (int64_t i = 0; i < window; ++i) { + sum += static_cast(values[i]) * static_cast(values[i]); + } + out[static_cast(frame)] = static_cast(std::sqrt(sum / static_cast(window))); + } + return out; +} + +// First index where `min_hits` of the next `window` frames are above threshold. +std::optional first_sustained_hit( + const std::vector & rms, float threshold, int64_t window, int64_t min_hits) { + const auto count = static_cast(rms.size()); + if (count < window) { + return std::nullopt; + } + for (int64_t start = 0; start + window <= count; ++start) { + int64_t hits = 0; + for (int64_t i = 0; i < window; ++i) { + if (rms[static_cast(start + i)] > threshold) { + ++hits; + } + } + if (hits >= min_hits) { + return start; + } + } + return std::nullopt; +} + +std::vector finish_with_room_tone( + const std::vector & wav, int sample_rate, float floor_level, std::mt19937_64 & rng) { + const auto fade = static_cast(kFadeSeconds * static_cast(sample_rate)); + std::vector out(wav); + if (fade > 0 && static_cast(out.size()) >= fade) { + const auto offset = static_cast(out.size()) - fade; + for (int64_t i = 0; i < fade; ++i) { + const float ramp = fade == 1 ? 1.0F + : 1.0F - static_cast(i) / static_cast(fade - 1); + out[static_cast(offset + i)] *= ramp; + } + } + const auto tone_samples = static_cast(kRoomToneSeconds * static_cast(sample_rate)); + std::normal_distribution normal(0.0F, 1.0F); + for (int64_t i = 0; i < tone_samples; ++i) { + out.push_back(normal(rng) * floor_level); + } + return out; +} + +} // namespace + +std::vector crop_on_pause( + const std::vector & wav, float target_seconds, int sample_rate, std::mt19937_64 & rng) { + const auto rate = static_cast(sample_rate); + const auto window = static_cast(rate * 0.025F); + const auto hop = static_cast(rate * 0.010F); + const auto total = static_cast(wav.size()); + if (window <= 0 || hop <= 0 || total < 4 * window) { + return wav; + } + const auto rms = frame_rms(wav, window, hop); + if (rms.empty()) { + return wav; + } + const float floor_level = quantile(rms, 0.1F); + std::vector quiet(rms.size(), 0); + for (size_t i = 0; i < rms.size(); ++i) { + quiet[i] = rms[i] < floor_level * 4.0F ? 1 : 0; + } + const auto runs = pause_runs( + quiet, std::max(1, std::lround(kPauseMinSeconds / 0.010F))); + const auto keep = static_cast(std::lround(kPauseKeepSeconds / 0.010F)); + const auto target = static_cast(std::lround(target_seconds * rate)); + + const auto cut_at = [&](const PauseRun & run) { + const int64_t end = (run.begin + std::min(run.end - run.begin, keep)) * hop + window; + return std::vector( + wav.begin(), wav.begin() + static_cast(std::min(end, total))); + }; + + if (total <= target) { + if (!runs.empty() && runs.back().end * hop + window >= total - hop) { + return wav; + } + const auto boundary = static_cast(static_cast(total) * kMinKeepFraction); + for (auto it = runs.rbegin(); it != runs.rend(); ++it) { + if (it->begin * hop >= boundary) { + return cut_at(*it); + } + } + return finish_with_room_tone(wav, sample_rate, floor_level, rng); + } + const auto forward_limit = target + static_cast(kCropForwardSeconds * rate); + for (const auto & run : runs) { + if (run.begin * hop >= target && run.begin * hop <= forward_limit) { + return cut_at(run); + } + } + const auto backward_limit = target - static_cast(kCropBackwardSeconds * rate); + for (auto it = runs.rbegin(); it != runs.rend(); ++it) { + if (it->begin * hop < target && it->end * hop >= backward_limit) { + return cut_at(*it); + } + } + std::vector truncated( + wav.begin(), wav.begin() + static_cast(std::min(target, total))); + return finish_with_room_tone(truncated, sample_rate, floor_level, rng); +} + +SpeechLevel speech_level_db(const std::vector & wav, int sample_rate) { + const auto rate = static_cast(sample_rate); + const auto window = static_cast(rate * 0.025F); + const auto hop = static_cast(rate * 0.010F); + SpeechLevel out; + if (window <= 0 || static_cast(wav.size()) < window) { + double sum = 0.0; + for (const float value : wav) { + sum += static_cast(value) * static_cast(value); + } + const double rms = wav.empty() ? 0.0 : std::sqrt(sum / static_cast(wav.size())); + out.level_db = 20.0F * std::log10(std::max(static_cast(rms), 1.0e-6F)); + return out; + } + const auto rms = frame_rms(wav, window, hop); + const float threshold = quantile(rms, 0.2F) * 1.5F; + std::vector active; + active.reserve(rms.size()); + for (const float value : rms) { + if (value > threshold) { + active.push_back(value); + } + } + if (active.empty()) { + active = rms; + } + out.level_db = 20.0F * std::log10(lower_median(active)); + out.active_seconds = static_cast(active.size()) * static_cast(hop) / rate; + return out; +} + +NormalizedReference normalize_reference(const std::vector & wav, int sample_rate) { + const auto level = speech_level_db(wav, sample_rate); + // Boost only: a reference that is already hotter than the prompt level is + // passed through untouched instead of being pulled down. + float gain_db = std::min(std::max(kPromptLevelDb - level.level_db, 0.0F), kRefGainLimitDb); + float peak = 0.0F; + for (const float value : wav) { + peak = std::max(peak, std::fabs(value)); + } + if (peak > 0.0F) { + // Never let the boost clip: cap it at the headroom left below 0.95. + gain_db = std::min(gain_db, std::max(0.0F, 20.0F * std::log10(kRefPeakCeiling / peak))); + } + const float gain = std::pow(10.0F, gain_db / 20.0F); + NormalizedReference out; + out.wav = wav; + for (auto & value : out.wav) { + value *= gain; + } + out.level_db = level.level_db + gain_db; + return out; +} + +float output_gain(float prompt_level_db) { + return std::pow(10.0F, (kOutputLevelDb - prompt_level_db) / 20.0F); +} + +float match_gain( + const std::vector & wav, int sample_rate, float target_db, float prompt_level_db) { + const auto level = speech_level_db(wav, sample_rate); + if (level.active_seconds < kMinActiveSeconds) { + return output_gain(prompt_level_db); + } + return std::pow(10.0F, (target_db - level.level_db) / 20.0F); +} + +void soft_limit(std::vector & wav, float knee) { + const float span = 1.0F - knee; + if (span <= 0.0F) { + return; + } + for (auto & value : wav) { + const float magnitude = std::fabs(value); + if (magnitude > knee) { + const float limited = knee + span * std::tanh((magnitude - knee) / span); + value = value < 0.0F ? -limited : limited; + } + } +} + +std::optional speech_onset(const std::vector & wav, int sample_rate) { + const auto window = static_cast(static_cast(sample_rate) * 0.010F); + if (window <= 0 || static_cast(wav.size()) < window * kOnsetWindowFrames) { + return std::nullopt; + } + const auto rms = block_rms(wav, window); + const auto hit = first_sustained_hit( + rms, onset_threshold(rms), kOnsetWindowFrames, kOnsetMinFrames); + if (!hit.has_value()) { + return std::nullopt; + } + return *hit * window; +} + +std::vector trim_lead( + const std::vector & wav, int sample_rate, float lead, float skip) { + const auto onset = speech_onset(wav, sample_rate); + if (!onset.has_value()) { + return wav; + } + const auto rate = static_cast(sample_rate); + int64_t cut = std::max(*onset - static_cast(lead * rate), + static_cast(skip * rate)); + cut = std::min(cut, std::max(0, *onset - static_cast(0.02F * rate))); + cut = std::min(cut, static_cast(wav.size())); + return std::vector(wav.begin() + static_cast(cut), wav.end()); +} + +std::vector trim_trail(const std::vector & wav, int sample_rate, float trail) { + const auto window = static_cast(static_cast(sample_rate) * 0.010F); + if (window <= 0 || static_cast(wav.size()) < window) { + return wav; + } + const auto rms = block_rms(wav, window); + const float threshold = onset_threshold(rms); + int64_t last = -1; + for (int64_t i = 0; i < static_cast(rms.size()); ++i) { + if (rms[static_cast(i)] > threshold) { + last = i; + } + } + if (last < 0) { + return wav; + } + const int64_t end = std::min( + static_cast(wav.size()), + (last + 1) * window + static_cast(trail * static_cast(sample_rate))); + return std::vector(wav.begin(), wav.begin() + static_cast(end)); +} + +void fade_edges( + std::vector & wav, int sample_rate, bool fade_in, bool fade_out, float fade_seconds) { + const auto fade = static_cast(fade_seconds * static_cast(sample_rate)); + if (fade <= 1 || static_cast(wav.size()) <= 2 * fade) { + return; + } + for (int64_t i = 0; i < fade; ++i) { + const float ramp = static_cast(i) / static_cast(fade - 1); + if (fade_in) { + wav[static_cast(i)] *= ramp; + } + if (fade_out) { + wav[wav.size() - static_cast(fade) + static_cast(i)] *= 1.0F - ramp; + } + } +} + +std::vector join_segments(std::vector> parts, int sample_rate) { + std::vector out; + const auto count = static_cast(parts.size()); + for (int64_t i = 0; i < count; ++i) { + fade_edges(parts[static_cast(i)], sample_rate, i > 0, i + 1 < count); + out.insert( + out.end(), + parts[static_cast(i)].begin(), + parts[static_cast(i)].end()); + } + return out; +} + +} // namespace audio_ops + +SoproReferenceBuilder::SoproReferenceBuilder( + const SoproTTSAssets & assets, + const SoproSpeakerEncoderRuntime & speaker_encoder, + const SoproSemanticEncoderRuntime & semantic_encoder, + const SoproVocoderRuntime & vocoder) + : config_(assets.config), + speaker_encoder_(speaker_encoder), + semantic_encoder_(semantic_encoder), + vocoder_(vocoder), + mel_mean_(assets.config.model.acoustic_mel_mean), + mel_std_(assets.config.model.acoustic_mel_std) { + const auto & source = *assets.model_weights; + const int64_t in_dim = config_.model.cond_in_dim; + const int64_t hidden = config_.model.cond_hidden_dim; + cond_proj_w0 = source.require_f32("cond_proj.0.weight", {hidden, in_dim}); + cond_proj_b0 = source.require_f32("cond_proj.0.bias", {hidden}); + cond_proj_w3 = source.require_f32("cond_proj.3.weight", {hidden, hidden}); + cond_proj_b3 = source.require_f32("cond_proj.3.bias", {hidden}); +} + +SoproReference SoproReferenceBuilder::build( + const std::vector & audio24, + float ref_seconds, + std::mt19937_64 & rng) const { + if (audio24.empty()) { + throw std::runtime_error("Sopro requires non-empty reference audio"); + } + const auto sample_rate = static_cast(config_.sample_rate); + auto cropped = audio_ops::crop_on_pause(audio24, ref_seconds, sample_rate, rng); + auto normalized = audio_ops::normalize_reference(cropped, sample_rate); + const float reference_level_db = normalized.level_db; + auto wav = std::move(normalized.wav); + + const auto speaker_rate = static_cast(config_.speaker_encoder.sample_rate); + const auto wav16 = engine::audio::resample_mono_torchaudio_sinc_hann( + wav, sample_rate, speaker_rate); + const auto embeddings = speaker_encoder_.encode(wav16); + + const int64_t in_dim = config_.model.cond_in_dim; + const int64_t hidden = config_.model.cond_hidden_dim; + std::vector conditioning; + conditioning.reserve(static_cast(in_dim)); + conditioning.insert(conditioning.end(), embeddings.id_emb.begin(), embeddings.id_emb.end()); + conditioning.insert(conditioning.end(), embeddings.style_emb.begin(), embeddings.style_emb.end()); + conditioning.insert(conditioning.end(), embeddings.style_ctrl.begin(), embeddings.style_ctrl.end()); + if (static_cast(conditioning.size()) != in_dim) { + throw std::runtime_error( + "Sopro speaker embeddings do not add up to model.cond_in_dim; check config.json"); + } + std::vector projected(static_cast(hidden), 0.0F); + for (int64_t o = 0; o < hidden; ++o) { + const float * row = cond_proj_w0.data() + static_cast(o * in_dim); + double sum = cond_proj_b0[static_cast(o)]; + for (int64_t i = 0; i < in_dim; ++i) { + sum += static_cast(row[i]) * static_cast(conditioning[static_cast(i)]); + } + const auto value = static_cast(sum); + projected[static_cast(o)] = value / (1.0F + std::exp(-value)); // SiLU + } + SoproReference out; + out.cond_vec.assign(static_cast(hidden), 0.0F); + for (int64_t o = 0; o < hidden; ++o) { + const float * row = cond_proj_w3.data() + static_cast(o * hidden); + double sum = cond_proj_b3[static_cast(o)]; + for (int64_t i = 0; i < hidden; ++i) { + sum += static_cast(row[i]) * static_cast(projected[static_cast(i)]); + } + out.cond_vec[static_cast(o)] = static_cast(sum); + } + + out.semantic_tokens = semantic_encoder_.encode(wav); + + auto mel = vocoder_.log_mel(wav); + const int64_t n_mels = vocoder_.n_mels(); + if (n_mels <= 0 || mel.size() % static_cast(n_mels) != 0) { + throw std::runtime_error("Sopro reference mel has an unexpected shape"); + } + out.mel_frames = static_cast(mel.size()) / n_mels; + for (int64_t c = 0; c < n_mels; ++c) { + const float mean = mel_mean_[static_cast(c)]; + const float scale = mel_std_[static_cast(c)]; + float * row = mel.data() + static_cast(c * out.mel_frames); + for (int64_t t = 0; t < out.mel_frames; ++t) { + row[t] = (row[t] - mean) / scale; + } + } + out.mel = std::move(mel); + out.level_db = reference_level_db; + return out; +} + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/sopro_tts/semantic_encoder.cpp b/src/community_models/sopro_tts/semantic_encoder.cpp new file mode 100644 index 000000000..a1f6161c5 --- /dev/null +++ b/src/community_models/sopro_tts/semantic_encoder.cpp @@ -0,0 +1,488 @@ +#include "engine/community_models/sopro_tts/semantic_encoder.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/audio/dsp.h" +#include "engine/framework/audio/resampling.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/attention/scaled_dot_product_attention.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { + +struct SoproSemanticLayerWeights { + engine::modules::NormWeights attn_norm; + engine::modules::LinearWeights q_proj; + engine::modules::LinearWeights k_proj; // bias=False upstream + engine::modules::LinearWeights v_proj; + engine::modules::LinearWeights out_proj; + engine::modules::NormWeights ffn_norm; + engine::modules::LinearWeights fc1; + engine::modules::LinearWeights fc2; +}; + +struct SoproSemanticEncoderWeights { + std::shared_ptr store; + engine::modules::Conv1dWeights conv1; + engine::modules::Conv1dWeights conv2; + engine::core::TensorValue pos_emb; + std::vector layers; + engine::modules::NormWeights final_norm; + // Host-side quantiser head. + std::vector pre_head_norm_weight; + std::vector pre_head_norm_bias; + std::vector digit_head_weight; // [digit_dim, d_model] + std::vector digit_head_bias; + // torchaudio MelSpectrogram buffers. + std::vector analysis_window; + std::vector mel_filterbank; +}; + +namespace { + +namespace binding = engine::modules::binding; + +constexpr float kLayerNormEps = 1.0e-5F; // torch.nn.LayerNorm default +constexpr int64_t kConvRightContextFrames = 2; // semantic.CONV_RIGHT_CONTEXT_FRAMES + +// SOPRO_DUMP_DIR: raw f32/i32 dumps for stage comparison against the reference. +template +void dump(const std::string & name, const std::vector & values) { + const char * dir = std::getenv("SOPRO_DUMP_DIR"); + if (dir == nullptr) { + return; + } + std::FILE * fh = std::fopen((std::string(dir) + "/" + name).c_str(), "wb"); + if (fh != nullptr) { + std::fwrite(values.data(), sizeof(T), values.size(), fh); + std::fclose(fh); + } +} + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +engine::core::TensorValue dense( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & value) { + return engine::core::wrap_tensor(ggml_cont(ctx.ggml, value.tensor), value.shape, GGML_TYPE_F32); +} + +std::shared_ptr load_weights( + ggml_backend_t backend, + engine::core::BackendType backend_type, + const engine::assets::TensorSource & source, + const SoproSemanticEncoderConfig & config, + size_t weight_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type) { + auto weights = std::make_shared(); + require_frontend_buffers( + source, "semantic encoder", + {"frontend.mel.spectrogram.window", + "frontend.mel.mel_scale.fb"}); + weights->store = std::make_shared( + backend, backend_type, "sopro_tts.semantic_encoder.weights", weight_context_bytes); + auto & store = *weights->store; + weights->conv1 = binding::conv1d_from_source( + store, source, "conv1", conv_storage_type, config.d_model, config.n_mels, 3, true); + weights->conv2 = binding::conv1d_from_source( + store, source, "conv2", conv_storage_type, config.d_model, config.d_model, 3, true); + weights->pos_emb = store.load_f32_tensor( + source, "pos_emb", {config.max_positions, config.d_model}); + weights->layers.reserve(static_cast(config.layers)); + for (int64_t layer = 0; layer < config.layers; ++layer) { + const std::string prefix = "layers." + std::to_string(layer); + SoproSemanticLayerWeights out; + out.attn_norm = binding::norm_from_source( + store, source, prefix + ".self_attn_layer_norm", config.d_model); + out.q_proj = binding::linear_from_source( + store, source, prefix + ".self_attn.q_proj", matmul_storage_type, + config.d_model, config.d_model, true); + out.k_proj = binding::linear_from_source( + store, source, prefix + ".self_attn.k_proj", matmul_storage_type, + config.d_model, config.d_model, false); + out.v_proj = binding::linear_from_source( + store, source, prefix + ".self_attn.v_proj", matmul_storage_type, + config.d_model, config.d_model, true); + out.out_proj = binding::linear_from_source( + store, source, prefix + ".self_attn.out_proj", matmul_storage_type, + config.d_model, config.d_model, true); + out.ffn_norm = binding::norm_from_source( + store, source, prefix + ".final_layer_norm", config.d_model); + out.fc1 = binding::linear_from_source( + store, source, prefix + ".fc1", matmul_storage_type, config.ffn_dim, config.d_model, true); + out.fc2 = binding::linear_from_source( + store, source, prefix + ".fc2", matmul_storage_type, config.d_model, config.ffn_dim, true); + weights->layers.push_back(std::move(out)); + } + weights->final_norm = binding::norm_from_source(store, source, "final_norm", config.d_model); + weights->pre_head_norm_weight = source.require_f32("pre_head_norm.weight", {config.d_model}); + weights->pre_head_norm_bias = source.require_f32("pre_head_norm.bias", {config.d_model}); + weights->digit_head_weight = source.require_f32( + "digit_head.weight", {config.digit_dim(), config.d_model}); + weights->digit_head_bias = source.require_f32("digit_head.bias", {config.digit_dim()}); + weights->analysis_window = source.require_f32( + "frontend.mel.spectrogram.window", {config.n_fft}); + weights->mel_filterbank = source.require_f32( + "frontend.mel.mel_scale.fb", {config.n_fft / 2 + 1, config.n_mels}); + store.upload(); + return weights; +} + +} // namespace + +struct SoproSemanticEncoderGraph { + SoproSemanticEncoderGraph( + ggml_backend_t backend_in, + engine::core::BackendType backend_type, + size_t graph_context_bytes, + const SoproSemanticEncoderConfig & config, + std::shared_ptr weights_in, + int64_t mel_frames_in, + int64_t keep_steps) + : backend(backend_in), + weights(std::move(weights_in)), + mel_frames(mel_frames_in), + steps(keep_steps), + d_model(config.d_model) { + if (backend == nullptr || weights == nullptr) { + throw std::runtime_error("Sopro semantic encoder graph requires a backend and weights"); + } + if (mel_frames <= 0 || steps <= 0) { + throw std::runtime_error("Sopro semantic encoder graph requires positive lengths"); + } + ggml_init_params params{graph_context_bytes, nullptr, true}; + ctx.reset(ggml_init(params)); + if (ctx == nullptr) { + throw std::runtime_error("failed to initialize the Sopro semantic encoder graph context"); + } + engine::core::ModuleBuildContext build_ctx{ctx.get(), "sopro_tts.semantic_encoder", backend_type}; + namespace mod = engine::modules; + const auto shape = engine::core::TensorShape::from_dims({1, config.n_mels, mel_frames}); + input = engine::core::make_tensor(build_ctx, GGML_TYPE_F32, shape).tensor; + ggml_set_input(input); + + auto hidden = mod::Conv1dModule({config.n_mels, d_model, 3, 1, 1, 1, true}) + .build(build_ctx, engine::core::wrap_tensor(input, shape, GGML_TYPE_F32), + weights->conv1); + hidden = mod::GeluModule({mod::GeluApproximation::ExactErf}).build(build_ctx, hidden); + hidden = mod::Conv1dModule({d_model, d_model, 3, 2, 1, 1, true}) + .build(build_ctx, hidden, weights->conv2); + hidden = mod::GeluModule({mod::GeluApproximation::ExactErf}).build(build_ctx, hidden); + // [1, C, T] -> [1, T, C] + hidden = mod::TransposeModule({{0, 2, 1, 3}, 3}).build(build_ctx, hidden); + const int64_t conv_steps = hidden.shape.dims[1]; + if (conv_steps < steps) { + throw std::runtime_error("Sopro semantic encoder produced fewer frames than expected"); + } + { + auto positions = mod::SliceModule({0, 0, conv_steps}).build(build_ctx, weights->pos_emb); + positions = engine::core::reshape_tensor( + build_ctx, engine::core::ensure_backend_addressable_layout(build_ctx, positions), + engine::core::TensorShape::from_dims({1, conv_steps, d_model})); + hidden = mod::AddModule{}.build(build_ctx, hidden, positions); + } + hidden = mod::SliceModule({1, 0, steps}).build(build_ctx, hidden); + hidden = dense(build_ctx, hidden); + + const int64_t heads = config.heads; + const int64_t head_dim = config.head_dim(); + for (const auto & layer : weights->layers) { + auto norm = mod::LayerNormModule({d_model, kLayerNormEps, true, true}) + .build(build_ctx, hidden, layer.attn_norm); + auto q = mod::LinearModule({d_model, d_model, true, GGML_PREC_F32}) + .build(build_ctx, norm, layer.q_proj); + auto k = mod::LinearModule({d_model, d_model, false, GGML_PREC_F32}) + .build(build_ctx, norm, layer.k_proj); + auto v = mod::LinearModule({d_model, d_model, true, GGML_PREC_F32}) + .build(build_ctx, norm, layer.v_proj); + const auto head_shape = engine::core::TensorShape::from_dims({1, steps, heads, head_dim}); + auto to_heads = [&](const engine::core::TensorValue & value) { + auto reshaped = engine::core::reshape_tensor( + build_ctx, engine::core::ensure_backend_addressable_layout(build_ctx, value), + head_shape); + // Flash attention needs dense [1, H, T, DH] operands. + return dense(build_ctx, mod::TransposeModule({{0, 2, 1, 3}, 4}).build(build_ctx, reshaped)); + }; + auto attn = mod::ScaledDotProductAttentionModule({ + head_dim, + mod::ScaledDotProductAttentionLowering::Flash, + GGML_PREC_F32, + mod::AttentionCausality::NonCausal, + }).build(build_ctx, to_heads(q), to_heads(k), to_heads(v)); + auto flat = engine::core::reshape_tensor( + build_ctx, engine::core::ensure_backend_addressable_layout(build_ctx, attn), + engine::core::TensorShape::from_dims({1, steps, d_model})); + auto projected = mod::LinearModule({d_model, d_model, true, GGML_PREC_F32}) + .build(build_ctx, flat, layer.out_proj); + hidden = mod::AddModule{}.build(build_ctx, hidden, projected); + + auto ffn = mod::LayerNormModule({d_model, kLayerNormEps, true, true}) + .build(build_ctx, hidden, layer.ffn_norm); + ffn = mod::LinearModule({d_model, config.ffn_dim, true, GGML_PREC_F32}) + .build(build_ctx, ffn, layer.fc1); + ffn = mod::GeluModule({mod::GeluApproximation::ExactErf}).build(build_ctx, ffn); + ffn = mod::LinearModule({config.ffn_dim, d_model, true, GGML_PREC_F32}) + .build(build_ctx, ffn, layer.fc2); + hidden = mod::AddModule{}.build(build_ctx, hidden, ffn); + } + hidden = mod::LayerNormModule({d_model, kLayerNormEps, true, true}) + .build(build_ctx, hidden, weights->final_norm); + hidden = engine::core::ensure_backend_addressable_layout(build_ctx, hidden); + output = hidden.tensor; + ggml_set_output(output); + graph = ggml_new_graph_custom(ctx.get(), 65536, false); + ggml_build_forward_expand(graph, output); + gallocr = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend)); + if (gallocr == nullptr || !ggml_gallocr_reserve(gallocr, graph) || + !ggml_gallocr_alloc_graph(gallocr, graph)) { + throw std::runtime_error("failed to allocate the Sopro semantic encoder graph"); + } + } + + ~SoproSemanticEncoderGraph() { + if (gallocr != nullptr) { + ggml_gallocr_free(gallocr); + gallocr = nullptr; + } + } + + bool matches(const SoproSemanticEncoderWeights & other, int64_t frames, int64_t keep) const noexcept { + return weights.get() == &other && mel_frames == frames && steps == keep; + } + + std::vector run(const std::vector & log_mel) { + ggml_backend_tensor_set(input, log_mel.data(), 0, log_mel.size() * sizeof(float)); + const ggml_status status = engine::core::compute_backend_graph(backend, graph); + ggml_backend_synchronize(backend); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Sopro semantic encoder graph compute failed"); + } + std::vector out(static_cast(steps * d_model), 0.0F); + ggml_backend_tensor_get(output, out.data(), 0, out.size() * sizeof(float)); + return out; + } + + ggml_backend_t backend = nullptr; + std::shared_ptr weights; + int64_t mel_frames = 0; + int64_t steps = 0; + int64_t d_model = 0; + std::unique_ptr ctx; + ggml_tensor * input = nullptr; + ggml_tensor * output = nullptr; + ggml_cgraph * graph = nullptr; + ggml_gallocr_t gallocr = nullptr; +}; + +SoproSemanticEncoderRuntime::SoproSemanticEncoderRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t weight_context_bytes, + size_t graph_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type) + : config_(assets.config.semantic_encoder), + source_sample_rate_(assets.config.sample_rate), + execution_context_(execution_context), + graph_context_bytes_(graph_context_bytes), + weights_(load_weights( + execution_context.backend(), + execution_context.backend_type(), + *assets.semantic_encoder_weights, + assets.config.semantic_encoder, + weight_context_bytes, + matmul_storage_type, + conv_storage_type)) {} + +SoproSemanticEncoderRuntime::~SoproSemanticEncoderRuntime() = default; + +std::vector SoproSemanticEncoderRuntime::encode(const std::vector & audio24) const { + if (audio24.empty()) { + throw std::runtime_error("Sopro semantic encoder requires a non-empty reference waveform"); + } + const auto n24 = static_cast(audio24.size()); + const int64_t token_samples = config_.token_samples_24k; + const int64_t n_tokens = (n24 + token_samples - 1) / token_samples; + + if (source_sample_rate_ <= 0 || config_.sample_rate <= 0) { + throw std::runtime_error("Sopro semantic encoder sample rates must be positive"); + } + auto audio16 = engine::audio::resample_mono_torchaudio_sinc_hann( + audio24, static_cast(source_sample_rate_), static_cast(config_.sample_rate)); + // SemanticEncoder.encode pins the resampled length so the token grid is + // exactly reproducible regardless of the resampler's tail behaviour. + const int64_t n16 = + (n24 * config_.sample_rate + source_sample_rate_ - 1) / source_sample_rate_; + audio16.resize(static_cast(n16), 0.0F); + + dump("sem_wav16.f32", audio16); + const int64_t frames = (n16 + config_.hop_length - 1) / config_.hop_length; + const int64_t mel_frames = frames + kConvRightContextFrames; + // WhisperMelFrontend right-pads n_fft zeros before the centred STFT. + audio16.resize(static_cast(n16 + config_.n_fft), 0.0F); + + const int64_t freq_bins = config_.n_fft / 2 + 1; + engine::audio::STFTConfig stft; + stft.n_fft = config_.n_fft; + stft.hop_length = config_.hop_length; + stft.win_length = config_.n_fft; + stft.center = true; + stft.pad_mode = engine::audio::STFTPadMode::Reflect; + const auto magnitude = engine::audio::STFT{}.compute_magnitude( + audio16, weights_->analysis_window, 1, static_cast(audio16.size()), stft, + static_cast(execution_context_.config().threads)); + if (magnitude.shape.size() != 3 || magnitude.shape[1] != freq_bins) { + throw std::runtime_error("Sopro semantic encoder STFT produced an unexpected layout"); + } + const int64_t stft_frames = magnitude.shape[2]; + if (stft_frames < mel_frames) { + throw std::runtime_error("Sopro semantic encoder reference audio is too short"); + } + + std::vector mel(static_cast(config_.n_mels * mel_frames), 0.0F); + for (int64_t f = 0; f < freq_bins; ++f) { + const float * fb_row = weights_->mel_filterbank.data() + static_cast(f * config_.n_mels); + const float * spec_row = magnitude.values.data() + static_cast(f * stft_frames); + for (int64_t m = 0; m < config_.n_mels; ++m) { + const float weight = fb_row[m]; + if (weight == 0.0F) { + continue; + } + float * out_row = mel.data() + static_cast(m * mel_frames); + for (int64_t t = 0; t < mel_frames; ++t) { + out_row[t] += weight * spec_row[t] * spec_row[t]; // power = 2.0 + } + } + } + float peak = -std::numeric_limits::infinity(); + for (auto & value : mel) { + value = std::log10(std::max(value, 1.0e-10F)); + peak = std::max(peak, value); + } + const float floor_value = peak - 8.0F; + for (auto & value : mel) { + value = (std::max(value, floor_value) + 4.0F) / 4.0F; + } + + dump("sem_mel.f32", mel); + const int64_t steps = (frames + 1) / 2; // n50 + if (steps <= 0) { + throw std::runtime_error("Sopro semantic encoder reference audio is too short"); + } + if (steps > config_.max_positions) { + throw std::runtime_error( + "Sopro semantic encoder reference audio exceeds the positional embedding table"); + } + if (graph_ == nullptr || !graph_->matches(*weights_, mel_frames, steps)) { + // Free the previous arena first; otherwise both are resident while the + // replacement is allocated, and every segment rebuilds this graph. + graph_.reset(); + graph_ = std::make_unique( + execution_context_.backend(), + execution_context_.backend_type(), + graph_context_bytes_, + config_, + weights_, + mel_frames, + steps); + } + const auto hidden = graph_->run(mel); + dump("sem_hidden.f32", hidden); + + // SemanticEncoder._interpolate: half-pixel aligned linear resampling from + // `steps` encoder frames onto the `n_tokens` output grid. + const int64_t d_model = config_.d_model; + const int64_t digit_dim = config_.digit_dim(); + std::vector tokens(static_cast(n_tokens), 0); + std::vector frame(static_cast(d_model), 0.0F); + std::vector logits(static_cast(digit_dim), 0.0F); + const float ratio = static_cast(steps) / static_cast(n_tokens); + for (int64_t t = 0; t < n_tokens; ++t) { + float source = (static_cast(t) + 0.5F) * ratio - 0.5F; + source = std::min(std::max(source, 0.0F), static_cast(steps - 1)); + const auto left = static_cast(std::floor(source)); + const int64_t right = std::min(left + 1, steps - 1); + const float weight = source - static_cast(left); + const float * left_row = hidden.data() + static_cast(left * d_model); + const float * right_row = hidden.data() + static_cast(right * d_model); + for (int64_t c = 0; c < d_model; ++c) { + frame[static_cast(c)] = left_row[c] * (1.0F - weight) + right_row[c] * weight; + } + // pre_head_norm + double sum = 0.0; + for (int64_t c = 0; c < d_model; ++c) { + sum += frame[static_cast(c)]; + } + const double mean = sum / static_cast(d_model); + double variance = 0.0; + for (int64_t c = 0; c < d_model; ++c) { + const double centred = frame[static_cast(c)] - mean; + variance += centred * centred; + } + variance /= static_cast(d_model); + const double inv_std = 1.0 / std::sqrt(variance + kLayerNormEps); + for (int64_t c = 0; c < d_model; ++c) { + frame[static_cast(c)] = static_cast( + (frame[static_cast(c)] - mean) * inv_std * + weights_->pre_head_norm_weight[static_cast(c)] + + weights_->pre_head_norm_bias[static_cast(c)]); + } + for (int64_t d = 0; d < digit_dim; ++d) { + double value = weights_->digit_head_bias[static_cast(d)]; + const float * row = weights_->digit_head_weight.data() + static_cast(d * d_model); + for (int64_t c = 0; c < d_model; ++c) { + value += static_cast(row[c]) * static_cast(frame[static_cast(c)]); + } + logits[static_cast(d)] = static_cast(value); + } + // Finite scalar quantiser: per-level arg-max, packed with mixed radix. + int64_t token = 0; + int64_t base = 1; + int64_t offset = 0; + for (const int64_t level : config_.fsq_levels) { + int64_t best = 0; + float best_value = logits[static_cast(offset)]; + for (int64_t i = 1; i < level; ++i) { + const float value = logits[static_cast(offset + i)]; + if (value > best_value) { + best_value = value; + best = i; + } + } + token += best * base; + base *= level; + offset += level; + } + tokens[static_cast(t)] = static_cast(token); + } + dump("sem_tokens.i32", tokens); + return tokens; +} + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/sopro_tts/semantic_lm.cpp b/src/community_models/sopro_tts/semantic_lm.cpp new file mode 100644 index 000000000..940e9012e --- /dev/null +++ b/src/community_models/sopro_tts/semantic_lm.cpp @@ -0,0 +1,597 @@ +#include "engine/community_models/sopro_tts/semantic_lm.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/transformers/qwen_causal_decode_runtime.h" +#include "engine/framework/modules/transformers/qwen_causal_decoder.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { +namespace { + +namespace binding = engine::modules::binding; + +constexpr float kRmsNormEps = 1.0e-6F; // sopro.nn.layers.RMSNorm default +constexpr float kMaskedLogit = -1.0e9F; // sampling.sample_next_token + +// StylePrefixEncoder runs on the host: eight learned queries cross-attend to at +// most a few hundred reference frames, which is under 0.1 GFLOP. +struct SoproStylePrefixWeights { + std::vector queries; // [tokens, dim] + std::vector kv_norm; // [dim] + std::vector q_proj; // [dim, dim] + std::vector k_proj; + std::vector v_proj; + std::vector out_proj; + std::vector out_norm; // [dim] +}; + +struct SoproSemanticLMHostWeights { + std::vector text_embedding; // [text_vocab, dim] + std::vector semantic_embedding; // [semantic_vocab + 2, dim], fused + SoproStylePrefixWeights style_prefix; +}; + +struct SoproSemanticLMBackendWeights { + std::shared_ptr store; + engine::core::TensorValue token_embedding; + engine::modules::QwenDecoderStackWeights stack; + engine::modules::NormWeights final_norm; + engine::modules::LinearWeights token_head; +}; + +void rms_norm(std::vector & values, const std::vector & weight) { + const size_t dim = weight.size(); + double sum = 0.0; + for (size_t i = 0; i < dim; ++i) { + sum += static_cast(values[i]) * static_cast(values[i]); + } + const double inv = 1.0 / std::sqrt(sum / static_cast(dim) + kRmsNormEps); + for (size_t i = 0; i < dim; ++i) { + values[i] = static_cast(values[i] * inv * weight[i]); + } +} + +// out[row] = weight @ in[row]; weight is [out_dim, in_dim] row-major. +void matmul_rows( + const std::vector & weight, + const float * input, + int64_t rows, + int64_t in_dim, + int64_t out_dim, + float * output) { +#ifdef _OPENMP +#pragma omp parallel for if (rows > 8) +#endif + for (int64_t row = 0; row < rows; ++row) { + const float * source = input + row * in_dim; + float * target = output + row * out_dim; + for (int64_t o = 0; o < out_dim; ++o) { + const float * w = weight.data() + static_cast(o * in_dim); + double sum = 0.0; + for (int64_t i = 0; i < in_dim; ++i) { + sum += static_cast(w[i]) * static_cast(source[i]); + } + target[o] = static_cast(sum); + } + } +} + +// Fuse LayerScale into the projection that feeds the residual: the branch is +// scale * W @ y with no bias, so scaling the rows of W is exact. +std::vector scale_rows(std::vector weight, const std::vector & scale, int64_t in_dim) { + for (size_t row = 0; row < scale.size(); ++row) { + float * values = weight.data() + static_cast(static_cast(row) * in_dim); + const float factor = scale[row]; + for (int64_t i = 0; i < in_dim; ++i) { + values[i] *= factor; + } + } + return weight; +} + +engine::modules::QwenCausalDecoderConfig make_decoder_config( + const SoproModelConfig & config, + engine::core::BackendType backend_type) { + engine::modules::QwenCausalDecoderConfig out; + out.stack.hidden_size = config.ar_model_dim; + out.stack.num_attention_heads = config.ar_heads; + out.stack.num_key_value_heads = config.ar_kv_heads; + out.stack.head_dim = config.ar_head_dim(); + out.stack.intermediate_size = config.ar_ffn_dim(); + out.stack.layers = config.ar_blocks; + out.stack.rms_norm_eps = kRmsNormEps; + out.stack.rope_theta = 10000.0F; + // sopro.nn.layers.rotate_half splits the head in halves, which is ggml's + // NEOX rotary layout. + out.stack.rope_type = GGML_ROPE_TYPE_NEOX; + out.stack.attention_precision = GGML_PREC_DEFAULT; + out.stack.projection_precision = GGML_PREC_DEFAULT; + out.stack.use_qk_norm = config.ar_qk_rms_norm; + out.stack.qkv_layout = engine::modules::QwenDecoderQKVLayout::PackedQKV; + out.stack.runtime.mlp.mode = engine::modules::QwenDecoderMLPMode::PackedGateUp; + out.stack.runtime.attention.prefill_mode = engine::modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.stack.runtime.attention.static_mode = engine::modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.stack.runtime.static_cache.update_mode = engine::modules::QwenDecoderStaticCacheUpdateMode::DirectSetRows; + out.logits_size = config.semantic_vocab_size + 2; + out.logits_mode = engine::modules::QwenCausalDecoderLogitsMode::LastStep; + out.use_lm_head_bias = true; // SemanticLM.token_head is a biased Linear + out.lm_head_precision = GGML_PREC_DEFAULT; + (void) backend_type; + return out; +} + +engine::modules::QwenDecoderLayerWeights load_layer( + engine::core::BackendWeightStore & store, + const engine::assets::TensorSource & source, + const SoproModelConfig & config, + engine::assets::TensorStorageType storage_type, + int64_t layer) { + const std::string prefix = "ar_prior.temporal.layers." + std::to_string(layer); + const int64_t dim = config.ar_model_dim; + const int64_t head_dim = config.ar_head_dim(); + const int64_t q_out = config.ar_heads * head_dim; + const int64_t kv_out = config.ar_kv_heads * head_dim; + const int64_t ffn = config.ar_ffn_dim(); + + engine::modules::QwenDecoderLayerWeights out; + out.input_norm = binding::norm_weight_from_source(store, source, prefix + ".attn_norm", dim); + auto qkv = source.require_f32(prefix + ".attn.q_proj.weight", {q_out, dim}); + const auto k_rows = source.require_f32(prefix + ".attn.k_proj.weight", {kv_out, dim}); + const auto v_rows = source.require_f32(prefix + ".attn.v_proj.weight", {kv_out, dim}); + qkv.insert(qkv.end(), k_rows.begin(), k_rows.end()); + qkv.insert(qkv.end(), v_rows.begin(), v_rows.end()); + out.self_attention.qkv_weight = store.make_from_f32( + engine::core::TensorShape::from_dims({q_out + kv_out * 2, dim}), storage_type, std::move(qkv)); + out.self_attention.out_weight = store.make_from_f32( + engine::core::TensorShape::from_dims({dim, q_out}), + storage_type, + scale_rows( + source.require_f32(prefix + ".attn.out_proj.weight", {dim, q_out}), + source.require_f32(prefix + ".attn_scale.scale", {dim}), + q_out)); + if (config.ar_qk_rms_norm) { + out.q_norm = binding::norm_weight_from_source(store, source, prefix + ".attn.q_norm", head_dim); + out.k_norm = binding::norm_weight_from_source(store, source, prefix + ".attn.k_norm", head_dim); + } + out.post_norm = binding::norm_weight_from_source(store, source, prefix + ".ffn_norm", dim); + auto gate_up = source.require_f32(prefix + ".ffn.gate_proj.weight", {ffn, dim}); + const auto up_rows = source.require_f32(prefix + ".ffn.up_proj.weight", {ffn, dim}); + gate_up.insert(gate_up.end(), up_rows.begin(), up_rows.end()); + out.mlp.gate_up_proj = engine::modules::LinearWeights{ + store.make_from_f32( + engine::core::TensorShape::from_dims({ffn * 2, dim}), storage_type, std::move(gate_up)), + std::nullopt}; + out.mlp.down_proj = engine::modules::LinearWeights{ + store.make_from_f32( + engine::core::TensorShape::from_dims({dim, ffn}), + storage_type, + scale_rows( + source.require_f32(prefix + ".ffn.down_proj.weight", {dim, ffn}), + source.require_f32(prefix + ".ffn_scale.scale", {dim}), + ffn)), + std::nullopt}; + return out; +} + +SoproSemanticLMHostWeights load_host_weights( + const engine::assets::TensorSource & source, + const SoproModelConfig & config) { + SoproSemanticLMHostWeights out; + const int64_t dim = config.ar_model_dim; + const int64_t latent = config.latent_dim; + const int64_t semantic_rows = config.semantic_vocab_size + 2; + + out.text_embedding = source.require_f32("text_tok_emb.weight", {config.text_vocab_size, dim}); + + // embed_semantic = sem_in_proj(semantic_tok_emb(id)); fold the projection + // into the table so every later lookup is a plain row gather. + const auto table = source.require_f32("semantic_tok_emb.weight", {semantic_rows, latent}); + const auto projection = source.require_f32("sem_in_proj.weight", {dim, latent}); + const auto bias = source.require_f32("sem_in_proj.bias", {dim}); + out.semantic_embedding.assign(static_cast(semantic_rows * dim), 0.0F); +#ifdef _OPENMP +#pragma omp parallel for +#endif + for (int64_t row = 0; row < semantic_rows; ++row) { + const float * source_row = table.data() + static_cast(row * latent); + float * target = out.semantic_embedding.data() + static_cast(row * dim); + for (int64_t o = 0; o < dim; ++o) { + const float * w = projection.data() + static_cast(o * latent); + double sum = bias[static_cast(o)]; + for (int64_t i = 0; i < latent; ++i) { + sum += static_cast(w[i]) * static_cast(source_row[i]); + } + target[o] = static_cast(sum); + } + } + + auto & style = out.style_prefix; + style.queries = source.require_f32("style_prefix.queries", {config.style_prefix_tokens, dim}); + style.kv_norm = source.require_f32("style_prefix.kv_norm.weight", {dim}); + style.q_proj = source.require_f32("style_prefix.q_proj.weight", {dim, dim}); + style.k_proj = source.require_f32("style_prefix.k_proj.weight", {dim, dim}); + style.v_proj = source.require_f32("style_prefix.v_proj.weight", {dim, dim}); + style.out_proj = source.require_f32("style_prefix.out_proj.weight", {dim, dim}); + style.out_norm = source.require_f32("style_prefix.out_norm.weight", {dim}); + return out; +} + +// StylePrefixEncoder.forward for a single sequence. +std::vector build_style_prefix( + const SoproStylePrefixWeights & weights, + const std::vector & reference, // [steps, dim] + int64_t steps, + int64_t tokens, + int64_t dim, + int64_t heads) { + const int64_t head_dim = dim / heads; + std::vector out(static_cast(tokens * dim), 0.0F); + if (steps == 0) { + // The reference path returns out_norm(queries) when there is nothing + // to attend to. + for (int64_t t = 0; t < tokens; ++t) { + std::vector row( + weights.queries.begin() + static_cast(t * dim), + weights.queries.begin() + static_cast((t + 1) * dim)); + rms_norm(row, weights.out_norm); + std::copy(row.begin(), row.end(), out.begin() + static_cast(t * dim)); + } + return out; + } + + std::vector kv(static_cast(steps * dim), 0.0F); + for (int64_t s = 0; s < steps; ++s) { + std::vector row( + reference.begin() + static_cast(s * dim), + reference.begin() + static_cast((s + 1) * dim)); + rms_norm(row, weights.kv_norm); + std::copy(row.begin(), row.end(), kv.begin() + static_cast(s * dim)); + } + + std::vector q(static_cast(tokens * dim), 0.0F); + std::vector k(static_cast(steps * dim), 0.0F); + std::vector v(static_cast(steps * dim), 0.0F); + matmul_rows(weights.q_proj, weights.queries.data(), tokens, dim, dim, q.data()); + matmul_rows(weights.k_proj, kv.data(), steps, dim, dim, k.data()); + matmul_rows(weights.v_proj, kv.data(), steps, dim, dim, v.data()); + + const auto scale = static_cast(1.0 / std::sqrt(static_cast(head_dim))); + std::vector context(static_cast(tokens * dim), 0.0F); + std::vector scores(static_cast(steps), 0.0F); + for (int64_t t = 0; t < tokens; ++t) { + for (int64_t h = 0; h < heads; ++h) { + const float * q_head = q.data() + static_cast(t * dim + h * head_dim); + float max_score = -std::numeric_limits::infinity(); + for (int64_t s = 0; s < steps; ++s) { + const float * k_head = k.data() + static_cast(s * dim + h * head_dim); + double sum = 0.0; + for (int64_t d = 0; d < head_dim; ++d) { + sum += static_cast(q_head[d]) * static_cast(k_head[d]); + } + const auto value = static_cast(sum) * scale; + scores[static_cast(s)] = value; + max_score = std::max(max_score, value); + } + double total = 0.0; + for (auto & score : scores) { + score = std::exp(score - max_score); + total += score; + } + float * target = context.data() + static_cast(t * dim + h * head_dim); + for (int64_t s = 0; s < steps; ++s) { + const float weight = static_cast(scores[static_cast(s)] / total); + const float * v_head = v.data() + static_cast(s * dim + h * head_dim); + for (int64_t d = 0; d < head_dim; ++d) { + target[d] += weight * v_head[d]; + } + } + } + } + + std::vector projected(static_cast(tokens * dim), 0.0F); + matmul_rows(weights.out_proj, context.data(), tokens, dim, dim, projected.data()); + for (int64_t t = 0; t < tokens; ++t) { + std::vector row(static_cast(dim), 0.0F); + for (int64_t d = 0; d < dim; ++d) { + row[static_cast(d)] = + weights.queries[static_cast(t * dim + d)] + + projected[static_cast(t * dim + d)]; + } + rms_norm(row, weights.out_norm); + std::copy(row.begin(), row.end(), out.begin() + static_cast(t * dim)); + } + return out; +} + +} // namespace + +int32_t sample_next_token( + std::vector & logits, + float temperature, + float top_p, + int64_t top_k, + int32_t bos_id, + int32_t eos_id, + bool allow_eos, + std::mt19937_64 & rng) { + const auto vocab = static_cast(logits.size()); + if (vocab <= 0) { + throw std::runtime_error("Sopro semantic LM produced empty logits"); + } + if (bos_id < 0 || bos_id >= vocab || eos_id < 0 || eos_id >= vocab) { + throw std::runtime_error("Sopro semantic LM bos/eos id is outside the logit range"); + } + logits[static_cast(bos_id)] = kMaskedLogit; + if (!allow_eos) { + logits[static_cast(eos_id)] = kMaskedLogit; + } + if (temperature <= 0.0F) { + return static_cast( + std::distance(logits.begin(), std::max_element(logits.begin(), logits.end()))); + } + const float inv_temperature = 1.0F / std::max(1.0e-5F, temperature); + float max_logit = -std::numeric_limits::infinity(); + for (auto & value : logits) { + value *= inv_temperature; + max_logit = std::max(max_logit, value); + } + std::vector probs(logits.size(), 0.0F); + double total = 0.0; + for (size_t i = 0; i < logits.size(); ++i) { + probs[i] = std::exp(logits[i] - max_logit); + total += probs[i]; + } + for (auto & value : probs) { + value = static_cast(value / total); + } + + if (top_k > 0 && top_k < vocab) { + std::vector sorted(probs); + std::nth_element( + sorted.begin(), sorted.begin() + static_cast(top_k - 1), sorted.end(), + std::greater()); + const float kth = sorted[static_cast(top_k - 1)]; + double sum = 0.0; + for (auto & value : probs) { + if (value < kth) { + value = 0.0F; + } + sum += value; + } + const auto inv = static_cast(1.0 / std::max(sum, 1.0e-8)); + for (auto & value : probs) { + value *= inv; + } + } + + if (top_p < 1.0F) { + const float threshold = std::min(std::max(top_p, 0.0F), 1.0F); + std::vector order(static_cast(vocab)); + std::iota(order.begin(), order.end(), 0); + std::stable_sort(order.begin(), order.end(), [&](int32_t a, int32_t b) { + return probs[static_cast(a)] > probs[static_cast(b)]; + }); + // remove[i] = cdf[i - 1] > p: the first token above the threshold is + // kept, everything past it is dropped. + std::vector nucleus(probs.size(), 0.0F); + double cumulative = 0.0; + double sum = 0.0; + for (size_t rank = 0; rank < order.size(); ++rank) { + const auto index = static_cast(order[rank]); + if (rank == 0 || cumulative <= threshold) { + nucleus[index] = probs[index]; + sum += probs[index]; + } + cumulative += probs[index]; + } + const auto inv = static_cast(1.0 / std::max(sum, 1.0e-8)); + for (size_t i = 0; i < probs.size(); ++i) { + probs[i] = nucleus[i] * inv; + } + } + + std::uniform_real_distribution uniform(0.0, 1.0); + const double draw = uniform(rng); + double cumulative = 0.0; + for (size_t i = 0; i < probs.size(); ++i) { + cumulative += probs[i]; + if (draw < cumulative) { + return static_cast(i); + } + } + for (int64_t i = vocab - 1; i >= 0; --i) { + if (probs[static_cast(i)] > 0.0F) { + return static_cast(i); + } + } + throw std::runtime_error("Sopro semantic LM sampling found no candidate token"); +} + +class SoproSemanticLMRuntime::Impl { +public: + Impl( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution, + size_t prefill_graph_arena_bytes, + size_t decode_graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType weight_storage_type) + : config_(assets.config.model) { + const auto & source = *assets.model_weights; + host_ = load_host_weights(source, config_); + + backend_.store = std::make_shared( + execution.backend(), execution.backend_type(), "sopro_tts.semantic_lm.weights", + weight_context_bytes); + auto & store = *backend_.store; + backend_.token_embedding = store.load_tensor( + source, "text_tok_emb.weight", weight_storage_type, + {config_.text_vocab_size, config_.ar_model_dim}); + backend_.stack.layers.reserve(static_cast(config_.ar_blocks)); + for (int64_t layer = 0; layer < config_.ar_blocks; ++layer) { + backend_.stack.layers.push_back( + load_layer(store, source, config_, weight_storage_type, layer)); + } + backend_.final_norm = binding::norm_weight_from_source( + store, source, "ar_prior.out_norm", config_.ar_model_dim); + backend_.token_head = binding::linear_from_source( + store, source, "ar_prior.token_head", weight_storage_type, + config_.semantic_vocab_size + 2, config_.ar_model_dim, true); + store.upload(); + + engine::modules::QwenCausalDecodeRuntimeConfig runtime_config; + runtime_config.trace_name = "sopro_tts.semantic_lm"; + runtime_config.decoder = make_decoder_config(config_, execution.backend_type()); + runtime_config.prefill_graph_arena_bytes = prefill_graph_arena_bytes; + runtime_config.decode_graph_arena_bytes = decode_graph_arena_bytes; + runtime_config.output_mode = engine::modules::QwenCausalDecodeOutputMode::Logits; + runtime_config.return_hidden = false; + + engine::modules::QwenCausalDecodeRuntimeWeights runtime_weights; + runtime_weights.token_embedding = backend_.token_embedding; + runtime_weights.stack = backend_.stack; + runtime_weights.final_norm = backend_.final_norm; + runtime_weights.lm_head = backend_.token_head; + decoder_ = std::make_unique( + execution, std::move(runtime_config), std::move(runtime_weights)); + } + + std::vector generate( + const std::vector & text_ids, + const std::vector & style_tokens, + const std::vector & prompt_tokens, + const SoproSemanticLMOptions & options, + std::mt19937_64 & rng) { + const int64_t dim = config_.ar_model_dim; + const auto text_steps = std::min( + static_cast(text_ids.size()), config_.max_text_len); + const int64_t style_steps = config_.style_prefix_tokens; + const auto prompt_steps = static_cast(prompt_tokens.size()); + const int64_t steps = style_steps + text_steps + prompt_steps + 1; + + std::vector prefix(static_cast(steps * dim), 0.0F); + { + std::vector style_reference( + static_cast(static_cast(style_tokens.size()) * dim), 0.0F); + for (size_t i = 0; i < style_tokens.size(); ++i) { + copy_semantic_row(style_tokens[i], style_reference.data() + i * static_cast(dim)); + } + const auto style = build_style_prefix( + host_.style_prefix, style_reference, static_cast(style_tokens.size()), + style_steps, dim, config_.ar_heads); + std::copy(style.begin(), style.end(), prefix.begin()); + } + int64_t offset = style_steps; + for (int64_t i = 0; i < text_steps; ++i) { + const int32_t id = text_ids[static_cast(i)]; + if (id < 0 || id >= config_.text_vocab_size) { + throw std::runtime_error("Sopro text token id is out of range"); + } + const float * row = host_.text_embedding.data() + static_cast(id * dim); + std::copy(row, row + dim, prefix.begin() + static_cast((offset + i) * dim)); + } + offset += text_steps; + for (int64_t i = 0; i < prompt_steps; ++i) { + copy_semantic_row( + prompt_tokens[static_cast(i)], + prefix.data() + static_cast((offset + i) * dim)); + } + offset += prompt_steps; + copy_semantic_row( + static_cast(config_.semantic_bos_id()), + prefix.data() + static_cast(offset * dim)); + + const int64_t max_steps = std::max(1, options.max_steps); + auto prefill = decoder_->prefill_embeddings(prefix, steps); + decoder_->start_decode_embeddings(prefill.state, steps + max_steps); + + const auto bos_id = static_cast(config_.semantic_bos_id()); + const auto eos_id = static_cast(config_.semantic_eos_id()); + const int64_t min_steps = std::max(1, options.min_steps); + std::vector tokens; + tokens.reserve(static_cast(max_steps)); + std::vector logits = std::move(prefill.logits); + std::vector embedding(static_cast(dim), 0.0F); + for (int64_t step = 0; step < max_steps; ++step) { + const bool allow_eos = (step + 1) >= min_steps; + int32_t token = sample_next_token( + logits, options.temperature, options.top_p, options.top_k, + bos_id, eos_id, allow_eos, rng); + if (allow_eos && token == eos_id) { + break; + } + token = std::min( + std::max(token, 0), static_cast(config_.semantic_vocab_size - 1)); + tokens.push_back(token); + if (step + 1 >= max_steps) { + break; + } + copy_semantic_row(token, embedding.data()); + logits = std::move(decoder_->decode_embedding(embedding).logits); + } + return tokens; + } + + void release_runtime_graphs() { + decoder_->release_runtime_graphs(); + } + +private: + void copy_semantic_row(int32_t id, float * target) const { + if (id < 0 || id >= config_.semantic_vocab_size + 2) { + throw std::runtime_error("Sopro semantic token id is out of range"); + } + const float * row = + host_.semantic_embedding.data() + static_cast(id * config_.ar_model_dim); + std::copy(row, row + config_.ar_model_dim, target); + } + + const SoproModelConfig & config_; + SoproSemanticLMHostWeights host_; + SoproSemanticLMBackendWeights backend_; + std::unique_ptr decoder_; +}; + +SoproSemanticLMRuntime::SoproSemanticLMRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t prefill_graph_arena_bytes, + size_t decode_graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType weight_storage_type) + : impl_(std::make_unique( + assets, execution_context, prefill_graph_arena_bytes, decode_graph_arena_bytes, + weight_context_bytes, weight_storage_type)) {} + +SoproSemanticLMRuntime::~SoproSemanticLMRuntime() = default; + +std::vector SoproSemanticLMRuntime::generate( + const std::vector & text_ids, + const std::vector & style_tokens, + const std::vector & prompt_tokens, + const SoproSemanticLMOptions & options, + std::mt19937_64 & rng) const { + return impl_->generate(text_ids, style_tokens, prompt_tokens, options, rng); +} + +void SoproSemanticLMRuntime::release_runtime_graphs() { + impl_->release_runtime_graphs(); +} + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/sopro_tts/session.cpp b/src/community_models/sopro_tts/session.cpp new file mode 100644 index 000000000..1a9921196 --- /dev/null +++ b/src/community_models/sopro_tts/session.cpp @@ -0,0 +1,591 @@ +#include "engine/community_models/sopro_tts/session.h" + +#include "engine/community_models/sopro_tts/acoustic.h" +#include "engine/community_models/sopro_tts/reference.h" +#include "engine/community_models/sopro_tts/semantic_encoder.h" +#include "engine/community_models/sopro_tts/semantic_lm.h" +#include "engine/community_models/sopro_tts/speaker_encoder.h" +#include "engine/community_models/sopro_tts/text_tokenizer.h" +#include "engine/community_models/sopro_tts/vocoder.h" + +#include "engine/framework/audio/conversion.h" +#include "engine/framework/audio/resampling.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/debug/trace.h" +#include "engine/framework/runtime/options.h" +#include "engine/framework/runtime/spec_backed_model.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { +namespace { + +constexpr const char * kFamily = "sopro_tts"; +constexpr size_t kWeightContextBytes = 512ull * 1024ull * 1024ull; +constexpr size_t kGraphArenaBytes = 1024ull * 1024ull * 1024ull; +// SoproTTS.DECODE_CONTEXT_FRAMES: mel frames of prompt fed to the vocoder so +// its convolutions start warm, then dropped from the output. +constexpr int64_t kDecodeContextFrames = 32; + +std::shared_ptr require_assets(std::shared_ptr assets) { + if (assets == nullptr) { + throw std::runtime_error("Sopro session requires assets"); + } + return assets; +} + +std::shared_ptr require_contract( + std::shared_ptr contract) { + if (contract == nullptr) { + throw std::runtime_error("Sopro session requires a model contract"); + } + return contract; +} + +const runtime::AudioBuffer * reference_audio(const runtime::TaskRequest & request) { + if (request.voice.has_value() && request.voice->speaker.has_value() && + request.voice->speaker->audio.has_value()) { + return &*request.voice->speaker->audio; + } + return request.audio_input.has_value() ? &*request.audio_input : nullptr; +} + +std::vector to_mono_24k(const runtime::AudioBuffer & audio, int target_rate) { + if (audio.samples.empty()) { + throw std::runtime_error("Sopro reference audio is empty"); + } + const int channels = std::max(1, audio.channels); + std::vector mono = channels == 1 + ? audio.samples + : engine::audio::mixdown_interleaved_to_mono_average(audio.samples, channels); + if (audio.sample_rate > 0 && audio.sample_rate != target_rate) { + mono = engine::audio::resample_mono_torchaudio_sinc_hann(mono, audio.sample_rate, target_rate); + } + // sopro.audio.to_mono_resampled clamps before anything else touches it. + for (auto & value : mono) { + value = std::min(1.0F, std::max(-1.0F, value)); + } + return mono; +} + +std::unique_ptr create_session( + const runtime::TaskSpec & task, + const runtime::SessionOptions & options, + std::shared_ptr assets, + std::shared_ptr contract) { + return std::make_unique(task, options, std::move(assets), std::move(contract)); +} + +} // namespace + +SoproTTSSession::SoproTTSSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract) + : RuntimeSessionBase(options), + task_(task), + assets_(require_assets(std::move(assets))), + contract_(require_contract(std::move(contract))) { + runtime::validate_spec_backed_session_options(options, *contract_, kFamily, "Sopro"); + if (const auto value = runtime::find_option(options.options, {"language"})) { + default_language_ = *value; + } + const auto matmul_storage = runtime::parse_tensor_storage_option( + options.options, + "matmul_weight_type", + assets::TensorStorageType::F32, + {assets::TensorStorageType::Native, + assets::TensorStorageType::F32, + assets::TensorStorageType::F16, + assets::TensorStorageType::BF16, + assets::TensorStorageType::Q8_0}); + const auto conv_storage = runtime::parse_tensor_storage_option( + options.options, + "conv_weight_type", + assets::TensorStorageType::F32, + {assets::TensorStorageType::Native, + assets::TensorStorageType::F32, + assets::TensorStorageType::F16}); + + core::ExecutionContext & execution = execution_context(); + tokenizer_ = std::make_unique( + assets_->tokenizer_path, assets_->config.model.max_text_len); + speaker_encoder_ = std::make_unique( + *assets_, execution, kWeightContextBytes, kGraphArenaBytes, matmul_storage, conv_storage); + semantic_encoder_ = std::make_unique( + *assets_, execution, kWeightContextBytes, kGraphArenaBytes, matmul_storage, conv_storage); + vocoder_ = std::make_unique( + *assets_, execution, kWeightContextBytes, kGraphArenaBytes, matmul_storage, conv_storage); + semantic_lm_ = std::make_unique( + *assets_, execution, kGraphArenaBytes, kGraphArenaBytes, kWeightContextBytes, matmul_storage); + acoustic_ = std::make_unique( + *assets_, execution, kWeightContextBytes, kGraphArenaBytes, matmul_storage, conv_storage); + reference_builder_ = std::make_unique( + *assets_, *speaker_encoder_, *semantic_encoder_, *vocoder_); +} + +SoproTTSSession::~SoproTTSSession() = default; + +std::string SoproTTSSession::family() const { + return kFamily; +} + +runtime::VoiceTaskKind SoproTTSSession::task_kind() const { + return runtime::VoiceTaskKind::Tts; +} + +runtime::RunMode SoproTTSSession::run_mode() const { + return task_.mode; +} + +void SoproTTSSession::prepare(const runtime::SessionPreparationRequest & request) { + runtime::validate_spec_backed_request_options(request.options, *contract_, "Sopro"); + mark_prepared(); +} + +SoproRequestOptions SoproTTSSession::parse_options(const runtime::TaskRequest & request) const { + const auto & defaults = assets_->config.generation; + SoproRequestOptions out; + out.language = default_language_; + out.temperature = defaults.temperature; + out.top_p = defaults.top_p; + out.top_k = defaults.top_k; + out.steps = defaults.steps; + out.max_seconds = defaults.max_seconds; + out.min_seconds = defaults.min_seconds; + out.max_segment_chars = defaults.max_segment_chars; + out.ref_seconds = defaults.ref_seconds; + + if (const auto value = runtime::find_option(request.options, {"language"})) { + out.language = *value; + } + if (const auto value = runtime::parse_finite_float_option(request.options, {"temperature"})) { + out.temperature = *value; + } + if (const auto value = runtime::parse_finite_float_option(request.options, {"top_p"})) { + out.top_p = *value; + } + if (const auto value = runtime::parse_i64_option(request.options, {"top_k"})) { + out.top_k = *value; + } + if (const auto value = runtime::parse_i64_option(request.options, {"num_inference_steps"})) { + out.steps = *value; + } + if (const auto value = runtime::parse_finite_float_option(request.options, {"max_seconds"})) { + out.max_seconds = *value; + } + if (const auto value = runtime::parse_finite_float_option(request.options, {"min_seconds"})) { + out.min_seconds = *value; + } + if (const auto value = runtime::parse_i64_option(request.options, {"text_chunk_size"})) { + out.max_segment_chars = *value; + } + if (const auto value = runtime::parse_finite_float_option(request.options, {"ref_seconds"})) { + out.ref_seconds = *value; + } + if (const auto value = runtime::parse_u64_option(request.options, {"seed"})) { + out.seed = *value; + out.has_seed = true; + } + if (!out.has_seed) { + out.seed = runtime::random_u64_seed(); + } + if (out.steps < 1) { + throw std::runtime_error("Sopro num_inference_steps must be positive"); + } + if (out.max_segment_chars < 1) { + throw std::runtime_error("Sopro text_chunk_size must be positive"); + } + if (out.max_seconds <= 0.0F) { + throw std::runtime_error("Sopro max_seconds must be positive"); + } + if (out.ref_seconds <= 0.0F) { + throw std::runtime_error("Sopro ref_seconds must be positive"); + } + if (out.min_seconds < 0.0F) { + throw std::runtime_error("Sopro min_seconds must not be negative"); + } + // A min above max leaves min_steps > max_steps, which never lets the LM + // emit EOS: every segment would run the full budget and be cut mid-word. + if (out.min_seconds > out.max_seconds) { + throw std::runtime_error("Sopro min_seconds must not exceed max_seconds"); + } + // language_tag() rejects anything outside the four supported languages, so + // fail before any weights are touched. + (void) language_tag(out.language); + return out; +} + +// The per-run state that every text segment of one synthesis shares. Offline +// drains it in a loop; streaming keeps it alive between next_stream_event +// calls, so both paths draw from the seeded RNG in the same order and a given +// seed produces the same audio either way. +struct SoproSynthesisState { + SoproRequestOptions options; + SoproReference voice; + SoproSemanticLMOptions lm_options; + std::vector style_tokens; + std::vector carry; + std::vector segments; + std::mt19937_64 rng; + size_t index = 0; // next segment to synthesize + size_t emitted = 0; // segments that produced audio so far + int sample_rate = 0; + float gain = 0.0F; + bool gain_ready = false; +}; + +std::unique_ptr SoproTTSSession::begin_synthesis( + const runtime::TaskRequest & request) { + runtime::validate_spec_backed_request_options(request.options, *contract_, "Sopro"); + if (!request.text_input.has_value() || request.text_input->text.empty()) { + throw std::runtime_error("Sopro requires non-empty text input"); + } + const runtime::AudioBuffer * reference = reference_audio(request); + if (reference == nullptr || reference->samples.empty()) { + throw std::runtime_error( + "Sopro requires reference voice audio (voice preset or voice_ref) for zero-shot cloning"); + } + + auto state = std::make_unique(); + state->options = parse_options(request); + const auto & config = assets_->config; + state->sample_rate = static_cast(config.sample_rate); + state->rng.seed(state->options.seed); + + const auto reference_audio24 = to_mono_24k(*reference, state->sample_rate); + const auto reference_start = std::chrono::steady_clock::now(); + state->voice = reference_builder_->build( + reference_audio24, state->options.ref_seconds, state->rng); + engine::debug::timing_log_scalar( + "sopro_tts.reference.prepare_ms", + engine::debug::elapsed_ms(reference_start, std::chrono::steady_clock::now())); + if (state->voice.semantic_tokens.empty() || state->voice.mel_frames <= 0) { + throw std::runtime_error("Sopro reference audio produced no semantic tokens"); + } + + // _steps(): one semantic token per token_samples output samples. + const int64_t token_samples = config.semantic_encoder.token_samples_24k; + const auto steps_for = [&](float seconds) { + return std::max( + 1, + static_cast(std::ceil( + static_cast(seconds) * static_cast(state->sample_rate) / + static_cast(token_samples)))); + }; + + const auto style_count = std::max( + 0, + std::min( + config.generation.style_tokens, + static_cast(state->voice.semantic_tokens.size()))); + state->style_tokens.assign( + state->voice.semantic_tokens.begin(), + state->voice.semantic_tokens.begin() + static_cast(style_count)); + if (config.generation.prompt_tokens > 0) { + const auto count = std::min( + config.generation.prompt_tokens, + static_cast(state->voice.semantic_tokens.size())); + // Continue from the *end* of the reference. synthesize_segment places + // this segment's tokens after the whole reference, and every later + // segment carries the tail of its predecessor, so anchoring the first + // one at the head would continue from the wrong point in the clip. + state->carry.assign( + state->voice.semantic_tokens.end() - static_cast(count), + state->voice.semantic_tokens.end()); + } + + state->lm_options.max_steps = steps_for(state->options.max_seconds); + state->lm_options.min_steps = steps_for(state->options.min_seconds); + state->lm_options.temperature = state->options.temperature; + state->lm_options.top_p = state->options.top_p; + state->lm_options.top_k = state->options.top_k; + + state->segments = split_text(request.text_input->text, state->options.max_segment_chars); + engine::debug::trace_log_scalar( + "sopro_tts.text.segments", static_cast(state->segments.size())); + return state; +} + +std::vector SoproTTSSession::synthesize_segment(SoproSynthesisState & state) { + if (state.index >= state.segments.size()) { + return {}; + } + const std::string & segment = state.segments[state.index++]; + const auto & config = assets_->config; + const int64_t token_samples = config.semantic_encoder.token_samples_24k; + const int64_t hop_ratio = config.hop_ratio(); + const int64_t n_mels = config.model.acoustic_mel_n_mels; + const int64_t vocoder_hop = vocoder_->hop_length(); + const auto prompt_budget = config.generation.prompt_tokens; + + const auto text_ids = tokenizer_->encode(segment, state.options.language); + const auto lm_start = std::chrono::steady_clock::now(); + const auto tokens = semantic_lm_->generate( + text_ids, state.style_tokens, state.carry, state.lm_options, state.rng); + engine::debug::timing_log_scalar( + "sopro_tts.semantic_lm.generate_ms", + engine::debug::elapsed_ms(lm_start, std::chrono::steady_clock::now())); + engine::debug::trace_log_scalar( + "sopro_tts.semantic_lm.tokens", static_cast(tokens.size())); + if (tokens.empty()) { + return {}; + } + if (prompt_budget > 0) { + const auto count = std::min(prompt_budget, static_cast(tokens.size())); + state.carry.assign(tokens.end() - static_cast(count), tokens.end()); + } + + SoproAcousticRequest acoustic; + acoustic.semantic_tokens = state.voice.semantic_tokens; + acoustic.semantic_tokens.insert(acoustic.semantic_tokens.end(), tokens.begin(), tokens.end()); + acoustic.cond_vec = state.voice.cond_vec; + acoustic.prompt_mel = state.voice.mel; + acoustic.prompt_frames = state.voice.mel_frames; + acoustic.total_frames = + state.voice.mel_frames + static_cast(tokens.size()) * hop_ratio; + acoustic.steps = state.options.steps; + acoustic.seed = state.rng(); + const auto acoustic_start = std::chrono::steady_clock::now(); + const auto mel = acoustic_->solve(acoustic); + engine::debug::timing_log_scalar( + "sopro_tts.acoustic.solve_ms", + engine::debug::elapsed_ms(acoustic_start, std::chrono::steady_clock::now())); + + // Denormalise and hand the vocoder a short prompt run-up so its + // convolution state matches the reference, then drop that run-up. + const int64_t context = std::min(kDecodeContextFrames, state.voice.mel_frames); + const int64_t begin = state.voice.mel_frames - context; + const int64_t decode_frames = acoustic.total_frames - begin; + std::vector decode_mel(static_cast(n_mels * decode_frames), 0.0F); + for (int64_t c = 0; c < n_mels; ++c) { + const float mean = config.model.acoustic_mel_mean[static_cast(c)]; + const float scale = config.model.acoustic_mel_std[static_cast(c)]; + const float * source = mel.data() + static_cast(c * acoustic.total_frames + begin); + float * target = decode_mel.data() + static_cast(c * decode_frames); + for (int64_t t = 0; t < decode_frames; ++t) { + target[t] = source[t] * scale + mean; + } + } + auto wav = vocoder_->decode(decode_mel, decode_frames); + const int64_t skip = context * vocoder_hop; + const int64_t target_length = static_cast(tokens.size()) * token_samples; + if (static_cast(wav.size()) <= skip) { + return {}; + } + const int64_t end = std::min(static_cast(wav.size()), skip + target_length); + return std::vector( + wav.begin() + static_cast(skip), wav.begin() + static_cast(end)); +} + +runtime::TaskResult SoproTTSSession::run(const runtime::TaskRequest & request) { + require_prepared("Sopro run"); + if (task_.mode != runtime::RunMode::Offline) { + throw std::runtime_error("Sopro run requires an offline session"); + } + auto state = begin_synthesis(request); + + std::vector> parts; + while (state->index < state->segments.size()) { + auto part = synthesize_segment(*state); + if (!part.empty()) { + parts.push_back(std::move(part)); + } + } + + const int sample_rate = state->sample_rate; + runtime::TaskResult result; + runtime::AudioBuffer audio; + audio.sample_rate = sample_rate; + audio.channels = 1; + if (parts.empty()) { + audio.samples.assign( + static_cast(assets_->config.semantic_encoder.token_samples_24k), 0.0F); + result.audio_output = std::move(audio); + return result; + } + + // Level-match once over the whole utterance, then trim and cross-fade the + // segment joins (SoproTTS.synthesize). + std::vector concatenated; + for (const auto & part : parts) { + concatenated.insert(concatenated.end(), part.begin(), part.end()); + } + const float gain = audio_ops::match_gain( + concatenated, sample_rate, audio_ops::kOutputLevelDb, state->voice.level_db); + std::vector> trimmed; + trimmed.reserve(parts.size()); + for (size_t index = 0; index < parts.size(); ++index) { + auto part = parts[index]; + for (auto & value : part) { + value *= gain; + } + part = index == 0 + ? audio_ops::trim_lead(part, sample_rate) + : audio_ops::trim_lead( + part, sample_rate, audio_ops::kSegmentLeadSeconds, audio_ops::kSegmentSkipSeconds); + trimmed.push_back(audio_ops::trim_trail(part, sample_rate)); + } + auto out = audio_ops::join_segments(std::move(trimmed), sample_rate); + audio_ops::soft_limit(out); + audio_ops::fade_edges(out, sample_rate, false, true, audio_ops::kFinalFadeSeconds); + audio.samples = std::move(out); + result.audio_output = std::move(audio); + return result; +} + +// --------------------------------------------------------------------------- // +// Streaming interface +// --------------------------------------------------------------------------- // +runtime::StreamingPolicy SoproTTSSession::streaming_policy() const { + // The acoustic head and the vocoder both look at a whole span at once, and + // this checkpoint ships no causal vocoder, so one text segment is the + // smallest unit that can leave without boundary artefacts. text_chunk_size + // is what trades first-audio latency against segment length. + runtime::StreamingPolicy policy; + policy.input = runtime::StreamingInputKind::None; + policy.output = runtime::StreamingOutputKind::PullEvents; + return policy; +} + +void SoproTTSSession::start_stream(const runtime::TaskRequest & request) { + require_prepared("Sopro start_stream"); + if (task_.mode != runtime::RunMode::Streaming) { + throw std::runtime_error("Sopro start_stream requires a streaming session"); + } + reset(); + // The reference voice is encoded once here rather than per event, so every + // event after the first costs only its own LM, solver and vocoder passes. + stream_state_ = begin_synthesis(request); + if (stream_state_->segments.empty()) { + throw std::runtime_error("Sopro streaming text chunking produced no segments"); + } +} + +std::optional SoproTTSSession::next_stream_event() { + if (stream_state_ == nullptr) { + throw std::runtime_error("Sopro streaming has not been started"); + } + SoproSynthesisState & state = *stream_state_; + const auto event_start = std::chrono::steady_clock::now(); + std::vector part; + while (part.empty() && state.index < state.segments.size()) { + part = synthesize_segment(state); + } + if (part.empty()) { + return std::nullopt; + } + engine::debug::timing_log_scalar( + "sopro_tts.streaming.event.synthesize_ms", + engine::debug::elapsed_ms(event_start, std::chrono::steady_clock::now())); + + // Offline levels the whole utterance at once. A stream cannot see the + // segments it has not generated yet, so the first one fixes the gain for + // all of them; that keeps their relative loudness instead of pushing every + // segment onto the target level on its own. + const int sample_rate = state.sample_rate; + if (!state.gain_ready) { + state.gain = audio_ops::match_gain( + part, sample_rate, audio_ops::kOutputLevelDb, state.voice.level_db); + state.gain_ready = true; + engine::debug::trace_log_scalar( + "sopro_tts.streaming.gain", static_cast(state.gain)); + } + for (auto & value : part) { + value *= state.gain; + } + part = state.emitted == 0 + ? audio_ops::trim_lead(part, sample_rate) + : audio_ops::trim_lead( + part, sample_rate, audio_ops::kSegmentLeadSeconds, audio_ops::kSegmentSkipSeconds); + part = audio_ops::trim_trail(part, sample_rate); + // Same order as the offline tail: join fade, limiter, then the final fade + // on whichever segment turns out to be the last one. + const bool has_more = state.index < state.segments.size(); + audio_ops::fade_edges(part, sample_rate, state.emitted > 0, has_more); + audio_ops::soft_limit(part); + if (!has_more) { + audio_ops::fade_edges(part, sample_rate, false, true, audio_ops::kFinalFadeSeconds); + } + + runtime::AudioBuffer audio; + audio.sample_rate = sample_rate; + audio.channels = 1; + audio.samples = std::move(part); + const size_t chunk_index = state.emitted++; + stream_chunks_.push_back(audio); + + runtime::StreamEvent event; + event.named_audio_outputs.push_back({ + "segment_" + std::to_string(chunk_index), + std::move(audio), + {}, + }); + return event; +} + +void SoproTTSSession::set_stream_event_sink(runtime::StreamEventCallback sink) { + // Every driver of a PullEvents session (app/streaming/streaming.cpp, and the + // server through it) forwards whatever next_stream_event returns to its own + // sink, so pushing here as well would deliver each segment twice. + (void) sink; +} + +runtime::TaskResult SoproTTSSession::finish_stream() { + if (stream_state_ == nullptr) { + throw std::runtime_error("Sopro streaming has not been started"); + } + // Each event is already levelled, trimmed and faded, so the utterance is a + // plain concatenation of what the consumer has already heard. + runtime::AudioBuffer merged; + merged.sample_rate = stream_state_->sample_rate; + merged.channels = 1; + if (stream_chunks_.empty()) { + merged.samples.assign( + static_cast(assets_->config.semantic_encoder.token_samples_24k), 0.0F); + } + for (const auto & chunk : stream_chunks_) { + runtime::append_audio_buffer(merged, chunk); + } + runtime::TaskResult result; + result.audio_output = std::move(merged); + reset(); + return result; +} + +void SoproTTSSession::reset() { + stream_state_.reset(); + stream_chunks_.clear(); +} + +runtime::StreamEvent SoproTTSSession::process_audio_chunk(const runtime::AudioChunk & chunk) { + (void) chunk; + throw std::runtime_error("Sopro is a TTS model and does not accept streamed audio input"); +} + +runtime::TaskResult SoproTTSSession::finalize() { + return runtime::TaskResult{}; +} + +std::shared_ptr make_sopro_tts_loader() { + runtime::SpecBackedVoiceModelConfig config; + config.family = kFamily; + // The upstream repo and the model card both call the family "sopro"; keep + // the short spelling working as a --family hint. + config.aliases = {"sopro", "sopro_v2", "sopro_v2_turbo"}; + config.load_assets = load_sopro_tts_assets; + config.create_session = create_session; + return runtime::make_spec_backed_voice_loader(std::move(config)); +} + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/sopro_tts/speaker_encoder.cpp b/src/community_models/sopro_tts/speaker_encoder.cpp new file mode 100644 index 000000000..9c79583b6 --- /dev/null +++ b/src/community_models/sopro_tts/speaker_encoder.cpp @@ -0,0 +1,637 @@ +#include "engine/community_models/sopro_tts/speaker_encoder.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/audio/dsp.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/streaming_conv_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { + +// A two-layer MLP with SiLU between, i.e. nn.Sequential(Linear, SiLU, +// Identity, Linear) -> keys ".0" and ".3". +struct SoproSpeakerHeadWeights { + std::vector fc1_weight; // [hidden, in] + std::vector fc1_bias; + std::vector fc2_weight; // [out, hidden] + std::vector fc2_bias; + int64_t in_features = 0; + int64_t hidden = 0; + int64_t out_features = 0; +}; + +struct SoproSpeakerResBlockWeights { + engine::modules::NormWeights norm1; + engine::modules::Conv1dWeights pw_in; + engine::modules::DepthwiseConv1dWeights dw; + engine::modules::NormWeights norm2; + engine::modules::Conv1dWeights se_reduce; + engine::modules::Conv1dWeights se_expand; + engine::modules::Conv1dWeights pw_out; + int64_t channels = 0; + int64_t se_hidden = 0; + int dilation = 1; +}; + +struct SoproSpeakerStageWeights { + engine::modules::Conv1dWeights transition_conv; + engine::modules::NormWeights transition_norm; + int transition_stride = 1; + int64_t out_channels = 0; + std::vector blocks; +}; + +struct SoproSpeakerWeights { + std::shared_ptr store; + engine::modules::Conv1dWeights stem_conv; + engine::modules::NormWeights stem_norm; + std::vector stages; + engine::modules::Conv1dWeights fuse_conv; + engine::modules::NormWeights fuse_norm; + // Host-side pooling heads. + std::vector attn_conv1_weight; // [attn_hidden, channels] + std::vector attn_conv1_bias; + std::vector attn_conv2_weight; // [1, attn_hidden] + std::vector attn_conv2_bias; + SoproSpeakerHeadWeights id_head; + SoproSpeakerHeadWeights style_head; + SoproSpeakerHeadWeights style_ctrl_head; + // torchaudio MelSpectrogram buffers. + std::vector analysis_window; // win_length taps + std::vector mel_filterbank; // [freq_bins, n_mels] +}; + +namespace { + +namespace binding = engine::modules::binding; + +constexpr float kGroupNormEps = 1.0e-5F; // torch.nn.GroupNorm default +constexpr float kLayerNormEps = 1.0e-5F; // torch.nn.functional.layer_norm default + +// SOPRO_DUMP_DIR: raw f32 dumps for stage comparison against the reference. +void dump(const std::string & name, const std::vector & values) { + const char * dir = std::getenv("SOPRO_DUMP_DIR"); + if (dir == nullptr) { + return; + } + std::FILE * fh = std::fopen((std::string(dir) + "/" + name).c_str(), "wb"); + if (fh != nullptr) { + std::fwrite(values.data(), sizeof(float), values.size(), fh); + std::fclose(fh); + } +} + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +engine::modules::NormWeights group_norm( + engine::core::BackendWeightStore & store, + const engine::assets::TensorSource & source, + const std::string & prefix, + int64_t channels) { + return binding::norm_from_source(store, source, prefix, channels); +} + +SoproSpeakerHeadWeights load_head( + const engine::assets::TensorSource & source, + const std::string & prefix, + int64_t in_features, + int64_t hidden, + int64_t out_features) { + SoproSpeakerHeadWeights out; + out.in_features = in_features; + out.hidden = hidden; + out.out_features = out_features; + out.fc1_weight = source.require_f32(prefix + ".0.weight", {hidden, in_features}); + out.fc1_bias = source.require_f32(prefix + ".0.bias", {hidden}); + out.fc2_weight = source.require_f32(prefix + ".3.weight", {out_features, hidden}); + out.fc2_bias = source.require_f32(prefix + ".3.bias", {out_features}); + return out; +} + +std::vector apply_head(const SoproSpeakerHeadWeights & head, const std::vector & input) { + if (static_cast(input.size()) != head.in_features) { + throw std::runtime_error("Sopro speaker head input size mismatch"); + } + std::vector hidden(static_cast(head.hidden), 0.0F); + for (int64_t h = 0; h < head.hidden; ++h) { + double sum = head.fc1_bias[static_cast(h)]; + const float * row = head.fc1_weight.data() + static_cast(h * head.in_features); + for (int64_t i = 0; i < head.in_features; ++i) { + sum += static_cast(row[i]) * static_cast(input[static_cast(i)]); + } + const auto value = static_cast(sum); + hidden[static_cast(h)] = value / (1.0F + std::exp(-value)); // SiLU + } + std::vector out(static_cast(head.out_features), 0.0F); + for (int64_t o = 0; o < head.out_features; ++o) { + double sum = head.fc2_bias[static_cast(o)]; + const float * row = head.fc2_weight.data() + static_cast(o * head.hidden); + for (int64_t h = 0; h < head.hidden; ++h) { + sum += static_cast(row[h]) * static_cast(hidden[static_cast(h)]); + } + out[static_cast(o)] = static_cast(sum); + } + return out; +} + +std::shared_ptr load_speaker_weights( + ggml_backend_t backend, + engine::core::BackendType backend_type, + const engine::assets::TensorSource & source, + const SoproSpeakerEncoderConfig & config, + size_t weight_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type) { + (void) matmul_storage_type; + auto weights = std::make_shared(); + require_frontend_buffers( + source, "speaker encoder", + {"frontend.mel.spectrogram.window", + "frontend.mel.mel_scale.fb"}); + weights->store = std::make_shared( + backend, backend_type, "sopro_tts.speaker_encoder.weights", weight_context_bytes); + auto & store = *weights->store; + + weights->stem_conv = binding::conv1d_from_source( + store, source, "stem.0.conv", conv_storage_type, + config.stem_channels, config.n_mels, 5, true); + weights->stem_norm = group_norm(store, source, "stem.1", config.stem_channels); + + int64_t in_channels = config.stem_channels; + int64_t fused_in = 0; + weights->stages.reserve(config.stage_channels.size()); + for (size_t stage = 0; stage < config.stage_channels.size(); ++stage) { + SoproSpeakerStageWeights out; + out.out_channels = config.stage_channels[stage]; + out.transition_stride = stage == 0 ? 2 : 1; + const std::string transition = "transitions." + std::to_string(stage); + out.transition_conv = binding::conv1d_from_source( + store, source, transition + ".conv", conv_storage_type, + out.out_channels, in_channels, 3, true); + out.transition_norm = group_norm(store, source, transition + ".norm", out.out_channels); + const int64_t blocks = config.blocks_per_stage[stage]; + out.blocks.reserve(static_cast(blocks)); + for (int64_t block = 0; block < blocks; ++block) { + const std::string prefix = + "stages." + std::to_string(stage) + "." + std::to_string(block); + SoproSpeakerResBlockWeights weights_block; + weights_block.channels = out.out_channels; + weights_block.dilation = static_cast( + config.dilation_cycle[static_cast(block) % config.dilation_cycle.size()]); + weights_block.se_hidden = std::max(8, out.out_channels / config.se_reduction); + weights_block.norm1 = group_norm(store, source, prefix + ".norm1", out.out_channels); + weights_block.pw_in = binding::conv1d_from_source( + store, source, prefix + ".pw_in", conv_storage_type, + out.out_channels * 2, out.out_channels, 1, true); + weights_block.dw = binding::depthwise_conv1d_from_source( + store, source, prefix + ".dw.conv", conv_storage_type, + out.out_channels, config.depthwise_kernel_size, true); + weights_block.norm2 = group_norm(store, source, prefix + ".norm2", out.out_channels); + weights_block.se_reduce = binding::conv1d_from_source( + store, source, prefix + ".se.net.1", conv_storage_type, + weights_block.se_hidden, out.out_channels, 1, true); + weights_block.se_expand = binding::conv1d_from_source( + store, source, prefix + ".se.net.3", conv_storage_type, + out.out_channels, weights_block.se_hidden, 1, true); + weights_block.pw_out = binding::conv1d_from_source( + store, source, prefix + ".pw_out", conv_storage_type, + out.out_channels, out.out_channels, 1, true); + out.blocks.push_back(std::move(weights_block)); + } + fused_in += out.out_channels; + in_channels = out.out_channels; + weights->stages.push_back(std::move(out)); + } + + const int64_t head_channels = config.stage_channels.back(); + weights->fuse_conv = binding::conv1d_from_source( + store, source, "fuse.0", conv_storage_type, head_channels, fused_in, 1, true); + weights->fuse_norm = group_norm(store, source, "fuse.1", head_channels); + + weights->attn_conv1_weight = source.require_f32( + "id_pool.attn.0.weight", {config.attn_hidden, head_channels, 1}); + weights->attn_conv1_bias = source.require_f32("id_pool.attn.0.bias", {config.attn_hidden}); + weights->attn_conv2_weight = source.require_f32( + "id_pool.attn.2.weight", {1, config.attn_hidden, 1}); + weights->attn_conv2_bias = source.require_f32("id_pool.attn.2.bias", {1}); + weights->id_head = load_head( + source, "id_head", head_channels * 2, config.id_head_hidden, config.id_emb_dim); + weights->style_head = load_head( + source, "style_head", head_channels * 4, config.style_head_hidden, config.style_emb_dim); + weights->style_ctrl_head = load_head( + source, "style_ctrl_head", head_channels * 4, config.style_head_hidden, config.style_ctrl_dim); + + weights->analysis_window = source.require_f32( + "frontend.mel.spectrogram.window", {config.win_length}); + weights->mel_filterbank = source.require_f32( + "frontend.mel.mel_scale.fb", {config.n_fft / 2 + 1, config.n_mels}); + + store.upload(); + return weights; +} + +engine::core::TensorValue build_res_block( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & input, + const SoproSpeakerResBlockWeights & weights, + int64_t kernel_size, + int64_t frames) { + const int64_t channels = weights.channels; + auto hidden = engine::modules::GroupNormModule({channels, 1, kGroupNormEps, true, true}) + .build(ctx, input, weights.norm1); + hidden = engine::modules::Conv1dModule({channels, channels * 2, 1, 1, 0, 1, true}) + .build(ctx, hidden, weights.pw_in); + // chunk(2, dim=1): the gate multiplies the first half by sigmoid(second). + auto gate_a = engine::modules::SliceModule({1, 0, channels}).build(ctx, hidden); + auto gate_b = engine::modules::SliceModule({1, channels, channels}).build(ctx, hidden); + gate_b = engine::modules::SigmoidModule{}.build(ctx, gate_b); + hidden = engine::modules::MulModule{}.build(ctx, gate_a, gate_b); + hidden = engine::modules::DepthwiseConv1dModule({ + channels, kernel_size, 1, + static_cast(weights.dilation * (kernel_size - 1) / 2), weights.dilation, true, + }).build(ctx, hidden, weights.dw); + hidden = engine::modules::GroupNormModule({channels, 1, kGroupNormEps, true, true}) + .build(ctx, hidden, weights.norm2); + hidden = engine::modules::SiluModule{}.build(ctx, hidden); + // SqueezeExcite1d: global average pool -> 1x1 bottleneck -> sigmoid gate. + auto pooled = engine::modules::ReduceMeanModule({2}).build(ctx, hidden); + pooled = engine::modules::Conv1dModule({channels, weights.se_hidden, 1, 1, 0, 1, true}) + .build(ctx, pooled, weights.se_reduce); + pooled = engine::modules::SiluModule{}.build(ctx, pooled); + pooled = engine::modules::Conv1dModule({weights.se_hidden, channels, 1, 1, 0, 1, true}) + .build(ctx, pooled, weights.se_expand); + pooled = engine::modules::SigmoidModule{}.build(ctx, pooled); + auto scale = engine::modules::RepeatModule({ + engine::core::TensorShape::from_dims({1, channels, frames})}).build(ctx, pooled); + hidden = engine::modules::MulModule{}.build(ctx, hidden, scale); + hidden = engine::modules::Conv1dModule({channels, channels, 1, 1, 0, 1, true}) + .build(ctx, hidden, weights.pw_out); + return engine::modules::AddModule{}.build(ctx, input, hidden); +} + +} // namespace + +struct SoproSpeakerGraph { + SoproSpeakerGraph( + ggml_backend_t backend_in, + engine::core::BackendType backend_type, + size_t graph_context_bytes, + const SoproSpeakerEncoderConfig & config, + std::shared_ptr weights_in, + int64_t frames_in) + : backend(backend_in), + weights(std::move(weights_in)), + frames(frames_in), + mel_bins(config.n_mels) { + if (backend == nullptr || weights == nullptr) { + throw std::runtime_error("Sopro speaker encoder graph requires a backend and weights"); + } + if (frames <= 0) { + throw std::runtime_error("Sopro speaker encoder graph requires a positive frame count"); + } + ggml_init_params params{graph_context_bytes, nullptr, true}; + ctx.reset(ggml_init(params)); + if (ctx == nullptr) { + throw std::runtime_error("failed to initialize the Sopro speaker encoder graph context"); + } + engine::core::ModuleBuildContext build_ctx{ctx.get(), "sopro_tts.speaker_encoder", backend_type}; + const auto shape = engine::core::TensorShape::from_dims({1, mel_bins, frames}); + input = engine::core::make_tensor(build_ctx, GGML_TYPE_F32, shape).tensor; + ggml_set_input(input); + + auto hidden = engine::modules::Conv1dModule({ + mel_bins, config.stem_channels, 5, 1, 2, 1, true, + }).build(build_ctx, engine::core::wrap_tensor(input, shape, GGML_TYPE_F32), weights->stem_conv); + hidden = engine::modules::GroupNormModule({config.stem_channels, 1, kGroupNormEps, true, true}) + .build(build_ctx, hidden, weights->stem_norm); + hidden = engine::modules::SiluModule{}.build(build_ctx, hidden); + + int64_t in_channels = config.stem_channels; + int64_t stage_frames = frames; + std::vector stage_outputs; + for (const auto & stage : weights->stages) { + if (stage.transition_stride == 2) { + // F.pad(x, (1, 1)) then Conv1d(kernel 3, stride 2). + stage_frames = (stage_frames + 2 - 3) / 2 + 1; + } + hidden = engine::modules::Conv1dModule({ + in_channels, stage.out_channels, 3, stage.transition_stride, 1, 1, true, + }).build(build_ctx, hidden, stage.transition_conv); + hidden = engine::modules::GroupNormModule({stage.out_channels, 1, kGroupNormEps, true, true}) + .build(build_ctx, hidden, stage.transition_norm); + hidden = engine::modules::SiluModule{}.build(build_ctx, hidden); + for (const auto & block : stage.blocks) { + hidden = build_res_block( + build_ctx, hidden, block, config.depthwise_kernel_size, stage_frames); + } + stage_outputs.push_back(hidden); + in_channels = stage.out_channels; + } + trunk_frames = stage_frames; + + auto fused = stage_outputs.front(); + for (size_t i = 1; i < stage_outputs.size(); ++i) { + fused = engine::modules::ConcatModule({1}).build(build_ctx, fused, stage_outputs[i]); + } + int64_t fused_in = 0; + for (const auto & stage : weights->stages) { + fused_in += stage.out_channels; + } + channels = config.stage_channels.back(); + fused = engine::modules::Conv1dModule({fused_in, channels, 1, 1, 0, 1, true}) + .build(build_ctx, fused, weights->fuse_conv); + fused = engine::modules::GroupNormModule({channels, 1, kGroupNormEps, true, true}) + .build(build_ctx, fused, weights->fuse_norm); + fused = engine::modules::SiluModule{}.build(build_ctx, fused); + fused = engine::core::ensure_backend_addressable_layout(build_ctx, fused); + output = fused.tensor; + ggml_set_output(output); + graph = ggml_new_graph_custom(ctx.get(), 65536, false); + ggml_build_forward_expand(graph, output); + gallocr = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend)); + if (gallocr == nullptr || !ggml_gallocr_reserve(gallocr, graph) || + !ggml_gallocr_alloc_graph(gallocr, graph)) { + throw std::runtime_error("failed to allocate the Sopro speaker encoder graph"); + } + } + + ~SoproSpeakerGraph() { + if (gallocr != nullptr) { + ggml_gallocr_free(gallocr); + gallocr = nullptr; + } + } + + bool matches(const SoproSpeakerWeights & other, int64_t other_frames) const noexcept { + return weights.get() == &other && frames == other_frames; + } + + std::vector run(const std::vector & log_mel) { + ggml_backend_tensor_set(input, log_mel.data(), 0, log_mel.size() * sizeof(float)); + const ggml_status status = engine::core::compute_backend_graph(backend, graph); + ggml_backend_synchronize(backend); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Sopro speaker encoder graph compute failed"); + } + std::vector out(static_cast(channels * trunk_frames), 0.0F); + ggml_backend_tensor_get(output, out.data(), 0, out.size() * sizeof(float)); + return out; + } + + ggml_backend_t backend = nullptr; + std::shared_ptr weights; + int64_t frames = 0; + int64_t mel_bins = 0; + int64_t trunk_frames = 0; + int64_t channels = 0; + std::unique_ptr ctx; + ggml_tensor * input = nullptr; + ggml_tensor * output = nullptr; + ggml_cgraph * graph = nullptr; + ggml_gallocr_t gallocr = nullptr; +}; + +SoproSpeakerEncoderRuntime::SoproSpeakerEncoderRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t weight_context_bytes, + size_t graph_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type) + : config_(assets.config.speaker_encoder), + execution_context_(execution_context), + graph_context_bytes_(graph_context_bytes), + weights_(load_speaker_weights( + execution_context.backend(), + execution_context.backend_type(), + *assets.speaker_encoder_weights, + assets.config.speaker_encoder, + weight_context_bytes, + matmul_storage_type, + conv_storage_type)) {} + +SoproSpeakerEncoderRuntime::~SoproSpeakerEncoderRuntime() = default; + +int64_t SoproSpeakerEncoderRuntime::sample_rate() const noexcept { + return config_.sample_rate; +} + +std::vector SoproSpeakerEncoderRuntime::trunk( + const std::vector & log_mel, + int64_t frames, + int64_t & out_frames) const { + if (graph_ == nullptr || !graph_->matches(*weights_, frames)) { + // Free the previous arena first; otherwise both are resident while the + // replacement is allocated, and every segment rebuilds this graph. + graph_.reset(); + graph_ = std::make_unique( + execution_context_.backend(), + execution_context_.backend_type(), + graph_context_bytes_, + config_, + weights_, + frames); + } + out_frames = graph_->trunk_frames; + return graph_->run(log_mel); +} + +SoproSpeakerEmbeddings SoproSpeakerEncoderRuntime::encode(const std::vector & audio16) const { + if (audio16.empty()) { + throw std::runtime_error("Sopro speaker encoder requires a non-empty reference waveform"); + } + // LogMelFrontend: power-2 mel, log with a floor, then a per-frame LayerNorm + // across the mel axis (no affine parameters). + const int64_t freq_bins = config_.n_fft / 2 + 1; + engine::audio::STFTConfig stft; + stft.n_fft = config_.n_fft; + stft.hop_length = config_.hop_length; + stft.win_length = config_.win_length; + stft.center = true; + stft.pad_mode = engine::audio::STFTPadMode::Reflect; + const auto magnitude = engine::audio::STFT{}.compute_magnitude( + audio16, weights_->analysis_window, 1, static_cast(audio16.size()), stft, + static_cast(execution_context_.config().threads)); + if (magnitude.shape.size() != 3 || magnitude.shape[1] != freq_bins) { + throw std::runtime_error("Sopro speaker encoder STFT produced an unexpected layout"); + } + const int64_t frames = magnitude.shape[2]; + if (frames < 3) { + throw std::runtime_error("Sopro speaker encoder reference audio is too short"); + } + std::vector mel(static_cast(config_.n_mels * frames), 0.0F); + for (int64_t f = 0; f < freq_bins; ++f) { + const float * fb_row = weights_->mel_filterbank.data() + static_cast(f * config_.n_mels); + const float * spec_row = magnitude.values.data() + static_cast(f * frames); + for (int64_t m = 0; m < config_.n_mels; ++m) { + const float weight = fb_row[m]; + if (weight == 0.0F) { + continue; + } + float * out_row = mel.data() + static_cast(m * frames); + for (int64_t t = 0; t < frames; ++t) { + // power=2.0: the mel filters see squared magnitudes. + out_row[t] += weight * spec_row[t] * spec_row[t]; + } + } + } + for (auto & value : mel) { + value = std::log(std::max(value, config_.mel_log_floor)); + } + for (int64_t t = 0; t < frames; ++t) { + double sum = 0.0; + for (int64_t m = 0; m < config_.n_mels; ++m) { + sum += mel[static_cast(m * frames + t)]; + } + const double mean = sum / static_cast(config_.n_mels); + double variance = 0.0; + for (int64_t m = 0; m < config_.n_mels; ++m) { + const double centred = mel[static_cast(m * frames + t)] - mean; + variance += centred * centred; + } + variance /= static_cast(config_.n_mels); + const double inv_std = 1.0 / std::sqrt(variance + kLayerNormEps); + for (int64_t m = 0; m < config_.n_mels; ++m) { + auto & value = mel[static_cast(m * frames + t)]; + value = static_cast((value - mean) * inv_std); + } + } + + dump("spk_wav16.f32", audio16); + dump("spk_mel.f32", mel); + int64_t trunk_frames = 0; + const auto features = trunk(mel, frames, trunk_frames); + dump("spk_trunk.f32", features); + const int64_t channels = config_.stage_channels.back(); + if (trunk_frames <= 0) { + throw std::runtime_error("Sopro speaker encoder produced no trunk frames"); + } + + // AttentiveStatsPool: softmax attention over time, then weighted mean/std. + std::vector scores(static_cast(trunk_frames), 0.0F); + std::vector attn_hidden(static_cast(config_.attn_hidden), 0.0F); + for (int64_t t = 0; t < trunk_frames; ++t) { + for (int64_t h = 0; h < config_.attn_hidden; ++h) { + double sum = weights_->attn_conv1_bias[static_cast(h)]; + const float * row = weights_->attn_conv1_weight.data() + static_cast(h * channels); + for (int64_t c = 0; c < channels; ++c) { + sum += static_cast(row[c]) * + static_cast(features[static_cast(c * trunk_frames + t)]); + } + attn_hidden[static_cast(h)] = std::tanh(static_cast(sum)); + } + double sum = weights_->attn_conv2_bias[0]; + for (int64_t h = 0; h < config_.attn_hidden; ++h) { + sum += static_cast(weights_->attn_conv2_weight[static_cast(h)]) * + static_cast(attn_hidden[static_cast(h)]); + } + scores[static_cast(t)] = static_cast(sum); + } + const float max_score = *std::max_element(scores.begin(), scores.end()); + double score_sum = 0.0; + for (auto & score : scores) { + score = std::exp(score - max_score); + score_sum += score; + } + for (auto & score : scores) { + score = static_cast(score / score_sum); + } + + std::vector id_input(static_cast(channels * 2), 0.0F); + for (int64_t c = 0; c < channels; ++c) { + const float * row = features.data() + static_cast(c * trunk_frames); + double mean = 0.0; + for (int64_t t = 0; t < trunk_frames; ++t) { + mean += static_cast(scores[static_cast(t)]) * static_cast(row[t]); + } + double variance = 0.0; + for (int64_t t = 0; t < trunk_frames; ++t) { + const double centred = static_cast(row[t]) - mean; + variance += static_cast(scores[static_cast(t)]) * centred * centred; + } + id_input[static_cast(c)] = static_cast(mean); + id_input[static_cast(channels + c)] = + static_cast(std::sqrt(std::max(variance, 1.0e-6))); + } + + // MultiScaleStylePool: mean/std of the trunk features and of a length-5 + // moving average of them (AvgPool1d(kernel 5, stride 1, padding 2), which + // divides by the kernel size including the zero padding). + std::vector style_input(static_cast(channels * 4), 0.0F); + std::vector smoothed(static_cast(trunk_frames), 0.0F); + const auto denominator = static_cast(trunk_frames); + for (int64_t c = 0; c < channels; ++c) { + const float * row = features.data() + static_cast(c * trunk_frames); + for (int64_t t = 0; t < trunk_frames; ++t) { + double sum = 0.0; + for (int64_t k = -2; k <= 2; ++k) { + const int64_t index = t + k; + if (index >= 0 && index < trunk_frames) { + sum += static_cast(row[index]); + } + } + smoothed[static_cast(t)] = static_cast(sum / 5.0); + } + const float * scales[2] = {row, smoothed.data()}; + for (int scale = 0; scale < 2; ++scale) { + const float * values = scales[scale]; + double sum = 0.0; + for (int64_t t = 0; t < trunk_frames; ++t) { + sum += static_cast(values[t]); + } + const double mean = sum / denominator; + double variance = 0.0; + for (int64_t t = 0; t < trunk_frames; ++t) { + const double centred = static_cast(values[t]) - mean; + variance += centred * centred; + } + variance /= denominator; + style_input[static_cast(scale * 2 * channels + c)] = static_cast(mean); + style_input[static_cast((scale * 2 + 1) * channels + c)] = + static_cast(std::sqrt(std::max(variance, 1.0e-6))); + } + } + + SoproSpeakerEmbeddings out; + out.id_emb = apply_head(weights_->id_head, id_input); + double norm = 0.0; + for (const float value : out.id_emb) { + norm += static_cast(value) * static_cast(value); + } + const auto inv_norm = static_cast(1.0 / std::max(std::sqrt(norm), 1.0e-12)); + for (auto & value : out.id_emb) { + value *= inv_norm; + } + out.style_emb = apply_head(weights_->style_head, style_input); + out.style_ctrl = apply_head(weights_->style_ctrl_head, style_input); + dump("spk_id_emb.f32", out.id_emb); + dump("spk_style_emb.f32", out.style_emb); + dump("spk_style_ctrl.f32", out.style_ctrl); + return out; +} + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/sopro_tts/text_tokenizer.cpp b/src/community_models/sopro_tts/text_tokenizer.cpp new file mode 100644 index 000000000..af9b4c651 --- /dev/null +++ b/src/community_models/sopro_tts/text_tokenizer.cpp @@ -0,0 +1,433 @@ +#include "engine/community_models/sopro_tts/text_tokenizer.h" + +#include "sentencepiece_processor.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { +namespace { + +bool is_ascii_space(char c) noexcept { + return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; +} + +// Python str.split() on whitespace followed by " ".join(...). +std::string collapse_whitespace(const std::string & text) { + std::string out; + out.reserve(text.size()); + size_t index = 0; + bool first = true; + while (index < text.size()) { + while (index < text.size() && is_ascii_space(text[index])) { + ++index; + } + const size_t start = index; + while (index < text.size() && !is_ascii_space(text[index])) { + ++index; + } + if (index > start) { + if (!first) { + out.push_back(' '); + } + out.append(text, start, index - start); + first = false; + } + } + return out; +} + +std::string trim(const std::string & text) { + size_t begin = 0; + size_t end = text.size(); + while (begin < end && is_ascii_space(text[begin])) { + ++begin; + } + while (end > begin && is_ascii_space(text[end - 1])) { + --end; + } + return text.substr(begin, end - begin); +} + +size_t utf8_sequence_length(unsigned char lead) noexcept { + if (lead < 0x80U) { + return 1; + } + if ((lead & 0xE0U) == 0xC0U) { + return 2; + } + if ((lead & 0xF0U) == 0xE0U) { + return 3; + } + if ((lead & 0xF8U) == 0xF0U) { + return 4; + } + return 1; // lone continuation byte: treat as one unit so we never stall +} + +size_t codepoint_length(const std::string & text) noexcept { + size_t count = 0; + size_t index = 0; + while (index < text.size()) { + index += std::min(utf8_sequence_length(static_cast(text[index])), + text.size() - index); + ++count; + } + return count; +} + +uint32_t decode_utf8(const std::string & text, size_t offset, size_t & length) noexcept { + const auto lead = static_cast(text[offset]); + length = std::min(utf8_sequence_length(lead), text.size() - offset); + if (length == 1) { + return lead; + } + static constexpr std::array kLeadMask = {0, 0x7FU, 0x1FU, 0x0FU, 0x07U}; + uint32_t value = lead & kLeadMask[length]; + for (size_t i = 1; i < length; ++i) { + value = (value << 6U) | (static_cast(text[offset + i]) & 0x3FU); + } + return value; +} + +void append_utf8(std::string & out, uint32_t codepoint) { + if (codepoint < 0x80U) { + out.push_back(static_cast(codepoint)); + } else if (codepoint < 0x800U) { + out.push_back(static_cast(0xC0U | (codepoint >> 6U))); + out.push_back(static_cast(0x80U | (codepoint & 0x3FU))); + } else if (codepoint < 0x10000U) { + out.push_back(static_cast(0xE0U | (codepoint >> 12U))); + out.push_back(static_cast(0x80U | ((codepoint >> 6U) & 0x3FU))); + out.push_back(static_cast(0x80U | (codepoint & 0x3FU))); + } else { + out.push_back(static_cast(0xF0U | (codepoint >> 18U))); + out.push_back(static_cast(0x80U | ((codepoint >> 12U) & 0x3FU))); + out.push_back(static_cast(0x80U | ((codepoint >> 6U) & 0x3FU))); + out.push_back(static_cast(0x80U | (codepoint & 0x3FU))); + } +} + +// str.islower()/str.upper() for the Latin ranges the four supported languages +// use (ASCII, Latin-1 supplement, Latin Extended-A). Anything else is left as +// it is, which matches Python for scripts without case. +bool codepoint_is_lower(uint32_t cp) noexcept { + if (cp >= 'a' && cp <= 'z') { + return true; + } + if (cp == 0xDFU) { // sharp s has no single-codepoint uppercase + return false; + } + if (cp >= 0xE0U && cp <= 0xFEU && cp != 0xF7U) { + return true; + } + if (cp >= 0x100U && cp <= 0x17FU) { + return (cp % 2U) == 1U; // Latin Extended-A alternates upper/lower + } + return false; +} + +uint32_t codepoint_to_upper(uint32_t cp) noexcept { + if (cp >= 'a' && cp <= 'z') { + return cp - 32U; + } + if (cp >= 0xE0U && cp <= 0xFEU && cp != 0xF7U) { + return cp - 32U; + } + if (cp >= 0x100U && cp <= 0x17FU && (cp % 2U) == 1U) { + return cp - 1U; + } + return cp; +} + +std::string capitalize_first(const std::string & text) { + if (text.empty()) { + return text; + } + size_t length = 0; + const uint32_t cp = decode_utf8(text, 0, length); + if (!codepoint_is_lower(cp)) { + return text; + } + std::string out; + out.reserve(text.size() + 1); + append_utf8(out, codepoint_to_upper(cp)); + out.append(text, length, std::string::npos); + return out; +} + +void replace_all(std::string & text, const std::string & from, const std::string & to) { + if (from.empty()) { + return; + } + size_t position = 0; + while ((position = text.find(from, position)) != std::string::npos) { + text.replace(position, from.size(), to); + position += to.size(); + } +} + +// sopro.text._TERMINALS +bool ends_with_terminal(const std::string & text) noexcept { + if (text.empty()) { + return false; + } + const char last = text.back(); + return last == '.' || last == '!' || last == '?' || last == '-' || + last == ',' || last == ';' || last == ':'; +} + +// sopro.text._pack: greedily join parts while they fit the codepoint budget. +std::vector pack(const std::vector & parts, int64_t max_chars) { + std::vector out; + std::string current; + for (const auto & part : parts) { + if (current.empty()) { + current = part; + } else if (static_cast(codepoint_length(current) + 1 + codepoint_length(part)) <= + max_chars) { + current += " "; + current += part; + } else { + out.push_back(current); + current = part; + } + } + if (!current.empty()) { + out.push_back(current); + } + return out; +} + +// Split on runs of whitespace that follow one of `delimiters`, i.e. the +// Python lookbehind patterns (?<=[.!?…])\s+ and (?<=[,;:])\s+. +std::vector split_after( + const std::string & text, + const std::vector & delimiters) { + std::vector out; + size_t start = 0; + size_t index = 0; + while (index < text.size()) { + if (!is_ascii_space(text[index])) { + ++index; + continue; + } + bool preceded = false; + for (const auto & delimiter : delimiters) { + if (index >= delimiter.size() && + text.compare(index - delimiter.size(), delimiter.size(), delimiter) == 0) { + preceded = true; + break; + } + } + size_t run_end = index; + while (run_end < text.size() && is_ascii_space(text[run_end])) { + ++run_end; + } + if (preceded) { + out.push_back(text.substr(start, index - start)); + start = run_end; + } + index = run_end; + } + out.push_back(text.substr(start)); + return out; +} + +std::vector split_on_spaces(const std::string & text) { + std::vector out; + size_t start = 0; + for (size_t index = 0; index <= text.size(); ++index) { + if (index == text.size() || text[index] == ' ') { + out.push_back(text.substr(start, index - start)); + start = index + 1; + } + } + return out; +} + +// Matches ^((?:<\|[^|\s]+?\|>\s*)+)(.*)$ — a run of leading <|...|> markers. +size_t special_prefix_end(const std::string & text) noexcept { + size_t index = 0; + size_t last_match_end = 0; + while (index + 2 <= text.size() && text.compare(index, 2, "<|") == 0) { + const size_t close = text.find("|>", index + 2); + if (close == std::string::npos) { + break; + } + bool valid = close > index + 2; + for (size_t i = index + 2; i < close && valid; ++i) { + if (text[i] == '|' || is_ascii_space(text[i])) { + valid = false; + } + } + if (!valid) { + break; + } + index = close + 2; + last_match_end = index; + while (index < text.size() && is_ascii_space(text[index])) { + ++index; + } + } + return last_match_end == 0 ? 0 : index; +} + +} // namespace + +std::string language_tag(const std::string & language) { + if (language.empty()) { + return {}; + } + std::string key = trim(language); + std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + if (key.empty()) { + return {}; + } + if (key == "en" || key == "pt" || key == "fr" || key == "de") { + return "<|lang_" + key + "|>"; + } + throw std::runtime_error( + "Sopro: unsupported language '" + language + "'; expected one of de, en, fr, pt"); +} + +std::string normalize_text(const std::string & text) { + std::string body = trim(text); + if (body.empty()) { + return "You need to add some text for me to talk."; + } + const size_t prefix_end = special_prefix_end(body); + if (prefix_end > 0) { + const std::string prefix = collapse_whitespace(body.substr(0, prefix_end)); + const std::string rest = trim(body.substr(prefix_end)); + return rest.empty() ? prefix : prefix + " " + normalize_text(rest); + } + body = capitalize_first(body); + body = collapse_whitespace(body); + replace_all(body, "\xE2\x80\xA6", "..."); // U+2026 HORIZONTAL ELLIPSIS + static const std::pair kReplacements[] = { + {" ,", ","}, {" .", "."}, {" !", "!"}, {" ?", "?"}, {" ;", ";"}, {" :", ":"}, + {"\xE2\x80\x9C", "\""}, // U+201C + {"\xE2\x80\x9D", "\""}, // U+201D + {"\xE2\x80\x98", "'"}, // U+2018 + {"\xE2\x80\x99", "'"}, // U+2019 + }; + for (const auto & [from, to] : kReplacements) { + replace_all(body, from, to); + } + body = trim(collapse_whitespace(body)); + if (!ends_with_terminal(body)) { + body += "."; + } + return body; +} + +std::vector split_text(const std::string & text, int64_t max_chars) { + const std::string flat = collapse_whitespace(text); + if (max_chars < 1) { + throw std::runtime_error("Sopro max_segment_chars must be positive"); + } + if (static_cast(codepoint_length(flat)) <= max_chars) { + return flat.empty() ? std::vector{} : std::vector{flat}; + } + static const std::vector kSentenceEnd = {".", "!", "?", "\xE2\x80\xA6"}; + static const std::vector kClauseEnd = {",", ";", ":"}; + std::vector segments; + for (const auto & sentence : pack(split_after(flat, kSentenceEnd), max_chars)) { + if (static_cast(codepoint_length(sentence)) <= max_chars) { + segments.push_back(sentence); + continue; + } + for (const auto & clause : pack(split_after(sentence, kClauseEnd), max_chars)) { + if (static_cast(codepoint_length(clause)) <= max_chars) { + segments.push_back(clause); + } else { + for (auto & piece : pack(split_on_spaces(clause), max_chars)) { + segments.push_back(std::move(piece)); + } + } + } + } + return segments; +} + +class SoproTextTokenizer::Impl { +public: + Impl(const std::filesystem::path & model_path, int64_t max_length) + : max_length_(max_length) { + const auto status = processor_.Load(model_path.string()); + if (!status.ok()) { + throw std::runtime_error( + "Sopro: failed to load SentencePiece model '" + model_path.string() + + "': " + status.ToString()); + } + bos_id_ = processor_.bos_id() >= 0 ? processor_.bos_id() : 1; + eos_id_ = processor_.eos_id() >= 0 ? processor_.eos_id() : 2; + unk_id_ = processor_.unk_id() >= 0 ? processor_.unk_id() : 0; + vocab_size_ = processor_.GetPieceSize(); + if (max_length_ < 2) { + throw std::runtime_error("Sopro tokenizer max_length must be at least 2"); + } + } + + std::vector encode(const std::string & text, const std::string & language) const { + const std::string tag = language_tag(language); + const std::string normalized = normalize_text(tag.empty() ? text : tag + " " + text); + std::vector pieces; + const auto status = processor_.Encode(normalized, &pieces); + if (!status.ok()) { + throw std::runtime_error("Sopro: SentencePiece encode failed: " + status.ToString()); + } + std::vector ids; + ids.reserve(pieces.size() + 2); + ids.push_back(bos_id_); + for (const int piece : pieces) { + ids.push_back(static_cast(piece)); + } + ids.push_back(eos_id_); + if (static_cast(ids.size()) > max_length_) { + // Drop overflowing text ids, not the EOS marker the LM keys on. + // The constructor guarantees max_length_ >= 2. + ids.resize(static_cast(max_length_ - 1)); + ids.push_back(eos_id_); + } + if (ids.empty()) { + ids.push_back(unk_id_); + } + return ids; + } + + int32_t bos_id_ = 1; + int32_t eos_id_ = 2; + int32_t unk_id_ = 0; + int64_t vocab_size_ = 0; + +private: + sentencepiece::SentencePieceProcessor processor_; + int64_t max_length_ = 512; +}; + +SoproTextTokenizer::SoproTextTokenizer(const std::filesystem::path & model_path, int64_t max_length) + : impl_(std::make_unique(model_path, max_length)) {} + +SoproTextTokenizer::~SoproTextTokenizer() = default; + +std::vector SoproTextTokenizer::encode( + const std::string & text, + const std::string & language) const { + return impl_->encode(text, language); +} + +int32_t SoproTextTokenizer::bos_id() const noexcept { return impl_->bos_id_; } +int32_t SoproTextTokenizer::eos_id() const noexcept { return impl_->eos_id_; } +int32_t SoproTextTokenizer::unk_id() const noexcept { return impl_->unk_id_; } +int64_t SoproTextTokenizer::vocab_size() const noexcept { return impl_->vocab_size_; } + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/sopro_tts/vocoder.cpp b/src/community_models/sopro_tts/vocoder.cpp new file mode 100644 index 000000000..40774c736 --- /dev/null +++ b/src/community_models/sopro_tts/vocoder.cpp @@ -0,0 +1,458 @@ +#include "engine/community_models/sopro_tts/vocoder.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/audio/dsp.h" +#include "engine/framework/audio/fft.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/streaming_conv_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::sopro_tts { + +struct SoproVocoderConvNeXtWeights { + engine::modules::DepthwiseConv1dWeights dwconv; + engine::modules::NormWeights norm; + engine::modules::LinearWeights pwconv1; + engine::modules::LinearWeights pwconv2; + engine::core::TensorValue gamma; +}; + +struct SoproVocoderWeights { + std::shared_ptr store; + engine::modules::Conv1dWeights embed; + engine::modules::NormWeights norm; + std::vector convnext; + engine::modules::NormWeights final_norm; + engine::modules::LinearWeights head_out; + std::vector istft_window; // head.istft.window, n_fft taps + std::vector analysis_window; // MelSpectrogram STFT window + std::vector mel_filterbank; // [freq_bins, n_mels], row-major +}; + +namespace { + +namespace binding = engine::modules::binding; + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +// Multiply the last (channel) dimension of a channel-last tensor by a vector. +engine::core::TensorValue scale_last_dim( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & input, + const engine::core::TensorValue & scale) { + const auto view = engine::core::reshape_tensor( + ctx, scale, engine::core::TensorShape::from_dims({1, 1, scale.shape.dims[0]})); + const auto repeated = engine::modules::RepeatModule({input.shape}).build(ctx, view); + return engine::modules::MulModule{}.build(ctx, input, repeated); +} + +engine::modules::TransposeConfig swap_channel_time() { + return engine::modules::TransposeConfig{{0, 2, 1, 3}, 3}; +} + +std::shared_ptr load_vocoder_weights( + ggml_backend_t backend, + engine::core::BackendType backend_type, + const engine::assets::TensorSource & source, + const SoproVocoderConfig & config, + size_t weight_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type) { + auto weights = std::make_shared(); + require_frontend_buffers( + source, "vocoder", + {"feature_extractor.mel_spec.spectrogram.window", + "feature_extractor.mel_spec.mel_scale.fb", + "head.istft.window"}); + weights->store = std::make_shared( + backend, backend_type, "sopro_tts.vocoder.weights", weight_context_bytes); + weights->embed = binding::conv1d_from_source( + *weights->store, source, "backbone.embed", conv_storage_type, + config.dim, config.n_mels, 7, true); + weights->norm = binding::norm_from_source( + *weights->store, source, "backbone.norm", config.dim); + weights->convnext.reserve(static_cast(config.num_layers)); + for (int64_t layer = 0; layer < config.num_layers; ++layer) { + const std::string prefix = "backbone.convnext." + std::to_string(layer); + SoproVocoderConvNeXtWeights block; + block.dwconv = binding::depthwise_conv1d_from_source( + *weights->store, source, prefix + ".dwconv", conv_storage_type, config.dim, 7, true); + block.norm = binding::norm_from_source( + *weights->store, source, prefix + ".norm", config.dim); + block.pwconv1 = binding::linear_from_source( + *weights->store, source, prefix + ".pwconv1", matmul_storage_type, + config.intermediate_dim, config.dim, true); + block.pwconv2 = binding::linear_from_source( + *weights->store, source, prefix + ".pwconv2", matmul_storage_type, + config.dim, config.intermediate_dim, true); + block.gamma = weights->store->load_f32_tensor(source, prefix + ".gamma", {config.dim}); + weights->convnext.push_back(std::move(block)); + } + weights->final_norm = binding::norm_from_source( + *weights->store, source, "backbone.final_layer_norm", config.dim); + weights->head_out = binding::linear_from_source( + *weights->store, source, "head.out", matmul_storage_type, + config.n_fft + 2, config.dim, true); + weights->istft_window = source.require_f32("head.istft.window", {config.n_fft}); + // torchaudio MelSpectrogram keeps both of these as persistent buffers, so + // the analysis filterbank is byte-identical to the reference pipeline's. + weights->analysis_window = source.require_f32( + "feature_extractor.mel_spec.spectrogram.window", {config.n_fft}); + weights->mel_filterbank = source.require_f32( + "feature_extractor.mel_spec.mel_scale.fb", {config.n_fft / 2 + 1, config.n_mels}); + weights->store->upload(); + return weights; +} + +engine::core::TensorValue build_convnext_block( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & input_bct, + const SoproVocoderConvNeXtWeights & weights, + const SoproVocoderConfig & config) { + auto hidden = engine::modules::DepthwiseConv1dModule({ + config.dim, 7, 1, 3, 1, weights.dwconv.bias.has_value(), + }).build(ctx, input_bct, weights.dwconv); + hidden = engine::modules::TransposeModule(swap_channel_time()).build(ctx, hidden); + hidden = engine::modules::LayerNormModule({config.dim, 1.0e-6F, true, true}) + .build(ctx, hidden, weights.norm); + hidden = engine::modules::LinearModule({config.dim, config.intermediate_dim, true, GGML_PREC_F32}) + .build(ctx, hidden, weights.pwconv1); + hidden = engine::modules::GeluModule({engine::modules::GeluApproximation::ExactErf}).build(ctx, hidden); + hidden = engine::modules::LinearModule({config.intermediate_dim, config.dim, true, GGML_PREC_F32}) + .build(ctx, hidden, weights.pwconv2); + hidden = scale_last_dim(ctx, hidden, weights.gamma); + hidden = engine::modules::TransposeModule(swap_channel_time()).build(ctx, hidden); + return engine::modules::AddModule{}.build(ctx, input_bct, hidden); +} + +engine::core::TensorValue build_vocoder_head( + engine::core::ModuleBuildContext & ctx, + const engine::core::TensorValue & mel_bct, + const SoproVocoderWeights & weights, + const SoproVocoderConfig & config) { + auto hidden = engine::modules::Conv1dModule({ + config.n_mels, config.dim, 7, 1, 3, 1, weights.embed.bias.has_value(), + }).build(ctx, mel_bct, weights.embed); + hidden = engine::modules::TransposeModule(swap_channel_time()).build(ctx, hidden); + hidden = engine::modules::LayerNormModule({config.dim, 1.0e-6F, true, true}) + .build(ctx, hidden, weights.norm); + hidden = engine::modules::TransposeModule(swap_channel_time()).build(ctx, hidden); + for (const auto & block : weights.convnext) { + hidden = build_convnext_block(ctx, hidden, block, config); + } + hidden = engine::modules::TransposeModule(swap_channel_time()).build(ctx, hidden); + hidden = engine::modules::LayerNormModule({config.dim, 1.0e-6F, true, true}) + .build(ctx, hidden, weights.final_norm); + return engine::modules::LinearModule({config.dim, config.n_fft + 2, true, GGML_PREC_F32}) + .build(ctx, hidden, weights.head_out); +} + +} // namespace + +int64_t band_limit_bin(const SoproVocoderConfig & config) { + const int64_t freq_bins = config.n_fft / 2 + 1; + if (config.band_limit_hz <= 0.0F) { + return freq_bins; + } + const auto cut = static_cast(std::ceil( + static_cast(config.band_limit_hz) * static_cast(config.n_fft) / + static_cast(config.sample_rate))); + return std::clamp(cut, 0, freq_bins); +} + +namespace { + +// ISTFTHead.spectrogram + ISTFT.forward. torch.fft.irfft(norm="backward") +// scales by 1/n_fft; the overlap-add envelope is fold(window^2) and, unlike +// the streaming path, the offline path divides by it without clamping. +std::vector istft_from_head( + const std::vector & head, + int64_t frames, + const SoproVocoderConfig & config, + const std::vector & window, + size_t threads) { + const int64_t freq_bins = config.n_fft / 2 + 1; + const int64_t out_dim = config.n_fft + 2; + if (static_cast(head.size()) != frames * out_dim) { + throw std::runtime_error("Sopro vocoder head output shape mismatch"); + } + if (static_cast(window.size()) != config.n_fft) { + throw std::runtime_error("Sopro vocoder ISTFT window shape mismatch"); + } + if (frames < 2) { + throw std::runtime_error("Sopro vocoder requires at least two mel frames"); + } + const float log_max_magnitude = std::log(config.max_magnitude); + // Everything at or above band_limit_hz is zeroed before the inverse + // transform, which kills the vocoder's high-frequency hiss. spectrum is + // value-initialised, so the loop below simply stops at the cut instead of + // writing zeros over the tail. + const int64_t synthesised_bins = band_limit_bin(config); + std::vector> spectrum(static_cast(frames * freq_bins)); + const int omp_threads = static_cast(std::max(1, threads)); +#ifdef _OPENMP +#pragma omp parallel for num_threads(omp_threads) if (frames >= 8) +#endif + for (int64_t frame = 0; frame < frames; ++frame) { + const float * row = head.data() + static_cast(frame * out_dim); + for (int64_t freq = 0; freq < synthesised_bins; ++freq) { + const float magnitude = std::exp(std::min(row[freq], log_max_magnitude)); + const float phase = row[freq_bins + freq]; + spectrum[static_cast(frame * freq_bins + freq)] = { + magnitude * std::cos(phase), magnitude * std::sin(phase)}; + } + } + + std::vector framed(static_cast(frames * config.n_fft), 0.0F); + engine::audio::real_fft_inverse( + {static_cast(frames), static_cast(config.n_fft)}, + { + static_cast(freq_bins * static_cast(sizeof(std::complex))), + static_cast(sizeof(std::complex)), + }, + { + static_cast(config.n_fft * static_cast(sizeof(float))), + static_cast(sizeof(float)), + }, + 1, spectrum.data(), framed.data(), + 1.0F / static_cast(config.n_fft), threads); + + const int64_t output_size = (frames - 1) * config.hop_length + config.n_fft; + std::vector folded(static_cast(output_size), 0.0F); + std::vector envelope(static_cast(output_size), 0.0F); + { + // Blocked over the output axis so each sample is accumulated in the + // same frame order as the serial loop. + const int64_t block = 4096; + const int64_t blocks = (output_size + block - 1) / block; +#ifdef _OPENMP +#pragma omp parallel for num_threads(omp_threads) if (blocks > 1) +#endif + for (int64_t b = 0; b < blocks; ++b) { + const int64_t begin = b * block; + const int64_t end = std::min(output_size, begin + block); + int64_t first = (begin - config.n_fft) / config.hop_length + 1; + first = std::max(first, 0); + int64_t last = std::min((end - 1) / config.hop_length, frames - 1); + for (int64_t frame = first; frame <= last; ++frame) { + const int64_t start = frame * config.hop_length; + const int64_t i0 = std::max(begin - start, 0); + const int64_t i1 = std::min(end - start, config.n_fft); + const float * src = framed.data() + static_cast(frame * config.n_fft); + for (int64_t i = i0; i < i1; ++i) { + const float w = window[static_cast(i)]; + folded[static_cast(start + i)] += src[i] * w; + envelope[static_cast(start + i)] += w * w; + } + } + } + } + + const int64_t pad = config.n_fft / 2; + const int64_t samples = output_size - 2 * pad; + if (samples <= 0) { + throw std::runtime_error("Sopro vocoder ISTFT produced no samples after trimming"); + } + std::vector audio(static_cast(samples), 0.0F); + for (int64_t i = 0; i < samples; ++i) { + const size_t src = static_cast(i + pad); + const float denominator = envelope[src]; + audio[static_cast(i)] = denominator != 0.0F ? folded[src] / denominator : 0.0F; + } + return audio; +} + +} // namespace + +struct SoproVocoderGraph { + SoproVocoderGraph( + ggml_backend_t backend_in, + engine::core::BackendType backend_type, + size_t graph_context_bytes, + const SoproVocoderConfig & config_in, + std::shared_ptr weights_in, + int64_t frames_in) + : backend(backend_in), + weights(std::move(weights_in)), + frames(frames_in), + head_dim(config_in.n_fft + 2), + config(&config_in) { + if (backend == nullptr || weights == nullptr) { + throw std::runtime_error("Sopro vocoder graph requires a backend and weights"); + } + if (frames <= 0) { + throw std::runtime_error("Sopro vocoder graph requires a positive frame count"); + } + ggml_init_params params{graph_context_bytes, nullptr, true}; + ctx.reset(ggml_init(params)); + if (ctx == nullptr) { + throw std::runtime_error("failed to initialize the Sopro vocoder graph context"); + } + engine::core::ModuleBuildContext build_ctx{ctx.get(), "sopro_tts.vocoder", backend_type}; + const auto shape = engine::core::TensorShape::from_dims({1, config_in.n_mels, frames}); + input = engine::core::make_tensor(build_ctx, GGML_TYPE_F32, shape).tensor; + ggml_set_input(input); + auto head = build_vocoder_head( + build_ctx, engine::core::wrap_tensor(input, shape, GGML_TYPE_F32), *weights, config_in); + head = engine::core::ensure_backend_addressable_layout(build_ctx, head); + output = head.tensor; + ggml_set_output(output); + graph = ggml_new_graph_custom(ctx.get(), 65536, false); + ggml_build_forward_expand(graph, output); + gallocr = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend)); + if (gallocr == nullptr || !ggml_gallocr_reserve(gallocr, graph) || + !ggml_gallocr_alloc_graph(gallocr, graph)) { + throw std::runtime_error("failed to allocate the Sopro vocoder graph"); + } + } + + ~SoproVocoderGraph() { + if (gallocr != nullptr) { + ggml_gallocr_free(gallocr); + gallocr = nullptr; + } + } + + bool matches(const SoproVocoderWeights & other, int64_t other_frames) const noexcept { + return weights.get() == &other && frames == other_frames; + } + + std::vector run(const std::vector & mel, size_t threads) { + ggml_backend_tensor_set(input, mel.data(), 0, mel.size() * sizeof(float)); + const ggml_status status = engine::core::compute_backend_graph(backend, graph); + ggml_backend_synchronize(backend); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Sopro vocoder graph compute failed"); + } + std::vector head(static_cast(frames * head_dim), 0.0F); + ggml_backend_tensor_get(output, head.data(), 0, head.size() * sizeof(float)); + return istft_from_head(head, frames, *config, weights->istft_window, threads); + } + + ggml_backend_t backend = nullptr; + std::shared_ptr weights; + int64_t frames = 0; + int64_t head_dim = 0; + const SoproVocoderConfig * config = nullptr; + std::unique_ptr ctx; + ggml_tensor * input = nullptr; + ggml_tensor * output = nullptr; + ggml_cgraph * graph = nullptr; + ggml_gallocr_t gallocr = nullptr; +}; + +SoproVocoderRuntime::SoproVocoderRuntime( + const SoproTTSAssets & assets, + engine::core::ExecutionContext & execution_context, + size_t weight_context_bytes, + size_t graph_context_bytes, + engine::assets::TensorStorageType matmul_storage_type, + engine::assets::TensorStorageType conv_storage_type) + : config_(assets.config.vocoder), + execution_context_(execution_context), + graph_context_bytes_(graph_context_bytes), + weights_(load_vocoder_weights( + execution_context.backend(), + execution_context.backend_type(), + *assets.vocoder_weights, + assets.config.vocoder, + weight_context_bytes, + matmul_storage_type, + conv_storage_type)) {} + +SoproVocoderRuntime::~SoproVocoderRuntime() = default; + +std::vector SoproVocoderRuntime::decode( + const std::vector & mel, + int64_t frames) const { + if (frames <= 0 || static_cast(mel.size()) != frames * config_.n_mels) { + throw std::runtime_error("Sopro vocoder requires a [n_mels, frames] input"); + } + if (graph_ == nullptr || !graph_->matches(*weights_, frames)) { + // Free the previous arena first; otherwise both are resident while the + // replacement is allocated, and every segment rebuilds this graph. + graph_.reset(); + graph_ = std::make_unique( + execution_context_.backend(), + execution_context_.backend_type(), + graph_context_bytes_, + config_, + weights_, + frames); + } + return graph_->run(mel, static_cast(execution_context_.config().threads)); +} + +std::vector SoproVocoderRuntime::log_mel(const std::vector & audio) const { + if (audio.empty()) { + throw std::runtime_error("Sopro vocoder mel extraction requires a non-empty waveform"); + } + const int64_t freq_bins = config_.n_fft / 2 + 1; + engine::audio::STFTConfig stft; + stft.n_fft = config_.n_fft; + stft.hop_length = config_.hop_length; + stft.win_length = config_.n_fft; + stft.center = true; + stft.pad_mode = engine::audio::STFTPadMode::Reflect; + const auto magnitude = engine::audio::STFT{}.compute_magnitude( + audio, weights_->analysis_window, 1, static_cast(audio.size()), stft, + static_cast(execution_context_.config().threads)); + if (magnitude.shape.size() != 3 || magnitude.shape[1] != freq_bins) { + throw std::runtime_error("Sopro vocoder STFT produced an unexpected layout"); + } + const int64_t frames = magnitude.shape[2]; + std::vector mel(static_cast(config_.n_mels * frames), 0.0F); + // MelScale: mel[m, t] = sum_f magnitude[f, t] * fb[f, m], then log-clamped. + for (int64_t f = 0; f < freq_bins; ++f) { + const float * fb_row = weights_->mel_filterbank.data() + static_cast(f * config_.n_mels); + const float * spec_row = magnitude.values.data() + static_cast(f * frames); + for (int64_t m = 0; m < config_.n_mels; ++m) { + const float weight = fb_row[m]; + if (weight == 0.0F) { + continue; + } + float * out_row = mel.data() + static_cast(m * frames); + for (int64_t t = 0; t < frames; ++t) { + out_row[t] += weight * spec_row[t]; + } + } + } + for (auto & value : mel) { + value = std::log(std::max(value, 1.0e-7F)); + } + return mel; +} + +int64_t SoproVocoderRuntime::mel_frames(int64_t samples) const noexcept { + return samples / config_.hop_length + 1; // centred STFT +} + +int64_t SoproVocoderRuntime::hop_length() const noexcept { return config_.hop_length; } +int64_t SoproVocoderRuntime::n_mels() const noexcept { return config_.n_mels; } +int SoproVocoderRuntime::sample_rate() const noexcept { return static_cast(config_.sample_rate); } + +} // namespace engine::community_models::sopro_tts diff --git a/src/community_models/vibeasr/assets.cpp b/src/community_models/vibeasr/assets.cpp new file mode 100644 index 000000000..fcccdc04e --- /dev/null +++ b/src/community_models/vibeasr/assets.cpp @@ -0,0 +1,273 @@ +#include "engine/community_models/vibeasr/assets.h" + +#include "engine/framework/model_spec/package.h" + +#include + +#include +#include +#include +#include +#include + +namespace engine::community_models::vibeasr { +namespace { + +// VibeASR's AudioVAEEncoder fixes the stride schedule in code (it is not part of +// the checkpoint), giving a total downsampling factor of 3200 samples per frame. +constexpr int64_t kDownsampleStrides[] = {1, 2, 2, 4, 5, 5, 8}; +constexpr size_t kNumStages = sizeof(kDownsampleStrides) / sizeof(kDownsampleStrides[0]); + +std::vector require_shape( + const assets::TensorSource & source, + const std::string & name, + size_t expected_rank) { + auto shape = source.require_metadata(name).shape; + if (shape.size() != expected_rank) { + throw std::runtime_error( + "VibeASR VAE tensor " + name + " has rank " + std::to_string(shape.size()) + + ", expected " + std::to_string(expected_rank)); + } + return shape; +} + +std::string block_prefix(const std::string & branch, size_t stage, size_t block) { + return branch + ".stages." + std::to_string(stage) + "." + std::to_string(block); +} + +VaeBlockConfig derive_block(const assets::TensorSource & source, const std::string & prefix) { + VaeBlockConfig block; + // Depthwise kernel is stored as [channels, 1, kernel_size]. + const auto mixer = require_shape(source, prefix + ".mixer.conv.conv.conv.weight", 3); + block.channels = mixer[0]; + block.kernel_size = mixer[2]; + if (mixer[1] != 1) { + throw std::runtime_error("VibeASR VAE mixer conv at " + prefix + " is not depthwise"); + } + // Linear weights are stored as [out_features, in_features]. + const auto fc1 = require_shape(source, prefix + ".ffn.linear1.weight", 2); + const auto fc2 = require_shape(source, prefix + ".ffn.linear2.weight", 2); + block.ffn_hidden = fc1[0]; + if (fc1[1] != block.channels || fc2[0] != block.channels || fc2[1] != block.ffn_hidden) { + throw std::runtime_error("VibeASR VAE FFN shapes at " + prefix + " are inconsistent"); + } + return block; +} + +VaeBranchConfig derive_branch(const assets::TensorSource & source, const std::string & prefix) { + VaeBranchConfig branch; + branch.prefix = prefix; + branch.total_stride = 1; + + int64_t expected_in_channels = 1; // raw mono waveform + for (size_t stage = 0; stage < kNumStages; ++stage) { + const std::string downsample = + prefix + ".downsample_layers." + std::to_string(stage) + ".0.conv.conv.weight"; + if (!source.has_tensor(downsample)) { + throw std::runtime_error("VibeASR VAE checkpoint is missing " + downsample); + } + // Conv weight is stored as [out_channels, in_channels, kernel_size]. + const auto shape = require_shape(source, downsample, 3); + + VaeStageConfig config; + config.out_channels = shape[0]; + config.in_channels = shape[1]; + config.downsample_kernel_size = shape[2]; + config.downsample_stride = kDownsampleStrides[stage]; + if (config.in_channels != expected_in_channels) { + throw std::runtime_error("VibeASR VAE stage " + std::to_string(stage) + " channel count does not chain"); + } + if (config.downsample_kernel_size < config.downsample_stride) { + throw std::runtime_error("VibeASR VAE stage " + std::to_string(stage) + " kernel is shorter than its stride"); + } + + for (size_t block = 0;; ++block) { + const std::string block_name = block_prefix(prefix, stage, block); + if (!source.has_tensor(block_name + ".norm.weight")) { + break; + } + auto derived = derive_block(source, block_name); + if (derived.channels != config.out_channels) { + throw std::runtime_error("VibeASR VAE block " + block_name + " width does not match its stage"); + } + config.blocks.push_back(derived); + } + if (config.blocks.empty()) { + throw std::runtime_error("VibeASR VAE stage " + std::to_string(stage) + " has no blocks"); + } + + branch.total_stride *= config.downsample_stride; + expected_in_channels = config.out_channels; + branch.stages.push_back(std::move(config)); + } + + const auto head = require_shape(source, prefix + ".head.conv.conv.weight", 3); + branch.latent_dim = head[0]; + branch.head_kernel_size = head[2]; + if (head[1] != expected_in_channels) { + throw std::runtime_error("VibeASR VAE head input width does not match the last stage"); + } + + const auto fc1 = require_shape(source, prefix + "_connector.fc1.weight", 2); + const auto fc2 = require_shape(source, prefix + "_connector.fc2.weight", 2); + branch.connector_hidden = fc1[0]; + if (fc1[1] != branch.latent_dim || fc2[0] != branch.connector_hidden || + fc2[1] != branch.connector_hidden) { + throw std::runtime_error("VibeASR VAE " + prefix + " connector shapes are inconsistent"); + } + return branch; +} + +// assets::TensorSource exposes tensors, not the GGUF KV block, and the decoder +// geometry lives entirely in the KV block. Reading it directly is what the other +// community entries do (see sense_asr/assets.cpp). +class GgufMetadataReader { +public: + explicit GgufMetadataReader(const std::filesystem::path & path) { + gguf_init_params params{}; + params.no_alloc = true; + params.ctx = nullptr; + gguf_context * gguf = gguf_init_from_file(path.string().c_str(), params); + if (gguf == nullptr) { + throw std::runtime_error("Failed to read VibeASR GGUF metadata from " + path.string()); + } + ctx_.reset(gguf); + } + + int64_t require_u32(const char * key) const { + const int64_t id = gguf_find_key(ctx_.get(), key); + if (id < 0) { + throw std::runtime_error(std::string("VibeASR LM GGUF is missing ") + key); + } + return static_cast(gguf_get_val_u32(ctx_.get(), id)); + } + + float require_f32(const char * key) const { + const int64_t id = gguf_find_key(ctx_.get(), key); + if (id < 0) { + throw std::runtime_error(std::string("VibeASR LM GGUF is missing ") + key); + } + return gguf_get_val_f32(ctx_.get(), id); + } + + std::string kv_str(const char * key, std::string fallback) const { + const int64_t id = gguf_find_key(ctx_.get(), key); + return id < 0 ? std::move(fallback) : std::string(gguf_get_val_str(ctx_.get(), id)); + } + +private: + struct GgufDeleter { + void operator()(gguf_context * ctx) const noexcept { + if (ctx != nullptr) { + gguf_free(ctx); + } + } + }; + + std::unique_ptr ctx_; +}; + +} // namespace + +int64_t VaeBranchConfig::frames_for_samples(int64_t num_samples) const { + // Every stage is a causal conv with left padding kernel_size - stride, so + // its output length is ggml_calc_conv_output_size() with that padding. + int64_t length = num_samples; + for (const auto & stage : stages) { + const int64_t padding = stage.downsample_kernel_size - stage.downsample_stride; + length = (length + padding - stage.downsample_kernel_size) / stage.downsample_stride + 1; + if (length <= 0) { + return 0; + } + } + return length; +} + +VibeASRVaeConfig derive_vae_config(const assets::TensorSource & source) { + VibeASRVaeConfig config; + config.acoustic = derive_branch(source, "acoustic"); + config.semantic = derive_branch(source, "semantic"); + if (config.acoustic.connector_hidden != config.semantic.connector_hidden) { + throw std::runtime_error("VibeASR VAE branches disagree on the connector width"); + } + return config; +} + +std::shared_ptr load_vibeasr_vae_assets(const std::filesystem::path & model_path) { + return make_vibeasr_vae_assets(engine::assets::open_tensor_source(model_path)); +} + +std::shared_ptr make_vibeasr_vae_assets( + std::shared_ptr source) { + auto assets = std::make_shared(); + assets->config = derive_vae_config(*source); + assets->source = std::move(source); + return assets; +} + +VibeASRLmConfig derive_lm_config(const assets::TensorSource & source) { + const GgufMetadataReader reader(source.source_path()); + + const std::string architecture = reader.kv_str("general.architecture", ""); + if (architecture != "qwen2") { + throw std::runtime_error( + "VibeASR LM GGUF declares architecture '" + architecture + "', expected qwen2"); + } + + VibeASRLmConfig config; + config.vocab_size = reader.require_u32("qwen2.vocab_size"); + config.hidden_size = reader.require_u32("qwen2.embedding_length"); + config.intermediate_size = reader.require_u32("qwen2.feed_forward_length"); + config.num_hidden_layers = reader.require_u32("qwen2.block_count"); + config.num_attention_heads = reader.require_u32("qwen2.attention.head_count"); + config.num_key_value_heads = reader.require_u32("qwen2.attention.head_count_kv"); + config.max_position_embeddings = reader.require_u32("qwen2.context_length"); + // The checkpoint has no attention.key_length: Qwen2 stores the per-head width + // only as the RoPE dimension count, which for this model equals + // embedding_length / head_count. + config.head_dim = reader.require_u32("qwen2.rope.dimension_count"); + config.rms_norm_eps = reader.require_f32("qwen2.attention.layer_norm_rms_epsilon"); + config.rope_theta = reader.require_f32("qwen2.rope.freq_base"); + + if (config.head_dim * config.num_attention_heads != config.hidden_size) { + throw std::runtime_error("VibeASR LM head_dim * head_count does not match embedding_length"); + } + if (config.num_key_value_heads <= 0 || config.num_attention_heads % config.num_key_value_heads != 0) { + throw std::runtime_error("VibeASR LM head_count is not a multiple of head_count_kv"); + } + if (config.num_hidden_layers <= 0) { + throw std::runtime_error("VibeASR LM declares no layers"); + } + + // Cross-check the metadata against the one tensor whose shape pins both dims. + const auto embedding = source.require_metadata("token_embd.weight").shape; + if (embedding.size() != 2 || embedding[0] != config.vocab_size || embedding[1] != config.hidden_size) { + throw std::runtime_error("VibeASR LM token_embd.weight does not match the declared geometry"); + } + return config; +} + +std::shared_ptr load_vibeasr_assets(const std::filesystem::path & model_path) { + auto assets = std::make_shared(); + assets->resources = engine::model_spec::load_resource_bundle_for_family(model_path, "vibeasr"); + + // A GGUF still carrying the VibeASR fork's type ids (36/37) fails deep inside + // the reader with an unhelpful message, so name the fix here. + auto open = [&assets](const char * id) { + try { + return assets->resources.open_tensor_source(id); + } catch (const std::exception & error) { + throw std::runtime_error( + std::string("VibeASR could not open the '") + id + "' GGUF (" + error.what() + + "). If it came straight from huggingface.co/microsoft/VibeVoice-ASR-BitNet, run " + "tools/community_models/convert_vibeasr_gguf.py --in-place on it first."); + } + }; + + assets->vae = make_vibeasr_vae_assets(open("vae_weights")); + assets->lm_weights = open("lm_weights"); + assets->lm = derive_lm_config(*assets->lm_weights); + return assets; +} + +} // namespace engine::community_models::vibeasr diff --git a/src/community_models/vibeasr/lm_decoder.cpp b/src/community_models/vibeasr/lm_decoder.cpp new file mode 100644 index 000000000..35bde5ef5 --- /dev/null +++ b/src/community_models/vibeasr/lm_decoder.cpp @@ -0,0 +1,679 @@ +#include "engine/community_models/vibeasr/lm_decoder.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/modules/lookup_modules.h" +#include "engine/framework/modules/positional_modules.h" +#include "engine/framework/modules/transformers/qwen_causal_decoder.h" +#include "engine/framework/runtime/errors.h" +#include "engine/framework/runtime/kv_cache.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::vibeasr { +namespace { + +namespace modules = engine::modules; +using Clock = std::chrono::steady_clock; + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +struct GgmlGallocrDeleter { + void operator()(ggml_gallocr_t alloc) const noexcept { + if (alloc != nullptr) { + ggml_gallocr_free(alloc); + } + } +}; + +struct LmLayerWeights { + core::TensorValue input_norm; + core::TensorValue q_proj; + core::TensorValue q_bias; + core::TensorValue k_proj; + core::TensorValue k_bias; + core::TensorValue v_proj; + core::TensorValue v_bias; + core::TensorValue o_proj; + core::TensorValue post_norm; + core::TensorValue gate_proj; + core::TensorValue up_proj; + core::TensorValue down_proj; +}; + +struct LmWeights { + std::shared_ptr store; + core::TensorValue token_embedding; + std::vector layers; + core::TensorValue norm; + core::TensorValue lm_head; +}; + +struct PrefillOutput { + std::vector logits; + runtime::TransformerKVState kv_state; +}; + +// I2_S is a whole-tensor quantization whose in-band F32 scale sits after the +// packed codes, which is exactly what ggml_nbytes() accounts for, so the GGUF +// payload goes to the backend byte for byte. Same contract as the encoder's +// load_i8_s_tensor(). +core::TensorValue load_i2_s_tensor( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & name, + const std::vector & expected_shape) { + const auto metadata = source.require_metadata(name); + if (metadata.dtype != "i2_s") { + throw std::runtime_error("VibeASR LM tensor " + name + " is " + metadata.dtype + ", expected i2_s"); + } + if (metadata.shape != expected_shape) { + throw std::runtime_error("VibeASR LM tensor " + name + " has an unexpected shape"); + } + + core::TensorShape shape; + shape.rank = expected_shape.size(); + for (size_t i = 0; i < shape.rank; ++i) { + shape.dims[i] = expected_shape[i]; + } + + const auto raw = source.require_tensor_data(name); + return store.make_tensor(shape, GGML_TYPE_I2_S, raw.bytes.data(), raw.bytes.size()); +} + +modules::QwenDecoderLayerWeights to_qwen_layer_weights(const LmLayerWeights & weights) { + modules::QwenDecoderLayerWeights out; + out.input_norm = {weights.input_norm, std::nullopt}; + out.self_attention.q_weight = weights.q_proj; + out.self_attention.q_bias = weights.q_bias; + out.self_attention.k_weight = weights.k_proj; + out.self_attention.k_bias = weights.k_bias; + out.self_attention.v_weight = weights.v_proj; + out.self_attention.v_bias = weights.v_bias; + out.self_attention.out_weight = weights.o_proj; + out.post_norm = {weights.post_norm, std::nullopt}; + out.mlp.gate_proj = {weights.gate_proj, std::nullopt}; + out.mlp.up_proj = {weights.up_proj, std::nullopt}; + out.mlp.down_proj = {weights.down_proj, std::nullopt}; + return out; +} + +// Plain Qwen2: attention biases, no per-head Q/K norms. Nothing here depends on +// the weight type, which is why the framework's decoder runs unmodified on I2_S +// projections -- ggml_mul_mat dispatches on the tensor type. +modules::QwenCausalDecoderConfig make_qwen_decoder_config(const VibeASRLmConfig & config) { + modules::QwenCausalDecoderConfig out; + out.stack.hidden_size = config.hidden_size; + out.stack.num_attention_heads = config.num_attention_heads; + out.stack.num_key_value_heads = config.num_key_value_heads; + out.stack.head_dim = config.head_dim; + out.stack.intermediate_size = config.intermediate_size; + out.stack.layers = config.num_hidden_layers; + out.stack.rms_norm_eps = config.rms_norm_eps; + out.stack.rope_theta = config.rope_theta; + out.stack.use_qk_norm = false; + out.stack.runtime.static_cache.update_mode = modules::QwenDecoderStaticCacheUpdateMode::DirectSetRows; + out.logits_size = config.vocab_size; + out.logits_mode = modules::QwenCausalDecoderLogitsMode::LastStep; + return out; +} + +modules::QwenCausalDecoderWeights make_qwen_decoder_weights(const LmWeights & weights) { + modules::QwenCausalDecoderWeights out; + out.stack.layers.reserve(weights.layers.size()); + for (const auto & layer : weights.layers) { + out.stack.layers.push_back(to_qwen_layer_weights(layer)); + } + out.final_norm = {weights.norm, std::nullopt}; + out.lm_head = {weights.lm_head, std::nullopt}; + return out; +} + +// Token embeddings with the encoder's speech features written over the +// <|speech_pad|> slots. Doing the overwrite in-graph with ggml_set_rows keeps +// the prompt a single I32 upload instead of a host-side embedding matrix. +core::TensorValue prompt_embeddings( + core::ModuleBuildContext & ctx, + const LmWeights & weights, + const VibeASRLmConfig & config, + ggml_tensor * token_ids, + ggml_tensor * speech_embeddings, + ggml_tensor * speech_positions, + int64_t prompt_steps, + int64_t speech_tokens) { + auto ids = core::wrap_tensor(token_ids, core::TensorShape::from_dims({prompt_steps}), GGML_TYPE_I32); + auto x = modules::EmbeddingModule({config.vocab_size, config.hidden_size}) + .build(ctx, ids, weights.token_embedding); + if (speech_tokens > 0) { + auto speech = core::wrap_tensor( + speech_embeddings, + core::TensorShape::from_dims({speech_tokens, config.hidden_size}), + GGML_TYPE_F32); + auto positions = core::wrap_tensor( + speech_positions, + core::TensorShape::from_dims({speech_tokens}), + GGML_TYPE_I64); + x = core::wrap_tensor( + ggml_set_rows(ctx.ggml, x.tensor, speech.tensor, positions.tensor), + x.shape, + GGML_TYPE_F32); + } + return core::reshape_tensor(ctx, x, core::TensorShape::from_dims({1, prompt_steps, config.hidden_size})); +} + +LmWeights load_weights( + const assets::TensorSource & source, + const VibeASRLmConfig & config, + ggml_backend_t backend, + core::BackendType backend_type, + size_t weight_context_bytes) { + LmWeights weights; + weights.store = std::make_shared( + backend, + backend_type, + "vibeasr.lm.weights", + weight_context_bytes); + + // The embedding table and the output projection are the two tensors VibeASR + // leaves unternarized -- Q6_K and F16 in the published checkpoint -- so they + // load through the framework's normal path. + weights.token_embedding = weights.store->load_tensor( + source, + "token_embd.weight", + assets::TensorStorageType::Native, + {config.vocab_size, config.hidden_size}); + + const int64_t dim = config.head_dim; + const int64_t q_dim = config.num_attention_heads * dim; + const int64_t kv_dim = config.num_key_value_heads * dim; + weights.layers.reserve(static_cast(config.num_hidden_layers)); + for (int64_t layer = 0; layer < config.num_hidden_layers; ++layer) { + const std::string prefix = "blk." + std::to_string(layer) + "."; + LmLayerWeights w; + w.input_norm = weights.store->load_f32_tensor(source, prefix + "attn_norm.weight", {config.hidden_size}); + w.q_proj = load_i2_s_tensor(*weights.store, source, prefix + "attn_q.weight", {q_dim, config.hidden_size}); + w.q_bias = weights.store->load_f32_tensor(source, prefix + "attn_q.bias", {q_dim}); + w.k_proj = load_i2_s_tensor(*weights.store, source, prefix + "attn_k.weight", {kv_dim, config.hidden_size}); + w.k_bias = weights.store->load_f32_tensor(source, prefix + "attn_k.bias", {kv_dim}); + w.v_proj = load_i2_s_tensor(*weights.store, source, prefix + "attn_v.weight", {kv_dim, config.hidden_size}); + w.v_bias = weights.store->load_f32_tensor(source, prefix + "attn_v.bias", {kv_dim}); + w.o_proj = load_i2_s_tensor(*weights.store, source, prefix + "attn_output.weight", {config.hidden_size, q_dim}); + w.post_norm = weights.store->load_f32_tensor(source, prefix + "ffn_norm.weight", {config.hidden_size}); + w.gate_proj = load_i2_s_tensor( + *weights.store, source, prefix + "ffn_gate.weight", {config.intermediate_size, config.hidden_size}); + w.up_proj = load_i2_s_tensor( + *weights.store, source, prefix + "ffn_up.weight", {config.intermediate_size, config.hidden_size}); + w.down_proj = load_i2_s_tensor( + *weights.store, source, prefix + "ffn_down.weight", {config.hidden_size, config.intermediate_size}); + weights.layers.push_back(std::move(w)); + } + + weights.norm = weights.store->load_f32_tensor(source, "output_norm.weight", {config.hidden_size}); + weights.lm_head = weights.store->load_tensor( + source, + "output.weight", + assets::TensorStorageType::Native, + {config.vocab_size, config.hidden_size}); + weights.store->upload(); + return weights; +} + +int32_t argmax_index(const std::vector & values) { + if (values.empty()) { + throw std::runtime_error("VibeASR LM cannot select from empty logits"); + } + size_t best = 0; + for (size_t i = 1; i < values.size(); ++i) { + if (values[i] > values[best]) { + best = i; + } + } + return static_cast(best); +} + +class LmWeightsRuntime { +public: + LmWeightsRuntime( + std::shared_ptr source, + VibeASRLmConfig config, + core::ExecutionContext & execution, + size_t weight_context_bytes) + : source_(std::move(source)), + config_(std::make_shared(config)), + backend_(execution.backend()), + backend_type_(execution.backend_type()), + threads_(std::max(1, execution.config().threads)), + weights_(std::make_shared(load_weights( + *source_, + *config_, + backend_, + backend_type_, + weight_context_bytes))) {} + + const VibeASRLmConfig & config() const noexcept { return *config_; } + const LmWeights & weights() const noexcept { return *weights_; } + ggml_backend_t backend() const noexcept { return backend_; } + core::BackendType backend_type() const noexcept { return backend_type_; } + int threads() const noexcept { return threads_; } + +private: + std::shared_ptr source_; + std::shared_ptr config_; + ggml_backend_t backend_ = nullptr; + core::BackendType backend_type_ = core::BackendType::Cpu; + int threads_ = 1; + std::shared_ptr weights_; +}; + +class PrefillGraph { +public: + PrefillGraph( + std::shared_ptr runtime, + int64_t prompt_steps, + int64_t speech_tokens, + size_t graph_arena_bytes) + : runtime_(std::move(runtime)), + prompt_steps_(prompt_steps), + speech_tokens_(speech_tokens) { + if (prompt_steps_ <= 0) { + throw std::runtime_error("VibeASR LM prefill requires positive prompt length"); + } + if (speech_tokens_ < 0 || speech_tokens_ > prompt_steps_) { + throw std::runtime_error("VibeASR LM prefill speech token count is invalid"); + } + const auto build_start = Clock::now(); + ggml_init_params params{graph_arena_bytes, nullptr, true}; + ctx_.reset(ggml_init(params)); + if (ctx_ == nullptr) { + throw std::runtime_error("failed to initialize VibeASR LM prefill graph context"); + } + const auto & config = runtime_->config(); + const auto & weights = runtime_->weights(); + core::ModuleBuildContext ctx{ctx_.get(), "vibeasr.lm.prefill", runtime_->backend_type()}; + token_ids_ = ggml_new_tensor_1d(ctx_.get(), GGML_TYPE_I32, prompt_steps_); + speech_embeddings_ = ggml_new_tensor_2d( + ctx_.get(), GGML_TYPE_F32, config.hidden_size, std::max(speech_tokens_, 1)); + speech_positions_ = ggml_new_tensor_1d(ctx_.get(), GGML_TYPE_I64, std::max(speech_tokens_, 1)); + auto x = prompt_embeddings( + ctx, + weights, + config, + token_ids_, + speech_embeddings_, + speech_positions_, + prompt_steps_, + speech_tokens_); + positions_ = ggml_new_tensor_1d(ctx_.get(), GGML_TYPE_I32, prompt_steps_); + auto positions = core::wrap_tensor(positions_, core::TensorShape::from_dims({prompt_steps_}), GGML_TYPE_I32); + + auto decoder_out = modules::QwenCausalDecoderModule(make_qwen_decoder_config(config)) + .build(ctx, x, positions, make_qwen_decoder_weights(weights)); + for (const auto & layer : decoder_out.state.layers) { + if (!layer.key.has_value() || !layer.value.has_value()) { + throw std::runtime_error("VibeASR LM prefill decoder did not return K/V state"); + } + // Copy K/V out of the graph-allocated intermediates and mark them as + // outputs so the allocator cannot recycle them before run() reads + // them back. + auto * key = ggml_cpy(ctx_.get(), layer.key->tensor, ggml_dup_tensor(ctx_.get(), layer.key->tensor)); + auto * value = ggml_cpy(ctx_.get(), layer.value->tensor, ggml_dup_tensor(ctx_.get(), layer.value->tensor)); + ggml_set_output(key); + ggml_set_output(value); + keys_.push_back(key); + values_.push_back(value); + } + logits_ = decoder_out.logits.tensor; + ggml_set_output(logits_); + graph_ = ggml_new_graph_custom(ctx_.get(), 65536, false); + ggml_build_forward_expand(graph_, logits_); + for (auto * key : keys_) { + ggml_build_forward_expand(graph_, key); + } + for (auto * value : values_) { + ggml_build_forward_expand(graph_, value); + } + const auto try_alloc = [&]() { + gallocr_.reset(ggml_gallocr_new(ggml_backend_get_default_buffer_type(runtime_->backend()))); + return gallocr_ != nullptr && + ggml_gallocr_reserve(gallocr_.get(), graph_) && + ggml_gallocr_alloc_graph(gallocr_.get(), graph_); + }; + if (!try_alloc() && (engine::core::trim_backend_pools(runtime_->backend()), !try_alloc())) { + throw engine::runtime::CapacityError( + "VibeASR LM prefill graph does not fit in device memory at this size (" + + std::to_string(prompt_steps_) + " prompt steps, of which " + + std::to_string(speech_tokens_) + " are speech tokens)"); + } + position_ids_ = modules::qwen_position_ids(prompt_steps_); + debug::timing_log_scalar("vibeasr.lm.prefill.graph.build_ms", engine::debug::elapsed_ms(build_start, Clock::now())); + debug::trace_log_scalar("vibeasr.lm.prefill_prompt_steps", prompt_steps_); + } + + ~PrefillGraph() { + engine::core::release_backend_graph_resources(runtime_->backend(), graph_, true); + } + + bool matches(const LmWeightsRuntime & runtime, int64_t prompt_steps, int64_t speech_tokens) const { + return runtime_.get() == &runtime && prompt_steps_ == prompt_steps && speech_tokens_ == speech_tokens; + } + + PrefillOutput run( + const std::vector & token_ids, + const std::vector & speech_embeddings, + const std::vector & speech_positions) { + const auto & config = runtime_->config(); + if (static_cast(token_ids.size()) != prompt_steps_) { + throw std::runtime_error("VibeASR LM prefill token id count mismatch"); + } + if (static_cast(speech_embeddings.size()) != speech_tokens_ * config.hidden_size) { + throw std::runtime_error("VibeASR LM prefill speech embedding size mismatch"); + } + if (static_cast(speech_positions.size()) != speech_tokens_) { + throw std::runtime_error("VibeASR LM prefill speech position count mismatch"); + } + // Re-uploaded on every run: leaves are not pinned by the graph allocator. + ggml_backend_tensor_set(positions_, position_ids_.data(), 0, position_ids_.size() * sizeof(int32_t)); + ggml_backend_tensor_set(token_ids_, token_ids.data(), 0, token_ids.size() * sizeof(int32_t)); + if (speech_tokens_ > 0) { + const std::vector positions(speech_positions.begin(), speech_positions.end()); + ggml_backend_tensor_set( + speech_embeddings_, speech_embeddings.data(), 0, speech_embeddings.size() * sizeof(float)); + ggml_backend_tensor_set(speech_positions_, positions.data(), 0, positions.size() * sizeof(int64_t)); + } + core::set_backend_threads(runtime_->backend(), runtime_->threads()); + const auto compute_start = Clock::now(); + const ggml_status status = engine::core::compute_backend_graph(runtime_->backend(), graph_); + ggml_backend_synchronize(runtime_->backend()); + debug::timing_log_scalar("vibeasr.lm.prefill.graph.compute_ms", engine::debug::elapsed_ms(compute_start, Clock::now())); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("VibeASR LM prefill graph compute failed"); + } + PrefillOutput out; + out.logits.resize(static_cast(config.vocab_size)); + ggml_backend_tensor_get(logits_, out.logits.data(), 0, out.logits.size() * sizeof(float)); + out.kv_state.current_end = prompt_steps_; + out.kv_state.layers.resize(keys_.size()); + const size_t layer_values = + static_cast(prompt_steps_ * config.num_key_value_heads * config.head_dim); + for (size_t layer = 0; layer < keys_.size(); ++layer) { + auto & state = out.kv_state.layers[layer]; + state.valid_steps = prompt_steps_; + state.key.resize(layer_values); + state.value.resize(layer_values); + ggml_backend_tensor_get(keys_[layer], state.key.data(), 0, state.key.size() * sizeof(float)); + ggml_backend_tensor_get(values_[layer], state.value.data(), 0, state.value.size() * sizeof(float)); + } + return out; + } + +private: + std::shared_ptr runtime_; + int64_t prompt_steps_ = 0; + int64_t speech_tokens_ = 0; + std::unique_ptr ctx_; + ggml_tensor * token_ids_ = nullptr; + ggml_tensor * speech_embeddings_ = nullptr; + ggml_tensor * speech_positions_ = nullptr; + ggml_tensor * positions_ = nullptr; + ggml_tensor * logits_ = nullptr; + std::vector keys_; + std::vector values_; + std::vector position_ids_; + ggml_cgraph * graph_ = nullptr; + std::unique_ptr, GgmlGallocrDeleter> gallocr_; +}; + +class DecodeGraph { +public: + DecodeGraph(std::shared_ptr runtime, int64_t cache_steps, size_t graph_arena_bytes) + : runtime_(std::move(runtime)), + cache_steps_(cache_steps) { + if (cache_steps_ <= 0) { + throw std::runtime_error("VibeASR LM decode requires positive cache length"); + } + const auto build_start = Clock::now(); + ggml_init_params params{graph_arena_bytes, nullptr, true}; + ctx_.reset(ggml_init(params)); + if (ctx_ == nullptr) { + throw std::runtime_error("failed to initialize VibeASR LM decode graph context"); + } + const auto & config = runtime_->config(); + const auto & weights = runtime_->weights(); + core::ModuleBuildContext ctx{ctx_.get(), "vibeasr.lm.decode", runtime_->backend_type()}; + token_id_ = ggml_new_tensor_1d(ctx_.get(), GGML_TYPE_I32, 1); + auto token_id = core::wrap_tensor(token_id_, core::TensorShape::from_dims({1}), GGML_TYPE_I32); + auto x = modules::EmbeddingModule({config.vocab_size, config.hidden_size}) + .build(ctx, token_id, weights.token_embedding); + x = core::reshape_tensor(ctx, x, core::TensorShape::from_dims({1, 1, config.hidden_size})); + positions_ = ggml_new_tensor_1d(ctx_.get(), GGML_TYPE_I32, 1); + auto positions = core::wrap_tensor(positions_, core::TensorShape::from_dims({1}), GGML_TYPE_I32); + cache_slot_ = ggml_new_tensor_1d(ctx_.get(), GGML_TYPE_I32, 1); + auto cache_slot = core::wrap_tensor(cache_slot_, core::TensorShape::from_dims({1}), GGML_TYPE_I32); + attention_mask_ = ggml_new_tensor_4d(ctx_.get(), GGML_TYPE_F16, cache_steps_, 1, 1, 1); + auto attention_mask = core::wrap_tensor( + attention_mask_, core::TensorShape::from_dims({1, 1, 1, cache_steps_}), GGML_TYPE_F16); + graph_ = ggml_new_graph_custom(ctx_.get(), 65536, false); + auto decoder_out = modules::QwenCausalDecoderModule(make_qwen_decoder_config(config)) + .build_static_cache_tail( + ctx, + graph_, + x, + positions, + make_qwen_decoder_weights(weights), + cache_steps_, + attention_mask, + cache_slot); + step_cache_ = std::move(decoder_out.cache); + logits_ = decoder_out.logits.tensor; + ggml_set_output(logits_); + ggml_build_forward_expand(graph_, logits_); + buffer_ = ggml_backend_alloc_ctx_tensors(ctx_.get(), runtime_->backend()); + if (buffer_ == nullptr) { + engine::core::trim_backend_pools(runtime_->backend()); + buffer_ = ggml_backend_alloc_ctx_tensors(ctx_.get(), runtime_->backend()); + } + if (buffer_ == nullptr) { + throw engine::runtime::CapacityError( + "VibeASR LM decode graph does not fit in device memory at " + + std::to_string(cache_steps_) + " cache steps"); + } + attention_mask_values_.assign(static_cast(cache_steps_), ggml_fp32_to_fp16(-INFINITY)); + debug::timing_log_scalar("vibeasr.lm.decode.graph.build_ms", engine::debug::elapsed_ms(build_start, Clock::now())); + debug::trace_log_scalar("vibeasr.lm.decode_cache_steps", cache_steps_); + } + + ~DecodeGraph() { + engine::core::release_backend_graph_resources(runtime_->backend(), graph_, true); + if (buffer_ != nullptr) { + ggml_backend_buffer_free(buffer_); + } + } + + bool can_run(const LmWeightsRuntime & runtime, int64_t required_steps) const { + return runtime_.get() == &runtime && cache_steps_ >= required_steps; + } + + void import_state(const runtime::TransformerKVState & state) { + step_cache_.import_state(state); + } + + std::vector run_step(int32_t token) { + const auto & config = runtime_->config(); + if (step_cache_.valid_steps() >= cache_steps_) { + throw std::runtime_error("VibeASR LM decode cache exhausted"); + } + ggml_backend_tensor_set(token_id_, &token, 0, sizeof(int32_t)); + const int32_t position = static_cast(step_cache_.current_end()); + ggml_backend_tensor_set(positions_, &position, 0, sizeof(int32_t)); + const int32_t cache_slot = static_cast(step_cache_.valid_steps()); + ggml_backend_tensor_set(cache_slot_, &cache_slot, 0, sizeof(int32_t)); + modules::write_qwen_cached_step_mask( + attention_mask_, + attention_mask_values_, + cache_steps_, + step_cache_.valid_steps(), + step_cache_.valid_steps()); + core::set_backend_threads(runtime_->backend(), runtime_->threads()); + const ggml_status status = engine::core::compute_backend_graph(runtime_->backend(), graph_); + ggml_backend_synchronize(runtime_->backend()); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("VibeASR LM decode graph compute failed"); + } + logits_buffer_.resize(static_cast(config.vocab_size)); + ggml_backend_tensor_get(logits_, logits_buffer_.data(), 0, logits_buffer_.size() * sizeof(float)); + step_cache_.advance_after_direct_append(1); + // The caller moves out of this buffer before the next step. + return std::move(logits_buffer_); + } + +private: + std::shared_ptr runtime_; + int64_t cache_steps_ = 0; + std::unique_ptr ctx_; + ggml_tensor * token_id_ = nullptr; + ggml_tensor * positions_ = nullptr; + ggml_tensor * cache_slot_ = nullptr; + ggml_tensor * attention_mask_ = nullptr; + ggml_tensor * logits_ = nullptr; + std::vector attention_mask_values_; + std::vector logits_buffer_; + runtime::TransformerKVCache step_cache_; + ggml_cgraph * graph_ = nullptr; + ggml_backend_buffer_t buffer_ = nullptr; +}; + +} // namespace + +struct VibeASRLmRuntime::Impl { + Impl( + std::shared_ptr weights_source, + const VibeASRLmConfig & config, + core::ExecutionContext & execution, + size_t prefill_graph_arena_bytes, + size_t decode_graph_arena_bytes, + size_t weight_context_bytes) + : weights(std::make_shared( + std::move(weights_source), + config, + execution, + weight_context_bytes)), + prefill_graph_arena_bytes(prefill_graph_arena_bytes), + decode_graph_arena_bytes(decode_graph_arena_bytes) {} + + void validate_speech(const VibeASRLmPrompt & prompt, const VibeASRSpeechEmbeddings & speech) const { + const auto & config = weights->config(); + if (speech.tokens > 0 && speech.hidden_size != config.hidden_size) { + throw std::runtime_error("VibeASR speech embedding hidden size mismatch"); + } + if (speech.tokens != static_cast(prompt.speech_positions.size())) { + throw std::runtime_error("VibeASR speech embedding count does not match the prompt's speech pads"); + } + if (static_cast(speech.values.size()) != speech.tokens * speech.hidden_size) { + throw std::runtime_error("VibeASR speech embedding value count mismatch"); + } + for (const int32_t position : prompt.speech_positions) { + if (position < 0 || position >= static_cast(prompt.input_ids.size())) { + throw std::runtime_error("VibeASR speech pad position out of range"); + } + } + } + + std::shared_ptr weights; + size_t prefill_graph_arena_bytes = 0; + size_t decode_graph_arena_bytes = 0; + std::unique_ptr prefill_graph; + std::unique_ptr decode_graph; +}; + +VibeASRLmRuntime::VibeASRLmRuntime( + std::shared_ptr weights_source, + const VibeASRLmConfig & config, + core::ExecutionContext & execution, + size_t prefill_graph_arena_bytes, + size_t decode_graph_arena_bytes, + size_t weight_context_bytes) + : impl_(std::make_unique( + std::move(weights_source), + config, + execution, + prefill_graph_arena_bytes, + decode_graph_arena_bytes, + weight_context_bytes)) {} + +VibeASRLmRuntime::~VibeASRLmRuntime() = default; + +std::vector VibeASRLmRuntime::generate( + const VibeASRLmPrompt & prompt, + const VibeASRSpeechEmbeddings & speech, + const VibeASRGenerationOptions & options) { + const auto & config = impl_->weights->config(); + if (prompt.input_ids.empty()) { + throw std::runtime_error("VibeASR LM prompt is empty"); + } + if (options.max_new_tokens <= 0) { + throw std::runtime_error("VibeASR max_new_tokens must be positive"); + } + const int64_t prompt_steps = static_cast(prompt.input_ids.size()); + if (prompt_steps + options.max_new_tokens > config.max_position_embeddings) { + throw std::runtime_error("VibeASR request exceeds the decoder context length"); + } + impl_->validate_speech(prompt, speech); + + if (impl_->prefill_graph == nullptr || + !impl_->prefill_graph->matches(*impl_->weights, prompt_steps, speech.tokens)) { + impl_->prefill_graph.reset(); + impl_->prefill_graph = std::make_unique( + impl_->weights, prompt_steps, speech.tokens, impl_->prefill_graph_arena_bytes); + } + auto prefill = impl_->prefill_graph->run(prompt.input_ids, speech.values, prompt.speech_positions); + + const int64_t required_cache_steps = prompt_steps + options.max_new_tokens; + if (impl_->decode_graph == nullptr || !impl_->decode_graph->can_run(*impl_->weights, required_cache_steps)) { + impl_->decode_graph.reset(); + impl_->decode_graph = + std::make_unique(impl_->weights, required_cache_steps, impl_->decode_graph_arena_bytes); + } + impl_->decode_graph->import_state(prefill.kv_state); + + const auto is_eos = [&options](int32_t token) { + return std::find(options.eos_token_ids.begin(), options.eos_token_ids.end(), token) != + options.eos_token_ids.end(); + }; + + std::vector out; + std::vector logits = std::move(prefill.logits); + const auto decode_start = Clock::now(); + for (int64_t step = 0; step < options.max_new_tokens; ++step) { + const int32_t token = argmax_index(logits); + if (is_eos(token)) { + break; + } + out.push_back(token); + logits = impl_->decode_graph->run_step(token); + } + debug::timing_log_scalar("vibeasr.lm.decode_total_ms", engine::debug::elapsed_ms(decode_start, Clock::now())); + return out; +} + +} // namespace engine::community_models::vibeasr diff --git a/src/community_models/vibeasr/session.cpp b/src/community_models/vibeasr/session.cpp new file mode 100644 index 000000000..b4d4b7989 --- /dev/null +++ b/src/community_models/vibeasr/session.cpp @@ -0,0 +1,368 @@ +#include "engine/community_models/vibeasr/session.h" + +#include "engine/framework/audio/conversion.h" +#include "engine/framework/audio/resampling.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/debug/trace.h" +#include "engine/framework/io/text.h" +#include "engine/framework/runtime/options.h" +#include "engine/framework/runtime/spec_backed_model.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::community_models::vibeasr { +namespace { + +using Clock = std::chrono::steady_clock; + +constexpr size_t kWeightContextBytes = 64ull * 1024ull * 1024ull; + +// VibeASR resamples to 24 kHz and RMS-normalizes to -25 dBFS before the encoder; +// both numbers are fixed in the reference implementation, not in the checkpoint. +constexpr int kSampleRate = 24000; +constexpr float kTargetDbFs = -25.0F; +constexpr float kNormalizeEps = 1.0e-6F; + +// Canonical HuggingFace ids for the VibeVoice special tokens. VibeASR inserts +// them numerically rather than through the tokenizer, because the GGUF vocab's +// text for these slots is Qwen2.5's original <|object_ref_start|> family while +// the embedding rows are the ones VibeVoice trained. +constexpr int32_t kEndOfText = 151643; +constexpr int32_t kImStart = 151644; +constexpr int32_t kImEnd = 151645; +constexpr int32_t kSpeechStart = 151646; +constexpr int32_t kSpeechEnd = 151647; +constexpr int32_t kSpeechPad = 151648; + +constexpr const char * kSystemPrompt = + "You are a helpful assistant that transcribes audio input into text output in JSON format."; + +std::shared_ptr require_assets(std::shared_ptr assets) { + if (assets == nullptr) { + throw std::runtime_error("VibeASR session requires assets"); + } + return assets; +} + +const engine::model_spec::ModelContract & require_contract( + const std::shared_ptr & contract) { + if (contract == nullptr) { + throw std::runtime_error("VibeASR session requires a model contract"); + } + return *contract; +} + +runtime::SessionOptions validate_session_setup( + const runtime::TaskSpec & task, + runtime::SessionOptions options, + const engine::model_spec::ModelContract & contract) { + if (task.task != runtime::VoiceTaskKind::Asr) { + throw std::runtime_error("VibeASR only supports VoiceTaskKind::Asr"); + } + if (task.mode != runtime::RunMode::Offline) { + throw std::runtime_error("VibeASR only supports offline sessions"); + } + runtime::validate_spec_backed_session_options(options, contract, "vibeasr", "VibeASR"); + return options; +} + +size_t encoder_graph_arena_bytes(const runtime::SessionOptions & options) { + return runtime::parse_size_mb_option( + options.options, {"vibeasr.encoder_graph_arena_mb"}, 64ull * 1024ull * 1024ull); +} + +size_t prefill_graph_arena_bytes(const runtime::SessionOptions & options) { + return runtime::parse_size_mb_option( + options.options, {"vibeasr.prefill_graph_arena_mb"}, 256ull * 1024ull * 1024ull); +} + +size_t decode_graph_arena_bytes(const runtime::SessionOptions & options) { + return runtime::parse_size_mb_option( + options.options, {"vibeasr.decode_graph_arena_mb"}, 256ull * 1024ull * 1024ull); +} + +std::shared_ptr load_tokenizer(const VibeASRAssets & assets) { + // No merges.txt in the published package, so the tokenizer comes from + // tokenizer.json alone. + return engine::tokenizers::load_llama_bpe_tokenizer(engine::tokenizers::LlamaBpeTokenizerSpec{ + {}, + {}, + assets.resources.require_file("tokenizer_config"), + assets.resources.require_file("tokenizer_json"), + engine::tokenizers::LlamaBpePreTokenizer::Qwen2, + }); +} + +std::string format_duration(float seconds) { + char buffer[64]; + std::snprintf(buffer, sizeof(buffer), "%.2f", static_cast(seconds)); + return std::string(buffer); +} + +} // namespace + +VibeASRSession::VibeASRSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract) + : RuntimeSessionBase(validate_session_setup(task, std::move(options), require_contract(contract))), + task_(std::move(task)), + assets_(require_assets(std::move(assets))), + contract_(std::move(contract)), + tokenizer_(load_tokenizer(*assets_)), + encoder_(assets_->vae, execution_context(), encoder_graph_arena_bytes(RuntimeSessionBase::options())), + lm_(assets_->lm_weights, + assets_->lm, + execution_context(), + prefill_graph_arena_bytes(RuntimeSessionBase::options()), + decode_graph_arena_bytes(RuntimeSessionBase::options()), + kWeightContextBytes) { + // Both weight stores have uploaded by now; drop the resident file blobs. + assets_->vae->source->release_storage(); + assets_->lm_weights->release_storage(); +} + +VibeASRSession::~VibeASRSession() = default; + +std::string VibeASRSession::family() const { + return "vibeasr"; +} + +runtime::VoiceTaskKind VibeASRSession::task_kind() const { + return task_.task; +} + +runtime::RunMode VibeASRSession::run_mode() const { + return task_.mode; +} + +void VibeASRSession::prepare(const runtime::SessionPreparationRequest & request) { + (void)request; + mark_prepared(); +} + +VibeASRSession::RequestOptions VibeASRSession::parse_request_options(const runtime::TaskRequest & request) const { + runtime::validate_spec_backed_request_options(request.options, require_contract(contract_), "VibeASR"); + RequestOptions out; + if (const auto value = runtime::find_option(request.options, {"output_format"}); value.has_value()) { + if (*value != "text" && *value != "json") { + throw std::runtime_error("VibeASR output_format must be text or json"); + } + out.output_format = *value; + } + if (const auto value = runtime::find_option(request.options, {"context"}); value.has_value()) { + out.context = *value; + } + out.max_new_tokens = runtime::parse_positive_i64_option(request.options, {"max_new_tokens"}, out.max_new_tokens); + return out; +} + +runtime::AudioBuffer VibeASRSession::normalize(const runtime::AudioBuffer & audio) const { + if (audio.samples.empty()) { + throw std::runtime_error("VibeASR requires non-empty audio"); + } + auto mono = engine::audio::mixdown_interleaved_to_mono_average(audio.samples, audio.channels); + if (audio.sample_rate != kSampleRate) { + // VibeASR.cpp resamples with a naive linear kernel; audio.cpp's soxr path + // is the better filter, so a non-24 kHz input will not match the + // reference sample for sample. + engine::audio::SoxrResampleOptions options; + options.profile = engine::audio::SoxrResampleProfile::QualityOnly; + options.output_length_policy = engine::audio::SoxrOutputLengthPolicy::ExactExpected; + options.output_padding = 256; + options.reject_empty_output = true; + options.warning_context = "VibeASR audio"; + options.fallback_description = "linear resampling"; + mono = engine::audio::resample_mono_soxr_or_linear(mono, audio.sample_rate, kSampleRate, options); + } + double sum = 0.0; + for (const float sample : mono) { + sum += static_cast(sample) * static_cast(sample); + } + const float rms = std::sqrt(static_cast(sum / std::max(mono.size(), 1))); + if (rms >= kNormalizeEps) { + const float target = std::pow(10.0F, kTargetDbFs / 20.0F); + const float gain = target / (rms + kNormalizeEps); + float max_abs = 0.0F; + for (float & sample : mono) { + sample *= gain; + max_abs = std::max(max_abs, std::abs(sample)); + } + // Not in VibeASR.cpp, which can clip on a loud clip; audio.cpp's own + // vibevoice_asr frontend clamps here and this port follows it. + if (max_abs > 1.0F) { + const float scale = max_abs + kNormalizeEps; + for (float & sample : mono) { + sample /= scale; + } + } + } + return runtime::AudioBuffer{kSampleRate, 1, std::move(mono)}; +} + +VibeASRSpeechEmbeddings VibeASRSession::encode_speech(const std::vector & samples) { + const auto encode_start = Clock::now(); + const auto acoustic = encoder_.encode_acoustic(samples); + const auto semantic = encoder_.encode_semantic(samples); + debug::timing_log_scalar("vibeasr.session.encoder_ms", engine::debug::elapsed_ms(encode_start)); + if (acoustic.frames != semantic.frames || acoustic.dim != semantic.dim) { + throw std::runtime_error("VibeASR encoder branches disagree on the feature shape"); + } + if (acoustic.dim != assets_->lm.hidden_size) { + throw std::runtime_error("VibeASR connector width does not match the decoder hidden size"); + } + + // Both connectors are LM-width, so the reference sums them element-wise. + VibeASRSpeechEmbeddings out; + out.tokens = acoustic.frames; + out.hidden_size = acoustic.dim; + out.values.resize(acoustic.values.size()); + for (size_t i = 0; i < out.values.size(); ++i) { + out.values[i] = acoustic.values[i] + semantic.values[i]; + } + return out; +} + +VibeASRLmPrompt VibeASRSession::build_prompt( + int64_t speech_tokens, + float duration_seconds, + const RequestOptions & options) const { + // Qwen2.5 ChatML, assembled exactly as VibeASR.cpp does it: + // <|im_start|>system\n{SYSTEM}<|im_end|>\n + // <|im_start|>user\n<|speech_start|><|speech_pad|>xN<|speech_end|>{suffix}<|im_end|>\n + // There is deliberately no generation prompt -- the model emits the + // <|im_start|>assistant\n header itself. + const auto encode = [this](const std::string & text) { + return tokenizer_->encode(text, false); + }; + + const std::string instruction = options.output_format == "json" + ? "please transcribe it with these keys: Start, End, Speaker, Content" + : "please transcribe it."; + std::string suffix; + if (options.context.empty()) { + suffix = "\nThis is a " + format_duration(duration_seconds) + " seconds audio, " + instruction; + } else { + suffix = "\nThis is a " + format_duration(duration_seconds) + " seconds audio, with extra info: " + + options.context + "\n\n" + + (options.output_format == "json" + ? "Please transcribe it with these keys: Start, End, Speaker, Content" + : "Please transcribe it."); + } + + const auto system_content = encode(std::string("system\n") + kSystemPrompt); + const auto newline = encode("\n"); + const auto user_prefix = encode("user\n"); + const auto user_suffix = encode(suffix); + + VibeASRLmPrompt prompt; + const auto append = [&prompt](const std::vector & ids) { + prompt.input_ids.insert(prompt.input_ids.end(), ids.begin(), ids.end()); + }; + prompt.input_ids.push_back(kImStart); + append(system_content); + prompt.input_ids.push_back(kImEnd); + append(newline); + prompt.input_ids.push_back(kImStart); + append(user_prefix); + prompt.input_ids.push_back(kSpeechStart); + // The reference builds ceil(samples / 3200) pads but only prefills + // min(pads, frames) of them, so emitting exactly `frames` pads produces the + // same sequence. + for (int64_t i = 0; i < speech_tokens; ++i) { + prompt.speech_positions.push_back(static_cast(prompt.input_ids.size())); + prompt.input_ids.push_back(kSpeechPad); + } + prompt.input_ids.push_back(kSpeechEnd); + append(user_suffix); + prompt.input_ids.push_back(kImEnd); + append(newline); + return prompt; +} + +std::string VibeASRSession::decode_tokens(const std::vector & token_ids) const { + // The prompt carries no generation prompt, so the model emits its own + // "<|im_start|>assistant\n" header; drop it exactly as the reference does. + size_t begin = 0; + const auto piece = [this](int32_t id) { return tokenizer_->decode({id}, true); }; + if (!token_ids.empty() && token_ids[0] == kImStart) { + begin = 1; + if (begin < token_ids.size() && piece(token_ids[begin]) == "assistant") { + ++begin; + if (begin < token_ids.size() && piece(token_ids[begin]) == "\n") { + ++begin; + } + } + } + + std::vector filtered; + filtered.reserve(token_ids.size() - begin); + for (size_t i = begin; i < token_ids.size(); ++i) { + const int32_t id = token_ids[i]; + if (id == kSpeechPad || id == kSpeechStart || id == kSpeechEnd || id == kEndOfText || + tokenizer_->is_control_token_id(id)) { + continue; + } + filtered.push_back(id); + } + if (filtered.empty()) { + return ""; + } + return engine::io::trim_ascii_whitespace(tokenizer_->decode(filtered, true)); +} + +runtime::TaskResult VibeASRSession::run(const runtime::TaskRequest & request) { + require_prepared("VibeASR run()"); + if (!request.audio_input.has_value()) { + throw std::runtime_error("VibeASR run() requires audio_input"); + } + const auto wall_start = Clock::now(); + const auto options = parse_request_options(request); + const auto audio = normalize(*request.audio_input); + const float duration_seconds = + static_cast(audio.samples.size()) / static_cast(kSampleRate); + + auto speech = encode_speech(audio.samples); + if (speech.tokens <= 0) { + throw std::runtime_error("VibeASR audio is too short to produce a single encoder frame"); + } + const auto prompt = build_prompt(speech.tokens, duration_seconds, options); + + VibeASRGenerationOptions generation; + generation.max_new_tokens = options.max_new_tokens; + generation.eos_token_ids = {kImEnd, kEndOfText}; + const auto generated = lm_.generate(prompt, speech, generation); + + runtime::TaskResult result; + result.text_output = runtime::Transcript{decode_tokens(generated), ""}; + debug::trace_log_scalar("vibeasr.session.speech_tokens", speech.tokens); + debug::trace_log_scalar("vibeasr.session.generated_tokens", static_cast(generated.size())); + debug::timing_log_scalar("session.wall_ms", engine::debug::elapsed_ms(wall_start)); + return result; +} + +std::shared_ptr make_vibeasr_loader() { + runtime::SpecBackedVoiceModelConfig config; + config.family = "vibeasr"; + config.load_assets = [](const std::filesystem::path & model_path) { + return load_vibeasr_assets(model_path); + }; + config.create_session = []( + const runtime::TaskSpec & task, + const runtime::SessionOptions & options, + std::shared_ptr assets, + std::shared_ptr contract) { + return std::make_unique(task, options, std::move(assets), std::move(contract)); + }; + return runtime::make_spec_backed_voice_loader(std::move(config)); +} + +} // namespace engine::community_models::vibeasr diff --git a/src/community_models/vibeasr/vae_encoder.cpp b/src/community_models/vibeasr/vae_encoder.cpp new file mode 100644 index 000000000..2894fd119 --- /dev/null +++ b/src/community_models/vibeasr/vae_encoder.cpp @@ -0,0 +1,379 @@ +#include "engine/community_models/vibeasr/vae_encoder.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +// The in-band tensor scale of GGML_TYPE_I8_S is internal to ggml, so the +// waveform quantizer and the feature dequantizer reach for the same declarations +// the implementation uses rather than re-deriving the layout here. Buffer sizes +// still come from the public ggml_nbytes(), which already accounts for the +// trailing scale. +extern "C" { +void ggml_i8_s_to_float (const void * x, float * y, int64_t n); +size_t ggml_i8_s_from_float(const float * x, void * y, int64_t n); +} + +namespace engine::community_models::vibeasr { +namespace { + +// The graph is ~530 nodes for the published 7-stage encoder; leave headroom for +// deeper stage stacks without making the arena reservation depend on the config. +constexpr size_t kGraphNodes = 8192; + +// Activation layout inside this file is described in ggml `ne` order, which is +// the reverse of core::TensorShape. The encoder alternates between two layouts: +// +// channel-major ne [C, L] -- what a matmul produces, what the norms and the +// FFN want, since they reduce over ne[0] +// length-major ne [L, C] -- what im2col wants, since it slides over ne[0] +// +// VibeASR's graph flips between them with permute + cont in exactly the places +// reproduced below. + +core::TensorValue load_i8_s_tensor( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & name, + const std::vector & expected_shape) { + const auto metadata = source.require_metadata(name); + if (metadata.dtype != "i8_s") { + throw std::runtime_error("VibeASR VAE tensor " + name + " is " + metadata.dtype + ", expected i8_s"); + } + if (metadata.shape != expected_shape) { + throw std::runtime_error("VibeASR VAE tensor " + name + " has an unexpected shape"); + } + + core::TensorShape shape; + shape.rank = expected_shape.size(); + for (size_t i = 0; i < shape.rank; ++i) { + shape.dims[i] = expected_shape[i]; + } + + // I8_S is a whole-tensor quantization: the GGUF payload is the int8 values + // followed by one padded F32 scale, which is exactly what ggml_nbytes() + // expects, so the bytes go to the backend untouched. + const auto raw = source.require_tensor_data(name); + return store.make_tensor(shape, GGML_TYPE_I8_S, raw.bytes.data(), raw.bytes.size()); +} + +VaeBlockWeights load_block_weights( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + const VaeBlockConfig & config) { + const int64_t channels = config.channels; + const int64_t hidden = config.ffn_hidden; + + VaeBlockWeights weights; + weights.mixer_norm = store.load_f32_tensor(source, prefix + ".norm.weight", {channels}); + weights.mixer_conv_weight = load_i8_s_tensor( + store, source, prefix + ".mixer.conv.conv.conv.weight", {channels, 1, config.kernel_size}); + weights.mixer_conv_bias = store.load_f32_tensor(source, prefix + ".mixer.conv.conv.conv.bias", {channels}); + weights.mixer_gamma = store.load_f32_tensor(source, prefix + ".gamma", {channels}); + weights.ffn_norm = store.load_f32_tensor(source, prefix + ".ffn_norm.weight", {channels}); + weights.ffn_fc1_weight = load_i8_s_tensor(store, source, prefix + ".ffn.linear1.weight", {hidden, channels}); + weights.ffn_fc1_bias = store.load_f32_tensor(source, prefix + ".ffn.linear1.bias", {hidden}); + weights.ffn_fc2_weight = load_i8_s_tensor(store, source, prefix + ".ffn.linear2.weight", {channels, hidden}); + weights.ffn_fc2_bias = store.load_f32_tensor(source, prefix + ".ffn.linear2.bias", {channels}); + weights.ffn_gamma = store.load_f32_tensor(source, prefix + ".ffn_gamma", {channels}); + return weights; +} + +VaeBranchWeights load_branch_weights( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const VaeBranchConfig & config) { + VaeBranchWeights weights; + weights.stages.reserve(config.stages.size()); + + for (size_t stage = 0; stage < config.stages.size(); ++stage) { + const auto & stage_config = config.stages[stage]; + const std::string stage_prefix = config.prefix + ".stages." + std::to_string(stage); + const std::string downsample_prefix = + config.prefix + ".downsample_layers." + std::to_string(stage) + ".0.conv.conv"; + + VaeStageWeights stage_weights; + stage_weights.downsample_weight = load_i8_s_tensor( + store, + source, + downsample_prefix + ".weight", + {stage_config.out_channels, stage_config.in_channels, stage_config.downsample_kernel_size}); + stage_weights.downsample_bias = + store.load_f32_tensor(source, downsample_prefix + ".bias", {stage_config.out_channels}); + stage_weights.blocks.reserve(stage_config.blocks.size()); + for (size_t block = 0; block < stage_config.blocks.size(); ++block) { + stage_weights.blocks.push_back(load_block_weights( + store, source, stage_prefix + "." + std::to_string(block), stage_config.blocks[block])); + } + weights.stages.push_back(std::move(stage_weights)); + } + + const int64_t last_channels = config.stages.back().out_channels; + weights.head_weight = load_i8_s_tensor( + store, source, config.prefix + ".head.conv.conv.weight", + {config.latent_dim, last_channels, config.head_kernel_size}); + weights.head_bias = store.load_f32_tensor(source, config.prefix + ".head.conv.conv.bias", {config.latent_dim}); + + const std::string connector = config.prefix + "_connector"; + weights.connector_fc1_weight = load_i8_s_tensor( + store, source, connector + ".fc1.weight", {config.connector_hidden, config.latent_dim}); + weights.connector_fc1_bias = store.load_f32_tensor(source, connector + ".fc1.bias", {config.connector_hidden}); + weights.connector_norm = store.load_f32_tensor(source, connector + ".norm.weight", {config.connector_hidden}); + weights.connector_fc2_weight = load_i8_s_tensor( + store, source, connector + ".fc2.weight", {config.connector_hidden, config.connector_hidden}); + weights.connector_fc2_bias = store.load_f32_tensor(source, connector + ".fc2.bias", {config.connector_hidden}); + return weights; +} + +// channel-major [C, L] -> length-major [L, C], and back. +ggml_tensor * transpose_layout(core::ModuleBuildContext & ctx, ggml_tensor * x) { + return ggml_cont(ctx.ggml, ggml_permute(ctx.ggml, x, 1, 0, 2, 3)); +} + +// x [C, L], gamma [C] -> [C, L]. Reduces over the channel axis, matching the +// channels-last RMSNorm of the reference implementation. +ggml_tensor * rms_norm(core::ModuleBuildContext & ctx, ggml_tensor * x, ggml_tensor * gamma, float eps) { + return ggml_rms_norm_scaled(ctx.ggml, x, gamma, eps); +} + +// x [IC, L], w [IC, OC], bias [OC] -> [OC, L]. +// +// Everything is flattened to 2D for the matmul, so the trailing ne of x carry no +// information beyond the total number of positions. +ggml_tensor * linear( + core::ModuleBuildContext & ctx, + ggml_tensor * x, + ggml_tensor * w, + ggml_tensor * bias, + bool fuse_relu) { + GGML_ASSERT(x->ne[3] == 1); + const int64_t in_features = x->ne[0]; + const int64_t out_features = w->ne[1]; + const int64_t positions = x->ne[1] * x->ne[2]; + + ggml_tensor * flat = ggml_reshape_2d(ctx.ggml, x, in_features, positions); + ggml_tensor * out = fuse_relu ? ggml_mul_mat_add_relu(ctx.ggml, w, flat, bias) + : ggml_mul_mat_add(ctx.ggml, w, flat, bias); + return ggml_reshape_2d(ctx.ggml, out, out_features, positions); +} + +// Causal Conv1d. x [L, IC, 1], w [K, IC, OC], bias [OC] -> [OC, OW]. +// +// The left pad is K - stride and the right pad is zero, which is what makes the +// stack causal; the converter left-pads short kernels with zeros so the padded +// K stays exact. +ggml_tensor * conv1d_causal( + core::ModuleBuildContext & ctx, + ggml_tensor * x, + ggml_tensor * w, + ggml_tensor * bias, + int stride) { + const int64_t kernel_size = w->ne[0]; + const int64_t in_channels = w->ne[1]; + const int64_t out_channels = w->ne[2]; + const int left_pad = static_cast(kernel_size) - stride; + GGML_ASSERT(left_pad >= 0); + + // im2col gives [IC*K, OW, N]. + ggml_tensor * cols = ggml_im2col_asym( + ctx.ggml, w, x, stride, 0, /*lp0=*/left_pad, /*rp0=*/0, /*p1=*/0, /*d0=*/1, /*d1=*/0, + /*is_2D=*/false, GGML_TYPE_I8_S); + + ggml_tensor * w2d = ggml_reshape_2d(ctx.ggml, w, kernel_size * in_channels, out_channels); + ggml_tensor * cols2d = ggml_reshape_2d(ctx.ggml, cols, cols->ne[0], cols->ne[1] * cols->ne[2]); + return ggml_mul_mat_add(ctx.ggml, w2d, cols2d, bias); +} + +// Causal depthwise Conv1d. x [L, C], w [K, 1, C], bias [C] -> [C, L]. +// +// ggml_mul_mat_add takes its depthwise contraction path when the weight is +// [K, 1, C], producing [1, L, C]; the trailing reshape and permute fold that +// back to channel-major. +ggml_tensor * conv1d_dw_causal( + core::ModuleBuildContext & ctx, + ggml_tensor * x, + ggml_tensor * w, + ggml_tensor * bias) { + const int64_t kernel_size = w->ne[0]; + + ggml_tensor * x4d = ggml_reshape_4d(ctx.ggml, x, x->ne[0], 1, x->ne[1], 1); + ggml_tensor * cols = ggml_im2col_asym( + ctx.ggml, w, x4d, /*s0=*/1, 0, /*lp0=*/static_cast(kernel_size) - 1, /*rp0=*/0, /*p1=*/0, + /*d0=*/1, /*d1=*/0, /*is_2D=*/false, GGML_TYPE_I8_S); + + ggml_tensor * out = ggml_mul_mat_add(ctx.ggml, w, cols, bias); + out = ggml_reshape_3d(ctx.ggml, out, out->ne[1], out->ne[2], 1); + return transpose_layout(ctx, out); +} + +// One ConvNeXt block. x [C, L] -> [C, L]. +ggml_tensor * build_block( + core::ModuleBuildContext & ctx, + ggml_tensor * x, + const VaeBlockWeights & weights, + float eps) { + ggml_tensor * residual = x; + ggml_tensor * h = rms_norm(ctx, x, weights.mixer_norm.tensor, eps); + h = transpose_layout(ctx, h); + h = conv1d_dw_causal(ctx, h, weights.mixer_conv_weight.tensor, weights.mixer_conv_bias.tensor); + // LayerScale folded into the residual add: h * gamma + residual. + x = ggml_add_scaled(ctx.ggml, h, residual, weights.mixer_gamma.tensor); + + residual = x; + h = rms_norm(ctx, x, weights.ffn_norm.tensor, eps); + // The I8_S FFN uses ReLU, fused into the first matmul. VibeASR's F32 + // fallback uses GELU instead; only the quantized path has published weights, + // so only ReLU is ported. + h = linear(ctx, h, weights.ffn_fc1_weight.tensor, weights.ffn_fc1_bias.tensor, /*fuse_relu=*/true); + h = linear(ctx, h, weights.ffn_fc2_weight.tensor, weights.ffn_fc2_bias.tensor, /*fuse_relu=*/false); + return ggml_add_scaled(ctx.ggml, h, residual, weights.ffn_gamma.tensor); +} + +// waveform [n_samples, 1, 1] -> features [connector_hidden, frames]. +ggml_tensor * build_branch( + core::ModuleBuildContext & ctx, + ggml_tensor * waveform, + const VaeBranchConfig & config, + const VaeBranchWeights & weights, + float eps) { + ggml_tensor * x = waveform; + + for (size_t stage = 0; stage < config.stages.size(); ++stage) { + const auto & stage_weights = weights.stages[stage]; + x = conv1d_causal( + ctx, + x, + stage_weights.downsample_weight.tensor, + stage_weights.downsample_bias.tensor, + static_cast(config.stages[stage].downsample_stride)); + for (const auto & block : stage_weights.blocks) { + x = build_block(ctx, x, block, eps); + } + // Back to length-major for the next stage's im2col (and for the head). + x = transpose_layout(ctx, x); + } + + x = conv1d_causal(ctx, x, weights.head_weight.tensor, weights.head_bias.tensor, /*stride=*/1); + + x = linear(ctx, x, weights.connector_fc1_weight.tensor, weights.connector_fc1_bias.tensor, false); + x = rms_norm(ctx, x, weights.connector_norm.tensor, eps); + return linear(ctx, x, weights.connector_fc2_weight.tensor, weights.connector_fc2_bias.tensor, false); +} + +} // namespace + +VibeASRVaeEncoderRuntime::VibeASRVaeEncoderRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution_context, + size_t graph_arena_bytes) + : assets_(std::move(assets)), + execution_context_(&execution_context), + weight_store_( + execution_context.backend(), + execution_context.backend_type(), + "VibeASR VAE encoder weights", + 256ull * 1024ull * 1024ull), + graph_arena_bytes_(graph_arena_bytes) { + if (assets_ == nullptr) { + throw std::runtime_error("VibeASR VAE encoder runtime requires assets"); + } + weights_.acoustic = load_branch_weights(weight_store_, *assets_->source, assets_->config.acoustic); + weights_.semantic = load_branch_weights(weight_store_, *assets_->source, assets_->config.semantic); + weight_store_.upload(); +} + +VaeEncoderFeatures VibeASRVaeEncoderRuntime::encode_acoustic(const std::vector & samples) { + return encode(assets_->config.acoustic, weights_.acoustic, samples); +} + +VaeEncoderFeatures VibeASRVaeEncoderRuntime::encode_semantic(const std::vector & samples) { + return encode(assets_->config.semantic, weights_.semantic, samples); +} + +VaeEncoderFeatures VibeASRVaeEncoderRuntime::encode( + const VaeBranchConfig & config, + const VaeBranchWeights & weights, + const std::vector & samples) { + const int64_t num_samples = static_cast(samples.size()); + const int64_t expected_frames = config.frames_for_samples(num_samples); + if (expected_frames <= 0) { + // Shorter than one encoder frame: the first stage's im2col would have no + // output column at all. + return {}; + } + + ggml_init_params params{}; + params.mem_size = graph_arena_bytes_; + params.mem_buffer = nullptr; + params.no_alloc = true; + + ggml_context * ggml_ctx = ggml_init(params); + if (ggml_ctx == nullptr) { + throw std::runtime_error("Failed to initialize GGML context for the VibeASR VAE encoder"); + } + + ggml_gallocr * galloc = ggml_gallocr_new( + ggml_backend_get_default_buffer_type(execution_context_->backend())); + if (galloc == nullptr) { + ggml_free(ggml_ctx); + throw std::runtime_error("Failed to initialize GGML allocator for the VibeASR VAE encoder"); + } + + VaeEncoderFeatures features; + + try { + core::ModuleBuildContext ctx{ggml_ctx, "vibeasr_vae_encoder", execution_context_->backend_type()}; + + // The waveform enters the graph already quantized: the encoder never + // touches F32 activations, so there is no leading quantize node. + ggml_tensor * waveform = ggml_new_tensor_3d(ggml_ctx, GGML_TYPE_I8_S, num_samples, 1, 1); + ggml_set_input(waveform); + + ggml_tensor * out = build_branch(ctx, waveform, config, weights, assets_->config.rms_norm_eps); + ggml_set_output(out); + + ggml_cgraph * gf = ggml_new_graph_custom(ggml_ctx, kGraphNodes, false); + ggml_build_forward_expand(gf, out); + + if (!ggml_gallocr_alloc_graph(galloc, gf)) { + throw std::runtime_error("Failed to allocate the GGML graph for the VibeASR VAE encoder"); + } + + std::vector quantized(ggml_nbytes(waveform)); + ggml_i8_s_from_float(samples.data(), quantized.data(), num_samples); + ggml_backend_tensor_set(waveform, quantized.data(), 0, quantized.size()); + + if (ggml_backend_graph_compute(execution_context_->backend(), gf) != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Failed to compute the GGML graph for the VibeASR VAE encoder"); + } + + features.dim = out->ne[0]; + features.frames = out->ne[1]; + if (features.frames != expected_frames) { + throw std::runtime_error("VibeASR VAE encoder produced an unexpected frame count"); + } + + // The result is still I8_S, one scale for the whole feature block. + std::vector raw(ggml_nbytes(out)); + ggml_backend_tensor_get(out, raw.data(), 0, raw.size()); + features.values.resize(static_cast(features.dim * features.frames)); + ggml_i8_s_to_float(raw.data(), features.values.data(), features.dim * features.frames); + } catch (...) { + ggml_gallocr_free(galloc); + ggml_free(ggml_ctx); + throw; + } + + ggml_gallocr_free(galloc); + ggml_free(ggml_ctx); + return features; +} + +} // namespace engine::community_models::vibeasr diff --git a/src/framework/audio/flashsr.cpp b/src/framework/audio/flashsr.cpp index 7c959691d..2b7b1e271 100644 --- a/src/framework/audio/flashsr.cpp +++ b/src/framework/audio/flashsr.cpp @@ -14,8 +14,12 @@ #include #include #include +#include #include #include +#ifdef _OPENMP +#include +#endif #include #include #include @@ -318,6 +322,341 @@ std::vector normalize_output(const std::vector & input) { return output; } +// --------------------------------------------------------------------------- +// CPU fast path for the FlashSR U-Net (enabled by default; set +// AUDIOCPP_FLASHSR_DSP=0 to compare against the generic graph). +// +// The generic ggml graph expresses every convolution as im2col + mul_mat and +// every replicate padding as slice/repeat/concat, so a single utterance moves +// several GB of temporary data (profiled: ~2.4 s for a 3.2 s utterance on 32 +// threads, of which ~1 s per resblock). This class re-implements the same f32 +// math directly: zero-padded direct convolutions (no im2col), per-channel +// fused upsample+snake+downsample activation (no pad copies), and the exact +// ggml bilinear expression for the 3x interpolate. Numerically it matches the +// ggml path to ~1e-6 relative (identical f32 ops and sinf; only the +// convolution reduction order differs). + +struct FlashSrDspWeights { + std::vector conv_pre_w; // [32][7] + std::vector conv_pre_b; // [32] + std::vector conv_post_w; // [32][7] + struct Block { + int kernel = 0; + std::vector w1[3]; // [32][32][K] + std::vector b1[3]; // [32] + std::vector w2[3]; + std::vector b2[3]; + std::vector alpha[6]; // [32] + std::vector inv_beta[6]; // [32] + }; + Block block2; + Block block0; + std::vector alpha_post; // [32] + std::vector inv_beta_post; // [32] + std::vector filter; // [12] raw lowpass filter + std::vector upsample_w; // [12] = 2 * filter[11-k] + std::vector downsample_w; // [12] = filter[k] +}; + +inline std::vector read_f32_tensor(const core::TensorValue & value) { + std::vector out(static_cast(value.shape.num_elements())); + ggml_backend_tensor_get(value.tensor, out.data(), 0, out.size() * sizeof(float)); + return out; +} + +inline void flashsr_dsp_load_block( + FlashSrDspWeights::Block & dst, + const ResBlockWeights & src, + int kernel) { + dst.kernel = kernel; + for (int i = 0; i < 3; ++i) { + dst.w1[i] = read_f32_tensor(src.convs1[i].conv.weight); + dst.b1[i] = read_f32_tensor(*src.convs1[i].conv.bias); + dst.w2[i] = read_f32_tensor(src.convs2[i].conv.weight); + dst.b2[i] = read_f32_tensor(*src.convs2[i].conv.bias); + } + for (int i = 0; i < 6; ++i) { + dst.alpha[i] = read_f32_tensor(src.activations[i].alpha); + dst.inv_beta[i] = read_f32_tensor(src.activations[i].inv_beta); + } +} + +// Zero-padded direct 1D convolution over [C][n] channel-major buffers, in the +// standard torch/ggml convention for (odd) kernels with symmetric padding: +// out[oc][p] = bias[oc] + sum_ic sum_k w[oc][ic][k] * in[ic][p + dilation*(k - c)] +// where c = (kernel - 1) / 2 and out-of-range input positions read zero. The +// `pad` argument must equal dilation * c (true for every FlashSR convolution). +inline void conv1d_direct( + int64_t n, + int kernel, + int dilation, + int64_t pad, + const float * in, + const float * w, + const float * bias, + float * out) { + (void)pad; + constexpr int C = kFlashSrChannels; + constexpr int kMaxTaps = 11; + const int c = (kernel - 1) / 2; + #pragma omp parallel for schedule(static) + for (int64_t p0 = 0; p0 < n; p0 += 256) { + const int64_t p1 = std::min(p0 + 256, n); + for (int64_t p = p0; p < p1; ++p) { + int taps[kMaxTaps]; + int64_t src[kMaxTaps]; + int ns = 0; + for (int k = 0; k < kernel; ++k) { + const int64_t s = p + static_cast(dilation) * (k - c); + if (s < 0 || s >= n) { + continue; + } + taps[ns] = k; + src[ns] = s; + ++ns; + } + float window[C][kMaxTaps]; + for (int ic = 0; ic < C; ++ic) { + const float * row = in + static_cast(ic) * n; + for (int j = 0; j < ns; ++j) { + window[ic][j] = row[src[j]]; + } + } + for (int oc = 0; oc < C; ++oc) { + float acc = bias != nullptr ? bias[oc] : 0.0f; + for (int ic = 0; ic < C; ++ic) { + const float * wr = w + (static_cast(oc) * C + ic) * kernel; + for (int j = 0; j < ns; ++j) { + acc += wr[taps[j]] * window[ic][j]; + } + } + out[static_cast(oc) * n + p] = acc; + } + } + } +} + +// Replicates modules::Interpolate1dModule(Linear) == ggml BILINEAR for the +// [1][C][n] -> [1][C][3n] case: same scale factors and same arithmetic order +// as ggml_compute_forward_upscale_f32, so results match the ggml path bit-for-bit. +inline void interp_linear_3x(int64_t n, const float * in, float * out) { + constexpr int C = kFlashSrChannels; + #pragma omp parallel for schedule(static) + for (int64_t i0 = 0; i0 < n * 3; ++i0) { + const float x = (static_cast(i0) + 0.5f) / 3.0f - 0.5f; + int64_t x0 = static_cast(floorf(x)); + int64_t x1 = x0 + 1; + x0 = std::max(0, std::min(x0, n - 1)); + x1 = std::max(0, std::min(x1, n - 1)); + float dx = x - static_cast(x0); + dx = std::max(0.0f, std::min(dx, 1.0f)); + const float dy = 0.0f; + for (int c = 0; c < C; ++c) { + const float * row = in + static_cast(c) * n; + const float a = row[x0]; + const float b = row[x1]; + const float cc = row[x0]; + const float d = row[x1]; + out[static_cast(c) * n * 3 + i0] = + a * (1.0f - dx) * (1.0f - dy) + b * dx * (1.0f - dy) + cc * (1.0f - dx) * dy + d * dx * dy; + } + } +} + +// Fused per-channel activation1d: replicate-pad(5,5) -> convT(k=12, s=2, x2, +// diagonal) -> crop[15:-15] -> snake(x + sin^2(x*alpha)*inv_beta) +// -> replicate-pad(5,6) -> conv1d(k=12, s=2, diagonal). +// Buffers: in/out [C][n], up [C][2n+30], snake [C][2n]. +inline void activation1d_dsp( + int64_t n, + const float * in, + const float * alpha, + const float * inv_beta, + const float * upsample_w, + const float * downsample_w, + float * up, + float * snake, + float * out) { + // Full convT output length before the [15:-15] crop: 2*(n + 10) + (12 - 2) = 2n + 30. + const int64_t lu = 2 * n + 30; + #pragma omp parallel for schedule(static) + for (int c = 0; c < kFlashSrChannels; ++c) { + const float * x = in + static_cast(c) * n; + const int64_t lp = n + kFlashSrActivationKernel - 2; // replicate pad 5/5 + float * up_c = up + static_cast(c) * lu; + for (int64_t j = 0; j < lu; ++j) { + float acc = 0.0f; + for (int k = 0; k < kFlashSrActivationKernel; ++k) { + const int64_t t = j - k; + if (t < 0 || (t & 1) != 0) { + continue; + } + const int64_t idx = t / 2; + if (idx >= lp) { + continue; + } + // idx is a position in the replicate-padded input (pad 5/5): + // [0,5) -> x[0], [5, n+5) -> x[idx-5], [n+5, n+10) -> x[n-1]. + const int64_t sample = idx < 5 ? 0 : (idx >= n + 5 ? n - 1 : idx - 5); + acc += upsample_w[k] * x[sample]; + } + up_c[j] = acc; + } + const float * crop = up_c + 15; + float * s = snake + static_cast(c) * n * 2; + const float a = alpha[c]; + const float ib = inv_beta[c]; + for (int64_t m = 0; m < n * 2; ++m) { + const float u = crop[m]; + const float t1 = u * a; + const float t2 = sinf(t1); + const float t3 = t2 * t2; + s[m] = u + t3 * ib; + } + for (int64_t p = 0; p < n; ++p) { + float acc = 0.0f; + for (int k = 0; k < kFlashSrActivationKernel; ++k) { + int64_t idx = 2 * p + k - (kFlashSrActivationKernel / 2 - 1); + idx = idx < 0 ? 0 : (idx >= n * 2 ? n * 2 - 1 : idx); + acc += downsample_w[k] * s[idx]; + } + out[static_cast(c) * n + p] = acc; + } + } +} + +inline void flashsr_dsp_resblock( + int64_t n, + const FlashSrDspWeights::Block & block, + const float * x, + const FlashSrDspWeights & weights, + float * xt1, + float * xt2, + float * xt3, + float * up_buf, + float * snake_buf, + float * out) { + const int k = block.kernel; + constexpr int kDilations[3] = {1, 3, 5}; + std::memcpy(out, x, static_cast(kFlashSrChannels) * n * sizeof(float)); + for (int i = 0; i < 3; ++i) { + activation1d_dsp(n, out, block.alpha[i * 2].data(), block.inv_beta[i * 2].data(), + weights.upsample_w.data(), weights.downsample_w.data(), up_buf, snake_buf, xt1); + conv1d_direct(n, k, kDilations[i], (k * kDilations[i] - kDilations[i]) / 2, + xt1, block.w1[i].data(), block.b1[i].data(), xt2); + activation1d_dsp(n, xt2, block.alpha[i * 2 + 1].data(), block.inv_beta[i * 2 + 1].data(), + weights.upsample_w.data(), weights.downsample_w.data(), up_buf, snake_buf, xt3); + conv1d_direct(n, k, 1, (k - 1) / 2, xt3, block.w2[i].data(), block.b2[i].data(), xt1); + for (size_t i2 = 0; i2 < static_cast(kFlashSrChannels) * n; ++i2) { + out[i2] += xt1[i2]; + } + } +} + +class FlashSrDsp { +public: + explicit FlashSrDsp(const FlashSrWeights & weights) : weights_(make_weights(weights)) { +#ifdef _OPENMP + omp_set_num_threads(std::max(1, weights.threads)); +#endif + } + + // waveform: [n] 16 kHz mono. Returns raw 48 kHz output (3n samples, + // pre-normalization), matching FlashSrGraph::run. + std::vector run(const std::vector & waveform) const { + const int64_t n = static_cast(waveform.size()); + const int64_t l = n * 3; + constexpr size_t C = kFlashSrChannels; + std::vector x_pre(C * static_cast(n)); + std::vector x3(C * static_cast(l)); + std::vector xs(C * static_cast(l)); + std::vector xs0(C * static_cast(l)); + std::vector z(C * static_cast(l)); + std::vector xt1(C * static_cast(l)); + std::vector xt2(C * static_cast(l)); + std::vector xt3(C * static_cast(l)); + std::vector up_buf(C * static_cast(2 * l + 30)); + std::vector snake_buf(C * static_cast(2 * l)); + std::vector za(C * static_cast(l)); + std::vector out(static_cast(l)); + + // conv_pre: [n] -> [C][n], kernel 7, zero pad 3, with bias. + #pragma omp parallel for schedule(static) + for (int64_t p = 0; p < n; ++p) { + float win[7]; + for (int k = 0; k < 7; ++k) { + const int64_t idx = p - 3 + k; + win[k] = (idx >= 0 && idx < n) ? waveform[static_cast(idx)] : 0.0f; + } + for (int c = 0; c < kFlashSrChannels; ++c) { + float acc = weights_.conv_pre_b[static_cast(c)]; + for (int k = 0; k < 7; ++k) { + acc += weights_.conv_pre_w[static_cast(c) * 7 + k] * win[k]; + } + x_pre[static_cast(c) * n + p] = acc; + } + } + interp_linear_3x(n, x_pre.data(), x3.data()); + + flashsr_dsp_resblock(l, weights_.block2, x3.data(), weights_, xt1.data(), xt2.data(), xt3.data(), + up_buf.data(), snake_buf.data(), xs.data()); + flashsr_dsp_resblock(l, weights_.block0, x3.data(), weights_, xt1.data(), xt2.data(), xt3.data(), + up_buf.data(), snake_buf.data(), xs0.data()); + + for (size_t i = 0; i < z.size(); ++i) { + z[i] = (xs[i] + xs0[i]) * 0.5f; + } + activation1d_dsp(l, z.data(), weights_.alpha_post.data(), weights_.inv_beta_post.data(), + weights_.upsample_w.data(), weights_.downsample_w.data(), up_buf.data(), snake_buf.data(), za.data()); + + // conv_post: [C][l] -> [l], kernel 7, zero pad 3, no bias, then tanh. + #pragma omp parallel for schedule(static) + for (int64_t p = 0; p < l; ++p) { + float acc = 0.0f; + for (int c = 0; c < kFlashSrChannels; ++c) { + const float * row = za.data() + static_cast(c) * l; + for (int k = 0; k < 7; ++k) { + const int64_t idx = p - 3 + k; + if (idx >= 0 && idx < l) { + acc += weights_.conv_post_w[static_cast(c) * 7 + k] * row[idx]; + } + } + } + out[static_cast(p)] = tanhf(acc); + } + return out; + } + +private: + static FlashSrDspWeights make_weights(const FlashSrWeights & weights) { + FlashSrDspWeights w; + w.conv_pre_w = read_f32_tensor(weights.conv_pre.conv.weight); + w.conv_pre_b = read_f32_tensor(*weights.conv_pre.conv.bias); + w.conv_post_w = read_f32_tensor(weights.conv_post.conv.weight); + w.alpha_post = read_f32_tensor(weights.activation_post.alpha); + w.inv_beta_post = read_f32_tensor(weights.activation_post.inv_beta); + // Raw lowpass filter = diagonal of the downsample weight matrix. + // The store layout is [ic][oc][k] (see make_diagonal_filter_weights), so the + // channel-0 diagonal is simply the first kFlashSrActivationKernel values. + auto down = read_f32_tensor(weights.downsample_filter.weight); + w.filter.resize(kFlashSrActivationKernel); + for (int k = 0; k < kFlashSrActivationKernel; ++k) { + w.filter[static_cast(k)] = down[static_cast(k)]; + } + w.upsample_w.resize(kFlashSrActivationKernel); + w.downsample_w = w.filter; + for (int k = 0; k < kFlashSrActivationKernel; ++k) { + w.upsample_w[static_cast(k)] = + w.filter[static_cast(kFlashSrActivationKernel - 1 - k)] * static_cast(kFlashSrActivationRatio); + } + flashsr_dsp_load_block(w.block2, weights.resblock2, weights.resblock2.convs1[0].kernel); + flashsr_dsp_load_block(w.block0, weights.resblock0, weights.resblock0.convs1[0].kernel); + return w; + } + + FlashSrDspWeights weights_; +}; + class FlashSrGraph { public: FlashSrGraph(const FlashSrWeights & weights, int64_t input_samples) @@ -420,7 +759,17 @@ FlashSrModel FlashSrModel::load_from_directory(const std::filesystem::path & mod FlashSrModel FlashSrModel::load_from_directory( const std::filesystem::path & model_dir, const core::BackendConfig & backend_config) { - auto source = engine::assets::open_tensor_source(model_dir / "flashsr.safetensors"); + return load_from_tensor_source( + engine::assets::open_tensor_source(model_dir / "flashsr.safetensors"), + backend_config); +} + +FlashSrModel FlashSrModel::load_from_tensor_source( + std::shared_ptr source, + const core::BackendConfig & backend_config) { + if (!source) { + throw std::runtime_error("FlashSR tensor source is missing"); + } auto weights = std::make_shared(); weights->backend.reset(core::init_backend(backend_config)); weights->backend_type = core::backend_type(weights->backend.get()); @@ -447,6 +796,17 @@ FlashSrModel FlashSrModel::load_from_directory( return FlashSrModel(std::move(weights)); } +// The direct DSP path avoids the large im2col temporaries used by the generic +// graph. It is CPU-only and enabled by default; set AUDIOCPP_FLASHSR_DSP=0 to +// retain the generic graph for comparison or diagnostics. +bool flashsr_dsp_enabled(const FlashSrWeights & weights) { + if (weights.backend_type != core::BackendType::Cpu) { + return false; + } + const char * value = std::getenv("AUDIOCPP_FLASHSR_DSP"); + return value == nullptr || value[0] == '\0' || value[0] != '0'; +} + FlashSrOutput FlashSrModel::super_resolve_mono_16k(const std::vector & waveform) const { if (!weights_) { throw std::runtime_error("FlashSR model is not loaded"); @@ -454,12 +814,21 @@ FlashSrOutput FlashSrModel::super_resolve_mono_16k(const std::vector & wa if (waveform.empty()) { throw std::runtime_error("FlashSR input waveform is empty"); } + const bool use_dsp = flashsr_dsp_enabled(*weights_); constexpr int64_t segment_samples = 16000 * 4; constexpr int64_t stride_samples = 16000 * 3; constexpr int64_t segment_threshold = segment_samples * 2; constexpr int64_t output_ratio = 3; const int64_t original_samples = static_cast(waveform.size()); if (original_samples <= segment_threshold) { + if (use_dsp) { + if (!dsp_) { + dsp_ = std::make_unique(*weights_); + } + return FlashSrOutput{ + kFlashSrOutputSampleRate, + normalize_output(dsp_->run(waveform))}; + } if (!graph_ || !graph_->matches(original_samples)) { graph_ = std::make_unique(*weights_, original_samples); } @@ -472,7 +841,11 @@ FlashSrOutput FlashSrModel::super_resolve_mono_16k(const std::vector & wa padded.insert(padded.end(), static_cast(stride_samples - remainder), 0.0f); } const int64_t padded_samples = static_cast(padded.size()); - if (!graph_ || !graph_->matches(segment_samples)) { + if (use_dsp) { + if (!dsp_) { + dsp_ = std::make_unique(*weights_); + } + } else if (!graph_ || !graph_->matches(segment_samples)) { graph_ = std::make_unique(*weights_, segment_samples); } std::vector output(static_cast(padded_samples * output_ratio), 0.0f); @@ -484,7 +857,7 @@ FlashSrOutput FlashSrModel::super_resolve_mono_16k(const std::vector & wa std::vector segment( padded.begin() + static_cast(current), padded.begin() + static_cast(current + segment_samples)); - const auto segment_output = graph_->run(segment); + const auto segment_output = use_dsp ? dsp_->run(segment) : graph_->run(segment); if (static_cast(segment_output.size()) != segment_output_samples) { throw std::runtime_error("FlashSR segmented output length mismatch"); } diff --git a/src/framework/core/attention_fallback.cpp b/src/framework/core/attention_fallback.cpp new file mode 100644 index 000000000..e61955fe3 --- /dev/null +++ b/src/framework/core/attention_fallback.cpp @@ -0,0 +1,125 @@ +#include "engine/framework/core/attention_fallback.h" + +#include +#include +#include +#include +#include +#include + +#include "ggml.h" +#include "ggml-backend.h" + +// ggml-hip publicly defines GGML_USE_CUDA for its consumers (hipified CUDA +// sources), so GGML_USE_CUDA alone does not imply a real CUDA toolchain with +// the driver library linked. ENGINE_GGML_HIP_BACKEND marks the HIP case. +#if defined(GGML_USE_CUDA) && !defined(ENGINE_GGML_HIP_BACKEND) +#define AUDIOCPP_CUDA_DRIVER_PROBE 1 +// CUDA driver API, declared manually so this translation unit needs neither +// the CUDA headers on its include path nor any CMake changes. The driver +// library is already linked transitively through ggml-cuda. Attribute ids +// CU_DEVICE_ATTRIBUTE_COMPUTE_CAPABILITY_MAJOR=75 / MINOR=76 are stable ABI. +extern "C" { +typedef int kCcProbeCuDevice; +typedef int kCcProbeCuResult; +kCcProbeCuResult cuDeviceGet(kCcProbeCuDevice * device, int ordinal); +kCcProbeCuResult cuDeviceGetAttribute(int * value, int attrib, kCcProbeCuDevice device); +} +#endif + +namespace engine::core { +namespace { + +std::string to_lower(std::string value) { + std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return value; +} + +AttentionPreference parse_preference_value(const std::string & value, const char * option_name) { + const std::string lowered = to_lower(value); + if (lowered == "auto") { + return AttentionPreference::Auto; + } + if (lowered == "flash" || lowered == "on" || lowered == "1") { + return AttentionPreference::Flash; + } + if (lowered == "eager" || lowered == "off" || lowered == "0") { + return AttentionPreference::Eager; + } + throw std::runtime_error( + std::string(option_name) + " must be 'auto', 'flash', or 'eager' (got '" + value + "')"); +} + +// Auto-resolution for the CUDA flash-attention path. +// +// ggml_backend_supports_op() cannot be used here: on Volta it returns true +// (the MMA kernel is "selected") yet large prefill shapes die at launch with +// "flash_attn_ext_f16 has no device code compatible with CUDA arch 700". +// Instead, gate on compute capability, mirroring the kernel guards in +// ggml-cuda: flash below 700 (only generic TILE/VEC kernels exist) and at or +// above 800 (MMA fully instantiated); eager on 700-800, where large shapes +// select the MMA kernel with no usable device code. Unknown backends and +// query failures fail OPEN to preserve current behavior. +bool cuda_device_wants_eager(ggml_backend_t backend) { +#ifdef AUDIOCPP_CUDA_DRIVER_PROBE + if (backend == nullptr) { + return false; + } + ggml_backend_dev_t device = ggml_backend_get_device(backend); + if (device == nullptr) { + return false; + } + if (ggml_backend_dev_type(device) != GGML_BACKEND_DEVICE_TYPE_GPU) { + return false; + } + const char * name = ggml_backend_dev_name(device); + if (name == nullptr || std::strncmp(name, "CUDA", 4) != 0) { + return false; // HIP / Vulkan / Metal / CPU: unchanged behavior. + } + char * end = nullptr; + const long ordinal = std::strtol(name + 4, &end, 10); + if (end == name + 4 || ordinal < 0) { + return false; + } + kCcProbeCuDevice cu_device = -1; + if (cuDeviceGet(&cu_device, static_cast(ordinal)) != 0) { + return false; + } + int major = 0; + int minor = 0; + if (cuDeviceGetAttribute(&major, 75 /* COMPUTE_CAPABILITY_MAJOR */, cu_device) != 0) { + return false; + } + if (cuDeviceGetAttribute(&minor, 76 /* COMPUTE_CAPABILITY_MINOR */, cu_device) != 0) { + return false; + } + const int cc = major * 100 + minor * 10; + return cc >= 700 && cc < 800; +#else + (void) backend; + return false; +#endif // AUDIOCPP_CUDA_DRIVER_PROBE +} + +} // namespace + +AttentionPreference parse_attention_preference(const std::string & value, const char * option_name) { + return parse_preference_value(value, option_name != nullptr ? option_name : "attention"); +} + +bool resolve_flash_attention(ggml_backend_t backend, int64_t head_dim, AttentionPreference preference) { + (void) head_dim; + switch (preference) { + case AttentionPreference::Flash: + return true; + case AttentionPreference::Eager: + return false; + case AttentionPreference::Auto: + break; + } + return !cuda_device_wants_eager(backend); +} + +} // namespace engine::core diff --git a/src/framework/model_spec/schema.cpp b/src/framework/model_spec/schema.cpp index fbc49aa9e..ff8447979 100644 --- a/src/framework/model_spec/schema.cpp +++ b/src/framework/model_spec/schema.cpp @@ -104,7 +104,7 @@ const std::unordered_set & precisions() { const std::unordered_set & download_kinds() { static const std::unordered_set values = { - "huggingface_snapshot", "local_snapshot", "converter", "unsupported", + "huggingface_snapshot", "modelscope_snapshot", "local_snapshot", "converter", "unsupported", }; return values; } @@ -408,7 +408,7 @@ void validate_runtime(const json::Value & value, std::string_view path) { validate_string_array(require_spec_field(value, "tags", path), &runtime_tags(), std::string(path) + ".tags", "runtime tag"); } -void validate_hf_snapshot_download(const json::Value & value, std::string_view path) { +void validate_snapshot_download(const json::Value & value, std::string_view path) { require_spec_object(value, path); (void) require_spec_string(require_spec_field(value, "repo", path), std::string(path) + ".repo"); if (const auto * revision = value.find("revision")) { @@ -431,8 +431,8 @@ void validate_download(const json::Value & value, std::string_view path) { require_spec_object(value, path); const auto kind = require_spec_string(require_spec_field(value, "kind", path), std::string(path) + ".kind"); validate_enum(kind, download_kinds(), std::string(path) + ".kind", "download kind"); - if (kind == "huggingface_snapshot") { - validate_hf_snapshot_download(value, path); + if (kind == "huggingface_snapshot" || kind == "modelscope_snapshot") { + validate_snapshot_download(value, path); } else if (kind == "local_snapshot") { (void) require_spec_string(require_spec_field(value, "path", path), std::string(path) + ".path"); if (const auto * array = value.find("include")) { @@ -546,10 +546,14 @@ ValidatedPackage validate_package(const json::Value & value, std::string_view pa std::unordered_set validate_packages( const json::Value & value, std::string_view path, - bool has_default_download) { + bool has_default_download, + bool allow_empty) { const auto & packages = require_spec_array(value, path); if (packages.empty()) { - fail(path, "packages must not be empty"); + if (allow_empty) { + return {}; + } + fail(path, "packages must not be empty unless status is experimental"); } bool has_default = false; std::unordered_set package_ids; @@ -651,10 +655,14 @@ void validate_dependencies( void validate_ui(const json::Value & value, const std::unordered_set & package_ids, std::string_view path) { require_spec_object(value, path); - const auto recommended = require_spec_string(require_spec_field(value, "recommended_package", path), - std::string(path) + ".recommended_package"); - if (package_ids.find(recommended) == package_ids.end()) { - fail(std::string(path) + ".recommended_package", "unknown package '" + recommended + "'"); + if (const auto * recommended_value = value.find("recommended_package")) { + const auto recommended = require_spec_string( + *recommended_value, std::string(path) + ".recommended_package"); + if (package_ids.find(recommended) == package_ids.end()) { + fail(std::string(path) + ".recommended_package", "unknown package '" + recommended + "'"); + } + } else if (!package_ids.empty()) { + fail(std::string(path) + ".recommended_package", "missing required field"); } if (const auto * min_vram = value.find("min_vram_gb")) { require_spec_number(*min_vram, std::string(path) + ".min_vram_gb"); @@ -675,8 +683,9 @@ void validate_v1(const json::Value & spec, std::string_view source_name) { (void) require_spec_string(require_spec_field(spec, "display_name", source_name), std::string(source_name) + ".display_name"); validate_enum(require_spec_string(require_spec_field(spec, "category", source_name), std::string(source_name) + ".category"), categories(), std::string(source_name) + ".category", "category"); - validate_enum(require_spec_string(require_spec_field(spec, "status", source_name), std::string(source_name) + ".status"), - statuses(), std::string(source_name) + ".status", "status"); + const auto status = require_spec_string( + require_spec_field(spec, "status", source_name), std::string(source_name) + ".status"); + validate_enum(status, statuses(), std::string(source_name) + ".status", "status"); const auto task_ids = validate_nonempty_string_set( require_spec_field(spec, "tasks", source_name), &tasks(), std::string(source_name) + ".tasks", "task"); validate_nonempty_string_set( @@ -698,7 +707,8 @@ void validate_v1(const json::Value & spec, std::string_view source_name) { const auto packages_path = std::string(source_name) + ".packages"; const auto & packages_field = require_spec_field(spec, "packages", source_name); - const auto package_ids = validate_packages(packages_field, packages_path, has_default_download); + const auto package_ids = validate_packages( + packages_field, packages_path, has_default_download, status == "experimental"); validate_dependencies( require_spec_field(spec, "dependencies", source_name), family, diff --git a/src/framework/modules/conv_modules.cpp b/src/framework/modules/conv_modules.cpp index 185b66e35..d3f5b3345 100644 --- a/src/framework/modules/conv_modules.cpp +++ b/src/framework/modules/conv_modules.cpp @@ -193,6 +193,99 @@ core::TensorValue depthwise_conv2d_weight( int64_t conv1d_output_frames(const Conv1dConfig & config, int64_t input_frames) { return (input_frames + 2 * config.padding - config.dilation * (config.kernel_size - 1) - 1) / config.stride + 1; } +bool is_conv1d_pertap_fast_path_eligible( + const core::ModuleBuildContext & ctx, + const Conv1dConfig & config, + const core::TensorValue & input) noexcept { + return ctx.backend_type == core::BackendType::Metal && + config.padding == 0 && + config.stride == 1 && + input.shape.dims[0] == 1 && + input.type == GGML_TYPE_F32 && + input.tensor->ne[0] == input.shape.dims[2] && + input.tensor->ne[1] == config.in_channels && + ggml_is_contiguous(input.tensor); +} + +// Per-tap GEMM accumulation on a channel-fast [in_channels, frames] F32 input: one +// contiguous GEMM per kernel tap over shifted column views, accumulated into +// [out_channels, output_frames]. No layout conversion here -- callers at region edges +// transpose; chained callers keep everything channel-fast. +ggml_tensor * conv1d_pertap_gemm_channel_fast( + core::ModuleBuildContext & ctx, + ggml_tensor * input_cf, + ggml_tensor * weight_f32, + int64_t in_channels, + int64_t out_channels, + int64_t kernel_size, + int64_t dilation, + int64_t output_frames) { + // weight logical [OC, IC, K] -> ggml ne [K, IC, OC]; regroup rows so each tap slice + // [IC, OC] is a contiguous view: row index = channel + in_channels * tap. + auto * weight_taps = ggml_reshape_2d( + ctx.ggml, + ggml_cont(ctx.ggml, ggml_permute(ctx.ggml, weight_f32, 1, 0, 2, 3)), + in_channels * kernel_size, + out_channels); + // accumulate-in-place GEMM only where the tensor-core mm kernel applies + // (mirrors the Metal supports gate); otherwise keep the mul_mat + add chain + const bool use_acc = in_channels >= 64 && output_frames > 8; + ggml_tensor * acc = nullptr; + for (int64_t tap = 0; tap < kernel_size; ++tap) { + // columns[c, j] = input[tap * dilation + j, c]: contiguous column view of input_cf. + auto * columns = ggml_view_2d( + ctx.ggml, + input_cf, + in_channels, + output_frames, + input_cf->nb[1], + static_cast(tap * dilation) * in_channels * sizeof(float)); + auto * tap_weights = ggml_view_2d( + ctx.ggml, + weight_taps, + in_channels, + out_channels, + weight_taps->nb[1], + static_cast(tap) * in_channels * sizeof(float)); + if (acc == nullptr) { + acc = ggml_mul_mat(ctx.ggml, tap_weights, columns); + } else if (use_acc) { + acc = ggml_mul_mat_acc(ctx.ggml, tap_weights, columns, acc); + } else { + acc = ggml_add(ctx.ggml, acc, ggml_mul_mat(ctx.ggml, tap_weights, columns)); + } + } + return acc; +} + +// Metal fast path for stride-1 conv1d on the time-fast [frames, channels] layout used by +// the audio codecs. ggml_conv_1d materializes an im2col matrix whose kernel taps are +// strided gathers in this layout (~200 ms per conv at [569k, 96] on M4); instead, transpose +// the input to channel-fast once, run one contiguous GEMM per kernel tap, and transpose +// the accumulator back (~3-6x faster). +core::TensorValue build_conv1d_pertap_fast_path( + core::ModuleBuildContext & ctx, + const Conv1dConfig & config, + const core::TensorValue & input, + const core::TensorValue & weight_f32, + const core::TensorShape & output_shape) { + // channel-fast copy of the input: [IC, frames]; kernel taps become contiguous columns. + auto * input_cf = ggml_cont(ctx.ggml, ggml_transpose(ctx.ggml, input.tensor)); + auto * acc = conv1d_pertap_gemm_channel_fast( + ctx, + input_cf, + weight_f32.tensor, + config.in_channels, + config.out_channels, + config.kernel_size, + config.dilation, + output_shape.dims[2]); + // mul_mat yields [OC, frames]; restore the canonical [frames, OC] orientation. + return core::wrap_tensor( + ggml_cont(ctx.ggml, ggml_transpose(ctx.ggml, acc)), + output_shape, + GGML_TYPE_F32); +} int64_t conv2d_output_dim(int64_t input, int kernel, int stride, int padding, int dilation) { return (input + 2 * padding - dilation * (kernel - 1) - 1) / stride + 1; @@ -319,10 +412,97 @@ bool is_conv_transpose1d_col2im_fast_path_eligible( const core::ModuleBuildContext & ctx, const ConvTranspose1dConfig & config) noexcept { return (core::uses_ggml_cuda_or_hip_backend(ctx.backend_type) || - ctx.backend_type == core::BackendType::Metal) && + ctx.backend_type == core::BackendType::Metal || + ctx.backend_type == core::BackendType::Vulkan) && config.dilation == 1; } +ggml_tensor * conv1d_pertap_channel_fast( + core::ModuleBuildContext & ctx, + const Conv1dWeights & weights, + ggml_tensor * input_cf, + const Conv1dConfig & config) { + if (ctx.ggml == nullptr || input_cf == nullptr) { + throw std::runtime_error("conv1d_pertap_channel_fast requires a ggml context and an input tensor"); + } + if (config.padding != 0 || config.stride != 1) { + throw std::runtime_error("conv1d_pertap_channel_fast requires padding=0 and stride=1"); + } + if (input_cf->type != GGML_TYPE_F32 || input_cf->ne[0] != config.in_channels || + !ggml_is_contiguous(input_cf)) { + throw std::runtime_error("conv1d_pertap_channel_fast requires contiguous F32 [in_channels, frames] input"); + } + auto weight = regular_conv_weight(ctx, weights.weight, "conv1d_pertap_channel_fast"); + if (weight.type != GGML_TYPE_F32) { + weight = core::wrap_tensor( + ggml_cast(ctx.ggml, weight.tensor, GGML_TYPE_F32), weight.shape, GGML_TYPE_F32); + } + const int64_t output_frames = input_cf->ne[1] - config.dilation * (config.kernel_size - 1); + ggml_tensor * acc = conv1d_pertap_gemm_channel_fast( + ctx, + input_cf, + weight.tensor, + config.in_channels, + config.out_channels, + config.kernel_size, + config.dilation, + output_frames); + if (config.use_bias) { + if (!weights.bias.has_value()) { + throw std::runtime_error("conv1d_pertap_channel_fast requires bias when use_bias is true"); + } + const auto bias = ensure_f32(ctx, *weights.bias); + core::validate_shape(bias, core::TensorShape::from_dims({config.out_channels}), "bias"); + acc = ggml_add(ctx.ggml, acc, ggml_reshape_2d(ctx.ggml, bias.tensor, config.out_channels, 1)); + } + return acc; +} + +ggml_tensor * conv_transpose1d_col2im_channel_fast( + core::ModuleBuildContext & ctx, + const ConvTranspose1dWeights & weights, + ggml_tensor * input_cf, + const ConvTranspose1dConfig & config) { + if (!is_conv_transpose1d_col2im_fast_path_eligible(ctx, config)) { + throw std::runtime_error("conv_transpose1d_col2im_channel_fast called with an ineligible config"); + } + if (input_cf == nullptr || input_cf->type != GGML_TYPE_F32 || + input_cf->ne[0] != config.in_channels || !ggml_is_contiguous(input_cf)) { + throw std::runtime_error( + "conv_transpose1d_col2im_channel_fast requires contiguous F32 [in_channels, frames] input"); + } + auto weight_contiguous = tensor_layout::ensure_contiguous_layout_if_needed(ctx, weights.weight); + if (weight_contiguous.type != GGML_TYPE_F32) { + weight_contiguous = core::wrap_tensor( + ggml_cast(ctx.ggml, weight_contiguous.tensor, GGML_TYPE_F32), + weight_contiguous.shape, + GGML_TYPE_F32); + } + auto * weight_perm = ggml_reshape_2d( + ctx.ggml, + ggml_cont(ctx.ggml, ggml_permute(ctx.ggml, weight_contiguous.tensor, 1, 2, 0, 3)), + config.in_channels, + config.kernel_size * config.out_channels); + auto * columns = ggml_mul_mat(ctx.ggml, weight_perm, input_cf); + auto * output = ggml_col2im_1d( + ctx.ggml, + columns, + config.stride, + static_cast(config.out_channels), + config.padding); + if (config.use_bias) { + if (!weights.bias.has_value()) { + throw std::runtime_error("conv_transpose1d_col2im_channel_fast requires bias when use_bias is true"); + } + core::validate_shape(*weights.bias, core::TensorShape::from_dims({config.out_channels}), "bias"); + output = ggml_add( + ctx.ggml, + output, + ggml_reshape_2d(ctx.ggml, weights.bias->tensor, 1, config.out_channels)); + } + return output; +} + Conv1dModule::Conv1dModule(Conv1dConfig config) : config_(config) { if (config_.in_channels <= 0 || config_.out_channels <= 0 || config_.kernel_size <= 0) { throw std::runtime_error("Conv1dConfig dimensions must be positive"); @@ -362,7 +542,10 @@ core::TensorValue Conv1dModule::build( const auto input_contiguous = ensure_f32(ctx, tensor_layout::ensure_contiguous_layout_if_needed(ctx, input)); const auto weight_contiguous = regular_conv_weight(ctx, weights.weight, "Conv1dModule"); core::TensorValue output; - if (input.shape.dims[0] == 1) { + if (is_conv1d_pertap_fast_path_eligible(ctx, config_, input) && + weight_contiguous.type == GGML_TYPE_F32) { + output = build_conv1d_pertap_fast_path(ctx, config_, input_contiguous, weight_contiguous, output_shape); + } else if (input.shape.dims[0] == 1) { output = core::wrap_tensor( ggml_conv_1d( ctx.ggml, diff --git a/src/framework/modules/text_encoders/t5_gemma_encoder.cpp b/src/framework/modules/text_encoders/t5_gemma_encoder.cpp index 0530468dd..a84b2f9f5 100644 --- a/src/framework/modules/text_encoders/t5_gemma_encoder.cpp +++ b/src/framework/modules/text_encoders/t5_gemma_encoder.cpp @@ -14,14 +14,27 @@ namespace engine::modules { namespace { +int64_t attention_size(const T5GemmaEncoderConfig & config) { + return config.attention_size > 0 ? config.attention_size : config.hidden_size; +} + void validate_config(const T5GemmaEncoderConfig & config) { if (config.hidden_size <= 0 || config.layers <= 0 || config.attention_heads <= 0 || config.kv_heads <= 0 || config.head_dim <= 0 || config.intermediate_size <= 0 || config.vocab_size <= 0) { throw std::runtime_error("T5GemmaEncoderConfig dimensions must be positive"); } - if (config.attention_heads * config.head_dim != config.hidden_size) { - throw std::runtime_error("T5GemmaEncoderConfig attention_heads * head_dim must equal hidden_size"); + if (config.attention_heads * config.head_dim != attention_size(config)) { + throw std::runtime_error("T5GemmaEncoderConfig attention_heads * head_dim must equal attention_size"); + } + if (config.attention_heads % config.kv_heads != 0) { + throw std::runtime_error("T5GemmaEncoderConfig attention_heads must be divisible by kv_heads"); + } + if (!config.layer_rope_theta.empty() && static_cast(config.layer_rope_theta.size()) != config.layers) { + throw std::runtime_error("T5GemmaEncoderConfig layer_rope_theta must match layer count"); + } + if (!config.layer_rope_freq_scale.empty() && static_cast(config.layer_rope_freq_scale.size()) != config.layers) { + throw std::runtime_error("T5GemmaEncoderConfig layer_rope_freq_scale must match layer count"); } if (!(config.rope_theta > 0.0F) || !(config.rms_norm_eps > 0.0F) || !(config.query_pre_attn_scalar > 0.0F)) { @@ -29,6 +42,18 @@ void validate_config(const T5GemmaEncoderConfig & config) { } } +float layer_rope_theta(const T5GemmaEncoderConfig & config, int64_t layer_index) { + return config.layer_rope_theta.empty() + ? config.rope_theta + : config.layer_rope_theta.at(static_cast(layer_index)); +} + +float layer_rope_freq_scale(const T5GemmaEncoderConfig & config, int64_t layer_index) { + return config.layer_rope_freq_scale.empty() + ? config.rope_freq_scale + : config.layer_rope_freq_scale.at(static_cast(layer_index)); +} + core::TensorValue ensure_contiguous(core::ModuleBuildContext & ctx, const core::TensorValue & input) { return core::ensure_backend_addressable_layout(ctx, input); } @@ -63,15 +88,19 @@ core::TensorValue matmul_f32(core::ModuleBuildContext & ctx, const core::TensorV return core::wrap_tensor(output, output_shape, GGML_TYPE_F32); } -core::TensorValue gemma_rms_norm( +core::TensorValue t5_gemma_rms_norm( core::ModuleBuildContext & ctx, const core::TensorValue & input, const core::TensorValue & weight, + T5GemmaRMSNormStyle style, float eps, int64_t hidden_size) { core::validate_last_dim(input, hidden_size, "T5GemmaEncoder RMSNorm input"); core::validate_shape(weight, core::TensorShape::from_dims({hidden_size}), "T5GemmaEncoder RMSNorm weight"); auto normalized = core::wrap_tensor(ggml_rms_norm(ctx.ggml, ensure_contiguous(ctx, input).tensor, eps), input.shape, GGML_TYPE_F32); + if (style == T5GemmaRMSNormStyle::Direct) { + return core::wrap_tensor(ggml_mul(ctx.ggml, normalized.tensor, weight.tensor), input.shape, GGML_TYPE_F32); + } auto one_plus_weight = core::wrap_tensor(ggml_scale_bias(ctx.ggml, weight.tensor, 1.0F, 1.0F), weight.shape, GGML_TYPE_F32); return core::wrap_tensor(ggml_mul(ctx.ggml, normalized.tensor, one_plus_weight.tensor), input.shape, GGML_TYPE_F32); } @@ -85,28 +114,62 @@ core::TensorValue reshape_heads( return core::reshape_tensor(ctx, contiguous, core::TensorShape::from_dims({input.shape.dims[0], input.shape.dims[1], heads, dim})); } +core::TensorValue repeat_kv_heads(core::ModuleBuildContext & ctx, const core::TensorValue & input, int64_t repeats) { + if (repeats == 1) { + return input; + } + core::validate_rank_between(input, 4, 4, "T5GemmaEncoder repeat_kv_heads input"); + auto contiguous = ensure_contiguous(ctx, input); + const int64_t batch = contiguous.shape.dims[0]; + const int64_t kv_heads = contiguous.shape.dims[1]; + const int64_t steps = contiguous.shape.dims[2]; + const int64_t dim = contiguous.shape.dims[3]; + auto expanded = core::reshape_tensor( + ctx, + contiguous, + core::TensorShape::from_dims({batch, kv_heads, 1, steps * dim})); + expanded = RepeatModule({core::TensorShape::from_dims({batch, kv_heads, repeats, steps * dim})}) + .build(ctx, expanded); + return core::reshape_tensor( + ctx, + expanded, + core::TensorShape::from_dims({batch, kv_heads * repeats, steps, dim})); +} + core::TensorValue self_attention( core::ModuleBuildContext & ctx, const core::TensorValue & input, const core::TensorValue & positions, const core::TensorValue & additive_attention_mask, const T5GemmaEncoderLayerWeights & weights, - const T5GemmaEncoderConfig & config) { + const T5GemmaEncoderConfig & config, + int64_t layer_index) { + const int64_t attn_size = attention_size(config); const LinearModule q_proj({config.hidden_size, config.attention_heads * config.head_dim, false, GGML_PREC_F32}); const LinearModule k_proj({config.hidden_size, config.kv_heads * config.head_dim, false, GGML_PREC_F32}); const LinearModule v_proj({config.hidden_size, config.kv_heads * config.head_dim, false, GGML_PREC_F32}); - const LinearModule o_proj({config.attention_heads * config.head_dim, config.hidden_size, false, GGML_PREC_F32}); + const LinearModule o_proj({attn_size, config.hidden_size, false, GGML_PREC_F32}); auto q = q_proj.build(ctx, input, weights.q_proj); auto k = k_proj.build(ctx, input, weights.k_proj); auto v = v_proj.build(ctx, input, weights.v_proj); q = reshape_heads(ctx, q, config.attention_heads, config.head_dim); k = reshape_heads(ctx, k, config.kv_heads, config.head_dim); v = reshape_heads(ctx, v, config.kv_heads, config.head_dim); - q = RoPEModule({config.head_dim, GGML_ROPE_TYPE_NEOX, config.rope_theta}).build(ctx, q, positions); - k = RoPEModule({config.head_dim, GGML_ROPE_TYPE_NEOX, config.rope_theta}).build(ctx, k, positions); + if (config.use_qk_norm) { + if (!weights.q_norm.weight.has_value() || !weights.k_norm.weight.has_value()) { + throw std::runtime_error("T5GemmaEncoder q/k norm weights are required when use_qk_norm is enabled"); + } + q = t5_gemma_rms_norm(ctx, q, *weights.q_norm.weight, config.rms_norm_style, config.rms_norm_eps, config.head_dim); + k = t5_gemma_rms_norm(ctx, k, *weights.k_norm.weight, config.rms_norm_style, config.rms_norm_eps, config.head_dim); + } + q = RoPEModule({config.head_dim, GGML_ROPE_TYPE_NEOX, layer_rope_theta(config, layer_index), layer_rope_freq_scale(config, layer_index)}).build(ctx, q, positions); + k = RoPEModule({config.head_dim, GGML_ROPE_TYPE_NEOX, layer_rope_theta(config, layer_index), layer_rope_freq_scale(config, layer_index)}).build(ctx, k, positions); auto q_heads = TransposeModule({{0, 2, 1, 3}, q.shape.rank}).build(ctx, q); auto k_heads = TransposeModule({{0, 2, 1, 3}, k.shape.rank}).build(ctx, k); auto v_heads = TransposeModule({{0, 2, 1, 3}, v.shape.rank}).build(ctx, v); + const int64_t kv_repeats = config.attention_heads / config.kv_heads; + k_heads = repeat_kv_heads(ctx, k_heads, kv_repeats); + v_heads = repeat_kv_heads(ctx, v_heads, kv_repeats); auto k_heads_contiguous = ensure_contiguous(ctx, k_heads); auto scores_raw = ggml_mul_mat(ctx.ggml, k_heads_contiguous.tensor, q_heads.tensor); ggml_mul_mat_set_prec(scores_raw, GGML_PREC_F32); @@ -145,7 +208,7 @@ core::TensorValue self_attention( context = core::reshape_tensor( ctx, context, - core::TensorShape::from_dims({input.shape.dims[0], input.shape.dims[1], config.attention_heads * config.head_dim})); + core::TensorShape::from_dims({input.shape.dims[0], input.shape.dims[1], attn_size})); return o_proj.build(ctx, context, weights.o_proj); } @@ -169,14 +232,15 @@ core::TensorValue layer( const core::TensorValue & positions, const core::TensorValue & additive_attention_mask, const T5GemmaEncoderLayerWeights & weights, - const T5GemmaEncoderConfig & config) { - auto hidden = gemma_rms_norm(ctx, input, weights.pre_self_attn_norm, config.rms_norm_eps, config.hidden_size); - hidden = self_attention(ctx, hidden, positions, additive_attention_mask, weights, config); - hidden = gemma_rms_norm(ctx, hidden, weights.post_self_attn_norm, config.rms_norm_eps, config.hidden_size); + const T5GemmaEncoderConfig & config, + int64_t layer_index) { + auto hidden = t5_gemma_rms_norm(ctx, input, weights.pre_self_attn_norm, config.rms_norm_style, config.rms_norm_eps, config.hidden_size); + hidden = self_attention(ctx, hidden, positions, additive_attention_mask, weights, config, layer_index); + hidden = t5_gemma_rms_norm(ctx, hidden, weights.post_self_attn_norm, config.rms_norm_style, config.rms_norm_eps, config.hidden_size); auto output = AddModule{}.build(ctx, input, hidden); - hidden = gemma_rms_norm(ctx, output, weights.pre_ff_norm, config.rms_norm_eps, config.hidden_size); + hidden = t5_gemma_rms_norm(ctx, output, weights.pre_ff_norm, config.rms_norm_style, config.rms_norm_eps, config.hidden_size); hidden = mlp(ctx, hidden, weights, config); - hidden = gemma_rms_norm(ctx, hidden, weights.post_ff_norm, config.rms_norm_eps, config.hidden_size); + hidden = t5_gemma_rms_norm(ctx, hidden, weights.post_ff_norm, config.rms_norm_style, config.rms_norm_eps, config.hidden_size); return AddModule{}.build(ctx, output, hidden); } @@ -213,9 +277,9 @@ core::TensorValue T5GemmaEncoderModule::build( GGML_TYPE_F32); } for (int64_t i = 0; i < config_.layers; ++i) { - hidden = layer(ctx, hidden, positions, additive_attention_mask, weights.layers[static_cast(i)], config_); + hidden = layer(ctx, hidden, positions, additive_attention_mask, weights.layers[static_cast(i)], config_, i); } - return gemma_rms_norm(ctx, hidden, weights.norm, config_.rms_norm_eps, config_.hidden_size); + return t5_gemma_rms_norm(ctx, hidden, weights.norm, config_.rms_norm_style, config_.rms_norm_eps, config_.hidden_size); } } // namespace engine::modules diff --git a/src/framework/modules/transformers/qwen_causal_decode_runtime.cpp b/src/framework/modules/transformers/qwen_causal_decode_runtime.cpp index 5be454a29..9df457ae4 100644 --- a/src/framework/modules/transformers/qwen_causal_decode_runtime.cpp +++ b/src/framework/modules/transformers/qwen_causal_decode_runtime.cpp @@ -644,7 +644,6 @@ class QwenCausalDecodeRuntime::Impl { void ensure_prefill_token_graph(int64_t steps) { if (prefill_graph_ != nullptr && prefill_input_kind_ == InputKind::Token && prefill_steps_ == steps) { - debug::timing_log_scalar(config_.trace_name + ".prefill.graph.build_ms", 0.0); debug::trace_log_scalar(config_.trace_name + ".prefill.steps", steps); return; } @@ -654,7 +653,6 @@ class QwenCausalDecodeRuntime::Impl { void ensure_prefill_embedding_graph(int64_t steps) { if (prefill_graph_ != nullptr && prefill_input_kind_ == InputKind::Embedding && prefill_steps_ == steps) { - debug::timing_log_scalar(config_.trace_name + ".prefill.graph.build_ms", 0.0); debug::trace_log_scalar(config_.trace_name + ".prefill.steps", steps); return; } @@ -828,7 +826,6 @@ class QwenCausalDecodeRuntime::Impl { void ensure_batched_prefill_token_graph(int64_t batch_size, int64_t steps) { if (batched_prefill_graph_ != nullptr && batched_prefill_input_kind_ == InputKind::Token && batched_prefill_batch_size_ == batch_size && batched_prefill_steps_ == steps) { - debug::timing_log_scalar(config_.trace_name + ".batched_prefill.graph.build_ms", 0.0); return; } release_batched_prefill_graph(); @@ -838,7 +835,6 @@ class QwenCausalDecodeRuntime::Impl { void ensure_batched_prefill_embedding_graph(int64_t batch_size, int64_t steps) { if (batched_prefill_graph_ != nullptr && batched_prefill_input_kind_ == InputKind::Embedding && batched_prefill_batch_size_ == batch_size && batched_prefill_steps_ == steps) { - debug::timing_log_scalar(config_.trace_name + ".batched_prefill.graph.build_ms", 0.0); return; } release_batched_prefill_graph(); @@ -1037,7 +1033,6 @@ class QwenCausalDecodeRuntime::Impl { void ensure_decode_token_graph(int64_t cache_steps) { if (decode_graph_ != nullptr && decode_input_kind_ == InputKind::Token && decode_cache_steps_ >= cache_steps) { - debug::timing_log_scalar(config_.trace_name + ".decode.graph.build_ms", 0.0); debug::trace_log_scalar(config_.trace_name + ".decode.cache_steps", cache_steps); return; } @@ -1047,7 +1042,6 @@ class QwenCausalDecodeRuntime::Impl { void ensure_decode_embedding_graph(int64_t cache_steps) { if (decode_graph_ != nullptr && decode_input_kind_ == InputKind::Embedding && decode_cache_steps_ >= cache_steps) { - debug::timing_log_scalar(config_.trace_name + ".decode.graph.build_ms", 0.0); debug::trace_log_scalar(config_.trace_name + ".decode.cache_steps", cache_steps); return; } @@ -1146,7 +1140,6 @@ class QwenCausalDecodeRuntime::Impl { void ensure_batched_decode_token_graph(int64_t cache_steps, int64_t batch_size) { if (batched_decode_graph_ != nullptr && batched_decode_input_kind_ == InputKind::Token && batched_decode_cache_steps_ >= cache_steps && batched_decode_batch_size_ == batch_size) { - debug::timing_log_scalar(config_.trace_name + ".batched_decode.graph.build_ms", 0.0); return; } release_batched_decode_graph(); @@ -1156,7 +1149,6 @@ class QwenCausalDecodeRuntime::Impl { void ensure_batched_decode_embedding_graph(int64_t cache_steps, int64_t batch_size) { if (batched_decode_graph_ != nullptr && batched_decode_input_kind_ == InputKind::Embedding && batched_decode_cache_steps_ >= cache_steps && batched_decode_batch_size_ == batch_size) { - debug::timing_log_scalar(config_.trace_name + ".batched_decode.graph.build_ms", 0.0); return; } release_batched_decode_graph(); diff --git a/src/framework/modules/transformers/qwen_decoder.cpp b/src/framework/modules/transformers/qwen_decoder.cpp index a50b7191c..743f5aaa9 100644 --- a/src/framework/modules/transformers/qwen_decoder.cpp +++ b/src/framework/modules/transformers/qwen_decoder.cpp @@ -48,6 +48,9 @@ int64_t require_head_dim(const QwenDecoderLayerConfig & config) { config.activation_cast.type != GGML_TYPE_F16 && config.activation_cast.type != GGML_TYPE_BF16) { throw std::runtime_error("QwenDecoderLayerConfig activation cast supports only f32, f16, and bf16"); } + if (config.activation_cast.fused_round && config.activation_cast.type != GGML_TYPE_BF16) { + throw std::runtime_error("QwenDecoderLayerConfig fused activation rounding requires bf16"); + } return config.head_dim; } @@ -235,6 +238,13 @@ core::TensorValue activation_cast( if (policy.type == GGML_TYPE_F32) { return core::wrap_tensor(ggml_cast(ctx.ggml, input.tensor, GGML_TYPE_F32), input.shape, GGML_TYPE_F32); } + if (policy.fused_round && policy.type == GGML_TYPE_BF16 && ggml_is_contiguous_rows(input.tensor)) { + // Fused single-kernel round-to-bf16: f32/f16/bf16 in, always f32 out, + // values rounded to bf16. Numerically identical to the cast round trip + // below (bf16 input is already rounded, so rounding is a widening no-op), + // but avoids the intermediate bf16 tensor and one kernel launch. + return core::wrap_tensor(ggml_round_bf16(ctx.ggml, input.tensor), input.shape, GGML_TYPE_F32); + } auto rounded = core::wrap_tensor(ggml_cast(ctx.ggml, input.tensor, policy.type), input.shape, policy.type); return core::wrap_tensor(ggml_cast(ctx.ggml, rounded.tensor, GGML_TYPE_F32), input.shape, GGML_TYPE_F32); } @@ -245,6 +255,10 @@ struct QKVProjections { core::TensorValue v; }; +bool flash_branches_allowed(const QwenDecoderLayerConfig & config) { + return config.runtime.attention.allow_flash_attention; +} + QKVProjections build_qkv_projections( core::ModuleBuildContext & ctx, const core::TensorValue & input, @@ -534,13 +548,18 @@ QwenDecoderLayerOutputs QwenDecoderLayerModule::build( v = core::ensure_backend_addressable_layout(ctx, v); auto q_heads = TransposeModule({{0, 2, 1, 3}, q.shape.rank}).build(ctx, q); + const bool allow_flash = flash_branches_allowed(config_); const bool use_prefix_flash = + allow_flash && prefix_key.has_value() && config_.runtime.attention.prefix_mode == QwenDecoderPrefixAttentionMode::FlashWithPrefix && config_.runtime.attention.prefill_mode == QwenDecoderAttentionMode::FlashGroupedViewKV; core::TensorValue all_k = k; core::TensorValue all_v = v; - if (use_prefix_flash) { + // Cached prefix KV may be stored in a different dtype than the current + // K/V (e.g. Higgs reference state); cast before concat on every path. + // (The eager branch previously skipped this and died in ggml_concat.) + if (prefix_key.has_value()) { auto attention_prefix_key = prefix_key; auto attention_prefix_value = prefix_value; if (attention_prefix_key->type != k.type) { @@ -557,12 +576,9 @@ QwenDecoderLayerOutputs QwenDecoderLayerModule::build( } all_k = ConcatModule({1}).build(ctx, *attention_prefix_key, k); all_v = ConcatModule({1}).build(ctx, *attention_prefix_value, v); - } else if (prefix_key.has_value()) { - all_k = ConcatModule({1}).build(ctx, *prefix_key, k); - all_v = ConcatModule({1}).build(ctx, *prefix_value, v); } core::TensorValue context; - if (!prefix_key.has_value() && attention_mask.has_value() && + if (allow_flash && !prefix_key.has_value() && attention_mask.has_value() && config_.runtime.attention.prefill_mode == QwenDecoderAttentionMode::FlashGroupedViewKV) { q_heads = core::wrap_tensor(ggml_cont(ctx.ggml, q_heads.tensor), q_heads.shape, q_heads.type); auto k_heads = TransposeModule({{0, 2, 1, 3}, all_k.shape.rank}).build(ctx, all_k); @@ -575,10 +591,20 @@ QwenDecoderLayerOutputs QwenDecoderLayerModule::build( dim, *attention_mask, config_.attention_precision); - } else if (attention_mask.has_value() && - ((!prefix_key.has_value() && - config_.runtime.attention.prefill_mode == QwenDecoderAttentionMode::FlashGrouped) || - use_prefix_flash)) { + } else if (allow_flash && attention_mask.has_value() && use_prefix_flash) { + q_heads = core::wrap_tensor(ggml_cont(ctx.ggml, q_heads.tensor), q_heads.shape, q_heads.type); + auto k_heads = TransposeModule({{0, 2, 1, 3}, all_k.shape.rank}).build(ctx, all_k); + auto v_heads = TransposeModule({{0, 2, 1, 3}, all_v.shape.rank}).build(ctx, all_v); + context = flash_attention_from_grouped_heads_view_kv( + ctx, + q_heads, + k_heads, + v_heads, + dim, + *attention_mask, + config_.attention_precision); + } else if (allow_flash && attention_mask.has_value() && !prefix_key.has_value() && + config_.runtime.attention.prefill_mode == QwenDecoderAttentionMode::FlashGrouped) { q_heads = core::wrap_tensor(ggml_cont(ctx.ggml, q_heads.tensor), q_heads.shape, q_heads.type); auto k_heads = TransposeModule({{0, 2, 1, 3}, all_k.shape.rank}).build(ctx, all_k); auto v_heads = TransposeModule({{0, 2, 1, 3}, all_v.shape.rank}).build(ctx, all_v); @@ -728,6 +754,7 @@ QwenDecoderLayerOutputs QwenDecoderLayerModule::build_with_static_cache_tail( auto k_heads = TransposeModule({{0, 2, 1, 3}, attention_key_cache.shape.rank}).build(ctx, attention_key_cache); auto v_heads = TransposeModule({{0, 2, 1, 3}, attention_value_cache.shape.rank}).build(ctx, attention_value_cache); core::TensorValue context; + const bool allow_flash = flash_branches_allowed(config_); const bool use_grouped_query = config_.runtime.attention.static_mode == QwenDecoderAttentionMode::ManualRepeatThenGroupedQuery && config_.runtime.attention.grouped_query_min_steps > 0 && @@ -745,7 +772,8 @@ QwenDecoderLayerOutputs QwenDecoderLayerModule::build_with_static_cache_tail( config_.num_attention_heads, config_.num_key_value_heads, attention_mask); - } else if (config_.runtime.attention.static_mode == QwenDecoderAttentionMode::ManualRepeat || + } else if (!allow_flash || + config_.runtime.attention.static_mode == QwenDecoderAttentionMode::ManualRepeat || config_.runtime.attention.static_mode == QwenDecoderAttentionMode::ManualRepeatThenGroupedQuery) { k_heads = repeat_kv_heads(ctx, k_heads, kv_repeats); v_heads = repeat_kv_heads(ctx, v_heads, kv_repeats); @@ -888,6 +916,7 @@ QwenDecoderLayerOutputs QwenDecoderLayerModule::build_with_static_cache_tail_bat auto k_heads = TransposeModule({{0, 2, 1, 3}, attention_key_cache.shape.rank}).build(ctx, attention_key_cache); auto v_heads = TransposeModule({{0, 2, 1, 3}, attention_value_cache.shape.rank}).build(ctx, attention_value_cache); core::TensorValue context; + const bool allow_flash = flash_branches_allowed(config_); const bool use_grouped_query = config_.runtime.attention.static_mode == QwenDecoderAttentionMode::ManualRepeatThenGroupedQuery && config_.runtime.attention.grouped_query_min_steps > 0 && @@ -905,7 +934,8 @@ QwenDecoderLayerOutputs QwenDecoderLayerModule::build_with_static_cache_tail_bat config_.num_attention_heads, config_.num_key_value_heads, attention_mask); - } else if (config_.runtime.attention.static_mode == QwenDecoderAttentionMode::ManualRepeat || + } else if (!allow_flash || + config_.runtime.attention.static_mode == QwenDecoderAttentionMode::ManualRepeat || config_.runtime.attention.static_mode == QwenDecoderAttentionMode::ManualRepeatThenGroupedQuery) { k_heads = repeat_kv_heads(ctx, k_heads, kv_repeats); v_heads = repeat_kv_heads(ctx, v_heads, kv_repeats); diff --git a/src/framework/package_manager/manager.cpp b/src/framework/package_manager/manager.cpp index 87ad42202..f7549ddb9 100644 --- a/src/framework/package_manager/manager.cpp +++ b/src/framework/package_manager/manager.cpp @@ -45,7 +45,7 @@ struct Package { std::string strip_prefix; std::string download_kind; std::string repo; - std::string revision = "main"; + std::string revision; bool gated = false; }; @@ -94,6 +94,10 @@ std::string huggingface_token() { return token; } +std::string modelscope_token() { + return getenv_text("AUDIOCPP_MS_TOKEN"); +} + bool unreserved(unsigned char ch) { return std::isalnum(ch) != 0 || ch == '-' || ch == '_' || ch == '.' || ch == '~'; } @@ -121,6 +125,23 @@ std::string hf_url(const Package & package, const std::string & remote_path) { url_encode(package.revision, false) + "/" + url_encode(remote_path, true); } +std::string modelscope_base_url() { + auto base = getenv_text("AUDIOCPP_MS_BASE_URL"); + if (base.empty()) base = "https://www.modelscope.cn"; + while (!base.empty() && base.back() == '/') base.pop_back(); + return base; +} + +std::string ms_url(const Package & package, const std::string & remote_path) { + return modelscope_base_url() + "/models/" + package.repo + "/resolve/" + + url_encode(package.revision, false) + "/" + url_encode(remote_path, true); +} + +std::string ms_files_url(const Package & package) { + return modelscope_base_url() + "/api/v1/models/" + package.repo + + "/repo/files?Revision=" + url_encode(package.revision, false) + "&Recursive=true"; +} + std::filesystem::path validate_relative_path(const std::filesystem::path & path, const char * label) { if (path.empty() || path == "." || path.is_absolute()) { throw std::runtime_error(std::string(label) + " must be a non-empty relative path: " + path.string()); @@ -192,13 +213,13 @@ std::vector parse_specs(const std::vectorfind("download")) { default_kind = json::optional_string(*download, "kind", ""); default_repo = json::optional_string(*download, "repo", ""); - default_revision = json::optional_string(*download, "revision", "main"); + default_revision = json::optional_string(*download, "revision", ""); default_gated = json::optional_bool(*download, "gated", false); } } @@ -225,9 +246,15 @@ std::vector parse_specs(const std::vector & cancelled, - const std::function & progress) { + const std::function & progress, + RequestAuth auth) { const auto parsed = parse_http_url(url); httplib::Client client(http_origin(parsed)); - client.set_follow_location(true); - client.set_connection_timeout(60, 0); - client.set_read_timeout(head ? 60 : 300, 0); - client.set_write_timeout(60, 0); -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT - client.enable_server_certificate_verification(true); -#endif + configure_client(client, head ? 60 : 300); - httplib::Headers request_headers{{"User-Agent", "audio.cpp native model manager/1.0"}}; - const auto token = huggingface_token(); - if (!token.empty()) { - request_headers.emplace("Authorization", "Bearer " + token); - } + const auto headers = request_headers(auth); uint64_t downloaded = 0; bool write_failed = false; @@ -369,8 +417,8 @@ HttpResult http_request( }; httplib::Result response = head - ? client.Head(parsed.path, request_headers) - : client.Get(parsed.path, request_headers, receiver, keep_downloading); + ? client.Head(parsed.path, headers) + : client.Get(parsed.path, headers, receiver, keep_downloading); if (!response) { if (cancelled && cancelled->load()) throw Cancelled(); if (write_failed) throw std::runtime_error("could not write downloaded model file"); @@ -385,8 +433,104 @@ HttpResult http_request( return result; } -RemoteFileInfo remote_info(const Package & package, const std::string & remote) { - const auto response = http_request(hf_url(package, remote), true, nullptr, {}, {}); +HttpResult http_get_body(const std::string & url, std::string & body, RequestAuth auth) { + const auto parsed = parse_http_url(url); + httplib::Client client(http_origin(parsed)); + configure_client(client, 60); + auto response = client.Get(parsed.path, request_headers(auth)); + if (!response) { + throw std::runtime_error("model host request failed: " + httplib::to_string(response.error())); + } + HttpResult result; + result.status = response->status; + for (const auto & [name, value] : response->headers) { + result.headers[lower(name)] = value; + } + body = std::move(response->body); + return result; +} + +using RemoteFileMap = std::map; + +// ModelScope resolve HEAD responses carry no Content-Length or ETag, so +// per-file size and checksum come from the repo file-list API instead. The +// listing is fetched once per repo+revision and shared through this cache. +using ListingCache = std::map; + +RemoteFileMap fetch_modelscope_listing(const Package & package) { + std::string body; + const auto response = http_get_body(ms_files_url(package), body, RequestAuth::modelscope); + if (response.status < 200 || response.status >= 300) { + throw std::runtime_error("ModelScope repo listing is not accessible: " + package.repo + + " (HTTP " + std::to_string(response.status) + ")"); + } + const auto root = json::parse(body); + if (json::optional_i64(root, "Code", 0) != 200) { + throw std::runtime_error("ModelScope repo listing failed for " + package.repo + ": " + + json::optional_string(root, "Message", "unknown error")); + } + const auto * data = root.find("Data"); + const auto * files = data != nullptr ? data->find("Files") : nullptr; + if (files == nullptr || !files->is_array()) { + throw std::runtime_error("ModelScope repo listing has no file list: " + package.repo); + } + RemoteFileMap listing; + for (const auto & item : files->as_array()) { + if (json::optional_string(item, "Type", "") != "blob") continue; + const auto path = json::optional_string(item, "Path", ""); + if (path.empty()) continue; + RemoteFileInfo info; + info.etag = json::optional_string(item, "Sha256", ""); + const auto size = json::optional_i64(item, "Size", -1); + if (size >= 0) info.size = static_cast(size); + listing.emplace(path, std::move(info)); + } + return listing; +} + +RemoteFileInfo modelscope_remote_info( + const Package & package, + const std::string & remote, + ListingCache & cache) { + const auto key = package.repo + "\n" + package.revision; + auto found = cache.find(key); + if (found == cache.end()) { + RemoteFileMap listing; + try { + listing = fetch_modelscope_listing(package); + } catch (...) { + listing.clear(); + } + found = cache.emplace(key, std::move(listing)).first; + } + const auto file = found->second.find(remote); + if (file != found->second.end()) return file->second; + if (!found->second.empty()) { + throw std::runtime_error("remote file is not accessible: " + package.repo + "/" + remote + + " (not in the ModelScope repo listing)"); + } + // The listing API is unreachable; fall back to a HEAD on the resolve URL, + // which reports the file checksum as X-Linked-Etag but no size. + const auto response = http_request(ms_url(package, remote), true, nullptr, {}, {}, RequestAuth::modelscope); + if (response.status < 200 || response.status >= 300) { + throw std::runtime_error("remote file is not accessible: " + package.repo + "/" + remote + + " (HTTP " + std::to_string(response.status) + ")"); + } + RemoteFileInfo result; + const auto etag = response.headers.find("x-linked-etag"); + if (etag != response.headers.end()) result.etag = trim_quotes(etag->second); + return result; +} + +RemoteFileInfo remote_info(const Package & package, const std::string & remote, ListingCache * ms_cache) { + if (package.download_kind == "modelscope_snapshot") { + if (ms_cache == nullptr) { + ListingCache local; + return modelscope_remote_info(package, remote, local); + } + return modelscope_remote_info(package, remote, *ms_cache); + } + const auto response = http_request(hf_url(package, remote), true, nullptr, {}, {}, RequestAuth::huggingface); if (response.status == 401 || response.status == 403) { if (package.gated) return {}; } @@ -415,11 +559,16 @@ void download_file( std::filesystem::create_directories(destination.parent_path()); std::ofstream output(destination, std::ios::binary | std::ios::trunc); if (!output) throw std::runtime_error("could not create " + destination.string()); - const auto response = http_request(hf_url(package, remote), false, &output, cancelled, progress); + const auto url = package.download_kind == "modelscope_snapshot" + ? ms_url(package, remote) + : hf_url(package, remote); + const auto response = http_request(url, false, &output, cancelled, progress, package_auth(package)); output.close(); if (response.status == 401 || response.status == 403) { throw std::runtime_error(package.repo + "/" + remote + - " requires accepted Hugging Face access and a valid HF token"); + (package.download_kind == "modelscope_snapshot" + ? " requires ModelScope access to this repo" + : " requires accepted Hugging Face access and a valid HF token")); } if (response.status < 200 || response.status >= 300) { throw std::runtime_error("failed to download " + package.repo + "/" + remote + @@ -513,11 +662,12 @@ std::string PackageManager::install( } std::map remote_files; + ListingCache ms_cache; uint64_t total = 0; bool known_total = true; for (const auto & [remote, output] : downloads) { (void) output; - auto info = remote_info(package, remote); + auto info = remote_info(package, remote, &ms_cache); if (!info.size) known_total = false; else total += *info.size; remote_files.emplace(remote, std::move(info)); } @@ -568,7 +718,7 @@ std::string PackageManager::install( // Include reused sidecars in the per-package manifest as well. for (const auto & [remote, output] : plan) { (void) output; - if (remote_files.count(remote) == 0) remote_files.emplace(remote, remote_info(package, remote)); + if (remote_files.count(remote) == 0) remote_files.emplace(remote, remote_info(package, remote, &ms_cache)); } json::Value::Object manifest; manifest["schema_version"] = number_value(1); @@ -659,6 +809,7 @@ std::string PackageManager::inventory(bool query_remote, const std::string & pac workers.reserve(worker_count); for (size_t worker = 0; worker < worker_count; ++worker) { workers.push_back(std::async(std::launch::async, [this, query_remote, &selected, &rows, &next] { + ListingCache ms_cache; for (;;) { const auto index = next.fetch_add(1); if (index >= selected.size()) return; @@ -683,7 +834,7 @@ std::string PackageManager::inventory(bool query_remote, const std::string & pac std::map remote; std::string revision; for (const auto & file : package.files) { - auto info = remote_info(package, file); + auto info = remote_info(package, file, &ms_cache); if (!info.size) size_known = false; else total += *info.size; if (revision.empty()) revision = info.revision; remote.emplace(file, std::move(info)); diff --git a/src/framework/sampling/hf_sampler.cpp b/src/framework/sampling/hf_sampler.cpp index f6cb8cf65..fbbebb6cd 100644 --- a/src/framework/sampling/hf_sampler.cpp +++ b/src/framework/sampling/hf_sampler.cpp @@ -327,6 +327,57 @@ void HfLogitsProcessor::apply_top_p( scratch.probabilities_scores_size_ = scores.size(); } +void HfLogitsProcessor::apply_min_p( + std::vector & scores, + float min_p, + int64_t min_tokens_to_keep, + HfSamplerScratch & scratch) { + require_min_tokens(min_tokens_to_keep); + if (min_p < 0.0F || min_p > 1.0F || !std::isfinite(min_p)) { + throw std::runtime_error("HF sampler min_p must be finite and in [0, 1]"); + } + if (min_p == 0.0F) { + return; + } + if (scores.empty()) { + return; + } + auto & order = scratch.candidates_; + order.clear(); + order.reserve(scores.size()); + for (size_t index = 0; index < scores.size(); ++index) { + if (std::isfinite(scores[index])) { + order.push_back(static_cast(index)); + } + } + if (order.empty()) { + return; + } + const size_t min_keep = std::min( + static_cast(min_tokens_to_keep), order.size()); + std::partial_sort( + order.begin(), + order.begin() + static_cast(min_keep), + order.end(), + [&](int32_t lhs, int32_t rhs) { + const float lhs_score = scores[static_cast(lhs)]; + const float rhs_score = scores[static_cast(rhs)]; + return lhs_score == rhs_score ? lhs < rhs : lhs_score > rhs_score; + }); + const float max_score = scores[static_cast(order.front())]; + const float threshold = max_score + std::log(min_p); + const float protected_threshold = + scores[static_cast(order[min_keep - 1])]; + for (float & score : scores) { + if (score < threshold && score < protected_threshold) { + score = -std::numeric_limits::infinity(); + } + } + scratch.probabilities_ready_ = false; + scratch.probabilities_scores_data_ = nullptr; + scratch.probabilities_scores_size_ = 0; +} + void HfLogitsProcessor::apply_temperature(std::vector & scores, float temperature) { if (!(temperature > 0.0F) || !std::isfinite(temperature)) { throw std::runtime_error("HF sampler temperature must be finite and positive"); @@ -434,7 +485,8 @@ int32_t HfSampler::sample( const bool needs_repetition_penalty = options.repetition_penalty != 1.0F && !history.empty(); const bool needs_sampling_processors = options.do_sample && - (options.temperature != 1.0F || options.top_k > 0 || options.top_p < 1.0F); + (options.temperature != 1.0F || options.top_k > 0 || + options.top_p < 1.0F || options.min_p > 0.0F); if (!needs_repetition_penalty && !needs_sampling_processors) { if (!options.do_sample) { return HfLogitsProcessor::argmax(logits.data(), logits.size(), context); @@ -452,6 +504,7 @@ int32_t HfSampler::sample( HfLogitsProcessor::apply_temperature(scores, options.temperature); HfLogitsProcessor::apply_top_k(scores, options.top_k, options.min_tokens_to_keep, scratch); HfLogitsProcessor::apply_top_p(scores, options.top_p, options.min_tokens_to_keep, scratch); + HfLogitsProcessor::apply_min_p(scores, options.min_p, options.min_tokens_to_keep, scratch); return HfTokenSampler::sample_from_processed_scores(scores, scratch, fallback_rng, torch_state, context, true); } diff --git a/src/framework/tokenizers/llama_bpe.cpp b/src/framework/tokenizers/llama_bpe.cpp index 80219614a..ca4cfa4ca 100644 --- a/src/framework/tokenizers/llama_bpe.cpp +++ b/src/framework/tokenizers/llama_bpe.cpp @@ -251,10 +251,27 @@ void load_merges(const std::filesystem::path & merges_path, BpeVocabulary & voca } } +std::string replace_spaces(std::string text, const std::string & replacement) { + if (replacement.empty()) { + return text; + } + std::string out; + out.reserve(text.size()); + for (const char ch : text) { + if (ch == ' ') { + out += replacement; + } else { + out.push_back(ch); + } + } + return out; +} + } // namespace struct LlamaBpeTokenizer::Impl { explicit Impl(const LlamaBpeTokenizerSpec & spec) { + normalizer_space_replacement = spec.normalizer_space_replacement; vocab.pre_type = convert_pre_type(spec.pre_type); const bool has_vocab = !spec.vocab_path.empty(); const bool has_merges = !spec.merges_path.empty(); @@ -283,6 +300,7 @@ struct LlamaBpeTokenizer::Impl { } BpeVocabulary vocab; + std::string normalizer_space_replacement; }; LlamaBpeTokenizer::LlamaBpeTokenizer(LlamaBpeTokenizerSpec spec) @@ -297,11 +315,11 @@ TokenizedText LlamaBpeTokenizer::tokenize(const std::string & text) const { } std::vector LlamaBpeTokenizer::encode(const std::string & text) const { - return vendor::tokenize_bpe(impl_->vocab, text, true); + return vendor::tokenize_bpe(impl_->vocab, replace_spaces(text, impl_->normalizer_space_replacement), true); } std::vector LlamaBpeTokenizer::encode(const std::string & text, const bool parse_special) const { - return vendor::tokenize_bpe(impl_->vocab, text, parse_special); + return vendor::tokenize_bpe(impl_->vocab, replace_spaces(text, impl_->normalizer_space_replacement), parse_special); } std::string LlamaBpeTokenizer::decode(const std::vector & token_ids, const bool skip_special_tokens) const { diff --git a/src/models/breeze_tts/assets.cpp b/src/models/breeze_tts/assets.cpp new file mode 100644 index 000000000..ad8f5f25a --- /dev/null +++ b/src/models/breeze_tts/assets.cpp @@ -0,0 +1,177 @@ +#include "engine/models/breeze_tts/assets.h" + +#include "engine/framework/io/json.h" +#include "engine/framework/model_spec/package.h" + +#include +#include + +namespace engine::models::breeze_tts { +namespace { + +constexpr const char * kFamily = "breeze_tts"; + +int64_t nested_i64(const engine::io::json::Value & object, const std::string & key, int64_t fallback) { + return engine::io::json::optional_i64(object, key, fallback); +} + +float nested_f32(const engine::io::json::Value & object, const std::string & key, float fallback) { + return engine::io::json::optional_f32(object, key, fallback); +} + +float rope_theta_for_type( + const engine::io::json::Value & rope_parameters, + const std::string & layer_type, + float fallback) { + const auto * object = rope_parameters.find(layer_type); + if (object == nullptr || !object->is_object()) { + return fallback; + } + return nested_f32(*object, "rope_theta", fallback); +} + +float rope_freq_scale_for_type( + const engine::io::json::Value & rope_parameters, + const std::string & layer_type, + float fallback) { + const auto * object = rope_parameters.find(layer_type); + if (object == nullptr || !object->is_object()) { + return fallback; + } + const std::string rope_type = engine::io::json::optional_string(*object, "rope_type", "default"); + if (rope_type != "linear") { + return 1.0F; + } + const float factor = nested_f32(*object, "factor", 1.0F); + if (!(factor > 0.0F)) { + throw std::runtime_error("BreezeTTS text rope linear factor must be positive"); + } + return 1.0F / factor; +} + +void parse_config(const engine::io::json::Value & root, BreezeTTSConfig & config) { + config.hidden_size = nested_i64(root, "hidden_size", config.hidden_size); + config.intermediate_size = nested_i64(root, "intermediate_size", config.intermediate_size); + config.layers = nested_i64(root, "num_hidden_layers", config.layers); + config.heads = nested_i64(root, "num_attention_heads", config.heads); + config.kv_heads = nested_i64(root, "num_key_value_heads", config.kv_heads); + config.head_dim = nested_i64(root, "head_dim", config.head_dim); + config.vocab_size = nested_i64(root, "vocab_size", config.vocab_size); + config.lm_head_size = config.vocab_size + 1; + config.text_vocab_size = nested_i64(root, "text_vocab_size", config.text_vocab_size); + config.num_codebooks = nested_i64(root, "num_codebooks", config.num_codebooks); + config.max_position_embeddings = nested_i64(root, "max_position_embeddings", config.max_position_embeddings); + config.rms_norm_eps = nested_f32(root, "rms_norm_eps", config.rms_norm_eps); + config.rope_theta = nested_f32(root, "rope_theta", config.rope_theta); + if (const auto * rope = root.find("rope_scaling"); rope != nullptr && rope->is_object()) { + config.rope_scaling_enabled = true; + config.rope_scaling_factor = nested_f32(*rope, "factor", config.rope_scaling_factor); + config.rope_low_freq_factor = nested_f32(*rope, "low_freq_factor", config.rope_low_freq_factor); + config.rope_high_freq_factor = nested_f32(*rope, "high_freq_factor", config.rope_high_freq_factor); + config.rope_original_max_position_embeddings = nested_i64( + *rope, + "original_max_position_embeddings", + config.rope_original_max_position_embeddings); + } + if (const auto * backbone = root.find("backbone_config"); backbone != nullptr && backbone->is_object()) { + config.hidden_size = nested_i64(*backbone, "hidden_size", config.hidden_size); + config.intermediate_size = nested_i64(*backbone, "intermediate_size", config.intermediate_size); + config.layers = nested_i64(*backbone, "num_hidden_layers", config.layers); + config.heads = nested_i64(*backbone, "num_attention_heads", config.heads); + config.kv_heads = nested_i64(*backbone, "num_key_value_heads", config.kv_heads); + config.head_dim = nested_i64(*backbone, "head_dim", config.head_dim); + config.rms_norm_eps = nested_f32(*backbone, "rms_norm_eps", config.rms_norm_eps); + config.rope_theta = nested_f32(*backbone, "rope_theta", config.rope_theta); + config.rope_scaling_enabled = false; + if (const auto * rope = backbone->find("rope_scaling"); rope != nullptr && rope->is_object()) { + config.rope_scaling_enabled = true; + config.rope_scaling_factor = nested_f32(*rope, "factor", config.rope_scaling_factor); + config.rope_low_freq_factor = nested_f32(*rope, "low_freq_factor", config.rope_low_freq_factor); + config.rope_high_freq_factor = nested_f32(*rope, "high_freq_factor", config.rope_high_freq_factor); + config.rope_original_max_position_embeddings = nested_i64( + *rope, + "original_max_position_embeddings", + config.rope_original_max_position_embeddings); + } + } + config.audio_token_id = nested_i64(root, "audio_token_id", config.audio_token_id); + config.audio_eos_token_id = nested_i64(root, "audio_eos_token_id", config.audio_eos_token_id); + config.codebook_pad_token_id = nested_i64(root, "codebook_pad_token_id", config.codebook_pad_token_id); + config.codebook_eos_token_id = nested_i64(root, "codebook_eos_token_id", config.codebook_eos_token_id); + + if (const auto * text = root.find("text_encoder_config"); text != nullptr && text->is_object()) { + config.text_hidden_size = nested_i64(*text, "hidden_size", config.text_hidden_size); + config.text_intermediate_size = nested_i64(*text, "intermediate_size", config.text_intermediate_size); + config.text_layers = nested_i64(*text, "num_hidden_layers", config.text_layers); + config.text_heads = nested_i64(*text, "num_attention_heads", config.text_heads); + config.text_kv_heads = nested_i64(*text, "num_key_value_heads", config.text_kv_heads); + config.text_head_dim = nested_i64(*text, "head_dim", config.text_head_dim); + config.text_max_position_embeddings = nested_i64(*text, "max_position_embeddings", config.text_max_position_embeddings); + config.text_rms_norm_eps = nested_f32(*text, "rms_norm_eps", config.text_rms_norm_eps); + config.text_query_pre_attn_scalar = nested_f32(*text, "query_pre_attn_scalar", config.text_query_pre_attn_scalar); + if (const auto * rope = text->find("rope_parameters"); rope != nullptr && rope->is_object()) { + if (const auto * full = rope->find("full_attention"); full != nullptr && full->is_object()) { + config.text_rope_theta = nested_f32(*full, "rope_theta", config.text_rope_theta); + config.text_rope_linear_factor = nested_f32(*full, "factor", config.text_rope_linear_factor); + } + const auto layer_types = engine::io::json::optional_string_array(*text, "layer_types"); + if (!layer_types.empty()) { + if (static_cast(layer_types.size()) != config.text_layers) { + throw std::runtime_error("BreezeTTS text layer_types must match text layer count"); + } + config.text_layer_rope_theta.clear(); + config.text_layer_rope_freq_scale.clear(); + config.text_layer_rope_theta.reserve(layer_types.size()); + config.text_layer_rope_freq_scale.reserve(layer_types.size()); + for (const auto & layer_type : layer_types) { + config.text_layer_rope_theta.push_back(rope_theta_for_type(*rope, layer_type, config.text_rope_theta)); + config.text_layer_rope_freq_scale.push_back( + rope_freq_scale_for_type(*rope, layer_type, 1.0F / config.text_rope_linear_factor)); + } + } + } + } + if (const auto * depth = root.find("depth_decoder_config"); depth != nullptr && depth->is_object()) { + config.depth_hidden_size = nested_i64(*depth, "hidden_size", config.depth_hidden_size); + config.depth_intermediate_size = nested_i64(*depth, "intermediate_size", config.depth_intermediate_size); + config.depth_layers = nested_i64(*depth, "num_hidden_layers", config.depth_layers); + config.depth_heads = nested_i64(*depth, "num_attention_heads", config.depth_heads); + config.depth_kv_heads = nested_i64(*depth, "num_key_value_heads", config.depth_kv_heads); + config.depth_head_dim = nested_i64(*depth, "head_dim", config.depth_head_dim); + config.depth_rms_norm_eps = nested_f32(*depth, "rms_norm_eps", config.depth_rms_norm_eps); + config.depth_rope_theta = nested_f32(*depth, "rope_theta", config.depth_rope_theta); + config.depth_rope_scaling_enabled = false; + if (const auto * rope = depth->find("rope_scaling"); rope != nullptr && rope->is_object()) { + config.depth_rope_scaling_enabled = true; + config.depth_rope_scaling_factor = nested_f32(*rope, "factor", config.depth_rope_scaling_factor); + config.depth_rope_low_freq_factor = nested_f32(*rope, "low_freq_factor", config.depth_rope_low_freq_factor); + config.depth_rope_high_freq_factor = nested_f32(*rope, "high_freq_factor", config.depth_rope_high_freq_factor); + config.depth_rope_original_max_position_embeddings = nested_i64( + *rope, + "original_max_position_embeddings", + config.depth_rope_original_max_position_embeddings); + } + } +} + +void validate_shapes(const engine::assets::TensorSource & source, const BreezeTTSConfig & config) { + engine::assets::require_tensor_shape(source, "embed_text_tokens.weight", {config.text_vocab_size, config.hidden_size}); + engine::assets::require_tensor_shape(source, "text_encoder_proj.weight", {config.hidden_size, config.text_hidden_size}); + engine::assets::require_tensor_shape(source, "lm_head.weight", {config.lm_head_size, config.hidden_size}); + engine::assets::require_tensor_shape(source, "depth_decoder.model.embed_tokens.weight", {config.num_codebooks * config.vocab_size, config.hidden_size}); + engine::assets::require_tensor_shape(source, "depth_decoder.codebooks_head.weight", {config.num_codebooks - 1, config.depth_hidden_size, config.vocab_size}); +} + +} // namespace + +std::shared_ptr load_breeze_tts_assets(const std::filesystem::path & model_path) { + auto assets = std::make_shared(); + assets->resources = engine::model_spec::load_resource_bundle_for_family(model_path, kFamily); + assets->model_root = assets->resources.model_root(); + assets->weights = assets->resources.open_tensor_source("model_weights"); + parse_config(assets->resources.parse_json("config_json"), assets->config); + validate_shapes(*assets->weights, assets->config); + return assets; +} + +} // namespace engine::models::breeze_tts diff --git a/src/models/breeze_tts/generator.cpp b/src/models/breeze_tts/generator.cpp new file mode 100644 index 000000000..54b5e360b --- /dev/null +++ b/src/models/breeze_tts/generator.cpp @@ -0,0 +1,1203 @@ +#include "engine/models/breeze_tts/generator.h" + +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/transformers/qwen_causal_decode_runtime.h" +#include "engine/framework/modules/weight_binding.h" +#include "engine/framework/sampling/hf_sampler.h" +#include "engine/framework/sampling/torch_random.h" +#include "engine/models/breeze_tts/speech_decoder.h" +#include "engine/models/breeze_tts/speech_encoder.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::breeze_tts { +namespace { + +namespace assets = engine::assets; +namespace binding = engine::modules::binding; +namespace core = engine::core; +namespace modules = engine::modules; +namespace runtime = engine::runtime; +namespace sampling = engine::sampling; +using Clock = std::chrono::steady_clock; + +constexpr int64_t kCodecCodebookSize = 2048; +constexpr float kRepetitionPenalty = 1.1F; + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +// The official Breeze-TTS 2 inference runs the backbone and depth decoder with +// bf16 activations and a bf16 KV cache. Pure fp32 activations measurably drift +// into degenerate trajectories on some prompts (mispronunciations, repetition +// collapse), so match the reference bf16 behavior on GPU backends. +modules::QwenDecoderActivationCastPolicy breeze_bf16_activation_policy(core::BackendType backend_type) { + modules::QwenDecoderActivationCastPolicy policy; + if (backend_type != core::BackendType::Cuda && backend_type != core::BackendType::Hip && + backend_type != core::BackendType::Vulkan) { + return policy; + } + policy.enabled = true; + policy.type = GGML_TYPE_BF16; + // CUDA/HIP/Vulkan implement the fused round-to-bf16 unary op. + policy.fused_round = backend_type == core::BackendType::Cuda || backend_type == core::BackendType::Hip || + backend_type == core::BackendType::Vulkan; + policy.after_input_norm = true; + policy.after_qkv_projection = true; + policy.after_qk_norm = true; + policy.after_rope = true; + policy.after_static_cache_update = true; + policy.after_attention = true; + policy.after_attention_output = true; + policy.after_residual = true; + policy.after_ffn_norm = true; + policy.after_mlp_projection = true; + policy.after_mlp_silu = true; + policy.after_mlp_mul = true; + policy.after_output = true; + return policy; +} + +modules::QwenCausalDecodeRuntimeConfig backbone_config( + const BreezeTTSConfig & config, + core::BackendType backend_type, + size_t graph_arena_bytes, + bool allow_flash_attention = true) { + modules::QwenCausalDecodeRuntimeConfig out; + out.trace_name = "breeze_tts.backbone"; + out.prefill_graph_arena_bytes = graph_arena_bytes; + out.decode_graph_arena_bytes = graph_arena_bytes; + out.decoder.stack.hidden_size = config.hidden_size; + out.decoder.stack.num_attention_heads = config.heads; + out.decoder.stack.num_key_value_heads = config.kv_heads; + out.decoder.stack.head_dim = config.head_dim; + out.decoder.stack.intermediate_size = config.intermediate_size; + out.decoder.stack.layers = config.layers; + out.decoder.stack.rms_norm_eps = config.rms_norm_eps; + out.decoder.stack.rope_theta = config.rope_theta; + out.decoder.stack.rope_type = GGML_ROPE_TYPE_NEOX; + out.decoder.stack.use_qk_norm = true; + out.decoder.stack.qkv_layout = modules::QwenDecoderQKVLayout::PackedQKV; + out.decoder.stack.runtime.mlp.mode = modules::QwenDecoderMLPMode::PackedGateUp; + out.decoder.stack.attention_precision = GGML_PREC_F32; + out.decoder.stack.projection_precision = GGML_PREC_DEFAULT; + // Eager graph for GPUs without a flash kernel (e.g. sm70). + out.decoder.stack.runtime.attention.allow_flash_attention = allow_flash_attention; + if (allow_flash_attention) { + out.decoder.stack.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.decoder.stack.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + } else { + out.decoder.stack.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::ManualRepeat; + out.decoder.stack.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::ManualRepeat; + } + out.decoder.stack.runtime.static_cache.update_mode = modules::QwenDecoderStaticCacheUpdateMode::DirectSetRows; + out.decoder.stack.runtime.static_cache.set_rows_mode = modules::QwenDecoderStaticCacheSetRowsMode::BackendViewOptimized; + if (backend_type == core::BackendType::Cuda || backend_type == core::BackendType::Hip || + backend_type == core::BackendType::Vulkan) { + // BF16 KV cache matches the reference implementation, but flash + // attention only accelerates bf16 cache with native bf16 MMA + // (sm_80+); on older parts it is ~3x slower, so only HIP uses it. + out.decoder.static_cache_type = + backend_type == core::BackendType::Hip ? GGML_TYPE_BF16 : GGML_TYPE_F16; + out.decoder.stack.activation_cast = breeze_bf16_activation_policy(backend_type); + } + out.decoder.logits_size = config.lm_head_size; + out.decoder.logits_mode = modules::QwenCausalDecoderLogitsMode::LastStep; + out.output_mode = modules::QwenCausalDecodeOutputMode::Logits; + out.return_hidden = true; + out.logits_readback_token_ids.reserve(static_cast(config.lm_head_size)); + for (int32_t token = 0; token < static_cast(config.lm_head_size); ++token) { + out.logits_readback_token_ids.push_back(token); + } + out.decoder.lm_head_input_type = GGML_TYPE_F32; + return out; +} + +modules::QwenCausalDecodeRuntimeConfig depth_config( + const BreezeTTSConfig & config, + core::BackendType backend_type, + size_t graph_arena_bytes, + bool allow_flash_attention = true) { + modules::QwenCausalDecodeRuntimeConfig out; + out.trace_name = "breeze_tts.depth_decoder"; + out.prefill_graph_arena_bytes = graph_arena_bytes; + out.decode_graph_arena_bytes = graph_arena_bytes; + out.decoder.stack.hidden_size = config.depth_hidden_size; + out.decoder.stack.num_attention_heads = config.depth_heads; + out.decoder.stack.num_key_value_heads = config.depth_kv_heads; + out.decoder.stack.head_dim = config.depth_head_dim; + out.decoder.stack.intermediate_size = config.depth_intermediate_size; + out.decoder.stack.layers = config.depth_layers; + out.decoder.stack.rms_norm_eps = config.depth_rms_norm_eps; + out.decoder.stack.rope_theta = config.depth_rope_theta; + out.decoder.stack.rope_type = GGML_ROPE_TYPE_NEOX; + out.decoder.stack.use_qk_norm = false; + out.decoder.stack.qkv_layout = modules::QwenDecoderQKVLayout::PackedQKV; + out.decoder.stack.runtime.mlp.mode = modules::QwenDecoderMLPMode::PackedGateUp; + out.decoder.stack.attention_precision = GGML_PREC_F32; + out.decoder.stack.projection_precision = GGML_PREC_DEFAULT; + out.decoder.stack.runtime.attention.allow_flash_attention = allow_flash_attention; + if (allow_flash_attention) { + out.decoder.stack.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.decoder.stack.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + } else { + out.decoder.stack.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::ManualRepeat; + out.decoder.stack.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::ManualRepeat; + } + out.decoder.stack.runtime.static_cache.update_mode = modules::QwenDecoderStaticCacheUpdateMode::DirectSetRows; + out.decoder.stack.runtime.static_cache.set_rows_mode = modules::QwenDecoderStaticCacheSetRowsMode::BackendViewOptimized; + if (backend_type == core::BackendType::Cuda || backend_type == core::BackendType::Hip || + backend_type == core::BackendType::Vulkan) { + // See backbone_config: only HIP uses a bf16 KV cache; CUDA and Vulkan + // keep F16. + out.decoder.static_cache_type = + backend_type == core::BackendType::Hip ? GGML_TYPE_BF16 : GGML_TYPE_F16; + out.decoder.stack.activation_cast = breeze_bf16_activation_policy(backend_type); + } + out.decoder.logits_mode = modules::QwenCausalDecoderLogitsMode::LastStep; + out.output_mode = modules::QwenCausalDecodeOutputMode::Hidden; + out.return_hidden = true; + return out; +} + +std::vector llama3_rope_factors( + int64_t head_dim, + float rope_theta, + float scaling_factor, + float low_freq_factor, + float high_freq_factor, + int64_t original_max_position_embeddings) { + constexpr double pi = 3.14159265358979323846; + const double low_wavelength = + static_cast(original_max_position_embeddings) / + static_cast(low_freq_factor); + const double high_wavelength = + static_cast(original_max_position_embeddings) / + static_cast(high_freq_factor); + std::vector out(static_cast(head_dim / 2), 1.0F); + for (int64_t index = 0; index < head_dim / 2; ++index) { + const double inv_freq = 1.0 / std::pow( + static_cast(rope_theta), + static_cast(2 * index) / static_cast(head_dim)); + const double wavelength = 2.0 * pi / inv_freq; + double scaled = inv_freq; + if (wavelength > low_wavelength) { + scaled = inv_freq / static_cast(scaling_factor); + } else if (wavelength >= high_wavelength) { + const double smooth = + (static_cast(original_max_position_embeddings) / wavelength - + static_cast(low_freq_factor)) / + (static_cast(high_freq_factor) - + static_cast(low_freq_factor)); + scaled = + (1.0 - smooth) * inv_freq / static_cast(scaling_factor) + + smooth * inv_freq; + } + out[static_cast(index)] = static_cast(inv_freq / scaled); + } + return out; +} + +// Pack [a; b; ...] projection rows into a single tensor, matching the +// higgs_audio_tts loader: fewer, larger matmuls per layer. Parts may have +// different row counts (e.g. q vs k/v in GQA models). +core::TensorValue pack_projection_rows( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::vector> & parts, + assets::TensorStorageType storage_type, + int64_t in_dim) { + std::vector packed; + int64_t total_out = 0; + ggml_type packed_type = GGML_TYPE_COUNT; + for (const auto & [name, out_dim] : parts) { + const auto part = source.require_tensor(name, storage_type, {out_dim, in_dim}); + if (packed_type == GGML_TYPE_COUNT) { + packed_type = part.type; + } else if (part.type != packed_type) { + throw std::runtime_error("BreezeTTS packed projection weights require matching storage types"); + } + packed.insert(packed.end(), part.bytes.begin(), part.bytes.end()); + total_out += out_dim; + } + return store.make_tensor( + core::TensorShape::from_dims({total_out, in_dim}), + packed_type, + packed.data(), + packed.size()); +} + +modules::QwenDecoderLayerWeights load_backbone_layer( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const BreezeTTSConfig & config, + assets::TensorStorageType storage_type, + const std::optional & rope_factors, + int64_t layer) { + const std::string prefix = "backbone_model.layers." + std::to_string(layer); + const int64_t q_out = config.heads * config.head_dim; + const int64_t kv_out = config.kv_heads * config.head_dim; + modules::QwenDecoderLayerWeights out; + out.input_norm = binding::norm_weight_from_source(store, source, prefix + ".input_layernorm", config.hidden_size); + // Packed layout is [q; k; v] with row counts q_out, kv_out, kv_out. + out.self_attention.qkv_weight = pack_projection_rows( + store, + source, + {{prefix + ".self_attn.q_proj.weight", q_out}, + {prefix + ".self_attn.k_proj.weight", kv_out}, + {prefix + ".self_attn.v_proj.weight", kv_out}}, + storage_type, + config.hidden_size); + out.self_attention.out_weight = store.load_tensor(source, prefix + ".self_attn.o_proj.weight", storage_type, {config.hidden_size, config.heads * config.head_dim}); + out.q_norm = binding::norm_weight_from_source(store, source, prefix + ".self_attn.q_norm", config.head_dim); + out.k_norm = binding::norm_weight_from_source(store, source, prefix + ".self_attn.k_norm", config.head_dim); + out.post_norm = binding::norm_weight_from_source(store, source, prefix + ".post_attention_layernorm", config.hidden_size); + out.mlp.gate_up_proj = modules::LinearWeights{ + pack_projection_rows( + store, + source, + {{prefix + ".mlp.gate_proj.weight", config.intermediate_size}, + {prefix + ".mlp.up_proj.weight", config.intermediate_size}}, + storage_type, + config.hidden_size), + std::nullopt, + }; + out.mlp.down_proj = binding::linear_from_source(store, source, prefix + ".mlp.down_proj", storage_type, config.hidden_size, config.intermediate_size, false); + out.rope_frequency_factors = rope_factors; + return out; +} + +modules::QwenDecoderLayerWeights load_depth_layer( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const BreezeTTSConfig & config, + assets::TensorStorageType storage_type, + const std::optional & rope_factors, + int64_t layer) { + const std::string prefix = "depth_decoder.model.layers." + std::to_string(layer); + const int64_t q_out = config.depth_heads * config.depth_head_dim; + const int64_t kv_out = config.depth_kv_heads * config.depth_head_dim; + modules::QwenDecoderLayerWeights out; + out.input_norm = binding::norm_weight_from_source(store, source, prefix + ".input_layernorm", config.depth_hidden_size); + // Packed layout is [q; k; v] with row counts q_out, kv_out, kv_out. + out.self_attention.qkv_weight = pack_projection_rows( + store, + source, + {{prefix + ".self_attn.q_proj.weight", q_out}, + {prefix + ".self_attn.k_proj.weight", kv_out}, + {prefix + ".self_attn.v_proj.weight", kv_out}}, + storage_type, + config.depth_hidden_size); + out.self_attention.out_weight = store.load_tensor(source, prefix + ".self_attn.o_proj.weight", storage_type, {config.depth_hidden_size, config.depth_heads * config.depth_head_dim}); + out.post_norm = binding::norm_weight_from_source(store, source, prefix + ".post_attention_layernorm", config.depth_hidden_size); + out.mlp.gate_up_proj = modules::LinearWeights{ + pack_projection_rows( + store, + source, + {{prefix + ".mlp.gate_proj.weight", config.depth_intermediate_size}, + {prefix + ".mlp.up_proj.weight", config.depth_intermediate_size}}, + storage_type, + config.depth_hidden_size), + std::nullopt, + }; + out.mlp.down_proj = binding::linear_from_source(store, source, prefix + ".mlp.down_proj", storage_type, config.depth_hidden_size, config.depth_intermediate_size, false); + out.rope_frequency_factors = rope_factors; + return out; +} + +std::vector frame_embedding( + const std::vector & table, + int64_t rows, + int64_t dim, + int64_t vocab, + const std::vector & codes) { + std::vector out(static_cast(dim), 0.0F); + for (int64_t codebook = 0; codebook < static_cast(codes.size()); ++codebook) { + const int64_t row = codebook * vocab + codes[static_cast(codebook)]; + if (row < 0 || row >= rows) { + throw std::runtime_error("BreezeTTS audio code is outside embedding table"); + } + const size_t begin = static_cast(row * dim); + for (int64_t i = 0; i < dim; ++i) { + out[static_cast(i)] += table[begin + static_cast(i)]; + } + } + return out; +} + +void suppress_reserved(std::vector & logits, int64_t codebook_size, int64_t vocab_size) { + for (int64_t token = codebook_size; token < vocab_size; ++token) { + if (token >= 0 && token < static_cast(logits.size())) { + logits[static_cast(token)] = -std::numeric_limits::infinity(); + } + } +} + +int32_t sample_logits( + std::vector logits, + const std::vector & history, + const sampling::HfSamplingOptions & options, + sampling::HfSamplerScratch & scratch, + std::mt19937 & fallback_rng, + const sampling::TorchCudaSamplingPolicy * policy, + uint64_t seed, + uint64_t & call_index, + uint64_t & offset_blocks, + std::string_view context) { + const sampling::HfTorchSamplingState torch_state{policy, seed, call_index, offset_blocks, true}; + const int32_t token = sampling::HfSampler{}.sample( + logits, + history, + options, + scratch, + fallback_rng, + policy != nullptr && policy->cuda_fast_path ? &torch_state : nullptr, + context); + ++call_index; + if (policy != nullptr && policy->cuda_fast_path) { + offset_blocks += sampling::torch_cuda_tensor_iterator_offset_blocks(static_cast(logits.size()), *policy); + } + return token; +} + +struct BreezeWeights { + std::shared_ptr store; + modules::QwenCausalDecodeRuntimeWeights backbone; + modules::QwenCausalDecodeRuntimeWeights depth; + std::vector audio_embedding; + modules::LinearWeights depth_projector; + core::TensorValue depth_heads; +}; + +std::shared_ptr load_weights( + const BreezeTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + assets::TensorStorageType storage_type, + const modules::QwenCausalDecodeRuntimeConfig & backbone_runtime_config) { + auto out = std::make_shared(); + out->store = std::make_shared( + execution.backend(), + execution.backend_type(), + "breeze_tts.generator.weights", + weight_context_bytes); + const auto & source = *assets.weights; + const auto & config = assets.config; + std::optional backbone_rope_factors; + if (config.rope_scaling_enabled) { + backbone_rope_factors = out->store->make_f32( + core::TensorShape::from_dims({config.head_dim / 2}), + llama3_rope_factors( + config.head_dim, + config.rope_theta, + config.rope_scaling_factor, + config.rope_low_freq_factor, + config.rope_high_freq_factor, + config.rope_original_max_position_embeddings)); + } + std::optional depth_rope_factors; + if (config.depth_rope_scaling_enabled) { + depth_rope_factors = out->store->make_f32( + core::TensorShape::from_dims({config.depth_head_dim / 2}), + llama3_rope_factors( + config.depth_head_dim, + config.depth_rope_theta, + config.depth_rope_scaling_factor, + config.depth_rope_low_freq_factor, + config.depth_rope_high_freq_factor, + config.depth_rope_original_max_position_embeddings)); + } + out->backbone.token_embedding = out->store->load_tensor( + source, + "depth_decoder.model.embed_tokens.weight", + storage_type, + {config.num_codebooks * config.vocab_size, config.hidden_size}); + out->backbone.stack.layers.reserve(static_cast(config.layers)); + for (int64_t layer = 0; layer < config.layers; ++layer) { + out->backbone.stack.layers.push_back(load_backbone_layer(*out->store, source, config, storage_type, backbone_rope_factors, layer)); + } + out->backbone.final_norm = binding::norm_weight_from_source(*out->store, source, "backbone_model.norm", config.hidden_size); + out->backbone.lm_head = binding::linear_from_source( + *out->store, + source, + "lm_head", + storage_type, + backbone_runtime_config.decoder.logits_size, + config.hidden_size, + false); + + out->depth.token_embedding = out->backbone.token_embedding; + out->depth.stack.layers.reserve(static_cast(config.depth_layers)); + for (int64_t layer = 0; layer < config.depth_layers; ++layer) { + out->depth.stack.layers.push_back(load_depth_layer(*out->store, source, config, storage_type, depth_rope_factors, layer)); + } + out->depth.final_norm = binding::norm_weight_from_source(*out->store, source, "depth_decoder.model.norm", config.depth_hidden_size); + + out->audio_embedding = source.require_f32("depth_decoder.model.embed_tokens.weight", {config.num_codebooks * config.vocab_size, config.hidden_size}); + out->depth_projector = { + out->store->load_f32_tensor( + source, + "depth_decoder.model.inputs_embeds_projector.weight", + {config.depth_hidden_size, config.hidden_size}), + std::nullopt}; + const auto depth_heads = source.require_f32( + "depth_decoder.codebooks_head.weight", + {config.num_codebooks - 1, config.depth_hidden_size, config.vocab_size}); + std::vector transposed_depth_heads( + static_cast((config.num_codebooks - 1) * config.vocab_size * config.depth_hidden_size)); + for (int64_t codebook = 0; codebook < config.num_codebooks - 1; ++codebook) { + const int64_t in_codebook_offset = codebook * config.depth_hidden_size * config.vocab_size; + const int64_t out_codebook_offset = codebook * config.vocab_size * config.depth_hidden_size; + for (int64_t row = 0; row < config.depth_hidden_size; ++row) { + const int64_t in_row_offset = in_codebook_offset + row * config.vocab_size; + for (int64_t token = 0; token < config.vocab_size; ++token) { + transposed_depth_heads[static_cast(out_codebook_offset + token * config.depth_hidden_size + row)] = + depth_heads[static_cast(in_row_offset + token)]; + } + } + } + out->depth_heads = out->store->make_f32( + core::TensorShape::from_dims({config.num_codebooks - 1, config.vocab_size, config.depth_hidden_size}), + transposed_depth_heads); + out->store->upload(); + return out; +} + +class BreezeDepthProjectionRuntime { +public: + BreezeDepthProjectionRuntime( + ggml_backend_t backend, + core::BackendType backend_type, + int threads, + size_t graph_arena_bytes, + const BreezeTTSConfig & config, + const modules::LinearWeights & projector_weights, + const core::TensorValue & packed_heads) + : backend_(backend), + threads_(std::max(1, threads)), + hidden_(config.hidden_size), + depth_hidden_(config.depth_hidden_size), + vocab_(config.vocab_size) { + if (backend_ == nullptr || hidden_ <= 0 || depth_hidden_ <= 0 || vocab_ <= 0 || config.num_codebooks <= 1) { + throw std::runtime_error("BreezeTTS depth projection shape is invalid"); + } + ctx_.reset(ggml_init({graph_arena_bytes, nullptr, true})); + if (ctx_ == nullptr) { + throw std::runtime_error("failed to initialize BreezeTTS depth projection graph context"); + } + core::ModuleBuildContext ctx{ctx_.get(), "breeze_tts.depth_projection", backend_type}; + projector_single_ = build_linear_graph( + ctx, + 1, + hidden_, + depth_hidden_, + projector_weights, + "breeze_tts.depth_projector.single"); + projector_pair_ = build_linear_graph( + ctx, + 2, + hidden_, + depth_hidden_, + projector_weights, + "breeze_tts.depth_projector.pair"); + head_graphs_.reserve(static_cast(config.num_codebooks - 1)); + for (int64_t codebook = 1; codebook < config.num_codebooks; ++codebook) { + head_graphs_.push_back(build_head_graph(ctx, packed_heads, codebook)); + } + head_paired_staging_.assign(static_cast(2 * vocab_), 0.0F); + graph_buffer_ = ggml_backend_alloc_ctx_tensors(ctx_.get(), backend_); + if (graph_buffer_ == nullptr) { + throw std::runtime_error("failed to allocate BreezeTTS depth projection graphs"); + } + } + + ~BreezeDepthProjectionRuntime() { + release_graph(projector_single_); + release_graph(projector_pair_); + for (const auto & graph : head_graphs_) { + release_graph(graph); + } + if (graph_buffer_ != nullptr) { + ggml_backend_buffer_free(graph_buffer_); + } + } + + void project_single(const float * hidden, float * out) const { + run_graph_direct(projector_single_, hidden, hidden_, out, depth_hidden_); + } + + std::vector project_single(const std::vector & hidden) const { + if (static_cast(hidden.size()) != hidden_) { + throw std::runtime_error("BreezeTTS depth projection input size mismatch"); + } + std::vector out(static_cast(depth_hidden_)); + project_single(hidden.data(), out.data()); + return out; + } + + void project_pair( + const float * cond_hidden, + const float * uncond_hidden, + float * out) const { + ggml_backend_tensor_set(projector_pair_.input, cond_hidden, 0, static_cast(hidden_) * sizeof(float)); + ggml_backend_tensor_set( + projector_pair_.input, + uncond_hidden, + static_cast(hidden_) * sizeof(float), + static_cast(hidden_) * sizeof(float)); + core::set_backend_threads(backend_, threads_); + if (core::compute_backend_graph(backend_, projector_pair_.graph, nullptr, projector_pair_.label) != GGML_STATUS_SUCCESS) { + throw std::runtime_error("BreezeTTS depth projection graph compute failed"); + } + ggml_backend_synchronize(backend_); + ggml_backend_tensor_get(projector_pair_.output, out, 0, static_cast(2 * depth_hidden_) * sizeof(float)); + } + + std::vector project_pair( + const std::vector & cond_hidden, + const std::vector & uncond_hidden) const { + if (static_cast(cond_hidden.size()) != hidden_ || + static_cast(uncond_hidden.size()) != hidden_) { + throw std::runtime_error("BreezeTTS depth projector pair input size mismatch"); + } + std::vector out(static_cast(2 * depth_hidden_)); + project_pair(cond_hidden.data(), uncond_hidden.data(), out.data()); + return out; + } + + void logits_cfg( + const float * cond_hidden, + const float * uncond_hidden, + int64_t codebook, + float guidance_scale, + float * out) const { + if (codebook <= 0 || static_cast(codebook) > head_graphs_.size()) { + throw std::runtime_error("BreezeTTS depth codebook index is invalid"); + } + const auto & graph = head_graphs_[static_cast(codebook - 1)]; + ggml_backend_tensor_set(graph.input, cond_hidden, 0, static_cast(depth_hidden_) * sizeof(float)); + ggml_backend_tensor_set( + graph.input, + uncond_hidden, + static_cast(depth_hidden_) * sizeof(float), + static_cast(depth_hidden_) * sizeof(float)); + core::set_backend_threads(backend_, threads_); + if (core::compute_backend_graph(backend_, graph.graph, nullptr, graph.label) != GGML_STATUS_SUCCESS) { + throw std::runtime_error("BreezeTTS depth projection graph compute failed"); + } + ggml_backend_synchronize(backend_); + ggml_backend_tensor_get(graph.output, head_paired_staging_.data(), 0, head_paired_staging_.size() * sizeof(float)); + const size_t vocab = static_cast(vocab_); + if (guidance_scale == 1.0F) { + // CFG is a no-op at scale 1: copy the conditional half directly, + // avoiding an inexact uncond + 1 * (cond - uncond) round trip. + std::memcpy(out, head_paired_staging_.data(), vocab * sizeof(float)); + return; + } + for (int64_t token = 0; token < vocab_; ++token) { + const size_t index = static_cast(token); + out[index] = head_paired_staging_[vocab + index] + guidance_scale * (head_paired_staging_[index] - head_paired_staging_[vocab + index]); + } + } + + std::vector logits_cfg( + const std::vector & cond_hidden, + const std::vector & uncond_hidden, + int64_t codebook, + float guidance_scale) const { + if (static_cast(cond_hidden.size()) != depth_hidden_ || + static_cast(uncond_hidden.size()) != depth_hidden_) { + throw std::runtime_error("BreezeTTS depth head input size mismatch"); + } + std::vector out(static_cast(vocab_)); + logits_cfg(cond_hidden.data(), uncond_hidden.data(), codebook, guidance_scale, out.data()); + return out; + } + +private: + struct Graph { + ggml_tensor * input = nullptr; + ggml_tensor * output = nullptr; + ggml_cgraph * graph = nullptr; + int64_t input_size = 0; + int64_t output_size = 0; + const char * label = nullptr; + }; + + Graph build_linear_graph( + core::ModuleBuildContext & ctx, + int64_t batch, + int64_t in_features, + int64_t out_features, + const modules::LinearWeights & weights, + const char * label) { + auto input = core::make_tensor(ctx, GGML_TYPE_F32, core::TensorShape::from_dims({batch, in_features})); + auto output = modules::LinearModule({in_features, out_features, false}) + .build(ctx, input, weights) + .tensor; + auto * graph = ggml_new_graph_custom(ctx_.get(), 32768, false); + ggml_set_output(output); + ggml_build_forward_expand(graph, output); + return {input.tensor, output, graph, batch * in_features, batch * out_features, label}; + } + + Graph build_head_graph( + core::ModuleBuildContext & ctx, + const core::TensorValue & packed_heads, + int64_t codebook) { + auto input = core::make_tensor(ctx, GGML_TYPE_F32, core::TensorShape::from_dims({2, depth_hidden_})); + const size_t row_stride = packed_heads.tensor->nb[1]; + const size_t codebook_stride = packed_heads.tensor->nb[2]; + const size_t offset = static_cast(codebook - 1) * codebook_stride; + auto weight = core::wrap_tensor( + ggml_view_2d(ctx_.get(), packed_heads.tensor, depth_hidden_, vocab_, row_stride, offset), + core::TensorShape::from_dims({vocab_, depth_hidden_}), + packed_heads.type); + auto output = modules::LinearModule({depth_hidden_, vocab_, false}) + .build(ctx, input, modules::LinearWeights{weight, std::nullopt}) + .tensor; + auto * graph = ggml_new_graph_custom(ctx_.get(), 32768, false); + ggml_set_output(output); + ggml_build_forward_expand(graph, output); + return {input.tensor, output, graph, 2 * depth_hidden_, 2 * vocab_, "breeze_tts.depth_head"}; + } + + void release_graph(const Graph & graph) const { + core::release_backend_graph_resources(backend_, graph.graph); + } + + void run_graph_direct( + const Graph & graph, + const float * input, + int64_t in_size, + float * output, + int64_t out_size) const { + if (in_size != graph.input_size || out_size != graph.output_size) { + throw std::runtime_error("BreezeTTS depth projection input size mismatch"); + } + ggml_backend_tensor_set(graph.input, input, 0, static_cast(in_size) * sizeof(float)); + core::set_backend_threads(backend_, threads_); + if (core::compute_backend_graph(backend_, graph.graph, nullptr, graph.label) != GGML_STATUS_SUCCESS) { + throw std::runtime_error("BreezeTTS depth projection graph compute failed"); + } + ggml_backend_synchronize(backend_); + ggml_backend_tensor_get(graph.output, output, 0, static_cast(out_size) * sizeof(float)); + } + + std::vector run_graph(const Graph & graph, const std::vector & input) const { + if (static_cast(input.size()) != graph.input_size) { + throw std::runtime_error("BreezeTTS depth projection input size mismatch"); + } + std::vector output(static_cast(graph.output_size)); + run_graph_direct(graph, input.data(), static_cast(input.size()), output.data(), graph.output_size); + return output; + } + + ggml_backend_t backend_ = nullptr; + int threads_ = 1; + int64_t hidden_ = 0; + int64_t depth_hidden_ = 0; + int64_t vocab_ = 0; + std::unique_ptr ctx_; + ggml_backend_buffer_t graph_buffer_ = nullptr; + Graph projector_single_; + Graph projector_pair_; + std::vector head_graphs_; + mutable std::vector head_paired_staging_; +}; + +} // namespace + +struct BreezeGeneratorRuntime::Impl { + Impl( + std::shared_ptr assets, + core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + assets::TensorStorageType storage_type, + core::AttentionPreference attention_preference = core::AttentionPreference::Auto) + : assets(std::move(assets)), + execution(execution), + tokenizer(this->assets), + text_encoder(this->assets, execution, graph_arena_bytes, weight_context_bytes, storage_type), + sampling_policy(sampling::resolve_torch_cuda_sampling_policy( + execution.backend_type(), + execution.config().device, + "breeze_tts.sampling", + "BreezeTTS", + sampling::TorchCudaSamplingPolicyFailureMode::FallbackToDefault)) { + if (this->assets == nullptr) { + throw std::runtime_error("BreezeTTS generator requires assets"); + } + const auto & config = this->assets->config; + const bool allow_backbone_flash = core::resolve_flash_attention( + execution.backend(), config.head_dim, attention_preference); + const bool allow_depth_flash = core::resolve_flash_attention( + execution.backend(), config.depth_head_dim, attention_preference); + engine::debug::trace_log_scalar("breeze_tts.attention.allow_backbone_flash", allow_backbone_flash); + engine::debug::trace_log_scalar("breeze_tts.attention.allow_depth_flash", allow_depth_flash); + backbone_runtime_config = backbone_config(config, execution.backend_type(), graph_arena_bytes, allow_backbone_flash); + depth_runtime_config = depth_config(config, execution.backend_type(), graph_arena_bytes, allow_depth_flash); + weights = load_weights(*this->assets, execution, weight_context_bytes, storage_type, backbone_runtime_config); + backbone_cond = std::make_unique(execution, backbone_runtime_config, weights->backbone); + backbone_uncond = std::make_unique(execution, backbone_runtime_config, weights->backbone); + depth_pair = std::make_unique(execution, depth_runtime_config, weights->depth); + depth_projection = std::make_unique( + execution.backend(), + execution.backend_type(), + execution.config().threads, + graph_arena_bytes, + config, + weights->depth_projector, + weights->depth_heads); + speech_encoder = std::make_unique( + this->assets, + execution, + graph_arena_bytes, + storage_type, + storage_type, + attention_preference); + speech_decoder = std::make_unique( + this->assets, + execution, + graph_arena_bytes, + weight_context_bytes, + storage_type, + storage_type, + attention_preference); + depth_first_embed_staging_.assign(static_cast(config.depth_hidden_size), 0.0F); + depth_projected_pair_staging_.assign(static_cast(2 * config.depth_hidden_size), 0.0F); + depth_prefill_staging_.assign(static_cast(4 * config.depth_hidden_size), 0.0F); + depth_next_embed_staging_.assign(static_cast(config.depth_hidden_size), 0.0F); + depth_next_pair_staging_.assign(static_cast(2 * config.depth_hidden_size), 0.0F); + depth_logits_staging_.assign(static_cast(config.vocab_size), 0.0F); + depth_cond_hidden_now_.assign(static_cast(config.depth_hidden_size), 0.0F); + depth_uncond_hidden_now_.assign(static_cast(config.depth_hidden_size), 0.0F); + } + + std::vector merge_prompt(const BreezePromptBranch & branch, const std::vector & reference_codes) { + const auto & config = assets->config; + const int64_t embedding_rows = config.num_codebooks * config.vocab_size; + std::vector out; + out.reserve(branch.input_ids.size() * static_cast(config.hidden_size)); + size_t text_segment_index = 0; + int64_t text_segment_offset = 0; + BreezeProjectedText projected_text; + int64_t audio_frame = 0; + for (size_t pos = 0; pos < branch.input_ids.size(); ++pos) { + if (branch.text_mask[pos] != 0) { + if (text_segment_offset == 0) { + if (text_segment_index >= branch.text_segments.size()) { + throw std::runtime_error("BreezeTTS text segment state mismatch"); + } + projected_text = text_encoder.encode(branch.text_segments[text_segment_index]); + } + const size_t begin = static_cast(text_segment_offset * config.hidden_size); + out.insert( + out.end(), + projected_text.values.begin() + static_cast(begin), + projected_text.values.begin() + static_cast(begin + static_cast(config.hidden_size))); + ++text_segment_offset; + if (text_segment_offset == projected_text.tokens) { + ++text_segment_index; + text_segment_offset = 0; + } + continue; + } + const int32_t id = branch.input_ids[pos]; + if (id == tokenizer.audio_token_id()) { + if ((audio_frame + 1) * config.num_codebooks > static_cast(reference_codes.size())) { + throw std::runtime_error("BreezeTTS reference audio code count is shorter than prompt placeholders"); + } + std::vector frame(static_cast(config.num_codebooks)); + for (int64_t codebook = 0; codebook < config.num_codebooks; ++codebook) { + frame[static_cast(codebook)] = + reference_codes[static_cast(audio_frame * config.num_codebooks + codebook)]; + } + const auto embedded = frame_embedding( + weights->audio_embedding, + embedding_rows, + config.hidden_size, + config.vocab_size, + frame); + out.insert(out.end(), embedded.begin(), embedded.end()); + ++audio_frame; + } else if (id == tokenizer.audio_eos_token_id()) { + std::vector eos(static_cast(config.num_codebooks), static_cast(config.codebook_eos_token_id)); + const auto embedded = frame_embedding( + weights->audio_embedding, + embedding_rows, + config.hidden_size, + config.vocab_size, + eos); + out.insert(out.end(), embedded.begin(), embedded.end()); + } else { + throw std::runtime_error("BreezeTTS prompt has non-text token that is not audio"); + } + } + return out; + } + + std::vector generate_frame( + const std::vector & cond_hidden, + const std::vector & uncond_hidden, + int32_t first_token, + const BreezeGenerationRequest & request, + sampling::HfSamplerScratch & scratch, + std::mt19937 & fallback_rng, + uint64_t & call_index, + uint64_t & offset_blocks) { + const auto & config = assets->config; + std::vector frame; + frame.reserve(static_cast(config.num_codebooks)); + frame.push_back(first_token); + + const size_t depth_hidden_size = static_cast(config.depth_hidden_size); + const size_t depth_hidden_bytes = depth_hidden_size * sizeof(float); + + const auto project_audio_embedding_row = [&](int64_t row, float * out) { + const int64_t rows = config.num_codebooks * config.vocab_size; + if (row < 0 || row >= rows) { + throw std::runtime_error("BreezeTTS embedding row is outside table"); + } + const size_t begin = static_cast(row * config.hidden_size); + depth_projection->project_single( + weights->audio_embedding.data() + begin, + out); + }; + + project_audio_embedding_row(first_token, depth_first_embed_staging_.data()); + depth_projection->project_pair( + cond_hidden.data(), + uncond_hidden.data(), + depth_projected_pair_staging_.data()); + + std::memcpy(depth_prefill_staging_.data(), + depth_projected_pair_staging_.data(), + depth_hidden_bytes); + std::memcpy(depth_prefill_staging_.data() + depth_hidden_size, + depth_first_embed_staging_.data(), + depth_hidden_bytes); + std::memcpy(depth_prefill_staging_.data() + 2 * depth_hidden_size, + depth_projected_pair_staging_.data() + depth_hidden_size, + depth_hidden_bytes); + std::memcpy(depth_prefill_staging_.data() + 3 * depth_hidden_size, + depth_first_embed_staging_.data(), + depth_hidden_bytes); + + auto depth = depth_pair->prefill_embeddings_batched(depth_prefill_staging_, 2, 2); + if (static_cast(depth.hidden.size()) != 2 * config.depth_hidden_size) { + throw std::runtime_error("BreezeTTS batched depth prefill hidden size mismatch"); + } + std::memcpy(depth_cond_hidden_now_.data(), + depth.hidden.data(), + depth_hidden_bytes); + std::memcpy(depth_uncond_hidden_now_.data(), + depth.hidden.data() + depth_hidden_size, + depth_hidden_bytes); + depth_pair->start_decode_embeddings_batched(depth.state, config.num_codebooks + 1); + + sampling::HfSamplingOptions options; + options.do_sample = true; + options.temperature = request.depth_temperature; + options.top_k = request.top_k; + options.top_p = request.top_p; + options.min_tokens_to_keep = 1; + for (int64_t codebook = 1; codebook < config.num_codebooks; ++codebook) { + depth_projection->logits_cfg( + depth_cond_hidden_now_.data(), + depth_uncond_hidden_now_.data(), + codebook, + request.guidance_scale, + depth_logits_staging_.data()); + suppress_reserved(depth_logits_staging_, kCodecCodebookSize, config.vocab_size); + const int32_t token = sample_logits( + depth_logits_staging_, + {}, + options, + scratch, + fallback_rng, + sampling_policy.cuda_fast_path ? &sampling_policy : nullptr, + request.seed, + call_index, + offset_blocks, + "BreezeTTS depth sampler"); + frame.push_back(token); + if (codebook + 1 < config.num_codebooks) { + project_audio_embedding_row(codebook * config.vocab_size + token, depth_next_embed_staging_.data()); + std::memcpy(depth_next_pair_staging_.data(), + depth_next_embed_staging_.data(), + depth_hidden_bytes); + std::memcpy(depth_next_pair_staging_.data() + depth_hidden_size, + depth_next_embed_staging_.data(), + depth_hidden_bytes); + const auto step = depth_pair->decode_embeddings_batched(depth_next_pair_staging_, 2); + if (static_cast(step.hidden.size()) != 2 * config.depth_hidden_size) { + throw std::runtime_error("BreezeTTS batched depth decode hidden size mismatch"); + } + std::memcpy(depth_cond_hidden_now_.data(), + step.hidden.data(), + depth_hidden_bytes); + std::memcpy(depth_uncond_hidden_now_.data(), + step.hidden.data() + depth_hidden_size, + depth_hidden_bytes); + } + } + return frame; + } + + runtime::AudioBuffer generate(const BreezeGenerationRequest & request) { + if (request.text.empty()) { + throw std::runtime_error("BreezeTTS requires text"); + } + const auto & config = assets->config; + BreezeSpeechCodes reference; + if (request.reference_codes.has_value()) { + reference = *request.reference_codes; + } else if (request.reference_audio.has_value()) { + reference = speech_encoder->encode(*request.reference_audio); + speech_encoder->release_runtime_graphs(); + } + std::vector reference_codes; + int64_t reference_frames = 0; + if (!reference.codes.empty()) { + if (reference.frames < 0 || reference.code_groups <= 0) { + throw std::runtime_error("BreezeTTS speech codes have invalid shape"); + } + if (static_cast(reference.codes.size()) != reference.frames * reference.code_groups) { + throw std::runtime_error("BreezeTTS speech code count does not match shape"); + } + reference_codes = reference.codes; + reference_frames = static_cast(reference_codes.size()) / config.num_codebooks; + } + BreezePromptBranch cond_branch; + BreezePromptBranch uncond_branch; + std::vector cond_embeddings; + std::vector uncond_embeddings; + int64_t cond_steps = 0; + int64_t uncond_steps = 0; + // guidance_scale == 1 makes CFG a no-op (logits == cond), so skip the + // unconditional branch entirely and halve the backbone work. + const bool use_cfg = request.guidance_scale != 1.0F; + const double prompt_ms = engine::debug::measure_ms([&] { + if (!reference_codes.empty()) { + if (request.reference_text.empty()) { + throw std::runtime_error("BreezeTTS clone requires reference_text"); + } + cond_branch = tokenizer.build_clone(request.text, request.instruction, request.reference_text, reference_frames); + if (use_cfg) { + uncond_branch = tokenizer.build_clone_negative(request.text, request.reference_text, reference_frames); + } + } else { + cond_branch = tokenizer.build_tts_instruction(request.text, request.instruction); + if (use_cfg) { + uncond_branch = tokenizer.build_tts_plain(request.text); + } + } + cond_embeddings = merge_prompt(cond_branch, reference_codes); + cond_steps = static_cast(cond_branch.input_ids.size()); + if (use_cfg) { + uncond_embeddings = merge_prompt(uncond_branch, reference_codes); + uncond_steps = static_cast(uncond_branch.input_ids.size()); + } + }); + engine::debug::timing_log_scalar("breeze_tts.generate.prompt_ms", prompt_ms); + text_encoder.release_runtime_graphs(); + double backbone_cond_decode_ms = 0.0; + double backbone_uncond_decode_ms = 0.0; + std::vector first_codebook_history; + std::vector codes; + double backbone_cond_prefill_ms = 0.0; + double backbone_uncond_prefill_ms = 0.0; + const double ar_ms = engine::debug::measure_ms([&] { + modules::QwenCausalPrefillResult cond; + backbone_cond_prefill_ms = engine::debug::measure_ms([&] { + cond = backbone_cond->prefill_embeddings(cond_embeddings, cond_steps); + }); + std::optional uncond; + if (use_cfg) { + uncond.emplace(); + backbone_uncond_prefill_ms = engine::debug::measure_ms([&] { + *uncond = backbone_uncond->prefill_embeddings(uncond_embeddings, uncond_steps); + }); + } + backbone_cond->start_decode_embeddings(cond.state, cond_steps + request.max_tokens); + if (use_cfg) { + backbone_uncond->start_decode_embeddings(uncond->state, uncond_steps + request.max_tokens); + } + + sampling::HfSamplerScratch scratch; + scratch.reserve_vocab(static_cast(config.lm_head_size)); + std::mt19937 fallback_rng(static_cast(request.seed)); + uint64_t sample_call_index = 0; + uint64_t offset_blocks = 0; + sampling::HfSamplingOptions first_options; + first_options.do_sample = true; + first_options.temperature = request.temperature; + first_options.top_k = request.top_k; + first_options.top_p = request.top_p; + first_options.repetition_penalty = kRepetitionPenalty; + first_options.min_tokens_to_keep = 1; + + codes.reserve(static_cast(request.max_tokens * config.num_codebooks)); + for (int64_t step = 0; step < request.max_tokens; ++step) { + if (use_cfg && cond.logits.size() != uncond->logits.size()) { + throw std::runtime_error("BreezeTTS CFG logits shape mismatch"); + } + std::vector logits; + if (use_cfg) { + logits.resize(cond.logits.size()); + for (size_t i = 0; i < logits.size(); ++i) { + logits[i] = uncond->logits[i] + request.guidance_scale * (cond.logits[i] - uncond->logits[i]); + } + } else { + logits = cond.logits; + } + suppress_reserved(logits, kCodecCodebookSize, config.vocab_size); + const int32_t first_token = sample_logits( + std::move(logits), + first_codebook_history, + first_options, + scratch, + fallback_rng, + sampling_policy.cuda_fast_path ? &sampling_policy : nullptr, + request.seed, + sample_call_index, + offset_blocks, + "BreezeTTS semantic sampler"); + if (first_token == config.vocab_size) { + break; + } + if (first_token == config.codebook_pad_token_id) { + continue; + } + const auto frame = generate_frame( + cond.hidden, + use_cfg ? uncond->hidden : cond.hidden, + first_token, + request, + scratch, + fallback_rng, + sample_call_index, + offset_blocks); + first_codebook_history.push_back(first_token); + codes.insert(codes.end(), frame.begin(), frame.end()); + const auto embedded = frame_embedding( + weights->audio_embedding, + config.num_codebooks * config.vocab_size, + config.hidden_size, + config.vocab_size, + frame); + modules::QwenCausalDecodeStepResult cond_step; + backbone_cond_decode_ms += engine::debug::measure_ms([&] { + cond_step = backbone_cond->decode_embedding(embedded); + }); + cond.logits = cond_step.logits; + cond.hidden = cond_step.hidden; + if (use_cfg) { + modules::QwenCausalDecodeStepResult uncond_step; + backbone_uncond_decode_ms += engine::debug::measure_ms([&] { + uncond_step = backbone_uncond->decode_embedding(embedded); + }); + uncond->logits = uncond_step.logits; + uncond->hidden = uncond_step.hidden; + } + } + }); + engine::debug::timing_log_scalar("breeze_tts.ar.total_ms", ar_ms); + engine::debug::timing_log_scalar("breeze_tts.ar.backbone_cond_prefill_ms", backbone_cond_prefill_ms); + engine::debug::timing_log_scalar("breeze_tts.ar.backbone_uncond_prefill_ms", backbone_uncond_prefill_ms); + engine::debug::timing_log_scalar("breeze_tts.ar.backbone_cond_decode_ms", backbone_cond_decode_ms); + engine::debug::timing_log_scalar("breeze_tts.ar.backbone_uncond_decode_ms", backbone_uncond_decode_ms); + backbone_cond->release_runtime_graphs(); + if (use_cfg) { + backbone_uncond->release_runtime_graphs(); + } + depth_pair->release_runtime_graphs(); + if (codes.empty()) { + throw std::runtime_error("BreezeTTS generated no audio codes"); + } + if (config.num_codebooks <= 0 || static_cast(codes.size()) % config.num_codebooks != 0) { + throw std::runtime_error("BreezeTTS audio code count must be divisible by num_codebooks"); + } + BreezeSpeechCodes speech_codes; + speech_codes.codes = codes; + speech_codes.code_groups = config.num_codebooks; + speech_codes.frames = static_cast(codes.size()) / config.num_codebooks; + runtime::AudioBuffer audio = speech_decoder->decode(speech_codes); + speech_decoder->release_runtime_graphs(); + for (float & sample : audio.samples) { + sample = std::clamp(sample, -1.0F, 1.0F); + } + return audio; + } + + std::shared_ptr assets; + core::ExecutionContext & execution; + BreezeTextTokenizer tokenizer; + BreezeTextEncoderRuntime text_encoder; + sampling::TorchCudaSamplingPolicy sampling_policy; + modules::QwenCausalDecodeRuntimeConfig backbone_runtime_config; + modules::QwenCausalDecodeRuntimeConfig depth_runtime_config; + std::shared_ptr weights; + std::unique_ptr backbone_cond; + std::unique_ptr backbone_uncond; + std::unique_ptr depth_pair; + std::unique_ptr depth_projection; + std::unique_ptr speech_encoder; + std::unique_ptr speech_decoder; + std::vector depth_first_embed_staging_; + std::vector depth_projected_pair_staging_; + std::vector depth_prefill_staging_; + std::vector depth_next_embed_staging_; + std::vector depth_next_pair_staging_; + std::vector depth_logits_staging_; + std::vector depth_cond_hidden_now_; + std::vector depth_uncond_hidden_now_; +}; + +BreezeGeneratorRuntime::BreezeGeneratorRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type, + engine::core::AttentionPreference attention_preference) + : impl_(std::make_unique( + std::move(assets), execution, graph_arena_bytes, weight_context_bytes, storage_type, attention_preference)) {} + +BreezeGeneratorRuntime::~BreezeGeneratorRuntime() = default; + +engine::runtime::AudioBuffer BreezeGeneratorRuntime::generate(const BreezeGenerationRequest & request) { + const auto start = Clock::now(); + auto audio = impl_->generate(request); + engine::debug::timing_log_scalar("breeze_tts.generate.total_ms", engine::debug::elapsed_ms(start)); + return audio; +} + +BreezeSpeechCodes BreezeGeneratorRuntime::encode_reference(const engine::runtime::AudioBuffer & audio) const { + return impl_->speech_encoder->encode(audio); +} + +} // namespace engine::models::breeze_tts diff --git a/src/models/breeze_tts/session.cpp b/src/models/breeze_tts/session.cpp new file mode 100644 index 000000000..dd400d5a1 --- /dev/null +++ b/src/models/breeze_tts/session.cpp @@ -0,0 +1,388 @@ +#include "engine/models/breeze_tts/session.h" + +#include "engine/framework/core/attention_fallback.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/runtime/options.h" +#include "engine/framework/runtime/spec_backed_model.h" +#include "engine/framework/text/chunking.h" +#include "engine/models/breeze_tts/generator.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::breeze_tts { +namespace { + +constexpr const char * kFamily = "breeze_tts"; +constexpr const char * kModelName = "BreezeTTS"; +constexpr int64_t kDefaultTextChunkSize = 600; +constexpr int64_t kDefaultReferenceCacheSlots = 1; + +std::shared_ptr require_assets(std::shared_ptr assets) { + if (assets == nullptr) { + throw std::runtime_error("BreezeTTS session requires assets"); + } + return assets; +} + +std::shared_ptr require_contract( + std::shared_ptr contract) { + if (contract == nullptr) { + throw std::runtime_error("BreezeTTS session requires a model contract"); + } + return contract; +} + +std::vector split_request(const runtime::TaskRequest & request) { + const int64_t text_chunk_size = + engine::text::parse_text_chunk_size_override(request.options).value_or(kDefaultTextChunkSize); + if (text_chunk_size <= 0) { + throw std::runtime_error("BreezeTTS text_chunk_size must be positive"); + } + const auto text_chunk_mode = + engine::text::parse_text_chunk_mode_override(request.options).value_or(engine::text::TextChunkMode::Default); + return runtime::chunk_text_request(request, text_chunk_size, text_chunk_mode); +} + +core::AttentionPreference attention_preference_from_options(const runtime::SessionOptions & options) { + if (const auto value = runtime::find_option(options.options, {"breeze_tts.attention"})) { + return core::parse_attention_preference(*value, "breeze_tts.attention"); + } + return core::AttentionPreference::Auto; +} + +void trace_attention_preference(core::AttentionPreference preference) { + const char * name = "auto"; + if (preference == core::AttentionPreference::Flash) { + name = "flash"; + } else if (preference == core::AttentionPreference::Eager) { + name = "eager"; + } + engine::debug::trace_log_scalar("breeze_tts.attention.preference", std::string_view(name)); +} + +void validate_session_options( + const runtime::SessionOptions & options, + const engine::model_spec::ModelContract & contract) { + auto validation_options = options; + // Older standalone GGUF packages embed a v1 contract that predates this + // backend-compatibility option; keep them usable while still validating + // the option value in attention_preference_from_options(). + if (contract.session_option_keys.find("breeze_tts.attention") == + contract.session_option_keys.end()) { + validation_options.options.erase("breeze_tts.attention"); + } + runtime::validate_spec_backed_session_options(validation_options, contract, kFamily, kModelName); +} + +std::size_t reference_cache_slots_from_options(const runtime::SessionOptions & options) { + const int64_t slots = runtime::parse_i64_option( + options.options, + {"reference_cache_slots"}) + .value_or(kDefaultReferenceCacheSlots); + if (slots < 0) { + throw std::runtime_error("breeze_tts.reference_cache_slots must be non-negative"); + } + if (static_cast(slots) > static_cast(std::numeric_limits::max())) { + throw std::runtime_error("breeze_tts.reference_cache_slots is too large"); + } + return static_cast(slots); +} + +uint64_t fnv1a_mix(uint64_t hash, const void * data, size_t size) { + const auto * bytes = static_cast(data); + for (size_t i = 0; i < size; ++i) { + hash ^= bytes[i]; + hash *= 1099511628211ull; + } + return hash; +} + +uint64_t hash_audio_samples(const runtime::AudioBuffer & audio) { + uint64_t hash = 1469598103934665603ull; + for (const float sample : audio.samples) { + uint32_t bits = 0; + std::memcpy(&bits, &sample, sizeof(bits)); + hash = fnv1a_mix(hash, &bits, sizeof(bits)); + } + return hash; +} + +std::unique_ptr create_breeze_tts_session( + const runtime::TaskSpec & task, + const runtime::SessionOptions & options, + std::shared_ptr assets, + std::shared_ptr contract) { + return std::make_unique(task, options, std::move(assets), std::move(contract)); +} + +} // namespace + +BreezeTTSSession::BreezeTTSSession( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract) + : runtime::RuntimeSessionBase(options), + task_(task), + assets_(require_assets(std::move(assets))), + contract_(require_contract(std::move(contract))), + reference_cache_(reference_cache_slots_from_options(options)) { + validate_session_options(options, *contract_); + if (task_.task != runtime::VoiceTaskKind::Tts && + task_.task != runtime::VoiceTaskKind::VoiceCloning && + task_.task != runtime::VoiceTaskKind::VoiceDesign) { + throw std::runtime_error("BreezeTTS supports tts, clone, and voice design tasks"); + } + if (task_.mode != runtime::RunMode::Offline && + task_.mode != runtime::RunMode::Streaming) { + throw std::runtime_error("BreezeTTS supports offline and streaming sessions"); + } + using T = engine::assets::TensorStorageType; + const auto storage_type = runtime::parse_tensor_storage_option( + options.options, + "weight_type", + T::Native, + {T::Native, T::F32, T::F16, T::BF16, T::Q8_0, T::Q4_0, T::Q4_K}); + const auto graph_arena_bytes = runtime::parse_size_mb_option( + options.options, + {"graph_arena_mb"}, + 1024ull * 1024ull * 1024ull); + const auto weight_context_bytes = runtime::parse_size_mb_option( + options.options, + {"weight_context_mb"}, + 2048ull * 1024ull * 1024ull); + const auto attention_preference = attention_preference_from_options(options); + trace_attention_preference(attention_preference); + generator_ = std::make_unique( + assets_, + execution_context(), + graph_arena_bytes, + weight_context_bytes, + storage_type, + attention_preference); +} + +BreezeTTSSession::~BreezeTTSSession() = default; + +std::string BreezeTTSSession::family() const { + return kFamily; +} + +runtime::VoiceTaskKind BreezeTTSSession::task_kind() const { + return task_.task; +} + +runtime::RunMode BreezeTTSSession::run_mode() const { + return task_.mode; +} + +void BreezeTTSSession::prepare(const runtime::SessionPreparationRequest & request) { + runtime::validate_spec_backed_request_options(request.options, *contract_, kModelName); + mark_prepared(); +} + +BreezeSpeechCodes BreezeTTSSession::resolve_reference_codes(const runtime::AudioBuffer & audio) { + ReferenceCacheKey key; + key.sample_rate = audio.sample_rate; + key.channels = audio.channels; + key.sample_count = static_cast(audio.samples.size()); + key.sample_hash = hash_audio_samples(audio); + if (const auto * cached = reference_cache_.find(key)) { + engine::debug::trace_log_scalar("breeze_tts.reference_cache.hit", 1); + engine::debug::trace_log_scalar("breeze_tts.reference_cache.slots", static_cast(reference_cache_.capacity())); + engine::debug::trace_log_scalar("breeze_tts.reference_cache.entries", static_cast(reference_cache_.size())); + return cached->codes; + } + const auto start = std::chrono::steady_clock::now(); + ReferenceCacheEntry entry; + entry.codes = generator_->encode_reference(audio); + engine::debug::trace_log_scalar("breeze_tts.reference.frames", entry.codes.frames); + engine::debug::trace_log_scalar("breeze_tts.reference.codebooks", entry.codes.code_groups); + if (reference_cache_.capacity() == 0) { + uncached_reference_ = std::move(entry); + } else { + reference_cache_.put(key, std::move(entry)); + } + engine::debug::trace_log_scalar("breeze_tts.reference_cache.hit", 0); + engine::debug::trace_log_scalar("breeze_tts.reference_cache.slots", static_cast(reference_cache_.capacity())); + engine::debug::trace_log_scalar("breeze_tts.reference_cache.entries", static_cast(reference_cache_.size())); + engine::debug::timing_log_scalar("breeze_tts.reference_encode_ms", engine::debug::elapsed_ms(start)); + if (reference_cache_.capacity() == 0) { + return uncached_reference_->codes; + } + const auto * cached = reference_cache_.find(key); + if (cached == nullptr) { + throw std::runtime_error("BreezeTTS reference cache insert failed"); + } + return cached->codes; +} + +runtime::TaskResult BreezeTTSSession::run(const runtime::TaskRequest & request) { + const auto wall_start = std::chrono::steady_clock::now(); + runtime::validate_spec_backed_request_options(request.options, *contract_, kModelName); + require_prepared("BreezeTTS run"); + if (task_.mode != runtime::RunMode::Offline) { + throw std::runtime_error("BreezeTTS run requires an offline session"); + } + if (!request.text_input.has_value() || request.text_input->text.empty()) { + throw std::runtime_error("BreezeTTS requires text input"); + } + runtime::AudioBuffer merged; + auto chunks = split_request(request); + const int64_t text_chunk_size = + engine::text::parse_text_chunk_size_override(request.options).value_or(kDefaultTextChunkSize); + const auto text_chunk_mode = + engine::text::parse_text_chunk_mode_override(request.options).value_or(engine::text::TextChunkMode::Default); + engine::debug::trace_log_scalar("breeze_tts.text_chunk_mode", engine::text::text_chunk_mode_name(text_chunk_mode)); + engine::debug::trace_log_scalar("breeze_tts.text_chunk_size", text_chunk_size); + engine::debug::trace_log_scalar("breeze_tts.text.chunk_count", static_cast(chunks.size())); + std::optional reference_codes; + if (request.voice.has_value() && + request.voice->speaker.has_value() && + request.voice->speaker->audio.has_value()) { + reference_codes = resolve_reference_codes(*request.voice->speaker->audio); + } + for (size_t index = 0; index < chunks.size(); ++index) { + runtime::append_audio_buffer(merged, generator_->generate(build_generation_request(chunks[index], reference_codes, index))); + } + runtime::TaskResult result; + result.audio_output = std::move(merged); + engine::debug::timing_log_scalar("session.wall_ms", engine::debug::elapsed_ms(wall_start)); + return result; +} + +runtime::StreamingPolicy BreezeTTSSession::streaming_policy() const { + runtime::StreamingPolicy policy; + policy.input = runtime::StreamingInputKind::None; + policy.output = runtime::StreamingOutputKind::PullEvents; + return policy; +} + +void BreezeTTSSession::start_stream(const runtime::TaskRequest & request) { + runtime::validate_spec_backed_request_options(request.options, *contract_, kModelName); + require_prepared("BreezeTTS streaming"); + if (task_.mode != runtime::RunMode::Streaming) { + throw std::runtime_error("BreezeTTS start_stream requires a streaming session"); + } + if (!request.text_input.has_value() || request.text_input->text.empty()) { + throw std::runtime_error("BreezeTTS streaming requires text input"); + } + reset(); + stream_chunk_requests_ = split_request(request); + const int64_t text_chunk_size = + engine::text::parse_text_chunk_size_override(request.options).value_or(kDefaultTextChunkSize); + const auto text_chunk_mode = + engine::text::parse_text_chunk_mode_override(request.options).value_or(engine::text::TextChunkMode::Default); + engine::debug::trace_log_scalar("breeze_tts.streaming.text_chunk_mode", engine::text::text_chunk_mode_name(text_chunk_mode)); + engine::debug::trace_log_scalar("breeze_tts.streaming.text_chunk_size", text_chunk_size); + engine::debug::trace_log_scalar("breeze_tts.streaming.text.chunk_count", static_cast(stream_chunk_requests_.size())); + if (request.voice.has_value() && + request.voice->speaker.has_value() && + request.voice->speaker->audio.has_value()) { + stream_reference_codes_ = resolve_reference_codes(*request.voice->speaker->audio); + } + stream_started_ = true; +} + +std::optional BreezeTTSSession::next_stream_event() { + if (!stream_started_) { + throw std::runtime_error("BreezeTTS streaming has not been started"); + } + if (stream_chunk_index_ >= stream_chunk_requests_.size()) { + return std::nullopt; + } + const size_t chunk_index = stream_chunk_index_++; + auto chunk_audio = generator_->generate( + build_generation_request(stream_chunk_requests_[chunk_index], stream_reference_codes_, chunk_index)); + runtime::append_audio_buffer(stream_merged_audio_, chunk_audio); + runtime::StreamEvent event; + event.named_audio_outputs.push_back({ + "chunk_" + std::to_string(chunk_index), + std::move(chunk_audio), + {}, + }); + return event; +} + +void BreezeTTSSession::set_stream_event_sink(runtime::StreamEventCallback sink) { + (void)sink; +} + +runtime::TaskResult BreezeTTSSession::finish_stream() { + if (!stream_started_) { + throw std::runtime_error("BreezeTTS streaming has not been started"); + } + while (next_stream_event().has_value()) { + } + runtime::TaskResult result; + result.audio_output = std::move(stream_merged_audio_); + reset(); + return result; +} + +void BreezeTTSSession::reset() { + stream_chunk_requests_.clear(); + stream_reference_codes_.reset(); + stream_merged_audio_ = runtime::AudioBuffer{}; + stream_chunk_index_ = 0; + stream_started_ = false; +} + +runtime::StreamEvent BreezeTTSSession::process_audio_chunk(const runtime::AudioChunk & chunk) { + (void)chunk; + throw std::runtime_error("BreezeTTS streaming does not consume audio chunks"); +} + +runtime::TaskResult BreezeTTSSession::finalize() { + return finish_stream(); +} + +BreezeGenerationRequest BreezeTTSSession::build_generation_request( + const runtime::TaskRequest & request, + const std::optional & reference_codes, + size_t chunk_index) const { + BreezeGenerationRequest generation; + generation.text = request.text_input->text; + generation.instruction = runtime::find_option(request.options, {"instruction"}).value_or(""); + generation.reference_text = runtime::find_option(request.options, {"reference_text"}).value_or(""); + generation.guidance_scale = runtime::parse_finite_float_option(request.options, {"guidance_scale"}).value_or(generation.guidance_scale); + if (generation.guidance_scale < 0.0F) { + throw std::runtime_error("BreezeTTS guidance_scale must be non-negative"); + } + generation.temperature = runtime::parse_positive_finite_float_option(request.options, {"temperature"}).value_or(generation.temperature); + generation.depth_temperature = runtime::parse_positive_finite_float_option(request.options, {"depth_temperature"}).value_or(generation.depth_temperature); + generation.top_k = runtime::parse_i64_option(request.options, {"top_k"}).value_or(generation.top_k); + generation.top_p = runtime::parse_positive_finite_float_option(request.options, {"top_p"}).value_or(generation.top_p); + generation.max_tokens = runtime::parse_positive_i64_option(request.options, {"max_tokens"}, generation.max_tokens); + generation.seed = runtime::parse_u64_option(request.options, {"seed"}).value_or(generation.seed); + generation.reference_codes = reference_codes; + if (chunk_index > 0) { + ++generation.seed; + } + return generation; +} + +bool BreezeTTSSession::ReferenceCacheKeyEqual::operator()( + const ReferenceCacheKey & lhs, + const ReferenceCacheKey & rhs) const noexcept { + return lhs.sample_rate == rhs.sample_rate && + lhs.channels == rhs.channels && + lhs.sample_count == rhs.sample_count && + lhs.sample_hash == rhs.sample_hash; +} + +std::shared_ptr make_breeze_tts_loader() { + runtime::SpecBackedVoiceModelConfig config; + config.family = kFamily; + config.load_assets = load_breeze_tts_assets; + config.create_session = create_breeze_tts_session; + return runtime::make_spec_backed_voice_loader(std::move(config)); +} + +} // namespace engine::models::breeze_tts diff --git a/src/models/breeze_tts/speech_decoder.cpp b/src/models/breeze_tts/speech_decoder.cpp new file mode 100644 index 000000000..1edf0c1c4 --- /dev/null +++ b/src/models/breeze_tts/speech_decoder.cpp @@ -0,0 +1,1193 @@ +#include "engine/models/breeze_tts/speech_decoder.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/io/json.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/attention/feed_forward.h" +#include "engine/framework/modules/attention/scaled_dot_product_attention.h" +#include "engine/framework/modules/attention/types.h" +#include "engine/framework/modules/conditioning_modules.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/lookup_modules.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/positional_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/weight_binding.h" + +#include "engine/framework/core/constant_tensor_cache.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::breeze_tts { +namespace json = engine::io::json; +namespace { + +using Clock = std::chrono::steady_clock; +namespace binding = modules::binding; + +constexpr int64_t kSampleRate = 24000; +constexpr int64_t kDecodeSamplesPerCode = 1920; +constexpr int64_t kChunkCodes = 300; +constexpr int64_t kLeftContextCodes = 25; +constexpr std::array kStrixHaloCachedChunkFrames{300, 105}; +#if defined(ENGINE_HIP_STRIX_HALO_OPTIMIZATIONS) +constexpr bool kStrixHaloGraphCacheEnabled = true; +#else +constexpr bool kStrixHaloGraphCacheEnabled = false; +#endif +constexpr float kCodebookEps = 1.0e-5F; +constexpr float kMaskNegInf = -1.0e9F; + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +struct TransformerLayerWeights { + modules::AttentionWeights attention; + modules::GatedFeedForwardWeights mlp; + modules::NormWeights input_norm; + modules::NormWeights post_norm; + modules::LayerScaleWeights attn_scale; + modules::LayerScaleWeights mlp_scale; +}; + +struct ConvNeXtWeights { + modules::Conv1dWeights dwconv; + modules::NormWeights norm; + modules::LinearWeights pwconv1; + modules::LinearWeights pwconv2; + modules::LayerScaleWeights gamma; +}; + +struct ResidualUnitWeights { + std::vector act1_alpha; + std::vector act1_beta; + modules::Conv1dWeights conv1; + std::vector act2_alpha; + std::vector act2_beta; + modules::Conv1dWeights conv2; +}; + +struct UpsampleStageWeights { + modules::ConvTranspose1dWeights upconv; + ConvNeXtWeights convnext; +}; + +struct DecoderBlockWeights { + std::vector input_alpha; + std::vector input_beta; + modules::ConvTranspose1dWeights upconv; + std::vector residual_units; +}; + +struct DecoderConfig { + int64_t codebook_size = 0; + int64_t codebook_dim = 0; + int64_t latent_dim = 0; + int64_t hidden_size = 0; + int64_t intermediate_size = 0; + int64_t decoder_dim = 0; + int64_t num_heads = 0; + int64_t num_kv_heads = 0; + int64_t num_layers = 0; + int64_t num_quantizers = 0; + int64_t num_semantic_quantizers = 1; + int64_t sliding_window = 0; + int64_t head_dim = 0; + float rope_theta = 10000.0F; + float rms_norm_eps = 1.0e-5F; + std::vector upsample_rates; + std::vector upsampling_ratios; +}; + +} // namespace + +struct BreezeSpeechDecoderWeights { + std::shared_ptr store; + DecoderConfig config; + std::vector semantic_codebooks; + std::vector acoustic_codebooks; + modules::LinearWeights semantic_output_proj; + modules::LinearWeights acoustic_output_proj; + modules::Conv1dWeights pre_conv; + modules::LinearWeights transformer_input_proj; + std::vector transformer_layers; + modules::NormWeights transformer_norm; + modules::LinearWeights transformer_output_proj; + std::vector upsample_stages; + modules::Conv1dWeights decoder_input_conv; + std::vector decoder_blocks; + std::vector output_alpha; + std::vector output_beta; + modules::Conv1dWeights output_conv; +}; + +namespace { + +core::TensorValue normalized_codebook( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + int64_t size, + int64_t dim) { + const auto cluster_usage = source.require_f32(prefix + "cluster_usage", {size}); + const auto embedding_sum = source.require_f32(prefix + "embedding_sum", {size, dim}); + std::vector embedding(embedding_sum.size(), 0.0F); + for (int64_t code = 0; code < size; ++code) { + const float denom = std::max(cluster_usage[static_cast(code)], kCodebookEps); + for (int64_t col = 0; col < dim; ++col) { + const size_t offset = static_cast(code * dim + col); + embedding[offset] = embedding_sum[offset] / denom; + } + } + return store.make_f32(core::TensorShape::from_dims({size, dim}), embedding); +} + +DecoderConfig load_decoder_config(const BreezeTTSAssets & assets) { + const auto root = assets.resources.parse_json("audio_tokenizer_config_json"); + const auto & decoder = root.require("decoder_config"); + DecoderConfig config; + config.codebook_size = json::require_i64(decoder, "codebook_size"); + config.codebook_dim = json::require_i64(decoder, "codebook_dim"); + config.latent_dim = json::require_i64(decoder, "latent_dim"); + config.hidden_size = json::require_i64(decoder, "hidden_size"); + config.intermediate_size = json::require_i64(decoder, "intermediate_size"); + config.decoder_dim = json::require_i64(decoder, "decoder_dim"); + config.num_heads = json::require_i64(decoder, "num_attention_heads"); + config.num_kv_heads = json::require_i64(decoder, "num_key_value_heads"); + config.num_layers = json::require_i64(decoder, "num_hidden_layers"); + config.num_quantizers = json::require_i64(decoder, "num_quantizers"); + config.num_semantic_quantizers = json::require_i64(decoder, "num_semantic_quantizers"); + config.sliding_window = json::require_i64(decoder, "sliding_window"); + config.head_dim = json::require_i64(decoder, "head_dim"); + config.rope_theta = json::optional_f32(decoder, "rope_theta", config.rope_theta); + config.rms_norm_eps = json::optional_f32(decoder, "rms_norm_eps", config.rms_norm_eps); + config.upsample_rates = json::require_i64_array(decoder, "upsample_rates"); + config.upsampling_ratios = json::require_i64_array(decoder, "upsampling_ratios"); + return config; +} + +modules::LinearWeights load_conv1x1_as_linear( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const std::string & prefix, + assets::TensorStorageType storage_type, + int64_t input_dim, + int64_t output_dim) { + modules::LinearWeights weights; + const auto data = source.require_tensor_as_shape( + prefix + ".weight", + storage_type, + {output_dim, input_dim, 1}, + {output_dim, input_dim}); + weights.weight = store.make_tensor(data.shape, data.type, data.bytes.data(), data.bytes.size()); + return weights; +} + +std::shared_ptr load_weights( + const BreezeTTSAssets & assets, + ggml_backend_t backend, + core::BackendType backend_type, + assets::TensorStorageType linear_weight_storage_type, + assets::TensorStorageType conv_weight_storage_type) { + const auto & source = *assets.weights; + auto weights = std::make_shared(); + weights->store = std::make_shared( + backend, + backend_type, + "breeze_tts.speech_decoder.weights", + 32ull * 1024ull * 1024ull); + weights->config = load_decoder_config(assets); + const auto & config = weights->config; + const int64_t split_dim = config.codebook_dim / 2; + + for (int64_t layer = 0; layer < config.num_semantic_quantizers; ++layer) { + const std::string prefix = "codec_model.decoder.quantizer.rvq_first.vq.layers." + std::to_string(layer) + "._codebook."; + weights->semantic_codebooks.push_back(normalized_codebook(*weights->store, source, prefix, config.codebook_size, split_dim)); + } + for (int64_t layer = 0; layer < config.num_quantizers - config.num_semantic_quantizers; ++layer) { + const std::string prefix = "codec_model.decoder.quantizer.rvq_rest.vq.layers." + std::to_string(layer) + "._codebook."; + weights->acoustic_codebooks.push_back(normalized_codebook(*weights->store, source, prefix, config.codebook_size, split_dim)); + } + weights->semantic_output_proj = load_conv1x1_as_linear( + *weights->store, + source, + "codec_model.decoder.quantizer.rvq_first.output_proj", + linear_weight_storage_type, + split_dim, + config.hidden_size); + weights->acoustic_output_proj = load_conv1x1_as_linear( + *weights->store, + source, + "codec_model.decoder.quantizer.rvq_rest.output_proj", + linear_weight_storage_type, + split_dim, + config.hidden_size); + weights->pre_conv = binding::conv1d_from_source( + *weights->store, + source, + "codec_model.decoder.pre_conv.conv", + conv_weight_storage_type, + config.latent_dim, + config.hidden_size, + 3, + true); + weights->transformer_input_proj = binding::linear_from_source( + *weights->store, + source, + "codec_model.decoder.pre_transformer.input_proj", + linear_weight_storage_type, + config.hidden_size, + config.latent_dim, + true); + for (int64_t layer = 0; layer < config.num_layers; ++layer) { + const std::string prefix = "codec_model.decoder.pre_transformer.layers." + std::to_string(layer); + TransformerLayerWeights block; + block.input_norm = binding::norm_weight_from_source(*weights->store, source, prefix + ".input_layernorm", config.hidden_size); + block.post_norm = binding::norm_weight_from_source(*weights->store, source, prefix + ".post_attention_layernorm", config.hidden_size); + block.attention.q_weight = weights->store->load_tensor( + source, + prefix + ".self_attn.q_proj.weight", + linear_weight_storage_type, + {config.num_heads * config.head_dim, config.hidden_size}); + block.attention.k_weight = weights->store->load_tensor( + source, + prefix + ".self_attn.k_proj.weight", + linear_weight_storage_type, + {config.num_kv_heads * config.head_dim, config.hidden_size}); + block.attention.v_weight = weights->store->load_tensor( + source, + prefix + ".self_attn.v_proj.weight", + linear_weight_storage_type, + {config.num_kv_heads * config.head_dim, config.hidden_size}); + block.attention.out_weight = weights->store->load_tensor( + source, + prefix + ".self_attn.o_proj.weight", + linear_weight_storage_type, + {config.hidden_size, config.num_heads * config.head_dim}); + block.mlp.gate_proj = binding::linear_from_source( + *weights->store, + source, + prefix + ".mlp.gate_proj", + linear_weight_storage_type, + config.intermediate_size, + config.hidden_size, + false); + block.mlp.up_proj = binding::linear_from_source( + *weights->store, + source, + prefix + ".mlp.up_proj", + linear_weight_storage_type, + config.intermediate_size, + config.hidden_size, + false); + block.mlp.down_proj = binding::linear_from_source( + *weights->store, + source, + prefix + ".mlp.down_proj", + linear_weight_storage_type, + config.hidden_size, + config.intermediate_size, + false); + block.attn_scale = binding::layer_scale_from_named_source(*weights->store, source, prefix + ".self_attn_layer_scale.scale"); + block.mlp_scale = binding::layer_scale_from_named_source(*weights->store, source, prefix + ".mlp_layer_scale.scale"); + weights->transformer_layers.push_back(std::move(block)); + } + weights->transformer_norm = binding::norm_weight_from_source( + *weights->store, + source, + "codec_model.decoder.pre_transformer.norm", + config.hidden_size); + weights->transformer_output_proj = binding::linear_from_source( + *weights->store, + source, + "codec_model.decoder.pre_transformer.output_proj", + linear_weight_storage_type, + config.latent_dim, + config.hidden_size, + true); + + for (size_t i = 0; i < config.upsampling_ratios.size(); ++i) { + const std::string prefix = "codec_model.decoder.upsample." + std::to_string(i); + UpsampleStageWeights stage; + stage.upconv = binding::conv_transpose1d_from_source( + *weights->store, + source, + prefix + ".0.conv", + conv_weight_storage_type, + config.latent_dim, + config.latent_dim, + config.upsampling_ratios[i], + true); + stage.convnext.dwconv = binding::conv1d_from_source( + *weights->store, + source, + prefix + ".1.dwconv.conv", + conv_weight_storage_type, + config.latent_dim, + 1, + 7, + true); + stage.convnext.norm = binding::norm_from_source(*weights->store, source, prefix + ".1.norm", config.latent_dim); + stage.convnext.pwconv1 = binding::linear_from_source( + *weights->store, + source, + prefix + ".1.pwconv1", + linear_weight_storage_type, + config.latent_dim * 4, + config.latent_dim, + true); + stage.convnext.pwconv2 = binding::linear_from_source( + *weights->store, + source, + prefix + ".1.pwconv2", + linear_weight_storage_type, + config.latent_dim, + config.latent_dim * 4, + true); + stage.convnext.gamma = binding::layer_scale_from_named_source(*weights->store, source, prefix + ".1.gamma"); + weights->upsample_stages.push_back(std::move(stage)); + } + + weights->decoder_input_conv = binding::conv1d_from_source( + *weights->store, + source, + "codec_model.decoder.decoder.0.conv", + conv_weight_storage_type, + config.decoder_dim, + config.latent_dim, + 7, + true); + int64_t channels = config.decoder_dim; + for (size_t i = 0; i < config.upsample_rates.size(); ++i) { + const std::string prefix = "codec_model.decoder.decoder." + std::to_string(i + 1) + ".block"; + const int64_t out_channels = channels / 2; + DecoderBlockWeights block; + block.input_alpha = source.require_f32(prefix + ".0.alpha", {channels}); + block.input_beta = source.require_f32(prefix + ".0.beta", {channels}); + block.upconv = binding::conv_transpose1d_from_source( + *weights->store, + source, + prefix + ".1.conv", + conv_weight_storage_type, + channels, + out_channels, + config.upsample_rates[i] * 2, + true); + for (int unit_index = 0; unit_index < 3; ++unit_index) { + const std::string unit = prefix + "." + std::to_string(unit_index + 2); + ResidualUnitWeights residual; + residual.act1_alpha = source.require_f32(unit + ".act1.alpha", {out_channels}); + residual.act1_beta = source.require_f32(unit + ".act1.beta", {out_channels}); + residual.conv1 = binding::conv1d_from_source( + *weights->store, + source, + unit + ".conv1.conv", + conv_weight_storage_type, + out_channels, + out_channels, + 7, + true); + residual.act2_alpha = source.require_f32(unit + ".act2.alpha", {out_channels}); + residual.act2_beta = source.require_f32(unit + ".act2.beta", {out_channels}); + residual.conv2 = binding::conv1d_from_source( + *weights->store, + source, + unit + ".conv2.conv", + conv_weight_storage_type, + out_channels, + out_channels, + 1, + true); + block.residual_units.push_back(std::move(residual)); + } + weights->decoder_blocks.push_back(std::move(block)); + channels = out_channels; + } + weights->output_alpha = source.require_f32("codec_model.decoder.decoder.5.alpha", {channels}); + weights->output_beta = source.require_f32("codec_model.decoder.decoder.5.beta", {channels}); + weights->output_conv = binding::conv1d_from_source( + *weights->store, + source, + "codec_model.decoder.decoder.6.conv", + conv_weight_storage_type, + 1, + channels, + 7, + true); + weights->store->upload(); + return weights; +} + +core::TensorValue causal_conv1d( + core::ModuleBuildContext & build_ctx, + const core::TensorValue & input, + const modules::Conv1dWeights & weights, + int64_t out_channels, + int64_t kernel, + int64_t stride, + int64_t dilation, + int64_t groups, + bool use_bias) { + const int64_t in_channels = input.shape.dims[1]; + const int64_t kernel_extent = (kernel - 1) * dilation + 1; + const int64_t left_pad = kernel_extent - stride; + const int64_t length = input.shape.dims[2]; + const float n_frames = static_cast(length - kernel_extent + left_pad) / static_cast(stride) + 1.0F; + const int64_t ideal_length = + (static_cast(std::ceil(n_frames)) - 1) * stride + (kernel_extent - left_pad); + const int64_t right_pad = std::max(0, ideal_length - length); + auto * padded = ggml_pad_ext( + build_ctx.ggml, + input.tensor, + static_cast(left_pad), + static_cast(right_pad), + 0, + 0, + 0, + 0, + 0, + 0); + auto padded_value = core::wrap_tensor( + padded, + core::TensorShape::from_dims({input.shape.dims[0], input.shape.dims[1], input.shape.dims[2] + left_pad + right_pad}), + GGML_TYPE_F32); + if (weights.weight.type != GGML_TYPE_F32 && weights.weight.type != GGML_TYPE_F16) { + throw std::runtime_error( + std::string("Breeze speech decoder depthwise conv does not support weight type: ") + + ggml_type_name(weights.weight.type)); + } + ggml_tensor * result = nullptr; + if (groups == in_channels) { + ggml_tensor * bias = nullptr; + if (use_bias) { + if (!weights.bias.has_value()) { + throw std::runtime_error("Breeze speech decoder depthwise conv requires bias"); + } + bias = core::reshape_tensor(build_ctx, *weights.bias, core::TensorShape::from_dims({out_channels, 1})).tensor; + } + for (int64_t batch = 0; batch < padded_value.shape.dims[0]; ++batch) { + auto * batch_input = ggml_view_2d( + build_ctx.ggml, + padded, + padded->ne[0], + padded->ne[1], + padded->nb[1], + static_cast(batch) * padded->nb[2]); + auto * batch_output = ggml_conv_1d_dw( + build_ctx.ggml, + weights.weight.tensor, + core::has_backend_addressable_layout(batch_input) ? batch_input : ggml_cont(build_ctx.ggml, batch_input), + static_cast(stride), + 0, + static_cast(dilation)); + if (bias != nullptr) { + batch_output = ggml_add(build_ctx.ggml, batch_output, bias); + } + batch_output = ggml_reshape_3d(build_ctx.ggml, batch_output, batch_output->ne[0], batch_output->ne[1], 1); + result = result == nullptr ? batch_output : ggml_concat(build_ctx.ggml, result, batch_output, 2); + } + return core::wrap_tensor( + result, + core::TensorShape::from_dims({input.shape.dims[0], out_channels, result->ne[0]}), + GGML_TYPE_F32); + } + return modules::Conv1dModule({ + in_channels, + out_channels, + kernel, + static_cast(stride), + 0, + static_cast(dilation), + use_bias, + }).build(build_ctx, padded_value, weights); +} + +core::TensorValue causal_conv_transpose1d( + core::ModuleBuildContext & build_ctx, + const core::TensorValue & input, + const modules::ConvTranspose1dWeights & weights, + int64_t out_channels, + int64_t kernel, + int64_t stride, + bool use_bias) { + const int64_t right_trim = kernel - stride; + auto output_bct = modules::ConvTranspose1dModule({ + input.shape.dims[1], + out_channels, + kernel, + static_cast(stride), + 0, + 1, + use_bias, + }).build(build_ctx, input, weights); + if (right_trim <= 0) { + return output_bct; + } + const int64_t trimmed_frames = output_bct.tensor->ne[0] - right_trim; + return core::wrap_tensor( + ggml_cont( + build_ctx.ggml, + ggml_view_3d( + build_ctx.ggml, + output_bct.tensor, + trimmed_frames, + out_channels, + input.shape.dims[0], + output_bct.tensor->nb[1], + output_bct.tensor->nb[2], + 0)), + core::TensorShape::from_dims({input.shape.dims[0], out_channels, trimmed_frames}), + GGML_TYPE_F32); +} + +core::TensorValue snake_beta( + core::ModuleBuildContext & build_ctx, + const core::TensorValue & input, + core::ConstantTensorCache & constants, + const std::vector & alpha, + const std::vector & beta) { + std::vector alpha_exp_values(alpha.size()); + std::transform(alpha.begin(), alpha.end(), alpha_exp_values.begin(), [](float value) { return std::exp(value); }); + std::vector inv_beta_exp_values(beta.size()); + std::transform(beta.begin(), beta.end(), inv_beta_exp_values.begin(), [](float value) { + return 1.0F / (std::exp(value) + 1.0e-9F); + }); + auto alpha_exp = constants.make_f32( + core::TensorShape::from_dims({1, static_cast(alpha.size()), 1}), + alpha_exp_values); + auto inv_beta_exp = constants.make_f32( + core::TensorShape::from_dims({1, static_cast(beta.size()), 1}), + inv_beta_exp_values); + auto * periodic = ggml_sqr( + build_ctx.ggml, + ggml_sin(build_ctx.ggml, ggml_mul(build_ctx.ggml, input.tensor, alpha_exp.tensor))); + return core::wrap_tensor( + ggml_add(build_ctx.ggml, input.tensor, ggml_mul(build_ctx.ggml, periodic, inv_beta_exp.tensor)), + input.shape, + GGML_TYPE_F32); +} + +core::TensorValue quantizer_decode( + ggml_context * ctx, + core::ModuleBuildContext & build_ctx, + ggml_tensor * codes_t_q_b, + const BreezeSpeechDecoderWeights & weights) { + const auto & config = weights.config; + const int64_t split_dim = config.codebook_dim / 2; + core::TensorValue semantic_sum; + for (int64_t group = 0; group < config.num_semantic_quantizers; ++group) { + auto * code_slice = ggml_view_2d( + ctx, + codes_t_q_b, + codes_t_q_b->ne[0], + codes_t_q_b->ne[2], + codes_t_q_b->nb[2], + static_cast(group) * codes_t_q_b->nb[1]); + auto indices = core::wrap_tensor( + code_slice, + core::TensorShape::from_dims({code_slice->ne[1], code_slice->ne[0]}), + GGML_TYPE_I32); + indices = core::ensure_backend_addressable_layout(build_ctx, indices); + auto decoded = modules::CodebookLookupModule({config.codebook_size, split_dim}) + .build(build_ctx, indices, weights.semantic_codebooks[static_cast(group)]); + semantic_sum = semantic_sum.valid() ? modules::AddModule{}.build(build_ctx, semantic_sum, decoded) : decoded; + } + semantic_sum = modules::LinearModule(binding::linear_config(split_dim, config.hidden_size, false)) + .build(build_ctx, semantic_sum, weights.semantic_output_proj); + + core::TensorValue acoustic_sum; + for (int64_t group = 0; group < config.num_quantizers - config.num_semantic_quantizers; ++group) { + const int64_t source_group = config.num_semantic_quantizers + group; + auto * code_slice = ggml_view_2d( + ctx, + codes_t_q_b, + codes_t_q_b->ne[0], + codes_t_q_b->ne[2], + codes_t_q_b->nb[2], + static_cast(source_group) * codes_t_q_b->nb[1]); + auto indices = core::wrap_tensor( + code_slice, + core::TensorShape::from_dims({code_slice->ne[1], code_slice->ne[0]}), + GGML_TYPE_I32); + indices = core::ensure_backend_addressable_layout(build_ctx, indices); + auto decoded = modules::CodebookLookupModule({config.codebook_size, split_dim}) + .build(build_ctx, indices, weights.acoustic_codebooks[static_cast(group)]); + acoustic_sum = acoustic_sum.valid() ? modules::AddModule{}.build(build_ctx, acoustic_sum, decoded) : decoded; + } + acoustic_sum = modules::LinearModule(binding::linear_config(split_dim, config.hidden_size, false)) + .build(build_ctx, acoustic_sum, weights.acoustic_output_proj); + return modules::AddModule{}.build(build_ctx, semantic_sum, acoustic_sum); +} + +core::TensorValue attention( + ggml_context * ctx, + core::ModuleBuildContext & build_ctx, + const core::TensorValue & input, + ggml_tensor * positions, + const core::TensorValue & attention_mask, + const modules::AttentionWeights & weights, + const DecoderConfig & config, + modules::ScaledDotProductAttentionLowering lowering = modules::ScaledDotProductAttentionLowering::Flash) { + const int64_t kv_repeat = config.num_heads / config.num_kv_heads; + auto q_value = modules::LinearModule(binding::linear_config(config.hidden_size, config.num_heads * config.head_dim, false)) + .build(build_ctx, input, {weights.q_weight, weights.q_bias}); + auto k_value = modules::LinearModule(binding::linear_config(config.hidden_size, config.num_kv_heads * config.head_dim, false)) + .build(build_ctx, input, {weights.k_weight, weights.k_bias}); + auto v_value = modules::LinearModule(binding::linear_config(config.hidden_size, config.num_kv_heads * config.head_dim, false)) + .build(build_ctx, input, {weights.v_weight, weights.v_bias}); + auto * q = q_value.tensor; + auto * k = k_value.tensor; + auto * v = v_value.tensor; + const int64_t seq = q->ne[1]; + const int64_t batch = q->ne[2]; + q = ggml_reshape_4d(ctx, q, config.head_dim, config.num_heads, seq, batch); + k = ggml_reshape_4d(ctx, k, config.head_dim, config.num_kv_heads, seq, batch); + v = ggml_reshape_4d(ctx, v, config.head_dim, config.num_kv_heads, seq, batch); + auto position_value = core::wrap_tensor(positions, core::TensorShape::from_dims({seq}), GGML_TYPE_I32); + q = modules::RoPEModule({ + config.head_dim, + GGML_ROPE_TYPE_NEOX, + config.rope_theta, + }).build( + build_ctx, + core::wrap_tensor(q, core::TensorShape::from_dims({batch, seq, config.num_heads, config.head_dim}), GGML_TYPE_F32), + position_value) + .tensor; + k = modules::RoPEModule({ + config.head_dim, + GGML_ROPE_TYPE_NEOX, + config.rope_theta, + }).build( + build_ctx, + core::wrap_tensor(k, core::TensorShape::from_dims({batch, seq, config.num_kv_heads, config.head_dim}), GGML_TYPE_F32), + position_value) + .tensor; + auto q_heads = modules::TransposeModule({{0, 2, 1, 3}, 4}).build( + build_ctx, + core::wrap_tensor(q, core::TensorShape::from_dims({batch, seq, config.num_heads, config.head_dim}), GGML_TYPE_F32)); + auto k_heads = modules::TransposeModule({{0, 2, 1, 3}, 4}).build( + build_ctx, + core::wrap_tensor(k, core::TensorShape::from_dims({batch, seq, config.num_kv_heads, config.head_dim}), GGML_TYPE_F32)); + auto v_heads = modules::TransposeModule({{0, 2, 1, 3}, 4}).build( + build_ctx, + core::wrap_tensor(v, core::TensorShape::from_dims({batch, seq, config.num_kv_heads, config.head_dim}), GGML_TYPE_F32)); + if (kv_repeat > 1) { + std::vector repeated_k; + std::vector repeated_v; + repeated_k.reserve(static_cast(config.num_heads)); + repeated_v.reserve(static_cast(config.num_heads)); + for (int64_t head = 0; head < config.num_kv_heads; ++head) { + auto one_k = modules::SliceModule({1, head, 1}).build(build_ctx, k_heads); + auto one_v = modules::SliceModule({1, head, 1}).build(build_ctx, v_heads); + for (int64_t repeat = 0; repeat < kv_repeat; ++repeat) { + repeated_k.push_back(one_k); + repeated_v.push_back(one_v); + } + } + k_heads = repeated_k.front(); + v_heads = repeated_v.front(); + for (size_t index = 1; index < repeated_k.size(); ++index) { + k_heads = modules::ConcatModule({1}).build(build_ctx, k_heads, repeated_k[index]); + v_heads = modules::ConcatModule({1}).build(build_ctx, v_heads, repeated_v[index]); + } + } + auto context = modules::ScaledDotProductAttentionModule({ + config.head_dim, + lowering, + GGML_PREC_F32, + modules::AttentionCausality::NonCausal, + }).build( + build_ctx, + q_heads, + k_heads, + v_heads, + attention_mask); + context = core::ensure_backend_addressable_layout(build_ctx, context); + return modules::LinearModule(binding::linear_config(config.num_heads * config.head_dim, config.hidden_size, false)) + .build( + build_ctx, + core::reshape_tensor(build_ctx, context, core::TensorShape::from_dims({batch, seq, config.num_heads * config.head_dim})), + {weights.out_weight, weights.out_bias}); +} + +core::TensorValue convnext( + core::ModuleBuildContext & build_ctx, + const core::TensorValue & input_bct, + const ConvNeXtWeights & weights) { + const int64_t channels = input_bct.shape.dims[1]; + auto hidden = causal_conv1d(build_ctx, input_bct, weights.dwconv, channels, 7, 1, 1, channels, true); + hidden = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build_ctx, hidden); + hidden = modules::LayerNormModule({channels, 1.0e-6F, true, true}) + .build(build_ctx, hidden, weights.norm); + hidden = modules::LinearModule(binding::linear_config(channels, channels * 4, true)) + .build(build_ctx, hidden, weights.pwconv1); + hidden = modules::GeluModule({modules::GeluApproximation::ExactErf}).build(build_ctx, hidden); + hidden = modules::LinearModule(binding::linear_config(channels * 4, channels, true)) + .build(build_ctx, hidden, weights.pwconv2); + hidden = modules::LayerScaleModule{}.build( + build_ctx, + hidden, + weights.gamma); + hidden = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build_ctx, hidden); + return modules::AddModule{}.build(build_ctx, input_bct, hidden); +} + +core::TensorValue residual_unit( + core::ModuleBuildContext & build_ctx, + const core::TensorValue & input, + const ResidualUnitWeights & weights, + int64_t dilation, + core::ConstantTensorCache & constants) { + const int64_t channels = input.shape.dims[1]; + auto hidden = snake_beta( + build_ctx, + input, + constants, + weights.act1_alpha, + weights.act1_beta); + hidden = causal_conv1d(build_ctx, hidden, weights.conv1, channels, 7, 1, dilation, 1, true); + hidden = snake_beta( + build_ctx, + hidden, + constants, + weights.act2_alpha, + weights.act2_beta); + hidden = causal_conv1d(build_ctx, hidden, weights.conv2, channels, 1, 1, 1, 1, true); + return modules::AddModule{}.build(build_ctx, input, hidden); +} + +std::vector make_mask(int64_t frames, int64_t window) { + std::vector mask(static_cast(frames * frames), kMaskNegInf); + for (int64_t q = 0; q < frames; ++q) { + const int64_t min_k = std::max(0, q - window + 1); + for (int64_t k = min_k; k <= q; ++k) { + mask[static_cast(k + frames * q)] = 0.0F; + } + } + return mask; +} + +int graph_node_capacity(const DecoderConfig & config) { + return static_cast(4096 + config.num_layers * config.num_heads * 16 + config.upsample_rates.size() * 512); +} + +} // namespace + +class BreezeSpeechDecoderGraph { +public: + BreezeSpeechDecoderGraph( + std::shared_ptr weights, + int64_t code_frames, + core::ExecutionContext & execution_context, + core::ConstantTensorCache & constants, + size_t graph_arena_bytes, + bool allow_flash_attention = true) + : weights_(std::move(weights)), + code_frames_(code_frames), + backend_(execution_context.backend()), + allow_flash_attention_(allow_flash_attention), + compute_threads_(std::max(1, execution_context.config().threads)) { + if (weights_ == nullptr) { + throw std::runtime_error("Breeze speech decoder graph requires weights"); + } + if (code_frames_ <= 0) { + throw std::runtime_error("Breeze speech decoder graph requires positive frame count"); + } + if (backend_ == nullptr) { + throw std::runtime_error("Breeze speech decoder backend is not initialized"); + } + const auto & config = weights_->config; + waveform_frames_ = code_frames_; + for (const auto factor : config.upsampling_ratios) { + waveform_frames_ *= factor; + } + for (const auto factor : config.upsample_rates) { + waveform_frames_ *= factor; + } + + ggml_init_params params{ + /*.mem_size =*/ graph_arena_bytes, + /*.mem_buffer =*/ nullptr, + /*.no_alloc =*/ true, + }; + ctx_.reset(ggml_init(params)); + if (ctx_ == nullptr) { + throw std::runtime_error("failed to initialize Breeze speech decoder ggml context"); + } + + codes_ = ggml_new_tensor_3d(ctx_.get(), GGML_TYPE_I32, code_frames_, config.num_quantizers, 1); + ggml_set_input(codes_); + positions_ = ggml_new_tensor_1d(ctx_.get(), GGML_TYPE_I32, code_frames_); + ggml_set_input(positions_); + mask_ = ggml_new_tensor_4d(ctx_.get(), GGML_TYPE_F16, code_frames_, code_frames_, 1, 1); + ggml_set_input(mask_); + + core::ModuleBuildContext build_ctx{ + ctx_.get(), + "breeze_tts.speech_decoder", + execution_context.backend_type(), + }; + constants.begin_graph(); + auto hidden = quantizer_decode(ctx_.get(), build_ctx, codes_, *weights_); + hidden = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build_ctx, hidden); + hidden = causal_conv1d(build_ctx, hidden, weights_->pre_conv, config.latent_dim, 3, 1, 1, 1, true); + hidden = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build_ctx, hidden); + hidden = modules::LinearModule(binding::linear_config(config.latent_dim, config.hidden_size, true)) + .build(build_ctx, hidden, weights_->transformer_input_proj); + for (const auto & layer : weights_->transformer_layers) { + auto attn_in = modules::RMSNormModule({config.hidden_size, config.rms_norm_eps, true, false}) + .build(build_ctx, hidden, layer.input_norm); + auto attention_mask = core::wrap_tensor( + mask_, + core::TensorShape::from_dims({1, 1, code_frames_, code_frames_}), + GGML_TYPE_F16); + auto attn_out = attention( + ctx_.get(), + build_ctx, + attn_in, + positions_, + attention_mask, + layer.attention, + config, + allow_flash_attention_ ? modules::ScaledDotProductAttentionLowering::Flash + : modules::ScaledDotProductAttentionLowering::Explicit); + attn_out = modules::LayerScaleModule{}.build( + build_ctx, + attn_out, + layer.attn_scale); + hidden = modules::AddModule{}.build(build_ctx, hidden, attn_out); + auto mlp_in = modules::RMSNormModule({config.hidden_size, config.rms_norm_eps, true, false}) + .build(build_ctx, hidden, layer.post_norm); + auto mlp_out = modules::GatedFeedForwardModule({ + config.hidden_size, + config.intermediate_size, + false, + modules::GatedFeedForwardActivation::Silu, + }).build(build_ctx, mlp_in, layer.mlp); + mlp_out = modules::LayerScaleModule{}.build( + build_ctx, + mlp_out, + layer.mlp_scale); + hidden = modules::AddModule{}.build(build_ctx, hidden, mlp_out); + } + hidden = modules::RMSNormModule({config.hidden_size, config.rms_norm_eps, true, false}) + .build(build_ctx, hidden, weights_->transformer_norm); + hidden = modules::LinearModule(binding::linear_config(config.hidden_size, config.latent_dim, true)) + .build(build_ctx, hidden, weights_->transformer_output_proj); + hidden = modules::TransposeModule({{0, 2, 1, 3}, 3}).build(build_ctx, hidden); + for (size_t i = 0; i < weights_->upsample_stages.size(); ++i) { + const auto & stage = weights_->upsample_stages[i]; + hidden = causal_conv_transpose1d( + build_ctx, + hidden, + stage.upconv, + config.latent_dim, + config.upsampling_ratios[i], + config.upsampling_ratios[i], + true); + hidden = convnext(build_ctx, hidden, stage.convnext); + } + hidden = causal_conv1d(build_ctx, hidden, weights_->decoder_input_conv, config.decoder_dim, 7, 1, 1, 1, true); + int64_t decoder_channels = config.decoder_dim; + for (size_t block_index = 0; block_index < weights_->decoder_blocks.size(); ++block_index) { + const auto & block = weights_->decoder_blocks[block_index]; + const int64_t out_channels = decoder_channels / 2; + hidden = snake_beta( + build_ctx, + hidden, + constants, + block.input_alpha, + block.input_beta); + hidden = causal_conv_transpose1d( + build_ctx, + hidden, + block.upconv, + out_channels, + config.upsample_rates[block_index] * 2, + config.upsample_rates[block_index], + true); + for (size_t unit_index = 0; unit_index < block.residual_units.size(); ++unit_index) { + const int64_t dilation = unit_index == 0 ? 1 : unit_index == 1 ? 3 : 9; + hidden = residual_unit( + build_ctx, + hidden, + block.residual_units[unit_index], + dilation, + constants); + } + decoder_channels = out_channels; + } + hidden = snake_beta( + build_ctx, + hidden, + constants, + weights_->output_alpha, + weights_->output_beta); + output_ = ggml_clamp(ctx_.get(), causal_conv1d(build_ctx, hidden, weights_->output_conv, 1, 7, 1, 1, 1, true).tensor, -1.0F, 1.0F); + ggml_set_output(output_); + graph_ = ggml_new_graph_custom(ctx_.get(), graph_node_capacity(config), false); + ggml_build_forward_expand(graph_, output_); + constants.finish_graph(); + constants.ensure_uploaded(); + + gallocr_ = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend_)); + if (gallocr_ == nullptr || !ggml_gallocr_alloc_graph(gallocr_, graph_)) { + throw std::runtime_error("failed to allocate Breeze speech decoder graph"); + } + positions_data_.resize(static_cast(code_frames_)); + for (int64_t i = 0; i < code_frames_; ++i) { + positions_data_[static_cast(i)] = static_cast(i); + } + const auto mask = make_mask(code_frames_, config.sliding_window); + mask_f16_data_.resize(mask.size()); + for (size_t index = 0; index < mask.size(); ++index) { + mask_f16_data_[index] = ggml_fp32_to_fp16(mask[index]); + } + upload_static_inputs(); + } + + ~BreezeSpeechDecoderGraph() { + engine::core::release_backend_graph_resources(backend_, graph_); + if (gallocr_ != nullptr) { + ggml_gallocr_free(gallocr_); + } + } + + bool matches( + const BreezeSpeechDecoderWeights & weights, + int64_t code_frames, + ggml_backend_t backend, + int threads) const { + const bool frame_match = code_frames_ == code_frames; + return weights_.get() == &weights && frame_match && backend_ == backend && + compute_threads_ == std::max(1, threads); + } + + std::vector run(const int32_t * codes, size_t code_count) { + const int64_t input_frames = static_cast(code_count / weights_->config.num_quantizers); + const size_t expected = static_cast(code_frames_ * weights_->config.num_quantizers); + if (code_count % static_cast(weights_->config.num_quantizers) != 0 || input_frames <= 0 || input_frames > code_frames_) { + throw std::runtime_error("Breeze speech decoder code count exceeds graph capacity"); + } + // Cached GGML graphs may reuse backend allocations whose input contents are + // not guaranteed to survive a prior execution. Restore every declared input, + // not only the request-varying codes, before replaying a retained graph. + upload_static_inputs(); + std::vector tensor_codes(expected, 0); + for (int64_t frame = 0; frame < input_frames; ++frame) { + for (int64_t group = 0; group < weights_->config.num_quantizers; ++group) { + tensor_codes[static_cast(frame + code_frames_ * group)] = + codes[static_cast(frame * weights_->config.num_quantizers + group)]; + } + } + ggml_backend_tensor_set(codes_, tensor_codes.data(), 0, tensor_codes.size() * sizeof(int32_t)); + core::set_backend_threads(backend_, compute_threads_); + const ggml_status status = engine::core::compute_backend_graph(backend_, graph_); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Breeze speech decoder graph compute failed"); + } + std::vector audio(static_cast(waveform_frames_), 0.0F); + ggml_backend_tensor_get(output_, audio.data(), 0, audio.size() * sizeof(float)); + return audio; + } + +private: + void upload_static_inputs() { + ggml_backend_tensor_set( + positions_, + positions_data_.data(), + 0, + positions_data_.size() * sizeof(int32_t)); + ggml_backend_tensor_set( + mask_, + mask_f16_data_.data(), + 0, + mask_f16_data_.size() * sizeof(ggml_fp16_t)); + } + + std::shared_ptr weights_; + int64_t code_frames_ = 0; + int64_t waveform_frames_ = 0; + ggml_backend_t backend_ = nullptr; + bool allow_flash_attention_ = true; + int compute_threads_ = 1; + std::unique_ptr ctx_; + ggml_tensor * codes_ = nullptr; + ggml_tensor * positions_ = nullptr; + ggml_tensor * mask_ = nullptr; + ggml_tensor * output_ = nullptr; + std::vector positions_data_; + std::vector mask_f16_data_; + ggml_cgraph * graph_ = nullptr; + ggml_gallocr_t gallocr_ = nullptr; +}; + +BreezeSpeechDecoderRuntime::BreezeSpeechDecoderRuntime( + std::shared_ptr assets, + core::ExecutionContext & execution_context, + size_t graph_arena_bytes, + size_t constant_context_bytes, + assets::TensorStorageType linear_weight_storage_type, + assets::TensorStorageType conv_weight_storage_type, + core::AttentionPreference attention_preference) + : assets_(std::move(assets)), + execution_context_(&execution_context), + graph_arena_bytes_(graph_arena_bytes) { + if (assets_ == nullptr) { + throw std::runtime_error("BreezeTTS speech decoder requires assets"); + } + weights_ = load_weights( + *assets_, + execution_context_->backend(), + execution_context_->backend_type(), + linear_weight_storage_type, + conv_weight_storage_type); + allow_flash_attention_ = core::resolve_flash_attention( + execution_context_->backend(), weights_->config.head_dim, attention_preference); + engine::debug::trace_log_scalar("breeze_tts.attention.allow_decoder_flash", allow_flash_attention_); + constants_ = std::make_unique( + execution_context_->backend(), + std::max(1, execution_context_->config().threads), + "breeze_tts.speech_decoder.constants", + constant_context_bytes); +} + +BreezeSpeechDecoderRuntime::~BreezeSpeechDecoderRuntime() = default; + +runtime::AudioBuffer BreezeSpeechDecoderRuntime::decode(const BreezeSpeechCodes & codec_codes) const { + const auto total_start = Clock::now(); + if (codec_codes.frames <= 0 || codec_codes.code_groups != weights_->config.num_quantizers) { + throw std::runtime_error("Breeze speech decoder received invalid codec shape"); + } + if (static_cast(codec_codes.codes.size()) != codec_codes.frames * codec_codes.code_groups) { + throw std::runtime_error("Breeze speech decoder codec payload size mismatch"); + } + std::vector samples; + samples.reserve(static_cast(codec_codes.frames * kDecodeSamplesPerCode)); + for (int64_t start = 0; start < codec_codes.frames; start += kChunkCodes) { + const int64_t end = std::min(start + kChunkCodes, codec_codes.frames); + const int64_t context = start > kLeftContextCodes ? kLeftContextCodes : start; + const int64_t chunk_start = start - context; + const int64_t chunk_frames = end - chunk_start; + std::vector chunk(static_cast(chunk_frames * codec_codes.code_groups), 0); + for (int64_t frame = 0; frame < chunk_frames; ++frame) { + const int64_t src_frame = chunk_start + frame; + const auto src = codec_codes.codes.begin() + static_cast(src_frame * codec_codes.code_groups); + const auto dst = chunk.begin() + static_cast(frame * codec_codes.code_groups); + std::copy(src, src + codec_codes.code_groups, dst); + } + const int threads = std::max(1, execution_context_->config().threads); + auto * graph_slot = &graph_; +#if defined(ENGINE_HIP_STRIX_HALO_OPTIMIZATIONS) + const bool optimized_cache_enabled = + kStrixHaloGraphCacheEnabled && execution_context_->backend_type() == core::BackendType::Hip; + if (optimized_cache_enabled) { + for (size_t index = 0; index < kStrixHaloCachedChunkFrames.size(); ++index) { + if (chunk_frames == kStrixHaloCachedChunkFrames[index]) { + graph_slot = &optimized_graphs_[index]; + break; + } + } + } +#endif + auto & graph = *graph_slot; + const bool graph_rebuilt = + graph == nullptr || !graph->matches(*weights_, chunk_frames, execution_context_->backend(), threads); + if (graph_rebuilt) { + auto replacement = std::make_unique( + weights_, + chunk_frames, + *execution_context_, + *constants_, + graph_arena_bytes_, + allow_flash_attention_); + graph = std::move(replacement); + } + auto decoded = graph->run(chunk.data(), chunk.size()); + const int64_t drop = context * kDecodeSamplesPerCode; + if (drop > static_cast(decoded.size())) { + throw std::runtime_error("Breeze speech decoder chunk context exceeds decoded waveform"); + } + const int64_t valid_samples = chunk_frames * kDecodeSamplesPerCode; + if (valid_samples < drop || valid_samples > static_cast(decoded.size())) { + throw std::runtime_error("Breeze speech decoder valid sample range exceeds decoded waveform"); + } + samples.insert( + samples.end(), + decoded.begin() + static_cast(drop), + decoded.begin() + static_cast(valid_samples)); + } + debug::timing_log_scalar("breeze_tts.speech_decoder.total_ms", engine::debug::elapsed_ms(total_start, Clock::now())); + return runtime::AudioBuffer{kSampleRate, 1, std::move(samples)}; +} + +runtime::AudioBuffer BreezeSpeechDecoderRuntime::decode_and_trim_reference( + const BreezeSpeechCodes & reference_codes, + const BreezeSpeechCodes & generated_codes) const { + if (reference_codes.code_groups != generated_codes.code_groups) { + throw std::runtime_error("Breeze speech decoder reference/generated code group mismatch"); + } + if (reference_codes.frames < 0 || generated_codes.frames < 0 || reference_codes.code_groups <= 0) { + throw std::runtime_error("Breeze speech decoder reference/generated code shape is invalid"); + } + if (reference_codes.frames > std::numeric_limits::max() - generated_codes.frames) { + throw std::runtime_error("Breeze speech decoder combined frame count is too large"); + } + BreezeSpeechCodes combined; + combined.frames = reference_codes.frames + generated_codes.frames; + combined.code_groups = reference_codes.code_groups; + if (combined.frames > std::numeric_limits::max() / combined.code_groups) { + throw std::runtime_error("Breeze speech decoder combined code count is too large"); + } + const int64_t combined_code_count = combined.frames * combined.code_groups; + if (static_cast(combined_code_count) > std::numeric_limits::max()) { + throw std::runtime_error("Breeze speech decoder combined code count exceeds host size limits"); + } + combined.codes.reserve(static_cast(combined_code_count)); + combined.codes.insert(combined.codes.end(), reference_codes.codes.begin(), reference_codes.codes.end()); + combined.codes.insert(combined.codes.end(), generated_codes.codes.begin(), generated_codes.codes.end()); + auto audio = decode(combined); + if (reference_codes.frames > std::numeric_limits::max() / kDecodeSamplesPerCode) { + throw std::runtime_error("Breeze speech decoder reference sample count is too large"); + } + const int64_t cut = reference_codes.frames * kDecodeSamplesPerCode; + if (static_cast(cut) > audio.samples.size()) { + throw std::runtime_error("Breeze speech decoder reference trim is out of range"); + } + audio.samples.erase(audio.samples.begin(), audio.samples.begin() + static_cast(cut)); + return audio; +} + +void BreezeSpeechDecoderRuntime::release_runtime_graphs() const { + graph_.reset(); + for (auto & graph : optimized_graphs_) { + graph.reset(); + } +} + +} // namespace engine::models::breeze_tts diff --git a/src/models/breeze_tts/speech_encoder.cpp b/src/models/breeze_tts/speech_encoder.cpp new file mode 100644 index 000000000..d9453ce9f --- /dev/null +++ b/src/models/breeze_tts/speech_encoder.cpp @@ -0,0 +1,838 @@ +#include "engine/models/breeze_tts/speech_encoder.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/audio/conversion.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/attention/feed_forward.h" +#include "engine/framework/modules/attention/scaled_dot_product_attention.h" +#include "engine/framework/modules/attention/types.h" +#include "engine/framework/modules/conditioning_modules.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/positional_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/streaming_conv_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/transformers/qwen_causal_decoder.h" +#include "engine/framework/modules/weight_binding.h" + +#include "engine/framework/core/constant_tensor_cache.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::breeze_tts { + +namespace { + +using Clock = std::chrono::steady_clock; +namespace binding = modules::binding; + +} // namespace + +constexpr int64_t kSampleRate = 24000; +constexpr int64_t kDownsampleRate = 1920; +constexpr int64_t kHiddenSize = 512; +constexpr int64_t kQuantizerDim = 256; +constexpr int64_t kCodebookSize = 2048; +constexpr int64_t kValidQuantizers = 16; +constexpr float kCodebookEps = 1.0e-5F; +constexpr std::array kEncoderConvConfigs{{ + {1, 64, 7, 1, 0, 1, true}, + {64, 128, 8, 4, 0, 1, true}, + {128, 256, 10, 5, 0, 1, true}, + {256, 512, 12, 6, 0, 1, true}, + {512, 1024, 16, 8, 0, 1, true}, + {1024, 512, 3, 1, 0, 1, true}, +}}; +constexpr std::array kEncoderConvPadModes{{ + modules::StreamingPadMode::Constant, + modules::StreamingPadMode::Constant, + modules::StreamingPadMode::Constant, + modules::StreamingPadMode::Constant, + modules::StreamingPadMode::Constant, + modules::StreamingPadMode::Constant, +}}; +constexpr modules::Conv1dConfig kDownsampleConvConfig{512, 512, 4, 2, 0, 1, false}; +constexpr modules::Conv1dConfig kSemanticProjectionConfig{512, 256, 1, 1, 0, 1, false}; +constexpr modules::Conv1dConfig kAcousticProjectionConfig{512, 256, 1, 1, 0, 1, false}; + +// The conv stack downsamples 960x before the transformer (strides 4*5*6*8). +constexpr int64_t kTransformerStride = 960; +// Conv chunks are kChunkSamples of new audio preceded by kChunkOverlapSamples of +// left context. The exact left context the stack needs is the sum of each +// layer's left pad scaled by the cumulative stride: +// 6+2 + 4+2*4 + 5*4+2*20 + 6*20+2*120 + 8*120+2*960 + 2*960 = 5240 samples. +constexpr int64_t kChunkSamples = 120000; // 5 s at 24 kHz, 125 transformer frames +constexpr int64_t kChunkOverlapSamples = 9600; // 10 transformer frames > 5240 +constexpr int64_t kChunkCapacity = kChunkSamples + kChunkOverlapSamples; +static_assert(kChunkSamples % kTransformerStride == 0); +static_assert(kChunkOverlapSamples % kTransformerStride == 0); +constexpr int64_t kChunkFrames = kChunkCapacity / kTransformerStride; +// The transformer graph is built at frame capacities rounded up to this +// bucket, so reference lengths within a bucket share one graph instead of +// rebuilding per exact length. +constexpr int64_t kTransformerFrameBucket = kChunkSamples / kTransformerStride; + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +struct ResBlockWeights { + modules::Conv1dWeights conv1; + modules::Conv1dWeights conv2; +}; + +struct TransformerLayerWeights { + modules::AttentionWeights attention; + modules::FeedForwardWeights feed_forward; + modules::NormWeights norm1; + modules::NormWeights norm2; + modules::LayerScaleWeights scale1; + modules::LayerScaleWeights scale2; +}; + +struct BreezeSpeechEncoderWeights { + std::shared_ptr store; + std::vector encoder_convs; + std::vector residual_blocks; + std::vector transformer_layers; + modules::Conv1dWeights downsample; + modules::Conv1dWeights semantic_projection; + modules::Conv1dWeights acoustic_projection; + std::vector> semantic_codebooks; + std::vector> acoustic_codebooks; +}; + +core::TensorValue speech_conv( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const modules::Conv1dWeights & weights, + modules::Conv1dConfig config, + modules::StreamingPadMode pad_mode) { + const int64_t effective_kernel = (config.kernel_size - 1) * config.dilation + 1; + const int64_t left_pad = effective_kernel - config.stride; + const int64_t right_pad = (config.stride - (input.shape.dims[2] % config.stride)) % config.stride; + auto padded = input; + if (left_pad > 0) { + auto prefix = modules::SliceModule({2, 0, 1}).build(ctx, input); + prefix = core::ensure_backend_addressable_layout(ctx, prefix); + if (pad_mode == modules::StreamingPadMode::Constant) { + prefix = core::wrap_tensor(ggml_scale(ctx.ggml, prefix.tensor, 0.0F), prefix.shape, GGML_TYPE_F32); + } + prefix = modules::RepeatModule({ + core::TensorShape::from_dims({input.shape.dims[0], input.shape.dims[1], left_pad}), + }).build(ctx, prefix); + padded = modules::ConcatModule({2}).build(ctx, prefix, padded); + } + if (right_pad > 0) { + auto suffix = modules::SliceModule({2, input.shape.dims[2] - 1, 1}).build(ctx, input); + suffix = core::ensure_backend_addressable_layout(ctx, suffix); + if (pad_mode == modules::StreamingPadMode::Constant) { + suffix = core::wrap_tensor(ggml_scale(ctx.ggml, suffix.tensor, 0.0F), suffix.shape, GGML_TYPE_F32); + } + suffix = modules::RepeatModule({ + core::TensorShape::from_dims({input.shape.dims[0], input.shape.dims[1], right_pad}), + }).build(ctx, suffix); + padded = modules::ConcatModule({2}).build(ctx, padded, suffix); + } + config.padding = 0; + return modules::Conv1dModule(config).build(ctx, padded, weights); +} + +core::TensorValue speech_residual_block( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const ResBlockWeights & block, + core::ConstantTensorCache &) { + const int64_t channels = input.shape.dims[1]; + auto x = modules::EluModule{}.build(ctx, input); + x = speech_conv(ctx, x, block.conv1, {channels, channels / 2, 3, 1, 0, 1, true}, modules::StreamingPadMode::Constant); + x = modules::EluModule{}.build(ctx, x); + x = speech_conv(ctx, x, block.conv2, {channels / 2, channels, 1, 1, 0, 1, true}, modules::StreamingPadMode::Constant); + return modules::AddModule{}.build(ctx, input, x); +} + +core::TensorValue mimi_self_attention( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const core::TensorValue & positions, + const TransformerLayerWeights & weights, + const std::optional & attention_mask, + modules::ScaledDotProductAttentionLowering lowering = modules::ScaledDotProductAttentionLowering::Flash) { + constexpr int64_t kHeads = 8; + constexpr int64_t kHeadDim = 64; + auto q = modules::LinearModule(binding::linear_config(kHiddenSize, kHiddenSize, false)) + .build(ctx, input, {weights.attention.q_weight, weights.attention.q_bias}); + auto k = modules::LinearModule(binding::linear_config(kHiddenSize, kHiddenSize, false)) + .build(ctx, input, {weights.attention.k_weight, weights.attention.k_bias}); + auto v = modules::LinearModule(binding::linear_config(kHiddenSize, kHiddenSize, false)) + .build(ctx, input, {weights.attention.v_weight, weights.attention.v_bias}); + q = core::reshape_tensor( + ctx, + core::ensure_backend_addressable_layout(ctx, q), + core::TensorShape::from_dims({q.shape.dims[0], q.shape.dims[1], kHeads, kHeadDim})); + k = core::reshape_tensor( + ctx, + core::ensure_backend_addressable_layout(ctx, k), + core::TensorShape::from_dims({k.shape.dims[0], k.shape.dims[1], kHeads, kHeadDim})); + v = core::reshape_tensor( + ctx, + core::ensure_backend_addressable_layout(ctx, v), + core::TensorShape::from_dims({v.shape.dims[0], v.shape.dims[1], kHeads, kHeadDim})); + q = modules::RoPEModule({kHeadDim, GGML_ROPE_TYPE_NEOX}).build(ctx, q, positions); + k = modules::RoPEModule({kHeadDim, GGML_ROPE_TYPE_NEOX}).build(ctx, k, positions); + auto q_heads = modules::TransposeModule({{0, 2, 1, 3}, q.shape.rank}).build(ctx, q); + auto k_heads = modules::TransposeModule({{0, 2, 1, 3}, k.shape.rank}).build(ctx, k); + auto v_heads = modules::TransposeModule({{0, 2, 1, 3}, v.shape.rank}).build(ctx, v); + auto context = modules::ScaledDotProductAttentionModule({ + kHeadDim, + lowering, + GGML_PREC_F32, + modules::AttentionCausality::Causal, + }).build(ctx, q_heads, k_heads, v_heads, attention_mask); + context = core::ensure_backend_addressable_layout(ctx, context); + context = core::reshape_tensor(ctx, context, core::TensorShape::from_dims({input.shape.dims[0], input.shape.dims[1], kHiddenSize})); + return modules::LinearModule(binding::linear_config(kHiddenSize, kHiddenSize, false)) + .build(ctx, context, {weights.attention.out_weight, weights.attention.out_bias}); +} + +core::TensorValue transformer_block( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const core::TensorValue & positions, + const TransformerLayerWeights & weights, + const std::optional & attention_mask, + modules::ScaledDotProductAttentionLowering lowering = modules::ScaledDotProductAttentionLowering::Flash) { + const modules::LayerNormModule norm({kHiddenSize, 1.0e-5F, true, true}); + auto x = norm.build(ctx, input, weights.norm1); + auto attn_out = modules::LayerScaleModule{}.build( + ctx, + mimi_self_attention(ctx, x, positions, weights, attention_mask, lowering), + weights.scale1); + x = modules::AddModule{}.build(ctx, input, attn_out); + auto y = norm.build(ctx, x, weights.norm2); + y = modules::FeedForwardModule({ + kHiddenSize, + 2048, + false, + modules::GeluApproximation::ExactErf, + }).build(ctx, y, weights.feed_forward); + y = modules::LayerScaleModule{}.build(ctx, y, weights.scale2); + return modules::AddModule{}.build(ctx, x, y); +} + +std::vector codebook_embedding( + const assets::TensorSource & source, + const std::string & prefix) { + const auto cluster_usage = source.require_f32(prefix + "cluster_usage", {kCodebookSize}); + const auto embedding_sum = source.require_f32(prefix + "embed_sum", {kCodebookSize, kQuantizerDim}); + std::vector embedding(embedding_sum.size(), 0.0F); + for (int64_t code = 0; code < kCodebookSize; ++code) { + const float denom = std::max(cluster_usage[static_cast(code)], kCodebookEps); + for (int64_t dim = 0; dim < kQuantizerDim; ++dim) { + const size_t offset = static_cast(code * kQuantizerDim + dim); + embedding[offset] = embedding_sum[offset] / denom; + } + } + return embedding; +} + +int32_t nearest_code(const std::vector & residual, const std::vector & embedding) { + int32_t best = 0; + float best_distance = std::numeric_limits::infinity(); + for (int64_t code = 0; code < kCodebookSize; ++code) { + float distance = 0.0F; + for (int64_t dim = 0; dim < kQuantizerDim; ++dim) { + const float diff = + residual[static_cast(dim)] - embedding[static_cast(code * kQuantizerDim + dim)]; + distance += diff * diff; + } + if (distance < best_distance) { + best_distance = distance; + best = static_cast(code); + } + } + return best; +} + +std::vector quantize_projected( + const std::vector & semantic, + const std::vector & acoustic, + int64_t frames, + const BreezeSpeechEncoderWeights & weights) { + if (weights.semantic_codebooks.empty() || static_cast(weights.acoustic_codebooks.size()) < kValidQuantizers - 1) { + throw std::runtime_error("Breeze speech encoder has insufficient quantizer codebooks"); + } + if (static_cast(semantic.size()) != kQuantizerDim * frames || + static_cast(acoustic.size()) != kQuantizerDim * frames) { + throw std::runtime_error("Breeze speech encoder projected tensor size mismatch"); + } + + std::vector codes(static_cast(frames * kValidQuantizers), 0); + std::vector residual(static_cast(kQuantizerDim), 0.0F); + for (int64_t frame = 0; frame < frames; ++frame) { + for (int64_t dim = 0; dim < kQuantizerDim; ++dim) { + residual[static_cast(dim)] = semantic[static_cast(dim * frames + frame)]; + } + const int32_t semantic_code = nearest_code(residual, weights.semantic_codebooks[0]); + codes[static_cast(frame * kValidQuantizers)] = semantic_code; + + for (int64_t dim = 0; dim < kQuantizerDim; ++dim) { + residual[static_cast(dim)] = acoustic[static_cast(dim * frames + frame)]; + } + for (int64_t group = 1; group < kValidQuantizers; ++group) { + const auto & embedding = weights.acoustic_codebooks[static_cast(group - 1)]; + const int32_t code = nearest_code(residual, embedding); + codes[static_cast(frame * kValidQuantizers + group)] = code; + for (int64_t dim = 0; dim < kQuantizerDim; ++dim) { + residual[static_cast(dim)] -= embedding[static_cast(code * kQuantizerDim + dim)]; + } + } + } + return codes; +} + +std::shared_ptr load_weights( + const BreezeTTSAssets & assets, + ggml_backend_t backend, + core::BackendType backend_type, + assets::TensorStorageType linear_weight_storage_type, + assets::TensorStorageType conv_weight_storage_type) { + const auto & source = *assets.weights; + auto weights = std::make_shared(); + weights->store = std::make_shared( + backend, + backend_type, + "breeze_tts.speech_encoder.weights", + 32ull * 1024ull * 1024ull); + + const char * conv_prefixes[] = { + "codec_model.encoder.encoder.layers.0.conv", + "codec_model.encoder.encoder.layers.3.conv", + "codec_model.encoder.encoder.layers.6.conv", + "codec_model.encoder.encoder.layers.9.conv", + "codec_model.encoder.encoder.layers.12.conv", + "codec_model.encoder.encoder.layers.14.conv", + }; + for (size_t i = 0; i < std::size(conv_prefixes); ++i) { + const auto & conv = kEncoderConvConfigs[i]; + weights->encoder_convs.push_back( + binding::conv1d_from_source( + *weights->store, + source, + conv_prefixes[i], + conv_weight_storage_type, + conv.out_channels, + conv.in_channels, + conv.kernel_size, + conv.use_bias)); + } + + const int residual_indices[] = {1, 4, 7, 10}; + const int64_t residual_channels[] = {64, 128, 256, 512}; + for (size_t i = 0; i < std::size(residual_indices); ++i) { + const int idx = residual_indices[i]; + const int64_t channels = residual_channels[i]; + ResBlockWeights block; + block.conv1 = binding::conv1d_from_source( + *weights->store, + source, + "codec_model.encoder.encoder.layers." + std::to_string(idx) + ".block.1.conv", + conv_weight_storage_type, + channels / 2, + channels, + 3, + true); + block.conv2 = binding::conv1d_from_source( + *weights->store, + source, + "codec_model.encoder.encoder.layers." + std::to_string(idx) + ".block.3.conv", + conv_weight_storage_type, + channels, + channels / 2, + 1, + true); + weights->residual_blocks.push_back(std::move(block)); + } + + for (int layer = 0; layer < 8; ++layer) { + const std::string prefix = "codec_model.encoder.encoder_transformer.layers." + std::to_string(layer); + TransformerLayerWeights block; + block.attention.q_weight = weights->store->load_tensor(source, prefix + ".self_attn.q_proj.weight", linear_weight_storage_type, {512, 512}); + block.attention.k_weight = weights->store->load_tensor(source, prefix + ".self_attn.k_proj.weight", linear_weight_storage_type, {512, 512}); + block.attention.v_weight = weights->store->load_tensor(source, prefix + ".self_attn.v_proj.weight", linear_weight_storage_type, {512, 512}); + block.attention.out_weight = weights->store->load_tensor(source, prefix + ".self_attn.o_proj.weight", linear_weight_storage_type, {512, 512}); + block.feed_forward.fc1_weight = weights->store->load_tensor(source, prefix + ".mlp.fc1.weight", linear_weight_storage_type, {2048, 512}); + block.feed_forward.fc2_weight = weights->store->load_tensor(source, prefix + ".mlp.fc2.weight", linear_weight_storage_type, {512, 2048}); + block.norm1 = binding::norm_from_source(*weights->store, source, prefix + ".input_layernorm", 512); + block.norm2 = binding::norm_from_source(*weights->store, source, prefix + ".post_attention_layernorm", 512); + block.scale1 = binding::layer_scale_from_named_source(*weights->store, source, prefix + ".self_attn_layer_scale.scale"); + block.scale2 = binding::layer_scale_from_named_source(*weights->store, source, prefix + ".mlp_layer_scale.scale"); + weights->transformer_layers.push_back(std::move(block)); + } + + weights->downsample = binding::conv1d_from_source( + *weights->store, + source, + "codec_model.encoder.downsample.conv", + conv_weight_storage_type, + kDownsampleConvConfig.out_channels, + kDownsampleConvConfig.in_channels, + kDownsampleConvConfig.kernel_size, + kDownsampleConvConfig.use_bias); + weights->semantic_projection = binding::conv1d_from_source( + *weights->store, + source, + "codec_model.encoder.quantizer.semantic_residual_vector_quantizer.input_proj", + conv_weight_storage_type, + kSemanticProjectionConfig.out_channels, + kSemanticProjectionConfig.in_channels, + kSemanticProjectionConfig.kernel_size, + kSemanticProjectionConfig.use_bias); + weights->acoustic_projection = binding::conv1d_from_source( + *weights->store, + source, + "codec_model.encoder.quantizer.acoustic_residual_vector_quantizer.input_proj", + conv_weight_storage_type, + kAcousticProjectionConfig.out_channels, + kAcousticProjectionConfig.in_channels, + kAcousticProjectionConfig.kernel_size, + kAcousticProjectionConfig.use_bias); + + weights->semantic_codebooks.push_back(codebook_embedding( + source, + "codec_model.encoder.quantizer.semantic_residual_vector_quantizer.layers.0.codebook.")); + + for (int layer = 0; layer < 31; ++layer) { + const std::string prefix = + "codec_model.encoder.quantizer.acoustic_residual_vector_quantizer.layers." + std::to_string(layer) + ".codebook."; + weights->acoustic_codebooks.push_back(codebook_embedding(source, prefix)); + } + + weights->store->upload(); + return weights; +} + +// Conv stack runs on fixed-size chunks (plus left overlap), so its graph +// memory is constant regardless of reference length. Chunk outputs stitch +// exactly: chunk lengths are multiples of kTransformerStride, so no per-stage +// right padding occurs, and discarded overlap frames absorb the zero left +// pads that represent audio start in the first chunk. +class BreezeSpeechEncoderConvGraph { +public: + BreezeSpeechEncoderConvGraph( + std::shared_ptr weights, + core::ExecutionContext & execution_context, + core::ConstantTensorCache & constants, + size_t graph_arena_bytes) + : weights_(std::move(weights)), + backend_(execution_context.backend()), + compute_threads_(std::max(1, execution_context.config().threads)) { + if (weights_ == nullptr) { + throw std::runtime_error("Breeze speech encoder conv graph requires weights"); + } + if (backend_ == nullptr) { + throw std::runtime_error("Breeze speech encoder backend is not initialized"); + } + + ggml_init_params params{ + /*.mem_size =*/ graph_arena_bytes, + /*.mem_buffer =*/ nullptr, + /*.no_alloc =*/ true, + }; + ctx_.reset(ggml_init(params)); + if (ctx_ == nullptr) { + throw std::runtime_error("failed to initialize Breeze speech encoder conv ggml context"); + } + + core::ModuleBuildContext build_ctx{ + ctx_.get(), + "breeze_tts.speech_encoder.conv", + execution_context.backend_type(), + }; + auto x = core::make_tensor(build_ctx, GGML_TYPE_F32, core::TensorShape::from_dims({1, 1, kChunkCapacity})); + input_ = x.tensor; + + constants.begin_graph(); + x = speech_conv(build_ctx, x, weights_->encoder_convs[0], kEncoderConvConfigs[0], kEncoderConvPadModes[0]); + for (size_t i = 0; i < weights_->residual_blocks.size(); ++i) { + x = speech_residual_block(build_ctx, x, weights_->residual_blocks[i], constants); + x = modules::EluModule{}.build(build_ctx, x); + x = speech_conv(build_ctx, x, weights_->encoder_convs[i + 1], kEncoderConvConfigs[i + 1], kEncoderConvPadModes[i + 1]); + } + x = modules::EluModule{}.build(build_ctx, x); + x = speech_conv(build_ctx, x, weights_->encoder_convs.back(), kEncoderConvConfigs.back(), kEncoderConvPadModes.back()); + auto seq = modules::TransposeModule({{0, 2, 1, 3}, x.shape.rank}).build(build_ctx, x); + seq = core::ensure_backend_addressable_layout(build_ctx, seq); + output_ = seq.tensor; + ggml_set_output(output_); + graph_ = ggml_new_graph_custom(ctx_.get(), 32768, false); + ggml_build_forward_expand(graph_, output_); + constants.finish_graph(); + constants.ensure_uploaded(); + + gallocr_ = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend_)); + if (gallocr_ == nullptr || !ggml_gallocr_alloc_graph(gallocr_, graph_)) { + throw std::runtime_error("failed to allocate Breeze speech encoder conv graph"); + } + } + + ~BreezeSpeechEncoderConvGraph() { + engine::core::release_backend_graph_resources(backend_, graph_); + if (gallocr_ != nullptr) { + ggml_gallocr_free(gallocr_); + } + } + + bool matches(const BreezeSpeechEncoderWeights & weights, ggml_backend_t backend, int threads) const { + return weights_.get() == &weights && backend_ == backend && compute_threads_ == std::max(1, threads); + } + + std::vector run(const std::vector & chunk_input) { + if (static_cast(chunk_input.size()) != kChunkCapacity) { + throw std::runtime_error("Breeze speech encoder conv chunk size mismatch"); + } + ggml_backend_tensor_set(input_, chunk_input.data(), 0, chunk_input.size() * sizeof(float)); + core::set_backend_threads(backend_, compute_threads_); + const ggml_status status = engine::core::compute_backend_graph(backend_, graph_); + ggml_backend_synchronize(backend_); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Breeze speech encoder conv graph compute failed"); + } + std::vector features(static_cast(kHiddenSize * kChunkFrames)); + ggml_backend_tensor_get(output_, features.data(), 0, features.size() * sizeof(float)); + return features; + } + +private: + std::shared_ptr weights_; + std::unique_ptr ctx_; + ggml_tensor * input_ = nullptr; + ggml_tensor * output_ = nullptr; + ggml_cgraph * graph_ = nullptr; + ggml_backend_t backend_ = nullptr; + int compute_threads_ = 1; + ggml_gallocr_t gallocr_ = nullptr; +}; + +// Transformer + downsample + projections run once over the full frame +// sequence; at frame scale (960x downsampled) this graph is a few tens of MiB +// even for minute-long references. Attention is causal, so frames computed +// from right-padded tail regions never affect earlier frames. +class BreezeSpeechEncoderTransformerGraph { +public: + BreezeSpeechEncoderTransformerGraph( + std::shared_ptr weights, + int64_t frames, + core::ExecutionContext & execution_context, + core::ConstantTensorCache & constants, + size_t graph_arena_bytes, + bool allow_flash_attention = true) + : weights_(std::move(weights)), + allow_flash_attention_(allow_flash_attention), + frames_(frames), + backend_(execution_context.backend()), + compute_threads_(std::max(1, execution_context.config().threads)) { + if (weights_ == nullptr) { + throw std::runtime_error("Breeze speech encoder transformer graph requires weights"); + } + if (frames_ <= 0) { + throw std::runtime_error("Breeze speech encoder transformer graph requires positive frame count"); + } + if (backend_ == nullptr) { + throw std::runtime_error("Breeze speech encoder backend is not initialized"); + } + + ggml_init_params params{ + /*.mem_size =*/ graph_arena_bytes, + /*.mem_buffer =*/ nullptr, + /*.no_alloc =*/ true, + }; + ctx_.reset(ggml_init(params)); + if (ctx_ == nullptr) { + throw std::runtime_error("failed to initialize Breeze speech encoder transformer ggml context"); + } + + core::ModuleBuildContext build_ctx{ + ctx_.get(), + "breeze_tts.speech_encoder.transformer", + execution_context.backend_type(), + }; + auto seq = core::make_tensor( + build_ctx, + GGML_TYPE_F32, + core::TensorShape::from_dims({1, frames_, kHiddenSize})); + input_ = seq.tensor; + + constants.begin_graph(); + positions_ = ggml_new_tensor_1d(ctx_.get(), GGML_TYPE_I32, frames_); + auto positions_value = core::wrap_tensor(positions_, core::TensorShape::from_dims({frames_}), GGML_TYPE_I32); + attention_mask_ = ggml_new_tensor_4d(ctx_.get(), GGML_TYPE_F16, frames_, frames_, 1, 1); + const auto attention_mask = core::wrap_tensor( + attention_mask_, + core::TensorShape::from_dims({1, 1, frames_, frames_}), + GGML_TYPE_F16); + for (const auto & layer : weights_->transformer_layers) { + seq = transformer_block( + build_ctx, + seq, + positions_value, + layer, + attention_mask, + allow_flash_attention_ ? modules::ScaledDotProductAttentionLowering::Flash + : modules::ScaledDotProductAttentionLowering::Explicit); + } + auto x = modules::TransposeModule({{0, 2, 1, 3}, seq.shape.rank}).build(build_ctx, seq); + x = core::ensure_backend_addressable_layout(build_ctx, x); + x = speech_conv(build_ctx, x, weights_->downsample, kDownsampleConvConfig, modules::StreamingPadMode::Replicate); + auto semantic = speech_conv(build_ctx, x, weights_->semantic_projection, kSemanticProjectionConfig, modules::StreamingPadMode::Constant); + auto acoustic = speech_conv(build_ctx, x, weights_->acoustic_projection, kAcousticProjectionConfig, modules::StreamingPadMode::Constant); + output_frames_ = semantic.shape.dims[2]; + semantic_output_ = semantic.tensor; + acoustic_output_ = acoustic.tensor; + ggml_set_output(semantic_output_); + ggml_set_output(acoustic_output_); + graph_ = ggml_new_graph_custom(ctx_.get(), 32768, false); + ggml_build_forward_expand(graph_, semantic_output_); + ggml_build_forward_expand(graph_, acoustic_output_); + constants.finish_graph(); + constants.ensure_uploaded(); + + gallocr_ = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend_)); + if (gallocr_ == nullptr || !ggml_gallocr_alloc_graph(gallocr_, graph_)) { + throw std::runtime_error("failed to allocate Breeze speech encoder transformer graph"); + } + positions_data_.resize(static_cast(frames_)); + for (int64_t i = 0; i < frames_; ++i) { + positions_data_[static_cast(i)] = static_cast(i); + } + if (attention_mask_ != nullptr) { + auto mask = modules::qwen_causal_prefill_mask_values(1, frames_); + attention_mask_data_ = std::move(mask); + } + upload_static_inputs(); + } + + ~BreezeSpeechEncoderTransformerGraph() { + engine::core::release_backend_graph_resources(backend_, graph_); + if (gallocr_ != nullptr) { + ggml_gallocr_free(gallocr_); + } + } + + bool matches(const BreezeSpeechEncoderWeights & weights, int64_t frames, ggml_backend_t backend, int threads) const { + return weights_.get() == &weights && frames_ == frames && backend_ == backend && + compute_threads_ == std::max(1, threads); + } + + BreezeSpeechEncoderOutput run(const std::vector & features) { + if (static_cast(features.size()) != kHiddenSize * frames_) { + throw std::runtime_error("Breeze speech encoder transformer input size mismatch"); + } + upload_static_inputs(); + ggml_backend_tensor_set(input_, features.data(), 0, features.size() * sizeof(float)); + core::set_backend_threads(backend_, compute_threads_); + const ggml_status status = engine::core::compute_backend_graph(backend_, graph_); + ggml_backend_synchronize(backend_); + if (status != GGML_STATUS_SUCCESS) { + throw std::runtime_error("Breeze speech encoder transformer graph compute failed"); + } + BreezeSpeechEncoderOutput out; + out.semantic_projected.resize(static_cast(kQuantizerDim * output_frames_)); + out.acoustic_projected.resize(static_cast(kQuantizerDim * output_frames_)); + ggml_backend_tensor_get(semantic_output_, out.semantic_projected.data(), 0, out.semantic_projected.size() * sizeof(float)); + ggml_backend_tensor_get(acoustic_output_, out.acoustic_projected.data(), 0, out.acoustic_projected.size() * sizeof(float)); + return out; + } + + int64_t output_frames() const noexcept { + return output_frames_; + } + +private: + void upload_static_inputs() { + ggml_backend_tensor_set( + positions_, + positions_data_.data(), + 0, + positions_data_.size() * sizeof(int32_t)); + if (attention_mask_ != nullptr) { + ggml_backend_tensor_set( + attention_mask_, + attention_mask_data_.data(), + 0, + attention_mask_data_.size() * sizeof(ggml_fp16_t)); + } + } + + std::shared_ptr weights_; + bool allow_flash_attention_ = true; + int64_t frames_ = 0; + int64_t output_frames_ = 0; + std::unique_ptr ctx_; + ggml_tensor * input_ = nullptr; + ggml_tensor * positions_ = nullptr; + ggml_tensor * attention_mask_ = nullptr; + ggml_tensor * semantic_output_ = nullptr; + ggml_tensor * acoustic_output_ = nullptr; + std::vector positions_data_; + std::vector attention_mask_data_; + ggml_cgraph * graph_ = nullptr; + ggml_backend_t backend_ = nullptr; + int compute_threads_ = 1; + ggml_gallocr_t gallocr_ = nullptr; +}; + +BreezeSpeechEncoderRuntime::BreezeSpeechEncoderRuntime( + std::shared_ptr assets, + core::ExecutionContext & execution_context, + size_t graph_arena_bytes, + assets::TensorStorageType linear_weight_storage_type, + assets::TensorStorageType conv_weight_storage_type, + core::AttentionPreference attention_preference) + : assets_(std::move(assets)), + execution_context_(&execution_context), + graph_arena_bytes_(graph_arena_bytes) { + if (assets_ == nullptr) { + throw std::runtime_error("Breeze speech encoder requires assets"); + } + weights_ = load_weights( + *assets_, + execution_context_->backend(), + execution_context_->backend_type(), + linear_weight_storage_type, + conv_weight_storage_type); + // Mimi encoder self-attention head dim (kHeadDim in mimi_self_attention). + allow_flash_attention_ = core::resolve_flash_attention(execution_context_->backend(), 64, attention_preference); + engine::debug::trace_log_scalar("breeze_tts.attention.allow_encoder_flash", allow_flash_attention_); + constants_ = std::make_unique( + execution_context_->backend(), + std::max(1, execution_context_->config().threads), + "breeze_tts.speech_encoder.constants", + 768ull * 1024ull * 1024ull); +} + +BreezeSpeechEncoderRuntime::~BreezeSpeechEncoderRuntime() = default; + +BreezeSpeechCodes BreezeSpeechEncoderRuntime::encode(const runtime::AudioBuffer & audio) const { + const auto start = Clock::now(); + if (execution_context_ == nullptr) { + throw std::runtime_error("Breeze speech encoder execution context is missing"); + } + if (audio.sample_rate <= 0 || audio.channels <= 0 || audio.samples.empty()) { + throw std::runtime_error("Breeze speech encoder requires non-empty reference audio"); + } + const auto waveform = engine::audio::convert_interleaved_audio_to_mono_linear_resampled( + audio.samples, + audio.sample_rate, + audio.channels, + static_cast(kSampleRate)); + const int64_t valid_samples = static_cast(waveform.size()); + const int64_t frames = std::max(1, (valid_samples + kDownsampleRate - 1) / kDownsampleRate); + const int64_t transformer_frames = (valid_samples + kTransformerStride - 1) / kTransformerStride; + const int64_t graph_frames = + (transformer_frames + kTransformerFrameBucket - 1) / kTransformerFrameBucket * kTransformerFrameBucket; + const int threads = std::max(1, execution_context_->config().threads); + if (conv_graph_ == nullptr || !conv_graph_->matches(*weights_, execution_context_->backend(), threads)) { + conv_graph_.reset(); + conv_graph_ = std::make_unique( + weights_, + *execution_context_, + *constants_, + graph_arena_bytes_); + } + if (transformer_graph_ == nullptr || + !transformer_graph_->matches(*weights_, graph_frames, execution_context_->backend(), threads)) { + transformer_graph_.reset(); + transformer_graph_ = std::make_unique( + weights_, + graph_frames, + *execution_context_, + *constants_, + graph_arena_bytes_, + allow_flash_attention_); + } + + std::vector features(static_cast(kHiddenSize * graph_frames)); + std::vector chunk_input(static_cast(kChunkCapacity)); + int64_t dst_frame = 0; + for (int64_t pos = 0; pos < valid_samples; pos += kChunkSamples) { + const int64_t overlap = pos > 0 ? kChunkOverlapSamples : 0; + const int64_t fresh = std::min(kChunkSamples, valid_samples - pos); + std::fill(chunk_input.begin(), chunk_input.end(), 0.0F); + std::copy( + waveform.begin() + (pos - overlap), + waveform.begin() + (pos + fresh), + chunk_input.begin()); + const auto chunk_features = conv_graph_->run(chunk_input); + const int64_t skip_frames = overlap / kTransformerStride; + const int64_t keep_frames = (fresh + kTransformerStride - 1) / kTransformerStride; + std::copy_n( + chunk_features.begin() + skip_frames * kHiddenSize, + keep_frames * kHiddenSize, + features.begin() + dst_frame * kHiddenSize); + dst_frame += keep_frames; + } + // Pad unused bucket frames with the last real frame (not zeros): causal + // attention keeps padding invisible to real frames, and the downsample + // conv's Replicate right pad then sees the same value as an exact-length + // graph would produce. + for (int64_t f = transformer_frames; f < graph_frames; ++f) { + std::copy_n( + features.begin() + (transformer_frames - 1) * kHiddenSize, + kHiddenSize, + features.begin() + f * kHiddenSize); + } + + auto out = transformer_graph_->run(features); + const int64_t produced_frames = transformer_graph_->output_frames(); + if (produced_frames != frames) { + // Projected outputs are channel-major with stride produced_frames; + // drop the padding frames before quantization. + auto slice_frames = [frames, produced_frames](std::vector & projected) { + std::vector sliced(static_cast(kQuantizerDim * frames)); + for (int64_t dim = 0; dim < kQuantizerDim; ++dim) { + std::copy_n( + projected.begin() + dim * produced_frames, + frames, + sliced.begin() + dim * frames); + } + projected = std::move(sliced); + }; + slice_frames(out.semantic_projected); + slice_frames(out.acoustic_projected); + } + out.codes.frames = frames; + out.codes.code_groups = kValidQuantizers; + out.codes.codes = quantize_projected(out.semantic_projected, out.acoustic_projected, frames, *weights_); + debug::timing_log_scalar("breeze_tts.speech_encoder.total_ms", engine::debug::elapsed_ms(start)); + return out.codes; +} + +void BreezeSpeechEncoderRuntime::release_runtime_graphs() const { + conv_graph_.reset(); + transformer_graph_.reset(); +} + +} // namespace engine::models::breeze_tts diff --git a/src/models/breeze_tts/text_encoder.cpp b/src/models/breeze_tts/text_encoder.cpp new file mode 100644 index 000000000..7fafa9cf3 --- /dev/null +++ b/src/models/breeze_tts/text_encoder.cpp @@ -0,0 +1,282 @@ +#include "engine/models/breeze_tts/text_encoder.h" + +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/text_encoders/t5_gemma_encoder.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include +#include +#include + +namespace engine::models::breeze_tts { +namespace { + +namespace binding = engine::modules::binding; +namespace core = engine::core; +namespace modules = engine::modules; +using Clock = std::chrono::steady_clock; + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +modules::T5GemmaEncoderConfig text_config(const BreezeTTSConfig & config) { + modules::T5GemmaEncoderConfig out; + out.hidden_size = config.text_hidden_size; + out.layers = config.text_layers; + out.attention_heads = config.text_heads; + out.kv_heads = config.text_kv_heads; + out.head_dim = config.text_head_dim; + out.attention_size = config.text_heads * config.text_head_dim; + out.intermediate_size = config.text_intermediate_size; + out.vocab_size = config.text_vocab_size; + out.rope_theta = config.text_rope_theta; + out.rope_freq_scale = 1.0F / config.text_rope_linear_factor; + out.rms_norm_eps = config.text_rms_norm_eps; + out.query_pre_attn_scalar = config.text_query_pre_attn_scalar; + out.attn_logit_softcap = 0.0F; + out.scale_embeddings = true; + out.use_qk_norm = true; + out.rms_norm_style = modules::T5GemmaRMSNormStyle::Gemma; + out.layer_rope_theta = config.text_layer_rope_theta; + out.layer_rope_freq_scale = config.text_layer_rope_freq_scale; + return out; +} + +modules::T5GemmaEncoderLayerWeights load_text_layer( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const BreezeTTSConfig & config, + assets::TensorStorageType storage_type, + int64_t layer) { + const std::string prefix = "text_encoder.layers." + std::to_string(layer); + modules::T5GemmaEncoderLayerWeights out; + out.pre_self_attn_norm = store.load_f32_tensor(source, prefix + ".pre_self_attn_layernorm.weight", {config.text_hidden_size}); + out.post_self_attn_norm = store.load_f32_tensor(source, prefix + ".post_self_attn_layernorm.weight", {config.text_hidden_size}); + out.pre_ff_norm = store.load_f32_tensor(source, prefix + ".pre_feedforward_layernorm.weight", {config.text_hidden_size}); + out.post_ff_norm = store.load_f32_tensor(source, prefix + ".post_feedforward_layernorm.weight", {config.text_hidden_size}); + out.q_proj = binding::linear_from_source( + store, source, prefix + ".self_attn.q_proj", storage_type, config.text_heads * config.text_head_dim, config.text_hidden_size, false); + out.k_proj = binding::linear_from_source( + store, source, prefix + ".self_attn.k_proj", storage_type, config.text_kv_heads * config.text_head_dim, config.text_hidden_size, false); + out.v_proj = binding::linear_from_source( + store, source, prefix + ".self_attn.v_proj", storage_type, config.text_kv_heads * config.text_head_dim, config.text_hidden_size, false); + out.o_proj = binding::linear_from_source( + store, source, prefix + ".self_attn.o_proj", storage_type, config.text_hidden_size, config.text_heads * config.text_head_dim, false); + out.q_norm = binding::norm_weight_from_source(store, source, prefix + ".self_attn.q_norm", config.text_head_dim); + out.k_norm = binding::norm_weight_from_source(store, source, prefix + ".self_attn.k_norm", config.text_head_dim); + out.gate_proj = binding::linear_from_source( + store, source, prefix + ".mlp.gate_proj", storage_type, config.text_intermediate_size, config.text_hidden_size, false); + out.up_proj = binding::linear_from_source( + store, source, prefix + ".mlp.up_proj", storage_type, config.text_intermediate_size, config.text_hidden_size, false); + out.down_proj = binding::linear_from_source( + store, source, prefix + ".mlp.down_proj", storage_type, config.text_hidden_size, config.text_intermediate_size, false); + return out; +} + +struct BreezeTextWeights { + std::shared_ptr store; + modules::T5GemmaEncoderWeights encoder; + modules::LinearWeights projector; +}; + +std::shared_ptr load_text_weights( + const BreezeTTSAssets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + assets::TensorStorageType storage_type) { + auto out = std::make_shared(); + out->store = std::make_shared( + execution.backend(), + execution.backend_type(), + "breeze_tts.text_encoder.weights", + weight_context_bytes); + const auto & source = *assets.weights; + const auto & config = assets.config; + out->encoder.embed_tokens = out->store->load_tensor( + source, + "text_encoder.embed_tokens.weight", + storage_type, + {config.text_vocab_size, config.text_hidden_size}); + out->encoder.layers.reserve(static_cast(config.text_layers)); + for (int64_t layer = 0; layer < config.text_layers; ++layer) { + out->encoder.layers.push_back(load_text_layer(*out->store, source, config, storage_type, layer)); + } + out->encoder.norm = out->store->load_f32_tensor(source, "text_encoder.norm.weight", {config.text_hidden_size}); + out->projector = binding::linear_from_source( + *out->store, + source, + "text_encoder_proj", + storage_type, + config.hidden_size, + config.text_hidden_size, + false); + out->store->upload(); + return out; +} + +std::vector full_attention_mask(int64_t heads, int64_t tokens) { + return std::vector(static_cast(heads * tokens * tokens), 0.0F); +} + +} // namespace + +struct BreezeTextEncoderRuntime::Impl { + struct Graph { + Graph( + core::ExecutionContext & execution, + size_t graph_arena_bytes, + const BreezeTTSConfig & config, + std::shared_ptr weights, + int64_t tokens) + : execution(execution), + config(config), + weights(std::move(weights)), + tokens(tokens) { + ctx.reset(ggml_init({graph_arena_bytes, nullptr, true})); + if (ctx == nullptr) { + throw std::runtime_error("failed to initialize BreezeTTS text encoder graph context"); + } + core::ModuleBuildContext build{ctx.get(), "breeze_tts.text_encoder", execution.backend_type()}; + input_ids_value = core::make_tensor(build, GGML_TYPE_I32, core::TensorShape::from_dims({1, tokens})); + positions_value = core::make_tensor(build, GGML_TYPE_I32, core::TensorShape::from_dims({tokens})); + attention_value = core::make_tensor(build, GGML_TYPE_F32, core::TensorShape::from_dims({1, config.text_heads, tokens, tokens})); + input_ids = input_ids_value.tensor; + positions = positions_value.tensor; + attention = attention_value.tensor; + auto encoded = modules::T5GemmaEncoderModule(text_config(config)).build( + build, + input_ids_value, + positions_value, + attention_value, + this->weights->encoder); + auto projected = modules::LinearModule({config.text_hidden_size, config.hidden_size, false, GGML_PREC_DEFAULT}).build( + build, + encoded, + this->weights->projector); + output = core::ensure_backend_addressable_layout(build, projected).tensor; + ggml_set_input(input_ids); + ggml_set_input(positions); + ggml_set_input(attention); + ggml_set_output(output); + graph = ggml_new_graph_custom(ctx.get(), 65536, false); + ggml_build_forward_expand(graph, output); + if (core::is_host_backend(execution.backend())) { + params_buffer = ggml_backend_alloc_ctx_tensors(ctx.get(), execution.backend()); + } + galloc = ggml_gallocr_new(ggml_backend_get_default_buffer_type(execution.backend())); + if (galloc == nullptr || !ggml_gallocr_reserve(galloc, graph) || !ggml_gallocr_alloc_graph(galloc, graph)) { + throw std::runtime_error("failed to allocate BreezeTTS text encoder graph"); + } + } + + ~Graph() { + engine::core::release_backend_graph_resources(execution.backend(), graph); + if (galloc != nullptr) { + ggml_gallocr_free(galloc); + } + if (params_buffer != nullptr) { + ggml_backend_buffer_free(params_buffer); + } + } + + BreezeProjectedText run(const std::vector & ids) { + if (static_cast(ids.size()) != tokens) { + throw std::runtime_error("BreezeTTS text encoder graph token count mismatch"); + } + std::vector pos(static_cast(tokens)); + for (int64_t i = 0; i < tokens; ++i) { + pos[static_cast(i)] = static_cast(i); + } + const auto mask = full_attention_mask(config.text_heads, tokens); + core::write_tensor_i32(input_ids_value, ids); + core::write_tensor_i32(positions_value, pos); + core::write_tensor_f32(attention_value, mask); + core::set_backend_threads(execution.backend(), execution.config().threads); + if (core::compute_backend_graph(execution.backend(), graph, nullptr, "breeze_tts.text_encoder") != GGML_STATUS_SUCCESS) { + throw std::runtime_error("BreezeTTS text encoder graph compute failed"); + } + return {tokens, core::read_tensor_f32(output)}; + } + + core::ExecutionContext & execution; + BreezeTTSConfig config; + std::shared_ptr weights; + int64_t tokens = 0; + std::unique_ptr ctx; + core::TensorValue input_ids_value; + core::TensorValue positions_value; + core::TensorValue attention_value; + ggml_tensor * input_ids = nullptr; + ggml_tensor * positions = nullptr; + ggml_tensor * attention = nullptr; + ggml_tensor * output = nullptr; + ggml_cgraph * graph = nullptr; + ggml_gallocr_t galloc = nullptr; + ggml_backend_buffer_t params_buffer = nullptr; + }; + + Impl( + std::shared_ptr assets, + core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + assets::TensorStorageType storage_type) + : assets(std::move(assets)), + execution(execution), + graph_arena_bytes(graph_arena_bytes), + weights(load_text_weights(*this->assets, execution, weight_context_bytes, storage_type)) {} + + BreezeProjectedText encode(const std::vector & input_ids) { + const auto start = Clock::now(); + const int64_t tokens = static_cast(input_ids.size()); + if (tokens <= 0) { + throw std::runtime_error("BreezeTTS text encoder requires non-empty input"); + } + if (graph == nullptr || graph->tokens != tokens) { + graph = std::make_unique(execution, graph_arena_bytes, assets->config, weights, tokens); + } + auto out = graph->run(input_ids); + engine::debug::timing_log_scalar("breeze_tts.text_encoder.total_ms", engine::debug::elapsed_ms(start)); + return out; + } + + void release_runtime_graphs() { + graph.reset(); + } + + std::shared_ptr assets; + core::ExecutionContext & execution; + size_t graph_arena_bytes = 0; + std::shared_ptr weights; + std::unique_ptr graph; +}; + +BreezeTextEncoderRuntime::BreezeTextEncoderRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type) + : impl_(std::make_unique(std::move(assets), execution, graph_arena_bytes, weight_context_bytes, storage_type)) {} + +BreezeTextEncoderRuntime::~BreezeTextEncoderRuntime() = default; + +BreezeProjectedText BreezeTextEncoderRuntime::encode(const std::vector & input_ids) { + return impl_->encode(input_ids); +} + +void BreezeTextEncoderRuntime::release_runtime_graphs() { + impl_->release_runtime_graphs(); +} + +} // namespace engine::models::breeze_tts diff --git a/src/models/breeze_tts/tokenizer_text.cpp b/src/models/breeze_tts/tokenizer_text.cpp new file mode 100644 index 000000000..ac323cbda --- /dev/null +++ b/src/models/breeze_tts/tokenizer_text.cpp @@ -0,0 +1,136 @@ +#include "engine/models/breeze_tts/tokenizer_text.h" + +#include "engine/framework/tokenizers/llama_bpe.h" + +#include +#include + +namespace engine::models::breeze_tts { +namespace { + +constexpr const char * kDefaultInstruction = "Speak clearly and naturally."; +constexpr const char * kBos = ""; +constexpr const char * kSpeaker0 = "[S0]"; +constexpr const char * kInstructionBos = ""; +constexpr const char * kInstructionEos = ""; + +std::vector breeze_special_tokens(const BreezeTTSConfig & config) { + return { + {kSpeaker0, 262146}, + {"[S1]", 262147}, + {"[S2]", 262148}, + {"[S3]", 262149}, + {"[S4]", 262150}, + {"[S5]", 262151}, + {"[S6]", 262152}, + {"[S7]", 262153}, + {"[S8]", 262154}, + {"[S9]", 262155}, + {kInstructionBos, 262156}, + {kInstructionEos, 262157}, + {"<|AUDIO|>", static_cast(config.audio_token_id)}, + {"<|audio_eos|>", static_cast(config.audio_eos_token_id)}, + }; +} + +void append_tokens(BreezePromptBranch & out, const std::vector & ids, bool text) { + out.input_ids.insert(out.input_ids.end(), ids.begin(), ids.end()); + out.text_mask.insert(out.text_mask.end(), ids.size(), text ? uint8_t{1} : uint8_t{0}); + if (text) { + out.text_segment_lengths.push_back(static_cast(ids.size())); + out.text_segments.push_back(ids); + } +} + +void append_audio_placeholders(BreezePromptBranch & out, int32_t audio_token, int32_t audio_eos, int64_t frames) { + if (frames <= 0) { + throw std::runtime_error("BreezeTTS clone requires encoded reference audio frames"); + } + out.input_ids.insert(out.input_ids.end(), static_cast(frames), audio_token); + out.text_mask.insert(out.text_mask.end(), static_cast(frames), uint8_t{0}); + out.input_ids.push_back(audio_eos); + out.text_mask.push_back(uint8_t{0}); +} + +} // namespace + +struct BreezeTextTokenizer::Impl { + Impl(std::shared_ptr assets) + : assets(std::move(assets)), + tokenizer(engine::tokenizers::LlamaBpeTokenizerSpec{ + {}, + {}, + this->assets->resources.require_file("tokenizer_config_json"), + this->assets->resources.require_file("tokenizer_json"), + engine::tokenizers::LlamaBpePreTokenizer::Gemma4, + breeze_special_tokens(this->assets->config), + "\xE2\x96\x81"}) { + audio_token = static_cast(this->assets->config.audio_token_id); + audio_eos = static_cast(this->assets->config.audio_eos_token_id); + } + + std::vector encode_text_segment(const std::string & text) const { + return tokenizer.encode(std::string(kBos) + text, true); + } + + std::shared_ptr assets; + engine::tokenizers::LlamaBpeTokenizer tokenizer; + int32_t audio_token = 0; + int32_t audio_eos = 0; +}; + +BreezeTextTokenizer::BreezeTextTokenizer(std::shared_ptr assets) + : impl_(std::make_unique(std::move(assets))) {} + +BreezeTextTokenizer::~BreezeTextTokenizer() = default; + +BreezePromptBranch BreezeTextTokenizer::build_tts_instruction( + const std::string & text, + const std::string & instruction) const { + const std::string actual_instruction = instruction.empty() ? kDefaultInstruction : instruction; + BreezePromptBranch out; + append_tokens(out, impl_->encode_text_segment( + std::string(kSpeaker0) + kInstructionBos + actual_instruction + kInstructionEos + text), true); + return out; +} + +BreezePromptBranch BreezeTextTokenizer::build_tts_plain(const std::string & text) const { + BreezePromptBranch out; + append_tokens(out, impl_->encode_text_segment(std::string(kSpeaker0) + text), true); + return out; +} + +BreezePromptBranch BreezeTextTokenizer::build_clone( + const std::string & text, + const std::string & instruction, + const std::string & reference_text, + int64_t reference_audio_frames) const { + const std::string actual_instruction = instruction.empty() ? kDefaultInstruction : instruction; + BreezePromptBranch out; + append_tokens(out, impl_->encode_text_segment(std::string(kSpeaker0) + reference_text), true); + append_audio_placeholders(out, impl_->audio_token, impl_->audio_eos, reference_audio_frames); + append_tokens(out, impl_->encode_text_segment( + std::string(kSpeaker0) + kInstructionBos + actual_instruction + kInstructionEos + text), true); + return out; +} + +BreezePromptBranch BreezeTextTokenizer::build_clone_negative( + const std::string & text, + const std::string & reference_text, + int64_t reference_audio_frames) const { + BreezePromptBranch out; + append_tokens(out, impl_->encode_text_segment(std::string(kSpeaker0) + reference_text), true); + append_audio_placeholders(out, impl_->audio_token, impl_->audio_eos, reference_audio_frames); + append_tokens(out, impl_->encode_text_segment(std::string(kSpeaker0) + text), true); + return out; +} + +int32_t BreezeTextTokenizer::audio_token_id() const noexcept { + return impl_->audio_token; +} + +int32_t BreezeTextTokenizer::audio_eos_token_id() const noexcept { + return impl_->audio_eos; +} + +} // namespace engine::models::breeze_tts diff --git a/src/models/chatterbox/text_tokenizer.cpp b/src/models/chatterbox/text_tokenizer.cpp index d33d755e1..25a39ee1e 100644 --- a/src/models/chatterbox/text_tokenizer.cpp +++ b/src/models/chatterbox/text_tokenizer.cpp @@ -1,6 +1,7 @@ #include "engine/models/chatterbox/text_tokenizer.h" #include "engine/framework/io/json.h" +#include "engine/framework/text/unicode_normalization.h" #include "unicode.h" #include @@ -190,12 +191,12 @@ std::string lower_ascii(std::string text) { return text; } -std::string lower_and_normalize_nfd(const std::string & text) { +std::string lower_and_normalize_nfkd(const std::string & text) { auto codepoints = unicode_cpts_from_utf8(text); for (uint32_t & codepoint : codepoints) { codepoint = unicode_tolower(codepoint); } - codepoints = unicode_cpts_normalize_nfd(codepoints); + codepoints = engine::text::normalize_nfkd_codepoints(codepoints); std::string out; for (const uint32_t codepoint : codepoints) { out += unicode_cpt_to_utf8(codepoint); @@ -431,7 +432,7 @@ std::vector encode_chatterbox_multilingual_text( const std::string & language) { const auto & tokenizer = tokenizer_base; const std::string normalized_language = normalize_chatterbox_language_code(language); - std::string prepared = lower_and_normalize_nfd(text); + std::string prepared = lower_and_normalize_nfkd(text); if (normalized_language == "ko") { prepared = decompose_korean_hangul(prepared); } diff --git a/src/models/cosyvoice3/ar.cpp b/src/models/cosyvoice3/ar.cpp new file mode 100644 index 000000000..80b3e55fb --- /dev/null +++ b/src/models/cosyvoice3/ar.cpp @@ -0,0 +1,572 @@ +#include "engine/models/cosyvoice3/ar.h" + +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/modules/transformers/qwen_causal_decode_runtime.h" +#include "engine/framework/modules/weight_binding.h" +#include "engine/framework/sampling/hf_sampler.h" +#include "engine/framework/sampling/torch_random.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::cosyvoice3 { +namespace { + +namespace binding = engine::modules::binding; +namespace core = engine::core; +namespace modules = engine::modules; + +using Clock = std::chrono::steady_clock; + +constexpr float kTopP = 0.8F; +constexpr int64_t kRasWindow = 10; +constexpr float kRasTau = 0.1F; +constexpr std::array kSilentTokens{1, 2, 28, 29, 55, 248, 494, 2241, 2242, 2322, 2323}; +constexpr int64_t kMaxConsecutiveSilentTokens = 5; + +modules::QwenCausalDecodeRuntimeConfig make_qwen_config( + const CosyVoice3Config & config, + core::BackendType backend_type, + size_t graph_arena_bytes) { + modules::QwenCausalDecodeRuntimeConfig out; + out.trace_name = "cosyvoice3.ar"; + out.prefill_graph_arena_bytes = graph_arena_bytes; + out.decode_graph_arena_bytes = graph_arena_bytes; + out.decoder.stack.hidden_size = config.hidden_size; + out.decoder.stack.num_attention_heads = config.heads; + out.decoder.stack.num_key_value_heads = config.kv_heads; + out.decoder.stack.head_dim = config.head_dim; + out.decoder.stack.intermediate_size = config.intermediate_size; + out.decoder.stack.layers = config.layers; + out.decoder.stack.rms_norm_eps = 1.0e-6F; + out.decoder.stack.rope_theta = 1000000.0F; + out.decoder.stack.rope_type = GGML_ROPE_TYPE_NEOX; + out.decoder.stack.use_qk_norm = false; + out.decoder.stack.attention_precision = GGML_PREC_F32; + out.decoder.stack.projection_precision = GGML_PREC_DEFAULT; + out.decoder.stack.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.decoder.stack.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.decoder.stack.runtime.static_cache.update_mode = modules::QwenDecoderStaticCacheUpdateMode::DirectSetRows; + out.decoder.stack.runtime.static_cache.set_rows_mode = modules::QwenDecoderStaticCacheSetRowsMode::BackendViewOptimized; + out.decoder.logits_size = config.speech_token_size + config.speech_reserved_tokens; + out.decoder.logits_mode = modules::QwenCausalDecoderLogitsMode::LastStep; + out.output_mode = modules::QwenCausalDecodeOutputMode::Logits; + out.logits_readback_token_ids.reserve(static_cast(out.decoder.logits_size)); + for (int32_t token = 0; token < static_cast(out.decoder.logits_size); ++token) { + out.logits_readback_token_ids.push_back(token); + } + if (backend_type == core::BackendType::Metal) { + out.decoder.lm_head_input_type = GGML_TYPE_F32; + } else if (backend_type == core::BackendType::Vulkan) { + out.decoder.lm_head_input_type = GGML_TYPE_F16; + } + return out; +} + +modules::QwenDecoderLayerWeights load_qwen_layer( + core::BackendWeightStore & store, + const assets::TensorSource & source, + const CosyVoice3Config & config, + assets::TensorStorageType storage_type, + int64_t layer) { + const std::string prefix = "llm.model.model.layers." + std::to_string(layer); + modules::QwenDecoderLayerWeights out; + out.input_norm = binding::norm_weight_from_source(store, source, prefix + ".input_layernorm", config.hidden_size); + out.self_attention.q_weight = store.load_tensor( + source, + prefix + ".self_attn.q_proj.weight", + storage_type, + {config.heads * config.head_dim, config.hidden_size}); + out.self_attention.q_bias = store.load_f32_tensor( + source, + prefix + ".self_attn.q_proj.bias", + {config.heads * config.head_dim}); + out.self_attention.k_weight = store.load_tensor( + source, + prefix + ".self_attn.k_proj.weight", + storage_type, + {config.kv_heads * config.head_dim, config.hidden_size}); + out.self_attention.k_bias = store.load_f32_tensor( + source, + prefix + ".self_attn.k_proj.bias", + {config.kv_heads * config.head_dim}); + out.self_attention.v_weight = store.load_tensor( + source, + prefix + ".self_attn.v_proj.weight", + storage_type, + {config.kv_heads * config.head_dim, config.hidden_size}); + out.self_attention.v_bias = store.load_f32_tensor( + source, + prefix + ".self_attn.v_proj.bias", + {config.kv_heads * config.head_dim}); + out.self_attention.out_weight = store.load_tensor( + source, + prefix + ".self_attn.o_proj.weight", + storage_type, + {config.hidden_size, config.heads * config.head_dim}); + out.post_norm = binding::norm_weight_from_source(store, source, prefix + ".post_attention_layernorm", config.hidden_size); + out.mlp.gate_proj = binding::linear_from_source( + store, + source, + prefix + ".mlp.gate_proj", + storage_type, + config.intermediate_size, + config.hidden_size, + false); + out.mlp.up_proj = binding::linear_from_source( + store, + source, + prefix + ".mlp.up_proj", + storage_type, + config.intermediate_size, + config.hidden_size, + false); + out.mlp.down_proj = binding::linear_from_source( + store, + source, + prefix + ".mlp.down_proj", + storage_type, + config.hidden_size, + config.intermediate_size, + false); + return out; +} + +struct CosyVoice3ArWeights { + std::shared_ptr store; + modules::QwenCausalDecodeRuntimeWeights qwen; + std::vector text_embedding; + std::vector speech_embedding; +}; + +std::shared_ptr load_ar_weights( + const CosyVoice3Assets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + assets::TensorStorageType storage_type, + const modules::QwenCausalDecodeRuntimeConfig & qwen_config) { + auto weights = std::make_shared(); + weights->store = std::make_shared( + execution.backend(), + execution.backend_type(), + "cosyvoice3.ar.weights", + weight_context_bytes); + const auto & source = *assets.llm_weights; + const auto & config = assets.config; + weights->qwen.token_embedding = weights->store->load_tensor( + source, + "llm.model.model.embed_tokens.weight", + storage_type, + {config.text_vocab_size, config.hidden_size}); + weights->qwen.stack.layers.reserve(static_cast(config.layers)); + for (int64_t layer = 0; layer < config.layers; ++layer) { + weights->qwen.stack.layers.push_back(load_qwen_layer(*weights->store, source, config, storage_type, layer)); + } + weights->qwen.final_norm = binding::norm_weight_from_source( + *weights->store, + source, + "llm.model.model.norm", + config.hidden_size); + weights->qwen.lm_head = binding::linear_from_source( + *weights->store, + source, + "llm_decoder", + storage_type, + qwen_config.decoder.logits_size, + config.hidden_size, + false); + weights->text_embedding = source.require_f32( + "llm.model.model.embed_tokens.weight", + {config.text_vocab_size, config.hidden_size}); + weights->speech_embedding = source.require_f32( + "speech_embedding.weight", + {config.speech_token_size + config.speech_reserved_tokens, config.hidden_size}); + weights->store->upload(); + assets.llm_weights->release_storage(); + return weights; +} + +void append_embedding_rows( + std::vector & out, + const std::vector & table, + int64_t rows, + int64_t dim, + const std::vector & tokens, + const char * label) { + for (const int32_t token : tokens) { + if (token < 0 || token >= rows) { + throw std::runtime_error(std::string("CosyVoice3 AR ") + label + " token is outside embedding table"); + } + const size_t begin = static_cast(token) * static_cast(dim); + out.insert(out.end(), table.begin() + static_cast(begin), table.begin() + static_cast(begin + static_cast(dim))); + } +} + +std::vector embedding_row( + const std::vector & table, + int64_t rows, + int64_t dim, + int32_t token, + const char * label) { + if (token < 0 || token >= rows) { + throw std::runtime_error(std::string("CosyVoice3 AR ") + label + " token is outside embedding table"); + } + const size_t begin = static_cast(token) * static_cast(dim); + return std::vector( + table.begin() + static_cast(begin), + table.begin() + static_cast(begin + static_cast(dim))); +} + +std::vector log_softmax(const std::vector & logits) { + float max_value = -std::numeric_limits::infinity(); + for (const float value : logits) { + if (std::isfinite(value)) { + max_value = std::max(max_value, value); + } + } + if (!std::isfinite(max_value)) { + throw std::runtime_error("CosyVoice3 AR logits have no finite value"); + } + double sum = 0.0; + for (const float value : logits) { + if (std::isfinite(value)) { + sum += std::exp(static_cast(value - max_value)); + } + } + if (!(sum > 0.0) || !std::isfinite(sum)) { + throw std::runtime_error("CosyVoice3 AR logits have invalid probability mass"); + } + const float log_sum = max_value + static_cast(std::log(sum)); + std::vector out(logits.size(), -std::numeric_limits::infinity()); + for (size_t index = 0; index < logits.size(); ++index) { + if (std::isfinite(logits[index])) { + out[index] = logits[index] - log_sum; + } + } + return out; +} + +int32_t sample_from_scores( + const std::vector & scores, + sampling::HfSamplerScratch & scratch, + std::mt19937 & fallback_rng, + const sampling::TorchCudaSamplingPolicy & policy, + uint64_t seed, + uint64_t & sample_call_index, + uint64_t & rng_offset_blocks, + std::string_view context) { + const sampling::HfTorchSamplingState torch_state{ + &policy, + seed, + sample_call_index, + rng_offset_blocks, + true, + }; + const int32_t token = sampling::HfTokenSampler::sample_from_processed_scores( + scores, + scratch, + fallback_rng, + policy.cuda_fast_path ? &torch_state : nullptr, + context); + ++sample_call_index; + if (policy.cuda_fast_path) { + rng_offset_blocks += sampling::torch_cuda_tensor_iterator_offset_blocks( + static_cast(scores.size()), + policy); + } + return token; +} + +int32_t nucleus_sample( + const std::vector & log_probs, + int64_t top_k, + sampling::HfSamplerScratch & scratch, + std::mt19937 & fallback_rng, + const sampling::TorchCudaSamplingPolicy & policy, + uint64_t seed, + uint64_t & sample_call_index, + uint64_t & rng_offset_blocks) { + std::vector order(log_probs.size()); + std::iota(order.begin(), order.end(), 0); + std::stable_sort(order.begin(), order.end(), [&](int32_t lhs, int32_t rhs) { + return log_probs[static_cast(lhs)] > log_probs[static_cast(rhs)]; + }); + + std::vector kept; + std::vector kept_scores; + kept.reserve(static_cast(std::min(top_k, static_cast(order.size())))); + kept_scores.reserve(kept.capacity()); + double cumulative = 0.0; + for (const int32_t token : order) { + const float score = log_probs[static_cast(token)]; + if (!std::isfinite(score)) { + continue; + } + if (cumulative < static_cast(kTopP) && static_cast(kept.size()) < top_k) { + cumulative += std::exp(static_cast(score)); + kept.push_back(token); + kept_scores.push_back(score); + } else { + break; + } + } + if (kept.empty()) { + throw std::runtime_error("CosyVoice3 AR nucleus sampler has no finite candidates"); + } + const int32_t local = sample_from_scores( + kept_scores, + scratch, + fallback_rng, + policy, + seed, + sample_call_index, + rng_offset_blocks, + "CosyVoice3 AR nucleus sampler"); + return kept[static_cast(local)]; +} + +int32_t ras_sample( + std::vector log_probs, + const std::vector & decoded_tokens, + int64_t top_k, + sampling::HfSamplerScratch & scratch, + std::mt19937 & fallback_rng, + const sampling::TorchCudaSamplingPolicy & policy, + uint64_t seed, + uint64_t & sample_call_index, + uint64_t & rng_offset_blocks) { + const int32_t top_id = nucleus_sample( + log_probs, + top_k, + scratch, + fallback_rng, + policy, + seed, + sample_call_index, + rng_offset_blocks); + int64_t repeat_count = 0; + const size_t window = std::min(decoded_tokens.size(), static_cast(kRasWindow)); + for (size_t index = decoded_tokens.size() - window; index < decoded_tokens.size(); ++index) { + if (decoded_tokens[index] == top_id) { + ++repeat_count; + } + } + if (static_cast(repeat_count) >= static_cast(kRasWindow) * kRasTau) { + log_probs[static_cast(top_id)] = -std::numeric_limits::infinity(); + return sample_from_scores( + log_probs, + scratch, + fallback_rng, + policy, + seed, + sample_call_index, + rng_offset_blocks, + "CosyVoice3 AR RAS fallback sampler"); + } + return top_id; +} + +bool is_silent_token(int32_t token) { + return std::find(kSilentTokens.begin(), kSilentTokens.end(), token) != kSilentTokens.end(); +} + +} // namespace + +class CosyVoice3ArRuntime::Impl { +public: + Impl( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type) + : assets_(std::move(assets)), + execution_(execution), + sampling_policy_(sampling::resolve_torch_cuda_sampling_policy( + execution.backend_type(), + execution.config().device, + "cosyvoice3.ar.sampling", + "CosyVoice3 AR", + sampling::TorchCudaSamplingPolicyFailureMode::FallbackToDefault)) { + if (assets_ == nullptr) { + throw std::runtime_error("CosyVoice3 AR runtime requires assets"); + } + qwen_config_ = make_qwen_config(assets_->config, execution_.backend_type(), graph_arena_bytes); + weights_ = load_ar_weights(*assets_, execution_, weight_context_bytes, storage_type, qwen_config_); + qwen_runtime_ = std::make_unique( + execution_, + qwen_config_, + weights_->qwen); + } + + CosyVoice3ArOutput generate(const CosyVoice3ArRequest & request) { + const auto & config = assets_->config; + if (request.target_text_tokens.empty()) { + throw std::runtime_error("CosyVoice3 AR target text tokens are empty"); + } + if (request.top_k <= 0) { + throw std::runtime_error("CosyVoice3 AR top_k must be positive"); + } + const int64_t min_len = request.min_tokens >= 0 + ? request.min_tokens + : static_cast(request.target_text_tokens.size()) * 2; + const int64_t max_len = request.max_tokens >= 0 + ? request.max_tokens + : static_cast(request.target_text_tokens.size()) * 20; + if (min_len < 0 || max_len <= 0 || min_len > max_len) { + throw std::runtime_error("CosyVoice3 AR token length bounds are invalid"); + } + + const int32_t sos = static_cast(config.speech_token_size); + const int32_t task_id = static_cast(config.speech_token_size + 2); + const int64_t speech_embedding_rows = config.speech_token_size + config.speech_reserved_tokens; + + std::vector text_tokens = request.prompt_text_tokens; + text_tokens.insert(text_tokens.end(), request.target_text_tokens.begin(), request.target_text_tokens.end()); + + const int64_t prefill_steps = + 1 + static_cast(text_tokens.size()) + 1 + static_cast(request.prompt_speech_tokens.size()); + std::vector embeddings; + embeddings.reserve(static_cast(prefill_steps * config.hidden_size)); + append_embedding_rows( + embeddings, + weights_->speech_embedding, + speech_embedding_rows, + config.hidden_size, + std::vector{sos}, + "sos"); + append_embedding_rows( + embeddings, + weights_->text_embedding, + config.text_vocab_size, + config.hidden_size, + text_tokens, + "text"); + append_embedding_rows( + embeddings, + weights_->speech_embedding, + speech_embedding_rows, + config.hidden_size, + std::vector{task_id}, + "task"); + append_embedding_rows( + embeddings, + weights_->speech_embedding, + speech_embedding_rows, + config.hidden_size, + request.prompt_speech_tokens, + "prompt speech"); + + const int64_t required_cache_steps = prefill_steps + max_len; + auto timing_start = Clock::now(); + qwen_runtime_->release_runtime_graphs(); + auto prefill = qwen_runtime_->prefill_embeddings(embeddings, prefill_steps); + debug::timing_log_scalar("cosyvoice3.ar.prefill.total_ms", engine::debug::elapsed_ms(timing_start, Clock::now())); + qwen_runtime_->start_decode_embeddings(prefill.state, required_cache_steps); + + sampling::HfSamplerScratch scratch; + scratch.reserve_vocab(static_cast(config.speech_token_size + config.speech_reserved_tokens)); + std::mt19937 fallback_rng(request.seed); + uint64_t sample_call_index = 0; + uint64_t rng_offset_blocks = 0; + + CosyVoice3ArOutput out; + std::vector decoded_tokens; + int64_t consecutive_silent_tokens = 0; + int64_t filtered_silent_tokens = 0; + std::vector logits = std::move(prefill.logits); + timing_start = Clock::now(); + for (int64_t step = 0; step < max_len; ++step) { + auto log_probs = log_softmax(logits); + if (step < min_len) { + log_probs[static_cast(sos)] = -std::numeric_limits::infinity(); + } + const int32_t token = ras_sample( + std::move(log_probs), + decoded_tokens, + request.top_k, + scratch, + fallback_rng, + sampling_policy_, + request.seed, + sample_call_index, + rng_offset_blocks); + if (token >= config.speech_token_size) { + break; + } + decoded_tokens.push_back(token); + bool append_token = true; + if (is_silent_token(token)) { + ++consecutive_silent_tokens; + if (consecutive_silent_tokens > kMaxConsecutiveSilentTokens) { + append_token = false; + ++filtered_silent_tokens; + } + } else { + consecutive_silent_tokens = 0; + } + if (append_token) { + out.speech_tokens.push_back(token); + } + logits = qwen_runtime_->decode_embedding(embedding_row( + weights_->speech_embedding, + speech_embedding_rows, + config.hidden_size, + token, + "sampled speech")).logits; + } + debug::timing_log_scalar("cosyvoice3.ar.decode.total_ms", engine::debug::elapsed_ms(timing_start, Clock::now())); + (void) prefill_steps; + (void) filtered_silent_tokens; + return out; + } + + void release_graphs() { + if (qwen_runtime_ != nullptr) { + qwen_runtime_->release_runtime_graphs(); + } + } + +private: + std::shared_ptr assets_; + core::ExecutionContext & execution_; + modules::QwenCausalDecodeRuntimeConfig qwen_config_; + std::shared_ptr weights_; + std::unique_ptr qwen_runtime_; + sampling::TorchCudaSamplingPolicy sampling_policy_; +}; + +CosyVoice3ArRuntime::CosyVoice3ArRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type) + : impl_(std::make_unique( + std::move(assets), + execution, + graph_arena_bytes, + weight_context_bytes, + storage_type)) {} + +CosyVoice3ArRuntime::~CosyVoice3ArRuntime() = default; + +CosyVoice3ArOutput CosyVoice3ArRuntime::generate(const CosyVoice3ArRequest & request) { + return impl_->generate(request); +} + +void CosyVoice3ArRuntime::release_graphs() { + impl_->release_graphs(); +} + +} // namespace engine::models::cosyvoice3 diff --git a/src/models/cosyvoice3/assets.cpp b/src/models/cosyvoice3/assets.cpp new file mode 100644 index 000000000..5d8a3972b --- /dev/null +++ b/src/models/cosyvoice3/assets.cpp @@ -0,0 +1,69 @@ +#include "engine/models/cosyvoice3/assets.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/model_spec/package.h" + +#include + +namespace engine::models::cosyvoice3 { +namespace { + +constexpr const char * kFamily = "cosyvoice3"; + +std::filesystem::path find_gguf_path(const engine::assets::ResourceBundle & resources) { + for (const auto & file : resources.files()) { + if (file.path.extension() == ".gguf") { + return file.path; + } + } + return {}; +} + +void validate_llm_shapes(const engine::assets::TensorSource & source, CosyVoice3Config & config) { + const auto embedding = source.require_metadata("llm.model.model.embed_tokens.weight"); + if (embedding.shape.size() != 2) { + throw std::runtime_error("CosyVoice3 LLM embedding must be rank 2"); + } + config.text_vocab_size = embedding.shape[0]; + config.hidden_size = embedding.shape[1]; + engine::assets::require_tensor_shape(source, "speech_embedding.weight", {config.speech_token_size + config.speech_reserved_tokens, config.hidden_size}); + engine::assets::require_tensor_shape(source, "llm_decoder.weight", {config.speech_token_size + config.speech_reserved_tokens, config.hidden_size}); + engine::assets::require_tensor_shape(source, "llm.model.model.layers.0.self_attn.q_proj.weight", {config.heads * config.head_dim, config.hidden_size}); + engine::assets::require_tensor_shape(source, "llm.model.model.layers.0.self_attn.k_proj.weight", {config.kv_heads * config.head_dim, config.hidden_size}); + engine::assets::require_tensor_shape(source, "llm.model.model.layers.0.self_attn.v_proj.weight", {config.kv_heads * config.head_dim, config.hidden_size}); +} + +void validate_flow_shapes(const engine::assets::TensorSource & source, const CosyVoice3Config & config) { + engine::assets::require_tensor_shape(source, "input_embedding.weight", {config.speech_token_size, config.flow_mel_channels}); + engine::assets::require_tensor_shape(source, "spk_embed_affine_layer.weight", {config.flow_mel_channels, config.speaker_dim}); + engine::assets::require_tensor_shape(source, "decoder.rand_noise", {1, config.flow_mel_channels, 50 * 300}); + engine::assets::require_tensor_shape(source, "decoder.estimator.input_embed.proj.weight", {config.flow_hidden_size, 320}); + engine::assets::require_tensor_shape(source, "decoder.estimator.proj_out.weight", {config.flow_mel_channels, config.flow_hidden_size}); +} + +void validate_hift_shapes(const engine::assets::TensorSource & source) { + engine::assets::require_tensor_shape(source, "conv_pre.parametrizations.weight.original1", {512, 80, 5}); + engine::assets::require_tensor_shape(source, "conv_post.parametrizations.weight.original1", {18, 64, 7}); +} + +} // namespace + +std::shared_ptr load_cosyvoice3_assets(const std::filesystem::path & model_path) { + auto assets = std::make_shared(); + assets->resources = engine::model_spec::load_resource_bundle_for_family(model_path, kFamily); + assets->model_root = assets->resources.model_root(); + assets->gguf_path = find_gguf_path(assets->resources); + assets->llm_weights = assets->resources.open_tensor_source("llm_weights"); + assets->flow_weights = assets->resources.open_tensor_source("flow_weights"); + assets->hift_weights = assets->resources.open_tensor_source("hift_weights"); + assets->campplus_weights = assets->resources.open_tensor_source("campplus_weights"); + assets->speech_tokenizer_weights = assets->resources.open_tensor_source("speech_tokenizer_weights"); + assets->blank_en_weights = assets->resources.open_tensor_source("blank_en_weights"); + + validate_llm_shapes(*assets->llm_weights, assets->config); + validate_flow_shapes(*assets->flow_weights, assets->config); + validate_hift_shapes(*assets->hift_weights); + return assets; +} + +} // namespace engine::models::cosyvoice3 diff --git a/src/models/cosyvoice3/flow.cpp b/src/models/cosyvoice3/flow.cpp new file mode 100644 index 000000000..c3dc4e23f --- /dev/null +++ b/src/models/cosyvoice3/flow.cpp @@ -0,0 +1,861 @@ +#include "engine/models/cosyvoice3/flow.h" + +#include "engine/framework/core/backend.h" +#include "engine/framework/core/backend_weight_store.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/debug/trace.h" +#include "engine/framework/modules/activation_modules.h" +#include "engine/framework/modules/attention/feed_forward.h" +#include "engine/framework/modules/attention/projected_grouped_self_attention.h" +#include "engine/framework/modules/conv_modules.h" +#include "engine/framework/modules/linear_module.h" +#include "engine/framework/modules/lookup_modules.h" +#include "engine/framework/modules/norm_modules.h" +#include "engine/framework/modules/primitive_modules.h" +#include "engine/framework/modules/structural_modules.h" +#include "engine/framework/modules/weight_binding.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::cosyvoice3 { +namespace { + +namespace binding = engine::modules::binding; +namespace core = engine::core; +namespace modules = engine::modules; + +constexpr float kPi = 3.14159265358979323846F; +constexpr float kInferenceCfgRate = 0.7F; +constexpr int64_t kTimeEmbeddingSize = 256; +constexpr int64_t kConvPosGroups = 16; +constexpr int64_t kConvPosKernel = 31; + +struct GgmlContextDeleter { + void operator()(ggml_context * ctx) const noexcept { + if (ctx != nullptr) { + ggml_free(ctx); + } + } +}; + +struct GgmlGallocrDeleter { + void operator()(ggml_gallocr_t alloc) const noexcept { + if (alloc != nullptr) { + ggml_gallocr_free(alloc); + } + } +}; + +struct GraphMemory { + std::unique_ptr ctx; + std::unique_ptr input_ctx; + std::unique_ptr, GgmlGallocrDeleter> gallocr; + ggml_backend_buffer_t input_buffer = nullptr; + ggml_cgraph * graph = nullptr; + + ~GraphMemory() { + reset(nullptr); + } + + void reset(ggml_backend_t backend) { + if (graph != nullptr && backend != nullptr) { + core::release_backend_graph_resources(backend, graph); + } + graph = nullptr; + gallocr.reset(); + if (input_buffer != nullptr) { + ggml_backend_buffer_free(input_buffer); + input_buffer = nullptr; + } + input_ctx.reset(); + ctx.reset(); + } +}; + +std::vector position_ids(int64_t steps) { + std::vector out(static_cast(steps)); + for (int64_t i = 0; i < steps; ++i) { + out[static_cast(i)] = static_cast(i); + } + return out; +} + +std::vector cosine_time_schedule(int64_t steps) { + if (steps <= 0) { + throw std::runtime_error("CosyVoice3 num_inference_steps must be positive"); + } + std::vector out(static_cast(steps + 1)); + for (int64_t i = 0; i <= steps; ++i) { + const float u = static_cast(i) / static_cast(steps); + out[static_cast(i)] = 1.0F - std::cos(u * 0.5F * kPi); + } + return out; +} + +std::vector timestep_embedding(float timestep) { + const int64_t half = kTimeEmbeddingSize / 2; + const float step = std::log(10000.0F) / static_cast(half - 1); + std::vector out(static_cast(kTimeEmbeddingSize)); + for (int64_t i = 0; i < half; ++i) { + const float freq = std::exp(static_cast(i) * -step); + const float arg = 1000.0F * timestep * freq; + out[static_cast(i)] = std::sin(arg); + out[static_cast(half + i)] = std::cos(arg); + } + return out; +} + +core::TensorValue repeat_like( + core::ModuleBuildContext & ctx, + const core::TensorValue & value, + const core::TensorValue & like) { + return modules::RepeatModule({like.shape}).build(ctx, value); +} + +core::TensorValue mul_broadcast( + core::ModuleBuildContext & ctx, + const core::TensorValue & x, + const core::TensorValue & scale) { + return modules::MulModule().build(ctx, x, repeat_like(ctx, scale, x)); +} + +core::TensorValue modulate( + core::ModuleBuildContext & ctx, + const core::TensorValue & x, + const core::TensorValue & shift, + const core::TensorValue & scale) { + auto one_plus = core::wrap_tensor( + ggml_scale_bias(ctx.ggml, repeat_like(ctx, scale, x).tensor, 1.0F, 1.0F), + x.shape, + GGML_TYPE_F32); + auto shifted = modules::AddModule().build(ctx, modules::MulModule().build(ctx, x, one_plus), repeat_like(ctx, shift, x)); + return shifted; +} + +core::TensorValue mish(core::ModuleBuildContext & ctx, const core::TensorValue & x) { + auto input = core::ensure_backend_addressable_layout(ctx, x); + auto softplus = core::wrap_tensor(ggml_softplus(ctx.ggml, input.tensor), input.shape, GGML_TYPE_F32); + auto t = core::wrap_tensor(ggml_tanh(ctx.ggml, softplus.tensor), input.shape, GGML_TYPE_F32); + return modules::MulModule().build(ctx, input, t); +} + +core::TensorValue grouped_conv1d( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const modules::Conv1dWeights & weights, + int64_t groups) { + const int64_t channels = input.shape.dims[1]; + const int64_t out_channels = weights.weight.shape.dims[0]; + const int64_t kernel = weights.weight.shape.dims[2]; + if (groups <= 0 || channels % groups != 0 || out_channels % groups != 0) { + throw std::runtime_error("CosyVoice3 grouped Conv1d channel/group mismatch"); + } + const int64_t channels_per_group = channels / groups; + const int64_t out_channels_per_group = out_channels / groups; + core::TensorValue output; + const auto input_contiguous = core::ensure_backend_addressable_layout(ctx, input); + for (int64_t group = 0; group < groups; ++group) { + auto input_group = modules::SliceModule({1, group * channels_per_group, channels_per_group}).build(ctx, input_contiguous); + auto weight_group = modules::SliceModule({0, group * out_channels_per_group, out_channels_per_group}).build(ctx, weights.weight); + modules::Conv1dWeights group_weights{weight_group, std::nullopt}; + if (weights.bias.has_value()) { + group_weights.bias = + modules::SliceModule({0, group * out_channels_per_group, out_channels_per_group}).build(ctx, *weights.bias); + } + auto group_out = modules::Conv1dModule({ + channels_per_group, + out_channels_per_group, + kernel, + 1, + 0, + 1, + weights.bias.has_value()}).build(ctx, input_group, group_weights); + output = output.valid() ? modules::ConcatModule({1}).build(ctx, output, group_out) : group_out; + } + return output; +} + +struct CosyDiTBlockWeights { + modules::LinearWeights attn_norm; + modules::ProjectedGroupedSelfAttentionWeights attention; + modules::NormWeights ff_norm; + modules::FeedForwardWeights ff; +}; + +struct CosyFlowWeights { + std::shared_ptr store; + core::TensorValue token_embedding; + modules::LinearWeights speaker_projection; + modules::Conv1dWeights pre_lookahead_conv1; + modules::Conv1dWeights pre_lookahead_conv2; + modules::LinearWeights input_projection; + modules::Conv1dWeights conv_pos_1; + modules::Conv1dWeights conv_pos_2; + modules::LinearWeights time_fc1; + modules::LinearWeights time_fc2; + std::vector blocks; + modules::LinearWeights final_norm; + modules::LinearWeights output_projection; + core::TensorValue rand_noise; + std::vector rand_noise_host; +}; + +modules::ProjectedGroupedSelfAttentionWeights load_attention( + core::BackendWeightStore & store, + const engine::assets::TensorSource & source, + const std::string & prefix, + int64_t hidden, + engine::assets::TensorStorageType storage_type) { + modules::ProjectedGroupedSelfAttentionWeights out; + out.q_proj = binding::linear_from_source(store, source, prefix + ".to_q", storage_type, hidden, hidden, true); + out.k_proj = binding::linear_from_source(store, source, prefix + ".to_k", storage_type, hidden, hidden, true); + out.v_proj = binding::linear_from_source(store, source, prefix + ".to_v", storage_type, hidden, hidden, true); + out.o_proj = binding::linear_from_source(store, source, prefix + ".to_out.0", storage_type, hidden, hidden, true); + return out; +} + +CosyDiTBlockWeights load_block( + core::BackendWeightStore & store, + const engine::assets::TensorSource & source, + const std::string & prefix, + const CosyVoice3Config & config, + engine::assets::TensorStorageType storage_type) { + CosyDiTBlockWeights out; + out.attn_norm = binding::linear_from_source( + store, source, prefix + ".attn_norm.linear", storage_type, 6 * config.flow_hidden_size, config.flow_hidden_size, true); + out.attention = load_attention(store, source, prefix + ".attn", config.flow_hidden_size, storage_type); + out.ff.fc1_weight = store.load_tensor( + source, + prefix + ".ff.ff.0.0.weight", + storage_type, + {config.flow_hidden_size * config.flow_ff_mult, config.flow_hidden_size}); + out.ff.fc1_bias = store.load_f32_tensor(source, prefix + ".ff.ff.0.0.bias", {config.flow_hidden_size * config.flow_ff_mult}); + out.ff.fc2_weight = store.load_tensor( + source, + prefix + ".ff.ff.2.weight", + storage_type, + {config.flow_hidden_size, config.flow_hidden_size * config.flow_ff_mult}); + out.ff.fc2_bias = store.load_f32_tensor(source, prefix + ".ff.ff.2.bias", {config.flow_hidden_size}); + return out; +} + +std::shared_ptr load_flow_weights( + const CosyVoice3Assets & assets, + core::ExecutionContext & execution, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type) { + auto weights = std::make_shared(); + weights->store = std::make_shared( + execution.backend(), + execution.backend_type(), + "cosyvoice3.flow.weights", + weight_context_bytes); + const auto & source = *assets.flow_weights; + const auto & c = assets.config; + weights->token_embedding = weights->store->load_tensor(source, "input_embedding.weight", storage_type, {c.speech_token_size, c.flow_mel_channels}); + weights->speaker_projection = binding::linear_from_source( + *weights->store, source, "spk_embed_affine_layer", storage_type, c.flow_mel_channels, c.speaker_dim, true); + weights->pre_lookahead_conv1 = binding::conv1d_from_source( + *weights->store, source, "pre_lookahead_layer.conv1", storage_type, c.flow_hidden_size, c.flow_mel_channels, c.pre_lookahead_len + 1, true); + weights->pre_lookahead_conv2 = binding::conv1d_from_source( + *weights->store, source, "pre_lookahead_layer.conv2", storage_type, c.flow_mel_channels, c.flow_hidden_size, 3, true); + weights->input_projection = binding::linear_from_source( + *weights->store, source, "decoder.estimator.input_embed.proj", storage_type, c.flow_hidden_size, 320, true); + weights->conv_pos_1.weight = weights->store->load_tensor( + source, + "decoder.estimator.input_embed.conv_pos_embed.conv1.0.weight", + storage_type, + {c.flow_hidden_size, c.flow_hidden_size / kConvPosGroups, kConvPosKernel}); + weights->conv_pos_1.bias = weights->store->load_f32_tensor( + source, + "decoder.estimator.input_embed.conv_pos_embed.conv1.0.bias", + {c.flow_hidden_size}); + weights->conv_pos_2.weight = weights->store->load_tensor( + source, + "decoder.estimator.input_embed.conv_pos_embed.conv2.0.weight", + storage_type, + {c.flow_hidden_size, c.flow_hidden_size / kConvPosGroups, kConvPosKernel}); + weights->conv_pos_2.bias = weights->store->load_f32_tensor( + source, + "decoder.estimator.input_embed.conv_pos_embed.conv2.0.bias", + {c.flow_hidden_size}); + weights->time_fc1 = binding::linear_from_source( + *weights->store, source, "decoder.estimator.time_embed.time_mlp.0", storage_type, c.flow_hidden_size, kTimeEmbeddingSize, true); + weights->time_fc2 = binding::linear_from_source( + *weights->store, source, "decoder.estimator.time_embed.time_mlp.2", storage_type, c.flow_hidden_size, c.flow_hidden_size, true); + weights->blocks.reserve(static_cast(c.flow_layers)); + for (int64_t layer = 0; layer < c.flow_layers; ++layer) { + weights->blocks.push_back(load_block( + *weights->store, + source, + "decoder.estimator.transformer_blocks." + std::to_string(layer), + c, + storage_type)); + } + weights->final_norm = binding::linear_from_source( + *weights->store, source, "decoder.estimator.norm_out.linear", storage_type, 2 * c.flow_hidden_size, c.flow_hidden_size, true); + weights->output_projection = binding::linear_from_source( + *weights->store, source, "decoder.estimator.proj_out", storage_type, c.flow_mel_channels, c.flow_hidden_size, true); + weights->rand_noise = weights->store->load_tensor(source, "decoder.rand_noise", engine::assets::TensorStorageType::F32, {1, c.flow_mel_channels, 50 * 300}); + weights->rand_noise_host = source.require_f32("decoder.rand_noise", {1, c.flow_mel_channels, 50 * 300}); + weights->store->upload(); + return weights; +} + +modules::ProjectedGroupedSelfAttentionConfig attention_config(const CosyVoice3Config & config) { + modules::ProjectedGroupedSelfAttentionConfig out; + out.hidden_size = config.flow_hidden_size; + out.attention_heads = config.flow_heads; + out.kv_heads = config.flow_heads; + out.head_dim = config.flow_head_dim; + out.use_bias = true; + out.use_rope = true; + out.apply_rope_to_projected_prefix = true; + out.rope_type = GGML_ROPE_TYPE_NORMAL; + out.rope_theta = 10000.0F; + out.local_rope_theta = 10000.0F; + out.causality = modules::AttentionCausality::NonCausal; + out.lowering = modules::GroupedQueryAttentionLowering::FlashGroupedViewKV; + out.attention_precision = GGML_PREC_F32; + return out; +} + +core::TensorValue causal_conv_pos_embed( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const CosyFlowWeights & weights) { + auto x = modules::TransposeModule({{0, 2, 1, 3}, input.shape.rank}).build(ctx, input); + x = core::wrap_tensor( + ggml_pad_ext(ctx.ggml, x.tensor, kConvPosKernel - 1, 0, 0, 0, 0, 0, 0, 0), + core::TensorShape::from_dims({x.shape.dims[0], x.shape.dims[1], x.shape.dims[2] + kConvPosKernel - 1}), + GGML_TYPE_F32); + x = grouped_conv1d(ctx, x, weights.conv_pos_1, kConvPosGroups); + x = mish(ctx, x); + x = core::wrap_tensor( + ggml_pad_ext(ctx.ggml, x.tensor, kConvPosKernel - 1, 0, 0, 0, 0, 0, 0, 0), + core::TensorShape::from_dims({x.shape.dims[0], x.shape.dims[1], x.shape.dims[2] + kConvPosKernel - 1}), + GGML_TYPE_F32); + x = grouped_conv1d(ctx, x, weights.conv_pos_2, kConvPosGroups); + x = mish(ctx, x); + x = modules::TransposeModule({{0, 2, 1, 3}, x.shape.rank}).build(ctx, x); + return x; +} + +core::TensorValue dit_block( + core::ModuleBuildContext & ctx, + const core::TensorValue & input, + const core::TensorValue & time, + const core::TensorValue & positions, + const CosyDiTBlockWeights & weights, + const CosyVoice3Config & config, + int64_t layer) { + auto ada = modules::SiluModule().build(ctx, time); + ada = modules::LinearModule({config.flow_hidden_size, 6 * config.flow_hidden_size, true}).build(ctx, ada, weights.attn_norm); + auto shift_msa = modules::SliceModule({2, 0 * config.flow_hidden_size, config.flow_hidden_size}).build(ctx, ada); + auto scale_msa = modules::SliceModule({2, 1 * config.flow_hidden_size, config.flow_hidden_size}).build(ctx, ada); + auto gate_msa = modules::SliceModule({2, 2 * config.flow_hidden_size, config.flow_hidden_size}).build(ctx, ada); + auto shift_mlp = modules::SliceModule({2, 3 * config.flow_hidden_size, config.flow_hidden_size}).build(ctx, ada); + auto scale_mlp = modules::SliceModule({2, 4 * config.flow_hidden_size, config.flow_hidden_size}).build(ctx, ada); + auto gate_mlp = modules::SliceModule({2, 5 * config.flow_hidden_size, config.flow_hidden_size}).build(ctx, ada); + + auto x = modules::LayerNormModule({config.flow_hidden_size, 1.0e-6F, false, false}).build(ctx, input, {}); + x = modulate(ctx, x, shift_msa, scale_msa); + x = modules::ProjectedGroupedSelfAttentionModule(attention_config(config)).build(ctx, x, positions, weights.attention, layer); + x = mul_broadcast(ctx, x, gate_msa); + auto out = modules::AddModule().build(ctx, input, x); + + x = modules::LayerNormModule({config.flow_hidden_size, 1.0e-6F, false, false}).build(ctx, out, {}); + x = modulate(ctx, x, shift_mlp, scale_mlp); + x = modules::FeedForwardModule({ + config.flow_hidden_size, + config.flow_hidden_size * config.flow_ff_mult, + true, + modules::GeluApproximation::Tanh, + }).build(ctx, x, weights.ff); + x = mul_broadcast(ctx, x, gate_mlp); + return modules::AddModule().build(ctx, out, x); +} + +class ConditionGraph { +public: + ConditionGraph( + core::ExecutionContext & execution, + std::shared_ptr weights, + CosyVoice3Config config, + size_t graph_arena_bytes) + : execution_(execution), + weights_(std::move(weights)), + config_(config), + graph_arena_bytes_(graph_arena_bytes) {} + + ~ConditionGraph() { + mem_.reset(execution_.backend()); + } + + std::vector run(const std::vector & prompt_tokens, const std::vector & target_tokens) { + const int64_t tokens = static_cast(prompt_tokens.size() + target_tokens.size()); + if (tokens <= 0) { + throw std::runtime_error("CosyVoice3 flow requires speech tokens"); + } + ensure(tokens); + std::vector all_tokens; + all_tokens.reserve(static_cast(tokens)); + all_tokens.insert(all_tokens.end(), prompt_tokens.begin(), prompt_tokens.end()); + all_tokens.insert(all_tokens.end(), target_tokens.begin(), target_tokens.end()); + ggml_backend_tensor_set(token_ids_, all_tokens.data(), 0, all_tokens.size() * sizeof(int32_t)); + if (core::compute_backend_graph(execution_.backend(), mem_.graph, nullptr, "cosyvoice3.flow.condition") != GGML_STATUS_SUCCESS) { + throw std::runtime_error("CosyVoice3 flow condition graph compute failed"); + } + auto output = core::read_tensor_f32(output_.tensor); + return output; + } + + void release_graph() { + mem_.reset(execution_.backend()); + tokens_ = 0; + token_ids_ = nullptr; + output_ = {}; + } + +private: + void ensure(int64_t tokens) { + if (mem_.graph != nullptr && tokens_ == tokens) { + return; + } + mem_.reset(execution_.backend()); + ggml_init_params params{graph_arena_bytes_, nullptr, true}; + mem_.ctx.reset(ggml_init(params)); + ggml_init_params input_params{16ull * 1024ull * 1024ull, nullptr, true}; + mem_.input_ctx.reset(ggml_init(input_params)); + core::ModuleBuildContext ctx{mem_.ctx.get(), "cosyvoice3.flow.condition", execution_.backend_type()}; + core::ModuleBuildContext input_ctx{mem_.input_ctx.get(), "cosyvoice3.flow.condition.inputs", execution_.backend_type()}; + + auto ids = core::make_tensor(input_ctx, GGML_TYPE_I32, core::TensorShape::from_dims({1, tokens})); + token_ids_ = ids.tensor; + ggml_set_input(token_ids_); + auto h = modules::EmbeddingModule({config_.speech_token_size, config_.flow_mel_channels}) + .build(ctx, ids, weights_->token_embedding); + const auto residual = h; + h = modules::TransposeModule({{0, 2, 1, 3}, h.shape.rank}).build(ctx, h); + if (execution_.backend_type() == core::BackendType::Metal) { + h = core::ensure_backend_addressable_layout(ctx, h); + } + h = core::wrap_tensor( + ggml_pad_ext(ctx.ggml, h.tensor, 0, config_.pre_lookahead_len, 0, 0, 0, 0, 0, 0), + core::TensorShape::from_dims({1, config_.flow_mel_channels, tokens + config_.pre_lookahead_len}), + GGML_TYPE_F32); + h = modules::Conv1dModule({config_.flow_mel_channels, config_.flow_hidden_size, config_.pre_lookahead_len + 1, 1, 0, 1, true}) + .build(ctx, h, weights_->pre_lookahead_conv1); + h = modules::LeakyReluModule().build(ctx, h); + h = core::wrap_tensor( + ggml_pad_ext(ctx.ggml, h.tensor, 2, 0, 0, 0, 0, 0, 0, 0), + core::TensorShape::from_dims({1, config_.flow_hidden_size, tokens + 2}), + GGML_TYPE_F32); + h = modules::Conv1dModule({config_.flow_hidden_size, config_.flow_mel_channels, 3, 1, 0, 1, true}) + .build(ctx, h, weights_->pre_lookahead_conv2); + h = modules::TransposeModule({{0, 2, 1, 3}, h.shape.rank}).build(ctx, h); + h = modules::AddModule().build(ctx, h, residual); + h = modules::Interpolate1dModule({tokens * config_.token_mel_ratio, modules::Interpolate1dMode::Nearest}) + .build(ctx, modules::TransposeModule({{0, 2, 1, 3}, h.shape.rank}).build(ctx, h)); + h = modules::TransposeModule({{0, 2, 1, 3}, h.shape.rank}).build(ctx, h); + output_ = core::ensure_backend_addressable_layout(ctx, h); + ggml_set_output(output_.tensor); + mem_.graph = ggml_new_graph_custom(mem_.ctx.get(), 20000, false); + ggml_build_forward_expand(mem_.graph, output_.tensor); + mem_.input_buffer = ggml_backend_alloc_ctx_tensors(mem_.input_ctx.get(), execution_.backend()); + mem_.gallocr.reset(ggml_gallocr_new(ggml_backend_get_default_buffer_type(execution_.backend()))); + if (mem_.input_buffer == nullptr || mem_.gallocr == nullptr || + !ggml_gallocr_reserve(mem_.gallocr.get(), mem_.graph) || + !ggml_gallocr_alloc_graph(mem_.gallocr.get(), mem_.graph)) { + mem_.reset(execution_.backend()); + throw std::runtime_error("failed to allocate CosyVoice3 flow condition graph"); + } + tokens_ = tokens; + } + + core::ExecutionContext & execution_; + std::shared_ptr weights_; + CosyVoice3Config config_; + size_t graph_arena_bytes_ = 0; + GraphMemory mem_; + int64_t tokens_ = 0; + ggml_tensor * token_ids_ = nullptr; + core::TensorValue output_; +}; + +class DiTGraph { +public: + DiTGraph( + core::ExecutionContext & execution, + std::shared_ptr weights, + CosyVoice3Config config, + size_t graph_arena_bytes) + : execution_(execution), + weights_(std::move(weights)), + config_(config), + graph_arena_bytes_(graph_arena_bytes) {} + + ~DiTGraph() { + mem_.reset(execution_.backend()); + } + + std::vector run( + const std::vector & x, + const std::vector & mu, + const std::vector & cond, + const std::vector & speaker, + const std::vector & time_embedding, + int64_t frames) { + if (frames <= 0) { + throw std::runtime_error("CosyVoice3 DiT frames must be positive"); + } + const int64_t batch = 2; + const int64_t channels = config_.flow_mel_channels; + if (static_cast(x.size()) != batch * channels * frames || + static_cast(mu.size()) != batch * channels * frames || + static_cast(cond.size()) != batch * channels * frames || + static_cast(speaker.size()) != batch * channels || + static_cast(time_embedding.size()) != batch * kTimeEmbeddingSize) { + throw std::runtime_error("CosyVoice3 DiT input size mismatch"); + } + ensure(frames); + ggml_backend_tensor_set(x_, x.data(), 0, x.size() * sizeof(float)); + ggml_backend_tensor_set(mu_, mu.data(), 0, mu.size() * sizeof(float)); + ggml_backend_tensor_set(cond_, cond.data(), 0, cond.size() * sizeof(float)); + ggml_backend_tensor_set(spks_, speaker.data(), 0, speaker.size() * sizeof(float)); + ggml_backend_tensor_set(time_, time_embedding.data(), 0, time_embedding.size() * sizeof(float)); + if (core::compute_backend_graph(execution_.backend(), mem_.graph, nullptr, "cosyvoice3.flow.dit") != GGML_STATUS_SUCCESS) { + throw std::runtime_error("CosyVoice3 DiT graph compute failed"); + } + return core::read_tensor_f32(output_.tensor); + } + + void release_graph() { + mem_.reset(execution_.backend()); + frames_ = 0; + x_ = nullptr; + mu_ = nullptr; + cond_ = nullptr; + spks_ = nullptr; + time_ = nullptr; + positions_ = nullptr; + output_ = {}; + } + +private: + void ensure(int64_t frames) { + if (mem_.graph != nullptr && frames_ == frames) { + return; + } + mem_.reset(execution_.backend()); + constexpr int64_t batch = 2; + const int64_t channels = config_.flow_mel_channels; + ggml_init_params params{graph_arena_bytes_, nullptr, true}; + mem_.ctx.reset(ggml_init(params)); + ggml_init_params input_params{32ull * 1024ull * 1024ull, nullptr, true}; + mem_.input_ctx.reset(ggml_init(input_params)); + core::ModuleBuildContext ctx{mem_.ctx.get(), "cosyvoice3.flow.dit", execution_.backend_type()}; + core::ModuleBuildContext input_ctx{mem_.input_ctx.get(), "cosyvoice3.flow.dit.inputs", execution_.backend_type()}; + + auto x = core::make_tensor(input_ctx, GGML_TYPE_F32, core::TensorShape::from_dims({batch, channels, frames})); + x_ = x.tensor; + ggml_set_input(x_); + auto mu = core::make_tensor(input_ctx, GGML_TYPE_F32, core::TensorShape::from_dims({batch, channels, frames})); + mu_ = mu.tensor; + ggml_set_input(mu_); + auto cond = core::make_tensor(input_ctx, GGML_TYPE_F32, core::TensorShape::from_dims({batch, channels, frames})); + cond_ = cond.tensor; + ggml_set_input(cond_); + auto spks = core::make_tensor(input_ctx, GGML_TYPE_F32, core::TensorShape::from_dims({batch, channels})); + spks_ = spks.tensor; + ggml_set_input(spks_); + auto time = core::make_tensor(input_ctx, GGML_TYPE_F32, core::TensorShape::from_dims({batch, kTimeEmbeddingSize})); + time_ = time.tensor; + ggml_set_input(time_); + auto positions = core::make_tensor(input_ctx, GGML_TYPE_I32, core::TensorShape::from_dims({frames})); + positions_ = positions.tensor; + + x = modules::TransposeModule({{0, 2, 1, 3}, x.shape.rank}).build(ctx, x); + mu = modules::TransposeModule({{0, 2, 1, 3}, mu.shape.rank}).build(ctx, mu); + cond = modules::TransposeModule({{0, 2, 1, 3}, cond.shape.rank}).build(ctx, cond); + x = core::ensure_backend_addressable_layout(ctx, x); + mu = core::ensure_backend_addressable_layout(ctx, mu); + cond = core::ensure_backend_addressable_layout(ctx, cond); + auto spks_btf = core::reshape_tensor(ctx, spks, core::TensorShape::from_dims({batch, 1, channels})); + spks_btf = modules::RepeatModule({core::TensorShape::from_dims({batch, frames, channels})}).build(ctx, spks_btf); + spks_btf = core::ensure_backend_addressable_layout(ctx, spks_btf); + auto input = modules::ConcatModule({2}).build(ctx, x, cond); + input = modules::ConcatModule({2}).build(ctx, input, mu); + input = modules::ConcatModule({2}).build(ctx, input, spks_btf); + input = core::ensure_backend_addressable_layout(ctx, input); + input = modules::LinearModule({320, config_.flow_hidden_size, true}).build(ctx, input, weights_->input_projection); + input = core::ensure_backend_addressable_layout(ctx, input); + auto pos = causal_conv_pos_embed(ctx, input, *weights_); + pos = core::ensure_backend_addressable_layout(ctx, pos); + input = modules::AddModule().build(ctx, input, pos); + input = core::ensure_backend_addressable_layout(ctx, input); + + time = modules::LinearModule({kTimeEmbeddingSize, config_.flow_hidden_size, true}).build(ctx, time, weights_->time_fc1); + time = modules::SiluModule().build(ctx, time); + time = modules::LinearModule({config_.flow_hidden_size, config_.flow_hidden_size, true}).build(ctx, time, weights_->time_fc2); + time = core::reshape_tensor(ctx, time, core::TensorShape::from_dims({batch, 1, config_.flow_hidden_size})); + + for (size_t layer = 0; layer < weights_->blocks.size(); ++layer) { + input = dit_block(ctx, input, time, positions, weights_->blocks[layer], config_, static_cast(layer)); + } + auto norm_mod = modules::SiluModule().build(ctx, time); + norm_mod = modules::LinearModule({config_.flow_hidden_size, 2 * config_.flow_hidden_size, true}).build(ctx, norm_mod, weights_->final_norm); + auto scale = modules::SliceModule({2, 0, config_.flow_hidden_size}).build(ctx, norm_mod); + auto shift = modules::SliceModule({2, config_.flow_hidden_size, config_.flow_hidden_size}).build(ctx, norm_mod); + input = modules::LayerNormModule({config_.flow_hidden_size, 1.0e-6F, false, false}).build(ctx, input, {}); + input = modulate(ctx, input, shift, scale); + input = modules::LinearModule({config_.flow_hidden_size, channels, true}).build(ctx, input, weights_->output_projection); + input = modules::TransposeModule({{0, 2, 1, 3}, input.shape.rank}).build(ctx, input); + output_ = core::ensure_backend_addressable_layout(ctx, input); + ggml_set_output(output_.tensor); + mem_.graph = ggml_new_graph_custom(mem_.ctx.get(), 200000, false); + ggml_build_forward_expand(mem_.graph, output_.tensor); + mem_.input_buffer = ggml_backend_alloc_ctx_tensors(mem_.input_ctx.get(), execution_.backend()); + mem_.gallocr.reset(ggml_gallocr_new(ggml_backend_get_default_buffer_type(execution_.backend()))); + if (mem_.input_buffer == nullptr || mem_.gallocr == nullptr || + !ggml_gallocr_reserve(mem_.gallocr.get(), mem_.graph) || + !ggml_gallocr_alloc_graph(mem_.gallocr.get(), mem_.graph)) { + mem_.reset(execution_.backend()); + throw std::runtime_error("failed to allocate CosyVoice3 DiT graph"); + } + const auto pos_ids = position_ids(frames); + ggml_backend_tensor_set(positions_, pos_ids.data(), 0, pos_ids.size() * sizeof(int32_t)); + frames_ = frames; + } + + core::ExecutionContext & execution_; + std::shared_ptr weights_; + CosyVoice3Config config_; + size_t graph_arena_bytes_ = 0; + GraphMemory mem_; + int64_t frames_ = 0; + ggml_tensor * x_ = nullptr; + ggml_tensor * mu_ = nullptr; + ggml_tensor * cond_ = nullptr; + ggml_tensor * spks_ = nullptr; + ggml_tensor * time_ = nullptr; + ggml_tensor * positions_ = nullptr; + core::TensorValue output_; +}; + +std::vector normalize_and_project_speaker( + const CosyVoice3Config & config, + const CosyFlowWeights & weights, + core::ExecutionContext & execution, + const std::vector & speaker_embedding, + size_t graph_arena_bytes) { + if (static_cast(speaker_embedding.size()) != config.speaker_dim) { + throw std::runtime_error("CosyVoice3 speaker embedding size mismatch"); + } + float norm_sq = 0.0F; + for (float value : speaker_embedding) { + norm_sq += value * value; + } + const float inv_norm = 1.0F / std::sqrt(std::max(norm_sq, 1.0e-12F)); + std::vector normalized(speaker_embedding.size()); + for (size_t i = 0; i < speaker_embedding.size(); ++i) { + normalized[i] = speaker_embedding[i] * inv_norm; + } + + GraphMemory mem; + ggml_init_params params{graph_arena_bytes, nullptr, true}; + mem.ctx.reset(ggml_init(params)); + ggml_init_params input_params{4ull * 1024ull * 1024ull, nullptr, true}; + mem.input_ctx.reset(ggml_init(input_params)); + core::ModuleBuildContext ctx{mem.ctx.get(), "cosyvoice3.flow.speaker", execution.backend_type()}; + core::ModuleBuildContext input_ctx{mem.input_ctx.get(), "cosyvoice3.flow.speaker.inputs", execution.backend_type()}; + auto input = core::make_tensor(input_ctx, GGML_TYPE_F32, core::TensorShape::from_dims({1, config.speaker_dim})); + ggml_set_input(input.tensor); + auto output = modules::LinearModule({config.speaker_dim, config.flow_mel_channels, true}).build(ctx, input, weights.speaker_projection); + output = core::ensure_backend_addressable_layout(ctx, output); + ggml_set_output(output.tensor); + mem.graph = ggml_new_graph_custom(mem.ctx.get(), 4096, false); + ggml_build_forward_expand(mem.graph, output.tensor); + mem.input_buffer = ggml_backend_alloc_ctx_tensors(mem.input_ctx.get(), execution.backend()); + mem.gallocr.reset(ggml_gallocr_new(ggml_backend_get_default_buffer_type(execution.backend()))); + if (mem.input_buffer == nullptr || mem.gallocr == nullptr || + !ggml_gallocr_reserve(mem.gallocr.get(), mem.graph) || + !ggml_gallocr_alloc_graph(mem.gallocr.get(), mem.graph)) { + throw std::runtime_error("CosyVoice3 speaker projection graph failed"); + } + ggml_backend_tensor_set(input.tensor, normalized.data(), 0, normalized.size() * sizeof(float)); + if (core::compute_backend_graph(execution.backend(), mem.graph, nullptr, "cosyvoice3.flow.speaker") != GGML_STATUS_SUCCESS) { + throw std::runtime_error("CosyVoice3 speaker projection graph failed"); + } + auto out = core::read_tensor_f32(output.tensor); + mem.reset(execution.backend()); + return out; +} + +std::vector read_noise_prefix( + const CosyVoice3Config & config, + const CosyFlowWeights & weights, + int64_t frames) { + if (frames > 50 * 300) { + throw std::runtime_error("CosyVoice3 requested mel frames exceed fixed flow noise capacity"); + } + const auto & full = weights.rand_noise_host; + std::vector out(static_cast(config.flow_mel_channels * frames)); + for (int64_t ch = 0; ch < config.flow_mel_channels; ++ch) { + const auto src = full.begin() + static_cast(ch * 50 * 300); + const auto dst = out.begin() + static_cast(ch * frames); + std::copy(src, src + frames, dst); + } + return out; +} + +} // namespace + +class CosyVoice3FlowRuntime::Impl { +public: + Impl( + std::shared_ptr assets, + core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type) + : assets_(std::move(assets)), + execution_(execution), + weights_(load_flow_weights(*assets_, execution_, weight_context_bytes, storage_type)), + condition_(execution_, weights_, assets_->config, graph_arena_bytes), + dit_(execution_, weights_, assets_->config, graph_arena_bytes), + graph_arena_bytes_(graph_arena_bytes) { + if (assets_ == nullptr) { + throw std::runtime_error("CosyVoice3 flow runtime requires assets"); + } + } + + CosyVoice3FlowOutput generate(const CosyVoice3FlowRequest & request) { + const auto start = std::chrono::steady_clock::now(); + const auto & c = assets_->config; + if (request.speech_tokens.empty()) { + throw std::runtime_error("CosyVoice3 flow requires generated speech tokens"); + } + if (request.prompt_mel_frames <= 0 || + static_cast(request.prompt_mel.size()) != request.prompt_mel_frames * c.flow_mel_channels) { + throw std::runtime_error("CosyVoice3 flow prompt mel size mismatch"); + } + const auto cond_start = std::chrono::steady_clock::now(); + auto mu_btc = condition_.run(request.prompt_speech_tokens, request.speech_tokens); + const int64_t total_frames = static_cast(mu_btc.size()) / c.flow_mel_channels; + const int64_t target_frames = total_frames - request.prompt_mel_frames; + if (target_frames <= 0) { + throw std::runtime_error("CosyVoice3 flow target mel frames must be positive"); + } + engine::debug::timing_log_scalar("cosyvoice3.flow.condition_ms", engine::debug::elapsed_ms(cond_start)); + + std::vector mu(static_cast(2 * c.flow_mel_channels * total_frames), 0.0F); + for (int64_t frame = 0; frame < total_frames; ++frame) { + for (int64_t ch = 0; ch < c.flow_mel_channels; ++ch) { + mu[static_cast(ch * total_frames + frame)] = + mu_btc[static_cast(frame * c.flow_mel_channels + ch)]; + } + } + std::vector cond(static_cast(2 * c.flow_mel_channels * total_frames), 0.0F); + for (int64_t frame = 0; frame < request.prompt_mel_frames; ++frame) { + for (int64_t ch = 0; ch < c.flow_mel_channels; ++ch) { + cond[static_cast(ch * total_frames + frame)] = + request.prompt_mel[static_cast(frame * c.flow_mel_channels + ch)]; + } + } + auto x = read_noise_prefix(c, *weights_, total_frames); + std::vector x_batched(static_cast(2 * c.flow_mel_channels * total_frames)); + std::copy(x.begin(), x.end(), x_batched.begin()); + std::copy(x.begin(), x.end(), x_batched.begin() + static_cast(x.size())); + + auto speaker_cond = normalize_and_project_speaker( + c, + *weights_, + execution_, + request.speaker_embedding, + graph_arena_bytes_); + std::vector spks(static_cast(2 * c.flow_mel_channels), 0.0F); + std::copy(speaker_cond.begin(), speaker_cond.end(), spks.begin()); + + const auto schedule = cosine_time_schedule(request.num_inference_steps); + const auto dit_start = std::chrono::steady_clock::now(); + for (int64_t step = 1; step < static_cast(schedule.size()); ++step) { + const float t = schedule[static_cast(step - 1)]; + const float dt = schedule[static_cast(step)] - t; + auto temb = timestep_embedding(t); + std::vector time_batched(static_cast(2 * kTimeEmbeddingSize)); + std::copy(temb.begin(), temb.end(), time_batched.begin()); + std::copy(temb.begin(), temb.end(), time_batched.begin() + kTimeEmbeddingSize); + auto pred = dit_.run(x_batched, mu, cond, spks, time_batched, total_frames); + const size_t branch = static_cast(c.flow_mel_channels * total_frames); + for (size_t i = 0; i < branch; ++i) { + const float guided = (1.0F + kInferenceCfgRate) * pred[i] - kInferenceCfgRate * pred[branch + i]; + x_batched[i] += dt * guided; + } + std::copy(x_batched.begin(), x_batched.begin() + static_cast(branch), x_batched.begin() + static_cast(branch)); + } + engine::debug::timing_log_scalar("cosyvoice3.flow.dit_ms", engine::debug::elapsed_ms(dit_start)); + + CosyVoice3FlowOutput out; + out.frames = target_frames; + out.mel.resize(static_cast(target_frames * c.flow_mel_channels)); + for (int64_t frame = 0; frame < target_frames; ++frame) { + for (int64_t ch = 0; ch < c.flow_mel_channels; ++ch) { + out.mel[static_cast(frame * c.flow_mel_channels + ch)] = + x_batched[static_cast(ch * total_frames + request.prompt_mel_frames + frame)]; + } + } + engine::debug::timing_log_scalar("cosyvoice3.flow.total_ms", engine::debug::elapsed_ms(start)); + engine::debug::trace_log_scalar("cosyvoice3.flow.total_frames", static_cast(total_frames)); + engine::debug::trace_log_scalar("cosyvoice3.flow.target_frames", static_cast(target_frames)); + return out; + } + + void release_graphs() { + condition_.release_graph(); + dit_.release_graph(); + } + +private: + std::shared_ptr assets_; + core::ExecutionContext & execution_; + std::shared_ptr weights_; + ConditionGraph condition_; + DiTGraph dit_; + size_t graph_arena_bytes_ = 0; +}; + +CosyVoice3FlowRuntime::CosyVoice3FlowRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type) + : impl_(std::make_unique( + std::move(assets), + execution, + graph_arena_bytes, + weight_context_bytes, + storage_type)) {} + +CosyVoice3FlowRuntime::~CosyVoice3FlowRuntime() = default; + +CosyVoice3FlowOutput CosyVoice3FlowRuntime::generate(const CosyVoice3FlowRequest & request) { + return impl_->generate(request); +} + +void CosyVoice3FlowRuntime::release_graphs() { + impl_->release_graphs(); +} + +} // namespace engine::models::cosyvoice3 diff --git a/src/models/cosyvoice3/frontend.cpp b/src/models/cosyvoice3/frontend.cpp new file mode 100644 index 000000000..a8df8f22c --- /dev/null +++ b/src/models/cosyvoice3/frontend.cpp @@ -0,0 +1,282 @@ +#include "engine/models/cosyvoice3/frontend.h" + +#include "engine/framework/audio/conversion.h" +#include "engine/framework/audio/dsp.h" +#include "engine/framework/audio/kaldi_fbank.h" +#include "engine/framework/audio/resampling.h" +#include "engine/framework/debug/profiler.h" +#include "engine/framework/modules/speech_encoders/campplus_encoder.h" +#include "engine/framework/modules/speech_encoders/s3_tokenizer.h" +#include "engine/framework/runtime/cache_slots.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace engine::models::cosyvoice3 { +namespace { + +uint64_t hash_audio(const engine::runtime::AudioBuffer & audio) { + uint64_t hash = 1469598103934665603ull; + auto mix = [&hash](uint64_t value) { + hash ^= value; + hash *= 1099511628211ull; + }; + mix(static_cast(audio.sample_rate)); + mix(static_cast(audio.channels)); + mix(static_cast(audio.samples.size())); + for (float sample : audio.samples) { + uint32_t bits = 0; + static_assert(sizeof(bits) == sizeof(sample)); + std::memcpy(&bits, &sample, sizeof(bits)); + mix(bits); + } + return hash; +} + +std::vector mono_resampled(const engine::runtime::AudioBuffer & audio, int sample_rate) { + if (audio.sample_rate <= 0 || audio.channels <= 0 || audio.samples.empty()) { + throw std::runtime_error("CosyVoice3 requires non-empty reference audio"); + } + auto mono = engine::audio::mixdown_interleaved_to_mono_average(audio.samples, audio.channels); + if (audio.sample_rate == sample_rate) { + return mono; + } + engine::audio::TorchaudioSincHannResampleOptions options; + options.kernel_mode = engine::audio::TorchaudioSincHannKernelMode::Float64ComputationStoredAsFloat64; + return engine::audio::resample_mono_torchaudio_sinc_hann(mono, audio.sample_rate, sample_rate, options); +} + +engine::runtime::AudioBuffer mono_buffer(const engine::runtime::AudioBuffer & audio, int sample_rate) { + engine::runtime::AudioBuffer out; + out.sample_rate = sample_rate; + out.channels = 1; + out.samples = mono_resampled(audio, sample_rate); + return out; +} + +int64_t reflect_index(int64_t index, int64_t length) { + while (index < 0 || index >= length) { + if (index < 0) { + index = -index; + } else { + index = 2 * length - index - 2; + } + } + return index; +} + +void compute_prompt_mel( + const engine::runtime::AudioBuffer & audio, + std::vector & values, + int64_t & frames) { + constexpr int64_t kSampleRate = 24000; + constexpr int64_t kNfft = 1920; + constexpr int64_t kHop = 480; + constexpr int64_t kWin = 1920; + constexpr int64_t kMels = 80; + constexpr float kEps = 1.0e-9F; + constexpr float kLogClamp = 1.0e-5F; + const auto mono = mono_resampled(audio, kSampleRate); + if (mono.size() < 2) { + throw std::runtime_error("CosyVoice3 reference audio is too short for prompt mel"); + } + const int64_t pad = (kNfft - kHop) / 2; + const int64_t padded_samples = static_cast(mono.size()) + 2 * pad; + std::vector padded(static_cast(padded_samples), 0.0F); + for (int64_t index = 0; index < padded_samples; ++index) { + padded[static_cast(index)] = + mono[static_cast(reflect_index(index - pad, static_cast(mono.size())))]; + } + const engine::audio::STFTConfig stft_config{ + kNfft, + kHop, + kWin, + false, + engine::audio::STFTPadMode::Constant, + engine::audio::STFTFamily::Kokoro, + }; + const auto & window = engine::audio::get_cached_stft_window(stft_config); + const auto magnitude = engine::audio::STFT().compute_magnitude( + padded, + window, + 1, + padded_samples, + stft_config); + frames = magnitude.shape[2]; + const int64_t freq_bins = kNfft / 2 + 1; + const auto filterbank = engine::audio::MelFilterbank().build( + engine::audio::MelFilterbankConfig{kSampleRate, kNfft, kMels, 0.0F, 0.0F, true}); + values.assign(static_cast(frames * kMels), 0.0F); +#ifdef _OPENMP +#pragma omp parallel for if (frames > 8) +#endif + for (int64_t frame = 0; frame < frames; ++frame) { + for (int64_t mel = 0; mel < kMels; ++mel) { + double sum = 0.0; + for (int64_t bin = 0; bin < freq_bins; ++bin) { + const float mag = magnitude.values[static_cast(bin * frames + frame)]; + const float stabilized = std::sqrt(mag * mag + kEps); + sum += static_cast(filterbank.values[static_cast(mel * freq_bins + bin)]) * + static_cast(stabilized); + } + values[static_cast(frame * kMels + mel)] = + static_cast(std::log(std::max(sum, static_cast(kLogClamp)))); + } + } +} + +std::vector compute_campplus_fbank(const engine::runtime::AudioBuffer & audio, int64_t & frames) { + const auto mono = mono_resampled(audio, 16000); + engine::audio::KaldiFbankOptions options; + options.sample_rate = 16000; + options.num_mels = 80; + options.frame_length_ms = 25.0F; + options.frame_shift_ms = 10.0F; + options.window_type = engine::audio::KaldiFbankWindowType::Povey; + options.lfr_m = 1; + options.lfr_n = 1; + options.preemphasis = 0.97F; + options.low_frequency = 20.0F; + options.high_frequency = 0.0F; + options.remove_dc_offset = true; + options.upscale_samples = false; + options.apply_cmvn = false; + auto fbank = engine::audio::extract_kaldi_fbank(mono, options); + if (fbank.frames <= 0 || fbank.feature_dim != 80) { + throw std::runtime_error("CosyVoice3 reference audio is too short for CAMPPlus"); + } + frames = fbank.frames; + for (int64_t dim = 0; dim < fbank.feature_dim; ++dim) { + float mean = 0.0F; + for (int64_t frame = 0; frame < fbank.frames; ++frame) { + mean += fbank.values[static_cast(frame * fbank.feature_dim + dim)]; + } + mean /= static_cast(fbank.frames); + for (int64_t frame = 0; frame < fbank.frames; ++frame) { + fbank.values[static_cast(frame * fbank.feature_dim + dim)] -= mean; + } + } + return std::move(fbank.values); +} + +} // namespace + +class CosyVoice3Frontend::Impl { +public: + Impl( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type, + size_t reference_cache_slots) + : assets_(std::move(assets)), + reference_cache_(reference_cache_slots) { + (void) graph_arena_bytes; + (void) weight_context_bytes; + if (assets_ == nullptr) { + throw std::runtime_error("CosyVoice3 frontend requires assets"); + } + engine::modules::S3TokenizerConfig speech_tokenizer_config; + speech_tokenizer_config.weight_storage_type = storage_type; + speech_tokenizer_ = std::make_unique( + engine::modules::S3TokenizerComponent::load_from_source( + assets_->speech_tokenizer_weights, + execution, + speech_tokenizer_config)); + engine::modules::CampplusEncoderConfig campplus_config; + campplus_config.feat_dim = 80; + campplus_config.embedding_size = assets_->config.speaker_dim; + campplus_config.weight_storage_type = storage_type; + campplus_config.weight_layout = engine::modules::CampplusEncoderWeightLayout::Fused; + campplus_config.normalize_partial_segment_by_full_length = true; + campplus_ = engine::modules::CampplusEncoderComponent::load_from_tensor_source( + assets_->campplus_weights, + execution.config(), + campplus_config); + } + + const CosyVoice3ReferenceFeatures & prepare_reference(const engine::runtime::AudioBuffer & audio) { + const auto key = hash_audio(audio); + const auto * cached = reference_cache_.find(key); + if (cached != nullptr) { + return *cached; + } + + const auto started = std::chrono::steady_clock::now(); + CosyVoice3ReferenceFeatures features; + auto audio16 = mono_buffer(audio, 16000); + auto token_output = speech_tokenizer_->tokenize(audio16, std::nullopt); + features.speech_tokens = std::move(token_output.tokens); + features.speech_token_count = token_output.token_count; + compute_prompt_mel(audio, features.prompt_mel, features.prompt_mel_frames); + int64_t fbank_frames = 0; + auto fbank = compute_campplus_fbank(audio, fbank_frames); + auto speaker = campplus_.embed_from_features(fbank, fbank_frames, 80); + features.speaker_embedding = std::move(speaker.embedding); + if (static_cast(features.speaker_embedding.size()) != assets_->config.speaker_dim) { + throw std::runtime_error("CosyVoice3 CAMPPlus speaker embedding size mismatch"); + } + engine::debug::timing_log_scalar("cosyvoice3.frontend.reference_ms", engine::debug::elapsed_ms(started)); + + if (reference_cache_.capacity() == 0) { + uncached_ = std::move(features); + return uncached_; + } + reference_cache_.put(key, std::move(features)); + const auto * inserted = reference_cache_.find(key); + if (inserted == nullptr) { + throw std::runtime_error("CosyVoice3 reference cache insert failed"); + } + return *inserted; + } + + void release_graphs() { + if (speech_tokenizer_ != nullptr) { + speech_tokenizer_->release_runtime_cache(); + } + campplus_.release_runtime_graph(); + } + +private: + std::shared_ptr assets_; + std::unique_ptr speech_tokenizer_; + engine::modules::CampplusEncoderComponent campplus_; + engine::runtime::CacheSlots reference_cache_; + CosyVoice3ReferenceFeatures uncached_; +}; + +CosyVoice3Frontend::CosyVoice3Frontend( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + size_t graph_arena_bytes, + size_t weight_context_bytes, + engine::assets::TensorStorageType storage_type, + size_t reference_cache_slots) + : impl_(std::make_unique( + std::move(assets), + execution, + graph_arena_bytes, + weight_context_bytes, + storage_type, + reference_cache_slots)) {} + +CosyVoice3Frontend::~CosyVoice3Frontend() = default; + +const CosyVoice3ReferenceFeatures & CosyVoice3Frontend::prepare_reference(const engine::runtime::AudioBuffer & audio) { + return impl_->prepare_reference(audio); +} + +void CosyVoice3Frontend::release_graphs() { + impl_->release_graphs(); +} + +} // namespace engine::models::cosyvoice3 diff --git a/src/models/cosyvoice3/hift.cpp b/src/models/cosyvoice3/hift.cpp new file mode 100644 index 000000000..048a54e5e --- /dev/null +++ b/src/models/cosyvoice3/hift.cpp @@ -0,0 +1,111 @@ +#include "engine/models/cosyvoice3/hift.h" + +#include "engine/framework/modules/vocoders/hift_vocoder.h" + +#include +#include + +namespace engine::models::cosyvoice3 { +namespace { + +engine::modules::HiftVocoderConfig make_hift_config(engine::assets::TensorStorageType storage_type) { + engine::modules::HiftVocoderConfig config; + config.in_channels = 80; + config.base_channels = 512; + config.nb_harmonics = 8; + config.sampling_rate = 24000; + config.nsf_alpha = 0.1F; + config.nsf_sigma = 0.003F; + config.nsf_voiced_threshold = 10.0F; + config.upsample_rates = {8, 5, 3}; + config.upsample_kernel_sizes = {16, 11, 7}; + config.istft_n_fft = 16; + config.istft_hop = 4; + config.resblock_kernel_sizes = {3, 7, 11}; + config.resblock_dilation_sizes = {{1, 3, 5}, {1, 3, 5}, {1, 3, 5}}; + config.source_resblock_kernel_sizes = {7, 7, 11}; + config.source_resblock_dilation_sizes = {{1, 3, 5}, {1, 3, 5}, {1, 3, 5}}; + config.lrelu_slope = 0.1F; + config.audio_limit = 0.99F; + config.conv_pre_kernel_size = 5; + config.causal_convolutions = true; + config.f0_num_class = 1; + config.f0_in_channels = 80; + config.f0_cond_channels = 512; + config.f0_condnet_kernel_sizes = {4, 3, 3, 3, 3}; + config.weight_storage_type = storage_type; + config.weight_layout = engine::modules::HiftVocoderWeightLayout::TorchParametrizedWeightNorm; + config.upsample_mode = engine::modules::HiftVocoderUpsampleMode::CausalConv1dNearest; + config.source_mode = engine::modules::HiftVocoderSourceMode::CausalSineGen2; + return config; +} + +} // namespace + +class CosyVoice3HiftRuntime::Impl { +public: + Impl( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + engine::assets::TensorStorageType storage_type) + : assets_(std::move(assets)) { + if (assets_ == nullptr) { + throw std::runtime_error("CosyVoice3 HiFT runtime requires assets"); + } + component_ = engine::modules::HiftVocoderComponent::load_from_tensor_source( + assets_->hift_weights, + execution.config(), + make_hift_config(storage_type)); + } + + engine::runtime::AudioBuffer synthesize( + const std::vector & mel, + int64_t frames, + uint64_t seed) { + if (frames <= 0 || static_cast(mel.size()) != frames * 80) { + throw std::runtime_error("CosyVoice3 HiFT mel shape mismatch"); + } + std::vector channel_major(mel.size()); + for (int64_t frame = 0; frame < frames; ++frame) { + for (int64_t channel = 0; channel < 80; ++channel) { + channel_major[static_cast(channel * frames + frame)] = + mel[static_cast(frame * 80 + channel)]; + } + } + auto out = component_.synthesize(channel_major, frames, seed, 0); + engine::runtime::AudioBuffer audio; + audio.sample_rate = static_cast(out.sample_rate); + audio.channels = 1; + audio.samples = std::move(out.waveform); + return audio; + } + + void release_graphs() { + component_.release_runtime_cache(); + } + +private: + std::shared_ptr assets_; + engine::modules::HiftVocoderComponent component_; +}; + +CosyVoice3HiftRuntime::CosyVoice3HiftRuntime( + std::shared_ptr assets, + engine::core::ExecutionContext & execution, + engine::assets::TensorStorageType storage_type) + : impl_(std::make_unique(std::move(assets), execution, storage_type)) {} + +CosyVoice3HiftRuntime::~CosyVoice3HiftRuntime() = default; + +engine::runtime::AudioBuffer CosyVoice3HiftRuntime::synthesize( + const std::vector & mel, + int64_t frames, + uint64_t seed) { + return impl_->synthesize(mel, frames, seed); +} + +void CosyVoice3HiftRuntime::release_graphs() { + impl_->release_graphs(); +} + +} // namespace engine::models::cosyvoice3 diff --git a/src/models/cosyvoice3/session.cpp b/src/models/cosyvoice3/session.cpp new file mode 100644 index 000000000..724f29ee7 --- /dev/null +++ b/src/models/cosyvoice3/session.cpp @@ -0,0 +1,256 @@ +#include "engine/models/cosyvoice3/session.h" + +#include "engine/framework/debug/profiler.h" +#include "engine/framework/runtime/options.h" +#include "engine/framework/runtime/spec_backed_model.h" +#include "engine/framework/text/chunking.h" +#include "engine/models/cosyvoice3/ar.h" +#include "engine/models/cosyvoice3/flow.h" +#include "engine/models/cosyvoice3/frontend.h" +#include "engine/models/cosyvoice3/hift.h" + +#include +#include +#include +#include + +namespace engine::models::cosyvoice3 { +namespace { + +constexpr const char * kFamily = "cosyvoice3"; +constexpr const char * kModelName = "CosyVoice3"; +constexpr int64_t kDefaultTextChunkSize = 600; +constexpr size_t kDefaultReferenceCacheSlots = 4; + +std::shared_ptr require_assets(std::shared_ptr assets) { + if (assets == nullptr) { + throw std::runtime_error("CosyVoice3 session requires assets"); + } + return assets; +} + +std::shared_ptr require_contract( + std::shared_ptr contract) { + if (contract == nullptr) { + throw std::runtime_error("CosyVoice3 session requires a model contract"); + } + return contract; +} + +std::string template_name(const runtime::TaskRequest & request) { + return runtime::find_option(request.options, {"template_name"}).value_or("zero_shot"); +} + +std::string reference_text(const runtime::TaskRequest & request) { + return runtime::find_option(request.options, {"reference_text"}).value_or(""); +} + +std::string instruction_text(const runtime::TaskRequest & request) { + return runtime::find_option(request.options, {"instruction"}).value_or(""); +} + +std::vector split_request(const runtime::TaskRequest & request) { + const int64_t text_chunk_size = + engine::text::parse_text_chunk_size_override(request.options).value_or(kDefaultTextChunkSize); + if (text_chunk_size <= 0) { + throw std::runtime_error("CosyVoice3 text_chunk_size must be positive"); + } + const auto text_chunk_mode = + engine::text::parse_text_chunk_mode_override(request.options).value_or(engine::text::TextChunkMode::Default); + return runtime::chunk_text_request(request, text_chunk_size, text_chunk_mode); +} + +std::unique_ptr create_cosyvoice3_session( + const runtime::TaskSpec & task, + const runtime::SessionOptions & options, + std::shared_ptr assets, + std::shared_ptr contract) { + return std::make_unique( + task, + options, + std::move(assets), + std::move(contract)); +} + +} // namespace + +CosyVoice3Session::CosyVoice3Session( + runtime::TaskSpec task, + runtime::SessionOptions options, + std::shared_ptr assets, + std::shared_ptr contract) + : runtime::RuntimeSessionBase(options), + task_(task), + assets_(require_assets(std::move(assets))), + contract_(require_contract(std::move(contract))), + tokenizer_(std::make_unique(assets_)) { + runtime::validate_spec_backed_session_options(options, *contract_, kFamily, kModelName); + if (task_.task != runtime::VoiceTaskKind::Tts && task_.task != runtime::VoiceTaskKind::VoiceCloning) { + throw std::runtime_error("CosyVoice3 supports tts and clone tasks"); + } + if (task_.mode != runtime::RunMode::Offline) { + throw std::runtime_error("CosyVoice3 supports offline sessions"); + } + using T = engine::assets::TensorStorageType; + const auto storage_type = runtime::parse_tensor_storage_option( + options.options, + "cosyvoice3.weight_type", + T::Native, + {T::Native, T::F32, T::F16, T::BF16, T::Q8_0, T::Q4_0, T::Q4_K}); + const auto graph_arena_bytes = runtime::parse_size_mb_option( + options.options, + {"cosyvoice3.graph_arena_mb"}, + 1024ull * 1024ull * 1024ull); + const auto weight_context_bytes = runtime::parse_size_mb_option( + options.options, + {"cosyvoice3.weight_context_mb"}, + 2048ull * 1024ull * 1024ull); + if (const auto mem_saver = runtime::find_option(options.options, {"cosyvoice3.mem_saver"})) { + mem_saver_ = runtime::parse_bool_option(*mem_saver, "cosyvoice3.mem_saver"); + } + const int64_t reference_cache_slots = runtime::parse_i64_option( + options.options, + {"cosyvoice3.reference_cache_slots"}) + .value_or(static_cast(kDefaultReferenceCacheSlots)); + if (reference_cache_slots < 0) { + throw std::runtime_error("cosyvoice3.reference_cache_slots must be non-negative"); + } + + frontend_ = std::make_unique( + assets_, + execution_context(), + graph_arena_bytes, + weight_context_bytes, + storage_type, + static_cast(reference_cache_slots)); + ar_ = std::make_unique( + assets_, + execution_context(), + graph_arena_bytes, + weight_context_bytes, + storage_type); + flow_ = std::make_unique( + assets_, + execution_context(), + graph_arena_bytes, + weight_context_bytes, + storage_type); + hift_ = std::make_unique( + assets_, + execution_context(), + storage_type); +} + +CosyVoice3Session::~CosyVoice3Session() = default; + +std::string CosyVoice3Session::family() const { + return kFamily; +} + +runtime::VoiceTaskKind CosyVoice3Session::task_kind() const { + return task_.task; +} + +runtime::RunMode CosyVoice3Session::run_mode() const { + return task_.mode; +} + +void CosyVoice3Session::prepare(const runtime::SessionPreparationRequest & request) { + runtime::validate_spec_backed_request_options(request.options, *contract_, kModelName); + mark_prepared(); +} + +runtime::TaskResult CosyVoice3Session::run(const runtime::TaskRequest & request) { + const auto wall_start = std::chrono::steady_clock::now(); + runtime::validate_spec_backed_request_options(request.options, *contract_, kModelName); + require_prepared("CosyVoice3 run"); + if (!request.text_input.has_value() || request.text_input->text.empty()) { + throw std::runtime_error("CosyVoice3 requires text input"); + } + if (!request.voice.has_value() || + !request.voice->speaker.has_value() || + !request.voice->speaker->audio.has_value()) { + throw std::runtime_error("CosyVoice3 requires reference audio"); + } + + runtime::AudioBuffer merged_audio; + auto chunks = split_request(request); + for (size_t index = 0; index < chunks.size(); ++index) { + const auto & chunk = chunks[index]; + const auto mode = template_name(chunk); + CosyVoice3TextTokens text_tokens; + if (mode == "zero_shot") { + text_tokens = tokenizer_->encode_zero_shot(chunk.text_input->text, reference_text(chunk)); + } else if (mode == "cross_lingual") { + text_tokens = tokenizer_->encode_cross_lingual(chunk.text_input->text); + } else if (mode == "instruct") { + text_tokens = tokenizer_->encode_instruct(chunk.text_input->text, instruction_text(chunk)); + } else { + throw std::runtime_error("unknown CosyVoice3 template_name: " + mode); + } + const auto & reference = frontend_->prepare_reference(*chunk.voice->speaker->audio); + if (mem_saver_) { + frontend_->release_graphs(); + } + CosyVoice3ArRequest ar_request; + ar_request.prompt_text_tokens = std::move(text_tokens.prompt); + ar_request.target_text_tokens = std::move(text_tokens.target); + if (mode == "zero_shot") { + ar_request.prompt_speech_tokens = reference.speech_tokens; + } + ar_request.seed = runtime::parse_u32_option(chunk.options, {"seed"}).value_or(ar_request.seed); + ar_request.top_k = runtime::parse_positive_i64_option(chunk.options, {"top_k"}, ar_request.top_k); + ar_request.min_tokens = runtime::parse_i64_option(chunk.options, {"min_tokens"}).value_or(ar_request.min_tokens); + ar_request.max_tokens = runtime::parse_i64_option(chunk.options, {"max_tokens"}).value_or(ar_request.max_tokens); + if (index > 0) { + ar_request.seed += static_cast(index); + } + CosyVoice3ArOutput ar_output = ar_->generate(ar_request); + if (mem_saver_) { + ar_->release_graphs(); + } + + CosyVoice3FlowRequest flow_request; + flow_request.speech_tokens = std::move(ar_output.speech_tokens); + flow_request.prompt_speech_tokens = reference.speech_tokens; + flow_request.prompt_mel = reference.prompt_mel; + flow_request.prompt_mel_frames = reference.prompt_mel_frames; + flow_request.speaker_embedding = reference.speaker_embedding; + flow_request.seed = ar_request.seed; + flow_request.num_inference_steps = runtime::parse_positive_i64_option( + chunk.options, + {"num_inference_steps"}, + flow_request.num_inference_steps); + CosyVoice3FlowOutput flow_output = flow_->generate(flow_request); + if (mem_saver_) { + flow_->release_graphs(); + } + runtime::append_audio_buffer( + merged_audio, + hift_->synthesize(flow_output.mel, flow_output.frames, flow_request.seed)); + if (mem_saver_) { + hift_->release_graphs(); + } + } + + if (mem_saver_) { + frontend_->release_graphs(); + ar_->release_graphs(); + flow_->release_graphs(); + hift_->release_graphs(); + } + runtime::TaskResult result; + result.audio_output = std::move(merged_audio); + engine::debug::timing_log_scalar("session.wall_ms", engine::debug::elapsed_ms(wall_start)); + return result; +} + +std::shared_ptr make_cosyvoice3_loader() { + runtime::SpecBackedVoiceModelConfig config; + config.family = kFamily; + config.load_assets = load_cosyvoice3_assets; + config.create_session = create_cosyvoice3_session; + return runtime::make_spec_backed_voice_loader(std::move(config)); +} + +} // namespace engine::models::cosyvoice3 diff --git a/src/models/cosyvoice3/tokenizer_text.cpp b/src/models/cosyvoice3/tokenizer_text.cpp new file mode 100644 index 000000000..cd8398cb0 --- /dev/null +++ b/src/models/cosyvoice3/tokenizer_text.cpp @@ -0,0 +1,381 @@ +#include "engine/models/cosyvoice3/tokenizer_text.h" + +#include "engine/framework/tokenizers/llama_bpe.h" + +#include +#include +#include +#include + +namespace engine::models::cosyvoice3 { +namespace { + +constexpr int32_t kEndOfPromptId = 151646; +constexpr std::string_view kEndOfPromptText = "<|endofprompt|>"; +constexpr std::string_view kDefaultPromptPrefix = "You are a helpful assistant."; + +std::vector cosyvoice3_special_tokens() { + std::vector tokens; + tokens.reserve(280); + tokens.emplace_back("<|im_start|>", 151644); + tokens.emplace_back("<|im_end|>", 151645); + tokens.emplace_back("<|endofprompt|>", 151646); + tokens.emplace_back("[breath]", 151647); + tokens.emplace_back("", 151648); + tokens.emplace_back("", 151649); + tokens.emplace_back("[noise]", 151650); + tokens.emplace_back("[laughter]", 151651); + tokens.emplace_back("[cough]", 151652); + tokens.emplace_back("[clucking]", 151653); + tokens.emplace_back("[accent]", 151654); + tokens.emplace_back("[quick_breath]", 151655); + tokens.emplace_back("", 151656); + tokens.emplace_back("", 151657); + tokens.emplace_back("[hissing]", 151658); + tokens.emplace_back("[sigh]", 151659); + tokens.emplace_back("[vocalized-noise]", 151660); + tokens.emplace_back("[lipsmack]", 151661); + tokens.emplace_back("[mn]", 151662); + tokens.emplace_back("<|endofsystem|>", 151663); + tokens.emplace_back("[AA]", 151664); + tokens.emplace_back("[AA0]", 151665); + tokens.emplace_back("[AA1]", 151666); + tokens.emplace_back("[AA2]", 151667); + tokens.emplace_back("[AE]", 151668); + tokens.emplace_back("[AE0]", 151669); + tokens.emplace_back("[AE1]", 151670); + tokens.emplace_back("[AE2]", 151671); + tokens.emplace_back("[AH]", 151672); + tokens.emplace_back("[AH0]", 151673); + tokens.emplace_back("[AH1]", 151674); + tokens.emplace_back("[AH2]", 151675); + tokens.emplace_back("[AO]", 151676); + tokens.emplace_back("[AO0]", 151677); + tokens.emplace_back("[AO1]", 151678); + tokens.emplace_back("[AO2]", 151679); + tokens.emplace_back("[AW]", 151680); + tokens.emplace_back("[AW0]", 151681); + tokens.emplace_back("[AW1]", 151682); + tokens.emplace_back("[AW2]", 151683); + tokens.emplace_back("[AY]", 151684); + tokens.emplace_back("[AY0]", 151685); + tokens.emplace_back("[AY1]", 151686); + tokens.emplace_back("[AY2]", 151687); + tokens.emplace_back("[B]", 151688); + tokens.emplace_back("[CH]", 151689); + tokens.emplace_back("[D]", 151690); + tokens.emplace_back("[DH]", 151691); + tokens.emplace_back("[EH]", 151692); + tokens.emplace_back("[EH0]", 151693); + tokens.emplace_back("[EH1]", 151694); + tokens.emplace_back("[EH2]", 151695); + tokens.emplace_back("[ER]", 151696); + tokens.emplace_back("[ER0]", 151697); + tokens.emplace_back("[ER1]", 151698); + tokens.emplace_back("[ER2]", 151699); + tokens.emplace_back("[EY]", 151700); + tokens.emplace_back("[EY0]", 151701); + tokens.emplace_back("[EY1]", 151702); + tokens.emplace_back("[EY2]", 151703); + tokens.emplace_back("[F]", 151704); + tokens.emplace_back("[G]", 151705); + tokens.emplace_back("[HH]", 151706); + tokens.emplace_back("[IH]", 151707); + tokens.emplace_back("[IH0]", 151708); + tokens.emplace_back("[IH1]", 151709); + tokens.emplace_back("[IH2]", 151710); + tokens.emplace_back("[IY]", 151711); + tokens.emplace_back("[IY0]", 151712); + tokens.emplace_back("[IY1]", 151713); + tokens.emplace_back("[IY2]", 151714); + tokens.emplace_back("[JH]", 151715); + tokens.emplace_back("[K]", 151716); + tokens.emplace_back("[L]", 151717); + tokens.emplace_back("[M]", 151718); + tokens.emplace_back("[N]", 151719); + tokens.emplace_back("[NG]", 151720); + tokens.emplace_back("[OW]", 151721); + tokens.emplace_back("[OW0]", 151722); + tokens.emplace_back("[OW1]", 151723); + tokens.emplace_back("[OW2]", 151724); + tokens.emplace_back("[OY]", 151725); + tokens.emplace_back("[OY0]", 151726); + tokens.emplace_back("[OY1]", 151727); + tokens.emplace_back("[OY2]", 151728); + tokens.emplace_back("[P]", 151729); + tokens.emplace_back("[R]", 151730); + tokens.emplace_back("[S]", 151731); + tokens.emplace_back("[SH]", 151732); + tokens.emplace_back("[T]", 151733); + tokens.emplace_back("[TH]", 151734); + tokens.emplace_back("[UH]", 151735); + tokens.emplace_back("[UH0]", 151736); + tokens.emplace_back("[UH1]", 151737); + tokens.emplace_back("[UH2]", 151738); + tokens.emplace_back("[UW]", 151739); + tokens.emplace_back("[UW0]", 151740); + tokens.emplace_back("[UW1]", 151741); + tokens.emplace_back("[UW2]", 151742); + tokens.emplace_back("[V]", 151743); + tokens.emplace_back("[W]", 151744); + tokens.emplace_back("[Y]", 151745); + tokens.emplace_back("[Z]", 151746); + tokens.emplace_back("[ZH]", 151747); + tokens.emplace_back("[a]", 151748); + tokens.emplace_back("[ai]", 151749); + tokens.emplace_back("[an]", 151750); + tokens.emplace_back("[ang]", 151751); + tokens.emplace_back("[ao]", 151752); + tokens.emplace_back("[b]", 151753); + tokens.emplace_back("[c]", 151754); + tokens.emplace_back("[ch]", 151755); + tokens.emplace_back("[d]", 151756); + tokens.emplace_back("[e]", 151757); + tokens.emplace_back("[ei]", 151758); + tokens.emplace_back("[en]", 151759); + tokens.emplace_back("[eng]", 151760); + tokens.emplace_back("[f]", 151761); + tokens.emplace_back("[g]", 151762); + tokens.emplace_back("[h]", 151763); + tokens.emplace_back("[i]", 151764); + tokens.emplace_back("[ian]", 151765); + tokens.emplace_back("[in]", 151766); + tokens.emplace_back("[ing]", 151767); + tokens.emplace_back("[iu]", 151768); + tokens.emplace_back("[ià]", 151769); + tokens.emplace_back("[iàn]", 151770); + tokens.emplace_back("[iàng]", 151771); + tokens.emplace_back("[iào]", 151772); + tokens.emplace_back("[iá]", 151773); + tokens.emplace_back("[ián]", 151774); + tokens.emplace_back("[iáng]", 151775); + tokens.emplace_back("[iáo]", 151776); + tokens.emplace_back("[iè]", 151777); + tokens.emplace_back("[ié]", 151778); + tokens.emplace_back("[iòng]", 151779); + tokens.emplace_back("[ióng]", 151780); + tokens.emplace_back("[iù]", 151781); + tokens.emplace_back("[iú]", 151782); + tokens.emplace_back("[iā]", 151783); + tokens.emplace_back("[iān]", 151784); + tokens.emplace_back("[iāng]", 151785); + tokens.emplace_back("[iāo]", 151786); + tokens.emplace_back("[iē]", 151787); + tokens.emplace_back("[iě]", 151788); + tokens.emplace_back("[iōng]", 151789); + tokens.emplace_back("[iū]", 151790); + tokens.emplace_back("[iǎ]", 151791); + tokens.emplace_back("[iǎn]", 151792); + tokens.emplace_back("[iǎng]", 151793); + tokens.emplace_back("[iǎo]", 151794); + tokens.emplace_back("[iǒng]", 151795); + tokens.emplace_back("[iǔ]", 151796); + tokens.emplace_back("[j]", 151797); + tokens.emplace_back("[k]", 151798); + tokens.emplace_back("[l]", 151799); + tokens.emplace_back("[m]", 151800); + tokens.emplace_back("[n]", 151801); + tokens.emplace_back("[o]", 151802); + tokens.emplace_back("[ong]", 151803); + tokens.emplace_back("[ou]", 151804); + tokens.emplace_back("[p]", 151805); + tokens.emplace_back("[q]", 151806); + tokens.emplace_back("[r]", 151807); + tokens.emplace_back("[s]", 151808); + tokens.emplace_back("[sh]", 151809); + tokens.emplace_back("[t]", 151810); + tokens.emplace_back("[u]", 151811); + tokens.emplace_back("[uang]", 151812); + tokens.emplace_back("[ue]", 151813); + tokens.emplace_back("[un]", 151814); + tokens.emplace_back("[uo]", 151815); + tokens.emplace_back("[uà]", 151816); + tokens.emplace_back("[uài]", 151817); + tokens.emplace_back("[uàn]", 151818); + tokens.emplace_back("[uàng]", 151819); + tokens.emplace_back("[uá]", 151820); + tokens.emplace_back("[uái]", 151821); + tokens.emplace_back("[uán]", 151822); + tokens.emplace_back("[uáng]", 151823); + tokens.emplace_back("[uè]", 151824); + tokens.emplace_back("[ué]", 151825); + tokens.emplace_back("[uì]", 151826); + tokens.emplace_back("[uí]", 151827); + tokens.emplace_back("[uò]", 151828); + tokens.emplace_back("[uó]", 151829); + tokens.emplace_back("[uā]", 151830); + tokens.emplace_back("[uāi]", 151831); + tokens.emplace_back("[uān]", 151832); + tokens.emplace_back("[uāng]", 151833); + tokens.emplace_back("[uē]", 151834); + tokens.emplace_back("[uě]", 151835); + tokens.emplace_back("[uī]", 151836); + tokens.emplace_back("[uō]", 151837); + tokens.emplace_back("[uǎ]", 151838); + tokens.emplace_back("[uǎi]", 151839); + tokens.emplace_back("[uǎn]", 151840); + tokens.emplace_back("[uǎng]", 151841); + tokens.emplace_back("[uǐ]", 151842); + tokens.emplace_back("[uǒ]", 151843); + tokens.emplace_back("[vè]", 151844); + tokens.emplace_back("[w]", 151845); + tokens.emplace_back("[x]", 151846); + tokens.emplace_back("[y]", 151847); + tokens.emplace_back("[z]", 151848); + tokens.emplace_back("[zh]", 151849); + tokens.emplace_back("[à]", 151850); + tokens.emplace_back("[ài]", 151851); + tokens.emplace_back("[àn]", 151852); + tokens.emplace_back("[àng]", 151853); + tokens.emplace_back("[ào]", 151854); + tokens.emplace_back("[á]", 151855); + tokens.emplace_back("[ái]", 151856); + tokens.emplace_back("[án]", 151857); + tokens.emplace_back("[áng]", 151858); + tokens.emplace_back("[áo]", 151859); + tokens.emplace_back("[è]", 151860); + tokens.emplace_back("[èi]", 151861); + tokens.emplace_back("[èn]", 151862); + tokens.emplace_back("[èng]", 151863); + tokens.emplace_back("[èr]", 151864); + tokens.emplace_back("[é]", 151865); + tokens.emplace_back("[éi]", 151866); + tokens.emplace_back("[én]", 151867); + tokens.emplace_back("[éng]", 151868); + tokens.emplace_back("[ér]", 151869); + tokens.emplace_back("[ì]", 151870); + tokens.emplace_back("[ìn]", 151871); + tokens.emplace_back("[ìng]", 151872); + tokens.emplace_back("[í]", 151873); + tokens.emplace_back("[ín]", 151874); + tokens.emplace_back("[íng]", 151875); + tokens.emplace_back("[ò]", 151876); + tokens.emplace_back("[òng]", 151877); + tokens.emplace_back("[òu]", 151878); + tokens.emplace_back("[ó]", 151879); + tokens.emplace_back("[óng]", 151880); + tokens.emplace_back("[óu]", 151881); + tokens.emplace_back("[ù]", 151882); + tokens.emplace_back("[ùn]", 151883); + tokens.emplace_back("[ú]", 151884); + tokens.emplace_back("[ún]", 151885); + tokens.emplace_back("[ā]", 151886); + tokens.emplace_back("[āi]", 151887); + tokens.emplace_back("[ān]", 151888); + tokens.emplace_back("[āng]", 151889); + tokens.emplace_back("[āo]", 151890); + tokens.emplace_back("[ē]", 151891); + tokens.emplace_back("[ēi]", 151892); + tokens.emplace_back("[ēn]", 151893); + tokens.emplace_back("[ēng]", 151894); + tokens.emplace_back("[ě]", 151895); + tokens.emplace_back("[ěi]", 151896); + tokens.emplace_back("[ěn]", 151897); + tokens.emplace_back("[ěng]", 151898); + tokens.emplace_back("[ěr]", 151899); + tokens.emplace_back("[ī]", 151900); + tokens.emplace_back("[īn]", 151901); + tokens.emplace_back("[īng]", 151902); + tokens.emplace_back("[ō]", 151903); + tokens.emplace_back("[ōng]", 151904); + tokens.emplace_back("[ōu]", 151905); + tokens.emplace_back("[ū]", 151906); + tokens.emplace_back("[ūn]", 151907); + tokens.emplace_back("[ǎ]", 151908); + tokens.emplace_back("[ǎi]", 151909); + tokens.emplace_back("[ǎn]", 151910); + tokens.emplace_back("[ǎng]", 151911); + tokens.emplace_back("[ǎo]", 151912); + tokens.emplace_back("[ǐ]", 151913); + tokens.emplace_back("[ǐn]", 151914); + tokens.emplace_back("[ǐng]", 151915); + tokens.emplace_back("[ǒ]", 151916); + tokens.emplace_back("[ǒng]", 151917); + tokens.emplace_back("[ǒu]", 151918); + tokens.emplace_back("[ǔ]", 151919); + tokens.emplace_back("[ǔn]", 151920); + tokens.emplace_back("[ǘ]", 151921); + tokens.emplace_back("[ǚ]", 151922); + tokens.emplace_back("[ǜ]", 151923); + return tokens; +} + +void require_end_of_prompt(const std::vector & tokens, const char * context) { + for (const int32_t token : tokens) { + if (token == kEndOfPromptId) { + return; + } + } + throw std::runtime_error(std::string("CosyVoice3 ") + context + " must contain <|endofprompt|>"); +} + +std::string prompt_with_boundary(std::string_view text) { + std::string out(text); + if (out.find(kEndOfPromptText) == std::string::npos) { + out = std::string(kDefaultPromptPrefix) + std::string(kEndOfPromptText) + out; + } + return out; +} + +std::string instruction_with_boundary(std::string_view instruction) { + std::string out(instruction); + if (out.find(kEndOfPromptText) == std::string::npos) { + out = std::string(kDefaultPromptPrefix) + " " + out + std::string(kEndOfPromptText); + } + return out; +} + +} // namespace + +class CosyVoice3TextTokenizer::Impl { +public: + explicit Impl(const CosyVoice3Assets & assets) { + engine::tokenizers::LlamaBpeTokenizerSpec spec; + spec.vocab_path = assets.resources.require_file("vocab_json"); + spec.merges_path = assets.resources.require_file("merges_txt"); + spec.tokenizer_config_path = assets.resources.require_file("tokenizer_config"); + spec.pre_type = engine::tokenizers::LlamaBpePreTokenizer::Qwen2; + spec.additional_special_tokens = cosyvoice3_special_tokens(); + tokenizer = engine::tokenizers::load_llama_bpe_tokenizer(spec); + } + + std::shared_ptr tokenizer; +}; + +CosyVoice3TextTokenizer::CosyVoice3TextTokenizer(std::shared_ptr assets) { + if (assets == nullptr) { + throw std::runtime_error("CosyVoice3 tokenizer requires assets"); + } + impl_ = std::make_shared(*assets); +} + +CosyVoice3TextTokenizer::~CosyVoice3TextTokenizer() = default; + +CosyVoice3TextTokens CosyVoice3TextTokenizer::encode_zero_shot( + std::string_view text, + std::string_view prompt_text) const { + CosyVoice3TextTokens out; + out.prompt = impl_->tokenizer->encode(prompt_with_boundary(prompt_text), true); + out.target = impl_->tokenizer->encode(std::string(text), true); + require_end_of_prompt(out.prompt, "zero-shot prompt"); + return out; +} + +CosyVoice3TextTokens CosyVoice3TextTokenizer::encode_cross_lingual(std::string_view text) const { + CosyVoice3TextTokens out; + out.target = impl_->tokenizer->encode(prompt_with_boundary(text), true); + require_end_of_prompt(out.target, "cross-lingual text"); + return out; +} + +CosyVoice3TextTokens CosyVoice3TextTokenizer::encode_instruct( + std::string_view text, + std::string_view instruction) const { + CosyVoice3TextTokens out; + out.prompt = impl_->tokenizer->encode(instruction_with_boundary(instruction), true); + out.target = impl_->tokenizer->encode(std::string(text), true); + require_end_of_prompt(out.prompt, "instruction"); + return out; +} + +} // namespace engine::models::cosyvoice3 diff --git a/src/models/higgs_audio_tts/ar.cpp b/src/models/higgs_audio_tts/ar.cpp index e7646dda9..cd4166f0c 100644 --- a/src/models/higgs_audio_tts/ar.cpp +++ b/src/models/higgs_audio_tts/ar.cpp @@ -39,7 +39,9 @@ struct GgmlContextDeleter { } }; -modules::QwenDecoderStackConfig make_higgs_qwen_stack_config(const HiggsTextConfig & config) { +modules::QwenDecoderStackConfig make_higgs_qwen_stack_config( + const HiggsTextConfig & config, + bool allow_flash_attention = true) { modules::QwenDecoderStackConfig out; out.hidden_size = config.hidden_size; out.num_attention_heads = config.num_attention_heads; @@ -53,9 +55,17 @@ modules::QwenDecoderStackConfig make_higgs_qwen_stack_config(const HiggsTextConf out.projection_precision = GGML_PREC_DEFAULT; out.qkv_layout = modules::QwenDecoderQKVLayout::Separate; out.use_qk_norm = true; - out.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; - out.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; - out.runtime.attention.prefix_mode = modules::QwenDecoderPrefixAttentionMode::FlashWithPrefix; + // Eager graph for GPUs without a flash kernel (e.g. sm70). + out.runtime.attention.allow_flash_attention = allow_flash_attention; + if (allow_flash_attention) { + out.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::FlashGroupedViewKV; + out.runtime.attention.prefix_mode = modules::QwenDecoderPrefixAttentionMode::FlashWithPrefix; + } else { + out.runtime.attention.prefill_mode = modules::QwenDecoderAttentionMode::ManualRepeat; + out.runtime.attention.static_mode = modules::QwenDecoderAttentionMode::ManualRepeat; + out.runtime.attention.prefix_mode = modules::QwenDecoderPrefixAttentionMode::Exact; + } out.runtime.static_cache.update_mode = modules::QwenDecoderStaticCacheUpdateMode::DirectSetRows; out.runtime.static_cache.set_rows_mode = modules::QwenDecoderStaticCacheSetRowsMode::BackendViewOptimized; out.runtime.mlp.mode = modules::QwenDecoderMLPMode::PackedGateUp; @@ -64,8 +74,8 @@ modules::QwenDecoderStackConfig make_higgs_qwen_stack_config(const HiggsTextConf class HiggsQwenDecoderComponent { public: - HiggsQwenDecoderComponent(const HiggsTextConfig & config, bool packed_qkv) - : stack_config_(make_higgs_qwen_stack_config(config)), + HiggsQwenDecoderComponent(const HiggsTextConfig & config, bool packed_qkv, bool allow_flash_attention = true) + : stack_config_(make_higgs_qwen_stack_config(config, allow_flash_attention)), layer_config_(modules::qwen_decoder_layer_config_from_stack(stack_config_)), layer_module_([&] { layer_config_.qkv_layout = packed_qkv @@ -376,12 +386,18 @@ HiggsARRuntime::HiggsARRuntime( std::shared_ptr assets, core::ExecutionContext & execution, size_t weight_context_bytes, - assets::TensorStorageType weight_storage_type) + assets::TensorStorageType weight_storage_type, + core::AttentionPreference attention_preference) : assets_(std::move(assets)), backend_(execution.backend()), backend_type_(execution.backend_type()), device_(execution.config().device), - threads_(std::max(1, execution.config().threads)) { + threads_(std::max(1, execution.config().threads)), + allow_flash_attention_(core::resolve_flash_attention( + execution.backend(), + this->assets_->config.text.head_dim, + attention_preference)) { + engine::debug::trace_log_scalar("higgs_audio_tts.attention.allow_flash", allow_flash_attention_); if (assets_ == nullptr) { throw std::runtime_error("Higgs TTS AR runtime requires assets"); } @@ -404,6 +420,10 @@ ggml_backend_t HiggsARRuntime::backend() const noexcept { return backend_; } +bool HiggsARRuntime::allow_flash_attention() const noexcept { + return allow_flash_attention_; +} + core::BackendType HiggsARRuntime::backend_type() const noexcept { return backend_type_; } @@ -600,7 +620,8 @@ struct HiggsARDecodeGraph::Impl { GGML_TYPE_F16); graph = ggml_new_graph_custom(ctx.get(), 65536, false); - const HiggsQwenDecoderComponent decoder(config.text, tensor_weights.packed_qkv); + const HiggsQwenDecoderComponent decoder( + config.text, tensor_weights.packed_qkv, runtime->allow_flash_attention()); for (size_t layer_index = 0; layer_index < tensor_weights.decoder.layers.size(); ++layer_index) { auto out = decoder.build_decode_layer( build_ctx, @@ -845,7 +866,8 @@ struct HiggsARPrefillGraph::Impl { graph = ggml_new_graph_custom(ctx.get(), 262144, false); keys.reserve(tensor_weights.decoder.layers.size()); values.reserve(tensor_weights.decoder.layers.size()); - const HiggsQwenDecoderComponent decoder(config.text, tensor_weights.packed_qkv); + const HiggsQwenDecoderComponent decoder( + config.text, tensor_weights.packed_qkv, runtime->allow_flash_attention()); for (size_t layer_index = 0; layer_index < tensor_weights.decoder.layers.size(); ++layer_index) { std::optional prefix_key; std::optional prefix_value; @@ -1034,7 +1056,8 @@ struct HiggsARPrefillGraph::Impl { attention_mask, core::TensorShape::from_dims({1, 1, steps, steps}), GGML_TYPE_F16); - const HiggsQwenDecoderComponent decoder(config.text, runtime.weights().packed_qkv); + const HiggsQwenDecoderComponent decoder( + config.text, runtime.weights().packed_qkv, runtime.allow_flash_attention()); auto out = decoder.build_prefill_layer( build_ctx, x, diff --git a/src/models/higgs_audio_tts/codec.cpp b/src/models/higgs_audio_tts/codec.cpp index 33204ad08..157ac9eaf 100644 --- a/src/models/higgs_audio_tts/codec.cpp +++ b/src/models/higgs_audio_tts/codec.cpp @@ -64,6 +64,12 @@ constexpr int64_t kCodecPadSamples = kCodecHopLength / 2; constexpr int64_t kCodecDecodeWindowFrames = 128; constexpr int64_t kCodecDecodeContextFrames = 32; constexpr int64_t kCodecFullDecodeMaxFrames = 512; +// The decoder is a non-causal conv stack, so the last frames of a stream see zero padding +// on their right instead of real context and decode as a rising hiss over the final +// ~300 ms (worst on short utterances). Repeating the last frame past the end gives those +// frames context; the extra audio is trimmed again. Eight frames is enough — 16 and 32 +// produce identical samples. +constexpr int64_t kCodecTailContextFrames = 8; constexpr int64_t kResidualDilations[] = {1, 3, 9}; struct GgmlContextDeleter { @@ -1549,6 +1555,40 @@ HiggsCodecDecodeOutput HiggsCodecRuntime::decode_codes(const std::vector(codes.size()) != frames * codebooks) { throw std::runtime_error("Higgs TTS codec decode code count mismatch"); } + if (kCodecTailContextFrames <= 0) { + return decode_codes_impl(codes, frames, codebooks); + } + + std::vector padded_codes; + padded_codes.reserve(static_cast((frames + kCodecTailContextFrames) * codebooks)); + padded_codes.insert(padded_codes.end(), codes.begin(), codes.end()); + const auto last_frame_begin = codes.end() - static_cast(codebooks); + for (int64_t i = 0; i < kCodecTailContextFrames; ++i) { + padded_codes.insert(padded_codes.end(), last_frame_begin, codes.end()); + } + + auto out = decode_codes_impl(padded_codes, frames + kCodecTailContextFrames, codebooks); + const int64_t keep_samples = frames * kCodecHopLength; + if (keep_samples <= 0 || keep_samples > static_cast(out.values.size())) { + throw std::runtime_error("Higgs TTS codec tail-context decode produced too few samples"); + } + out.values.resize(static_cast(keep_samples)); + out.samples = keep_samples; + return out; +} + +HiggsCodecDecodeOutput HiggsCodecRuntime::decode_codes_impl(const std::vector & codes, + int64_t frames, + int64_t codebooks) const { + if (frames <= 0) { + throw std::runtime_error("Higgs TTS codec decode requires positive frame count"); + } + if (codebooks != kCodecCodebooks) { + throw std::runtime_error("Higgs TTS codec decode requires exactly 8 codebooks"); + } + if (static_cast(codes.size()) != frames * codebooks) { + throw std::runtime_error("Higgs TTS codec decode code count mismatch"); + } engine::debug::trace_log_scalar("higgs_audio_tts.codec.decode.input_frames", frames); engine::debug::trace_log_scalar("higgs_audio_tts.codec.decode.input_codebooks", codebooks); engine::debug::trace_log_i32("higgs_audio_tts.codec.decode.input_codes", diff --git a/src/models/higgs_audio_tts/loader.cpp b/src/models/higgs_audio_tts/loader.cpp index efc600c72..5d77f9ccc 100644 --- a/src/models/higgs_audio_tts/loader.cpp +++ b/src/models/higgs_audio_tts/loader.cpp @@ -56,6 +56,7 @@ runtime::ModelCliInterface cli(const HiggsAssets &) { {"higgs_audio_tts.codec_decode_graph_arena_mb", "n", "Codec decode graph arena size."}, {"higgs_audio_tts.codec_encode_graph_arena_mb", "n", "Codec encode graph arena size."}, {"higgs_audio_tts.reference_cache_slots", "n", "Encoded reference-audio cache slots; default 1."}, + {"higgs_audio_tts.attention", "auto|flash|eager", "Attention lowering; auto probes the backend and falls back to eager on GPUs without a flash kernel (e.g. sm70); default auto."}, }; return out; } diff --git a/src/models/higgs_audio_tts/session.cpp b/src/models/higgs_audio_tts/session.cpp index 0a5d47790..ca3110b38 100644 --- a/src/models/higgs_audio_tts/session.cpp +++ b/src/models/higgs_audio_tts/session.cpp @@ -1,5 +1,6 @@ #include "engine/models/higgs_audio_tts/session.h" +#include "engine/framework/core/attention_fallback.h" #include "engine/framework/debug/profiler.h" #include "engine/framework/debug/trace.h" #include "engine/framework/runtime/options.h" @@ -10,6 +11,7 @@ #include #include #include +#include #include namespace engine::models::higgs_audio_tts { @@ -50,6 +52,23 @@ uint64_t hash_audio_samples(const runtime::AudioBuffer & audio) { return hash; } +core::AttentionPreference resolve_attention_preference(const runtime::SessionOptions & options) { + if (const auto value = runtime::find_option(options.options, {"higgs_audio_tts.attention"})) { + return core::parse_attention_preference(*value, "higgs_audio_tts.attention"); + } + return core::AttentionPreference::Auto; +} + +void trace_attention_preference(core::AttentionPreference preference) { + const char * name = "auto"; + if (preference == core::AttentionPreference::Flash) { + name = "flash"; + } else if (preference == core::AttentionPreference::Eager) { + name = "eager"; + } + debug::trace_log_scalar("higgs_audio_tts.attention.preference", std::string_view(name)); +} + std::size_t resolve_reference_cache_slots(const runtime::SessionOptions & options) { const int64_t slots = runtime::parse_i64_option( options.options, @@ -169,6 +188,7 @@ HiggsTTSSession::HiggsTTSSession( key != "higgs_audio_tts.codec_decode_graph_arena_mb" && key != "higgs_audio_tts.codec_encode_graph_arena_mb" && key != "higgs_audio_tts.reference_cache_slots" && + key != "higgs_audio_tts.attention" && key != "higgs_audio_tts.weight_type" && key != "higgs_audio_tts.ar_weight_type" && key != "higgs_audio_tts.codec_weight_type") { @@ -176,11 +196,14 @@ HiggsTTSSession::HiggsTTSSession( } } + const auto attention_preference = resolve_attention_preference(options); + trace_attention_preference(attention_preference); ar_ = std::make_shared( assets_, execution_context(), ar_weight_context_bytes_, - ar_weight_storage_type_); + ar_weight_storage_type_, + attention_preference); codec_ = std::make_shared( assets_, execution_context(), diff --git a/src/models/pocket_tts/acoustic_model.cpp b/src/models/pocket_tts/acoustic_model.cpp index 062da351e..8fcf8ffb4 100644 --- a/src/models/pocket_tts/acoustic_model.cpp +++ b/src/models/pocket_tts/acoustic_model.cpp @@ -37,6 +37,51 @@ std::vector sample_normal(std::mt19937 & rng, int64_t count, float stddev return values; } +void validate_generation_inputs( + const FlowLMConfig & flow_config, + const std::vector & text_embeddings, + const AcousticGenerationConfig & config) { + if (config.max_steps <= 0) { + throw std::runtime_error("PocketTTS acoustic max_steps must be positive"); + } + if (config.temperature <= 0.0F) { + throw std::runtime_error("PocketTTS acoustic temperature must be positive"); + } + if (!config.noise_schedule.empty() && config.noise_schedule.size() % static_cast(flow_config.latent_size) != 0) { + throw std::runtime_error("PocketTTS acoustic noise_schedule must be a multiple of latent_size"); + } + if (!config.noise_schedule.empty()) { + const size_t scheduled_steps = + config.noise_schedule.size() / static_cast(flow_config.latent_size); + if (scheduled_steps < static_cast(config.max_steps)) { + throw std::runtime_error("PocketTTS acoustic noise_schedule must provide at least max_steps latent noise vectors"); + } + } + if (text_embeddings.size() % static_cast(flow_config.hidden_size) != 0) { + throw std::runtime_error("PocketTTS acoustic text embeddings must be a multiple of hidden_size"); + } +} + +std::vector sample_noise_for_step(const FlowLMConfig & flow_config, AcousticStreamState & state) { + if (!state.config.noise_schedule.empty()) { + const size_t start = static_cast(state.step) * static_cast(flow_config.latent_size); + return std::vector( + state.config.noise_schedule.begin() + static_cast(start), + state.config.noise_schedule.begin() + static_cast(start + static_cast(flow_config.latent_size))); + } + if (state.config.noise_clamp > 0.0F) { + return sample_trunc_normal( + state.rng, + flow_config.latent_size, + std::sqrt(state.config.temperature), + state.config.noise_clamp); + } + return sample_normal( + state.rng, + flow_config.latent_size, + std::sqrt(state.config.temperature)); +} + } // namespace AcousticModel::AcousticModel(FlowLMConfig config) : flow_lm_(std::move(config)) {} @@ -133,80 +178,15 @@ AcousticModelResult AcousticModel::generate( const std::vector & text_embeddings, const FlowLMState & initial_state, const AcousticGenerationConfig & config) const { - (void) manifest; - (void) weights; - if (config.max_steps <= 0) { - throw std::runtime_error("PocketTTS acoustic max_steps must be positive"); - } - if (config.temperature <= 0.0F) { - throw std::runtime_error("PocketTTS acoustic temperature must be positive"); - } - if (!config.noise_schedule.empty() && config.noise_schedule.size() % static_cast(flow_lm_.config().latent_size) != 0) { - throw std::runtime_error("PocketTTS acoustic noise_schedule must be a multiple of latent_size"); - } - if (!config.noise_schedule.empty()) { - const size_t scheduled_steps = - config.noise_schedule.size() / static_cast(flow_lm_.config().latent_size); - if (scheduled_steps < static_cast(config.max_steps)) { - throw std::runtime_error("PocketTTS acoustic noise_schedule must provide at least max_steps latent noise vectors"); - } - } - if (text_embeddings.size() % static_cast(flow_lm_.config().hidden_size) != 0) { - throw std::runtime_error("PocketTTS acoustic text embeddings must be a multiple of hidden_size"); - } - - const int64_t prompt_steps = runtime.prompt_steps; - if (runtime.step_runtime == nullptr) { - throw std::runtime_error("PocketTTS acoustic runtime is not initialized"); - } AcousticModelResult result; const double generate_ms = engine::debug::measure_ms([&]() { - flow_lm_.apply_prompt(*runtime.step_runtime, text_embeddings, prompt_steps, initial_state); - - std::vector current_input( - static_cast(flow_lm_.config().latent_size), - std::numeric_limits::quiet_NaN()); + auto state = start_stream(runtime, manifest, weights, text_embeddings, initial_state, config); result.latents.reserve(static_cast(config.max_steps) * static_cast(flow_lm_.config().latent_size)); result.eos_logits.reserve(static_cast(config.max_steps)); - std::mt19937 rng(config.seed); - int eos_step = -1; - for (int step = 0; step < config.max_steps; ++step) { - std::vector noise; - if (!config.noise_schedule.empty()) { - const size_t start = static_cast(step) * static_cast(flow_lm_.config().latent_size); - noise.assign( - config.noise_schedule.begin() + static_cast(start), - config.noise_schedule.begin() + static_cast(start + static_cast(flow_lm_.config().latent_size))); - } else if (config.noise_clamp > 0.0F) { - noise = sample_trunc_normal( - rng, - flow_lm_.config().latent_size, - std::sqrt(config.temperature), - config.noise_clamp); - } else { - noise = sample_normal( - rng, - flow_lm_.config().latent_size, - std::sqrt(config.temperature)); - } - - const auto step_result = flow_lm_.run_step_in_place( - *runtime.step_runtime, - current_input, - noise); - - const bool is_eos = step_result.eos_logit > config.eos_threshold; - if (is_eos && eos_step < 0) { - eos_step = step; - } - if (eos_step >= 0 && step >= eos_step + config.frames_after_eos) { - break; - } - - result.eos_logits.push_back(step_result.eos_logit); - result.latents.insert(result.latents.end(), step_result.next_latent.begin(), step_result.next_latent.end()); - current_input = step_result.next_latent; + while (auto step_result = next_stream_step(state)) { + result.eos_logits.push_back(step_result->eos_logit); + result.latents.insert(result.latents.end(), step_result->next_latent.begin(), step_result->next_latent.end()); result.generated_steps += 1; } const auto flow_timing = flow_lm_.runtime_timing(*runtime.step_runtime); @@ -233,6 +213,64 @@ AcousticModelResult AcousticModel::generate( return result; } +AcousticStreamState AcousticModel::start_stream( + const AcousticPreparedRuntime & runtime, + const models::pocket_tts::PocketTTSAssets & manifest, + const models::pocket_tts::PocketTTSBackendWeights & weights, + const std::vector & text_embeddings, + const FlowLMState & initial_state, + const AcousticGenerationConfig & config) const { + (void) manifest; + (void) weights; + validate_generation_inputs(flow_lm_.config(), text_embeddings, config); + if (runtime.step_runtime == nullptr) { + throw std::runtime_error("PocketTTS acoustic runtime is not initialized"); + } + flow_lm_.apply_prompt(*runtime.step_runtime, text_embeddings, runtime.prompt_steps, initial_state); + + AcousticStreamState state; + state.runtime = runtime; + state.config = config; + state.current_input.assign( + static_cast(flow_lm_.config().latent_size), + std::numeric_limits::quiet_NaN()); + state.rng.seed(config.seed); + return state; +} + +std::optional AcousticModel::next_stream_step(AcousticStreamState & state) const { + if (state.done) { + return std::nullopt; + } + if (state.runtime.step_runtime == nullptr) { + throw std::runtime_error("PocketTTS acoustic runtime is not initialized"); + } + if (state.step >= state.config.max_steps) { + state.done = true; + return std::nullopt; + } + + auto noise = sample_noise_for_step(flow_lm_.config(), state); + auto step_result = flow_lm_.run_step_in_place( + *state.runtime.step_runtime, + state.current_input, + noise); + + const bool is_eos = step_result.eos_logit > state.config.eos_threshold; + if (is_eos && state.eos_step < 0) { + state.eos_step = state.step; + } + if (state.eos_step >= 0 && state.step >= state.eos_step + state.config.frames_after_eos) { + state.done = true; + return std::nullopt; + } + + state.current_input = step_result.next_latent; + ++state.step; + ++state.generated_steps; + return step_result; +} + void AcousticModel::clear_runtime_cache() const noexcept { runtime_cache_ = {}; } diff --git a/src/models/pocket_tts/audio_decoder.cpp b/src/models/pocket_tts/audio_decoder.cpp index 691a45cd0..a55d892bb 100644 --- a/src/models/pocket_tts/audio_decoder.cpp +++ b/src/models/pocket_tts/audio_decoder.cpp @@ -66,6 +66,39 @@ std::vector AudioDecoder::decode( use_full_sequence_path); } +void AudioDecoder::reset_streaming_state() const { + decoder_.reset_streaming_state(); +} + +std::vector AudioDecoder::decode_streaming_step( + ggml_backend_t backend, + int threads, + const models::pocket_tts::PocketTTSAssets & manifest, + const models::pocket_tts::PocketTTSBackendWeights & weights, + const std::vector & normalized_latent, + size_t conv_graph_context_bytes, + size_t transformer_graph_context_bytes, + size_t tail_graph_context_bytes) const { + if (normalized_latent.size() != static_cast(decoder_.config().latent_size)) { + throw std::runtime_error("PocketTTS streaming audio decoder expects one latent step"); + } + const auto & emb_mean = weights.host.emb_mean; + const auto & emb_std = weights.host.emb_std; + if (emb_mean.size() != emb_std.size() || emb_mean.size() != static_cast(decoder_.config().latent_size)) { + throw std::runtime_error("PocketTTS latent normalization stats must match Mimi latent_size"); + } + auto denormalized = denormalize_latents(normalized_latent, emb_mean, emb_std); + return decoder_.decode_streaming_step( + backend, + threads, + manifest, + weights, + denormalized, + conv_graph_context_bytes, + transformer_graph_context_bytes, + tail_graph_context_bytes); +} + void AudioDecoder::clear_runtime_cache() const noexcept { decoder_.clear_runtime_cache(); } diff --git a/src/models/pocket_tts/loader.cpp b/src/models/pocket_tts/loader.cpp index 0098a1d35..3b769c003 100644 --- a/src/models/pocket_tts/loader.cpp +++ b/src/models/pocket_tts/loader.cpp @@ -22,7 +22,7 @@ std::string requested_language(const runtime::ModelLoadRequest & request) { runtime::CapabilitySet capabilities(const PocketTTSAssets & assets) { runtime::CapabilitySet out; out.supported_tasks = { - {runtime::VoiceTaskKind::Tts, {runtime::RunMode::Offline}}, + {runtime::VoiceTaskKind::Tts, {runtime::RunMode::Offline, runtime::RunMode::Streaming}}, }; out.languages = {assets.language}; out.supports_speaker_reference = true; @@ -63,7 +63,7 @@ class PocketTTSLoader final : public runtime::IVoiceModelLoader { runtime::CapabilitySet advertised_capabilities() const override { runtime::CapabilitySet out; out.supported_tasks = { - {runtime::VoiceTaskKind::Tts, {runtime::RunMode::Offline}}, + {runtime::VoiceTaskKind::Tts, {runtime::RunMode::Offline, runtime::RunMode::Streaming}}, }; out.supports_speaker_reference = true; out.supports_style_condition = true; diff --git a/src/models/pocket_tts/mimi_decoder.cpp b/src/models/pocket_tts/mimi_decoder.cpp index 0ad1b52cf..6e1cec483 100644 --- a/src/models/pocket_tts/mimi_decoder.cpp +++ b/src/models/pocket_tts/mimi_decoder.cpp @@ -882,6 +882,21 @@ class MimiTransformerRuntime { } core::set_backend_threads(backend_, threads_); graph_ = ggml_new_graph_custom(ggml_ctx_, 32768, false); + for (const auto & mask : attention_masks_) { + ggml_build_forward_expand(graph_, mask.tensor); + } + for (const auto & tensor : work_prefix_keys_) { + ggml_build_forward_expand(graph_, tensor.tensor); + } + for (const auto & tensor : work_prefix_values_) { + ggml_build_forward_expand(graph_, tensor.tensor); + } + for (const auto & tensor : zero_prefix_keys_) { + ggml_build_forward_expand(graph_, tensor.tensor); + } + for (const auto & tensor : zero_prefix_values_) { + ggml_build_forward_expand(graph_, tensor.tensor); + } ggml_build_forward_expand(graph_, output_bct_.tensor); galloc_ = ggml_gallocr_new(ggml_backend_get_default_buffer_type(backend_)); if (galloc_ == nullptr || @@ -890,6 +905,20 @@ class MimiTransformerRuntime { release_partial_graph_runtime(galloc_, params_buffer_, ggml_ctx_); throw std::runtime_error("Mimi transformer graph allocation failed"); } + for (size_t keep = 0; keep < carry_key_sources_.size(); ++keep) { + for (size_t layer = 0; layer < carry_key_sources_[keep].size(); ++layer) { + if (carry_key_sources_[keep][layer] != nullptr) { + ggml_backend_view_init(carry_key_sources_[keep][layer]); + ggml_backend_view_init(carry_value_sources_[keep][layer]); + ggml_backend_view_init(carry_key_destinations_[keep][layer]); + ggml_backend_view_init(carry_value_destinations_[keep][layer]); + } + ggml_backend_view_init(append_key_sources_[keep][layer]); + ggml_backend_view_init(append_value_sources_[keep][layer]); + ggml_backend_view_init(append_key_destinations_[keep][layer]); + ggml_backend_view_init(append_value_destinations_[keep][layer]); + } + } core::write_tensor_f32(input_bct_, std::vector(static_cast(config_.hidden_size * frames_), 0.0F)); core::write_tensor_i32(positions_, std::vector(static_cast(frames_), 0)); core::write_tensor_f32(attention_mask_, std::vector(static_cast(frames_ * (cache_steps_ + frames_)), -INFINITY)); @@ -1340,6 +1369,14 @@ struct MimiDecoder::RuntimeCache { int64_t full_output_runtime_frames = -1; }; +struct MimiDecoder::StreamingState { + DecoderState decoder_state; + bool transformer_sequence_initialized = false; + + explicit StreamingState(const MimiDecoderConfig & config) + : decoder_state(make_decoder_state(config)) {} +}; + MimiDecoder::MimiDecoder(MimiDecoderConfig config) : config_(std::move(config)) {} MimiDecoder::~MimiDecoder() { @@ -2059,8 +2096,373 @@ std::vector MimiDecoder::decode( return audio; } +void MimiDecoder::reset_streaming_state() const { + streaming_state_ = std::make_unique(config_); +} + +std::vector MimiDecoder::decode_streaming_step( + ggml_backend_t backend, + int threads, + const models::pocket_tts::PocketTTSAssets & manifest, + const models::pocket_tts::PocketTTSBackendWeights & weights, + const std::vector & latent, + size_t conv_graph_context_bytes, + size_t transformer_graph_context_bytes, + size_t tail_graph_context_bytes) const { + const auto decode_started = std::chrono::steady_clock::now(); + if (latent.size() != static_cast(config_.latent_size)) { + throw std::runtime_error("PocketTTS Mimi streaming decoder expects one latent step"); + } + auto & runtime_cache = runtime_cache_; + if (!runtime_cache || runtime_cache->manifest != &manifest || runtime_cache->backend != backend || runtime_cache->threads != threads + || runtime_cache->conv_graph_context_bytes != conv_graph_context_bytes + || runtime_cache->transformer_graph_context_bytes != transformer_graph_context_bytes + || runtime_cache->tail_graph_context_bytes != tail_graph_context_bytes) { + runtime_cache = std::make_unique(); + runtime_cache->manifest = &manifest; + runtime_cache->backend = backend; + runtime_cache->threads = threads; + runtime_cache->conv_graph_context_bytes = conv_graph_context_bytes; + runtime_cache->transformer_graph_context_bytes = transformer_graph_context_bytes; + runtime_cache->tail_graph_context_bytes = tail_graph_context_bytes; + } + if (!streaming_state_) { + reset_streaming_state(); + } + auto & cache = *runtime_cache; + auto & state = streaming_state_->decoder_state; + const auto & decoder_weights = weights.mimi_decoder; + const auto & quantizer_weight = decoder_weights.quantizer_output_proj_weight; + const auto & encoder_upsample_weight = decoder_weights.encoder_upsample_weight; + const auto & input_projection = decoder_weights.input_projection; + const auto & stage0_upsample = decoder_weights.stage0_upsample; + const auto & stage1_upsample = decoder_weights.stage1_upsample; + const auto & stage2_upsample = decoder_weights.stage2_upsample; + const auto & output_projection = decoder_weights.output_projection; + auto & quantizer_runtime = cache.quantizer_runtime; + auto & transformer_runtime = cache.transformer_runtime; + auto & input_projection_runtime = cache.input_projection_runtime; + auto & stage0_upsample_runtime = cache.stage0_upsample_runtime; + auto & stage0_conv1_runtime = cache.stage0_conv1_runtime; + auto & stage0_conv2_runtime = cache.stage0_conv2_runtime; + auto & stage1_upsample_runtime = cache.stage1_upsample_runtime; + auto & stage1_conv1_runtime = cache.stage1_conv1_runtime; + auto & stage1_conv2_runtime = cache.stage1_conv2_runtime; + auto & stage2_upsample_runtime = cache.stage2_upsample_runtime; + auto & stage2_conv1_runtime = cache.stage2_conv1_runtime; + auto & stage2_conv2_runtime = cache.stage2_conv2_runtime; + auto & output_projection_runtime = cache.output_projection_runtime; + auto & resblock_conv1_frames = cache.resblock_conv1_frames; + auto & resblock_conv2_frames = cache.resblock_conv2_frames; + + auto run_resblock = [&](DecoderState & state_ref, + const std::vector & input_bct, + int64_t channels, + int64_t hidden_channels, + int stage_index, + std::unique_ptr & conv1_runtime, + std::unique_ptr & conv2_runtime, + const PocketTTSBackendResidualBlockWeights & block_weights) { + const int64_t frames_bct = static_cast(input_bct.size()) / channels; + const int64_t conv1_needed_frames = + frames_bct + state_ref.stage_residual_convs[static_cast(stage_index)][0].history_frames; + if (!conv1_runtime || resblock_conv1_frames[static_cast(stage_index)] != conv1_needed_frames) { + conv1_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + block_weights.conv1.weight, + block_weights.conv1.bias, + channels, + conv1_needed_frames, + hidden_channels, + 3, + 1, + 1); + resblock_conv1_frames[static_cast(stage_index)] = conv1_needed_frames; + } + const int64_t conv2_needed_frames = + frames_bct + state_ref.stage_residual_convs[static_cast(stage_index)][1].history_frames; + if (!conv2_runtime || resblock_conv2_frames[static_cast(stage_index)] != conv2_needed_frames) { + conv2_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + block_weights.conv2.weight, + block_weights.conv2.bias, + hidden_channels, + conv2_needed_frames, + channels, + 1, + 1, + 1); + resblock_conv2_frames[static_cast(stage_index)] = conv2_needed_frames; + } + auto x = elu(input_bct); + x = run_streaming_conv1d_step( + *conv1_runtime, + x, + channels, + frames_bct, + hidden_channels, + 3, + 1, + 1, + modules::StreamingPadMode::Constant, + state_ref.stage_residual_convs[static_cast(stage_index)][0]); + x = elu(x); + x = run_streaming_conv1d_step( + *conv2_runtime, + x, + hidden_channels, + frames_bct, + channels, + 1, + 1, + 1, + modules::StreamingPadMode::Constant, + state_ref.stage_residual_convs[static_cast(stage_index)][1]); + return add_bct(input_bct, x); + }; + + if (!quantizer_runtime || cache.quantizer_steps != 1) { + quantizer_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + quantizer_weight, + std::nullopt, + config_.latent_size, + 1, + config_.hidden_size, + 1, + 1, + 1); + cache.quantizer_steps = 1; + } + if (!cache.encoder_rate_upsample_runtime || cache.encoder_rate_upsample_steps != 1) { + cache.encoder_rate_upsample_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + encoder_upsample_weight, + 1, + config_.hidden_size, + config_.encoder_upsample_stride * 2, + static_cast(config_.encoder_upsample_stride)); + cache.encoder_rate_upsample_steps = 1; + } + auto & encoder_rate_upsample_runtime = *cache.encoder_rate_upsample_runtime; + + auto x = quantizer_runtime->run(latent); + x = run_depthwise_convtranspose1d_step( + encoder_rate_upsample_runtime, + x, + config_.hidden_size, + 1, + config_.encoder_upsample_stride * 2, + static_cast(config_.encoder_upsample_stride), + state.encoder_rate_upsample); + const int64_t encoder_frames = static_cast(x.size()) / config_.hidden_size; + if (!transformer_runtime || cache.transformer_frames != encoder_frames) { + transformer_runtime = std::make_unique( + backend, + threads, + transformer_graph_context_bytes, + weights, + config_, + encoder_frames, + 250); + cache.transformer_frames = encoder_frames; + } + if (!streaming_state_->transformer_sequence_initialized) { + transformer_runtime->reset_sequence(state.transformer.current_end); + streaming_state_->transformer_sequence_initialized = true; + } + x = transformer_runtime->run(x).output_bct; + const int64_t needed_input_frames = encoder_frames + state.input_projection.history_frames; + if (!input_projection_runtime || cache.input_projection_frames != needed_input_frames) { + input_projection_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + input_projection.weight, + input_projection.bias, + config_.hidden_size, + needed_input_frames, + config_.hidden_size, + 7, + 1, + 1); + cache.input_projection_frames = needed_input_frames; + } + x = run_streaming_conv1d_step( + *input_projection_runtime, + x, + config_.hidden_size, + encoder_frames, + config_.hidden_size, + 7, + 1, + 1, + modules::StreamingPadMode::Constant, + state.input_projection); + + x = elu(x); + if (!stage0_upsample_runtime || cache.stage0_upsample_frames != encoder_frames) { + stage0_upsample_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + stage0_upsample.weight, + stage0_upsample.bias, + config_.hidden_size, + encoder_frames, + 256, + 12, + 6); + cache.stage0_upsample_frames = encoder_frames; + } + x = run_streaming_convtranspose1d_step( + *stage0_upsample_runtime, + x, + config_.hidden_size, + encoder_frames, + 256, + 12, + 6, + decoder_weights.stage0_upsample_bias_values, + state.stage_upsamples[0]); + x = run_resblock( + state, + x, + 256, + 128, + 0, + stage0_conv1_runtime, + stage0_conv2_runtime, + decoder_weights.stage0_block); + + const int64_t stage1_frames = static_cast(x.size()) / 256; + x = elu(x); + if (!stage1_upsample_runtime || cache.stage1_upsample_frames != stage1_frames) { + stage1_upsample_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + stage1_upsample.weight, + stage1_upsample.bias, + 256, + stage1_frames, + 128, + 10, + 5); + cache.stage1_upsample_frames = stage1_frames; + } + x = run_streaming_convtranspose1d_step( + *stage1_upsample_runtime, + x, + 256, + stage1_frames, + 128, + 10, + 5, + decoder_weights.stage1_upsample_bias_values, + state.stage_upsamples[1]); + x = run_resblock( + state, + x, + 128, + 64, + 1, + stage1_conv1_runtime, + stage1_conv2_runtime, + decoder_weights.stage1_block); + + const int64_t stage2_frames = static_cast(x.size()) / 128; + x = elu(x); + if (!stage2_upsample_runtime || cache.stage2_upsample_frames != stage2_frames) { + stage2_upsample_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + stage2_upsample.weight, + stage2_upsample.bias, + 128, + stage2_frames, + 64, + 8, + 4); + cache.stage2_upsample_frames = stage2_frames; + } + x = run_streaming_convtranspose1d_step( + *stage2_upsample_runtime, + x, + 128, + stage2_frames, + 64, + 8, + 4, + decoder_weights.stage2_upsample_bias_values, + state.stage_upsamples[2]); + x = run_resblock( + state, + x, + 64, + 32, + 2, + stage2_conv1_runtime, + stage2_conv2_runtime, + decoder_weights.stage2_block); + + const int64_t output_frames = static_cast(x.size()) / 64; + x = elu(x); + const int64_t needed_output_frames = output_frames + state.output_projection.history_frames; + if (!output_projection_runtime || cache.output_projection_frames != needed_output_frames) { + output_projection_runtime = std::make_unique( + backend, + weights.backend_type, + threads, + conv_graph_context_bytes, + output_projection.weight, + output_projection.bias, + 64, + needed_output_frames, + 1, + 3, + 1, + 1); + cache.output_projection_frames = needed_output_frames; + } + x = run_streaming_conv1d_step( + *output_projection_runtime, + x, + 64, + output_frames, + 1, + 3, + 1, + 1, + modules::StreamingPadMode::Constant, + state.output_projection); + + engine::debug::timing_log_scalar( + "pocket_tts.mimi.streaming_decoder_step_ms", + engine::debug::elapsed_ms(decode_started)); + return x; +} + void MimiDecoder::clear_runtime_cache() const noexcept { runtime_cache_.reset(); + streaming_state_.reset(); } } // namespace engine::models::pocket_tts diff --git a/src/models/pocket_tts/session.cpp b/src/models/pocket_tts/session.cpp index 4a7bc2288..5f4fe48e0 100644 --- a/src/models/pocket_tts/session.cpp +++ b/src/models/pocket_tts/session.cpp @@ -474,8 +474,8 @@ PocketTTSSession::PocketTTSSession( if (task_.task != runtime::VoiceTaskKind::Tts) { throw std::runtime_error("PocketTTS only supports VoiceTaskKind::Tts"); } - if (task_.mode != runtime::RunMode::Offline) { - throw std::runtime_error("PocketTTS only supports offline mode"); + if (task_.mode != runtime::RunMode::Offline && task_.mode != runtime::RunMode::Streaming) { + throw std::runtime_error("PocketTTS only supports offline and streaming mode"); } if (graph_capacity_.prompt_mode == runtime::GraphCapacityMode::Unsupported || graph_capacity_.generation_mode == runtime::GraphCapacityMode::Unsupported) { @@ -761,6 +761,128 @@ void PocketTTSSession::prepare(const runtime::SessionPreparationRequest & reques runtime::TaskResult PocketTTSSession::run(const runtime::TaskRequest & request) { require_prepared("PocketTTS run()"); audio_decoder_.clear_runtime_cache(); + const GenerationRequest generation_request = effective_request_for_run(request); + const GenerationResult generated = generate(generation_request); + runtime::TaskResult result; + result.audio_output = runtime::AudioBuffer{ + generated.sample_rate, + 1, + generated.audio, + }; + return result; +} + +runtime::StreamingPolicy PocketTTSSession::streaming_policy() const { + runtime::StreamingPolicy policy; + policy.input = runtime::StreamingInputKind::None; + policy.output = runtime::StreamingOutputKind::PullEvents; + return policy; +} + +void PocketTTSSession::start_stream(const runtime::TaskRequest & request) { + require_prepared("PocketTTS streaming"); + if (task_.mode != runtime::RunMode::Streaming) { + throw std::runtime_error("PocketTTS start_stream requires a streaming session"); + } + reset(); + stream_request_ = effective_request_for_run(request); + validate_generation_request(stream_request_); + const int64_t streaming_chunk_size = + engine::text::parse_text_chunk_size_override(request.options).value_or(stream_request_.max_tokens); + stream_text_chunks_ = engine::text::split_text_chunks(stream_request_.text, streaming_chunk_size); + if (stream_text_chunks_.empty()) { + throw std::runtime_error("PocketTTS streaming text chunking produced no segments"); + } + const auto voice_plan = resolve_voice_conditioning_plan(model_dir_, stream_request_); + stream_voice_state_ = resolve_prepared_voice_state(voice_plan); + stream_merged_audio_ = runtime::AudioBuffer{manifest_->model_config.sample_rate, 1, {}}; + audio_decoder_.reset_streaming_state(); + stream_started_at_ = std::chrono::steady_clock::now(); + stream_started_ = true; + engine::debug::trace_log_scalar("pocket_tts.streaming.text_chunk_size", streaming_chunk_size); + engine::debug::trace_log_scalar("pocket_tts.streaming.text_chunk_count", static_cast(stream_text_chunks_.size())); +} + +std::optional PocketTTSSession::next_stream_event() { + if (!stream_started_) { + throw std::runtime_error("PocketTTS streaming has not been started"); + } + while (true) { + if (!stream_acoustic_state_.has_value()) { + if (!start_next_stream_text_chunk()) { + return std::nullopt; + } + } + auto acoustic_step = acoustic_model_.next_stream_step(*stream_acoustic_state_); + if (!acoustic_step.has_value()) { + stream_acoustic_state_.reset(); + continue; + } + auto audio = audio_decoder_.decode_streaming_step( + execution_context().backend(), + options().backend.threads, + *manifest_, + *weights_, + acoustic_step->next_latent, + graph_capacity_.mimi_conv_graph_context_bytes, + graph_capacity_.mimi_transformer_graph_context_bytes, + graph_capacity_.mimi_tail_graph_context_bytes); + runtime::AudioBuffer chunk{ + manifest_->model_config.sample_rate, + 1, + std::move(audio), + }; + runtime::append_audio_buffer(stream_merged_audio_, chunk); + + runtime::StreamEvent event; + event.named_audio_outputs.push_back({ + "chunk_" + std::to_string(stream_audio_chunk_index_++), + std::move(chunk), + {}, + }); + return event; + } +} + +void PocketTTSSession::set_stream_event_sink(runtime::StreamEventCallback sink) { + stream_event_sink_ = std::move(sink); +} + +runtime::TaskResult PocketTTSSession::finish_stream() { + if (!stream_started_) { + throw std::runtime_error("PocketTTS streaming has not been started"); + } + while (next_stream_event().has_value()) { + } + runtime::TaskResult result; + result.audio_output = std::move(stream_merged_audio_); + engine::debug::timing_log_scalar("session.wall_ms", engine::debug::elapsed_ms(stream_started_at_)); + reset(); + return result; +} + +void PocketTTSSession::reset() { + stream_request_ = {}; + stream_voice_state_ = {}; + stream_text_chunks_.clear(); + stream_text_chunk_index_ = 0; + stream_acoustic_state_.reset(); + audio_decoder_.reset_streaming_state(); + stream_merged_audio_ = {}; + stream_audio_chunk_index_ = 0; + stream_started_ = false; +} + +runtime::StreamEvent PocketTTSSession::process_audio_chunk(const runtime::AudioChunk & chunk) { + (void) chunk; + throw std::runtime_error("PocketTTS streaming does not accept audio chunks"); +} + +runtime::TaskResult PocketTTSSession::finalize() { + return finish_stream(); +} + +GenerationRequest PocketTTSSession::effective_request_for_run(const runtime::TaskRequest & request) const { runtime::TaskRequest effective_request = request; effective_request.voice.reset(); GenerationRequest generation_request = @@ -774,14 +896,44 @@ runtime::TaskResult PocketTTSSession::run(const runtime::TaskRequest & request) generation_request.noise_schedule = prepared_session_request_.noise_schedule; generation_request.noise_schedule_path = prepared_session_request_.noise_schedule_path; generation_request.voice = prepared_session_request_.voice; - const GenerationResult generated = generate(generation_request); - runtime::TaskResult result; - result.audio_output = runtime::AudioBuffer{ - generated.sample_rate, - 1, - generated.audio, - }; - return result; + return generation_request; +} + +bool PocketTTSSession::start_next_stream_text_chunk() { + if (stream_text_chunk_index_ >= stream_text_chunks_.size()) { + return false; + } + const auto & chunk = stream_text_chunks_[stream_text_chunk_index_++]; + const TextConditioningResult text_state = text_conditioner_.prepare(*manifest_, weights_->host, chunk); + const AcousticGenerationConfig acoustic_config = resolve_acoustic_generation_config( + *manifest_, + text_state, + stream_request_, + acoustic_model_.config().latent_size); + const int64_t prompt_steps = static_cast( + text_state.text_embeddings.size() / static_cast(acoustic_model_.config().hidden_size)); + const AcousticCapacitySelection capacities = select_acoustic_capacities(prompt_steps, acoustic_config.max_steps); + AcousticPreparedRuntime acoustic_runtime = acoustic_model_.prepare_runtime( + execution_context().backend(), + options().backend.threads, + *manifest_, + *weights_, + text_state.text_embeddings, + stream_voice_state_, + acoustic_config, + capacities.prompt_capacity, + stream_voice_state_.current_end, + capacities.generation_capacity, + graph_capacity_.flow_weights_view_context_bytes, + graph_capacity_.flow_step_graph_context_bytes); + stream_acoustic_state_ = acoustic_model_.start_stream( + acoustic_runtime, + *manifest_, + *weights_, + text_state.text_embeddings, + stream_voice_state_, + acoustic_config); + return true; } void PocketTTSSession::prepare_generation(const GenerationRequest & request) { diff --git a/src/models/vevo2/session.cpp b/src/models/vevo2/session.cpp index 9ed46404b..a778353aa 100644 --- a/src/models/vevo2/session.cpp +++ b/src/models/vevo2/session.cpp @@ -1,6 +1,7 @@ #include "engine/models/vevo2/session.h" #include "engine/framework/audio/conversion.h" +#include "engine/framework/audio/chunking.h" #include "engine/framework/audio/dsp.h" #include "engine/framework/audio/resampling.h" #include "engine/framework/audio/waveform_ops.h" @@ -28,6 +29,8 @@ namespace { constexpr size_t kMaxAudioCacheEntries = 8; constexpr int64_t kDefaultTextChunkSize = 128; +constexpr float kDefaultAudioChunkOverlapSec = 1.0F; +constexpr float kPi = 3.141592653589793238462643383279502884F; std::shared_ptr require_assets(std::shared_ptr assets) { if (assets == nullptr) { @@ -208,6 +211,93 @@ int64_t frame_count_24k(const runtime::AudioBuffer & audio) { 24000).size()) / 480; } +uint32_t vevo2_chunk_seed(uint32_t seed, int64_t chunk_index) { + uint32_t value = seed + 0x9e3779b9u * static_cast(chunk_index + 1); + value ^= value >> 16; + value *= 0x85ebca6bu; + value ^= value >> 13; + value *= 0xc2b2ae35u; + value ^= value >> 16; + return value; +} + +std::vector plan_source_audio_chunks( + const runtime::AudioBuffer & audio, + float chunk_duration_sec, + float overlap_sec) { + if (audio.sample_rate <= 0 || audio.channels <= 0) { + throw std::runtime_error("Vevo2 audio chunking requires valid source audio"); + } + if (chunk_duration_sec <= 0.0F) { + return {{0, static_cast(audio.samples.size() / static_cast(audio.channels))}}; + } + if (overlap_sec < 0.0F) { + throw std::runtime_error("Vevo2 cross_fade_duration_sec must be non-negative"); + } + const int64_t frames = static_cast(audio.samples.size() / static_cast(audio.channels)); + const int64_t chunk_frames = static_cast( + std::llround(static_cast(chunk_duration_sec) * static_cast(audio.sample_rate))); + const int64_t overlap_frames = static_cast( + std::llround(static_cast(overlap_sec) * static_cast(audio.sample_rate))); + if (chunk_frames <= 0) { + throw std::runtime_error("Vevo2 audio_chunk_duration_sec must be positive"); + } + if (overlap_frames >= chunk_frames) { + throw std::runtime_error("Vevo2 cross_fade_duration_sec must be smaller than audio_chunk_duration_sec"); + } + if (frames <= chunk_frames) { + return {{0, frames}}; + } + + std::vector chunks; + const int64_t hop_frames = chunk_frames - overlap_frames; + for (int64_t start = 0; start < frames;) { + const int64_t end = std::min(frames, start + chunk_frames); + chunks.push_back({start, end}); + if (end == frames) { + break; + } + start += hop_frames; + } + return chunks; +} + +void append_cross_faded_audio( + runtime::AudioBuffer & merged, + const runtime::AudioBuffer & chunk, + int64_t fade_frames) { + if (fade_frames <= 0 || merged.samples.empty()) { + runtime::append_audio_buffer(merged, chunk); + return; + } + if (merged.sample_rate != chunk.sample_rate || merged.channels != chunk.channels || chunk.channels <= 0) { + throw std::runtime_error("Vevo2 audio chunk merge requires matching audio formats"); + } + const int64_t channels = chunk.channels; + const int64_t merged_frames = static_cast(merged.samples.size()) / channels; + const int64_t chunk_frames = static_cast(chunk.samples.size()) / channels; + const int64_t effective = std::min(fade_frames, std::min(merged_frames, chunk_frames)); + if (effective <= 1) { + runtime::append_audio_buffer(merged, chunk); + return; + } + const int64_t merged_tail = merged_frames - effective; + for (int64_t frame = 0; frame < effective; ++frame) { + const float alpha = static_cast(frame) / static_cast(effective - 1); + const float fade_out = std::cos(alpha * kPi * 0.5F); + const float fade_in = std::sin(alpha * kPi * 0.5F); + for (int64_t channel = 0; channel < channels; ++channel) { + const size_t dst = static_cast((merged_tail + frame) * channels + channel); + const size_t src = static_cast(frame * channels + channel); + merged.samples[dst] = merged.samples[dst] * fade_out + chunk.samples[src] * fade_in; + } + } + merged.samples.insert( + merged.samples.end(), + chunk.samples.begin() + static_cast(effective * channels), + chunk.samples.end()); +} + uint64_t hash_audio_buffer(const runtime::AudioBuffer & audio) { uint64_t hash = 1469598103934665603ull; auto mix = [&hash](const void * data, size_t bytes) { @@ -722,6 +812,15 @@ runtime::TaskResult Vevo2Session::run(const runtime::TaskRequest & request) { double fm_ms = 0.0; double vocoder_ms = 0.0; const auto text_chunk_size_override = engine::text::parse_text_chunk_size_override(request.options); + const auto audio_chunk_duration_override = engine::audio::parse_audio_chunk_seconds_override(request.options); + const auto audio_chunk_mode = engine::audio::parse_audio_chunk_mode(request.options); + const bool audio_chunking_enabled = + audio_chunk_mode != engine::audio::AudioChunkMode::None && + audio_chunk_duration_override.has_value() && + *audio_chunk_duration_override > 0.0F; + const float audio_chunk_duration_sec = audio_chunk_duration_override.value_or(0.0F); + const float audio_chunk_overlap_sec = runtime::parse_float_option(request.options, {"cross_fade_duration_sec"}) + .value_or(kDefaultAudioChunkOverlapSec); auto vevo2_request = make_request(request); std::vector chunk_requests; if (vevo2_request.path == Vevo2InferencePath::TextProsodyToTargetVoice) { @@ -746,7 +845,25 @@ runtime::TaskResult Vevo2Session::run(const runtime::TaskRequest & request) { } } } else { - chunk_requests.push_back(std::move(vevo2_request)); + const auto spans = audio_chunking_enabled + ? plan_source_audio_chunks(*vevo2_request.refs.source_audio, audio_chunk_duration_sec, audio_chunk_overlap_sec) + : std::vector{{0, static_cast( + vevo2_request.refs.source_audio->samples.size() / + static_cast(vevo2_request.refs.source_audio->channels))}}; + engine::debug::trace_log_scalar("vevo2.audio_chunk_duration_sec", audio_chunk_duration_sec); + engine::debug::trace_log_scalar("vevo2.audio_chunk_overlap_sec", audio_chunking_enabled ? audio_chunk_overlap_sec : 0.0F); + engine::debug::trace_log_scalar("vevo2.audio_chunk_count", static_cast(spans.size())); + chunk_requests.reserve(spans.size()); + auto source_audio = std::move(*vevo2_request.refs.source_audio); + vevo2_request.refs.source_audio.reset(); + for (size_t index = 0; index < spans.size(); ++index) { + Vevo2Request chunk_request = vevo2_request; + chunk_request.refs.source_audio = engine::audio::slice_audio_buffer(source_audio, spans[index]); + if (spans.size() > 1) { + chunk_request.generation.seed = vevo2_chunk_seed(vevo2_request.generation.seed, static_cast(index)); + } + chunk_requests.push_back(std::move(chunk_request)); + } } runtime::TaskResult result; @@ -861,6 +978,10 @@ runtime::TaskResult Vevo2Session::run(const runtime::TaskRequest & request) { if (!have_audio_output) { merged_audio = chunk_audio; have_audio_output = true; + } else if (audio_chunking_enabled && chunk_request.path == Vevo2InferencePath::SourceAudioToTargetVoice) { + const int64_t fade_frames = static_cast( + std::llround(static_cast(audio_chunk_overlap_sec) * static_cast(chunk_audio.sample_rate))); + append_cross_faded_audio(merged_audio, chunk_audio, fade_frames); } else { runtime::append_audio_buffer(merged_audio, chunk_audio); } diff --git a/src/models/voxcpm2/loader.cpp b/src/models/voxcpm2/loader.cpp index 889bd81ab..89026395a 100644 --- a/src/models/voxcpm2/loader.cpp +++ b/src/models/voxcpm2/loader.cpp @@ -33,6 +33,8 @@ runtime::ModelCliInterface cli(const VoxCPM2Assets &) { out.request_options = { {"text_chunk_mode", "default|tag_aware|japanese|endline", "Text chunking mode; default tag_aware."}, + {"voxcpm2.chunk_strategy", "continuation|stateless", + "Long-form chunk generation strategy; default continuation."}, }; out.session_options = { {"voxcpm2.mem_saver", "true|false", diff --git a/src/models/voxcpm2/session.cpp b/src/models/voxcpm2/session.cpp index 771881a80..aabd875a1 100644 --- a/src/models/voxcpm2/session.cpp +++ b/src/models/voxcpm2/session.cpp @@ -23,6 +23,36 @@ namespace { using Clock = std::chrono::steady_clock; constexpr int64_t kDefaultTextChunkSize = 2048; +enum class ChunkStrategy { + Continuation, + Stateless, +}; + +ChunkStrategy parse_chunk_strategy( + const std::unordered_map &options) { + const auto value = + runtime::find_option(options, {"voxcpm2.chunk_strategy", "chunk_strategy"}) + .value_or("continuation"); + if (value == "continuation") { + return ChunkStrategy::Continuation; + } + if (value == "stateless") { + return ChunkStrategy::Stateless; + } + throw std::runtime_error( + "VoxCPM2 chunk_strategy must be continuation or stateless"); +} + +const char *chunk_strategy_name(ChunkStrategy strategy) { + switch (strategy) { + case ChunkStrategy::Continuation: + return "continuation"; + case ChunkStrategy::Stateless: + return "stateless"; + } + return "unknown"; +} + std::shared_ptr require_assets(std::shared_ptr assets) { if (assets == nullptr) { @@ -311,6 +341,7 @@ runtime::TaskResult VoxCPM2SessionBase::run_offline_request(const runtime::TaskR .value_or(engine::text::TextChunkMode::TagAware); const auto chunk_requests = chunk_voxcpm2_request(request, text_chunk_size, text_chunk_mode); + const auto chunk_strategy = parse_chunk_strategy(request.options); const auto generation_options = generation_options_from_request(request); const auto prompt_text = runtime::find_option(request.options, {"voxcpm2.prompt_text", @@ -355,7 +386,8 @@ runtime::TaskResult VoxCPM2SessionBase::run_offline_request(const runtime::TaskR decoder_ms += engine::debug::elapsed_ms(decoder_start, Clock::now()); runtime::append_audio_buffer(merged_audio, audio); - if (chunk_requests.size() > 1 && generated.generated_patches > 0) { + if (chunk_strategy == ChunkStrategy::Continuation && + chunk_requests.size() > 1 && generated.generated_patches > 0) { VoxCPM2EncodedPrompt next_prompt; if (prompt != nullptr) { next_prompt.reference_features = prompt->reference_features; @@ -376,6 +408,8 @@ runtime::TaskResult VoxCPM2SessionBase::run_offline_request(const runtime::TaskR engine::text::text_chunk_mode_name(text_chunk_mode)); debug::trace_log_scalar("voxcpm2.text_chunk_count", static_cast(chunk_requests.size())); + debug::trace_log_scalar("voxcpm2.chunk_strategy", + std::string_view(chunk_strategy_name(chunk_strategy))); debug::timing_log_scalar("voxcpm2.generator_ms", generator_ms); debug::timing_log_scalar("voxcpm2.audiovae_decoder_ms", decoder_ms); debug::timing_log_scalar("session.wall_ms", diff --git a/tests/fixtures/native_model_manager_server.py b/tests/fixtures/native_model_manager_server.py index be343f6ba..01e077d29 100644 --- a/tests/fixtures/native_model_manager_server.py +++ b/tests/fixtures/native_model_manager_server.py @@ -2,8 +2,11 @@ """Local HTTP fixture for server_model_installer_test; never used at runtime.""" import argparse +import hashlib +import json import threading import time +import urllib.parse from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer FILES = { @@ -13,6 +16,28 @@ "/org/repo/resolve/main/Slow/model.gguf": b"s" * (16 * 1024 * 1024), } +# ModelScope-style repo: default branch is master, and the file-list API is +# the source of truth for per-file size and sha256. +MS_REPO = "ms/repo" +MS_REVISION = "master" +MS_FILES = { + "Demo/model-q8.gguf": b"q8-model-payload", + "Demo/shared.json": b'{"shared":true}\n', +} + + +def ms_listing_payload(): + files = [ + { + "Path": path, + "Size": len(payload), + "Sha256": hashlib.sha256(payload).hexdigest(), + "Type": "blob", + } + for path, payload in sorted(MS_FILES.items()) + ] + return json.dumps({"Code": 200, "Success": True, "Data": {"Files": files}}).encode() + class Handler(BaseHTTPRequestHandler): def do_HEAD(self): @@ -22,6 +47,17 @@ def do_GET(self): self.respond(True) def respond(self, body): + parsed = urllib.parse.urlsplit(self.path) + if parsed.path.startswith("/api/v1/models/"): + if not self.check_ms_auth(): + return + self.respond_ms_listing(parsed, body) + return + if parsed.path.startswith("/models/"): + if not self.check_ms_auth(): + return + self.respond_ms_resolve(parsed.path, body) + return payload = FILES.get(self.path) if payload is None: self.send_error(404) @@ -31,6 +67,58 @@ def respond(self, body): self.send_header("ETag", '"fixture-etag-' + str(len(payload)) + '"') self.send_header("X-Repo-Commit", "fixture-commit") self.end_headers() + self.write_payload(payload, body) + + def check_ms_auth(self): + # ModelScope requests must never carry the Hugging Face credential; + # when --ms-token is given they must carry exactly that token. + auth = self.headers.get("Authorization", "") + hf_token = self.server.hf_token + ms_token = self.server.ms_token + if hf_token and auth == "Bearer " + hf_token: + self.send_error(403, "HF token leaked into a ModelScope request") + self.count_request() + return False + if ms_token and auth != "Bearer " + ms_token: + self.send_error(401, "ModelScope request missing its own token") + self.count_request() + return False + return True + + def respond_ms_listing(self, parsed, body): + prefix = "/api/v1/models/" + MS_REPO + "/repo/files" + query = urllib.parse.parse_qs(parsed.query) + revision = query.get("Revision", [""])[0] + if parsed.path != prefix or revision != MS_REVISION: + self.send_error(404) + return + payload = ms_listing_payload() + self.send_response(200) + self.send_header("Content-Length", str(len(payload))) + self.send_header("Content-Type", "application/json") + self.end_headers() + self.write_payload(payload, body) + + def respond_ms_resolve(self, path, body): + prefix = "/models/" + MS_REPO + "/resolve/" + MS_REVISION + "/" + if not path.startswith(prefix): + self.send_error(404) + return + payload = MS_FILES.get(path[len(prefix):]) + if payload is None: + self.send_error(404) + return + etag = hashlib.sha256(payload).hexdigest() + self.send_response(200) + # Like real ModelScope resolve responses: no Content-Length and no + # ETag on HEAD, only X-Linked-Etag. + self.send_header("X-Linked-Etag", etag) + if body: + self.send_header("Content-Length", str(len(payload))) + self.end_headers() + self.write_payload(payload, body) + + def write_payload(self, payload, body): if body: try: for offset in range(0, len(payload), 64 * 1024): @@ -40,6 +128,9 @@ def respond(self, body): time.sleep(0.003) except (BrokenPipeError, ConnectionResetError): pass + self.count_request() + + def count_request(self): with self.server.remaining_lock: self.server.remaining -= 1 if self.server.remaining <= 0: @@ -51,10 +142,15 @@ def log_message(self, *_args): parser = argparse.ArgumentParser() parser.add_argument("--requests", type=int, default=14) +parser.add_argument("--port", type=int, default=18991) +parser.add_argument("--hf-token", default="") +parser.add_argument("--ms-token", default="") args = parser.parse_args() -server = ThreadingHTTPServer(("127.0.0.1", 18991), Handler) +server = ThreadingHTTPServer(("127.0.0.1", args.port), Handler) server.remaining = args.requests server.remaining_lock = threading.Lock() +server.hf_token = args.hf_token +server.ms_token = args.ms_token timeout = threading.Timer(30, server.shutdown) timeout.daemon = True timeout.start() diff --git a/tests/mira_tts/LOCAL_HEAD_VALIDATION.md b/tests/mira_tts/LOCAL_HEAD_VALIDATION.md new file mode 100644 index 000000000..795f67dab --- /dev/null +++ b/tests/mira_tts/LOCAL_HEAD_VALIDATION.md @@ -0,0 +1,60 @@ +# MiraTTS-local sparse output head + +Validated on Windows, 2026-09-06, using the RTX 3090 for CUDA and Vulkan +(Vulkan device 1). + +MiraTTS now creates its CPU sparse output-head view when loading its own +weights. The view shares the embedding storage without copying or converting +weights. Its metadata context lives with the model weights and outlives all +prefill/decode graphs. MiraTTS rebases only the compact logits readback indices; +prompt and generated token IDs still use the full embedding vocabulary. + +The shared `qwen_causal_decode_runtime.h` and `.cpp` are restored to upstream +`origin/main` at `a8fccb4`, with no sparse-head field, slicing, or index rebasing. +GPU backends retain their full output projection. The model-local CPU +`AUDIOCPP_MIRA_TTS_SPARSE_HEAD=0` diagnostic remains available. + +## Output preservation + +All six WAV files are byte-for-byte identical to the corresponding samples +generated before this refactor: CPU, Vulkan, and CUDA, each with Q8 and BF16. +The comparison uses SHA-256 on the complete WAV files. + +Text: `Hello, this is a native Mira TTS test.` +Reference: `../models_v3_test/MiraTTS-comparison/00-reference-voice.wav`. +Seed: 1234. Maximum tokens: 1024. Other sampling parameters: CLI defaults. + +Build each backend with: + +```powershell +cmake --build build/windows-cpu-release --target audiocpp_cli mira_tts_warm_bench -j 12 +cmake --build build/windows-vulkan-release --target audiocpp_cli mira_tts_warm_bench -j 12 +cmake --build build/windows-cuda-release --target audiocpp_cli mira_tts_warm_bench -j 12 +``` + +Example (substitute backend and precision; Vulkan uses `--device 1`): + +```powershell +.\build\windows-cpu-release\bin\audiocpp_cli.exe --task clon --family mira_tts --model ..\models_v3_test\MiraTTS-GGUF\mira-tts-q8.gguf --backend cpu --text "Hello, this is a native Mira TTS test." --voice-ref ..\models_v3_test\MiraTTS-comparison\00-reference-voice.wav --seed 1234 --max-tokens 1024 --out ..\outputs\MiraTTS-q8-CPU-local-head.wav --metrics +``` + +Local samples are named `../outputs/MiraTTS-{q8,bf16}-{cpu,vulkan,cuda}-local-head.wav`. +The previously generated files without `-local-head` are the comparison baseline. + +## Repeated CPU requests + +The existing five-request offline fixture exercises the new view through +repeated requests, a changed prompt, a longer request, and graph release/rebuild. +Both precisions use `assets/resources/b.wav` with eight CPU threads: + +```powershell +.\build\windows-cpu-release\bin\mira_tts_warm_bench.exe --model ..\models_v3_test\MiraTTS-GGUF\mira-tts-q8.gguf --model-spec-override model_specs/mira_tts.json --backend cpu --voice-ref assets/resources/b.wav --request-file tests/mira_tts/offline_requests.json --audio-out-dir ../outputs/mira-local-head-q8-offline --summary-file ../outputs/mira-local-head-q8-offline.json --log-file ../outputs/mira-local-head-q8-offline.log +``` + +Repeat with `bf16` instead of `q8` for the second precision. The fixture's token +budgets cap outputs at 5.12 and 15.36 seconds; these requests test lifecycle and +determinism rather than full-prompt coverage. + +All five requests completed for each precision. The cold, immediate-repeat, +and repeat-after-long-form audio hashes match within each precision: +Q8 `eac900c3f8b032d9`, BF16 `97ef7aca310a5692`. diff --git a/tests/mira_tts/README.md b/tests/mira_tts/README.md new file mode 100644 index 000000000..3d1b33b54 --- /dev/null +++ b/tests/mira_tts/README.md @@ -0,0 +1,113 @@ +# MiraTTS validation + +This directory follows the long-lived validation pattern used for the OuteTTS +port in PR #63. The model is loaded once and exercised by a sequence containing +cold, repeat, changed-prompt, long-form, and post-long-form repeat requests. +Streaming has a separate long-lived sequence so that offline and streaming +sessions do not duplicate model weights in VRAM. + +The benchmark reports one JSON object per request with wall time, generated +duration, real-time factor, sample/frame counts, a deterministic audio hash, +and output path. Streaming runs additionally report first-event latency and the +number of progressively emitted audio events. + +## Build + +```powershell +cmake -S . -B build/windows-cuda-release ` + -DGGML_CUDA=ON -DENGINE_BUILD_WARMBENCH=ON ` + -DCMAKE_BUILD_TYPE=Release +cmake --build build/windows-cuda-release --config Release ` + --target mira_tts_warm_bench -j 8 +``` + +## Native long-lived runs + +Set paths once: + +```powershell +$bench = "build/windows-cuda-release/bin/mira_tts_warm_bench.exe" +$model = "../models_v3_test/MiraTTS-GGUF/mira-tts-bf16.gguf" +$voice = "../models_v3_test/MiraTTS-comparison/00-reference-voice.wav" +$spec = "model_specs/mira_tts.json" +$out = "build/logs/warmbench/mira_tts" +``` + +Run the five-case offline sequence: + +```powershell +& $bench --model $model --model-spec-override $spec --backend cuda ` + --voice-ref $voice --request-file tests/mira_tts/offline_requests.json ` + --audio-out-dir "$out/native-offline" ` + --summary-file "$out/native-offline.json" ` + --log-file "$out/native-offline.log" +``` + +Run the repeated streaming sequence: + +```powershell +& $bench --model $model --model-spec-override $spec --backend cuda ` + --run-mode streaming --voice-ref $voice ` + --request-file tests/mira_tts/streaming_requests.json ` + --audio-out-dir "$out/native-streaming" ` + --summary-file "$out/native-streaming.json" ` + --log-file "$out/native-streaming.log" +``` + +Validate deterministic repeats, streaming events, durations, hashes, and WAV +readability (with FFprobe when available): + +```powershell +python tests/mira_tts/validate_bench.py ` + --offline-summary "$out/native-offline.json" ` + --streaming-summary "$out/native-streaming.json" +``` + +For a memory measurement, start the benchmark with `--hold-seconds 30` and +sample the process plus the selected GPU while it is holding the loaded +session. Record both peak host RSS and peak device memory; do not report only +the final idle value. + +## Trusted Python sequence + +Run the same offline cases through a local, revision-pinned upstream snapshot: + +```powershell +python tools/community_models/mira_tts_reference_bench.py ` + --model-dir --reference $voice ` + --request-file tests/mira_tts/offline_requests.json ` + --output-dir "$out/python-offline" ` + --summary-file "$out/python-offline.json" +``` + +Compare every matched request. Exact frame count is required for the strongest +deterministic parity run: + +```powershell +python tools/community_models/compare_mira_tts_outputs.py ` + --cpp-summary "$out/native-offline.json" ` + --python-summary "$out/python-offline.json" ` + --output "$out/parity.json" --require-exact-frames +``` + +The default gates are waveform cosine >= 0.95 and log-mel cosine >= 0.95. +Threshold changes must be justified by a saved artifact and must not hide a +frame-count, token-boundary, or sampling mismatch. + +## Acceptance matrix + +| Check | Required evidence | +|---|---| +| Cold/repeat determinism | `clone_cold`, `clone_repeat`, and `clone_repeat_after_longform` have identical hashes | +| Long-lived lifecycle | All offline requests finish in one process without reloading the model | +| Long form | `longform` produces non-empty audio within its token budget | +| Streaming | More than one event, finite first-event latency, merged non-empty WAV | +| Streaming determinism | `stream_cold` and `stream_repeat` have identical hashes and event counts | +| Python/native parity | Per-case WAV cosine, log-mel cosine, and frame counts are recorded | +| Audio validity | Every WAV is readable by FFmpeg and has the reported sample rate/channels | +| Resource use | Peak RSS, peak GPU memory, wall time, duration, and RTF are recorded | +| Backends | CUDA is required; CPU/Vulkan results or explicit limitations are documented | + +Run deterministic checks before experimenting with unseeded sampling. If a +repeat hash changes, inspect generated speech-token boundaries, prompt/context +tokens, and component traces before comparing subjective audio quality. diff --git a/tests/mira_tts/VALIDATION.md b/tests/mira_tts/VALIDATION.md new file mode 100644 index 000000000..437e30120 --- /dev/null +++ b/tests/mira_tts/VALIDATION.md @@ -0,0 +1,123 @@ +# MiraTTS local validation record + +Date: 2026-09-02 + +Model: `mira-tts-q8.gguf` + +Reference: `00-reference-voice.wav` + +Host: Windows, NVIDIA GeForce RTX 3090 24 GB + +This record is intentionally local and is not a claim that untested devices or +backends have parity. Generated WAVs and machine-readable summaries are under +`build/logs/warmbench/mira_tts/` and are not source-controlled. + +## CUDA offline — long-lived session + +One model and one session handled all five requests. + +| Request | Wall ms | Audio s | RTF | FNV-1a audio hash | +|---|---:|---:|---:|---| +| clone_cold | 1707.41 | 5.12 | 0.3335 | `426924e4695337c6` | +| clone_repeat | 1560.66 | 5.12 | 0.3048 | `426924e4695337c6` | +| short_second_prompt | 1566.03 | 5.12 | 0.3059 | `8255f4342afeb014` | +| longform | 4771.24 | 15.36 | 0.3106 | `5708fcccf7c25080` | +| clone_repeat_after_longform | 1583.74 | 5.12 | 0.3093 | `426924e4695337c6` | + +The three identical requests remain bit-deterministic before and after the +different-prompt and long-form requests. Internal traces show the decode graph +reused after its first build for matching capacity; larger/different prompt +capacities can build a separate prefill graph. + +## Upstream Python performance comparison + +The revision-pinned upstream MiraTTS implementation was run on the same RTX +3090 with the same prompts, reference voice, sampling parameters, token budgets, +and request order. Model initialization is excluded from both request loops. + +| Test | Original MiraTTS | audio.cpp Q8 | Difference | +|---|---:|---:|---| +| Cold request, 5.12 s audio | 2.227 s | 1.707 s | audio.cpp 1.30× faster* | +| Repeated request, 5.12 s | 0.911 s | 1.561 s | Original 1.71× faster | +| Second short prompt, 5.12 s | 0.895 s | 1.566 s | Original 1.75× faster | +| Long-form, 15.36 s | 2.976 s | 4.771 s | Original 1.60× faster | +| Repeat after long-form, 5.12 s | 0.918 s | 1.584 s | Original 1.73× faster | + +\* The original cold request paid a one-time ONNX Runtime CUDA fallback cost; +the warm measurements are the representative steady-state comparison. + +The three warm short requests average 908.02 ms upstream and 1570.14 ms in +native Q8, making upstream about 1.73x faster in this measurement. For the +15.36-second long-form case, upstream achieved RTF 0.1938 (5.16x real time) +and native Q8 achieved RTF 0.3106 (3.22x real time). + +This is not a precision-matched comparison: upstream uses BF16 while the native +run uses GGUF Q8. Upstream also encodes and retains the reference context before +the timed request sequence, whereas the native request currently processes its +reference input on each run. + +## CUDA streaming + +| Request | Wall ms | First event ms | Events | Audio s | RTF | FNV-1a audio hash | +|---|---:|---:|---:|---:|---:|---| +| stream_cold | 6639.50 | 2489.93 | 4 | 21.14 | 0.3141 | `26eb3542a073a8db` | +| stream_repeat | 6498.52 | 2368.89 | 4 | 21.14 | 0.3074 | `26eb3542a073a8db` | + +The stream emits multiple independently consumable audio events and the merged +repeat is bit-deterministic. + +## Resident memory — Q8 CUDA + +With the Q8 model held after the five-request sequence: + +| Measurement | Resident usage | +|---|---:| +| Process working set | 1429.54 MiB | +| Process private bytes | 5958.28 MiB | +| Windows GPU Process Memory dedicated usage | 2551.55 MiB | +| `nvidia-smi` total device memory in use | 2552 MiB | + +These are held-resident samples, not an instrumented peak across model loading. +The README therefore still requires peak sampling for a formal performance +submission. + +## Python/native decoder parity + +The previously saved exact-token comparison uses identical upstream speech and +context tokens: + +| Metric | Python | Native | Result | +|---|---:|---:|---:| +| Sample rate | 48000 Hz | 48000 Hz | Match | +| Frame count | 122880 | 122880 | Exact match | +| Waveform cosine | — | — | 0.99996735 | +| Log-mel cosine | — | — | 0.99943239 | + +This isolates the native processor/decoder path from autoregressive sampling. + +## Backend coverage + +| Backend | Build | Runtime result | Status | +|---|---|---|---| +| CUDA, RTX 3090 | Passed | Offline, streaming, determinism, and WAV validation passed | Validated | +| Vulkan, RTX 3090 | Passed | Deterministic, but under-generated (3.44 s vs 5.12 s) and diverged on other prompts | Not parity-clean | +| Vulkan, AMD integrated GPU | Passed | Deterministic, but generated only 0.10 s for the 5.12 s CUDA case | Not parity-clean | +| CPU | Not run | No runtime measurement recorded | Untested | + +`mira_tts_warm_bench` builds successfully with the Vulkan configuration. It is +not parity-clean in this test: + +- AMD integrated Vulkan device: deterministic repeats, but only 0.10 seconds + for the 5.12-second CUDA case. +- RTX 3090 Vulkan device: deterministic repeats, but 3.44 seconds for the same + case and severe under-generation on the other prompts. + +Vulkan is therefore recorded as compile-tested but unsupported for MiraTTS +output parity until the backend divergence is diagnosed. CUDA is the validated +runtime for this model. + +## Automated result + +`tests/mira_tts/validate_bench.py` passed all offline/streaming lifecycle, +determinism, duration, event-count, and WAV-readability checks for the CUDA +summaries. diff --git a/tests/mira_tts/VULKAN_GPU_VALIDATION.md b/tests/mira_tts/VULKAN_GPU_VALIDATION.md new file mode 100644 index 000000000..a7e874354 --- /dev/null +++ b/tests/mira_tts/VULKAN_GPU_VALIDATION.md @@ -0,0 +1,60 @@ +# MiraTTS Vulkan GPU validation + +Local validation: 2026-09-05, Windows, RTX 3090, Vulkan device 1. + +The generator now uses the selected Vulkan execution context instead of a CPU +fallback. Its decoder uses F32 projection precision, grouped flash attention +(materialized grouped K/V heads), and F32 input to the output projection. +DirectSetRows KV updates and compact logits remain enabled. + +The previous GPU configuration truncated the short test to 0.74 seconds. +Projection precision alone and attention-layout changes alone did not resolve +that failure in local probes. This is a model-scoped configuration fix, not a +claim that an individual upstream Vulkan kernel has been conclusively diagnosed. + +## Long-lived offline session + +| Request | Q8 wall ms | BF16 wall ms | Audio seconds | +|---|---:|---:|---:| +| Cold | 1632.78 | 1286.46 | 5.12 | +| Repeat | 884.67 | 923.20 | 5.12 | +| Different prompt | 902.05 | 926.29 | 5.12 | +| Long-form | 4957.44 | 3108.20 | 15.36 | +| Repeat after long-form | 933.39 | 1007.46 | 5.12 | + +The three identical requests have identical audio hashes within each precision. +These fixtures are token-capped; their duration is not evidence of complete +prompt coverage. Timing excludes process startup/model loading and is not a +controlled cross-backend performance comparison. + +## Streaming + +| Precision/request | Wall ms | First event ms | Events | Audio seconds | +|---|---:|---:|---:|---:| +| Q8 cold | 3770.73 | 1437.21 | 4 | 20.02 | +| Q8 repeat | 3717.18 | 1375.11 | 4 | 20.02 | +| BF16 cold | 4202.34 | 1686.40 | 4 | 20.60 | +| BF16 repeat | 3986.38 | 1459.62 | 4 | 20.60 | + +Repeated streaming audio hashes match. Both precisions pass +`tests/mira_tts/validate_bench.py` with their offline/streaming summaries. +Use `--model-spec-override model_specs/mira_tts.json` for the streaming fixture's +chunking options, as in the test README. + +## Full short sentence + +Text: "Hello, this is a native Mira TTS test." +Reference: `assets/resources/b.wav`, seed 1234, default CLI token budget. + +| Precision | Generation ms | Audio seconds | +|---|---:|---:| +| Q8 | 1154.30 | 5.56 | +| BF16 | 1363.06 | 6.60 | + +Qwen3-ASR recognizes the complete sentence from both outputs (including Mira +and TTS). This is a transcription smoke check, not perceptual quality parity. +Samples and benchmark JSON/logs are in the local sibling `outputs` directory, +with names `MiraTTS-q8-Vulkan-GPU.wav`, `MiraTTS-bf16-Vulkan-GPU.wav`, and +`vulkan-gpu-{q8,bf16}-{offline,streaming}`. + +CUDA waveform parity and other Vulkan vendors/devices are not claimed. diff --git a/tests/mira_tts/mira_tts_warm_bench.cpp b/tests/mira_tts/mira_tts_warm_bench.cpp new file mode 100644 index 000000000..195b7285e --- /dev/null +++ b/tests/mira_tts/mira_tts_warm_bench.cpp @@ -0,0 +1,372 @@ +#include "engine/framework/audio/wav_reader.h" +#include "engine/framework/audio/wav_writer.h" +#include "engine/framework/debug/trace.h" +#include "engine/framework/io/json.h" +#include "engine/framework/runtime/registry.h" +#include "engine/framework/runtime/session.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct RequestCase { + std::string name; + std::string text; + std::string language; + std::filesystem::path voice_ref; + std::unordered_map options; +}; + +std::string arg_value(int argc, char **argv, const std::string &name, + const std::string &fallback) { + for (int i = 1; i + 1 < argc; ++i) { + if (argv[i] == name) + return argv[i + 1]; + } + return fallback; +} + +std::vector arg_values(int argc, char **argv, + const std::string &name) { + std::vector out; + for (int i = 1; i + 1 < argc; ++i) { + if (argv[i] == name) + out.emplace_back(argv[i + 1]); + } + return out; +} + +int int_arg(int argc, char **argv, const std::string &name, int fallback) { + return std::stoi(arg_value(argc, argv, name, std::to_string(fallback))); +} + +engine::core::BackendType parse_backend(const std::string &value) { + if (value == "cpu") + return engine::core::BackendType::Cpu; + if (value == "cuda") + return engine::core::BackendType::Cuda; + if (value == "vulkan") + return engine::core::BackendType::Vulkan; + if (value == "best") + return engine::core::BackendType::BestAvailable; + throw std::runtime_error("unsupported backend: " + value); +} + +std::string scalar_option(const engine::io::json::Value &value) { + if (value.is_string()) + return value.as_string(); + if (value.is_bool()) + return value.as_bool() ? "true" : "false"; + if (value.is_number()) + return engine::io::json::stringify_number(value.as_number()); + throw std::runtime_error( + "MiraTTS warm-bench options must be scalar values"); +} + +void copy_option_if_present( + std::unordered_map &options, + const engine::io::json::Value &item, const std::string &name) { + if (const auto *value = item.find(name); + value != nullptr && !value->is_null()) { + options[name] = scalar_option(*value); + } +} + +std::vector load_requests( + const std::filesystem::path &path, + const std::filesystem::path &default_voice_ref, + const std::unordered_map &defaults) { + const auto root = engine::io::json::parse_file(path); + const auto &items = root.require("requests").as_array(); + if (items.empty()) + throw std::runtime_error("MiraTTS request file has no requests"); + + std::vector out; + out.reserve(items.size()); + for (size_t index = 0; index < items.size(); ++index) { + const auto &item = items[index]; + RequestCase request; + request.name = engine::io::json::optional_string( + item, "name", "request_" + std::to_string(index)); + request.text = engine::io::json::require_string(item, "text"); + request.language = + engine::io::json::optional_string(item, "language", "en"); + request.voice_ref = engine::io::json::optional_string( + item, "voice_ref", default_voice_ref.string()); + request.options = defaults; + for (const char *name : + {"max_tokens", "seed", "temperature", "top_k", "top_p", + "min_p", "repetition_penalty", "text_chunk_size", + "text_chunk_mode"}) { + copy_option_if_present(request.options, item, name); + } + if (request.voice_ref.empty()) + throw std::runtime_error("MiraTTS request '" + request.name + + "' has no reference voice"); + out.push_back(std::move(request)); + } + return out; +} + +engine::runtime::AudioBuffer read_audio(const std::filesystem::path &path) { + const auto wav = engine::audio::read_wav_f32(path); + return {wav.sample_rate, wav.channels, wav.samples}; +} + +double audio_seconds(const engine::runtime::AudioBuffer &audio) { + if (audio.sample_rate <= 0 || audio.channels <= 0) + return 0.0; + return static_cast(audio.samples.size()) / + static_cast(audio.sample_rate * audio.channels); +} + +std::string fnv1a64_hex(const engine::runtime::AudioBuffer &audio) { + uint64_t hash = 1469598103934665603ULL; + const auto mix = [&hash](const void *data, size_t size) { + const auto *bytes = static_cast(data); + for (size_t i = 0; i < size; ++i) { + hash ^= static_cast(bytes[i]); + hash *= 1099511628211ULL; + } + }; + mix(&audio.sample_rate, sizeof(audio.sample_rate)); + mix(&audio.channels, sizeof(audio.channels)); + for (float sample : audio.samples) { + uint32_t bits = 0; + std::memcpy(&bits, &sample, sizeof(bits)); + mix(&bits, sizeof(bits)); + } + std::ostringstream out; + out << std::hex << std::setfill('0') << std::setw(16) << hash; + return out.str(); +} + +engine::runtime::TaskRequest make_request( + const RequestCase &request, + std::unordered_map + &audio_cache) { + engine::runtime::TaskRequest out; + out.text_input = + engine::runtime::Transcript{request.text, request.language}; + out.options = request.options; + const std::string key = request.voice_ref.lexically_normal().string(); + auto found = audio_cache.find(key); + if (found == audio_cache.end()) + found = audio_cache.emplace(key, read_audio(request.voice_ref)).first; + out.voice = engine::runtime::VoiceCondition{}; + out.voice->speaker = engine::runtime::VoiceReference{}; + out.voice->speaker->audio = found->second; + return out; +} + +engine::io::json::Value result_json( + const RequestCase &request, int iteration, const std::string &mode, + const engine::runtime::AudioBuffer &audio, double wall_ms, + double first_event_ms, int event_count, + const std::filesystem::path &output_path) { + const double seconds = audio_seconds(audio); + return engine::io::json::Value::make_object({ + {"name", engine::io::json::Value::make_string(request.name)}, + {"iteration", engine::io::json::Value::make_number(iteration)}, + {"mode", engine::io::json::Value::make_string(mode)}, + {"wall_ms", engine::io::json::Value::make_number(wall_ms)}, + {"audio_seconds", engine::io::json::Value::make_number(seconds)}, + {"rtf", engine::io::json::Value::make_number( + seconds > 0.0 ? wall_ms / 1000.0 / seconds : 0.0)}, + {"first_event_ms", + first_event_ms >= 0.0 + ? engine::io::json::Value::make_number(first_event_ms) + : engine::io::json::Value::make_null()}, + {"event_count", engine::io::json::Value::make_number(event_count)}, + {"sample_rate", + engine::io::json::Value::make_number(audio.sample_rate)}, + {"channels", engine::io::json::Value::make_number(audio.channels)}, + {"samples", + engine::io::json::Value::make_number( + static_cast(audio.samples.size()))}, + {"audio_hash", engine::io::json::Value::make_string(fnv1a64_hex(audio))}, + {"audio_out", + engine::io::json::Value::make_string(output_path.string())}, + }); +} + +} // namespace + +int main(int argc, char **argv) try { + const std::filesystem::path model_path = + arg_value(argc, argv, "--model", "models/MiraTTS/model.gguf"); + const std::filesystem::path request_file = + arg_value(argc, argv, "--request-file", ""); + if (request_file.empty()) + throw std::runtime_error("MiraTTS warm bench requires --request-file"); + const std::filesystem::path default_voice_ref = + arg_value(argc, argv, "--voice-ref", ""); + const std::filesystem::path output_dir = + arg_value(argc, argv, "--audio-out-dir", + "build/logs/warmbench/mira_tts_audio"); + const std::filesystem::path log_file = + arg_value(argc, argv, "--log-file", + "build/logs/warmbench/mira_tts.log"); + const std::filesystem::path summary_file = + arg_value(argc, argv, "--summary-file", + "build/logs/warmbench/mira_tts_summary.json"); + const std::filesystem::path spec_override = + arg_value(argc, argv, "--model-spec-override", ""); + const std::string backend_name = arg_value(argc, argv, "--backend", "cuda"); + const std::string mode = arg_value(argc, argv, "--run-mode", "offline"); + const int device = int_arg(argc, argv, "--device", 0); + const int threads = int_arg(argc, argv, "--threads", 8); + const int iterations = int_arg(argc, argv, "--iterations", 1); + const int hold_seconds = int_arg(argc, argv, "--hold-seconds", 0); + if (mode != "offline" && mode != "streaming") + throw std::runtime_error("--run-mode must be offline or streaming"); + if (iterations <= 0) + throw std::runtime_error("--iterations must be positive"); + if (hold_seconds < 0) + throw std::runtime_error("--hold-seconds must be non-negative"); + + std::unordered_map defaults; + for (const auto &option : arg_values(argc, argv, "--request-option")) { + const size_t equals = option.find('='); + if (equals == std::string::npos || equals == 0) + throw std::runtime_error("invalid --request-option: " + option); + defaults[option.substr(0, equals)] = option.substr(equals + 1); + } + const auto requests = + load_requests(request_file, default_voice_ref, defaults); + + std::filesystem::create_directories(output_dir); + if (!log_file.parent_path().empty()) + std::filesystem::create_directories(log_file.parent_path()); + if (!summary_file.parent_path().empty()) + std::filesystem::create_directories(summary_file.parent_path()); + engine::debug::configure_logging( + engine::debug::LoggingConfig{true, log_file.string()}); + + auto registry = engine::runtime::make_default_registry(); + engine::runtime::ModelLoadRequest load_request; + load_request.model_path = model_path; + load_request.family_hint = "mira_tts"; + if (!spec_override.empty()) + load_request.model_spec_override = spec_override; + auto model = registry.load(load_request); + + engine::runtime::SessionOptions session_options; + session_options.backend.type = parse_backend(backend_name); + session_options.backend.device = device; + session_options.backend.threads = threads; + for (const auto &option : arg_values(argc, argv, "--session-option")) { + const size_t equals = option.find('='); + if (equals == std::string::npos || equals == 0) + throw std::runtime_error("invalid --session-option: " + option); + session_options.options[option.substr(0, equals)] = + option.substr(equals + 1); + } + + const auto run_mode = mode == "streaming" + ? engine::runtime::RunMode::Streaming + : engine::runtime::RunMode::Offline; + auto session_base = model->create_task_session( + {engine::runtime::VoiceTaskKind::VoiceCloning, run_mode}, + session_options); + auto *offline = dynamic_cast( + session_base.get()); + auto *streaming = dynamic_cast( + session_base.get()); + if (mode == "offline" && offline == nullptr) + throw std::runtime_error("MiraTTS did not create an offline session"); + if (mode == "streaming" && streaming == nullptr) + throw std::runtime_error("MiraTTS did not create a streaming session"); + + std::unordered_map audio_cache; + auto first_request = make_request(requests.front(), audio_cache); + session_base->prepare( + engine::runtime::build_preparation_request(first_request)); + + engine::io::json::Value::Array results; + for (const auto &request_case : requests) { + for (int iteration = 1; iteration <= iterations; ++iteration) { + auto request = make_request(request_case, audio_cache); + const auto started = std::chrono::steady_clock::now(); + engine::runtime::TaskResult result; + double first_event_ms = -1.0; + int event_count = 0; + if (mode == "streaming") { + streaming->start_stream(request); + while (auto event = streaming->next_stream_event()) { + ++event_count; + if (first_event_ms < 0.0) { + first_event_ms = + std::chrono::duration( + std::chrono::steady_clock::now() - started) + .count(); + } + } + result = streaming->finish_stream(); + } else { + result = offline->run(request); + } + const double wall_ms = + std::chrono::duration( + std::chrono::steady_clock::now() - started) + .count(); + if (!result.audio_output.has_value()) + throw std::runtime_error("MiraTTS produced no audio"); + const auto output_path = + output_dir / + (request_case.name + "_" + mode + "_" + + std::to_string(iteration) + ".wav"); + engine::audio::write_pcm16_wav( + output_path, result.audio_output->sample_rate, + result.audio_output->channels, result.audio_output->samples); + auto item = result_json(request_case, iteration, mode, + *result.audio_output, wall_ms, first_event_ms, + event_count, output_path); + std::cout << "result_json=" << engine::io::json::stringify(item) + << "\n"; + results.push_back(std::move(item)); + } + } + + auto summary = engine::io::json::Value::make_object({ + {"family", engine::io::json::Value::make_string("mira_tts")}, + {"backend", engine::io::json::Value::make_string(backend_name)}, + {"mode", engine::io::json::Value::make_string(mode)}, + {"model", engine::io::json::Value::make_string(model_path.string())}, + {"results", + engine::io::json::Value::make_array(std::move(results))}, + }); + { + std::ofstream output(summary_file, std::ios::binary | std::ios::trunc); + if (!output) + throw std::runtime_error("failed to open summary file: " + + summary_file.string()); + output << engine::io::json::stringify(summary) << "\n"; + } + std::cout << "summary_json=" << summary_file.string() << "\n"; + std::cout << "log_out=" << log_file.string() << "\n"; + if (hold_seconds > 0) { + std::cout << "holding_session_seconds=" << hold_seconds << "\n"; + std::cout.flush(); + std::this_thread::sleep_for(std::chrono::seconds(hold_seconds)); + } + engine::debug::reset_logging(); + return 0; +} catch (const std::exception &error) { + std::cerr << "mira_tts_warm_bench failed: " << error.what() << "\n"; + return 1; +} diff --git a/tests/mira_tts/offline_requests.json b/tests/mira_tts/offline_requests.json new file mode 100644 index 000000000..1c4ccce94 --- /dev/null +++ b/tests/mira_tts/offline_requests.json @@ -0,0 +1,34 @@ +{ + "requests": [ + { + "name": "clone_cold", + "text": "Good morning. This deterministic request checks the first synthesis in a newly prepared session.", + "seed": 1234, + "max_tokens": 256 + }, + { + "name": "clone_repeat", + "text": "Good morning. This deterministic request checks the first synthesis in a newly prepared session.", + "seed": 1234, + "max_tokens": 256 + }, + { + "name": "short_second_prompt", + "text": "The same loaded model now speaks a different sentence without restarting the process.", + "seed": 4321, + "max_tokens": 256 + }, + { + "name": "longform", + "text": "MiraTTS remains loaded while this longer paragraph exercises its autoregressive generator, acoustic processor, low resolution decoder, and audio upsampler. The benchmark records wall time, generated duration, real time factor, and a deterministic audio hash. A second sentence ensures that punctuation and a longer token budget are covered by the same warm session.", + "seed": 2468, + "max_tokens": 768 + }, + { + "name": "clone_repeat_after_longform", + "text": "Good morning. This deterministic request checks the first synthesis in a newly prepared session.", + "seed": 1234, + "max_tokens": 256 + } + ] +} diff --git a/tests/mira_tts/streaming_requests.json b/tests/mira_tts/streaming_requests.json new file mode 100644 index 000000000..8a87214f6 --- /dev/null +++ b/tests/mira_tts/streaming_requests.json @@ -0,0 +1,20 @@ +{ + "requests": [ + { + "name": "stream_cold", + "text": "The first streaming request emits this sentence in several progressive audio segments. The listener can begin playback before the complete paragraph has finished synthesizing.", + "seed": 1234, + "max_tokens": 384, + "text_chunk_size": 72, + "text_chunk_mode": "default" + }, + { + "name": "stream_repeat", + "text": "The first streaming request emits this sentence in several progressive audio segments. The listener can begin playback before the complete paragraph has finished synthesizing.", + "seed": 1234, + "max_tokens": 384, + "text_chunk_size": 72, + "text_chunk_mode": "default" + } + ] +} diff --git a/tests/mira_tts/validate_bench.py b/tests/mira_tts/validate_bench.py new file mode 100644 index 000000000..353eb5d69 --- /dev/null +++ b/tests/mira_tts/validate_bench.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""Validate deterministic MiraTTS warm-benchmark artifacts.""" + +from __future__ import annotations + +import argparse +import json +import shutil +import subprocess +from pathlib import Path + + +def load_results(path: Path) -> dict[str, dict[str, object]]: + payload = json.loads(path.read_text(encoding="utf-8")) + return {item["name"]: item for item in payload["results"]} + + +def require(condition: bool, message: str, failures: list[str]) -> None: + if not condition: + failures.append(message) + + +def validate_audio(item: dict[str, object], failures: list[str]) -> None: + path = Path(str(item["audio_out"])) + require(path.is_file(), f"missing WAV: {path}", failures) + require(int(item["sample_rate"]) == 48000, f"unexpected sample rate: {path}", failures) + require(int(item["channels"]) == 1, f"unexpected channel count: {path}", failures) + require(int(item["samples"]) > 0, f"empty audio: {path}", failures) + require(float(item["audio_seconds"]) > 0.0, f"zero duration: {path}", failures) + require(float(item["rtf"]) > 0.0, f"invalid RTF: {path}", failures) + if path.is_file() and shutil.which("ffprobe"): + process = subprocess.run( + [ + "ffprobe", + "-v", + "error", + "-select_streams", + "a:0", + "-show_entries", + "stream=sample_rate,channels", + "-of", + "json", + str(path), + ], + capture_output=True, + text=True, + check=False, + ) + require(process.returncode == 0, f"ffprobe rejected WAV: {path}", failures) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--offline-summary", type=Path, required=True) + parser.add_argument("--streaming-summary", type=Path, required=True) + args = parser.parse_args() + + offline = load_results(args.offline_summary) + streaming = load_results(args.streaming_summary) + failures: list[str] = [] + + expected_offline = { + "clone_cold", + "clone_repeat", + "short_second_prompt", + "longform", + "clone_repeat_after_longform", + } + expected_streaming = {"stream_cold", "stream_repeat"} + require(expected_offline <= set(offline), "offline cases are incomplete", failures) + require(expected_streaming <= set(streaming), "streaming cases are incomplete", failures) + + for item in [*offline.values(), *streaming.values()]: + validate_audio(item, failures) + + if expected_offline <= set(offline): + repeat_hashes = { + str(offline[name]["audio_hash"]) + for name in ( + "clone_cold", + "clone_repeat", + "clone_repeat_after_longform", + ) + } + require(len(repeat_hashes) == 1, "offline deterministic hashes differ", failures) + require( + offline["short_second_prompt"]["audio_hash"] + != offline["clone_cold"]["audio_hash"], + "different prompts unexpectedly produced the same audio hash", + failures, + ) + require( + int(offline["longform"]["samples"]) + > int(offline["clone_cold"]["samples"]), + "long-form case is not longer than the short case", + failures, + ) + + if expected_streaming <= set(streaming): + require( + streaming["stream_cold"]["audio_hash"] + == streaming["stream_repeat"]["audio_hash"], + "streaming deterministic hashes differ", + failures, + ) + require( + int(streaming["stream_cold"]["event_count"]) > 1, + "streaming cold case emitted fewer than two events", + failures, + ) + require( + streaming["stream_cold"]["event_count"] + == streaming["stream_repeat"]["event_count"], + "streaming repeat event counts differ", + failures, + ) + for name in expected_streaming: + require( + float(streaming[name]["first_event_ms"]) > 0.0, + f"{name} has no valid first-event latency", + failures, + ) + + report = { + "passed": not failures, + "failures": failures, + "offline_cases": sorted(offline), + "streaming_cases": sorted(streaming), + } + print(json.dumps(report, indent=2)) + if failures: + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/tests/sopro_tts/sopro_probe.cpp b/tests/sopro_tts/sopro_probe.cpp new file mode 100644 index 000000000..1dcd6fc38 --- /dev/null +++ b/tests/sopro_tts/sopro_probe.cpp @@ -0,0 +1,383 @@ +// Stage probe for the sopro_tts port. +// +// The offline pipeline has five stages and a broken one is hard to localise +// from the final waveform. This binary exercises them in isolation: +// +// mel analysis mel -> vocoder -> waveform. Vocos is trained to invert +// its own analysis mel, so a clean round trip proves the mel front +// end and the whole vocoder graph at once. +// semantic reference waveform -> FSQ token ids (histogram + first ids). +// speaker reference waveform -> id/style/style-ctrl embedding statistics. +// +// Usage: sopro_probe [out-dir] + +#include "engine/community_models/sopro_tts/acoustic.h" +#include "engine/community_models/sopro_tts/assets.h" +#include "engine/community_models/sopro_tts/reference.h" +#include "engine/community_models/sopro_tts/semantic_encoder.h" +#include "engine/community_models/sopro_tts/speaker_encoder.h" +#include "engine/community_models/sopro_tts/vocoder.h" + +#include "engine/framework/assets/tensor_source.h" +#include "engine/framework/audio/conversion.h" +#include "engine/framework/audio/resampling.h" +#include "engine/framework/audio/wav_reader.h" +#include "engine/framework/audio/wav_writer.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/execution_context.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace sopro = engine::community_models::sopro_tts; + +namespace { + +struct Stats { + float min = 0.0F; + float max = 0.0F; + double mean = 0.0; + double rms = 0.0; + size_t nonfinite = 0; +}; + +Stats describe(const std::vector & values) { + Stats out; + if (values.empty()) { + return out; + } + out.min = out.max = values.front(); + double sum = 0.0; + double square_sum = 0.0; + for (const float value : values) { + if (!std::isfinite(value)) { + ++out.nonfinite; + continue; + } + out.min = std::min(out.min, value); + out.max = std::max(out.max, value); + sum += value; + square_sum += static_cast(value) * value; + } + const auto count = static_cast(values.size()); + out.mean = sum / count; + out.rms = std::sqrt(square_sum / count); + return out; +} + +void print_stats(const char * label, const std::vector & values) { + const auto stats = describe(values); + std::printf( + " %-22s n=%-8zu min=%+.4f max=%+.4f mean=%+.5f rms=%.5f nonfinite=%zu\n", + label, values.size(), stats.min, stats.max, stats.mean, stats.rms, stats.nonfinite); +} + +// Segmental SNR between two aligned signals, in dB. +double snr_db(const std::vector & reference, const std::vector & test) { + const size_t n = std::min(reference.size(), test.size()); + double signal = 0.0; + double noise = 0.0; + for (size_t i = 0; i < n; ++i) { + const double r = reference[i]; + const double d = r - test[i]; + signal += r * r; + noise += d * d; + } + if (noise <= 0.0) { + return 999.0; + } + return 10.0 * std::log10(std::max(signal, 1e-20) / noise); +} + +} // namespace + +int main(int argc, char ** argv) { + if (argc < 3) { + std::fprintf(stderr, "usage: %s [out-dir]\n", argv[0]); + return 2; + } + const std::filesystem::path model_path = argv[1]; + const std::filesystem::path reference_path = argv[2]; + const std::filesystem::path out_dir = argc > 3 ? std::filesystem::path(argv[3]) + : std::filesystem::path("."); + std::filesystem::create_directories(out_dir); + + auto assets = sopro::load_sopro_tts_assets(model_path); + const auto & config = assets->config; + const auto sample_rate = static_cast(config.sample_rate); + + engine::core::BackendConfig backend_config; + backend_config.type = engine::core::BackendType::Cpu; + backend_config.threads = 8; + engine::core::ExecutionContext execution(backend_config); + + constexpr size_t kWeightBytes = 512ull * 1024ull * 1024ull; + constexpr size_t kGraphBytes = 1024ull * 1024ull * 1024ull; + const auto storage = engine::assets::TensorStorageType::F32; + + // ---- input ---- + const auto wav = engine::audio::read_wav_f32(reference_path); + std::vector mono = wav.channels == 1 + ? wav.samples + : engine::audio::mixdown_interleaved_to_mono_average(wav.samples, wav.channels); + if (wav.sample_rate != sample_rate) { + mono = engine::audio::resample_mono_torchaudio_sinc_hann(mono, wav.sample_rate, sample_rate); + } + for (auto & value : mono) { + value = std::min(1.0F, std::max(-1.0F, value)); + } + std::printf("reference: %s\n", reference_path.string().c_str()); + std::printf(" source_rate=%d channels=%d samples=%zu -> %.3f s at %d Hz\n", + wav.sample_rate, wav.channels, mono.size(), + static_cast(mono.size()) / sample_rate, sample_rate); + + // ---- crop / level normalisation, as prepare_reference does ---- + std::mt19937_64 rng(1234); + auto cropped = sopro::audio_ops::crop_on_pause( + mono, config.generation.ref_seconds, sample_rate, rng); + const auto level = sopro::audio_ops::speech_level_db(mono, sample_rate); + std::printf(" speech_level=%.2f dB active=%.3f s\n", level.level_db, level.active_seconds); + std::printf(" crop_on_pause(%.1f s): %zu -> %zu samples (%.3f s)\n", + config.generation.ref_seconds, mono.size(), cropped.size(), + static_cast(cropped.size()) / sample_rate); + const auto crop_level = sopro::audio_ops::speech_level_db(cropped, sample_rate); + float crop_peak = 0.0F; + for (const float value : cropped) { + crop_peak = std::max(crop_peak, std::fabs(value)); + } + auto normalisation = sopro::audio_ops::normalize_reference(cropped, sample_rate); + const auto normalised = std::move(normalisation.wav); + std::printf(" normalize_reference: level %.2f -> %.2f dB (gain %+.2f dB, crop peak %.3f)\n", + crop_level.level_db, normalisation.level_db, + normalisation.level_db - crop_level.level_db, crop_peak); + + // ---- stage: vocoder round trip ---- + { + std::printf("\n[mel] analysis mel -> vocoder round trip\n"); + sopro::SoproVocoderRuntime vocoder( + *assets, execution, kWeightBytes, kGraphBytes, storage, storage); + const auto mel = vocoder.log_mel(normalised); + const int64_t n_mels = vocoder.n_mels(); + const int64_t frames = static_cast(mel.size()) / n_mels; + std::printf(" frames=%lld (expected %lld)\n", + static_cast(frames), + static_cast(vocoder.mel_frames(static_cast(normalised.size())))); + print_stats("log_mel", mel); + // Dump for an independent numpy check of the front end. + { + std::FILE * fh = std::fopen((out_dir / "probe_input_logmel.f32").string().c_str(), "wb"); + if (fh != nullptr) { + std::fwrite(mel.data(), sizeof(float), mel.size(), fh); + std::fclose(fh); + } + std::FILE * wh = std::fopen((out_dir / "probe_input_audio.f32").string().c_str(), "wb"); + if (wh != nullptr) { + std::fwrite(normalised.data(), sizeof(float), normalised.size(), wh); + std::fclose(wh); + } + } + + const auto audio = vocoder.decode(mel, frames); + print_stats("round_trip_audio", audio); + engine::audio::write_pcm16_wav(out_dir / "probe_vocoder_roundtrip.wav", sample_rate, 1, audio); + { + std::FILE * fh = std::fopen((out_dir / "probe_roundtrip_audio.f32").string().c_str(), "wb"); + if (fh != nullptr) { + std::fwrite(audio.data(), sizeof(float), audio.size(), fh); + std::fclose(fh); + } + } + + // Waveform SNR is phase-sensitive and Vocos predicts phase, so the + // meaningful check is whether re-analysing the output reproduces the + // mel the vocoder was asked to render. + std::printf(" waveform SNR vs input: %.2f dB (phase-sensitive, informative only)\n", + snr_db(normalised, audio)); + const auto remel = vocoder.log_mel(audio); + const int64_t reframes = static_cast(remel.size()) / n_mels; + const int64_t common = std::min(frames, reframes); + double abs_sum = 0.0; + double abs_max = 0.0; + size_t counted = 0; + for (int64_t c = 0; c < n_mels; ++c) { + for (int64_t t = 0; t < common; ++t) { + const double a = mel[static_cast(c * frames + t)]; + const double b = remel[static_cast(c * reframes + t)]; + // Ignore bins pinned at the log floor; they carry no signal. + if (a <= -16.0) { + continue; + } + const double d = std::fabs(a - b); + abs_sum += d; + abs_max = std::max(abs_max, d); + ++counted; + } + } + const double mae = counted > 0 ? abs_sum / static_cast(counted) : 0.0; + std::printf(" mel round-trip MAE=%.4f max=%.4f over %zu bins (log units)\n", + mae, abs_max, counted); + std::printf(" -> %s\n", + mae < 0.35 ? "vocoder + mel front end look CORRECT" + : "vocoder or mel front end is WRONG"); + } + + // ---- stage: semantic encoder ---- + { + std::printf("\n[semantic] FSQ tokeniser\n"); + sopro::SoproSemanticEncoderRuntime encoder( + *assets, execution, kWeightBytes, kGraphBytes, storage, storage); + const auto tokens = encoder.encode(normalised); + const int64_t expected = (static_cast(normalised.size()) + + config.semantic_encoder.token_samples_24k - 1) / + config.semantic_encoder.token_samples_24k; + std::printf(" tokens=%zu (expected %lld)\n", tokens.size(), + static_cast(expected)); + std::map histogram; + for (const int32_t token : tokens) { + ++histogram[token]; + } + int32_t lo = tokens.empty() ? 0 : *std::min_element(tokens.begin(), tokens.end()); + int32_t hi = tokens.empty() ? 0 : *std::max_element(tokens.begin(), tokens.end()); + std::printf(" distinct=%zu range=[%d, %d] of [0, %lld)\n", + histogram.size(), lo, hi, + static_cast(config.model.semantic_vocab_size)); + std::printf(" first ids:"); + for (size_t i = 0; i < std::min(16, tokens.size()); ++i) { + std::printf(" %d", tokens[i]); + } + std::printf("\n"); + // A collapsed tokeniser (one id repeated) means the encoder is broken. + int most = 0; + for (const auto & [id, count] : histogram) { + (void) id; + most = std::max(most, count); + } + const double share = tokens.empty() ? 0.0 : static_cast(most) / tokens.size(); + std::printf(" most common id share: %.1f%% %s\n", share * 100.0, + share > 0.5 ? "(COLLAPSED - encoder likely wrong)" : "(looks healthy)"); + } + + // ---- stage: speaker encoder ---- + { + std::printf("\n[speaker] identity / style embeddings\n"); + sopro::SoproSpeakerEncoderRuntime encoder( + *assets, execution, kWeightBytes, kGraphBytes, storage, storage); + const auto wav16 = engine::audio::resample_mono_torchaudio_sinc_hann( + normalised, sample_rate, static_cast(encoder.sample_rate())); + const auto embeddings = encoder.encode(wav16); + print_stats("id_emb", embeddings.id_emb); + print_stats("style_emb", embeddings.style_emb); + print_stats("style_ctrl", embeddings.style_ctrl); + double norm = 0.0; + for (const float value : embeddings.id_emb) { + norm += static_cast(value) * value; + } + std::printf(" |id_emb| = %.6f (must be 1.0)\n", std::sqrt(norm)); + } + + // ---- stage: acoustic head self-reconstruction ---- + // Re-render the second half of the reference from its own semantic tokens, + // conditioned on the first half as the prompt. The acoustic head is lossy + // (it only sees FSQ ids) but a correct one tracks the real mel closely; + // a broken one produces something uncorrelated with it. + { + std::printf("\n[acoustic] self-reconstruction from the reference's own tokens\n"); + sopro::SoproVocoderRuntime vocoder( + *assets, execution, kWeightBytes, kGraphBytes, storage, storage); + sopro::SoproSpeakerEncoderRuntime speaker( + *assets, execution, kWeightBytes, kGraphBytes, storage, storage); + sopro::SoproSemanticEncoderRuntime semantic( + *assets, execution, kWeightBytes, kGraphBytes, storage, storage); + sopro::SoproReferenceBuilder builder(*assets, speaker, semantic, vocoder); + std::mt19937_64 build_rng(1234); + const auto voice = builder.build(mono, config.generation.ref_seconds, build_rng); + + const int64_t n_mels = config.model.acoustic_mel_n_mels; + const int64_t hop_ratio = config.hop_ratio(); + const auto total_tokens = static_cast(voice.semantic_tokens.size()); + const int64_t split = total_tokens / 2; + const int64_t prompt_frames = std::min(voice.mel_frames, split * hop_ratio); + const int64_t gen_tokens = total_tokens - split; + const int64_t total_frames = prompt_frames + gen_tokens * hop_ratio; + std::printf(" reference: %lld tokens, %lld mel frames\n", + static_cast(total_tokens), static_cast(voice.mel_frames)); + std::printf(" prompt: %lld frames, regenerating %lld tokens -> %lld frames\n", + static_cast(prompt_frames), static_cast(gen_tokens), + static_cast(total_frames)); + + sopro::SoproAcousticRequest request; + request.semantic_tokens = voice.semantic_tokens; + request.cond_vec = voice.cond_vec; + request.prompt_mel.assign(static_cast(n_mels * prompt_frames), 0.0F); + for (int64_t c = 0; c < n_mels; ++c) { + for (int64_t t = 0; t < prompt_frames; ++t) { + request.prompt_mel[static_cast(c * prompt_frames + t)] = + voice.mel[static_cast(c * voice.mel_frames + t)]; + } + } + request.prompt_frames = prompt_frames; + request.total_frames = total_frames; + request.steps = 32; // many steps: isolate the field from the schedule + request.seed = 1234; + + sopro::SoproAcousticRuntime acoustic( + *assets, execution, kWeightBytes, kGraphBytes, storage, storage); + const auto solved = acoustic.solve(request); + print_stats("solved_mel(normalised)", solved); + + // Compare only the regenerated span against the true reference mel. + const int64_t compare_end = std::min(total_frames, voice.mel_frames); + double abs_sum = 0.0; + double ref_sq = 0.0; + double err_sq = 0.0; + size_t counted = 0; + for (int64_t c = 0; c < n_mels; ++c) { + for (int64_t t = prompt_frames; t < compare_end; ++t) { + const double truth = voice.mel[static_cast(c * voice.mel_frames + t)]; + const double got = solved[static_cast(c * total_frames + t)]; + abs_sum += std::fabs(truth - got); + ref_sq += truth * truth; + err_sq += (truth - got) * (truth - got); + ++counted; + } + } + if (counted > 0) { + const double mae = abs_sum / static_cast(counted); + const double nmse = err_sq / std::max(ref_sq, 1e-12); + std::printf(" regenerated span vs true mel: MAE=%.4f NMSE=%.4f (%zu bins)\n", + mae, nmse, counted); + std::printf(" -> %s\n", + nmse < 0.6 ? "acoustic head tracks the reference (looks CORRECT)" + : "acoustic head output is uncorrelated with the reference (WRONG)"); + } + std::FILE * fh = std::fopen((out_dir / "probe_acoustic_solved.f32").string().c_str(), "wb"); + if (fh != nullptr) { + std::fwrite(solved.data(), sizeof(float), solved.size(), fh); + std::fclose(fh); + } + std::FILE * th = std::fopen((out_dir / "probe_ref_tokens.i32").string().c_str(), "wb"); + if (th != nullptr) { + std::fwrite(voice.semantic_tokens.data(), sizeof(int32_t), voice.semantic_tokens.size(), th); + std::fclose(th); + } + std::FILE * ch = std::fopen((out_dir / "probe_cond_vec.f32").string().c_str(), "wb"); + if (ch != nullptr) { + std::fwrite(voice.cond_vec.data(), sizeof(float), voice.cond_vec.size(), ch); + std::fclose(ch); + } + std::FILE * mh = std::fopen((out_dir / "probe_ref_mel.f32").string().c_str(), "wb"); + if (mh != nullptr) { + std::fwrite(voice.mel.data(), sizeof(float), voice.mel.size(), mh); + std::fclose(mh); + } + } + + std::printf("\nwrote %s\n", (out_dir / "probe_vocoder_roundtrip.wav").string().c_str()); + return 0; +} diff --git a/tests/sopro_tts/test_sopro_tts_audio_ops.cpp b/tests/sopro_tts/test_sopro_tts_audio_ops.cpp new file mode 100644 index 000000000..fb480169e --- /dev/null +++ b/tests/sopro_tts/test_sopro_tts_audio_ops.cpp @@ -0,0 +1,182 @@ +// Host-side checks for the sopro reference level chain and the ISTFT head's +// band limit. Both are pure functions over plain buffers, so none of this needs +// the checkpoint; the weight-bound stages are covered by sopro_probe instead. +#include "engine/community_models/sopro_tts/assets.h" +#include "engine/community_models/sopro_tts/reference.h" +#include "engine/community_models/sopro_tts/vocoder.h" +#include "test_assert.h" + +#include +#include +#include +#include +#include + +namespace { + +namespace test = engine::test; +namespace sopro = engine::community_models::sopro_tts; +namespace audio_ops = sopro::audio_ops; + +constexpr int kSampleRate = 24000; + +// A constant-amplitude buffer makes speech_level_db exact: every 25 ms frame +// has the same RMS, so the 0.2-quantile gate keeps all of them and the median +// is the amplitude itself. Peak and speech level therefore agree, which keeps +// the expected values below plain arithmetic. +std::vector flat(float amplitude, float seconds = 1.0F) { + const auto samples = static_cast(static_cast(kSampleRate) * seconds); + return std::vector(samples, amplitude); +} + +float peak_of(const std::vector & wav) { + float peak = 0.0F; + for (const float value : wav) { + peak = std::max(peak, std::fabs(value)); + } + return peak; +} + +void test_speech_level_db_reads_a_flat_buffer() { + const auto level = audio_ops::speech_level_db(flat(0.1F), kSampleRate); + test::require_close(level.level_db, -20.0F, 1.0e-3F, "flat 0.1 level"); + // 98 frames at a 10 ms hop; all of them survive the activity gate. + test::require_close(level.active_seconds, 0.98F, 1.0e-3F, "flat 0.1 active seconds"); + + // Shorter than one 25 ms window: whole-buffer RMS, and no active span. + const auto tiny = audio_ops::speech_level_db(std::vector(100, 0.1F), kSampleRate); + test::require_close(tiny.level_db, -20.0F, 1.0e-3F, "short buffer level"); + test::require_close(tiny.active_seconds, 0.0F, 1.0e-6F, "short buffer active seconds"); + + // Silence floors at 1e-6 rather than diverging. + const auto silent = audio_ops::speech_level_db(std::vector(100, 0.0F), kSampleRate); + test::require_close(silent.level_db, -120.0F, 1.0e-3F, "silence level"); +} + +void test_quiet_reference_is_boosted_to_the_prompt_level() { + const auto input = flat(0.01F); // -40 dB, 20.2 dB below the prompt level + const auto out = audio_ops::normalize_reference(input, kSampleRate); + + test::require_close(out.level_db, audio_ops::kPromptLevelDb, 1.0e-3F, "boosted level"); + const float expected_gain = std::pow(10.0F, 20.2F / 20.0F); + test::require_close(out.wav.front(), 0.01F * expected_gain, 1.0e-6F, "boosted sample"); + // Well clear of the 0.95 ceiling, so the peak guard must not have bitten. + test::require(peak_of(out.wav) < 0.95F, "boosted peak stays below the ceiling"); + test::require_eq(out.wav.size(), input.size(), "boosted length"); +} + +void test_hot_reference_is_left_alone() { + // -6.02 dB, well above the prompt level. The pre-2.1 rule attenuated this + // by 13.78 dB; boost-only must pass it through untouched. + const auto input = flat(0.5F); + const auto out = audio_ops::normalize_reference(input, kSampleRate); + + test::require_close(out.level_db, -6.0206F, 1.0e-3F, "hot level is unchanged"); + for (size_t i = 0; i < input.size(); i += 997) { + test::require_eq(out.wav[i], input[i], "hot sample is unchanged"); + } +} + +void test_peak_guard_caps_the_boost() { + // An impulse train with a high crest factor: every 25 ms window holds + // exactly six 0.9 spikes, so the frame RMS is a uniform 0.09 (-20.92 dB) + // and the activity gate keeps all of it. A lone spike would not do — the + // gate would keep only the frames containing it and read the level off + // those. The buffer wants +1.12 dB but the peak leaves only +0.47 dB. + std::vector input(static_cast(kSampleRate), 0.0F); + for (size_t i = 0; i < input.size(); i += 100) { + input[i] = 0.9F; + } + const auto out = audio_ops::normalize_reference(input, kSampleRate); + + const float capped_gain = 20.0F * std::log10(0.95F / 0.9F); + test::require_close(out.level_db, -20.9151F + capped_gain, 1.0e-2F, "peak-guarded level"); + test::require_close(peak_of(out.wav), 0.95F, 1.0e-4F, "peak lands on the ceiling"); + test::require(out.level_db < audio_ops::kPromptLevelDb, "peak guard undershoots the target"); +} + +void test_boost_is_limited_to_thirty_db() { + // -60 dB with 59.5 dB of peak headroom, so the 30 dB gain limit is what + // binds rather than the ceiling. + const auto out = audio_ops::normalize_reference(flat(0.001F), kSampleRate); + test::require_close(out.level_db, -30.0F, 1.0e-2F, "gain-limited level"); +} + +void test_output_gain_tracks_the_reference_level() { + test::require_close( + audio_ops::output_gain(), std::pow(10.0F, -3.2F / 20.0F), 1.0e-6F, "default output gain"); + // A hotter reference has to be pulled down further to reach -23 dB. + test::require( + audio_ops::output_gain(-11.24F) < audio_ops::output_gain(), + "a hot reference gets a smaller output gain"); + test::require_close( + audio_ops::output_gain(-11.24F), std::pow(10.0F, -11.76F / 20.0F), 1.0e-6F, + "hot reference output gain"); +} + +void test_match_gain_falls_back_to_the_reference_level() { + // Under kMinActiveSeconds of measurable speech, so match_gain cannot level + // off the audio itself and defers to the reference it was cloned from. + const std::vector too_short(100, 0.1F); + test::require_close( + audio_ops::match_gain(too_short, kSampleRate, audio_ops::kOutputLevelDb, -11.24F), + audio_ops::output_gain(-11.24F), 1.0e-6F, "fallback uses the reference level"); + test::require( + audio_ops::match_gain(too_short, kSampleRate, audio_ops::kOutputLevelDb, -11.24F) != + audio_ops::match_gain(too_short, kSampleRate), + "fallback varies with the reference level"); + + // With enough speech to measure, the reference level is irrelevant: the + // gain comes from the audio actually produced. + const auto measurable = flat(0.1F); // -20 dB, 0.98 s active + const float expected = std::pow(10.0F, -3.0F / 20.0F); + test::require_close( + audio_ops::match_gain(measurable, kSampleRate), expected, 1.0e-4F, "measured gain"); + test::require_close( + audio_ops::match_gain(measurable, kSampleRate, audio_ops::kOutputLevelDb, -11.24F), + expected, 1.0e-4F, "measured gain ignores the reference level"); +} + +void test_band_limit_bin() { + sopro::SoproVocoderConfig config; // 24 kHz, n_fft 1024, 10900 Hz + // ceil(10900 * 1024 / 24000) = 466 of 513 bins, i.e. a 10921.9 Hz cut. + test::require_eq(sopro::band_limit_bin(config), int64_t{466}, "default cut"); + + config.band_limit_hz = 0.0F; + test::require_eq(sopro::band_limit_bin(config), int64_t{513}, "zero keeps every bin"); + config.band_limit_hz = -1.0F; + test::require_eq(sopro::band_limit_bin(config), int64_t{513}, "negative keeps every bin"); + + // Nyquist itself is bin 512, and the cut is inclusive, so a 12 kHz limit + // still drops that last bin — which is the one the unlimited head used to + // synthesise with a bogus imaginary part. + config.band_limit_hz = 12000.0F; + test::require_eq(sopro::band_limit_bin(config), int64_t{512}, "Nyquist cut"); + config.band_limit_hz = 48000.0F; + test::require_eq(sopro::band_limit_bin(config), int64_t{513}, "above Nyquist clamps"); + + // The cut follows the transform size, not a hardcoded bin index. + config.band_limit_hz = 10900.0F; + config.n_fft = 2048; + test::require_eq(sopro::band_limit_bin(config), int64_t{931}, "n_fft 2048 cut"); +} + +} // namespace + +int main() { + try { + test_speech_level_db_reads_a_flat_buffer(); + test_quiet_reference_is_boosted_to_the_prompt_level(); + test_hot_reference_is_left_alone(); + test_peak_guard_caps_the_boost(); + test_boost_is_limited_to_thirty_db(); + test_output_gain_tracks_the_reference_level(); + test_match_gain_falls_back_to_the_reference_level(); + test_band_limit_bin(); + } catch (const std::exception & error) { + std::cerr << "FAIL: " << error.what() << "\n"; + return 1; + } + std::cout << "PASS: sopro_tts audio ops checks\n"; + return 0; +} diff --git a/tests/unittests/test_attention_fallback.cpp b/tests/unittests/test_attention_fallback.cpp new file mode 100644 index 000000000..d3a35f2d0 --- /dev/null +++ b/tests/unittests/test_attention_fallback.cpp @@ -0,0 +1,68 @@ +#include "engine/framework/core/attention_fallback.h" + +#include "test_assert.h" + +#include +#include +#include + +namespace { + +using engine::core::AttentionPreference; +using engine::test::require; +using engine::test::require_eq; + +void test_parse_attention_preference() { + require_eq( + static_cast(engine::core::parse_attention_preference("auto", "attention")), + static_cast(AttentionPreference::Auto), + "parse auto"); + require_eq( + static_cast(engine::core::parse_attention_preference("flash", "attention")), + static_cast(AttentionPreference::Flash), + "parse flash"); + require_eq( + static_cast(engine::core::parse_attention_preference("eager", "attention")), + static_cast(AttentionPreference::Eager), + "parse eager"); + bool threw = false; + try { + engine::core::parse_attention_preference("sometimes", "breeze_tts.attention"); + } catch (const std::runtime_error & error) { + threw = true; + require( + std::string(error.what()).find("breeze_tts.attention") != std::string::npos, + "parse error names the option"); + } + require(threw, "parse invalid must throw"); +} + +void test_resolve_flash_attention() { + require( + engine::core::resolve_flash_attention(nullptr, 128, AttentionPreference::Flash), + "explicit flash resolves true"); + require( + !engine::core::resolve_flash_attention(nullptr, 128, AttentionPreference::Eager), + "explicit eager resolves false"); + // Null backend preserves historical behavior regardless of head_dim. + require( + engine::core::resolve_flash_attention(nullptr, 128, AttentionPreference::Auto), + "auto with null backend preserves flash"); + require( + engine::core::resolve_flash_attention(nullptr, -1, AttentionPreference::Auto), + "auto with bad head_dim preserves flash"); +} + +} // namespace + +int main() { + try { + test_parse_attention_preference(); + test_resolve_flash_attention(); + } catch (const std::exception & error) { + std::cerr << "attention_fallback_test failed: " << error.what() << '\n'; + return 1; + } + std::cout << "attention_fallback_test passed\n"; + return 0; +} diff --git a/tests/unittests/test_audio8_tts_falcon_kv_cache.cpp b/tests/unittests/test_audio8_tts_falcon_kv_cache.cpp new file mode 100644 index 000000000..40c3e0bbb --- /dev/null +++ b/tests/unittests/test_audio8_tts_falcon_kv_cache.cpp @@ -0,0 +1,66 @@ +// Regression test for the Falcon-H1 host KV cache append (audio8_tts 0.1B). +// +// The cache is stored in ggml's col-major [head_dim, seq, n_kv] layout where +// the per-head stride is the CURRENT sequence length. Appending a token +// without re-laying the existing entries into the new stride makes the new +// token overwrite the previous head blocks: with n_kv=2, appending token 1 +// wrote head 0 at floats [64,128) — exactly where token 0's head 1 lived — +// so from the second token on, attention read corrupted keys/values for every +// head past the first (argmax diverged from the HF reference at prompt step 3 +// and the recurrent state blew up on long sequences). +// +// This test feeds recognizable per-(token, head, dim) values through +// append_falcon_kv_token and checks the full cache contents after every +// append; the historical implementation fails from the second append on. + +#include "falcon_kv_cache.h" + +#include "test_assert.h" + +#include +#include +#include + +namespace { + +float marker(int64_t token, int64_t head, int64_t dim) { + return static_cast(token * 100000 + head * 1000 + dim); +} + +} // namespace + +int main() { + using engine::test::require; + using engine::test::require_eq; + using engine::models::audio8_tts::append_falcon_kv_token; + + constexpr int64_t n_kv = 2; + constexpr int64_t head_dim = 64; + constexpr int64_t n_tokens = 8; + + std::vector cache; + for (int64_t t = 0; t < n_tokens; ++t) { + std::vector fresh(static_cast(n_kv * head_dim)); + for (int64_t h = 0; h < n_kv; ++h) { + for (int64_t d = 0; d < head_dim; ++d) { + fresh[static_cast(d + head_dim * h)] = marker(t, h, d); + } + } + append_falcon_kv_token(cache, t, n_kv, head_dim, fresh.data()); + + const int64_t seq = t + 1; + require_eq(static_cast(cache.size()), seq * n_kv * head_dim, "cache size after append"); + for (int64_t h = 0; h < n_kv; ++h) { + for (int64_t tt = 0; tt < seq; ++tt) { + for (int64_t d = 0; d < head_dim; ++d) { + const float actual = cache[static_cast(d + head_dim * (tt + seq * h))]; + require(actual == marker(tt, h, d), + "cache entry corrupted after appending token " + std::to_string(t)); + } + } + } + } + + std::cout << "audio8_tts_falcon_kv_cache_test passed\n"; + return 0; +} diff --git a/tests/unittests/test_audio_dsp.cpp b/tests/unittests/test_audio_dsp.cpp index fe6dce63a..3439dab3e 100644 --- a/tests/unittests/test_audio_dsp.cpp +++ b/tests/unittests/test_audio_dsp.cpp @@ -345,8 +345,8 @@ void test_istft_matches_reference_across_configs_and_variants() { require_close( reconstructed.values, reference.values, - 2.0e-5f, - 2.0e-6, + 3.0e-5f, + 5.0e-6, "istft_variant_parity"); } } diff --git a/tests/unittests/test_encoder_modules.cpp b/tests/unittests/test_encoder_modules.cpp index 10785aea2..4abae5687 100644 --- a/tests/unittests/test_encoder_modules.cpp +++ b/tests/unittests/test_encoder_modules.cpp @@ -207,6 +207,7 @@ engine::runtime::GraphOptimizationBackend graph_optimizer_backend_for_test(engin case engine::core::BackendType::Cpu: return engine::runtime::GraphOptimizationBackend::Cpu; case engine::core::BackendType::Cuda: + case engine::core::BackendType::Hip: return engine::runtime::GraphOptimizationBackend::Gpu; case engine::core::BackendType::Vulkan: case engine::core::BackendType::Metal: diff --git a/tests/unittests/test_hf_sampler.cpp b/tests/unittests/test_hf_sampler.cpp index 421ea47e1..5aa3b783e 100644 --- a/tests/unittests/test_hf_sampler.cpp +++ b/tests/unittests/test_hf_sampler.cpp @@ -283,6 +283,28 @@ void test_matches_python_hf_processor_reference_values() { } } +void test_min_p_masks_relative_probability_tail() { + std::vector scores{4.0F, 2.0F, 1.0F, -1.0F}; + HfSamplerScratch scratch; + engine::sampling::HfLogitsProcessor::apply_min_p(scores, 0.1F, 1, scratch); + + engine::test::require(std::isfinite(scores[0]), "min-p keeps maximum token"); + engine::test::require(std::isfinite(scores[1]), "min-p keeps token above relative threshold"); + engine::test::require( + std::isinf(scores[2]) && scores[2] < 0.0F, + "min-p masks token below relative threshold"); + engine::test::require( + std::isinf(scores[3]) && scores[3] < 0.0F, + "min-p masks probability tail"); + + std::vector protected_scores{4.0F, 0.0F, -1.0F}; + engine::sampling::HfLogitsProcessor::apply_min_p( + protected_scores, 0.9F, 2, scratch); + engine::test::require( + std::isfinite(protected_scores[0]) && std::isfinite(protected_scores[1]), + "min-p honors min_tokens_to_keep"); +} + void test_matches_python_hf_cuda_multinomial_reference_sequence() { TorchCudaSamplingPolicy policy; policy.cuda_fast_path = true; @@ -506,6 +528,7 @@ int main() { try { test_greedy_fast_path_matches_reference(); test_matches_python_hf_processor_reference_values(); + test_min_p_masks_relative_probability_tail(); test_matches_python_hf_cuda_multinomial_reference_sequence(); test_matches_python_hf_cpu_multinomial_reference_sequence(); test_no_processor_sampling_fast_path_matches_reference(); diff --git a/tests/unittests/test_i2_s_mul_mat.cpp b/tests/unittests/test_i2_s_mul_mat.cpp new file mode 100644 index 000000000..e20928fb0 --- /dev/null +++ b/tests/unittests/test_i2_s_mul_mat.cpp @@ -0,0 +1,373 @@ +// Numeric checks for the ternary GGML_TYPE_I2_S matmul, which is what the +// VibeASR language model runs on. +// +// The op is reachable through plain ggml_mul_mat with an I2_S src0 and an F32 +// src1, and it quantizes src1 to int8 itself. Its arithmetic is entirely +// integral up to a single float multiply at the end, so the references below are +// exact rather than approximate: the reference computes sum(w*q) as int32 with +// w in {-1,0,+1} and multiplies by the same combined scale, which is bit for bit +// what the kernel does after it subtracts the row sum out of its {0,1,2} codes. +// A tolerance here would hide a wrong packing that happens to be close. + +#include "test_assert.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +extern "C" { +size_t ggml_type_extra_bytes(enum ggml_type type); +void ggml_i2_s_to_float (const void * x, float * y, int64_t n); +size_t ggml_i2_s_from_float(const float * x, void * y, int64_t n); +void ggml_i8_s_quantize_act(const float * x, int8_t * q, int64_t n, float * scale, int32_t * sum); +} + +using engine::test::require; +using engine::test::require_close; +using engine::test::require_eq; + +namespace { + +constexpr size_t kCtxBytes = 512u * 1024 * 1024; + +const float * inband_scale(const ggml_tensor * t) { + return reinterpret_cast( + static_cast(t->data) + ggml_nbytes(t) - ggml_type_extra_bytes(t->type)); +} + +// Decode the packing by hand instead of going through ggml_i2_s_to_float, so +// that the layout the kernel reads is pinned independently of the dequantizer +// that was written alongside it: 128 values per 32-byte group, byte gp holding +// the values at group-relative positions gp, 32+gp, 64+gp and 96+gp in bit pairs +// 6, 4, 2, 0. +std::vector unpack_i2_s(const void * data, int64_t n) { + const uint8_t * q = static_cast(data); + + std::vector out(static_cast(n)); + for (int64_t base = 0; base < n; base += 128) { + const uint8_t * group = q + base / 4; + + for (int gp = 0; gp < 32; ++gp) { + const uint8_t b = group[gp]; + + out[base + 0 + gp] = static_cast((b >> 6) & 3) - 1; + out[base + 32 + gp] = static_cast((b >> 4) & 3) - 1; + out[base + 64 + gp] = static_cast((b >> 2) & 3) - 1; + out[base + 96 + gp] = static_cast((b >> 0) & 3) - 1; + } + } + return out; +} + +// Ternary weights with a deliberate mix of all three values and a stride that is +// coprime with 32 and 128, so no code lands in the same bit position of every +// byte and a swapped shift shows up immediately. +std::vector ternary_pattern(size_t n, float scale, int phase) { + std::vector v(n); + for (size_t i = 0; i < n; ++i) { + const int k = static_cast((i * 7 + phase) % 3); + v[i] = scale * static_cast(k - 1); + } + return v; +} + +std::vector activation_pattern(size_t n, float phase, float scale) { + std::vector v(n); + for (size_t i = 0; i < n; ++i) { + const float x = static_cast(i); + v[i] = scale * (std::sin(phase + 0.11f * x) + 0.35f * std::cos(0.05f * x - phase)); + } + return v; +} + +void compute(ggml_context * ctx, ggml_tensor * result, int n_threads) { + ggml_cgraph * gf = ggml_new_graph(ctx); + ggml_build_forward_expand(gf, result); + ggml_graph_compute_with_ctx(ctx, gf, n_threads); +} + +// Reference for one batch of ggml_mul_mat(weight_i2_s, activations_f32). +// +// The activation quantization is the op's own, called directly: the point of +// this test is the packing, the code-to-weight bias, and the scale combination, +// not to re-derive an absmax. +std::vector reference(const std::vector & w, // [K*N], ternary + const std::vector & a, // [K*M] + int64_t K, int64_t N, int64_t M, + float w_scale) { + std::vector out(static_cast(N * M)); + + std::vector q(static_cast(K)); + for (int64_t col = 0; col < M; ++col) { + float act_scale = 0.0f; + int32_t act_sum = 0; + ggml_i8_s_quantize_act(a.data() + col * K, q.data(), K, &act_scale, &act_sum); + + const float d = w_scale * act_scale; + + for (int64_t oc = 0; oc < N; ++oc) { + int32_t dot = 0; + for (int64_t k = 0; k < K; ++k) { + dot += w[static_cast(oc * K + k)] * static_cast(q[static_cast(k)]); + } + out[static_cast(col * N + oc)] = static_cast(dot) * d; + } + } + return out; +} + +std::string shape_label(const char * what, int64_t K, int64_t N, int64_t M, int nth) { + return std::string(what) + " K=" + std::to_string(K) + " N=" + std::to_string(N) + + " M=" + std::to_string(M) + " nth=" + std::to_string(nth); +} + +// The packer is whole-tensor, so this also checks that a row-major [K, N] weight +// packs into groups that never straddle a row -- true because every I2_S row +// length in the model is a multiple of 128, and false the moment K is not. +void test_pack_layout() { + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + const int64_t K = 256; + const int64_t N = 3; + + ggml_tensor * w = ggml_new_tensor_2d(ctx, GGML_TYPE_I2_S, K, N); + + const std::vector values = ternary_pattern(static_cast(K * N), 0.75f, 1); + ggml_i2_s_from_float(values.data(), w->data, K * N); + + require_close(*inband_scale(w), 0.75f, 0.0f, "pack layout scale"); + + // Row size is ceil(K/128)*32 bytes, and nb[1] has to agree or the kernel + // walks into the wrong row. + require_eq(static_cast(w->nb[1]), K / 4, "pack layout row stride"); + + const std::vector codes = unpack_i2_s(w->data, K * N); + for (int64_t i = 0; i < K * N; ++i) { + const float expected = values[static_cast(i)]; + require_close(static_cast(codes[static_cast(i)]) * 0.75f, expected, 0.0f, + "pack layout value " + std::to_string(i)); + } + + // And the shipped dequantizer agrees with the hand decode. + std::vector dequantized(static_cast(K * N)); + ggml_i2_s_to_float(w->data, dequantized.data(), K * N); + for (int64_t i = 0; i < K * N; ++i) { + require_close(dequantized[static_cast(i)], values[static_cast(i)], 0.0f, + "pack layout dequantize " + std::to_string(i)); + } + + ggml_free(ctx); +} + +void test_mul_mat(int n_threads, int64_t K, int64_t N, int64_t M) { + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + ggml_tensor * w = ggml_new_tensor_2d(ctx, GGML_TYPE_I2_S, K, N); + ggml_tensor * a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, K, M); + + const float w_scale = 0.031f; + const std::vector w_values = ternary_pattern(static_cast(K * N), w_scale, static_cast(N)); + ggml_i2_s_from_float(w_values.data(), w->data, K * N); + + const std::vector a_values = activation_pattern(static_cast(K * M), 0.4f, 1.7f); + std::memcpy(a->data, a_values.data(), a_values.size() * sizeof(float)); + + ggml_tensor * result = ggml_mul_mat(ctx, w, a); + require_eq(result->ne[0], N, "mul_mat ne0"); + require_eq(result->ne[1], M, "mul_mat ne1"); + + compute(ctx, result, n_threads); + + const std::vector expected = + reference(unpack_i2_s(w->data, K * N), a_values, K, N, M, *inband_scale(w)); + + const float * got = static_cast(result->data); + for (int64_t i = 0; i < N * M; ++i) { + require_close(got[i], expected[static_cast(i)], 0.0f, + shape_label("mul_mat", K, N, M, n_threads) + " element " + std::to_string(i)); + } + + ggml_free(ctx); +} + +// src1 with a third dimension, so the row indexing has to walk nb12 and the +// output has to walk nb2. The weight is shared across the batch. +void test_mul_mat_batched(int n_threads) { + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + const int64_t K = 384; + const int64_t N = 70; + const int64_t M = 3; + const int64_t B = 4; + + ggml_tensor * w = ggml_new_tensor_2d(ctx, GGML_TYPE_I2_S, K, N); + ggml_tensor * a = ggml_new_tensor_3d(ctx, GGML_TYPE_F32, K, M, B); + + const std::vector w_values = ternary_pattern(static_cast(K * N), 0.02f, 2); + ggml_i2_s_from_float(w_values.data(), w->data, K * N); + + const std::vector a_values = activation_pattern(static_cast(K * M * B), 1.1f, 0.9f); + std::memcpy(a->data, a_values.data(), a_values.size() * sizeof(float)); + + ggml_tensor * result = ggml_mul_mat(ctx, w, a); + require_eq(result->ne[2], B, "batched ne2"); + + compute(ctx, result, n_threads); + + const std::vector codes = unpack_i2_s(w->data, K * N); + const float * got = static_cast(result->data); + + for (int64_t ib = 0; ib < B; ++ib) { + const std::vector slice(a_values.begin() + static_cast(ib * K * M), + a_values.begin() + static_cast((ib + 1) * K * M)); + const std::vector expected = reference(codes, slice, K, N, M, *inband_scale(w)); + + for (int64_t i = 0; i < N * M; ++i) { + require_close(got[ib * N * M + i], expected[static_cast(i)], 0.0f, + "batched batch " + std::to_string(ib) + " element " + std::to_string(i)); + } + } + + ggml_free(ctx); +} + +// Every code at its maximum (2) against every activation at its maximum (127) is +// the worst case for the int16 accumulation inside the AVX2 body: 32 lanes each +// summing 32 products of 2*127, i.e. 16256, which is why the kernel widens to +// int32 every eight groups rather than at the end of the row. K spans more than +// eight groups so the flush actually has to happen. +void test_accumulator_headroom(int n_threads) { + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + const int64_t K = 1536; + const int64_t N = 5; + + ggml_tensor * w = ggml_new_tensor_2d(ctx, GGML_TYPE_I2_S, K, N); + ggml_tensor * a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, K, 1); + + // All +1: every packed code is 2. + const std::vector w_values(static_cast(K * N), 0.5f); + ggml_i2_s_from_float(w_values.data(), w->data, K * N); + + // Flat, so the absmax is 1 and every quantized activation is exactly +127. + const std::vector a_values(static_cast(K), 1.0f); + std::memcpy(a->data, a_values.data(), a_values.size() * sizeof(float)); + + ggml_tensor * result = ggml_mul_mat(ctx, w, a); + compute(ctx, result, n_threads); + + const float expected = static_cast(K * 127) * (0.5f * (1.0f / 127.0f)); + const float * got = static_cast(result->data); + for (int64_t oc = 0; oc < N; ++oc) { + require_close(got[oc], expected, 0.0f, "headroom output " + std::to_string(oc)); + } + + ggml_free(ctx); +} + +// An all-zero weight tensor has scale 0, and an all-zero activation row has +// scale 0. Neither may produce a NaN: the scales are multipliers here, and a +// reciprocal convention would divide by zero in both cases. +void test_degenerate_scales(int n_threads) { + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + const int64_t K = 128; + const int64_t N = 8; + const int64_t M = 2; + + ggml_tensor * w = ggml_new_tensor_2d(ctx, GGML_TYPE_I2_S, K, N); + ggml_tensor * a = ggml_new_tensor_2d(ctx, GGML_TYPE_F32, K, M); + + const std::vector zeros_w(static_cast(K * N), 0.0f); + ggml_i2_s_from_float(zeros_w.data(), w->data, K * N); + require_close(*inband_scale(w), 0.0f, 0.0f, "degenerate weight scale"); + + std::vector a_values = activation_pattern(static_cast(K * M), 0.2f, 1.0f); + std::memcpy(a->data, a_values.data(), a_values.size() * sizeof(float)); + + ggml_tensor * zero_w_result = ggml_mul_mat(ctx, w, a); + compute(ctx, zero_w_result, n_threads); + + const float * got = static_cast(zero_w_result->data); + for (int64_t i = 0; i < N * M; ++i) { + require(std::isfinite(got[i]), "degenerate weight output finite " + std::to_string(i)); + require_close(got[i], 0.0f, 0.0f, "degenerate weight output " + std::to_string(i)); + } + + // Now a real weight against a zero activation row. Only the second column is + // zeroed, so the first still has to come out right. + const std::vector w_values = ternary_pattern(static_cast(K * N), 0.25f, 0); + ggml_i2_s_from_float(w_values.data(), w->data, K * N); + + for (int64_t k = 0; k < K; ++k) { + a_values[static_cast(K + k)] = 0.0f; + } + std::memcpy(a->data, a_values.data(), a_values.size() * sizeof(float)); + + ggml_tensor * zero_act_result = ggml_mul_mat(ctx, w, a); + compute(ctx, zero_act_result, n_threads); + + const std::vector expected = + reference(unpack_i2_s(w->data, K * N), a_values, K, N, M, *inband_scale(w)); + + got = static_cast(zero_act_result->data); + for (int64_t i = 0; i < N * M; ++i) { + require(std::isfinite(got[i]), "degenerate activation output finite " + std::to_string(i)); + require_close(got[i], expected[static_cast(i)], 0.0f, + "degenerate activation output " + std::to_string(i)); + } + for (int64_t oc = 0; oc < N; ++oc) { + require_close(got[N + oc], 0.0f, 0.0f, "degenerate activation zero column " + std::to_string(oc)); + } + + ggml_free(ctx); +} + +} // namespace + +int main() { + try { + test_pack_layout(); + + // Single- and multi-threaded: the activation quantization and the output + // pass are separated by a barrier, and threads split the output features, + // so a missing barrier or an overlapping split shows up as a thread-count + // dependent result. + for (int nth : {1, 4}) { + // K=128 is one group. K=1024 is exactly the eight groups the AVX2 + // body accumulates in int16 before widening; K=1152 is that plus one, + // so the second, shorter flush runs too. + // N=1 is a single output row (the decode-time lm_head shape), N=64 is + // exactly one output-channel chunk, N=100 spans two, and N=70 is an + // unaligned span. + test_mul_mat(nth, 128, 1, 1); + test_mul_mat(nth, 128, 64, 1); + test_mul_mat(nth, 128, 100, 5); + test_mul_mat(nth, 1024, 70, 1); + test_mul_mat(nth, 1152, 70, 3); + // Fewer output features than threads: the tail threads get an empty + // range and must not write outside it. + test_mul_mat(nth, 256, 2, 2); + test_mul_mat_batched(nth); + test_accumulator_headroom(nth); + test_degenerate_scales(nth); + } + } catch (const std::exception & e) { + std::cerr << "FAILED: " << e.what() << "\n"; + return 1; + } + + std::cout << "all i2_s mul_mat tests passed\n"; + return 0; +} diff --git a/tests/unittests/test_i8_s_fused_ops.cpp b/tests/unittests/test_i8_s_fused_ops.cpp new file mode 100644 index 000000000..8f58519e6 --- /dev/null +++ b/tests/unittests/test_i8_s_fused_ops.cpp @@ -0,0 +1,550 @@ +// Numeric checks for the VibeASR INT8 pipeline additions to ggml: +// GGML_TYPE_I8_S / GGML_TYPE_I2_S and the five fused ops built on them. +// +// Every op is compared against a plain-loop reference computed from the same +// inputs. The references duplicate the requantization ordering deliberately, +// including the parts that lose range -- ggml_mul_mat_add_relu clamps after +// rounding, so the absmax that sets the output scale still counts the negatives +// that are about to become zero. The point of these tests is to pin the +// arithmetic that the SIMD kernels and the model port have to reproduce, not to +// judge whether that arithmetic is optimal. + +#include "test_assert.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +// The in-band scale layout is internal to ggml, so the tests reach for the same +// declarations the implementation uses rather than re-deriving the offsets. +extern "C" { +size_t ggml_type_extra_bytes(enum ggml_type type); +void ggml_i8_s_to_float (const void * x, float * y, int64_t n); +size_t ggml_i8_s_from_float(const float * x, void * y, int64_t n); +void ggml_i2_s_to_float (const void * x, float * y, int64_t n); +size_t ggml_i2_s_from_float(const float * x, void * y, int64_t n); +} + +using engine::test::require; +using engine::test::require_close; +using engine::test::require_eq; + +namespace { + +constexpr size_t kCtxBytes = 256u * 1024 * 1024; + +float * inband_scale(ggml_tensor * t) { + return reinterpret_cast( + static_cast(t->data) + ggml_nbytes(t) - ggml_type_extra_bytes(t->type)); +} + +// Quantize into an existing I8_S tensor, returning the scale that was chosen. +float fill_i8_s(ggml_tensor * t, const std::vector & values) { + require_eq(static_cast(values.size()), ggml_nelements(t), "fill_i8_s size"); + ggml_i8_s_from_float(values.data(), t->data, ggml_nelements(t)); + return *inband_scale(t); +} + +std::vector read_i8_s(ggml_tensor * t) { + std::vector out(ggml_nelements(t)); + ggml_i8_s_to_float(t->data, out.data(), ggml_nelements(t)); + return out; +} + +std::vector patterned(size_t n, float phase, float scale) { + std::vector v(n); + for (size_t i = 0; i < n; ++i) { + const float x = static_cast(i); + v[i] = scale * (std::sin(phase + 0.13f * x) + 0.4f * std::cos(0.07f * x - phase)); + } + return v; +} + +// The reference requantizer, mirroring ggml_i8_s_requantize: absmax over the +// whole result -- taken before the relu clamp, so a value about to be zeroed +// still widens the scale -- then clamp, then round ties to even. +// +// std::rint, not std::round: the op rounds ties to even so that its AVX2 and +// NEON bodies agree with their scalar tails. std::round would round ties away +// from zero and make the byte-exact comparisons below fail on any tie. +std::vector requantize_ref(const std::vector & values, bool relu, float * scale_out) { + float amax = 0.0f; + for (float v : values) { + amax = std::max(amax, std::fabs(v)); + } + + const float id = amax != 0.0f ? 127.0f / amax : 0.0f; + const float lo = relu ? 0.0f : -127.0f; + + std::vector q(values.size()); + for (size_t i = 0; i < values.size(); ++i) { + float v = values[i] * id; + v = std::max(lo, std::min(127.0f, v)); + q[i] = static_cast(std::rint(v)); + } + + *scale_out = amax != 0.0f ? amax / 127.0f : 0.0f; + return q; +} + +void compare_i8(ggml_tensor * got, const std::vector & want, float want_scale, + const std::string & label) { + require_eq(static_cast(want.size()), ggml_nelements(got), label + " size"); + + const auto * q = static_cast(got->data); + for (size_t i = 0; i < want.size(); ++i) { + // Exact: both sides do the same rounding on the same floats. A single + // LSB of drift would mean the scale differs, which is what matters. + require_eq(static_cast(q[i]), static_cast(want[i]), + label + " value at " + std::to_string(i)); + } + + require_close(*inband_scale(got), want_scale, 1e-9f, label + " scale"); +} + +// Runs a single-node graph on the CPU backend with the given thread count. The +// four requantizing ops reduce an absmax across threads, so the result has to be +// independent of nth -- these tests run every case at 1 and 4. +void compute(ggml_context * ctx, ggml_tensor * result, int n_threads) { + ggml_cgraph * gf = ggml_new_graph(ctx); + ggml_build_forward_expand(gf, result); + ggml_graph_compute_with_ctx(ctx, gf, n_threads); +} + +// ---------------------------------------------------------------- round trips + +void test_i8_s_round_trip() { + const int64_t n = 4096; + const auto values = patterned(n, 0.3f, 2.5f); + + std::vector buf(n + ggml_type_extra_bytes(GGML_TYPE_I8_S)); + const size_t written = ggml_i8_s_from_float(values.data(), buf.data(), n); + require_eq(written, buf.size(), "i8_s written bytes"); + + std::vector back(n); + ggml_i8_s_to_float(buf.data(), back.data(), n); + + float amax = 0.0f; + for (float v : values) { + amax = std::max(amax, std::fabs(v)); + } + // One step of the quantization grid is amax/127; rounding puts every value + // within half of that. + const float tol = amax / 127.0f * 0.5f + 1e-6f; + + for (int64_t i = 0; i < n; ++i) { + require_close(back[i], values[i], tol, "i8_s round trip at " + std::to_string(i)); + } + + std::cout << "i8_s round trip: n=" << n << " step=" << amax / 127.0f << " OK\n"; +} + +void test_i2_s_round_trip() { + // Ternary input: the type represents {-d, 0, +d} exactly, so this has to + // round trip bit for bit rather than approximately. + const int64_t n = 128 * 7; + const float d = 0.0731f; + + std::vector values(n); + for (int64_t i = 0; i < n; ++i) { + const int code = static_cast(i * 7 % 3) - 1; // -1, 0, +1 cycling + values[i] = static_cast(code) * d; + } + + const size_t payload = static_cast(n / 128) * 32; + std::vector buf(payload + ggml_type_extra_bytes(GGML_TYPE_I2_S)); + const size_t written = ggml_i2_s_from_float(values.data(), buf.data(), n); + require_eq(written, buf.size(), "i2_s written bytes"); + + std::vector back(n); + ggml_i2_s_to_float(buf.data(), back.data(), n); + + for (int64_t i = 0; i < n; ++i) { + require_close(back[i], values[i], 1e-7f, "i2_s round trip at " + std::to_string(i)); + } + + // Check the packing itself, not just the round trip: byte gp of a group + // holds positions gp, 32+gp, 64+gp, 96+gp in bit pairs 6, 4, 2, 0. A + // self-consistent but differently-ordered packing would pass the round trip + // above while being unreadable by the SIMD kernels. + for (int gp = 0; gp < 32; ++gp) { + const uint8_t b = buf[gp]; + for (int sub = 0; sub < 4; ++sub) { + const int code = (b >> (6 - 2 * sub)) & 3; + const int want = static_cast((sub * 32 + gp) * 7 % 3) - 1; + const float value = static_cast(code - 1); + require_close(value, static_cast(want), 1e-7f, + "i2_s bit layout at byte " + std::to_string(gp) + + " pair " + std::to_string(sub)); + } + } + + std::cout << "i2_s round trip: n=" << n << " exact, bit layout OK\n"; +} + +// ---------------------------------------------------------------------- ops + +void test_add_scaled(int n_threads) { + const int64_t C = 96; // channels, ne[0] + const int64_t L = 37; // positions + + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + ggml_tensor * a = ggml_new_tensor_2d(ctx, GGML_TYPE_I8_S, C, L); + ggml_tensor * b = ggml_new_tensor_2d(ctx, GGML_TYPE_I8_S, C, L); + ggml_tensor * gamma = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, C); + + const auto a_f = patterned(C * L, 0.1f, 1.7f); + const auto b_f = patterned(C * L, 1.9f, 0.6f); + + fill_i8_s(a, a_f); + fill_i8_s(b, b_f); + + const auto g = patterned(C, 0.5f, 0.25f); + std::memcpy(gamma->data, g.data(), g.size() * sizeof(float)); + + ggml_tensor * result = ggml_add_scaled(ctx, a, b, gamma); + compute(ctx, result, n_threads); + + // Reference works from the dequantized inputs, so it sees exactly what the + // op sees -- the input quantization error is shared, not compounded. + const auto a_q = read_i8_s(a); + const auto b_q = read_i8_s(b); + + std::vector want(C * L); + for (int64_t i = 0; i < C * L; ++i) { + want[i] = a_q[i] * g[i % C] + b_q[i]; + } + + float want_scale = 0.0f; + const auto want_q = requantize_ref(want, false, &want_scale); + compare_i8(result, want_q, want_scale, "add_scaled nth=" + std::to_string(n_threads)); + + ggml_free(ctx); + std::cout << "add_scaled: C=" << C << " L=" << L << " nth=" << n_threads << " OK\n"; +} + +void test_rms_norm_scaled(int n_threads) { + const int64_t C = 128; + const int64_t L = 29; + const float eps = 1e-5f; + + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + ggml_tensor * a = ggml_new_tensor_2d(ctx, GGML_TYPE_I8_S, C, L); + ggml_tensor * gamma = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, C); + + const auto a_f = patterned(C * L, 0.7f, 3.1f); + fill_i8_s(a, a_f); + + const auto g = patterned(C, 1.1f, 0.9f); + std::memcpy(gamma->data, g.data(), g.size() * sizeof(float)); + + ggml_tensor * result = ggml_rms_norm_scaled(ctx, a, gamma, eps); + compute(ctx, result, n_threads); + + // Reference normalizes in the float domain. The op instead cancels the input + // scale and keeps the sum of squares in integers, which is the same value + // computed a different way -- so this also checks that the eps rescaling is + // right, since eps is the one term that does not cancel. + const auto a_q = read_i8_s(a); + + std::vector want(C * L); + for (int64_t row = 0; row < L; ++row) { + double sum_sq = 0.0; + for (int64_t i = 0; i < C; ++i) { + const double v = a_q[row * C + i]; + sum_sq += v * v; + } + const float rms_inv = 1.0f / std::sqrt(static_cast(sum_sq / C) + eps); + for (int64_t i = 0; i < C; ++i) { + want[row * C + i] = a_q[row * C + i] * rms_inv * g[i]; + } + } + + float want_scale = 0.0f; + const auto want_q = requantize_ref(want, false, &want_scale); + + // The two paths reach the same value through different arithmetic, so a + // borderline element can round to either side of a grid step. Compare the + // dequantized results with a tolerance of one step instead of demanding + // identical bytes. + require_close(*inband_scale(result), want_scale, want_scale * 1e-4f, + "rms_norm_scaled scale nth=" + std::to_string(n_threads)); + + const auto * q = static_cast(result->data); + for (int64_t i = 0; i < C * L; ++i) { + require_close(static_cast(q[i]), static_cast(want_q[i]), 1.0f, + "rms_norm_scaled value at " + std::to_string(i)); + } + + ggml_free(ctx); + std::cout << "rms_norm_scaled: C=" << C << " L=" << L << " nth=" << n_threads << " OK\n"; +} + +// IC and OC are varied by the caller rather than fixed: IC decides how much of +// the contraction the SIMD path covers and how much falls to the scalar tail, +// and OC decides whether the output-channel chunking loop wraps around. +void test_mul_mat_add(bool relu, int n_threads, int64_t IC, int64_t OC) { + const int64_t N = 23; + + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + ggml_tensor * w = ggml_new_tensor_2d(ctx, GGML_TYPE_I8_S, IC, OC); + ggml_tensor * x = ggml_new_tensor_2d(ctx, GGML_TYPE_I8_S, IC, N); + ggml_tensor * bias = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, OC); + + fill_i8_s(w, patterned(IC * OC, 0.2f, 0.8f)); + fill_i8_s(x, patterned(IC * N, 1.3f, 2.2f)); + + const auto b = patterned(OC, 0.9f, 0.15f); + std::memcpy(bias->data, b.data(), b.size() * sizeof(float)); + + ggml_tensor * result = relu ? ggml_mul_mat_add_relu(ctx, w, x, bias) + : ggml_mul_mat_add(ctx, w, x, bias); + compute(ctx, result, n_threads); + + require_eq(result->ne[0], OC, "mul_mat_add ne0"); + require_eq(result->ne[1], N, "mul_mat_add ne1"); + require_eq(static_cast(result->type), static_cast(GGML_TYPE_I8_S), + "mul_mat_add type"); + + const auto w_q = read_i8_s(w); + const auto x_q = read_i8_s(x); + + std::vector want(OC * N); + for (int64_t col = 0; col < N; ++col) { + for (int64_t oc = 0; oc < OC; ++oc) { + double acc = 0.0; + for (int64_t k = 0; k < IC; ++k) { + acc += static_cast(w_q[oc * IC + k]) * x_q[col * IC + k]; + } + want[col * OC + oc] = static_cast(acc) + b[oc]; + } + } + + float want_scale = 0.0f; + const auto want_q = requantize_ref(want, relu, &want_scale); + + const std::string label = std::string(relu ? "mul_mat_add_relu" : "mul_mat_add") + + " IC=" + std::to_string(IC) + " OC=" + std::to_string(OC) + + " nth=" + std::to_string(n_threads); + + require_close(*inband_scale(result), want_scale, want_scale * 1e-4f, label + " scale"); + + const auto * q = static_cast(result->data); + bool saw_negative_input = false; + for (int64_t i = 0; i < OC * N; ++i) { + require_close(static_cast(q[i]), static_cast(want_q[i]), 1.0f, + label + " value at " + std::to_string(i)); + if (want[i] < 0.0f) saw_negative_input = true; + } + + if (relu) { + // Without negatives in the pre-activation the clamp would be untested. + require(saw_negative_input, label + ": inputs never went negative"); + for (int64_t i = 0; i < OC * N; ++i) { + require(q[i] >= 0, label + " left a negative at " + std::to_string(i)); + } + } + + ggml_free(ctx); + std::cout << label << " N=" << N << " OK\n"; +} + +// K is the contraction length, so it decides how much of the dot product the SIMD +// path covers. N is the number of positions, which is what the depthwise path +// batches -- so it decides whether the chunking loop wraps around. +void test_mul_mat_add_depthwise(int n_threads, int64_t K, int64_t N) { + const int64_t C = 40; // channels + + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + // ne[1] == 1 with ne[2] > 1 is what selects the depthwise path. + ggml_tensor * w = ggml_new_tensor_3d(ctx, GGML_TYPE_I8_S, K, 1, C); + ggml_tensor * x = ggml_new_tensor_3d(ctx, GGML_TYPE_I8_S, K, N, C); + ggml_tensor * bias = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, C); + + fill_i8_s(w, patterned(K * C, 0.4f, 1.1f)); + fill_i8_s(x, patterned(K * N * C, 1.7f, 1.9f)); + + const auto b = patterned(C, 0.2f, 0.3f); + std::memcpy(bias->data, b.data(), b.size() * sizeof(float)); + + ggml_tensor * result = ggml_mul_mat_add(ctx, w, x, bias); + compute(ctx, result, n_threads); + + const auto w_q = read_i8_s(w); + const auto x_q = read_i8_s(x); + + // Output index is ch*N + col: channel-major, one row of N per channel. + std::vector want(C * N); + for (int64_t ch = 0; ch < C; ++ch) { + for (int64_t col = 0; col < N; ++col) { + double acc = 0.0; + for (int64_t k = 0; k < K; ++k) { + acc += static_cast(w_q[ch * K + k]) * x_q[ch * N * K + col * K + k]; + } + want[ch * N + col] = static_cast(acc) + b[ch]; + } + } + + float want_scale = 0.0f; + const auto want_q = requantize_ref(want, false, &want_scale); + + const std::string label = "mul_mat_add depthwise K=" + std::to_string(K) + + " N=" + std::to_string(N) + + " nth=" + std::to_string(n_threads); + require_close(*inband_scale(result), want_scale, want_scale * 1e-4f, label + " scale"); + + const auto * q = static_cast(result->data); + for (int64_t i = 0; i < C * N; ++i) { + require_close(static_cast(q[i]), static_cast(want_q[i]), 1.0f, + label + " value at " + std::to_string(i)); + } + + ggml_free(ctx); + std::cout << label << ": C=" << C << " OK\n"; +} + +void test_im2col_asym(int n_threads) { + const int64_t IW = 33; + const int64_t IC = 5; + const int64_t KW = 4; + const int s0 = 2; + const int lp0 = 3; // asymmetric on purpose: the whole reason this op + const int rp0 = 0; // exists is that ggml_im2col cannot express it + const int d0 = 1; + + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + ggml_tensor * kernel = ggml_new_tensor_3d(ctx, GGML_TYPE_I8_S, KW, IC, 1); + ggml_tensor * input = ggml_new_tensor_3d(ctx, GGML_TYPE_I8_S, IW, IC, 1); + + fill_i8_s(kernel, patterned(KW * IC, 0.1f, 1.0f)); + const float in_scale = fill_i8_s(input, patterned(IW * IC, 0.8f, 1.4f)); + + ggml_tensor * result = ggml_im2col_asym(ctx, kernel, input, s0, 0, lp0, rp0, 0, + d0, 0, false, GGML_TYPE_I8_S); + + const int64_t OW = (IW + lp0 + rp0 - d0 * (KW - 1) - 1) / s0 + 1; + require_eq(result->ne[0], IC * KW, "im2col_asym ne0"); + require_eq(result->ne[1], OW, "im2col_asym ne1"); + + compute(ctx, result, n_threads); + + const auto * in = static_cast(input->data); + const auto * out = static_cast(result->data); + + for (int64_t iow = 0; iow < OW; ++iow) { + for (int64_t iic = 0; iic < IC; ++iic) { + for (int64_t ikw = 0; ikw < KW; ++ikw) { + const int64_t iiw = iow * s0 + ikw * d0 - lp0; + const int8_t want = (iiw < 0 || iiw >= IW) ? 0 : in[iic * IW + iiw]; + require_eq(static_cast(out[iow * (IC * KW) + iic * KW + ikw]), + static_cast(want), + "im2col_asym at ow=" + std::to_string(iow) + + " ic=" + std::to_string(iic) + " kw=" + std::to_string(ikw)); + } + } + } + + // Rearrangement only, so the scale must pass through untouched. + require_close(*inband_scale(result), in_scale, 0.0f, "im2col_asym scale passthrough"); + + ggml_free(ctx); + std::cout << "im2col_asym: IW=" << IW << " KW=" << KW << " lp0=" << lp0 + << " OW=" << OW << " nth=" << n_threads << " OK\n"; +} + +// The VibeASR encoder flips its activations between channel-major and +// length-major with ggml_cont(ggml_permute(...)), which lands in +// ggml_compute_forward_dup. A byte copy moves the payload but not the in-band +// scale, and the permuted view it is handed has no scale of its own, so the +// scale has to be read through view_src. Getting this wrong leaves the copy +// holding whatever was in the buffer -- values stay right while everything +// downstream is off by an arbitrary factor. +void test_i8_s_cont_permute(int n_threads) { + const int64_t C = 5; + const int64_t L = 33; + + ggml_init_params ip = { kCtxBytes, nullptr, false }; + ggml_context * ctx = ggml_init(ip); + + ggml_tensor * src = ggml_new_tensor_2d(ctx, GGML_TYPE_I8_S, C, L); + const float src_scale = fill_i8_s(src, patterned(C * L, 0.6f, 1.9f)); + + ggml_tensor * result = ggml_cont(ctx, ggml_permute(ctx, src, 1, 0, 2, 3)); + require_eq(result->ne[0], L, "cont(permute) ne0"); + require_eq(result->ne[1], C, "cont(permute) ne1"); + + compute(ctx, result, n_threads); + + const auto * in = static_cast(src->data); + const auto * out = static_cast(result->data); + for (int64_t ic = 0; ic < C; ++ic) { + for (int64_t il = 0; il < L; ++il) { + require_eq(static_cast(out[ic * L + il]), static_cast(in[il * C + ic]), + "cont(permute) value at c=" + std::to_string(ic) + " l=" + std::to_string(il)); + } + } + + // Rearrangement only: the same scale has to come out the other side. + require_close(*inband_scale(result), src_scale, 0.0f, "cont(permute) scale carried over"); + + ggml_free(ctx); + std::cout << "cont(permute) i8_s: C=" << C << " L=" << L + << " nth=" << n_threads << " OK\n"; +} + +} // namespace + +int main() { + try { + test_i8_s_round_trip(); + test_i2_s_round_trip(); + + // Every requantizing op reduces an absmax across threads, so each is run + // single-threaded and multi-threaded; a missing barrier shows up as a + // scale that depends on the thread count. + for (int nth : {1, 4}) { + test_add_scaled(nth); + test_rms_norm_scaled(nth); + // IC=64: SIMD only, no tail. IC=67: three elements land in the + // scalar tail, which a truncating block count would silently drop. + // IC=13: shorter than one 32-byte step, so entirely scalar. + // OC=48 stays inside one output-channel chunk, OC=100 spans two. + test_mul_mat_add(false, nth, 64, 48); + test_mul_mat_add(true, nth, 64, 48); + test_mul_mat_add(false, nth, 67, 48); + test_mul_mat_add(false, nth, 13, 48); + test_mul_mat_add(false, nth, 67, 100); + // K=7 is shorter than any vector step, so the dot is all scalar; + // K=36 runs the 32-byte body plus a 4-element tail. N=19 stays inside + // one position chunk, N=150 spans three. + test_mul_mat_add_depthwise(nth, 7, 19); + test_mul_mat_add_depthwise(nth, 36, 19); + test_mul_mat_add_depthwise(nth, 7, 150); + test_im2col_asym(nth); + test_i8_s_cont_permute(nth); + } + } catch (const std::exception & e) { + std::cerr << "FAILED: " << e.what() << "\n"; + return 1; + } + + std::cout << "all i8_s fused op tests passed\n"; + return 0; +} diff --git a/tests/unittests/test_model_spec_system.cpp b/tests/unittests/test_model_spec_system.cpp index 6c9230e23..c190f8a3c 100644 --- a/tests/unittests/test_model_spec_system.cpp +++ b/tests/unittests/test_model_spec_system.cpp @@ -227,6 +227,32 @@ void expect_rejects(const std::string & label, const std::string & spec_text, co engine::test::require(rejected, label + " should reject with: " + needle); } +std::string spec_with_download(const std::string & download) { + auto text = schema_v1_spec_text("[]"); + const std::string anchor = "\"download\": {\"kind\": \"unsupported\", \"reason\": \"test fixture\"}"; + const auto at = text.find(anchor); + engine::test::require(at != std::string::npos, "download fixture anchor exists"); + text.replace(at, anchor.size(), "\"download\": " + download); + return text; +} + +void test_download_kinds_schema() { + // ModelScope snapshot downloads validate like Hugging Face snapshots: + // repo is required, revision is optional. + engine::model_spec::validate_spec( + json::parse(spec_with_download( + R"JSON({"kind": "modelscope_snapshot", "repo": "audio-cpp/toy-model"})JSON")), + "modelscope_snapshot_repo_only"); + engine::model_spec::validate_spec( + json::parse(spec_with_download( + R"JSON({"kind": "modelscope_snapshot", "repo": "audio-cpp/toy-model", "revision": "master"})JSON")), + "modelscope_snapshot_with_revision"); + expect_rejects( + "modelscope_snapshot_missing_repo", + spec_with_download(R"JSON({"kind": "modelscope_snapshot"})JSON"), + "missing required field 'repo'"); +} + void test_legacy_dependencies_schema() { // Valid legacy specs accept required model dependencies and conditional bundled dependencies. const auto spec = json::parse(schema_v1_spec_text(R"JSON([ @@ -1223,6 +1249,43 @@ void test_legacy_spec_contract_behavior_unchanged() { std::filesystem::remove_all(root); } +void test_experimental_spec_without_installable_package() { + const std::string experimental = R"JSON({ + "schema_version": 1, + "family": "local_only_model", + "display_name": "Local Only Model", + "description": "Requires a local checkpoint conversion.", + "category": "tts", + "status": "experimental", + "tasks": ["tts"], + "modes": ["offline"], + "languages": ["en"], + "runtime": {"tags": ["gguf"]}, + "capabilities": {}, + "options": {"request": [], "session": [], "load": []}, + "packages": [], + "dependencies": [], + "ui": {"tags": ["TTS"], "docs": ["docs/local.md"]}, + "sources": [{ + "format": "safetensors", + "roots": {"model": "."}, + "files": {"config": "model:config.json"}, + "tensors": {"weights": "model:model.safetensors"} + }] + })JSON"; + engine::model_spec::validate_spec( + json::parse(experimental), "experimental_local_only"); + + auto community = experimental; + const auto status = community.find("\"status\": \"experimental\""); + engine::test::require(status != std::string::npos, "experimental status fixture"); + community.replace(status, std::string("\"status\": \"experimental\"").size(), + "\"status\": \"community\""); + expect_rejects( + "community_requires_package", community, + "packages must not be empty unless status is experimental"); +} + void test_contract_spec_prefers_workspace_over_package_local_spec() { const auto root = make_temp_root(); const auto workspace = root / "workspace"; @@ -1284,6 +1347,7 @@ void test_loading_and_resource_bundle() { int main() { try { test_legacy_dependencies_schema(); + test_download_kinds_schema(); test_typed_schema_renamed_dependencies(); test_dependency_option_mapping_from_production_spec(); test_options_schema(); @@ -1291,6 +1355,7 @@ int main() { test_schema_v1_metadata_projection(); test_contract_projection_ignores_package_metadata_validation(); test_legacy_spec_contract_behavior_unchanged(); + test_experimental_spec_without_installable_package(); test_contract_spec_prefers_workspace_over_package_local_spec(); test_loading_and_resource_bundle(); } catch (const std::exception & error) { diff --git a/tests/unittests/test_package_manager_modelscope.cpp b/tests/unittests/test_package_manager_modelscope.cpp new file mode 100644 index 000000000..81d4f8c70 --- /dev/null +++ b/tests/unittests/test_package_manager_modelscope.cpp @@ -0,0 +1,147 @@ +#include "engine/framework/package_manager/manager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +void require(bool condition, const std::string & message) { + if (!condition) throw std::runtime_error(message); +} + +std::filesystem::path make_root() { + const auto suffix = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()).count(); + const auto root = std::filesystem::temp_directory_path() / + ("audiocpp-modelscope-package-manager-test-" + std::to_string(suffix)); + std::filesystem::create_directories(root); + return root; +} + +void write(const std::filesystem::path & path, const std::string & value) { + std::filesystem::create_directories(path.parent_path()); + std::ofstream output(path, std::ios::binary | std::ios::trunc); + output << value; +} + +std::string read(const std::filesystem::path & path) { + std::ifstream input(path, std::ios::binary); + std::ostringstream buffer; + buffer << input.rdbuf(); + return buffer.str(); +} + +void set_base_url(const std::string & value) { +#ifdef _WIN32 + _putenv_s("AUDIOCPP_MS_BASE_URL", value.c_str()); +#else + setenv("AUDIOCPP_MS_BASE_URL", value.c_str(), 1); +#endif +} + +void set_env(const char * name, const std::string & value) { +#ifdef _WIN32 + _putenv_s(name, value.c_str()); +#else + setenv(name, value.c_str(), 1); +#endif +} + +void test_modelscope_package_lifecycle() { + const auto root = make_root(); + try { + // No explicit revision: modelscope_snapshot must default to master. + write(root / "model_specs" / "demo_ms.json", R"JSON({ + "family":"demo_ms","display_name":"Demo MS","description":"","category":"tts", + "status":"supported","tasks":["tts"],"modes":["offline"],"languages":[], + "capabilities":{},"runtime":{}, + "package_defaults":{"download":{"kind":"modelscope_snapshot","repo":"ms/repo"}}, + "packages":[ + {"id":"demo_ms_q8","display_name":"Demo MS Q8","default":true,"format":"gguf","precision":"q8_0", + "target_directory":"DemoMS","files":["Demo/model-q8.gguf","Demo/shared.json"],"strip_prefix":"Demo"} + ],"sources":[] + })JSON"); + set_base_url("http://127.0.0.1:18992"); + // The fixture rejects any ModelScope request carrying this HF token, + // and requires every ModelScope request to carry AUDIOCPP_MS_TOKEN. + set_env("HF_TOKEN", "hf-secret-fixture-token"); + set_env("AUDIOCPP_MS_TOKEN", "ms-secret-fixture-token"); + auto fixture = std::async(std::launch::async, [] { +#ifdef _WIN32 + return std::system("python \"" AUDIOCPP_NATIVE_MANAGER_FIXTURE "\" --port 18992 --requests 4" + " --hf-token hf-secret-fixture-token --ms-token ms-secret-fixture-token"); +#else + return std::system("python3 \"" AUDIOCPP_NATIVE_MANAGER_FIXTURE "\" --port 18992 --requests 4" + " --hf-token hf-secret-fixture-token --ms-token ms-secret-fixture-token"); +#endif + }); + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + + engine::package_manager::PackageManager manager(root, root / "models"); + uint64_t last_total = 0; + const auto installed = manager.install("demo_ms_q8", false, nullptr, + [&](const engine::package_manager::PackageProgress & progress) { + last_total = progress.total_bytes; + }); + require(installed.find("Installed demo_ms_q8") != std::string::npos, + "modelscope package installs through the fixture"); + require(last_total == 16 + 16, "listing sizes drive the download total"); + require(std::filesystem::is_regular_file(root / "models" / "DemoMS" / "model-q8.gguf"), + "model payload is installed"); + require(std::filesystem::is_regular_file(root / "models" / "DemoMS" / "shared.json"), + "sidecar is installed"); + + const auto manifest_path = + root / "models" / "DemoMS" / ".audiocpp-package-demo_ms_q8.json"; + require(std::filesystem::is_regular_file(manifest_path), + "native install writes a version manifest"); + const auto manifest = read(manifest_path); + require(manifest.find("\"requested_revision\":\"master\"") != std::string::npos, + "modelscope packages default to the master revision"); + require(manifest.find("\"resolved_revision\":\"master\"") != std::string::npos, + "resolved revision falls back to the requested revision"); + require(manifest.find("\"etag\":\"1a53a6a47a59980589f5c699aa3da20ee9502d67224708a2bf5113852e1e1fa6\"") + != std::string::npos, + "manifest records the ModelScope sha256 as the etag"); + + const auto again = manager.install("demo_ms_q8", false, nullptr, nullptr); + require(again.find("Already installed demo_ms_q8") != std::string::npos, + "re-install without overwrite is a no-op"); + + const auto inventory = manager.inventory(true); + require(inventory.find("\"id\":\"demo_ms_q8\"") != std::string::npos, + "inventory includes the modelscope package"); + require(inventory.find("\"version_state\":\"up_to_date\"") != std::string::npos, + "sha256 etag version check reports up to date"); + require(inventory.find("\"size_bytes\":32") != std::string::npos, + "inventory reports the listing size total"); + + require(fixture.get() == 0, "local HTTP fixture completed normally"); + } catch (...) { + std::error_code error; + std::filesystem::remove_all(root, error); + throw; + } + std::error_code error; + std::filesystem::remove_all(root, error); +} + +} // namespace + +int main() { + try { test_modelscope_package_lifecycle(); } + catch (const std::exception & error) { + std::cerr << error.what() << '\n'; + return 1; + } + std::cout << "package_manager_modelscope_test passed\n"; + return 0; +} diff --git a/tests/unittests/test_sanotts_frontend.cpp b/tests/unittests/test_sanotts_frontend.cpp new file mode 100644 index 000000000..23e79dd34 --- /dev/null +++ b/tests/unittests/test_sanotts_frontend.cpp @@ -0,0 +1,56 @@ +#include "engine/community_models/sanotts/frontend.h" +#include "engine/community_models/sanotts/runtime.h" + +#include "test_assert.h" + +#include +#include +#include + +int main() try { + using engine::models::sanotts::SanoTtsFrontend; + using engine::models::sanotts::sanotts_text_seed; + + // sha256 known-answer vectors: the seed is the first 8 digest bytes, + // big-endian, exactly int.from_bytes(sha256(text).digest()[:8], "big"). + engine::test::require( + sanotts_text_seed("abc") == 0xBA7816BF8F01CFEAULL, + "sha256 seed of 'abc'"); + engine::test::require( + sanotts_text_seed("") == 0xE3B0C44298FC1C14ULL, + "sha256 seed of the empty string"); + // 64 bytes forces the two-block tail path (rem >= 56). + engine::test::require( + sanotts_text_seed(std::string(64, 'a')) == 0xFFE054FE7AE0CB6DULL, + "sha256 seed across the two-block tail"); + + const auto chunks = SanoTtsFrontend::split_text( + "First sentence ends here. Second sentence is also short. " + "And a third one rounds it out.", + 40); + engine::test::require(chunks.size() >= 2, "long text must split"); + for (const auto & chunk : chunks) { + engine::test::require(!chunk.empty(), "no empty chunks"); + } + + const auto single = SanoTtsFrontend::split_text("Tiny.", 280); + engine::test::require( + single.size() == 1 && single[0] == "Tiny.", + "short text stays one chunk"); + + engine::test::require( + SanoTtsFrontend::boundary_pause_seconds("Ends with a period.") == 0.20, + "sentence end pause"); + engine::test::require( + SanoTtsFrontend::boundary_pause_seconds("ends with a comma,") == 0.08, + "clause pause"); + engine::test::require( + SanoTtsFrontend::boundary_pause_seconds("trailing space. ") == 0.20, + "pause looks through trailing whitespace"); + + std::cout << "sanotts frontend tests passed\n"; + return 0; +} catch (const std::exception & error) { + std::cerr << "sanotts frontend test failed: " << error.what() << "\n"; + return 1; +} diff --git a/tests/vibeasr/test_vibeasr_asr.cpp b/tests/vibeasr/test_vibeasr_asr.cpp new file mode 100644 index 000000000..dea782212 --- /dev/null +++ b/tests/vibeasr/test_vibeasr_asr.cpp @@ -0,0 +1,145 @@ +// End-to-end probe for the ported VibeASR pipeline: I8_S VAE encoder -> ternary +// I2_S Qwen2 decoder -> transcript. +// +// The package ships two GGUFs, so --model points at the LM GGUF and the spec is +// resolved from the repo (same convention as minimax_h3). Skips with 125 when +// the checkpoint is not installed. +// +// Upstream: https://github.com/microsoft/VibeASR.cpp + +#include "engine/framework/audio/wav_reader.h" +#include "engine/framework/io/filesystem.h" +#include "engine/framework/io/text.h" +#include "engine/framework/runtime/model.h" +#include "engine/framework/runtime/registry.h" +#include "engine/framework/runtime/session.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef ENGINE_REPO_ROOT +#define ENGINE_REPO_ROOT "." +#endif + +namespace { + +constexpr int kExitPass = 0; +constexpr int kExitFail = 1; +constexpr int kExitSkip = 125; + +// LibriSpeech test-clean 6930-75918-0000, transcribed by VibeASR.cpp's own +// asr_infer --greedy on the same two GGUFs. +const char * kExpectedText = "Concord returned to its place amidst the tents."; + +std::filesystem::path repo_path(const std::string & relative) { + return std::filesystem::path(ENGINE_REPO_ROOT) / relative; +} + +std::string arg_value(int argc, char ** argv, const std::string & name, const std::string & fallback) { + for (int i = 1; i + 1 < argc; ++i) { + if (argv[i] == name) { + return argv[i + 1]; + } + } + return fallback; +} + +std::string normalize_text(const std::string & text) { + std::string out; + out.reserve(text.size()); + for (char ch : text) { + if (std::isalnum(static_cast(ch)) || std::isspace(static_cast(ch))) { + out.push_back(static_cast(std::tolower(static_cast(ch)))); + } + } + return engine::io::trim_ascii_whitespace(std::move(out)); +} + +} // namespace + +int main(int argc, char ** argv) { + const std::filesystem::path model_path = arg_value( + argc, argv, "--model", repo_path("models/vibeasr/vibeasr-lm-i2_s-embed-q6_k.gguf").string()); + const std::filesystem::path spec_override = arg_value( + argc, argv, "--model-spec-override", repo_path("model_specs").string()); + const std::filesystem::path audio_path = arg_value( + argc, argv, "--audio", + repo_path("assets/asr_validation/librispeech/librispeech_test_clean_6930-75918-0000.wav").string()); + const int threads = std::atoi(arg_value(argc, argv, "--threads", "4").c_str()); + + if (!engine::io::is_existing_file(model_path) || !engine::io::is_existing_file(audio_path)) { + std::fprintf( + stderr, + "SKIP: test_vibeasr_asr needs the LM GGUF at '%s' and audio at '%s'.\n" + " Fetch huggingface.co/microsoft/VibeVoice-ASR-BitNet and run\n" + " tools/community_models/convert_vibeasr_gguf.py --in-place on both GGUFs.\n", + model_path.string().c_str(), + audio_path.string().c_str()); + return kExitSkip; + } + + try { + auto registry = engine::runtime::make_default_registry(); + engine::runtime::ModelLoadRequest load_request; + load_request.model_path = model_path; + load_request.model_spec_override = spec_override; + load_request.family_hint = "vibeasr"; + auto model = registry.load(load_request); + + const engine::runtime::TaskSpec task{ + engine::runtime::VoiceTaskKind::Asr, + engine::runtime::RunMode::Offline, + }; + engine::runtime::SessionOptions session_options; + session_options.backend.threads = threads > 0 ? threads : 1; + + auto session = model->create_task_session(task, session_options); + auto * offline = dynamic_cast(session.get()); + if (offline == nullptr) { + std::cerr << "FAIL: VibeASR session is not an IOfflineVoiceTaskSession\n"; + return kExitFail; + } + + const auto wav = engine::audio::read_wav_f32(audio_path); + engine::runtime::AudioBuffer audio; + audio.sample_rate = wav.sample_rate; + audio.channels = wav.channels; + audio.samples = wav.samples; + + offline->prepare(engine::runtime::build_preparation_request(audio)); + + engine::runtime::TaskRequest request; + request.audio_input = audio; + const auto result = offline->run(request); + + if (!result.text_output.has_value()) { + std::cerr << "FAIL: VibeASR produced no text output\n"; + return kExitFail; + } + const std::string actual = result.text_output->text; + std::cout << "transcript: " << actual << "\n"; + std::cout << "expected: " << kExpectedText << "\n"; + + // Raw equality pins punctuation and casing against the reference decode; + // the normalized compare is only there to localize a failure. + if (actual != kExpectedText) { + if (normalize_text(actual) == normalize_text(kExpectedText)) { + std::cerr << "FAIL: transcript differs only in punctuation or casing\n"; + } else { + std::cerr << "FAIL: transcript mismatch\n"; + } + return kExitFail; + } + + std::cout << "PASS: VibeASR end-to-end transcription matches VibeASR.cpp\n"; + return kExitPass; + } catch (const std::exception & error) { + std::cerr << "FAIL: " << error.what() << "\n"; + return kExitFail; + } +} diff --git a/tests/vibeasr/test_vibeasr_vae_encoder.cpp b/tests/vibeasr/test_vibeasr_vae_encoder.cpp new file mode 100644 index 000000000..9d10404de --- /dev/null +++ b/tests/vibeasr/test_vibeasr_vae_encoder.cpp @@ -0,0 +1,261 @@ +// Parity probe for the ported VibeASR VAE encoder. +// +// Without --reference-* the probe only checks that the graph runs and produces a +// sane feature block. With a reference dump from VibeASR.cpp's own vae_server +// (raw float32, frames * dim, row-major) it reports max abs error, mean abs +// error, and cosine similarity, and fails outside the tolerances below. +// +// Upstream: https://github.com/microsoft/VibeASR.cpp + +#include "engine/community_models/vibeasr/vae_encoder.h" +#include "engine/framework/audio/wav_reader.h" +#include "engine/framework/core/backend.h" +#include "engine/framework/core/execution_context.h" +#include "engine/framework/io/filesystem.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef ENGINE_REPO_ROOT +#define ENGINE_REPO_ROOT "." +#endif + +namespace { + +constexpr int kExitPass = 0; +constexpr int kExitFail = 1; +constexpr int kExitSkip = 125; + +// Both encoders end in an I8_S matmul, so the whole feature block shares one +// scale: agreement is judged relative to that block's dynamic range rather than +// with an absolute epsilon. +// +// Bit-exactness is not reachable here and the tolerances reflect a measured +// noise floor rather than a guess. Every stage requantizes to int8, and the two +// implementations disagree in the last float bit of the per-tensor scale (this +// port stores amax/127 and multiplies, VibeASR.cpp stores 127/amax and divides), +// which flips a handful of values by one int8 step early on. Nudging a single +// input sample by one int8 step and re-running VibeASR.cpp against itself moves +// its own output by cosine 0.9959 (acoustic) / 0.9870 (semantic) -- i.e. the +// graph amplifies one LSB to about the same distance we see between the two +// implementations, so anything tighter would be testing rounding luck. +constexpr double kMaxMeanRelativeError = 0.02; +constexpr double kMinCosineSimilarity = 0.98; + +std::string arg_value(int argc, char ** argv, const std::string & name, const std::string & fallback) { + for (int i = 1; i + 1 < argc; ++i) { + if (argv[i] == name) { + return argv[i + 1]; + } + } + return fallback; +} + +std::vector read_f32_dump(const std::filesystem::path & path) { + std::ifstream file(path, std::ios::binary | std::ios::ate); + if (!file) { + throw std::runtime_error("cannot open reference dump: " + path.string()); + } + const auto bytes = static_cast(file.tellg()); + if (bytes % sizeof(float) != 0) { + throw std::runtime_error("reference dump is not a whole number of floats: " + path.string()); + } + std::vector values(bytes / sizeof(float)); + file.seekg(0); + file.read(reinterpret_cast(values.data()), static_cast(bytes)); + if (!file) { + throw std::runtime_error("short read on reference dump: " + path.string()); + } + return values; +} + +bool check_features( + const char * branch, + const engine::community_models::vibeasr::VaeEncoderFeatures & features, + int64_t expected_dim, + int64_t expected_frames) { + if (features.frames != expected_frames || features.dim != expected_dim) { + std::fprintf( + stderr, + "FAIL: %s features are [%lld frames, %lld dim], expected [%lld, %lld]\n", + branch, + static_cast(features.frames), + static_cast(features.dim), + static_cast(expected_frames), + static_cast(expected_dim)); + return false; + } + + double amax = 0.0; + for (float value : features.values) { + if (!std::isfinite(value)) { + std::fprintf(stderr, "FAIL: %s features contain a non-finite value\n", branch); + return false; + } + amax = std::max(amax, static_cast(std::fabs(value))); + } + if (amax == 0.0) { + std::fprintf(stderr, "FAIL: %s features are all zero\n", branch); + return false; + } + std::printf("%s: %lld frames x %lld dim, amax %.6f\n", + branch, + static_cast(features.frames), + static_cast(features.dim), + amax); + return true; +} + +bool compare_reference( + const char * branch, + const engine::community_models::vibeasr::VaeEncoderFeatures & features, + const std::filesystem::path & reference_path) { + const auto reference = read_f32_dump(reference_path); + if (reference.size() != features.values.size()) { + std::fprintf( + stderr, + "FAIL: %s reference has %zu values, encoder produced %zu\n", + branch, + reference.size(), + features.values.size()); + return false; + } + + double max_abs = 0.0; + double sum_abs = 0.0; + double reference_amax = 0.0; + double dot = 0.0; + double norm_a = 0.0; + double norm_b = 0.0; + for (size_t i = 0; i < reference.size(); ++i) { + const double a = features.values[i]; + const double b = reference[i]; + const double diff = std::fabs(a - b); + max_abs = std::max(max_abs, diff); + sum_abs += diff; + reference_amax = std::max(reference_amax, std::fabs(b)); + dot += a * b; + norm_a += a * a; + norm_b += b * b; + } + const double mean_abs = sum_abs / static_cast(reference.size()); + const double cosine = (norm_a > 0.0 && norm_b > 0.0) ? dot / std::sqrt(norm_a * norm_b) : 0.0; + const double max_relative = reference_amax > 0.0 ? max_abs / reference_amax : max_abs; + const double mean_relative = reference_amax > 0.0 ? mean_abs / reference_amax : mean_abs; + + std::printf( + "%s vs reference: max abs %.6g (%.3g of range), mean abs %.6g (%.3g of range), cosine %.8f\n", + branch, max_abs, max_relative, mean_abs, mean_relative, cosine); + + bool ok = true; + if (mean_relative > kMaxMeanRelativeError) { + std::fprintf(stderr, "FAIL: %s mean relative error %.6g exceeds %.6g\n", + branch, mean_relative, kMaxMeanRelativeError); + ok = false; + } + if (cosine < kMinCosineSimilarity) { + std::fprintf(stderr, "FAIL: %s cosine %.8f is below %.8f\n", branch, cosine, kMinCosineSimilarity); + ok = false; + } + return ok; +} + +} // namespace + +int main(int argc, char ** argv) { + const std::filesystem::path model_path = arg_value(argc, argv, "--model", ""); + const std::filesystem::path audio_path = arg_value(argc, argv, "--audio", ""); + const std::filesystem::path acoustic_reference = arg_value(argc, argv, "--reference-acoustic", ""); + const std::filesystem::path semantic_reference = arg_value(argc, argv, "--reference-semantic", ""); + const int threads = std::atoi(arg_value(argc, argv, "--threads", "4").c_str()); + + if (model_path.empty() || !engine::io::is_existing_file(model_path) || + audio_path.empty() || !engine::io::is_existing_file(audio_path)) { + std::fprintf( + stderr, + "SKIP: test_vibeasr_vae_encoder needs --model and --audio .\n" + " Convert a VibeASR.cpp checkpoint with tools/community_models/convert_vibeasr_gguf.py first.\n"); + return kExitSkip; + } + + try { + const auto wav = engine::audio::read_wav_f32(audio_path); + if (wav.channels != 1) { + std::fprintf(stderr, "SKIP: %s has %d channels, the encoder takes mono\n", + audio_path.string().c_str(), wav.channels); + return kExitSkip; + } + // The encoder is a raw-waveform stack: it accepts whatever rate the clip + // carries, and only the frame count and the reported RTF depend on it. + if (wav.sample_rate <= 0) { + std::fprintf(stderr, "SKIP: %s reports sample rate %d\n", + audio_path.string().c_str(), wav.sample_rate); + return kExitSkip; + } + + auto assets = engine::community_models::vibeasr::load_vibeasr_vae_assets(model_path); + const auto & config = assets->config; + std::printf( + "acoustic: %zu stages, total stride %lld, latent %lld, connector %lld\n", + config.acoustic.stages.size(), + static_cast(config.acoustic.total_stride), + static_cast(config.acoustic.latent_dim), + static_cast(config.acoustic.connector_hidden)); + + engine::core::BackendConfig backend_config; + backend_config.type = engine::core::BackendType::Cpu; + backend_config.threads = threads > 0 ? threads : 1; + engine::core::ExecutionContext execution_context(backend_config); + + engine::community_models::vibeasr::VibeASRVaeEncoderRuntime runtime(assets, execution_context); + + const auto num_samples = static_cast(wav.samples.size()); + const double audio_seconds = static_cast(num_samples) / static_cast(wav.sample_rate); + + const auto acoustic_start = std::chrono::steady_clock::now(); + const auto acoustic = runtime.encode_acoustic(wav.samples); + const auto semantic_start = std::chrono::steady_clock::now(); + const auto semantic = runtime.encode_semantic(wav.samples); + const auto encode_end = std::chrono::steady_clock::now(); + + const auto ms = [](auto from, auto to) { + return std::chrono::duration(to - from).count(); + }; + const double acoustic_ms = ms(acoustic_start, semantic_start); + const double semantic_ms = ms(semantic_start, encode_end); + std::printf( + "encode wall: acoustic %.1f ms, semantic %.1f ms, both %.1f ms for %.2f s of audio (RTF %.4f)\n", + acoustic_ms, semantic_ms, acoustic_ms + semantic_ms, audio_seconds, + (acoustic_ms + semantic_ms) / 1000.0 / audio_seconds); + + bool ok = true; + ok &= check_features( + "acoustic", acoustic, config.acoustic.connector_hidden, + config.acoustic.frames_for_samples(num_samples)); + ok &= check_features( + "semantic", semantic, config.semantic.connector_hidden, + config.semantic.frames_for_samples(num_samples)); + + if (!acoustic_reference.empty()) { + ok &= compare_reference("acoustic", acoustic, acoustic_reference); + } + if (!semantic_reference.empty()) { + ok &= compare_reference("semantic", semantic, semantic_reference); + } + if (acoustic_reference.empty() && semantic_reference.empty()) { + std::printf("no reference dump given: shape and sanity checks only\n"); + } + + return ok ? kExitPass : kExitFail; + } catch (const std::exception & error) { + std::fprintf(stderr, "FAIL: %s\n", error.what()); + return kExitFail; + } +} diff --git a/tools/audiocpp_cli/audiocpp_cli_longform_tts_clone_cases.json b/tools/audiocpp_cli/audiocpp_cli_longform_tts_clone_cases.json index 4ad10c62b..30db64f4f 100644 --- a/tools/audiocpp_cli/audiocpp_cli_longform_tts_clone_cases.json +++ b/tools/audiocpp_cli/audiocpp_cli_longform_tts_clone_cases.json @@ -330,6 +330,51 @@ } ] }, + { + "id": "sopro_tts_voice_clone_longform", + "coverage": "Sopro V2 Turbo reference voice clone with shared long-form text for chunking and RTF measurement", + "family": "sopro_tts", + "model": "models/sopro-v2-turbo", + "task": "tts", + "mode": "offline", + "outputs": [ + "audio" + ], + "requests": [ + { + "id": "clone_longform", + "text": "At dawn the harbor station opens its tall windows and the first clerk begins a careful report for the day. She notes the weather above the river, the slow cargo boats beyond the bridge, and the market voices arriving from the eastern road. A brass clock marks each quarter hour while porters stack wooden crates, bakers carry warm bread across the square, and a violinist practices the same bright phrase under the stone archway. By midmorning the keeper of the lighthouse sends a message about shifting currents, the museum guide unlocks a cabinet of maps, and a teacher leads a quiet line of students toward the ferry. In the afternoon a painter describes the silver color of the water, a mechanic jokes with the tram driver, and the station master reads an announcement that asks every traveler to keep close watch over letters, tickets, and parcels. After sunset the same clerk continues the report because new visitors keep arriving from the inland road. She explains that a florist carries pale roses past the fountain, two carpenters compare measurements beside the warehouse door, and the watchman checks each lock before the tide reaches its highest mark. A child laughs when the tram bell rings, a cook lowers a basket of fruit to the cellar, and three sailors unfold a chart that shows old channels, sandbars, and safe turning points for the morning crossing. Near midnight the lamps still glow on wet stone, the last cart rattles toward the market gate, and the report ends by saying that the harbor remains orderly, the wind has softened, the ferries are secure, and the town can rest until the next sunrise returns over the water. On the following morning the clerk resumes the record with even greater care because a week of inspections is about to begin. She writes that a ferry captain checks the mooring ropes one by one, a bookseller arranges travel guides beside the station cafe, and a pair of gardeners lift wet soil into bright clay pots near the west entrance. The bakery sends out trays of seed bread, the telegraph operator copies three official notices, and a tailor unfolds navy cloth across a polished wooden counter while customers wait in a line that bends toward the fountain. Before noon a surveyor compares bridge numbers against an old ledger, two cousins argue cheerfully about the best route to the fish market, and a choir director rehearses a patient scale that echoes against the warehouse wall. The lighthouse keeper reports that the northern channel is calmer than expected, the harbor pilot recommends a slower turn near the sandbar, and the customs officer stamps a packet of forms before waving a cart through the side gate. Later the schoolteacher returns with another group of students, asking them to observe the colors of rope, paint, stone, and water so they can write more exact descriptions in the classroom. A photographer kneels beside a rain barrel to capture the reflection of the clock tower, a mechanic tightens a brass hinge on the tram door, and an elderly traveler asks the clerk whether the evening ferry still stops at the orchard village beyond the marsh. As dusk arrives, lamps are trimmed again, shutters are tested against the wind, and the station kitchen sends bowls of soup to workers who remain on the late shift. The report continues with notes about a carpenter measuring floorboards in the east hall, a florist tying silver ribbon around the last stems of the day, and a violin case resting open on a bench beside the ticket window while its owner copies melody marks into a notebook. Long after the market gate closes, the clerk still writes that the harbor road stays busy, the river glints beneath scattered lamps, and the town maintains its patient rhythm of signals, footsteps, voices, bells, and distant engines. On the third day the clerk decides the record should be more precise, so she marks each event by the quarter hour and notes which sounds carry farthest through the station concourse. At first light she hears broom bristles on the stone steps, kettle lids in the cafe kitchen, and the slow scrape of crates being nudged across a loading cart beside the river wall. A messenger in a green coat delivers two canvas pouches, the ticket agent counts rolled coins into a brass tray, and a mother reads directions aloud while her son traces the painted ferry schedule with one curious finger. Midmorning brings a burst of sunlight across the waiting hall, making every brass handle shine while the museum guide escorts visitors toward the gallery of maps and navigational instruments. A porter pauses to describe the oldest compass in the display, a student sketches the harbor outline in graphite, and an apprentice clockmaker compares the station bell to a pocket watch that once belonged to his grandfather. By noon the fish market sends salt and seaweed scents through the open doors, tram wheels hiss at the curb, and the baker from the square exchanges a laugh with the florist who is carrying fresh lilies to the hotel veranda. The clerk writes that a cooper rolls three narrow barrels toward the cellar ramp, a translator copies weather bulletins for inland travelers, and a painter in a blue scarf studies the changing color of the tide as if each small wave might explain a different part of the sky. In the late afternoon the station master reviews freight tags, the customs officer checks a parcel of glassware, and a choir of children crosses the square singing a phrase so soft that the watchman removes his cap to listen. Evening settles slowly; lamps brighten in sequence, a cook inventories apples and onions in the pantry, and two sailors spread a faded chart on a crate so they can debate whether the shoals have shifted since the previous autumn. Before sleep the clerk closes the day with a final note that every vessel is accounted for, every platform has been swept, every lock has been tested twice, and the harbor seems ready to welcome another tide, another market, and another patient stream of voices at sunrise.", + "language": "en", + "voice_ref": "assets/resources/a.wav", + "seed": 1234, + "num_inference_steps": 8, + "text_chunk_size": 200 + } + ] + }, + { + "id": "sopro_tts_voice_clone_longform_streaming", + "coverage": "Sopro V2 Turbo streaming clone: one pull event per text segment over the same long-form text", + "family": "sopro_tts", + "model": "models/sopro-v2-turbo", + "task": "tts", + "mode": "streaming", + "outputs": [ + "audio", + "named_audio" + ], + "requests": [ + { + "id": "clone_longform_streaming", + "text": "At dawn the harbor station opens its tall windows and the first clerk begins a careful report for the day. She notes the weather above the river, the slow cargo boats beyond the bridge, and the market voices arriving from the eastern road. A brass clock marks each quarter hour while porters stack wooden crates, bakers carry warm bread across the square, and a violinist practices the same bright phrase under the stone archway. By midmorning the keeper of the lighthouse sends a message about shifting currents, the museum guide unlocks a cabinet of maps, and a teacher leads a quiet line of students toward the ferry. In the afternoon a painter describes the silver color of the water, a mechanic jokes with the tram driver, and the station master reads an announcement that asks every traveler to keep close watch over letters, tickets, and parcels. After sunset the same clerk continues the report because new visitors keep arriving from the inland road. She explains that a florist carries pale roses past the fountain, two carpenters compare measurements beside the warehouse door, and the watchman checks each lock before the tide reaches its highest mark. A child laughs when the tram bell rings, a cook lowers a basket of fruit to the cellar, and three sailors unfold a chart that shows old channels, sandbars, and safe turning points for the morning crossing. Near midnight the lamps still glow on wet stone, the last cart rattles toward the market gate, and the report ends by saying that the harbor remains orderly, the wind has softened, the ferries are secure, and the town can rest until the next sunrise returns over the water. On the following morning the clerk resumes the record with even greater care because a week of inspections is about to begin. She writes that a ferry captain checks the mooring ropes one by one, a bookseller arranges travel guides beside the station cafe, and a pair of gardeners lift wet soil into bright clay pots near the west entrance. The bakery sends out trays of seed bread, the telegraph operator copies three official notices, and a tailor unfolds navy cloth across a polished wooden counter while customers wait in a line that bends toward the fountain. Before noon a surveyor compares bridge numbers against an old ledger, two cousins argue cheerfully about the best route to the fish market, and a choir director rehearses a patient scale that echoes against the warehouse wall. The lighthouse keeper reports that the northern channel is calmer than expected, the harbor pilot recommends a slower turn near the sandbar, and the customs officer stamps a packet of forms before waving a cart through the side gate. Later the schoolteacher returns with another group of students, asking them to observe the colors of rope, paint, stone, and water so they can write more exact descriptions in the classroom. A photographer kneels beside a rain barrel to capture the reflection of the clock tower, a mechanic tightens a brass hinge on the tram door, and an elderly traveler asks the clerk whether the evening ferry still stops at the orchard village beyond the marsh. As dusk arrives, lamps are trimmed again, shutters are tested against the wind, and the station kitchen sends bowls of soup to workers who remain on the late shift. The report continues with notes about a carpenter measuring floorboards in the east hall, a florist tying silver ribbon around the last stems of the day, and a violin case resting open on a bench beside the ticket window while its owner copies melody marks into a notebook. Long after the market gate closes, the clerk still writes that the harbor road stays busy, the river glints beneath scattered lamps, and the town maintains its patient rhythm of signals, footsteps, voices, bells, and distant engines. On the third day the clerk decides the record should be more precise, so she marks each event by the quarter hour and notes which sounds carry farthest through the station concourse. At first light she hears broom bristles on the stone steps, kettle lids in the cafe kitchen, and the slow scrape of crates being nudged across a loading cart beside the river wall. A messenger in a green coat delivers two canvas pouches, the ticket agent counts rolled coins into a brass tray, and a mother reads directions aloud while her son traces the painted ferry schedule with one curious finger. Midmorning brings a burst of sunlight across the waiting hall, making every brass handle shine while the museum guide escorts visitors toward the gallery of maps and navigational instruments. A porter pauses to describe the oldest compass in the display, a student sketches the harbor outline in graphite, and an apprentice clockmaker compares the station bell to a pocket watch that once belonged to his grandfather. By noon the fish market sends salt and seaweed scents through the open doors, tram wheels hiss at the curb, and the baker from the square exchanges a laugh with the florist who is carrying fresh lilies to the hotel veranda. The clerk writes that a cooper rolls three narrow barrels toward the cellar ramp, a translator copies weather bulletins for inland travelers, and a painter in a blue scarf studies the changing color of the tide as if each small wave might explain a different part of the sky. In the late afternoon the station master reviews freight tags, the customs officer checks a parcel of glassware, and a choir of children crosses the square singing a phrase so soft that the watchman removes his cap to listen. Evening settles slowly; lamps brighten in sequence, a cook inventories apples and onions in the pantry, and two sailors spread a faded chart on a crate so they can debate whether the shoals have shifted since the previous autumn. Before sleep the clerk closes the day with a final note that every vessel is accounted for, every platform has been swept, every lock has been tested twice, and the harbor seems ready to welcome another tide, another market, and another patient stream of voices at sunrise.", + "language": "en", + "voice_ref": "assets/resources/a.wav", + "seed": 1234, + "num_inference_steps": 8, + "text_chunk_size": 200 + } + ] + }, { "id": "inflect_v2_tts_longform", "coverage": "Inflect v2 fixed-voice TTS with repeated requests, punctuation-aware long-form chunking, graph reuse, and RTF measurement", diff --git a/tools/audiocpp_cli/audiocpp_cli_path_cases.json b/tools/audiocpp_cli/audiocpp_cli_path_cases.json index 20c2b69d3..89579f802 100644 --- a/tools/audiocpp_cli/audiocpp_cli_path_cases.json +++ b/tools/audiocpp_cli/audiocpp_cli_path_cases.json @@ -3588,6 +3588,132 @@ } } ] + }, + { + "id": "cosyvoice3_official_paths", + "coverage": "CosyVoice3 official zero-shot, cross-lingual, instruction, and hotfix-pronunciation paths after a short same-session warmup", + "family": "cosyvoice3", + "model": "models/audio.cpp-gguf/CosyVoice3-GGUF/cosyvoice3-q8_0.gguf", + "task": "clon", + "mode": "offline", + "outputs": [ + "audio" + ], + "requests": [ + { + "id": "warmup_short_random", + "text": "你好。", + "reference_text": "You are a helpful assistant.<|endofprompt|>希望你以后能够做的比我还好呦。", + "voice_ref": "reference/CosyVoice/asset/zero_shot_prompt.wav", + "options": { + "template_name": "zero_shot", + "max_tokens": 80 + }, + "seed": 20260828 + }, + { + "id": "zero_shot_zh_official", + "text": "八百标兵奔北坡,北坡炮兵并排跑,炮兵怕把标兵碰,标兵怕碰炮兵炮。", + "reference_text": "You are a helpful assistant.<|endofprompt|>希望你以后能够做的比我还好呦。", + "voice_ref": "reference/CosyVoice/asset/zero_shot_prompt.wav", + "options": { + "template_name": "zero_shot" + }, + "seed": 1986 + }, + { + "id": "fine_grained_control_official", + "text": "You are a helpful assistant.<|endofprompt|>[breath]因为他们那一辈人[breath]在乡里面住的要习惯一点,[breath]邻居都很活络,[breath]嗯,都很熟悉。[breath]", + "voice_ref": "reference/CosyVoice/asset/zero_shot_prompt.wav", + "options": { + "template_name": "cross_lingual" + }, + "seed": 1986 + }, + { + "id": "instruct_cantonese_official", + "text": "好少咯,一般系放嗰啲国庆啊,中秋嗰啲可能会咯。", + "voice_ref": "reference/CosyVoice/asset/zero_shot_prompt.wav", + "options": { + "template_name": "instruct", + "instruction": "You are a helpful assistant. 请用广东话表达。<|endofprompt|>" + }, + "seed": 1986 + }, + { + "id": "hotfix_pronunciation_official", + "text": "高管也通过电话、短信、微信等方式对报道[j][ǐ]予好评。", + "reference_text": "You are a helpful assistant.<|endofprompt|>希望你以后能够做的比我还好呦。", + "voice_ref": "reference/CosyVoice/asset/zero_shot_prompt.wav", + "options": { + "template_name": "zero_shot" + }, + "seed": 1986 + } + ] + }, + { + "id": "breeze_tts_clone_official_paths", + "coverage": "BreezeTTS 2 official voice clone and voice direction paths using prompt audio, prompt transcript, T5Gemma text conditioning, Qwen acoustic generation, depth codebook decoding, and Mimi decode", + "family": "breeze_tts", + "model": "models/audio.cpp-gguf/Breeze-TTS-2-GGUF/breeze-tts-2-bf16.gguf", + "task": "clon", + "mode": "offline", + "outputs": [ + "audio" + ], + "requests": [ + { + "id": "voice_clone_en_official", + "text": "(sigh) It is good to hear your voice again after all this time.", + "reference_text": "Some call me nature. Others call me Mother Nature. I have been here for over four and a half billion years. Twenty two thousand five hundred times longer than you.", + "voice_ref": "assets/resources/b.wav", + "guidance_scale": 1.0, + "seed": 42 + }, + { + "id": "voice_direction_en_official", + "text": "(clears throat) We need to discuss what happened last night.", + "reference_text": "Some call me nature. Others call me Mother Nature. I have been here for over four and a half billion years. Twenty two thousand five hundred times longer than you.", + "voice_ref": "assets/resources/b.wav", + "options": { + "instruction": "Speak slowly with a restrained, serious tone." + }, + "guidance_scale": 4.0, + "seed": 45 + } + ] + }, + { + "id": "breeze_tts_design_official_paths", + "coverage": "BreezeTTS 2 official English and Chinese voice design paths using instruction conditioning, T5Gemma text conditioning, Qwen acoustic generation, depth codebook decoding, and Mimi decode", + "family": "breeze_tts", + "model": "models/audio.cpp-gguf/Breeze-TTS-2-GGUF/breeze-tts-2-bf16.gguf", + "task": "tts", + "mode": "offline", + "outputs": [ + "audio" + ], + "requests": [ + { + "id": "voice_design_en_official", + "text": "(sigh) Welcome aboard. Your journey begins now.", + "options": { + "instruction": "A warm, thoughtful young woman with a clear voice and a calm, reflective delivery." + }, + "guidance_scale": 4.0, + "seed": 43 + }, + { + "id": "voice_design_zh_official", + "text": "[笑] 欢迎来到今晚的故事时间,让我们一起开始吧。", + "options": { + "instruction": "一位温柔自信的年轻女性,声音清晰,语气亲切,表达轻快而富有感染力。" + }, + "guidance_scale": 4.0, + "seed": 44 + } + ] } ], "audit_gaps": [ diff --git a/tools/community_models/compare_mira_tts_outputs.py b/tools/community_models/compare_mira_tts_outputs.py new file mode 100644 index 000000000..866d8115c --- /dev/null +++ b/tools/community_models/compare_mira_tts_outputs.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +"""Compare matched upstream/native MiraTTS WAV outputs.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import librosa +import numpy as np +import soundfile as sf + + +def mono(path: Path) -> tuple[np.ndarray, int]: + audio, sample_rate = sf.read(path, dtype="float32", always_2d=True) + return np.mean(audio, axis=1, dtype=np.float32), sample_rate + + +def cosine(left: np.ndarray, right: np.ndarray) -> float: + denominator = float(np.linalg.norm(left) * np.linalg.norm(right)) + return 1.0 if denominator == 0.0 else float(np.dot(left, right) / denominator) + + +def compare(cpp_path: Path, python_path: Path) -> dict[str, object]: + cpp, cpp_rate = mono(cpp_path) + python, python_rate = mono(python_path) + if cpp_rate != python_rate: + raise RuntimeError( + f"sample-rate mismatch for {cpp_path.name}: {cpp_rate} != {python_rate}" + ) + common = min(cpp.size, python.size) + cpp_common = cpp[:common].astype(np.float64, copy=False) + python_common = python[:common].astype(np.float64, copy=False) + wav_cosine = cosine(cpp_common, python_common) + mel_kwargs = { + "sr": cpp_rate, + "n_fft": 2048, + "hop_length": 512, + "win_length": 2048, + "n_mels": 128, + "power": 2.0, + } + cpp_mel = np.log( + np.maximum(librosa.feature.melspectrogram(y=cpp, **mel_kwargs), 1.0e-10) + ) + python_mel = np.log( + np.maximum(librosa.feature.melspectrogram(y=python, **mel_kwargs), 1.0e-10) + ) + mel_frames = min(cpp_mel.shape[1], python_mel.shape[1]) + log_mel_cosine = cosine( + cpp_mel[:, :mel_frames].reshape(-1).astype(np.float64, copy=False), + python_mel[:, :mel_frames].reshape(-1).astype(np.float64, copy=False), + ) + return { + "cpp_audio": str(cpp_path), + "python_audio": str(python_path), + "sample_rate": cpp_rate, + "cpp_frames": int(cpp.size), + "python_frames": int(python.size), + "exact_frame_count": cpp.size == python.size, + "common_frames": int(common), + "waveform_cosine": wav_cosine, + "log_mel_cosine": log_mel_cosine, + } + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--cpp-summary", type=Path, required=True) + parser.add_argument("--python-summary", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--wav-cosine-min", type=float, default=0.95) + parser.add_argument("--log-mel-cosine-min", type=float, default=0.95) + parser.add_argument("--require-exact-frames", action="store_true") + args = parser.parse_args() + + cpp = json.loads(args.cpp_summary.read_text(encoding="utf-8")) + python = json.loads(args.python_summary.read_text(encoding="utf-8")) + cpp_by_name = {item["name"]: item for item in cpp["results"]} + python_by_name = {item["name"]: item for item in python["results"]} + names = sorted(set(cpp_by_name) & set(python_by_name)) + if not names: + raise RuntimeError("the summaries contain no matching request names") + + comparisons = [] + failed = [] + for name in names: + item = compare( + Path(cpp_by_name[name]["audio_out"]), + Path(python_by_name[name]["audio_out"]), + ) + item["name"] = name + item["passed"] = ( + item["waveform_cosine"] >= args.wav_cosine_min + and item["log_mel_cosine"] >= args.log_mel_cosine_min + and (not args.require_exact_frames or item["exact_frame_count"]) + ) + comparisons.append(item) + if not item["passed"]: + failed.append(name) + print(json.dumps(item)) + + report = { + "thresholds": { + "waveform_cosine": args.wav_cosine_min, + "log_mel_cosine": args.log_mel_cosine_min, + "require_exact_frames": args.require_exact_frames, + }, + "passed": not failed, + "failed": failed, + "comparisons": comparisons, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(report, indent=2), encoding="utf-8") + if failed: + raise SystemExit(f"MiraTTS parity failed: {', '.join(failed)}") + + +if __name__ == "__main__": + main() diff --git a/tools/community_models/convert_mira_tts.py b/tools/community_models/convert_mira_tts.py new file mode 100644 index 000000000..5368b78a8 --- /dev/null +++ b/tools/community_models/convert_mira_tts.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +"""Convert the official MiraTTS checkpoint into audio.cpp tensor assets. + +The upstream package combines a Qwen2 safetensors checkpoint, two ONNX +graphs, a DAC decoder safetensors checkpoint, and a small PyTorch 48 kHz +upsampler. audio.cpp does not execute ONNX or pickle at runtime: this tool +extracts and gives stable names to all learned tensors, fuses upsampler weight +normalization, and writes one ordinary safetensors file per runtime namespace. +""" + +from __future__ import annotations + +import argparse +import json +import shutil +from pathlib import Path + +import numpy as np +import onnx +from onnx import numpy_helper +import torch +from safetensors.torch import load_file, save_file + + +SPEAKER_ANONYMOUS = { + "onnx::MatMul_874": "perceiver.proj_context.weight", + "onnx::Expand_875": "perceiver.latents", + "onnx::MatMul_878": "perceiver.layers.0.attn.q.weight", + "onnx::MatMul_879": "perceiver.layers.0.attn.kv.weight", + "onnx::MatMul_883": "perceiver.layers.0.attn.out.weight", + "onnx::MatMul_884": "perceiver.layers.0.ff.in.weight", + "onnx::MatMul_885": "perceiver.layers.0.ff.out.weight", + "onnx::MatMul_886": "perceiver.layers.1.attn.q.weight", + "onnx::MatMul_887": "perceiver.layers.1.attn.kv.weight", + "onnx::MatMul_891": "perceiver.layers.1.attn.out.weight", + "onnx::MatMul_892": "perceiver.layers.1.ff.in.weight", + "onnx::MatMul_893": "perceiver.layers.1.ff.out.weight", + "onnx::MatMul_896": "quantizer.project_in.weight", +} + + +def processor_anonymous() -> dict[str, str]: + names = { + "onnx::MatMul_978": "speaker_encoder.quantizer.project_out.weight", + "onnx::MatMul_980": "prenet.linear_pre.weight", + "onnx::MatMul_1013": "prenet.linear.weight", + } + index = 981 + for downsample in range(2): + for block in range(2): + base = f"prenet.downsample.{downsample}.1.convnext.{block}" + names[f"onnx::MatMul_{index}"] = f"{base}.pwconv1.weight" + names[f"onnx::MatMul_{index + 1}"] = f"{base}.pwconv2.weight" + index += 2 + for block in range(12): + base = f"prenet.vocos_backbone.convnext.{block}" + names[f"onnx::MatMul_{index}"] = f"{base}.pwconv1.weight" + names[f"onnx::MatMul_{index + 1}"] = f"{base}.pwconv2.weight" + index += 2 + if index != 1013: + raise AssertionError(index) + return names + + +def onnx_tensors(path: Path, namespace: str, anonymous: dict[str, str]) -> dict[str, torch.Tensor]: + model = onnx.load(path, load_external_data=True) + output: dict[str, torch.Tensor] = {} + found_anonymous: set[str] = set() + for tensor in model.graph.initializer: + name = anonymous.get(tensor.name, tensor.name) + if tensor.name.startswith("onnx::MatMul_"): + if tensor.name not in anonymous: + raise SystemExit(f"unmapped learned ONNX tensor: {tensor.name}") + if tensor.name in anonymous: + found_anonymous.add(tensor.name) + value = np.asarray(numpy_helper.to_array(tensor)) + if value.ndim == 0: + value = value.reshape(1) + # ONNX MatMul parameters are [in, out]; audio.cpp Linear parameters + # follow PyTorch's [out, in] convention. + if tensor.name.startswith("onnx::MatMul_"): + value = value.T + output[f"{namespace}.{name}"] = torch.from_numpy(np.array(value, copy=True, order="C")) + missing = set(anonymous) - found_anonymous + if missing: + raise SystemExit(f"ONNX graph is missing expected tensors: {sorted(missing)}") + return output + + +def processor_context_codebook(path: Path) -> np.ndarray: + """Recover the exported FSQ 4096x6 implicit codebook constant.""" + model = onnx.load(path, load_external_data=True) + candidates: list[np.ndarray] = [] + for node in model.graph.node: + if node.op_type != "Constant": + continue + for attribute in node.attribute: + if attribute.type != onnx.AttributeProto.TENSOR: + continue + value = np.asarray(numpy_helper.to_array(attribute.t)) + if value.shape == (4096, 6): + candidates.append(value) + if len(candidates) != 1: + raise SystemExit( + f"expected one processor FSQ [4096, 6] codebook constant, found {len(candidates)}" + ) + return np.ascontiguousarray(candidates[0].astype(np.float32)) + + +def safetensor_tensors(path: Path, namespace: str) -> dict[str, torch.Tensor]: + return { + f"{namespace}.{name}": value.contiguous() + for name, value in load_file(str(path), device="cpu").items() + } + + +def upsampler_tensors(path: Path) -> dict[str, torch.Tensor]: + checkpoint = torch.load(path, map_location="cpu", weights_only=True) + state = checkpoint.get("model", checkpoint) + tensors = { + name: value.detach().cpu().float().numpy() + for name, value in state.items() + if hasattr(value, "detach") and not name.startswith("optimizer.") + } + fused_weights: dict[str, np.ndarray] = {} + for name, value in tensors.items(): + if name.endswith(".weight_g"): + base = name[:-len("_g")] + direction = tensors[base + "_v"].astype(np.float32) + axes = tuple(range(1, direction.ndim)) + norm = np.sqrt(np.sum(direction * direction, axis=axes, keepdims=True)) + fused_weights[base] = value.astype(np.float32) * direction / np.maximum(norm, 1.0e-12) + + # Use the framework FlashSR tensor contract. MiraTTS runs only residual + # blocks 2 and 0, matching FastAudioSR.Generator.forward(). + output: dict[str, torch.Tensor] = { + "upsampler.conv_pre.weight": torch.from_numpy(np.ascontiguousarray(fused_weights["dec.conv_pre.weight"])), + "upsampler.conv_pre.bias": torch.from_numpy(np.ascontiguousarray(tensors["dec.conv_pre.bias"])), + "upsampler.conv_post.weight": torch.from_numpy(np.ascontiguousarray(tensors["dec.conv_post.weight"])), + "upsampler.activation_filter": torch.from_numpy(np.ascontiguousarray( + tensors["dec.activation_post.upsample.filter"])), + } + for block in ("0", "2"): + for group in (1, 2): + for index in range(3): + source = f"dec.resblocks.{block}.convs{group}.{index}" + target = f"upsampler.resblocks.{block}.convs{group}.{index}" + output[target + ".weight"] = torch.from_numpy( + np.ascontiguousarray(fused_weights[source + ".weight"])) + output[target + ".bias"] = torch.from_numpy( + np.ascontiguousarray(tensors[source + ".bias"])) + for index in range(6): + source = f"dec.resblocks.{block}.activations.{index}.act" + target = f"upsampler.resblocks.{block}.activations.{index}" + output[target + ".alpha"] = torch.from_numpy( + np.ascontiguousarray(np.exp(tensors[source + ".alpha"])).reshape(1, 32, 1)) + output[target + ".inv_beta"] = torch.from_numpy( + np.ascontiguousarray(1.0 / (np.exp(tensors[source + ".beta"]) + 1.0e-9)).reshape(1, 32, 1)) + output["upsampler.activation_post.alpha"] = torch.from_numpy( + np.ascontiguousarray(np.exp(tensors["dec.activation_post.act.alpha"])).reshape(1, 32, 1)) + output["upsampler.activation_post.inv_beta"] = torch.from_numpy( + np.ascontiguousarray(1.0 / (np.exp(tensors["dec.activation_post.act.beta"]) + 1.0e-9)).reshape(1, 32, 1)) + return output + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + parser.add_argument("input", type=Path, help="downloaded YatharthS/MiraTTS directory") + parser.add_argument("output", type=Path, help="output audio.cpp model directory") + parser.add_argument("--overwrite", action="store_true") + args = parser.parse_args() + root = args.input.resolve() + output = args.output.resolve() + namespaces = ("language_model", "speaker_encoder", "processor", "decoder", "upsampler") + destinations = [output / f"{namespace}.safetensors" for namespace in namespaces] + existing = [path for path in destinations if path.exists()] + if existing and not args.overwrite: + raise SystemExit(f"output exists (pass --overwrite): {existing[0]}") + + required = { + "lm": root / "model.safetensors", + "speaker": root / "decoders" / "s_encoder.onnx", + "processor": root / "decoders" / "processer.onnx", + "decoder": root / "decoders" / "detokenizer.safetensors", + "upsampler": root / "decoders" / "upsampler.pth", + "config": root / "config.json", + "tokenizer": root / "tokenizer.json", + "tokenizer_config": root / "tokenizer_config.json", + } + missing = [str(path) for path in required.values() if not path.is_file()] + if missing: + raise SystemExit("missing MiraTTS files: " + ", ".join(missing)) + + tensors: dict[str, torch.Tensor] = {} + tensors.update(safetensor_tensors(required["lm"], "language_model")) + tensors.update(onnx_tensors(required["speaker"], "speaker_encoder", SPEAKER_ANONYMOUS)) + tensors.update(onnx_tensors(required["processor"], "processor", processor_anonymous())) + tensors["processor.speaker_encoder.context_codebook"] = torch.from_numpy( + processor_context_codebook(required["processor"]) + ) + tensors.update(safetensor_tensors(required["decoder"], "decoder")) + tensors.update(upsampler_tensors(required["upsampler"])) + + output.mkdir(parents=True, exist_ok=True) + total = 0 + for namespace in namespaces: + prefix = namespace + "." + scoped = { + name[len(prefix):]: value + for name, value in tensors.items() + if name.startswith(prefix) + } + if not scoped: + raise SystemExit(f"no tensors collected for namespace {namespace}") + save_file( + scoped, + str(output / f"{namespace}.safetensors"), + metadata={ + "format": "pt", + "source": "YatharthS/MiraTTS", + "audiocpp_family": "mira_tts", + "audiocpp_namespace": namespace, + }, + ) + total += len(scoped) + for name in ("config.json", "tokenizer.json", "tokenizer_config.json"): + shutil.copy2(root / name, output / name) + print(f"wrote {len(namespaces)} tensor namespaces ({total} tensors) to {output}") + + +if __name__ == "__main__": + main() diff --git a/tools/community_models/convert_vibeasr_gguf.py b/tools/community_models/convert_vibeasr_gguf.py new file mode 100644 index 000000000..2bfb9704a --- /dev/null +++ b/tools/community_models/convert_vibeasr_gguf.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +"""Convert a VibeASR.cpp GGUF into an audio.cpp GGUF package. + +Upstream: https://github.com/microsoft/VibeASR.cpp +Weights: https://huggingface.co/microsoft/VibeVoice-ASR-BitNet + +Handles both halves of the published package -- the I8_S VAE encoder and the +ternary I2_S language model -- because they need exactly the same fix and +nothing else. VibeASR.cpp ships both already quantized by its own ggml fork, so +there is nothing to re-quantize here. The only thing that differs is the numeric +type id: the VibeASR fork picked 36 (I2_S) and 37 (I8_S), which upstream ggml had +already used for the retired IQ4_NL_4_4 / IQ4_NL_4_8 slots. audio.cpp therefore +registers the same two types at 42 (I8_S) and 43 (I2_S). Tensors of any other +type in the file -- the LM's Q6_K token embedding, its F16 output projection, and +every F32 norm and bias -- are already portable and pass through untouched. + +The on-disk layout is identical either way -- an I8_S tensor is `nelements` int8 +bytes followed by a single padded F32 tensor scale, an I2_S tensor is the same +with 128 ternary codes packed per 32 bytes, and ggml's GGUF writer sizes every +tensor with ggml_nbytes() -- so this tool rewrites the 4-byte type field of each +tensor info and copies everything else through byte for byte. Data offsets, the +data section, and the KV block are untouched. + +Examples: + # inspect a VibeASR GGUF without writing anything + python3 tools/community_models/convert_vibeasr_gguf.py \ + --input vibeasr-vae-encoder-i8_s.gguf --list + + # convert a downloaded package where it sits (both halves) + python3 tools/community_models/convert_vibeasr_gguf.py \ + --input VibeVoice-ASR-BitNet/vibeasr-vae-encoder-i8_s.gguf --in-place + python3 tools/community_models/convert_vibeasr_gguf.py \ + --input VibeVoice-ASR-BitNet/vibeasr-lm-i2_s-embed-q6_k.gguf --in-place + + # or write the converted copy somewhere else + python3 tools/community_models/convert_vibeasr_gguf.py \ + --input vibeasr-vae-encoder-i8_s.gguf \ + --output models/vibeasr/vae_encoder-i8_s.gguf + + # confirm an already converted package needs no further remapping + python3 tools/community_models/convert_vibeasr_gguf.py \ + --input models/vibeasr/vae_encoder-i8_s.gguf --check +""" +import argparse +import struct +import sys +from pathlib import Path + +GGUF_MAGIC = b"GGUF" + +# VibeASR.cpp fork id -> audio.cpp id. See external/ggml/include/ggml.h for why +# audio.cpp cannot reuse 36/37. +TYPE_REMAP = {36: 43, 37: 42} + +TYPE_NAMES = {0: "f32", 1: "f16", 8: "q8_0", 14: "q6_k", 42: "i8_s", 43: "i2_s"} + +# GGUF metadata value type ids. +( + KV_UINT8, + KV_INT8, + KV_UINT16, + KV_INT16, + KV_UINT32, + KV_INT32, + KV_FLOAT32, + KV_BOOL, + KV_STRING, + KV_ARRAY, + KV_UINT64, + KV_INT64, + KV_FLOAT64, +) = range(13) + +KV_FIXED_SIZE = { + KV_UINT8: 1, + KV_INT8: 1, + KV_UINT16: 2, + KV_INT16: 2, + KV_UINT32: 4, + KV_INT32: 4, + KV_FLOAT32: 4, + KV_BOOL: 1, + KV_UINT64: 8, + KV_INT64: 8, + KV_FLOAT64: 8, +} + + +class Reader: + """Minimal forward-only GGUF header reader that tracks field offsets.""" + + def __init__(self, data: bytes): + self.data = data + self.pos = 0 + + def take(self, n: int) -> bytes: + if self.pos + n > len(self.data): + raise ValueError("GGUF header is truncated") + chunk = self.data[self.pos : self.pos + n] + self.pos += n + return chunk + + def u32(self) -> int: + return struct.unpack(" int: + return struct.unpack(" str: + return self.take(self.u64()).decode("utf-8", errors="replace") + + def skip_kv_value(self, kv_type: int) -> None: + if kv_type in KV_FIXED_SIZE: + self.take(KV_FIXED_SIZE[kv_type]) + elif kv_type == KV_STRING: + self.string() + elif kv_type == KV_ARRAY: + item_type = self.u32() + count = self.u64() + if item_type in KV_FIXED_SIZE: + self.take(KV_FIXED_SIZE[item_type] * count) + elif item_type == KV_STRING: + for _ in range(count): + self.string() + else: + raise ValueError(f"unsupported GGUF array element type {item_type}") + else: + raise ValueError(f"unsupported GGUF metadata type {kv_type}") + + +def parse_tensor_infos(data: bytes): + """Return (tensor_infos, alignment). Each info records where its type field lives.""" + reader = Reader(data) + if reader.take(4) != GGUF_MAGIC: + raise ValueError("not a GGUF file") + version = reader.u32() + if version != 3: + raise ValueError(f"unsupported GGUF version {version}") + n_tensors = reader.u64() + n_kv = reader.u64() + + alignment = 32 + for _ in range(n_kv): + key = reader.string() + kv_type = reader.u32() + if key == "general.alignment" and kv_type == KV_UINT32: + alignment = reader.u32() + else: + reader.skip_kv_value(kv_type) + + infos = [] + for _ in range(n_tensors): + name = reader.string() + n_dims = reader.u32() + dims = [reader.u64() for _ in range(n_dims)] + type_offset = reader.pos + type_id = reader.u32() + data_offset = reader.u64() + infos.append( + { + "name": name, + "dims": dims, + "type": type_id, + "type_offset": type_offset, + "data_offset": data_offset, + } + ) + return infos, alignment + + +def type_name(type_id: int) -> str: + return TYPE_NAMES.get(type_id, f"type#{type_id}") + + +def list_tensors(infos) -> None: + histogram = {} + for info in infos: + histogram[info["type"]] = histogram.get(info["type"], 0) + 1 + print(f" {info['name']:<64} {type_name(info['type']):>6} {info['dims']}") + print(f"{len(infos)} tensors") + for type_id in sorted(histogram): + print(f" {type_name(type_id):>6}: {histogram[type_id]}") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--input", type=Path, required=True, help="VibeASR.cpp GGUF (VAE encoder or LM)") + parser.add_argument("--output", type=Path, help="audio.cpp GGUF to write") + parser.add_argument("--in-place", action="store_true", help="rewrite --input itself instead of writing a copy") + parser.add_argument("--list", action="store_true", help="print the tensor table and exit") + parser.add_argument("--check", action="store_true", help="exit non-zero if any tensor still needs remapping") + args = parser.parse_args() + + if args.in_place: + if args.output is not None: + parser.error("--in-place and --output are mutually exclusive") + args.output = args.input + + data = bytearray(args.input.read_bytes()) + infos, alignment = parse_tensor_infos(bytes(data)) + + if args.list: + list_tensors(infos) + return 0 + + stale = [info for info in infos if info["type"] in TYPE_REMAP] + if args.check: + if stale: + print(f"{args.input}: {len(stale)} tensors still use VibeASR fork type ids", file=sys.stderr) + return 1 + print(f"{args.input}: type ids are already audio.cpp native") + return 0 + + if args.output is None: + parser.error("--output or --in-place is required unless --list or --check is given") + + # Guard against a double conversion: the fork ids and the audio.cpp ids are + # both valid ggml types, so a second pass would silently corrupt nothing but + # would also hide a mistake in the source package. + already = [info for info in infos if info["type"] in set(TYPE_REMAP.values())] + if already and stale: + raise SystemExit("input mixes VibeASR fork type ids with audio.cpp ids") + if not stale: + print(f"{args.input}: nothing to remap, copying through") + + for info in stale: + struct.pack_into(" str: + pcm = np.clip(waveform, -1.0, 1.0).astype(np.float32, copy=False) + return hashlib.sha256(pcm.tobytes()).hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--model-dir", type=Path, required=True) + parser.add_argument("--reference", type=Path, required=True) + parser.add_argument("--request-file", type=Path, required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--summary-file", type=Path, required=True) + args = parser.parse_args() + + model_dir = args.model_dir.resolve() + huggingface_hub.snapshot_download = ( + lambda *unused_args, **unused_kwargs: str(model_dir) + ) + + # The official checkpoint contains a revision-pinned legacy PyTorch file. + import transformers.modeling_utils + + transformers.modeling_utils.check_torch_load_is_safe = lambda: None + + from lmdeploy import GenerationConfig, TurbomindEngineConfig, pipeline + from ncodec.codec import TTSCodec + + requests = json.loads(args.request_file.read_text(encoding="utf-8"))["requests"] + if not requests: + raise RuntimeError("MiraTTS reference request file is empty") + args.output_dir.mkdir(parents=True, exist_ok=True) + args.summary_file.parent.mkdir(parents=True, exist_ok=True) + + codec = TTSCodec() + context_tokens = codec.encode(str(args.reference.resolve()), encode_semantic=False) + context_ids = np.asarray( + [[[int(token) for token in re.findall(r"context_token_(\d+)", context_tokens)]]], + dtype=np.int32, + ) + backend = TurbomindEngineConfig( + cache_max_entry_count=0.2, + tp=1, + dtype="bfloat16", + enable_prefix_caching=False, + ) + pipe = pipeline(str(model_dir), backend_config=backend) + decoder = codec.audio_decoder + + results: list[dict[str, object]] = [] + for index, request in enumerate(requests): + name = request.get("name", f"request_{index}") + prompt = codec.format_prompt(request["text"], context_tokens, None) + generation = GenerationConfig( + top_p=float(request.get("top_p", 0.95)), + top_k=int(request.get("top_k", 50)), + temperature=float(request.get("temperature", 0.8)), + max_new_tokens=int(request.get("max_tokens", 1024)), + repetition_penalty=float(request.get("repetition_penalty", 1.2)), + min_p=float(request.get("min_p", 0.05)), + do_sample=True, + random_seed=int(request.get("seed", 1234)), + ) + started = time.perf_counter() + response = pipe([prompt], gen_config=generation, do_preprocess=False)[0] + speech_tokens = response.text + speech_ids = np.asarray( + [[int(token) for token in re.findall(r"speech_token_(\d+)", speech_tokens)]], + dtype=np.int64, + ) + latent = decoder.processor_detokenizer.run( + ["preprocessed_output"], + {"context_tokens": context_ids, "speech_tokens": speech_ids}, + )[0] + audio = codec.decode(speech_tokens, context_tokens) + waveform = audio.detach().float().cpu().numpy().reshape(-1) + wall_ms = (time.perf_counter() - started) * 1000.0 + output = args.output_dir / f"{name}.wav" + scipy.io.wavfile.write(output, 48000, np.clip(waveform, -1.0, 1.0)) + results.append( + { + "name": name, + "wall_ms": wall_ms, + "audio_seconds": waveform.size / 48000.0, + "rtf": wall_ms / 1000.0 / (waveform.size / 48000.0), + "sample_rate": 48000, + "samples": int(waveform.size), + "speech_token_count": int(speech_ids.size), + "audio_sha256_f32": audio_sha256(waveform), + "audio_out": str(output), + "processor_latent": { + "shape": list(latent.shape), + "sum": float(np.sum(latent, dtype=np.float64)), + "sum_sq": float(np.sum(np.square(latent), dtype=np.float64)), + }, + } + ) + print(json.dumps(results[-1])) + + summary = { + "family": "mira_tts", + "implementation": "upstream_python", + "model_dir": str(model_dir), + "reference": str(args.reference.resolve()), + "results": results, + } + args.summary_file.write_text(json.dumps(summary, indent=2), encoding="utf-8") + print(args.summary_file) + + +if __name__ == "__main__": + main() diff --git a/tools/community_models/mira_tts_reference_smoke.py b/tools/community_models/mira_tts_reference_smoke.py new file mode 100644 index 000000000..8263a98fd --- /dev/null +++ b/tools/community_models/mira_tts_reference_smoke.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python3 +"""Run the trusted upstream MiraTTS implementation for native parity checks.""" + +from __future__ import annotations + +import argparse +import json +import re +from pathlib import Path + +import huggingface_hub +import numpy as np +import scipy.io.wavfile +import torch + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--model-dir", type=Path, required=True) + parser.add_argument("--reference", type=Path, required=True) + parser.add_argument("--text", required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--max-new-tokens", type=int, default=1024) + parser.add_argument("--top-k", type=int, default=50) + parser.add_argument("--top-p", type=float, default=0.95) + parser.add_argument("--min-p", type=float, default=0.05) + parser.add_argument("--temperature", type=float, default=0.8) + parser.add_argument("--repetition-penalty", type=float, default=1.2) + parser.add_argument("--seed", type=int, default=1234) + args = parser.parse_args() + + model_dir = args.model_dir.resolve() + # TTSCodec hardcodes snapshot_download; keep the upstream implementation intact + # while making it use the already verified local snapshot. + huggingface_hub.snapshot_download = lambda *unused_args, **unused_kwargs: str(model_dir) + + # The official checkpoint uses a legacy PyTorch file for FlashSR. The pinned + # Windows Torch in this isolated environment predates Transformers' new guard. + # Only the official, revision-pinned checkpoint is admitted here. + import transformers.modeling_utils + + transformers.modeling_utils.check_torch_load_is_safe = lambda: None + + from lmdeploy import GenerationConfig, TurbomindEngineConfig, pipeline + from ncodec.codec import TTSCodec + + codec = TTSCodec() + context_tokens = codec.encode(str(args.reference), encode_semantic=False) + prompt = codec.format_prompt(args.text, context_tokens, None) + + backend = TurbomindEngineConfig( + cache_max_entry_count=0.2, + tp=1, + dtype="bfloat16", + enable_prefix_caching=False, + ) + pipe = pipeline(str(model_dir), backend_config=backend) + generation = GenerationConfig( + top_p=args.top_p, + top_k=args.top_k, + temperature=args.temperature, + max_new_tokens=args.max_new_tokens, + repetition_penalty=args.repetition_penalty, + min_p=args.min_p, + do_sample=True, + random_seed=args.seed, + ) + response = pipe([prompt], gen_config=generation, do_preprocess=False)[0] + speech_tokens = response.text + speech_ids = np.asarray( + [[int(token) for token in re.findall(r"speech_token_(\d+)", speech_tokens)]], + dtype=np.int64, + ) + context_ids = np.asarray( + [[[int(token) for token in re.findall(r"context_token_(\d+)", context_tokens)]]], + dtype=np.int32, + ) + decoder = codec.audio_decoder + latent = decoder.processor_detokenizer.run( + ["preprocessed_output"], + {"context_tokens": context_ids, "speech_tokens": speech_ids}, + )[0] + lowres = decoder.audio_detokenizer.decode( + torch.from_numpy(latent).to("cuda:0") + ).squeeze().detach().float().cpu().numpy() + audio = codec.decode(speech_tokens, context_tokens) + waveform = audio.detach().float().cpu().numpy().reshape(-1) + + args.output.parent.mkdir(parents=True, exist_ok=True) + scipy.io.wavfile.write(args.output, 48000, np.clip(waveform, -1.0, 1.0)) + scipy.io.wavfile.write( + args.output.with_name(args.output.stem + "-lowres.wav"), + 16000, + np.clip(lowres, -1.0, 1.0), + ) + args.output.with_suffix(".json").write_text( + json.dumps( + { + "model_dir": str(model_dir), + "reference": str(args.reference.resolve()), + "text": args.text, + "context_tokens": context_tokens, + "speech_tokens": speech_tokens, + "sample_rate": 48000, + "samples": int(waveform.size), + "rms": float(np.sqrt(np.mean(np.square(waveform)))), + "peak": float(np.max(np.abs(waveform))), + "processor_latent": { + "shape": list(latent.shape), + "sum": float(np.sum(latent, dtype=np.float64)), + "sum_sq": float(np.sum(np.square(latent), dtype=np.float64)), + "first": latent.reshape(-1)[:10].astype(float).tolist(), + }, + }, + indent=2, + ), + encoding="utf-8", + ) + print(args.output) + + +if __name__ == "__main__": + main() diff --git a/tools/model_manager_v2.py b/tools/model_manager_v2.py index d6bde8d1a..5b5811a56 100644 --- a/tools/model_manager_v2.py +++ b/tools/model_manager_v2.py @@ -8,8 +8,9 @@ import shutil import sys import tempfile +import threading import time -from dataclasses import dataclass +from dataclasses import dataclass, replace from pathlib import Path from typing import Any from urllib.error import HTTPError @@ -41,6 +42,7 @@ class PackageRecord: strip_prefix: str download: dict[str, Any] default: bool + source_overridden: bool = False @dataclass(frozen=True) @@ -73,9 +75,28 @@ def hf_endpoint() -> str: return endpoint or "https://huggingface.co" -def http_headers() -> dict[str, str]: +def ms_endpoint() -> str: + """Base URL for ModelScope requests. + + Honors AUDIOCPP_MS_BASE_URL, mirroring the native C++ package manager. + Falls back to https://www.modelscope.cn. Empty values and trailing slashes + are tolerated. + """ + endpoint = os.environ.get("AUDIOCPP_MS_BASE_URL", "").strip().rstrip("/") + return endpoint or "https://www.modelscope.cn" + + +def modelscope_token() -> str | None: + token = os.environ.get("AUDIOCPP_MS_TOKEN", "").strip() + return token or None + + +def http_headers(source: str = "huggingface") -> dict[str, str]: + # Auth is provider-scoped: HF requests may carry the HF token, ModelScope + # requests carry only AUDIOCPP_MS_TOKEN, so one provider's credential is + # never sent to the other provider's host. headers = {"User-Agent": "audio.cpp model_manager_v2.py"} - token = huggingface_token() + token = modelscope_token() if source == "modelscope" else huggingface_token() if token: headers["Authorization"] = f"Bearer {token}" return headers @@ -123,6 +144,47 @@ def merged_download(spec: dict[str, Any], package: dict[str, Any]) -> dict[str, return download +def package_kind(package: PackageRecord) -> str: + return str(package.download.get("kind", "")) + + +def package_source(package: PackageRecord) -> str: + """Download provider for a package: 'modelscope' or 'huggingface'.""" + return "modelscope" if package_kind(package) == "modelscope_snapshot" else "huggingface" + + +def package_revision(package: PackageRecord) -> str: + """Kind-aware revision default: master on ModelScope, main on Hugging Face.""" + revision = str(package.download.get("revision", "")).strip() + if revision: + return revision + return "master" if package_kind(package) == "modelscope_snapshot" else "main" + + +def apply_source_override(record: PackageRecord, source_repo: str | None) -> PackageRecord: + """Redirect a package's download to ModelScope (--source modelscope). + + The repo comes from --source-repo when given, otherwise the spec's own + repo is reused on ModelScope. An unset or 'main' spec revision becomes + ModelScope's default branch ('master', via the kind-aware default); any + other explicit revision passes through unchanged. + """ + download = dict(record.download) + download["kind"] = "modelscope_snapshot" + if source_repo: + download["repo"] = source_repo + revision = str(download.get("revision", "")).strip() + if not revision or revision == "main": + download.pop("revision", None) + return replace(record, download=download, source_overridden=True) + + +def source_override_hint(package: PackageRecord) -> str: + if package.source_overridden: + return "; the spec repo may not exist on ModelScope, name it with --source-repo " + return "" + + def flatten_packages(specs: list[dict[str, Any]]) -> list[PackageRecord]: records: list[PackageRecord] = [] for spec in specs: @@ -177,9 +239,99 @@ def hf_url(repo: str, revision: str, remote_path: str) -> str: return f"{hf_endpoint()}/{repo}/resolve/{quote(revision, safe='')}/{quote_repo_path(remote_path)}" +def ms_url(repo: str, revision: str, remote_path: str) -> str: + return f"{ms_endpoint()}/models/{repo}/resolve/{quote(revision, safe='')}/{quote_repo_path(remote_path)}" + + +def ms_files_url(repo: str, revision: str) -> str: + return f"{ms_endpoint()}/api/v1/models/{repo}/repo/files?Revision={quote(revision, safe='')}&Recursive=true" + + +def download_url(package: PackageRecord, remote_path: str) -> str: + repo = package.download["repo"] + revision = package_revision(package) + if package_kind(package) == "modelscope_snapshot": + return ms_url(repo, revision, remote_path) + return hf_url(repo, revision, remote_path) + + +# ModelScope resolve HEAD responses carry no Content-Length or ETag, so +# per-file size and checksum come from the repo file-list API instead. The +# listing is fetched once per repo+revision and shared through this cache; an +# empty entry means the listing was unavailable and HEAD fallback applies. +_MS_LISTING_CACHE: dict[tuple[str, str, str], dict[str, RemoteFileInfo]] = {} +_MS_LISTING_LOCK = threading.Lock() + + +def ms_repo_listing(repo: str, revision: str) -> dict[str, RemoteFileInfo]: + key = (ms_endpoint(), repo, revision) + with _MS_LISTING_LOCK: + cached = _MS_LISTING_CACHE.get(key) + if cached is not None: + return cached + listing: dict[str, RemoteFileInfo] = {} + try: + request = Request(ms_files_url(repo, revision), headers=http_headers("modelscope")) + with urlopen(request, timeout=60) as response: + payload = json.loads(response.read().decode("utf-8")) + if payload.get("Code") != 200: + raise ManagerError(f"ModelScope repo listing failed for {repo}: {payload.get('Message', 'unknown error')}") + files = payload.get("Data", {}).get("Files") + if not isinstance(files, list): + raise ManagerError(f"ModelScope repo listing has no file list: {repo}") + for item in files: + if not isinstance(item, dict) or item.get("Type") != "blob": + continue + path = item.get("Path") + if not path: + continue + size = item.get("Size") + listing[str(path)] = RemoteFileInfo( + size=int(size) if size is not None else None, + revision="", + etag=str(item.get("Sha256") or ""), + ) + except Exception: + listing = {} + with _MS_LISTING_LOCK: + _MS_LISTING_CACHE[key] = listing + return listing + + +def check_ms_remote_file(package: PackageRecord, remote_path: str) -> RemoteFileInfo: + repo = package.download["repo"] + revision = package_revision(package) + listing = ms_repo_listing(repo, revision) + info = listing.get(remote_path) + if info is not None: + return info + if listing: + raise ManagerError( + f"remote file is not accessible: {repo}/{remote_path} (not in the ModelScope repo listing)" + f"{source_override_hint(package)}" + ) + # The listing API is unreachable; fall back to a HEAD on the resolve URL, + # which reports the file checksum as X-Linked-Etag but no size. + request = Request(ms_url(repo, revision, remote_path), headers=http_headers("modelscope"), method="HEAD") + try: + with urlopen(request, timeout=60) as response: + return RemoteFileInfo( + size=None, + revision="", + etag=response.headers.get("X-Linked-Etag", "").strip('"'), + ) + except HTTPError as error: + raise ManagerError( + f"remote file is not accessible: {repo}/{remote_path} ({error.code})" + f"{source_override_hint(package)}" + ) from error + + def check_remote_file(package: PackageRecord, remote_path: str) -> RemoteFileInfo: + if package_kind(package) == "modelscope_snapshot": + return check_ms_remote_file(package, remote_path) repo = package.download["repo"] - revision = package.download.get("revision", "main") + revision = package_revision(package) request = Request(hf_url(repo, revision, remote_path), headers=http_headers(), method="HEAD") try: with urlopen(request, timeout=60) as response: @@ -207,8 +359,7 @@ def download_file( cancel_file: Path | None = None, ) -> None: repo = package.download["repo"] - revision = package.download.get("revision", "main") - request = Request(hf_url(repo, revision, remote_path), headers=http_headers()) + request = Request(download_url(package, remote_path), headers=http_headers(package_source(package))) try: with urlopen(request, timeout=300) as response: expected_header = response.headers.get("Content-Length") @@ -239,18 +390,23 @@ def download_file( raise ManagerError( f"{repo}/{remote_path} requires accepted Hugging Face access and a valid HF token" ) from error - raise ManagerError(f"failed to download {repo}/{remote_path}: HTTP {error.code}") from error + if package_kind(package) == "modelscope_snapshot" and error.code in (401, 403): + raise ManagerError(f"{repo}/{remote_path} requires ModelScope access to this repo") from error + raise ManagerError( + f"failed to download {repo}/{remote_path}: HTTP {error.code}{source_override_hint(package)}" + ) from error -def ensure_hf_package(package: PackageRecord) -> None: +def ensure_snapshot_package(package: PackageRecord) -> None: kind = package.download.get("kind") - if kind != "huggingface_snapshot": + if kind not in ("huggingface_snapshot", "modelscope_snapshot"): raise ManagerError( - f"{package.id} uses download kind '{kind}'. model_manager_v2 only installs huggingface_snapshot packages; " + f"{package.id} uses download kind '{kind}'. model_manager_v2 only installs " + "huggingface_snapshot and modelscope_snapshot packages; " "use tools/model_manager.py for legacy composite or converter installs." ) if not package.download.get("repo"): - raise ManagerError(f"{package.id} has no Hugging Face repo") + raise ManagerError(f"{package.id} has no remote repo for download kind '{kind}'") def package_manifest_path(package: PackageRecord, models_root: Path) -> Path: @@ -281,7 +437,7 @@ def write_package_manifest( "schema_version": 1, "package_id": package.id, "repo": package.download.get("repo", ""), - "requested_revision": package.download.get("revision", "main"), + "requested_revision": package_revision(package), "resolved_revision": resolved_revision, "installed_at_unix": int(time.time()), "files": { @@ -321,7 +477,7 @@ def reusable_package_outputs( def install_package(package: PackageRecord, records: list[PackageRecord], args: argparse.Namespace) -> None: - ensure_hf_package(package) + ensure_snapshot_package(package) target_dir = validate_relative_path(package.target_directory, "target_directory") models_root = Path(args.models_root) final_dir = models_root / target_dir @@ -329,7 +485,7 @@ def install_package(package: PackageRecord, records: list[PackageRecord], args: full_plan = list(plan) print(f"selected {package.id} ({package.family})") - print(f"repo {package.download['repo']}@{package.download.get('revision', 'main')}") + print(f"repo {package.download['repo']}@{package_revision(package)}") print(f"target {final_dir}") for remote, output in plan: if args.check: @@ -421,7 +577,7 @@ def emit_progress(downloaded: int) -> None: shutil.rmtree(staging) resolved_revision = next( (info.revision for info in remote_files.values() if info.revision), - package.download.get("revision", "main"), + package_revision(package), ) write_package_manifest(package, models_root, resolved_revision, remote_files) except Exception: @@ -578,7 +734,7 @@ def package_version_state( def package_size_record(package: PackageRecord, models_root: Path | None = None) -> dict[str, Any]: installed = package_is_installed(package, models_root) try: - ensure_hf_package(package) + ensure_snapshot_package(package) total = 0 unknown = False remote_revision = "" @@ -651,6 +807,21 @@ def command_installed(records: list[PackageRecord], args: argparse.Namespace) -> print(json.dumps(rows, ensure_ascii=False)) +def add_source_arguments(parser: argparse.ArgumentParser) -> None: + parser.add_argument( + "--source", + choices=["huggingface", "modelscope"], + default="huggingface", + help="download source override; 'modelscope' downloads from ModelScope even when the " + "spec says huggingface_snapshot (an unset or 'main' revision becomes 'master'). " + "Manifest etags are source-specific, so cross-source checks may report updates", + ) + parser.add_argument( + "--source-repo", + help="ModelScope repo (namespace/name) for --source modelscope; defaults to the spec's repo", + ) + + def make_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Install audio.cpp model packages from model_specs/*.json.") parser.add_argument("--specs-dir", default=str(DEFAULT_SPECS_DIR), help="directory containing model spec JSON files") @@ -670,6 +841,7 @@ def make_parser() -> argparse.ArgumentParser: sizes_parser.add_argument("--jobs", type=int, default=12, help="parallel metadata checks") sizes_parser.add_argument("--models-root", help="also report packages whose required files are installed") sizes_parser.add_argument("--json", action="store_true", help="retained for command symmetry; output is JSON") + add_source_arguments(sizes_parser) installed_parser = sub.add_parser("installed", help="report locally installed packages without network access") installed_parser.add_argument("--models-root", default="models") @@ -685,7 +857,7 @@ def make_parser() -> argparse.ArgumentParser: clean_parser.add_argument("package", help="package id or family") clean_parser.add_argument("--models-root", default="models") - install_parser = sub.add_parser("install", help="install one Hugging Face snapshot package") + install_parser = sub.add_parser("install", help="install one Hugging Face or ModelScope snapshot package") install_parser.add_argument("package", help="package id or family") install_parser.add_argument("--format") install_parser.add_argument("--precision") @@ -699,14 +871,21 @@ def make_parser() -> argparse.ArgumentParser: action="store_true", help="emit machine-readable AUDIOCPP_PROGRESS lines while downloading", ) + add_source_arguments(install_parser) return parser def main() -> int: parser = make_parser() args = parser.parse_args() + source = getattr(args, "source", "huggingface") + source_repo = getattr(args, "source_repo", None) + if source_repo and source != "modelscope": + parser.error("--source-repo requires --source modelscope") try: records = flatten_packages(load_specs(Path(args.specs_dir))) + if source == "modelscope": + records = [apply_source_override(record, source_repo) for record in records] if args.command == "list": command_list(records, args) elif args.command == "info": diff --git a/webui/configs/model_params.json b/webui/configs/model_params.json index 7b3d71bbc..8883d6e67 100644 --- a/webui/configs/model_params.json +++ b/webui/configs/model_params.json @@ -98,12 +98,49 @@ {"name": "text_chunk_mode", "type": "choice", "label": "text_chunk_mode", "default": "default", "choices": ["default", "tag_aware", "japanese", "endline"]} ], + "breeze_tts": [ + {"name": "instruction", "type": "text", "label": "instruction", "label_en": "Instruction", "default": "", "placeholder": "Describe the target voice for VoiceDesign."}, + {"name": "text_chunk_size", "type": "number", "label": "text_chunk_size", "default": 600, "minimum": 1, "step": 1, "precision": 0}, + {"name": "text_chunk_mode", "type": "choice", "label": "text_chunk_mode", "default": "default", "choices": ["default", "tag_aware", "japanese", "endline"]}, + {"name": "guidance_scale", "type": "slider", "label": "guidance_scale", "default": 1.0, "minimum": 0.0, "maximum": 10.0, "step": 0.1}, + {"name": "temperature", "type": "slider", "label": "temperature", "default": 0.9, "minimum": 0.0, "maximum": 2.0, "step": 0.05}, + {"name": "depth_temperature", "type": "slider", "label": "depth_temperature", "default": 0.9, "minimum": 0.0, "maximum": 2.0, "step": 0.05}, + {"name": "top_k", "type": "number", "label": "top_k", "default": 50, "minimum": 0, "step": 1, "precision": 0}, + {"name": "top_p", "type": "slider", "label": "top_p", "default": 1.0, "minimum": 0.0, "maximum": 1.0, "step": 0.01} + ], + + "breeze-tts": [ + {"name": "text_chunk_size", "type": "number", "label": "text_chunk_size", "default": 600, "minimum": 1, "step": 1, "precision": 0}, + {"name": "text_chunk_mode", "type": "choice", "label": "text_chunk_mode", "default": "default", "choices": ["default", "tag_aware", "japanese", "endline"]}, + {"name": "guidance_scale", "type": "slider", "label": "guidance_scale", "default": 1.0, "minimum": 0.0, "maximum": 10.0, "step": 0.1}, + {"name": "temperature", "type": "slider", "label": "temperature", "default": 0.9, "minimum": 0.0, "maximum": 2.0, "step": 0.05}, + {"name": "depth_temperature", "type": "slider", "label": "depth_temperature", "default": 0.9, "minimum": 0.0, "maximum": 2.0, "step": 0.05}, + {"name": "top_k", "type": "number", "label": "top_k", "default": 50, "minimum": 0, "step": 1, "precision": 0}, + {"name": "top_p", "type": "slider", "label": "top_p", "default": 1.0, "minimum": 0.0, "maximum": 1.0, "step": 0.01} + ], + + "cosyvoice3": [ + {"name": "template_name", "type": "choice", "label": "template_name", "default": "zero_shot", "choices": ["zero_shot", "cross_lingual", "instruct"]}, + {"name": "instruction", "type": "text", "label": "instruction", "label_en": "Instruction", "default": "", "placeholder": "Used by template_name=instruct."}, + {"name": "text_chunk_size", "type": "number", "label": "text_chunk_size", "default": 600, "minimum": 1, "step": 1, "precision": 0}, + {"name": "text_chunk_mode", "type": "choice", "label": "text_chunk_mode", "default": "default", "choices": ["default", "tag_aware", "japanese", "endline"]}, + {"name": "num_inference_steps", "type": "number", "label": "num_inference_steps", "default": 10, "minimum": 1, "step": 1, "precision": 0}, + {"name": "min_tokens", "type": "number", "label": "min_tokens", "default": 0, "minimum": 0, "step": 1, "precision": 0}, + {"name": "top_k", "type": "number", "label": "top_k", "default": 25, "minimum": 1, "step": 1, "precision": 0} + ], + "inflect_v2": [ {"name": "speaking_rate", "type": "slider", "label": "speaking_rate(语速倍率)", "label_en": "speaking_rate", "default": 1.0, "minimum": 0.5, "maximum": 2.0, "step": 0.05}, {"name": "variation", "type": "slider", "label": "variation(音色变化)", "label_en": "variation", "default": 0.667, "minimum": 0.0, "maximum": 1.0, "step": 0.01}, {"name": "text_chunk_size", "type": "number", "label": "text_chunk_size(长文本分段字符数)", "label_en": "text_chunk_size", "default": 280, "minimum": 1, "step": 1, "precision": 0} ], + "sanotts": [ + {"name": "speaking_rate", "type": "slider", "label": "speaking_rate(语速倍率)", "label_en": "speaking_rate", "default": 1.0, "minimum": 0.5, "maximum": 2.0, "step": 0.05}, + {"name": "text_chunk_size", "type": "number", "label": "text_chunk_size(长文本分段字符数)", "label_en": "text_chunk_size", "default": 280, "minimum": 1, "step": 1, "precision": 0}, + {"name": "text_chunk_mode", "type": "choice", "label": "text_chunk_mode", "default": "word_budget", "choices": ["word_budget"]} + ], + "dramabox": [ {"name": "negative_prompt", "type": "text", "label": "negative_prompt(负向提示)", "label_en": "negative_prompt", "default": "", "placeholder": "留空=模型内置质量提示", "placeholder_en": "Blank = built-in quality prompt"}, {"name": "duration_sec", "type": "number", "label": "duration_sec(0=自动估时)", "label_en": "duration_sec (0 = auto)", "default": 0.0, "minimum": 0.0, "step": 0.5}, @@ -217,13 +254,15 @@ {"name": "route", "type": "choice", "label": "route(任务路线)", "default": "", "choices": ["", "style_preserved_vc", "style_converted_vc", "style_preserved_svc", "style_converted_svc", "singing_style_conversion", "editing"], "info": "留空=按任务默认;详见 webui/README.md"}, {"name": "num_inference_steps", "type": "number", "label": "num_inference_steps", "default": 32, "minimum": 1, "step": 1, "precision": 0, "info": "流匹配步数"}, {"name": "use_pitch_shift", "type": "choice", "label": "use_pitch_shift(自动音高对齐)", "default": "", "choices": ["", "true", "false"], "info": "留空=按路线默认"}, + {"name": "audio_chunk_duration_sec", "type": "number", "label": "audio_chunk_duration_sec(源音频分段秒数)", "default": 0.0, "minimum": 0.0, "step": 1.0, "info": "0=关闭;仅用于 source-audio VC/SVC 路线"}, + {"name": "cross_fade_duration_sec", "type": "number", "label": "cross_fade_duration_sec(分段重叠/淡化秒数)", "default": 1.0, "minimum": 0.0, "step": 0.1, "info": "source-audio 分段启用时作为输入重叠和输出交叉淡化时长"}, {"name": "temperature", "type": "slider", "label": "temperature(AR 路线用)", "default": 0.7, "minimum": 0.0, "maximum": 2.0, "step": 0.05, "info": "默认取自模型 generation_config.json"}, {"name": "top_k", "type": "number", "label": "top_k(AR 路线用)", "default": 20, "minimum": 0, "step": 1, "precision": 0, "info": "默认取自模型 generation_config.json"}, {"name": "top_p", "type": "slider", "label": "top_p(AR 路线用)", "default": 0.8, "minimum": 0.0, "maximum": 1.0, "step": 0.01} ], "heartmula": [ - {"name": "tags", "type": "text", "label": "tags(必填,逗号分隔)", "default": "", "placeholder": "pop,bright,drums,female vocals", "info": "风格/情绪/乐器/人声标签,模型必需"}, + {"name": "tags", "type": "text", "label": "tags(逗号分隔)", "default": "pop", "placeholder": "pop,bright,drums,female vocals", "info": "风格/情绪/乐器/人声标签"}, {"name": "temperature", "type": "slider", "label": "temperature", "default": 1.0, "minimum": 0.0, "maximum": 2.0, "step": 0.05}, {"name": "top_k", "type": "number", "label": "top_k", "default": 50, "minimum": 0, "step": 1, "precision": 0}, {"name": "guidance_scale", "type": "slider", "label": "guidance_scale(MuLa CFG)", "default": 1.5, "minimum": 0.0, "maximum": 5.0, "step": 0.1}, @@ -360,6 +399,17 @@ {"name": "temperature", "type": "slider", "label": "temperature", "default": 0.7, "minimum": 0.0, "maximum": 2.0, "step": 0.05} ], + "sopro_tts": [ + {"name": "language", "type": "choice", "label": "language", "label_en": "Language tag", "default": "", "choices": ["", "en", "pt", "fr", "de"], "info": "Optional <|lang_xx|> tag; helps pronunciation on ambiguous text."}, + {"name": "temperature", "type": "slider", "label": "temperature", "label_en": "Temperature", "default": 0.8, "minimum": 0.0, "maximum": 2.0, "step": 0.05}, + {"name": "top_p", "type": "slider", "label": "top_p", "label_en": "Top-p", "default": 0.9, "minimum": 0.0, "maximum": 1.0, "step": 0.01}, + {"name": "top_k", "type": "number", "label": "top_k", "label_en": "Top-k", "default": 25, "minimum": 0, "step": 1, "precision": 0, "info": "0 disables top-k truncation."}, + {"name": "num_inference_steps", "type": "number", "label": "num_inference_steps", "label_en": "Acoustic steps", "default": 2, "minimum": 1, "maximum": 32, "step": 1, "precision": 0, "info": "Rectified-flow Euler steps for the acoustic head."}, + {"name": "max_seconds", "type": "number", "label": "max_seconds", "label_en": "Max seconds per segment", "default": 30.0, "minimum": 1.0, "maximum": 60.0, "step": 0.5, "precision": 1}, + {"name": "min_seconds", "type": "number", "label": "min_seconds", "label_en": "Min seconds per segment", "default": 0.4, "minimum": 0.0, "maximum": 10.0, "step": 0.1, "precision": 1, "info": "Must not exceed max_seconds."}, + {"name": "ref_seconds", "type": "number", "label": "ref_seconds", "label_en": "Reference seconds", "default": 10.0, "minimum": 1.0, "maximum": 30.0, "step": 0.5, "precision": 1, "info": "Reference window used for cloning."}, + {"name": "text_chunk_size", "type": "number", "label": "text_chunk_size", "label_en": "Segment size", "default": 300, "minimum": 20, "maximum": 2000, "step": 10, "precision": 0, "info": "Max codepoints per synthesis segment."} + ], "supertonic": [ {"name": "voice", "type": "choice", "label": "voice(预置音色:M 男声 / F 女声)", "label_en": "voice (M = male, F = female presets)", "default": "M1", "choices": ["M1", "M2", "M3", "M4", "M5", "F1", "F2", "F3", "F4", "F5"]}, {"name": "speaking_rate", "type": "slider", "label": "speaking_rate(语速倍率)", "label_en": "speaking_rate", "default": 1.05, "minimum": 0.5, "maximum": 2.0, "step": 0.05}, diff --git a/webui/configs/models_catalog.json b/webui/configs/models_catalog.json index 0d426b1bb..7ac799310 100644 --- a/webui/configs/models_catalog.json +++ b/webui/configs/models_catalog.json @@ -14,8 +14,18 @@ { "id": "qwen3-tts", "display_name": "Qwen3-TTS 0.6B (tts)", "family": "qwen3_tts", "path": "models/Qwen3-TTS-12Hz-0.6B-Base", "task": "tts", "mode": "offline", "download_id": "qwen3_tts_0_6b_base", "min_vram_gb": 5 }, { "id": "qwen3-tts-1.7b", "display_name": "Qwen3-TTS 1.7B Base (tts)", "family": "qwen3_tts", "path": "models/Qwen3-TTS-12Hz-1.7B-Base", "task": "tts", "mode": "offline", "download_id": "qwen3_tts_1_7b_base", "min_vram_gb": 8 }, { "id": "qwen3-tts-1.7b-custom", "display_name": "Qwen3-TTS 1.7B CustomVoice (tts)", "family": "qwen3_tts", "path": "models/Qwen3-TTS-12Hz-1.7B-CustomVoice", "task": "tts", "mode": "offline", "download_id": "qwen3_tts_1_7b_custom_voice", "min_vram_gb": 8 }, + { "id": "breeze-tts", "display_name": "BreezeTTS 2 VoiceDesign", "family": "breeze_tts", "path": "models/Breeze-TTS-2-GGUF/breeze-tts-2-q8_0.gguf", "task": "vdes", "mode": "offline", "download_id": "breeze_tts_2_q8_0", "min_vram_gb": 8, + "input_hint_en": "**BreezeTTS 2 VoiceDesign**: enter text and describe the target voice in Model parameters. No reference voice is required." }, + { "id": "breeze-tts-clone", "display_name": "BreezeTTS 2 Clone", "family": "breeze_tts", "path": "models/Breeze-TTS-2-GGUF/breeze-tts-2-q8_0.gguf", "task": "clon", "mode": "offline", "download_id": "breeze_tts_2_q8_0", "min_vram_gb": 8, + "input_hint_en": "**BreezeTTS 2 Clone**: upload a reference voice and provide the matching reference transcript." }, + { "id": "cosyvoice3", "display_name": "CosyVoice3 Clone", "family": "cosyvoice3", "path": "models/CosyVoice3-GGUF/cosyvoice3-q8_0.gguf", "task": "clon", "mode": "offline", "download_id": "cosyvoice3_q8_0", "min_vram_gb": 8, + "input_hint_en": "**CosyVoice3 Clone**: upload a reference voice and provide the matching reference transcript. Use `template_name` for zero-shot or cross-lingual requests." }, + { "id": "cosyvoice3-instruct", "display_name": "CosyVoice3 Instruct", "family": "cosyvoice3", "path": "models/CosyVoice3-GGUF/cosyvoice3-q8_0.gguf", "task": "tts", "mode": "offline", "download_id": "cosyvoice3_q8_0", "min_vram_gb": 8, + "input_hint_en": "**CosyVoice3 Instruct**: upload a reference voice, provide its transcript, and set `template_name=instruct` with an instruction." }, { "id": "miotts", "display_name": "MioTTS 1.7B (tts; needs MioCodec)", "family": "miotts", "path": "models/MioTTS-1.7B", "task": "tts", "mode": "offline", "download_id": "miotts_1_7b", "min_vram_gb": 8 }, + { "id": "sopro-tts", "display_name": "Sopro V2 Turbo (tts + clone)", "family": "sopro_tts", "path": "models/sopro-v2-turbo", "task": "tts", "mode": "offline", "download_id": "sopro_v2_turbo_safetensors", "min_vram_gb": 2, "request_options": ["language", "temperature", "top_p", "top_k", "num_inference_steps", "max_seconds", "min_seconds", "ref_seconds", "text_chunk_size", "seed"] }, { "id": "soprano-tts", "display_name": "Soprano TTS (tts)", "family": "soprano_tts", "path": "models/Soprano-1.1-80M-GGUF", "task": "tts", "mode": "offline", "download_id": "soprano_1_1_80m_q8_0", "min_vram_gb": 1 }, + { "id": "sanotts", "display_name": "sanoTTS voice family (tts, community)", "family": "sanotts", "path": "models/sanoTTS-heart-nano-GGUF/heart-nano-f32.gguf", "task": "tts", "mode": "offline", "download_id": "sanotts_heart_nano_orig", "min_vram_gb": 1 }, { "id": "voxcpm2", "display_name": "VoxCPM2 (tts)", "family": "voxcpm2", "path": "models/VoxCPM2", "task": "tts", "mode": "offline", "download_id": "voxcpm2", "session_options": { "voxcpm2.weight_type": "q8_0" }, "min_vram_gb": 6 }, { "id": "voxcpm1", "display_name": "VoxCPM1 0.5B (tts + clone)", "family": "voxcpm1", "path": "models/VoxCPM1-GGUF", "task": "tts", "mode": "offline", "download_id": "voxcpm1_0_5b_q8_0", "min_vram_gb": 4 }, { "id": "vibevoice", "display_name": "VibeVoice 1.5B/7B (tts, long-form/multi-speaker)", "family": "vibevoice", "path": "models/VibeVoice-1.5B", "task": "tts", "mode": "offline", "download_id": "vibevoice_1_5b", "min_vram_gb": 7 }, @@ -72,8 +82,10 @@ "input_hint_en": "**Echo-TTS**: English zero-shot cloning at 44.1 kHz. Upload a reference voice -- no transcript needed. Output is CC-BY-NC-SA and may not be used commercially." }, { "id": "chatterbox", "display_name": "Chatterbox (voice clone)", "family": "chatterbox", "path": "models/chatterbox", "task": "clon", "mode": "offline", "download_id": "chatterbox", "min_vram_gb": 12 }, + { "id": "chatterbox-turbo", "display_name": "Chatterbox Turbo (tts)", "family": "chatterbox_turbo", "path": "models/Chatterbox-Turbo-GGUF/chatterbox-turbo-q8_0.gguf", "task": "tts", "mode": "offline", "download_id": "chatterbox_turbo_q8_0", "min_vram_gb": 4, + "input_hint_en": "**Chatterbox Turbo**: fast English TTS with the built-in voice. No reference voice is required." }, - { "id": "ace-step", "display_name": "ACE-Step 1.5 (music gen)", "family": "ace_step", "path": "models/Ace-Step1.5", "task": "gen", "mode": "offline", "download_id": "ace_step", "session_options": { "ace_step.mem_saver": "true", "ace_step.dit_weight_type": "q8_0", "ace_step.text_encoder_weight_type": "q8_0", "ace_step.planner_weight_type": "q8_0" }, "min_vram_gb": 8 }, + { "id": "ace-step", "display_name": "ACE-Step 1.5 (music gen)", "family": "ace_step", "path": "models/Ace-Step1.5", "task": "gen", "mode": "offline", "download_id": "ace_step", "default_text": "upbeat pop music with bright vocals and energetic drums", "session_options": { "ace_step.mem_saver": "true", "ace_step.dit_weight_type": "q8_0", "ace_step.text_encoder_weight_type": "q8_0", "ace_step.planner_weight_type": "q8_0" }, "min_vram_gb": 8 }, { "id": "minimax-music3", "display_name": "MiniMax-Music3 (song gen)", "family": "minimax_music3", "path": "models/MiniMax-Music3-GGUF", "task": "gen", "mode": "offline", "download_id": "minimax_music3_q4_0", "min_vram_gb": 12 }, { "id": "stable-audio-small-music","display_name": "Stable Audio 3 Small Music (gen)", "family": "stable_audio", "path": "models/stable-audio-3-small-music", "task": "gen", "mode": "offline", "download_id": "stable_audio_3_small_music", "min_vram_gb": 4 }, { "id": "stable-audio-small-sfx", "display_name": "Stable Audio 3 Small SFX (gen)", "family": "stable_audio", "path": "models/stable-audio-3-small-sfx", "task": "gen", "mode": "offline", "download_id": "stable_audio_3_small_sfx", "min_vram_gb": 4 }, diff --git a/webui/native/dist/index.html b/webui/native/dist/index.html index 39e4b1ef9..bafd74955 100644 --- a/webui/native/dist/index.html +++ b/webui/native/dist/index.html @@ -31,20 +31,20 @@
diff --git a/webui/native/src/lib/catalog.ts b/webui/native/src/lib/catalog.ts index baaf51af4..6a31e2e8d 100644 --- a/webui/native/src/lib/catalog.ts +++ b/webui/native/src/lib/catalog.ts @@ -42,10 +42,13 @@ const specsByFamily = new Map(Object.values(specModules).map((spec) => [spec.fam const exposeAllGgufPackageFamilies = new Set([ 'audiosr', 'controlfoley', + 'breeze_tts', + 'cosyvoice3', 'firered_audio', 'fireredtts3', 'meanvc2', - 'midashenglm_gen' + 'midashenglm_gen', + 'sanotts' ]); const hanCharacters = /[\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff]/u; @@ -116,6 +119,7 @@ function relatedExposeAllGgufPackages(entry: CatalogEntry): PackageEntry[] { const family = packages.filter((candidate) => candidate.family === entry.family && candidate.format === 'gguf'); if (!family.length) return []; + if (entry.family === 'sanotts') return family; if (!entry.download_id) return family; const exact = family.find((candidate) => candidate.id === entry.download_id); if (!exact) return relatedPackages(entry); @@ -134,6 +138,15 @@ function exposedPackageRank(entry: PackageEntry, selectedId?: string): number { } function packageLabel(entry: PackageEntry): string { + if (entry.family === 'sanotts') { + if (entry.id.includes('_heart_nano_')) return 'Heart Nano'; + if (entry.id.includes('_heart_')) return 'Heart'; + if (entry.id.includes('_amy_')) return 'Amy'; + if (entry.id.includes('_hfc_')) return 'HFC'; + if (entry.id.includes('_kristin_')) return 'Kristin'; + if (entry.id.includes('_vi_')) return 'Vietnamese'; + if (entry.id.includes('_id_')) return 'Indonesian'; + } if (entry.family === 'ace_step') { const precision = entry.precision === 'bf16' ? 'BF16' diff --git a/webui/native/src/lib/types.ts b/webui/native/src/lib/types.ts index 061ef0977..caeceefef 100644 --- a/webui/native/src/lib/types.ts +++ b/webui/native/src/lib/types.ts @@ -22,6 +22,7 @@ export interface CatalogEntry { min_vram_gb?: number; input_hint?: string; input_hint_en?: string; + default_text?: string; default_options?: Record; load_options?: StringMap; session_options?: StringMap; diff --git a/webui/native/src/routes/+page.svelte b/webui/native/src/routes/+page.svelte index 777984830..0e493c83e 100644 --- a/webui/native/src/routes/+page.svelte +++ b/webui/native/src/routes/+page.svelte @@ -145,6 +145,8 @@ const exposeAllStudioPackageFamilies = new Set([ 'audiosr', 'controlfoley', + 'breeze_tts', + 'cosyvoice3', 'firered_audio', 'fireredtts3', 'meanvc2', @@ -246,6 +248,10 @@ } } + function requestText() { + return text.trim() ? text : (selected.default_text || ''); + } + const workflowTabs = [ { id: 'tts', label: 'Text to speech', filterLabel: 'TTS', tasks: ['tts', 'clon'] }, { id: 'asr', label: 'ASR / Transcription', filterLabel: 'ASR', tasks: ['asr'] }, @@ -272,6 +278,8 @@ qwen3_asr: 'Qwen3-ASR', vevo2: 'Vevo2', seed_vc: 'Seed-VC', + breeze_tts: 'BreezeTTS 2', + cosyvoice3: 'CosyVoice3', magpie_tts: 'MagpieTTS', meanvc2: 'MeanVC2', personaplex: 'PersonaPlex' @@ -375,8 +383,9 @@ $: modelGroups = groupCatalog(activeCatalog); $: selected = activeCatalog.find((entry) => entry.id === selectedId) || activeCatalog[0] || catalog[0]; $: activeWorkflowSpec = workflowTabs.find((workflow) => workflow.id === activeWorkflow) || workflowTabs[0]; - $: workflowModels = activeCatalog.filter((entry) => - activeWorkflowSpec.tasks.some((task) => task === entry.task)); + $: workflowModels = activeCatalog + .filter((entry) => activeWorkflowSpec.tasks.some((task) => task === entry.task)) + .sort((left, right) => compareModelNames(left.display_name, right.display_name)); $: filteredModelGroups = modelGroups.map((group) => ({ ...group, entries: group.entries.filter((entry) => { @@ -392,13 +401,17 @@ $: usesDurationSecOption = selected?.family === 'controlfoley' || selected?.family === 'midashenglm_gen'; + $: supportsTextOnlyTts = ( + selected?.family === 'breeze_tts' || + selected?.family === 'chatterbox_turbo' + ) && selected?.task === 'tts'; $: needsSource = ['asr', 'vc', 'svc', 's2s', 'sep', 'vad', 'diar', 'align', 'midi'].includes(selected?.task) || isFireRedAudioEdit; $: acceptsSource = needsSource || selected?.task === 'gen'; $: acceptsVideo = selected?.request_options?.includes('video') === true; $: needsVoice = (['clon', 'vc', 'svc'].includes(selected?.task) && selected?.family !== 'rvc') || (selected?.task === 's2s' && selected?.family === 'personaplex') || - (selected?.task === 'tts' && !['supertonic'].includes(selected?.family)); + (selected?.task === 'tts' && !['supertonic'].includes(selected?.family) && !supportsTextOnlyTts); $: usesVibeVoiceSpeakerFiles = selected?.family === 'vibevoice'; $: isQwenBase = selected?.task === 'tts' && selected?.family === 'qwen3_tts' && !selected?.id.includes('custom'); @@ -988,6 +1001,9 @@ } else if (selected?.task === 'gen') { duration = 30; } + if (!text.trim() && selected?.default_text) { + text = selected.default_text; + } advancedJson = '{}'; } @@ -1644,6 +1660,8 @@ if (['gen', 's2s', 'align'].includes(selected.task) && text.trim()) request.text = text; if (['gen', 's2s', 'align'].includes(selected.task) && language.trim()) request.language = language; if (selected.task === 'gen') { + const resolvedText = requestText(); + if (resolvedText) request.text = resolvedText; if (lyrics.trim()) request.lyrics = lyrics; if (!isFireRedAudioEdit) { if (usesDurationSecOption) options.duration_sec = duration; @@ -2227,12 +2245,9 @@ {/if} {#if selected.task === 'gen'}
- + setDuration(event.currentTarget.valueAsNumber)} /> - {#if allowsAutoDuration} - {tr('request.autoDuration')} - {/if} {#if selected.family === 'minimax_h3'} {tr('request.minimaxFrames', { frames: Number(advancedValues.num_frames || 0) })} {/if}