Mage-VL / neural_codec /reproduce_bench.py
Xinjie-Q's picture
Upload Mage-VL: unified codec-native streaming VLM (image+video understanding + proactive gate)
12acbba verified
Raw
History Blame Contribute Delete
6.5 kB
#!/usr/bin/env python3
"""Reproduce the benchmark pipeline for a SINGLE video, end-to-end.
Runs the EXACT same DCVC-RT readiness patch-selection used to generate the
benchmark codec assets (deterministic on the pytorch fallback path), then feeds
the resulting canvases to the release model — giving the same result a benchmark
eval would produce for this video.
ALL DCVC selection params live in ``<model>/processor/preprocessor_config.json`` under
``codec.dcvc`` and are read by the pipeline via ``codec_dcvc_config`` — NOT env
vars. The file defaults to the ``b50`` benchmark config. ``--config`` optionally
patches that file to a named preset (cap12 / b50 / s95_b50) before generating;
``--config current`` (default) uses the file as-is.
Note: inference uses trust_remote_code; the Mage-VL-Ported remote code targets a
newer transformers. Under the eval env (transformers 4.57.3) pass the equivalent
``--model .../Mage-VL-Base`` (identical weights, 4.57.3-compatible code).
Usage:
python neural_codec/reproduce_bench.py --video V.mp4 --question "..." [--config b50]
python neural_codec/reproduce_bench.py --video V.mp4 --gen_only # just write canvases
"""
from __future__ import annotations
import argparse, os, subprocess, sys
from pathlib import Path
_HERE = os.path.dirname(os.path.abspath(__file__))
_MODEL_DEFAULT = os.path.dirname(_HERE) # Mage-VL-Ported/
# Named presets (written into codec.dcvc by --config). Values are config keys.
CONFIGS = {
"cap12": {"per_frame_cap_ratio": 1.2, "bottom_atten": 1.0, "bottom_band": 0.10, "threshold_scale": 1.0},
"b50": {"per_frame_cap_ratio": 1.2, "bottom_atten": 0.5, "bottom_band": 0.10, "threshold_scale": 1.0},
"s95_b50": {"per_frame_cap_ratio": 1.2, "bottom_atten": 0.5, "bottom_band": 0.10, "threshold_scale": 0.95},
}
def _patch_config_file(config: str):
"""Optionally write a named preset's selection knobs into the model's
preprocessor_config.json (codec.dcvc), so the readiness pipeline picks them
up. ``config='current'`` leaves the file untouched (use whatever is set)."""
if config == "current":
return
import json, collections
p = os.path.join(_MODEL_DEFAULT, "processor", "preprocessor_config.json")
with open(p, encoding="utf-8") as f:
c = json.load(f, object_pairs_hook=collections.OrderedDict)
c["codec"]["dcvc"].update(CONFIGS[config])
tmp = p + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(c, f, indent=2)
os.replace(tmp, p) # atomic: never leave a truncated config on write error
print(f"[reproduce] wrote preset '{config}' into {p} (codec.dcvc): {CONFIGS[config]}")
def gen_canvases(video: str, out_dir: str, config: str, gpu: int) -> str:
# ALL DCVC selection params come from preprocessor_config.json's codec.dcvc
# (read by the pipeline via codec_dcvc_config) — NOT env / CLI. --config may
# patch that file first.
_patch_config_file(config)
env = dict(os.environ)
env["CUDA_VISIBLE_DEVICES"] = str(gpu)
env["http_proxy"] = ""; env["https_proxy"] = ""
os.makedirs(out_dir, exist_ok=True)
cmd = [sys.executable, os.path.join(_HERE, "dcvc_readiness_gen.py"),
"--video", video, "--out_dir", out_dir]
print(f"[reproduce] gen (config={config}): {' '.join(cmd)}", flush=True)
subprocess.run(cmd, check=True, env=env)
return out_dir
def run_inference(model_dir: str, asset_dir: str, question: str, max_new_tokens: int,
max_pixels, gpu: int) -> str:
import torch
sys.path.insert(0, _HERE) # codec_loader
sys.path.insert(0, os.path.join(model_dir, "processor")) # MageVLProcessor + siblings
from codec_loader import build_inputs_from_assets # noqa: E402
from processing_magevl import MageVLProcessor # noqa: E402
from transformers import AutoModelForCausalLM
device = torch.device(f"cuda:{gpu}" if torch.cuda.is_available() else "cpu")
processor = MageVLProcessor.from_pretrained(model_dir) # subdir-aware
model = AutoModelForCausalLM.from_pretrained(
os.path.join(model_dir, "transformer"), trust_remote_code=True,
torch_dtype="auto").to(device).eval()
messages = [{"role": "user", "content": [{"type": "video"},
{"type": "text", "text": question}]}]
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = build_inputs_from_assets(processor, asset_dir, text,
max_pixels=max_pixels, device=device)
inputs["pixel_values"] = inputs["pixel_values"].to(model.dtype)
with torch.inference_mode():
gen = model.generate(**inputs, max_new_tokens=max_new_tokens, do_sample=False)
new = gen[0, inputs["input_ids"].shape[1]:]
return processor.tokenizer.decode(new, skip_special_tokens=True).strip()
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--video", required=True)
ap.add_argument("--question", default="Describe this video in detail.")
ap.add_argument("--config", default="current", choices=["current"]+list(CONFIGS),
help="current=use preprocessor_config.json as-is; a preset patches codec.dcvc first")
ap.add_argument("--model", default=_MODEL_DEFAULT)
ap.add_argument("--out_dir", default=None, help="asset dir (default /tmp/reproduce/<stem>)")
ap.add_argument("--gpu", type=int, default=0)
ap.add_argument("--max_new_tokens", type=int, default=256)
ap.add_argument("--max_pixels", type=int, default=None,
help="canvas pixel budget; default None -> codec.dcvc.max_pixels "
"(150000) from preprocessor_config.json")
ap.add_argument("--gen_only", action="store_true")
args = ap.parse_args()
out_dir = args.out_dir or f"/tmp/reproduce/{Path(args.video).stem}_{args.config}"
gen_canvases(args.video, out_dir, args.config, args.gpu)
nc = len([p for ext in ("jpg", "png") for p in Path(out_dir).glob(f"canvas_*.{ext}")])
print(f"[reproduce] wrote {nc} canvases -> {out_dir}")
if args.gen_only:
return
ans = run_inference(args.model, out_dir, args.question, args.max_new_tokens,
args.max_pixels, args.gpu)
print("\n================ ANSWER ================\n" + ans + "\n")
if __name__ == "__main__":
main()