复现LLM:带你从零认识语言模型

前言

本文会以Qwen2-0.5B模型为例,从使用者的角度,从零开始一步一步的探索语言模型的推理过程。主要内容如下:

  1. 从使用的角度来接触模型
  2. 本地运行的方式来认识模型
  3. 以文本生成过程来理解模型
  4. 以内部窥探的方式来解剖模型

1. 模型前台使用

1.1 可视化UI的方式使用模型

大部分用户接触大语言模型都是从一个聊天框开始,用户输入一个问题,AI输出一个回答,如下所示。
在这里插入图片描述

1.2 使用openai库来访问模型

如果是编程人员使用模型,则会编写程序通过开放API来访问模型,以便将模型能力集成到自己的程序中,最常见的就是用openai库。

from openai import OpenAIdef predict(prompt):client = OpenAI(api_key="0",base_url="http://127.0.0.1:8000/v1")messages = [{"role": "user", "content": prompt}]result = client.chat.completions.create(messages=messages, model="Qwen2-0.5B-Instruct")return result.choices[0].message.contentpredict("详细介绍下你自己。")

上述代码执行后的输出:

'我是阿里云研发的超大规模语言模型,我叫通义千问。'

上面的client.chat.completions.create方法调用本质上是发送了类似下面一个请求。

!curl -s http://127.0.0.1:8000/v1/chat/completions \-H "Content-Type: application/json" \-d '{ \"model": "Qwen2-0.5B-Instruct",\"messages": [{"role": "user", "content": "详细介绍下你自己。"}],\"max_tokens": 512,\"temperature": 0.7  \}' | jq
  • models: 表示要访问的模型名称;
  • messages: 要发送的提示语,格式需要遵循相应模型的提示词模板,不同模型提示词模型可能不同;
  • max_tokens:最大输出token数量;
  • temperature: 温度参数,控制着token选取的随机性,取值范围0-2,值越小越固定,越大越随机。

注:最后用管道符|连接的jq命令,作用是将输出内容进行json格式化。

这个http请求的原始输出如下:

{"id": "chat-373150f974fd4367b53632817ed44cbc","object": "chat.completion","created": 1730414253,"model": "Qwen2-0.5B-Instruct","choices": [{"index": 0,"message": {"role": "assistant","content": "我是阿里云开发的一款超大规模语言模型,我叫通义千问。","tool_calls": []},"logprobs": null,"finish_reason": "stop","stop_reason": null}],"usage": {"prompt_tokens": 23,"total_tokens": 41,"completion_tokens": 18},"prompt_logprobs": null
}

上面是OpenAI接口的完整response,在聊天窗口中显示的推理结果只是取了response['choices'][0]['message']['content']

相信通过上面的演示,我们已经了解如何在前端通过http接口来访问模型的能力。那这个http请求在服务器后台是怎么运行的呢?

2. 模型后台运行

模型的后台运行我们可以简单分为如下四步:

  1. 标准化提示语
  2. 序列化提示语
  3. 模型推理
  4. 反序列化为文本

但在处理我们从前端发送的请求之前,需要先加载模型。

2.1 加载模型

加载模型最基本的方式是使用 transformers 库,它是由 Hugging Face 开发的一个开源库,提供了很方便的方式来访问和使用各种自然语言处理(NLP)模型,也提供了便捷的API来微调和训练开源模型。

目前业界开源的语言模型基本都会发布到HuggingFace,也都支持transformers库来加载。huggingface上不仅提供模型、开发库,还提供数据集甚至算力。

先来看下要加载的模型文件。

!ls -l /data2/anti_fraud/models/modelscope/hub/Qwen/Qwen2-0___5B-Instruct
total 976188
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua       659 Aug  1 18:26 config.json
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua        48 Aug  1 18:26 configuration.json
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua       242 Aug  1 18:26 generation_config.json
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua     11344 Aug  1 18:26 LICENSE
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua   1671839 Aug  1 18:26 merges.txt
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua 988097824 Aug  1 18:31 model.safetensors
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua      3551 Aug  1 18:31 README.md
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua      1287 Aug  1 18:31 tokenizer_config.json
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua   7028015 Aug  1 18:31 tokenizer.json
-rw-rw-r-- 1 xiaoguanghua xiaoguanghua   2776833 Aug  1 18:31 vocab.json

上面输出中的model.safetensors就是要加载的模型参数,约988MB,而tokenizer.jsonvocab.jsonmerges.txt则是分词器相关的词表和词条合并记录。

导入常用库,并加载模型和分词器。

import os
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer# 定义模型本地路径和要加载到的目标设备
device = "cuda:4" 
model_path = "/data2/anti_fraud/models/modelscope/hub/Qwen/Qwen2-0___5B-Instruct"model = AutoModelForCausalLM.from_pretrained(model_path, torch_dtype=torch.bfloat16).to(device)
tokenizer = AutoTokenizer.from_pretrained(model_path, use_fast=False, trust_remote_code=True)
  • AutoModelForCausalLM用于加载因果语言模型,“因果”的含义是指模型在生成新词时只依赖于之前生成的词,而不考虑未来的词。

  • AutoTokenizer用于加载分词器,它能根据模型路径自动加载词表。

2.2 标准化提示语

每个模型都有一套提示词模板,这个提示词模板是被模型训练过的,只要按这个模板来组织提示语,模型就能够有效识别并遵循你的指令。

prompt = "详细介绍下你自己。"
messages = [{"role": "system", "content": "You are a helpful assistant."},{"role": "user", "content": prompt}
]
# 将消息应用到标准模板,使结构一致
input_text = tokenizer.apply_chat_template(messages,tokenize=False,add_generation_prompt=True,
)
print(f"{input_text}") 

上面messages中的json格式是面向应用侧的提示语模板,经过模型的标准化处理后变为:

    <|im_start|>systemYou are a helpful assistant.<|im_end|><|im_start|>user详细介绍下你自己。<|im_end|><|im_start|>assistant

可以看到,应用聊天模板后的变化,就是把json格式的分隔符替换成了一套特殊的<|im_start|>和<|im_end|>标记,用于标记一段完整文本的开始和结束。

为什么不使用原先的json分隔符,而要单独定义一套标记作为分隔符呢?

如果拿json中的这些符号{}[]"去标记文本的开始和结束,就会和json格式中的这些符号的原本定义冲突,相当于一符多义。而<|im_start|>和<|im_end|>这类特殊标记因为不常用,所以区分度更好,更适合用来标记文本的开始和结束。

2.3 提示语序列化

序列化就是用分词器把人类输入的文本转换为语言模型可以处理的数字。

tokenizer内置的__call__方法就可以实现序列化,它支持的是对多个文本进行批量处理,因此传参使用数组。

model_inputs = tokenizer([input_text], return_tensors="pt").to(device)
print(f"{model_inputs}")
{'input_ids': tensor([[151644,   8948,    198,   2610,    525,    264,  10950,  17847,     13,151645,    198, 151644,    872,    198, 113511,  16872, 107828,   1773,151645,    198, 151644,  77091,    198]], device='cuda:4'), 'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]],device='cuda:4')}
  • return_tensors=‘pt’表示以pytorch张量的形式返回,并通过’.to(device)'来将数据移动到指定的GPU设备上,便于后续计算。

  • attention_mask主要用于批量预测场景,用于计算注意力时屏蔽填充token。

序列化的过程可以简单理解为:先将文本切分成一个词列表(即token序列),再通过词表映射将词转换为数字ID,这个数字ID可以理解成词在词表中的唯一编号(如下图所示)。

在这里插入图片描述

2.4 模型推理

model.generate函数用于对一个输入的序列进行推理,并输出预测的新序列。

response_ids = model.generate(model_inputs.input_ids,max_new_tokens=512,
)
print(f"response_ids: {response_ids}")
response_ids: tensor([[151644,   8948,    198,   2610,    525,    264,  10950,  17847,     13,151645,    198, 151644,    872,    198, 113511,  16872, 107828,   1773,151645,    198, 151644,  77091,    198, 104198, 101919, 102661,  99718,104197, 100176, 102064, 104949,   3837,  35946,  99882,  31935,  64559,99320,  56007,   1773, 151645]], device='cuda:4')

推理的结果是一串数字,表示模型预测出的token ID序列,我们需要将其转换为文本,才能供人类阅读。

2.5 反序列化为文本

反序列化操作与上面提示语序列化正好相反,目的是将计算机认识的token ID转换为人类能理解的文本。

上面的推理结果中,前半部分151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 151645, 198, 151644, 872, 198, 113511, 16872, 107828, 1773, 151645, 198, 151644, 77091, 198 为原始的输入序列,后面才是真正的输出序列。因此,在反序列化之前,需要先通过切片操作得到真正的输出序列。

response_ids = [output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, response_ids)
]
print(f"response_ids: {response_ids}")
response_text = tokenizer.batch_decode(response_ids, skip_special_tokens=True)[0]
print(f"{response_text}")
response_ids: [tensor([104198, 101919, 102661,  99718, 104197, 100176, 102064, 104949,   3837,35946,  99882,  31935,  64559,  99320,  56007,   1773, 151645],device='cuda:4')]
我是来自阿里云的大规模语言模型,我叫通义千问。

模型的输出结果中会包含特殊token(例如:<|im_end|>),需要通过skip_special_tokens=False过滤,只保留真正有效的文本。

上面是模型生成文本的一个过程,从结果看起来模型一次性就生成了整个序列,但真的是这样吗?

3. model.generate探究

实际上model.generate并不是pytorch的标准API,而是为了方便调用而高度封装过的,model.forward才是pytorch模型标准调用方法。

PyTorch 是Facebook开发一个深度学习框架,它相比于Tensorflow来说更加容易学习和上手,其发展速度已经超过了Tensorflow。

3.1 手动模拟

我们尝试还原一下model.generate方法的运行过程。

先定义最终的序列,初始值为输入序列。

generate_ids = model_inputs.input_ids

定义一个函数generate_next_token,作用是预测一个token。

def generate_next_token(model, generate_ids, temperature=0.7, debug=False):print("input_ids:", generate_ids) if debug else Nonelogits = model.forward(generate_ids).logitsprint("logits: ", logits) if debug else Noneif temperature > 0:probs = torch.softmax(logits[:, -1] / temperature, dim = -1)print("probs: ", len(probs[0]), probs) if debug else Nonenext_token = torch.multinomial(probs[-1], num_samples=1)  # 按照概率采样得到下一个tokenelse:next_token = torch.argmax(logits[:, -1], dim=-1)print("next_id: ", next_token, ", token: ", tokenizer.decode(next_token)) if debug else Nonereturn next_token.reshape(-1, 1)
  • model.forward是为了执行矩阵运算,得到未归一化的概率。
  • torch.softmax是为了得到归一化的概率(所有可能值的概率之和为1),logits[:, -1]表示取三维张量的第二维最后一列,即最后一个token的张量,用于预测下一个token。
  • temperature在这里起到的作用是:温度越高(大于1),概率分布越平滑,温度越低(小于1),概率分布越尖锐。
  • torch.multinomial:按照概率大小进行随机加权采样,num_samples=1表示只采样一个值作为结果返回。
next_token = generate_next_token(model, generate_ids, debug=True)
generate_ids = torch.cat((generate_ids, next_token), dim=1)
tokenizer.batch_decode(generate_ids[:, len(model_inputs.input_ids[0]):], skip_special_tokens=True)[0] 

上面这段代码每运行一次,就会生成一个新的next_token,并把新生成的next_token追加到generate_ids的末尾,下一次运行时会作为新的输入序列。

    input_ids: tensor([[151644,   8948,    198,   2610,    525,    264,  10950,  17847,     13,151645,    198, 151644,    872,    198, 113511,  16872, 107828,   1773,151645,    198, 151644,  77091,    198, 104198, 101919]],device='cuda:4')logits:  tensor([[[ 2.4219,  2.6250,  2.9531,  ..., -3.3281, -3.3281, -3.3281],[ 0.5938,  1.8359,  3.6250,  ..., -4.5000, -4.5000, -4.5000],[ 8.0625,  4.5938, 10.8125,  ..., -3.4531, -3.4531, -3.4531],...,[ 6.0312, 10.1250,  5.4062,  ..., -3.4062, -3.3906, -3.3906],[ 2.5781,  6.5000,  3.3906,  ..., -5.1562, -5.1562, -5.1562],[ 2.0781,  6.0938,  3.8438,  ..., -5.9062, -5.8750, -5.8750]]],device='cuda:4', grad_fn=<ToCopyBackward0>)probs:  151936 tensor([[5.4616e-10, 1.6931e-07, 6.8037e-09,  ..., 6.0764e-15, 6.3538e-15,6.3538e-15]], device='cuda:4', grad_fn=<SoftmaxBackward0>)next_id:  tensor([102661], device='cuda:4') , token:  阿里'我是来自阿里'

通过打印的中间结果可以理解next_token的生成过程。

  • input_ids:是我们的输入序列,它会不断更新,上面示例中最后的104198, 101919就是新生成后追加到末尾的两个token我是来自
  • logits: 是模型对下一个token_id的预测,有151396(词表长度)种可能,元素值表示每个token的可能值大小。
  • probs:logtis的概率化表示,所有可能性总和为1。
  • next_id: 根据概率大小采样得到的下一个token。

torch.cat是用于将下一个token追加到序列的末尾,作为新的上下文重新输入给模型。

通过generate_next_token产生一个新token后,需要将这个token追加到输入序列的结尾,继续预测下一个token,等预测出来后,还是继续追加到输入序列的结尾……如此循环,来逐步生成一个完整的序列。

3.2 自动化

将上面一次次手动运行的过程,写成一个循环,一次性输出整个序列。

import time
max_new_tokens = 128
end_token_id = 151645
generate_ids = model_inputs.input_idsfor _ in range(max_new_tokens):next_token = generate_next_token(model, generate_ids, debug=False)generate_ids = torch.cat((generate_ids, next_token), dim=1)if next_token.item() == end_token_id:breakprint(tokenizer.decode(next_token[0], skip_special_tokens=False), end='')time.sleep(0.1)

运行上述代码,就能输出完整的序列,看起来就和model.generate函数的效果是一样。

    作为一个人工智能,我是一个计算机程序,没有情感、意识或身体,因此我无法有自己的思想、感情或身体。我被设计为帮助用户解答问题、提供信息和完成任务。我每天都会不断学习和更新,以便更好地服务用户。

像上面这样,每生成一个token,都追加到上下文的末尾,作为新的输入序列来预测下一个token这种生成方式,就叫自回归解码。自回归的过程类似下图所示:

在这里插入图片描述

采用自回归解码的模型被称为因果语言模型,这类模型预测下一个token只依赖前面的token,也是上面AutoModelForCausalLM这个名字的由来。

4. model内部探究

4.1 主体结构

上面的推理过程是将模型对象作为一个整体来调用的,下面来看下模型的内部结构。

print(model)
Qwen2ForCausalLM((model): Qwen2Model((embed_tokens): Embedding(151936, 896)(layers): ModuleList((0-23): 24 x Qwen2DecoderLayer((self_attn): Qwen2SdpaAttention((q_proj): Linear(in_features=896, out_features=896, bias=True)(k_proj): Linear(in_features=896, out_features=128, bias=True)(v_proj): Linear(in_features=896, out_features=128, bias=True)(o_proj): Linear(in_features=896, out_features=896, bias=False)(rotary_emb): Qwen2RotaryEmbedding())(mlp): Qwen2MLP((gate_proj): Linear(in_features=896, out_features=4864, bias=False)(up_proj): Linear(in_features=896, out_features=4864, bias=False)(down_proj): Linear(in_features=4864, out_features=896, bias=False)(act_fn): SiLU())(input_layernorm): Qwen2RMSNorm()(post_attention_layernorm): Qwen2RMSNorm()))(norm): Qwen2RMSNorm())(lm_head): Linear(in_features=896, out_features=151936, bias=False)
)
  • embed_tokens: 嵌入层,每个token嵌入到896维的向量空间。
  • self_attn:自注意力层,用以捕获每个token在上下文中的含义,由q、k、v、o 四个线性层组成。
  • mlp: 前馈神经网络层,通过维度缩放来捕获更丰富的信息和不同层次的抽象特征。
  • layers: 多层解码器堆叠而成,每层解码器都由一个attention和一个mlp组成。
4.2 模型参数

通过上面的模型结构可以估算模型参数量。

Attention+MLP参数量估算:

  • W(q)参数量 = 896 × 896 + 896 = 803712
  • W(k)参数量 = 896 × 128 + 128 = 115584
  • W(v)参数量 = 896 × 128 + 128 = 115584
  • W(o)参数量 = 896 × 896 = 802816
  • W(gate)参数量 = 896 × 4864 = 4358144
  • W(up)参数量 = 896 × 4864 = 4358144
  • W(down)参数量 = 4864 × 896 = 4358144

DecoderLayer参数量估算

  • W(DecoderLayer) = W(q) + W(k) + W(v) + W(o) + W(gate) + W(up) + W(down) = 14911928
  • W(lm_head)参数量 = 896 × 151936 = 136134656

每生成一个token,都要进行24层的解码器运算,每层解码器都要有14911928个参数进行运算,所以语言模型的推理功能对GPU运算的消耗是巨大的。

模型总参数量估算:

  • W(model) = W(Decoderlayer) * 24 + W(lm_head) = 357886272 + 136134656 = 494020928

通过上面的计算,模型的总参数量大概为494M,即4.94亿参数量,与模型上标注的0.5B(5亿)参数量非常接近。

通过model.state_dict()可以获得整个模型的状态,即所有的权重参数。并能进一步查看每一层的具体参数值,如下示例。

params = model.state_dict()
params['model.layers.0.self_attn.q_proj.weight']
    tensor([[-0.0024, -0.0156,  0.0229,  ..., -0.0079, -0.0128,  0.0023],[ 0.0200,  0.0100, -0.0208,  ...,  0.0234,  0.0090, -0.0013],[-0.0151, -0.0035,  0.0219,  ..., -0.0092, -0.0283,  0.0043],...,[-0.0415, -0.0522, -0.0233,  ...,  0.0427, -0.0374,  0.0063],[-0.0398, -0.0284, -0.0083,  ...,  0.0378, -0.0408, -0.0204],[ 0.0491, -0.0129,  0.0015,  ..., -0.0439,  0.0197,  0.0352]],device='cuda:4', dtype=torch.bfloat16)

小结:本文以使用模型作为起点,由前台到后台,由外及里,逐层解开模型生成文本的整个过程,目的是让学习者快速理解生成式语言模型的生成文本原理。在这个过程中,我们详细讨论了文本生成的细节,并提供了可操作的代码块,以方便感兴趣的同学自己动手尝试。

相关阅读

  • 复现LLM:带你从零训练tokenizer

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

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

相关文章

【NLP自然语言处理】深入探索Self-Attention:自注意力机制详解

目录 &#x1f354; Self-attention的特点 &#x1f354; Self-attention中的归一化概述 &#x1f354; softmax的梯度变化 3.1 softmax函数的输入分布是如何影响输出的 3.2 softmax函数在反向传播的过程中是如何梯度求导的 3.3 softmax函数出现梯度消失现象的原因 &…

Matlab 火焰识别技术

课题介绍 森林承担着为人类提供氧气以及回收二氧化碳等废弃气体的作用&#xff0c;森林保护显得尤其重要。但是每年由于火灾引起的事故不计其数&#xff0c;造成重大的损失。如果有一款监测软件&#xff0c;从硬件处获得的图像中监测是否有火焰&#xff0c;从而报警&#xff0…

Linux多线程(个人笔记)

Linux多线程 1.Linux线程概念1.1线程的优点1.2线程的缺点 2.Linux线程VS进程3.Linux线程控制3.1创建线程3.2线程tid及进程地址空间布局3.3线程终止3.4线程等待 4.分离线程5.线程互斥5.1互斥锁mutex5.2互斥锁接口5.3互斥锁实现原理5.4可重入VS线程安全 6.线程同步6.1条件变量6.2…

【EdgeBox-8120AI-TX2】Ubuntu18.04 + ROS_ Melodic + HP60C上手体验

简介&#xff1a;介绍安思疆 HP60C 深度相机在8120AI-TX2控制器上&#xff0c;TX2核心模块环境&#xff08;Ubuntu18.04&#xff09;下测试ROS驱动&#xff0c;打开摄像头图像和查看深度图和点云图&#xff0c;本文的前提条件是你的TX2里已经安装了ROS版本&#xff1a;Melodic。…

DataWind将字符串数组拆出多行的方法

摘要&#xff1a; 可视化建模中先将字符串split为array再用explode(array)即可 可视化建模 进入“可视化建模”页面 1.1 新建任务 如果团队内没有可视化建模任务。请点击“新建任务”&#xff0c;输入名称并确定。 1.2 建立数据连接 在左边栏中选择“数据连接”&#xff0c…

【大数据学习 | kafka】简述kafka的消费者consumer

1. 消费者的结构 能够在kafka中拉取数据进行消费的组件或者程序都叫做消费者。 这里面要涉及到一个动作叫做拉取。 首先我们要知道kafka这个消息队列主要的功能就是起到缓冲的作用&#xff0c;比如flume采集数据然后交给spark或者flink进行计算分析&#xff0c;但是flume采用的…

CCF ChinaOSC |「开源科学计算与系统建模openSCS专题分论坛」11月9日与您相约深圳

2024年11月9日至10日&#xff0c;以“湾区聚力 开源启智”为主题的2024年中国计算机学会中国开源大会&#xff08;CCF ChinaOSC&#xff09;将在深圳召开。大会将汇聚国内外学术界、顶尖科技企业、科研机构及开源社区的精英力量&#xff0c;共同探索人工智能技术和人类智慧的无…

人工智能——小白学习指南

知孤云出岫 目录 1. **智能评测系统**2. **个性化学习路径推荐**3. **虚拟学习助手**4. **学习行为分析**5. **数据驱动的教学决策**6. **自动化课程推荐**7. **数据隐私与安全保护** 人工智能知识点的总结和学习路线&#xff0c;以数据表格形式呈现&#xff0c;并附带在教育行…

现代Web开发:React Hooks深入解析

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 现代Web开发&#xff1a;React Hooks深入解析 现代Web开发&#xff1a;React Hooks深入解析 现代Web开发&#xff1a;React Hook…

RTC精度及校准

RTC精度偏差&#xff1a; RTC的基准时间和精度与石英晶体的频率相关&#xff0c;晶体的谐振频率取决于温度&#xff0c;因此RTC性能与温度相关&#xff0c;晶体的频率偏差是晶体正常频率的温度反转函数。 一、硬件方面&#xff1a; 1.使用高精度振荡器的RTC模块&#xff1b; …

了解bootstrap改造asp.net core MVC的样式模板

我们都知道&#xff0c;在使用默认的asp.net core MVC模板建立项目的时候&#xff0c;里面的样式是已经事先被写好了的。一般来说都在css目录下的site.css和bootstrap.css及下面的bootstrap.min.css中。我们打开bootstrap这些样式文件&#xff0c;里面有大量的样式类的定义&…

流类库与输入输出

来源&#xff1a;《C语言程序设计》 像C语言一样&#xff0c;C语言也没有输入输出语句。 但C标准库中有一个面向对象的输入输出软件包&#xff0c;即I/O流类库。 流是I/O流类的中心概念。 ------ I/O流类库是C语言中I/O函数在面向对象的程序设计方法中的一个替换产品。 -…

cocos creator 3.8.3物理组件分组的坑

坑&#xff0c;坑的不行的大坑 group用的二进制的左移获取十进制的数值 目前是这样判断的&#xff0c;也不知道对不对&#xff0c;什么get、set Group没找到

基于MFC实现的赛车游戏

一、问题描述 游戏背景为一环形车道图&#xff0c;选择菜单选项“开始游戏”则可开始游戏。游戏的任务是使用键盘上的方向键操纵赛道上的蓝色赛车追赶红色赛车&#xff0c;红色赛车沿车道顺时针行驶&#xff0c;出发点和终点均位于车道左上方。任一赛车先达到终点则比赛结束。…

RHCE的学习(12)

第九章 Ubuntu 什么是Ubuntu 概述 Ubuntu&#xff08;乌班图&#xff09;属于Debian系列&#xff0c;Debian是社区类Linux的典范&#xff0c;是迄今为止最遵循GNU规范的Linux系统。 Debian最早由Ian Murdock于1993年创建&#xff0c;分为三个版本分支&#xff08;branch&…

【案例】故障雪花屏

开发平台&#xff1a;Unity 6.0 开发工具&#xff1a;Shader Graph 参考视频&#xff1a;【U2D Shader Graph】❄️雪❄️花❄️屏❄️   一、效果图 二、Shader Graph 路线图 三、案例分析 核心思路&#xff1a;雪花屏幕效果 &#xff08;混合&#xff09; 原图像 最终图像…

有什么办法换网络ip动态

在数字化时代&#xff0c;网络已成为我们生活、工作不可或缺的一部分。然而&#xff0c;随着网络应用的日益广泛&#xff0c;IP地址作为设备在网络中的唯一标识&#xff0c;其重要性不言而喻。动态换IP&#xff0c;作为一种灵活且高效的网络技术&#xff0c;正逐渐受到越来越多…

Spring Boot中集成MyBatis操作数据库详细教程

目录 前言1. 项目依赖配置1.1 引入MyBatis和数据库驱动依赖1.2 数据源配置 2. 创建数据库映射实体类3. 创建Mapper层接口4. 创建Service层4.1 定义Service接口4.2 实现Service接口 5. 创建Controller层6. 运行和测试项目6.1 启动项目6.2 测试接口 7. 总结 前言 在Java开发中&a…

【大语言模型】ACL2024论文-07 BitDistiller: 释放亚4比特大型语言模型的潜力通过自蒸馏

【大语言模型】ACL2024论文-07 BitDistiller: 释放亚4比特大型语言模型的潜力通过自蒸馏 目录 文章目录 【大语言模型】ACL2024论文-07 BitDistiller: 释放亚4比特大型语言模型的潜力通过自蒸馏目录摘要研究背景问题与挑战如何解决创新点算法模型实验效果代码推荐阅读指数&…

鸿蒙next打包流程

目录 下载团结引擎 添加开源鸿蒙打包支持 打包报错 路径问题 安装DevEcoStudio 可以在DevEcoStudio进行打包hap和app 包结构 没法直接用previewer运行 真机运行和测试需要配置签名,DevEcoStudio可以自动配置, 模拟器安装hap提示报错 安装成功,但无法打开 团结1.3版本新增工具…