LLM 训练核心机制深度解析:Warmup、Cosine Decay 与 Perplexity 的完整知识体系

LLM 训练核心机制深度解析:Warmup、Cosine Decay 与 Perplexity 的完整知识体系

本文定位: 面向希望从"会用 LLM"进阶到"理解 LLM 如何被训练出来"的工程师与研究者。全文以训练全流程为主线,将学习率调度(Warmup + Cosine Decay)与模型评估(Perplexity)编织为一条完整的因果链,并给出可直接落地的工程实践。


目录


一、全景图

在训练一个数十亿乃至万亿参数的 LLM 时,核心循环可以抽象为:

复制代码
数据 → 前向传播 → Loss 计算 → 反向传播 → 参数更新 → 评估
         ↑                                    ↓
         └──────── 学习率调度器 ←──────────────┘

本文涉及的三个概念分别锚定在这条链路的三个关键节点上:

概念 锚定节点 核心职责
Warmup 参数更新(训练初期) 防止初期梯度噪声导致参数发散
Cosine Decay 参数更新(训练中后期) 平滑收敛至最优解邻域
Perplexity 评估 量化模型对语言的建模能力

一句话串联: Warmup 保证训练"活下来",Cosine Decay 保证训练"收敛好",Perplexity 告诉你训练"好不好"。


二、学习率预热(Warmup)

2.1 问题本质:为什么不能一上来就用大学习率?

训练初期存在三重不稳定因素:

  1. 参数随机初始化: 所有权重服从 N ( 0 , σ 2 ) \mathcal{N}(0, \sigma^2) N(0,σ2),梯度方向近似随机,方差极大。
  2. 归一化层统计量未收敛: LayerNorm / RMSNorm 的 running mean/variance 在前几十步内剧烈波动,导致梯度尺度不可控。
  3. 优化器动量冷启动: Adam/AdamW 的一阶矩 m t m_t mt 和二阶矩 v t v_t vt 初始为零,前几步的自适应步长估计严重偏大。

若此时施加峰值学习率(如 3 × 10 − 4 3 \times 10^{-4} 3×10−4),参数更新幅度远超损失曲面的局部曲率半径,直接后果是 Loss 爆炸 → NaN → 训练崩溃

2.2 数学定义

设 Warmup 总步数为 T w T_w Tw,目标峰值学习率为 η max ⁡ \eta_{\max} ηmax,则第 t t t 步( t ≤ T w t \le T_w t≤Tw)的学习率为:

η t = η max ⁡ ⋅ t T w \eta_t = \eta_{\max} \cdot \frac{t}{T_w} ηt=ηmax⋅Twt

这是线性 Warmup,也是 LLM 领域的绝对主流。少数工作使用指数 Warmup 或二次 Warmup,但实证差异可忽略。

2.3 Warmup 步数怎么选?

模型规模 典型 Warmup 步数 占比(总步数)
< 1B 500 -- 2,000 ~1%
1B -- 13B 2,000 -- 5,000 0.5% -- 1%
70B+ 2,000 -- 10,000 0.1% -- 0.5%

经验法则: Warmup 步数与模型规模弱相关,与 batch size 强相关。Batch size 越大,梯度估计越准,所需 Warmup 越短;反之亦然。

关键洞察: Warmup 本质上是在"用时间换稳定性"------牺牲极少量的训练预算(通常 < 1%),换取整个训练过程不发散。这是 LLM 训练中性价比最高的单一技巧。

2.4 Warmup 期间的梯度行为(实证观察)

复制代码
Loss
│  ╲  ← 无 Warmup:剧烈震荡甚至 NaN
│   ╲╱╲╱╲
│
│  ──── ← 有 Warmup:平滑下降
│       ╲
│        ╲
└──────────────────→ step

在 LLaMA-2 的公开训练日志中可以观察到:前 2000 步(Warmup 阶段)Loss 从 ~11 降至 ~4,曲线单调且平滑;若去掉 Warmup,Loss 在前 50 步即出现不可恢复的尖峰。


三、余弦退火(Cosine Decay)

3.1 数学定义

Warmup 结束后( t > T w t > T_w t>Tw),学习率按余弦曲线衰减:

η t = η min ⁡ + 1 2 ( η max ⁡ − η min ⁡ ) 1 + cos ⁡ ( t − T w T total − T w ⋅ π ) \eta_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})\left1 + \\cos\\left(\\frac{t - T_w}{T_{\\text{total}} - T_w} \\cdot \\pi\\right)\\right ηt=ηmin+21(ηmax−ηmin)1+cos(Ttotal−Twt−Tw⋅π)

其中:

  • η max ⁡ \eta_{\max} ηmax:峰值学习率(Warmup 终点)
  • η min ⁡ \eta_{\min} ηmin:最终学习率,通常取 0.1 × η max ⁡ 0.1 \times \eta_{\max} 0.1×ηmax
  • T total T_{\text{total}} Ttotal:总训练步数

3.2 余弦曲线的三段特征

将余弦衰减按进度分为三段,每段对应不同的优化语义:

阶段 进度 曲线特征 优化语义
早期衰减 0% -- 30% 下降极缓 维持较大步长,快速穿越损失曲面的高曲率区域
中期衰减 30% -- 70% 下降最快 逐步缩小搜索半径,逼近局部最优
末期衰减 70% -- 100% 下降极缓,趋于平坦 精细搜索,在最优解邻域内充分探索

关键洞察: Cosine Decay 的核心价值在于末期的"长尾微调"效应。相比 Linear Decay 的匀速下降,Cosine 在训练最后 20% 的步数里,学习率变化极小,等价于自动执行了一段低学习率的 fine-tuning,使最终 Loss 通常低 0.5% -- 2%。

3.3 为什么 η min ⁡ ≠ 0 \eta_{\min} \neq 0 ηmin=0?

将 η min ⁡ \eta_{\min} ηmin 设为 0 意味着训练末期完全停止更新,这会导致:

  • 梯度信号被浪费(仍有数据输入但不更新参数)
  • 若训练因故需要延长(如追加数据),无法平滑续训

设为 0.1 × η max ⁡ 0.1 \times \eta_{\max} 0.1×ηmax 是 GPT-3、LLaMA、Qwen 等模型的共同选择,兼顾了末期收敛与续训灵活性。

3.4 与其他调度策略的定量对比

以下对比基于 GPT-2 规模(355M)模型在 OpenWebText 上训练 100B tokens 的公开复现结果:

调度策略 最终 PPL 训练稳定性 超参敏感度
Constant 22.4
Linear Decay 21.1
Cosine Decay 20.7
Step Decay (×0.1 @50%, ×0.01 @80%) 21.5 中(阶梯处 Loss 跳变)
Inverse Sqrt 21.3

Cosine Decay 在最终 PPL 和鲁棒性上均取得最优平衡。


四、黄金组合

4.1 完整学习率曲线

将 Warmup 与 Cosine Decay 拼接,得到 LLM 训练的标准学习率曲线:

复制代码
η (学习率)
│
η_max ┤          ╭──────────╮
      │        ╱              ╲
      │      ╱                  ╲
      │    ╱                      ╲
      │  ╱                          ╲
η_min ┤╱                              ╲___________
      │
      └──┬──────────────────────────────────────→ step
         0    T_w                    T_total
         ├─Warmup─┤├───Cosine Decay────────────┤

4.2 为什么二者缺一不可?

  • 只有 Cosine Decay,没有 Warmup: 训练从 η max ⁡ \eta_{\max} ηmax 起步 → 初期梯度爆炸 → Loss NaN。
  • 只有 Warmup,没有 Decay: 学习率恒定在 η max ⁡ \eta_{\max} ηmax → 末期在最优解附近大幅震荡,无法收敛。
  • 二者结合: 先"安全起飞"(Warmup),再"平稳着陆"(Cosine Decay),覆盖训练全生命周期。

4.3 超参配置速查表(以 7B 模型为例)

超参 典型值 说明
η max ⁡ \eta_{\max} ηmax 3 × 10 − 4 3 \times 10^{-4} 3×10−4 与 batch size、模型规模相关
η min ⁡ \eta_{\min} ηmin 3 × 10 − 5 3 \times 10^{-5} 3×10−5 0.1 × η max ⁡ 0.1 \times \eta_{\max} 0.1×ηmax
T w T_w Tw(Warmup 步数) 2,000 约占总步数 0.5%
T total T_{\text{total}} Ttotal 400,000 由总 token 数 / (batch_size × seq_len) 决定
优化器 AdamW ( β 1 = 0.9 , β 2 = 0.95 \beta_1=0.9, \beta_2=0.95 β1=0.9,β2=0.95) β 2 \beta_2 β2 取 0.95 而非默认 0.999
权重衰减 0.1 与 LR 解耦

五、困惑度(Perplexity)

5.1 从交叉熵到困惑度

语言模型训练的目标函数是交叉熵损失(Cross-Entropy Loss)

$$\mathcal{L} = -\frac{1}{N}\sum_{i=1}^{N} \log P_\theta(x_i \mid x_{ 注意: PPL 的理论下界是 1(而非 0),上界是词表大小 V V V(对于均匀分布)。一个 PPL = 15 的模型意味着它在每个位置的有效选择空间约为 15 个 token。

5.3 计算细节与陷阱

陷阱一:Token 粒度 vs. 字节粒度

不同 tokenizer 将同一文本切分为不同数量的 token。英文文本中:

  • GPT-2 tokenizer:~1.3 tokens/word
  • LLaMA tokenizer:~1.1 tokens/word
  • 字节级 BPE:~3.5 bytes/word

直接比较不同 tokenizer 模型的 PPL 是无意义的。 公平比较需换算为 bits-per-byte (BPB)bits-per-character (BPC)

BPB = L × N tokens N bytes × ln ⁡ 2 \text{BPB} = \frac{\mathcal{L} \times N_{\text{tokens}}}{N_{\text{bytes}} \times \ln 2} BPB=Nbytes×ln2L×Ntokens

陷阱二:测试集选择

PPL 高度依赖测试集的领域分布:

测试集 特点 适用场景
WikiText-103 百科全书,正式文体 通用语言能力基准
C4 validation 网页文本,多样 预训练分布内评估
Code(如 GitHub) 代码语法 代码模型评估
对话数据 口语化、短文本 对话模型评估

报告 PPL 时必须注明测试集,否则数字无意义。

陷阱三:滑动窗口 vs. 全序列

对于超过模型上下文长度的测试文本,有两种计算方式:

  • 全序列截断: 只取前 L L L 个 token,简单但丢失信息
  • 滑动窗口(stride = 512/1024): 多窗口取平均,更准确但计算量大

HuggingFace 的 perplexity 评估脚本默认使用滑动窗口,stride 通常设为 512。

5.4 PPL 的局限性:它不能告诉你什么

这是理解 PPL 最重要的一节:

PPL 能衡量的 PPL 不能衡量的
下一个 token 的预测准确率 事实正确性(模型可以流畅地编造)
语言流畅度 指令遵循能力
文本分布的拟合程度 推理与逻辑能力
训练收敛程度 安全性、对齐质量
不同 checkpoint 的相对优劣 用户体验 / 主观偏好

经典反例: 一个在维基百科上 PPL = 12 的模型,可能在"法国首都是哪里"这个问题上自信地回答"柏林"。PPL 低只说明它学会了"用流畅的语言说话",不保证"说的是真话"。

这就是为什么现代 LLM 评估必须结合外在基准:

评估维度 代表基准
知识 MMLU, TriviaQA
推理 GSM8K, MATH, ARC
代码 HumanEval, MBPP
指令遵循 IFEval, MT-Bench
综合主观 Chatbot Arena (Elo)

六、三者的因果闭环

将三个概念串联起来,形成一条完整的因果链:

复制代码
┌─────────────────────────────────────────────────────────┐
│                    LLM 训练因果链                        │
│                                                         │
│  Warmup ──→ 训练不崩溃 ──→ 参数进入有效优化区间           │
│     │                                                   │
│     ▼                                                   │
│  Cosine Decay ──→ 末期精细收敛 ──→ Loss 达到下界         │
│     │                                                   │
│     ▼                                                   │
│  Loss (Cross-Entropy) ──→ exp() ──→ Perplexity          │
│     │                                                   │
│     ▼                                                   │
│  PPL 作为反馈信号 ──→ 指导超参调整 ──→ 优化 Warmup/Decay  │
│                                                         │
└─────────────────────────────────────────────────────────┘

具体而言:

  1. Warmup → Cosine Decay: Warmup 决定了 Cosine Decay 的起点( η max ⁡ \eta_{\max} ηmax)。Warmup 不充分会导致前几百步 Loss 异常,进而污染整个 Cosine 曲线的收敛轨迹。

  2. Cosine Decay → Loss: Cosine Decay 的末期平坦段直接决定了最终 Loss 能达到的下界。实验表明,将 η min ⁡ \eta_{\min} ηmin 从 0 提高到 0.1 η max ⁡ 0.1\eta_{\max} 0.1ηmax,最终 Loss 可降低约 0.3% -- 0.8%。

  3. Loss → PPL: PPL = e L \text{PPL} = e^{\mathcal{L}} PPL=eL,这是一个单调映射。Loss 每降低 0.1,PPL 大约降低 1 − e − 0.1 ≈ 9.5 % 1 - e^{-0.1} \approx 9.5\% 1−e−0.1≈9.5%。

  4. PPL → 超参调整: 如果验证集 PPL 在训练中期出现平台期或上升,通常意味着:

    • 学习率过大 → 降低 η max ⁡ \eta_{\max} ηmax
    • Warmup 不足 → 增加 T w T_w Tw
    • 数据质量问题 → 清洗训练数据

七、工程实践

7.1 PyTorch 实现

python 复制代码
import math
import torch
from torch.optim.lr_scheduler import LambdaLR

def get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps: int,
    num_training_steps: int,
    min_lr_ratio: float = 0.1,
    last_epoch: int = -1,
):
    """
    Warmup + Cosine Decay 学习率调度器。
    
    Args:
        optimizer: 优化器
        num_warmup_steps: Warmup 步数
        num_training_steps: 总训练步数
        min_lr_ratio: 最小学习率占峰值的比例(默认 0.1)
    """
    def lr_lambda(current_step: int) -> float:
        # Phase 1: Linear Warmup
        if current_step < num_warmup_steps:
            return current_step / max(1, num_warmup_steps)
        
        # Phase 2: Cosine Decay
        progress = (current_step - num_warmup_steps) / max(
            1, num_training_steps - num_warmup_steps
        )
        cosine_decay = 0.5 * (1.0 + math.cos(math.pi * progress))
        return min_lr_ratio + (1.0 - min_lr_ratio) * cosine_decay

    return LambdaLR(optimizer, lr_lambda, last_epoch)


# ── 使用示例 ──
model = MyLLM()  # 你的模型
optimizer = torch.optim.AdamW(
    model.parameters(),
    lr=3e-4,
    betas=(0.9, 0.95),
    weight_decay=0.1,
)

total_steps = 400_000
warmup_steps = 2_000

scheduler = get_cosine_schedule_with_warmup(
    optimizer,
    num_warmup_steps=warmup_steps,
    num_training_steps=total_steps,
    min_lr_ratio=0.1,
)

for step, batch in enumerate(dataloader):
    loss = model(**batch).loss
    loss.backward()
    
    torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
    
    optimizer.step()
    scheduler.step()
    optimizer.zero_grad()
    
    if step % 100 == 0:
        print(f"Step {step} | LR: {scheduler.get_last_lr()[0]:.2e} | Loss: {loss.item():.4f}")

7.2 HuggingFace Transformers 内置方案

python 复制代码
from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir="./output",
    learning_rate=3e-4,
    lr_scheduler_type="cosine",       # ← Cosine Decay
    warmup_steps=2000,                # ← Linear Warmup
    max_steps=400000,
    adam_beta1=0.9,
    adam_beta2=0.95,
    weight_decay=0.1,
    bf16=True,
    gradient_accumulation_steps=4,
    logging_steps=10,
)

7.3 Perplexity 计算脚本

python 复制代码
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
import math

def compute_perplexity(model_name: str, text: str, stride: int = 512):
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16)
    model.eval()
    
    encodings = tokenizer(text, return_tensors="pt")
    max_length = model.config.n_positions  # 模型最大上下文长度
    seq_len = encodings.input_ids.size(1)
    
    nlls = []
    prev_end_loc = 0
    
    for begin_loc in range(0, seq_len, stride):
        end_loc = min(begin_loc + max_length, seq_len)
        trg_len = end_loc - prev_end_loc  # 实际计算 loss 的长度
        
        input_ids = encodings.input_ids[:, begin_loc:end_loc]
        target_ids = input_ids.clone()
        target_ids[:, :-trg_len] = -100  # 只计算新 token 的 loss
        
        with torch.no_grad():
            outputs = model(input_ids, labels=target_ids)
            neg_log_likelihood = outputs.loss * trg_len
        
        nlls.append(neg_log_likelihood)
        prev_end_loc = end_loc
        
        if end_loc == seq_len:
            break
    
    ppl = torch.exp(torch.stack(nlls).sum() / seq_len)
    return ppl.item()

7.4 推理服务预热(System Warmup)

注意区分:部署场景下的 "Warmup" 指的是服务预热,与训练学习率预热无关。

python 复制代码
# vLLM 推理服务预热示例
from vllm import LLM, SamplingParams

llm = LLM(model="Qwen/Qwen2.5-7B-Instruct")

# 发送 dummy 请求,触发 CUDA kernel 编译 + KV Cache 分配
dummy_params = SamplingParams(max_tokens=1)
for _ in range(3):
    llm.generate(["warmup"] * 8, dummy_params)  # batch warmup

print("✅ 服务预热完成,可接入真实流量")

八、进阶话题与前沿变体

8.1 WSD 调度(Warmup-Stable-Decay)

2024 年由 MiniCPM 团队提出的替代方案:

复制代码
η
│    ┌──────────────────┐
│   ╱                    ╲
│  ╱                      ╲
│ ╱                        ╲____
└──────────────────────────────→ step
  Warmup    Stable     Decay
  • Stable 阶段: 学习率恒定在 η max ⁡ \eta_{\max} ηmax,持续大部分训练时间
  • 优势: 支持"随时截断 + 快速 Decay",适合数据量不确定的持续预训练
  • 现状: 已在多个国产大模型中采用,但尚未取代 Cosine Decay 的主流地位

8.2 Cosine Annealing with Warm Restarts

周期性地将学习率重置回 η max ⁡ \eta_{\max} ηmax:

η t = η min ⁡ + 1 2 ( η max ⁡ − η min ⁡ ) ( 1 + cos ⁡ ( T cur   m o d   T i T i ⋅ π ) ) \eta_t = \eta_{\min} + \frac{1}{2}(\eta_{\max} - \eta_{\min})\left(1 + \cos\left(\frac{T_{\text{cur}} \bmod T_i}{T_i} \cdot \pi\right)\right) ηt=ηmin+21(ηmax−ηmin)(1+cos(TiTcurmodTi⋅π))

  • 用途: 持续预训练、多阶段微调
  • 注意: 基座模型预训练通常不使用重启,因为每次重启都会短暂破坏已收敛的参数

8.3 学习率与模型规模的 Scaling 关系

模型参数量 推荐 η max ⁡ \eta_{\max} ηmax 推荐 Batch Size (tokens)
125M 6 × 10 − 4 6 \times 10^{-4} 6×10−4 0.5M
1.3B 3 × 10 − 4 3 \times 10^{-4} 3×10−4 1M
7B 3 × 10 − 4 3 \times 10^{-4} 3×10−4 4M
70B 1.5 × 10 − 4 1.5 \times 10^{-4} 1.5×10−4 4M
405B 8 × 10 − 5 8 \times 10^{-5} 8×10−5 8M

趋势: 模型越大,峰值学习率越低,batch size 越大。这与损失曲面的曲率随参数量增大而变尖锐有关。

8.4 PPL 的替代与补充指标

指标 优势 适用场景
Bits-per-byte (BPB) 跨 tokenizer 可比 多模型横向评测
CE Loss(直接报告) 无指数放大,数值更稳定 训练日志监控
Accuracy@1 (next token) 直觉友好 快速 sanity check
Calibration Error 衡量模型置信度是否准确 对齐与可靠性评估

九、常见误区与 FAQ

Q1:Warmup 步数越多越安全吗?

不是。 过长的 Warmup(如占总步数 > 5%)会浪费训练预算在低学习率的无效更新上。实证表明,超过 2% 后收益递减,甚至因为"有效训练步数减少"而导致最终 PPL 上升。

Q2:可以把 η min ⁡ \eta_{\min} ηmin 设为 0 吗?

技术上可以,但不推荐。 设为 0 意味着训练末期完全停止参数更新。如果训练中途需要追加数据或延长训练,LR 已归零将无法继续。 0.1 × η max ⁡ 0.1 \times \eta_{\max} 0.1×ηmax 是更安全的选择。

Q3:PPL 降低了 1 个点,实际体验会有差别吗?

通常没有可感知的差别。 PPL 从 15 降到 14 对应 Loss 降低约 0.07,这在主观体验上几乎无法区分。PPL 的显著差异(如 15 vs. 25)才对应可感知的质量差距。

Q4:不同框架(Megatron、DeepSpeed、FSDP)的 Cosine Decay 实现一致吗?

核心公式一致,但细节有差异:

  • 步数计数: 有的按 optimizer step 计,有的按 micro-batch 计(gradient accumulation 时需注意)
  • η min ⁡ \eta_{\min} ηmin 默认值: 有的默认 0,有的默认 0.1 η max ⁡ 0.1\eta_{\max} 0.1ηmax
  • Warmup 形状: 极少数实现用指数 Warmup 而非线性

建议: 在训练日志中打印实际 LR 曲线,与理论曲线对比验证。

Q5:微调(Fine-tuning)时还需要 Warmup + Cosine Decay 吗?

需要,但参数不同:

  • Warmup 步数通常更短(50 -- 200 步)
  • 峰值学习率更低( 1 × 10 − 5 1 \times 10^{-5} 1×10−5 到 5 × 10 − 5 5 \times 10^{-5} 5×10−5)
  • 总步数更少(几百到几千步)
  • Cosine Decay 仍然是首选调度策略

Q6:PPL 能用来判断模型是否过拟合吗?

可以,且是最直接的信号。 如果训练集 PPL 持续下降而验证集 PPL 开始上升,即为过拟合。但在 LLM 预训练中,由于数据量通常远大于模型容量(Chinchilla 最优比例),过拟合较少出现,更多见于小规模微调场景。


十、参考文献

  1. Vaswani, A., et al. (2017). Attention Is All You Need. NeurIPS.
  2. Brown, T., et al. (2020). Language Models are Few-Shot Learners. NeurIPS. (GPT-3)
  3. Touvron, H., et al. (2023). LLaMA: Open and Efficient Foundation Language Models. arXiv:2302.13971.
  4. Hoffmann, J., et al. (2022). Training Compute-Optimal Large Language Models. (Chinchilla)
  5. Hu, S., et al. (2024). MiniCPM: Unveiling the Potential of Small Language Models with Scalable Training Strategies. arXiv:2404.06395. (WSD)
  6. Loshchilov, I., & Hutter, F. (2017). SGDR: Stochastic Gradient Descent with Warm Restarts. ICLR.
  7. Loshchilov, I., & Hutter, F. (2019). Decoupled Weight Decay Regularization. ICLR. (AdamW)

总结: Warmup 是 LLM 训练的"安全阀",Cosine Decay 是"着陆引导系统",Perplexity 是"仪表盘"。三者共同构成了大模型训练工程的最小完备知识集------理解它们,你就掌握了阅读任何 LLM 训练论文和复现任何开源模型训练流程的基础语言。

相关推荐
小绫网络安全1 小时前
【每日一讲】学习网络安全的一些注意事项
网络·安全·web安全
一只旭宝1 小时前
细讲C加加【9】C++ std::function与std::bind详解|仿函数、绑定器、类成员绑定、占位符、成员偏移指针
开发语言·c++·算法
龙虾PRO1 小时前
把握人工智能时代机遇,夯实科技强国建设根基
人工智能·科技·百度
良木林2 小时前
子串 - LeetCode hot 100
算法·leetcode·职场和发展
冬奇Lab2 小时前
企业知识库系列(00):在写第一行代码之前,先把评测数据集做好
人工智能
二川bro2 小时前
Obsidian+AI自生长知识库,告别无效笔记整理
人工智能
冬奇Lab2 小时前
开源项目第184期:MiroFish — 盛大出品的群体智能预测引擎,用数千 AI Agent 模拟社会演化来预测未来
人工智能·开源·资讯
陌诺曦.2 小时前
Python扩展小练习
算法