vLLM-prefix浅析(System Prompt,大模型推理加速)

原文:vLLM-prefix浅析(System Prompt,大模型推理加速)

简介

本文浅析了在大模型推理加速方面一个非常优秀的项目 vLLM 的一个新特性 Prefix。在 Prompt 中有相同前缀时可以提高吞吐量降低延迟,换句话说可以省去这部分相同前缀在自注意力阶段的重复计算。

更新 2024.1.18:Prefix 已经合并到主分支上了!如果你的 vLLM 不能使用也许是时候升级一下体验下新功能哩!

https://github.com/vllm-project/vllm​github.com/vllm-project/vllm

同时安利一下我在 Prefix 方面的一点小工作:添加了 Prefix 删除功能,使用了 Trie 来维护 Prefix 以实现多级Prefix,实现 Prefix 对 Alibi 的支持,对一批含有 Prefix 的询问进行贪心调度(一个特殊场景下的 Prefix 调度)。

DouHappy 的 vllm-prefix​github.com/DouHappy/vllm-prefix


更新 2024.1.18:已经有了更好的工作:SGLang

这篇博客中更加深入的讨论了关于 prefix sharing 的问题。他们同样采用了 Trie 的思想,使用 LRU 对 Prefix 片段进行调度。

https://lmsys.org/blog/2024-01-17-sglang/​lmsys.org/blog/2024-01-17-sglang/


问题背景Fast and Expressive LLM Inference with RadixAttention and SGLang | LMSYS Org问题背景

在很多大模型的实际应用场景下需要在给出大模型具体任务信息之前给模型介绍任务,可以让大模型在这项任务上的效果有不少提升,这里推一下有 9w 多 Star 的 awesome-chatgpt-prompts,这部分内容一般放在 Prompt 的最前端,在 PagedAttention(vLLM是PagedAttention的具体实现)论文中把这一段 Prompt 称之为 System Prompt。System Prompt 往往经过人工调整之后就固定不变了,模型每次在自注意力阶段都需要进行一段相同的计算,如果把这部分的计算结果保存下来,在以后的计算中就可以省去这部分计算,从而增加大模型服务的吞吐量,降低延迟。文本中所说的 Prefix 基于这个想法实现了更加一般化的实现。

相关工作

Triton

为了更高的效率,核心的 PagedAttention 使用了 Triton 语言,一个非常易于上手同时运行高效的 GPU 并行编程语言。为了文章的易读性,这篇文章简单略过对于项目中涉及到 Triton 的部分,主要写项目的实现思路。感兴趣可以在他们的 Github 仓库里看更详细的内容。

Triton​github.com/openai/triton

PagedAttention和vLLM

PagedAttention 最早在 Efficient Memory Management for Large Language Model Serving with PagedAttentio 这篇论文中正式提出,不过他们的项目 vLLM 很早就开始维护了。

Efficient Memory Management for Large Language Model Serving with PagedAttention​arxiv.org/abs/2309.06180

目前显卡吞吐量的瓶颈往往在于数据的传送带宽与计算速度的差距,显卡有很大部分时间是在等数据,因此如何同时塞更多的数据(增大 batchsize)来满足 GPU 的并行计算速度提高显卡吞吐量的一个重要想法。PagedAttention 是根据计算机操作系统中对内存的分页思想提出的,对大语言模型推理时的显存浪费问题提出了一种显存分页的 Attention 计算方法,能很大程度上节省在显存的浪费,从而提高 batchsize 的大小。因为显存的分页,所以 Token 不需要连续的存储在显存中,这中更灵活的存储方式给了很大的显存优化空间。

不同LLM服务系统的内存浪费百分比(绿色为利用的比例)

灰色是模型参数,粉色是预先申请的 KV Cache 用于之后 KV 值的存储。在相同的batchsize大小下vLLM远小于传统实现

Prefix

安装

需要下载 PR1669 中的代码并从下载的源码安装 vLLM。因为 vLLM 在 CUDA12.2 下编译的所以最好在 12.2 环境下安装。 vLLM 安装在官方的文档中有详细介绍。

Installation - vLLM​docs.vllm.ai/en/latest/getting_started/installation.html

Prefix功能使用方法Installation - vLLMPrefix功能使用方法

目前官方的实现只支持提供参数 prefix_pos 表示前多少个 token 是用户指定的 prefix。我实现了一个函数来比较方便的调用官方的接口。

import time
import datetime
import os

from vllm import LLM
from vllm import SamplingParams


def test_prefix(llm = None, sampling_params=None, prompts=None, prompt_token_ids=None, prefix_len=None, save_file=None, detile=True):
    assert prompts != None or prompt_token_ids != None, f"prompt and tokens can't both be None"
    if isinstance(prefix_len, int):
        prefix_len = [prefix_len]
        assert len(prompts) % len(prefix_len) == 0, f"len of prompts must be multiple of len of prefix_len"
    print("------start generating------")
    start_time = time.time()
    # whether use Prefix
    if prefix_len != None:
        # start inference
        if prompt_token_ids != None:
            outputs = llm.generate(prompt_token_ids=prompt_token_ids,
                                   sampling_params=sampling_params,
                                   prefix_pos=prefix_len * (len(prompts) // len(prefix_len)))
        else:
            outputs = llm.generate(prompts=prompts,
                                   sampling_params=sampling_params,
                                   prefix_pos=prefix_len * (len(prompts) // len(prefix_len)))
    else:
        outputs = llm.generate(prompts, sampling_params=sampling_params)

    end_time = time.time()
    print(f"cost time {end_time - start_time}")

    if save_file != None:
        print("saving output......")
        for index, output in enumerate(outputs):
            if detile == True:
                print(output, file=save_file)
            else:
                print(output.outputs[0].text, file=save_file)
        print(f"output saved in {save_file.name} {datetime.datetime.now()}")

# 你需要对下面这些参数进行改变
# set gpus
os.environ['CUDA_VISIBLE_DEVICES']="0"
tensor_parallel_size = len(os.getenv('CUDA_VISIBLE_DEVICES').split(','))
# set inference model
# 这里需要换成你的模型位置
model = "/data/images/llms/models--baichuan-inc--Baichuan2-13B-Chat"
# Create an LLM.
llm = LLM(model=model, tokenizer_mode='auto', trust_remote_code=True, tensor_parallel_size=tensor_parallel_size)
# get prompts
prompts = ["这是一个 Prefix 功能使用的示例,因为 Prefix 的存储以物理块为单位,所以 Prompt 的长度需要至少大于等于一个物理块,这是第一句话",
           "这是一个 Prefix 功能使用的示例,因为 Prefix 的存储以物理块为单位,所以 Prompt 的长度需要至少大于等于一个物理块,这是第二句话"]
prompt_token_ids = llm.tokenizer(prompts)
# set SamplingParams
sampling_params = SamplingParams(temperature=0,
                                 max_tokens=100,
                                 )

# prefix_len 是与 prompts 等长的 list,表示对应 prompts 的 prefix 长度,没有设为 None
with open("output.txt", 'w') as f:
    test_prefix(llm=llm,
                # prompts=prompts,
                prompt_token_ids=prompt_token_ids
                prefix_len=[16, 32],
                save_file=f,
                sampling_params=sampling_params,
                detile=False,
                )

官方还实现了一个在线版本的API,在目录 vllm/entrypoints/api_server.py 中的 generate 函数,但是只支持一次传一个,需要自己写异步函数来处理同时进行多次访问。

注意:

  1. 在同一个 batch 中因为在并行计算所以并不会利用 Prefix 进行加速,需要等待一个推理 batch 结束后 Prefix 计算出的信息才会存储到 KV cache 中供后续推理使用,这种行为暂时被称之为 warmup。
  2. 过多无用的 Prefix 保留在 KV cache 会因占用推理所需的显存空间导致推理速度下降甚至停止推理(没有空余显存空间)。
  3. 因 Prefix 的存储单位是以块的形式存储的,不同模型一个物理块存储的 Token 数量不同,以 Baichuan2-13B 为例,每个物理块为 16 个,在加入 Prefix 之前会先截断为 16 的整数倍再进行计算,截断的 16 个对速度几乎没有影响。这样做的方式是为了方便后续的计算和存储。

更加详细的采样参数和模型参数可以在 SamplingParams 和 LLM中查看。这里贴一下:

SamplingParams参数:
    """Sampling parameters for text generation.

    Overall, we follow the sampling parameters from the OpenAI text completion
    API (https://platform.openai.com/docs/api-reference/completions/create).
    In addition, we support beam search, which is not supported by OpenAI.

    Args:
        n: Number of output sequences to return for the given prompt.
        best_of: Number of output sequences that are generated from the prompt.
            From these `best_of` sequences, the top `n` sequences are returned.
            `best_of` must be greater than or equal to `n`. This is treated as
            the beam width when `use_beam_search` is True. By default, `best_of`
            is set to `n`.
        presence_penalty: Float that penalizes new tokens based on whether they
            appear in the generated text so far. Values > 0 encourage the model
            to use new tokens, while values < 0 encourage the model to repeat
            tokens.
        frequency_penalty: Float that penalizes new tokens based on their
            frequency in the generated text so far. Values > 0 encourage the
            model to use new tokens, while values < 0 encourage the model to
            repeat tokens.
        repetition_penalty: Float that penalizes new tokens based on whether
            they appear in the generated text so far. Values > 1 encourage the
            model to use new tokens, while values < 1 encourage the model to
            repeat tokens.
        temperature: Float that controls the randomness of the sampling. Lower
            values make the model more deterministic, while higher values make
            the model more random. Zero means greedy sampling.
        top_p: Float that controls the cumulative probability of the top tokens
            to consider. Must be in (0, 1]. Set to 1 to consider all tokens.
        top_k: Integer that controls the number of top tokens to consider. Set
            to -1 to consider all tokens.
        use_beam_search: Whether to use beam search instead of sampling.
        length_penalty: Float that penalizes sequences based on their length.
            Used in beam search.
        early_stopping: Controls the stopping condition for beam search. It
            accepts the following values: `True`, where the generation stops as
            soon as there are `best_of` complete candidates; `False`, where an
            heuristic is applied and the generation stops when is it very
            unlikely to find better candidates; `"never"`, where the beam search
            procedure only stops when there cannot be better candidates
            (canonical beam search algorithm).
        stop: List of strings that stop the generation when they are generated.
            The returned output will not contain the stop strings.
        stop_token_ids: List of tokens that stop the generation when they are
            generated. The returned output will contain the stop tokens unless
            the stop tokens are sepcial tokens.
        ignore_eos: Whether to ignore the EOS token and continue generating
            tokens after the EOS token is generated.
        max_tokens: Maximum number of tokens to generate per output sequence.
        logprobs: Number of log probabilities to return per output token.
            Note that the implementation follows the OpenAI API: The return
            result includes the log probabilities on the `logprobs` most likely
            tokens, as well the chosen tokens. The API will always return the
            log probability of the sampled token, so there  may be up to
            `logprobs+1` elements in the response.
        prompt_logprobs: Number of log probabilities to return per prompt token.
        skip_special_tokens: Whether to skip special tokens in the output.
        spaces_between_special_tokens: Whether to add spaces between special
            tokens in the output.  Defaults to True.
        logits_processors: List of functions that modify logits based on
            previously generated tokens.
    """

LLM加载参数:
"""An LLM for generating texts from given prompts and sampling parameters.

This class includes a tokenizer, a language model (possibly distributed
across multiple GPUs), and GPU memory space allocated for intermediate
states (aka KV cache). Given a batch of prompts and sampling parameters,
this class generates texts from the model, using an intelligent batching
mechanism and efficient memory management.

NOTE: This class is intended to be used for offline inference. For online
serving, use the `AsyncLLMEngine` class instead.
NOTE: For the comprehensive list of arguments, see `EngineArgs`.

Args:
    model: The name or path of a HuggingFace Transformers model.
    tokenizer: The name or path of a HuggingFace Transformers tokenizer.
    tokenizer_mode: The tokenizer mode. "auto" will use the fast tokenizer
        if available, and "slow" will always use the slow tokenizer.
    trust_remote_code: Trust remote code (e.g., from HuggingFace) when
        downloading the model and tokenizer.
    tensor_parallel_size: The number of GPUs to use for distributed
        execution with tensor parallelism.
    dtype: The data type for the model weights and activations. Currently,
        we support `float32`, `float16`, and `bfloat16`. If `auto`, we use
        the `torch_dtype` attribute specified in the model config file.
        However, if the `torch_dtype` in the config is `float32`, we will
        use `float16` instead.
    quantization: The method used to quantize the model weights. Currently,
        we support "awq". If None, we assume the model weights are not
        quantized and use `dtype` to determine the data type of the weights.
    revision: The specific model version to use. It can be a branch name,
        a tag name, or a commit id.
    tokenizer_revision: The specific tokenizer version to use. It can be a
        branch name, a tag name, or a commit id.
    seed: The seed to initialize the random number generator for sampling.
    gpu_memory_utilization: The ratio (between 0 and 1) of GPU memory to
        reserve for the model weights, activations, and KV cache. Higher
        values will increase the KV cache size and thus improve the model's
        throughput. However, if the value is too high, it may cause out-of-
        memory (OOM) errors.
    swap_space: The size (GiB) of CPU memory per GPU to use as swap space.
        This can be used for temporarily storing the states of the requests
        when their `best_of` sampling parameters are larger than 1. If all
        requests will have `best_of=1`, you can safely set this to 0.
        Otherwise, too small values may cause out-of-memory (OOM) errors.
"""

PrefixPool和Prefix

目前 vLLM 中对于 Prefix 功能的实现主要是通过两个类 Prefix 和 PrefixPool 来进行管理

Prefix的主要运行逻辑如下:

  1. add_request。检查用户每个请求中的 Prefix 是否已经存在,若不存在则加入 PrefixPool 中(此时未分配物理块)
  2. schedule。vLLM选择一部分未结束的用户请求放入Running队列准备进行推理,此时进行物理块的分配。若 Prefix 未被分配物理块则为 Prefix 分配。
  3. step。进行一步推理,一步推理,定义为一次 Self-Attention 或一次 Cross-Attention,具体取决为用户的请求出于哪一个状态。
  4. 将推理结果中需要存储的部分存储到指定物理块中。
  5. 回到步骤2

PrefixTrie

PrefixPool 以 token 的 hash 值作为关键字形成一个字典,来保存对应的 Prefix。PrefixTrie 基于 Trie 的思想以Token 为关键字来维护 Prefix,这样能够在多个 Prefix 之间有相同前缀时实现物理块的共享,进一步减小了对显存的占用,还可以快速搜索最长匹配的 Prefix,对一些特殊场景下可能有较大的帮助。

若对 PrefixTrie 的实现感兴趣可以看我的仓库,欢迎提出意见。

Prefix调度策略

目前官方没有实现 Prefix 的删除功能,但已经有佬实现了一种基于 FIFO 的 Prefix 调度策略,即限制 Prefix 占用的显存大小,超过限制时删除最早加入的Prefix,这种调度策略最直接,对于各种场景下都有一定的优化。

因为应用场景的原因,我遇到的应用场景知道所有询问和对应的Prefix,这种场景下我实现了一个基于贪心的调度策略,对于 Prefix 的 warmup 过程进行了优化。主要逻辑如下:

  1. 根据使用的 Prefix 不同对用户请求进行分配将信息存储在 PrefixGroup 这个类中
  2. 计算每种 Prefix 相关的所有请求预估需要多少显存(物理块数量)
  3. 根据所需的物理块数量对 PrefixGroup 进行降序排序
  4. 根据排序的顺序依次贪心判断预估下一个 batch 剩余空间能否完成对该 Group 的所有请求,能完成则加入推理队列
  5. 将加入推理队列中的所有 Prefix 进行 Warmup(只进行 Self-Attention 不进行 Cross-Attention)
  6. 对所有加入推理队列中的请求进行推理并收集推理结果,然后删除对应 Prefix。
  7. 回到步骤 4 直到没有 Group 能加入推理队列
  8. 对于剩余请求依次进行 Warmup 和推理

Self-Attention 和 Prefix

对于一般的 Self-Attention 计算,qkv 三者的长度都是相同的即序列的长度,但是在 Prefix 的情况下因为属于 Prefix 部分的 q 是不需要参与 Self-Attention 计算的即我们希望优化掉的重复计算,所以 q 的长度会少 Prefix 的部分。一般来说qkv形状都为[batchsize, 序列最大长度,注意力头数量,注意力头大小],使用 Prefix 情况下kv长度不变,q的第二维长度变为除去 Prefix 之后的 Token 长度。传统方法中对每个询问都需要填充到最长长度以进行并行计算,但是这样会造成较大的内存浪费,vLLM 将第一维和第二维即 “batchsize”和“序列最大长度”这两维压缩到一维,不采用填充的方式形成 batch,而采用拼接的方式。这样能节省需要的显存空间。

拼接后使用 PagedAttention 算子进行计算,PagedAttention 具体实现中利用 Triton 语言并融合了 FlashAttention 的思想进行了优化。

这里有一个位置编码问题:对于 Rope 或者传统的位置编码都是在算子外部添加偏移,但是 Alibi 需要在计算 qk 后添加偏移,因此 vLLM 中有两个算子,其中一个算子在计算中加入Alibi,这也是这两个算子的唯一区别。

Prefix 的删除

Prefix的删除涉及两个方面,一个是逻辑块的释放,一个是存储 Prefix 的数据结构的删除。

  • 逻辑块的删除可以直接调用 BlockManager 接口。
  • 存储数据结构的删除涉及到具体数据结构的实现

PrefixPool 中的删除:

对于原 PrefixPool 的实现只需要删除列表和字典中对 Prefix 的记录即可,这里给出一种实现:

def delete_prefix(self, prefix_hash: int) -> Optional[int]:
    if prefix_hash not in self.prefixes_hash:
        return None
    
    prefix_id = self.prefixes_hash[prefix_hash]
    # physics block will be deleted in block_manager outside this function
    # del prefix
    self.prefixes_hash.pop(prefix_hash)
    for key, value in self.prefixes_hash.items():
        if value > prefix_id:
            self.prefixes_hash[key] -= 1

    del self.prefixes[prefix_id]
    
    return prefix_id

现在(2024.1.17)的存储方式是使用字典记录 Prefix 在列表中的编号,Prefix实际存储在列表中。之后的存储方式将直接采用 hash 值到 Prefix 的映射,因此这种实现方式后续应该会被简化。

供外部使用的接口实现:

def delete_prefix(self, prefix_tokens:List[int]) -> int:
    '''
    Input:
        prefix: the token_ids of prefix
    Output:
        deleted_id: the prefix_id of deleted prefix if successfully deleted
                    or None
    '''
    block_size = self.cache_config.block_size
    prefix_pos = len(prefix_tokens) // block_size * block_size
    truncated_prefix_token_ids = prefix_tokens[:prefix_pos]
    prefix_hash = hash(tuple(truncated_prefix_token_ids))
    prefix = self.scheduler.prefix_pool.fixed_search(prefix_hash)
    deleted_id = self.scheduler.prefix_trie.delete_prefix(truncated_prefix_token_ids)
    return deleted_id

实验效果

在 Baichuan2-13B-chat 上,约长 700 的 Prompt,其中前 596 为 Prefix,能提速 2-3 倍。

后序对于其他模型,其他常用 Prompt,其他数据(吞吐量,延迟)会做一些实验继续更新。


更新于 [UTC-8] 2024.1.18-18:26

总时间Warmup时间推理时间数据推理框架
llama-7b7.790.137.24107组
prompt:约550
perfix:530
output:30
vLLM-prefix
llama-7b2.70--SGLang
llama-7b3.52--vLLM-genreate
llama-7b31.000.1229.12500组
prompt:约780
perfix:752
output:30
vLLM-prefix
llama-7b12.52--SGLang
llama-7b12.91--vllm-generate

vLLM-prefix 使用我实现的 PrefixGroup 贪心调度,vLLM-generate 使用 vLLM 的 generate 函数,通过指定prefix长度进行推理, SGLang 使用 run_batch

  • SGLang 对全流程的算子都进行了一定的优化(Triton/CUDA),速度上略快与generate。SGLang目前还不是很稳定,有一定概率崩溃
  • 为何 vLLM-prefix 需要花费这么长时间进行推理可能有待进一步思考,正常来说推理时间应该与 generate 相近

挖坑

后面可能会对一些后续工作,比如Prefix的手动删除,Prefix调度,关于Prefix和Alibi等写点东西。

有不少文章对vLLM的实现做了详解,比自己干看代码能理解快不少,但是因为vLLM版本在不断地更新,有些文章中的实现可能与现在的项目有些出入,有些实现可能后面会被启用优化掉,但是大致的框架应该不会有很大的改动,如果有比较大的更新,后面可能做一期最新版vLLM的实现解析。

F&Q

放点大家问的问题,如果我有能力解答的话。

参考

编辑于 2024-01-26 14:25・IP 属地北京

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

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

相关文章

【做算法学数据结构】二叉树的层序遍历【二叉树】

文章目录 题目二叉树二叉树描述二叉树的java描述和前序遍历、后序遍历、中序遍历 题解总结以及二叉树应用场景 题目 给你二叉树的根节点 root &#xff0c;返回其节点值 自底向上的层序遍历 。 &#xff08;即按从叶子节点所在层到根节点所在的层&#xff0c;逐层从左向右遍历…

德思特GNSS模拟器为物流行业保驾护航

作者介绍 一、前言 德思特GNSS模拟器能够在最短的时间内高效、准确的协助完成虹科MSR运输数据记录仪的定位准确性以及抗干扰能力测试&#xff0c;确保在运输或存储过程中&#xff0c;让用户知道何时何地发生了超出预设公差范围的事件&#xff0c;快速、准确的记录定位数据&…

【UE 材质】水波纹效果

效果 模拟雨水打落在水面上的效果 步骤 1. 下载所需纹理和纹理 纹理2. 新建一个材质&#xff0c;这里命名为“M_WaterRipples” 打开“M_WaterRipples”&#xff0c;添加一个纹理采样节点&#xff0c;纹理使用第一步下载的纹理 将纹理采样节点的R通道连接到基础颜色&#x…

李沐57_长短期记忆网络LSTM——自学笔记

LSTM 1.忘记门&#xff1a;将值朝着0减少 2.输入门&#xff1a;决定不是忽略掉输入数据 3.输出门&#xff1a;决定是不是使用隐状态 !pip install --upgrade d2l0.17.5 #d2l需要更新首先加载时光机器数据集。 import torch from torch import nn from d2l import torch a…

Ajax和axios基础

AJAX Asynchronous JavaScript And XML 异步的JavaScript和XML 作用 数据交换: 通过Ajax可以给服务器发送请求,服务器将数据直接响应回给浏览器. 异步交互: 可以在不重新加载整个页面的情况下,与服务器交换数据并更新部分网页的技术. 同步和异步 同步发送请求: 浏览器发…

阿斯达年代记账号注册教程 阿斯达年代记苹果id注册教程

阿斯达年代记账号注册教程 阿斯达年代记苹果id注册教程 即将开服的新款大型多人角色扮演类游戏阿斯达年代记三强争霸将于4月24号上线&#xff0c;小伙伴们可以在本次开服之后进行游戏&#xff0c;这款游戏除了常规的职业分化之外&#xff0c;目前开放了四种角色供玩家选择&…

getopt, getopt_long使用笔记

An element of argv that starts with - (and is not exactly "-" or "--") is an option element. The characters of this element (aside from the initial -) are option characters. 以-’开头的字符(注意!不是字符串!!)就是命令行参数选项 通…

C++中的程序流程结构

一、选择结构 1.1 if语句 作用&#xff1a;执行满足条件的语句 if语句的三种形式 单行格式if语句多行格式if语句多条件的if语句 #include <iostream> using namespace std;int main(){//选择结构 单行if语句//用户输入分数&#xff0c;如果分数>600,视为考上一本大…

代码随想录 Day19 字符串 | LC28 实现strStr() 【KMP经典题目】

六、实现strStr() 题目&#xff1a; 力扣28&#xff1a;找出字符串中第一个匹配的下标 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack…

全身照怎么缩小做头像?在线图片改大小尺寸的方法

在日常工作中&#xff0c;有不少人喜欢把自己的全身照作为微信或者QQ头像&#xff0c;这时候就经常遇到一个问题&#xff0c;就是图片尺寸太大&#xff0c;无法完整的展现&#xff0c;那么这个时候该怎么处理呢&#xff1f;可以试试下面介绍的这个在线图片改大小尺寸的方法&…

上位机图像处理和嵌入式模块部署(树莓派4b的一种固件部署方法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 如果软件开发好了之后&#xff0c;下面就是实施和部署。对于树莓派4b来说&#xff0c;部署其实就是烧录卡和拷贝文件。之前我们烧录卡&#xff0c;…

YashanDB连获多项权威认证

近期&#xff0c;YashanDB产品能力再获认可&#xff0c;顺利通过多项权威测试认证&#xff0c;包括通过《数据库政府采购需求标准(2023年版)》测评&#xff1b;通过国密检测机构测试&#xff0c;产品支持GB/T38636-2020《信息安全技术传输层密码协议(TLCP)》国标协议&#xff1…

BRC铭文NFT铸造质押挖矿系统开发运营

区块链技术的不断演进与应用拓展&#xff0c;为数字资产领域带来了更多可能性。BRC铭文NFT铸造质押挖矿系统的开发与运营&#xff0c;将为用户提供一种全新的数字资产体验&#xff0c;下文将介绍其版/需求方案/逻辑项目。 1. 系统概述 BRC铭文NFT铸造质押挖矿系统旨在结合区块…

『docker』 容器虚拟化技术之空间隔离实战

文章目录 容器虚拟化基础之 NameSpaceNameSpace 隔离实战实战目的基础知识dd 命令详解mkfs 命令详解df 命令详解mount 命令详解unshare 命令详解 实战操作一&#xff08;PID 隔离&#xff09;实战操作二&#xff08;Mount 隔离&#xff09; 容器虚拟化基础之 NameSpace 什么是…

RepViT:当MobileNet遇到ViT

paper&#xff1a;https://arxiv.org/abs/2307.09283 code&#xff1a;https://github.com/THU-MIG/RepViT 目录 0. 摘要 1. 引言 2. 相关工作 3. 方法 3.1. 准备工作 3.2. block设计 3.3. 宏观设计 3.4. 微观设计 3.5. 网络结构 4. 实验 4.1. Image Classification …

Day:动态规划 LeedCode 123.买卖股票的最佳时机III 188.买卖股票的最佳时机IV

123. 买卖股票的最佳时机 III 给定一个数组&#xff0c;它的第 i 个元素是一支给定的股票在第 i 天的价格。 设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。 注意&#xff1a;你不能同时参与多笔交易&#xff08;你必须在再次购买前出售掉之前的股票&a…

系统架构设计精华知识

数据流风格&#xff1a;适合于分阶段做数据处理&#xff0c;交互性差&#xff0c;包括&#xff1a;批处理序列、管理过滤器。调用/返回风格&#xff1a;一般系统都要用到&#xff0c;包括&#xff1a;主程序/子程序&#xff0c;面向对象&#xff0c;层次结构&#xff08;分层越…

Rootkit介绍

一、定义 Rootkit是一种恶意软件&#xff0c;旨在让黑客访问和控制目标设备。虽然大多数Rootkit 会影响软件和操作系统&#xff0c;但有些还会感染计算机的硬件和固件。Rootkit善于隐藏自己&#xff0c;担当它们保持隐藏时&#xff0c;其实处于活跃状态。 一旦未经授权获得对计…

让更多的人能使用AI才能提升国内AI竞争力

随着人工智能技术的快速发展,AI正在深入影响我们的生活和工作。然而,目前AI技术的使用和应用主要集中在少数大型科技公司和研究机构,普通大众对AI技术的接触和使用还相对有限。如何让更多的人能够便捷地使用AI,从而带动整个国内AI产业的发展,已成为当前亟需解决的问题。 首先…

SQLAIchemy 异步DBManager封装-03得心应手

前言 SQLAIchemy 异步DBManager封装-01入门理解SQLAIchemy 异步DBManager封装-02熟悉掌握 在前两篇文章中&#xff0c;我们详细介绍了SQLAlchemy异步DBManager的封装过程。第一篇文章帮助我们入门理解了整体的封装结构和思路&#xff0c;第二篇文章则帮助我们更加熟悉和掌握了这…