moondream 1.0.2__py3-none-any.whl
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.
- moondream/__init__.py +5 -0
- moondream/torch/__init__.py +9 -0
- moondream/torch/inference.py +108 -0
- moondream/torch/layers.py +68 -0
- moondream/torch/rope.py +49 -0
- moondream/torch/text.py +92 -0
- moondream/torch/vision.py +107 -0
- moondream/torch/weights.py +189 -0
- moondream-1.0.2.dist-info/LICENSE +21 -0
- moondream-1.0.2.dist-info/METADATA +21 -0
- moondream-1.0.2.dist-info/RECORD +14 -0
- moondream-1.0.2.dist-info/WHEEL +5 -0
- moondream-1.0.2.dist-info/entry_points.txt +2 -0
- moondream-1.0.2.dist-info/top_level.txt +1 -0
moondream/__init__.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import argparse
|
|
3
|
+
import os
|
|
4
|
+
from PIL import Image
|
|
5
|
+
from transformers import AutoTokenizer
|
|
6
|
+
from .weights import load_from_safetensors
|
|
7
|
+
from .vision import encode_image
|
|
8
|
+
from .text import text_encoder, text_decoder, lm_head
|
|
9
|
+
from .rope import precompute_freqs_cis
|
|
10
|
+
|
|
11
|
+
def run_inference(image_path: str, prompt: str, model_path: str, max_tokens: int = 200, sampler: str = "greedy"):
|
|
12
|
+
"""
|
|
13
|
+
Run inference on an image with a given prompt.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
image_path (str): Path to the image file
|
|
17
|
+
prompt (str): Question to ask about the image
|
|
18
|
+
model_path (str): Path to the model weights
|
|
19
|
+
max_tokens (int, optional): Maximum number of tokens to generate. Defaults to 200.
|
|
20
|
+
sampler (str, optional): Sampling strategy ("greedy" or "multinomial"). Defaults to "greedy".
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: Generated answer
|
|
24
|
+
"""
|
|
25
|
+
if torch.cuda.is_available():
|
|
26
|
+
torch.set_default_device("cuda")
|
|
27
|
+
elif torch.backends.mps.is_available():
|
|
28
|
+
torch.set_default_device("mps")
|
|
29
|
+
|
|
30
|
+
# Load model
|
|
31
|
+
if not os.path.exists(model_path):
|
|
32
|
+
raise FileNotFoundError(f"Model not found at {model_path}")
|
|
33
|
+
model = load_from_safetensors(model_path)
|
|
34
|
+
|
|
35
|
+
# Your existing inference code here...
|
|
36
|
+
# [Rest of your original code from sample.py goes here,
|
|
37
|
+
# wrapped in the function with appropriate returns]
|
|
38
|
+
if not os.path.exists(image_path):
|
|
39
|
+
raise FileNotFoundError(f"Image not found at {image_path}")
|
|
40
|
+
image = Image.open(image_path)
|
|
41
|
+
image = image.resize((378, 378))
|
|
42
|
+
image_tensor = encode_image(image, model.vision)
|
|
43
|
+
|
|
44
|
+
# Encode text, and create inputs_embeds.
|
|
45
|
+
tokenizer = AutoTokenizer.from_pretrained("vikhyatk/moondream2")
|
|
46
|
+
prompt = f"\n\nQuestion: {prompt}\n\nAnswer:"
|
|
47
|
+
input_ids = tokenizer(prompt, return_tensors="pt")["input_ids"]
|
|
48
|
+
input_ids = torch.cat([torch.tensor([[tokenizer.eos_token_id]]), input_ids], dim=1)
|
|
49
|
+
inputs_embeds = text_encoder(input_ids, model.text)
|
|
50
|
+
inputs_embeds = torch.cat(
|
|
51
|
+
[
|
|
52
|
+
inputs_embeds[:, 0:1, :],
|
|
53
|
+
image_tensor.unsqueeze(0),
|
|
54
|
+
inputs_embeds[:, 1:, :],
|
|
55
|
+
],
|
|
56
|
+
dim=1,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
kv_cache = torch.empty(24, 2, 1, 32, 0, 64, dtype=torch.float16)
|
|
60
|
+
freqs_cis = precompute_freqs_cis(32, 2048)
|
|
61
|
+
|
|
62
|
+
for _ in range(max_tokens):
|
|
63
|
+
with torch.no_grad():
|
|
64
|
+
hidden, kv_cache = text_decoder(
|
|
65
|
+
inputs_embeds, model.text, kv_cache, freqs_cis
|
|
66
|
+
)
|
|
67
|
+
logits = lm_head(hidden, model.text)
|
|
68
|
+
|
|
69
|
+
if sampler == "multinomial":
|
|
70
|
+
next_token = torch.multinomial(
|
|
71
|
+
torch.softmax(logits, dim=-1), num_samples=1
|
|
72
|
+
).squeeze(0)
|
|
73
|
+
elif sampler == "greedy":
|
|
74
|
+
next_token = torch.argmax(logits, dim=-1)
|
|
75
|
+
else:
|
|
76
|
+
raise ValueError(f"Invalid sampler: {sampler}")
|
|
77
|
+
|
|
78
|
+
if next_token == tokenizer.eos_token_id:
|
|
79
|
+
print()
|
|
80
|
+
break
|
|
81
|
+
|
|
82
|
+
input_ids = next_token.unsqueeze(0)
|
|
83
|
+
inputs_embeds = text_encoder(input_ids, model.text)
|
|
84
|
+
|
|
85
|
+
output_text = tokenizer.batch_decode(input_ids)[0]
|
|
86
|
+
|
|
87
|
+
print(output_text, end="", flush=True)
|
|
88
|
+
|
|
89
|
+
def main():
|
|
90
|
+
parser = argparse.ArgumentParser()
|
|
91
|
+
parser.add_argument("--image", "-i", type=str, required=True)
|
|
92
|
+
parser.add_argument("--prompt", "-p", type=str, required=True)
|
|
93
|
+
parser.add_argument("--model", "-m", type=str, required=True)
|
|
94
|
+
parser.add_argument("--max-tokens", "-t", type=int, default=200)
|
|
95
|
+
parser.add_argument("--sampler", "-s", type=str, default="greedy")
|
|
96
|
+
args = parser.parse_args()
|
|
97
|
+
|
|
98
|
+
result = run_inference(
|
|
99
|
+
args.image,
|
|
100
|
+
args.prompt,
|
|
101
|
+
args.model,
|
|
102
|
+
max_tokens=args.max_tokens,
|
|
103
|
+
sampler=args.sampler
|
|
104
|
+
)
|
|
105
|
+
print(result)
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
main()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import math
|
|
3
|
+
from typing import Literal
|
|
4
|
+
from torch import nn
|
|
5
|
+
from torch.nn import functional as F
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def gelu_approx(x):
|
|
10
|
+
return F.gelu(x, approximate="tanh")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class LinearWeights:
|
|
15
|
+
weight: torch.Tensor
|
|
16
|
+
bias: torch.Tensor
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def linear(x: torch.Tensor, w: LinearWeights) -> torch.Tensor:
|
|
20
|
+
return F.linear(x, w.weight, w.bias)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class LayerNormWeights:
|
|
25
|
+
weight: torch.Tensor
|
|
26
|
+
bias: torch.Tensor
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def layer_norm(x: torch.Tensor, w: LayerNormWeights) -> torch.Tensor:
|
|
30
|
+
return F.layer_norm(x, w.bias.shape, w.weight, w.bias)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class MLPWeights:
|
|
35
|
+
fc1: LinearWeights
|
|
36
|
+
fc2: LinearWeights
|
|
37
|
+
act: Literal["gelu_approx"] = "gelu_approx"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def mlp(x: torch.Tensor, w: MLPWeights) -> torch.Tensor:
|
|
41
|
+
x = linear(x, w.fc1)
|
|
42
|
+
if w.act == "gelu_approx":
|
|
43
|
+
x = gelu_approx(x)
|
|
44
|
+
else:
|
|
45
|
+
raise NotImplementedError(f"Activation function {w.act} not implemented.")
|
|
46
|
+
x = linear(x, w.fc2)
|
|
47
|
+
return x
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class AttentionWeights:
|
|
52
|
+
qkv: LinearWeights
|
|
53
|
+
proj: LinearWeights
|
|
54
|
+
n_heads: int
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def attn(x: torch.Tensor, w: AttentionWeights) -> torch.Tensor:
|
|
58
|
+
bsz, q_len, d_model = x.shape
|
|
59
|
+
n_heads, head_dim = w.n_heads, d_model // w.n_heads
|
|
60
|
+
|
|
61
|
+
q, k, v = [
|
|
62
|
+
t.view(bsz, q_len, n_heads, head_dim).transpose(1, 2)
|
|
63
|
+
for t in linear(x, w.qkv).chunk(3, dim=-1)
|
|
64
|
+
]
|
|
65
|
+
out = F.scaled_dot_product_attention(q, k, v)
|
|
66
|
+
out = out.transpose(1, 2).reshape(bsz, q_len, d_model)
|
|
67
|
+
out = linear(out, w.proj)
|
|
68
|
+
return out
|
moondream/torch/rope.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Ethically sourced from https://github.com/xjdr-alt/entropix
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
from typing import Tuple
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def precompute_freqs_cis(
|
|
8
|
+
dim: int,
|
|
9
|
+
end: int,
|
|
10
|
+
theta: float = 10000.0,
|
|
11
|
+
use_scaled: bool = False,
|
|
12
|
+
dtype: torch.dtype = torch.float32,
|
|
13
|
+
) -> torch.Tensor:
|
|
14
|
+
freqs = 1.0 / (theta ** (torch.arange(0, dim, 2, dtype=dtype)[: (dim // 2)] / dim))
|
|
15
|
+
t = torch.arange(end, dtype=dtype).unsqueeze(1)
|
|
16
|
+
freqs = t * freqs.unsqueeze(0)
|
|
17
|
+
freqs = torch.exp(1j * freqs)
|
|
18
|
+
return torch.stack([freqs.real, freqs.imag], dim=-1)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def apply_rotary_emb(
|
|
22
|
+
xq: torch.Tensor,
|
|
23
|
+
xk: torch.Tensor,
|
|
24
|
+
freqs_cis: torch.Tensor,
|
|
25
|
+
interleave: bool = False,
|
|
26
|
+
) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
27
|
+
if interleave:
|
|
28
|
+
xq_r = xq.float().reshape(*xq.shape[:-1], -1, 2)[..., 0]
|
|
29
|
+
xq_i = xq.float().reshape(*xq.shape[:-1], -1, 2)[..., 1]
|
|
30
|
+
xk_r = xk.float().reshape(*xk.shape[:-1], -1, 2)[..., 0]
|
|
31
|
+
xk_i = xk.float().reshape(*xk.shape[:-1], -1, 2)[..., 1]
|
|
32
|
+
else:
|
|
33
|
+
d_q, d_k = xq.shape[-1] // 2, xk.shape[-1] // 2
|
|
34
|
+
xq_r, xq_i = xq[..., :d_q], xq[..., d_q:]
|
|
35
|
+
xk_r, xk_i = xk[..., :d_k], xk[..., d_k:]
|
|
36
|
+
|
|
37
|
+
freqs_cos = freqs_cis[..., 0].unsqueeze(0).unsqueeze(0)
|
|
38
|
+
freqs_sin = freqs_cis[..., 1].unsqueeze(0).unsqueeze(0)
|
|
39
|
+
|
|
40
|
+
# Complex multiplication: (a + bi) * (c + di) = (ac - bd) + (ad + bc)i
|
|
41
|
+
xq_out_r = xq_r * freqs_cos - xq_i * freqs_sin
|
|
42
|
+
xq_out_i = xq_r * freqs_sin + xq_i * freqs_cos
|
|
43
|
+
xk_out_r = xk_r * freqs_cos - xk_i * freqs_sin
|
|
44
|
+
xk_out_i = xk_r * freqs_sin + xk_i * freqs_cos
|
|
45
|
+
|
|
46
|
+
xq_out = torch.stack((xq_out_r, xq_out_i), dim=-1).flatten(-2)
|
|
47
|
+
xk_out = torch.stack((xk_out_r, xk_out_i), dim=-1).flatten(-2)
|
|
48
|
+
|
|
49
|
+
return xq_out.to(xq.dtype), xk_out.to(xk.dtype)
|
moondream/torch/text.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from typing import Dict, Tuple, Optional, List
|
|
3
|
+
from torch.nn import functional as F
|
|
4
|
+
|
|
5
|
+
from .rope import apply_rotary_emb, precompute_freqs_cis
|
|
6
|
+
from .layers import layer_norm, linear, mlp
|
|
7
|
+
from .weights import TextModel, AttentionWeights, load_from_safetensors
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def text_encoder(input_ids: torch.Tensor, w: TextModel):
|
|
11
|
+
return F.embedding(input_ids, w.wte)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def attn_mask(pos, seq_len):
|
|
15
|
+
"""
|
|
16
|
+
Create an attention mask that aligns with the bottom right of the
|
|
17
|
+
attention matrix. For example, if q_len = 2 and kv_len = 5, we want the
|
|
18
|
+
following:
|
|
19
|
+
|
|
20
|
+
1 1 1 1 0
|
|
21
|
+
1 1 1 1 1
|
|
22
|
+
|
|
23
|
+
and not this, which is what we get by default if we just set is_causal.
|
|
24
|
+
|
|
25
|
+
1 0 0 0 0
|
|
26
|
+
1 1 0 0 0
|
|
27
|
+
"""
|
|
28
|
+
mask = torch.ones(seq_len, pos + seq_len, dtype=torch.bool)
|
|
29
|
+
mask[:, pos:] = torch.tril(torch.ones(seq_len, seq_len, dtype=torch.bool))
|
|
30
|
+
mask = mask.unsqueeze(0).unsqueeze(0) # Add batch and head dimensions
|
|
31
|
+
return mask
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def attn(
|
|
35
|
+
x: torch.Tensor,
|
|
36
|
+
w: AttentionWeights,
|
|
37
|
+
freqs_cis: torch.Tensor,
|
|
38
|
+
layer_kv_cache: torch.Tensor,
|
|
39
|
+
):
|
|
40
|
+
bsz, q_len, d_model = x.shape
|
|
41
|
+
pos = 0 if layer_kv_cache is None else layer_kv_cache.shape[3]
|
|
42
|
+
n_heads, head_dim = w.n_heads, d_model // w.n_heads
|
|
43
|
+
|
|
44
|
+
q, k, v = [
|
|
45
|
+
t.view(bsz, q_len, n_heads, head_dim).transpose(1, 2)
|
|
46
|
+
for t in linear(x, w.qkv).chunk(3, dim=-1)
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
q_rot, q_pass = q.chunk(2, dim=-1)
|
|
50
|
+
k_rot, k_pass = k.chunk(2, dim=-1)
|
|
51
|
+
q_rot, k_rot = apply_rotary_emb(q_rot, k_rot, freqs_cis[pos : pos + q_len])
|
|
52
|
+
q = torch.cat([q_rot, q_pass], dim=-1)
|
|
53
|
+
k = torch.cat([k_rot, k_pass], dim=-1)
|
|
54
|
+
|
|
55
|
+
if layer_kv_cache is not None:
|
|
56
|
+
k = torch.cat([layer_kv_cache[0], k], dim=2)
|
|
57
|
+
v = torch.cat([layer_kv_cache[1], v], dim=2)
|
|
58
|
+
|
|
59
|
+
out = F.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask(pos, q_len)).to(
|
|
60
|
+
# This type conversion isn't needed when running in PyTorch directly, but the
|
|
61
|
+
# ONNX export runs attention in float32 because the attention mask is cast to
|
|
62
|
+
# float32.
|
|
63
|
+
x.dtype
|
|
64
|
+
)
|
|
65
|
+
out = out.transpose(1, 2).reshape(bsz, q_len, d_model)
|
|
66
|
+
out = linear(out, w.proj)
|
|
67
|
+
return out, torch.stack([k, v])
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def text_decoder(
|
|
71
|
+
inputs_embeds: torch.Tensor,
|
|
72
|
+
w: TextModel,
|
|
73
|
+
kv_cache: torch.Tensor,
|
|
74
|
+
freqs_cis: torch.Tensor,
|
|
75
|
+
):
|
|
76
|
+
hidden_BTC = inputs_embeds
|
|
77
|
+
new_kv_cache = [torch.empty(0)] * len(w.blocks)
|
|
78
|
+
|
|
79
|
+
for i, block in enumerate(w.blocks):
|
|
80
|
+
l_in = layer_norm(hidden_BTC, block.ln)
|
|
81
|
+
l_attn, new_kv_cache[i] = attn(l_in, block.attn, freqs_cis, kv_cache[i])
|
|
82
|
+
l_mlp = mlp(l_in, block.mlp)
|
|
83
|
+
hidden_BTC = hidden_BTC + l_attn + l_mlp
|
|
84
|
+
|
|
85
|
+
return hidden_BTC, torch.stack(new_kv_cache)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def lm_head(hidden_BTC: torch.Tensor, w: TextModel):
|
|
89
|
+
hidden_BC = hidden_BTC[:, -1, :]
|
|
90
|
+
hidden_BC = layer_norm(hidden_BC, w.post_ln)
|
|
91
|
+
logits = linear(hidden_BC, w.lm_head)
|
|
92
|
+
return logits
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import math
|
|
3
|
+
from typing import List, Tuple, Union
|
|
4
|
+
from einops import rearrange
|
|
5
|
+
from torch.nn import functional as F
|
|
6
|
+
from PIL import Image
|
|
7
|
+
from torchvision.transforms.v2 import InterpolationMode
|
|
8
|
+
from torchvision.transforms.v2.functional import (
|
|
9
|
+
resize as tv_resize,
|
|
10
|
+
to_image,
|
|
11
|
+
to_dtype,
|
|
12
|
+
normalize,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
from .weights import VisionModel, load_from_safetensors
|
|
16
|
+
from .layers import attn, linear, layer_norm, mlp
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def im_resize(
|
|
20
|
+
image: Image.Image,
|
|
21
|
+
size: List[int],
|
|
22
|
+
interpolation: InterpolationMode = InterpolationMode.BICUBIC,
|
|
23
|
+
) -> Image.Image:
|
|
24
|
+
"""
|
|
25
|
+
The 'resize' function from torchvision has bad type signatures.
|
|
26
|
+
it accepts both PIL images and torch tensors, but the type signature
|
|
27
|
+
only allows tensors.
|
|
28
|
+
"""
|
|
29
|
+
return tv_resize(
|
|
30
|
+
image, # type: ignore
|
|
31
|
+
size,
|
|
32
|
+
InterpolationMode.BICUBIC,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def create_patches(
|
|
37
|
+
image: Image.Image, image_patch_size=378
|
|
38
|
+
) -> Tuple[List[Image.Image], Tuple[int, int]]:
|
|
39
|
+
"""
|
|
40
|
+
Split the given image into a variable number of patches depending upon its
|
|
41
|
+
resolution.
|
|
42
|
+
"""
|
|
43
|
+
# Start off with the global patch.
|
|
44
|
+
patches = [im_resize(image, [image_patch_size, image_patch_size])]
|
|
45
|
+
|
|
46
|
+
# Find the closest resolution template.
|
|
47
|
+
res_templates = [(1, 2), (2, 1), (2, 2)]
|
|
48
|
+
im_width, im_height = image.size
|
|
49
|
+
max_dim = max(im_width, im_height)
|
|
50
|
+
if max_dim < image_patch_size * 1.4:
|
|
51
|
+
# If the image is already small, we just do a single patch that is a
|
|
52
|
+
# duplicate of the global patch. This creates a small amount of
|
|
53
|
+
# redundant computation now, but it is simpler and future-proofs us
|
|
54
|
+
# if/when we condition the vision encoder on the patch type.
|
|
55
|
+
res_template = (1, 1)
|
|
56
|
+
patches.append(patches[0])
|
|
57
|
+
else:
|
|
58
|
+
aspect_ratio = im_width / im_height
|
|
59
|
+
res_template = min(
|
|
60
|
+
res_templates, key=lambda size: abs((size[1] / size[0]) - aspect_ratio)
|
|
61
|
+
)
|
|
62
|
+
# TODO: Actually implement patching... just going to put in the global
|
|
63
|
+
# patch for now to make progress on other aspects.
|
|
64
|
+
patches.append(patches[0])
|
|
65
|
+
|
|
66
|
+
return patches, res_template
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def encode_image(image: Image.Image, weights: VisionModel) -> torch.Tensor:
|
|
70
|
+
patches, res_template = create_patches(image.convert("RGB"))
|
|
71
|
+
patches = torch.stack(
|
|
72
|
+
[
|
|
73
|
+
normalize(
|
|
74
|
+
to_dtype(to_image(patch), torch.float16, scale=True),
|
|
75
|
+
mean=[0.5, 0.5, 0.5],
|
|
76
|
+
std=[0.5, 0.5, 0.5],
|
|
77
|
+
)
|
|
78
|
+
for patch in patches
|
|
79
|
+
]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
outputs = vision_encoder(patches, weights)
|
|
83
|
+
|
|
84
|
+
# TODO: Merge sub-image patch outputs properly... for now we'll just assume
|
|
85
|
+
# that the global patch is repeated.
|
|
86
|
+
assert outputs.shape[0] == 2, "Expected single image patch."
|
|
87
|
+
outputs = torch.cat([outputs[0], outputs[1]], dim=-1)
|
|
88
|
+
|
|
89
|
+
return mlp(outputs, weights.proj_mlp)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def vision_encoder(input_BCHW: torch.Tensor, w: VisionModel):
|
|
93
|
+
x = rearrange(
|
|
94
|
+
input_BCHW,
|
|
95
|
+
"b c (h p1) (w p2) -> b (h w) (c p1 p2)",
|
|
96
|
+
p1=w.patch_size,
|
|
97
|
+
p2=w.patch_size,
|
|
98
|
+
) # B3HW -> B(HxW)(3xP1xP2), aka BTC
|
|
99
|
+
|
|
100
|
+
x = linear(x, w.patch_emb)
|
|
101
|
+
x = x + w.pos_emb
|
|
102
|
+
for block in w.blocks:
|
|
103
|
+
x = x + attn(layer_norm(x, block.ln1), block.attn)
|
|
104
|
+
x = x + mlp(layer_norm(x, block.ln2), block.mlp)
|
|
105
|
+
x = layer_norm(x, w.post_ln)
|
|
106
|
+
|
|
107
|
+
return x
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
import math
|
|
3
|
+
import safetensors
|
|
4
|
+
from typing import Dict, Union, Literal, List, Any
|
|
5
|
+
from contextlib import contextmanager
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
|
|
8
|
+
from .layers import LinearWeights, LayerNormWeights, MLPWeights, AttentionWeights
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class VisionBlock:
|
|
13
|
+
ln1: LayerNormWeights
|
|
14
|
+
attn: AttentionWeights
|
|
15
|
+
ln2: LayerNormWeights
|
|
16
|
+
mlp: MLPWeights
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class VisionModel:
|
|
21
|
+
patch_size: int
|
|
22
|
+
patch_emb: LinearWeights
|
|
23
|
+
pos_emb: torch.Tensor
|
|
24
|
+
blocks: List[VisionBlock]
|
|
25
|
+
post_ln: LayerNormWeights
|
|
26
|
+
proj_mlp: MLPWeights
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class TextBlock:
|
|
31
|
+
ln: LayerNormWeights
|
|
32
|
+
attn: AttentionWeights
|
|
33
|
+
mlp: MLPWeights
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class TextModel:
|
|
38
|
+
wte: torch.Tensor
|
|
39
|
+
blocks: List[TextBlock]
|
|
40
|
+
post_ln: LayerNormWeights
|
|
41
|
+
lm_head: LinearWeights
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class MoondreamModel:
|
|
46
|
+
vision: VisionModel
|
|
47
|
+
text: TextModel
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@contextmanager
|
|
51
|
+
def safetensors_open(safetensors_file: str):
|
|
52
|
+
"""
|
|
53
|
+
Simplify interfacing with safetensors files. Eliminates the need to ignore
|
|
54
|
+
type errors when using the `safe_open` function.
|
|
55
|
+
"""
|
|
56
|
+
with safetensors.safe_open(
|
|
57
|
+
safetensors_file, framework="pt"
|
|
58
|
+
) as st: # pyright: ignore
|
|
59
|
+
|
|
60
|
+
def get_tensor(name: str) -> torch.Tensor:
|
|
61
|
+
return st.get_tensor(name)
|
|
62
|
+
|
|
63
|
+
yield get_tensor
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def load_from_safetensors(
|
|
67
|
+
safetensors_file: str,
|
|
68
|
+
vision_blocks: int = 27,
|
|
69
|
+
text_blocks: int = 24,
|
|
70
|
+
) -> MoondreamModel:
|
|
71
|
+
with safetensors_open(safetensors_file) as get_tensor:
|
|
72
|
+
## Vision encoder
|
|
73
|
+
prefix = "vision_encoder.encoder.model.visual.patch_embed.linear"
|
|
74
|
+
patch_emb = LinearWeights(
|
|
75
|
+
weight=get_tensor(f"{prefix}.weight"), bias=get_tensor(f"{prefix}.bias")
|
|
76
|
+
)
|
|
77
|
+
patch_size = int(math.sqrt(patch_emb.weight.shape[1] // 3))
|
|
78
|
+
pos_emb = get_tensor("vision_encoder.encoder.model.visual.pos_embed")
|
|
79
|
+
post_ln = LayerNormWeights(
|
|
80
|
+
weight=get_tensor("vision_encoder.encoder.model.visual.norm.weight"),
|
|
81
|
+
bias=get_tensor("vision_encoder.encoder.model.visual.norm.bias"),
|
|
82
|
+
)
|
|
83
|
+
blocks = []
|
|
84
|
+
for i in range(vision_blocks):
|
|
85
|
+
prefix = f"vision_encoder.encoder.model.visual.blocks.{i}"
|
|
86
|
+
blocks.append(
|
|
87
|
+
VisionBlock(
|
|
88
|
+
ln1=LayerNormWeights(
|
|
89
|
+
weight=get_tensor(f"{prefix}.norm1.weight"),
|
|
90
|
+
bias=get_tensor(f"{prefix}.norm1.bias"),
|
|
91
|
+
),
|
|
92
|
+
attn=AttentionWeights(
|
|
93
|
+
qkv=LinearWeights(
|
|
94
|
+
weight=get_tensor(f"{prefix}.attn.qkv.weight"),
|
|
95
|
+
bias=get_tensor(f"{prefix}.attn.qkv.bias"),
|
|
96
|
+
),
|
|
97
|
+
proj=LinearWeights(
|
|
98
|
+
weight=get_tensor(f"{prefix}.attn.proj.weight"),
|
|
99
|
+
bias=get_tensor(f"{prefix}.attn.proj.bias"),
|
|
100
|
+
),
|
|
101
|
+
n_heads=16,
|
|
102
|
+
),
|
|
103
|
+
ln2=LayerNormWeights(
|
|
104
|
+
weight=get_tensor(f"{prefix}.norm2.weight"),
|
|
105
|
+
bias=get_tensor(f"{prefix}.norm2.bias"),
|
|
106
|
+
),
|
|
107
|
+
mlp=MLPWeights(
|
|
108
|
+
fc1=LinearWeights(
|
|
109
|
+
weight=get_tensor(f"{prefix}.mlp.fc1.weight"),
|
|
110
|
+
bias=get_tensor(f"{prefix}.mlp.fc1.bias"),
|
|
111
|
+
),
|
|
112
|
+
fc2=LinearWeights(
|
|
113
|
+
weight=get_tensor(f"{prefix}.mlp.fc2.weight"),
|
|
114
|
+
bias=get_tensor(f"{prefix}.mlp.fc2.bias"),
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
)
|
|
118
|
+
)
|
|
119
|
+
proj_mlp = MLPWeights(
|
|
120
|
+
fc1=LinearWeights(
|
|
121
|
+
weight=get_tensor("vision_encoder.projection.mlp.fc1.weight"),
|
|
122
|
+
bias=get_tensor("vision_encoder.projection.mlp.fc1.bias"),
|
|
123
|
+
),
|
|
124
|
+
fc2=LinearWeights(
|
|
125
|
+
weight=get_tensor("vision_encoder.projection.mlp.fc2.weight"),
|
|
126
|
+
bias=get_tensor("vision_encoder.projection.mlp.fc2.bias"),
|
|
127
|
+
),
|
|
128
|
+
act="gelu_approx",
|
|
129
|
+
)
|
|
130
|
+
vision = VisionModel(
|
|
131
|
+
patch_size=patch_size,
|
|
132
|
+
patch_emb=patch_emb,
|
|
133
|
+
pos_emb=pos_emb,
|
|
134
|
+
blocks=blocks,
|
|
135
|
+
post_ln=post_ln,
|
|
136
|
+
proj_mlp=proj_mlp,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
## Text decoder model
|
|
140
|
+
wte = get_tensor("text_model.transformer.embd.wte.weight")
|
|
141
|
+
post_ln = LayerNormWeights(
|
|
142
|
+
weight=get_tensor("text_model.lm_head.ln.weight"),
|
|
143
|
+
bias=get_tensor("text_model.lm_head.ln.bias"),
|
|
144
|
+
)
|
|
145
|
+
lm_head = LinearWeights(
|
|
146
|
+
weight=get_tensor("text_model.lm_head.linear.weight"),
|
|
147
|
+
bias=get_tensor("text_model.lm_head.linear.bias"),
|
|
148
|
+
)
|
|
149
|
+
blocks = []
|
|
150
|
+
for i in range(text_blocks):
|
|
151
|
+
prefix = f"text_model.transformer.h.{i}"
|
|
152
|
+
blocks.append(
|
|
153
|
+
TextBlock(
|
|
154
|
+
ln=LayerNormWeights(
|
|
155
|
+
weight=get_tensor(f"{prefix}.ln.weight"),
|
|
156
|
+
bias=get_tensor(f"{prefix}.ln.bias"),
|
|
157
|
+
),
|
|
158
|
+
attn=AttentionWeights(
|
|
159
|
+
qkv=LinearWeights(
|
|
160
|
+
weight=get_tensor(f"{prefix}.mixer.Wqkv.weight"),
|
|
161
|
+
bias=get_tensor(f"{prefix}.mixer.Wqkv.bias"),
|
|
162
|
+
),
|
|
163
|
+
proj=LinearWeights(
|
|
164
|
+
weight=get_tensor(f"{prefix}.mixer.out_proj.weight"),
|
|
165
|
+
bias=get_tensor(f"{prefix}.mixer.out_proj.bias"),
|
|
166
|
+
),
|
|
167
|
+
n_heads=32,
|
|
168
|
+
),
|
|
169
|
+
mlp=MLPWeights(
|
|
170
|
+
fc1=LinearWeights(
|
|
171
|
+
weight=get_tensor(f"{prefix}.mlp.fc1.weight"),
|
|
172
|
+
bias=get_tensor(f"{prefix}.mlp.fc1.bias"),
|
|
173
|
+
),
|
|
174
|
+
fc2=LinearWeights(
|
|
175
|
+
weight=get_tensor(f"{prefix}.mlp.fc2.weight"),
|
|
176
|
+
bias=get_tensor(f"{prefix}.mlp.fc2.bias"),
|
|
177
|
+
),
|
|
178
|
+
act="gelu_approx",
|
|
179
|
+
),
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
text = TextModel(wte=wte, blocks=blocks, post_ln=post_ln, lm_head=lm_head)
|
|
183
|
+
|
|
184
|
+
return MoondreamModel(vision=vision, text=text)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
if __name__ == "__main__":
|
|
188
|
+
weights = load_from_safetensors("model.safetensors")
|
|
189
|
+
print(weights)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Moondream
|
|
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,21 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: moondream
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: A package for image-based question answering using Moondream
|
|
5
|
+
Home-page: https://github.com/1997MarsRover/moondream
|
|
6
|
+
Author: 1997marsrover
|
|
7
|
+
Author-email: antonygithinji11156@gmail.com
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Requires-Python: >=3.7
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: torch
|
|
19
|
+
Requires-Dist: Pillow
|
|
20
|
+
Requires-Dist: transformers
|
|
21
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
moondream/__init__.py,sha256=E3ndp0duYUi6CLWsoIFpltrlvf-OK4QQfrCbgT4Kf6A,95
|
|
2
|
+
moondream/torch/__init__.py,sha256=AttwzPDVjIPtRGFHq83__VI2i2pemSCblQ4C1pyo1ek,202
|
|
3
|
+
moondream/torch/inference.py,sha256=IrzYiSU-YF6beOevyng-BmwEGWECFRh_zWtx5bJPawc,3847
|
|
4
|
+
moondream/torch/layers.py,sha256=u6E-0JmMuKryzpMPWmxI_n5SRXFRlPXc-slKg-501UE,1556
|
|
5
|
+
moondream/torch/rope.py,sha256=FU017lf51z7iSS2bs_4KDk7-frz8P4pVEl-0pUWhNQA,1741
|
|
6
|
+
moondream/torch/text.py,sha256=4JodKX937h9ghWm4oNyhQgKtZVogV8CvxyfyEg2OUVQ,2920
|
|
7
|
+
moondream/torch/vision.py,sha256=4RfTFvtNYpM005XDklqAeZebgVWtJAF9zhF4Igl-Q9M,3393
|
|
8
|
+
moondream/torch/weights.py,sha256=C5whBJPmrKacRAwmylQnz6H-m8z36T6YFF43FcwlwD0,6599
|
|
9
|
+
moondream-1.0.2.dist-info/LICENSE,sha256=6LnNiERg1JyLVd1l0jpDHgTAeNouDbfhXrAnVfEDTCA,1065
|
|
10
|
+
moondream-1.0.2.dist-info/METADATA,sha256=yr43jlIOrQ9fvNpRlHrSlgXsoDj8sw1-m4gvliju3UI,750
|
|
11
|
+
moondream-1.0.2.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
12
|
+
moondream-1.0.2.dist-info/entry_points.txt,sha256=iuQDF-fzfsB8YXZ0XxhGecvZ1c6lyibrL_D67EWCE6w,61
|
|
13
|
+
moondream-1.0.2.dist-info/top_level.txt,sha256=m_eOqzX4m1xP9Njpi_vHQeOP5B720mGJazO2gurLn28,10
|
|
14
|
+
moondream-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
moondream
|