檢索增強生成(RAG)實踐:基於LlamaIndex和Qwen1.5搭建智慧問答系統

汀、人工智能發表於2024-05-07

檢索增強生成(RAG)實踐:基於LlamaIndex和Qwen1.5搭建智慧問答系統

  • 什麼是 RAG

LLM 會產生誤導性的 “幻覺”,依賴的資訊可能過時,處理特定知識時效率不高,缺乏專業領域的深度洞察,同時在推理能力上也有所欠缺。

正是在這樣的背景下,檢索增強生成技術(Retrieval-Augmented Generation,RAG)應時而生,成為 AI 時代的一大趨勢。RAG 透過在語言模型生成答案之前,先從廣泛的文件資料庫中檢索相關資訊,然後利用這些資訊來引導生成過程,極大地提升了內容的準確性和相關性。RAG 有效地緩解了幻覺問題,提高了知識更新的速度,並增強了內容生成的可追溯性,使得大型語言模型在實際應用中變得更加實用和可信

一個典型的 RAG 的例子:

這裡面主要包括包括三個基本步驟:

  1. 索引 — 將文件庫分割成較短的 Chunk,並透過編碼器構建向量索引。
  2. 檢索 — 根據問題和 chunks 的相似度檢索相關文件片段。
  3. 生成 — 以檢索到的上下文為條件,生成問題的回答。

1.通義千問Qwen 1.5

1.1 簡介

  • 官方連結:
    • https://github.com/QwenLM/Qwen1.5
    • https://github.com/QwenLM/Qwen/blob/main/README_CN.md

Qwen1.5 版本年前開源了包括 0.5B、1.8B、4B、7B、14B 和 72B 在內的六種大小的基礎和聊天模型,同時,也開源了量化模型。不僅提供了 Int4 和 Int8 的 GPTQ 模型,還有 AWQ 模型,以及 GGUF 量化模型。為了提升開發者體驗,Qwen1.5 的程式碼合併到 Hugging Face Transformers 中,開發者現在可以直接使用 transformers>=4.37.0 而無需 trust_remote_code。

與之前的版本相比,Qwen1.5 顯著提升了聊天模型與人類偏好的一致性,並且改善了它們的多語言能力。所有模型提供了統一的上下文長度支援,支援 32K 上下文。還有,基礎語言模型的質量也有所小幅改進

Qwen1.5 全系列統一具備強大的連結外部系統能力(agent/RAG/Tool-use/Code-interpreter)

正因為 Qwen1.5 作為中文 LLM 率先合入了 Transformers,我們也可以使用 LLaMaIndex 的原生 HuggingFaceLLM 來載入模型。

當前基礎模型已經穩定訓練了大規模高質量且多樣化的資料,覆蓋多語言(當前以中文和英文為主),總量高達3萬億token。在相關基準評測中,Qwen系列模型拿出非常有競爭力的表現,顯著超出同規模模型並緊追一系列最強的閉源模型。此外,我們利用SFT和RLHF技術實現對齊,從基座模型訓練得到對話模型。Qwen-Chat具備聊天、文字創作、摘要、資訊抽取、翻譯等能力,同時還具備一定的程式碼生成和簡單數學推理的能力。在此基礎上,我們針對LLM對接外部系統等方面針對性地做了最佳化,當前具備較強的工具呼叫能力,以及最近備受關注的Code Interpreter的能力和扮演Agent的能力。我們將各個大小模型的特點列到了下表。

模型 開源日期 最大上下文長度 System Prompt強化 預訓練token數 微調(Q-Lora)最小GPU用量 生成2048個token的最小視訊記憶體佔用 工具呼叫
Qwen-1.8B 23.11.30 32K 2.2T 5.8GB 2.9GB
Qwen-7B 23.08.03 32K 2.4T 11.5GB 8.2GB
Qwen-14B 23.09.25 8K 3.0T 18.7GB 13.0GB
Qwen-72B 23.11.30 32K 3.0T 61.4GB 48.9GB

Qwen系列模型相比同規模模型均實現了效果的顯著提升。我們評測的資料集包括MMLU、C-Eval、 GSM8K、 MATH、HumanEval、MBPP、BBH等資料集,考察的能力包括自然語言理解、知識、數學計算和推理、程式碼生成、邏輯推理等。Qwen-72B在所有任務上均超越了LLaMA2-70B的效能,同時在10項任務中的7項任務中超越GPT-3.5.

Model MMLU C-Eval GSM8K MATH HumanEval MBPP BBH CMMLU
5-shot 5-shot 8-shot 4-shot 0-shot 3-shot 3-shot 5-shot
LLaMA2-7B 46.8 32.5 16.7 3.3 12.8 20.8 38.2 31.8
LLaMA2-13B 55.0 41.4 29.6 5.0 18.9 30.3 45.6 38.4
LLaMA2-34B 62.6 - 42.2 6.2 22.6 33.0 44.1 -
ChatGLM2-6B 47.9 51.7 32.4 6.5 - - 33.7 -
InternLM-7B 51.0 53.4 31.2 6.3 10.4 14.0 37.0 51.8
InternLM-20B 62.1 58.8 52.6 7.9 25.6 35.6 52.5 59.0
Baichuan2-7B 54.7 56.3 24.6 5.6 18.3 24.2 41.6 57.1
Baichuan2-13B 59.5 59.0 52.8 10.1 17.1 30.2 49.0 62.0
Yi-34B 76.3 81.8 67.9 15.9 26.2 38.2 66.4 82.6
XVERSE-65B 70.8 68.6 60.3 - 26.3 - - -
Qwen-1.8B 45.3 56.1 32.3 2.3 15.2 14.2 22.3 52.1
Qwen-7B 58.2 63.5 51.7 11.6 29.9 31.6 45.0 62.2
Qwen-14B 66.3 72.1 61.3 24.8 32.3 40.8 53.4 71.0
Qwen-72B 77.4 83.3 78.9 35.2 35.4 52.2 67.7 83.6

1.2 推理效能

測算了BF16、Int8和Int4模型在生成2048個token時的平均推理速度(tokens/s)和視訊記憶體使用。結果如下所示:

Model Size Quantization Speed (Tokens/s) GPU Memory Usage
1.8B BF16 54.09 4.23GB
Int8 55.56 3.48GB
Int4 71.07 2.91GB
7B BF16 40.93 16.99GB
Int8 37.47 11.20GB
Int4 50.09 8.21GB
14B BF16 32.22 30.15GB
Int8 29.28 18.81GB
Int4 38.72 13.01GB
72B BF16 8.48 144.69GB (2xA100)
Int8 9.05 81.27GB (2xA100)
Int4 11.32 48.86GB
72B + vLLM BF16 17.60 2xA100

1.3 訓練需要配置

下面記錄7B和14B模型在單GPU使用LoRA(LoRA (emb)指的是embedding和輸出層參與訓練,而LoRA則不最佳化這部分引數)和QLoRA時處理不同長度輸入的視訊記憶體佔用和訓練速度的情況。本次評測執行於單張A100-SXM4-80G GPU,使用CUDA 11.8和Pytorch 2.0,並使用了flash attention 2。我們統一使用batch size為1,gradient accumulation為8的訓練配置,記錄輸入長度分別為256、512、1024、2048、4096和8192的視訊記憶體佔用(GB)和訓練速度(s/iter)。我們還使用2張A100測了Qwen-7B的全引數微調。受限於視訊記憶體大小,我們僅測試了256、512和1024token的效能。

對於 Qwen-7B,我們額外測試了多機微調的效能。我們在兩臺伺服器上執行評測,每臺伺服器包含兩張A100-SXM4-80G GPU,其餘配置與Qwen-7B的其他評測相同。多機微調的結果在表中以 LoRA (multinode) 標示。對於 Qwen-72B,我們測試了兩種方案:1)使用4個 A100-SXM4-80G GPUs,透過 Lora + DeepSpeed ZeRO 3 微調和2)使用單張A100-SXM4-80G GPU,透過 QLora (int4) 微調。請注意,使用 LoRA (emb) 微調和不帶 DeepSpeed ZeRO 3 的 LoRA 微調在4個A100-SXM4-80G GPUs 上都會出現OOM(你可以透過將--deepspeed finetune/ds_config_zero3.json引數傳給finetune/finetune_lora_ds.sh來開啟 DeepSpeed ZeRO 3 配置)。

具體數值如下所示:

Model Size Method #Nodes #GPUs per node Sequence Length
256 512 1024 2048 4096
1.8B LoRA 1 1 6.7G / 1.0s/it
LoRA (emb) 1 1 13.7G / 1.0s/it 14.0G / 1.0s/it
Q-LoRA 1 1 5.8G / 1.4s/it 6.0G / 1.4s/it
Full-parameter 1 1 43.5G / 2.1s/it 43.5G / 2.2s/it
7B LoRA 1 1 20.1G / 1.2s/it
LoRA (emb) 1 1 33.7G / 1.4s/it 34.1G / 1.6s/it
Q-LoRA 1 1 11.5G / 3.0s/it 11.5G / 3.0s/it
Full-parameter 1 2 139.2G / 4.0s/it 148.0G / 4.0s/it
LoRA (multinode) 2 2 74.7G / 2.09s/it 77.6G / 3.16s/it
14B LoRA 1 1 34.6G / 1.6s/it
LoRA (emb) 1 1 51.2 / 1.7s/it 51.1G / 2.6s/it
Q-LoRA 1 1 18.7G / 5.3s/it 18.4G / 6.3s/it
72B LoRA + Deepspeed Zero3 1 4 215.4G / 17.6s/it
Q-LoRA 1 1 61.4G / 27.4s/it 61.4G / 31.5s/it

2. LLaMaIndex

2.1 簡介

LlamaIndex 是一個基於 LLM 的應用程式的資料框架,受益於上下文增強。 這種 LLM 系統被稱為 RAG 系統,代表 “檢索增強生成”。LlamaIndex 提供了必要的抽象,可以更輕鬆地攝取、構建和訪問私有或特定領域的資料,以便將這些資料安全可靠地注入 LLM 中,以實現更準確的文字生成。

  • 官方連結

    • https://docs.llamaindex.ai/en/stable/

    • https://www.llamaindex.ai/

首先,它有助於“攝取”資料,這意味著將資料從原始來源獲取到系統中。其次,它有助於“結構化”資料,這意味著以語言模型易於理解的方式組織資料。第三,它有助於“檢索”,這意味著在需要時查詢和獲取正確的資料。最後,它簡化了“整合”,使您更容易將資料與各種應用程式框架融合在一起。

  • LllamaIndex 以專用索引的形式提供獨特的資料結構:
    • 向量儲存索引:最常用,允許您回答對大型資料集的查詢。
    • 樹索引:對於總結文件集合很有用。
    • 列表索引:對於合成一個結合了多個資料來源資訊的答案很有用。
    • 關鍵字表索引:用於將查詢路由到不同的資料來源。
    • 結構化儲存索引:對於結構化資料(例如 SQL 查詢)很有用。
    • 知識圖譜索引:對於構建知識圖譜很有用。

LlamaIndex 有用性的核心是其有助於構建 LLM 應用程式的功能和工具。在這裡,我們詳細討論它們:

  • 資料聯結器

LlamaIndex 提供資料聯結器,可以提取您現有的資料來源和格式。無論是 API、PDF、文件還是 SQL 資料庫,LlamaIndex 都可以與它們無縫整合,為您的 LLM 準備資料。

  • 資料結構

使用 LLM 的主要挑戰之一是以易於使用的方式構建資料。LlamaIndex 提供了在索引或圖表中構建資料的工具。

  • 高階檢索/查詢介面

LlamaIndex 不僅僅是攝取和構建資料。它還為您的資料提供高階檢索或查詢介面。只需輸入任何 LLM 輸入提示,LlamaIndex 將返回檢索到的上下文和知識增強輸出。

  • 與其他框架整合

LlamaIndex 允許與您的外部應用程式框架輕鬆整合。您可以將它與 LangChain、Flask、Docker、ChatGPT 以及您的專案可能需要的任何其他工具一起使用。

  • 高階和低階 API

無論您的熟練程度如何,LlamaIndex 都能滿足您的需求。初學者使用者會喜歡高階 API,它允許使用 LlamaIndex 以僅五行程式碼來攝取和查詢他們的資料。另一方面,高階使用者可以根據需要利用較低階別的 API 自定義和擴充套件任何模組(資料聯結器、索引、檢索器、查詢引擎、重新排名模組)。

2.2 快速使用

  • 使用 pip 安裝 LlamaIndex 非常簡單:
pip install llama-index
  • 如何構建向量儲存索引並查詢它的簡單示例:
import os
os.environ["OPENAI_API_KEY"] = 'YOUR_OPENAI_API_KEY'
 
from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader
documents = SimpleDirectoryReader('data').load_data()
index = GPTVectorStoreIndex.from_documents(documents)
 
#o query:
query_engine = index.as_query_engine()
query_engine.query("<question_text>?")
 
#By default, data is stored in-memory. To persist to disk (under ./storage):
index.storage_context.persist()
 
#To reload from disk:
from llama_index import StorageContext, load_index
 
_from_storage
#rebuild storage context
storage_context = StorageContext.from_defaults(persist_dir='./storage')
#load index
index = load_index_from_storage(storage_context)

LlamaIndex 不僅僅是一個資料框架;它是更大的工具和資源生態系統的一部分:
LlamaHub:資料載入器的社群庫。
LlamaLab:使用 LlamaIndex 的尖端 AGI 專案平臺。

3.GTE 文字向量

3.1 簡介

  • 模型連結:
    • https://www.modelscope.cn/models?name=GTE-zh
    • https://www.modelscope.cn/models/iic/nlp_gte_sentence-embedding_chinese-base/summary

文字表示是自然語言處理 (NLP) 領域的核心問題, 其在很多 NLP、資訊檢索的下游任務中發揮著非常重要的作用。近幾年, 隨著深度學習的發展,尤其是預訓練語言模型的出現極大的推動了文字表示技術的效果, 基於預訓練語言模型的文字表示模型在學術研究資料、工業實際應用中都明顯優於傳統的基於統計模型或者淺層神經網路的文字表示模型。這裡, 我們主要關注基於預訓練語言模型的文字表示。

文字表示示例, 輸入一個句子, 輸入一個固定維度的連續向量:

  • 輸入: 吃完海鮮可以喝牛奶嗎?
  • 輸出: [0.27162,-0.66159,0.33031,0.24121,0.46122,...]

文字的向量表示通常可以用於文字聚類、文字相似度計算、文字向量召回等下游任務中。

基於監督資料訓練的文字表示模型通常採用 Dual Encoder 框架, 如下圖所示。在 Dual Encoder 框架中, Query 和 Document 文字透過預訓練語言模型編碼後, 通常採用預訓練語言模型 [CLS] 位置的向量作為最終的文字向量表示。基於標註資料的標籤, 透過計算 query-document 之間的 cosine 距離度量兩者之間的相關性。

  • GTE-zh 模型使用 retromae 初始化訓練模型,之後利用兩階段訓練方法訓練模型:
    • 第一階段利用大規模弱弱監督文字對資料訓練模型,
    • 第二階段利用高質量精標文字對資料以及挖掘的難負樣本資料訓練模型。

具體訓練方法請參考論文 Towards General Text Embeddings with Multi-stage Contrastive Learning

  • 使用方式:直接推理,對給定文字計算其對應的文字向量表示,向量維度 768

  • 使用範圍:本模型可以使用在通用領域的文字向量表示及其下游應用場景, 包括雙句文字相似度計算、query & 多 doc 候選的相似度排序

在 ModelScope 框架上,提供輸入文字 (預設最長文字長度為 128),即可以透過簡單的 Pipeline 呼叫來使用 GTE 文字向量表示模型。ModelScope 封裝了統一的介面對外提供單句向量表示、雙句文字相似度、多候選相似度計算功能

3.2 程式碼示例

from modelscope.models import Model
from modelscope.pipelines import pipeline
from modelscope.utils.constant import Tasks

model_id = "iic/nlp_gte_sentence-embedding_chinese-base"
pipeline_se = pipeline(Tasks.sentence_embedding,
                       model=model_id,
                       sequence_length=512
                       ) # sequence_length 代表最大文字長度,預設值為128

#當輸入包含“soure_sentence”與“sentences_to_compare”時,會輸出source_sentence中首個句子與sentences_to_compare中每個句子的向量表示,以及source_sentence中首個句子與sentences_to_compare中每個句子的相似度。
inputs = {
        "source_sentence": ["吃完海鮮可以喝牛奶嗎?"],
        "sentences_to_compare": [
            "不可以,早晨喝牛奶不科學",
            "吃了海鮮後是不能再喝牛奶的,因為牛奶中含得有維生素C,如果海鮮喝牛奶一起服用會對人體造成一定的傷害",
            "吃海鮮是不能同時喝牛奶吃水果,這個至少間隔6小時以上才可以。",
            "吃海鮮是不可以吃檸檬的因為其中的維生素C會和海鮮中的礦物質形成砷"
        ]
    }

result = pipeline_se(input=inputs)
print (result)
'''
{'text_embedding': array([[ 1.6415151e-04,  2.2334497e-02, -2.4202393e-02, ...,
         2.7710509e-02,  2.5980933e-02, -3.1285528e-02],
       [-9.9107623e-03,  1.3627578e-03, -2.1072682e-02, ...,
         2.6786461e-02,  3.5029035e-03, -1.5877936e-02],
       [ 1.9877627e-03,  2.2191243e-02, -2.7656069e-02, ...,
         2.2540951e-02,  2.1780970e-02, -3.0861111e-02],
       [ 3.8688166e-05,  1.3409532e-02, -2.9691193e-02, ...,
         2.9900728e-02,  2.1570563e-02, -2.0719109e-02],
       [ 1.4484422e-03,  8.5943500e-03, -1.6661938e-02, ...,
         2.0832840e-02,  2.3828523e-02, -1.1581291e-02]], dtype=float32), 'scores': [0.8859604597091675, 0.9830712080001831, 0.966042160987854, 0.891857922077179]}
'''


#當輸入僅含有soure_sentence時,會輸出source_sentence中每個句子的向量表示。
inputs2 = {
        "source_sentence": [
            "不可以,早晨喝牛奶不科學",
            "吃了海鮮後是不能再喝牛奶的,因為牛奶中含得有維生素C,如果海鮮喝牛奶一起服用會對人體造成一定的傷害",
            "吃海鮮是不能同時喝牛奶吃水果,這個至少間隔6小時以上才可以。",
            "吃海鮮是不可以吃檸檬的因為其中的維生素C會和海鮮中的礦物質形成砷"
        ]
}
result = pipeline_se(input=inputs2)
print (result)
'''
{'text_embedding': array([[-9.9107623e-03,  1.3627578e-03, -2.1072682e-02, ...,
         2.6786461e-02,  3.5029035e-03, -1.5877936e-02],
       [ 1.9877627e-03,  2.2191243e-02, -2.7656069e-02, ...,
         2.2540951e-02,  2.1780970e-02, -3.0861111e-02],
       [ 3.8688166e-05,  1.3409532e-02, -2.9691193e-02, ...,
         2.9900728e-02,  2.1570563e-02, -2.0719109e-02],
       [ 1.4484422e-03,  8.5943500e-03, -1.6661938e-02, ...,
         2.0832840e-02,  2.3828523e-02, -1.1581291e-02]], dtype=float32), 'scores': []}
'''

預設向量維度 768, scores 中的 score 計算兩個向量之間的內成積距離得到

  • 訓練示例程式碼
#需在GPU環境執行
#載入資料集過程可能由於網路原因失敗,請嘗試重新執行程式碼
from modelscope.metainfo import Trainers                                                                                                                                                              
from modelscope.msdatasets import MsDataset
from modelscope.trainers import build_trainer
import tempfile
import os

tmp_dir = tempfile.TemporaryDirectory().name
if not os.path.exists(tmp_dir):
    os.makedirs(tmp_dir)

#load dataset
ds = MsDataset.load('dureader-retrieval-ranking', 'zyznull')
train_ds = ds['train'].to_hf_dataset()
dev_ds = ds['dev'].to_hf_dataset()
model_id = 'iic/nlp_gte_sentence-embedding_chinese-base'
def cfg_modify_fn(cfg):
    cfg.task = 'sentence-embedding'
    cfg['preprocessor'] = {'type': 'sentence-embedding','max_length': 256}
    cfg['dataset'] = {
        'train': {
            'type': 'bert',
            'query_sequence': 'query',
            'pos_sequence': 'positive_passages',
            'neg_sequence': 'negative_passages',
            'text_fileds': ['text'],
            'qid_field': 'query_id'
        },
        'val': {
            'type': 'bert',
            'query_sequence': 'query',
            'pos_sequence': 'positive_passages',
            'neg_sequence': 'negative_passages',
            'text_fileds': ['text'],
            'qid_field': 'query_id'
        },
    }
    cfg['train']['neg_samples'] = 4
    cfg['evaluation']['dataloader']['batch_size_per_gpu'] = 30
    cfg.train.max_epochs = 1
    cfg.train.train_batch_size = 4
    return cfg 
kwargs = dict(
    model=model_id,
    train_dataset=train_ds,
    work_dir=tmp_dir,
    eval_dataset=dev_ds,
    cfg_modify_fn=cfg_modify_fn)
trainer = build_trainer(name=Trainers.nlp_sentence_embedding_trainer, default_args=kwargs)
trainer.train()

3.3 模型效果評估

  • 中文多工向量評測榜單C-MTEB結果如下:
Model Size Method #Nodes #GPUs per node Sequence Length
256 512 1024 2048 4096
1.8B LoRA 1 1 6.7G / 1.0s/it
LoRA (emb) 1 1 13.7G / 1.0s/it 14.0G / 1.0s/it
Q-LoRA 1 1 5.8G / 1.4s/it 6.0G / 1.4s/it
Full-parameter 1 1 43.5G / 2.1s/it 43.5G / 2.2s/it
7B LoRA 1 1 20.1G / 1.2s/it
LoRA (emb) 1 1 33.7G / 1.4s/it 34.1G / 1.6s/it
Q-LoRA 1 1 11.5G / 3.0s/it 11.5G / 3.0s/it
Full-parameter 1 2 139.2G / 4.0s/it 148.0G / 4.0s/it
LoRA (multinode) 2 2 74.7G / 2.09s/it 77.6G / 3.16s/it
14B LoRA 1 1 34.6G / 1.6s/it
LoRA (emb) 1 1 51.2 / 1.7s/it 51.1G / 2.6s/it
Q-LoRA 1 1 18.7G / 5.3s/it 18.4G / 6.3s/it
72B LoRA + Deepspeed Zero3 1 4 215.4G / 17.6s/it
Q-LoRA 1 1 61.4G / 27.4s/it 61.4G / 31.5s/it
  • 英文多工向量評測榜單MTEB結果如下:
Model Name Model Size (GB) Dimension Sequence Length Average (56) Clustering (11) Pair Classification (3) Reranking (4) Retrieval (15) STS (10) Summarization (1) Classification (12)
gte-large 0.67 1024 512 63.13 46.84 85.00 59.13 52.22 83.35 31.66 73.33
gte-base 0.22 768 512 62.39 46.2 84.57 58.61 51.14 82.3 31.17 73.01
e5-large-v2 1.34 1024 512 62.25 44.49 86.03 56.61 50.56 82.05 30.19 75.24
e5-base-v2 0.44 768 512 61.5 43.80 85.73 55.91 50.29 81.05 30.28 73.84
gte-small 0.07 384 512 61.36 44.89 83.54 57.7 49.46 82.07 30.42 72.31
text-embedding-ada-002 - 1536 8192 60.99 45.9 84.89 56.32 49.25 80.97 30.8 70.93
e5-small-v2 0.13 384 512 59.93 39.92 84.67 54.32 49.04 80.39 31.16 72.94
sentence-t5-xxl 9.73 768 512 59.51 43.72 85.06 56.42 42.24 82.63 30.08 73.42
all-mpnet-base-v2 0.44 768 514 57.78 43.69 83.04 59.36 43.81 80.28 27.49 65.07
sgpt-bloom-7b1-msmarco 28.27 4096 2048 57.59 38.93 81.9 55.65 48.22 77.74 33.6 66.19
all-MiniLM-L12-v2 0.13 384 512 56.53 41.81 82.41 58.44 42.69 79.8 27.9 63.21
all-MiniLM-L6-v2 0.09 384 512 56.26 42.35 82.37 58.04 41.95 78.9 30.81 63.05
contriever-base-msmarco 0.44 768 512 56.00 41.1 82.54 53.14 41.88 76.51 30.36 66.68
sentence-t5-base 0.22 768 512 55.27 40.21 85.18 53.09 33.63 81.14 31.39 69.81

4.魔搭社群最佳實踐

4.1 逐步解析

  • 環境配置與安裝

    1. python 3.10 及以上版本
    2. pytorch 1.12 及以上版本,推薦 2.0 及以上版本
    3. 建議使用 CUDA 11.4 及以上本文主要演示的模型推理程式碼可在魔搭社群免費例項 PAI-DSW 的配置下執行(視訊記憶體 24G) :
  • 第一步:點選模型右側 Notebook 快速開發按鈕,選擇 GPU 環境

  • 第二步:新建 Notebook

安裝依賴庫1

!pip install llama-index llama-index-llms-huggingface ipywidgets
!pip install transformers -U
import logging
import sys

logging.basicConfig(stream=sys.stdout, level=logging.INFO)
logging.getLogger().addHandler(logging.StreamHandler(stream=sys.stdout))


from IPython.display import Markdown, display
import torch
from llama_index.llms.huggingface import HuggingFaceLLM
from llama_index.core.prompts import PromptTemplate
from modelscope import snapshot_download
from llama_index.core.base.embeddings.base import BaseEmbedding, Embedding
from abc import ABC
from typing import Any, List, Optional, Dict, cast
from llama_index.core import (
    VectorStoreIndex,
    ServiceContext,
    set_global_service_context,
    SimpleDirectoryReader,
)
  • 載入大語言模型

因為 Qwen 本次支援了 Transformers,使用 HuggingFaceLLM 載入模型,模型為(Qwen1.5-4B-Chat)1

#Model names 
qwen2_4B_CHAT = "qwen/Qwen1.5-4B-Chat"

selected_model = snapshot_download(qwen2_4B_CHAT)

SYSTEM_PROMPT = """You are a helpful AI assistant.
"""

query_wrapper_prompt = PromptTemplate(
    "[INST]<<SYS>>\n" + SYSTEM_PROMPT + "<</SYS>>\n\n{query_str}[/INST] "
)

llm = HuggingFaceLLM(
    context_window=4096,
    max_new_tokens=2048,
    generate_kwargs={"temperature": 0.0, "do_sample": False},
    query_wrapper_prompt=query_wrapper_prompt,
    tokenizer_name=selected_model,
    model_name=selected_model,
    device_map="auto",
    # change these settings below depending on your GPU
    model_kwargs={"torch_dtype": torch.float16},
)
  • 載入資料:匯入測試資料
!mkdir -p 'data/xianjiaoda/'
!wget 'https://modelscope.oss-cn-beijing.aliyuncs.com/resource/rag/xianjiaoda.md' -O 'data/xianjiaoda/xianjiaoda.md'
documents = SimpleDirectoryReader("/mnt/workspace/data/xianjiaoda/").load_data()
documents
  • 構建 Embedding 類

載入 GTE 模型,使用 GTE 模型構造 Embedding 類

embedding_model = "iic/nlp_gte_sentence-embedding_chinese-base"
class ModelScopeEmbeddings4LlamaIndex(BaseEmbedding, ABC):
    embed: Any = None
    model_id: str = "iic/nlp_gte_sentence-embedding_chinese-base"

    def __init__(
            self,
            model_id: str,
            **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        try:
            from modelscope.models import Model
            from modelscope.pipelines import pipeline
            from modelscope.utils.constant import Tasks
            # 使用modelscope的embedding模型(包含下載)
            self.embed = pipeline(Tasks.sentence_embedding, model=self.model_id)

        except ImportError as e:
            raise ValueError(
                "Could not import some python packages." "Please install it with `pip install modelscope`."
            ) from e

    def _get_query_embedding(self, query: str) -> List[float]:
        text = query.replace("\n", " ")
        inputs = {"source_sentence": [text]}
        return self.embed(input=inputs)['text_embedding'][0].tolist()

    def _get_text_embedding(self, text: str) -> List[float]:
        text = text.replace("\n", " ")
        inputs = {"source_sentence": [text]}
        return self.embed(input=inputs)['text_embedding'][0].tolist()

    def _get_text_embeddings(self, texts: List[str]) -> List[List[float]]:
        texts = list(map(lambda x: x.replace("\n", " "), texts))
        inputs = {"source_sentence": texts}
        return self.embed(input=inputs)['text_embedding'].tolist()

    async def _aget_query_embedding(self, query: str) -> List[float]:
        return self._get_query_embedding(query)

  • 建設索引

載入資料後,基於文件物件列表(或節點列表),建設他們的 index,就可以方便的檢索他們。1

embeddings = ModelScopeEmbeddings4LlamaIndex(model_id=embedding_model)
service_context = ServiceContext.from_defaults(embed_model=embeddings, llm=llm)
set_global_service_context(service_context)

index = VectorStoreIndex.from_documents(documents)
  • 查詢和問答

搭建基於本地知識庫的問答引擎1


query_engine = index.as_query_engine()
response = query_engine.query("西安交大是由哪幾個學校合併的?")
print(response)

4.2 完整程式碼

!pip install pypdf langchain unstructured transformers_stream_generator
!pip install modelscope  nltk pydantic  tiktoken  llama-index
!wget https://modelscope.oss-cn-beijing.aliyuncs.com/resource/rag/averaged_perceptron_tagger.zip
!wget https://modelscope.oss-cn-beijing.aliyuncs.com/resource/rag/punkt.zip
!wget https://modelscope.oss-cn-beijing.aliyuncs.com/resource/rag/xianjiaoda.md

!mkdir -p /root/nltk_data/tokenizers
!mkdir -p /root/nltk_data/taggers
!cp /mnt/workspace/punkt.zip /root/nltk_data/tokenizers
!cp /mnt/workspace/averaged_perceptron_tagger.zip /root/nltk_data/taggers
!cd /root/nltk_data/tokenizers; unzip punkt.zip;
!cd /root/nltk_data/taggers; unzip averaged_perceptron_tagger.zip;

!mkdir -p /mnt/workspace/custom_data
!mv /mnt/workspace/xianjiaoda.md /mnt/workspace/custom_data

!cd /mnt/workspace
import os
from abc import ABC
from typing import Any, List, Optional, Dict, cast

import torch
from langchain_core.language_models.llms import LLM
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from modelscope import AutoModelForCausalLM, AutoTokenizer
from llama_index import GPTVectorStoreIndex, SimpleDirectoryReader
from llama_index import ServiceContext
from llama_index.embeddings.base import BaseEmbedding
from llama_index import set_global_service_context
from langchain_core.retrievers import BaseRetriever
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from llama_index.retrievers import VectorIndexRetriever

# configs for LLM
llm_name = "Qwen/Qwen-1_8B-Chat"
llm_revision = "master"

# configs for embedding model
embedding_model = "damo/nlp_gte_sentence-embedding_chinese-small"

# file path for your custom knowledge base
knowledge_doc_file_dir = "/mnt/workspace/custom_data/"
knowledge_doc_file_path = knowledge_doc_file_dir + "xianjiaoda.md"


# define our Embedding class to use models in Modelscope
class ModelScopeEmbeddings4LlamaIndex(BaseEmbedding, ABC):
    embed: Any = None
    model_id: str = "damo/nlp_gte_sentence-embedding_chinese-small"

    def __init__(
            self,
            model_id: str,
            **kwargs: Any,
    ) -> None:
        super().__init__(**kwargs)
        try:
            from modelscope.models import Model
            from modelscope.pipelines import pipeline
            from modelscope.utils.constant import Tasks
            self.embed = pipeline(Tasks.sentence_embedding, model=self.model_id)

        except ImportError as e:
            raise ValueError(
                "Could not import some python packages." "Please install it with `pip install modelscope`."
            ) from e

    def _get_query_embedding(self, query: str) -> List[float]:
        text = query.replace("\n", " ")
        inputs = {"source_sentence": [text]}
        return self.embed(input=inputs)['text_embedding'][0]

    def _get_text_embedding(self, text: str) -> List[float]:
        text = text.replace("\n", " ")
        inputs = {"source_sentence": [text]}
        return self.embed(input=inputs)['text_embedding'][0]

    def _get_text_embeddings(self, texts: List[str]) -> List[List[float]]:
        texts = list(map(lambda x: x.replace("\n", " "), texts))
        inputs = {"source_sentence": texts}
        return self.embed(input=inputs)['text_embedding']

    async def _aget_query_embedding(self, query: str) -> List[float]:
        return self._get_query_embedding(query)


# define our Retriever with llama-index to co-operate with Langchain
# note that the 'LlamaIndexRetriever' defined in langchain-community.retrievers.llama_index.py
# is no longer compatible with llamaIndex code right now.
class LlamaIndexRetriever(BaseRetriever):
    index: Any
    """LlamaIndex index to query."""

    def _get_relevant_documents(
        self, query: str, *, run_manager: CallbackManagerForRetrieverRun
    ) -> List[Document]:
        """Get documents relevant for a query."""
        try:
            from llama_index.indices.base import BaseIndex
            from llama_index.response.schema import Response
        except ImportError:
            raise ImportError(
                "You need to install `pip install llama-index` to use this retriever."
            )
        index = cast(BaseIndex, self.index)
        print('@@@ query=', query)

        response = index.as_query_engine().query(query)
        response = cast(Response, response)
        # parse source nodes
        docs = []
        for source_node in response.source_nodes:
            print('@@@@ source=', source_node)
            metadata = source_node.metadata or {}
            docs.append(
                Document(page_content=source_node.get_text(), metadata=metadata)
            )
        return docs

def torch_gc():
    os.environ["TOKENIZERS_PARALLELISM"] = "false"
    DEVICE = "cuda"
    DEVICE_ID = "0"
    CUDA_DEVICE = f"{DEVICE}:{DEVICE_ID}" if DEVICE_ID else DEVICE
    a = torch.Tensor([1, 2])
    a = a.cuda()
    print(a)

    if torch.cuda.is_available():
        with torch.cuda.device(CUDA_DEVICE):
            torch.cuda.empty_cache()
            torch.cuda.ipc_collect()


# global resources used by QianWenChatLLM (this is not a good practice)
tokenizer = AutoTokenizer.from_pretrained(llm_name, revision=llm_revision, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(llm_name, revision=llm_revision, device_map="auto",
                                             trust_remote_code=True, fp16=True).eval()


# define QianWen LLM based on langchain's LLM to use models in Modelscope
class QianWenChatLLM(LLM):
    max_length = 10000
    temperature: float = 0.01
    top_p = 0.9

    def __init__(self):
        super().__init__()

    @property
    def _llm_type(self):
        return "ChatLLM"

    def _call(
            self,
            prompt: str,
            stop: Optional[List[str]] = None,
            run_manager=None,
            **kwargs: Any,
    ) -> str:
        print(prompt)
        response, history = model.chat(tokenizer, prompt, history=None)
        torch_gc()
        return response


# STEP1: create LLM instance
qwllm = QianWenChatLLM()
print('STEP1: qianwen LLM created')

# STEP2: load knowledge file and initialize vector db by llamaIndex
print('STEP2: reading docs ...')
embeddings = ModelScopeEmbeddings4LlamaIndex(model_id=embedding_model)
service_context = ServiceContext.from_defaults(embed_model=embeddings, llm=None)
set_global_service_context(service_context)     # global config, not good

llamaIndex_docs = SimpleDirectoryReader(knowledge_doc_file_dir).load_data()
llamaIndex_index = GPTVectorStoreIndex.from_documents(llamaIndex_docs, chunk_size=512)
retriever = LlamaIndexRetriever(index=llamaIndex_index)
print(' 2.2 reading doc done, vec db created.')

# STEP3: create chat template
prompt_template = """請基於內的內容回答問題。"{context}
我的問題是:{question}。
"""
prompt = ChatPromptTemplate.from_template(template=prompt_template)
print('STEP3: chat prompt template created.')

# STEP4: create RAG chain to do QA
chain = (
        {"context": retriever, "question": RunnablePassthrough()}
        | prompt
        | qwllm
        | StrOutputParser()
)
chain.invoke('西安交大的校訓是什麼?')
# chain.invoke('魔搭社群有哪些模型?')
# chain.invoke('modelscope是什麼?')
# chain.invoke('蕭峰和喬峰是什麼關係?')

更多優質內容請關注公號:汀丶人工智慧;會提供一些相關的資源和優質文章,免費獲取閱讀。

  • 參考連結
    https://github.com/modelscope/modelscope/tree/master/examples/pytorch/application

相關文章