@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 646b85544b
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 6, 21:45 UTC

[Refactor] Remove Molmo2 processor wrapper (#36667)

Signed-off-by: DarkLight1337 <tlleungac@connect.ust.hk>

Cyrus Leung committed Mar 11, 2026 at 18:07 UTC 646b85544b05a18b3cb652debd3f1d078948a781
1 file changed +246 -413
vllm/model_executor/models/molmo2.py
+246 -413
@@ -3,7 +3,7 @@
3 import math
4 from collections.abc import Iterable, Mapping, Sequence
5 from dataclasses import dataclass, fields
6 -from functools import cached_property, partial
6 +from functools import partial
7 from itertools import islice
8 from typing import Annotated, Any
9
@@ -14,14 +14,14 @@ import torch.nn.functional as F
14 from PIL import ImageOps
15 from PIL.Image import Image
16 from transformers import (
17 + BaseImageProcessor,
18 + BaseVideoProcessor,
19 BatchFeature,
20 PretrainedConfig,
21 ProcessorMixin,
20 - TensorType,
22 )
23 from transformers.image_utils import ImageInput
23 -from transformers.tokenization_utils_base import TextInput
24 -from transformers.video_utils import VideoInput, VideoMetadata
24 +from transformers.video_utils import VideoMetadata
25
26 from vllm.compilation.decorators import support_torch_compile
27 from vllm.config import CacheConfig, VllmConfig
@@ -1337,12 +1337,14 @@ def exif_transpose(
1337
1338 def build_flat_image_bool_length(
1339 image_grids: torch.LongTensor,
1340 - image_patch_id: int,
1341 - low_res_image_start_id: int,
1342 - image_start_id: int,
1343 - image_col_id: int,
1344 - image_end_id: int,
1340 + hf_config: PretrainedConfig,
1341 ) -> tuple[torch.LongTensor, torch.LongTensor]:
1342 + image_patch_id = hf_config.image_patch_id
1343 + low_res_image_start_id = hf_config.low_res_image_start_token_id
1344 + image_start_id = hf_config.image_start_token_id
1345 + image_col_id = hf_config.image_col_id
1346 + image_end_id = hf_config.image_end_token_id
1347 +
1348 device = image_grids.device
1349 B = image_grids.shape[0]
1350
@@ -1401,10 +1403,12 @@ def build_flat_image_bool_length(
1403
1404 def build_flat_video_bool_length(
1405 video_grids: torch.LongTensor,
1404 - image_patch_id: int,
1405 - frame_start_id: int,
1406 - frame_end_id: int,
1406 + hf_config: PretrainedConfig,
1407 ) -> tuple[torch.LongTensor, torch.LongTensor]:
1408 + image_patch_id = hf_config.image_patch_id
1409 + frame_start_id = hf_config.frame_start_token_id
1410 + frame_end_id = hf_config.frame_end_token_id
1411 +
1412 device = video_grids.device
1413 B = video_grids.shape[0]
1414
@@ -1439,314 +1443,6 @@ def build_flat_video_bool_length(
1443 return flat, lengths
1444
1445
1442 -class Molmo2ProcessorWrapper:
1443 - """
1444 - Wraps :class:`Molmo2Processor` so that it can be called directly.
1445 - """
1446 -
1447 - def __init__(self, processor: ProcessorMixin, hf_config: PretrainedConfig):
1448 - super().__init__()
1449 -
1450 - self.processor = processor
1451 - self.hf_config = hf_config
1452 -
1453 - @cached_property
1454 - def vocab(self) -> dict[str, int]:
1455 - return self.processor.tokenizer.vocab # type: ignore
1456 -
1457 - @cached_property
1458 - def max_crops(self) -> int:
1459 - image_processor = self.processor.image_processor # type: ignore
1460 -
1461 - max_crops = image_processor.max_crops
1462 - assert isinstance(max_crops, int)
1463 -
1464 - return max_crops
1465 -
1466 - @cached_property
1467 - def image_pooling_h(self) -> int:
1468 - image_processor = self.processor.image_processor # type: ignore
1469 -
1470 - image_pooling_h = image_processor.pooling_size[0]
1471 - assert isinstance(image_pooling_h, int)
1472 -
1473 - return image_pooling_h
1474 -
1475 - @cached_property
1476 - def image_pooling_w(self) -> int:
1477 - image_processor = self.processor.image_processor # type: ignore
1478 -
1479 - image_pooling_w = image_processor.pooling_size[1]
1480 - assert isinstance(image_pooling_w, int)
1481 -
1482 - return image_pooling_w
1483 -
1484 - @cached_property
1485 - def video_pooling_h(self) -> int:
1486 - video_processor = self.processor.video_processor # type: ignore
1487 -
1488 - video_pooling_h = video_processor.pooling_size[0]
1489 - assert isinstance(video_pooling_h, int)
1490 -
1491 - return video_pooling_h
1492 -
1493 - @cached_property
1494 - def video_pooling_w(self) -> int:
1495 - video_processor = self.processor.video_processor # type: ignore
1496 -
1497 - video_pooling_w = video_processor.pooling_size[1]
1498 - assert isinstance(video_pooling_w, int)
1499 -
1500 - return video_pooling_w
1501 -
1502 - @cached_property
1503 - def base_image_input_size(self) -> tuple[int, int]:
1504 - if getattr(self.processor, "image_processor", None) is not None:
1505 - processor = self.processor.image_processor # type: ignore
1506 - else:
1507 - processor = self.processor.video_processor # type: ignore
1508 -
1509 - base_image_input_size = (processor.size["height"], processor.size["width"])
1510 -
1511 - return base_image_input_size
1512 -
1513 - @cached_property
1514 - def image_patch_size(self) -> int:
1515 - if getattr(self.processor, "image_processor", None) is not None:
1516 - processor = self.processor.image_processor # type: ignore
1517 - else:
1518 - processor = self.processor.video_processor # type: ignore
1519 -
1520 - image_patch_size = processor.patch_size
1521 - assert isinstance(image_patch_size, int)
1522 -
1523 - return image_patch_size
1524 -
1525 - @cached_property
1526 - def overlap_margins(self) -> tuple[int, int]:
1527 - image_processor = self.processor.image_processor # type: ignore
1528 -
1529 - left_margin, right_margin = image_processor.overlap_margins
1530 - assert isinstance(left_margin, int)
1531 - assert isinstance(right_margin, int)
1532 -
1533 - return left_margin, right_margin
1534 -
1535 - @cached_property
1536 - def bos_token(self) -> str:
1537 - return self.processor.tokenizer.bos_token or self.processor.tokenizer.eos_token
1538 -
1539 - @cached_property
1540 - def image_patch_id(self) -> int:
1541 - return self.hf_config.image_patch_id
1542 -
1543 - @cached_property
1544 - def im_col_id(self) -> int:
1545 - return self.hf_config.image_col_id
1546 -
1547 - @cached_property
1548 - def im_start_id(self) -> int:
1549 - return self.hf_config.image_start_token_id
1550 -
1551 - @cached_property
1552 - def im_end_id(self) -> int:
1553 - return self.hf_config.image_end_token_id
1554 -
1555 - @cached_property
1556 - def low_res_im_start_id(self) -> int:
1557 - return self.hf_config.low_res_image_start_token_id
1558 -
1559 - @cached_property
1560 - def frame_start_id(self) -> int:
1561 - return self.hf_config.frame_start_token_id
1562 -
1563 - @cached_property
1564 - def frame_end_id(self) -> int:
1565 - return self.hf_config.frame_end_token_id
1566 -
1567 - @cached_property
1568 - def im_low_res_id(self) -> int:
1569 - return self.hf_config.image_low_res_id
1570 -
1571 - @cached_property
1572 - def image_placeholder_id(self) -> int:
1573 - return self.vocab[IMAGE_PROMPT]
1574 -
1575 - @cached_property
1576 - def video_placeholder_id(self) -> int:
1577 - return self.vocab[VIDEO_PROMPT]
1578 -
1579 - @cached_property
1580 - def image_token_ids(self) -> list[int]:
1581 - return [
1582 - self.image_patch_id,
1583 - self.im_col_id,
1584 - self.im_start_id,
1585 - self.low_res_im_start_id,
1586 - self.frame_start_id,
1587 - self.im_end_id,
1588 - self.frame_end_id,
1589 - self.im_low_res_id,
1590 - ]
1591 -
1592 - def select_tiling(
1593 - self,
1594 - *,
1595 - image_height: int,
1596 - image_width: int,
1597 - ) -> tuple[int, int]:
1598 - max_crops = self.max_crops
1599 - left_margin, right_margin = self.overlap_margins
1600 - base_image_input_size = self.base_image_input_size
1601 - base_image_input_d = self.image_patch_size
1602 -
1603 - total_margin_pixels = base_image_input_d * (right_margin + left_margin)
1604 - crop_patches = base_image_input_size[0] // base_image_input_d
1605 - crop_window_patches = crop_patches - (right_margin + left_margin)
1606 - crop_window_size = crop_window_patches * base_image_input_d
1607 - tiling_h, tiling_w = select_tiling(
1608 - height=image_height - total_margin_pixels,
1609 - width=image_width - total_margin_pixels,
1610 - patch_size=crop_window_size,
1611 - max_num_patches=max_crops,
1612 - )
1613 -
1614 - return tiling_h, tiling_w
1615 -
1616 - def get_base_grid_size(self, is_video: bool) -> tuple[int, int]:
1617 - base_image_input_size = self.base_image_input_size
1618 -
1619 - return get_patches_grid_size(
1620 - image_h=base_image_input_size[0],
1621 - image_w=base_image_input_size[1],
1622 - patch_size=self.image_patch_size,
1623 - pool_h=self.video_pooling_h if is_video else self.image_pooling_h,
1624 - pool_w=self.video_pooling_w if is_video else self.image_pooling_w,
1625 - )
1626 -
1627 - def get_patches_grid_size(
1628 - self,
1629 - *,
1630 - image_height: int,
1631 - image_width: int,
1632 - ) -> tuple[int, int]:
1633 - left_margin, right_margin = self.overlap_margins
1634 - base_image_input_size = self.base_image_input_size
1635 - base_image_input_d = self.image_patch_size
1636 -
1637 - total_margin_pixels = base_image_input_d * (right_margin + left_margin)
1638 - crop_patches = base_image_input_size[0] // base_image_input_d
1639 - crop_window_patches = crop_patches - (right_margin + left_margin)
1640 - crop_window_size = crop_window_patches * base_image_input_d
1641 -
1642 - tiling_h, tiling_w = self.select_tiling(
1643 - image_height=image_height,
1644 - image_width=image_width,
1645 - )
1646 -
1647 - h, w = [
1648 - tiling_h * crop_window_size + total_margin_pixels,
1649 - tiling_w * crop_window_size + total_margin_pixels,
1650 - ]
1651 - nrows, ncols = get_patches_grid_size(
1652 - image_h=h,
1653 - image_w=w,
1654 - patch_size=base_image_input_d,
1655 - pool_h=self.image_pooling_h,
1656 - pool_w=self.image_pooling_w,
1657 - )
1658 -
1659 - return nrows, ncols
1660 -
1661 - def __call__(
1662 - self,
1663 - text: TextInput | list[TextInput] | None = None,
1664 - images: ImageInput | None = None,
1665 - videos: VideoInput | None = None,
1666 - return_tensors: str | TensorType = None,
1667 - **kwargs: object,
1668 - ) -> BatchFeature:
1669 - inputs = [text]
1670 - images = exif_transpose(images)
1671 - if getattr(self.processor, "image_processor", None) is not None:
1672 - inputs.append(images)
1673 - if getattr(self.processor, "video_processor", None) is not None:
1674 - inputs.append(videos)
1675 - outputs = self.processor( # type: ignore
1676 - *inputs,
1677 - return_tensors=return_tensors,
1678 - **kwargs,
1679 - )
1680 -
1681 - # revert insert bos token
1682 - if outputs["input_ids"][0, 0] == self.vocab[self.bos_token]:
1683 - outputs["input_ids"] = outputs["input_ids"][:, 1:]
1684 -
1685 - if images is None:
1686 - images = []
1687 - if not isinstance(images, list):
1688 - images = [images]
1689 -
1690 - if videos is None:
1691 - videos = []
1692 - if not isinstance(videos, list):
1693 - videos = [videos]
1694 -
1695 - assert len(videos) in {0, 1}, "At most one video is supported for Molmo2"
1696 -
1697 - _attention_mask: torch.Tensor = outputs.pop("attention_mask")
1698 - _token_type_ids: torch.Tensor = outputs.pop("token_type_ids", None)
1699 -
1700 - if len(images) > 0:
1701 - # For each image: tiling_h * tiling_w + global view
1702 - num_crops = []
1703 - for image in images:
1704 - image_size = get_image_size(image)
1705 - tiling = self.select_tiling(
1706 - image_height=image_size.height,
1707 - image_width=image_size.width,
1708 - )
1709 - num_crops.append(np.prod(tiling) + 1)
1710 -
1711 - assert sum(num_crops) == len(outputs["pixel_values"])
1712 - assert sum(num_crops) == outputs["image_num_crops"].sum().item()
1713 - image_grids: torch.Tensor = outputs.pop("image_grids")
1714 - image_num_pooled_patches: torch.Tensor = image_grids[:, :2].prod(
1715 - dim=1
1716 - ) + image_grids[:, 2:].prod(dim=1)
1717 - outputs["image_num_pooled_patches"] = image_num_pooled_patches
1718 - n_patches = outputs["pixel_values"].shape[1]
1719 - outputs["image_num_patches"] = outputs["image_num_crops"] * n_patches
1720 - image_tokens, num_image_tokens = build_flat_image_bool_length(
1721 - image_grids,
1722 - self.image_patch_id,
1723 - self.low_res_im_start_id,
1724 - self.im_start_id,
1725 - self.im_col_id,
1726 - self.im_end_id,
1727 - )
1728 - outputs["image_tokens"] = image_tokens
1729 - outputs["num_image_tokens"] = num_image_tokens
1730 -
1731 - if len(videos) > 0:
1732 - video_grids: torch.Tensor = outputs.pop("video_grids")
1733 - assert video_grids[:, 0].sum() == len(outputs["pixel_values_videos"])
1734 - outputs["video_num_crops"] = video_grids[:, 0]
1735 - outputs["video_num_pooled_patches"] = video_grids.prod(dim=1)
1736 - n_patches = outputs["pixel_values_videos"].shape[1]
1737 - outputs["video_num_patches"] = outputs["video_num_crops"] * n_patches
1738 - video_tokens, num_video_tokens = build_flat_video_bool_length(
1739 - video_grids,
1740 - self.image_patch_id,
1741 - self.frame_start_id,
1742 - self.frame_end_id,
1743 - )
1744 - outputs["video_tokens"] = video_tokens
1745 - outputs["num_video_tokens"] = num_video_tokens
1746 -
1747 - return BatchFeature(outputs)
1748 -
1749 -
1446 def get_candidate_target_fps(
1447 video_fps: int | float,
1448 sampling_fps: int | float,
@@ -1856,36 +1552,101 @@ class Molmo2ProcessingInfo(BaseProcessingInfo):
1552 expected_hidden_size=self._get_expected_hidden_size(),
1553 )
1554
1859 - def get_hf_processor(self, **kwargs: object) -> Molmo2ProcessorWrapper:
1860 - processor = self.ctx.get_hf_processor(**kwargs)
1861 - hf_config = self.ctx.get_hf_config()
1862 - return Molmo2ProcessorWrapper(processor, hf_config)
1863 -
1555 def get_supported_mm_limits(self) -> Mapping[str, int | None]:
1556 return {"image": None, "video": 1}
1557
1558 + def select_tiling(
1559 + self,
1560 + *,
1561 + image_width: int,
1562 + image_height: int,
1563 + image_processor: BaseImageProcessor,
1564 + ) -> tuple[int, int]:
1565 + max_crops = image_processor.max_crops
1566 + left_margin, right_margin = image_processor.overlap_margins
1567 + base_image_input_d = image_processor.patch_size
1568 +
1569 + total_margin_pixels = base_image_input_d * (right_margin + left_margin)
1570 + crop_patches = image_processor.size["height"] // base_image_input_d
1571 + crop_window_patches = crop_patches - (right_margin + left_margin)
1572 + crop_window_size = crop_window_patches * base_image_input_d
1573 + tiling_h, tiling_w = select_tiling(
1574 + height=image_height - total_margin_pixels,
1575 + width=image_width - total_margin_pixels,
1576 + patch_size=crop_window_size,
1577 + max_num_patches=max_crops,
1578 + )
1579 +
1580 + return tiling_w, tiling_h
1581 +
1582 + def get_base_grid_size(
1583 + self,
1584 + image_processor: BaseImageProcessor | BaseVideoProcessor,
1585 + ) -> tuple[int, int]:
1586 + nrows, ncols = get_patches_grid_size(
1587 + image_h=image_processor.size["height"],
1588 + image_w=image_processor.size["width"],
1589 + patch_size=image_processor.patch_size,
1590 + pool_h=image_processor.pooling_size[0],
1591 + pool_w=image_processor.pooling_size[1],
1592 + )
1593 +
1594 + return ncols, nrows
1595 +
1596 + def get_patches_grid_size(
1597 + self,
1598 + *,
1599 + image_width: int,
1600 + image_height: int,
1601 + image_processor: BaseImageProcessor,
1602 + ) -> tuple[int, int]:
1603 + left_margin, right_margin = image_processor.overlap_margins
1604 + base_image_input_d = image_processor.patch_size
1605 +
1606 + total_margin_pixels = base_image_input_d * (right_margin + left_margin)
1607 + crop_patches = image_processor.size["height"] // base_image_input_d
1608 + crop_window_patches = crop_patches - (right_margin + left_margin)
1609 + crop_window_size = crop_window_patches * base_image_input_d
1610 +
1611 + tiling_w, tiling_h = self.select_tiling(
1612 + image_height=image_height,
1613 + image_width=image_width,
1614 + image_processor=image_processor,
1615 + )
1616 +
1617 + nrows, ncols = get_patches_grid_size(
1618 + image_h=tiling_h * crop_window_size + total_margin_pixels,
1619 + image_w=tiling_w * crop_window_size + total_margin_pixels,
1620 + patch_size=base_image_input_d,
1621 + pool_h=image_processor.pooling_size[0],
1622 + pool_w=image_processor.pooling_size[1],
1623 + )
1624 +
1625 + return ncols, nrows
1626 +
1627 def get_num_image_tokens(
1628 self,
1629 *,
1630 image_height: int,
1631 image_width: int,
1872 - processor: Molmo2ProcessorWrapper,
1632 + processor: ProcessorMixin,
1633 ) -> int:
1874 - hf_processor = processor.processor
1634 + image_processor = processor.image_processor
1635
1876 - resize_nrows, resize_cols = processor.get_base_grid_size(is_video=False)
1636 + resize_ncols, resize_nrows = self.get_base_grid_size(image_processor)
1637 # start/end tokens + image patch token + col tokens
1878 - if hf_processor.use_single_crop_col_tokens is not None:
1879 - use_col_tokens = hf_processor.use_single_crop_col_tokens
1638 + if processor.use_single_crop_col_tokens is not None:
1639 + use_col_tokens = processor.use_single_crop_col_tokens
1640 else:
1881 - use_col_tokens = hf_processor.image_use_col_tokens
1882 - extra = 2 + resize_nrows * (resize_cols + int(use_col_tokens))
1883 - overlap_nrows, overlap_ncols = processor.get_patches_grid_size(
1641 + use_col_tokens = processor.image_use_col_tokens
1642 + extra = 2 + resize_nrows * (resize_ncols + int(use_col_tokens))
1643 + overlap_ncols, overlap_nrows = self.get_patches_grid_size(
1644 image_height=image_height,
1645 image_width=image_width,
1646 + image_processor=image_processor,
1647 )
1648 joint = 2 + overlap_nrows * (
1888 - overlap_ncols + int(hf_processor.image_use_col_tokens)
1649 + overlap_ncols + int(processor.image_use_col_tokens)
1650 )
1651
1652 return extra + joint
@@ -1894,28 +1655,28 @@ class Molmo2ProcessingInfo(BaseProcessingInfo):
1655 self,
1656 *,
1657 num_frames: int,
1897 - processor: Molmo2ProcessorWrapper,
1658 + processor: ProcessorMixin,
1659 ) -> int:
1899 - resize_nrows, resize_cols = processor.get_base_grid_size(is_video=True)
1660 + video_processor = processor.video_processor
1661 +
1662 + resize_ncols, resize_nrows = self.get_base_grid_size(video_processor)
1663 # start/end tokens
1901 - extra = 2 + resize_nrows * (
1902 - resize_cols + int(processor.processor.video_use_col_tokens)
1903 - )
1664 + extra = 2 + resize_nrows * (resize_ncols + int(processor.video_use_col_tokens))
1665 return num_frames * extra
1666
1667 def get_image_size_with_most_features(self) -> ImageSize:
1668 processor = self.get_hf_processor()
1669 + image_processor = processor.image_processor
1670
1909 - left_margin, right_margin = processor.overlap_margins
1910 - base_image_input_size = processor.base_image_input_size
1911 - base_image_input_d = processor.image_patch_size
1671 + left_margin, right_margin = image_processor.overlap_margins
1672 + base_image_input_d = image_processor.patch_size
1673
1674 total_margin_pixels = base_image_input_d * (right_margin + left_margin)
1914 - crop_patches = base_image_input_size[0] // base_image_input_d
1675 + crop_patches = image_processor.size["height"] // base_image_input_d
1676 crop_window_patches = crop_patches - (right_margin + left_margin)
1677 crop_window_size = crop_window_patches * base_image_input_d
1678
1918 - tilings = get_candidate_tilings(processor.max_crops)
1679 + tilings = get_candidate_tilings(image_processor.max_crops)
1680 largest_feature_size, largest_feature_pinpoint = 0, None
1681
1682 for hr, wr in tilings:
@@ -1939,7 +1700,7 @@ class Molmo2ProcessingInfo(BaseProcessingInfo):
1700 def _get_max_video_frames(
1701 self,
1702 max_tokens: int,
1942 - processor: Molmo2ProcessorWrapper,
1703 + processor: ProcessorMixin,
1704 ) -> int:
1705 num_tokens_per_frame = self.get_num_video_tokens(
1706 num_frames=1,
@@ -1954,7 +1715,8 @@ class Molmo2ProcessingInfo(BaseProcessingInfo):
1715 mm_counts: Mapping[str, int],
1716 ) -> int:
1717 processor = self.get_hf_processor()
1957 - video_processor = processor.processor.video_processor
1718 + video_processor = processor.video_processor
1719 +
1720 num_frames = video_processor.num_frames
1721 max_videos = mm_counts.get("video", 0)
1722 max_total_frames = self._get_max_video_frames(seq_len, processor)
@@ -2030,7 +1792,9 @@ class Molmo2ProcessingInfo(BaseProcessingInfo):
1792 metadata: dict[str, Any],
1793 do_sample_frames: bool | None = None,
1794 ) -> list[float]:
2033 - video_processor = self.get_hf_processor().processor.video_processor
1795 + processor = self.get_hf_processor()
1796 + video_processor = processor.video_processor
1797 +
1798 # metadata["fps"] refers to the true fps of the input video.
1799 video_fps = metadata["fps"]
1800 frames_indices = metadata.get("frames_indices")
@@ -2104,7 +1868,7 @@ class Molmo2DummyInputsBuilder(BaseDummyInputsBuilder[Molmo2ProcessingInfo]):
1868
1869 if num_videos > 0:
1870 processor = self.info.get_hf_processor()
2107 - base_image_input_size = processor.base_image_input_size
1871 + video_size = processor.video_processor.size
1872 target_num_frames = self.info.get_num_frames_with_most_features(
1873 seq_len, mm_counts
1874 )
@@ -2131,8 +1895,8 @@ class Molmo2DummyInputsBuilder(BaseDummyInputsBuilder[Molmo2ProcessingInfo]):
1895 target_num_frames = min(target_num_frames, num_frames_override)
1896
1897 dummy_videos = self._get_dummy_videos(
2134 - width=base_image_input_size[1],
2135 - height=base_image_input_size[0],
1898 + width=video_size["width"],
1899 + height=video_size["height"],
1900 num_frames=target_num_frames,
1901 num_videos=num_videos,
1902 )
@@ -2174,10 +1938,10 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
1938 prompt_tokens: list[int],
1939 ) -> list[int]:
1940 processor = self.info.get_hf_processor()
2177 - tokenizer = processor.processor.tokenizer
1941 + tokenizer = processor.tokenizer
1942 bos_token_id = tokenizer.bos_token_id or tokenizer.eos_token_id
1943
2180 - if len(prompt_tokens) > 0 and prompt_tokens[0] != bos_token_id:
1944 + if len(prompt_tokens) == 0 or prompt_tokens[0] != bos_token_id:
1945 # Prepend the bos token to the prompt tokens
1946 prompt_tokens = [bos_token_id] + prompt_tokens
1947
@@ -2191,9 +1955,26 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
1955 tok_kwargs: Mapping[str, object],
1956 ) -> BatchFeature:
1957 mm_data = dict(mm_data)
2194 - processor = self.info.get_hf_processor(**mm_kwargs)
1958 +
1959 + hf_config = self.info.get_hf_config()
1960 + hf_processor = self.info.get_hf_processor(**mm_kwargs)
1961 +
1962 + def patched_call(text=None, images=None, videos=None, **kwargs) -> BatchFeature:
1963 + res = hf_processor(text=text, images=images, videos=videos, **kwargs)
1964 +
1965 + # Molmo2Processor.insert_bos results in float outputs
1966 + # if the input text is empty
1967 + if not text:
1968 + res["input_ids"] = res["input_ids"].long()
1969 +
1970 + return res
1971 +
1972 + tokenizer = hf_processor.tokenizer
1973 + image_processor = hf_processor.image_processor
1974
1975 if videos := mm_data.pop("videos", []):
1976 + bos_token_id = tokenizer.bos_token_id or tokenizer.eos_token_id
1977 +
1978 pixel_values_videos_lst = []
1979 video_token_pooling_lst = []
1980 video_num_crops_lst = []
@@ -2228,18 +2009,32 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2009 video_mm_data["videos"] = [[video_array]]
2010 video_mm_data["video_metadata"] = [[metadata]]
2011
2231 - video_outputs = super()._call_hf_processor(
2232 - prompt=VIDEO_PROMPT,
2233 - mm_data=video_mm_data,
2234 - mm_kwargs=video_mm_kwargs,
2235 - tok_kwargs=tok_kwargs,
2012 + video_outputs = self.info.ctx.call_hf_processor(
2013 + patched_call,
2014 + dict(text=VIDEO_PROMPT, **video_mm_data),
2015 + dict(**video_mm_kwargs, **tok_kwargs),
2016 )
2017 +
2018 input_ids = video_outputs.pop("input_ids")
2238 - video_string = processor.processor.tokenizer.batch_decode(input_ids)[0]
2239 - prompt = prompt.replace(
2240 - VIDEO_PROMPT,
2241 - video_string,
2242 - 1,
2019 + if input_ids[0, 0] == bos_token_id:
2020 + input_ids = input_ids[:, 1:]
2021 +
2022 + video_string = tokenizer.batch_decode(input_ids)[0]
2023 + prompt = prompt.replace(VIDEO_PROMPT, video_string, 1)
2024 +
2025 + video_grids = video_outputs.pop("video_grids")
2026 + assert video_grids[:, 0].sum() == len(
2027 + video_outputs["pixel_values_videos"]
2028 + )
2029 +
2030 + video_outputs["video_num_crops"] = video_grids[:, 0]
2031 + video_outputs["video_num_pooled_patches"] = video_grids.prod(dim=1)
2032 + n_patches = video_outputs["pixel_values_videos"].shape[1]
2033 + video_outputs["video_num_patches"] = (
2034 + video_outputs["video_num_crops"] * n_patches
2035 + )
2036 + (video_outputs["video_tokens"], video_outputs["num_video_tokens"]) = (
2037 + build_flat_video_bool_length(video_grids, hf_config)
2038 )
2039
2040 pixel_values_videos_lst.append(video_outputs["pixel_values_videos"])
@@ -2252,7 +2047,7 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2047 video_tokens_lst.append(video_outputs["video_tokens"])
2048 num_video_tokens_lst.append(video_outputs["num_video_tokens"])
2049
2255 - video_outputs = dict(
2050 + all_video_outputs = dict(
2051 pixel_values_videos=torch.cat(pixel_values_videos_lst),
2052 video_token_pooling=torch.cat(video_token_pooling_lst),
2053 video_num_crops=torch.cat(video_num_crops_lst),
@@ -2262,30 +2057,50 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2057 num_video_tokens=torch.cat(num_video_tokens_lst),
2058 )
2059 else:
2265 - video_outputs = dict()
2060 + all_video_outputs = dict()
2061
2267 - processed_outputs = super()._call_hf_processor(
2268 - prompt=prompt,
2269 - mm_data=mm_data,
2270 - mm_kwargs=mm_kwargs,
2271 - tok_kwargs=tok_kwargs,
2062 + processed_outputs = self.info.ctx.call_hf_processor(
2063 + patched_call,
2064 + dict(text=prompt, **mm_data),
2065 + dict(**mm_kwargs, **tok_kwargs),
2066 )
2067
2274 - bos_token_id = processor.vocab[processor.bos_token]
2275 - input_ids = processed_outputs["input_ids"]
2276 - # add bos token back to prompt start
2277 - if input_ids.numel() > 0 and input_ids[0, 0] != bos_token_id:
2278 - bos_token_id_tensor = torch.tensor(
2279 - [[bos_token_id]], device=input_ids.device, dtype=input_ids.dtype
2280 - )
2281 - processed_outputs["input_ids"] = torch.concat(
2282 - [bos_token_id_tensor, input_ids], dim=1
2068 + if (images := mm_data.get("images")) is not None:
2069 + mm_items = self.info.parse_mm_data({"image": images}, validate=False)
2070 + parsed_images = mm_items.get_items("image", ImageProcessorItems)
2071 + image_sizes = [
2072 + parsed_images.get_image_size(i) for i in range(len(parsed_images))
2073 + ]
2074 +
2075 + # For each image: tiling_h * tiling_w + global view
2076 + tilings = [
2077 + self.info.select_tiling(
2078 + image_width=image_size.width,
2079 + image_height=image_size.height,
2080 + image_processor=image_processor,
2081 + )
2082 + for image_size in image_sizes
2083 + ]
2084 + num_crops = torch.tensor(tilings).prod(-1) + 1
2085 + assert sum(num_crops) == len(processed_outputs["pixel_values"])
2086 + assert sum(num_crops) == processed_outputs["image_num_crops"].sum().item()
2087 +
2088 + image_grids = processed_outputs.pop("image_grids")
2089 + image_num_pooled_patches = image_grids[:, :2].prod(dim=1) + image_grids[
2090 + :, 2:
2091 + ].prod(dim=1)
2092 +
2093 + processed_outputs["image_num_pooled_patches"] = image_num_pooled_patches
2094 + n_patches = processed_outputs["pixel_values"].shape[1]
2095 + processed_outputs["image_num_patches"] = (
2096 + processed_outputs["image_num_crops"] * n_patches
2097 )
2284 - combined_outputs = dict(
2285 - processed_outputs,
2286 - **video_outputs,
2287 - )
2288 - return BatchFeature(combined_outputs)
2098 + (
2099 + processed_outputs["image_tokens"],
2100 + processed_outputs["num_image_tokens"],
2101 + ) = build_flat_image_bool_length(image_grids, hf_config)
2102 +
2103 + return BatchFeature({**processed_outputs, **all_video_outputs})
2104
2105 def _get_mm_fields_config(
2106 self,
@@ -2338,41 +2153,65 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2153 hf_processor_mm_kwargs: Mapping[str, object],
2154 out_mm_kwargs: MultiModalKwargsItems,
2155 ) -> Sequence[PromptUpdate]:
2156 + hf_config = self.info.get_hf_config()
2157 + img_patch_id = hf_config.image_patch_id
2158 + img_col_id = hf_config.image_col_id
2159 + img_start_id = hf_config.image_start_token_id
2160 + img_end_id = hf_config.image_end_token_id
2161 + low_res_im_start_id = hf_config.low_res_image_start_token_id
2162 + frame_start_id = hf_config.frame_start_token_id
2163 + frame_end_id = hf_config.frame_end_token_id
2164 + im_low_res_id = hf_config.image_low_res_id
2165 +
2166 + emb_tok_ids = [
2167 + img_patch_id,
2168 + img_col_id,
2169 + img_start_id,
2170 + low_res_im_start_id,
2171 + frame_start_id,
2172 + img_end_id,
2173 + frame_end_id,
2174 + im_low_res_id,
2175 + ]
2176 +
2177 processor = self.info.get_hf_processor(**hf_processor_mm_kwargs)
2342 - img_patch_id = processor.image_patch_id
2343 - img_col_id = processor.im_col_id
2344 - img_start_id = processor.im_start_id
2345 - img_end_id = processor.im_end_id
2346 - image_use_col_tokens = processor.processor.image_use_col_tokens
2347 - use_single_crop_col_tokens = processor.processor.use_single_crop_col_tokens
2348 - use_single_crop_start_token = processor.processor.use_single_crop_start_token
2349 - video_use_col_tokens = processor.processor.video_use_col_tokens
2350 - use_frame_special_tokens = processor.processor.use_frame_special_tokens
2351 -
2352 - def get_image_replacement_molmo2(item_idx: int) -> list[int]:
2178 + image_use_col_tokens = processor.image_use_col_tokens
2179 + use_single_crop_col_tokens = processor.use_single_crop_col_tokens
2180 + use_single_crop_start_token = processor.use_single_crop_start_token
2181 + video_use_col_tokens = processor.video_use_col_tokens
2182 + use_frame_special_tokens = processor.use_frame_special_tokens
2183 +
2184 + tokenizer = processor.tokenizer
2185 + vocab = tokenizer.get_vocab()
2186 +
2187 + image_processor = processor.image_processor
2188 + video_processor = processor.video_processor
2189 +
2190 + def get_image_replacement_molmo2(item_idx: int):
2191 images = mm_items.get_items("image", ImageProcessorItems)
2192 image = images.get(item_idx)
2193 image = exif_transpose(image)
2194
2357 - resize_nrows, resize_cols = processor.get_base_grid_size(is_video=False)
2195 + resize_ncols, resize_nrows = self.info.get_base_grid_size(image_processor)
2196 if use_single_crop_col_tokens is not None:
2197 use_col_tokens = use_single_crop_col_tokens
2198 else:
2199 use_col_tokens = image_use_col_tokens
2200 if use_single_crop_start_token:
2363 - start_id = processor.low_res_im_start_id
2201 + start_id = low_res_im_start_id
2202 else:
2203 start_id = img_start_id
2366 - extra_row = [img_patch_id] * resize_cols + [img_col_id] * int(
2204 + extra_row = [img_patch_id] * resize_ncols + [img_col_id] * int(
2205 use_col_tokens
2206 )
2207 extra_joint = [start_id] + extra_row * resize_nrows + [img_end_id]
2208
2209 image_size = get_image_size(image)
2210
2373 - nrows, ncols = processor.get_patches_grid_size(
2211 + ncols, nrows = self.info.get_patches_grid_size(
2212 image_height=image_size.height,
2213 image_width=image_size.width,
2214 + image_processor=image_processor,
2215 )
2216
2217 joint_row = [img_patch_id] * ncols + [img_col_id] * int(
@@ -2381,21 +2220,18 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2220 joint = [img_start_id] + joint_row * nrows + [img_end_id]
2221 img_token_ids = extra_joint + joint
2222
2384 - return PromptUpdateDetails.select_token_ids(
2385 - img_token_ids,
2386 - processor.image_token_ids,
2387 - )
2223 + return PromptUpdateDetails.select_token_ids(img_token_ids, emb_tok_ids)
2224
2389 - def get_video_replacement_molmo2(item_idx: int) -> list[int]:
2225 + def get_video_replacement_molmo2(item_idx: int):
2226 video, metadata = mm_items["video"][item_idx]
2227 do_sample_frames = hf_processor_mm_kwargs.get("do_sample_frames")
2228
2229 timestamps = self.info._get_video_second_idx(metadata, do_sample_frames)
2394 - nrows, ncols = processor.get_base_grid_size(is_video=True)
2230 + ncols, nrows = self.info.get_base_grid_size(video_processor)
2231
2232 if use_frame_special_tokens:
2397 - start_id = processor.frame_start_id
2398 - end_id = processor.frame_end_id
2233 + start_id = frame_start_id
2234 + end_id = frame_end_id
2235 else:
2236 start_id = img_start_id
2237 end_id = img_end_id
@@ -2408,7 +2244,7 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2244 prev_space + f"{frame_time:.1f} "
2245 ) # explicit whitespace before/after image tokens
2246
2411 - img_token_ids += processor.processor.tokenizer.encode(
2247 + img_token_ids += tokenizer.encode(
2248 frame_prefix,
2249 add_special_tokens=False,
2250 )
@@ -2419,10 +2255,7 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2255 joint = [start_id] + nrows * joint_row + [end_id]
2256 img_token_ids += joint
2257
2422 - return PromptUpdateDetails.select_token_ids(
2423 - img_token_ids,
2424 - processor.image_token_ids,
2425 - )
2258 + return PromptUpdateDetails.select_token_ids(img_token_ids, emb_tok_ids)
2259
2260 return [
2261 PromptReplacement(
@@ -2432,7 +2265,7 @@ class Molmo2MultiModalProcessor(BaseMultiModalProcessor[Molmo2ProcessingInfo]):
2265 )
2266 for modality, target, replacement_fn in zip(
2267 ["image", "video"],
2435 - [processor.image_placeholder_id, processor.video_placeholder_id],
2268 + [vocab[IMAGE_PROMPT], vocab[VIDEO_PROMPT]],
2269 [get_image_replacement_molmo2, get_video_replacement_molmo2],
2270 )
2271 ]