從開發到部署,搭建離線私有大模型知識庫

易迟發表於2024-05-19

背景介紹

最近一段時間搭建了一套完整的私有大模型知識庫,目前完整的服務已經完成測試部署上線。基本之前的實踐過程,從工程角度整理技術方案以及中間碰到的一些問題,方便後續對這個方向有需求的研發同學們。

為什麼做離線私有化部署

在大模型火熱起來之後,很多企業都有嘗試相關服務。但是實際會碰到大模型不瞭解公司個性化的情況,無法針對公司情況給出個性化回答。因此就出現了針對大模型的知識庫,透過提供公司內部的背景知識提升大模型個性化回答的能力。

但是這個方案馬上就碰到一個問題,大量公司的內部資訊被洩露。因此大部分公司會選擇使用私有化部署的模型,這樣就解決了資訊洩露問題,這就是私有化部署的企業知識庫。

部分企業資料更加敏感,基礎資料的服務需要離線在內部執行,因此需要搭建離線的私有化部署的企業知識庫。透過這一套服務,將企業內部散落的各種型別的文件知識組織起來,從而更好地為企業提供服務。

技術方案

目前大模型知識庫的構建一般是使用檢索增強生成 (RAG) 的技術方案,RAG 首先會檢索與問題相關的文件,之後會將檢索到的資訊提供給大模型(LLM),LLM 使用它們來生成更準確、更相關的響應。RAG 有兩個核心元件:

  • 檢索元件:負責從外部知識庫中檢索與使用者查詢相關的資訊。
  • 生成元件:負責生成最終響應。生成元件通常是 LLM,例如 GPT-3 或 ChatGLM 等;

RAG 的常規流程如下所示:

上面的流程圖中,第 1~14 步對應於資訊的檢索,而第 15 步對應於響應的生成。可以看到 RAG 最核心的是資訊檢索。下面主要關注 RAG 的檢索流程。

RAG 的資訊檢索主要包含兩個步驟:

  • 向量資料庫的構建,對應於上面的 1~7 步,透過對原始的檔案資訊進行預處理,方便後續快速檢索;
  • 使用者查詢流程,對應於上面的 8~15 步,透過使用者查詢獲取匹配的資訊,透過構造生成合適的 prompt,提供給大模型得到最終的結果;

向量資料庫的構建

向量資料庫的構建主要包含下面的步驟:

  1. 本地檔案的載入,實際中檔案的型別可能比較多樣化,需要根據檔案型別選擇對應的載入器。檔案載入需要去除無關的資訊,從而保證後續的高效處理;
  2. 檔案切分,單個檔案的內容可能會過大,考慮到大模型實際可用的輸入有限,一般會對載入的原始檔案執行切分,得到對應的檔案塊;
  3. 檔案塊向量化,為了方便後續根據使用者查詢匹配對應的檔案塊,因此會進行檔案塊的向量化,這樣就根據查詢資料的向量與檔案塊的向量之間的差異確定匹配的檔案塊;
  4. 向量庫的構建,基於向量化後的檔案塊構建向量資料庫,後續就可以在向量資料庫中進行查詢;

使用者檢索流程

使用者檢索的流程主要包含下面的核心步驟:

  1. 向量化查詢語句,從向量資料庫中獲取匹配的檔案塊;
  2. 將匹配的檔案塊與使用者的查詢語句合併構造為完整的 prompt;
  3. 基於 prompt 發起 LLM 查詢,獲得 LLM 響應;

上面的流程包含 RAG 的核心流程,如果希望提升 RAG 的效果,需要對流程中的特定環境進行最佳化。

方案落地

為了實現完整的 RAG 框架,常規的方案是基於 langchain 開發,langchain 提供了不同格式的檔案處理的封裝,而且可以快速切換不同的 LLM 從而迭代調優 RAG 系統的效果。

但是實際中我選擇了對 langchain 進行了二次封裝的 Langchain-Chatchat,完整看完 Langchain-Chatchat 的實現後,感覺此專案相對完善,適合作為開箱即用的 RAG 服務。

更友好的是,Langchain-Chatchat 提供了線上大模型 API 的接入,因此可以在沒有強大 GPU 伺服器的個人筆記本上執行起來,方便快速測試,當部署至效能更強大的 GPU 伺服器上,可以透過修改配置快速切換為私有化部署的大模型。

RAG 的實現需要接入向量化模型(Embedder model)與大語言模型(LLM),Langchain-Chatchat 推薦的是 bge-large-zhchatglm3-6b 組合。為了在本地環境快速啟動,我選擇的是百度提供 Embedding-V1 和 ERNIE-Bot 模型進行測試。

評估體系

實現了基礎版本的 RAG 服務後,如何迭代提升服務效果呢?

RAG 的響應是非結構化的文字,無法評估正確性。而且 RAG 涉及大量的環節,無法確定是什麼模組導致了最終效果不佳。調優的第一步就是先進行量化評估,然後才能在此基礎上進行迭代。

評估體系的構建可以參考吳恩達的課程 構建和評估高階的RAG模型應用,透過下面的三維度進行評估:

  1. Context Relevance: 上下文相關性,評估檢索獲得的內容與問題的相關性,透過這個指標可以確認檢索模組是否檢索到了相關的內容,如果當前指標評分較低,則需要考慮調優向量庫構建的環節;
  2. Groundedness: 立足性,答案是否是基於上下文推測出來的,如果評分較低,可以考慮調優 prompt 構造或大模型;
  3. Answer Relevance: 答案的相關性,評估系統是否答非所問,如果指標評分較低,則需要考慮調優大模型;

評估系統可以基於下面的開源庫實現:

  • ragas
  • trulens
  • phoenix
  • llamaindex

我實際選擇的是 ragas,可以在現有服務之外使用獨立的模組進行服務的評估,簡化後的實現如下所示:

# 將評估系統的問題 questions 與問題預期的答案 ground_truths 合併構造出評估資料集

async def batch_evaluate_chat(questions: list[str],
                              ground_truths: Optional[list] = None):
    tasks = [
        search_knowledge_base_iter(q, ground_truth)
        for q, ground_truth in zip(questions, ground_truths)
    ]
    results = await asyncio.gather(*tasks)

    question_list, answer_list, contexts_list, ground_truth_list = [], [], [], []
    for question, answer, contexts, ground_truth in results:
        question_list.append(question)
        answer_list.append(answer)
        contexts_list.append(contexts)
        ground_truth_list.append(ground_truth)

    data_samples = {
        'question': question_list,
        'answer': answer_list,
        'contexts': contexts_list,
    }

    if contain_data(ground_truth_list):
        data_samples["ground_truth"] = ground_truth_list

    return Dataset.from_dict(data_samples)

# 選擇指標 context_relevancy, faithfulness, answer_relevancy 進行評估

async def evaluate_data():
    dataset = await batch_evaluate_chat(QUESTIONS, GROUND_TRUTH)

    result = evaluate(
        dataset,
        metrics=[
            context_relevancy,
            faithfulness,
            answer_relevancy,
        ],
    )
    return result

透過使用上面的 evaluate_data() 可以預設的問題和答案用於評估系統的可靠性。

向量化調優

初始版本進行評估後實際效果一般,得分不佳主要在於 Context Relevance 指標得分較差,部分問題得分甚至低於 0.1,表明沒有獲取到正確內容。

調研後找到 huggingface 上的 向量化模型榜單,根據榜單選擇了表現良好而且相對輕量的模型庫 stella-base-zh-v3-1792d,基於此向量化模型進行了測試。

實際使用個人開發筆記本進行測試,載入模型沒有壓力,而且向量化模型推理的速度較快,看起來沒有 GPU 情況下推理模型的離線部署問題不大。切換後再次進行評估,同樣的問題 Context Relevance 得分提高為 0.6 甚至到達 0.8,看起來檢索的質量得到了明顯提高。從實際的答案來看,質量也得到了明顯提升。

離線部署

部分企業內部部署的服務是完全無法聯網的,只能透過跳板機進行部署。那麼如何將完整的 Python 執行環境部署就需要特殊設計了。

Python 虛擬環境部署

實際選擇 conda 進行虛擬環境的離線遷移,因為 conda 提供了 conda-pack 用於將本地建立的 Python 虛擬環境進行打包,之後就可以在離線環境下解壓縮正常使用。

打包虛擬環境時要特別注意,如果需要在 GPU 伺服器上執行,那麼安裝虛擬環境時需要安裝對應的版本 torch, torchvision, torchaudio 和 xformers。而 GPU 版本的虛擬環境安裝需要匹配伺服器的 cuda 與 python 版本,如果版本不匹配,很可能會導致無法正確執行。

GPU 版本的執行環境的安裝還是有點折磨人的,不過網上相關資訊已經很多了,這邊就不額外展開了。

缺失離線包安裝

在實際執行服務時,報錯 libGL.so.1 缺失,看起來是需要安裝 libgl1-mesa-dev,聯網環境下是相對容易解決的,可以直接 apt-get install 安裝,但是離線就很費勁了。初始情況下我考慮使用 apt-get download,直接下載之後透過 dpkg 安裝。

但是實際發現安裝此包時會出現間接依賴缺失,接下來繼續 apt-get download 安裝缺失的間接依賴,將間接依賴傳遞至伺服器進行安裝。發現間接依賴還存在間接依賴,而且缺失的間接依賴數量很多,手工下載看起來不可行。 是否存在一次性獲取libgl1-mesa-dev 所有間接的依賴的方案呢?

答案是 YES,可以使用 apt-rdepends,透過獲取所有的間接依賴然後批次下載,一次性傳輸至伺服器上,即可離線安裝,實踐測試正確的命令如下所示:

sudo apt-get install apt-rdepends
apt-rdepends libgl1-mesa-dev | grep -v "^ " > dependencies
cat dependencies | xargs -I {} apt-get download {}

將本地的所有 deb 檔案打包傳輸至伺服器,之後就可以在伺服器上可以透過下面的命令一次性安裝所有的依賴:

sudo dpkg -i *.deb

使用此命令順利安裝所需的依賴。

總結

本文是搭建離線私有大模型知識庫的一次完整實踐,整理了主要的實現方案以及離線部署中碰到的一些問題,記錄的版本還是一個初步驗證版本,知識庫的實踐還需要對中間環節進行大量調優,目前存在大量的論文進行這方面的研究,個人也在持續進行多輪的迭代調優,後續會持續整理和分享中間碰到的有價值的內容,歡迎持續關注。

相關文章