moondream 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.
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.1
2
+ Name: moondream
3
+ Version: 0.0.1
4
+ Summary: Python client library for moondream
5
+ Author: vik
6
+ Author-email: vik@moondream.ai
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Requires-Dist: numpy (>=2.1.2,<3.0.0)
14
+ Requires-Dist: onnx (>=1.17.0,<2.0.0)
15
+ Requires-Dist: onnxruntime (>=1.19.2,<2.0.0)
16
+ Requires-Dist: pillow (>=10.4.0,<11.0.0)
17
+ Requires-Dist: tokenizers (>=0.20.1,<0.21.0)
18
+ Description-Content-Type: text/markdown
19
+
20
+ # Moondream Python Client Library
21
+
22
+ Python client library for moondream. This library is an alpha preview -- it is
23
+ in an early stage of development, and backward compatibility is not yet
24
+ guaranteed. If you are using this in production, please pin the revision you
25
+ are using.
26
+
27
+ This library currently offers optimized CPU inference, but will be slower than
28
+ the PyTorch implementation for CUDA and MPS backends. If you are running on a
29
+ Mac with M1/M2/M3 etc. chips, or if you have a GPU available, this library is
30
+ not recommended yet.
31
+
32
+ ## Setup
33
+
34
+ Install the library using pip:
35
+
36
+ ```
37
+ pip install moondream==0.0.1
38
+ ```
39
+
40
+ Then download the model weights:
41
+
42
+ ```
43
+ # int8 weights (recommended):
44
+ wget "https://huggingface.co/vikhyatk/moondream2/resolve/client/moondream-latest-int8.bin.gz?download=true" -O - | gunzip > moondream-latest-int8.bin
45
+ # ...or, for FP16 weights:
46
+ wget "https://huggingface.co/vikhyatk/moondream2/resolve/client/moondream-latest-f16.bin.gz?download=true" -O - | gunzip > moondream-latest-f16.bin
47
+ ```
48
+
49
+ ## Usage
50
+
51
+ ```python
52
+ import moondream as md
53
+ from PIL import Image
54
+
55
+ model = md.VL("moondream-latest-int8.bin")
56
+ image = Image.open("path/to/image.jpg")
57
+
58
+ # Optional -- encode the image to efficiently run multiple queries on the same
59
+ # image. This is not mandatory, since the model will automatically encode the
60
+ # image if it is not already encoded.
61
+ encoded_image = model.encode_image(image)
62
+
63
+ # Caption the image.
64
+ caption = model.caption(encoded_image)
65
+
66
+ # ...or, if you want to stream the output:
67
+ for t in model.caption(encoded_image, stream=True)["caption"]:
68
+ print(t, end="", flush=True)
69
+
70
+ # Ask a question about the image.
71
+ question = "How many people are in this image?"
72
+ answer = model.answer_question(encoded_image, question)["answer"]
73
+
74
+ # ...or again, if you want to stream the output:
75
+ for t in model.answer_question(encoded_image, question, stream=True)["answer"]:
76
+ print(t, end="", flush=True)
77
+ ```
78
+
79
+ ### Accelerators
80
+
81
+ (TK -- document how ONNX execution providers work.)
82
+
@@ -0,0 +1,62 @@
1
+ # Moondream Python Client Library
2
+
3
+ Python client library for moondream. This library is an alpha preview -- it is
4
+ in an early stage of development, and backward compatibility is not yet
5
+ guaranteed. If you are using this in production, please pin the revision you
6
+ are using.
7
+
8
+ This library currently offers optimized CPU inference, but will be slower than
9
+ the PyTorch implementation for CUDA and MPS backends. If you are running on a
10
+ Mac with M1/M2/M3 etc. chips, or if you have a GPU available, this library is
11
+ not recommended yet.
12
+
13
+ ## Setup
14
+
15
+ Install the library using pip:
16
+
17
+ ```
18
+ pip install moondream==0.0.1
19
+ ```
20
+
21
+ Then download the model weights:
22
+
23
+ ```
24
+ # int8 weights (recommended):
25
+ wget "https://huggingface.co/vikhyatk/moondream2/resolve/client/moondream-latest-int8.bin.gz?download=true" -O - | gunzip > moondream-latest-int8.bin
26
+ # ...or, for FP16 weights:
27
+ wget "https://huggingface.co/vikhyatk/moondream2/resolve/client/moondream-latest-f16.bin.gz?download=true" -O - | gunzip > moondream-latest-f16.bin
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ```python
33
+ import moondream as md
34
+ from PIL import Image
35
+
36
+ model = md.VL("moondream-latest-int8.bin")
37
+ image = Image.open("path/to/image.jpg")
38
+
39
+ # Optional -- encode the image to efficiently run multiple queries on the same
40
+ # image. This is not mandatory, since the model will automatically encode the
41
+ # image if it is not already encoded.
42
+ encoded_image = model.encode_image(image)
43
+
44
+ # Caption the image.
45
+ caption = model.caption(encoded_image)
46
+
47
+ # ...or, if you want to stream the output:
48
+ for t in model.caption(encoded_image, stream=True)["caption"]:
49
+ print(t, end="", flush=True)
50
+
51
+ # Ask a question about the image.
52
+ question = "How many people are in this image?"
53
+ answer = model.answer_question(encoded_image, question)["answer"]
54
+
55
+ # ...or again, if you want to stream the output:
56
+ for t in model.answer_question(encoded_image, question, stream=True)["answer"]:
57
+ print(t, end="", flush=True)
58
+ ```
59
+
60
+ ### Accelerators
61
+
62
+ (TK -- document how ONNX execution providers work.)
@@ -0,0 +1 @@
1
+ from .vl import VL
@@ -0,0 +1,62 @@
1
+ import numpy as np
2
+ from typing import List, Tuple, Union
3
+ from PIL import Image
4
+
5
+
6
+ def im_resize(
7
+ image: Image.Image,
8
+ size: Tuple[int, int],
9
+ resample: int = Image.Resampling.BICUBIC,
10
+ ) -> Image.Image:
11
+ return image.resize(size, resample=resample)
12
+
13
+
14
+ def normalize(
15
+ image: np.ndarray,
16
+ mean: List[float] = [0.5, 0.5, 0.5],
17
+ std: List[float] = [0.5, 0.5, 0.5],
18
+ ) -> np.ndarray:
19
+ """
20
+ Normalize an image array.
21
+ """
22
+ return (image - np.array(mean)) / np.array(std)
23
+
24
+
25
+ def create_patches(image: Image.Image, image_patch_size=378) -> np.ndarray:
26
+ """
27
+ Split the given image into a variable number of patches depending upon its
28
+ resolution.
29
+ """
30
+ # Start off with the global patch.
31
+ patches = [im_resize(image, (image_patch_size, image_patch_size))]
32
+
33
+ # Find the closest resolution template.
34
+ res_templates = [(1, 2), (2, 1), (2, 2)]
35
+ im_width, im_height = image.size
36
+ max_dim = max(im_width, im_height)
37
+ if max_dim < image_patch_size * 1.4:
38
+ # If the image is already small, we just do a single patch that is a
39
+ # duplicate of the global patch. This creates a small amount of
40
+ # redundant computation now, but it is simpler and future-proofs us
41
+ # if/when we condition the vision encoder on the patch type.
42
+ patches.append(patches[0])
43
+ else:
44
+ aspect_ratio = im_width / im_height
45
+ res_template = min(
46
+ res_templates, key=lambda size: abs((size[1] / size[0]) - aspect_ratio)
47
+ )
48
+ # TODO: Actually implement patching... just going to put in the global
49
+ # patch for now to make progress on other aspects.
50
+ patches.append(patches[0])
51
+
52
+ return np.stack(
53
+ [
54
+ normalize(
55
+ (np.array(patch_img) / 255.0),
56
+ mean=[0.5, 0.5, 0.5],
57
+ std=[0.5, 0.5, 0.5],
58
+ ).transpose(2, 0, 1)
59
+ for patch_img in patches
60
+ ],
61
+ dtype=np.float16,
62
+ )
@@ -0,0 +1,271 @@
1
+ import onnx
2
+ import onnxruntime as ort
3
+ import numpy as np
4
+ import os
5
+ import tarfile
6
+ import json
7
+
8
+ from typing import Generator, List, Union, Optional, Dict, TypedDict, Any
9
+ from PIL import Image
10
+ from dataclasses import dataclass
11
+ from io import BytesIO
12
+ from tokenizers import Tokenizer
13
+
14
+ from .preprocess import create_patches
15
+
16
+
17
+ @dataclass
18
+ class EncodedImage:
19
+ kv_caches: List[np.ndarray]
20
+
21
+
22
+ SamplingSettings = TypedDict(
23
+ "SamplingSettings",
24
+ {"max_tokens": int},
25
+ total=False,
26
+ )
27
+
28
+ CaptionOutput = TypedDict(
29
+ "CaptionOutput", {"caption": Union[str, Generator[str, None, None]]}
30
+ )
31
+ QueryOutput = TypedDict(
32
+ "QueryOutput", {"answer": Union[str, Generator[str, None, None]]}
33
+ )
34
+
35
+ DEFAULT_MAX_TOKENS = 1024
36
+ LATEST_SUPPORTED_VERSION = 0
37
+
38
+
39
+ class Region:
40
+ pass
41
+
42
+
43
+ class VL:
44
+ def __init__(self, model_path: Optional[str], ort_settings: Dict[str, Any] = {}):
45
+ """
46
+ Initialize the Moondream VL (Vision Language) model.
47
+
48
+ Args:
49
+ model_path (str): The path to the model file.
50
+
51
+ Returns:
52
+ None
53
+ """
54
+
55
+ if model_path is None or not os.path.isfile(model_path):
56
+ raise ValueError("Model path is invalid or file does not exist.")
57
+
58
+ if not tarfile.is_tarfile(model_path):
59
+ raise ValueError(
60
+ "Model format not recognized. You may need to upgrade the moondream package."
61
+ )
62
+
63
+ self.text_decoders = []
64
+
65
+ with tarfile.open(model_path, "r:*") as tar:
66
+ for member in tar.getmembers():
67
+ name = member.name.split("/")[-1]
68
+
69
+ f = tar.extractfile(member)
70
+ if f is not None:
71
+ contents = f.read()
72
+ else:
73
+ continue
74
+
75
+ if name == "vision_encoder.onnx":
76
+ self.vision_encoder = ort.InferenceSession(contents, **ort_settings)
77
+ elif name == "vision_projection.onnx":
78
+ self.vision_projection = ort.InferenceSession(
79
+ contents, **ort_settings
80
+ )
81
+ elif name == "text_encoder.onnx":
82
+ self.text_encoder = ort.InferenceSession(contents, **ort_settings)
83
+ elif "text_decoder" in name and name.endswith(".onnx"):
84
+ self.text_decoders.append(
85
+ ort.InferenceSession(contents, **ort_settings)
86
+ )
87
+ elif name == "tokenizer.json":
88
+ self.tokenizer = Tokenizer.from_buffer(contents)
89
+ elif name == "initial_kv_caches.npy":
90
+ self.initial_kv_caches = [x for x in np.load(BytesIO(contents))]
91
+ elif name == "config.json":
92
+ self.config = json.loads(contents)
93
+
94
+ assert self.vision_encoder is not None
95
+ assert self.vision_projection is not None
96
+ assert self.text_encoder is not None
97
+ assert len(self.text_decoders) > 0
98
+ assert self.tokenizer is not None
99
+ assert self.initial_kv_caches is not None
100
+ assert self.config is not None
101
+
102
+ if type(self.config) != dict or "model_version" not in self.config:
103
+ raise ValueError("Model format not recognized.")
104
+ if self.config["model_version"] > LATEST_SUPPORTED_VERSION:
105
+ raise ValueError(
106
+ "Model version not supported. You may need to upgrade the moondream package."
107
+ )
108
+
109
+ self.special_tokens = self.config["special_tokens"]
110
+ self.templates = self.config["templates"]
111
+
112
+ def encode_image(self, image: Union[Image.Image, EncodedImage]) -> EncodedImage:
113
+ """
114
+ Preprocess the image by running it through the model.
115
+
116
+ This method is useful if the user wants to make multiple queries with the same image.
117
+ The output is not guaranteed to be backward-compatible across version updates,
118
+ and should not be persisted out of band.
119
+
120
+ Args:
121
+ image (Image.Image): The input image to be encoded.
122
+
123
+ Returns:
124
+ The encoded representation of the image.
125
+ """
126
+ if type(image) == EncodedImage:
127
+ return image
128
+
129
+ image_patches = create_patches(image) # type: ignore
130
+
131
+ patch_emb = self.vision_encoder.run(None, {"input": image_patches})[0]
132
+ patch_emb = np.concatenate([patch_emb[0], patch_emb[1]], axis=-1)
133
+ patch_emb = np.expand_dims(patch_emb, axis=0)
134
+ (inputs_embeds,) = self.vision_projection.run(None, {"input": patch_emb})
135
+
136
+ kv_caches = self.initial_kv_caches
137
+ for i, decoder in enumerate(self.text_decoders):
138
+ inputs_embeds, kv_caches[i] = decoder.run(
139
+ None,
140
+ {
141
+ "inputs_embeds": inputs_embeds,
142
+ "kv_cache": kv_caches[i],
143
+ },
144
+ )
145
+ return EncodedImage(kv_caches=kv_caches)
146
+
147
+ def _generate(
148
+ self, hidden: np.ndarray, encoded_image: EncodedImage, max_tokens: int
149
+ ) -> Generator[str, None, None]:
150
+ kv_caches = {
151
+ i: encoded_image.kv_caches[i] for i in range(len(self.text_decoders))
152
+ }
153
+
154
+ generated_tokens = 0
155
+ while generated_tokens < max_tokens:
156
+ for i, decoder in enumerate(self.text_decoders):
157
+ hidden, kv_caches[i] = decoder.run(
158
+ None, {"inputs_embeds": hidden, "kv_cache": kv_caches[i]}
159
+ )
160
+
161
+ next_token = np.argmax(hidden, axis=-1)[0]
162
+ if next_token == self.special_tokens["eos"]:
163
+ break
164
+
165
+ yield self.tokenizer.decode([next_token])
166
+ generated_tokens += 1
167
+ (hidden,) = self.text_encoder.run(None, {"input_ids": [[next_token]]})
168
+
169
+ def caption(
170
+ self,
171
+ image: Union[Image.Image, EncodedImage],
172
+ length: str = "normal",
173
+ stream: bool = False,
174
+ settings: Optional[SamplingSettings] = None,
175
+ ) -> CaptionOutput:
176
+ """
177
+ Generate a caption for the input image.
178
+
179
+ Args:
180
+ image (Union[Image.Image, EncodedImage]): The input image to be captioned.
181
+ settings (Optional[SamplingSettings]): Optional settings for the caption generation.
182
+ If not provided, default settings will be used.
183
+
184
+ Returns:
185
+ str: The caption for the input image.
186
+ """
187
+ if "caption" not in self.templates:
188
+ raise ValueError("Model does not support captioning.")
189
+ if length not in self.templates["caption"]:
190
+ raise ValueError(f"Model does not support caption length '{length}'.")
191
+
192
+ (input_embeds,) = self.text_encoder.run(
193
+ None, {"input_ids": [self.templates["caption"][length]]}
194
+ )
195
+ if settings is None:
196
+ settings = {}
197
+ max_tokens = settings.get("max_tokens", DEFAULT_MAX_TOKENS)
198
+
199
+ encoded_image = self.encode_image(image)
200
+
201
+ def generator():
202
+ for t in self._generate(input_embeds, encoded_image, max_tokens):
203
+ yield t
204
+
205
+ if stream:
206
+ return {"caption": generator()}
207
+ else:
208
+ out = ""
209
+ for t in generator():
210
+ out += t
211
+ return {"caption": out}
212
+
213
+ def query(
214
+ self,
215
+ image: Union[Image.Image, EncodedImage],
216
+ question: str,
217
+ stream: bool = False,
218
+ settings: Optional[SamplingSettings] = None,
219
+ ) -> QueryOutput:
220
+ """
221
+ Generate an answer to the input question about the input image.
222
+
223
+ Args:
224
+ image (Union[Image.Image, EncodedImage]): The input image to be queried.
225
+ question (str): The question to be answered.
226
+
227
+ Returns:
228
+ str: The answer to the input question about the input image.
229
+ """
230
+ if "query" not in self.templates:
231
+ raise ValueError("Model does not support querying.")
232
+
233
+ question_toks = (
234
+ self.templates["query"]["prefix"]
235
+ + self.tokenizer.encode(question).ids
236
+ + self.templates["query"]["suffix"]
237
+ )
238
+
239
+ (input_embeds,) = self.text_encoder.run(None, {"input_ids": [question_toks]})
240
+ if settings is None:
241
+ settings = {}
242
+ max_tokens = settings.get("max_tokens", DEFAULT_MAX_TOKENS)
243
+
244
+ encoded_image = self.encode_image(image)
245
+
246
+ def generator():
247
+ for t in self._generate(input_embeds, encoded_image, max_tokens):
248
+ yield t
249
+
250
+ if stream:
251
+ return {"answer": generator()}
252
+ else:
253
+ out = ""
254
+ for t in generator():
255
+ out += t
256
+ return {"answer": out}
257
+
258
+ def detect(
259
+ self, image: Union[Image.Image, EncodedImage], object: str
260
+ ) -> List[Region]:
261
+ """
262
+ Detect and localize the specified object in the input image.
263
+
264
+ Args:
265
+ image (Union[Image.Image, EncodedImage]): The input image to be analyzed.
266
+ object (str): The object to be detected in the image.
267
+
268
+ Returns:
269
+ List[Region]: A list of Region objects representing the detected instances of the specified object.
270
+ """
271
+ return []
@@ -0,0 +1,24 @@
1
+ [tool.poetry]
2
+ name = "moondream"
3
+ version = "0.0.1"
4
+ description = "Python client library for moondream"
5
+ authors = ["vik <vik@moondream.ai>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.10"
10
+ pillow = "^10.4.0"
11
+ onnxruntime = "^1.19.2"
12
+ numpy = "^2.1.2"
13
+ onnx = "^1.17.0"
14
+ tokenizers = "^0.20.1"
15
+
16
+
17
+ [tool.pyright]
18
+ venvPath = "."
19
+ venv = ".venv"
20
+ reportMissingParameterType = false
21
+
22
+ [build-system]
23
+ requires = ["poetry-core"]
24
+ build-backend = "poetry.core.masonry.api"