普通视图

发现新文章,点击刷新页面。
昨天以前掘金专栏-百度Geek说

前沿多模态模型开发与应用实战3:DeepSeek-VL2多模态理解大模型算法解析与功能抢先体验

作者 百度Geek说
2025年4月3日 11:03

多模态理解大模型,是一类可以同时处理和理解多种数据形式(如图像、文本、视频等)的人工智能大模型,可以应用于图文理解、视觉问答、文档理解、场景描述等任务。本文将介绍目前热门的 DeepSeek-VL2多模态大模型。DeepSeek-VL2是一款基于混合专家(MoE,Mixture of Experts)架构的多模态大模型,结合了混合专家架构和多模态数据处理能力,通过稀疏计算和专家分工的方式高效处理多种模态(如文本、图像、音频等)的数据,推理时只激活部分网络参数。而前两期课程介绍的 Qwen2.5VL、Janus-Pro 以及 DeepSeek-VL第一代模型,则是经典的 Dense 类的多模态理解大模型,会对所有模型参数进行计算和更新。MoE(Mixture of Experts)混合专家模型的核心思想是将模型划分为多个专家子网络(experts),并通过路由机制(router)动态选择合适的专家来处理输入数据。MoE 的最大优势就是是稀疏激活,只有少数几个专家网络模块会被激活,这意味着计算量可以显著减少,计算效率得到提升,同时精度指标远远超出相同激活参数量的 Dense 类模型。

图片

DeepSeek-VL2在视觉理解上的效果展示

接下来,本篇文章内容将包括模型结构、训练流程、模型能力的展示,并以飞桨多模态开发套件 PaddleMIX 中 DeepSeek-VL2的实现为例,对代码进行逐步解读。

01 模型架构

DeepSeek-VL2的前身是去年发布的 DeepSeek-VL,其模型结构设计是经典的 Dense 模型结构,也就是有参数都会进行计算和更新。DeepSeek-VL 由三个主要模块组成:

  • Hybrid Vision Encoder:**混合视觉编码器,采用 SigLIP-L 作为视觉编码器,结合 SAM-B 和 SigLIP-L 编码器,能够高效处理高分辨率图像(1024×1024),同时保留语义和细节信息。高分辨率特征图经过插值和卷积处理后,与低分辨率特征图连接,生成具有2048个维度的视觉 token。

  • VL Adaptor:**视觉语言适配器,使用两层混合 MLP 桥接视觉编码器和语言模型。高分辨率和低分辨率特征分别经过单层 MLP 处理后沿维度连接,再通过另一层 MLP 转换到语言模型的输入空间。

  • DeepSeek LLM:**语言模型是 DeepSeek-LLM,其设计遵循 LLaMA,采用 Pre-Norm 结构和 SwiGLU 激活函数,使用旋转嵌入进行位置编码。

图片

DeepSeek-VL 架构

而近期发布的 DeepSeek-VL2尽管是 MoE 架构,但它也是由三部分核心模块组成:视觉编码器 Vision Encoder、视觉-语言适配器 VL Adaptor 和 DeepSeek-MoE 语言模型。与其前身 DeepSeek-VL 相比,DeepSeek-VL2在视觉编码器和语言建模部分都有了显著的提升,这主要是因为 DeepSeek-VL2引入了两项重大改进:动态切片策略,以及采用多头隐变量注意力(Multi-head Latent Attention,MLA)机制的 DeepSeek-MoE 语言模型。这些创新使得 DeepSeek-VL2能够更高效地处理高分辨率视觉输入和文本数据。

图片

DeepSeek-VL2架构

  • Vision Encoder:**DeepSeek-VL2采用的也是 SigLIP,同时引入了动态切片策略(Dynamic Tiling Strategy),能够处理不同分辨率和长宽比的高分辨率图像。传统的图像编码方法往往固定分辨率,导致在处理较大或不规则图像时性能下降。动态切片策略通过将高分辨率图像分割成多个小块进行处理,减少了计算成本,同时保留了详细的视觉特征。该方法避免了传统视觉编码器的固定分辨率限制,使得模型在处理复杂图像任务(如视觉引导、文档分析等)时具有更好的性能。

  • VL Adaptor:**DeepSeek-VL2采用两层多层感知器(MLP),然后再使用2×2 pixel shuffle 操作压缩每个图像块的 token 数目,用于视觉特征映射到文本空间。

  • DeepSeek-MoE LLM:**语言模型采用了 DeepSeek-MoE(Mixture of Experts)架构,并结合了多头潜在注意力机制(Multi-head Latent Attention,MLA)。MLA 机制能够有效压缩键值缓存(KV Cache),提升推理效率。MoE 架构则通过稀疏计算进一步提升了效率,使得模型在处理大规模数据时能够实现更高的吞吐量。

在模型尺寸上,DeepSeek-VL2系列目前有以下3个参数版本:DeepSeek-VL2-Tiny、DeepSeek-VL2-Small 和 DeepSeek-VL2,分别拥有1B、2.8B 和4.5B 的激活参数。具体的结构设置如下表所示:

图片

DeepSeek-VL2三种参数量的模型设置

02 创新点

2.1 动态图像切片编码策略

动态切片策略

DeepSeek-VL2将一张高分辨率图像切片,为了适应不同长宽比,首先定义了一组候选分辨率:CR={(m⋅384,n⋅384) ∣ m∈N,n∈N,1≤m,n,mn≤9}, m:n表示宽高比。对于一张(H,W)图像,在保证宽高比不变下调整图像分辨率,计算以长边对其到候选分辨率所需要的填充的区域面积。选择面积最小的分辨率 (mi⋅384,ni⋅384),然后将调整大小后的图像划分成 mi×ni 个384×384分辨率的局部图块以及一个全局缩略图块。出于计算效率和上下文长度管理的考虑,在处理多于2张图像时,禁用动态图块策略。

图片

DeepSeek-VL2中的动态切片策略

在将图片切片后,再使用2×2 pixel shuffle 操作压缩每个图像块的 token 数目,从27×27压缩至14×14=196 tokens 对于全局缩略图像块(14×14),在每一行的末尾添加14个标记,从而总共得到14×15=210个 tokens。当处理 mi×ni 个局部图像块时,在每一行的局部块末尾新增,共新增 mi⋅14个 tokens,完整的 Visual Token 包含210+1+mi⋅14×(ni⋅14+1) 个视觉标记,这些 Tokens 随后使用两层多层感知器(MLP)投影到语言模型的 Embedding 空间中。

2.2 DeepSeek-MoE语言模型

在语言模型部分,DeepSeek-VL2使用了 DeepSeek-MoE 语言模型,该模型结合了混合专家(Mixture of Experts, MoE)架构和多头潜在注意力(Multi-head Latent Attention,MLA)机制。MoE 架构通过选择性激活不同的专家网络,实现了计算资源的高效利用和模型性能的提升。而 MLA 机制 MLA 机制通过将键值缓存压缩为潜在向量,增强了推理效率,从而提升了吞吐量,且能够在处理多模态信息时,更好地捕捉到视觉和语言之间的复杂关系,进而提升模型在图文理解、问答等任务中的表现。

在 MoE 训练过程中,为每个专家引入了一个全局偏置项,以经济高效的方式改善专家之间的负载均衡。现有的 MoE 架构可能存在知识混杂(Knowledge Hybridity)和知识冗余(Knowledge Redundancy)的问题,限制了专家的专业化。在实现思想上,DeepSeek-MoE 采用两个主要策略:

  • Fine-Grained Expert Segmentation-细粒度的专家分割,通过细化 FFN 中间隐藏维度,维持参数数量不变的同时激活更多细粒度的专家,使得激活的专家更加灵活和适应性更强;

  • Shared Expert Isolation-共享专家隔离,将某些专家隔离为共享专家,始终激活,旨在捕捉和巩固不同上下文中的共同知识。

图片

DeepSeek-MOE 的架构

2.3 高校的推理速度与吞吐量

为了提升模型的推理速度,DeepSeek-VL2在语言部分的处理上引入了键值缓存压缩技术。这项技术能够有效减少计算中的冗余操作,从而提高推理过程的效率,尤其在处理大规模数据时表现出色。通过这种优化,DeepSeek-VL2在多个任务上不仅表现出了更高的准确率,也大大提升了计算效率。

03 训练方法

3.1 训练数据

DeepSeek-VL2从多种来源构建了一个综合性的视觉-语言数据集。训练过程分为三个阶段:(1)视觉-语言对齐(VL alignment);(2)视觉-语言预训练(VL pretraining);(3)监督微调(Supervised Fine-Tuning)。

1.VL alignment 数据

对齐阶段专注于训练多层感知机(MLP)VL Adaptor,以桥接预训练的视觉编码器和大型语言模型。这一阶段使用了 ShareGPT4V 数据集,该数据集包含大约120万个描述和对话样本。

2.VL-Pretrain 数据

VL-Pretrain 数据结合了视觉-语言数据和纯文本数据,以保持 VL 能力和纯文本性能之间的平衡。对于 DeepSeek-VL2,作者保持了大约70%的 VL 数据和30%的纯文本数据的比例,后者直接来源于作者基础大型语言模型(LLM)的预训练语料库。

Image-Text 混合数据

数据收集始于几个开源数据集,包括 WIT、WikiHow 和 OBELICS 中的30%随机样本。这一特定的混合比例是通过使用 DeepSeek-VL2-Tiny 进行初步实验确定的。为了增强多语言能力,在主要以英语为主的数据集中补充了从 Wanjuan 中提取的中文内容。此外,DeepSeek-VL2还开发了一个内部数据集,以扩大对一般现实世界知识的覆盖范围。

Image Caption 数据

图像描述是视觉语言模型(VLM)训练中的基础数据,提供了视觉信息和文本信息之间的直接对齐。因为开源数据集质量差异很大,为了解决这些质量不一致的问题,DeepSeek-VL2开发了一个全面的图像描述流程,该流程考虑了:(1)光学字符识别(OCR)提示;(2)元信息(例如位置、相机设置);(3)原始描述作为提示。DeepSeek-VL2使用内部 Captioner,使用 类似于 PixelProse 的提示策略重新为图像添加描述,采用不同的指令来指导 VLM 生成描述。尽管 Catpion 整体质量有所提高,在大规模标注流程中观察到了重复问题。为了缓解这一问题,DeepSeek-VL2采用一个质量控制流程,使用 DeepSeek Chat 仅根据 Caption 的写作质量进行评分。

OCR 数据

LaTex OCR 和12M RenderedText、包括不同文档类型的大规模内部数据集

VQA 数据

  • DeepSeek-VL 通用的 VQA 数据。

  • 表格、图表和文档理解数据。PubTabNet、FinTabNet 和 Docmatix。

  • Web-to-code 和 plot-to-Python 生成。Websight,并遵循 DeepSeek-VL 的方法,使用公开的 Jupyter 笔记本中的 Python 图表。通过使用 DeepSeek V2.5对 Websight 部分数据增强。作者还利用 DeepSeek V2.5生成的 Python 图表代码来减少 plot-to-code 中的噪声。

  • 包括视觉提示的 QA 数据:参考 Vip-llava 构建具有不同视觉提示(箭头、方框、圆圈和涂鸦)的数据。

Visual grounding 数据

基于 Kosmos-2和 Objects365构建 视觉定位数据,并采用以下模版构建

  • Prompt: \texttt{Locate <|ref|><|/ref|> in the given image.}

  • Response: \texttt{<|ref|><|/ref|><|det|>[[x1, y1, x2, y2],\ldots]<|/det|>}

Grounded 对话数据

基于 Kosmos-2构建视觉定位对话数据并采用以下模版构建

  • Prompt: \texttt{<|grounding|>Can you describe the content of the image?}
  • Response: $\texttt{Two <|ref|>dogs<|/ref|><|det|>[[x1, y1, x2, y2],\ldots]<|/det|> are running on the grass.}

3.SFT 数据

DeepSeek-VL2的 SFT 数据结合了多种开源数据集与高质量的内部 QA 对。

General visual question-answering

虽然 VQA 数据集种类繁多,但它们通常存在三大局限:(1)回答简短;(2)光学字符识别(OCR)质量不佳;(3)内容虚幻。为解决这些问题,DeepSeek-VL2综合考虑原始问题、图像和 OCR 信息来重新生成回答。作者的实验表明,这种方法能产生更全面、更准确的结果。在 DeepSeek-VL2的开发过程中早期版本,尤其是 Tiny 变体,偶尔会在中文回答中不恰当地插入英文单词。这一问题在 DeepSeek-VL2大型模型中并不存在,这表明它源于模型容量有限以及视觉-语言预训练阶段中英文数据的不平衡。为解决小型模型中的这一局限,DeepSeek-VL2团队开发了一个包含多样图像描述和单轮/多轮对话的内部中文问答数据集。该数据集有助于缓解语言混合问题。此外还创建了补充现实世界的和文化相关的视觉知识,包括动漫、网络梗、美食和艺术的内部数据集。

OCR and document understanding

得益于 DeepSeek-VL2先进的 Caption Pipeline,DeepSeek-VL2已经展现出比其他最先进的视觉语言模型(VLM)更优越的 OCR 能力。因此,在 SFT 阶段未进一步提升 OCR 性能,而是专注于清理现有的开源数据集,通过移除 OCR 质量不佳的样本。对于文档理解,DeepSeek-VL2团队从内部数据中筛选了一个多样化的文档页面子集。然后针对文档理解生成了多轮对话式问答对。

Table and chart understanding

通过对除 Cauldron(其已展现出高质量)外的所有公共数据集基于其原始问题重新生成回答,从而增强了基于表格的问答数据。与在视觉语言预训练阶段开发的 OCR 能力类似,的模型在图表理解方面也表现出色,且无需额外努力。

Textbook and academic questions

从文档集合中构建了一个专注于教科书的内部数据集。该数据集主要强调多个学科领域的大学水平内容。

Web-to-code and plot-to-Python generation

网页到代码与图表到 Python 代码生成。扩展了内部关于网页代码和 Python 图表代码的数据集,这些数据集超出了预训练期间所使用的范围。对于开源数据集,通过重新生成答案来提高其质量。

纯文本数据

为了保持模型的语言能力,在 SFT 阶段,还使用了纯文本指令调整数据集。

3.2 训练阶段

DeepSeek-VL2通过三阶段的流程进行训练:

  • 初始阶段:使用3.1.1节中详细描述的图文配对数据,训练视觉编码器和视觉-语言适配器 MLP,同时保持语言模型固定。
  • 预训练阶段:使用3.1.2节描述的数据进行视觉-语言预训练。在此阶段,所有模型参数,包括视觉编码器、视觉-语言适配器和语言模型,都会解锁并同时训练。
  • 微调阶段:使用第3.1.3节概述的数据进行有监督的微调,进一步优化模型性能。

在预训练和微调阶段,强调视觉理解能力,并仅在文本标记上计算下一个标记预测损失。

视觉-语言对齐

基于预训练的语言模型(DeepSeekMoE 3B/16B/27B),的主要目标是建立视觉特征和语言特征之间的稳固连接。这种对齐使得预训练的语言模型能够有效地处理视觉输入。与之前的方法不同,这些方法保持预训练的视觉编码器和语言模型固定,调整固定分辨率的视觉编码器以适应动态高分辨率图像。在这个阶段,优化视觉编码器和视觉-语言适配器,同时保持语言模型冻结。

视觉-语言预训练 在嵌入空间中建立视觉-语言对齐之后,将大部分计算资源用于视觉-语言预训练。这个阶段的重点是开发跨多种任务的综合性联合视觉-语言知识。解锁所有参数,包括视觉编码器、视觉-语言适配器和语言模型,并同时进行训练。通过这些阶段的系统训练,DeepSeek-VL2不仅能够处理高分辨率的视觉输入,还能够在多模态任务中表现出色。这种训练方法使得模型在多样化的任务中提高了视觉和语言理解能力。

有监督微调 在最后阶段,通过有监督的微调来增强预训练模型的指令跟随能力和对话能力。利用内部的视觉语言 SFT 数据,优化所有参数,但仅对答案和特殊标记进行监督,同时屏蔽系统和用户提示。为了加强对话理解,将多模态数据与来自 DeepSeek-V2的纯文本对话数据结合使用。这种方法确保了在各种视觉语言任务中具有强大的性能,包括密集图像描述、通用视觉问答(VQA)、光学字符识别(OCR)、表格/图表/文档/图形理解、视觉到代码、视觉推理、视觉定位和语言理解等。

图片

DeepSeek-VL2的训练超参数

3.3 结果评估

DeepSeek-VL2在多个常用的多模态基准数据集上进行了评估,包括 DocVQA、ChartQA、InfoVQA、TextVQA 等。这些基准涵盖了从文档理解到逻辑推理等多种任务,全面评估了 DeepSeek-VL2在不同任务上的表现。

视觉引导能力

DeepSeek-VL2在视觉引导任务上展现了强大的能力,能够根据图像中的描述性信息准确定位物体,并生成相应的回答。

多图像对话能力

DeepSeek-VL2在处理多图像对话任务时表现突出,能够分析多张图片之间的关系,并基于这些信息进行简单的推理。

视觉故事生成能力

在视觉故事生成任务中,DeepSeek-VL2能够根据图片创作出创意十足的故事,并且能够有效结合图像中的细节,如地标识别和 OCR 结果。

图片

DeepSeek-VL2 OCR 相关能力指标结果

图片

DeepSeek-VL2用 VQA 和数学相关能力指标结果

图片

DeepSeek-VL2视觉故事生成能力展示

04 代码解读

下面以 PaddleMIX 中 DeepSeek-VL2的实现为例,对关键创新点的代码实现进行讲解。

PaddleMIX

github.com/PaddlePaddl…

4.1 标动态切片策略

功能

该函数 select_best_resolution 的目的是在给定的候选分辨率列表中找到最适合原始图像大小的分辨率。

步骤实现

DeepSeek-VL2在处理多图像对话任务时表现突出,能够分析多张图片之间的关系,并基于这些信息进行简单的推理。

  • 计算缩放比例:对于每个候选分辨率,计算其相对于原始图像尺寸的缩放比例(取宽度和高度缩放比例中的最小值)。

  • 计算缩放后的尺寸:使用上述缩放比例计算缩放后的图像宽度和高度。

  • 计算有效分辨率:有效分辨率是缩放后的图像分辨率与原始图像分辨率中较小的一个。这是为了确保缩放后的图像不会比原始图像具有更高的分辨率。

  • 计算浪费的分辨率:浪费的分辨率是候选分辨率的面积减去有效分辨率的面积。

  • 选择最佳匹配:遍历所有候选分辨率,找到有效分辨率最大且浪费分辨率最小的那个作为最佳匹配。如果两个候选分辨率的有效分辨率相同,则选择浪费分辨率较小的那个。

输出

返回一个元组,代表最佳匹配的分辨率(宽度和高度)。如果没有找到任何合适的分辨率,理论上应该返回 None(尽管在当前的实现中,如果至少有一个候选分辨率,它总是会返回一个结果)。

def select_best_resolution(image_size, candidate_resolutions):
    original_width, original_height = image_size
    best_fit = None
    max_effective_resolution = 0
    min_wasted_resolution = float("inf")

    for width, height in candidate_resolutions:
        scale = min(width / original_width, height / original_height)
        downscaled_width, downscaled_height = int(original_width * scale), int(original_height * scale)
        effective_resolution = min(downscaled_width * downscaled_height, original_width * original_height)
        wasted_resolution = width * height - effective_resolution

        if (
            effective_resolution > max_effective_resolution
            or effective_resolution == max_effective_resolution
            and wasted_resolution < min_wasted_resolution
        ):
            max_effective_resolution = effective_resolution
            min_wasted_resolution = wasted_resolution
            best_fit = width, height

    return best_fit


4.2 VL Adapter

方法

tokenize_with_images

功能

该函数 tokenize_with_images 的目的是将包含 texttt\\texttt{} 标签的文本对话进行分词处理,并同时处理与文本对话相关联的图像。它将文本和图像转换为适合模型处理的格式,包括图像的分辨率调整、裁剪、以及将文本和图像转换为一系列 tokens。

参数

  • conversation:包含 texttt\\texttt{} 标签的原始文本对话。

  • images:与文本对话中的 texttt\\texttt{} 标签相对应的图像列表。

  • bos:布尔值,指定是否在分词结果的开头添加开始序列(Begin Of Sequence, BOS) token。默认为 True。

  • eos:布尔值,指定是否在分词结果的末尾添加结束序列(End Of Sequence, EOS)token。默认为 True。

  • cropping:布尔值,指定是否对图像进行裁剪以适应特定的分辨率。默认为 True。

步骤实现

  • 断言检查:确保文本对话中的 texttt\\texttt{} 标签数量与提供的图像数量相匹配。

  • 文本分割:使用 texttt\\texttt{} 标签将文本对话分割成多个部分。

  • 初始化列表:用于存储处理后的图像、图像序列掩码、图像空间裁剪信息、图像 token 数量以及分词后的字符串。

  • 遍历文本和图像:对于每个文本部分和对应的图像,执行以下操作:

  • 文本分词:将文本部分分词,但不添加 BOS 和 EOS token。

  • 图像分辨率选择:根据裁剪标志选择最佳图像分辨率。

  • 全局视图处理:将图像调整为固定大小(self.image_size),并填充背景色。

  • 局部视图处理:根据最佳分辨率将图像裁剪成多个小块,并对每个小块进行处理。

  • 记录裁剪信息:记录每个图像在宽度和高度上被裁剪成的小块数量。

  • 添加图像 token:为每个图像(全局和局部视图)生成一系列图像 token,并添加到分词后的字符串中。

  • 更新掩码和 token 数量:更新图像序列掩码和图像 token 数量列表。

  • 处理最后一个文本部分:对最后一个文本部分进行分词处理(但不添加 BOS 和 EOS token),并更新分词后的字符串和图像序列掩码。

  • 添加 BOS 和 EOS token:根据参数设置,在分词结果的开头和末尾添加 BOS 和 EOS token。

  • 断言检查:确保分词后的字符串长度与图像序列掩码的长度相匹配。

输出

返回一个元组,包含以下内容:

  • tokenized_str:分词后的字符串,包含文本和图像 token。

  • images_list:处理后的图像列表,包括全局视图和局部视图。

  • images_seq_mask:图像序列掩码,用于指示哪些 token 是图像 token。

  • images_spatial_crop:图像空间裁剪信息,记录每个图像在宽度和高度上的裁剪小块数量。

  • num_image_tokens:每个图像对应的 token 数量列表。

def tokenize_with_images(
        self, conversation: str, images: List[Image.Image], bos: bool = True, eos: bool = True, cropping: bool = True
    ):
        """Tokenize text with <image> tags."""
        assert conversation.count(self.image_token) == len(images)
        text_splits = conversation.split(self.image_token)
        images_list, images_seq_mask, images_spatial_crop = [], [], []
        num_image_tokens = []
        tokenized_str = []
        for text_sep, image in zip(text_splits, images):
            """encode text_sep"""
            tokenized_sep = self.encode(text_sep, bos=False, eos=False)
            tokenized_str += tokenized_sep
            images_seq_mask += [False] * len(tokenized_sep)
            """select best resolution for anyres"""
            if cropping:
                best_width, best_height = select_best_resolution(image.size, self.candidate_resolutions)
            else:
                best_width, best_height = self.image_size, self.image_size

            """process the global view"""
            global_view = ImageOps.pad(
                image, (self.image_size, self.image_size), color=tuple(int(x * 255for x in self.image_transform.mean)
            )
            images_list.append(self.image_transform(global_view))

            """process the local views"""
            local_view = ImageOps.pad(
                image, (best_width, best_height), color=tuple(int(x * 255for x in self.image_transform.mean)
            )

            for i in range(0, best_height, self.image_size):
                for j in range(0, best_width, self.image_size):
                    images_list.append(
                        self.image_transform(local_view.crop((j, i, j + self.image_size, i + self.image_size)))
                    )

            """record height / width crop num"""
            num_width_tiles, num_height_tiles = (best_width // self.image_size, best_height // self.image_size)
            images_spatial_crop.append([num_width_tiles, num_height_tiles])

            """add image tokens"""
            h = w = math.ceil(self.image_size // self.patch_size / self.downsample_ratio)
            tokenized_image = [self.image_token_id] * h * (w + 1)
            tokenized_image += [self.image_token_id]
            tokenized_image += [self.image_token_id] * (num_height_tiles * h) * (num_width_tiles * w + 1)
            tokenized_str += tokenized_image
            images_seq_mask += [True] * len(tokenized_image)
            num_image_tokens.append(len(tokenized_image))

        """process the last text split"""
        tokenized_sep = self.encode(text_splits[-1], bos=False, eos=False)
        tokenized_str += tokenized_sep
        images_seq_mask += [False] * len(tokenized_sep)

        """add the bos and eos tokens"""
        if bos:
            tokenized_str = [self.bos_id] + tokenized_str
            images_seq_mask = [False] + images_seq_mask
        if eos:
            tokenized_str = tokenized_str + [self.eos_id]
            images_seq_mask = images_seq_mask + [False]
        assert len(tokenized_str) == len(
            images_seq_mask
        ), f"tokenize_with_images func: tokenized_str's length {len(tokenized_str)} is not equal to imags_seq_mask's length {len(images_seq_mask)}"
        return (tokenized_str, images_list, images_seq_mask, images_spatial_crop, num_image_tokens)


4.3 MLA(Multi-head Latent Attention)

类名

DeepseekV2Attention

主要功能

实现多头注意力机制,用于处理序列数据,支持缓存机制和不同的 RoPE(Rotary Position Embedding)缩放策略。

初始化参数 (init)

  • config:DeepseekV2Config 类型的配置对象,包含模型的各种配置参数。

  • layer_idx:可选参数,表示当前层的索引,用于缓存机制。

前向传播参数 (forward)

  • hidden_states:paddle.Tensor 类型的输入张量,表示隐藏状态。

  • attention_mask:可选参数,paddle.Tensor 类型的注意力掩码,用于屏蔽不需要关注的位置。

  • position_ids:可选参数,paddle.Tensor 类型的位置编码,用于 RoPE。

  • past_key_value:可选参数,Tuple[paddle.Tensor] 类型的缓存键值对,用于加速推理。

  • output_attentions:布尔类型,表示是否输出注意力权重。

  • use_cache:布尔类型,表示是否使用缓存机制。

  • kwargs:其他可选参数。

前向传播 (forward)

实现多头注意力机制,用于处理序列数据,支持缓存机制和不同的 RoPE(Rotary Position Embedding)缩放策略。

1.查询投影(Query Projection):

  • 如果 q_lora_rank 为 None,则使用 q_proj 对查询进行投影。

  • 否则,使用基于 LoRA 的投影(q_a_proj、q_a_layernorm 和 q_b_proj)。

2.键值投影(Key-Value Projection):

  • 使用 kv_a_proj_with_mqa 对键和值进行投影。

  • 将结果拆分为 LoRA 和 RoPE 组件。

  • RoPE 应用(RoPE Application)。

  • 计算 RoPE 的余弦和正弦值。

  • 将 RoPE 应用于查询和键。

3.缓存(Caching):

  • 如果 use_cache 为 True,则更新缓存的键和值。

注意力权重(Attention Weights)

实现多头注意力机制,用于处理序列数据,支持缓存机制和不同的 RoPE(Rotary Position Embedding)缩放策略。

  • 使用缩放点积注意力计算注意力分数。

  • 应用注意力掩码和 softmax。

  • 输出投影(Output Projection)。

  • 使用注意力权重和投影后的值计算注意力输出。

  • 应用输出投影(o_proj)。

class DeepseekV2Attention(paddle.nn.Layer):
    """Multi-headed attention from 'Attention Is All You Need' paper"""

    def __init__(self, config: DeepseekV2Config, layer_idx: Optional[int] = None):
        super().__init__()
        self.config = config
        """
        ..............
        """
        if self.q_lora_rank is None:
            self.q_proj = nn.Linear(self.hidden_size, self.num_heads * self.q_head_dim, bias_attr=False)
        else:
            self.q_a_proj = nn.Linear(self.hidden_size, config.q_lora_rank, bias_attr=config.attention_bias)
            self.q_a_layernorm = DeepseekV2RMSNorm(config=config, hidden_size=config.q_lora_rank)
            self.q_b_proj = nn.Linear(config.q_lora_rank, self.num_heads * self.q_head_dim, bias_attr=False)

        self.kv_a_proj_with_mqa = nn.Linear(self.hidden_size, config.kv_lora_rank + config.qk_rope_head_dim, bias_attr=config.attention_bias)
        self.kv_a_layernorm = DeepseekV2RMSNorm(config=config, hidden_size=config.kv_lora_rank)
        self.kv_b_proj = nn.Linear(config.kv_lora_rank, self.num_heads * (self.q_head_dim - self.qk_rope_head_dim + self.v_head_dim), bias_attr=False)

        self.o_proj = nn.Linear(self.num_heads * self.v_head_dim, self.hidden_size, bias_attr=config.attention_bias)

        self._init_rope()

        self.softmax_scale = self.q_head_dim**-0.5
        if self.config.rope_scaling is not None:
            mscale_all_dim = self.config.rope_scaling.get("mscale_all_dim"0)
            scaling_factor = self.config.rope_scaling["factor"]
            if mscale_all_dim:
                mscale = yarn_get_mscale(scaling_factor, mscale_all_dim)
                self.softmax_scale = self.softmax_scale * mscale * mscale

    def forward(
        self,
        hidden_states: paddle.Tensor,
        attention_mask: Optional[paddle.Tensor] = None,
        position_ids: Optional[paddle.Tensor] = None,
        past_key_value: Optional[Tuple[paddle.Tensor]] = None,
        output_attentions: bool = False,
        use_cache: bool = False,
        **kwargs,
    ) -> Tuple[paddle.Tensor, Optional[paddle.Tensor], Optional[Tuple[paddle.Tensor]]]:

        bsz, q_len, _ = tuple(hidden_states.shape)
        if self.q_lora_rank is None:
            q = self.q_proj(hidden_states)
        else:
            q = self.q_b_proj(self.q_a_layernorm(self.q_a_proj(hidden_states)))

        q = q.reshape([bsz, q_len, self.num_heads, self.q_head_dim]).transpose(perm=[0213])
        q_nope, q_pe = paddle.split(q, [self.qk_nope_head_dim, self.qk_rope_head_dim], axis=-1)

        compressed_kv = self.kv_a_proj_with_mqa(hidden_states)
        compressed_kv, k_pe = paddle.split(compressed_kv, [self.kv_lora_rank, self.qk_rope_head_dim], axis=-1)
        compressed_kv = self.kv_a_layernorm(compressed_kv)
        k_pe = k_pe.reshape([bsz, q_len, 1, self.qk_rope_head_dim]).transpose(perm=[0213])

        kv_seq_len = tuple(k_pe.shape)[-2]
        if past_key_value is not None:
            kv_seq_len += past_key_value[0].shape[1]
        cos, sin = self.rotary_emb(q_pe, seq_len=kv_seq_len)
        q_pe, k_pe = apply_rotary_pos_emb(q_pe, k_pe, cos, sin, position_ids)

        if use_cache and past_key_value is not None:
            compressed_kv = compressed_kv.unsqueeze(axis=2)
            k_pe = k_pe.transpose(perm=[0213])  # (b h l d) to (b l h d)
            k_pe = paddle.concat([past_key_value[0], k_pe], axis=1)
            compressed_kv = paddle.concat([past_key_value[1], compressed_kv], axis=1)

            past_key_value = (k_pe, compressed_kv)

            k_pe = k_pe.transpose(perm=[0213])  # go back to (b l h d)
            compressed_kv = compressed_kv.squeeze(2)
        elif use_cache:
            past_key_value = (k_pe.transpose([0213]), compressed_kv.unsqueeze(axis=2))
        else:
            past_key_value = None

        # shit tranpose liner weight
        kv_b_proj = self.kv_b_proj.weight.T.reshape([self.num_heads, -1, self.kv_lora_rank])
        q_absorb = kv_b_proj[:, :self.qk_nope_head_dim, :]
        out_absorb = kv_b_proj[:, self.qk_nope_head_dim:, :]

        q_nope = paddle.matmul(q_nope, q_absorb)
        attn_weights = (
            paddle.matmul(q_pe, k_pe.transpose([0132])) # [1, 16, 1304, 64] * [1, 1, 1304, 64]
            + paddle.matmul(q_nope, compressed_kv.unsqueeze(axis=-3).transpose([0132])) #  [1, 16, 1304, 512] * [1, 1, 1304, 512]
        ) * self.softmax_scale

        if tuple(attn_weights.shape) != (bsz, self.num_heads, q_len, kv_seq_len):
            raise ValueError(
                f"Attention weights should be of size {bsz, self.num_heads, q_len, kv_seq_len}, but is {tuple(attn_weights.shape)}"
            )
        assert attention_mask is not None
        if attention_mask is not None:
            if tuple(attention_mask.shape) != (bsz, 1, q_len, kv_seq_len):
                raise ValueError(
                    f"Attention mask should be of size {bsz, 1, q_len, kv_seq_len}, but is {tuple(attention_mask.shape)}"
                )
            attn_weights = attn_weights + attention_mask

        # upcast attention to fp32
        attn_weights = F.softmax(attn_weights, axis=-1, dtype="float32").to(q_pe.dtype)
        attn_weights = F.dropout(attn_weights, self.attention_dropout, training=self.training)
        attn_output = paddle.einsum("bhql,blc->bhqc", attn_weights, compressed_kv)
        attn_output = paddle.matmul(attn_output, out_absorb.transpose([021]))

        if tuple(attn_output.shape) != (bsz, self.num_heads, q_len, self.v_head_dim):
            raise ValueError(
                f"`attn_output` should be of size {bsz, self.num_heads, q_len, self.v_head_dim}, but is {tuple(attn_output.shape)}"
            )
        attn_output = attn_output.transpose([0213])
        attn_output = attn_output.reshape([bsz, q_len, self.num_heads * self.v_head_dim])
        attn_output = self.o_proj(attn_output)
        if not output_attentions:
            attn_weights = None
        return attn_output, attn_weights, past_key_value


4.4 DeepSeekV2-MoE

类名

DeepseekV2MoE

主要功能

实现混合专家机制,通过路由机制将输入分配给多个专家网络,并将结果加权组合。

初始化参数 (init)

config:配置对象,包含模型的各种参数,如专家数量、共享专家数量、中间层大小等。

步骤实现

1.初始化 (init):

  • 从配置对象中读取参数,如专家数量、共享专家数量、中间层大小等。

  • 根据分布式环境(ep_size)分配专家网络到不同的设备上。

  • 初始化专家网络列表(self.experts)和共享专家网络(self.shared_experts)。

  • 初始化门控机制(self.gate)。

2.前向传播 (forward):

  • 保存输入张量的原始形状和值(identity 和 orig_shape)。

  • 使用门控机制(self.gate)计算路由索引(topk_idx)、路由权重(topk_weight)和辅助损失(aux_loss)。

  • 将输入张量展平以便处理。

  • 训练模式:

    ·将输入张量复制多次以匹配每个专家的输入。

    ·根据路由索引将输入分配给对应的专家网络,并计算输出。

    ·对专家输出进行加权求和,并恢复原始形状。

    ·添加辅助损失(AddAuxiliaryLoss.apply)。

  • 推理模式:

    ·调用 moe_infer 方法处理输入,并恢复原始形状。

    ·如果存在共享专家网络,将其输出与专家网络的输出相加。

    ·返回最终的输出张量。

class DeepseekV2MoE(paddle.nn.Layer):
    """
    A mixed expert module containing shared experts.
    """
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.num_experts_per_tok = config.num_experts_per_tok
        if hasattr(config, "ep_size") and config.ep_size > 1:
            assert config.ep_size == dist.get_world_size()
            self.ep_size = config.ep_size
            self.experts_per_rank = config.n_routed_experts // config.ep_size
            self.ep_rank = dist.get_rank()
            self.experts = nn.ModuleList(
                [
                    (
                        DeepseekV2MLP(
                            config, intermediate_size=config.moe_intermediate_size
                        )
                        if i >= self.ep_rank * self.experts_per_rank
                        and i < (self.ep_rank + 1) * self.experts_per_rank
                        else None
                    )
                    for i in range(config.n_routed_experts)
                ]
            )
        else:
            self.ep_size1
            self.experts_per_rank = config.n_routed_experts
            self.ep_rank0
            self.experts = nn.LayerList(
                [
                    DeepseekV2MLP(config, intermediate_size=config.moe_intermediate_size)
                    for i in range(config.n_routed_experts)
                ]
            )
        self.gate = MoEGate(config)
        if config.n_shared_experts is not None:
            intermediate_size = config.moe_intermediate_size * config.n_shared_experts
            self.shared_experts = DeepseekV2MLP(config=config, intermediate_size=intermediate_size)

    def forward(self, hidden_states):
        identity = hidden_states
        orig_shape = hidden_states.shape
        topk_idx, topk_weight, aux_loss = self.gate(hidden_states)
        hidden_states = hidden_states.reshape([-1, hidden_states.shape[-1]])
        flat_topk_idx = topk_idx.reshape([-1])
        # remove the infer method
        if self.training:
            hidden_states = hidden_states.repeat_interleave(self.num_experts_per_tok, axis=0)
            y = paddle.empty_like(hidden_states)
            for i, expert in enumerate(self.experts):
                # y[flat_topk_idx == i] = expert(hidden_states[flat_topk_idx == i])
                if paddle.any(flat_topk_idx == i):
                    y[flat_topk_idx == i] = expert(hidden_states[flat_topk_idx == i])

            y = (y.reshape([*topk_weight.shape, -1]) * topk_weight.unsqueeze(-1)).sum(axis=1)
            y = paddle.cast(y, hidden_states.dtype).reshape([*orig_shape])
            if self.gate.alpha > 0.0:
                y = AddAuxiliaryLoss.apply(y, aux_loss)
        else:
            y = self.moe_infer(hidden_states, topk_idx, topk_weight).reshape([*orig_shape])
        if self.config.n_shared_experts is not None:
            y = y + self.shared_experts(identity)
        return y


4.5 MoEGate

类名

MoEGate

主要功能

实现混合专家机制的门控逻辑,包括路由权重计算、专家选择和辅助损失计算。

初始化参数 (init)

config:配置对象,包含模型的各种参数,如专家数量、路由缩放因子、评分函数等。

步骤实现 1.初始化 (init):

  • 从配置对象中读取参数,如专家数量、路由缩放因子、评分函数等。

  • 初始化门控权重(self.weight)和路由策略相关参数。

  • 如果使用 noaux_tc 路由策略,初始化专家评分校正偏置(self.e_score_correction_bias)。

  • 调用 reset_parameters 方法初始化权重。

2.权重初始化 (reset_parameters):

  • 使用 Kaiming 均匀分布初始化门控权重。

3.前向传播 (forward):

  • 将输入张量展平以便处理。

  • 使用线性变换计算路由得分(logits)。

  • 根据评分函数(如 softmax 或 sigmoid)计算路由权重(scores)。

  • 如果 top_k > 1 且 norm_topk_prob 为 True,对路由权重进行归一化。

  • 在训练模式下,计算辅助损失(aux_loss)以优化路由机制。

  • 返回路由索引(topk_idx)、路由权重(topk_weight)和辅助损失(aux_loss)。

class MoEGate(paddle.nn.Layer):
    def __init__(self, config):
        super().__init__()
        self.config = config
        self.top_k = config.num_experts_per_tok
        self.n_routed_experts = config.n_routed_experts
        self.routed_scaling_factor = config.routed_scaling_factor
        self.scoring_func = config.scoring_func
        self.alpha = config.aux_loss_alpha
        self.seq_aux = config.seq_aux
        self.topk_method = config.topk_method
        self.n_group = config.n_group
        self.topk_group = config.topk_group
        # topk selection algorithm
        self.norm_topk_prob = config.norm_topk_prob
        self.gating_dim = config.hidden_size
        self.weight = paddle.base.framework.EagerParamBase.from_tensor(
            tensor=paddle.empty(shape=(self.gating_dim, self.n_routed_experts))
        )
        if self.topk_method == "noaux_tc":
            self.e_score_correction_bias = paddle.base.framework.EagerParamBase.from_tensor(
                tensor=paddle.empty(shape=[self.n_routed_experts])
            )

    def forward(self, hidden_states):
        bsz, seq_len, h = tuple(hidden_states.shape)
        hidden_states = hidden_states.reshape([-1, h])
        logits = paddle.nn.functional.linear(
            x=hidden_states.astype("float32"), weight=self.weight.astype("float32"), bias=None
        )
        if self.scoring_func == "softmax":
            scores = paddle.nn.functional.softmax(logits, axis=-1, dtype="float32")
        elif self.scoring_func == "sigmoid":
            scores = logits.sigmoid()
        else:
            raise NotImplementedError(f"insupportable scoring function for MoE gating: {self.scoring_func}")
        if self.topk_method == "greedy":
            topk_weight, topk_idx = paddle.topk(k=self.top_k, sorted=False, x=scores, axis=-1)
        elif self.topk_method == "group_limited_greedy":
            group_scores = scores.reshape(bsz * seq_len, self.n_group, -1).max(dim=-1).values

            group_idx = paddle.topk(k=self.topk_group, sorted=False, x=group_scores, axis=-1)[1]
            group_mask = paddle.zeros_like(x=group_scores)
            group_mask.put_along_axis_(axis=1, indices=group_idx, values=1, broadcast=False)
            score_mask = (
                group_mask.unsqueeze(axis=-1)
                .expand(shape=[bsz * seq_len, self.n_group, self.n_routed_experts // self.n_group])
                .reshape([bsz * seq_len, -1])
            )
            tmp_scores = scores.masked_fill(mask=~score_mask.astype(dtype="bool"), value=0.0)
            topk_weight, topk_idx = paddle.topk(k=self.top_k, sorted=False, x=tmp_scores, axis=-1)
        elif self.topk_method == "noaux_tc":
            assert not self.training
            scores_for_choice = scores.reshape([bsz * seq_len, -1]) + self.e_score_correction_bias.unsqueeze(axis=0)
            group_scores = scores_for_choice.reshape([bsz * seq_len, self.n_group, -1]).topk(k=2, axis=-1)[0].sum(axis=-1)

            group_idx = paddle.topk(k=self.topk_group, sorted=False, x=group_scores, axis=-1)[1]
            group_mask = paddle.zeros_like(x=group_scores)
            group_mask.put_along_axis_(axis=1, indices=group_idx, values=1, broadcast=False)
            # todo
            score_mask = (
                group_mask.unsqueeze(axis=-1)
                .expand(shape=[bsz * seq_len, self.n_group, self.n_routed_experts // self.n_group])
                .reshape([bsz * seq_len, -1])
            )
            tmp_scores = scores_for_choice.masked_fill(mask=~score_mask.astype(dtype="bool"), value=0.0)
            _, topk_idx = paddle.topk(k=self.top_k, sorted=False, x=tmp_scores, axis=-1)
            topk_weight = scores.take_along_axis(axis=1, indices=topk_idx, broadcast=False)

        if self.top_k > 1 and self.norm_topk_prob:
            denominator = topk_weight.sum(axis=-1, keepdim=True) + 1e-20
            topk_weight = topk_weight / denominator * self.routed_scaling_factor
        else:
            topk_weight = topk_weight * self.routed_scaling_factor
        if self.training and self.alpha > 0.0:
            scores_for_aux = scores
            aux_topk = self.top_k
            topk_idx_for_aux_loss = topk_idx.reshape([bsz, -1])
            if self.seq_aux:
                scores_for_seq_aux = scores_for_aux.reshape([bsz, seq_len, -1])
                ce = paddle.zeros(shape=[bsz, self.n_routed_experts])
                ce.put_along_axis_(
                    axis=1,
                    indices=topk_idx_for_aux_loss,
                    values=paddle.ones(shape=[bsz, seq_len * aux_topk]),
                    reduce="add",
                ).divide_(y=paddle.to_tensor(seq_len * aux_topk / self.n_routed_experts))
                aux_loss = (ce * scores_for_seq_aux.mean(axis=1)).sum(axis=1).mean() * self.alpha
            else:
                mask_ce = paddle.nn.functional.one_hot(
                    num_classes=self.n_routed_experts, x=topk_idx_for_aux_loss.reshape([-1])
                ).astype("int64")
                ce = mask_ce.astype(dtype="float32").mean(axis=0)
                Pi = scores_for_aux.mean(axis=0)
                fi = ce * self.n_routed_experts
                aux_loss = (Pi * fi).sum() * self.alpha
        else:
            aux_loss = None
        return topk_idx, topk_weight, aux_loss


05 上手教程

5.1 DeepSeek-VL2在 PaddleMIX 里快速体验

通过解析代码我们也更深入地理解模型的实现细节和技术创新,快跟着我们的 aistudio 教程一起来动手实践一下吧!

  • AI Studio 教程链接:

aistudio.baidu.com/projectdeta…

我们以 DeepSeek-VL2-tiny 为例,在单卡 V100上需23G 显存可推理完成图像理解。 首先下载 PaddleMIX 代码库:

# clone PaddleMIX代码库
git clone https://github.com/PaddlePaddle/PaddleMIX.git

cd PaddleMIX


安装 PaddlePaddle:

# 提供三种 PaddlePaddle 安装命令示例,也可参考PaddleMIX主页的安装教程进行安装

# 3.0.0b2版本安装示例 (CUDA 11.8)
python -m pip install paddlepaddle-gpu==3.0.0b2 -i https://www.paddlepaddle.org.cn/packages/stable/cu118/

# Develop 版本安装示例
python -m pip install paddlepaddle-gpu==0.0.0.post118 -f https://www.paddlepaddle.org.cn/whl/linux/gpu/develop.html

# sh 脚本快速安装
sh build_paddle_env.sh


安装 PaddleMIX 环境依赖包:

# 提供两种 PaddleMIX 依赖安装命令示例

# pip 安装示例,安装paddlemix、ppdiffusers、项目依赖、paddlenlp
python -m pip install -e . --user
python -m pip install -e ppdiffusers --user
python -m pip install -r requirements.txt --user
python -m pip install paddlenlp==3.0.0b3 --user

# sh 脚本快速安装
sh build_env.sh


5.2图像理解

运行以下命令即可:

# Deepseek-vl2-tiny multi image understanding
python paddlemix/examples/deepseek_vl2/multi_image_infer.py \
    --model_path="deepseek-ai/deepseek-vl2-tiny" \
    --image_file_1="paddlemix/demo_images/examples_image1.jpg" \
    --image_file_2="paddlemix/demo_images/examples_image2.jpg" \
    --image_file_3="paddlemix/demo_images/twitter3.jpeg" \
    --question="Can you tell me what are in the images?" \
    --dtype="bfloat16"


输出结果:

<|User|>: This is image_1:

This is image_2:

This is image_3:

Can you tell me what are in the images?

<|Assistant|>: The first image shows a red panda resting on a wooden platform. The second image features a giant panda sitting among bamboo plants. The third image captures a rocket launch at night, with the bright trail of the rocket illuminating the sky.<|end▁of▁sentence|>

06 总结

DeepSeek-VL2是一个基于 MoE 架构的前沿多模态大模型。通过引入动态图像切片编码策略,高效处理不同长宽比的高分辨率图像,大幅提升了视觉理解、视觉问答等任务的表现;其语言模型部分 DeepSeek-MoE 也通过压缩键值缓存的方式优化了推理速度和吞吐量。

百度飞桨团队推出的 PaddleMIX 套件现已完整实现这个热门模型的推理训练全流程支持,通过深入解析其代码实现,研究人员和开发者能够更透彻地理解模型的核心技术细节与创新突破。我们诚挚推荐您访问 AI Studio 平台的专项教程(点击以下链接🔗),通过实践演练掌握前沿多模态模型的开发与应用技巧。

▎论文链接

DeepSeek-VL: Towards Real-World Vision-Language Understanding

arxiv.org/pdf/2403.05…

DeepSeek-VL2: Mixture-of-Experts Vision-Language Models for Advanced Multimodal Understanding arxiv.org/pdf/2412.10…

▎项目地址

DeepSeek-VL2:github.com/PaddlePaddl…

▎AI Studio 教程链接

aistudio.baidu.com/projectdeta…

END

推荐阅读

秒哒首发即爆发!上线首日吸引2万用户,打造3万应用!

秒哒,全面开放!

图灵数据洞察平台-TDF(Turing Data Finder)

两连发!文心大模型4.5及X1,上线千帆!

百度百舸万卡集群的训练稳定性系统设计和实践

秒哒首发即爆发!上线首日吸引2万用户,打造3万应用!

作者 百度Geek说
2025年4月1日 09:57

3月24日,国内首个对话式应用开发平台百度秒哒全量上线,上线24小时就迅速吸引超2万用户体验,创建应用数量突破3万个,相当于每3秒就诞生1个应用!

秒哒以 "无代码编程+多智能体协作+多工具调用" 的技术组合,颠覆传统开发流程。用户通过自然语言描述需求即可自动生成完整功能代码,实现“3分钟生成+1小时迭代”的极致开发体验,生成H5邀请函、网站、小游戏等,用户登录秒哒首页即可体验。

(体验地址miaoda.baidu.com)

图片

----------END----------

推荐阅读

秒哒,全面开放!

图灵数据洞察平台-TDF(Turing Data Finder)

两连发!文心大模型4.5及X1,上线千帆!

百度百舸万卡集群的训练稳定性系统设计和实践

LLM增强语义嵌入的模型算法综述

图灵数据洞察平台-TDF(Turing Data Finder)

作者 百度Geek说
2025年3月25日 11:03

导读

在数字化时代,企业对用户数据的挖掘和分析能力直接影响业务增长和竞争力。图灵数据洞察平台(TDF) 是一款面向企业的数据分析与用户增长平台,提供一站式的行为数据生产、用户行为分析、及广告效果评估等功能。它能够利用多维分析模型深入洞察用户行为,助力精细化运营。图灵数据洞察平台还支持数据可视化和智能分析,帮助企业优化营销策略,提高用户转化和留存率。本文将详细介绍图灵数据洞察平台的核心功能、应用场景及其在提升数据决策效率和驱动业务增长方面的优势,为企业提供数据智能化运营的最佳实践。

01 平台背景

1.1 背景

百度MEG上一代大数据产品存在平台多、质量参差不齐和易用性差的问题。这些问题导致开发人员面临较高的研发依赖、开发效率低下和高昂的学习成本;业务部门则感知需求支持迟缓、数据产出延迟及数据质量低的问题。

图灵3.0旨在解决旧有大数据产品使用分散且技术相对落后的问题。

图灵3.0是一个覆盖数据全生命周期的强大生态系统,支持全链路的数据操作,包括数据计算引擎、数据开发和数据分析三个核心部分:

  1. TDE(Turing Data Engine):图灵生态的计算引擎,包含Spark计算引擎和ClickHouse。

  2. TDS(Turing Data Studio):一站式数据开发治理平台。

  3. TDA(Turing Data Analysis):新一代可视化BI产品。

图片

△图灵3.0生态

1.2 问题

  • 目前图灵生态内的可视化BI产品TDA聚焦于宏观分析,依赖用户自建的聚合后的数据集,缺乏对产品用户明细行为的关注分析。

  • 宏观的数据报表基于固定的分析目标建模,建设周期长,关注点相对固定,分析新视角的更新依赖于数据开发工程师,且存在计算资源重复消耗。

  • TDA下分析模式关注宏观趋势,分析与图表也以宏观趋势为主,缺少漏斗用户路径之类明细视角的深度分析即相关方法论落地能力,对增长中用户留存、流失等问题的深层次归因缺乏整体的解决方案,依赖分析师的经验预建模,无法灵活快捷分析。

  • 新产品和营销活动很难及时看到留存、转化等效果,难以复用过往沉淀的分析模型,依赖数据开发工程师全程跟进,响应周期长。

  • 业界对增长分析普遍关注有很多成熟产品,如Mixpanel、Google Analytics、Amplitude、字节火山引擎等,需要快速跟进完善分析能力。

基于以上问题,我们建设了数据洞察平台(增长分析平台Turing Data Finder), 以下简称TDF

1.3 与TDA平台的差异

有相关同学可能了解过TDA平台,它属于meg下的可视化数据分析产品,TDA与TDF在分析的数据、数据存储使用、关注的问题等方面都有较大的区别。

TDA的产品定位是一个可以实现用户一站式自助查询的BI平台,用户可以自由拖拽数据集,进行可视化数据分析,并进行核心仪表盘的搭建。分析模型如留存分析数据模型一般在数据生产阶段完成,然后在TDA平台多样化展示。

TDF更专注于数据增长分析,是一站式用户分析与运营平台,旨在为增长场景的数据分析带来全自动、全流程的解决方案,提升全流程迭代效率和分析深度,内部聚合多种高级分析模型,灵活洞察用户全生命周期的行为表现,从而发现指标背后增长的可能。分析模型在分析阶段打造,平台基于明细数据和用户选择的分析场景生成分析数据模型后展示查询结果。

图片

△功能对比

02 平台整体架构介绍

TDF平台致力于打造全自动的、全流程的解决方案用于提升用增场景数据分析的深度,适配核心业务增长需求。

整体架构如下:

图片

整体流程为数据开发工程师产出固定格式的ck明细日志后,用户在明细日志的基础上进行用户行为分析和用户分析,分析结果可保存至仪表盘。接下来从数据接入与管理、增长分析、仪表盘等方面对TDF功能进行详细的描述。

03 数据接入与管理

3.1 数据接入

日志中台数据接入流程如下:

  1. 用户在TDF选择需要从日志中台同步的页面;

  2. TDF定时同步页面对应的事件meta数据;

  3. TDF定时输出同步的meta数据给数据rd;

  4. 数据rd根据meta数据处理日志中台日志输出数据到ck;

对于非日志中台的日志,用户需要给TDF平台提供固定格式的事件meta信息。

3.2 标明细数据规范

因为增长分析场景复杂,很难针对不同的业务线的不同的用户行为表结构做定制化的sql模版开发,所以定义统一的明细数据规范,用户按照规范生成固定格式的数据,表模版字段如下:

3.3 数据管理

事件管理:事件管理支持新建事件、删除事件、支持设置必要属性(对于日志中台事件,必要属性不符合需求的事件会过滤)。

虚拟事件管理:用户可基于已有的元事件及事件属性建设虚拟事件,在分析时可选择虚拟事件进行分析。

图片

属性管理:可管理属性、管理属性枚举值、为属性绑定事件。

04 增长分析

4.1 事件分析

事件分析支持用户进行属性筛选以及分组后计算不同的指标,,对用户行为进行多维分析,并提供多样化的可视化图表。也可以选择人群包查看人群在不同事件中的表现。事件分析方便用户掌握产品不同功能的使用情况,可以快速展开多维下钻分析,配置出业务相关的分析指标。

图片

△配置填写

图片

△图展示

图片

△表格展示

  1. 支持的指标:pv、uv、人均次数、按任意属性去重、按任意属性求和。

图片

△指标选择

  1. 支持多种图表类型,对于分天数据包括折线图、柱状图、面积图等,对于总数包括饼图、柱状图等。

  2. 支持多个自定义公式,可按照数值和百分比展示。

图片

△多公式支持

  1. 支持与上期日期对比。

4.2 留存分析

留存分析用于衡量用户在一段时间内持续使用产品或服务的情况。它能帮助企业评估用户粘性、产品价值以及优化用户体验,以提高用户的留存率和忠诚度。方便对升级或活动所影响用户群体的留存率进行监控分

用户通过在页面选择起始事件(是指用户在特定时间段内完成的某个事件),回访事件(是指在起始事件之后,用户在特定时间段内再次完成的某个事件),以及筛选事件的属性和分组,查看在不同维度下回访事件相对于起始事件的n天(可选是当日留存还是累计留存)。

图片

△条件筛选

图片

△结果展示

  1. 不同位置的筛选作用于不同的事件,右侧的筛选作用于两个事件。

  2. 支持累计留存,累计留存是n天内回访事件的回访人群累计相对于起始事件的留存率。

4.3 漏斗分析

分析概述

漏斗模型通过分析用户流程中转化流失情况,反映用户行为状态以及从起点到终点各阶段用户转化情况,通过分析整个过程的转化率以及每一层的转化率,可以帮助我们明确优化的方向,找到转化率低的节点,进而可以定位用户流失的环节和原因。

用户通过按顺序选择漏斗中参与的事件、对事件属性进行筛选、添加分组,可查看不同维度下用户在一系列事件中的转化情况。

图片

△条件筛选

图片

△结果展示

  1. 可查看转化步骤,也可查看转化趋势,转化趋势是看选定的事件中事件a到事件b每天的转化情况。

  2. 转化率计算时可与前一事件相比也可与起始时间相比。

3)分析主体切换,配置后支持切换分析主体,默认分析视角是用户数查看用户的流向,在左上角切换后可选择比如商户、订单等查看这些主体的流转情况。

图片

△分析主体切换

4.4 用户路径

用于记录和分析用户在各个事件之间的流转过程,通过可视化的用户流量流转图高效查看分析用户在各个页面中的行为分布,用户路径通过分析这些事件的序列和流向,可以洞察用户的行为模式、偏好以及在不同页面或功能间的转换效率。

用户通过选择起始事件和参与的事件、对事件属性进行筛选,可查看用户在所选的一系列事件中的流转情况和转化率。

图片

△条件筛选

图片

△结果展示

1)用户选择起始时间后可选择参与分析也可不选择,不选择参与分析的时间则能流转到任意事件,否则只能流向起始时间和参与分析的时间以及others

2)通过限制但级别最大节点数来限制每层级的最大node数据,显示的node表示这一层级流向的top事件。

3)通过日期组件旁边的步数选择来控制层级数量。最少2步、最多5步。

4.5 成分分析

查看目标群体(通过用户做过的事和固定属性确认)的属性分布和对比,  成分分析通过分析一些典型属性如手机品牌、用户年龄、新老用户等场景,可以帮助我们全盘掌握用户公共属性的分布情况,辅助我们进一步优化运营策略。

用户在左侧选择需要查看的成分,右侧通过筛选用户做过的事件和其他固定属性值确认分析的目标群体,可查看目标群体在不同成分下的用户量对比。

图片

△条件筛选

图片

△结果展示

1)可选多个属性(最多5个)查看分析,非交叉分析时每个属性的结果单独显示,交叉分析时显示属性的多个组合结果

2)可选择人群对照组(最多5个),对照结果并排显示

3)可切换柱状图显示比例

4.6 分布分析

分布分析指在整体或某一维度下,按照计算结果划分出一些区间,查看对应人数在各区间内的分布情况。分布分析有很多种类,比如按事件发生频次查看人数分布、按属性值计算结果查看人数分布、按一段时间内累计发生的时长或天数查看人数分布等,可用于分析用户的页面功能的满意度情况。

用户通过选择想要查看的事件、属性筛选,选择分布区间算法和间隔后,可以从不同的分组查看用户实现该事件次数的分布状况。

图片

△条件筛选

图片

△结果展示

1)分布区间可以按照sturges算法计算,也可以自定义区间。自定义区间可以自由定义几等分、按多少间隔分隔总共多少组,也可以完全自由定义,用户自由限制每个区间范围。

图片

△自定义等分

图片

△自定义区间

图片

△自定义组数和间隔

2)是否整体用于计算在这段时间内用户整体的分布,这段时间内的用户整体去重。

3)展示结果图表右上角可选pv和占比,与默认展示的用户数自由组合。

4)可切换分析主体,默认分析视角是用户数,在左上角切换后可选择比如商户、订单等。

5)可选其他指标的去重分布,默认为次数。

4.7 归因分析

归因分析主要用于查看用户选择的多个待归因事件对最终结果事件的转化贡献,通过归因分析也可查看事件发生与目标事件发生之间的相关性。

用户选择目标事件和待归因事件,添加筛选条件后,可查看每个事件和目标事件的相关性,以及对整体目标出发的贡献。

图片

△条件选择及结果展示

1)用户可选择是否添加过程事件,添加过程事件表示用户在放生目标事件前必须发生过程事件才算做一次转化。

2)相关系数采用皮尔森相关系数算法计算。

3)同时计算其他归因节点默认不选择时只计算归因事件的转化,选择时则计算所有目标事件的转化,将非待归因事件汇总为others事件。

4)归因方法可选首次归因、末次归因、线性归因。

5)归因窗口可调整,在归因窗口期内即发生目标事件又发生待归因事件切符合归因算法,则算做一次待归因事件对归因事件的转化。

05 增长分析效率优化

增长分析关注的是用户行为明细数据数据量大,同时留存、漏斗、用户路径等分析场景模型复杂涉及到多层sql嵌套和长时间跨度的数据,平台会面临较大的查询压力。我们从多个方面对查询效率进行了优化。

1. 数据模型简化

1)合并用户数据和事件明细数据将关联前置到数据生成阶段,避免查询时大表关联

2)拆分业务线不同活动数据至不同分区,最小化查询数据集。

2. 分析模型逻辑优化

1)切分分析逻辑至查询最小粒度,多线程获取查询结果,在内存中合并获取最终结果。如事件分析中不同事件结果并行获取、成分分析中不同成分和对照组交叉时并行获取。

2)业界高效方案应用。如留存场景取用户群交集时使用Roaringbitmap方案,按位计算,极大提升查询效率。

3. 复杂字段物化与数据缓存

1)由于明细数据必须使用固定模版,所以对于多样化的事件属性、用户属性需求,这些属性统一存放在map字段中,查询时会拉低查询的效率。目前依据业务经验,对常用的部分属性进行物化,后期会根据查询日志获取高频查看属性列表,自动物化高频属性。

2)触发缓存和定时缓存结合,优化高频报表查询效率。用户触发缓存:按照用户查询条件缓存数据查询结果。定时缓存:对于高频的慢查询报表,根据明细数据表是否发生变更决定缓存更新频率,未发生变更时拉长缓存更新频率,发生变更则立即更新缓存。

06 总结与展望

TDF平台作为图灵3.0系统的新成员,作为一个全新的平台,功能已基本对齐业界竞品, 目前在用增、搜索等数据团队推广使用中。目标是通过平台基础数据分析能力,以数据为驱动为用户增长提供有效的线索,给产品、运营和分析师等提供新的增长分析思路。

展望未来,TDF平台有望在以下几个方面进一步发展和完善:

  1. 智能化助力数据分析:AI交互,探索数据智能洞察

  2. 多平台分析联通与交互:与TDA平台仪表盘打通,在同一仪表盘内数据从宏观分析深入到日志级别增长分析;与其他平台(如人机平台)交互,扩大人群分析能力和使用范围。

  3. 分析场景拓展:接入更多的数据源,支持更多的业务场景;拓展分析场景如ahamoment、LTV等,多角度为增长分析提供线索;提升分析效率,高效服务用户

总体而言,TDF平台将持续完善自身能力,通过不断的技术创新和功能完善,提升增长分析的深度、适配业务增长的需求,在增长分析中发挥重要作用。

----------END----------

推荐阅读

两连发!文心大模型4.5及X1,上线千帆!

百度百舸万卡集群的训练稳定性系统设计和实践

LLM增强语义嵌入的模型算法综述

持续推进“人工智能+”行动,百度智能云+DeepSeek为何成为国有企业首选?

两连发!文心大模型4.5及X1,上线千帆!

作者 百度Geek说
2025年3月20日 10:11

3月16日,文心大模型4.5文心大模型X1正式发布!

目前,两款模型已在文心一言官网上线,免费向用户开放。_(yiyan.baidu.com/)_同时,文心大模型4…

01 文心大模型4.5原生多模态基础大模型

文心大模型4.5是百度自主研发的新一代原生多模态基础大模型,通过多个模态联合建模实现协同优化,多模态理解能力优秀;具备更精进的语言能力,理解、生成、逻辑、记忆能力全面提升,去幻觉、逻辑推理、代码能力显著提升。

图片

多模态能力

图片

文本能力

文心大模型4.5具备优秀的多模态理解能力,能对文字、图片、音频、视频等内容进行综合理解。例如,针对下面这道题,文心大模型4.5综合理解了图中的文字与图表内容,提取出题目考查的重点;并给出详细的解题步骤和细化的选项分析,最终得出正确答案。

图片

不仅是“高智商”,文心大模型4.5也拥有“高情商”,网络梗图、讽刺漫画等等,理解起来都不在话下。例如,在下文中,这张梗图蕴含着“连续不一定可倒、可倒一定连续”的数学概念,文心大模型4.5不仅秒懂,点出梗图的巧妙和幽默;还能详细对其中蕴含的数学概念和画面逻辑进行解释。

图片

文心大模型4.5能力显著增强,离不开这些关键技术:

◎ FlashMask动态注意力掩码:加速大模型灵活注意力掩码计算,有效提升长序列建模能力和训练效率,优化长文处理能力和多轮交互表现;

◎ 多模态异构专家扩展技术:根据模态特点构建模态异构专家,结合自适应模态感知损失函数,解决不同模态梯度不均衡问题,提升多模态融合能力;

◎ 时空维度表征压缩技术:在时空维度对图片和视频的语义表征进行高效压缩,大幅提升多模态数据训练效率,增强了从长视频中吸取世界知识的能力;

◎ 基于知识点的大规模数据构建技术:基于知识分级采样、数据压缩与融合、稀缺知识点定向合成技术,构建高知识密度预训练数据,提升模型学习效率,大幅降低模型幻觉;

◎ 基于自反馈的Post-training技术:融合多种评价方式的自反馈迭代式后训练技术,全面提升强化学习稳定性和鲁棒性,大幅提升预训练模型对齐人类意图能力。

02 文心大模型X1能力更全面的深度思考模型

文心大模型X1具备更强的理解、规划、反思、进化能力,并支持多模态,是首个自主运用工具的深度思考模型。作为能力更全面的深度思考模型,文心大模型X1兼备准确、创意和文采,在中文知识问答、文学创作、文稿写作、日常对话、逻辑推理、复杂计算及工具调用等方面表现尤为出色。

作为能自主运用工具的大模型,文心大模型X1已支持高级搜索、文档问答、图片理解、AI绘图、代码解释器、网页链接读取、TreeMind树图、百度学术检索、商业信息查询、加盟信息查询等多款工具。

例如,让文心大模型X1使用中国各朝代历史人物典故,替换《寒窑赋》原文中的事例,它展现出清晰的思维链:首先明确原文主旨→分析语言风格和句式特点→选择合适的朝代和人物案例→参考原文结构创作,最后生成了和原文立意、文风句式都基本一致的文本。

文心大模型X1能力的全面提升,得益于这些关键技术的支撑:

递进式强化学习训练方法:创新性地应用递进式强化学习方法,在创作、搜索、工具调用、推理等场景全面提升模型的综合应用能力;

基于思维链和行动链的端到端训练:针对深度搜索、工具调用等场景,根据结果反馈进行端到端的模型训练,显著提升训练效果;

◎ 多元统一的奖励系统:建立了统一的奖励系统,融合多种类型的奖励机制,为模型训练提供更加鲁棒的反馈。

在百度智能云千帆大模型平台,现可直接调用文心大模型4.5API,输入价格低至0.004元/千tokens,输出价格低至0.016元/千tokens;文心大模型X1也即将在千帆平台上线,输入价格低至0.002元/千tokens,输出价格低至0.008元/千tokens。

图片

百度智能云千帆大模型平台始终致力于为用户提供全流程、一站式的AI服务,以开放性、易用性、低成本的平台理念,开发者能够更高效地探索大模型应用,提升创新效率,加速各类AI应用从概念到落地的转化,为AI技术在更多领域的拓展与应用注入强大动力。

2025是大模型技术全面迭代的一年,我们将在人工智能芯片、数据中心、云基础设施上更大胆地投入,打造更好、更智能的下一代模型。

----------END----------

推荐阅读

百度百舸万卡集群的训练稳定性系统设计和实践

LLM增强语义嵌入的模型算法综述

持续推进“人工智能+”行动,百度智能云+DeepSeek为何成为国有企业首选?

GPU 云服务器的软件系统设计和实践

基于Flink的配置化实时反作弊系统

百度百舸万卡集群的训练稳定性系统设计和实践

作者 百度Geek说
2025年3月18日 10:25

01 AI 训练稳定性的演进历程

2012 年 ImageNet 竞赛中 AlexNet 的横空出世,开启了现代 AI 发展的新纪元。彼时我们不会想到,十年后支撑 AI 训练的 GPU 集群会从研究室里的几台服务器,发展成需要专门供电系统的万卡级计算矩阵。在这个算力爆发式增长的过程中,训练系统的稳定性管理正经历着从「简单运维」到「精密工程」的深刻变革。

1.1 标早期的小模型时代:手动运维的黄金年代

2022 年之前的 AI 训练,更像是手工作坊式的精雕细琢。大多数训练任务只需十几块 GPU,利用 PyTorch 或 TensorFlow 的数据并行功能就能轻松应对。记得那时算法工程师们有个共识:如果训练遇到问题,重启往往比排查更高效。

当时我们构建的监控系统就像汽车仪表盘,只能显示最基本的任务状态。当训练意外中断时,工程师们会像侦探一样翻查日志 —— 如果发现是 GPU 报错,就联系运维同事。运维人员则带着「NVIDIA三件套」(nvidia-smi、dcgm、nsys)到机房巡检,像老中医把脉般通过温度、功耗等指标判断硬件状态。这种工作模式虽简单,但应对数十卡规模的集群还算游刃有余。

1.2 大模型风暴:从量变到质变的冲击

ChatGPT 的登场如同打开潘多拉魔盒,将 AI 训练带入新的纪元。当我们开始部署千卡/万卡集群时,才发现原有的运维体系就像用小渔网捕鲸鱼 —— 完全无法匹配新需求。

让我们通过百度百舸经历过的一个真实案例来深入理解这个问题:

2024 年初,百度百舸帮助一家 AIGC 创业公司迅速将其训练规模从百卡扩展到千卡级别。然而在训练数天后的某个周末凌晨,训练进程意外发生了 hang 死。由于当时缺乏有效的故障感知和容错机制,直到第二天算法工程师发现任务超时退出时,已经耽误了数小时宝贵的训练时间。更糟糕的是,任务日志中除了简单的 timeout 报错外毫无线索,平台监控也显示所有训练节点状态正常。

着急恢复训练的算法工程师没有立即上报问题,而是选择直接重新提交任务。但不幸的是,新任务运行数小时后再次出现相同的超时退出。这时他们才不得不寻求技术支持,但值班工程师面对这种任务 hang 死的问题也缺乏诊断经验,只能通过二分法慢慢定位。最终发现是某个节点的静默故障(SDC)导致了训练进程假死。等问题得到解决时,距离首次故障已经过去将近 30 小时,这意味着损失了价值巨大的千卡算力资源。

02 百度百舸集群训练稳定性全景图

站在现在的时间点回望,AI 训练稳定性已从辅助功能演变为核心基础设施。就像现代建筑中的抗震结构,它虽不直接参与空间构成,却是万丈高楼得以屹立的关键。当行业向着数万卡集群迈进时,这套隐形护甲的质量,将直接决定 AI 进化的速度与边界。

在 2024 年百度百舸对训练过程的生命周期进行了更细致的拆分,提出了「无效训练时间」这一关键指标,并致力于将其最小化。具体来说:

任务无效训练时间 = 故障中断次数 × 任务故障恢复时长 + 任务常态写 Ckpt 总时长

其中,任务故障恢复时长 = 故障感知召回耗时(自动/人工定位)+ 任务调度耗时 + 任务初始化耗时 + 任务重算时长。

通过这个公式可以看出,要降低无效训练时间,需要「围绕基础设施稳定性」、「任务容错」两个维度来系统展开,重点解决三个方面的问题:

  • 提高基础设施的交付质量。

  • 提高任务故障容错的召回率、准确率和时效性。

  • 优化 checkpoint 机制,减少保存时间和恢复时的重算时间。

经过容错架构的整体变革,百度百舸形成了从 「任务负载 — 框架 — 通信 — 基础架构」全链路的自动异常感知、诊断、恢复能力,可覆盖 90%+ 的训练异常场景,时效性最快可以实现秒级异常感知、分钟级定位,以及平均 3 分钟的故障自愈能力。

图片

03 基础设施交付质量保障

基础设施的交付质量保障是稳定性的基础。

CPU 时代,机器的交付前可能仅会跑一些常规的 CPU 计算、网络的压力测试,并不会从业务视角去评估基础架构,机器交付后硬件异常的故障频率相对较少。有硬件故障时,通常走工单系统人工换机用户相对是可接受的。

而 GPU 时代,AI Infra 的交付则需要考虑 CPU、GPU、RDMA 网络、存储,甚至机房的功率、温度等各方面因素,遗漏任何一个环节都会成为后续稳定性的隐患。在交付给客户后,机器也可能会由于长时间的高负载运行频繁出现硬件故障,而 GPU 机器的高昂成本,使客户对节点故障感知、换机的时效性提出了非常高的要求。

图片

因此百度百舸对 GPU 机器交付前及交付后的稳定性质量进行了系统性管理:

  • 交付前,百度百舸会对机器进行 200 多项指标检测,然后进行 48 小时烤机,以及 NCCL-Test 的机内、机间的大环、同号卡通信性能基准测试,端到端的大模型训练、推理性能基准测试。

  • 交付后,需要能够实时的感知节点故障及定期巡检,并具备分级处理的自愈能力,例如 Error 级别的故障实现自动排水、重启,Fault 级别故障实现自动换机。

04 任务容错的准召率保障

任务层面稳定性最核心的就是做好容错,能够让业务在无论遇到何种故障时都能快速恢复。

那么,首要的工作就是我们能够准确的识别出异常,然后对故障进行诊断定位,最后能够自动化的从异常中恢复。

因此,任务容错需要能够从端侧(即每个训练 worker)探测到进程与环境的各类异常,同时有个中心服务(Master)从任务全局的视角去诊断、定位异常,最终做出相应的决策来使任务能够快速从异常中恢复。

图片

任务容错最重要的就是提升故障的召回率与准确率,即如何能够尽可能的准确识别、定位所有故障。我们将故障分类两类:显式故障和隐式故障。

  • 显式的故障通常比较容易召回,我们将实践积累的各种进程异常状态及各类报错pattern形成专家知识库,再结合硬件感知服务(HAS Agent)的硬件全链路 10 秒级监控能力,可以实现显式故障的召回率达到 95%+。

  • 隐式的异常则往往很难轻易的识别,例如训练进程 hang、慢节点就是典型的隐式故障,需要丰富的经验积累才能准确的识别出异常。

下面我们就以最典型的隐式故障场景 —— 训练进程 hang 死为例,来看下如何能够做好 hang 自动感知、诊断。

4.1 训练****hang 的自动感知

训练任务发⽣ hang 之后,绝⼤多数情况都会以 timeout 的⽅式报错并退出进程,最常⻅的就是在通信过程中如果发⽣ hang,NCCL 的 watchdog 会中断通信,并有报如下 timeout 报错,然后再由 pytorch 的 torchrun 进程感知并中断训练过程。

[E ProcessGroupNCCL.cpp:828] [Rank 1] Watchdog caught collective operation timeout: WorkNCCL(SeqNum=15173, OpType=ALLREDUCE, Timeout(ms)=1800000) ran for 1802710 milliseconds before timing out.
[E ProcessGroupNCCL.cpp:828] [Rank 0] Watchdog caught collective operation timeout: WorkNCCL(SeqNum=15173, OpType=ALLREDUCE, Timeout(ms)=1800000) ran for 1802713 milliseconds before timing out.


Pytorch 默认为 10 分钟 NCCL 通信超时,而 Megatron-LM 为 30 分钟。在万卡规模训练场景中,意味着一万张卡要至少浪费 30 分钟才能被发现。这个时效性是不可接受的。而且当 30 分钟超时后程序会立马退出,很难有机会进行下一步定位,需要一些时效性更高的感知机制,并且在程序退出前获取一些有效信息供后续诊断分析。

很多公司、实验室在面对 hang 的问题时,会在采用框架层插桩的方式来 trace 训练进程,这种方式通常是比较直接且准确的,但是有比较强的侵入性,而且可能还会有一些性能开销。对于云厂商来说,需要寻找对用户更透明、更无损的方式来感知、定位 hang 异常。

如何感知训练 hang,以百度百舸的产品设计思路为例,我们可以从以下几个方向去思考:

  1. 训练进程 hang 的最直观表现是什么?

    人工判断一个任务是否 hang 了,最直接的方式就是看是否所有 worker 的任务日志一段时间内都不输出日志了,所以 hang 自动感知的第一种方法就是采集所有 worker 的日志,并判断所有 worker 日志中最后一行日志是否为 x 分钟前的(x 小于 Pytorch 的通信超时时间,例如 8 分钟),如果是则基本可以判定为 hang。

  2. 任务 hang 时进程有什么样的表现?

    任务 hang 时,可能进程的调用栈都不在发生变化,进程的调用栈可以通过 py-spy/pystack 等工具进行探测,所以我们可以用此类工具对所有训练任务进行一个定时采样,当采集 n 个样本所有进程栈都没有变化时,可以判定一次 hang,这种方式通常可以将 hang 感知缩小至 3~5 分钟。

  3. 任务 hang 时监控指标有哪些变化?

    训练进程中的 CUDA 算子计算、集合通信操作通常都是在毫秒,甚至微秒、纳秒内完成的,当任务在正常迭代过程中发生了 hang,我们常遇到的情况是所有 rank 的 RDMA 流量会降到 0,而 GPU 的利用率为 100%、SM 利用率则在很低的水位。如果持续几分钟都是这种状态时,意味着训练进程已经计算完成,在等着集合通信完成,这种情况下基本可以判定为 hang。

  4. 是否能在通信库中更快的感知通信 hang?

    通常单次集合通信操作都是在 ms 级的,如果一次操作在 30 秒钟都没有完成,那就可以判定为通信 hang 死了。百度自研的 BCCL 集合通信库层可以对每一次集合通信操作都进行打点,来实现通信 hang 感知。

上述几种方法,我们可以分别实现一种探针,来抓取相应的特征到中心端 master 组件进行下一步诊断和容错决策。

百度集合通信库 BCCL是百度智能云推出的一款面向大模型训练场景优化的集合通信库。

BCCL 基于开源的 NCCL 进行了功能扩展和能力增强,针对大模型训练场景在可观测性、故障诊断、稳定性等方面进行优化,进一步提升集合通信库的可运维能力。相比 NCCL,BCCL 的关键特性如下:

* 可观测性:新增集合通信带宽实时统计能力;

* 故障诊断:新增集合通信 hang 时的故障诊断能力;

* 稳定性:增强网络稳定性和故障容错能力;

* 性能优化:提升大模型训练主流 GPU 芯片的集合通信性能。

4.2 训练 hang 的自动诊断

有了以上感知手段,我们需要进一步的诊断、定位,来确定是否真的发生了 hang,以及 hang 的具体位置。具体的来讲,master 收集到各类 agent 的数据后,会做一些综合分析:

  1. 是否真的发生了 hang?

    感知阶段各种探针只能探测到 hang 的一种特征,并没有办法 100% 的确定是否真的 hang 住了,事实上不侵入用户进程是很难做到 100% 确定 hang 的。因此,为了提高 hang 的判定准确率,我们需要将各种特种综合起来判断,探针上报到 master 后,由一个 hang 诊断模块,按照一个时间窗口(例如 5 分钟),进行综合判断。如果在时间窗口内日志、监控、进程调用栈、通信库中有 2 条以上都处于不处于活跃状态时,我们判断任务真正发生了 hang。

  2. hang 的具体发生的位置?

    确定任务 hang 了之后,我们需要找到 hang 所在的节点来对它进行隔离。因此诊断模块需要在探针上报的数据中进一步找寻特征,来确定 hang 发生的位置:

  3. BCCL Tracehang 诊断:在感知阶段,BCCL 可以在通信库层面对所有 rank 的通信进行打点。如果有节点一直未完成通信则是发生了 hang。但是此节点可能并非真正发生 hang 的源头,有可能是在等待其他节点完成通信。诊断模块可以根据 BCCL 打印的通信组信息,进行交叉判断,如果某个节点在多个通信组中都未完成通信,那这个节点就是 hang 的源头。

  4. RDMA/GPU 指标诊断:上文中我们提到,通信阶段发生 hang 之后,所有 rank 的 RDMA 流量都会降到 0,而同时绝大部分 rank 的 GPU 利用率持续为 100%,只有某一两个 rank 的 GPU 利用率为 0,那这个 rank 很有可能是 hang 的源头。

  5. 进程调用栈诊断:进程调用栈也可以作为一个 hang 源头诊断的重要参考。当发生 hang 之后,绝大部分的 rank 都要么处于 barrier 等待状态,要么处于通信等待阶段。只有个别的 rank 卡在其他函数上,那么通过对比分析,可以将调用栈与其他 rank 不同的节点初步判定为 hang 的源头。

  6. 综合诊断:上面 3 种特征为我们提供了 hang 的诊断依据,将 3 者关联起来分析后,我们基本上可以比较准确的确定一个具体的 hang 的源头,再结合硬件故障感知的相关信息可以进一步明确根因。

4.3 基于 eBPF 的隐式故障感知与诊断

在复杂的大规模分布式训练场景中,传统用户态监控往往难以捕获系统内核层面的异常事件。

百度百舸基于 eBPF(Extended Berkeley Packet Filter)技术的隐式故障感知体系,能够在不侵入用户代码的前提下,对训练进程的系统调用、网络通信、CPU 调度等内核态行为以及训练框架关键函数运行时间建立立体观测能力。

eBPF 探针部署原理通过在内核关键路径注入轻量级探针,实现低开销的系统级行为捕获。针对训练场景特点,主要聚焦 4 类事件跟踪:

  • 训练关键函数跟踪:微秒级跟踪训练过程中,前向计算、反向计算、集合通信操作等关键函数执行耗时,记录函数间调用关系。

  • 进程调度阻塞跟踪:挂钩 sched_switch 事件,检测进程在 TASK_UNINTERRUPTIBLE 状态持续时间,当单次持续超过阈值(如 5 秒)时捕获调用栈。

  • CUDA 运行时 API 监控:通过 uprobe 在 libcuda.so 等关键库注入探针,记录 CUDA API 调用耗时分布。

  • RDMA Verbs 级通信监控:在 ibv_post_send/ibv_poll_cq 等核心通信接口设置观测点,统计通信时延分布。

结合上面 4 类事件,完成以下 2 类数据分析:

  • 单体异常探测基线与实时数据对比。

  • 群体一致性检测。采用卡间对比算法,当某一 rank 的以下指标偏离集群中位数超过阈值时判定异常,包括系统调用频率、进程就绪队列等待时长、NVLink/RDMA 带宽利用率等。

基于以上所述方法,百度百舸针对以下 2 类典型的隐式故障进行诊断:

  • 训练 hang 根因定位。通过关联 eBPF 捕获的多维度数据进行如下操作:

  • 当检测到某 rank 的 GPU  Kernel 执行出现分钟级空跑(SM 利用率 > 70% 但无有效计算输出)。

  • 同时伴随该节点 RDMA QP 状态停滞(ibv_poll_cq 无新完成事件)。

  • 内核调度器显示进程处于 D 状态超过阈值。

  • 性能抖动溯源。基于 eBPF 火焰图、时序图等进行分析:

  • 抓取发生性能下降时段的 CPU on-cpu/off-cpu 堆栈。

  • 对比正常时段数据,识别出异常的锁竞争(futex 调用占比上升)。

  • 结合 NUMA 内存访问统计,定位跨 NUMA 内存访问导致的 TLB 颠簸问题。

此类技术已在百度百舸的万卡规模训练集群中验证,相比单纯依赖应用层监控的方案,将隐式故障的平均检测时间从分钟级缩短至秒级,诊断准确率提升 40% 以上。

通过与既有硬件故障感知服务、BCCL 通信库监测体系联动,百度百舸形成了覆盖从硬件到系统内核再到应用层的立体化诊断能力。

05 任务故障恢复的时效性保障

故障恢复的时效性也是容错能力的一个重要指标,反映的是任务从故障发生到再次重新进入训练迭代的时间,恢复效率越高则算力浪费越少。影响到任务恢复效率有 2 个重要因素,一是任务平均中断时间,二是训练重算时间。

5.1 多级重启策略减少故障中断时间

任务发生异常后,上文中我们提到需要经过故障自动感知、诊断和自愈等 3 个环节,那么减少中断时间的核心思想,就是尽可能的缩短这 3 个环节的时间,通过多维度的感知、诊断手段可以将故障发现、定位的时效性降低至分钟级甚至秒级。自愈则需要能够根据不同的诊断结果进行分级恢复和故障屏蔽的能力:

  • 单点显式故障:重调度异常节点(replace),对节点进行集群级别屏蔽。

  • 单点隐式故障:重调度异常节点,对节点进行任务级别屏蔽。

  • 非单点故障:原地重启尝试恢复(restart),无法恢复时重新调度所有节点(resubmit)。

通过多级重启策略,尽可能避免单点故障引发全部节点的重新调度。在万卡级别的训练场景中,百度百舸将大部分训练异常场景恢复时间从过去的 30min 缩短至现在的 30s 内,成功率到 95%+。

5.2 触发式 checkpoint 减少训练重算时间

除了上述的多级任务重启策略外,另一个提高任务故障恢复效率的重要手段就是减少训练重算时间。在探讨具体技术方案之前,我们先来看看目前主流的 checkpoint 保存策略。

传统的 checkpoint 保存通常采用固定间隔策略,比如每隔 N 个 step 或每隔 T 小时保存一次,这种方式实现简单但缺乏灵活性,可能会产生大量冗余存储,同时在故障发生时可能会损失较多训练进度。

而触发式 checkpoint 则是一种更智能的方案,它根据特定条件或异常事件(如故障、显存不足、显式指令等)动态触发模型状态保存。其核心目标是通过灵活的控制保存时机,减少不必要的存储开销和训练中断时间,从而降低因频繁或冗余保存导致的重算时间浪费。

随着大模型训练规模的扩大,还有一种更激进的「零重复 checkpoint」技术,即在每个训练 step 都保存一次 checkpoint。这种方案的优势在于可以将重算时间降到最低,确保故障发生时能够从最近的 step 恢复,几乎不会损失训练进度。但其显著的缺点是存储开销巨大,即使采用增量式存储,仍然需要相当大的存储空间和 I/O 带宽。此外,频繁的 checkpoint 操作也可能影响训练性能。

相比之下,触发式 checkpoint 走的是一条平衡之路。我们来看下它实现的几个核心要点:

  • 集成容错:训练进程集成容错的故障感知与定位机制,在进程退出前自动触发保存。这种主动感知机制能够在故障发生的第一时间保存训练状态,最大限度减少进度损失。

  • 高速转储:异步 checkpoint 保存机制会将 checkpoint 暂存到共享内存中,再由外部程序转储至磁盘。当某个节点异常时,容错组件会拉起新节点,并在新节点训练进程启动前,利用 RDMA 技术实现 checkpoint 快速从故障节点转储至新节点,这大大减少了从远程存储拉取 checkpoint 的时间。

  • 冗余备份:触发式 checkpoint 也并非完美无缺,例如在节点发生内核 crash 等严重故障时,可能无法触发自动保存。因此,需要通过定期的冗余备份机制进行兜底,确保 checkpoint 不会完全丢失。

实践表明,当触发式 checkpoint 与异步、增量式的 checkpoint 机制结合使用时,可以在保证数据安全性的同时,显著提高 checkpoint 保存效率,减少训练重算时间。

相比零重复 checkpoint 的重型方案,触发式 checkpoint 提供了一个更实用的折中方案,在合理的存储开销下实现较好的容错效果。当然,具体选择哪种方案,还需要根据实际的训练规模、硬件条件和可用资源来权衡。

随着分布式训练规模的持续增长,相信未来会出现更多创新的 checkpoint 方案,比如基于预测的主动保存策略、多级存储架构的智能调度等,这些都将为提高大规模训练的可靠性提供新的可能。

06 业务发展对稳定性的要求

AI 训练的稳定性管理已经演变为智能时代的精密工程。从最初靠人工重启解决问题的摸索阶段,到如今能自动感知异常、快速恢复的智能系统,每一次进步都映照着算力规模的跨越式发展。

让人不禁思考,在未来十万卡集群的算力洪流中,或许会出现更精妙的动态平衡方案:既能像鹰隼般敏锐捕捉故障征兆,又能如雁群迁移般智能调度资源,在秒级恢复与 PB 级存储成本之间找到新的平衡支点。

目前百度百舸支持厂内千卡和万卡集群有效训练时长已经可达 99.5%,为客户大模型的预训练保驾护航,比如国内第一个数学大模型——九章算术,国内第一个类 Sora 大模型 —— Vidu 等。

----------END----------

推荐阅读

LLM增强语义嵌入的模型算法综述

持续推进“人工智能+”行动,百度智能云+DeepSeek为何成为国有企业首选?

GPU 云服务器的软件系统设计和实践

基于Flink的配置化实时反作弊系统

百度智能云xDeepSeek,最具性价比的DeepSeek一体机合集来了!

LLM增强语义嵌入的模型算法综述

作者 百度Geek说
2025年3月13日 10:56

导读

语义嵌入是自然语言处理、信息检索、推荐系统等领域的核心技术,对于精准捕捉文本深层次语义信息至关重要。近年来,大语言模型(LLM)的兴起为语义嵌入技术开拓了新的方向。本文介绍了LLM在提升语义嵌入效果的最新模型与算法,聚焦在如何利用LLM生成合成数据以及如何作为模型骨干来优化语义嵌入。文章概述了当前研究的主要方向和先进成果,展示了LLM在学习语义嵌入方面的独特优势,并展望了其在搜索、推荐等下游任务的广阔应用前景。

01 背景介绍

1.1 语义嵌入

文本语义嵌入,作为将文本映射到语义空间的关键技术,通过将原本高维且稀疏的向量转化为低维稠密向量,实现了对单词、句子乃至整个文档深层次语义信息的精确捕捉。这一技术在信息检索、问答系统、文本相似度计算及推荐系统等自然语言处理(NLP)的诸多任务中展现出广泛的应用价值。

早期语义嵌入方法,诸如 Word2vec 和 GloVe,主要基于统计特性构建,然而,这些方法受限于其静态性,难以充分捕捉自然语言复杂多变的上下文信息。随后,BERT 及其衍生的一系列上下文敏感模型(如 RoBERTa、DistilBERT、ALBERT 等)应运而生,通过独特的模型增强与优化策略,不仅保留了双向编码的核心优势,还显著提升了语义嵌入对上下文信息的感知能力。此外,Sentence-BERT 模型的提出,通过引入连体网络(Siamese Network)和三重网络(Triplet Network)结构,实现了高效的句子级嵌入生成。近年来,随着对比学习框架的兴起,如 SimCSE 等方法利用标准 dropout 作为噪声源,无需额外监督数据即可生成高质量的句子向量,进一步推动了语义嵌入技术的革新与发展。

当前研究热点聚焦于通用文本嵌入技术的优化,通过引入多阶段训练范式和复杂的数据混合策略,如 E5[1]、BGE[2]、GTE[3] 等模型,在数据规模、质量及多样性方面取得了显著进展。随着大语言模型(LLM)的蓬勃发展,利用 LLM 生成语义嵌入成为新的研究方向。研究者们通过合成高质量训练数据以提升嵌入性能,或直接以 LLM 作为模型主干生成嵌入,显著增强了语义嵌入的鲁棒性和泛化能力,为 NLP 领域的未来发展探索出了新的道路。

1.2 大语言模型(LLM)

LLM 作为一种规模宏大的预训练语言模型,通常具有数亿乃至数十亿之多的参数,经由海量数据的训练,已具备理解和生成更为繁复且丰富文本内容的能力。随着模型规模的不断扩张与数据量的显著增加,LLM 已能极为娴熟地应对众多下游自然语言处理任务。凭借其强大的表达能力和出色的泛化性能,LLM 能够应对各类自然语言处理任务,并在诸多场景下展现出超越传统机器学习方法的卓越表现。“预训练+提示”作为 LLM 的第四范式,开创了一种引导生成输出以匹配多样化下游任务需求的新颖路径。在此范式下,模型的适应不再单纯依赖微调,而是通过引入“提示”这一文本或模板元素来引导生成过程,旨在促使模型在接收任务时能够有效唤醒预训练阶段所学到的知识,并据此精准地生成符合任务特定要求的输出。诸如 LLaMA-2[4]、LLaMA-3[5] 及 Mistral[6] 等开源 LLM 模型已取得了显著成效,它们不仅推动了 LLM 在语义嵌入领域的广泛应用,更掀起了利用 LLM 进行语义嵌入研究的热潮。

LLM 语义嵌入与传统语义嵌入之间的对比:

综上所述,使用 LLM 进行语义嵌入与传统语义嵌入(如使用BERT)在模型结构与训练方式、嵌入质量、应用场景等方面都存在显著差异。这些差异使得 LLM 语义嵌入在复杂的 NLP 任务中表现出更强的性能和适应性。下文将介绍一些当前主流的 LLM 语义嵌入方法。

02 算法解读

随着 LLM 的广泛应用,出现了一些利用 LLM 来生成语义嵌入的尝试。Jiang等人[7] 首次提出了基于提示(prompt)的句子嵌入方法,结合对比学习框架,通过设计如 “[X] means [MASK]” (其中,[X] 表示一个 placeholder,对应一个句子,[MASK] 则表示待预测的 token)的模板捕捉句子表征,并借助模板去噪避免语义偏向。随后,Zeng等人[8] 提出了 Prompt-derived Virtual semantic Prototypes(ConPVP)的对比学习方法,利用提示构建虚拟语义原型及否定原型,通过原型对比损失优化句子嵌入。Cheng等人[9] 则进一步提出了基于AI反馈(CLAIF)的句子嵌入对比学习方法,利用来自 LLM 的AI反馈来构建具有细粒度样本相似性分数的样本对,提升句子嵌入的监督对比学习的效果。

当前,利用 LLM 提升语义嵌入的研究热点聚焦于合成数据模型骨干两大方向,如图 1 所示。下面将具体介绍各方向的一些代表模型。

图片

△图1: LLM 提升语义嵌入的算法模型梳理

2.1 合成数据

利用 LLM 进行数据合成正日益受到业界的广泛关注,该方法能够显著提升模型在处理多任务及多语言环境下的能力多样性,从而训练出更为强健的语义嵌入模型,可广泛应用于各类下游任务中。具体而言,LLM 在生成高质量、多语言、多任务合成数据方面展现出巨大潜力。例如,通过将 LLM 作为数据注释工具,可以高效地标注伪监督数据以强化模型训练效果;利用如 GPT-4 等先进的 LLM,可以生成覆盖广泛语言范围及多种语义嵌入任务的合成数据,极大地丰富了训练数据的多样性;此外,合成数据还被用于从大模型中提取知识并融入检索系统中。接下来,本文将重点介绍在 MTEB 基准测试中表现突出的几个典型语义嵌入模型,包括 E5-mistral-7b-instruct[10] 混合真实与 LLM 合成数据进行微调、SFR-Embedding-Mistral[11] 采用改进的难负样本进行多任务微调的方法,以及 Gecko[12] 采用两步蒸馏法将LLM知识融入检索器的技术。

2.1.1 E5-mistral-7b-instruct

【主要贡献】

微软的研究团队提出的语义嵌入训练方法,无需复杂的流程设计或人工构建的数据集,只需要利用 LLM 来合成多样化的文本数据,就可以为93种语言的数十万文本嵌入任务生成高质量的语义嵌入,整个训练过程还不到1000步。

实验结果证明,当仅对合成数据进行微调时,Mistral-7B 在 BEIR 和 MTEB 基准上获得了非常有竞争力的性能;当同时加入合成和标注数据进行微调时,即可实现 SOTA 性能。

【算法概述】

  • 数据合成:

为了生成不同的合成数据,提出了一种简单的分类法,将嵌入任务分为几个组,然后对每个组应用不同的提示模板。如图 2 所示,对于每个组,该算法设计了一个两步提示模板,首先提示 LLM 生成一系列的任务,然后为每个任务生成(用户查询、正样本、难负样本)三元组。“{...}”表示一个占位符,将在其中填入从预先定义的一组值中随机选取的值。

为了覆盖不同的应用场景,该算法为每个任务类型设计了多个提示模板,并将不同模板生成的数据进行组合。为了进一步提高合成数据的多样性,在每个提示模板中加入了几个占位符,在运行时随机采样,例如 {query_length} 是从集合 {少于5个单词,5-10个单词,至少10个单词} 中采样的。

图片

△图2: E5-mistral-7b-instruct 数据合成两步提示模板

  • 训练阶段:

给定一个预训练的 LLM,在查询和文档的末尾添加一个 [EOS] 标记。最后一层 [EOS] 向量被用作语义嵌入。为了帮助模型适应不同的任务,在生成新的查询时,可以使用统一的指令模板,给定相关的查询-文档对(q+,d+)(q^+, d^+),先使用原始查询 q+q^+ 来生成一个新的指令qinst+q_{inst}^+,其中 {task_definition} 是嵌入任务的一句话描述的占位符:

图片

最后,选择预训练的开源 LLM Mistral-7B 模型,在对一组混合的合成和标记数据(包含 13 个公开数据集)进行采样后进行微调。

2.1.2 SFR-Embedding-Mistral

【主要贡献】

用改进的难负样本对 E5-mistral-7b-instruct 进行多任务微调,将性能提高了0.93%。主要结论有:

  • 语义嵌入与聚类任务集成后检索性能显著提高,并通过多任务知识转移进一步增强检索性能。此外,通过采用多任务训练并使模型适应特定任务,可以增强泛化能力。

  • 采用任务同构批处理,即同个batch里的所有样本来自于同个任务,这样可以保证对比学习的 in-batch negatives 更具挑战性。

  • 之前的难负样本挖掘方法都是先通过检索将排序靠前的文档作为难负样本,这样有可能混入部分错误的负样本,影响模型训练结果。该方法发现使用排序在30-100之间的文档作为难负样本能提升模型性能,而使用排序在0-100之间的文档则容易引入错误的负样本,使用排序在50-100之间的文档则缺乏训练难度。

2.1.3 Gecko

【主要贡献】

Gecko 提出的主要思想:从 LLM 中提取知识到检索器中。使用两步蒸馏过程:首先使用 LLM 生成不同的任务-查询对数据。接下来通过为每个查询检索一组候选段落,并使用相同的 LLM 重新标记正样本和难负样本段落,进一步改进数据质量。

图片

△图3: Gecko 整体流程概述

Gecko 选用 gtr-t5-xl (1.2B, encoder from T5-3B model) 模型骨架。通过将 LLM 生成的和经 LLM 排序的数据,与人工标注的数据相结合,Gecko 模型在 MTEB 基准上实现了较好性能。

算法概述】

该方法利用知识蒸馏的思想,创建了一个两步骤的 LLM 驱动的嵌入模型。

  • 多元查询生成

如图 4 所示,首先向 LLM 中输入一段网络文章节选,让其生成任务描述和任务的相关查询:

图片

其中 pseedp_{seed} 是从web语料库中随机抽取的一个段落,而 PQGP_{QG} 是一个固定的提示符。对于每个示例来说,提示模板是相同的,并且由几个示例和说明组成。LLM 生成一个任务描述 t(描述了检索的类型,如问答或事实检查)。通过对这些任务描述进行抽样,可以指导 LLM 生成与任务一致的查询 𝑞。

  • 正负样本挖掘

首先,给定生成的查询 𝑞,使用一个嵌入模型来检索最相关的段落。然后,使用 LLM 根据检索到的段落与查询的相关性对它们进行排序。排序方法采用的是查询似然(query likelihood)和相关性分类(relevance classification)这两个少样本提示的 LLM 排序函数。最后将两种不同提示结果的排名与标准的倒数秩融合(RRF)方法集成在一起,获得排名函数𝑅(𝑞,𝑝),并根据排名找到更多相关的正样本以及生成查询的难负样本。

图片

△图4 :两步检索流程

2.2 模型骨干

另一类主流方法是将 LLM 作为语义嵌入的主干网络,原因在于它们无需遵循现有语义嵌入模型中普遍采用的对比预训练步骤,使得 LLM 在训练过程中能够更加灵活地处理文本数据。得益于全面的自回归预训练,LLM 能够充分捕捉到文本中的上下文信息和语义关系,具有出色的文本表征能力,并且仅需最少的微调即可转化为高效的通用文本嵌入模型。

同样,后续内容将着重介绍在 MTEB 基准测试中表现优异的几个语义嵌入模型。其中,NV-Embed-v2[13] 模型通过关注潜在向量并采用两阶段对比指令调优方法来提升性能;BGE-EN-ICL[14] 模型则利用LLM中的 in-context learning 能力来优化语义嵌入的生成;Echo-mistral[15] 模型采用双向注意机制,通过重复输入并提取第二次出现的嵌入来增强效果;LLM2Vec[16] 模型同样采用了双向注意力机制,还结合掩码下一个token预测以及无监督对比学习来提升嵌入效果;GRIT[17] 模型通过训练LLM和指令,实现了生成和嵌入任务的统一处理;GTE-Qwen1.5-7B-instruct[18] 模型则利用双向注意力机制、查询侧指令调优,并融合了弱监督与监督数据进行训练;最后,stella_en_1.5B_v5[19] 模型简化了提示的使用,为大多数通用任务提供了序列到段落(s2p)和序列到序列(s2s)两种便捷的提示方式。

2.2.1 NV-Embed-v2

【主要贡献】

一个通用文本嵌入模型,NV-Embed-v2 提出了几个新的设计,包括让 LLM 关注潜在向量以获得更好的池化嵌入输出,并展示了一种两阶段指令调优方法以提高检索和非检索任务的准确性。此外,NV-Embed-v2 采用了一种新的难负样本挖掘方法,该方法考虑了正样本的相关分数,以更好地去除假负样本。

基于预训练的 Mistral-7B 模型进行训练,该模型在大规模文本嵌入基准(MTEB基准)(截至2024年12月3日)中排名第一,在56个文本嵌入任务中得分为72.31。

【算法概述】

  • 模型架构

提出了一种潜在注意层来获得一系列 token 的池嵌入。如图 5 所示,将解码器最后一个隐藏层表示为查询 Q,然后结合潜在数组 K = V(可训练的“字典”),用于获得更好的表示。该交叉注意的输出是:

图片

最后通过一个正则 MLP 层和平均池化来获得整个序列的嵌入。

为了进一步增强表征学习,在 decoder-only 的 LLM 对比训练过程中去掉了因果注意掩码,从而缓解了单向注意力对模型表示能力的限制。

图片

△图5: NV-Embed-v2 模型架构

  • 模型训练

引入了一种两阶段的对比指令调优方法。第一阶段利用批量负样本和精选的难负样本,在多种检索数据集上利用指令进行对比训练。在第二阶段,该算法在检索和非检索数据集的组合上执行对比指令调优。由于同一批次内的负样本可能会误导非检索任务,该算法在第二阶段不采用 in-batch negatives 方案。这种设计不仅提高了分类、聚类和语义文本相似度任务的准确性,而且显著提高了检索性能。

2.2.2 BGE-EN-ICL

【主要贡献】

利用 LLM 中的in-context learning(ICL)能力来增强语义嵌入生成的过程。BGE-EN-ICL 模型使用少量示例来生成高质量的语义嵌入,直接将与任务相关的示例集成到查询端,从而在多种任务上取得了显著的改进。此外,还研究了如何有效地将 LLM 用作嵌入模型,包括各种注意力机制、池化方法等。

该模型采用 Mistral-7B 作为模型骨架,研究结果表明,保留原始框架往往能获得最佳结果。在 MTEB 基准测试上的实验结果目前排名第二。

【算法概述】

  • 上下文学习增强嵌入模型

图片

△图6: 基于ICL的模型体系结构

通过少样本对比训练实现 ICL 增强嵌入模型。考虑嵌入任务中的一个查询-段落对(q_i,p_i)(q\_i , p\_i ),首先构建一个示例模板如下:

图片

“task definition”表示特定嵌入任务的描述(图 6)。对于一个相关的查询-段落对(q+,p+)(q^+, p^+),修改后的查询构建如下:

图片

  • 表征方法

该方法认为在嵌入微调过程中引入双向注意力与模型的预训练设计不匹配,可能会破坏其上下文学习和生成属性。为了保留特定任务的嵌入表示与生成属性,与大多数现有的嵌入方法一样,该方法保留了单向注意力机制。具体来说,在输入修改后的查询和段落的末尾添加一个 [EOS] 标记,以通过因果注意力机制捕捉语义和上下文学习模式,然后将它们输入到 LLM 中,通过提取最后一层的 [EOS] 向量来获得嵌入。训练过程中采用标准的 InfoNCE 损失函数,同时利用 in-batch negatives 和 hard negatives。

  • 基于上下文学习的指令微调

实验表明,如果在训练过程中一直提供示例,可能会降低模型的零样本推理能力。因此,该方法提出了一种动态训练过程。即在每个训练步骤中,查询会被提供数量可变(0 到 n)的少量样本,这种方法在 ICL 能力和零样本性能之间取得了平衡。

2.2.3 Echo-mistral

【主要贡献】

自回归模型中由于存在因果注意力掩码,会导致token嵌入无法包含出现在输入中较靠后的token的信息。为了解决这一限制,Echo-mistral 模型中设计了 “Echo embeddings”(如图 7 所示),即使用双向注意:重复两次输入并从第二次出现中提取嵌入。原则上,第二次出现的语境化嵌入可以关注第一次中呈现的整个句子。此外,需要在 LLM 的提示信息中包括如“重写”或“重述”等词,从而尽可能确保第二次真的“编码”了关于第一次的信息。实验表明,Echo嵌入可以编码靠后token的信息,从而最大限度地利用 LLM 进行嵌入。

在 MTEB 排行榜上,Echo嵌入比传统嵌入提高了9%以上,在微调后提高了0.7%左右。与之前没有利用合成微调数据的开源模型相比,Mistral-7B 模型的Echo嵌入也有较大提升。

图片

△图7: Echo嵌入概念介绍

2.2.4 LLM2Vec

【主要贡献】

与 Echo-mistral 模型的思想类似,语义嵌入任务中解码器模型的因果注意力机制,限制了从整个输入序列中获取并创建双向上下文表示的能力。因此,为了改进 decoder-only 获得的语义嵌入,LLM2Vec 提出了一种简单的无监督方法,可以将任何 decoder-only LLM 转换为强大的文本编码器,如图 8 所示,只需三步即可:1)启用双向注意力机制(bidirectional attention);2)掩码下一个token预测(masked next token prediction);3)无监督对比学习(unsupervised contrastive learning)。

图片

△图8:LLM2Vec的三个步骤

LLM2Vec 选择 Llama-3 和 Mistral-7B 作为模型骨架,实验结果和广泛的分析表明,能够以参数高效的方式将 LLM 有效地转变为通用文本编码器,而无需额外的调整或利用 GPT-4 合成的数据。

【算法概述】

  • 双向注意力机制

用全1矩阵替换 decoder-only LLM 的因果注意掩码,转换为双向 LLM,每个token可以访问序列中的其他token。

  • 掩码下一个token预测

通过掩码下一个token预测(MNTP)来调整模型,使其更好的利用双向注意力。MNTP 是一种训练目标,它将下一个token预测与掩码语言建模相结合。给定一个任意序列作为输入,首先掩蔽输入 token 的一部分,然后训练模型根据前后文来预测这些掩蔽的 token。

  • 无监督对比学习

decoder-only LLM 没有经过明确训练来捕捉整个序列的上下文,该方法采用 SimCSE 的无监督对比学习弥补了这一缺陷。具体来说,给定一个输入句子,让句子两次通过模型,每次使用独立采样的dropout掩码,从而为同一个句子生成两种不同的表征。模型被训练为最大化这两种表征之间的相似性,同时最小化它们与批次中其他句子表征之间的相似性。最后对词表征进行池化操作,以获得句子表征。

2.2.5 GRIT

【主要贡献】

Generative Representational Instruction Tuning (GRIT) 模型**,**与 Echo-mistral 和 LLM2Vec 的思想类似,也强调了双向注意力对通用文本嵌入的重要性。然而,生成式表征指令调优(GRIT)方法的关键在于,通过训练 LLM 和指令来统一处理生成和嵌入任务。对于长文本,不再需要单独的检索和生成模型,该模型可以将检索增强生成(RAG)的速度提高60%以上。模型骨架选用 Mistral-7b 和 Mistral-8x7b。

【算法概述】

GRIT 将表征式指令调优和生成式指令调优统一到了一个模型中。采用一致的数据格式,使用表征和生成式指令数据对预训练的 LLM 进行微调,如图 9 所示。对于表征数据,使用 in-batch negatives 的对比目标来计算损失,同时采用双向注意力机制,随后进行平均池化对序列长度上的最终隐藏状态进行平均。为了计算生成式数据的损失,GRIT 使用语言建模目标,即模型需要预测下一个 token。最后,将目标函数与损失权重λRep λ_{Rep}(表征式损失权重)和 λGenλ_{Gen}(生成式损失权重)相加。

图片

图片

△图9: GRIT的架构和格式

2.2.6 GTE-Qwen1.5-7B-instruct

【主要贡献】

GTE-Qwen1.5-7B-instruct 基于 GTE 嵌入模型和 Qwen1.5-7B LLM 所构建,借鉴了 Qwen1.5-7B 模型强大的自然语言处理能力。通过先进的嵌入训练技术,该模型融入了多项关键改进:

  • 整合了双向注意力机制,丰富了模型的上下文理解能力;

  • 仅在查询侧应用了指令调优,以提高效率;

  • 在涵盖不同领域和场景的大规模、多语言文本语料库上进行了全面训练。这种训练同时利用了弱监督和监督数据,确保了模型在多种语言和广泛的下游任务中的适用性。

此外,还推出了 GTE-base-en-v1.5 和 GTE-large-en-v1.5 两个最新的英文嵌入模型,在同模型尺寸类别的MTEB基准测试上取得了很好的成绩,并且支持长达8192的上下文长度。

2.2.7 stella_en_1.5B_v5

【主要贡献】

该模型是基于 GTE-large-en-v1.5 和 GTE-Qwen2-1.5B-instruct 进行训练的。简化了提示的使用,为大多数通用任务提供了两个提示,一个是用于序列到段落(s2p)任务,另一个是用于序列到序列(s2s)任务。

  • 序列到段落(s2p)任务(例如检索任务)的提示:“Instruct: Given a web search query, retrieve relevant passages that answer the query.\n Query: {query}”

  • 序列到序列(s2s)任务(例如语义文本相似性任务)的提示:“Instruct: Retrieve semantically similar text.\n Query: {query}”

该模型最终选用 MRL 框架进行训练,从而具备多个维度设置,包括512维、768维、1024维、2048维、4096维、6144维以及8192维等多个层次。在理论及实践层面,通常观察到的一个趋势是,随着维度的提升,模型的性能呈现出增强的态势。然而在多数应用场景中,1024维的配置已经展现出了极为可观的性能表现,其在 MTEB 测试中的得分与最高维度的8192维模型相比,差距仅有0.001分。这表明1024维模型在性能与计算资源之间的良好平衡,也为后续模型选择与优化提供了有价值的参考依据。

2.3 方法小结

在本节中,我们详细阐述了基于 LLM 的通用文本嵌入算法模型(特别是在 MTEB 测试中排名前10的模型)。多数算法研究表明,通过全面的自回归预训练策略,LLM 能够习得高质量的文本表征,仅需少量微调即可转型为高效的通用文本嵌入模型。具体而言,微软提出的 E5-mistral-7b-instruct 与谷歌 DeepMind 的 Gecko 模型展示了两种创新途径,即通过 LLM 生成合成数据以进一步优化通用文本嵌入性能。同时,Echo-mistral 与LLM2Vec 的研究则强调,专为 decoder-only LLM 设计双向注意力机制,无需依赖合成数据,同样能实现出色的通用文本嵌入效果。BGE-EN-ICL 利用 LLM 中的 ICL 能力来增强文本嵌入生成的过程。NV-Embed-v2 设计了关注潜在向量的池化嵌入输出,并提出一种两阶段指令调优方法以提高检索和非检索任务的准确性。值得注意的是,本节介绍的所有专注于文本语义嵌入的 LLM 模型均采用了指令调优方法。这主要归因于 LLM 卓越的指令遵循能力,从而可以构建能够灵活应对多样化任务的通用文本嵌入模型的优选。此外,Mistral-7B 模型在 LLM 增强文本嵌入技术中是最流行的基线模型。其中一个关键因素在于,即便未经任何特定训练,为 Mistral-7B 启用双向注意力机制也已展现出了显著成效。LLM2Vec的作者据此推测,Mistral 模型可能已预先经历了某种形式的双向注意力训练。

03 总结

本文开篇即对文本语义嵌入方法进行了系统性概述,剖析了当前主流算法模型的架构特征及其发展脉络与趋势。鉴于 LLM 的迅速崛起,文章进一步在多维视角下对比了 LLM 文本嵌入技术与传统语义嵌入方法之间的显著差异。传统的文本语义嵌入技术以其高效性、广泛应用性和强大的语义表示能力为众多任务提供了有力支持,在自然语言处理领域占据重要地位。然而,该技术也面临着数据依赖性、静态性以及计算资源消耗等瓶颈问题。相比之下,LLM 通过捕捉文本上下文信息、支持多语言处理及出色的生成能力,为提升文本嵌入质量带来了新视角。随后,核心内容聚焦于 LLM 在生成文本嵌入领域的最新研究进展。当前,研究界对通用文本嵌入模型给予了高度关注,该模型旨在构建一个统一且全面的框架,以灵活应对不同长度的输入文本、多样化的下游任务、广泛的领域覆盖以及多语言处理需求。为实现 LLM 在提升文本嵌入能力上的潜力,研究者们探索了两条主要路径:一是通过合成数据生成策略来增加训练数据多样性和灵活性;二是直接将 LLM 作为核心架构,来优化通用文本嵌入的性能。本文介绍了这两条路径下的代表性模型,这些前沿方法均在多文本嵌入基准测试(MTEB)排行榜上名列前茅,在训练数据的规模上实现了显著提升,同时在数据质量与多样性方面展现了创新性的突破。这些进展不仅为文本语义嵌入技术提供了新的研究视角,也为未来自然语言处理领域的发展奠定了坚实的基础。

尽管 LLM 能生成更精确的文本语义嵌入,但也面临计算资源消耗大、数据隐私与伦理挑战以及解释性不足等难题。尤为关键的是,LLM 高度依赖于提示的质量,然而为各种任务创建精确且全面的提示不仅繁琐且耗时,同时,将提示整合到文本嵌入中会增加输入长度,这对于大型数据集和模型而言,将带来额外的计算开销。随着技术的持续进步,未来应该会出现更高效的训练算法,这将显著加速 LLM 的训练过程,进而推动其在 NLP 领域的广泛应用。同时,文本嵌入与图像、音频等其他模态嵌入的融合将成为趋势,旨在实现多模态自然语言处理任务,拓宽模型的应用范围并提升其性能。此外,为应对日益增长的模型解释性需求,未来 LLM 将朝着更强可解释性方向发展,优化提示生成算法和质量评估,确保决策过程的合理性、准确性和伦理性。随着这些技术的不断成熟,LLM 语义嵌入生成技术将在智能客服、内容创作、教育等众多领域展现更为广阔的应用前景,开启自然语言处理的新篇章。

主要参考文献:

[1] Wang L, Yang N, Huang X, et al. Text embeddings by weakly-supervised contrastive pre-training[J]. arXiv preprint arXiv:2212.03533, 2022.

[2] Xiao S, Liu Z, Zhang P, et al. C-pack: Packed resources for general chinese embeddings[C]//Proceedings of the 47th International ACM SIGIR Conference on Research and Development in Information Retrieval. 2024: 641-649.

[3] Li Z, Zhang X, Zhang Y, et al. Towards general text embeddings with multi-stage contrastive learning[J]. arXiv preprint arXiv:2308.03281, 2023.

[4] Touvron H, Martin L, Stone K, et al. Llama 2: Open foundation and fine-tuned chat models[J]. arXiv preprint arXiv:2307.09288, 2023.

[5] Dubey A, Jauhri A, Pandey A, et al. The llama 3 herd of models[J]. arXiv preprint arXiv:2407.21783, 2024.

[6] Jiang A Q, Sablayrolles A, Mensch A, et al. Mistral 7B[J]. arXiv preprint arXiv:2310.06825, 2023.

[7] Jiang T, Jiao J, Huang S, et al. PromptBERT: Improving BERT Sentence Embeddings with Prompts[C]//Proceedings of the 2022 Conference on Empirical Methods in Natural Language Processing. Association for Computational Linguistics, 2022.

[8] Zeng J, Yin Y, Jiang Y, et al. Contrastive Learning with Prompt-derived Virtual Semantic Prototypes for Unsupervised Sentence Embedding[C]//Findings of the Association for Computational Linguistics: EMNLP 2022. 2022: 7042-7053.

[9] Cheng Q, Yang X, Sun T, et al. Improving Contrastive Learning of Sentence Embeddings from AI Feedback[C]//Findings of the Association for Computational Linguistics: ACL 2023. 2023: 11122-11138.

[10] Wang L, Yang N, Huang X, et al. Improving Text Embeddings with Large Language Models[J]. arXiv preprint arXiv:2401.00368, 2024.

[11] M. Rui, L. Ye, J. Shafiq Rayhan, X. Caiming, Z. Yingbo, and Y. Semih, “Sfr-embedding-mistral:enhance text retrieval with transfer learning.” Salesforce AI Research Blog, 2024.

[12] Lee J, Dai Z, Ren X, et al. Gecko: Versatile text embeddings distilled from large language models[J]. arXiv preprint arXiv:2403.20327, 2024.

[13] Lee C, Roy R, Xu M, et al. NV-Embed: Improved Techniques for Training LLMs as Generalist Embedding Models[J]. arXiv preprint arXiv:2405.17428, 2024.

[14] Li C, Qin M H, Xiao S, et al. Making text embedders few-shot learners[J]. arXiv preprint arXiv:2409.15700, 2024.

[15] Springer J M, Kotha S, Fried D, et al. Repetition improves language model embeddings[J]. arXiv preprint arXiv:2402.15449, 2024.

[16] BehnamGhader P, Adlakha V, Mosbach M, et al. Llm2vec: Large language models are secretly powerful text encoders[J]. arXiv preprint arXiv:2404.05961, 2024.

[17] Muennighoff N, Hongjin S U, Wang L, et al. Generative Representational Instruction Tuning[C]//ICLR 2024 Workshop: How Far Are We From AGI.

[18] Li Z, Zhang X, Zhang Y, et al. Towards general text embeddings with multi-stage contrastive learning[J]. arXiv preprint arXiv:2308.03281, 2023.

[19] Dun Zhang. stella_en_1.5B_v5. huggingface.co/dunzhang/st…, 2023.

----------END----------

推荐阅读

持续推进“人工智能+”行动,百度智能云+DeepSeek为何成为国有企业首选?

GPU 云服务器的软件系统设计和实践

基于Flink的配置化实时反作弊系统

百度智能云xDeepSeek,最具性价比的DeepSeek一体机合集来了!

基于Flink的配置化实时反作弊系统

作者 百度Geek说
2025年3月4日 10:33

导读

本文详细阐述了基于Flink构建的实时反作弊流式过滤系统,针对大流量场景下的复杂特征计算、高频策略热更新、模拟过滤验证及多场景数仓对接等核心挑战,提出来多项解决方案,实现了秒级特征计算的实时过滤功能,有效支撑高并发场景下的精准风控判定,并通过ClickHouse与图灵双链路数据输出,满足实时监控与离线分析的多样化需求,为互联网业务提供了高吞吐、低延迟、强稳定的实时反作弊解决方案。

01 简介

在互联网业务高速发展的今天,反作弊已成为APP厂商生态稳定运行的重要保障。作弊行为层出不穷,包括恶意点击、刷单、羊毛党等,这些行为不仅会破坏平台公平性,还可能造成巨大的经济损失。因此,构建一个高效、灵活、可扩展的实时反作弊系统变得尤为重要。

反作弊系统根据业务属性和时效性可分为三类:在线反作弊、实时反作弊与离线反作弊。其中,在线反作弊具备最高的时效性,能够即时响应风险;离线反作弊依托最全面的信息,支持深度分析与建模;而实时反作弊则兼具二者优势,提供平衡的时效性与信息丰富度。

在线反作弊系统通过快速处理简单指标进行判断,例如分析当前请求携带的字段信息,并结合基于 Redis 的简单累计值(如访问频率或特定行为计数)来制定策略。这种系统以低延迟为核心,能够在毫秒级别响应反作弊判定结果,适用于拦截时效要求高的风控需求。

离线反作弊系统通过对完整的离线数据进行大规模、长周期的数据挖掘和样本分析,为优化线上策略、构建特征黑产库和训练高精度模型提供支持。然而,由于依赖离线数据的批量处理,其时效性相对较低,通常难以满足实时风控的需求,更适合用于长期策略优化和深度分析场景。

实时反作弊系统能够在秒级别和分钟级别对用户的异常行为做出反馈,及时识别作弊用户并对业务进行止损。虽然其时效性略低于在线反作弊,但得益于对丰富维度和行为序列特征的分析,实时反作弊可以实现更加精准的策略判定,在精准性与时效性之间达到良好的平衡。

图片

本篇文章我们将重点分析实时反作弊流式系统的相关实现。

02 流式系统面临的核心问题

在实际建设过程中,我们需要解决以下关键挑战。

2.1 复杂的特征计算

在实时反作弊场景中,用户行为数据规模庞大且动态变化(如电商大促、搜索点击等),系统需要处理海量的用户行为数据,并需基于时间窗口快速计算多维特征(如用户点击频率、IP集中度、设备关联账户数)。这些特征需覆盖不同窗口粒度(秒级、分钟级、天级)和窗口类型(滑动、滚动、会话窗口),以捕捉异常行为模式。

  • 窗口特征计算的挑战(多维度多窗口多指标聚合):反作弊策略通常需要基于不同时间窗口(如分钟级、小时级、天级),不同维度(用户、设备、IP等)进行特征累积计算。例如,计算某个用户在过去1小时内的点击次数,或者某个IP在过去24小时内的访问频率。这些计算涉及滑动窗口、滚动窗口等多种窗口类型,计算量大且复杂。

  • 数据乱序问题:网络延迟或分区消费不均可能导致事件乱序到达,若未正确处理,会导致特征计算不准确,进而影响反作弊策略的判定。

  • 高并发下的状态存储优化:在高并发场景下,特征累积计算需要频繁访问状态后端(如RocksDB),导致性能瓶颈。例如,当QPS达到数十万甚至上百万时,状态后端的访问压力会显著增加,影响系统的吞吐量和延迟。长周期窗口(如月级)到期时,大量Key需同时清理状态,引发瞬时资源争抢,导致作业卡顿。

详见 3.2 大规模窗口特征计算,通过 内存缓存+微批处理减少状态访问、事件时间排序缓解乱序影响、keyBy和trigger优化降低状态后端压力,最终支撑高吞吐场景下的精准计算。

2.2 高频的策略更新迭代

反作弊策略需要快速响应新型作弊行为。例如,当出现新的刷单手段或恶意点击行为时,风控团队需要迅速调整策略,以应对新的威胁。此外,不同业务场景(如广告点击、电商交易、社交互动)的反作弊策略差异较大,策略的复杂性和多样性增加了系统维护的难度。

  • 高频迭代需求:

  • 反作弊策略需要根据业务需求和作弊手段的变化进行高频更新,传统开发模式(修改代码→测试→发布)无法满足时效性。部分策略需“热生效”,避免作业重启导致数据丢失或计算中断。

  • 策略复杂性升级:

  • 多规则嵌套:单一策略可能需组合字段匹配(如IP黑名单)、模型评分(如行为异常概率>90%)、时间窗口特征(如近5分钟同一设备注册账号数>3)等多层条件,这些策略的复杂性增加了开发和维护的成本。

  • 配置管理风险

  • 人工修改配置文件易出错(如语法错误、字段误配),导致作业崩溃或策略漏判。

详见 3.3 配置化,通过全流程的配置化升级和配置文件托管,将策略规则、特征计算、字段抽取等逻辑抽象为配置文件,支持快速策略调整和上线,减少对底层代码的依赖,提升策略迭代效率。

2.3 模拟过滤的支持

在反作弊策略上线前,风控团队需要对策略进行测试和验证,以确保其有效性和稳定性,这一过程称为模拟过滤。在实时反作弊系统中,模拟过滤是策略上线前的核心验证环节,其必要性体现在以下三个关键维度:

提前规避线上风险,防止“误杀”与“漏杀”:直接在生产环境上线新策略存在风险,可能导致误判或漏判,影响业务正常运行。因此,需要在测试环境中对策略进行模拟过滤,确保其准确性和稳定性。

验证策略性能,避免作业过载:模拟过滤历史峰值流量(如大促期间数据),验证作业在极限负载下的稳定性。

历史回溯与极端场景覆盖:从HDFS读取数月前的全量数据(如黑产攻击事件日志),验证策略对历史攻击的检测能力和进行数据回溯。

图片

详见 3.4 模拟过滤的实现,通过配置化、线上流与测试流隔离、数据Source改造等方式,加速策略效果验证环节。

2.4 多场景数仓对接与平台整合

我们的系统产出的数据需要支持业务方的复杂分析需求。例如,基于反作弊结果进行策略优化,实时监控作弊行为的影响,对历史数据进行深度挖掘。

目前我们支持多种数仓形式(如实时ClickHouse与离线Hive)的数据产出,满足不同业务场景下的需求,包括实时数据看板、策略评估、历史回溯等应用。

  • 数据产出的便利性:反作弊系统需要将计算结果输出到多种存储系统(如ClickHouse、Hive、Redis等),以满足不同业务场景的需求。例如,实时数据需要写入ClickHouse用于实时监控,离线数据需要写入Hive用于历史分析。

  • 自助分析能力:业务方需要对反作弊结果进行多维度的分析,例如按时间、地域、用户群体等维度进行统计分析。传统的固定报表无法满足这种灵活的分析需求。所以支持业务方进行自助分析,能够根据需求灵活查询和分析数据,而不依赖开发团队的支持。

详见 3.5 便捷的数据分析,通过将反作弊结果输出到ClickHouse和Hive,支持实时和离线分析。同时,接入TDA(Turing Data Analysis自助分析平台),业务方可以通过简单的SQL查询或可视化工具,灵活分析反作弊数据,满足复杂的分析需求。

03 反作弊流式框架介绍

3.1 反作弊系统整体框架

整个实时反作弊的生效流程图如下:

图片

上图展示了 Flink 反作弊流式实时过滤系统 的整体架构,包括 风控平台、实时作业、外部存储 三大核心模块,整体流程如下:

  • 风控平台(配置分发):反作弊工程师在平台上编辑策略规则、配置特征计算逻辑,并一键生成配置文件和启动模拟过滤验证策略效果。测试通过后,策略配置通过平台分发至实时作业。

  • 实时作业(配置解析与执行):Flink 作业解析平台下发的配置文件后,构建作业各个模块,包括数据接入、ETL处理、特征计算、规则匹配等,最后提交并执行流式任务。

  • 作业结果存储(结果输出):ClickHouse,存储实时计算结果,支持快速查询与监控。Hive:存储离线数据,用于历史回溯与深度分析。Redis:提供低延迟查询,支持在线服务实时访问反作弊结果。消息队列:将判定结果传输至下游业务系统,供下游实时决策。

Flink作业内部,实时流运行各个模块拆解如下:

图片

流式作业的主要模块可以分为:

  • 数据接入Source:业务事件日志数据(用户行为、支付、点击、搜索等)接入。

  • 数据ETL处理:数据清洗、转换、标准化;简单维度拼接(ip 映射城市等);第三方字段请求(风险评分、黑设备、用户画像等)。

  • 多重窗口特征计算:时间窗口(分钟级、小时级、天级、周级、月级)、滑动、滚动窗口等,多种维度多种聚合函数进行特征累积聚合。

  • Join阶段:负责将特征和原始日志进行join。

  • 规则策略匹配与判定:机器学习模型打分,配置化规则引擎基于之前的所有信息进行最终判定。

  • 下游输出:实时反馈给线上服务、下发给业务方、入数仓表等方式将判定结果进行输出落盘。

3.2 大规模窗口特征计算

对于整个作业而言,主要计算资源就是用于累积基于窗口的特征。对于业务需求而言,不同窗口下的特征聚合结果是提升判定的准确率和召回率最重要的信息。

我们的窗口累积逻辑主要基于 Flink 窗口功能实现,包括TumblingWindows、SlidingWindows和SessionWindows,Session窗口使用较少。我们未使用其原生Aggregate 函数,而是采用了更底层的 WindowProcessFunction实现窗口聚合逻辑。这种方式的优势在于为后续优化提供了更大的灵活性和定制空间。

为了满足业务诉求,我们也对原生的窗口机制进行了多项优化,主要升级点有以下几个:

  • 提前触发:无需等待窗口结束即可实时下发累积结果,满足业务对于数据时效性的要求。

  • 批量更新和抗乱序:采用批量状态更新方式,减少频繁读取与写入,同时在微批更新时进行局部重排序,以降低乱序影响。

  • 键缩减-粗粒度KeyBy:优化keyBy和窗口触发器设计,减少状态访问频次,提高缓存命中率,降低计算开销。

下边将分别进行介绍。

3.2.1 时效性优化-提前触发

默认情况下,Flink 的每个窗口自带一个触发器(Trigger),在窗口结束时触发计算并生成聚合结果。然而,在实时性要求较高的反作弊场景中,如果窗口长度长达一天,等待窗口结束再下发结果显然不符合要求的。因此,我们需要在窗口尚未结束时,通过特定条件提前触发窗口计算,这种机制称为“提前触发”。

Flink 提供了多种现成的窗口触发方式,包括按ProcessTime定时触发、按EventTime定时触发、按固定条数触发等,同时也支持自定义触发方式。针对我们的业务需求,目前采用的是按事件时间的间隔提前触发方式。具体触发间隔依据不同业务场景设定,能够在秒级或分钟级就能得到窗口的聚合结果。

图片

上图: 展示了 Flink 原生窗口的触发机制及其聚合过程。每个绿色矩形表示一个窗口,窗口范围内累积了多个事件,编号为 1、2、3 、4、5。红色圆圈表示触发时下发的特征数据,从上图可以看到,窗口触发是在窗口结束时统一执行的,下发了2、5、3、1四条特征。

下图:改造后 - 提前触发机制**,**展示了优化后的窗口触发机制,通过提前触发减少延迟。每个绿色矩形依旧表示一个窗口,但触发时间提前,避免了窗口结束时的集中计算,红色圆圈同样表示输出结果。提前触发机制在窗口中按事件到达顺序多次输出,窗口中的事件可以更早地被处理,提升了时效性。

3.2.2 乱序和性能优化-批量更新和乱序纠正

在大流量场景下的测试表明,当前吞吐瓶颈主要受限于窗口聚合时RocksDB 状态后端读写。由于一条数据会抽取多条特征,所以特征窗口累积算子会对 Source 输入数据进行爆炸式扩展,例如当输入数据 QPS 达到 10 万时,特征累积算子的 QPS 可能攀升至数十万甚至上百万,导致大量状态读写请求集中在 RocksDB,使其难以支撑高吞吐需求。

Flink 默认的窗口机制会在每条数据到达时更新累积值,并与状态后端交互,进一步加剧了 RocksDB 的负担。为优化性能,我们将窗口触发和累积调整为微批模式,每次批量更新数据,并引入内存缓存层,微批内优先访问内存缓存,有效减少状态的访问次数。

在百度搜索和点击流量场景下的测试结果显示,该优化方案使内存缓存命中率提升至 90% 以上,意味着特征累积阶段减少了约 90% 的状态后端访问。

同时,在微批数据内部,我们会进行排序,还能有效缓解数据乱序问题,提高计算准确性。如下图所示。

图片

上图:Flink 默认窗口累积机制,**绿色矩形代表窗口的时间范围,窗口中的每一条数据(标记为 1、2 等)都会触发累积操作。图示中展示了 5 条pv的状态后端访问,每条pv都需要与 状态后端(图中黄色区域)进行交互,包括查询、更新、写入等操作。红色圆圈是输出的累积结果,红色边框标记的条目表示乱序数据。上图存在两个问题,第一,对状态后端的频繁随机访问会导致性能瓶颈,尤其是在高并发和大流量场景下。第二,输入数据是乱序的情况下,输出数据也是乱序的。

下图:优化后的窗口累积机制,**优化引入了内存缓存和微批模式。数据小批量更新(如标记为2、1、4为一批、3、5为一批)。每次窗口触发时,首先会对本次微批内的数据进行排序(2,1,4被纠正为1,2,4),然后再累积。累积时,窗口内的累积查询会先访问内存缓存,如果内存miss,再访问状态后端。最终图示中仅有 4 次状态后端交互,较优化前的15次减少11次。数据乱序也得到了缓解。

3.2.3 大流量场景优化-键缩减(粗粒度KeyBy)

窗口聚合过程中累积器需要频繁读写状态后端。此前,我们通过引入缓存层和微批模式大幅减少窗口累积器对状态的访问频次,优化效果显著。然而,在实际应用中,我们发现窗口触发器(Trigger) 也会频繁访问状态后端,带来额外的性能开销。

在实际业务场景中,特征累积的窗口划分通常较细粒度,例如基于ip、query、uid进行 keyBy,且随着业务接入的线索和特征增多,key的数量变多,计算压力进一步加大。这导致两个主要问题:

  • Key 数量激增,触发频繁访问状态:keyBy 后的Key量级极大,每个 Key 维护独立的Trigger,这些Trigger需要不断访问状态后端进行触发注册,造成高频状态交互,影响吞吐。

  • 窗口清理(clear)导致计算压力骤增:当水位(watermark)推进到窗口末端时,大量Key需要同时触发Clear操作,瞬时状态访问量暴增,可能导致作业卡顿甚至崩溃,特别是在窗口长度较长、窗口内Key数量庞大的情况下。

针对上述问题,我们探索了更高效的Trigger机制,以降低状态访问开销,提高作业稳定性。

第一,减少 Trigger 数量:

举个例子,我们基于UID进行特征聚合,如果我们在对特征数据执行keyBy操作时,直接按照最细粒度的UID维度进行分区处理。那么每个唯一的key都会绑定一个触发器(trigger),而触发器的数量直接影响状态访问的频次和资源占用。

为了解决这个问题,我们采用了按UID进行取模分区的方式(例如按uid%100 进行keyBy分区)。这种方式显著减少了触发器的数量,从而降低了状态存储和访问的开销。同时我们定制了聚合函数,保证每个分区内进行聚合计算的时候还是会按照原本的UID作为key进行特征累积,保证特征累积的准确性。

第二,状态放入内存:

进一步优化时,我们发现,当按照固定数量(如100个分区)取模后,key的数量和值是确定且有限的。基于此特性,我们将触发器的状态从Flink的状态后端迁移到内存中管理,这样能够进一步提升性能,避免频繁访问状态存储带来的开销。

有人可能担心:触发器状态迁移到内存后,作业一旦发生重启,内存中的数据会丢失,这可能导致窗口数据无法正常触发。例如,若按UID进行keyBy计算,某个UID仅有一条数据,且此时作业重启导致其触发器状态丢失,那么作业恢复后这条数据永远可能无法下发。

但通过固定分区取模(如按 %100 分区)后,我们有效解决了这个问题:

  • 取模分区的 key 数量是有限的(如 100 个),并且这样能保证每个分区会持续接收到新的数据。

  • 当作业重启时,新数据的到来会自动重新注册触发时间,即便原有内存状态丢失,后续的数据流动能够重新触发正常的处理逻辑。因此,即使触发器状态短暂丢失,取模后的分区会很快自愈,确保数据下发的正确性和完整性。

图片

上图最左边-原始设计,数据流按照 uid 进行 keyBy 分组,每个 uid 都对应一个独立的 trigger。每个trigger需要与状态后端 (StateBackend) 频繁交互,包括保存和更新状态。存在问题是:状态后端需要频繁访问,尤其在高并发场景下,性能瓶颈明显。每个 uid 都维持一个独立的窗口触发器,资源消耗较高。

上图中间-第一版优化,将原始 uid 进行取模操作 (uid % 100),将原本细粒度的分组合并为粗粒度的分组。即多个 uid 合并到同一个分组中,减少了窗口触发器的数量。状态后端的访问频率有所减少,降低资源消耗,提升了整体吞吐量。

上图右边-第二版优化,内存的引入,每个trigger相关的信息存储于内存中,而不是直接与状态后端交互。大幅减少状态后端的访问次数。提升了系统性能,确保作业稳定运行。

综上,在 Flink 反作弊系统的窗口特征累积优化中,我们针对高吞吐、低延迟、抗乱序等业务需求,进行了多项改进。

1.提升时效性:反作弊策略依赖实时特征,默认窗口触发方式无法满足业务需求。因此,我们采用提前触发机制,基于事件时间间隔触发计算,使特征聚合结果能够秒级或分钟级输出,避免长窗口带来的数据滞后问题。

2.优化性能瓶颈:在高并发场景下,特征计算涉及海量状态存储访问,容易导致RocksDB负载过高,影响作业稳定性。我们引入批量更新、内存缓存 、trigger优化、分区缩减等方式,大幅提升吞吐量。

综合优化后,该方案使 Flink 反作弊系统具备更快的特征计算能力、更高的吞吐性,有效支撑高并发业务场景下的实时风控需求。

3.3 配置化

为了满足反作弊策略的高频上线和模拟过滤等需求,我们的实时系统实现了高度配置化。并且配置文件全部托管到风控平台。通过配置化驱的架构,无论是字段抽取、特征加工、策略规则定义和数仓产出,均可以通过简单的配置操作快速完成,极大地缩短了开发周期,同时降低了对底层框架代码开发的依赖。只需要在风控平台上编辑好策略,就可以一键分发并启动对应的测试或线上作业。

图片

如上图所示,相关配置文件可以分为两类分别是工程配置(绿色)和策略配置(黄色),策略配置主要用于定义业务过滤规则和逻辑,工程配置侧重于系统的底层运行,比如输入输出、并行度等配置。并且部分配置文件为非必需项,这意味着如果某个计算模块不需要使用,则相应的配置文件可以省略。

3.3.1 工程配置

工程配置是管理流式作业运行的系统层面参数。针对反作弊场景的实时流式任务,与 Flink CDC YAML的设计思路类似,也是通过 YAML 文件对通用工程配置进行抽象和统一管理,确保流式作业能够灵活适配多种业务场景。

为了保证一个 Flink 流式作业的正常运行,完整的工程配置需要包含以下几个关键部分:输入配置、输出配置、并发配置。

  • 输入配置:决定了 Flink 作业如何接收和解析源数据,定义数据源类型(如Kafka、HDFS)、连接参数、消费策略等。

  • 输出配置:定义了 Flink 作业的计算结果如何存储或传输到下游系统,指定结果存储方式(如ClickHouse表、Redis集群、Kafka Topic)。

  • 并发配置:直接影响 Flink 作业的性能、吞吐量以及资源使用情况,设置算子并行度、检查点间隔等,优化作业性能。

3.3.2 策略配置

策略配置是指将反作弊拦截策略的核心逻辑规范化,以配置文件的形式灵活定义和管理。通过策略配置化设计,能够快速调整或部署反作弊策略,无需修改底层代码。

策略的配置主要由字段抽取配置、特征配置、词表配置、模型配置和规则配置等组成。

字段抽取配置:字段是反作弊策略和数仓的最基础的信息,根据抽取方式不同分为:

  • 基础字段:直接从原始数据流中提取的字段,例如设备 ID、用户 ID 等。

  • 二次计算字段:通过基础字段计算生成的派生字段,设备ID是否合法,UID是否为历史黑用户等。

  • 外部服务字段:通过调用外部服务接口动态获取的字段,例如 IP 地址归属地、安全风控标签等。

  • 维表字段:通过查询词表映射关系获得的字段,例如黑名单匹配结果、分类标签等。

我们将字段抽取逻辑进行了配置化抽象,策略开发人员使用类似于写sql的方式即可完成简单字段的etl逻辑的开发,如常见的json字段抽取,字符串处理,反作弊内部的常用UDF等,配置能覆盖大部分字段抽取,对于复杂的字段抽取逻辑仍旧使用Flink的Datastream API开发实现。

特征配置:特征是策略的重要判定依据,特征配置包括以下几个关键方面:

  • 特征类型:数据的聚合方式,如sum、count、distinct等。

  • 窗口信息:设置聚合特征的时间窗口范围和窗口形式,时间范围如:1 分钟、1 小时等,窗口形式如:滑动窗口、滚动窗口等。

  • 特征维度:特征的聚合维度,如用户、设备、IP 地址等。

词表配置:词表通常是离线挖掘得到的黑名单、字段映射(如ip映射城市)等固定维表信息,配置内容需包括以下几个方面:

  • 词表路径:指定词表的存储位置,支持文件路径或分布式存储地址。

  • 词表类型:支持多种形式的词表,包括集合(set)、键值对映射(kv)、正则表达式(regex)等。

模型配置:通过模型实现复杂的行为预测和风险判定,关键配置内容包括:

  • 模型路径:指定模型的存储位置,支持本地或远程加载。

  • 模型类型:支持多种模型形式,例如线性回归、GBDT等,目前模型的加载是通过PMML框架实现的。

  • 模型输入输出:明确模型所需的输入字段和输出字段等。

规则配置:规则配置决定了作弊行为的最终判定规则和处置方式:

  • 策略判定阈值:定义触发策略的条件,例如基础字段匹配、词表匹配、风险评分的阈值、特征累积阈值、模型打分阈值等。

  • 策略判黑等级:设定风险等级,区分低、中、高风险及对应的处置措施。

如下图所示,规则配置能够获取所有字段信息,并基于这些信息进行最后的策略判定。

图片

这张图展示了反作弊规则的判定流程:

1.输入数据:每条PV包含多个字段**,**包括基础字段(如IP、手机号、UID等)、外部抽取字段(如IP归属地、是否异常等)、计算得到的特征(如统计特征fea1、fea2等)以及模型得分(多个模型计算的分值)。

2.策略判定:系统基于预设的反作弊规则,对各字段、特征、模型分数进行综合评估。例如,规则1要求【fea1 > 100 && model2 > 0.95】,规则2要求 【IP like '192.%' && fea2 > 100 && model1 > 0.65】。多个规则都会执行判定逻辑,判断是否命中。

3.结果输出:最终的PV数据会带上反作弊命中结果。例如,在示例中,该PV数据命中了规则2,表明该行为可能存在风险。

以上就是策略配置的所有介绍,通过配置化管理字段、特征、词表、模型和规则,反作弊系统能够快速响应业务需求,灵活调整检测逻辑。同时,配置化设计大幅降低了开发部署成本,提高了策略迭代效率。

3.4 模拟过滤的实现

通过配置化的支持,可以方便地切换数据输入和输出,所以仅需调整测试配置文件,即可启动模拟过滤作业,提升测试效率。同样的模拟过滤功能也接入了风控平台,开发者可以直接一键调启模拟过滤任务。

对于直接接入实时消息队列进行模拟过滤,基本无需修改输入配置,然而,基于实时消息队列的模拟过滤存在一定局限性,首先运行耗时较长,需要1:1的时间来进行测试。如过滤24小时数据需24小时,难以快速验证策略长期效果,其次消息队列仅保存最近几天的数据,历史数据回溯能力有限。一般仅适用于简单工程测试和策略模拟过滤。

为此,我们扩展了 Flink 的 HDFS Source 组件,支持直接读取HDFS上的 Parquet 文件进行模拟过滤。在读取 Parquet 文件时,因为是流式计算,主要挑战在于数据顺序问题,为确保数据时序一致性,我们采取了以下优化策略:

  • 文件级别排序:在读取数据时,按照文件路径和名称进行排序,确保数据按时间顺序加载。

  • 顺序读取文件:严格按照排序后的顺序依次读取 Parquet 文件,避免乱序问题,保证数据的时序性。

通过这一优化方案,我们在策略模拟过滤中与线上对比测试,准确率达到 99% 左右,大幅提升了模拟过滤的可靠性和一致性。

图片

除了Source组件适配了读取离线数据外,其他组件跟线上完全一致,这样就保证了模拟过滤的准确性。极端场景下(如线上作业出错需重新回溯数据),可通过此方式对离线数据再次模拟流式过滤,实现数据修正。

3.5 便捷的数据分析

在实时反作弊系统中,数据分析不仅是风控团队优化策略的核心工具,也是业务方监控风险、评估影响的重要支撑。为了满足不同角色的分析需求,系统提供了离线和实时的数仓产出,帮助进行便捷的数据分析。

图片

上图展示了我们的数仓方案,其中 Flink 负责实时数据流的计算和处理,最终将数据分别存储到 HDFS(Parquet 格式) 和 ClickHouse。

  • 离线Hive表:Flink 将数据以 Parquet 格式 存储到 HDFS,支持按刻钟或小时级别分区产出数据,并挂载到图灵表中,便于后续使用 Hive、Spark 进行批量计算和查询。也可以作为后续回溯和测试模拟过滤的直接输入。

  • 实时ClickHouse表:支持实时数据产出,用于高性能的 OLAP 分析,可用于快速分析策略上线效果、构建实时看板以及告警监控等场景。

这种架构能够同时满足 实时查询 和 离线存储分析 需求,实现高效的数据流式处理与存储。

高效便捷的数据分析主要满足了如下需求:

  • 实时监控与告警:业务方需实时了解反作弊策略的执行效果(如拦截量趋势、高风险用户分布)。

  • 基于 ClickHouse 的秒级查询能力,支持实时生成监控看板(如“近1小时拦截量TOP 10 IP”)。

  • 配置告警规则(如“拦截量突增50%”),通过邮件或消息推送及时通知相关人员。

  • 自助分析与可视化:业务方能够灵活分析作弊行为特征(如羊毛党设备型号分布、异常行为时间规律)。

  • 接入 TDA自助分析平台,支持 SQL 查询与拖拽式可视化分析,无需依赖数据团队。预置常用分析仪表盘,降低使用门槛。

  • 离线挖掘与模型优化:数据同学能够于历史数据挖掘作弊模式,优化策略规则与机器学习模型。

  • 将全量数据存储至 Hive,支持 Spark、Flink 等分布式计算引擎进行进一步的复杂分析。

通过便捷的数据分析能力,系统不仅提升了风控策略的迭代效率,还赋能业务方自主探索数据价值,实现从“被动响应”到“主动洞察”的转变。

04 总结

本文介绍了基于 Flink 的实时反作弊流式过滤系统,围绕架构设计、挑战应对及优化方案展开。通过特征计算和配置化管理,提升了系统的检测效率和稳定性。实践表明,该方案在提升数据处理时效性与反作弊效果方面均取得显著成效。未来,将进一步优化策略检测机制,提升检测精准度,并探索更智能的风险识别手段。

------END-----

推荐阅读

百度智能云xDeepSeek,最具性价比的DeepSeek一体机合集来了!

图引擎在智能体开发场景的应用实践

直播间互动框架性能优化与稳定性实践

百度网盘防雪崩架构实践

如何在百度百舸部署满血版DeepSeek-V3、DeepSeek-R1模型

图引擎在智能体开发场景的应用实践

作者 百度Geek说
2025年2月25日 10:06

导读

随着AGI理论的不断突破,智能体已经成为LLM在企业落地的最重要的形式之一。一个完备的智能体必须能实现:感知、推理、计划、执行等一套完整的功能,从工程的角度来看workflow特别适合这种复杂任务的分析、拆解、重组、执行,  再结合CoT技术, 实现LLM和业务功能完美契合的智能体应用。本文尝试用成熟的图引擎技术驱动workflow探索更多样性的拓展agent能力的方法,以更好应对各类业务场景。

01 简介

1.1 什么是智能体

以大模型(LLM)为核心, 具备以下特性的智能化系统:

  • 交互性: 通过文字,语音,图像等多种交互方式来理解用户的持续性需求  (感知);

  • 适应性: 感知环境的变化持续进化,以更好地完成任务和适应复杂环境      (记忆);

  • 自主性: 能够自主学习,主动思考和决策                                               (推理);

图片

图片

1.2 业务形态、流程

一个智能体生态平台,用户可以在上面体验功能各异的智能体app,同时也能让用户将自己的优秀想法以极低的成本(通过快速组装已有的插件、workflow、知识库、记忆) 快速实现成新的agent。

图片

图片

图片

系统特色:

  • 流程编排能力:支持可视化的数据流加工,通过编辑各个处理节点将原始input加工成output;

  • 功能复用能力:众多的agent库、插件库都可直接复用到自己的智能体里, 可插拔、替换;

  • 低代码能力:无需大量写代码,直接通过拖拽元素就能拼装出想要的功能。

图片

图片

1.3 业务场景的需求难点

1.3.1 能自由组装流程实现人机无缝衔接、数据解耦

  • 能将人的需求表达和agent思考结果的进行完美串联融合,发挥各自优点;

  • 除context外更多样性的数据传递方式,更好满足workflow、cot等流程编排的场景;

  • 细粒度控制数据传递、适配方式,满足特定场景的灵活性和性能的平衡需求。

1.3.2 能更精细规划路径、简化流程设计

  • 支持多种路径控制能力,满足多样性的静态化任务编排;

  • 支持在workflow内部动态编排新的子flow, 满足动态化的场景。

1.3.3 能对流程进行统一的控制、干预

  • 流程运行过程中当出现超时、异常等非预期情况需要框架能提供快速干预、退出机制;

  • 摆脱对executor(执行器的依赖),更低成本支持大量功能异构的流程。

1.3.4 能进行简单的功能注入

  • 支持在模型前后、工具调用前后等地方注入策略逻辑和观测代码,避免对大量节点进行浸入式改造;

  • 支持流程编排时给节点初始化赋值,降低数据传递的成本;

  • 支持任意节点信息的流式输出能力,满足长流程中阶段性结果的sse输出需求。

1.3.5 能支持缺少代码能力的使用场景

  • 将用户生或者LLM产生的cot转化成具体流程配置;

  • 将流程配置转换成可运行的代码。

1.4 为什么自研图引擎

1.4.1 常用智能体开发框架简介

  • LangChain框架:一个开发智能体的框架,定义了prompts,  index, memory, agents, tools, outputParser等一系列功能抽象,通过chains将各个功能串联成应用。

  • 开发模式:

  • Chains:  规划静态任务,  很多抽象都实现了chains的接口,规划好路径就能让各功能有序执行

  • AgentExecutor:  执行动态任务,某些场景无法预知执行路径,需要不同的输入走不同的分支,因此引入代理人(AgentExecutor), 通过多轮循环推理产生最终结果

  • 总结:多轮学习和推理是自主ai系统的基本的能力, Chains不具备循环”能力,  AgentExecutor多轮调度是一个不透明度黑盒。

图片

图片

  • LangGraph:基于LangChain基础上演化的框架,引入条件边,赋予用户对循环的控制能力。

  • 开发模式:用透明化的有向状态图打破LangChain动态任务的循环黑盒 (AgentExecutor)

图片

图片

已有框架比较注重系统的自主性,对业务执行路径的编排能力较弱。

1.4.2 业务需求的挑战

  • 强化的路径控制能力:既能满足llm的多轮循环特性, 又能结合cot模式的功能编排;

  • 传统功能的结合能力:模型存在知识能力边界,业务需要结合之前传统功能来满足多样性、个性化的需求, 在数据的校验、传递、并发、同步,流程控制等这些贯穿整个业务随处可见的基建功能都需要支持。

这些都不是已有智能体开发框架本身所擅长的,从下层视角看需要提供一个更通用、更精细化控制能力的流程驱动框架增强以下特性来满足业务需求。

02 用seda图引擎驱动workflow的模式开发智能体

对于以上共性需求我们引入图引擎驱动workflow的开发模式:将任务拆解成独立的功能节点,不仅可以包含 LLM, prompts, memory, tools, 等智能体(ai)元素还融入了 分支、循环、条件、多路复用、数据传递等传统应用所擅长的路径编排、数据转发、超时控制、错误处理等方面的功能,为智能体应用提供一个更强大、稳定、便于解耦且可黏合性强的基座环境。

图片

图片

2.1 实现更灵活的流程拆分组装、数据解耦

2.1.1 流程拆分

元素:

算子:  若干个函数的集合,组成一个业务逻辑上可简单描述的功能模块;

边:  联接2个算子,具有方向性,代表执行顺序,起始节点作为上游算子,指向节点作为下游算子;

联接:

串联: 一个上游算子联接一个下游算子,上下游顺序执行;

并联: 一个上游算子联接多个下游算子,上下游顺序执行,下游算子之间并行执行;

join:   多个上游算子联接一个下游算子,上游之间并行执行,下游等待所有上游执行完后再执行,实现汇聚、归并。

flow:

将复杂业务功能分解成若干个易实现、解耦、可复用的算子,根据业务执行顺序用边将算子串联起来,形成工作流。

用户请求从起始算子流入,通过flow的算子进行加工,最终从结束算子输出,完成整个业务流程。同时支持 dag 和 dcg(有环图)。

图片

2.1.2 分层组装

  • 子图(sub-flow):把一个flow也当成一个算子(sub-flow),直接挂到另一flow上,当数据推动到这个算子的时候就会触发这个(sub-flow)的内部逻辑,实现flow级别的抽象和复用;

  • 分层脉络地图:功能复杂导致算子数量过多时可以按不同粒度划分层级,把相同抽象粒度的算子放到同一个层级,不同层级的算子通过子图实现串联。通过点击进入子图内部浏览功能细节,通过收拢子图回到上层抽象,以此实现功能导航地图,既方便浏览又方便解耦拆分到多人协作开发。

图片

2.1.3 数据解耦

context共享可以方便的在所有算子间传递数据,算子的插拔替换、顺序调整也无需考虑数据的匹配,但同时也带来了数据加工处理过程黑盒,以及为了尽量避免参数读、写范围放大而需要加强数据访问权限保护这2个问题。

为此我们提供了额外的方式,满足不同场景的需求。

  • 链式推导 -- 更细粒度数据解耦

描述:上游output和下游input类型一致,就能串联并传递数据。

优势:output/intput是数据传播的契约约束,数据加工流程清晰。相同intput, output定义的算子之间能串联且插拔替换;相互之间不匹配的算子能通过定义中间类型的"桥接"算子来实现串联。

短板:需要用户手工串联算子,而且算子间容易产生串联类型不匹配以及并发问题也需要用户自己解决。

  • 自动推导 -- 自动优化使用和运行效率

描述:算子数量越多人工编排效率越低,在链式推导的方式上引入Referential Transparency, Single Assignment等规则约束来实现调用链自动推导。

优势:可以帮助用户自动管理大量算子的联接关系, 省时省力的同时还能在保证数据并发安全性的同时实现最大程度的并行化。

短板:关注的焦点从过程转移到数据,用户需要对流程做设计、优化时需要从数据入手做逆向推导流程(数据是在流程中慢慢产生和迭代的),违背了用户的使用习惯。而且对现有系统来说这种对数据读写的严格拆分、解耦往往需要全部推翻重来。

图片

2.1.4 拷贝与引用

除了以上的数据传递方式外在不同的需求场景下我们同时还需要思考是传递数据的副本拷贝还是传递数据引用(本身)的问题

  • context共享:出于性能考虑,基本都是采用传递引用的方式;

  • 链式推导:可以手工指定,默认通过简单的判断下游算子的数量来自动决定:下游算子个数等于1:传引用,大于1: 第一个算子传引用,剩余算子传副本,尽可能在局部范围内降低用户心智负担;

  • 自动推导:在Referential Transparency, Single Assignment双重数据访问规则保护下无需用户考虑并发安全性问题;

  • 自定义:用户可以在联接2个算子之间的边(edge)上显示指定是副本拷贝还是传递数据引用(本身)来获取更大的灵活度。

图片

2.1.5 类型适配

同一类功往往有多种实现,各自针对不同的使用场景有自己的优化,这些算子的input/output 往往基本结构一致,但又稍稍有一些小差异,为了更方便让这些算子能快速插拔,提高功能复用度,降低复用成本,我们提供了一系列自动适配机制。

  • 类型自动放大:内置多种数据类型及其变体自动转换逻辑:

A  <--->  *A,  interface{}

A  -> []A, []*A, []interface{}

  • 特型自动匹配:上游算子的output类型是被下游算子input类型内部组合的子类型,相互能串联,并完成自动赋值。

  • 自定义适配器:一个单独的适配器完成2个功能类同,但是intput/output参数形式差异较大的算子之间串联,适配器的input是上游算子的output, 其output是下游算子的input。

  • 为什么不直接用过度算子实现:避免大量转换算子干扰业务流程;

  • 为什么不把适配逻辑放算子内:避免侵入式改造和过度引用导致耦合。

图片

2.2 实现精细化的路径规划

2.2.1 多功能边

  • if:                    条件边 (单分支);

  • switch:               多条件边 (多分支);

  • multiplex:           多路复用边 (同时监听多个资源、信号);

  • optional edges:  可选边 (按需放弃对非强依赖上游的等待);

  • while:                 循环边 (条件和循环次数组合实现dcg);

图片

2.2.2 动态规划

编译时构建一个基础的大致性的功能脉络(上层基础逻辑定义),具体实现逻辑由执行期根据代码运行情况及LLM返回结果动态规划做逻辑的实时增删、拼接、构建。

图片

2.3 实现统一的驱动控制和干预

2.3.1 流程驱动

  • executor 执行器:

  • 存算分离设计:执行器读取flow信息并驱动执行,提供路径控制、资源调配,flow仅存储数据;

  • 一次解析多次执行:对flow进行一次解析后可以得到该flow的一个excutor, 后续相同的flow都可以通过该executor进行执行;

  • 适用场景:系统里flow异构类型有限, 都需要大量重复的被执行, 能节省每次构建重复flow的开销。

  • flow自驱动:

  • 无需解析,直接执行:flow被构建出来后,利用flow自驱动的功能直接运行, 无需构建执行器;

  • 适用场景:系统里大量异构flow,或者会动态产生出新构型的flow,能节省复用度低的执行器构建的开销。

图片

2.3.2 错误处理

  • 退出流程

当算子返回值 <0 时表示发生错误,默认直接跳到最后一个算子执行(让系统能有一个给用户回包、打日志的机会) 后并退出当前流程,快速终止无效的执行逻辑,迅速释放资源。

  • 逐层退出

当有多层(子图)时候,每一层有算子返回值 <0都会直接跳转到本层子图的最后一个算子执行,随即返回回上一层。用户可以按需给上层指定返回值,当给上层的返回值 >=0时候上游正常往后面算子执行,否则继续跳到本层的最后一个算子执行,以此类推直到整个flow结束。

图片

2.3.3 超时控制

  • 图超时

我们可以给整个flow指定一个执行的超时时间,触发后直接跳入到本层最后一个算子(让系统能有一个给用户回包、打日志的机会),这样快速终止无效的执行逻辑,迅速释放资源。

  • 算子超时

一般没有特殊需求我们建议根据业务接口要求通过flow控制整体超时即可,由系统自行判断可以尽量避免一些超时设置不合理的问题,同时我们也保留单独指定算子超时的能力,算子发生超时但flow整体没有超时时会跳过超时算子继续往下执行;,尽可能保持必要的灵活性。

图片

2.4 实现通用注入

2.4.1 事件监听

  • 监听通用事件

提供以aop接口的形式统一支持算子 on_enter, on_leave, on_timeout, on_error等关键事件的钩子机制,以全局函数或者算子成员函数的形式进行改写,以便用户能统一加入自定义的日志记录、监控、通知、错误处理等应对机制。

  • 监听自定义事件

除了上面的通用事件,我们还提供统一的机制,提供用户在算子内部任意地方加入自定义事件的能力,帮用户简便地完成应用层框架监听机制的建设需求。

图片

2.4.2 附加属性

除了context共享,链式推导的传递机制外我们还提供了额外第三种"附加属性"的方式来给算子传递数据,方便用户在编辑算子时就能给算子指定一些固定属性值,算子运行时能快速被读取,降低初始化成本。

图片

2.4.3 流式输出

放置一个内置的流式输出的算子到workflow里,通过向这个算子的channel里写入数据即可实现在任意算子里流式输出信息来满足sse需求,同时将流式输出算子和最后一个算子相连,即可实现优雅退出。

图片

2.5 实现低代码

  • 提供可视化编辑器,让用户拖拽设计流程并产生对应的配置文件;

  • 后端提供算子仓库作为用户功能实现的基础素材;

  • 图引擎的generate负责将配置翻译成流程代码, builder动态构建流程,driver负责驱动流程运行并返回结果。

图片

2.6 seda相较其他图引擎实现的优势

2.6.1 图引擎实现方案一:多线程并发(thread-base-on-request)

本质:线程数和请求数N:M的模型,基于请求数量规划线程的设计, 由操作系统保证线程调度、资源分配的均衡。

优势:实现简单、数据局部性好,负载在系统处理能力阈值内性能及佳,适用于对耗时要求苛刻的场景。

短板:

  • 流程黑盒:线程粒度太粗(请求粒度),不利于功能迭代、优化。

  • 扩展性差:请求数受系统线程数制约, 负载超出系统处理能力阈值会使系统陷入“调度内耗” (上下文切换,锁竞争),处理能力指数级下降。

图片

2.6.2 图引擎实现方案二:事件驱动并发(thread-base-on-resource)

本质:资源数和线程数N:M的模型, scheduler根据系统资源初始化若干线程,将请求拆解成由若干个non-blocking节点组成的有限状态机(FSM),节点执行后将状态回传给scheduler, 由其根据当时资源使用情况分配下一个节点的处理线程,直到整个有限状态机结束。

优势:

  • 流程可视:通过有线状态机实现各个功能节点之间互相解耦;

  • 扩展性佳:线程数不受请求数影响,能始终控制在系统资源可高效运行的阈值范围内;

  • 吞吐量大:事件驱动极大程度避免了多线程之间的"资源内耗",能有效提升系统并发和吞图。

短板:

  • 时延增大:一次请求处理过程跨多个线程执行增加了数据传递消耗的同时也降低了硬件缓存命中率导致请求延迟增大;

  • 实现困难:中心化的scheduler调度器既要驱动业务状态流转又要管理资源调度往往会顾此失彼。

图片

2.6.3 图引擎实现方案三:基于seda的图引擎驱动(thread-base-on-stage)

seda:staged event driven architecture

本质:将上面中心化的scheduler模块给拆分成多个子部件实现。

  • 用多个事件队列将每个状态节点(stage), 组成一张数据流动网络 (Directed Graph);

  • 每个stage都由事件队列(接收数据)、控制器(分配资源,驱动stage流转)、线程池(调节线程数)、事件处理器(业务处理handler)组成;

优势:

  • 资源按流程stage拆分,粒度适当 (按request粒度过小,容易陷入内耗;按resource粒度过大,容易浪费);

  • 去除中心化模块,通过对事件队列的流速控制使得每个stage可以单独进行负载调节。

短板:

相比运行状态良好情况下的单线程处理一个请求的设计, 时延上会有增大 。

图片

03 通用图引擎在智能体场景的实际应用

3.1 功能场景应用

3.1.1 根据大模型COT结果动态生成子workflow

  • query:请预测下明年下周的天气情况

  • 大模型将问题拆解成具备先后依赖关系的多个小步骤:

  1. 计算下一周对应的时间范围

  2. 查询本周天气情况

  3. 查询历史上前n年对应时间范围的的天气情况,

  4. 根据历史查询结合当前情况推测明年对应的时间范围的天气情况(结果保存到短期记忆)

  5. 如样本范围太小或结果单一则重复前面过程3-4,直到给出预测结果的具体概率分布

  • 大模型根据当前执行到的具体步骤将工作内容动态分解到子图并执行。

图片

3.1.2 复杂场景的功能拆分解耦和精细化路径控制能力

  • 用"多路复用"同时监听多值,支持任意数量路径分发,将 "路由和子功能调用" 算子进行拆分解耦;

  • 用"可选边"将多处可能会触发到的公共逻辑"润色模型"模块拆分成独立算子;

  • 用过"融合边"将各种不同类型的边融合汇聚到一个算子, 便于整体控制流程结束逻辑;

  • 通过以上多种精细化路径控制功能,减少大量胶水代码的同时方便对流程图做快速修改,让用户专注于业务逻辑自身。

图片

3.1.3 通用注入和循环增强

  • 由侵入式改造转变成通用事件注入来统一控制算子内部的共性行为;

  • 个性化功能增强也可以通过非侵入式方式注入算子内部;

  • 在之前纯代码逻辑控制循环结束条件的同时增加了框架保护机制,避免响应不及时和资源长时间侵占。

图片

3.2 小结

通过图引擎驱动workflow的开发模式提供了一个强大的基座,用户可以快速在其上通过插拔替换、顺序调整、串联汇聚、编辑出任意自己想要的流程,其强大的解耦和精细化路径控制能力从根本上解决传统ai开发模式带来的黑盒问题和相关不确定性问题,同时还能获得极佳的运行时效率(天然并发);其自带的低代码、分层导航等特性减少了大量胶水代码,还有助于多人同时入场并行开发,降低开发、维护成本。

目前系统已经接入80w开发者,15w合作企业,超过10w个智能体。

————END————

推荐阅读

直播间互动框架性能优化与稳定性实践

百度网盘防雪崩架构实践

如何在百度百舸部署满血版DeepSeek-V3、DeepSeek-R1模型

首日调用客户破1.5万!DeepSeek-V3/R1上线背后的超低推理成本技术揭秘

唤醒 AI 算力,专有云 ABC Stack 面向企业级智算平台的 GPU 提效实践

直播间互动框架性能优化与稳定性实践

作者 百度Geek说
2025年2月20日 10:29

导读

直播间互动体验框架技术实践,揭秘性能与稳定性优化之道,快来探索吧!在百度直播间歌会红包等活动中,我们创新性地将红包互动与高质内容深度融合,通过技术架构升级与系统性优化,打造了"音乐+红包"(边听歌边抢红包)的沉浸式体验。本次实践显著提升了直播间的并发承载能力、实时互动响应速度和用户参与满意度,同时沉淀出可复用的技术方案,为后续大型直播活动奠定坚实基础。

01 百度直播间歌会红包运营活动介绍

为提升直播内容质量和用户粘性,需注入多元化内容,增强直播间多样性和观赏性。同时,通过活动裂变扩大影响力,吸引特定用户群体,保持用户新鲜感和期待感,为平台长期发展奠定基础。

为落实直播歌会目标要求,需加快直播间互动体验框架建设,探索新型混合模式和沉淀通用能力,着力适配重点业务场景,打造"音乐+红包"的互动体验,提升直播间品质:

一是通用基础。主要包括组件复用、大图压缩等减少产物体积,页面异常、性能、白屏监控,BFF服务编排扩缩、稳定性监控等。

二是访问保障。主要包括页面多域名容灾、开启强缓存;字体、图片、CSS、JS等静态文件单独CDN强缓存域名,开启多级缓存等。

三是红包性能。主要包括页面预静态化、数据预加载、文档预取、资源预取、视图预渲染、动效降级等。

四是开发体验。主要基于直播前端一站式,建强队伍,确保项目开发流程规范统一,搭建增质增效的研发环境等。

图片

02 体积

2.1 页面划分

在大型产品需求中,通过合理的页面划分策略,实现高效开发与维护。面对产品需求中罗列的多样玩法功能和19种以上的红包状态,研发团队面临的首要挑战是如何将这些功能合理的拆解成多个页面承载。合理的页面划分不仅关乎用户体验的流畅性,更是减小产物体积、提升跨页面资源缓存利用率的关键。通过深入分析业务逻辑与用户行为路径,我们精心设计了页面边界,确保每个页面和组件都承载着唯一元素,同时避免了冗余代码的产生。此外,这一策略还极大地促进了开发团队的协作效率,明确的页面划分减少了代码冲突的可能性,使得团队成员可以高效并行集成,从而加速了开发迭代周期。在直播间端能力的规范化构建上,同样遵循了通用化这一原则。

在页面划分时,我们非常注重跨页面资源的最优利用,通过策略性地缓存HTML、CSS和JavaScript等资源,确保一旦用户在任意时刻首次触发了红包弹出事件,这些资源即可被全面缓存,使用户在后续的页面切换过程中无需再次加载这些核心资源。

通过一系列设计举措,划分多页应用(MPA)10+个、单页应用(SPA)20+个、红包组件状态(Component)19+个、规范化直播间端能力(Scheme)30+个,每一项都经过精心设计,共同作用于提升应用的整体性能,为用户带来更加轻盈、快速且协同良好的使用体验。

2.2 性能优化

在直播歌会抢红包运营活动中,Web页占据了80%的比重,对于每一个依赖较多网络资源的玩法页面,在直播间中实现即时加载和快速展现确实面临较大挑战,尤其是在高并发、低延迟的场景下。

图片

△页面展现过程

为了有效应对这些挑战,通过深入分析页面展现过程中的各个环节,直播间互动框架提炼出七种通用的优化方案。旨在提升用户交互体验、增强系统的整体性能。并确保直播间玩法在高并发场景下依然能够流畅运行。这些优化方案覆盖了从页面加载、资源获取到实时交互的各个方面,形成了一个全方位的性能提升样板,具体方案如下:

图片

2.3 页面预静态化(SSG)

在直播歌会抢红包的场景中,所有不变的内容(如活动规则、活动主页框架等)使用SSG能够显著提高页面通用静态内容的加载速度,同时通过集成CSR可以实现部分动态内容的及时更新。

图片

△框架原生SSG Webpack插件

图片

△图1:活动规则 △图2:攒百元半屏页 △图3:支线攒碎片

2.4 页面静态化(SSR)

在直播歌会抢红包的场景中,节目单页作为用户获取歌曲节目信息的第一入口,其快速加载至关重要。SSR提供快速的节目单页初始加载,后续通过客户端的JavaScript动态增强功能(如进度提醒、节目回放等)获得更丰富的交互体验。

图片

2.5 增量静态渲染(ISR)

在直播歌会抢红包的场景中,对于实时性要求极高的红包抢夺场景,ISR的动态更新和实时交互特性为活动的各个环节提供了实时回显的用户体验:

  • 首先,在全屏红包弹窗页(如初始红包、任务红包和裂变红包)中,ISR使得页面无需刷新即可实时更新用户的红包状态。当用户参与活动或完成任务时,ISR的快速响应确保用户能即时获得任务状态和奖励领取情况,增强了用户的参与感与互动性。

  • 对于实时轮次切换功能,ISR保障用户迅速在游戏阶段间切换,提升了同页面不同状态的连续性。当活动结束时,系统能够快速通知用户并更新活动状态为下线,避免误导用户继续参与。

  • 在内容分享与社交互动方面,ISR处理高效的页面加载与实时显示,如微信邀请和海报分享,保证用户能快速刷新助力进度。在邀请分享页,主态用户能立即看到受邀好友的参与情况和贡献,进一步增强社交互动体验。

图片

2.6 数据预取(Prefetch Data)

在直播歌会抢红包的场景中,通过NA与H5之间的有效数据预取和缓存衔接,实现了端数据的复用,有效减少与服务器的交互频率,消除了数据加载的等待时间,确保了在直播环境中用户能够高效参与活动:

  • 预取皮肤元素配置,进入直播间后,NA会预取皮肤元素配置,预加载与活动相关的皮肤素材,并将这些信息进行缓存,包括页面主题色和红包动画等。同时,前端JavaScript能够在页面弹出时,通过端能力或全局变量直接获取相关数据,用户不需要等待皮肤配置加载,界面视觉能够立即呈现,从而实现在操作上的流畅体验。

  • 攒百元红包的进度更新,在活动进行中,用户需要实时查看攒百元红包的进度,通过数据预取的方式,能够迅速更新至用户界面。在启动WebView的同时,NA实现数据的并行获取。这意味着在用户点击挂件后,相关的数据请求会立即开始,前端JavaScript则能够在执行时通过端能力直接获取这些已经预取的数据,降低了数据延迟加载等待时间,增强了用户参与活动的效率。

图片

2.7 文档预取(Prefetch HTML)

在互动性较强的直播歌会抢红包的场景中,用户不仅可以观看演出,还能参与抢红包活动。为提供最佳的用户体验,确保用户在首次点击不同功能时能够快速上屏相关内容,采用文档预取能力在后台主动下载歌会相关HTML内容,如攒百元半屏页、节目单页等。当用户最终点击某个链接时,直接从内存中读取HTML文档内容,无需网络请求,从而显著提高页面加载速度,确保用户在直播间里的互动预期。

通过数据结果来看,文档预取的效果非常显著。在优化了节目单页的性能后,Android用户的首屏加载时间从3秒级减少到500毫秒级,iOS用户的首屏加载时间从2.5秒级减少到500毫秒级。这样的性能提升显然改善了用户体验,使得用户能够快速获取所需信息,进而积极参与到活动中,营造出活跃的直播间氛围。

图片

△Prefetch SSR/CSR HTML

2.8 资源预取(Prefetch Resource)

在直播歌会的场景中,用户参与抢红包是一个关键的互动环节。在此过程中,确保红包弹出的多层动画和红包图能够迅速、完整地展示对于增强用户体验至关重要。为此,资源预取在这一场景中得到了有效应用,在正式直播活动开始前期,后台服务主动下载、缓存、更新关键资源,包括红包的动画文件和高质量的红包皮图像。以确保当红包正式弹出时,最新的文件已被准备妥当,用户能够立即看到完整的红包图和流畅的动画效果,避免了图片逐块加载造成的卡顿和不完整展示。

通过数据结果来看,资源预取的效果非常显著。Android用户资源加载耗时提升幅度约46.7%,iOS用户资源加载耗时提升幅度达86.1%,大幅提升了整体互动体验,使用户在关键时刻享受到快速且流畅的操作体验。

为了确保资源预取的有效性,需要注意以下几点:

  • 预取的资源应以用户行为的合理预测为基础,避免过度预取,从而造成带宽浪费。

  • 采用分模块的离线包设计,将每个模块的资源单独管理。

  • 在活动结束后,应及时下线不再需要的资源,释放带宽和用户手机空间。

图片

2.9 视图预渲染(Prerender WebView)

在直播歌会的场景中,观众们期待快速响应的抢红包互动体验,此时视图预渲染能力发挥了重要作用。当用户进入直播间后,提前在后台加载并渲染抢红包页面内容,并注册页面可见性监听器。即使用户专注于观看直播,红包页面也已准备妥当。用户点击按钮,抢红包页面便迅速显示,无需等待加载和渲染时间,同时触发监听器实时更新数据。这样的即时反馈使得用户几乎可以瞬间查看抢红包的结果,极大提升了参与的积极性和体验感,进一步增强了直播的互动乐趣。

在预渲染过程中,仅对用户频繁访问的页面进行预渲染,避免资源浪费,确保当前视图性能不受影响。由于预渲染占用内存资源,因此需要控制WebView的数量,防止内存泄漏。在实施时应关注内存管理、时机选择、兼容性和安全性,以灵活适应具体应用场景。

图片

图片

03 稳定性

3.1 弹窗稳定性

保障直播间红包弹层的进退场稳定性,防止透明弹层卡住以致用户无法互动,是一项关键挑战。在直播间中,红包弹层通过覆盖全屏透明WebView实现,且与动画控制密切相关,用户在拆红包动画播放过程中无法进行任何交互,关闭按钮在动画结束后才会显示。这要求我们确保红包动画的持续时间和效果稳定,以便在合适的时机正确显示关闭按钮。为确保红包弹窗正常退出,尤其是在H5页面渲染异常或网络不稳定的情况下,用户也能得到一个状态友好的反馈。保障直播间抢红包互动的稳定性,我们设计了“一次握手”和“双重兜底”策略:

  • 一次握手,即Web内容健康检查:

  • JavaScript通过WebContentLoaded端能力,表示H5成功接管用户交互,并通知Native端取消WebView的超时销毁策略,以确保全屏红包弹窗能够稳定展示。

  • 如果H5接管未在规定时间内完成,Native端将销毁上层全屏透明的WebView。这一措施确保用户不会因弹窗问题而中断观看体验,从而能够持续与直播间进行交互。

  • 双重兜底,即NA兜底页和H5兜底页:

  • NA兜底页,当HTML入口文件请求异常时,展示Native兜底页面,确保用户有可见的替代内容。

  • H5兜底页,在JS业务组件发生异常(例如接口请求异常、端能力调用失败、组件内部异常、重要资源缺失)时,展示H5兜底内容,为用户提供实质反馈。

图片

图片   

△图1:NA兜底页 △图2:H5__兜底页 △图3:请求______兜底

3.2 动效降级

炫酷的动效的表现直接影响用户的体验,在直播歌会场景中,红包动效由复杂的元素组成,包括Lottie动画、AFX透明视频和CSS动画。炫酷的动效虽然可以增强视觉吸引力,但在低端手机上可能导致卡顿。为确保所有用户可以顺畅参与活动,我们实施了分级动效降级策略:

  • 高性能设备(High):在高性能设备上,展示完整的动画和丰富的动态效果,享受到丰富的视觉效果。

  • 低性能设备(Low):对于低端手机,复杂的动效将被简化为静态图像或低复杂度的CSS动画。例如,红包拆开时只展示基本的静态图形,替代激烈的动态效果,确保用户能够正常阅读红包金额,而不至于因动效卡顿而影响体验。

分级动效降级策略能够根据当前手机的实时性能情况,在用户点击拆红包时动态调整展示的动效级别,确保以最佳效果参与活。这种适应性有效地解决了不同设备用户在参与红包活动时可能遇到的性能问题,从而提升整体用户体验的品质。

图片

3.3 组件响应

随着用户体验的不断优化,直播歌会抢红包活动中页面组件的运行环境日益复杂。特别是在复杂组件的开发中,组件开发者必须意识到各项适配工作的必要性,以确保用户体验与开发体验之间的平衡。为了有效满足用户需求并提升开发效率,我们需要综合考虑多个环境及其不同状态。至此,在一个组件的设计和实现过程中,需要针对以下五种状态进行响应和适配:

  1. SSG环境(编译线环境):组件在编译过程中,通过Node.js将公共的信息(如活动规则)提前生成静态内容,以提供快速响应时间。

  2. SSR环境(服务端环境):组件服务器集群上,通过Node.js根据用户请求动态生成相应的内容(如歌会节目单),减去客户端JavaScript加载执行时间,加快页面首屏展示速度。

  3. ISR环境(客户端环境):组件在浏览器中,通过JavaScript进行动态渲染、响应用户点击、滑动等操作,通过异步接口获取最新数据(如红包金额、助力信息)并即时更新界面,保证用户体验的实时性和互动性。

  4. 页面可见(Visibility):组件在浏览器中,通过JavaScript控制组件的渲染时机,仅在内容需要展示时才进行渲染(如播放红包动画),减少不必要的DOM操作,提升性能并降低资源消耗。

  5. 动效级别(High / Low):组件在浏览器中,通过Native端能力获取用户设备的性能,动态调整组件中的动效,在高性能设备上展示更炫酷的动效,在低性能设备上则展示更简单的动效,确保流畅体验。

04 总结

  • 沉淀直播框架能力:通过优化直播间视图容器组件,并形成标准化的组合能力样板,拉升直播间活动页面的性能水准,这些方案具备良好复用性,适用于未来各种直播活动。

  • 系统稳定性保障:组件复用、性能监控和容错机制,减少重复开发和维护成本,进行压力测试与优化,提升系统可靠性和用户体验,确保高峰流量下的稳定性。

  • 强化互动性体验:在直播歌会中建立综合能力框架,特别是在抢红包等互动性强的活动中,确保用户在享受歌会演出的同时体验流畅的互动,鼓励积极参与

————END————

推荐阅读

百度网盘防雪崩架构实践

如何在百度百舸部署满血版DeepSeek-V3、DeepSeek-R1模型

首日调用客户破1.5万!DeepSeek-V3/R1上线背后的超低推理成本技术揭秘

唤醒 AI 算力,专有云 ABC Stack 面向企业级智算平台的 GPU 提效实践

百度APP iOS端磁盘优化实践(上)

百度网盘防雪崩架构实践

作者 百度Geek说
2025年2月18日 10:33
导读 大模型在研发效能领域代码生成方面发挥了越来越大的作用 而大模型的预训练依赖大量的精标代码,这些精标数据必须是比较好的工程实践代码 这些比较好的工程实践代码,需要大量的技术沉淀,包括工程架构,代码

如何在百度百舸部署满血版DeepSeek-V3、DeepSeek-R1模型

作者 百度Geek说
2025年2月13日 11:09

百度百舸·AI异构计算平台已支持快速部署DeepSeek-V3、DeepSeek-R1及其蒸馏的Llama、Qwen等小规模dense模型。您可以登录百度百舸平台快速部署DeepSeek系列模型体验模型效果。

01 开通轻量计算实例

开通一台H20(ebc.lgn7t.c208m2048.8h20.4d)规格的计算实例并添加到百度百舸·AI异构计算平台。

图片

02 部署vLLM

在百度百舸平台的左侧导航中选择「工具市场」页面,部署工具vLLM。

图片

03 模型推理

vLLM部署成功,登录实例下载模型并启动vLLM服务,安装WebUl客户端。

图片

发送请求开始对话。

图片

04 各系列模型的推荐配置清单

图片

在完成满血版DeepSeek模型的快速部署后,百度百舸·AI异构计算平台还能为这些在线服务提供全生命周期管理、自研框架推理加速、推理资源碎片整理等能力。在保障服务稳定性的同时,有效降低推理成本并提升推理性能。

访问百度百舸页面cloud.baidu.com/product/aih…

————END————

推荐阅读

首日调用客户破1.5万!DeepSeek-V3/R1上线背后的超低推理成本技术揭秘

唤醒 AI 算力,专有云 ABC Stack 面向企业级智算平台的 GPU 提效实践

百度APP iOS端磁盘优化实践(上)

对话AI原生|比帮你写代码更爽的是:让Agent来打工

0 Token 间间隔 100% GPU 利用率,百度百舸 AIAK 大模型推理引擎极限优化 TPS

首日调用客户破1.5万!DeepSeek-V3/R1上线背后的超低推理成本技术揭秘

作者 百度Geek说
2025年2月11日 10:18
2月3日,百度智能云千帆大模型平台正式上线了DeepSeek-R1与DeepSeek-V3模型,模型上线首日,已有超1.5万家客户通过千帆平台进行模型调用。百度智能云针对此次模型上线提供了行业领先的超

唤醒 AI 算力,专有云 ABC Stack 面向企业级智算平台的 GPU 提效实践

作者 百度Geek说
2025年2月6日 10:30

从「建好」到「用好」,企业级智算平台借助专有云 ABC Stack 的 GPU 提效服务,应对大模型业务挑战,唤醒 AI 算力,加速 AI 原生业务的落地。

01 难以一步到位的GPU效能

当企业的私有化智算平台项目上线一段时间后,用户普遍会反馈 GPU 效能相关的问题:

  • 将全部资源分配给各个业务部门后,集群全部 GPU 资源的平均利用率在 30% 左右。这个指标处于什么水平,是否充分发挥 GPU 效能?

  • 大模型训练的时候,我们会请技术专家排查 GPU 集群故障问题,有时居然要 2~3 个小时才能搞定,这个时间这么长,还能缩短吗?

  • 新平台按照最高的硬件进行配置,但是常有业务线反馈,相比过去的老集群,智算平台上的任务速度并没有明显提升,这是为什么呢?

那么,企业遇上这些问题的原因是什么呢, GPU 效能可以一步到位吗?

先说结论。根据对不同的企业级智算平台类项目实践的总结:在平台落地后就处于 GPU 最佳效能的状态,这是几乎不可能的。

这些问题的出现和解决,正好体现了企业级智算平台和客户大模型业务落地磨合的过程。

这些问题的原因,有一部分来自于智算平台从无到有,再到大规模 AI 业务落地过程中,智算平台管理部门在不同阶段,关注的目标和业务运行环境的变化所致:

  • 在 POC 阶段,通常是用若干个典型任务做功能、性能和稳定性测试。这些测试可以提前规划,可控性更大。整个过程关注的是平台自身能力的评估。

  • 在大规模生产落地阶段平台开始承载所有部门的业务,需要考量的维度更加复杂,比如资源如何分配满足不同业务需要,平台如何正确使用确保业务能够高效运行等。

另外一部分原因,可能占更大比例,则是因为企业级客户,在过去已经习惯「小模型」和「老平台」后,面对「新平台」运行「大模型」中,需要有一段学习和适应的时间。

02 从资源管理到任务设置,唤醒AI算力

基于百度在大规模集群的技术积累和工程实践,在向企业交付智算平台后,专有云 ABC Stack 还为客户提供了一套面向整体 GPU 算力平均利用率、训推任务加速和稳定性等场景的 GPU 提效服务。

2.1 调整资源分配策略,提升GPU平均利用率

每个业务部门都期望能够获得/储备更多的 GPU 资源加速自己的 AI 任务速度,也可以免去申请更多资源的时间。不过,智算平台管理部门的目标稍有不同,会更多聚焦于在全局资源有限的情况下,能够实时按需分配资源,覆盖全部业务,使得资源利用效率最大化。

为了在「各个部门的业务效率」和「集团整体资源利用率」之间达到平衡,智算平台管理部门需要深入分析不同部门的业务模型,统计过往的任务类型和 GPU 资源使用量等情况,以便找到合适的资源分配策略。

比如,将过去统一把全部资源分发给业务部门的模式,变成把其中一部分资源作为保底资源分发给业务部门,剩余资源作为所有部门的共享按需调度的模式。其中,本周期内各个部门的保底资源额度,可以按照「上一个周期的统计数据」进行预测,适当进行缩减或者扩大。当通过监控数据发现资源总量不足时,及时进行扩容。

2.1.1 实践

传统车企 A 的自动驾驶平台,将智算平台的全部 GPU 资源固定划分给车辆视觉、雷达感知、数据处理、BEV 等 9 类业务。全部业务上线运行 2 个月后,整体 GPU 平均利用率在 30% 附近波动。

为探索 GPU 利用率是否有提升空间,车企 A 联合专有云 ABC Stack 共同对各个业务在过去 2 个月的使用情况进行了详尽的调研,发现全部的节点中:

  • 20% 节点的 GPU 利用率长期不足 1%,这说明这些 GPU 资源几乎被浪费了;

  • 20% 节点的 GPU 利用率较高,且多次超过 80%。这说明在未来这些资源有一定的超负荷风险;

  • 另有 30% 的节点的 GPU 利用率大幅波动。这说明这些 GPU 存在一定的弹性调度空间。

因此,智算平台管理部门将预设的「整体资源按业务部门固定分配」管理方式,调整为「整体资源按调度方式灵活分配:保底 + 共享」的管理方式。

针对各个业务设置保底 GPU 资源,然后将未划分的 GPU 算力纳入集团公共资源池中,供各个业务方按需调用。同时,为了能够更好地管理资源,适应业务变化,车企 A 成立了 GPU 资源管理专委会,每两周对资源使用情况进行汇总分析,动态调整保底 GPU 资源,监控整体 GPU 资源水位。

通过以上资源管理措施的调整,车企 A 的 GPU  整体平均利用率从 30% 提升到了 45%。

2.2 系统性建设容错&稳定性能力,提升GPU有效训练时长

在小规模 GPU 场景下,通常只需要关注硬件异常引发的任务中断,快速替换故障节点并重新拉起训练任务进行故障恢复,就能解决大部分的问题。在千卡的大模型场景中,有很多问题并不会直接反映出硬件异常,例如训练进程 hang 死、训练降速、loss 跑飞(loss 值为 NaN/Inf)等等,这类问题可能跟用户代码、训练框架、硬件故障、操作系统、网络、存储都有关系。

此时,仅仅依赖专家经验人工处理故障,时长和结果都将是一件不可控的事情。

**我们需要更系统的方法,来实现感知异常、诊断定位及故障恢复。通过对训练进程、节点状态、网络流量和计算负载等多维度数据的监控与分析,快速识别异常行为,然后进行自动恢复,最终生成详细的故障报告,**缩短「感知–定位–重启–恢复」整个流程时间,提升有效训练时长。

2.2.1 实践

互联网企业 Z 经历了从小模型升级到大模型业务的转变。在小模型场景已经积累了足够的专家经验处理各类故障问题。在切换至大模型场景后,没有第一时间进行平台稳定性的建设,在故障感知、定位和恢复中投入了大量的人力成本,造成了资源的严重浪费。

借助百度百舸平台的稳定性&容错能力, 互联网企业 Z 在大模型训练任务中实现了显性故障和隐形故障的及时感知、精准定位、故障隔离和自动恢复,平均故障恢复时间从 3 小时缩短到 20 分钟,任务有效训练时长大幅提升,确保了大规模训练任务的持续稳定运行。

2.3 正确配置系统参数,释放GPU性能加速训练任务

大模型训练任务的效率,不仅仅和集群中 GPU 的性能和数量相关,还需要将计算、网络、存储各类资源进行合理配置,使得他们能够将任务各个环节进行无缝衔接,充分发挥整个平台的能力。

一个完整的业务流程,从数据采集开始,再到将预处理好的数据送入 GPU 进行训练,经过多轮迭代后,将最终结果写入存储完成训练。整个业务流程步骤非常多,每个环节的提速都能缩短大模型训练任务的时间。

尤其各类面向大规模 GPU 集群的全新高性能组件(并行文件存储 PFS、RDMA、模型训推加速库等)的引入,对于习惯了小模型业务场景,刚接触大模型和 GPU 集群的企业用户来说,如何才能用好这些能力加速模型任务呢?

为了全面地提升任务运行效率,需要对大模型训练过程中的各个环节进行梳理,给出理想的系统配置和关键指标,然后与智算平台的硬件配置和期望指标对比,以便找到潜在的优化点。

2.3.1 实践 1

传统能源企业 H 使用并行文件存储 PFS 提速从对象存储 BOS 到 GPU 的数据加载流程。运行一段时间后,业务部门发现模型训练速度虽有提升,但似乎离预期还有不小距离。

专有云 ABC Stack 对客户的整个数据流转过程和相应配置参数进行了梳理和分析,发现客户将 PFS 的工作模式设置为「仅加载元数据」,即仅将对象存储 BOS 的元数据进行了加速,导致在任务中未能充分发挥 PFS 的性能。

传统能源企业 H 的业务团队在将 PFS 的工作模式从「仅加载元数据」修改为「加载完整数据」后,任务训练速度提升了近 40 倍。

2.3.2 实践 2

互金企业 Y 将多机多卡的 AI 模型训练任务部署至拥有 RDMA 网卡的新 GPU 集群进行训练。但经过一段时间,发现新集群的训练速度,与未配置 RDMA 的老平台相比,并没有预期成比例提升。

专有云 ABC Stack 与客户深入沟通后发现,性能未达预期的原因在于 RDMA 未被正确配置。互金企业 Y 此前主要运行小模型训练任务,并不需要使用 RDMA。所以在大规模 GPU 集群的使用过程中,直接将老平台的使用经验复制过来,没有将 NCCL 中 RDMA 相关的环境变量配置到容器中。

互金企业 Y 在使能了 RDMA 网卡节点后,数据加载性能和 GPU 多卡训练性能明显提升,任务训练效率对比提高约 2 倍。

2.3.3 实践 3

在自动驾驶业务场景中,会经历「模型选型 - 模型训练 - 模型上车」等几个步骤,研发团队需要在不同模型中做实验选出最合适的模型,并完成模型训练,最后部署在量产车上。所以模型训练的速度越快,量产车获得最新 AI 能力的速度就越快,客户的体验就越好。

在与专有云 ABC Stack 的交流中,传统车企 C 了解到百度百舸的模型算法团队针对各类主流的自动驾驶模型都进行了极致优化,相比开源版本性能有大幅度提升,均已在 AI 加速套件 AIAK 中上线。

车企 C 的智算平台升级了最新的 AIAK 加速库,使得工程团队可以从 AIAK 中直接调用经过优化的模型,吞吐量最高提升 400% ,缩短 80% 的训练时间。

03 从「建好」到「用好」,加速 AI 原生业务的落地

当然,不止于上文提到的方法,GPU 效能的提高涉及到方方面面,比如合理划分故障域、为新的 AI 加速芯片开发监控指标、部署合适的任务资源调度策略、编写适用于大模型平台的管理手册等。

从「过去几块 GPU 跑小模型,业务逐步智能化」到「现在 GPU 集群跑大模型,业务全面智能化」业务场景的转变,这给企业的智算平台高效能运行带来了挑战。同时,由于 AI 原生应用、大模型、基础设施平台等相关技术正在快速演进,AI 算力提效将是一个长期存在的课题。

凭借着百度百舸 4.0 在大模型基础设施方向的领先技术,以及在不同企业级智算平台项目中积累的丰富经验,专有云 ABC Stack 将帮助企业成功应对最新的大模型业务挑战,「建好」和「用好」智算平台,加速 AI 原生业务的落地。

————END————

推荐阅读

百度APP iOS端磁盘优化实践(上)

对话AI原生|比帮你写代码更爽的是:让Agent来打工

0 Token 间间隔 100% GPU 利用率,百度百舸 AIAK 大模型推理引擎极限优化 TPS

百度视频搜索架构演进

网页结构建模在低质采集站上的识别应用

0 Token 间间隔 100% GPU 利用率,百度百舸 AIAK 大模型推理引擎极限优化 TPS

作者 百度Geek说
2025年1月16日 10:14

01 什么是大模型推理引擎

大模型推理引擎是生成式语言模型运转的发动机,是接受客户输入 prompt 和生成返回 response 的枢纽,也是拉起异构硬件,将物理电能转换为人类知识的变形金刚。

大模型推理引擎的基本工作模式可以概括为,接收包括输入 prompt 和采样参数的并发请求,分词并且组装成 batch 输入给引擎,调度 GPU 执行前向推理,处理计算结果并转为词元返回给用户。

  • 和人类大脑处理语言的机制类似,大模型首先会把输入的 prompt 进行统一理解,形成具有记忆能力的上下文。这个阶段通常称为 Prefill 阶段。

  • 在结束 Prefill 阶段之后,大模型引擎会根据生成的上下文不停地推断下一个可能出现的词语,如此往复循环,直到遇到停止符或者满足采样参数中的停止条件。这是一个自回归过程,通常称为 Decoder 阶段。

由于 Prefill 阶段和 Decoder 阶段所完成的任务不同,通常来讲,会从用户视角出发使用 SLO(Service Level Object): TTFT(Time To First Token)和TPOT(Time Per Output Token)去评测引擎。

  • TTFT 就是首 token 延迟,用于衡量 Prefill 阶段的性能。也就是用户发出请求之后,收到第一个词元返回的间隔,也就是系统的反应时间。对于客户来说,这个指标越低越好。

  • TPOT 就是出字间隔,用于衡量 Decoder 阶段的性能。也就是每生成两个词元之间的间隔。通常需要比人眼阅读文字的速度要快,这个指标同样也是越低越好。

当然,只用这些 SLO 并不能完全评测推理引擎对资源的使用状态,所以,和其他使用异构资源的系统一样,会使用吞吐来评测引擎对资源的使用效率,常用的指标就是极限出字率。

极限出字率 TPS(Tokens Per Second )就是系统在满载的情况下,使用所有可用的资源在 1s 内可以生成的词元的最大数量。这个指标越高,代表硬件的效率越高,可以支持的用户规模就越多。

目前市面上流行的推理引擎有很多,比如说 vLLM、SGLang、LMDeploy、TRT-LLM 等。其中 vLLM 是业界第一个完美解决了大模型不定长特性来各种问题的推理引擎,也是市面上使用最多,社区最活跃的推理引擎。

vLLM 的原创性高效显存管理、高吞吐、极易用、易拓展、模式众多、新特性支持快,社区活跃等特性是其受欢迎的原因。但是,vLLM 对复杂调度逻辑的处理没有做到极致,引入了大量的 CPU 操作,拉长了 TPOT。TPOT 的拉长会降低用户体验,降低了出字率,造成了 GPU 资源浪费。

02 影响 TPOT 的罪魁祸首 —— Token 间间隔

区别于小模型推理以 batch 为最小推理单位,大模型推理的最小单位是 step。这也是由大模型推理中自回归的特点所决定的。

每一次 step 会给 batch 内部的每个请求生成一个词元,如果有请求生成了结束符,那么这个请求将会提前结束,并且从下个 step 的 batch 中剔除,空余出来的资源将会被引擎动态的分配给其余正在排队的请求。用户可以感知到的观测指标 TPOT,就是每次 step 的执行时间。

每个 step 的执行逻辑可以简单的概括为一下两部分:前向推理和 Token 间间隔。

  • 前向推理是调用 GPU 计算资源对 Transfomer 结构进行运算的过程,是一个典型的 GPU 密集计算型任务。

  • Token 间间隔,则负责做词元拼接、结束检测、用户响应、请求调度、输入准备等工作,是典型的 CPU 逻辑密集型任务。

优化推理引擎的终极目标其实就是,极限提升前向推理的吞吐,同时极限压缩 Token 间间隔,最终提高极限出字率。****

然而,vLLM 的实现中,这两者天然存在着矛盾。极限提升前向推理的吞吐,(即充分发挥 GPU 算力)要求在适当范围内尽可能增加 batch 内的请求数。然而更多的请求数却拉长了 Token 间间隔,这样不仅会使 TPOT 拉长,还会导致 GPU 断流,出现空闲。在最差的情况下(比如 batch 为 256),Token 间间隔和前向推理时间几乎相同,GPU 的利用率只有 50%-60%。

为了提升极限出字率,同时确保高 GPU 利用率,优化 Token 间间隔成为了提升推理速度的关键。

03 百度百舸 AIAK 优化 Token 间间隔的方案

百度百舸的 AI 加速套件 AIAK 基于 vLLM ,在优化 TPOT 持续发力,并且始终保持着对社区在同周期的技术领先。

3.1 标解决方案1:多进程架构

这个方案的目标是尽可能缩短 Token 间间隔,将 detokenizer 所耗费的时间从 TPOT 中拿去。

我们发现在处理输入请求和生成返回的过程中,tokenize/detokenize 过程(token id 和字符串的转换)是完全可以独立于 GPU 推理运算的逻辑操作。

所以,我们借助 NVIDIA Triton 框架,将 tokenize/detokenize 的过程从推理流程中抽象出来作为单独的 Triton 模型部署,借助 Triton 的 ensemble 机制,把串行过程转变为 3 阶段( 3 进程)流水,实现了 tokenize/detokenize 和 GPU 推理 overlap,有效缩短了 Token 间隔时间。尽管这个优化只把 Token 间间隔中一部分 CPU 操作消除了,但是依然有将近 10% 的收益。

图片

3.2 解决方案 2:静态Slot方案

这个方案主要改造了 vLLM 的调度逻辑,全方位优化了词元拼接、结束检测、用户响应、请求调度、输入准备,提高了各个模块的并行效率,实现了对上一个方案中的「剩余部分」耗时的压缩**。**

我们发现 vLLM 的调度逻辑是面向全局视角的。也就是说每个 step 的调度都会从全局中进行重新筛选,相当于当前 step 结束之后,调度器会把当前 batch 中的句子「放回」全局请求池子中,然后在下一个 step 开始前,从这个全局池子中「取回」适当请求进行运算,这一放一取引入了额外的 overhead。

为了实现全局调度,vLLM 在词元拼接等其他环节引入了大量的 for 循环去串行的处理每个请求,由于这些操作都在发生在 CPU 上,导致在输入打包过程中,必须要引入耗时较长的 host to device 操作。

事实上,step 之间的很多信息是可以复用的(每次放回去的请求和取回来的请求很大一部分是重复的)。也正是基于这个洞见,百度百舸的 AIAK 把 GPU 每次可以迭代的 batch 当成一批固定的 slot,一旦某个请求被调度到某个 slot 后,在完成请求所有推理迭代之前,都不会被唤出,也正是有了这些固定 slot 抽象,AIAK 实现了:

  • 将全局调度改造为局部调度。也就是在下一个 step 调度时,最大程度复用上一个 step 的信息,避免全局搜索,只做增量调度。

  • 串行转并行。也正是有了 slot 的引入,词元拼接、结束检测等这些原本串行的操作可以用 CUDA Kernel 做并发处理,耗时从 ms 级别降低到 us 级别。

  • 避开 host to device 操作。输入打包的工作得以复用前序的显存,有效避开了 host to device 操作。

图片

3.3 方案 3:异步化执行

多进程架构将逻辑上容易独立的部分解耦到其他进程做流水并行,静态 Slot 方案则直面 token 间耗时问题,优化调度模式压榨各个环节的耗时。有了这两个方案,Token 间间隔已经从 35ms 降低到 14ms,GPU 的利用率已经从 50% 提升到了 75%,但是距离 100% 的 GPU 利用率和零耗时 Token 间间隔的目标还有不少距离。

百度百舸 AIAK 通过异步调度模式,将前一个方案中的「剩余部分」全部取出,最终实现了上述极限目标。

简单来讲,就是将 CPU 操作密集的 Token 间间隔和 GPU 计算密集的前向推理完全分开到两条流水线上做二级流水并行。

  • 从逻辑上来讲,核心调度逻辑摆脱了对前向推理的同步依赖,实现异步化调度。

  • 从效果上来说,GPU 避免 token 间同步导致的断流问题,处于一直繁忙状态,实现了推理过程中 100% 利用率和 0 Token 间间隔。

为了简化实现,我们将操作相对简单的前向推理当做一个任务放在后台线程中进行运行,主线程则运行核心的复杂的调度逻辑。两个线程通过一个队列进行交互,分别冲当生产者和消费者,通过线程信号量和 GPU 流上的事件进行信号同步,实现二级流互相 overlap。

图片

和其他任何使用 GPU 类似的硬件作为加速器的系统一样,追求 100% 的利用率一直是所有工程师的终极目标。百度百舸的 AI 加速套件 AIAK 在优化 TPOT,同时打满 GPU 利用率这一目标上经历漫长而又艰辛的探索,最终才彻底实现了 0 Token 间间隔和 100% 利用率这一目标。

当然,除去在这个过程中使用的诸多巧妙的优化手段外,百度百舸的 AIAK 还在量化、投机式、服务化、分离式、多芯适配等领域做了大量工作,致力于实现一个适用于全场景、多芯片、高性能的推理引擎,助力用户在「降低推理成本,优化用户体验上」更上一层楼。

————END————

推荐阅读

百度视频搜索架构演进

网页结构建模在低质采集站上的识别应用

海量存储的批量计算框架

网页多模态建模思考

百度垂搜一站式研发平台演进实践

百度视频搜索架构演进

作者 百度Geek说
2025年1月9日 10:26

导读

随着信息技术的迅猛发展,搜索引擎作为人们获取信息的主要途径,其背后的技术架构也在不断演进。本文详细阐述了近年来视频搜索排序框架的重大变革,特别是在大模型技术需求驱动下,如何从传统的多阶段级联框架逐步演变为更加高效、灵活的端到端排序框架。

01 背景

过去近十年,搜索引擎的主流框架为多阶段级联框架,分为召回,粗排,精排几个阶段。在每个阶段中,系统会基于相关性、质量、时效性和点击率等维度独立建模,然后通过模型融合这些信号进行排序和截断,最终产出检索结果。随着以BERT、ERNIE和GPT为代表的预训练大模型技术的逐渐成熟,利用一套端到端框架解决信息检索问题变得越来越可行。同时,用户差异化,多样化,深层次信息需求越来越强烈, 为了满足这些需求,系统的算力需求也在不断增加。在这种技术及需求趋势的引导下,传统视频搜索排序架构如何演变,已经成为视频搜索最重要课题,同时也对排序架构提出了重大的挑战。

02 目标

以大模型技术为主线,打造高性能,扩展灵活的视频搜索排序框架,同时完成存量排序系统的熵减治理,从而来大幅度提升排序系统的系统能力,降级系统长期运营治理成本。

03 问题与挑战

  • 架构功能如何解耦:视频搜索排序架构经历了多年的积累和发展,已经形成了策略、架构和产品逻辑高度耦合的局面。这种耦合导致排序模块承担了过多且复杂的功能,直接影响了研发效率,并频繁引发稳定性问题。此外,模块功能定位模糊,严重制约了新产品和业务的快速落地与迭代。面对这些挑战,我们亟需打破现有的陈旧框架,从更底层进行架构优化,以实现理想的业务和架构收益。

  • 系统效能如何提升: 目前核心排序模块缺少灵活高效的并行计算框架,制约系统资源使用率的提升。与此同时,系统流量低峰时段会存在大量空闲资源,没有得到充分使用,如何充分,高效挖掘这部分空闲资源资源,来满足业务对资源大量需求。

  • 端到端架构如何演进:在端到端大模型技术的引导下,排序策略的复杂性将逐步被模型内部化,现有策略实现可以得到极大的简化。传统多阶段级联排序架构如何演进升级,以适应这种新的排序模式,也是一个需要深入研究和探索的重要课题。

04 整体思路

对上述问题和挑战,我们采取了一系列综合措施来加以解决。首先,为了解决架构耦合与复杂性问题,我们对核心排序模块进行了深度重构,将原本集成在其中的召回处理与摘要计算功能独立出来,从而实现系统分层的合理化。其次,采用支持串行、并行和数据并行的灵活框架,提升视频排序流程的可视化管理和并行计算能力,并基于弹性算力分配控制中心,高效利用系统空闲资源,最大化搜索视频业务收益。最后,在大模型端到端排序模式下,推动多阶段级联框架向单阶段端到端框架转变升级。下面详细介绍以上解决方案的设计思想:

  • 核心排序功能解耦:

  • 视频核心排序模块是在线检索核心模块之一,之前承接排序和部分召回功能。累积了大量的视频独有的策略和业务逻辑,支持了视频搜索业务的不断发展。随着越来越多的策略、架构功能迭代,核心排序模块也越来越臃肿,接手、开发、维护等成本不断攀升。同时也面临例如不支持云原生、整体框架设计老旧、功能耦合严重等问题。

  1. 将排序模块中召回处理阶段独立分拆,整体功能迁移至新的视频召回模块。

  2. 利用图引擎将多Query串行执行升级至Query全并行执行,包含请求构建,Cache读取,结果解析。

  3. 常用架构,策略功能组件化,插件化,易于理解、开发和维护。

图片

△新召回模块

  • 为满足用户差异化,多样化查询需求,每次请求都需要重新进行召回,排序计算,摘要处理等阶段。如果全量穿透系统缓存,会带来巨大的资源,耗时增长,系统成本无法承担,所以需要考虑目前视频搜系统分层设计是否合理,是否需要重新设计。为解决视频个性化带来的资源,速度问题,我们对视频搜索核心排序功能进行重新分层设计:
  1. 核心排序系统结果返回和摘要获取解耦,视频排序系统有能力提供更多量结果集,弥补之前机制能力缺失的短板。

  2. 新增个性化排序模块,优化传输协议,在核心排序模块返回更多结果基础上,同时穿透更多基础排序,供个性化排序使用。

  3. 根据最终个性化排序结果集合,对Top N进行摘要处理计算,最后返回给上游模块。

    图片

△视频个性化排序演进

  • 系统效能提升:

  • 当前的视频搜索排序框架采用单线多策略管理器的串行执行模式。这种单线程串行处理方式在吞吐量和延迟方面表现不佳。此外,框架缺乏灵活的并行化配置能力,依靠人工经验引入各种omp,bthread等并行组件,并且存在历史遗留的冗余计算逻辑,架构组件较为陈旧。为了设计出能实际解决业务需求的现代引擎框架,我们对主流图引擎的特性进行了调研总结:

  1. 驱动方式:排序层当有大量算子,上千维特征时,无论数据驱动,还是人工编排,可读性都很差。这种复杂性不仅增加了理解整个排序层架构的难度,还进一步影响了项目的研发效率。

  2. 并行方式:目前主流job/processer算子并行方式,没有办法很好去支撑算子job内部并行,排序列队list/item-wise并行。排序数据通常含有多list, list内包含成百上千个item数据,这样数据处理模式需要job内部灵活的并行计算方案。

图片

△驱动&并行方式

  • 事实上,我们发现没有一套图引擎能够完全满足排序业务场景的需求。因此,我们提出了一种图框架引擎主张,灵活的支持搜索排序各个场景。
  1. 除了支持serial,paralle模式,常见的job 间的串,并行模式,框架还支持data_parallel模式。召回返回数据通常包含多list队列,list队列间要做排序,list内有成百上千个item,同样需要排序,常见并行模式不能很好解决这种排序需求,所以我们在框架层做了data_paralllel模式设计,让它契我们当前排序模式,支持list+item的混合排序模式,同时能满足各种并行场景使用需求。

  2. 对业务阶段进行清晰的stage,sub_stage抽象,相对传统图引擎算子推导,缺少很好可读的效果,我们做了stage抽象,配置可读性更好,配置即可读,排序全流程可视化管理易读易接手,这也就是我们做编排配置及推导的主要目的。

    图片

△Rankflow框架

  • 我们不仅要提升现有系统的并行计算能力,还优化资源的分配和使用方式,因为搜索系统的输入流量、资源消耗、响应时间等系统状态存在着周期性的波峰-波谷变动,而系统资源已经预先分配好。在波谷期,由于用户输入流量的减少,系统资源不会得到充分利用;而波峰期,随着用户输入流量的增多,系统往往面临着资源紧缺甚至不足的情况。于此同时,搜索系统的业务链路复杂,时常还会遭受某一中间节点的故障甚至是外部流量徒增等稳定性问题。

  • 架构方案:

  • 构建全局视角的弹性算力分配控制中心。

  • 通过对集群各种维度指标的获取、策略分析及周期性执行最适合当前机器负载状态的策略组合参数,实现其核心弹性算力分配决策。

  • 业务应用:

  • 目前支持视频搜索短小视频扩触发,高峰减载,系统异常处置等功能。

图片

△智能弹性算力系统

  • 端到端排序架构升级:

  • 视频核心排序模块主要分为粗排,精排级联两阶段,排序策略是依据这两阶段排序模式进行迭代升级,如粗排阶段完成初步相关性计算用于初步筛选,减少精排阶段系统计算量,精排阶段少量优质结果进行复杂计算。以大模型排序为核心的排序框架打破了原来多阶段级联模式,端到端排序框架需要对计算和数据方案进行重新设计。

  1. 精简精排前调权和挖掘队列策略,优化索引召回和模型计算选送逻辑,粗排和精排阶段统一为粗精排一体化排序阶段。

  2. 由于缺少粗排模型提前初筛作用,端到端模型需要计算数量更多的候选结果集,计算候选集合从原来精排阶段的几十条增加到几百条。

  3. 升级精排模块,利用Rankflow框架,高并发处理候选结果集数量增加带来的耗时问题。

图片

△端到端排序架构

05 总结与展望

视频搜索排序框架通过系统分层优化、Rankflow框架引入及弹性资源复用等架构演进,显著提升了排序系统的性能与灵活性,提高研发效率,降低了长期运营成本。

  • 在大模型技术趋势下,视频搜索系统如何更好提供RAG搜索增强功能。

  • 如何使视频与通搜端到端融合,达到搜索端到端理想态,都是我们后续探索研究的方向。

————END————

推荐阅读

网页结构建模在低质采集站上的识别应用

如何定量分析 Llama 3,大模型系统工程师视角的 Transformer 架构

微服务架构革新:百度Jarvis2.0与云原生技术的力量

技术路线速通!用飞桨让京剧人物照片动起来

无需业务改造,一套数据库满足 OLTP 和 OLAP,GaiaDB 发布并行查询能力

网页结构建模在低质采集站上的识别应用

作者 百度Geek说
2025年1月7日 10:41
导读 百度搜索是全球最大的中文搜索引擎,拥有着几十亿级的流量,作弊团伙通过各种各样的手段妄想从巨大的流量中不劳而获。搜索反作弊团队维护百度搜索生态安全和质量,经过不断探索并利用前沿技术过滤低质作弊网页

海量存储的批量计算框架

作者 百度Geek说
2024年12月31日 10:25

导读

本文介绍了百度针对海量存储数据计算需求研发的HTAP表格存储系统及计算调度架构。项目背景源于原有存储系统难以满足日益增长的OLAP业务需求,因此构建了集OLTP与OLAP于一体的HTAP系统,通过存算分离、Serverless设计等创新点提升IO访问能力和资源利用率。同时,自研的计算与调度系统实现了任务开发的SQL化和数据处理的FaaS化,简化了业务使用成本,提高了开发效率。整体方案在存储成本、IO能力、IO放大率等方面取得显著成果,为海量存储数据的计算提供了高效、灵活的解决方案。

01 项目背景及目标

1.1 项目背景

搜索内容存储团队主要负责各类数据,如网页、图片、网页关系等,的在线存储读写(OLTP)、离线高吞吐计算(OLAP)等工作。

原有架构底层存储系统普通采用百度自研表格存储(Table)来完成数据的读、写、存工作,此存储系统更偏向于OLTP业务场景。随着近几年大数据计算、AI模型训练的演进,对存储系统OLAP业务场景的依赖越来越重,如数据关系分析、全网数据分析、AI样本数据管理筛选。在OLTP存储场景的架构下,支持OLAP存储需求对资源成本、系统吞吐、业务时效带来了巨大挑战。为此我们在百度自研表格存储之外,结合业务实际workflow针对性优化,增加构建了一套符合业务需求的HTAP表格存储系统以及相应的计算框架,共同组成面向海量存储数据的大批量计算架构系统。

1.2 项目目标

  • 提供海量存储数据计算的超高IO访问能力。当前内容存储数据达几十P+,访问频率按照每周一轮估算,平均IO能力需要达到34G/s,峰值IO能力需要达到200G/s。面对如此庞大的IO访问能力,需要从文件系统、存储引擎、分布式存储系统、访问模型等全方位进行深度优化来满足需求;

  • 提供海量存储数据计算的快速开发&部署能力。在提供海量访问能力的同时,也需要为业务提供访问配套的基础设施,来满足业务开发&部署计算任务的需求。

02 现有研发条件和工作基础

搜索内容架构存储团负责各类数据,如网页、图片、网页关系等,的在线存储读写(OLTP)、离线高吞吐计算(OLAP)等工作。面对当前海量存储数据的计算需求有清晰的技术和业务认知,第一视角明确清楚地知道系统瓶颈、技术难点、业务需求。

  • 系统瓶颈——当前存储系统能提供的IO能力与业务计算需求之间的矛盾。随着大数据、机器学习、大语言模型等新技术的兴起,业务对数据的计算访问需求越来越强烈,然而存储系统的IO能力却一直止步不前。为此,迫切需要一款面向数据计算的存储系统;

  • 技术难点——数据表格存储系统的数据访问模型与计算模型之间的矛盾。当前架构底层存储普遍采用百度自研表格存储(Table)来完成数据的读写存工作,此存储系统更偏向于OLTP业务场景。但随着近几年大数据计算、AI模型训练的演进,对存储系统OLAP业务场景的依赖越来越重,如数据关系分析、全网数据分析、AI样本数据管理筛选。在OLTP存储场景的架构下,支持OLAP存储需求对资源成本、系统吞吐、业务时效带来了巨大技术挑战;

  • 业务需求——方便高效快速的任务开发&部署能力的需要。在大量搜索内容OLAP workflow中,从表格存储系统中提取筛选数据只占全部任务的一小部分,大量任务需要对数据进行加工处理得到需要的结果。常规的做法是多任务串联,这样做的缺陷是大量中间临时数据存储开销。为此我们为HTAP表格存储系统构建了一套计算与调度系统。

03 整体方案

3.1 概览

本项目拟研发面向海量存储数据的大批量计算架构,主要分为两大系统,HTAP表格存储系统、计算&调度架构。

3.1.1 HTAP表格存储系统

图片 △图2.3

  • 架构采用业界HTAP主流设计思想,将OLTP和OLAP workflow拆分到两套存储系统中,如F1 Lightning、ByteHTAP,在SDK层根据任务类型分发到不同的存储系统中;

  • OLTP存储系统——Neptune,采用Multi-Raft分布式协议组建存储集群,采用本地磁盘(SSD/HDD等) + 百度分布式文件系统AFS组成存储介质;

  • OLAP存储系统——Saturn,Serverless设计模式,无常驻Server,即用即加载,贴合OLAP workflow的不确定性和间歇性;

  • OLTP与OLAP存储系统间,采用数据文件硬链的方式进行数据同步,全版本替换,成本低、速度快,充分贴合Saturn Serverless设计模式。

如上架构设计图,可将OLTP与OLAP workflow拆分到两套独立的系统中,解决上述提到的存算耦合问题。

  • 解决存储空间放大问题。空间放大主要带来的问题是存储节点成本,Workflow分离的架构将OLAP需要的数据文件采用AFS低成本存储,减少了对存储节点存储空间的压力。

图片 △图2.4

OLAP存储系统的数据写入并没有使用常见的log redo或raft learner模式,最主要还是在保证OLAP存储系统的Serverless特性的同时,又能实时感知到OLTP系统的最新写入结果。

  • 解决存储节点资源冗余问题。拆分后,分布式存储节点将大量重型OLAP workflow转移到OLAP存储——Saturn中,将极大减少存储节点的计算压力。同时,OLAP存储的Serverless设计模式又可贴合workflow的不确定性和间歇性。

图片

△图2.5 Saturn Serverless模型

计算节点可以部署在任意计算集群中,如Map-Reduce、自研计算节点Pioneer等,在SDK中直接初始化存储引擎,从AFS中访问对应分片的数据文件。计算节点可充分利用云原生系统(PaaS)的弹性资源,解决资源常驻冗余问题。

3.1.2 一次开发,多端部署

图片

  • 任务生成。自研KQL数据查询语言。在任务生成阶段将KQL语句解析优化成相关的调度任务,一个Job包含多个Task。

  • 任务调度。

  • 任务调度的计算节点可以是Map-Reduce,也可以是自研计算集群Pioneer,负责不同计算场景。

  • 任务运行容器负责数据依赖部署和运行计算框架。

  • 计算框架采用插件化设计思想,依托KQL语言进行差异化描述。计算框架的最大特点是,可在数据处理节点执行用户自定义FaaS函数。

3.2 详细介绍

3.2.1 HTAP表格存储系统

3.2.1.1 OLTP存储系统——Neptune

图片

Neptune引擎主要支持四类操作:写、删、读、Scan。每一类操作都通过RegionMapper进行映射,对外隔离分区概念。

Neptune存在两类分区:索引分区、数据分区。

  • 索引分区。索引分区用于减少因为数据分区导致Key所在数据分区不明确导致的随机访问IO放大问题,提升随机查性能。

  • 数据分区。Neptune可配置多个数据分区,每个数据分区内包含多个Locality-Group。分区间的数据理论上是互斥的。

Neptune各类操作的流程:

  • 写操作:

  • 根据RowWriter中设置的Region信息找到需要写入的Region的Handle,按照列语义将数据序列化成RawData。

  • 同时根据Region信息生成当前Key的Region索引信息。

  • 将RawData与RegionIndex作为一条操作记录Commit到引擎中,整个操作为原子操作。

  • 删操作:

  • 由于存在Region的概念,删除某个Key是需要明确当前Key所在的分区。目前的做法是查询一遍分区索引获取分区信息,再准确删除对应分区的数据。这样带来一个问题,删除操作会增加一次分区查询操作,我们可以考虑将分区信息全部加载到内存提升性能。

  • 读操作:

  • 读操作类似删除操作,会首先查询分区索引表,如果在分区索引中查询不到则表明当前Key不存在,直接返回NotFound。否则,根据分区索引查询对应的分区即可。

  • Scan操作:

  • Scan时业务可以指定对应的分区以及CF信息,RegionMapper根据这些信息Select出合适的物理存储Handle,然后对这些物理存储进行Scan。

3.2.1.2 OLAP存储系统——Saturn

图片

Saturn主要分三层:文件系统(File-System)、Table(表级别的抽象,非TG的Table)、访问层(SDK),Meta-Server为每一层提供全局Meta信息支持。

  • 文件系统。Saturn既可以支持AFS,也支持本地文件系统,同时后续可以支持其他类型的文件系统。文件系统的类型对于Saturn来说是插件化可插拔的。使用AFS作为文件系统相比于Table在成本层面有巨大优势。

  • Table。一个抽象的Table包含多个Slice,理论上每个Slice间的数据是互斥的,这里引入数据模型的概念。当前支持两种数据模型:哈希序(hash order)、全局序(global order),两种模型与Table完全对等。

  • SDK。SDK目前支持Seek和Scan功能,使用方式跟通用的列存储系统保持一致,SDK直接与文件系统(AFS)连接,对外提供存储Serverless的访问能力。

同时,Table数据的更新和构建包含两种模式:全量构建、增量合并

  • 全量构建。全量构建通过完整Dump Table数据的方式对表中的每个分片进行逐步替换,替换过程中采用多版本机制保证访问的稳定性。

  • 增量合并。增量合并通过控制TG Table做Major Compaction的时机,保证每次获取增量数据前不会发生Major Compaction。增量数据通过Snapshot的形式对外提供所有的操作记录,这些记录保存在Table SST文件中,Saturn把这些SST文件Transform成自身协议的SST,再发起Ingest操作即可。

3.2.1.3 存储引擎优化——数据行分区

数据行分区思想在很多OLAP存储系统中很常见,如当前比较流行的一些数据湖架构,ClickHouse、IceBerg等。在表格存储中,数据行分区的好处是可以极大减少在数据行筛选过程中IO放大率。以下是我们在存储引擎中支持数据行分区的设计思路:

图片

△图2.6

数据行分区的思想在OLTP和OLAP存储引擎中都有使用,OLTP存储引擎以数据行分区构建的数据文件可直接被OLAP存储引擎加载,减少了OLAP存储的数据构建工作。

数据行分区在Write、Read、Scan场景下的处理流程分别为:

  • Write操作。Write时会根据请求中的特殊Region描述,如分区键,找到需要写入的Region-Index和Region上下文,前者保存Key的分区索引信息,后者中保存实际数据,操作记录由WAL中保存。

  • Read操作。Read操作相比通常直接访问数据,需要多进行一次分区索引访问,为减少多一次访问带来的性能折损,我们将分区索引信息全内存化。由于索引数据非常小,因此全内存化是可接受的。

  • Scan操作。Scan操作相比之下没有任何变更,但在Scan特殊分区场景下可大量减少IO放大。因为相比之前的行过滤模式,可直接跳过大量不需要的数据。

在业务存储支持时,合理设置数据行分区,可极大减少数据行筛选过程中的IO放大率。

3.2.1.4 存储引擎优化——增量数据筛选

在实际业务中,有很大一个场景是获取近期(如近几个小时、近一天)有值变化的数据,常规的做法是Scan全量数据,以时间区间作为过滤条件,筛选出符合条件的结果。但如此的筛选逻辑会带来严重的IO放大,因为满足条件的结果只占全量结果的一小部分。为此,我们在引擎层调整优化Compaction时机以及调整筛选流程,减少增量数据筛选过程中需要访问的数据文件集合,降低IO放大,业务提速。

图片

△图2.7 LSMT

3.2.1.5 存储引擎优化——动态列结构

在OLAP存储引擎中,还存在一类访问场景会带来IO放大问题,数据列筛选。在表格存储系统中,一个Key可以包含多个列族(Column Family),一个列族中可以包含任何多个数据字段,这些字段以行结构存储在同一物理存储(Locality Group)中,当筛选特定数据列时,需要进行整行读取,然后过滤出需要的字段,这也将带来IO放大问题。

同时,OLAP workflow的访问不确定性导致存储层无法及时调整数据在物理存储中的结构。为此,我们引入动态列结构的概念,在逻辑层对业务透明,在物理层根据近期OLAP workflow特性及时调整物理结构。

图片

△图2.8

如上图,在逻辑存储中,分为两个LG,根据workflow特性,把业务常用的访问字段在Compaction阶段存放在同一物理存储结构中,反之,这样可以减少字段筛选阶段的IO放大率。

动态列结构只在OLAP存储引擎中生效,我们在原有OLAP存储中引入workflow收集以及compaction任务,将从OLTP存储中同步的数据构建成更适合OLAP场景的存储结构。

3.2.2 计算与调度架构

在本节,我们将介绍在此HTAP表格存储系统基础上,如何设计实现任务计算和调度系统,简化业务使用成本,提升业务效率。

在大量搜索内容OLAP workflow中,从表格存储系统中提取筛选数据只占全部任务的一小部分,大量任务需要对数据进行加工处理得到需要的结果。常规的做法是多任务串联,这样做的缺陷是大量中间临时数据存储开销。

为此我们为HTAP表格存储系统构建了一套计算与调度系统,系统两大特点:任务开发SQL化、数据处理FaaS化。

3.2.2.1 SQL化与FaaS化

我们充分贴合上述存储系统特性,自研了一套数据查询语言——KQL,KQL类似于SQL Server语法。同时,又结合存储系统特性以及计算框架,支持一些特殊语言能力,最主要的是能支持原生FaaS函数定义,当然也支持外部FaaS函数包依赖。

如下是一段KQL语句例子以及说明:

function classify = { #定义一个Python FaaS函数
def classify(cbytes, ids):
    unique_ids=set(ids)
    classify=int.from_bytes(cbytes, byteorder='little', signed=False)
    while classify != 0:
        tmp = classify & 0xFF
        if tmp in unique_ids:
            return True
        classify = classify >> 8
    return False
}

declare ids = [2, 8];
declare ts_end = function@gettimeofday_us();      # 调用Native Function获取时间
declare ts_beg = @ts_end - 24 * 3600 * 1000000;   # 四则运算

select * from my_table region in timeliness       # 利用存储分区特性,从my_table中的timeliness分区获取数据
where timestamp between @ts_beg and @ts_end       # 利用存储增量区间特性,筛选增量数据
    filter by function@classify(@cf0:types, @ids) # 在Filter阶段调用自定义FaaS函数
    convert by json outlet by row;
desc:                                             # 对计算框架进行特殊描述
    --multi_output=true;

3.2.2.2 任务生成与调度

图片

任务生成与调度主要分为三层,任务解析层、任务调度执行层、任务执行容器。

  • 任务解析层。负责将KQL表达式解析成实际的任务执行计划,并保存在任务存储容器中。

  • 任务调度执行层。负责将任务计划分发到任务执行容器,并轮训检测任务状态,执行探活、重试等操作。

  • 任务执行容器。提供两种任务执行容器,Pioneer、EMR。前者为自研任务执行容器,后者为公司Map-Reduce执行平台。

3.3 技术经济指标

通过上述的架构设计以及优化手段,我们在IO能力、访问成本、开发效率等方面取得显著成果。

04 主要创新点

4.1 自研HTAP表格存储系统

结合业务特性以及实际需求,构建符合业务场景的HTAP存储系统。架构采用业界HTAP主流设计思想,将OLTP和OLAP workflow拆分到两套存储系统中,如F1 Lightning、ByteHTAP,在SDK层根据任务类型分发到不同的存储系统中。系统创新点如下:

  • 存算分离架构。解决OLTP存储系统的空间放大问题,将OLAP Workflow从OLTP存储中分离,分离的架构将OLAP需要的数据文件采用AFS低成本存储,减少了对存储节点存储空间的压力。

  • OLAP Serverless设计。分布式存储节点将大量重型OLAP workflow转移到OLAP存储——Saturn中,将极大减少存储节点的计算压力。同时,OLAP存储的Serverless设计模式又可贴合workflow的不确定性和间歇性。计算节点可以部署在任意计算集群中,如Map-Reduce、自研计算节点Pioneer等,在SDK中直接初始化存储引擎,从AFS中访问对应分片的数据文件。计算节点可充分利用云原生系统(PaaS)的弹性资源,解决资源常驻冗余问题。

  • 表格数据行分区。数据行分区思想在很多OLAP存储系统中很常见,如当前比较流行的一些数据湖架构,ClickHouse、IceBerg等。在表格存储中,数据行分区的好处是可以极大减少在数据行筛选过程中IO放大率。

  • 增量数据筛选支持。在实际业务中,有很大一个场景是获取近期(如近几个小时、近一天)有值变化的数据,常规的做法是Scan全量数据,以时间区间作为过滤条件,筛选出符合条件的结果。但如此的筛选逻辑会带来严重的IO放大,因为满足条件的结果只占全量结果的一小部分。为此,我们在引擎层调整优化Compaction时机以及调整筛选流程,减少增量数据筛选过程中需要访问的数据文件集合,降低IO放大,业务提速。

  • 表格数据动态列结构。根据workflow特性,把业务常用的访问字段在Compaction阶段存放在同一物理存储结构中,反之,这样可以减少字段筛选阶段的IO放大率。动态列结构只在OLAP存储引擎中生效,我们在原有OLAP存储中引入workflow收集以及compaction任务,将从OLTP存储中同步的数据构建成更适合OLAP场景的存储结构。

4.2 自研任务生成与调度系统

在大量搜索内容OLAP workflow中,从表格存储系统中提取筛选数据只占全部任务的一小部分,大量任务需要对数据进行加工处理得到需要的结果。常规的做法是多任务串联,这样做的缺陷是大量中间临时数据存储开销。

为此我们为HTAP表格存储系统构建了一套计算与调度系统,系统两大特点:任务开发SQL化、数据处理FaaS化。

  • SQL化。我们充分贴合上述存储系统特性,自研了一套数据查询语言——KQL,KQL类似于SQL Server语法。

  • FaaS化。在SQL化的基础上,同时结合存储系统特性以及计算框架,支持原生FaaS函数定义能力,当然也支持外部FaaS函数包依赖。

————END————

推荐阅读

网页多模态建模思考

百度垂搜一站式研发平台演进实践

初探图谱Embedding用于异常检测(一)

AIAPI - 转向AI原生检索

学校新来了一位AI作文老师:能看、会评、还教改写

❌
❌