Transformer的并行计算与长序列处理瓶颈总结

发布于:2025-09-02 ⋅ 阅读:(25) ⋅ 点赞:(0)

🌟 第0层:极简版(30秒理解)

一句话核心:Transformer像圆桌会议——所有人都能同时交流(并行优势),但人越多会议越混乱(长序列瓶颈)。

核心问题

  • 并行优势:所有词语可以同时"交流",不像RNN必须按顺序一个字一个字处理
  • 长序列瓶颈:每个词都要关注所有其他词,1000个词需要计算100万次关系(O(n²)复杂度)

生活比喻

想象一个会议:

  • 短会议(10人):每个人都能快速和其他人交流 → 高效
  • 长会议(1000人):每个人都需要和999人交流 → 极其混乱低效

💡 记住这个公式计算量 ≈ 序列长度²
512词 → 26万次计算 | 1024词 → 104万次计算(4倍增长!


📚 第1层:基础概念(5分钟理解)

1. 为什么Transformer能并行计算?

传统RNN(不能并行)
词1
词2
词3
词4
  • 必须按顺序处理:计算词2必须等词1完成
  • 像流水线:一个环节卡住,整个流程停滞
Transformer(完全并行)
同时交流
同时交流
同时交流
同时交流
词1
所有人
词2
词3
词4
  • 所有词同时处理:没有先后依赖
  • GPU友好:一次处理整个序列,充分利用并行计算能力

2. 长序列瓶颈的直观理解

自注意力机制如何工作
关注
关注
关注
关注
关注
关注
关注
关注
关注
关注
关注
关注
关注
关注
关注
关注
人工
智能
计算量暴增
序列长度 需要计算的关系数 内存占用(假设d=512)
10词 100 0.02 MB
100词 10,000 2 MB
512词 262,144 1024 MB (1GB)
1024词 1,048,576 4096 MB (4GB)
8192词 67,108,864 262,144 MB (256GB)

⚠️ 关键问题:现代GPU通常只有24-80GB内存,无法处理超长序列

3. 实际影响

  • 训练限制:大多数模型限制输入长度为512或1024词
  • 信息丢失:长文档必须截断或分块,可能丢失关键上下文
  • 速度下降:序列长度翻倍 → 计算时间增加4倍

🔍 第2层:中等深度(15分钟理解)

1. 并行计算详解

为什么Transformer能完全并行?

关键在于自注意力公式

Attention(Q,K,V) = softmax(QKᵀ/√dₖ)V

计算步骤

  1. Q, K, V矩阵同时计算
    Q=X⋅WQ,K=X⋅WK,V=X⋅WV Q = X·W_Q, K = X·W_K, V = X·W_V Q=XWQ,K=XWK,V=XWV

    • X是输入矩阵(所有词向量组成)
    • 一次矩阵乘法完成所有词的转换
  2. 注意力分数并行计算

    S = Q·Kᵀ/√dₖ
    
    • 矩阵乘法,完全并行
    • 结果S是n×n的注意力矩阵
  3. softmax并行应用

    • 对S的每一行独立应用softmax
    • 无跨行依赖,可完全并行
  4. 加权和并行计算

    O = S·V
    
    • 矩阵乘法,完全并行
并行优势 vs RNN
Transformer
同时计算
同时计算
同时计算
同时计算
所有Q/K/V
词1
词2
词3
词4
同时计算注意力
同时得到输出
RNN
必须等待
必须等待
必须等待
词2
词1
词3
词4
A,B,C,D
E,G,H,I,F,J,K

2. 长序列瓶颈的数学本质

自注意力的复杂度分析

假设:

  • 序列长度:n
  • 特征维度:d

计算步骤与复杂度

  1. Q, K, V计算

    • X (n×d) × W (d×d) → Q/K/V (n×d)
    • 复杂度:O(n·d²)
  2. QKᵀ计算

    • Q (n×d) × Kᵀ (d×n) → S (n×n)
    • 复杂度:O(n²·d)
  3. softmax应用

    • 对n×n矩阵的每行应用softmax
    • 复杂度:O(n²)
  4. SV计算

    • S (n×n) × V (n×d) → O (n×d)
    • 复杂度:O(n²·d)

总复杂度:O(n²·d)(由步骤2和4主导)

内存瓶颈详解
  • 注意力矩阵S:n×n×4字节(float32)

    • n=512 → 1,048,576元素 → 4MB
    • n=2048 → 16,777,216元素 → 64MB
    • n=8192 → 268,435,456元素 → 1024MB (1GB)
  • 实际内存需求

    • 前向传播:1×注意力矩阵
    • 反向传播:3×注意力矩阵(输入、输出、梯度)
    • 优化器状态:2×模型参数
    • 总计:对于12层Transformer,n=8192可能需要**>100GB内存**

3. 常见解决方案概览

1. 序列截断
  • 方法:简单丢弃超出长度的部分
  • 缺点:丢失重要上下文
  • 使用场景:短文本任务(如情感分析)
2. 滑动窗口
  • 方法:将长文本分成重叠块处理
  • 缺点:块间信息不连贯
  • 示例:BERT处理长文档的方式
3. 稀疏注意力
  • 方法:只计算部分词对间的注意力
  • 类型
    • 局部注意力(关注邻近词)
    • 随机注意力(随机选择词对)
    • 膨胀注意力(不同距离关注)
  • 优势:复杂度降至O(n·w),w为窗口大小
4. 线性注意力
  • 方法:重新排列计算顺序
  • 核心思想:使用核函数近似softmax
  • 优势:复杂度降至O(n·d)
问题
解决方案
解决方案
解决方案
解决方案
标准注意力 O n²d
长序列瓶颈
稀疏注意力
线性注意力
分块处理
内存优化

⚙️ 第3层:技术深度(30分钟理解)

1. 并行计算技术细节

Transformer并行实现
import torch
import torch.nn as nn

class ParallelTransformerLayer(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.attn = nn.MultiheadAttention(d_model, n_heads)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, 4 * d_model),
            nn.ReLU(),
            nn.Linear(4 * d_model, d_model)
        )
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
    
    def forward(self, x):
        # 所有操作完全并行
        # 1. 多头注意力(核心并行点)
        attn_out, _ = self.attn(x, x, x)  # 所有词同时计算
        
        # 2. 残差连接和归一化
        x = self.norm1(x + attn_out)
        
        # 3. 前馈网络(同样并行)
        ffn_out = self.ffn(x)
        
        # 4. 残差连接和归一化
        return self.norm2(x + ffn_out)
GPU并行优化技术
  1. 矩阵分块计算

    • 将大矩阵乘法分解为小块
    • 适应GPU的SM(流式多处理器)结构
  2. 混合精度训练

    • 使用FP16/FP32混合精度
    • 减少内存占用,加速计算
  3. 内核融合

    • 将多个操作融合为单一CUDA内核
    • 减少GPU内核启动开销

2. 长序列瓶颈技术分析

自注意力复杂度证明(详细)

给定:

  • 输入 X∈R(n×d)X ∈ R^{(n×d)}XR(n×d)
  • 权重矩阵 WQ,WK,WV∈R(d×d)W_Q, W_K, W_V ∈ R^{(d×d)}WQ,WK,WVR(d×d)

计算步骤:

  1. Q=X⋅WQ,K=X⋅WK,V=X⋅WVQ = X·W_Q, K = X·W_K, V = X·W_VQ=XWQ,K=XWK,V=XWV

    • 矩阵乘法复杂度:O(n·d·d) = O(n·d²)
    • 每个词独立计算,完全并行
  2. S = Q·Kᵀ/√dₖ

    • Q∈R(n×d),KT∈R(d×n)Q ∈ R^{(n×d)}, Kᵀ ∈ R^{(d×n)}QR(n×d),KTR(d×n)
    • 矩阵乘法复杂度:O(n·d·n) = O(n²·d)
    • 这是瓶颈步骤
  3. P = softmax(S)

    • 对n×n矩阵应用softmax
    • 复杂度:O(n²)
    • 每行独立计算,可并行
  4. O = P·V

    • P∈R(n×n),V∈R(n×d)P ∈ R^{(n×n)}, V ∈ R^{(n×d)}PR(n×n),VR(n×d)
    • 矩阵乘法复杂度:O(n·n·d) = O(n²·d)
    • 瓶颈步骤

总复杂度:O(n·d²) + O(n²·d) + O(n²) + O(n²·d) = O(n²·d)

内存瓶颈的量化分析

对于n=2048, d=1024的序列:

组件 内存需求 计算方式
输入X 8MB 2048×1024×4
Q/K/V 24MB 3×2048×1024×4
注意力矩阵S 16MB 2048×2048×4
FFN中间表示 32MB 2048×4096×4
单层前向 80MB 总和
12层前向 960MB 12×80MB
反向传播 ~2.9GB 3×前向
优化器状态 ~3.9GB 2×模型参数(983M)
总计 ~7.8GB 可行但紧张

当n=8192时:

  • 注意力矩阵S:256MB
  • 12层前向:~3.1GB
  • 反向传播:~9.2GB
  • 优化器状态:~3.9GB
  • 总计:~16.2GB(接近单卡极限)

3. 高级解决方案详解

1. 稀疏注意力

Longformer的滑动窗口注意力

窗口内
窗口内
窗口内
窗口内
窗口内
窗口内
全局
全局
全局
全局
全局
词1
词2
词3
词4
词5
全局词

代码实现

def sliding_window_attention(Q, K, V, window_size=512):
    n, d = Q.shape[1], Q.shape[-1]
    scores = torch.zeros((n, n), device=Q.device)
    
    # 只计算窗口内的注意力
    for i in range(n):
        start = max(0, i - window_size // 2)
        end = min(n, i + window_size // 2 + 1)
        scores[i, start:end] = torch.matmul(
            Q[:, i:i+1, :], 
            K[:, start:end, :].transpose(-2, -1)
        ) / (d ** 0.5)
    
    attn = torch.softmax(scores, dim=-1)
    return torch.matmul(attn, V)

复杂度:O(n·w·d),w为窗口大小
内存:O(n·w)而非O(n²)

2. 线性注意力

核心思想:使用核函数重排计算顺序

Attention(Q,K,V) = softmax(QKᵀ/√dₖ)V
                 = (φ(Q)·φ(K)ᵀ)·V / normalization
                 = φ(Q)·(φ(K)ᵀ·V) / normalization

其中φ是特征映射函数

Performers的随机特征映射

def random_fourier_features(X, n_random=256):
    """将X映射到随机傅里叶特征空间"""
    d = X.shape[-1]
    # 生成随机矩阵
    R = torch.randn(d, n_random, device=X.device) / (d ** 0.25)
    # 计算特征映射
    phi_X = torch.sqrt(torch.tensor(2.0 / n_random)) * torch.cos(
        torch.matmul(X, R) + torch.rand(n_random, device=X.device) * 2 * torch.pi
    )
    return phi_X

def linear_attention(Q, K, V):
    """线性复杂度的注意力计算"""
    # 应用特征映射
    phi_Q = random_fourier_features(Q)
    phi_K = random_fourier_features(K)
    
    # 重排计算顺序
    KV = torch.einsum('nld,nlk->dk', phi_K, V)
    Z = 1.0 / (torch.einsum('nld,dk->nl', phi_Q, torch.ones_like(KV)) + 1e-6)
    V_pred = torch.einsum('nld,dk,nl->nld', phi_Q, KV, Z)
    
    return V_pred

复杂度:O(n·d·m),m为随机特征数(通常m<d)
内存:O(n·d)而非O(n²)

3. 内存优化技术

梯度检查点(Gradient Checkpointing)

from torch.utils.checkpoint import checkpoint

class CheckpointedTransformerLayer(nn.Module):
    def __init__(self, d_model, n_heads):
        super().__init__()
        self.attn = nn.MultiheadAttention(d_model, n_heads)
        self.ffn = nn.Sequential(
            nn.Linear(d_model, 4 * d_model),
            nn.ReLU(),
            nn.Linear(4 * d_model, d_model)
        )
        self.norm1 = nn.LayerNorm(d_model)
        self.norm2 = nn.LayerNorm(d_model)
    
    def attention_part(self, x):
        attn_out, _ = self.attn(x, x, x)
        return self.norm1(x + attn_out)
    
    def ffn_part(self, x):
        ffn_out = self.ffn(x)
        return self.norm2(x + ffn_out)
    
    def forward(self, x):
        # 仅保存关键中间结果,需要时重新计算
        x = checkpoint(self.attention_part, x)
        return checkpoint(self.ffn_part, x)

效果

  • 内存减少:从O(n)降至O(√n)
  • 计算增加:约20-30%
  • 适合内存受限场景
4. 长文本专用架构

Transformer-XL的记忆机制

Segment 2
Segment 1
记忆
记忆
记忆
记忆
词8
词7
词6
词5
词4
词3
词2
词1
Segment

关键创新

  • 段落级递归:保留上一段的隐藏状态
  • 相对位置编码:解决位置信息不一致问题
  • 内存重用:减少长距离依赖的衰减

🔬 第4层:前沿研究(60分钟理解)

1. 最新高效注意力技术

FlashAttention

核心思想:IO感知算法优化GPU内存访问

技术细节

  1. 分块处理

    • 将注意力矩阵分成适合SRAM的小块
    • 减少对HBM(高带宽内存)的访问
  2. 重计算策略

    • 重新计算中间结果而非存储
    • 减少内存需求
  3. CUDA内核融合

    • 将注意力计算融合为单一内核
    • 减少内核启动开销

性能提升

  • 速度提升:2-5倍
  • 内存减少:50-80%
  • 支持序列长度:2-4倍增加

代码示例

# 使用FlashAttention(PyTorch 2.0+)
import torch
import torch.nn.functional as F

# 标准注意力
def standard_attention(Q, K, V):
    attn = torch.softmax(Q @ K.transpose(-2, -1) / (Q.shape[-1] ** 0.5), dim=-1)
    return attn @ V

# FlashAttention(自动使用优化内核)
def flash_attention(Q, K, V):
    return F.scaled_dot_product_attention(Q, K, V)
xFormers库

Facebook开发的高效Transformer库,提供多种注意力实现:

from xformers.components.attention import (
    build_attention, 
    ScaledDotProduct, 
    LowerTriangularMask
)

# 选择最优注意力实现
attention = build_attention(
    "scaled_dot_product",
    ScaledDotProduct(
        dropout=0.1,
        causal=True,
        scale=None
    )
)

# 处理长序列
output = attention(
    q, k, v, 
    att_mask=LowerTriangularMask()  # 因果掩码
)

支持的注意力类型

  • 稀疏注意力
  • 线性注意力
  • 内存高效注意力
  • 因果注意力

2. 无限长度Transformer

Transformer-XL详解

三大创新

  1. 段落级递归

    hseg2=TransformerXL(segment2,mem=segment1hidden)h^{seg2} = TransformerXL(segment2, mem=segment1_hidden)hseg2=TransformerXL(segment2,mem=segment1hidden)

    • 保留上一段的隐藏状态作为记忆
    • 长期依赖可达数千词
  2. 相对位置编码

    • 位置信息基于相对距离而非绝对位置
    • 解决跨段位置不一致问题
    • 公式:
      Attention = (QW_q)·(KW_k + R_ikW_r)ᵀ
      
      其中R_ik是相对位置编码
  3. 片段递归机制

    输出
    输出
    作为记忆
    作为记忆
    Segment 1
    Memory 1
    Segment 2
    Memory 2
    Segment 3

性能对比

模型 最大上下文 语言建模PPL 长距离依赖
Transformer 512 20.2
Transformer-XL 3800 18.3 优秀
实际效果 ↑ 10倍上下文 ↓ 10% PPL ↑ 35% 任务
Compressive Transformer

扩展Transformer-XL,增加压缩记忆:

Segment 2
Segment 1
常规记忆
压缩记忆
解压
词8
词7
词6
词5
词4
词3
词2
词1
压缩存储
Segment

压缩机制

  • 定期将旧记忆压缩为更紧凑表示
  • 使用卷积操作进行压缩
  • 保留关键信息,丢弃细节

优势

  • 长期记忆能力更强
  • 内存效率更高
  • 适合超长序列任务(>10,000词)

3. 状态空间模型(SSM)方法

S4与Mamba模型

核心思想:将序列建模为连续状态演化

状态空间方程

h'(t) = A·h(t) + B·x(t)
y(t) = C·h(t) + D·x(t)

其中:

  • h(t):隐藏状态
  • x(t):输入
  • y(t):输出
  • A,B,C,D:参数矩阵

离散化与选择性机制

  • 离散化为高效计算形式
  • 引入选定性:参数随输入动态变化

复杂度:O(n)(线性)而非O(n²)

Mamba架构

线性复杂度
SSM
动态调整
状态演化
选择性参数
输入序列
离散化
输出投影
输出

性能对比

模型 复杂度 1K上下文速度 8K上下文速度 语言建模PPL
Transformer O(n²) 1.0x 0.15x 20.2
Longformer O(n) 0.9x 0.8x 19.8
Mamba O(n) 1.1x 1.0x 18.5

优势

  • 真正的线性复杂度
  • 速度不受序列长度影响
  • 在长序列任务上显著优于Transformer

4. 未来方向与开放问题

1. 混合架构研究

ConvFormer:结合CNN的局部归纳偏置和Transformer的全局依赖

输入
卷积层
Transformer层
卷积层
Transformer层
输出

优势

  • 局部结构捕捉更高效
  • 全局依赖仍然保留
  • 计算量减少30-50%
2. 自适应计算

Adaptive Computation Time (ACT)

  • 根据输入复杂度动态调整计算量
  • 简单序列快速处理,复杂序列精细处理

实现思路

def adaptive_transformer(x, max_layers=12):
    h = x
    remaining = torch.ones(x.shape[0], device=x.device)
    halting = torch.zeros_like(remaining)
    
    for i in range(max_layers):
        # 计算该层的"完成概率"
        p = halting_probability(h)
        
        # 更新完成状态
        still_running = (halting < 1.0).float()
        new_halted = (halting + p * still_running > 1.0).float() * still_running
        
        # 更新剩余计算量
        update = (1 - halting) * p
        halting = halting + update
        remaining = remaining - update
        
        # 只对仍在计算的样本应用变换
        layer_output = transformer_layer(h)
        h = h + layer_output * remaining.unsqueeze(-1)
        
        # 检查是否全部完成
        if torch.all(new_halted > 0):
            break
    
    return h
3. 理论突破方向
  • 信息保留与计算效率的平衡

    • 证明最小信息损失下的最优计算模式
    • 确定不同任务所需的最小上下文长度
  • 近似注意力的理论保证

    • 量化稀疏/线性注意力的信息损失
    • 为不同任务提供近似质量保证
4. 开放问题
  1. 理论极限:是否存在O(n)复杂度且保持完整表达能力的注意力机制?
  2. 评估标准:如何公平比较不同长文本模型?
  3. 实际需求:在真实场景中,多长的上下文真正有用?
  4. 训练方法:如何有效训练超长序列模型?

📊 解决方案对比表

方法 计算复杂度 内存需求 实现难度 适用场景 代表模型
标准Transformer O(n²d) O(n²) 短文本任务 BERT, GPT
序列截断 O(n²d) O(n²) 极低 简单任务 所有模型
滑动窗口 O(nwd) O(nw) 中等长度文本 BERT-wwm
稀疏注意力 O(nwd) O(nw) 长文本任务 Longformer
线性注意力 O(nmd) O(nm) 中高 超长文本 Performer
内存优化 O(n²d) O(n) 内存受限 Gradient Checkpointing
段落递归 O(n²d) O(nm) 极长文本 Transformer-XL
状态空间模型 O(nd) O(n) 超长序列 Mamba

选择建议

  • <512词:标准Transformer(简单高效)
  • 512-2048词:稀疏注意力或内存优化
  • 2048-8192词:Longformer或Transformer-XL
  • >8192词:线性注意力或状态空间模型

💡 实用建议与代码示例

1. 选择合适的方法

def choose_attention_mechanism(seq_length):
    """根据序列长度选择注意力机制"""
    if seq_length <= 512:
        return "standard"
    elif seq_length <= 2048:
        return "sliding_window"
    elif seq_length <= 8192:
        return "longformer"
    else:
        return "linear"

2. 使用Hugging Face实现长文本处理

from transformers import AutoTokenizer, AutoModelForCausalLM

# 选择适合长文本的模型
model_name = "bigscience/bloom-560m"  # 支持2048上下文
# model_name = "facebook/bart-large"  # 仅支持1024上下文
# model_name = "google/bigbird-pegasus-large-arxiv"  # 支持4096上下文

tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name)

# 处理长文本
long_text = "很长的文本..." * 100

# 分块处理
max_length = model.config.max_position_embeddings
inputs = tokenizer(
    long_text, 
    return_tensors="pt", 
    max_length=max_length,
    truncation=True,
    stride=64,  # 重叠部分
    return_overflowing_tokens=True
)

# 逐块处理
outputs = []
for i in range(len(inputs["input_ids"])):
    input_chunk = {k: v[i:i+1] for k, v in inputs.items()}
    with torch.no_grad():
        output = model(**input_chunk)
    outputs.append(output)

# 合并结果(根据任务需求)

3. 实现梯度检查点

import torch
from torch.utils.checkpoint import checkpoint

class CheckpointedTransformer(nn.Module):
    def __init__(self, num_layers, d_model, n_heads):
        super().__init__()
        self.layers = nn.ModuleList([
            nn.TransformerEncoderLayer(d_model, n_heads) 
            for _ in range(num_layers)
        ])
    
    def forward(self, src):
        for layer in self.layers:
            # 使用梯度检查点减少内存
            src = checkpoint(layer, src)
        return src

# 使用示例
model = CheckpointedTransformer(12, 512, 8)
output = model(input_data)

4. 使用FlashAttention(PyTorch 2.0+)

import torch
import torch.nn.functional as F

# 确保使用支持FlashAttention的设备
device = "cuda" if torch.cuda.is_available() else "cpu"
x = torch.randn(32, 1024, 512, device=device)

# 启用FlashAttention
with torch.backends.cuda.sdp_kernel(
    enable_flash=True,
    enable_math=False,
    enable_mem_efficient=False
):
    # 使用优化的注意力计算
    attn_output = F.scaled_dot_product_attention(
        q, k, v,
        attn_mask=None,
        dropout_p=0.0,
        is_causal=False
    )

🌐 信息图:Transformer长序列处理演进

flowchart LR
    A[2017: 标准Transformer] -->|O n²d| B[问题: 长序列瓶颈]
    
    B --> C1[序列截断]
    B --> C2[滑动窗口]
    B --> C3[稀疏注意力]
    B --> C4[线性注意力]
    B --> C5[内存优化]
    B --> C6[段落递归]
    B --> C7[状态空间模型]
    
    C1 --> D1[简单但信息丢失]
    C2 --> D2[局部依赖强]
    C3 --> D3[Longformer/BigBird]
    C4 --> D4[Performer/Linear Transformer]
    C5 --> D5[梯度检查点]
    C6 --> D6[Transformer-XL/Compressive]
    C7 --> D7[Mamba/S4]
    
    D3 & D4 & D6 & D7 --> E[当前最佳实践]
    
    classDef problem fill:#ffcdd2,stroke:#d32f2f
    classDef solution fill:#c8e6c9,stroke:#388e3c
    classDef current fill:#4fc3f7,stroke:#0288d1
    
    class B problem
    class C1,C2,C3,C4,C5,C6,C7 solution
    class E current

📌 总结与关键洞见

1. 核心矛盾

  • 并行优势:Transformer的并行计算能力使其训练效率远超RNN
  • 长序列瓶颈:自注意力的O(n²)复杂度限制了实际应用中的序列长度

2. 三大解决思路

  1. 减少计算量:稀疏注意力、线性注意力
  2. 优化内存使用:梯度检查点、内存高效实现
  3. 改变架构:段落递归、状态空间模型

3. 选择策略

  • 短文本(<512词):标准Transformer(简单高效)
  • 中等长度(512-2048词):稀疏注意力或内存优化
  • 长文本(2048-8192词):Longformer或Transformer-XL
  • 超长序列(>8192词):线性注意力或状态空间模型

4. 未来趋势

  • 硬件感知设计:针对特定硬件优化注意力实现
  • 混合架构:结合不同方法的优势
  • 理论突破:降低复杂度同时保持表达能力
  • 状态空间模型崛起:Mamba等模型可能成为长序列新标准

💡 终极洞见序列长度不是目标,而是手段——关键是根据任务需求选择合适的上下文长度和处理方法,平衡计算效率与模型性能。


网站公告

今日签到

点亮在社区的每一天
去签到