从 FlashAttention-1 到 FlashAttention-3:矩阵乘法如何砍掉 87% 访存

举报
江南清风起 发表于 2026/01/08 15:01:41 2026/01/08
【摘要】 从 FlashAttention-1 到 FlashAttention-3:矩阵乘法如何砍掉 87% 访存 引言:注意力机制的内存瓶颈在Transformer架构中,注意力计算的时间和内存复杂度与序列长度的平方成正比,这成为大语言模型处理长上下文的主要瓶颈。传统注意力机制需要将大小为 O(N2)O(N^2)O(N2) 的注意力矩阵存储到内存中,其中N是序列长度。当序列长度达到数万甚至数十万...

从 FlashAttention-1 到 FlashAttention-3:矩阵乘法如何砍掉 87% 访存

引言:注意力机制的内存瓶颈

在Transformer架构中,注意力计算的时间和内存复杂度与序列长度的平方成正比,这成为大语言模型处理长上下文的主要瓶颈。传统注意力机制需要将大小为 O(N2)O(N^2) 的注意力矩阵存储到内存中,其中N是序列长度。当序列长度达到数万甚至数十万时,这会导致巨大的内存消耗和计算延迟。

FlashAttention系列算法通过一系列巧妙的优化,重新组织了注意力计算的数据流,在不改变数学结果的前提下,大幅减少了内存访问量。从FlashAttention-1到FlashAttention-3,每一次演进都在矩阵乘法的访存效率上实现了质的飞跃。

FlashAttention-1:分块计算与重计算策略

核心思想:避免存储中间矩阵

FlashAttention-1的核心创新在于将注意力计算分解为多个块,通过分块处理避免存储整个注意力矩阵。它结合了两种关键技术:

  1. 分块计算:将输入Q、K、V矩阵分成小块,在SRAM中进行计算
  2. 重计算:在反向传播时重新计算注意力权重,而不是存储它们

算法原理

传统注意力计算:

Attention(Q,K,V)=softmax(QKTdk)V\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V

FlashAttention-1采用在线softmax算法,分块计算注意力:

import torch
import torch.nn.functional as F

def flash_attention_v1(Q, K, V, block_size=256):
    """
    FlashAttention-1 简化实现
    Q, K, V: [batch_size, seq_len, d_model]
    block_size: 分块大小,根据SRAM容量确定
    """
    batch_size, seq_len, d_model = Q.shape
    O = torch.zeros_like(V)
    L = torch.zeros(batch_size, seq_len, 1, device=Q.device)
    M = torch.full((batch_size, seq_len, 1), float('-inf'), device=Q.device)
    
    # 分块处理
    for i in range(0, seq_len, block_size):
        Q_block = Q[:, i:i+block_size, :]
        
        for j in range(0, seq_len, block_size):
            K_block = K[:, j:j+block_size, :]
            V_block = V[:, j:j+block_size, :]
            
            # 计算当前块的注意力分数
            S_block = torch.matmul(Q_block, K_block.transpose(-2, -1)) / (d_model ** 0.5)
            
            # 更新最大值和归一化因子
            M_new = torch.maximum(M[:, i:i+block_size], S_block.max(dim=-1, keepdim=True).values)
            P = torch.exp(S_block - M_new)
            L_new = torch.exp(M[:, i:i+block_size] - M_new) * L[:, i:i+block_size] + \
                   torch.sum(P, dim=-1, keepdim=True)
            
            # 更新输出
            O[:, i:i+block_size] = torch.exp(M[:, i:i+block_size] - M_new) * O[:, i:i+block_size] + \
                                   torch.matmul(P, V_block)
            
            M[:, i:i+block_size] = M_new
            L[:, i:i+block_size] = L_new
    
    # 最终归一化
    O = O / L
    
    return O

# 性能对比
def benchmark_attention():
    batch_size, seq_len, d_model = 4, 4096, 1024
    Q = torch.randn(batch_size, seq_len, d_model, device='cuda')
    K = torch.randn(batch_size, seq_len, d_model, device='cuda')
    V = torch.randn(batch_size, seq_len, d_model, device='cuda')
    
    # 传统注意力(仅用于比较,内存消耗大)
    def standard_attention(Q, K, V):
        with torch.no_grad():
            scores = torch.matmul(Q, K.transpose(-2, -1)) / (d_model ** 0.5)
            attn = F.softmax(scores, dim=-1)
            return torch.matmul(attn, V)
    
    import time
    torch.cuda.synchronize()
    
    # FlashAttention-1
    start = time.time()
    out_fa1 = flash_attention_v1(Q, K, V)
    torch.cuda.synchronize()
    fa1_time = time.time() - start
    
    print(f"FlashAttention-1 计算时间: {fa1_time:.4f}s")
    print(f"峰值内存: {torch.cuda.max_memory_allocated() / 1024**2:.2f} MB")

访存分析

FlashAttention-1的关键优化在于将HBM(高带宽内存)和SRAM(静态随机存储器)的访问模式重新组织:

  • 传统注意力:需要将大小为 N×NN \times N 的注意力矩阵写入HBM
  • FlashAttention-1:只在SRAM中计算分块,仅将最终结果写回HBM

理论访存量从 O(N2)O(N^2) 降低到 O(N)O(N),实际应用中可减少约60-70%的访存。

FlashAttention-2:计算并行化与工作负载重平衡

改进之处

FlashAttention-2在v1的基础上进行了三处关键优化:

  1. 减少非矩阵乘法运算:将更多的计算集中在GEMM操作上
  2. 并行化策略改进:在序列维度而非批处理维度上进行并行化
  3. 工作负载重平衡:将计算更均匀地分配到不同的线程块中

算法实现

import torch
from typing import Optional, Tuple

def flash_attention_v2(
    Q: torch.Tensor,
    K: torch.Tensor,
    V: torch.Tensor,
    block_q: int = 128,
    block_k: int = 256,
    causal: bool = False
) -> torch.Tensor:
    """
    FlashAttention-2 简化实现
    关键改进:改进的并行化和工作负载平衡
    """
    batch_size, seq_len_q, d = Q.shape
    seq_len_k = K.shape[1]
    
    # 初始化输出和统计量
    O = torch.zeros_like(Q)
    L = torch.zeros(batch_size, seq_len_q, 1, device=Q.device)
    m = torch.full((batch_size, seq_len_q, 1), float('-inf'), device=Q.device)
    
    # 计算缩放因子
    scale = d ** -0.5
    
    # 改进的并行化:每个线程块处理一个查询块
    for i in range(0, seq_len_q, block_q):
        Q_block = Q[:, i:i+block_q, :]
        
        # 初始化当前块的统计量
        m_block = m[:, i:i+block_q].clone()
        L_block = L[:, i:i+block_q].clone()
        O_block = O[:, i:i+block_q, :].clone()
        
        for j in range(0, seq_len_k, block_k):
            K_block = K[:, j:j+block_k, :]
            V_block = V[:, j:j+block_k, :]
            
            # 计算注意力分数
            S = torch.matmul(Q_block, K_block.transpose(-2, -1)) * scale
            
            # 因果掩码(可选)
            if causal:
                mask = torch.triu(
                    torch.full((block_q, block_k), float('-inf'), device=Q.device),
                    diagonal=1
                )
                if i + block_q <= seq_len_q and j + block_k <= seq_len_k:
                    S[:, i//block_q*block_q:(i//block_q+1)*block_q, 
                      j//block_k*block_k:(j//block_k+1)*block_k] += mask
            
            # 在线softmax更新
            m_new = torch.maximum(m_block, S.max(dim=-1, keepdim=True).values)
            alpha = torch.exp(m_block - m_new)
            beta = torch.exp(S - m_new)
            
            # 累积更新
            L_block = alpha * L_block + beta.sum(dim=-1, keepdim=True)
            O_block = alpha * O_block + torch.matmul(beta, V_block)
            m_block = m_new
        
        # 写回结果
        O[:, i:i+block_q] = O_block / L_block
        L[:, i:i+block_q] = L_block
        m[:, i:i+block_q] = m_block
    
    return O

# 工作负载重平衡示例
def workload_balance_demo():
    """
    展示FlashAttention-2的工作负载重平衡策略
    """
    seq_len = 8192
    d_model = 1024
    
    # 传统并行化:按批次维度分割
    def traditional_parallel(batch_size=8):
        # 每个设备处理一部分批次
        return f"传统并行:{batch_size}个批次,每个批次完整序列"
    
    # FlashAttention-2并行化:按序列维度分割
    def fa2_parallel(num_devices=4):
        # 每个设备处理序列的一部分
        chunk_size = seq_len // num_devices
        return f"FA2并行:{num_devices}个设备,每个处理{chunk_size}个token"
    
    print(traditional_parallel())
    print(fa2_parallel())

性能提升

FlashAttention-2相对于v1的主要改进:

  1. 计算效率提升:将更多操作融合到GEMM中,减少kernel启动开销
  2. 并行化改进:在序列维度并行化,更好地利用GPU资源
  3. 访存优化:进一步减少共享内存的bank冲突

在实际测试中,FlashAttention-2比v1快约1.5-2倍,访存进一步减少10-15%。

FlashAttention-3:硬件感知优化与异步操作

革命性突破

FlashAttention-3代表了注意力计算优化的最新突破,专为现代GPU架构(特别是Hopper架构)设计,引入了多项创新:

  1. 异步全局内存加载:利用Tensor Memory Accelerator (TMA)
  2. 3D并行化策略:结合序列、批处理和头维度的并行
  3. 双缓冲技术:隐藏内存延迟
  4. 稀疏注意力支持:动态块稀疏模式

核心实现

import torch
import torch.nn as nn
from torch.cuda.amp import custom_fwd, custom_bwd

class FlashAttention3(nn.Module):
    """
    FlashAttention-3 简化概念实现
    注:实际FlashAttention-3使用CUDA C++实现,此处展示算法逻辑
    """
    
    def __init__(self, d_model: int, num_heads: int, block_size: int = 256):
        super().__init__()
        self.d_model = d_model
        self.num_heads = num_heads
        self.block_size = block_size
        self.head_dim = d_model // num_heads
        
    @custom_fwd
    def forward(self, Q: torch.Tensor, K: torch.Tensor, V: torch.Tensor) -> torch.Tensor:
        batch_size, seq_len, _ = Q.shape
        
        # 3D并行化:批次×头数×序列块
        total_blocks = (seq_len + self.block_size - 1) // self.block_size
        
        # 使用双缓冲技术预取数据
        def prefetch_next_block(step, total, tensor):
            next_step = (step + 1) % total
            return tensor[:, next_step*self.block_size:(next_step+1)*self.block_size, :]
        
        # 初始化输出
        O = torch.zeros_like(Q)
        
        # 主计算循环
        for block_idx in range(total_blocks):
            # 异步加载下一个块(概念展示)
            if block_idx < total_blocks - 1:
                next_Q = prefetch_next_block(block_idx, total_blocks, Q)
                next_K = prefetch_next_block(block_idx, total_blocks, K)
                next_V = prefetch_next_block(block_idx, total_blocks, V)
            
            # 获取当前块
            Q_block = Q[:, block_idx*self.block_size:(block_idx+1)*self.block_size, :]
            
            # 处理所有键值块
            for kv_block_idx in range(total_blocks):
                K_block = K[:, kv_block_idx*self.block_size:(kv_block_idx+1)*self.block_size, :]
                V_block = V[:, kv_block_idx*self.block_size:(kv_block_idx+1)*self.block_size, :]
                
                # 计算块间注意力
                attn_scores = torch.matmul(
                    Q_block.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2),
                    K_block.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2).transpose(-2, -1)
                ) / (self.head_dim ** 0.5)
                
                # 应用softmax并累积
                attn_weights = torch.softmax(attn_scores, dim=-1)
                
                # 累积到输出
                O_block = torch.matmul(
                    attn_weights,
                    V_block.view(batch_size, -1, self.num_heads, self.head_dim).transpose(1, 2)
                )
                
                # 聚合结果
                start_idx = block_idx * self.block_size
                end_idx = min((block_idx + 1) * self.block_size, seq_len)
                O[:, start_idx:end_idx, :] += O_block.transpose(1, 2).reshape(
                    batch_size, end_idx - start_idx, self.d_model
                )
        
        return O

# 异步操作和硬件特性利用
class HardwareAwareOptimization:
    """
    展示FlashAttention-3的硬件感知优化
    """
    
    @staticmethod
    def use_tensor_core_optimization(matrix_a, matrix_b):
        """
        利用Tensor Core进行混合精度计算
        """
        with torch.cuda.amp.autocast():
            # Tensor Core在混合精度下效率最高
            result = torch.matmul(matrix_a, matrix_b)
        return result
    
    @staticmethod
    def memory_access_pattern_optimization(data, block_size, warp_size=32):
        """
        优化内存访问模式,减少bank冲突
        """
        batch, seq, dim = data.shape
        
        # 重新组织数据布局以提高合并访问
        optimized_data = data.contiguous()
        
        # 块状访问模式
        for i in range(0, seq, block_size):
            # 确保对齐的全局内存访问
            block = optimized_data[:, i:i+block_size, :]
            
            # 使用向量化加载(概念展示)
            # 实际实现中使用内联PTX汇编或CUDA内置函数
            pass
        
        return optimized_data
    
    @staticmethod
    def dynamic_sparse_pattern(attn_scores, sparsity_threshold=0.1):
        """
        动态稀疏注意力模式
        """
        # 基于分数动态确定稀疏模式
        max_scores = attn_scores.max(dim=-1, keepdim=True).values
        threshold = max_scores * sparsity_threshold
        
        # 创建掩码
        mask = attn_scores > threshold
        
        # 仅计算非零元素
        sparse_scores = attn_scores * mask.float()
        
        return sparse_scores, mask

# 性能测试对比
def benchmark_all_versions():
    """对比三个版本的性能"""
    import time
    import numpy as np
    
    torch.manual_seed(42)
    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
    
    # 测试配置
    configs = [
        {'batch': 2, 'seq_len': 4096, 'd_model': 1024, 'heads': 16},
        {'batch': 4, 'seq_len': 8192, 'd_model': 2048, 'heads': 16},
    ]
    
    results = []
    
    for config in configs:
        batch, seq_len, d_model, heads = config['batch'], config['seq_len'], config['d_model'], config['heads']
        
        Q = torch.randn(batch, seq_len, d_model, device=device)
        K = torch.randn(batch, seq_len, d_model, device=device)
        V = torch.randn(batch, seq_len, d_model, device=device)
        
        # 测试不同版本
        versions = [
            ('FA-1', lambda: flash_attention_v1(Q, K, V)),
            ('FA-2', lambda: flash_attention_v2(Q, K, V)),
            ('FA-3', FlashAttention3(d_model, heads).to(device)),
        ]
        
        for name, attn_fn in versions:
            torch.cuda.synchronize()
            torch.cuda.reset_peak_memory_stats()
            
            start_time = time.time()
            
            if name == 'FA-3':
                output = attn_fn(Q, K, V)
            else:
                output = attn_fn()
            
            torch.cuda.synchronize()
            elapsed = time.time() - start_time
            memory = torch.cuda.max_memory_allocated() / 1024**2
            
            results.append({
                'config': config,
                'version': name,
                'time': elapsed,
                'memory': memory
            })
            
            print(f"{name} | SeqLen={seq_len} | Time={elapsed:.3f}s | Memory={memory:.1f}MB")
    
    return results

关键优化技术

  1. 异步全局内存加载

    # 概念代码:在实际CUDA实现中
    # 使用TMA(Tensor Memory Accelerator)进行异步加载
    # __global__ void async_load(float* dst, const float* src, size_t size) {
    #     asm volatile("cp.async.ca.shared.global [%0], [%1], %2;" :: "r"(dst), "l"(src), "n"(size));
    # }
    
  2. 3D并行化策略

    • 网格维度1:批次大小
    • 网格维度2:注意力头数
    • 网格维度3:序列块数
  3. 访存减少分析

    • FA-1相比传统:减少约70%访存
    • FA-2相比FA-1:额外减少10-15%
    • FA-3相比传统:累计减少87%访存

矩阵乘法访存优化的数学原理

屋顶线模型分析

屋顶线模型(Roofline Model)帮助我们理解计算瓶颈:

import matplotlib.pyplot as plt
import numpy as np

def roofline_model():
    """展示FlashAttention系列在屋顶线模型上的位置"""
    
    # 计算强度(FLOPs/Byte)
    ops_per_byte = {
        '传统注意力': 0.5,  # 低计算强度,内存受限
        'FlashAttention-1': 2.0,
        'FlashAttention-2': 3.5,
        'FlashAttention-3': 8.0,  # 高计算强度,计算受限
    }
    
    # 理论性能(以A100为例)
    peak_flops = 312e12  # 312 TFLOPS (FP16 Tensor Core)
    peak_memory_bw = 2039e9  # 2039 GB/s
    
    # 屋顶线
    x = np.logspace(-1, 2, 100)
    y = np.minimum(peak_flops, peak_memory_bw * x)
    
    plt.figure(figsize=(10, 6))
    plt.loglog(x, y, 'r-', label='屋顶线 (A100)')
    
    # 标记不同算法
    for name, ci in ops_per_byte.items():
        attainable_perf = min(peak_flops, peak_memory_bw * ci)
        plt.plot(ci, attainable_perf / 1e12, 'o', markersize=10, label=name)
        plt.text(ci * 1.2, attainable_perf / 1e12 * 0.9, name, fontsize=9)
    
    plt.xlabel('计算强度 (FLOPs/Byte)', fontsize=12)
    plt.ylabel('可达性能 (TFLOPS)', fontsize=12)
    plt.title('FlashAttention系列在屋顶线模型中的位置', fontsize=14)
    plt.grid(True, which='both', linestyle='--', alpha=0.7)
    plt.legend()
    plt.tight_layout()
    plt.show()

# 运行屋顶线分析
roofline_model()

访存复杂度分析

  1. 传统注意力

    HBM访问=4N2+4Nd\text{HBM访问} = 4N^2 + 4Nd

  2. FlashAttention-1

    HBM访问=4Nd+4N2BM\text{HBM访问} = 4Nd + 4N^2\frac{B}{M}

    其中B是块大小,M是SRAM大小

  3. FlashAttention-3

    HBM访问0.13×(4N2+4Nd)\text{HBM访问} \approx 0.13 \times (4N^2 + 4Nd)

    相比传统减少87%

实际应用与性能对比

在LLaMA中的集成

class OptimizedLLaMAAttention(nn.Module):
    """在LLaMA中集成FlashAttention-3"""
    
    def __init__(self, config):
        super().__init__()
        self.hidden_size = config.hidden_size
        self.num_heads = config.num_attention_heads
        self.head_dim = self.hidden_size // self.num_heads
        
        # 投影层
        self.q_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=False)
        self.k_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=False)
        self.v_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=False)
        self.o_proj = nn.Linear(self.hidden_size, self.hidden_size, bias=False)
        
        # FlashAttention-3优化
        self.flash_attn = FlashAttention3(
            d_model=self.hidden_size,
            num_heads=self.num_heads,
            block_size=256
        )
        
    def forward(self, hidden_states, attention_mask=None):
        batch_size, seq_len, _ = hidden_states.shape
        
        # 线性投影
        Q = self.q_proj(hidden_states)
        K = self.k_proj(hidden_states)
        V = self.v_proj(hidden_states)
        
        # 使用FlashAttention-3
        attn_output = self.flash_attn(Q, K, V)
        
        # 输出投影
        output = self.o_proj(attn_output)
        
        return output

# 性能基准测试
def run_comprehensive_benchmark():
    """运行全面的性能基准测试"""
    
    import pandas as pd
    from tabulate import tabulate
    
    # 测试不同序列长度
    seq_lengths = [1024, 2048, 4096, 8192, 16384]
    results = []
    
    for seq_len in seq_lengths:
        # 创建测试数据
        batch_size = 2
        d_model = 4096
        num_heads = 32
        
        Q = torch.randn(batch_size, seq_len, d_model).cuda()
        K = torch.randn(batch_size, seq_len, d_model).cuda()
        V = torch.randn(batch_size, seq_len, d_model).cuda()
        
        # 测试不同版本
        for version_name, attn_fn in [
            ('FA-1', flash_attention_v1),
            ('FA-2', flash_attention_v2),
            ('FA-3', FlashAttention3(d_model, num_heads).cuda())
        ]:
            # 预热
            for _ in range(3):
                _ = attn_fn(Q, K, V) if version_name != 'FA-3' else attn_fn(Q, K, V)
            
            # 正式测试
            torch.cuda.synchronize()
            start = torch.cuda.Event(enable_timing=True)
            end = torch.cuda.Event(enable_timing=True)
            
            start.record()
            for _ in range(10):
                if version_name != 'FA-3':
                    output = attn_fn(Q, K, V)
                else:
                    output = attn_fn(Q, K, V)
            end.record()
            torch.cuda.synchronize()
            
            elapsed_time = start.elapsed_time(end) / 10  # 毫秒
            
            # 内存使用
            torch.cuda.reset_peak_memory_stats()
            _ = attn_fn(Q, K, V) if version_name != 'FA-3' else attn_fn(Q, K, V)
            memory_used = torch.cuda.max_memory_allocated() / 1024**2
            
            results.append({
                'Sequence Length': seq_len,
                'Version': version_name,
                'Time (ms)': elapsed_time,
                'Memory (MB)': memory_used
            })
    
    # 创建结果表格
    df = pd.DataFrame(results)
    pivot_table = df.pivot_table(
        index='Sequence Length',
        columns='Version',
        values=['Time (ms)', 'Memory (MB)'],
        aggfunc='mean'
    )
    
    print("性能基准测试结果:")
    print(tabulate(pivot_table, headers='keys', tablefmt='grid'))
    
    # 计算加速比
    fa1_times = df[df['Version'] == 'FA-1'].set_index('Sequence Length')['Time (ms)']
    fa3_times = df[df['Version'] == 'FA-3'].set_index('Sequence Length')['Time (ms)']
    
    speedup = (fa1_times / fa3_times).mean()
    print(f"\nFlashAttention-3 相比 FlashAttention-1 平均加速: {speedup:.2f}x")
    
    return df

未来展望与研究方向

持续优化方向

  1. 硬件协同设计

    • 为下一代GPU架构(如Blackwell)优化
    • 利用新型内存技术(HBM3e, GDDR7)
  2. 算法改进

    • 动态稀疏模式的进一步优化
    • 混合精度计算的自动调优
  3. 系统集成

    • 与编译器技术(如Triton)深度集成
    • 分布式注意力计算的优化

新兴应用场景

  1. 超长上下文处理

    • 百万token级别的文档理解
    • 长时间序列预测
  2. 多模态模型

    • 图像-文本联合注意力优化
    • 视频序列的高效处理
  3. 边缘设备部署

    • 移动端和边缘设备的注意力优化
    • 低功耗注意力计算

结论

从FlashAttention-1到FlashAttention-3的演进,代表了注意力计算优化的重大进步。通过深入理解硬件特性和算法特性,FlashAttention系列成功地将注意力计算的访存量减少了87%,为处理更长上下文、构建更大模型奠定了坚实基础。

关键收获:

  1. 分块计算是减少中间内存占用的核心策略
  2. 硬件感知优化可以释放现代GPU的全部潜力
  3. 算法-硬件协同设计是未来性能突破的关键

FlashAttention-3不仅是一个算法优化,更是一种新的计算范式,它展示了通过深入理解硬件特性来实现算法突破的重要性。随着AI模型的不断发展,这种硬件感知的优化方法将在未来发挥越来越重要的作用。


注:本文中的代码示例为简化实现,旨在说明算法原理。实际生产环境中的FlashAttention-3使用高度优化的CUDA C++实现,并包含更多细节优化。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。