你睡觉的 8 小时AI 跑了 300 个实验无人值守实验调度系统实战如果你做过机器学习项目大概率经历过这样的夜晚白天写好了训练脚本晚上睡觉前手动启动一个实验第二天早上满怀期待地查看结果发现 loss 曲线在第 200 轮就发散或者模型效果根本不达预期。于是你又调参、重跑再等一个晚上。你有没有想过这两个动作其实完全可以交给 AI 来完成不是让 AI 替你想出更好的模型结构而是让 AI 在夜间帮你批量调度实验、监控指标、记录日志并在早上给你一份清晰的结果摘要。这篇文章要讨论的正是“你睡觉的 8 小时AI 跑了 300 个实验”这个场景背后的工程实现如何搭建一套无人值守的 AI 实验调度系统把夜间算力用满、把实验流程自动化并且不烧掉你的服务器。我会从场景痛点、架构设计、环境准备、代码实现、运行验证到常见问题和最佳实践完整地拆解这套系统。1. AI 自动化实验到底是什么先给一个准确但不晦涩的定义AI 自动化实验是指利用任务调度框架、Agent 编排和自动日志分析让实验系统在无人干预的情况下自动完成参数组合探索、模型训练、指标评估、结果汇总和异常告警的完整流程。这里要区分几个容易混淆的概念概念核心动作典型工具强调重点传统脚本跑批循环启动训练任务Shell、Cron、Python for 循环批量执行自动化机器学习自动搜索模型结构与超参AutoML、NAS、Optuna算法自动设计模型AI Agent 编排Agent 根据目标决定下一步LangChain、AutoGen、自定义 Agent目标驱动的自动化决策无人值守实验调度跑一组预定义或系统生成的任务并自动收集与汇总结果Airflow、Dagster、Celery、Docker Compose 自定义调度工程层面的全流程闭环本文讨论的“AI 跑了 300 个实验”更准确地说是最后一种利用 AI Agent 或规则引擎自动生成和调度大量实验任务并用程序化手段完成结果收集和分析。它真正降低的是三类成本工程师的等待成本。以前你盯着一块屏幕等一个结果现在机器在夜间自动排队执行。参数探索的重复成本。手动改超参、重启脚本既容易出错又浪费时间无人值守系统可以自动化遍历参数组合。结果整理的沟通成本。实验结束后系统自动生成实验报告不需要有人逐条翻日志。这套系统适合谁适合正在做模型调优、算法实验、数据集消融测试的团队也适合个人开发者拥有多张 GPU 卡但白天没有时间干预实验的场景。如果你只是偶尔跑一两个脚本暂时用不上但如果你每天有超过 10 个实验要跑且需要反复对比参数这套系统带来的效率提升是明显的。2. 核心概念与架构设计无人值守实验调度系统看起来像一个“高级任务队列”但真正落地时需要把它拆成几个清晰的模块。如果一开始就把所有逻辑写在一个大脚本里后面会非常痛苦。2.1 系统核心模块一个可用的无人值守实验系统至少包含以下部分任务定义层。 每一个实验任务需要明确训练脚本、数据集、超参数组合、依赖镜像、资源限制、优先级。任务定义应该被结构化比如 JSON、YAML 或者 Python dataclass而不是散落在多个 Shell 脚本里。调度与执行层。 系统根据资源空闲情况和任务优先级从队列中取出任务并执行。执行环境要隔离推荐使用 Docker 容器或 Kubernetes Pod避免实验之间互相污染依赖。Agent/规则决策层。 这是“AI 跑实验”的关键。可以有两种实现路径规则型调度预设超参搜索空间由调度器遍历组合生成大量任务。Agent 型调度一个 LLM Agent 根据历史实验结果动态调整下一步实验策略类似一个小型 AutoML 大脑。它读取实验指标判断哪些参数方向更值得探索生成对应训练指令。监控与告警层。 系统需要看每个实验的 GPU 使用率、显存占用、训练 loss 是否发散、进程是否崩溃。异常时能自动重启或终止并推送告警到企业微信/钉钉/邮件。结果汇总层。 实验结束后解析日志中的关键指标如 loss、accuracy、训练时长统一写入 CSV 或 SQLite 数据库并生成 Markdown 或 HTML 报告。2.2 为什么 AI Agent 在这里能提升效率如果只是“批量跑多个实验”传统脚本完全足够。引入 AI Agent 的价值在于它能把“人的调参经验”低成本的复制到实验流程中。比如传统方式是先跑 50 组学习率再人工看结果再跑下一批。而 Agent 方式可以做到第一轮先跑 20 组覆盖全参数空间的实验系统自动解析 20 组实验的指标Agent 根据指标判断当前参数区域的梯度变化方向生成下一轮 30 个实验如此迭代8 小时可以完成原本需要 3 天人工参与的实验循环。2.3 架构图文字版任务定义(YAML) → 任务队列(Redis/SQLite) → 调度器(Docker API) ↓ Agent 决策模块(读取指标,生成新任务) ↓ 隔离训练容器(Docker) ↓ 日志解析与指标入库 ↓ 报告生成与告警通知这个流程看起来不复杂但每一步都有不少坑。接下来我直接给出一套可以在单机环境跑通的完整方案。3. 环境准备与前置条件这个实战项目的目标环境是一台带 NVIDIA GPU 的 Linux 服务器可以是你的个人开发机也可以是一台云 GPU 服务器。操作系统建议 Ubuntu 20.04/22.04其他 Linux 发行版思路一致。版本信息我不过度写死因为 AI 工具链变化极快。你需要确认本机已经具备以下能力3.1 硬件与驱动# 确认 GPU 驱动可用 nvidia-smi3.2 核心软件栈组件作用说明Docker实验环境隔离与并行调度建议已安装 Docker EngineNVIDIA Container Toolkit让容器访问 GPU必须配置否则容器内无法用 GPUPython 3.9编写调度脚本与 Agent建议使用 3.10 以上Redis 或 SQLite任务队列存储小实验用 SQLite 就够llama-index / openai SDK / 本地模型 APIAgent 决策调用按你的模型访问方式选择3.3 安装 NVIDIA Container Toolkit没有这一步Docker 容器是无法调用 GPU 的。# Ubuntu 下安装 nvidia-container-toolkit curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \ sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg # 配置源并安装 distribution$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/libnvidia-container/$distribution/nvidia-container-toolkit.list | \ sed s#deb https://#deb [signed-by/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update sudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtimedocker sudo systemctl restart docker验证方法docker run --rm --gpus all nvidia/cuda:12.2.0-base-ubuntu20.04 nvidia-smi如果能看到 GPU 信息说明容器 GPU 环境就绪。3.4 创建项目目录mkdir -p ~/auto_experiment/{tasks,logs,results,agents,dockerfiles} cd ~/auto_experiment之后我们的所有代码都放在这个目录下。4. 核心流程拆解有了环境基础我们把“AI 跑 300 个实验”这件事拆成五个步骤。这也是整个系统中最重要的流程设计。4.1 步骤一定义任务模板不要手动写 300 个实验命令而是用参数模板描述 300 种组合。比如定义学习率、batch size、模型层数三个维度的搜索空间系统自动组合。4.2 步骤二调度器取任务并启动容器调度器从队列中取出一个任务调用 Docker API 启动一个一次性容器。容器跑完就退出日志写入宿主机文件。4.3 步骤三Agent 决策模块介入Agent 模块读取已完成实验的指标判断下一步实验方向。这一步可以简单实现为“根据已跑实验的验证集指标动态调整参数范围”也可以接入 LLM 做更复杂的策略分析。我建议先跑通规则版再接 LLM。4.4 步骤四日志解析与指标入库训练脚本统一把关键指标输出到标准输出比如METRIC loss 0.023。调度器解析这些行写入 SQLite。4.5 步骤五最终报告生成全部任务跑完后系统读取 SQLite 数据生成一个 Markdown 报告包含每个实验的参数、结果、排名并推送通知。5. 完整示例代码实现我们用一个非常小的机器学习任务作为实验载体在 Iris 数据集上训练一个简单分类器超参搜索空间是学习率[0.001, 0.01, 0.1]和隐藏层大小[16, 32, 64]。这不是一个需要 GPU 的深度学习任务但完全能展示无人值守调度的完整流程。你可以把这个思路迁移到你自己的深度学习训练脚本上。5.1 定义实验任务模型文件路径tasks/task_schema.pyfrom dataclasses import dataclass, asdict import json dataclass class ExperimentTask: task_id: str script: str params: dict image: str python:3.10-slim priority: int 10 max_retry: int 2 def to_json(self): return json.dumps(asdict(self), ensure_asciiFalse) classmethod def from_json(cls, data: str): obj json.loads(data) return cls(**obj)说明task_id唯一标识任务。script是实验容器内要执行的训练脚本路径。params是超参数字典。priority用于排序数字越小优先级越高。max_retry允许失败重试次数。5.2 定义超参搜索空间与任务生成器文件路径tasks/task_generator.pyimport itertools import uuid from datetime import datetime from tasks.task_schema import ExperimentTask def generate_search_tasks(): 用笛卡尔积生成超参组合任务 learning_rates [0.001, 0.01, 0.1] hidden_sizes [16, 32, 64] dropouts [0.0, 0.2] combinations list(itertools.product(learning_rates, hidden_sizes, dropouts)) tasks [] for lr, hidden, dropout in combinations: task_id str(uuid.uuid4())[:8] task ExperimentTask( task_idtask_id, scripttrain_iris.py, params{ learning_rate: lr, hidden_size: hidden, dropout: dropout, epochs: 20, }, imagepython:3.10-slim ) tasks.append(task.to_json()) return tasks if __name__ __main__: tasks generate_search_tasks() now datetime.now().strftime(%Y%m%d%H%M%S) with open(ftasks/tasks_{now}.jsonl, w) as f: for t in tasks: f.write(t \n) print(f已生成 {len(tasks)} 个实验任务)这段代码使用了笛卡尔积来自动生成 18 个实验任务。如果你的搜索空间有 4 个维度每个维度 5 个值生成的任务数就是几百个。300 个实验就是这样自动批量生成的。5.3 训练脚本文件路径dockerfiles/train_iris.py这是一个放在 Docker 容器内部执行的训练脚本。为了演示调度系统脚本用随机数据模拟训练过程但结构和真实任务一致。真实项目中这里会替换成你的模型训练代码。import argparse import random import time import sys def parse_args(): parser argparse.ArgumentParser() parser.add_argument(--learning_rate, typefloat, default0.01) parser.add_argument(--hidden_size, typeint, default32) parser.add_argument(--dropout, typefloat, default0.0) parser.add_argument(--epochs, typeint, default20) return parser.parse_args() def simulate_train(args): 模拟训练并定期输出 METRIC 指标 best_loss 999.0 for epoch in range(args.epochs): # 模拟训练耗时 time.sleep(0.1) # 模拟 loss 变化学习率太大时会发散 noise random.uniform(0.01, 0.05) if args.learning_rate 0.05: loss 1.0 epoch * 0.02 noise else: loss 0.8 - epoch * 0.01 noise best_loss min(best_loss, loss) print(fepoch{epoch1} loss{loss:.4f}) print(fMETRIC loss {loss:.6f}) sys.stdout.flush() acc max(0.5, 0.95 - best_loss * 0.5) print(fMETRIC best_loss {best_loss:.6f}) print(fMETRIC accuracy {acc:.4f}) print(TRAIN_FINISHED) if __name__ __main__: args parse_args() simulate_train(args)这个脚本的核心设计是统一输出METRIC key value格式的指标行。调度器会按这个格式解析结果不需要解析复杂的 TensorFlow/PyTorch 日志。通过sys.stdout.flush()强制实时输出避免容器日志缓冲导致调度器读不到中间日志。训练结束输出TRAIN_FINISHED标记调度器根据这个标记判断任务成功。5.4 Dockerfile文件路径dockerfiles/Dockerfile.trainFROM python:3.10-slim WORKDIR /workspace # 本示例不需要额外依赖真实项目在这里安装 torch/tensorflow/sklearn 等 # RUN pip install torch --index-url https://download.pytorch.org/whl/cu118 COPY train_iris.py /workspace/train_iris.py ENTRYPOINT [python, /workspace/train_iris.py]构建镜像cd ~/auto_experiment/dockerfiles docker build -t exp-runner:latest -f Dockerfile.train .真实场景中你只需要修改 Dockerfile 安装你自己的训练依赖即可。镜像一旦构建好实验调度完全与宿主机环境隔离。5.5 核心调度器这是整个系统的核心负责从任务文件读任务、调用 Docker 运行、解析日志、写入结果。文件路径scheduler.pyimport docker import json import os import sqlite3 import time from datetime import datetime from tasks.task_schema import ExperimentTask class ExperimentScheduler: def __init__(self, task_file: str, db_path: str results/experiments.db): self.client docker.from_env() self.task_file task_file self.db_path db_path self.init_db() def init_db(self): os.makedirs(os.path.dirname(self.db_path), exist_okTrue) conn sqlite3.connect(self.db_path) cur conn.cursor() cur.execute( CREATE TABLE IF NOT EXISTS experiments ( id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT, script TEXT, params TEXT, start_time TEXT, end_time TEXT, status TEXT, best_loss REAL, accuracy REAL, log_file TEXT ) ) conn.commit() conn.close() def load_tasks(self): tasks [] with open(self.task_file, r) as f: for line in f: line line.strip() if line: task ExperimentTask.from_json(line) tasks.append(task) return tasks def run_task(self, task: ExperimentTask, log_dir: str) - str: 运行单个实验任务返回日志文件路径 os.makedirs(log_dir, exist_okTrue) log_file os.path.join(log_dir, f{task.task_id}.log) container_name fexp-{task.task_id} try: cmd [ --learning_rate, str(task.params[learning_rate]), --hidden_size, str(task.params[hidden_size]), --dropout, str(task.params[dropout]), --epochs, str(task.params[epochs]), ] container self.client.containers.run( imagetask.image, commandcmd, namecontainer_name, detachTrue, removeTrue, runtimenvidia, environment{NVIDIA_VISIBLE_DEVICES: 0}, volumes{ os.path.abspath(task.script): { bind: /workspace/train_iris.py, mode: ro } }, ) logs container.logs(streamTrue, followTrue) with open(log_file, w) as f: for line in logs: text line.decode(utf-8).strip() f.write(text \n) f.flush() status self.client.containers.get(container_name).wait() return_code status.get(StatusCode) if return_code 0: return log_file else: return fERROR: exit code {return_code} except docker.errors.ContainerError as e: return fERROR: {str(e)} finally: try: self.client.containers.get(container_name).remove(forceTrue) except docker.errors.NotFound: pass def parse_logs(self, log_file: str): 从日志中解析 METRIC 行 best_loss None accuracy None if not os.path.exists(log_file): return best_loss, accuracy with open(log_file, r) as f: for line in f: if METRIC in line: parts line.strip().split() if len(parts) 3: key, value parts[1], float(parts[2]) if key best_loss: best_loss value elif key accuracy: accuracy value return best_loss, accuracy def record_result(self, task: ExperimentTask, start_time, end_time, status, log_file, best_loss, accuracy): conn sqlite3.connect(self.db_path) cur conn.cursor() cur.execute( INSERT INTO experiments (task_id, script, params, start_time, end_time, status, best_loss, accuracy, log_file) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) , ( task.task_id, task.script, json.dumps(task.params), start_time, end_time, status, best_loss, accuracy, log_file )) conn.commit() conn.close() def run_all(self): tasks self.load_tasks() print(f共加载 {len(tasks)} 个任务) for i, task in enumerate(tasks): start_time datetime.now().isoformat() log_file self.run_task(task, logs) end_time datetime.now().isoformat() best_loss, accuracy self.parse_logs(log_file) if ERROR in str(log_file): status failed else: status success self.record_result(task, start_time, end_time, status, log_file, best_loss, accuracy) print(f[{i1}/{len(tasks)}] 任务 {task.task_id} 状态{status} best_loss{best_loss}) time.sleep(1) if __name__ __main__: import sys task_file sys.argv[1] if len(sys.argv) 1 else tasks/tasks_20250101000000.jsonl scheduler ExperimentScheduler(task_file) scheduler.run_all()这段代码是“串行版”调度器。单机小规模场景足够逻辑也清晰。如果你的服务器有多块 GPU可以改成多进程并发我会在第 8 节给出并发思路。这里容易踩的坑是Docker SDK 的containers.run如果使用detachTrue容器会自动清理吗需要配合removeTrue或者手动remove(forceTrue)否则会残留大量死容器。容器日志流式读取必须加followTrue否则日志会在任务结束后一次性接收无法实时看到训练进度。environment中的NVIDIA_VISIBLE_DEVICES指定使用哪块 GPU。如果服务器有 4 卡可以定义成一个资源池调度器根据每卡负载分配。5.6 指标汇总与报告生成文件路径report.pyimport sqlite3 from datetime import datetime def generate_report(db_path: str results/experiments.db): conn sqlite3.connect(db_path) cur conn.cursor() cur.execute( SELECT task_id, params, status, best_loss, accuracy, start_time, end_time FROM experiments ORDER BY best_loss ASC ) rows cur.fetchall() lines [# 夜间实验结果报告, ] lines.append(f生成时间: {datetime.now().isoformat()}) lines.append(f实验总数: {len(rows)}) lines.append() lines.append(| 排序 | 任务ID | 学习率 | 隐藏层 | dropout | 状态 | best_loss | accuracy |) lines.append(| --- | --- | --- | --- | --- | --- | --- | --- |) for idx, row in enumerate(rows, 1): params eval(row[2]) # 注意生产环境不要用 eval这里仅做演示 lr params.get(learning_rate, -) hidden params.get(hidden_size, -) dropout params.get(dropout, -) lines.append( f| {idx} | {row[0]} | {lr} | {hidden} | {dropout} | {row[3]} | f{row[4] if row[4] else -} | {row[5] if row[5] else -} | ) report \n.join(lines) report_file results/report.md with open(report_file, w) as f: f.write(report) print(report_file) return report_file if __name__ __main__: generate_report()运行报告生成python report.py6. 运行结果与效果验证现在我们把整套流程跑通。假设任务文件已经生成在tasks/tasks_20250101000000.jsonl。6.1 构建镜像cd ~/auto_experiment/dockerfiles docker build -t exp-runner:latest -f Dockerfile.train .6.2 启动调度cd ~/auto_experiment python scheduler.py tasks/tasks_20250101000000.jsonl预期输出类似共加载 18 个任务 [1/18] 任务 3d9f2c1a 状态success best_loss0.650012 [2/18] 任务 8a2b5e6d 状态success best_loss0.710334 ... [18/18] 任务 a3bfd0e2 状态success best_loss0.5801016.3 查看数据库sqlite3 results/experiments.db select task_id, params, status, best_loss, accuracy from experiments;6.4 查看报告cat results/report.md如果报告生成成功你应该能看到一张按best_loss升序排列的表格。这就相当于 AI 在夜间帮你完成了所有参数组合的探索并且把最优参数排在了最前面。6.5 判断系统是否成功三条标准所有任务都产生了日志文件没有容器残留。SQLite 中有完整任务状态、指标记录。Thread 报告按指标排序正确。如果失败先看logs/下对应任务日志文件。多数错误是脚本路径挂载错误或依赖缺失。7. 常见问题与排查方法问题现象可能原因排查方式解决方案容器内无法使用 GPUNVIDIA Container Toolkit 未配置docker run --rm --gpus all nvidia/cuda:12.2.0-base-ubuntu20.04 nvidia-smi重新安装 toolkit 并重启 docker实验任务全部启动失败镜像不存在或脚本路径错误查看调度器报错信息手动执行镜像测试docker build重新构建镜像检查 Dockerfile 和挂载路径loss 指标始终为空训练脚本没有输出METRIC行查看日志文件内容统一指标输出格式确保sys.stdout.flush()Docker 容器大量残留removeTrue未生效或异常中断docker ps -a | grep exp-在finally中强制 remove任务执行到一半卡死日志读取阻塞或容器挂起查看docker stats确认容器状态给容器加超时控制超时后 kill检查训练脚本是否有死循环并发任务导致资源耗尽调度器同时启动太多容器nvidia-smi查看 GPU 显存占用引入信号量控制并发数量预留显存Agent 决策没有效果参数搜索空间太小或指标噪声太大打印 Agent 的决策输入输出增加参数候选项使用多次重复实验取均值8. 基于 AI Agent 的轻量决策增强上面这套系统已经可以称为“无人值守实验系统”但它还不是标题里说的“AI 跑了 300 个实验”。真正的 AI 增强体现在 Agent 决策层。下面给出一个轻量实现让它具有“根据实验反馈动态调整超参”的能力。文件路径agents/agent_decision.pyimport sqlite3 import openai import os class ExperimentAgent: def __init__(self, db_path: str, model: str gpt-4o-mini): self.db_path db_path # 示意代码真实使用时请通过环境变量配置 self.client openai.OpenAI(api_keyos.getenv(OPENAI_API_KEY)) self.model model def get_best_results(self, limit: int 10): conn sqlite3.connect(self.db_path) cur conn.cursor() cur.execute( SELECT task_id, params, best_loss, accuracy FROM experiments WHERE status success ORDER BY best_loss ASC LIMIT ? , (limit,)) rows cur.fetchall() conn.close() return rows def decide_next_experiments(self, n: int 5): history self.get_best_results() history_text \n.join([ ftask: {r[0]}, params: {r[1]}, best_loss: {r[2]}, accuracy: {r[3]} for r in history ]) prompt f 你是一个机器学习实验调度助手。以下是我们已经完成的实验及指标best_loss 越低越好。 {history_text} 请基于当前结果生成 {n} 个新的实验配置。要求 1. 新配置要与已有配置有明显差异。 2. 优先探索 best_loss 较低的区域附近。 3. 返回 JSON 数组每个元素包含 learning_rate、hidden_size、dropout 三个字段。 4. 不要输出任何额外解释只输出 JSON。 resp self.client.chat.completions.create( modelself.model, messages[{role: user, content: prompt}], temperature0.7, response_format{type: json_object} ) content resp.choices[0].message.content return content这个 Agent 的核心逻辑是把已完成实验的结果表格喂给大模型让模型基于历史表现判断下一步超参方向生成 JSON 配置。在真实项目中你可以把这个 JSON 解析后交给任务生成器继续生成下一轮实验。这样就形成了“实验-反馈-再实验”的闭环。需要注意的风险是大模型可能生成无效超参比如负数学习率、字符串数字需要加入校验层。Agent 决策存在随机性建议设置随机种子或让 Agent 提供多个候选再通过工程规则筛选。不要把所有决策都交给模型对安全边界类参数如显存上限、训练时长上限要由工程层强制拦截。如果你不想依赖外部大模型 API也可以用本地模型替换核心思路不变读取指标、生成策略、校验参数。9. 生产落地最佳实践从“在笔记本上跑通”到“在服务器上稳定跑一夜”中间还有很长的距离。以下是我认为最值得注意的几点。9.1 并发控制串行调度在单卡小实验场景没问题但如果你想一晚上跑 300 个实验必须并发。推荐做法from concurrent.futures import ThreadPoolExecutor with ThreadPoolExecutor(max_workers8) as executor: futures {executor.submit(self.run_task, task): task for task in tasks} for future in as_completed(futures): task futures[future] result future.result() self.record_result(task, result)重点在于max_workers要根据 GPU 数量和显存动态设置。可以先探测 GPU 卡数和显存大小import subprocess output subprocess.check_output([nvidia-smi, --query-gpumemory.free, --formatcsv,noheader]) free_memory [int(x.split()[0]) for x in output.decode().strip().split(\n)]通过显存剩余量决定并发上限避免多个任务抢显存导致 OOM。9.2 Docker 镜像复用尽量把你的训练依赖打进一个基础镜像而不是每次跑实验都pip install。一个包含了 PyTorch、CUDA 和常用库的镜像构建一次后后续 300 个任务只是反复docker run效率高很多。9.3 网络策略与镜像加速如果服务器在国内拉取 Docker Hub 镜像可能比较慢。建议提前把基础镜像拉到本地或者配置镜像加速器。9.4 故障恢复与断点续跑系统最怕跑到凌晨 3 点崩溃。建议任务状态写数据库时每完成一个就提交一次不要攒批提交。调度器增加--resume参数启动时查询数据库中已完成的任务 ID跳过已经跑完的实验。使用环境变量注入超时时间超时任务自动视为失败并记录。9.5 日志保留策略300 个实验会生成 300 个日志文件。当天保留全量日志一周后可以压缩归档。指标结果永远保留在 SQLite 中日志文件可以按task_id命名方便检索。9.6 安全与权限这里要特别提醒调度器会以宿主机权限调用 Docker。生产环境必须保证调度器代码可信不能被未授权用户操控。不要让 Web 接口直接暴露调度器的 Docker 权限。容器内的训练脚本应尽量减少挂载宿主机目录尤其是不要挂载/root、/etc等敏感目录。数据库需要定时备份。SQLite 在并发写入时可能锁库建议生产环境改用 PostgreSQL 或 MySQL。10. 总结与后续学习方向回到最初的问题你睡觉的 8 小时AI 能不能帮你跑 300 个实验从工程实现看完全可以。你只需要做好任务定义、容器隔离、调度执行、日志解析和报告生成这五件事再叠加一个 AI Agent 决策层就能把大量的参数探索工作交给机器在夜间完成。这个方案的价值不在于“AI 替你发明了新算法”而在于它把工程师从繁琐的重复操作中解放出来。你有更多时间思考和阅读而不是盯着 terminal 等结果。如果你想继续深入建议按下面路线学习把 Iris 示例替换成自己的模型训练代码在单机上跑通。加入并发执行和显存感知调度用真正的 GPU 任务验证。接入 Prometheus Grafana监控 GPU 利用率和实验进度。用 Airflow 或 Dagster 替代自研调度器降低维护成本。把 Agent 决策层接入多轮迭代形成完整的 AutoML 闭环。这套系统的坑主要集中在工程层而不是 AI 层。只要先把 Docker 隔离、日志规范、状态管理和资源控制这四件事做好剩下的就是扩展并发和加 Agent 的问题了。建议收藏这篇文章搭一套最小系统跑一夜第二天早上看报告的感觉比你手动盯屏一晚上要舒服得多。