File size: 3,897 Bytes
4afe981
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#!/home/devcontainers/tinyllm/.venv/bin/python
"""DuoVLM-40M 命令行:给一张图 + 一个问题 → 回答。

  python ask_cli.py --image photo.jpg --question "How many people are in the image?"
  python ask_cli.py --image photo.jpg                      # 不给问题 = 描述这张图
  python ask_cli.py --image photo.jpg --blind              # 并排跑一遍"不看图"对照
  python ask_cli.py --dir ./photos --question "What is happening?"
  python ask_cli.py --text "The history of the city begins in" --continue-n 60

提示:40M 小模型擅长物体/场景识别、计数、是-否、简短描述;不擅长精细颜色、
读文字(OCR)、长句推理;长输出必须带 no_repeat_ngram=3(默认已开)。
"""
import argparse
import json
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

from duovlm_infer import DESC_Q, DuoVLMInfer  # noqa: E402


def main() -> None:
    ap = argparse.ArgumentParser()
    ap.add_argument("--image", action="append", default=[])
    ap.add_argument("--dir", default="")
    ap.add_argument("--question", default="")
    ap.add_argument("--questions", default="", help="同一张图连问多个,用 | 分隔")
    ap.add_argument("--text", default="", help="纯文本续写种子(不用图)")
    ap.add_argument("--continue-n", type=int, default=40)
    ap.add_argument("--blind", action="store_true")
    ap.add_argument("--no-image", action="store_true")
    ap.add_argument("--max-new", type=int, default=12)
    ap.add_argument("--ngram", type=int, default=3)
    ap.add_argument("--rep-penalty", type=float, default=1.0)
    ap.add_argument("--temperature", type=float, default=0.0)
    ap.add_argument("--top-k", type=int, default=0)
    ap.add_argument("--no-loop-break", action="store_true")
    ap.add_argument("--weights", default="")
    ap.add_argument("--json", action="store_true", help="输出 JSON(给脚本/服务用)")
    a = ap.parse_args()

    imgs = list(a.image)
    if a.dir:
        imgs += [str(p) for p in sorted(Path(a.dir).glob("*.jpg"))[:20]]
    if not imgs and not a.text and not a.no_image:
        print("需要 --image / --dir(或 --text / --no-image 走无图模式)", file=sys.stderr)
        raise SystemExit(2)

    qs = [x.strip() for x in a.questions.split("|") if x.strip()] or [a.question]
    kw = dict(ngram=a.ngram, rep_penalty=a.rep_penalty, temperature=a.temperature,
              top_k=a.top_k, loop_break=not a.no_loop_break)

    vlm = DuoVLMInfer(weights=a.weights or None, verbose=not a.json)
    if not a.json:
        print(vlm.param_report())
        print("-" * 88, flush=True)
    rows = []
    if a.text:
        for seed in [x for x in a.text.split("|") if x]:
            r = vlm.continue_text(seed, max_new=a.continue_n, **kw)
            r["seed"] = seed
            rows.append(r)
            if not a.json:
                print(f"种子: {seed}\n续写: {r['answer']}\n      ({r['ms']}ms)\n", flush=True)
    else:
        for p in imgs or [None]:
            for i, q in enumerate(qs):
                r = vlm.ask(p, q, no_image=a.no_image, max_new=a.max_new, **kw)
                r.update({"image": p, "question": q or DESC_Q})
                if a.blind and p:
                    b = vlm.ask(p, q, blind=True, max_new=a.max_new, **kw)
                    r["blind_answer"] = b["answer"]
                rows.append(r)
                if not a.json:
                    tag = "" if i else Path(p).name if p else "(无图)"
                    print(f"{tag:30s} Q: {r['question']}")
                    print(f"{'':30s} [{r['source']}] {r['ms']}ms  答: {r['answer']}")
                    if "blind_answer" in r:
                        print(f"{'':30s} 不看图: {r['blind_answer']}")
    if a.json:
        print(json.dumps(rows, ensure_ascii=False, indent=1))


if __name__ == "__main__":
    main()