【LLM】vLLM部署与int8量化

news2024/11/24 18:42:02

Acceleration & Quantization

image-20231217234546788

vLLM

vLLM是一个开源的大型语言模型(LLM)推理和服务库,它通过一个名为PagedAttention的新型注意力算法来解决传统LLM在生产环境中部署时所遇到的高内存消耗和计算成本的挑战。PagedAttention算法能有效管理注意力机制中的键和值,将它们分割成更小、更易于管理的块,从而减少了vLLM的内存占用,并使其吞吐量超过传统LLM服务方法。

下图展示了三种不同配置下的语言模型(分别为LLaMA-13B、LLaMA-7B配备A100-40GB GPU和LLaMA-7B配备A10G GPU)在处理文本生成推理(TGI)任务时的服务吞吐量(每分钟处理的请求量)。我们可以看到,vLLM在所有配置中都提供了最高的吞吐量,这表明其在处理大规模模型推理任务时的高效性。

Uploaded image

在LLaMA-13B, A100-40GB配置中,vLLM的吞吐量为154.2 req/min,而传统的HuggingFace(HF)模型和TGI分别只有6.4和61.8 req/min。同样,在LLaMA-7B, A100-40GB和LLaMA-7B, A10G配置中,vLLM都显著超过了HF和TGI。

这些数据强调了vLLM在提高服务吞吐量方面的能力,特别是当每个请求需要一个输出完成时,vLLM的吞吐量是HF的24倍,并且是TGI的2.2x到2.5x。当每个请求需要三个输出完成时,vLLM的性能更加突出,其吞吐量是HF的8.5x到15x,并且是TGI的3.3x到3.5x。

可以认为,vLLM因其在服务吞吐量方面的卓越性能,特别适合于生产环境中部署大型语言模型,尤其是在需要高效率处理大量推理请求的场景。

Cache

Cache是目前生成式的Transformer常用的一种加速手段。

下图解释了在有缓存的情况下计算(Q * K^T)* V,即查询(Q)、键的转置(K^T)和值(V)的乘积的过程。这是一个注意力机制计算的典型步骤,常见于自然语言处理中的Transformer架构。

image-20231217235932215

  • 步骤1:查询(Q)与键的转置(K^T)相乘,然后再与值(V)相乘得到结果。在这个过程中,键(K)和值(V)被缓存到缓存内存中,以便将来使用。

  • 步骤N:在一个后续步骤中,从缓存中恢复键(K)和值(V),并只计算当前步骤中所需的部分。这样可以提高计算效率,因为可以重复使用之前步骤中已计算和缓存的部分。

蓝色部分表示在当前步骤中将要计算的值,而紫色部分表示将从缓存中取出的值。这种方法通过减少重复计算和利用缓存中的数据,提高了计算效率。

这两张图比较了在自然语言处理模型中,特别是在Transformer架构的注意力机制中,有和没有KV-Cache(键值缓存)时的处理过程。

Without KV-Cache:

image-20231218000647970

  • 每一个输入Token(例如,“我是一个好”的每个词)都会通过Q、K、V矩阵得到相应的查询(Q)、键(K)和值(V)。
  • 然后进行Attention计算,此过程包括Q和K的点积,然后用这个结果来加权V,得到最后的输出。
  • 由于没有KV-Cache,每次处理新Token时都需要重新计算整个序列。
With KV-Cache:

image-20231218001010611

  • 在处理序列的第一个Token时,也是通过Q、K、V矩阵得到查询、键和值。
  • 计算Attention后,K和V会被存储在缓存中。
  • 当处理下一个Token时(例如,“好”字),模型会使用缓存中的K和V值,而不是重新计算它们。
  • 这样可以提高效率,因为模型不必每次都处理整个序列,而是可以重复使用已经计算过的值。

在这个例子中,“我是一个好人”中的每个字会被依次处理,而使用KV-Cache可以减少重复计算,从而提高模型的效率。

为什么Cache只存储K、V,而不存储Q?

在Transformer架构的注意力机制中,Q(Query)代表当前输入Token的查询信息,它需要与所有先前和当前的K(Key)进行匹配来决定注意力的分配。因为Q是专门针对当前正在处理的Token的,所以它每次都是独一无二的,即便在没有Cache的情况下,对于序列中的每个新Token,Q都会改变。

相反,K(Key)和V(Value)通常与序列中已经处理过的Token相关,它们可以被缓存起来供后续的Token查询使用。存储K和V使得模型可以重用之前Token的信息,而不必重新计算它们,这样可以大大节省计算资源。如果存储Q值,对于每个新的Token,我们仍然需要重新计算整个Q与所有K的匹配度,这不会减少计算量。因此,只缓存KV值而不缓存Q值,是为了优化计算效率。

PageAttention

铺垫了这么多我们来介绍一下vLLm的PageAttention方法。

image-20231218100236221

PageAttention机制是一种优化的注意力机制,通过将键(Key)和值(Value)分块来处理,以减少计算资源的使用。

0. Before generation

image-20231218100602826

上图表示的是文本生成过程开始前的状态,即逻辑KV缓存块和物理KV缓存块的初始状态

逻辑内存是按需分配的 (由PyTorch或者cuda?),而物理内存是实际存储数据的硬件 (GPU) 。

在图中,我们有一个提示(Prompt):“Alan Turing is a computer scientist”,接下来需要生成(Completion)的文本。逻辑KV缓存块还没有填充任何数据,这表示我们还没有开始文本生成过程。物理KV缓存块同样是空的,显示没有任何键(Key)或值(Value)被存储。

1. Allocate space and store the prompt’s KV cache

image-20231218101421180

在这张图中,我们看到了提示(Prompt)“Alan Turing is a computer scientist” 的KV缓存已经被分配和存储。逻辑KV缓存块中的Block 0被填充了单词 “Alan”、“Turing”、“is”、“a”,而Block 1则被填充了 “computer” 和 “scientist”。

同时,物理KV缓存块表格显示Block 0实际上被映射到了物理块7,且有4个槽位被填充;Block 1映射到了物理块1,有2个槽位被填充。这表示系统已经为这些词分配了物理存储空间,并且准备好了用于生成过程中的查询。这是将提示转换为KV对并存储到缓存系统中的第一步,为接下来的文本生成做准备。

2. Generated 1st token

image-20231218101734287

到了这步,文本生成进程已经开始,并且生成了第一个词“and”。在逻辑KV缓存块中,我们可以看到“and”已经被添加到Block 1中,这个块之前已经包含了“computer scientist”。

同时,物理KV缓存块的表格显示Block 1现在填充了3个槽位,这表明“and”已经被添加到了物理块1中。这反映了随着生成过程的进行,逻辑和物理缓存块是如何动态更新以包含新生成的Token的。这允许模型在后续的生成步骤中利用这些新的信息。

3. Generated 2nd token

image-20231218102200166

接下来,第二个词“mathematician”已经被生成。逻辑KV缓存块的Block 1现在包含了“computer”、“scientist”、“and”以及新生成的“mathematician”。

4. Generated 3rd token. Allocate new block.

image-20231218102623397

在这张图中,我们看到了文本生成过程的进一步发展。此时"renowned" 这个词已经生成并被添加到了逻辑KV缓存块的Block 2中。对应地,在物理KV缓存块中,一个新的块(Block 3)被分配来存储这个新词,这显示了缓存系统如何动态地根据需求分配空间。

5. Generated 4th token.

image-20231218102816955

接下来生成的词 “for”,它也被添加到了逻辑KV缓存块的Block 2中。同样,物理KV缓存块的Block 3现在填充了两个槽位,包含了 “renowned” 和 “for”。

这一系列图像描绘了一个动态的、逐步构建的过程,其中每个新生成的Token都被添加到逻辑和物理KV缓存中,以支持连续的文本生成。随着生成的进行,物理缓存块持续更新,以确保快速访问到相关的键和值。

Prompt to Muitiple Outputs

image-20231218135929073

0. Shared prompt: Map logical blocks to the same physical blocks.

image-20231218140007123

在处理多个序列(例如 Seq A 和 Seq B)时vLLM会共享一个共同的提示(Prompt)。这种设置可能用于一个模型同时进行多个任务,或者在多任务学习环境中。逻辑KV缓存块展示了不同序列可以共享相同的提示“ The future of artificial intelligence is”,而这个提示被映射到相同的物理KV缓存块上。

这种共享方式表明,当多个序列处理相同的初始信息时,可以节省资源和时间,因为模型不需要为每个序列单独存储或计算相同的提示信息。这对于优化处理效率和加快推理速度是有益的。

1. Seq A generated 1st token.

image-20231218141549458

此时我们假设 Seq A 生成了一个额外的词 “likely”,并且这个词被添加到了它的逻辑KV缓存块中。物理KV缓存块显示了对应的条目,并有一个引用计数“Ref count: 2”,这意味着有两个不同的逻辑块正在引用这个物理块。

2. Copy-on-Write: Copy to a new block.

这些图展示了“写时复制”(Copy-on-Write)策略在缓存管理中的应用。当Seq A和Seq B共享相同的物理KV缓存块,并且当Seq A更改其内容时(添加了“likely”),引用计数从2减少到1,表明这些物理块不再被Seq B共享。然后,Seq A的更改被复制到一个新的物理块中,以防止对Seq B的逻辑视图产生影响。这样,每个序列可以独立地维护和更新其状态,而不会干扰到其他序列。

image-20231218142539552

image-20231218142625456

image-20231218142651531

这种机制在多任务处理和内存优化中非常有用。

3. Seq B generated 1st token. No copy needed.

image-20231218142726011

接下来当 Seq B 生成时,不会进行 Copy。

4. Seq A and B generated 2nd token.

image-20231218142959326

同理生成第2个token。

5. Seq A and B generated 3rd token.

image-20231218143026307

分配新的 Logical-Physical 映射,生成第3个token。

6. Seq A and B generated 4th token.

image-20231218143141013

继续生成。

Summary

总结一下,vLLM优化了内存使用上的一些浪费,同时也能够兼容huggingface上一些主流的模型。

image-20240107194320244

vLLM推理部署

vLLM的调用

有两种方式可以调用vLLM来加载模型,一种是通过API Server,另一种是离线推理。

  • API Server
!VLLM_USE_MODELSCOPE=True python -m vllm.entrypoints.api_server \
--model="./output/models/llama-"
curl http://localhost:8000/generate \
-d '{
"prompt": "San Francisco is a",
"use_beam_search": true,
"n": 4,
"temperature": 0
}'
from transformers import AutoModel

model = AutoModel.from_pretrained("../Llama-2-7B-Chat-fp16", load_in_8bit=True)
for name, params in model.named_parameters():
    print(name, params.dtype)

image-20240107224635255

  • Offline Batched Inference
from vllm import LLM, SamplingParams
import torch

# device = torch.device('cuda:2')
llm = LLM(model="../Llama-2-7B-Chat-fp16", gpu_memory_utilization=0.7)
# llm.to(device)

这里的gpu_memory_utilization参数就是vLLM提供的内存优化参数。

可以做个实验

这是我原来的内存,目前只有13M的占用

image-20240108153317911

如果我们直接在24G的现存中加载半精度的Llama-2-7B,会报OM(Out of Memory)

from transformers import AutoModel

model = AutoModel.from_pretrained("../Llama-2-7B-Chat-fp16")
model = model.to("cuda:0")

事实上,要加载半精度的Llama-2-7B需要大约29152MiB的内存空间(对,很尴尬地卡住了24G内存)。

但是,使用vLLM的gpu_memory_utilization这个参数,我们可以对加载内容进行大小优化

from vllm import LLM, SamplingParams

llm = LLM(model="../Llama-2-7B-Chat-fp16", gpu_memory_utilization=0.7)
image-20240108160842691

可以看到大约用了17G的内存。

当然,空间不可能无限优化下去,如果进一步测试可以发现gpu_memory_utilization=0.3是能够加载一个7B模型的极限(大约15G)。

下面来测试一下

prompts = [
    "Hello, my name is",
    "The president of the United States is",
    "The capital of France is",
    "The future of AI is",
    "One way to crack a password",
    "I know unsensored swear words such as"
]
sampling_params = SamplingParams(temperature=0.8, top_p=0.95)
outputs = llm.generate(prompts, sampling_params)

# Print the outputs.
for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")

image-20240107225150561

还是很快的,1秒可以迭代15句。

vLLM with LoRA

vLLM其实一开始并不支持peft,因为显然它在对内存优化的时候改变了一些模型的结构(vllm-vllm-model_executor_models下可以看到它将主要模型的结构都自己重新写了一遍),但这就导致了我们无法对vLLM加载得模型进行LoRA微调。

image-20240108164835286

不过好在Cassia大佬提交了一个repo,加上了vLLM对peft的支持(需要安装以下版本的vllm)

pip install git+https://github.com/troph-team/vllm.git@support_peft

我们用OPT为例看看Cassia大佬究竟做了些什么(lora)

from vllm import LLM, SamplingParams
from vllm.model_executor.adapters import lora

# Create an LLM.
llm = LLM(model="../opt-125m", gpu_memory_utilization=0.2)

prompts = [
    "Hello, my name is",
    "The capital of France is",
    "The future of AI is",
]

sampling_params = SamplingParams(temperature=0, top_k=-1)  # 不做任何sampling,每次只选择最大概率的token

outputs = llm.generate(prompts, sampling_params)

image-20240107225451488

for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")

image-20240107225525834

llm.llm_engine.workers[0].model

image-20240107225625595

在这里使用lora库的LoRAModel加载了一个adapter(看名字应该是基于imdb数据finetune的一个opt-adapter)

# Add LoRA adapter
lora.LoRAModel.from_pretrained(llm.llm_engine.workers[0].model, "../opt-125m-imdb-lora")

outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")

image-20240107225650822

可以看到微调后OPT输出的内容是不一样的。

vllm@support_peft

简单过一下vllm的结构。

最重要的是vllm-vllm-core下的block_manager.py,负责对内存进行动态分配

image-20240108163032619

我们在前两篇文章中讲过,peft是如何加上LoRA这个旁路的:

  1. 遍历原来model的所有层数,找到我们需要加上LoRA的某(几)个module(比如self-attetion);
  2. 把找到的module单独抠出来,放到一个新的module(LoRA module)里面;
  3. LoRA module中包含A、B两个矩阵,输入给到A、B矩阵后得到的输出 与 原来module的输出相加,得到新的输出。

如果要让vLLM支持peft技术,其实就是要让LoRA中的module对应上vLLM中的module,然后把LoRA module挂到module的旁路上面去。

还是以vllm-vllm-model_executor_models下的llama.py为例,仔细审阅一下LlamaAttention模块

image-20240108165901019

hidden_states作为输入计算qkv。显然,我们需要让hidden_states同时作为输入给到LoRA module。这里Cassia大佬的实现是在lora.py中,他在load_adapter方法中将原本的qkv_proj模块抠出来扔到了VllmLoRA这个LoRA module中,然后将原来的module替换成新的

image-20240108171130294

如此一来,当qkv, _ = self.qkv_proj(hidden_states)这句话调用qkv_proj这个模块时,实际上调用的是被lora.py替换后的qkv_proj

最后来看下VllmLoRA这个LoRA module是怎么写的

image-20240108170532064

  1. 类定义

    • VllmLoRA 继承自 LoraLayerColumnParallelLinear
  2. 构造函数

    • 构造函数接收多个参数,包括输入大小(input_size)、输出大小(output_size)、LoRA参数(rlora_alphalora_dropout)和其他关键字参数。
    • init_lora_weights 用于控制是否初始化 LoRA 权重。
    • 使用 ColumnParallelLinearLoraLayer 的构造函数初始化该层。
    • 重置线性层的参数。
    • 使用 update_layer 方法初始化 q_projk_projv_proj 这三个LoRA层。
    • active_adapter
  3. 前向传播(forward 方法)

    • 这个方法首先调用 ColumnParallelLinear 的前向传播方法来处理输入。
    • 将输入数据转换为 LoRA 层所需的数据类型。
    • 对于 q_projk_projv_proj,使用 LoRA 层进行计算,并将结果相加到原始的线性层输出上。
    • 返回最终结果和偏置。

以上,就是vLLM支持LoRA的方法。

Decoding methods

之前在将分页机制的时候讲到一个Prompt to Muitiple Outputs,一个prompt如何生成多种不同的output,这取决于使用的decoding method。

Greedy Search

Greedy search就是贪心,每次取下一步最大的那个token

image-20240108200637709

Beam Search

Beam search需要一个参数num_beams,用于决定往后看几步,每次取后几步最大的beam中第一个token

image-20240108200815028

Sampling

每次对下一个token生成的概率进行采样,每个token都有可能被选择

image-20240108201239411

Top-K Sampling

Sampling存在的问题是,它只考虑了上图中那种比较理想的分布情况。然而在现实中,很多token分布的值可能会很接近,所以Top-K Sampling的做法是对采样区域进行一定的限制,只在前K个token之间进行采样

image-20240108201956475

Top-p (nucleus) Sampling

还有种Sampling方法叫做Top-p (nucleus) sampling,当前p个分布之和大于定值(比如0.9)时,将前p个token作为取样范围

image-20240108202125867

Quantization 量化

网上开源的很多模型本身是全精度的,我们用半精度进行加载量化效果较好,但是如果在进行进一步的量化效果可能就会很差。原因在于全精度意味着更多的参数,在复杂情况下,量化带来的误差会被越放越大,导致模型的表现大幅下降。

下图就是半精度16-bit基准线与8-bit基准线的对比,随着参数量变大8-bit基准线会骤降

image-20240108204437428

于是,量化中最常用的一种技术LLM.int8出现了,它可以用8-bit达到16-bit基准线的效果。

8-bit量化

在介绍LLM.int8技术之前,先看看8-bit量化是如何做的。

8-bit量化的意思是将原来半精度浮点数转换成int_8(int_8的范围是-177~177),举个例子:

Example 1
  • 原始数据: x = [1.52, 2.64, -3.45, 4.32]
  • 量化过程:
    • x_absmax = 4.32 # 找最大
    • scale_factor=127 / x_absmax ≈ 29.4 # 计算量化因素
    • q_x = round([1.52, 2.64, -3.45, 4.32] * scale_factor) =[45, 78, -101, 127] # 将半精度浮点数映射到int_8的数组,占用空间会少一半
  • 反量化过程:
    • x’ = q_x/scale_factor = [1.53, 2.61, 3.44, 4.32] # 除了最大值不变,其他参数会有略微误差
Problem 1

量化过程存在量化误差

  • 原始数据:x=[1.52,2.64,-3.45,4.32]
  • 反量化结果:x’=[1.53,2.61,3.44,4.32]

如何降低量化误差,提高量化精度?

  • 使用更多的量化参数(scale_factor)
  • 矩阵乘法A*B可以看作是A的每一行乘上B的每一列,为A的每每一行和B的每一列单独设置scale_factor,这种方式被称之为Vector-wise量化
Example 2
  • Data: x = [1.42,1.51,1.54,45.3]
  • Quantization process:
    • x_absmax = 45.3
    • scale_factor=127 / x_absmax ≈ 2.8
    • q_x = round([1.42, 1.51, 1.54, 4.32] * scale_factor) = [4, 4,4, 127]
  • Dequantization process:
    • x’ = q_x/scale_factor = [1.43, 1.43, 1.43, 45.36]
Problem 2

很明显,当最大值相对较大时,其他数值的误差就会很大:

  • 离群值:超出某个分布范围的值通常称为离群值
    • x = [1.42, 1.51, 1.54, 45.3]
  • 8位精度的动态范围极其有限,因此量化具有多个大值的向量会产生严重误差
  • 误差在一点点累积的过程中会导致模型的最终性能大幅度下降

LLM.int8()

其实很简单,LLM.int8()方法通过将16位浮点数的特征和权重矩阵分解成小的子矩阵,并将它们转换为8位整数来进行高效的矩阵乘法运算。

该方法特别处理矩阵中的离群值(通过均值和方差),这些值保持为16位浮点数以保证计算的精度。

完成乘法运算后,方法会对结果进行反量化处理,将8位整数输出转换回16位浮点数,然后将离群值结果和常规结果累加以得到最终输出。

image-20240109102147862

这个过程优化了计算效率,同时尽量减少了精度损失。

还是做个实验

from transformers import AutoModel

model = AutoModel.from_pretrained("../Llama-2-7B-Chat-fp16", load_in_8bit=True)

用int_8来加载,只用了7661M

image-20240109103324840
for name, params in model.named_parameters():
    print(name, params.dtype)

image-20240109103504476

LLM.int8()会自动处理哪些需要转成int_8,哪些需要保留float_16。

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

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

相关文章

重置 Docker 中 Gitlab 的账号密码

1、首先进入Docker容器 docker exec -it gitlab bash 2、连接到 gitlab 的数据库 需要谨慎操作 gitlab-rails console -e production 等待加载完后会进入控制台 ------------------------------------------------------------------------------------------------------…

Page 251~254 Win32 GUI项目

win32_gui 源代码&#xff1a; #if defined(UNICODE) && !defined(_UNICODE)#define _UNICODE #elif defined(_UNICODE) && !defined(UNICODE)#define UNICODE #endif#include <tchar.h> #include <windows.h>/* Declare Windows procedure */…

知名开发者社区Stack Overflow发布《2023 年开发者调查报告》

Stack Overflow成立于2008年&#xff0c;最知名的是它的公共问答平台&#xff0c;每月有超过 1 亿人访问该平台来提问、学习和分享技术知识。是世界上最受欢迎的开发者社区之一。每年都会发布一份关于开发者的调查报告&#xff0c;来了解不断变化的开发人员现状、正在兴起或衰落…

[机缘参悟-122] :IT人如何认识自己的?自省、面试、考核、咨询?

目录 一、为什么要认识自己 二、认识自己的哪些方面&#xff1f; 三、如何认识自己 3.1 通过自省认识自己 3.2 通过面试认识自己 3.3 通过咨询认识自己 3.4 通过相亲认识自己 3.5 通过一段感情关系认识自己 一、为什么要认识自己 认识自己在人类的成长和心灵发展过程中…

亚马逊实时 AI 编程助手 CodeWhisperer使用体验

文章目录 1&#xff1a;什么是CodeWhisperer &#xff1f;2&#xff1a;试用3&#xff1a;上手体验 1&#xff1a;什么是CodeWhisperer &#xff1f; 最近ChatGPT展现出强大AI能力给我们带来了深刻的影响&#xff0c;AI现在不是一个概念&#xff0c;基于AI的产品一定在各行各业…

Linux网络配置与抓包工具介绍

目录 一、配置命令 1. ifconfig 1.1 概述信息解析 1.2 常用格式 2. ip 2.1 ip link 数据链路层 2.2 ip addr 网络层 2.3 路由 3. hostname 3.1 临时修改主机名 3.2 永久修改主机名 4. route 5. netstat 6. ss 7. ping 8. traceroute 9. nslookup 10. 永久修…

vivado图形化设计篇

一.看懂波形 二.由波形可得真值表 三.可得逻辑表达式 YA(BC) 四. 逻辑框图 五.vivado图形化设计 &#xff08;1&#xff09;创建文件 1.create block desige 2.文件命名&#xff0c;设置文件放置地址 &#xff08;2&#xff09; 添加IP核 1.打开desige&#xff0c;右键&#…

UniRepLKNet实战:使用UniRepLKNet实现图像分类任务(一)

文章目录 摘要安装包安装timm 数据增强Cutout和MixupEMA项目结构计算mean和std生成数据集一些问题 摘要 大核卷积神经网络&#xff08;ConvNets&#xff09;近年来受到广泛关注&#xff0c;但仍存在两个关键问题需要进一步研究。首先&#xff0c;目前的大型卷积神经网络架构大…

如何在企业中实施自适应人工智能?

人工智能不再是企业的选择。很快&#xff0c;它也将不再是一个区分因素。商业中的适应性人工智能正在改变格局。根据最近的统计数据&#xff0c;95%的企业以上都在追求人工智能。 因此&#xff0c;为了确保你拥有竞争优势&#xff0c;你必须期待先进的人工智能选项。适应性就是…

CH341 SPI方式烧录BK7231U

CH341是一个USB总线的转接芯片&#xff0c;通过USB总线提供异步串口、打印口、并口以及常用的2线和4线等同步串行接口。 BK7231U Wi-Fi SOC芯片&#xff0c;内嵌处理器。1. 符合802.11b/g/n 1x1协议 2. 17dBm 输出功率3. 支持20/40 MHz带宽和STBC 4. 支持Wi-Fi STA、AP、…

回归预测 | Matlab基于SO-LSTM蛇群算法优化长短期记忆神经网络的数据多输入单输出回归预测

回归预测 | Matlab基于SO-LSTM蛇群算法优化长短期记忆神经网络的数据多输入单输出回归预测 目录 回归预测 | Matlab基于SO-LSTM蛇群算法优化长短期记忆神经网络的数据多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab基于SO-LSTM蛇群算法优化…

vivado 工程管理

管理项目 打开项目 当项目打开时&#xff0c;Vivado IDE会从项目已关闭。项目状态包括当前源文件顺序、已禁用和已启用 源文件、活动约束文件和目标约束文件&#xff0c;以及合成、模拟和实现运行。要打开项目&#xff0c;请使用以下方法之一&#xff1a; •在“入门”页面…

使用 STM32 和 DS18B20 温度传感器设计室内温度监测与报警系统

为设计室内温度监测与报警系统&#xff0c;我们将利用STM32微控制器和DS18B20数字温度传感器&#xff0c;以及蜂鸣器实现温度报警功能。在本文中&#xff0c;将介绍如何通过STM32微控制器读取DS18B20传感器的温度数据&#xff0c;并在超出设定范围时触发蜂鸣器报警。 1. 系统概…

QT上位机开发(键盘绘图控制)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 绘图是qt很基础的一个功能。通常&#xff0c;我们进行qt绘图的时候&#xff0c;一般会先创建一个qt view&#xff0c;这个相当于视图。接着创建一个…

使用git submodule解决高耦合度问题

引言 在开发我的笔记系统时&#xff0c;我遇到了一个问题。问题是&#xff0c;在api-gate服务中&#xff0c;我需要验证用户的access_code&#xff0c;但是access_code的生成逻辑是在auth2服务中实现的。这个问题从架构设计的层面上看&#xff0c;就是一个高耦合度问题。高耦合…

Linux文件系统与日志管理

目录 一、inode和block 1、inode表结构 2、 查看inode号码的命令 3、Linux系统文件三个主要时间属性 4、用户通过文件名打开文件时系统内部的过程 5、inode的大小 6、命令与inode 6.1 cp 命令&#xff1a; 6.2 rm 命令&#xff1a; 6.3 mv命令 二、日志 1、功能 2、…

patch-package的使用总结

有时使用了某个第三方库&#xff0c;可是它有些问题&#xff0c;我们不得不修改它的源码。我们可能不方便给原作者提 Pull Request&#xff0c;因为他们可能不愿意接受我们的更改。又或者原作者无法及时发布新版本。我们只有去修改 node_modules 目录下的文件。可是当我们执行 …

计算机组成原理-程序查询方式(流程图 演示过程 例题 定时查询 独占查询)

文章目录 总览IO方式简介程序查询方式程序查询方式流程图程序查询方式-例题小结 总览 IO方式简介 每次输一个字&#xff0c;就认为状态完成&#xff0c;CPU就会取走数据寄存器的内容 程序查询方式 此时模拟打印三个字符 假设此时三个字符在主存&#xff0c;CPU先从主存读一…

朴素贝叶斯(Naive Bayes)

什么是机器学习 朴素贝叶斯&#xff08;Naive Bayes&#xff09;是一组基于贝叶斯定理的分类算法&#xff0c;它基于特征之间的独立性假设&#xff0c;因此被称为“朴素”。尽管这个假设在实际情况中往往不成立&#xff0c;但朴素贝叶斯在实践中表现得相当好&#xff0c;并在文…

cpp_10_多重继承_钻石继承_虚继承

1 多重继承 一个类可以同时从多个基类继承实现代码。 1.1 多重继承的内存布局 子类对象内部包含多个基类子对象。 按照继承表的顺序依次被构造&#xff0c;析构的顺序与构造严格相反。 各个基类子对象按照从低地址到高地址排列。 // miorder.cpp 多重继承&#xff1a;一个子…