<cite id="ffb66"></cite><cite id="ffb66"><track id="ffb66"></track></cite>
      <legend id="ffb66"><li id="ffb66"></li></legend>
      色婷婷久,激情色播,久久久无码专区,亚洲中文字幕av,国产成人A片,av无码免费,精品久久国产,99视频精品3
      網易首頁 > 網易號 > 正文 申請入駐

      向量搜索升級指南:FAISS 到 Qdrant 遷移方案與代碼實現

      0
      分享至

      FAISS 在實驗階段確實好用,速度快、上手容易,notebook 里跑起來很順手。但把它搬到生產環境還是有很多問題:

      首先是元數據的問題,FAISS 索引只認向量,如果想按日期或其他條件篩選還需要自己另外搞一套查找系統。

      其次它本質上是個庫而不是服務,讓如果想對外提供接口還得自己用 Flask 或 FastAPI 包一層。

      最后最麻煩的是持久化,pod 一旦掛掉索引就沒了,除非提前手動存盤。

      Qdrant 的出現解決了這些痛點,它更像是個真正的數據庫,提供開箱即用的 API、數據重啟后依然在、原生支持元數據過濾。更關鍵的是混合搜索(Dense + Sparse)和量化這些高級功能都是內置的。

      MS MARCO Passages 數據集

      這次用的是 MS MARCO Passage Ranking 數據集,信息檢索領域的標準測試集。

      數據是從網頁抓取的約880萬條短文本段落,選它的原因很簡單:段落短(平均50詞),不用處理復雜的文本分塊,可以把精力放在遷移工程本身。

      實際測試時用了10萬條數據的子集,這樣速度會很快

      嵌入模型用的是 sentence-transformers/all-MiniLM-L6-v2,輸出384維的稠密向量。

      FAISS 階段的初始配置

      生成嵌入向量

      加載原始數據,批量生成嵌入向量。這里關鍵的一步是把結果存成 .npy 文件,避免后續重復計算。



      import pandas as pd
      from sentence_transformers import SentenceTransformer
      import numpy as np
      import os
      import csv
      DATA_PATH = '../data'
      TSV_FILE = f'{DATA_PATH}/collection.tsv'
      SAMPLE_SIZE = 100000
      MODEL_ID = 'all-MiniLM-L6-v2'
      def prepare_data():
      print(f"Loading Model '{MODEL_ID}'...")
      model = SentenceTransformer(MODEL_ID)
      print(f"Reading first {SAMPLE_SIZE} lines from {TSV_FILE}...")
      ids = []
      passages = []
      # Efficiently read line-by-line without loading entire 8GB file to RAM
      try:
      with open(TSV_FILE, 'r', encoding='utf8') as f:
      reader = csv.reader(f, delimiter='\t')
      for i, row in enumerate(reader):
      if i >= SAMPLE_SIZE:
      break
      # MS MARCO format is: [pid, text]
      if len(row) >= 2:
      ids.append(int(row[0]))
      passages.append(row[1])
      except FileNotFoundError:
      print(f"Error: Could not find {TSV_FILE}")
      return
      print(f"Loaded {len(passages)} passages.")
      # Save text metadata (for Qdrant payload)
      print("Saving metadata to CSV...")
      df = pd.DataFrame({'id': ids, 'text': passages})
      df.to_csv(f'{DATA_PATH}/passages.csv', index=False)
      # Generate Embeddings
      print("Encoding Embeddings (this may take a moment)...")
      embeddings = model.encode(passages, show_progress_bar=True)
      # Save binary files (for FAISS and Qdrant)
      print("5. Saving numpy arrays...")
      np.save(f'{DATA_PATH}/embeddings.npy', embeddings)
      np.save(f'{DATA_PATH}/ids.npy', np.array(ids))
      print(f"Success! Saved {embeddings.shape} embeddings to {DATA_PATH}")
      if __name__ == "__main__":
      os.makedirs(DATA_PATH, exist_ok=True)
      prepare_data()

      構建索引

      用 IndexFlatL2 做精確搜索,對于百萬級別的數據量來說足夠了。

      import faiss
      import numpy as np
      import os
      DATA_PATH = '../data'
      INDEX_OUTPUT_PATH = './my_index.faiss'
      def build_index():
      print("Loading embeddings...")
      # Load the vectors
      if not os.path.exists(f'{DATA_PATH}/embeddings.npy'):
      print(f"Error: {DATA_PATH}/embeddings.npy not found.")
      return
      embeddings = np.load(f'{DATA_PATH}/embeddings.npy')
      d = embeddings.shape[1] # Dimension (should be 384 for MiniLM)
      print(f"Building Index (Dimension=djd33nj)...")
      # We use IndexFlatL2 for exact search (Simple & Accurate for <1M vectors).
      index = faiss.IndexFlatL2(d)
      index.add(embeddings)
      print(f"Saving index to {INDEX_OUTPUT_PATH}..")
      faiss.write_index(index, INDEX_OUTPUT_PATH)
      print(f"Success! Index contains {index.ntotal} vectors.")
      if __name__ == "__main__":
      os.makedirs(os.path.dirname(INDEX_OUTPUT_PATH), exist_ok=True)
      build_index()

      語義搜索測試

      隨便跑一個查詢就能看出問題了。返回的是 [42, 105] 這種 ID,如果想拿到實際文本還得寫一堆代碼去 CSV 里查,這種割裂感是遷移的主要原因。

      import faiss
      import numpy as np
      import pandas as pd
      from sentence_transformers import SentenceTransformer
      INDEX_PATH = './my_index.faiss'
      DATA_PATH = '../data'
      MODEL_NAME = 'all-MiniLM-L6-v2'
      def search_faiss():
      print("Loading Index and Metadata...")
      index = faiss.read_index(INDEX_PATH)
      # LIMITATION: We must manually load the CSV to get text back.
      # FAISS only stores vectors, not the text itself.
      df = pd.read_csv(f'{DATA_PATH}/passages.csv')
      model = SentenceTransformer(MODEL_NAME)
      # userquery
      query_text = "What is the capital of France?"
      print(f"\nQuery: '{query_text}'")
      # Encode and Search
      query_vector = model.encode([query_text])
      D, I = index.search(query_vector, k=3) # Search for top 3 results
      print("\n--- Results ---")
      for rank, idx in enumerate(I[0]):
      # LIMITATION: If we wanted to filter by "text_length > 50",
      # we would have to fetch ALL results first, then filter in Python.
      # FAISS cannot filter during search.
      text = df.iloc[idx]['text'] # Manual lookup
      score = D[0][rank]
      print(f"[{rank+1}] ID: {idx} | Score: {score:.4f}")
      print(f" Text: {text[:100]}...")
      if __name__ == "__main__":
      search_faiss()

      遷移步驟

      從 FAISS 導出向量

      前面步驟已經有 embeddings.npy 了,直接加載 numpy 數組就行,省去了導出環節。

      本地啟動 Qdrant 很簡單:

      docker run -p 6333:6333 qdrant/qdrant
      from qdrant_client import QdrantClient
      from qdrant_client.models import VectorParams, Distance, HnswConfigDiff
      QDRANT_URL = "http://localhost:6333"
      COLLECTION_NAME = "ms_marco_passages"
      def create_collection():
      client = QdrantClient(url=QDRANT_URL)
      print(f"Creating collection '{COLLECTION_NAME}'...")
      client.recreate_collection(
      collection_name=COLLECTION_NAME,
      vectors_config=VectorParams(
      size=384,# Dimension (MiniLM)- we should follow the existing dimension from FAISS
      distance=Distance.COSINE
      ),
      hnsw_config=HnswConfigDiff(
      m=16, # Links per node (default is 16)
      ef_construct=100 # Search depth during build (default is 100)
      )
      )
      print(f"Collection '{COLLECTION_NAME}' created with HNSW config.")
      if __name__ == "__main__":
      create_collection()

      批量上傳數據

      import pandas as pd
      import numpy as np
      from qdrant_client import QdrantClient
      from qdrant_client.models import PointStruct
      QDRANT_URL = "http://localhost:6333"
      COLLECTION_NAME = "ms_marco_passages"
      DATA_PATH = '../data'
      BATCH_SIZE = 500
      def upload_data():
      client = QdrantClient(url=QDRANT_URL)
      print("Loading local data...")
      embeddings = np.load(f'{DATA_PATH}/embeddings.npy')
      df_meta = pd.read_csv(f'{DATA_PATH}/passages.csv')
      total = len(df_meta)
      print(f"Starting upload of {total} vectors...")
      points_batch = []
      for i, row in df_meta.iterrows():
      # Metadata to attach
      payload = {
      "passage_id": int(row['id']),
      "text": row['text'],
      "text_length": len(str(row['text'])),
      "dataset_source": "msmarco_passages"
      }
      points_batch.append(PointStruct(
      id=int(row['id']),
      vector=embeddings[i].tolist(),
      payload=payload
      ))
      # Upload batch
      if len(points_batch) >= BATCH_SIZE or i == total - 1:
      client.upsert(
      collection_name=COLLECTION_NAME,
      points=points_batch
      )
      points_batch = []
      if i % 1000 == 0:
      print(f" Processed {i}/{total}...")
      print("Upload Complete.")
      if __name__ == "__main__":
      upload_data()

      驗證遷移結果

      from qdrant_client import QdrantClient
      from qdrant_client.models import Filter, FieldCondition, Range, MatchValue
      from sentence_transformers import SentenceTransformer
      QDRANT_URL = "http://localhost:6333"
      COLLECTION_NAME = "ms_marco_passages"
      MODEL_NAME = 'all-MiniLM-L6-v2'
      def validate_migration():
      client = QdrantClient(url=QDRANT_URL)
      model = SentenceTransformer(MODEL_NAME)
      # Verify total count
      count_result = client.count(COLLECTION_NAME)
      print(f"Total Vectors in Qdrant: {count_result.count}")
      # Query example
      query_text = "What is a GPU?"
      print(f"\n--- Query: '{query_text}' ---")
      query_vector = model.encode(query_text).tolist()
      # Filter Definition
      print("Applying filters (Length < 200 AND Source == msmarco)...")
      search_filter = Filter(
      must=[
      FieldCondition(
      key="text_length",
      range=Range(lt=200) # can be changed as per the requirement
      ),
      FieldCondition(
      key="dataset_source",
      match=MatchValue(value="msmarco_passages")
      )
      ]
      )
      results = client.query_points(
      collection_name=COLLECTION_NAME,
      query=query_vector,
      query_filter=search_filter,
      limit=3
      ).points
      for hit in results:
      print(f"\nID: {hit.id} (Score: {hit.score:.3f})")
      print(f"Text: {hit.payload['text']}")
      print(f"Metadata: {hit.payload}")
      if __name__ == "__main__":
      validate_migration()

      性能對比

      針對10個常見查詢做了對比測試。

      FAISS(本地 CPU):約 0.5ms,純數學計算的速度

      Qdrant(Docker):約 3ms,包含了網絡傳輸的開銷

      對 Web 服務來說3ms 的延遲完全可以接受,何況換來的是一堆新功能。

      import time
      import faiss
      import numpy as np
      from qdrant_client import QdrantClient
      from sentence_transformers import SentenceTransformer
      FAISS_INDEX_PATH = './faiss_index/my_index.faiss'
      QDRANT_URL = "http://localhost:6333"
      COLLECTION_NAME = "ms_marco_passages"
      MODEL_NAME = 'all-MiniLM-L6-v2'
      QUERIES = [
      "What is a GPU?",
      "Who is the president of France?",
      "How to bake a cake?",
      "Symptoms of the flu",
      "Python programming language",
      "Best places to visit in Italy",
      "Define quantum mechanics",
      "History of the Roman Empire",
      "What is machine learning?",
      "Healthy breakfast ideas"
      ]
      def run_comparison():
      print("---Loading Resources ---")
      # Load Model
      model = SentenceTransformer(MODEL_NAME)
      # Load FAISS (The "Old Way")
      print("Loading FAISS index...")
      faiss_index = faiss.read_index(FAISS_INDEX_PATH)
      # Connect to Qdrant (The "New Way")
      print("Connecting to Qdrant...")
      client = QdrantClient(url=QDRANT_URL)
      print(f"\n---Running Race ({len(QUERIES)} queries) ---")
      print(f"{'Query':<30} | {'FAISS (ms)':<10} | {'Qdrant (ms)':<10}")
      print("-" * 60)
      faiss_times = []
      qdrant_times = []
      for query_text in QUERIES:
      # Encode once
      query_vector = model.encode(query_text).tolist()
      # --- MEASURE FAISS ---
      start_f = time.perf_counter()
      # FAISS expects a numpy array of shape (1, d)
      faiss_input = np.array([query_vector], dtype='float32')
      _, _ = faiss_index.search(faiss_input, k=3)
      end_f = time.perf_counter()
      faiss_ms = (end_f - start_f) * 1000
      faiss_times.append(faiss_ms)
      # --- MEASURE QDRANT ---
      start_q = time.perf_counter()
      _ = client.query_points(
      collection_name=COLLECTION_NAME,
      query=query_vector,
      limit=3
      )
      end_q = time.perf_counter()
      qdrant_ms = (end_q - start_q) * 1000
      qdrant_times.append(qdrant_ms)
      print(f"{query_text[:30]:<30} | {faiss_ms:>10.2f} | {qdrant_ms:>10.2f}")
      print("-" * 60)
      print(f"{'AVERAGE':<30} | {np.mean(faiss_times):>10.2f} | {np.mean(qdrant_times):>10.2f}")
      if __name__ == "__main__":
      run_comparison()

      測試結果:



      最大的差異不在速度,在于省心。

      用 FAISS 時有次跑了個索引腳本處理大批數據,耗時40分鐘,占了12GB內存??焱瓿蓵r SSH 連接突然斷了,進程被殺,因為 FAISS 只是個跑在內存里的庫一切都白費了。

      換成 Qdrant 就不一樣了:它像真正的數據庫,數據推送后會持久化保存,即便突然斷開 docker 連接重啟后數據還在。

      用過 FAISS 就知道為了把向量 ID 映射回文本,還需要額外維護一個 CSV 文件。遷移到 Qdrant 后這些查找邏輯都刪掉了,文本和向量存在一起,直接查詢 API 就能拿到完整結果,不再需要管理各種文件,就是在用一個微服務。



      遷移總結

      這次遷移斷斷續續做了一周但收獲很大。最爽的不是寫 Qdrant 腳本,是刪掉舊代碼——提交的 PR 幾乎全是紅色刪除行。CSV 加載工具、手動 ID 映射、各種"代碼"全刪了,代碼量減少了30%,可讀性明顯提升。

      只用 FAISS 時,搜索有時像在碰運氣——語義上相似但事實錯誤的結果時常出現。遷移到 Qdrant拿到的不只是數據庫,更是對系統的掌控力。稠密向量配合關鍵詞過濾(混合搜索),終于能回答"顯示 GPU 相關的技術文檔,但只要官方手冊里的"這種精確查詢,這在之前根本做不到。

      信心的變化最明顯,以前不敢加載完整的880萬數據怕內存撐不住?,F在架構解耦了可以把全部數據推給 Qdrant,它會在磁盤上處理存儲和索引,應用層保持輕量。終于有了個在生產環境和 notebook 里都能跑得一樣好的系統。



      總結

      FAISS 適合離線研究和快速實驗,但要在生產環境跑起來Qdrant 提供了必需的基礎設施。如果還在用額外的 CSV 文件來理解向量含義該考慮遷移了。

      https://avoid.overfit.cn/post/ce7c45d8373741f6b8af465bb06bc398

      作者:Sai Bhargav Rallapalli

      特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。

      Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

      相關推薦
      熱點推薦
      驚人一致!99%的女人玩夠男人后,都會默契地做出這3種行為。

      驚人一致!99%的女人玩夠男人后,都會默契地做出這3種行為。

      荷蘭豆愛健康
      2026-03-07 12:19:26
      終身追殺令生效,什葉派終極殺招,特朗普與內塔尼亞胡無處可逃

      終身追殺令生效,什葉派終極殺招,特朗普與內塔尼亞胡無處可逃

      健身狂人
      2026-03-06 17:28:14
      哈登喜迎好幫手,騎士這奪冠拼圖終于齊了

      哈登喜迎好幫手,騎士這奪冠拼圖終于齊了

      章蠞戶外
      2026-03-07 11:38:45
      這老師真是絕代美人??!

      這老師真是絕代美人??!

      東方不敗然多多
      2026-03-01 01:09:31
      妹子給貓定做項鏈,發貓名卻被判定為違禁詞?一看貓名:真的很少站平臺

      妹子給貓定做項鏈,發貓名卻被判定為違禁詞?一看貓名:真的很少站平臺

      拜見喵主子
      2026-03-06 12:23:47
      56歲大媽心梗離世,醫生:吃他汀時除了牛奶,這6種食物盡量少碰

      56歲大媽心梗離世,醫生:吃他汀時除了牛奶,這6種食物盡量少碰

      岐黃傳人孫大夫
      2026-02-28 22:15:03
      特朗普拒絕收拾爛攤子?對以色列下達最后通牒,24小時內必須執行

      特朗普拒絕收拾爛攤子?對以色列下達最后通牒,24小時內必須執行

      咣當地球
      2026-03-06 20:27:02
      中國AI算力暗戰:字節阿里押注英偉達,訊飛全國產,百度走雙軌

      中國AI算力暗戰:字節阿里押注英偉達,訊飛全國產,百度走雙軌

      劉曠
      2026-03-06 08:53:37
      中東沒結束,亞太又出問題?朝鮮突發導彈,特朗普收到一封挑戰書

      中東沒結束,亞太又出問題?朝鮮突發導彈,特朗普收到一封挑戰書

      阿校談史
      2026-03-07 11:44:53
      《鏢人》票房超12億,打破14項紀錄,虧損超2億

      《鏢人》票房超12億,打破14項紀錄,虧損超2億

      影視高原說
      2026-03-06 07:03:59
      曾經全球僅存1株!2016年北京又發現1株快死的,現在怎樣了?

      曾經全球僅存1株!2016年北京又發現1株快死的,現在怎樣了?

      萬象硬核本尊
      2026-03-06 14:11:42
      全國人大代表黃勇平:不要讓做好研究的人,天天把時間花在申請經費上

      全國人大代表黃勇平:不要讓做好研究的人,天天把時間花在申請經費上

      上觀新聞
      2026-03-06 22:37:05
      新加坡急了,外長幾乎是拍著桌子,讓中國“尊重”馬六甲的地位。

      新加坡急了,外長幾乎是拍著桌子,讓中國“尊重”馬六甲的地位。

      南權先生
      2026-01-26 15:41:26
      山西王閻錫山的妹妹,沒來得及跟哥哥逃到臺灣,她的結局如何?

      山西王閻錫山的妹妹,沒來得及跟哥哥逃到臺灣,她的結局如何?

      老范談史
      2026-03-03 17:43:41
      倒閉注銷!天津這家33年老清真館,也黃了!?

      倒閉注銷!天津這家33年老清真館,也黃了!?

      天津族
      2026-03-07 07:35:06
      再讀《穆斯林的葬禮》,對茅盾文學獎的信任崩塌了!

      再讀《穆斯林的葬禮》,對茅盾文學獎的信任崩塌了!

      難得君
      2026-03-06 13:43:16
      96年王光美為沒錢憂愁不已,忽然看到一只象牙筆筒:它也能換錢?

      96年王光美為沒錢憂愁不已,忽然看到一只象牙筆筒:它也能換錢?

      談古論今歷史有道
      2026-03-07 13:05:03
      回顧70歲老漢慘死家中,胸口紙條寫著:你該死讓你下輩子再玩女人

      回顧70歲老漢慘死家中,胸口紙條寫著:你該死讓你下輩子再玩女人

      談史論天地
      2026-03-06 15:17:43
      補時“50分鐘” 皇馬94分鐘絕殺:丑陋2-1止住2連敗 43歲少帥命硬

      補時“50分鐘” 皇馬94分鐘絕殺:丑陋2-1止住2連敗 43歲少帥命硬

      風過鄉
      2026-03-07 06:31:03
      俄媒:蘇萊曼尼繼任者是內鬼,確認哈梅內伊位置,會沒開完就溜了

      俄媒:蘇萊曼尼繼任者是內鬼,確認哈梅內伊位置,會沒開完就溜了

      梁訊
      2026-03-07 04:17:49
      2026-03-07 13:39:00
      deephub incentive-icons
      deephub
      CV NLP和數據挖掘知識
      1940文章數 1456關注度
      往期回顧 全部

      科技要聞

      OpenClaw爆火,六位"養蝦人"自述與AI共生

      頭條要聞

      特朗普突然放話"先解決伊朗后解決古巴" 梅西聽懵了

      頭條要聞

      特朗普突然放話"先解決伊朗后解決古巴" 梅西聽懵了

      體育要聞

      塔圖姆歸來:凱爾特人的春之綠

      娛樂要聞

      周杰倫田馥甄的“JH戀” 被扒得底朝天

      財經要聞

      針對"不敢休、不讓休"怪圈 國家出手了

      汽車要聞

      逃離ICU,上汽通用“止血”企穩

      態度原創

      健康
      本地
      游戲
      房產
      公開課

      轉頭就暈的耳石癥,能開車上班嗎?

      本地新聞

      食味印象|一口入魂!康樂烤肉串起千年絲路香

      鍵鼠不是萬能的神!外媒盤點近年適合用手柄玩的游戲

      房產要聞

      傳統學區房熄火?2月??诙址勘鸬陌鍓K竟然是…

      公開課

      李玫瑾:為什么性格比能力更重要?

      無障礙瀏覽 進入關懷版