从头开始实现LoRA以及一些实用技巧

LoRA是Low-Rank Adaptation或Low-Rank Adaptors的缩写,它提供了一种用于对预先存在的语言模型进行微调的高效且轻量级的方法。

LoRA的主要优点之一是它的效率。通过使用更少的参数,lora显著降低了计算复杂度和内存使用。这使我们能够在消费级gpu上训练大型模型,并将我们的lora(以兆字节计)分发给其他人。

lora可以提高泛化性能。通过限制模型的复杂性,它们有助于防止过拟合,特别是在训练数据有限的情况下。这就产生了更有弹性的模型,这些模型在处理新的、看不见的数据时表现出色,或者至少保留了它们最初训练任务中的知识。

LoRA可以无缝集成到现有的神经网络架构中。这种集成允许以最小的额外训练成本对预训练模型进行微调和适应,使它们非常适合迁移学习应用。

本文将首先深入研究LoRA,然后以RoBERTa模型例从头开发一个LoRA,然后使用GLUE和SQuAD基准测试对实现进行基准测试,并讨论一些技巧和改进。

LoRA是如何工作的

LoRA的基本思想是将预训练的矩阵(即原始模型的参数)冻结(即处于固定状态),只在原始矩阵上添加一个小的delta,其参数比原始矩阵少。

例如矩阵W,它可以是全链接层的参数,也可以是transformer注意力机制的矩阵之一:

如果w-orig的维度是n×m,我们只是初始化一个新的具有相同维度的矩阵来微调则会把参数翻倍。

所以我们通过从低维矩阵B和A进行矩阵乘法来构建ΔW,使其比原始矩阵“维度”更少。

其中,我们首先定义秩r,并且小于基本矩阵的维数r≪n, r≪m。那么矩阵B是n×r矩阵A是r×m。将它们相乘得到一个与W具有相同维数的矩阵,但是参数更少。

我们希望在训练开始的时候像原始模型一样,所以B通常初始化为全零,而A初始化为随机(通常为正态分布)值。

假设我们的基本维数是1024,我们选择了一个LoRA秩r为4,那么:

W有1024 * 1024≈100万个参数;A和B各有r * 1024 = 4 * 1024≈4k参数,共8k

也就是说只需要训练0.8%的参数就可以用LoRA更新我们的矩阵。在LoRA论文中,他们用alpha参数衡量delta矩阵:

如果你只是将α设置为r并微调学习率,可已得到与论文近似的结果。我们在下面的实现中忽略这个细节,但它是许多其他LoRA库(例如hugs Face的PEFT)中的一个常见特性。

手写LoRA

我们在这里的实现将在PyTorch中完成,虽然我们希望严格遵循原始的LoRA论文,但是我们稍微简化了代码,这样它应该更容易阅读,同时仍然显示了基本元素。

我们这里使用RoBERTa模型。使用Huggingface的实现RobertaSelfAttention作为基类创建新类LoraRobertaSelfAttention,这里将初始化LoRA矩阵。所有B矩阵初始化为零,所有A矩阵初始化为正态分布中的随机数。

 class LoraRobertaSelfAttention(RobertaSelfAttention):
     """
     Extends RobertaSelfAttention with LoRA (Low-Rank Adaptation) matrices.
     LoRA enhances efficiency by only updating the query and value matrices.
     This class adds LoRA matrices and applies LoRA logic in the forward method.
 
     Parameters:
     - r (int): Rank for LoRA matrices.
     - config: Configuration of the Roberta Model.
     """
     def __init__(self, r=8, *args, **kwargs):
         super().__init__(*args, **kwargs)
         d = self.all_head_size
 
         # Initialize LoRA matrices for query and value
         self.lora_query_matrix_B = nn.Parameter(torch.zeros(d, r))
         self.lora_query_matrix_A = nn.Parameter(torch.randn(r, d))
         self.lora_value_matrix_B = nn.Parameter(torch.zeros(d, r))
         self.lora_value_matrix_A = nn.Parameter(torch.randn(r, d))

给定这些矩阵,需要定义新的类方法lora_query和lora_value。这些计算ΔW矩阵,即BA,并将其添加到原始矩阵中,我们从原始方法query和value中调用原始矩阵。

 class LoraRobertaSelfAttention(RobertaSelfAttention):
     # ...
 
     def lora_query(self, x):
         """
         Applies LoRA to the query component. Computes a modified query output by adding 
         the LoRA adaptation to the standard query output. Requires the regular linear layer 
         to be frozen before training.
         """
         lora_query_weights = torch.matmul(self.lora_query_matrix_B, self.lora_query_matrix_A)
         return self.query(x) + F.linear(x, lora_query_weights)
 
     def lora_value(self, x):
         """
         Applies LoRA to the value component. Computes a modified value output by adding 
         the LoRA adaptation to the standard value output. Requires the regular linear layer 
         to be frozen before training.
         """
         lora_value_weights = torch.matmul(self.lora_value_matrix_B, self.lora_value_matrix_A)
         return self.value(x) + F.linear(x, lora_value_weights)

要使用这些方法,我们必须重写RobertaSelfAttention的原始转发函数。虽然这有点硬编码(后面有改进的讨论),但它非常简单。首先,我们从modeling_roberta.py复制原始的转发代码。然后将每次对query的调用替换为lora_query,并将每次对value的调用替换为lora_value。然后函数看起来像这样:

 class LoraRobertaSelfAttention(RobertaSelfAttention):
     # ...
     def forward(self, hidden_states, *args, **kwargs):
         """Copied from
 https://github.com/huggingface/transformers/blob/main/src/transformers/models/roberta/modeling_roberta.py
         but replaced the query and value calls with calls to the
         lora_query and lora_value functions.
         We will just sketch of how to adjust this here. 
         Change every call to self.value and self.query in the actual version.
         """
         # original code for query:
         ## mixed_query_layer = self.query(hidden_states)
         # updated query for LoRA:
         mixed_query_layer = self.lora_query(hidden_states)
 
         # The key has no LoRA, thus leave these calls unchanged
         key_layer = self.transpose_for_scores(self.key(hidden_states))
 
         # original code for value:
         ## value_layer = self.transpose_for_scores(self.value(hidden_states))
         # updated value for LoRA:
         value_layer = self.transpose_for_scores(self.lora_value(hidden_states))
         
         # ... (rest of the forward code, unchanged)

这样我们就在注意力层添加了lora部分。剩下任务就是替换掉原来RoBERTa模型中的注意力模块。

这里我们需要遍历RoBERTa模型的每个命名组件,检查它是否属于RobertaSelfAttention类,如果是,则将其替换为LoraRobertaSelfAttention,同时保留原始权重矩阵。

 class LoraWrapperRoberta(nn.Module):
     def __init__(self, task_type, num_classes=None, dropout_rate=0.1, model_id="roberta-large",
                  lora_rank=8, train_biases=True, train_embedding=False, train_layer_norms=True):
         """
         A wrapper for RoBERTa with Low-Rank Adaptation (LoRA) for various NLP tasks.
         - task_type: Type of NLP task ('glue', 'squad_v1', 'squad_v2').
         - num_classes: Number of classes for classification (varies with task).
         - dropout_rate: Dropout rate in the model.
         - model_id: Pre-trained RoBERTa model ID.
         - lora_rank: Rank for LoRA adaptation.
         - train_biases, train_embedding, train_layer_norms: 
             Flags whether to keep certain parameters trainable 
             after initializing LoRA.
         
         Example:
             model = LoraWrapperRoberta(task_type='glue')
         """
         super().__init__()
         # 1. Initialize the base model with parameters
         self.model_id = model_id
         self.tokenizer = RobertaTokenizer.from_pretrained(model_id)
         self.model = RobertaModel.from_pretrained(model_id)
         self.model_config = self.model.config
 
         # 2. Add the layer for the benchmark tasks
         d_model = self.model_config.hidden_size
         self.finetune_head_norm = nn.LayerNorm(d_model)
         self.finetune_head_dropout = nn.Dropout(dropout_rate)
         self.finetune_head_classifier = nn.Linear(d_model, num_classes)
 
         # 3. Set up the LoRA model for training
         self.replace_multihead_attention()
         self.freeze_parameters_except_lora_and_bias()

self.replace_multihead_attention:用我们之前写的LoraRobertaSelfAttention替换了所有神经网络的注意力层

self.freeze_parameters_except_lora_and_bias:这将冻结训练的所有主要参数,这样梯度和优化器步骤仅应用于LoRA参数以及我们希望可训练的其他例如归一化层等参数。

 class LoraWrapperRoberta(nn.Module):
     # ...
 
     def replace_multihead_attention_recursion(self, model):
         """
         Replaces RobertaSelfAttention with LoraRobertaSelfAttention in the model.
         This method applies the replacement recursively to all sub-components.
 
         Parameters
         ----------
         model : nn.Module
             The PyTorch module or model to be modified.
         """
         for name, module in model.named_children():
             if isinstance(module, RobertaSelfAttention):
                 # Replace RobertaSelfAttention with LoraRobertaSelfAttention
                 new_layer = LoraRobertaSelfAttention(r=self.lora_rank, config=self.model_config)
                 new_layer.load_state_dict(module.state_dict(), strict=False)
                 setattr(model, name, new_layer)
             else:
                 # Recursive call for child modules
                 self.replace_multihead_attention_recursion(module)

然后就是递归地遍历所有模型部分,冻结所有不想再训练的参数:

 class LoraWrapperRoberta(nn.Module):
     # ...
 
     def freeze_parameters_except_lora_and_bias(self):
         """
         Freezes all model parameters except for specific layers and types based on the configuration.
         Parameters in LoRA layers, the finetune head, bias parameters, embeddings, and layer norms 
         can be set as trainable based on class settings.
         """
         for name, param in self.model.named_parameters():
             is_trainable = (
                 "lora_" in name or
                 "finetune_head_" in name or
                 (self.train_biases and "bias" in name) or
                 (self.train_embeddings and "embeddings" in name) or
                 (self.train_layer_norms and "LayerNorm" in name)
             )
             param.requires_grad = is_trainable

以上就是我们最简单的一个LORA的实现,下面我们看看效果

用GLUE和SQuAD进行基准测试

我们使用GLUE(通用语言理解评估)和SQuAD(斯坦福问答数据集)基准进行评估。

GLUE基准是一套由8个不同的NLP任务组成的测试,它包括情感分析、文本蕴涵和句子相似性等挑战,为模型的语言适应性和熟练程度提供了一个强有力的衡量标准。

SQuAD专注于评估问答模型。它包括从维基百科的段落中提取答案,模型在其中识别相关的文本跨度。SQuAD v2是一个更高级的版本,引入了无法回答的问题,增加了复杂性,并反映了现实生活中的情况,在这种情况下,模型必须识别文本缺乏答案。

对于下面的基准测试,没有调优任何超参数,没有进行多个runes(特别是较小的GLUE数据集容易出现随机噪声),没有进行任何早停,也没有从之前的GLUE任务开始微调(通常这样做是为了减少小数据集噪声的可变性并防止过拟合)。

从刚初始化的rank为8的LoRA注入到RoBERTa-base模型开始,每个任务的训练精确地进行了6次训练。在前2个epoch中,学习率线性放大到最大值,然后在剩余的4个epoch中线性衰减到零。所有任务的最大学习率为5e-4。所有任务的批处理大小为16

基于roberta的模型有1.246亿个参数。有了LoRA我们只有42万个参数需要训练。这意味着我们实际上只使用0.34%的原始参数进行训练。LoRA为这些特定任务引入的参数数量非常少,实际磁盘大小仅为1.7 MB。

训练后重新加载LoRA参数,在每个任务的验证集上测试性能。结果如下:

它清楚地证明了我们的LoRA实现是有效的,并且注入的低秩矩阵正在学习。

改进思路

我们上面很多的代码都是硬编码,有人可能会想:“除了重新编码自关注类并执行复杂的替换之外,还有更有效、更通用(即可转移到其他网络体系结构)的方法吗?”

其实我们可以简单地实现nn.Linear的包装器,也就是说我们想用它替换哪些层,通过检查它们的名字直接进行替换就可以了。

 class LoraLinear(nn.Linear):
     """
     Extends a PyTorch linear layer with Low-Rank Adaptation (LoRA).
     LoRA adds two matrices to the layer, allowing for efficient training of large models.
     """
     def __init__(self, in_features, out_features, r=8, *args, **kwargs):
         super().__init__(in_features, out_features, *args, **kwargs)
 
         # Initialize LoRA matrices
         self.lora_matrix_B = nn.Parameter(torch.zeros(out_features, r))
         self.lora_matrix_A = nn.Parameter(torch.randn(r, in_features))
         
         # Freeze the original weight matrix
         self.weight.requires_grad = False
 
     def forward(self, x: Tensor) -> Tensor:
         # Compute LoRA weight adjustment
         lora_weights = torch.matmul(self.lora_matrix_B, self.lora_matrix_A)
         # Apply the original and LoRA-adjusted linear transformations
         return super().forward(x) + F.linear(x, lora_weights)

只将LoRA注入所有线性层也成为一种相当普遍的做法。因为保持偏差和归一化已经很小了,所以你不需要再去精简它们。

另外,上面的代码实际上是(接近)huggingface PEFT库实现LoRA的方式。虽然我们的实现是可用的,但是还是强烈建议您使用PEFT,因为我们不是为了学习原理,而不是新造一个轮子。所以下面我们还是要介绍一下如何使用PEFT

PEFT使用指南

我们以量化的方式加载模型。由于bitsandbytes与transformers 库(于2023年5月推出)的集成,这是一件轻而易举的事情。

 import bitsandbytes as bnb
 from transformers import AutoModel, AutoModelForSequenceClassification, BitsAndBytesConfig
 
 # Configuration to load a quantized model
 bnb_config = BitsAndBytesConfig(
     load_in_4bit=True,  # Enable 4-bit loading
     bnb_4bit_quant_type="nf4",
     bnb_4bit_compute_dtype=torch.bfloat16,
     llm_int8_skip_modules=['classifier', 'qa_outputs'],  # Skip these for quantization
 )
 
 # Load the model from Huggingface with quantization
 model = AutoModelForSequenceClassification.from_pretrained('roberta-base',
           torch_dtype="auto", quantization_config=bnb_config)

我们这里使用4位的量化加载,速度会慢一些,我们也可以可以通过检查模型的模块和参数数据类型来验证4位加载:

 # Verify 4-bit loading
 print("Verifying 4-bit elements (Linear4bit) in the attention layer:")
 print(model.roberta.encoder.layer[4].attention)
 
 print("Checking for uint8 data type:")
 print(model.roberta.encoder.layer[4].attention.self.query.weight.dtype)

现在用PEFT注入LoRA参数。PEFT库通过模块的名称定位要替换的模块;因此要看一下模型model.named_parameters()。这是非量子化roberta基模型的样子。

 Module                                                        Parameters
 ----------------------------------------------------------  ------------
 roberta.embeddings.word_embeddings.weight                     38_603_520
 roberta.embeddings.position_embeddings.weight                    394_752
 roberta.embeddings.token_type_embeddings.weight                      768
 roberta.embeddings.LayerNorm.weight                                  768
 roberta.embeddings.LayerNorm.bias                                    768
 roberta.encoder.layer.0.attention.self.query.weight              589_824
 roberta.encoder.layer.0.attention.self.query.bias                    768
 roberta.encoder.layer.0.attention.self.key.weight                589_824
 roberta.encoder.layer.0.attention.self.key.bias                      768
 roberta.encoder.layer.0.attention.self.value.weight              589_824
 roberta.encoder.layer.0.attention.self.value.bias                    768
 roberta.encoder.layer.0.attention.output.dense.weight            589_824
 roberta.encoder.layer.0.attention.output.dense.bias                  768
 roberta.encoder.layer.0.attention.output.LayerNorm.weight            768
 roberta.encoder.layer.0.attention.output.LayerNorm.bias              768
 roberta.encoder.layer.0.intermediate.dense.weight              2_359_296
 roberta.encoder.layer.0.intermediate.dense.bias                    3_072
 roberta.encoder.layer.0.output.dense.weight                    2_359_296
 roberta.encoder.layer.0.output.dense.bias                            768
 roberta.encoder.layer.0.output.LayerNorm.weight                      768
 roberta.encoder.layer.0.output.LayerNorm.bias                        768
 roberta.encoder.layer.1.attention.self.query.weight              589_824
 ...
 roberta.encoder.layer.11.output.LayerNorm.bias                       768
 classifier.dense.weight                                          589_824
 classifier.dense.bias                                                768
 classifier.out_proj.weight                                         1_536
 classifier.out_proj.bias                                               2
 ----------------------------------------------------------  ------------
 TOTAL                                                        124_647_170

然后我们可以指定要为那些层进行LoRA微调。。所有未注入LoRA参数的层将自动冻结。如果我们想以原始形式训练层,可以通过将列表传递给Lora-Config的modules_to_save参数来指定它们。在我们的例子中,

下面的示例注入rank为2的LoRA。我们用上面的8来指定alpha参数,因为这是我们第一次尝试的秩,应该可以让我们使用上面例子中的学习率。

 import peft
 
 # Config for the LoRA Injection via PEFT
 peft_config = peft.LoraConfig(
     r=2, # rank dimension of the LoRA injected matrices
     lora_alpha=8, # parameter for scaling, use 8 here to make it comparable with our own implementation
     target_modules=['query', 'key', 'value', 'intermediate.dense', 'output.dense'], # be precise about dense because classifier has dense too
     modules_to_save=["LayerNorm", "classifier", "qa_outputs"], # Retrain the layer norm; classifier is the fine-tune head; qa_outputs is for SQuAD
     lora_dropout=0.1, # dropout probability for layers
     bias="all", # none, all, or lora_only
 )
 
 model = peft.get_peft_model(model, peft_config)

为LoRA注入指定更多模块可能会增加VRAM需求。如果遇到VRAM限制,请考虑减少目标模块的数量或降低LoRA等级。

对于训练,特别是QLoRA,选择与量化矩阵兼容的优化器。将标准优化器替换为bitsandbytes变体,如下所示:

 import torch
 import bitsandbytes as bnb
 
 # replace this
 optimizer = torch.optim.AdamW(args here)
 # with this
 optimizer = bnb.optim.AdamW8bit(same args here)

这样就可以像以前一样训练这个模型了,训练完成后,保存和重新加载模型的过程很简单。使用模型。Save_pretrained保存模型,指定所需的文件名。PEFT库将在此位置自动创建一个目录,在其中存储模型权重和配置文件。该文件包括基本模型和LoRA配置参数等基本细节。

用peft.AutoPeftModel.from_pretrained,将目录路径作为参数可以重新加载模型。要记住的关键一点是,LoRA配置目前没有保留初始化automodelforsequencecclassification的类的数量。当使用from_pretrained时,需要手动输入这个作为附加参数。

重新加载的模型将包含应用了LoRA的原始基本模型。如果您决定将LoRA永久地集成到基本模型矩阵中,只需执行model.merge_and_unload()。

总结

我们从简单的(尽管是硬编码的)LoRA实现,深入了解了LoRA、它们的实际实现和基准测试。并且介绍了另一种更有效的实现策略,并深入研究了用于LoRA集成的PEFT等现有库的优点。

完整的代码可以在这里找到:

https://avoid.overfit.cn/post/ed4e5c208ab64a4d9296f5667dfe50ac

作者:Martin Dittgen

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

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

相关文章

uniapp H5项目使用ucharts的Echart组件方式创建圆环

问题:没有报错但是图表不出来 【 调试了半天圆环图表没有不出来。是因为没有明示设置宽度与高度】 /* 请根据实际需求修改父元素尺寸,组件自动识别宽高 */ .charts-box { width: 100%; height: 300px; } 最终效果 先导入ucharts到项目 uniapp的项目…

大模型(LLM)+词槽(slot)构建动态场景多轮对话系统

构建动态场景多轮对话系统 引言 在人工智能和自然语言处理领域,聊天机器人的开发一直是一个热点话题。近年来,随着大型语言模型(LLM)的进步,构建能够理解和响应各种用户需求的聊天机器人变得更加可行和强大。本文将介…

机器学习---决策树

介绍 决策树和随机森林都是非线性有监督的分类模型。 决策树是一种树形结构,树内部每个节点表示一个属性上的测试,每个分支代表一个测试输出,每个叶子节点代表一个分类类别。通过训练数据构建决策树,可以对未知数据进行分类, 随机森林是由多个决策树组成,随机森林中每…

很抱歉,Midjourney,但Leonardo AI的图像指导暂时还无人能及…至少目前是这样

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…

使用Python Scrapy设置代理IP的详细教程

目录 前言 一、代理IP的作用和原理 二、Scrapy框架中设置代理IP的方法 步骤1:安装依赖库 步骤2:配置代理IP池 步骤3:创建代理IP中间件 步骤4:激活代理IP中间件 步骤5:运行爬虫程序 三、代码示例 四、常见问题…

Redis7--基础篇9(SpringBoot集成Redis)

1. jedis、lettuce、Redistemplate的关系 第一代为jedis,之后推出了lettuce,然后springboot继承了Redistemplate,现推荐使用Redistemplate。 总的来说,jedis、lettuce、Redistemplate都是java操作Redis数据库的驱动。 2. 本地Ja…

一文快速了解oCPX

01 什么是oCPX o是Optimized,优化一词的首字母。 CPX即Cost Per X,“X”即泛指传统的那些按不同方式进行结算的模式,如:CPC(Cost Per Click,按点击付费)、CPM(Cost Per Mille&…

我从阿里云学到的返回值处理技巧

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO 联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬 阿里云CosmoController…

为什么多片DDR菊花链拓扑连接时末端需要接很多的电阻

大家如果做过DDR的设计可能会发现在进行多片DDR连线时,通常在信号的末端会放置很多的电阻(如下图所示),那么这些电阻都是起什么作用的呢? 通常在DDR末端的电阻是为了防止信号反射的,起阻抗匹配的作用,之前我们介绍过另…

linux修改用户uid和gid并且修改文件所有权(所属用户及所属用户组)(chown命令、chgrp命令)(批量修改查找并修改文件、目录uid和gid)

文章目录 修改Linux用户UID和GID以及文件所有权1. 修改用户的UID和GID1.1 用户UID和GID的概念1.2 修改用户UID1.3 修改用户GID 2. 修改文件所有权2.1 文件所有权的概念2.2 修改文件所有者(chown命令)2.3 修改文件所属用户组(chgrp命令&#x…

Apache CouchDB 垂直权限绕过漏洞 CVE-2017-12635 已亲自复现

Apache CouchDB 垂直权限绕过漏洞 CVE-2017-12635 已亲自复现 漏洞名称影响版本影响版本 漏洞复现环境搭建漏洞利用 总结 漏洞名称 影响版本 Apache CouchDB是一个开源的NoSQL数据库,专注于易用性和成为“完全拥抱web的数据库”。它是一个使用JSON作为数据存储格式…

设计模式——代理模式(Proxy Pattern)

概述 代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在访问对象和目标对象之间起到中介作用。代理对象也可以在不修改目标对象的前提下,提供额外的功能操作,拓展目标对象的功能,比如说在目标对象的某个方法…

LeetCode 1901. 寻找峰值 II:二分查找

【LetMeFly】1901.寻找峰值 II:二分查找 力扣题目链接:https://leetcode.cn/problems/find-a-peak-element-ii/ 一个 2D 网格中的 峰值 是指那些 严格大于 其相邻格子(上、下、左、右)的元素。 给你一个 从 0 开始编号 的 m x n 矩阵 mat &#xff0c…

C#调用阿里云接口实现动态域名解析,支持IPv6(Windows系统下载可用)

电信宽带一般能申请到公网IP,但是是动态的,基本上每天都要变,所以想到做一个定时任务,随系统启动,网上看了不少博文很多都支持IPv4,自己动手写了一个。 (私信可全程指导) 部署步骤…

20231218在Ubuntu18.04下以EXT4格式化HDD

20231218在Ubuntu18.04下以EXT4格式化HDD 2023/12/18 17:24 缘起: 编译一个Android10大概要200GB,编译10个Android10的SDK,3TB的HDD机械硬盘就估计会被填满了! 如果使用rm -rf *这个命令将SDK一个一个逐个地删除,估计2…

强大的电子书阅读器:OmniReader Pro for mac

🔍 OmniReader Pro 是一款专为 Mac 设计的强大阅读工具,它能够帮助你更高效地阅读和处理各种文本内容。无论是电子书、新闻文章、网页文本还是文件资料,OmniReader Pro 都能胜任! ✅ OmniReader Pro 提供了丰富的功能&#xff0c…

UE5 C++(六)— 枚举UENUM、结构体USTRUCT和补充属性说明符

文章目录 枚举(ENUM)第一种方式第二种方式 结构体(USTRUCT)补充属性说明符(ExposeOnSoawn)结构体创建数据表格 枚举(ENUM) 第一种方式 定义枚举 UENUM(BlueprintType) namespace …

java配置+J_IDEA配置+git配置+maven配置+基本语句

当前目录文件夹dir 进入文件夹cd 返回上一级cd.. 创建文件夹:mkdir 文件名删除文件夹:rd 文件夹名, 目录不为空不能直接删 rd /s 带子文件夹一起删 清屏cls 切换d盘才能进入 下载git地址: Git - Downloading Package (g…

Linux网络编程(一):网络基础(上)

参考引用 UNIX 环境高级编程 (第3版)嵌入式Linux C应用编程-正点原子 1. 网络通信概述 网络通信本质上是一种进程间通信,是位于网络中不同主机上的进程之间的通信,属于 IPC 的一种,通常称为 socket IPC,网络通信是为了解决在网络…

德思特EMC RICI测试方案助您对抗电磁设备干扰!

来源:德思特测试测量 德思特方案丨德思特EMC RICI测试方案助您对抗电磁设备干扰! 原文链接:https://mp.weixin.qq.com/s/D8wdQr_reaFG-yppT8nzkw 欢迎关注虹科,为您提供最新资讯! 方案背景 电磁或射频干扰的敏感性&…