feat(all): 源精度命名体系、工作流可观测台、节点停用管理与白名单归档
核心变更:
1. GridAxisValue 源精度命名
- 新增 GridAxisValue 类型,携带 f64 数值 + YAML 源书写文本(Deref 透明兼容算术)
- config.rs 绕过 serde_yaml 归一化,逐 token 捕获轴值原文(logg: 5.0 → g5.0)
- runner/executor/scheduler 全链路改用 DB TEXT 列权威 point_name,
修复 REAL 列回读丢精度导致的 model_name 错配
2. 工作流执行可观测台
- 新增 stats/progress/points 三组 API(进度时间序列、经验速率 ETA、
停滞预警、逐点明细分页、收敛性热力图数据)
- 新增 workflow_progress_snapshots 表 + tasks/grid_points 耗时列
- runner 携带 last_iter/worst_depth/n_depths 进 conv.json
- 前端新增 hash 路由、工作流详情页(概览/网格点/收敛分析三 Tab)、YAML 编辑器
3. 节点停用/启用管理
- 新增 disabled 状态 + disable/enable API;停用节点保持心跳但停止分发,
worker 空闲待命而非退出;移除 revoke API,token 失效统一走重发覆盖;
移除 host_name 字段
4. 白名单结果归档
- 新增 result_filter 模块,只归档有语义产物,丢弃 Tlusty 中间单元(~2MB/模型)
- executor 原子写入归档 + 200 点 LRU 上限
5. 历史数据导入
- sync_seeds 重写为 import_results:经 /admin/import_seed 标记 converged +
按新版命名迁移产物树
6. 部署与目录重规划
- data/results→seeds、data/archive→result + migrate_data_dirs.sh
- deploy.sh 增强(SSH 复用、Profile、远程 env);Dockerfile 瘦身
7. 文档同步更新 api/database/architecture/deployment
This commit is contained in:
+109
-3
@@ -125,6 +125,7 @@ impl<'a> ExecutionRunner<'a> {
|
||||
pub async fn run_model(
|
||||
&self,
|
||||
params: &GridPointParams,
|
||||
name: &str,
|
||||
task_type: TaskType,
|
||||
custom_chain: Option<Vec<StageConfig>>,
|
||||
seed_atmos: Option<&Path>,
|
||||
@@ -132,6 +133,7 @@ impl<'a> ExecutionRunner<'a> {
|
||||
) -> Result<ModelSummary> {
|
||||
self.run_model_with_timeout(
|
||||
params,
|
||||
name,
|
||||
task_type,
|
||||
custom_chain,
|
||||
seed_atmos,
|
||||
@@ -141,17 +143,32 @@ impl<'a> ExecutionRunner<'a> {
|
||||
.await
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn run_model_with_timeout(
|
||||
&self,
|
||||
params: &GridPointParams,
|
||||
name: &str,
|
||||
task_type: TaskType,
|
||||
custom_chain: Option<Vec<StageConfig>>,
|
||||
seed_atmos: Option<&Path>,
|
||||
synspec_cfg: Option<&SynspecConfig>,
|
||||
timeout_sec: u64,
|
||||
) -> Result<ModelSummary> {
|
||||
let name = params.model_name();
|
||||
let model_dir = self.work_dir.join(&name);
|
||||
// `name` 取自权威的 TaskSpec.point_name(DB 的 grid_points.name 列,源精度正确),
|
||||
// 而非 params.model_name()。原因:服务端把 GridPointParams 存成 6 个 REAL 数值列,
|
||||
// 回读时用 from_value() 反推文本会丢精度("5.0"→"5"),导致 params.model_name()
|
||||
// 产出错误名(g5 而非 g5.0)。point_name 走独立 TEXT 列,精度全程保留。
|
||||
// 下游(沙盒子目录、各阶段快照、conv.json.name、归档目录)全部用此 name,
|
||||
// 故只需在此处用权威 name 即可让整条链精度正确。
|
||||
let derived = params.model_name();
|
||||
if derived != name {
|
||||
warn!(
|
||||
"网格点权威名 {} 与 params 重推名 {} 不一致(DB REAL 列回读丢精度所致),\
|
||||
采用权威 point_name",
|
||||
name, derived
|
||||
);
|
||||
}
|
||||
let model_dir = self.work_dir.join(name);
|
||||
tokio::fs::create_dir_all(&model_dir).await?;
|
||||
|
||||
info!("开始物理计算网格模型 {} (类型: {:?})", name, task_type);
|
||||
@@ -279,6 +296,9 @@ impl<'a> ExecutionRunner<'a> {
|
||||
best_max_relc: None,
|
||||
elapsed_sec: stage_t0.elapsed().as_secs_f64(),
|
||||
note: None,
|
||||
last_iter: None,
|
||||
worst_depth: None,
|
||||
n_depths: None,
|
||||
};
|
||||
|
||||
if rc == 0 && fort7.is_file() {
|
||||
@@ -287,6 +307,11 @@ impl<'a> ExecutionRunner<'a> {
|
||||
let res = check_fort9(&fort9, eff_chmax);
|
||||
stage_summary.converged = res.converged;
|
||||
stage_summary.best_max_relc = Some(res.max_relc);
|
||||
// 携带迭代诊断量进 conv.json(旧版在此处丢弃):
|
||||
// 迭代数/最差深度点供详情页阶段链展示收敛难度。
|
||||
stage_summary.last_iter = res.last_iter;
|
||||
stage_summary.worst_depth = Some(res.worst_depth);
|
||||
stage_summary.n_depths = Some(res.n_depths);
|
||||
|
||||
// Save fort.9 snapshot
|
||||
let snap_name = format!("{}.{}_chmax{}.9", name, stage_def.label, eff_chmax);
|
||||
@@ -306,6 +331,30 @@ impl<'a> ExecutionRunner<'a> {
|
||||
stage_summary.note = Some(format!("tlusty rc={} or missing fort.7", rc));
|
||||
}
|
||||
|
||||
// 快照本阶段的同名输入/输出文件,带阶段标签保留。
|
||||
// 背景:.5/.6/.err/nst 在每阶段用同名文件覆盖,若不快照则只有最后阶段(nl)
|
||||
// 的版本能存活到归档,nc 等中间阶段的日志/输入会丢失。失败阶段的日志对排错
|
||||
// 尤其重要,因此此处无条件(不论 rc 是否为 0)快照。
|
||||
// 命名风格与上方 .7/.9 快照一致:<name>.<label>.<后缀>(单 name,不重复)。
|
||||
// 注意:stage_def.label 由配置保证唯一(lte/nc/nl/seed_nc),不会与 synspec 产物冲突。
|
||||
//
|
||||
// 不快照 fort.9:上方 L292 已把 fort.9 收敛诊断存为 `<name>.<label>_chmax*.9`
|
||||
// (带 chmax 阈值语义),再快照成 `<name>.<label>.9` 会与它内容完全重复。
|
||||
// 故 .9 收敛诊断只保留 `_chmax*.9` 一份,不留重复快照。
|
||||
// (suffix, full_src_name) —— suffix 用于快照名后缀,full_src_name 用于定位源文件
|
||||
for (suffix, full_name) in [
|
||||
("5", format!("{}.5", name)),
|
||||
("6", format!("{}.6", name)),
|
||||
("err", format!("{}.err", name)),
|
||||
("nst", "nst".to_string()),
|
||||
] {
|
||||
let src = model_dir.join(&full_name);
|
||||
if src.is_file() {
|
||||
let snap = model_dir.join(format!("{}.{}.{}", name, stage_def.label, suffix));
|
||||
let _ = tokio::fs::copy(&src, &snap).await;
|
||||
}
|
||||
}
|
||||
|
||||
final_chmax = stage_def.chmax;
|
||||
final_converged = stage_summary.converged;
|
||||
if let Some(r) = stage_summary.best_max_relc {
|
||||
@@ -429,9 +478,28 @@ impl<'a> ExecutionRunner<'a> {
|
||||
synspec_err = Some("No atmosphere .7 produced".to_string());
|
||||
}
|
||||
|
||||
// 清理冗余的裸文件:这些文件的内容已被带阶段标签的快照或重命名的科学产物覆盖,
|
||||
// 保留它们只会与归档里的 <name>.<label>.* / <name>.iden / <name>.cont 等重复(尤其
|
||||
// .spec/.cont 是大文件,双份存储浪费磁盘)。删除后归档目录干净无冗余。
|
||||
// 注意:fort.8(synspec 输入大气)和 fort.55(synspec 控制卡)有独立语义,予以保留。
|
||||
for redundant in [
|
||||
format!("{}.5", name), // 同 <name>.<最后阶段label>.5
|
||||
format!("{}.6", name), // 同 <name>.<最后阶段label>.6
|
||||
format!("{}.err", name), // 同 <name>.<最后阶段label>.err
|
||||
"nst".to_string(), // 同 <name>.<最后阶段label>.nst
|
||||
"fort.9".to_string(), // 内容已被 <name>.<label>_chmax*.9 收敛诊断覆盖
|
||||
"fort.12".to_string(), // 同 <name>.iden(synspec 谱线证认)
|
||||
"fort.17".to_string(), // 同 <name>.cont(synspec 连续谱)
|
||||
] {
|
||||
let p = model_dir.join(&redundant);
|
||||
if p.is_file() {
|
||||
let _ = tokio::fs::remove_file(&p).await;
|
||||
}
|
||||
}
|
||||
|
||||
let elapsed_sec = t0.elapsed().as_secs_f64();
|
||||
let summary = ModelSummary {
|
||||
name,
|
||||
name: name.to_string(),
|
||||
params: params.clone(),
|
||||
stages: stage_summaries,
|
||||
converged: final_converged,
|
||||
@@ -460,6 +528,8 @@ impl<'a> ExecutionRunner<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::models::{GridAxisValue, GridPointParams};
|
||||
|
||||
#[test]
|
||||
fn test_synspec_timeout_calculation() {
|
||||
let long_tlusty_timeout: u64 = 7200;
|
||||
@@ -470,4 +540,40 @@ mod tests {
|
||||
let synspec_timeout_short = 600_u64.min(short_tlusty_timeout);
|
||||
assert_eq!(synspec_timeout_short, 300);
|
||||
}
|
||||
|
||||
/// 回归测试:复现命名精度丢失场景,并锁定「runner 用 point_name 作权威名」的契约。
|
||||
///
|
||||
/// 背景:服务端 grid_points 表把 GridPointParams 存成 6 个 REAL 列,回读时用
|
||||
/// `GridAxisValue::from_value()` 反推文本(`format_float_minimal`),整数-valued
|
||||
/// 浮点数会丢小数(5.0 → "5")。于是 `params.model_name()` 产出 `g5` 而非 `g5.0`。
|
||||
/// 而 `TaskSpec.point_name`(DB 的 name TEXT 列,源精度)始终是 `g5.0`。
|
||||
///
|
||||
/// runner 的 `run_model_with_timeout` 现接收外部 `name: &str`(由 executor 传入
|
||||
/// `task.point_name`),不再用降级的 `params.model_name()`。本测试构造降级后的
|
||||
/// params,证明二者确实不同,从而确认「必须用 point_name」的修复是必要的。
|
||||
#[test]
|
||||
fn test_point_name_bypasses_degraded_params_model_name() {
|
||||
// 模拟 DB REAL 列回读后的 params:logg 经 from_value(5.0) 丢精度
|
||||
let degraded = GridPointParams {
|
||||
teff: GridAxisValue::from_value(20000.0),
|
||||
logg: GridAxisValue::from_value(5.0), // text 退化为 "5"
|
||||
loghe: GridAxisValue::from_value(-2.0),
|
||||
logc: GridAxisValue::from_value(-4.0),
|
||||
logn: GridAxisValue::from_value(-4.0),
|
||||
logo: GridAxisValue::from_value(-4.0),
|
||||
};
|
||||
// 权威 point_name(DB name 列,保留源精度)
|
||||
let point_name = "t20000_g5.0_he-2_c-4_n-4_o-4";
|
||||
|
||||
// 降级的 params 重推出的名字丢了 ".0"
|
||||
assert_ne!(
|
||||
degraded.model_name(),
|
||||
point_name,
|
||||
"降级 params.model_name() 应与权威 point_name 不同(这是 bug 的可观测证据)"
|
||||
);
|
||||
assert_eq!(degraded.model_name(), "t20000_g5_he-2_c-4_n-4_o-4");
|
||||
// runner 现在直接采用 point_name(不再调 params.model_name()),故归档/产物名正确
|
||||
let authoritative_name = point_name; // 即 executor 传入的 task.point_name
|
||||
assert_eq!(authoritative_name, "t20000_g5.0_he-2_c-4_n-4_o-4");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user