feat(all): seed_step_stab 稳定化种子链与同族种子轮换、ladder/自适应 Teff/丰度轴延拓三级回退与梯级种子入库复用、热启动首末比豁免、workflow 完成翻转回退链阻塞修复、大气收敛权威统计与 M14 回填
收敛攻坚(81/400 失败点根因与实测,见 docs/failed81_cno_seed_popzer_dpsilg_2026_08_18.md): - runner: 新增 seed_step_stab 策略链——同物理族(同 Teff/logg/logHe)CNO 邻居种子 + DPSILG=3.0 λ 算子欠松弛 + POPZER=1E-10 微布居置零(联动 POPZR2/RADZER 同值 + NITZER=1),针对 20kK He 富大气 He I/II 电离前沿布居极限环 - runner: 三级链内延拓回退(主链与 nl_direct 全败后自动触发): · 固定 ladder 步进——plan_ladder_steps 按归一化间隔选轴,Δlogg≤0.25/ΔTeff≤2.5kK,≤4 步 · 自适应 Teff 延拓——步长 1250K 起、成功 ×1.5 恢复、失败二分至 25K 折叠墙,≤48 步 · C/N 丰度轴延拓——高温域(Teff>30kK)专用,严格同族种子沿 C(优先)/N 轴 0.2 dex 起步 延拓阶段 NITER 下限提至 300(慢收敛 waypoint 迭代饥饿误判修复) - runner: 稳定化多档回退(DPSILG/POPZER 三档互补,联合回收 41/65); 域门控 Teff≤30kK——高温高金属域实测旋钮致散(17 拍爆至 4e16),域外跳过 - 梯级种子持久化: 收敛中间模型登记 ladder_seeds 随上报落 server seeds 表 (任务失败也上传,合成名 _ladder 与真实网格点零冲突),簇内相邻失败点自动复用 调度与执行: - scheduler: 策略解析新增 seed_step_stab 臂——find_exact_family_seed_from_db 严格 同物理族判定(不做 global 退化,防 ladder 中间种子 ΔTeff≤5000K 误命中), 排除本点历史已用种子实现重试轮换;链在 stab 耗尽时轮换未试过同族邻居重派 - executor: seed_step_stab 补种子下载(漏列曾致 78 任务假失败,seed_nc 无 fort.8 崩溃); Teff>30kK 域外自动降级普通种子链 收敛判据: - conv_check: 热启动豁免——首拍 max_relc<1(种子已近解)时首末比 1e3 判据数学上 不可达,豁免后交五重物理硬门槛裁决(修复 nl_ladder 0.038→6e-4 物理全过被误杀); 冷启动仍受判据门控 workflow 生命周期: - workflows/tasks: 完成 flip 增加「未消费回退链」阻塞子句——failed 点策略链未耗尽 或链尾 seed_step_stab 尚有未试过同族种子时不得置 completed(修复最后活跃点 cold 失败上报抢先 flip、still_running 守卫拦截后续策略永不派发);按 failed_stage 归因 (synspec 失败行只看 synspec 链,防 stale 审计副本永久卡死)+ json_valid 脏行防护 统计与前端: - 统计新增权威口径 tlusty_converged(不按策略拆)与 seed_step_stab_converged 分项, 前端详情页色带/概览卡消费权威总数并新增稳定化青色段(修复 stab 收敛点漏计, 生产 9137/9216 差额);M14 迁移回填历史 completed 点的 tlusty_status 文档: - 新增 failed81 POPZER/DPSILG 制胜配方根因分析、Windows 节点经跳板 RDP 运维手册; failed400 增补 ladder 生产化实现与第二轮 121 残点实测矩阵
This commit is contained in:
@@ -20,7 +20,7 @@ pub async fn execute_task(
|
||||
result_dir: &Path,
|
||||
task: &TaskSpec,
|
||||
shutdown: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
|
||||
) -> Result<(ModelSummary, Option<Vec<u8>>)> {
|
||||
) -> Result<(ModelSummary, Option<Vec<u8>>, Vec<(String, Vec<u8>)>)> {
|
||||
info!(
|
||||
"开始执行计算任务 {} (网格点: {})",
|
||||
task.task_id, task.point_name
|
||||
@@ -68,7 +68,11 @@ pub async fn execute_task(
|
||||
// 全新 slot 内不可能已有目标大气;重试任务的 slot 亦全新(task_id 唯一)。
|
||||
let tlusty_enabled = task.tlusty_config.enabled;
|
||||
let current_strategy = task.tlusty_config.current_strategy("cold_run");
|
||||
let needs_seed_download = (tlusty_enabled && current_strategy == "seed_step")
|
||||
// seed_step_stab(2026-08-18 稳定化种子步进)同样是种子热启动链,必须下载种子;
|
||||
// 2026-08-19 生产事故:漏列该策略导致种子未下载,seed_nc 无 fort.8 直接
|
||||
// EOF 崩溃(rc=2),78 个任务全部假失败。
|
||||
let needs_seed_download = (tlusty_enabled
|
||||
&& matches!(current_strategy, "seed_step" | "seed_step_stab"))
|
||||
|| (!tlusty_enabled && task.synspec_config.enabled);
|
||||
|
||||
if needs_seed_download {
|
||||
@@ -223,6 +227,7 @@ pub async fn execute_task(
|
||||
&task.tlusty_chain_params,
|
||||
&task.seed_chain_params,
|
||||
&task.task_id.to_string(),
|
||||
task.params.teff.value(),
|
||||
);
|
||||
// TLUSTY 输入文件全局参数(NFREAD/ions 表/nst extra_keys 等)。
|
||||
// None → runner 用代码内硬编码默认(向后兼容)。
|
||||
@@ -239,6 +244,12 @@ pub async fn execute_task(
|
||||
}
|
||||
}
|
||||
});
|
||||
// 种子参数(ladder 步进规划需要种子与目标的参数差):从权威 seed_point_name
|
||||
// 解析。解析失败(命名不符)→ None → ladder 回退不触发,安全降级。
|
||||
let seed_params = task
|
||||
.seed_point_name
|
||||
.as_deref()
|
||||
.and_then(common::models::GridPointParams::parse_point_name);
|
||||
let summary = runner
|
||||
.run_model_with_timeout(
|
||||
&task.params,
|
||||
@@ -248,6 +259,7 @@ pub async fn execute_task(
|
||||
current_strategy,
|
||||
Some(chain),
|
||||
seed_atmos_path.as_deref(),
|
||||
seed_params.as_ref(),
|
||||
synspec_cfg.as_ref(),
|
||||
// 阶段独立配置开关(见 docs/task_engine_decoupling_design.md §5)。
|
||||
task.tlusty_config.enabled,
|
||||
@@ -303,7 +315,30 @@ pub async fn execute_task(
|
||||
slot_work_dir.display()
|
||||
);
|
||||
|
||||
Ok((summary, seed_bytes))
|
||||
// ladder 中间梯级种子字节:即便最终失败也上报(簇内相邻失败点可复用已收敛梯级)。
|
||||
// 阶段快照命名 <name>.<label>.7(execute_tlusty_stage 写入 model_dir)。
|
||||
let mut ladder_seed_files: Vec<(String, Vec<u8>)> = Vec::new();
|
||||
if !summary.ladder_seeds.is_empty() {
|
||||
let model_sub_dir = slot_work_dir.join(&summary.name);
|
||||
for info in &summary.ladder_seeds {
|
||||
let path = model_sub_dir.join(format!("{}.{}.7", summary.name, info.label));
|
||||
if path.is_file() {
|
||||
if let Ok(bytes) = tokio::fs::read(&path).await {
|
||||
info!(
|
||||
"读取 ladder 中间种子 {}({},{} 字节)",
|
||||
info.point_name,
|
||||
path.display(),
|
||||
bytes.len()
|
||||
);
|
||||
ladder_seed_files.push((info.point_name.clone(), bytes));
|
||||
}
|
||||
} else {
|
||||
warn!("ladder 种子快照缺失: {}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok((summary, seed_bytes, ladder_seed_files))
|
||||
}
|
||||
|
||||
/// 清理任务在 Node 端的沙盒目录
|
||||
@@ -518,6 +553,7 @@ fn resolve_execution_chain(
|
||||
tlusty_chain_params: &Option<serde_json::Value>,
|
||||
seed_chain_params: &Option<serde_json::Value>,
|
||||
task_id: &str,
|
||||
teff: f64,
|
||||
) -> Vec<ChainStep> {
|
||||
if current_strategy == "seed_step" {
|
||||
seed_chain_params
|
||||
@@ -534,6 +570,27 @@ fn resolve_execution_chain(
|
||||
})
|
||||
.filter(|c| !c.is_empty())
|
||||
.unwrap_or_else(common::runner::default_seed_chain)
|
||||
} else if current_strategy == "seed_step_stab" {
|
||||
// 稳定化种子步进:POPZER+DPSILG 稳定化参数即本策略的实质——用户自定义链
|
||||
// (tlusty_chain/seed_chain)不带这些旋钮,热启动会重演发散,故不采纳、
|
||||
// 恒用 default_seed_stab_chain。
|
||||
// 2026-08-21 域门控(60k 攻坚实测,docs/failed81_...md §十一):DPSILG/POPZER
|
||||
// 族旋钮仅在低温 He 富域(Teff≤30kK)有效;高温高金属域(60k/g5.0 o-1 角)
|
||||
// 实测自复现收敛种子加档后 17 拍爆到 4e16——致散。域外改用普通种子链,
|
||||
// 交给 runner 的自适应 Teff 延拓处理。
|
||||
if teff <= 30000.0 {
|
||||
common::runner::default_seed_stab_chain()
|
||||
} else {
|
||||
info!(
|
||||
"任务 {}:Teff={}>30kK 域外,seed_step_stab 降级为普通种子链(稳定化旋钮高温致散)",
|
||||
task_id, teff
|
||||
);
|
||||
seed_chain_params
|
||||
.as_ref()
|
||||
.and_then(|v| serde_json::from_value::<Vec<ChainStep>>(v.clone()).ok())
|
||||
.filter(|c| !c.is_empty())
|
||||
.unwrap_or_else(common::runner::default_seed_chain)
|
||||
}
|
||||
} else {
|
||||
tlusty_chain_params
|
||||
.as_ref()
|
||||
@@ -585,6 +642,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
}
|
||||
}
|
||||
@@ -604,7 +662,7 @@ mod tests {
|
||||
]);
|
||||
|
||||
// seed_chain_params=None → 走 default_seed_chain;tlusty_chain_params 被忽略。
|
||||
let chain = resolve_execution_chain("seed_step", &Some(custom_cold), &None, "t1");
|
||||
let chain = resolve_execution_chain("seed_step", &Some(custom_cold), &None, "t1", 20000.0);
|
||||
assert_eq!(labels(&chain), vec!["seed_nc", "nl"]);
|
||||
// 首步必须是非灰 LTE(ltgray=F),否则会删 fort.8 丢弃种子。
|
||||
assert_eq!(chain[0].ltgray, "F");
|
||||
@@ -618,13 +676,32 @@ mod tests {
|
||||
{"label": "nl", "lte": "F", "ltgray": "F", "ilvlin": 100, "niter": 100, "orelax": 0.5},
|
||||
]);
|
||||
|
||||
let chain = resolve_execution_chain("seed_step", &None, &Some(custom_seed), "t1b");
|
||||
let chain = resolve_execution_chain("seed_step", &None, &Some(custom_seed), "t1b", 20000.0);
|
||||
assert_eq!(labels(&chain), vec!["seed_nc", "nl"]);
|
||||
// 用户配置的 orelax 生效。
|
||||
assert_eq!(chain[0].orelax, Some(0.3));
|
||||
assert_eq!(chain[1].orelax, Some(0.5));
|
||||
}
|
||||
|
||||
/// seed_step_stab 温度域门控(2026-08-21,60k 攻坚):
|
||||
/// Teff≤30kK 用稳定化链(全步带 DPSILG/POPZER);
|
||||
/// 高温域(60k/g5.0 高金属角实测旋钮致散)降级为普通种子链。
|
||||
#[test]
|
||||
fn seed_step_stab_teff_domain_gating() {
|
||||
let cold = resolve_execution_chain("seed_step_stab", &None, &None, "hot1", 20000.0);
|
||||
assert!(
|
||||
cold.iter().all(|s| s.dpsilg.is_some() && s.popzer.is_some()),
|
||||
"低温域 seed_step_stab 应带稳定化参数"
|
||||
);
|
||||
|
||||
let hot = resolve_execution_chain("seed_step_stab", &None, &None, "hot2", 60000.0);
|
||||
assert!(
|
||||
hot.iter().all(|s| s.dpsilg.is_none() && s.popzer.is_none()),
|
||||
"高温域 seed_step_stab 应降级为普通种子链(不带稳定化旋钮)"
|
||||
);
|
||||
assert_eq!(labels(&hot), vec!["seed_nc", "nl"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cold_run_uses_custom_chain_when_provided() {
|
||||
let custom = serde_json::json!([
|
||||
@@ -633,13 +710,13 @@ mod tests {
|
||||
{"label": "nl", "lte": "F", "ltgray": "F", "ilvlin": 100, "niter": 100},
|
||||
]);
|
||||
|
||||
let chain = resolve_execution_chain("cold_run", &Some(custom), &None, "t2");
|
||||
let chain = resolve_execution_chain("cold_run", &Some(custom), &None, "t2", 20000.0);
|
||||
assert_eq!(labels(&chain), vec!["lte", "nc", "nl"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cold_run_falls_back_to_default_when_no_custom_chain() {
|
||||
let chain = resolve_execution_chain("cold_run", &None, &None, "t3");
|
||||
let chain = resolve_execution_chain("cold_run", &None, &None, "t3", 20000.0);
|
||||
assert_eq!(labels(&chain), vec!["lte", "nc", "nl"]);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,12 +50,22 @@ fn derive_report_status(s: &ModelSummary) -> TaskStatus {
|
||||
}
|
||||
}
|
||||
|
||||
/// executor 返回值:(summary, 最终种子字节, ladder 中间梯级种子字节列表)。
|
||||
pub type ExecOutcome = Result<
|
||||
(
|
||||
ModelSummary,
|
||||
Option<Vec<u8>>,
|
||||
Vec<(String, Vec<u8>)>,
|
||||
),
|
||||
String,
|
||||
>;
|
||||
|
||||
pub async fn report_result(
|
||||
client: &Client,
|
||||
server_url: &str,
|
||||
node_id: &str,
|
||||
task: &TaskSpec,
|
||||
exec_res: Result<(ModelSummary, Option<Vec<u8>>), String>,
|
||||
exec_res: ExecOutcome,
|
||||
) -> Result<()> {
|
||||
let report_url = format!("{}/api/task/report", server_url);
|
||||
|
||||
@@ -69,8 +79,9 @@ pub async fn report_result(
|
||||
summary_json,
|
||||
seed_bytes,
|
||||
failed_stage,
|
||||
ladder_seed_files,
|
||||
) = match exec_res {
|
||||
Ok((s, s_bytes)) => (
|
||||
Ok((s, s_bytes, ladder_files)) => (
|
||||
derive_report_status(&s),
|
||||
s.result_valid,
|
||||
s.final_max_relc,
|
||||
@@ -80,6 +91,7 @@ pub async fn report_result(
|
||||
serde_json::to_string(&s).unwrap_or_default(),
|
||||
s_bytes,
|
||||
infer_failed_stage(task, &s),
|
||||
ladder_files,
|
||||
),
|
||||
Err(e) => (
|
||||
TaskStatus::Failed,
|
||||
@@ -91,6 +103,7 @@ pub async fn report_result(
|
||||
serde_json::json!({"error": e}).to_string(),
|
||||
None,
|
||||
None,
|
||||
Vec::new(),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -128,6 +141,15 @@ pub async fn report_result(
|
||||
}
|
||||
}
|
||||
|
||||
// ladder 中间梯级种子:即便最终任务失败也上传(executor 只收集收敛+无 NaN
|
||||
// 的中间模型),server 端落 seeds 表供相邻失败点复用。
|
||||
for (seed_name, bytes) in &ladder_seed_files {
|
||||
let part = Part::bytes(bytes.clone())
|
||||
.file_name(format!("{}.7", seed_name))
|
||||
.mime_str("application/octet-stream")?;
|
||||
form = form.part("ladder_seed", part);
|
||||
}
|
||||
|
||||
match client.post(&report_url).multipart(form).send().await {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
info!(
|
||||
@@ -239,6 +261,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,7 +542,7 @@ impl NodeWorker {
|
||||
// 严重失败(runner 抛 Err)时 summary_opt=None,此时回退到 task.point_name
|
||||
// (二者均源自 params.model_name())作为归档目录名,确保失败任务的
|
||||
// 排错日志也能落盘而非随沙盒删除丢失。
|
||||
let summary_opt = res.as_ref().ok().map(|(s, _)| s.clone());
|
||||
let summary_opt = res.as_ref().ok().map(|(s, _, _)| s.clone());
|
||||
let result_name = summary_opt
|
||||
.as_ref()
|
||||
.map(|s| s.name.clone())
|
||||
|
||||
Reference in New Issue
Block a user