duovlm-40m-v1 / code /ask_cli.py
Duoia's picture
DuoVLM-40M v1: from-scratch 40M vision-language model (frozen CLIP + MiniPile-pretrained LM)
4afe981 verified
Raw History Blame Contribute Delete
3.9 kB
#!/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()