当前位置: 首页 > news >正文

【速写】use_cache参数与decode再探讨

序言

纳什最近指出一个小细节,比如在Qwen系列模型中,两个special token:

  • eos_token(<|im_end|>): 151645(im_end 中的 im 指的是 instruct message)
  • pad_token(<|endoftext|>): 151643。

这是很有趣的事情,我们通常理解的 eos 反而是 pad token,而 eos 对应的是 <|im_end|>

我们在进行 sft 训练时,需要对 response 追加 eos,即要监督训练模型什么时候停止输出。

如前所说,eos 对应的 <|im_end|> 中的 im 指的是 instruct message(与之 pair 的是 <|im_start|>)。这俩 token(<|im_start|> <|im_end|> 封装进来的内容即为大家调 api 时用到的 role 和 content 的内容)。

即不管是 user 还是 assistant 它们的 eos 就是 <|im_end|>

这两个 token 也是从 base model (训练/推理)过度到 instruct model 的基础。

本文主要是探讨带kv-cache的解码方法的手写实现。实际上后来发现如果加上use_cache这个参数,可以省略大量的输入。这样是否意味着长上下文是个伪命题呢?其实本质上还是kv-cache太长了。那么之前所谓的sepllm(用点逗来替代长上下文)工作似乎就是隔靴搔痒了。


文章目录

  • 序言
  • 1 Beam Search 解码算法实现
  • 2 实现带KV Cache的Beam Search解码
  • 3 关于在带kv-cache的情况下的use_cache参数


1 Beam Search 解码算法实现

下面是一个使用PyTorch实现的beam search解码算法:

几个小细节:

  • 束搜索可以加入length_penalty,目前model.generate也是有这个参数的,这个惩罚项直接是用来除生成概率的
  • 通常这种需要计算概率相乘的情况,都是避免做乘法,而是使用log p相加
  • 具体实现中应当考虑eos标识符导致的early stop的候选序列,需要提前存储到外面
  • 然后就是关于使用log softmax得到log概率后,这其实是一个负的概率,序列越长,log prob会越小,- log prob 才是越大的,因此在做惩罚的时候,应该是吧 prob / len(seq) ** penality,即长序列的 log prob 会被除掉更多,这是合理的,因为短序列的 - log prob 天然地比 长序列地 - log prob 要更小,这样量纲才是正确的
import torch
import torch.nn.functional as F
from typing import List, Tupledef beam_search(model: torch.nn.Module,initial_input: torch.Tensor,beam_width: int,max_length: int,vocab_size: int,device: torch.device,length_penalty: float = 1.0,early_stopping: bool = True
) -> Tuple[List[List[int]], List[float]]:"""Beam search 解码算法实现参数:model: 用于预测下一个token的模型initial_input: 初始输入张量 (shape: [1, seq_len])beam_width: beam大小max_length: 生成序列的最大长度vocab_size: 词汇表大小device: 使用的设备 (cpu/cuda)length_penalty: 长度惩罚系数 (α), 用于调整对长序列的偏好early_stopping: 是否在所有beam序列达到EOS时提前停止返回:Tuple[List[List[int]], List[float]]: (生成的序列列表, 对应的分数列表)"""# 初始化beamsequences = [[initial_input.tolist()[0]]]  # 初始序列scores = [0.0]  # 初始分数 (log概率)# 存储完整的beam (已经生成EOS的序列)completed_sequences = []completed_scores = []for step in range(max_length):# 如果所有beam都已完成,提前停止if early_stopping and len(sequences) == 0:break# 准备当前步的输入candidates = []for i, seq in enumerate(sequences):# 跳过已经完成的序列if len(seq) > 0 and seq[-1] == 2:  # 假设2是EOS tokencompleted_sequences.append(seq)completed_scores.append(scores[i])continue# 将序列转换为张量input_tensor = torch.tensor([seq], dtype=torch.long).to(device)# 获取模型预测with torch.no_grad():outputs = model(input_tensor)next_token_logits = outputs[:, -1, :]  # 取最后一个token的logitsnext_token_probs = F.log_softmax(next_token_logits, dim=-1)# 获取top-k tokens和它们的log概率topk_probs, topk_tokens = torch.topk(next_token_probs, beam_width, dim=-1)topk_probs = topk_probs.squeeze(0)topk_tokens = topk_tokens.squeeze(0)# 创建候选序列for j in range(beam_width):new_seq = seq.copy()new_seq.append(topk_tokens[j].item())new_score = scores[i] + topk_probs[j].item()candidates.append((new_seq, new_score))# 如果没有候选序列,提前停止if not candidates:break# 选择top-k候选序列,并剪枝candidates.sort(key=lambda x: x[1] / (len(x[0]) ** length_penalty), reverse=True)sequences, scores = zip(*candidates[:beam_width])sequences = list(sequences)scores = list(scores)# 添加剩余的未完成序列到完成列表中completed_sequences.extend(sequences)completed_scores.extend(scores)# 对完成的序列按分数排序sorted_sequences = [seq for seq, _ in sorted(zip(completed_sequences, completed_scores),key=lambda x: x[1] / (len(x[0]) ** length_penalty),reverse=True)]sorted_scores = sorted(completed_scores,key=lambda score: score / (len(sorted_sequences[completed_scores.index(score)]) ** length_penalty),reverse=True)return sorted_sequences, sorted_scores
  1. 模型要求:

    • 模型应接受形状为 [batch_size, seq_len] 的输入
    • 模型应输出形状为 [batch_size, seq_len, vocab_size] 的logits
  2. 参数说明:

    • initial_input: 初始输入序列(如开始token)
    • beam_width: 控制搜索宽度,值越大结果可能越好但计算成本越高
    • length_penalty: 控制生成长度的偏好(α>1鼓励更长序列,α<1鼓励更短序列)
    • early_stopping: 设为True时,当所有beam都生成EOS token时停止
  3. 返回值:

    • 返回按分数排序的序列列表和对应的分数列表

如何调用?

model = ...  # 你的PyTorch模型
initial_input = torch.tensor([[1]])  # 假设1是开始token
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)sequences, scores = beam_search(model=model,initial_input=initial_input,beam_width=5,max_length=50,vocab_size=10000,  # 你的词汇表大小device=device,length_penalty=1.2
)print("Top sequence:", sequences[0])
print("Score:", scores[0])

2 实现带KV Cache的Beam Search解码

这个是关键,之前写过一个k步的贪心解码,使用了递归写法,但不知道怎么在递归中加入kv cache,其实感觉怎么写都不太好。

其实这样看只要写循环就可以了。

实际上transformers的model.generate方法里,对greedy、beam、以及其他采样的方法都是有做 kv cache 缓存优化的:

# 首次调用(处理初始输入)
outputs = model(input_ids, use_cache=True)
logits = outputs.logits
past_key_values = outputs.past_key_values  # 缓存KV# 后续步骤(生成时)
next_token_input = torch.tensor([[new_token]])
outputs = model(next_token_input, past_key_values=past_key_values, use_cache=True)
new_past_key_values = outputs.past_key_values  # 更新缓存

要在Beam Search中为每个候选序列维护独立的KV Cache,我们需要对标准实现进行一些修改。KV Cache可以显著提高自回归模型的推理效率,因为它避免了重复计算先前token的key和value。

以下是带有KV Cache的Beam Search实现:

  • 因为我们已经有cache了,因此实际上每步迭代生成下一个token时,只需要把当前序列的最后一个token输入即可,只是很棒的操作
import torch
import torch.nn.functional as F
from typing import List, Tuple, Optional, Dict, Anydef beam_search_with_kv_cache(model: torch.nn.Module,initial_input: torch.Tensor,beam_width: int,max_length: int,vocab_size: int,device: torch.device,length_penalty: float = 1.0,early_stopping: bool = True,use_kv_cache: bool = True
) -> Tuple[List[List[int]], List[float]]:"""带KV Cache的Beam Search解码算法参数:model: 用于预测下一个token的模型initial_input: 初始输入张量 (shape: [1, seq_len])beam_width: beam大小max_length: 生成序列的最大长度vocab_size: 词汇表大小device: 使用的设备 (cpu/cuda)length_penalty: 长度惩罚系数early_stopping: 是否在所有beam序列达到EOS时提前停止use_kv_cache: 是否使用KV Cache加速返回:Tuple[List[List[int]], List[float]]: (生成的序列列表, 对应的分数列表)"""# 初始化beamsequences = [[initial_input.tolist()[0]]]scores = [0.0]# 存储KV Cache (每个候选序列一个cache)kv_caches = [None]  # 初始cache为None# 存储完整的beamcompleted_sequences = []completed_scores = []for step in range(max_length):if early_stopping and len(sequences) == 0:breakcandidates = []new_kv_caches = []for i, (seq, score, kv_cache) in enumerate(zip(sequences, scores, kv_caches)):# 跳过已经完成的序列if len(seq) > 0 and seq[-1] == 2:  # 假设2是EOS tokencompleted_sequences.append(seq)completed_scores.append(score)continue# 准备输入 (只使用最后一个token,因为前面的已经cache了)input_tensor = torch.tensor([[seq[-1]]], dtype=torch.long).to(device)# 前向传播,使用或更新KV Cachewith torch.no_grad():if use_kv_cache:if kv_cache is None:# 第一次调用,处理整个初始序列full_input = torch.tensor([seq], dtype=torch.long).to(device)outputs = model(full_input, use_cache=True)next_token_logits = outputs.logits[:, -1, :]new_kv_cache = outputs.past_key_valueselse:# 后续调用,使用KV Cacheoutputs = model(input_tensor, past_key_values=kv_cache, use_cache=True)next_token_logits = outputs.logits[:, -1, :]new_kv_cache = outputs.past_key_valueselse:# 不使用KV Cache的情况full_input = torch.tensor([seq], dtype=torch.long).to(device)outputs = model(full_input, use_cache=False)next_token_logits = outputs.logits[:, -1, :]new_kv_cache = Nonenext_token_probs = F.log_softmax(next_token_logits, dim=-1)# 获取top-k tokenstopk_probs, topk_tokens = torch.topk(next_token_probs, beam_width, dim=-1)topk_probs = topk_probs.squeeze(0)topk_tokens = topk_tokens.squeeze(0)# 创建候选序列for j in range(beam_width):new_seq = seq.copy()new_seq.append(topk_tokens[j].item())new_score = score + topk_probs[j].item()candidates.append((new_seq, new_score, new_kv_cache))# 如果没有候选序列,提前停止if not candidates:break# 选择top-k候选序列candidates.sort(key=lambda x: x[1] / (len(x[0]) ** length_penalty), reverse=True)top_candidates = candidates[:beam_width]# 解包候选序列sequences = [cand[0] for cand in top_candidates]scores = [cand[1] for cand in top_candidates]kv_caches = [cand[2] for cand in top_candidates]# 添加剩余的未完成序列completed_sequences.extend(sequences)completed_scores.extend(scores)# 对完成的序列按分数排序sorted_pairs = sorted(zip(completed_sequences, completed_scores),key=lambda x: x[1] / (len(x[0]) ** length_penalty),reverse=True)sorted_sequences = [pair[0] for pair in sorted_pairs]sorted_scores = [pair[1] for pair in sorted_pairs]return sorted_sequences, sorted_scores

关键修改说明

  1. KV Cache维护:

    • 每个候选序列现在维护自己的KV Cache
    • 初始时KV Cache为None
    • 第一次处理序列时生成完整的KV Cache
    • 后续步骤只处理最后一个token并更新KV Cache
  2. 模型接口要求:

    • 模型需要支持past_key_values参数来接收先前的KV Cache
    • 模型需要返回past_key_values作为输出的一部分
    • 典型实现方式(如HuggingFace的transformers):
      outputs = model(input_ids, past_key_values=past_key_values, use_cache=True)
      next_token_logits = outputs.logits
      past_key_values = outputs.past_key_values
      
  3. 性能优化:

    • 使用KV Cache后,每次前向传播只处理最后一个token(这个很有趣,但是要设置use_cache=True
    • 避免了重复计算先前token的key和value
    • 对于长序列可以显著提高速度

一个简单的调用示例:

# 假设我们有一个支持KV Cache的模型
model = ...  # 例如HuggingFace的GPT2模型
initial_input = torch.tensor([[model.config.bos_token_id]])  # 开始token
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)# 运行带KV Cache的beam search
sequences, scores = beam_search_with_kv_cache(model=model,initial_input=initial_input,beam_width=5,max_length=50,vocab_size=model.config.vocab_size,device=device,length_penalty=1.2,use_kv_cache=True  # 启用KV Cache
)print("Top sequence:", sequences[0])
print("Score:", scores[0])

补注:

在这个部分:

            # 前向传播,使用或更新KV Cachewith torch.no_grad():if use_kv_cache:if kv_cache is None:# 第一次调用,处理整个初始序列full_input = torch.tensor([seq], dtype=torch.long).to(device)outputs = model(full_input, use_cache=True)

上,输出的full_input 的size是[1, 1, seqlen],理论上应该是[1, seqlen]才对,因此要么是

            # 前向传播,使用或更新KV Cachewith torch.no_grad():if use_kv_cache:if kv_cache is None:# 第一次调用,处理整个初始序列full_input = torch.tensor(seq, dtype=torch.long).to(device)outputs = model(full_input, use_cache=True)

要么是:

            # 前向传播,使用或更新KV Cachewith torch.no_grad():if use_kv_cache:if kv_cache is None:# 第一次调用,处理整个初始序列full_input = torch.tensor([seq], dtype=torch.long).to(device)outputs = model(full_input.squeeze(0), use_cache=True)

这样测试跑通应该是没有问题的


3 关于在带kv-cache的情况下的use_cache参数

比如之前手写的一个贪心解码算法:

# -*- coding: utf8 -*-
# @author: caoyang
# @email: caoyang@stu.sufe.edu.cnimport torch
import logging
from copy import deepcopy
from functools import wraps
from torch.nn import functional as Ffrom transformers import AutoTokenizer, AutoModelForCausalLM# Standard greedy decode
# @param model: Huggingface model object
# @param tokenizer: Huggingface tokenizer Object
# @param prompt: Str
# @param max_length: Int, the number of tokens to be generated
# @param device: Str, e.g. "cuda" or "cpu"
# @param kv_cache: Boolean, whether to use KV-cache to accelerate, if True then large memory will be consumed
# @return generated_text: Str
# @return generated_token_prob: List[Tuple(Int, Str, Float)], `len(generated_id_prob)` is `max_length`, indicating the generated probability of each token
# @return generated_logits: Tuple[FloatTensor(1, n_vocab)], `len(generated_logits)` is `max_length`, indicating the logits when each token is generated
def greedy_decode(model,tokenizer,prompt, max_length,device = "cuda",kv_cache = True,):inputs = tokenizer.encode(prompt, return_tensors="pt").to(device)	# Str => Long(1, n_tokens)past_key_values = Nonegenerated_token_probs = list()generated_logits = list()model.gradient_checkpointing_enable()for i in range(max_length):logging.info(f"Round {i}: {past_key_values.key_cache[0].size() if past_key_values is not None else None}")outputs = model(inputs, past_key_values=past_key_values)logits = outputs.logits	# Float(1, n_tokens + i + 1, n_vocab), where `n_vocab` is 151936 in DeepSeek-R1-Distill-Qwenif kv_cache:past_key_values = outputs.past_key_values	# Dictlike[key_cache: Float(1, 2, X, hidden_size), value_cache: Float(1, 2, X, hidden_size)], where X = (i + 1) * (n_tokens + i / 2)next_token_probs = F.softmax(logits[:, -1, :], dim=-1)	# Float(1, n_tokens + i + 1, n_vocab) => Float(1, n_vocab)next_token_id = torch.argmax(next_token_probs, dim=-1)	# Float(1, n_vocab) => Long(1, )next_token_prob = next_token_probs[0, next_token_id].item()	# Float(1, n_vocab) => Float()next_token = tokenizer.decode(next_token_id[0].item(), skip_special_tokens=False)	# Long(1, ) => Strinputs = torch.cat([inputs, next_token_id.unsqueeze(-1)], dim=-1)	# Long(1, n_tokens + i) => Long(1, n_tokens + i + 1)generated_token_probs.append((next_token_id.item(), next_token, next_token_prob))generated_logits.append(logits[:, -1, :])generated_text = tokenizer.decode(token_ids = inputs[0], skip_special_tokens=True, clean_up_tokenization_spaces=True,)	# Long(1, n_tokens + max_length) => Strreturn generated_text, generated_token_probs, tuple(generated_logits)

实际上除了第一次输入外,接下来都可以用最后一个token作为输入,而不需要把之前整个一长串的input都输入到model中去:

# -*- coding: utf8 -*-
# @author: caoyang
# @email: caoyang@stu.sufe.edu.cnimport torch
import logging
from copy import deepcopy
from functools import wraps
from torch.nn import functional as Ffrom transformers import AutoTokenizer, AutoModelForCausalLM# Standard greedy decode
# @param model: Huggingface model object
# @param tokenizer: Huggingface tokenizer Object
# @param prompt: Str
# @param max_length: Int, the number of tokens to be generated
# @param device: Str, e.g. "cuda" or "cpu"
# @param kv_cache: Boolean, whether to use KV-cache to accelerate, if True then large memory will be consumed
# @return generated_text: Str
# @return generated_token_prob: List[Tuple(Int, Str, Float)], `len(generated_id_prob)` is `max_length`, indicating the generated probability of each token
# @return generated_logits: Tuple[FloatTensor(1, n_vocab)], `len(generated_logits)` is `max_length`, indicating the logits when each token is generated
def greedy_decode(model,tokenizer,prompt, max_length,device = "cuda",kv_cache = True,):inputs = tokenizer.encode(prompt, return_tensors="pt").to(device)	# Str => Long(1, n_tokens)past_key_values = Nonegenerated_token_probs = list()generated_logits = list()model.gradient_checkpointing_enable()for i in range(max_length):logging.info(f"Round {i}: {past_key_values.key_cache[0].size() if past_key_values is not None else None}")if kv_cache:if i == 0:outputs = model(inputs, past_key_values=past_key_values)else:outputs = model(inputs[:, -1].unsqueeze(0), past_key_values=past_key_values, use_cache=True)else:outputs = model(inputs, past_key_values=None)logits = outputs.logits	# Float(1, n_tokens + i + 1, n_vocab), where `n_vocab` is 151936 in DeepSeek-R1-Distill-Qwenif kv_cache:past_key_values = outputs.past_key_values	# Dictlike[key_cache: Float(1, 2, X, hidden_size), value_cache: Float(1, 2, X, hidden_size)], where X = (i + 1) * (n_tokens + i / 2)next_token_probs = F.softmax(logits[:, -1, :], dim=-1)	# Float(1, n_tokens + i + 1, n_vocab) => Float(1, n_vocab)next_token_id = torch.argmax(next_token_probs, dim=-1)	# Float(1, n_vocab) => Long(1, )next_token_prob = next_token_probs[0, next_token_id].item()	# Float(1, n_vocab) => Float()next_token = tokenizer.decode(next_token_id[0].item(), skip_special_tokens=False)	# Long(1, ) => Strinputs = torch.cat([inputs, next_token_id.unsqueeze(-1)], dim=-1)	# Long(1, n_tokens + i) => Long(1, n_tokens + i + 1)generated_token_probs.append((next_token_id.item(), next_token, next_token_prob))generated_logits.append(logits[:, -1, :])generated_text = tokenizer.decode(token_ids = inputs[0], skip_special_tokens=True, clean_up_tokenization_spaces=True,)	# Long(1, n_tokens + max_length) => Strreturn generated_text, generated_token_probs, tuple(generated_logits)

这个确实是很有帮助的,能加速推理很多。这个原理其实很简单,因为只需要KVcache与最后一个token就可以计算得到下一层的注意力权重(其实就是下一轮生成的KVcache),然后倒是发现deepseek在生成图像链接时出错了,难得逮到DeepSeek犯错的时候(生成图片链接失败):

在这里插入图片描述

use_cache=True 时,Transformer 模型的前向传播会启用 KV Cache 机制,这是解码阶段(如文本生成)的核心优化手段。以下是其具体执行逻辑和缓存内容的详细说明:

模型会缓存每一层(Layer)的 Key 矩阵(K)Value 矩阵(V),这些矩阵来自历史 token 的自注意力计算。具体来说:

  • K Cache: 形状为 [batch_size, num_heads, seq_len, head_dim]
  • V Cache: 形状为 [batch_size, num_heads, seq_len, head_dim]
  • Query(Q) 是当前 token 的向量,每次生成时需重新计算。
  • Key/Value 是历史 token 的向量,生成新 token 时可直接复用,无需重复计算。

看起来如果设置了use_cache = True的话,其实根本就不需要再手动更新kv_cache了,但这个事情还不太好直接验证。只能从运行时间上分辨。

  1. 首次调用(处理完整输入序列)
outputs = model(input_ids, use_cache=True)
  • 计算步骤:
    1. 对输入的所有 token 计算完整的自注意力(包括 Q、K、V)。
    2. 将每一层的 K 和 V 存入 past_key_values(形状为 [num_layers, 2, batch_size, num_heads, seq_len, head_dim])。
    3. 返回最后一个 token 的 logits 和缓存的 past_key_values
  1. 后续调用(生成新 token)
outputs = model(new_token, past_key_values=past_key_values, use_cache=True)
  • 计算步骤:
    1. 仅计算新 token 的 Q(因为 K/V 已缓存)。
    2. 将新 token 的 Q 与缓存的 K/V 计算注意力分数:
      Attention(Q_new, K_cache, V_cache) = softmax(Q_new @ K_cache^T / √d) @ V_cache
      
    3. 将新 token 自身的 K/V 追加到缓存中,更新 past_key_values
    4. 返回新 token 的 logits 和更新后的 past_key_values

KV Cache 的代码级实现(以 HuggingFace 为例)

缓存的数据结构

past_key_values = [(K_layer1, V_layer1),  # 第1层的K/V(K_layer2, V_layer2),  # 第2层的K/V...                    # 所有层的K/V
]

关键代码逻辑

# 在模型的自注意力层中(简化版)
if use_cache:# 合并历史K/V与新K/Vkey_states = torch.cat([past_key_values[0], current_key], dim=2])  # 沿seq_len维度拼接value_states = torch.cat([past_key_values[1], current_value], dim=2])# 更新缓存present_key_values = (key_states, value_states)
else:present_key_values = None

KV Cache 的显存占用分析

假设以下参数:

  • 模型层数 L(如 LLaMA-7B 有 32 层)
  • 注意力头数 H(如 32)
  • 头维度 D(如 128)
  • 序列长度 S
  • 批大小 B
  • 数据类型 dtype(如 float16 占 2字节)

缓存总大小

显存 ≈ L × 2 × B × H × S × D × dtype_size

例如:LLaMA-7B 生成 1024 token 时,单样本的缓存约占用 32×2×1×32×1024×128×2 = 512MB


KV Cache 的优化效果

操作计算复杂度(无缓存)计算复杂度(有缓存)
生成第 N 个 tokenO(N²)O(N)
显存占用O(1)O(N)
  • 速度提升:生成 1000 token 时,理论加速约 1000 倍(从 1M 次计算降到 1K 次)。
  • 代价:显存随序列长度线性增长。

相关文章:

【速写】use_cache参数与decode再探讨

序言 纳什最近指出一个小细节&#xff0c;比如在Qwen系列模型中&#xff0c;两个special token&#xff1a; eos_token(<|im_end|>): 151645&#xff08;im_end 中的 im 指的是 instruct message&#xff09;pad_token(<|endoftext|>): 151643。 这是很有趣的事…...

智能网联汽车“内外协同、虚实共生”的通信生态

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 钝感力的“钝”&#xff0c;不是木讷、迟钝&#xff0c;而是直面困境的韧劲和耐力&#xff0c;是面对外界…...

《Python星球日记》 第64天:NLP 概述与文本预处理

名人说&#xff1a;路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原《离骚》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、NLP 简介1. 什么是自然语言处理&#xff1f;NLP 的应用场景&#xff1a; 2.…...

Java中堆栈

文章目录 Java中堆栈1. 栈&#xff08;Stack&#xff09;特点示例 2. 堆&#xff08;Heap&#xff09;特点示例 3. 核心区别4. 常见问题5. 内存可视化示例内存布局示意图&#xff1a; 总结 Java中堆栈 在 Java 中&#xff0c;“堆栈” 通常指的是堆&#xff08;Heap&#xff0…...

模块化PCB设计中联排半孔的应用

随着电子产品的快速发展&#xff0c;高密度、多功能和小型化已成为未来的趋势。电路板上的元件几何指数在增加&#xff0c;而PCB尺寸却越来越小&#xff0c;因此需要与支撑板做配合。如果用助焊剂将圆孔焊接到母板上&#xff0c;由于圆孔体积较大&#xff0c;会产生冷焊&#x…...

xss-lab靶场4-7关基础详解

前言&#xff1a; 仅作为练习&#xff0c;复盘 推荐html在线运行平台&#xff0c;弹窗标签可以在平台运行&#xff0c;看语句是否能正常弹窗 HTML/CSS/Javascript在线代码运行工具 | 菜鸟教程 内容&#xff1a; 第四关 打开一看&#xff0c;输入<script>alert(1)&l…...

【Linux】进程状态、优先级、切换和调度

目录 一、传统操作系统进程状态 &#xff08;一&#xff09;什么是状态 &#xff08;二&#xff09;运行状态 &#xff08;三&#xff09;阻塞状态 &#xff08;四&#xff09;挂起状态 二、Linux进程状态 &#xff08;一&#xff09;进程状态 &#xff08;二&#xff…...

软件测试基础知识详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1、黑盒测试、白盒测试、灰盒测试 1.1 黑盒测试 黑盒测试 又叫 功能测试、数据驱动测试 或 基于需求规格说明书的功能测试。该类测试注重于测试软件的功能性需…...

【WordPress博客AI内容辅助生成/优化工具箱插件下载无标题】

主要功能 AI内容生成/优化 使用AI模型生成或优化段落内容 支持撤销和模型切换 AI自动评论 智能分析文章内容生成相关评论 可配置的评论数量和随机调度 生成评论回复的概率(评论数>10时) 可能以特定概率包含表情符号 AI标签提取 从文章内容自动提取相关标签 新标签可自动生…...

js 画立方体软件开发日记2

我不懂但我大为震惊 重开几次又回去了 这说明之前的操作无效 搞了个调试当前文件 导出模块有问题&#xff0c;跑显示没事 启动太慢&#xff0c;重构吧 ----------------------------------------------- 把那些鬼相机投影代码删了就有4s了 按钮全删了&#xff0c;还是卡&…...

element plus el-table多选框跨页多选保留

一、基础多选配置 通过 type“selection” 开启多选列&#xff0c;并绑定 selection-change 事件获取选中数据 <template><el-table :data"tableData" selection-change"handleSelectionChange"><el-table-column type"selection&qu…...

智能家居“心脏“升级战:GD25Q127CSIG国产芯片如何重构家庭物联生态

在智能家居设备出货量突破10亿台的2023年&#xff0c;家庭网关正经历着前所未有的技术革新。作为连接云端与终端设备的中枢神经&#xff0c;智能网关的存储芯片选择直接决定着整个智能生态系统的运行效率。在这场技术升级浪潮中&#xff0c;兆易创新GD25Q127CSIG串行闪存芯片主…...

智能枪弹柜管理系统|智能枪弹柜管理系统LK-QDG-Q20型

是一种用于存放枪支弹药的智能化设备&#xff0c;主要应用于涉枪单位&#xff0c;以下将从其功能特点、系统组成、优势等维度展开介绍&#xff1a; 功能特点 身份识别功能&#xff1a;采用多种生物识别技术&#xff0c;如指纹识别、指静脉识别、虹膜识别、人脸识别、声纹识别等…...

bootstrap table 添加跳转到指定页的功能(仅自己可见)

Table回调方法中&#xff0c;添加input和button至Table下方&#xff08;Table页渲染结束后执行&#xff09; &#xff08;input用来输入页码&#xff0c;button执行跳转&#xff09; function ajaxRequestExtends(data){$(".page-list").append("<input idp…...

rufus+Ubuntu 18.04 镜像

参考&#xff1a;https://blog.csdn.net/Li060703/article/details/106075597 Rufus 官网&#xff1a; https://rufus.ie/zh/ 步骤 安装U盘做好后插在主板io的USB口上&#xff0c;启动阶段用F2或DEL打断进bios&#xff0c;bios里面指定从安装U盘来启动&#xff0c;里面的B…...

基于事件驱动和策略模式的差异化处理方案

一、支付成功后事件驱动 1、支付成功事件 /*** 支付成功事件** author ronshi* date 2025/5/12 14:40*/ Getter Setter public class PaymentSuccessEvent extends ApplicationEvent {private static final long serialVersionUID 1L;private ProductOrderDO productOrderDO;…...

【运维】MacOS蓝牙故障排查与修复指南

在日常使用macOS系统过程中&#xff0c;蓝牙连接问题时有发生。无论是无法连接设备、连接不稳定还是蓝牙功能完全失效&#xff0c;这些问题都会严重影响我们的工作效率。本文将分享一些实用的排查方法和修复技巧&#xff0c;帮助你解决macOS系统上的蓝牙故障。 问题症状 常见…...

linux小主机搭建自己的nas(三)docker安装nextcloud

小主机用的TF卡&#xff0c;不可能把nas的数据放在卡上&#xff0c;所以我买了个2T的移动硬盘 1.挂载移动硬盘 查找硬盘 lsblk # 或 fdisk -l 创建挂载点 sudo mkdir -p alon_ssd 查看硬盘文件系统,文件系统类型一会儿设置挂载用 sudo blkid /dev/sda1 开机自动挂载&…...

Go语言:json 作用和语法

在 Go 语言中&#xff0c;JSON 字段&#xff08;也称为 JSON Tag&#xff09;是附加在结构体字段上的元数据&#xff0c;用于控制该字段在 JSON 编码&#xff08;序列化&#xff09;和解码&#xff08;反序列化&#xff09; 时的行为。它的语法是&#xff1a; type StructName…...

mageia系统详解

Mageia 是一个由社区驱动的 Linux 发行版&#xff0c;源自 Mandriva Linux&#xff08;原 Mandrake Linux&#xff09;&#xff0c;以用户友好性和强大的系统工具著称。它继承了 Mandriva 的易用性传统&#xff0c;同时专注于稳定性和社区协作。以下从历史背景、技术架构、系统…...

六、STM32 HAL库回调机制详解:从设计原理到实战应用

STM32 HAL库回调机制详解&#xff1a;从设计原理到实战应用 一、回调机制的本质与设计目标 在STM32 HAL库中&#xff0c;回调机制是实现异步事件处理的核心设计模式。它通过弱定义函数用户重写的方式&#xff0c;将硬件事件&#xff08;如数据传输完成、定时器溢出等&#xf…...

USB传输模式

USB有四种传输模式: 控制传输, 中断传输, 同步传输, 批量传输 1. 中断传输 中断传输一般用于小批量, 非连续的传输. 对实时性要求较高. 常见的使用此传输模式的设备有: 鼠标, 键盘等. 要注意的是, 这里的 “中断” 和我们常见的中断概念有差异. Linux中的中断是设备主动发起的…...

lenis滑动插件的笔记

官网 lenis - npm 方法一&#xff1a;基础判断&#xff08;推荐&#xff09; 通过 Lenis 自带的 scroll 和 limit 属性直接判断&#xff1a; const lenis new Lenis()// 滚动事件监听 lenis.on(scroll, ({ scroll, limit }) > {const distanceToBottom limit - scroll…...

【网络安全】SQL注入

如果文章不足还请各位师傅批评指正&#xff01; 想象一下&#xff0c;你经营着一家咖啡店&#xff0c;顾客可以通过店内的点单系统下单。这个系统会根据顾客的输入&#xff0c;向后厨发送指令&#xff0c;比如“为顾客A准备一杯拿铁”。 然而&#xff0c;如果有个不怀好意的顾客…...

window server 2012安装sql server2008 r2

执行sql server2008 r2安装目录下的setup 选择运行程序而不获取帮助 然后就是让人绝望的 只能先搞这个了&#xff0c;F*微软&#xff0c;自家软件不让正常安装 打开服务器管理器->添加角色和功能->选择Web 服务&#xff08;IIS&#xff09;->添加.NET Framework3.5 然…...

uni-app学习笔记五--vue3插值表达式的使用

vue3快速上手导航&#xff1a;简介 | Vue.js 模板语法 插值表达式 最基本的数据绑定形式是文本插值&#xff0c;它使用的是“Mustache”语法 (即双大括号)&#xff1a; <span>Message: {{ msg }}</span> 双大括号标签会被替换为相应组件实例中 msg 属性的值。同…...

RuoYi 中使用 PageUtils.startPage() 实现分页查询的完整解析

文章目录 一、PageHelper 简介与基本用法使用方式如下&#xff1a; 二、Mapper 接口返回类型对分页的影响1. 返回 Page<T> 类型&#xff08;推荐&#xff09;2. 返回 List<T> 类型&#xff08;不推荐&#xff09; 三、解析RuoYi 是如何使用 PageUtils.startPage()1…...

【番外】02:Windows 编译带 DNN_CUDA 功能的 OpenCV 动态链接库

文章目录 1. 环境准备2. 兼容性说明3. 算力查询4. 编译步骤5. 网盘资料 提示&#xff1a; 如果读者因网络环境受限&#xff0c;无法正常下载与本文相关的软件安装包、压缩包&#xff0c;以及编译时的依赖文件&#xff0c;可以从文章最后提供的网盘链接下载资源。 1. 环境准备 …...

Java详解LeetCode 热题 100(14):LeetCode 56. 合并区间(Merge Intervals)详解

文章目录 1. 题目描述2. 理解题目3. 解法一&#xff1a;排序 一次遍历法3.1 思路3.2 Java代码实现3.3 代码详解3.4 复杂度分析3.5 适用场景 4. 解法二&#xff1a;双指针法4.1 思路4.2 Java代码实现4.3 代码详解4.4 复杂度分析4.5 与解法一的比较 5. 解法三&#xff1a;TreeMa…...

回答 | 图形数据库neo4j社区版可以应用小型企业嘛?

刚在知乎上看到了一个提问&#xff0c;挺有意思&#xff0c;于是乎&#xff0c;贴到这里再简聊一二。 转自知乎提问 当然可以&#xff0c;不过成本问题不容小觑。另外还有性能上的考量。 就在最近&#xff0c;米国国家航空航天局——NASA因为人力成本问题&#xff0c;摒弃了使…...

2024年北理工Python123第六章测验题整理

测验题一般不会太难&#xff0c;但是这次的题目&#xff0c;未免太多了&#xff0c;有的还很难 一、选择题 二、编程题 1-10 列表和字符串 1-10都是和列表、字符串有关的题目&#xff0c;都很简单,我直接给出代码了 1.列表排序输出 import random random.seed(int(input()…...

常用的设计模式详解

常用的设计模式详解 在后端开发中&#xff0c;设计模式是提升代码可维护性、扩展性和灵活性的关键工具。通过合理应用设计模式&#xff0c;开发者能够高效解决复杂问题并优化系统架构。本文将结合实际案例&#xff0c;深入探讨后端开发中常用的设计模式及其核心应用场景。 一、…...

OFCMS代码审计-freemaker注入sql注入xxexss文件上传

环境搭建 下载地址&#xff1a;https://gitee.com/oufu/ofcms/repository/archive/V1.1.2?formatzip SSTI模板注入&#xff08;freemaker) FreeMarker模板注入实现远程命令执行 - Eleven_Liu - 博客园 在admin中找到这个 发现请求的是这个 找到他 <#assign value"f…...

python与nodejs哪个性能高

在一般的Web开发和高并发场景中&#xff0c;Node.js的性能通常优于Python&#xff0c;特别是在处理大量异步任务和实时应用时更具优势&#xff1b;而在数据分析、机器学习及计算密集型任务中&#xff0c;Python则表现出更高的性能优势。 Node.js以事件驱动的非阻塞I/O模型著称&…...

云平台管理部署知识点——问题+答案

1、在k8s 中定义副本数量的关键字是那个&#xff1f;处于那个模块下&#xff1f; 关键字&#xff1a;replicas 模块&#xff1a;spec下 2、在k8s中&#xff0c;有状态应用和无状态应用的区别&#xff1f;创建有状态和无状态应用分别使用哪种资源对象类型&#xff1f; &#…...

数据结构(六)——树和二叉树

一、树和二叉树的定义与存储 1.树的定义 树是一种非线性的数据结构&#xff0c;它是由n个有限结点组成有层次关系的集合 树具有以下特点&#xff1a; &#xff08;1&#xff09;每个结点具有0个或多个子结点 &#xff08;2&#xff09;每个子结点只有一个父结点 &#xff…...

基于构件的开发方法与传统开发方法的区别

在软件开发领域,基于构件的开发方法和传统开发方法有着截然不同的特点与应用效果,这些差异显著影响着项目的实施过程与最终成果。下面,我们将从多个关键维度展开对比分析。​ 一、开发模式:线性搭建与模块组装​ 传统开发方法遵循线性的、自顶向下的流程,就像搭建一座高楼…...

cursor对话关键词技巧

提示词基本结构与原则 一个好的 Cursor 提示词通常包含三个部分&#xff1a;目标说明 上下文信息 具体要求。 例如&#xff1a; 创建一个React登录组件&#xff0c;使用Tailwind CSS样式&#xff0c;需要包含邮箱验证功能和记住密码选项。 效果演示&#xff1a; 提示词的的…...

克隆虚拟机组成集群

一、克隆虚拟机 1. 准备基础虚拟机 确保基础虚拟机已安装好操作系统&#xff08;如 Ubuntu&#xff09;、Java 和 Hadoop。关闭防火墙并禁用 SELinux&#xff08;如适用&#xff09;&#xff1a; bash sudo ufw disable # Ubuntu sudo systemctl disable firewalld # CentO…...

添加购物车-02.代码开发

一.代码开发 购物车属于用户端功能&#xff0c;因此要在user下创建controller代码。 Controller层 package com.sky.controller.user;import com.sky.dto.ShoppingCartDTO; import com.sky.entity.ShoppingCart; import com.sky.result.Result; import com.sky.service.Shopp…...

2094. 找出 3 位偶数

from typing import Listclass Solution:def findEvenNumbers(self, digits: List[int]) -> List[int]:# 统计 digits 中每个数字&#xff08;0-9&#xff09;的出现次数。cnt [0] * 10for d in digits:cnt[d] 1ans []# i0 百位&#xff0c;i1 十位&#xff0c;i2 个位&a…...

外出充电不发愁,倍思便携式移动电源成出行新宠

电子设备已深度融入现代快节奏生活&#xff0c;成为出行必备。但随之而来的电量焦虑&#xff0c;却始终困扰着人们。无论是出差远行、户外探索&#xff0c;还是每日通勤&#xff0c;如何随时为设备充电&#xff0c;成了亟待解决的难题。倍思极客充伸缩数据线充电宝应运而生&…...

防火墙安全策略基础配置

拓朴图 设备基础配置 # AR1 路由器配置 [Huawei]interface GigabitEthernet0/0/0 [Huawei-GigabitEthernet0/0/0]ip address 1.1.1.2 255.255.255.0 [Huawei]ip route-static 192.168.1.0 255.255.255.0 1.1.1.1# FW1 防火墙配置 [USG6000V1]sysname FW1 [FW1]interface Gigab…...

系统架构-通信系统架构设计

通信网络系统架构 局域网 单一机构所拥有的专用计算机网络 局域网从早期只提供二层交换功能的简单网络发展到现在&#xff0c;还提供三层路由功能的复杂网络 局域网的典型架构风格&#xff1a; 单核心架构&#xff1a;由一台核心二层或三层交换设备充当网络的核心设备&…...

2.3 定积分

一、数学定义与核心公式 核心思想&#xff1a; 定积分是通过无限细分区间、累加微小矩形面积来逼近曲边图形面积的数学工具。其本质是极限过程下的误差控制与动态平衡。 公式与符号解析&#xff1a; 表达式&#xff1a;定积分写作 ∫ₐᵇ f(x)dx&#xff0c;表示在区间 [a, …...

TCPIP详解 卷1协议 八 ICMPv4和ICMPv6 Internet控制报文协议

8.1——ICMPv4和ICMPv6 Internet控制报文协议 IP 协议本身并没有为终端系统提供直接的方法来发现那些发往目的地址失败的IP数据包。此外&#xff0c;IP 没有提供直接的方式来获取诊断信息&#xff08;例如&#xff0c;哪些路由器在沿途中被使用了或使用一种方法来估计往返时间…...

ik 分词器 设置自定义词典

进入 ES 的安装目录&#xff0c;进入 /elasticsearch-8.10.0/plugins/ik/config/ 文件夹目录&#xff0c;打开 IKAnalyzer.cfg.xml 文件进行配置。 一、添加 自定义扩展词典 扩展词&#xff1a;就是不想哪些词分开&#xff0c;让他们成为一个词&#xff0c;比如“蒙的全是对…...

RabbitMQ 工作模式

RabbitMQ 一共有 7 中工作模式&#xff0c;可以先去官网上了解一下&#xff08;一下截图均来自官网&#xff09;&#xff1a;RabbitMQ 官网 Simple P&#xff1a;生产者&#xff0c;要发送消息的程序&#xff1b;C&#xff1a;消费者&#xff0c;消息的接受者&#xff1b;hell…...

sqlmap使用入门

sqlmap加速了sql注入的发展&#xff0c;需要掌握6点&#xff0c;其一是--dbs获得数据库名称&#xff0c;其二是-D 数据库名称 --tables 获得数据库中的所有表名&#xff0c;其三是-D 数据库名 -T 表名 -C 字段1,字段2 --dump 获得数据库中的表中的字段的值&#xff0c;其四是-r…...

C++23 中的 views::stride:让范围操作更灵活

文章目录 什么是 views::stride语法与用法参数与返回值实现细节适用场景编译器支持总结 什么是 views::stride views::stride 是 C23 引入的一个范围适配器。它允许我们从一个范围中以固定步长提取元素&#xff0c;从而生成一个新的范围视图。具体来说&#xff0c;给定一个范围…...