Video-Text-to-Text
Transformers
Safetensors
English
Chinese
moss_vl
feature-extraction
MOSS-VL
realtime
streaming
video-understanding
bitsandbytes
NF4
quantized
custom_code
4-bit precision
Instructions to use OpenMOSS-Team/MOSS-VL-Realtime-NF4 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use OpenMOSS-Team/MOSS-VL-Realtime-NF4 with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("OpenMOSS-Team/MOSS-VL-Realtime-NF4", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
per-sample media budgets in processor; per-sample MRoPE position ids for batched vision inputs
Browse files- processing_moss_vl.py +68 -13
processing_moss_vl.py
CHANGED
|
@@ -23,7 +23,7 @@ import torch
|
|
| 23 |
from torchvision.transforms.v2 import functional as F
|
| 24 |
from PIL import Image
|
| 25 |
from transformers.feature_extraction_utils import BatchFeature
|
| 26 |
-
from transformers.image_utils import ImageInput, SizeDict
|
| 27 |
from transformers.image_processing_utils_fast import group_images_by_shape, reorder_images
|
| 28 |
from transformers.utils import TensorType
|
| 29 |
from transformers.processing_utils import (
|
|
@@ -384,6 +384,56 @@ class MossVLProcessor(ProcessorMixin):
|
|
| 384 |
# Token ids that should always be masked in labels (e.g. <|image_pad|>)
|
| 385 |
self.mask_token_ids = {self.image_token_id}
|
| 386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
def __call__(
|
| 388 |
self,
|
| 389 |
text: Union[TextInput, PreTokenizedInput, List[TextInput], List[PreTokenizedInput]] = None,
|
|
@@ -445,12 +495,21 @@ class MossVLProcessor(ProcessorMixin):
|
|
| 445 |
**kwargs,
|
| 446 |
)
|
| 447 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 448 |
# Step 1: Process images if provided
|
| 449 |
if images is not None:
|
| 450 |
images_kwargs = output_kwargs["images_kwargs"].copy()
|
| 451 |
images_kwargs["return_tensors"] = None
|
| 452 |
-
image_inputs = self.
|
| 453 |
-
|
|
|
|
|
|
|
| 454 |
else:
|
| 455 |
image_inputs = {}
|
| 456 |
image_grid_thw = None
|
|
@@ -459,25 +518,21 @@ class MossVLProcessor(ProcessorMixin):
|
|
| 459 |
if videos is not None:
|
| 460 |
videos_kwargs = output_kwargs["videos_kwargs"].copy()
|
| 461 |
videos_kwargs["return_tensors"] = None
|
| 462 |
-
videos_inputs = self.
|
| 463 |
-
|
|
|
|
|
|
|
| 464 |
# If user has not requested video metadata, pop it
|
| 465 |
if "return_metadata" not in kwargs:
|
| 466 |
-
video_metadata = videos_inputs.pop("video_metadata")
|
| 467 |
else:
|
| 468 |
-
video_metadata = videos_inputs
|
| 469 |
else:
|
| 470 |
videos_inputs = {}
|
| 471 |
video_grid_thw = None
|
| 472 |
video_metadata = None
|
| 473 |
|
| 474 |
# Step 3: Process text with placeholder replacement
|
| 475 |
-
if text is None or (isinstance(text, str) and len(text.strip()) == 0):
|
| 476 |
-
raise ValueError("Text input is required for MossVL processor and cannot be empty.")
|
| 477 |
-
|
| 478 |
-
if not isinstance(text, list):
|
| 479 |
-
text = [text]
|
| 480 |
-
|
| 481 |
text = text.copy() # Copy to avoid in-place modifications
|
| 482 |
|
| 483 |
# Prepare labels_spans if provided
|
|
|
|
| 23 |
from torchvision.transforms.v2 import functional as F
|
| 24 |
from PIL import Image
|
| 25 |
from transformers.feature_extraction_utils import BatchFeature
|
| 26 |
+
from transformers.image_utils import ImageInput, SizeDict, make_flat_list_of_images
|
| 27 |
from transformers.image_processing_utils_fast import group_images_by_shape, reorder_images
|
| 28 |
from transformers.utils import TensorType
|
| 29 |
from transformers.processing_utils import (
|
|
|
|
| 384 |
# Token ids that should always be masked in labels (e.g. <|image_pad|>)
|
| 385 |
self.mask_token_ids = {self.image_token_id}
|
| 386 |
|
| 387 |
+
|
| 388 |
+
def _process_media_per_sample(self, media, texts, kind, processor, kwargs):
|
| 389 |
+
"""Apply a complete media budget independently to each text sample.
|
| 390 |
+
|
| 391 |
+
Lists within one sample still share the image/video budget (and video
|
| 392 |
+
frame limit). A segmented video is one source item occupying several
|
| 393 |
+
video placeholders; it must not cross text-sample boundaries.
|
| 394 |
+
"""
|
| 395 |
+
if len(texts) == 1:
|
| 396 |
+
return processor(**{kind: media}, **kwargs)
|
| 397 |
+
if kind == "images":
|
| 398 |
+
items = make_flat_list_of_images(media)
|
| 399 |
+
widths = [1] * len(items)
|
| 400 |
+
token = self.image_placeholder
|
| 401 |
+
else:
|
| 402 |
+
items = media if isinstance(media, list) else [media]
|
| 403 |
+
widths = [
|
| 404 |
+
len(item["segments"]) if isinstance(item, dict) and item.get("segments") else 1
|
| 405 |
+
for item in items
|
| 406 |
+
]
|
| 407 |
+
token = self.video_placeholder
|
| 408 |
+
counts = [text.count(token) for text in texts]
|
| 409 |
+
if sum(counts) != sum(widths):
|
| 410 |
+
raise ValueError(
|
| 411 |
+
f"{kind} placeholders do not match supplied media: "
|
| 412 |
+
f"per-sample counts={counts}, media slots={sum(widths)}"
|
| 413 |
+
)
|
| 414 |
+
groups, offset = [], 0
|
| 415 |
+
for count in counts:
|
| 416 |
+
start, remaining = offset, count
|
| 417 |
+
while remaining:
|
| 418 |
+
width = widths[offset]
|
| 419 |
+
if width > remaining:
|
| 420 |
+
raise ValueError("A segmented video cannot cross text-sample boundaries")
|
| 421 |
+
remaining -= width
|
| 422 |
+
offset += 1
|
| 423 |
+
groups.append(items[start:offset])
|
| 424 |
+
# Keep media order, but never share resize/frame budgets across samples.
|
| 425 |
+
results = [processor(**{kind: group}, **kwargs) for group in groups if group]
|
| 426 |
+
if not results:
|
| 427 |
+
return {}
|
| 428 |
+
combined = {}
|
| 429 |
+
for key in results[0]:
|
| 430 |
+
values = [result[key] for result in results]
|
| 431 |
+
if key == "video_metadata":
|
| 432 |
+
combined[key] = [item for value in values for item in value]
|
| 433 |
+
else:
|
| 434 |
+
combined[key] = _concat_array_or_tensor(values, axis=0)
|
| 435 |
+
return combined
|
| 436 |
+
|
| 437 |
def __call__(
|
| 438 |
self,
|
| 439 |
text: Union[TextInput, PreTokenizedInput, List[TextInput], List[PreTokenizedInput]] = None,
|
|
|
|
| 495 |
**kwargs,
|
| 496 |
)
|
| 497 |
|
| 498 |
+
# Establish sample boundaries before media preprocessing: each sample
|
| 499 |
+
# receives its own complete image budget and video budget.
|
| 500 |
+
if text is None or (isinstance(text, str) and not text.strip()):
|
| 501 |
+
raise ValueError("Text input is required for MossVL processor and cannot be empty.")
|
| 502 |
+
if not isinstance(text, list):
|
| 503 |
+
text = [text]
|
| 504 |
+
|
| 505 |
# Step 1: Process images if provided
|
| 506 |
if images is not None:
|
| 507 |
images_kwargs = output_kwargs["images_kwargs"].copy()
|
| 508 |
images_kwargs["return_tensors"] = None
|
| 509 |
+
image_inputs = self._process_media_per_sample(
|
| 510 |
+
images, text, "images", self.image_processor, images_kwargs
|
| 511 |
+
)
|
| 512 |
+
image_grid_thw = image_inputs.get("image_grid_thw")
|
| 513 |
else:
|
| 514 |
image_inputs = {}
|
| 515 |
image_grid_thw = None
|
|
|
|
| 518 |
if videos is not None:
|
| 519 |
videos_kwargs = output_kwargs["videos_kwargs"].copy()
|
| 520 |
videos_kwargs["return_tensors"] = None
|
| 521 |
+
videos_inputs = self._process_media_per_sample(
|
| 522 |
+
videos, text, "videos", self.video_processor, videos_kwargs
|
| 523 |
+
)
|
| 524 |
+
video_grid_thw = videos_inputs.get("video_grid_thw")
|
| 525 |
# If user has not requested video metadata, pop it
|
| 526 |
if "return_metadata" not in kwargs:
|
| 527 |
+
video_metadata = videos_inputs.pop("video_metadata", [])
|
| 528 |
else:
|
| 529 |
+
video_metadata = videos_inputs.get("video_metadata", [])
|
| 530 |
else:
|
| 531 |
videos_inputs = {}
|
| 532 |
video_grid_thw = None
|
| 533 |
video_metadata = None
|
| 534 |
|
| 535 |
# Step 3: Process text with placeholder replacement
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
text = text.copy() # Copy to avoid in-place modifications
|
| 537 |
|
| 538 |
# Prepare labels_spans if provided
|