精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

MUVERA:讓RAG系統(tǒng)中的多向量檢索像單向量一樣高效

人工智能
隨著 ColBERT、ColPali 等多向量模型的進(jìn)一步發(fā)展,以及 MUVERA 這類(lèi)優(yōu)化算法的不斷演進(jìn),多向量檢索的效率瓶頸正在逐步被克服。未來(lái),在推薦系統(tǒng)、搜索引擎、文檔檢索等場(chǎng)景中,多向量技術(shù)很可能成為標(biāo)準(zhǔn)配置。

在向量數(shù)據(jù)庫(kù)和信息檢索領(lǐng)域,多向量嵌入模型(如 ColBERT、ColPali)憑借其強(qiáng)大的語(yǔ)義捕獲能力正在成為主流選擇。這類(lèi)模型能夠保留文本的詞元級(jí)別含義,或是識(shí)別圖像不同部分的信息特征。然而,它們也帶來(lái)了顯著的性能挑戰(zhàn):龐大的內(nèi)存占用和較慢的檢索速度。Weaviate 在 1.31 版本中引入的 MUVERA 編碼算法,正是為了解決這些問(wèn)題而生。

多向量模型的優(yōu)勢(shì)與困境

多向量嵌入的核心優(yōu)勢(shì)在于其細(xì)粒度的語(yǔ)義表達(dá)能力。相比單向量模型將整個(gè)文檔壓縮成一個(gè)固定長(zhǎng)度的向量,多向量模型為文檔的每個(gè)詞元或圖像塊生成獨(dú)立的向量表示。這種設(shè)計(jì)使得模型能夠捕捉更豐富的語(yǔ)義信息,在檢索任務(wù)中展現(xiàn)出更高的準(zhǔn)確性。

單向量與多向量對(duì)比單向量與多向量對(duì)比

但這種精細(xì)化表示的代價(jià)同樣明顯。假設(shè)要索引一百萬(wàn)個(gè)文檔,每個(gè)文檔平均包含 100 個(gè)詞元。使用傳統(tǒng)的單向量模型(768 維,32 位浮點(diǎn)數(shù)),大約需要 3.1GB 內(nèi)存。而多向量模型(96 維)的內(nèi)存消耗可能高達(dá) 40GB,超過(guò)十倍的差距。這種內(nèi)存壓力在大規(guī)模部署場(chǎng)景下會(huì)轉(zhuǎn)化為實(shí)實(shí)在在的成本負(fù)擔(dān)。

多向量嵌入內(nèi)存對(duì)比多向量嵌入內(nèi)存對(duì)比

性能瓶頸不僅體現(xiàn)在存儲(chǔ)層面。在檢索階段,多向量模型需要使用 MaxSim 運(yùn)算符計(jì)算相似度。這個(gè)過(guò)程需要遍歷查詢的每個(gè)詞元,找出它與文檔所有詞元中的最佳匹配,然后累加所有匹配得分。數(shù)學(xué)表達(dá)式如下:

這種非線性計(jì)算相比簡(jiǎn)單的點(diǎn)積運(yùn)算復(fù)雜得多,直接影響了查詢響應(yīng)速度和數(shù)據(jù)導(dǎo)入效率。

單向量和多向量?jī)?nèi)存使用情況單向量和多向量?jī)?nèi)存使用情況

MUVERA 的核心思想

MUVERA(Multi-Vector Retrieval via Fixed Dimensional Encodings)的設(shè)計(jì)哲學(xué)是將復(fù)雜的多向量檢索問(wèn)題轉(zhuǎn)化為單向量最大內(nèi)積搜索。算法的關(guān)鍵在于構(gòu)建固定維度編碼(FDE),將一組長(zhǎng)度不定的向量集合壓縮成單個(gè)固定長(zhǎng)度的向量表示。

整個(gè)轉(zhuǎn)換過(guò)程可以用一個(gè)簡(jiǎn)潔的映射函數(shù)表示:

這里的核心目標(biāo)是讓編碼后的單向量點(diǎn)積能夠很好地近似原始多向量的 MaxSim 相似度:

MUVERA 高層概覽MUVERA 高層概覽

這種轉(zhuǎn)換帶來(lái)的效率提升是顯著的。對(duì)于包含 100 萬(wàn)個(gè)文檔、每個(gè)文檔 100 個(gè)向量的數(shù)據(jù)集,傳統(tǒng)方案需要索引 1 億個(gè)向量,而 MUVERA 只需處理 100 萬(wàn)個(gè) FDE 向量,將 HNSW 圖的規(guī)模縮減到原來(lái)的 1%。

算法實(shí)現(xiàn)細(xì)節(jié)

MUVERA 通過(guò)四個(gè)精心設(shè)計(jì)的步驟完成編碼轉(zhuǎn)換:空間劃分、降維、重復(fù)增強(qiáng)和最終投影。每個(gè)步驟都有明確的數(shù)學(xué)基礎(chǔ)和實(shí)際考量。

空間劃分策略

第一步是將高維向量空間劃分成若干個(gè)桶。算法采用 SimHash 技術(shù)實(shí)現(xiàn)這一過(guò)程,這是一種基于局部敏感哈希的方法。具體來(lái)說(shuō),算法會(huì)采樣  個(gè)高斯向量,然后通過(guò)計(jì)算輸入向量與這些高斯向量的點(diǎn)積符號(hào)來(lái)確定桶編號(hào):

這種劃分方式的優(yōu)勢(shì)在于其與數(shù)據(jù)分布無(wú)關(guān),不需要預(yù)先訓(xùn)練,也不會(huì)因?yàn)閿?shù)據(jù)漂移而失效。劃分完成后,屬于同一個(gè)桶的向量會(huì)被聚合成一個(gè)代表性向量。

MUVERA 步驟 1 - 空間劃分MUVERA 步驟 1 - 空間劃分

MUVERA 步驟 2 - 填充空簇MUVERA 步驟 2 - 填充空簇

降維與重復(fù)

MUVERA 步驟 3 - 降維MUVERA 步驟 3 - 降維

性能評(píng)測(cè)與實(shí)際效果

Weaviate 團(tuán)隊(duì)使用 LoTTE 基準(zhǔn)測(cè)試數(shù)據(jù)集進(jìn)行了詳細(xì)的性能評(píng)估。該數(shù)據(jù)集包含約 11.9 萬(wàn)個(gè)文檔,使用 ColBERT v2.0 編碼后生成了 1500 萬(wàn)個(gè) 128 維向量,總內(nèi)存占用約 8GB。

未使用 MUVERA + SQ 與 MUVERA + SQ 時(shí)的堆內(nèi)存分配未使用 MUVERA + SQ 與 MUVERA + SQ 時(shí)的堆內(nèi)存分配

數(shù)據(jù)導(dǎo)入速度的改善同樣顯著。基準(zhǔn)場(chǎng)景下,導(dǎo)入 11 萬(wàn)個(gè)對(duì)象需要 20 多分鐘,相當(dāng)于每秒只能處理約 100 個(gè)對(duì)象。而使用 MUVERA 后,這個(gè)時(shí)間縮短到 3-6 分鐘。對(duì)于需要頻繁更新索引的生產(chǎn)環(huán)境,這種效率提升意義重大。

性能權(quán)衡考量

技術(shù)方案從來(lái)不是完美的,MUVERA 也有其代價(jià)。最主要的妥協(xié)體現(xiàn)在召回率上。測(cè)試數(shù)據(jù)顯示,在相同的搜索參數(shù)下,啟用 MUVERA 會(huì)導(dǎo)致召回率下降。不過(guò),這個(gè)問(wèn)題可以通過(guò)調(diào)整 HNSW 的 ef 參數(shù)來(lái)緩解。

當(dāng) ef 值設(shè)置在 512 以上時(shí),召回率可以恢復(fù)到 80% 以上;而在 2048 時(shí)甚至能超過(guò) 90%。但提高 ef 值意味著要檢索更多的候選集,這會(huì)降低查詢吞吐量。因此,實(shí)際應(yīng)用中需要在召回質(zhì)量和查詢速度之間找到平衡點(diǎn)。

MUVERA 對(duì)比MUVERA 對(duì)比

Google Research 團(tuán)隊(duì)的實(shí)驗(yàn)結(jié)果進(jìn)一步驗(yàn)證了 MUVERA 的效果。在 BEIR 基準(zhǔn)測(cè)試中,相比基于單向量啟發(fā)式的 PLAID 系統(tǒng),MUVERA 在召回率平均提升 10% 的同時(shí),將延遲降低了 90%。這種性能提升在大規(guī)模部署中的價(jià)值不言而喻。

適用場(chǎng)景分析

MUVERA 并非萬(wàn)能方案,它最適合以下幾類(lèi)應(yīng)用場(chǎng)景。首先是內(nèi)存成本敏感的大規(guī)模部署。當(dāng)數(shù)據(jù)集規(guī)模達(dá)到千萬(wàn)甚至億級(jí)時(shí),內(nèi)存占用的降低可以直接轉(zhuǎn)化為每年數(shù)萬(wàn)甚至數(shù)十萬(wàn)美元的成本節(jié)約。其次是對(duì)索引速度有較高要求的場(chǎng)景,比如需要頻繁更新的實(shí)時(shí)系統(tǒng)。

另一個(gè)重要考量是對(duì)召回質(zhì)量的容忍度。如果應(yīng)用場(chǎng)景對(duì)檢索精度有極致要求,那么需要仔細(xì)權(quán)衡 MUVERA 帶來(lái)的召回率下降是否可以接受。不過(guò)對(duì)于許多實(shí)際應(yīng)用來(lái)說(shuō),輕微的召回?fù)p失往往是可以承受的,特別是考慮到可以通過(guò)調(diào)整搜索參數(shù)來(lái)部分恢復(fù)性能。

從實(shí)現(xiàn)角度看,Weaviate 的集成使得啟用 MUVERA 變得非常簡(jiǎn)單,只需要幾行配置代碼。用戶可以設(shè)置的主要參數(shù)包括 k_sim(空間劃分的細(xì)粒度)、d_proj(降維后的維度)和 r_reps(重復(fù)次數(shù))。Weaviate 團(tuán)隊(duì)為這些參數(shù)提供了合理的默認(rèn)值,大多數(shù)場(chǎng)景下可以直接使用。

值得注意的是,MUVERA 的固定維度編碼還可以結(jié)合標(biāo)量量化(Scalar Quantization)等技術(shù)進(jìn)一步壓縮。Google 的研究表明,通過(guò)乘積量化可以在幾乎不影響檢索質(zhì)量的前提下,將內(nèi)存占用再減少 32 倍。這為超大規(guī)模應(yīng)用提供了更多優(yōu)化空間。

實(shí)現(xiàn)

圖片圖片

https://github.com/sionic-ai/muvera-py/tree/master

我在github上面找到一個(gè)MUVERA的python實(shí)現(xiàn),大家可以嘗試一下。

import logging
import time

import numpy as np
from dataclasses import dataclass, replace
from enum import Enum
from typing import Optional, List


class EncodingType(Enum):
    DEFAULT_SUM = 0
    AVERAGE = 1


class ProjectionType(Enum):
    DEFAULT_IDENTITY = 0
    AMS_SKETCH = 1


@dataclass
class FixedDimensionalEncodingConfig:
    dimension: int = 128
    num_repetitions: int = 10
    num_simhash_projections: int = 6
    seed: int = 42
    encoding_type: EncodingType = EncodingType.DEFAULT_SUM
    projection_type: ProjectionType = ProjectionType.DEFAULT_IDENTITY
    projection_dimension: Optional[int] = None
    fill_empty_partitions: bool = False
    final_projection_dimension: Optional[int] = None


def _append_to_gray_code(gray_code: int, bit: bool) -> int:
    return (gray_code << 1) + (int(bit) ^ (gray_code & 1))


def _gray_code_to_binary(num: int) -> int:
    mask = num >> 1
    while mask != 0:
        num = num ^ mask
        mask >>= 1
    return num


def _simhash_matrix_from_seed(
    dimension: int, num_projections: int, seed: int
) -> np.ndarray:
    rng = np.random.default_rng(seed)
    return rng.normal(loc=0.0, scale=1.0, size=(dimension, num_projections)).astype(
        np.float32
    )


def _ams_projection_matrix_from_seed(
    dimension: int, projection_dim: int, seed: int
) -> np.ndarray:
    rng = np.random.default_rng(seed)
    out = np.zeros((dimension, projection_dim), dtype=np.float32)
    indices = rng.integers(0, projection_dim, size=dimension)
    signs = rng.choice([-1.0, 1.0], size=dimension)
    out[np.arange(dimension), indices] = signs
    return out


def _apply_count_sketch_to_vector(
    input_vector: np.ndarray, final_dimension: int, seed: int
) -> np.ndarray:
    rng = np.random.default_rng(seed)
    out = np.zeros(final_dimension, dtype=np.float32)
    indices = rng.integers(0, final_dimension, size=input_vector.shape[0])
    signs = rng.choice([-1.0, 1.0], size=input_vector.shape[0])
    np.add.at(out, indices, signs * input_vector)
    return out


def _simhash_partition_index_gray(sketch_vector: np.ndarray) -> int:
    partition_index = 0
    for val in sketch_vector:
        partition_index = _append_to_gray_code(partition_index, val > 0)
    return partition_index


def _distance_to_simhash_partition(
    sketch_vector: np.ndarray, partition_index: int
) -> int:
    num_projections = sketch_vector.size
    binary_representation = _gray_code_to_binary(partition_index)
    sketch_bits = (sketch_vector > 0).astype(int)
    binary_array = (binary_representation >> np.arange(num_projections - 1, -1, -1)) & 1
    return int(np.sum(sketch_bits != binary_array))


def _generate_fde_internal(
    point_cloud: np.ndarray, config: FixedDimensionalEncodingConfig
) -> np.ndarray:
    if point_cloud.ndim != 2 or point_cloud.shape[1] != config.dimension:
        raise ValueError(
            f"Input data shape {point_cloud.shape} is inconsistent with config dimension {config.dimension}."
        )
    if not (0 <= config.num_simhash_projections < 32):
        raise ValueError(
            f"num_simhash_projections must be in [0, 31]: {config.num_simhash_projections}"
        )

    num_points, original_dim = point_cloud.shape
    num_partitions = 2**config.num_simhash_projections

    use_identity_proj = config.projection_type == ProjectionType.DEFAULT_IDENTITY
    projection_dim = original_dim if use_identity_proj else config.projection_dimension
    if not use_identity_proj and (not projection_dim or projection_dim <= 0):
        raise ValueError(
            "A positive projection_dimension is required for non-identity projections."
        )

    final_fde_dim = config.num_repetitions * num_partitions * projection_dim
    out_fde = np.zeros(final_fde_dim, dtype=np.float32)

    for rep_num in range(config.num_repetitions):
        current_seed = config.seed + rep_num

        sketches = point_cloud @ _simhash_matrix_from_seed(
            original_dim, config.num_simhash_projections, current_seed
        )

        if use_identity_proj:
            projected_matrix = point_cloud
        elif config.projection_type == ProjectionType.AMS_SKETCH:
            ams_matrix = _ams_projection_matrix_from_seed(
                original_dim, projection_dim, current_seed
            )
            projected_matrix = point_cloud @ ams_matrix

        rep_fde_sum = np.zeros(num_partitions * projection_dim, dtype=np.float32)
        partition_counts = np.zeros(num_partitions, dtype=np.int32)
        partition_indices = np.array(
            [_simhash_partition_index_gray(sketches[i]) for i in range(num_points)]
        )

        for i in range(num_points):
            start_idx = partition_indices[i] * projection_dim
            rep_fde_sum[start_idx : start_idx + projection_dim] += projected_matrix[i]
            partition_counts[partition_indices[i]] += 1

        if config.encoding_type == EncodingType.AVERAGE:
            for i in range(num_partitions):
                start_idx = i * projection_dim
                if partition_counts[i] > 0:
                    rep_fde_sum[start_idx : start_idx + projection_dim] /= (
                        partition_counts[i]
                    )
                elif config.fill_empty_partitions and num_points > 0:
                    distances = [
                        _distance_to_simhash_partition(sketches[j], i)
                        for j in range(num_points)
                    ]
                    nearest_point_idx = np.argmin(distances)
                    rep_fde_sum[start_idx : start_idx + projection_dim] = (
                        projected_matrix[nearest_point_idx]
                    )

        rep_start_index = rep_num * num_partitions * projection_dim
        out_fde[rep_start_index : rep_start_index + rep_fde_sum.size] = rep_fde_sum

    if config.final_projection_dimension and config.final_projection_dimension > 0:
        return _apply_count_sketch_to_vector(
            out_fde, config.final_projection_dimension, config.seed
        )

    return out_fde


def generate_query_fde(
    point_cloud: np.ndarray, config: FixedDimensionalEncodingConfig
) -> np.ndarray:
    """Generates a Fixed Dimensional Encoding for a query point cloud (using SUM)."""
    if config.fill_empty_partitions:
        raise ValueError(
            "Query FDE generation does not support 'fill_empty_partitions'."
        )
    query_config = replace(config, encoding_type=EncodingType.DEFAULT_SUM)
    return _generate_fde_internal(point_cloud, query_config)


def generate_document_fde(
    point_cloud: np.ndarray, config: FixedDimensionalEncodingConfig
) -> np.ndarray:
    """Generates a Fixed Dimensional Encoding for a document point cloud (using AVERAGE)."""
    doc_config = replace(config, encoding_type=EncodingType.AVERAGE)
    return _generate_fde_internal(point_cloud, doc_config)


def generate_fde(
    point_cloud: np.ndarray, config: FixedDimensionalEncodingConfig
) -> np.ndarray:
    if config.encoding_type == EncodingType.DEFAULT_SUM:
        return generate_query_fde(point_cloud, config)
    elif config.encoding_type == EncodingType.AVERAGE:
        return generate_document_fde(point_cloud, config)
    else:
        raise ValueError(f"Unsupported encoding type in config: {config.encoding_type}")


def generate_document_fde_batch(
    doc_embeddings_list: List[np.ndarray], config: FixedDimensionalEncodingConfig
) -> np.ndarray:
    """
    Generates FDEs for a batch of documents using highly optimized NumPy vectorization.
    Fully compliant with C++ implementation including all projection types.
    """
    batch_start_time = time.perf_counter()
    num_docs = len(doc_embeddings_list)

    if num_docs == 0:
        logging.warning("[FDE Batch] Empty document list provided")
        return np.array([])

    logging.info(f"[FDE Batch] Starting batch FDE generation for {num_docs} documents")

    # Input validation
    valid_docs = []
    for i, doc in enumerate(doc_embeddings_list):
        if doc.ndim != 2:
            logging.warning(
                f"[FDE Batch] Document {i} has invalid shape (ndim={doc.ndim}), skipping"
            )
            continue
        if doc.shape[1] != config.dimension:
            raise ValueError(
                f"Document {i} has incorrect dimension: expected {config.dimension}, got {doc.shape[1]}"
            )
        if doc.shape[0] == 0:
            logging.warning(f"[FDE Batch] Document {i} has no vectors, skipping")
            continue
        valid_docs.append(doc)

    if len(valid_docs) == 0:
        logging.warning("[FDE Batch] No valid documents after filtering")
        return np.array([])

    num_docs = len(valid_docs)
    doc_embeddings_list = valid_docs

    # Determine projection dimension (matching C++ logic)
    use_identity_proj = config.projection_type == ProjectionType.DEFAULT_IDENTITY
    if use_identity_proj:
        projection_dim = config.dimension
        logging.info(f"[FDE Batch] Using identity projection (dim={projection_dim})")
    else:
        if not config.projection_dimension or config.projection_dimension <= 0:
            raise ValueError(
                "A positive projection_dimension must be specified for non-identity projections"
            )
        projection_dim = config.projection_dimension
        logging.info(
            f"[FDE Batch] Using {config.projection_type.name} projection: "
            f"{config.dimension} -> {projection_dim}"
        )

    # Configuration summary
    num_partitions = 2**config.num_simhash_projections
    logging.info(
        f"[FDE Batch] Configuration: {config.num_repetitions} repetitions, "
        f"{num_partitions} partitions, projection_dim={projection_dim}"
    )

    # Document tracking
    doc_lengths = np.array([len(doc) for doc in doc_embeddings_list], dtype=np.int32)
    total_vectors = np.sum(doc_lengths)
    doc_boundaries = np.insert(np.cumsum(doc_lengths), 0, 0)
    doc_indices = np.repeat(np.arange(num_docs), doc_lengths)

    logging.info(
        f"[FDE Batch] Total vectors: {total_vectors}, avg per doc: {total_vectors / num_docs:.1f}"
    )

    # Concatenate all embeddings
    concat_start = time.perf_counter()
    all_points = np.vstack(doc_embeddings_list).astype(np.float32)
    concat_time = time.perf_counter() - concat_start
    logging.info(f"[FDE Batch] Concatenation completed in {concat_time:.3f}s")

    # Pre-allocate output
    final_fde_dim = config.num_repetitions * num_partitions * projection_dim
    out_fdes = np.zeros((num_docs, final_fde_dim), dtype=np.float32)
    logging.info(f"[FDE Batch] Output FDE dimension: {final_fde_dim}")

    # Process each repetition
    for rep_num in range(config.num_repetitions):
        # rep_start_time = time.perf_counter()
        current_seed = config.seed + rep_num

        if rep_num % 5 == 0:  # Log every 5 repetitions
            logging.info(
                f"[FDE Batch] Processing repetition {rep_num + 1}/{config.num_repetitions}"
            )

        # Step 1: SimHash projection
        simhash_start = time.perf_counter()
        simhash_matrix = _simhash_matrix_from_seed(
            config.dimension, config.num_simhash_projections, current_seed
        )
        all_sketches = all_points @ simhash_matrix
        simhash_time = time.perf_counter() - simhash_start

        # Step 2: Apply dimensionality reduction if configured
        proj_start = time.perf_counter()
        if use_identity_proj:
            projected_points = all_points
        elif config.projection_type == ProjectionType.AMS_SKETCH:
            ams_matrix = _ams_projection_matrix_from_seed(
                config.dimension, projection_dim, current_seed
            )
            projected_points = all_points @ ams_matrix
        else:
            raise ValueError(f"Unsupported projection type: {config.projection_type}")
        proj_time = time.perf_counter() - proj_start

        # Step 3: Vectorized partition index calculation
        partition_start = time.perf_counter()
        bits = (all_sketches > 0).astype(np.uint32)
        partition_indices = np.zeros(total_vectors, dtype=np.uint32)

        # Vectorized Gray Code computation
        for bit_idx in range(config.num_simhash_projections):
            partition_indices = (partition_indices << 1) + (
                bits[:, bit_idx] ^ (partition_indices & 1)
            )

        partition_time = time.perf_counter() - partition_start

        # Step 4: Vectorized aggregation
        agg_start = time.perf_counter()

        # Initialize storage for this repetition
        rep_fde_sum = np.zeros(
            (num_docs * num_partitions * projection_dim,), dtype=np.float32
        )
        partition_counts = np.zeros((num_docs, num_partitions), dtype=np.int32)

        # Count vectors per partition per document
        np.add.at(partition_counts, (doc_indices, partition_indices), 1)

        # Aggregate vectors using flattened indexing for efficiency
        doc_part_indices = doc_indices * num_partitions + partition_indices
        base_indices = doc_part_indices * projection_dim

        for d in range(projection_dim):
            flat_indices = base_indices + d
            np.add.at(rep_fde_sum, flat_indices, projected_points[:, d])

        # Reshape for easier manipulation
        rep_fde_sum = rep_fde_sum.reshape(num_docs, num_partitions, projection_dim)

        agg_time = time.perf_counter() - agg_start

        # Step 5: Convert sums to averages (for document FDE)
        avg_start = time.perf_counter()

        # Vectorized division where counts > 0
        non_zero_mask = partition_counts > 0
        counts_3d = partition_counts[:, :, np.newaxis]  # Broadcasting for division

        # Safe division (avoid divide by zero)
        np.divide(rep_fde_sum, counts_3d, out=rep_fde_sum, where=counts_3d > 0)

        # Fill empty partitions if configured
        empty_filled = 0
        if config.fill_empty_partitions:
            empty_mask = ~non_zero_mask
            empty_docs, empty_parts = np.where(empty_mask)

            for doc_idx, part_idx in zip(empty_docs, empty_parts):
                if doc_lengths[doc_idx] == 0:
                    continue

                # Get sketches for this document
                doc_start = doc_boundaries[doc_idx]
                doc_end = doc_boundaries[doc_idx + 1]
                doc_sketches = all_sketches[doc_start:doc_end]

                # Vectorized distance calculation
                binary_rep = _gray_code_to_binary(part_idx)
                target_bits = (
                    binary_rep >> np.arange(config.num_simhash_projections - 1, -1, -1)
                ) & 1
                distances = np.sum(
                    (doc_sketches > 0).astype(int) != target_bits, axis=1
                )

                nearest_local_idx = np.argmin(distances)
                nearest_global_idx = doc_start + nearest_local_idx

                rep_fde_sum[doc_idx, part_idx, :] = projected_points[nearest_global_idx]
                empty_filled += 1

        avg_time = time.perf_counter() - avg_start

        # Step 6: Copy results to output array
        rep_output_start = rep_num * num_partitions * projection_dim
        out_fdes[
            :, rep_output_start : rep_output_start + num_partitions * projection_dim
        ] = rep_fde_sum.reshape(num_docs, -1)

        # Log timing for first repetition
        if rep_num == 0:
            logging.info("[FDE Batch] Repetition timing breakdown:")
            logging.info(f"  - SimHash: {simhash_time:.3f}s")
            logging.info(f"  - Projection: {proj_time:.3f}s")
            logging.info(f"  - Partition indices: {partition_time:.3f}s")
            logging.info(f"  - Aggregation: {agg_time:.3f}s")
            logging.info(f"  - Averaging: {avg_time:.3f}s")
            if config.fill_empty_partitions:
                logging.info(f"  - Filled {empty_filled} empty partitions")

    # Step 7: Apply final projection if configured
    if config.final_projection_dimension and config.final_projection_dimension > 0:
        logging.info(
            f"[FDE Batch] Applying final projection: {final_fde_dim} -> "
            f"{config.final_projection_dimension}"
        )
        final_proj_start = time.perf_counter()

        # Process in chunks to avoid memory issues
        chunk_size = min(100, num_docs)
        final_fdes = []

        for i in range(0, num_docs, chunk_size):
            chunk_end = min(i + chunk_size, num_docs)
            chunk_fdes = np.array(
                [
                    _apply_count_sketch_to_vector(
                        out_fdes[j], config.final_projection_dimension, config.seed
                    )
                    for j in range(i, chunk_end)
                ]
            )
            final_fdes.append(chunk_fdes)

        out_fdes = np.vstack(final_fdes)
        final_proj_time = time.perf_counter() - final_proj_start
        logging.info(
            f"[FDE Batch] Final projection completed in {final_proj_time:.3f}s"
        )

    # Final statistics and validation
    total_time = time.perf_counter() - batch_start_time
    logging.info(f"[FDE Batch] Batch generation completed in {total_time:.3f}s")
    logging.info(
        f"[FDE Batch] Average time per document: {total_time / num_docs * 1000:.2f}ms"
    )
    logging.info(f"[FDE Batch] Throughput: {num_docs / total_time:.1f} docs/sec")
    logging.info(f"[FDE Batch] Output shape: {out_fdes.shape}")

    # Validate output dimensions
    expected_dim = (
        final_fde_dim
        if not config.final_projection_dimension
        else config.final_projection_dimension
    )
    assert out_fdes.shape == (num_docs, expected_dim), (
        f"Output shape mismatch: {out_fdes.shape} != ({num_docs}, {expected_dim})"
    )

    # doc_config = replace(config, encoding_type=EncodingType.AVERAGE)

    return out_fdes


if __name__ == "__main__":
    print(f"\n{'=' * 20} SCENARIO 1: Basic FDE Generation {'=' * 20}")

    base_config = FixedDimensionalEncodingConfig(
        dimensinotallow=128, num_repetitinotallow=2, num_simhash_projectinotallow=4, seed=42
    )
    query_data = np.random.randn(32, base_config.dimension).astype(np.float32)
    doc_data = np.random.randn(80, base_config.dimension).astype(np.float32)

    query_fde = generate_query_fde(query_data, base_config)
    doc_fde = generate_document_fde(
        doc_data, replace(base_config, fill_empty_partitinotallow=True)
    )

    expected_dim = (
        base_config.num_repetitions
        * (2**base_config.num_simhash_projections)
        * base_config.dimension
    )
    print(f"Query FDE Shape: {query_fde.shape} (Expected: {expected_dim})")
    print(f"Document FDE Shape: {doc_fde.shape} (Expected: {expected_dim})")
    print(f"Similarity Score: {np.dot(query_fde, doc_fde):.4f}")
    assert query_fde.shape[0] == expected_dim

    print(f"\n{'=' * 20} SCENARIO 2: Inner Projection (AMS Sketch) {'=' * 20}")

    ams_config = replace(
        base_config, projection_type=ProjectionType.AMS_SKETCH, projection_dimensinotallow=16
    )
    query_fde_ams = generate_query_fde(query_data, ams_config)
    expected_dim_ams = (
        ams_config.num_repetitions
        * (2**ams_config.num_simhash_projections)
        * ams_config.projection_dimension
    )
    print(f"AMS Sketch FDE Shape: {query_fde_ams.shape} (Expected: {expected_dim_ams})")
    assert query_fde_ams.shape[0] == expected_dim_ams

    print(f"\n{'=' * 20} SCENARIO 3: Final Projection (Count Sketch) {'=' * 20}")

    final_proj_config = replace(base_config, final_projection_dimensinotallow=1024)
    query_fde_final = generate_query_fde(query_data, final_proj_config)
    print(
        f"Final Projection FDE Shape: {query_fde_final.shape} (Expected: {final_proj_config.final_projection_dimension})"
    )
    assert query_fde_final.shape[0] == final_proj_config.final_projection_dimension

    print(f"\n{'=' * 20} SCENARIO 4: Top-level `generate_fde` wrapper {'=' * 20}")

    query_fde_2 = generate_fde(
        query_data, replace(base_config, encoding_type=EncodingType.DEFAULT_SUM)
    )
    doc_fde_2 = generate_fde(
        doc_data, replace(base_config, encoding_type=EncodingType.AVERAGE)
    )
    print(
        f"Wrapper-generated Query FDE is identical: {np.allclose(query_fde, query_fde_2)}"
    )
    print(
        f"Wrapper-generated Document FDE is identical: {np.allclose(doc_fde, doc_fde_2)}"
    )

    print("\nAll test scenarios completed successfully.")

結(jié)語(yǔ)

隨著 ColBERT、ColPali 等多向量模型的進(jìn)一步發(fā)展,以及 MUVERA 這類(lèi)優(yōu)化算法的不斷演進(jìn),多向量檢索的效率瓶頸正在逐步被克服。未來(lái),在推薦系統(tǒng)、搜索引擎、文檔檢索等場(chǎng)景中,多向量技術(shù)很可能成為標(biāo)準(zhǔn)配置。而 MUVERA 所展示的將復(fù)雜問(wèn)題簡(jiǎn)化為經(jīng)典問(wèn)題的思路,也為其他領(lǐng)域的算法優(yōu)化提供了有價(jià)值的參考。


責(zé)任編輯:武曉燕 來(lái)源: ChallengeHub
相關(guān)推薦

2017-05-22 10:33:14

PythonJuliaCython

2011-10-24 13:07:00

2009-12-08 18:06:12

戴爾存儲(chǔ)動(dòng)車(chē)組

2025-05-19 08:24:29

圖片加載開(kāi)發(fā)

2009-12-08 14:26:13

大型網(wǎng)絡(luò)運(yùn)維

2023-04-05 14:19:07

FlinkRedisNoSQL

2013-07-05 14:59:50

程序員GPU

2021-10-02 10:36:00

YAML編程語(yǔ)言軟件開(kāi)發(fā)

2012-10-26 12:33:58

視頻會(huì)議視頻通信華為

2021-04-13 22:30:17

SpringBoot日志微服務(wù)

2025-10-31 10:13:19

2023-05-23 13:59:41

RustPython程序

2013-12-17 09:02:03

Python調(diào)試

2022-12-21 15:56:23

代碼文檔工具

2013-12-31 09:19:23

Python調(diào)試

2015-11-06 16:20:36

107

2015-11-09 10:07:11

107

2022-12-30 10:48:27

2025-04-29 09:22:17

2023-02-15 08:17:20

VSCodeTypeScrip
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

久久精品亚洲94久久精品| 在线视频一区二区免费| 国产精品久久久久久久久久久久冷 | 日韩成人动漫| 国产精品区一区二区三| 3d精品h动漫啪啪一区二区| 国产大片中文字幕在线观看| 免费不卡中文字幕在线| 欧美日韩综合色| 无码熟妇人妻av在线电影| 免费国产在线视频| 国产麻豆午夜三级精品| 国产精品91久久久| 精国产品一区二区三区a片| 亚洲精品亚洲人成在线观看| 欧美一区二区三区四区久久| 日韩免费一级视频| 成人短视频在线| 久久亚洲精品小早川怜子| 91亚洲永久免费精品| 久草手机在线视频| 欧美午夜免费影院| 中文字幕久热精品视频在线| 97中文字幕在线观看| 国产精品高潮久久| 精品久久久久久久久久ntr影视| 亚洲精品日韩精品| 天堂在线观看视频| 国产一区二区电影| 国产精品美女久久久久av超清| 久青草免费视频| 三级电影一区| 亚洲日本成人网| 男女性杂交内射妇女bbwxz| 欧美亚洲综合视频| 色狠狠综合天天综合综合| 日韩欧美视频免费在线观看| 午夜伦全在线观看| 久久久精品人体av艺术| 国产精品美女xx| 国产情侣自拍小视频| 日韩黄色片在线观看| 欧美激情三级免费| 99热精品免费| 国产精品久久久久久久| 国产午夜精品一区二区三区| 中文字幕 亚洲一区| 成人精品动漫一区二区三区| 欧美一卡二卡三卡| 国产精品久久久久久久99| 成人不卡视频| 欧美影院一区二区三区| 国产精品免费观看久久| 韩国成人二区| 香蕉加勒比综合久久| avav在线播放| 手机电影在线观看| 亚洲自拍与偷拍| 青青在线视频免费观看| 污污片在线免费视频| 亚洲欧美精品午睡沙发| 51xx午夜影福利| av片在线观看永久免费| 一区二区成人在线视频| 真人做人试看60分钟免费| 91在线中字| 一区二区三区美女| 日韩精品综合在线| 性欧美18xxxhd| 色一情一乱一乱一91av| 久久久久免费精品| 久草综合在线| 欧美一级久久久久久久大片| 午夜福利三级理论电影 | 91精品国产综合久久久久久久久久| www.色就是色| 欧美一级免费| 日韩欧美的一区二区| 国产a级黄色片| 天天久久夜夜| 国产亚洲欧洲高清| 国产日产精品一区二区三区的介绍| 国产精品99一区二区三区| 欧美精品免费在线| 日本三级理论片| 六月天综合网| 成人av电影天堂| 亚洲国产成人在线观看| wwww国产精品欧美| 91香蕉视频网址| 97在线视频免费观看完整版| 一本到不卡精品视频在线观看| 国产91色在线观看| silk一区二区三区精品视频 | 欧美粗暴jizz性欧美20| 久久久久久久久久久av| 久久久精品毛片| 国产一区二区在线视频| 精品无码久久久久久久动漫| 在线观看国产原创自拍视频| 亚洲一区二区在线视频| 老熟妇仑乱视频一区二区| 国产美女精品视频免费播放软件| 亚洲成人国产精品| 少妇的滋味中文字幕bd| 亚洲国产免费看| 国产精品久久97| 亚洲经典一区二区三区| 中日韩av电影| 国产视频九色蝌蚪| 亚洲欧洲二区| 亚洲毛茸茸少妇高潮呻吟| 2021亚洲天堂| 美女网站在线免费欧美精品| 国产精品久久久久av福利动漫| 91在线免费看| 天天综合网天天综合色| 国产探花在线观看视频| 国产伦精品一区二区三区千人斩| 色综合视频一区中文字幕| 午夜精品久久久久久久蜜桃| 成人高清视频在线| 欧美 日韩 国产 在线观看| 欧美91看片特黄aaaa| 欧美mv日韩mv国产网站app| 国产极品视频在线观看| 性伦欧美刺激片在线观看| 草莓视频一区| а√天堂8资源在线官网| 在线影院国内精品| 亚洲一区二区三区四区五区六区 | 久久综合九色综合97婷婷女人 | 国产黄色小视频在线| 色婷婷狠狠综合| 第四色在线视频| 国产一区二区三区自拍| 成人午夜在线观看| 99re在线视频| 欧美午夜影院一区| 国产成人av一区二区三区不卡| 亚洲高清电影| 春色成人在线视频| 亚洲欧美成人影院| 91麻豆精品久久久久蜜臀| 一二三四在线观看视频| 日韩**一区毛片| 欧美日韩三区四区| 欧美电影h版| 一区二区国产精品视频| 老熟妇一区二区三区| 久久久噜噜噜久噜久久综合| 色欲av无码一区二区人妻| 久久大胆人体视频| 91国产中文字幕| 欧美一区二区三区激情| 亚洲国产一区二区a毛片| 欧美丰满熟妇bbb久久久| 欧美激情自拍| 国产福利一区二区三区在线观看| 性欧美ⅴideo另类hd| 欧美一区二区精美| 欧美日韩一级在线观看| 成人性生交大片免费看视频在线| 精品国产一区二区三区无码| 日韩a级大片| 日韩免费在线视频| 国产69精品久久app免费版| 欧美在线观看18| 三级黄色免费观看| 国产精品一二三在| 久久艹国产精品| 美女视频亚洲色图| 热久久免费国产视频| 国内三级在线观看| 欧美理论电影在线| 国产在线观看成人| 久久午夜国产精品| 亚洲老女人av| 欧美日韩精品| 久久精品国产第一区二区三区最新章节| 亚洲精品福利电影| 日韩在线精品一区| 成人久久久精品国产乱码一区二区 | 日韩精品一区二区三区中文精品| 日本少妇毛茸茸高潮| 久久久精品人体av艺术| 91视频福利网| 香蕉av777xxx色综合一区| 日韩一区免费观看| 日韩在线成人| 国产成人久久久| av激情在线| 日韩精品免费综合视频在线播放| 国产成人av免费| 一区二区三区在线播放| 成人免费看aa片| 国产在线精品一区二区夜色 | 日本成人在线视频网站| 黄色一级视频播放| 亚洲传媒在线| 91亚洲精品久久久| 伊人久久综合一区二区| 精品国产一区二区三区久久狼5月 精品国产一区二区三区久久久狼 精品国产一区二区三区久久久 | 成人黄色免费网| 亚洲综合免费观看高清完整版 | 亚洲警察之高压线| 成人中文字幕在线观看| 在线人成日本视频| 欧美成人精品h版在线观看| 青青久在线视频| 日韩视频永久免费| 欧美高清69hd| 狠狠色狠狠色综合日日小说 | 欧美日韩国产精品一区二区不卡中文 | 久久久精品人体av艺术| 伊人影院在线观看视频| 青椒成人免费视频| 5月婷婷6月丁香| 在线免费观看日本欧美爱情大片| 日本不卡久久| 日本一区福利在线| 成人综合电影| 日本免费一区二区三区视频| 国产极品精品在线观看| 草草在线观看| 欧美日韩国产123| 精品国产99久久久久久| 在线观看免费高清视频97| 日韩黄色影片| 亚洲国产成人久久| 亚洲成熟女性毛茸茸| 欧美日韩精品综合在线| 久久久黄色大片| 图片区小说区区亚洲影院| 久草网在线观看| 一区二区久久久| 亚洲成人生活片| 亚洲色图视频网| 三级黄色在线观看| 国产精品护士白丝一区av| 日本成人免费视频| 久久精品欧美一区二区三区不卡| 国产精品无码专区| 99久久99久久精品免费观看| 美女被爆操网站| 国产成人精品免费看| 国内av免费观看| 国产精品一区二区91| 免费黄视频在线观看| 国产成人精品aa毛片| 国产精品一区二区在线免费观看| 国产精品一区二区男女羞羞无遮挡| 欧美一级小视频| 精品一区免费av| 欧美专区第二页| 国产精品一区在线观看你懂的| 一级黄色高清视频| 国产成人三级在线观看| 精品人妻一区二区免费| 99综合电影在线视频| 亚洲精品在线视频免费观看| 91网站在线观看视频| 欧美 变态 另类 人妖| 久久婷婷综合激情| 亚洲无人区码一码二码三码的含义 | 麻豆影视国产在线观看| 欧美成人免费va影院高清| 亚洲无线看天堂av| 亚洲 日韩 国产第一| 大胆人体一区二区| 国产极品jizzhd欧美| 亚洲人成网站在线在线观看| 91黄色精品| 日韩高清电影免费| av色综合久久天堂av色综合在| 中文字幕欧美国产| 最新av电影网站| 一区二区三区蜜桃网| 国产做受高潮漫动| 色综合一个色综合| 一级特黄aaa大片| 日韩精品一区二区三区swag | 亚洲大胆人体视频| 欧美女优在线| 久久精品99国产精品酒店日本| 50度灰在线| 欧美一区二区影院| 四虎地址8848精品| 国产乱码一区| 日韩一区电影| 精品国产一区三区| 免费成人av在线| 成人欧美精品一区二区| 久久久久久久综合狠狠综合| 国产在线观看免费视频软件| 亚洲一区二区三区自拍| 波多野结衣啪啪| 日韩免费成人网| 国产人成在线视频| 欧美国产日韩一区二区| av在线不卡精品| 国产精品成人一区二区三区| 欧美午夜精品一区二区三区电影| 日韩极品视频在线观看| 日本亚洲最大的色成网站www| 性折磨bdsm欧美激情另类| 日本一区二区三区在线观看| 看片网站在线观看| 欧美在线视频日韩| 天天综合网在线| www.99久久热国产日韩欧美.com| 韩国成人二区| 国产成人精品免费视频大全最热| 日韩激情免费| 韩国日本在线视频| 成人禁用看黄a在线| 99自拍视频在线| 在线观看亚洲一区| 天天干免费视频| 精品中文字幕在线| 欧美videos粗暴| 日本精品二区| 国产精品试看| 怡红院一区二区| 一区二区三区欧美| 夜夜爽8888| 宅男66日本亚洲欧美视频| 日韩伦理在线一区| 国产午夜精品一区| 欧美xxx在线观看| 在线不卡一区二区三区| 欧美国产视频在线| 国产成人自拍偷拍| 日韩精品中文字幕视频在线| 波多野结衣乳巨码无在线观看| 91人成网站www| 久久一区二区三区喷水| 午夜国产一区二区三区| 国产亲近乱来精品视频| 天天干天天色综合| 亚洲美女福利视频网站| а√在线天堂官网| 国产精品日韩欧美一区二区三区| 综合久久综合| 992kp免费看片| 亚洲激情网站免费观看| 91片黄在线观看喷潮| 精品国产拍在线观看| 亚洲免费一区| 日本一本草久p| 国产成人综合在线观看| 永久久久久久久| 日韩欧美综合一区| 免费av不卡在线观看| 国产丝袜不卡| 亚洲永久免费| 国产精久久一区二区三区| 91福利国产精品| 最新av网站在线观看 | 日韩精品免费视频一区二区三区| 精品久久免费观看| 国产福利精品一区| 久草精品视频在线观看| 亚洲国产精品va| 自由日本语热亚洲人| 日韩精品欧美一区二区三区| 久久99精品久久久久婷婷| 国产精品视频一区二区三| 日韩美女在线视频| а√天堂中文在线资源8| 欧美日韩精品中文字幕一区二区| 蜜桃91丨九色丨蝌蚪91桃色| 午夜成人亚洲理伦片在线观看| 欧美一区二区三区在| 9765激情中文在线| 欧洲精品一区色| 狠狠网亚洲精品| 91精品国产高潮对白| 亚洲视频国产视频| 亚洲毛片在线免费| 免费观看美女裸体网站| 国产女主播在线一区二区| 国产精品毛片一区视频播 | 色在线视频网| 精品一区久久| 免费高清视频精品| 久久久美女视频| 亚洲欧洲美洲在线综合| 亚洲国产一区二区久久| 日韩欧美不卡在线| 中文字幕av一区二区三区免费看 | 国产一区二区三区av电影| 69精品久久久| 中文字幕日韩欧美| 成午夜精品一区二区三区软件| 性欧美videossex精品| 亚洲高清视频在线| 视频三区在线| 久久这里精品国产99丫e6| 国模无码大尺度一区二区三区|