构建VisionRAG:使用Llama 3.2、Qdrant和LitServe实现AI图像搜索

2024年10月08日 由 alex 发表 23 0

VisionRAG结合了强大的 AI 工具,可提供无缝的基于图像的搜索体验。用户上传图像(如建筑图或 ER 模型)并进行查询。利用RunPod可扩展计算,Llama 3.2使用LitServe高级转换器提取见解,然后对响应进行矢量化。这些向量存储在 中Qdrant,用作由 Google Cloud 提供支持的可搜索知识库。该系统可实现高效的历史搜索,从而改变用户与图像数据和复杂视觉结构的交互方式。


1


架构:

VisionRAG 的架构旨在利用强大的人工智能功能进行基于图像的查询和检索。其核心是集成了 Llama 3.2 Vision-Instruct 模型,该模型通过 LitServe 托管在 Runpod 的云基础设施上,由双 L40s GPU、48GB GPU vRAM 和 100GB 存储器提供支持。这种设置确保了高计算效率和处理复杂图像输入的能力。用户可以发送各种类型的图像,如建筑蓝图或实体关系图,并提出与这些图像相关的特定查询。


收到图像后,Llama 模型会对其进行处理,以提取详细信息,并对用户的查询做出响应。该响应被矢量化,随后存储在矢量数据库 Qdrant 中,供未来参考。部署在谷歌云上的 Qdrant 是一个历史搜索引擎,允许用户高效地搜索以前处理过的数据。它在检索相关信息方面发挥着至关重要的作用,尤其是在用户可能需要重新访问或基于过去查询的场景中。


在本系统中,VisionRAG 不仅能处理基于图像的实时查询,还能建立一个不断增长的过去交互知识库,为用户提供即时响应和长期查询数据,供未来使用。这种架构优化了从图像提取到历史搜索的数据流,为用户使用可视数据创造了无缝体验。


实施过程


步骤 1:Steup Runpod 基础架构

1.选择 GPU Pod 配置:

  • 在第一张截图中,我们看到了 GPU 选择界面。Runpod 根据 vRAM、RAM 和 vCPU 提供不同的 GPU 选项。你可以根据所需的 vRAM 过滤 GPU,在本例中,vRAM 至少设置为 48 GB。
  • 可用的 GPU 选项包括 MI300X(AMD)、H100 SXM、RTX 6000 Ada、2x RTX 4090 等,每个选项都有各自的时价。
  • 对于高性能深度学习任务,推荐使用 H100 SXM。


2


2.选择部署选项:

  • 第二张截图显示了 GPU Pod 配置屏幕。
  • 你正在部署一个 RunPod PyTorch 2.1 模板,该模板针对使用 CUDA 11.8 的深度学习模型进行了优化。
  • 选择所需的 GPU 数量(从 1 到 8),还可以选择按需定价或选择节省计划(1 周、1 个月、3 个月)以获得折扣。还有一种现货定价选项,价格更便宜,但可能会中断。
  • 启用 SSH 终端访问或启动 Jupyter Notebook 等选项,即可开始使用你的环境。


3


3.设置模板重载:

  • 第三张截图显示的是模板覆盖部分,在这里你可以为 Pod 定制特定的部署设置。
  • 在这里,你可以更改容器镜像(本例中为 RunPod PyTorch 2.1),并指定临时和持久存储的容量大小。
  • 你还可以公开特定端口(如 8888、8000),以便通过网络访问 Jupyter Notebook 或其他应用程序。此外,你可能需要公开 SSH 端口 22 以实现安全访问。
  • 通过这些设置,你可以根据项目需要定制环境。


4


4.选择模板

  • 第四张截图显示了模板选择界面。
  • 选择适合你工作负载的 RunPod PyTorch 版本(如 PyTorch 2.1、2.4.0、2.2.0)。每个模板都预配置了机器学习和人工智能工作负载所需的库和工具。
  • 选定模板后,就可以通过配置 pod 所需的存储、端口和环境变量来继续部署。


5


步骤2:设置 Qdrant 云(免费层用于实验)

启动 Qdrant 集群的过程非常简单,只需导航至 qdrant.tech,点击 “Start Free(免费启动)”,或直接导航至 https://cloud.qdrant.io/,然后按照说明操作即可。这为高效的矢量搜索功能打开了大门。启动集群创建后,用户会看到一个简洁、直观的界面,引导他们完成设置过程。系统会自动提供一个群集,在本例中命名为 “Cluster0”,分配适中的资源,适合初步探索或小规模项目。


6


新推出的集群配备了0.5个vCPU、1GB内存和4GB磁盘空间,这是Qdrant免费提供的部分配置。这样的配置非常适合希望使用矢量数据库的开发人员或正在进行概念验证项目的开发人员。集群的健康状况一目了然,显眼的 “健康 ”状态让人放心。


7


深入了解集群的详细信息,可以发现大量的信息和管理选项。该集群运行于Qdrant 1.11.5版本,部署在us-east4地区,充分利用了谷歌云平台的强大基础设施。每个集群都有一个唯一的标识符和端点URL,这对于API访问和与应用程序集成至关重要。


Qdrant仪表板提供了集群性能和使用情况的全面可视性。可提供内存和 CPU 利用率的实时指标,使用户能够监控资源消耗,并计划潜在的扩展需求。虽然新集群通常显示的使用率极低,但随着数据的摄取和查询的执行,这些指标将变得非常宝贵。


对于那些渴望了解 Qdrant 部署内部运作情况的用户,日志部分提供了系统运行的详细情况。从启动过程到配置细节,这些日志都是故障排除和优化的宝贵资源。


8


在任何数据管理系统中,安全性都是最重要的问题,Qdrant通过其API密钥管理界面解决了这一问题。一开始,API密钥是不存在的,它会提示用户创建一个,这是确保集群访问安全的关键一步。该功能可确保只有经授权的应用程序才能与矢量数据库交互,从而维护数据的完整性和保密性。


9


步骤3:实现 LitServe API 服务器

本代码定义了一个自定义 API 服务 VisionLitAPI,用于根据输入图像和用户查询生成文本响应。该服务使用 litserve(一个用于创建轻量级推理 API 的框架)构建,并集成了 Hugging Face 的转换器和模型托管功能。实现的主要部分分为初始化、请求解码、模型预测和响应编码。


首先,导入必要的软件包,包括用于环境变量的 os、用于发出 HTTP 请求的 requests、用于在硬件加速设备上处理模型的 torch、用于构建 API 的 litserve、用于图像处理的 PIL,以及用于加载模型和处理器的 Hugging Face 变压器。环境变量使用 dotenv 加载,它能从环境中获取 Hugging Face 标记(HF_TOKEN)和模型名称(MODEL_NAME)等配置值。


VisionLitAPI 类继承自 ls.LitAPI,定义了核心 API 行为。在初始化过程中,它会使用存储在环境变量中的令牌登录 Hugging Face,并为处理器和模型设置占位符。设置方法会根据模型 ID 初始化模型(MllamaForConditionalGeneration)和图像文本处理器(AutoProcessor)。模型被加载到与自动硬件加速兼容的设备上。


decode_request 方法负责解释传入的 API 请求。它会从请求中检索图像 URL,并使用 PIL 加载图像。它还会创建一条包含用户查询的信息,并将其格式化为文本和图像内容,供模型处理。此外,它还会从请求中提取最大标记限制,以限制输出的生成。


预测方法执行实际推理。它使用处理器将输入图像和查询格式化为适合模型的格式,标记图像和文本输入,为模型推理做好准备。然后,模型生成一个响应,使用处理器的解码方法将其解码为人类可读的文本。


最后,encode_response 方法会将生成的输出封装成字典格式,以便于将响应返回给客户端。脚本的最后部分是实例化 VisionLitAPI,并在 8000 端口运行 LitServer,同时启用自动设备管理。该服务器将侦听传入的请求,并使用视觉语言模型提供预测服务。


import os
import requests
import torch
import litserve as ls
from PIL import Image
from transformers import MllamaForConditionalGeneration, AutoProcessor
from huggingface_hub import login
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())

class VisionLitAPI(ls.LitAPI):
    def __init__(self):
        login(token=os.environ.get('HF_TOKEN'))
        self.processor = None
        self.model = None
    def setup(self, device):
        model_id = os.environ.get('MODEL_NAME')
        self.model = MllamaForConditionalGeneration.from_pretrained(
            pretrained_model_name_or_path=model_id,
            torch_dtype=torch.bfloat16,
            device_map="auto",
        )
        self.processor = AutoProcessor.from_pretrained(pretrained_model_name_or_path=model_id)
    def decode_request(self, request):
        # "https://huggingface.co/datasets/huggingface/documentation-images/resolve/0052a70beed5bf71b92610a43a52df6d286cd5f3/diffusers/rabbit.jpg"
        url = request['image_url']
        image = Image.open(requests.get(url, stream=True).raw)
        # user_query: "If I had to write a haiku for this one, it would be: "
        messages = [
            {"role": "user", "content": [
                {"type": "image"},
                {"type": "text", "text": request['user_query']}
            ]}
        ]
        # max_tokens from request
        max_new_tokens = int(request['max_tokens'])
        return image, messages, max_new_tokens
    def predict(self, data):
        input_text = self.processor.apply_chat_template(data[1], add_generation_prompt=True)
        inputs = self.processor(
            data[0],
            input_text,
            add_special_tokens=False,
            return_tensors="pt"
        ).to(self.model.device)
        output = self.model.generate(**inputs, max_new_tokens=data[2])
        response = self.processor.decode(output[0])
        print(response)
        return response
    def encode_response(self, output):
        return {"output": output}

if __name__ == "__main__":
    api = VisionLitAPI()
    server = ls.LitServer(api, accelerator="auto")
    server.run(port=8000)


该代码使用基于 CLIP 的文本和图像嵌入实现了一个多模态搜索系统。它初始化了一个 MultiModalSearch 类,该类接受包含文本标题和图像路径的文档列表。该类使用 fastembed 库嵌入这些文档--对标题使用 CLIP 文本编码器,对图像使用 CLIP 图像编码器。这些嵌入内容使用 Qdrant 存储在矢量数据库中。


_create_and_insert 方法会检查 Qdrant 集合是否存在,并在必要时创建它。然后将文本和图像嵌入都插入到该集合中。该类还提供了两个搜索函数:search_image_by_text 通过嵌入查询和搜索图像嵌入信息,根据文本查询查找图像;search_text_by_image 通过嵌入图像和搜索文本嵌入信息,根据图像查询查找文本标题。


import os
from fastembed import TextEmbedding, ImageEmbedding
from qdrant_client import QdrantClient, models
from PIL import Image
from typing import List
from dotenv import load_dotenv, find_dotenv

class MultiModalSearch:
    def __init__(self, documents: List[dict]):
        _ = load_dotenv(find_dotenv())
        self.documents = documents
        self.collection_name = os.environ.get('COLLECTION_NAME')
        text_model_name = os.environ.get('TEXT_MODEL')  # CLIP text encoder
        self.text_model = TextEmbedding(model_name=text_model_name)
        self.text_embeddings_size = self.text_model._get_model_description(text_model_name)[
            "dim"]  # dimension of text embeddings, produced by CLIP text encoder (512)
        self.texts_embeded = list(
            self.text_model.embed(
                [document["caption"] for document in documents]))  # embedding captions with CLIP text encoder
        image_model_name = os.environ.get('IMAGE_MODEL')  # CLIP image encoder
        self.image_model = ImageEmbedding(model_name=image_model_name)
        self.image_embeddings_size = self.image_model._get_model_description(image_model_name)[
            "dim"]  # dimension of image embeddings, produced by CLIP image encoder (512)
        self.images_embeded = list(
            self.image_model.embed(
                [document["image"] for document in documents]))  # embedding images with CLIP image encoder
        self.client = QdrantClient(url=os.environ.get('DB_URL'), api_key=os.environ.get('DB_API_KEY'))
        self._create_and_insert()
    # this method will create the collection if dones not exist and inserts  the data into it
    def _create_and_insert(self):
        if not self.client.collection_exists(self.collection_name):  # creating a Collection
            self.client.create_collection(
                collection_name=self.collection_name,
                vectors_config={  # Named Vectors
                    "image": models.VectorParams(size=self.image_embeddings_size, distance=models.Distance.COSINE),
                    "text": models.VectorParams(size=self.text_embeddings_size, distance=models.Distance.COSINE),
                }
            )
        self.client.upload_points(
            collection_name=self.collection_name,
            points=[
                models.PointStruct(
                    id=idx,  # unique id of a point, pre-defined by the user
                    vector={
                        "text": self.texts_embeded[idx],  # embeded caption
                        "image": self.images_embeded[idx]  # embeded image
                    },
                    payload=doc  # original image and its caption
                )
                for idx, doc in enumerate(self.documents)
            ]
        )
    def search_image_by_text(self, user_query: str):
        find_image = self.text_model.embed(
            [
                "suggest an architecture for designing Vision RAG platform"])  # query, we embed it, so it also becomes a vector
        image_path = self.client.search(
            collection_name=self.collection_name,  # searching in our collection
            query_vector=("image", list(find_image)[0]),  # searching only among image vectors with our textual query
            with_payload=["image"],
            # user-readable information about search results, we are interested to see which image we will find
            limit=1  # top-1 similar to the query result
        )[0].payload['image']
        Image.open(image_path).show()
    def search_text_by_image(self, image_path: str):
        find_image = self.image_model.embed([image_path])  # embedding our image query
        response = self.client.search(
            collection_name=self.collection_name,
            query_vector=("text", list(find_image)[0]),
            # now we are searching only among text vectors with our image query
            with_payload=["caption"],
            # user-readable information about search results, we are interested to see which caption we will get
            limit=1
        )[0].payload['caption']
        return response


步骤4:部署和访问

将上述代码移植到 Runpod 实例中,并以 python server.py 运行代码


10


11


12


13


同时,数据也会被推送到 qdrant。这些数据将用于多模式搜索。


14


结论

总之,这项应用程序接口服务展示了将先进的视觉语言模型与 litserve 这样的轻量级推理框架相结合的威力。通过利用 Hugging Face 的预训练模型(如 MllamaForConditionalGeneration)并将其与图像处理器集成,开发人员可以构建可解释视觉和文本输入的强大多模态应用程序。云托管模型、环境驱动配置和硬件加速提供的灵活性确保了 API 可以轻松部署和扩展,以满足生产用例的需要。


文章来源:https://medium.com/towardsdev/building-visionrag-ai-powered-image-search-with-llama-3-2-qdrant-and-litserve-10bf22df5d41
欢迎关注ATYUN官方公众号
商务合作及内容投稿请联系邮箱:bd@atyun.com
评论 登录
热门职位
Maluuba
20000~40000/月
Cisco
25000~30000/月 深圳市
PilotAILabs
30000~60000/年 深圳市
写评论取消
回复取消