CCCCyx commited on
Commit
1694c7f
·
verified ·
1 Parent(s): 7d165a6

per-sample media budgets in processor; per-sample MRoPE position ids for batched vision inputs

Browse files
Files changed (1) hide show
  1. 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.image_processor(images=images, **images_kwargs)
453
- image_grid_thw = image_inputs["image_grid_thw"]
 
 
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.video_processor(videos=videos, **videos_kwargs)
463
- video_grid_thw = videos_inputs["video_grid_thw"]
 
 
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["video_metadata"]
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