使用统计方法在AMD GPU上使用JAX Profiler可靠地比较大型生成AI模型中的算法性能

Using statistical methods to reliably compare algorithm performance in large generative AI models with JAX Profiler on AMD GPUs — ROCm Blogs

摘要

本文提供了一份详细的指南,介绍如何在JAX实现的生成AI模型中测量和比较各种算法的性能。利用JAX Profiler和统计分析,本文展示了如何可靠地评估关键步骤并比较AMD GPU上算法的性能。

引言

在GPU加速计算的动态领域,追求最佳性能和效率需要有效的性能分析技术。性能分析通过仔细检查执行时间、内存利用率和内核占用率等指标,提供了对基于GPU的应用程序行为和性能特征的全面了解。这对于大规模生成AI模型尤为重要,因为优化性能可以显著提升最终用户体验和收入来源。通过利用性能分析技术,开发人员可以找出低效之处,深入了解运行时行为,并最终优先考虑战略性优化工作,从而带来显著的性能提升。
JAX是谷歌的一款开源数值计算库(尽管不是官方的谷歌产品),由于其能够利用硬件加速器和自动微分的能力,正在生成AI领域引起广泛关注。最初用于高性能机器学习研究,JAX的函数式编程方法和对GPU及TPU的支持使其成为构建和部署大型语言模型(LLMs)和其他前沿生成AI应用的首选。值得注意的是,像 X.AI这样的公司利用JAX开发开源模型如Grok-1,进一步推动了该库在生成AI领域的流行。凭借其性能、灵活性及其适合先进AI模型开发和部署的特点,JAX继续在受欢迎程度上不断攀升。

ROCm博客系列此前已探索过各种性能分析工具,如 *rocprof*,可以用于在AMD GPU上分析模型性能,还有针对TensorFlow和PyTorch的框架特定性能分析工具。尽管JAX的官方页面涵盖了其性能分析工具的基本用法,本教程深入探讨了更高级的技术。例如,它解释了在评估算法时,如何在考虑到大量随机噪声的情况下确定一种算法是否显著优于另一种算法。本文通过统计分析和假设检验,展示了如何可靠地测量和比较在大型语言模型中执行相同步骤的不同算法的性能。具体而言,它比较了在JAX-based生成预训练变换器(GPT)模型的`CausalSelfAttention`组件中,使用`einsum`与`matmul`实现两个矩阵乘法步骤的性能。(参见博客中关于在JAX中实现GPT模型的文章)。要了解更多关于`einsum`的信息,请访问这篇博客。 

实现

要实现此代码示例,请首先设置ROCm环境,并安装必要的软件包和Python脚本。值得注意的是,该代码示例是平台无关的,这意味着只要加速计算平台和Python包配置正确,它就兼容AMD GPU以及其他GPU或TPU。

环境设置

按照以下步骤为本教程设置运行环境:

1. 在Linux shell中使用下面的代码拉取并运行docker容器:

docker run -it --ipc=host --network=host --device=/dev/kfd --device=/dev/dri \--group-add video --cap-add=SYS_PTRACE --security-opt seccomp=unconfined \--name=nanogpt rocm/pytorch:rocm6.1_ubuntu22.04_py3.10_pytorch_2.1.2 /bin/bash

2. 在docker容器内运行以下代码,以安装必要的Python包并配置XLA环境变量:

python3 -m pip install --upgrade pip
pip install optax==0.2.2 flax==0.8.2 transformers==4.38.2 tiktoken==0.6.0 datasets==2.17.1 perfetto==0.7.0 matplotlib==3.8.4 scipy==1.13.0
python3 -m pip install https://github.com/ROCmSoftwarePlatform/jax/releases/download/jaxlib-v0.4.26/jaxlib-0.4.26+rocm610-cp310-cp310-manylinux2014_x86_64.whl
python3 -m pip install https://github.com/ROCmSoftwarePlatform/jax/archive/refs/tags/jaxlib-v0.4.26.tar.gz
pip install numpy==1.22.0
export XLA_FLAGS="--xla_gpu_autotune_level=0"

3. 使用以下命令从 ROCm/rocm-blogs GitHub 存储库下载用于该博客的文件。

git clone https://github.com/ROCm/rocm-blogs.git
cd rocm-blogs/blogs/artificial-intelligence/nanoGPT-JAX

4. 将`nanoGPT-JAX`文件夹中的`model.py`和`sample.py`脚本替换为当前博客在GitHub上*src*文件夹中的对应文件。具体参考此链接。

特别需要注意的是,对`model.py`文件的修改如下面代码块所示。新添加的两行代码使用`jax.named_scope`为两个矩阵乘法步骤注释唯一名称,这是一个将用户指定名称纳入JAX名称堆栈的上下文管理器。程序随后使用指定名称提取这些步骤的相关性能数据。该技巧对于快速将同类型操作的日志映射到应用程序或模型中的每个步骤非常宝贵,因为默认的日志名称可能会在同类型操作之间非常相似或令人困惑。下面的代码块封装了两个不同的矩阵乘法步骤,并分别为它们指派了不同的范围名称`attn_q_k`和`attn_att_v`。

class CausalSelfAttention(nn.Module):config: GPTConfig@nn.compactdef __call__(self, x, train=False, rng1=None, rng2=None):assert self.config.n_embd % self.config.n_head == 0B, T, C = x.shape # batch size, sequence length, embedding dimensionality (n_embd)# calculate query, key, values for all heads in batch and move head forward to be the batch dimq, k, v  = jnp.split(nn.Dense(self.config.n_embd * 3, name="c_attn")(x), 3, axis=-1)k = k.reshape(B, T, self.config.n_head, C // self.config.n_head).swapaxes(1, 2) # (B, nh, T, hs)q = q.reshape(B, T, self.config.n_head, C // self.config.n_head).swapaxes(1, 2) # (B, nh, T, hs)v = v.reshape(B, T, self.config.n_head, C // self.config.n_head).swapaxes(1, 2) # (B, nh, T, hs)
+       with jax.named_scope("attn_q_k"):
+           att = (jnp.einsum('bhts,bhqs->bhtq', q, k, optimize=True) if self.config.use_einsum else jnp.matmul(q, k.swapaxes(-2, -1))) * (1.0 / jnp.sqrt(k.shape[-1]))
-       att = (jnp.einsum('bhts,bhqs->bhtq', q, k, optimize=True) if self.config.use_einsum else jnp.matmul(q, k.swapaxes(-2, -1))) * (1.0 / jnp.sqrt(k.shape[-1]))mask = jnp.tril(jnp.ones((T, T))).reshape((1, 1, T, T))att = jnp.where(mask == 0, float('-inf'), att)att = nn.softmax(att, axis=-1)att = nn.Dropout(self.config.dropout, name='attn_dropout', deterministic=not train)(att, rng=rng1)
+       with jax.named_scope("attn_att_v"):
+           y = jnp.einsum('bhts,bhsq->bhtq', att, v, optimize=True) if self.config.use_einsum else jnp.matmul(att, v)   # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
-       y = jnp.einsum('bhts,bhsq->bhtq', att, v, optimize=True) if self.config.use_einsum else jnp.matmul(att, v)   # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)y = y.swapaxes(1, 2).reshape(B, T, C)  # re-assemble all head outputs side by side# output projectiony = nn.Dense(self.config.n_embd, name='c_proj')(y)y = nn.Dropout(self.config.dropout, name='resid_dropout', deterministic=not train)(y, rng=rng2)return y

对于`sample.py`文件的主要修改包括使用`jax.profiler.start_trace()`和`jax.profiler.stop_trace()`包裹负责运行基于JAX的GPT模型的推理的函数。这将记录每个生成样本的跟踪信息。或者,您可以使用`jax.profiler.trace()`上下文管理器来捕获跟踪,具体可参见本指南。每个样本的性能分析输出将存储在单独的文件夹中,这使得分析个别跟踪更为方便。

for i in range(num_samples): 
+   jax.profiler.start_trace(profile_dir+f'_{i}')output = generate([jnp.array(start_ids)], seed+i)
+   jax.profiler.stop_trace()print(f'\nGenerated output __{i}__: \n__________________________________\n{decode(output[0].tolist())}\n__________________________________')

使用不同的矩阵乘法算法对GPT模型进行性能分析

为了演示性能分析,本示例比较了`einsum`和`matmul`这两种在注意力计算步骤中执行矩阵乘法的内置方法。`use_einsum`标志控制了是选择`einsum`还是`matmul`进行矩阵乘法。运行以下命令以收集这两种不同算法的性能分析输出:

# Generate profiling output using matmul
python sample.py --init_from='gpt2' --max_new_tokens=50 --start="The weather today is" --num_samples=10 --profile_dir="trace_file_matmul"# Generate profiling output using einsum
python sample.py --init_from='gpt2' --max_new_tokens=50 --start="The weather today is" --num_samples=10 --profile_dir="trace_file_einsum" --override_args="{'use_einsum':True}"

每条命令调用`sample.py`文件生成10个样本,每个样本包含最多50个新生成的tokens。这会生成20个文件夹(每种算法10个文件夹,每个生成的样本一个文件夹),这些文件夹包含性能分析的输出。在每个文件夹中,性能分析输出存储在一个压缩的`.gz`文件中。在docker终端中运行以下命令来解压输出: 

for i in {0..9}; dogzip -d trace_file_einsum_$i/plugins/profile/202*/*.json.gzgzip -d trace_file_matmul_$i/plugins/profile/202*/*.json.gz
done

统计分析与两种算法的性能测试

现在,你可以读取剖析数据并进行统计分析。对于每个迭代(对应于每种算法生成的一个样本),程序比较两种算法在矩阵乘法执行时间(以纳秒为单位)分布上的差异。可以使用箱线图来直观地检查差异。Wilcoxon秩和检验(Mann-Whitney U检验)用来确定位置参数(如均值和中位数)是否显著不同。较短的执行时间表示更好的性能。

下面的代码块导入了分析所需的包,并定义了绘制箱线图的函数。

import glob
from perfetto.trace_processor import TraceProcessor
from scipy.stats import ranksums
import matplotlib.pyplot as pltdef plot_boxplot(df1, df2, columns1, columns2=None, df1_lab='matmul', df2_lab='einsum'):"""Plot boxplots for specified columns in two DataFrames. This function will be used to compare the distribution of running time for the two algorithmswe profiled.Args:df1 (pandas.DataFrame): First DataFrame.df2 (pandas.DataFrame): Second DataFrame.columns1 (list): List of column names from the first DataFrame to plot.columns2 (list): List of column names from the second DataFrame to plot.df1_lab (string): Label for df1 in the plot.df2_lab (string): Label for df2 in the plot."""if columns2 is None:columns2 = columns1# Combine data from both DataFramesdata = [df1[col] for col in columns1] + [df2[col] for col in columns2]# Create labels for boxplotslabels = [df1_lab + '_' + col for col in columns1] + [df2_lab + '_' + col for col in columns2]# Plot boxplotsplt.figure(figsize=(10, 6))plt.boxplot(data, labels=labels)plt.xlabel('Algorithms')plt.ylabel('Time in nanoseconds')plt.title('Performance comparison on the scale of nanoseconds')plt.xticks(rotation=45)plt.grid(True)plt.show()

程序随后比较了每次样本生成迭代中两种算法的执行时间。它在SQL查询中使用`where display_value like "%attn_q_k%"来过滤在第一个named_scope`中的操作。你可以修改SQL查询以探索不同的列并计算感兴趣的指标。

程序省略了第一次迭代,因为第一次迭代包括编译时间,这会使比较失真。它打印了每种算法的执行时间的均值和标准偏差,以及数据框的形状,以确保剖析器和SQL查询捕获了所有事件。例如,对于包含12层的模型,并且每个样本最多生成50个新的token(导致对模型的最多50次函数调用),应捕获最多`12*50=600`次矩阵乘法事件。

最后,程序打印了Wilcoxon秩和检验的统计量和p值,该检验评估两种算法的执行时间分布的位置参数(如均值和中位数)是否显著不同。尽管t检验广泛用于检验两种总体的均值是否相等,但由于样本中存在许多异常值,因此示例使用秩基非参数检验。这些异常值可能显著降低t检验的可靠性。

for i in range(1, 10):# Process the profiling data for matmultp = TraceProcessor(trace=glob.glob(f'trace_file_matmul_{i}/plugins/profile/202*/*.json'))# SQL query to get the operations enclosed by the named_scopequery_text='''INCLUDE PERFETTO MODULE slices.slices;WITH arg_sets_0 AS (SELECT DISTINCT arg_set_id, display_valueFROM argsWHERE key = 'args.name')SELECT name, display_value, durFROM _slice_with_thread_and_process_infoINNER JOIN arg_sets_0 ON arg_sets_0.arg_set_id = _slice_with_thread_and_process_info.arg_set_idwhere display_value like "%attn_q_k%"'''# Query the profiling data and convert to dataframeqr_matmul = tp.query(query_text).as_pandas_dataframe()# Process the profiling data for einsumtp = TraceProcessor(trace=glob.glob(f'trace_file_einsum_{i}/plugins/profile/202*/*.json'))# Query the profiling data and convert to dataframeqr_einsum = tp.query(query_text).as_pandas_dataframe()print(f'###########i={i}###########')print('#'*30)# Print out the mean, standard dev. and shape for each algorithmprint(f'Matmul: Mean={qr_matmul.dur.mean()}, std. dev.={qr_matmul.dur.std()}, shape of df:{qr_matmul.shape}')print(f'Einsum: Mean={qr_einsum.dur.mean()}, std. dev.={qr_einsum.dur.std()}, shape of df:{qr_einsum.shape}')plot_boxplot(qr_matmul, qr_einsum, ['dur'])stat, p = ranksums(qr_matmul['dur'], qr_einsum['dur'])print(f'Test statistic={stat}, p_val={p}')

下面是两次迭代的截断输出,所有九次迭代都观察到了相同的模式。

###########i=1###########
##############################
Matmul: Mean=6461.875, std. dev.=504.8818364954699, shape of df:(600, 3)
Einsum: Mean=5813.346666666666, std. dev.=455.80420754410954, shape of df:(600, 3)
Test statistic=20.22982266255362, p_val=5.349499343834845e-91

###########i=2###########
##############################
Matmul: Mean=6293.076666666667, std. dev.=514.1309448993132, shape of df:(600, 3)
Einsum: Mean=5797.615, std. dev.=397.86885546863283, shape of df:(600, 3)
Test statistic=16.932946075063718, p_val=2.5717953759559878e-64

基于结果,可以明显看出,对于矩阵乘法算法`einsum`比`matmul`在计算`query`和`key`矩阵之间的矩阵乘法时显著更快。但对于在`attention`和`value`矩阵之间的矩阵乘法时,`matmul`如何表现呢?结果显示在下面的代码块中:

for i in range(1, 10):# Process the profiling data for matmultp = TraceProcessor(trace=glob.glob(f'trace_file_matmul_{i}/plugins/profile/202*/*.json'))# SQL query to get the operations enclosed by the named_scopequery_text='''INCLUDE PERFETTO MODULE slices.slices;WITH arg_sets_0 AS (SELECT DISTINCT arg_set_id, display_valueFROM argsWHERE key = 'args.name')SELECT name, display_value,durFROM _slice_with_thread_and_process_infoINNER JOIN arg_sets_0 ON arg_sets_0.arg_set_id = _slice_with_thread_and_process_info.arg_set_idwhere display_value like "%attn_att_v%"'''# Query the profiling data and convert to dataframeqr_matmul = tp.query(query_text).as_pandas_dataframe()# Process the profiling data for einsumtp = TraceProcessor(trace=glob.glob(f'trace_file_einsum_{i}/plugins/profile/202*/*.json'))# Query the profiling data and convert to dataframeqr_einsum = tp.query(query_text).as_pandas_dataframe()print(f'###########i={i}###########')print('#'*30)# Print out the mean, standard dev. and shape for each algorithmprint(f'Matmul: Mean={qr_matmul.dur.mean()}, std. dev.={qr_matmul.dur.std()}, shape of df:{qr_matmul.shape}')print(f'Einsum: Mean={qr_einsum.dur.mean()}, std. dev.={qr_einsum.dur.std()}, shape of df:{qr_einsum.shape}')plot_boxplot(qr_matmul, qr_einsum, ['dur'])stat, p = ranksums(qr_matmul['dur'], qr_einsum['dur'])print(f'Test statistic={stat}, p_val={p}')

下面是两次迭代的截断输出,所有九次迭代都观察到了相同的模式。

###########i=1###########
##############################
Matmul: Mean=5204.543333333333, std. dev.=882.6151202759834, shape of df:(600, 3)
Einsum: Mean=6360.556666666666, std. dev.=373.461514250933, shape of df:(600, 3)
Test statistic=-21.986424230986046, p_val=3.884153635651101e-107

###########i=2###########
##############################
Matmul: Mean=5145.61, std. dev.=876.5247080600369, shape of df:(600, 3)
Einsum: Mean=6396.01, std. dev.=381.7892458942073, shape of df:(600, 3)
Test statistic=-22.450480914300588, p_val=1.2659476932444539e-111

这次,令人惊讶的是,`matmul`显著比`einsum`更快。这表明一种矩阵乘法算法并不总是优于另一种。矩阵的大小、形状和其他操作(如矩阵转置)等因素可能会影响速度。这突显了在应用或模型关键步骤中选择最佳算法时使用剖析技术的重要性。另外,如果你检查同一算法在箱线图中的数据点范围,可能会注意到许多异常值。这就是为什么在得出有效结论时,统计分析和适当的方法是如此重要的原因。本例中也使用了秩基检验而非经典的t检验,因为后者通常对异常值敏感。

总结

在剖析应用或模型性能时应应用稳健的统计分析和测试,以确保随机噪声的影响不会损害我们结论的有效性。 

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

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

相关文章

35岁程序员的4条出路!请提早布局!

小编准备入门了Python入门学习籽料80个Python爬虫实战入门实例 点击 领取(无偿获得) 20多岁,初入职场,满腔热血,对未来充满憧憬; 30多岁,家庭事业双重压力,开始迷茫,对…

阿里云发送短信功能(Java)

(1)注册用户,并且开通短信套餐 (2) 点击快速学习,然后绑定测试的手机号码。 选用专用测试签名(自定义的话阿里可能会验证什么什么的比较麻烦) 然后在选取调用API (3&…

3秒AI写真出图,Stable Diffusion2024升级版+使用教程来了!(无需安装,解压即用)

要说今年摄影圈最大的新秀 那妥妥的就Stable Diffusion 比如下面的写真照片 你敢信这是SD绘画生成的? 就在刚刚它又全面升级了 新版无需安装,直接解压就能用 比之前推送的更加智能、快速和简单 另外还特意为大家准备了 Stable Diffusion 人工智能…

故障诊断 | 基于小波时频图与Swin Transformer的轴承故障诊断方法(PyTorch)

文章目录 文章概述程序设计参考资料文章概述 基于小波时频图与Swin Transformer的轴承故障诊断方法 针对用传统的故障诊断方法难以对非线性非平稳的柴油机故障信号进行准确高效诊断的问题, 提出基于小波时频图与Swin Transformer的故障诊断方法。该方法可以有效结合小波时频分…

Git实战精粹

一、快速入门 1. 什么是Git Git是一个分布式的版本控制软件。 软件,类似于QQ、office、dota等安装到电脑上才能使用的工具版本控制,类似于毕业论文、写文案、视频剪辑等,需要反复修改和保留原历史数据分布式 文件夹拷贝本地版本控制集中式…

如何在Java中使用protobuf

写在前面 本文看下在Java中如何使用protofbuf。 1:介绍 1.1:什么是protobuf 是一种数据格式,同json,xml,等。但是一种二进制数据格式。 1.2:强在哪里?为啥要用? 小&#xff0c…

JS中this的指向问题、JS的执行机制、offset、client、scroll

JS中this的指向问题 1. 在全局环境下 在全局环境中(在浏览器中是 window 对象,在Node.js中是 global 对象),this 指向全局对象。 console.log(this window); // 在浏览器中为 true console.log(this.document ! undefined); //…

如何练高音技巧

如何练高音技巧 高音的练习技巧有:练“a”(啊)音,让口腔打开,声带放松,反复几次;再练“u”(呜)音,这个音可以有按摩声带的功能,也使声带进一步放松;发“i”(衣)音,逐步加…

k8s 四种Service类型(ClusterIP、NodePort、LoadBalancer、ExternalName)详解

🐇明明跟你说过:个人主页 🏅个人专栏:《Kubernetes航线图:从船长到K8s掌舵者》 🏅 🔖行路有良友,便是天堂🔖 目录 一、引言 1、k8s概述 2、Service在Kubernetes中的…

大模型本地化部署2-Docker部署MaxKB

大模型本地化部署2-Docker部署MaxKB 0、MaxKB简介1、安装docker2、在docker中拉取MaxKB镜像3、运行镜像4、访问MaxKB5、创建应用6、使用应用进行对话 0、MaxKB简介 MaxKB是一款基于LLM大预言模型的知识库问答系统。具有以下特点: 多模型支持:支持对接主…

开放式耳机怎么戴?佩戴舒适在线的几款开放式耳机分享

开放式耳机的佩戴方式与传统的入耳式耳机有所不同,它采用了一种挂耳式的设计,提供了一种新颖的佩戴体验,以下是开放式耳机的佩戴方式。 1. 开箱及外观:首先,从包装盒中取出耳机及其配件,包括耳机本体、充电…

什么是密码学?

什么是密码学? 密码学是一种通过使用编码算法、哈希和签名来保护信息的实践。此信息可以处于静态(例如硬盘驱动器上的文件)、传输中(例如两方或多方之间交换的电子通信)或使用中(在对数据进行计算时&#…

设计模式-结构性模式-桥接模式

1.桥接模式定义 桥接模式就是将抽象部分与他的实现部分分离,使他们都可以独立的变化; 桥接模式用一种巧妙地方式处理多层继承存在的问题,用抽象关联来取代传统的多层继承,将类之间的静态继承关系转变为动态的组合关系,…

Leetcode JAVA刷刷站(99)恢复二叉搜索树

一、题目概述 二、思路方向 要解决这个问题,我们可以采用中序遍历二叉搜索树(BST)的方法,因为中序遍历BST会返回一个有序的数组。由于只有两个节点被错误地交换了,所以中序遍历的结果中将有两个位置上的元素是逆序的。…

什么是数据库 DevOps?

原文地址 https://www.bytebase.com/blog/what-is-database-devops/ 在深入研究数据库 DevOps 之前,先回顾一下什么是 DevOps。它没有统一的定义,但我们知道它起源于软件开发方法与部署和运维的结合。 大约 2007 年和 2008 年,软件开发和 I…

.NET8 Web 利用BAT命令 一键部署 IIS - CI-CD基础

1. Windows Server 前置准备 1.1 IIS安装好 1.2 .NET8 Sdk 运行时 安装 官方下载地址:https://dotnet.microsoft.com/zh-cn/download/dotnet/8.0 1.3 创建一个.NET8 WebMvc项目 生成发布包 微软MVC这个项目模板直接创建,发布 2. 利用 BAT 来一键部署…

【STM32】FMC

FMC功能与FSMC类似,但比FSMC更强大,但仅在F4 / F7 / H7等高级一点的MCU上支持,F1不支持。虽然我的是F103,但顺便都看了。 大部分图片来源:正点原子HAL库课程 专栏目录:记录自己的嵌入式学习之路-CSDN博客 目…

Vue 和 Element Plus 弹框组件详解:从基本实现到异步数据加载与自定义内容(实战)

目录 前言1. 基本知识2. 模版3. 实战 前言 主要是通过一个按钮触发一个按钮框,多种方式的逻辑,多种场景 原先通过实战总结,基本的知识推荐阅读: 详细分析Element Plus中的ElMessageBox弹窗用法(附Demo及模版&#x…

秋招复习笔记——嵌入式裸机开发

底层相关的内容,之前掌握的不扎实,现在重新把相关重点记录一下,做个笔记记诵。 相关基础知识 ST简单内容 用的F103ZET6,72MHz,FLASH是512KB,SRAM是64KB,144个引脚,2基本定时器&am…

目标 CDC实例数据库更改密码,预定启动报错SQL 错误代码为“-30082”。SQL 状态为:08001。

更改完CDC目标端实例密码后,登录MC更新存储器密码,存储器可正常连接,启动预定报错如下: 源 IBM Data Replication 未获授权,无法复制到该目标。 登录认证失败。 发生 SQL 异常。SQL 错误代码为“-30082”。SQL 状态…