”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 使用 OpenVINO 和 Postgres 构建快速高效的语义搜索系统

使用 OpenVINO 和 Postgres 构建快速高效的语义搜索系统

发布于2024-11-07
浏览:430

Building a Fast and Efficient Semantic Search System Using OpenVINO and Postgres

照片由 real-napster 在 Pixabay上拍摄

在我最近的一个项目中,我必须构建一个语义搜索系统,该系统可以高性能扩展并为报告搜索提供实时响应。我们在 AWS RDS 上使用 PostgreSQL 和 pgvector,并搭配 AWS Lambda 来实现这一目标。面临的挑战是允许用户使用自然语言查询而不是依赖严格的关键字进行搜索,同时确保响应时间在 1-2 秒甚至更短,并且只能利用 CPU 资源。

在这篇文章中,我将逐步介绍构建此搜索系统的步骤,从检索到重新排名,以及使用 OpenVINO 和智能批处理进行标记化进行的优化。

语义搜索概述:检索和重新排序

现代最先进的搜索系统通常由两个主要步骤组成:检索重新排名

1) 检索:第一步涉及根据用户查询检索相关文档的子集。这可以使用预先训练的嵌入模型来完成,例如 OpenAI 的小型和大型嵌入、Cohere 的嵌入模型或 Mixbread 的 mxbai 嵌入。检索的重点是通过测量文档与查询的相似性来缩小文档池的范围。

这是一个使用 Huggingface 的句子转换器库进行检索的简化示例,这是我最喜欢的库之一:

from sentence_transformers import SentenceTransformer
import numpy as np

# Load a pre-trained sentence transformer model
model = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")

# Sample query and documents (vectorize the query and the documents)
query = "How do I fix a broken landing gear?"
documents = ["Report 1 on landing gear failure", "Report 2 on engine problems"]

# Get embeddings for query and documents
query_embedding = model.encode(query)
document_embeddings = model.encode(documents)

# Calculate cosine similarity between query and documents
similarities = np.dot(document_embeddings, query_embedding)

# Retrieve top-k most relevant documents
top_k = np.argsort(similarities)[-5:]
print("Top 5 documents:", [documents[i] for i in top_k])

2) 重新排名:检索到最相关的文档后,我们使用交叉编码器模型进一步提高这些文档的排名。此步骤会更准确地重新评估与查询相关的每个文档,重点关注更深入的上下文理解。
重新排名是有益的,因为它通过更精确地评​​分每个文档的相关性来增加额外的细化层。

下面是使用 cross-encoder/ms-marco-TinyBERT-L-2-v2(轻量级交叉编码器)进行重新排名的代码示例:

from sentence_transformers import CrossEncoder

# Load the cross-encoder model
cross_encoder = CrossEncoder("cross-encoder/ms-marco-TinyBERT-L-2-v2")

# Use the cross-encoder to rerank top-k retrieved documents
query_document_pairs = [(query, doc) for doc in documents]
scores = cross_encoder.predict(query_document_pairs)

# Rank documents based on the new scores
top_k_reranked = np.argsort(scores)[-5:]
print("Top 5 reranked documents:", [documents[i] for i in top_k_reranked])

识别瓶颈:标记化和预测的成本

在开发过程中,我发现在使用句子转换器的默认设置处理 1,000 个报告时,标记化和预测阶段花费了相当长的时间。这造成了性能瓶颈,特别是因为我们的目标是实时响应。

下面我使用 SnakeViz 分析了我的代码以可视化性能:

Building a Fast and Efficient Semantic Search System Using OpenVINO and Postgres

正如您所看到的,标记化和预测步骤异常缓慢,导致提供搜索结果的严重延迟。总的来说,平均需要 4-5 秒。这是因为标记化和预测步骤之间存在阻塞操作。如果我们还添加其他操作,如数据库调用、过滤等,我们很容易就总共需要 8-9 秒。

使用 OpenVINO 优化性能

我面临的问题是:我们可以让它更快吗?答案是肯定的,通过利用OpenVINO,一个针对 CPU 推理的优化后端。 OpenVINO 有助于加速英特尔硬件上的深度学习模型推理,我们在 AWS Lambda 上使用该硬件。

OpenVINO 优化的代码示例
以下是我如何将 OpenVINO 集成到搜索系统中以加快推理速度:

import argparse
import numpy as np
import pandas as pd
from typing import Any
from openvino.runtime import Core
from transformers import AutoTokenizer


def load_openvino_model(model_path: str) -> Core:
    core = Core()
    model = core.read_model(model_path   ".xml")
    compiled_model = core.compile_model(model, "CPU")
    return compiled_model


def rerank(
    compiled_model: Core,
    query: str,
    results: list[str],
    tokenizer: AutoTokenizer,
    batch_size: int,
) -> np.ndarray[np.float32, Any]:
    max_length = 512
    all_logits = []

    # Split results into batches
    for i in range(0, len(results), batch_size):
        batch_results = results[i : i   batch_size]
        inputs = tokenizer(
            [(query, item) for item in batch_results],
            padding=True,
            truncation="longest_first",
            max_length=max_length,
            return_tensors="np",
        )

        # Extract input tensors (convert to NumPy arrays)
        input_ids = inputs["input_ids"].astype(np.int32)
        attention_mask = inputs["attention_mask"].astype(np.int32)
        token_type_ids = inputs.get("token_type_ids", np.zeros_like(input_ids)).astype(
            np.int32
        )

        infer_request = compiled_model.create_infer_request()
        output = infer_request.infer(
            {
                "input_ids": input_ids,
                "attention_mask": attention_mask,
                "token_type_ids": token_type_ids,
            }
        )

        logits = output["logits"]
        all_logits.append(logits)

    all_logits = np.concatenate(all_logits, axis=0)
    return all_logits


def fetch_search_data(search_text: str) -> pd.DataFrame:
    # Usually you would fetch the data from a database
    df = pd.read_csv("cnbc_headlines.csv")
    df = df[~df["Headlines"].isnull()]

    texts = df["Headlines"].tolist()

    # Load the model and rerank
    openvino_model = load_openvino_model("cross-encoder-openvino-model/model")
    tokenizer = AutoTokenizer.from_pretrained("cross-encoder/ms-marco-TinyBERT-L-2-v2")
    rerank_scores = rerank(openvino_model, search_text, texts, tokenizer, batch_size=16)

    # Add the rerank scores to the DataFrame and sort by the new scores
    df["rerank_score"] = rerank_scores
    df = df.sort_values(by="rerank_score", ascending=False)

    return df


if __name__ == "__main__":
    parser = argparse.ArgumentParser(
        description="Fetch search results with reranking using OpenVINO"
    )

    parser.add_argument(
        "--search_text",
        type=str,
        required=True,
        help="The search text to use for reranking",
    )

    args = parser.parse_args()

    df = fetch_search_data(args.search_text)
    print(df)

通过这种方法,我们可以获得 2-3 倍的加速,将原来的 4-5 秒减少到 1-2 秒。完整的工作代码位于 Github 上。

速度微调:批量大小和标记化

提高性能的另一个关键因素是优化令牌化流程并调整批量大小令牌长度。通过增加批量大小(batch_size = 16)和减少令牌长度(max_length = 512),我们可以并行化令牌化并减少重复操作的开销。在我们的实验中,我们发现 16 到 64 之间的 batch_size 效果很好,任何更大的值都会降低性能。同样,我们将 max_length 设置为 128,如果报告的平均长度相对较短,则该值是可行的。通过这些更改,我们实现了 8 倍的整体加速,将重新排名时间缩短至 1 秒以下,即使在 CPU 上也是如此。

在实践中,这意味着尝试不同的批量大小和令牌长度,以找到数据速度和准确性之间的适当平衡。通过这样做,我们看到响应时间显着缩短,使得搜索系统即使有 1,000 份报告也可扩展。

结论

通过使用 OpenVINO 并优化标记化和批处理,我们能够构建一个高性能语义搜索系统,满足仅 CPU 设置的实时要求。事实上,我们的整体速度提升了 8 倍。使用句子转换器的检索和使用交叉编码器模型的重新排名相结合,创造了强大的、用户友好的搜索体验。

如果您正在构建响应时间和计算资源受到限制的类似系统,我强烈建议您探索 OpenVINO 和智能批处理以释放更好的性能。

希望您喜欢这篇文章。如果您觉得这篇文章有用,请给我一个赞,以便其他人也可以找到它,并与您的朋友分享。在 Linkedin 上关注我,了解我的最新工作。感谢您的阅读!

版本声明 本文转载于:https://dev.to/datitran/building-a-fast-and-efficient-semantic-search-system-using-openvino-and-postgres-fd6?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何有效地使用 Laravel 5 的 `orWhereLike` 方法来进行部分字符串匹配?
    如何有效地使用 Laravel 5 的 `orWhereLike` 方法来进行部分字符串匹配?
    使用 Laravel-5 的 'LIKE' 等效项(雄辩)在 Laravel 5 中,您可以使用 like 运算符来执行部分字符串匹配在数据库列上。但是,在使用 orWhereLike 方法时,仔细构建查询以确保它产生所需的结果非常重要。要解决问题中描述的情况(其中 orWhereL...
    编程 发布于2024-11-08
  • 方法重载
    方法重载
    Java 允许方法重载,即同一类的两个或多个方法可以共享相同的名称。 当方法参数声明不同时,就会发生方法重载。 方法重载是Java中实现多态性的一种方式。 要重载一个方法,需要声明它的不同版本。 编译器根据参数自动选择正确的方法。 一个重要的限制:重载方法的参数类型和/或数量必须不同,而不仅仅是返...
    编程 发布于2024-11-08
  • Grep 的幕后花絮:Python 实践挑战
    Grep 的幕后花絮:Python 实践挑战
    构建我自己的 Grep:深入研究文本搜索 我最近开始参与 codecrafters.io 的“构建自己的 grep”挑战,这是一次令人难以置信的学习经历。 Grep 是我们经常认为理所当然的工具,但从头开始构建它让我对其复杂性和实用性有了全新的认识。 为什么要接受这个挑战? 我想了解 grep 等工...
    编程 发布于2024-11-08
  • 如何将 Git 修订信息嵌入到 Go 二进制文件中?
    如何将 Git 修订信息嵌入到 Go 二进制文件中?
    将 Git 修订版信息添加到 Go 二进制文件确定构建 Go 二进制文件的 Git 修订版的能力对于调试和版本跟踪。通过将此信息合并到二进制文件本身中,工程师可以轻松识别用于创建特定版本的源代码。为什么在源代码中设置版本号是不够的虽然包含修订号 bezpośrednio 似乎很直观,但 Git 修订...
    编程 发布于2024-11-08
  • 以下是一些标题选项,请记住问题格式和内容重点:

选项 1(关注问题和解决方案):

* 如何在 Spring Boot 应用程序中设置上下文路径:避免
    以下是一些标题选项,请记住问题格式和内容重点: 选项 1(关注问题和解决方案): * 如何在 Spring Boot 应用程序中设置上下文路径:避免
    向 Spring Boot 应用程序添加上下文路径要以编程方式为 Spring Boot 应用程序设置上下文根,您可以使用 EmbeddedServletContainerFactory豆。此 bean 允许您自定义应用程序使用的 servlet 容器。在提供的示例中,您创建了一个名为 servle...
    编程 发布于2024-11-08
  • Laravel 中的全局范围(StepWise)。
    Laravel 中的全局范围(StepWise)。
    全局范围是 Laravel 中的一个重要概念,可以在整个应用程序中重用 Eloquent 条件。通过实施全局范围,您可以将特定条件应用于所有模型的查询,从而促进代码重用和一致性。相比之下,局部范围仅限于单个模型。在本教程中,我们将重点介绍在 Laravel 中创建和使用全局范围。 在这一步中,我们将...
    编程 发布于2024-11-08
  • 如何在 React 应用程序中嵌入带预览的链接
    如何在 React 应用程序中嵌入带预览的链接
    介绍 构建 Web 应用程序时,显示链接内容的预览通常很有用,就像社交媒体平台在共享 URL 时如何显示链接预览一样。因此,除了 url 文本之外,您还可以在 url 旁边显示图片和描述等信息。 在这篇文章中,我将引导您在 React 应用程序中嵌入链接,同时使用 axios 和 ...
    编程 发布于2024-11-08
  • 在 Sass 中使用 Mixin
    在 Sass 中使用 Mixin
    如果您深入研究前端开发世界,您很可能遇到过Sass(语法很棒的样式表)。 Sass 是一个强大的 CSS 预处理器,它通过提供 变量、嵌套、函数和 mixins 等功能来增强您的 CSS 工作流程。在这些功能中,mixins 作为游戏规则改变者脱颖而出,允许您有效地重用代码并保持样式表的一致性。 ...
    编程 发布于2024-11-08
  • PHP 5.3 中的 ?: 运算符是什么?
    PHP 5.3 中的 ?: 运算符是什么?
    PHP 5.3 中的 ?: 运算符PHP 5.3 引入了 ?: 运算符,这是以前可用的条件运算符的压缩形式。最初,条件运算符采用以下形式:expr ? val_if_true : val_if_false在 PHP 5.3 中,您可以省略中间部分,从而得到 ?: 语法。这相当于:expr ? exp...
    编程 发布于2024-11-08
  • 通过静态分析、映像初始化和堆快照提高性能
    通过静态分析、映像初始化和堆快照提高性能
    从整体结构到分布式系统世界,应用程序开发已经走过了漫长的道路。云计算和微服务架构的大规模采用极大地改变了服务器应用程序的创建和部署方式。我们现在拥有独立、单独部署的服务,而不是巨大的应用程序服务器 当需要时。 然而,可能影响这种平稳运行的新玩家可能是“冷启动”。当第一个请求在新产生的工作进程上处理时...
    编程 发布于2024-11-08
  • 增强 React 列表渲染:干净且可重用的模式
    增强 React 列表渲染:干净且可重用的模式
    作为 React 开发人员,我们都遇到过需要渲染数据列表的场景。虽然 .map() 方法效果很好,但每次渲染列表时重复相同的逻辑可能会让人筋疲力尽,并导致代码重复。幸运的是,有一种更干净、可扩展的方法来处理这个问题,使用可重用组件、高阶组件或自定义挂钩。 在本文中,我将分享一种改进 React 中列...
    编程 发布于2024-11-08
  • 前端与后端开发人员
    前端与后端开发人员
    海·德夫, 在Web开发中,有两个重要的角色:前端开发和后端开发。这两个学科对于任何成功的 Web 项目都是必不可少的,但它们具有不同的角色、技能和职责。在本博客中,我们将探索前端和后端开发的世界,比较它们的特点、所需的技能和挑战。因此,无论您是经验丰富的开发人员还是新手,请准备好探索前端与后端开发...
    编程 发布于2024-11-08
  • Pulsy 自述文件已更新
    Pulsy 自述文件已更新
    Pulsy - React 的轻量级状态管理库 Pulsy 是一个轻量级、灵活且易于使用的 React 状态管理库,提供持久性、中间件、记忆、计算和组合存储、时间旅行和 DevTools 集成等功能。它可以帮助您有效地管理 React 应用程序中的全局状态,而无需不必要的复杂性。 ...
    编程 发布于2024-11-08
  • 从命令行运行时,如何解决 Maven 中的“NoClassDefFoundError”问题?
    从命令行运行时,如何解决 Maven 中的“NoClassDefFoundError”问题?
    通过 Shade 插件解决 NoClassDefFoundError 的依赖关系在第一次使用 Maven 时,Eclipse 和命令行使用之间可能会出现不一致,原因是依赖管理。尽管在 Eclipse 中成功创建了 Maven 项目并添加了依赖项,但通过命令行执行项目可能会导致 NoClassDefF...
    编程 发布于2024-11-08
  • 如何在 Mac 上的 XAMPP 中安装 PHP 的国际扩展?
    如何在 Mac 上的 XAMPP 中安装 PHP 的国际扩展?
    Mac OS 上 XAMPP 的 PHP-intl 安装要解决在 Mac 上使用 XAMPP 时在 php 中安装 intl 扩展的问题,请按照以下步骤操作:确认PHP路径:执行哪个php来确定使用的PHP路径。对于 XAMPP,它应该是 /Applications/XAMPP/xamppfiles...
    编程 发布于2024-11-08

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3