[Bugfix][MiniCPM-o] Align Daily-Omni offline loading and duplex soft-… (#6095)

Signed-off-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
amy-why-3459 authored and GitHub committed 2026-08-12 18:36:31 +08:00
1 parent 31384cb39c
commit 7cd826d28c
6 files changed
+327 -97

No files matched your search

@@ -0,0 +1,92 @@
# SPDX-License-Identifier: Apache-2.0
"""Offline Daily-Omni sources: local mirrors must not fall back to the Hub id."""
import json
import os
import tarfile
from pathlib import Path
import pytest
from vllm_omni.benchmarks.data_modules.daily_omni_dataset import (
daily_omni_local_qa_json,
daily_omni_local_videos_dir,
resolve_daily_omni_local_root,
)
pytestmark = [pytest.mark.core_model, pytest.mark.cpu, pytest.mark.benchmark]
def _write_qa_json(root: Path) -> Path:
qa = root / "qa.json"
qa.write_text(json.dumps([{"Question": "q", "Answer": "A", "video_id": "vid0"}]), encoding="utf-8")
return qa
def test_resolve_local_root_returns_none_for_hub_id() -> None:
assert resolve_daily_omni_local_root("liarliar/Daily-Omni") is None
assert resolve_daily_omni_local_root("") is None
assert resolve_daily_omni_local_root(None) is None
def test_plain_local_mirror(tmp_path: Path) -> None:
_write_qa_json(tmp_path)
(tmp_path / "Videos" / "vid0").mkdir(parents=True)
root = resolve_daily_omni_local_root(str(tmp_path))
assert root == tmp_path.resolve()
assert daily_omni_local_qa_json(root) == (tmp_path / "qa.json").resolve()
assert daily_omni_local_videos_dir(root) == root / "Videos"
def test_hf_cache_dir_resolves_to_snapshot(tmp_path: Path) -> None:
cache_dir = tmp_path / "datasets--liarliar--Daily-Omni"
snapshot = cache_dir / "snapshots" / "deadbeef"
snapshot.mkdir(parents=True)
_write_qa_json(snapshot)
(cache_dir / "refs").mkdir()
(cache_dir / "refs" / "main").write_text("deadbeef", encoding="utf-8")
root = resolve_daily_omni_local_root(str(cache_dir))
assert root == snapshot.resolve()
assert daily_omni_local_qa_json(root) == (snapshot / "qa.json").resolve()
def test_hf_cache_dir_without_refs_picks_newest_snapshot(tmp_path: Path) -> None:
cache_dir = tmp_path / "datasets--liarliar--Daily-Omni"
older = cache_dir / "snapshots" / "aaa"
newer = cache_dir / "snapshots" / "bbb"
older.mkdir(parents=True)
newer.mkdir(parents=True)
os.utime(older, (1, 1))
os.utime(newer, (10, 10))
assert resolve_daily_omni_local_root(str(cache_dir)) == newer.resolve()
def test_videos_tar_is_extracted(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("HF_HOME", str(tmp_path / "hf_home"))
root = tmp_path / "mirror"
(root / "Videos" / "vid0").mkdir(parents=True)
(root / "Videos" / "vid0" / "vid0_video.mp4").write_bytes(b"fake")
with tarfile.open(root / "Videos.tar", "w") as tf:
tf.add(root / "Videos", arcname="Videos")
# Only the tarball is left in the mirror.
for child in (root / "Videos").rglob("*"):
if child.is_file():
child.unlink()
(root / "Videos" / "vid0").rmdir()
(root / "Videos").rmdir()
videos_dir = daily_omni_local_videos_dir(root)
assert videos_dir is not None
assert (videos_dir / "vid0" / "vid0_video.mp4").is_file()
# Second call reuses the extraction instead of redoing it.
assert daily_omni_local_videos_dir(root) == videos_dir
def test_missing_qa_json_reports_none(tmp_path: Path) -> None:
root = resolve_daily_omni_local_root(str(tmp_path))
assert daily_omni_local_qa_json(root) is None
assert daily_omni_local_videos_dir(root) is None
+7 -19
View File
@@ -2,9 +2,8 @@
The ``minicpm-interleave`` mode is an accuracy-critical protocol: MiniCPM-o only reaches
OpenBMB's reported Daily-Omni score when frames and 1s audio segments arrive strictly
interleaved, with ``max_slice_nums=1`` / ``use_image_id=False`` and an empty MiniCPM
system message (not the Qwen system prompt). These tests pin that contract so a refactor
cannot silently regress the score.
interleaved, with ``max_slice_nums=1`` / ``use_image_id=False`` and no Qwen system prompt.
These tests pin that contract so a refactor cannot silently regress the score.
vllm stubs are installed by tests/benchmarks/conftest.py before collection.
"""
@@ -99,22 +98,13 @@ def _stub_extract(monkeypatch, dataset: DailyOmniDataset, counter: list[int]) ->
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(("n", "k"), [(10, 4), (100, 64), (1000, 64), (7, 7), (3, 5), (1, 1)])
def test_uniform_sample_indices_matches_openbmb_midpoint(n: int, k: int) -> None:
"""Pin OpenBMB midpoint-bin sampling (not np.linspace endpoints)."""
@pytest.mark.parametrize(("n", "k"), [(10, 4), (100, 64), (7, 7), (3, 5), (1, 1)])
def test_uniform_sample_indices_matches_numpy_linspace(n: int, k: int) -> None:
got = _uniform_sample_indices(n, k)
if n <= k:
assert got == list(range(n))
else:
gap = n / k
expected = [int(i * gap + gap / 2) for i in range(k)]
assert got == expected
# Endpoints differ from linspace for typical Daily-Omni downsample sizes.
linspace = [int(i) for i in np.linspace(0, n - 1, k, dtype=int)]
if n == 1000 and k == 64:
assert got[0] == 7 and got[-1] == 992
assert linspace[0] == 0 and linspace[-1] == 999
assert got != linspace
assert got == [int(i) for i in np.linspace(0, n - 1, k, dtype=int)]
def test_uniform_sample_indices_non_positive_k() -> None:
@@ -223,16 +213,14 @@ def test_minicpm_interleave_missing_local_video_returns_none(qa_json, tmp_path)
# ---------------------------------------------------------------------------
def test_minicpm_interleave_keeps_empty_system_message(monkeypatch, qa_json, video_dir) -> None:
"""Official MiniCPM omni mode still sends role=system with empty content."""
def test_minicpm_interleave_omits_qwen_system_message(monkeypatch, qa_json, video_dir) -> None:
ds = _make_dataset(qa_json, video_dir, pack_mode="minicpm-interleave", input_mode="all")
_stub_extract(monkeypatch, ds, [])
parts, _extra, _position = ds._compose_daily_omni_multimodal(_VIDEO_ID, None)
messages = ds._build_daily_omni_openai_messages(parts, "q?", {"A": "a"})
assert [m["role"] for m in messages] == ["system", "user"]
assert messages[0]["content"][0]["text"] == ""
assert [m["role"] for m in messages] == ["user"]
def test_qwen_pack_mode_keeps_system_message(qa_json, video_dir) -> None:
@@ -3,8 +3,8 @@
Daily-Omni settings follow the MiniCPM interleaved AV recipe that reaches
~78% overall accuracy on Daily-Omni (``minicpm-interleave``, ``temperature=0``,
text modalities with ``use_tts_template``, and server ``--interleave-mm-strings``
+ 1fps / 128-frame media-io kwargs, also pinned in ``minicpmo_4_5.yaml``).
text modalities, and server ``--interleave-mm-strings`` + 1fps / 128-frame
media-io kwargs, also pinned in ``minicpmo_4_5.yaml``).
"""
from __future__ import annotations
@@ -40,10 +40,7 @@ _MAX_SEED_TTS_MEAN_WER = 0.05
# Match the validated Daily-Omni client body from daily_omni_bench.sh.
_DAILY_EXTRA_BODY = {
"modalities": ["text"],
"chat_template_kwargs": {
"enable_thinking": False,
"use_tts_template": True,
},
"chat_template_kwargs": {"enable_thinking": False},
}
_SEED_EXTRA_BODY = {
"modalities": ["text", "audio"],
@@ -53,7 +53,11 @@ def test_duplex_admission_and_expiry_reaper(omni_server, model_prefix: str, tmp_
assert result["admission"]["overflow_error_code"] == "resource_exhausted"
@hardware_test(res={"cuda": "H100", "npu": "A3"}, num_cards=1)
# CUDA-only for now: this contract asserts the model speaks while the user is
# still talking, which only holds when the duplex pipeline sustains real-time
# throughput. The current NPU stack runs several times slower than real time,
# so it never reaches a mid-stream decision point.
@hardware_test(res={"cuda": "H100"}, num_cards=1)
@pytest.mark.parametrize("omni_server", SERVER_PARAMS, indirect=True)
def test_duplex_soft_interrupt(omni_server, model_prefix: str, tmp_path: Path) -> None:
input_wav = validated_soft_interrupt_wav()
@@ -70,13 +74,8 @@ def test_duplex_soft_interrupt(omni_server, model_prefix: str, tmp_path: Path) -
timeout_s=180.0,
require_audio=True,
no_realtime_pacing=False,
# Single-GPU co-location cannot reliably finish the fixture's
# two-response soft-interrupt handoff before the WAV ends.
# model-policy + deterministic sampling checks streaming audio
# lifecycle instead of the diagnostic two-response contract.
validation_mode="model-policy",
temperature=0.0,
min_responses=1,
validation_mode="response-required",
min_responses=2,
min_audio_deltas_per_response=2,
input_sha256=SOFT_INTERRUPT_SHA256,
expect_followup_response_substring=None,
@@ -88,5 +87,4 @@ def test_duplex_soft_interrupt(omni_server, model_prefix: str, tmp_path: Path) -
assert result["error_count"] == 0
assert result["response_lifecycle_ok"] is True
assert result["response_audio_contract_ok"] is True
assert result["response_before_final_commit"] is True
assert result["enough_responses"] is True
assert result["followup_response_transcript_ok"] is True
@@ -110,47 +110,17 @@ def _daily_omni_find_videos_root_in_extract(tmp: Path) -> Path:
)
def ensure_daily_omni_hub_videos_dir(repo_id: str) -> Path:
"""Download ``Videos.tar`` from the Hugging Face dataset repo and return the ``Videos`` root.
def _extract_daily_omni_videos_tar(tar_path: Path, cache_key: str) -> Path:
"""Extract ``Videos.tar`` once into the shared media cache and return the ``Videos`` root.
The returned path matches ``--daily-omni-video-dir`` (directory containing ``{{video_id}}/``).
Cached under ``HF_HOME`` / ``vllm_omni/daily_omni_media/<repo>``. Reuses extraction when the
tarball fingerprint matches.
Raises:
ImportError: if ``huggingface_hub`` is not installed.
FileNotFoundError / RuntimeError: if the archive is missing or malformed.
Cached under ``HF_HOME`` / ``vllm_omni/daily_omni_media/<cache_key>``. Reuses a previous
extraction when the tarball fingerprint matches.
"""
rid = (repo_id or "").strip()
if not rid:
raise ValueError("repo_id is required to download Daily-Omni Videos.tar")
try:
from huggingface_hub import hf_hub_download
except ImportError as e:
raise ImportError(
"Daily-Omni Hub media download requires huggingface_hub. "
"Install it (e.g. with vLLM) or provide --daily-omni-video-dir with a local extract."
) from e
safe = rid.replace("/", "__").replace("\\", "_")
safe = cache_key.replace("/", "__").replace("\\", "_")
staging_root = _daily_omni_hf_cache_root() / "vllm_omni" / "daily_omni_media" / safe
videos_dir = staging_root / "Videos"
marker = staging_root / ".videos_extracted"
tar_path: Path | None = None
for fname in ("Videos.tar", "videos.tar"):
try:
tar_path = Path(hf_hub_download(repo_id=rid, filename=fname, repo_type="dataset"))
break
except Exception:
continue
if tar_path is None or not tar_path.is_file():
raise FileNotFoundError(
f"Could not download Videos.tar from Hugging Face dataset {rid!r} (tried Videos.tar / videos.tar)."
)
fp = _daily_omni_tar_fingerprint(tar_path)
if marker.is_file() and videos_dir.is_dir():
try:
@@ -167,7 +137,7 @@ def ensure_daily_omni_hub_videos_dir(repo_id: str) -> Path:
shutil.rmtree(work, ignore_errors=True)
work.mkdir(parents=True)
try:
logger.info("Extracting Daily-Omni Videos.tar from %s (repo=%s)", tar_path, rid)
logger.info("Extracting Daily-Omni Videos.tar from %s (cache_key=%s)", tar_path, cache_key)
with tarfile.open(tar_path, "r:*") as tf:
tf.extractall(path=work, filter="data")
found = _daily_omni_find_videos_root_in_extract(work)
@@ -178,10 +148,106 @@ def ensure_daily_omni_hub_videos_dir(repo_id: str) -> Path:
shutil.rmtree(work, ignore_errors=True)
marker.write_text(fp, encoding="utf-8")
logger.info("Daily-Omni Hub media ready at %s", videos_dir)
logger.info("Daily-Omni media ready at %s", videos_dir)
return videos_dir
def ensure_daily_omni_hub_videos_dir(repo_id: str) -> Path:
"""Download ``Videos.tar`` from the Hugging Face dataset repo and return the ``Videos`` root.
The returned path matches ``--daily-omni-video-dir`` (directory containing ``{{video_id}}/``).
Raises:
ImportError: if ``huggingface_hub`` is not installed.
FileNotFoundError / RuntimeError: if the archive is missing or malformed.
"""
rid = (repo_id or "").strip()
if not rid:
raise ValueError("repo_id is required to download Daily-Omni Videos.tar")
try:
from huggingface_hub import hf_hub_download
except ImportError as e:
raise ImportError(
"Daily-Omni Hub media download requires huggingface_hub. "
"Install it (e.g. with vLLM) or provide --daily-omni-video-dir with a local extract."
) from e
tar_path: Path | None = None
for fname in ("Videos.tar", "videos.tar"):
try:
tar_path = Path(hf_hub_download(repo_id=rid, filename=fname, repo_type="dataset"))
break
except Exception:
continue
if tar_path is None or not tar_path.is_file():
raise FileNotFoundError(
f"Could not download Videos.tar from Hugging Face dataset {rid!r} (tried Videos.tar / videos.tar)."
)
return _extract_daily_omni_videos_tar(tar_path, rid)
def _resolve_hf_cache_snapshot(path: Path) -> Path:
"""Map a HF hub cache dir (``datasets--org--name``) to its checked-out snapshot dir.
Any other directory is returned unchanged.
"""
snapshots = path / "snapshots"
if not snapshots.is_dir():
return path
ref = path / "refs" / "main"
if ref.is_file():
try:
target = snapshots / ref.read_text(encoding="utf-8").strip()
except OSError:
target = snapshots
if target.is_dir():
return target
revisions = [p for p in snapshots.iterdir() if p.is_dir()]
if revisions:
return max(revisions, key=lambda p: p.stat().st_mtime)
return path
def resolve_daily_omni_local_root(dataset_path: str | None) -> Path | None:
"""Return the local Daily-Omni mirror root for ``dataset_path``, or ``None`` if not local.
Accepts a plain directory (``qa.json`` + ``Videos/`` or ``Videos.tar``, i.e. the layout of the
Hub repo) as well as a HuggingFace hub cache directory (``datasets--liarliar--Daily-Omni``),
which is resolved to its snapshot. Hub ids such as ``liarliar/Daily-Omni`` return ``None``.
"""
raw = (dataset_path or "").strip()
if not raw:
return None
path = Path(raw).expanduser()
if not path.is_dir():
return None
return _resolve_hf_cache_snapshot(path.resolve())
def daily_omni_local_qa_json(root: Path) -> Path | None:
"""Locate ``qa.json`` inside a local Daily-Omni mirror (``None`` when absent)."""
for name in ("qa.json", "QA.json"):
candidate = root / name
if candidate.is_file():
return candidate
return None
def daily_omni_local_videos_dir(root: Path) -> Path | None:
"""Return the ``Videos`` root of a local mirror, extracting ``Videos.tar`` when needed."""
for name in ("Videos", "videos"):
candidate = root / name
if candidate.is_dir():
return candidate
for name in ("Videos.tar", "videos.tar"):
tar_path = root / name
if tar_path.is_file():
return _extract_daily_omni_videos_tar(tar_path, f"local__{root.name}")
return None
class _ListDatasetIterator:
"""Simple iterator wrapper around a list to mimic HuggingFace streaming dataset behavior."""
@@ -222,24 +288,92 @@ DAILY_OMNI_SYSTEM_TEXT = (
"capable of perceiving auditory and visual inputs, as well as generating text and speech."
)
# MiniCPM-o ``get_sys_prompt(mode="omni")`` without ref-audio is an empty system string
# (still sent as role=system; see ``_build_daily_omni_openai_messages``).
# MiniCPM-o ``get_sys_prompt(mode="omni")`` without ref-audio is an empty system string.
MINICPM_OMNI_SYSTEM_TEXT = ""
def _uniform_sample_indices(n: int, k: int) -> list[int]:
"""Evenly pick ``k`` indices from ``0..n-1`` (OpenBMB ``uniform_sample`` midpoint bins).
Matches OmniEvalKit / MiniCPM ``uniform_sample``: midpoint of each equal-width bin,
not ``np.linspace`` endpoints. See
https://github.com/OpenBMB/OmniEvalKit/blob/1adac0577258d539efc03f16d8a0f4b3f7df6c19/o_e_Kit/utils/utils.py#L50-L54
"""
"""Evenly pick ``k`` indices from ``0..n-1`` (MiniCPM ``uniform_sample`` on index lists)."""
if k <= 0:
return []
if n <= k:
return list(range(n))
gap = n / k
return [int(i * gap + gap / 2) for i in range(k)]
# Match numpy linspace(..., dtype=int) used upstream.
try:
import numpy as np
return [int(i) for i in np.linspace(0, n - 1, k, dtype=int)]
except ImportError:
if k == 1:
return [0]
return [int(round(i * (n - 1) / (k - 1))) for i in range(k)]
def _probe_video_frames_and_fps(video_path: Path) -> tuple[int, float]:
"""Frame count and average FPS, replacing ``len(vr)`` / ``vr.get_avg_fps()``.
PyAV is used instead of ``decord`` because ``decord`` ships x86_64-only Linux wheels
and is unmaintained, while ``av`` is already a first-class requirement and has
aarch64 wheels (Ascend hosts).
"""
import av
with av.open(str(video_path)) as container:
stream = container.streams.video[0]
avg_fps = float(stream.average_rate) if stream.average_rate else 0.0
num_frames = stream.frames or 0
if num_frames <= 0:
# Containers such as raw webm leave ``frames`` at 0; derive it from duration.
duration: float | None = None
if stream.duration is not None and stream.time_base is not None:
duration = float(stream.duration * stream.time_base)
elif container.duration is not None:
duration = container.duration / av.time_base
if duration and avg_fps:
num_frames = int(math.floor(duration * avg_fps))
if num_frames <= 0:
# Last resort for streams with no usable metadata at all.
stream.thread_type = "AUTO"
num_frames = sum(1 for _ in container.decode(stream))
if num_frames <= 0:
raise ValueError(f"No decodable video frames in {video_path}")
return num_frames, avg_fps or 1.0
def _decode_video_frames_rgb(video_path: Path, frame_idx: list[int]) -> list[Any]:
"""Decode presentation-order ``frame_idx`` into RGB ``PIL.Image``s.
Mirrors ``decord.VideoReader.get_batch(frame_idx)``: one image per requested index,
in the requested order, duplicates repeated. Decoding is a single forward pass
(no seeking) so the mapping from index to picture matches decord's indexing.
"""
import av
wanted = sorted({int(i) for i in frame_idx})
if not wanted:
return []
decoded: dict[int, Any] = {}
with av.open(str(video_path)) as container:
stream = container.streams.video[0]
stream.thread_type = "AUTO"
cursor = 0
for position, frame in enumerate(container.decode(stream)):
if wanted[cursor] > position:
continue
image = frame.to_image()
# ``<=`` also absorbs indices that overshoot the real frame count.
while cursor < len(wanted) and wanted[cursor] <= position:
decoded[wanted[cursor]] = image
cursor += 1
if cursor >= len(wanted):
break
if not decoded:
raise ValueError(f"Decoded no frames from {video_path} for indices {wanted[:8]}")
# Metadata may promise more frames than the stream actually decodes; clamp to the last one.
last_image = decoded[max(decoded)]
return [decoded.get(int(i), last_image) for i in frame_idx]
def _numpy_to_wav_bytes(audio_np: Any, sample_rate: int = _MINICPM_AUDIO_SR) -> bytes:
@@ -1028,28 +1162,24 @@ class DailyOmniDataset(BenchmarkDataset):
) -> tuple[list[Any], list[Any]]:
"""Port of MiniCPM ``get_video_frame_audio_segments`` (stack_frames=1, 1fps)."""
import numpy as np
from decord import VideoReader, cpu
from PIL import Image
from vllm.multimodal.media.audio import load_audio
vr = VideoReader(str(video_path), ctx=cpu(0))
avg_fps = float(vr.get_avg_fps()) or 1.0
duration = len(vr) / avg_fps
num_video_frames, avg_fps = _probe_video_frames_and_fps(video_path)
duration = num_video_frames / avg_fps
num_seconds = max(1, int(math.ceil(duration)))
second_timestamps = list(range(num_seconds))
if duration > max_num_frames:
timestamps = [round(i * 0.1, 1) for i in range(int(duration / 0.1))]
frame_idx = [min(int(ts * avg_fps), len(vr) - 1) for ts in timestamps]
frame_idx = [min(int(ts * avg_fps), num_video_frames - 1) for ts in timestamps]
pick = _uniform_sample_indices(len(frame_idx), max_num_frames)
frame_idx = [frame_idx[i] for i in pick]
timestamps = [timestamps[i] for i in pick]
else:
frame_idx = [min(int(i * avg_fps), len(vr) - 1) for i in range(num_seconds)]
frame_idx = [min(int(i * avg_fps), num_video_frames - 1) for i in range(num_seconds)]
timestamps = second_timestamps
video = vr.get_batch(frame_idx).asnumpy()
frames = [Image.fromarray(v.astype("uint8")).convert("RGB") for v in video]
frames = _decode_video_frames_rgb(video_path, frame_idx)
audio_segments: list[Any] = []
if include_audio:
@@ -1127,10 +1257,7 @@ class DailyOmniDataset(BenchmarkDataset):
user_content: list[dict[str, Any]] = [*mm_list, {"type": "text", "text": user_text}]
system_text = self._system_text_for_pack_mode()
messages: list[dict[str, Any]] = []
# MiniCPM-o ``get_sys_prompt(mode="omni")`` still emits role=system with empty
# content (no ref-audio). Truthiness would drop that role and change chat-template
# control tokens; keep the empty system message for minicpm-interleave.
if self.pack_mode == "minicpm-interleave" or system_text:
if system_text:
messages.append({"role": "system", "content": [{"type": "text", "text": system_text}]})
messages.append({"role": "user", "content": user_content})
return messages
+30 -2
View File
@@ -14,6 +14,7 @@ import wave
from collections.abc import Iterable
from dataclasses import dataclass, replace
from datetime import datetime
from pathlib import Path
from typing import Any, Literal
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
@@ -38,7 +39,13 @@ from vllm.logger import init_logger
from vllm.tokenizers import TokenizerLike
from vllm_omni.benchmarks.audio_continuity import compute_continuity_stats
from vllm_omni.benchmarks.data_modules.daily_omni_dataset import DailyOmniDataset, DailyOmniSampleRequest
from vllm_omni.benchmarks.data_modules.daily_omni_dataset import (
DailyOmniDataset,
DailyOmniSampleRequest,
daily_omni_local_qa_json,
daily_omni_local_videos_dir,
resolve_daily_omni_local_root,
)
from vllm_omni.benchmarks.data_modules.random_multi_modal_dataset import OmniRandomMultiModalDataset
from vllm_omni.benchmarks.data_modules.seed_tts_dataset import (
SEED_TTS_DEFAULT_OMNI_SYSTEM_PROMPT,
@@ -282,6 +289,26 @@ def get_samples(args, tokenizer):
if isinstance(qa_json, str):
qa_json = qa_json.strip() or None
# A local mirror of the dataset repo (plain directory or HF hub cache dir) is used
# directly: no Hub round-trip, so offline / air-gapped runs work without extra flags.
local_root: Path | None = None
if qa_json is None:
local_root = resolve_daily_omni_local_root(getattr(args, "dataset_path", None)) or (
resolve_daily_omni_local_root(getattr(args, "hf_name", None))
)
if local_root is not None:
local_qa = daily_omni_local_qa_json(local_root)
if local_qa is not None:
qa_json = str(local_qa)
if video_dir is None:
video_dir = daily_omni_local_videos_dir(local_root)
logger.info("Using local Daily-Omni mirror: root=%s", local_root)
else:
logger.info(
"Local Daily-Omni path %s has no qa.json; loading it with `datasets` instead",
local_root,
)
if qa_json is not None:
logger.info(
"Loading Daily-Omni dataset: qa_json=%s, video_dir=%s (Hub not used for QA)",
@@ -304,7 +331,8 @@ def get_samples(args, tokenizer):
repo_id = _daily_omni_repo_from_args(args)
if args.dataset_name == "daily-omni":
if repo_id is None:
repo_id = _DEFAULT_DAILY_OMNI_REPO
# Prefer an on-disk copy over the Hub id so offline runs keep working.
repo_id = str(local_root) if local_root is not None else _DEFAULT_DAILY_OMNI_REPO
elif repo_id is None:
raise ValueError(
"Daily-Omni with --dataset-name hf requires "