【大模型学习】现代大模型架构(一): 组注意力机制(GQA)和 RMSNorm

前言

✍ 在大模型论文学习中,相信很多读者和笔者一样,一开始都会有一种感觉:“现在大模型架构都差不多,主要是数据和算力在堆积。”当笔者慢慢总结llamaqwendeepseek这些模型架构的时候发现,在 attention、位置编码、ffn 与归一化 上,其实已经悄悄从经典 transformer 走到了另一套“默认配置”。相较于最初的 transformer,现在的主流大模型在架构上,已经逐渐从:

MQA → GQA(Grouped Query Attention)绝对位置编码 → RoPE(Rotary Positional Embedding)ReLU / GELU 前馈网络 → SwiGLU 前馈网络LayerNorm → RMSNorm + Pre-Norm…

因此,在本文的学习中,我们主要聚焦于目前的大模型”默认配置“的学习,了解现在的”Transformer“!

一、现如今的”Transformer“

读者肯定很疑惑,为什么我要把第一章名字起为现如今的”Transformer“,实际上在以前,不管是科研还是工作,大家都会把Transformer作为一个baseline去进行优化,就像BERT、GPT等等,一直沿用的是Transformer的架构。但到了现在,研究者发现其中模块的更替可以达到更好的的效果。因此,现如今的大模型,已经不再直接将以前的Transformer架构作为baseline,而是将更换了模块的Transformer架构作为baseline。那现如今的baseline模块长什么样子呢,笔者统计了比较经典的模块所采用的注意力机制、位置编码、MLP激活层以及归一化的方式:

模型家族

注意力

位置编码

MLP 激活

归一化

早期 GPT/BERT

MHA

绝对 PE / learned pos

GELU

LayerNorm

LLaMA 1/2/3 系列

GQA(大模型)

RoPE

SwiGLU

RMSNorm

Qwen2 / Qwen2.5

GQA

RoPE

SwiGLU

RMSNorm

Mistral 7B

GQA + sliding window

RoPE

SwiGLU

RMSNorm

DeepSeek-LLM 等

GQA/自研高效注意力

RoPE

SwiGLU

RMSNorm

Granite / Gemma 等

GQA/MQA

RoPE

SwiGLU/GeGLU

RMSNorm/LN

如表格所示, 对比早期 GPT/BERT 模型我们就可以发现了,现如今大模型的各个模块都有所改变:

瞬映 瞬映

AI 快速创作数字人视频,一站式视频创作平台,让视频创作更简单。

瞬映 57 查看详情 瞬映 注意力机制:MQA → GQA(Grouped Query Attention)位置编码: 绝对位置编码 → RoPE(Rotary Positional Embedding)MLP 激活层:ReLU / GELU 前馈网络 → SwiGLU 前馈网络归一化: LayerNorm → RMSNorm + Pre-Norm

所以如果你能把这四件套讲明白,基本就把现代 LLM 架构里 理清,并且可以快速找到文章的贡献点。

二、Attention Serious

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

【大模型学习】现代大模型架构(一): 组注意力机制(GQA)和 RMSNorm

2.1 Multi-Head Attention (MHA)

首先来回顾一下以前的注意力机制:

text{Attention}(Q,K,V) = text{softmax}left(frac{QK^top}{sqrt{d_k}}right)V

在标准的自注意力中,我们通过 $QK^T / sqrt{d_k}$ 来计算不同 token 之间的注意力权重。但作者发现,仅用一个注意力头往往难以同时捕捉多种语义关系(如词法、语义、句法等)。因此,Transformer 提出了多头注意力机制 (Multi-Head Attention, MHA)。

将输入特征通过不同的线性投影矩阵,映射到多个低维子空间中:

text{head}_i = text{Attention}(QW_i^Q, , KW_i^K, , VW_i^V)

然后将所有头拼接(concatenate)再线性变换:

text{MultiHead}(Q,K,V) = text{Concat}(text{head}_1, dots, text{head}_h) W^O

【大模型学习】现代大模型架构(一): 组注意力机制(GQA)和 RMSNorm

MHA通过多个小头可以从不同角度捕捉语义信息,增强模型的表达能力和稳定性,比单头更鲁棒。

代码手撕

import torchimport torch.nn as nnimport torch.nn.functional as Fclass MultiHeadAttention(nn.Module):    def __init__(self, d_model, num_heads, dropout=0.0):        super().__init__()        assert d_model % num_heads == 0        self.d_model = d_model        self.num_heads = num_heads        self.head_dim = d_model // num_heads        self.w_q = nn.Linear(d_model, d_model)        self.w_k = nn.Linear(d_model, d_model)        self.w_v = nn.Linear(d_model, d_model)        self.w_o = nn.Linear(d_model, d_model)        self.dropout = nn.Dropout(dropout)    def forward(self, x, attn_mask=None):        """        x: [B, L, d_model]        """        B, L, _ = x.size()        # 1. 线性投影        Q = self.w_q(x)  # [B, L, d_model]        K = self.w_k(x)        V = self.w_v(x)        # 2. reshape 为 [B, H, L, Dh]        def reshape_heads(t):            return t.view(B, L, self.num_heads, self.head_dim).transpose(1, 2)        Q = reshape_heads(Q)        K = reshape_heads(K)        V = reshape_heads(V)        # Q,K,V: [B, H, L, Dh]        # 3. 缩放点积注意力        scores = Q @ K.transpose(-2, -1) / (self.head_dim ** 0.5)  # [B, H, L, L]        if attn_mask is not None:            scores = scores.masked_fill(attn_mask == 0, float('-inf'))        attn = F.softmax(scores, dim=-1)        attn = self.dropout(attn)        out = attn @ V  # [B, H, L, Dh]        # 4. 合并头        out = out.transpose(1, 2).contiguous().view(B, L, self.d_model)        return self.w_o(out)

2.2 Multi-Query Attention (MQA)

有了 MHA 之后,大家第一反应是:头越多越好,越能学到多种语义关系。但在大模型、尤其是 Decoder-Only + 长上下文 + 自回归生成 的场景下,MHA 暴露出了一个非常现实的问题:

KV Cache 太贵了。

在自回归生成过程中,每生成一个新 token,都需要用到历史所有位置的K, V

对于标准 MHA:每个注意力头都维护一份自己的 K_h, V_h 如果有 h 个头,那么 KV Cache 的内存开销大致是: mathcal{O}(h cdot L cdot d_{text{head}})

当我们把头数堆到 32、64 甚至更多,再把上下文长度拉到 32K、64K 时,这个开销就会变成显存吞噬怪,直接限制推理速度与可部署性。因此,为了在几乎不损失模型效果的前提下,压缩 KV Cache 和带宽成本,就提出了 Multi-Query Attention(MQA)。

MHA中的每一个头都是独享一份$K, V$,相反的,MQA 提出了所有的头共享同一份$K, V$也就是说,只保留一组 W^K, W^V ,而 W_i^Q 仍然为每个头独立:

Q_i = X W_i^Q,quad K = X W^K,quad V = X W^V.

于是每个头的注意力就变成:

text{head}_i = text{Attention}(Q_i, K, V) = text{softmax}left(frac{Q_i K^top}{sqrt{d_k}}right) V.

最后依然是拼接再线性变换:

text{MQA}(X) = text{Concat}(text{head}_1, dots, text{head}_h) W^O.

? 经验发现“多 KV”并没有带来线性收益, Q 仍然是多头的,多头仍能捕捉多种语义关系。

代码手撕

class MultiQueryAttention(nn.Module):    def __init__(self, d_model, num_heads, dropout=0.0):        super().__init__()        assert d_model % num_heads == 0        self.d_model = d_model        self.num_heads = num_heads        self.head_dim = d_model // num_heads        self.w_q = nn.Linear(d_model, d_model)        # 注意:K/V 只有一组,所以输出维度是 head_dim        self.w_k = nn.Linear(d_model, self.head_dim)        self.w_v = nn.Linear(d_model, self.head_dim)        self.w_o = nn.Linear(d_model, d_model)        self.dropout = nn.Dropout(dropout)    def forward(self, x, attn_mask=None):        """        x: [B, L, d_model]        """        B, L, _ = x.size()        # 1. 多头 Q        Q = self.w_q(x)  # [B, L, d_model]        Q = Q.view(B, L, self.num_heads, self.head_dim).transpose(1, 2)        # Q: [B, H, L, Dh]        # 2. 单头 K/V        K = self.w_k(x)  # [B, L, Dh]        V = self.w_v(x)  # [B, L, Dh]        # 3. 为了和 Q 匹配,将 K/V 在头维上 broadcast        K = K.unsqueeze(1)  # [B, 1, L, Dh]        V = V.unsqueeze(1)  # [B, 1, L, Dh]        K = K.expand(B, self.num_heads, L, self.head_dim)        V = V.expand(B, self.num_heads, L, self.head_dim)        # 4. 缩放点积注意力(与 MHA 相同)        scores = Q @ K.transpose(-2, -1) / (self.head_dim ** 0.5)  # [B, H, L, L]        if attn_mask is not None:            scores = scores.masked_fill(attn_mask == 0, float('-inf'))        attn = F.softmax(scores, dim=-1)        attn = self.dropout(attn)        out = attn @ V  # [B, H, L, Dh]        out = out.transpose(1, 2).contiguous().view(B, L, self.d_model)        return self.w_o(out)

2.3 Grouped Query Attention (GQA)

根据前面两节的分析,我们可以总结出:

MHA:每个头都有独立的 K_h, V_h ,表达能力强,但 KV Cache 成本最高;MQA:所有头共享同一份 K, V ,KV Cache 成本最低,但多头之间视角差异弱,表达能力稍打折

于是就自然出现了一个折中思路:能不能在 “省 KV” 和 “头之间有点差异” 之间找个平衡?这就是 Grouped-Query Attention(GQA)。GQA 的核心思想:Q 仍然是很多头,但 K/V 的头数减少为更少的组(num_kv_heads),每组 KV 服务若干个 Q 头。

代码手撕

class GroupedQueryAttention(nn.Module):    def __init__(self, d_model, num_q_heads, num_kv_heads, dropout=0.0):        super().__init__()        assert d_model % num_q_heads == 0        assert num_q_heads % num_kv_heads == 0        self.d_model = d_model        self.num_q_heads = num_q_heads        self.num_kv_heads = num_kv_heads        self.head_dim = d_model // num_q_heads        self.group_size = num_q_heads // num_kv_heads  # 每组多少个 Q 头共享一个 KV        self.w_q = nn.Linear(d_model, d_model)        self.w_k = nn.Linear(d_model, num_kv_heads * self.head_dim)        self.w_v = nn.Linear(d_model, num_kv_heads * self.head_dim)        self.w_o = nn.Linear(d_model, d_model)        self.dropout = nn.Dropout(dropout)    def forward(self, x, attn_mask=None):        """        x: [B, L, d_model]        """        B, L, _ = x.size()        # 1. Q: 多头; K/V: 少量头        Q = self.w_q(x)  # [B, L, d_model]        K = self.w_k(x)  # [B, L, num_kv_heads * head_dim]        V = self.w_v(x)        Q = Q.view(B, L, self.num_q_heads, self.head_dim).transpose(1, 2)        K = K.view(B, L, self.num_kv_heads, self.head_dim).transpose(1, 2)        V = V.view(B, L, self.num_kv_heads, self.head_dim).transpose(1, 2)        # Q: [B, Hq,  L, Dh]        # K,V: [B, Hkv, L, Dh]        # 2. 将每个 KV 头“扩展”为 group_size 个 Q 头使用        #    例如 Hq=8, Hkv=2 -> group_size=4        K = K.repeat_interleave(self.group_size, dim=1)  # [B, Hq, L, Dh]        V = V.repeat_interleave(self.group_size, dim=1)        # 3. 缩放点积注意力        scores = Q @ K.transpose(-2, -1) / (self.head_dim ** 0.5)  # [B, Hq, L, L]        if attn_mask is not None:            scores = scores.masked_fill(attn_mask == 0, float("-inf"))        attn = F.softmax(scores, dim=-1)        attn = self.dropout(attn)        out = attn @ V  # [B, Hq, L, Dh]        # 4. 合并头        out = out.transpose(1, 2).contiguous().view(B, L, self.d_model)        return self.w_o(out)

三、归一化:LayerNorm → RMSNorm + Pre-Norm

在 Transformer 里,归一化(Normalization)主要解决两个问题:

深层网络训练不稳定:梯度可能爆炸或消失;不同层输出分布漂移,导致学习变慢。

最早的 Transformer 使用的是 LayerNorm + Post-Norm 残差结构(指在全连接层后跟上一个归一化层)

【大模型学习】现代大模型架构(一): 组注意力机制(GQA)和 RMSNorm

但到了 LLaMA、DeepSeek 等大模型时,大家开始逐渐转向:RMSNorm + Pre-Norm(指在全连接层前跟上一个归一化层)

? Post-Norm(原始 Transformer 用法)

最早的 Transformer 论文(Attention Is All You Need)使用的是 Post-Norm,代码结构类似:

# Post-Norm 结构out = x + sublayer(x)out = layer_norm(out)
? Pre-Norm(现代 LLM 常用)

大多数现代 LLM(如 LLaMA、DeepSeek 系列)改成了 Pre-Norm:代码结构类似:

# Pre-Norm 结构h = layer_norm(x)out = x + sublayer(h)

? 实践上,Pre-Norm 再配合 RMSNorm,只调节尺度不改均值,在 Decoder-only 结构里训练更稳定、实现也更简单。

3.1 LayerNorm

Layer Normalization(LN)是在 Transformer 中使用最广的归一化方式之一。给定一个 token 的隐藏表示 x in mathbb{R}^{d} ,LayerNorm 对其 特征维度 进行归一化:

mu = frac{1}{d} sum_{i=1}^{d} x_i,quad sigma^2 = frac{1}{d} sum_{i=1}^{d} (x_i – mu)^2
text{LN}(x) = frac{x – mu}{sqrt{sigma^2 + epsilon}} cdot gamma + beta

其中:

gamma, beta in mathbb{R}^{d} 是可学习的缩放和平移参数;归一化是在单个样本、单个 token 的通道维度上完成的。

? 直觉理解:

在 PyTorch 中,你平时看到的 nn.LayerNorm 就是这个东西:

import torchimport torch.nn as nnx = torch.randn(2, 4, 8)  # [B, L, d_model]ln = nn.LayerNorm(8)y = ln(x)  # 每个位置的最后一维做 LN
? 1.为什么不用 BatchNorm,而用 LayerNorm / RMSNorm?(面经)

这一问是面试官很喜欢的一个考点,尤其是 Transformer / LLM 岗位。核心区别在于:归一化时用哪些维度来统计均值与方差。

BatchNorm(BN):在 CV 里常用,对 batch 维度 + 空间维度 做统计;对每个通道c,使用整批数据的统计量:mu_c = mathbb{E}_{N,H,W}x_{n,c,h,w} LayerNorm(LN):对单个样本、单个 token 的所有特征求均值和方差,不依赖 batch 大小。

在 Transformer / LLM 场景中,BN 存在几个问题:

序列长度不固定:BN 在变长序列上不自然,统计维度不好选;推理阶段 batch 很小甚至为 1:BN 的 running mean/var 与训练时差异大,容易分布漂移;自注意力中不同 token 之间差异大:BN 混合不同 token 的统计量,会引入额外噪声。

因此,大模型里更偏向用 LayerNorm / RMSNorm 这种“不依赖 batch、只看自己”的归一化方式。

3.2 RMSNorm

RMSNorm 是基于“层归一化中主要起作用的是缩放因子,而非平移因子”这个发现而提出的归一化方法。在层归一化中需要减去均值,而模型在训练过程中已经学会通过投影矩阵自动调节均值;而 gamma 的作用是调整每一维的相对 scale,是表达力的核心。给定 x in mathbb{R}^d ,RMSNorm 的公式为:

text{RMS}(x) = sqrt{frac{1}{d} sum_{i=1}^{d} x_i^2 + epsilon}

text{RMSNorm}(x) = frac{x}{text{RMS}(x)} cdot gamma

? 直觉理解:

? 实践上,在 Decoder-only 大模型里:RMSNorm + Pre-Norm 组合在超深层网络(几十层)上表现更稳定,这也是 LLaMA / DeepSeek / Qwen 等系列广泛采用它的原因之一。

代码手撕

class RMSNorm(nn.Module):    def __init__(self, d_model, eps=1e-8):        super().__init__()        self.weight = nn.Parameter(torch.ones(d_model))        self.eps = eps    def forward(self, x):        """        x: [B, L, d_model]        """        # 均方根:sqrt(mean(x^2))        rms = x.pow(2).mean(dim=-1, keepdim=True).add(self.eps).sqrt()        x_norm = x / rms        return self.weight * x_norm

四、总结

本章我们先把现代大模型里的两块“基础设施”打牢:一块是从 MHA → MQA → GQA 的注意力演化,用更少的 KV 头(甚至共享 KV)在不明显掉点的前提下,大幅降低 KV Cache 与长上下文显存开销;另一块是从 LayerNorm → RMSNorm + Pre-Norm 的归一化升级,用“只归一化能量”的 RMSNorm 配合 Pre-Norm 结构,让超深的 Decoder-only 模型在训练和推理中都更加稳定。后面的章节,我们再把 RoPE / SwiGLU / MoE / MLA 这些“进阶武器”一个个拆开,拼成一整套现代 LLM 的“架构面经图谱”。

以上就是【大模型学习】现代大模型架构(一): 组注意力机制(GQA)和 RMSNorm的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/986610.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
CSS布局中inline-flex和block-flex区别_Flex应用解析
上一篇 2025年12月1日 21:29:50
在Java中如何解决classpath找不到的问题_Java项目环境故障排查与处理方法
下一篇 2025年12月1日 21:29:54

相关推荐

  • composer require-dev和require有什么不同_Composer Require与Require-Dev区别解析

    require用于声明项目运行必需的依赖,如框架、数据库组件和第三方SDK,这些包会随项目部署到生产环境;2. require-dev用于声明仅在开发和测试阶段需要的工具,如PHPUnit、PHPStan、Faker等,不会默认部署到生产环境;3. 安装时composer install根据环境决定…

    2026年5月10日
    1000
  • 怎么在PHP代码中实现图片上传功能_PHP图片上传功能实现与安全处理教程

    首先创建含enctype的HTML表单,再用PHP接收文件,检查目录、移动临时文件,验证类型与大小,生成唯一文件名,并调整php.ini限制以确保上传成功。 如果您尝试在PHP项目中添加图片上传功能,但服务器无法正确接收或保存文件,则可能是由于表单配置、文件处理逻辑或安全限制的问题。以下是实现该功能…

    2026年5月10日
    100
  • 理解编程指令:当结果正确,但实现方式不符要求时

    本文探讨了在编程实践中,即使程序输出了正确的结果,但若其实现方式未能严格遵循既定指令,仍可能被视为“不正确”的问题。我们将通过具体示例,对比直接求和与累加求和两种实现策略,强调理解和遵守编程规范的重要性,以确保代码的健壮性、可维护性及符合项目要求。 在软件开发过程中,我们经常会遇到这样的情况:编写的…

    2026年5月10日
    000
  • php常量怎么用_PHP常量(define/const)定义与使用方法

    PHP中可通过define函数和const关键字定义常量,用于存储不可变值。define适用于全局作用域,支持动态名称和条件定义,如define(‘SITE_NAME’, ‘MyWebsite’);const在编译时生效,语法简洁但限制多,只能在类或全…

    2026年5月10日
    000
  • PHP动态生成表单输入与POST数据获取实践指南

    本教程详细阐述了如何在php中根据动态数据源(如数据库值)生成多个表单输入框,并演示了如何通过post方法准确无误地获取这些动态生成的输入值。文章强调了正确的输入框命名策略,避免了常见的命名误区,并提供了完整的代码示例,确保开发者能够高效处理动态表单数据。 动态生成表单输入 在Web开发中,我们经常…

    2026年5月10日
    000
  • Discord.py 交互按钮超时与持久化解决方案

    本教程旨在解决Discord.py中交互按钮在一段时间后出现“This Interaction Failed”错误的问题。我们将深入探讨视图(View)的超时机制,并提供通过正确设置timeout参数以及利用bot.add_view()方法实现按钮持久化的具体方案,确保您的机器人交互功能稳定可靠,即…

    2026年5月10日
    000
  • c++如何实现UDP通信_c++基于UDP的网络通信示例

    UDP通信基于套接字实现,适用于实时性要求高的场景。1. 流程包括创建套接字、绑定地址(接收方)、发送(sendto)与接收(recvfrom)数据、关闭套接字;2. 服务端监听指定端口,接收客户端消息并回传;3. 客户端发送消息至服务端并接收响应;4. 跨平台需处理Winsock初始化与库链接,编…

    2026年5月10日
    100
  • JS如何实现迭代器?迭代器协议

    JavaScript中实现迭代器需遵循可迭代协议和迭代器协议,通过定义[Symbol.iterator]方法返回具备next()方法的迭代器对象,从而支持for…of和展开运算符;该机制统一了数据结构的遍历接口,实现惰性求值,适用于自定义对象、树、图及无限序列等复杂场景,提升代码通用性与…

    2026年5月10日
    100
  • Golang使用Protobuf定义接口与消息格式

    Protobuf通过字段编号实现兼容性,新增字段可忽略、删除字段可保留编号,确保新旧版本互操作,支持服务独立演进。 在Golang项目中,利用Protobuf定义接口和消息格式,本质上是为服务间通信构建了一套高效、类型安全且跨语言的契约。它让数据结构清晰可见,RPC调用标准化,极大地简化了分布式系统…

    2026年5月10日
    000
  • Go语言接口与切片:如何识别和操作[]interface{}

    本文将深入探讨Go语言中如何识别和操作`[]interface{}`类型的切片。我们将介绍类型断言(Type Assertion)的关键作用,并通过`switch`语句演示如何安全地检测`[]interface{}`类型,并进而遍历其内部元素。文章旨在提供清晰的示例代码和专业指导,帮助开发者有效地处…

    2026年5月10日
    000
  • PHP多维数组到复杂XML结构的SOAP序列化实践

    本文旨在解决php多维数组向复杂soap xml结构序列化时遇到的“无法序列化结果”问题。通过深入理解soap xml的结构要求,包括命名空间和类型属性,文章将指导您如何构建符合特定xml schema的php关联数组。我们将利用`spatie/array-to-xml`库,详细演示其安装与使用方法…

    2026年5月10日
    000
  • 使用 Ajax 和 FormData 实现文件上传及文本数据提交的完整教程

    本文旨在解决在使用 Ajax 和 FormData 进行文件上传时,遇到的 $_POST 和 $_FILES 为空的问题。通过详细的代码示例和解释,我们将展示如何正确地构建 FormData 对象,并通过 Ajax 将文件和文本数据发送到服务器端,同时避免常见的错误配置,确保数据能够成功地被 PHP…

    2026年5月10日
    000
  • 深入理解MQTT多级通配符#的用法限制与Paho-MQTT订阅实践

    本文旨在解析mqtt多级通配符`#`在订阅主题时的严格使用规则,尤其是在paho-mqtt库中遇到的`valueerror: ‘invalid subscription filter.’`问题。我们将详细阐述mqtt规范中关于`#`必须作为主题过滤器最后一个字符的规定,并通过…

    2026年5月10日
    000
  • 解决Persistent UTM代码导致链接意外添加问号的问题

    本文旨在解决在使用JavaScript持久化UTM参数时,链接在没有UTM参数的情况下被意外添加问号的问题。通过分析问题代码,找出错误原因,并提供修正后的代码示例,确保只有当存在UTM参数时,链接才会被添加相应的参数。同时,强调了代码的健壮性和可维护性,避免不必要的修改和潜在的错误。 在使用Java…

    2026年5月10日
    200
  • JavaScript 中使用多个 querySelector 更新页面元素

    本文旨在讲解如何在 JavaScript 的 if 语句中使用多个 querySelector 来更新不同的页面元素,并提供示例代码和注意事项,帮助开发者理解并应用此技术。通过该方法,可以根据特定条件动态修改页面内容,提升用户体验。 使用 querySelector 在 if 语句中更新多个元素 在…

    2026年5月10日
    100
  • 硬盘数据被误删除怎么办?教你快速找回删除的文件!

    硬盘数据被误删除,别慌!恢复数据并非不可能,关键在于你接下来的操作。立刻停止对该硬盘的任何写入操作,然后尝试使用专业的数据恢复软件。 解决方案 首先,数据恢复的原理是,删除文件后,操作系统只是将文件占用的空间标记为“可覆盖”,但文件本身的数据可能还存在于硬盘上。所以,避免新的数据写入覆盖掉旧数据,是…

    2026年5月10日
    000
  • CodeIgniter在IIS环境下实现URL重写与index.php移除指南

    本教程详细指导如何在IIS服务器上部署的CodeIgniter应用中,移除URL中不必要的index.php。核心解决方案涉及修改CodeIgniter的config.php文件,将$config[‘index_page’]设置为空,并辅以正确的IIS web.config重…

    2026年5月10日
    100
  • c++中头文件和源文件的区别_c++头文件与源文件作用对比

    头文件声明接口,源文件实现逻辑。头文件含类、函数声明及宏定义,通过#include被多文件共享,用include守卫防重;源文件实现具体功能,编译为目标文件后由链接器合并。声明与实现分离提升模块化与编译效率,模板和内联函数因需编译时可见故常置于头文件,命名空间避免符号冲突,整体结构使项目更清晰易维护…

    2026年5月10日
    000
  • HTML文档的基本结构是什么? 3分钟带你了解HTML文档基础框架

    html文档的基础结构由四部分组成:1. 声明,用于告知浏览器以html5标准模式解析页面,避免怪异模式导致的兼容性问题;2. 根元素,包裹整个文档内容,并可通过lang属性指定语言;3. 头部区域,包含元数据如设置字符编码、实现响应式布局、定义页面标题、引入css和favicon、加载脚本等;4.…

    2026年5月10日
    000
  • Android和iOS系统下,HTML+JS代码运行结果差异:为什么input宽度为0时,Android输入方向异常?

    Android和iOS系统HTML+JS代码运行差异分析:input宽度为0引发的Android输入方向异常 开发OTP输入组件时,我们发现一个有趣的现象:当input元素的宽度设置为0 (style=”width: 0;”)时,Android系统下的输入方向会异常,而iOS系统则正常工作。 移除w…

    2026年5月10日
    000

发表回复

登录后才能评论
关注微信