mlx-vlm 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mlx_vlm-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright © 2023 Apple Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ include ./requirements.txt
2
+ recursive-include mlx_vlm/ *.py
mlx_vlm-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,49 @@
1
+ Metadata-Version: 2.1
2
+ Name: mlx-vlm
3
+ Version: 0.0.1
4
+ Summary: Vision LLMs on Apple silicon with MLX and the Hugging Face Hub
5
+ Home-page: https://github.com/Blaizzy/mlx-vlm
6
+ Author: MLX Contributors
7
+ Author-email: prince.gdt@gmail.com
8
+ License: MIT
9
+ Requires-Python: >=3.8
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: mlx>=0.8.0
13
+ Requires-Dist: mlx-lm>=0.4.0
14
+ Requires-Dist: numpy
15
+ Requires-Dist: transformers
16
+ Requires-Dist: torch
17
+ Requires-Dist: huggingface_hub
18
+ Requires-Dist: Pillow
19
+ Requires-Dist: requests
20
+
21
+ # MLX-VLM
22
+
23
+ MLX-VLM a package for running Vision LLMs on your Mac using MLX.
24
+
25
+
26
+ ## Get started
27
+
28
+ The easiest way to get started is to install the `mlx-vlm` package:
29
+
30
+ **With `pip`**:
31
+
32
+ ```sh
33
+ pip install mlx-vlm
34
+ ```
35
+
36
+ **Inference**:
37
+
38
+ ```sh
39
+ python -m mlx_vlm.generate --model mlx-community/llava-1.5-7b-hf-4bit --max-tokens 10 --temp 0.0
40
+ ```
41
+
42
+ **Convert models**:
43
+
44
+ ```sh
45
+ pip -m mlx_vlm.convert
46
+ --hf-path llava-hf/llava-1.5-7b-hf\
47
+ -q \
48
+ --upload-repo mlx-community/llava-1.5-7b-hf-4bit
49
+ ```
@@ -0,0 +1,29 @@
1
+ # MLX-VLM
2
+
3
+ MLX-VLM a package for running Vision LLMs on your Mac using MLX.
4
+
5
+
6
+ ## Get started
7
+
8
+ The easiest way to get started is to install the `mlx-vlm` package:
9
+
10
+ **With `pip`**:
11
+
12
+ ```sh
13
+ pip install mlx-vlm
14
+ ```
15
+
16
+ **Inference**:
17
+
18
+ ```sh
19
+ python -m mlx_vlm.generate --model mlx-community/llava-1.5-7b-hf-4bit --max-tokens 10 --temp 0.0
20
+ ```
21
+
22
+ **Convert models**:
23
+
24
+ ```sh
25
+ pip -m mlx_vlm.convert
26
+ --hf-path llava-hf/llava-1.5-7b-hf\
27
+ -q \
28
+ --upload-repo mlx-community/llava-1.5-7b-hf-4bit
29
+ ```
@@ -0,0 +1,2 @@
1
+ from .utils import convert, generate, load
2
+ from .version import __version__
@@ -0,0 +1,62 @@
1
+ # Copyright © 2023-2024 Apple Inc.
2
+
3
+ import argparse
4
+
5
+ from .utils import convert
6
+
7
+
8
+ def configure_parser() -> argparse.ArgumentParser:
9
+ """
10
+ Configures and returns the argument parser for the script.
11
+
12
+ Returns:
13
+ argparse.ArgumentParser: Configured argument parser.
14
+ """
15
+ parser = argparse.ArgumentParser(
16
+ description="Convert Hugging Face model to MLX format"
17
+ )
18
+
19
+ parser.add_argument("--hf-path", type=str, help="Path to the Hugging Face model.")
20
+ parser.add_argument(
21
+ "--mlx-path", type=str, default="mlx_model", help="Path to save the MLX model."
22
+ )
23
+ parser.add_argument(
24
+ "-q", "--quantize", help="Generate a quantized model.", action="store_true"
25
+ )
26
+ parser.add_argument(
27
+ "--q-group-size", help="Group size for quantization.", type=int, default=64
28
+ )
29
+ parser.add_argument(
30
+ "--q-bits", help="Bits per weight for quantization.", type=int, default=4
31
+ )
32
+ parser.add_argument(
33
+ "--dtype",
34
+ help="Type to save the parameters, ignored if -q is given.",
35
+ type=str,
36
+ choices=["float16", "bfloat16", "float32"],
37
+ default="float16",
38
+ )
39
+ parser.add_argument(
40
+ "--upload-repo",
41
+ help="The Hugging Face repo to upload the model to.",
42
+ type=str,
43
+ default=None,
44
+ )
45
+ parser.add_argument(
46
+ "-d",
47
+ "--dequantize",
48
+ help="Dequantize a quantized model.",
49
+ action="store_true",
50
+ default=False,
51
+ )
52
+ return parser
53
+
54
+
55
+ def main():
56
+ parser = configure_parser()
57
+ args = parser.parse_args()
58
+ convert(**vars(args))
59
+
60
+
61
+ if __name__ == "__main__":
62
+ main()
@@ -0,0 +1,93 @@
1
+ import argparse
2
+ import codecs
3
+
4
+ import mlx.core as mx
5
+ from .utils import load, load_config, load_image_processor, generate, get_model_path
6
+
7
+
8
+ MODEL_TYPE = ""
9
+ def parse_arguments():
10
+ parser = argparse.ArgumentParser(
11
+ description="Generate text from an image using a model."
12
+ )
13
+ parser.add_argument(
14
+ "--model",
15
+ type=str,
16
+ default="qnguyen3/nanoLLaVA",
17
+ help="The path to the local model directory or Hugging Face repo.",
18
+ )
19
+ parser.add_argument(
20
+ "--image",
21
+ type=str,
22
+ default="http://images.cocodataset.org/val2017/000000039769.jpg",
23
+ help="URL or path of the image to process.",
24
+ )
25
+ parser.add_argument(
26
+ "--prompt",
27
+ type=str,
28
+ default="What are these?",
29
+ help="Message to be processed by the model.",
30
+ )
31
+ parser.add_argument(
32
+ "--max-tokens",
33
+ type=int,
34
+ default=100,
35
+ help="Maximum number of tokens to generate.",
36
+ )
37
+ parser.add_argument(
38
+ "--temp", type=float, default=0.3, help="Temperature for sampling."
39
+ )
40
+ return parser.parse_args()
41
+
42
+
43
+ def get_model_and_processors(model_path):
44
+ model_path = get_model_path(model_path)
45
+ model, processor = load(model_path, {"trust_remote_code": True})
46
+ config = load_config(model_path)
47
+ image_processor = load_image_processor(config)
48
+ return model, processor, image_processor
49
+
50
+
51
+ def sample(logits, temperature=0.0):
52
+ if temperature == 0:
53
+ return mx.argmax(logits, axis=-1)
54
+ else:
55
+ return mx.random.categorical(logits * (1 / temperature))
56
+
57
+
58
+
59
+ def main():
60
+ args = parse_arguments()
61
+ model, processor, image_processor = get_model_and_processors(args.model)
62
+
63
+ prompt = codecs.decode(args.prompt, "unicode_escape")
64
+
65
+ if "chat_template" in processor.__dict__.keys():
66
+ prompt = processor.apply_chat_template(
67
+ [{"role": "user", "content": f"<image>\n{prompt}"}],
68
+ tokenize=False,
69
+ add_generation_prompt=True,
70
+ )
71
+ elif "tokenizer" in processor.__dict__.keys():
72
+ prompt = processor.tokenizer.apply_chat_template(
73
+ [{"role": "user", "content": f"<image>\n{prompt}"}],
74
+ tokenize=False,
75
+ add_generation_prompt=True,
76
+ )
77
+ else:
78
+ ValueError("Error: processor does not have 'chat_template' or 'tokenizer' attribute.")
79
+
80
+ generate(
81
+ model,
82
+ processor,
83
+ args.image,
84
+ prompt,
85
+ image_processor,
86
+ args.temp,
87
+ args.max_tokens,
88
+ True
89
+ )
90
+
91
+
92
+ if __name__ == "__main__":
93
+ main()
File without changes
@@ -0,0 +1,32 @@
1
+
2
+ from abc import ABC, abstractmethod
3
+ from typing import Dict
4
+
5
+ from transformers.image_processing_utils import get_size_dict
6
+ from transformers.image_utils import (ChannelDimension, PILImageResampling )
7
+
8
+
9
+ class BaseImageProcessor(ABC):
10
+ def __init__(self,
11
+ image_mean=(0.5, 0.5, 0.5),
12
+ image_std=(0.5, 0.5, 0.5),
13
+ size=(384, 384),
14
+ crop_size: Dict[str, int] = None,
15
+ resample=PILImageResampling.BICUBIC,
16
+ rescale_factor=1 / 255,
17
+ data_format=ChannelDimension.FIRST):
18
+ crop_size = crop_size if crop_size is not None else {"height": 384, "width": 384}
19
+ crop_size = get_size_dict(crop_size, default_to_square=True, param_name="crop_size")
20
+
21
+ self.image_mean = image_mean
22
+ self.image_std = image_std
23
+ self.size = size
24
+ self.resample = resample
25
+ self.rescale_factor = rescale_factor
26
+ self.data_format = data_format
27
+ self.crop_size = crop_size
28
+
29
+ @abstractmethod
30
+ def preprocess(self, images):
31
+ pass
32
+
@@ -0,0 +1 @@
1
+ from .llava import Model, ModelConfig, VisionConfig, TextConfig, VisionModel, LanguageModel
@@ -0,0 +1,211 @@
1
+
2
+ import inspect
3
+ from dataclasses import dataclass
4
+ from typing import Dict, Optional, Tuple, Union
5
+
6
+ import mlx.core as mx
7
+ import mlx.nn as nn
8
+
9
+
10
+ @dataclass
11
+ class TextConfig:
12
+ model_type: str
13
+ hidden_size: int = 4096
14
+ num_hidden_layers: int = 32
15
+ intermediate_size: int = 11008
16
+ num_attention_heads: int = 32
17
+ rms_norm_eps: float = 1e-6
18
+ vocab_size: int = 32000
19
+ num_key_value_heads: int = None
20
+ rope_theta: float = 10000
21
+ rope_traditional: bool = False
22
+ rope_scaling: Optional[Dict[str, Union[float, str]]] = None
23
+
24
+ @classmethod
25
+ def from_dict(cls, params):
26
+ return cls(
27
+ **{
28
+ k: v
29
+ for k, v in params.items()
30
+ if k in inspect.signature(cls).parameters
31
+ }
32
+ )
33
+
34
+ def __post_init__(self):
35
+ if self.num_key_value_heads is None:
36
+ self.num_key_value_heads = self.num_attention_heads
37
+
38
+ if self.rope_scaling:
39
+ required_keys = {"factor", "type"}
40
+ if not all(key in self.rope_scaling for key in required_keys):
41
+ raise ValueError(f"rope_scaling must contain keys {required_keys}")
42
+
43
+ if self.rope_scaling["type"] != "linear":
44
+ raise ValueError("rope_scaling 'type' currently only supports 'linear'")
45
+
46
+
47
+ class Attention(nn.Module):
48
+ def __init__(self, config: TextConfig):
49
+ super().__init__()
50
+
51
+ dim = config.hidden_size
52
+ self.n_heads = n_heads = config.num_attention_heads
53
+ self.n_kv_heads = n_kv_heads = config.num_key_value_heads
54
+
55
+ self.repeats = n_heads // n_kv_heads
56
+
57
+ head_dim = config.hidden_size // n_heads
58
+ self.scale = head_dim**-0.5
59
+
60
+ self.q_proj = nn.Linear(dim, n_heads * head_dim, bias=False)
61
+ self.k_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False)
62
+ self.v_proj = nn.Linear(dim, n_kv_heads * head_dim, bias=False)
63
+ self.o_proj = nn.Linear(n_heads * head_dim, dim, bias=False)
64
+
65
+ rope_scale = (
66
+ 1 / config.rope_scaling["factor"]
67
+ if config.rope_scaling is not None
68
+ and config.rope_scaling["type"] == "linear"
69
+ else 1
70
+ )
71
+ self.rope = nn.RoPE(
72
+ head_dim,
73
+ traditional=config.rope_traditional,
74
+ base=config.rope_theta,
75
+ scale=rope_scale,
76
+ )
77
+
78
+ def __call__(
79
+ self,
80
+ x: mx.array,
81
+ mask: Optional[mx.array] = None,
82
+ cache: Optional[Tuple[mx.array, mx.array]] = None,
83
+ ) -> mx.array:
84
+ B, L, D = x.shape
85
+
86
+ queries, keys, values = self.q_proj(x), self.k_proj(x), self.v_proj(x)
87
+
88
+ # Prepare the queries, keys and values for the attention computation
89
+ queries = queries.reshape(B, L, self.n_heads, -1).transpose(0, 2, 1, 3)
90
+ keys = keys.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3)
91
+ values = values.reshape(B, L, self.n_kv_heads, -1).transpose(0, 2, 1, 3)
92
+
93
+ if cache is not None:
94
+ key_cache, value_cache = cache
95
+ queries = self.rope(queries, offset=key_cache.shape[2])
96
+ keys = self.rope(keys, offset=key_cache.shape[2])
97
+ keys = mx.concatenate([key_cache, keys], axis=2)
98
+ values = mx.concatenate([value_cache, values], axis=2)
99
+ else:
100
+ queries = self.rope(queries)
101
+ keys = self.rope(keys)
102
+
103
+ output = mx.fast.scaled_dot_product_attention(
104
+ queries, keys, values, scale=self.scale, mask=mask
105
+ )
106
+ output = output.transpose(0, 2, 1, 3).reshape(B, L, -1)
107
+ return self.o_proj(output), (keys, values)
108
+
109
+
110
+ class MLP(nn.Module):
111
+ def __init__(self, dim, hidden_dim):
112
+ super().__init__()
113
+ self.gate_proj = nn.Linear(dim, hidden_dim, bias=False)
114
+ self.down_proj = nn.Linear(hidden_dim, dim, bias=False)
115
+ self.up_proj = nn.Linear(dim, hidden_dim, bias=False)
116
+
117
+ def __call__(self, x) -> mx.array:
118
+ return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x))
119
+
120
+
121
+ class TransformerBlock(nn.Module):
122
+ def __init__(self, config: TextConfig):
123
+ super().__init__()
124
+ self.num_attention_heads = config.num_attention_heads
125
+ self.hidden_size = config.hidden_size
126
+ self.self_attn = Attention(config)
127
+ self.mlp = MLP(config.hidden_size, config.intermediate_size)
128
+ self.input_layernorm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
129
+ self.post_attention_layernorm = nn.RMSNorm(
130
+ config.hidden_size, eps=config.rms_norm_eps
131
+ )
132
+ self.config = config
133
+
134
+ def __call__(
135
+ self,
136
+ x: mx.array,
137
+ mask: Optional[mx.array] = None,
138
+ cache: Optional[Tuple[mx.array, mx.array]] = None,
139
+ ) -> mx.array:
140
+ r, cache = self.self_attn(self.input_layernorm(x), mask, cache)
141
+ h = x + r
142
+ r = self.mlp(self.post_attention_layernorm(h))
143
+ out = h + r
144
+ return out, cache
145
+
146
+
147
+ class Llama(nn.Module):
148
+ def __init__(self, config: TextConfig):
149
+ super().__init__()
150
+ self.config = config
151
+ self.vocab_size = config.vocab_size
152
+ self.num_hidden_layers = config.num_hidden_layers
153
+ assert self.vocab_size > 0
154
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
155
+ self.layers = [
156
+ TransformerBlock(config=config) for _ in range(config.num_hidden_layers)
157
+ ]
158
+ self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
159
+
160
+ def __call__(
161
+ self,
162
+ inputs: mx.array,
163
+ cache=None,
164
+ inputs_embeds=None,
165
+ ):
166
+ # for passing merged input embeddings
167
+ if inputs_embeds is None:
168
+ h = self.embed_tokens(inputs)
169
+ else:
170
+ h = inputs_embeds
171
+
172
+ mask = None
173
+ if h.shape[1] > 1:
174
+ mask = nn.MultiHeadAttention.create_additive_causal_mask(h.shape[1])
175
+ mask = mask.astype(h.dtype)
176
+
177
+ if cache is None:
178
+ cache = [None] * len(self.layers)
179
+
180
+ for e, layer in enumerate(self.layers):
181
+ h, cache[e] = layer(h, mask, cache[e])
182
+
183
+ return self.norm(h), cache
184
+
185
+
186
+ class LanguageModel(nn.Module):
187
+ def __init__(self, config: TextConfig):
188
+ super().__init__()
189
+ self.model_type = config.model_type
190
+ if self.model_type != "llama":
191
+ raise ValueError(
192
+ f"Model type {self.model_type} not supported. Currently only 'llama' is supported"
193
+ )
194
+ self.model = Llama(config)
195
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
196
+
197
+ def __call__(
198
+ self,
199
+ inputs: mx.array,
200
+ cache=None,
201
+ inputs_embeds=None,
202
+ ):
203
+ out, cache = self.model(inputs, cache, inputs_embeds)
204
+ return self.lm_head(out), cache
205
+
206
+ @staticmethod
207
+ def sanitize(weights):
208
+ # Remove unused precomputed rotary freqs
209
+ return {
210
+ k: v for k, v in weights.items() if "self_attn.rotary_emb.inv_freq" not in k
211
+ }
@@ -0,0 +1,179 @@
1
+
2
+
3
+ import glob
4
+ import inspect
5
+ import json
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import mlx.core as mx
11
+ import mlx.nn as nn
12
+ import numpy as np
13
+ from huggingface_hub import snapshot_download
14
+ from .language import LanguageModel, TextConfig
15
+ from .vision import VisionConfig, VisionModel
16
+
17
+
18
+ @dataclass
19
+ class ModelConfig:
20
+ text_config: TextConfig
21
+ vision_config: VisionConfig
22
+ ignore_index: int = -100
23
+ image_token_index: int = 32000
24
+ vision_feature_select_strategy: str = "default"
25
+ vision_feature_layer: int = -2
26
+ vocab_size: int = 32000
27
+
28
+ @classmethod
29
+ def from_dict(cls, params):
30
+ return cls(
31
+ **{
32
+ k: v
33
+ for k, v in params.items()
34
+ if k in inspect.signature(cls).parameters
35
+ }
36
+ )
37
+
38
+
39
+ class LlavaMultiModalProjector(nn.Module):
40
+ def __init__(self, config: ModelConfig):
41
+ super().__init__()
42
+ self.linear_1 = nn.Linear(
43
+ config.vision_config.hidden_size, config.text_config.hidden_size, bias=True
44
+ )
45
+ self.gelu = nn.GELU()
46
+ self.linear_2 = nn.Linear(
47
+ config.text_config.hidden_size, config.text_config.hidden_size, bias=True
48
+ )
49
+
50
+ def __call__(self, x: mx.array) -> mx.array:
51
+ x = self.linear_1(x)
52
+ x = self.gelu(x)
53
+ x = self.linear_2(x)
54
+ return x
55
+
56
+
57
+ class Model(nn.Module):
58
+ def __init__(self, config: ModelConfig):
59
+ self.config = config
60
+ self.vision_tower = VisionModel(config.vision_config)
61
+ self.language_model = LanguageModel(config.text_config)
62
+ self.multi_modal_projector = LlavaMultiModalProjector(config)
63
+ self.vision_feature_layer = config.vision_feature_layer
64
+ self.vision_feature_select_strategy = config.vision_feature_select_strategy
65
+
66
+ def get_input_embeddings(
67
+ self,
68
+ input_ids: Optional[mx.array] = None,
69
+ pixel_values: Optional[mx.array] = None,
70
+ ):
71
+ if pixel_values is None:
72
+ return self.language_model(input_ids)
73
+
74
+ # Get the input embeddings from the language model
75
+ inputs_embeds = self.language_model.model.embed_tokens(input_ids)
76
+
77
+ # Get the ouptut hidden states from the vision model
78
+ *_, hidden_states = self.vision_tower(
79
+ pixel_values.transpose(0, 2, 3, 1), output_hidden_states=True
80
+ )
81
+
82
+ # Select the hidden states from the desired layer
83
+ selected_image_feature = hidden_states[self.vision_feature_layer]
84
+
85
+ if self.vision_feature_select_strategy == "default":
86
+ selected_image_feature = selected_image_feature[:, 1:]
87
+ elif self.vision_feature_select_strategy == "full":
88
+ selected_image_feature = selected_image_feature
89
+ else:
90
+ raise ValueError(
91
+ "Unexpected feature selection strategy: "
92
+ f"{self.vision_feature_select_strategy}"
93
+ )
94
+
95
+ # Pass image features through the multi-modal projector
96
+ image_features = self.multi_modal_projector(selected_image_feature)
97
+
98
+ # Insert special image tokens in the input_ids
99
+ final_inputs_embeds = self._merge_input_ids_with_image_features(
100
+ image_features, inputs_embeds, input_ids
101
+ )
102
+ return final_inputs_embeds
103
+
104
+ def _merge_input_ids_with_image_features(
105
+ self, image_features, inputs_embeds, input_ids
106
+ ):
107
+ image_token_index = self.config.image_token_index
108
+ num_images, num_image_patches, embed_dim = image_features.shape
109
+
110
+ # Positions of <image> tokens in input_ids, assuming batch size is 1
111
+ image_positions = np.where(input_ids[0] == image_token_index)[0].tolist()
112
+
113
+ if len(image_positions) != num_images:
114
+ raise ValueError(
115
+ f"The number of image tokens ({len(image_positions)}) does not "
116
+ f" match the number of image inputs ({num_images})."
117
+ )
118
+
119
+ text_segments = []
120
+ start_idx = 0
121
+
122
+ for position in image_positions:
123
+ text_segments.append(inputs_embeds[:, start_idx:position])
124
+ start_idx = position + 1
125
+
126
+ image_embeddings = mx.split(image_features, image_features.shape[0])
127
+ final_embeddings = [v for p in zip(text_segments, image_embeddings) for v in p]
128
+ final_embeddings += [inputs_embeds[:, start_idx:]]
129
+
130
+ # Create a final embedding of shape
131
+ # (1, num_image_patches*num_images + sequence_len, embed_dim)
132
+ return mx.concatenate(final_embeddings, axis=1)
133
+
134
+ def __call__(self, input_ids: mx.array, pixel_values: mx.array, cache=None):
135
+ input_embddings = self.get_input_embeddings(input_ids, pixel_values)
136
+ logits, cache = self.language_model(
137
+ input_ids, cache=cache, inputs_embeds=input_embddings
138
+ )
139
+ return logits, cache
140
+
141
+ @staticmethod
142
+ def from_pretrained(path_or_hf_repo: str):
143
+ path = Path(path_or_hf_repo)
144
+ if not path.exists():
145
+ path = Path(
146
+ snapshot_download(
147
+ repo_id=path_or_hf_repo,
148
+ allow_patterns=[
149
+ "*.json",
150
+ "*.safetensors",
151
+ "*.py",
152
+ "tokenizer.model",
153
+ "*.tiktoken",
154
+ ],
155
+ )
156
+ )
157
+
158
+ with open(path / "config.json", "r") as f:
159
+ model_config = json.load(f)
160
+
161
+ model_config = ModelConfig.from_dict(model_config)
162
+
163
+ model_config.vision_config = VisionConfig.from_dict(model_config.vision_config)
164
+ model_config.text_config = TextConfig.from_dict(model_config.text_config)
165
+
166
+ model = Model(model_config)
167
+ weight_files = glob.glob(str(path / "*.safetensors"))
168
+ if not weight_files:
169
+ raise FileNotFoundError(f"No safetensors found in {path}")
170
+
171
+ weights = {}
172
+ for wf in weight_files:
173
+ weights.update(mx.load(wf))
174
+
175
+ weights = VisionModel.sanitize(weights)
176
+ weights = LanguageModel.sanitize(weights)
177
+
178
+ model.load_weights(list(weights.items()))
179
+ return model