Add shared-AV multiwindow Qwen feature extraction

This commit is contained in:
Xin Wang
2026-09-17 03:00:05 +08:00
parent a9f748d6a6
commit 9bc89b556f
8 changed files with 971 additions and 6 deletions

View File

@@ -0,0 +1,151 @@
# EasyCom:一次共享 AV 提取,多种独立 context cap
状态:实现和 CPU 合成测试已通过;**未跑真实 Qwen GPU smoke、全量提取或训练**。
入口:[extract_easycom_multiwindow.py](../extract_easycom_multiwindow.py);
缓存实现:[streaming_turn_multiwindow.py](../streaming_turn_multiwindow.py)。
既有 [streaming_turn_model_directly.py](../streaming_turn_model_directly.py)
单窗口命令不变,也可直接传入 `--memory-seconds-list`。
## 实验契约
默认导出 2/4/6/8/10/12 s;2 s native block、5 Hz 截止时间、2 FPS 因果视觉网格、
observed partial tail;默认保存 last4 stack `[T,4,D]`。必须使用现有 Thinker replay
要求的 `transformers==4.52.3`,不能用局部 encoder 对 4.57.6 的兼容性替代它。
- 一次 session reader,共享每个截止点的 preprocessing、AV block/tail embeddings。
模型在所选 sessions 间只加载一次,所有缓存在 session 边界清空。
- **每个 cap 有独立 Thinker KV、滚动重建和 readout**。旧历史移出后,用缓存的
encoder embeddings 从空 Thinker KV 重放留下的完整 blocks。
不裁剪长窗口 KV 或 pooled state 来假装短窗口。
- 同一尾块仍随 200 ms 截止点增长并重编码;不是整个视频只编码一次。
六窗口生产 AV preparation 为 `T` 次,不是 `6T` 次;Thinker 成本仍按窗口增加。
`--verify-every` 额外从原媒体独立重放,不计入生产调用计数。
- Cap 是**最大呈现预算**,不是每行精确固定长度的滑窗。稳态 span 为
`cap - 2s + 当前 tail phase`,phase 在 0.2--2.0 s;startup 少于 cap。
例如 cutoff=20.6 s:cap=2/4/8/12 s 分别读取
`[20,20.6)` / `[18,20.6)` / `[14,20.6)` / `[10,20.6)`。
局部 A 都是 `[19.6,20.6)`;0.2 s horizon 都指向 20.8 s。
- Sessions 4--12 训练、1--3 测试。当前测试已多次查看,曲线如实标 exploratory;
不按测试涨点选择窗口、训练 epoch 或 early stopping。
- 同一份 1 s local sidecar 可复用,但其 dense rows、times、label indices、源媒体和
encoder contract 必须一致;加载失败不得 padding/clamp。
- 联合提取 wall time/显存不是单窗口流式效率。后者需单独运行所选 cap,测整个流水线。
## 1. 先跑 Session 4 的 24 s GPU smoke
从仓库根目录执行,路径自行设置,不使用某台机器的固定挂载。
24 s 会触发最大 12 s cap 的滚动重建;4 s smoke 不足以覆盖全部窗口。
```bash
MODEL_ID=/path/to/Qwen2.5-Omni-3B
VIDEO_ROOT=/path/to/easycom/Main/Video_Compressed
TARGET_ROOT=/path/to/EasyCom/target_perframe
MULTI_ROOT=/path/to/omni_features/qwen_multiwindow_2fps_last4_stack
CUDA_VISIBLE_DEVICES=0 python3 -u extract_easycom_multiwindow.py \
--model-id "$MODEL_ID" \
--easycom-video-root "$VIDEO_ROOT" \
--sessions Session_4 \
--memory-seconds-list 2 4 6 8 10 12 \
--end 24 --verify-every 11 \
--flash-attention-2 \
--output-root features/easycom_multiwindow_smoke
```
也可用 `--dataset-root /path/to/easycom` 代替 `--easycom-video-root`。
`--media-only` 只检验媒体/decoder、不加载模型、不写 features。
`--end` 是 bounded smoke,故即便给了 target root 也不附加整段 target;输出标记
`partial_extraction=true`,不能与整段 target/local sidecar 混用。
检查 shape、时间、freshness、预算、commit schedule 和独立 replay:
```bash
python3 - <<'PY'
from pathlib import Path
import torch
from local_turn_features import reference_alignment
root = Path("features/easycom_multiwindow_smoke")
for cap in (2, 4, 6, 8, 10, 12):
p = torch.load(root / f"context_{cap}s/Session_4.pt", map_location="cpu", weights_only=False)
times, indices = reference_alignment(p)
d = p["extraction_diagnostics"]
assert p["states"].ndim == 3 and p["states"].shape[:2] == (120, 4)
assert torch.isfinite(p["states"]).all()
assert torch.equal(times, d["state_source_times"])
assert d["state_age_seconds"].eq(0).all()
assert (d["actual_context_seconds"] <= cap + 1e-6).all()
assert d["payload_commit_mask"].nonzero().flatten().tolist() == list(range(9,120,10))
valid = indices[:, None] + torch.arange(1,11)[None, :] < len(times)
assert int(valid.all(dim=1).sum()) == p["valid_10_horizon_steps"] == 110
checks = [c for c in p["verification"] if c["kind"] == "cache_replay"]
assert len(checks) == 10
print(cap, tuple(p["states"].shape), "shared AV calls=", d["production_processor_calls_shared"],
"max_abs=", max(c["max_abs"] for c in checks),
"min_cosine=", min(c["cosine"] for c in checks))
PY
```
Same-segmentation `cache_replay` 应 exact/effectively exact;显著差异先排查,不开始全量。
若要核对既有单窗口生产路径,再运行相同媒体/参数的 `--memory-seconds 8 --end 24`
到另一目录,逐行比较其 states 与 `context_8s/Session_4.pt`。
CPU 测试已覆盖 >12 s 的滚动重建、可变 KV、从头 replay、future-only 合成输入改动、
跨调用 reset、last4/mean、CLI 单窗口兼容及 head/local 加载成功和失败路径;
这不能替代真实模型/媒体的因果 GPU 审计。
## 2. Smoke 审计通过后,一条命令提取所有 sessions
去掉 `--end`;完整目标长度和原始 20 FPS 视频帧数必须匹配。
默认 sessions 为 1--12,也可以先只提取训练侧。验证关闭以避免额外 reference 开销。
```bash
CUDA_VISIBLE_DEVICES=0 python3 -u extract_easycom_multiwindow.py \
--model-id "$MODEL_ID" \
--easycom-video-root "$VIDEO_ROOT" \
--target-root "$TARGET_ROOT" \
--memory-seconds-list 2 4 6 8 10 12 \
--flash-attention-2 \
--output-root "$MULTI_ROOT"
```
输出结构:每个 `context_2s/` ... `context_12s/` 含独立 `Session_1.pt` ...
`Session_12.pt`。每个 payload 仍是 `mode=open-stream-replay`,保留 model、source、
target、readout 和 sampling provenance,并记录 actual history range、phase boundary
和共享生产计数。`extraction_summary.json` 每完成一个 session 原子更新,记录本次调用的
sessions、输出 shape、cap、运行 commit/dirty 状态、依赖版本及联合耗时。
默认拒绝覆盖任一已有 bank。中断后检查文件;可用 `--sessions` 选择未完成 sessions,
或换新输出目录。**不实现 checkpoint resume**;显式 `--overwrite` 才会重跑/替换。
Summary 只描述本次调用,不能将上次与本次不同配置的输出默认为同一个实验。
联合保存每个文件原子替换,但六个文件不是跨文件事务;异常后仍需核查是否齐全。
## 3. 每种 cap 独立训练 G 或 G+A head
复用 [run_easycom_local_baselines.sh](../run_easycom_local_baselines.sh) 的固定四 epoch
recipe;不要复用 8 s head 到其他 cap 当作训练对照。
`audio` 表示 **G+A**,不是纯 audio-only。`LOCAL_ROOT` 指向已验证的 1 s local sidecar。
```bash
export TARGET_ROOT
export LOCAL_ROOT=/path/to/omni_features/local_qwen_1s
for cap in 2 4 6 8 10 12; do
CUDA_VISIBLE_DEVICES=0 \
FEATURE_ROOT="$MULTI_ROOT/context_${cap}s" \
CHECKPOINT_ROOT="checkpoints/easycom_context_${cap}s_2026-09-17" \
RESULT_ROOT="results/easycom_context_${cap}s_2026-09-17" \
bash run_easycom_local_baselines.sh audio 0 1 2
done
```
要检验 local A 是否缓解 context 敏感性,在相同 roots 下另外运行 `baseline 0 1 2`
(G-only);不要在已生成的 `audio_seedN` 路径重复启动。局部 V 是独立条件追加对照。
现有 driver 训练 4--12 后立即推理 1--3,因此开始整组运行前冻结分析方案。
报告完整预指定六-cap 曲线、dense 三类 AP/mAP、raw wearer endpoint onset/offset AP、
prevalence 和 common valid pairs;semantic ENTRY/YIELD 仍等标注审计。
CPU 回归命令:
```bash
python3 -m unittest test_streaming_multiwindow test_streaming_chunk_ablation \
test_easycom_head_objectives test_local_turn_features test_encoder_only_workflow
```