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:
fmq
2026-07-31 01:34:05 +08:00
parent b91f1e4fa5
commit 1bfa240cb0
73 changed files with 12332 additions and 1608 deletions
+56 -61
View File
@@ -11,16 +11,11 @@ use crate::db::Database;
pub struct GridScheduler {
db: Database,
queue: Arc<SqliteTaskQueue>,
_results_dir: String,
}
impl GridScheduler {
pub fn new(db: Database, queue: Arc<SqliteTaskQueue>, results_dir: String) -> Self {
Self {
db,
queue,
_results_dir: results_dir,
}
pub fn new(db: Database, queue: Arc<SqliteTaskQueue>) -> Self {
Self { db, queue }
}
/// Expands grid points from config and registers them into the database.
@@ -52,19 +47,19 @@ impl GridScheduler {
}
let mut points = Vec::new();
for &teff in &cfg.grid.teff {
for &logg in &cfg.grid.logg {
for &loghe in &cfg.grid.loghe {
for &logc in &cfg.grid.logc {
for &logn in &cfg.grid.logn {
for &logo in &cfg.grid.logo {
for teff in &cfg.grid.teff {
for logg in &cfg.grid.logg {
for loghe in &cfg.grid.loghe {
for logc in &cfg.grid.logc {
for logn in &cfg.grid.logn {
for logo in &cfg.grid.logo {
points.push(GridPointParams {
teff,
logg,
loghe,
logc,
logn,
logo,
teff: teff.clone(),
logg: logg.clone(),
loghe: loghe.clone(),
logc: logc.clone(),
logn: logn.clone(),
logo: logo.clone(),
});
}
}
@@ -196,7 +191,7 @@ impl GridScheduler {
.await?;
let mut dispatched = 0;
for (name, params, _wave) in pending {
for (name, params, wave) in pending {
// Check if any seed is available in DB for active SeedStep schedulingseeds 全局共享)
let (task_type, seed_name) = match self.db.find_best_seed_from_db(&params).await {
Ok(Some(seed_match)) => {
@@ -217,6 +212,7 @@ impl GridScheduler {
seed_point_name: seed_name,
timeout_sec,
workflow_name: Some(workflow_name.to_string()),
wave,
};
self.db.insert_task(&task_spec).await?;
@@ -274,6 +270,7 @@ impl GridScheduler {
pub async fn trigger_seed_step_fallback(
&self,
params: &GridPointParams,
name: &str,
workflow_name: &str,
) -> Result<bool> {
// 该工作流须仍处于 running 态才回退(避免 stop 后继续派发)
@@ -291,9 +288,13 @@ impl GridScheduler {
return Ok(false);
}
let name = params.model_name();
// name 取自权威的 report.point_name= grid_points.name 列,源精度正确),
// 而非 params.model_name()。原因:此处 params 经 node 上报回传,其 logg 等
// 轴在服务端 DB REAL 列回读时已丢精度(5.0→"5"),重推 model_name() 会得到
// 降级名(g5 而非 g5.0),导致回退任务的 point_name 与 grid_points.name 列错配,
// 状态更新静默失败。与 runner 的修复保持同一原则:用权威 name。
// 种子回退仅一次:该点在该工作流中已经派发过 seed_step 任务就不再触发新的回退
if self.db.has_seed_step_attempt(&name, workflow_name).await? {
if self.db.has_seed_step_attempt(name, workflow_name).await? {
info!(
"网格点 {} 已使用过一次种子热启动回退,不再重复回退,保持 failed 终态",
name
@@ -306,30 +307,27 @@ impl GridScheduler {
if let Some(seed_match) = seed_match_opt {
let timeout_sec = self.get_workflow_timeout_sec(workflow_name).await;
let name = params.model_name();
let task_spec = TaskSpec {
task_id: Uuid::new_v4(),
point_name: name.clone(),
point_name: name.to_string(),
params: params.clone(),
task_type: TaskType::SeedStep,
seed_point_name: Some(seed_match.name.clone()),
timeout_sec,
workflow_name: Some(workflow_name.to_string()),
// seed_step 是失败后的回退任务,wave 设 0 不抢占正常调度队列里的低难度 wave 优先级。
wave: 0,
};
self.db.insert_task(&task_spec).await?;
self.db
.update_grid_status(
&name,
common::models::GridPointStatus::Queued,
workflow_name,
)
.update_grid_status(name, common::models::GridPointStatus::Queued, workflow_name)
.await?;
if let Err(e) = self.queue.push_task(&task_spec).await {
let _ = self
.db
.update_grid_status(
&name,
name,
common::models::GridPointStatus::Pending,
workflow_name,
)
@@ -358,7 +356,6 @@ mod tests {
let temp_dir = tempfile::tempdir().unwrap();
let db_path = temp_dir.path().join("sched_db.db");
let queue_db_path = temp_dir.path().join("sched_queue.db");
let results_dir = temp_dir.path().join("results");
let db = Database::new(&db_path.to_string_lossy()).await.unwrap();
let queue = Arc::new(
@@ -366,20 +363,17 @@ mod tests {
.await
.unwrap(),
);
let scheduler = GridScheduler::new(
db.clone(),
queue.clone(),
results_dir.to_string_lossy().to_string(),
);
let scheduler = GridScheduler::new(db.clone(), queue.clone());
#[allow(deprecated)] // results 是死字段,构造时必须填 None
let cfg = GridConfig {
grid: GridAxesConfig {
teff: vec![35000.0],
logg: vec![5.5],
loghe: vec![-1.0],
logc: vec![-2.0],
logn: vec![-2.0],
logo: vec![-2.0],
teff: vec![35000.0.into()],
logg: vec![5.5.into()],
loghe: vec![(-1.0).into()],
logc: vec![(-2.0).into()],
logn: vec![(-2.0).into()],
logo: vec![(-2.0).into()],
},
chain: vec![],
synspec: None,
@@ -425,16 +419,17 @@ mod tests {
.await
.unwrap(),
);
let scheduler = GridScheduler::new(db.clone(), queue.clone(), "results".to_string());
let scheduler = GridScheduler::new(db.clone(), queue.clone());
#[allow(deprecated)] // results 是死字段,构造时必须填 None
let mk_cfg = |teff: f64| GridConfig {
grid: GridAxesConfig {
teff: vec![teff],
logg: vec![5.5],
loghe: vec![-1.0],
logc: vec![-2.0],
logn: vec![-2.0],
logo: vec![-2.0],
teff: vec![teff.into()],
logg: vec![5.5.into()],
loghe: vec![(-1.0).into()],
logc: vec![(-2.0).into()],
logn: vec![(-2.0).into()],
logo: vec![(-2.0).into()],
},
chain: vec![],
synspec: None,
@@ -466,12 +461,12 @@ mod tests {
// 重新推一个 wf_a 任务(上一行 pop 掉了),再初始化 wf_b
db.update_grid_status(
&GridPointParams {
teff: 35000.0,
logg: 5.5,
loghe: -1.0,
logc: -2.0,
logn: -2.0,
logo: -2.0,
teff: 35000.0.into(),
logg: 5.5.into(),
loghe: (-1.0).into(),
logc: (-2.0).into(),
logn: (-2.0).into(),
logo: (-2.0).into(),
}
.model_name(),
common::models::GridPointStatus::Pending,
@@ -496,12 +491,12 @@ mod tests {
// 关键断言:把 wf_a 任务重新推回队列后,初始化 wf_b 不应清空它。
db.update_grid_status(
&GridPointParams {
teff: 35000.0,
logg: 5.5,
loghe: -1.0,
logc: -2.0,
logn: -2.0,
logo: -2.0,
teff: 35000.0.into(),
logg: 5.5.into(),
loghe: (-1.0).into(),
logc: (-2.0).into(),
logn: (-2.0).into(),
logo: (-2.0).into(),
}
.model_name(),
common::models::GridPointStatus::Pending,