当前位置: 首页 > news >正文

quantization-大模型权重量化简介

原文地址

https://towardsdatascience.com/introduction-to-weight-quantization-2494701b9c0c/

https://towardsdatascience.com/4-bit-quantization-with-gptq-36b0f4f02c34/

权重量化简介

  • 大型语言模型(LLM) 以其庞大的计算需求而闻名。通常,模型的大小是通过将参数数量( 大小 )乘以这些值的精度( 数据类型 )来计算的。但是,为了节省内存,可以通过称为量化的过程,使用较低精度的数据类型来存储权重。

    以下列举了7B和32B模型在全精度 (FP32类型) 下的模型参数大小

    7B参数模型

    1. 参数数量:7B = 7 × 10^9
    2. 每个参数的精度:FP32 = 32 bits = 4 Bytes
    3. 模型大小 = 参数数量 × 每个参数的精度 = 7 × 10^9 × 4 Bytes = 28 × 10^9 Bytes
    4. 将Bytes转换为GB:28 × 10^9 Bytes / (1024 × 1024 × 1024) ≈ 26.21 GB

    32B参数模型

    1. 参数数量:32B = 32 × 10^9
    2. 每个参数的精度:FP32 = 32 bits = 4 Bytes
    3. 模型大小 = 参数数量 × 每个参数的精度 = 32 × 10^9 × 4 Bytes = 128 × 10^9 Bytes
    4. 将Bytes转换为GB:128 × 10^9 Bytes / (1024 × 1024 × 1024) ≈ 119.21 GB
  • 我们在文献中区分了两种主要的权重量化技术:

    Post-Training Quantization (PTQ): 训练后量化,是一种简单的技术,它将已训练模型的权重转换为较低的精度,而无需重新训练。虽然 PTQ 易于实现,但它可能会导致性能下降。

    Quantization-Aware Trainin(QAT): 量化感知训练,在预训练或微调阶段加入了权重转换过程,从而提升了模型性能。然而,QAT 的计算成本较高,并且需要具有代表性的训练数据。

  • 在本文中,我们重点介绍PTQ来降低参数的精度。为了获得更好的直观感受。我们将使用GPT-2模型,在一个玩具示例中分别应用简单和更复杂的技术。


浮点表示的背景

  • 数据类型的选择决定了所需的计算资源数量,从而影响模型的速度和效率。在深度学习应用中,平衡精度和计算性能至关重要 ,因为更高的精度通常意味着更高的计算需求。

  • 在众多数据类型中,浮点数因其能够高精度地表示各种值而广泛应用于深度学习。通常,浮点数使用 n 位来存储数值。这 n 位又可进一步划分为三个不同的部分:

    1. Sign (符号) :符号位表示数字的正负性质。它占用一位,其中 0 表示正数,1 表示负数。

    2. Exponent (指数) :指数是一段表示底数(二进制通常为 2)的幂的位。指数可以是正数或负数,从而表示非常大或非常小的值。

    3. Significand/Mantissa (尾数/有效位):剩余的位用于存储有效位,也称为尾数。它代表数字的有效数字。数字的精度很大程度上取决于有效位的长度。

  • 这种设计允许浮点数以不同的精度覆盖更广泛的值。其表示公式为:
    KaTeX parse error: Expected 'EOF', got '&' at position 2: &̲(-1)^{\mathbf{s…
    为了更好地理解这一点,让我们深入研究深度学习中最常用的一些数据类型:float32(FP32)、float16(FP16)和 bfloat16(BF16)


FP32

  • FP32 使用 32 位来表示一个数字:1 位表示符号位,8 位表示指数位,其余 23 位表示尾数。虽然 FP32 提供了较高的精度,但它的缺点是计算量和内存占用较高。

    image-20250503140802811

    在FP32表示中,

    1. sign : s i g n = ( − 1 ) 0 = 1 \mathbf{sign} = (-1)^0 = 1 sign=(1)0=1

    2. exponent: e x p o n e n t = 2 2 7 − 127 \mathbf{exponent} = 2^{2^7-127} exponent=227127

    3. significand:
      s i g n i f i c a n d = 1 + f r a c t i o n = 1 + 2 − 1 + 2 − 4 + 2 − 7 + 2 − 12 + 2 − 13 + 2 − 14 + 2 − 15 + 2 − 16 + 2 − 17 + 2 − 19 + 2 − 20 + 2 − 22 + 2 − 23 = 1 + 0.5707963705062866 = 1.5707963705062866 \begin{aligned} \mathbf{significand} &=1 + \mathbf{fraction} \\ &= 1+2^{-1}+2^{-4}+2^{-7}+2^{-12}+2^{-13}+2^{-14}+2^{-15}+2^{-16}+2^{-17}+2^{-19}+2^{-20}+2^{-22}+2^{-23} \\ &=1 + 0.5707963705062866 \\ &= 1.5707963705062866 \end{aligned} significand=1+fraction=1+21+24+27+212+213+214+215+216+217+219+220+222+223=1+0.5707963705062866=1.5707963705062866

    所以上面表示的数是
    ( − 1 ) 0 × 2 128 − 127 × 1.5707963705062866 (-1)^0 \times 2^{128-127} \times 1.5707963705062866 (1)0×2128127×1.5707963705062866


FP16

  • FP16** 使用 16 位来存储数字:1 位用于符号位,5 位用于指数位,10 位用于尾数。虽然这提高了内存效率并加快了计算速度,但范围和精度的降低可能会导致数值不稳定性, 从而可能影响模型精度。

    image-20250503143152228

    在FP16表示中,

    1. sign : s i g n = ( − 1 ) 0 = 1 \mathbf{sign} = (-1)^0 = 1 sign=(1)0=1

    2. exponent: e x p o n e n t = 2 2 5 − 15 \mathbf{exponent} = 2^{2^5-15} exponent=22515

    3. significand:
      s i g n i f i c a n d = 1 + f r a c t i o n = 1 + 2 − 1 + 2 − 4 + 2 − 7 = 1 + 0.5703125 = 1.5703125 \begin{aligned} \mathbf{significand} &=1 + \mathbf{fraction} \\ &= 1+2^{-1}+2^{-4}+2^{-7} \\ &=1 + 0.5703125 \\ &= 1.5703125 \end{aligned} significand=1+fraction=1+21+24+27=1+0.5703125=1.5703125

    所以上面表示的数是
    ( − 1 ) 0 × 2 16 − 15 × 1.5703125 (-1)^0 \times 2^{16-15} \times 1.5703125 (1)0×21615×1.5703125


BF16

  • BF16 也是 16 位格式,但其中 1 位表示符号位, 8 位表示指数位, 7 位表示尾数。与 FP16 相比,BF16 扩展了可表示范围,从而降低了下溢和上溢风险。尽管由于尾数位减少导致精度降低,但 BF16 通常不会显著影响模型性能,对于深度学习任务而言,这是一种有效的折衷方案。

    在BF16表示中,

    1. sign : s i g n = ( − 1 ) 0 = 1 \mathbf{sign} = (-1)^0 = 1 sign=(1)0=1

    2. exponent: e x p o n e n t = 2 2 7 − 128 \mathbf{exponent} = 2^{2^7-128} exponent=227128

    3. significand:
      s i g n i f i c a n d = 1 + f r a c t i o n = 1 + 2 − 1 + 2 − 4 + 2 − 7 = 1 + 0.5703125 = 1.5703125 \begin{aligned} \mathbf{significand} &=1 + \mathbf{fraction} \\ &= 1+2^{-1}+2^{-4}+2^{-7} \\ &=1 + 0.5703125 \\ &= 1.5703125 \end{aligned} significand=1+fraction=1+21+24+27=1+0.5703125=1.5703125

    所以上面表示的数是
    ( − 1 ) 0 × 2 16 − 15 × 1.5703125 (-1)^0 \times 2^{16-15} \times 1.5703125 (1)0×21615×1.5703125


  • 在机器学习术语中,FP32 通常被称为“全精度”(4 字节),而 BF16 和 FP16 被称为“半精度”(2 字节)。但我们能否做得更好,用一个字节来存储权重?答案是 INT8 数据类型,它由 8 位表示形式组成,能够存储 2⁸ = 256 个不同的值。在下一节中,我们将了解如何将 FP32 权重转换为 INT8 格式。

简单的8位量化

  • 在本节中,我们将实现两种量化技术:一种是采用绝对最大值 (absmax) 量化的对称量化技术,另一种是采用零点量化的非对称量化技术 。 这两种量化技术的目标都是将 FP32 张量 X (原始权重)映射到 INT8 张量 X_quant (量化权重)。

绝对最大值量化

  • 对称量化技术

  • 使用绝对最大值量化时,原始数字会除以张量的绝对最大值,再乘以缩放因子 (127),以将输入映射到 [-127, 127] 范围内。为了检索原始 FP16 值,需要将 INT8 数字除以量化因子,并承认由于舍入而导致的一些精度损失。
    X q u a n t = r o u n d ( X m a x ∣ X ∣ ⋅ 127 ) \mathbf{X_{quant}} = \mathbf{round}(\frac{\mathbf{X}}{\mathbf{max|X|}} \cdot 127) Xquant=round(max∣X∣X127)

    X d e q u a n t = X q u a n t 127 ⋅ m a x ∣ X ∣ \mathbf{X_{dequant}} = \frac{\mathbf{X_{quant}}}{127}\cdot \mathbf{max|X|} Xdequant=127Xquantmax∣X∣

    例如,假设绝对最大值为 3.2。权重 0.1 将被量化为
    X q u a n t = r o u n d ( 0.1 / 3.2 × 127 ) = r o u n d ( 3.96875 ) = 4 \begin{aligned} \mathbf{X_{quant}} &= \mathbf{round}(0.1/3.2 × 127) \\ &= \mathbf{round}(3.96875)\\ &= 4 \end{aligned} Xquant=round(0.1/3.2×127)=round(3.96875)=4
    。如果我们要对其进行反量化,则会得到
    X d e q u a n t = 4 / 127 × 3.2 = 0.1008 \begin{aligned} \mathbf{X_{dequant}} &= 4/127 × 3.2 \\ &= 0.1008 \end{aligned} Xdequant=4/127×3.2=0.1008

    ,这意味着误差为 0.008。以下是相应的 Python 实现:

    import torchdef absmax_quantize(X):# Calculate scalescale = 127 / torch.max(torch.abs(X))# QuantizeX_quant = (scale * X).round()# DequantizeX_dequant = X_quant / scalereturn X_quant.to(torch.int8), X_dequant
    

零点量化

  • 非对称量化技术

  • 通过零点量化 ,我们可以考虑非对称输入分布,这在考虑 ReLU 函数的输出(例如,仅考虑正值)时非常有用。首先,将输入值按值的总范围 (255) 除以最大值和最小值之差进行缩放。然后,将该分布按零点偏移,将其映射到 [-128, 127] 范围内(注意与 absmax 相比的额外值)。首先,我们计算缩放因子和零点值:
    s a c l e = 255 m a x ( X ) − m i n ( X ) \mathbf{sacle} = \frac{255}{\mathbf{max}(X) -\mathbf{min}(X)} sacle=max(X)min(X)255

    z e r o p o i n t = − r o u n d [ s c a l e ⋅ m i n ( X ) ] − 128 \mathbf{zeropoint} = -\mathbf{round}\left[\mathbf{scale} \cdot \mathbf{min(X)}\right] - 128 zeropoint=round[scalemin(X)]128

    然后,我们可以使用这些变量来量化或反量化我们的权重:
    X q u a n t = r o u n d ( s c a l e ⋅ X + z e r o p o i n t ) \mathbf{X_{quant}} = \mathbf{round}(\mathbf{scale} \cdot \mathbf{X} + \mathbf{zeropoint}) Xquant=round(scaleX+zeropoint)

    X d e q u a n t = X q u a n t − z e r o p o i n t s c a l e \mathbf{X_{dequant}} = \frac{\mathbf{X_{quant}}-\mathbf{zeropoint}}{\mathbf{scale}} Xdequant=scaleXquantzeropoint

    举个例子:最大值为 3.2,最小值为 -3.0。我们可以计算出比例为
    s c a l e = 255 / ( 3.2 + 3.0 ) = 41.13 \mathbf{scale} = 255/(3.2 + 3.0) = 41.13 scale=255/(3.2+3.0)=41.13
    零点为
    z e r o p o i n t = − r o u n d ( 41.13 × − 3.0 ) – 128 = 123 − 128 = − 5 \begin{aligned} \mathbf{zeropoint} &= -round(41.13 × -3.0) – 128 \\ &= 123 -128 \\ &= -5 \end{aligned} zeropoint=round(41.13×3.0)–128=123128=5
    因此之前的权重 0.1 将被量化为
    X q u a n t = r o u n d ( 41.13 × 0.1 + ( − 5 ) ) = − 1 \begin{aligned} \mathbf{X_{quant}}& = \mathbf{round}(41.13 × 0.1 +(-5))\\ &= -1 \end{aligned} Xquant=round(41.13×0.1+(5))=1
    这与之前使用 absmax 获得的值(4 vs. -1)截然不同。

    image-20250503150120995

  • 零点量化的python实现也非常简单

    import torchdef zeropoint_quantize(X):# Calculate value range (denominator)x_range = torch.max(X) - torch.min(X)x_range = 1 if x_range == 0 else x_range# Calculate scalescale = 255 / x_range# Shift by zero-pointzeropoint = (-scale * torch.min(X) - 128).round()# Scale and round the inputsX_quant = torch.clip((X * scale + zeropoint).round(), -128, 127)# DequantizeX_dequant = (X_quant - zeropoint) / scalereturn X_quant.to(torch.int8), X_dequant
    
  • 借助 transformers 库,我们可以在真实模型上使用这两个函数,而不必依赖完整的玩具示例。

  • 我们首先加载 GPT-2 的模型和分词器。这是一个非常小的模型,我们可能不想对其进行量化,但对于本教程来说已经足够了。首先,我们需要观察模型的大小,以便稍后进行比较,并评估 8 位量化带来的内存节省

    安装下面的库

    pip install'bitsandbytes>=0.39.0'
    pip install accelerate
    pip install transformers
    
    from transformers import AutoModelForCausalLM, AutoTokenizer
    import torch
    torch.manual_seed(0)# Set device to CPU for now
    device = 'cpu'# Load model and tokenizer
    model_id = 'gpt2'
    model = AutoModelForCausalLM.from_pretrained(model_id).to(device)
    tokenizer = AutoTokenizer.from_pretrained(model_id)# Print model size
    print(f"Model size: {model.get_memory_footprint():,} bytes")"""输出如下"""
    Model size: 510,342,192 bytes
    

    GPT-2 模型在 FP32 下的大小约为 510 , 342 , 192 / 1024 / 1024 ≈ 487 M B 510,342,192/1024/1024\approx 487MB 510,342,192/1024/1024487MB。下一步是使用零点量化和绝对最大量化来量化权重。在以下示例中,我们将这些技术应用于 GPT-2 的第一个注意力层,以查看结果。

    # Extract weights of the first layer
    weights = model.transformer.h[0].attn.c_attn.weight.data
    print("Original weights:")
    print(weights)# Quantize layer using absmax quantization
    weights_abs_quant, _ = absmax_quantize(weights)
    print("nAbsmax quantized weights:")
    print(weights_abs_quant)# Quantize layer using absmax quantization
    weights_zp_quant, _ = zeropoint_quantize(weights)
    print("nZero-point quantized weights:")
    print(weights_zp_quant)
    

    输出如下

    Original weights:
    tensor([[-0.4738, -0.2614, -0.0978,  ...,  0.0513, -0.0584,  0.0250],[ 0.0874,  0.1473,  0.2387,  ..., -0.0525, -0.0113, -0.0156],[ 0.0039,  0.0695,  0.3668,  ...,  0.1143,  0.0363, -0.0318],...,[-0.2592, -0.0164,  0.1991,  ...,  0.0095, -0.0516,  0.0319],[ 0.1517,  0.2170,  0.1043,  ...,  0.0293, -0.0429, -0.0475],[-0.4100, -0.1924, -0.2400,  ..., -0.0046,  0.0070,  0.0198]],device='cuda:0',dtype=torch.float32)Absmax quantized weights:
    tensor([[-21, -12,  -4,  ...,   2,  -3,   1],[  4,   7,  11,  ...,  -2,  -1,  -1],[  0,   3,  16,  ...,   5,   2,  -1],...,[-12,  -1,   9,  ...,   0,  -2,   1],[  7,  10,   5,  ...,   1,  -2,  -2],[-18,  -9, -11,  ...,   0,   0,   1]], device='cuda:0',dtype=torch.int8)Zero-point quantized weights:
    tensor([[-20, -11,  -3,  ...,   3,  -2,   2],[  5,   8,  12,  ...,  -1,   0,   0],[  1,   4,  18,  ...,   6,   3,   0],...,[-11,   0,  10,  ...,   1,  -1,   2],[  8,  11,   6,  ...,   2,  -1,  -1],[-18,  -8, -10,  ...,   1,   1,   2]], device='cuda:0',dtype=torch.int8)
    

    原始值 (FP32) 和量化值 (INT8) 之间的差异很明显,但绝对最大权重和零点权重之间的差异则更为微妙。在本例中,输入看起来偏移了 -1。这表明该层的权重分布相当对称。

  • 我们可以通过量化 GPT-2 中的每一层(线性层、注意力层等)来比较这些技术,并创建两个新模型: model_absmodel_zp 。准确地说,我们实际上会用去量化的权重替换原始权重。这样做有两个好处:1/ 它允许我们比较权重的分布(相同尺度);2/ 实际运行模型

  • 确实,PyTorch 默认不允许进行 INT8 矩阵乘法。在实际场景中,我们会对它们进行反量化以运行模型(例如在 FP16 中),但将它们存储为 INT8。在下一节中,我们将使用 [bitsandbytes]

    import numpy as np
    from copy import deepcopy# Store original weights
    weights = [param.data.clone() for param in model.parameters()]# Create model to quantize
    model_abs = deepcopy(model)# Quantize all model weights
    weights_abs = []
    for param in model_abs.parameters():_, dequantized = absmax_quantize(param.data)param.data = dequantizedweights_abs.append(dequantized)# Create model to quantize
    model_zp = deepcopy(model)# Quantize all model weights
    weights_zp = []
    for param in model_zp.parameters():_, dequantized = zeropoint_quantize(param.data)param.data = dequantizedweights_zp.append(dequantized)
    
  • 现在我们的模型已经量化,我们想检查这个过程的影响。直观地说,我们要确保量化后的权重接近原始权重 。一个直观的检查方法是绘制反量化后权重和原始权重的分布。如果量化是有损的,它会极大地改变权重分布。

    下图显示了两者的比较,其中蓝色直方图表示原始 (FP32) 权重,红色直方图表示去量化 (来自 INT8) 权重。请注意,我们仅显示 -2 到 2 之间的此图,因为存在绝对值非常高的异常值(稍后会详细介绍)。

    img

  • 两幅图非常相似,在 0 附近有一个令人惊讶的尖峰。这个尖峰表明我们的量化过程损耗很大,因为逆转过程不会输出原始值。对于 absmax 模型尤其如此,它在 0 附近既显示出较低的谷值,也显示出较高的尖峰。

    让我们比较一下原始模型和量化模型的性能。为此,我们定义了一个 generate_text() 函数,通过 top-k 采样生成 50 个 token。

    def generate_text(model, input_text, max_length=50):input_ids = tokenizer.encode(input_text, return_tensors='pt').to(device)output = model.generate(inputs=input_ids,max_length=max_length,do_sample=True,top_k=30,pad_token_id=tokenizer.eos_token_id,attention_mask=input_ids.new_ones(input_ids.shape))return tokenizer.decode(output[0], skip_special_tokens=True)# Generate text with original and quantized models
    original_text = generate_text(model, "I have a dream")
    absmax_text   = generate_text(model_abs, "I have a dream")
    zp_text       = generate_text(model_zp, "I have a dream")print(f"Original model:\n{original_text}")
    print("-" * 50)
    print(f"Absmax model:\n{absmax_text}")
    print("-" * 50)
    print(f"Zeropoint model:\n{zp_text}")
    

    输出如下

    Original model:
    I have a dream. I don't know what will come of it, but I am going to have to look for something that will be right. I haven't thought about it for a long time, but I have to try to get that thing
    --------------------------------------------------
    Absmax model:
    I have a dream job.If you can talk about your work and what you believe, then come on! I'm gonna love you!Letters in support of this article were also accepted on Facebook
    --------------------------------------------------
    Zeropoint model:
    I have a dream!"This was followed by "I have a dream!" "I dream!""My dream.""My dream!" "I dream!"In the first week of the holiday, the two-
    
  • 我们不必费力去判断某个输出是否比其他输出更有意义,而是可以通过计算每个输出的困惑度 (perplexity) 来量化它。困惑度是评估语言模型的常用指标,它衡量模型在预测序列中下一个标记时的不确定性。在这种比较中,我们通常假设分数越低,模型越好。实际上,困惑度高的句子也可能是正确的。

    我们使用最小函数来实现它,因为我们的句子很短,所以它不需要考虑上下文窗口的长度之类的细节。

    def calculate_perplexity(model, text):# Encode the textencodings = tokenizer(text, return_tensors='pt').to(device)# Define input_ids and target_idsinput_ids = encodings.input_idstarget_ids = input_ids.clone()with torch.no_grad():outputs = model(input_ids, labels=target_ids)# Loss calculationneg_log_likelihood = outputs.loss# Perplexity calculationppl = torch.exp(neg_log_likelihood)return pplppl     = calculate_perplexity(model, original_text)
    ppl_abs = calculate_perplexity(model_abs, absmax_text)
    ppl_zp  = calculate_perplexity(model_zp, absmax_text)print(f"Original perplexity:  {ppl.item():.2f}")
    print(f"Absmax perplexity:    {ppl_abs.item():.2f}")
    print(f"Zeropoint perplexity: {ppl_zp.item():.2f}")"""输出如下"""
    Original perplexity:  7.94
    Absmax perplexity:    18.97
    Zeropoint perplexity: 21.97
    
  • 我们发现原始模型的困惑度略低于其他两个模型。单次实验的可靠性不高,但我们可以重复多次此过程,以查看每个模型之间的差异。理论上,零点量化应该比绝对最大量化略好,但计算成本也更高。

  • 在此示例中,我们将量化技术应用于整个层(每个张量)。但是,我们可以将其应用于不同的粒度级别:从整个模型到单个值。一次性量化整个模型会严重降低性能,而量化单个值会产生巨大的开销。在实践中,我们通常更喜欢向量量化 ,它考虑了同一张量内行和列值的变化。

  • 然而,即使是向量量化也无法解决异常特征的问题。异常特征是指当模型达到一定规模(>6.7B 个参数)时,所有 Transformer 层中都会出现的极值(负值或正值)。这是一个问题,因为单个异常值就会降低所有其他值的精度。但丢弃这些异常特征并非明智之举,因为这会极大地降低模型的性能。

使用 LLM.int8() 进行 8 位量化

  • Dettmers 等人 (2022) 提出的 LLM.int8() 函数解决了异常值问题。它基于向量 (absmax) 量化方案,并引入了混合精度量化。这意味着异常值特征将以 FP16 格式处理以保持其精度,而其他值则以 INT8 格式处理。由于异常值约占所有值的 0.1%,这有效地将 LLM 的内存占用减少了近 2 倍。

    Image by author

  • LLM.int8() 通过三个关键步骤进行矩阵乘法计算:

    1. 使用自定义阈值从输入隐藏状态 X 中提取包含异常特征的列。
    2. 使用 FP16 对异常值进行矩阵乘法,使用 INT8 对非异常值进行矩阵乘法,并进行矢量量化(对于隐藏状态 X 则按行进行,对于权重矩阵 W 则按列进行)。
    3. 对非异常结果(INT8 到 FP16)进行去量化,并将其添加到异常结果中以获得 FP16 中的完整结果。

    Image by author

  • 这种方法是必要的,因为 8 位精度有限,在量化大值向量时可能导致严重误差。这些误差在多层传播时也容易被放大。

  • 由于 bitsandbytes 库已集成到 Hugging Face 生态系统中,我们可以轻松使用这项技术。我们只需在加载模型时指定 load_in_8bit=True (它也需要 GPU)。

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')model_int8 = AutoModelForCausalLM.from_pretrained(model_id,device_map='auto',load_in_8bit=True,)
    print(f"Model size: {model_int8.get_memory_footprint():,} bytes")"""输出如下"""
    Model size: 176,527,896 bytes
    

    $176,527,896 bytes / 1024/1024 \approx 168MB $ 加上这行代码后,模型体积几乎缩小了三倍(168MB vs. 487MB)。我们甚至可以像之前一样比较原始权重和量化权重的分布:

    img

    在这种情况下,我们看到在 -2、-1、0、1、2 等附近的峰值。这些值对应于以 INT8 格式存储的参数(非异常值)。您可以使用 model_int8.parameters() 打印模型的权重来验证这一点。

  • 我们还可以用这个量化模型生成文本,并将其与原始模型进行比较

    # Generate text with quantized model
    text_int8 = generate_text(model_int8, "I have a dream")print(f"Original model:n{original_text}")
    print("-" * 50)
    print(f"LLM.int8() model:n{text_int8}")
    

    输出

    Original model:
    I have a dream. I don't know what will come of it, but I am going to have to look for something that will be right. I haven't thought about it for a long time, but I have to try to get that thing
    --------------------------------------------------
    LLM.int8() model:
    I have a dream, I want to start writing." (This was when I was 6 years old). I have a dream, I want to start writing.After that I started college (I was a writer for three years). I started
    

    很难判断什么是最佳输出,但我们可以依靠困惑度指标来给我们一个(近似的)答案。

    print(f"Perplexity (original):   {ppl.item():.2f}")ppl = calculate_perplexity(model_int8, text_int8)
    print(f"Perplexity (LLM.int8()): {ppl.item():.2f}")"""输出"""
    Perplexity (original):   9.42
    Perplexity (LLM.int8()): 11.37
    

    在这种情况下,量化模型的困惑度一般解禁原始模型。这显示出这种量化技术非常具有竞争力。事实上,LLM.int8() 的作者表明,性能下降非常低,可以忽略不计 (<1%)。然而,它在计算方面有额外的成本:对于大型模型,LLM.int8() 的速度大约会慢 20%。

conclusion

  • 本文概述了最流行的权重量化技术。我们首先了解了浮点表示,然后介绍了两种 8 位量化技术: 绝对最大 (absmax)零点量化 。然而,它们的局限性,尤其是在处理异常值方面,促成了 LLM.int8() 的诞生,这种技术也能保持模型的性能。这种方法突显了权重量化领域的进展,揭示了正确处理异常值的重要性。

使用GPTQ进行4位量化

  • 权重量化领域的最新进展使我们能够在消费级硬件上运行大规模大型语言模型,例如在 RTX 3090 GPU 上运行 LLaMA-30B 模型。这得益于 GPTQ 、 GGML 和 NF4 等性能损失极小的新型 4 位量化技术。

  • 在上一篇文章中,我们介绍了简单的 8 位量化技术和优秀的 LLM.int8() 函数。在本文中,我们将探索流行的 GPTQ 算法 ,了解其工作原理,并使用 AutoGPTQ 库实现它。

Optimal Brain Quantization

  • 首先介绍一下我们要解决的问题。对于网络中的每一层ℓ,我们希望找到原始权重 Wₗ 的量化版本 Ŵₗ 。这被称为逐层压缩问题 。更具体地说 为了最大限度地减少性能下降,我们希望这些新权重的输出( ŴᵨXᵨ )尽可能接近原始权重( WᵨXᵨ 。换句话说,我们希望找到:
    arg ⁡ min ⁡ W ^ ℓ ∥ W ℓ X ℓ − W ^ ℓ X ℓ ∥ 2 2 \arg \min _{\widehat{\mathbf{W}}_{\ell}}\left\|\mathbf{W}_{\ell} \mathbf{X}_{\ell}-\widehat{\mathbf{W}}_{\ell} \mathbf{X}_{\ell}\right\|_2^2 argW min WXW X 22
    已经提出了不同的方法来解决这个问题,但我们感兴趣的是**Optimal Brain Quantization **(OBQ) 框架。

  • 该方法的灵感来源于一种剪枝技术 ,该技术可以从完全训练的密集神经网络(Optimal Brain Surgeon)中谨慎地移除权重。它使用了一种近似技术,并提供了明确的公式,用于计算待移除的最佳单个权重 w𐞥 和最佳更新 δ ,以调整剩余的非量化权重集合 F __ 来弥补移除的影响:
    w q = arg ⁡ min ⁡ w q ( quant ⁡ ( w q ) − w q ) 2 [ H F − 1 ] q q , δ F = − w q − quant ⁡ ( w q ) [ H F − 1 ] q q ⋅ ( H F − 1 ) : , q . \begin{aligned} & w_q=\arg \min _{w_q} \frac{\left(\operatorname{quant}\left(w_q\right)-w_q\right)^2}{\left[\mathbf{H}_F^{-1}\right]_{q q}}, \\ & \delta_F=-\frac{w_q-\operatorname{quant}\left(w_q\right)}{\left[\mathbf{H}_F^{-1}\right]_{q q}} \cdot\left(\mathbf{H}_F^{-1}\right)_{:, q} . \end{aligned} wq=argwqmin[HF1]qq(quant(wq)wq)2,δF=[HF1]qqwqquant(wq)(HF1):,q.
    其中,quant( w ) 是量化给出的权重舍入, Hꟳ 是 Hessian。

    使用 OBQ,我们可以先量化最容易量化的权重,然后调整所有剩余的未量化权重来补偿精度损失 。之后,我们选择下一个要量化的权重,依此类推。

  • 这种方法的一个潜在问题是,当存在异常权重时,这会导致较高的量化误差 。通常,这些异常值会最后量化,因为此时剩余的未量化权重很少,无法进行调整以补偿较大的误差。如果某些权重通过中间更新被进一步推到网格之外,这种影响可能会加剧。我们采用了一种简单的启发式方法来防止这种情况:异常值一出现就立即量化。

  • 这个过程计算量可能很大,尤其是对于 LLM 来说。为了解决这个问题,OBQ 方法使用了一个技巧,避免每次简化权重时都重新进行整个计算。在量化权重之后,它会通过移除与该权重相关的行和列 (使用高斯消元法)来调整用于计算的矩阵(Hessian):
    H − q − 1 = ( H − 1 − 1 [ H − 1 ] q q H : , q − 1 H q , : − 1 ) − p . \mathbf{H}_{-q}^{-1}=\left(\mathbf{H}^{-1}-\frac{1}{\left[\mathbf{H}^{-1}\right]_{q q}} \mathbf{H}_{:, q}^{-1} \mathbf{H}_{q,:}^{-1}\right)_{-p} . Hq1=(H1[H1]qq1H:,q1Hq,:1)p.

    该方法还采用向量化来一次性处理权重矩阵的多行。尽管 OBQ 效率很高,但随着权重矩阵规模的增加,其计算时间会显著增加。这种立方增长使得 OBQ 难以应用于包含数十亿个参数的大型模型


GPTQ 算法

  • GPTQ 算法由 Frantar 等人 (2023) 提出,它从 OBQ 方法中汲取灵感,但进行了重大改进,可以扩展到(非常)大型的语言模型。

步骤 1:任意顺序洞察

  • OBQ 方法会选择权重(模型中的参数)按特定顺序进行量化,该顺序由增加的额外误差最小决定。然而,GPTQ 观察到,对于大型模型,以任何固定顺序量化权重都能获得同样好的效果。这是因为,即使某些权重单独使用时可能会引入更多误差,它们也会在量化过程的后期进行量化,因为此时剩下的其他可能增加误差的权重很少。因此,顺序并不像我们想象的那么重要

    基于这一洞见,GPTQ 旨在以相同的顺序量化矩阵所有行的所有权重。这使得处理速度更快,因为某些计算只需对每列进行一次,而不是对每个权重进行一次。

    Image by author

步骤 2:延迟批量更新

  • 这种方案速度不快,因为它需要更新一个庞大的矩阵 ,而每个条目的计算量却很少。这类操作无法充分利用 GPU 的计算能力,并且会因内存限制(内存吞吐量瓶颈)而减慢速度。

  • 为了解决这个问题,GPTQ 引入了“惰性批量”更新。事实证明,给定列的最终舍入决策仅受对该列执行的更新影响,而不会受后续列的影响。因此,GPTQ 可以一次将算法应用于一批列 (例如 128 列),仅更新这些列及其对应的矩阵块。在完全处理完一个块后,该算法将对整个矩阵执行全局更新。
    δ F = − ( w Q − quant ⁡ ( w Q ) ) ( [ H F − 1 ] Q Q ) − 1 ( H F − 1 ) ; , Q , H − Q − 1 = ( H − 1 − H ; , Q − 1 ( [ H F − 1 ] Q Q ) − 1 H Q , : − 1 ) − Q . \begin{aligned} \delta_F & =-\left(\mathbf{w}_Q-\operatorname{quant}\left(\mathbf{w}_Q\right)\right)\left(\left[\mathbf{H}_F^{-1}\right]_{Q Q}\right)^{-1}\left(\mathbf{H}_F^{-1}\right)_{;, Q}, \\ \mathbf{H}_{-Q}^{-1} & =\left(\mathbf{H}^{-1}-\mathbf{H}_{;, Q}^{-1}\left(\left[\mathbf{H}_F^{-1}\right]_{Q Q}\right)^{-1} \mathbf{H}_{Q,:}^{-1}\right)_{-Q} . \end{aligned} δFHQ1=(wQquant(wQ))([HF1]QQ)1(HF1);,Q,=(H1H;,Q1([HF1]QQ)1HQ,:1)Q.

步骤 3:Cholesky 重新表述

  • 然而,还有一个问题需要解决。当算法扩展到非常大的模型时,数值不准确性可能会成为一个问题。具体来说,重复应用某个操作可能会累积数值误差

  • 为了解决这个问题,GPTQ 使用了 Cholesky 分解 ,这是一种用于解决某些数学问题的数值稳定方法。它涉及使用 Cholesky 方法从矩阵中预先计算一些所需信息。这种方法与轻微的“衰减”(在矩阵的对角线元素上添加一个小常数)相结合,有助于算法避免数值问题

    完整的算法可以概括为几个步骤:

    1. GPTQ 算法首先对 Hessian 逆进行 Cholesky 分解(该矩阵有助于决定如何调整权重)
    2. 然后它循环运行,一次处理一批列。
    3. 对于批次中的每一列,它量化权重,计算误差,并相应地更新块中的权重。
    4. 处理批次后,它会根据块的错误更新所有剩余的权重。
  • GPTQ 算法已在各种语言生成任务上进行了测试。我们将其与其他量化方法进行了比较,例如将所有权重四舍五入到最接近的量化值 (RTN)。GPTQ 与 BLOOM(176B 参数)和 OPT(175B 参数)模型系列配合使用,并使用单个 NVIDIA A100 GPU 对模型进行量化。

使用 AutoGPTQ 量化LLM

  • 这里注意啊,AutoGPTQ已经停止开发了(2024年2月之后就没有版本了)

  • GPTQ 非常流行,它用于创建能够在 GPU 上高效运行的 4 位精度模型。您可以在 Hugging Face Hub 上找到许多示例,尤其是来自 TheBloke 的示例。如果您正在寻找一种对 CPU 更友好的方法, GGML 目前是您的最佳选择。最后,带有 bitsandbytestransformers 库允许您在加载模型时使用 load_in_4bit=true 参数对其进行量化,这需要下载完整模型并将其存储在 RAM 中。

  • 让我们使用 AutoGPTQ 库实现 GPTQ 算法,并量化 GPT-2 模型。这需要 GPU,但 Google Colab 上的免费 T4 即可满足需求。首先,加载库并定义要量化的模型(在本例中为 GPT-2)。

    pip install auto-gptq
    pip install transformers
    
    import randomfrom auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
    from datasets import load_dataset
    import torch
    from transformers import AutoTokenizer# Define base model and output directory
    model_id = "gpt2"
    out_dir = model_id + "-GPTQ"
    
  • 现在我们要加载模型和分词器。分词器使用 transformers 库中经典的 AutoTokenizer 类加载。另一方面,我们需要传递一个特定的配置 ( BaseQuantizeConfig ) 来加载模型。

    在此配置中,我们可以指定要量化的位数(此处为 bits=4 )和组大小(惰性批次的大小)。请注意,此组大小是可选的:我们也可以对整个权重矩阵使用一组参数 。在实践中,这些组通常能够以非常低的成本提高量化质量(尤其是在 group_size=1024 情况下)。damp_percent damp_percent 用于辅助 Cholesky 重构,不应更改。

    最后, desc_act (也称为 act order)是一个比较棘手的参数。它允许你根据激活函数的递减顺序来处理行 ,这意味着最重要或影响最大的行(由采样的输入和输出决定)会被优先处理。此方法旨在将大部分量化误差(量化过程中不可避免地引入)放在不太重要的权重上。这种方法通过确保以更高的精度处理最重要的权重,提高了量化过程的整体准确性。然而,当与组大小一起使用时, desc_act 可能会导致性能下降,因为需要频繁重新加载量化参数。因此,我们在这里不会使用它(不过,将来可能会修复)。

    # Load quantize config, model and tokenizer
    quantize_config = BaseQuantizeConfig(bits=4,group_size=128,damp_percent=0.01,desc_act=False,
    )
    model = AutoGPTQForCausalLM.from_pretrained(model_id, quantize_config)
    tokenizer = AutoTokenizer.from_pretrained(model_id)
    

    量化过程高度依赖样本来评估和提升量化质量。样本提供了一种比较原始模型和新量化模型输出的方法。样本数量越多,就越有可能进行更准确、更有效的比较,从而提高量化质量。

  • 在本文中,我们利用 **C4(Colossal Clean Crawled Corpus,巨型干净爬取语料库)数据**集生成样本。C4 数据集是一个从 Common Crawl 项目收集的大规模多语言网络文本集合。该数据集经过清理和预处理,专门用于训练大规模语言模型,是此类任务的理想资源。WikiText 数据集也是另一个热门选择。

    在下面的代码块中,我们从 C4 数据集中加载 1024 个样本,对其进行标记并格式化。

    # Load data and tokenize examples
    n_samples = 1024
    data = load_dataset("allenai/c4", data_files="en/c4-train.00001-of-01024.json.gz", split=f"train[:{n_samples*5}]")
    tokenized_data = tokenizer("nn".join(data['text']), return_tensors='pt')# Format tokenized examples
    examples_ids = []
    for _ in range(n_samples):i = random.randint(0, tokenized_data.input_ids.shape[1] - tokenizer.model_max_length - 1)j = i + tokenizer.model_max_lengthinput_ids = tokenized_data.input_ids[:, i:j]attention_mask = torch.ones_like(input_ids)examples_ids.append({'input_ids': input_ids, 'attention_mask': attention_mask})
    

    现在数据集已准备就绪,我们可以以批次大小 1 开始量化过程。我们还可以选择使用 OpenAI Triton (CUDA 的替代方案)与 GPU 通信。完成后,我们将标记器和模型保存为 safetensors 格式。

    # Quantize with GPTQ
    model.quantize(examples_ids,batch_size=1,use_triton=True,
    )# Save model and tokenizer
    model.save_quantized(out_dir, use_safetensors=True)
    tokenizer.save_pretrained(out_dir)
    
  • 按照惯例,可以使用 AutoGPTQForCausalLMAutoTokenizer 类从输出目录加载模型和标记器

    device = "cuda:0" if torch.cuda.is_available() else "cpu"# Reload model and tokenizer
    model = AutoGPTQForCausalLM.from_quantized(out_dir,device=device,use_triton=True,use_safetensors=True,
    )
    tokenizer = AutoTokenizer.from_pretrained(out_dir)
    

    让我们检查一下模型是否正常工作。AutoGPTQ 模型(大部分情况下)可以作为普通的 transformers 模型运行,这使得它与推理流程兼容,如下例所示:

    from transformers import pipelinegenerator = pipeline('text-generation', model=model, tokenizer=tokenizer)
    result = generator("I have a dream", do_sample=True, max_length=50)[0]['generated_text']
    print(result)"""输出如下"""
    I have a dream," she told CNN last week. "I have this dream of helping my mother find her own. But, to tell that for the first time, now that I'm seeing my mother now, just knowing how wonderful it is that
  • 我们成功地从量化后的 GPT-2 模型中获得了令人信服的完备性。更深入的评估需要测量量化模型与原始模型的困惑度 。不过,这部分内容不在本文的讨论范围内。

结论

  • 在本文中,我们介绍了 GPTQ 算法,这是一种在消费级硬件上运行 LLM 的先进量化技术。我们展示了它如何基于改进的 OBS 技术(该技术具有任意阶洞察、惰性批量更新和 Cholesky 重构功能)解决逐层压缩问题。这种新颖的方法显著降低了内存和计算需求 ,使 LLM 能够被更广泛的用户所接受。

  • 此外,我们在免费的 T4 GPU 上量化了我们自己的 LLM 模型 ,并运行它来生成文本。您可以在 Hugging Face Hub 上推送您自己的 GPTQ 4 位量化模型版本。正如简介中提到的,GPTQ 并非唯一的 4 位量化算法: GGML 和 NF4 也是优秀的替代方案,只是适用范围略有不同。我鼓励您进一步了解它们并尝试一下!

相关文章:

quantization-大模型权重量化简介

原文地址 https://towardsdatascience.com/introduction-to-weight-quantization-2494701b9c0c/ https://towardsdatascience.com/4-bit-quantization-with-gptq-36b0f4f02c34/ 权重量化简介 大型语言模型(LLM) 以其庞大的计算需求而闻名。通常&#xff0c;模型的大小是通过将参…...

unity ScriptObject的使用

1.先定义一个类数据类型 [Serializable] public class FoodItemData { public int foodID; // 食物唯一ID public string foodName; // 食物名称 [TextArea(3, 10)] // 多行文本输入 public string description; // 食物描述 pu…...

广义线性模型三剑客:线性回归、逻辑回归与Softmax分类的统一视角

文章目录 广义线性模型三剑客&#xff1a;线性回归、逻辑回归与Softmax分类的统一视角引言&#xff1a;机器学习中的"家族相似性"广义线性模型(GLMs)基础三位家族成员的统一视角1. 线性回归(Linear Regression)2. 逻辑回归(Logistic Regression)3. Softmax分类(Softm…...

Linux时钟与时间API

深入理解 Linux 时钟与时间 API 时间是计算领域的基础概念之一。在 Linux 系统中&#xff0c;精确可靠的时间管理对于系统日志记录、任务调度、网络通信、性能分析、文件系统操作乃至应用程序的正确运行都至关重要。本文将深入探讨 Linux 中的时钟类型、相关的 C API、使用示例…...

闭包(Closure)及其作用和影响

一、闭包是什么 闭包&#xff08;Closure&#xff09;指的是​​一个函数能够记住并访问其词法作用域&#xff08;lexical scope&#xff09;&#xff0c;即使该函数在其词法作用域之外执行​​。换句话说&#xff0c;闭包让函数可以“记住”它被创建时的环境。 闭包的核心特…...

toLua笔记

基本 LuaState luaStatenew LuaState(); luaState.Start(); luaState.DoString("xxx"); luaState.DoFile("yyy.lua"); luaState.Require("zzz");//不要加.lua后缀 luaState.CheckTop();//检查解析器栈顶为空 luaState.Dispose(); luaStatenull;…...

20:深度学习-多层感知器原理

深度学习-多层感知器的原理 ------------------常州龙熙机器视觉培训班-课程资料 1.单层感知机 多层感知机是由感知机推广而来&#xff0c;感知机学习算法(PLA: Perceptron Learning Algorithm)用神经元的结构进行描述的话就是一个单独的。 首先了解下单层感知机: b--常量 …...

高频数据冲击数据库的技术解析与应对方案

目录 前言一、问题现象与影响分析1.1 典型场景表现1.2 核心问题分类 二、失效根源深度剖析2.1 架构设计缺陷2.2 缓存策略缺陷 三、解决方案与最佳实践3.1 缓存架构设计3.1.1 分层缓存架构3.1.2 热点数据识别 3.2 缓存策略优化3.2.1 动态过期时间算法3.2.2 缓存更新策略对比 3.3…...

(37)VTK C++开发示例 ---纹理地球

文章目录 1. 概述2. CMake链接VTK3. main.cpp文件4. 演示效果 更多精彩内容&#x1f449;内容导航 &#x1f448;&#x1f449;VTK开发 &#x1f448; 1. 概述 将图片纹理贴到球体上&#xff0c;实现3D地球的效果。 该代码使用了 VTK (Visualization Toolkit) 库来创建一个纹理…...

LeetCode - 1137.第N个泰波那契数

目录 题目 解法 动态规划解法 核心思想 执行流程 具体例子 时间复杂度和空间复杂度 代码 题目 1137. 第 N 个泰波那契数 - 力扣&#xff08;LeetCode&#xff09; 解法 动态规划解法 核心思想 动态规划是一种通过将复杂问题分解为更小子问题来解决的算法方法。我将…...

智能决策支持系统的系统结构:四库架构与融合范式

前文我们已经了解了智能决策支持系统的基本概念以及基本构件&#xff0c;接下来我们了解一下系统结构。 有关“智能决策支持系统的基本概念”的内容&#xff0c;可看我文章&#xff1a;智能决策支持系统的基本概念与理论体系-CSDN博客 有关“智能决策支持系统的基本构建”的…...

单片机裸机环境下临界区保护

目录 1、直接中断屏蔽法 2、嵌套计数优化法 3、BASEPRI寄存器应用 4、动态优先级调整策略 5、LDREX/STREX指令应用 6、位带别名区原子访问 7、上下文感知保护 8、中断延迟优化技术 在嵌入式系统开发中&#xff0c;临界区保护是确保系统可靠性的关键技术。本文以ARM Cor…...

【数字电路】第六章 时序逻辑电路

一、时序逻辑电路概述 1.逻辑电路的分类 2.时序逻辑电路的一般结构形式 3.时序逻辑电路的描述方法 4.时序逻辑电路按触发器动作特点分类 5.时序逻辑电路按输出信号特点分类 6.常用时序逻辑电路 二、同步时序逻辑电路的分析 1.同步时序逻辑电路的分析方法 TTL触发器允许输入端…...

Spring Boot的GraalVM支持:构建低资源消耗微服务

文章目录 引言一、GraalVM原生镜像技术概述二、Spring Boot 3.x的GraalVM支持三、适配GraalVM的关键技术点四、构建原生镜像微服务实例五、性能优化与最佳实践总结 引言 微服务架构已成为企业应用开发的主流模式&#xff0c;但随着微服务数量的增加&#xff0c;资源消耗问题日…...

MySQL中的窗口函数

深入理解窗口函数&#xff08;Window Functions&#xff09; 窗口函数确实经常用于分组后为行分配序号&#xff08;如1,2,3…&#xff09;&#xff0c;但它的功能远不止于此。窗口函数是SQL中极其强大的分析工具&#xff0c;可以让你在不减少行数的情况下进行复杂计算。 窗口函…...

WITH在MYSQL中的用法

WITH 子句&#xff08;也称为公共表表达式&#xff0c;Common Table Expression&#xff0c;简称 CTE&#xff09;是 SQL 中一种强大的查询构建工具&#xff0c;它可以显著提高复杂查询的可读性和可维护性。 一、基本语法结构 WITH cte_name AS (SELECT ... -- 定义CTE的查询…...

人工智能:如何快速筛选出excel中某列存在跳号的单元格位置?

前提&#xff1a; 电脑上必须提前安装好了【office AI】软件工具 方法如下&#xff1a; 1、打开要操作的excel表格&#xff0c;点击上方的【officeAI】&#xff0c;再点击左边的【右侧面板】按钮&#xff0c;就会出现如下右侧的【OfficeAI助手】 2、在OfficeAI助手的聊天框…...

动态功耗与静态功耗

0 英文缩写 SOI&#xff08;Silicon on Insulator&#xff09;绝缘体上硅FET&#xff08;Field-Effect Transistor&#xff09;场效应管CMOS&#xff08;Complementary Metal Oxide Semiconductor&#xff09;互补金属氧化物半导体 1 功耗分类 CMOS电路功耗主要可以通过如下…...

Webug4.0靶场通关笔记10- 第14关链接注入

目录 第14关 链接注入 1.打开靶场 2.源码分析 3.渗透实战 &#xff08;1&#xff09;方法1&#xff1a;跳转外部网页 &#xff08;2&#xff09;方法2&#xff1a;获取cookie 4.漏洞防御 本文通过《webug靶场第14关 链接注入》来进行渗透实战。 第14关 链接注入 链接注…...

PyTorch_指定运算设备 (包含安装 GPU 的 PyTorch)

PyTorch默认会将张量创建在 CPU 控制的内存中&#xff0c;即&#xff1a;默认的运算设备为 CPU。我们也可以将张量创建在 GPU 上&#xff0c;能够利用对于矩阵计算的优势加快模型训练。 将张量移动到 GPU 上有两种方法&#xff1a; 使用 cuda 方法直接在 GPU 上创建张量使用 …...

Pytorch-CUDA版本环境配置

Pytorch-CUDA版本环境配置 电脑如果是Windows平台下的Nvidia GPU的用户&#xff0c;需配置Pytorch的CUDA版本&#xff0c;分为三步&#xff1a; 1. 安装或更新NVIDA显卡驱动 官方驱动下载地址&#xff1a; https://www.nvidia.cn/Download/index.aspx?langcn 2. 安装CUDA To…...

力扣:24两两交换链表的节点

目录 1.题目描述&#xff1a; 2.算法思路&#xff1a; 3.代码展示&#xff1a; 1.题目描述&#xff1a; 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能…...

SETNX的存在问题和redisson进行改进的原理

首先分布式锁的原理就是当锁不存在时则创建&#xff0c;创建到锁的线程则执行业务。但是在这些操作中会有一些问题&#xff0c;下面是redis命令setNX设置锁的代码片段 if(缓存中有){返回缓存中的数据 }else{获取分布式锁if(获取锁成功&#xff09;{try{查询数据库}finally{释放…...

抽象工厂模式(Abstract Factory Pattern)

很好&#xff01;你现在已经开始接触设计模式了&#xff0c;而**抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;是一种常用于“创建一系列相关产品”**的经典设计模式。 我会一步步帮你理解&#xff1a; &#x1f9e0; 一句话解释 抽象工厂模式&#xff1a;提…...

AVIOContext 再学习

这个目前阶段用的不多&#xff0c;暂时不要花费太多精力。 url 的格式不同&#xff0c;使用的传输层协议也不同。这块看代码还没看到自己想的这样。 目前看的信息是&#xff1a;avformatContext 的 io_open 回调函数 在默认情况下叫 io_open_default&#xff0c;在解复用的 av…...

Power Query精通指南1:查询结构设计、数据类型、数据导入与迁移(平面文件、Excel、Web)

文章目录 零、Power Query简介0.1 Power Query 主要功能0.2 Power Query 的优势0.3 Power Query 组件 一、Power Query数据处理基本流程1.1 前期准备1.2 提取1.3 转换1.3.1 Power Query 编辑器界面1.3.2 默认转换1.3.3 自定义转换 1.4 加载1.4.1 自动检测数据类型1.4.2 重命名查…...

Linux 内核升级问题

一、内核升级后启动失败 原因&#xff1a;initramfs 镜像未正确生成或 GRUB 配置错误。 处理步骤如下&#xff1a; 1、进入旧内核启动系统。 2、重新生成 initramfs&#xff1a; sudo dracut -f --regenerate-all 3、更新 GRUB 配置&#xff1a; sudo grub2-mkconfig -o /boo…...

Linux 进程间通信(IPC)详解

进程间通信&#xff08;IPC&#xff09;深入解析 一、进程间通信概述 在操作系统里&#xff0c;不同进程间常常需要进行数据交换、同步协调等操作&#xff0c;进程间通信&#xff08;Inter - Process Communication&#xff0c;IPC&#xff09;机制应运而生。在Linux系统中&a…...

第3章 Python 3 基础语法001

文章目录 一、缩进规则1. 基本规则2. 示例3. 多级缩进4. 常见错误二、注释规则1. 单行注释2. 多行注释3. 特殊注释4. 注释规范三、代码块规则1. 控制结构2. 函数定义3. 类定义4. 上下文管理器四、总结与最佳实践五、调试技巧以下是 Python 3 基础语法规则的详细说明,涵盖 缩进…...

数据库介绍以及windows下mysql安装

文章目录 1. 前言2. MySQL概述2.1 相关概念2.2 DBMS的分类2.3 数据库交互图2.4 MySQL 介绍 3. MySQL 安装 数据库介绍以及windows下mysql安装 1. 前言 我们浏览的淘宝商品页面详情、刷视频网站的一个个视频&#xff0c;这些数据其实都是存储在公司的存储系统中的。想象一下&…...

list的两种设计

1. 内存布局对比 (1) MSVC 的实现 cpp class _List_node {_List_node* _Next; // 指向下一个节点_List_node* _Prev; // 指向前一个节点_Value_type _Value; // 存储的数据 }; 特点&#xff1a; 每个节点包含两个指针和一个数据成员。 Debug 模式&#xff1a;可能添加迭代…...

【C#】一个类中的接口方法使用static和不使用static的区别

在C#中&#xff0c;类中的接口方法是否使用 static 修饰符会带来显著的区别。这是因为接口方法的实现和调用方式与普通方法不同&#xff0c;而 static 关键字的使用进一步改变了这些行为。 以下是两者的区别&#xff1a; 1. 不使用 static 的接口方法 在这种情况下&#xff0…...

共铸价值:RWA 联合曲线价值模型,撬动现实资产生态

摘要 本文提出了一种针对真实资产&#xff08;RWA&#xff09;产业的联合曲线激励模型&#xff0c;将劳动与数据贡献映射为曲线价值&#xff0c;并基于固定档位与指数衰减奖励发放总计 2.1亿积分。该模型结合了去中心化定价与平滑递减机制&#xff0c;不仅为早期贡献者提供更高…...

【libuv】基于libuv的exe链接错误

vs2017构建 基于libuv的exe链接错误 1>libuv.lib(util.obj) : error LNK2019: unresolved external symbol __imp__GetAdaptersAddresses20 referenced in function _uv_interface_addresses 1>libuv.lib(util.obj) : error LNK2019: unresolved external symbol __imp__…...

什么是生成式 AI (GenAI)?

在科技飞速发展的今天,人工智能(AI)已不再是一个遥远的概念,而是悄然融入了我们的日常生活。从智能语音助手到自动驾驶汽车,从个性化推荐系统到医疗诊断辅助,AI正以前所未有的速度改变着世界。然而,在AI的广阔领域中,有一个分支正逐渐崭露头角,成为推动未来创新的关键…...

爬虫准备前工作

1.Pycham的下载 网址&#xff1a;PyCharm: The only Python IDE you need 2.Python的下载 网址&#xff1a;python.org&#xff08;python3.9版本之后都可以&#xff09; 3.node.js的下载 网址&#xff1a;Node.js — 在任何地方运行 JavaScript&#xff08;版本使用18就可…...

JVM——JVM 是如何处理异常的?

JVM 是如何处理异常的&#xff1f; 在 Java 编程语言中&#xff0c;异常处理是一种强大的机制&#xff0c;用于应对程序运行时出现的错误和意外情况。而 Java 虚拟机&#xff08;JVM&#xff09;作为 Java 程序运行的核心环境&#xff0c;在异常处理过程中扮演着至关重要的角色…...

网络基础-----C语言经典题目(12)

一、MTU&#xff0c;IP 协议头中 TTL是什么&#xff1f; MTU 指的是网络层能够接收的最大数据包大小&#xff0c;单位为字节。主要作用是限制数据链路层一次能够传输的数据量。 IP 协议头中的 TTL 是 IP 数据头部的一个 8 位字段&#xff0c;最初它的设计目的是限制数据包在网络…...

【第十六届蓝桥杯省赛】比赛心得与经验分享(PythonA 组)

文章目录 一、我的成绩二、我的备赛经历三、如何备赛&#xff08;个人观点&#xff09;1. 基础语法2. 数据结构3. 算法4. 数学 四、做题技巧与注意事项五、我的题解试题A 偏蓝 &#x1f3c6;100%试题B IPV6 &#x1f3c6;0%试题C 2025图形 &#x1f3c6;100%试题D 最大数字 &am…...

解决Maven项目中报错“java不支持版本6即更高的版本 7”

错误背景 当Maven项目编译或运行时出现错误提示 Java不支持版本6即更高的版本7&#xff0c;通常是由于项目配置的JDK版本与当前环境或编译器设置不一致导致的。例如&#xff1a; 项目配置的Java版本为6或7&#xff0c;但实际使用的是JDK 17。Maven或IDE的编译器未正确指定目标…...

MySQL--索引入门

MySQL官方对索引的定义为&#xff1a;索引&#xff08;Index&#xff09;是帮助MySQL高效获取数据的数据结构。 Mysql在存储数据之外&#xff0c;数据库系统各种还维护着满足特定查找算法的数据结构&#xff0c;这些数据结构以某种引用&#xff08;指向&#xff09;表中的数据…...

【网络原理】深入理解HTTPS协议

本篇博客给大家带来的是网络原理的知识点, 由于时间有限, 分三天来写, 本篇为线程第三篇,也是最后一篇. &#x1f40e;文章专栏: JavaEE初阶 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动…...

利用Elixir中的原子特性 + 错误消息泄露 -- Atom Bomb

题目信息: This new atom bomb early warning system is quite strange… 题目使用 elixir 语言 一开始,我们会访问 /page.html <!DOCTYPE html> <!-- 设定文档语言为英语 --> <html lang"en"> <head><!-- 设定字符编码为UTF-8 --><…...

机器人--STM32

STM32启动模式 1,从主闪存存储启动器启动(默认) 2,从系统存储启动器启动 下载程序时需要使用的启动方式。 3&#xff0c;从内置的SRAM启动...

LVGL -文本显示 英文、中文

1 文本 在 LVGL 中,文本控件(Label)是一种基本的 UI 组件,用于显示文本信息。文本控件可以用于各种场景,如显示状态信息、提示消息、标题等。在图形用户界面(GUI)开发中,文本是传达信息和指导用户的重要组成部分。为了有效地展示文本,以下是与文本相关的几个关键方面…...

Java面试资源获取

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 **1. GitHub开源项目****2. 技术博客与社区*…...

探索 Spring AI 的 ChatClient API:构建智能对话应用的利器

探索 Spring AI 的 ChatClient API&#xff1a;构建智能对话应用的利器 前言 在当今人工智能蓬勃发展的时代&#xff0c;智能对话系统成为了众多应用的核心组成部分。无论是客服机器人、智能助手还是聊天应用&#xff0c;都离不开高效、灵活的对话处理能力。Spring AI 作为 S…...

Java大师成长计划之第11天:Java Memory Model与Volatile关键字

&#x1f4e2; 友情提示&#xff1a; 本文由银河易创AI&#xff08;https://ai.eaigx.com&#xff09;平台gpt-4o-mini模型辅助创作完成&#xff0c;旨在提供灵感参考与技术分享&#xff0c;文中关键数据、代码与结论建议通过官方渠道验证。 在多线程编程中&#xff0c;线程的执…...

java学习之数据结构:一、数组

主要是对数组所有的东西进行总结&#xff0c;整理 适合小白~ 目录 1.什么是数组 1.1数组定义 1.2数组创建 1&#xff09;静态创建 2&#xff09;动态创建 1.3数组遍历 1&#xff09;for和while遍历 2&#xff09;foreach遍历 2.数组越界问题及解决 2.1数组越界问题 2…...

Oracle OCP认证考试考点详解083系列04

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 16. 第16题&#xff1a; 题目 解析及答案&#xff1a; 关于使用恢复管理器&#xff08;RMAN&#xff09;恢复表&#xff0c;以下哪三项是…...