From a7273847bc3d2bfb7a4891f0ec4ff0b5f0053d7d Mon Sep 17 00:00:00 2001 From: rcmerci Date: Thu, 20 Mar 2025 22:27:56 +0800 Subject: [PATCH] feat(vec-search): debug ui support selecting model --- deps/db/src/logseq/db/frontend/kv_entity.cljs | 6 +- .../components/vector_search/sidebar.cljs | 67 +++++++++---- src/main/frontend/core.cljs | 1 + src/main/frontend/handler.cljs | 4 +- .../vector_search_background_tasks.cljs | 22 +++++ .../handler/db_based/vector_search_flows.cljs | 9 ++ .../inference_worker/inference_worker.cljs | 8 ++ src/main/frontend/inference_worker/state.cljs | 1 + .../inference_worker/text_embedding.cljs | 98 +++++++++++++------ src/main/frontend/worker/db_worker.cljs | 14 +++ src/main/frontend/worker/embedding.cljs | 37 ++++++- 11 files changed, 214 insertions(+), 53 deletions(-) create mode 100644 src/main/frontend/handler/db_based/vector_search_background_tasks.cljs diff --git a/deps/db/src/logseq/db/frontend/kv_entity.cljs b/deps/db/src/logseq/db/frontend/kv_entity.cljs index 03fd8f4e8a..7327edef2c 100644 --- a/deps/db/src/logseq/db/frontend/kv_entity.cljs +++ b/deps/db/src/logseq/db/frontend/kv_entity.cljs @@ -25,4 +25,8 @@ RTC won't start when major-schema-versions don't match" :logseq.kv/graph-backup-folder {:doc "Backup folder for automated backup feature" :rtc {:rtc/ignore-entity-when-init-upload true :rtc/ignore-entity-when-init-download true}} - :logseq.kv/graph-initial-schema-version {:doc "Graph's schema version when created"}) + :logseq.kv/graph-initial-schema-version {:doc "Graph's schema version when created"} + + :logseq.kv/graph-text-embedding-model-name {:doc "Graph's text-embedding model name" + :rtc {:rtc/ignore-entity-when-init-upload true + :rtc/ignore-entity-when-init-download true}}) diff --git a/src/main/frontend/components/vector_search/sidebar.cljs b/src/main/frontend/components/vector_search/sidebar.cljs index 59758ab7bd..ed915b32d8 100644 --- a/src/main/frontend/components/vector_search/sidebar.cljs +++ b/src/main/frontend/components/vector_search/sidebar.cljs @@ -15,6 +15,7 @@ [] (let [repo (state/get-current-repo) ^js worker @db-browser/*worker + [model-info set-model-info] (hooks/use-state nil) [vec-search-state set-vec-search-state] (hooks/use-state nil) [query-string set-query-string] (hooks/use-state nil) [result set-result] (hooks/use-state nil)] @@ -26,6 +27,18 @@ vector-search-flows/vector-search-state-flow) ::update-vec-search-state :succ (constantly nil))) []) + (hooks/use-effect! + (fn [] + (c.m/run-task + (m/reduce + (constantly nil) + (m/ap + (m/?> vector-search-flows/infer-worker-ready-flow) + (let [model-info (ldb/read-transit-str (c.m/index-info repo])] [:pre.select-text (with-out-str (fipp/pprint state-map {:width 10}))]) - (shui/button - {:size :sm - :class "mx-2" - :on-click (fn [_] (.vec-search-embedding-stale-blocks worker repo))} - "embedding-stale-blocks") - (shui/button - {:size :sm - :class "mx-2" - :on-click (fn [_] (.vec-search-re-embedding-graph-data worker repo))} - "force-embedding-all-graph-blocks") - (when (get-in vec-search-state [:repo->index-info repo :indexing?]) - (shui/button - {:size :sm - :class "mx-2" - :on-click (fn [_] (.vec-search-cancel-indexing worker repo))} - "cancel-current-indexing")) [:hr] - [:b "Search:"] + [:b "Actions"] + [:div + (shui/button + {:size :sm + :class "mx-2" + :on-click (fn [_] (.vec-search-embedding-stale-blocks worker repo))} + "embedding-stale-blocks") + (shui/button + {:size :sm + :class "mx-2" + :on-click (fn [_] (.vec-search-re-embedding-graph-data worker repo))} + "force-embedding-all-graph-blocks") + (when (get-in vec-search-state [:repo->index-info repo :indexing?]) + (shui/button + {:size :sm + :class "mx-2" + :on-click (fn [_] (.vec-search-cancel-indexing worker repo))} + "cancel-current-indexing"))] + [:hr] + [:b "Settings"] + (shui/select + {:on-value-change (fn [model-name] + (c.m/run-task + (m/sp + (c.m/ flows/current-repo-flow) + (when-let [^js worker @db-browser/*worker] + (c.m/js (keys infer-worker.text-embedding/available-embedding-models))) + (set-db-worker-proxy [_this proxy] (reset! infer-worker.state/*db-worker proxy) diff --git a/src/main/frontend/inference_worker/state.cljs b/src/main/frontend/inference_worker/state.cljs index f4ee28f685..33eb98aaad 100644 --- a/src/main/frontend/inference_worker/state.cljs +++ b/src/main/frontend/inference_worker/state.cljs @@ -9,3 +9,4 @@ (defonce *hnsw-index (atom {})) (defonce *extractor (atom nil)) +(defonce *model-name+config (atom nil)) diff --git a/src/main/frontend/inference_worker/text_embedding.cljs b/src/main/frontend/inference_worker/text_embedding.cljs index dc4345d23a..967a23f071 100644 --- a/src/main/frontend/inference_worker/text_embedding.cljs +++ b/src/main/frontend/inference_worker/text_embedding.cljs @@ -2,6 +2,7 @@ "text embedding fns" (:require ["@huggingface/transformers" :refer [pipeline]] ["hnswlib-wasm" :refer [loadHnswlib]] + [clojure.data :as data] [frontend.common.missionary :as c.m] [frontend.inference-worker.state :as infer-worker.state] [frontend.worker-common.util :as worker-util] @@ -9,9 +10,16 @@ [missionary.core :as m] [promesa.core :as p])) +(add-watch infer-worker.state/*hnsw-index :delete-obj-when-dissoc + (fn [_ _ o n] + (let [[old-only] (data/diff o n)] + (doseq [[repo ^js hnsw-index] old-only] + (when hnsw-index + (log/info :delete-hnsw-index repo) + (.delete hnsw-index)))))) + (def ^:private embedding-opts #js{"pooling" "mean" "normalize" true}) -(def ^:private num-dimensions 384) (def ^:private init-max-elems 100) (defn- split-into-chunks @@ -24,23 +32,37 @@ (recur (+ i chunk-size)))) result)) -(defn- init-index +(defn- init-index! [^js hnsw] (.initIndex hnsw init-max-elems 16 200 100) (.setEfSearch hnsw 64 ;;default 32 )) -(defn- ^js ensure-hnsw-index! +(defn- ^js get-hnsw-index [repo] (or (@infer-worker.state/*hnsw-index repo) (let [hnsw-ctor (.-HierarchicalNSW ^js @infer-worker.state/*hnswlib) - hnsw (new hnsw-ctor "cosine" num-dimensions "") + hnsw (new hnsw-ctor "cosine" (or (:dims (:hnsw-config (second @infer-worker.state/*model-name+config))) 384) "") file-exists? (.checkFileExists (.-EmscriptenFileSystemManager ^js @infer-worker.state/*hnswlib) repo)] - (if file-exists? + (when file-exists? (.readIndex hnsw repo init-max-elems) - (init-index hnsw)) - (swap! infer-worker.state/*hnsw-index assoc repo hnsw) - (@infer-worker.state/*hnsw-index repo)))) + (swap! infer-worker.state/*hnsw-index assoc repo hnsw) + hnsw)))) + +(defn- ^js new-hnsw-index! + [repo] + (when (get-hnsw-index repo) + (swap! infer-worker.state/*hnsw-index dissoc repo)) + (let [hnsw-ctor (.-HierarchicalNSW ^js @infer-worker.state/*hnswlib) + hnsw (new hnsw-ctor "cosine" (or (:dims (:hnsw-config (second @infer-worker.state/*model-name+config))) 384) "")] + (init-index! hnsw) + (swap! infer-worker.state/*hnsw-index assoc repo hnsw) + hnsw)) + +(defn- model-loaded? + [] + (and @infer-worker.state/*extractor + @infer-worker.state/*model-name+config)) (defn js (-> (:tf-config config) + (assoc "device" "webgpu") + (assoc "progress_callback" #(log/info :progress %)))))] + (reset! infer-worker.state/*extractor extractor) + (reset! infer-worker.state/*model-name+config [model-name config]) + true))) + (defn index-info {} ;; repo->index-info - :repo->canceler {} ;; repo->canceler + :repo->canceler {} ;; repo->canceler }) (def ^:private vector-search-state-keys (set (keys empty-vector-search-state))) @@ -98,7 +99,7 @@ (defn- labels-update-tx-data [db e+updated-at-coll added-labels] - (assert (= (count e+updated-at-coll) (count added-labels))) + (assert (= (count e+updated-at-coll) (count added-labels)) [e+updated-at-coll added-labels]) (let [es (map first e+updated-at-coll) exist-es (set (keep (fn [b] (when (:block/uuid b) (:db/id b))) @@ -179,6 +180,34 @@ :re-embedding-graph-data! :succ (constantly nil))] (reset-*vector-search-state! repo :canceler canceler)))) +(defn task--embedding-model-info + [repo] + (m/sp + (when-let [^js infer-worker @worker-state/*infer-worker] + (let [available-model-names (c.m/