【速写】hook与fx
文章目录
- 问题
- 方法
- 方法 1:使用 PyTorch 的 `register_forward_hook`
- 方法 2:自定义前向传播(修改 `forward` 方法)
- 方法 3:使用 `output_attentions` 或 `output_hidden_states`
- 方法 4:使用 `torch.fx` 进行动态追踪
- 总结
- 前向钩子的输出到底是什么?
- 反向钩子应该怎么用?
- 更好的写法:钩子与装饰器
最近师弟要跑步,目标是减肥(大概跟我差不多高,但比我要重40斤),想着下半年能跑个半马(不过以他目前的水平来说,还是有点差距的,虽然他只是想能在关门前跑完就行了)。
有半个月了,一周跑5天,周日休息,周四下午跟他去打一个小时羽毛球,大概6-7分的配,一天跑2-3km,有点找回快乐跑步的初衷。但实在还是太慢了,不过也无所大谓。
讨论了一些问题,我很惊异于他们做diffuser的,居然融合lora就直接查一下lora权重的绝对值,然后取top-k大的保留,其余的直接dropout掉了
目前diffuser方法有很强的解释性,很多研究都表明了模型的不同层是可以近似对应图层进行解耦的,即比如第1层控制的是layout,第2层控制的是color,第3层控制的是mainbody,因此如果只是去调颜色,就只需要对第2层套lora(我之前以为这个事情目前不太好实现,其实就是target_modules里的名字写得不对,不能按照key或者model.named_modules()
来写,要省掉前面几项才行),不过也可以用笨方法冻结:
# 先对所有 q_proj 应用 LoRA
peft_config = LoraConfig(target_modules=["q_proj"])# 然后手动冻结不需要的层
for name, param in model.named_parameters():if "layers.1." in name: # 例如冻结第1层及之后的参数param.requires_grad = False
问题
这个不是重点,我的观点是,权重的数值分布并不重要( W W W不重要),输出的值分布才是更重要的( W x Wx Wx才是重要的),或者说激活后的输出才是最重要的( L a y e r N o r m ( σ ( W x + b ) ) LayerNorm(\sigma(Wx+b)) LayerNorm(σ(Wx+b))),上个月何凯明发的用tanh替代LayerNorm的那个神文里,说的也是这个道理,其实无所谓到底发生了什么,输出的分布近似tanh,那就别搞什么LayerNorm了。
那么这又是一个老生常谈的问题:
使用transformers调用DeepSeek-R1-Distill-Qwen-32B模型,可以用
model.generate
方法输出模型的结果,也可以使用model.forward
(即model.__call__
)调用,现在的问题是,如果想知道模型中每一层(比如说,每一个self_attn的q_proj,k_proj的输出结果),那么我有办法实现吗,在不修改transformers源码的情况下。
以下是几种可行的方法:
- 法1:最为常用,就是要学会用钩子,其实这是个很好的习惯,自己写模型的时候也可以提前把钩子写好,以前觉得什么
register_buffer
、register_hook
没有意义,并非如此。 - 法2:这个很奇特,不太会写
- 法3:传统方案,不管用
- 法4:用
torch.fx
,其实这个本质上就是修改了源码
方法
方法 1:使用 PyTorch 的 register_forward_hook
你可以为模型的某个子模块(如 q_proj
、k_proj
)注册一个钩子,在推理时捕获它们的输出。
示例代码
import torch
from transformers import AutoModelForCausalLM, AutoTokenizermodel_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")# 存储各层输出的字典
outputs = {}def get_layer_output_hook(name):def hook(module, input, output):outputs[name] = outputreturn hook# 注册钩子(以第0层的self_attn的q_proj为例)
layer_idx = 0
q_proj = model.model.layers[layer_idx].self_attn.q_proj
hook = q_proj.register_forward_hook(get_layer_output_hook(f"layer_{layer_idx}_q_proj"))# 运行推理
input_text = "Hello, how are you?"
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
with torch.no_grad():model.generate(**inputs, max_new_tokens=5)# 查看输出
print(outputs) # 包含 layer_0_q_proj 的输出
hook.remove() # 移除钩子
适用场景
- 适用于 单次推理 时获取特定层的输出。
- 可以注册多个钩子,捕获不同层的输出。
方法 2:自定义前向传播(修改 forward
方法)
如果你希望更灵活地控制前向传播过程,可以 临时替换 某些模块的 forward
方法,以记录中间结果。
示例代码
original_forward = None
captured_outputs = []def custom_forward(module, *args, **kwargs):global original_forwardoutput = original_forward(*args, **kwargs)captured_outputs.append(output)return output# 替换第0层 self_attn.q_proj 的 forward 方法
layer_idx = 0
q_proj = model.model.layers[layer_idx].self_attn.q_proj
original_forward = q_proj.forward
q_proj.forward = lambda *args, **kwargs: custom_forward(q_proj, *args, **kwargs)# 运行推理
input_text = "Hello, how are you?"
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
with torch.no_grad():model.generate(**inputs, max_new_tokens=5)# 恢复原始 forward 方法
q_proj.forward = original_forwardprint(captured_outputs) # 包含 q_proj 的输出
适用场景
- 适用于需要 更灵活控制 的情况,比如在训练时动态修改某些层的行为。
- 比钩子更灵活,但需要手动管理
forward
方法的替换和恢复。
output_attentions
或 output_hidden_states
方法 3:使用 某些 HuggingFace 模型支持直接返回注意力权重或隐藏状态(但可能不适用于所有层或所有模型)。
示例代码
inputs = tokenizer("Hello, how are you?", return_tensors="pt").to(model.device)
outputs = model(**inputs,output_attentions=True, # 返回注意力权重output_hidden_states=True, # 返回所有隐藏状态
)# 获取第0层的隐藏状态
hidden_states = outputs.hidden_states[0]
print(hidden_states.shape) # [batch_size, seq_len, hidden_dim]# 获取第0层的注意力权重
attentions = outputs.attentions[0]
print(attentions.shape) # [batch_size, num_heads, seq_len, seq_len]
适用场景
- 适用于 标准 Transformer 结构,但可能无法获取
q_proj
/k_proj
的中间输出。 - 适用于快速获取 注意力权重 或 隐藏状态。
方法 4:使用 torch.fx
进行动态追踪
如果你需要 更复杂的中间结果提取,可以使用 PyTorch 的 torch.fx
动态追踪计算图。
示例代码
from torch.fx import symbolic_trace# 追踪模型的计算图
traced_model = symbolic_trace(model)# 自定义提取中间结果
class ExtractModuleOutputs(torch.nn.Module):def __init__(self, model):super().__init__()self.model = modeldef forward(self, *args, **kwargs):# 在这里手动提取中间结果return self.model(*args, **kwargs)# 运行推理
extractor = ExtractModuleOutputs(traced_model)
output = extractor(**inputs)
简单一个例子,比如还是查看第1层的q_proj的输出:
import torch
from torch.fx import symbolic_trace
from transformers import AutoModelForCausalLM, AutoTokenizer# 加载模型和分词器
model_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")# 定义一个包装类,用于提取中间输出
class ExtractModuleOutputs(torch.nn.Module):def __init__(self, model, target_module_path):super().__init__()self.model = modelself.target_module_path = target_module_pathself.captured_output = None# 使用 torch.fx 追踪计算图self.traced_model = symbolic_trace(self.model)# 在计算图中插入代码,捕获目标模块的输出self._insert_capture_code()def _insert_capture_code(self):# 找到目标模块(例如 "model.layers.0.self_attn.q_proj")target_module = eval(f"self.model.{self.target_module_path}")# 定义一个钩子函数,用于捕获输出def capture_output(module, input, output):self.captured_output = outputreturn output # 不影响原始计算# 注册钩子target_module.register_forward_hook(capture_output)def forward(self, *args, **kwargs):# 运行追踪后的模型self.captured_output = None # 清空之前的捕获output = self.traced_model(*args, **kwargs)return output# 初始化提取器,指定目标模块路径
extractor = ExtractModuleOutputs(model,target_module_path="model.layers[0].self_attn.q_proj"
)# 运行推理并捕获输出
input_text = "Hello, how are you?"
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
with torch.no_grad():logits = extractor(**inputs) # 正常前向传播q_proj_output = extractor.captured_output # 提取的 q_proj 输出print("q_proj output shape:", q_proj_output.shape) # 例如 [batch_size, seq_len, hidden_dim]
用的还是hook来找的,不过你也可以直接把源码复制一份扔到forward
里,然后手动logging一下想要的输出,不过这个事情似乎并不是很好实现,因为源码是高度解耦的,不太好直接并起来。
适用场景
- 适用于 复杂模型,需要 动态修改计算图 的情况。
- 学习成本较高,但灵活性最强。
总结
方法 | 适用场景 | 灵活性 | 实现难度 |
---|---|---|---|
钩子 (register_forward_hook ) | 单次推理,获取中间输出 | 中等 | 低 |
自定义 forward | 需要动态修改前向逻辑 | 高 | 中 |
output_attentions /output_hidden_states | 获取标准隐藏状态/注意力 | 低 | 低 |
torch.fx | 复杂模型,动态修改计算图 | 最高 | 高 |
如果你的目标是 简单获取 q_proj
/k_proj
的输出,推荐 方法 1(钩子),因为它不需要修改模型结构,且易于管理。
前向钩子的输出到底是什么?
在方法1中,一般来讲q_proj
的钩子所捕获的输出应该是 Q x Qx Qx吧,那加入权重 Q Q Q被调用了两次,比方说,有 Q x 1 Qx_1 Qx1和 Q x 2 Qx_2 Qx2,那么outputs
里就会有对应的两个输出吗?
deepseek认为我的理解是正确的!在 方法 1(钩子) 中,每次目标模块(如 q_proj
)被调用时,钩子都会捕获它的输出。因此,如果 q_proj
在 一次完整的前向传播中被调用了两次(例如计算 (Qx_1) 和 (Qx_2)),那么 outputs
字典中会 按调用顺序存储这两个输出。
关键验证实验
我们可以通过一个简单的例子验证这一点。假设 q_proj
在模型中被多次调用(例如,由于多轮注意力计算或不同的输入路径):
import torch
from transformers import AutoModelForCausalLM, AutoTokenizermodel_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")# 存储所有调用的输出
outputs = []def hook(module, input, output):outputs.append(output)return output # 不影响原始计算# 注册钩子到第0层的 q_proj
layer_idx = 0
q_proj = model.model.layers[layer_idx].self_attn.q_proj
hook_handle = q_proj.register_forward_hook(hook)# 模拟输入(假设模型内部会多次调用 q_proj)
input_text = "Hello, how are you?"
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)with torch.no_grad():model(**inputs) # 正常前向传播# 检查 outputs
print(f"Number of q_proj calls: {len(outputs)}")
for i, out in enumerate(outputs):print(f"Call {i+1} output shape: {out.shape}")# 移除钩子
hook_handle.remove()
可能的结果
- 如果
q_proj
在 单次前向传播中被调用一次(例如标准的自注意力机制),outputs
会包含 1 个张量(形状如[batch_size, seq_len, hidden_dim]
)。 - 如果
q_proj
被调用多次(例如模型有 多头注意力 或 分块处理),outputs
会按调用顺序存储所有输出。
为什么会出现多次调用?
-
多头注意力(Multi-Head Attention)
- 某些实现中,
q_proj
可能被拆分为多个头(heads),但通常仍是一次性计算 (Qx)。 - 如果每个头独立调用
q_proj
(罕见),则钩子会捕获多次输出。
- 某些实现中,
-
分块处理(Chunking)
- 长序列可能被拆分为块(chunks),导致
q_proj
被多次调用(例如x1
和x2
是两块输入)。
- 长序列可能被拆分为块(chunks),导致
-
自定义模型逻辑
- 如果模型在
forward
中显式调用了多次q_proj
(例如某些动态路由机制),钩子会捕获所有调用。
- 如果模型在
如何区分不同调用的来源?
如果需要明确每次调用对应的输入(例如区分 (Qx_1) 和 (Qx_2)),可以:
- 检查输入参数(通过钩子的
input
参数):def hook(module, input, output):print(f"Input shape: {input[0].shape}") # input 是元组,取第一个元素outputs.append(output)
- 记录调用堆栈(适用于调试):
import traceback def hook(module, input, output):print(f"Call stack:\n{traceback.format_stack()[-3]}")outputs.append(output)
总结
- 钩子会捕获目标模块的所有调用。如果
q_proj
被调用 (N) 次,outputs
会存储 (N) 个输出。 - 典型情况:在标准 Transformer 中,
q_proj
通常 每层每前向传播仅调用一次(计算 (Qx))。 - 异常情况:如果模型有特殊设计(如分块、动态路由),可能会多次调用,此时需结合输入分析。
如果你的目标是 仅捕获第一次调用,可以在钩子中设置条件逻辑(例如 if len(outputs) == 0
)。
反向钩子应该怎么用?
前面的方法针对的是前向钩子,用于捕获推理时的输出值,但是反向钩子同样重要,一般用于捕获梯度。
在 PyTorch 中,register_backward_hook
用于在反向传播(即梯度计算)时捕获模块的 输入梯度、输出梯度 或 权重梯度。它的使用方式与 register_forward_hook
类似,但关注的是反向传播过程。以下是详细说明和示例:
1. register_backward_hook
的基本用法
钩子函数的签名如下:
def backward_hook(module, grad_input, grad_output) -> Tensor or None:# grad_input: 输入的梯度(通常是一个元组,对应 forward 输入的梯度)# grad_output: 输出的梯度(通常是一个元组,对应 forward 输出的梯度)# 可以返回修改后的梯度(可选)return modified_grad_input
关键点
grad_input
:是forward
输入的梯度(例如x
的梯度),格式为Tuple[Tensor]
。grad_output
:是forward
输出的梯度(例如y
的梯度),格式为Tuple[Tensor]
。- 返回值:可以修改
grad_input
并返回(例如梯度裁剪),如果不需要修改则返回None
。
2. 示例:捕获 q_proj
的反向梯度
假设我们想捕获 q_proj
在反向传播时的 输入梯度 和 输出梯度:
import torch
from transformers import AutoModelForCausalLM, AutoTokenizermodel_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")# 存储梯度信息
gradients = {"grad_input": None,"grad_output": None
}def backward_hook(module, grad_input, grad_output):gradients["grad_input"] = grad_input # 保存输入的梯度gradients["grad_output"] = grad_output # 保存输出的梯度return None # 不修改梯度# 注册反向钩子到第0层的 q_proj
layer_idx = 0
q_proj = model.model.layers[layer_idx].self_attn.q_proj
hook_handle = q_proj.register_backward_hook(backward_hook)# 准备输入并计算损失
input_text = "Hello, how are you?"
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
outputs = model(**inputs, labels=inputs["input_ids"]) # 需要 labels 以计算 loss
loss = outputs.loss# 反向传播(触发钩子)
loss.backward()# 检查梯度
print("grad_input (q_proj 输入的梯度):", [g.shape for g in gradients["grad_input"] if g is not None])
print("grad_output (q_proj 输出的梯度):", [g.shape for g in gradients["grad_output"] if g is not None])# 移除钩子
hook_handle.remove()
输出解释
grad_input
:是q_proj.forward
的输入(即x
)的梯度,形状通常与x
相同。grad_output
:是q_proj.forward
的输出(即Qx
)的梯度,形状通常与Qx
相同。
3. 常见用途
(1) 梯度裁剪(Gradient Clipping)
def gradient_clip_hook(module, grad_input, grad_output):max_norm = 1.0grad_input = tuple(torch.clamp(g, -max_norm, max_norm) for g in grad_input)return grad_input # 返回修改后的输入梯度
(2) 梯度检查(Debugging)
def debug_hook(module, grad_input, grad_output):print(f"Module: {module.__class__.__name__}")print(f"grad_input: {[g.shape for g in grad_input]}")print(f"grad_output: {[g.shape for g in grad_output]}")return None
(3) 梯度修改(自定义优化策略)
def custom_grad_hook(module, grad_input, grad_output):# 例如:对梯度加权grad_input = tuple(g * 0.5 for g in grad_input)return grad_input
4. 注意事项
-
梯度顺序
grad_input
的顺序与forward
的输入顺序一致,但可能包含None
(例如未计算梯度的输入)。- 对于
Linear
层(如q_proj
),grad_input
通常是(x的梯度, weight的梯度, bias的梯度)
。
-
内存占用
- 反向钩子会保存梯度张量,可能增加内存消耗,调试后需及时移除钩子:
hook_handle.remove() # 必须手动移除!
- 反向钩子会保存梯度张量,可能增加内存消耗,调试后需及时移除钩子:
-
动态图 vs 静态图
- 在
torch.no_grad()
或eval()
模式下,反向钩子不会被触发。 - 如果模型使用
torch.jit.script
或torch.compile
,钩子可能失效。
- 在
5. 与 register_full_backward_hook
的区别
PyTorch 2.0+ 引入了 register_full_backward_hook
,它更强大且兼容自动微分(autograd):
def full_backward_hook(module, grad_input, grad_output) -> Tuple[Tensor]:# 可以同时修改输入和输出梯度return modified_grad_input, modified_grad_outputhook_handle = q_proj.register_full_backward_hook(full_backward_hook)
总结
register_backward_hook
用于捕获或修改反向传播的梯度。- 典型场景:梯度调试、裁剪、自定义优化。
- 重要步骤:
- 定义钩子函数,处理
grad_input
/grad_output
。 - 注册到目标模块(如
q_proj
)。 - 运行
loss.backward()
触发钩子。 - 务必移除钩子(避免内存泄漏)。
- 定义钩子函数,处理
更好的写法:钩子与装饰器
第二天写到一半发现,hook明显和装饰器更契合,便于代码复用与热插拔。
下面是一个例子:
你的想法非常好!使用 装饰器(Decorator) 来封装前向钩子的注册逻辑,可以大幅简化代码,使其更模块化和可复用。以下是完整的实现示例,包含装饰器的定义和用法:
1. 定义装饰器 register_forward_hook_decorator
这个装饰器的作用是:
- 自动注册前向钩子 到指定模块(如
q_proj
)。 - 捕获模块的输出,并存储到预定义的字典中。
- 在函数调用结束后自动移除钩子,避免内存泄漏。
import torch
from functools import wraps
from transformers import AutoModelForCausalLM, AutoTokenizerdef register_forward_hook_decorator(module_path):"""装饰器:注册前向钩子到指定模块,并捕获其输出。参数:module_path (str): 目标模块的路径,例如 "model.layers[0].self_attn.q_proj"。"""def decorator(func):@wraps(func)def wrapper(model, *args, **kwargs):# 获取目标模块target_module = eval(f"model.{module_path}")# 存储输出的字典outputs = {}# 定义钩子函数def hook(module, input, output):outputs["captured_output"] = output# 注册钩子hook_handle = target_module.register_forward_hook(hook)try:# 调用原始函数(例如 model.generate 或 model.forward)result = func(model, *args, **kwargs)# 将捕获的输出附加到结果中(可选)if hasattr(result, "logs"):result.logs = outputselse:result.captured_output = outputs["captured_output"]return resultfinally:# 确保钩子被移除hook_handle.remove()return wrapperreturn decorator
2. 使用装饰器捕获 q_proj
的输出
示例 1:装饰 model.generate
# 加载模型
model_name = "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")# 定义装饰函数
@register_forward_hook_decorator("model.layers[0].self_attn.q_proj")
def generate_with_hook(model, inputs, **kwargs):return model.generate(**inputs, **kwargs)# 调用函数(自动捕获输出)
input_text = "Hello, how are you?"
inputs = tokenizer(input_text, return_tensors="pt").to(model.device)
output = generate_with_hook(model, inputs, max_new_tokens=5)# 获取捕获的输出
print("Captured q_proj output:", output.captured_output.shape) # 例如 [batch_size, seq_len, hidden_dim]
示例 2:装饰 model.forward
@register_forward_hook_decorator("model.layers[0].self_attn.k_proj")
def forward_with_hook(model, inputs, **kwargs):return model(**inputs, **kwargs)outputs = forward_with_hook(model, inputs)
print("Captured k_proj output:", outputs.captured_output.shape)
3. 进阶:支持多模块捕获
如果需要同时捕获多个模块的输出,可以扩展装饰器:
def register_multi_hooks_decorator(module_paths):"""装饰器:注册多个前向钩子,捕获多个模块的输出。参数:module_paths (List[str]): 目标模块路径列表,例如 ["model.layers[0].self_attn.q_proj", ...]。"""def decorator(func):@wraps(func)def wrapper(model, *args, **kwargs):outputs = {}handles = []# 为每个模块注册钩子for path in module_paths:module = eval(f"model.{path}")def make_hook(name):def hook(module, input, output):outputs[name] = outputreturn hookhandles.append(module.register_forward_hook(make_hook(path)))try:result = func(model, *args, **kwargs)result.captured_outputs = outputs # 附加所有输出return resultfinally:for handle in handles:handle.remove()return wrapperreturn decorator# 使用示例
@register_multi_hooks_decorator(["model.layers[0].self_attn.q_proj","model.layers[0].self_attn.k_proj"
])
def forward_with_multi_hooks(model, inputs):return model(**inputs)outputs = forward_with_multi_hooks(model, inputs)
print("q_proj output:", outputs.captured_outputs["model.layers[0].self_attn.q_proj"].shape)
print("k_proj output:", outputs.captured_outputs["model.layers[0].self_attn.k_proj"].shape)
4. 关键点说明
- 自动清理钩子
- 使用
try/finally
确保钩子始终被移除,避免内存泄漏。
- 使用
- 输出附加到结果
- 将捕获的输出直接附加到模型返回的结果对象上(如
output.captured_output
),方便访问。
- 将捕获的输出直接附加到模型返回的结果对象上(如
- 灵活性
- 装饰器可以动态指定任意模块路径(如
"model.layers[1].mlp.dense_h_to_4h"
)。
- 装饰器可以动态指定任意模块路径(如
总而言之,又是被AI拷打的一天。
相关文章:
【速写】hook与fx
文章目录 问题方法方法 1:使用 PyTorch 的 register_forward_hook方法 2:自定义前向传播(修改 forward 方法)方法 3:使用 output_attentions 或 output_hidden_states方法 4:使用 torch.fx 进行动态追踪总结…...
基于javaweb的SpringBoot扶农助农平台管理系统设计与实现(源码+文档+部署讲解)
技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文…...
用户模块-SpringEvent观察者模式
1. 背景与需求 在很多系统中,我们常常需要对用户的行为进行处理,比如发放奖励、处理通知等。在这个例子中,我们希望在两个场景下发放“改名卡”这个奖励: 用户注册时:当一个新用户注册成功时,我们希望立即发…...
三目云台转动性能稳定性
三目云台是一种具备三个摄像头或观测窗口的云台设备,其转动性能对于实现全方位、多角度的监控或观测至关重要。以下是对三目云台转动的详细分析: 一、转动原理 云台本身是一种摄像机稳定器,通过内置的电机和控制系统实现转动。三目云台则在…...
Python基础语法3
目录 1、函数 1.1、语法格式 1.2、函数返回值 1.3、变量作用域 1.4、执行过程 1.5、链式调用 1.6、嵌套调用 1.7、函数递归 1.8、参数默认值 1.9、关键字参数 2、列表 2.1、创建列表 2.2、下标访问 2.3、切片操作 2.4、遍历列表元素 2.5、新增元素 2.6、查找元…...
45、子类需要重写父类的构造函数嘛,子类自己的构造函数呢?
45、子类需要重写父类的构造函数嘛,子类自己的构造函数呢? 一、子类是否需要重写父类的构造函数? 1. 不需要重写的场景 基类有无参构造函数 若父类(基类)显式或隐式定义了无参构造函数,子类无需重写构造函…...
C语言 ——— 分支循环语句
目录 分支循环语句 单分支 多分支 switch 分支语句 牛刀小试 判断一个数是否是奇数 输出 1-100之间 的奇数 计算 n 的阶乘 计算 1! 2! 3! ... n! 在一个有序数组中查找具体的某一个数字 打印 100-200 之间的素数 求两个整数的最大公约数 getchar函数 和 putc…...
解耦旧系统的利器:Java 中的适配器模式(Adapter Pattern)实战解析
在现代软件开发中,我们经常需要与旧系统、第三方库或不一致接口打交道。这时候,如果能优雅地整合这些不兼容组件,又不破坏原有结构,就需要一位“翻译官” —— 适配器模式。本文将通过 Java 实例,详细讲解适配器模式的…...
C++学习之游戏服务器开发十五QT登录器实现
目录 1.界面搭建 2.登录客户端步骤分析 3.拼接登录请求实现 4.发送http请求 5.服务器登录请求处理 6.客户端处理服务器回复数据 7.注册页面启动 8.qt启动游戏程序 1.界面搭建 2.登录客户端步骤分析 3.拼接登录请求实现 CGI 程序处理流程 程序员自己写程序处理各种业务 …...
搭建Stable Diffusion图像生成系统实现通过网址访问(Ngrok+Flask实现项目系统公网测试,轻量易部署)
目录 前言 背景与需求 🎯 需求分析 核心功能 网络优化 方案确认 1. 安装 Flask 和 Ngrok 2. 构建 Flask 应用 3. 使用 Ngrok 实现内网穿透 4. 测试图像生成接口 技术栈 实现流程 优化目标 实现细节 1. 迁移到Flask 2. 持久化提示词 3. 图像下载功能 …...
第五章:5.3 ESP32物联网应用:阿里云IoT平台与腾讯云IoT平台的数据上传与远程控制
一、阿里云IoT平台接入 1. 准备工作 注册阿里云账号 访问阿里云官网,注册并完成实名认证。创建产品和设备 进入物联网平台控制台 → 公共实例 → 创建产品(例如产品名称“ESP32_Sensor”,节点…...
【AI News | 20250423】每日AI进展
AI Repos 1、suna Suna是一款完全开源的AI助手,旨在通过自然对话帮助用户轻松完成现实世界的任务。它作为您的数字伙伴,提供研究、数据分析和日常问题解决等功能,并结合强大的能力与直观的界面,理解您的需求并交付成果。Suna的工…...
3.1 Agent定义与分类:自主Agent、协作Agent与混合Agent的特点
随着人工智能技术的快速发展,智能代理(Agent)作为一种能够感知环境、自主决策并采取行动的计算实体,已成为人工智能领域的重要研究对象和应用工具。特别是在大模型(Large Models)的赋能下,Agent…...
stack和queue的学习
stack的介绍 stack的文档介绍 stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,…...
leetcode-位运算
位运算 371. 两整数之和 题目 给你两个整数 a 和 b ,不使用 运算符 和 - ,计算并返回两整数之和。 示例 1: 输入: a 1, b 2 输出: 3 示例 2: 输入: a 2, b 3 输出: 5 提示&am…...
《浔川AI翻译v6.1.0问题已修复公告》
《浔川AI翻译v6.1.0问题已修复公告》 尊敬的浔川AI翻译用户: 感谢您对浔川AI翻译的支持与反馈!我们已针对 **v6.1.0** 版本中用户反馈的多个问题进行了全面修复,并优化了系统稳定性。以下是本次修复的主要内容: 已修复问题 ✅…...
Unity 创建、读取、改写Excel表格数据
1.导入EPPlus.dll、Excel.dll、Mysql.Data.dll、System.Data.dll;(我这里用的是:Unity2017.3.0) 2.代码如下: using System.Data; using System.IO; using UnityEngine; using OfficeOpenXml; using UnityEditor; us…...
【阿里云大模型高级工程师ACP习题集】2.3 优化提示词改善答疑机器人回答质量
练习题: 【单选题】在使用大模型进行意图识别时,通过设计特定提示词引导模型生成符合预期回答的方法,其本质是( )。 A. 修改模型本身参数 B. 依靠构造输入激发模型内部已有知识 C. 对模型进行微调 D. 改变模型的训练数据 【多选题】以下哪些属于提示词框架中的要素( )。…...
富文本编辑器实现
🎨 富文本编辑器实现原理全解析 📝 基本实现路径图 #mermaid-svg-MO1B8a6kAOmD8B6Y {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-MO1B8a6kAOmD8B6Y .error-icon{fill:#552222;}#mermaid-s…...
海量粒子特效解决方案:VEG
Unity 官方除了一个 GPU 粒子特效的解决方案:Visual Effect Graph,即 VEG,能支持百万级粒子特效的播放。在性能要求高的使用场景中,这个解决方案就能完美解决原本 Particle System 性能低下的问题。关于 VEG 的基本使用方法参考官…...
Java高频面试之并发编程-06
hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝🐶 面试官:线程上下文切换是什么? 线程上下文切换(Thread Context Switching)是操作系统中 CPU…...
Windows 同步技术-一次性初始化
组件通常设计为在首次调用时执行初始化任务,而不是加载它们时。 一次性初始化函数可确保此初始化仅发生一次,即使多个线程可能尝试初始化也是如此。 Windows Server 2003 和 Windows XP: 应用程序必须使用 互锁函数 或其他同步机制提供自己的…...
Transformer起源-Attention Is All You Need
这篇笔记主要讲解Attention Is All You Need论文。《Attention Is All You Need》由 Ashish Vaswani 等人撰写,于 2017 年发表在 NIPS(Neural Information Processing Systems)会议上。它提出了一种全新的神经网络架构——Transformer&#x…...
被裁20240927 --- 视觉目标跟踪算法
永远都像初次见你那样使我心荡漾 参考文献目前主流的视觉目标跟踪算法一、传统跟踪算法1. 卡尔曼滤波(Kalman Filter)2. 相关滤波(Correlation Filter,如KCF、MOSSE)3. 均值漂移(MeanShift/CamShift&#x…...
每日学习Java之一万个为什么(JUC)
文章目录 Git复习synchronized介绍基本概念特点 使用模板1. 同步方法格式特点 2. 同步代码块格式特点 常见面试题1. synchronized的实现原理?2. synchronized与ReentrantLock的区别?3. synchronized的缺点?4. 死锁的四个必要条件?…...
代码分享:python实现svg图片转换为png和gif
import cairosvg import imageio from PIL import Image import io import osdef svg_to_png(svg_path, png_path):try:cairosvg.svg2png(urlsvg_path, write_topng_path)print(f"成功将 {svg_path} 转换为 {png_path}")except Exception as e:print(f"转换为 P…...
前端热门面试题day1
内容回答较粗糙,如有疑问请自行搜索资料 什么是vue中的slot?它有什么作用 Vue中的Slot(插槽)就像给组件预先留的“内容停车位”,让父组件能把自定义内容“塞”到子组件的指定位置。它的主要作用是: 灵活定…...
DCAN,ECAN和MCAN的区别
DCAN、ECAN和MCAN的主要区别在于它们各自的管理范围和功能。 DCAN(动力CAN系统):DCAN主要负责协调电机控制单元(MCU)、电池管理系统(BMS)、直流电压变换器(DC/DC)和…...
基于Python爬虫的豆瓣电影信息爬取(可以根据选择电影编号得到需要的电影信息)
# 豆瓣电影信息爬虫(展示效果如下图所示:) 这是一个功能强大的豆瓣电影信息爬虫程序,可以获取豆瓣电影 Top 250 的详细信息。 ## 功能特点 - 自动爬取豆瓣电影 Top 250 的所有电影信息 - 支持分页获取,每页 25 部电影,共 10 页 - 获取每部电影的详细信息,包括: - 标题…...
Linux系统学习----概述与目录结构
linux 是一个开源、免费的操作系统,其稳定性、安全性、处理多并发已经得到业界的认可,目前很多企业级的项目 (c/c/php/python/java/go)都会部署到 Linux/unix 系统上。 一、虚拟机系统操作 1.网络连接的三种方式(桥接模式、nat模式、主机模…...
软考资料分享
分享一些软考资料 16系统分析师-基础知识精讲夸克网盘分享1701系统分析师夸克网盘分享1804系统架构设计师夸克网盘分享19软考系统架构设计师2024年11月夸克网盘分享2006信息系统项目管理师夸克网盘分享21软考高级信息系统项目夸克网盘分享22系统分析师视频教程真题资料夸克网盘…...
什么是 GLTF/GLB? 3D 内容创建的基本数据格式说明,怎么下载GLB/GLTF格式模型
GLTF 概述 GLTF 是一种 3D 模型格式,广泛用于在 Web 上共享和显示 3D 内容。 它经过优化,可在 WebGL 中轻松加载,使用 WebGL 可以快速高效地渲染 3D 模型。 GLTF 是 Khronos Group 开发的开放标准之一,以 JSON 或二进制格式表示…...
湖南大学-操作系统实验四
HUNAN UNIVERSITY 操作系统实验报告 一、实验题目 实验四 中断、异常和陷阱指令是操作系统的基石,现代操作系统就是由中断驱动的。本实验和实验五的目的在于深刻理解中断的原理和机制,掌握CPU访问中断控制器的方法,掌握Arm体系结构的中断机…...
3.第三章:数据治理的战略价值
文章目录 3.1 数据治理与企业战略3.1.1 数据驱动的决策体系3.1.2 数据资产的价值挖掘3.1.3 风险防控与合规管理 3.2 数据治理的业务价值3.2.1 提升客户体验3.2.2 优化运营效率3.2.3 加速产品创新 3.3 数据治理的技术价值3.3.1 提升数据质量3.3.2 优化数据架构3.3.3 增强系统集成…...
[KVM] KVM挂起状态恢复失败与KVM存储池迁移
背景:发现KVM host上的几台虚拟机挂起了(paused),但是并没有执行virsh suspend <vm_hostname>,且使用virsh resume <vm_hostname> 无法恢复。原因是这个几个虚拟机归属的存储池所在的磁盘满了。所以想把虚拟机迁移到磁盘空间富余…...
图文结合 - 光伏系统产品设计PRD文档 -(慧哥)慧知开源充电桩平台
光伏系统产品设计PRD文档 版本号:1.0 修订日期:2023年10月 作者: 一、文档概述 1.1 背景与目标 行业背景:全球光伏装机量年增长20%,数字化运维需求迫切用户痛点:现有系统存在数据延…...
linux-相关命令
一、Linux 详细介绍 1. 什么是 Linux? Linux 是一个开源的类 Unix 操作系统,其核心是 Linux 内核。它最早由 Linus Torvalds 在 1991 年发布,后来逐渐发展成各种发行版(如 Ubuntu、CentOS、Debian、Arch 等)。 2. L…...
Hive中Map和Reduce阶段的分工
在Hive查询执行过程中,Map和Reduce阶段有明确的分工,但实际情况要复杂一些。 基本分工原则 Map阶段: 主要职责是读取输入数据并进行初步处理输出键值对形式的数据Reduce阶段: 接收Map阶段输出的键值对对相同键的值进行聚合/计算输出最终结果实际执行中的复…...
前端笔记-Vue router
学习目标 Vue Router路由管理1、路由配置2、嵌套路由3、路由守卫与权限控制 一、路由配置(给网站做地图) npm i vue-router 作用:告诉浏览器什么地址该显示什么页面 核心代码: // 创建路由并暴露出去// 第一步&#x…...
MySQL的日志--Redo Log【学习笔记】
MySQL的日志--Redo Log 知识来源: 《MySQL是怎样运行的》--- 小孩子4919 MySQL的事务四大特性之一就是持久性(Durability)。但是底层是如何实现的呢?这就需要我们的Redo Log(重做日志)闪亮登场了。它记录着…...
《系统分析师-第三阶段—总结(五)》
背景 采用三遍读书法进行阅读,此阶段是第三遍。 过程 第9章 总结 在这个过程中,对导图的规范越来越清楚,开始结构化,找关系,找联系。...
【LangChain4j】AI 第二弹:项目中接入 LangChain4j
普通接入方式 参考文档: Get Started https://docs.langchain4j.dev/get-started 1.添加依赖 <!-- 基于open-ai的langchain4j接口:ChatGPT、deepseek都是open-ai标准下的大模型 --> <dependency><groupId>dev.langchain4j</grou…...
测试基础笔记第十天
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、查询语句1.基本查询2.条件查询3.模糊查询4.范围查询5.判断空 二、其他复杂查询1.排序2.聚合函数3.分组4.分页查询 一、查询语句 1.基本查询 – 需求1: 准备商…...
代理模式:控制对象访问的中间层设计
代理模式:控制对象访问的中间层设计 一、模式核心:通过代理对象控制对目标对象的访问 在软件开发中,有时需要为对象添加一个 “代理” 来控制对它的访问,例如: 远程代理:访问远程对象时(如 R…...
Python类和对象二(十一)
构造函数: 重写: 通过类名访问类里面的方法的做法,称为调用未绑定的父类方法,他有时候会产生钻石继承问题: 发现A重复初始化了两次,类c同事继承类B1和B2,类B1和B2又是继承类A的,当c…...
大模型在代码安全检测中的应用
大模型在代码安全检测领域的应用近年来取得显著进展,尤其在代码审查(Code Review, CR)场景中展现出高效性与准确性。以下是其核心优势、技术路径、挑战及实践案例的总结: 一、技术优势与核心能力 语义理解与上下文分析 大模型通过…...
Python实现图片浏览器
Python实现图片浏览器 支持浏览多种常见图片格式:JPG, JPEG, PNG, GIF, BMP, TIFF, WEBP 通过"打开文件夹"按钮选择任何包含图片的文件夹 灵活的排序选项: 按时间排序(新→旧或旧→新) 按文件名排序(A→…...
网页设计规范:从布局到交互的全方位指南
网页设计规范看似繁杂,但其实都是为了给用户提供更好的体验。只有遵循这些规范,才能设计出既美观又实用的网页,让用户在浏览网页时感到舒适、愉悦。 一、用户体验至上 用户体验(UX)是网页设计的核心原则之一。设计师…...
哪些心电图表现无缘事业编体检呢?
根据《公务员录用体检通用标准》心血管系统条款及事业单位体检实施细则,心电图不合格主要涉及以下类型及处置方案: 一、心律失常类 早搏:包括房性早搏、室性早搏和交界性早搏。如果每分钟早搏次数较多(如超过5次)&…...
Java基础系列-HashMap源码解析1-BST树
文章目录 序二叉搜索树(BST)引入查找5插入9极端情况删除删除叶节点 10删除节点只有左子树或只有右子树删除节点既有左子树又有右子树为什么这么代替? 序 提到HashMap,就不得不提红黑树(HashMap1.8之后)&am…...