使用统计方法在AMD GPU上使用JAX Profiler可靠地比较大型生成AI模型中的算法性能

news2024/11/15 5:42:49

Using statistical methods to reliably compare algorithm performance in large generative AI models with JAX Profiler on AMD GPUs — ROCm Blogs

摘要

本文提供了一份详细的指南,介绍如何在JAX实现的生成AI模型中测量和比较各种算法的性能。利用JAX Profiler和统计分析,本文展示了如何可靠地评估关键步骤并比较AMD GPU上算法的性能。

引言

在GPU加速计算的动态领域,追求最佳性能和效率需要有效的性能分析技术。性能分析通过仔细检查执行时间、内存利用率和内核占用率等指标,提供了对基于GPU的应用程序行为和性能特征的全面了解。这对于大规模生成AI模型尤为重要,因为优化性能可以显著提升最终用户体验和收入来源。通过利用性能分析技术,开发人员可以找出低效之处,深入了解运行时行为,并最终优先考虑战略性优化工作,从而带来显著的性能提升。
JAX是谷歌的一款开源数值计算库(尽管不是官方的谷歌产品),由于其能够利用硬件加速器和自动微分的能力,正在生成AI领域引起广泛关注。最初用于高性能机器学习研究,JAX的函数式编程方法和对GPU及TPU的支持使其成为构建和部署大型语言模型(LLMs)和其他前沿生成AI应用的首选。值得注意的是,像 X.AI这样的公司利用JAX开发开源模型如Grok-1,进一步推动了该库在生成AI领域的流行。凭借其性能、灵活性及其适合先进AI模型开发和部署的特点,JAX继续在受欢迎程度上不断攀升。

ROCm博客系列此前已探索过各种性能分析工具,如 *rocprof*,可以用于在AMD GPU上分析模型性能,还有针对TensorFlow和PyTorch的框架特定性能分析工具。尽管JAX的官方页面涵盖了其性能分析工具的基本用法,本教程深入探讨了更高级的技术。例如,它解释了在评估算法时,如何在考虑到大量随机噪声的情况下确定一种算法是否显著优于另一种算法。本文通过统计分析和假设检验,展示了如何可靠地测量和比较在大型语言模型中执行相同步骤的不同算法的性能。具体而言,它比较了在JAX-based生成预训练变换器(GPT)模型的`CausalSelfAttention`组件中,使用`einsum`与`matmul`实现两个矩阵乘法步骤的性能。(参见博客中关于在JAX中实现GPT模型的文章)。要了解更多关于`einsum`的信息,请访问这篇博客。 

实现

要实现此代码示例,请首先设置ROCm环境,并安装必要的软件包和Python脚本。值得注意的是,该代码示例是平台无关的,这意味着只要加速计算平台和Python包配置正确,它就兼容AMD GPU以及其他GPU或TPU。

环境设置

按照以下步骤为本教程设置运行环境:

1. 在Linux shell中使用下面的代码拉取并运行docker容器:

docker run -it --ipc=host --network=host --device=/dev/kfd --device=/dev/dri \
           --group-add video --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \
           --name=nanogpt rocm/pytorch:rocm6.1_ubuntu22.04_py3.10_pytorch_2.1.2 /bin/bash

2. 在docker容器内运行以下代码,以安装必要的Python包并配置XLA环境变量:

python3 -m pip install --upgrade pip
pip install optax==0.2.2 flax==0.8.2 transformers==4.38.2 tiktoken==0.6.0 datasets==2.17.1 perfetto==0.7.0 matplotlib==3.8.4 scipy==1.13.0
python3 -m pip install https://github.com/ROCmSoftwarePlatform/jax/releases/download/jaxlib-v0.4.26/jaxlib-0.4.26+rocm610-cp310-cp310-manylinux2014_x86_64.whl
python3 -m pip install https://github.com/ROCmSoftwarePlatform/jax/archive/refs/tags/jaxlib-v0.4.26.tar.gz
pip install numpy==1.22.0
export XLA_FLAGS="--xla_gpu_autotune_level=0"

3. 使用以下命令从 ROCm/rocm-blogs GitHub 存储库下载用于该博客的文件。

git clone https://github.com/ROCm/rocm-blogs.git
cd rocm-blogs/blogs/artificial-intelligence/nanoGPT-JAX

4. 将`nanoGPT-JAX`文件夹中的`model.py`和`sample.py`脚本替换为当前博客在GitHub上*src*文件夹中的对应文件。具体参考此链接。

特别需要注意的是,对`model.py`文件的修改如下面代码块所示。新添加的两行代码使用`jax.named_scope`为两个矩阵乘法步骤注释唯一名称,这是一个将用户指定名称纳入JAX名称堆栈的上下文管理器。程序随后使用指定名称提取这些步骤的相关性能数据。该技巧对于快速将同类型操作的日志映射到应用程序或模型中的每个步骤非常宝贵,因为默认的日志名称可能会在同类型操作之间非常相似或令人困惑。下面的代码块封装了两个不同的矩阵乘法步骤,并分别为它们指派了不同的范围名称`attn_q_k`和`attn_att_v`。

class CausalSelfAttention(nn.Module):
    config: GPTConfig

    @nn.compact
    def __call__(self, x, train=False, rng1=None, rng2=None):
        assert self.config.n_embd % self.config.n_head == 0
        B, T, C = x.shape # batch size, sequence length, embedding dimensionality (n_embd)
        # calculate query, key, values for all heads in batch and move head forward to be the batch dim
        q, k, v  = jnp.split(nn.Dense(self.config.n_embd * 3, name="c_attn")(x), 3, axis=-1)
        k = k.reshape(B, T, self.config.n_head, C // self.config.n_head).swapaxes(1, 2) # (B, nh, T, hs)
        q = q.reshape(B, T, self.config.n_head, C // self.config.n_head).swapaxes(1, 2) # (B, nh, T, hs)
        v = v.reshape(B, T, self.config.n_head, C // self.config.n_head).swapaxes(1, 2) # (B, nh, T, hs)
+       with jax.named_scope("attn_q_k"):
+           att = (jnp.einsum('bhts,bhqs->bhtq', q, k, optimize=True) if self.config.use_einsum else jnp.matmul(q, k.swapaxes(-2, -1))) * (1.0 / jnp.sqrt(k.shape[-1]))
-       att = (jnp.einsum('bhts,bhqs->bhtq', q, k, optimize=True) if self.config.use_einsum else jnp.matmul(q, k.swapaxes(-2, -1))) * (1.0 / jnp.sqrt(k.shape[-1]))
        mask = jnp.tril(jnp.ones((T, T))).reshape((1, 1, T, T))
        att = jnp.where(mask == 0, float('-inf'), att)
        att = nn.softmax(att, axis=-1)
        att = nn.Dropout(self.config.dropout, name='attn_dropout', deterministic=not train)(att, rng=rng1)
+       with jax.named_scope("attn_att_v"):
+           y = jnp.einsum('bhts,bhsq->bhtq', att, v, optimize=True) if self.config.use_einsum else jnp.matmul(att, v)   # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
-       y = jnp.einsum('bhts,bhsq->bhtq', att, v, optimize=True) if self.config.use_einsum else jnp.matmul(att, v)   # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
        y = y.swapaxes(1, 2).reshape(B, T, C)  # re-assemble all head outputs side by side
        # output projection
        y = nn.Dense(self.config.n_embd, name='c_proj')(y)
        y = nn.Dropout(self.config.dropout, name='resid_dropout', deterministic=not train)(y, rng=rng2)

        return y

对于`sample.py`文件的主要修改包括使用`jax.profiler.start_trace()`和`jax.profiler.stop_trace()`包裹负责运行基于JAX的GPT模型的推理的函数。这将记录每个生成样本的跟踪信息。或者,您可以使用`jax.profiler.trace()`上下文管理器来捕获跟踪,具体可参见本指南。每个样本的性能分析输出将存储在单独的文件夹中,这使得分析个别跟踪更为方便。

for i in range(num_samples): 
+   jax.profiler.start_trace(profile_dir+f'_{i}')
    output = generate([jnp.array(start_ids)], seed+i)
+   jax.profiler.stop_trace()
    print(f'\nGenerated output __{i}__: \n__________________________________\n{decode(output[0].tolist())}\n__________________________________')

使用不同的矩阵乘法算法对GPT模型进行性能分析

为了演示性能分析,本示例比较了`einsum`和`matmul`这两种在注意力计算步骤中执行矩阵乘法的内置方法。`use_einsum`标志控制了是选择`einsum`还是`matmul`进行矩阵乘法。运行以下命令以收集这两种不同算法的性能分析输出:

# Generate profiling output using matmul
python sample.py --init_from='gpt2' --max_new_tokens=50 --start="The weather today is" --num_samples=10 --profile_dir="trace_file_matmul"

# Generate profiling output using einsum
python sample.py --init_from='gpt2' --max_new_tokens=50 --start="The weather today is" --num_samples=10 --profile_dir="trace_file_einsum" --override_args="{'use_einsum':True}"

每条命令调用`sample.py`文件生成10个样本,每个样本包含最多50个新生成的tokens。这会生成20个文件夹(每种算法10个文件夹,每个生成的样本一个文件夹),这些文件夹包含性能分析的输出。在每个文件夹中,性能分析输出存储在一个压缩的`.gz`文件中。在docker终端中运行以下命令来解压输出: 

for i in {0..9}; do
    gzip -d trace_file_einsum_$i/plugins/profile/202*/*.json.gz
    gzip -d trace_file_matmul_$i/plugins/profile/202*/*.json.gz
done

统计分析与两种算法的性能测试

现在,你可以读取剖析数据并进行统计分析。对于每个迭代(对应于每种算法生成的一个样本),程序比较两种算法在矩阵乘法执行时间(以纳秒为单位)分布上的差异。可以使用箱线图来直观地检查差异。Wilcoxon秩和检验(Mann-Whitney U检验)用来确定位置参数(如均值和中位数)是否显著不同。较短的执行时间表示更好的性能。

下面的代码块导入了分析所需的包,并定义了绘制箱线图的函数。

import glob
from perfetto.trace_processor import TraceProcessor
from scipy.stats import ranksums
import matplotlib.pyplot as plt


def plot_boxplot(df1, df2, columns1, columns2=None, df1_lab='matmul', df2_lab='einsum'):
    """
    Plot boxplots for specified columns in two DataFrames. This function will 
    be used to compare the distribution of running time for the two algorithms
    we profiled.

    Args:
    df1 (pandas.DataFrame): First DataFrame.
    df2 (pandas.DataFrame): Second DataFrame.
    columns1 (list): List of column names from the first DataFrame to plot.
    columns2 (list): List of column names from the second DataFrame to plot.
    df1_lab (string): Label for df1 in the plot.
    df2_lab (string): Label for df2 in the plot.
    """
    if columns2 is None:
        columns2 = columns1
    # Combine data from both DataFrames
    data = [df1[col] for col in columns1] + [df2[col] for col in columns2]
    
    # Create labels for boxplots
    labels = [df1_lab + '_' + col for col in columns1] + [df2_lab + '_' + col for col in columns2]
    
    # Plot boxplots
    plt.figure(figsize=(10, 6))
    plt.boxplot(data, labels=labels)
    plt.xlabel('Algorithms')
    plt.ylabel('Time in nanoseconds')
    plt.title('Performance comparison on the scale of nanoseconds')
    plt.xticks(rotation=45)
    plt.grid(True)
    plt.show()

程序随后比较了每次样本生成迭代中两种算法的执行时间。它在SQL查询中使用`where display_value like "%attn_q_k%"来过滤在第一个named_scope`中的操作。你可以修改SQL查询以探索不同的列并计算感兴趣的指标。

程序省略了第一次迭代,因为第一次迭代包括编译时间,这会使比较失真。它打印了每种算法的执行时间的均值和标准偏差,以及数据框的形状,以确保剖析器和SQL查询捕获了所有事件。例如,对于包含12层的模型,并且每个样本最多生成50个新的token(导致对模型的最多50次函数调用),应捕获最多`12*50=600`次矩阵乘法事件。

最后,程序打印了Wilcoxon秩和检验的统计量和p值,该检验评估两种算法的执行时间分布的位置参数(如均值和中位数)是否显著不同。尽管t检验广泛用于检验两种总体的均值是否相等,但由于样本中存在许多异常值,因此示例使用秩基非参数检验。这些异常值可能显著降低t检验的可靠性。

for i in range(1, 10):
    # Process the profiling data for matmul
    tp = TraceProcessor(trace=glob.glob(f'trace_file_matmul_{i}/plugins/profile/202*/*.json'))
    # SQL query to get the operations enclosed by the named_scope
    query_text='''INCLUDE PERFETTO MODULE slices.slices;
    WITH arg_sets_0 AS (
        SELECT DISTINCT arg_set_id, display_value
        FROM args
        WHERE key = 'args.name'
    )
    SELECT name, display_value, dur
        FROM _slice_with_thread_and_process_info
        INNER JOIN arg_sets_0 ON arg_sets_0.arg_set_id = _slice_with_thread_and_process_info.arg_set_id
    where display_value like "%attn_q_k%"
    '''
    # Query the profiling data and convert to dataframe
    qr_matmul = tp.query(query_text).as_pandas_dataframe()
    # Process the profiling data for einsum
    tp = TraceProcessor(trace=glob.glob(f'trace_file_einsum_{i}/plugins/profile/202*/*.json'))
    # Query the profiling data and convert to dataframe
    qr_einsum = tp.query(query_text).as_pandas_dataframe()
    print(f'###########i={i}###########')
    print('#'*30)
    # Print out the mean, standard dev. and shape for each algorithm
    print(f'Matmul: Mean={qr_matmul.dur.mean()}, std. dev.={qr_matmul.dur.std()}, shape of df:{qr_matmul.shape}')
    print(f'Einsum: Mean={qr_einsum.dur.mean()}, std. dev.={qr_einsum.dur.std()}, shape of df:{qr_einsum.shape}')
    plot_boxplot(qr_matmul, qr_einsum, ['dur'])
    stat, p = ranksums(qr_matmul['dur'], qr_einsum['dur'])
    print(f'Test statistic={stat}, p_val={p}')

下面是两次迭代的截断输出,所有九次迭代都观察到了相同的模式。

###########i=1###########
##############################
Matmul: Mean=6461.875, std. dev.=504.8818364954699, shape of df:(600, 3)
Einsum: Mean=5813.346666666666, std. dev.=455.80420754410954, shape of df:(600, 3)
Test statistic=20.22982266255362, p_val=5.349499343834845e-91

###########i=2###########
##############################
Matmul: Mean=6293.076666666667, std. dev.=514.1309448993132, shape of df:(600, 3)
Einsum: Mean=5797.615, std. dev.=397.86885546863283, shape of df:(600, 3)
Test statistic=16.932946075063718, p_val=2.5717953759559878e-64

基于结果,可以明显看出,对于矩阵乘法算法`einsum`比`matmul`在计算`query`和`key`矩阵之间的矩阵乘法时显著更快。但对于在`attention`和`value`矩阵之间的矩阵乘法时,`matmul`如何表现呢?结果显示在下面的代码块中:

for i in range(1, 10):
    # Process the profiling data for matmul
    tp = TraceProcessor(trace=glob.glob(f'trace_file_matmul_{i}/plugins/profile/202*/*.json'))
    # SQL query to get the operations enclosed by the named_scope
    query_text='''INCLUDE PERFETTO MODULE slices.slices;
    WITH arg_sets_0 AS (
        SELECT DISTINCT arg_set_id, display_value
        FROM args
        WHERE key = 'args.name'
    )
    SELECT name, display_value,dur
        FROM _slice_with_thread_and_process_info
        INNER JOIN arg_sets_0 ON arg_sets_0.arg_set_id = _slice_with_thread_and_process_info.arg_set_id
    where display_value like "%attn_att_v%"
    '''
    # Query the profiling data and convert to dataframe
    qr_matmul = tp.query(query_text).as_pandas_dataframe()
    # Process the profiling data for einsum
    tp = TraceProcessor(trace=glob.glob(f'trace_file_einsum_{i}/plugins/profile/202*/*.json'))
    # Query the profiling data and convert to dataframe
    qr_einsum = tp.query(query_text).as_pandas_dataframe()
    print(f'###########i={i}###########')
    print('#'*30)
    # Print out the mean, standard dev. and shape for each algorithm
    print(f'Matmul: Mean={qr_matmul.dur.mean()}, std. dev.={qr_matmul.dur.std()}, shape of df:{qr_matmul.shape}')
    print(f'Einsum: Mean={qr_einsum.dur.mean()}, std. dev.={qr_einsum.dur.std()}, shape of df:{qr_einsum.shape}')
    plot_boxplot(qr_matmul, qr_einsum, ['dur'])
    stat, p = ranksums(qr_matmul['dur'], qr_einsum['dur'])
    print(f'Test statistic={stat}, p_val={p}')

下面是两次迭代的截断输出,所有九次迭代都观察到了相同的模式。

###########i=1###########
##############################
Matmul: Mean=5204.543333333333, std. dev.=882.6151202759834, shape of df:(600, 3)
Einsum: Mean=6360.556666666666, std. dev.=373.461514250933, shape of df:(600, 3)
Test statistic=-21.986424230986046, p_val=3.884153635651101e-107

###########i=2###########
##############################
Matmul: Mean=5145.61, std. dev.=876.5247080600369, shape of df:(600, 3)
Einsum: Mean=6396.01, std. dev.=381.7892458942073, shape of df:(600, 3)
Test statistic=-22.450480914300588, p_val=1.2659476932444539e-111

这次,令人惊讶的是,`matmul`显著比`einsum`更快。这表明一种矩阵乘法算法并不总是优于另一种。矩阵的大小、形状和其他操作(如矩阵转置)等因素可能会影响速度。这突显了在应用或模型关键步骤中选择最佳算法时使用剖析技术的重要性。另外,如果你检查同一算法在箱线图中的数据点范围,可能会注意到许多异常值。这就是为什么在得出有效结论时,统计分析和适当的方法是如此重要的原因。本例中也使用了秩基检验而非经典的t检验,因为后者通常对异常值敏感。

总结

在剖析应用或模型性能时应应用稳健的统计分析和测试,以确保随机噪声的影响不会损害我们结论的有效性。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2083845.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python编码系列—Docker容器的高效使用与实战应用

🌟🌟 欢迎来到我的技术小筑,一个专为技术探索者打造的交流空间。在这里,我们不仅分享代码的智慧,还探讨技术的深度与广度。无论您是资深开发者还是技术新手,这里都有一片属于您的天空。让我们在知识的海洋中…

前后端分离项目实战-通用管理系统搭建(前端Vue3+ElementPlus,后端Springboot+Mysql+Redis)第八篇:Tab标签页的实现

天行健,君子以自强不息;地势坤,君子以厚德载物。 每个人都有惰性,但不断学习是好好生活的根本,共勉! 文章均为学习整理笔记,分享记录为主,如有错误请指正,共同学习进步。…

【Selenium】UI自动化实践——输入验证码登录

文章目录 实战题目解题方案 实战题目 使用pythonselenium实现输入验证码的UI自动化。登录页面如图: 解题方案 验证码登录需要导入相关模块和库,本文使用的是opencv和ddddocr模块组合,导入方式采用pip3 install opencv-python、pip3 insta…

JMeter 工具安装以及简单使用

一、安装以及汉化 傻瓜式JMeter下载和环境配置及永久汉化-CSDN博客https://blog.csdn.net/weixin_45608163/article/details/136528719 二、发送GET请求 配置请求头: 配置该线程组的请求: 放在线程组统计,下面请求则共享配置

深度强化学习算法(三)(附带MATLAB程序)

深度强化学习(Deep Reinforcement Learning, DRL)结合了深度学习和强化学习的优点,能够处理具有高维状态和动作空间的复杂任务。它的核心思想是利用深度神经网络来逼近强化学习中的策略函数和价值函数,从而提高学习能力和决策效率…

光性能 -- 光功率平坦度

什么是光功率平坦度? 光功率平坦度指的是,光放各单波功率值与所有波平均值的功率差。 通过MCA(多通道光谱分析单元)扫描OMS(光复用段)上的所有单波光功率,计算经过光放的所有波长的功率平均值&#xff0…

OHIF viewers

OHIF Viewer 是一个开源的 DICOM(数字成像和通信医学)图像查看器,旨在为医疗影像学提供一个灵活且功能强大的解决方案。以下是 OHIF Viewer 的详细介绍,包括发展史、特点、优势、应用及目的等方面的信息。 1. 介绍 OHIF Viewer 是…

一个初始化的服务器,需要配置的相关软件以及环境(cuda、torch、conda)

文章目录 一个刚初始化的服务器需要下载的应用google chromeghelp 解压安装包解压大型zip文件 更新nvidia的驱动pycharm设置conda相关下载condaconda换源 torch相关安装torch包,浏览器下载包安装pytorch常用包安装 导包的方法 一个刚初始化的服务器需要下载的应用 …

【AI】:探索在图像领域的无限可能

欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 图像识别与分类的飞跃图像生成与创造的艺术图像增强与修复的神奇图像搜索与理解的智能图像分析与挖掘的洞察图形生成技术1. 生成对抗网络(GANs)2. 卷积神经网络(CN…

Jenkins+Docker | K8S虚拟化实现网站自动部署 简单流程 未完待续,,

目录 大纲 1.Jenkins 的设置与 Docker、Kubernetes 集成指南 1. 创建新的Pipeline项目或Freestyle项目 1.1 创建Pipeline项目 1.2 创建Freestyle项目 2. 配置源代码管理 2.1 配置Git作为源代码管理工具 3. 配置构建触发器 4. 配置构建步骤 4.1 对于Pipeline项目 4.2…

Threadlocal+拦截器+JWT实现登录

很多数据库表都会有创建时间和修改时间,这个可以用mp的自动填充来实现。 也有修改人和更新人的字段,用户登录进来后,修改数据如何拿到修改人呢?每次操作不能把操作人的信息都携带者,那么如何拿到修改人的数据&#xf…

数学建模赛前备赛——模拟退火算法

一.什么是智能优化算法 智能优化算法本质上是一个优化算法,它通过不断优化模型的参数,使得系统表现达到最优,常见的只能优化算法有很多,比如说蚁群算法,遗传算法以及我们今天的主角——模拟退火算法。 二.模拟算法的前身——爬山算法 爬山算法是一种简…

【Python入门】第1节 基础语法

📖第1节 基础语法 ✅字面量✅注释✅变量✅数据类型🧊数据类型转换 ✅标识符✅运算符✅字符串扩展🧊字符串的三种定义方式🧊字符串拼接🧊字符串格式化🧊格式化的精度控制🧊字符串格式化方式2&…

equals与== 区别,全面总结如何使用(Java)

先理解JVM内存模型 虚拟机栈:JVM 运行过程中存储当前线程运行方法所需的数据, 指令、 返回地址本地方法栈:Java程序自动调用底层C/C函数库程序计数器:当前线程执行的字节码的行号指示器堆:存放我们申请的对象&#xff…

【Python 千题 —— 基础篇】入门异常处理

Python 千题持续更新中 …… 脑图地址 👉:⭐https://twilight-fanyi.gitee.io/mind-map/Python千题.html⭐ 题目描述 题目描述 编写一个程序,要求在处理用户输入时捕获各种异常情况,并为每种异常提供相应的处理方式。具体要求如下: 定义一个函数 divide_numbers(),它接…

php mail函数配置SMTP服务器发邮件的指南!

php mail函数安全性考虑?PHP mail()函数漏洞利用技巧? 在使用PHP进行开发时,发送邮件是一个常见的需求。使用php mail函数配置SMTP服务器发邮件,则是实现这一需求的有效途径。AokSend将详细探讨如何通过php mail函数来配置SMTP服…

Density-invariant Features for Distant Point Cloud Registration 论文解读

目录 一、导言 二、先导知识 1、FCGF 三、相关工作 1、深度学习的点云配准 2、对抗密度变化的方法 3、对比学习 四、GCL方法 1、U型曲线假设 一、导言 该论文来自于ICCV2023,上海交通大学提出的基于组对比学习的方案,来提取密度不变的几何特征&…

【终端IDPS】开源安全平台Wazuh之Wazuh Server

引言 Wazuh是一个开源的、免费的企业级安全监控解决方案,专注于威胁检测、完整性监控、事件响应和合规性。它由部署在受监控系统的端点安全代理和管理服务器组成,服务器收集并分析代理收集的数据。Wazuh支持多平台,包括Windows、Linux、macOS…

Linux学习笔记4 重点!网络排障命令

网络排障命令 命令行下载工具wget wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.20.17.tar.gz wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.20.17.tar.gz 限速下载 wget --limit-rate1M https://mirrors.edge.kernel.or…

【已解决】Vue Duplicate keys detected: ‘[object Object]’

【已解决】Vue Duplicate keys detected: ‘[object Object]’ 在Vue项目开发过程中,我们可能会遇到这样的报错:“Duplicate keys detected: ‘[object Object]’. This may cause an update error.”。这个错误通常发生在Vue的虚拟DOM进行渲染更新时&a…