# 科研中心任务发布规范

版本：2026-10-16。本文面向准备任务包的用户 agent，内容对应中心当前固定 Python 解析器与执行程序。

提交者一次性交付 ZIP：程序、输入、依赖和结构化任务清单。中心负责校验、调度、环境准备、执行、符合条件的恢复和结果交付。用户操作为上传、下载结果、取消；提交者无需试跑、校准或持续在线。

当前执行平台为 **Windows x64**。程序必须非交互运行；不能假定存在 Bash、apt、Linux 路径或系统命令。Python 多进程入口使用 `if __name__ == "__main__":`，并在退出前等待子进程完成。外部可执行程序须随包提供兼容版本，或使用中心已发布的环境。

准备材料可从平台用户指南下载，包含本规范、运行环境快照、恢复策略附录和接入说明。用户 agent 可以离线完成打包，不需要登录账户。环境快照带日期；需要最新目录时由已登录用户访问 `/api/environments` 并交给 agent。

## 1. 任务包结构

一个 ZIP 对应一个批次，包含 1–200 个计算单元。根目录直接放 `manifest.json`，不要再套一层总文件夹。

```text
manifest.json
main.py
requirements.txt
inputs/data.csv
```

推荐声明资源估计；下例适用于可安全从头重复执行的 Python 任务：

```json
{
  "schema_version": 1,
  "name": "数据分析批次",
  "tasks": [
    {
      "id": "analysis",
      "name": "数据分析",
      "kind": "python",
      "runtime": "python",
      "entry": "main.py",
      "args": ["--input", "inputs/data.csv"],
      "requirements": "requirements.txt",
      "restart_policy": "from_start",
      "resources": {
        "mode": "estimated",
        "cpus": 2,
        "memory_gb": 2,
        "commit_gb": 3,
        "disk_gb": 4,
        "wall_minutes": 120
      }
    }
  ]
}
```

无额外依赖时，可以去掉 requirements 字段和文件。`from_start` 表示在全新工作目录重跑不会产生不可接受的外部副作用；否则填写 `never`。

**务必显式选择恢复策略：省略 `resources.mode` 和 `restart_policy` 时，实际为 `fixed + never`，不会启用内存低估后的自动扩额重算。** 可安全从头重跑的任务使用上例的 `estimated + from_start`；有外部副作用的任务应保持 `never`。

ZIP 上传上限默认 2 GiB；解压上限 8 GiB，最多 20,000 个条目。`manifest.json` 和每份依赖文件各不超过 1 MiB。路径用 `/` 分隔；不允许绝对路径、`.`/`..` 路径段、反斜杠、Windows 保留文件名、符号链接、加密 ZIP 或大小写冲突的重名文件。

## 2. 清单字段

顶层字段为 `schema_version`（固定为 1）、`name`（可选批次名）、`tasks`（计算单元数组）。每个单元支持：

| 字段 | 含义与默认值 |
| --- | --- |
| `id` | 批次内唯一 ID，1–80 个字母、数字、`_` 或 `-`；省略时依次为 `task-1`、`task-2`。有依赖时建议明确填写。 |
| `name` | 展示名称，默认入口文件名去扩展名；名称最多保留 120 字符。 |
| `kind` | `python`、`matlab`、`comsol`；默认 `python`。 |
| `runtime` | 中心已发布的环境 ID，默认与 kind 相同。 |
| `entry` | 必填，包内入口相对路径。对应 `.py`、`.m`、`.mph`。 |
| `args` | Python 参数字符串数组，最多100项，每项最多2048字符；MATLAB/COMSOL 参数写进脚本或模型。 |
| `requirements` | Python 依赖文件相对路径；省略时，若根目录有 requirements.txt 就自动使用。填 `null` 可明确不使用该文件。 |
| `depends_on` | 同批次前序 ID 数组，默认空；不能重复、不存在或构成循环。 |
| `restart_policy` | `never`（默认）或 `from_start`。 |
| `resources` | 下表中的结构化资源字段。 |

CPU、内存等请写在 `resources` 内。顶层旧字段 `cores`、`memory_gb`、`estimated_minutes`、`max_minutes`、`target` 不参与 ZIP 任务的资源和主机分配。中心自动选主机，用户身份由登录账户绑定。自然语言估算说明不作为机器执行指令。

## 3. 资源估计

| resources 字段 | 范围与默认值 |
| --- | --- |
| `mode` | `estimated` 或 `fixed`；省略为 `fixed`。建议新任务明确使用 `estimated`。 |
| `cpus` | 1–256 的整数，单位为逻辑 CPU；Python 默认1，其他类型默认4。 |
| `memory_gb` | 0.25–1024 GiB；Python 默认4，其他类型默认8。 |
| `commit_gb` | 不小于 memory_gb，不超过4096 GiB；省略等于 memory_gb。 |
| `disk_gb` | 1–16384 GiB，默认4；中心实际取申报值与“包解压大小×2+2 GiB”的较大值。 |
| `gpus` | 0–16 的整数，默认0。 |
| `gpu_memory_gb` | 每张所申请 GPU 需要的可用显存，0–256 GiB，默认0。申请显存时同时设置 gpus。 |
| `wall_minutes` | 1–10080 分钟，支持小数；省略时采用中心运行时上限，默认10080分钟。 |

估计整个计算进程树的峰值：输入载入、计算临时副本、并行工作进程、结果整理都要计入。已有相同输入规模的实测可用作参考，无需为了提交额外试跑。

- **CPU**：申报代码能够实际利用的并行度。多进程或线程数读取 `LAB_COMPUTE_THREADS`，不要固定占满整台主机。
- **RAM**：估计同时驻留的内存，不把整机容量当作任务需要。
- **提交内存**：估计进程树需要操作系统提供后备存储的私有内存峰值；不机械取 RAM 的两倍。协议要求申报值不低于 RAM。
- **磁盘**：覆盖输入、前序结果、临时文件及输出，并留打包空间；本字段用于调度与部分容量检查，不是完整磁盘配额隔离。
- **时间**：填写覆盖最慢正常计算阶段的上限；这是停止线，不是预计完成时间。准备环境单独限30分钟。入口运行后动态补装依赖的时间计入计算时间。

`estimated` 的值是预计峰值，中心自动增加初始余量；`fixed` 直接作为预算和限额。小任务按实际需求填写。具体预算公式见准备材料中的《资源恢复策略附录》。

### 提交者 agent 如何静态估计内存

提交者提供任务包和数字预算，无需试跑、校准或提交自然语言估算方法。按各阶段同时存活的数据计算峰值，再取整个进程树的最大值：

- 数组按元素数×数据类型字节数计算，并计入同时存在的输入、副本、输出和解压缓冲。稠密矩阵按行数×列数，稀疏矩阵按实际非零项及索引存储计算。
- 多进程的私有数据按同时运行的工作进程数计入；共享只读数据计一次。线程不会天然把全部内存乘以线程数。
- 神经仿真计入分段、突触、状态变量和记录缓冲。只有确实保留全部时间序列时，记录内存才随时长增长；流式写盘按缓冲大小计算。
- 加入解释器、依赖库及必要工作区。不要使用压缩包大小或整机容量代替预算；estimated 已由中心加余量，避免重复放大。

例如100万行、64列的 float64 数组约0.477 GiB，若同时保留输入及两个同尺寸副本，总计约1.43 GiB，再加约0.5 GiB运行开销，RAM估计约2 GiB。提交内存另按私有分配估计，以下2.5 GiB仅为示例。

可在 resources 中增加机器可读的规模信息：

```json
{"mode":"estimated","cpus":1,"memory_gb":2,"commit_gb":2.5,"workload":{"family":"dense-transform-f64-v1","features":{"live_elements":192000000}}}
```

workload.family 使用1–80位字母、数字、下划线、点、冒号或短横线；features 为1–8个非负有限数字计数，键名为1–40位字母、数字或下划线。规模应表达实际内存增长量，例如矩阵元素数而非只有边长；同一 family 保持相同定义。中心还绑定程序、运行时和用户身份，只利用可比的完整运行记录；超过已有规模两倍的任务不会依靠该模型下调。无法提供可靠规模时可省略 workload，中心保留较保守的申报预算。

中心依据进度和历史耗时估算完成时间。有 wall_minutes 的任务可参与有时间边界的队列回填；申报资源不等于主机已经具备相应能力。

## 4. GPU 任务

使用 `runtime: "python-gpu"`：Windows x64、Python 3.11、PyTorch 2.7.1 + CUDA 12.6。四种显卡为 RTX 3050（8 GiB）、RTX 4060（8 GiB）、GTX 1660（6 GiB）、RTX 3090（24 GiB）。实际可用显存扣除桌面占用，以中心采样为准。

可直接从准备材料中的 `GPU示例.zip` 开始。包根目录的 manifest.json：

```json
{"schema_version":1,"name":"GPU矩阵计算","tasks":[{"id":"matrix","kind":"python","runtime":"python-gpu","entry":"main.py","restart_policy":"from_start","resources":{"mode":"estimated","cpus":2,"memory_gb":1.5,"commit_gb":2,"disk_gb":4,"gpus":1,"gpu_memory_gb":1,"wall_minutes":10}}]}
```

main.py：

```python
import json
from pathlib import Path
import torch

x = torch.arange(4096, dtype=torch.float32, device="cuda").reshape(64, 64) / 4096
y = x @ x.T
torch.cuda.synchronize()
Path("outputs").mkdir(exist_ok=True)
Path("outputs/result.json").write_text(json.dumps({"sum": y.sum().item(), "gpu": torch.cuda.get_device_name(0)}))
```

示例预算只适用于上面的小型矩阵计算；训练模型或更大数据须重新估计峰值。

此环境自动安装固定的 torch 和 numpy，无需在 requirements.txt 重复声明。需要 torchvision 或 torchaudio 时，可在依赖文件中写包名，或由入口直接导入触发自动安装；中心将版本约束为 CUDA 12.6 对应的 torchvision 0.22.1、torchaudio 2.7.1。与模板冲突的版本要求会被拒绝，不会静默换成 CPU 版本。

中心先使用缓存，缓存缺包时联网补装；每个任务仍有独立环境。执行前再次验证 CUDA 可用并完成微型计算。中心另为 GPU 环境预留 8 GiB 安装空间，任务的 disk_gb 只需覆盖自己的输入、临时文件与输出。

当前每台主机为单卡，单个计算单元填写 gpus=1；跨主机联合训练尚未支持。独立 GPU 计算可拆成多个单元，由平台并行安排。

中心任务间整卡分配，代码读取 CUDA_VISIBLE_DEVICES 后使用逻辑设备编号，不硬编码物理卡号。GPU 与 Windows 桌面显示共享，准入检查当前利用率、已识别的外部计算进程和可用显存，并保留 0.5 GiB 显存余量。该机制不是恶意代码的设备访问硬隔离，平台面向课题组可信用户。

显存申报是准入要求，不是硬限额；GPU OOM 不触发 RAM 扩额，已有输出仍会交付。驱动、系统 CUDA 编译工具链、商业许可证由中心维护，任务安装不会修改它们。预编译的 PyTorch CUDA 运算不要求用户安装 CUDA Toolkit；自定义 CUDA 扩展、其他框架或未发布的系统依赖须由中心提供对应环境。仅声明 GPU 数量不会把 CPU 程序改成 GPU 计算。

## 5. 运行环境与依赖

准备材料中的 `运行环境快照.json` 可离线阅读，无需账户；其中同一 ID 的多种 status 表示不同主机的安装情况。`version` 是该环境报告的软件版本，专用环境不一定指 Python 版本。最新信息以登录后的 `/api/environments` 返回为准；环境按主机列出。选择 kind 匹配且 status=ready 的环境，实际启动还取决于主机在线、开放派发和可用资源。

常用环境包括 `python`（基础 Python）、`python-science`（科学计算包）、已发布的专用 Python 环境，以及 MATLAB/COMSOL。具体版本和软件包取环境目录，不把某个节点的软件安装状态硬编码到任务包。

Python 每次执行使用独立虚拟环境：安装环境模板，再安装本单元 requirements，再执行 pip check。依赖建议固定版本；同包不同单元可各自指定依赖文件。

```text
humanize==4.13.0
```

不要假设其他任务安装过的包会出现在自己的环境。中心可使用管理员维护的 wheelhouse；普通用户任务的私有 pip 缓存会在交付后清理，不保证跨任务共享下载缓存。不要打包整套虚拟环境。

`vendor/` 直接加入 Python 导入搜索路径，适合纯 Python 源码，例如 `vendor/myhelpers/__init__.py` 对应 `import myhelpers`。中心不会扫描并安装 vendor 中的 wheel 文件。

随包离线 wheel 放入 `wheels/`，并在 requirements.txt 中逐行填写包根目录相对路径，例如 `./wheels/your_package-1.0.0-py3-none-any.whl`（替换为真实文件名）。pip 的工作目录是任务包根目录，即使 requirements 位于子目录也一样。二进制 wheel 必须匹配 Windows x64 与目标 Python；仅随包携带一个 wheel 不代表它的所有传递依赖都已包含。

任务入口直接导入缺失的顶层 Python 包时，中心可按已知映射或同名包自动补装，单次执行最多尝试24个不同包。完整依赖仍应写在 requirements 中，尤其是分发包名与导入名不同、版本约束或子进程需要的依赖。自动补装不等于任意第三方库内部或子进程缺失依赖都能识别。

二进制库需与所选环境的 Windows x64/Python 版本兼容。CUDA、系统程序、MATLAB 工具箱和商业许可证使用管理员发布的环境。

## 6. 多任务与依赖

将同一实验批次的独立计算放入 tasks，平台自动并行；需要前序输出时明确写 depends_on。例如：

```json
{
  "schema_version":1,
  "name":"两阶段计算",
  "tasks":[
    {"id":"solve","entry":"solve.py","restart_policy":"from_start","resources":{"mode":"estimated","cpus":1,"memory_gb":1,"commit_gb":1.5,"disk_gb":4,"wall_minutes":30}},
    {"id":"aggregate","entry":"aggregate.py","depends_on":["solve"],"restart_policy":"from_start","resources":{"mode":"estimated","cpus":1,"memory_gb":0.5,"commit_gb":1,"disk_gb":8,"wall_minutes":10}}
  ]
}
```

每个单元有独立工作目录，不共享写入。后序在前序成功且结果到达中心后启动，读取 `dependencies/solve/work/outputs/…`；只注入显式声明的直接前序。dependencies/ 由中心管理，有依赖的包中不得自带该目录。

后序磁盘预算需覆盖整个前序结果包的解压内容并留至少1 GiB余量。当前前序结果解包仍采用8 GiB/20,000条目的检查上限；若前序输出过大，应在提交设计时按独立数据单元拆分。

前序自动恢复期间后序等待；恢复成功后自动放行。前序最终失败后，未执行的后序明确结束，不无限等待。

## 7. 程序、进度和输出

工作目录为任务包解压根目录，输入输出均使用相对路径。入口位于子目录时，工作目录仍是包根目录。建议将主要结果写入 outputs/。

程序无需键盘或窗口交互，成功返回退出码0，失败返回非零。入口退出前等待自己启动的全部计算子进程结束。Python 使用入口脚本；MATLAB 使用 -batch；COMSOL 使用 batch，模型输出为 computed_model.mph。

可在工作目录根部原子更新 progress.json：

```json
{"completed":25,"total":100}
```

completed、total 是有限数值，要求 `0 ≤ completed ≤ total` 且 total>0；文件最多4096字节。以样本、参数组等实际工作单位更新，先写临时文件再替换。未提供进度也可运行，中心在有足够数据时给出预计时间。

结果 ZIP 包含 work/ 下的输出及执行日志、资源统计和环境记录。多单元批次可整体或分别下载。中心完成表示计算成功退出且结果保存，不附加科学验收动作。

## 8. 自动恢复与交付

- **内存低估**：`estimated + from_start` 的计算触及 RAM/提交限额后，中心在保存输出、确认清理后增加预算并重算；超过恢复上限会结束并提供已有输出。
- **环境准备或运行中断**：中心对可恢复故障自动重试；运行中断重算要求 `from_start`。
- **预算偏高**：中心根据完整成功运行的峰值、相同程序与运行环境，以及数字化任务规模调整尚未启动的 estimated + from_start 任务；当前排队任务同样适用。没有可比的完整记录时保留申报预算，不再统一下调15%。运行中的限额不缩小，不要求用户试跑或校准。校正后触限会根据观测扩额并自动重算。
- **结果回传**：自动重试交付，不重新计算；保存成功后出现下载入口。
- **取消**：停止等待或执行、清理进程并保留已有输出，不自动重启。

每次重试从新目录开始，不猜测检查点格式、不混用失败执行的输出。任务错误、超时和 GPU 显存不足不触发 RAM 自动扩额。中心自动清理临时环境、缓存和任务进程，保留输入、结果与执行记录。

## 9. 交付与自动上传

提交者静态核对入口路径、输入、依赖、任务 ID/依赖关系、资源字段及输出位置后交付 ZIP，说明批次名称和结果位置即可。

如已获用户授权自动上传，可使用 submit_batch.py 或 POST /api/jobs（multipart 文件字段 package）。浏览器/API 写入均使用登录会话及 CSRF；上传使用稳定的 Idempotency-Key。相同账户、相同文件的网络重试复用同一标识；有意重新计算用新标识。不同文件复用旧标识会拒绝。

上传回执返回任务 ids 与 batch_id。普通批次可用回执 batch_id 调用批次下载/取消；页面状态可能给出兼容的展示 ID，两者均受账户权限检查。凭据留在受保护的客户端配置中，不放入任务 ZIP。

当前运行开放状态以平台页面为准；主机维护、旧任务交接与部署记录见平台运维交接，不属于用户任务包协议。
