6.9 KiB
6.9 KiB
远程临时实验 Smoke
适用范围
本模板用于一次性远程 build、Docker GPU、模型下载或硬件 smoke。目标是让执行者在一个实验目录内完成:
- 一次 preflight;
- 一个或多个串行后台 job;
- 一次原入口 smoke;
- 一份有界 evidence;
- 一次幂等 cleanup。
正式 CI/CD、YAML-first apply、常驻服务、Secret 下发和公网暴露继续使用对应专项 CLI 或 skill,不能套用本模板。
必填参数
开始写入目标前必须明确以下参数,不得根据宿主名、历史目录或上一任务猜测:
| 参数 | 约束 |
|---|---|
route |
任务上下文提供的 Target:absoluteWorkspace 或对应 k3s route。 |
work_dir |
目标允许范围内的绝对临时目录,默认位于 /tmp。 |
resource_prefix |
容器、镜像、网络、PID 和证据文件共享的任务前缀。 |
job_id |
当前长步骤的稳定短标识,例如 model-download 或 image-build。 |
command |
写入 job 脚本的单一长命令,不在同步 trans 中等待完成。 |
artifact_allowlist |
允许保留的模型缓存、WAV、JSON 或摘要文件。 |
stop_condition |
OOM、ABI、许可证、上游 ref 或硬件能力等首次确定停止条件。 |
参数缺失时先返回可操作错误:
- 不要创建第二条执行路径;
- 不要把 Secret、token 或完整环境变量写入参数、脚本、日志和 evidence。
单次 Smoke 流程
1. Preflight
只做一次有界探测,确认目录、资源前缀、依赖入口和停止条件:
trans <Target>:/tmp sh <<'SH'
set -eu
work_dir=<absolute-work-dir>
resource_prefix=<resource-prefix>
test "${work_dir#/tmp/}" != "$work_dir"
case "$resource_prefix" in
''|*[!a-zA-Z0-9_.-]*) echo 'invalid resource_prefix' >&2; exit 2 ;;
esac
mkdir -p "$work_dir/jobs" "$work_dir/artifacts"
printf 'host='; hostname
df -h "$work_dir" | tail -1
docker version --format 'docker={{.Server.Version}}' 2>/dev/null || true
nvidia-smi --query-gpu=name,driver_version,memory.total,memory.free --format=csv,noheader 2>/dev/null || true
docker ps -a --filter "label=unidesk.task=$resource_prefix" --format '{{.Names}} {{.Status}} {{.Image}}' 2>/dev/null || true
SH
若目标范围不是 /tmp,必须由 owning issue 或任务上下文明确授权,再相应替换目录断言。
2. 写入 Job 脚本
远端文本修改只使用 apply-patch。每个长步骤的脚本必须写最终 exit code 和 done 标记:
trans <Target>:<absolute-work-dir> apply-patch <<'PATCH'
*** Begin Patch
*** Add File: jobs/<job-id>.sh
+#!/bin/sh
+set +e
+
+<one-long-command>
+code=$?
+printf '%s\n' "$code" > /absolute/work-dir/jobs/<job-id>.exit
+touch /absolute/work-dir/jobs/<job-id>.done
+exit "$code"
*** End Patch
PATCH
job 脚本只能引用任务已确认的绝对目录、资源前缀和公开依赖身份。需要 Secret 的任务不使用本模板自行读取运行面值。
3. Submit
submit 只负责清理旧状态、后台启动和返回 PID,不等待长命令:
trans <Target>:<absolute-work-dir> sh <<'SH'
set -eu
job_id=<job-id>
job_dir=/absolute/work-dir/jobs
rm -f "$job_dir/$job_id.done" "$job_dir/$job_id.exit" "$job_dir/$job_id.stdout" "$job_dir/$job_id.stderr"
chmod +x "$job_dir/$job_id.sh"
nohup sh "$job_dir/$job_id.sh" \
> "$job_dir/$job_id.stdout" \
2> "$job_dir/$job_id.stderr" \
< /dev/null &
printf '%s\n' "$!" > "$job_dir/$job_id.pid"
printf 'job=%s state=submitted pid=%s\n' "$job_id" "$(cat "$job_dir/$job_id.pid")"
SH
4. Poll
每次 poll 只输出状态和有界日志尾部。不得重复 submit,也不得恢复完整 stdout dump:
trans <Target>:<absolute-work-dir> sh <<'SH'
set -eu
job_id=<job-id>
job_dir=/absolute/work-dir/jobs
pid=$(cat "$job_dir/$job_id.pid" 2>/dev/null || true)
if [ -f "$job_dir/$job_id.done" ]; then
state=finished
exit_code=$(cat "$job_dir/$job_id.exit")
elif [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
state=running
exit_code=-
else
state=lost
exit_code=-
fi
printf 'job=%s state=%s exit=%s pid=%s\n' "$job_id" "$state" "$exit_code" "${pid:--}"
printf '%s\n' '--- stdout tail ---'
tail -n 20 "$job_dir/$job_id.stdout" 2>/dev/null || true
printf '%s\n' '--- stderr tail ---'
tail -n 20 "$job_dir/$job_id.stderr" 2>/dev/null || true
SH
state=lost 表示 PID 已消失但没有 done 标记:
- 先保留状态文件和日志;
- 不要自动重提;
- 根据首个确定错误决定停止或创建新的
job_id。
5. 原入口 Smoke 与 Evidence
只执行 owning issue 要求的一个 smoke。通用 evidence 至少包含:
sourceRef、模型或依赖 ref、许可证;- 容器基础镜像 digest、构建镜像 ID、runtime 和 GPU request;
- 精确 API 或命令参数;
- 首次调用和热调用耗时;
- GPU 基线、峰值显存和 OOM 状态;
- 生成物大小、哈希和原生解码/检查摘要;
- 首个失败点和是否触发
stop_condition; - cleanup 结果和允许保留的 artifact 路径。
证据文件只写摘要、presence、fingerprint、ID、digest 和有界日志尾部,不复制 Secret、完整环境、模型正文或无界 stdout。
6. Cleanup
cleanup 必须幂等,并按资源前缀或 label 精确删除:
trans <Target>:<absolute-work-dir> sh <<'SH'
set -eu
work_dir=/absolute/work-dir
resource_prefix=<resource-prefix>
for container in $(docker ps -aq --filter "label=unidesk.task=$resource_prefix"); do
docker rm -f "$container"
done
for image in $(docker images -q --filter "label=unidesk.task=$resource_prefix"); do
docker image rm "$image" || true
done
for network in $(docker network ls -q --filter "label=unidesk.task=$resource_prefix"); do
docker network rm "$network" || true
done
find "$work_dir/jobs" -type f \( -name '*.pid' -o -name '*.done' -o -name '*.exit' \) -delete
printf '%s\n' 'containers:'
docker ps -a --filter "label=unidesk.task=$resource_prefix" --format '{{.Names}} {{.Status}} {{.Image}}'
printf '%s\n' 'images:'
docker images --filter "label=unidesk.task=$resource_prefix" --format '{{.Repository}}:{{.Tag}} {{.ID}} {{.Size}}'
printf '%s\n' 'networks:'
docker network ls --filter "label=unidesk.task=$resource_prefix" --format '{{.Name}}'
SH
只保留 artifact_allowlist 明确列出的缓存和证据。临时容器、镜像、网络、PID 和非复用 scratch 不得伪装成正式运行面。
停止判定
- 首个确定的 OOM、ABI、许可证、上游 ref 或硬件能力错误触发
stop_condition后立即停止同族试配。 - 依赖下载失败与模型失败必须分层记录,不能把网络、registry 或包 ABI 问题误判为模型不可用。
- smoke 未进入原入口时,首次调用、热调用、峰值显存和生成物必须明确写
未执行,不能填推测值。 - 后续候选模型或依赖组合使用新的 issue、TaskTree Task、资源前缀和实验目录,不能在当前 smoke 中无界扩展;遗留 MDTODO 按
$unidesk-tasktree迁移。