← Về thư mục
📄 / / profiles / r-and-d / raw_evidence / OpenAI4S / openai4s / compute / README_zh.md

远程计算 Host 后端

English

持续演进中的 host.compute job 通道,其 Host 侧实现放在这里;专用 remote-science service 用来找到真实 GPU 主机的那份独立注册表也在这里。重活离开本机只有两条路:走发现到的 byoc:<id> provider,或者走用户已经配好的 ssh:<alias> 连接。本包只做编排与传输,里面没有调度器,没有 GPU 运行时,也没有任何科学模型的实现。

在架构中的位置

Python 的 host.compute SDK 把每次调用变成一个 compute_<operation> Host RPC。HostDispatcher 在第一次用到时为 session 建一个 ComputeManager,并把 ComputeError 映射成结构化的软失败。native 的 compute_submit、compute_result、compute_cancel、compute_close 只暴露这个控制平面里有界的一小块;SDK 那些更丰富的兼容调用最终也落到同一个 manager。

走 byoc:* 时,manager 到 skills/remote-compute-<id>/ 下面找 provider shim,把 job 归档暂存好,再运行受限的 openai4s_compute_provider helper,credential 从 stdin 递进去。走 ssh:* 时,它就是对着用户配置的 alias 调本地的 ssh/scp。回收下来的文件一律落在已配置数据目录下的 hpc/<job_id>/。

文件

文件 职责
__init__.py 对外只导出 Host 后端要用的两个名字:ComputeManager 和结构化的 ComputeError。
manager.py 两条传输路径都在这里。它发现 BYOC 的 provider Skill,路由 byoc:* 与 ssh:*,并在 session 并发上限达到时拒绝新的提交,暂存输入与 job 模板,并跟踪在跑的 job 和预热的沙箱,负责轮询、取消、关闭与产物回收。由 agent 点名的 alias 必须是用户自己交出来过的——注册表里的,或者他自己 ~/.ssh/config 里的;而且每一条 ssh/scp argv 都在同一处拼装,所以这道形状检查是结构性的,而不是靠人在每个调用点记着。来自不受控机器的输出会被读干净,免得远端写阻塞,但只保留到一个上限为止:否则一个话多的登录 profile 或者一条 forced command,就能用整个单命令超时窗口逼 daemon 一直分配内存。credential 是按 provider 自己声明的那几个环境变量名挑出来的,而且走 helper 的 stdin 递进去,不进它的环境变量。helper 的环境其余部分就是 daemon 自己那一份,但会摘掉 helper 自己的 scrub_secret_env 本来就要删的名字(形似凭据的变量名和基线密钥前缀),以及 provider 声明的 secret_env 名字。这道过滤在 exec 之前做,而不只是在 helper 进程里做:进程总能读到自己 exec 时的环境块,进程内的清理改变不了它。helper 在自己独立的 session 里启动:在 macOS 上,正是这一点让 profile 里的 process-info 拒绝规则生效,所以它也读不到 daemon 的环境。暂存输入可以不给路径、改点名一个 Artifact version_id:manager 只暂存 Host 为该 version 解析出的冻结快照字节,Host 没解析过的 version 直接拒绝;声明的 version 会持久化到 job 行上,之后每一次 result 都带着它们。ssh 传输则完全不接受暂存输入——要在提交前自己显式上传。
stage11.py Stage 11 产品钩子,经 OPENAI4S_STAGE11_DURABLE_REMOTE_COMPUTE 显式开启:开关本身、惰性 manager 构造时对其重新装载的那些 durable job 的首次访问恢复投影,以及把经验证的 manifest——远端环境、输入 version、job receipt、逐文件 checksum——绑定到确切工作区路径的 harvest Artifact provenance receipt。终态 job 的缓存 harvest 再被轮询时刻意不产出 receipt,这里也绝不重新提交任何 job。
states.py 任务状态词表及其转换表,在写入状态时强制执行。unknown 有意归为存活态:远端操作可能落地也可能没有,所以它会被重新装载并参与调和,而不是被遗忘。终态不会被迟到的探测重新打开——写入同一个状态也不行,因为这次写入带着确立它的那份证据;否则两个都读到 running 的轮询者会各自把自己的 manifest 盖到对方头上。
manifest.py 一次回收究竟拿到了什么:逐个文件的 {path, size, sha256} 加上整份记录的一个摘要,以及把这份记录与任务在 outputs 里声明的 glob 对账。声明了却匹配不到任何文件,正是一个退出码为 0 的任务仍然会是 failed 的原因——而大小与哈希这一对,是唯一能看出「rc 为 0 但传输被截断」的东西。声明里还可以写 visibility: hidden 或 residency: remote:两者都不会被算成缺失,而且 stay-remote 的 pattern 是直接不参与传输,不只是在对账时被豁免。
registry.py 记住有哪些 SSH 主机 alias、默认用哪一台、每台上开通了 fold/score_mutations 这类 capability 元数据,原子写入 <data_dir>/remote_compute.json。native 注册会先探测主机,通过之后才写下验证时间;用旧环境变量 seed 出来的主机则可能一直没验证过。is_known_alias 同时是 ssh 传输的授权侧:要么在这里注册过,要么在用户自己的 ~/.ssh/config 里。它不存 SSH private key,也不存 provider token。
safe_archive.py 对来自不受控机器的收割结果先枚举后解包——两条传输路径都用它,因为这些字节无论从哪条路回来都得当作攻击者构造的输入。穿越、绝对路径、链接、设备节点与解压炸弹在写出任何字节之前被拒绝,而且拒绝是全有全无的:把一个恶意归档解一半出来,照样是一次沦陷。在本项目支持的 Python 版本上,tarfile.extractall 对上述任何一种都不设防。

子目录

目录 职责
templates/ 暂存进 BYOC job 的 shell 模板:运行提交的 command,处理超时与 deadline,并把输出和日志打包好供回收。参见其 README。

当前生命周期

  1. submit 校验 provider 家族、检查并发计数,走 ssh:* 时还会拒绝用户从未交出来过的目标主机。job 行在真正发起远程调用之前就已占位,所以这次提交无论以哪种方式结束,都留下了可供对账的东西。
  2. BYOC 提交会新建或复用一个 provider 沙箱,用 wrapper、command 和输入拼出 in.tar.gz,然后调用 helper 的 create 与 submit 操作。提交里带着容器的绝对到期时刻,连同 harvest margin 与 term grace 一起递过去,wrapper 的看门狗这才真正上膛:以前 Host 侧根本不产出这几个值,容器可能在 job 跑到一半时被回收,结果一并带走。SSH 提交则建一个远程工作目录,用 nohup 起 run.sh。
  3. result 轮询本 session 拥有的那个确切 job。BYOC 路径上,helper 的 wait 会暂存出 out.tar.gz;SSH 路径则在远端自己把工作目录打成归档再拉回来。两者都要过同一个抗恶意输入的解包器,最终落到 hpc/<job_id>/。SSH 的工作目录本身仍留在远端,被删掉的只有那份暂存的 tarball。
  4. cancel 给远程 process group 发信号并确认它确实没了,或者终止 BYOC 沙箱。close 逐个终止每个 job,只有确认之后才标成 cancelled;没能确认的仍然算存活、继续占着并发槽,并被点名回报给调用方。又因为 BYOC 的 job 每个 provider 共用一个沙箱,一次确认成功的沙箱终止会结束骑在它上面的每一个存活 job——包括这个 handle 从未点过名的那些。

持久化、审批与成熟度边界

相关文档