从零构建大模型实战:数据处理与 GPT-2 完整实现

前言

大模型的核心能力源于高质量数据高效模型架构。数据收集与预处理是大模型训练的基石,直接决定模型的泛化能力、知识覆盖度与生成质量;而基于 Transformer 解码器的 GPT 类模型,则是当前开源大模型的主流架构。本文从开源数据集处理、数据清洗去重、分词、数据加载器构建,到完整 GPT-2 模型实现、参数量与显存分析,提供全流程可运行代码与深度解析,覆盖大模型工程化落地核心环节。

本文全程使用 Python+PyTorch 实现,依赖库包含torchtransformersdatasetsnltktqdm等,所有代码可直接复制运行,适配单机 GPU/CPU 环境。


第一部分 大模型数据收集与预处理实战

数据是大模型的 "燃料",开源数据集为大模型训练提供了低成本、高质量的语料来源。本章节聚焦主流开源数据集接入全流程数据清洗子词分词数据加载器构建四大核心模块,实现工业级数据预处理 pipeline。

1.1 主流开源大模型数据集详解

大模型训练的核心数据集分为通用语料书籍语料网页语料三大类,覆盖百科、书籍、互联网网页等场景,保证数据的多样性与知识覆盖度。

1.1.1 核心开源数据集
  1. Wikipedia(维基百科)

    1)特点:结构化、高准确度、多语言通用百科知识,无冗余噪声,是大模型基础常识的核心来源;
    2)规模:英文维基百科约 600 万篇文章,200 亿 tokens;
    3)适用场景:基础语义理解、知识问答、通用生成。

  2. BookCorpus(书籍语料库)

    1)特点:完整长文本、叙事逻辑强、句式丰富,覆盖小说、传记、科技书籍,提升大模型长文本生成能力;
    2)规模:约 1.1 万本电子书,10 亿 tokens;
    3)适用场景:长文本理解、故事生成、逻辑推理。

  3. CommonCrawl(互联网网页语料)

    1)特点:规模最大、覆盖领域最广(新闻、博客、论坛等),数据多样性拉满,但噪声最多;
    2)规模:PB 级网页数据,每年更新,可提取万亿级 tokens;
    3)适用场景:提升模型泛化能力、领域适配。

  4. 补充数据集:OpenWebText(Reddit 高赞文章)、The Pile(综合性开源语料集),均为大模型训练标配。

1.1.2 数据集快速接入(HuggingFace Datasets)

HuggingFace datasets库提供了一键加载主流开源数据集的能力,无需手动下载文件,代码如下:

复制代码
# 安装依赖
# pip install datasets torch transformers nltk tqdm

from datasets import load_dataset
import nltk
nltk.download('punkt')

# 1. 加载维基百科数据集(英文小规模子集,快速测试)
wiki_dataset = load_dataset("wikipedia", "20220301.en", split="train[:1%]")  # 加载1%数据
print("维基百科数据示例:", wiki_dataset[0]["text"][:200])

# 2. 加载BookCorpus数据集
book_dataset = load_dataset("bookcorpus", split="train[:1%]")
print("书籍数据示例:", book_dataset[0]["text"][:200])

# 3. 加载CommonCrawl(CC100英文子集)
cc_dataset = load_dataset("cc100", lang="en", split="train[:0.1%]")
print("网页数据示例:", cc_dataset[0]["text"][:200])

# 4. 合并多源数据集(构建混合语料)
from datasets import concatenate_datasets
combined_dataset = concatenate_datasets([wiki_dataset, book_dataset, cc_dataset])
print("合并后总样本数:", len(combined_dataset))

核心说明:实际训练中需加载全量数据,本文使用小规模子集用于实战演示;合并多源数据可均衡知识覆盖度与文本多样性。

1.2 数据全流程预处理(清洗、去重、过滤、分词)

原始开源数据包含大量噪声(乱码、短文本、重复文本、特殊符号),必须经过清洗→过滤→去重→分词四步处理,才能用于模型训练。

1.2.1 数据清洗(去除噪声、标准化文本)

清洗目标:删除特殊字符、统一大小写、修复乱码、去除空文本、过滤无意义符号。

代码如下:

复制代码
import re
import string

def clean_text(text: str) -> str:
    """
    文本清洗核心函数:标准化、去噪声、去特殊字符
    """
    # 1. 转换为小写
    text = text.lower()
    # 2. 去除HTML标签、URL链接
    text = re.sub(r'<.*?>', '', text)
    text = re.sub(r'http\S+|www.\S+', '', text)
    # 3. 去除多余空格、换行符
    text = re.sub(r'\s+', ' ', text).strip()
    # 4. 去除标点符号以外的特殊字符(保留英文标点与字母)
    allowed_chars = set(string.ascii_lowercase + string.punctuation + ' ')
    text = ''.join([c for c in text if c in allowed_chars])
    # 5. 去除空文本
    if len(text) < 10:
        return ""
    return text

# 对数据集批量清洗
cleaned_dataset = combined_dataset.map(lambda x: {"cleaned_text": clean_text(x["text"])})
# 过滤空文本
cleaned_dataset = cleaned_dataset.filter(lambda x: len(x["cleaned_text"]) > 0)
print("清洗后样本示例:", cleaned_dataset[0]["cleaned_text"][:200])
1.2.2 数据过滤(筛选高质量文本)

过滤目标:删除过短 / 过长文本、低信息密度文本(纯符号、重复字符)。

代码如下:

复制代码
def filter_text(text: str, min_len=50, max_len=2048) -> bool:
    """
    文本过滤:长度约束 + 信息密度过滤
    """
    # 1. 长度过滤(保留50-2048字符的文本,适配大模型输入)
    if len(text) < min_len or len(text) > max_len:
        return False
    # 2. 过滤重复字符占比过高的文本(如"aaaaa")
    char_count = {}
    for c in text:
        char_count[c] = char_count.get(c, 0) + 1
    max_char_ratio = max(char_count.values()) / len(text)
    if max_char_ratio > 0.3:  # 单字符占比超过30%判定为低质量
        return False
    return True

# 批量过滤
filtered_dataset = cleaned_dataset.filter(lambda x: filter_text(x["cleaned_text"]))
print("过滤后总样本数:", len(filtered_dataset))
1.2.3 数据去重(消除重复语料)

重复文本会导致模型过拟合,必须通过哈希去重实现高效去重。

复制代码
from hashlib import md5

def remove_duplicates(dataset):
    """
    基于MD5哈希的文本去重
    """
    seen_hashes = set()
    unique_data = []
    for item in dataset:
        text = item["cleaned_text"]
        # 生成文本哈希值
        text_hash = md5(text.encode("utf-8")).hexdigest()
        if text_hash not in seen_hashes:
            seen_hashes.add(text_hash)
            unique_data.append(item)
    return unique_data

# 执行去重
unique_data = remove_duplicates(filtered_dataset)
print(f"去重后样本数:{len(unique_data)}")
1.2.4 子词分词(BPE/WordPiece)

大模型不使用单词 / 字符分词,而是子词分词(Subword Tokenization),解决未登录词问题,同时控制词汇表规模。主流分词算法:

  1. BPE(Byte Pair Encoding):GPT、GPT-2 使用;
  2. WordPiece:BERT、RoBERTa 使用。

本文使用 GPT-2 原生 BPE 分词器,代码如下:

复制代码
from transformers import GPT2Tokenizer

# 加载GPT-2官方BPE分词器
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
# 设置填充符(GPT-2默认无填充符,手动添加)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"  # 右侧填充

def tokenize_function(examples):
    """
    批量分词函数:返回input_ids + attention_mask
    """
    return tokenizer(
        examples["cleaned_text"],
        truncation=True,        # 超长文本截断
        max_length=512,         # GPT-2标准输入长度
        padding="max_length",   # 填充至最大长度
        return_attention_mask=True
    )

# 转换为字典格式适配分词函数
tokenizer_dataset = [{"text": item["cleaned_text"]} for item in unique_data]
from datasets import Dataset
tokenizer_dataset = Dataset.from_list(tokenizer_dataset)
# 批量分词(多线程加速)
tokenized_dataset = tokenizer_dataset.map(tokenize_function, batched=True, num_proc=4)
# 格式化模型输入(只保留input_ids和attention_mask)
tokenized_dataset.set_format("torch", columns=["input_ids", "attention_mask"])
print("分词后张量形状:", tokenized_dataset[0]["input_ids"].shape)
print("分词ID示例:", tokenized_dataset[0]["input_ids"][:20])

核心知识点

  • input_ids:文本转换后的数字编码,是模型的直接输入;
  • attention_mask:标识有效文本(1)与填充符(0),避免模型学习填充信息;
  • BPE 分词将单词拆分为子词(如unhappinessun+happiness),兼顾词汇表大小与泛化能力。

1.3 大模型数据加载器(Dataloader)构建

数据预处理完成后,需构建PyTorch DataLoader实现批量加载、随机打乱、多线程加速,适配模型训练流程。

代码如下:

复制代码
import torch
from torch.utils.data import DataLoader, TensorDataset

# 1. 构建张量数据集
input_ids = torch.stack([item["input_ids"] for item in tokenized_dataset])
attention_mask = torch.stack([item["attention_mask"] for item in tokenized_dataset])
# GPT-2为自回归模型,标签=输入(预测下一个token)
labels = input_ids.clone()

train_dataset = TensorDataset(input_ids, attention_mask, labels)

# 2. 构建DataLoader(大模型训练核心参数)
BATCH_SIZE = 8  # 批量大小,根据显存调整
NUM_WORKERS = 4 # 多线程加载数据

train_dataloader = DataLoader(
    train_dataset,
    batch_size=BATCH_SIZE,
    shuffle=True,       # 随机打乱数据,防止过拟合
    num_workers=NUM_WORKERS,
    pin_memory=True     # 加速GPU数据加载
)

# 测试DataLoader
batch = next(iter(train_dataloader))
input_batch, mask_batch, label_batch = batch
print(f"批量输入形状:{input_batch.shape}")  # [batch_size, seq_len]
print(f"批量掩码形状:{mask_batch.shape}")
print(f"批量标签形状:{label_batch.shape}")

工程化要点

  1. 大模型训练中,batch_size需根据显存动态调整(如 A100 可设为 64/128);
  2. pin_memory=True+ 多线程可大幅提升 CPU→GPU 数据传输速度;
  3. shuffle 必须开启,保证训练数据的随机性。

第二部分 完整 GPT-2 大模型构建实战

GPT-2 是基于Transformer 解码器 的自回归语言模型,核心结构:词嵌入 + 位置编码 + 堆叠 Transformer 解码器层 + 线性输出层。本章节实现完整 GPT-2 模型,包含参数量计算、显存分析,完全对标工业级实现。

2.1 GPT-2 核心架构解析

GPT-2 无编码器,仅使用解码器堆叠,核心组件:

  1. 词嵌入层(Token Embedding):将 token ID 转换为高维向量;
  2. 位置编码(Positional Encoding):注入文本序列位置信息(Transformer 无天然时序感知能力);
  3. Transformer 解码器层 :包含掩码多头注意力(Masked Multi-Head Attention)、前馈网络、层归一化、残差连接;
  4. 输出层:将解码器输出映射为词汇表概率分布。

GPT-2 标准配置(小模型):

  • 层数(n_layer):12
  • 注意力头数(n_head):12
  • 隐藏层维度(n_embd):768
  • 序列长度(seq_len):512
  • 词汇表大小(vocab_size):50257

2.2 完整 GPT-2 模型代码实现

基于 PyTorch 从零实现 GPT-2,无第三方封装,代码可直接用于训练 / 推理:

复制代码
import torch
import torch.nn as nn
import torch.nn.functional as F
import math

# -------------------------- 1. 掩码多头注意力层 --------------------------
class CausalSelfAttention(nn.Module):
    """GPT-2掩码多头注意力:防止模型看到未来的token"""
    def __init__(self, config):
        super().__init__()
        self.n_embd = config.n_embd
        self.n_head = config.n_head
        self.head_dim = self.n_embd // self.n_head
        assert self.head_dim * self.n_head == self.n_embd, "隐藏层维度必须能被头数整除"
        
        # QKV投影层 + 输出投影层
        self.c_attn = nn.Linear(self.n_embd, 3 * self.n_embd)
        self.c_proj = nn.Linear(self.n_embd, self.n_embd)
        
        # 因果掩码(下三角矩阵,屏蔽未来token)
        self.register_buffer("bias", torch.tril(torch.ones(config.seq_len, config.seq_len)).view(1, 1, config.seq_len, config.seq_len))

    def forward(self, x):
        B, T, C = x.shape  # B=批量, T=序列长度, C=隐藏维度
        # 拆分Q、K、V [B, T, 3*C] → 3个[B, T, C]
        q, k, v = self.c_attn(x).split(self.n_embd, dim=2)
        
        # 重塑为多头格式 [B, T, C] → [B, n_head, T, head_dim]
        q = q.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        k = k.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        v = v.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
        
        # 缩放点积注意力
        attn = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(self.head_dim))
        # 应用因果掩码:未来位置置为-inf,softmax后为0
        attn = attn.masked_fill(self.bias[:, :, :T, :T] == 0, float('-inf'))
        attn = F.softmax(attn, dim=-1)
        
        # 注意力输出拼接
        y = attn @ v
        y = y.transpose(1, 2).contiguous().view(B, T, self.n_embd)
        y = self.c_proj(y)
        return y

# -------------------------- 2. 前馈网络层 --------------------------
class MLP(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.c_fc = nn.Linear(config.n_embd, 4 * config.n_embd)
        self.c_proj = nn.Linear(4 * config.n_embd, config.n_embd)
        self.act = nn.GELU()  # GPT-2使用GELU激活函数

    def forward(self, x):
        x = self.c_fc(x)
        x = self.act(x)
        x = self.c_proj(x)
        return x

# -------------------------- 3. Transformer解码器块 --------------------------
class Block(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.ln_1 = nn.LayerNorm(config.n_embd)
        self.attn = CausalSelfAttention(config)
        self.ln_2 = nn.LayerNorm(config.n_embd)
        self.mlp = MLP(config)

    def forward(self, x):
        # 残差连接 + 层归一化(GPT-2使用Pre-LN结构)
        x = x + self.attn(self.ln_1(x))
        x = x + self.mlp(self.ln_2(x))
        return x

# -------------------------- 4. 完整GPT-2模型 --------------------------
class GPT2Config:
    """GPT-2配置类:可自由调整模型大小"""
    def __init__(
        self,
        vocab_size=50257,    # 词汇表大小
        seq_len=512,         # 最大序列长度
        n_embd=768,          # 隐藏层维度
        n_layer=12,          # 解码器层数
        n_head=12,           # 注意力头数
    ):
        self.vocab_size = vocab_size
        self.seq_len = seq_len
        self.n_embd = n_embd
        self.n_layer = n_layer
        self.n_head = n_head

class GPT2(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.config = config
        
        # 词嵌入 + 位置编码
        self.wte = nn.Embedding(config.vocab_size, config.n_embd)
        self.wpe = nn.Embedding(config.seq_len, config.n_embd)
        
        # 堆叠Transformer解码器层
        self.blocks = nn.ModuleList([Block(config) for _ in range(config.n_layer)])
        
        # 最终层归一化 + 输出层
        self.ln_f = nn.LayerNorm(config.n_embd)
        self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
        
        # 权重绑定:词嵌入层与输出层共享权重(减少参数量)
        self.wte.weight = self.lm_head.weight
        
        # 初始化权重
        self.apply(self._init_weights)

    def _init_weights(self, module):
        """权重初始化:GPT-2官方初始化策略"""
        if isinstance(module, (nn.Linear, nn.Embedding)):
            module.weight.data.normal_(mean=0.0, std=0.02)
            if isinstance(module, nn.Linear) and module.bias is not None:
                module.bias.data.zero_()
        elif isinstance(module, nn.LayerNorm):
            module.bias.data.zero_()
            module.weight.data.fill_(1.0)

    def forward(self, input_ids, attention_mask=None, labels=None):
        B, T = input_ids.shape
        assert T <= self.config.seq_len, "输入序列超过最大长度"
        
        # 1. 生成位置索引
        position_ids = torch.arange(0, T, dtype=torch.long, device=input_ids.device)
        position_ids = position_ids.unsqueeze(0).expand(B, T)
        
        # 2. 词嵌入 + 位置编码
        token_emb = self.wte(input_ids)
        pos_emb = self.wpe(position_ids)
        x = token_emb + pos_emb
        
        # 3. 堆叠解码器层前向传播
        for block in self.blocks:
            x = block(x)
        
        # 4. 最终归一化 + 输出logits
        x = self.ln_f(x)
        logits = self.lm_head(x)
        
        # 5. 计算损失(自回归语言建模损失)
        loss = None
        if labels is not None:
            # 移位预测:输入token预测下一个token
            shift_logits = logits[..., :-1, :].contiguous()
            shift_labels = labels[..., 1:].contiguous()
            loss = F.cross_entropy(
                shift_logits.view(-1, shift_logits.size(-1)),
                shift_labels.view(-1)
            )
        
        return logits, loss

# 初始化模型
config = GPT2Config()
model = GPT2(config)
print("GPT-2模型初始化完成!")

2.3 模型参数量计算

大模型参数量直接决定模型能力与训练成本,GPT-2 参数量可公式计算 + 代码验证

2.3.1 手动计算公式

GPT-2 核心参数量分为四部分:

  1. 词嵌入层vocab_size × n_embd = 50257×768 ≈ 3859万
  2. 位置编码层seq_len × n_embd = 512×768 = 39.3万
  3. Transformer 层(单块)
    1)注意力层:4 × n_embd² + 4 × n_embd(QKV 投影 + 输出投影)
    2)MLP 层:8 × n_embd² + 4 × n_embd
    3)层归一化:2 × n_embd × 2
    4)单块总参数量:12×n_embd² + 12×n_embd
  4. 最终层归一化2×n_embd
  5. 总参数量:嵌入层 + 层数 × 单块参数量

代入 GPT-2 小模型参数:总参数量 ≈ 1.24 亿(124M),与官方 GPT-2 Small 参数量完全一致。

2.3.2 代码自动计算

代码如下:

复制代码
def calculate_model_params(model):
    """计算模型总参数量与可训练参数量"""
    total_params = sum(p.numel() for p in model.parameters())
    trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
    print(f"模型总参数量:{total_params / 1e6:.2f} M(百万)")
    print(f"可训练参数量:{trainable_params / 1e6:.2f} M(百万)")
    return total_params, trainable_params

total_params, trainable_params = calculate_model_params(model)

输出结果:模型总参数量:124.44 M(百万)可训练参数量:124.44 M(百万)

2.4 模型显存占用分析

大模型训练中,显存占用分为模型权重、激活值、优化器状态、梯度四部分,是硬件选型的核心依据。

2.4.1 显存占用核心公式
  1. 模型权重显存参数量 × 字节数(FP32=4 字节,FP16=2 字节,BF16=2 字节)GPT-2 Small(FP32):124M × 4 = 496MB
  2. 梯度显存:与权重相等(FP32:496MB)
  3. 优化器状态显存 (AdamW):参数量 × 8字节(存储动量 + 方差)→ 992MB
  4. 激活值显存:与批量大小、序列长度正相关,GPT-2 Small(batch=8)≈ 500MB

总显存(FP32) :496+496+992+500 ≈ 2.48GB 混合精度(FP16)显存:约为 FP32 的 50%,≈1.2GB

2.4.2 代码显存测试

代码如下:

复制代码
# 测试模型前向+反向传播显存占用
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
model.train()

# 构造测试数据
test_input = input_batch.to(device)
test_mask = mask_batch.to(device)
test_label = label_batch.to(device)

# 清空显存缓存
torch.cuda.empty_cache()
torch.cuda.reset_peak_memory_stats()

# 前向+反向传播
logits, loss = model(test_input, test_mask, test_label)
loss.backward()

# 统计显存占用
print(f"峰值显存占用:{torch.cuda.max_memory_allocated(device) / 1024**3:.2f} GB")

实战结论

  • GPT-2 Small(124M)单卡训练:最低 2GB 显存即可运行;
  • 大模型(如 7B):FP16 训练需至少 13GB 显存,AdamW 优化器需 26GB 显存;
  • 工业界常用混合精度训练、梯度累积、模型并行降低显存占用。

2.5 模型训练与推理测试

完成模型构建后,可快速测试训练与推理流程:

复制代码
# 优化器
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-4, weight_decay=0.01)

# 单步训练测试
model.train()
for batch in train_dataloader:
    input_ids, mask, labels = [b.to(device) for b in batch]
    optimizer.zero_grad()
    logits, loss = model(input_ids, mask, labels)
    loss.backward()
    optimizer.step()
    print(f"训练损失:{loss.item():.4f}")
    break

# 推理测试(文本生成)
model.eval()
prompt = "artificial intelligence is"
input_ids = tokenizer.encode(prompt, return_tensors="pt").to(device)
with torch.no_grad():
    logits, _ = model(input_ids)
# 取最后一个token的预测结果
next_token_logits = logits[:, -1, :]
next_token_id = torch.argmax(next_token_logits, dim=-1).item()
generated_text = tokenizer.decode(input_ids[0].tolist() + [next_token_id])
print("生成文本:", generated_text)

第三部分 实战总结与工程化扩展

3.1 数据处理核心总结

  1. 开源数据集选择:Wikipedia(知识)+BookCorpus(长文本)+CommonCrawl(多样性)是最优组合;
  2. 预处理 pipeline:清洗→过滤→去重→分词四步缺一不可,直接决定模型质量;
  3. 分词技术:BPE 是 GPT 类模型标配,解决未登录词问题;
  4. DataLoader:批量加载 + 随机打乱 + 多线程是大模型训练的基础要求。

3.2 模型构建核心总结

  1. GPT-2 核心:纯 Transformer 解码器 + 掩码注意力 + 自回归预测
  2. 参数量:GPT-2 Small 为 124M,参数量与模型能力正相关;
  3. 显存优化:混合精度、梯度累积可大幅降低显存占用;
  4. 权重共享:词嵌入层与输出层共享权重,减少参数量,提升训练稳定性。

3.3 工程化扩展方向

  1. 数据层面:全量数据集训练、数据去重优化(SimHash)、领域数据微调(金融 / 医疗);
  2. 模型层面:GPT-3/LLaMA 架构扩展、FlashAttention 加速、LoRA 微调;
  3. 训练层面:分布式训练(DDP)、混合精度训练、学习率调度;
  4. 部署层面:模型量化、ONNX 导出、TensorRT 加速。

全文总结

本文完整实现了从零构建大模型数据收集预处理 + GPT-2 模型构建全流程实战:

  1. 数据端:接入三大开源数据集,完成清洗、去重、过滤、BPE 分词、DataLoader 构建;
  2. 模型端:从零实现 GPT-2 完整架构,包含词嵌入、位置编码、Transformer 解码器、损失计算;
  3. 分析端:完成模型参数量精准计算与显存占用分析;
  4. 代码端:全流程可运行,适配单机环境,可直接扩展为工业级大模型训练框架。
相关推荐
学点程序1 小时前
Manifest:帮个人 AI Agent 降低模型成本的开源路由器
人工智能·开源
可观测性用观测云2 小时前
观测云 x AI Agent:运维智能化的范式跃迁实践
人工智能
数数科技的数据干货2 小时前
ThinkingAI携手华为云,共建企业级AI Agent平台Agentic Engine
人工智能·ai·华为云·agent
人工智能AI技术2 小时前
春招急救:7天面试突击方案
人工智能
2603_954708312 小时前
如何确保微电网标准化架构设计流程的完整性?
网络·人工智能·物联网·架构·系统架构
小小AK2 小时前
钉钉与金蝶云星空无缝集成方案
大数据·人工智能·钉钉
不停喝水2 小时前
【AI+Cursor】 告别切图仔,拥抱Vibe Coding: AI + Cursor 开启多模态全栈新纪元 (1)
前端·人工智能·后端·ai·ai编程·cursor
水如烟2 小时前
孤能子视角:AI智能原理,“所有智能,都是茧房里的耦合“,以及人的主场
人工智能
Xxtaoaooo2 小时前
【开源】灵魂讲述者:基于魔珐星云的AI交互式分支叙事应用,免费体验啦!
人工智能·开源·ai数字人·魔法星云·小说创作