mirror of
https://github.com/ggml-org/llama.cpp.git
synced 2026-08-27 02:27:34 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
925e117994 | ||
|
|
539f24529b | ||
|
|
0379a19f09 | ||
|
|
5e6a37cb11 | ||
|
|
bf94216469 | ||
|
|
d0132a680a | ||
|
|
4d19b28769 |
@@ -64,7 +64,7 @@ jobs:
|
||||
needs: create_tag
|
||||
uses: ./.github/workflows/ui-build.yml
|
||||
with:
|
||||
hf_ui_version: ${{ needs.create_tag.outputs.source_tag }}
|
||||
ui_version: ${{ needs.create_tag.outputs.source_tag }}
|
||||
|
||||
prepare_matrices:
|
||||
name: Prepare Docker matrices
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
if: ${{ matrix.config.prebuilt_ui == true }}
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: Set up QEMU
|
||||
|
||||
+72
-118
@@ -61,31 +61,8 @@ jobs:
|
||||
echo "should_release=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
get-version:
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
ui_version: ${{ steps.version.outputs.ui_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- id: version
|
||||
run: |
|
||||
# Resolve UI version: BUILD_NUMBER from cmake/build-info.cmake > git hash + epoch > fallback
|
||||
version=""
|
||||
if grep -q "BUILD_NUMBER" cmake/build-info.cmake; then
|
||||
build_number=$(grep "set(BUILD_NUMBER" cmake/build-info.cmake | grep -oP '\d+')
|
||||
if [ -n "$build_number" ] && [ "$build_number" -gt 0 ]; then
|
||||
version="b${build_number}"
|
||||
fi
|
||||
fi
|
||||
if [ -z "$version" ]; then
|
||||
version=$(git rev-parse --short HEAD)-$(date +%s)
|
||||
fi
|
||||
echo "ui_version=${version}" >> $GITHUB_OUTPUT
|
||||
|
||||
macos-cpu:
|
||||
needs: [check-release, get-version]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -119,12 +96,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
@@ -141,7 +117,6 @@ jobs:
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DLLAMA_FATAL_WARNINGS=ON \
|
||||
-DLLAMA_BUILD_BORINGSSL=ON \
|
||||
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
|
||||
${{ env.CMAKE_ARGS }}
|
||||
cmake --build build --config Release -j $(sysctl -n hw.logicalcpu)
|
||||
|
||||
@@ -167,7 +142,7 @@ jobs:
|
||||
key: release-${{ matrix.os }}-${{ matrix.arch }}
|
||||
|
||||
ubuntu-cpu:
|
||||
needs: [check-release, get-version]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -191,12 +166,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
@@ -227,7 +201,6 @@ jobs:
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DLLAMA_FATAL_WARNINGS=ON \
|
||||
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
|
||||
${{ env.CMAKE_ARGS }}
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
|
||||
@@ -254,7 +227,7 @@ jobs:
|
||||
key: release-${{ matrix.os }}-cpu
|
||||
|
||||
ubuntu-vulkan:
|
||||
needs: [check-release, get-version]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
strategy:
|
||||
@@ -277,12 +250,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: Dependencies
|
||||
id: depends
|
||||
@@ -314,7 +286,6 @@ jobs:
|
||||
-DGGML_NATIVE=OFF \
|
||||
-DGGML_CPU_ALL_VARIANTS=ON \
|
||||
-DGGML_VULKAN=ON \
|
||||
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
|
||||
${{ env.CMAKE_ARGS }}
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
|
||||
@@ -340,7 +311,7 @@ jobs:
|
||||
key: release-${{ matrix.os }}-vulkan
|
||||
|
||||
android-arm64:
|
||||
needs: [check-release, get-version]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
@@ -358,12 +329,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v5
|
||||
@@ -407,7 +377,6 @@ jobs:
|
||||
-DLLAMA_FATAL_WARNINGS=ON \
|
||||
-DGGML_OPENMP=OFF \
|
||||
-DLLAMA_BUILD_BORINGSSL=ON \
|
||||
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
|
||||
${{ env.CMAKE_ARGS }}
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
|
||||
@@ -433,7 +402,7 @@ jobs:
|
||||
name: llama-bin-android-arm64.tar.gz
|
||||
|
||||
ubuntu-24-openvino:
|
||||
needs: [check-release, get-version]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -460,12 +429,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
@@ -508,7 +476,6 @@ jobs:
|
||||
-DGGML_OPENVINO=ON \
|
||||
-DCMAKE_INSTALL_RPATH='$ORIGIN' \
|
||||
-DCMAKE_BUILD_WITH_INSTALL_RPATH=ON \
|
||||
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
|
||||
${{ env.CMAKE_ARGS }}
|
||||
cmake --build build/ReleaseOV --config Release --parallel
|
||||
|
||||
@@ -552,7 +519,7 @@ jobs:
|
||||
key: release-ubuntu-24.04-openvino-release-no-preset-v1
|
||||
|
||||
windows-openvino:
|
||||
needs: [check-release]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: windows-2022
|
||||
@@ -577,12 +544,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
@@ -682,7 +648,7 @@ jobs:
|
||||
|
||||
windows-cpu:
|
||||
name: windows-cpu / ${{ matrix.arch }}
|
||||
needs: [check-release]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: windows-2025-vs2026
|
||||
@@ -702,12 +668,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: Install Ninja
|
||||
run: |
|
||||
@@ -749,8 +714,10 @@ jobs:
|
||||
with:
|
||||
key: release-windows-2025-vs2026-${{ matrix.arch }}-cpu
|
||||
|
||||
# TODO: build only the ggml-hip backend like the other windows backend jobs
|
||||
# (windows-cuda, windows-sycl), then drop the ui-build dependency
|
||||
windows-rocm:
|
||||
needs: [check-release]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: windows-2022
|
||||
@@ -769,6 +736,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
with:
|
||||
@@ -879,6 +852,8 @@ jobs:
|
||||
with:
|
||||
key: windows-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
|
||||
|
||||
# note: builds only the backend library - llama-server (with the embedded UI)
|
||||
# is injected from the windows-cpu zip during the release "Merge artifacts" step
|
||||
windows:
|
||||
needs: [check-release]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
@@ -909,13 +884,6 @@ jobs:
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
|
||||
- name: Install Vulkan SDK
|
||||
id: get_vulkan
|
||||
if: ${{ matrix.backend == 'vulkan' }}
|
||||
@@ -978,6 +946,8 @@ jobs:
|
||||
path: llama-bin-win-${{ matrix.backend }}-${{ matrix.arch }}.zip
|
||||
name: llama-bin-win-${{ matrix.backend }}-${{ matrix.arch }}.zip
|
||||
|
||||
# note: builds only the ggml-cuda backend - llama-server is injected from the
|
||||
# windows-cpu zip during the release "Merge artifacts" step
|
||||
windows-cuda:
|
||||
name: windows-cuda (${{ matrix.cuda }}, ${{ matrix.arch }})
|
||||
needs: [check-release]
|
||||
@@ -1006,13 +976,6 @@ jobs:
|
||||
id: checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
|
||||
- name: Install Cuda Toolkit
|
||||
uses: ./.github/actions/windows-setup-cuda
|
||||
with:
|
||||
@@ -1084,6 +1047,8 @@ jobs:
|
||||
with:
|
||||
key: release-windows-2022-${{ matrix.arch }}-cuda-${{ matrix.cuda }}
|
||||
|
||||
# note: builds only the ggml-sycl backend - llama-server is injected from the
|
||||
# windows-cpu zip during the release "Merge artifacts" step
|
||||
windows-sycl:
|
||||
needs: [check-release]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
@@ -1118,13 +1083,6 @@ jobs:
|
||||
Expand-Archive -Path "level-zero-win-sdk.zip" -DestinationPath "C:/level-zero-sdk" -Force
|
||||
"LEVEL_ZERO_V1_SDK_PATH=C:/level-zero-sdk" | Out-File -FilePath $env:GITHUB_ENV -Append
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
with:
|
||||
@@ -1195,7 +1153,7 @@ jobs:
|
||||
key: release-windows-2022-x64-sycl
|
||||
|
||||
ubuntu-24-sycl:
|
||||
needs: [check-release]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
strategy:
|
||||
@@ -1237,12 +1195,11 @@ jobs:
|
||||
wget -q "https://github.com/oneapi-src/level-zero/releases/download/v${LEVEL_ZERO_VERSION}/level-zero-devel_${LEVEL_ZERO_VERSION}%2B${LEVEL_ZERO_UBUNTU_VERSION}_amd64.deb" -O level-zero-devel.deb
|
||||
sudo apt-get install -y ./level-zero.deb ./level-zero-devel.deb
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: ccache
|
||||
uses: ggml-org/ccache-action@v1.2.21
|
||||
@@ -1288,7 +1245,7 @@ jobs:
|
||||
key: release-ubuntu-24.04-sycl-${{ matrix.build }}
|
||||
|
||||
ubuntu-24-rocm:
|
||||
needs: [check-release, get-version]
|
||||
needs: [check-release, ui-build]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
@@ -1310,12 +1267,11 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
- name: Download UI build
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist
|
||||
|
||||
- name: Free up disk space
|
||||
uses: ggml-org/free-disk-space@v1.3.1
|
||||
@@ -1388,7 +1344,6 @@ jobs:
|
||||
-DGPU_TARGETS="${{ matrix.gpu_targets }}" \
|
||||
-DGGML_HIP=ON \
|
||||
-DHIP_PLATFORM=amd \
|
||||
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }} \
|
||||
${{ env.CMAKE_ARGS }}
|
||||
cmake --build build --config Release -j $(nproc)
|
||||
|
||||
@@ -1417,7 +1372,7 @@ jobs:
|
||||
key: release-ubuntu-24.04-rocm-${{ matrix.ROCM_VERSION }}-${{ matrix.build }}
|
||||
|
||||
ios-xcode:
|
||||
needs: [check-release, get-version]
|
||||
needs: [check-release]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
runs-on: macos-26
|
||||
|
||||
@@ -1445,8 +1400,7 @@ jobs:
|
||||
-DLLAMA_BUILD_SERVER=OFF \
|
||||
-DCMAKE_SYSTEM_NAME=iOS \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=16.0 \
|
||||
-DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=ggml \
|
||||
-DHF_UI_VERSION=${{ needs.get-version.outputs.ui_version }}
|
||||
-DCMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM=ggml
|
||||
cmake --build build --config Release -j $(sysctl -n hw.logicalcpu) -- CODE_SIGNING_ALLOWED=NO
|
||||
|
||||
- name: xcodebuild for swift package
|
||||
@@ -1569,11 +1523,9 @@ jobs:
|
||||
# name: llama-bin-${{ matrix.chip_type }}-openEuler-${{ matrix.arch }}${{ matrix.use_acl_graph == 'on' && '-aclgraph' || '' }}.tar.gz
|
||||
|
||||
ui-build:
|
||||
needs: [check-release, get-version]
|
||||
needs: [check-release]
|
||||
if: ${{ needs.check-release.outputs.should_release == 'true' }}
|
||||
uses: ./.github/workflows/ui-build.yml
|
||||
with:
|
||||
hf_ui_version: ${{ needs.get-version.outputs.ui_version }}
|
||||
|
||||
release:
|
||||
if: ${{ ( github.event_name == 'push' && github.ref == 'refs/heads/master' ) || github.event.inputs.create_release == 'true' }}
|
||||
@@ -1588,7 +1540,6 @@ jobs:
|
||||
runs-on: ubuntu-slim
|
||||
|
||||
needs:
|
||||
- get-version
|
||||
- windows
|
||||
- windows-cpu
|
||||
- windows-cuda
|
||||
@@ -1628,24 +1579,27 @@ jobs:
|
||||
path: ./artifact
|
||||
merge-multiple: true
|
||||
|
||||
- name: Move artifacts
|
||||
- name: Merge artifacts
|
||||
id: move_artifacts
|
||||
run: |
|
||||
mkdir -p release
|
||||
|
||||
echo "Adding CPU backend files to existing zips..."
|
||||
# the windows-cpu zip contains the full toolset (llama-server with the embedded
|
||||
# UI, ggml-cpu) - inject it into the other windows zips so that every archive
|
||||
# ships the same binaries, only with a different backend library on top
|
||||
echo "Injecting windows-cpu binaries (llama-server + CPU backend) into the backend zips..."
|
||||
for arch in x64 arm64; do
|
||||
cpu_zip="artifact/llama-bin-win-cpu-${arch}.zip"
|
||||
temp_dir=$(mktemp -d)
|
||||
echo "Extracting CPU backend for $arch..."
|
||||
echo "Extracting windows-cpu-${arch} package..."
|
||||
unzip "$cpu_zip" -d "$temp_dir"
|
||||
|
||||
echo "Adding CPU files to $arch zips..."
|
||||
echo "Merging into $arch zips..."
|
||||
for target_zip in artifact/llama-bin-win-*-${arch}.zip; do
|
||||
if [[ "$target_zip" == "$cpu_zip" ]]; then
|
||||
continue
|
||||
fi
|
||||
echo "Adding CPU backend to $(basename "$target_zip")"
|
||||
echo "Injecting into $(basename "$target_zip")"
|
||||
realpath_target_zip=$(realpath "$target_zip")
|
||||
(cd "$temp_dir" && zip -r "$realpath_target_zip" .)
|
||||
done
|
||||
@@ -1669,7 +1623,7 @@ jobs:
|
||||
id: download_ui
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: ./ui-dist
|
||||
|
||||
- name: Package UI
|
||||
|
||||
@@ -73,13 +73,6 @@ jobs:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.inputs.sha || github.event.pull_request.head.sha || github.sha || github.head_ref || github.ref_name }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: "npm"
|
||||
cache-dependency-path: "tools/ui/package-lock.json"
|
||||
|
||||
- name: Build
|
||||
id: cmake_build
|
||||
run: |
|
||||
|
||||
@@ -31,6 +31,6 @@ jobs:
|
||||
- name: Upload built UI
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist/
|
||||
retention-days: 1
|
||||
|
||||
@@ -3,8 +3,8 @@ name: UI Build
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
hf_ui_version:
|
||||
description: 'Version string for version.json (e.g. 12345)'
|
||||
ui_version:
|
||||
description: 'Version string embedded in build.json (e.g. b1234); defaults to b<commit-count>'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
@@ -17,6 +17,17 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Resolve UI version
|
||||
id: version
|
||||
run: |
|
||||
version="${{ inputs.ui_version }}"
|
||||
if [ -z "$version" ]; then
|
||||
version="b$(git rev-list --count HEAD)"
|
||||
fi
|
||||
echo "ui_version=${version}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v6
|
||||
@@ -31,8 +42,7 @@ jobs:
|
||||
|
||||
- name: Build application
|
||||
env:
|
||||
HF_UI_VERSION: ${{ inputs.hf_ui_version || '' }}
|
||||
LLAMA_BUILD_NUMBER: ${{ inputs.hf_ui_version || 'b0000' }}
|
||||
LLAMA_BUILD_NUMBER: ${{ steps.version.outputs.ui_version }}
|
||||
run: npm run build
|
||||
working-directory: tools/ui
|
||||
|
||||
@@ -43,6 +53,6 @@ jobs:
|
||||
- name: Upload built UI
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist/
|
||||
retention-days: 1
|
||||
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
- name: Download UI build artifact
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist/
|
||||
|
||||
- name: Create distribution archive
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
- name: Download built UI artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist/
|
||||
|
||||
- name: Run type checking
|
||||
@@ -106,7 +106,7 @@ jobs:
|
||||
- name: Download built UI artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist/
|
||||
|
||||
- name: Build Storybook
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
- name: Download built UI artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist/
|
||||
|
||||
- name: Install dependencies
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
- name: Download built UI artifacts (reuses ui-build)
|
||||
uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: ui-build
|
||||
name: llama-ui.zip
|
||||
path: tools/ui/dist/
|
||||
|
||||
- name: Install Playwright browsers
|
||||
|
||||
+2
-2
@@ -134,8 +134,8 @@ option(LLAMA_BUILD_TOOLS "llama: build tools"
|
||||
option(LLAMA_BUILD_EXAMPLES "llama: build examples" ${LLAMA_STANDALONE})
|
||||
option(LLAMA_BUILD_SERVER "llama: build server example" ${LLAMA_STANDALONE})
|
||||
option(LLAMA_BUILD_APP "llama: build the unified binary" ${LLAMA_STANDALONE})
|
||||
option(LLAMA_BUILD_UI "llama: build the embedded Web UI for server" ON)
|
||||
option(LLAMA_USE_PREBUILT_UI "llama: use prebuilt UI from HF Bucket when available (requires LLAMA_BUILD_UI=ON)" ON)
|
||||
option(LLAMA_BUILD_UI "llama: build the embedded Web UI for server" OFF)
|
||||
option(LLAMA_USE_PREBUILT_UI "llama: use prebuilt UI from HF Bucket when available" ON)
|
||||
|
||||
option(LLAMA_TOOLS_INSTALL "llama: install tools" ${LLAMA_TOOLS_INSTALL_DEFAULT})
|
||||
option(LLAMA_TESTS_INSTALL "llama: install tests" ON)
|
||||
|
||||
+2
-2
@@ -35,8 +35,8 @@ Legend:
|
||||
| COS | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| COUNT_EQUAL | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| CPY | ❌ | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | 🟡 | ❌ | ❌ |
|
||||
| CROSS_ENTROPY_LOSS | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| CROSS_ENTROPY_LOSS_BACK | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| CROSS_ENTROPY_LOSS | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| CROSS_ENTROPY_LOSS_BACK | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
| CUMSUM | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| DIAG | ❌ | ❌ | ✅ | ✅ | 🟡 | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
| DIAG_MASK_INF | ❌ | ✅ | ✅ | ✅ | ❌ | ❌ | 🟡 | ✅ | ✅ | ❌ | ❌ | ❌ |
|
||||
|
||||
+4
-4
@@ -19292,10 +19292,10 @@
|
||||
"Vulkan0","FLASH_ATTN_EXT","hsk=128,hsv=128,nh=8,nr23=[4,1],kv=4096,nb=512,mask=1,sinks=0,max_bias=0.000000,logit_softcap=0.000000,prec=f32,type_K=f16,type_V=f16,permute=[0,1,2,3]","support","1","yes","Vulkan"
|
||||
"Vulkan0","FLASH_ATTN_EXT","hsk=256,hsv=256,nh=4,nr23=[6,1],kv=16384,nb=512,mask=1,sinks=0,max_bias=0.000000,logit_softcap=0.000000,prec=f32,type_K=f16,type_V=f16,permute=[0,1,2,3]","support","1","yes","Vulkan"
|
||||
"Vulkan0","FLASH_ATTN_EXT","hsk=128,hsv=128,nh=8,nr23=[4,1],kv=16384,nb=512,mask=1,sinks=0,max_bias=0.000000,logit_softcap=0.000000,prec=f32,type_K=f16,type_V=f16,permute=[0,1,2,3]","support","1","yes","Vulkan"
|
||||
"Vulkan0","CROSS_ENTROPY_LOSS","type=f32,ne=[10,5,4,3]","support","0","no","Vulkan"
|
||||
"Vulkan0","CROSS_ENTROPY_LOSS","type=f32,ne=[30000,1,1,1]","support","0","no","Vulkan"
|
||||
"Vulkan0","CROSS_ENTROPY_LOSS_BACK","type=f32,ne=[10,5,4,3]","support","0","no","Vulkan"
|
||||
"Vulkan0","CROSS_ENTROPY_LOSS_BACK","type=f32,ne=[30000,1,1,1]","support","0","no","Vulkan"
|
||||
"Vulkan0","CROSS_ENTROPY_LOSS","type=f32,ne=[10,5,4,3]","support","1","yes","Vulkan"
|
||||
"Vulkan0","CROSS_ENTROPY_LOSS","type=f32,ne=[30000,1,1,1]","support","1","yes","Vulkan"
|
||||
"Vulkan0","CROSS_ENTROPY_LOSS_BACK","type=f32,ne=[10,5,4,3]","support","1","yes","Vulkan"
|
||||
"Vulkan0","CROSS_ENTROPY_LOSS_BACK","type=f32,ne=[30000,1,1,1]","support","1","yes","Vulkan"
|
||||
"Vulkan0","OPT_STEP_ADAMW","type=f32,ne=[10,5,4,3]","support","1","yes","Vulkan"
|
||||
"Vulkan0","OPT_STEP_SGD","type=f32,ne=[10,5,4,3]","support","1","yes","Vulkan"
|
||||
"Vulkan0","GATED_DELTA_NET","type=f32,head_count=32,head_size=128,n_seq_tokens=1,n_seqs=1,v_repeat=1,permuted=0,kda=0,K=1","support","1","yes","Vulkan"
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -6,8 +6,8 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RPC_PROTO_MAJOR_VERSION 5
|
||||
#define RPC_PROTO_MINOR_VERSION 1
|
||||
#define RPC_PROTO_MAJOR_VERSION 6
|
||||
#define RPC_PROTO_MINOR_VERSION 0
|
||||
#define RPC_PROTO_PATCH_VERSION 0
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
+441
-164
@@ -9,6 +9,9 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <condition_variable>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
@@ -17,6 +20,8 @@
|
||||
#include <fstream>
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
static const char * RPC_DEBUG = std::getenv("GGML_RPC_DEBUG");
|
||||
|
||||
@@ -72,6 +77,7 @@ enum rpc_cmd {
|
||||
RPC_CMD_DEVICE_COUNT,
|
||||
RPC_CMD_GRAPH_RECOMPUTE,
|
||||
RPC_CMD_MEMSET_TENSOR,
|
||||
RPC_CMD_NONE,
|
||||
RPC_CMD_COUNT,
|
||||
};
|
||||
|
||||
@@ -223,24 +229,24 @@ struct ggml_backend_rpc_buffer_type_context {
|
||||
size_t max_size;
|
||||
};
|
||||
|
||||
class rpc_dispatcher;
|
||||
struct ggml_backend_rpc_context {
|
||||
std::string endpoint;
|
||||
uint32_t device;
|
||||
std::string name;
|
||||
std::shared_ptr<rpc_dispatcher> dispatcher;
|
||||
uint32_t device;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct ggml_backend_rpc_buffer_context {
|
||||
std::shared_ptr<socket_t> sock;
|
||||
void * base_ptr;
|
||||
uint64_t remote_ptr;
|
||||
std::shared_ptr<rpc_dispatcher> dispatcher;
|
||||
void * base_ptr;
|
||||
uint64_t remote_ptr;
|
||||
};
|
||||
|
||||
// RPC helper functions
|
||||
|
||||
// Computes FNV-1a hash of the data
|
||||
static uint64_t fnv_hash(const uint8_t * data, size_t len) {
|
||||
static uint64_t fnv_hash(const uint8_t * data, size_t len, uint64_t hash = 0xcbf29ce484222325ULL) {
|
||||
const uint64_t fnv_prime = 0x100000001b3ULL;
|
||||
uint64_t hash = 0xcbf29ce484222325ULL;
|
||||
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
hash ^= data[i];
|
||||
@@ -357,44 +363,248 @@ static bool negotiate_hello(const std::shared_ptr<socket_t> & sock) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static std::shared_ptr<socket_t> get_socket(const std::string & endpoint) {
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::unordered_map<std::string, std::weak_ptr<socket_t>> sockets;
|
||||
template <typename T>
|
||||
class message_queue {
|
||||
public:
|
||||
message_queue() {}
|
||||
|
||||
auto it = sockets.find(endpoint);
|
||||
if (it != sockets.end()) {
|
||||
if (auto sock = it->second.lock()) {
|
||||
return sock;
|
||||
bool push(const T &value) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
if (interrupted) {
|
||||
return false;
|
||||
}
|
||||
queue.push(value);
|
||||
cvar.notify_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool pop(T* out) {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
cvar.wait(lock, [this] { return !queue.empty() || interrupted; });
|
||||
if (interrupted) {
|
||||
return false;
|
||||
}
|
||||
*out = queue.front();
|
||||
queue.pop();
|
||||
return true;
|
||||
}
|
||||
|
||||
void interrupt() {
|
||||
std::unique_lock<std::mutex> lock(mutex);
|
||||
interrupted = true;
|
||||
lock.unlock();
|
||||
cvar.notify_all();
|
||||
}
|
||||
|
||||
private:
|
||||
bool interrupted = false;
|
||||
std::queue<T> queue;
|
||||
std::mutex mutex;
|
||||
std::condition_variable cvar;
|
||||
};
|
||||
|
||||
class rpc_dispatcher {
|
||||
public:
|
||||
rpc_dispatcher() {
|
||||
}
|
||||
|
||||
void send(enum rpc_cmd cmd, std::shared_ptr<const void> input, size_t input_size);
|
||||
void send(enum rpc_cmd cmd, std::shared_ptr<const void> input, size_t input_size, void * output, size_t output_size);
|
||||
void send_async(enum rpc_cmd cmd, std::shared_ptr<const void> input, size_t input_size);
|
||||
void send_async(enum rpc_cmd cmd, std::shared_ptr<const void> input, size_t input_size, void * output, size_t output_size);
|
||||
|
||||
ggml_backend_event_t event_new(ggml_backend_dev_t dev);
|
||||
void event_free(ggml_backend_event_t event);
|
||||
void event_synchronize(ggml_backend_event_t event);
|
||||
void event_record(ggml_backend_event_t event);
|
||||
void synchronize();
|
||||
|
||||
void start(const std::string & endpoint);
|
||||
void work();
|
||||
|
||||
~rpc_dispatcher();
|
||||
|
||||
private:
|
||||
struct rpc_msg {
|
||||
rpc_cmd cmd;
|
||||
std::shared_ptr<const void> input;
|
||||
size_t input_size;
|
||||
void * output;
|
||||
size_t output_size;
|
||||
std::promise<void> completion;
|
||||
};
|
||||
using rpc_msg_ptr = std::shared_ptr<rpc_msg>;
|
||||
using rpc_msg_queue = message_queue<rpc_msg_ptr>;
|
||||
struct rpc_event {
|
||||
rpc_msg_ptr msg;
|
||||
std::shared_future<void> sf;
|
||||
};
|
||||
rpc_msg_queue queue;
|
||||
socket_ptr sock;
|
||||
std::atomic_bool running;
|
||||
std::thread thread;
|
||||
};
|
||||
|
||||
static void rpc_dispatcher_trampoline(rpc_dispatcher * dispatcher)
|
||||
{
|
||||
dispatcher->work();
|
||||
}
|
||||
|
||||
void rpc_dispatcher::send(enum rpc_cmd cmd, std::shared_ptr<const void> input, size_t input_size) {
|
||||
auto msg = std::make_shared<rpc_msg>();
|
||||
msg->cmd = cmd;
|
||||
msg->input = input;
|
||||
msg->input_size = input_size;
|
||||
msg->output = nullptr;
|
||||
msg->output_size = 0;
|
||||
GGML_ASSERT(queue.push(msg));
|
||||
auto future = msg->completion.get_future();
|
||||
future.wait();
|
||||
}
|
||||
|
||||
void rpc_dispatcher::send_async(enum rpc_cmd cmd, std::shared_ptr<const void> input, size_t input_size) {
|
||||
auto msg = std::make_shared<rpc_msg>();
|
||||
msg->cmd = cmd;
|
||||
msg->input = input;
|
||||
msg->input_size = input_size;
|
||||
msg->output = nullptr;
|
||||
msg->output_size = 0;
|
||||
GGML_ASSERT(queue.push(msg));
|
||||
}
|
||||
|
||||
void rpc_dispatcher::send(enum rpc_cmd cmd, std::shared_ptr<const void> input, size_t input_size, void * output, size_t output_size) {
|
||||
auto msg = std::make_shared<rpc_msg>();
|
||||
msg->cmd = cmd;
|
||||
msg->input = input;
|
||||
msg->input_size = input_size;
|
||||
msg->output = output;
|
||||
msg->output_size = output_size;
|
||||
GGML_ASSERT(queue.push(msg));
|
||||
auto future = msg->completion.get_future();
|
||||
future.wait();
|
||||
}
|
||||
|
||||
void rpc_dispatcher::send_async(enum rpc_cmd cmd, std::shared_ptr<const void> input, size_t input_size, void * output, size_t output_size) {
|
||||
auto msg = std::make_shared<rpc_msg>();
|
||||
msg->cmd = cmd;
|
||||
msg->input = input;
|
||||
msg->input_size = input_size;
|
||||
msg->output = output;
|
||||
msg->output_size = output_size;
|
||||
GGML_ASSERT(queue.push(msg));
|
||||
}
|
||||
|
||||
ggml_backend_event_t rpc_dispatcher::event_new(ggml_backend_dev_t dev) {
|
||||
rpc_event * ev = new rpc_event;
|
||||
ev->msg = std::make_shared<rpc_msg>();
|
||||
ev->msg->cmd = RPC_CMD_NONE;
|
||||
ev->sf = ev->msg->completion.get_future().share();
|
||||
GGML_ASSERT(queue.push(ev->msg));
|
||||
return new ggml_backend_event {
|
||||
/* .device = */ dev,
|
||||
/* .context = */ ev,
|
||||
};
|
||||
}
|
||||
|
||||
void rpc_dispatcher::event_free(ggml_backend_event_t event) {
|
||||
rpc_event * ev = (rpc_event *)event->context;
|
||||
delete ev;
|
||||
}
|
||||
|
||||
void rpc_dispatcher::event_synchronize(ggml_backend_event_t event) {
|
||||
rpc_event * ev = (rpc_event *)event->context;
|
||||
ev->sf.wait();
|
||||
}
|
||||
|
||||
void rpc_dispatcher::event_record(ggml_backend_event_t event) {
|
||||
rpc_event * ev = (rpc_event *)event->context;
|
||||
ev->msg = std::make_shared<rpc_msg>();
|
||||
ev->msg->cmd = RPC_CMD_NONE;
|
||||
ev->sf = ev->msg->completion.get_future().share();
|
||||
GGML_ASSERT(queue.push(ev->msg));
|
||||
}
|
||||
|
||||
void rpc_dispatcher::synchronize() {
|
||||
// to ensure all messages are processed, submit dummy message and wait for it to complete
|
||||
auto msg = std::make_shared<rpc_msg>();
|
||||
msg->cmd = RPC_CMD_NONE;
|
||||
GGML_ASSERT(queue.push(msg));
|
||||
msg->completion.get_future().wait();
|
||||
}
|
||||
|
||||
void rpc_dispatcher::start(const std::string & endpoint) {
|
||||
std::string host;
|
||||
int port;
|
||||
if (!parse_endpoint(endpoint, host, port)) {
|
||||
GGML_LOG_ERROR("Failed to parse endpoint: %s\n", endpoint.c_str());
|
||||
return nullptr;
|
||||
GGML_ABORT("Failed to parse endpoint: %s\n", endpoint.c_str());
|
||||
}
|
||||
if (!rpc_transport_init()) {
|
||||
GGML_ABORT("RPC transport initialization failed\n");
|
||||
}
|
||||
|
||||
if (!rpc_transport_init()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto sock = socket_t::connect(host.c_str(), port);
|
||||
sock = socket_t::connect(host.c_str(), port);
|
||||
if (sock == nullptr) {
|
||||
return nullptr;
|
||||
GGML_ABORT("Failed to connect to %s\n", endpoint.c_str());
|
||||
}
|
||||
if (!negotiate_hello(sock)) {
|
||||
return nullptr;
|
||||
GGML_ABORT("RPC handshake failed for %s\n", endpoint.c_str());
|
||||
}
|
||||
LOG_DBG("[%s] connected to %s\n", __func__, endpoint.c_str());
|
||||
sockets[endpoint] = sock;
|
||||
return sock;
|
||||
running = true;
|
||||
thread = std::thread(rpc_dispatcher_trampoline, this);
|
||||
}
|
||||
|
||||
void rpc_dispatcher::work() {
|
||||
while (running) {
|
||||
rpc_msg_ptr msg_ptr;
|
||||
if (!queue.pop(&msg_ptr)) {
|
||||
break;
|
||||
}
|
||||
if (msg_ptr->cmd != RPC_CMD_NONE) {
|
||||
if (msg_ptr->output) {
|
||||
bool status = send_rpc_cmd(sock, msg_ptr->cmd, msg_ptr->input.get(), msg_ptr->input_size, msg_ptr->output, msg_ptr->output_size);
|
||||
RPC_STATUS_ASSERT(status);
|
||||
} else {
|
||||
bool status = send_rpc_cmd(sock, msg_ptr->cmd, msg_ptr->input.get(), msg_ptr->input_size);
|
||||
RPC_STATUS_ASSERT(status);
|
||||
}
|
||||
}
|
||||
msg_ptr->completion.set_value();
|
||||
}
|
||||
}
|
||||
|
||||
rpc_dispatcher::~rpc_dispatcher() {
|
||||
running = false;
|
||||
queue.interrupt();
|
||||
sock = nullptr;
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<rpc_dispatcher> get_dispatcher(const std::string & endpoint) {
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
static std::unordered_map<std::string, std::weak_ptr<rpc_dispatcher>> dispatchers;
|
||||
|
||||
auto it = dispatchers.find(endpoint);
|
||||
if (it != dispatchers.end()) {
|
||||
if (auto dispatcher = it->second.lock()) {
|
||||
return dispatcher;
|
||||
}
|
||||
}
|
||||
|
||||
auto dispatcher = std::make_shared<rpc_dispatcher>();
|
||||
dispatcher->start(endpoint);
|
||||
dispatchers[endpoint] = dispatcher;
|
||||
return dispatcher;
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_buffer_free_buffer(ggml_backend_buffer_t buffer) {
|
||||
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
|
||||
rpc_msg_free_buffer_req request = {ctx->remote_ptr};
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_FREE_BUFFER, &request, sizeof(request), nullptr, 0);
|
||||
RPC_STATUS_ASSERT(status);
|
||||
auto request = std::make_shared<rpc_msg_free_buffer_req>();
|
||||
request->remote_ptr = ctx->remote_ptr;
|
||||
ctx->dispatcher->send(RPC_CMD_FREE_BUFFER, request, sizeof(*request));
|
||||
delete ctx;
|
||||
}
|
||||
|
||||
@@ -403,10 +613,10 @@ static void * ggml_backend_rpc_buffer_get_base(ggml_backend_buffer_t buffer) {
|
||||
if (ctx->base_ptr != nullptr) {
|
||||
return ctx->base_ptr;
|
||||
}
|
||||
rpc_msg_buffer_get_base_req request = {ctx->remote_ptr};
|
||||
auto request = std::make_shared<rpc_msg_buffer_get_base_req>();
|
||||
request->remote_ptr = ctx->remote_ptr;
|
||||
rpc_msg_buffer_get_base_rsp response;
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_BUFFER_GET_BASE, &request, sizeof(request), &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
ctx->dispatcher->send(RPC_CMD_BUFFER_GET_BASE, request, sizeof(*request), &response, sizeof(response));
|
||||
ctx->base_ptr = reinterpret_cast<void *>(response.base_ptr);
|
||||
return ctx->base_ptr;
|
||||
}
|
||||
@@ -463,12 +673,9 @@ static enum ggml_status ggml_backend_rpc_buffer_init_tensor(ggml_backend_buffer_
|
||||
// Due to bandwidth constraints, we only call the server init tensor functions if necessary.
|
||||
// In particular, only quantized tensors need padding
|
||||
if (ggml_is_quantized(tensor->type) && (tensor->ne[0] % 512 != 0) && (tensor->view_src == nullptr)) {
|
||||
rpc_msg_init_tensor_req request;
|
||||
|
||||
request.tensor = serialize_tensor(tensor);
|
||||
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_INIT_TENSOR, &request, sizeof(request), nullptr, 0);
|
||||
RPC_STATUS_ASSERT(status);
|
||||
auto request = std::make_shared<rpc_msg_init_tensor_req>();
|
||||
request->tensor = serialize_tensor(tensor);
|
||||
ctx->dispatcher->send(RPC_CMD_INIT_TENSOR, request, sizeof(*request));
|
||||
}
|
||||
return GGML_STATUS_SUCCESS;
|
||||
}
|
||||
@@ -476,27 +683,24 @@ static enum ggml_status ggml_backend_rpc_buffer_init_tensor(ggml_backend_buffer_
|
||||
static void ggml_backend_rpc_buffer_memset_tensor(
|
||||
ggml_backend_buffer_t buffer, ggml_tensor * tensor, uint8_t value, size_t offset, size_t size) {
|
||||
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
|
||||
rpc_msg_memset_tensor_req request = {
|
||||
/* .tensor = */ serialize_tensor(tensor),
|
||||
/* .offset = */ offset,
|
||||
/* .size = */ size,
|
||||
/* .value = */ value,
|
||||
};
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_MEMSET_TENSOR, &request, sizeof(request), nullptr, 0);
|
||||
RPC_STATUS_ASSERT(status);
|
||||
auto request = std::make_shared<rpc_msg_memset_tensor_req>();
|
||||
request->tensor = serialize_tensor(tensor);
|
||||
request->offset = offset;
|
||||
request->size = size;
|
||||
request->value = value;
|
||||
ctx->dispatcher->send(RPC_CMD_MEMSET_TENSOR, request, sizeof(*request));
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_buffer_set_tensor(ggml_backend_buffer_t buffer, ggml_tensor * tensor, const void * data, size_t offset, size_t size) {
|
||||
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
|
||||
rpc_tensor rpc_tensor = serialize_tensor(tensor);
|
||||
if (size > HASH_THRESHOLD) {
|
||||
rpc_msg_set_tensor_hash_req request;
|
||||
request.tensor = rpc_tensor;
|
||||
request.offset = offset;
|
||||
request.hash = fnv_hash((const uint8_t*)data, size);
|
||||
auto request = std::make_shared<rpc_msg_set_tensor_hash_req>();
|
||||
request->tensor = rpc_tensor;
|
||||
request->offset = offset;
|
||||
request->hash = fnv_hash((const uint8_t*)data, size);
|
||||
rpc_msg_set_tensor_hash_rsp response;
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_SET_TENSOR_HASH, &request, sizeof(request), &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
ctx->dispatcher->send(RPC_CMD_SET_TENSOR_HASH, request, sizeof(*request), &response, sizeof(response));
|
||||
if (response.result) {
|
||||
// the server has the same data, no need to send it
|
||||
return;
|
||||
@@ -504,22 +708,21 @@ static void ggml_backend_rpc_buffer_set_tensor(ggml_backend_buffer_t buffer, ggm
|
||||
}
|
||||
// input serialization format: | rpc_tensor | offset (8 bytes) | data (size bytes)
|
||||
size_t input_size = sizeof(rpc_tensor) + sizeof(uint64_t) + size;
|
||||
std::vector<uint8_t> input(input_size, 0);
|
||||
memcpy(input.data(), &rpc_tensor, sizeof(rpc_tensor));
|
||||
memcpy(input.data() + sizeof(rpc_tensor), &offset, sizeof(offset));
|
||||
memcpy(input.data() + sizeof(rpc_tensor) + sizeof(offset), data, size);
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_SET_TENSOR, input.data(), input.size());
|
||||
RPC_STATUS_ASSERT(status);
|
||||
uint8_t * input = new uint8_t[input_size]();
|
||||
memcpy(input, &rpc_tensor, sizeof(rpc_tensor));
|
||||
memcpy(input + sizeof(rpc_tensor), &offset, sizeof(offset));
|
||||
memcpy(input + sizeof(rpc_tensor) + sizeof(offset), data, size);
|
||||
std::shared_ptr<uint8_t> input_ptr(input, std::default_delete<uint8_t[]>());
|
||||
ctx->dispatcher->send(RPC_CMD_SET_TENSOR, input_ptr, input_size);
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_buffer_get_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * tensor, void * data, size_t offset, size_t size) {
|
||||
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
|
||||
rpc_msg_get_tensor_req request;
|
||||
request.tensor = serialize_tensor(tensor);
|
||||
request.offset = offset;
|
||||
request.size = size;
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_GET_TENSOR, &request, sizeof(request), data, size);
|
||||
RPC_STATUS_ASSERT(status);
|
||||
auto request = std::make_shared<rpc_msg_get_tensor_req>();
|
||||
request->tensor = serialize_tensor(tensor);
|
||||
request->offset = offset;
|
||||
request->size = size;
|
||||
ctx->dispatcher->send(RPC_CMD_GET_TENSOR, request, sizeof(*request), data, size);
|
||||
}
|
||||
|
||||
static bool ggml_backend_rpc_buffer_cpy_tensor(ggml_backend_buffer_t buffer, const ggml_tensor * src, ggml_tensor * dst) {
|
||||
@@ -529,16 +732,15 @@ static bool ggml_backend_rpc_buffer_cpy_tensor(ggml_backend_buffer_t buffer, con
|
||||
ggml_backend_rpc_buffer_context * src_ctx = (ggml_backend_rpc_buffer_context *)src_buffer->context;
|
||||
ggml_backend_buffer_t dst_buffer = dst->buffer;
|
||||
ggml_backend_rpc_buffer_context * dst_ctx = (ggml_backend_rpc_buffer_context *)dst_buffer->context;
|
||||
if (src_ctx->sock != dst_ctx->sock) {
|
||||
if (src_ctx->dispatcher != dst_ctx->dispatcher) {
|
||||
return false;
|
||||
}
|
||||
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
|
||||
rpc_msg_copy_tensor_req request;
|
||||
request.src = serialize_tensor(src);
|
||||
request.dst = serialize_tensor(dst);
|
||||
auto request = std::make_shared<rpc_msg_copy_tensor_req>();
|
||||
request->src = serialize_tensor(src);
|
||||
request->dst = serialize_tensor(dst);
|
||||
rpc_msg_copy_tensor_rsp response;
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_COPY_TENSOR, &request, sizeof(request), &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
ctx->dispatcher->send(RPC_CMD_COPY_TENSOR, request, sizeof(*request), &response, sizeof(response));
|
||||
return response.result;
|
||||
}
|
||||
return false;
|
||||
@@ -546,9 +748,10 @@ static bool ggml_backend_rpc_buffer_cpy_tensor(ggml_backend_buffer_t buffer, con
|
||||
|
||||
static void ggml_backend_rpc_buffer_clear(ggml_backend_buffer_t buffer, uint8_t value) {
|
||||
ggml_backend_rpc_buffer_context * ctx = (ggml_backend_rpc_buffer_context *)buffer->context;
|
||||
rpc_msg_buffer_clear_req request = {ctx->remote_ptr, value};
|
||||
bool status = send_rpc_cmd(ctx->sock, RPC_CMD_BUFFER_CLEAR, &request, sizeof(request), nullptr, 0);
|
||||
RPC_STATUS_ASSERT(status);
|
||||
auto request = std::make_shared<rpc_msg_buffer_clear_req>();
|
||||
request->remote_ptr = ctx->remote_ptr;
|
||||
request->value = value;
|
||||
ctx->dispatcher->send(RPC_CMD_BUFFER_CLEAR, request, sizeof(*request));
|
||||
}
|
||||
|
||||
static ggml_backend_buffer_i ggml_backend_rpc_buffer_interface = {
|
||||
@@ -572,15 +775,17 @@ static const char * ggml_backend_rpc_buffer_type_name(ggml_backend_buffer_type_t
|
||||
|
||||
static ggml_backend_buffer_t ggml_backend_rpc_buffer_type_alloc_buffer(ggml_backend_buffer_type_t buft, size_t size) {
|
||||
ggml_backend_rpc_buffer_type_context * buft_ctx = (ggml_backend_rpc_buffer_type_context *)buft->context;
|
||||
rpc_msg_alloc_buffer_req request = {buft_ctx->device, size};
|
||||
auto request = std::make_shared<rpc_msg_alloc_buffer_req>();
|
||||
request->device = buft_ctx->device;
|
||||
request->size = size;
|
||||
rpc_msg_alloc_buffer_rsp response;
|
||||
auto sock = get_socket(buft_ctx->endpoint);
|
||||
bool status = send_rpc_cmd(sock, RPC_CMD_ALLOC_BUFFER, &request, sizeof(request), &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
|
||||
auto dispatcher = get_dispatcher(buft_ctx->endpoint);
|
||||
dispatcher->send(RPC_CMD_ALLOC_BUFFER, request, sizeof(*request), &response, sizeof(response));
|
||||
if (response.remote_ptr != 0) {
|
||||
ggml_backend_buffer_t buffer = ggml_backend_buffer_init(buft,
|
||||
ggml_backend_rpc_buffer_interface,
|
||||
new ggml_backend_rpc_buffer_context{sock, nullptr, response.remote_ptr},
|
||||
new ggml_backend_rpc_buffer_context{dispatcher, nullptr, response.remote_ptr},
|
||||
response.remote_size);
|
||||
return buffer;
|
||||
} else {
|
||||
@@ -588,11 +793,11 @@ static ggml_backend_buffer_t ggml_backend_rpc_buffer_type_alloc_buffer(ggml_back
|
||||
}
|
||||
}
|
||||
|
||||
static size_t get_alignment(const std::shared_ptr<socket_t> & sock, uint32_t device) {
|
||||
rpc_msg_get_alignment_req request = {device};
|
||||
static size_t get_alignment(const std::shared_ptr<rpc_dispatcher> & dispatcher, uint32_t device) {
|
||||
auto request = std::make_shared<rpc_msg_get_alignment_req>();
|
||||
request->device = device;
|
||||
rpc_msg_get_alignment_rsp response;
|
||||
bool status = send_rpc_cmd(sock, RPC_CMD_GET_ALIGNMENT, &request, sizeof(request), &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
dispatcher->send(RPC_CMD_GET_ALIGNMENT, request, sizeof(*request), &response, sizeof(response));
|
||||
return response.alignment;
|
||||
}
|
||||
|
||||
@@ -601,11 +806,11 @@ static size_t ggml_backend_rpc_buffer_type_get_alignment(ggml_backend_buffer_typ
|
||||
return buft_ctx->alignment;
|
||||
}
|
||||
|
||||
static size_t get_max_size(const std::shared_ptr<socket_t> & sock, uint32_t device) {
|
||||
rpc_msg_get_max_size_req request = {device};
|
||||
static size_t get_max_size(const std::shared_ptr<rpc_dispatcher> & dispatcher, uint32_t device) {
|
||||
auto request = std::make_shared<rpc_msg_get_max_size_req>();
|
||||
request->device = device;
|
||||
rpc_msg_get_max_size_rsp response;
|
||||
bool status = send_rpc_cmd(sock, RPC_CMD_GET_MAX_SIZE, &request, sizeof(request), &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
dispatcher->send(RPC_CMD_GET_MAX_SIZE, request, sizeof(*request), &response, sizeof(response));
|
||||
return response.max_size;
|
||||
}
|
||||
|
||||
@@ -628,23 +833,63 @@ static size_t ggml_backend_rpc_buffer_type_get_alloc_size(ggml_backend_buffer_ty
|
||||
|
||||
if (rpc_get) {
|
||||
ggml_backend_rpc_buffer_type_context * buft_ctx = (ggml_backend_rpc_buffer_type_context *)buft->context;
|
||||
auto sock = get_socket(buft_ctx->endpoint);
|
||||
|
||||
rpc_msg_get_alloc_size_req request = {
|
||||
/*.device =*/ buft_ctx->device,
|
||||
/*.tensor =*/ serialize_tensor(tensor),
|
||||
/*.srcs =*/ {},
|
||||
// Cache key for calls to read the alloc_size.
|
||||
// We deliberately exclude src tensor dimensions from the key because:
|
||||
// 1. For CPU backends, alloc_size = ggml_nbytes(output) regardless of src shapes
|
||||
// 2. For GPU backends, the reservation graph uses max dimensions, so the
|
||||
// cached value from reservation is always >= any subsequent request
|
||||
// 3. Including src dims causes cache misses per-ubatch (e.g. growing KV cache)
|
||||
// which blocks the main thread behind in-flight GRAPH_COMPUTE commands
|
||||
struct alloc_size_cache_key {
|
||||
uint32_t device;
|
||||
uint32_t type;
|
||||
uint32_t op;
|
||||
int32_t op_params[GGML_MAX_OP_PARAMS / sizeof(int32_t)];
|
||||
uint32_t ne[GGML_MAX_DIMS];
|
||||
};
|
||||
|
||||
alloc_size_cache_key key = {};
|
||||
key.device = buft_ctx->device;
|
||||
key.type = tensor->type;
|
||||
key.op = tensor->op;
|
||||
memcpy(key.op_params, tensor->op_params, sizeof(key.op_params));
|
||||
for (int i = 0; i < GGML_MAX_DIMS; i++) {
|
||||
key.ne[i] = (uint32_t)tensor->ne[i];
|
||||
}
|
||||
|
||||
uint64_t cache_hash = fnv_hash((const uint8_t *)&key, sizeof(key));
|
||||
cache_hash = fnv_hash((const uint8_t *)buft_ctx->endpoint.data(), buft_ctx->endpoint.size(), cache_hash);
|
||||
|
||||
// alloc sizes are immutable for a given tensor configuration
|
||||
static std::mutex cache_mutex;
|
||||
static std::unordered_map<uint64_t, size_t> cache;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cache_mutex);
|
||||
auto it = cache.find(cache_hash);
|
||||
if (it != cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
auto request = std::make_shared<rpc_msg_get_alloc_size_req>();
|
||||
request->device = buft_ctx->device;
|
||||
request->tensor = serialize_tensor(tensor);
|
||||
|
||||
// .get_alloc_size could be a function of the tensor's srcs, so we must serialize them as well
|
||||
for (int i = 0; i < GGML_MAX_SRC; i++) {
|
||||
request.srcs[i] = serialize_tensor(tensor->src[i]);
|
||||
request->srcs[i] = serialize_tensor(tensor->src[i]);
|
||||
}
|
||||
|
||||
// TODO: cache the alloc responses to avoid extra RPC calls?
|
||||
rpc_msg_get_alloc_size_rsp response;
|
||||
bool status = send_rpc_cmd(sock, RPC_CMD_GET_ALLOC_SIZE, &request, sizeof(request), &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
auto dispatcher = get_dispatcher(buft_ctx->endpoint);
|
||||
dispatcher->send(RPC_CMD_GET_ALLOC_SIZE, request, sizeof(*request), &response, sizeof(response));
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(cache_mutex);
|
||||
cache[cache_hash] = response.alloc_size;
|
||||
}
|
||||
|
||||
return response.alloc_size;
|
||||
}
|
||||
@@ -673,9 +918,44 @@ static void ggml_backend_rpc_free(ggml_backend_t backend) {
|
||||
delete backend;
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_set_tensor_async(ggml_backend_t backend, ggml_tensor * tensor, const void * data, size_t offset, size_t size) {
|
||||
ggml_backend_rpc_context * ctx = (ggml_backend_rpc_context *)backend->context;
|
||||
rpc_tensor rpc_tensor = serialize_tensor(tensor);
|
||||
if (size > HASH_THRESHOLD) {
|
||||
auto request = std::make_shared<rpc_msg_set_tensor_hash_req>();
|
||||
request->tensor = rpc_tensor;
|
||||
request->offset = offset;
|
||||
request->hash = fnv_hash((const uint8_t*)data, size);
|
||||
rpc_msg_set_tensor_hash_rsp response;
|
||||
// TODO: make this async
|
||||
ctx->dispatcher->send(RPC_CMD_SET_TENSOR_HASH, request, sizeof(*request), &response, sizeof(response));
|
||||
if (response.result) {
|
||||
// the server has the same data, no need to send it
|
||||
return;
|
||||
}
|
||||
}
|
||||
// input serialization format: | rpc_tensor | offset (8 bytes) | data (size bytes)
|
||||
size_t input_size = sizeof(rpc_tensor) + sizeof(uint64_t) + size;
|
||||
uint8_t * input = new uint8_t[input_size]();
|
||||
memcpy(input, &rpc_tensor, sizeof(rpc_tensor));
|
||||
memcpy(input + sizeof(rpc_tensor), &offset, sizeof(offset));
|
||||
memcpy(input + sizeof(rpc_tensor) + sizeof(offset), data, size);
|
||||
std::shared_ptr<uint8_t> input_ptr(input, std::default_delete<uint8_t[]>());
|
||||
ctx->dispatcher->send_async(RPC_CMD_SET_TENSOR, input_ptr, input_size);
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_get_tensor_async(ggml_backend_t backend, const ggml_tensor * tensor, void * data, size_t offset, size_t size) {
|
||||
ggml_backend_rpc_context * ctx = (ggml_backend_rpc_context *)backend->context;
|
||||
auto request = std::make_shared<rpc_msg_get_tensor_req>();
|
||||
request->tensor = serialize_tensor(tensor);
|
||||
request->offset = offset;
|
||||
request->size = size;
|
||||
ctx->dispatcher->send_async(RPC_CMD_GET_TENSOR, request, sizeof(*request), data, size);
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_synchronize(ggml_backend_t backend) {
|
||||
GGML_UNUSED(backend);
|
||||
// this is no-op because we don't have any async operations
|
||||
ggml_backend_rpc_context * rpc_ctx = (ggml_backend_rpc_context *)backend->context;
|
||||
rpc_ctx->dispatcher->synchronize();
|
||||
}
|
||||
|
||||
static void add_tensor(ggml_tensor * tensor, const ggml_cgraph * cgraph, std::vector<rpc_tensor> & tensors, std::unordered_set<ggml_tensor*> & visited) {
|
||||
@@ -698,7 +978,7 @@ static void add_tensor(ggml_tensor * tensor, const ggml_cgraph * cgraph, std::ve
|
||||
tensors.push_back(result);
|
||||
}
|
||||
|
||||
static void serialize_graph(uint32_t device, const ggml_cgraph * cgraph, std::vector<uint8_t> & output) {
|
||||
static uint8_t * serialize_graph(uint32_t device, const ggml_cgraph * cgraph, size_t * output_size) {
|
||||
uint32_t n_nodes = cgraph->n_nodes;
|
||||
std::vector<rpc_tensor> tensors;
|
||||
std::unordered_set<ggml_tensor*> visited;
|
||||
@@ -708,9 +988,9 @@ static void serialize_graph(uint32_t device, const ggml_cgraph * cgraph, std::ve
|
||||
// serialization format:
|
||||
// | device (4 bytes) | n_nodes (4 bytes) | nodes (n_nodes * sizeof(uint64_t) | n_tensors (4 bytes) | tensors (n_tensors * sizeof(rpc_tensor)) |
|
||||
uint32_t n_tensors = tensors.size();
|
||||
int output_size = 2*sizeof(uint32_t) + n_nodes * sizeof(uint64_t) + sizeof(uint32_t) + n_tensors * sizeof(rpc_tensor);
|
||||
output.resize(output_size, 0);
|
||||
uint8_t * dest = output.data();
|
||||
*output_size = 2*sizeof(uint32_t) + n_nodes * sizeof(uint64_t) + sizeof(uint32_t) + n_tensors * sizeof(rpc_tensor);
|
||||
uint8_t * output = new uint8_t[*output_size]();
|
||||
uint8_t * dest = output;
|
||||
memcpy(dest, &device, sizeof(device));
|
||||
dest += sizeof(device);
|
||||
memcpy(dest, &n_nodes, sizeof(n_nodes));
|
||||
@@ -723,6 +1003,7 @@ static void serialize_graph(uint32_t device, const ggml_cgraph * cgraph, std::ve
|
||||
dest += sizeof(n_tensors);
|
||||
rpc_tensor * out_tensors = (rpc_tensor *)dest;
|
||||
memcpy(out_tensors, tensors.data(), n_tensors * sizeof(rpc_tensor));
|
||||
return output;
|
||||
}
|
||||
|
||||
static enum ggml_status ggml_backend_rpc_graph_compute(ggml_backend_t backend, ggml_cgraph * cgraph) {
|
||||
@@ -733,27 +1014,35 @@ static enum ggml_status ggml_backend_rpc_graph_compute(ggml_backend_t backend, g
|
||||
GGML_ASSERT(cgraph->n_nodes > 0);
|
||||
bool reuse = cgraph->uid != 0 && rpc_dev_ctx->last_graph_uid == cgraph->uid;
|
||||
if (reuse) {
|
||||
rpc_msg_graph_recompute_req request;
|
||||
request.device = rpc_ctx->device;
|
||||
auto sock = get_socket(rpc_ctx->endpoint);
|
||||
bool status = send_rpc_cmd(sock, RPC_CMD_GRAPH_RECOMPUTE, &request, sizeof(request));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
auto request = std::make_shared<rpc_msg_graph_recompute_req>();
|
||||
request->device = rpc_ctx->device;
|
||||
rpc_ctx->dispatcher->send_async(RPC_CMD_GRAPH_RECOMPUTE, request, sizeof(*request));
|
||||
} else {
|
||||
rpc_dev_ctx->last_graph_uid = cgraph->uid;
|
||||
std::vector<uint8_t> input;
|
||||
serialize_graph(rpc_ctx->device, cgraph, input);
|
||||
auto sock = get_socket(rpc_ctx->endpoint);
|
||||
bool status = send_rpc_cmd(sock, RPC_CMD_GRAPH_COMPUTE, input.data(), input.size());
|
||||
RPC_STATUS_ASSERT(status);
|
||||
size_t input_size = 0;
|
||||
uint8_t * input = serialize_graph(rpc_ctx->device, cgraph, &input_size);
|
||||
std::shared_ptr<uint8_t> input_ptr(input, std::default_delete<uint8_t[]>());
|
||||
rpc_ctx->dispatcher->send_async(RPC_CMD_GRAPH_COMPUTE, input_ptr, input_size);
|
||||
}
|
||||
return GGML_STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_event_record(ggml_backend_t backend, ggml_backend_event_t event) {
|
||||
ggml_backend_rpc_context * rpc_ctx = (ggml_backend_rpc_context *)backend->context;
|
||||
rpc_ctx->dispatcher->event_record(event);
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_event_wait(ggml_backend_t backend, ggml_backend_event_t event) {
|
||||
// this is noop for RPC as we have a single stream
|
||||
GGML_UNUSED(backend);
|
||||
GGML_UNUSED(event);
|
||||
}
|
||||
|
||||
static ggml_backend_i ggml_backend_rpc_interface = {
|
||||
/* .get_name = */ ggml_backend_rpc_name,
|
||||
/* .free = */ ggml_backend_rpc_free,
|
||||
/* .set_tensor_async = */ NULL,
|
||||
/* .get_tensor_async = */ NULL,
|
||||
/* .set_tensor_async = */ ggml_backend_rpc_set_tensor_async,
|
||||
/* .get_tensor_async = */ ggml_backend_rpc_get_tensor_async,
|
||||
/* .set_tensor_2d_async = */ NULL,
|
||||
/* .get_tensor_2d_async = */ NULL,
|
||||
/* .cpy_tensor_async = */ NULL,
|
||||
@@ -763,8 +1052,8 @@ static ggml_backend_i ggml_backend_rpc_interface = {
|
||||
/* .graph_plan_update = */ NULL,
|
||||
/* .graph_plan_compute = */ NULL,
|
||||
/* .graph_compute = */ ggml_backend_rpc_graph_compute,
|
||||
/* .event_record = */ NULL,
|
||||
/* .event_wait = */ NULL,
|
||||
/* .event_record = */ ggml_backend_rpc_event_record,
|
||||
/* .event_wait = */ ggml_backend_rpc_event_wait,
|
||||
/* .graph_optimize = */ NULL,
|
||||
};
|
||||
|
||||
@@ -778,13 +1067,9 @@ ggml_backend_buffer_type_t ggml_backend_rpc_buffer_type(const char * endpoint, u
|
||||
if (it != buft_map.end()) {
|
||||
return it->second;
|
||||
}
|
||||
auto sock = get_socket(endpoint);
|
||||
if (sock == nullptr) {
|
||||
GGML_LOG_ERROR("Failed to connect to %s\n", endpoint);
|
||||
return nullptr;
|
||||
}
|
||||
size_t alignment = get_alignment(sock, device);
|
||||
size_t max_size = get_max_size(sock, device);
|
||||
auto dispatcher = get_dispatcher(endpoint);
|
||||
size_t alignment = get_alignment(dispatcher, device);
|
||||
size_t max_size = get_max_size(dispatcher, device);
|
||||
ggml_backend_rpc_buffer_type_context * buft_ctx = new ggml_backend_rpc_buffer_type_context {
|
||||
/* .endpoint = */ endpoint,
|
||||
/* .device = */ device,
|
||||
@@ -804,10 +1089,11 @@ ggml_backend_buffer_type_t ggml_backend_rpc_buffer_type(const char * endpoint, u
|
||||
|
||||
ggml_backend_t ggml_backend_rpc_init(const char * endpoint, uint32_t device) {
|
||||
std::string dev_name = "RPC" + std::to_string(device) + "[" + std::string(endpoint) + "]";
|
||||
auto dispatcher = get_dispatcher(endpoint);
|
||||
ggml_backend_rpc_context * ctx = new ggml_backend_rpc_context {
|
||||
/* .endpoint = */ endpoint,
|
||||
/* .device = */ device,
|
||||
/* .name = */ dev_name,
|
||||
/* .dispatcher = */ dispatcher,
|
||||
/* .device = */ device,
|
||||
/* .name = */ dev_name,
|
||||
};
|
||||
auto reg = ggml_backend_rpc_add_server(endpoint);
|
||||
ggml_backend_t backend = new ggml_backend {
|
||||
@@ -823,26 +1109,16 @@ bool ggml_backend_is_rpc(ggml_backend_t backend) {
|
||||
return backend != NULL && ggml_guid_matches(backend->guid, ggml_backend_rpc_guid());
|
||||
}
|
||||
|
||||
static void get_device_memory(const std::shared_ptr<socket_t> & sock, uint32_t device, size_t * free, size_t * total) {
|
||||
rpc_msg_get_device_memory_req request;
|
||||
request.device = device;
|
||||
void ggml_backend_rpc_get_device_memory(const char * endpoint, uint32_t device, size_t * free, size_t * total) {
|
||||
auto dispatcher = get_dispatcher(endpoint);
|
||||
auto request = std::make_shared<rpc_msg_get_device_memory_req>();
|
||||
request->device = device;
|
||||
rpc_msg_get_device_memory_rsp response;
|
||||
bool status = send_rpc_cmd(sock, RPC_CMD_GET_DEVICE_MEMORY, &request, sizeof(request), &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
dispatcher->send(RPC_CMD_GET_DEVICE_MEMORY, request, sizeof(*request), &response, sizeof(response));
|
||||
*free = response.free_mem;
|
||||
*total = response.total_mem;
|
||||
}
|
||||
|
||||
void ggml_backend_rpc_get_device_memory(const char * endpoint, uint32_t device, size_t * free, size_t * total) {
|
||||
auto sock = get_socket(endpoint);
|
||||
if (sock == nullptr) {
|
||||
*free = 0;
|
||||
*total = 0;
|
||||
return;
|
||||
}
|
||||
get_device_memory(sock, device, free, total);
|
||||
}
|
||||
|
||||
// RPC server-side implementation
|
||||
|
||||
class rpc_server {
|
||||
@@ -1647,9 +1923,6 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
|
||||
if (!server.free_buffer(request)) {
|
||||
return;
|
||||
}
|
||||
if (!send_msg(sock, nullptr, 0)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RPC_CMD_BUFFER_CLEAR: {
|
||||
@@ -1660,9 +1933,6 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
|
||||
if (!server.buffer_clear(request)) {
|
||||
return;
|
||||
}
|
||||
if (!send_msg(sock, nullptr, 0)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RPC_CMD_MEMSET_TENSOR: {
|
||||
@@ -1673,9 +1943,6 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
|
||||
if (!server.memset_tensor(request)) {
|
||||
return;
|
||||
}
|
||||
if (!send_msg(sock, nullptr, 0)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RPC_CMD_SET_TENSOR: {
|
||||
@@ -1710,9 +1977,6 @@ static void rpc_serve_client(const std::vector<ggml_backend_t> & backends, const
|
||||
if (!server.init_tensor(request)) {
|
||||
return;
|
||||
}
|
||||
if (!send_msg(sock, nullptr, 0)) {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RPC_CMD_GET_TENSOR: {
|
||||
@@ -1889,10 +2153,10 @@ static void ggml_backend_rpc_device_get_props(ggml_backend_dev_t dev, struct ggm
|
||||
props->type = ggml_backend_rpc_device_get_type(dev);
|
||||
ggml_backend_rpc_device_get_memory(dev, &props->memory_free, &props->memory_total);
|
||||
props->caps = {
|
||||
/* .async = */ false,
|
||||
/* .async = */ true,
|
||||
/* .host_buffer = */ false,
|
||||
/* .buffer_from_host_ptr = */ false,
|
||||
/* .events = */ false,
|
||||
/* .events = */ true,
|
||||
/* .mmap_support = */ true,
|
||||
};
|
||||
}
|
||||
@@ -1929,6 +2193,24 @@ static bool ggml_backend_rpc_device_supports_buft(ggml_backend_dev_t dev, ggml_b
|
||||
return buft_ctx->endpoint == dev_ctx->endpoint && buft_ctx->device == dev_ctx->device;
|
||||
}
|
||||
|
||||
static ggml_backend_event_t ggml_backend_rpc_device_event_new(ggml_backend_dev_t dev) {
|
||||
ggml_backend_rpc_device_context * ctx = (ggml_backend_rpc_device_context *)dev->context;
|
||||
auto dispatcher = get_dispatcher(ctx->endpoint);
|
||||
return dispatcher->event_new(dev);
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_device_event_free(ggml_backend_dev_t dev, ggml_backend_event_t event) {
|
||||
ggml_backend_rpc_device_context * ctx = (ggml_backend_rpc_device_context *)dev->context;
|
||||
auto dispatcher = get_dispatcher(ctx->endpoint);
|
||||
dispatcher->event_free(event);
|
||||
}
|
||||
|
||||
static void ggml_backend_rpc_device_event_synchronize(ggml_backend_dev_t dev, ggml_backend_event_t event) {
|
||||
ggml_backend_rpc_device_context * ctx = (ggml_backend_rpc_device_context *)dev->context;
|
||||
auto dispatcher = get_dispatcher(ctx->endpoint);
|
||||
dispatcher->event_synchronize(event);
|
||||
}
|
||||
|
||||
static const struct ggml_backend_device_i ggml_backend_rpc_device_i = {
|
||||
/* .get_name = */ ggml_backend_rpc_device_get_name,
|
||||
/* .get_description = */ ggml_backend_rpc_device_get_description,
|
||||
@@ -1942,9 +2224,9 @@ static const struct ggml_backend_device_i ggml_backend_rpc_device_i = {
|
||||
/* .supports_op = */ ggml_backend_rpc_device_supports_op,
|
||||
/* .supports_buft = */ ggml_backend_rpc_device_supports_buft,
|
||||
/* .offload_op = */ NULL,
|
||||
/* .event_new = */ NULL,
|
||||
/* .event_free = */ NULL,
|
||||
/* .event_synchronize = */ NULL,
|
||||
/* .event_new = */ ggml_backend_rpc_device_event_new,
|
||||
/* .event_free = */ ggml_backend_rpc_device_event_free,
|
||||
/* .event_synchronize = */ ggml_backend_rpc_device_event_synchronize,
|
||||
};
|
||||
|
||||
// backend reg interface
|
||||
@@ -2004,14 +2286,9 @@ ggml_backend_reg_t ggml_backend_rpc_reg(void) {
|
||||
}
|
||||
|
||||
static uint32_t ggml_backend_rpc_get_device_count(const char * endpoint) {
|
||||
auto sock = get_socket(endpoint);
|
||||
if (sock == nullptr) {
|
||||
GGML_LOG_ERROR("Failed to connect to %s\n", endpoint);
|
||||
return 0;
|
||||
}
|
||||
auto dispatcher = get_dispatcher(endpoint);
|
||||
rpc_msg_device_count_rsp response;
|
||||
bool status = send_rpc_cmd(sock, RPC_CMD_DEVICE_COUNT, nullptr, 0, &response, sizeof(response));
|
||||
RPC_STATUS_ASSERT(status);
|
||||
dispatcher->send(RPC_CMD_DEVICE_COUNT, nullptr, 0, &response, sizeof(response));
|
||||
return response.device_count;
|
||||
}
|
||||
|
||||
|
||||
@@ -1042,6 +1042,8 @@ struct vk_device_struct {
|
||||
vk_pipeline pipeline_argsort_large_f32[num_argsort_pipelines];
|
||||
vk_pipeline pipeline_topk_f32[num_topk_pipelines];
|
||||
vk_pipeline pipeline_sum_rows_f32;
|
||||
vk_pipeline pipeline_cross_entropy_loss_f32, pipeline_cross_entropy_loss_f32_wg512;
|
||||
vk_pipeline pipeline_cross_entropy_loss_back_f32, pipeline_cross_entropy_loss_back_f32_wg512;
|
||||
vk_pipeline pipeline_fwht_f32[4];
|
||||
vk_pipeline pipeline_cumsum_f32;
|
||||
vk_pipeline pipeline_cumsum_small_f32;
|
||||
@@ -4169,10 +4171,16 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
const uint32_t subgroup_size_16 = std::max(device->subgroup_size, 16u);
|
||||
const uint32_t subgroup_size_32 = std::max(device->subgroup_size, 32u);
|
||||
|
||||
// clamp WARP for l_/m_ warptiles so WM <= BM (breaks on subgroupSize > 64)
|
||||
const uint32_t mm_warp_8 = std::min(subgroup_size_8, 64u);
|
||||
const uint32_t mm_warp_16 = std::min(subgroup_size_16, 64u);
|
||||
|
||||
const uint32_t mul_mat_subgroup_size = (device->vendor_id == VK_VENDOR_ID_INTEL && device->subgroup_size_control) ? device->subgroup_min_size : device->subgroup_size;
|
||||
const uint32_t mul_mat_subgroup_size_8 = std::max(mul_mat_subgroup_size, 8u);
|
||||
const uint32_t mul_mat_subgroup_size_16 = std::max(mul_mat_subgroup_size, 16u);
|
||||
const uint32_t mul_mat_subgroup_size_32 = std::max(mul_mat_subgroup_size, 32u);
|
||||
const uint32_t mul_mat_mm_warp_8 = std::min(mul_mat_subgroup_size_8, 64u);
|
||||
const uint32_t mul_mat_mm_warp_16 = std::min(mul_mat_subgroup_size_16, 64u);
|
||||
|
||||
const bool subgroup_min_size_16 = (!device->subgroup_size_control && device->subgroup_size >= 16) ||
|
||||
(device->subgroup_size_control && device->subgroup_max_size >= 16);
|
||||
@@ -4253,39 +4261,39 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
|
||||
const uint32_t s_warptile_wm = device->subgroup_size == 8 ? 8 : 32;
|
||||
|
||||
l_warptile = { 128, 128, 128, 16, subgroup_size_8 * 2, 64, 2, tm_l, tn_l, tk_l, subgroup_size_8 };
|
||||
m_warptile = { 128, 64, 64, 16, subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
|
||||
s_warptile = { subgroup_size_32, 32, 32, 16, s_warptile_wm, 32, 2, tm_s, tn_s, tk_s, subgroup_size_8 };
|
||||
l_warptile = { 128, 128, 128, 16, mm_warp_8 * 2, 64, 2, tm_l, tn_l, tk_l, mm_warp_8 };
|
||||
m_warptile = { 128, 64, 64, 16, mm_warp_8, 32, 2, tm_m, tn_m, tk_m, mm_warp_8 };
|
||||
s_warptile = { subgroup_size_32, 32, 32, 16, s_warptile_wm, 32, 2, tm_s, tn_s, tk_s, subgroup_size_8 };
|
||||
|
||||
l_warptile_mmq = { 128, 128, 128, 32, subgroup_size_8 * 2, 64, 2, tm_l, tn_l, tk_l, subgroup_size_8 };
|
||||
m_warptile_mmq = { 128, 64, 64, 32, subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
|
||||
s_warptile_mmq = { subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 2, tm_s, tn_s, tk_s, subgroup_size_8 };
|
||||
l_warptile_mmq = { 128, 128, 128, 32, mm_warp_8 * 2, 64, 2, tm_l, tn_l, tk_l, mm_warp_8 };
|
||||
m_warptile_mmq = { 128, 64, 64, 32, mm_warp_8, 32, 2, tm_m, tn_m, tk_m, mm_warp_8 };
|
||||
s_warptile_mmq = { subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 2, tm_s, tn_s, tk_s, subgroup_size_8 };
|
||||
|
||||
// Integer MMQ has a smaller shared memory profile, but heavier register use
|
||||
l_warptile_mmq_int = { 128, 128, 128, 32, subgroup_size_8 * 2, 64, 2, 4, 4, 1, subgroup_size_8 };
|
||||
m_warptile_mmq_int = { 128, 64, 64, 32, subgroup_size_8, 32, 2, 2, 2, 1, subgroup_size_8 };
|
||||
s_warptile_mmq_int = { subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 2, 2, 1, 1, subgroup_size_8 };
|
||||
l_warptile_mmq_int = { 128, 128, 128, 32, mm_warp_8 * 2, 64, 2, 4, 4, 1, mm_warp_8 };
|
||||
m_warptile_mmq_int = { 128, 64, 64, 32, mm_warp_8, 32, 2, 2, 2, 1, mm_warp_8 };
|
||||
s_warptile_mmq_int = { subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 2, 2, 1, 1, subgroup_size_8 };
|
||||
|
||||
// K-quants use even more registers, mitigate by setting WMITER to 1
|
||||
l_warptile_mmq_int_k = { 128, 128, 128, 32, subgroup_size_8 * 2, 64, 1, 4, 4, 1, subgroup_size_8 };
|
||||
m_warptile_mmq_int_k = { 128, 64, 64, 32, subgroup_size_8, 32, 1, 2, 2, 1, subgroup_size_8 };
|
||||
s_warptile_mmq_int_k = { subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 1, 2, 1, 1, subgroup_size_8 };
|
||||
l_warptile_mmq_int_k = { 128, 128, 128, 32, mm_warp_8 * 2, 64, 1, 4, 4, 1, mm_warp_8 };
|
||||
m_warptile_mmq_int_k = { 128, 64, 64, 32, mm_warp_8, 32, 1, 2, 2, 1, mm_warp_8 };
|
||||
s_warptile_mmq_int_k = { subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 1, 2, 1, 1, subgroup_size_8 };
|
||||
|
||||
l_warptile_id = { 128, 128, 128, 16, mul_mat_subgroup_size_16 * 2, 64, 2, tm_l, tn_l, tk_l, mul_mat_subgroup_size_16 };
|
||||
m_warptile_id = { 128, 64, 64, 16, mul_mat_subgroup_size_16, 32, 2, tm_m, tn_m, tk_m, mul_mat_subgroup_size_16 };
|
||||
s_warptile_id = { mul_mat_subgroup_size_16, 32, 32, 16, s_warptile_wm, 32, 2, tm_s, tn_s, tk_s, mul_mat_subgroup_size_16 };
|
||||
l_warptile_id = { 128, 128, 128, 16, mul_mat_mm_warp_16 * 2, 64, 2, tm_l, tn_l, tk_l, mul_mat_mm_warp_16 };
|
||||
m_warptile_id = { 128, 64, 64, 16, mul_mat_mm_warp_16, 32, 2, tm_m, tn_m, tk_m, mul_mat_mm_warp_16 };
|
||||
s_warptile_id = { mul_mat_subgroup_size_16, 32, 32, 16, s_warptile_wm, 32, 2, tm_s, tn_s, tk_s, mul_mat_subgroup_size_16 };
|
||||
|
||||
l_warptile_mmqid = { 128, 128, 128, 32, mul_mat_subgroup_size_8 * 2, 64, 2, tm_l, tn_l, tk_l, mul_mat_subgroup_size_8 };
|
||||
m_warptile_mmqid = { 128, 64, 64, 32, mul_mat_subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, mul_mat_subgroup_size_8 };
|
||||
s_warptile_mmqid = { mul_mat_subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 2, tm_s, tn_s, tk_s, mul_mat_subgroup_size_8 };
|
||||
l_warptile_mmqid = { 128, 128, 128, 32, mul_mat_mm_warp_8 * 2, 64, 2, tm_l, tn_l, tk_l, mul_mat_mm_warp_8 };
|
||||
m_warptile_mmqid = { 128, 64, 64, 32, mul_mat_mm_warp_8, 32, 2, tm_m, tn_m, tk_m, mul_mat_mm_warp_8 };
|
||||
s_warptile_mmqid = { mul_mat_subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 2, tm_s, tn_s, tk_s, mul_mat_subgroup_size_8 };
|
||||
|
||||
l_warptile_mmqid_int = { 128, 128, 128, 32, mul_mat_subgroup_size_8 * 2, 64, 2, 4, 4, 1, mul_mat_subgroup_size_8 };
|
||||
m_warptile_mmqid_int = { 128, 64, 64, 32, mul_mat_subgroup_size_8, 32, 2, 2, 2, 1, mul_mat_subgroup_size_8 };
|
||||
s_warptile_mmqid_int = { mul_mat_subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 2, 2, 1, 1, mul_mat_subgroup_size_8 };
|
||||
l_warptile_mmqid_int = { 128, 128, 128, 32, mul_mat_mm_warp_8 * 2, 64, 2, 4, 4, 1, mul_mat_mm_warp_8 };
|
||||
m_warptile_mmqid_int = { 128, 64, 64, 32, mul_mat_mm_warp_8, 32, 2, 2, 2, 1, mul_mat_mm_warp_8 };
|
||||
s_warptile_mmqid_int = { mul_mat_subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 2, 2, 1, 1, mul_mat_subgroup_size_8 };
|
||||
|
||||
l_warptile_mmqid_int_k = { 128, 128, 128, 32, mul_mat_subgroup_size_16 * 2, 64, 1, 4, 4, 1, mul_mat_subgroup_size_16 };
|
||||
m_warptile_mmqid_int_k = { 128, 64, 64, 32, mul_mat_subgroup_size_16, 32, 1, 2, 2, 1, mul_mat_subgroup_size_16 };
|
||||
s_warptile_mmqid_int_k = { mul_mat_subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 1, 2, 1, 1, mul_mat_subgroup_size_16 };
|
||||
l_warptile_mmqid_int_k = { 128, 128, 128, 32, mul_mat_mm_warp_16 * 2, 64, 1, 4, 4, 1, mul_mat_mm_warp_16 };
|
||||
m_warptile_mmqid_int_k = { 128, 64, 64, 32, mul_mat_mm_warp_16, 32, 1, 2, 2, 1, mul_mat_mm_warp_16 };
|
||||
s_warptile_mmqid_int_k = { mul_mat_subgroup_size_32, 32, 32, 32, s_warptile_wm, 32, 1, 2, 1, 1, mul_mat_subgroup_size_16 };
|
||||
|
||||
// chip specific tuning
|
||||
if ((device->architecture == AMD_GCN) && (device->driver_id != vk::DriverId::eAmdProprietary)) {
|
||||
@@ -4293,13 +4301,13 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
m_warptile_mmqid = m_warptile_mmqid_int = { 256, 64, 64, 32, 16, 16, 2, 2, 2, 1, 16 };
|
||||
} else if (device->vendor_id == VK_VENDOR_ID_AMD && device->coopmat_support && device->driver_id != vk::DriverId::eAmdProprietary) {
|
||||
// This is intentionally using tx_m values, slight performance increase
|
||||
l_warptile = { 256, 128, 128, 16, subgroup_size_8, 64, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
|
||||
l_warptile_mmq = l_warptile_mmq_int = { 256, 128, 128, 32, subgroup_size_8, 64, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
|
||||
l_warptile_mmq_int_k = { 256, 128, 128, 32, subgroup_size_16, 64, 1, 4, 2, 1, subgroup_size_16 };
|
||||
l_warptile = { 256, 128, 128, 16, mm_warp_8, 64, 2, tm_m, tn_m, tk_m, mm_warp_8 };
|
||||
l_warptile_mmq = l_warptile_mmq_int = { 256, 128, 128, 32, mm_warp_8, 64, 2, tm_m, tn_m, tk_m, mm_warp_8 };
|
||||
l_warptile_mmq_int_k = { 256, 128, 128, 32, mm_warp_16, 64, 1, 4, 2, 1, mm_warp_16 };
|
||||
} else if (device->vendor_id == VK_VENDOR_ID_INTEL && device->coopmat_support) {
|
||||
// Xe2/Xe3 with coopmat enabled - warptile performance tuning
|
||||
l_warptile = { 512, 128, 128, 16, subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
|
||||
l_warptile_mmq = { 512, 128, 128, 32, subgroup_size_8, 32, 2, tm_m, tn_m, tk_m, subgroup_size_8 };
|
||||
l_warptile = { 512, 128, 128, 16, mm_warp_8, 32, 2, tm_m, tn_m, tk_m, mm_warp_8 };
|
||||
l_warptile_mmq = { 512, 128, 128, 32, mm_warp_8, 32, 2, tm_m, tn_m, tk_m, mm_warp_8 };
|
||||
}
|
||||
|
||||
l_mmq_wg_denoms = l_wg_denoms = {128, 128, 1 };
|
||||
@@ -5172,8 +5180,8 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
const uint32_t s_warptile_wm = device->subgroup_size == 8 ? 8 : 32;
|
||||
|
||||
// use scalar tile sizes
|
||||
l_warptile = { 128, 128, 128, 16, subgroup_size_8 * 2, 64, 2, 4, 4, 1, subgroup_size_8 };
|
||||
m_warptile = { 128, 64, 64, 16, subgroup_size_8, 32, 2, 4, 2, 1, subgroup_size_8 };
|
||||
l_warptile = { 128, 128, 128, 16, mm_warp_8 * 2, 64, 2, 4, 4, 1, mm_warp_8 };
|
||||
m_warptile = { 128, 64, 64, 16, mm_warp_8, 32, 2, 4, 2, 1, mm_warp_8 };
|
||||
s_warptile = { subgroup_size_32, 32, 32, 16, s_warptile_wm, 32, 2, 2, 2, 1, subgroup_size_8 };
|
||||
|
||||
l_wg_denoms = {128, 128, 1 };
|
||||
@@ -5758,6 +5766,10 @@ static void ggml_vk_load_shaders(vk_device& device, vk_pipeline requested) {
|
||||
ggml_vk_create_pipeline(device, device->pipeline_argmax_f32, "argmax_f32", argmax_f32_len, argmax_f32_data, "main", 2, sizeof(vk_op_push_constants), {1, 1, 1}, { device->subgroup_size }, 1);
|
||||
|
||||
ggml_vk_create_pipeline(device, device->pipeline_sum_rows_f32, "sum_rows_f32", sum_rows_f32_len, sum_rows_f32_data, "main", 2, sizeof(vk_op_sum_rows_push_constants), {1, 1, 1}, { device->subgroup_size }, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_cross_entropy_loss_f32, "cross_entropy_loss_f32", cross_entropy_loss_f32_len, cross_entropy_loss_f32_data, "main", 3, sizeof(vk_op_push_constants), {1, 1, 1}, { device->subgroup_size }, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_cross_entropy_loss_f32_wg512, "cross_entropy_loss_f32_wg512", cross_entropy_loss_f32_len, cross_entropy_loss_f32_data, "main", 3, sizeof(vk_op_push_constants), {1, 1, 1}, { 512 }, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_cross_entropy_loss_back_f32, "cross_entropy_loss_back_f32", cross_entropy_loss_back_f32_len, cross_entropy_loss_back_f32_data, "main", 4, sizeof(vk_op_push_constants), {1, 1, 1}, { device->subgroup_size }, 1);
|
||||
ggml_vk_create_pipeline(device, device->pipeline_cross_entropy_loss_back_f32_wg512, "cross_entropy_loss_back_f32_wg512", cross_entropy_loss_back_f32_len, cross_entropy_loss_back_f32_data, "main", 4, sizeof(vk_op_push_constants), {1, 1, 1}, { 512 }, 1);
|
||||
// Intel Windows driver in range [32.0.101.8509, 32.0.101.8860) will crash when using fwht kernels so we gate that here
|
||||
const bool can_use_fwht = device->driver_id != vk::DriverId::eIntelProprietaryWindows ||
|
||||
!ggml_vk_intel_windows_driver_in_range(device->properties.driverVersion, 101, 8509, 101, 8860);
|
||||
@@ -11577,6 +11589,17 @@ static vk_pipeline ggml_vk_op_get_pipeline(ggml_backend_vk_context * ctx, const
|
||||
return ctx->device->pipeline_sum_rows_f32;
|
||||
}
|
||||
return nullptr;
|
||||
case GGML_OP_CROSS_ENTROPY_LOSS:
|
||||
if (src0->type == GGML_TYPE_F32 && src1->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
|
||||
return src0->ne[0] > 1024 ? ctx->device->pipeline_cross_entropy_loss_f32_wg512 : ctx->device->pipeline_cross_entropy_loss_f32;
|
||||
}
|
||||
return nullptr;
|
||||
case GGML_OP_CROSS_ENTROPY_LOSS_BACK:
|
||||
// src0 is the scalar grad; src1 is logits
|
||||
if (src0->type == GGML_TYPE_F32 && src1->type == GGML_TYPE_F32 && src2 && src2->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
|
||||
return src1->ne[0] > 1024 ? ctx->device->pipeline_cross_entropy_loss_back_f32_wg512 : ctx->device->pipeline_cross_entropy_loss_back_f32;
|
||||
}
|
||||
return nullptr;
|
||||
case GGML_OP_CUMSUM:
|
||||
if (src0->type == GGML_TYPE_F32 && dst->type == GGML_TYPE_F32) {
|
||||
if (src0->ne[0] <= 512) {
|
||||
@@ -13942,6 +13965,103 @@ static void ggml_vk_cumsum(ggml_backend_vk_context * ctx, vk_context& subctx, co
|
||||
ctx->prealloc_split_k_need_sync = true;
|
||||
}
|
||||
|
||||
static std::array<uint32_t, 3> ggml_vk_nrows_elements(uint32_t nr) {
|
||||
if (nr > 262144) {
|
||||
return { 512, 512, CEIL_DIV(nr, 262144) };
|
||||
}
|
||||
if (nr > 512) {
|
||||
return { 512, CEIL_DIV(nr, 512), 1 };
|
||||
}
|
||||
return { nr, 1, 1 };
|
||||
}
|
||||
|
||||
static void ggml_vk_cross_entropy_loss(ggml_backend_vk_context * ctx, vk_context& subctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * src0 = dst->src[0];
|
||||
const ggml_tensor * src1 = dst->src[1];
|
||||
|
||||
GGML_ASSERT(src0->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(src1->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(dst->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(ggml_is_contiguous(src0));
|
||||
GGML_ASSERT(ggml_is_contiguous(src1));
|
||||
GGML_ASSERT(ggml_is_contiguous(dst));
|
||||
GGML_ASSERT(ggml_are_same_shape(src0, src1));
|
||||
GGML_ASSERT(ggml_is_scalar(dst));
|
||||
|
||||
const uint32_t nclasses = (uint32_t)src0->ne[0];
|
||||
const uint32_t nrows = (uint32_t)ggml_nrows(src0);
|
||||
|
||||
vk_pipeline pipeline = ggml_vk_op_get_pipeline(ctx, src0, src1, nullptr, dst, GGML_OP_CROSS_ENTROPY_LOSS);
|
||||
GGML_ASSERT(pipeline != nullptr);
|
||||
|
||||
ggml_pipeline_request_descriptor_sets(ctx, pipeline, 1);
|
||||
ggml_pipeline_request_descriptor_sets(ctx, ctx->device->pipeline_sum_rows_f32, 1);
|
||||
|
||||
vk_subbuffer src0_buf = ggml_vk_tensor_subbuffer(ctx, src0);
|
||||
vk_subbuffer src1_buf = ggml_vk_tensor_subbuffer(ctx, src1);
|
||||
vk_subbuffer dst_buf = ggml_vk_tensor_subbuffer(ctx, dst, true);
|
||||
|
||||
const vk_op_push_constants pc = { nclasses, nrows, 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
|
||||
const size_t tmp_size = (size_t)nrows * sizeof(float);
|
||||
if (ctx->prealloc_size_x < tmp_size) {
|
||||
ctx->prealloc_size_x = tmp_size;
|
||||
ggml_vk_preallocate_buffers(ctx, subctx);
|
||||
}
|
||||
if (ctx->prealloc_x_need_sync) {
|
||||
ggml_vk_sync_buffers(ctx, subctx);
|
||||
}
|
||||
|
||||
vk_subbuffer tmp_buf = { ctx->prealloc_x, 0, tmp_size };
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { src0_buf, src1_buf, tmp_buf }, pc, ggml_vk_nrows_elements(nrows));
|
||||
ggml_vk_sync_buffers(ctx, subctx);
|
||||
|
||||
vk_op_sum_rows_push_constants sp = {};
|
||||
sp.n_cols = nrows;
|
||||
sp.ne01 = 1;
|
||||
sp.ne02 = 1;
|
||||
sp.weight = 1.0f;
|
||||
init_pushconst_fastdiv(sp);
|
||||
sp.misalign_offsets = get_misalign_bytes(ctx, dst) / ggml_type_size(dst->type);
|
||||
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, ctx->device->pipeline_sum_rows_f32, { tmp_buf, dst_buf }, sp, { 1, 1, 1 });
|
||||
ctx->prealloc_x_need_sync = true;
|
||||
}
|
||||
|
||||
static void ggml_vk_cross_entropy_loss_back(ggml_backend_vk_context * ctx, vk_context& subctx, ggml_tensor * dst) {
|
||||
const ggml_tensor * grad = dst->src[0];
|
||||
const ggml_tensor * logits = dst->src[1];
|
||||
const ggml_tensor * labels = dst->src[2];
|
||||
|
||||
GGML_ASSERT(grad->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(logits->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(labels->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(dst->type == GGML_TYPE_F32);
|
||||
GGML_ASSERT(ggml_is_scalar(grad));
|
||||
GGML_ASSERT(ggml_is_contiguous(grad));
|
||||
GGML_ASSERT(ggml_is_contiguous(logits));
|
||||
GGML_ASSERT(ggml_is_contiguous(labels));
|
||||
GGML_ASSERT(ggml_is_contiguous(dst));
|
||||
GGML_ASSERT(ggml_are_same_shape(logits, labels));
|
||||
GGML_ASSERT(ggml_are_same_shape(logits, dst));
|
||||
|
||||
const uint32_t nclasses = (uint32_t)logits->ne[0];
|
||||
const uint32_t nrows = (uint32_t)ggml_nrows(logits);
|
||||
|
||||
vk_pipeline pipeline = ggml_vk_op_get_pipeline(ctx, grad, logits, labels, dst, GGML_OP_CROSS_ENTROPY_LOSS_BACK);
|
||||
GGML_ASSERT(pipeline != nullptr);
|
||||
|
||||
ggml_pipeline_request_descriptor_sets(ctx, pipeline, 1);
|
||||
|
||||
vk_subbuffer grad_buf = ggml_vk_tensor_subbuffer(ctx, grad);
|
||||
vk_subbuffer logits_buf = ggml_vk_tensor_subbuffer(ctx, logits);
|
||||
vk_subbuffer labels_buf = ggml_vk_tensor_subbuffer(ctx, labels);
|
||||
vk_subbuffer dst_buf = ggml_vk_tensor_subbuffer(ctx, dst);
|
||||
|
||||
const vk_op_push_constants pc = { nclasses, nrows, 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
ggml_vk_dispatch_pipeline(ctx, subctx, pipeline, { grad_buf, logits_buf, labels_buf, dst_buf }, pc, ggml_vk_nrows_elements(nrows));
|
||||
}
|
||||
|
||||
static void ggml_vk_argmax(ggml_backend_vk_context * ctx, vk_context& subctx, const ggml_tensor * src0, ggml_tensor * dst) {
|
||||
ggml_vk_op_f32<vk_op_push_constants>(ctx, subctx, src0, nullptr, nullptr, nullptr, dst, GGML_OP_ARGMAX, { (uint32_t)src0->ne[0], (uint32_t)src0->ne[1], 0.0f, 0.0f, 0.0f, 0.0f });
|
||||
}
|
||||
@@ -15687,6 +15807,14 @@ static bool ggml_vk_build_graph(ggml_backend_vk_context * ctx, ggml_cgraph * cgr
|
||||
case GGML_OP_ARGMAX:
|
||||
ggml_vk_argmax(ctx, compute_ctx, src0, node);
|
||||
|
||||
break;
|
||||
case GGML_OP_CROSS_ENTROPY_LOSS:
|
||||
ggml_vk_cross_entropy_loss(ctx, compute_ctx, node);
|
||||
|
||||
break;
|
||||
case GGML_OP_CROSS_ENTROPY_LOSS_BACK:
|
||||
ggml_vk_cross_entropy_loss_back(ctx, compute_ctx, node);
|
||||
|
||||
break;
|
||||
case GGML_OP_COUNT_EQUAL:
|
||||
ggml_vk_count_equal(ctx, compute_ctx, src0, src1, node);
|
||||
@@ -18511,6 +18639,18 @@ static bool ggml_backend_vk_device_supports_op(ggml_backend_dev_t dev, const ggm
|
||||
}
|
||||
case GGML_OP_ARGMAX:
|
||||
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32;
|
||||
case GGML_OP_CROSS_ENTROPY_LOSS:
|
||||
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32
|
||||
&& ggml_is_contiguous(op->src[1]) && op->src[1]->type == GGML_TYPE_F32
|
||||
&& ggml_are_same_shape(op->src[0], op->src[1])
|
||||
&& ggml_is_contiguous(op) && ggml_is_scalar(op) && op->type == GGML_TYPE_F32;
|
||||
case GGML_OP_CROSS_ENTROPY_LOSS_BACK:
|
||||
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_F32 && ggml_is_scalar(op->src[0])
|
||||
&& ggml_is_contiguous(op->src[1]) && op->src[1]->type == GGML_TYPE_F32
|
||||
&& ggml_is_contiguous(op->src[2]) && op->src[2]->type == GGML_TYPE_F32
|
||||
&& ggml_are_same_shape(op->src[1], op->src[2])
|
||||
&& ggml_are_same_shape(op->src[1], op)
|
||||
&& ggml_is_contiguous(op) && op->type == GGML_TYPE_F32;
|
||||
case GGML_OP_COUNT_EQUAL:
|
||||
return ggml_is_contiguous(op->src[0]) && op->src[0]->type == GGML_TYPE_I32
|
||||
&& ggml_is_contiguous(op->src[1]) && op->src[1]->type == GGML_TYPE_I32;
|
||||
@@ -19437,6 +19577,10 @@ static void ggml_vk_check_results_0(ggml_backend_vk_context * ctx, ggml_cgraph *
|
||||
tensor_clone = ggml_mean(ggml_ctx, src_clone[0]);
|
||||
} else if (tensor->op == GGML_OP_ARGMAX) {
|
||||
tensor_clone = ggml_argmax(ggml_ctx, src_clone[0]);
|
||||
} else if (tensor->op == GGML_OP_CROSS_ENTROPY_LOSS) {
|
||||
tensor_clone = ggml_cross_entropy_loss(ggml_ctx, src_clone[0], src_clone[1]);
|
||||
} else if (tensor->op == GGML_OP_CROSS_ENTROPY_LOSS_BACK) {
|
||||
tensor_clone = ggml_cross_entropy_loss_back(ggml_ctx, src_clone[0], src_clone[1], src_clone[2]);
|
||||
} else if (tensor->op == GGML_OP_COUNT_EQUAL) {
|
||||
tensor_clone = ggml_count_equal(ggml_ctx, src_clone[0], src_clone[1]);
|
||||
} else if (tensor->op == GGML_OP_SOLVE_TRI) {
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
#version 450
|
||||
|
||||
#include "generic_head.glsl"
|
||||
#include "types.glsl"
|
||||
|
||||
#extension GL_EXT_control_flow_attributes : enable
|
||||
|
||||
layout(constant_id = 0) const uint BLOCK_SIZE = 32;
|
||||
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout (binding = 0) readonly buffer A {A_TYPE data_a[];};
|
||||
layout (binding = 1) readonly buffer B {B_TYPE data_b[];};
|
||||
layout (binding = 2) writeonly buffer D {D_TYPE data_d[];};
|
||||
|
||||
shared FLOAT_TYPE tmp[BLOCK_SIZE];
|
||||
|
||||
FLOAT_TYPE wg_reduce_max(FLOAT_TYPE v) {
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
tmp[tid] = v;
|
||||
barrier();
|
||||
[[unroll]] for (uint s = BLOCK_SIZE / 2; s > 0; s >>= 1) {
|
||||
if (tid < s) {
|
||||
tmp[tid] = max(tmp[tid], tmp[tid + s]);
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
v = tmp[0];
|
||||
barrier();
|
||||
return v;
|
||||
}
|
||||
|
||||
FLOAT_TYPE wg_reduce_sum(FLOAT_TYPE v) {
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
tmp[tid] = v;
|
||||
barrier();
|
||||
[[unroll]] for (uint s = BLOCK_SIZE / 2; s > 0; s >>= 1) {
|
||||
if (tid < s) {
|
||||
tmp[tid] += tmp[tid + s];
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
v = tmp[0];
|
||||
barrier();
|
||||
return v;
|
||||
}
|
||||
|
||||
void main() {
|
||||
const uint row = gl_WorkGroupID.z * 262144 + gl_WorkGroupID.y * 512 + gl_WorkGroupID.x;
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
|
||||
if (row >= p.KY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint off = row * p.KX;
|
||||
|
||||
FLOAT_TYPE max_logit = FLOAT_TYPE(uintBitsToFloat(0xFF800000));
|
||||
for (uint i = tid; i < p.KX; i += BLOCK_SIZE) {
|
||||
max_logit = max(max_logit, FLOAT_TYPE(data_a[off + i]));
|
||||
}
|
||||
max_logit = wg_reduce_max(max_logit);
|
||||
|
||||
FLOAT_TYPE sum_exp = FLOAT_TYPE(0.0f);
|
||||
for (uint i = tid; i < p.KX; i += BLOCK_SIZE) {
|
||||
sum_exp += exp(FLOAT_TYPE(data_a[off + i]) - max_logit);
|
||||
}
|
||||
const FLOAT_TYPE log_sum = log(wg_reduce_sum(sum_exp));
|
||||
|
||||
FLOAT_TYPE loss = FLOAT_TYPE(0.0f);
|
||||
for (uint i = tid; i < p.KX; i += BLOCK_SIZE) {
|
||||
loss += (FLOAT_TYPE(data_a[off + i]) - max_logit - log_sum) * FLOAT_TYPE(data_b[off + i]);
|
||||
}
|
||||
loss = -wg_reduce_sum(loss) / FLOAT_TYPE(p.KY);
|
||||
|
||||
if (tid == 0) {
|
||||
data_d[row] = D_TYPE(loss);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#version 450
|
||||
|
||||
#include "generic_head.glsl"
|
||||
#include "types.glsl"
|
||||
|
||||
#extension GL_EXT_control_flow_attributes : enable
|
||||
|
||||
layout(constant_id = 0) const uint BLOCK_SIZE = 32;
|
||||
layout(local_size_x_id = 0, local_size_y = 1, local_size_z = 1) in;
|
||||
|
||||
layout (binding = 0) readonly buffer G {A_TYPE data_g[];};
|
||||
layout (binding = 1) readonly buffer X {B_TYPE data_x[];};
|
||||
layout (binding = 2) readonly buffer Y {B_TYPE data_y[];};
|
||||
layout (binding = 3) writeonly buffer D {D_TYPE data_d[];};
|
||||
|
||||
shared FLOAT_TYPE tmp[BLOCK_SIZE];
|
||||
|
||||
FLOAT_TYPE wg_reduce_max(FLOAT_TYPE v) {
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
tmp[tid] = v;
|
||||
barrier();
|
||||
[[unroll]] for (uint s = BLOCK_SIZE / 2; s > 0; s >>= 1) {
|
||||
if (tid < s) {
|
||||
tmp[tid] = max(tmp[tid], tmp[tid + s]);
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
v = tmp[0];
|
||||
barrier();
|
||||
return v;
|
||||
}
|
||||
|
||||
FLOAT_TYPE wg_reduce_sum(FLOAT_TYPE v) {
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
tmp[tid] = v;
|
||||
barrier();
|
||||
[[unroll]] for (uint s = BLOCK_SIZE / 2; s > 0; s >>= 1) {
|
||||
if (tid < s) {
|
||||
tmp[tid] += tmp[tid + s];
|
||||
}
|
||||
barrier();
|
||||
}
|
||||
v = tmp[0];
|
||||
barrier();
|
||||
return v;
|
||||
}
|
||||
|
||||
void main() {
|
||||
const uint row = gl_WorkGroupID.z * 262144 + gl_WorkGroupID.y * 512 + gl_WorkGroupID.x;
|
||||
const uint tid = gl_LocalInvocationID.x;
|
||||
|
||||
if (row >= p.KY) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint off = row * p.KX;
|
||||
const FLOAT_TYPE d_by_nrows = FLOAT_TYPE(data_g[0]) / FLOAT_TYPE(p.KY);
|
||||
|
||||
FLOAT_TYPE max_logit = FLOAT_TYPE(uintBitsToFloat(0xFF800000));
|
||||
for (uint i = tid; i < p.KX; i += BLOCK_SIZE) {
|
||||
max_logit = max(max_logit, FLOAT_TYPE(data_x[off + i]));
|
||||
}
|
||||
max_logit = wg_reduce_max(max_logit);
|
||||
|
||||
FLOAT_TYPE sum_exp = FLOAT_TYPE(0.0f);
|
||||
for (uint i = tid; i < p.KX; i += BLOCK_SIZE) {
|
||||
sum_exp += exp(FLOAT_TYPE(data_x[off + i]) - max_logit);
|
||||
}
|
||||
const FLOAT_TYPE inv_sum = FLOAT_TYPE(1.0f) / wg_reduce_sum(sum_exp);
|
||||
|
||||
for (uint i = tid; i < p.KX; i += BLOCK_SIZE) {
|
||||
const FLOAT_TYPE sm = exp(FLOAT_TYPE(data_x[off + i]) - max_logit) * inv_sum;
|
||||
data_d[off + i] = D_TYPE((sm - FLOAT_TYPE(data_y[off + i])) * d_by_nrows);
|
||||
}
|
||||
}
|
||||
@@ -1029,6 +1029,8 @@ void process_shaders() {
|
||||
|
||||
string_to_spv("argmax_f32", "argmax.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"D_TYPE", "int"}}));
|
||||
string_to_spv("sum_rows_f32", "sum_rows.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"D_TYPE", "float"}}));
|
||||
string_to_spv("cross_entropy_loss_f32", "cross_entropy_loss.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"B_TYPE", "float"}, {"D_TYPE", "float"}}));
|
||||
string_to_spv("cross_entropy_loss_back_f32", "cross_entropy_loss_back.comp", merge_maps(base_dict, {{"A_TYPE", "float"}, {"B_TYPE", "float"}, {"D_TYPE", "float"}}));
|
||||
string_to_spv("fwht_f32", "fwht.comp", {});
|
||||
string_to_spv("fwht_shmem_f32", "fwht.comp", {{"FWHT_SHMEM", "1"}});
|
||||
string_to_spv("count_equal_i32", "count_equal.comp", merge_maps(base_dict, {{"A_TYPE", "int"}, {"B_TYPE", "int"}, {"D_TYPE", "int"}}));
|
||||
|
||||
+2
-2
@@ -43,10 +43,10 @@
|
||||
#define LLAMA_FILE_MAGIC_GGSQ 0x67677371u // 'ggsq'
|
||||
|
||||
#define LLAMA_SESSION_MAGIC LLAMA_FILE_MAGIC_GGSN
|
||||
#define LLAMA_SESSION_VERSION 9
|
||||
#define LLAMA_SESSION_VERSION 10
|
||||
|
||||
#define LLAMA_STATE_SEQ_MAGIC LLAMA_FILE_MAGIC_GGSQ
|
||||
#define LLAMA_STATE_SEQ_VERSION 2
|
||||
#define LLAMA_STATE_SEQ_VERSION 3
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
||||
+92
-11
@@ -12,6 +12,7 @@
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
|
||||
static bool ggml_is_power_of_2(int n) {
|
||||
return (n & (n - 1)) == 0;
|
||||
@@ -1128,11 +1129,18 @@ void llama_kv_cache::apply_ubatch(const slot_info & sinfo, const llama_ubatch &
|
||||
|
||||
cells.pos_set(idx, ubatch.pos[i]);
|
||||
|
||||
if (ubatch.is_pos_2d()) {
|
||||
llama_kv_cell_ext ext {
|
||||
/*.x =*/ ubatch.pos[i + ubatch.n_tokens*2],
|
||||
/*.y =*/ ubatch.pos[i + ubatch.n_tokens],
|
||||
};
|
||||
if (ubatch.is_pos_2d() || ubatch.token) {
|
||||
llama_kv_cell_ext ext;
|
||||
|
||||
if (ubatch.is_pos_2d()) {
|
||||
ext.x = ubatch.pos[i + ubatch.n_tokens*2];
|
||||
ext.y = ubatch.pos[i + ubatch.n_tokens];
|
||||
}
|
||||
|
||||
if (ubatch.token) {
|
||||
ext.tok = ubatch.token[i];
|
||||
}
|
||||
|
||||
cells.ext_set(idx, ext);
|
||||
}
|
||||
|
||||
@@ -1805,6 +1813,69 @@ void llama_kv_cache::set_input_v_rot(ggml_tensor * dst) const {
|
||||
memcpy(dst->data, attn_rot_hadamard.at(n_rot).data(), ggml_nbytes(dst));
|
||||
}
|
||||
|
||||
bool llama_kv_cache::has_cell_ext() const {
|
||||
return hparams.n_pos_per_embd() > 1;
|
||||
}
|
||||
|
||||
void llama_kv_cache::get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res) const {
|
||||
const uint32_t n_tokens = ubatch.n_tokens;
|
||||
|
||||
res.clear();
|
||||
res.resize(n_tokens*n, LLAMA_TOKEN_NULL);
|
||||
|
||||
if (n == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note: apply_ubatch() has already stored the current ubatch
|
||||
// the window below thus covers tokens of this very ubatch as well, which is what we want
|
||||
llama_pos p_min = std::numeric_limits<llama_pos>::max();
|
||||
llama_pos p_max = std::numeric_limits<llama_pos>::min();
|
||||
|
||||
std::bitset<LLAMA_MAX_SEQ> seqs;
|
||||
|
||||
for (uint32_t i = 0; i < n_tokens; ++i) {
|
||||
p_min = std::min(p_min, ubatch.pos[i]);
|
||||
p_max = std::max(p_max, ubatch.pos[i]);
|
||||
}
|
||||
|
||||
for (uint32_t s = 0; s < ubatch.n_seqs_unq; ++s) {
|
||||
seqs.set(ubatch.seq_id_unq[s]);
|
||||
}
|
||||
|
||||
// (seq_id, pos) -> token, for every cell that could be a predecessor of a ubatch token
|
||||
std::unordered_map<uint64_t, llama_token> hist;
|
||||
|
||||
const auto key = [](llama_seq_id seq_id, llama_pos pos) {
|
||||
return ((uint64_t) seq_id << 32) | (uint32_t) pos;
|
||||
};
|
||||
|
||||
for (uint32_t s = 0; s < n_stream; ++s) {
|
||||
v_cells[s].for_each_token_in(seqs, p_min - (llama_pos) n, p_max,
|
||||
[&](llama_seq_id seq_id, llama_pos pos, llama_token tok) {
|
||||
hist[key(seq_id, pos)] = tok;
|
||||
});
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < n_tokens; ++i) {
|
||||
// TODO: a token that belongs to more than one sequence has an ambiguous history.
|
||||
// the n-gram architectures have to reject such batches
|
||||
const llama_seq_id seq_id = ubatch.seq_id[i][0];
|
||||
|
||||
for (uint32_t j = 0; j < n; ++j) {
|
||||
const llama_pos p = ubatch.pos[i] - (llama_pos) (n - j);
|
||||
if (p < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto it = hist.find(key(seq_id, p));
|
||||
if (it != hist.end()) {
|
||||
res[i*n + j] = it->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t llama_kv_cache::total_size() const {
|
||||
size_t size = 0;
|
||||
|
||||
@@ -2106,7 +2177,7 @@ void llama_kv_cache::state_write_meta(llama_io_write_i & io, const cell_ranges_t
|
||||
io.write(&pos, sizeof(pos));
|
||||
io.write(&n_seq_id, sizeof(n_seq_id));
|
||||
|
||||
if (hparams.n_pos_per_embd() > 1) {
|
||||
if (has_cell_ext()) {
|
||||
const llama_kv_cell_ext ext = cells.ext_get(i);
|
||||
io.write(&ext, sizeof(ext));
|
||||
}
|
||||
@@ -2243,12 +2314,17 @@ bool llama_kv_cache::state_read_meta(llama_io_read_i & io, uint32_t strm, uint32
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hparams.n_pos_per_embd() > 1) {
|
||||
if (has_cell_ext()) {
|
||||
llama_kv_cell_ext ext;
|
||||
io.read(&ext, sizeof(ext));
|
||||
|
||||
ubatch.pos[i + ubatch.n_tokens] = ext.y;
|
||||
ubatch.pos[i + ubatch.n_tokens*2] = ext.x;
|
||||
if (hparams.n_pos_per_embd() > 1) {
|
||||
ubatch.pos[i + ubatch.n_tokens] = ext.y;
|
||||
ubatch.pos[i + ubatch.n_tokens*2] = ext.x;
|
||||
}
|
||||
|
||||
// apply_ubatch() below restores ext.tok from the ubatch tokens
|
||||
ubatch.token[i] = ext.tok;
|
||||
}
|
||||
|
||||
// read the sequence id, but directly discard it - we will use dest_seq_id instead
|
||||
@@ -2268,7 +2344,8 @@ bool llama_kv_cache::state_read_meta(llama_io_read_i & io, uint32_t strm, uint32
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: we cannot yet restore llama_kv_cell_ext as the apply_ubatch() does not support it yet
|
||||
// note: apply_ubatch() rebuilds llama_kv_cell_ext from the ubatch
|
||||
// only ext.tok and the M-RoPE 2D position round-trip through it
|
||||
// see: https://github.com/ggml-org/llama.cpp/pull/16825#issuecomment-3460868350
|
||||
apply_ubatch(sinfo, ubatch);
|
||||
|
||||
@@ -2301,7 +2378,7 @@ bool llama_kv_cache::state_read_meta(llama_io_read_i & io, uint32_t strm, uint32
|
||||
|
||||
cells.pos_set(i, pos);
|
||||
|
||||
if (hparams.n_pos_per_embd() > 1) {
|
||||
if (has_cell_ext()) {
|
||||
llama_kv_cell_ext ext;
|
||||
io.read(&ext, sizeof(ext));
|
||||
cells.ext_set(i, ext);
|
||||
@@ -2652,3 +2729,7 @@ void llama_kv_cache_context::set_input_k_rot(ggml_tensor * dst) const {
|
||||
void llama_kv_cache_context::set_input_v_rot(ggml_tensor * dst) const {
|
||||
kv->set_input_v_rot(dst);
|
||||
}
|
||||
|
||||
void llama_kv_cache_context::get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res) const {
|
||||
kv->get_prev_tokens(ubatch, n, res);
|
||||
}
|
||||
|
||||
@@ -219,6 +219,14 @@ public:
|
||||
void set_input_k_rot(ggml_tensor * dst) const;
|
||||
void set_input_v_rot(ggml_tensor * dst) const;
|
||||
|
||||
// true if llama_kv_cell_ext holds information that has to survive a state save/restore
|
||||
bool has_cell_ext() const;
|
||||
|
||||
// for every token of the ubatch, the ids of the n tokens that precede it in its sequence
|
||||
// entries with no matching cell are set to LLAMA_TOKEN_NULL
|
||||
// note: used by n-gram input embeddings
|
||||
void get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res) const;
|
||||
|
||||
private:
|
||||
const llama_model & model;
|
||||
const llama_hparams & hparams;
|
||||
@@ -401,6 +409,9 @@ public:
|
||||
void set_input_k_rot(ggml_tensor * dst) const;
|
||||
void set_input_v_rot(ggml_tensor * dst) const;
|
||||
|
||||
// see llama_kv_cache::get_prev_tokens()
|
||||
void get_prev_tokens(const llama_ubatch & ubatch, uint32_t n, std::vector<llama_token> & res) const;
|
||||
|
||||
private:
|
||||
llama_memory_status status;
|
||||
|
||||
|
||||
+28
-1
@@ -15,6 +15,10 @@ struct llama_kv_cell_ext {
|
||||
llama_pos x = 0;
|
||||
llama_pos y = 0;
|
||||
|
||||
// when tok = LLAMA_TOKEN_NULL when the cell is produced by embedding input (i.e. multimodal)
|
||||
// use case: n-gram embeddings hash
|
||||
llama_token tok = LLAMA_TOKEN_NULL;
|
||||
|
||||
// return true if the current 2D spatial position is greater than other
|
||||
bool is_2d_gt(llama_pos ox, llama_pos oy) const {
|
||||
return (y > oy) || (y == oy && x > ox);
|
||||
@@ -23,7 +27,7 @@ struct llama_kv_cell_ext {
|
||||
void reset() {
|
||||
static_assert(std::is_trivially_copyable_v<llama_kv_cell_ext>);
|
||||
|
||||
memset(this, 0, sizeof(*this));
|
||||
*this = llama_kv_cell_ext{};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -305,6 +309,29 @@ public:
|
||||
return seq[i].test(seq_id);
|
||||
}
|
||||
|
||||
// gather the token ids of the cells in `seqs` with position in [p0, p1)
|
||||
// the callback receives (seq_id, pos, token) for every such (cell, seq) pair
|
||||
// note: used by n-gram input embeddings to recover the tokens preceding a ubatch
|
||||
template<typename F>
|
||||
void for_each_token_in(const std::bitset<LLAMA_MAX_SEQ> & seqs, llama_pos p0, llama_pos p1, F && f) const {
|
||||
for (const auto & i : used) {
|
||||
if (pos[i] < p0 || pos[i] >= p1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto m = seq[i] & seqs;
|
||||
if (m.none()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (llama_seq_id s = 0; s < LLAMA_MAX_SEQ; ++s) {
|
||||
if (m.test(s)) {
|
||||
f(s, pos[i], ext[i].tok);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// note: call only if the cell is not empty and the seq_id is not in the cell
|
||||
void seq_add(uint32_t i, llama_seq_id seq_id) {
|
||||
assert(i < pos.size());
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
ChatFormInputFileInputInvisible,
|
||||
ChatFormMcpResourcesList,
|
||||
ChatFormPickers,
|
||||
DialogMcpResourcesBrowser
|
||||
DialogMcpResourcesBrowser,
|
||||
DialogMcpServers
|
||||
} from '$lib/components/app';
|
||||
import {
|
||||
CLIPBOARD_CONTENT_QUOTE_PREFIX,
|
||||
@@ -183,6 +184,9 @@
|
||||
let isResourceDialogOpen = $state(false);
|
||||
let preSelectedResourceUri = $state<string | undefined>(undefined);
|
||||
|
||||
// MCP Servers Dialog State
|
||||
let isMcpServersDialogOpen = $state(false);
|
||||
|
||||
let currentConfig = $derived(settingsStore.config);
|
||||
|
||||
let pasteLongTextToFileLength = $derived.by(() => {
|
||||
@@ -618,6 +622,7 @@
|
||||
onFileUpload={handleFileUpload}
|
||||
onMcpPromptClick={showMcpPromptButton ? () => pickers.openPromptPicker() : undefined}
|
||||
onMcpResourcesClick={() => (isResourceDialogOpen = true)}
|
||||
onMcpSettingsClick={() => (isMcpServersDialogOpen = true)}
|
||||
onMicClick={handleMicClick}
|
||||
{onStop}
|
||||
onSystemPromptClick={() => onSystemPromptClick?.({ files: uploadedFiles, message: value })}
|
||||
@@ -656,3 +661,5 @@
|
||||
}}
|
||||
preSelectedUri={preSelectedResourceUri}
|
||||
/>
|
||||
|
||||
<DialogMcpServers bind:open={isMcpServersDialogOpen} />
|
||||
|
||||
+9
-39
@@ -1,10 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { File, FolderOpen, MessageSquare, Plus, Zap } from '@lucide/svelte';
|
||||
import {
|
||||
ChatFormActionAddMcpServersSubmenu,
|
||||
ChatFormActionAddReasoningSubmenu,
|
||||
ChatFormActionAddToolsSubmenu
|
||||
} from '$lib/components/app';
|
||||
import { File, MessageSquare, Plus } from '@lucide/svelte';
|
||||
import { ChatFormActionAddToolsSubmenu, McpLogo } from '$lib/components/app';
|
||||
import { buttonVariants } from '$lib/components/ui/button';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import * as Tooltip from '$lib/components/ui/tooltip';
|
||||
@@ -31,11 +27,6 @@
|
||||
// must not restore focus to the trigger on close
|
||||
let suppressCloseAutoFocus = false;
|
||||
|
||||
function handleMcpSettingsClick() {
|
||||
dropdownOpen = false;
|
||||
chatFormActions.onMcpSettingsClick?.();
|
||||
}
|
||||
|
||||
const attachmentMenu = useAttachmentMenu(
|
||||
() => ({
|
||||
hasAudioModality: chatFormActions.hasAudioModality,
|
||||
@@ -93,10 +84,6 @@
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ChatFormActionAddReasoningSubmenu />
|
||||
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
<File class={ICON_CLASS_DEFAULT} />
|
||||
@@ -156,31 +143,14 @@
|
||||
|
||||
<ChatFormActionAddToolsSubmenu />
|
||||
|
||||
<ChatFormActionAddMcpServersSubmenu onMcpSettingsClick={handleMcpSettingsClick} />
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={chatFormActions.onMcpSettingsClick}
|
||||
>
|
||||
<McpLogo class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
{#if chatFormActions.hasMcpPromptsSupport}
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={chatFormActions.onMcpPromptClick}
|
||||
>
|
||||
<Zap class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>MCP Prompt</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
{#if chatFormActions.hasMcpResourcesSupport}
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={chatFormActions.onMcpResourcesClick}
|
||||
>
|
||||
<FolderOpen class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>MCP Resources</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
<span>MCP Servers</span>
|
||||
</DropdownMenu.Item>
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Root>
|
||||
</div>
|
||||
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Plus, Settings } from '@lucide/svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { DropdownMenuSearchable, McpLogo, McpServerIdentity } from '$lib/components/app';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { Switch } from '$lib/components/ui/switch';
|
||||
import { ICON_CLASS_DEFAULT, ROUTES } from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { conversationsStore, mcpStore } from '$lib/stores';
|
||||
import type { MCPServerSettingsEntry } from '$lib/types';
|
||||
|
||||
interface Props {
|
||||
onMcpSettingsClick?: () => void;
|
||||
}
|
||||
|
||||
let { onMcpSettingsClick }: Props = $props();
|
||||
|
||||
let mcpSearchQuery = $state('');
|
||||
// Every configured server is listed; `enabled` is an on/off state,
|
||||
// not a visibility filter, so a disabled server stays toggleable.
|
||||
let mcpServers = $derived(mcpStore.getServers());
|
||||
let hasMcpServers = $derived(mcpServers.length > 0);
|
||||
let filteredMcpServers = $derived.by(() => {
|
||||
const query = mcpSearchQuery.toLowerCase().trim();
|
||||
|
||||
if (!query) return mcpServers;
|
||||
|
||||
return mcpServers.filter((s) => {
|
||||
const name = getServerLabel(s).toLowerCase();
|
||||
const url = s.url.toLowerCase();
|
||||
|
||||
return name.includes(query) || url.includes(query);
|
||||
});
|
||||
});
|
||||
|
||||
function getServerLabel(server: MCPServerSettingsEntry): string {
|
||||
return mcpStore.getServerLabel(server);
|
||||
}
|
||||
|
||||
function isServerEnabledForChat(serverId: string): boolean {
|
||||
return conversationsStore.preferences.isMcpServerEnabledForChat(serverId);
|
||||
}
|
||||
|
||||
async function toggleServerForChat(serverId: string) {
|
||||
await conversationsStore.preferences.toggleMcpServerForChat(serverId);
|
||||
}
|
||||
|
||||
function handleMcpSubMenuOpen(open: boolean) {
|
||||
if (open) {
|
||||
mcpSearchQuery = '';
|
||||
mcpStore.runHealthChecksForServers(mcpServers);
|
||||
}
|
||||
}
|
||||
|
||||
function handleMcpSettingsClick() {
|
||||
onMcpSettingsClick?.();
|
||||
|
||||
goto(`${hasMcpServers ? '' : '?add'}${ROUTES.MCP_SERVERS}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Sub onOpenChange={handleMcpSubMenuOpen}>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
<McpLogo class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>MCP Servers</span>
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<DropdownMenu.SubContent class="w-72 pt-0">
|
||||
{#if hasMcpServers}
|
||||
<DropdownMenuSearchable
|
||||
bind:searchValue={mcpSearchQuery}
|
||||
emptyMessage="No servers found"
|
||||
isEmpty={filteredMcpServers.length === 0}
|
||||
placeholder="Search servers..."
|
||||
>
|
||||
<div class="max-h-64 overflow-y-auto">
|
||||
{#each filteredMcpServers as server (server.id)}
|
||||
{@const healthState = mcpStore.getHealthCheckState(server.id)}
|
||||
{@const hasError = healthState.status === HealthCheckStatus.ERROR}
|
||||
{@const isEnabledForChat = isServerEnabledForChat(server.id)}
|
||||
{@const displayName = getServerLabel(server)}
|
||||
{@const faviconUrl = mcpStore.getServerFavicon(server.id)}
|
||||
|
||||
<button
|
||||
class="flex w-full items-center justify-between gap-2 rounded-sm px-2 py-2 text-left transition-colors hover:bg-accent disabled:cursor-not-allowed disabled:opacity-50"
|
||||
disabled={hasError}
|
||||
onclick={() => !hasError && toggleServerForChat(server.id)}
|
||||
type="button"
|
||||
>
|
||||
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||
<div class="min-w-0 flex-1">
|
||||
<McpServerIdentity
|
||||
{displayName}
|
||||
{faviconUrl}
|
||||
iconClass={ICON_CLASS_DEFAULT}
|
||||
iconRounded="rounded-sm"
|
||||
nameClass="text-sm"
|
||||
showVersion={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if hasError}
|
||||
<span
|
||||
class="shrink-0 rounded bg-destructive/15 px-1.5 py-0.5 text-xs text-destructive"
|
||||
>
|
||||
Error
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Switch
|
||||
checked={isEnabledForChat}
|
||||
disabled={hasError}
|
||||
onCheckedChange={() => toggleServerForChat(server.id)}
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
/>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#snippet footer()}
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={handleMcpSettingsClick}
|
||||
>
|
||||
<Settings class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Manage MCP Servers</span>
|
||||
</DropdownMenu.Item>
|
||||
{/snippet}
|
||||
</DropdownMenuSearchable>
|
||||
{:else}
|
||||
<div class="px-2 py-3 text-center text-sm text-muted-foreground">
|
||||
No MCP servers configured
|
||||
</div>
|
||||
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={handleMcpSettingsClick}
|
||||
>
|
||||
<Plus class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Add MCP Servers</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
</DropdownMenu.Root>
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
<script lang="ts">
|
||||
import { FolderOpen, Server, Zap } from '@lucide/svelte';
|
||||
import { McpLogo } from '$lib/components/app';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { getChatFormActionsContext } from '$lib/contexts';
|
||||
|
||||
const chatFormActions = getChatFormActionsContext();
|
||||
|
||||
function handleServersClick() {
|
||||
chatFormActions.onMcpSettingsClick?.();
|
||||
}
|
||||
</script>
|
||||
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger class="flex cursor-pointer items-center gap-2">
|
||||
<McpLogo class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>MCP</span>
|
||||
</DropdownMenu.SubTrigger>
|
||||
|
||||
<DropdownMenu.SubContent class="w-48">
|
||||
<DropdownMenu.Item class="flex cursor-pointer items-center gap-2" onclick={handleServersClick}>
|
||||
<Server class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Servers</span>
|
||||
</DropdownMenu.Item>
|
||||
|
||||
{#if chatFormActions.hasMcpPromptsSupport}
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={chatFormActions.onMcpPromptClick}
|
||||
>
|
||||
<Zap class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Prompts</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
|
||||
{#if chatFormActions.hasMcpResourcesSupport}
|
||||
<DropdownMenu.Item
|
||||
class="flex cursor-pointer items-center gap-2"
|
||||
onclick={chatFormActions.onMcpResourcesClick}
|
||||
>
|
||||
<FolderOpen class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span>Resources</span>
|
||||
</DropdownMenu.Item>
|
||||
{/if}
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Sub>
|
||||
+4
-3
@@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { SkipForward, Square } from '@lucide/svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import {
|
||||
ChatFormActionModels,
|
||||
@@ -10,7 +9,7 @@
|
||||
ChatFormContextGauge
|
||||
} from '$lib/components/app';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { ICON_CLASS_DEFAULT, ROUTES } from '$lib/constants';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import { setChatFormActionsContext } from '$lib/contexts';
|
||||
import { FileTypeCategory, MessageRole } from '$lib/enums';
|
||||
import { ChatService } from '$lib/services';
|
||||
@@ -34,6 +33,7 @@
|
||||
onSystemPromptClick?: () => void;
|
||||
onMcpPromptClick?: () => void;
|
||||
onMcpResourcesClick?: () => void;
|
||||
onMcpSettingsClick?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -47,6 +47,7 @@
|
||||
onFileUpload,
|
||||
onMcpPromptClick,
|
||||
onMcpResourcesClick,
|
||||
onMcpSettingsClick,
|
||||
onMicClick,
|
||||
onStop,
|
||||
onSystemPromptClick,
|
||||
@@ -163,7 +164,7 @@
|
||||
return onMcpResourcesClick;
|
||||
},
|
||||
get onMcpSettingsClick() {
|
||||
return () => goto(ROUTES.MCP_SERVERS);
|
||||
return onMcpSettingsClick;
|
||||
},
|
||||
get onSystemPromptClick() {
|
||||
return onSystemPromptClick;
|
||||
|
||||
@@ -221,25 +221,17 @@ export { default as ChatFormActionModels } from './ChatForm/ChatFormActions/Chat
|
||||
export { default as ChatFormActionAddToolsSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddToolsSubmenu.svelte';
|
||||
|
||||
/**
|
||||
* Dropdown submenu for managing MCP servers in the chat form.
|
||||
* Dropdown submenu for MCP prompts and resources in the chat form.
|
||||
*
|
||||
* Displays a searchable list of enabled MCP servers with toggle switches
|
||||
* to enable/disable each server for chat. Shows server favicon, health status,
|
||||
* and a "Manage MCP Servers" settings link.
|
||||
*
|
||||
* Features:
|
||||
* - Search/filter servers by name or URL
|
||||
* - Per-server toggle to enable/disable for chat
|
||||
* - Health check indicator (shows "Error" badge for failed servers)
|
||||
* - Server favicon display
|
||||
* - Settings link to manage MCP server configuration
|
||||
* Shows an "MCP" sub-menu item with entries for MCP Prompts and MCP
|
||||
* Resources. Only visible when the server supports them.
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <ChatFormActionAddMcpServersSubmenu onMcpSettingsClick={handleMcpSettingsClick} />
|
||||
* <ChatFormActionAddMcpSubmenu />
|
||||
* ```
|
||||
*/
|
||||
export { default as ChatFormActionAddMcpServersSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddMcpServersSubmenu.svelte';
|
||||
export { default as ChatFormActionAddMcpSubmenu } from './ChatForm/ChatFormActions/ChatFormActionAdd/ChatFormActionAddMcpSubmenu.svelte';
|
||||
|
||||
/**
|
||||
* Dropdown submenu for selecting reasoning effort level.
|
||||
|
||||
@@ -253,7 +253,7 @@
|
||||
</script>
|
||||
|
||||
<Dialog.Root onOpenChange={handleOpenChange} {open}>
|
||||
<Dialog.Content class="max-h-[80vh] !max-w-4xl overflow-hidden p-0">
|
||||
<Dialog.Content class="max-h-[80vh] md:max-w-4xl! w-full! overflow-hidden p-0">
|
||||
<Dialog.Header class="border-b border-border/30 px-6 py-4">
|
||||
<Dialog.Title class="flex items-center gap-2">
|
||||
<FolderOpen class="h-5 w-5" />
|
||||
|
||||
@@ -246,7 +246,7 @@
|
||||
</script>
|
||||
|
||||
<Dialog.Root onOpenChange={handleOpenChange} {open}>
|
||||
<Dialog.Content class="sm:max-w-2xl">
|
||||
<Dialog.Content class="max-w-2xl!">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title class="select-none">Add New MCP Server</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { McpLogo } from '$lib/components/app';
|
||||
import { SettingsMcpServers } from '$lib/components/app/settings';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
let { onOpenChange, open = $bindable(false) }: Props = $props();
|
||||
|
||||
function handleOpenChange(value: boolean) {
|
||||
open = value;
|
||||
onOpenChange?.(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root onOpenChange={handleOpenChange} {open}>
|
||||
<Dialog.Content
|
||||
class="md:h-[calc(100vh-4rem)]! md:max-h-240! md:w-[calc(100vw-4rem)]! md:max-w-360! flex flex-col"
|
||||
>
|
||||
<Dialog.Header>
|
||||
<Dialog.Title class="flex items-center gap-2">
|
||||
<McpLogo class="h-5 w-5" />
|
||||
|
||||
<span>MCP Servers</span>
|
||||
</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<SettingsMcpServers class="mt-4" />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -14,6 +14,7 @@
|
||||
<Dialog.Root bind:open {onOpenChange}>
|
||||
<Dialog.Content
|
||||
class="z-999999 grid max-h-full max-w-full! grid-rows-[1fr_auto] overflow-hidden p-0 md:h-[90vh] md:max-w-[90vw]!"
|
||||
showCloseButton
|
||||
>
|
||||
<MermaidPreview {svgHtml} />
|
||||
</Dialog.Content>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<script lang="ts">
|
||||
import { Settings } from '@lucide/svelte';
|
||||
import { SettingsChat } from '$lib/components/app/settings';
|
||||
import * as Dialog from '$lib/components/ui/dialog';
|
||||
|
||||
interface Props {
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
initialSection?: string;
|
||||
}
|
||||
|
||||
let { initialSection, onOpenChange, open = $bindable(false) }: Props = $props();
|
||||
|
||||
function handleOpenChange(value: boolean) {
|
||||
open = value;
|
||||
onOpenChange?.(value);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Dialog.Root onOpenChange={handleOpenChange} {open}>
|
||||
<Dialog.Content
|
||||
class="md:h-[calc(100vh-4rem)]! md:max-h-240! md:w-[calc(100vw-4rem)]! md:max-w-6xl! flex flex-col p-0 md:p-6 gap-0"
|
||||
>
|
||||
<Dialog.Header class="md:p-0 p-4">
|
||||
<Dialog.Title class="flex items-center gap-2">
|
||||
<Settings class="h-5 w-5" />
|
||||
|
||||
<span>Settings</span>
|
||||
</Dialog.Title>
|
||||
</Dialog.Header>
|
||||
|
||||
<SettingsChat {initialSection} onClose={() => (open = false)} onSectionChange={() => {}} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Root>
|
||||
@@ -18,6 +18,23 @@
|
||||
*/
|
||||
export { default as DialogMcpServerAddNew } from './DialogMcpServerAddNew.svelte';
|
||||
|
||||
/**
|
||||
* **DialogMcpServers** - MCP servers dialog shown from the chat form
|
||||
*
|
||||
* Shows the same MCP server list as the `/mcp-servers` route inside a modal
|
||||
* dialog.
|
||||
*/
|
||||
export { default as DialogMcpServers } from './DialogMcpServers.svelte';
|
||||
|
||||
/**
|
||||
* **DialogSettingsChat** - Chat settings shown in a modal dialog
|
||||
*
|
||||
* Wraps the full SettingsChat layout (sidebar, mobile header, fields, footer)
|
||||
* inside a ShadCN Dialog instead of a dedicated route. Section switching is
|
||||
* handled in-app via `onSectionChange` rather than URL navigation.
|
||||
*/
|
||||
export { default as DialogSettingsChat } from './DialogSettingsChat.svelte';
|
||||
|
||||
/**
|
||||
* **DialogExportSettings** - Settings export dialog with sensitive data warning
|
||||
*
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
<script lang="ts">
|
||||
import { Database, FileText, ListChecks, MessageSquare, Sparkles, Wrench } from '@lucide/svelte';
|
||||
import {
|
||||
Database,
|
||||
ExternalLink,
|
||||
FileText,
|
||||
ListChecks,
|
||||
MessageSquare,
|
||||
Sparkles,
|
||||
Wrench
|
||||
} from '@lucide/svelte';
|
||||
import { Badge } from '$lib/components/ui/badge';
|
||||
import type { MCPCapabilitiesInfo } from '$lib/types';
|
||||
|
||||
interface Props {
|
||||
capabilities?: MCPCapabilitiesInfo;
|
||||
onBrowseResources?: () => void;
|
||||
}
|
||||
|
||||
let { capabilities }: Props = $props();
|
||||
let { capabilities, onBrowseResources }: Props = $props();
|
||||
</script>
|
||||
|
||||
{#if capabilities}
|
||||
@@ -20,10 +29,24 @@
|
||||
{/if}
|
||||
|
||||
{#if capabilities.server.resources}
|
||||
<Badge class="h-5 gap-1 bg-blue-50 px-1.5 text-[10px] dark:bg-blue-950" variant="outline">
|
||||
<Badge
|
||||
class="h-5 cursor-pointer gap-1 bg-blue-50 px-1.5 text-[10px] transition-colors hover:bg-blue-100 dark:bg-blue-950 dark:hover:bg-blue-900"
|
||||
onclick={onBrowseResources}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
onBrowseResources?.();
|
||||
}
|
||||
}}
|
||||
role="button"
|
||||
tabindex={0}
|
||||
variant="outline"
|
||||
>
|
||||
<Database class="h-3 w-3 text-blue-600 dark:text-blue-400" />
|
||||
|
||||
Resources
|
||||
|
||||
<ExternalLink class="h-3 w-3 text-blue-600 dark:text-blue-400" />
|
||||
</Badge>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -22,9 +22,10 @@
|
||||
onToggle: (enabled: boolean) => void;
|
||||
onUpdate: (updates: Partial<MCPServerSettingsEntry>) => void;
|
||||
onDelete: () => void;
|
||||
onBrowseResources?: () => void;
|
||||
}
|
||||
|
||||
let { enabled, onDelete, onToggle, onUpdate, server }: Props = $props();
|
||||
let { enabled, onBrowseResources, onDelete, onToggle, onUpdate, server }: Props = $props();
|
||||
|
||||
let healthState = $derived<HealthCheckState>(mcpStore.getHealthCheckState(server.id));
|
||||
let displayName = $derived(mcpStore.getServerLabel(server));
|
||||
@@ -125,6 +126,7 @@
|
||||
{displayName}
|
||||
enabled={enabled ?? server.enabled}
|
||||
{faviconUrl}
|
||||
{onBrowseResources}
|
||||
{onToggle}
|
||||
{serverInfo}
|
||||
{transportType}
|
||||
@@ -191,12 +193,14 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<McpServerCardActions
|
||||
{isHealthChecking}
|
||||
onDelete={handleDeleteClick}
|
||||
onEdit={startEditing}
|
||||
onRefresh={handleHealthCheck}
|
||||
/>
|
||||
<div class="flex items-center gap-2">
|
||||
<McpServerCardActions
|
||||
{isHealthChecking}
|
||||
onDelete={handleDeleteClick}
|
||||
onEdit={startEditing}
|
||||
onRefresh={handleHealthCheck}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</Card.Root>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
enabled: boolean;
|
||||
disabled?: boolean;
|
||||
onToggle: (enabled: boolean) => void;
|
||||
onBrowseResources?: () => void;
|
||||
serverInfo?: MCPServerInfo;
|
||||
capabilities?: MCPCapabilitiesInfo;
|
||||
transportType?: MCPTransportType;
|
||||
@@ -23,6 +24,7 @@
|
||||
displayName,
|
||||
enabled,
|
||||
faviconUrl,
|
||||
onBrowseResources,
|
||||
onToggle,
|
||||
serverInfo,
|
||||
transportType
|
||||
@@ -57,7 +59,7 @@
|
||||
{/if}
|
||||
|
||||
{#if capabilities}
|
||||
<McpCapabilitiesBadges {capabilities} />
|
||||
<McpCapabilitiesBadges {capabilities} {onBrowseResources} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
+6
-1
@@ -5,6 +5,7 @@
|
||||
import {
|
||||
ActionIcon,
|
||||
DialogConversationRename,
|
||||
DialogSettingsChat,
|
||||
Logo,
|
||||
SidebarNavigationActions,
|
||||
SidebarNavigationConversationList
|
||||
@@ -91,6 +92,7 @@
|
||||
let selectedIds = new SvelteSet<string>();
|
||||
|
||||
let renameDialogOpen = $state(false);
|
||||
let settingsDialogOpen = $state(false);
|
||||
let renameTargetConversationId = $state<string | null>(null);
|
||||
let renameDraft = $state('');
|
||||
let renameOriginalTitle = $state('');
|
||||
@@ -308,7 +310,7 @@
|
||||
|
||||
<svelte:window bind:innerWidth onkeydown={handleKeydown} />
|
||||
|
||||
{#if innerWidth > 768 || (!page.url.hash.includes(ROUTES.SETTINGS) && !page.url.hash.includes(ROUTES.MCP_SERVERS) && !page.url.hash.includes(ROUTES.SEARCH))}
|
||||
{#if innerWidth > 768 || !page.url.hash.includes(ROUTES.SEARCH)}
|
||||
<aside
|
||||
class={[
|
||||
'fixed md:sticky top-2 left-2 md:left-0 md:ml-2 md:mt-2 pt-2 z-10 w-[calc(100dvw-1rem)]',
|
||||
@@ -400,6 +402,7 @@
|
||||
isSearchModeActive = false;
|
||||
searchQuery = '';
|
||||
}}
|
||||
onSettingsClick={() => (settingsDialogOpen = true)}
|
||||
/>
|
||||
|
||||
{#if uiStore.isSidebarExpanded || isOnMobile}
|
||||
@@ -447,6 +450,8 @@
|
||||
onConfirm={handleRenameConfirm}
|
||||
/>
|
||||
|
||||
<DialogSettingsChat bind:open={settingsDialogOpen} />
|
||||
|
||||
<style>
|
||||
aside {
|
||||
@media (max-width: 768px) {
|
||||
|
||||
+22
-16
@@ -26,6 +26,7 @@
|
||||
onSearchDeactivated?: () => void;
|
||||
onSearchClick?: () => void;
|
||||
onNewChat?: () => void;
|
||||
onSettingsClick?: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -35,6 +36,7 @@
|
||||
onNewChat,
|
||||
onSearchClick,
|
||||
onSearchDeactivated,
|
||||
onSettingsClick,
|
||||
searchQuery = $bindable('')
|
||||
}: Props = $props();
|
||||
|
||||
@@ -115,14 +117,16 @@
|
||||
onNewChat?.();
|
||||
void conversationsStore.openNewChat();
|
||||
}
|
||||
: item.route
|
||||
? () => {
|
||||
onNewChat?.();
|
||||
goto(item.route!);
|
||||
}
|
||||
: isSearchOnMobile
|
||||
? undefined
|
||||
: onSearchClick}
|
||||
: item.action === SidebarAction.SETTINGS
|
||||
? () => onSettingsClick?.()
|
||||
: item.route
|
||||
? () => {
|
||||
onNewChat?.();
|
||||
goto(item.route!);
|
||||
}
|
||||
: isSearchOnMobile
|
||||
? undefined
|
||||
: onSearchClick}
|
||||
{@const itemTransition = {
|
||||
delay: !initialized ? i * ICON_STRIP_TRANSITION_DELAY_MULTIPLIER : 0,
|
||||
duration: ICON_STRIP_TRANSITION_DURATION,
|
||||
@@ -169,14 +173,16 @@
|
||||
onNewChat?.();
|
||||
void conversationsStore.openNewChat();
|
||||
}
|
||||
: item.route
|
||||
? () => {
|
||||
onNewChat?.();
|
||||
goto(item.route!);
|
||||
}
|
||||
: isSearchOnMobile
|
||||
? undefined
|
||||
: onSearchClick}
|
||||
: item.action === SidebarAction.SETTINGS
|
||||
? () => onSettingsClick?.()
|
||||
: item.route
|
||||
? () => {
|
||||
onNewChat?.();
|
||||
goto(item.route!);
|
||||
}
|
||||
: isSearchOnMobile
|
||||
? undefined
|
||||
: onSearchClick}
|
||||
{@const itemTransition = {
|
||||
delay: !initialized ? i * ICON_STRIP_TRANSITION_DELAY_MULTIPLIER : 0,
|
||||
duration: ICON_STRIP_TRANSITION_DURATION,
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { RefreshCw } from '@lucide/svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import {
|
||||
SettingsChatDesktopSidebar,
|
||||
SettingsChatFields,
|
||||
@@ -18,21 +16,29 @@
|
||||
SETTINGS_SECTION_SLUGS
|
||||
} from '$lib/constants';
|
||||
import { ColorMode } from '$lib/enums/ui.enums';
|
||||
import { RouterService } from '$lib/services/router.service';
|
||||
import { modelsStore, serverStore, settingsReferrer, settingsStore } from '$lib/stores';
|
||||
import type { SettingsSection } from '$lib/types';
|
||||
import { modelsStore, serverStore, settingsStore } from '$lib/stores';
|
||||
import type { SettingsSection, SettingsSectionTitle } from '$lib/types';
|
||||
import { setMode } from 'mode-watcher';
|
||||
import { fade } from 'svelte/transition';
|
||||
interface Props {
|
||||
initialSection?: string;
|
||||
getSectionHref?: (section: SettingsSection) => string;
|
||||
onSectionChange?: (section: SettingsSectionTitle) => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
let { getSectionHref, initialSection }: Props = $props();
|
||||
let { initialSection, onClose, onSectionChange }: Props = $props();
|
||||
|
||||
let activeSlug = $derived(
|
||||
initialSection ?? (page.params as Record<string, string | undefined>).section ?? 'general'
|
||||
);
|
||||
let activeSlug = $derived(initialSection ?? 'general');
|
||||
|
||||
function handleSectionChange(section: SettingsSectionTitle) {
|
||||
const found = SETTINGS_CHAT_SECTIONS.find((s) => s.title === section);
|
||||
|
||||
if (found) {
|
||||
activeSlug = found.slug;
|
||||
}
|
||||
|
||||
onSectionChange?.(section);
|
||||
}
|
||||
|
||||
let currentSection = $derived(
|
||||
SETTINGS_CHAT_SECTIONS.find((section) => section.slug === activeSlug) ||
|
||||
@@ -115,7 +121,7 @@
|
||||
}
|
||||
|
||||
settingsStore.updateMultipleConfig(processedConfig);
|
||||
goto(settingsReferrer.url);
|
||||
onClose?.();
|
||||
}
|
||||
|
||||
export function reset() {
|
||||
@@ -123,32 +129,24 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div in:fade={{ duration: 150 }} class="mx-auto flex h-full w-full flex-col md:pl-8">
|
||||
<div class="flex flex-1 flex-col gap-4 md:flex-row">
|
||||
<div in:fade={{ duration: 150 }} class="mx-auto flex h-full w-full flex-col">
|
||||
<div class="flex flex-1 flex-col md:flex-row md:gap-4">
|
||||
<SettingsChatDesktopSidebar
|
||||
getHref={getSectionHref ??
|
||||
((section: SettingsSection) => RouterService.settings(section.slug))}
|
||||
isActive={(section: SettingsSection) => section.slug === activeSlug}
|
||||
onSectionChange={handleSectionChange}
|
||||
sections={SETTINGS_CHAT_SECTIONS}
|
||||
/>
|
||||
|
||||
<SettingsChatMobileHeader
|
||||
bind:this={mobileHeader}
|
||||
getHref={getSectionHref ??
|
||||
((section: SettingsSection) => RouterService.settings(section.slug))}
|
||||
isActive={(section: SettingsSection) => section.slug === activeSlug}
|
||||
onSectionChange={handleSectionChange}
|
||||
sections={SETTINGS_CHAT_SECTIONS}
|
||||
/>
|
||||
|
||||
<div class="mx-auto max-w-3xl flex-1">
|
||||
<div class="space-y-6 p-4 md:p-6 md:pt-28">
|
||||
<div class="mx-auto max-w-2xl px-4 flex-1 md:mt-4">
|
||||
<div class="space-y-6 pt-3">
|
||||
<div class="grid">
|
||||
<div class="mb-6 flex items-center gap-2 border-b border-border/30 pb-6 md:flex">
|
||||
<currentSection.icon class="h-5 w-5" />
|
||||
|
||||
<h3 class="text-lg font-semibold">{currentSection.title}</h3>
|
||||
</div>
|
||||
|
||||
{#if currentSection.slug === SETTINGS_SECTION_SLUGS.TOOLS}
|
||||
<SettingsChatToolsTab />
|
||||
{:else if currentSection.slug === SETTINGS_SECTION_SLUGS.IMPORT_EXPORT}
|
||||
|
||||
@@ -1,54 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { Settings } from '@lucide/svelte';
|
||||
import { ICON_CLASS_DEFAULT } from '$lib/constants';
|
||||
import type { SettingsSection, SettingsSectionTitle } from '$lib/types';
|
||||
|
||||
interface Props {
|
||||
sections: SettingsSection[];
|
||||
isActive: (section: SettingsSection) => boolean;
|
||||
getHref?: (section: SettingsSection) => string;
|
||||
onSectionChange?: (section: SettingsSectionTitle) => void;
|
||||
}
|
||||
|
||||
let { getHref, isActive, onSectionChange, sections }: Props = $props();
|
||||
let { isActive, onSectionChange, sections }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="sticky top-2 hidden w-64 flex-col self-start bg-background py-4 md:flex gap-6">
|
||||
<div class="flex items-center gap-2 py-2">
|
||||
<Settings class="h-5 w-5 md:h-6 md:w-6" />
|
||||
|
||||
<h1 class="text-xl font-semibold md:text-2xl">Settings</h1>
|
||||
</div>
|
||||
|
||||
<div class="sticky top-12 hidden w-64 flex-col self-start bg-background md:flex gap-6">
|
||||
<nav class="space-y-1">
|
||||
{#each sections as section (section.title)}
|
||||
{#if getHref}
|
||||
<a
|
||||
class="flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left text-sm no-underline transition-colors hover:bg-accent {isActive(
|
||||
section
|
||||
)
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground'}"
|
||||
href={getHref(section)}
|
||||
>
|
||||
<section.icon class={ICON_CLASS_DEFAULT} />
|
||||
<button
|
||||
class="flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-accent {isActive(
|
||||
section
|
||||
)
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground'}"
|
||||
onclick={() => onSectionChange?.(section.title)}
|
||||
>
|
||||
<section.icon class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span class="ml-2">{section.title}</span>
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
class="flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-accent {isActive(
|
||||
section
|
||||
)
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground'}"
|
||||
onclick={() => onSectionChange?.(section.title)}
|
||||
>
|
||||
<section.icon class={ICON_CLASS_DEFAULT} />
|
||||
|
||||
<span class="ml-2">{section.title}</span>
|
||||
</button>
|
||||
{/if}
|
||||
<span class="ml-2">{section.title}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { Settings } from '@lucide/svelte';
|
||||
import { ScrollCarousel } from '$lib/components/app';
|
||||
import { ICON_CLASS_DEFAULT, UI_DATA_ATTRS } from '$lib/constants';
|
||||
import { BooleanString } from '$lib/enums';
|
||||
@@ -10,11 +9,10 @@
|
||||
interface Props {
|
||||
sections: SettingsSection[];
|
||||
isActive: (section: SettingsSection) => boolean;
|
||||
getHref?: (section: SettingsSection) => string;
|
||||
onSectionChange?: (section: SettingsSectionTitle) => void;
|
||||
}
|
||||
|
||||
let { getHref, isActive, onSectionChange, sections }: Props = $props();
|
||||
let { isActive, onSectionChange, sections }: Props = $props();
|
||||
|
||||
const carousel = useScrollCarousel();
|
||||
|
||||
@@ -37,51 +35,26 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="sticky top-0 z-10 flex flex-col bg-background md:hidden">
|
||||
<div class="flex items-center gap-2 px-4 pt-4 pb-2 md:pt-6">
|
||||
<Settings class="h-5 w-5 md:h-6 md:w-6" />
|
||||
|
||||
<h1 class="text-xl font-semibold md:text-2xl">Settings</h1>
|
||||
</div>
|
||||
|
||||
<div class="border-b border-border/30 py-2">
|
||||
<div class="flex flex-col bg-background md:hidden sticky top-13 z-50">
|
||||
<div class="border-b border-border/30">
|
||||
<ScrollCarousel alwaysShowArrows {carousel} containerClass="py-2" innerClass="gap-2">
|
||||
{#each sections as section (section.title)}
|
||||
{#if getHref}
|
||||
<a
|
||||
class="flex cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-sm whitespace-nowrap no-underline transition-colors first:ml-4 last:mr-4 hover:bg-accent {isActive(
|
||||
section
|
||||
)
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground'}"
|
||||
{...{ [UI_DATA_ATTRS.ACTIVE]: isActive(section) }}
|
||||
href={getHref(section)}
|
||||
onclick={(e: MouseEvent) => {
|
||||
carousel.scrollToCenter(e.currentTarget as HTMLElement);
|
||||
}}
|
||||
>
|
||||
<section.icon class="{ICON_CLASS_DEFAULT} flex-shrink-0" />
|
||||
<button
|
||||
class="flex cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-sm whitespace-nowrap transition-colors first:ml-4 last:mr-4 hover:bg-accent {isActive(
|
||||
section
|
||||
)
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground'}"
|
||||
{...{ [UI_DATA_ATTRS.ACTIVE]: isActive(section) }}
|
||||
onclick={(e: MouseEvent) => {
|
||||
onSectionChange?.(section.title);
|
||||
carousel.scrollToCenter(e.currentTarget as HTMLElement);
|
||||
}}
|
||||
>
|
||||
<section.icon class="{ICON_CLASS_DEFAULT} flex-shrink-0" />
|
||||
|
||||
<span>{section.title}</span>
|
||||
</a>
|
||||
{:else}
|
||||
<button
|
||||
class="flex cursor-pointer items-center gap-2 rounded-lg px-3 py-2 text-sm whitespace-nowrap transition-colors first:ml-4 last:mr-4 hover:bg-accent {isActive(
|
||||
section
|
||||
)
|
||||
? 'bg-accent text-accent-foreground'
|
||||
: 'text-muted-foreground'}"
|
||||
{...{ [UI_DATA_ATTRS.ACTIVE]: isActive(section) }}
|
||||
onclick={(e: MouseEvent) => {
|
||||
onSectionChange?.(section.title);
|
||||
carousel.scrollToCenter(e.currentTarget as HTMLElement);
|
||||
}}
|
||||
>
|
||||
<section.icon class="{ICON_CLASS_DEFAULT} flex-shrink-0" />
|
||||
|
||||
<span>{section.title}</span>
|
||||
</button>
|
||||
{/if}
|
||||
<span>{section.title}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</ScrollCarousel>
|
||||
</div>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="sticky bottom-0 mx-auto mt-4 flex w-full justify-between p-6">
|
||||
<div class="sticky bottom-0 mx-auto mt-4 flex w-full justify-between pb-4 md:pb-0">
|
||||
<div class="flex gap-2">
|
||||
<Button onclick={handleResetClick} variant="outline">
|
||||
<RotateCcw class="h-3 w-3" />
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<script lang="ts">
|
||||
import McpLogo from '../mcp/McpLogo.svelte';
|
||||
import { Plus, X } from '@lucide/svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto, replaceState } from '$app/navigation';
|
||||
import { Plus } from '@lucide/svelte';
|
||||
import { replaceState } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { ActionIcon, McpServerCard, McpServerCardSkeleton } from '$lib/components/app';
|
||||
import { DialogMcpServerAddNew } from '$lib/components/app/dialogs';
|
||||
import { McpServerCard, McpServerCardSkeleton } from '$lib/components/app';
|
||||
import { DialogMcpResourcesBrowser, DialogMcpServerAddNew } from '$lib/components/app/dialogs';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import * as Empty from '$lib/components/ui/empty';
|
||||
import { ROUTES } from '$lib/constants';
|
||||
import { HealthCheckStatus } from '$lib/enums';
|
||||
import { conversationsStore, mcpStore, toolsStore } from '$lib/stores';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -23,26 +20,7 @@
|
||||
let servers = $derived(mcpStore.getServers());
|
||||
|
||||
let isAddingServer = $state(false);
|
||||
|
||||
let previousRouteId = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
const currentId = page.route.id;
|
||||
|
||||
return () => {
|
||||
previousRouteId = currentId;
|
||||
};
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
const prevIsMcpServers = previousRouteId === '/mcp-servers';
|
||||
|
||||
if (browser && window.history.length > 1 && !prevIsMcpServers) {
|
||||
history.back();
|
||||
} else {
|
||||
goto(ROUTES.START);
|
||||
}
|
||||
}
|
||||
let isResourcesDialogOpen = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
if (page.url.searchParams.has('add')) {
|
||||
@@ -71,25 +49,13 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div in:fade={{ duration: 150 }} class="flex min-h-[calc(100dvh-4rem)] flex-col">
|
||||
<div class="fixed top-4.5 right-4 z-50 md:hidden">
|
||||
<ActionIcon icon={X} onclick={handleClose} tooltip="Close" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="sticky top-0 z-10 mt-4 mb-2 flex items-start gap-4 md:p-4 p-0 px-4 md:justify-between md:px-8"
|
||||
>
|
||||
<div class="flex items-center gap-2">
|
||||
<McpLogo class="h-5 w-5 md:h-6 md:w-6" />
|
||||
|
||||
<h1 class="text-lg font-semibold md:text-2xl">MCP Servers</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div in:fade={{ duration: 150 }} class="flex flex-col h-full">
|
||||
<DialogMcpServerAddNew bind:open={isAddingServer} />
|
||||
|
||||
<DialogMcpResourcesBrowser bind:open={isResourcesDialogOpen} />
|
||||
|
||||
{#if servers.length === 0}
|
||||
<div class="flex flex-1 items-center justify-center py-16">
|
||||
<div class="flex flex-1 items-center justify-center pb-20 pt-10 my-auto">
|
||||
<Empty.Root class="max-w-md">
|
||||
<Empty.Header>
|
||||
<Empty.Media variant="icon">
|
||||
@@ -112,8 +78,8 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div
|
||||
class="grid gap-3 {className}"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(min(32rem, calc(100dvw - 2rem)), 1fr));"
|
||||
class="grid gap-4 {className}"
|
||||
style="grid-template-columns: repeat(auto-fill, minmax(min(25rem, calc(100dvw - 4rem)), 1fr));"
|
||||
>
|
||||
{#each servers as server (server.id)}
|
||||
{#if isServerPending(server.id, server.enabled)}
|
||||
@@ -121,6 +87,7 @@
|
||||
{:else}
|
||||
<McpServerCard
|
||||
enabled={conversationsStore.preferences.isMcpServerEnabledForChat(server.id)}
|
||||
onBrowseResources={() => (isResourcesDialogOpen = true)}
|
||||
onDelete={() => mcpStore.removeServer(server.id)}
|
||||
onToggle={async () => {
|
||||
const wasEnabled = conversationsStore.preferences.isMcpServerEnabledForChat(
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
/**
|
||||
* Full chat settings page layout with sidebar, mobile header, and content area.
|
||||
* Manages local configuration state, section navigation, and context setup.
|
||||
* Accepts an optional `initialSection` prop to override the URL-based section resolution.
|
||||
* Accepts an optional `initialSection` prop to set the initial active section.
|
||||
*/
|
||||
export { default as SettingsChat } from './SettingsChat/SettingsChat.svelte';
|
||||
|
||||
/**
|
||||
* Desktop sidebar navigation for chat settings.
|
||||
* Displays a list of settings sections with icons and titles.
|
||||
* Supports both hash-link navigation (via `getHref`) and in-app section switching (via `onSectionChange`).
|
||||
* Switches sections in-app via `onSectionChange`.
|
||||
*/
|
||||
export { default as SettingsChatDesktopSidebar } from './SettingsChatDesktopSidebar.svelte';
|
||||
|
||||
/**
|
||||
* Mobile header with a horizontally scrollable section picker for chat settings.
|
||||
* Shows chevron buttons for scroll navigation and highlights the active section.
|
||||
* Supports both hash-link navigation (via `getHref`) and in-app section switching (via `onSectionChange`).
|
||||
* Switches sections in-app via `onSectionChange`.
|
||||
*/
|
||||
export { default as SettingsChatMobileHeader } from './SettingsChatMobileHeader.svelte';
|
||||
|
||||
|
||||
@@ -5,3 +5,9 @@
|
||||
</script>
|
||||
|
||||
<DialogPrimitive.Close bind:ref data-slot="dialog-close" {...restProps} />
|
||||
|
||||
<style>
|
||||
:global([data-dialog-close]) {
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
class: className,
|
||||
portalProps,
|
||||
ref = $bindable(null),
|
||||
showCloseButton = true,
|
||||
showCloseButton = false,
|
||||
...restProps
|
||||
}: WithoutChildrenOrChild<DialogPrimitive.ContentProps> & {
|
||||
portalProps?: DialogPrimitive.PortalProps;
|
||||
@@ -25,7 +25,8 @@
|
||||
<DialogPrimitive.Content
|
||||
bind:ref
|
||||
class={cn(
|
||||
`fixed top-[50%] left-[50%] z-50 grid max-h-[100dvh] w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-lg border border-border/30 bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:fill-mode-forwards data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg md:max-h-[100vh]`,
|
||||
`fixed top-[50%] left-[50%] z-50 grid translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto rounded-lg border border-border/30 bg-background p-6 shadow-lg duration-200 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:fill-mode-forwards data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95`,
|
||||
'max-h-[100dvh] max-w-full size-auto sm:max-w-lg md:w-auto md:size-auto md:max-w-[calc(100%-2rem)]',
|
||||
className
|
||||
)}
|
||||
data-slot="dialog-content"
|
||||
|
||||
@@ -1,20 +1,43 @@
|
||||
<script lang="ts">
|
||||
import XIcon from '@lucide/svelte/icons/x';
|
||||
import { cn, type WithElementRef } from '$lib/components/ui/utils';
|
||||
import { Dialog as DialogPrimitive } from 'bits-ui';
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
|
||||
let {
|
||||
children,
|
||||
class: className,
|
||||
ref = $bindable(null),
|
||||
showCloseButton = true,
|
||||
...restProps
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> = $props();
|
||||
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
|
||||
showCloseButton?: boolean;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Header is `sticky`, so it stays at the top while the dialog body scrolls. The close
|
||||
button lives here (not in the body) so it sticks together with the title. `sticky`
|
||||
makes it the containing block, so the close can be absolutely placed at its corner.
|
||||
-->
|
||||
<div
|
||||
bind:this={ref}
|
||||
class={cn('flex flex-col gap-2 text-center sm:text-left', className)}
|
||||
class={cn(
|
||||
'flex flex-col gap-2 text-center sm:text-left sticky top-0 z-50 bg-background md:bg-transparent',
|
||||
className
|
||||
)}
|
||||
data-slot="dialog-header"
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
{#if showCloseButton}
|
||||
<DialogPrimitive.Close
|
||||
class="absolute top-0 right-0 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
|
||||
>
|
||||
<XIcon />
|
||||
|
||||
<span class="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -15,10 +15,6 @@ export const ROUTES = {
|
||||
MCP_SERVERS: '#/mcp-servers',
|
||||
/** Search — mobile-only full-page conversation search. */
|
||||
SEARCH: '#/search',
|
||||
/** Settings base — for dynamic settings URLs use RouterService. */
|
||||
SETTINGS: '#/settings',
|
||||
/** Exit destination for the settings view (fallback when no referrer). */
|
||||
SETTINGS_EXIT: '#/',
|
||||
/** Root — start of the app. */
|
||||
START: '#/'
|
||||
} as const;
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { ROUTES } from './routes.constants';
|
||||
import { Package, Search, Settings, SquarePen } from '@lucide/svelte';
|
||||
import McpLogo from '$lib/components/app/mcp/McpLogo.svelte';
|
||||
import { SidebarAction, ToolSource } from '$lib/enums';
|
||||
import type { DesktopIconStripItem } from '$lib/types';
|
||||
|
||||
@@ -64,15 +62,8 @@ export const SIDEBAR_ACTIONS_ITEMS: DesktopIconStripItem[] = [
|
||||
},
|
||||
{ icon: Search, keys: ['cmd', 'k'], tooltip: 'Search' },
|
||||
{
|
||||
activeRouteId: '/mcp-servers',
|
||||
icon: McpLogo,
|
||||
route: ROUTES.MCP_SERVERS,
|
||||
tooltip: 'MCP Servers'
|
||||
},
|
||||
{
|
||||
activeUrlIncludes: '#/settings',
|
||||
action: SidebarAction.SETTINGS,
|
||||
icon: Settings,
|
||||
route: `${ROUTES.SETTINGS}/general`,
|
||||
tooltip: 'Settings'
|
||||
}
|
||||
];
|
||||
|
||||
@@ -23,7 +23,8 @@ export enum ScrollCarouselVariant {
|
||||
* Sidebar icon strip actions handled directly by the sidebar.
|
||||
*/
|
||||
export enum SidebarAction {
|
||||
NEW_CHAT = 'new-chat'
|
||||
NEW_CHAT = 'new-chat',
|
||||
SETTINGS = 'settings'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import { beforeNavigate } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { ROUTES } from '$lib/constants';
|
||||
import { settingsReferrer } from '$lib/stores';
|
||||
|
||||
export interface ChatSettings {
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
export function useSettingsNavigation() {
|
||||
const subroute = $state({
|
||||
activePanel: 'chat' as 'chat' | 'settings' | 'mcp',
|
||||
chatSettingsRef: undefined as ChatSettings | undefined
|
||||
});
|
||||
const isSettingsRoute = $derived(!!page.route.id?.startsWith('/settings'));
|
||||
|
||||
beforeNavigate(({ from, to }) => {
|
||||
if (to?.route?.id?.startsWith('/settings') && !from?.route?.id?.startsWith('/settings')) {
|
||||
settingsReferrer.url = window.location.hash || ROUTES.START;
|
||||
}
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (subroute.activePanel === 'settings' && subroute.chatSettingsRef) {
|
||||
subroute.chatSettingsRef.reset();
|
||||
}
|
||||
});
|
||||
|
||||
// Return to chat when navigating to a new route
|
||||
$effect(() => {
|
||||
void page.url;
|
||||
|
||||
subroute.activePanel = 'chat';
|
||||
});
|
||||
|
||||
return {
|
||||
get isSettingsRoute() {
|
||||
return isSettingsRoute;
|
||||
},
|
||||
|
||||
get panel() {
|
||||
return subroute;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -307,7 +307,6 @@ export { SandboxService } from './sandbox.service';
|
||||
*
|
||||
* **Key Responsibilities:**
|
||||
* - Build chat URLs for specific conversations: `RouterService.chat(id)` → `#/chat/:id`
|
||||
* - Build settings URLs for sections: `RouterService.settings(section)` → `#/settings/:section`
|
||||
*
|
||||
* @see ROUTES in constants/routes.ts — static route base paths
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* RouterService - Builds app route paths
|
||||
*
|
||||
* Returns chat and settings route strings from a single source of truth
|
||||
* (ROUTES). No state.
|
||||
* Returns chat route strings from a single source of truth (ROUTES). No state.
|
||||
*/
|
||||
|
||||
import { ROUTES } from '$lib/constants';
|
||||
@@ -11,8 +10,4 @@ export class RouterService {
|
||||
static chat(id: string): string {
|
||||
return `${ROUTES.CHAT}/${id}`;
|
||||
}
|
||||
|
||||
static settings(section: string): string {
|
||||
return `${ROUTES.SETTINGS}/${section}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,8 +49,6 @@ export { uiStore } from './ui.svelte';
|
||||
// SETTINGS / UI PREFERENCES
|
||||
export { settingsStore } from './settings/index.svelte';
|
||||
|
||||
export { settingsReferrer } from './settings/referrer.svelte';
|
||||
|
||||
export { permissionsStore } from './permissions.svelte';
|
||||
|
||||
// TOOLS
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* settingsReferrer - Remembers the settings route to return to after exit
|
||||
*
|
||||
* Tracks the last settings section the user was on so the app can return
|
||||
* there after a fallback exit. Standalone reactive value, no host.
|
||||
*/
|
||||
|
||||
import { ROUTES } from '$lib/constants';
|
||||
|
||||
let _url = $state<string>(ROUTES.SETTINGS_EXIT);
|
||||
|
||||
export const settingsReferrer = {
|
||||
get url() {
|
||||
return _url;
|
||||
},
|
||||
set url(value: string) {
|
||||
_url = value;
|
||||
}
|
||||
};
|
||||
@@ -1,5 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { SettingsMcpServers } from '$lib/components/app/settings';
|
||||
</script>
|
||||
|
||||
<SettingsMcpServers class="mx-auto w-full p-4 md:p-8 md:py-8" />
|
||||
@@ -1,38 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { X } from '@lucide/svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { ActionIcon } from '$lib/components/app';
|
||||
import { ROUTES } from '$lib/constants';
|
||||
|
||||
let { children } = $props();
|
||||
|
||||
let previousRouteId = $state<string | null>(null);
|
||||
|
||||
$effect(() => {
|
||||
const currentId = page.route.id;
|
||||
|
||||
return () => {
|
||||
previousRouteId = currentId;
|
||||
};
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
const prevIsSettings = previousRouteId?.startsWith('/settings');
|
||||
|
||||
if (browser && window.history.length > 1 && !prevIsSettings) {
|
||||
history.back();
|
||||
} else {
|
||||
goto(ROUTES.SETTINGS_EXIT);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="fixed top-4.5 right-4 z-50 md:hidden">
|
||||
<ActionIcon icon={X} onclick={handleClose} tooltip="Close" />
|
||||
</div>
|
||||
|
||||
<div class="min-h-full">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
@@ -1,15 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, replaceState } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { SettingsChat } from '$lib/components/app/settings';
|
||||
import { SETTINGS_SECTION_SLUGS } from '$lib/constants';
|
||||
import { RouterService } from '$lib/services';
|
||||
|
||||
afterNavigate(() => {
|
||||
if (!page.params.section) {
|
||||
replaceState(RouterService.settings(SETTINGS_SECTION_SLUGS.GENERAL), {});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<SettingsChat initialSection={(page.params as Record<string, string | undefined>).section} />
|
||||
Reference in New Issue
Block a user