Skip to content

Instantly share code, notes, and snippets.

@wastemobile
Last active April 5, 2026 00:56
Show Gist options
  • Select an option

  • Save wastemobile/20528f4bd78f66a2e5b584fff1877dfb to your computer and use it in GitHub Desktop.

Select an option

Save wastemobile/20528f4bd78f66a2e5b584fff1877dfb to your computer and use it in GitHub Desktop.
QMD 2.0.1 中文檢索維度不符 Bug 實驗報告

QMD 2.0.1 中文檢索維度不符 Bug 實驗報告

日期: 2026-04-05
環境: Docker (OrbStack) · Bun 1.3.10 · QMD 2.0.1 · Linux arm64
語料: Obsidian vault MAGI(517 個 Markdown 檔,513 成功索引) 實驗者: Claude Code


一、問題背景

QMD(Query Markup Documents,@tobilu/qmd)是一套本機混合式搜尋引擎,支援三種查詢模式:

指令 機制
qmd search BM25 全文檢索(FTS5)
qmd vsearch 純向量語意搜尋
qmd query BM25 + 向量 + RRF fusion + LLM reranker(最高品質)

QMD 預設使用 embeddinggemma-300M-Q8_0(768-dim)作為 embedding model。對於中文語料,該模型語言覆蓋率不足,官方支援透過環境變數 QMD_EMBED_MODEL 切換為多語言模型。

本實驗使用 Qwen3-Embedding-0.6B-Q8_0(1024-dim)作為替代模型,以改善中文檢索品質。使用者反映:索引與 qmd search 可正常運作,但 qmd query 會失敗,懷疑 query 路徑未正確沿用自訂 embedding 設定,導致向量維度不符(768 vs 1024 mismatch)。

實驗目標:

  1. 確認此 mismatch 是否能重現,並取得具體的 error message 與實際維度數字
  2. 定位 bug 在 QMD 原始碼的確切位置
  3. 提出可能的修復方式

二、實驗環境

容器配置

基底映像:oven/bun:1-debian
額外套件:sqlite3、python3、make、g++(供 better-sqlite3 原生編譯)
QMD 安裝:bun install -g @tobilu/qmd

模型清單(自動從 HuggingFace 下載)

模型 用途 大小 向量維度
embeddinggemma-300M-Q8_0 預設 embedding 328 MB 768
Qwen3-Embedding-0.6B-Q8_0 自訂 embedding(中文) 639 MB 1024
qwen3-reranker-0.6b-q8_0 Re-ranking 639 MB
qmd-query-expansion-1.7B-q4_k_m Query 展開 1.28 GB

語料

  • 來源:~/Library/Mobile Documents/iCloud~md~obsidian/Documents/MAGI
  • 總計:517 個 .md 檔,513 成功索引,507 個唯一 hash 需 embedding
  • Embedding 結果:896 chunks 成功,554 chunks 失敗(部分文件過長或格式問題)

三、實驗方法

Phase 1:設定 QMD_EMBED_MODEL 進行索引與嵌入

export QMD_EMBED_MODEL="hf:Qwen/Qwen3-Embedding-0.6B-GGUF/Qwen3-Embedding-0.6B-Q8_0.gguf"

qmd collection add /magi --name magi
qmd update   # 索引文件
qmd embed    # 使用 Qwen3 生成向量(1024-dim)

嵌入完成後,透過 SQLite 查詢確認儲存的向量維度與 model tag:

sqlite3 ~/.cache/qmd/index.sqlite ".schema vectors_vec"
sqlite3 ~/.cache/qmd/index.sqlite \
  "SELECT DISTINCT model, COUNT(*) FROM content_vectors GROUP BY model;"

並測試三種模式(全部設定 QMD_EMBED_MODEL):

  • qmd search "知識管理" / "學習筆記" / "工作流程"
  • qmd vsearch "知識管理" / "學習筆記" / "工作流程"
  • qmd query "知識管理" / "學習筆記" / "工作流程"

Phase 2:取消 QMD_EMBED_MODEL,模擬新開 terminal 的查詢

unset QMD_EMBED_MODEL

qmd query "知識管理"
qmd query "學習筆記"
qmd query "工作流程"

此情境模擬使用者在不同 shell session 下使用 QMD 的真實情況。


四、實驗結果

4.1 儲存的向量維度(Phase 1 確認)

-- vectors_vec 虛擬表 schema
CREATE VIRTUAL TABLE vectors_vec USING vec0(
  hash_seq TEXT PRIMARY KEY,
  embedding float[1024] distance_metric=cosine
);

-- content_vectors 的 model tag
embeddinggemma | 896 chunks

關鍵發現:

  • 向量維度確實為 1024-dim(Qwen3 所產生)
  • 但 model tag 全部被記錄為 "embeddinggemma"而非 Qwen3 的 URI
    • 這是一個附帶 bug:model tag 標記錯誤

4.2 Phase 1 查詢結果(QMD_EMBED_MODEL 設定中)

三種模式在 QMD_EMBED_MODEL 設定的情況下均正常運作

模式 知識管理 學習筆記 工作流程
search ✅ 成功(BM25) ✅ 成功 ✅ 成功
vsearch ✅ 成功(向量) ✅ 成功 ✅ 成功
query ✅ 成功(混合) ✅ 成功 ✅ 成功

此結果表面上看似 bug 未重現,但實為意外成功——原因詳見第五節分析。

4.3 Phase 2 查詢結果(取消 QMD_EMBED_MODEL

Phase 2 中,QMD 自動下載並載入預設 embedding 模型 embeddinggemma-300M-Q8_0(328 MB,768-dim),隨即在向量查詢時觸發維度不符錯誤。

三組 query 全部出現相同 error:

▷ qmd query "知識管理"
...
Embedding 4 queries... (8.6s)
2046 |     // Step 1: Get vector matches from sqlite-vec (no JOINs allowed)
2047 |     const vecResults = db.prepare(`
2048 |     SELECT hash_seq, distance
2049 |     FROM vectors_vec
2050 |     WHERE embedding MATCH ? AND k = ?
2051 |   `).all(new Float32Array(embedding), limit * 3);
            ^
SQLiteError: Dimension mismatch for query vector for the "embedding" column.
Expected 1024 dimensions but received 768.
      errno: 1,
 byteOffset: -1,

      at searchVec (dist/store.js:2051:6)
      at hybridQuery (dist/store.js:2830:44)

Bun v1.3.10 (Linux arm64)
Query 結果
知識管理 ❌ SQLiteError: Expected 1024 dims, got 768
學習筆記 ❌ SQLiteError: Expected 1024 dims, got 768
工作流程 ❌ SQLiteError: Expected 1024 dims, got 768

Bug 成功重現。


五、Bug 根因分析

5.1 兩個互相脫節的 default model 常數

QMD 原始碼中存在兩個各自獨立定義 default embedding model 的地方:

src/llm.ts(正確讀取 env var,控制實際載入的 GGUF 模型):

const DEFAULT_EMBED_MODEL = process.env.QMD_EMBED_MODEL
  ?? "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";

LlamaCpp singleton 以此值初始化,因此當 QMD_EMBED_MODEL 設定時,載入的是 Qwen3(1024-dim)

src/store.ts 約第 42 行(靜態字串,完全不讀 env var,控制 model tag 與 searchVec 呼叫):

export const DEFAULT_EMBED_MODEL = "embeddinggemma";  // 固定值,不受 env var 影響

5.2 三個 broken call site

src/store.ts 中有三處呼叫 searchVec 時,傳入的 model 參數均使用這個靜態常數:

原始碼位置(約) 編譯後位置 函式
src/store.ts ~3994 行 dist/store.js:2830 hybridQuery(第一趟 vec 搜尋)
src/store.ts ~4377 行 dist/store.js:2830 hybridQuery(第二趟 vec 搜尋)
src/store.ts ~4227 行 dist/store.js:2051 searchVec(直接呼叫)

三處均傳入 "embeddinggemma" 字串,而非從 process.env.QMD_EMBED_MODEL 解析出的實際 URI。

5.3 Bug 的完整傳播路徑

qmd embed(設定 QMD_EMBED_MODEL=Qwen3)
  │
  ├─ llm.ts: LlamaCpp singleton 載入 Qwen3(1024-dim)✅
  └─ store.ts: vectorIndex("embeddinggemma", ...)
       │
       └─ 實際執行 embed:LlamaCpp 忽略 "embeddinggemma" 字串,
          用已載入的 Qwen3 生成 1024-dim 向量
          → 向量寫入 vectors_vec(float[1024])
          → content_vectors.model = "embeddinggemma"(tag 錯誤)

          ↓ 使用者開新 terminal,未設定 QMD_EMBED_MODEL

qmd query(未設定 QMD_EMBED_MODEL)
  │
  ├─ llm.ts: LlamaCpp singleton 載入預設 gemma(768-dim)
  └─ store.ts: searchVec(query, "embeddinggemma", ...)
       │
       └─ getEmbedding() → 用 gemma 計算 768-dim query 向量
          → sqlite-vec: WHERE embedding MATCH Float32Array(768)
          → vectors_vec 期待 1024-dim
          → SQLiteError: Expected 1024 dimensions but received 768 ❌

5.4 為何 Phase 1 意外成功

QMD_EMBED_MODEL 兩邊都有設定時:

  • embed:LlamaCpp 用 Qwen3(1024-dim),model tag 存成 "embeddinggemma"
  • query:LlamaCpp 也用 Qwen3(1024-dim),searchVec 也傳 "embeddinggemma" 作為 tag filter

兩邊使用相同的錯誤 tag "embeddinggemma",向量維度也一致(1024 = 1024),因此意外地能正常運作。這是一種以錯補錯的偶然成功,並非正確行為。


六、建議修復方式

src/store.ts 中,將三個 searchVec call site 的 model 參數從靜態常數改為讀取 env var 的動態值。

方案 A:從 llm.ts import(最乾淨)

// src/store.ts 頂部
import { DEFAULT_EMBED_MODEL_URI } from './llm.js';

// 三個 searchVec call site 改為:
store.searchVec(query, DEFAULT_EMBED_MODEL_URI, limit, collection)

前提是 llm.ts 需將該常數 export。

方案 B:在 store.ts 內部解析 env var

// src/store.ts 頂部新增
const RESOLVED_EMBED_MODEL = process.env.QMD_EMBED_MODEL
  ?? "hf:ggml-org/embeddinggemma-300M-GGUF/embeddinggemma-300M-Q8_0.gguf";

// 三個 searchVec call site 改為:
store.searchVec(query, RESOLVED_EMBED_MODEL, limit, collection)

此方案不需改動 llm.ts,改動範圍更小。

兩種方案都同步修正了 content_vectors.model 的 tag 錯誤問題(embed 時的 tag 也應改為使用 RESOLVED_EMBED_MODEL)。


七、結論

項目 結果
Bug 能否重現 ,三組 query 全部確認
錯誤類型 SQLiteError(非靜默失敗)
觸發條件 embed 時有設 QMD_EMBED_MODEL,query 時未設
儲存向量維度 1024-dim(Qwen3 正確生成)
Query 向量維度 768-dim(gemma 預設,維度不符)
Bug 所在檔案 src/store.ts 約第 42 行及三個 searchVec call site
編譯後 crash 位置 dist/store.js:2051(searchVec)、2830(hybridQuery)
修復複雜度 低(3 個 call site,單行改動)

GOAL.md 中描述的「query 模式沿用另一組預設維度」確認成立:問題不在資料索引或 Qwen3 模型本身,而在於 store.tsDEFAULT_EMBED_MODEL 常數未讀取 QMD_EMBED_MODEL env var,導致 query 時 sqlite-vec 收到維度不符的查詢向量而拋出 SQLiteError。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment