2. 参数扫描任务建模
并行调度的前提不是“把任务丢进队列”,而是先把一次参数扫描准确表达成调度器能够理解的对象。参数扫描任务建模要回答四个问题:扫描哪些参数、每个任务覆盖多大参数范围、任务之间是否存在先后约束、最终如何组织成可执行的任务图。建模质量直接决定并行度能否被充分释放,也决定结果是否可追溯、失败能否局部重跑。
参数空间表示:把“扫描意图”变成可枚举集合
参数空间是扫描任务的全部输入组合。工程上通常用“参数名—取值集合”描述,再通过笛卡尔积展开为具体组合。例如某次电路参数扫描需要考察三个维度:负载电阻 Rload 取 10、22、47、100 Ω,供电电压 Vdd 取 3.3、5.0 V,温度 Temp 取 -40、25、85 ℃。组合总数为 4×2×3=24。若每个组合还要跑 5 个随机种子,则实例数为 120。
表示方式建议采用“维度定义 + 展开规则”两层结构,而不是直接罗列 120 条记录。维度定义便于校验和复用,展开规则负责生成实例 ID。实例 ID 应可逆地编码参数,例如 R47_V5.0_T25_S3,这样日志、结果目录和失败重跑都能直接定位到参数组合,而不必反查映射表。对于连续参数,应先离散化为有限网格,并明确网格步长与边界,避免“扫描空间”在实现时被隐式改变。
任务粒度划分:在并行度与开销之间取平衡
任务粒度指一个调度单元覆盖多少参数组合。粒度过细,任务数暴涨,调度、启动、结果汇总的开销可能超过计算本身;粒度过粗,单个任务运行时间长,负载不均,尾部拖尾明显。常见策略有三种:按单组合切分(一组合一任务)、按参数维度分块(如固定 Vdd 与 Temp,把 Rload 的 4 个取值合并为一个任务)、按预估耗时装箱(把预计运行时间相近的组合聚成一批)。
以 24 个组合、单组合预计 40 s 为例:若一组合一任务,理论并行 24 路,但每个任务启动与结果写回约 3 s,开销占比约 7%;若按 Rload 分块成 6 个任务,每任务约 160 s,开销占比降到约 2%,但最大并行度只有 6。实际选择应结合可用算力:若只有 8 个执行槽,6 个分块任务已接近饱和,细粒度反而增加排队管理成本。粒度划分还应保证同一任务内参数组合可独立计算,不引入任务内串行依赖。
依赖关系建模:区分数据依赖与资源约束
参数扫描中常见的依赖有三类。第一类是数据依赖:后处理任务需要等待其覆盖的全部仿真任务完成,例如汇总 24 个组合的功耗统计。第二类是阶段依赖:先生成网表再仿真,或先仿真再提取指标。第三类是资源约束:某些任务必须占用特定许可证或大内存节点,这属于调度约束而非逻辑依赖,建模时不应混入 DAG 的边,否则会人为拉长关键路径。
依赖建模的关键是明确“边代表什么”。建议约定:有向边 A→B 表示 B 必须在 A 成功完成后才能开始,且 B 读取 A 的输出。对于仅需要“全部完成才汇总”的场景,可引入一个汇聚节点,把 24 条边收敛为 24→1,避免后处理节点被重复表达。对于允许部分结果先汇总的流式场景,则应拆成多个分批汇聚节点,而不是一个全局屏障。
任务图构建与实例分析
把上述要素组合起来,就得到可调度的 DAG。下面以一个 6 组合的简化扫描为例:参数为 Rload∈{10,47,100}、Vdd∈{3.3,5.0},先生成网表,再并行仿真,最后统一汇总。图中节点标注了任务类型与覆盖范围,边表示“完成后才能开始并传递结果”。
该图的关键路径为“生成网表→任一仿真→汇总”,并行宽度为 6。若把 6 个仿真合并为 2 个分块任务(每个覆盖 3 个组合),并行宽度降为 2,但单任务时长约为原来的 3 倍,总完成时间取决于执行槽数量:在 2 个槽时两者接近,在 6 个槽时细粒度明显更优。这说明粒度选择必须与可用并行资源一起评估,而不能只看任务数。
构建 DAG 时还应处理失败与重跑语义。建议每个仿真节点携带参数组合、输入网表版本和输出路径三元信息,汇总节点只依赖“成功完成”的前驱;失败节点保留在图中并标记状态,重跑时仅重置该节点及其下游,而不是重建整张图。这样既保证结果可追溯,也避免局部失败导致全量重算。最后,任务图应可序列化为结构化描述(节点表 + 边表),便于调度器加载、校验环依赖并计算入度,为后续并行调度提供稳定输入。