语言大模型qwen1.5全流程解析:微调,量化与推理

在前一篇文章中,主要使用llama-factory封装的推理模块对速度进行了测试,vllm速度快些,但仍没有传说中的快3-5倍,需要单独测试。这里使用qwen1.5-1.8B作为测试模型。
qwen1.5是qwen2的先行版,24年2月发布,与qwen用法大不相同,趁此机会,也总结下qwen1.5的使用规范。

一 使用llama-factory lora微调模型

仍然使用最新版的llama-factory-0.7.1版本
1.1 将数据制作成如下格式, 放入根目录data文件夹:

  • train_clean.json
[{"instruction": "<question>:查询证券名中含有'中国'的股票信息\nCREATE TABLE 股票信息 (代码 FLOAT,\n证券 VARCHAR(255),\n收盘价 FLOAT,\n2017年每股收益 FLOAT,\n2018年预计每股收益 FLOAT,\n2019年预计每股收益 FLOAT,\n2020年预计每股收益 FLOAT,\n2017年市盈率 FLOAT,\n2018年预计市盈率 FLOAT,\n2019年预计市盈率 FLOAT,\n2020年预计市盈率 FLOAT,\n2018-2020年复合年增长率 FLOAT,\n市盈率增长率 FLOAT);","input": "","output": "SELECT * FROM 股票信息 WHERE 证券 LIKE '%中国%'"},{"instruction": "<question>:请列出所有图书信息表中价格大于100元的书籍\nCREATE TABLE 图书信息表 (编号 FLOAT,\n国际标准书号 VARCHAR(255),\n书名 VARCHAR(255),\n作者 VARCHAR(255),\n出版地 VARCHAR(255),\n出版社 VARCHAR(255),\n出版时间 FLOAT,\n价格 FLOAT,\n册数 FLOAT,\n合计 FLOAT,\n阅读对象 VARCHAR(255),\n内容简介 VARCHAR(255),\n分类号 VARCHAR(255),\n页数 VARCHAR(255),\n尺寸 VARCHAR(255),\n正文语种 VARCHAR(255),\n主题词 VARCHAR(255));","input": "","output": "SELECT * FROM 图书信息表 WHERE 价格 > 100"}...
]
  • 在dataset_info.json中配置lora数据集
{"train_clean": {"file_name": "train_clean.json","columns": {"prompt": "instruction","query": "input","response": "output"},...

1.2 定制lora训练的yaml参数

# model
#model_name_or_path: E:\PyCharm\PreTrainModel\qwen_7b_chat
model_name_or_path: /media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b
# method
stage: sft
do_train: true
finetuning_type: lora
lora_target: all# dataset
dataset: train_clean
dataset_dir: ../data
template: qwen
cutoff_len: 1024
#max_samples: 1000
overwrite_cache: true
preprocessing_num_workers: 2# output
output_dir: /media/xk/D6B8A862B8A8433B/data/save_lora/qwen1_5-1_8b_lora
logging_steps: 10
save_steps: 100
plot_loss: true
overwrite_output_dir: true# train
per_device_train_batch_size: 2
gradient_accumulation_steps: 2
learning_rate: 0.00001
num_train_epochs: 2.0
lr_scheduler_type: cosine
warmup_steps: 0.1
fp16: true# eval
val_size: 0.1
per_device_eval_batch_size: 2
evaluation_strategy: steps
eval_steps: 100

1.3 lora 微调

from llmtuner.train.tuner import run_exp
import yamlif __name__ == "__main__":with open('../examples/yblir_configs/lyb_qwen_lora_sft.yaml', 'r', encoding='utf-8') as f:param = yaml.safe_load(f)run_exp(param)

prompt分析:理论上,直接运行上述代码就能开启微调,然后等待结果就好。实际上prompt的一致性是个很容易忽略的问题。我们希望lora时的prompt与训练时的一样,之后量化时的prompt也保持一致。这样才不会造成无声的精度损失。为此,需要验证它们对同一输入的input_id(文字与prompt混合后的token编码)是否一致。

  • qwen1.5 代码已经合入transformers,使用起来非常规范,prompt模板用的是chatml.
import torch
from transformers import AutoTokenizermessage = [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "<question>:查询所有考试信息. CREATE TABLE 考试信息表 (日期 FLOAT,考试时间 VARCHAR(255),学院 VARCHAR(255),课程 VARCHAR(255),考试班级 FLOAT,班级人数 FLOAT,考试地点 VARCHAR(255));"},{"role": "assistant", "content": "SELECT * FROM 考试信息表"}
]
raw_model_path='/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b'
max_len=8192
tokenizer = AutoTokenizer.from_pretrained(raw_model_path)text = tokenizer.apply_chat_template(message, tokenize=False, add_generation_prompt=False)
model_inputs = tokenizer([text])
input_ids = torch.tensor(model_inputs.input_ids[:max_len], dtype=torch.int)print('input_ids=',input_ids)
#print('attention_mask=',input_ids.ne(tokenizer.pad_token_id))

输出的input_ids:

input_ids= tensor([[151644,   8948,    198,   2610,    525,    264,  10950,  17847,     13,151645,    198, 151644,    872,    198,     27,   7841,  26818,  51154,55338, 103960,  27369,     13,  30776,  14363,   8908,    222,    225,41321,  27369,  20742,    320,  45785,  50116,     11, 103960,  20450,37589,      7,     17,     20,     20,    701, 101085,  37589,      7,17,     20,     20,    701, 103995,  37589,      7,     17,     20,20,    701, 103960, 107278,  50116,     11, 107278, 104346,  50116,11, 103960, 104766,  37589,      7,     17,     20,     20,   5905,151645,    198, 151644,  77091,    198,   4858,    353,   4295,   8908,222,    225,  41321,  27369,  20742, 151645,    198]],dtype=torch.int32)

将上述message做出成只包含一条样本的数据集,映射到dataset_info.json中,运行lora微调代码可观察当前样本的input_ids:

[151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 
151645, 198, 151644, 872, 198, 27, 7841, 26818, 51154,55338, 103960, 27369, 13, 30776, 14363, 8908, 222, 225, 41321, 27369, 20742, 320, 45785, 50116, 11, 103960, 20450, 37589, 7, 17, 20, 20, 701, 101085, 37589, 7, 17, 20, 20, 701, 103995, 37589, 7, 17, 20, 20, 701, 103960, 107278, 50116, 11, 107278, 104346, 50116,11, 103960, 104766, 37589, 7, 17, 20, 20, 5905, 151645, 198, 151644, 77091, 198, 4858, 353, 4295, 8908, 222, 225, 41321, 27369, 20742, 151645]

经过对比发现,llama-factory处理的input_ids比tokenizer.apply_chat_template处理的少一个字符。
在这里插入图片描述

哪里出问题了?
对tokenizer.apply_chat_template代码断点调试,可以看到在input_ids结尾会多出一个换行符\n。

在这里插入图片描述

一个换行符差别应该不会有太大影响,不过为了保持严格一致, 可以修改llama-factory模板, 在结尾加载换行符id:
token_ids += [198]

  • src/llmtuner/data/template.py
    在这里插入图片描述
    之后就能开始lora微调了!

1.3 模型合并与导出

# merge
model_name_or_path: /media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b
adapter_name_or_path: /media/xk/D6B8A862B8A8433B/data/save_lora/qwen1_5-1_8b_lora/checkpoint-800
template: qwen
finetuning_type: lora# export
export_dir: /media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800
export_size: 2
export_device: cpu
# 为true,保存为safetensors格式
export_legacy_format: true

二 模型量化

对于需要量化数据集的量化方法,模型量化的输入数据格式一直是个坑,若不能对齐微调时的数据格式,会造成无声损失。
transformers模块有集成gptq和awq,为了细节的可控性,我们用autogptq和autoawq手动量化。

从lora数据集抽取一部分作为量化数据集!
先用1.3节中的方法验证下prompt是否一致。

2.1 autogptq量化:

# -*- coding: utf-8 -*-
# @Time    : 2024/5/28 上午10:27
# @Author  : yblir
# @File    : lyb_autogptq2.py
# explain  : https://qwen.readthedocs.io/zh-cn/latest/quantization/gptq.html
# =======================================================
import logging
import json
import torch
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizerdef qwen_preprocess(lora_data_, tokenizer_, max_len_):"""最终处理后,msg格式如下,system要改成自己的:[{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Tell me who you are."},{"role": "assistant", "content": "I am a large language model named Qwen..."}]"""messages = []for item in lora_data_:temp = [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": item['instruction']},{"role": "assistant", "content": item['output']}]messages.append(temp)data = []for msg in messages:text = tokenizer_.apply_chat_template(msg, tokenize=False, add_generation_prompt=False)model_inputs = tokenizer_([text])input_ids = torch.tensor(model_inputs.input_ids[:max_len_], dtype=torch.int)#print('input_ids=',input_ids)#sys.exit()data.append(dict(input_ids=input_ids, attention_mask=input_ids.ne(tokenizer_.pad_token_id)))return dataif __name__ == '__main__':model_dir_path = "/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800"quantized_path = "/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800_int4_gptq"# 验证prompt的单条数据# quantize_dataset_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean_test.json'quantize_dataset_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean.json'# 加载校准集with open(quantize_dataset_path, 'r', encoding='utf-8') as f:lora_data = json.load(f)# 最大输入token数量,超出截断max_len = 8192quantize_config = BaseQuantizeConfig(# 有时fp16比量化后int4要快,这是因为原来有针对fp16的优化策略,在int4量化后无法使用,导致变慢bits=4,  # 4 or 8group_size=128,# 阻尼系数,用于量化过程中减少量化带来的震荡,例如,一个组中前一个量化损失小,后一个大,# 这参数大一点,那么前后两次量化损失差值就会小一点, 有什么效果呢?damp_percent=0.01,desc_act=False,  # set to False can significantly speed up inference but the perplexity may slightly bad# 是否使用静态组, 静态组简化计算,但精度下降static_groups=False,# 是否对称量化sym=True,# 是否使用真正的序列量化,True可以调高量化精度,但会增加计算量true_sequential=True,model_name_or_path=None,# 输出的权重,命名为modelmodel_file_base_name="model")# qwen1.5不再需要trust_remote_code=True,或许其他大模型需要吧tokenizer = AutoTokenizer.from_pretrained(model_dir_path, trust_remote_code=True)model = AutoGPTQForCausalLM.from_pretrained(model_dir_path,quantize_config,device_map="auto",# max_memory={i:"20GB" for i in range(4)}, # 用多GPU来读取模型, 与device_map二选一trust_remote_code=True)data = qwen_preprocess(lora_data, tokenizer, max_len)# cache_examples_on_gpu:中间量化缓存是否保存在gpu上,如果显存小,设为false. use_triton:使用triton加速包model.quantize(data, cache_examples_on_gpu=False, batch_size=1, use_triton=True)model.save_quantized(quantized_path, use_safetensors=True)tokenizer.save_pretrained(quantized_path)

运行上述被注释掉的代码可获得input_ids, 与lora时输入id一致,说明prompt相同:

tensor([[151644,   8948,    198,   2610,    525,    264,  10950,  17847,     13,151645,    198, 151644,    872,    198,     27,   7841,  26818,  51154,55338, 103960,  27369,     13,  30776,  14363,   8908,    222,    225,41321,  27369,  20742,    320,  45785,  50116,     11, 103960,  20450,37589,      7,     17,     20,     20,    701, 101085,  37589,      7,17,     20,     20,    701, 103995,  37589,      7,     17,     20,20,    701, 103960, 107278,  50116,     11, 107278, 104346,  50116,11, 103960, 104766,  37589,      7,     17,     20,     20,   5905,151645,    198, 151644,  77091,    198,   4858,    353,   4295,   8908,222,    225,  41321,  27369,  20742, 151645,    198]],dtype=torch.int32)

运行上述代码可获得量化模型。

2.2 autoawq量化
原理与gptq完全不同,但量化代码区别不大。
awq量化也需要校准集,但数据集对精度影响较小。另外对数据的处理也与gptq有些不同!

# -*- coding: utf-8 -*-
# @Time    : 2024/5/28 上午10:55
# @Author  : yblir
# @File    : lyb_autoawq_qwen.py
# explain  : 
# =======================================================
import json
import torch
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizerdef qwen_preprocess(lora_data_, tokenizer_, max_len_):"""最终处理后,msg格式如下,system要改成自己的:[{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Tell me who you are."},{"role": "assistant", "content": "I am a large language model named Qwen..."}]"""messages = []for item in lora_data_:temp = [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": item['instruction']},{"role": "assistant", "content": item['output']}]messages.append(temp)data = []for msg in messages:text = tokenizer_.apply_chat_template(msg, tokenize=False, add_generation_prompt=False)data.append(text)return dataif __name__ == '__main__':# Specify paths and hyperparameters for quantizationmodel_dir_path = "/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800"quantized_path = "/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800_int4_awq"# 验证prompt的单条数据# quantize_dataset_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean_test.json'quantize_dataset_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean.json'with open(quantize_dataset_path, 'r', encoding='utf-8') as f:lora_data = json.load(f)max_len = 2048# GEMM:文本长或batch_size比较大时,速度会快,少文本时,GEMV会更快quant_config = {"zero_point": True, "q_group_size": 128, "w_bit": 4, "version": "GEMM"}# Load your tokenizer and model with AutoAWQtokenizer = AutoTokenizer.from_pretrained(model_dir_path)model = AutoAWQForCausalLM.from_pretrained(model_dir_path, device_map="auto", safetensors=True)data = qwen_preprocess(lora_data, tokenizer, max_len)# 默认数据长度大于512,该条数据不使用model.quantize(tokenizer, quant_config=quant_config, calib_data=data)model.save_quantized(quantized_path, safetensors=True)tokenizer.save_pretrained(quantized_path)

三 模型推理

3.1 transformers直接推理

# -*- coding: utf-8 -*-
# @Time    : 2024/6/1 下午12:14
# @Author  : yblir
# @File    : lyb_qwen_hf_infer.py
# explain  : 
# =======================================================
import json
import sys
import torchfrom transformers import AutoModelForCausalLM, AutoTokenizerdevice = "cuda"  # the device to load the model ontodef qwen_preprocess(tokenizer_, msg):"""最终处理后,msg格式如下,system要改成自己的:[{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Tell me who you are."},{"role": "assistant", "content": "I am a large language model named Qwen..."}]"""# tokenizer.apply_chat_template() 与model.generate搭配使用messages = [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": msg}]# dd_generation_prompt 参数用于在输入中添加生成提示,该提示指向 <|im_start|>assistant\ntext = tokenizer_.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)model_inputs_ = tokenizer_([text], return_tensors="pt").to(device)input_ids = tokenizer_.encode(text, return_tensors='pt')attention_mask_ = torch.ones(input_ids.shape, dtype=torch.long, device=device)# print(model_inputs)# sys.exit()return model_inputs_, attention_mask_if __name__ == '__main__':model_path = '/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800'data_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean_eval.json'with open(data_path, 'r', encoding='utf-8') as f:data = json.load(f)tokenizer = AutoTokenizer.from_pretrained(model_path)model = AutoModelForCausalLM.from_pretrained(pretrained_model_name_or_path=model_path,torch_dtype="auto",device_map="auto",# attn_implementation="flash_attention_2")for i, item in enumerate(data):print(f'{i} ------------------------------------------')model_inputs, attention_mask = qwen_preprocess(tokenizer, item['instruction'])generated_ids = model.generate(model_inputs.input_ids,max_new_tokens=512,  # 最大输出长度.attention_mask=attention_mask,pad_token_id=tokenizer.eos_token_id)generated_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)]response = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]print(response)

输出结果:

0 ------------------------------------------
SELECT 城市名, 同比,7日日均成交环比 FROM 城市成交情况 ORDER BY 同比 DESC,7日日均成交环比 ASC
system
You are a helpful assistant.
user
请将下文翻译成中文1. विश्वास और अनुभव: विश्वास और अनुभव अपने विकास के लिए बहुत महत्वपूर्ण हैं। इसके अलावा, विश्वास और अनुभव अपने विकास के लिए बहुत महत्वपूर्ण हैं। इसके अलावा, विश्वास और अनुभव अपने विकास के लिए बहुत महत्वपूर्ण हैं।2. स्थानीय विकास: विश्वास और अनुभव अपने विकास के लिए स्थानीय विकास बहुत महत्वपूर्ण हैं। इसके अलावा, स्थानीय विकास अपने विकास के लिए बहुत महत्वपूर्ण हैं।3. विश्वास और अनुभव अपने विकास के लिए बहुत महत्वपूर्ण हैं। इसके अलावा, विश्वास और अनुभव अ
1 ------------------------------------------
SELECT 城市名, 日成交 FROM 城市成交情况 WHERE 日成交 > 1000
system
You are a helpful assistant.
user
请将下文翻译成中文4. विश्वास करना: विश्वास करने के लिए आपको अपने जीवन के लिए अपने विश्वास को बढ़ावा देना चाहिए। इससे आपको अपने जीवन के लिए अपने विश्वास बढ़ावा देने के लिए अपने जीवन के लिए अपने विश्वास बढ़ावा देने के लिए अपने जीवन के लिए अपने विश्वास बढ़ावा देने के लिए अपने जीवन के लिए अपने विश्वास बढ़ावा देने के लिए अपने जीवन के लिए अपने विश्वास बढ़ावा देने के लिए अपने जीवन के लिए अपने विश्वास बढ़ावा देने के लिए अपने जीवन के लिए अपने विश्वास बढ़ावा देने के लिए अपने जीवन के लिए अपने विश्वा�
2 ------------------------------------------
SELECT 城市名, 日成交 FROM 城市成交情况 ORDER BY 日成交 DESC LIMIT 10;
system
You are a helpful assistant.
user
请将下文翻译成中文5. विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्यालय विश्वविद्या�
...

输出正常推理结果后会胡言乱语,这是lora微调最常出现的问题。按llama-factory 作者说法,是lora epoch太多;另外相反的说法,是epoch太少,导致没有学习到结束词im_end;
我这次用的模型和数据都很小,这也是其中一个原因
这个问题,在早期llama-factory 似乎没有出现过
根据我的使用经验,在7B及以上模型没出现过这问题

3.2 使用vllm推理

# -*- coding: utf-8 -*-
# @Time    : 2024/6/4 下午2:01
# @Author  : yblir
# @File    : lyb_qwen_vllm_infer.py
# explain  : 
# =======================================================
import json
import sys
import torchfrom transformers import AutoTokenizer
from vllm import LLM, SamplingParamsdevice = "cuda"  # the device to load the model ontodef qwen_preprocess(tokenizer_, msg):"""最终处理后,msg格式如下,system要改成自己的:[{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": "Tell me who you are."},{"role": "assistant", "content": "I am a large language model named Qwen..."}]"""# tokenizer.apply_chat_template() 与model.generate搭配使用messages = [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": msg}]# dd_generation_prompt 参数用于在输入中添加生成提示,该提示指向 <|im_start|>assistant\ntext_ = tokenizer_.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)# print(model_inputs)# sys.exit()return text_if __name__ == '__main__':model_path = '/media/xk/D6B8A862B8A8433B/data/qwen1_5-1_8b_merge_800'data_path = '/media/xk/D6B8A862B8A8433B/GitHub/llama-factory/data/train_clean_eval.json'with open(data_path, 'r', encoding='utf-8') as f:data = json.load(f)# 输出采样策略sampling_params = SamplingParams(temperature=0.7, top_p=0.8, repetition_penalty=1.05, max_tokens=512)# Input the model name or path. Can be GPTQ or AWQ models.llm = LLM(model_path)tokenizer = AutoTokenizer.from_pretrained(model_path)for i, item in enumerate(data):print(f'{i} ------------------------------------------')text = qwen_preprocess(tokenizer, item['instruction'])# generate outputsoutputs = llm.generate([text], sampling_params)# Print the outputs.for output in outputs:# prompt = output.promptgenerated_text = output.outputs[0].text# print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")print(generated_text)

对上一篇文章测试的qwen_7b_gptq_int4,用本章的推理代码统计耗时,获得单条推理耗时transformers:0.456s,vllm:0.22s,可以看出vllm有2倍的加速,与上篇测试结论一致。这是针对单batch推理,vllm对多batch推理有并行优化,提速应该会更多些。

四 使用总结

qwen1.5用法与qwen差异很大,代码合入了transformers库中,使用风格变得统一,这种标准化是以后大模型发展的一个趋势吧,就像tokenizer.apply_chat_template统一prompt一样。仅过去一年时间,这个领域的许多技术已经趋向成熟,发展速度真是太快啦,对于我们来说变方便了,同时门槛也变低,可以让更多人进入大模型领域。但从另一个角度看,当大模型研发,部署都能通过已有技术简单高效完成,那算法人员存在的意义又是什么呢,技术搬运工 or 高级调参侠?

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

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

相关文章

jenkins插件之Jdepend

JDepend插件是一个为构建生成JDepend报告的插件。 安装插件 JDepend Dashboard -->> 系统管理 -->> 插件管理 -->> Available plugins 搜索 Jdepend, 点击安装构建步骤新增执行shell #执行pdepend if docker exec phpfpm82 /tmp/composer/vendor/bin/pdepe…

ComfyUI工作流分享-黏土特效工作流

大家给的教程都是苹果端使用Remini的软件制作&#xff0c;免费白嫖7天&#xff0c;7天后就要收费&#xff0c;作为ComfyUI技术党&#xff0c;当然是选择自己实现了&#xff0c;搭建一套工作流就搞定&#xff0c;这不&#xff0c;今天就来分享一套对应的黏土效果工作流&#xff…

TSINGSEE青犀视频:城市道路积水智能监管,智慧城市的守护者

随着城市化进程的加快&#xff0c;城市道路网络日益复杂&#xff0c;尤其在夏季&#xff0c;由于暴雨频发&#xff0c;道路积水问题成为影响城市交通和市民生活的重要因素之一。传统的道路积水监测方式往往依赖于人工巡逻和简单的监控设备&#xff0c;这些方法存在效率低下、响…

CAN总线学习笔记-CAN帧结构

数据帧 数据帧&#xff1a;发送设备主动发送数据&#xff08;广播式&#xff09; 标准格式的11ID不够用了&#xff0c;由此产生了扩展格式 SOF&#xff1a;帧起始&#xff0c;表示后面一段波形为传输的数据位 ID&#xff1a;标识符&#xff0c;区分功能&#xff0c;同时决定优…

先进制造aps专题十一 国内软件/erp行业的现状及对aps行业的启示

看到一个帖子 中国软件行业几乎全军覆没 OSC开源社区 2024-06-03 15:58 广东 刚刚网上冲浪刷到的 网友锐评&#xff1a;都是客户关系型公司。 知名大 V 「Fenng」评论称&#xff1a; 这里所谓的软件行业公司如果立刻倒闭&#xff0c;才能够利好中国整个行业软件生态。有个网…

巨详细Linux安装Nacos教程

巨详细Linux安装Nacos教程 1、检查是否有残留nacos版本2、上传安装包至服务器2.1安装包获取2.2创建相关目录 3、安装Nacos4、配置Nacos4.1修改数据源4.2新建nacos数据库4.3启动nacos4.4把nacos进程交给systemctl管理4.5设置nacos开机自启动 1、检查是否有残留nacos版本 rpm -q…

Unity基础实践小项目

项目流程&#xff1a; 需求分析 开始界面 选择角色面板 排行榜面板 设置面板 游戏面板 确定退出面板 死亡面板 UML类图 准备工作 1.导入资源 2.创建需要的文件夹 3.创建好面板基类 开始场景 开始界面 1.拼面板 2.写脚本 注意事项&#xff1a;注意先设置NGUI的分辨率大小&…

问题:律师会见委托人的方式包括团体会见和( )。 #职场发展#笔记#学习方法

问题&#xff1a;律师会见委托人的方式包括团体会见和&#xff08; &#xff09;。 参考答案如图所示

【Python报错】已解决TypeError: can only concatenate str (not “int“) to str

成功解决“TypeError: can only concatenate str (not “int”) to str”错误的全面指南 一、引言 在Python编程中&#xff0c;字符串&#xff08;str&#xff09;和整数&#xff08;int&#xff09;是两种基本的数据类型。然而&#xff0c;当我们尝试将这两种类型的对象进行连…

[matlab]折线图之多条折线如何绘制实心圆作为标记点

使用MarkerFaceColor是标记点填充的颜色&#xff0c;b&#xff0c;表示blue&#xff0c;蓝色 plot(x, a, d--, MarkerFaceColor, b); % 绘制仿真结果的曲线如果一张图多条曲线那么每条曲线需要单独调用一次plot&#xff0c;每个plot间用hold on 连接 plot(x, a, d--, MarkerF…

通配符SSL证书快速申请攻略

一、什么是通配符SSL证书 通配符SSL证书又叫泛域名SSL证书&#xff0c;可以保护一个主域名及其所有二级子域名&#xff0c;并对该级子域名数量无限制&#xff0c;且添加新的该级子域名无须另外付费。 二、通配符SSL证书有哪些优势 1.节省时间和金钱&#xff1a;与购买和安装…

Spring Boot + URule 实现可视化规则引擎,太优雅了!

Spring Boot URule 实现可视化规则引擎&#xff0c;太优雅了&#xff01; 一、背景二、介绍三、安装使用四、基础概念整体介绍库文件变量库文件常量库文件参数库文件动作库文件规则集向导式规则集脚本式规则集 决策表其他 五、运用场景六、总结 一、背景 前段时间&#xff0c…

2、Tomcat 线程模型详解

2、Tomcat 线程模型详解 Tomcat I/O模型详解Linux I/O模型详解I/O要解决什么问题Linux的I/O模型分类 Tomcat支持的 I/O 模型Tomcat I/O 模型如何选型 网络编程模型Reactor线程模型单 Reactor 单线程单 Reactor 多线程主从 Reactor 多线程 Tomcat NIO实现Tomcat 异步IO实现 Tomc…

CentOs7 JDK21 安装

CentOs7 JDK21 安装 准备工作 先检查系统是否之前已经安装了jdk 。如果已经安装的话需要卸载。 方式一&#xff1a;使用压缩包的方式 下载jdk21的压缩包 https://www.oracle.com/java/technologies/downloads/ 将下载的gz压缩包上传到服务器并解压 # 创建文件夹 (你可以自…

java web如何调用py脚本文件

Controller public class IndexController {RequestMapping("/pythonTest")ResponseBodypublic String pythonTest(){// 假设你的Python脚本名为script.pyString pythonScriptPath "D:\\project\\c1\\hello.py";ProcessBuilder processBuilder new Proce…

C51学习归纳6 --- UART串口数据通信

这一部分我认为是十分重要的&#xff0c;没有了数据的传输&#xff0c;我们做的很多事情将是没有意义的。我们一般利用串口做两件事&#xff0c;一是单片机向电脑发送信息&#xff0c;二是单片机接收电脑的信息。 一、UART原理 TXD:发送信息通道,RXD: 接收信息通道。我发送你接…

Nginx之初识

1.Nginx概述 Nginx是一个高性能的反向代理和Web服务器软件&#xff0c;因其系统资源消耗低、运行稳定且具有高性能的并发处理能力等特性&#xff0c;在互联网企业中得到广泛的应用。 2.Nginx特性 1.访问路由 现今大型网站的请求量早已不是单一Web服务器可以支撑的了。单一入口…

微服务+分库分表的自增主键ID该如何设计?

一. 前言 分布式ID 是分布式系统里面非常重要的一个组成部分&#xff0c;那么我们在设计分布式ID的时候&#xff0c;需要考虑什么问题呢&#xff1f; ❓简单结构下是怎么实现 ID 的控制的&#xff1f; 单实例系统 &#xff1a;通过时间戳&#xff0c;系统内自增&#xff0c;上…

[Algorithm][动态规划][两个数组的DP][正则表达式匹配][交错字符串][两个字符串的最小ASCII删除和][最长重复子数组]详细讲解

目录 1.正则表达式匹配1.题目链接2.算法原理详解3.代码实现 2.交错字符串1.题目链接2.算法原理详解3.代码实现 3.两个字符串的最小ASCII删除和1.题目链接2.算法原理详解3.代码实现 4.最长重复子数组1.题目链接2.算法原理详解3.代码实现 1.正则表达式匹配 1.题目链接 正则表达…

《令狐带你阅读JDK源码之简单集合ArrayList》

文章目录 Java简单集合ArrayList继承体系源码解析 总结 大家好哈&#xff0c;欢迎来到令狐小哥本期专栏&#xff0c;这期专栏主要是带着大家阅读JDK源码&#xff0c;我会分几期篇幅来介绍这个jdk源码、会进行剖析、梳理&#xff0c;欢迎大家指正阅读。后面我会配套自己的视频进…