一文看懂如何增强LLM的长文本处理能力(包含代码和原理解析)

2024-06-04 21:12

本文主要是介绍一文看懂如何增强LLM的长文本处理能力(包含代码和原理解析),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本篇博客是LLM中的RoPE位置编码代码解析与RoPE的性质分析(一)的续集,若对RoPE的性质不了解(比如远程衰减性、周期性与频率特性),建议先看LLM中的RoPE位置编码代码解析与RoPE的性质分析(一)

如何增强使用RoPE的LLM的处理长文本的能力

我们继续定义模型的训练长度为 L t r a i n L_{train} Ltrain,模型的测试长度为 L t e s t L_{test} Ltest L t e s t > L t r a i n L_{test} > L_{train} Ltest>Ltrain,定义 s = L t e s t L t r a i n s={L_{test} \over L_{train}} s=LtrainLtest为内插因子。

回忆一下RoPE的实现:
[ q 0 q 1 q 2 q 3 . . q d − 2 q d − 1 ] ∗ [ c o s m θ 0 c o s m θ 0 c o s m θ 1 c o s m θ 1 . . c o s m θ d / 2 − 1 c o s m θ d / 2 − 1 ] + [ − q 1 q 0 − q 3 q 2 . . − q d − 1 q d − 2 ] ∗ [ s i n m θ 0 s i n m θ 0 s i n m θ 1 s i n m θ 1 . . s i n m θ d / 2 − 1 s i n m θ d / 2 − 1 ] ( 1 ) \begin{bmatrix} %该矩阵一共3列,每一列都居中放置 q_0\\ %第一行元素 q_1\\ %第二行元素 q_2 \\ q_3 \\ .. \\ q_{d-2}\\ q_{d-1} \end{bmatrix} * \begin{bmatrix} %该矩阵一共3列,每一列都居中放置 cosm\theta_0\\ %第一行元素 cosm\theta_0\\ %第二行元素 cosm\theta_1 \\ cosm\theta_1 \\ .. \\ cosm\theta_{d/2-1}\\ cosm\theta_{d/2-1} \end{bmatrix} + \begin{bmatrix} %该矩阵一共3列,每一列都居中放置 -q_1\\ %第一行元素 q_0\\ %第二行元素 -q_3 \\ q_2 \\ .. \\ -q_{d-1}\\ q_{d-2} \end{bmatrix} * \begin{bmatrix} %该矩阵一共3列,每一列都居中放置 sinm\theta_0\\ %第一行元素 sin m\theta_0\\ %第二行元素 sinm\theta_1 \\ sinm\theta_1 \\ .. \\ sinm\theta_{d/2-1}\\ sinm\theta_{d/2-1} \end{bmatrix} \ \ \ \ \ \ \ \ \ \ \ \ (1) q0q1q2q3..qd2qd1 cosmθ0cosmθ0cosmθ1cosmθ1..cosmθd/21cosmθd/21 + q1q0q3q2..qd1qd2 sinmθ0sinmθ0sinmθ1sinmθ1..sinmθd/21sinmθd/21             (1)
在公式(1)中, m m m表示query向量的位置,同时cos函数与sin函数的输入均是 m θ i m\theta_{i} mθi i i i表示分量。

在RoPE中, m θ i m\theta_{i} mθi的定义是:
f ( m , θ i ) = m θ i = m b a s e 2 i / d ( 2 ) f(m, \theta_{i}) = m\theta_{i} = {m \over base^{2i/d}} \ \ \ \ \ \ \ \ \ \ \ \ \ \ (2) f(m,θi)=mθi=base2i/dm              (2)

对于某个模型来说, d d d是固定的。

位置编码内插是早期对基于RoPE的LLM进行长文本能力扩展的方法。

PI(位置编码内插,Position Interpolation)

EXTENDING CONTEXT WINDOW OF LARGE LANGUAGE MODELS VIA POSITION INTERPOLATION

位置编码内插是指,将超过 L t r a i n L_{train} Ltrain的文本的position_id缩放到 [ 0 , L t r a i n − 1 ] [0, L_{train}-1] [0,Ltrain1]。下图的第二行展示了位置编码内插后的效果,可以看到PI之后,原本两个点直接的距离变得更短了,压缩了局部token之间的分辨率,从而可能会造成局部失真。所以PI的方法需要一定量的额外训练,从而缓解失真问题(或者说是让模型适应比较拥挤的位置编码)。
在这里插入图片描述
transformers库对于PI的实现也是非常简单,可以看到,相比原始的RoPE,简单的将position_ids除了一个内插因子 s s s有相关论文表明,即使PI+后期fintune,最多也只能外推8倍的长度,再高性能便开始下降了。

class LlamaLinearScalingRotaryEmbedding(LlamaRotaryEmbedding):"""LlamaRotaryEmbedding extended with linear scaling. Credits to the Reddit user /u/kaiokendev"""def forward(self, x, position_ids):# difference to the original RoPE: a scaling factor is aplied to the position idsposition_ids = position_ids.float() / self.scaling_factorcos, sin = super().forward(x, position_ids)return cos, sin
进一步理解位置编码高频外推与低频内插的含义

PI方法简单粗暴,但也存在很多缺点:

  • 从position_id的角度理解,就是如上文所说的降低了模型的分辨率,造成局部失真。
  • 从LLM中的RoPE位置编码代码解析与RoPE的性质分析(一)中的高频低频角度理解,PI方法没有考虑到高频分量和低频分量的特性,统一的对所有分量进行了内插。好的RoEP扩展方法应当是做到:高频外推、低频内插。

在LLM中的RoPE位置编码代码解析与RoPE的性质分析(一)的最后,简单介绍过高频外推与低频内插。
在这里插入图片描述
在上图中,我们说到第 i = 0 i=0 i=0组属于高频分量,此时整个圈上的每一段弧线都被训练过,所以可以直接外推(这里外推的含义就是当 m ∈ [ L t r a i n , 2 L t r a i n − 1 ] m \in [L_{train},2L_{train}-1] m[Ltrain,2Ltrain1],不需要对 f ( m , θ 0 ) f(m,\theta_{0}) f(m,θ0)任何改动,可以直接扩展)。第 i = 20 i=20 i=20组属于低频分量,需要内插(这里内插的含义就是当 m ∈ [ L t r a i n , 2 L t r a i n − 1 ] m\in[L_{train},2L_{train}-1] m[Ltrain,2Ltrain1]时,需要缩放到 [ 0 , L t r a i n − 1 ] [0, L_{train}-1] [0,Ltrain1],此时 f f f函数变为 f ( m / s , θ 20 ) , 这里缩放因子 s = 2 f(m/s, \theta_{20}),这里缩放因子s=2 f(m/s,θ20),这里缩放因子s=2)。

也就是说,好的RoPE扩展方法,应该是当 m ∈ [ L t r a i n , 2 L t r a i n − 1 ] m \in [L_{train},2L_{train}-1] m[Ltrain,2Ltrain1]时:
f ( m , θ i ) = { f ( m , θ i ) , i ∈ ϕ h i g h f ( m / s , θ i ) , i ∈ ϕ l o w ( 3 ) f(m, \theta_{i})= \left\{ \begin{aligned} & f(m, \theta_{i}) \ \ \ \ \ \ \ \ , i \in \phi_{high} \\ & f(m/s, \theta_{i}) \ \ \ \ \ \ \ \ , i \in \phi_{low} \\ \end{aligned} \ \ \ \ \ \ \ \ \ (3) \right. f(m,θi)={f(m,θi)        ,iϕhighf(m/s,θi)        ,iϕlow         (3)
其中, ϕ h i g h \phi_{high} ϕhigh属于高频分量集合, ϕ l o w \phi_{low} ϕlow属于低频分量集合。可见PI的方法对低频分量做到了内插,但没有对高频分量做到外推。

那么对于低频分量来说,将 m / s m/s m/s,本质上其实还是扩大了 b a s e base base参数,我们令

m b a s e ^ 2 i / d = m / s b a s e 2 i / d ( 4 ) {m \over \hat{base}^{2i/d}} = {m /s \over base^{2i/d}} \ \ \ \ \ \ \ \ \ \ \ \ \ (4) base^2i/dm=base2i/dm/s             (4)
解得, b a s e ^ = s d / 2 i b a s e \hat{base}=s^{d/2i}base base^=sd/2ibase,也就是将base参数扩大了 s d / 2 i s^{d/2i} sd/2i倍。

ABF(增加base参数,Adjusted Base Frequency)

Effective Long-Context Scaling of Foundation Models

如何理解简单粗暴的修改base参数就可以增加模型的外推能力?

在实验中,我们发现,LLaMA3的base=10000,只能从8k直接外推到9k左右,而Qwen1.5的base=1000000,可以从32k直接外推到52k。

  • 角度1:从远程衰减性的角度
    LLM中的RoPE位置编码代码解析与RoPE的性质分析(一)中提到,RoPE具有远程衰减性,当base参数较小时(比如10000),这时对于长文本来说,远程的token注意力就更减弱了,因此这篇文章中提出,可以直接将base参数从10000 增加到 500000,这样可以降低RoPE的远程衰减性。

  • 角度2:从容量的角度
    如下图所示,左侧的图表示,用3维4进制的数,最多可以表示 [ 0 − 63 ] [0-63] [063],我们用 f ( b a s e = 4 , d i m = 3 ) f(base=4,dim=3) f(base=4,dim=3)表示。
    假设现在需要表示 [ 0 , 124 ] [0, 124] [0,124],应该如何做?当然可以继续按照4进制,将维数由3维扩展为4维。也可以有另外的方法,将进制改为5进制,这样可以在不增加维数的情况下,增加 f f f的表示容量。 f ( b a s e = 4 , d i m = 3 ) f(base=4,dim=3) f(base=4,dim=3)的容量是64,而 f ( b a s e = 5 , d i m = 3 ) f(base=5,dim=3) f(base=5,dim=3)的容量是125。
    在这里插入图片描述
    对于RoPE来说,增加base参数,相当于增加了RoPE的表示容量,所以在长度外推的时候,大的base参数,外推能力要好于小base参数的外推能力,并且,增加base参数,并不会改变相邻两个点的距离,所以不会有PI方法的局部失真问题,如论文中的图所示。
    在这里插入图片描述

NTK-RoPE (NTK-aware)

https://www.reddit.com/r/LocalLLaMA/comments/14mrgpr/dynamically_scaled_rope_further_increases/

ABF的方法明显是对模型的长文本处理是有效果的,但是并没有给出一个具体计算扩大原本base的公式,NTK-RoPE给出了扩大base参数的公式:
f ( m , θ i ) = m ( b a s e ∗ s d / ( d − 2 ) ) 2 i / d ( 5 ) f(m, \theta_{i}) = {m \over (base*s^{d/(d-2)}) ^{2i/d}} \ \ \ \ \ \ \ \ \ \ \ \ (5) f(m,θi)=(basesd/(d2))2i/dm            (5)
相比于公式(2),base参数被扩大了 s d / d − 2 s^{d/d-2} sd/d2倍。那么base参数为什么要扩大这么多倍?NTK-RoPE的作者提出,在推导的时候,先是保证了让最低频( i = d / 2 − 1 i=d/2-1 i=d/21)执行完整的内插。也就是有:
( b a s e ∗ k ) − 2 i / d = s ∗ b a s e − 2 i / d (base*k)^{-2i/d} = s*base^{-2i/d} (basek)2i/d=sbase2i/d
解得 k = s d / ( d − 2 ) = L t e s t L t r a i n d / ( d − 2 ) k = s^{d/(d-2)} = {L_{test} \over L_{train}}^{d/(d-2)} k=sd/(d2)=LtrainLtestd/(d2)。在这种情况下,虽然NTK-RoPE对于每一个分量,都将base参数扩大了 s d / d − 2 s^{d/d-2} sd/d2倍,但是保证了最高频( i = 0 时, f ( m , θ 0 ) 结果不变 i=0时,f(m, \theta_{0})结果不变 i=0时,f(m,θ0)结果不变),从而实现了最高频外推(不插值),最低频( i = d / / 2 − 1 i=d//2-1 i=d//21)插值,从而在免训练的情况下,效果超过了PI。

但是在微调情况下,PI的效果要好于NTK-RoPE,原因在于NTK-RoPE可能会出现越界值,缓解的办法是,调高 s s s

代码实现如下:

class LlamaDynamicNTKScalingRotaryEmbedding(LlamaRotaryEmbedding):"""LlamaRotaryEmbedding extended with Dynamic NTK scaling. Credits to the Reddit users /u/bloc97 and /u/emozilla"""def forward(self, x, seq_len):# difference to the original RoPE: inv_freq is recomputed when the sequence length > original lengthposition_ids = torch.arange(seq_len, dtype=torch.int64).unsqueeze(0).to(x.device)# seq_len = torch.max(position_ids) + 1if seq_len > self.max_position_embeddings:base = self.base * ((self.scaling_factor * seq_len / self.max_position_embeddings) - (self.scaling_factor - 1)) ** (self.dim / (self.dim - 2))inv_freq = 1.0 / (base ** (torch.arange(0, self.dim, 2, dtype=torch.int64).float().to(x.device) / self.dim))self.register_buffer("inv_freq", inv_freq, persistent=False)  # TODO joao: this may break with compilationcos, sin = super().forward(x, position_ids)return cos, sin

目前所有的非Dynamic的长度扩展方法均会对 L t r a i n L_{train} Ltrain长度内的文本性能造成一定下降。为了避免外推的时候,影响 L t r a i n L_{train} Ltrain长度内的文本性能表现,上面的代码使用了Dynamic的方法。

Yarn (NTK-by-parts)

YaRN: Efficient Context Window Extension of Large Language Models
这篇论文写的很好,建议阅读。

NTK-RoPE的方法只保证了最高频分量外推和最低频分量内插。根据公式(3),应该是对高频分量集合 ϕ h i g h \phi_{high} ϕhigh外推,对低频分量集合 ϕ l o w \phi_{low} ϕlow内插。那么如何得到 ϕ h i g h \phi_{high} ϕhigh ϕ l o w \phi_{low} ϕlow

这里需要额外引入一个概念,波长 λ \lambda λ,我们定义RoPE embedding的第 i i i组分量的波长计算公式:
λ i = 2 π θ i = 2 π b a s e 2 i / d ( 6 ) \lambda_{i} = {2\pi \over \theta_{i}} =2\pi base^{2i/d} \ \ \ \ \ \ \ \ (6) λi=θi2π=2πbase2i/d        (6)
公式(6)描述了RoPE的第 i i i组分量旋转360度(一圈)所走过的长度。Yarn论文的作者还发现,对于某些低频分量,其波长 λ i > L t r a i n \lambda_{i}>L_{train} λi>Ltrain,也就是如我们前面所讲的,在整个训练过程中,没有转够一圈,整个圈上只有一段弧线被训练过。对于高频分量来说,可能已经转了很多圈。所以我们还可以定义一个在 L t r a i n L_{train} Ltrain长度上的圈数:
r i = L t r a i n λ i r_{i} = {L_{train} \over \lambda_{i} } ri=λiLtrain
那么,我们可以定义两个超参数, β f a s t \beta_{fast} βfast β s l o w \beta_{slow} βslow,分别表示筛选高频分量的阈值与筛选低频分量的阈值:

  • r i > β f a s t r_{i} > \beta_{fast} ri>βfast,那么认为是高频分量,不需要内插。
  • r i < β s l o w r_{i} < \beta_{slow} ri<βslow,那么认为是低频分量,需要内插。
  • 若介于二者之间,则内插和外推均可。

若用公式来表达,那就是:

f ( m , θ i ) = m / s b a s e 2 i / d ( 1 − α ( r i ) ) + m b a s e 2 i / d α ( r i ) ( 7 ) f(m, \theta_{i}) = {m/s \over base^{2i/d}} (1-\alpha(r_{i})) +{m \over base^{2i/d} } \alpha(r_{i}) \ \ \ \ \ \ \ \ \ \ (7) f(m,θi)=base2i/dm/s(1α(ri))+base2i/dmα(ri)          (7)
其中:
α ( r i ) = { 1 , r i > β f a s t 0 , r i < β s l o w r i − β s l o w β f a s t − β s l o w , β s l o w < = r i < = β f a s t ( 8 ) \alpha(r_{i})= \left\{ \begin{aligned} & 1 \ \ \ \ \ \ \ \ , r_{i} > \beta_{fast} \\ & 0 \ \ \ \ \ \ \ \ , r_{i} < \beta_{slow} \\ & {r_{i}-\beta_{slow} \over \beta_{fast}-\beta_{slow}} \ \ \ \ \ \ , \beta_{slow}<= r_{i} <= \beta_{fast}\\ \end{aligned} \ \ \ \ \ \ \ \ \ (8) \right. α(ri)= 1        ,ri>βfast0        ,ri<βslowβfastβslowriβslow      ,βslow<=ri<=βfast         (8)

  1. 截止目前,其实都是NTK-by-parts的内容。
  2. NTK-by-parts在微调和非微调的场景下,均取得了最好的性能。
  3. 在论文中,作者发现对于LLaMA2来说, β f a s t = 32 , β s l o w = 1 \beta_{fast}=32, \beta_{slow}=1 βfast=32,βslow=1时,可以有最好的性能表现。

下图展示了LLaMA2-7B的不同分量对应的旋转圈数的变化曲线, β f a s t = 32 , β s l o w = 1 , i ∈ [ 0 , 2048 ) \beta_{fast}=32, \beta_{slow}=1, i \in [0, 2048) βfast=32,βslow=1,i[0,2048)
在这里插入图片描述

  • i = 670 i=670 i=670左侧的区域均直接外推,不进行插值
  • i = 1441 i=1441 i=1441右侧的区域进行内插
  • 中间的区域外推和内插混合。

制图的代码如下

import torch
import math
import matplotlib.pyplot as pltdef find_correction_dim(num_rotations, dim, base=10000, max_position_embeddings=2048):return (dim * math.log(max_position_embeddings/(num_rotations * 2 * math.pi)))/(2 * math.log(base))# Find dim range bounds based on rotations
def find_correction_range(beta_fast, beta_slow, dim, base=10000, max_position_embeddings=2048):low = math.floor(find_correction_dim(beta_fast, dim, base, max_position_embeddings))high = math.ceil(find_correction_dim(beta_slow, dim, base, max_position_embeddings))return max(low, 0), min(high, dim-1)  # Clamp values just in casedim = 4096
max_pos_embeddings = 4096
base = 10000
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2) / dim))# 波长
lambda_ = 2 * math.pi / inv_freq
# 旋转圈数
num_ros = max_pos_embeddings / lambda_x = torch.arange(dim//2).numpy()
y = num_ros.numpy()low_dim, high_dim = find_correction_range(beta_fast=32, beta_slow=1, dim=dim, base=base, max_position_embeddings=max_pos_embeddings)
print(low_dim)
print(high_dim)
print(num_ros[high_dim], num_ros[low_dim])plt.title(f'LLaMA2-7B-L_train={max_pos_embeddings}-base={base}')
plt.plot(x, y,  label=f"base={base}")
plt.axvline(x=low_dim, color="r", linestyle='--', label=f'i={low_dim}')
plt.axvline(x=high_dim, color="g", linestyle='--', label=f'i={high_dim}')
plt.legend()
plt.xlabel('i')
plt.ylabel('num rotations')
plt.show()

接下来应该是Yarn的版本了。Yarn是在NTK-by-parts的基础上,在计算attention score的时候,进行了一个温度系数的惩罚。
s o f t m a x ( q m k n T t d ) softmax({q_{m}k_{n}^{T} \over t \sqrt{d}}) softmax(td qmknT)
由于RoPE的特性,直接将 q m 与 k n q_{m}与k_{n} qmkn除以 1 / t \sqrt{1/t} 1/t ,可以达到同样的效果。那么 1 / t \sqrt{1/t} 1/t 如何确定?Yarn论文作者取值为:
1 / t = 0.1 l n ( s ) + 1 \sqrt{1/t} = 0.1ln(s) +1 1/t =0.1ln(s)+1

至此,Yarn的方法就结束了。
Yarn的代码如下:

class LlamaYaRNScaledRotaryEmbedding(LlamaRotaryEmbedding):def __init__(self,*args,original_max_position_embeddings=2048,extrapolation_factor=1,attn_factor=1,beta_fast=32,beta_slow=1,device=None,**kwargs):super().__init__(*args, **kwargs)self.original_max_position_embeddings = original_max_position_embeddingsself.extrapolation_factor = extrapolation_factorself.attn_factor = attn_factorself.beta_fast = beta_fastself.beta_slow = beta_slowself.yarn(device)def yarn(self, device):pos_freqs = self.base ** (torch.arange(0, self.dim, 2).float().to(device) / self.dim)inv_freq_extrapolation = 1.0 / pos_freqsinv_freq_interpolation = 1.0 / (self.scaling_factor * pos_freqs)# beta_fast和beta_slow表示分量旋转弧度的阈值,意思就是分量d的旋转弧度大于beat_fast,可以认为整个圈上的每个点都训练充分了,直接外推# 分量d的旋转弧度小于beta_slow,可以认为是整个圈上,只有一段弧线被训练过,需要内插low, high = find_correction_range(self.beta_fast, self.beta_slow, self.dim, self.base, self.original_max_position_embeddings)# 对应yarn论文的公式(13)inv_freq_mask = (1 - linear_ramp_mask(low, high, self.dim // 2).float().to(device)) * self.extrapolation_factor  # Get n-d rotational scaling corrected for extrapolationinv_freq = inv_freq_interpolation * (1 - inv_freq_mask) + inv_freq_extrapolation * inv_freq_maskself.register_buffer("inv_freq", inv_freq)self.mscale = float(get_mscale(self.scaling_factor) * self.attn_factor)   # Get n-d magnitude scaling corrected for interpolationdef forward(self, x, seq_len=None):# x: [bs, num_attention_heads, seq_len, head_size]# This `if` block is unlikely to be run after we build sin/cos in `__init__`. Keep the logic here just in case.cos, sin = super().forward(x, seq_len)cos = cos * self.mscalesin = sin * self.mscalereturn cos, sin

目前开源厂家如何增强LLM的长文本能力

Yi

根据Yi的技术报告,Yi的方案是:

  1. 训练4k 预训练模型 ,base=10000
  2. 修改base为10000000,max_position_embeddings=32768,在长文本(数据来源于书籍,长度可能是16k、32k)上少量训练,1-2Btoken就可收敛
  3. 外推到200k(不用任何长度扩展技术?根据目前开源的checkpoint是这样的。)
InternLM

根据InternLM的技术报告,InterLM的方案是:

  1. 训练4k 预训练模型,base=50000
  2. 修改base=1000000,max_position_embeddings=32768,混合50%的32k数据继续训练。
  3. 外推到200k
qwen1.5

qwen1.5没有技术报告,但是可以从config.json中看到,qwen1.5的max_position_embeddings=32768,达到了32k,base=1000000,方案可能和Yi、InternLM的类似。
在我们的测试下,qwen1.5直接外推可以到52k长度,yarn可以扩展到128k。

参考文献:
https://spaces.ac.cn/archives/9948/comment-page-2#comments
https://blog.csdn.net/PennyYu123/article/details/131717323
https://openreview.net/pdf?id=wHBfxhZu1u

这篇关于一文看懂如何增强LLM的长文本处理能力(包含代码和原理解析)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1031130

相关文章

使用 sql-research-assistant进行 SQL 数据库研究的实战指南(代码实现演示)

《使用sql-research-assistant进行SQL数据库研究的实战指南(代码实现演示)》本文介绍了sql-research-assistant工具,该工具基于LangChain框架,集... 目录技术背景介绍核心原理解析代码实现演示安装和配置项目集成LangSmith 配置(可选)启动服务应用场景

Python中顺序结构和循环结构示例代码

《Python中顺序结构和循环结构示例代码》:本文主要介绍Python中的条件语句和循环语句,条件语句用于根据条件执行不同的代码块,循环语句用于重复执行一段代码,文章还详细说明了range函数的使... 目录一、条件语句(1)条件语句的定义(2)条件语句的语法(a)单分支 if(b)双分支 if-else(

MySQL数据库函数之JSON_EXTRACT示例代码

《MySQL数据库函数之JSON_EXTRACT示例代码》:本文主要介绍MySQL数据库函数之JSON_EXTRACT的相关资料,JSON_EXTRACT()函数用于从JSON文档中提取值,支持对... 目录前言基本语法路径表达式示例示例 1: 提取简单值示例 2: 提取嵌套值示例 3: 提取数组中的值注意

CSS3中使用flex和grid实现等高元素布局的示例代码

《CSS3中使用flex和grid实现等高元素布局的示例代码》:本文主要介绍了使用CSS3中的Flexbox和Grid布局实现等高元素布局的方法,通过简单的两列实现、每行放置3列以及全部代码的展示,展示了这两种布局方式的实现细节和效果,详细内容请阅读本文,希望能对你有所帮助... 过往的实现方法是使用浮动加

JAVA调用Deepseek的api完成基本对话简单代码示例

《JAVA调用Deepseek的api完成基本对话简单代码示例》:本文主要介绍JAVA调用Deepseek的api完成基本对话的相关资料,文中详细讲解了如何获取DeepSeekAPI密钥、添加H... 获取API密钥首先,从DeepSeek平台获取API密钥,用于身份验证。添加HTTP客户端依赖使用Jav

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

C语言中自动与强制转换全解析

《C语言中自动与强制转换全解析》在编写C程序时,类型转换是确保数据正确性和一致性的关键环节,无论是隐式转换还是显式转换,都各有特点和应用场景,本文将详细探讨C语言中的类型转换机制,帮助您更好地理解并在... 目录类型转换的重要性自动类型转换(隐式转换)强制类型转换(显式转换)常见错误与注意事项总结与建议类型

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查

nginx-rtmp-module模块实现视频点播的示例代码

《nginx-rtmp-module模块实现视频点播的示例代码》本文主要介绍了nginx-rtmp-module模块实现视频点播,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习... 目录预置条件Nginx点播基本配置点播远程文件指定多个播放位置参考预置条件配置点播服务器 192.

在Rust中要用Struct和Enum组织数据的原因解析

《在Rust中要用Struct和Enum组织数据的原因解析》在Rust中,Struct和Enum是组织数据的核心工具,Struct用于将相关字段封装为单一实体,便于管理和扩展,Enum用于明确定义所有... 目录为什么在Rust中要用Struct和Enum组织数据?一、使用struct组织数据:将相关字段绑