从零开始实现大语言模型(十三):预训练大语言模型GPTModel

1. 前言

使用梯度下降算法通过下一个token预测任务预训练大语言模型GPTModel,前向传播流程每次会输入一个batch的长度均为context_len的训练样本,执行 batch_size × context_len \text{batch\_size}\times\text{context\_len} batch_size×context_len次下一个token预测任务,共预测输出 batch_size × context_len \text{batch\_size}\times\text{context\_len} batch_size×context_len个tokens。后向传播流程首先会使用交叉熵(Cross Entropy)损失函数计算大语言模型GPTModel的预测输出与训练样本标签之间的损失(loss),再通过后向传播算法计算大语言模型参数梯度,最后使用梯度下降算法更新大语言模型的参数。

本文使用交叉熵损失函数计算生成大语言模型GPTModel的预测输出与训练样本标签之间的loss,介绍大语言模型的预训练流程,并实现预训练大语言模型的函数pretrain_model

2. 损失函数Cross Entropy

交叉熵损失函数可以度量大语言模型的预测输出与训练样本标签之间的差异。损失函数计算生成的loss值越大,表明大语言模型的预测输出与训练样本标签之间的差异越大,loss值越小,表明大语言模型的预测输出与训练样本标签之间的差异越小。

对输入文本做tokenization,将输入文本转换成包含context_len个token ID的列表,并输入大语言模型GPTModel,可以得到context_len个维度为vocabulary_size的logits向量,第 i i i个logits向量是大语言模型根据前 i i i个token预测生成的下一个token的概率分数向量,logits向量中的第 k k k个概率分数值越大,表明大语言模型预测生成的下一个token的ID为 k k k的概率越高。

使用softmax函数将大语言模型预测生成的logits向量归一化,得到大语言模型预测生成的下一个token的概率分布,概率分布中对应样本标签位置的概率值表示大语言模型预测输出的token为相应训练样本标签的概率。对应样本标签位置的概率值越接近1,表明大语言模型预测输出的token为相应训练样本标签的概率越高,大语言模型的预测输出与训练样本标签之间的差异越小。

图一

使用梯度下降算法预训练大语言模型GPTModel的前向传播流程中,大语言模型每次会预测生成 batch_size × context_len \text{batch\_size}\times\text{context\_len} batch_size×context_len个下一个token的概率分布。如下图所示,交叉熵损失函数会分别获取 batch_size × context_len \text{batch\_size}\times\text{context\_len} batch_size×context_len个概率分布中对应样本标签位置的概率值,使用对数函数计算这些概率值的对数,并计算所有对数值的均值,最后将对数均值的相反数作为大语言模型GPTModel的预测输出与训练样本标签之间的损失loss。

图二

如下面的代码所示,使用torch.tensor函数创建训练样本inputs及训练样本标签targets,将训练样本inputs输入大语言模型gpt2_small,并使用softmax函数将大语言模型的输出张量logits归一化,得到 2 × 3 2\times3 2×3个下一个token的概率分布,其中每个概率分布的维度均等于词汇表的大小50257。分别获取 2 × 3 2\times3 2×3个下一个token的概率分布中对应样本标签位置的概率值,使用torch.log函数计算这些概率值的对数,并计算所有对数值均值的相反数,可以得到大语言模型gpt2_small的预测输出与样本标签targets之间的交叉熵损失:

import torch
# from [从零开始实现大语言模型(七):多头注意力机制] import MultiHeadAttention
# from [从零开始实现大语言模型(八):Layer Normalization] import LayerNorm
# from [从零开始实现大语言模型(九):前馈神经网络与GELU激活函数] import GELU, FeedForward
# from [从零开始实现大语言模型(十一):构建大语言模型GPTModel] import TransformerBlock, GPTModeltorch.manual_seed(123)embedding_dim = 768
num_layers = 12
num_heads = 12
context_len = 1024
vocabulary_size = 50257
dropout = 0.1
qkv_bias = Falsegpt2_small = GPTModel(embedding_dim=embedding_dim,num_layers=num_layers,num_heads=num_heads,context_len=context_len,vocabulary_size=vocabulary_size,dropout=dropout,qkv_bias=qkv_bias
)inputs = torch.tensor([[16833, 3626, 6100],  # [["every effort moves"],[40, 1107, 588]]      # ["I really like"]]
)targets = torch.tensor([[3626, 6100, 345],  # [[" effort moves you"],[588, 428, 11311]]  # [" really like chocolate"]]
)with torch.no_grad():logits = gpt2_small(inputs)
probas = torch.softmax(logits, dim=-1)target_probas_1 = probas[0, [0, 1, 2], targets[0]]
target_probas_2 = probas[1, [0, 1, 2], targets[1]]log_probas = torch.log(torch.cat((target_probas_1, target_probas_2)))
avg_log_probas = torch.mean(log_probas)
neg_avg_log_probas = avg_log_probas * -1print("probas shape:", probas.shape)
print("target_probas_1:", target_probas_1)
print("target_probas_2:", target_probas_2)
print("log_probas:", log_probas)
print("avg_log_probas:", avg_log_probas)
print("cross entropy loss:", neg_avg_log_probas)

执行上面代码,打印结果如下:

probas shape: torch.Size([2, 3, 50257])
target_probas_1: tensor([2.6369e-05, 1.5997e-05, 1.6926e-05])
target_probas_2: tensor([1.5638e-05, 8.9422e-06, 1.7967e-05])
log_probas: tensor([-10.5433, -11.0431, -10.9867, -11.0658, -11.6247, -10.9270])
avg_log_probas: tensor(-11.0318)
cross entropy loss: tensor(11.0318)

可以直接使用PyTorch内置的cross_entropy函数执行上述计算流程,得到大语言模型gpt2_small的预测输出logits与样本标签targets之间的交叉熵损失:

loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), targets.flatten())
print(loss)

执行上面代码,打印结果如下:

tensor(11.0318)

根据打印结果可知,上述6个步骤计算得到的损失值与PyTorch内置的cross_entropy函数计算得到的交叉熵损失完全相同。交叉熵损失本质上就是大语言模型预测生成的 batch_size × context_len \text{batch\_size}\times\text{context\_len} batch_size×context_len个下一个token的概率分布中对应样本标签位置概率值的对数均值的相反数。

3. 大语言模型预训练流程

预训练大语言模型的流程与训练普通神经网络模型本质上并没有任何不同。如下图所示,预训练大语言模型可以把整个训练数据集扫几个epoch,每个epoch会把整个训练数据集扫一遍,每次会使用训练数据集中一个batch的训练样本训练一次大语言模型。前向传播流程会将一个batch的训练样本输入大语言模型,得到大语言模型的预测输出logits。后向传播流程首先会使用交叉熵损失函数计算大语言模型的预测输出logits与训练样本标签targets之间的损失loss,再通过后向传播算法计算大语言模型参数梯度,最后使用梯度下降算法更新大语言模型的参数。

图三

可以使用如下代码定义计算一个batch样本数据交叉熵损失的函数calc_loss_batch,以及计算整个数据集上所有样本数据交叉熵损失的函数calc_loss_loader。函数calc_loss_batch将一个batch的样本数据输入大语言模型,得到大语言模型的预测输出logits,并使用torch.nn.functional.cross_entropy函数计算大语言模型的预测输出logits与样本标签targets之间的损失loss。函数calc_loss_loader每次取数据集中一个batch的样本数据,使用calc_loss_batch函数计算该batch样本数据的交叉熵损失,并返回数据集上所有样本数据损失loss的均值:

def calc_loss_batch(input_batch, target_batch, model, device):input_batch = input_batch.to(device)target_batch = target_batch.to(device)logits = model(input_batch)loss = torch.nn.functional.cross_entropy(logits.flatten(0, 1), target_batch.flatten())return lossdef calc_loss_loader(data_loader, model, device, num_batches=None):model.eval()total_loss = 0.0if num_batches is None:num_batches = len(data_loader)else:num_batches = min(num_batches, len(data_loader))with torch.no_grad():for i, (input_batch, target_batch) in enumerate(data_loader):if i < num_batches:loss = calc_loss_batch(input_batch, target_batch, model, device)total_loss += loss.item()else:breakreturn total_loss / num_batches

实现预训练大语言模型的函数pretrain_model,可以使用for循环将整个训练数据集扫num_epochs遍,并在每次训练大语言模型的循环中,首先使用optimizer.zero_grad函数将大语言模型所有参数的梯度置为0,然后使用函数calc_loss_batch计算一个batch训练样本的交叉熵损失loss。使用loss.backward函数可以执行后向传播流程,计算大语言模型所有参数的梯度,并通过optimizer.step函数使用梯度下降算法更新大语言模型参数。具体代码如下所示:

# from [从零开始实现大语言模型(十二):文本生成策略] import generate_textdef pretrain_model(model, optimizer, train_loader, num_epochs, device,eval_freq, eval_iter, tokenizer, start_context,save_freq, checkpoint_dir, checkpoint=None, val_loader=None
):if not os.path.exists(checkpoint_dir):os.makedirs(checkpoint_dir, exist_ok=True)if checkpoint is not None:model_checkpoint_path = os.path.join(checkpoint_dir, f"model_{checkpoint:06d}.pth")optimizer_checkpoint_path = os.path.join(checkpoint_dir, f"optimizer_{checkpoint:06d}.pth")model.load_state_dict(torch.load(model_checkpoint_path))optimizer.load_state_dict(torch.load(optimizer_checkpoint_path))else:checkpoint = -1train_losses, val_losses, track_tokens_seen = [], [], []tokens_seen, global_step = 0, -1for epoch in range(num_epochs):model.train()for i, (input_batch, target_batch) in enumerate(train_loader):if global_step % eval_freq == 0:model.train()optimizer.zero_grad()loss = calc_loss_batch(input_batch, target_batch, model, device)loss.backward()optimizer.step()tokens_seen += input_batch.numel()global_step += 1print(f"Epoch {epoch + 1} (Batch {i:06d}): Train loss {loss.item():.3f}")checkpoint, train_loss, val_loss = val_and_save(model, optimizer, train_loader, val_loader, epoch, global_step, eval_freq,eval_iter, start_context, tokenizer, save_freq, checkpoint_dir, checkpoint, device)if train_loss is not None:train_losses.append(train_loss)val_losses.append(val_loss)track_tokens_seen.append(tokens_seen)checkpoint, _, _ = val_and_save(model, optimizer, train_loader, val_loader, epoch, global_step, 1,eval_iter, start_context, tokenizer, 1, checkpoint_dir, checkpoint, device)print(f"Epoch {epoch + 1} finished, checkpoint: {checkpoint:06d}")return train_losses, val_losses, track_tokens_seendef val_and_save(model, optimizer, train_loader, val_loader, epoch, global_step, eval_freq,eval_iter, start_context, tokenizer, save_freq, checkpoint_dir, checkpoint, device
):train_loss, val_loss = None, Noneif global_step % eval_freq == 0:if val_loader is not None:train_loss = calc_loss_loader(train_loader, model, device, eval_iter)val_loss = calc_loss_loader(val_loader, model, device, eval_iter)print(f"Epoch {epoch + 1} (Step {global_step:06d}): Train loss {train_loss:.3f}, Val loss {val_loss:.3f}")generated_sample_text = generate_text(model, start_context, max_new_tokens=50, tokenizer=tokenizer,context_size=model.pos_emb.weight.shape[0], top_k=1, compact_format=True)print(f"Generated Sample Text: {generated_sample_text}")print("=====================================================================")if global_step % save_freq == 0:checkpoint += 1model_checkpoint_path = os.path.join(checkpoint_dir, f"model_{checkpoint:06d}.pth")optimizer_checkpoint_path = os.path.join(checkpoint_dir, f"optimizer_{checkpoint:06d}.pth")torch.save(model.state_dict(), model_checkpoint_path)torch.save(optimizer.state_dict(), optimizer_checkpoint_path)return checkpoint, train_loss, val_loss

PyTorch中神经网络模型的state_dict是一个字典对象,字典中的key为神经网络模型中参数的名称,value为相应的参数。使用.state_dict函数可以一次性获取神经网络模型中的所有参数,并通过torch.save函数将所有参数保存为一个checkpoint。torch.load函数可以读取指定checkpoint,通过.load_state_dict函数可以将神经网络模型中的参数修改为checkpoint中的记录值。

所有具有自适应能力的优化器(如AdamW可以根据历史梯度信息动态调整学习率)都需要记录每个神经网络参数的历史梯度等信息,同样可以使用.state_dict一次性获取优化器中的所有数据记录,以及通过.load_state_dict函数从指定checkpoint中还原这些记录数据。

如下面的代码所示,使用从零开始实现大语言模型(二):文本数据处理中构建的Dataset创建训练集train_dataset及验证集val_dataset,并通过PyTorch内置的torch.utils.data.DataLoader类创建训练集及验证集对应的DataLoader。使用torch.optim.AdamW实例化训练大语言模型的优化器optimizer,最后使用函数pretrain_model预训练大语言模型gpt2_small

import os
import random
import tiktoken
from torch.utils.data import Dataset, DataLoader# from [从零开始实现大语言模型(二):文本数据处理] import LLMDatasettrain_data_path = "train_data"
val_data_path = "val_data"
vocabulary = "gpt2"
special_token_id = 50256
context_len = 1024
stride = 1024
batch_size = 2num_epochs = 10
eval_freq = 5
eval_iter = 1
save_freq = 5
checkpoint_dir = "checkpoint"
start_context = "萧炎,斗之力,三段"
tokenizer = tiktoken.encoding_for_model(vocabulary)
device = torch.device("cpu")
gpt2_small.to(device)
optimizer = torch.optim.AdamW(gpt2_small.parameters(), lr=0.0006, weight_decay=0.1)train_dataset = LLMDataset(train_data_path, vocabulary, special_token_id, context_len, stride)
val_dataset = LLMDataset(val_data_path, vocabulary, special_token_id, context_len, stride)
train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True, drop_last=True)
val_loader = DataLoader(dataset=val_dataset, batch_size=batch_size, shuffle=False, drop_last=False)
print(f"train_loader len: {len(train_loader)}")train_losses, val_losses, tokens_seen = pretrain_model(gpt2_small, optimizer, train_loader, num_epochs, device,eval_freq, eval_iter, tokenizer, start_context,save_freq, checkpoint_dir, val_loader=val_loader
)

执行上面代码,打印结果如下:

train_loader len: 7
Epoch 1 (Batch 000000): Train loss 11.034
Epoch 1 (Step 000000): Train loss 9.827, Val loss 9.784
Generated Sample Text: 萧炎,斗之力,三段 Knowledge�缌�缌缌�703 clashes�缌 longest,,缌,���,缌缌�
=====================================================================
Epoch 1 (Batch 000001): Train loss 9.940
Epoch 1 (Batch 000002): Train loss 8.811
Epoch 1 (Batch 000003): Train loss 7.954
Epoch 1 (Batch 000004): Train loss 7.286
Epoch 1 (Batch 000005): Train loss 6.629
Epoch 1 (Step 000005): Train loss 5.980, Val loss 6.003
Generated Sample Text: 萧炎,斗之力,三段,,,,,,,,,,,,,,,,�
=====================================================================
Epoch 1 (Batch 000006): Train loss 6.027
Epoch 1 (Step 000006): Train loss 5.390, Val loss 5.479
Generated Sample Text: 萧炎,斗之力,三段,,,�,,,��,,,,,�,�,,,
=====================================================================
Epoch 1 finished, checkpoint: 000002
Epoch 2 (Batch 000000): Train loss 5.401
Epoch 2 (Batch 000001): Train loss 5.028
Epoch 2 (Batch 000002): Train loss 4.788
Epoch 2 (Batch 000003): Train loss 4.616
Epoch 2 (Step 000010): Train loss 4.511, Val loss 4.526
Generated Sample Text: 萧炎,斗之力,三段,�,�,�,�,�,�,�,�,�,��,�,
=====================================================================[...]Epoch 9 (Step 000060): Train loss 2.561, Val loss 3.470
Generated Sample Text: 萧炎,斗之力,三段���是在脸�的�,�炣�殸废是萧炣也是曰�,萧�
=====================================================================
Epoch 9 (Batch 000005): Train loss 2.560
Epoch 9 (Batch 000006): Train loss 2.558
Epoch 9 (Step 000062): Train loss 2.456, Val loss 3.455
Generated Sample Text: 萧炎,斗之力,三段���,脸庿,炎�,萧炎萧�炎�萧�,萧�的�
=====================================================================
Epoch 9 finished, checkpoint: 000021
Epoch 10 (Batch 000000): Train loss 2.525
Epoch 10 (Batch 000001): Train loss 2.388
Epoch 10 (Batch 000002): Train loss 2.663
Epoch 10 (Step 000065): Train loss 2.270, Val loss 3.468
Generated Sample Text: 萧炎,斗之力,三段��技萧�的萧炣也�,萧�讵��更中着曰萧�着�
=====================================================================
Epoch 10 (Batch 000003): Train loss 2.464
Epoch 10 (Batch 000004): Train loss 2.602
Epoch 10 (Batch 000005): Train loss 2.511
Epoch 10 (Batch 000006): Train loss 2.557
Epoch 10 (Step 000069): Train loss 2.117, Val loss 3.474
Generated Sample Text: 萧炎,斗之力,三段��,这的�法的萧�炼�萧�法,萧�级级父了�
=====================================================================
Epoch 10 finished, checkpoint: 000023

从上面的打印结果可知,使用梯度下降算法训练大语言模型gpt2_small,可以减小大语言模型的预测输出与样本标签之间的交叉熵损失,并显著提升大语言模型的文本生成能力。在训练刚开始时,将萧炎,斗之力,三段输入大语言模型gpt2_small,生成的是Knowledge�缌�缌缌�703 clashes�缌 longest,,缌,���,缌缌�或者,,,,,,,,,,,,,,,,�这样不包含任何有效信息的自然语言文本序列。在仅包含7个batch训练样本的数据集上训练10个epoch,大语言模型gpt2_small已经可以生成���,脸庿,炎�,萧炎萧�炎�萧�,萧�的�以及��,这的�法的萧�炼�萧�法,萧�级级父了�这样与训练数据集存在一定关联的自然语言文本了。

可以使用如下代码,分别绘制大语言模型gpt2_small在训练集及验证集上交叉熵损失的变化情况图像:

import matplotlib.pyplot as pltdef plot_losses(epochs_seen, tokens_seen, train_losses, val_losses):fig, ax1 = plt.subplots(figsize=(5, 3))ax1.plot(epochs_seen, train_losses, label="Training loss")ax1.plot(epochs_seen, val_losses, linestyle="-.", label="Validation loss")ax1.set_xlabel("Epochs")ax1.set_ylabel("Loss")ax1.legend(loc="upper right")ax2 = ax1.twiny()ax2.plot(tokens_seen, train_losses, alpha=0)ax2.set_xlabel("Tokens seen")fig.tight_layout()plt.show()epochs_tensor = torch.linspace(0, num_epochs, len(train_losses))
plot_losses(epochs_tensor, tokens_seen, train_losses, val_losses)

执行上面代码,生成交叉熵损失的变化情况图像如下:

图四

从上面的交叉熵损失变化情况图像可知,在训练刚开始时,训练集及验证集上的交叉熵损失都非常大。使用梯度下降算法训练大语言模型gpt2_small,可以减小大语言模型的预测输出与样本标签之间的交叉熵损失,使大语言模型的预测输出与样本标签之间的差异性更小。

随着训练的进行,训练集和验证集上交叉熵损失的差异会越来越大,训练集上的交叉熵损失值会比验证集小的越来越明显,表明大语言模型在训练数据集上的过拟合情况越来越严重。在工业界的预训练大语言模型实践中,并不会在一个很小的训练数据集上训练多个epoch,而是会在一个非常大的训练数据集上训练少数几个甚至只训练一个epoch,这种训练策略可以很大程度上解决预训练大语言模型时的过拟合问题。

4. 结束语

前向传播流程将一个batch的训练样本输入大语言模型,共预测输出 batch_size × context_len \text{batch\_size}\times\text{context\_len} batch_size×context_len个维度为vocabulary_size的logits向量。后向传播流程首先使用交叉熵损失函数计算大语言模型的预测输出与训练样本标签之间的损失loss,并通过后向传播算法计算大语言模型参数梯度,最后使用梯度下降算法更新大语言模型的参数。

预训练大语言模型就是不断从训练数据集中获取一个batch的训练样本,然后执行这个操作直至收敛的过程。

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

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

相关文章

JavaScript(最后一个元素的索引就是数组的长度减 1)array.length - 1

在不同的编程语言中&#xff0c;表示数组中最后一个元素的方法略有不同&#xff0c;但基本思路都是利用数组的长度或索引来实现。 以下是一些常见编程语言中获取数组最后一个元素的方法&#xff1a; 1. JavaScript: 使用 array.length - 1 索引: 这是最常见和传统的方法。Java…

RV1126+FFMPEG多路码流监控项目

一.项目介绍&#xff1a; 本项目采用的是易百纳RV1126开发板和CMOS摄像头&#xff0c;使用的推流框架是FFMPEG开源项目。这个项目的工作流程如下(如上图)&#xff1a;通过采集摄像头的VI模块&#xff0c;再通过硬件编码VENC模块进行H264/H265的编码压缩&#xff0c;并把压缩后的…

Python组合数据类型(一)

目录 一、数据类型 1、基本数据类型 2、组合数据类型 二、介绍两个函数 1、 isinstance函数 2、len函数 三、Python指针 1、指针 2、is运算符和的区别 3、列表的指针 四、函数参数的传递 1、例子一 2、例子二 五、字符串详解 1、转义字符 2、字符串的切片 3、字…

Doris vs ClickHouse 企业级实时分析引擎怎么选?

Apache Doris 与 ClickHouse 同作为OLAP领域的佼佼者&#xff0c;在企业级实时分析引擎该如何选择呢。本文将详细介绍 Doris 的优势&#xff0c;并通过直观对比展示两者的关键差异&#xff0c;同时分享一个企业成功用 Doris 替换 ClickHouse 的实践案例&#xff0c;帮助您做出明…

【ThreeJS Basics 09】Debug

文章目录 简介从 dat.GUI 到 lil-gui例子安装 lil-gui 并实例化不同类型的调整改变位置针对非属性的调整复选框颜色 功能/按钮调整几何形状文件夹调整 GUI宽度标题关闭文件夹隐藏按键切换 结论 简介 每一个创意项目的一个基本方面是能够轻松调整。开发人员和参与项目的其他参与…

Android Native 之 文件系统挂载

一、文件系统挂载流程概述 二、文件系统挂载流程细节 1、Init启动阶段 众所周知&#xff0c;init进程为android系统的第一个进程&#xff0c;也是native世界的开端&#xff0c;要想让整个android世界能够稳定的运行&#xff0c;文件系统的创建和初始化是必不可少的&#xff…

Chain of Draft: 借鉴人类草稿思维让大型语言模型更快地思考

这个研究探讨了大型语言模型&#xff08;LLMs&#xff09;在执行复杂推理任务时面临的计算资源消耗与响应延迟问题。研究特别聚焦于思维链&#xff08;Chain-of-Thought, CoT&#xff09;提示范式的效率局限性。CoT虽然有效&#xff0c;但在推理过程中需要生成冗长、详尽的逐步…

《A++ 敏捷开发》- 18 软件需求

需求并不是关于需求 (Requirements are not really about requirements) 大家去公共图书馆寄存物品&#xff0c;以前都是扫二维码开箱&#xff0c;有些图书馆升级了使用指纹识别。 “是否新方法比以前好&#xff1f;”我问年轻的开发人员。 “当然用指纹识别好。新技术&#x…

【智能体架构:Agent】LangChain智能体类型ReAct、Self-ASK的区别

1. 什么是智能体 将大语言模型作为一个推理引擎。给定一个任务&#xff0c; 智能体自动生成完成任务所需步骤&#xff0c; 执行相应动作&#xff08;例如选择并调用工具&#xff09;&#xff0c; 直到任务完成。 2. 先定义工具&#xff1a;Tools 可以是一个函数或三方 API也…

Vue进阶之Vue3源码解析(一)

Vue3源码解析 目录结构编译compiler-corepackage.jsonsrc/index.ts 入口文件src/compile.ts生成ASTsrc/parse.ts 代码转换src/transform.ts几种策略模式src/transforms/transformElement.tssrc/transforms/transformText.tssrc/transforms/transformExpression.ts 代码生成src/…

servlet tomcat

在spring-mvc demo程序运行到DispatcherServlet的mvc处理 一文中&#xff0c;我们实践了浏览器输入一个请求&#xff0c;然后到SpringMvc的DispatcherServlet处理的整个流程. 设计上这些都是tomcat servlet的处理 那么究竟这是怎么到DispatcherServlet处理的&#xff0c;本文将…

【我的待办(MyTodolists)-免费无内购的 IOS 应用】

我的待办&#xff08;MyTodolists&#xff09; 我的待办&#xff1a;智能任务管理助手应用说明主要功能为什么选择"我的待办"&#xff1f;隐私保障使用截图 我的待办&#xff1a;智能任务管理助手 应用说明 "我的待办"是一款智能化的任务管理应用&#x…

GCC RISCV 后端 -- C语言语法分析过程

在 GCC 编译一个 C 源代码时&#xff0c;先会通过宏处理&#xff0c;形成 一个叫转译单元&#xff08;translation_unit&#xff09;&#xff0c;接着进行语法分析&#xff0c;C 的语法分析入口是 static void c_parser_translation_unit(c_parser *parser); 接着就通过类似递…

Vim复制内容到系统剪切板

参考链接 【Vim】Vim 中将文件内容复制到系统剪切板的方法_vi 复制到系统剪贴板-CSDN博客 [转]vim如何复制到系统剪贴板 - biiigwang - 博客园 1. 确定Vim是否支持复制到系统剪切板 输入命令 vim --version | grep clipboard 如果是开头&#xff0c;说明支持系统剪切板&…

测试用大模型组词

已经把hanzi-writer的js的调用、hanzi-writer调用的数千个汉字的json文件&#xff0c;全都放在本地了。虽然用的办法还是比较笨的。我注意到 大模型也可以部署本地&#xff0c;虽然使用频率低的情况下不划算。 尝试直接通过html的javascript通过api key调用大语言模型&#x…

华为eNSP:配置单区域OSPF

一、什么是OSPF&#xff1f; OSPF&#xff08;Open Shortest Path First&#xff0c;开放最短路径优先&#xff09;是一种链路状态路由协议&#xff0c;属于内部网关协议&#xff08;IGP&#xff09;&#xff0c;主要用于在单一自治系统&#xff08;AS&#xff09;内部动态发现…

P62 线程

这篇文章我们来讲一下线程。截止到目前&#xff0c;我们的代码都是在单线程上运行的&#xff0c;现在看起来没有什么问题&#xff0c;但是目前所有的计算机几乎都不只有一个逻辑线程&#xff0c;所以如果我们一直使用单线程运行&#xff0c;这样的话效率会很低。尤其是如果我们…

Android AudioFlinger(五)—— 揭开AudioMixer面纱

前言&#xff1a; 在 Android 音频系统中&#xff0c;AudioMixer 是音频框架中一个关键的组件&#xff0c;用于处理多路音频流的混音操作。它主要存在于音频回放路径中&#xff0c;是 AudioFlinger 服务的一部分。 上一节我们讲threadloop的时候&#xff0c;提到了一个函数pr…

im即时聊天客服系统SaaS还是私有化部署:成本、安全与定制化的权衡策略

随着即时通讯技术的不断发展&#xff0c;IM即时聊天客服系统已经成为企业与客户沟通、解决问题、提升用户体验的重要工具。在选择IM即时聊天客服系统时&#xff0c;企业面临一个重要决策&#xff1a;选择SaaS&#xff08;软件即服务&#xff09;解决方案&#xff0c;还是进行私…

DeepSeek系列模型技术报告的阅读笔记

DeepSeek系列模型技术报告的阅读笔记 之前仔细阅读了DeepSeek系列模型的主要技术方面内容与发展脉络&#xff0c;以下是DeepSeek系列模型技术报告的笔记&#xff0c;有错误的地方欢迎指正&#xff01; 文章目录 DeepSeek系列模型技术报告的阅读笔记GQADeepseek MoEAbstractIn…