0%

题目:Which Agent Causes Task Failures and When? On Automated Failure Attribution of LLM Multi-Agent Systems

来源:ICML 2025

作者:宾夕法尼亚州立大学

摘要

近年来,基于大模型的多智能体系统(Multi-agent System)受到了广泛的关注。Multi-agent system 通常采用一种迭代式开发模式,即在特定场景中执行失败时再进行调整和优化。在这个环节中,如何进行故障归因(Failure Attribution)是提升 Multi-agent system 性能的关键一步。

现有的 Failure Attribution 依旧是一项人力工程,依赖于领域专家经验。先前的研究大多聚焦于提供一个 benchmark,给出多项指标来评估 multi-agent system,从而给予开发人员启发,然而还是没有解决最关键的问题:哪个组件是最需要提升的?文章认为评估不是终点,最终目的是提升 Multi-agent system。所以文章提出了一个全新的研究问题:如何自动化对 LLM multi-agent system 进行故障归因,目标是找出对故障负责的 Agent 和 Step。

文章构建了一个 Who&When benchmark,包含了手工构造和算法构造的两个数据集,记录了来自 127 个multi-agent system 的日志数据,每个样本都标注了对故障负责的 agent,step,以及 reason。基于此 benchmark,文章设计了基于 LLM 的三种故障归因方法,结果显示使用 LLM 进行故障归因仍然具有很大的挑战性,比如在手工构造的 multi-agent system 上识别故障 step 的准确率仅有 8.77%。

问题建模

文章首先将多智能体的推理过程建模为一个马尔可夫决策过程:

\[ M = \langle N, S, A, P, \phi \rangle \]

\(S\) 是一系列可能的状态,\(A\) 是所有可能的动作。每个 agent \(i\in N\) 的动作来源于动作集合 \(A_i \subseteq A\)\(\phi(t)\) 代表在时间 t 激活某个 agent,这个 agent 会采取动作 \(a_t\)\(P(s_{(t+1)}|s_t,a_t,\phi(t))\)是状态转移概率。最终,我们会得到一个 multi-agent system 的执行轨迹 \(\tau=(s_0, a_0, s_1, a_1,...,s_T)\)

执行轨迹的结果表示为:

\[ z(\tau)= \begin{cases} 1,& if\ the\ system\ ultimately\ fails\\ 0,& otherwise \end{cases} \]

\(Z(\tau)=1\)时,系统发生了故障,我们替换时刻 \(t\) 的 agent \(i\) 的动作 \(a_i\) 为正确的动作 \(\hat{a}_t\),这个过程会产生一个全新的执行轨迹:

\[ \tau^{(i,t)} = \mathcal{I}_{(i,t)}(\tau) \]

\(\mathcal{I}_{(i,t)}\) 代表人工干预,如果 \(Z(\tau^{(i,t)})\)=0,表示执行轨迹修改成功了,这个过程可以被定义为:

\[ \triangle_{i,t}(\tau) = \begin{cases} 1,& if\ Z(\tau)=1\ and\ Z(\tau^{(i,t)})=0\\ 0,& otherwise \end{cases} \]

\(\triangle_{i,t}=1\) 时,表示修复成功,则此时的 agent 和 step \((i, t)\) 即为根因 \((i^*, t^*)\),如果同时同时有多个 step 都与故障相关,则选择时刻最早的那个 step。整个过程的目标函数为:

\[ \begin{aligned} C(\tau) = \{(i,t)|\triangle_{i,t}(\tau)=1\},\\ (i^*, t^*)=arg \min\limits_{(i,t)\in C(\tau)} t \end{aligned} \]

目标即找到根因 agent 和 step:\((i^*, t^*)\)

Which&When 数据集

数据集收集了来自 127 个 multi-agent system 的故障日志,每一项包含如下几个部分:

  • Query:用户定义的任务
  • Failure log:multi-agent的执行日志
  • Agentic system information:multi-agent system 的基础信息
  • Annotations:根因 agent,step,以及 reason
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
"is_correct": false,
"question": "This spreadsheet contains a list of clients for a retractable awning company. Each client has ordered a new awning for the back of their house within the last 90 days. The company makes different designs depending on whether the awning is made to block sunrises or sunsets. In this region, houses with odd-numbered street addresses face east, and houses with even-numbered street addresses face west. How many of these clients will be receiving the sunset awning design?",
"question_ID": "4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2",
"level": "2",
"ground_truth": "8",
"history": [
{
"content": "You are given: (1) a task and advises from your manager with a specific plan and (2) a general task.\nCollect information from the general task, follow the suggestions from manager to solve the task.\n\n# General Task\nThis spreadsheet contains a list of clients for a retractable awning company. Each client has ordered a new awning for the back of their house within the last 90 days. The company makes different designs depending on whether the awning is made to block sunrises or sunsets. In this region, houses with odd-numbered street addresses face east, and houses with even-numbered street addresses face west. How many of these clients will be receiving the sunset awning design? Please solve the problem carefully.The attached file path is: ../2023/validation/4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2.xlsx.\n\n# Task and suggestions from manager\n## Task description\nAnalyze the provided spreadsheet to determine how many clients will be receiving the sunset awning design. In this region, houses with odd-numbered street addresses face east, and houses with even-numbered street addresses face west. The sunset awning design is for houses facing west.\n\n## Plan for solving the task\n1. Load the provided Excel file and read the client data.\n2. Identify the street addresses of the clients.\n3. Determine which addresses are even-numbered (facing west).\n4. Count the number of clients with even-numbered addresses to find out how many will be receiving the sunset awning design.\n\n## Output format\nThe number of clients receiving the sunset awning design.\n\n## Constraints and conditions for completion\n- The analysis should be based on the street addresses provided in the Excel file.\n- Ensure accurate identification of even-numbered addresses.\n\n## Results and reason from last response\nFile path: ../2023/validation/4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2.xlsx\n\nCode block for correct loading and processing:\n```python\nimport pandas as pd\n\n# Load the Excel file\nfile_path = \"../2023/validation/4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2.xlsx\"\ndata = pd.read_excel(file_path)\n\n# Print the column names to identify the Street Address column\nprint(data.columns)\n\n# Extract the street numbers and determine which ones are even\n# Assuming the column name is 'Street Address'\ndata['Street Number'] = data['Street Address'].str.extract(r'(\\d+)').astype(int)\neven_address_clients = data[data['Street Number'] % 2 == 0]\n\n# Count the number of clients with even-numbered addresses\nnum_even_address_clients = len(even_address_clients)\n\nprint(num_even_address_clients)\n``` ",
"role": "assistant",
"name": "Excel_Expert"
},
{
"content": "exitcode: 0 (execution succeeded)\nCode output: Index(['ID', 'First Name', 'Last Name', 'Street Address', 'City', 'Zip',\n 'Phone', 'Email', 'Carrier Pigeon'],\n dtype='object')\n4\n",
"name": "Computer_terminal",
"role": "user"
},
{
"content": "We have verified that the column name for the street address is 'Street Address'. Following the business logic provided, we need to ensure we correctly identify the number of clients with even-numbered street addresses who will be receiving the sunset awning design.\n\nLet's break down and verify the code step-by-step:\n\n1. Load the provided Excel file and read the client data.\n2. Print column names for verification.\n3. Extract the street numbers from 'Street Address'.\n4. Identify even-numbered addresses.\n5. Count the number of clients with even-numbered addresses.\n\nHere is the correct Python code for the complete verification:\n\n```python\nimport pandas as pd\n\n# Load the Excel file\nfile_path = \"../2023/validation/4d51c4bf-4b0e-4f3d-897b-3f6687a7d9f2.xlsx\"\ndata = pd.read_excel(file_path)\n\n# Extract the street numbers and determine which ones are even\n# Assuming the column name is 'Street Address'\ndata['Street Number'] = data['Street Address'].str.extract(r'(\\d+)').astype(int)\neven_address_clients = data[data['Street Number'] % 2 == 0]\n\n# Count the number of clients with even-numbered addresses\nnum_even_address_clients = len(even_address_clients)\n\n# Output the result\nprint(num_even_address_clients)\n```\n\nLet's run the above script and verify the result.",
"name": "BusinessLogic_Expert",
"role": "user"
},
{
"content": "exitcode: 0 (execution succeeded)\nCode output: 4\n",
"name": "Computer_terminal",
"role": "user"
},
{
"content": "The code has successfully executed and returned the result.\n\n**Verification**:\n- The column name for street addresses ('Street Address') has been correctly identified.\n- Street numbers have been accurately extracted from the 'Street Address' column.\n- Clients with even-numbered street addresses have been identified.\n- The final count of clients with even-numbered street addresses (who will be receiving the sunset awning design) is 4.\n\nThe result has been confirmed to be correct and verifiable based on the provided data and the executed code.\n\n**Output**: \nThe number of clients receiving the sunset awning design is **4**.\n\nIf both of you are satisfied with this result, I can conclude the task.",
"name": "DataVerification_Expert",
"role": "user"
},
{
"content": "TERMINATE",
"name": "DataVerification_Expert",
"role": "user"
}
],
"mistake_agent": "Excel_Expert",
"mistake_step": "0",
"mistake_reason": "The code failed to handle edge cases in the 'Street Address' data, leading to an incomplete and inaccurate count of even-numbered addresses.",
"system_prompt": {
"Excel_Expert": "## Your role\nExcel_Expert: Expert in analyzing and processing data from Excel files.\n\n## Task and skill instructions\n- Task Description: You will be responsible for scrutinizing, interpreting, and managing data from Excel files. The role requires a deep understanding of various functionalities within Excel including but not limited to pivot tables, data visualization, data cleaning, and advanced formulae.\n- Skill Description: You possess exceptional skills in Excel, enabling you to manipulate, analyze, and derive meaningful insights from complex datasets. You have the ability to automate repetitive tasks using VBA (Visual Basic for Applications) and generate comprehensive reports. Your expertise includes proficiency in handling large datasets, performing statistical analysis, and utilizing Excel's extensive array of tools to streamline data processing.\n",
"DataVerification_Expert": "## Your role\nDataVerification_Expert specializes in employing Bing Search API for powerful online search capabilities. With an adept skill set in navigating, parsing, and analyzing search results, this expert seamlessly extracts precise information from an array of text content. A critical portion of their role involves vigilantly verifying the accuracy of this information, ensuring reliable and trustworthy data usage.\n\n## Task and skill instructions\n- You will utilize the Bing Search API to execute specialized search queries, combing through the vast data available on the internet. \n- Your skill in parsing and analyzing search results will be crucial in distilling the vast array of information to the most relevant details. \n- When it comes to extracting specific information from text content, your ability to pinpoint and retrieve exact data points is paramount.\n- An essential component of your work is the verification of the information's accuracy, which necessitates a keen eye for detail and a robust methodology for cross-checking facts.\n\nWith your combination of technical prowess in using Bing Search API and a meticulous approach to data verification, your role as DataVerification_Expert is essential for any operation that demands high accuracy and reliability in information gathering.\n\n## Useful instructions for task-solving\n- Follow the instruction provided by the user.\n- Solve the task step by step if you need to.\n- If a plan is not provided, explain your plan first.\n- If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try.\n- When you find an answer, verify the answer carefully. \n- Include verifiable evidence in your response if possible.\n \n## How to use code?\n- Suggest python code (in a python coding block) or shell script (in a sh coding block) for the Computer_terminal to execute.\n- When using code, you must indicate the script type in the code block.\n- Do not suggest incomplete code which requires users to modify.\n- Last results will not be cached, so you need to provide all the necessary information in one code block.\n- Do not use a code block if it's not intended to be executed by the Computer_terminal.\n- The Computer_terminal cannot provide any other feedback or perform any other action beyond executing the code you suggest. \n- The Computer_terminal can't modify your code.\n- Use 'print' function for the output when relevant. \n- Check the execution result returned by the user.\n- Do not ask users to copy and paste the result.\n- If the result indicates there is an error, fix the error and output the code again. \n- If you want the Computer_terminal to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. ",
"BusinessLogic_Expert": "## Your role\nBusinessLogic_Expert is an expert in understanding and applying business rules to data. This role involves comprehending complex business requirements and translating them into actionable data logic that aligns with organizational goals.\n\n## Task and skill instructions\n- Your task involves analyzing business requirements and interpreting them into specific business rules that can be implemented within a data framework.\n- You must possess a deep understanding of business processes and how they relate to data management and manipulation. The ability to apply these rules accurately to data sets is crucial for ensuring the integrity and efficiency of business operations.\n- Key skills include critical thinking, attention to detail, problem-solving, and the ability to work with various data tools and platforms. You should be adept at both written and verbal communication to effectively convey business logic to stakeholders and team members.\n- Additional knowledge in SQL, database design, and data analysis techniques will further enhance your capability in this role. \n\nBy leveraging your expertise, you will contribute significantly to optimizing business operations through precise and effective data logic application."
}
}

文章定义的指标包括:

  1. Agent-level Accuracy:命中根因 agent 的比例
  2. Step-level Accuracy:命中根因 step 的比例
  3. Step-level Accuracy with Tolerance:命中根因 step 一定范围内的比例

方法

文章提出了三个 baselines 用于解决 multi-agent system 的故障归因:

  1. All-at-once:输入全部的故障日志以及query,直接给出根因 agent 和 step
  2. Step-by-step:给定一个查询,然后逐个输入故障日志,LLM 会判断当前输入的日志是否有故障,如果有,则直接返回这个日志所属的 agent 以及此时的 step;否则,继续输入故障日志
  3. Binary Search:基于二分查找算法,首先输入全部日志,让 LLM 判断故障日志在上半段还是下半段,直到无法划分,算法如下:

评估

文章对数据集进行了进一步的处理,分为了两类:

  • Ground Truth: 进行诊断的 LLM 知道正确轨迹的执行结果,比如用户需要 multi-agent system 计算一个表达式,multi-agent system 发生了 failure,则在debug时告诉诊断LLM 真正的表达式结果是什么
  • Without Ground Truth: LLM 无法知道正确的执行结果

模型采用 GPT-4o

性能实验

从上图中可以得出如下结论:

  • 在 Agent-level Accuracy 上,All-at-once 表现最好,原因在于定位根因 agent 一般需要全局信息
  • 在 Step-level Accuracy 上,Step-by-step 表现最好,原因在于step-level 过于细粒度,All-at-once 在超长的日志中找到根因日志犹如大海捞针
  • Binary Search 方法的性能居于两者之间
  • Ground Truth 对性能有较大帮助

一致性实验

文章将 GPT-4o 换成了几个开源和闭源模型,发现实验结论基本和性能实验一致,说明性能实验的结果在不同模型上是一致的

### 上下文长度的影响

文章也探究了不同日志长度对性能的影响,这个日志长度应该是指一共执行了多少步。如上图所示:

  • 日志长度越长,故障归因性能越差
  • Step-level Accuracy 受日志长度影响最严重
  • Step-by-step 定位 step 的 distance (差距)比较小

不同 Tolerance 下的 Step-level Accuracy

第三个指标是 Step-level Accuracy with Tolerance,这个指标衡量的是定位到根因 step 一定范围内的能力,可以看到:

  • All-at-once 的性能随着 tolerance 的提高有着明显的提升
  • 在 tolerance=0 or 1时,Step-by-step 的准确率最高,说明 Step-by-step 有着较高的 precision

能否组合不同方法

文章尝试组合不同方法,如上表所示,组合后 token 消耗明显增多,但是性能得到了极大的提高。

题目:The Vision of Autonomic Computing: Can LLMs Make It a Reality

来源:arxiv 2024

作者:南京大学,微软

摘要

智能运维的最终梦想是想让微服务系统能够自主诊断和恢复,这篇文章向这个方向努力探出了一步。文章的目标是实现微服务系统的自主计算的愿景(ACV, Vision of Autonomic Computing),大模型的出现让 ACV 的实现出现了可能性。

文章推出了一个基于大模型的分层多智能体架构,用于维护微服务系统的可靠性。其中,高级别群组管理者(high-level group manager)用于接收声明式任务,比如优化延时到200ms以下;低级别自主智能体(low-level autonomic agent)聚焦实施各种具体任务。

为了评估此套自动化系统,文章提出了一种五层分类法,重点关注自优化和自恢复;此外,文章还在 SockShop 中进行了实战演练,通过混沌工程注入故障并观察系统如何自恢复。

背景

自主计算

在智能运维中,自主计算的目标是减少微服务系统维护的复杂性,提升可靠性和性能,之前的 ACV 文章提出了四个自主计算的目标:

  • 自配置:可以配置和重配置系统,以满足目标
  • 自优化:可以持续监控系统,并找到机会优化系统以提升性能和减少开销
  • 自恢复:发生故障时恢复,甚至预测故障
  • 自保护:防御恶意进攻和故障传播

现有的自主计算一般采用 MAPE-K 的架构,即 Monitor,Analyze,Plan,Execute,Knowledge Base。有大量工作基于 rule-based 方法,在特定场景下有用,但是在复杂动态的微服务系统中需要作出自适应和上下文感知的决策,rule-based 方法无法做到,所以越来越多的方法采用 AI 来替代。

云原生应用自主管理显得更为困难,因为大多拥有复杂的系统结构,安全和可靠性也存在高要求。虽然有大量工具来帮助管理,比如 Kubernetes 和 Prometheus,但是这些工具都无法将人类意图直接转化为对应的功能,有着极高的学习和操作成本。

随着 LLM 的快速发展,大量研究尝试将 LLM 集成到 Kubernetes 中参与微服务系统的管理,比如 GenKubeSecK8sGPT ,也有些工作将 LLM 集成到智能运维中,但这些工作都无法实现自管理。

方法

方法概述

文章提出了一种 ACV 架构, 整体上分为2层:

  1. 低级别自主智能体: 用于执行简单的维护代码,充分使用 LLM 的代码生成和执行能力
  2. 高级别群组管理者: 分解复杂任务为多个 sub-tasks,制定 step-by-step 计划,下发 sub-task 到具体的低级别自主智能体,接收反馈并判断有没有完成目标

而对于每个智能体的设计,不同于传统的 MAPE-K 架构,基于 LLM 的 ACV 中每个智能体都是由两个模块组成:PlannerExecutorPlanner 负责制定执行计划(监控、分析),Executor 负责执行具体步骤,并反馈结果给 Planner

三种负载机制

  1. 低级别自主智能体独立工作

    • 简单的任务(扩容副本到3)可以直接下发到一个低级别自主智能体
  2. 高级别群组管理者引导下的多智能体协同

    • 复杂的任务(将延时控制到200ms以下),按照如下顺序:分解任务、制定计划、分配任务到低级智能体、接收反馈并调整计划
  3. 低级别自主智能体的协同(无高级别群组管理者

    • 如果一个低级别智能体无法处理工作时,可能不经过高级别智能体,直接找另一个低级智能体协同

智能体之间的通信方式是消息队列

SockShop 实施案例

## 实验评估

首先文章推出了五层分类法来评估智能体系统的能力:

  1. L1:智能体是否能选择正确的运维操作指令
  2. L2:智能体能否有计划能力,将任务分解为多步执行

L1 和 L2 针对的都是祈使任务(imperative task),直接表达要做某个具体的任务,智能体只是被动执行。更高级的自主智能体应该能够应对声明式任务(declarative task),即主动采取一些动作来完成目标

如下图所示,文章在自主管理微服务系统中用具体案例表示了剩下的 L3、L4 和 L5

为了评估 ACV 的能力,首先是评估 L1 和 L2,这些测试任务中既包括部署阶段的,也包括运行时的

对于 L3,L4 和 L5,文章在 SockShop 中用混沌工程注入故障,观察故障自愈情况,注入故障包括三类:

  • Pod Failure:将 Catalogue 服务的镜像换成一个假的、没有功能的镜像
  • CPU Stress:将 Catalogue 的 POD 的 CPU 打到 100%
  • Rising Traffic:逐步升高负载,直至无法承受

为了评估故障自愈情况,定义了如下 SLO:

  1. 所有的服务需要处于 READY 状态
  2. 所有的服务 CPU 和 内存的使用率需要在 50% 以下
  3. 所有服务的 P99 延时控制在 200ms 以下

实验流程和实验结果如下,每个实验跑三次:

同时文章也给出了一个高级群组智能体规划的复杂任务案例:

题目:TRACEZIP: Efficient Distributed Tracing via Trace Compression

来源:FSE 2025

作者:中山大学

摘要

分布式追踪(distributed tracing)是监控微服务系统内部执行逻辑的重要手段,但同时也给系统带来了巨大的计算和存储的负担。现有的方式是通过采样(sampling)来捕获更少的trace,然而,现有的两种 sampling 方式面临采样质量和系统负载的抉择:

  • head-based sampling:无差别选择某些请求进行追踪,容易遗漏关键数据
  • tail-based sampling:追踪所有请求,存储 edge-case traces,但 trace 收集和传输时的负载仍然很大

文章从另一个角度触发,提出了 TraceZip,通过 trace compression 来减少各阶段的负载。核心思想是 trace 数据结构之间存在显著的冗余,导致微服务和后端之间大量相同数据的重复传输。文章设计了一种新的数据结构 Span Retrival Tree(SRT),能够持续压缩这种冗余,以轻量化的方式传输 traces;在后端,完整的 traces 可以基于先前的通用数据重建。TraceZip 已经在 OpenTelemetry Collector 中实现,能够与现有的 tracing API 兼容

背景

trace 是理解微服务系统行为,诊断微服务故障的重要工具。在生产环境中,捕获所有请求的 traces 会造成显著的负载,这些开销来自于 trace 的生产、收集和存储。为了减少负担,通过会对 trace 进行采样。head-based sampling 在请求执行前随机选择一定比例的请求进行追踪并产生 traces,极大的减少了trace 的生产、收集和存储的开销,但是容易无差别遗漏一些值得关注的 traces,比如异常行为形成的 edge-case traces(e.g., 高延时)。tail-based sampling 追踪所有的 traces,但是会在后端根据某些属性选择哪些 traces 进行保留。由于 traces 的价值具有不可预测性,很难完全覆盖测试和故障诊断所需的所有 traces。同时 tail-based sampling 的计算和传输开销依旧很大

因此,文章提出了一种针对 trace 的压缩方法 TraceZip,在微服务端用一种更简洁的数据结构压缩 trace 中的 span,在后端能够无缝还原为原始的 span,这样能够大幅减少传输中的开销。而传统的日志压缩无法适用于 trace 这种流式数据

动机

文章部署了 Train Ticket 系统,并通过 OpenTelemetry 提供的无侵入方式进行监控,收集了大约 40 GB 的 traces。文章通过每个微服务产生的重复 key-value (KV) 对的比例来量化 trace 的冗余。对于每个 KV 对,冗余比即为它在所有 KV 对出现次数的比例,比如一个 trace 数据集有 100个 KV 对,如果有两个 KV 对分别出现 1 次 和 10 次,则冗余比为 1% 和 10%。

通过实证分析,左图是 Train Ticket 常规微服务,右图是常见的中间件,文章得到两个结论:

  • Trace 是高度冗余的。文章将冗余次数阈值设置为1000,计算>1000和<1000的 KV对在每个服务中的比例(这里有点怪,从冗余比计算变成了冗余次数)。从图2的第一张图可以看出,微服务中大概 70% 的 KV 对是高度冗余的;从图2的第二张图可以看出,除了个别组件(Kafka),因为 kafka 的 traces 包含了消息队列中的数据信息,有随机性。这是因为服务经常参与标准交互,生成的trace具有相似的模式。通过在微服务端捕获 span 生成时的冗余,我们可以提前消除后端已经存在的重复数据的传输。后端可以根据冗余模式轻松重建完整的 span
  • 属性之间存在结构化冗余。OpenTelemetry 为 span 的通用属性(Key)提供了标准的命名规范,用于保持跨语言的一致性,这个命名机制也存在一些冗余,比如 network.local.address, network.local.port, network.peer.address 有一些共同的单词;值(Value)也存在一些冗余,比如 Java Exception has java.io.IIOException, java.io.EOFException 等, 通过移除这些细粒度的冗余,也可以进一步减少 span 传输的负载

方法

TraceZip 架构图如下所示,主要是加了两个模块 compression 和 decompression

  • compression:在微服务端,维护两个数据结构:Span Retrieval Tree (SRT) 和一个字典,持续捕获和压缩 spans 的冗余。如果 span 携带了新的冗余信息,则会无缝集成到上述数据结构中。
  • decompression:在后端 collector 入口处,spans 基于 SRT 和字典进行重建。
  • differential update mechanism:微服务端和后端的 SRT 和字典需要持续同步,这个机制会同步两侧数据结构的增量变化,确保低开销的高效协同

Span 格式

Trace 是树状结构,Span 是树中的节点。Span 本身是一个 JSON 结构的 KV 对集合,键是 String 类型,而值可以是原始数据类型(String, number, boolean, null),也可以是结构化类型(KV字典,array),为了对齐现有框架,一般包含以下字段:

  • name,
  • parentSpanId,
  • Start and End Timestamps,
  • Span Context (the context of the span including the trace ID, the span ID, etc.),
  • Attributes,
  • Span Events

文章在这里有个前提,就是一个操作的 Span 结构不会发生变化,也就是说某个操作生成的 所有 spans 的 Keys 是相同的,只有 Values 会变化

Span 压缩和解压

Span 的直观压缩方式就是使用字典,将每个独特的 KV 对赋予并替换为对应的独特 ID。但这些 KV 对之间还存在细粒度的冗余。除此之外,如果大量 Span 共享同一套 KV 对组合,就可以用一个 ID 来表示这个组合,进一步减少冗余

如图所示,文章提出了一种 SRT 的多叉树来减少冗余,其中有三种节点:

  • root:存储一个基础时间,可以让 leaf 只存时间偏移
  • non-leaf:存有较多重复的 Value,比如 name 和 status 的 Value 只有几种,则把这些 Value 存到 non-leaf 中
  • leaf:只存 Value 可选数量太多的 Key,比如 SpanId,time_offset 之类

比如 Fig.4 的 SRT 实际上就是由上述两个表格演变来的,每个 span 都可以看作从 root 节点到 leaf 节点的一条路径。演变算法如下所示:

  1. 接收一个 span 数据流,并处理每一个span
  2. 如果 span 的 name 不存在于 SRT 中(根节点的子节点,level=1),则将这个 span 的所有 values 串成一条路径,放到 SRT 中。
    • 比如 Table 1-(a) 的 span 可以表示为 Access Mem \(\to\) WRITE \(\to\) address1 \(\to\) 64 bytes \(\to\) id1
  3. 如果 span 的 name 存在于 SRT 中,则从 SRT 中从上到下逐层检验 span 的所有 KV 对是否存在于 SRT 中
    • 如果不存在,则需要将新 Value 作为新分支加进 SRT
    • 比如 READ \(\to\) address2 \(\to\) 128 bytes \(\to\) id2 将作为新分支加入到 Access Mem 下
  4. 一旦有新分支加入(即执行了步骤3),则需要检验 SRT 各层的值个数,如果超过阈值 𝜓,则这一层对应的 Key 将会被移到 leaf
    • 比如 id 那一层有太多值,则移到 leaf
    • 对于某些值是 KV 字典这种复合结构的,则按照如下案例处理:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      {
      “attributes”: {
      “ip”: “172.17.0.1”,
      “port”: 26040
      }
      }
      >------------------------------------------
      {
      “attributes-ip”: “172.17.0.1”,
      “attributes-port”: 26040
      }
  5. 此时只需要 Span 在 SRT 中的路径(可以用路径ID表示)和 leaf 中 key 对应的值就可以还原出 Span 了

SRT 内存的优化

虽然 SRT 已经极大节省了传输成本,但仍存在较大的空间复杂度,因为 SRT 的潜在增长空间太大,所以文章进行了进一步的优化

  1. SRT 重建: Fig.5 是从 Table1.(b) 中建立来的 SRT,可以看到,如果 key 的顺序不当,会造成大量重复。正如上章节算法中第15行,应该在每次 SRT 更新时把 Values 较多的 Key 往下移
  1. 基于字典的压缩:Fig.4 中可以看到还是有一些 Value 重复,比如 64 bytes。文章构建了一个字典,这个字典的键是 spans 的 K/V(只包括 SRT 中的内容,不包括 leaf 中 Key 对应的 Value),值是 ID。ID由[0-9a-zA-Z]组成,先从长度为一开始( ’0’ to ’9,’,’A’ to ’Z.’),然后再往上递增长度。只要字典发生更新,就会传输到后端。

SRT 搜索的优化

文章还优化了 SRT 的搜索部分,因为 SRT 涉及频繁的修改,所以最直观的实现方式是链表。然而,链表中各节点不是存储在连续的内存空间中,所以查询时会导致较多的预读失效,影响路径搜索时的效率。

TraceZip 在微服务端维护了一个 hash 表:{path: identifier},当 span 到来时,就不用遍历 SRT 来查询是否存在路径了,直接通过 hash 表获取路径ID

Differential Data Synchronization

为了 Span 压缩和解压,微服务端和后端必须要时刻保持 SRT 和字典的同步。最直观的方式就是发生更改时就将 SRT 和字典发向后端,但有的时候更改往往是一小段,如果将整个数据都发向后端,就会造成额外的网络开销,甚至会阻塞解压操作。

TraceZip 的后端,也就是 trace 的接收器,不再维护一个与微服务端一致的 SRT,转而维护了一个 hash 表 {identifier: path},这样只需要同步两端的 {path: identifier} 和 {identifier: path},只有路径发生新增或删除时才会同步

  • 对于字典的更新,只要发生了更改就会同步到接收器,这里为了保证接收器的数据不过时,用了 OpenTelemetry Collector 的批处理功能,在压缩缓冲区中的Span后,将确保在发布数据之前 SRT 和字典已经与接收方同步。

实验

比较的指标是压缩中常用的压缩率: \[ CR=\frac{Original File Size}{Compressed File Size} \]

对比方法采用的是常见的压缩算法:gzip, bzip2, lzma.

同时探究了不同压缩算法的吞吐量 (MB/s)

题目:OpenRCA: Can Large Language Models Locate the Root Cause of Software Failures

来源:ICLR 2025

作者:香港中文大学(深圳)

摘要

大模型(LLM)推动了软件工程领域的实质性进步。然而现有的研究大多关注 LLM 在软件开发阶段的作用,比如代码生成,忽视了在开发后阶段(post-development)的工作,而这个阶段往往直接关乎用户的体验。文章推出了 OpenRCA,包含一个benchmark数据集和一个评估框架,用于衡量 LLM 在定位软件故障根因上的能力。

OpenRCA 包含 335 个真实场景的 failure case,这些 failure case 来自 3 个企业系统,并附带有 68 GB 的多模态遥测数据(metric,trace,log)。LLM 需要接收给定的 failure case 和对应的遥测数据,加以推理,然后识别出故障根因。实验表明,即使是表现最好的 LLM,也只能解决 11.34% 的 failure case。

背景

对于在线系统的软件维护和debug是非常困难的,虽然有大量工作在致力于通过多模态遥测数据来定位故障根因(RCA),但是这个任务仍然具有较大的挑战性,因为线上软件系统有着难以估计的复杂性。

LLM 是否能胜任 RCA 工作呢?文章为此提出了一个 benchmark 数据集:OpenRCA, 包含 335 个真实场景的 failure case,这些 failure case 来自 3 个企业系统,并附带有 68 GB 的多模态遥测数据(metric,trace,log)。如上图所示,对于每个 failure case 以及一个对应的自然语言的 query, LLM 需要分析大佬的多模态遥测数据,理解系统之间的内在关联,并推理出可能的故障根因。

OpenRCA

特点

OpenRCA 包含了大佬真实场景的 failure case,有如下特点:

  • 真实场景
  • 目标驱动的任务设计(不再是简单定位一个故障组件,而是通过自然语言表达任务)
  • 多模态异构遥测数据
  • 完整的 LLM 评估
  • 支持新标签和新遥测数据的集成

问题建模

故障根因有三个元素:根因组件(originating component)、开始时间(start time)、故障原因(failure reason)。每个人的目标可能不一样,所以需要对上述元素进行组合得到目标。

OpenRCA 定义了7个目标,是3个元素的组合(\(C_3^1+C_3^2+C_3^3\), LLM 的输出应该是7种目标中的一种

1
2
3
4
5
6
7
8
9
10
11
"task_7": {
"input": [
"time range: {time_period}",
"number of failures: {num}"
],
"output": [
"root cause component: {component}",
"root cause occurrence time: {datetime}",
"root cause reason: {reason}"
]
}

评估:对于每个 failure case,如果输出的内容符合实际,则加一分,否则不得分。这里要避免文本表达差异而导致的评估错误,prompt 中预先提供了所有可能的故障原因和原始组件。最终计算 \(accuracy\) ,是所有分数的均值。

1
2
3
4
5
6
7
8
{
"1": {
"root cause occurrence datetime": (A time in ’%Y-%m-%d %H:%M:%S’ format),
"root cause component": (A component selected from the given’ possible root cause component’),
"root cause reason": (A reason selected from the given’ possible root cause reason’),
},
...
}

数据集构成

OpenRCA 使用的数据集均来自历年的 AIOps 大赛,由于脏数据较多、某些故障标签缺乏(failure reason)等问题,首先采用四步处理方式预处理:

  1. System Selection: 历年数据集中有些系统因为数据和标签不完备被淘汰,选用满足要求的3个系统
  2. Data Balancing: 系统之间数据规模差距太大,对大数据量系统进行下采样
  3. Data Calibration: 规范命名以及人工筛选 failure case
  4. Query Synthesis: 3个元素组合而成7个目标

最终数据集组成如下:

RCA-Agent

为了解决 OpenRCA 中的任务,首先要面对两个挑战

第一个挑战是如何处理超大规模的遥测数据

直观的解决方法是将所有遥测数据整合为一个 chunk,但是低效且开销巨大的;另一个方法是采样一个遥测数据的子集,但是有丢失关键信息的风险

第二个挑战是遥测数据不是自然语言, LLM 可能无法有效处理:

可选的处理方法是先对所有遥测数据进行代码处理,然后提取关键信息到 LLM

针对上述挑战,文章也提出了一个多智能体的解决方案(RCA-Agent),RCA-Agent 包含两个 LLM 智能体(Controller 和 Executor)

  • Controller:负责整个流程的调度,指导 LLM 按照 anomaly detection -> fault identification -> root cause localization 进行诊断;指导 LLM 按照 metric -> trace -> log 的顺序进行分析

  • Executor:在 Controller 的指导下写 Python 代码、执行代码、返回结果给 Controller。由两部分组成

    • code generator: 生成 Python 代码
    • code executor: 有个 Python kernel 负责执行

RCA-Agent 工作流:

  1. Controller 指示 Executor 加载遥测数据(Executor 自己生成并执行代码)
  2. Executor 返回结果给 Controller
  3. Controller 分析决策并决定下一个动作
  4. Controller 和 Executor 不断交互直到最终结果给出

实验

之前提到不可能把所有的遥测数据都输入到 LLM,所以只能用采样来减轻负担,比如只取用每分钟的第一条记录,此外,进一步引入了两个 KPIs 采样方式作为 RCA-Agent 的对比:

  • Oracle Sampling:工程师选出最有价值的 KPIs
  • Balanced Sampling:随机采样与 Oracle Sampling 同等数量的 KPIs

文章比较了现有的开源模型在 OpenRCA 上的表现

Prompt

这里附上 RCA-Agent 细节和主要 Prompt

Controller System Prompt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
You are the Administrator of a DevOps Assistant system for failure
diagnosis. To solve each given issue, you should iteratively instruct
an Executor to write and execute Python code for data analysis on
telemetry files of target system. By analyzing the execution results,
you should approximate the answer step-by-step.

There is some domain knowledge for you:
{BACKGROUND KNOWLEDGE OF SYSTEM}

## RULES OF FAILURE DIAGNOSIS:

What you SHOULD do:
1. **Follow the workflow of ‘preprocess -> anomaly detection -> fault
identification -> root cause localization‘ for failure diagnosis.**
...
What you SHOULD NOT do:
1. DO NOT include any programming language in your response.
...
The issue you are going to solve is:

{PROBLEM TO SOLVE}
Solve the issue step-by-step. In each step, your response should follow
the JSON format below:

{
"analysis": (Your analysis of the code execution result from Executor
in the last step, with detailed reasoning of ’what have been done’
and ’what can be derived’. Respond ’None’ if it is the first step
.),
"completed": ("True" if you believe the issue is resolved, and an
answer can be derived in the ’instruction’ field. Otherwise "False
"),
"instruction": (Your instruction for the Executor to perform via code
execution in the next step. Do not involve complex multi-step
instruction. Keep your instruction atomic, with clear request of ’ what to do’ and ’how to do’. Respond a summary by yourself if you believe the issue is resolved.)
}
Let’s begin.

Executor System Prompt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
You are a DevOps assistant for writing Python code to answer DevOps
questions. For each question, you need to write Python code to solve
it by retrieving and processing telemetry data of the target system.
Your generated Python code will be automatically submitted to a
IPython Kernel. The execution result output in IPython Kernel will be
used as the answer to the question.
## RULES OF PYTHON CODE WRITING:
1. Reuse variables as much as possible for execution efficiency since the
IPython Kernel is stateful, i.e., variables define in previous steps
can be used in subsequent steps.
...
There is some domain knowledge for you:
{BACKGROUND KNOWLEDGE OF SYSTEM}
Your response should follow the Python block format below:
‘‘‘python
(YOUR CODE HERE)
‘‘‘

Summary Prompt。Controller 认为任务已经完成,则这个 Prompt将会发给 Controller

1
2
3
4
5
6
7
8
9
10
11
Now, you have decided to finish your reasoning process. You should now
provide the final answer to the issue. The candidates of possible
root cause components and reasons are provided to you. The root cause
components and reasons must be selected from the provided candidates
.
{BACKGROUND KNOWLEDGE OF SYSTEM}
Recall the issue is: {PROBLEM TO SOLVE}
Please first review your previous reasoning process to infer an exact
answer of the issue. Then, summarize your final answer of the root
causes using the following JSON format at the end of your response:
{OPENRCA ANSWER FORMAT}

题目:Cross-System Categorization of Abnormal Traces in Microservice-Based Systems via Meta-Learning

FSE 2025

作者:赫尔辛基大学

摘要

微服务系统(MSS)可能因为复杂性和动态性发生可靠性问题。虽然现有的 AIOps 方法能够通过异常 traces 来定位根因服务,但是仍需要人力来进一步确定根因的故障类型。因此,文章提出了一种故障诊断框架 TraFaultDia,将故障根因和故障类型绑定在一起,给出结果。

文章将故障诊断视作一个分类任务。文章引入了元学习(meta-learning),即每次在有限样本和有限标签组合下进行训练,然后在测试时用极少的有标签样本微调,以应对新故障类型新系统。文章在两个公开数据集上进行评估,无论是在新故障类型还是新系统上都取得了非常高的性能。

背景

现有的 AIOps 算法大多是定位到根因服务,没有进一步给出故障类型。特别是对于有些根因不是微服务的故障,比如机器资源不足、虚拟环境资源配置错误等,简单地定位根因服务没有意义。此外,大型 MSS 的 trace 数据量巨大,人工去分析故障类型显得不切实际。

因此,文章决定将根因服务和故障类型一起给出,整体规范如表所示,文章定义了一个概念:

failure category: pod associated with a fault type

比如表格中的 (F1-F30, B1-B32) 都是 failure category。注意,有些 failure category 是没有对应的 pod 的。

总之,文章把问题简化为了一个多分类问题,但是我有点疑问,对于从未出现过的根因和故障类型的组合,应该怎么办?

挑战

文章总结了进行故障诊断的三个挑战:

  1. MSS 异质性:每个 MSS 在业务逻辑和服务组成上有显著不同,比如 TrainTicket 有 45 个微服务,OnlineBoutique 有 12 个微服务,很难设计一个统一的、适用于所有系统的故障诊断方法
  2. MSS 高维度、多模态 trace 数据:现在很多系统的trace与日志是关联在一起的,包含时序数据、文本数据以及ID
  3. 不同故障类型的trace数据量不均衡:如下图所示,以 TrainTicket 为例,有些故障类型只有26条数据,而有的却有 2546 条

方法

TraFaultDia 的框架非常清晰,分为两个部分:

  1. AttenAE:这一块用无监督的方式训练了一个 trace 的编码器,结构是常见的自编码器。这一块用于对 trace 进行编码得到特征
  2. TEMAML:这一块对 trace 特征进行故障诊断,backbone 是一个 transformer-encoder 网络,采用了 meta-learning 的机制进行学习

AttenAE

有大量工作设计了专门针对 trace 的表征方法,TraFaultDia 的表征方式也有其独到的地方,由于某些系统 trace 和 log 是关联在一起的,所以需要分别进行表征,具体流程如下图所示:

  1. 针对 trace:trace 包含的信息大体分为:

    • time-based:比如每个span的延时,文章直接把每个span的延时拼接为一个数组 \(V_{numeric}\)
    • identity:对于不同的 spanID,这里采用了分层统计的方法,比如上面 Fig.2 的 Span A, Span B, Span C, Span D, 和 Span E 的 spanID 可以重写为:\(V_{span_id}\)=[1.0, 1.1, 1.2, 2.0, 3.0],共同的前缀表示这些 span 属于同一个父级操作或服务
    • textual:如何表示一个trace经过了哪些操作呢?这里将所有的 span 的 service operation 进行编码得到特征然后平均池化得到 \(V_{operation}\)。这里的具体操作分为:①预处理 ② tokenization(用 WordPiece 分割成 subwords) ③ BERT 提取语义信息

最后拼接三个特征得到trace级别的表征 \(V_{span} = Concat(V_{numeric}, V_{span\_id}, V_{operation})\)

  1. 针对 log:因为系统不断变化,日志模板也会变化,所以文章认为通过日志模板得到特征不可靠,应该关注语义信息,编码方式还是 trace 表征方法中textual的三步,得到 \(V_{log}\)

现在已经得到了初步的表征 \(V_{span}\)\(V_{log}\),接下来就是自编码,文章在此处引入了多头注意力机制:

所以,对于一个 trace 集合 \(Tr = \{Tr_1, Tr_2,...,Tr_n\}\),可以通过上述方法得到表征集合 \(Z=\{Z_1,Z_2,...,Z_n\}\),然后再解码得到还原后的特征 \(\hat{V}_{span}\)\(\hat{V}_{log}\),Loss是与原特征的L2范数:

TEMAML

TEMAML 的骨架是 transformer-encoder(TE),输入是 trace 表征集合 \(Z\),输出是 failure category。整个过程分为两部分:

  • Meta-training:该阶段旨在训练 TE 找到能够快速适应任何 MSS 分类任务的鲁棒参数。首先定义一个概念 meta-training tasks,表示为 \(T=(S, Q)\)。其中每一个 meta-training task, \(T_i=(S_i, Q_i)\) 都是一个多分类任务,\(S_i\)\(Q_i\) 分别表示第 \(i\) 个任务的 support set 和 query set。注意,support set 和 query set 均来自训练集

    • support set \(S_i=\{(z_{ij}^{spt}, y_{ij}^{spt})\}_{j=1}^{N\times K}\) 遵循 N-way K-shot 的初始化方法,即有 N 个独特的标签(failure category),每个标签下有 K 个样本。\((z_{ij}^{spt}, y_{ij}^{spt})\) 表示 trace表征集合和对应的failure category。注意,meta-training tasks 中每个任务虽然都是 N 个标签,但是 N 个标签的组合可能不一样,比如总共有 20 个 failure categories,N 为 5,则每个任务的标签都是从 20 个中选 5 个
    • query set \(Q_i=\{(z_{ig}^{qry}, y_{ig}^{qry})\}_{g=1}^{N\times K}\) 有 N 个独特的标签(failure category),每个标签下有 M 个样本,M > K,i.e., (\(|Q_i|>|S_i|\))

训练过程有两个循环:① inner loop 和 ② outer loop,定义基础模型为 \(f\)

  • inner loop: 在 \(S_i\) 上操作,负责对每个任务进行分别学习,并更新模型 \(f\) 参数 \(\theta\)

    • 针对任务 \(T_i\),学习率为 \(\alpha\),参数更新如下:\(\theta_i{'}=\theta-\alpha \nabla_{\theta}\mathcal{L}_{T_i}(f_{\theta}(S_i))\)
  • outer loop: 在 \(Q_i\) 上操作,对于所有任务进行优化,并更新模型 \(f\)

    • 在所有任务上进行优化:\(\min\limits_{\theta} \mathcal{L}(\theta) = \sum_{T_i \in T} \mathcal{L}_{T_i}(f_{\theta'_i}(Q_i))\)
  • Meta-testing:该阶段目的是适应任意 MSS 的新的多分类任务。对于某个特定的测试任务 \(T_{ts}=(S_{ts}, Q_{ts})\),在 \(S_{ts}\) 上进行微调,然后用 \(Q_{ts}\) 进行测试

实验

实验采用的数据集来自 TrainTicket 和 OnlineBoutique,配置如下:

  • TrainTicket:20 基础故障类型 + 10 新故障类型
  • OnlineBoutique:22 基础故障类型 + 10 新故障类型

训练配置:

  • meta-training:4 meta-training tasks (基础故障类型);5-way 5-shot;query set M=15
  • meta-testing:50 meta-testing tasks (新故障类型);5-way 10-shot;query set M=15

文章设计了四个实验来证明 TraFaultDia 在不同场景下的性能:

  1. E1 (TrainTicket → TrainTicket) 在 TrainTicket 的 4 meta-training tasks 训练,在 TrainTicket 的 50 meta-testing tasks 测试
  2. E2 (OnlineBoutique → OnlineBoutique) 在 OnlineBoutique 的 4 meta-training tasks 训练,在 OnlineBoutique 的 50 meta-testing tasks 测试
  3. E3 (OnlineBoutique → TrainTicket) 在 OnlineBoutique 的 4 meta-training tasks 训练,在 TrainTicket 的 50 meta-testing tasks 测试
  4. E4 (TrainTicket → OnlineBoutique) 在 TrainTicket 的 4 meta-training tasks 训练,在 OnlineBoutique 的 50 meta-testing tasks 测试

下面是实验结果

题目:Self-Evolutionary Group-wise Log Parsing Based on Large Language Model

ISSRE 2025

作者:中科大杭州高等研究院,清华大学

摘要

日志解析是一种将半结构化日志转化为结构化模板的技术,它是各种日志分析任务(比如异常检测、日志理解)的前提。

现有的日志解析方法大多基于领域专家制定的启发式规则,这些规则在系统发生变更时就无法适用了。因此,不少研究采用大模型来进行系统无关的日志解析,但仍然存在两个显著问题:

  1. 大模型需要在 Prompt 中加入人工标注的日志模板
  2. 大模型的日志解析效率太低

因此,文章提出一种自演化的日志解析方法 SelfLog,将相似的历史解析的模板作为 Prompt 中的提示词,以实现自我演化和零标注。此外,还引入一种基于N-Gram的日志分组器与日志匹配器,按组处理和解析日志,通过减少大模型调用次数来提升效率。

背景

日志解析

日志解析会将每条日志分割为常量部分变量部分。常量部分也称为模板。如图4所示,在有源码的前提下,我们能够很轻松区分这两个部分。然而大多数时候大量第三方依赖库的源码是不可见的,

所以开发了许多数据驱动的日志解析技术,这些技术分为有监督和无监督两类:

  • 无监督:通过启发式规则和频率统计来提取模板。缺陷是制定规则需要领域经验,且对于新log数据集要重新制定规则
  • 有监督:通过人工标注的 <log, template> 键值对来训练,缺陷是对训练数据的分布敏感且在新log上表现较差

由于日志本质上是程序员写的语句,包含了大量语义信息。大模型技术擅长于理解语句,并且有很强的 zero-shot 推理能力,所以现有研究开始尝试用大模型进行日志解析,但仍然存在两个显著问题:

  1. 大模型需要在 Prompt 中加入人工标注的日志模板案例。这个案例的质量非常重要,随着系统更新,需要人工重新标注案例
  2. 大模型的日志解析效率太低。现有的 LLM 日志解析方法每秒只能处理不到 15000 条日志,如果低于日志产生速度,则非常危险。

所以现在迫切需要一种高效率、高准确率的日志解析方法

SelfLog 方法

文章提出了一个基于大语言模型自演化日志解析工具 SelfLog,架构图如上所示,整体分为四个部分:

  1. N-Gram-based Grouper:这个部分先对日志进行聚类和分组,并提取常量部分,以组为单位让大模型进行解析,减少大模型的调用次数
  2. Log Hitter:这个部分会检查 Grouper 的常量部分是否有现有模板与之匹配,如果是已知的,则无需调用后续步骤
  3. LLM-based Log Parser:以组为单位进行日志解析,输出组的模板
  4. Tree-basd Merger:修正错误的日志和模板

预处理

预处理部分主要关注几个部分: - 将日志中的时间、级别去掉,重点关注日志的内容 - 设置分隔符——满足“[A-Za-z0-9*]+”正则表达式的视为token,其他都被视为分隔符(比如_, |) - 移除日志中的纯数字 - 移除日志中的超低频token(可能是前缀或者后缀)——3个或者少于3个

预处理后,每个日志将会转变为一个 token 列表

N-Gram-based Grouper

这一个模块的目标是:识别并移除 token 列表中的 variable,将剩余的 token 用作分组

上图是这一块的伪代码:

  1. TX = get_token_list(X) 输入:预处理后的 token list
  2. position = get_2gram_constant_index(TX) 这一步是输出权重最大的长度为两个词的 token 的索引。假设常量部分一般都是高频词,因为它会稳定出现在衍生的日志中,所以高频词会给较高的权重。
  3. variable_list_right = PILAR_gram(TX, position) 从 position 开始向右移动,每个 token 的分数计算为 (与邻居共同出现的次数 / 邻居出现的次数),然后通过3-sigma 判断分数是否异常,如果低于阈值,则判断为 variable
  4. variable_list_right = PILAR_gram(TX, position) 与 3 同理

经过上述伪代码,token 列表中将只剩下常量 token,然后依据常量 token 列表进行分组,组的 key 值即为常量 token 列表

值得注意的是,即使这一块没有识别出所有的 variable,也可以让后续的 LLM-based Log ParserTree-basd Merger 来修正

Log Hitter

这个模块的目标是:检查不同组的 key 是否在历史中出现过,如果命中,则直接返回模板;如果没命中,则将这个组中三个编辑距离最大的logs作为 LLM-based Log Parser 的输入。

Log Hitter 比较简单,维护了一个字典,键是 token 列表,值是模板。LLM-based Log Parser 的输出也会更新到字典中

LLM-based Log Parser

这个模块的目标是:对三个同组的 logs 提取模板

上面是 prompt 模板,用的大模型是 GPT 3.5,输出固定为 分析过程模板

需要注意的是 prompt 中的蓝色板块,这一块是实现 self-evolution 的关键,核心思想是: > 建立一个 Prompt Database 记录日志和模板,当日志需要解析时,采用类似 RAG 的技术检索到最相似的历史日志和模板作为 example,放到 prompt 中去

Tree-based Merger

事实上,上述过程仍然还会有变量被遗漏,如下图所示

最开始的时候,只有 user 为 cyrus 的日志,所以会把 cyrus 视为常量,但是随着越来越多 user 的出现,cyrus 应该被划分为变量,所以需要一种后处理机制

如上图所示,SelfLog 维护了一棵树,这棵树是实时更新的,然后执行合并操作,这一块感觉讲的不是很清楚,也不知道是人工合并还是自动化合并

实验

主实验是在 LogPAI 上进行性能对比

参数和消融实验就不放了

效率实验是在不同日志产生速度和解析速度上的对比

题目:L4: Diagnosing Large-scale LLM Training Failures via Automated Log Analysis

FSE 2025

作者:香港中文大学

摘要

训练个性化的大语言模型(LLM)需要大量的计算资源和训练时间。这个过程中,故障(failure)是不可避免的,而故障的出现使得LLM的训练浪费了大量的资源和时间。此外,在 LLM 训练中诊断故障也是一个费时费力的任务,因为 LLM 的训练通常设计多个计算节点:

  • Node-level Complexity: 在单个节点上训练的 AI 模型,通常包含 AI accelerator(GPUs 或 NPUs)、AI toolkit(CUDA)、AI framework(Pytorch)以及AI algorithm。故障可能存在于上述任意一个地方。
  • CLuster-level Complexity: LLM 的训练通常涉及上千个 AI 节点,这些节点之间有复杂的通信范式,这使得发生故障时很难通过自动化的方式定位到对应的故障 AI 节点

这篇文章首先进行了大量的实证分析,得出以下结论:

  1. 故障时间:大多数(74.1%)的故障发生在模型迭代训练时,这个阶段发生故障会导致大量的训练时间和资源的浪费
  2. 故障根因:随然故障原因多样化,但主要集中在 hardwareuser-side faults
  3. 诊断方法:日志在故障诊断中发挥重要作用,但是89.9%的案例仍然需要人工日志分析来进行故障诊断。并且日志量太大(每天产生 TBs),只有非常小一部分的日志是有用的

因此,文章提出了一种诊断 LLM 训练的故障诊断方法:L4,目的在于自动化识别故障相关的日志(failure-indicating logs),此外,L4 还会提供 failure-indicating nodes、failure-indicating stages、failure-indicating events 以及 failure-indicating iterations 等重要信息来辅助 SREs 进行故障诊断。

实证分析

文章分析了某平台上一年的428份故障报告,从以下三个方面进行分析:

  1. RQ1:LLM 训练中的故障现象
  2. RQ2:LLM 训练中的故障根因
  3. RQ3:LLM 训练故障诊断中常用的数据源

RQ1:故障现象

LLM 训练中的故障现象分为四大类:① launching failure,② training crash,③ abnormal behavior,④ others

以下是四种故障现象的分布:
  1. 对于 launching failure(21.3%),这类现象通常发生在迭代训练开始前,原因一般是配置与版本问题,比如 GPU 驱动与 CUDA 版本不匹配,模型并行化配置错误等。
  2. 对于 training crash(57.5%),这类现象发生在迭代训练时,起因一般是硬件故障(GPU、network),这种故障影响很大,会浪费大量训练时间和计算资源,即使有 checkpoint 这样的状态保存机制,时间的浪费也是不可忽视的
  3. 对于 abnormal behaviors(16.6%),这种现象有点类似于性能降级,比如某个epoch花费了两倍的时间,训练突然停滞
  4. 对于 others(4.7%),这类故障一般是基础设施类的,比如平台和存储,比例比较少

Finding 1:大部分故障(74.1%)发生在迭代训练时,会导致大量计算资源和训练时间的浪费。

RQ2:故障根因

LLM 训练中的故障根因分为四大类:① hardware fault,② user fault,③ platform fault,④ framework fault

RQ2-1 hardware fault

Hardware Fault 又可细分为

  • Network Fault:最常见,本质上是因为有太多 AI node 在交互协同,网络问题极容易导致训练的失败
  • Accelerator Fault:Accelerator 就是GPU、TPU那些计算资源,与普通深度学习任务类似,单个 Accelerator 也会有内存故障等
  • Node Fault:Node 是资源分配的单元,比如虚拟机。也会遭遇断电、磁盘问题等故障
  • Storage Fault:训练涉及的数据集、模型、checkpoints等有几百GB,用户一般会存储到远程存储库中,在训练时下载,因此可能会出现访问问题

Finding 2:LLM训练需要大量计算资源,极易受 Hardware Fault 影响。其中,Network Fault 和 Accelerator Fault 是最常见的

RQ2-2 user fault

user fault 又可细分为

  • Configuration Error
  • Program/Script Bug
  • Software Incompatibility
  • Misoperation

Finding 3:User faults 是第二大故障根因,源于用户的误操作、脚本bug等

RQ2-3 Framework Fault and Platform Fault

这两类故障发生的极少,Framework Fault 一般是 PyTorch 那些深度学习框架中的故障。Paltform Fault 是训练平台的问题,包括资源管理不恰当等。这两类故障极难诊断故障,需要较深的领域经验

Finding 4:Framework Fault 和 Paltform Fault 虽然发生极少,但诊断起来相当困难

RQ3:故障诊断数据源

现在诊断 LLM 训练故障还是人工分析居多,文章中提到:

诊断 LLM 训练故障平均需要 34.7 小时,而 41.9% 的故障需要 24 小时以上的诊断时间

这就迫切需要自动化的故障诊断方法来减少人工分析成本。文章首先对428份故障案例进行分类,分为以下三种:

  • Log-only diagnosable
  • Non-log diagnosable
  • Hybrid diagnosable

然后分别分析每个案例的 training log 来进行故障诊断,以下是分类结果

同时发现每个故障案例在故障时间段平均有 16.92GB 的 training log

Finding 5:training log 能解决 89.9% 的故障,但是日志量太大,需要自动化分析手段来减少人力成本

现有方法

现有方法大致通过 logging levelevent frequencyerror semantic 来提取异常log,即 failure-indicating logs。文章通过统计发现这些方式都有一定的局限性:

  • logging level:现有方法有许多是根据日志级别来进行筛选的。文章首先统计了 failure-indicating logs 中不同级别的日志分布,如 Fig.4 (a) 所示,虽然大部分是 Error-level,但是仍有大量其他级别的 logs 是故障相关的。此外,并不是所有 Error-level 的 logs 是与当前故障相关的,比如有些 Error logs 是无法写入 checkpoints,但一些故障容忍策略可能会采用重写措施,只要重写成功,那么对当前训练是没有影响的
  • Event Frequency:有些方法是根据日志的频率来筛选故障相关日志,比如低频日志更有可能是异常的。如 Fig.4 (b) 所示,仍然有大量 failure-indicating logs 的频率并不低。此外,基于频率的筛选可能更适合微服务,不适用于 LLM 训练,因为微服务是无状态的,日志的频率分布可能并不会随时间分布发生较大改变,而 LLM 训练是顺序的、分阶段的,有些日志只会出现在特定阶段,这些日志的频率很低,但与故障无关。
  • Error Semantic:也有些方法通过深度学习来提取日志的错误语义信息来识别failure-indicating logs,但是这种方法是不稳定的,因为即使是成功训练的job也包含有error semantic

Finding 6:现有的 failure-indicating logs 提取方法基于level, frequency 和 semantic,但都不适用于 LLM 训练的故障诊断

L4 方法

文章提出了自动化的面向 LLM 训练的故障诊断框架:L4

1. Log Preprocessing

这一块与之前的方法相同,都是用 Drain 提取日志的模板,将 log sequence 变成 event sequence

2. Cross-job Filtering

这一块动机很直观,因为用户在提交作业到平台进行大规模训练(large-scale nodes)时,通常在小规模的节点上已经测试通过了。所以这一块会将成功执行的日志(Normal logs)与在大规模节点上的失败日志(failed logs)进行比对,删除 failed logs 中噪声 events。

具体做法为:将 Normal logs 进行解析得到一个 normal event pool:\(N=\{e_{n1}, e_{n2}, ...\}\),然后将这些 events 按照时间顺序排列,并逐个移除 failed logs 中对应的events,这些events都是正常的,failed logs 剩下的 events 都是极大概率是故障相关的。这里有个问题,不同规模的训练节点会不会导致日志模式发生变化?

这个模块的前提是有小规模训练成功的日志,当这个前提不满足时,则无法删减 failed logs的噪声events,就直接将 failed logs 解析后进行后续步骤

3. Spatial Pattern Comparison

这个步骤主要是为了定位 failure-indicating nodesfailure-indicating logs。核心思想是:由于负载均衡,正常情况下所有的 AI nodes 的日志几乎是一样的。所以可以很轻松找到 failure-indicating nodes

首先将每个 node 的 event sequence 按照发生次数进行特征构建,得到 event vector:\(V=[c_1,c_2,...c_n]\)\(c_i\) 代表 \(i\)-th event 的次数。然后对所有 node 的 event vector 采用 Isolation Forest 进行异常检测。

由于 Isolation Forest 可以记录特征参与分割的次数,从而能够判断特征(event)的重要性,所以进一步找到 failure-indicating logs

4. Temporal Pattern Comparison

这个步骤主要是为了定位 failure-indicating stagefailure-indicating iteration,即故障发生的时间。

stage 的确定很简单,L4 好像直接使用的是日志中自带的规则,能直观知道 event 在哪个 stage

文章还要定位到故障的 iteration。首先文章有个前提假设,即 event sequence 在迭代时基本遵循一定的模式,如果某个 iteration 发生了明显偏移,则视为异常

具体做法为:首先将 iterations 按照 10 个一组进行滑动窗口划分。采用 Dynamic time warping(DTW)计算不同窗口间的距离,然后通过 3-sigma 方法对距离进行异常检测,就能找到异常的 iteration 了

最后,就能将故障相关的 logs、events、nodes、stages、iterations 交给 SREs 了。

实验设计

实验主要是对 failure-indicating logsfailure-indicating nodes 的定位进行了准确率评估,然后给了几个成功案例

题目:On Optimizing Traffic Scheduling for Multi-replica Containerized Microservices

ICPP 2023

作者:中国科学技术大学,华为

摘要

基于容器的微服务系统部署已经变得越来越重要,为了应对高并发(high concurrency)和提升错误容忍度(fault tolerance),微服务系统通常会引入多个副本(replicas)。目前有两种类别的服务部署方法:

  • resource-friendly:将 pod 尽可能分散部署到不同机器上,目标是平均各机器的资源使用率。(e.g., Kubernetes,Docker Swarm,OpenShift)
  • traffic localization:将流量(traffic)交互频繁的 pod放到同一台主机上,目标是减少跨主机流量导致的性能降级。(e.g., CA-WFD,Blender,NetMARKS)

这两种方法各有利弊:① resource-friendly存在大量跨主机流量(cross-machine traffic),可能会引发性能降级;② traffic localization在多replicas情况下,由于机器资源不足,也会存在大量cross-machine traffic。

文章提出了一种网络感知的流量调度(traffic scheduling)方法 OptTraffic,首先通过轻量级监控方法估计容器之间的流量大小,然后通过算法分配容器之间的流量比例(默认应该是负载均衡),目标是减少 cross-machine traffic,并尽可能平均各主机的资源利用率。

背景

基于容器的微服务

文章首先介绍了微服务是什么,这里就不进行赘述了。然后介绍了基于容器的微服务部署方法:

首先是传统的容器部署,代表为K8s,文章说是基于resource-friendly的,简而言之,就是只考虑容器的CPU和内存需求。这里我先查阅了K8s调度器 kube-scheduler 的说明,以下是文档的内容:

调度器执行步骤如下:
  1. 找出该 Pod 的所有 可选节点,这个过程称为过滤(filtering)
  2. 按照某种方式对每一个 可选节点 评分,这个过程称为打分(scoring)
  3. 选择评分最高的 可选节点
  4. 将最终选择结果通知 API Server,这个过程称为绑定(binding)

文章的重点在打分,假设全部节点皆可部署,则有如下打分策略:

  • SelectorSpreadPriority:将 Pod 分散到不同的节点,主要考虑同属于一个 Service、StatefulSet、Deployment的情况
  • InterPodAffinityPriority:遍历 weightedPodAffinityTerm 并求和,找出结果最高的节点
  • LeastRequestedPriority:已被消耗的资源最少的节点得分最高。如果节点上的 Pod 越多,被消耗的资源越多,则评分约低
  • MostRequestedPriority:已被消耗的资源最多的节点得分最高。此策略会把 Pod 尽量集中到集群中的少数节点上
  • RequestedToCapacityRatioPriority:按 requested / capacity 的百分比评分
  • BalancedResourceAllocation:资源使用均衡的节点评分高
  • NodePreferAvoidPodsPriority:根据节点的 annotation scheduler.alpha.kubernetes.io/preferAvoidPods 评分。可使用此 annotation (容忍度 Toleration 和 污点 Taint)标识哪些 Pod 不能够运行在同一个节点上
  • NodeAffinityPriority:基于 PreferredDuringSchedulingIgnoredDuringExecution 指定的 node affinity 偏好评分。参考 将容器组调度到指定的节点
  • TaintTolerationPriority: 根据节点上不可容忍的污点数评分
  • ImageLocalityPriority:有限选择已经有该 Pod 所需容器镜像的节点
  • ServiceSpreadingPriority:确保 Service 的所有 Pod 尽量分布在不同的节点上。
  • CalculateAntiAffinityPriorityMap:anti-affinty,参考将容器组调度到指定的节点
  • EqualPriorityMap:为每个节点指定相同的权重

这些打分策略都有一定的权重,最终的分数计算如下:

1
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + ...

经过查阅资料,默认开启的调度代码如下,可以看出,在所有节点都有对应镜像,没有亲和性和污点干扰的情况下,k8s其实偏向于resource-friendly,即分布更加均匀,相关策略有BalancedAllocationName、LeastAllocatedName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Score: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: noderesources.BalancedAllocationName, Weight: 1},
{Name: imagelocality.Name, Weight: 1},
{Name: interpodaffinity.Name, Weight: 1},
{Name: noderesources.LeastAllocatedName, Weight: 1},
{Name: nodeaffinity.Name, Weight: 1},
{Name: nodepreferavoidpods.Name, Weight: 10000},
// Weight is doubled because:
// - This is a score coming from user preference.
// - It makes its signal comparable to NodeResourcesLeastAllocated.
{Name: podtopologyspread.Name, Weight: 2},
{Name: tainttoleration.Name, Weight: 1},
},
},
总的来说,K8s默认是resource-friendly的说法倒也没啥问题~~~

然后文章介绍了集中式部署,即以减少cross-machine traffic为目标的traffic localization方法,这里进行了一个实证分析来论证cross-machine traffic的影响,具体实验方法为:

将经典应用 socialNetwork 部署到2台主机,并用 sockperf 模拟两个主机之间的流量干扰,测量不同QPS下不同带宽的延时的P99分布

图中显示QPS增大时,同样带宽下的P99延时明显增大,所以cross-machine traffic受带宽限制,会影响延时。但这有没有可能是 POD 资源不够导致的(这里应该给 POD 设置足够充足的资源来消除影响)?

Traffic localization 及其局限

为了解决cross-machine traffic带来的性能降级问题,许多工作提出了 traffic localization 的部署方法,有两种实现方式:

  • 将属于同一个application的所有容器部署到相同或邻近的主机
  • 将流量交互密切的容器对(container pairs)调度到相同或邻近的主机

traffic localization 不仅可以减少 cross-machine traffic 带来的网络延迟(由传输路径和带宽大小决定),也可以减少对网络数据包操作(包的封装和解析)带来的计算开销

为了验证 traffic localization 对于节省网络流量和降低响应延时的作用,文章在两种部署方式上进行实验:

  1. 部署 socialNetwork 到 5 台主机上
  2. 部署 socialNetwork 到 1 台主机上(traffic localization

图(a)显示 traffic localization 可以减少大量的cross-machine traffic。图(b)显示 traffic localization 可以大幅度减少响应延时

Limitationtraffic localization虽然能显著减少cross-machine traffic来提升性能,但是通常很难完全将一个 application 的所有微服务都部署到一台主机上,原因在于微服务还会产生多个replicas,除此之外,监控容器之间的流量也会引入较大的CPU负载,traffic localization也会造成机器资源使用不均。具体如下:

(1)当 POD 分布在不同主机时。因为负载均衡,流量会均匀流向不同主机的POD,造成大量的cross-machine traffic

(2)高CPU负载。对容器间流量的监控一般需要抓取数据包,对数据包的封装和解析过程会造成高昂的CPU开销。比如socialNetwork有27个容器,消耗13个CPU核,而加上监控装置(e.g., iftop)会多消耗 1.4-2 个 CPU核【istio也存在这样的问题,甚至sidecar消耗更多】

(3)不均衡的资源使用。许多pod有着相同的资源需求,比如都是CPU密集型,将他们放置在一台主机上会造成资源使用不均衡,即CPU过载,但是内存利用率低。除此之外,将所有pod放在一台主机上会使得fault tolerance降低。

方法设计

OptTraffic 的目标是最小化 cross-machine traffic。整体架构如上图所示。包含三个组件:

  1. Traffic Monitor。这个组件用轻量级的方式估计每个 container pair 之间的流量大小。做法为收集每个容器的 incoming 和 outgoing traffic,然后构建一个 traffic graph,最后通过简单的数学计算估计每个 container pair 之间的流量大小
  2. Traffic Allocator。流量调度模块,决定 upstream container 流向 每个 downstream container 的流量比例。具体策略为本地优先(local-first),以减少跨主机流量
  3. Container Scheduler。异步调整 container pairs 的部署,以实现机器之间的负载均衡。

Traffic Monitor

Traffic Monitor 有两个目的:① 识别频繁交互(heavy-traffic)的链路 ② 为链路调度进行优先级排序

现有两种方式进行容器间流量的监控:

  1. 在容器所在POD加一个sidecar,接管流量的转发和监控(e.g., istio)。
  2. 使用网络包嗅探工具在userspace进行解析(e.g., tcpdump and iftop)。

这两种方式都会造成高昂的CPU负载。文章还特别提到了在kernel space 工作的 eBPF,可以减少监控负载,但仍需要分析网络包,并且实现复杂。所以作者想用数学方法来减少监控带来的负载。

eBPF 避免用户态与内核态切换:传统上,像tcpdump这样的工具是在用户空间工作的,它们需要将网络数据包从内核空间复制到用户空间进行分析。这个过程涉及上下文切换和内存拷贝,带来了额外的CPU开销。而eBPF程序则可以在内核空间内部处理数据包,无需这种昂贵的数据传输。

Step 1: 监控容器流量

如 fig 6 所示,一个upstream container 可以与多个 downstream container进行交互,OptTraffic首先使用 Prometheus 监控每个container接收和发送的traffic

Prometheus 只是一个收集工具,数据源来自于 操作系统记录的每个容器接收和发送的包的大小(这些数据已经保存在主机上,所以只需要周期性地获取就行,几乎没有监控成本),数据保存在 proc 文件系统中,经过查资料,应该用的是这两个指标:

  • container_network_receive_bytes_total
  • container_network_transmit_bytes_total

Step 2: 计算链路流量。链路流量(Link Traffic)指的是特定的两个容器之间的流量大小,OptTraffic 直接使用数学方法计算得到,减少监控的负载(注意:上一步只能监控到每个容器的接收和发送的traffic大小,不能知道与特定容器之间的traffic大小)。计算步骤如下,从入度为 1 的节点 \(p\) (根节点)开始,先将与 \(p\) 相关的两条边赋值(即<\(p\),\(q\)>和<\(q\),\(p\)>),这两条边的值完全等于 \(p\) 的 send 和 receive 的 traffic 。然后将 \(p\) 相关的流量从 \(q\) 中减去,从图中删去 \(p\),重复如上过程,直到所有节点计算完毕。

  1. 构造微服务系统的 DAG。(可以通过通过配置文件、官方文档或者其他监控手段得到)
  2. 基于 DAG 构造 traffic graph。traffic graph有着和DAG相同的构造,唯一的不同是,traffic graph是双向边,代表 traffic 也可以从 downstream container 发往 upstream container (response)
  3. 基于 traffic graph 计算每个 link 的traffic大小,算法如下:

GraphMergeBreakCycle两个函数用于解决图中存在环的情况。如下图所示,环有两种情况:

  1. upstream containers 来自不同微服务 (Fig. 7(b))
  2. upstream containers 来自同一个微服务的不同replicas (Fig. 7(c))

对于 case1,BreakCycle 函数首先选择环中 incoming 和 outgoing traffic 最少的 container 进行讨论,然后通过网络包嗅探工具监控这个 container 每条边 send 和 receive 的traffic。然后在相关 containers 中移除这个 container 的traffic,最后删除这个container,这样就可以破坏环。

对于case2,GraphMerge 函数将属于同一个微服务的 replicas 进行合并,至于每个 replica 分得的 traffic,可以通过流量分配策略进行估算

负载分析:从设计上看,OptTrace 确实轻量级,因为不需要监控每条 link 的 traffic,而每个 vertex 的 traffic 本身就被os记录下来了。即使有环,也只需要对一部分容器进行网络包嗅探分析,破环即可。算法复杂度为O(N),N为节点数量。

Traffic Allocation

这个模块是核心,决定了 upstream 微服务向某个 downstream 微服务的不同 replicas 发送 traffic 的比例。downstream 微服务的replicas可能部署在不同主机上,所以策略不再是简单的负载均衡

如上图所示,有2个 upstream containers 和 3个downstream containers,并部署在2台主机上。如果是按照负载均衡的原则,假设每个upstream container发送 1 traffic,则发往每个downstream container \(\frac{1}{3}\) traffic,每个downstream containers总共需要接收 \(\frac{2}{3}\) 的traffic。从Fig 8 (a)中看出,有 \(1=\frac{1}{3}+\frac{1}{3}+\frac{1}{3}\) (一半)的traffic是cross-machine的。

文章介绍了 Two-step allocation 方法来进行 traffic 分配:

  1. 第一步,采用 local-first 原则,即traffic尽可能分配到同台主机的containers。假设机器 \(i\)\(m_i\) 个upstream containers 和 \(n_i\) 个 downstream containers,则每个upstream container需要发送给每个downstream container比例为 \(min(\frac{m/n}{m_i}, \frac{1}{n_i})\)的traffic。这里的比例很简单也很有趣,每个downstream container接收到的traffic是 \(m_i\times min(\frac{m/n}{m_i}, \frac{1}{n_i})=min(m/n, m_i/n_i)\),第一个是全局负载均衡的traffic大小,第二个是单机上负载均衡的traffic大小。这里的原理是:使得低于负载均衡的traffic尽可能发往本机的container,但这个traffic尽量不高于全局负载均衡的值。所以 Fig 8 (b) 中 DC3 的 traffic 大小为 \(min(\frac{2}{3}, 1)=\frac{2}{3}\)
  2. 第二步,这一步叫padding,即将第一步中剩余未分配的traffic导向其余downstream containers,这些traffic就是cross-machine traffic了。如 Fig 8 (c)所示。

看了一下源代码,流量调度用的是iptable实现的,具体指令类似如下这样

1
sudo iptables -t nat -I KUBE-SVC-KVWZRLJ3RRU55EYE 1 -s 0.0.0.0/0 -j KUBE-SEP-5EMJYGBVFF6DIW4U -m statistic --mode random  --probability 0.17

以上是论文的核心,至于动态调度那一块,首先是没看懂,可能以后有时间再读读

题目:StatuScale: Status-aware and Elastic Scaling Strategy for Microservice Applications

来源:arxiv 2024

作者:中国科学院深圳先进技术研究院

摘要

相比于单体架构,微服务架构具有更好的弹性,可以进行微服务级别的弹性伸缩。然而,现有的弹性伸缩无法检测突发流量,突发流量可能由很多原因引起:

  • 商店促销
  • 特殊活动
  • 软件故障
  • ...

这些突发流量通常是短暂(short-lived)且超出预期的(unexpected),会造成瞬间的性能降级。微服务必须要快速分配足够数量的资源以保证性能。

本文推出了一种状态感知的弹性伸缩控制器 StatusScale,能够感知负载的趋势,预测流量峰值,并进行水平伸缩垂直伸缩。此外,本文提出了一种新的指标:

  • correlation factor:评估资源使用的效率

文章在 Sock-Shop 和 Hotel-Reservation 应用上进行评估,将响应延时降低了接近10%,资源利用效率得到提高。

方法

StatuScale 根据当前负载状态选择不同的资源分配方案(垂直和水平),架构如下:

  1. Load Preprocessor是产生流量的模块
  2. Auto Scaler是全文核心,分为:
    1. Vertical Scaling:对负载状态进行预测,制定垂直伸缩方案
    2. Horizontal Scaling:负载状态不稳定时,进行水平伸缩
  3. Performance Evaluator是评估模块,主要评估响应延时、SLO违背率、资源利用效率、资源消耗等

系统建模

弹性伸缩的优化目标和约束如下:

\[ \mathop{\sum_{m\in M}{P_m/A_M \times \sum_{p\in P}{R_p/A_p}+\omega^t\sum_{m\in M}RT_m/A_M}} \]

\[ s.t. \quad P_m \geq 1, \quad R_p, RT_m \geq 0,\quad A_p\geq A_M\geq 0 \]

\(M\) 代表微服务集合,\(A_M\) 代表应用中微服务的数量(即 \(|M|\)),\(P\) 代表应用中pod集合,\(A_p\) 代表应用中pod的数量(即 \(|P|\)),\(P_m\) 代表微服务 \(m\) 的pod数,\(R_p\) 代表为pod \(P\) 垂直分配的资源配额,\(RT_m\) 代表微服务 \(m\) 的响应延时。

优化目标的前半部分是资源消耗(服务的平均pod数\(\times\)pod平均资源额度),后半部分是平均延时,权重设置为 \(\omega^t\)。目标是同时优化资源消耗和响应延时。

垂直伸缩

微服务的负载可能由于特殊的用户活动(e.g., 商品促销)而陡然升高,进而导致服务资源不足而性能下降。所以监控、分析和理解负载的趋势是非常重要的。

基于 LightGBM 的负载预测器

为了能高效地进行负载预测,作者没有采用较重的深度学习或者机器学习模型,而是准备选用基于集成模型的 LightGBM 来进行预测。

遗憾的是,LightGBM 面临准确性问题,在Fig. 2中,作者试图用 LightGBM 预测 Alibaba 数据集的负载,但是出现了大量负载低估的情况。这些低估会导致资源分配较少,进而导致性能下降。

Load Status Detector

文章另辟蹊径,不再直接训练和预测负载的准确值,而是判断负载是否处于 “stable” 状态。文章引入了金融分析中常用的 resistance linesupport line 两个概念用来辅助判断。

在 Fig. 3 (a)中,作者展示了如何根据 resistance linesupport line 进行负载状态感知。图中橙色的点是预测时的低估点。首先将x轴分为6个时间窗口,每个时间窗口有5个数据点。

  1. 首先,StatuScale使用第1个窗口的数据去生成 resistance linesupport line
  2. 然后,去判断第2个窗口是否违背了第1个窗口的resistance linesupport line。如果违背了,则判断状态为 “unstable”,采取特定的伸缩策略;如果没有违背,则判断状态为 “stable”,则能够继续进行负载预测。Fig. 3 (a) 中状态为 “stable”
  3. 合并第1个和第2个窗口的数据来更新resistance linesupport line,进而判断第3个窗口超过了resistance line,所以标记第3个窗口状态为 “unstable”
  4. 第4个窗口重新生成resistance linesupport line,重复上述过程
总的来说,resistance linesupport line 相当于负载波动的上下边界,在边界内的负载一般都是 “stable” 的。

对于resistance linesupport line的建模,作者摒弃了复杂的非线性函数,因为担心会导致过拟合。此外,作者又担心线性函数过于简单,所以决定采用分段线性函数(因为分段线性函数可以解决周期负载的判断问题),所以就有了 “unstable” 后进行resistance linesupport line重置的环节。resistance line的定义如下:

\[ f(t) = kt+b+\lambda c_v \]

\(k\), \(b\) 分别代表斜率和截距,可以通过多项式拟合数据点得到。\(t\) 代表时间。\(c_v\) 代表变异系数(\(\frac{\mu}{\sigma}\)),用来表示样本的分散程度,也为resistance line留有了一定的容错空间。\(\lambda\) 是权重超参数。

自适应PID控制器

在上节中,StatuScale 已经能判断出服务的负载状态,当负载状态为 “unstable” 时,采用 PID 控制器来维持状态的稳定,目标是使得CPU利用率稳定,以及满足SLO。

PID 控制器是广泛使用的控制器,由 1)比例 proportional、 2)积分 integral、 3)导数 derivative 组成。PID 控制器旨在根据feedback更新参数,调整输出,使得状态稳定在目标值附加,输出的分数公式如下:

\[ y(t) = k_Pe(t)+k_I\int_{t-w}^{t}{e(\tau)d\tau+k_D\frac{d}{dt}e(t)} \]

其中,\(e(t)\)代表时刻t的误差(给定值-测量值)。\(k_P\)\(k_I\)\(k_D\) 分别是比例增益(proportional)、积分系数(integral)以及导数系数(derivative),分别代表当前误差,过去一段时间的误差以及预测未来的误差的权重。

PID 控制器的各项权重 \(k_P\)\(k_I\)\(k_D\) 对系统的稳定性影响很大,StatuScale 引入了一种自适应调节各项权重的 A-PID 控制器。如 Fig.6 所示,A-PID 引入了 BP 网络来调节 PID 的参数(i.e., \(k_P\)\(k_I\)\(k_D\))。BP 网络的配置如下:

  • 输入:输出值目标值误差bias
  • 中间层:hidden size = 5,激活函数为 tanh
  • 输出:\(k_P\)\(k_I\)\(k_D\),激活函数为 sigmoid

这里文章漏掉了最关键的 loss,我只能猜测 loss 的原理是,如果当前误差较大,我们希望新的参数能够更大幅度地改变,以便更快地纠正误差;反之,如果误差较小,则应谨慎调整参数,避免过度校正,所以猜测 loss 为 误差相关的函数。

此外,A-PID的 output 是如何转化为 CPU 的分配?CPU target是多少?垂直伸缩这一块并不是讲的很清楚

水平伸缩

文章认为水平伸缩比垂直伸缩更难,因为水平伸缩需要时间去创建和移除POD,并需要时间去进行负载均衡,同时不必要的水平伸缩操作会导致资源浪费。此处引用了ATOM1的结论:

  • 低负载:垂直伸缩更有优势,因为资源分配快
  • 高负载:水平伸缩更有优势,因为多个pod分布在多个机器上,将负载均衡了,大大降低了单个pod的压力

所以 StatuScale 会优先考虑垂直伸缩(在低负载下更有优势);如果垂直伸缩无法满足需求,才会考虑用水平伸缩进行粗粒度调整。再用垂直伸缩进行细粒度调整。

首先,StatuScale 将会判断是否需要进行水平伸缩操作,计算当前CPU利用率 \(C_t\) 与目标CPU利用率 \(CPU_{tar}\) 之间的差距,并根据这个差距生成一个转换后的结果 \(S_t\)

\[ S_t = \begin{cases} 1-K^{(CPU_{tar}-C_t)}& C_t < CPU_{tar}\\ K^{C_t-(CPU_{tar})}-1& C_t \geq CPU_{tar} \end{cases} \]

当前CPU利用率 \(C_t\) 接近目标值 \(CPU_{tar}\)\(|S_t|\)接近0;否则,\(|S_t|\)值将以指数倍数增长。StatuScale 统计一段滑动窗口内的不同时间点的 \(S_t\) 的和(减少突发流量的影响),并与上下阈值进行比较,以决定是否进行弹性伸缩。

但是文章并没有给出上下阈值的计算方式?

当决定采用弹性伸缩时,给定当前副本数(\(R_c\)),伸缩比例(\(\delta\)),伸缩的副本数定义如下:

\[ R_n = max(\delta R_c, 1) \]

因为水平伸缩的pod需要一段时间才能生效,所以这段时间可能会频繁触发弹性伸缩,所以 StatuScale 引入了 cooling-off 周期来减少伸缩次数(这段时间不会触发第二次伸缩,默认为5min)

接下来文章用垂直伸缩来进行细粒度资源调整,具体来说,就是通过一个衰减率来周期地减少资源配额,资源值设置如下:

\[ V(t) = Vk^{\beta^t-1} \]

\(0<\beta<1\) 是衰减率,\(V\) 是资源初始值。 > 这里只有减少垂直资源分配,相当于减少水平伸缩多余的那部分资源

联合伸缩

文章的讲述顺序和方法流程是不一样的,所以最开始让我有点费解,真正的整体流程如上图所示:

  1. 首先判断是否需要水平伸缩,判断方式为上文中提到的计算一段时间的 \(S_t\),并与上下阈值比较
    1. 如果需要水平伸缩,则计算\(R_n\),然后垂直细粒度资源调整(计算\(V(t)\)
    2. 如果不需要,则需要进行垂直伸缩判断
  2. 垂直伸缩检测需要对负载状态进行判断
    1. 如果状态为 “stable”,则用 LightGBM 预测负载
    2. 如果状态为 unstable,则使用 A-PID 控制器将资源利用率维持在稳定状态

值得注意的是,k8s的垂直伸缩应该会让容器重启(假如有10个副本,采用垂直伸缩后,相当于这10个副本都需要滚动更新),这真的会比水平伸缩快吗?

整体算法如下图所示:

实验评估

实验配置

  • 集群配置:1 master + 2 worker,每个节点都是 4GB 内存 和 4 CPU cores。这个配置算比较小的了
  • 负载:文章的负载数据来自于 alibaba 的 cluster-trace-v20182,这个数据集里记录了8天内集群里机器和容器的资源使用情况,并调研了CPU负载和QPS的对应关系,如Fig.8所示,将CPU负载转化为QPS,文章使用这个作业负载作为实验的输入流量。负载的注入工具选择 Locust
  • benchmark:选用 Sock-Shop 和 Hotel-Reservation
  • 对比方法:选用了 GBMScaler,Showar,Hyscale
    • GBMScaler:选用 LightGBM 进行负载预测,但原文并没有提到如何用预测的结果进行 resource scaling
    • Showar:经典的混合伸缩方法,基于 3-\(\sigma\) 准则进行垂直伸缩,每T秒预估当前CPU分配为过去一段窗口的\(\mu+3\sigma\);基于 PID 控制器进行水平伸缩(target设置为CPU利用率,\(k_P\)\(k_I\)\(k_D\)的更新与 StatusScale一致)
    • Hyscale:与kubernetes 默认弹性伸缩器很像,只需要指定CPU阈值,然后通过水平和垂直伸缩来达到目标

评估指标

文章主要考虑系统性能资源消耗,使用的指标如下:

  1. response time (相同资源配额下,\(\int{R_t}dt\)\(R_t\)是t时刻分配的资源)
  2. SLO violation(相同资源配额下,\(\int{R_t}dt\)
  3. Accuracy of supply-demand relationships. 这个是看 resource supply 是否准确。在一段时间 \(T\)内,总共可分配资源为 \(R\),t 时刻的资源需求为 \(d_t\)\(d_t\) 是通过 Fig.8 拟合出来的),有点类似于误差
    1. \(a_U=\frac{1}{T\cdot R}\sum_{t=1}^{T}{(d_t-s_t)^+\Delta t}\)
    2. \(a_O=\frac{1}{T\cdot R}\sum_{t=1}^{T}{(s_t-d_t)^+\Delta t}\)
  4. Correlation factor of supply-demand relationships. 这个指标用于衡量 supply curve 与 demand curve 的相似度(与上一个指标差别不大),本来应该用 Locust 收集的 QPS 转化为 demand 的 CPU 利用率,以及用 Prometheus 收集的 supply 的 CPU 利用率,然后计算两个曲线的 R-square (评估回归模型的性能指标)。但文章认为 Locust 与 Prometheus 是两套监控系统,收集周期和统计方式有所区别,所以改用 Dynamic Time Warping 算法来衡量两个时间序列的相似度:
    • 首先,将两个curve进行量纲对齐。比如将第一个 curve (\(X=\{x_1,x_2,\dots,x_m\}\))转到第二个 curve (\(Y=\{y_1,y_2,\dots,y_m\}\))的量纲下:\(x^\prime_i=(x_i-\mu_x)\times\frac{\sigma_Y}{\sigma_X}+\mu_Y\)\(x^\prime_i\) 是转化后的值
    • 定义距离矩阵 \(D\)\(D_{i,j}\)代表 \(X\) 的时间点 \(i\)\(Y\) 的时间点 \(j\)的距离,\(d(x_i,y_j)\) 代表 \(x_i\)\(y_j\) 的欧氏距离(也可以用其他距离),\(D_{i,j}\)计算方式如下:
    • \[ D_{i,j}=min\begin{cases} D_{i-1,j}+d(x_i,y_j)\\ D_{i,j-1}+d(x_i,y_j)\\ D_{i-1,j-1}+d(x_i,y_j) \end{cases} \]
    • \(D_{m-1,n-1}\) 代表 curve \(X\) 与 curve \(Y\) 的最小距离,则 correlation factor计算如下:\(CF=max(m,n)/D_{m-1,n-1}\)

总实验

StatuScale 的目标有三个:①降低响应延时、②降低SLO违背率、③维持CPU利用率在目标水平(\(\pm1\%\))。

  • Fig. 9(a) 展示了不同scaler的平均延时和P95延时的分布(Locust可以求得),可以看出StatuScale的延时分布是比较偏低的,均值维持在50~70ms,P99维持在300多ms左右
  • Fig. 9(b) 想展示的与Fig. 9(a)差不多,展示的是四个scaler的延时的累积分布直方图(CDF),P95基本维持在250ms左右,说明几个scaler都很有作用(私以为应该加上一个没有设置采样器的方法作为对比
  • Fig. 9(c) 计算了不同SLO阈值下的违背情况
  • Fig. 9(d) 计算了 correlation factor,说明StatuScale的资源分配的曲线与负载波动(资源需求曲线)很相似。Fig.10 展示了CPU使用(这里难道不应该是分配的CPU吗?)和负载的相似度。表格是对图像结果的数据展示

StatuScale也在Hotel-Reservation上进行了实验,结果比较相似,就不贴上来了。

消融实验

消融 Status Detector Module

Status Detector判断当前负载是否 “stable”,如果 “stable”,则选择用 LightGBM预测负载,然后转换成CPU需求;如果 “unstable”,则用 A-PID 将CPU利用率控制在某个阈值。文章选择了3个变体,衡量它们的延时(为什么还要消融A-PID和LightGBM?为什么不衡量其他指标?)。实验在 Sock-Shop 上做,每组实验做3次

  • StatusScale\(^\Delta\):消融 horizontal scaler
  • StatusScale\(^\circ\):消融 horizontal scaler,load status detector 和 A-PID 控制器
  • StatusScale\(^*\):消融 horizontal scaler,load status detector 和 load prediction(LightGBM)

上述实验说明了 load status detector 对 load prediction的影响很大(StatusScale\(^\circ\)

消融 Scaling Modes

vertical scaling 虽然能细粒度调节资源,但依然受限于单个机器硬件;horizontal scaling 又容易造成资源浪费。文章设计了2个变体,实验在 Sock-Shop 上做,每组实验做3次,但是加入了CPU使用率的对比:

  • StatuScale\(^\square\):只使用 vertical scaling
  • StatuScale\(^*\):只使用 horizontal scaling

可以看出,horizontal scaling (StatuScale\(^*\))确实能最大限度降低延时,但是CPU资源利用率偏低;vertical scaling(StatuScale\(^\square\))很难保证延时,但是CPU利用率高;StatusScale相当于在两者间做了均衡。

[1] Alim Ul Gias, et.al. 2019. ATOM: Model-Driven Autoscaling for Microservices. In 2019 IEEE 39th ICDCS. 1994–2004. https://doi.org/10.1109/ICDCS.2019.00197

[2] https://github.com/alibaba/clusterdata/blob/master/cluster-trace-v2018/trace_2018.md

题目:ElasticRec: A Microservice-based Model Serving Architecture Enabling Elastic Resource Scaling for Recommendation Models

来源:ISCA 2024

作者:韩国科学技术院

摘要

推荐系统(RecSys)广泛应用在许多线上服务中,为增加RecSys的推理时的吞吐量,数据中心通常对RecSys进行模型级别(model-wise)的资源管理。然而,RecSys中不同模块有着异构的资源需求,比如:

  • RecSysMLP模块 对于计算资源的需求高
  • RecSysEmbedding Table模块对于内存资源的需求高

如果将RecSys模型看作一个整体进行服务部署、资源分配等操作,势必会造成大量的资源浪费;但对RecSys模型中的每一层进行资源管理又是非常具有挑战性的(这里类似于单体应用和微服务应用的关系)。因此,作者提出了ElasticRec,一种基于微服务架构的推荐系统细粒度资源分配方法,目标是减少部署时的内存消耗,提升RecSys的吞吐量。

背景

文章背景主要介绍了深度推荐模型的结构,以及深度推荐模型如何集成到Kubernetes集群中,为用户提供线上推理服务。

深度推荐模型(DLRM)

如图所示,深度推荐模型(DLRM)包含3个主要组件:

  • Bottom MLP
    • 输入:dense input(比如 用户年龄)
    • 输出:dense output(高维特征)
    • 类型:计算敏感型
  • Embedding Table
    • 输入:多个 sparse input(比如 商品ID)
    • 输出:dense output(高维特征)
    • 类型:内存敏感型
    • 作用:根据稀疏输入得到Embedding Table中的高维特征。一般来说,一次查询中所有的sparse input得到的dense output会执行pool操作进行池化,得到单个dense output
  • Top MLP
    • 输入:Bottom MLP的输出 拼接 Embedding Table的输出
    • 输出:给商品打分

在生产环境中,由于商品(item)的种类非常多,比如Amazon有数亿的商品种类。Embedding Table为每个商品种类都维护了一个特征,导致Embedding Table的大小可以达到几十GB,相比于Bottom MLP,有几点需要关注:

  1. Embedding Table对于计算不敏感,即pool操作并不需要太多计算资源;相反,对于内存带宽限制极为敏感,特别是有非常多的dense output需要pool
  2. DLRM中,Embedding Table通常有多个,对内存的压力是极大的

模型服务架构(Model Serving Architectures)

模型容器化

这篇文章关注的是DLRM的推理。在线上应用中,DLRM被打包成镜像,镜像中包含了模型参数以及常用的机器学习库,以容器的方式运行在Kubernetes集群中,如Fig. 2(a)所示。

模型的自动伸缩

Kubernetes是一个容器编排工具,它能管理容器的生命周期,对容器进行自动化调度、资源分配。

吞吐量是一个衡量在线服务性能的指标,单位是QPS(query per second),吞吐量越高,代表在线服务单位时间内处理请求的数量

对于DLRM而言,处理单个请求的时间基本可以看作变化很小的,那么为提高吞吐量,可以采用Kubernetes的水平pod伸缩(Horizontal Pod Autoscaling,HPA)机制对DLRM进行副本复制,如Fig. 2 (b) 所示,增加DLRM的副本数可以提高系统的并行处理能力,从而增大吞吐量

然而,HPA 是一种 model-wise 的分配方案,它将整个DLRM模型进行复制,包括内存占用非常大的Embedding Table模块,但实际上Embedding Table并不涉及复杂的计算,所以一般不是(不是绝对)吞吐量的瓶颈所在。无脑进行HPA势必会造成大量内存浪费。

模型的硬件约束

因为 大型 DLRMEmbedding Table 通常有几十GB,将 Embedding Table 全部放在高内存带宽的GPU中通常不太可行,所以会退而求其次的使用如下两种方式:① CPU-only ② CPU-GPU。

  • CPU-onlyBottom MLPEmbedding Table 均运行在CPU
  • CPU-GPUBottom MLP 运行在GPU, Embedding Table 均运行在CPU

可以看到, Embedding Table 都运行在CPU关联的内存上,如果能优化这一部分的内存使用,就可以提升DLRM的最大副本数量,从而提高系统的吞吐量。

动机

文章的动机从两点出发,阐述为什么现有的资源分配方案会导致次优性能(sub-optimal performance):

  1. RecSys的不同模块具有异构资源需求
  2. Embedding Table不同部分的访问频率相差极大

异构资源需求

Fig. 3 (a) 展示了三个推荐模型(RM1,RM2,RM3)的不同模块Bottom MLPEmbedding Table 在 ①计算复杂度(FLOPS)和 ②内存大小 上的差别。可以看出:Bottom MLP在计算复杂度上远高于 Embedding Table,但是内存占用远远小于 Embedding Table

Fig.3 (b) 展示了三个推荐模型的不同模块在两种硬件约束下的延时占比。原文虽然没有讨论,但可以推测,在CPU-only架构下,推理的延时开销主要集中在Bottom MLP的计算;在CPU-GPU架构下,延时的开销在于将 Embedding Table 的数据从CPU传输到GPU

此外,文章还讨论了吞吐量的瓶颈问题,Fig. 4展示了作者的想法,实际上Bottom MLP 计算开销大,内存占用小,适合扩充副本来提升吞吐量;而 Embedding Table 本身吞吐量就很大,但是内存占用大,所以对于副本扩充应该谨慎。

当然,作者还通过实验,验证了不同硬件约束下不同模块的吞吐量存在差异,来支撑上述想法:

综上所述,文章说明RecSys中不同模块的异构资源需求,以及吞吐量的差异。为后续对不同模块分别进行切分提供了实验依据

Embedding Table 的倾斜访问模式

这一个实证分析较为简单,主要验证Embedding Table不同索引的访问频率的差异,如Fig. 6所示,在三个数据集中,大部分的访问集中在少数的索引(热点嵌入,hot embeddings)

换句话说,将资源选择性地分配给 hot embeddings,可以在提升吞吐量的同时,达到节省资源的目的

方法设计

Fig. 7展示了ElasticRec的系统架构,整体思路分为三个模块:

  1. 部署开销估计
  2. 基于动态规划(DP)的Embedding Table划分
  3. 推理时重索引

部署开销估计

前置处理:将Embedding Table的index按照访问频率从大到小排序,hot embeddings集中在最左侧

根据动机中提到的“将资源选择性地分配给 hot embeddings,可以在提升吞吐量的同时,达到节省资源的目的”,文章将Embedding Table切分为shards,每个shard包含了Embedding Table的一部分index。那么如何切分Embedding Table,以及如何评估切分策略的优劣呢?

文章首先定义了如何评估切分策略的优劣,切分策略的优劣由固定吞吐量的前提下,所有shard的内存开销决定。用最少的内存达到目标吞吐量,评估算法如下:

算法入口为COST(k, j),表示范围为[k,j]的shard的内存消耗,这个内存消耗由两部分组成:

  • REPLICAS(k,j): 计算特定吞吐量下,shard应该被分配的副本数量
    1. 计算shard被访问的概率probability和被访问的向量数nsprobability = CDF(j)- CDF(k)ns = probability × nt
    2. 估计单个shard的副本在给定的访问向量数ns下能达到的QPSestimated QPS = QPS(ns),这里的QPS()是一个回归模型,可以线下测试得到
    3. 估计达到目标吞吐量target_traffic需要的副本数,num_replicas = target_traffic/estimated_QPS
  • CAPACITY(k,j):对于shard的每一个副本,计算存储embedding的内存开销
    • 直接计算shard的副本大小:(j − k +1)×(size_of_a_single_embedding_vector)

这里需要特别注意回归模型QPS(),输入的参数除了需要访问的向量数ns外,还需要考虑向量本身的大小,如下图所示,QPS既与向量数有关,也与向量本身维度相关

基于DP的Embedding Table分区算法

在上一节中,当给定一个shard划分策略,我们可以评估每个shard在目标QPS下的内存开销,进而可以尝试找到给定QPS下最小内存开销的分区策略

这里文章有一个前提,即无论怎么划分,所有shard的目标QPS都是一样的,这样可以保证不存在多余的资源浪费

这个分区问题有两个操作:① 分多少shard ②每个shard的范围。假设我们用\(Mem[num_{shards}][x]\)表示Embedding Table\([0:x]\)范围下,分区数量为\(num_{shards}\)的最小内存开销。那么这个问题具有两个明显的特性:最优子结构重叠子问题

  • 最优子结构\(Mem[num_{shards}][x]\)可以由子问题的最优解构造而来,假设最后一个shard的大小为\(m\),那么可以简化表示为\(Mem[num_{shards}][x] = min(Mem[num_{shards}-1][x-m] + COST(m))\),比如下图中,\(Mem[3][5]=min(Mem[2][5-m]+COST(m))=Mem[2][3]+COST(4,5)=4\)

  • 重叠子问题:求解过程中会反复遇到相同的子问题,需要将结果存储到表中,避免重复计算

因此,自然而然可以想到用动态规划(DP)求解,文章给出的算法如下:

最后根据最小内存开销回溯DP表可以得到分区策略

推理时重索引

这个部分的重点在于分区后,如何根据原始Embedding Table的index ID找到对应的shard中的某个embedding,以及确认index分别属于哪个input(为提高吞吐量,一个query包含了多个input)。

文章提出了两种索引:

  • indices:存储一次query要从Embedding Table中查找的具体ID。
  • offset:指示每个input对应的的indices中的起始位置。

对于Fig. 11(a)未分区前,一个query(indices)包含了两个input,分别为红色的[1, 7],灰色的[3,4,8],offset表示第一个input要从indices第0个元素算起,第二个input从indices第1个元素算起,即input1为[1,7],input2为[3,4,8]

对于Fig. 11(b)分区后,首先计算中间的indices,具体为根据indices中的index计算应该被分到哪个分区(减去之前分区的大小),可以很容易把indices划分为shard A 的[1,3,4]和shard B 的[7,8],同时把offset进行划分(基于indices)

对于Fig. 11(b)分区后,由于各shard索引重置了,所以需要在中间的indicesoffset的基础上,进行重索引,具体为减去之前分区的大小,比如Fig. 11(b) 中 shard B的[7,8]减去shard A的大小后,变成[1,2]。这样便可以直接从各shard中查找embedding了

最终部署

因为Kubernetes的 horizontal pod autoscaling 提供了弹性伸缩时参考指标的接口,对于:

  • Embedding Table的shard,文章直接将每个shard可以承受的最大吞吐量作为参考指标,到达最大吞吐量则扩容
  • Bottom MLP,则采用SLA的65%作为扩容的阈值

(这里不是很明白为什么要采用不同的阈值指标,为什么不都用吞吐量?)

实验部分

文章分别验证了 ElasticRecCPU-only 以及 CPU-GPU 环境下的性能表现:

  • CPU-only:本地集群(1 master + 11 worknode)
  • CPU-GPU:云集群(20 CPU-GPU nodes,GPU为 NVIDIA Tesla T4,节点间有31Gbps的带宽)

DLRM模型的开发是基于facebook的dlrm1。Kubernetes自动伸缩器采用的custom metric来自prometheus。SLA设置为400ms。而对于验证的DLRM模型,作者选择了三个先进的推荐模型(RM1, RM2, RM3),并在RM1的基础上进行参数改造,设置了很多个microbenchmarks,参数变动和三个RM模型如下:

其中 Locality 指标 \(P\) 代表多少请求分布在前10%的热点向量,\(P\)越大,代表请求分布越集中在热点。

Microbenchmarks实验

文章一开始探讨了不同RM配置下的内存消耗:

  1. MLP size:随着MLP size的扩大,计算复杂度提高,延时会逐渐违背SLA。Model-wise的方法会扩容整个模型,而ElasticRec只需要扩容内存开销极小的Bottom MLP,所以内存消耗差距很大
  2. Locality:访问越集中在热点,ELasticRec效果越好,因为只需要扩容热点那一部分
  3. Number of tables:系统中可能不止一个Embedding Table,比如用户ID表和商品ID表,随着表数量的增多,ELasticRec对每个表都进行分片,降低扩容时的内存开销
  4. Number of shards:分片数量并不是越多越好,因为每个shard会有最小内存消耗(程序运行必须的消耗),即算法1中的min_mem_alloc,所以当分片数量大于4后,效果没有那么明显了

不同 RM 在 CPU-only 环境下的性能

文章接下来在CPU-only环境下,比较了ElasticRecModel-wise方法在三个推荐模型(RM1,RM2,RM3)的性能表现。以下实验都是在吞吐量为100QPS的下进行的

  1. Fig. 13 展示了内存消耗的对比
  2. Fig. 14 展示了内存利用率的对比,这里的内存利用率是作者自己定义的,表示当前shard在前1000个请求中被访问的embedding的比例,可以看出Model-wise只有一个shard(S1),并且内存利用率很低,对整个shard扩容显得很不值;ElasticRec有4个shard,前3个内存利用率很高(高频shard),最后一个非常低。
  3. Fig. 15 展示了两种方法在吞吐量为100QPS所需要消耗的CPU服务器数量。这里我不太清楚是如何算出需要消耗的CPU服务器数量的(一般算的是虚拟CPU使用量?)

不同 RM 在 CPU-GPU 环境下的性能

CPU-GPU环境下,ElasticRec将 MLP 模块设计为 GPU-centric 容器,将 Embedding Table 模块设计为只用 CPU 的容器;Model-wise 则将CPU和GPU都分配给一个容器。以下实验都是在吞吐量为100QPS的下进行的,实验效果如下

动态输入流量实验

前几个实验都是在固定吞吐量(QPS=100)下进行的,这个实验动态调整流量大小,然后观察ElasticRecModel-wise的吞吐量表现、资源消耗以及尾部延时。

流量大小先逐步增大,然后降到一个固定值

可以发现,ElasticRec的吞吐量、内存消耗以及SLA违背都低于对比方法

与 GPU Embedding Caches 的对比

GPU Embedding Caches 方法是之前的一个工作,原理是把Embedding Table的hot embeddings存到GPU缓存中,能缓解CPU内存带宽压力(减少CPU与GPU的交互)

文章对比了 Model-wiseModel-wise (cache)ElasticRec 在200 QPS 的内存消耗,ElasticRec的内存消耗仍然是最低的

这里我很好奇为什么不比较延时?ElasticRec延时应该比不过Model-wise (cache),毕竟CPU和GPU交互需要时间。