Llama改进之——分组查询注意力

引言

今天介绍LLAMA2模型引入的关于注意力的改进——分组查询注意力(Grouped-query attention,GQA)1

Transformer中的多头注意力在解码阶段来说是一个性能瓶颈。多查询注意力2通过共享单个key和value头,同时不减少query头来提升性能。多查询注意力可能导致质量下降和训练不稳定,因此常用的是分组查询注意力。

然后我们结合上篇文章3探讨的旋转位置编码,将选择位置编码应用到分组查询注意力上。

多头注意力

我们先回顾以下原始多头注意力的实现。

import torch
from torch import nn, Tensor

import math
from dataclasses import dataclass


@dataclass
class ModelArgs:
    hidden_size: int = 512
    num_heads: int = 8
    attention_dropout: float = 0.1


class MultiHeadAttention(nn.Module):
    def __init__(self, args: ModelArgs) -> None:
        super().__init__()
        self.hidden_size = args.hidden_size
        self.num_heads = args.num_heads
        self.head_dim = self.hidden_size // self.num_heads
        self.attention_dropout = args.attention_dropout

        self.q_proj = nn.Linear(
            self.hidden_size, self.num_heads * self.head_dim, bias=False
        )
        self.k_proj = nn.Linear(
            self.hidden_size, self.num_heads * self.head_dim, bias=False
        )
        self.v_proj = nn.Linear(
            self.hidden_size, self.num_heads * self.head_dim, bias=False
        )
        self.o_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=False)

    def forward(self, hidden_states: Tensor, attention_mask: Tensor = None):

        batch_size, seq_len, _ = hidden_states.shape

        query_states, key_states, value_states = (
            self.q_proj(hidden_states),
            self.k_proj(hidden_states),
            self.v_proj(hidden_states),
        )

        query_states = query_states.view(
            batch_size, seq_len, self.num_heads, self.head_dim
        ).transpose(1, 2)
        key_states = key_states.view(
            batch_size, seq_len, self.num_heads, self.head_dim
        ).transpose(1, 2)
        value_states = value_states.view(
            batch_size, seq_len, self.num_heads, self.head_dim
        ).transpose(1, 2)

        attn_weights = torch.matmul(
            query_states, key_states.transpose(2, 3)
        ) / math.sqrt(self.head_dim)

        if attention_mask is not None:
            causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
            attn_weights = attn_weights + causal_mask

        # upcast attention to fp32 see https://github.com/huggingface/transformers/pull/17437
        attn_weights = nn.functional.softmax(
            attn_weights, dim=-1, dtype=torch.float32
        ).to(query_states.dtype)

        attn_weights = nn.functional.dropout(
            attn_weights, p=self.attention_dropout, training=self.training
        )
        attn_output = torch.matmul(attn_weights, value_states)

        attn_output = attn_output.transpose(1, 2).contiguous()

        attn_output = attn_output.reshape(batch_size, seq_len, self.hidden_size)

        attn_output = self.o_proj(attn_output)

        return attn_output


别忘了测试一下:

    args = ModelArgs()
    attention = MultiHeadAttention(args)
    inputs = torch.randn(32, 8, args.hidden_size)
    print(attention(inputs).shape)
torch.Size([32, 8, 512])

原始多头注意力就不再赘述了,之前的文章有过详细介绍。

分组查询注意力

分组查询注意力使用折中数量的key-value头(超过一个,但少于多头注意力全部的头数量)来提升性能。

多头注意力、分组查询注意力以及多查询注意力之间的区别如下:

image-20240413222803653

该图来自参考1中的论文。

202405301726

如上图所示,分组查询注意力是针对多头注意力的一种改进,每组Query头(这里两个Query一组)共享同一个Key和Value头,使得推理更加高效。

实际上在实现的时候,会将共享的Key和Value头进行广播(复制)成与Query头相同的数量:

202405301734

这样,我们就可以像普通多头注意力一样去计算了。

我们增加num_key_value_heads表示key、value头数;num_heads还是表示query头数。

@dataclass
class ModelArgs:
    hidden_size: int = 512
    num_heads: int = 8
    num_key_value_heads: int = 4
    attention_dropout: float = 0.1

分组查询注意力和多查询注意力可以合并在一起实现:

class GroupedQueryAttention(nn.Module):
    def __init__(self, args: ModelArgs) -> None:
        super().__init__()

        self.hidden_size = args.hidden_size
        self.num_heads = args.num_heads
        # 每个头的维度计算和之前一样
        self.head_dim = self.hidden_size // self.num_heads
        # 保存key/value头数
        self.num_key_value_heads = args.num_key_value_heads
        # 每组内要复制的次数,若为1,即退化为多头注意力;若为num_heads,则为多查询注意力
        self.num_key_value_groups = self.num_heads // args.num_key_value_heads
        self.attention_dropout = args.attention_dropout

        self.q_proj = nn.Linear(
            self.hidden_size, self.num_heads * self.head_dim, bias=False
        )
        # 注意Key和Value的映射这里节省了参数,加速了推理效率。
        self.k_proj = nn.Linear(
            self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False
        )
        self.v_proj = nn.Linear(
            self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False
        )
        # 最后的输出映射和之前一样
        self.o_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=False)

    def forward(self, hidden_states: Tensor, attention_mask: Tensor = None):

        batch_size, seq_len, _ = hidden_states.shape

        query_states, key_states, value_states = (
            self.q_proj(hidden_states),
            self.k_proj(hidden_states),
            self.v_proj(hidden_states),
        )

        query_states = query_states.view(
            batch_size, seq_len, self.num_heads, self.head_dim
        ).transpose(1, 2)
        # 转换为对应的形状
        key_states = key_states.view(
            batch_size, seq_len, self.num_key_value_heads, self.head_dim
        ).transpose(1, 2)
        value_states = value_states.view(
            batch_size, seq_len, self.num_key_value_heads, self.head_dim
        ).transpose(1, 2)
        
		# 重复num_key_value_groups次,使得和query头数一致
        key_states = repeat_kv(key_states, self.num_key_value_groups)
        value_states = repeat_kv(value_states, self.num_key_value_groups)
		# 后面和普通多头注意力一样计算
        attn_weights = torch.matmul(
            query_states, key_states.transpose(2, 3)
        ) / math.sqrt(self.head_dim)

        if attention_mask is not None:
            causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
            attn_weights = attn_weights + causal_mask

        # upcast attention to fp32 see https://github.com/huggingface/transformers/pull/17437
        attn_weights = nn.functional.softmax(
            attn_weights, dim=-1, dtype=torch.float32
        ).to(query_states.dtype)

        attn_weights = nn.functional.dropout(
            attn_weights, p=self.attention_dropout, training=self.training
        )
        attn_output = torch.matmul(attn_weights, value_states)

        attn_output = attn_output.transpose(1, 2).contiguous()

        attn_output = attn_output.reshape(batch_size, seq_len, self.hidden_size)

        attn_output = self.o_proj(attn_output)

        return attn_output

其中num_key_value_groups为每组内要复制的次数,若为1,即退化为多头注意力;若为num_heads,则为多查询注意力。

复制时调用repeat_kv方法,如其名所示,只针对key和value:

def repeat_kv(hidden_states: Tensor, n_rep: int) -> Tensor:
    """
    The hidden states go from (batch, num_key_value_heads, seq_len, head_dim) to (batch, num_attention_heads, seq_len, head_dim)
    n_rep is the number of repeat times.
    """
    batch, num_key_value_heads, seq_len, head_dim = hidden_states.shape
    if n_rep == 1:
        # do nothing
        return hidden_states
    # add a new dimension and repeat n_rep times
    hidden_states = hidden_states[:, :, None, :, :].expand(
        batch, num_key_value_heads, n_rep, seq_len, head_dim
    )
    # reshape to (batch, num_attention_heads, seq_len, head_dim)
    return hidden_states.reshape(batch, num_key_value_heads * n_rep, seq_len, head_dim)

有了分组查询注意力,下面我们来看如何应用上篇文章3介绍的旋转位置编码到query和key上。

应用旋转位置编码

注意,实现的时候要考虑维度,因此代码和上篇文章的旋转位置编码3有所不同。

首先,我们实现RotaryEmbedding,它缓存了频率张量inv_freq的计算。

class RotaryEmbedding(nn.Module):
    def __init__(
        self, dim: int, max_position_embeddings: int = 2048, theta: int = 10000
    ):
        super().__init__()
        self.dim = dim  # head dim
        self.max_position_embeddings = max_position_embeddings
        self.theta = theta
        inv_freq = 1.0 / (
            theta
            ** (torch.arange(0, self.dim, 2, dtype=torch.int64).float() / self.dim)
        )
        self.register_buffer("inv_freq", inv_freq, persistent=False)
	# 不需要计算梯度
    @torch.no_grad()
    def forward(self, position_ids: torch.LongTensor):
        freqs = torch.outer(position_ids, self.inv_freq).float()
        return torch.polar(torch.ones_like(freqs), freqs)

该实现修改自旋转位置编码文章3中的precompute_freqs_cis函数。

然后我们改写apply_rotary_emb函数,主要是确定了输入和输出维度的正确性:

def apply_rotary_emb(q: Tensor, k: Tensor, freq_cis: Tensor):
    """

    Args:
        q (Tensor): (batch_size, num_heads, seq_len, head_dim)
        k (Tensor): (batch_size, num_key_value_heads, seq_len, head_dim)
        freq_cis (Tensor): (seq_len, batch_size)
    """

    # q_ (batch_size, num_heads, seq_len, head_dim // 2, 2)
    q_ = q.float().reshape(*q.shape[:-1], -1, 2)
    # k_ (batch_size, num_key_value_heads, seq_len, head_dim // 2, 2)
    k_ = k.float().reshape(*k.shape[:-1], -1, 2)

    # turn to complex
    # q_ (batch_size, num_heads, seq_len, head_dim // 2)
    q_ = torch.view_as_complex(q_)
    # k_ (batch_size, num_key_value_heads, seq_len, head_dim // 2)
    k_ = torch.view_as_complex(k_)

    # freq_cis (batch_size, 1, seq_len, 1)
    freq_cis = reshape_for_broadcast(freq_cis, q_)

    # 应用旋转操作,然后将结果转回实数
    # view_as_real (batch_size, num_heads, seq_len, head_dim // 2, 2)
    # xq_out (batch_size, num_heads, seq_len, head_dim)
    xq_out = torch.view_as_real(q_ * freq_cis).flatten(-2)
    # view_as_real (batch_size, num_key_value_heads, seq_len, head_dim // 2, 2)
    # xk_out (batch_size, num_key_value_heads, seq_len, head_dim)
    xk_out = torch.view_as_real(k_ * freq_cis).flatten(-2)

    return xq_out.type_as(q), xk_out.type_as(k)

其中需要调用reshape_for_broadcast将频率张量的维度从(seq_len, batch_size)调整到(batch_size, 1, seq_len, 1)

def reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor):
    """
    Args:
        freqs_cis (torch.Tensor): (seq_len, batch_size)
        x (torch.Tensor): (batch_size, num_heads, seq_len, head_dim // 2)
    """
    # enumerate(x.shape) = [(0, batch_size), (1, num_heads), (2, seq_len), (3, head_dim // 2)]
    # (batch_size, 1, seq_len, 1)
    shape = [d if i == 0 or i == 2 else 1 for i, d in enumerate(x.shape)]
    return freqs_cis.view(*shape)

我们把每个维度都写出来就不会出错。

再确保下repeat_kv函数的维度:

def repeat_kv(hidden_states: Tensor, n_rep: int) -> Tensor:
    """
    The hidden states go from (batch, num_key_value_heads seq_len, head_dim) to (batch, num_attention_heads, seq_len, head_dim)
    n_rep is the number of repeat times.
    """
    batch, num_key_value_heads, seq_len, head_dim = hidden_states.shape
    if n_rep == 1:
        # do nothing
        return hidden_states
    # add a new dimension and repeat n_rep times
    hidden_states = hidden_states[:, :, None, :, :].expand(
        batch, num_key_value_heads, n_rep, seq_len, head_dim
    )
    # reshape to (batch, num_attention_heads, seq_len, head_dim)
    return hidden_states.reshape(batch, num_key_value_heads * n_rep, seq_len, head_dim)

最后将旋转位置编码整合到GroupedQueryAttention中:

class GroupedQueryAttention(nn.Module):
    def __init__(self, args: ModelArgs) -> None:
        super().__init__()

        self.hidden_size = args.hidden_size
        self.num_heads = args.num_heads
        # 每个头的维度计算和之前一样
        self.head_dim = self.hidden_size // self.num_heads
        # 保存key/value头数
        self.num_key_value_heads = args.num_key_value_heads
        # 每组内要复制的次数,若为1,即退化为多头注意力;若为num_heads,则为多查询注意力
        self.num_key_value_groups = self.num_heads // args.num_key_value_heads
        self.attention_dropout = args.attention_dropout

        self.max_position_embeddings = args.max_position_embeddings
        self.rope_theta = args.theta

        self.q_proj = nn.Linear(
            self.hidden_size, self.num_heads * self.head_dim, bias=False
        )
        # 注意Key和Value的映射这里节省了参数,加速了推理效率。
        self.k_proj = nn.Linear(
            self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False
        )
        self.v_proj = nn.Linear(
            self.hidden_size, self.num_key_value_heads * self.head_dim, bias=False
        )
        # 最后的输出映射和之前一样
        self.o_proj = nn.Linear(
            self.num_heads * self.head_dim, self.hidden_size, bias=False
        )
		# 定义了RotaryEmbedding实例
        self.rotary_emb = RotaryEmbedding(
            self.head_dim,
            max_position_embeddings=self.max_position_embeddings,
            theta=self.rope_theta,
        )

    def forward(
        self,
        hidden_states: Tensor,
        attention_mask: Tensor = None,
        position_ids: torch.LongTensor = None,
    ):

        batch_size, seq_len, _ = hidden_states.shape

        query_states, key_states, value_states = (
            self.q_proj(hidden_states),
            self.k_proj(hidden_states),
            self.v_proj(hidden_states),
        )
        # query_states(batch_size, num_heads, seq_len, head_dim)
        query_states = query_states.view(
            batch_size, seq_len, self.num_heads, self.head_dim
        ).transpose(1, 2)
        # 转换为对应的形状
        # key_states (batch_size, num_key_value_heads, seq_len, head_dim)
        key_states = key_states.view(
            batch_size, seq_len, self.num_key_value_heads, self.head_dim
        ).transpose(1, 2)
        # value_states (batch_size, num_key_value_heads, seq_len, head_dim)
        value_states = value_states.view(
            batch_size, seq_len, self.num_key_value_heads, self.head_dim
        ).transpose(1, 2)

        # 计算频率张量
        # freq_cis (seq_len, batch_size)
        freq_cis = self.rotary_emb(position_ids)

        # 针对query和key应用旋转位置编码
        # query_states (batch_size, num_heads, seq_len, head_dim)
        # key_states (batch_size, num_key_value_heads, seq_len, head_dim)
        query_states, key_states = apply_rotary_emb(query_states, key_states, freq_cis)

        # 重复num_key_value_groups次,使得和query头数一致
        # key_states (batch_size, num_heads, seq_len, head_dim)
        key_states = repeat_kv(key_states, self.num_key_value_groups)
        # value_states (batch_size, num_heads, seq_len, head_dim)
        value_states = repeat_kv(value_states, self.num_key_value_groups)

        # 后面和普通多头注意力一样计算
        attn_weights = torch.matmul(
            query_states, key_states.transpose(2, 3)
        ) / math.sqrt(self.head_dim)

        if attention_mask is not None:
            causal_mask = attention_mask[:, :, :, : key_states.shape[-2]]
            attn_weights = attn_weights + causal_mask

        # upcast attention to fp32 see https://github.com/huggingface/transformers/pull/17437
        attn_weights = nn.functional.softmax(
            attn_weights, dim=-1, dtype=torch.float32
        ).to(query_states.dtype)

        attn_weights = nn.functional.dropout(
            attn_weights, p=self.attention_dropout, training=self.training
        )
        attn_output = torch.matmul(attn_weights, value_states)

        attn_output = attn_output.transpose(1, 2).contiguous()

        attn_output = attn_output.reshape(batch_size, seq_len, self.hidden_size)

        attn_output = self.o_proj(attn_output)

        return attn_output

主要修改是在调用repeat_kv之前应用旋转位置编码到(每个Attention的)query和key中:

# 计算频率张量
# freq_cis (seq_len, batch_size)
freq_cis = self.rotary_emb(position_ids)

# 针对query和key应用旋转位置编码
# query_states (batch_size, num_heads, seq_len, head_dim)
# key_states (batch_size, num_key_value_heads, seq_len, head_dim)
query_states, key_states = apply_rotary_emb(query_states, key_states, freq_cis)

这里简单探讨下为什么旋转位置编码只是应用到query和key上,没有应用到value上,考虑Attention的计算公式:
a m , n = exp ⁡ ( q m T k n d ) ∑ j = 1 N exp ⁡ q m T k j d o m = ∑ n = 1 N a m , n v n \begin{aligned} a_{m,n} &= \frac{\exp(\frac{\pmb q^T_m \pmb k_n}{\sqrt d})}{\sum_{j=1}^N \exp \frac{\pmb q^T_m \pmb k_j}{\sqrt d}} \\ \pmb o_m &= \sum_{n=1}^N a_{m,n}\pmb v_n \\ \end{aligned} am,nooom=j=1Nexpd qqqmTkkkjexp(d qqqmTkkkn)=n=1Nam,nvvvn

我们可以看到,实际上只有query和key之间会进行交互(点乘),而value只是用于计算加权和,不参与交互,因此没有必要应用旋转位置编码,但也可以尝试应用到value上。

苏神在博客也说了:“通过在q,k中施行该位置编码,那么效果就等价于相对位置编码,而如果还需要显式的绝对位置信息,则可以同时在v上也施行这种位置编码。总的来说,我们通过绝对位置的操作,可以达到绝对位置的效果,也能达到相对位置的效果。”

最后,进行一个简单的测试:

@dataclass
class ModelArgs:
    hidden_size: int = 512
    num_heads: int = 8
    num_key_value_heads: int = 4
    attention_dropout: float = 0.1
    max_position_embeddings: int = 2048
    theta: int = 10000
    
if __name__ == "__main__":
    args = ModelArgs()
    attention = GroupedQueryAttention(args)

    inputs = torch.randn(32, 16, args.hidden_size)

    seq_len = inputs.size(1)

    position_ids = torch.arange(seq_len, dtype=torch.long)

    print(attention(inputs, position_ids=position_ids).shape)

torch.Size([32, 16, 512])

参考


  1. [论文翻译]GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints ↩︎

  2. Fast Transformer Decoding: One Write-Head is All You Need ↩︎

  3. Llama改进之——RoPE旋转位置编码 ↩︎ ↩︎ ↩︎ ↩︎

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

联芸科技偏高的关联交易:业绩波动性明显,海康威视曾拥有一票否决

《港湾商业观察》施子夫 5月31日,上交所上市审核委员会将召开2024年第14次审议会议,届时将审议联芸科技(杭州)股份有限公司招股书(以下简称,联芸科技)的首发上会事项。 据悉,此次系…

php反序列化学习(3)

1、session 当session_start()被调用或者php.ini中session.auto_start为1时,php内部调用会话管理器,访问用户session被序列化后,存储到指定目录(默认为/tmp)。 漏洞产生:写入格式与读取格式不一致 处理器…

C# 代码配置的艺术

文章目录 1、代码配置的定义及其在软件工程中的作用2、C# 代码配置的基本概念和工具3、代码配置的实践步骤4、实现代码配置使用属性(Properties)使用配置文件(Config Files)使用依赖注入(Dependency Injection&#xf…

模拟建造游戏:城市:天际线Cities: Skylines for Mac/win中文原生版

《城市:天际线》(Cities: Skylines)是一款由Colossal Order开发,Paradox Interactive发行的城市建设模拟游戏。这款游戏于2015年首次发布,迅速赢得了玩家和评论家的好评,并成为了备受欢迎的城市建设游戏之一…

Centos7.9环境下keepalived结合nginx实现负载均衡的高可用(亲测版)

目录 一、负载均衡高可用解释 二、安装 三、Nginx检查脚本创建 四、修改keepalived配置文件 一、负载均衡高可用解释 nginx 作为负载均衡器,所有请求都到了nginx,如果nginx服务器宕机后端web服务将无法提供服务,影响严重。这样nginx作为负…

使用 Django Model 构建强大的数据库模型

文章目录 创建一个简单的 Django Model迁移数据库使用 Django Shell 操作模型Django Admin结论 在 Django 中,Model 是构建数据库模型的基础。它允许开发人员定义数据的结构,并提供了方便的方式来与数据库进行交互。本文将介绍如何使用 Django Model 来创…

Vitis HLS 学习笔记--控制驱动与数据驱动混合编程

目录 1. 简介 2. 示例分析 2.1 代码分析 2.2 控制驱动TLP的关键特征 2.3 数据驱动TLP的关键特征 3. 总结 1. 简介 在 HLS 硬件加速领域,Vitis HLS 提供了强大的抽象并行编程模型。这些模型包括控制驱动和数据驱动的任务级并行性(TLP)&…

腾讯元宝APP横空出世,传统搜索面临巨大挑战

关注卢松松,会经常给你分享一些我的经验和观点。 松松有个同事也叫:X元宝。我们公司旁边有个小吃街,就叫元宝街。每提到腾讯元宝,我就想起了我同事和这条街。 我今天看了腾讯混元大模型团队的发布会,他们发布了一款名…

存储 Bean 对象更加简单的方式

前置操作 如果是在 spring-config 中添加 bean 标签来注册内容,每个类都要弄一次就显得麻烦和臃肿了,对于 new 操作而言就没有什么优势了。因此 spring 就引入了注解操作来实现对 Bean 对象的存储。 配置扫描路径 想要将对象成功的存储到 Spring 中&…

【Python】解决Python报错:AttributeError: ‘str‘ object has no attribute ‘xxx‘

🧑 博主简介:阿里巴巴嵌入式技术专家,深耕嵌入式人工智能领域,具备多年的嵌入式硬件产品研发管理经验。 📒 博客介绍:分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向…

【redis】宝塔,线上环境报Redis error: ERR unknown command del 错误

两种方式: 1.打开宝塔上的redis,通过配置文件修改权限,注释:#rename-command DEL “” 2.打开服务器,宝塔中默认redis安装位置是:cd /www/server/redis 找到redis.conf,拉到最后,注释#rename-co…

「vue同一个组件,不同路由切换时界面没有更新问题」

问题&#xff1a;vue项目中不同路由切换时&#xff0c;因为引用的同一个组件&#xff0c;界面数据没有更新 一、解决方法 添加key&#xff0c;具体原理可参考vue中的diff算法 <router-view :key"$route.fullPath"></router-view>

Linux学习笔记(清晰且清爽)

本文首次发布于个人博客 想要获得最佳的阅读体验&#xff08;无广告且清爽&#xff09;&#xff0c;请访问本篇笔记 Linux安装 关于安装这里就不过多介绍了&#xff0c;安装版本是CentOS 7&#xff0c;详情安装步骤见下述博客在VMware中安装CentOS7&#xff08;超详细的图文教…

【Mac版】Java生成二维码

软件版本 IntelliJ IDEA&#xff1a;2023.2 JDK&#xff1a;17 Tomcat&#xff1a;10.1.11 Maven&#xff1a;3.9.3 技术栈 servlet谷歌的&#xff1a;zxing 生成普通的黑白二维码在二维码中间添加一个小图标 github开源项目&#xff1a;qrcode qrcode开源项目的内部是基于z…

服务器数据恢复—EqualLogic存储硬盘灯亮黄色的数据恢复案例

服务器数据恢复环境&#xff1a; 一台某品牌EqualLogic PS 6011型号存储&#xff0c;底层有一组由16块SAS硬盘组建的RAID5阵列&#xff0c;上层存储空间划分了4个卷&#xff0c;格式化为VMFS文件系统&#xff0c;存放虚拟机文件。 服务器故障&#xff1a; 存储设备上两块硬盘指…

flink left join消费kafka数据

left join会产生回车流数据 在控制台数据 import com.sjfood.sjfood.gmallrealtime.app.BaseSQLAPP; import com.sjfood.sjfood.gmallrealtime.util.SQLUtil; import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment; import org.apache.flink.table.…

Ubuntu20.04安装VINS_Mono 和 VINS_Fusion

文章目录 一、问题描述二、依赖环境1. Eigen 安装2. glog 安装3. gflags 安装4. ceres 安装 三、VINS-Mono 安装1. git 下载并安装2. OpenCV 版本冲突3. 运行 四、VINS—Fusion 安装1. git 下载并安装2. OpenCV 版本冲突3. 运行 五、日常bug1. 动静态库链接冲突 一、问题描述 …

无缝接入GPT-4o:智创聚合API平台的创新与实践

在2024年5月13日&#xff0c;美国开放人工智能研究中心&#xff08;OpenAI&#xff09;发布了最新版本的ChatGPT——GPT-4o。这一更新标志着人工智能领域的又一重大进步&#xff0c;引起了全球科技界的广泛关注。GPT-4o的“o”代表“omni”&#xff08;全能&#xff09;&#x…

简要分析学习spring内存马,劫持马

简要分析学习spring内存马&#xff0c;劫持马 本文主要是通过SpringMemShell这个工程&#xff0c;来对spring内存马进行演示&#xff0c;利用。 写在前面&#xff1a; 参考的是大佬给的流程以及思路,其中的解释与分析非常详细 ----->>大佬的链接 这里的内存马文件取自gi…

大坝安全监测自动化技术的规范化设计准则

大坝安全监测自动化技术的规范化设计准则 一、施工阶段自动化系统设计要点 在施工阶段&#xff0c;大坝安全监测自动化系统的设计应当涵盖以下几个核心内容&#xff1a; 监测仪器的布局规划及详细的施工图纸设计。 配套土建项目以及防雷设施的施工设计规划。 明确施工过程中的技…