Accurate INT8 Training Through Dynamic Block-Level Fallback
TL;DR 精炼摘要
本文针对现代Transformer模型(含GLU单元)在INT8低精度训练中,因激活值复杂离群点导致精度下降的挑战,提出了一种“回退量化”方法。该方法采用混合精度通用矩阵乘法(GEMM),能动态地为包含离群点的激活块从8位回退到16位精度计算。实验证明,此方案在微调和预训练场景下均能保持稳健的训练精度,并在RTX4090 GPU上实现了1.57倍的端到端训练加速,有效提升了训练效率。
摘要
Transformer models have achieved remarkable success across various AI applications but face significant training costs. Low-bit training, such as INT8 training, can leverage computational units with higher throughput, and has already demonstrated its effectiveness on GPT2 models with block-level quantization. However, it struggles with modern Transformer variants incorporating GLU units. This is because those variants demonstrate complex distributions of activation outliers. To address the challenge, we propose Fallback Quantization, implementing mixed-precision GEMM that dynamically falls back 8-bit to 16-bit for activation blocks containing outliers. Experiments show that our approach is robustly competent in both fine-tuning and pretraining settings. Moreover, our method achieves a 1.57x end-to-end training speedup on RTX4090 GPUs.
思维导图
论文精读
中文精读
1. 论文基本信息 (Bibliographic Information)
- 标题 (Title): Accurate INT8 Training Through Dynamic Block-Level Fallback (通过动态块级回退实现精确的 INT8 训练)
- 作者 (Authors): Pengle Zhang, Jia Wei, Jintao Zhang, Jun Zhu, Jianfei Chen
- 发表期刊/会议 (Journal/Conference): 本文目前为预印本 (Preprint),发布于 arXiv。arXiv 是一个广泛使用的学术论文预印本平台,通常用于在正式同行评审前快速分享研究成果。
- 发表年份 (Publication Year): 2024 (根据 arXiv 提交日期推断)
- 摘要 (Abstract): Transformer 模型在人工智能应用中取得了巨大成功,但训练成本高昂。INT8 等低比特训练方法可以利用吞吐量更高的计算单元,并在 GPT2 模型上通过块级量化证明了其有效性。然而,这种方法在处理包含门控线性单元 (GLU) 的现代 Transformer 变体时遇到了困难,因为这些模型的激活值离群点 (outliers) 分布更为复杂。为应对此挑战,论文提出了一种名为 回退量化 (Fallback Quantization) 的方法,它实现了一种混合精度通用矩阵乘法 (GEMM),能够为包含离群点的激活块动态地从 8-bit 回退到 16-bit 精度。实验表明,该方法在微调和预训练场景下都表现稳健,并在 RTX4090 GPU 上实现了 1.57 倍的端到端训练加速。
- 原文链接 (Source Link):
- arXiv 页面: https://arxiv.org/abs/2503.08040v3
- PDF 链接: http://arxiv.org/pdf/2503.08040v3
- 发布状态:预印本 (Preprint)
2. 整体概括 (Executive Summary)
-
研究背景与动机 (Background & Motivation - Why):
- 核心问题: 训练现代大型 Transformer 模型(如 Llama、Qwen)的计算成本极高。使用 INT8 这样的低精度数据格式进行训练是降低成本、提升速度的有效途径。
- 现有挑战 (Gap): 现有的 INT8 训练方法在处理较早的 GPT-2 等模型时表现尚可,但无法很好地适应包含
GLU(Gated Linear Unit) 结构的现代 Transformer。这是因为GLU单元的乘法操作会放大激活值,产生量级极大且分布复杂的离群点 (outliers)。这些离群点会严重破坏 INT8 量化的精度,导致训练不稳定甚至失败。 - 切入点/创新思路: 论文观察到,这些破坏性的离群点在激活矩阵中虽然量级大,但分布是稀疏的,通常集中在少数几个量化块内。因此,没有必要对整个模型或整个层使用高精度,而是可以“精确打击”——只对那些包含离群点的特定块动态地提升精度,而其他大部分块仍然使用高效的 INT8 计算。
-
核心贡献/主要发现 (Main Contribution/Findings - What):
- 提出
Fallback Quantization(回退量化) 方法: 这是一种新颖的动态混合精度训练方案。它在块级量化的基础上,能够自动检测包含离群点的激活块,并对这些块采用一种等效于 16-bit 的精度表示(通过两次 INT8 量化实现),而其他块则保持 INT8。 - 首次实现对现代 GLU 模型的无损 INT8 训练: 实验证明,该方法成功地在 Llama-3.1 和 Qwen-2.5 等先进模型上完成了微调和预训练任务,其训练曲线和最终精度与高精度的 BF16 基线几乎完全重叠,解决了现有 INT8 方法的瓶颈。
- 实现显著的端到端训练加速: 论文不仅提出了算法,还实现了高效的 CUDA 核函数。最终,该方法在消费级 RTX4090 GPU 上实现了高达 1.57 倍的端到端训练加速,并减少了 38% 的激活值内存占用。
- 提出
3. 预备知识与相关工作 (Prerequisite Knowledge & Related Work)
-
基础概念 (Foundational Concepts):
- 低精度训练 (Low-Precision Training): 指在神经网络训练过程中,使用比标准的 32-bit 浮点数 (FP32) 更低位数的数据格式,如
BF16(16-bit)、FP8(8-bit) 或INT8(8-bit 整数)。其主要目的是利用现代 GPU 中为低精度计算设计的专用硬件单元(如 NVIDIA Tensor Cores),以获得更高的计算吞吐量(速度)和更低的内存占用。 - 量化 (Quantization): 将连续或取值范围大的浮点数映射到离散且取值范围小的整数的过程。在神经网络中,通常通过一个缩放因子 (scale) 将浮点数张量缩放到一个目标整数范围内(例如,INT8 的 [-127, 127])。
- 激活离群点 (Activation Outliers): 在神经网络的激活值中,出现的一些数值远大于其他绝大多数数值的现象。这些离群点对量化是致命的,因为量化的缩放因子由最大绝对值决定,一个巨大的离群点会使得绝大多数正常数值被量化到接近于 0,从而丢失大量信息。
GLU(Gated Linear Unit,门控线性单元): 一种常用于现代 Transformer 的激活结构,其计算方式为 ,其中 和 是来自前一线性层的输出, 是 Sigmoid 或类似的激活函数。由于两个激活值相乘,它有可能会产生比常规激活函数(如 ReLU)更大范围的输出值,从而加剧了离群点问题。- 分组量化 (Group Quantization): 一种细粒度的量化策略。它不是对整个张量使用一个缩放因子,而是将张量划分为多个小组(或块,block),并为每个小组计算独立的缩放因子。这使得量化能更好地适应张量内部数值分布不均匀的情况。根据分组方式的不同,可以分为:
-
per-tensor: 整个张量一个缩放因子。 -
per-token: 每行(代表一个 token)一个缩放因子。 -
per-channel: 每列(代表一个通道)一个缩放因子。 -
per-block: 将张量划分为固定大小的二维块,每块一个缩放因子。
图1:(a) 展示了不同粒度的分组量化,包括 per-tensor、per-token 和 per-block。(b) 展示了在 RTX4090 上,INT8 GEMM 的计算性能 (TFLOPS) 随着分组大小 (Group Size K) 的增加而提升,说明更大的分组/块尺寸有利于提升硬件效率。
-
- 低精度训练 (Low-Precision Training): 指在神经网络训练过程中,使用比标准的 32-bit 浮点数 (FP32) 更低位数的数据格式,如
-
前人工作 (Previous Works):
Switchback: 采用per-token和per-channel的量化粒度。这种方法可以处理某些方向上的离群点,但无法解决现代 LLM 中同时存在于 token 和 channel 维度的复杂离群点模式。Jetfire: 提出per-block量化(使用 的小块),能更灵活地隔离离群点。但其主要问题是:1) 块太小导致计算和访存开销增大,实际加速效果有限;2) 仅在 GPT-2 这类非 GLU 模型上进行了验证,其有效性在现代模型上存疑。LLM.int8(): 一种用于推理 (inference) 的混合精度方法,它将离群点保持为 FP16 格式。但这种方法依赖于特定的数据排布和计算方式,不适用于训练过程中数值动态变化的场景。
-
技术演进 (Technological Evolution): 神经网络训练的精度演进路线大致为:
FP32(标准精度) →混合精度训练(FP16/BF16,首次大规模应用低精度加速) →全量化训练 (FQT)(FP8/INT8,追求极致的硬件效率)。本文的工作属于 FQT 领域,专注于解决 INT8 训练在现代架构上的瓶颈。 -
差异化分析 (Differentiation): 本文方法与之前工作的核心区别在于动态性和适应性。
- 与
Jetfire相比,Jetfire对所有数据块一视同仁地使用 INT8,即使块内有巨大离群点,也只能硬着头皮量化,导致精度损失。而本文方法能动态识别这些“问题块”,并为其“回退”到更高精度,从而保证准确性。同时,通过使用更大的块尺寸 (128x128),获得了比Jetfire更高的计算效率。 - 与
LLM.int8()相比,本文方法是为训练 (training) 设计的,能够处理动态变化的激活值,而LLM.int8()仅适用于静态的、已训练好的模型推理。
- 与
4. 方法论 (Methodology - Core Technology & Implementation Details)
-
方法原理 (Methodology Principles):
-
对 GLU 激活离群点的观察: 论文首先分析了 Llama-3.1 和 Qwen-2.5 等现代 GLU 模型的激活值分布,总结出三个关键特性 (P1, P2, P3):
-
离群点量级极大 (P1):
GLU的乘法效应使得离群点数值可达数千,远超 INT8 的表达范围。 -
离群点模式复杂 (P2): 离群点不仅呈现在特定的行 (token) 或列 (channel),还会随机出现在其他位置。
-
离群点分布稀疏 (P3): 即使在包含离群点的行或列中,绝大多数数值仍然是正常的,只有极少数元素是离群点。
图2:该图展示了 GLU 结构对激活值分布的影响。(a) 和 (b) 表明 GLU 激活值的分布更宽,离群点更显著。(c) 的热力图直观地显示,最终的激活输出 y 中的高亮区域(离群点)非常稀疏。
-
-
核心思想: 基于离群点稀疏的特性,我们可以只为包含离群点的少数数据块付出额外精度代价,而对大多数数据块仍使用高效的 INT8。这种思想催生了动态块级回退量化 (Dynamic Block-Level Fallback)。
-
-
方法步骤与流程 (Steps & Procedures): 该方法的核心是
Fallback Quantization,一个巧妙的两步量化过程,用于对检测到的离群点块进行高精度表示。
该图像包含三部分:(a)为示意图,展示了“回退量化”(Fallback Quantization)过程,原始矩阵 先量化为 ,然后对包含激活异常值的子矩阵 采用16位精度回退量化,最终加和得到更精确的量化结果;(b)为折线图,展示了不同量化位数下,回退方法和普通量化的RMSE误差随量化位数变化的趋势,回退方法在低位宽表现更优;(c)也是折线图,显示了不同回退率下模型输出余弦相似度(CosSim),包括Amax、L1、L1-Rel三种方法,随着回退率增加相似度提升。
图3:(a) 详细展示了回退量化的过程。对于一个包含离群点(如 157.00)的块 ,常规 INT8 量化 会导致大部分非离群点信息丢失(被量化为0)。回退量化首先进行一次常规量化,然后计算残差 ,并对残差再进行一次量化 。最终的表示是这两部分的和,能同时保留离群点和非离群点的信息。(b) 表明回退量化在相同等效比特数下比标准的高位宽量化(如 INT16)误差 (RMSE) 更低。(c) 表明使用块内最大绝对值 (AbsMax) 作为回退判据效果最好。-
第一步:基础量化。 对一个待处理的块 ,首先进行一次标准的 INT8 分组量化,得到 。这一步会捕捉到块内的主要数值分布,但由于离群点的存在,精度可能不高。
-
第二步:残差量化。 计算原始块与第一次量化结果之间的残差:。然后,对这个残差块再进行一次 INT8 分组量化,得到 。
最终,原始块 就被近似表示为这两个 INT8 量化块的和:。这个组合等效于一个 16-bit 的表示,但它完全由 INT8 计算单元来处理,硬件兼容性好。
-
-
数学公式与关键细节 (Mathematical Formulas & Key Details):
-
标准块级量化 GEMM: 对于矩阵乘法 ,
Jetfire等方法将其分解为块的累加。每个输出块 的计算如下: 符号解释:- 分别是矩阵
A, B, C的子块。 - 表示量化操作,将浮点数块转换为 INT8 整数块。
- 是对应块的量化缩放因子 (scale)。
- 表示使用 INT8 硬件执行的整数矩阵乘法。
- 分别是矩阵
-
带回退量化的 GEMM (本文方法): 论文提出的方法修改了上述公式,只对矩阵 (通常是激活矩阵)引入回退机制: 符号解释:
- 表示量化-反量化操作,结果仍是浮点数,但其内在精度由 INT8 决定。
- 是一个回退指示符。如果块 被检测为离群点块,则 ,此时会额外计算残差项;否则 。
- 是对残差块进行量化-反量化的结果。
-
动态回退阈值 (Dynamic Fallback Threshold): 如何确定 ?
-
判据: 论文实验发现,直接使用块内元素的最大绝对值
AbsMax() 作为判断依据效果最好且计算开销最小。 -
动态调整: 为了避免全局同步
TopK带来的性能开销,论文采用了一种Delay Threshold机制。每个线性层维护一个独立的阈值 。如果在一次迭代中,该层的回退块比例低于预设下限 ,就降低阈值 (除以 );如果高于上限 ,就提高阈值 (乘以 )。这使得回退比例能动态地保持在一个合理范围内。
图4:(a) 可视化了 Qwen-2.5-3B 模型中一个线性层输入激活的回退块分布(黑色为回退块)。可见回退块既有沿通道(纵向)的规律性,也有零散分布,验证了动态检测的必要性。(b) 表明,使用了回退量化后,即使采用 128 甚至 256 的大块尺寸,模型困惑度 (Perplexity) 也能保持在较低水平,甚至优于使用 32 的小块尺寸的朴素块量化。这为使用大块尺寸提升性能提供了依据。
-
-
训练系统设计 (Training System Design):
-
线性层 (Linear Layer):
-
只对激活矩阵 的前向计算 使用回退量化。
-
对于反向传播中的梯度矩阵 和用于计算权重梯度的 (),则使用随机取整 (stochastic rounding) 的标准 INT8 块量化,这样可以简化激活值的保存(只需保存 INT8 格式)并提升效率。实验证明这种简化对精度影响不大。
图5:(a) 显示,在使用随机取整处理 的情况下,梯度误差主要来源于对 的量化。(b) 显示,只在前向传播对 使用回退(红色曲线),与在前后向都使用回退(蓝色曲线)相比,梯度余弦相似度几乎没有差异,证明了简化的合理性。
-
-
非线性层 (Non-Linear Layer):
-
如
LayerNorm、SiLU等操作对量化误差非常敏感,且其计算开销在大型模型中占比较小。 -
为了平衡精度和内存,论文对这些层的激活值采用
per-token的 10-bit 整数压缩存储,在反向传播时再解压回 BF16 进行计算。这既保证了计算精度,又显著降低了激活值内存占用。
图6:(a) 显示非线性层(红/粉色线)对量化位宽比线性层(蓝/青色线)敏感得多,低位宽下困惑度急剧上升。(b) 显示随着模型尺寸增大,线性层的计算时间占比(绿线)越来越高,因此加速线性层是优化的重点。
-
-
-
5. 实验设置 (Experimental Setup)
-
数据集 (Datasets):
- 微调 (Fine-tuning):
GSM8K: 数学应用题数据集,评估模型的推理能力。DROP: 阅读理解数据集,需要对段落进行离散推理。MMLU: 综合性多任务语言理解基准,覆盖 57 个不同领域的任务。HELLASWAG: 常识推理数据集,任务是完成句子。
- 预训练 (Pre-training):
OpenWebText: 一个大规模、高质量的英文网络文本语料库,常用于语言模型的从头预训练。
- 微调 (Fine-tuning):
-
评估指标 (Evaluation Metrics):
-
准确率 (Accuracy, Acc):
- 概念定义: 衡量分类任务中模型预测正确的样本占总样本数量的比例。它是最直观的性能评估指标,数值越高表示模型性能越好。
- 数学公式:
- 符号解释:
Number of Correct Predictions: 模型预测结果与真实标签相符的样本数量。Total Number of Predictions: 测试集中的总样本数量。
-
F1 分数 (F1 Score):
- 概念定义: 用于评估二分类或多分类模型性能的指标,是精确率 (Precision) 和召回率 (Recall) 的调和平均数。它在处理类别不平衡的数据集时比准确率更具参考价值,因为它同时考虑了模型的查准率和查全率。
- 数学公式:
- 符号解释:
- (精确率): 预测为正类的样本中,实际也为正类的比例。
- (召回率): 实际为正类的样本中,被成功预测为正类的比例。
TP(True Positive): 真正例;FP(False Positive): 假正例;FN(False Negative): 假负例。
-
困惑度 (Perplexity, PPL):
- 概念定义: 语言模型中用于衡量模型预测一个样本序列的好坏程度的指标。它的直观含义是模型对下一个词预测的不确定性。困惑度越低,表示模型对样本的概率分布预测得越准确,模型性能越好。
- 数学公式: 对于一个词序列 ,其困惑度计算公式为:
- 符号解释:
- : 序列的总长度。
- : 模型在给定前
i-1个词的条件下,预测第 个词为 的概率。
-
-
对比基线 (Baselines):
BF16: 使用 Brain Floating Point 16-bit 格式进行训练,作为高精度训练的基准 (golden standard)。Block: 只在线性层中使用朴素的 INT8 块量化 GEMM,不带回退机制,非线性层保持 BF16。Jetfire: 先前最先进的 INT8 训练方法,采用 块量化,并在整个数据流(包括线性和非线性层)中使用 INT8。
6. 实验结果与分析
-
核心结果分析 (Core Results Analysis):
-
微调性能 (Fine-tuning):
-
注意: 此表格为根据原文数据转录,非原始图像。
Model Method GSM8K(Acc) DROP(F1) MMLU(Acc) HELLASWAG(Acc) CAL-FLOPS(T) ACT-MEM(GB) Qwen2.5-1.5B BF16 0.522 0.644 0.560 0.908 112.37 3.92 Block 0.005 0.651 0.561 0.905 158.07 3.20 Jetfire 0.441 0.636 0.545 0.289 - 2.08 Ours 0.511 0.636 0.553 0.901 154.81(1.38x) 2.39(61%) Qwen2.5-3B BF16 0.590 0.655 0.611 0.928 125.23 4.91 Block 0.585 0.672 0.607 0.928 197.87 4.00 Jetfire 0.609 0.692 0.602 0.922 - 2.61 Ours 0.584 0.671 0.601 0.929 186.87(1.49x) 2.99(61%) Llama-3.2-1B BF16 0.265 0.521 0.420 0.817 118.15 4.07 Block 0.265 0.527 0.390 0.828 168.44 3.30 Jetfire 0.242 0.516 0.402 0.825 - 2.21 Ours 0.255 0.526 0.422 0.831 164.78(1.39x) 2.52(62%) Llama-3.1-8B BF16 0.475 0.597 0.520 0.910 135.31 3.74 Block 0.475 0.607 0.525 0.905 216.36 3.03 Jetfire 0.479 0.618 0.525 0.910 - 2.05 Ours 0.493 0.589 0.524 0.913 212.01(1.57x) 2.32(62%)
从上表可以看出:
- 本文方法 (
Ours) 表现稳健: 在所有模型和所有任务上,本文方法的性能都与BF16基线非常接近,证明了其无损的准确性。 - 其他 INT8 方法存在问题:
Block方法在 Qwen2.5-1.5B 的 GSM8K 任务上完全崩溃 (准确率 0.005),显示出朴素块量化的不稳定性。Jetfire在小模型上的性能也出现了明显下降,这归因于其 INT8 数据流对敏感的非线性层处理不当。 - 显著的加速和内存节省: 本文方法在 Llama-3.1-8B 上实现了 1.57 倍的训练吞吐量提升 (
CAL-FLOPS(T)),同时将激活值内存 (ACT-MEM) 降低到BF16的 62% 左右。
-
-
预训练性能 (Pre-training):
图7:(a) 显示了非线性层的激活值压缩到 10-bit 时,梯度余弦相似度已接近饱和,证明 10-bit 是一个很好的平衡点。(b) 展示了 Llama-1.5B 的预训练损失曲线。本文方法 (Ours) 的曲线与 BF16 高度重合,而 Jetfire 早期就出现显著偏差,验证损失很高(论文附录 E 指出这可能是信息泄露问题导致训练集过拟合)。(c) 和 (d) 分别是 Attention 和 MLP 模块的详细数据流图,展示了各部分采用的量化策略。
-
-
消融实验/参数分析 (Ablation Studies / Parameter Analysis):
图8:(a) 展示了在 Qwen2.5-1.5B 的 GSM8K 微调任务上,Block方法在不同随机种子下表现极不稳定(绿色和蓝色线),而本文方法 (Ours) 则始终保持稳定收敛(红色线)。(b) 进一步展示了本文方法在不同回退率下的表现,即使只有 2.5% 的回退率也能收敛,10% 的回退率即可达到非常稳定的训练效果。(c) 展示了回退 GEMM 核的性能。即使在最差情况下(需要回退的块集中在一起),性能也只是略有下降,且远高于Jetfire的峰值性能(灰色虚线)。-
稳定性: 图 8(a) 表明,本文方法解决了朴素块量化在某些任务上训练不稳定的问题。
-
回退率影响: 图 8(b) 表明,不需要很高的回退率就能保证训练稳定,10%-30% 的范围是比较理想的。
-
性能分析: 图 8(c) 显示,回退机制带来的性能开销很小。这是因为回退块的计算可以和非回退块的计算在 GPU 内部进行调度和重叠,避免了成为瓶颈。
-
硬件通用性: 附录中的图 9 显示,该方法在 3090、L20 和 A800 等不同架构的 GPU 上均能取得显著的加速效果。
图9:展示了回退 GEMM 核函数在 3090、L20 和 A800 GPU 上的吞吐量表现,证明了该方法的广泛适用性。
-
7. 总结与思考 (Conclusion & Personal Thoughts)
-
结论总结 (Conclusion Summary): 本文成功地解决了现代 GLU 结构 Transformer 模型难以进行 INT8 训练的核心痛点——激活值离群点。通过提出一种创新的动态块级回退量化 (Fallback Quantization) 方法,并实现高效的硬件核函数,论文首次在 Llama 和 Qwen 等先进模型上实现了与 BF16 精度持平的 INT8 训练,同时带来了显著的端到端加速 (最高 1.57x) 和内存节省。这项工作为在更广泛的硬件上进行经济高效的大模型训练铺平了道路。
-
局限性与未来工作 (Limitations & Future Work):
- 论文中提到,对于
Flash Attention这种高度融合的算子,目前仍保持 BF16 精度,因为对其进行量化需要更复杂的分析。未来的工作可以探索如何将这种动态回退量化的思想扩展到Flash Attention或其他复杂的融合算子中。 - 该方法主要针对激活矩阵 进行回退,而对梯度矩阵 采用的是随机取整。虽然实验证明可行,但探索对梯度也采用自适应策略可能是一个潜在的优化方向。
- 论文中提到,对于
-
个人启发与批判 (Personal Insights & Critique):
- 启发: 这篇论文最亮眼的启发是“不要一刀切,要精确打击”。在系统优化中,我们常常面临精度与效率的权衡。本文的方法表明,通过对问题的深入分析(发现离群点的稀疏性),我们可以设计出一种动态的、自适应的策略,只在最关键的地方付出精度代价,从而在全局上实现效率和精度的双赢。这种“80/20”法则的思想在很多其他领域也同样适用。
- 方法的巧妙之处: 使用两次 INT8 量化来模拟 16-bit 精度是一个非常聪明的工程实践。它避免了引入真正的 INT16 或 FP16 计算路径,从而简化了硬件核的设计,并最大限度地利用了硬件的 INT8 计算能力,这是该方法能取得良好性能的关键。
- 潜在问题与思考:
-
Delay Threshold机制虽然有效,但引入了新的超参数(如 ),这可能会增加调参的复杂性。虽然论文给出了经验值,但在不同模型或任务上是否需要重新调整,还有待观察。 -
该方法的效果高度依赖于离群点的“稀疏性”这一先验假设。如果未来出现新的网络结构,其离群点分布变得密集,那么该方法的优势(低回退率)可能会减弱,性能开销会随之增大。
-
论文附录中对
Jetfire的“信息泄露”问题的分析非常有趣。这提醒我们,在设计量化算法时,需要警惕量化过程本身(如计算缩放因子)是否会无意中将未来信息泄露给当前步骤,从而导致训练与评估的不一致。
-
相似论文推荐
基于向量语义检索推荐的相关论文。