Mistral 首个时序基础模型 Timestral:209M 参数在 CPU 上拿第一,权重许可证却换成了一道收入红线
一个只有 209M 参数的模型,在没有 GPU 的八线程 CPU 上,用 140 毫秒读完三周的十五分钟粒度历史,吐出未来一天的 21 个分位点。它的小兄弟更便宜:5M 版本只要 10 毫秒。
2026 年 10 月 15 日,Mistral 发布第一组时序基础模型(TSFM),家族名 Timestral。官方口径是:最大那个模型在 GIFT-Eval、TIME、fev-bench 三个主要公开基准上,「在预训练加零样本模型里排第一」,且只用三分之二的参数量超过此前最好成绩。
值得单独写的原因有两个:它的部署形态真的落在 CPU 上;而交付物被切成两套许可——代码 Apache 2.0,权重 Modified MIT,并带一条月收入 2000 万美元的红线。官方博客许可证一栏只写了「Modified MIT」四个字,红线藏在权重仓库附带的那份全文里。
官方给的四件事和一个数字
官方确认:四个规模 5M / 28M / 123M / 209M 参数,共享同一套架构与训练配方;上下文 32,768 个时间步;输出是概率预测,每个时间步 21 个分位点;输入是多变量目标 + 仅历史协变量 + 已知未来协变量;部署支持本地机房、VPC 与气隙环境;许可证一栏写 Modified MIT。
关键数字是上下文长度。它不是 token,而是每个序列变量各 32,768 个历史点,按官方延迟示例的十五分钟粒度折合 341.33 天(约 48.8 周,接近一年)。而官方延迟演示的「三周历史」只有 2016 步、仅占上限的 6.2%(我算的)——140 毫秒是在远未喂满上下文时测出来的,灌满一年数据后延迟如何变,官方未说明。
架构口径:patch 32、维度 768、12 头、MLP 隐层 2048、norm_eps 1e-06、注意力不带 bias;位置编码是 RoPE,rope_theta 10000.0,只对一半维度做旋转。我实测的 config.json 比官方表格多出两个细节,都是我从配置里数出来的:
第一,21 个分位点的位置。 quantile_knots 共 21 项,从 0.01 到 0.99 均匀排开。它输出的不是「一个数加一个区间」,而是一条完整的分位函数——中位数取第 11 档,90% 上界取最后一档。
第二,24 层里只有 6 层做联合注意力。 block_schedule 共 24 项,其中 6 项是 time_variate,其余 18 项是 time:四分之三的层只看时间轴,只有四分之一同时看「时间 × 变量」。官方博客没提这个比例,官方未说明为什么这么切。
训练配方里最值得抄的两条
官方列了四个要点,前两个常规,后两个值得抄:合成数据优先(大部分训练数据是合成的,显式注入趋势、季节性、噪声与跨序列非线性依赖);协变量感知注意力(每条序列拆成目标、仅历史协变量、已知未来协变量三类,信息只能从协变量流向目标——没有这条约束,模型会学会「偷看」离线时恰好存在的未来列)。
两阶段训练:先在 8,192 上下文上预训练合成数据,再在混合数据上以 4 倍更长上下文做后训练;损失是21 个分位点上的 pinball loss。Decoupled Horizon Forecasting(DHF):用专门的预测 token 读取因果编码的历史,观测值留在上下文里而不被掩码——官方明确说这与 Toto 2.0、TimesFM-3 的连续 patch 掩码不同。单次前向最多预测 1,024 步,更长视界按块推进、把每块中位数回喂当上下文;每个预测 patch 用上下文统计量与本块已预测中位数做反归一化。于是「一天 96 步一次前向够用;要填满 32,768 步上限,得分 32 块推进」(32768 ÷ 1024,我算的)——上下文 32,768 和单次前向 1,024 必须分开读。微调走 Mistral Forge:零样本推理、架构扩展(领域专属协变量)、私有数据微调、控制训练配比、回放预训练数据、生成合成样本,全流程可部署在客户硬件上,含气隙环境。
免费下载的是代码,不是权重的使用权
这是本文最想讲清的一件事,也是我 2026-10-26 亲手核到的最大落差。
我实测核对:GitHub 仓库 mistralai/timestral 创建于 2026-10-15(UTC 10:20)、只有一个 commit、星标 876(2026-10-26 读数),license 字段显示 Apache-2.0;仓库只提供独立推理代码,依赖仅 PyTorch、NumPy、safetensors、huggingface_hub,CPU 或 GPU 都能跑,安装即 pip install git+https://github.com/mistralai/timestral.git。权重在 Hugging Face:四个仓库创建于 2026-10-14(UTC),209M 08:52、5M 13:41、28M 与 123M 在 17:50 左右——权重仓库比代码仓库早一天建好。
真正的落差在这里:四个权重仓库的许可证标签都是 license:other,不是 apache-2.0,各带一份 Modified MIT License 全文,署名行写「2026 - Mistral AI」。「代码 Apache 2.0」与「权重 Modified MIT」是两套许可:前者只覆盖 GitHub 那份推理代码。该许可证的第 2 条最该落进流程:
如果你的公司(或雇主)上一个月的全球合并月收入超过 2000 万美元(或等值其他货币),你就不再被授权行使本许可证下的任何权利。
这句射程很宽,同样适用于基于该模型派生的作品、修改与组合作品,不论衍生品由 Mistral 还是第三方提供。越线后的官方出口只有两个:联系 sales@mistral.ai 申请商业许可(许可证写明 Mistral 有完全裁量权),或改用 Mistral 的托管服务。
| 你拿到的东西 | 载体 | 许可 | 触发限制的条件 |
|---|---|---|---|
| 推理代码 | GitHub mistralai/timestral | Apache 2.0 | 无 |
| 四套权重 | Hugging Face 权重仓库 | Modified MIT(标签 license:other) | 公司/雇主上月全球合并月收入 > 2000 万美元 |
| 衍生作品、修改、组合作品 | 任意 | 继承 Modified MIT | 同上,第三方提供的衍生品也适用 |
这处不一致不是我的独家发现——第三方报道也提到「GitHub 标 Apache 2.0 而权重带限制性条款」,与我的核对一致。两个方向都别过度解读:别读成闭源,红线以下的企业可免费下载、自建、进气隙机房;也别读成随便用,红线按雇主口径算且延伸到衍生品。挂在「开源权重」标签下面的,是一份带商业规模门槛的许可。
209M 在 CPU 上的延迟账
官方延迟是在八线程普通服务器 CPU上测的,场景是三周十五分钟粒度历史预测未来一天,并写明推理不需要 GPU。官方没直接给的两档我按官方口径换算(换算过程标在表后):
| 规模 | 权重文件(我实测) | 官方 README 表 | 单次延迟(官方口径) | 每晚 5000 条序列(我算的) |
|---|---|---|---|---|
| 5M | 约 19.9 MiB | 21MB | 10 ms | 50.0 s |
| 28M | 约 106.1 MiB | 111MB | 28 ms(换算) | 140.0 s |
| 123M | 约 467.7 MiB | 490MB | 105 ms(换算) | 525.0 s |
| 209M | 约 798.5 MiB | 837MB | 140 ms | 700.0 s |
官方给了 5M 10 毫秒、209M 140 毫秒,并说 28M 比 209M 快 5 倍、123M 延迟是 209M 的四分之三,所以 28 ms = 140 ÷ 5、105 ms = 140 × 0.75 是我的换算,不是官方原值。权重两种口径也对齐:官方 README 的 21MB / 111MB / 490MB / 837MB 是十进制 MB,我核到的字节数换成 MiB 是约 19.9 / 106.1 / 467.7 / 798.5 MiB——同一文件用 MB 报比 MiB 报大 4.9%,两处不矛盾。
表里最有信息量的是 123M:分数只比 209M 低 0.2%,延迟却是它的四分之三;夜批窗口紧张时这就是甜点。最后一列是我算的:每晚 5000 条序列、单线程依次调用,全家桶从 50 秒到 11.67 分钟,都塞得进一个夜批窗口。
协变量与 21 个分位点,在读什么
输入侧三类序列:目标、仅历史协变量(只能看过去,如实际客流)、已知未来协变量(未来已确定,如日历与已排定促销)。这不是学术洁癖,而是防泄漏的接口约定——模型内部注意力只允许协变量流向目标,你拼数据时也必须守同一约定,否则离线好看、上线就崩。
输出侧 21 档分位:返回形状 (series, variates, horizon, quantile levels)。预测 96 步、21 档,单变量就是 96 × 21 = 2016 个数(我算的),输出体积是点预测模型的 21 倍。分位点必须单调不减,这是它唯一的硬约束:第 11 档(中位数)不能低于第 10 档,否则「90% 安全水位」可能低于中位数,区间告警直接失去意义。这类失真叫 quantile crossing(分位交叉)——能不能自己查?能,我本机跑了一段。
上手:官方 README 的调用示例
前置三条:先 pip install git+https://github.com/mistralai/timestral.git;首次调用会自动下载约 798 MiB 权重;CPU 可直接跑,不需要 GPU。
# 官方 README 的调用形态:pip install git+https://github.com/mistralai/timestral.git
# 首次运行会下载约 798 MiB 的权重;CPU 可跑,不需要 GPU
from timestral import TimestralForecaster
model = TimestralForecaster.from_pretrained("mistralai/Timestral-209M")
# context 形状 (series, variates, time):多变量目标 + 仅历史协变量 + 已知未来协变量
forecast = model.forecast(context, prediction_length=96)
print(forecast.shape) # (series, variates, horizon, quantile levels)
print(forecast.median.shape) # 中位数:第 11 档分位点
print(forecast.quantile(0.9)) # 取 90% 上界,做安全水位
prediction_length=96 是十五分钟粒度下的一天,forecast.shape 最后一维就是 21 档;中位数取 forecast.median,任意档位取 forecast.quantile(0.9)。
我本机跑过的三段校验
下面这段代码不需要任何 key、不下载权重,只用标准库,做三件事:检查 21 分位矩阵的单调性并给出交叉反例与修法;用 pinball loss 对比分位预测与季节性朴素基线;按官方延迟口径算工程账。
#!/usr/bin/env python3
# timestral_check.py —— 只用标准库,不需要任何 key、不下载权重
# (a) 21 分位预测矩阵的单调性检查(quantile crossing)+ cummax 修复
# (b) 合成季节+趋势序列上:分位预测 vs 季节性朴素基线的 pinball loss
# (c) 按官方延迟口径算工程账:上下文长度换算 + 每晚 5000 条序列的总耗时
import random
from math import ceil, pi, sin
from statistics import NormalDist
random.seed(20261015)
ND = NormalDist()
# ---- (a) 官方口径的 21 个分位点:0.01 到 0.99
QUANTILES = [round(0.01 + 0.98 * i / 20, 2) for i in range(21)]
Z = [ND.inv_cdf(p) for p in QUANTILES] # 标准正态逆 CDF,用来造"干净"的分位矩阵
def crossing(rows):
"""数出违反 q_i <= q_{i+1} 的行数,并返回第一个反例"""
bad, first = 0, None
for i, r in enumerate(rows):
for j in range(len(r) - 1):
if r[j] > r[j + 1] + 1e-9: # 低分位的值高过高分位 = 交叉
bad += 1
if first is None:
first = (i, QUANTILES[j], QUANTILES[j + 1], r[j], r[j + 1])
break
return bad, first
def cummax(r):
"""单调修复:分位点取累积最大值,保证整行不再交叉"""
out, m = [], float("-inf")
for v in r:
m = max(m, v)
out.append(m)
return out
med = [100.0 + 3 * i for i in range(8)] # 8 条序列的中心
sig = [2.0, 4.0, 1.5, 6.0, 3.0, 2.5, 5.0, 1.8] # 各自的尺度
clean = [[round(med[i] + sig[i] * Z[j], 4) for j in range(21)] for i in range(8)]
print("--- (a) 21 分位矩阵的单调性检查")
print(f"分位点数 {len(QUANTILES)}:{QUANTILES[0]} / {QUANTILES[10]} / {QUANTILES[-1]}")
print(f"干净矩阵({len(clean)} 条序列 × 21 档):违反单调的行数 = {crossing(clean)[0]}")
dirty = [r[:] for r in clean]
dirty[2][5], dirty[2][6] = dirty[2][6] + 1.5, dirty[2][5] # 第 5/6 档互换并抬高
dirty[5][15] = dirty[5][14] + 2.0 # 第 15 档压过第 16 档
n, bad = crossing(dirty)
print(f"注入交叉后:违反单调的行数 = {n}")
print(f"第一个反例 序列#{bad[0]}:q_{bad[1]}={bad[3]} > q_{bad[2]}={bad[4]}")
fixed = [cummax(r) for r in dirty]
print(f"cummax 修复后:违反单调的行数 = {crossing(fixed)[0]}")
print(f"修复后 序列#2 前 8 档:{fixed[2][:8]}")
# ---- (b) pinball loss 对比
print()
print("--- (b) pinball loss:分位预测 vs 季节性朴素基线")
T, PERIOD, HORIZON = 336, 24, 48 # 14 天小时级,24 小时季节,预测 2 天
y = [50.0 + 0.08 * t + 6.0 * sin(2 * pi * t / PERIOD) + random.gauss(0, 1.5)
for t in range(T)]
train, test = y[:T - HORIZON], y[T - HORIZON:]
base = [train[len(train) - PERIOD + h % PERIOD] for h in range(HORIZON)] # 季节性朴素
resid = sorted(train[t] - train[t - PERIOD] for t in range(PERIOD, len(train)))
def emp_q(sorted_resid, alpha):
return sorted_resid[min(len(sorted_resid) - 1, max(0, ceil(alpha * len(sorted_resid)) - 1))]
def pinball(y_true, q, alpha):
return sum(alpha * (yt - qv) if yt >= qv else (1 - alpha) * (qv - yt)
for yt, qv in zip(y_true, q))
print(f"序列 {T} 点({T // PERIOD} 个周期),训练 {len(train)} / 测试 {HORIZON},残差样本 {len(resid)}")
print(f"{'alpha':>6} {'分位预测':>12} {'季节朴素':>12} {'差值':>10}")
for a in (0.1, 0.5, 0.9):
q = [b + emp_q(resid, a) for b in base] # 基线点预测 + 训练残差的经验分位
lq, lb = pinball(test, q, a), pinball(test, base, a)
print(f"{a:>6} {lq:>12.4f} {lb:>12.4f} {lq - lb:>+10.4f}")
lo = [b + emp_q(resid, 0.1) for b in base]
hi = [b + emp_q(resid, 0.9) for b in base]
cov = sum(1 for yt, a, b in zip(test, lo, hi) if a <= yt <= b) / HORIZON
print(f"0.1–0.9 区间实测覆盖率 = {cov:.4f}(名义 0.80)")
print(f"训练残差在 alpha=0.5 的经验分位 = {emp_q(resid, 0.5):+.4f}")
# ---- (c) 工程账
print()
print("--- (c) 按官方口径算工程账")
STEPS, GRAN_MIN, N_SERIES = 32768, 15, 5000
days = STEPS * GRAN_MIN / 60 / 24
print(f"上下文 {STEPS} 个时间步 × {GRAN_MIN} 分钟 = {STEPS * GRAN_MIN} 分钟 "
f"= {STEPS * GRAN_MIN / 60:.0f} 小时 = {days:.2f} 天(约 {days / 7:.1f} 周)")
LAT = {"5M": 10.0, "28M": 140.0 / 5, "123M": 140.0 * 0.75, "209M": 140.0} # 官方延迟
print(f"每晚 {N_SERIES} 条序列、单线程依次调用、每条一次前向:")
print(f"{'规模':>5} {'单次延迟':>10} {'总耗时':>10} {'折合':>11}")
for k in ("5M", "28M", "123M", "209M"):
t = LAT[k] * N_SERIES / 1000
print(f"{k:>5} {LAT[k]:>8.0f} ms {t:>8.1f} s {t / 60:>9.2f} min")
print(f"参考:三周 15 分钟粒度历史 = {21 * 24 * 4} 个时间步,"
f"只占 {STEPS} 步上下文的 {21 * 24 * 4 / STEPS:.1%}")
本机真实 stdout,原样贴出:
--- (a) 21 分位矩阵的单调性检查
分位点数 21:0.01 / 0.5 / 0.99
干净矩阵(8 条序列 × 21 档):违反单调的行数 = 0
注入交叉后:违反单调的行数 = 2
第一个反例 序列#2:q_0.26=106.7134 > q_0.3=105.035
cummax 修复后:违反单调的行数 = 0
修复后 序列#2 前 8 档:[102.5105, 103.6678, 104.1602, 104.5083, 104.7904, 106.7134, 106.7134, 106.7134]
--- (b) pinball loss:分位预测 vs 季节性朴素基线
序列 336 点(14 个周期),训练 288 / 测试 48,残差样本 264
alpha 分位预测 季节朴素 差值
0.1 18.1123 15.5202 +2.5921
0.5 36.9023 70.2940 -33.3917
0.9 15.3877 125.0678 -109.6801
0.1–0.9 区间实测覆盖率 = 0.8125(名义 0.80)
训练残差在 alpha=0.5 的经验分位 = +2.1376
--- (c) 按官方口径算工程账
上下文 32768 个时间步 × 15 分钟 = 491520 分钟 = 8192 小时 = 341.33 天(约 48.8 周)
每晚 5000 条序列、单线程依次调用、每条一次前向:
规模 单次延迟 总耗时 折合
5M 10 ms 50.0 s 0.83 min
28M 28 ms 140.0 s 2.33 min
123M 105 ms 525.0 s 8.75 min
209M 140 ms 700.0 s 11.67 min
参考:三周 15 分钟粒度历史 = 2016 个时间步,只占 32768 步上下文的 6.2%
(a) 分位交叉能被机械检出来。 干净矩阵(8 条序列 × 21 档)违反单调的行数是 0;只把第 2 条序列第 5、6 档互换并抬高,就立刻抓到 q_0.26=106.7134 > q_0.3=105.035——「26% 分位高于 30% 分位」这种不该存在的预测。修法是 cummax:整行取累积最大值,违反单调的行数立刻回到 0。这十几行值得加进你的推理服务——漏到线上,区间告警就变成噪声。
(b) 分位预测赢在中位数与高分位,低分位不一定赢。 按 alpha = 0.1 / 0.5 / 0.9 算 pinball:中位数档从 70.29 降到 36.90(−33.39),0.9 档从 125.07 降到 15.39(−109.68);但 0.1 档反而更差:18.11 对 15.52(+2.59)。原因是我这条合成序列的性质,不是对 Timestral 的评价:序列带趋势,季节性朴素基线取「上一周期同一位置」的值、天然系统性偏低,偏差恰好把它推到接近 0.1 分位的位置,我又叠了训练残差的经验分位(+2.1376 是残差中位数),属于过度修正。同时 0.1–0.9 区间实测覆盖率 0.8125,名义 0.80,校准很准。工程含义:分位输出的价值主要在「可信的上界」和「比点预测好的中位数」,不是每一档都更准。
(c) 上下文上限折合 341.33 天(约 48.8 周),而官方延迟演示的三周历史只有 2016 步、占上限 6.2%——那两个 10 / 140 毫秒是 6.2% 占用下测出来的。同一段输出还给出每晚 5000 条:5M 50 秒、28M 140 秒、123M 525 秒、209M 700 秒(11.67 分钟),都塞得进一个夜批窗口。
榜单领先有多薄
以下三张表全部来自 Mistral 自己发布/模型卡自述,我未独立复现,官方说明全部来自单个 checkpoint、没有 ensemble。
GIFT-Eval:209M 的 MASE 0.658,CRPS 0.446。
| 系统 | 参数量 | MASE | 与 209M 的差(我算的) |
|---|---|---|---|
| Timestral-209M | 209M | 0.658 | — |
| t0-gamma | 官方未说明 | 0.661 | 0.003 |
| TimesFM-3 | 330M | 0.667 | 0.009 |
| Toto-2.0-2.5B | 2.5B | 0.696 | 0.038 |
| Chronos-2 | 120M | 0.698 | 0.040 |
请盯着前两行:209M 对 t0-gamma 的领先只有 0.003。 这个量级在时序基准上基本等于并列,所谓「第一」是在第三位小数上分出来的。
TIME(98 个任务):MASE 0.626——t0-gamma 0.637、TimesFM-3 0.640、Toto-2.0-2.5B 0.642、Chronos-2 0.662,最好的对手落后 0.011。
fev-bench(100 个任务):SQL 分数 50.39——t0-gamma 49.42、TimesFM-3 48.99、Chronos-2 47.28、Toto-2.0-2.5B 44.42。这是差距最大的一张(对 TimesFM-3 领先 1.40、对 Toto-2.0 领先 5.97,差值是我减出来的)。但 「SQL」这个缩写官方未在博客里展开含义,别按字面理解成数据库查询能力。
官方另称 28M 在 GIFT-Eval 与 TIME 上超过参数量约 4 倍的 Chronos-2;第三方报道也在转这句——数字与官方表格一致,但口径以官方为准,本文按第三方转述标注。
三张表读完结论很干净:Timestral 的卖点不是「分高」,而是「同分数下的体积与部署形态」——这是工程属性的胜利,不是精度榜的胜利。
热度侧(2026-10-26 读数,我实测核对):5M 5,401 / 74、28M 6,540 / 38、123M 3,266 / 42、209M 9,765 / 367(下载 / 点赞)——下载最多的既不是最便宜的 5M,也不是中间档,而是最大的那一档。
坑位清单
- 代码 Apache 2.0,权重不是。 权重是
license:other的 Modified MIT,带 **2000 万美元月收入(雇主口径、上月全球合并)**红线,且延伸到衍生作品。 - 越线后只有两个出口:联系
sales@mistral.ai谈商业许可(Mistral 有完全裁量权),或改用托管服务。 - 首次调用要下约 798 MiB 权重,别在离线环境里第一次就跑
from_pretrained。 - 21 档分位要做单调性检查(cummax),极端分位(0.01 / 0.99)单独验收。
- 32,768 步上下文 ≠ 32,768 次前向:单次最多 1,024 步,更长视界按块推进。
- 官方 140 毫秒是三周历史、占上下文 6.2%、八线程 CPU 下测的,别直接写进 SLA。
- 协变量按三类切开,注意力只允许协变量流向目标——拼数据时也要守同一约束。
- 基准领先很薄(GIFT-Eval 对 t0-gamma 仅 0.003),且全是厂商自述、单 checkpoint、无 ensemble,我未独立复现。
- 权重仓库(10-14)比代码仓库(10-15)早一天,且没有训练代码。
参考与致谢
- 官方:博客
mistral.ai/news/timestral(2026-10-15);推理代码 GitHubmistralai/timestral(10-15 UTC 10:20、单 commit、876 星、Apache-2.0);权重为 Hugging Face 上四个 Timestral 仓库(10-14 UTC、license:other、各带 Modified MIT 全文) - 第三方转述:fev-bench「50.39 对 TimesFM-3 48.99」、「28M 超过约 4 倍参数的 Chronos-2」,以及「GitHub 标 Apache 2.0 而权重带限制性条款」
披露:本文不含推广链接,与 Mistral、Hugging Face 无利益关系;「我实测核对」的数字为 2026-10-26 读数;上文校验代码不需要 key、不下载权重,可本机直接复跑。
总结
把 Timestral 拆完,我的判断是:这是一次「把时序基础模型塞进 CPU 和机房」的发布,不是一次精度榜上的胜利。
- 领先幅度其实很薄。 GIFT-Eval 上 MASE 0.658 对 t0-gamma 的 0.661 只领先 0.003,TIME 上 0.011——而官方限定词也写明,第一是「在预训练加零样本模型里」。
- 许可证是两套。 GitHub 的 license 字段与 LICENSE 文件是 Apache 2.0,只覆盖代码;四个权重仓库在 Hugging Face 上标
license:other,各带一份 Modified MIT,第 2 条把授权画在**「公司/雇主上月全球合并月收入 2000 万美元」这条线上,并延伸到衍生作品。「跑代码」和「用权重」是两件事**,这是转发里最容易丢的一句。 - 工程属性才是差异点。 209M 约 798.5 MiB 权重、单次 140 毫秒、不需要 GPU、支持气隙部署;123M 只低 0.2% 分却快四分之一。这些数字拼起来,才是「能不能进我的机房」的答案。
- 21 个分位点是有成本的。 单变量单视界就是 2016 个数;这套输出必须配单调性检查——我本机注入的交叉立刻被抓到(
q_0.26=106.7134 > q_0.3=105.035),cummax 一修归零;区间覆盖率 0.8125(名义 0.80),校准是它最可信的部分。
只留一句行动项:把 sales@mistral.ai 和「2000 万美元月收入」写进你们技术选型文档的合规栏,然后在自己的数据上先验收 0.1 与 0.99 这两档分位。
Mistral 首个时序基础模型 Timestral:209M 参数在 CPU 上拿第一,权重许可证却换成了一道收入红线
- 作者
- 摆渡
- 发布于
- 许可协议
- CC BY-NC-SA 4.0
除另有声明外,本文采用上述许可协议。转载或改编请注明作者与原文地址,并遵守许可条款。