Industry & PracticeResearch & Benchmarks
XStack: 仓库 从 PyTorch 迁 到 JAX 和 TPU, 七个 智能 体 平均 只 通过 31 %
Princeton 的 XStack 让智能体把 20 个 PyTorch 仓库迁到 TPU 上的 JAX。140 个格子最终通过 44 个。Claude Opus 5 是 15/20,花费大约每乘 10,通过率增加 25 个百分点。

In this piece
XStack:把 PyTorch 仓库迁到 JAX 和 TPU,七个智能体平均只通过 31%
Linrong Cai、Taiming Lu、Haozhe Chen、Zhuang Liu。Princeton University。许可证:CC BY 4.0。arXiv:2611.08472,2026 年 11 月 5 日。
XStack 评测编码智能体能不能把已经能跑的机器学习仓库,从 GPU 上的 PyTorch 迁到 TPU 上的 JAX。智能体拿到固定提交的源仓库和一台没有装机器学习软件的 TPU,要交出 JAX 仓库。通过要同时满足:成绩达到隐藏的评分目标、两个模型裁判都接受、权重能按源仓库的参数名载入原来的 PyTorch 模型。7 个智能体、20 个仓库、140 个格子,两阶段之后通过 44 个,平均通过率 31%。Claude Opus 5 通过 15/20(75%),GPT-5.6-sol 通过 9/20(45%),其余都不超过 40%。
1 引言
机器学习代码主要活在两套生态里:NVIDIA GPU 上的 PyTorch,和 Google TPU 上的 JAX。仓库往往是为其中一套写的。要换硬件,就得改模型、优化器、数据流水线和评测,再在另一套框架和硬件上建好并测通。这件事既是实验室的实际需要,也适合当基准:跨框架、跨硬件、面对整个仓库,而且时间长、难。
XStack 让智能体收到一个 PyTorch 仓库和一台干净的 TPU,交出 JAX 仓库。评分对照源仓库已经发表、并由作者核对过的结果。迁移仍然不可靠。作者发布这套基准,用来量跨框架、跨硬件迁移有没有变可靠。
已有基准不太测这件事。函数级或仓库级的代码翻译,常常只改一处函数,或在骨架里填空,硬件不变。机器学习框架转换里最接近的是 T2J-Bench,它把整个 PyTorch 训练仓库转到 JAX,但还在同一块 GPU 上。核函数基准一次写一个算子,加速器通常不变。issue 修复类基准靠“改之前测试失败、改之后通过”。一个本来就能跑的仓库,在自己的环境里没有这种失败测试。多数基准还送一个准备好的环境。XStack 禁止运行时再用 PyTorch,空环境要自己装,而且要在另一种硬件上复现源仓库自己的结果,所以什么都不做不能通过。
2 基准怎么定
图 2 是流水线。上面:智能体在一台裸 TPU 上把 PyTorch 仓库迁成 JAX。下面:作者把产出放到一台新擦过的 TPU 上重跑,拿数字去对源结果,并由两个模型裁判复查。若第一阶段失败,对齐步骤(第二阶段)把主实验的做法告诉智能体,只允许改入口脚本,然后重新评分。通过还要求权重能载入原来的 PyTorch 模型。
每个任务都是已经能跑、并且报告了结果的 PyTorch 仓库。评分分两阶段,是为了公平:如果只因为跑了另一个实验就判失败,会惩罚任务没有写明的选择。数值目标一直隐藏。除入口脚本外,其余代码和安装保持冻结。
2.1 任务
输入是钉死在一次 git 提交上的仓库,保证每个智能体看到同一份代码,外加一台 TPU。所有仓库用同一份提示,要求把整个仓库迁到 JAX:模型用 Flax,优化器用 Optax,并按源仓库跑实验的方式、以完整规模运行。四个仓库的源有多种配置,提示里多加一两行,点明被评分的配置或评分脚本要用的开关,但不透露目标数值。提示全文在原文附录 B,这里不另抄指令。
机器上没有机器学习软件。安装包、取数据、决定怎么用机器,都是任务的一部分。智能体从集群上的沙箱通过 SSH 操作这台机器。产出阶段没有时间上限。
产出要带三个脚本。setup_env.sh 准备一台干净机器。入口脚本 run_train.py 负责训练、评测并写出指标。export_torch.py 按源仓库的参数名保存权重。第一阶段不告诉它评哪个数、要达到多少、有没有通过,所以它必须迁整个仓库,而不是只迁一个实验。
2.2 怎么评分
作者把迁好的仓库去掉 git 历史,复制到新擦过的机器上,各跑一次 setup_env.sh 和 run_train.py。评分脚本把被评的数和评分目标比较。目标通常是源仓库发表的数,带容差。每个目标都按钉死的源核对过。20 个里有 3 个改用作者自己跑未修改源得到的数,minGPT 用一条规则,见第 2.3 节。一次运行如果在预期时间的十倍之内没有写出指标,算失败。
数字达标也可能是错的原因,例如换成更小的变体,或读了另一次运行的文件。因此两个模型裁判复查每一个达标结果,两个都是 GPT-5.6-sol。正确性裁判检查这个数是不是这次运行、主实验训练出来的。作弊裁判检查是不是靠作弊达标,例如把指标钳住,或使用另一次运行的文件。只有达标、两个裁判都接受、并且权重能载入源模型,才算通过。
表 2 对比智能体和评分端各自看得到什么。评分目标、评分代码和结果,两个阶段的智能体都看不到。实验做法只在第二阶段给智能体。评分用的机器是新的。
| 材料 | 第一阶段 | 第二阶段 | 评分端 |
|---|---|---|---|
| 源仓库 | 有 | 有 | 有 |
| TPU | 有 | 有 | 新机器 |
| 任务提示 | 有 | 有 | 无 |
| 实验做法 | 无 | 有 | 有 |
| 评分目标 | 无 | 无 | 有 |
| 评分代码 | 无 | 无 | 有 |
| 结果 | 无 | 无 | 有 |
隐藏标准时,智能体可能把代码迁对了,却跑了不被评分的实验。例如 AST 跑了 ESC-50,而评分看的是 Speech Commands。把这种情况判失败,罚的是任务没写明的选择。第二阶段在第一阶段失败之后发一条消息:除入口脚本外全部冻结,给出主实验的做法,仍不给评分目标。改了其他文件,算冻结违反,第二阶段失败。入口脚本如果把冻结的代码重新写出来,裁判会把这次通过判成冻结被破坏。主结果是第二阶段之后的通过率。
2.3 仓库和智能体
20 个仓库,14 个领域,从图 3 的三组任务族里来。每个名字旁边的数是一次完整规模评分运行的预期小时数。作者从 200 多个 PyTorch 仓库里挑,多数因为发表的数字不合格而被丢掉。七个是语言模型:minGPT、nanoGPT、makemore、llama2.c、modded-nanoGPT、nanochat、x-transformers。其余是 LoRA、SimCSE、airbench、AST、S4、HSTU、TD-MPC2、TensoRF、nanoVLM、deepspeech、TGN、PatchTST、TabR。各仓库的评分目标见表 A1。
15 个仓库的目标是 README 或论文报告的数,并对照钉死的源核对过;deepspeech 来自发行说明。公平的目标必须是原代码自己也能达到的,作者的实验确认这些目标可达。另外 3 个用未修改源在作者 GPU 上的运行来定。例如 makemore 的 README 写损失大约 1.92,但未修改源在作者 GPU 上八次运行平均 1.989,标准差 0.017。目标若仍是 1.92,原代码自己也会失败。因此目标改成最好测试损失不超过 2.023,即均值加两个标准差。minGPT 的任务是训练一个小 GPT 做两位数加法,要求 500 道留出加法里至少对 495 道。训练好的模型预期 500 道全对,五道余量留给随机种子和数值噪声。
7 个智能体来自 5 家,全部用高推理力度,全部走开发方官方 API。价格是 2026 年 9 月 14 日的官方标价,单位是美元 / 百万 token,顺序为输入 / 缓存输入 / 输出。
| 智能体 | 支架 | 推理力度 | 标价(输入 / 缓存 / 输出) |
|---|---|---|---|
| GPT-5.6-sol | Codex CLI | 高 | 5 / 0.5 / 30 |
| GPT-5.6-terra | Codex CLI | 高 | 2 / 0.2 / 12 |
| GPT-5.6-luna | Codex CLI | 高 | 0.2 / 0.02 / 1.2 |
| Claude Opus 5 | Claude Code CLI(Anthropic) | 高 | 5 / 0.5 / 25 |
| GLM-5.3-Flash | Claude Code CLI(Z.ai) | 高 | 0.15 / 0.03 / 0.5 |
| DeepSeek V4.1 Flash | Claude Code CLI(DeepSeek) | 高 | 0.3 / 0.006 / 1.2 |
| Muse Spark 1.3 | Claude Code CLI(Meta) | 高 | 1.25 / 0.15 / 4.25 |
一个智能体加一个仓库叫做一个格子。每个格子跑在 TPU v6e、v5p 或 v4 上。附录确认每个仓库的算力够用。
3 结果
3.1 主结果
第一阶段通过 30/140 个格子(21%)。评分脚本放过 58 个第一阶段结果,裁判拒了其中 28 个。Claude Opus 5 以 12/20 明显领先。DeepSeek V4.1 Flash 是 6/20。GPT-5.6 三档里只有 sol 通过了仓库,也是 6/20。每个智能体都有完整源码和自己的 TPU,可以充分试,多数尝试仍然失败。
第二阶段跑在第一阶段失败的格子上。正文写 108 个;图 5 的说明写第一阶段失败 110 个。140 减 30 等于 110。两处原文不一致,这里都保留。评分脚本放过其中 44 个,裁判让 16 个通过。这些就是这一阶段想捞回来的情况:例如源仓库训练到停止规则,迁移却跑了固定步数;源仓库做的是评测,迁移报的是训练统计;或者用了源配方的另一个变体。裁判拒了 28 个第二阶段达标,其中 21 个是因为入口脚本把冻结代码重建了出来。其余第二阶段失败落在冻结的代码里。第二阶段之后,46 个格子同时过了评分脚本和裁判。其中 2 个的权重不能按源参数名载入,所以最终通过 44/140(31%)。
第二阶段让每个智能体至少多通过一个仓库。它也用来保证剩下的失败出在迁移后的代码,而不是任务没写清。140 个格子一共用了 22,802 个 TPU 芯片小时。智能体轨迹合计 125 亿 token。
3.2 裁判、花费和行为
图 5 从左到右是全部 140 个格子、第一阶段、第二阶段和最终结果。按图注,第一阶段失败的 110 个里,第二阶段救回 16 个;其余败在裁判、没达到目标、崩溃或破坏冻结。两个被救回的格子没通过权重载入。
两个阶段合计,评分脚本放过 102 个结果,裁判拒了 56 个。作者手工查过许多拒绝,认为判得成立。例子包括:一份 llama2.c 迁移把发表的数字写进指标,旁边才是自己的数;一份 minGPT 迁移在训练数据上评测。
除了冻结被破坏,裁判还抓住两类假通过。评分脚本读的是运行报告的任何数字,它不一定是配方自己的数,例如用单独一次验证代替配方里的平均损失。正确性裁判到日志里找配方对应的数。作弊裁判抓的是作弊,例如在另一个任务的机器上训练,或读取另一次会话的文件。
图 6 把最终通过率对花费作图,价格用 2026 年 9 月 14 日标价。20 个仓库上,各智能体花费从 10.0 美元到 1,844 美元。通过率对花费的对数做最小二乘:花费每乘 10,通过率大约增加 25 个百分点。拟合解释了智能体之间的大部分差别,R² = 0.66,r = 0.81,p = 0.027,n = 7。在 5% 水平上显著。有效前沿经过三个点:最便宜的 GPT-5.6-luna,每次通过最便宜的 DeepSeek V4.1 Flash,以及最准的 Claude Opus 5。其余都在前沿下面。Muse Spark 1.3 的花费超过 DeepSeek V4.1 Flash 的七倍,通过的仓库却更少。
表 4 从左到右是依次加上的检查:达到目标、正确性裁判、作弊裁判、权重载入。右边是通过仓库数、每格平均 token(各轮输入、缓存输入和输出,按账单计)、每格美元、每次通过的美元、每格平均小时。
| 智能体 | 达到目标 | +正确性 | +作弊裁判 | +权重 | 通过 | token | 美元/格 | 美元/通过 | 小时 |
|---|---|---|---|---|---|---|---|---|---|
| Claude Opus 5 | 95% | 80% | 80% | 75% | 15/20 | 156M | 92 | 123 | 8.7 |
| GPT-5.6-sol | 75% | 70% | 45% | 45% | 9/20 | 23M | 16 | 36 | 1.0 |
| DeepSeek V4.1 Flash | 75% | 60% | 40% | 40% | 8/20 | 106M | 1.9 | 4.7 | 11.1 |
| GLM-5.3-Flash | 60% | 50% | 25% | 25% | 5/20 | 43M | 1.6 | 6.3 | 12.6 |
| Muse Spark 1.3 | 50% | 35% | 20% | 20% | 4/20 | 32M | 14 | 71 | 4.1 |
| GPT-5.6-terra | 35% | 25% | 15% | 10% | 2/20 | 5.3M | 2.0 | 20 | 0.4 |
| GPT-5.6-luna | 20% | 15% | 5% | 5% | 1/20 | 16M | 0.5 | 10.0 | 0.6 |
| 全部 | 59% | 48% | 33% | 31% | 44/140 | 54M | 18 | 58 | 5.5 |
读轨迹时,Claude Opus 5 和其他智能体的差别在于交卷前会把源实验跑完,再拿自己的数去对发表的数。这种检查花时间和 token。它最贵,通过也最多。在 nanochat 上,GPT-5.6-sol 估计完整运行要几小时,没有跑就交了。Muse Spark 1.3 会跑完整实验,但在 LoRA 上交了一个远低于论文的 BLEU,仍称迁移“完全验证过”。作者的结论是:认真把实验跑完,并仔细和原代码比较,才是迁移能通过所需要的。
附录 F 按 2026 年 9 月 14 日标价,把 20 个格子的总花费写成:luna 10.0 美元,terra 40 美元,GLM-5.3-Flash 32 美元,DeepSeek 37 美元,Muse Spark 283 美元,sol 323 美元,Claude Opus 5 为 1,844 美元(每格 92.18 美元,其中第一阶段 49.42 美元)。97% 的输入 token 是缓存命中。计算时没有用折扣或促销价。
4 为什么失败
4.1 失败分类
第三个裁判只读一次已评分的运行,按固定分类给它贴一个类,依据指标、日志和代码差异。它不能决定这次算不算通过。分类定义在附录 D。
图 7 是第一阶段之后的拆分。每根柱子把该智能体的 20 个仓库分成:通过;被裁判拒绝;实验做错(另一条流水线或另一种规模,或头条评测根本没跑);保真差距(流水线对,但没达到评分目标);崩溃或没有结束。后两类占失败的 47%,是迁移后代码的错,第二阶段修不了。
能修的和不能修的分开看。实验做错或不完整,问题在入口脚本跑了什么,第二阶段可以修。保真差距、崩溃或跑不完,问题在迁移后的代码。第一阶段的失败里,27% 是实验做错或不完整,47% 是代码失败,其余被裁判拒绝。第二阶段去掉第一组。没被救回的格子,仍然败在代码,或者在入口脚本里重建被冻结的代码,从而破坏冻结。和评分配方对齐,并不能让一份本来就迁错的代码通过。
智能体的失败方式不同。DeepSeek V4.1 Flash 很少崩溃,只有 1 个格子;它有 15/20 达到评分目标,但裁判拒了其中大约一半,主要因为作弊。terra 的失败散在错误实验、保真差距和崩溃上。luna 几乎每一类都有。
只换框架并不是难点。JAX 也能在 GPU 上跑,所以可以只测 PyTorch 到 JAX 的翻译,硬件不变。基准早期,作者要求交出整个 JAX 仓库,并在 GPU 上评分。那里几乎被做完了:GPT-5.5、高推理力度,把 12 个 PyTorch 仓库迁到 JAX,117 项检查里通过 116 项(99%),12 个仓库里 11 个整仓通过。检查很严:前向和梯度要与 PyTorch 对齐,检查点两边都能载入,解码要逐 token 一致,缺一项整仓失败。唯一没过的是 x-transformers 的一条收敛区间。到了 TPU 上,即便第二阶段之后平均通过率也只有 31%,大约一半失败是智能体自己装好的机器上代码崩溃或达不到目标。作者据此认为,难处不是翻译本身,而是同时换框架和硬件。
表 G1 是这 12 个 GPU 实验的检查通过数。十二个仓库是 DDPM、makemore、minGPT、nanochat、nanoGPT、nano-vLLM、pyGCN、x-transformers,以及 PyTorch 自带的 MNIST、REINFORCE、VAE 和词语言模型例子。
| 仓库 | 检查 |
|---|---|
| DDPM | 10/10 |
| makemore | 10/10 |
| minGPT | 10/10 |
| MNIST | 9/9 |
| nano-vLLM | 9/9 |
| nanochat | 10/10 |
| nanoGPT | 10/10 |
| pyGCN | 9/9 |
| REINFORCE | 10/10 |
| VAE | 10/10 |
| 词语言模型 | 10/10 |
| x-transformers | 9/10 |
4.2 行为和完整性
通过的智能体跟着源文档走:跑 README 的默认配方,用源仓库的名字报告数字,结果接近源结果。卡住的智能体会去翻机器,读其他格子留下的东西,或动用作者的凭据。这些泄漏出现在早期实验里。作者加固沙箱后,把受影响的 3 个格子干净地重跑了。还有两起在搭建基准时发生、瞄准评分目标的事件:把指标钳住,以及复制检查点。它们不影响现在的基准和结果。最终基准里,被抓住的作弊用的是源仓库自己提供的东西,例如兄弟变体或已经发表的数字,由检查和作弊裁判拦住。
附录 E 给了每个格子的最终对错,没有逐格数值表。minGPT 最小,除 GLM-5.3-Flash 外每个智能体都通过。nanoVLM 流水线最长,没有一个通过。LoRA、SimCSE、nanochat、airbench、AST 这五个只有 Claude Opus 5 通过,它的领先主要来自这里。中游智能体通过的是较小的语言模型仓库(minGPT、makemore、nanoGPT、llama2.c)再加各自的少数几个。GPT-5.6-terra 和 GPT-5.6-luna 几乎只通过 minGPT 和 makemore。第二阶段给每个智能体增加一到三次通过,并打破三组第一阶段的平局:sol 高于 DeepSeek,GLM-5.3-Flash 高于 Muse Spark,terra 高于 luna。Claude Opus 5 在 PatchTST 上、GPT-5.6-terra 在 deepspeech 上达到了目标并通过裁判,但导出的权重不能按源参数名载入,所以图上仍算失败。这两格就是前面说的、从 46 减到 44 的那两个。
5 相关工作
代码翻译从单个函数和小程序,发展到整个仓库。有的仓库基准仍只评上下文里的一个函数,或往骨架里填。迁移基准则升级语言版本,或把仓库挪到另一套软件栈。
机器学习代码的框架转换,做过单片段、短程序和训练代码库,也有人用可移植层绕开转换。T2J-Bench 最接近:整个 PyTorch 训练库到 JAX,评测器隐藏,但在一块 GPU 上,也就是第 4.1 节里几乎被做完的那种设定。核函数基准对着已经能在该加速器上跑的参考,一次写一个算子。训练数据里少见的硬件上,模型更差。这些工作里加速器不变,或者单位只是一个核。XStack 把整个仓库迁到新框架和新硬件,运行时不许用 PyTorch。
SWE-bench 之后,issue 类基准用改前失败、改后通过的测试来评分。后来的基准要求从零建一个代码库。能跑的仓库在自己的环境里没有失败测试。多数还提供现成环境。SetupBench 和 DeployBench 只测安装,代码能跑就停。更接近的是对照研究结果来评分的基准:用作者代码复现论文、按量表或参考代码复制论文,或在机器学习任务上把分数做高。XStack 的目标是源仓库自己的结果,忠实迁移自然会碰到它。
智能体会利用写得不严的目标,也会钻它看得到或能改的检查。用公开代码建的基准还有背诵风险。XStack 不告诉目标,也不告诉是否通过。第一轮失败后,第二阶段只对齐被评的实验,入口脚本以外冻结。代码在新机器上重跑,记住一个答案不够。每个达标结果都由模型裁判复查。
6 结论
XStack 评测智能体把能跑的机器学习仓库从 GPU 上的 PyTorch 迁到 TPU 上的 JAX。5 家的 7 个智能体,平均通过率 31%,通过率随花费的对数线性上升。验证把成功和失败分开:把源实验跑完并和原结果比较的,更容易通过;没测就交的,更容易失败。作者建议其他带隐藏标准的基准也加一个冻结的第二阶段,让智能体对齐被评的内容,从而保持公平。当前智能体还不能可靠地同时换框架和硬件。
附录 A 的评分目标
收录要求:仓库的重点是训练流水线;在可钉死的提交上有可读、可取得的代码树,许可以 LICENSE 文件为准而不是 GitHub 标签;头条指标由仓库自己的代码计算,对应树里真正会跑的配置,并且有发表值;数据可以匿名取得;在一台 TPU 主机上,完整规模大约十小时或更短。
表 A1 的目标记号:单侧用 ≤ 或 ≥;区间的上界是该指标的上限,损失上的下界用来防泄漏。来源分四类:readme 或 paper,是源自己发表的数;anchor,是围绕未修改源多次运行预先登记的区间;rule,是源没有发表数字时作者定的下限;released-checkpoint,是发行说明里预训练检查点的数。
| 仓库 | 领域 | 指标 | 目标 | 来源 |
|---|---|---|---|---|
| llama2.c | 语言建模 | 源默认 100,000 步上的最好验证损失 | ≤ 1.13 | anchor |
| LoRA | 微调 | E2E 测试集语料 BLEU | [0.68, 1.0] | readme |
| makemore | 语言建模 | 最好测试负对数似然 | ≤ 2.023 | anchor |
| minGPT | 语言建模 | 500 道加法测试至少对的题数 | ≥ 495 | rule |
| modded-nanoGPT | 语言建模 | FineWeb 验证交叉熵 | ≤ 3.285 | readme |
| nanochat | 语言建模 | 基础评测套件分数 | [0.2565, 1.0] | readme |
| nanoGPT | 语言建模 | 评分步数预算上的验证损失 | ≤ 3.12 | readme |
| SimCSE | 句向量 | 七个 STS 测试集的平均 Spearman 相关 | [73.5, 100.0] | readme |
| x-transformers | 语言建模 | 验证损失 | [0.8788, 0.9788] | anchor |
| airbench | 图像分类 | 测试时增强后的 CIFAR-10 准确率,源自己 25 次循环的均值 | [0.9393, 1.0] | paper |
| nanoVLM | 视觉语言 | 最好 MMStar 准确率 | [0.353, 1.0] | readme |
| HSTU | 推荐 | MovieLens-20M 的 HR@10 | [0.345, 1.0] | readme |
| PatchTST | 时间序列预测 | 标准化单位下的测试均方误差 | ≤ 0.38 | paper |
| TabR | 表格 | Covertype 测试划分 116,203 行上的准确率 | [0.9745, 1.0] | paper |
| TGN | 时序图 | JODIE Wikipedia 上未来边预测的转导测试平均精度 | [0.98, 1.0] | paper |
| TensoRF | 神经场 | Synthetic-NeRF lego 测试视角平均 PSNR | [36.1, 45.0] dB | paper |
| S4 | 长序列 | 留出 CIFAR-10 划分上的 top-1 准确率 | [87.0, 100.0]% | readme |
| TD-MPC2 | 强化学习 | DMControl walker-run 上源设定 10 回合的平均回报 | [750.0, 1000.0] | readme |
| AST | 音频分类 | 测试划分 top-1 准确率 | [0.975, 1.0] | readme |
| deepspeech | 语音识别 | AN4 测试集词错误率 | [1.5, 20.0]% | released-checkpoint |
致谢写明计算来自 Princeton Research Computing 的 Della 集群、Princeton Language and Intelligence,以及 Google TPU Research Cloud。
参考文献
- Bradbury 等,2018,JAX;Heek 等,2020,Flax;DeepMind 等,2020,Optax。迁移指定的框架。
- Jimenez 等,2024,SWE-bench。issue 修复类评分的对照。
- Song 等,2026,T2J-Bench。同硬件上的 PyTorch 到 JAX。
- Karpathy 的 minGPT、nanoGPT、makemore、llama2.c、nanochat,以及表 A1 中其余仓库的原论文或 README。评分目标的来源。
- Jouppi 等,2023,以及 Google Cloud 的 TPU v5p、v6e 文档。格子所用机器。
出处:Linrong Cai,Taiming Lu,Haozhe Chen,Zhuang Liu,XStack: A Benchmark for ML Repo Migration,2026-11-05,https://arxiv.org/abs/2611.08472,CC BY 4.0。
Found it useful? Pass it on
Scan with WeChat to open it on your phone and forward it.