c160718934
- 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
228 lines
13 KiB
Markdown
228 lines
13 KiB
Markdown
---
|
||
change: ts-as-modality
|
||
design-doc: docs/superpowers/specs/2026-06-29-ts-as-modality-design.md
|
||
base-ref: 7a5a1d033fb1ac20af25a17a571b2791694bd923
|
||
---
|
||
|
||
# 时序作为模态的多模态模型 · 实现计划
|
||
|
||
> **对应规格**:`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=256
|
||
- `TSEncoder(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 为准
|
||
|
||
## 首次跑通的"冒烟路径"(先做这个)
|
||
|
||
不等全量数据,先用最小路径确认端到端通:
|
||
1. `gen_synthetic.py --n 200` 产小数据
|
||
2. T2.1-T2.4 单 batch 跑通
|
||
3. `stage1.py --max_steps 50` + `stage2.py --max_steps 50` 冒烟
|
||
4. `run_eval.sh` 在 200 样本上产出报告骨架
|
||
|
||
冒烟通过后再放大到 M1 全量数据 + 完整训练。
|
||
|
||
---
|
||
|
||
> 本计划把规格拆成 22 个任务、5 个带出口验证的里程碑。每个任务有"完成判定"可独立验证。建议从"冒烟路径"开始,确认端到端通后再放大规模。
|