Upload folder using huggingface_hub
Browse files- .env.example +19 -0
- .gitattributes +1 -0
- .gitignore +18 -0
- README.md +90 -0
- config.json +23 -0
- paper_experiments/README.md +45 -0
- paper_experiments/caches/judge_cache.json +3 -0
- paper_experiments/caches/novelty_holdout.json +1 -0
- paper_experiments/caches/web_base.jsonl +0 -0
- paper_experiments/caches/web_comp.jsonl +0 -0
- paper_experiments/caches/web_exh.jsonl +0 -0
- paper_experiments/caches/web_fs.jsonl +0 -0
- paper_experiments/caches/web_fs_alt.jsonl +0 -0
- paper_experiments/data/gold_holdout.json +1 -0
- paper_experiments/run.py +157 -0
- pyproject.toml +19 -0
- scripts/build_submission.py +178 -0
- scripts/infer_simple.py +98 -0
- scripts/run_inference.py +136 -0
- src/humomni/__init__.py +0 -0
- src/humomni/core/__init__.py +0 -0
- src/humomni/core/context.py +26 -0
- src/humomni/core/emb_cache.py +71 -0
- src/humomni/core/env_util.py +46 -0
- src/humomni/core/judges.py +266 -0
- src/humomni/core/pauc_eval.py +158 -0
- src/humomni/core/streaming_driver.py +70 -0
- src/humomni/core/validate_submission.py +67 -0
- src/humomni/phase1/__init__.py +0 -0
- src/humomni/phase1/dedup.py +31 -0
- src/humomni/phase1/perception_gate.py +27 -0
- src/humomni/phase1/policies.py +16 -0
- src/humomni/phase1/policy_api.py +68 -0
- src/humomni/phase1/vlm_client.py +425 -0
- src/humomni/tuning/__init__.py +0 -0
- src/humomni/tuning/cache.py +137 -0
- src/humomni/tuning/faithful_eval.py +67 -0
- tests/test_causality.py +38 -0
- tests/test_inference_parity.py +60 -0
.env.example
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Config for HumOmni Track 2 — copy to .env and fill the secrets. .env is gitignored.
|
| 2 |
+
# Two providers: OpenRouter (the VLM backbone) and Vertex AI (the Gemini entailment judge).
|
| 3 |
+
|
| 4 |
+
# --- OpenRouter: the VLM backbone (Gemini is NOT on OpenRouter, so not the judge) ---
|
| 5 |
+
OPENROUTER_API_KEY=sk-or-...your-key-here...
|
| 6 |
+
VLM_MODEL=qwen/qwen3.7-plus
|
| 7 |
+
|
| 8 |
+
# --- Vertex AI (express mode, REST): the Gemini 3.1 Flash-Lite judge (entailment dedup + evaluation) ---
|
| 9 |
+
# Auth = a Vertex AI EXPRESS-mode API key (a plain REST generateContent call; no SDK / service account).
|
| 10 |
+
VERTEX_KEY=...your-vertex-express-api-key...
|
| 11 |
+
# Vertex model id (NO `google/` prefix, NO date — judges.py normalizes those).
|
| 12 |
+
JUDGE_MODEL=gemini-3.1-flash-lite
|
| 13 |
+
# Optional tuning (Vertex can be slow): per-call timeout (s), aggregate rate cap, regional host override
|
| 14 |
+
VERTEX_TIMEOUT_S=120
|
| 15 |
+
JUDGE_RPM=280
|
| 16 |
+
# VERTEX_BASE_URL=https://us-central1-aiplatform.googleapis.com/v1
|
| 17 |
+
|
| 18 |
+
# Optional: silences the HF Hub unauthenticated-usage warning
|
| 19 |
+
HF_TOKEN=hf_...your-hf-token-here...
|
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
paper_experiments/caches/judge_cache.json filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Secrets — never commit the real .env (.env.example is the committed template)
|
| 2 |
+
.env
|
| 3 |
+
# Python
|
| 4 |
+
.venv/
|
| 5 |
+
__pycache__/
|
| 6 |
+
*.pyc
|
| 7 |
+
*.egg-info/
|
| 8 |
+
build/
|
| 9 |
+
# Data and regenerable caches (large)
|
| 10 |
+
/data
|
| 11 |
+
data/
|
| 12 |
+
cache/
|
| 13 |
+
# Outputs
|
| 14 |
+
submissions/
|
| 15 |
+
*.jsonl
|
| 16 |
+
*.log
|
| 17 |
+
# The reproduction pack IS tracked despite the rules above (it makes every reported number reproducible offline)
|
| 18 |
+
!paper_experiments/**
|
README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# HumOmni 2026 · Track 2 (ProactivEval) — proactive streaming video QA
|
| 2 |
+
|
| 3 |
+
Watch a video at 2 fps, given one question at t=0, and decide **when** to speak and **what** to
|
| 4 |
+
say — causally (using only frames with timestamp ≤ t). Ranking score = **0.9·PAUC + 0.1·(1−duplicate)**;
|
| 5 |
+
hidden judge = Gemini 3.1 Flash-Lite. Baseline MMDuet2 = 53.3; public leader at the time = 60.21.
|
| 6 |
+
|
| 7 |
+
This repository is the **Phase-1 system**: a causal streaming orchestrator around a hosted VLM
|
| 8 |
+
(`qwen/qwen3.7-plus` via OpenRouter). No training and no local large models — it runs on a single 8 GB GPU.
|
| 9 |
+
|
| 10 |
+
## The method in one paragraph
|
| 11 |
+
The hidden judge is **recall-only** — it asks whether the accumulated response text *covers* the
|
| 12 |
+
reference answer's key points, with **no precision penalty** for extra detail. So the winning strategy is
|
| 13 |
+
**comprehensive coverage**: fire on every frame (gate off), prompt the VLM to describe the scene
|
| 14 |
+
exhaustively and read all on-screen text verbatim, and strip only true repeats with an LLM-entailment
|
| 15 |
+
dedup. The final config queries a small **prompt ensemble** per frame — `v5` (exhaustive + OCR) + `v3`
|
| 16 |
+
(comprehensive) + `v8` (few-shot, teach-by-example) — and merges their drafts. The big lever was the
|
| 17 |
+
**prompt**, not timing or a bigger model.
|
| 18 |
+
|
| 19 |
+
## Result (WEB dev set, faithful Gemini judge, untouched holdout `gold[400:500]`)
|
| 20 |
+
| stage | score | note |
|
| 21 |
+
|---|---|---|
|
| 22 |
+
| baseline (focused prompt + throttle gate) | ~0.61 | the original leaderboard submission |
|
| 23 |
+
| + gate-off (fire every frame) | 0.625 | throttling loses coverage |
|
| 24 |
+
| + comprehensive prompt (`v3`) | 0.733 | recall-only judge ⇒ coverage wins |
|
| 25 |
+
| + exhaustive + OCR (`v5`), recent_k=16 | 0.7761 | best single prompt |
|
| 26 |
+
| + `v5`+`v3` ensemble @ recent_k=32 | 0.7909 | robust 2× config |
|
| 27 |
+
| + `v8` few-shot (`v5`+`v3`+`v8` @ k40) | **0.8018** | **the final submission** (gate off) |
|
| 28 |
+
| + perception gate (novelty ≥ 16, silence 3 s) | 0.7853 | optional lower-cost operating point (−51% calls) |
|
| 29 |
+
|
| 30 |
+
**Caveat:** WEB-dev is the leakage-free dev set, **not** the phase1 test set (which has no ground truth).
|
| 31 |
+
The `v8` gain is partly holdout-overfit — the robust gain over `v5`+`v3` is ~+0.002–0.005. Our PAUC harness
|
| 32 |
+
is verified bit-exact against the official scorer, and the grader is confirmed Gemini 3.1 Flash-Lite.
|
| 33 |
+
|
| 34 |
+
## Project layout
|
| 35 |
+
```
|
| 36 |
+
src/humomni/
|
| 37 |
+
core/ streaming_driver · context · pauc_eval · judges · emb_cache · env_util · validate_submission
|
| 38 |
+
phase1/ vlm_client · dedup · policy_api · policies · perception_gate
|
| 39 |
+
tuning/ cache · faithful_eval
|
| 40 |
+
scripts/ build_submission.py · run_inference.py · infer_simple.py
|
| 41 |
+
tests/ test_causality.py · test_inference_parity.py
|
| 42 |
+
config.json .env.example pyproject.toml
|
| 43 |
+
```
|
| 44 |
+
Run every command **from the repo root** — modules use repo-root-relative `data/` and `cache/` paths.
|
| 45 |
+
|
| 46 |
+
## Setup
|
| 47 |
+
```bash
|
| 48 |
+
python -m venv .venv && .venv\Scripts\activate # Windows (use: source .venv/bin/activate on *nix)
|
| 49 |
+
pip install -e . # installs humomni.* + deps (pyproject.toml)
|
| 50 |
+
cp .env.example .env # then fill in the keys (below)
|
| 51 |
+
```
|
| 52 |
+
`.env` needs `OPENROUTER_API_KEY` (the VLM) and `VERTEX_KEY` (the Gemini judge used for the LLM-entailment
|
| 53 |
+
dedup) — only for regenerating decisions from frames; the offline replay needs no keys. Put the test set at
|
| 54 |
+
`data/phase1/data/<id>/` — frames `0.5.jpg, 1.0.jpg, …` plus `question.json` per sample.
|
| 55 |
+
|
| 56 |
+
## Build the submission — one command
|
| 57 |
+
`config.json` holds the entire method (`"ensemble": ["v5","v3","v8"] @ 40`, `dedup`/`theta`, and the
|
| 58 |
+
perception `"gate"` disabled by default — `use_gate:false` gives the shipped max-score point; enabling it
|
| 59 |
+
at novelty ≥ 16 / silence 3 s selects a lower-cost operating point):
|
| 60 |
+
```bash
|
| 61 |
+
python scripts/build_submission.py --out submissions/tryanderror2.jsonl
|
| 62 |
+
```
|
| 63 |
+
For each ensemble prompt it ensures a per-prompt VLM decision cache — **building it from the test frames
|
| 64 |
+
if missing** (paid: ~1 VLM call per prompt per frame; resumable, skips cached frames) — then replays the
|
| 65 |
+
merge + LLM-entailment dedup and **validates** the output (all 500 ids present, times multiples of 0.5).
|
| 66 |
+
Re-runs are free once the caches exist.
|
| 67 |
+
|
| 68 |
+
The live equivalent produces byte-identical output (paid on every run — use when caches are absent):
|
| 69 |
+
```bash
|
| 70 |
+
python scripts/run_inference.py --policy api --workers 8 --out submissions/tryanderror2.jsonl
|
| 71 |
+
python scripts/infer_simple.py --policy api --out submissions/tryanderror2.jsonl # minimal twin
|
| 72 |
+
```
|
| 73 |
+
Both drive the same `APIPolicy` from `config.json`, so all three commands point at the same method
|
| 74 |
+
(live ≡ replay). Upload is manual: the file must be named exactly your Team ID.
|
| 75 |
+
|
| 76 |
+
## Tests
|
| 77 |
+
```bash
|
| 78 |
+
python tests/test_causality.py # causality assert fires on shuffled / out-of-order frames
|
| 79 |
+
python tests/test_inference_parity.py # run_inference == infer_simple; ensemble multi-emit per frame
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Compliance
|
| 83 |
+
Track 2 permits any open-source or commercial LLM API (organizer clarification, 2026-07-07), with one
|
| 84 |
+
condition: **when generating a response at time t, only frames at time ≤ t may be used as visual input.**
|
| 85 |
+
This system enforces that structurally (below) and tests it.
|
| 86 |
+
|
| 87 |
+
## The one hard rule — causality
|
| 88 |
+
Acting at time *t* uses **only** frames with timestamp ≤ *t*. Enforced structurally in
|
| 89 |
+
`humomni.core.streaming_driver.drive` (`assert t > last_t`) and guarded by `tests/test_causality.py`
|
| 90 |
+
(the shuffled-frame test). Keep it green always.
|
config.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"theta": 0.6,
|
| 3 |
+
"gate": {
|
| 4 |
+
"use_gate": false,
|
| 5 |
+
"novelty": 16,
|
| 6 |
+
"max_silence_s": 3.0,
|
| 7 |
+
"warmup_s": 1.0
|
| 8 |
+
},
|
| 9 |
+
"dedup": {
|
| 10 |
+
"sim_high": 0.97,
|
| 11 |
+
"sim_low": 0.55,
|
| 12 |
+
"recent_k": 16,
|
| 13 |
+
"use_llm": true
|
| 14 |
+
},
|
| 15 |
+
"ensemble": {
|
| 16 |
+
"prompts": [
|
| 17 |
+
"v5",
|
| 18 |
+
"v3",
|
| 19 |
+
"v8"
|
| 20 |
+
],
|
| 21 |
+
"recent_k": 40
|
| 22 |
+
}
|
| 23 |
+
}
|
paper_experiments/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Paper experiments — one command per table
|
| 2 |
+
|
| 3 |
+
Reproduces every number in the report's tables, **deterministically and offline**: the per-frame VLM
|
| 4 |
+
decisions (holdout videos) and all LLM-judge results are shipped as caches, so a rerun makes **zero API
|
| 5 |
+
calls** and needs **no keys**.
|
| 6 |
+
|
| 7 |
+
## Setup (once)
|
| 8 |
+
|
| 9 |
+
```bash
|
| 10 |
+
pip install -e . # from the repo root; first run downloads the MiniLM embedder (~90 MB)
|
| 11 |
+
```
|
| 12 |
+
|
| 13 |
+
## Run
|
| 14 |
+
|
| 15 |
+
```bash
|
| 16 |
+
python paper_experiments/run.py table1 # Table 1 — main progression (score AND cost)
|
| 17 |
+
python paper_experiments/run.py table2 # Table 2 — ensemble ablation (calls per frame)
|
| 18 |
+
python paper_experiments/run.py table3 # Table 3 — dedup ablation
|
| 19 |
+
python paper_experiments/run.py gate # Table 4 — perception-gate cost/score frontier
|
| 20 |
+
python paper_experiments/run.py sensitivity # few-shot example-sensitivity check
|
| 21 |
+
python paper_experiments/run.py all
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
Each line prints `score` (0.9·PAUC + 0.1·(1−dup)), `PAUC`, `dup`, mean emissions per video, and the
|
| 25 |
+
cost: mean **VLM calls per video** (+ an approximate US$ figure at our measured ≈$25 / 17.3k calls).
|
| 26 |
+
Runs in a few minutes on CPU.
|
| 27 |
+
|
| 28 |
+
## What is shipped, and why the numbers are exact
|
| 29 |
+
|
| 30 |
+
| file | content |
|
| 31 |
+
|---|---|
|
| 32 |
+
| `data/gold_holdout.json` | the 100 untouched holdout items (public WEB split, items 400–499) |
|
| 33 |
+
| `caches/web_{base,comp,exh,fs,fs_alt}.jsonl` | per-frame VLM decisions for each prompt (temperature 0), holdout videos only |
|
| 34 |
+
| `caches/judge_cache.json` | every LLM-judge / entailment result, keyed by input hash |
|
| 35 |
+
|
| 36 |
+
`run.py` replays the cached decisions through the *same* merge + entailment-dedup code the live system
|
| 37 |
+
runs (`humomni.phase1.dedup.EntailmentGuard`), then scores with the official-protocol harness
|
| 38 |
+
(`humomni.tuning.faithful_eval`). Since the VLM decisions, judge results, and dedup logic are all
|
| 39 |
+
deterministic replays, the printed numbers match the report exactly.
|
| 40 |
+
|
| 41 |
+
To regenerate the VLM caches from scratch instead (paid; OpenRouter + Vertex keys in `.env`):
|
| 42 |
+
`python -m humomni.tuning.cache --func vlm` with the corresponding prompt — see the repo README.
|
| 43 |
+
|
| 44 |
+
Prompt naming: EXH / COMP / FS in the report = `v5` / `v3` / `v8` in the code; `fs_alt` = `v10`
|
| 45 |
+
(the example-swapped variant used in the sensitivity check).
|
paper_experiments/caches/judge_cache.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:05bb3868ccce66c7b0a7776071ac4853ef2642f5b3a406fb529dd35c636051ef
|
| 3 |
+
size 24284720
|
paper_experiments/caches/novelty_holdout.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"09RFL5SKVjM.22.mp4": {"0.5": 255.0, "1.0": 20.33159637451172, "1.5": 17.13237762451172, "2.0": 8.304253578186035, "2.5": 24.09418487548828, "3.0": 13.19921875, "3.5": 2.760850667953491, "4.0": 2.5807292461395264, "4.5": 2.493055582046509, "5.0": 115.04513549804688, "5.5": 28.60112762451172, "6.0": 9.843316078186035, "6.5": 28.15407943725586, "7.0": 48.97005081176758, "7.5": 33.58984375, "8.0": 34.88584899902344, "8.5": 36.06944274902344, "9.0": 35.95790100097656, "9.5": 37.94270706176758, "10.0": 44.32334899902344, "10.5": 39.87977600097656, "11.0": 20.90234375, "11.5": 6.950520992279053, "12.0": 6.90928840637207, "12.5": 8.03428840637207, "13.0": 8.338541984558105}, "0IKKzYIK-AY.6.mp4": {"0.5": 255.0, "1.0": 31.18619728088379, "1.5": 67.21050262451172, "2.0": 1.4904513359069824, "2.5": 1.1818575859069824, "3.0": 1.328125, "3.5": 1.2864583730697632, "4.0": 80.57595825195312, "4.5": 40.58376693725586, "5.0": 39.92491149902344, "5.5": 40.35459899902344, "6.0": 43.63498306274414, "6.5": 45.05512237548828, "7.0": 74.87456512451172, "7.5": 33.19921875, "8.0": 26.33420181274414, "8.5": 31.59809112548828, "9.0": 59.23263931274414, "9.5": 38.55078125, "10.0": 61.86892318725586, "10.5": 10.82204818725586, "11.0": 7.750433921813965}, "0KoYCn3MQd0.9.mp4": {"0.5": 255.0, "1.0": 9.47482681274414, "1.5": 8.462239265441895, "2.0": 6.943142414093018, "2.5": 7.166666507720947, "3.0": 8.52647590637207, "3.5": 8.50607681274414, "4.0": 8.66319465637207, "4.5": 6.813802242279053, "5.0": 50.87022399902344, "5.5": 17.16275978088379, "6.0": 15.55295181274414, "6.5": 14.86892318725586, "7.0": 24.54817771911621, "7.5": 28.35460090637207, "8.0": 23.32204818725586, "8.5": 48.45833206176758, "9.0": 5.842447757720947, "9.5": 11.71961784362793, "10.0": 12.42404556274414, "10.5": 11.69140625, "11.0": 9.88194465637207, "11.5": 27.17317771911621, "12.0": 4.001302242279053, "12.5": 3.24609375, "13.0": 2.250433921813965, "13.5": 1.5512152910232544, "14.0": 52.96006774902344, "14.5": 28.20138931274414, "15.0": 30.06900978088379, "15.5": 26.30989646911621, "16.0": 27.10112762451172, "16.5": 47.13194274902344, "17.0": 2.0130207538604736, "17.5": 6.048177242279053, "18.0": 8.12109375, "18.5": 3.9357638359069824, "19.0": 35.83506774902344, "19.5": 12.04513931274414, "20.0": 10.99609375, "20.5": 10.226996421813965, "21.0": 9.51258659362793}, "0YSLASX3Iq8.3.mp4": {"0.5": 255.0, "1.0": 13.726996421813965, "1.5": 20.59418487548828, "2.0": 36.46527862548828, "2.5": 28.25390625, "3.0": 14.9765625, "3.5": 21.18272590637207, "4.0": 16.74001693725586, "4.5": 25.85416603088379, "5.0": 26.15277862548828, "5.5": 10.937933921813965, "6.0": 15.945746421813965, "6.5": 10.851996421813965, "7.0": 14.137152671813965, "7.5": 14.27951431274414, "8.0": 26.92491340637207, "8.5": 20.48220443725586, "9.0": 20.97612762451172, "9.5": 6.976128578186035, "10.0": 21.72309112548828, "10.5": 24.42751693725586, "11.0": 24.03949737548828, "11.5": 11.44227409362793, "12.0": 10.72439193725586, "12.5": 11.773003578186035, "13.0": 26.67144012451172, "13.5": 21.046875, "14.0": 10.750433921813965, "14.5": 6.41102409362793, "15.0": 6.671875, "15.5": 13.13454818725586, "16.0": 12.226996421813965, "16.5": 65.49478912353516, "17.0": 27.65277862548828, "17.5": 10.71310806274414, "18.0": 21.07725715637207, "18.5": 10.33116340637207, "19.0": 18.03732681274414, "19.5": 25.07725715637207, "20.0": 15.11631965637207, "20.5": 26.51302146911621, "21.0": 74.42274475097656, "21.5": 9.166666984558105, "22.0": 16.64887237548828, "22.5": 13.812066078186035, "23.0": 23.19921875, "23.5": 28.88107681274414, "24.0": 15.370659828186035, "24.5": 13.680989265441895, "25.0": 17.75911521911621, "25.5": 17.49045181274414, "26.0": 15.234375, "26.5": 13.46006965637207, "27.0": 24.81857681274414, "27.5": 13.005208015441895, "28.0": 8.023003578186035, "28.5": 3.7122395038604736, "29.0": 11.558159828186035, "29.5": 31.16970443725586, "30.0": 15.082465171813965, "30.5": 5.002170085906982, "31.0": 8.414496421813965, "31.5": 21.95225715637207, "32.0": 89.68966674804688, "32.5": 8.171441078186035, "33.0": 10.457465171813965, "33.5": 12.078125, "34.0": 10.99435806274414}, "0fbebx_l2Xw.4.mp4": {"0.5": 255.0, "1.0": 0.02690972201526165, "1.5": 1.9895833730697632, "2.0": 0.0928819477558136, "2.5": 0.0451388880610466, "3.0": 0.02604166604578495, "3.5": 8.627604484558105, "4.0": 0.009548611007630825, "4.5": 1.5177950859069824, "5.0": 0.0164930559694767, "5.5": 0.0026041667442768812, "6.0": 0.4830729067325592, "6.5": 7.781683921813965, "7.0": 0.010416666977107525, "7.5": 0.0047743055038154125, "8.0": 3.757378578186035, "8.5": 0.02864583395421505, "9.0": 0.234375, "9.5": 0.002170138992369175, "10.0": 0.4722222089767456, "10.5": 0.02690972201526165, "11.0": 0.0065104165114462376, "11.5": 0.005642361007630825, "12.0": 0.0017361111240461469, "12.5": 0.0, "13.0": 0.005642361007630825, "13.5": 0.009114583022892475, "14.0": 0.0065104165114462376, "14.5": 31.75868034362793, "15.0": 4.2265625, "15.5": 4.767361164093018, "16.0": 5.82725715637207, "16.5": 21.38020896911621, "17.0": 2.305555582046509, "17.5": 21.73567771911621, "18.0": 2.868055582046509, "18.5": 1.8441840410232544, "19.0": 32.78559112548828, "19.5": 0.1818576455116272, "20.0": 0.1488715261220932, "20.5": 35.7265625, "21.0": 6.101996421813965, "21.5": 5.26258659362793, "22.0": 6.866753578186035, "22.5": 12.76171875, "23.0": 15.006510734558105, "23.5": 13.987847328186035, "24.0": 11.94357681274414, "24.5": 9.78515625, "25.0": 8.555989265441895, "25.5": 2.559027671813965, "26.0": 0.3980034589767456, "26.5": 3.3376736640930176, "27.0": 19.39756965637207, "27.5": 25.13454818725586, "28.0": 17.16579818725586, "28.5": 13.921441078186035, "29.0": 9.089409828186035, "29.5": 7.028645992279053}, "0lbseqh9fsg.35.mp4": {"0.5": 255.0, "1.0": 14.947916984558105, "1.5": 15.18055534362793, "2.0": 15.676215171813965, "2.5": 16.24913215637207, "3.0": 16.18055534362793, "3.5": 16.26475715637207, "4.0": 16.05425262451172, "4.5": 45.34461975097656, "5.0": 4.90147590637207, "5.5": 4.87803840637207, "6.0": 4.813802242279053, "6.5": 5.02256965637207, "7.0": 4.90147590637207, "7.5": 5.092447757720947, "8.0": 5.263454914093018, "8.5": 5.05381965637207, "9.0": 40.58029556274414, "9.5": 7.583767414093018, "10.0": 7.952691078186035, "10.5": 7.87196159362793, "11.0": 8.098958015441895, "11.5": 7.875433921813965, "12.0": 79.27603912353516, "12.5": 1.265625, "13.0": 1.2769097089767456}, "0pHWbrdttl8.15.mp4": {"0.5": 255.0, "1.0": 14.56727409362793, "1.5": 9.471354484558105, "2.0": 5.86631965637207, "2.5": 2.863715171813965, "3.0": 4.611979007720947, "3.5": 49.32465362548828, "4.0": 50.83203125, "4.5": 15.70920181274414, "5.0": 14.12196159362793, "5.5": 55.78081512451172, "6.0": 0.1284722238779068, "6.5": 0.1449652761220932, "7.0": 0.1597222238779068, "7.5": 0.1801215261220932, "8.0": 0.1241319477558136, "8.5": 0.1423611044883728, "9.0": 0.3923611044883728, "9.5": 18.85243034362793, "10.0": 8.61501693725586, "10.5": 20.30989646911621, "11.0": 0.6809895634651184, "11.5": 1.1605902910232544, "12.0": 0.7986111044883728, "12.5": 0.9292534589767456, "13.0": 2.8068575859069824, "13.5": 5.650173664093018, "14.0": 5.152777671813965, "14.5": 5.17881965637207, "15.0": 2.182725667953491, "15.5": 1.8151041269302368, "16.0": 0.9561631679534912, "16.5": 0.4717881977558136, "17.0": 0.6857638955116272, "17.5": 0.8637152910232544, "18.0": 1.0755208730697632, "18.5": 1.0125868320465088, "19.0": 0.8463541865348816, "19.5": 3.6419270038604736, "20.0": 32.78602600097656, "20.5": 16.63585090637207, "21.0": 6.017795085906982, "21.5": 12.326823234558105, "22.0": 13.75868034362793, "22.5": 82.69357299804688}, "0tdkFPQri14.15.mp4": {"0.5": 255.0, "1.0": 5.466145992279053, "1.5": 5.421875, "2.0": 5.50868034362793, "2.5": 51.87109375, "3.0": 12.645833015441895, "3.5": 13.59375, "4.0": 13.523871421813965, "4.5": 13.86154556274414, "5.0": 13.898871421813965, "5.5": 14.27517318725586, "6.0": 16.51692771911621, "6.5": 18.90277862548828, "7.0": 60.35069274902344, "7.5": 2.390191078186035, "8.0": 29.25434112548828, "8.5": 2.946180582046509, "9.0": 3.297308921813965, "9.5": 2.8771700859069824, "10.0": 76.8203125, "10.5": 44.01909637451172, "11.0": 50.73307418823242, "11.5": 16.66362762451172, "12.0": 7.023871421813965, "12.5": 43.39453125, "13.0": 3.094618082046509, "13.5": 3.085069417953491, "14.0": 36.42881774902344, "14.5": 3.635850667953491, "15.0": 177.77734375, "15.5": 77.00650787353516, "16.0": 117.47786712646484, "16.5": 41.65234375, "17.0": 17.54817771911621, "17.5": 61.79123306274414}, "0vklWHzRAos.9.mp4": {"0.5": 255.0, "1.0": 0.6193576455116272, "1.5": 0.61328125, "2.0": 0.3489583432674408, "2.5": 0.4001736044883728, "3.0": 0.4296875, "3.5": 0.1818576455116272, "4.0": 0.2309027761220932, "4.5": 0.1961805522441864, "5.0": 8.8984375, "5.5": 0.6267361044883728, "6.0": 0.3120659589767456, "6.5": 0.4496527910232544, "7.0": 57.59939193725586, "7.5": 19.41579818725586, "8.0": 0.5694444179534912, "8.5": 0.4826388955116272, "9.0": 0.1323784738779068, "9.5": 65.67881774902344, "10.0": 0.2126736044883728, "10.5": 2.495659828186035, "11.0": 5.229166507720947}, "1rtBD13QbXE.16.mp4": {"0.5": 255.0, "1.0": 5.99913215637207, "1.5": 4.09765625, "2.0": 4.701388835906982, "2.5": 6.06727409362793, "3.0": 5.853298664093018, "3.5": 4.578558921813965, "4.0": 57.87456512451172, "4.5": 24.51736068725586, "5.0": 18.01953125, "5.5": 12.84461784362793, "6.0": 22.39800262451172, "6.5": 6.578125, "7.0": 1.8932291269302368, "7.5": 3.932725667953491, "8.0": 19.02690887451172, "8.5": 17.84027862548828, "9.0": 4.228298664093018, "9.5": 49.42057418823242, "10.0": 2.069444417953491, "10.5": 2.2135417461395264, "11.0": 3.659288167953491}, "2FmQgQW8clw.1.mp4": {"0.5": 255.0, "1.0": 8.45876693725586, "1.5": 7.798611164093018, "2.0": 8.049479484558105, "2.5": 62.39540100097656, "3.0": 9.733941078186035, "3.5": 12.0546875, "4.0": 14.89626693725586, "4.5": 20.98741340637207, "5.0": 116.23611450195312, "5.5": 5.836371421813965, "6.0": 4.944010257720947, "6.5": 4.40234375, "7.0": 5.337239742279053, "7.5": 9.364583015441895, "8.0": 6.859808921813965, "8.5": 7.072048664093018, "9.0": 6.772135257720947, "9.5": 136.51910400390625, "10.0": 1.4921875, "10.5": 1.7604166269302368, "11.0": 1.671875, "11.5": 1.4318575859069824, "12.0": 1.5112847089767456, "12.5": 1.7252603769302368}, "2ZcsoP1AUTI.6.mp4": {"0.5": 255.0, "1.0": 3.191840171813965, "1.5": 96.85720825195312, "2.0": 11.23263931274414, "2.5": 8.296441078186035, "3.0": 6.538628578186035, "3.5": 7.428385257720947, "4.0": 8.187933921813965, "4.5": 9.09157943725586, "5.0": 8.22873306274414, "5.5": 7.613715171813965, "6.0": 6.917534828186035, "6.5": 18.31684112548828, "7.0": 23.81380271911621, "7.5": 30.15234375, "8.0": 24.53645896911621, "8.5": 35.64453125, "9.0": 29.15798568725586, "9.5": 88.56987762451172, "10.0": 2.875868082046509, "10.5": 3.647569417953491, "11.0": 2.03125, "11.5": 4.145833492279053, "12.0": 6.441840171813965, "12.5": 4.425347328186035, "13.0": 2.569444417953491, "13.5": 8.309895515441895, "14.0": 6.05859375, "14.5": 4.79774284362793, "15.0": 2.288628578186035, "15.5": 2.325086832046509, "16.0": 2.3645832538604736, "16.5": 3.280381917953491, "17.0": 7.644965171813965, "17.5": 77.35025787353516, "18.0": 20.06597137451172, "18.5": 37.47222137451172, "19.0": 31.16579818725586, "19.5": 27.20703125, "20.0": 26.26996612548828, "20.5": 32.98090362548828, "21.0": 28.82682228088379, "21.5": 35.64583206176758, "22.0": 40.30642318725586, "22.5": 40.32465362548828}, "2iOaljmFfb0.4.mp4": {"0.5": 255.0, "1.0": 84.89322662353516, "1.5": 3.8650174140930176, "2.0": 3.421006917953491, "2.5": 3.444878578186035, "3.0": 6.398003578186035, "3.5": 3.163628578186035, "4.0": 5.18272590637207, "4.5": 3.6953125, "5.0": 6.689236164093018, "5.5": 4.203558921813965, "6.0": 5.217013835906982, "6.5": 1.5902777910232544, "7.0": 1.5681424140930176, "7.5": 2.118055582046509, "8.0": 2.5859375, "8.5": 4.47352409362793, "9.0": 2.655816078186035, "9.5": 1.8485243320465088, "10.0": 2.6497395038604736, "10.5": 2.7981770038604736, "11.0": 2.14453125, "11.5": 72.38107299804688, "12.0": 0.8420138955116272, "12.5": 31.61067771911621, "13.0": 59.79036331176758, "13.5": 31.2734375, "14.0": 1.6124131679534912, "14.5": 80.27083587646484, "15.0": 32.65147399902344, "15.5": 31.84809112548828}, "2l9I3UhWP10.8.mp4": {"0.5": 255.0, "1.0": 1.5720486640930176, "1.5": 9.929253578186035, "2.0": 1.7981771230697632, "2.5": 0.9635416865348816, "3.0": 4.08116340637207, "3.5": 3.9114582538604736, "4.0": 1.4822049140930176, "4.5": 89.00650787353516, "5.0": 20.20963478088379, "5.5": 23.11024284362793, "6.0": 17.61111068725586, "6.5": 12.96701431274414, "7.0": 13.85546875, "7.5": 9.940972328186035, "8.0": 12.30381965637207, "8.5": 11.88888931274414, "9.0": 11.72743034362793, "9.5": 11.37282943725586, "10.0": 97.89236450195312, "10.5": 9.53732681274414, "11.0": 2.7404513359069824, "11.5": 3.522569417953491, "12.0": 2.41796875, "12.5": 2.366753578186035, "13.0": 2.672743082046509, "13.5": 2.2122395038604736, "14.0": 3.7526042461395264, "14.5": 1.8771700859069824, "15.0": 46.83420181274414, "15.5": 50.26779556274414, "16.0": 32.21614456176758, "16.5": 27.29600715637207, "17.0": 28.85416603088379, "17.5": 34.18663024902344, "18.0": 47.06987762451172, "18.5": 23.17925262451172, "19.0": 13.105034828186035, "19.5": 24.18967056274414, "20.0": 29.01128387451172, "20.5": 11.25564193725586, "21.0": 40.37543487548828, "21.5": 34.57551956176758, "22.0": 112.46050262451172}, "2uthP1KSZMc.11.mp4": {"0.5": 255.0, "1.0": 10.265191078186035, "1.5": 10.58376693725586, "2.0": 10.511284828186035, "2.5": 10.60763931274414, "3.0": 10.998698234558105, "3.5": 11.07986068725586, "4.0": 11.41796875, "4.5": 11.38585090637207, "5.0": 12.16623306274414, "5.5": 30.37543487548828, "6.0": 39.71744918823242, "6.5": 5.615451335906982, "7.0": 5.493923664093018, "7.5": 5.670138835906982, "8.0": 5.7421875, "8.5": 5.73741340637207, "9.0": 5.804253578186035, "9.5": 5.673177242279053, "10.0": 5.689670085906982, "10.5": 7.140625, "11.0": 13.582465171813965, "11.5": 17.28559112548828, "12.0": 14.52560806274414, "12.5": 6.416232585906982}, "3EBMpTiWAg8.11.mp4": {"0.5": 255.0, "1.0": 21.35807228088379, "1.5": 81.77474212646484, "2.0": 37.61892318725586, "2.5": 33.75303649902344, "3.0": 38.36762237548828, "3.5": 52.28168487548828, "4.0": 40.72092056274414, "4.5": 38.70269012451172, "5.0": 20.45442771911621, "5.5": 20.44053840637207, "6.0": 30.38888931274414, "6.5": 28.72916603088379, "7.0": 19.65494728088379, "7.5": 17.42230987548828, "8.0": 62.28428649902344, "8.5": 53.39713668823242, "9.0": 6.042534828186035, "9.5": 37.34418487548828, "10.0": 34.70790100097656, "10.5": 102.98133850097656, "11.0": 3.409722328186035, "11.5": 5.240451335906982, "12.0": 1.4635416269302368, "12.5": 1.0642361640930176}, "4EDw7gydFBk.25.mp4": {"0.5": 255.0, "1.0": 7.692708492279053, "1.5": 8.13194465637207, "2.0": 7.572916507720947, "2.5": 62.14540100097656, "3.0": 30.61154556274414, "3.5": 23.24565887451172, "4.0": 31.94965362548828, "4.5": 30.90234375, "5.0": 29.62890625, "5.5": 22.49479103088379, "6.0": 28.52560806274414, "6.5": 29.40321159362793, "7.0": 23.58203125, "7.5": 15.8828125, "8.0": 55.50824737548828, "8.5": 9.17361068725586, "9.0": 7.66796875, "9.5": 10.29296875, "10.0": 6.956597328186035, "10.5": 2.804253578186035, "11.0": 7.911458492279053, "11.5": 5.40711784362793, "12.0": 3.8515625, "12.5": 5.40234375, "13.0": 6.930121421813965}, "4vZ7NszztMs.9.mp4": {"0.5": 255.0, "1.0": 72.75086975097656, "1.5": 28.74565887451172, "2.0": 28.04340362548828, "2.5": 27.65147590637207, "3.0": 26.60850715637207, "3.5": 24.80208396911621, "4.0": 23.52256965637207, "4.5": 22.60286521911621, "5.0": 21.44140625, "5.5": 27.96050262451172, "6.0": 82.71137237548828, "6.5": 5.11024284362793, "7.0": 3.405816078186035, "7.5": 27.11371612548828, "8.0": 59.71354293823242, "8.5": 23.95138931274414, "9.0": 22.58680534362793, "9.5": 44.09288024902344, "10.0": 48.30859375, "10.5": 32.47352600097656, "11.0": 32.93446350097656, "11.5": 32.24175262451172, "12.0": 75.35807037353516, "12.5": 32.05642318725586, "13.0": 31.97092056274414, "13.5": 21.25043487548828, "14.0": 21.25954818725586, "14.5": 39.1484375}, "61fybvkZiDE.2.mp4": {"0.5": 255.0, "1.0": 17.13671875, "1.5": 57.3515625, "2.0": 34.78125, "2.5": 36.17578125, "3.0": 32.66276168823242, "3.5": 31.0234375, "4.0": 53.47048568725586, "4.5": 0.1059027761220932, "5.0": 0.1154513880610466, "5.5": 0.1154513880610466, "6.0": 28.03342056274414, "6.5": 6.848090171813965, "7.0": 2.331597328186035, "7.5": 0.63671875, "8.0": 45.82595443725586, "8.5": 26.87586784362793, "9.0": 28.37847137451172, "9.5": 28.7734375, "10.0": 47.09505081176758, "10.5": 25.03515625, "11.0": 27.87152862548828, "11.5": 25.44748306274414, "12.0": 21.05859375, "12.5": 19.39409637451172, "13.0": 13.17795181274414, "13.5": 63.28689193725586, "14.0": 38.22309112548828, "14.5": 15.390191078186035}, "6He8k52im8I.6.mp4": {"0.5": 255.0, "1.0": 51.43923568725586, "1.5": 18.63498306274414, "2.0": 60.60980987548828, "2.5": 72.01996612548828, "3.0": 66.29080200195312, "3.5": 80.66709899902344, "4.0": 22.62239646911621, "4.5": 60.78385543823242, "5.0": 11.886284828186035, "5.5": 29.78125, "6.0": 22.50998306274414, "6.5": 5.980902671813965, "7.0": 6.271701335906982, "7.5": 22.44400978088379, "8.0": 5.467013835906982, "8.5": 5.40321159362793, "9.0": 4.2734375, "9.5": 4.236545085906982, "10.0": 6.368489742279053, "10.5": 28.58072853088379, "11.0": 10.91319465637207, "11.5": 12.463541984558105, "12.0": 7.373263835906982, "12.5": 7.49609375, "13.0": 9.19835090637207, "13.5": 82.95008850097656, "14.0": 59.74045181274414}, "6mLBr_ZxTj4.44.mp4": {"0.5": 255.0, "1.0": 8.28993034362793, "1.5": 16.3203125, "2.0": 10.625, "2.5": 86.16580200195312, "3.0": 1.19140625, "3.5": 2.7634549140930176, "4.0": 2.222222328186035, "4.5": 1.0885416269302368, "5.0": 3.008246421813965, "5.5": 103.97525787353516, "6.0": 1.6918402910232544, "6.5": 1.8663194179534912, "7.0": 1.9496527910232544, "7.5": 3.004774332046509, "8.0": 53.09809112548828, "8.5": 1.4084200859069824, "9.0": 3.109808921813965, "9.5": 1.6953125, "10.0": 2.582899332046509, "10.5": 2.8815104961395264, "11.0": 57.62282943725586, "11.5": 25.77907943725586}, "6qTEzEcD3No.5.mp4": {"0.5": 255.0, "1.0": 59.02994918823242, "1.5": 7.668402671813965, "2.0": 6.0, "2.5": 59.17881774902344, "3.0": 6.80164909362793, "3.5": 99.52734375, "4.0": 1.3984375, "4.5": 1.4422743320465088, "5.0": 63.03689193725586, "5.5": 57.50520706176758, "6.0": 6.24913215637207, "6.5": 2.909288167953491, "7.0": 1.8298611640930176, "7.5": 5.16102409362793, "8.0": 6.680121421813965, "8.5": 13.36545181274414, "9.0": 67.98654174804688, "9.5": 2.598524332046509, "10.0": 2.551649332046509, "10.5": 2.4279513359069824, "11.0": 58.55512237548828, "11.5": 0.7243923544883728, "12.0": 0.7643229365348816, "12.5": 1.203125}, "6ureBD2VY1c.3.mp4": {"0.5": 255.0, "1.0": 17.69270896911621, "1.5": 21.14019012451172, "2.0": 20.08897590637207, "2.5": 19.59939193725586, "3.0": 17.00390625, "3.5": 10.19704818725586, "4.0": 28.54557228088379, "4.5": 23.84895896911621, "5.0": 32.0390625, "5.5": 24.40407943725586, "6.0": 19.78385353088379, "6.5": 61.38498306274414, "7.0": 49.94053649902344, "7.5": 15.23654556274414, "8.0": 21.00086784362793, "8.5": 23.43880271911621, "9.0": 41.28298568725586, "9.5": 47.83203125, "10.0": 39.23611068725586, "10.5": 33.40668487548828, "11.0": 77.82855987548828, "11.5": 43.11458206176758, "12.0": 57.25390625, "12.5": 50.87673568725586, "13.0": 39.49131774902344, "13.5": 46.72569274902344, "14.0": 97.27560424804688, "14.5": 30.12413215637207, "15.0": 43.88628387451172, "15.5": 47.88454818725586, "16.0": 29.45529556274414, "16.5": 33.31423568725586, "17.0": 19.36805534362793, "17.5": 22.76432228088379, "18.0": 41.96440887451172, "18.5": 31.51128387451172, "19.0": 19.515625, "19.5": 10.126301765441895, "20.0": 12.413628578186035, "20.5": 37.10243225097656, "21.0": 31.49956512451172}, "7dyvxojt3o8.51.mp4": {"0.5": 255.0, "1.0": 18.65798568725586, "1.5": 20.13498306274414, "2.0": 78.90755462646484, "2.5": 4.59071159362793, "3.0": 3.90234375, "3.5": 4.205295085906982, "4.0": 3.887586832046509, "4.5": 69.17621612548828, "5.0": 10.40017318725586, "5.5": 9.54210090637207, "6.0": 8.105902671813965, "6.5": 118.41580200195312, "7.0": 173.8190155029297, "7.5": 25.60112762451172, "8.0": 20.08637237548828, "8.5": 21.59635353088379, "9.0": 20.80381965637207, "9.5": 22.56857681274414, "10.0": 21.44618034362793, "10.5": 22.81467056274414, "11.0": 63.38975524902344, "11.5": 7.5859375, "12.0": 7.062933921813965, "12.5": 7.658854007720947, "13.0": 6.950520992279053, "13.5": 7.655816078186035, "14.0": 7.012152671813965, "14.5": 7.569878578186035, "15.0": 7.504340171813965, "15.5": 17.30902862548828, "16.0": 70.765625, "16.5": 17.37543487548828, "17.0": 19.42708396911621, "17.5": 18.14670181274414}, "7k3S6IsDZ28.27.mp4": {"0.5": 255.0, "1.0": 15.906683921813965, "1.5": 68.56814575195312, "2.0": 22.1171875, "2.5": 13.75607681274414, "3.0": 14.05772590637207, "3.5": 13.694878578186035, "4.0": 13.58897590637207, "4.5": 17.48654556274414, "5.0": 85.91493225097656, "5.5": 54.96788024902344, "6.0": 33.81901168823242, "6.5": 25.56206512451172, "7.0": 26.26779556274414, "7.5": 27.06944465637207, "8.0": 30.24913215637207, "8.5": 32.20138931274414, "9.0": 29.10329818725586, "9.5": 70.04600524902344, "10.0": 35.32465362548828, "10.5": 34.27300262451172, "11.0": 33.64366149902344, "11.5": 33.89887237548828}, "84GzJD_ARp0.4.mp4": {"0.5": 255.0, "1.0": 1.3020833730697632, "1.5": 1.9691840410232544, "2.0": 1.1571180820465088, "2.5": 1.4370659589767456, "3.0": 1.5776909589767456, "3.5": 15.576823234558105, "4.0": 1.5434027910232544, "4.5": 1.6879340410232544, "5.0": 0.3919270932674408, "5.5": 55.06857681274414, "6.0": 54.62717056274414, "6.5": 35.13498306274414, "7.0": 33.79340362548828, "7.5": 6.258246421813965, "8.0": 3.8645832538604736, "8.5": 42.50520706176758, "9.0": 7.998697757720947, "9.5": 10.852864265441895, "10.0": 7.07421875, "10.5": 7.215277671813965, "11.0": 6.5703125, "11.5": 22.18359375}, "8GnsEFk5HaI.28.mp4": {"0.5": 255.0, "1.0": 1.2907986640930176, "1.5": 1.3307291269302368, "2.0": 1.2638888359069824, "2.5": 1.2790799140930176, "3.0": 1.1818575859069824, "3.5": 1.2508680820465088, "4.0": 1.1449652910232544, "4.5": 1.2638888359069824, "5.0": 1.1749131679534912, "5.5": 1.1527777910232544, "6.0": 1.3624131679534912, "6.5": 1.1675347089767456, "7.0": 1.203125, "7.5": 1.0360243320465088, "8.0": 1.1527777910232544, "8.5": 1.0989583730697632, "9.0": 0.9752604365348816, "9.5": 40.41926956176758, "10.0": 0.0, "10.5": 0.0, "11.0": 0.0828993022441864, "11.5": 0.2699652910232544, "12.0": 0.0017361111240461469, "12.5": 0.0, "13.0": 0.0, "13.5": 0.0, "14.0": 0.0, "14.5": 0.0, "15.0": 0.0, "15.5": 0.0, "16.0": 56.15234375, "16.5": 62.86371612548828, "17.0": 48.9921875, "17.5": 29.47525978088379, "18.0": 37.55946350097656, "18.5": 36.203125, "19.0": 26.50217056274414, "19.5": 30.68359375, "20.0": 29.79296875, "20.5": 33.23003387451172, "21.0": 39.91666793823242, "21.5": 33.44140625, "22.0": 36.90060806274414, "22.5": 28.99913215637207, "23.0": 35.61328125, "23.5": 37.58680725097656, "24.0": 31.53298568725586, "24.5": 59.44010543823242, "25.0": 1.8559027910232544, "25.5": 1.7890625, "26.0": 1.7834200859069824, "26.5": 1.7717013359069824, "27.0": 1.6892361640930176, "27.5": 1.8971353769302368, "28.0": 46.48350524902344, "28.5": 0.4084201455116272, "29.0": 0.4809027910232544, "29.5": 0.6276041865348816, "30.0": 0.4010416567325592, "30.5": 0.5768229365348816, "31.0": 0.5555555820465088, "31.5": 0.5329861044883728, "32.0": 0.7903645634651184, "32.5": 3.4986979961395264, "33.0": 0.7235243320465088, "33.5": 0.6436631679534912}, "8TJF2VwFgD8.8.mp4": {"0.5": 255.0, "1.0": 3.6080729961395264, "1.5": 2.266493082046509, "2.0": 86.73741149902344, "2.5": 1.2669271230697632, "3.0": 2.0182292461395264, "3.5": 0.2799479067325592, "4.0": 4.941840171813965, "4.5": 90.23871612548828, "5.0": 18.04730987548828, "5.5": 11.92578125, "6.0": 8.130208015441895, "6.5": 45.53298568725586, "7.0": 6.25, "7.5": 9.829426765441895, "8.0": 11.749566078186035, "8.5": 25.78211784362793, "9.0": 11.848958015441895, "9.5": 13.61154556274414, "10.0": 15.13975715637207, "10.5": 9.94835090637207, "11.0": 12.934895515441895, "11.5": 23.08680534362793, "12.0": 21.42795181274414}, "8VR6Ao97vNc.1.mp4": {"0.5": 255.0, "1.0": 131.77040100097656, "1.5": 0.0, "2.0": 122.08246612548828, "2.5": 29.10677146911621, "3.0": 43.98871612548828, "3.5": 6.014322757720947, "4.0": 7.83897590637207, "4.5": 0.7291666865348816, "5.0": 0.01519097201526165, "5.5": 0.1623263955116272, "6.0": 0.009114583022892475, "6.5": 0.0008680555620230734, "7.0": 29.94053840637207, "7.5": 3.7552082538604736, "8.0": 3.678819417953491, "8.5": 23.18359375, "9.0": 4.486979007720947, "9.5": 81.40538024902344, "10.0": 7.430121421813965, "10.5": 4.388888835906982, "11.0": 9.444010734558105, "11.5": 93.08506774902344}, "B_A9FCsWnlI.6.mp4": {"0.5": 255.0, "1.0": 45.42578125, "1.5": 59.26996612548828, "2.0": 63.36415100097656, "2.5": 43.88715362548828, "3.0": 57.57899475097656, "3.5": 66.32769012451172, "4.0": 56.80859375, "4.5": 42.98828125, "5.0": 27.16449737548828, "5.5": 53.20182418823242, "6.0": 63.73307418823242, "6.5": 5.683159828186035, "7.0": 3.164930582046509, "7.5": 58.39800262451172, "8.0": 16.86805534362793, "8.5": 10.055989265441895, "9.0": 78.40972137451172, "9.5": 5.971354007720947, "10.0": 7.962673664093018, "10.5": 60.734375, "11.0": 22.66753387451172, "11.5": 24.93185806274414, "12.0": 31.24392318725586, "12.5": 62.12543487548828, "13.0": 10.25217056274414, "13.5": 66.58810424804688, "14.0": 12.420573234558105, "14.5": 75.84852600097656, "15.0": 34.60590362548828, "15.5": 37.62890625, "16.0": 26.45659637451172, "16.5": 24.79123306274414, "17.0": 66.54080200195312, "17.5": 43.29209899902344}, "CJ-ER0GdJd8.36.mp4": {"0.5": 255.0, "1.0": 9.46875, "1.5": 8.83203125, "2.0": 5.66015625, "2.5": 9.15407943725586, "3.0": 15.48046875, "3.5": 16.46354103088379, "4.0": 18.15928840637207, "4.5": 18.01736068725586, "5.0": 15.51085090637207, "5.5": 14.733073234558105, "6.0": 6.8046875, "6.5": 6.046875, "7.0": 53.54079818725586, "7.5": 2.885850667953491, "8.0": 8.18359375, "8.5": 3.136284828186035, "9.0": 51.78081512451172, "9.5": 34.81944274902344, "10.0": 43.61675262451172, "10.5": 39.27907943725586, "11.0": 39.47395706176758, "11.5": 35.81336975097656, "12.0": 40.67665100097656, "12.5": 38.27083206176758, "13.0": 37.32421875, "13.5": 30.24131965637207, "14.0": 56.34461975097656}, "CJorIIaGAYI.5.mp4": {"0.5": 255.0, "1.0": 8.484808921813965, "1.5": 10.811198234558105, "2.0": 12.02126693725586, "2.5": 114.94834899902344, "3.0": 0.0026041667442768812, "3.5": 151.0026092529297, "4.0": 14.95920181274414, "4.5": 97.234375, "5.0": 0.0264756940305233, "5.5": 0.0078125, "6.0": 73.53125, "6.5": 25.45355987548828, "7.0": 21.91927146911621, "7.5": 29.84635353088379, "8.0": 30.56814193725586, "8.5": 38.0625, "9.0": 31.28342056274414, "9.5": 30.98784637451172, "10.0": 36.48828125, "10.5": 26.94314193725586, "11.0": 23.66145896911621, "11.5": 12.823784828186035, "12.0": 15.64366340637207, "12.5": 28.14236068725586, "13.0": 104.625, "13.5": 43.88584899902344, "14.0": 54.56857681274414}, "ClksIvHsYcc.24.mp4": {"0.5": 255.0, "1.0": 7.513888835906982, "1.5": 34.09592056274414, "2.0": 7.573784828186035, "2.5": 8.829426765441895, "3.0": 5.505208492279053, "3.5": 36.38975524902344, "4.0": 4.0859375, "4.5": 3.413628578186035, "5.0": 41.20746612548828, "5.5": 3.4244792461395264, "6.0": 2.5447049140930176, "6.5": 2.3927950859069824, "7.0": 2.8454861640930176, "7.5": 1.2183159589767456, "8.0": 43.40234375, "8.5": 18.67795181274414, "9.0": 15.915364265441895, "9.5": 43.96440887451172, "10.0": 40.20963668823242, "10.5": 21.19184112548828, "11.0": 44.81119918823242, "11.5": 20.25, "12.0": 25.70746612548828, "12.5": 16.12022590637207, "13.0": 20.55078125, "13.5": 89.98480987548828, "14.0": 1.4131944179534912}, "E2WelDikd4s.50.mp4": {"0.5": 255.0, "1.0": 3.081163167953491, "1.5": 3.1310763359069824, "2.0": 3.1510417461395264, "2.5": 3.2747395038604736, "3.0": 3.215277671813965, "3.5": 3.265191078186035, "4.0": 3.348090171813965, "4.5": 3.211805582046509, "5.0": 2.5256075859069824, "5.5": 1.6883680820465088, "6.0": 39.57595443725586, "6.5": 129.3580780029297, "7.0": 26.734375, "7.5": 16.83854103088379, "8.0": 7.487847328186035, "8.5": 8.19444465637207, "9.0": 8.397135734558105, "9.5": 19.58637237548828, "10.0": 8.3515625, "10.5": 2.6940104961395264, "11.0": 2.5611979961395264, "11.5": 2.1471354961395264, "12.0": 1.3250868320465088, "12.5": 102.27387237548828, "13.0": 42.43793487548828, "13.5": 5.08203125, "14.0": 6.51953125, "14.5": 7.21788215637207, "15.0": 6.98133659362793, "15.5": 6.609375, "16.0": 6.658420085906982, "16.5": 6.468316078186035, "17.0": 5.891058921813965, "17.5": 5.863715171813965, "18.0": 5.789496421813965, "18.5": 4.48741340637207, "19.0": 10.45486068725586}, "EwItjBAwCbQ.37.mp4": {"0.5": 255.0, "1.0": 4.931423664093018, "1.5": 4.533420085906982, "2.0": 5.763020992279053, "2.5": 4.5546875, "3.0": 4.396701335906982, "3.5": 5.1484375, "4.0": 4.58116340637207, "4.5": 4.816840171813965, "5.0": 2.2786457538604736, "5.5": 3.979600667953491, "6.0": 1.9939236640930176, "6.5": 3.2591145038604736, "7.0": 0.4205729067325592, "7.5": 3.003038167953491, "8.0": 2.727430582046509, "8.5": 3.089409828186035, "9.0": 2.8728299140930176, "9.5": 3.86328125, "10.0": 2.546006917953491, "10.5": 2.729600667953491, "11.0": 1.2230902910232544, "11.5": 37.66102600097656, "12.0": 25.13802146911621, "12.5": 20.55078125, "13.0": 17.65451431274414, "13.5": 15.64756965637207, "14.0": 13.988715171813965, "14.5": 17.20442771911621, "15.0": 5.973090171813965, "15.5": 3.430121421813965, "16.0": 5.03125, "16.5": 3.975694417953491, "17.0": 25.90581512451172, "17.5": 11.86892318725586, "18.0": 10.164496421813965, "18.5": 8.940972328186035, "19.0": 8.572916984558105, "19.5": 4.256076335906982}, "FL9WvBWs73I.18.mp4": {"0.5": 255.0, "1.0": 8.55164909362793, "1.5": 11.50303840637207, "2.0": 16.50824737548828, "2.5": 17.24609375, "3.0": 11.042534828186035, "3.5": 61.37630081176758, "4.0": 15.840277671813965, "4.5": 22.90711784362793, "5.0": 27.65364646911621, "5.5": 20.50173568725586, "6.0": 18.08767318725586, "6.5": 42.41796875, "7.0": 0.5855034589767456, "7.5": 0.5677083134651184, "8.0": 0.9136284589767456, "8.5": 0.55859375, "9.0": 1.5594618320465088, "9.5": 1.0173611640930176, "10.0": 1.6215277910232544, "10.5": 1.890625, "11.0": 64.79036712646484, "11.5": 22.66232681274414, "12.0": 27.58897590637207, "12.5": 15.97569465637207, "13.0": 65.21701049804688, "13.5": 18.48524284362793, "14.0": 18.3203125, "14.5": 20.35807228088379, "15.0": 20.63498306274414, "15.5": 10.846354484558105, "16.0": 9.35720443725586, "16.5": 8.977864265441895, "17.0": 6.49913215637207, "17.5": 21.64192771911621, "18.0": 15.715277671813965, "18.5": 16.45008659362793, "19.0": 10.14236068725586, "19.5": 10.01171875, "20.0": 5.082465171813965, "20.5": 4.908854007720947, "21.0": 3.648871421813965, "21.5": 4.562933921813965, "22.0": 36.62760543823242, "22.5": 2.4895832538604736, "23.0": 2.182725667953491, "23.5": 3.9596354961395264, "24.0": 2.984375, "24.5": 1.5164930820465088}, "FYvIo4ylZus.1.mp4": {"0.5": 255.0, "1.0": 10.085503578186035, "1.5": 10.18836784362793, "2.0": 7.785590171813965, "2.5": 5.11631965637207, "3.0": 2.7829861640930176, "3.5": 3.383246421813965, "4.0": 3.550347328186035, "4.5": 4.112847328186035, "5.0": 22.94661521911621, "5.5": 4.75477409362793, "6.0": 5.463107585906982, "6.5": 1.7934027910232544, "7.0": 1.0390625, "7.5": 1.3424478769302368, "8.0": 1.8845486640930176, "8.5": 24.22829818725586, "9.0": 5.21484375, "9.5": 10.819878578186035, "10.0": 19.52690887451172, "10.5": 12.50303840637207, "11.0": 4.10633659362793, "11.5": 3.6184895038604736, "12.0": 5.50868034362793, "12.5": 21.06857681274414, "13.0": 1.1605902910232544, "13.5": 0.8098958134651184, "14.0": 2.831597328186035, "14.5": 5.993489742279053, "15.0": 2.1158854961395264, "15.5": 0.6202256679534912, "16.0": 15.018229484558105, "16.5": 2.0247395038604736, "17.0": 3.0920138359069824, "17.5": 1.8671875, "18.0": 1.6085069179534912, "18.5": 2.4670138359069824, "19.0": 3.0017361640930176, "19.5": 2.164496421813965, "20.0": 7.855902671813965, "20.5": 13.429253578186035}, "F_0Nud7Wa8w.7.mp4": {"0.5": 255.0, "1.0": 6.666232585906982, "1.5": 14.677083015441895, "2.0": 62.33984375, "2.5": 44.52777862548828, "3.0": 8.586371421813965, "3.5": 9.214409828186035, "4.0": 30.42317771911621, "4.5": 71.34418487548828, "5.0": 2.7248263359069824, "5.5": 2.8025174140930176, "6.0": 80.99262237548828, "6.5": 45.98350524902344, "7.0": 49.13628387451172, "7.5": 34.54817581176758, "8.0": 25.83767318725586, "8.5": 29.72569465637207, "9.0": 31.04774284362793, "9.5": 38.13454818725586, "10.0": 33.00651168823242, "10.5": 23.67751693725586, "11.0": 6.682291507720947, "11.5": 8.42274284362793, "12.0": 12.84678840637207, "12.5": 19.16493034362793, "13.0": 52.03428649902344, "13.5": 25.72699737548828, "14.0": 29.16623306274414, "14.5": 29.53255271911621, "15.0": 31.99175262451172}, "FhmTwZGt97Y.22.mp4": {"0.5": 255.0, "1.0": 29.25, "1.5": 39.37196350097656, "2.0": 70.85112762451172, "2.5": 53.35546875, "3.0": 55.73003387451172, "3.5": 25.82508659362793, "4.0": 34.00043487548828, "4.5": 39.59288024902344, "5.0": 35.10720443725586, "5.5": 19.7890625, "6.0": 79.9765625, "6.5": 19.71657943725586, "7.0": 21.92578125, "7.5": 19.12282943725586, "8.0": 10.942708015441895, "8.5": 16.97786521911621, "9.0": 12.215277671813965, "9.5": 28.3359375, "10.0": 14.730902671813965, "10.5": 28.29253387451172, "11.0": 16.95355987548828, "11.5": 45.5625}, "GBERmCAKFic.12.mp4": {"0.5": 255.0, "1.0": 0.9127604365348816, "1.5": 0.5286458134651184, "2.0": 0.4704861044883728, "2.5": 0.4583333432674408, "3.0": 0.2955729067325592, "3.5": 0.3754340410232544, "4.0": 83.7578125, "4.5": 0.4144965410232544, "5.0": 0.7625868320465088, "5.5": 11.65842056274414, "6.0": 7.93836784362793, "6.5": 53.17100524902344, "7.0": 20.6015625, "7.5": 19.70225715637207, "8.0": 5.668402671813965, "8.5": 37.47699737548828, "9.0": 15.880208015441895, "9.5": 47.41796875, "10.0": 8.090277671813965, "10.5": 9.777777671813965, "11.0": 1.5108506679534912}, "GFhJFhBUc-Y.7.mp4": {"0.5": 255.0, "1.0": 13.467448234558105, "1.5": 7.164496421813965, "2.0": 85.03385162353516, "2.5": 1.8823784589767456, "3.0": 0.9986979365348816, "3.5": 1.0347222089767456, "4.0": 0.9483506679534912, "4.5": 0.8849826455116272, "5.0": 62.2734375, "5.5": 9.201823234558105, "6.0": 27.72048568725586, "6.5": 21.96788215637207, "7.0": 27.54253387451172, "7.5": 26.74696159362793, "8.0": 29.33940887451172, "8.5": 34.23307418823242, "9.0": 36.62543487548828, "9.5": 31.58289909362793, "10.0": 28.14322853088379, "10.5": 38.20659637451172, "11.0": 22.17838478088379, "11.5": 56.03819274902344, "12.0": 7.18663215637207, "12.5": 6.996527671813965, "13.0": 2.2291667461395264, "13.5": 3.850694417953491, "14.0": 4.45225715637207, "14.5": 0.9088541865348816, "15.0": 8.80381965637207}, "Gw1fS2Nze5s.1.mp4": {"0.5": 255.0, "1.0": 0.5625, "1.5": 0.14453125, "2.0": 0.02604166604578495, "2.5": 0.014322916977107525, "3.0": 0.0, "3.5": 0.006076388992369175, "4.0": 0.0008680555620230734, "4.5": 0.0013020833721384406, "5.0": 0.0004340277810115367, "5.5": 0.0052083334885537624, "6.0": 0.5190972089767456, "6.5": 0.0855034738779068, "7.0": 0.0069444444961845875, "7.5": 0.0065104165114462376, "8.0": 0.0, "8.5": 59.28819274902344, "9.0": 47.26432418823242, "9.5": 33.83246612548828, "10.0": 34.32161331176758, "10.5": 33.45051956176758, "11.0": 17.29861068725586, "11.5": 39.38151168823242, "12.0": 23.87803840637207, "12.5": 23.98741340637207, "13.0": 36.24262237548828, "13.5": 41.92578125, "14.0": 5.519965171813965, "14.5": 8.06857681274414, "15.0": 8.84592056274414, "15.5": 11.635416984558105, "16.0": 7.04210090637207, "16.5": 5.26649284362793, "17.0": 24.37065887451172, "17.5": 21.50868034362793, "18.0": 21.96657943725586, "18.5": 12.050347328186035, "19.0": 18.22309112548828}, "GzD9IDWLodk.15.mp4": {"0.5": 255.0, "1.0": 0.0008680555620230734, "1.5": 59.51519012451172, "2.0": 4.905816078186035, "2.5": 3.9474825859069824, "3.0": 2.2018229961395264, "3.5": 1.8302950859069824, "4.0": 2.0529513359069824, "4.5": 5.941840171813965, "5.0": 3.6497395038604736, "5.5": 4.105902671813965, "6.0": 5.380642414093018, "6.5": 57.14973831176758, "7.0": 0.2291666716337204, "7.5": 0.08203125, "8.0": 0.0030381944961845875, "8.5": 56.36675262451172, "9.0": 5.896267414093018, "9.5": 4.885416507720947, "10.0": 5.531683921813965, "10.5": 8.24045181274414}, "Itob3CQDzR4.18.mp4": {"0.5": 255.0, "1.0": 2.2890625, "1.5": 2.539496421813965, "2.0": 47.79991149902344, "2.5": 45.08984375, "3.0": 3.1927082538604736, "3.5": 3.256944417953491, "4.0": 0.7534722089767456, "4.5": 0.6848958134651184, "5.0": 5.84071159362793, "5.5": 10.798176765441895, "6.0": 14.27647590637207, "6.5": 20.53255271911621, "7.0": 22.40711784362793, "7.5": 24.56770896911621, "8.0": 20.45572853088379, "8.5": 15.626301765441895, "9.0": 11.418402671813965, "9.5": 6.36414909362793, "10.0": 4.220486164093018, "10.5": 2.3658854961395264, "11.0": 1.4236111640930176, "11.5": 3.01171875, "12.0": 3.035590171813965, "12.5": 12.67578125, "13.0": 31.50694465637207, "13.5": 42.96788024902344, "14.0": 1.8138021230697632, "14.5": 0.8194444179534912, "15.0": 0.953125, "15.5": 52.29253387451172}, "JljoS47Qf_s.20.mp4": {"0.5": 255.0, "1.0": 18.64366340637207, "1.5": 32.90581512451172, "2.0": 12.626301765441895, "2.5": 23.44444465637207, "3.0": 22.41970443725586, "3.5": 10.62977409362793, "4.0": 19.47873306274414, "4.5": 15.29079818725586, "5.0": 11.805121421813965, "5.5": 2.096788167953491, "6.0": 60.5390625, "6.5": 5.713107585906982, "7.0": 8.442708015441895, "7.5": 0.7113715410232544, "8.0": 17.64019012451172, "8.5": 9.359375, "9.0": 11.79210090637207, "9.5": 1.5130208730697632, "10.0": 42.55165100097656, "10.5": 24.94791603088379, "11.0": 30.80815887451172, "11.5": 0.6467013955116272, "12.0": 10.49522590637207, "12.5": 45.8359375, "13.0": 28.39279556274414, "13.5": 26.47743034362793, "14.0": 2.0403645038604736}, "K78OKshZKIA.2.mp4": {"0.5": 255.0, "1.0": 15.6328125, "1.5": 87.70355987548828, "2.0": 0.6358506679534912, "2.5": 1.1888021230697632, "3.0": 1.2947049140930176, "3.5": 0.7469618320465088, "4.0": 1.0177950859069824, "4.5": 1.0460069179534912, "5.0": 0.4865451455116272, "5.5": 1.2300347089767456, "6.0": 0.6527777910232544, "6.5": 0.3532986044883728, "7.0": 0.7916666865348816, "7.5": 0.4609375, "8.0": 0.5842013955116272, "8.5": 0.9544270634651184, "9.0": 0.4105902910232544, "9.5": 0.2252604216337204, "10.0": 1.4322916269302368, "10.5": 1.4292534589767456, "11.0": 1.4236111640930176, "11.5": 0.8246527910232544, "12.0": 1.2873263359069824, "12.5": 81.31510162353516, "13.0": 49.44357681274414, "13.5": 29.15928840637207, "14.0": 11.31467056274414, "14.5": 39.41232681274414, "15.0": 2.2096354961395264, "15.5": 10.70138931274414, "16.0": 9.125, "16.5": 35.66926956176758, "17.0": 3.99609375, "17.5": 2.773871421813965, "18.0": 6.635416507720947, "18.5": 45.69618225097656, "19.0": 5.384548664093018, "19.5": 5.248263835906982, "20.0": 47.1484375, "20.5": 26.9375, "21.0": 17.28385353088379, "21.5": 24.90104103088379, "22.0": 30.62586784362793, "22.5": 29.28125, "23.0": 10.20138931274414, "23.5": 58.10980987548828, "24.0": 41.81553649902344, "24.5": 11.600260734558105, "25.0": 6.266058921813965, "25.5": 12.99392318725586, "26.0": 12.191840171813965}, "KGTsMFEgpFI.3.mp4": {"0.5": 255.0, "1.0": 38.3046875, "1.5": 30.20355987548828, "2.0": 36.13932418823242, "2.5": 21.40234375, "3.0": 24.23914909362793, "3.5": 46.27517318725586, "4.0": 5.913628578186035, "4.5": 7.46788215637207, "5.0": 5.27256965637207, "5.5": 12.58116340637207, "6.0": 31.265625, "6.5": 20.98611068725586, "7.0": 23.04253387451172, "7.5": 21.38541603088379, "8.0": 21.81380271911621, "8.5": 22.21875, "9.0": 27.85416603088379, "9.5": 24.03255271911621, "10.0": 38.00173568725586, "10.5": 32.61805725097656, "11.0": 28.34288215637207, "11.5": 25.7109375}, "Kd3vC0lL9C4.57.mp4": {"0.5": 255.0, "1.0": 17.40234375, "1.5": 7.96397590637207, "2.0": 11.90625, "2.5": 31.50868034362793, "3.0": 7.352864742279053, "3.5": 4.472222328186035, "4.0": 7.244357585906982, "4.5": 15.264323234558105, "5.0": 30.59461784362793, "5.5": 9.934895515441895, "6.0": 9.53211784362793, "6.5": 5.352864742279053, "7.0": 7.45616340637207, "7.5": 5.427517414093018, "8.0": 17.90885353088379, "8.5": 43.24348831176758, "9.0": 27.18359375, "9.5": 11.44835090637207, "10.0": 44.88107681274414, "10.5": 14.280816078186035, "11.0": 32.19791793823242, "11.5": 14.509114265441895, "12.0": 17.90494728088379, "12.5": 47.17404556274414, "13.0": 19.11458396911621, "13.5": 7.57508659362793, "14.0": 2.6575520038604736, "14.5": 3.0915799140930176, "15.0": 2.4626736640930176}, "KqUZIzkIWaY.7.mp4": {"0.5": 255.0, "1.0": 6.02256965637207, "1.5": 24.85807228088379, "2.0": 39.23524475097656, "2.5": 15.83984375, "3.0": 30.29340362548828, "3.5": 32.48871612548828, "4.0": 43.77430725097656, "4.5": 54.37456512451172, "5.0": 3.1315104961395264, "5.5": 5.408420085906982, "6.0": 1.3519965410232544, "6.5": 1.3576388359069824, "7.0": 69.54166412353516, "7.5": 60.38845443725586, "8.0": 38.78645706176758, "8.5": 27.92621612548828, "9.0": 25.86284637451172, "9.5": 14.07204818725586, "10.0": 17.96657943725586, "10.5": 17.2421875, "11.0": 5.46571159362793, "11.5": 21.59461784362793, "12.0": 9.376301765441895, "12.5": 31.61067771911621, "13.0": 21.19357681274414}, "LbJ7VmR7Y9g.8.mp4": {"0.5": 255.0, "1.0": 47.96354293823242, "1.5": 6.46484375, "2.0": 7.654079914093018, "2.5": 7.126302242279053, "3.0": 7.259114742279053, "3.5": 6.35243034362793, "4.0": 7.006076335906982, "4.5": 9.27734375, "5.0": 6.576822757720947, "5.5": 7.666666507720947, "6.0": 50.75390625, "6.5": 15.96006965637207, "7.0": 22.84114646911621, "7.5": 22.63498306274414, "8.0": 20.859375, "8.5": 17.07769012451172, "9.0": 10.34765625, "9.5": 17.09635353088379, "10.0": 10.108073234558105, "10.5": 9.69357681274414, "11.0": 12.753472328186035, "11.5": 60.89236068725586, "12.0": 43.3203125, "12.5": 30.14019012451172, "13.0": 34.76041793823242, "13.5": 25.07465362548828, "14.0": 33.40451431274414}, "LjKTQ9RKdaM.1.mp4": {"0.5": 255.0, "1.0": 82.78298950195312, "1.5": 9.29296875, "2.0": 9.2890625, "2.5": 9.18967056274414, "3.0": 9.040364265441895, "3.5": 8.039496421813965, "4.0": 6.901041507720947, "4.5": 6.924479007720947, "5.0": 8.32595443725586, "5.5": 7.661892414093018, "6.0": 6.678385257720947, "6.5": 59.71050262451172, "7.0": 3.8029513359069824, "7.5": 4.95008659362793, "8.0": 2.881944417953491, "8.5": 6.089409828186035, "9.0": 2.76171875, "9.5": 3.715277671813965, "10.0": 1.3654513359069824, "10.5": 0.4982638955116272, "11.0": 3.593316078186035, "11.5": 1.4123263359069824}, "LpUyGPg_Dc8.8.mp4": {"0.5": 255.0, "1.0": 9.480034828186035, "1.5": 13.042534828186035, "2.0": 27.43315887451172, "2.5": 32.19487762451172, "3.0": 17.10546875, "3.5": 31.47439193725586, "4.0": 62.8125, "4.5": 8.16102409362793, "5.0": 4.81640625, "5.5": 1.6792534589767456, "6.0": 0.6840277910232544, "6.5": 0.8767361044883728, "7.0": 1.1128472089767456, "7.5": 1.4796006679534912, "8.0": 1.04296875, "8.5": 1.5590277910232544, "9.0": 51.12890625, "9.5": 10.06944465637207, "10.0": 15.188801765441895, "10.5": 11.951823234558105, "11.0": 12.74826431274414, "11.5": 15.15234375, "12.0": 15.6640625, "12.5": 22.67621612548828, "13.0": 24.34244728088379, "13.5": 25.04817771911621, "14.0": 30.23654556274414, "14.5": 33.67057418823242, "15.0": 29.77126693725586, "15.5": 24.20052146911621, "16.0": 27.82508659362793, "16.5": 32.2578125, "17.0": 19.84939193725586, "17.5": 33.05598831176758, "18.0": 28.953125, "18.5": 44.53689193725586, "19.0": 33.02040100097656, "19.5": 30.49131965637207, "20.0": 26.47135353088379, "20.5": 36.95355987548828, "21.0": 31.20920181274414}, "MtuZyKJPjAM.4.mp4": {"0.5": 255.0, "1.0": 4.173611164093018, "1.5": 3.2434895038604736, "2.0": 69.62673950195312, "2.5": 16.20008659362793, "3.0": 10.494791984558105, "3.5": 13.62326431274414, "4.0": 9.81336784362793, "4.5": 10.87109375, "5.0": 12.846354484558105, "5.5": 9.346354484558105, "6.0": 10.33116340637207, "6.5": 7.748697757720947, "7.0": 6.853298664093018, "7.5": 4.01258659362793, "8.0": 65.79122924804688, "8.5": 1.8020833730697632, "9.0": 2.0807292461395264, "9.5": 2.2005207538604736, "10.0": 2.58984375, "10.5": 2.7942707538604736, "11.0": 3.7486979961395264, "11.5": 2.973524332046509, "12.0": 3.167534828186035, "12.5": 4.444878578186035, "13.0": 34.56119918823242, "13.5": 23.21484375, "14.0": 25.59809112548828, "14.5": 24.10199737548828, "15.0": 19.62673568725586, "15.5": 16.74565887451172, "16.0": 9.70529556274414, "16.5": 4.508246421813965, "17.0": 38.62152862548828, "17.5": 33.11241149902344, "18.0": 20.96657943725586, "18.5": 33.34852600097656, "19.0": 33.74956512451172, "19.5": 38.79296875}, "O7aiG0D0qo0.25.mp4": {"0.5": 255.0, "1.0": 2.918402671813965, "1.5": 17.12065887451172, "2.0": 89.55902862548828, "2.5": 5.089409828186035, "3.0": 3.9296875, "3.5": 2.68359375, "4.0": 1.3415799140930176, "4.5": 1.4127603769302368, "5.0": 1.5078125, "5.5": 1.2643228769302368, "6.0": 1.0993924140930176, "6.5": 3.5104167461395264, "7.0": 109.93706512451172, "7.5": 39.49088668823242, "8.0": 26.38237762451172, "8.5": 4.99913215637207, "9.0": 0.3619791567325592, "9.5": 0.03125, "10.0": 4.386284828186035, "10.5": 7.7109375, "11.0": 28.62803840637207, "11.5": 87.50868225097656, "12.0": 12.60069465637207}, "PXMngeRSdsE.25.mp4": {"0.5": 255.0, "1.0": 25.24479103088379, "1.5": 21.46006965637207, "2.0": 22.3125, "2.5": 13.391926765441895, "3.0": 0.7513020634651184, "3.5": 31.51953125, "4.0": 24.16927146911621, "4.5": 3.5759549140930176, "5.0": 2.616319417953491, "5.5": 1.8294271230697632, "6.0": 17.04296875, "6.5": 60.65972137451172, "7.0": 0.4244791567325592, "7.5": 22.11762237548828, "8.0": 41.88324737548828, "8.5": 12.52647590637207, "9.0": 26.20486068725586, "9.5": 19.73697853088379, "10.0": 35.85980987548828, "10.5": 22.21484375, "11.0": 29.85199737548828, "11.5": 32.01953125, "12.0": 24.82335090637207, "12.5": 29.90060806274414, "13.0": 22.82855987548828, "13.5": 14.88368034362793, "14.0": 18.36805534362793, "14.5": 29.02951431274414, "15.0": 9.778645515441895, "15.5": 7.328125, "16.0": 6.98741340637207, "16.5": 5.947048664093018, "17.0": 9.003472328186035}, "PvvMz5FBofo.6.mp4": {"0.5": 255.0, "1.0": 51.85546875, "1.5": 64.55381774902344, "2.0": 32.73307418823242, "2.5": 28.58506965637207, "3.0": 31.6796875, "3.5": 33.48784637451172, "4.0": 60.96267318725586, "4.5": 1.1037325859069824, "5.0": 0.8333333134651184, "5.5": 0.5924479365348816, "6.0": 2.22265625, "6.5": 4.657986164093018, "7.0": 6.256076335906982, "7.5": 8.19357681274414, "8.0": 6.905816078186035, "8.5": 52.99392318725586, "9.0": 25.35069465637207, "9.5": 22.51822853088379, "10.0": 22.53732681274414, "10.5": 21.90581512451172, "11.0": 20.92795181274414, "11.5": 29.98003387451172}, "RdhTvVPcoIo.12.mp4": {"0.5": 255.0, "1.0": 21.29340362548828, "1.5": 18.43663215637207, "2.0": 7.68836784362793, "2.5": 6.394965171813965, "3.0": 10.710503578186035, "3.5": 11.09765625, "4.0": 48.9375, "4.5": 2.671441078186035, "5.0": 2.8025174140930176, "5.5": 52.2265625, "6.0": 32.15147399902344, "6.5": 8.96267318725586, "7.0": 123.13237762451172, "7.5": 12.28732681274414, "8.0": 76.65277862548828, "8.5": 68.47482299804688, "9.0": 97.88715362548828, "9.5": 8.1875, "10.0": 77.69791412353516, "10.5": 8.140625, "11.0": 33.97569274902344, "11.5": 1.09765625, "12.0": 2.0603299140930176, "12.5": 2.2161457538604736, "13.0": 1.6584200859069824}, "RjC6aV1SNIQ.3.mp4": {"0.5": 255.0, "1.0": 10.66493034362793, "1.5": 39.99826431274414, "2.0": 20.15798568725586, "2.5": 18.49392318725586, "3.0": 10.43272590637207, "3.5": 10.67404556274414, "4.0": 9.051215171813965, "4.5": 8.855034828186035, "5.0": 9.778645515441895, "5.5": 12.009114265441895, "6.0": 4.344183921813965, "6.5": 5.054253578186035, "7.0": 8.13975715637207, "7.5": 10.26171875, "8.0": 14.46180534362793, "8.5": 17.28168487548828, "9.0": 26.26388931274414, "9.5": 40.14670181274414, "10.0": 35.82161331176758, "10.5": 30.95355987548828, "11.0": 28.83029556274414, "11.5": 24.81119728088379, "12.0": 27.19140625, "12.5": 41.30729293823242, "13.0": 16.61024284362793, "13.5": 18.89670181274414, "14.0": 28.11241340637207, "14.5": 29.33940887451172, "15.0": 19.22873306274414, "15.5": 15.147135734558105, "16.0": 18.63194465637207, "16.5": 29.84765625, "17.0": 12.42361068725586, "17.5": 8.97829818725586, "18.0": 9.96006965637207, "18.5": 10.30164909362793}, "VWlP9ZnqmIQ.14.mp4": {"0.5": 255.0, "1.0": 2.0364582538604736, "1.5": 1.4500868320465088, "2.0": 0.8194444179534912, "2.5": 6.615451335906982, "3.0": 71.37369537353516, "3.5": 55.62456512451172, "4.0": 28.08637237548828, "4.5": 25.37369728088379, "5.0": 65.20355987548828, "5.5": 63.64973831176758, "6.0": 35.91493225097656, "6.5": 44.05338668823242, "7.0": 74.49435424804688, "7.5": 61.21223831176758, "8.0": 29.81900978088379, "8.5": 16.91319465637207, "9.0": 65.10199737548828, "9.5": 9.01953125, "10.0": 10.737847328186035, "10.5": 8.41015625, "11.0": 8.377604484558105, "11.5": 8.14756965637207, "12.0": 7.588541507720947, "12.5": 5.23350715637207, "13.0": 8.42881965637207, "13.5": 9.76388931274414, "14.0": 7.26258659362793, "14.5": 58.74088668823242, "15.0": 30.70789909362793, "15.5": 30.29123306274414, "16.0": 34.80598831176758, "16.5": 34.02951431274414, "17.0": 33.87934112548828, "17.5": 38.98350524902344, "18.0": 32.17795181274414, "18.5": 33.65451431274414, "19.0": 22.0390625, "19.5": 18.50217056274414, "20.0": 13.05685806274414, "20.5": 18.50043487548828, "21.0": 28.07204818725586, "21.5": 37.16059112548828, "22.0": 39.81553649902344, "22.5": 19.72352409362793, "23.0": 30.81814193725586}, "WQFI_AIsYtg.15.mp4": {"0.5": 255.0, "1.0": 122.50130462646484, "1.5": 122.09201049804688, "2.0": 44.95182418823242, "2.5": 38.0078125, "3.0": 20.5625, "3.5": 50.10676956176758, "4.0": 41.70008850097656, "4.5": 55.48003387451172, "5.0": 28.65711784362793, "5.5": 43.25651168823242, "6.0": 25.54861068725586, "6.5": 19.04644012451172, "7.0": 39.10850524902344, "7.5": 33.40668487548828, "8.0": 25.91840362548828, "8.5": 36.21180725097656, "9.0": 46.17751693725586, "9.5": 52.60026168823242, "10.0": 39.10894012451172, "10.5": 41.03211975097656, "11.0": 47.8515625, "11.5": 16.84114646911621, "12.0": 11.687933921813965, "12.5": 26.16493034362793, "13.0": 29.09071159362793, "13.5": 31.37803840637207, "14.0": 20.97092056274414, "14.5": 64.67534637451172, "15.0": 63.10416793823242, "15.5": 29.88107681274414, "16.0": 8.647135734558105, "16.5": 21.26215362548828, "17.0": 29.30034637451172, "17.5": 44.63845443725586, "18.0": 43.35807418823242, "18.5": 42.53125}, "WV1-ZuBmP-E.12.mp4": {"0.5": 255.0, "1.0": 10.532551765441895, "1.5": 20.34982681274414, "2.0": 12.51388931274414, "2.5": 19.26085090637207, "3.0": 91.61675262451172, "3.5": 9.17404556274414, "4.0": 5.451388835906982, "4.5": 1.9283853769302368, "5.0": 6.234375, "5.5": 91.95790100097656, "6.0": 2.1545138359069824, "6.5": 4.558159828186035, "7.0": 2.995659828186035, "7.5": 13.22439193725586, "8.0": 26.94400978088379, "8.5": 74.63932037353516, "9.0": 4.078125, "9.5": 1.6189236640930176, "10.0": 2.6783854961395264, "10.5": 14.01649284362793, "11.0": 3.836805582046509, "11.5": 5.327691078186035, "12.0": 3.855034828186035, "12.5": 7.065972328186035, "13.0": 6.411458492279053, "13.5": 1.9322916269302368, "14.0": 5.810329914093018, "14.5": 4.654947757720947, "15.0": 70.29600524902344}, "WZcMlzsh4XM.1.mp4": {"0.5": 255.0, "1.0": 24.23958396911621, "1.5": 21.39453125, "2.0": 84.53949737548828, "2.5": 91.92534637451172, "3.0": 93.81423950195312, "3.5": 55.79470443725586, "4.0": 46.17317581176758, "4.5": 88.31423950195312, "5.0": 79.48784637451172, "5.5": 65.05165100097656, "6.0": 57.94704818725586, "6.5": 23.56163215637207, "7.0": 37.875, "7.5": 62.26692581176758, "8.0": 46.52300262451172, "8.5": 99.64540100097656, "9.0": 17.45703125, "9.5": 18.82378387451172, "10.0": 5.154513835906982, "10.5": 6.09461784362793}, "XqDwTKVeUcI.31.mp4": {"0.5": 255.0, "1.0": 1.0959200859069824, "1.5": 1.0082465410232544, "2.0": 63.27387237548828, "2.5": 4.031683921813965, "3.0": 4.052951335906982, "3.5": 45.91796875, "4.0": 35.46918487548828, "4.5": 42.62022399902344, "5.0": 14.05295181274414, "5.5": 38.93663024902344, "6.0": 30.89279556274414, "6.5": 53.30034637451172, "7.0": 20.59939193725586, "7.5": 21.77864646911621, "8.0": 20.27994728088379, "8.5": 18.84505271911621, "9.0": 13.78993034362793, "9.5": 63.11198043823242, "10.0": 19.76953125, "10.5": 21.16840362548828, "11.0": 10.122395515441895, "11.5": 37.51388931274414, "12.0": 4.34852409362793, "12.5": 16.88585090637207, "13.0": 20.18880271911621, "13.5": 8.32508659362793, "14.0": 23.74782943725586, "14.5": 4.260416507720947, "15.0": 27.86849021911621, "15.5": 23.87152862548828, "16.0": 18.43142318725586}, "YofCMYZhYcs.5.mp4": {"0.5": 255.0, "1.0": 2.019965171813965, "1.5": 2.1657986640930176, "2.0": 1.515625, "2.5": 2.5295138359069824, "3.0": 2.1809895038604736, "3.5": 1.4735243320465088, "4.0": 1.4665799140930176, "4.5": 3.35546875, "5.0": 3.5217013359069824, "5.5": 3.8728299140930176, "6.0": 2.9192707538604736, "6.5": 75.11024475097656, "7.0": 2.1267361640930176, "7.5": 1.4236111640930176, "8.0": 0.7161458134651184, "8.5": 0.9895833134651184, "9.0": 0.8038194179534912, "9.5": 0.5503472089767456, "10.0": 1.109375, "10.5": 0.8072916865348816, "11.0": 1.1276041269302368, "11.5": 0.8107638955116272, "12.0": 0.6953125, "12.5": 2.112847328186035, "13.0": 65.03689575195312, "13.5": 5.98524284362793, "14.0": 3.58984375, "14.5": 3.2092013359069824, "15.0": 2.003472328186035, "15.5": 51.77256774902344, "16.0": 3.526909828186035, "16.5": 5.10243034362793, "17.0": 4.574652671813965, "17.5": 4.173611164093018, "18.0": 5.157986164093018, "18.5": 6.80946159362793, "19.0": 9.717448234558105, "19.5": 4.392361164093018}, "ZRL2bzC2GGE.67.mp4": {"0.5": 255.0, "1.0": 2.563368082046509, "1.5": 3.1393229961395264, "2.0": 2.7942707538604736, "2.5": 3.0872395038604736, "3.0": 3.624566078186035, "3.5": 2.292534828186035, "4.0": 4.111545085906982, "4.5": 2.276909828186035, "5.0": 3.531683921813965, "5.5": 4.907552242279053, "6.0": 3.6536457538604736, "6.5": 2.065538167953491, "7.0": 2.312066078186035, "7.5": 54.73567581176758, "8.0": 21.98220443725586, "8.5": 25.20703125, "9.0": 25.26866340637207, "9.5": 25.15277862548828, "10.0": 23.78602409362793, "10.5": 22.04730987548828, "11.0": 16.98003387451172, "11.5": 3.14453125, "12.0": 1.9813368320465088, "12.5": 1.5533853769302368, "13.0": 0.9201388955116272, "13.5": 49.11979293823242, "14.0": 3.303819417953491, "14.5": 2.7912325859069824, "15.0": 4.21006965637207, "15.5": 3.6536457538604736, "16.0": 1.1653646230697632, "16.5": 3.198784828186035, "17.0": 3.6228299140930176, "17.5": 46.39583206176758}, "_8UAR6y9vtM.1.mp4": {"0.5": 255.0, "1.0": 3.6697049140930176, "1.5": 3.3927950859069824, "2.0": 8.33810806274414, "2.5": 6.469183921813965, "3.0": 7.096354007720947, "3.5": 32.88975524902344, "4.0": 9.279948234558105, "4.5": 9.64279556274414, "5.0": 10.34375, "5.5": 3.164496421813965, "6.0": 3.11328125, "6.5": 4.2890625, "7.0": 4.233941078186035, "7.5": 2.971788167953491, "8.0": 2.9557292461395264, "8.5": 3.9782986640930176, "9.0": 3.0412325859069824, "9.5": 3.0368924140930176, "10.0": 3.0065104961395264, "10.5": 3.003472328186035, "11.0": 2.9665799140930176, "11.5": 2.940972328186035, "12.0": 3.0247395038604736, "12.5": 3.194444417953491, "13.0": 3.245659828186035, "13.5": 23.77821159362793, "14.0": 37.36024475097656, "14.5": 11.294270515441895, "15.0": 10.40451431274414, "15.5": 9.60633659362793, "16.0": 18.09114646911621, "16.5": 18.20746612548828, "17.0": 18.85677146911621, "17.5": 81.15364837646484, "18.0": 23.65277862548828, "18.5": 79.44444274902344, "19.0": 41.98741149902344, "19.5": 4.622395992279053, "20.0": 5.277777671813965, "20.5": 7.127604007720947, "21.0": 8.260416984558105, "21.5": 9.29774284362793, "22.0": 11.47048568725586, "22.5": 16.421875, "23.0": 24.53819465637207, "23.5": 42.84288024902344}, "_BVVEW8y_VA.9.mp4": {"0.5": 255.0, "1.0": 5.944010257720947, "1.5": 33.33073043823242, "2.0": 30.26085090637207, "2.5": 19.49045181274414, "3.0": 17.20486068725586, "3.5": 12.07638931274414, "4.0": 73.48611450195312, "4.5": 58.58333206176758, "5.0": 46.27256774902344, "5.5": 55.05295181274414, "6.0": 41.21831512451172, "6.5": 41.63628387451172, "7.0": 39.07986068725586, "7.5": 32.67144012451172, "8.0": 39.36241149902344, "8.5": 34.32942581176758, "9.0": 48.70095443725586, "9.5": 55.27430725097656, "10.0": 35.38194274902344, "10.5": 31.74001693725586, "11.0": 32.78819274902344, "11.5": 34.38237762451172, "12.0": 19.3359375, "12.5": 29.01085090637207, "13.0": 12.61154556274414, "13.5": 0.0954861119389534, "14.0": 0.0434027761220932, "14.5": 0.006076388992369175}, "_Jh8mvvoXsM.1.mp4": {"0.5": 255.0, "1.0": 6.411458492279053, "1.5": 5.93055534362793, "2.0": 5.1640625, "2.5": 4.021267414093018, "3.0": 3.5490450859069824, "3.5": 4.753472328186035, "4.0": 5.90625, "4.5": 3.0989582538604736, "5.0": 4.915364742279053, "5.5": 2.922743082046509, "6.0": 85.09765625, "6.5": 1.6848958730697632, "7.0": 1.3363715410232544, "7.5": 1.7478299140930176, "8.0": 1.5811631679534912, "8.5": 77.5546875, "9.0": 22.85460090637207, "9.5": 21.18663215637207, "10.0": 24.54166603088379}, "_KgWye11I8g.7.mp4": {"0.5": 255.0, "1.0": 4.512152671813965, "1.5": 4.42100715637207, "2.0": 4.20616340637207, "2.5": 4.051215171813965, "3.0": 33.39626693725586, "3.5": 49.98828125, "4.0": 51.96831512451172, "4.5": 22.83854103088379, "5.0": 21.02474021911621, "5.5": 14.05164909362793, "6.0": 3.438368082046509, "6.5": 4.724826335906982, "7.0": 12.760416984558105, "7.5": 16.81510353088379, "8.0": 18.45442771911621, "8.5": 16.78515625, "9.0": 14.88368034362793, "9.5": 32.9375, "10.0": 62.97309112548828, "10.5": 27.42447853088379, "11.0": 115.21875, "11.5": 45.22959899902344, "12.0": 27.43836784362793, "12.5": 34.70182418823242, "13.0": 44.55685806274414, "13.5": 68.06510162353516, "14.0": 76.34982299804688}, "_T2A_lgn69w.37.mp4": {"0.5": 255.0, "1.0": 57.33246612548828, "1.5": 7.4609375, "2.0": 4.040364742279053, "2.5": 6.951388835906982, "3.0": 4.266927242279053, "3.5": 3.938368082046509, "4.0": 6.420572757720947, "4.5": 5.33289909362793, "5.0": 6.132378578186035, "5.5": 41.94227600097656, "6.0": 31.75868034362793, "6.5": 15.694878578186035, "7.0": 5.261284828186035, "7.5": 6.310763835906982, "8.0": 5.455295085906982, "8.5": 10.265191078186035, "9.0": 8.12282943725586, "9.5": 5.298177242279053, "10.0": 10.562933921813965, "10.5": 8.44921875, "11.0": 7.286458492279053, "11.5": 7.327691078186035, "12.0": 5.322916507720947, "12.5": 9.12282943725586, "13.0": 12.53125, "13.5": 91.05294799804688, "14.0": 21.16796875, "14.5": 27.10633659362793, "15.0": 34.34244918823242, "15.5": 41.16753387451172, "16.0": 34.734375, "16.5": 31.54296875, "17.0": 25.37282943725586, "17.5": 22.54730987548828, "18.0": 85.34071350097656, "18.5": 13.50564193725586, "19.0": 20.46180534362793, "19.5": 30.21310806274414, "20.0": 39.61024475097656, "20.5": 42.26128387451172, "21.0": 31.14756965637207, "21.5": 12.43055534362793, "22.0": 16.52821159362793, "22.5": 17.04383659362793, "23.0": 5.099826335906982, "23.5": 7.99609375, "24.0": 6.292534828186035, "24.5": 9.798176765441895, "25.0": 11.46397590637207, "25.5": 13.13368034362793, "26.0": 16.13237762451172, "26.5": 28.36111068725586, "27.0": 36.63064193725586, "27.5": 42.56380081176758, "28.0": 50.06857681274414, "28.5": 27.46224021911621, "29.0": 18.37890625, "29.5": 19.79861068725586, "30.0": 19.58116340637207, "30.5": 12.0703125, "31.0": 9.66970443725586, "31.5": 14.098958015441895, "32.0": 11.89756965637207, "32.5": 8.374566078186035}, "_nbQsnUvlo4.9.mp4": {"0.5": 255.0, "1.0": 11.56944465637207, "1.5": 10.72743034362793, "2.0": 4.806423664093018, "2.5": 14.967448234558105, "3.0": 19.05685806274414, "3.5": 22.50173568725586, "4.0": 7.302083492279053, "4.5": 4.83897590637207, "5.0": 1.2513021230697632, "5.5": 43.70963668823242, "6.0": 18.87282943725586, "6.5": 8.430989265441895, "7.0": 14.38194465637207, "7.5": 14.138020515441895, "8.0": 4.430989742279053, "8.5": 5.209201335906982, "9.0": 9.953125, "9.5": 9.013020515441895, "10.0": 43.38454818725586, "10.5": 4.467013835906982, "11.0": 8.2578125, "11.5": 10.02430534362793, "12.0": 16.16189193725586, "12.5": 6.24609375}, "aGu1HuPl3Gs.1.mp4": {"0.5": 255.0, "1.0": 2.8541667461395264, "1.5": 14.846354484558105, "2.0": 15.86935806274414, "2.5": 0.1258680522441864, "3.0": 0.0850694477558136, "3.5": 0.2005208283662796, "4.0": 0.2986111044883728, "4.5": 0.03081597201526165, "5.0": 9.656683921813965, "5.5": 9.606770515441895, "6.0": 0.009548611007630825, "6.5": 34.22699737548828, "7.0": 29.80078125, "7.5": 8.179253578186035, "8.0": 0.1532118022441864, "8.5": 0.1019965261220932, "9.0": 0.5321180820465088, "9.5": 0.1671006977558136, "10.0": 0.2052951455116272, "10.5": 0.3098958432674408, "11.0": 0.4314236044883728, "11.5": 0.1970486044883728, "12.0": 0.1423611044883728, "12.5": 0.2608506977558136, "13.0": 0.4926215410232544, "13.5": 0.3919270932674408, "14.0": 2.327256917953491, "14.5": 1.6184896230697632}, "bTMVVjv5zQ0.2.mp4": {"0.5": 255.0, "1.0": 0.4748263955116272, "1.5": 61.97005081176758, "2.0": 3.269965171813965, "2.5": 15.794270515441895, "3.0": 0.9383680820465088, "3.5": 0.94921875, "4.0": 0.8997395634651184, "4.5": 22.69400978088379, "5.0": 3.213975667953491, "5.5": 3.2356770038604736, "6.0": 3.1822917461395264, "6.5": 26.38454818725586, "7.0": 8.52430534362793, "7.5": 8.585503578186035, "8.0": 28.60677146911621, "8.5": 16.26605987548828, "9.0": 0.6879340410232544, "9.5": 65.40103912353516, "10.0": 6.123697757720947, "10.5": 5.441840171813965, "11.0": 5.001736164093018, "11.5": 3.6857638359069824, "12.0": 15.93142318725586, "12.5": 90.81466674804688}, "cN039e4NGAw.7.mp4": {"0.5": 255.0, "1.0": 11.28515625, "1.5": 37.19314193725586, "2.0": 98.52908325195312, "2.5": 15.61414909362793, "3.0": 1.0629340410232544, "3.5": 1.1345486640930176, "4.0": 0.8884548544883728, "4.5": 0.9973958134651184, "5.0": 1.1032986640930176, "5.5": 1.10546875, "6.0": 2.1341145038604736, "6.5": 1.6553819179534912, "7.0": 2.881944417953491, "7.5": 1.9552950859069824, "8.0": 102.73090362548828, "8.5": 25.75694465637207, "9.0": 23.79947853088379, "9.5": 72.89149475097656, "10.0": 1.8723958730697632, "10.5": 1.8697916269302368, "11.0": 78.60025787353516, "11.5": 19.63932228088379}, "crFioD1EelM.13.mp4": {"0.5": 255.0, "1.0": 17.33159637451172, "1.5": 40.58159637451172, "2.0": 0.9409722089767456, "2.5": 0.5642361044883728, "3.0": 0.6657986044883728, "3.5": 0.6280381679534912, "4.0": 0.6024305820465088, "4.5": 0.5, "5.0": 34.46831512451172, "5.5": 19.02994728088379, "6.0": 8.67491340637207, "6.5": 9.97960090637207, "7.0": 6.423611164093018, "7.5": 5.694878578186035, "8.0": 7.322482585906982, "8.5": 6.02734375, "9.0": 4.9140625, "9.5": 3.852430582046509, "10.0": 3.9778645038604736, "10.5": 5.623263835906982, "11.0": 74.29080200195312, "11.5": 3.59765625, "12.0": 3.5768229961395264, "12.5": 3.766058921813965}, "dR9jfG9Zr5A.2.mp4": {"0.5": 255.0, "1.0": 5.451388835906982, "1.5": 7.773871421813965, "2.0": 7.108072757720947, "2.5": 6.54296875, "3.0": 4.970486164093018, "3.5": 3.7282986640930176, "4.0": 8.452691078186035, "4.5": 7.744357585906982, "5.0": 7.298177242279053, "5.5": 8.417534828186035, "6.0": 5.17578125, "6.5": 7.547308921813965, "7.0": 8.8515625, "7.5": 8.27821159362793, "8.0": 6.255642414093018, "8.5": 3.4704861640930176, "9.0": 5.42578125, "9.5": 3.504340171813965, "10.0": 4.457465171813965, "10.5": 3.4509549140930176, "11.0": 2.0681424140930176, "11.5": 91.97916412353516, "12.0": 4.161892414093018, "12.5": 3.3919270038604736, "13.0": 3.090277671813965, "13.5": 2.711805582046509, "14.0": 61.93619918823242, "14.5": 6.23350715637207, "15.0": 7.91015625, "15.5": 6.90928840637207, "16.0": 8.32725715637207, "16.5": 7.857638835906982, "17.0": 71.85372924804688, "17.5": 69.921875, "18.0": 14.085503578186035, "18.5": 21.57421875, "19.0": 17.23871612548828, "19.5": 15.538628578186035, "20.0": 44.38975524902344, "20.5": 0.3276909589767456, "21.0": 0.3932291567325592, "21.5": 1.7374131679534912, "22.0": 0.78515625, "22.5": 73.18663024902344, "23.0": 21.66449737548828, "23.5": 17.26692771911621, "24.0": 21.76258659362793, "24.5": 23.6875}, "dUAPtHIz8Rg.5.mp4": {"0.5": 255.0, "1.0": 11.925347328186035, "1.5": 18.61935806274414, "2.0": 8.867621421813965, "2.5": 9.48611068725586, "3.0": 60.38715362548828, "3.5": 61.58290100097656, "4.0": 41.27777862548828, "4.5": 36.91362762451172, "5.0": 33.73176956176758, "5.5": 57.328125, "6.0": 40.55946350097656, "6.5": 46.99652862548828, "7.0": 26.22309112548828, "7.5": 16.93359375, "8.0": 38.01692581176758, "8.5": 22.89670181274414, "9.0": 14.32508659362793, "9.5": 11.63758659362793, "10.0": 10.952691078186035, "10.5": 21.10590362548828, "11.0": 20.07465362548828, "11.5": 13.765191078186035, "12.0": 8.45486068725586, "12.5": 7.047308921813965, "13.0": 55.69618225097656, "13.5": 17.31032943725586, "14.0": 6.295138835906982, "14.5": 13.58680534362793, "15.0": 13.74045181274414, "15.5": 5.915798664093018, "16.0": 5.32118034362793, "16.5": 4.716579914093018, "17.0": 2.370225667953491, "17.5": 3.984375, "18.0": 5.463107585906982, "18.5": 4.361979007720947, "19.0": 7.718316078186035, "19.5": 6.69921875, "20.0": 6.351128578186035, "20.5": 3.051649332046509, "21.0": 2.6497395038604736, "21.5": 4.16493034362793, "22.0": 4.65538215637207, "22.5": 51.07421875, "23.0": 17.71614646911621, "23.5": 15.839409828186035, "24.0": 17.84505271911621, "24.5": 12.67664909362793, "25.0": 16.65928840637207}, "e3aLYhXDbz8.12.mp4": {"0.5": 255.0, "1.0": 9.112847328186035, "1.5": 9.60460090637207, "2.0": 9.518229484558105, "2.5": 9.42404556274414, "3.0": 9.752604484558105, "3.5": 27.203125, "4.0": 35.50737762451172, "4.5": 1.2473958730697632, "5.0": 1.3185763359069824, "5.5": 1.2204861640930176, "6.0": 1.2560763359069824, "6.5": 1.2300347089767456, "7.0": 1.3272569179534912, "7.5": 1.2712674140930176, "8.0": 53.73698043823242, "8.5": 41.09201431274414, "9.0": 1.4053819179534912, "9.5": 2.1653645038604736, "10.0": 2.049913167953491, "10.5": 3.0455729961395264, "11.0": 3.952691078186035, "11.5": 3.516493082046509}, "eDZVdZvmG74.3.mp4": {"0.5": 255.0, "1.0": 55.16362762451172, "1.5": 0.6588541865348816, "2.0": 0.3706597089767456, "2.5": 1.2274305820465088, "3.0": 0.8337673544883728, "3.5": 0.72265625, "4.0": 58.00260543823242, "4.5": 0.6883680820465088, "5.0": 1.2847222089767456, "5.5": 19.94921875, "6.0": 30.43706512451172, "6.5": 30.79557228088379, "7.0": 37.640625, "7.5": 50.56770706176758, "8.0": 25.12890625, "8.5": 3.019965171813965, "9.0": 1.5047743320465088, "9.5": 70.96875, "10.0": 1.0368924140930176, "10.5": 0.5559895634651184, "11.0": 0.5607638955116272, "11.5": 0.3862847089767456, "12.0": 0.2664930522441864, "12.5": 0.1935763955116272, "13.0": 0.1805555522441864, "13.5": 0.1827256977558136, "14.0": 0.1336805522441864, "14.5": 68.88237762451172, "15.0": 8.46701431274414, "15.5": 1.6388888359069824, "16.0": 0.8559027910232544, "16.5": 0.3676215410232544, "17.0": 0.5147569179534912, "17.5": 0.3875868022441864, "18.0": 0.3146701455116272, "18.5": 59.31119918823242, "19.0": 0.3133680522441864, "19.5": 0.3033854067325592, "20.0": 0.3697916567325592, "20.5": 0.6640625, "21.0": 83.57378387451172}, "eairs2hMa3I.31.mp4": {"0.5": 255.0, "1.0": 54.23654556274414, "1.5": 9.29774284362793, "2.0": 4.018229007720947, "2.5": 3.2009549140930176, "3.0": 4.66015625, "3.5": 22.29904556274414, "4.0": 31.58289909362793, "4.5": 9.385416984558105, "5.0": 9.238715171813965, "5.5": 9.002604484558105, "6.0": 55.69965362548828, "6.5": 58.82551956176758, "7.0": 34.16015625, "7.5": 27.33767318725586, "8.0": 15.37022590637207, "8.5": 15.6640625, "9.0": 15.99045181274414, "9.5": 14.09592056274414, "10.0": 6.555989742279053, "10.5": 14.2109375, "11.0": 35.38064193725586, "11.5": 11.68055534362793, "12.0": 11.362847328186035, "12.5": 10.961371421813965, "13.0": 10.61024284362793, "13.5": 10.24609375, "14.0": 10.038628578186035, "14.5": 9.783854484558105, "15.0": 67.51779174804688, "15.5": 17.1796875}, "ecilxRa6m5g.4.mp4": {"0.5": 255.0, "1.0": 3.4765625, "1.5": 2.004774332046509, "2.0": 10.958333015441895, "2.5": 6.577691078186035, "3.0": 4.873263835906982, "3.5": 14.648003578186035, "4.0": 4.35069465637207, "4.5": 8.48828125, "5.0": 6.93663215637207, "5.5": 3.550347328186035, "6.0": 20.99826431274414, "6.5": 5.13194465637207, "7.0": 4.829861164093018, "7.5": 8.180121421813965, "8.0": 6.454861164093018, "8.5": 4.358941078186035, "9.0": 4.305121421813965, "9.5": 2.733506917953491, "10.0": 51.23698043823242, "10.5": 29.25824737548828, "11.0": 31.734375, "11.5": 31.42838478088379, "12.0": 14.71657943725586}, "fRqJsYel7V0.15.mp4": {"0.5": 255.0, "1.0": 45.55121612548828, "1.5": 50.42230987548828, "2.0": 48.40364456176758, "2.5": 42.98828125, "3.0": 50.34678649902344, "3.5": 28.92708396911621, "4.0": 36.36024475097656, "4.5": 32.98698043823242, "5.0": 101.29513549804688, "5.5": 46.15060806274414, "6.0": 25.58159637451172, "6.5": 42.50347137451172, "7.0": 28.1171875, "7.5": 21.17447853088379, "8.0": 25.39800262451172, "8.5": 81.29036712646484, "9.0": 2.528211832046509, "9.5": 65.46267700195312, "10.0": 42.15928649902344, "10.5": 37.88888931274414, "11.0": 37.4609375, "11.5": 54.62760543823242, "12.0": 24.39670181274414, "12.5": 23.16362762451172, "13.0": 25.95659637451172}, "foGfkZjCHm0.21.mp4": {"0.5": 255.0, "1.0": 3.6432292461395264, "1.5": 41.45269012451172, "2.0": 4.069010257720947, "2.5": 1.9665799140930176, "3.0": 1.0963541269302368, "3.5": 35.68663024902344, "4.0": 12.23524284362793, "4.5": 11.004340171813965, "5.0": 8.84201431274414, "5.5": 4.366753578186035, "6.0": 3.824652671813965, "6.5": 62.734375, "7.0": 1.92578125, "7.5": 1.1232638359069824, "8.0": 0.5703125, "8.5": 0.8485243320465088, "9.0": 0.8997395634651184, "9.5": 1.0577256679534912, "10.0": 0.5677083134651184, "10.5": 1.0377603769302368, "11.0": 0.9431423544883728, "11.5": 2.06640625, "12.0": 0.6614583134651184, "12.5": 2.995225667953491, "13.0": 1.1961805820465088, "13.5": 1.8624131679534912, "14.0": 2.00390625, "14.5": 2.85546875, "15.0": 1.3871527910232544, "15.5": 1.1111111640930176, "16.0": 1.171875, "16.5": 1.2248263359069824, "17.0": 1.3532986640930176, "17.5": 1.1345486640930176, "18.0": 1.3233506679534912, "18.5": 1.3233506679534912, "19.0": 0.4835069477558136, "19.5": 1.1410590410232544}, "g5esQcMBxek.10.mp4": {"0.5": 255.0, "1.0": 42.67838668823242, "1.5": 2.6484375, "2.0": 1.6046006679534912, "2.5": 2.434027671813965, "3.0": 0.9574652910232544, "3.5": 0.9071180820465088, "4.0": 54.12847137451172, "4.5": 2.51171875, "5.0": 7.189670085906982, "5.5": 8.653645515441895, "6.0": 80.60546875, "6.5": 12.01649284362793, "7.0": 46.27170181274414, "7.5": 5.873697757720947, "8.0": 33.25130081176758, "8.5": 29.25737762451172, "9.0": 16.00303840637207, "9.5": 11.979166984558105, "10.0": 27.36154556274414, "10.5": 40.87239456176758, "11.0": 28.41275978088379, "11.5": 57.41276168823242, "12.0": 5.71571159362793, "12.5": 64.02040100097656, "13.0": 21.30555534362793, "13.5": 11.38845443725586}, "gBBS5XqzOu4.65.mp4": {"0.5": 255.0, "1.0": 0.0603298619389534, "1.5": 0.0512152761220932, "2.0": 0.0434027761220932, "2.5": 0.078125, "3.0": 53.24088668823242, "3.5": 36.75043487548828, "4.0": 51.71527862548828, "4.5": 2.6875, "5.0": 2.667100667953491, "5.5": 2.637152671813965, "6.0": 2.71875, "6.5": 2.5768229961395264, "7.0": 44.27083206176758, "7.5": 34.35373306274414, "8.0": 36.58203125, "8.5": 2.847222328186035, "9.0": 2.820746421813965, "9.5": 2.7712674140930176, "10.0": 17.35894012451172, "10.5": 37.87977600097656, "11.0": 53.41276168823242, "11.5": 2.596788167953491, "12.0": 2.476128578186035, "12.5": 2.5256075859069824, "13.0": 2.91015625, "13.5": 58.30295181274414}, "gCtvzTSzMxY.7.mp4": {"0.5": 255.0, "1.0": 0.1241319477558136, "1.5": 0.2161458283662796, "2.0": 0.3728298544883728, "2.5": 0.2703993022441864, "3.0": 0.4461805522441864, "3.5": 0.2751736044883728, "4.0": 0.5251736044883728, "4.5": 0.8897569179534912, "5.0": 0.7222222089767456, "5.5": 0.7625868320465088, "6.0": 60.63368225097656, "6.5": 16.59505271911621, "7.0": 1.5138888359069824, "7.5": 117.46397399902344, "8.0": 2.23046875, "8.5": 2.831163167953491, "9.0": 10.674479484558105, "9.5": 15.516058921813965, "10.0": 17.75607681274414, "10.5": 12.738715171813965, "11.0": 13.381510734558105, "11.5": 11.975260734558105}, "gw3w9MLxihk.5.mp4": {"0.5": 255.0, "1.0": 4.245659828186035, "1.5": 2.225694417953491, "2.0": 6.583767414093018, "2.5": 3.535590171813965, "3.0": 3.73046875, "3.5": 4.131076335906982, "4.0": 1.5407986640930176, "4.5": 66.63628387451172, "5.0": 6.874566078186035, "5.5": 6.78211784362793, "6.0": 3.9661457538604736, "6.5": 54.47526168823242, "7.0": 34.29861068725586, "7.5": 36.18359375, "8.0": 29.65625, "8.5": 42.94314193725586, "9.0": 37.71397399902344, "9.5": 32.21050262451172, "10.0": 35.31597137451172, "10.5": 52.45529556274414, "11.0": 21.27907943725586, "11.5": 16.91059112548828, "12.0": 18.13758659362793, "12.5": 53.40277862548828}, "h4I8LFOlnOY.4.mp4": {"0.5": 255.0, "1.0": 23.44097137451172, "1.5": 6.024739742279053, "2.0": 3.312933921813965, "2.5": 3.085503578186035, "3.0": 3.0559895038604736, "3.5": 3.121961832046509, "4.0": 5.446614742279053, "4.5": 26.62109375, "5.0": 22.62717056274414, "5.5": 2.374566078186035, "6.0": 2.319444417953491, "6.5": 2.3671875, "7.0": 2.335503578186035, "7.5": 2.352430582046509, "8.0": 2.344618082046509, "8.5": 14.343316078186035, "9.0": 17.29817771911621, "9.5": 3.3415799140930176, "10.0": 2.9010417461395264, "10.5": 2.9375, "11.0": 2.7630207538604736, "11.5": 21.83724021911621, "12.0": 82.45095825195312}, "h9ubQQpBWTE.1.mp4": {"0.5": 255.0, "1.0": 61.50737762451172, "1.5": 1.6427950859069824, "2.0": 1.2565103769302368, "2.5": 63.21614456176758, "3.0": 12.769097328186035, "3.5": 6.1171875, "4.0": 7.2890625, "4.5": 6.875433921813965, "5.0": 11.86154556274414, "5.5": 3.8415799140930176, "6.0": 9.078558921813965, "6.5": 6.86414909362793, "7.0": 4.376302242279053, "7.5": 7.152777671813965, "8.0": 40.87977600097656, "8.5": 8.011284828186035, "9.0": 6.90711784362793, "9.5": 5.646267414093018, "10.0": 10.22092056274414, "10.5": 4.33203125, "11.0": 51.58333206176758, "11.5": 30.77647590637207, "12.0": 45.21961975097656, "12.5": 41.27387237548828, "13.0": 33.22352600097656, "13.5": 40.296875, "14.0": 33.84852600097656, "14.5": 28.75477409362793, "15.0": 33.43098831176758, "15.5": 66.13237762451172, "16.0": 4.740885257720947, "16.5": 2.988715171813965, "17.0": 2.0568575859069824, "17.5": 2.991319417953491, "18.0": 1.1397569179534912, "18.5": 1.5807291269302368, "19.0": 59.23176956176758}, "iU6Vq2pBI-I.59.mp4": {"0.5": 255.0, "1.0": 7.456597328186035, "1.5": 11.9140625, "2.0": 7.16883659362793, "2.5": 7.123697757720947, "3.0": 8.3046875, "3.5": 7.94227409362793, "4.0": 11.96267318725586, "4.5": 9.97569465637207, "5.0": 2.188368082046509, "5.5": 8.546441078186035, "6.0": 13.049479484558105, "6.5": 8.615885734558105, "7.0": 5.24696159362793, "7.5": 12.371527671813965, "8.0": 7.36805534362793, "8.5": 3.7174479961395264, "9.0": 4.437933921813965, "9.5": 5.333333492279053, "10.0": 3.070746421813965, "10.5": 2.6111111640930176, "11.0": 9.723958015441895, "11.5": 28.95225715637207, "12.0": 4.056423664093018, "12.5": 5.154513835906982, "13.0": 9.38194465637207, "13.5": 9.558159828186035, "14.0": 39.19184112548828, "14.5": 12.013020515441895, "15.0": 10.259114265441895, "15.5": 10.95225715637207, "16.0": 3.9466145038604736, "16.5": 10.976128578186035, "17.0": 9.66493034362793, "17.5": 3.3828125, "18.0": 4.873263835906982, "18.5": 7.128472328186035, "19.0": 5.814670085906982, "19.5": 11.65321159362793, "20.0": 9.30946159362793, "20.5": 3.206163167953491, "21.0": 4.053385257720947, "21.5": 4.40321159362793, "22.0": 9.90321159362793, "22.5": 8.966145515441895, "23.0": 74.46744537353516, "23.5": 7.766058921813965, "24.0": 3.659288167953491, "24.5": 8.308159828186035, "25.0": 4.95703125, "25.5": 9.063801765441895, "26.0": 51.87803649902344, "26.5": 1.9670138359069824, "27.0": 3.734375, "27.5": 4.80772590637207, "28.0": 3.6345486640930176}, "iz6G1svj7Ng.4.mp4": {"0.5": 255.0, "1.0": 0.0681423619389534, "1.5": 0.0173611119389534, "2.0": 14.21657943725586, "2.5": 42.76605987548828, "3.0": 10.476996421813965, "3.5": 4.802517414093018, "4.0": 29.84461784362793, "4.5": 8.97960090637207, "5.0": 31.54817771911621, "5.5": 25.69487762451172, "6.0": 52.26171875, "6.5": 11.640191078186035, "7.0": 45.55642318725586, "7.5": 1.4236111640930176, "8.0": 0.1875, "8.5": 0.7634548544883728, "9.0": 16.91145896911621, "9.5": 0.2009548544883728, "10.0": 0.1189236119389534, "10.5": 0.0698784738779068, "11.0": 0.0889756977558136, "11.5": 0.6944444179534912, "12.0": 2.722222328186035, "12.5": 0.4474826455116272}, "k7OQGhkYkqs.1.mp4": {"0.5": 255.0, "1.0": 6.10460090637207, "1.5": 7.09071159362793, "2.0": 8.07899284362793, "2.5": 8.60373306274414, "3.0": 99.80902862548828, "3.5": 183.39149475097656, "4.0": 17.47612762451172, "4.5": 6.737847328186035, "5.0": 10.577691078186035, "5.5": 1.3524305820465088, "6.0": 1.3350694179534912, "6.5": 1.3498263359069824, "7.0": 1.3064236640930176, "7.5": 1.2686631679534912, "8.0": 127.58637237548828, "8.5": 15.73133659362793, "9.0": 12.006510734558105, "9.5": 13.02734375, "10.0": 18.47048568725586, "10.5": 24.40538215637207, "11.0": 14.273871421813965, "11.5": 15.21875}, "lIS3_02wKZM.8.mp4": {"0.5": 255.0, "1.0": 8.636284828186035, "1.5": 5.071614742279053, "2.0": 5.454427242279053, "2.5": 52.33159637451172, "3.0": 3.694878578186035, "3.5": 2.075086832046509, "4.0": 1.3172743320465088, "4.5": 1.8910590410232544, "5.0": 1.8971353769302368, "5.5": 2.5681424140930176, "6.0": 66.28775787353516, "6.5": 8.02560806274414, "7.0": 4.322048664093018, "7.5": 7.013888835906982, "8.0": 8.101128578186035, "8.5": 5.558159828186035, "9.0": 58.68836975097656, "9.5": 3.254774332046509, "10.0": 2.223524332046509, "10.5": 6.384114742279053, "11.0": 6.534722328186035, "11.5": 2.7209200859069824, "12.0": 2.675347328186035, "12.5": 76.00521087646484, "13.0": 9.76085090637207, "13.5": 12.36154556274414, "14.0": 21.43272590637207, "14.5": 25.30295181274414, "15.0": 25.09982681274414, "15.5": 21.33463478088379, "16.0": 66.11631774902344, "16.5": 2.7248263359069824, "17.0": 5.981770992279053, "17.5": 4.971354007720947, "18.0": 5.236111164093018, "18.5": 4.333333492279053, "19.0": 4.216579914093018}, "m4sWjNlPlVs.43.mp4": {"0.5": 255.0, "1.0": 6.845920085906982, "1.5": 2.891493082046509, "2.0": 21.27387237548828, "2.5": 24.08203125, "3.0": 24.11111068725586, "3.5": 33.65798568725586, "4.0": 17.80121612548828, "4.5": 15.75607681274414, "5.0": 14.440972328186035, "5.5": 11.94140625, "6.0": 42.06684112548828, "6.5": 12.26649284362793, "7.0": 10.17881965637207, "7.5": 9.430989265441895, "8.0": 12.451823234558105, "8.5": 17.09461784362793, "9.0": 4.584635257720947, "9.5": 1.5008680820465088, "10.0": 18.33940887451172, "10.5": 17.98220443725586, "11.0": 41.60807418823242, "11.5": 8.44227409362793, "12.0": 7.716579914093018, "12.5": 5.70225715637207, "13.0": 6.009114742279053, "13.5": 7.34852409362793, "14.0": 17.83333396911621, "14.5": 19.45355987548828, "15.0": 13.940104484558105, "15.5": 15.47439193725586, "16.0": 28.46050262451172, "16.5": 35.90451431274414, "17.0": 15.805989265441895, "17.5": 12.736979484558105, "18.0": 19.95138931274414, "18.5": 12.34765625, "19.0": 21.59244728088379, "19.5": 14.5, "20.0": 8.61111068725586, "20.5": 11.40017318725586, "21.0": 16.39930534362793, "21.5": 8.015625, "22.0": 6.539496421813965, "22.5": 21.68229103088379, "23.0": 18.54947853088379, "23.5": 9.94835090637207, "24.0": 37.30121612548828, "24.5": 32.86198043823242, "25.0": 9.03298568725586, "25.5": 11.234375, "26.0": 13.07986068725586, "26.5": 14.74001693725586, "27.0": 13.38671875, "27.5": 9.085503578186035, "28.0": 16.86371612548828, "28.5": 28.83246612548828, "29.0": 18.73784637451172, "29.5": 12.611979484558105, "30.0": 10.19704818725586, "30.5": 15.66796875, "31.0": 11.53689193725586, "31.5": 8.01171875, "32.0": 20.59678840637207, "32.5": 26.67751693725586, "33.0": 11.88671875, "33.5": 5.76866340637207, "34.0": 13.45486068725586, "34.5": 44.12760543823242, "35.0": 31.81032943725586}, "mtP9H7AClk8.7.mp4": {"0.5": 255.0, "1.0": 6.669270992279053, "1.5": 58.57291793823242, "2.0": 27.40364646911621, "2.5": 4.982204914093018, "3.0": 30.36979103088379, "3.5": 6.63671875, "4.0": 1.7938368320465088, "4.5": 42.98307418823242, "5.0": 41.50520706176758, "5.5": 5.181857585906982, "6.0": 3.94921875, "6.5": 2.28125, "7.0": 63.04383850097656, "7.5": 0.0177951380610466, "8.0": 64.33246612548828, "8.5": 0.0360243059694767, "9.0": 45.60329818725586, "9.5": 57.35459899902344, "10.0": 0.03081597201526165, "10.5": 69.90190887451172, "11.0": 0.0503472238779068, "11.5": 0.00737847201526165, "12.0": 48.83376693725586}, "muUcz5aYTMs.3.mp4": {"0.5": 255.0, "1.0": 3.7795138359069824, "1.5": 2.2517361640930176, "2.0": 190.34722900390625, "2.5": 6.490451335906982, "3.0": 10.464409828186035, "3.5": 14.159722328186035, "4.0": 3.967881917953491, "4.5": 1.5052083730697632, "5.0": 3.440972328186035, "5.5": 7.08116340637207, "6.0": 1.2035590410232544, "6.5": 0.2252604216337204, "7.0": 0.1202256977558136, "7.5": 0.0881076380610466, "8.0": 4.154947757720947, "8.5": 2.311631917953491, "9.0": 0.8828125, "9.5": 7.137152671813965, "10.0": 0.8116319179534912, "10.5": 0.0750868022441864, "11.0": 178.32464599609375, "11.5": 2.894097328186035, "12.0": 4.257378578186035}, "my2KTkgATPU.34.mp4": {"0.5": 255.0, "1.0": 24.53385353088379, "1.5": 51.28602600097656, "2.0": 17.77256965637207, "2.5": 69.52040100097656, "3.0": 15.65407943725586, "3.5": 32.69661331176758, "4.0": 15.80295181274414, "4.5": 20.82204818725586, "5.0": 18.53081512451172, "5.5": 19.60677146911621, "6.0": 25.18793487548828, "6.5": 28.22352409362793, "7.0": 17.82335090637207, "7.5": 22.02994728088379, "8.0": 29.39322853088379, "8.5": 27.53645896911621, "9.0": 33.56987762451172, "9.5": 20.97178840637207, "10.0": 27.52647590637207, "10.5": 24.87239646911621, "11.0": 56.65017318725586, "11.5": 58.61154556274414, "12.0": 42.92448043823242, "12.5": 35.46571350097656, "13.0": 26.80685806274414, "13.5": 25.44140625, "14.0": 27.99869728088379, "14.5": 16.30381965637207, "15.0": 39.64453125, "15.5": 46.76258850097656, "16.0": 33.56076431274414, "16.5": 36.44227600097656, "17.0": 29.19270896911621, "17.5": 25.8828125, "18.0": 30.03819465637207, "18.5": 29.52951431274414, "19.0": 17.03125}, "nTGjRCHPHYw.10.mp4": {"0.5": 255.0, "1.0": 6.33116340637207, "1.5": 6.016058921813965, "2.0": 5.88671875, "2.5": 5.453125, "3.0": 5.467013835906982, "3.5": 5.302517414093018, "4.0": 5.149739742279053, "4.5": 5.158854007720947, "5.0": 4.784722328186035, "5.5": 4.237847328186035, "6.0": 4.132378578186035, "6.5": 3.5881075859069824, "7.0": 3.230902671813965, "7.5": 2.684461832046509, "8.0": 2.2013888359069824, "8.5": 10.93836784362793, "9.0": 37.17013931274414, "9.5": 4.503472328186035, "10.0": 2.9700520038604736, "10.5": 4.660590171813965, "11.0": 3.1423611640930176, "11.5": 4.427083492279053, "12.0": 4.187933921813965, "12.5": 57.63758850097656, "13.0": 12.28515625, "13.5": 12.915364265441895, "14.0": 12.549479484558105, "14.5": 11.77560806274414, "15.0": 10.10850715637207, "15.5": 7.771267414093018, "16.0": 5.693576335906982, "16.5": 3.823784828186035, "17.0": 1.9001736640930176, "17.5": 1.2556424140930176, "18.0": 0.6657986044883728, "18.5": 59.20790100097656, "19.0": 3.7018229961395264, "19.5": 2.0407986640930176, "20.0": 0.9587673544883728, "20.5": 0.9887152910232544, "21.0": 0.9223090410232544, "21.5": 1.6180555820465088, "22.0": 3.422308921813965}, "rM-QRSeVngo.1.mp4": {"0.5": 255.0, "1.0": 4.635416507720947, "1.5": 6.076388835906982, "2.0": 7.651041507720947, "2.5": 7.026909828186035, "3.0": 7.9921875, "3.5": 5.536892414093018, "4.0": 60.34505081176758, "4.5": 2.148003578186035, "5.0": 1.7261284589767456, "5.5": 1.9097222089767456, "6.0": 1.5559896230697632, "6.5": 2.620659828186035, "7.0": 2.1393229961395264, "7.5": 64.359375, "8.0": 45.39800262451172, "8.5": 2.418402671813965, "9.0": 2.180121421813965, "9.5": 2.22265625, "10.0": 2.5221354961395264, "10.5": 2.4791667461395264, "11.0": 2.553819417953491, "11.5": 2.730034828186035, "12.0": 2.663194417953491, "12.5": 2.3098957538604736, "13.0": 52.71744918823242, "13.5": 2.0208332538604736, "14.0": 1.7595486640930176, "14.5": 1.6427950859069824, "15.0": 1.9830728769302368, "15.5": 1.5611978769302368, "16.0": 1.4092881679534912, "16.5": 1.2469618320465088, "17.0": 1.1675347089767456, "17.5": 2.0182292461395264, "18.0": 2.597222328186035, "18.5": 1.2864583730697632, "19.0": 1.1393228769302368, "19.5": 2.602430582046509, "20.0": 2.0529513359069824, "20.5": 2.5876736640930176}, "rwbhbyWCR2U.8.mp4": {"0.5": 255.0, "1.0": 40.83203125, "1.5": 50.13845443725586, "2.0": 5.772135257720947, "2.5": 5.641927242279053, "3.0": 0.0859375, "3.5": 0.013888888992369175, "4.0": 0.00390625, "4.5": 0.0017361111240461469, "5.0": 0.0, "5.5": 20.19140625, "6.0": 0.0, "6.5": 0.0, "7.0": 0.1545138955116272, "7.5": 0.0017361111240461469, "8.0": 0.002170138992369175, "8.5": 0.0, "9.0": 0.0, "9.5": 0.0, "10.0": 0.0, "10.5": 0.0008680555620230734, "11.0": 0.0008680555620230734, "11.5": 19.58724021911621, "12.0": 0.9457465410232544, "12.5": 0.1497395783662796, "13.0": 0.0763888880610466, "13.5": 0.0386284738779068, "14.0": 0.00737847201526165, "14.5": 0.0052083334885537624, "15.0": 0.0065104165114462376, "15.5": 0.0}}
|
paper_experiments/caches/web_base.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
paper_experiments/caches/web_comp.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
paper_experiments/caches/web_exh.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
paper_experiments/caches/web_fs.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
paper_experiments/caches/web_fs_alt.jsonl
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
paper_experiments/data/gold_holdout.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
[{"video": "6ureBD2VY1c.3.mp4", "question_id": "6ureBD2VY1c.3.mp4", "answer": [{"role": "assistant", "content": "A woman is speaking in front of the camera, and a man is taking photos.", "related_timespan": [0.0, 10.24], "reply_timespan": [0.0, 11.24]}, {"role": "assistant", "content": "A woman and a man are posing in front of a movie poster, and a woman is standing in front of a white background.", "related_timespan": [10.28, 20.79], "reply_timespan": [10.28, 20.820800000000002]}], "conversation": [{"role": "user", "content": "What activities are people doing in front of the camera?", "time": 0}], "duration": 20.820800000000002}, {"video": "_KgWye11I8g.7.mp4", "question_id": "_KgWye11I8g.7.mp4", "answer": [{"role": "assistant", "content": "A black game console is displayed.", "related_timespan": [0.0, 7.51], "reply_timespan": [0.0, 8.51]}, {"role": "assistant", "content": "A black game console is displayed on the screen.", "related_timespan": [7.57, 13.88], "reply_timespan": [7.57, 13.9139]}], "conversation": [{"role": "user", "content": "What is being displayed in the video?", "time": 0}], "duration": 13.9139}, {"video": "m4sWjNlPlVs.43.mp4", "question_id": "m4sWjNlPlVs.43.mp4", "answer": [{"role": "assistant", "content": "The person is showing a purple band while the piece of paper is mentioned to be in the background.", "related_timespan": [0.0, 5.38], "reply_timespan": [0.0, 6.38]}, {"role": "assistant", "content": "A person is pointing to a piece of paper.", "related_timespan": [5.42, 10.68], "reply_timespan": [5.42, 11.68]}, {"role": "assistant", "content": "The person is drawing, unfolding, and then folding and patting the piece of paper.", "related_timespan": [10.72, 34.99], "reply_timespan": [10.72, 35.035]}], "conversation": [{"role": "user", "content": "What is the person doing with the piece of paper?", "time": 0}], "duration": 35.035}, {"video": "GzD9IDWLodk.15.mp4", "question_id": "GzD9IDWLodk.15.mp4", "answer": [{"role": "assistant", "content": "The man is looking at and talking to the camera.", "related_timespan": [0.0, 10.51], "reply_timespan": [0.0, 10.543866666666666]}], "conversation": [{"role": "user", "content": "What is the man doing?", "time": 0}], "duration": 10.543866666666666}, {"video": "2ZcsoP1AUTI.6.mp4", "question_id": "2ZcsoP1AUTI.6.mp4", "answer": [{"role": "assistant", "content": "Machines are spraying white smoke onto an airplane.", "related_timespan": [0.0, 8.74], "reply_timespan": [0.0, 9.74]}, {"role": "assistant", "content": "A man in a safety vest checks a car.", "related_timespan": [16.75, 22.39], "reply_timespan": [16.75, 22.4224]}], "conversation": [{"role": "user", "content": "What activity is being carried out on or near vehicles?", "time": 0}], "duration": 22.4224}, {"video": "bTMVVjv5zQ0.2.mp4", "question_id": "bTMVVjv5zQ0.2.mp4", "answer": [{"role": "assistant", "content": "A man in a dark brown overcoat holds a gun in his hand and aims it into the distance in the forest.", "related_timespan": [0.0, 2.04], "reply_timespan": [0.0, 3.04]}, {"role": "assistant", "content": "The man is sprawling on the ground in the woods, surrounded by a bear sniffing his body.", "related_timespan": [2.09, 3.75], "reply_timespan": [2.09, 4.75]}, {"role": "assistant", "content": "The man is lying on the ground with a bear seemingly about to attack him.", "related_timespan": [3.8, 5.71], "reply_timespan": [3.8, 6.71]}, {"role": "assistant", "content": "The man on the ground is holding a gun up to a bear walking toward him.", "related_timespan": [5.76, 7.22], "reply_timespan": [5.76, 8.219999999999999]}, {"role": "assistant", "content": "The man is lying on the ground with many wounds and blood, with a bear standing near him.", "related_timespan": [7.26, 8.72], "reply_timespan": [7.26, 9.72]}], "conversation": [{"role": "user", "content": "What is happening with the man in the forest?", "time": 0}], "duration": 12.554208333333332}, {"video": "CJorIIaGAYI.5.mp4", "question_id": "CJorIIaGAYI.5.mp4", "answer": [{"role": "assistant", "content": "The woman in a jersey with glasses and twists is dancing with a person wearing a huge baseball-like headgear and is holding a bouquet of flowers.", "related_timespan": [5.37, 14.11], "reply_timespan": [5.37, 14.147466666666666]}], "conversation": [{"role": "user", "content": "What is happening with the woman in a jersey with glasses and twists?", "time": 0}], "duration": 14.147466666666666}, {"video": "Gw1fS2Nze5s.1.mp4", "question_id": "Gw1fS2Nze5s.1.mp4", "answer": [{"role": "assistant", "content": "Player number 30 throws the ball into the basket.", "related_timespan": [8.2, 12.96], "reply_timespan": [8.2, 13.96]}, {"role": "assistant", "content": "Player number 30 throws the ball to the basket.", "related_timespan": [13.0, 18.72], "reply_timespan": [13.0, 18.76]}], "conversation": [{"role": "user", "content": "What notable action is player number 30 doing during the game?", "time": 0}], "duration": 18.76}, {"video": "2l9I3UhWP10.8.mp4", "question_id": "2l9I3UhWP10.8.mp4", "answer": [{"role": "assistant", "content": "The man is holding a box of Switch games and later a screenshot covers it.", "related_timespan": [0.0, 3.94], "reply_timespan": [0.0, 4.9399999999999995]}, {"role": "assistant", "content": "The man is holding a box of Switch games and patting it while talking to the camera.", "related_timespan": [9.74, 14.41], "reply_timespan": [9.74, 15.41]}, {"role": "assistant", "content": "The man is holding a box of Switch games on the beach.", "related_timespan": [14.45, 22.19], "reply_timespan": [14.45, 22.2222]}], "conversation": [{"role": "user", "content": "What is the man doing with the Switch game box?", "time": 0}], "duration": 22.2222}, {"video": "8VR6Ao97vNc.1.mp4", "question_id": "8VR6Ao97vNc.1.mp4", "answer": [{"role": "assistant", "content": "The video features the Apple Music logo.", "related_timespan": [0.0, 9.18], "reply_timespan": [0.0, 10.18]}], "conversation": [{"role": "user", "content": "What is featured prominently in the video?", "time": 0}], "duration": 11.553208333333332}, {"video": "FL9WvBWs73I.18.mp4", "question_id": "FL9WvBWs73I.18.mp4", "answer": [{"role": "assistant", "content": "The person is unrolling the turf.", "related_timespan": [0.0, 5.92], "reply_timespan": [0.0, 6.92]}, {"role": "assistant", "content": "The man butts the turf to the edge and taps it down.", "related_timespan": [5.96, 10.44], "reply_timespan": [5.96, 11.44]}, {"role": "assistant", "content": "The man is taping the turf down.", "related_timespan": [10.48, 12.64], "reply_timespan": [10.48, 13.64]}, {"role": "assistant", "content": "The man taps the turf down and uncurls the edge.", "related_timespan": [12.68, 21.36], "reply_timespan": [12.68, 22.36]}, {"role": "assistant", "content": "The man gets another turf.", "related_timespan": [21.4, 24.44], "reply_timespan": [21.4, 24.48]}], "conversation": [{"role": "user", "content": "What is the man doing with the turf?", "time": 0}], "duration": 24.48}, {"video": "aGu1HuPl3Gs.1.mp4", "question_id": "aGu1HuPl3Gs.1.mp4", "answer": [{"role": "assistant", "content": "The players can judge the squad rating according to the number of stars.", "related_timespan": [0.0, 1.23], "reply_timespan": [0.0, 2.23]}, {"role": "assistant", "content": "The interface shows a position layout diagram and relevant information about each player, including nationality.", "related_timespan": [6.17, 14.25], "reply_timespan": [6.17, 14.280933333333333]}], "conversation": [{"role": "user", "content": "What information is presented on the video game interface?", "time": 0}], "duration": 14.280933333333333}, {"video": "2FmQgQW8clw.1.mp4", "question_id": "2FmQgQW8clw.1.mp4", "answer": [{"role": "assistant", "content": "The woman is walking her dog in a park.", "related_timespan": [0.0, 1.84], "reply_timespan": [0.0, 2.84]}, {"role": "assistant", "content": "The woman is leading her dog up the steps on a leash.", "related_timespan": [1.88, 4.48], "reply_timespan": [1.88, 5.48]}], "conversation": [{"role": "user", "content": "What is the woman doing with her dog?", "time": 0}], "duration": 12.44}, {"video": "Kd3vC0lL9C4.57.mp4", "question_id": "Kd3vC0lL9C4.57.mp4", "answer": [{"role": "assistant", "content": "A person is wearing a white full-wrap helmet.", "related_timespan": [0.0, 2.2], "reply_timespan": [0.0, 3.2]}, {"role": "assistant", "content": "A person is wearing a yellow full-wrap helmet.", "related_timespan": [2.24, 4.37], "reply_timespan": [2.24, 5.37]}, {"role": "assistant", "content": "A person is wearing a blue full-wrap shark-shaped helmet.", "related_timespan": [4.4, 8.04], "reply_timespan": [4.4, 9.04]}, {"role": "assistant", "content": "People are wearing jumpsuits and helmets in white, purple, red, yellow, black, and gray.", "related_timespan": [11.95, 14.78], "reply_timespan": [11.95, 14.8148]}], "conversation": [{"role": "user", "content": "What are the people wearing?", "time": 0}], "duration": 14.8148}, {"video": "E2WelDikd4s.50.mp4", "question_id": "E2WelDikd4s.50.mp4", "answer": [{"role": "assistant", "content": "Green mechanical vehicles on an outdoor lawn, with water and buildings in the distance.", "related_timespan": [0.0, 5.44], "reply_timespan": [0.0, 6.44]}, {"role": "assistant", "content": "Disney's castle with fireworks in the evening sky.", "related_timespan": [5.47, 12.11], "reply_timespan": [5.47, 13.11]}, {"role": "assistant", "content": "Disney Castle with a blue sky and white clouds in the background.", "related_timespan": [12.15, 18.95], "reply_timespan": [12.15, 18.985633333333332]}], "conversation": [{"role": "user", "content": "What is shown in the photos?", "time": 0}], "duration": 18.985633333333332}, {"video": "lIS3_02wKZM.8.mp4", "question_id": "lIS3_02wKZM.8.mp4", "answer": [{"role": "assistant", "content": "The woman in a blue blouse says something to the left.", "related_timespan": [0.0, 1.94], "reply_timespan": [0.0, 2.94]}, {"role": "assistant", "content": "The woman in a blue blouse is busy with something at a table.", "related_timespan": [12.1, 15.51], "reply_timespan": [12.1, 16.509999999999998]}], "conversation": [{"role": "user", "content": "What is the woman in the blue blouse doing?", "time": 0}], "duration": 19.153203342618383}, {"video": "4vZ7NszztMs.9.mp4", "question_id": "4vZ7NszztMs.9.mp4", "answer": [{"role": "assistant", "content": "Basketball.", "related_timespan": [0.0, 14.45], "reply_timespan": [0.0, 14.481133333333334]}], "conversation": [{"role": "user", "content": "What sport is being discussed?", "time": 0}], "duration": 14.481133333333334}, {"video": "h9ubQQpBWTE.1.mp4", "question_id": "h9ubQQpBWTE.1.mp4", "answer": [{"role": "assistant", "content": "Three people sit at a table in front of microphones. The right man talks with a laptop in front of him.", "related_timespan": [0.0, 2.07], "reply_timespan": [0.0, 3.07]}, {"role": "assistant", "content": "Three people sit at a table in front of microphones. The woman talks with her hands in front of her chest.", "related_timespan": [14.78, 18.92], "reply_timespan": [14.78, 18.952266666666667]}], "conversation": [{"role": "user", "content": "What is happening at the table?", "time": 0}], "duration": 18.952266666666667}, {"video": "CJ-ER0GdJd8.36.mp4", "question_id": "CJ-ER0GdJd8.36.mp4", "answer": [{"role": "assistant", "content": "The video shows a busy street in a city with many shops and restaurants, with people walking and some on motorcycles.", "related_timespan": [0.0, 6.29], "reply_timespan": [0.0, 7.29]}, {"role": "assistant", "content": "The video shows a busy street in a city with shops and restaurants, motorcycles, and people walking and driving on the road.", "related_timespan": [8.5, 14.04], "reply_timespan": [8.5, 14.083333333333334]}], "conversation": [{"role": "user", "content": "What does the video show about the city street?", "time": 0}], "duration": 14.083333333333334}, {"video": "h4I8LFOlnOY.4.mp4", "question_id": "h4I8LFOlnOY.4.mp4", "answer": [{"role": "assistant", "content": "There is green grass, reeds, and a small wooden bridge over the water.", "related_timespan": [0.0, 4.14], "reply_timespan": [0.0, 5.14]}, {"role": "assistant", "content": "There are leaves, lotus leaves, and grass floating on the lake.", "related_timespan": [4.2, 8.31], "reply_timespan": [4.2, 9.31]}, {"role": "assistant", "content": "There are stones and fish of many colors in the lake.", "related_timespan": [8.34, 11.98], "reply_timespan": [8.34, 12.012]}], "conversation": [{"role": "user", "content": "What features can be observed in and around the water?", "time": 0}], "duration": 12.012}, {"video": "g5esQcMBxek.10.mp4", "question_id": "g5esQcMBxek.10.mp4", "answer": [{"role": "assistant", "content": "Four people are sitting on the stage and having a conversation.", "related_timespan": [0.0, 3.5], "reply_timespan": [0.0, 4.5]}, {"role": "assistant", "content": "A man in a suit and glasses sits on the stage and talks in front of a black background.", "related_timespan": [3.54, 6.44], "reply_timespan": [3.54, 7.44]}, {"role": "assistant", "content": "A man wearing a suit has a hug with a man in a black shirt, and there is a piano performance.", "related_timespan": [6.47, 10.08], "reply_timespan": [6.47, 11.08]}], "conversation": [{"role": "user", "content": "What events or activities are taking place on the stage?", "time": 0}], "duration": 13.413400000000001}, {"video": "_T2A_lgn69w.37.mp4", "question_id": "_T2A_lgn69w.37.mp4", "answer": [{"role": "assistant", "content": "The man is holding a bottle of Apple Sidra in his hand.", "related_timespan": [4.97, 12.83], "reply_timespan": [4.97, 13.83]}, {"role": "assistant", "content": "The man unscrews a bottle of Apple Sidra, pours it into a glass cup, and drinks it.", "related_timespan": [17.63, 32.4], "reply_timespan": [17.63, 32.43333333333333]}], "conversation": [{"role": "user", "content": "What does the man do with the Apple Sidra?", "time": 0}], "duration": 32.43333333333333}, {"video": "6mLBr_ZxTj4.44.mp4", "question_id": "6mLBr_ZxTj4.44.mp4", "answer": [{"role": "assistant", "content": "The dog has bumps in its ear.", "related_timespan": [2.2, 4.94], "reply_timespan": [2.2, 5.94]}, {"role": "assistant", "content": "The dog has bumps and ticks.", "related_timespan": [5.0, 7.31], "reply_timespan": [5.0, 8.309999999999999]}, {"role": "assistant", "content": "The dog has a lump on its body.", "related_timespan": [7.37, 11.28], "reply_timespan": [7.37, 11.311300000000001]}], "conversation": [{"role": "user", "content": "What issue is the dog experiencing?", "time": 0}], "duration": 11.311300000000001}, {"video": "gBBS5XqzOu4.65.mp4", "question_id": "gBBS5XqzOu4.65.mp4", "answer": [{"role": "assistant", "content": "A map of the world is being shown on the screen, and the man points to different locations on it.", "related_timespan": [7.27, 13.28], "reply_timespan": [7.27, 13.3133]}], "conversation": [{"role": "user", "content": "What is being shown on the screen?", "time": 0}], "duration": 13.3133}, {"video": "XqDwTKVeUcI.31.mp4", "question_id": "XqDwTKVeUcI.31.mp4", "answer": [{"role": "assistant", "content": "The woman is wearing a hat and sunglasses.", "related_timespan": [0.0, 1.67], "reply_timespan": [0.0, 2.67]}, {"role": "assistant", "content": "The people are dressed in old-fashioned clothing, including overalls and hats.", "related_timespan": [1.7, 4.97], "reply_timespan": [1.7, 5.97]}, {"role": "assistant", "content": "A man with a black hat and many people standing around him.", "related_timespan": [5.0, 6.03], "reply_timespan": [5.0, 7.03]}, {"role": "assistant", "content": "A man is wearing a helmet and goggles.", "related_timespan": [8.87, 10.77], "reply_timespan": [8.87, 11.77]}, {"role": "assistant", "content": "A man is in a checkered shirt and hat; another man is next to him is smoking a cigarette.", "related_timespan": [10.8, 15.9], "reply_timespan": [10.8, 15.933333333333334]}], "conversation": [{"role": "user", "content": "What kind of clothing are people wearing in the video?", "time": 0}], "duration": 15.933333333333334}, {"video": "nTGjRCHPHYw.10.mp4", "question_id": "nTGjRCHPHYw.10.mp4", "answer": [{"role": "assistant", "content": "The man is standing in front of a table with several items on it and is talking and gesturing.", "related_timespan": [8.34, 11.95], "reply_timespan": [8.34, 12.95]}, {"role": "assistant", "content": "The man is standing in front of a table with different kinds of mats on it, speaking and gesturing.", "related_timespan": [17.95, 22.19], "reply_timespan": [17.95, 22.2222]}], "conversation": [{"role": "user", "content": "What is the man doing in the video?", "time": 0}], "duration": 22.2222}, {"video": "0vklWHzRAos.9.mp4", "question_id": "0vklWHzRAos.9.mp4", "answer": [{"role": "assistant", "content": "The video game features a character card of a player's identity information.", "related_timespan": [0.0, 6.58], "reply_timespan": [0.0, 7.58]}, {"role": "assistant", "content": "The video game allows you to choose a club and shows a squad of football players with their character cards.", "related_timespan": [6.62, 10.83], "reply_timespan": [6.62, 10.875]}], "conversation": [{"role": "user", "content": "What does the video game feature?", "time": 0}], "duration": 10.875}, {"video": "EwItjBAwCbQ.37.mp4", "question_id": "EwItjBAwCbQ.37.mp4", "answer": [{"role": "assistant", "content": "The image is dimly lit.", "related_timespan": [0.0, 10.8], "reply_timespan": [0.0, 11.8]}, {"role": "assistant", "content": "The tunnel is dimly lit.", "related_timespan": [10.83, 13.77], "reply_timespan": [10.83, 14.77]}, {"role": "assistant", "content": "The background is very dim.", "related_timespan": [13.8, 16.6], "reply_timespan": [13.8, 17.6]}, {"role": "assistant", "content": "The picture is very dim.", "related_timespan": [16.63, 19.27], "reply_timespan": [16.63, 19.3]}], "conversation": [{"role": "user", "content": "How is the lighting in the scenes described in the video?", "time": 0}], "duration": 19.3}, {"video": "eDZVdZvmG74.3.mp4", "question_id": "eDZVdZvmG74.3.mp4", "answer": [{"role": "assistant", "content": "A person fills up a car with a gas pump.", "related_timespan": [0.0, 3.6], "reply_timespan": [0.0, 4.6]}, {"role": "assistant", "content": "A gas station sign and branding are shown.", "related_timespan": [3.63, 9.1], "reply_timespan": [3.63, 10.1]}, {"role": "assistant", "content": "Cars are parked and one drives away while another drives in at the gas station.", "related_timespan": [14.2, 18.2], "reply_timespan": [14.2, 19.2]}], "conversation": [{"role": "user", "content": "What happens at the gas station?", "time": 0}], "duration": 21.0}, {"video": "_BVVEW8y_VA.9.mp4", "question_id": "_BVVEW8y_VA.9.mp4", "answer": [{"role": "assistant", "content": "The girl in blue jumps from a balcony to the ground before sliding under a cart.", "related_timespan": [4.17, 7.03], "reply_timespan": [4.17, 8.030000000000001]}, {"role": "assistant", "content": "The girl in blue jumps off the ledge, and people are looking up at her.", "related_timespan": [7.07, 8.43], "reply_timespan": [7.07, 9.43]}, {"role": "assistant", "content": "The girl in blue jumps from a height and falls on a basket of oranges.", "related_timespan": [8.47, 9.63], "reply_timespan": [8.47, 10.63]}, {"role": "assistant", "content": "After hitting the basket of oranges, the girl rolls down the steps.", "related_timespan": [9.67, 11.9], "reply_timespan": [9.67, 12.9]}], "conversation": [{"role": "user", "content": "What happens when the girl in blue jumps from the height?", "time": 0}], "duration": 14.633333333333333}, {"video": "F_0Nud7Wa8w.7.mp4", "question_id": "F_0Nud7Wa8w.7.mp4", "answer": [{"role": "assistant", "content": "The woman with long blonde hair is wearing a wedding dress and standing inside a bedroom.", "related_timespan": [0.0, 3.96], "reply_timespan": [0.0, 4.96]}, {"role": "assistant", "content": "The woman with long blonde hair is sitting and talking to the camera.", "related_timespan": [4.0, 12.68], "reply_timespan": [4.0, 13.68]}, {"role": "assistant", "content": "The woman with long blonde hair is walking next to a black bench and arranging pillows.", "related_timespan": [12.72, 15.1], "reply_timespan": [12.72, 15.140125]}], "conversation": [{"role": "user", "content": "What is the woman with long blonde hair doing?", "time": 0}], "duration": 15.140125}, {"video": "gw3w9MLxihk.5.mp4", "question_id": "gw3w9MLxihk.5.mp4", "answer": [{"role": "assistant", "content": "A woman in a green T-shirt talks and sits on the lawn.", "related_timespan": [0.0, 3.84], "reply_timespan": [0.0, 4.84]}, {"role": "assistant", "content": "A man and a woman squat in a garden and eat the plant in their hands.", "related_timespan": [3.88, 5.96], "reply_timespan": [3.88, 6.96]}, {"role": "assistant", "content": "Lots of people walk in a garden next to a house.", "related_timespan": [6.01, 7.84], "reply_timespan": [6.01, 8.84]}, {"role": "assistant", "content": "A man talks with a branch of a tree in his hand surrounded by lots of people in a garden.", "related_timespan": [7.88, 10.18], "reply_timespan": [7.88, 11.18]}, {"role": "assistant", "content": "A man and a woman stand next to a tree and eat leaves in their hands.", "related_timespan": [10.22, 12.68], "reply_timespan": [10.22, 12.721041666666666]}], "conversation": [{"role": "user", "content": "What are people doing in the garden?", "time": 0}], "duration": 12.721041666666666}, {"video": "8GnsEFk5HaI.28.mp4", "question_id": "8GnsEFk5HaI.28.mp4", "answer": [{"role": "assistant", "content": "The man in a suit and tie is standing with a microphone in front of him, speaking seriously. ", "related_timespan": [8.8, 15.53], "reply_timespan": [8.8, 16.53]}, {"role": "assistant", "content": "The man in a suit and glasses is walking down the red carpet and interacting with others.", "related_timespan": [15.57, 23.93], "reply_timespan": [15.57, 24.93]}], "conversation": [{"role": "user", "content": "What is the man in a suit and tie doing?", "time": 0}], "duration": 33.6}, {"video": "3EBMpTiWAg8.11.mp4", "question_id": "3EBMpTiWAg8.11.mp4", "answer": [{"role": "assistant", "content": "A person in a black T-shirt restrains another person on the ground.", "related_timespan": [0.0, 7.57], "reply_timespan": [0.0, 8.57]}, {"role": "assistant", "content": "A man in black moves on the ground beside a person sitting in a chair.", "related_timespan": [7.61, 9.88], "reply_timespan": [7.61, 10.88]}], "conversation": [{"role": "user", "content": "What action takes place involving a person in black?", "time": 0}], "duration": 12.512500000000001}, {"video": "JljoS47Qf_s.20.mp4", "question_id": "JljoS47Qf_s.20.mp4", "answer": [{"role": "assistant", "content": "The cartoon character with blonde hair holds a stick and talks on a pink background.", "related_timespan": [0.0, 5.26], "reply_timespan": [0.0, 6.26]}, {"role": "assistant", "content": "The cartoon character with blonde hair holds a stick pointing at the screen and talks to the camera.", "related_timespan": [5.3, 9.51], "reply_timespan": [5.3, 10.51]}, {"role": "assistant", "content": "A cartoon character with blonde hair appears with a blue background.", "related_timespan": [9.55, 14.06], "reply_timespan": [9.55, 14.097416666666666]}], "conversation": [{"role": "user", "content": "What is the cartoon character with blonde hair doing?", "time": 0}], "duration": 14.097416666666666}, {"video": "iU6Vq2pBI-I.59.mp4", "question_id": "iU6Vq2pBI-I.59.mp4", "answer": [{"role": "assistant", "content": "Credit cards, specifically the citi double cash card.", "related_timespan": [0.0, 22.59], "reply_timespan": [0.0, 23.59]}, {"role": "assistant", "content": "Features of a mobile phone app related to recognizing charges on a map.", "related_timespan": [25.46, 27.83], "reply_timespan": [25.46, 27.861166666666666]}], "conversation": [{"role": "user", "content": "What is being discussed in the video?", "time": 0}], "duration": 27.861166666666666}, {"video": "0pHWbrdttl8.15.mp4", "question_id": "0pHWbrdttl8.15.mp4", "answer": [{"role": "assistant", "content": "The man is using a black detector on the car door.", "related_timespan": [0.0, 2.8], "reply_timespan": [0.0, 3.8]}, {"role": "assistant", "content": "The device appears to be a detector with changing numbers.", "related_timespan": [4.94, 19.69], "reply_timespan": [4.94, 20.69]}, {"role": "assistant", "content": "The device appears to be a detector with a screen.", "related_timespan": [19.72, 22.66], "reply_timespan": [19.72, 22.689333333333334]}], "conversation": [{"role": "user", "content": "What is the purpose of the device the man is holding?", "time": 0}], "duration": 22.689333333333334}, {"video": "YofCMYZhYcs.5.mp4", "question_id": "YofCMYZhYcs.5.mp4", "answer": [{"role": "assistant", "content": "The two men are sitting at a table talking and gesturing.", "related_timespan": [0.0, 5.84], "reply_timespan": [0.0, 6.84]}, {"role": "assistant", "content": "The men are sitting at a table, with one man talking and the other nodding.", "related_timespan": [5.88, 12.55], "reply_timespan": [5.88, 13.55]}, {"role": "assistant", "content": "The men are sitting on a couch, with one man talking and gesturing while the other listens.", "related_timespan": [12.6, 19.64], "reply_timespan": [12.6, 19.686333333333334]}], "conversation": [{"role": "user", "content": "What are the men doing in the videos?", "time": 0}], "duration": 19.686333333333334}, {"video": "k7OQGhkYkqs.1.mp4", "question_id": "k7OQGhkYkqs.1.mp4", "answer": [{"role": "assistant", "content": "A white cylinder is placed on a windowsill.", "related_timespan": [0.0, 2.25], "reply_timespan": [0.0, 3.25]}, {"role": "assistant", "content": "A white object with a black bottom.", "related_timespan": [7.38, 11.34], "reply_timespan": [7.38, 11.386375]}], "conversation": [{"role": "user", "content": "What object is being shown in the video?", "time": 0}], "duration": 11.386375}, {"video": "RjC6aV1SNIQ.3.mp4", "question_id": "RjC6aV1SNIQ.3.mp4", "answer": [{"role": "assistant", "content": "A person is preparing to pour the white stuff from a clear container into a tub.", "related_timespan": [8.88, 12.18], "reply_timespan": [8.88, 13.18]}, {"role": "assistant", "content": "A woman uses a red spatula to scrape the white stuff into the silver tub.", "related_timespan": [12.22, 15.81], "reply_timespan": [12.22, 16.810000000000002]}, {"role": "assistant", "content": "A person is stirring the white stuff in the basin.", "related_timespan": [15.85, 18.27], "reply_timespan": [15.85, 18.30995833333333]}], "conversation": [{"role": "user", "content": "What is being done with the white stuff?", "time": 0}], "duration": 18.30995833333333}, {"video": "my2KTkgATPU.34.mp4", "question_id": "my2KTkgATPU.34.mp4", "answer": [{"role": "assistant", "content": "Macaroni is being stirred in a pot.", "related_timespan": [1.84, 14.35], "reply_timespan": [1.84, 15.35]}, {"role": "assistant", "content": "People are eating macaroni at the dining table.", "related_timespan": [14.38, 18.89], "reply_timespan": [14.38, 18.9189]}], "conversation": [{"role": "user", "content": "What type of food is being prepared or eaten?", "time": 0}], "duration": 18.9189}, {"video": "VWlP9ZnqmIQ.14.mp4", "question_id": "VWlP9ZnqmIQ.14.mp4", "answer": [{"role": "assistant", "content": "The man is pulling the zipper and pulling it up.", "related_timespan": [0.0, 2.5], "reply_timespan": [0.0, 3.5]}, {"role": "assistant", "content": "A person is holding the black bag and zipping it.", "related_timespan": [2.54, 8.31], "reply_timespan": [2.54, 9.31]}, {"role": "assistant", "content": "The man is pulling the zipper and checking it.", "related_timespan": [8.34, 13.78], "reply_timespan": [8.34, 14.78]}, {"role": "assistant", "content": "A person is unzipping the bag. The man takes out a black object from the bag.", "related_timespan": [13.81, 22.92], "reply_timespan": [13.81, 22.956266666666668]}], "conversation": [{"role": "user", "content": "What is being done with the black bags?", "time": 0}], "duration": 22.956266666666668}, {"video": "LbJ7VmR7Y9g.8.mp4", "question_id": "LbJ7VmR7Y9g.8.mp4", "answer": [{"role": "assistant", "content": "They are fishing on the ship.", "related_timespan": [0.0, 5.67], "reply_timespan": [0.0, 6.67]}, {"role": "assistant", "content": "Three people are fishing on the ship.", "related_timespan": [5.71, 10.76], "reply_timespan": [5.71, 11.76]}], "conversation": [{"role": "user", "content": "What is the activity taking place on the ship?", "time": 0}], "duration": 14.222541666666666}, {"video": "61fybvkZiDE.2.mp4", "question_id": "61fybvkZiDE.2.mp4", "answer": [{"role": "assistant", "content": "The man is talking and looking at the camera.", "related_timespan": [3.28, 7.6], "reply_timespan": [3.28, 8.6]}, {"role": "assistant", "content": "The man is holding and waving a transparent model and seems very happy.", "related_timespan": [7.64, 9.32], "reply_timespan": [7.64, 10.32]}, {"role": "assistant", "content": "The man is making something on the desk.", "related_timespan": [9.36, 12.96], "reply_timespan": [9.36, 13.96]}, {"role": "assistant", "content": "The man appears to be looking at something.", "related_timespan": [13.0, 14.48], "reply_timespan": [13.0, 14.52]}], "conversation": [{"role": "user", "content": "What is the man doing while wearing a blue shirt?", "time": 0}], "duration": 14.52}, {"video": "WZcMlzsh4XM.1.mp4", "question_id": "WZcMlzsh4XM.1.mp4", "answer": [{"role": "assistant", "content": "A man is putting a clod on a big plate made of concrete.", "related_timespan": [0.0, 1.7], "reply_timespan": [0.0, 2.7]}, {"role": "assistant", "content": "A person is checking the plumbing of a sink.", "related_timespan": [1.74, 3.94], "reply_timespan": [1.74, 4.9399999999999995]}, {"role": "assistant", "content": "A person is cleaning snow in the evening.", "related_timespan": [3.97, 7.81], "reply_timespan": [3.97, 8.809999999999999]}], "conversation": [{"role": "user", "content": "What activities are being shown in the video?", "time": 0}], "duration": 10.377033333333333}, {"video": "FhmTwZGt97Y.22.mp4", "question_id": "FhmTwZGt97Y.22.mp4", "answer": [{"role": "assistant", "content": "The person is pouring liquid from a white dish into a black pot on a table.", "related_timespan": [0.0, 5.6], "reply_timespan": [0.0, 6.6]}, {"role": "assistant", "content": "The person is pouring milk from an orange bowl into a black pot.", "related_timespan": [5.64, 11.64], "reply_timespan": [5.64, 11.68]}], "conversation": [{"role": "user", "content": "What is the person doing with the liquid?", "time": 0}], "duration": 11.68}, {"video": "B_A9FCsWnlI.6.mp4", "question_id": "B_A9FCsWnlI.6.mp4", "answer": [{"role": "assistant", "content": "A man is standing on a black hoverboard and riding it down and up a ramp in a room.", "related_timespan": [1.52, 4.72], "reply_timespan": [1.52, 5.72]}, {"role": "assistant", "content": "A man is standing on a black hoverboard and riding it down and up a ramp in a room, and there is also a close-up of a black hoverboard.", "related_timespan": [4.76, 7.08], "reply_timespan": [4.76, 8.08]}, {"role": "assistant", "content": "There is a black hoverboard on the table.", "related_timespan": [13.96, 17.6], "reply_timespan": [13.96, 17.64]}], "conversation": [{"role": "user", "content": "What activities involve the hoverboard?", "time": 0}], "duration": 17.64}, {"video": "muUcz5aYTMs.3.mp4", "question_id": "muUcz5aYTMs.3.mp4", "answer": [{"role": "assistant", "content": "A light is being shown.", "related_timespan": [0.0, 1.25], "reply_timespan": [0.0, 2.25]}, {"role": "assistant", "content": "Bicycle lights in a forest setting are being discussed, as indicated by the words \"Light Output (lumens), Beam Pattern, Mounting Options.\"", "related_timespan": [1.29, 10.68], "reply_timespan": [1.29, 11.68]}, {"role": "assistant", "content": "A man is showcasing bicycle lights.", "related_timespan": [10.72, 12.14], "reply_timespan": [10.72, 12.178833333333332]}], "conversation": [{"role": "user", "content": "What is being discussed or showcased?", "time": 0}], "duration": 12.178833333333332}, {"video": "WQFI_AIsYtg.15.mp4", "question_id": "WQFI_AIsYtg.15.mp4", "answer": [{"role": "assistant", "content": "The man's hands hold a sneaker with black and white accents.", "related_timespan": [0.0, 1.0], "reply_timespan": [0.0, 2.0]}, {"role": "assistant", "content": "The man is showing the camera the sneakers and removes an insole from the shoe.", "related_timespan": [1.03, 18.42], "reply_timespan": [1.03, 18.451766666666668]}], "conversation": [{"role": "user", "content": "What are the man's actions related to the sneakers?", "time": 0}], "duration": 18.451766666666668}, {"video": "RdhTvVPcoIo.12.mp4", "question_id": "RdhTvVPcoIo.12.mp4", "answer": [{"role": "assistant", "content": "The man is drawing on a monitor.", "related_timespan": [0.0, 3.5], "reply_timespan": [0.0, 4.5]}, {"role": "assistant", "content": "The man in black is spreading his arms out wide with a cloak-like item hanging behind him.", "related_timespan": [3.53, 5.1], "reply_timespan": [3.53, 6.1]}, {"role": "assistant", "content": "A man in purple and a woman in light blue are gazing at each other happily.", "related_timespan": [5.13, 6.4], "reply_timespan": [5.13, 7.4]}, {"role": "assistant", "content": "Various characters are dancing, looking at each other, or kissing.", "related_timespan": [6.43, 10.53], "reply_timespan": [6.43, 11.53]}, {"role": "assistant", "content": "A kneeling man dressed in black catches a flying figure in a red helmet.", "related_timespan": [10.57, 13.2], "reply_timespan": [10.57, 13.233333333333333]}], "conversation": [{"role": "user", "content": "What are the people or characters doing?", "time": 0}], "duration": 13.233333333333333}, {"video": "0fbebx_l2Xw.4.mp4", "question_id": "0fbebx_l2Xw.4.mp4", "answer": [{"role": "assistant", "content": "Having fun and smiling is a recurring theme.", "related_timespan": [0.0, 14.07], "reply_timespan": [0.0, 15.07]}, {"role": "assistant", "content": "Having fun and smiling is a recurring theme.", "related_timespan": [18.5, 19.97], "reply_timespan": [18.5, 20.97]}], "conversation": [{"role": "user", "content": "What is a recurring theme in the video?", "time": 0}], "duration": 29.466666666666665}, {"video": "7dyvxojt3o8.51.mp4", "question_id": "7dyvxojt3o8.51.mp4", "answer": [{"role": "assistant", "content": "A blue pickup truck with large tires is parked on a field.", "related_timespan": [0.0, 1.68], "reply_timespan": [0.0, 2.6799999999999997]}, {"role": "assistant", "content": "A blue pickup truck with large tires is parked on a field with trees and two tents in the background.", "related_timespan": [1.72, 4.16], "reply_timespan": [1.72, 5.16]}, {"role": "assistant", "content": "A large blue truck with large tires is parked in front of a large garage.", "related_timespan": [4.2, 10.36], "reply_timespan": [4.2, 11.36]}, {"role": "assistant", "content": "A white pickup truck with a brown stripe is parked in a field with trees in the background.", "related_timespan": [10.4, 15.28], "reply_timespan": [10.4, 16.28]}, {"role": "assistant", "content": "A large truck with large wheels is driving on a dirt road with people watching.", "related_timespan": [15.32, 17.32], "reply_timespan": [15.32, 17.36]}], "conversation": [{"role": "user", "content": "What can be observed about the vehicles in the video?", "time": 0}], "duration": 17.36}, {"video": "FYvIo4ylZus.1.mp4", "question_id": "FYvIo4ylZus.1.mp4", "answer": [{"role": "assistant", "content": "A person is using scissors to cut a circle in a white object.", "related_timespan": [0.0, 4.7], "reply_timespan": [0.0, 5.7]}, {"role": "assistant", "content": "A person is applying glue to a white disc with a white cap sitting next to it.", "related_timespan": [4.74, 8.07], "reply_timespan": [4.74, 9.07]}, {"role": "assistant", "content": "A person is gluing an orb to a white disc with a white cap placed next to it.", "related_timespan": [8.11, 12.11], "reply_timespan": [8.11, 13.11]}, {"role": "assistant", "content": "A person is placing a small disc dipped in orbs over a white lid.", "related_timespan": [12.15, 15.68], "reply_timespan": [12.15, 16.68]}, {"role": "assistant", "content": "A person is holding a white object with a lid that is covered with a clear orb.", "related_timespan": [15.72, 20.62], "reply_timespan": [15.72, 20.653966666666665]}], "conversation": [{"role": "user", "content": "What actions are being performed with the white object?", "time": 0}], "duration": 20.653966666666665}, {"video": "KGTsMFEgpFI.3.mp4", "question_id": "KGTsMFEgpFI.3.mp4", "answer": [{"role": "assistant", "content": "Two men in white uniforms are running around the field.", "related_timespan": [0.0, 1.47], "reply_timespan": [0.0, 2.4699999999999998]}, {"role": "assistant", "content": "A pitcher throws the baseball, and the batter hits it and starts running.", "related_timespan": [2.87, 5.51], "reply_timespan": [2.87, 6.51]}, {"role": "assistant", "content": "Some people are running on the field, and a few people are running out of an exit in the audience.", "related_timespan": [5.54, 11.38], "reply_timespan": [5.54, 11.4114]}], "conversation": [{"role": "user", "content": "What is happening during the baseball game?", "time": 0}], "duration": 11.4114}, {"video": "0KoYCn3MQd0.9.mp4", "question_id": "0KoYCn3MQd0.9.mp4", "answer": [{"role": "assistant", "content": "Elsa is walking toward the camera while talking.", "related_timespan": [0.0, 4.7], "reply_timespan": [0.0, 5.7]}, {"role": "assistant", "content": "Elsa is walking towards a terrace.", "related_timespan": [4.74, 8.17], "reply_timespan": [4.74, 9.17]}, {"role": "assistant", "content": "Elsa's feet touch the ground and the ground begins to freeze.", "related_timespan": [13.31, 16.15], "reply_timespan": [13.31, 17.15]}, {"role": "assistant", "content": "Elsa is holding hands with Anna and then pulling them apart.", "related_timespan": [16.18, 18.39], "reply_timespan": [16.18, 19.39]}, {"role": "assistant", "content": "Elsa is on a boat with other characters, with water splashing everywhere.", "related_timespan": [18.42, 21.05], "reply_timespan": [18.42, 21.087733333333333]}], "conversation": [{"role": "user", "content": "What is Elsa doing?", "time": 0}], "duration": 21.087733333333333}, {"video": "09RFL5SKVjM.22.mp4", "question_id": "09RFL5SKVjM.22.mp4", "answer": [{"role": "assistant", "content": "The words \"HOLLYWOOD\" are captured on the mountain.", "related_timespan": [6.27, 12.85], "reply_timespan": [6.27, 12.879533333333333]}], "conversation": [{"role": "user", "content": "What can be seen in the background on the mountain?", "time": 0}], "duration": 12.879533333333333}, {"video": "cN039e4NGAw.7.mp4", "question_id": "cN039e4NGAw.7.mp4", "answer": [{"role": "assistant", "content": "The man in a black suit glances to his left, turning his head and walking on.", "related_timespan": [7.61, 9.18], "reply_timespan": [7.61, 10.18]}, {"role": "assistant", "content": "The man in a black suit is standing and walking forward side by side with a man in a white shirt.", "related_timespan": [9.21, 11.38], "reply_timespan": [9.21, 11.4114]}], "conversation": [{"role": "user", "content": "What is the man in the suit doing?", "time": 0}], "duration": 11.4114}, {"video": "foGfkZjCHm0.21.mp4", "question_id": "foGfkZjCHm0.21.mp4", "answer": [{"role": "assistant", "content": "The man is doing squats.", "related_timespan": [0.0, 3.12], "reply_timespan": [0.0, 4.12]}, {"role": "assistant", "content": "The man is lying on the floor and exercising.", "related_timespan": [3.16, 5.76], "reply_timespan": [3.16, 6.76]}, {"role": "assistant", "content": "The man is exercising, sitting on the floor speaking to the camera.", "related_timespan": [5.8, 19.28], "reply_timespan": [5.8, 19.32]}], "conversation": [{"role": "user", "content": "What is the man doing in the video?", "time": 0}], "duration": 19.32}, {"video": "mtP9H7AClk8.7.mp4", "question_id": "mtP9H7AClk8.7.mp4", "answer": [{"role": "assistant", "content": "The theme involves a deer and characters Rick and Michonne in the woods.", "related_timespan": [0.0, 4.21], "reply_timespan": [0.0, 5.21]}, {"role": "assistant", "content": "The theme involves Michonne carrying a deer and pictures of Rick and Negan.", "related_timespan": [4.25, 10.01], "reply_timespan": [4.25, 11.01]}], "conversation": [{"role": "user", "content": "What is the recurring theme from The Walking Dead in the captions?", "time": 0}], "duration": 12.011999999999999}, {"video": "rM-QRSeVngo.1.mp4", "question_id": "rM-QRSeVngo.1.mp4", "answer": [{"role": "assistant", "content": "A \"DEADLY SHOOTING\" is being reported.", "related_timespan": [0.0, 12.41], "reply_timespan": [0.0, 13.41]}], "conversation": [{"role": "user", "content": "What is being reported in the news program?", "time": 0}], "duration": 20.4204}, {"video": "ZRL2bzC2GGE.67.mp4", "question_id": "ZRL2bzC2GGE.67.mp4", "answer": [{"role": "assistant", "content": "The setting is in front of rows of racks with motorcycle helmets and jackets.", "related_timespan": [0.0, 7.21], "reply_timespan": [0.0, 8.21]}, {"role": "assistant", "content": "The setting includes a motorcycle wall in the background.", "related_timespan": [7.24, 13.01], "reply_timespan": [7.24, 14.01]}, {"role": "assistant", "content": "The setting includes racks with motorcycle helmets and jackets, as well as a web page about \"2017 MOTO GEAR GUIDES\".", "related_timespan": [13.05, 17.42], "reply_timespan": [13.05, 17.450766666666667]}], "conversation": [{"role": "user", "content": "What is the setting for the video?", "time": 0}], "duration": 17.450766666666667}, {"video": "6He8k52im8I.6.mp4", "question_id": "6He8k52im8I.6.mp4", "answer": [{"role": "assistant", "content": "People are throwing torches at a vehicle and there is a street with many people walking on it.", "related_timespan": [2.77, 5.64], "reply_timespan": [2.77, 6.64]}, {"role": "assistant", "content": "A large group of people is marching through the street at night carrying signs and banners.", "related_timespan": [5.67, 6.94], "reply_timespan": [5.67, 7.94]}, {"role": "assistant", "content": "Police officers are lining up in a row and blocking the crowd on the street.", "related_timespan": [6.97, 9.91], "reply_timespan": [6.97, 10.91]}], "conversation": [{"role": "user", "content": "What notable events occur on the street?", "time": 0}], "duration": 14.1141}, {"video": "8TJF2VwFgD8.8.mp4", "question_id": "8TJF2VwFgD8.8.mp4", "answer": [{"role": "assistant", "content": "The woman is holding a concealer and showing it to the camera.", "related_timespan": [3.84, 5.84], "reply_timespan": [3.84, 6.84]}, {"role": "assistant", "content": "The woman applies concealer to the back of her hand.", "related_timespan": [5.88, 12.04], "reply_timespan": [5.88, 12.08]}], "conversation": [{"role": "user", "content": "What is the woman doing with the concealer?", "time": 0}], "duration": 12.08}, {"video": "7k3S6IsDZ28.27.mp4", "question_id": "7k3S6IsDZ28.27.mp4", "answer": [{"role": "assistant", "content": "A person in blue is holding a framed photograph with an orange button on it.", "related_timespan": [0.0, 4.63], "reply_timespan": [0.0, 5.63]}, {"role": "assistant", "content": "A man is holding a framed photograph of a yellow square.", "related_timespan": [4.67, 11.63], "reply_timespan": [4.67, 11.666666666666666]}], "conversation": [{"role": "user", "content": "Who is holding a framed photograph?", "time": 0}], "duration": 11.666666666666666}, {"video": "LjKTQ9RKdaM.1.mp4", "question_id": "LjKTQ9RKdaM.1.mp4", "answer": [{"role": "assistant", "content": "A brown mountain bike is shown.", "related_timespan": [0.0, 6.17], "reply_timespan": [0.0, 7.17]}, {"role": "assistant", "content": "A man stands in front of a black mountain bike.", "related_timespan": [6.21, 11.59], "reply_timespan": [6.21, 11.636624999999999]}], "conversation": [{"role": "user", "content": "What is shown in the video related to mountain biking?", "time": 0}], "duration": 11.636624999999999}, {"video": "eairs2hMa3I.31.mp4", "question_id": "eairs2hMa3I.31.mp4", "answer": [{"role": "assistant", "content": "The video shows ancient coins on display.", "related_timespan": [3.27, 5.71], "reply_timespan": [3.27, 6.71]}, {"role": "assistant", "content": "A man's hand is moving a pile of coins on a table.", "related_timespan": [5.77, 10.28], "reply_timespan": [5.77, 11.28]}], "conversation": [{"role": "user", "content": "What activities involve coins in the video?", "time": 0}], "duration": 15.4154}, {"video": "0IKKzYIK-AY.6.mp4", "question_id": "0IKKzYIK-AY.6.mp4", "answer": [{"role": "assistant", "content": "The man is walking while talking to the camera in a park.", "related_timespan": [0.0, 3.44], "reply_timespan": [0.0, 4.4399999999999995]}, {"role": "assistant", "content": "The man is riding a skateboard on concrete ground and talking.", "related_timespan": [6.31, 8.38], "reply_timespan": [6.31, 9.38]}, {"role": "assistant", "content": "The man is standing on the platform of a concrete staircase and talking to the camera.", "related_timespan": [9.58, 10.78], "reply_timespan": [9.58, 10.8108]}], "conversation": [{"role": "user", "content": "What is the man doing in the video?", "time": 0}], "duration": 10.8108}, {"video": "K78OKshZKIA.2.mp4", "question_id": "K78OKshZKIA.2.mp4", "answer": [{"role": "assistant", "content": "A person is holding a purple device in a black basket inside a machine.", "related_timespan": [16.18, 19.69], "reply_timespan": [16.18, 20.69]}], "conversation": [{"role": "user", "content": "What activities are being shown related to the purple device?", "time": 0}], "duration": 25.892533333333333}, {"video": "4EDw7gydFBk.25.mp4", "question_id": "4EDw7gydFBk.25.mp4", "answer": [{"role": "assistant", "content": "The man is sitting on a sofa with an electronic device in his hand.", "related_timespan": [0.0, 2.07], "reply_timespan": [0.0, 3.07]}, {"role": "assistant", "content": "The man puts down the electronic device.", "related_timespan": [7.61, 12.78], "reply_timespan": [7.61, 12.812800000000001]}], "conversation": [{"role": "user", "content": "What is the man doing with the electronic device?", "time": 0}], "duration": 12.812800000000001}, {"video": "GBERmCAKFic.12.mp4", "question_id": "GBERmCAKFic.12.mp4", "answer": [{"role": "assistant", "content": "The video shows an apartment building at night with police tape around it, indicating a crime scene.", "related_timespan": [3.54, 9.04], "reply_timespan": [3.54, 10.04]}, {"role": "assistant", "content": "There are two people standing next to a brick wall at night, one in uniform, implying police involvement at a crime scene.", "related_timespan": [9.08, 11.01], "reply_timespan": [9.08, 11.044366666666667]}], "conversation": [{"role": "user", "content": "What incident is being reported in the video?", "time": 0}], "duration": 11.044366666666667}, {"video": "_nbQsnUvlo4.9.mp4", "question_id": "_nbQsnUvlo4.9.mp4", "answer": [{"role": "assistant", "content": "The man is holding a white helmet in his hand and then placing it on a motorbike mirror.", "related_timespan": [0.0, 4.96], "reply_timespan": [0.0, 5.96]}, {"role": "assistant", "content": "The man is holding a white helmet, reaching into it, and then bringing it to his right hand while speaking into the camera.", "related_timespan": [9.47, 12.47], "reply_timespan": [9.47, 12.5125]}], "conversation": [{"role": "user", "content": "What is the man doing with the helmet?", "time": 0}], "duration": 12.5125}, {"video": "gCtvzTSzMxY.7.mp4", "question_id": "gCtvzTSzMxY.7.mp4", "answer": [{"role": "assistant", "content": "Face ID technology is being demonstrated.", "related_timespan": [0.0, 5.64], "reply_timespan": [0.0, 6.64]}, {"role": "assistant", "content": "The woman is using face recognition technology.", "related_timespan": [5.67, 7.17], "reply_timespan": [5.67, 8.17]}], "conversation": [{"role": "user", "content": "What technology is being demonstrated or discussed?", "time": 0}], "duration": 11.378033333333333}, {"video": "LpUyGPg_Dc8.8.mp4", "question_id": "LpUyGPg_Dc8.8.mp4", "answer": [{"role": "assistant", "content": "The pipe has many leak holes in it.", "related_timespan": [0.0, 3.4], "reply_timespan": [0.0, 4.4]}, {"role": "assistant", "content": "The pipe has a small hole in the center and is rusty.", "related_timespan": [3.44, 8.44], "reply_timespan": [3.44, 9.44]}, {"role": "assistant", "content": "The end of the pipe has two holes.", "related_timespan": [8.48, 20.92], "reply_timespan": [8.48, 20.96]}], "conversation": [{"role": "user", "content": "What is notable about the pipe?", "time": 0}], "duration": 20.96}, {"video": "WV1-ZuBmP-E.12.mp4", "question_id": "WV1-ZuBmP-E.12.mp4", "answer": [{"role": "assistant", "content": "The woman uses a make-up brush to make circles on her face.", "related_timespan": [0.0, 2.47], "reply_timespan": [0.0, 3.47]}, {"role": "assistant", "content": "A woman is painting her eyebrows with an eyebrow brush.", "related_timespan": [4.87, 15.02], "reply_timespan": [4.87, 15.048366666666666]}], "conversation": [{"role": "user", "content": "What is the woman doing with makeup?", "time": 0}], "duration": 15.048366666666666}, {"video": "MtuZyKJPjAM.4.mp4", "question_id": "MtuZyKJPjAM.4.mp4", "answer": [{"role": "assistant", "content": "A ball is thrown to an athlete, and there is a scoring box indicating a tracking of scores.", "related_timespan": [0.0, 1.47], "reply_timespan": [0.0, 2.4699999999999998]}, {"role": "assistant", "content": "An athlete throws a ball and another hits it and runs, showing an ongoing play in the sports event.", "related_timespan": [7.57, 12.28], "reply_timespan": [7.57, 13.28]}, {"role": "assistant", "content": "An athlete picks up and throws a ball, indicating gameplay activities.", "related_timespan": [12.31, 16.35], "reply_timespan": [12.31, 17.35]}, {"role": "assistant", "content": "An athlete is seen running and interacting on the court, with the presence of many players and spectators.", "related_timespan": [16.38, 19.39], "reply_timespan": [16.38, 19.4194]}], "conversation": [{"role": "user", "content": "What is happening in the sports event?", "time": 0}], "duration": 19.4194}, {"video": "dUAPtHIz8Rg.5.mp4", "question_id": "dUAPtHIz8Rg.5.mp4", "answer": [{"role": "assistant", "content": "A person is cooking chopped butternut squash.", "related_timespan": [7.34, 12.6], "reply_timespan": [7.34, 13.6]}, {"role": "assistant", "content": "The man is cooking pumpkin.", "related_timespan": [12.64, 21.86], "reply_timespan": [12.64, 22.86]}], "conversation": [{"role": "user", "content": "What is being cooked in the kitchen?", "time": 0}], "duration": 25.233541666666664}, {"video": "ecilxRa6m5g.4.mp4", "question_id": "ecilxRa6m5g.4.mp4", "answer": [{"role": "assistant", "content": "The woman is swinging to show off her trench coat.", "related_timespan": [0.0, 9.23], "reply_timespan": [0.0, 10.23]}, {"role": "assistant", "content": "The woman is standing in front of the camera showing off her clothing.", "related_timespan": [9.27, 12.07], "reply_timespan": [9.27, 12.1]}], "conversation": [{"role": "user", "content": "What is the woman doing in front of the camera?", "time": 0}], "duration": 12.1}, {"video": "fRqJsYel7V0.15.mp4", "question_id": "fRqJsYel7V0.15.mp4", "answer": [{"role": "assistant", "content": "The person is wrapping an onigiri in plastic wrap and putting the nori seam side down.", "related_timespan": [0.0, 4.4], "reply_timespan": [0.0, 5.4]}, {"role": "assistant", "content": "The person is putting down two onigiris on the table.", "related_timespan": [4.44, 8.94], "reply_timespan": [4.44, 9.94]}, {"role": "assistant", "content": "The person is putting an onigiri on a cutting board.", "related_timespan": [8.98, 10.94], "reply_timespan": [8.98, 11.94]}, {"role": "assistant", "content": "The person is cutting an onigiri on a cutting board.", "related_timespan": [10.98, 13.01], "reply_timespan": [10.98, 13.046366666666668]}], "conversation": [{"role": "user", "content": "What is being done to the onigiri?", "time": 0}], "duration": 13.046366666666668}, {"video": "_8UAR6y9vtM.1.mp4", "question_id": "_8UAR6y9vtM.1.mp4", "answer": [{"role": "assistant", "content": "A woman is explaining \"What is Antarctica?\"", "related_timespan": [0.0, 3.07], "reply_timespan": [0.0, 4.07]}, {"role": "assistant", "content": "Antarctica is shown as a white continent with \"South Pole\" written on it.", "related_timespan": [3.13, 13.27], "reply_timespan": [3.13, 14.27]}, {"role": "assistant", "content": "A snowy scene with wind is depicted, indicating the environmental conditions in Antarctica.", "related_timespan": [13.3, 16.83], "reply_timespan": [13.3, 17.83]}, {"role": "assistant", "content": "The video shows a vast white land, Antarctica, covered with snow and surrounded by ocean.", "related_timespan": [16.9, 23.37], "reply_timespan": [16.9, 23.4]}], "conversation": [{"role": "user", "content": "What is featured about Antarctica in the video?", "time": 0}], "duration": 23.4}, {"video": "rwbhbyWCR2U.8.mp4", "question_id": "rwbhbyWCR2U.8.mp4", "answer": [{"role": "assistant", "content": "Candles are present in the background and within the lamp the woman is holding.", "related_timespan": [0.0, 4.84], "reply_timespan": [0.0, 5.84]}, {"role": "assistant", "content": "The person lying on the ground is surrounded by a lot of lighted candles.", "related_timespan": [4.88, 11.01], "reply_timespan": [4.88, 12.01]}, {"role": "assistant", "content": "Candles are lit on the table and around the room for dim lighting.", "related_timespan": [11.05, 15.64], "reply_timespan": [11.05, 15.682333333333332]}], "conversation": [{"role": "user", "content": "What role do candles play in the scenes?", "time": 0}], "duration": 15.682333333333332}, {"video": "O7aiG0D0qo0.25.mp4", "question_id": "O7aiG0D0qo0.25.mp4", "answer": [{"role": "assistant", "content": "Lemons help flush out toxins throughout the body.", "related_timespan": [0.0, 1.37], "reply_timespan": [0.0, 2.37]}, {"role": "assistant", "content": "Lemons help reduce the risk of heart disease and stroke.", "related_timespan": [1.4, 6.43], "reply_timespan": [1.4, 7.43]}], "conversation": [{"role": "user", "content": "What are the potential health benefits of lemons mentioned in the video?", "time": 0}], "duration": 11.8}, {"video": "PXMngeRSdsE.25.mp4", "question_id": "PXMngeRSdsE.25.mp4", "answer": [{"role": "assistant", "content": "A cartoon character hangs in the air and rotates.", "related_timespan": [0.0, 2.87], "reply_timespan": [0.0, 3.87]}, {"role": "assistant", "content": "A game character stands on rocky terrain holding two lightsabers.", "related_timespan": [2.9, 6.14], "reply_timespan": [2.9, 7.14]}, {"role": "assistant", "content": "A person is preparing to open a car door.", "related_timespan": [7.31, 9.51], "reply_timespan": [7.31, 10.51]}, {"role": "assistant", "content": "A person opens the car door and pulls another person out.", "related_timespan": [9.54, 14.08], "reply_timespan": [9.54, 15.08]}, {"role": "assistant", "content": "A man stands on the street holding something, then falls down.", "related_timespan": [14.11, 16.75], "reply_timespan": [14.11, 16.783433333333335]}], "conversation": [{"role": "user", "content": "What actions are the characters performing?", "time": 0}], "duration": 16.783433333333335}, {"video": "0YSLASX3Iq8.3.mp4", "question_id": "0YSLASX3Iq8.3.mp4", "answer": [{"role": "assistant", "content": "Pasta.", "related_timespan": [0.0, 1.63], "reply_timespan": [0.0, 2.63]}, {"role": "assistant", "content": "Capsicum.", "related_timespan": [1.67, 4.5], "reply_timespan": [1.67, 5.5]}, {"role": "assistant", "content": "Tomatoes.", "related_timespan": [4.57, 7.57], "reply_timespan": [4.57, 8.57]}, {"role": "assistant", "content": "Onions and garlic.", "related_timespan": [7.61, 12.78], "reply_timespan": [7.61, 13.78]}, {"role": "assistant", "content": "Milk.", "related_timespan": [12.81, 16.05], "reply_timespan": [12.81, 17.05]}, {"role": "assistant", "content": "Flour and butter.", "related_timespan": [16.12, 20.45], "reply_timespan": [16.12, 21.45]}, {"role": "assistant", "content": "Cheese, salt, and black pepper.", "related_timespan": [20.49, 29.06], "reply_timespan": [20.49, 30.06]}, {"role": "assistant", "content": "Eggs.", "related_timespan": [29.13, 33.97], "reply_timespan": [29.13, 34.00063333333333]}], "conversation": [{"role": "user", "content": "What ingredients are shown in the video?", "time": 0}], "duration": 34.00063333333333}, {"video": "ClksIvHsYcc.24.mp4", "question_id": "ClksIvHsYcc.24.mp4", "answer": [{"role": "assistant", "content": "The person is dancing in front of a yellow background with many yellow and orange balloons.", "related_timespan": [0.0, 3.13], "reply_timespan": [0.0, 4.13]}, {"role": "assistant", "content": "The person is sitting on a white chair and playing the white piano.", "related_timespan": [3.17, 4.63], "reply_timespan": [3.17, 5.63]}, {"role": "assistant", "content": "The person is smiling and making gestures to the camera.", "related_timespan": [4.67, 7.34], "reply_timespan": [4.67, 8.34]}, {"role": "assistant", "content": "The person is talking and shaking his head.", "related_timespan": [7.38, 9.18], "reply_timespan": [7.38, 10.18]}, {"role": "assistant", "content": "The person is sitting by a black piano.", "related_timespan": [10.72, 13.72], "reply_timespan": [10.72, 13.763749999999998]}], "conversation": [{"role": "user", "content": "What is the person with a head that looks like the moon doing?", "time": 0}], "duration": 13.763749999999998}, {"video": "0lbseqh9fsg.35.mp4", "question_id": "0lbseqh9fsg.35.mp4", "answer": [{"role": "assistant", "content": "The man and the woman are embracing each other and seemed to enjoy each other's company.", "related_timespan": [0.0, 3.94], "reply_timespan": [0.0, 4.9399999999999995]}, {"role": "assistant", "content": "They have been friends for years according to the caption.", "related_timespan": [3.97, 8.71], "reply_timespan": [3.97, 9.71]}, {"role": "assistant", "content": "Fans are wondering when the two would cross the line of friendship.", "related_timespan": [8.74, 12.78], "reply_timespan": [8.74, 12.812800000000001]}], "conversation": [{"role": "user", "content": "What is the relationship between the man and the woman?", "time": 0}], "duration": 12.812800000000001}, {"video": "KqUZIzkIWaY.7.mp4", "question_id": "KqUZIzkIWaY.7.mp4", "answer": [{"role": "assistant", "content": "Oil is being poured into a pan on a stove.", "related_timespan": [0.0, 3.96], "reply_timespan": [0.0, 4.96]}, {"role": "assistant", "content": "The oil is being used to coat food and starts to sizzle.", "related_timespan": [6.59, 12.76], "reply_timespan": [6.59, 12.804458333333333]}], "conversation": [{"role": "user", "content": "What is being done with the oil?", "time": 0}], "duration": 12.804458333333333}, {"video": "2uthP1KSZMc.11.mp4", "question_id": "2uthP1KSZMc.11.mp4", "answer": [{"role": "assistant", "content": "A little girl with red hair holding a torch and wearing clothes made of leaves is depicted in the painting.", "related_timespan": [0.0, 5.21], "reply_timespan": [0.0, 6.21]}, {"role": "assistant", "content": "A large tree with red leaves and a bright light, with a man standing on a rock, is depicted in the painting.", "related_timespan": [5.26, 12.55], "reply_timespan": [5.26, 12.595916666666666]}], "conversation": [{"role": "user", "content": "What is depicted in the paintings?", "time": 0}], "duration": 12.595916666666666}, {"video": "crFioD1EelM.13.mp4", "question_id": "crFioD1EelM.13.mp4", "answer": [{"role": "assistant", "content": "A person is standing on a paddle board, paddling water with a paddle.", "related_timespan": [0.0, 4.63], "reply_timespan": [0.0, 5.63]}, {"role": "assistant", "content": "Three men are standing on a paddle board, gliding on the water.", "related_timespan": [10.39, 12.68], "reply_timespan": [10.39, 12.721041666666666]}], "conversation": [{"role": "user", "content": "What is happening on the paddle board?", "time": 0}], "duration": 12.721041666666666}, {"video": "6qTEzEcD3No.5.mp4", "question_id": "6qTEzEcD3No.5.mp4", "answer": [{"role": "assistant", "content": "The man in a black suit appears in front of a blue background, looks at the camera with a ferocious expression, and speaks.", "related_timespan": [0.0, 12.71], "reply_timespan": [0.0, 12.746066666666668]}], "conversation": [{"role": "user", "content": "What is the man in the suit doing?", "time": 0}], "duration": 12.746066666666668}, {"video": "dR9jfG9Zr5A.2.mp4", "question_id": "dR9jfG9Zr5A.2.mp4", "answer": [{"role": "assistant", "content": "The woman is doing sign language.", "related_timespan": [11.04, 13.65], "reply_timespan": [11.04, 14.65]}, {"role": "assistant", "content": "The woman is talking to two other ladies in sign language.", "related_timespan": [13.68, 19.29], "reply_timespan": [13.68, 20.29]}, {"role": "assistant", "content": "The woman is doing sign language with a sad expression on her face.", "related_timespan": [22.12, 24.52], "reply_timespan": [22.12, 24.557866666666666]}], "conversation": [{"role": "user", "content": "What is the woman in the dark camisole doing?", "time": 0}], "duration": 24.557866666666666}, {"video": "0tdkFPQri14.15.mp4", "question_id": "0tdkFPQri14.15.mp4", "answer": [{"role": "assistant", "content": "A man is sitting on a chair in a dimly lit room.", "related_timespan": [1.84, 7.41], "reply_timespan": [1.84, 8.41]}, {"role": "assistant", "content": "A man is sitting on a chair and speaks to the camera in a dimly lit room.", "related_timespan": [7.44, 9.41], "reply_timespan": [7.44, 10.41]}, {"role": "assistant", "content": "Special effects occur while a man sits in a dimly lit room, and something causes a man to rush out.", "related_timespan": [9.44, 11.91], "reply_timespan": [9.44, 12.91]}, {"role": "assistant", "content": "A man is sitting in a dimly lit room, talking to the camera.", "related_timespan": [11.95, 13.65], "reply_timespan": [11.95, 14.65]}, {"role": "assistant", "content": "A man is sitting in a dimly lit room, and the light slowly turns on revealing a monster.", "related_timespan": [13.68, 14.68], "reply_timespan": [13.68, 15.68]}, {"role": "assistant", "content": "Special effects occur as a man sits with someone passed out in a dimly lit room.", "related_timespan": [14.71, 17.58], "reply_timespan": [14.71, 17.6176]}], "conversation": [{"role": "user", "content": "What is happening in the dimly lit room?", "time": 0}], "duration": 17.6176}, {"video": "2iOaljmFfb0.4.mp4", "question_id": "2iOaljmFfb0.4.mp4", "answer": [{"role": "assistant", "content": "The video shows a woman speaking in front of a wall of light bulbs.", "related_timespan": [0.0, 10.94], "reply_timespan": [0.0, 11.94]}, {"role": "assistant", "content": "The video shows a close-up shot of a spiral-lit bulb.", "related_timespan": [10.98, 13.91], "reply_timespan": [10.98, 14.91]}], "conversation": [{"role": "user", "content": "What does the video primarily focus on?", "time": 0}], "duration": 15.3153}, {"video": "PvvMz5FBofo.6.mp4", "question_id": "PvvMz5FBofo.6.mp4", "answer": [{"role": "assistant", "content": "Players in red and white jerseys are in the field, and one player is walking forward.", "related_timespan": [0.0, 3.56], "reply_timespan": [0.0, 4.5600000000000005]}, {"role": "assistant", "content": "Players in red and white jerseys are passing the ball.", "related_timespan": [3.6, 8.12], "reply_timespan": [3.6, 9.12]}], "conversation": [{"role": "user", "content": "What are the players in red and white jerseys doing?", "time": 0}], "duration": 11.68}, {"video": "Itob3CQDzR4.18.mp4", "question_id": "Itob3CQDzR4.18.mp4", "answer": [{"role": "assistant", "content": "Colorful bubbles appear covering the whole screen after the yellow cow is shown.", "related_timespan": [0.0, 1.6], "reply_timespan": [0.0, 2.6]}, {"role": "assistant", "content": "Colorful bubbles appear covering the whole screen after the bear is formed.", "related_timespan": [1.64, 15.32], "reply_timespan": [1.64, 15.36]}], "conversation": [{"role": "user", "content": "What happens when the colorful bubbles appear?", "time": 0}], "duration": 15.36}, {"video": "e3aLYhXDbz8.12.mp4", "question_id": "e3aLYhXDbz8.12.mp4", "answer": [{"role": "assistant", "content": "Red.", "related_timespan": [0.0, 3.24], "reply_timespan": [0.0, 4.24]}, {"role": "assistant", "content": "Red and white.", "related_timespan": [3.32, 11.36], "reply_timespan": [3.32, 11.4]}], "conversation": [{"role": "user", "content": "What is the color of the vehicles mentioned?", "time": 0}], "duration": 11.4}, {"video": "_Jh8mvvoXsM.1.mp4", "question_id": "_Jh8mvvoXsM.1.mp4", "answer": [{"role": "assistant", "content": "A man in a space suit is smiling at the camera, implying involvement in space activities.", "related_timespan": [5.67, 8.04], "reply_timespan": [5.67, 9.04]}, {"role": "assistant", "content": "A space shuttle is launching into space.", "related_timespan": [8.07, 10.04], "reply_timespan": [8.07, 10.076733333333333]}], "conversation": [{"role": "user", "content": "What happens in space in the video?", "time": 0}], "duration": 10.076733333333333}, {"video": "GFhJFhBUc-Y.7.mp4", "question_id": "GFhJFhBUc-Y.7.mp4", "answer": [{"role": "assistant", "content": "A woman is speaking into a microphone.", "related_timespan": [0.0, 1.6], "reply_timespan": [0.0, 2.6]}, {"role": "assistant", "content": "Several people stand next to and behind the car.", "related_timespan": [1.63, 4.44], "reply_timespan": [1.63, 5.44]}, {"role": "assistant", "content": "A group of people stand by the side of the road watching a car drive.", "related_timespan": [4.47, 11.08], "reply_timespan": [4.47, 12.08]}, {"role": "assistant", "content": "A few people are still standing near the road.", "related_timespan": [11.14, 15.02], "reply_timespan": [11.14, 15.048366666666666]}], "conversation": [{"role": "user", "content": "What activities are people doing around the road?", "time": 0}], "duration": 15.048366666666666}, {"video": "iz6G1svj7Ng.4.mp4", "question_id": "iz6G1svj7Ng.4.mp4", "answer": [{"role": "assistant", "content": "The device is described as being a gaming console.", "related_timespan": [0.0, 1.84], "reply_timespan": [0.0, 2.84]}, {"role": "assistant", "content": "The device has several ports on the side, and a person is removing the lids from the ports.", "related_timespan": [1.87, 12.61], "reply_timespan": [1.87, 12.645966666666666]}], "conversation": [{"role": "user", "content": "What is shown about the device?", "time": 0}], "duration": 12.645966666666666}, {"video": "84GzJD_ARp0.4.mp4", "question_id": "84GzJD_ARp0.4.mp4", "answer": [{"role": "assistant", "content": "The woman is speaking to the camera with an onion in her hand.", "related_timespan": [0.0, 7.88], "reply_timespan": [0.0, 8.879999999999999]}, {"role": "assistant", "content": "The woman is pulling things out of a green shopping bag and talking to the camera.", "related_timespan": [7.92, 11.56], "reply_timespan": [7.92, 11.6]}], "conversation": [{"role": "user", "content": "What is the woman doing in her kitchen?", "time": 0}], "duration": 11.6}, {"video": "1rtBD13QbXE.16.mp4", "question_id": "1rtBD13QbXE.16.mp4", "answer": [{"role": "assistant", "content": "A black leather jacket and its details are being shown.", "related_timespan": [0.0, 9.18], "reply_timespan": [0.0, 10.18]}, {"role": "assistant", "content": "A black leather jacket and its details are being shown.", "related_timespan": [9.21, 11.01], "reply_timespan": [9.21, 11.044366666666667]}], "conversation": [{"role": "user", "content": "What is being shown or described in the video?", "time": 0}], "duration": 11.044366666666667}]
|
paper_experiments/run.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# paper_experiments/run.py — reproduce every table in the report with one command each.
|
| 3 |
+
#
|
| 4 |
+
# python paper_experiments/run.py table1 # Table 1: main progression (score AND cost)
|
| 5 |
+
# python paper_experiments/run.py table2 # Table 2: ensemble ablation (calls per frame)
|
| 6 |
+
# python paper_experiments/run.py table3 # Table 3: dedup ablation
|
| 7 |
+
# python paper_experiments/run.py gate # Table 4: perception-gate cost/score frontier
|
| 8 |
+
# python paper_experiments/run.py sensitivity # FS example-sensitivity check (Sec. 4.x)
|
| 9 |
+
# python paper_experiments/run.py all
|
| 10 |
+
#
|
| 11 |
+
# Self-contained and DETERMINISTIC: replays the shipped per-frame VLM decision caches
|
| 12 |
+
# (caches/web_*.jsonl, holdout videos only) through the system's merge + entailment dedup, and scores
|
| 13 |
+
# with the official-protocol harness using the shipped LLM-judge cache (caches/judge_cache.json) —
|
| 14 |
+
# so a rerun makes ZERO API calls and reproduces the reported numbers exactly. The perception gate is
|
| 15 |
+
# replayed from the shipped per-frame novelty cache (caches/novelty_holdout.json). Requires only
|
| 16 |
+
# `pip install -e .` (first run downloads the MiniLM sentence embedder, ~90 MB).
|
| 17 |
+
import argparse
|
| 18 |
+
import json
|
| 19 |
+
import os
|
| 20 |
+
import shutil
|
| 21 |
+
import sys
|
| 22 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 23 |
+
|
| 24 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 25 |
+
ROOT = os.path.dirname(HERE)
|
| 26 |
+
os.chdir(ROOT) # humomni modules use repo-root-relative cache paths
|
| 27 |
+
sys.path.insert(0, os.path.join(ROOT, "src"))
|
| 28 |
+
|
| 29 |
+
# The scoring harness reads its judge cache from cache/judge_cache.json (repo-root relative).
|
| 30 |
+
# Ship-in the reproduction copy if the local one is absent so reruns are exact and free.
|
| 31 |
+
os.makedirs("cache", exist_ok=True)
|
| 32 |
+
if not os.path.exists("cache/judge_cache.json"):
|
| 33 |
+
shutil.copyfile(os.path.join(HERE, "caches", "judge_cache.json"), "cache/judge_cache.json")
|
| 34 |
+
|
| 35 |
+
import humomni.core.emb_cache as emb_cache
|
| 36 |
+
from humomni.phase1.dedup import EntailmentGuard
|
| 37 |
+
from humomni.tuning.cache import VLMCache
|
| 38 |
+
from humomni.tuning.faithful_eval import faithful_score
|
| 39 |
+
|
| 40 |
+
THETA = 0.6 # NEW_ANSWER confidence threshold (config.json "theta")
|
| 41 |
+
GOLD = json.load(open(os.path.join(HERE, "data", "gold_holdout.json"), encoding="utf-8"))
|
| 42 |
+
CACHES = {n: VLMCache(os.path.join(HERE, "caches", f"web_{n}.jsonl"))
|
| 43 |
+
for n in ("base", "comp", "exh", "fs", "fs_alt")}
|
| 44 |
+
NOVELTY = json.load(open(os.path.join(HERE, "caches", "novelty_holdout.json"), encoding="utf-8"))
|
| 45 |
+
_TIMES = {} # qid -> ascending frame timestamps (from the cache itself)
|
| 46 |
+
for c in CACHES.values():
|
| 47 |
+
for (qid, t) in c.mem:
|
| 48 |
+
_TIMES.setdefault(qid, set()).add(t)
|
| 49 |
+
_TIMES = {q: sorted(ts) for q, ts in _TIMES.items()}
|
| 50 |
+
|
| 51 |
+
emb_cache.load_disk()
|
| 52 |
+
emb_cache.embed_many([d["draft"].strip() for c in CACHES.values()
|
| 53 |
+
for d in c.mem.values() if d.get("draft", "").strip()])
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def replay(names, k, use_llm=True, sim_high=0.97, sim_low=0.55, gate=None):
|
| 57 |
+
"""Replay the cached decisions of ensemble `names` through the merge + dedup (identical to the
|
| 58 |
+
live policy: prompts queried in order per frame, said=[]). `gate` = None (call the VLM on every
|
| 59 |
+
frame) or a dict {novelty, max_silence_s, warmup_s}: the CPU perception gate is replayed from the
|
| 60 |
+
per-frame novelty cache, and the VLM is consulted ONLY on frames where the gate fires.
|
| 61 |
+
Returns (predictions, vlm_calls_per_video)."""
|
| 62 |
+
cs = [CACHES[n] for n in names]
|
| 63 |
+
def one(item):
|
| 64 |
+
qid = item["question_id"]
|
| 65 |
+
g = EntailmentGuard(sim_high=sim_high, sim_low=sim_low, recent_k=k, use_llm=use_llm)
|
| 66 |
+
said, res, calls, last_fire = [], [], 0, -1e9
|
| 67 |
+
for t in _TIMES[qid]:
|
| 68 |
+
if gate is not None:
|
| 69 |
+
# Bit-identical replay of phase1.perception_gate.PerceptionGate.should_decide: the
|
| 70 |
+
# cached novelty (tuning.cache.build_novelty, keys f"{t:.1f}") uses the SAME grayscale
|
| 71 |
+
# 64x36 mean-abs-diff feature the live gate computes, and the live policy runs this
|
| 72 |
+
# check BEFORE the VLM request (policy_api.step) — skip means no API call.
|
| 73 |
+
nov = NOVELTY[qid][f"{t:.1f}"]
|
| 74 |
+
fire = (t <= gate["warmup_s"]) or (nov >= gate["novelty"]) \
|
| 75 |
+
or (t - last_fire >= gate["max_silence_s"])
|
| 76 |
+
if not fire:
|
| 77 |
+
continue
|
| 78 |
+
last_fire = t
|
| 79 |
+
calls += len(cs)
|
| 80 |
+
for c in cs:
|
| 81 |
+
d = c.get(qid, t)
|
| 82 |
+
if d and d["decision"] == "NEW_ANSWER" and d["confidence"] >= THETA:
|
| 83 |
+
dr = d["draft"].strip()
|
| 84 |
+
if dr and not g.is_duplicate(dr, said):
|
| 85 |
+
said.append(dr)
|
| 86 |
+
res.append({"time": round(t, 3), "content": dr})
|
| 87 |
+
return qid, (res, calls)
|
| 88 |
+
with ThreadPoolExecutor(max_workers=3) as ex:
|
| 89 |
+
out = dict(ex.map(one, GOLD))
|
| 90 |
+
preds = {q: v[0] for q, v in out.items()}
|
| 91 |
+
calls = sum(v[1] for v in out.values()) / len(out)
|
| 92 |
+
return preds, calls
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def row(label, **kw):
|
| 96 |
+
pred, calls = replay(**kw)
|
| 97 |
+
r = faithful_score(GOLD, pred, workers=3)
|
| 98 |
+
emit = sum(len(v) for v in pred.values()) / len(pred)
|
| 99 |
+
print(f" {label:46} score={r['score']:.4f} PAUC={r['PAUC']:.4f} dup={r['duplicate']:.4f} "
|
| 100 |
+
f"emits/video={emit:4.1f} VLM-calls/video={calls:5.1f}", flush=True)
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
def table1():
|
| 104 |
+
print("Table 1 — main progression: score AND cost (holdout, official protocol)", flush=True)
|
| 105 |
+
row("BASE (focused prompt), k=16", names=["base"], k=16)
|
| 106 |
+
row("COMP (comprehensive), k=16", names=["comp"], k=16)
|
| 107 |
+
row("EXH (exhaustive+OCR), k=16", names=["exh"], k=16)
|
| 108 |
+
row("EXH+COMP ensemble, k=32", names=["exh", "comp"], k=32)
|
| 109 |
+
row("EXH+COMP+FS ensemble, k=40 [max-score]", names=["exh", "comp", "fs"], k=40)
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def table2():
|
| 113 |
+
print("Table 2 — ensemble ablation (calls per frame)", flush=True)
|
| 114 |
+
row("1x EXH alone, k=16", names=["exh"], k=16)
|
| 115 |
+
row("1x FS alone (EXH rules + few-shot), k=16", names=["fs"], k=16)
|
| 116 |
+
row("2x EXH+COMP, k=32", names=["exh", "comp"], k=32)
|
| 117 |
+
row("3x EXH+COMP+FS, k=40", names=["exh", "comp", "fs"], k=40)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def table3():
|
| 121 |
+
print("Table 3 — dedup ablation (on EXH+COMP, k=32)", flush=True)
|
| 122 |
+
row("two-stage entailment (ours)", names=["exh", "comp"], k=32, use_llm=True)
|
| 123 |
+
row("cosine-only", names=["exh", "comp"], k=32, use_llm=False)
|
| 124 |
+
row("none (emit everything)", names=["exh", "comp"], k=32, use_llm=False, sim_high=2.0)
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
# Gate operating points: "deployed" = the configuration our initial system shipped with.
|
| 128 |
+
GATES = [("gate off (max coverage)", None),
|
| 129 |
+
("gate: nov>=8, silence 1.5s (deployed)", {"novelty": 8, "max_silence_s": 1.5, "warmup_s": 1.0}),
|
| 130 |
+
("gate: nov>=16, silence 3s (aggressive)", {"novelty": 16, "max_silence_s": 3.0, "warmup_s": 1.0})]
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def gate():
|
| 134 |
+
print("Table 4 — perception-gate cost/score frontier", flush=True)
|
| 135 |
+
print(" on EXH alone (k=16):", flush=True)
|
| 136 |
+
for lab, gcfg in GATES:
|
| 137 |
+
row(" " + lab, names=["exh"], k=16, gate=gcfg)
|
| 138 |
+
print(" on the full ensemble EXH+COMP+FS (k=40):", flush=True)
|
| 139 |
+
for lab, gcfg in GATES:
|
| 140 |
+
row(" " + lab, names=["exh", "comp", "fs"], k=40, gate=gcfg)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def sensitivity():
|
| 144 |
+
print("FS example-sensitivity (same shape, different examples)", flush=True)
|
| 145 |
+
row("EXH+COMP+FS (paper examples), k=40", names=["exh", "comp", "fs"], k=40)
|
| 146 |
+
row("EXH+COMP+FS-alt (swapped examples), k=40", names=["exh", "comp", "fs_alt"], k=40)
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
if __name__ == "__main__":
|
| 150 |
+
ap = argparse.ArgumentParser(description="Reproduce the report's experiment tables.")
|
| 151 |
+
ap.add_argument("what", choices=["table1", "table2", "table3", "gate", "sensitivity", "all"])
|
| 152 |
+
a = ap.parse_args()
|
| 153 |
+
todo = [table1, table2, table3, gate, sensitivity] if a.what == "all" else [globals()[a.what]]
|
| 154 |
+
for fn in todo:
|
| 155 |
+
fn()
|
| 156 |
+
print(flush=True)
|
| 157 |
+
emb_cache.save_disk()
|
pyproject.toml
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[build-system]
|
| 2 |
+
requires = ["setuptools>=61"]
|
| 3 |
+
build-backend = "setuptools.build_meta"
|
| 4 |
+
|
| 5 |
+
[project]
|
| 6 |
+
name = "humomni-track2"
|
| 7 |
+
version = "0.1.0"
|
| 8 |
+
description = "HumOmni 2026 Track 2 (ProactivEval) — causal proactive streaming video QA (Phase-1 API orchestrator)"
|
| 9 |
+
requires-python = ">=3.10"
|
| 10 |
+
dependencies = [
|
| 11 |
+
"openai", "pillow", "numpy", "scikit-learn", "sentence-transformers", "tqdm",
|
| 12 |
+
]
|
| 13 |
+
# Note: the Gemini judge talks to Vertex AI express mode over plain REST (stdlib urllib) — no SDK dep.
|
| 14 |
+
|
| 15 |
+
[tool.setuptools.packages.find]
|
| 16 |
+
where = ["src"]
|
| 17 |
+
|
| 18 |
+
[tool.pytest.ini_options]
|
| 19 |
+
testpaths = ["tests"]
|
scripts/build_submission.py
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# build_submission.py — ONE command from the test set to a validated submission.
|
| 3 |
+
#
|
| 4 |
+
# python scripts/build_submission.py --out submissions/tryanderror2.jsonl
|
| 5 |
+
#
|
| 6 |
+
# Everything is config-driven: the METHOD lives in config.json's "ensemble" block (v5+v3+v8 @ recent_k=40,
|
| 7 |
+
# the highest-score config = holdout 0.8018) + "dedup"/"gate"/"theta"; secrets (OPENROUTER_API_KEY for the
|
| 8 |
+
# VLM, VERTEX_KEY for the LLM dedup) live in .env. The script:
|
| 9 |
+
# 1. for EACH ensemble prompt, ensures a per-prompt decision cache exists — BUILDING it from the test
|
| 10 |
+
# frames (data/phase1/data) if missing/incomplete (resumable, skips cached frames);
|
| 11 |
+
# 2. REPLAYS the caches through the SAME merge + entailment-dedup as the live policy (APIPolicy.step) —
|
| 12 |
+
# identical output to `run_inference.py --policy api` (live ≡ replay: each prompt per frame, said=[],
|
| 13 |
+
# gate-off, then dedup);
|
| 14 |
+
# 3. writes the JSONL and VALIDATES it (500 ids, times ×0.5).
|
| 15 |
+
# First run builds the caches (PAID, ~1× VLM call per prompt per frame); subsequent runs are ~free.
|
| 16 |
+
# The only manual step is naming the upload file exactly your Team ID.
|
| 17 |
+
import argparse
|
| 18 |
+
import glob
|
| 19 |
+
import json
|
| 20 |
+
import os
|
| 21 |
+
import sys
|
| 22 |
+
import threading
|
| 23 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 24 |
+
|
| 25 |
+
import humomni.core.emb_cache as emb_cache
|
| 26 |
+
import humomni.core.judges as judges
|
| 27 |
+
from humomni.tuning.cache import VLMCache, frames_of
|
| 28 |
+
from humomni.phase1.dedup import EntailmentGuard
|
| 29 |
+
from humomni.core.validate_submission import validate, gold_ids_from_dir
|
| 30 |
+
|
| 31 |
+
_save_lock = threading.Lock()
|
| 32 |
+
_progress = [0]
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _checkpoint(every=20):
|
| 36 |
+
"""Persist the (LLM-dedup) judge cache every `every` samples so a Vertex 429 mid-build never
|
| 37 |
+
re-pays for finished work — a resubmit resumes from the disk cache. The original one-off never
|
| 38 |
+
saved these, which is why a fresh build floods Vertex with uncached entailment calls."""
|
| 39 |
+
with _save_lock:
|
| 40 |
+
_progress[0] += 1
|
| 41 |
+
if _progress[0] % every == 0:
|
| 42 |
+
judges.jc_save()
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def build(data_dir, out, cfg, cache_tpl, workers, limit=0, build_workers=8):
|
| 46 |
+
ens = cfg.get("ensemble")
|
| 47 |
+
if not ens:
|
| 48 |
+
sys.exit("config.json has no 'ensemble' block — the best method is the v5+v3 ensemble; add it.")
|
| 49 |
+
prompts = ens["prompts"]
|
| 50 |
+
recent_k = ens.get("recent_k", cfg.get("dedup", {}).get("recent_k", 4))
|
| 51 |
+
theta = cfg.get("theta", 0.6)
|
| 52 |
+
d = cfg.get("dedup", {})
|
| 53 |
+
|
| 54 |
+
# Optional perception gate (config.json "gate", use_gate=true): replayed BIT-IDENTICALLY to the live
|
| 55 |
+
# phase1.perception_gate (same grayscale-novelty feature, same fire rule, runs BEFORE the VLM lookup).
|
| 56 |
+
# Novelty per frame is computed once on CPU (free) and cached; gated-off frames emit nothing.
|
| 57 |
+
g = cfg.get("gate", {})
|
| 58 |
+
novelty = None
|
| 59 |
+
if g.get("use_gate"):
|
| 60 |
+
from humomni.tuning.cache import build_novelty
|
| 61 |
+
nov_path = "cache/phase1_novelty.json"
|
| 62 |
+
if not os.path.exists(nov_path):
|
| 63 |
+
print(f"[gate] computing per-frame novelty for {data_dir} -> {nov_path} (CPU, one-off) …", flush=True)
|
| 64 |
+
build_novelty(data_dir=data_dir, out=nov_path) # ALWAYS full — a --limit-partial cache would poison later runs
|
| 65 |
+
novelty = json.load(open(nov_path, encoding="utf-8"))
|
| 66 |
+
print(f"[gate] ON — novelty>={g['novelty']}, max_silence={g['max_silence_s']}s, warmup={g['warmup_s']}s", flush=True)
|
| 67 |
+
|
| 68 |
+
# Ensure a per-prompt decision cache for EACH ensemble prompt, BUILDING it from the test frames if
|
| 69 |
+
# missing/incomplete (resumable — skips already-cached frames). This makes the whole pipeline a SINGLE
|
| 70 |
+
# command from the test set to the submission; the config lives in config.json + secrets in .env.
|
| 71 |
+
# Building calls the VLM (PAID, ~1× per prompt per frame); a complete cache is a no-op replay.
|
| 72 |
+
from humomni.tuning.cache import build_web_cache
|
| 73 |
+
from humomni.phase1.vlm_client import prompt_for
|
| 74 |
+
caches = {}
|
| 75 |
+
for p in prompts:
|
| 76 |
+
path = cache_tpl.format(p)
|
| 77 |
+
print(f"[cache] ensuring {path} (prompt {p}) from {data_dir} …", flush=True)
|
| 78 |
+
build_web_cache(data_dir=data_dir, cache_path=path, limit=limit, workers=build_workers, prompt=prompt_for(p))
|
| 79 |
+
caches[p] = VLMCache(path)
|
| 80 |
+
|
| 81 |
+
emb_cache.load_disk() # prewarm draft embeddings (cosine dedup prefilter)
|
| 82 |
+
judges.jc_load() # reuse any saved LLM-dedup results (resume / re-run = free)
|
| 83 |
+
warm = [v["draft"].strip() for c in caches.values() for v in c.mem.values() if v.get("draft", "").strip()]
|
| 84 |
+
if warm:
|
| 85 |
+
emb_cache.embed_many(warm)
|
| 86 |
+
dirs = sorted(x for x in glob.glob(os.path.join(data_dir, "*")) if os.path.isdir(x))
|
| 87 |
+
if limit:
|
| 88 |
+
dirs = dirs[:limit] # quick correctness check (first N samples, same sort order)
|
| 89 |
+
|
| 90 |
+
# Sample-level RESUME: completed records are appended to a `<out>.partial` sidecar as they finish,
|
| 91 |
+
# so a killed build resumes instead of restarting (the dedup judge cache is already persisted
|
| 92 |
+
# incrementally). The final `out` is written — and the sidecar dropped — only once all ids are done.
|
| 93 |
+
partial = out + ".partial"
|
| 94 |
+
done = {}
|
| 95 |
+
if os.path.exists(partial):
|
| 96 |
+
for line in open(partial, encoding="utf-8"):
|
| 97 |
+
try:
|
| 98 |
+
done[json.loads(line)["question_id"]] = line.rstrip("\n")
|
| 99 |
+
except Exception:
|
| 100 |
+
pass
|
| 101 |
+
if done:
|
| 102 |
+
print(f"[resume] {len(done)} samples already done in {partial}", flush=True)
|
| 103 |
+
pw = open(partial, "a", encoding="utf-8")
|
| 104 |
+
pwlock = threading.Lock()
|
| 105 |
+
|
| 106 |
+
def one(sd):
|
| 107 |
+
qid = json.load(open(os.path.join(sd, "question.json"), encoding="utf-8"))["question_id"]
|
| 108 |
+
if qid in done: # resumed — skip, keep its earlier result
|
| 109 |
+
return
|
| 110 |
+
# one shared guard per sample == APIPolicy.step's dedup (sim band + LLM entailment, recent_k).
|
| 111 |
+
guard = EntailmentGuard(sim_high=d.get("sim_high", 0.97), sim_low=d.get("sim_low", 0.55),
|
| 112 |
+
recent_k=recent_k, use_llm=d.get("use_llm", True))
|
| 113 |
+
said, res, last_fire = [], [], -1e9
|
| 114 |
+
for t, _ in frames_of(qid, data_dir): # ASCENDING time — causal (frames ≤ t only)
|
| 115 |
+
if novelty is not None: # gate replay == PerceptionGate.should_decide
|
| 116 |
+
nov = novelty[qid][f"{t:.1f}"]
|
| 117 |
+
fire = (t <= g["warmup_s"]) or (nov >= g["novelty"]) or (t - last_fire >= g["max_silence_s"])
|
| 118 |
+
if not fire:
|
| 119 |
+
continue
|
| 120 |
+
last_fire = t
|
| 121 |
+
for p in prompts: # merge order = prompt order (v5 first, then v3)
|
| 122 |
+
dec = caches[p].get(qid, t)
|
| 123 |
+
if dec and dec["decision"] == "NEW_ANSWER" and dec["confidence"] >= theta:
|
| 124 |
+
draft = dec["draft"].strip()
|
| 125 |
+
if draft and not guard.is_duplicate(draft, said):
|
| 126 |
+
said.append(draft)
|
| 127 |
+
res.append({"time": round(t, 3), "content": draft})
|
| 128 |
+
line = json.dumps({"question_id": qid, "model_response_list": res}, ensure_ascii=False)
|
| 129 |
+
with pwlock: # append + fsync the completed sample immediately
|
| 130 |
+
pw.write(line + "\n"); pw.flush(); os.fsync(pw.fileno())
|
| 131 |
+
done[qid] = line
|
| 132 |
+
_checkpoint()
|
| 133 |
+
|
| 134 |
+
try:
|
| 135 |
+
with ThreadPoolExecutor(max_workers=workers) as ex:
|
| 136 |
+
list(ex.map(one, dirs))
|
| 137 |
+
finally:
|
| 138 |
+
pw.close()
|
| 139 |
+
judges.jc_save() # persist dedup results even on a mid-build 429
|
| 140 |
+
emb_cache.save_disk()
|
| 141 |
+
order = [json.load(open(os.path.join(sd, "question.json"), encoding="utf-8"))["question_id"] for sd in dirs]
|
| 142 |
+
records = [json.loads(done[q]) for q in order if q in done] # final output in dir order
|
| 143 |
+
with open(out, "w", encoding="utf-8") as w:
|
| 144 |
+
for r in records:
|
| 145 |
+
w.write(json.dumps(r, ensure_ascii=False) + "\n")
|
| 146 |
+
if len(records) == len(dirs) and os.path.exists(partial):
|
| 147 |
+
os.remove(partial) # complete -> drop the sidecar
|
| 148 |
+
return records
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
if __name__ == "__main__":
|
| 152 |
+
ap = argparse.ArgumentParser(description="Build + validate the best Phase-1 submission from per-prompt caches.")
|
| 153 |
+
ap.add_argument("--data_dir", default="data/phase1/data")
|
| 154 |
+
ap.add_argument("--out", default="submissions/submission.jsonl")
|
| 155 |
+
ap.add_argument("--config", default="config.json")
|
| 156 |
+
ap.add_argument("--cache_tpl", default="cache/phase1_{}.jsonl", help="per-prompt cache path ({} = prompt name)")
|
| 157 |
+
ap.add_argument("--workers", type=int, default=1,
|
| 158 |
+
help="parallel samples. Keep at 1 on a COLD judge cache (the LLM dedup floods Vertex → 429); "
|
| 159 |
+
"raise to 3-4 once cached (re-run is then free).")
|
| 160 |
+
ap.add_argument("--limit", type=int, default=0, help="only the first N samples (quick correctness check)")
|
| 161 |
+
ap.add_argument("--build_workers", type=int, default=8, help="parallelism for BUILDING missing per-prompt VLM caches")
|
| 162 |
+
a = ap.parse_args()
|
| 163 |
+
cfg = json.load(open(a.config, encoding="utf-8"))
|
| 164 |
+
os.makedirs(os.path.dirname(a.out) or ".", exist_ok=True)
|
| 165 |
+
|
| 166 |
+
records = build(a.data_dir, a.out, cfg, a.cache_tpl, a.workers, a.limit, a.build_workers)
|
| 167 |
+
avg = sum(len(r["model_response_list"]) for r in records) / max(1, len(records))
|
| 168 |
+
ens = cfg["ensemble"]
|
| 169 |
+
print(f"wrote {a.out}: {len(records)} samples, avg {avg:.1f} resp/sample "
|
| 170 |
+
f"(ensemble {ens['prompts']} @ recent_k={ens.get('recent_k')})")
|
| 171 |
+
|
| 172 |
+
errs = validate(a.out, gold_ids_from_dir(a.data_dir)) # same checks as validate_submission.py
|
| 173 |
+
if errs:
|
| 174 |
+
print(f"INVALID — {len(errs)} problem(s):")
|
| 175 |
+
for e in errs[:20]:
|
| 176 |
+
print(" -", e)
|
| 177 |
+
sys.exit(1)
|
| 178 |
+
print(f"VALID — all ids present, times are multiples of 0.5. Rename {a.out} to your Team ID to upload.")
|
scripts/infer_simple.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# infer_simple.py — the minimal causal inference loop, for reading and reference.
|
| 3 |
+
#
|
| 4 |
+
# Pick a policy (api or rl), stream each sample's frames one at a time in time order, and write
|
| 5 |
+
# one JSONL line per sample. A failing sample is skipped (empty record, run continues) so the
|
| 6 |
+
# output always has every id. The production runner (run_inference.py) adds the rest for a real
|
| 7 |
+
# paid 500-sample run — 3x retry, --resume, parallel workers, client warm-up — which this file
|
| 8 |
+
# deliberately omits.
|
| 9 |
+
import argparse
|
| 10 |
+
import glob
|
| 11 |
+
import json
|
| 12 |
+
import os
|
| 13 |
+
|
| 14 |
+
from humomni.core.streaming_driver import _emissions # SAME normalization as run_inference -> identical output
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def make_policy(name):
|
| 18 |
+
"""A fresh policy per sample (no state leak). Imported lazily — each pulls heavy deps."""
|
| 19 |
+
if name == "api":
|
| 20 |
+
from humomni.phase1.policy_api import APIPolicy
|
| 21 |
+
config = json.load(open("config.json", encoding="utf-8"))
|
| 22 |
+
return lambda: APIPolicy(config=config)
|
| 23 |
+
raise SystemExit(f"unknown policy: {name!r} (use 'api')")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _video_length(sample_dir):
|
| 27 |
+
"""The latest frame timestamp present — the 'video_length' the loop steps up to (frames sit on a
|
| 28 |
+
0.5 s grid: 0.5.jpg, 1.0.jpg, …)."""
|
| 29 |
+
ts = []
|
| 30 |
+
for p in glob.glob(os.path.join(sample_dir, "*.jpg")):
|
| 31 |
+
try:
|
| 32 |
+
ts.append(float(os.path.basename(p)[:-4]))
|
| 33 |
+
except ValueError:
|
| 34 |
+
pass # skip non-frame files
|
| 35 |
+
return max(ts) if ts else 0.0
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def infer_one(sample_dir, policy):
|
| 39 |
+
"""One sample, matching the competition inference pseudocode:
|
| 40 |
+
|
| 41 |
+
input(question)
|
| 42 |
+
for current_time in range(0.5, video_length, 0.5):
|
| 43 |
+
input(f"{current_time}.png") # frames at t > current_time are PROHIBITED
|
| 44 |
+
if respond_now: output(... @ current_time)
|
| 45 |
+
|
| 46 |
+
The policy gets one frame per tick, in ascending time, and never sees t > current_time.
|
| 47 |
+
policy.step() may return None / a str / a list of strs — one frame can emit several answers
|
| 48 |
+
(the v5+v3 ensemble); we append each AT current_time, exactly like streaming_driver.drive().
|
| 49 |
+
A transient failure returns an empty-but-present record (the run never aborts and every id
|
| 50 |
+
stays); a causality AssertionError is not caught. No retry/resume/parallelism — that's
|
| 51 |
+
run_inference.py.
|
| 52 |
+
"""
|
| 53 |
+
with open(os.path.join(sample_dir, "question.json"), encoding="utf-8") as f:
|
| 54 |
+
q = json.load(f)
|
| 55 |
+
question, qid = q["question"], q["question_id"]
|
| 56 |
+
try:
|
| 57 |
+
policy.reset(question=question)
|
| 58 |
+
responses, last_t = [], -1.0
|
| 59 |
+
video_length = _video_length(sample_dir)
|
| 60 |
+
for step in range(1, int(round(video_length / 0.5)) + 1): # current_time = 0.5 .. video_length
|
| 61 |
+
current_time = step * 0.5
|
| 62 |
+
frame_path = os.path.join(sample_dir, f"{current_time:.1f}.jpg")
|
| 63 |
+
if not os.path.exists(frame_path): # missing tick on the grid — skip
|
| 64 |
+
continue
|
| 65 |
+
assert current_time > last_t, "frames must arrive in order (no future frames)"
|
| 66 |
+
last_t = current_time
|
| 67 |
+
reply = policy.step(current_time, frame_path)
|
| 68 |
+
for content in _emissions(reply): # 0..N answers, all at current_time
|
| 69 |
+
responses.append({"time": current_time, "content": content})
|
| 70 |
+
return {"question_id": qid, "model_response_list": responses}
|
| 71 |
+
except AssertionError:
|
| 72 |
+
raise
|
| 73 |
+
except Exception as e:
|
| 74 |
+
print(f" ! {qid} failed ({type(e).__name__}: {str(e)[:80]}) — empty record")
|
| 75 |
+
return {"question_id": qid, "model_response_list": []}
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def main():
|
| 79 |
+
ap = argparse.ArgumentParser()
|
| 80 |
+
ap.add_argument("--policy", choices=["api"], default="api")
|
| 81 |
+
ap.add_argument("--data_dir", default="data/phase1/data")
|
| 82 |
+
ap.add_argument("--out", default="submission.jsonl")
|
| 83 |
+
a = ap.parse_args()
|
| 84 |
+
|
| 85 |
+
new_policy = make_policy(a.policy)
|
| 86 |
+
dirs = [d for d in sorted(glob.glob(os.path.join(a.data_dir, "*"))) if os.path.isdir(d)]
|
| 87 |
+
total = len(dirs)
|
| 88 |
+
with open(a.out, "w", encoding="utf-8") as w:
|
| 89 |
+
for i, sample_dir in enumerate(dirs, 1):
|
| 90 |
+
rec = infer_one(sample_dir, new_policy()) # fresh policy per sample
|
| 91 |
+
w.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
| 92 |
+
print(f"[{i}/{total}] {rec['question_id']} -> {len(rec['model_response_list'])} responses",
|
| 93 |
+
flush=True) # flush so progress shows live
|
| 94 |
+
print(f"done {total} samples -> {a.out}")
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
if __name__ == "__main__":
|
| 98 |
+
main()
|
scripts/run_inference.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# run_inference.py — drive every sample through a policy -> one JSONL line per sample.
|
| 2 |
+
# Works for any policy (SilentPolicy, APIPolicy, RLPolicy later). EVERY question_id must
|
| 3 |
+
# appear, even with an empty model_response_list. Robust for paid runs:
|
| 4 |
+
# - per-sample retry (transient Windows Errno 22 / API blips) with empty-record fallback,
|
| 5 |
+
# so ONE bad sample never aborts the whole run;
|
| 6 |
+
# - incremental flushed append, so a crash never loses (or re-pays for) finished samples;
|
| 7 |
+
# - --resume skips question_ids already present in the output.
|
| 8 |
+
import glob
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import time
|
| 12 |
+
import argparse
|
| 13 |
+
import threading
|
| 14 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 15 |
+
|
| 16 |
+
from humomni.core.streaming_driver import run_sample
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def make_policy_factory(name, config):
|
| 20 |
+
"""Return a zero-arg factory that builds a FRESH policy per sample (no state leak)."""
|
| 21 |
+
if name == "silent":
|
| 22 |
+
from humomni.phase1.policies import SilentPolicy
|
| 23 |
+
return lambda: SilentPolicy()
|
| 24 |
+
if name == "api":
|
| 25 |
+
from humomni.phase1.policy_api import APIPolicy # imported lazily (needs deps + API key)
|
| 26 |
+
return lambda: APIPolicy(config=config)
|
| 27 |
+
raise ValueError(f"unknown policy {name!r}")
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _qid(d):
|
| 31 |
+
with open(os.path.join(d, "question.json"), encoding="utf-8") as f:
|
| 32 |
+
return json.load(f)["question_id"]
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _done_qids(out):
|
| 36 |
+
done = set()
|
| 37 |
+
if os.path.exists(out):
|
| 38 |
+
with open(out, encoding="utf-8") as f:
|
| 39 |
+
for ln in f:
|
| 40 |
+
ln = ln.strip()
|
| 41 |
+
if ln:
|
| 42 |
+
try:
|
| 43 |
+
done.add(json.loads(ln)["question_id"])
|
| 44 |
+
except Exception:
|
| 45 |
+
pass
|
| 46 |
+
return done
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def main(data_dir, out, policy_factory, limit=0, workers=1, resume=False):
|
| 50 |
+
dirs = sorted(d for d in glob.glob(os.path.join(data_dir, "*")) if os.path.isdir(d))
|
| 51 |
+
if limit:
|
| 52 |
+
dirs = dirs[:limit] # used by experiment.py for fast partial Phase-2 evals
|
| 53 |
+
done = _done_qids(out) if resume else set()
|
| 54 |
+
if not resume:
|
| 55 |
+
open(out, "w", encoding="utf-8").close() # truncate (fresh run)
|
| 56 |
+
todo = [d for d in dirs if _qid(d) not in done]
|
| 57 |
+
print(f"{len(dirs)} samples | {len(done)} already done | {len(todo)} to run", flush=True)
|
| 58 |
+
|
| 59 |
+
lock = threading.Lock()
|
| 60 |
+
fails = []
|
| 61 |
+
wfile = open(out, "a", encoding="utf-8")
|
| 62 |
+
|
| 63 |
+
def process(d):
|
| 64 |
+
for attempt in range(3):
|
| 65 |
+
try:
|
| 66 |
+
return run_sample(d, policy_factory()) # fresh policy state per sample
|
| 67 |
+
except Exception as e:
|
| 68 |
+
if attempt == 2:
|
| 69 |
+
fails.append((_qid(d), str(e)[:120]))
|
| 70 |
+
return {"question_id": _qid(d), "model_response_list": []} # keep id present
|
| 71 |
+
time.sleep(0.5)
|
| 72 |
+
|
| 73 |
+
def handle(d):
|
| 74 |
+
rec = process(d)
|
| 75 |
+
with lock:
|
| 76 |
+
wfile.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
| 77 |
+
wfile.flush()
|
| 78 |
+
return rec
|
| 79 |
+
|
| 80 |
+
try:
|
| 81 |
+
if workers > 1:
|
| 82 |
+
with ThreadPoolExecutor(max_workers=workers) as ex:
|
| 83 |
+
for n, _ in enumerate(ex.map(handle, todo), 1):
|
| 84 |
+
if n % 50 == 0:
|
| 85 |
+
print(f" ...{n}/{len(todo)}", flush=True)
|
| 86 |
+
else:
|
| 87 |
+
for n, d in enumerate(todo, 1):
|
| 88 |
+
handle(d)
|
| 89 |
+
if n % 50 == 0:
|
| 90 |
+
print(f" ...{n}/{len(todo)}", flush=True)
|
| 91 |
+
finally:
|
| 92 |
+
wfile.close()
|
| 93 |
+
print(f"wrote -> {out} (failed samples: {len(fails)})")
|
| 94 |
+
for q, e in fails[:10]:
|
| 95 |
+
print(" FAIL", q, e)
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
if __name__ == "__main__":
|
| 99 |
+
ap = argparse.ArgumentParser()
|
| 100 |
+
ap.add_argument("--data_dir", default="data/phase1/data")
|
| 101 |
+
ap.add_argument("--out", default="submission.jsonl")
|
| 102 |
+
ap.add_argument("--policy", default="api", choices=["silent", "api"])
|
| 103 |
+
ap.add_argument("--config", default="config.json", help="orchestrator config (gate/theta/dedup)")
|
| 104 |
+
ap.add_argument("--limit", type=int, default=0, help="process only the first N samples (0=all; experiment.py uses it for fast Phase-2 evals)")
|
| 105 |
+
ap.add_argument("--workers", type=int, default=1, help="parallel samples (independent, still causal)")
|
| 106 |
+
ap.add_argument("--resume", action="store_true", help="skip question_ids already in --out")
|
| 107 |
+
a = ap.parse_args()
|
| 108 |
+
cfg = None
|
| 109 |
+
if a.policy == "api" and os.path.exists(a.config):
|
| 110 |
+
cfg = json.load(open(a.config, encoding="utf-8"))
|
| 111 |
+
|
| 112 |
+
if a.policy == "api" and a.workers > 1:
|
| 113 |
+
# Warm BOTH API clients + their lazy pydantic imports in the MAIN thread first;
|
| 114 |
+
# otherwise concurrent first-use across worker threads races and throws
|
| 115 |
+
# "No module named 'pydantic._migration'".
|
| 116 |
+
import humomni.phase1.vlm_client as vlm_client
|
| 117 |
+
try:
|
| 118 |
+
vlm_client._client_().chat.completions.create(
|
| 119 |
+
model=vlm_client.MODEL, max_tokens=1,
|
| 120 |
+
messages=[{"role": "user", "content": "ping"}])
|
| 121 |
+
except Exception:
|
| 122 |
+
pass
|
| 123 |
+
if (cfg or {}).get("dedup", {}).get("use_llm"):
|
| 124 |
+
import humomni.core.judges as judges
|
| 125 |
+
try:
|
| 126 |
+
judges.gemini("ping")
|
| 127 |
+
except Exception:
|
| 128 |
+
pass
|
| 129 |
+
try:
|
| 130 |
+
import humomni.core.emb_cache as emb_cache # warm MiniLM (cosine dedup prefilter) single-threaded
|
| 131 |
+
emb_cache.embed("ping")
|
| 132 |
+
except Exception:
|
| 133 |
+
pass
|
| 134 |
+
|
| 135 |
+
main(a.data_dir, a.out, make_policy_factory(a.policy, cfg),
|
| 136 |
+
limit=a.limit, workers=a.workers, resume=a.resume)
|
src/humomni/__init__.py
ADDED
|
File without changes
|
src/humomni/core/__init__.py
ADDED
|
File without changes
|
src/humomni/core/context.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# context.py — build the VLM input under a token/cost budget (§4).
|
| 2 |
+
# Recent frames at full detail, older history as sparse downscaled keyframes, plus an
|
| 3 |
+
# explicit memory of what we already said (structural anti-repeat). Causal by construction:
|
| 4 |
+
# `history` only ever contains frames with timestamp <= now.
|
| 5 |
+
# Frame budget is env-tunable (CTX_RECENT_S / CTX_HIST_STRIDE_S) so the perception lever can be
|
| 6 |
+
# swept without code edits; defaults reproduce the shipped behavior.
|
| 7 |
+
import os
|
| 8 |
+
|
| 9 |
+
RECENT_S = float(os.environ.get("CTX_RECENT_S", "6.0")) # last N s at full res (~2N frames @ 2 fps)
|
| 10 |
+
HIST_STRIDE_S = float(os.environ.get("CTX_HIST_STRIDE_S", "2.0")) # older history: 1 keyframe / N s
|
| 11 |
+
STEP = 0.5
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def build_context(question, history, said):
|
| 15 |
+
"""history: ascending list of (t, frame_path); said: list of emitted strings."""
|
| 16 |
+
now = history[-1][0]
|
| 17 |
+
recent = [(t, p) for (t, p) in history if now - t <= RECENT_S]
|
| 18 |
+
older = [(t, p) for (t, p) in history if now - t > RECENT_S]
|
| 19 |
+
keyframes = older[::max(1, int(HIST_STRIDE_S / STEP))] # subsample older frames
|
| 20 |
+
return {
|
| 21 |
+
"question": question,
|
| 22 |
+
"now": now,
|
| 23 |
+
"recent_frames": recent, # sent at native 480x270
|
| 24 |
+
"history_frames": keyframes, # downscaled in vlm_client
|
| 25 |
+
"already_said": said, # anti-repeat memory
|
| 26 |
+
}
|
src/humomni/core/emb_cache.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# emb_cache.py — one shared MiniLM instance + a text->vector cache (in-memory + disk).
|
| 2 |
+
# The fast metric (emb_judge) and the dedup guard both embed the same gold refs / drafts
|
| 3 |
+
# many times; caching by text turns repeated encodes into dict lookups, which is what makes
|
| 4 |
+
# offline replay and tuning run in seconds instead of minutes.
|
| 5 |
+
import os
|
| 6 |
+
import json
|
| 7 |
+
import threading
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
from humomni.core.env_util import load_env
|
| 12 |
+
|
| 13 |
+
load_env() # set HF_TOKEN / HF_HUB_OFFLINE from .env BEFORE sentence_transformers/huggingface_hub load
|
| 14 |
+
|
| 15 |
+
# Persist as a plain .npy matrix + a .json key list (NO pickle / allow_pickle) so loading
|
| 16 |
+
# never executes arbitrary code, even though this cache is only ever produced locally.
|
| 17 |
+
PATH = "cache/emb_cache" # -> emb_cache.npy + emb_cache.keys.json
|
| 18 |
+
_model = None
|
| 19 |
+
_cache = {}
|
| 20 |
+
_lock = threading.Lock()
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _model_():
|
| 24 |
+
global _model
|
| 25 |
+
if _model is None:
|
| 26 |
+
from sentence_transformers import SentenceTransformer
|
| 27 |
+
_model = SentenceTransformer("all-MiniLM-L6-v2")
|
| 28 |
+
return _model
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def load_disk(path=PATH):
|
| 32 |
+
npy, kj = path + ".npy", path + ".keys.json"
|
| 33 |
+
if os.path.exists(npy) and os.path.exists(kj):
|
| 34 |
+
vecs = np.load(npy) # plain float matrix, no pickle
|
| 35 |
+
keys = json.load(open(kj, encoding="utf-8"))
|
| 36 |
+
for k, v in zip(keys, vecs):
|
| 37 |
+
_cache.setdefault(k, v)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def save_disk(path=PATH):
|
| 41 |
+
if not _cache:
|
| 42 |
+
return
|
| 43 |
+
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
| 44 |
+
keys = list(_cache.keys())
|
| 45 |
+
vecs = np.stack([_cache[k] for k in keys]).astype(np.float32)
|
| 46 |
+
np.save(path + ".npy", vecs)
|
| 47 |
+
json.dump(keys, open(path + ".keys.json", "w", encoding="utf-8"))
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def embed_many(texts):
|
| 51 |
+
"""Return unit-norm vectors for texts (order preserved), encoding only cache misses."""
|
| 52 |
+
miss = [t for t in texts if t not in _cache]
|
| 53 |
+
if miss:
|
| 54 |
+
uniq = list(dict.fromkeys(miss))
|
| 55 |
+
with _lock:
|
| 56 |
+
still = [t for t in uniq if t not in _cache]
|
| 57 |
+
if still:
|
| 58 |
+
vecs = _model_().encode(still, normalize_embeddings=True,
|
| 59 |
+
batch_size=256, show_progress_bar=False)
|
| 60 |
+
for t, v in zip(still, np.asarray(vecs, dtype=np.float32)):
|
| 61 |
+
_cache[t] = v
|
| 62 |
+
return [_cache[t] for t in texts]
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def embed(text):
|
| 66 |
+
return embed_many([text])[0]
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def cos(a, b):
|
| 70 |
+
va, vb = embed_many([a, b])
|
| 71 |
+
return float(np.dot(va, vb))
|
src/humomni/core/env_util.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# env_util.py — minimal, dependency-free .env loader.
|
| 2 |
+
# Secrets (OPENROUTER_API_KEY) and optional model-id overrides live in .env (gitignored).
|
| 3 |
+
# Real environment variables always win over .env so CI / shell overrides keep working.
|
| 4 |
+
import os
|
| 5 |
+
import site
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def load_env(path=".env"):
|
| 10 |
+
"""Populate os.environ from a simple KEY=VALUE .env file (no-op if absent).
|
| 11 |
+
|
| 12 |
+
Existing environment variables are never overwritten. Lines that are blank,
|
| 13 |
+
comments (#...), or lack '=' are skipped. Surrounding quotes are stripped.
|
| 14 |
+
"""
|
| 15 |
+
if not os.path.exists(path):
|
| 16 |
+
return
|
| 17 |
+
with open(path, encoding="utf-8") as f:
|
| 18 |
+
for line in f:
|
| 19 |
+
line = line.strip()
|
| 20 |
+
if not line or line.startswith("#") or "=" not in line:
|
| 21 |
+
continue
|
| 22 |
+
key, _, val = line.partition("=")
|
| 23 |
+
key = key.strip()
|
| 24 |
+
val = val.strip().strip('"').strip("'")
|
| 25 |
+
if key and key not in os.environ:
|
| 26 |
+
os.environ[key] = val
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def strip_user_site_paths():
|
| 30 |
+
"""Remove user-local Python package paths from sys.path.
|
| 31 |
+
|
| 32 |
+
Some shared HPC environments inject ~/.local packages into otherwise isolated
|
| 33 |
+
conda envs. Those user-site packages can pull in binary wheels compiled against
|
| 34 |
+
a different NumPy and break imports deep inside transformers/peft.
|
| 35 |
+
"""
|
| 36 |
+
user_site = os.path.realpath(site.getusersitepackages())
|
| 37 |
+
user_base = os.path.realpath(os.path.expanduser("~/.local"))
|
| 38 |
+
keep = []
|
| 39 |
+
for path in sys.path:
|
| 40 |
+
real = os.path.realpath(path or os.getcwd())
|
| 41 |
+
if real == user_site or real.startswith(user_site + os.sep):
|
| 42 |
+
continue
|
| 43 |
+
if real == user_base or real.startswith(user_base + os.sep):
|
| 44 |
+
continue
|
| 45 |
+
keep.append(path)
|
| 46 |
+
sys.path[:] = keep
|
src/humomni/core/judges.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# judges.py — scoring/dedup judges for the PAUC harness (§9.2).
|
| 2 |
+
# llm_judge : faithful Gemini-family grader (matches the hidden leaderboard judge family)
|
| 3 |
+
# emb_judge : fast, API-free embedding approximation (never report as final)
|
| 4 |
+
# llm_entails : duplicate = "does NEW add info beyond PRIOR?" (for the dedup metric)
|
| 5 |
+
# The judge is Gemini 3.1 Flash-Lite via VERTEX AI in EXPRESS MODE — a plain REST generateContent call
|
| 6 |
+
# with an API key (no SDK / service account needed; Gemini is not served on OpenRouter). Set VERTEX_KEY
|
| 7 |
+
# (or VERTEX_API_KEY / GOOGLE_API_KEY) + JUDGE_MODEL in .env. Vertex can be slow, so a generous per-call
|
| 8 |
+
# timeout + retry/backoff are built into gemini() below. (The VLM backbone uses OpenRouter; only the
|
| 9 |
+
# judge goes through Vertex.)
|
| 10 |
+
import os
|
| 11 |
+
import re
|
| 12 |
+
import json
|
| 13 |
+
import time
|
| 14 |
+
import hashlib
|
| 15 |
+
import threading
|
| 16 |
+
import urllib.request
|
| 17 |
+
import urllib.error
|
| 18 |
+
|
| 19 |
+
from humomni.core.env_util import load_env
|
| 20 |
+
|
| 21 |
+
load_env()
|
| 22 |
+
# Vertex Gemini model id. Normalize an OpenRouter-style value (`google/gemini-3.1-flash-lite-20260507`):
|
| 23 |
+
# drop the `google/` prefix and a trailing `-YYYYMMDD` date — Vertex publisher ids have neither.
|
| 24 |
+
JUDGE_MODEL = re.sub(r"-\d{8}$", "", os.environ.get("JUDGE_MODEL", "gemini-3.1-flash-lite").split("/", 1)[-1])
|
| 25 |
+
if not re.fullmatch(r"[A-Za-z0-9.\-]+", JUDGE_MODEL): # catch a malformed .env value early (clear error vs a later 404)
|
| 26 |
+
raise ValueError(f"JUDGE_MODEL={JUDGE_MODEL!r} is not a valid Vertex model id (expected e.g. 'gemini-3.1-flash-lite')")
|
| 27 |
+
VERTEX_TIMEOUT_S = float(os.environ.get("VERTEX_TIMEOUT_S", "120")) # per-call request timeout (Vertex is slow)
|
| 28 |
+
# Express-mode REST host (global). Override for a regional endpoint, e.g. https://us-central1-aiplatform.googleapis.com
|
| 29 |
+
VERTEX_BASE_URL = os.environ.get("VERTEX_BASE_URL", "https://aiplatform.googleapis.com/v1")
|
| 30 |
+
|
| 31 |
+
# Disk-backed cache of Gemini judge/entail results so faithful evals are reusable + warmable
|
| 32 |
+
# in parallel. Keyed by a hash of the exact inputs; values are small ints/bools.
|
| 33 |
+
JUDGE_CACHE_PATH = "cache/judge_cache.json"
|
| 34 |
+
_jcache = {}
|
| 35 |
+
_jc_lock = threading.Lock()
|
| 36 |
+
_jc_loaded = False
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def jc_load():
|
| 40 |
+
# Under _jc_lock: many warm threads call this concurrently, and jc_save() rewrites the file
|
| 41 |
+
# under the same lock — without it a thread could json.load a half-written file or two threads
|
| 42 |
+
# could double-load. If the file is unreadable we refuse to proceed rather than silently starting
|
| 43 |
+
# from an empty cache (a later jc_save would then overwrite the on-disk cache with the empty one —
|
| 44 |
+
# exactly how a 480k-entry cache once got clobbered to a few thousand).
|
| 45 |
+
global _jc_loaded
|
| 46 |
+
with _jc_lock:
|
| 47 |
+
if not _jc_loaded:
|
| 48 |
+
if os.path.exists(JUDGE_CACHE_PATH):
|
| 49 |
+
_jcache.update(json.load(open(JUDGE_CACHE_PATH, encoding="utf-8")))
|
| 50 |
+
_jc_loaded = True
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def jc_save():
|
| 54 |
+
# ATOMIC: write to a temp file in the same dir, then os.replace() (atomic on Windows + POSIX).
|
| 55 |
+
# A process killed mid-save leaves the previous cache fully intact — never a truncated file.
|
| 56 |
+
os.makedirs(os.path.dirname(JUDGE_CACHE_PATH) or ".", exist_ok=True)
|
| 57 |
+
with _jc_lock:
|
| 58 |
+
tmp = JUDGE_CACHE_PATH + f".tmp{os.getpid()}"
|
| 59 |
+
with open(tmp, "w", encoding="utf-8") as f:
|
| 60 |
+
json.dump(_jcache, f)
|
| 61 |
+
os.replace(tmp, JUDGE_CACHE_PATH)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def jc_key(*parts):
|
| 65 |
+
return hashlib.sha1("\x1f".join(parts).encode()).hexdigest()
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _vertex_key():
|
| 69 |
+
"""The Vertex express-mode API key from .env (accepts a few common names)."""
|
| 70 |
+
return (os.environ.get("VERTEX_KEY") or os.environ.get("VERTEX_API_KEY")
|
| 71 |
+
or os.environ.get("GOOGLE_API_KEY"))
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
# Throttle the AGGREGATE judge call rate (regardless of worker count) to stay under the Vertex
|
| 75 |
+
# per-minute quota, and back off on timeouts/429/5xx so parallel warming can't blow it.
|
| 76 |
+
_RATE_LOCK = threading.Lock()
|
| 77 |
+
_LAST_CALL = [0.0]
|
| 78 |
+
_COOLDOWN_UNTIL = [0.0]
|
| 79 |
+
_MIN_INTERVAL = 60.0 / float(os.environ.get("JUDGE_RPM", "280")) # tune to your Vertex quota (default <300 rpm)
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def _retry_after_s(header_value):
|
| 83 |
+
"""Best-effort parse of Retry-After seconds from a Vertex 429/5xx response."""
|
| 84 |
+
if not header_value:
|
| 85 |
+
return None
|
| 86 |
+
try:
|
| 87 |
+
secs = int(float(header_value.strip()))
|
| 88 |
+
except Exception:
|
| 89 |
+
return None
|
| 90 |
+
return max(0, secs)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _throttle():
|
| 94 |
+
with _RATE_LOCK:
|
| 95 |
+
now = time.monotonic()
|
| 96 |
+
wait = max(_LAST_CALL[0] + _MIN_INTERVAL, _COOLDOWN_UNTIL[0]) - now
|
| 97 |
+
if wait > 0:
|
| 98 |
+
time.sleep(wait)
|
| 99 |
+
_LAST_CALL[0] = time.monotonic()
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def _cooldown(seconds):
|
| 103 |
+
"""Pause all subsequent judge calls for a shared cooldown window after quota/network spikes."""
|
| 104 |
+
if seconds <= 0:
|
| 105 |
+
return
|
| 106 |
+
with _RATE_LOCK:
|
| 107 |
+
_COOLDOWN_UNTIL[0] = max(_COOLDOWN_UNTIL[0], time.monotonic() + seconds)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
# --- HARD spend ceiling. Cap the number of LIVE Gemini calls across the WHOLE job (all stages and
|
| 111 |
+
# subprocesses share a counter file), so an unattended run cannot accumulate unbounded spend.
|
| 112 |
+
# 0 = unlimited; set via the JUDGE_MAX_CALLS environment variable.
|
| 113 |
+
_JUDGE_MAX = int(os.environ.get("JUDGE_MAX_CALLS", "0"))
|
| 114 |
+
_CALLS_FILE = os.environ.get("JUDGE_CALLS_FILE", "cache/.judge_calls")
|
| 115 |
+
_SINCE_SAVE = [0]
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
class JudgeBudgetExceeded(RuntimeError):
|
| 119 |
+
"""Raised when the JUDGE_MAX_CALLS live-call ceiling is hit — stop, don't keep spending."""
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def _bump_calls():
|
| 123 |
+
if not _JUDGE_MAX:
|
| 124 |
+
return
|
| 125 |
+
with _RATE_LOCK: # serialize the read-modify-write (approx across processes is fine)
|
| 126 |
+
try:
|
| 127 |
+
n = int((open(_CALLS_FILE, encoding="utf-8").read().strip() or "0"))
|
| 128 |
+
except Exception:
|
| 129 |
+
n = 0
|
| 130 |
+
if n >= _JUDGE_MAX:
|
| 131 |
+
raise JudgeBudgetExceeded(
|
| 132 |
+
f"JUDGE_MAX_CALLS={_JUDGE_MAX} reached — stopping to cap Vertex AI / Gemini spend. "
|
| 133 |
+
f"Best ckpt + metrics are saved; raise JUDGE_MAX_CALLS to continue.")
|
| 134 |
+
try:
|
| 135 |
+
os.makedirs(os.path.dirname(_CALLS_FILE) or ".", exist_ok=True)
|
| 136 |
+
open(_CALLS_FILE, "w", encoding="utf-8").write(str(n + 1))
|
| 137 |
+
except Exception:
|
| 138 |
+
pass
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def _extract_text(data):
|
| 142 |
+
"""Pull the answer text out of a Vertex generateContent response (or '' if blocked/empty)."""
|
| 143 |
+
try:
|
| 144 |
+
parts = data["candidates"][0]["content"]["parts"]
|
| 145 |
+
return "".join(p.get("text", "") for p in parts).strip()
|
| 146 |
+
except Exception:
|
| 147 |
+
return "" # blocked/empty candidate -> empty grade (llm_judge clamps to 0)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def gemini(prompt, temperature=0, retries=None):
|
| 151 |
+
"""Grade one prompt with Gemini via Vertex AI express-mode REST. Generous timeout + retry on
|
| 152 |
+
transient timeouts/429/5xx; fail fast on 4xx config/auth errors. stdlib only (no SDK)."""
|
| 153 |
+
retries = int(os.environ.get("VERTEX_RETRIES", str(retries or 12)))
|
| 154 |
+
max_wait_s = float(os.environ.get("VERTEX_MAX_WAIT_S", "1800"))
|
| 155 |
+
quota_backoff_cap_s = float(os.environ.get("VERTEX_429_BACKOFF_MAX_S", "600"))
|
| 156 |
+
transient_backoff_cap_s = float(os.environ.get("VERTEX_TRANSIENT_BACKOFF_MAX_S", "120"))
|
| 157 |
+
_bump_calls() # raises JudgeBudgetExceeded at the ceiling (before spending)
|
| 158 |
+
key = _vertex_key()
|
| 159 |
+
if not key:
|
| 160 |
+
raise RuntimeError("no Vertex API key — set VERTEX_KEY (or VERTEX_API_KEY) in .env for the Gemini judge")
|
| 161 |
+
url = f"{VERTEX_BASE_URL}/publishers/google/models/{JUDGE_MODEL}:generateContent"
|
| 162 |
+
body = json.dumps({
|
| 163 |
+
"contents": [{"role": "user", "parts": [{"text": prompt}]}],
|
| 164 |
+
"generationConfig": {"temperature": temperature, "maxOutputTokens": 8},
|
| 165 |
+
}).encode("utf-8")
|
| 166 |
+
last = None
|
| 167 |
+
started = time.monotonic()
|
| 168 |
+
for attempt in range(retries):
|
| 169 |
+
_throttle()
|
| 170 |
+
try:
|
| 171 |
+
req = urllib.request.Request(url, data=body, method="POST", headers={
|
| 172 |
+
"Content-Type": "application/json", "x-goog-api-key": key})
|
| 173 |
+
with urllib.request.urlopen(req, timeout=VERTEX_TIMEOUT_S) as resp:
|
| 174 |
+
data = json.loads(resp.read().decode("utf-8"))
|
| 175 |
+
_SINCE_SAVE[0] += 1
|
| 176 |
+
if _SINCE_SAVE[0] % 100 == 0: # persist the judge cache so a crash/cap-stop resumes cheap
|
| 177 |
+
jc_save()
|
| 178 |
+
return _extract_text(data)
|
| 179 |
+
except JudgeBudgetExceeded:
|
| 180 |
+
raise
|
| 181 |
+
except urllib.error.HTTPError as e: # has a status code
|
| 182 |
+
detail = ""
|
| 183 |
+
try:
|
| 184 |
+
detail = e.read().decode("utf-8")[:300]
|
| 185 |
+
except Exception:
|
| 186 |
+
pass
|
| 187 |
+
last = RuntimeError(f"Vertex HTTP {e.code}: {detail}")
|
| 188 |
+
if e.code in (400, 401, 403, 404): # bad key / model / project / request — NOT transient
|
| 189 |
+
raise last from e
|
| 190 |
+
retry_after = _retry_after_s(getattr(e, "headers", {}).get("Retry-After"))
|
| 191 |
+
if e.code == 429:
|
| 192 |
+
wait_s = max(retry_after or 0, min(2 ** attempt, quota_backoff_cap_s), 15)
|
| 193 |
+
_cooldown(wait_s)
|
| 194 |
+
else:
|
| 195 |
+
wait_s = max(retry_after or 0, min(2 ** attempt, transient_backoff_cap_s))
|
| 196 |
+
if attempt == retries - 1 or time.monotonic() + wait_s - started > max_wait_s:
|
| 197 |
+
raise last from e
|
| 198 |
+
time.sleep(wait_s)
|
| 199 |
+
except Exception as e: # timeout / connection reset -> transient, retry
|
| 200 |
+
last = e
|
| 201 |
+
wait_s = min(2 ** attempt, transient_backoff_cap_s)
|
| 202 |
+
_cooldown(wait_s)
|
| 203 |
+
if attempt == retries - 1 or time.monotonic() + wait_s - started > max_wait_s:
|
| 204 |
+
raise
|
| 205 |
+
time.sleep(wait_s) # generous backoff for Vertex transient transport failures
|
| 206 |
+
raise last if last is not None else RuntimeError("gemini: no attempts made (retries < 1)")
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
# Rubric mirrors the official ProactiveVideoQA judge (scale 3/2/1) so the Gemini grade
|
| 210 |
+
# tracks the leaderboard; we remap 3/2/1 -> 2/1/0 to match the PAUC y-axis (S=2).
|
| 211 |
+
JUDGE_INSTRUCTION = (
|
| 212 |
+
"You are an evaluator for a video question answering system. Rate whether the "
|
| 213 |
+
"predicted answer covers the key points of the ground truth answer. Use this scale:\n"
|
| 214 |
+
"- 3: Mostly covered; the predicted answer covers all key information in the ground "
|
| 215 |
+
"truth answer, though it may have minor inaccuracies or rephrases.\n"
|
| 216 |
+
"- 2: Partially covered; the predicted answer has some correct information, but also "
|
| 217 |
+
"contains significant inaccuracies or missing key points.\n"
|
| 218 |
+
"- 1: Incorrect; the predicted answer may be related but most information is missing, "
|
| 219 |
+
"or it is irrelevant / very poor quality.\n"
|
| 220 |
+
"Output the score only, do not add more explanations."
|
| 221 |
+
)
|
| 222 |
+
|
| 223 |
+
|
| 224 |
+
def judge_key(ref, ans, question=None):
|
| 225 |
+
"""Cache key for a judge call. question=None preserves the old (question-blind) key so legacy
|
| 226 |
+
cache entries still hit; with a question it is a distinct key (faithful, official format)."""
|
| 227 |
+
return jc_key("judge", ref if question is None else f"{question}\x00{ref}", ans)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
def llm_judge(ans, ref, question=None): # faithful: Gemini-family API, temp=0
|
| 231 |
+
jc_load()
|
| 232 |
+
k = judge_key(ref, ans, question)
|
| 233 |
+
if k in _jcache:
|
| 234 |
+
return _jcache[k]
|
| 235 |
+
if question is None:
|
| 236 |
+
prompt = (f"{JUDGE_INSTRUCTION}\n\nQuestion-moment ground truth: {ref}\n"
|
| 237 |
+
f"Predicted answer: {ans}")
|
| 238 |
+
else: # official format: Question + GT + Predicted
|
| 239 |
+
prompt = (f"{JUDGE_INSTRUCTION}\n\nQuestion: {question}\n"
|
| 240 |
+
f"Ground Truth Answer: {ref}\nPredicted Answer: {ans}")
|
| 241 |
+
out = gemini(prompt, temperature=0).strip()
|
| 242 |
+
# official maps the leading digit of {1,2,3} -> {0,1,2}; clamp anything else to 0.
|
| 243 |
+
v = {"1": 0, "2": 1, "3": 2}.get(out[:1], 0)
|
| 244 |
+
with _jc_lock:
|
| 245 |
+
_jcache[k] = v
|
| 246 |
+
return v
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def emb_judge(ans, ref, question=None): # fast fallback (approximate); ignores question
|
| 250 |
+
from humomni.core.emb_cache import cos
|
| 251 |
+
c = cos(ans, ref)
|
| 252 |
+
return 2 if c >= 0.62 else 1 if c >= 0.42 else 0
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def llm_entails(prior, hyp): # duplicate = "adds nothing new?"
|
| 256 |
+
jc_load()
|
| 257 |
+
k = jc_key("entail", " | ".join(prior), hyp)
|
| 258 |
+
if k in _jcache:
|
| 259 |
+
return _jcache[k]
|
| 260 |
+
p = ("Does the NEW statement add information beyond the PRIOR statements about the same "
|
| 261 |
+
"moment? Answer yes (adds new) or no (fully entailed/duplicate).\nPRIOR: "
|
| 262 |
+
+ " | ".join(prior) + "\nNEW: " + hyp)
|
| 263 |
+
v = gemini(p, temperature=0).strip().lower().startswith("no")
|
| 264 |
+
with _jc_lock:
|
| 265 |
+
_jcache[k] = v
|
| 266 |
+
return v
|
src/humomni/core/pauc_eval.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pauc_eval.py — fast offline harness for the ranking score 0.9*PAUC + 0.1*(1-duplicate).
|
| 2 |
+
#
|
| 3 |
+
# The PAUC math here is a faithful port of the OFFICIAL ProactiveVideoQA scorer
|
| 4 |
+
# (external/ProactiveVideoQA/pauc/judge_llm_scores_openai.py): area_under_line_ratio +
|
| 5 |
+
# stat_metric, plus its exact-string accumulation dedup and second-turn "bleed" removal.
|
| 6 |
+
# Verified equal to the official area function in scorer_crosscheck.py (M2).
|
| 7 |
+
#
|
| 8 |
+
# NOTE: the official repo computes PAUC only; the duplicate term (0.1 weight) is the
|
| 9 |
+
# HumOmni Track-2 addition and lives in duplicate_in_span() below.
|
| 10 |
+
import json
|
| 11 |
+
import os
|
| 12 |
+
import argparse
|
| 13 |
+
from typing import Callable
|
| 14 |
+
|
| 15 |
+
S = 2 # max judge score on the y-axis (official: max_scores-1 = 2)
|
| 16 |
+
START_SCORE = 0.5 # silence/pre-first-reply height (official start_score)
|
| 17 |
+
OMEGA = 0.5 # timeliness knob (paper default, ω=0.5)
|
| 18 |
+
SPAN = "reply_timespan" # the scored window (verified field; ~5.51 s on WEB)
|
| 19 |
+
RELATED = "related_timespan"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _turn_auc(points_rel, span_len: float, omega: float = OMEGA) -> float:
|
| 23 |
+
"""Area-under-step-curve ratio for ONE turn. Mirrors the official area_under_line_ratio.
|
| 24 |
+
|
| 25 |
+
points_rel: list of (time_relative_to_span_start, score) — UNWARPED times, score in [0, S].
|
| 26 |
+
Warp shifts only the reply times toward the start; the curve then holds the last score
|
| 27 |
+
out to the UNWARPED span_len, and the denominator is span_len * S.
|
| 28 |
+
"""
|
| 29 |
+
if not points_rel:
|
| 30 |
+
return START_SCORE / S # 0.25 — the silence floor
|
| 31 |
+
pts = sorted(points_rel, key=lambda p: p[0])
|
| 32 |
+
pts = [(x * (1 - omega), y) for x, y in pts] # warp reply times only
|
| 33 |
+
pts.append((span_len, pts[-1][1])) # extend last score to the TRUE end
|
| 34 |
+
prev_x, prev_y, area = 0.0, START_SCORE, 0.0
|
| 35 |
+
for x, y in pts:
|
| 36 |
+
area += (x - prev_x) * prev_y
|
| 37 |
+
prev_x, prev_y = x, y
|
| 38 |
+
return area / (span_len * S)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def _question_of(it) -> str:
|
| 42 |
+
"""The question text for a gold item (official judge is given this; pass it to the judge)."""
|
| 43 |
+
conv = it.get("conversation")
|
| 44 |
+
return (conv[0].get("content") if conv else it.get("question")) or None
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def turn_judge_scores(turn, preds, judge: Callable, question=None) -> dict:
|
| 48 |
+
"""Build {pred_time: score} for one turn, mirroring the official construction:
|
| 49 |
+
accumulated judging, exact-string dedup of the accumulation, restricted to the reply
|
| 50 |
+
span, then drop post-related-span replies that score below the last in-related score.
|
| 51 |
+
`question` (when given) is forwarded to the judge to match the official prompt format.
|
| 52 |
+
"""
|
| 53 |
+
a, b = turn[SPAN]
|
| 54 |
+
# FAITHFUL ORDER: accumulate in RESPONSE-LIST order, NOT sorted by time — matches the official
|
| 55 |
+
# create_openai_batch_input (it filters pred_data to the span without re-sorting). Identical to a
|
| 56 |
+
# time-sort for a time-ordered submission (ours always are), but exact for any input + overlapping spans.
|
| 57 |
+
ins = [p for p in preds if a <= p["time"] <= b]
|
| 58 |
+
acc, scores = [], {}
|
| 59 |
+
for p in ins:
|
| 60 |
+
c = p["content"]
|
| 61 |
+
if c in acc: # official: skip exact-duplicate accumulated text
|
| 62 |
+
continue
|
| 63 |
+
acc.append(c)
|
| 64 |
+
scores[p["time"]] = judge(" ".join(acc), turn["content"], question)
|
| 65 |
+
# second-turn "bleed" removal: a low-scoring reply after the related span is likely
|
| 66 |
+
# from the next turn — drop it (official process_openai_batch_output, lines ~111-121).
|
| 67 |
+
if len(scores) > 1 and RELATED in turn:
|
| 68 |
+
related_end = turn[RELATED][1]
|
| 69 |
+
last_related = 0
|
| 70 |
+
for t, sc in sorted(scores.items()):
|
| 71 |
+
if t <= related_end:
|
| 72 |
+
last_related = sc
|
| 73 |
+
elif sc < last_related:
|
| 74 |
+
del scores[t]
|
| 75 |
+
return scores
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def pauc_turn(turn, preds, judge: Callable, omega: float = OMEGA, question=None):
|
| 79 |
+
"""PAUC for one gold turn given a prediction list. Returns None for a degenerate span."""
|
| 80 |
+
a, b = turn[SPAN]
|
| 81 |
+
span_len = b - a
|
| 82 |
+
if span_len <= 0:
|
| 83 |
+
return None
|
| 84 |
+
scores = turn_judge_scores(turn, preds, judge, question)
|
| 85 |
+
points = [(t - a, sc) for t, sc in scores.items()]
|
| 86 |
+
return _turn_auc(points, span_len, omega)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def duplicate_in_span(turn, preds, entail: Callable[[list, str], bool]):
|
| 90 |
+
"""HumOmni duplicate term: (#in-span reply pairs, #entailed-by-earlier). Not in the
|
| 91 |
+
official PAUC repo. entail(prior_contents, new_content) -> True if new is a duplicate."""
|
| 92 |
+
a, b = turn[SPAN]
|
| 93 |
+
# response-list order ("starting from the second response … entailed by all prior") — see turn_judge_scores.
|
| 94 |
+
ins = [p for p in preds if a <= p["time"] <= b]
|
| 95 |
+
if len(ins) < 2:
|
| 96 |
+
return 0, 0
|
| 97 |
+
e = sum(entail([ins[j]["content"] for j in range(i)], ins[i]["content"])
|
| 98 |
+
for i in range(1, len(ins)))
|
| 99 |
+
return len(ins) - 1, e
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def score(gold, pred_by_id, judge, entail, omega: float = OMEGA) -> dict:
|
| 103 |
+
paucs, dnum, dden = [], 0, 0
|
| 104 |
+
for it in gold:
|
| 105 |
+
preds = pred_by_id.get(it["question_id"], [])
|
| 106 |
+
q = _question_of(it)
|
| 107 |
+
for turn in it["answer"]:
|
| 108 |
+
v = pauc_turn(turn, preds, judge, omega, question=q)
|
| 109 |
+
if v is not None:
|
| 110 |
+
paucs.append(v)
|
| 111 |
+
n, e = duplicate_in_span(turn, preds, entail)
|
| 112 |
+
dden += n
|
| 113 |
+
dnum += e
|
| 114 |
+
pauc = sum(paucs) / len(paucs) if paucs else 0.0
|
| 115 |
+
dup = dnum / dden if dden else 0.0
|
| 116 |
+
return {"PAUC": pauc, "duplicate": dup, "score": 0.9 * pauc + 0.1 * (1 - dup)}
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _load_pred(path) -> dict:
|
| 120 |
+
"""jsonl -> {question_id: model_response_list}. None/missing => all-silent."""
|
| 121 |
+
if not path or not os.path.exists(path):
|
| 122 |
+
return {}
|
| 123 |
+
out = {}
|
| 124 |
+
with open(path, encoding="utf-8") as f:
|
| 125 |
+
for ln in f:
|
| 126 |
+
ln = ln.strip()
|
| 127 |
+
if ln:
|
| 128 |
+
r = json.loads(ln)
|
| 129 |
+
out[r["question_id"]] = r.get("model_response_list", [])
|
| 130 |
+
return out
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
def make_judges(kind: str):
|
| 134 |
+
"""Return (judge_fn, entail_fn) for kind in {'emb','llm'}.
|
| 135 |
+
('local' = an on-GPU judge from a separate training package that is not included in this repository.)"""
|
| 136 |
+
if kind == "emb":
|
| 137 |
+
from humomni.core.judges import emb_judge
|
| 138 |
+
return emb_judge, (lambda P, h: emb_judge(h, " ".join(P)) >= 2)
|
| 139 |
+
if kind == "local":
|
| 140 |
+
raise ValueError("judge kind 'local' requires a separate training package not included in this repository; use 'llm' or 'emb'")
|
| 141 |
+
from humomni.core.judges import llm_judge, llm_entails
|
| 142 |
+
return llm_judge, llm_entails
|
| 143 |
+
|
| 144 |
+
|
| 145 |
+
if __name__ == "__main__":
|
| 146 |
+
ap = argparse.ArgumentParser()
|
| 147 |
+
ap.add_argument("--gold", default="data/ProactiveVideoQA/WEB/anno.json")
|
| 148 |
+
ap.add_argument("--pred", default=None, help="submission jsonl; omit for all-silent baseline")
|
| 149 |
+
ap.add_argument("--judge", default="emb", choices=["emb", "llm", "local"])
|
| 150 |
+
ap.add_argument("--limit", type=int, default=0, help="score only the first N gold items (0=all)")
|
| 151 |
+
a = ap.parse_args()
|
| 152 |
+
gold = json.load(open(a.gold, encoding="utf-8"))
|
| 153 |
+
if a.limit:
|
| 154 |
+
gold = gold[:a.limit]
|
| 155 |
+
judge_fn, entail_fn = make_judges(a.judge)
|
| 156 |
+
r = score(gold, _load_pred(a.pred), judge=judge_fn, entail=entail_fn)
|
| 157 |
+
print(json.dumps({**r, "judge": a.judge, "n_items": len(gold),
|
| 158 |
+
"pred": a.pred or "(all-silent)"}, indent=2))
|
src/humomni/core/streaming_driver.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# streaming_driver.py — the shared causal streaming loop (Phase 1 API + Phase 2 RL).
|
| 2 |
+
#
|
| 3 |
+
# THE ONE HARD RULE (causality): when the policy acts at time t it may only ever have
|
| 4 |
+
# seen frames with timestamp <= t. We enforce this STRUCTURALLY: frames are handed to
|
| 5 |
+
# the policy one at a time in strictly ascending t, and the policy keeps its own history
|
| 6 |
+
# (no handle to future frames exists). The `assert t > last_t` is a tripwire; the
|
| 7 |
+
# shuffled-frame unit test (test_causality.py) proves it fires.
|
| 8 |
+
import json
|
| 9 |
+
import glob
|
| 10 |
+
import os
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def iter_frames(sample_dir):
|
| 14 |
+
"""Yield (t, path) in ascending time — the ONLY way frames enter the system.
|
| 15 |
+
|
| 16 |
+
Non-frame files (e.g. question.json) and any unparsable stems are skipped.
|
| 17 |
+
"""
|
| 18 |
+
items = []
|
| 19 |
+
for f in glob.glob(os.path.join(sample_dir, "*.jpg")):
|
| 20 |
+
stem = os.path.basename(f)[:-4]
|
| 21 |
+
try:
|
| 22 |
+
items.append((float(stem), f))
|
| 23 |
+
except ValueError:
|
| 24 |
+
pass # skip non-frame files
|
| 25 |
+
items.sort(key=lambda x: x[0])
|
| 26 |
+
out, last = [], None
|
| 27 |
+
for t, f in items: # drop a malformed duplicate-timestamp frame (would trip drive()'s assert)
|
| 28 |
+
if t != last:
|
| 29 |
+
out.append((t, f))
|
| 30 |
+
last = t
|
| 31 |
+
return out
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def _emissions(out):
|
| 35 |
+
"""Normalize a policy.step() return into a list of reply strings.
|
| 36 |
+
None -> []; a str -> [str]; a list/tuple -> itself. Lets one frame emit several answers
|
| 37 |
+
(the v5+v3 ensemble) without changing single-reply policies (SilentPolicy/RLPolicy)."""
|
| 38 |
+
if out is None:
|
| 39 |
+
return []
|
| 40 |
+
return [out] if isinstance(out, str) else list(out)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def drive(frames, policy, question_id, question):
|
| 44 |
+
"""Run an ascending (t, frame_path) stream through a policy and collect emissions.
|
| 45 |
+
|
| 46 |
+
`frames` must be strictly time-ordered; the assert below makes a causality
|
| 47 |
+
violation structurally impossible to ignore. The policy sees one frame at a time.
|
| 48 |
+
"""
|
| 49 |
+
policy.reset(question=question)
|
| 50 |
+
responses, last_t = [], -1.0
|
| 51 |
+
for t, frame_path in frames:
|
| 52 |
+
assert t > last_t, "frames must arrive strictly in order" # causality guard
|
| 53 |
+
last_t = t
|
| 54 |
+
for content in _emissions(policy.step(t, frame_path)): # sees only frames up to t
|
| 55 |
+
responses.append({"time": round(t, 3), "content": content})
|
| 56 |
+
return {"question_id": question_id, "model_response_list": responses}
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def run_sample(sample_dir, policy):
|
| 60 |
+
"""Drive one sample directory (frames + question.json) through a fresh policy."""
|
| 61 |
+
with open(os.path.join(sample_dir, "question.json"), encoding="utf-8") as f:
|
| 62 |
+
q = json.load(f)
|
| 63 |
+
return drive(iter_frames(sample_dir), policy, q["question_id"], q["question"])
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# Policy contract (both phases implement this):
|
| 67 |
+
# policy.reset(question: str) -> None
|
| 68 |
+
# policy.step(t: float, frame_path: str) -> str | list[str] | None
|
| 69 |
+
# None / [] == stay silent; a str or a 1+ list == emit that/those answer(s) AT TIME t
|
| 70 |
+
# (the ensemble emits several per frame). See _emissions().
|
src/humomni/core/validate_submission.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# validate_submission.py — run before EVERY upload (a malformed line = an invalid entry).
|
| 2 |
+
# Checks: valid JSONL; model_response_list present & a list; times >= 0 and multiples of
|
| 3 |
+
# 0.5; non-empty string content; ALL gold ids present (emit [] for silent samples).
|
| 4 |
+
import json
|
| 5 |
+
import glob
|
| 6 |
+
import os
|
| 7 |
+
import argparse
|
| 8 |
+
import sys
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def validate(path, gold_ids):
|
| 12 |
+
seen, errs = set(), []
|
| 13 |
+
with open(path, encoding="utf-8") as f:
|
| 14 |
+
for i, ln in enumerate(f, 1):
|
| 15 |
+
ln = ln.strip()
|
| 16 |
+
if not ln:
|
| 17 |
+
continue
|
| 18 |
+
try:
|
| 19 |
+
r = json.loads(ln)
|
| 20 |
+
except Exception:
|
| 21 |
+
errs.append(f"L{i}: invalid JSON")
|
| 22 |
+
continue
|
| 23 |
+
qid = r.get("question_id")
|
| 24 |
+
seen.add(qid)
|
| 25 |
+
if not isinstance(r.get("model_response_list"), list):
|
| 26 |
+
errs.append(f"{qid}: missing/!list model_response_list")
|
| 27 |
+
continue
|
| 28 |
+
for resp in r["model_response_list"]:
|
| 29 |
+
t, c = resp.get("time"), resp.get("content")
|
| 30 |
+
# bool is a subclass of int — reject it explicitly.
|
| 31 |
+
if isinstance(t, bool) or not isinstance(t, (int, float)) or t < 0 or round(t * 2) != t * 2:
|
| 32 |
+
errs.append(f"{qid}: time {t!r} not a non-negative multiple of 0.5")
|
| 33 |
+
if not isinstance(c, str) or not c.strip():
|
| 34 |
+
errs.append(f"{qid}: empty/invalid content")
|
| 35 |
+
missing = set(gold_ids) - seen
|
| 36 |
+
if missing:
|
| 37 |
+
errs.append(f"{len(missing)} question_ids missing — emit every id, even with []")
|
| 38 |
+
extra = seen - set(gold_ids)
|
| 39 |
+
if extra:
|
| 40 |
+
errs.append(f"{len(extra)} unexpected question_ids not in the gold set")
|
| 41 |
+
return errs
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def gold_ids_from_dir(data_dir):
|
| 45 |
+
"""The authoritative id list = question.json['question_id'] for every sample dir."""
|
| 46 |
+
ids = []
|
| 47 |
+
for d in sorted(glob.glob(os.path.join(data_dir, "*"))):
|
| 48 |
+
qj = os.path.join(d, "question.json")
|
| 49 |
+
if os.path.isdir(d) and os.path.exists(qj):
|
| 50 |
+
with open(qj, encoding="utf-8") as f:
|
| 51 |
+
ids.append(json.load(f)["question_id"])
|
| 52 |
+
return ids
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
ap = argparse.ArgumentParser()
|
| 57 |
+
ap.add_argument("submission")
|
| 58 |
+
ap.add_argument("--data_dir", default="data/phase1/data")
|
| 59 |
+
a = ap.parse_args()
|
| 60 |
+
gold = gold_ids_from_dir(a.data_dir)
|
| 61 |
+
errs = validate(a.submission, gold)
|
| 62 |
+
if errs:
|
| 63 |
+
print(f"INVALID — {len(errs)} problem(s):")
|
| 64 |
+
for e in errs[:50]:
|
| 65 |
+
print(" -", e)
|
| 66 |
+
sys.exit(1)
|
| 67 |
+
print(f"VALID — {len(gold)} ids checked, all present, times are multiples of 0.5.")
|
src/humomni/phase1/__init__.py
ADDED
|
File without changes
|
src/humomni/phase1/dedup.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# dedup.py — duplicate guard (§6 + §19). At inference we don't know gold spans, so we dedup a
|
| 2 |
+
# draft against the most recent K emitted answers (a safe superset of "same span"): suppress if
|
| 3 |
+
# max cosine to an individual recent >= sim_high, and escalate the borderline band
|
| 4 |
+
# [sim_low, sim_high) to the LLM entailment judge (the proven, metric-aligned setting).
|
| 5 |
+
#
|
| 6 |
+
# (A speculative "concat"/n-gram mode was removed — no config or experiment ever used it; the
|
| 7 |
+
# §19 bidirectional-entailment idea it sketched lives on as the use_llm escalation above.)
|
| 8 |
+
import numpy as np
|
| 9 |
+
|
| 10 |
+
from humomni.core.judges import llm_entails
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class EntailmentGuard:
|
| 14 |
+
def __init__(self, sim_high=0.86, sim_low=0.70, recent_k=4, use_llm=True):
|
| 15 |
+
self.sim_high = sim_high
|
| 16 |
+
self.sim_low = sim_low
|
| 17 |
+
self.k = recent_k
|
| 18 |
+
self.use_llm = use_llm
|
| 19 |
+
|
| 20 |
+
def is_duplicate(self, draft, said):
|
| 21 |
+
recents = said[-self.k:]
|
| 22 |
+
if not recents:
|
| 23 |
+
return False
|
| 24 |
+
from humomni.core.emb_cache import embed_many
|
| 25 |
+
vecs = embed_many([draft] + recents)
|
| 26 |
+
sims = [float(np.dot(vecs[0], v)) for v in vecs[1:]]
|
| 27 |
+
if max(sims) >= self.sim_high: # clearly a repeat
|
| 28 |
+
return True
|
| 29 |
+
if self.use_llm and max(sims) >= self.sim_low: # borderline -> ask the NLI judge
|
| 30 |
+
return llm_entails(prior=recents, hyp=draft)
|
| 31 |
+
return False
|
src/humomni/phase1/perception_gate.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# perception_gate.py — Tier-1 CPU prefilter: decide whether a frame is worth a VLM call (§5.1).
|
| 2 |
+
# Baseline = grayscale frame-diff novelty + warmup + max-silence timer. No GPU, no model.
|
| 3 |
+
# The §19 upgrade (CLIP/DINO event-onset gating + hysteresis + refractory) lands at M5.
|
| 4 |
+
import numpy as np
|
| 5 |
+
from PIL import Image
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class PerceptionGate:
|
| 9 |
+
def __init__(self, novelty=12.0, max_silence_s=2.0, warmup_s=1.0):
|
| 10 |
+
self.prev = None
|
| 11 |
+
self.last_fire = -1e9
|
| 12 |
+
self.novelty = novelty
|
| 13 |
+
self.max_silence = max_silence_s
|
| 14 |
+
self.warmup = warmup_s
|
| 15 |
+
|
| 16 |
+
def _feat(self, path):
|
| 17 |
+
return np.asarray(Image.open(path).convert("L").resize((64, 36)), dtype=np.float32)
|
| 18 |
+
|
| 19 |
+
def should_decide(self, t, path):
|
| 20 |
+
"""Return (fire: bool, novelty: float). Fires at warmup, on visual novelty, or on timeout."""
|
| 21 |
+
f = self._feat(path)
|
| 22 |
+
nov = 255.0 if self.prev is None else float(np.abs(f - self.prev).mean())
|
| 23 |
+
self.prev = f
|
| 24 |
+
fire = (t <= self.warmup) or (nov >= self.novelty) or (t - self.last_fire >= self.max_silence)
|
| 25 |
+
if fire:
|
| 26 |
+
self.last_fire = t
|
| 27 |
+
return fire, nov
|
src/humomni/phase1/policies.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# policies.py — simple policies implementing the reset/step contract (§3).
|
| 2 |
+
# The real decision policy (APIPolicy, §5) arrives at M3 in policy_api.py.
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
class SilentPolicy:
|
| 6 |
+
"""Dummy always-silent baseline (M1).
|
| 7 |
+
|
| 8 |
+
Never speaks, so it scores 0.25 PAUC (pure silence) but proves the causal loop,
|
| 9 |
+
the submission format, and the validator end-to-end before any API cost.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
def reset(self, question):
|
| 13 |
+
self.question = question
|
| 14 |
+
|
| 15 |
+
def step(self, t, frame_path):
|
| 16 |
+
return None # stay silent
|
src/humomni/phase1/policy_api.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# policy_api.py — Phase 1 decision module (implements the reset/step policy contract, §5.2).
|
| 2 |
+
# gate -> VLM decision(s) -> theta threshold -> entailment guard -> emit.
|
| 3 |
+
# use_gate=False makes it call the VLM on EVERY frame (the M4 cache-building pass).
|
| 4 |
+
#
|
| 5 |
+
# BEST METHOD (config.json "ensemble"): query SEVERAL prompts per frame (v5 then v3) and merge
|
| 6 |
+
# their drafts through ONE shared dedup guard — the validated v5+v3 @ recent_k=32 ensemble (holdout
|
| 7 |
+
# 0.7909). With no "ensemble" block it is the single-prompt policy (the env VLM_PROMPT, default v5).
|
| 8 |
+
# step() returns a LIST of drafts (0..N) so one frame can emit more than one answer (the ensemble).
|
| 9 |
+
from humomni.core.context import build_context
|
| 10 |
+
from humomni.phase1.vlm_client import call_vlm, prompt_for
|
| 11 |
+
from humomni.phase1.dedup import EntailmentGuard
|
| 12 |
+
from humomni.phase1.perception_gate import PerceptionGate
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class APIPolicy:
|
| 16 |
+
def __init__(self, config=None, use_gate=True, vlm_fn=None):
|
| 17 |
+
cfg = config or {}
|
| 18 |
+
self.theta = cfg.get("theta", 0.6)
|
| 19 |
+
self.gate_cfg = cfg.get("gate", {"use_gate": True})
|
| 20 |
+
self.dedup_cfg = cfg.get("dedup", {})
|
| 21 |
+
self.ensemble_cfg = cfg.get("ensemble") # {"prompts": [...], "recent_k": N} or None
|
| 22 |
+
self.use_gate = use_gate and self.gate_cfg.get("use_gate", True)
|
| 23 |
+
self.vlm_fn = vlm_fn or call_vlm
|
| 24 |
+
|
| 25 |
+
def reset(self, question):
|
| 26 |
+
self.question = question
|
| 27 |
+
self.history = []
|
| 28 |
+
self.said = []
|
| 29 |
+
g = self.gate_cfg
|
| 30 |
+
self.gate = PerceptionGate(novelty=g.get("novelty", 12.0),
|
| 31 |
+
max_silence_s=g.get("max_silence_s", 2.0),
|
| 32 |
+
warmup_s=g.get("warmup_s", 1.0))
|
| 33 |
+
d = self.dedup_cfg
|
| 34 |
+
# Ensemble: a list of system-prompt texts queried per frame (None == the default SYSTEM_PROMPT);
|
| 35 |
+
# it uses its own (deeper) recent_k since merging prompts raises emit density. Single-prompt
|
| 36 |
+
# policy = one None entry, so behavior is byte-identical to the pre-ensemble policy.
|
| 37 |
+
if self.ensemble_cfg:
|
| 38 |
+
self.prompts = [prompt_for(p) for p in self.ensemble_cfg["prompts"]]
|
| 39 |
+
recent_k = self.ensemble_cfg.get("recent_k", d.get("recent_k", 4))
|
| 40 |
+
else:
|
| 41 |
+
self.prompts = [None]
|
| 42 |
+
recent_k = d.get("recent_k", 4)
|
| 43 |
+
self.guard = EntailmentGuard(sim_high=d.get("sim_high", 0.86), sim_low=d.get("sim_low", 0.70),
|
| 44 |
+
recent_k=recent_k, use_llm=d.get("use_llm", False))
|
| 45 |
+
|
| 46 |
+
def step(self, t, frame_path):
|
| 47 |
+
self.history.append((t, frame_path))
|
| 48 |
+
if self.use_gate:
|
| 49 |
+
fire, _ = self.gate.should_decide(t, frame_path)
|
| 50 |
+
if not fire:
|
| 51 |
+
return [] # cheap skip, no API call
|
| 52 |
+
# said=[] (NOT self.said): the VLM describes each frame fully; the entailment dedup below
|
| 53 |
+
# handles repeats. This MATCHES the offline cache (cache.py builds with said=[]) so the live
|
| 54 |
+
# submission reproduces the validated replay score — and scores HIGHER than feeding self.said,
|
| 55 |
+
# which makes a comprehensive (v5) VLM over-suppress ("SAME_ANSWER") and emit ~half as much.
|
| 56 |
+
ctx = build_context(self.question, self.history, [])
|
| 57 |
+
emitted = []
|
| 58 |
+
for prompt in self.prompts: # merge order = prompt order (v5 first, then v3)
|
| 59 |
+
d = self.vlm_fn(ctx, prompt=prompt) if prompt is not None else self.vlm_fn(ctx)
|
| 60 |
+
# §19 (M5): insert confidence calibration on d["confidence"] here before thresholding.
|
| 61 |
+
if d["decision"] != "NEW_ANSWER" or d["confidence"] < self.theta:
|
| 62 |
+
continue
|
| 63 |
+
draft = d["draft"].strip()
|
| 64 |
+
if not draft or self.guard.is_duplicate(draft, self.said):
|
| 65 |
+
continue
|
| 66 |
+
self.said.append(draft)
|
| 67 |
+
emitted.append(draft)
|
| 68 |
+
return emitted
|
src/humomni/phase1/vlm_client.py
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# vlm_client.py — VLM per-frame decision call (§7). TWO backends, selected by VLM_PROVIDER:
|
| 2 |
+
# openrouter (default) — OpenAI-style chat, the shipped qwen/qwen3.7-plus VLM.
|
| 3 |
+
# vertex — Gemini via Vertex AI express REST (e.g. gemini-3.5-flash), images inline,
|
| 4 |
+
# NO SDK / NO OpenRouter (same transport as the judge). Auth = VERTEX_KEY in .env.
|
| 5 |
+
# Deterministic (temperature=0), images as base64, robust JSON parse (fail-safe = silent),
|
| 6 |
+
# exponential backoff on transient errors. Model id + key come from env / .env.
|
| 7 |
+
import base64
|
| 8 |
+
import io
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import re
|
| 12 |
+
import time
|
| 13 |
+
import threading
|
| 14 |
+
import urllib.request
|
| 15 |
+
import urllib.error
|
| 16 |
+
|
| 17 |
+
from PIL import Image
|
| 18 |
+
|
| 19 |
+
from humomni.core.env_util import load_env
|
| 20 |
+
|
| 21 |
+
load_env()
|
| 22 |
+
VLM_PROVIDER = os.environ.get("VLM_PROVIDER", "openrouter").lower() # 'openrouter' | 'vertex'
|
| 23 |
+
MODEL = os.environ.get("VLM_MODEL", "qwen/qwen3.7-plus") # OpenRouter id; verify it is live
|
| 24 |
+
# qwen3.x "plus" is a reasoning model; a per-frame JSON decision needs no chain-of-thought,
|
| 25 |
+
# so disable thinking by default (huge latency + cost win). Override with VLM_REASONING=1.
|
| 26 |
+
DISABLE_REASONING = os.environ.get("VLM_REASONING", "0") != "1"
|
| 27 |
+
# --- Vertex backend (only used when VLM_PROVIDER=vertex) ---
|
| 28 |
+
_VLM_MODEL_VX = re.sub(r"-\d{8}$", "", MODEL.split("/", 1)[-1]) # strip google/ prefix + -YYYYMMDD date
|
| 29 |
+
VERTEX_BASE_URL = os.environ.get("VERTEX_BASE_URL", "https://aiplatform.googleapis.com/v1")
|
| 30 |
+
VLM_TIMEOUT_S = float(os.environ.get("VLM_TIMEOUT_S", "60")) # Gemini+images is slow
|
| 31 |
+
VLM_MAX_TOKENS = int(os.environ.get("VLM_MAX_TOKENS", "512")) # room for thinking + the JSON
|
| 32 |
+
_VLM_THINKING = os.environ.get("VLM_THINKING_BUDGET") # set "0" to try disabling thinking
|
| 33 |
+
_RECENT_PX = int(os.environ.get("CTX_RECENT_PX", "480")) # recent-frame max px (native 480)
|
| 34 |
+
_HIST_PX = int(os.environ.get("CTX_HIST_PX", "256")) # history-keyframe max px (perception lever)
|
| 35 |
+
_client = None
|
| 36 |
+
|
| 37 |
+
_SYSTEM_PROMPTS = {
|
| 38 |
+
# v1 — the shipped prompt (current submission). Default; unchanged behavior.
|
| 39 |
+
"v1": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 40 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 41 |
+
After each update you decide whether to speak.
|
| 42 |
+
|
| 43 |
+
RULES
|
| 44 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 45 |
+
of the question. If unsure, stay SILENT — silence beats a wrong or vague statement.
|
| 46 |
+
2. Be timely: the instant the evidence is visible, say it. Do not wait for the action
|
| 47 |
+
to finish or for more certainty than you need.
|
| 48 |
+
3. One NEW fact per message. Declarative, present tense, describe only what is visible.
|
| 49 |
+
No speculation, no hedging words ("maybe", "appears", "seems"), no timestamps in text.
|
| 50 |
+
4. NEVER repeat or rephrase anything you have already told the user (it is listed for
|
| 51 |
+
you). Speak only if you have genuinely new information.
|
| 52 |
+
5. Output STRICT JSON, nothing else:
|
| 53 |
+
{"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 54 |
+
- NEW_ANSWER: genuinely new, evidence-backed information (put it in "draft").
|
| 55 |
+
- SAME_ANSWER: the scene only repeats what you already said.
|
| 56 |
+
- NO_ANSWER: nothing answerable is visible yet.
|
| 57 |
+
"confidence" = your probability that "draft" is correct AND new.""",
|
| 58 |
+
# v2 — content-alignment experiment: answer the QUESTION's action/change tersely (caption
|
| 59 |
+
# style), stop cataloguing appearance/text/brands. Targets the dominant "scene-dump / wrong
|
| 60 |
+
# granularity" failure (inspect_wrong.py: ~20% of in-span turns). Select with VLM_PROMPT=v2.
|
| 61 |
+
"v2": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 62 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 63 |
+
After each update you decide whether to speak.
|
| 64 |
+
|
| 65 |
+
RULES
|
| 66 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 67 |
+
of the question. If unsure, stay SILENT — silence beats a wrong or vague statement.
|
| 68 |
+
2. Be timely: the instant the evidence is visible, say it. Do not wait for the action
|
| 69 |
+
to finish or for more certainty than you need.
|
| 70 |
+
3. ANSWER THE QUESTION DIRECTLY and briefly, like a one-line caption (aim for ≤12 words).
|
| 71 |
+
Report the single most relevant ACTION or CHANGE — what someone is DOING, or what just
|
| 72 |
+
happened — using a clear verb. Focus on events and interactions, NOT static scenery. Do
|
| 73 |
+
NOT list appearance, clothing, colors, background objects, on-screen text, brands, or
|
| 74 |
+
names unless that detail is exactly what the question asks for.
|
| 75 |
+
4. One NEW fact per message. Declarative, present tense. No speculation, no hedging words
|
| 76 |
+
("maybe", "appears", "seems"), no timestamps in text.
|
| 77 |
+
5. NEVER repeat or rephrase anything you have already told the user (it is listed for
|
| 78 |
+
you). Speak only if you have genuinely new information.
|
| 79 |
+
6. Output STRICT JSON, nothing else:
|
| 80 |
+
{"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 81 |
+
- NEW_ANSWER: genuinely new, evidence-backed information (put it in "draft").
|
| 82 |
+
- SAME_ANSWER: the scene only repeats what you already said.
|
| 83 |
+
- NO_ANSWER: nothing answerable is visible yet.
|
| 84 |
+
"confidence" = your probability that "draft" is correct AND new.""",
|
| 85 |
+
# v3 — content-alignment experiment, OPPOSITE of v2: maximize COVERAGE. The judge rewards partial
|
| 86 |
+
# credit for matching any aspect of the gold, so hedge multiple entities/actions instead of
|
| 87 |
+
# committing to one (targets the "wrong-focus" failures). Select with VLM_PROMPT=v3.
|
| 88 |
+
"v3": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 89 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 90 |
+
After each update you decide whether to speak.
|
| 91 |
+
|
| 92 |
+
RULES
|
| 93 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 94 |
+
of the question. If unsure, stay SILENT — silence beats a wrong or vague statement.
|
| 95 |
+
2. Be timely: the instant the evidence is visible, say it. Do not wait for the action
|
| 96 |
+
to finish or for more certainty than you need.
|
| 97 |
+
3. Answer the question as COMPLETELY as the current frames allow. State the main action or
|
| 98 |
+
event, AND also mention every other person, object, or action in view that is relevant to
|
| 99 |
+
the question. If several things could plausibly answer it, include them ALL rather than
|
| 100 |
+
committing to just one. Prefer concrete actions (verbs) and the specific subjects asked about.
|
| 101 |
+
4. Declarative, present tense. No speculation, no hedging words ("maybe", "appears", "seems"),
|
| 102 |
+
no timestamps in text. NEVER repeat or rephrase anything you have already told the user (it
|
| 103 |
+
is listed for you) — add only genuinely new information.
|
| 104 |
+
5. Output STRICT JSON, nothing else:
|
| 105 |
+
{"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 106 |
+
- NEW_ANSWER: genuinely new, evidence-backed information (put it in "draft").
|
| 107 |
+
- SAME_ANSWER: the scene only repeats what you already said.
|
| 108 |
+
- NO_ANSWER: nothing answerable is visible yet.
|
| 109 |
+
"confidence" = your probability that "draft" is correct AND new.""",
|
| 110 |
+
# v4 — v3's coverage PLUS v1's crisp lead. Two-part answer: direct answer FIRST, then enumerate
|
| 111 |
+
# everything relevant. Aims to capture the v3+v1 ensemble's +0.008 at 1x cost. Select VLM_PROMPT=v4.
|
| 112 |
+
"v4": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 113 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 114 |
+
After each update you decide whether to speak.
|
| 115 |
+
|
| 116 |
+
RULES
|
| 117 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 118 |
+
of the question. If unsure, stay SILENT — silence beats a wrong or vague statement.
|
| 119 |
+
2. Be timely: the instant the evidence is visible, say it. Do not wait for the action
|
| 120 |
+
to finish or for more certainty than you need.
|
| 121 |
+
3. Structure each answer in TWO parts. FIRST: state the single most DIRECT answer to the
|
| 122 |
+
question — the specific action or object asked about — in one clear opening clause. THEN:
|
| 123 |
+
add every other relevant person, object, action, or detail in view. If several things could
|
| 124 |
+
answer the question, include them ALL. Use concrete verbs and name the specific subjects asked about.
|
| 125 |
+
4. Declarative, present tense. No speculation, no hedging words ("maybe", "appears", "seems"),
|
| 126 |
+
no timestamps in text. NEVER repeat or rephrase anything you have already told the user (it
|
| 127 |
+
is listed for you) — add only genuinely new information.
|
| 128 |
+
5. Output STRICT JSON, nothing else:
|
| 129 |
+
{"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 130 |
+
- NEW_ANSWER: genuinely new, evidence-backed information (put it in "draft").
|
| 131 |
+
- SAME_ANSWER: the scene only repeats what you already said.
|
| 132 |
+
- NO_ANSWER: nothing answerable is visible yet.
|
| 133 |
+
"confidence" = your probability that "draft" is correct AND new.""",
|
| 134 |
+
# v5 — even MORE exhaustive than v3: explicitly read on-screen text/captions/numbers + spatial/context.
|
| 135 |
+
# Tests whether v3 leaves coverage on the table (esp. text-based questions). Select VLM_PROMPT=v5.
|
| 136 |
+
"v5": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 137 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 138 |
+
After each update you decide whether to speak.
|
| 139 |
+
|
| 140 |
+
RULES
|
| 141 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 142 |
+
of the question. If unsure, stay SILENT — silence beats a wrong or vague statement.
|
| 143 |
+
2. Be timely: the instant the evidence is visible, say it. Do not wait for the action
|
| 144 |
+
to finish or for more certainty than you need.
|
| 145 |
+
3. Describe the scene as EXHAUSTIVELY as the frames allow, to answer the question. Include:
|
| 146 |
+
every person and what each is DOING (concrete verbs); every relevant object; ALL on-screen
|
| 147 |
+
text, captions, labels, names, and numbers, quoted verbatim; and the setting/context. If
|
| 148 |
+
several things could answer the question, include them ALL — leave nothing relevant out.
|
| 149 |
+
4. Declarative, present tense. No speculation, no hedging words ("maybe", "appears", "seems"),
|
| 150 |
+
no timestamps in text. NEVER repeat or rephrase anything you have already told the user (it
|
| 151 |
+
is listed for you) — add only genuinely new information.
|
| 152 |
+
5. Output STRICT JSON, nothing else:
|
| 153 |
+
{"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 154 |
+
- NEW_ANSWER: genuinely new, evidence-backed information (put it in "draft").
|
| 155 |
+
- SAME_ANSWER: the scene only repeats what you already said.
|
| 156 |
+
- NO_ANSWER: nothing answerable is visible yet.
|
| 157 |
+
"confidence" = your probability that "draft" is correct AND new.""",
|
| 158 |
+
# v6 — v5's exhaustive breadth PLUS temporal coverage: describe the SEQUENCE of actions / what just
|
| 159 |
+
# changed across recent frames, to catch fleeting actions v5 misses (chew/throw/swirl). VLM_PROMPT=v6.
|
| 160 |
+
"v6": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 161 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 162 |
+
After each update you decide whether to speak.
|
| 163 |
+
|
| 164 |
+
RULES
|
| 165 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 166 |
+
of the question. If unsure, stay SILENT — silence beats a wrong or vague statement.
|
| 167 |
+
2. Be timely: the instant the evidence is visible, say it. Do not wait for the action
|
| 168 |
+
to finish or for more certainty than you need.
|
| 169 |
+
3. Describe EXHAUSTIVELY to answer the question, covering BOTH the static scene AND its motion:
|
| 170 |
+
- every person and the specific action each is performing RIGHT NOW (concrete verbs);
|
| 171 |
+
- what just CHANGED across the recent frames — the sequence of actions/events, not a snapshot
|
| 172 |
+
(e.g. picked up, threw, turned, poured, swirled, stepped forward);
|
| 173 |
+
- every relevant object; ALL on-screen text, captions, labels, names, and numbers, verbatim;
|
| 174 |
+
- the setting/context. If several things could answer the question, include them ALL.
|
| 175 |
+
4. Declarative, present tense. No speculation, no hedging words ("maybe", "appears", "seems"),
|
| 176 |
+
no timestamps in text. NEVER repeat or rephrase anything you have already told the user (it
|
| 177 |
+
is listed for you) — add only genuinely new information.
|
| 178 |
+
5. Output STRICT JSON, nothing else:
|
| 179 |
+
{"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 180 |
+
- NEW_ANSWER: genuinely new, evidence-backed information (put it in "draft").
|
| 181 |
+
- SAME_ANSWER: the scene only repeats what you already said.
|
| 182 |
+
- NO_ANSWER: nothing answerable is visible yet.
|
| 183 |
+
"confidence" = your probability that "draft" is correct AND new.""",
|
| 184 |
+
# v8 — FEW-SHOT (prompt-USAGE experiment): v5-style exhaustive rules + two GENERIC invented worked
|
| 185 |
+
# examples (no eval leakage) teaching "name the asked action, then cover everything, hedge if ambiguous".
|
| 186 |
+
# Probe: recovers v5-misses (+0.33) with far less good-turn poisoning than v7's instruction-anchoring.
|
| 187 |
+
"v8": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 188 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 189 |
+
After each update you decide whether to speak.
|
| 190 |
+
|
| 191 |
+
RULES
|
| 192 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 193 |
+
of the question. If unsure, stay SILENT.
|
| 194 |
+
2. Be timely: the instant the evidence is visible, say it.
|
| 195 |
+
3. Describe the scene as EXHAUSTIVELY as the frames allow, to answer the question. Make sure you state
|
| 196 |
+
what the specific subject the question asks about is DOING (the action/event), AND include every other
|
| 197 |
+
person and what each is doing, every relevant object, ALL on-screen text/captions/numbers verbatim, and
|
| 198 |
+
the setting. If the action could be read multiple ways, mention ALL plausible readings rather than guessing one.
|
| 199 |
+
4. Declarative, present tense. No hedging words, no timestamps. NEVER repeat what you already said.
|
| 200 |
+
5. Output STRICT JSON only: {"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 201 |
+
|
| 202 |
+
Two GENERIC examples of an ideal "draft" (invented — study how each NAMES the asked action up front, then covers everything else):
|
| 203 |
+
|
| 204 |
+
Q: "What is the person doing?" -> draft: "The person is whisking eggs in a glass mixing bowl. She wears
|
| 205 |
+
a blue apron and stands at a marble kitchen counter; beside the bowl are a carton of milk, a bag of flour,
|
| 206 |
+
and a wooden spoon. On-screen text in the corner reads 'Breakfast Recipes'."
|
| 207 |
+
|
| 208 |
+
Q: "What is happening in the game?" -> draft: "A soccer player in a red jersey kicks the ball toward the
|
| 209 |
+
goal while a defender in white slides in to block it. Other players run across the green pitch; a packed
|
| 210 |
+
stadium crowd is behind them, and the scoreboard shows '2 - 1'."
|
| 211 |
+
|
| 212 |
+
Now answer for the current frames. Output only the JSON.""",
|
| 213 |
+
# v9 — few-shot iteration of v8: 3 GENERIC examples covering the failure modes (#32): a clear action,
|
| 214 |
+
# a HEDGED ambiguous action (teach hedge-don't-commit), and a SCREEN/text+numbers case.
|
| 215 |
+
"v9": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 216 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 217 |
+
After each update you decide whether to speak.
|
| 218 |
+
|
| 219 |
+
RULES
|
| 220 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 221 |
+
of the question. If unsure, stay SILENT.
|
| 222 |
+
2. Be timely: the instant the evidence is visible, say it.
|
| 223 |
+
3. Describe the scene as EXHAUSTIVELY as the frames allow, to answer the question. Make sure you state
|
| 224 |
+
what the specific subject the question asks about is DOING (the action/event), AND include every other
|
| 225 |
+
person and what each is doing, every relevant object, ALL on-screen text/captions/numbers verbatim, and
|
| 226 |
+
the setting. If the action could be read multiple ways, mention ALL plausible readings rather than guessing one.
|
| 227 |
+
4. Declarative, present tense. No hedging words, no timestamps. NEVER repeat what you already said.
|
| 228 |
+
5. Output STRICT JSON only: {"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 229 |
+
|
| 230 |
+
Three GENERIC examples of an ideal "draft" (invented — study how each NAMES the asked action, COVERS everything, and HEDGES when unsure):
|
| 231 |
+
|
| 232 |
+
Q: "What is the person doing?" -> draft: "The person is whisking eggs in a glass mixing bowl. She wears
|
| 233 |
+
a blue apron and stands at a marble kitchen counter; beside the bowl are a carton of milk, a bag of flour,
|
| 234 |
+
and a wooden spoon. On-screen text in the corner reads 'Breakfast Recipes'."
|
| 235 |
+
|
| 236 |
+
Q: "What is the man doing with the paper?" -> draft: "The man is either folding or creasing a sheet of
|
| 237 |
+
paper — the exact motion is unclear, so both readings are possible. He holds it with both hands over a
|
| 238 |
+
wooden table scattered with scissors, a glue stick, and sheets of coloured card."
|
| 239 |
+
|
| 240 |
+
Q: "What is shown on the screen?" -> draft: "The screen shows a video-game menu titled 'SQUAD SELECT'
|
| 241 |
+
listing four teams, each with a star rating (three to five stars) and a percentage. A scoreboard in the
|
| 242 |
+
corner reads '1,250 pts'. A voice-over explains how the ratings are calculated."
|
| 243 |
+
|
| 244 |
+
Now answer for the current frames. Output only the JSON.""",
|
| 245 |
+
# v10 — ROBUSTNESS test of v8: SAME few-shot shape (2 clean action examples) but DIFFERENT generic
|
| 246 |
+
# content (gardening + car-repair). If v5+v3+v10 ≈ v5+v3+v8 (~0.80), the few-shot lever is robust to
|
| 247 |
+
# the example choice (not example-luck overfit). Generic/invented — no eval leakage.
|
| 248 |
+
"v10": """You are a proactive visual assistant watching a LIVE video stream to answer ONE
|
| 249 |
+
question, given at the start. You only ever receive frames up to the current moment.
|
| 250 |
+
After each update you decide whether to speak.
|
| 251 |
+
|
| 252 |
+
RULES
|
| 253 |
+
1. Speak ONLY when the current frames give clear visual evidence answering some part
|
| 254 |
+
of the question. If unsure, stay SILENT.
|
| 255 |
+
2. Be timely: the instant the evidence is visible, say it.
|
| 256 |
+
3. Describe the scene as EXHAUSTIVELY as the frames allow, to answer the question. Make sure you state
|
| 257 |
+
what the specific subject the question asks about is DOING (the action/event), AND include every other
|
| 258 |
+
person and what each is doing, every relevant object, ALL on-screen text/captions/numbers verbatim, and
|
| 259 |
+
the setting. If the action could be read multiple ways, mention ALL plausible readings rather than guessing one.
|
| 260 |
+
4. Declarative, present tense. No hedging words, no timestamps. NEVER repeat what you already said.
|
| 261 |
+
5. Output STRICT JSON only: {"decision":"NO_ANSWER"|"SAME_ANSWER"|"NEW_ANSWER","confidence":0.0-1.0,"draft":"..."}
|
| 262 |
+
|
| 263 |
+
Two GENERIC examples of an ideal "draft" (invented — study how each NAMES the asked action up front, then covers everything else):
|
| 264 |
+
|
| 265 |
+
Q: "What is the person doing?" -> draft: "The person is repotting a seedling — she lifts a small green
|
| 266 |
+
plant from a plastic tray and lowers it into a terracotta pot of dark soil. She works at a wooden potting
|
| 267 |
+
bench; a watering can and a bag of compost stand beside her, and a label on the pot reads 'BASIL'."
|
| 268 |
+
|
| 269 |
+
Q: "What is the man doing?" -> draft: "The man is changing a car tyre. He kneels by the front wheel of a
|
| 270 |
+
silver sedan and loosens the bolts with a wrench; a jack holds the car up and the removed hubcap lies on the
|
| 271 |
+
tarmac. He wears a grey hoodie and blue jeans."
|
| 272 |
+
|
| 273 |
+
Now answer for the current frames. Output only the JSON.""",
|
| 274 |
+
}
|
| 275 |
+
# v5 (exhaustive + OCR/text) is the validated default: +0.0408 on the WEB holdout vs v3 (0.7328->0.7737),
|
| 276 |
+
# which was itself +0.133 vs v1. Override with VLM_PROMPT=v1|v2|v3|v4 for ablation.
|
| 277 |
+
SYSTEM_PROMPT = _SYSTEM_PROMPTS[os.environ.get("VLM_PROMPT", "v5").lower()]
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def prompt_for(name):
|
| 281 |
+
"""Resolve a prompt name (v1..v6) to its system-prompt TEXT; None -> the env default SYSTEM_PROMPT.
|
| 282 |
+
Lets the ensemble policy call the VLM with several prompts per frame (e.g. v5 then v3)."""
|
| 283 |
+
return SYSTEM_PROMPT if name is None else _SYSTEM_PROMPTS[name.lower()]
|
| 284 |
+
|
| 285 |
+
|
| 286 |
+
_client_lock = threading.Lock()
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
def _client_():
|
| 290 |
+
global _client
|
| 291 |
+
if _client is None: # double-checked locking for thread pools
|
| 292 |
+
with _client_lock:
|
| 293 |
+
if _client is None:
|
| 294 |
+
from openai import OpenAI
|
| 295 |
+
_client = OpenAI(api_key=os.environ["OPENROUTER_API_KEY"],
|
| 296 |
+
base_url="https://openrouter.ai/api/v1")
|
| 297 |
+
return _client
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
def _b64(path, max_px):
|
| 301 |
+
for attempt in range(4): # Windows throws transient Errno 22 under load
|
| 302 |
+
try:
|
| 303 |
+
with Image.open(path) as im0: # context-manager => file handle is CLOSED
|
| 304 |
+
im0.load() # (leaking handles -> WinError 1450 under load)
|
| 305 |
+
im = im0.convert("RGB")
|
| 306 |
+
break # reached only on success -> `im` is bound
|
| 307 |
+
except OSError:
|
| 308 |
+
if attempt == 3:
|
| 309 |
+
raise
|
| 310 |
+
time.sleep(0.25)
|
| 311 |
+
# any non-OSError (MemoryError, bad mode, ...) propagates with its real traceback
|
| 312 |
+
if max(im.size) > max_px:
|
| 313 |
+
im.thumbnail((max_px, max_px))
|
| 314 |
+
buf = io.BytesIO()
|
| 315 |
+
im.save(buf, "JPEG", quality=85)
|
| 316 |
+
return base64.b64encode(buf.getvalue()).decode()
|
| 317 |
+
|
| 318 |
+
|
| 319 |
+
def _img(path, max_px):
|
| 320 |
+
return {"type": "image_url",
|
| 321 |
+
"image_url": {"url": f"data:image/jpeg;base64,{_b64(path, max_px)}"}}
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def _user_text(ctx):
|
| 325 |
+
"""The user-turn text for ONE streaming decision (question + already-said memory). Shared by both
|
| 326 |
+
backends + the cost probe so the prompt can't drift."""
|
| 327 |
+
said = "\n".join(f"- {s}" for s in ctx["already_said"]) or "(nothing yet)"
|
| 328 |
+
return (f'Question (asked at t=0): {ctx["question"]}\n'
|
| 329 |
+
f'Current time: {ctx["now"]:.1f}s\n\nYou have already told the user:\n{said}\n\n'
|
| 330 |
+
"Below are the most recent frames (newest last), then older keyframes. "
|
| 331 |
+
"Decide for THIS moment. Output only the JSON.")
|
| 332 |
+
|
| 333 |
+
|
| 334 |
+
def build_messages(ctx, prompt=None):
|
| 335 |
+
"""OpenAI-style system+user chat messages (recent frames 480px, then older keyframes 256px).
|
| 336 |
+
`prompt` (system-prompt text) overrides the default SYSTEM_PROMPT for one call (ensemble)."""
|
| 337 |
+
content = [{"type": "text", "text": _user_text(ctx)}]
|
| 338 |
+
content += [_img(p, _RECENT_PX) for _, p in ctx["recent_frames"]]
|
| 339 |
+
content += [_img(p, _HIST_PX) for _, p in ctx["history_frames"]]
|
| 340 |
+
return [{"role": "system", "content": prompt or SYSTEM_PROMPT}, {"role": "user", "content": content}]
|
| 341 |
+
|
| 342 |
+
|
| 343 |
+
def _vertex_key():
|
| 344 |
+
return os.environ.get("VERTEX_KEY") or os.environ.get("VERTEX_API_KEY") or os.environ.get("GOOGLE_API_KEY")
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
def _vx_text(data):
|
| 348 |
+
try:
|
| 349 |
+
return "".join(p.get("text", "") for p in data["candidates"][0]["content"]["parts"])
|
| 350 |
+
except Exception:
|
| 351 |
+
return ""
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def _call_vertex(ctx, retries=4, prompt=None):
|
| 355 |
+
"""Gemini VLM decision via Vertex AI express REST (images inline). Same JSON contract as OpenRouter.
|
| 356 |
+
`prompt` (system-prompt text) overrides the default SYSTEM_PROMPT for one call (ensemble)."""
|
| 357 |
+
key = _vertex_key()
|
| 358 |
+
if not key:
|
| 359 |
+
raise RuntimeError("no VERTEX_KEY — set it in .env to use VLM_PROVIDER=vertex")
|
| 360 |
+
parts = [{"text": _user_text(ctx)}]
|
| 361 |
+
parts += [{"inlineData": {"mimeType": "image/jpeg", "data": _b64(p, _RECENT_PX)}} for _, p in ctx["recent_frames"]]
|
| 362 |
+
parts += [{"inlineData": {"mimeType": "image/jpeg", "data": _b64(p, _HIST_PX)}} for _, p in ctx["history_frames"]]
|
| 363 |
+
gen = {"temperature": 0, "maxOutputTokens": VLM_MAX_TOKENS}
|
| 364 |
+
if _VLM_THINKING is not None: # e.g. "0" to try disabling thinking
|
| 365 |
+
gen["thinkingConfig"] = {"thinkingBudget": int(_VLM_THINKING)}
|
| 366 |
+
body = json.dumps({
|
| 367 |
+
"systemInstruction": {"parts": [{"text": prompt or SYSTEM_PROMPT}]},
|
| 368 |
+
"contents": [{"role": "user", "parts": parts}],
|
| 369 |
+
"generationConfig": gen,
|
| 370 |
+
}).encode("utf-8")
|
| 371 |
+
url = f"{VERTEX_BASE_URL}/publishers/google/models/{_VLM_MODEL_VX}:generateContent"
|
| 372 |
+
last = None
|
| 373 |
+
for attempt in range(retries):
|
| 374 |
+
try:
|
| 375 |
+
req = urllib.request.Request(url, data=body, method="POST",
|
| 376 |
+
headers={"Content-Type": "application/json", "x-goog-api-key": key})
|
| 377 |
+
with urllib.request.urlopen(req, timeout=VLM_TIMEOUT_S) as resp:
|
| 378 |
+
return _parse(_vx_text(json.loads(resp.read().decode("utf-8"))))
|
| 379 |
+
except urllib.error.HTTPError as e:
|
| 380 |
+
detail = ""
|
| 381 |
+
try:
|
| 382 |
+
detail = e.read().decode("utf-8")[:300]
|
| 383 |
+
except Exception:
|
| 384 |
+
pass
|
| 385 |
+
last = RuntimeError(f"Vertex VLM HTTP {e.code}: {detail}")
|
| 386 |
+
if e.code in (400, 401, 403, 404): # bad key / model / request — not transient
|
| 387 |
+
raise last from e
|
| 388 |
+
if attempt == retries - 1:
|
| 389 |
+
raise last from e
|
| 390 |
+
time.sleep(min(2 ** attempt, 20))
|
| 391 |
+
except Exception as e: # timeout / connection -> retry
|
| 392 |
+
last = e
|
| 393 |
+
if attempt == retries - 1:
|
| 394 |
+
raise
|
| 395 |
+
time.sleep(min(2 ** attempt, 20))
|
| 396 |
+
raise last
|
| 397 |
+
|
| 398 |
+
|
| 399 |
+
def call_vlm(ctx, max_tokens=200, retries=4, prompt=None):
|
| 400 |
+
"""One per-frame VLM decision. `prompt` (system-prompt text from prompt_for()) overrides the
|
| 401 |
+
default for one call so the ensemble policy can query v5 then v3 on the same frame."""
|
| 402 |
+
if VLM_PROVIDER == "vertex":
|
| 403 |
+
return _call_vertex(ctx, retries=retries, prompt=prompt)
|
| 404 |
+
messages = build_messages(ctx, prompt)
|
| 405 |
+
extra = {"extra_body": {"reasoning": {"enabled": False}}} if DISABLE_REASONING else {}
|
| 406 |
+
for attempt in range(retries):
|
| 407 |
+
try:
|
| 408 |
+
r = _client_().chat.completions.create(
|
| 409 |
+
model=MODEL, temperature=0, max_tokens=max_tokens, messages=messages, **extra)
|
| 410 |
+
return _parse(r.choices[0].message.content)
|
| 411 |
+
except Exception:
|
| 412 |
+
if attempt == retries - 1:
|
| 413 |
+
raise
|
| 414 |
+
time.sleep(2 ** attempt) # 1, 2, 4 s backoff on 429/5xx/transient
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def _parse(s):
|
| 418 |
+
"""Last flat JSON object in the text (robust to prose). Fail-safe = stay silent."""
|
| 419 |
+
try:
|
| 420 |
+
d = json.loads(re.findall(r"\{[^{}]*\}", s, re.S)[-1])
|
| 421 |
+
return {"decision": d.get("decision", "NO_ANSWER"),
|
| 422 |
+
"confidence": float(d.get("confidence", 0.0)),
|
| 423 |
+
"draft": str(d.get("draft", ""))}
|
| 424 |
+
except Exception:
|
| 425 |
+
return {"decision": "NO_ANSWER", "confidence": 0.0, "draft": ""}
|
src/humomni/tuning/__init__.py
ADDED
|
File without changes
|
src/humomni/tuning/cache.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# cache.py — disk-backed VLM-decision cache for the M4 "one paid pass -> free tuning" loop.
|
| 2 |
+
# Decisions are cached per (question_id, t) with a FIXED empty already_said, so each call is
|
| 3 |
+
# independent of theta/gate/dedup and can be parallelised + replayed offline (tune.py).
|
| 4 |
+
# Also precomputes per-frame visual novelty so the perception gate can be replayed offline.
|
| 5 |
+
import os
|
| 6 |
+
import glob
|
| 7 |
+
import json
|
| 8 |
+
import time
|
| 9 |
+
import threading
|
| 10 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 11 |
+
|
| 12 |
+
# VLM_CACHE_PATH lets a different VLM (e.g. a Vertex gemini probe) write a SEPARATE cache so it never
|
| 13 |
+
# collides with the shipped qwen cache — the (qid,t) key is VLM-agnostic, so the file must differ.
|
| 14 |
+
CACHE_PATH = os.environ.get("VLM_CACHE_PATH", "cache/web_vlm_cache.jsonl")
|
| 15 |
+
NOVELTY_PATH = "cache/web_novelty.json"
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class VLMCache:
|
| 19 |
+
"""Append-only jsonl cache keyed by (question_id, round(t,1)). Thread-safe, resumable."""
|
| 20 |
+
|
| 21 |
+
def __init__(self, path=CACHE_PATH):
|
| 22 |
+
self.path = path
|
| 23 |
+
self.mem = {}
|
| 24 |
+
self.lock = threading.Lock()
|
| 25 |
+
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
|
| 26 |
+
if os.path.exists(path):
|
| 27 |
+
with open(path, encoding="utf-8") as f:
|
| 28 |
+
for ln in f:
|
| 29 |
+
ln = ln.strip()
|
| 30 |
+
if ln:
|
| 31 |
+
r = json.loads(ln)
|
| 32 |
+
self.mem[(r["qid"], r["t"])] = r["v"]
|
| 33 |
+
|
| 34 |
+
def get(self, qid, t):
|
| 35 |
+
return self.mem.get((qid, round(float(t), 1)))
|
| 36 |
+
|
| 37 |
+
def set(self, qid, t, v):
|
| 38 |
+
k = (qid, round(float(t), 1))
|
| 39 |
+
line = json.dumps({"qid": k[0], "t": k[1], "v": v}, ensure_ascii=False) + "\n"
|
| 40 |
+
with self.lock:
|
| 41 |
+
self.mem[k] = v
|
| 42 |
+
for attempt in range(5): # ponytail: Windows throws transient EINVAL(22) on append under load
|
| 43 |
+
try:
|
| 44 |
+
with open(self.path, "a", encoding="utf-8") as w:
|
| 45 |
+
w.write(line)
|
| 46 |
+
return
|
| 47 |
+
except OSError:
|
| 48 |
+
if attempt == 4:
|
| 49 |
+
raise
|
| 50 |
+
time.sleep(0.1 * (attempt + 1))
|
| 51 |
+
|
| 52 |
+
def __len__(self):
|
| 53 |
+
return len(self.mem)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def frames_of(qid, data_dir="data/web_frames"):
|
| 57 |
+
"""Ascending (t, path) for a question_id (dir name drops the .mp4 suffix)."""
|
| 58 |
+
name = qid[:-4] if qid.endswith(".mp4") else qid
|
| 59 |
+
d = os.path.join(data_dir, name)
|
| 60 |
+
return sorted([(float(os.path.basename(p)[:-4]), p) for p in glob.glob(d + "/*.jpg")],
|
| 61 |
+
key=lambda x: x[0])
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def _sample_dirs(data_dir, limit):
|
| 65 |
+
dirs = sorted(d for d in glob.glob(os.path.join(data_dir, "*")) if os.path.isdir(d))
|
| 66 |
+
return dirs[:limit] if limit else dirs
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def build_web_cache(data_dir="data/web_frames", cache_path=CACHE_PATH, limit=0, workers=12, prompt=None):
|
| 70 |
+
"""Call the VLM on EVERY frame (gate OFF, already_said=[]) and cache the decision. Resumable (skips
|
| 71 |
+
already-cached frames). `prompt` = a system-prompt TEXT override (e.g. prompt_for('v8')) so one process
|
| 72 |
+
can build several prompt caches; None uses the env default SYSTEM_PROMPT."""
|
| 73 |
+
from humomni.core.context import build_context
|
| 74 |
+
from humomni.phase1.vlm_client import call_vlm
|
| 75 |
+
cache = VLMCache(cache_path)
|
| 76 |
+
tasks = []
|
| 77 |
+
for d in _sample_dirs(data_dir, limit):
|
| 78 |
+
q = json.load(open(os.path.join(d, "question.json"), encoding="utf-8"))
|
| 79 |
+
qid, question = q["question_id"], q["question"]
|
| 80 |
+
fr = sorted([(float(os.path.basename(p)[:-4]), p) for p in glob.glob(d + "/*.jpg")],
|
| 81 |
+
key=lambda x: x[0])
|
| 82 |
+
for i, (t, _) in enumerate(fr):
|
| 83 |
+
if cache.get(qid, t) is None:
|
| 84 |
+
tasks.append((qid, question, fr[:i + 1], t))
|
| 85 |
+
print(f"to compute: {len(tasks)} frame-decisions ({len(cache)} already cached)")
|
| 86 |
+
|
| 87 |
+
def work(task):
|
| 88 |
+
qid, question, hist, t = task
|
| 89 |
+
cache.set(qid, t, call_vlm(build_context(question, hist, said=[]), prompt=prompt))
|
| 90 |
+
return 1
|
| 91 |
+
|
| 92 |
+
if tasks:
|
| 93 |
+
try:
|
| 94 |
+
from tqdm import tqdm
|
| 95 |
+
with ThreadPoolExecutor(max_workers=workers) as ex:
|
| 96 |
+
for _ in tqdm(ex.map(work, tasks), total=len(tasks), desc="vlm-cache"):
|
| 97 |
+
pass
|
| 98 |
+
except ImportError:
|
| 99 |
+
with ThreadPoolExecutor(max_workers=workers) as ex:
|
| 100 |
+
list(ex.map(work, tasks))
|
| 101 |
+
print(f"cache size: {len(cache)} decisions -> {cache_path}")
|
| 102 |
+
return cache
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def build_novelty(data_dir="data/web_frames", out=NOVELTY_PATH, limit=0):
|
| 106 |
+
"""Per-frame grayscale novelty (consecutive-frame diff) so the gate is replayable offline."""
|
| 107 |
+
import numpy as np
|
| 108 |
+
from PIL import Image
|
| 109 |
+
res = {}
|
| 110 |
+
for d in _sample_dirs(data_dir, limit):
|
| 111 |
+
qid = json.load(open(os.path.join(d, "question.json"), encoding="utf-8"))["question_id"]
|
| 112 |
+
fr = sorted([(float(os.path.basename(p)[:-4]), p) for p in glob.glob(d + "/*.jpg")],
|
| 113 |
+
key=lambda x: x[0])
|
| 114 |
+
prev, nov = None, {}
|
| 115 |
+
for t, p in fr:
|
| 116 |
+
f = np.asarray(Image.open(p).convert("L").resize((64, 36)), dtype=np.float32)
|
| 117 |
+
nov[f"{t:.1f}"] = 255.0 if prev is None else float(np.abs(f - prev).mean())
|
| 118 |
+
prev = f
|
| 119 |
+
res[qid] = nov
|
| 120 |
+
os.makedirs(os.path.dirname(out) or ".", exist_ok=True)
|
| 121 |
+
json.dump(res, open(out, "w"))
|
| 122 |
+
print(f"novelty for {len(res)} samples -> {out}")
|
| 123 |
+
return res
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
import argparse
|
| 128 |
+
ap = argparse.ArgumentParser()
|
| 129 |
+
ap.add_argument("--func", choices=["vlm", "novelty", "both"], default="both")
|
| 130 |
+
ap.add_argument("--data_dir", default="data/web_frames")
|
| 131 |
+
ap.add_argument("--limit", type=int, default=0)
|
| 132 |
+
ap.add_argument("--workers", type=int, default=12)
|
| 133 |
+
a = ap.parse_args()
|
| 134 |
+
if a.func in ("novelty", "both"):
|
| 135 |
+
build_novelty(a.data_dir, limit=a.limit)
|
| 136 |
+
if a.func in ("vlm", "both"):
|
| 137 |
+
build_web_cache(a.data_dir, limit=a.limit, workers=a.workers)
|
src/humomni/tuning/faithful_eval.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# faithful_eval.py — score a config under the REAL Gemini judge, fast.
|
| 2 |
+
# Trick: a recording pass enumerates every (judge/entail) call the metric will make, we warm
|
| 3 |
+
# those concurrently through a thread pool (Gemini is I/O-bound, results cached to disk), then
|
| 4 |
+
# the real score() runs sequentially on cache hits. Subsequent configs reuse overlapping calls.
|
| 5 |
+
import concurrent.futures as cf
|
| 6 |
+
|
| 7 |
+
import humomni.core.judges as judges
|
| 8 |
+
from humomni.core.pauc_eval import score, make_judges
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def _collect(gold, pred):
|
| 12 |
+
"""Enumerate (superset of) the judge/entail string-pairs the metric needs. Dummy returns
|
| 13 |
+
(0 / False) make turn bleed-removal a no-op, so we capture ALL prefix pairs -> never miss."""
|
| 14 |
+
jpairs, epairs = [], []
|
| 15 |
+
|
| 16 |
+
def rec_judge(ans, ref, question=None):
|
| 17 |
+
jpairs.append((ans, ref, question))
|
| 18 |
+
return 0
|
| 19 |
+
|
| 20 |
+
def rec_entail(prior, hyp):
|
| 21 |
+
epairs.append((list(prior), hyp))
|
| 22 |
+
return False
|
| 23 |
+
|
| 24 |
+
score(gold, pred, judge=rec_judge, entail=rec_entail)
|
| 25 |
+
return jpairs, epairs
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def warm(gold, pred, workers=8):
|
| 29 |
+
judges.jc_load()
|
| 30 |
+
jpairs, epairs = _collect(gold, pred)
|
| 31 |
+
jobs, seen = [], set()
|
| 32 |
+
for ans, ref, question in jpairs:
|
| 33 |
+
k = judges.judge_key(ref, ans, question)
|
| 34 |
+
if k not in judges._jcache and k not in seen:
|
| 35 |
+
seen.add(k)
|
| 36 |
+
jobs.append(("judge", ans, ref, question))
|
| 37 |
+
for prior, hyp in epairs:
|
| 38 |
+
k = judges.jc_key("entail", " | ".join(prior), hyp)
|
| 39 |
+
if k not in judges._jcache and k not in seen:
|
| 40 |
+
seen.add(k)
|
| 41 |
+
jobs.append(("entail", prior, hyp))
|
| 42 |
+
|
| 43 |
+
def do(job):
|
| 44 |
+
if job[0] == "judge":
|
| 45 |
+
judges.llm_judge(job[1], job[2], job[3])
|
| 46 |
+
else:
|
| 47 |
+
judges.llm_entails(job[1], job[2])
|
| 48 |
+
|
| 49 |
+
if jobs:
|
| 50 |
+
# incremental jc_save every CHUNK so a mid-run credit drain doesn't lose warm progress
|
| 51 |
+
CHUNK = 100
|
| 52 |
+
with cf.ThreadPoolExecutor(max_workers=workers) as ex:
|
| 53 |
+
done = 0
|
| 54 |
+
for _ in ex.map(do, jobs):
|
| 55 |
+
done += 1
|
| 56 |
+
if done % CHUNK == 0:
|
| 57 |
+
judges.jc_save()
|
| 58 |
+
judges.jc_save()
|
| 59 |
+
return len(jobs)
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def faithful_score(gold, pred, workers=8):
|
| 63 |
+
n_new = warm(gold, pred, workers)
|
| 64 |
+
jf, ef = make_judges("llm") # cached Gemini judge + entailment
|
| 65 |
+
r = score(gold, pred, judge=jf, entail=ef)
|
| 66 |
+
r["new_judge_calls"] = n_new
|
| 67 |
+
return r
|
tests/test_causality.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# test_causality.py — the causality regression test (keep green at all times).
|
| 2 |
+
# Feeds shuffled frames to the driver and requires an AssertionError; also checks that
|
| 3 |
+
# a correctly-ordered stream runs and that the silent policy emits nothing.
|
| 4 |
+
# Dependency-free: run with `python test_causality.py` (exits non-zero on failure).
|
| 5 |
+
from humomni.core.streaming_driver import drive
|
| 6 |
+
from humomni.phase1.policies import SilentPolicy
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def test_shuffled_raises():
|
| 10 |
+
frames = [(0.5, "a"), (1.0, "b"), (2.0, "c"), (1.5, "d")] # 1.5 after 2.0 -> violation
|
| 11 |
+
try:
|
| 12 |
+
drive(frames, SilentPolicy(), "qid.mp4", "q")
|
| 13 |
+
except AssertionError:
|
| 14 |
+
return
|
| 15 |
+
raise SystemExit("FAIL: shuffled frames did NOT raise AssertionError (causality unguarded)")
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_ascending_ok():
|
| 19 |
+
frames = [(0.5, "a"), (1.0, "b"), (1.5, "c")]
|
| 20 |
+
rec = drive(frames, SilentPolicy(), "qid.mp4", "q")
|
| 21 |
+
assert rec["question_id"] == "qid.mp4", rec
|
| 22 |
+
assert rec["model_response_list"] == [], rec # silent policy never speaks
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def test_equal_timestamp_raises():
|
| 26 |
+
frames = [(0.5, "a"), (0.5, "b")] # not strictly increasing
|
| 27 |
+
try:
|
| 28 |
+
drive(frames, SilentPolicy(), "qid.mp4", "q")
|
| 29 |
+
except AssertionError:
|
| 30 |
+
return
|
| 31 |
+
raise SystemExit("FAIL: duplicate timestamp did NOT raise (assert must be strict >)")
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
if __name__ == "__main__":
|
| 35 |
+
test_shuffled_raises()
|
| 36 |
+
test_ascending_ok()
|
| 37 |
+
test_equal_timestamp_raises()
|
| 38 |
+
print("PASS: causality assert raises on shuffled/duplicate frames; ascending order OK.")
|
tests/test_inference_parity.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
# test_inference_parity.py — run_inference and infer_simple MUST produce identical output for the
|
| 3 |
+
# same policy (both drive the shared reset/step contract). Also checks the multi-emit contract:
|
| 4 |
+
# a frame whose step() returns a list emits several answers at that timestamp (the v5+v3 ensemble).
|
| 5 |
+
import importlib.util
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
import tempfile
|
| 10 |
+
|
| 11 |
+
from humomni.core.streaming_driver import run_sample, _emissions
|
| 12 |
+
|
| 13 |
+
# infer_simple lives in scripts/ (not a package) — load it by path so the test can call infer_one.
|
| 14 |
+
_p = os.path.join(os.path.dirname(__file__), "..", "scripts", "infer_simple.py")
|
| 15 |
+
_spec = importlib.util.spec_from_file_location("infer_simple", _p)
|
| 16 |
+
infer_simple = importlib.util.module_from_spec(_spec)
|
| 17 |
+
_spec.loader.exec_module(infer_simple)
|
| 18 |
+
infer_one = infer_simple.infer_one
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
class MockPolicy:
|
| 22 |
+
"""Deterministic, content-free: exercises None / str / list / [] returns from step()."""
|
| 23 |
+
def reset(self, question):
|
| 24 |
+
self.n = 0
|
| 25 |
+
|
| 26 |
+
def step(self, t, frame_path):
|
| 27 |
+
self.n += 1
|
| 28 |
+
if self.n == 2:
|
| 29 |
+
return "single" # one answer (str)
|
| 30 |
+
if self.n == 3:
|
| 31 |
+
return ["a", "b"] # TWO answers at one timestamp (the ensemble case)
|
| 32 |
+
if self.n == 4:
|
| 33 |
+
return [] # explicit silence
|
| 34 |
+
return None # silence
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def _make_sample(d, n_frames=5):
|
| 38 |
+
for i in range(1, n_frames + 1):
|
| 39 |
+
open(os.path.join(d, f"{i * 0.5:.1f}.jpg"), "wb").close() # empty frames on a 0.5 grid
|
| 40 |
+
json.dump({"question_id": "vid.mp4", "question": "what happens?"},
|
| 41 |
+
open(os.path.join(d, "question.json"), "w", encoding="utf-8"))
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def test_parity_and_multiemit():
|
| 45 |
+
with tempfile.TemporaryDirectory() as d:
|
| 46 |
+
_make_sample(d)
|
| 47 |
+
a = run_sample(d, MockPolicy()) # run_inference path (streaming_driver.drive)
|
| 48 |
+
b = infer_one(d, MockPolicy()) # infer_simple path
|
| 49 |
+
assert a == b, f"run_inference != infer_simple:\n {a}\n {b}"
|
| 50 |
+
resp = a["model_response_list"]
|
| 51 |
+
# frame 2 -> "single" @1.0; frame 3 -> "a","b" @1.5; frames 4,5 -> silent
|
| 52 |
+
assert [r["content"] for r in resp] == ["single", "a", "b"], resp
|
| 53 |
+
assert [r["time"] for r in resp] == [1.0, 1.5, 1.5], resp # two emits share t=1.5
|
| 54 |
+
# _emissions normalization
|
| 55 |
+
assert _emissions(None) == [] and _emissions("x") == ["x"] and _emissions(["a", "b"]) == ["a", "b"]
|
| 56 |
+
print("PASS: run_inference == infer_simple; multi-emit (ensemble) lands N answers at one timestamp.")
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
if __name__ == "__main__":
|
| 60 |
+
sys.exit(test_parity_and_multiemit())
|