- OpenSpec change: proposal/design/tasks + 3 delta specs (ts-synthesis, ts-multimodal-model, ts-evaluation; 21 requirements) - comet state initialized (phase=build), plan recorded - adopt existing reviewed design doc as Design Doc (+ comet frontmatter) - existing implementation plan (+ comet frontmatter) - workspace .gitignore
13 KiB
change, design-doc, base-ref
| change | design-doc | base-ref |
|---|---|---|
| ts-as-modality | docs/superpowers/specs/2026-06-29-ts-as-modality-design.md | 7a5a1d033f |
时序作为模态的多模态模型 · 实现计划
对应规格:
docs/superpowers/specs/2026-06-29-ts-as-modality-design.md日期:2026-06-29 目标:把规格拆成有序、可验证的实现任务,按里程碑 M1→M5 推进 原则:每个任务都有明确的"完成判定"(可跑一条命令/测试验证);任务间依赖清晰;先建最小可跑通骨架,再迭代填充
阶段总览
M1 数据管线 ──► M2 模型可跑通 ──► M3 阶段①对齐训练 ──► M4 阶段②SFT ──► M5 完整评测
(data) (model) (train stage1) (train stage2) (eval)
└─ 合成+真实 └─ TS Encoder └─ 50万 2-3ep └─ 10万 3ep └─ VUS-PR/问答/基线
每里程碑结束有"出口验证",通过才进下一个。首版先做"小规模冒烟"(数据 1k、训练几十步)确认管线通,再放大到全量。
M1 · 数据管线
目标:能产出合法 JSONL 样本(合成)+ 能加载真实 benchmark 窗口。
T1.1 项目脚手架
- 创建
pyproject.toml:依赖torch,transformers,peft,accelerate,datasets,numpy,tqdm,pyyaml,pytest - 创建
src/tsmm/__init__.py、configs/、scripts/、tests/目录骨架 - 建
data/、checkpoints/并加.gitignore - 完成判定:
python -c "import tsmm"不报错;pytest tests/能跑(哪怕空)
T1.2 属性词表与采样 data/attributes.py
- 定义变量名词表(CPU/内存/流量/延迟/温度/请求量…)、单位、采样率档位
sample_attributes(n_channels)→ 返回[{"name","unit","freq"}, ...]- 完成判定:单元测试
tests/test_attributes.py— 采样结果字段齐全、数量正确、可复现(设 seed)
T1.3 成分模型与时序合成 data/synthesis.py
- 实现
generate_series(T, C, seed):趋势+周期+基线+噪声(高斯/Student-t)叠加 - 实现
inject_anomaly(series, types):尖刺/水平偏移/方差膨胀/缺失段/缓慢漂移,返回(series, labels),labels 为逐点 0/1 + 段元数据 - 实现
couple_event(series, t, kind):在 t 注入阶跃并返回事件文本描述 - 实现
add_missing(series, rate):随机丢弃 5-10% 点(用 NaN 占位) - 完成判定:
tests/test_synthesis.py— 给定 seed 输出确定;异常段位置与 labels 一致;事件 t 处确有阶跃;可视化 1 条样本人工确认形态合理
T1.4 指令与回答生成 data/instruct.py
- 定义 6 类指令模板(描述/异常/根因/预测/比较/事件关联)
build_instruction(series_meta, labels, events, category)→(instruction, answer),answer 含 JSON 区间(异常类)或数值(统计类)或文本(解释类)- Evol-Instruct 演化:用模板组合 + 随机改写生成多样化问法
- 完成判定:
tests/test_instruct.py— 每类至少产 1 条,answer 的 JSON 可被json.loads解析;异常类 answer 段与 labels 吻合
T1.5 离线生成脚本 scripts/gen_synthetic.py
- 多进程跑 T1.2-T1.4,产出
data/align.jsonl(50万)+data/sft.jsonl(10万,Evol 演化)+data/eval_synth.jsonl(2k held-out,固定 seed) - 支持
--n、--out、--seed、--workers参数;带 tqdm 进度 - 完成判定:
python scripts/gen_synthetic.py --n 1000跑通;抽样 5 条人工可视检 schema 合规(含 series/attributes/timestamps/events/instruction/answer/labels)
T1.6 真实 benchmark 加载 data/real_bench.py
- 加载 SMD/MSL/SMAP/SWaT/PSM(先用本地缓存或公开下载脚本;若无现成包,至少实现 SWaT/MSL 两个)
load_windows(name, T=512, stride=256)→ 滑窗 + 归一化 + 异常标签- 用 LLM/模板把异常段转成问答对(评测用,不进训练),存
data/eval_real.jsonl - 完成判定:能加载至少 2 个数据集;窗口形状
[N, T, C];标签形状[N, T];问答对非空
M1 出口验证:
gen_synthetic.py --n 1000产出可视检合法的 JSONL;real_bench.py能加载 ≥2 数据集。人工抽看 5 条合成样本确认成分/异常/事件正确。
M2 · 模型可跑通
目标:TS Encoder + Projector + 多模态拼接能在 3060 上前向+反向不 OOM。
T2.1 TS Encoder model/ts_encoder.py
Patchify(patch=8, stride=4):通道独立切 patch,线性嵌入到 d=256TSEncoder(d=256, layers=2, heads=4):2 层 Transformer + 可学习位置编码- 前向:
[B, T, C]→[B, n_patches, 256] - 完成判定:
tests/test_ts_encoder.py— 随机输入[2, 512, 5]输出[2, 128, 256];参数量 ≈4M
T2.2 Projector model/projector.py
Projector(256 → 896):Linear + LayerNorm- 完成判定:输出
[B, 128, 896]对齐 Qwen2.5-0.5B hidden
T2.3 多模态拼接 model/multimodal.py
- 用 Qwen2.5-0.5B tokenizer 把
[属性][时间戳][事件][问题]文本 tokenize - 把 TS token([B,128,896])作为 inputs_embeds 插入对应位置:
[属性][时间戳][TS tok][事件][问题] - 统一构造
inputs_embeds(文本走get_input_embeddings(),TS 走 Projector 输出)+attention_mask+labels(仅回答段非 -100) - 完成判定:
tests/test_multimodal.py— 拼接后 seq_len ≤1024;mask/labels 形状对;回答段 label 非 -100
T2.4 训练/推理封装 model/wrapper.py
MultimodalTSModel:组合 TS Encoder + Projector + LLM + LoRA 挂载开关forward(batch)返回 loss;generate(batch)返回文本- 支持
freeze_llm(阶段①)和enable_lora(r=16)(阶段②)两种模式 - 完成判定:单 batch
forward在 3060 不 OOM;generate能出文本(哪怕乱码)
T2.5 Collator data/collator.py
- 把 JSONL 样本 batch 化为
(series, attributes, timestamps, events, instruction, answer)→ 张量 - 处理变长 C(padding + mask)、变长文本(padding + attention_mask)
- 完成判定:
tests/test_collator.py— batch=4 输出张量形状一致、无 NaN
M2 出口验证:单 batch 前向+反向在 3060 跑通,loss 是有限值且下降趋势;阶段①模式峰值显存 ~5GB。
M3 · 阶段① 对齐训练
目标:50万条 2-3 epoch,TS token 不被忽略。
T3.1 损失函数 train/losses.py
lm_loss(logits, labels):仅回答段计 LM loss(labels mask)contrastive_loss(model, batch):扰动时序(加噪/平移)→ 回答 embedding 应变化(InfoNCE 或扰动一致性)- 总 loss =
lm_loss + λ * contrastive_loss(λ 可配,默认 0.1) - 完成判定:
tests/test_losses.py— loss 标量、可反传、对比损失对扰动敏感
T3.2 阶段① 训练脚本 train/stage1.py + scripts/train_stage1.sh
- 加载
data/align.jsonl(datasets 流式) freeze_llm=True,仅训 TS Encoder + Projector(AdamW, lr=1e-4)- 配置:bs=8, grad_accum=4, ctx=512, BF16, 梯度检查点
- 每 2000 step 存 ckpt 到
checkpoints/stage1/,TensorBoard 记录 - 完成判定:
--max_steps 100冒烟跑通不 OOM;loss 下降;峰值显存 ≤6GB
T3.3 TS-token 有效性检验
- 训练后取 held-out 样本:原时序 vs 扰动时序,对比模型回答是否变化
- "必看时序"样本:问具体数值/段位置,看模型能否答对
- 完成判定:扰动后回答变化率 >50%;"必看时序"样本答对率显著高于纯 LLM 基线
M3 出口验证:阶段① ckpt 产出;扰动检验通过;显存 ≤6GB。失败则回查对比损失权重/数据质量。
M4 · 阶段② SFT
目标:10万条 Evol-Instruct 3 epoch + LoRA,6 类任务遵循指令。
T4.1 阶段② 训练脚本 train/stage2.py + scripts/train_stage2.sh
- 加载阶段① ckpt(TS Encoder + Projector)
enable_lora(r=16)on q/v_proj,LLM 其余冻结- 加载
data/sft.jsonl,配置:bs=4, grad_accum=8, ctx=1024, BF16, 梯度检查点 - 每 2000 step 存 ckpt(含 LoRA 适配器)到
checkpoints/stage2/ - 完成判定:
--max_steps 100冒烟跑通;峰值显存 ≤11GB;loss 下降
T4.2 指令遵循抽检
- 6 类任务各取 10 条 held-out,人工/规则看回答是否遵循格式与语义
- 异常类:JSON 可解析、段位置合理
- 解释类:回答切题、引用事件
- 完成判定:6 类任务回答可用率 >70%;JSON 解析成功率 >80%
M4 出口验证:阶段② ckpt 产出;6 类任务抽检达标;显存 ≤11GB。若 OOM 按风险对策降批量/开 CPU offload。
M5 · 完整评测
目标:产出 VUS-PR/Aff-F1/问答分 + 4 类基线对比。
T5.1 回答解析 eval/parse_answer.py
- 从模型文本回答中提取 JSON 异常段 → 逐点分数
[T](段内 1.0,段外 0.0) - 解析失败 → 全 0 + 记录失败标记
- 完成判定:
tests/test_parse.py— 多种回答格式(含噪声/嵌套)能解析;失败有兜底
T5.2 异常检测指标 eval/ts_metrics.py
- 用
tsb_uad算 VUS-PR(主);自实现 Affiliation-F1(λ=0.1/0.5/1.0)、AUC-PR、Point-F1(无PA) - PA-F1 单独算作对照
- 完成判定:
tests/test_ts_metrics.py— 对已知段能算出合理值;trivial 基线(全报异常)VUS-PR 不虚高
T5.3 问答评测 eval/qa_judge.py
- 规则解析轨:JSON/数值容差比对,产出准确率
- LLM-judge 轨:调 GPT-4o 对 (问题,时序摘要,参考答案,模型答案) 打 0-5 分(可换开源 judge 做 sanity)
- 不一致样本导出供人工抽检
- 完成判定:能对 eval 集产出 6 类任务的 {规则准确率, judge 均分, 解析成功率}
T5.4 对照基线 eval/baselines.py
- 纯 LLM 数值喂文本:时序数值拼字符串喂 Qwen2.5-0.5B,同评测脚本
- Time-LLM 风格重映射:同样 0.5B 骨干(简化复现)
- ChatTS(若可复现则纳入,否则标注未复现)
- Trivial:Random / Constant / 全报异常
- 全部走同一套
parse_answer+ts_metrics,保证同口径 - 完成判定:4 类基线均产出同口径指标表
T5.5 评测脚本与报告 scripts/run_eval.sh
- 跑
eval_synth.jsonl+eval_real.jsonl,产出 markdown 报告reports/eval-YYYYMMDD.md - 报告含:每数据集×每任务的 VUS-PR/Aff-F1/AUC-PR/Point-F1/PA-F1(对照)/问答分/解析成功率/消融/基线对比
- 标注是否用 PA、含 trivial 对照
- 完成判定:一键
run_eval.sh产出完整报告;报告满足规格 §5.4 必报清单
M5 出口验证:报告满足规格 §6.5 成功标准 —— 真实 benchmark VUS-PR 显著优于纯 LLM 基线;时序消融后指标明显下降;问答均分超基线;评测可复现含 trivial。
执行顺序与依赖
T1.1 ─► T1.2 ─► T1.3 ─► T1.4 ─► T1.5 ┐
├─► [M1 出口] ─► T2.1 ─► T2.2 ─► T2.3 ─► T2.4 ─► T2.5 ─► [M2 出口]
T1.6 ────────────────┘ │
▼
T3.1 ─► T3.2 ─► T3.3 ─► [M3 出口]
│
▼
T4.1 ─► T4.2 ─► [M4 出口]
│
▼
T5.1 ─► T5.2 ┐
T5.3 ────────┼─► T5.4 ─► T5.5 ─► [M5 出口]
风险触发时的回退
- M2 OOM:先降 ctx 到 256 / bs 到 2;仍不行退到只验证阶段①模式
- M3 TS token 被忽略:提升对比损失 λ;增加"必看时序"样本占比;检查 Projector 是否饱和
- M4 OOM:降 bs=2/grad_accum=16;开
optim=adamw_8bit+ CPU offload;ctx 降到 768 - M5 合成→真实泛化差:在阶段②掺入
real_bench改写样本重训;报告以真实 benchmark 为准
首次跑通的"冒烟路径"(先做这个)
不等全量数据,先用最小路径确认端到端通:
gen_synthetic.py --n 200产小数据- T2.1-T2.4 单 batch 跑通
stage1.py --max_steps 50+stage2.py --max_steps 50冒烟run_eval.sh在 200 样本上产出报告骨架
冒烟通过后再放大到 M1 全量数据 + 完整训练。
本计划把规格拆成 22 个任务、5 个带出口验证的里程碑。每个任务有"完成判定"可独立验证。建议从"冒烟路径"开始,确认端到端通后再放大规模。