Feature Extraction
MLX
Safetensors
jina_omni_small_mlx
multimodal
embeddings
semantic-search
image
audio
video
apple-silicon
Instructions to use lokimorty/nanobe with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use lokimorty/nanobe with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download lokimorty/nanobe --local-dir nanobe
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Release Nanobe model and MLX reference implementation
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +3 -0
- LICENSE +21 -0
- README.md +90 -0
- assets/hero-scores.png +3 -0
- assets/peer-bench-scores.png +3 -0
- dist/nanobe-0.1.0-py3-none-any.whl +0 -0
- dist/nanobe-0.1.0.tar.gz +3 -0
- licenses/jina-cc-by-nc-4.0.txt +408 -0
- licenses/meta-apache-2.0.txt +202 -0
- model/LICENSE +21 -0
- model/README.md +85 -0
- model/audio-pooler.safetensors +3 -0
- model/chat_template.jinja +154 -0
- model/config.json +54 -0
- model/geometry.json +67 -0
- model/head/head.json +32 -0
- model/head/head.safetensors +3 -0
- model/lanes.py +44 -0
- model/language.safetensors +3 -0
- model/licenses/jina-cc-by-nc-4.0.txt +408 -0
- model/licenses/meta-apache-2.0.txt +202 -0
- model/magnitude.py +80 -0
- model/manifest.json +91 -0
- model/media-adapter.safetensors +3 -0
- model/model.py +878 -0
- model/nanobe_mlx_omni.py +988 -0
- model/omni.safetensors +3 -0
- model/packaging.json +41 -0
- model/page-merger.safetensors +3 -0
- model/pe-audio/config.json +123 -0
- model/pe-audio/manifest.json +103 -0
- model/pe-audio/model.safetensors +3 -0
- model/pe-audio/nanobe_mlx_pe.py +270 -0
- model/pe-text/config.json +155 -0
- model/pe-text/manifest.json +120 -0
- model/pe-text/model.safetensors +3 -0
- model/pe-text/pe_text.py +158 -0
- model/pe-text/special_tokens_map.json +37 -0
- model/pe-text/tokenizer.json +0 -0
- model/pe-text/tokenizer_config.json +18 -0
- model/preprocessor_config.json +17 -0
- model/processor_config.json +61 -0
- model/tokenizer.bin +3 -0
- model/tokenizer.json +3 -0
- model/tokenizer_config.json +15 -0
- model/upstream-notices.md +5 -0
- model/video_preprocessor_config.json +22 -0
- source/LICENSE +21 -0
- source/README.md +105 -0
- source/docs/licenses/jina-cc-by-nc-4.0.txt +408 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
assets/hero-scores.png filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/peer-bench-scores.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
model/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Artem G.
|
| 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.
|
README.md
CHANGED
|
@@ -1,3 +1,93 @@
|
|
| 1 |
---
|
| 2 |
license: mit
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
license: mit
|
| 3 |
+
library_name: mlx
|
| 4 |
+
pipeline_tag: feature-extraction
|
| 5 |
+
base_model:
|
| 6 |
+
- jinaai/jina-embeddings-v5-omni-small-retrieval
|
| 7 |
+
- facebook/pe-av-small
|
| 8 |
+
tags:
|
| 9 |
+
- multimodal
|
| 10 |
+
- embeddings
|
| 11 |
+
- semantic-search
|
| 12 |
+
- image
|
| 13 |
+
- audio
|
| 14 |
+
- video
|
| 15 |
+
- apple-silicon
|
| 16 |
---
|
| 17 |
+
|
| 18 |
+
# Nanobe
|
| 19 |
+
|
| 20 |
+
Nanobe is a local multimodal embedding model for searching mixed folders. It encodes text, images, document pages, audio, and video into a shared vector space.
|
| 21 |
+
|
| 22 |
+
Audio and video are encoded directly, without generating transcripts or captions during indexing. Pages and recording segments get their own vectors, so a relevant page or moment can retrieve the whole file.
|
| 23 |
+
|
| 24 |
+
This release includes quantized model weights and a Python reference implementation using MLX on Apple Silicon.
|
| 25 |
+
|
| 26 |
+
Read the [release post](https://thiswickedtongue.net/nanobe/).
|
| 27 |
+
|
| 28 |
+
## Benchmark results
|
| 29 |
+
|
| 30 |
+
Nanobe Bench tests retrieval across mixed file types, including concepts, style, and relevant content deep inside documents and recordings.
|
| 31 |
+
|
| 32 |
+

|
| 33 |
+
|
| 34 |
+
Results on conventional retrieval benchmarks:
|
| 35 |
+
|
| 36 |
+

|
| 37 |
+
|
| 38 |
+
## Usage
|
| 39 |
+
|
| 40 |
+
Use an Apple Silicon Mac with Python 3.12 and [uv](https://docs.astral.sh/uv/). Download the release and install the reference implementation:
|
| 41 |
+
|
| 42 |
+
```sh
|
| 43 |
+
uvx --from huggingface-hub hf download lokimorty/nanobe --local-dir nanobe-release
|
| 44 |
+
cd nanobe-release/source
|
| 45 |
+
uv sync --frozen
|
| 46 |
+
export NANOBE_MODEL_DIR="$(cd ../model && pwd)"
|
| 47 |
+
```
|
| 48 |
+
|
| 49 |
+
### Python inference
|
| 50 |
+
|
| 51 |
+
Encode a text query and compare it with a local file. Run from the `source` directory and replace `example.jpg` with your file:
|
| 52 |
+
|
| 53 |
+
```python
|
| 54 |
+
from pathlib import Path
|
| 55 |
+
|
| 56 |
+
import numpy as np
|
| 57 |
+
|
| 58 |
+
from nanobe.inference import encode_file
|
| 59 |
+
from nanobe.runtime import load, load_lanes, load_query
|
| 60 |
+
|
| 61 |
+
package = Path("../model")
|
| 62 |
+
model = load(package)
|
| 63 |
+
calibration = load_lanes(package)
|
| 64 |
+
query = load_query(package).encode_query("a flamenco performance")
|
| 65 |
+
kind, segments = encode_file(model, Path("example.jpg"))
|
| 66 |
+
vectors = np.stack([calibration.item(segment.vector, kind) for segment in segments])
|
| 67 |
+
scores = vectors @ query / (np.linalg.norm(vectors, axis=1) * np.linalg.norm(query))
|
| 68 |
+
print(float(scores.max()))
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
The same file encoder accepts supported images, documents, audio, and video. See [Python inference](source/docs/model.md#python-inference) for details and a [search example](source/examples/search.py) that ranks multiple files.
|
| 72 |
+
|
| 73 |
+
### Folder search
|
| 74 |
+
|
| 75 |
+
The reference implementation also includes a local folder index:
|
| 76 |
+
|
| 77 |
+
```sh
|
| 78 |
+
uv run --frozen nanobe index ~/Creative
|
| 79 |
+
uv run --frozen nanobe search ~/Creative "a presentation about sustainable packaging"
|
| 80 |
+
uv run --frozen nanobe search ~/Creative --file ~/Desktop/reference.jpg
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
See the [source README](source/README.md) for supported formats and setup details.
|
| 84 |
+
|
| 85 |
+
## Model lineage
|
| 86 |
+
|
| 87 |
+
Nanobe builds on Jina AI's [jina-embeddings-v5-omni-small-retrieval](https://huggingface.co/jinaai/jina-embeddings-v5-omni-small-retrieval) and Meta's [Perception Encoder AV](https://huggingface.co/facebook/pe-av-small). See [model lineage](upstream-notices.md) for the recorded revisions.
|
| 88 |
+
|
| 89 |
+
## License
|
| 90 |
+
|
| 91 |
+
I release Nanobe under MIT because I believe reusable computational capabilities should be available for anyone to use, modify, and build on, including commercially.
|
| 92 |
+
|
| 93 |
+
See [LICENSE](LICENSE) for the full MIT License.
|
assets/hero-scores.png
ADDED
|
Git LFS Details
|
assets/peer-bench-scores.png
ADDED
|
Git LFS Details
|
dist/nanobe-0.1.0-py3-none-any.whl
ADDED
|
Binary file (53 kB). View file
|
|
|
dist/nanobe-0.1.0.tar.gz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d79f7bfb47e85ed6c48e7edba8c838d7de91208e1723edcbfdb7ef755473d448
|
| 3 |
+
size 111386
|
licenses/jina-cc-by-nc-4.0.txt
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Attribution-NonCommercial 4.0 International
|
| 2 |
+
|
| 3 |
+
=======================================================================
|
| 4 |
+
|
| 5 |
+
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
| 6 |
+
does not provide legal services or legal advice. Distribution of
|
| 7 |
+
Creative Commons public licenses does not create a lawyer-client or
|
| 8 |
+
other relationship. Creative Commons makes its licenses and related
|
| 9 |
+
information available on an "as-is" basis. Creative Commons gives no
|
| 10 |
+
warranties regarding its licenses, any material licensed under their
|
| 11 |
+
terms and conditions, or any related information. Creative Commons
|
| 12 |
+
disclaims all liability for damages resulting from their use to the
|
| 13 |
+
fullest extent possible.
|
| 14 |
+
|
| 15 |
+
Using Creative Commons Public Licenses
|
| 16 |
+
|
| 17 |
+
Creative Commons public licenses provide a standard set of terms and
|
| 18 |
+
conditions that creators and other rights holders may use to share
|
| 19 |
+
original works of authorship and other material subject to copyright
|
| 20 |
+
and certain other rights specified in the public license below. The
|
| 21 |
+
following considerations are for informational purposes only, are not
|
| 22 |
+
exhaustive, and do not form part of our licenses.
|
| 23 |
+
|
| 24 |
+
Considerations for licensors: Our public licenses are
|
| 25 |
+
intended for use by those authorized to give the public
|
| 26 |
+
permission to use material in ways otherwise restricted by
|
| 27 |
+
copyright and certain other rights. Our licenses are
|
| 28 |
+
irrevocable. Licensors should read and understand the terms
|
| 29 |
+
and conditions of the license they choose before applying it.
|
| 30 |
+
Licensors should also secure all rights necessary before
|
| 31 |
+
applying our licenses so that the public can reuse the
|
| 32 |
+
material as expected. Licensors should clearly mark any
|
| 33 |
+
material not subject to the license. This includes other CC-
|
| 34 |
+
licensed material, or material used under an exception or
|
| 35 |
+
limitation to copyright. More considerations for licensors:
|
| 36 |
+
wiki.creativecommons.org/Considerations_for_licensors
|
| 37 |
+
|
| 38 |
+
Considerations for the public: By using one of our public
|
| 39 |
+
licenses, a licensor grants the public permission to use the
|
| 40 |
+
licensed material under specified terms and conditions. If
|
| 41 |
+
the licensor's permission is not necessary for any reason--for
|
| 42 |
+
example, because of any applicable exception or limitation to
|
| 43 |
+
copyright--then that use is not regulated by the license. Our
|
| 44 |
+
licenses grant only permissions under copyright and certain
|
| 45 |
+
other rights that a licensor has authority to grant. Use of
|
| 46 |
+
the licensed material may still be restricted for other
|
| 47 |
+
reasons, including because others have copyright or other
|
| 48 |
+
rights in the material. A licensor may make special requests,
|
| 49 |
+
such as asking that all changes be marked or described.
|
| 50 |
+
Although not required by our licenses, you are encouraged to
|
| 51 |
+
respect those requests where reasonable. More considerations
|
| 52 |
+
for the public:
|
| 53 |
+
wiki.creativecommons.org/Considerations_for_licensees
|
| 54 |
+
|
| 55 |
+
=======================================================================
|
| 56 |
+
|
| 57 |
+
Creative Commons Attribution-NonCommercial 4.0 International Public
|
| 58 |
+
License
|
| 59 |
+
|
| 60 |
+
By exercising the Licensed Rights (defined below), You accept and agree
|
| 61 |
+
to be bound by the terms and conditions of this Creative Commons
|
| 62 |
+
Attribution-NonCommercial 4.0 International Public License ("Public
|
| 63 |
+
License"). To the extent this Public License may be interpreted as a
|
| 64 |
+
contract, You are granted the Licensed Rights in consideration of Your
|
| 65 |
+
acceptance of these terms and conditions, and the Licensor grants You
|
| 66 |
+
such rights in consideration of benefits the Licensor receives from
|
| 67 |
+
making the Licensed Material available under these terms and
|
| 68 |
+
conditions.
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
Section 1 -- Definitions.
|
| 72 |
+
|
| 73 |
+
a. Adapted Material means material subject to Copyright and Similar
|
| 74 |
+
Rights that is derived from or based upon the Licensed Material
|
| 75 |
+
and in which the Licensed Material is translated, altered,
|
| 76 |
+
arranged, transformed, or otherwise modified in a manner requiring
|
| 77 |
+
permission under the Copyright and Similar Rights held by the
|
| 78 |
+
Licensor. For purposes of this Public License, where the Licensed
|
| 79 |
+
Material is a musical work, performance, or sound recording,
|
| 80 |
+
Adapted Material is always produced where the Licensed Material is
|
| 81 |
+
synched in timed relation with a moving image.
|
| 82 |
+
|
| 83 |
+
b. Adapter's License means the license You apply to Your Copyright
|
| 84 |
+
and Similar Rights in Your contributions to Adapted Material in
|
| 85 |
+
accordance with the terms and conditions of this Public License.
|
| 86 |
+
|
| 87 |
+
c. Copyright and Similar Rights means copyright and/or similar rights
|
| 88 |
+
closely related to copyright including, without limitation,
|
| 89 |
+
performance, broadcast, sound recording, and Sui Generis Database
|
| 90 |
+
Rights, without regard to how the rights are labeled or
|
| 91 |
+
categorized. For purposes of this Public License, the rights
|
| 92 |
+
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
| 93 |
+
Rights.
|
| 94 |
+
d. Effective Technological Measures means those measures that, in the
|
| 95 |
+
absence of proper authority, may not be circumvented under laws
|
| 96 |
+
fulfilling obligations under Article 11 of the WIPO Copyright
|
| 97 |
+
Treaty adopted on December 20, 1996, and/or similar international
|
| 98 |
+
agreements.
|
| 99 |
+
|
| 100 |
+
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
| 101 |
+
any other exception or limitation to Copyright and Similar Rights
|
| 102 |
+
that applies to Your use of the Licensed Material.
|
| 103 |
+
|
| 104 |
+
f. Licensed Material means the artistic or literary work, database,
|
| 105 |
+
or other material to which the Licensor applied this Public
|
| 106 |
+
License.
|
| 107 |
+
|
| 108 |
+
g. Licensed Rights means the rights granted to You subject to the
|
| 109 |
+
terms and conditions of this Public License, which are limited to
|
| 110 |
+
all Copyright and Similar Rights that apply to Your use of the
|
| 111 |
+
Licensed Material and that the Licensor has authority to license.
|
| 112 |
+
|
| 113 |
+
h. Licensor means the individual(s) or entity(ies) granting rights
|
| 114 |
+
under this Public License.
|
| 115 |
+
|
| 116 |
+
i. NonCommercial means not primarily intended for or directed towards
|
| 117 |
+
commercial advantage or monetary compensation. For purposes of
|
| 118 |
+
this Public License, the exchange of the Licensed Material for
|
| 119 |
+
other material subject to Copyright and Similar Rights by digital
|
| 120 |
+
file-sharing or similar means is NonCommercial provided there is
|
| 121 |
+
no payment of monetary compensation in connection with the
|
| 122 |
+
exchange.
|
| 123 |
+
|
| 124 |
+
j. Share means to provide material to the public by any means or
|
| 125 |
+
process that requires permission under the Licensed Rights, such
|
| 126 |
+
as reproduction, public display, public performance, distribution,
|
| 127 |
+
dissemination, communication, or importation, and to make material
|
| 128 |
+
available to the public including in ways that members of the
|
| 129 |
+
public may access the material from a place and at a time
|
| 130 |
+
individually chosen by them.
|
| 131 |
+
|
| 132 |
+
k. Sui Generis Database Rights means rights other than copyright
|
| 133 |
+
resulting from Directive 96/9/EC of the European Parliament and of
|
| 134 |
+
the Council of 11 March 1996 on the legal protection of databases,
|
| 135 |
+
as amended and/or succeeded, as well as other essentially
|
| 136 |
+
equivalent rights anywhere in the world.
|
| 137 |
+
|
| 138 |
+
l. You means the individual or entity exercising the Licensed Rights
|
| 139 |
+
under this Public License. Your has a corresponding meaning.
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
Section 2 -- Scope.
|
| 143 |
+
|
| 144 |
+
a. License grant.
|
| 145 |
+
|
| 146 |
+
1. Subject to the terms and conditions of this Public License,
|
| 147 |
+
the Licensor hereby grants You a worldwide, royalty-free,
|
| 148 |
+
non-sublicensable, non-exclusive, irrevocable license to
|
| 149 |
+
exercise the Licensed Rights in the Licensed Material to:
|
| 150 |
+
|
| 151 |
+
a. reproduce and Share the Licensed Material, in whole or
|
| 152 |
+
in part, for NonCommercial purposes only; and
|
| 153 |
+
|
| 154 |
+
b. produce, reproduce, and Share Adapted Material for
|
| 155 |
+
NonCommercial purposes only.
|
| 156 |
+
|
| 157 |
+
2. Exceptions and Limitations. For the avoidance of doubt, where
|
| 158 |
+
Exceptions and Limitations apply to Your use, this Public
|
| 159 |
+
License does not apply, and You do not need to comply with
|
| 160 |
+
its terms and conditions.
|
| 161 |
+
|
| 162 |
+
3. Term. The term of this Public License is specified in Section
|
| 163 |
+
6(a).
|
| 164 |
+
|
| 165 |
+
4. Media and formats; technical modifications allowed. The
|
| 166 |
+
Licensor authorizes You to exercise the Licensed Rights in
|
| 167 |
+
all media and formats whether now known or hereafter created,
|
| 168 |
+
and to make technical modifications necessary to do so. The
|
| 169 |
+
Licensor waives and/or agrees not to assert any right or
|
| 170 |
+
authority to forbid You from making technical modifications
|
| 171 |
+
necessary to exercise the Licensed Rights, including
|
| 172 |
+
technical modifications necessary to circumvent Effective
|
| 173 |
+
Technological Measures. For purposes of this Public License,
|
| 174 |
+
simply making modifications authorized by this Section 2(a)
|
| 175 |
+
(4) never produces Adapted Material.
|
| 176 |
+
|
| 177 |
+
5. Downstream recipients.
|
| 178 |
+
|
| 179 |
+
a. Offer from the Licensor -- Licensed Material. Every
|
| 180 |
+
recipient of the Licensed Material automatically
|
| 181 |
+
receives an offer from the Licensor to exercise the
|
| 182 |
+
Licensed Rights under the terms and conditions of this
|
| 183 |
+
Public License.
|
| 184 |
+
|
| 185 |
+
b. No downstream restrictions. You may not offer or impose
|
| 186 |
+
any additional or different terms or conditions on, or
|
| 187 |
+
apply any Effective Technological Measures to, the
|
| 188 |
+
Licensed Material if doing so restricts exercise of the
|
| 189 |
+
Licensed Rights by any recipient of the Licensed
|
| 190 |
+
Material.
|
| 191 |
+
|
| 192 |
+
6. No endorsement. Nothing in this Public License constitutes or
|
| 193 |
+
may be construed as permission to assert or imply that You
|
| 194 |
+
are, or that Your use of the Licensed Material is, connected
|
| 195 |
+
with, or sponsored, endorsed, or granted official status by,
|
| 196 |
+
the Licensor or others designated to receive attribution as
|
| 197 |
+
provided in Section 3(a)(1)(A)(i).
|
| 198 |
+
|
| 199 |
+
b. Other rights.
|
| 200 |
+
|
| 201 |
+
1. Moral rights, such as the right of integrity, are not
|
| 202 |
+
licensed under this Public License, nor are publicity,
|
| 203 |
+
privacy, and/or other similar personality rights; however, to
|
| 204 |
+
the extent possible, the Licensor waives and/or agrees not to
|
| 205 |
+
assert any such rights held by the Licensor to the limited
|
| 206 |
+
extent necessary to allow You to exercise the Licensed
|
| 207 |
+
Rights, but not otherwise.
|
| 208 |
+
|
| 209 |
+
2. Patent and trademark rights are not licensed under this
|
| 210 |
+
Public License.
|
| 211 |
+
|
| 212 |
+
3. To the extent possible, the Licensor waives any right to
|
| 213 |
+
collect royalties from You for the exercise of the Licensed
|
| 214 |
+
Rights, whether directly or through a collecting society
|
| 215 |
+
under any voluntary or waivable statutory or compulsory
|
| 216 |
+
licensing scheme. In all other cases the Licensor expressly
|
| 217 |
+
reserves any right to collect such royalties, including when
|
| 218 |
+
the Licensed Material is used other than for NonCommercial
|
| 219 |
+
purposes.
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
Section 3 -- License Conditions.
|
| 223 |
+
|
| 224 |
+
Your exercise of the Licensed Rights is expressly made subject to the
|
| 225 |
+
following conditions.
|
| 226 |
+
|
| 227 |
+
a. Attribution.
|
| 228 |
+
|
| 229 |
+
1. If You Share the Licensed Material (including in modified
|
| 230 |
+
form), You must:
|
| 231 |
+
|
| 232 |
+
a. retain the following if it is supplied by the Licensor
|
| 233 |
+
with the Licensed Material:
|
| 234 |
+
|
| 235 |
+
i. identification of the creator(s) of the Licensed
|
| 236 |
+
Material and any others designated to receive
|
| 237 |
+
attribution, in any reasonable manner requested by
|
| 238 |
+
the Licensor (including by pseudonym if
|
| 239 |
+
designated);
|
| 240 |
+
|
| 241 |
+
ii. a copyright notice;
|
| 242 |
+
|
| 243 |
+
iii. a notice that refers to this Public License;
|
| 244 |
+
|
| 245 |
+
iv. a notice that refers to the disclaimer of
|
| 246 |
+
warranties;
|
| 247 |
+
|
| 248 |
+
v. a URI or hyperlink to the Licensed Material to the
|
| 249 |
+
extent reasonably practicable;
|
| 250 |
+
|
| 251 |
+
b. indicate if You modified the Licensed Material and
|
| 252 |
+
retain an indication of any previous modifications; and
|
| 253 |
+
|
| 254 |
+
c. indicate the Licensed Material is licensed under this
|
| 255 |
+
Public License, and include the text of, or the URI or
|
| 256 |
+
hyperlink to, this Public License.
|
| 257 |
+
|
| 258 |
+
2. You may satisfy the conditions in Section 3(a)(1) in any
|
| 259 |
+
reasonable manner based on the medium, means, and context in
|
| 260 |
+
which You Share the Licensed Material. For example, it may be
|
| 261 |
+
reasonable to satisfy the conditions by providing a URI or
|
| 262 |
+
hyperlink to a resource that includes the required
|
| 263 |
+
information.
|
| 264 |
+
|
| 265 |
+
3. If requested by the Licensor, You must remove any of the
|
| 266 |
+
information required by Section 3(a)(1)(A) to the extent
|
| 267 |
+
reasonably practicable.
|
| 268 |
+
|
| 269 |
+
4. If You Share Adapted Material You produce, the Adapter's
|
| 270 |
+
License You apply must not prevent recipients of the Adapted
|
| 271 |
+
Material from complying with this Public License.
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
Section 4 -- Sui Generis Database Rights.
|
| 275 |
+
|
| 276 |
+
Where the Licensed Rights include Sui Generis Database Rights that
|
| 277 |
+
apply to Your use of the Licensed Material:
|
| 278 |
+
|
| 279 |
+
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
| 280 |
+
to extract, reuse, reproduce, and Share all or a substantial
|
| 281 |
+
portion of the contents of the database for NonCommercial purposes
|
| 282 |
+
only;
|
| 283 |
+
|
| 284 |
+
b. if You include all or a substantial portion of the database
|
| 285 |
+
contents in a database in which You have Sui Generis Database
|
| 286 |
+
Rights, then the database in which You have Sui Generis Database
|
| 287 |
+
Rights (but not its individual contents) is Adapted Material; and
|
| 288 |
+
|
| 289 |
+
c. You must comply with the conditions in Section 3(a) if You Share
|
| 290 |
+
all or a substantial portion of the contents of the database.
|
| 291 |
+
|
| 292 |
+
For the avoidance of doubt, this Section 4 supplements and does not
|
| 293 |
+
replace Your obligations under this Public License where the Licensed
|
| 294 |
+
Rights include other Copyright and Similar Rights.
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
| 298 |
+
|
| 299 |
+
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
| 300 |
+
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
| 301 |
+
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
| 302 |
+
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
| 303 |
+
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
| 304 |
+
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
| 305 |
+
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
| 306 |
+
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
| 307 |
+
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
| 308 |
+
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
| 309 |
+
|
| 310 |
+
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
| 311 |
+
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
| 312 |
+
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
| 313 |
+
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
| 314 |
+
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
| 315 |
+
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
| 316 |
+
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
| 317 |
+
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
| 318 |
+
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
| 319 |
+
|
| 320 |
+
c. The disclaimer of warranties and limitation of liability provided
|
| 321 |
+
above shall be interpreted in a manner that, to the extent
|
| 322 |
+
possible, most closely approximates an absolute disclaimer and
|
| 323 |
+
waiver of all liability.
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
Section 6 -- Term and Termination.
|
| 327 |
+
|
| 328 |
+
a. This Public License applies for the term of the Copyright and
|
| 329 |
+
Similar Rights licensed here. However, if You fail to comply with
|
| 330 |
+
this Public License, then Your rights under this Public License
|
| 331 |
+
terminate automatically.
|
| 332 |
+
|
| 333 |
+
b. Where Your right to use the Licensed Material has terminated under
|
| 334 |
+
Section 6(a), it reinstates:
|
| 335 |
+
|
| 336 |
+
1. automatically as of the date the violation is cured, provided
|
| 337 |
+
it is cured within 30 days of Your discovery of the
|
| 338 |
+
violation; or
|
| 339 |
+
|
| 340 |
+
2. upon express reinstatement by the Licensor.
|
| 341 |
+
|
| 342 |
+
For the avoidance of doubt, this Section 6(b) does not affect any
|
| 343 |
+
right the Licensor may have to seek remedies for Your violations
|
| 344 |
+
of this Public License.
|
| 345 |
+
|
| 346 |
+
c. For the avoidance of doubt, the Licensor may also offer the
|
| 347 |
+
Licensed Material under separate terms or conditions or stop
|
| 348 |
+
distributing the Licensed Material at any time; however, doing so
|
| 349 |
+
will not terminate this Public License.
|
| 350 |
+
|
| 351 |
+
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
| 352 |
+
License.
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
Section 7 -- Other Terms and Conditions.
|
| 356 |
+
|
| 357 |
+
a. The Licensor shall not be bound by any additional or different
|
| 358 |
+
terms or conditions communicated by You unless expressly agreed.
|
| 359 |
+
|
| 360 |
+
b. Any arrangements, understandings, or agreements regarding the
|
| 361 |
+
Licensed Material not stated herein are separate from and
|
| 362 |
+
independent of the terms and conditions of this Public License.
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
Section 8 -- Interpretation.
|
| 366 |
+
|
| 367 |
+
a. For the avoidance of doubt, this Public License does not, and
|
| 368 |
+
shall not be interpreted to, reduce, limit, restrict, or impose
|
| 369 |
+
conditions on any use of the Licensed Material that could lawfully
|
| 370 |
+
be made without permission under this Public License.
|
| 371 |
+
|
| 372 |
+
b. To the extent possible, if any provision of this Public License is
|
| 373 |
+
deemed unenforceable, it shall be automatically reformed to the
|
| 374 |
+
minimum extent necessary to make it enforceable. If the provision
|
| 375 |
+
cannot be reformed, it shall be severed from this Public License
|
| 376 |
+
without affecting the enforceability of the remaining terms and
|
| 377 |
+
conditions.
|
| 378 |
+
|
| 379 |
+
c. No term or condition of this Public License will be waived and no
|
| 380 |
+
failure to comply consented to unless expressly agreed to by the
|
| 381 |
+
Licensor.
|
| 382 |
+
|
| 383 |
+
d. Nothing in this Public License constitutes or may be interpreted
|
| 384 |
+
as a limitation upon, or waiver of, any privileges and immunities
|
| 385 |
+
that apply to the Licensor or You, including from the legal
|
| 386 |
+
processes of any jurisdiction or authority.
|
| 387 |
+
|
| 388 |
+
=======================================================================
|
| 389 |
+
|
| 390 |
+
Creative Commons is not a party to its public
|
| 391 |
+
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
| 392 |
+
its public licenses to material it publishes and in those instances
|
| 393 |
+
will be considered the “Licensor.” The text of the Creative Commons
|
| 394 |
+
public licenses is dedicated to the public domain under the CC0 Public
|
| 395 |
+
Domain Dedication. Except for the limited purpose of indicating that
|
| 396 |
+
material is shared under a Creative Commons public license or as
|
| 397 |
+
otherwise permitted by the Creative Commons policies published at
|
| 398 |
+
creativecommons.org/policies, Creative Commons does not authorize the
|
| 399 |
+
use of the trademark "Creative Commons" or any other trademark or logo
|
| 400 |
+
of Creative Commons without its prior written consent including,
|
| 401 |
+
without limitation, in connection with any unauthorized modifications
|
| 402 |
+
to any of its public licenses or any other arrangements,
|
| 403 |
+
understandings, or agreements concerning use of licensed material. For
|
| 404 |
+
the avoidance of doubt, this paragraph does not form part of the
|
| 405 |
+
public licenses.
|
| 406 |
+
|
| 407 |
+
Creative Commons may be contacted at creativecommons.org.
|
| 408 |
+
|
licenses/meta-apache-2.0.txt
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright [yyyy] [name of copyright owner]
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
model/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Artem G.
|
| 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.
|
model/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: mit
|
| 3 |
+
---
|
| 4 |
+
|
| 5 |
+
# Nanobe model package
|
| 6 |
+
|
| 7 |
+
The package provides MLX inference for text, rendered document pages, images, audio and video with sound. It includes the language and multimodal weights, retrieval head, media adapters, document-page merger, audio-window pooler, Perception Encoder audio and text components, tokenizers and Python inference helpers.
|
| 8 |
+
|
| 9 |
+
## Embeddings
|
| 10 |
+
|
| 11 |
+
The active `geometry.json` defines a 2,690-coordinate model embedding. Folder indexing extends it to 2,696 coordinates with the package's calibration. The text scale is 0.94 and the raw acoustic query weight is 0.02. The model has a 640-coordinate retrieval head and a 1,024-coordinate native component, with acoustic features and padding completing the output. Head size is not the final embedding dimension.
|
| 12 |
+
|
| 13 |
+
The model geometry, tensor values, quantization settings, tokenizer settings, prompts, pooling and sampling constants define inference. The model and its helper files must remain together.
|
| 14 |
+
|
| 15 |
+
## Python inference
|
| 16 |
+
|
| 17 |
+
Install the matching source release with `uv sync --frozen` and set `NANOBE_MODEL_DIR` as described in the source README. The runnable `examples/search.py` ranks supplied files using either a text query or a reference file. It uses the same file encoder as the folder CLI.
|
| 18 |
+
|
| 19 |
+
### Text query against a file
|
| 20 |
+
|
| 21 |
+
Run this from the source directory after creating the README's demo files:
|
| 22 |
+
|
| 23 |
+
```python
|
| 24 |
+
import os
|
| 25 |
+
from pathlib import Path
|
| 26 |
+
|
| 27 |
+
import numpy as np
|
| 28 |
+
|
| 29 |
+
from nanobe.inference import encode_file
|
| 30 |
+
from nanobe.runtime import load, load_lanes, load_query
|
| 31 |
+
|
| 32 |
+
package = Path(os.environ["NANOBE_MODEL_DIR"])
|
| 33 |
+
query = load_query(package).encode_query("Saturn through a telescope")
|
| 34 |
+
runtime = load(package)
|
| 35 |
+
calibration = load_lanes(package)
|
| 36 |
+
|
| 37 |
+
kind, segments = encode_file(runtime, Path("demo/observatory.txt"))
|
| 38 |
+
vectors = np.stack([calibration.item(segment.vector, kind) for segment in segments])
|
| 39 |
+
scores = vectors @ query / (np.linalg.norm(vectors, axis=1) * np.linalg.norm(query))
|
| 40 |
+
best = int(np.argmax(scores))
|
| 41 |
+
print(segments[best].label, float(scores[best]))
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
`encode_file` returns the file kind and a list of segments. Every segment has an `ordinal`, `label`, raw `vector` and extracted `lexical` text when available. The raw vector has 2,690 coordinates. `calibration.item` transforms it into a 2,696-coordinate vector with the file kind's scale and extra coordinates. `load_query(...).encode_query` already returns a calibrated 2,696-coordinate query. Do not calibrate that query a second time or compare it directly with raw file vectors.
|
| 45 |
+
|
| 46 |
+
`load_query` loads only the components needed for text queries. `load` loads the complete runtime used by `encode_file`. Reuse these objects when encoding multiple inputs. The full runtime's lower-level `encode_query` method returns 2,690 coordinates; it needs `calibration.query` before comparison with calibrated items.
|
| 47 |
+
|
| 48 |
+
### Reference file as a query
|
| 49 |
+
|
| 50 |
+
A reference file can have multiple segments. Its query vectors use `calibration.query`, while the files being searched still use `calibration.item`:
|
| 51 |
+
|
| 52 |
+
```python
|
| 53 |
+
_, reference = encode_file(runtime, Path("reference.txt"))
|
| 54 |
+
queries = np.stack([calibration.query(segment.vector) for segment in reference])
|
| 55 |
+
scores = queries @ vectors.T
|
| 56 |
+
scores /= np.linalg.norm(queries, axis=1)[:, None] * np.linalg.norm(vectors, axis=1)[None, :]
|
| 57 |
+
query, item = np.unravel_index(np.argmax(scores), scores.shape)
|
| 58 |
+
print(reference[query].label, segments[item].label, float(scores[query, item]))
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
For several files, keep each file's best segment-pair score and sort descending. Exclude the reference file itself when it also appears among the files being searched. `examples/search.py` performs those steps.
|
| 62 |
+
|
| 63 |
+
### Segments and retrieval behavior
|
| 64 |
+
|
| 65 |
+
The shared file encoder applies the documented readers, text chunks, PDF page selection, audio/video windows, audio pooling and DOC/PPT conversion. Replacing the example paths with supported images, documents, recordings or videos uses those same rules. A whole file may return multiple vectors; averaging them changes the retrieval method.
|
| 66 |
+
|
| 67 |
+
These examples perform exact semantic comparisons on full-precision embeddings. The folder CLI stores segment values in float16, uses an int8 approximate index to select candidates, reranks with calibrated stored vectors, and selects the best segment per file. Small numerical differences can follow from storage precision. Text searches also combine semantic and lexical ranks, so their final scores and ordering can differ from pure semantic scoring. Reference-file CLI searches use semantic retrieval only.
|
| 68 |
+
|
| 69 |
+
## File readers
|
| 70 |
+
|
| 71 |
+
The Python reference implementation decodes AVIF and HEIC as RGB images and rasterizes SVG on white within a 2048-by-2048 box. HEIC uses the primary image, applies container orientation and reduces HDR to 8 bits. SVG text depends on installed fonts and the renderer's platform fallback.
|
| 72 |
+
|
| 73 |
+
RTF supplies extracted text. XLS/XLSX supply saved cell values, sheet names and repeated first-row headers without formula recalculation. Workbooks share eight 6,000-character segment slots across the first eight nonempty sheets and report truncation warnings. DOC/PPT require LibreOffice and use converted PDF pages. Nanobe finds LibreOffice on PATH or at its standard macOS application location. Conversion has a 120-second batch timeout.
|
| 74 |
+
|
| 75 |
+
DOCX/PPTX/EPUB supply extracted text. PDF and converted document inputs use the first 24 pages plus up to eight later pages. Audio and video retain whole-file embeddings and bounded additional segments. Missing decoders or converters, empty inputs and damaged files can fail. These readers prepare model inputs; they do not add a separate embedding model. The source README documents the complete format list and limits.
|
| 76 |
+
|
| 77 |
+
## Attribution
|
| 78 |
+
|
| 79 |
+
The base model is `jinaai/jina-embeddings-v5-omni-small-retrieval` at revision `12949877f0092093f366c6450340011320152a05`. Perception Encoder components derive from `facebook/pe-av-small`; the text component records revision `dd050762bb9704ae9cd996ca45532a98f81d817e`. Retained provenance hashes identify upstream inputs and are not runtime configuration.
|
| 80 |
+
|
| 81 |
+
See the accompanying upstream notices. No cloud inference service or upstream embedding-size claim applies to this Nanobe package.
|
| 82 |
+
|
| 83 |
+
## License
|
| 84 |
+
|
| 85 |
+
Nanobe is released under the [MIT License](LICENSE).
|
model/audio-pooler.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7bf8bc6420491454c3bb332bd243244d82efddfbcaa07cbc56dfb7603ab913f9
|
| 3 |
+
size 5251732
|
model/chat_template.jinja
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- set image_count = namespace(value=0) %}
|
| 2 |
+
{%- set video_count = namespace(value=0) %}
|
| 3 |
+
{%- macro render_content(content, do_vision_count, is_system_content=false) %}
|
| 4 |
+
{%- if content is string %}
|
| 5 |
+
{{- content }}
|
| 6 |
+
{%- elif content is iterable and content is not mapping %}
|
| 7 |
+
{%- for item in content %}
|
| 8 |
+
{%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
|
| 9 |
+
{%- if is_system_content %}
|
| 10 |
+
{{- raise_exception('System message cannot contain images.') }}
|
| 11 |
+
{%- endif %}
|
| 12 |
+
{%- if do_vision_count %}
|
| 13 |
+
{%- set image_count.value = image_count.value + 1 %}
|
| 14 |
+
{%- endif %}
|
| 15 |
+
{%- if add_vision_id %}
|
| 16 |
+
{{- 'Picture ' ~ image_count.value ~ ': ' }}
|
| 17 |
+
{%- endif %}
|
| 18 |
+
{{- '<|vision_start|><|image_pad|><|vision_end|>' }}
|
| 19 |
+
{%- elif 'video' in item or item.type == 'video' %}
|
| 20 |
+
{%- if is_system_content %}
|
| 21 |
+
{{- raise_exception('System message cannot contain videos.') }}
|
| 22 |
+
{%- endif %}
|
| 23 |
+
{%- if do_vision_count %}
|
| 24 |
+
{%- set video_count.value = video_count.value + 1 %}
|
| 25 |
+
{%- endif %}
|
| 26 |
+
{%- if add_vision_id %}
|
| 27 |
+
{{- 'Video ' ~ video_count.value ~ ': ' }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{{- '<|vision_start|><|video_pad|><|vision_end|>' }}
|
| 30 |
+
{%- elif 'text' in item %}
|
| 31 |
+
{{- item.text }}
|
| 32 |
+
{%- else %}
|
| 33 |
+
{{- raise_exception('Unexpected item type in content.') }}
|
| 34 |
+
{%- endif %}
|
| 35 |
+
{%- endfor %}
|
| 36 |
+
{%- elif content is none or content is undefined %}
|
| 37 |
+
{{- '' }}
|
| 38 |
+
{%- else %}
|
| 39 |
+
{{- raise_exception('Unexpected content type.') }}
|
| 40 |
+
{%- endif %}
|
| 41 |
+
{%- endmacro %}
|
| 42 |
+
{%- if not messages %}
|
| 43 |
+
{{- raise_exception('No messages provided.') }}
|
| 44 |
+
{%- endif %}
|
| 45 |
+
{%- if tools and tools is iterable and tools is not mapping %}
|
| 46 |
+
{{- '<|im_start|>system\n' }}
|
| 47 |
+
{{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
|
| 48 |
+
{%- for tool in tools %}
|
| 49 |
+
{{- "\n" }}
|
| 50 |
+
{{- tool | tojson }}
|
| 51 |
+
{%- endfor %}
|
| 52 |
+
{{- "\n</tools>" }}
|
| 53 |
+
{{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
|
| 54 |
+
{%- if messages[0].role == 'system' %}
|
| 55 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 56 |
+
{%- if content %}
|
| 57 |
+
{{- '\n\n' + content }}
|
| 58 |
+
{%- endif %}
|
| 59 |
+
{%- endif %}
|
| 60 |
+
{{- '<|im_end|>\n' }}
|
| 61 |
+
{%- else %}
|
| 62 |
+
{%- if messages[0].role == 'system' %}
|
| 63 |
+
{%- set content = render_content(messages[0].content, false, true)|trim %}
|
| 64 |
+
{{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
|
| 65 |
+
{%- endif %}
|
| 66 |
+
{%- endif %}
|
| 67 |
+
{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
|
| 68 |
+
{%- for message in messages[::-1] %}
|
| 69 |
+
{%- set index = (messages|length - 1) - loop.index0 %}
|
| 70 |
+
{%- if ns.multi_step_tool and message.role == "user" %}
|
| 71 |
+
{%- set content = render_content(message.content, false)|trim %}
|
| 72 |
+
{%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
|
| 73 |
+
{%- set ns.multi_step_tool = false %}
|
| 74 |
+
{%- set ns.last_query_index = index %}
|
| 75 |
+
{%- endif %}
|
| 76 |
+
{%- endif %}
|
| 77 |
+
{%- endfor %}
|
| 78 |
+
{%- if ns.multi_step_tool %}
|
| 79 |
+
{{- raise_exception('No user query found in messages.') }}
|
| 80 |
+
{%- endif %}
|
| 81 |
+
{%- for message in messages %}
|
| 82 |
+
{%- set content = render_content(message.content, true)|trim %}
|
| 83 |
+
{%- if message.role == "system" %}
|
| 84 |
+
{%- if not loop.first %}
|
| 85 |
+
{{- raise_exception('System message must be at the beginning.') }}
|
| 86 |
+
{%- endif %}
|
| 87 |
+
{%- elif message.role == "user" %}
|
| 88 |
+
{{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
|
| 89 |
+
{%- elif message.role == "assistant" %}
|
| 90 |
+
{%- set reasoning_content = '' %}
|
| 91 |
+
{%- if message.reasoning_content is string %}
|
| 92 |
+
{%- set reasoning_content = message.reasoning_content %}
|
| 93 |
+
{%- else %}
|
| 94 |
+
{%- if '</think>' in content %}
|
| 95 |
+
{%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
|
| 96 |
+
{%- set content = content.split('</think>')[-1].lstrip('\n') %}
|
| 97 |
+
{%- endif %}
|
| 98 |
+
{%- endif %}
|
| 99 |
+
{%- set reasoning_content = reasoning_content|trim %}
|
| 100 |
+
{%- if loop.index0 > ns.last_query_index %}
|
| 101 |
+
{{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
|
| 102 |
+
{%- else %}
|
| 103 |
+
{{- '<|im_start|>' + message.role + '\n' + content }}
|
| 104 |
+
{%- endif %}
|
| 105 |
+
{%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
|
| 106 |
+
{%- for tool_call in message.tool_calls %}
|
| 107 |
+
{%- if tool_call.function is defined %}
|
| 108 |
+
{%- set tool_call = tool_call.function %}
|
| 109 |
+
{%- endif %}
|
| 110 |
+
{%- if loop.first %}
|
| 111 |
+
{%- if content|trim %}
|
| 112 |
+
{{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 113 |
+
{%- else %}
|
| 114 |
+
{{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 115 |
+
{%- endif %}
|
| 116 |
+
{%- else %}
|
| 117 |
+
{{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
|
| 118 |
+
{%- endif %}
|
| 119 |
+
{%- if tool_call.arguments is defined %}
|
| 120 |
+
{%- for args_name, args_value in tool_call.arguments|items %}
|
| 121 |
+
{{- '<parameter=' + args_name + '>\n' }}
|
| 122 |
+
{%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
|
| 123 |
+
{{- args_value }}
|
| 124 |
+
{{- '\n</parameter>\n' }}
|
| 125 |
+
{%- endfor %}
|
| 126 |
+
{%- endif %}
|
| 127 |
+
{{- '</function>\n</tool_call>' }}
|
| 128 |
+
{%- endfor %}
|
| 129 |
+
{%- endif %}
|
| 130 |
+
{{- '<|im_end|>\n' }}
|
| 131 |
+
{%- elif message.role == "tool" %}
|
| 132 |
+
{%- if loop.previtem and loop.previtem.role != "tool" %}
|
| 133 |
+
{{- '<|im_start|>user' }}
|
| 134 |
+
{%- endif %}
|
| 135 |
+
{{- '\n<tool_response>\n' }}
|
| 136 |
+
{{- content }}
|
| 137 |
+
{{- '\n</tool_response>' }}
|
| 138 |
+
{%- if not loop.last and loop.nextitem.role != "tool" %}
|
| 139 |
+
{{- '<|im_end|>\n' }}
|
| 140 |
+
{%- elif loop.last %}
|
| 141 |
+
{{- '<|im_end|>\n' }}
|
| 142 |
+
{%- endif %}
|
| 143 |
+
{%- else %}
|
| 144 |
+
{{- raise_exception('Unexpected message role.') }}
|
| 145 |
+
{%- endif %}
|
| 146 |
+
{%- endfor %}
|
| 147 |
+
{%- if add_generation_prompt %}
|
| 148 |
+
{{- '<|im_start|>assistant\n' }}
|
| 149 |
+
{%- if enable_thinking is defined and enable_thinking is true %}
|
| 150 |
+
{{- '<think>\n' }}
|
| 151 |
+
{%- else %}
|
| 152 |
+
{{- '<think>\n\n</think>\n\n' }}
|
| 153 |
+
{%- endif %}
|
| 154 |
+
{%- endif %}
|
model/config.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"JinaOmniSmallEmbeddingModel"
|
| 4 |
+
],
|
| 5 |
+
"audio_config": {
|
| 6 |
+
"d_model": 1280,
|
| 7 |
+
"encoder_attention_heads": 20,
|
| 8 |
+
"encoder_ffn_dim": 5120,
|
| 9 |
+
"encoder_layers": 32,
|
| 10 |
+
"max_source_positions": 1500,
|
| 11 |
+
"num_mel_bins": 128,
|
| 12 |
+
"scale_embedding": false,
|
| 13 |
+
"n_window": 100
|
| 14 |
+
},
|
| 15 |
+
"audio_end_token_id": 151671,
|
| 16 |
+
"audio_start_token_id": 151670,
|
| 17 |
+
"audio_token_id": 151669,
|
| 18 |
+
"image_token_id": 151655,
|
| 19 |
+
"model_type": "jina_omni_small_mlx",
|
| 20 |
+
"text_config": {
|
| 21 |
+
"head_dim": 128,
|
| 22 |
+
"hidden_size": 1024,
|
| 23 |
+
"intermediate_size": 3072,
|
| 24 |
+
"max_position_embeddings": 32768,
|
| 25 |
+
"model_type": "",
|
| 26 |
+
"num_attention_heads": 16,
|
| 27 |
+
"num_hidden_layers": 28,
|
| 28 |
+
"num_key_value_heads": 8,
|
| 29 |
+
"rms_norm_eps": 1e-06,
|
| 30 |
+
"rope_parameters": {
|
| 31 |
+
"rope_theta": 3500000,
|
| 32 |
+
"rope_type": "default"
|
| 33 |
+
},
|
| 34 |
+
"vocab_size": 151672
|
| 35 |
+
},
|
| 36 |
+
"transformers_version": "5.4.0",
|
| 37 |
+
"video_token_id": 151656,
|
| 38 |
+
"vision_config": {
|
| 39 |
+
"depth": 24,
|
| 40 |
+
"hidden_size": 1024,
|
| 41 |
+
"in_channels": 3,
|
| 42 |
+
"intermediate_size": 4096,
|
| 43 |
+
"model_type": "",
|
| 44 |
+
"num_heads": 16,
|
| 45 |
+
"num_position_embeddings": 2304,
|
| 46 |
+
"out_hidden_size": 1024,
|
| 47 |
+
"patch_size": 16,
|
| 48 |
+
"spatial_merge_size": 2,
|
| 49 |
+
"temporal_patch_size": 2
|
| 50 |
+
},
|
| 51 |
+
"vision_end_token_id": 151653,
|
| 52 |
+
"vision_start_token_id": 151652,
|
| 53 |
+
"torch_dtype": "bfloat16"
|
| 54 |
+
}
|
model/geometry.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"format": "nanobe-model-geometry-v1",
|
| 3 |
+
"base_model": "jinaai/jina-embeddings-v5-omni-small-retrieval",
|
| 4 |
+
"base_revision": "12949877f0092093f366c6450340011320152a05",
|
| 5 |
+
"head_sha256": "3922c5b7cb892977d61cb3e6caaa025fece854d5be2fba0e047503e1c17f4ccf",
|
| 6 |
+
"native_document_share": 0.485,
|
| 7 |
+
"embedding_dimension": 2690,
|
| 8 |
+
"audio_budget_seconds": 120.0,
|
| 9 |
+
"native_lane_media": [
|
| 10 |
+
"audio",
|
| 11 |
+
"video"
|
| 12 |
+
],
|
| 13 |
+
"video_soundtrack_weight": 1.5,
|
| 14 |
+
"image_projection": {
|
| 15 |
+
"epsilon": 0.2,
|
| 16 |
+
"modality": "image",
|
| 17 |
+
"native_dimensions": 1024,
|
| 18 |
+
"head_dimensions": 640,
|
| 19 |
+
"output_dimensions": 1664,
|
| 20 |
+
"weight": "image_projection.weight",
|
| 21 |
+
"bias": "image_projection.bias"
|
| 22 |
+
},
|
| 23 |
+
"audio_projection": {
|
| 24 |
+
"encoder": "pe-audio",
|
| 25 |
+
"encoder_sha256": "790f80f549c70cb327b30c537017ca2ae6f47781b219ce8cd35f0144b1e058d7",
|
| 26 |
+
"weight": "audio_projection.weight",
|
| 27 |
+
"bias": "audio_projection.bias",
|
| 28 |
+
"share": 0.2
|
| 29 |
+
},
|
| 30 |
+
"base_embedding_dimension": 1664,
|
| 31 |
+
"audio_magnitude": {
|
| 32 |
+
"u": 0.9900000095367432,
|
| 33 |
+
"v": 0.02,
|
| 34 |
+
"text_encoder": "pe-text",
|
| 35 |
+
"text_encoder_sha256": "97ad081b8044cb4308995a5d2d93d36bdec39a7ccfcf01e6591b7a5fd6b0eeff",
|
| 36 |
+
"helper": "magnitude.py",
|
| 37 |
+
"helper_sha256": "93f267d198dcd0755134e86011d85d78af4fd5d61b818f98f8202cab1684930e",
|
| 38 |
+
"raw_cap": 8.0,
|
| 39 |
+
"padding_radius": 8.00001,
|
| 40 |
+
"output_dimensions": 2690
|
| 41 |
+
},
|
| 42 |
+
"index_dimension": 2696,
|
| 43 |
+
"lane_calibration": {
|
| 44 |
+
"helper": "lanes.py",
|
| 45 |
+
"helper_sha256": "b85311d9b5fe5d593d4b04730eeb66af11d39d942b393ee57e63ab3062510320",
|
| 46 |
+
"scales": {
|
| 47 |
+
"text": 0.94,
|
| 48 |
+
"pdf": 1.0,
|
| 49 |
+
"image": 1.0,
|
| 50 |
+
"audio": 1.0,
|
| 51 |
+
"video": 1.0
|
| 52 |
+
},
|
| 53 |
+
"tau": 0.1,
|
| 54 |
+
"delta": 0.0,
|
| 55 |
+
"radius": 1.004987562112089,
|
| 56 |
+
"block": 6,
|
| 57 |
+
"modalities": [
|
| 58 |
+
"text",
|
| 59 |
+
"pdf",
|
| 60 |
+
"image",
|
| 61 |
+
"audio",
|
| 62 |
+
"video"
|
| 63 |
+
],
|
| 64 |
+
"preference": null,
|
| 65 |
+
"output_dimensions": 2696
|
| 66 |
+
}
|
| 67 |
+
}
|
model/head/head.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architecture": "Asymmetric 1024-to-640 retrieval head with additive audio projection",
|
| 3 |
+
"input_dim": 1024,
|
| 4 |
+
"output_dim": 640,
|
| 5 |
+
"modalities": [
|
| 6 |
+
"text",
|
| 7 |
+
"image",
|
| 8 |
+
"document",
|
| 9 |
+
"audio",
|
| 10 |
+
"video",
|
| 11 |
+
"interleaved",
|
| 12 |
+
"unknown"
|
| 13 |
+
],
|
| 14 |
+
"image_projection": {
|
| 15 |
+
"epsilon": 0.2,
|
| 16 |
+
"modality": "image",
|
| 17 |
+
"native_dimensions": 1024,
|
| 18 |
+
"head_dimensions": 640,
|
| 19 |
+
"output_dimensions": 1664,
|
| 20 |
+
"weight": "image_projection.weight",
|
| 21 |
+
"bias": "image_projection.bias",
|
| 22 |
+
"parameters": 1049600
|
| 23 |
+
},
|
| 24 |
+
"audio_projection": {
|
| 25 |
+
"encoder": "pe-audio",
|
| 26 |
+
"encoder_sha256": "790f80f549c70cb327b30c537017ca2ae6f47781b219ce8cd35f0144b1e058d7",
|
| 27 |
+
"weight": "audio_projection.weight",
|
| 28 |
+
"bias": "audio_projection.bias",
|
| 29 |
+
"share": 0.2,
|
| 30 |
+
"parameters": 1049600
|
| 31 |
+
}
|
| 32 |
+
}
|
model/head/head.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3922c5b7cb892977d61cb3e6caaa025fece854d5be2fba0e047503e1c17f4ccf
|
| 3 |
+
size 16366128
|
model/lanes.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Per-modality calibration with six extra coordinates and unit document vectors."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
MODALITIES = ("text", "pdf", "image", "audio", "video")
|
| 8 |
+
BLOCK = len(MODALITIES) + 1
|
| 9 |
+
CATALOG_MODALITIES = {"text": "text", "document": "pdf", "image": "image", "audio": "audio", "video": "video"}
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
class Calibration:
|
| 13 |
+
def __init__(self, scales: dict[str, float], tau: float, delta: float, dimension: int):
|
| 14 |
+
self.scales = {name: float(scales[name]) for name in MODALITIES}
|
| 15 |
+
self.tau = float(tau)
|
| 16 |
+
self.delta = float(delta)
|
| 17 |
+
self.dimension = int(dimension)
|
| 18 |
+
self.radius = float(np.sqrt(max(self.scales.values()) ** 2 + self.tau ** 2))
|
| 19 |
+
if not all(0 < s <= 1 for s in self.scales.values()) or not 0 < self.tau < 1 or self.delta != 0:
|
| 20 |
+
raise ValueError("Lane calibration needs scales in (0, 1], tau in (0, 1) and delta = 0")
|
| 21 |
+
|
| 22 |
+
@property
|
| 23 |
+
def output_dimension(self) -> int:
|
| 24 |
+
return self.dimension + BLOCK
|
| 25 |
+
|
| 26 |
+
def item(self, vector: np.ndarray, modality: str) -> np.ndarray:
|
| 27 |
+
"""Extend one unit segment vector of a file of the specified modality."""
|
| 28 |
+
name = CATALOG_MODALITIES.get(modality, modality)
|
| 29 |
+
scale = self.scales[name]
|
| 30 |
+
values = np.asarray(vector, dtype=np.float32).reshape(-1)
|
| 31 |
+
if values.shape != (self.dimension,):
|
| 32 |
+
raise ValueError(f"segment vector must have {self.dimension} coordinates")
|
| 33 |
+
block = np.zeros(BLOCK, dtype=np.float32)
|
| 34 |
+
block[MODALITIES.index(name)] = self.tau
|
| 35 |
+
block[-1] = np.sqrt(max(0.0, self.radius ** 2 - scale ** 2 - self.tau ** 2))
|
| 36 |
+
return np.concatenate([scale * values, block]) / self.radius
|
| 37 |
+
|
| 38 |
+
def query(self, vector: np.ndarray) -> np.ndarray:
|
| 39 |
+
"""Append the six zero coordinates used by query vectors."""
|
| 40 |
+
values = np.asarray(vector, dtype=np.float32).reshape(-1)
|
| 41 |
+
if values.shape != (self.dimension,):
|
| 42 |
+
raise ValueError(f"query vector must have {self.dimension} coordinates")
|
| 43 |
+
block = np.zeros(BLOCK, dtype=np.float32)
|
| 44 |
+
return np.concatenate([values, block])
|
model/language.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:fea1441e6a0e56aba49d3cf2d0b92b36ee8142b8f98ed54b0e3bd30b1e909702
|
| 3 |
+
size 631565822
|
model/licenses/jina-cc-by-nc-4.0.txt
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Attribution-NonCommercial 4.0 International
|
| 2 |
+
|
| 3 |
+
=======================================================================
|
| 4 |
+
|
| 5 |
+
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
| 6 |
+
does not provide legal services or legal advice. Distribution of
|
| 7 |
+
Creative Commons public licenses does not create a lawyer-client or
|
| 8 |
+
other relationship. Creative Commons makes its licenses and related
|
| 9 |
+
information available on an "as-is" basis. Creative Commons gives no
|
| 10 |
+
warranties regarding its licenses, any material licensed under their
|
| 11 |
+
terms and conditions, or any related information. Creative Commons
|
| 12 |
+
disclaims all liability for damages resulting from their use to the
|
| 13 |
+
fullest extent possible.
|
| 14 |
+
|
| 15 |
+
Using Creative Commons Public Licenses
|
| 16 |
+
|
| 17 |
+
Creative Commons public licenses provide a standard set of terms and
|
| 18 |
+
conditions that creators and other rights holders may use to share
|
| 19 |
+
original works of authorship and other material subject to copyright
|
| 20 |
+
and certain other rights specified in the public license below. The
|
| 21 |
+
following considerations are for informational purposes only, are not
|
| 22 |
+
exhaustive, and do not form part of our licenses.
|
| 23 |
+
|
| 24 |
+
Considerations for licensors: Our public licenses are
|
| 25 |
+
intended for use by those authorized to give the public
|
| 26 |
+
permission to use material in ways otherwise restricted by
|
| 27 |
+
copyright and certain other rights. Our licenses are
|
| 28 |
+
irrevocable. Licensors should read and understand the terms
|
| 29 |
+
and conditions of the license they choose before applying it.
|
| 30 |
+
Licensors should also secure all rights necessary before
|
| 31 |
+
applying our licenses so that the public can reuse the
|
| 32 |
+
material as expected. Licensors should clearly mark any
|
| 33 |
+
material not subject to the license. This includes other CC-
|
| 34 |
+
licensed material, or material used under an exception or
|
| 35 |
+
limitation to copyright. More considerations for licensors:
|
| 36 |
+
wiki.creativecommons.org/Considerations_for_licensors
|
| 37 |
+
|
| 38 |
+
Considerations for the public: By using one of our public
|
| 39 |
+
licenses, a licensor grants the public permission to use the
|
| 40 |
+
licensed material under specified terms and conditions. If
|
| 41 |
+
the licensor's permission is not necessary for any reason--for
|
| 42 |
+
example, because of any applicable exception or limitation to
|
| 43 |
+
copyright--then that use is not regulated by the license. Our
|
| 44 |
+
licenses grant only permissions under copyright and certain
|
| 45 |
+
other rights that a licensor has authority to grant. Use of
|
| 46 |
+
the licensed material may still be restricted for other
|
| 47 |
+
reasons, including because others have copyright or other
|
| 48 |
+
rights in the material. A licensor may make special requests,
|
| 49 |
+
such as asking that all changes be marked or described.
|
| 50 |
+
Although not required by our licenses, you are encouraged to
|
| 51 |
+
respect those requests where reasonable. More considerations
|
| 52 |
+
for the public:
|
| 53 |
+
wiki.creativecommons.org/Considerations_for_licensees
|
| 54 |
+
|
| 55 |
+
=======================================================================
|
| 56 |
+
|
| 57 |
+
Creative Commons Attribution-NonCommercial 4.0 International Public
|
| 58 |
+
License
|
| 59 |
+
|
| 60 |
+
By exercising the Licensed Rights (defined below), You accept and agree
|
| 61 |
+
to be bound by the terms and conditions of this Creative Commons
|
| 62 |
+
Attribution-NonCommercial 4.0 International Public License ("Public
|
| 63 |
+
License"). To the extent this Public License may be interpreted as a
|
| 64 |
+
contract, You are granted the Licensed Rights in consideration of Your
|
| 65 |
+
acceptance of these terms and conditions, and the Licensor grants You
|
| 66 |
+
such rights in consideration of benefits the Licensor receives from
|
| 67 |
+
making the Licensed Material available under these terms and
|
| 68 |
+
conditions.
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
Section 1 -- Definitions.
|
| 72 |
+
|
| 73 |
+
a. Adapted Material means material subject to Copyright and Similar
|
| 74 |
+
Rights that is derived from or based upon the Licensed Material
|
| 75 |
+
and in which the Licensed Material is translated, altered,
|
| 76 |
+
arranged, transformed, or otherwise modified in a manner requiring
|
| 77 |
+
permission under the Copyright and Similar Rights held by the
|
| 78 |
+
Licensor. For purposes of this Public License, where the Licensed
|
| 79 |
+
Material is a musical work, performance, or sound recording,
|
| 80 |
+
Adapted Material is always produced where the Licensed Material is
|
| 81 |
+
synched in timed relation with a moving image.
|
| 82 |
+
|
| 83 |
+
b. Adapter's License means the license You apply to Your Copyright
|
| 84 |
+
and Similar Rights in Your contributions to Adapted Material in
|
| 85 |
+
accordance with the terms and conditions of this Public License.
|
| 86 |
+
|
| 87 |
+
c. Copyright and Similar Rights means copyright and/or similar rights
|
| 88 |
+
closely related to copyright including, without limitation,
|
| 89 |
+
performance, broadcast, sound recording, and Sui Generis Database
|
| 90 |
+
Rights, without regard to how the rights are labeled or
|
| 91 |
+
categorized. For purposes of this Public License, the rights
|
| 92 |
+
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
| 93 |
+
Rights.
|
| 94 |
+
d. Effective Technological Measures means those measures that, in the
|
| 95 |
+
absence of proper authority, may not be circumvented under laws
|
| 96 |
+
fulfilling obligations under Article 11 of the WIPO Copyright
|
| 97 |
+
Treaty adopted on December 20, 1996, and/or similar international
|
| 98 |
+
agreements.
|
| 99 |
+
|
| 100 |
+
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
| 101 |
+
any other exception or limitation to Copyright and Similar Rights
|
| 102 |
+
that applies to Your use of the Licensed Material.
|
| 103 |
+
|
| 104 |
+
f. Licensed Material means the artistic or literary work, database,
|
| 105 |
+
or other material to which the Licensor applied this Public
|
| 106 |
+
License.
|
| 107 |
+
|
| 108 |
+
g. Licensed Rights means the rights granted to You subject to the
|
| 109 |
+
terms and conditions of this Public License, which are limited to
|
| 110 |
+
all Copyright and Similar Rights that apply to Your use of the
|
| 111 |
+
Licensed Material and that the Licensor has authority to license.
|
| 112 |
+
|
| 113 |
+
h. Licensor means the individual(s) or entity(ies) granting rights
|
| 114 |
+
under this Public License.
|
| 115 |
+
|
| 116 |
+
i. NonCommercial means not primarily intended for or directed towards
|
| 117 |
+
commercial advantage or monetary compensation. For purposes of
|
| 118 |
+
this Public License, the exchange of the Licensed Material for
|
| 119 |
+
other material subject to Copyright and Similar Rights by digital
|
| 120 |
+
file-sharing or similar means is NonCommercial provided there is
|
| 121 |
+
no payment of monetary compensation in connection with the
|
| 122 |
+
exchange.
|
| 123 |
+
|
| 124 |
+
j. Share means to provide material to the public by any means or
|
| 125 |
+
process that requires permission under the Licensed Rights, such
|
| 126 |
+
as reproduction, public display, public performance, distribution,
|
| 127 |
+
dissemination, communication, or importation, and to make material
|
| 128 |
+
available to the public including in ways that members of the
|
| 129 |
+
public may access the material from a place and at a time
|
| 130 |
+
individually chosen by them.
|
| 131 |
+
|
| 132 |
+
k. Sui Generis Database Rights means rights other than copyright
|
| 133 |
+
resulting from Directive 96/9/EC of the European Parliament and of
|
| 134 |
+
the Council of 11 March 1996 on the legal protection of databases,
|
| 135 |
+
as amended and/or succeeded, as well as other essentially
|
| 136 |
+
equivalent rights anywhere in the world.
|
| 137 |
+
|
| 138 |
+
l. You means the individual or entity exercising the Licensed Rights
|
| 139 |
+
under this Public License. Your has a corresponding meaning.
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
Section 2 -- Scope.
|
| 143 |
+
|
| 144 |
+
a. License grant.
|
| 145 |
+
|
| 146 |
+
1. Subject to the terms and conditions of this Public License,
|
| 147 |
+
the Licensor hereby grants You a worldwide, royalty-free,
|
| 148 |
+
non-sublicensable, non-exclusive, irrevocable license to
|
| 149 |
+
exercise the Licensed Rights in the Licensed Material to:
|
| 150 |
+
|
| 151 |
+
a. reproduce and Share the Licensed Material, in whole or
|
| 152 |
+
in part, for NonCommercial purposes only; and
|
| 153 |
+
|
| 154 |
+
b. produce, reproduce, and Share Adapted Material for
|
| 155 |
+
NonCommercial purposes only.
|
| 156 |
+
|
| 157 |
+
2. Exceptions and Limitations. For the avoidance of doubt, where
|
| 158 |
+
Exceptions and Limitations apply to Your use, this Public
|
| 159 |
+
License does not apply, and You do not need to comply with
|
| 160 |
+
its terms and conditions.
|
| 161 |
+
|
| 162 |
+
3. Term. The term of this Public License is specified in Section
|
| 163 |
+
6(a).
|
| 164 |
+
|
| 165 |
+
4. Media and formats; technical modifications allowed. The
|
| 166 |
+
Licensor authorizes You to exercise the Licensed Rights in
|
| 167 |
+
all media and formats whether now known or hereafter created,
|
| 168 |
+
and to make technical modifications necessary to do so. The
|
| 169 |
+
Licensor waives and/or agrees not to assert any right or
|
| 170 |
+
authority to forbid You from making technical modifications
|
| 171 |
+
necessary to exercise the Licensed Rights, including
|
| 172 |
+
technical modifications necessary to circumvent Effective
|
| 173 |
+
Technological Measures. For purposes of this Public License,
|
| 174 |
+
simply making modifications authorized by this Section 2(a)
|
| 175 |
+
(4) never produces Adapted Material.
|
| 176 |
+
|
| 177 |
+
5. Downstream recipients.
|
| 178 |
+
|
| 179 |
+
a. Offer from the Licensor -- Licensed Material. Every
|
| 180 |
+
recipient of the Licensed Material automatically
|
| 181 |
+
receives an offer from the Licensor to exercise the
|
| 182 |
+
Licensed Rights under the terms and conditions of this
|
| 183 |
+
Public License.
|
| 184 |
+
|
| 185 |
+
b. No downstream restrictions. You may not offer or impose
|
| 186 |
+
any additional or different terms or conditions on, or
|
| 187 |
+
apply any Effective Technological Measures to, the
|
| 188 |
+
Licensed Material if doing so restricts exercise of the
|
| 189 |
+
Licensed Rights by any recipient of the Licensed
|
| 190 |
+
Material.
|
| 191 |
+
|
| 192 |
+
6. No endorsement. Nothing in this Public License constitutes or
|
| 193 |
+
may be construed as permission to assert or imply that You
|
| 194 |
+
are, or that Your use of the Licensed Material is, connected
|
| 195 |
+
with, or sponsored, endorsed, or granted official status by,
|
| 196 |
+
the Licensor or others designated to receive attribution as
|
| 197 |
+
provided in Section 3(a)(1)(A)(i).
|
| 198 |
+
|
| 199 |
+
b. Other rights.
|
| 200 |
+
|
| 201 |
+
1. Moral rights, such as the right of integrity, are not
|
| 202 |
+
licensed under this Public License, nor are publicity,
|
| 203 |
+
privacy, and/or other similar personality rights; however, to
|
| 204 |
+
the extent possible, the Licensor waives and/or agrees not to
|
| 205 |
+
assert any such rights held by the Licensor to the limited
|
| 206 |
+
extent necessary to allow You to exercise the Licensed
|
| 207 |
+
Rights, but not otherwise.
|
| 208 |
+
|
| 209 |
+
2. Patent and trademark rights are not licensed under this
|
| 210 |
+
Public License.
|
| 211 |
+
|
| 212 |
+
3. To the extent possible, the Licensor waives any right to
|
| 213 |
+
collect royalties from You for the exercise of the Licensed
|
| 214 |
+
Rights, whether directly or through a collecting society
|
| 215 |
+
under any voluntary or waivable statutory or compulsory
|
| 216 |
+
licensing scheme. In all other cases the Licensor expressly
|
| 217 |
+
reserves any right to collect such royalties, including when
|
| 218 |
+
the Licensed Material is used other than for NonCommercial
|
| 219 |
+
purposes.
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
Section 3 -- License Conditions.
|
| 223 |
+
|
| 224 |
+
Your exercise of the Licensed Rights is expressly made subject to the
|
| 225 |
+
following conditions.
|
| 226 |
+
|
| 227 |
+
a. Attribution.
|
| 228 |
+
|
| 229 |
+
1. If You Share the Licensed Material (including in modified
|
| 230 |
+
form), You must:
|
| 231 |
+
|
| 232 |
+
a. retain the following if it is supplied by the Licensor
|
| 233 |
+
with the Licensed Material:
|
| 234 |
+
|
| 235 |
+
i. identification of the creator(s) of the Licensed
|
| 236 |
+
Material and any others designated to receive
|
| 237 |
+
attribution, in any reasonable manner requested by
|
| 238 |
+
the Licensor (including by pseudonym if
|
| 239 |
+
designated);
|
| 240 |
+
|
| 241 |
+
ii. a copyright notice;
|
| 242 |
+
|
| 243 |
+
iii. a notice that refers to this Public License;
|
| 244 |
+
|
| 245 |
+
iv. a notice that refers to the disclaimer of
|
| 246 |
+
warranties;
|
| 247 |
+
|
| 248 |
+
v. a URI or hyperlink to the Licensed Material to the
|
| 249 |
+
extent reasonably practicable;
|
| 250 |
+
|
| 251 |
+
b. indicate if You modified the Licensed Material and
|
| 252 |
+
retain an indication of any previous modifications; and
|
| 253 |
+
|
| 254 |
+
c. indicate the Licensed Material is licensed under this
|
| 255 |
+
Public License, and include the text of, or the URI or
|
| 256 |
+
hyperlink to, this Public License.
|
| 257 |
+
|
| 258 |
+
2. You may satisfy the conditions in Section 3(a)(1) in any
|
| 259 |
+
reasonable manner based on the medium, means, and context in
|
| 260 |
+
which You Share the Licensed Material. For example, it may be
|
| 261 |
+
reasonable to satisfy the conditions by providing a URI or
|
| 262 |
+
hyperlink to a resource that includes the required
|
| 263 |
+
information.
|
| 264 |
+
|
| 265 |
+
3. If requested by the Licensor, You must remove any of the
|
| 266 |
+
information required by Section 3(a)(1)(A) to the extent
|
| 267 |
+
reasonably practicable.
|
| 268 |
+
|
| 269 |
+
4. If You Share Adapted Material You produce, the Adapter's
|
| 270 |
+
License You apply must not prevent recipients of the Adapted
|
| 271 |
+
Material from complying with this Public License.
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
Section 4 -- Sui Generis Database Rights.
|
| 275 |
+
|
| 276 |
+
Where the Licensed Rights include Sui Generis Database Rights that
|
| 277 |
+
apply to Your use of the Licensed Material:
|
| 278 |
+
|
| 279 |
+
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
| 280 |
+
to extract, reuse, reproduce, and Share all or a substantial
|
| 281 |
+
portion of the contents of the database for NonCommercial purposes
|
| 282 |
+
only;
|
| 283 |
+
|
| 284 |
+
b. if You include all or a substantial portion of the database
|
| 285 |
+
contents in a database in which You have Sui Generis Database
|
| 286 |
+
Rights, then the database in which You have Sui Generis Database
|
| 287 |
+
Rights (but not its individual contents) is Adapted Material; and
|
| 288 |
+
|
| 289 |
+
c. You must comply with the conditions in Section 3(a) if You Share
|
| 290 |
+
all or a substantial portion of the contents of the database.
|
| 291 |
+
|
| 292 |
+
For the avoidance of doubt, this Section 4 supplements and does not
|
| 293 |
+
replace Your obligations under this Public License where the Licensed
|
| 294 |
+
Rights include other Copyright and Similar Rights.
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
| 298 |
+
|
| 299 |
+
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
| 300 |
+
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
| 301 |
+
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
| 302 |
+
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
| 303 |
+
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
| 304 |
+
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
| 305 |
+
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
| 306 |
+
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
| 307 |
+
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
| 308 |
+
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
| 309 |
+
|
| 310 |
+
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
| 311 |
+
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
| 312 |
+
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
| 313 |
+
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
| 314 |
+
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
| 315 |
+
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
| 316 |
+
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
| 317 |
+
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
| 318 |
+
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
| 319 |
+
|
| 320 |
+
c. The disclaimer of warranties and limitation of liability provided
|
| 321 |
+
above shall be interpreted in a manner that, to the extent
|
| 322 |
+
possible, most closely approximates an absolute disclaimer and
|
| 323 |
+
waiver of all liability.
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
Section 6 -- Term and Termination.
|
| 327 |
+
|
| 328 |
+
a. This Public License applies for the term of the Copyright and
|
| 329 |
+
Similar Rights licensed here. However, if You fail to comply with
|
| 330 |
+
this Public License, then Your rights under this Public License
|
| 331 |
+
terminate automatically.
|
| 332 |
+
|
| 333 |
+
b. Where Your right to use the Licensed Material has terminated under
|
| 334 |
+
Section 6(a), it reinstates:
|
| 335 |
+
|
| 336 |
+
1. automatically as of the date the violation is cured, provided
|
| 337 |
+
it is cured within 30 days of Your discovery of the
|
| 338 |
+
violation; or
|
| 339 |
+
|
| 340 |
+
2. upon express reinstatement by the Licensor.
|
| 341 |
+
|
| 342 |
+
For the avoidance of doubt, this Section 6(b) does not affect any
|
| 343 |
+
right the Licensor may have to seek remedies for Your violations
|
| 344 |
+
of this Public License.
|
| 345 |
+
|
| 346 |
+
c. For the avoidance of doubt, the Licensor may also offer the
|
| 347 |
+
Licensed Material under separate terms or conditions or stop
|
| 348 |
+
distributing the Licensed Material at any time; however, doing so
|
| 349 |
+
will not terminate this Public License.
|
| 350 |
+
|
| 351 |
+
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
| 352 |
+
License.
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
Section 7 -- Other Terms and Conditions.
|
| 356 |
+
|
| 357 |
+
a. The Licensor shall not be bound by any additional or different
|
| 358 |
+
terms or conditions communicated by You unless expressly agreed.
|
| 359 |
+
|
| 360 |
+
b. Any arrangements, understandings, or agreements regarding the
|
| 361 |
+
Licensed Material not stated herein are separate from and
|
| 362 |
+
independent of the terms and conditions of this Public License.
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
Section 8 -- Interpretation.
|
| 366 |
+
|
| 367 |
+
a. For the avoidance of doubt, this Public License does not, and
|
| 368 |
+
shall not be interpreted to, reduce, limit, restrict, or impose
|
| 369 |
+
conditions on any use of the Licensed Material that could lawfully
|
| 370 |
+
be made without permission under this Public License.
|
| 371 |
+
|
| 372 |
+
b. To the extent possible, if any provision of this Public License is
|
| 373 |
+
deemed unenforceable, it shall be automatically reformed to the
|
| 374 |
+
minimum extent necessary to make it enforceable. If the provision
|
| 375 |
+
cannot be reformed, it shall be severed from this Public License
|
| 376 |
+
without affecting the enforceability of the remaining terms and
|
| 377 |
+
conditions.
|
| 378 |
+
|
| 379 |
+
c. No term or condition of this Public License will be waived and no
|
| 380 |
+
failure to comply consented to unless expressly agreed to by the
|
| 381 |
+
Licensor.
|
| 382 |
+
|
| 383 |
+
d. Nothing in this Public License constitutes or may be interpreted
|
| 384 |
+
as a limitation upon, or waiver of, any privileges and immunities
|
| 385 |
+
that apply to the Licensor or You, including from the legal
|
| 386 |
+
processes of any jurisdiction or authority.
|
| 387 |
+
|
| 388 |
+
=======================================================================
|
| 389 |
+
|
| 390 |
+
Creative Commons is not a party to its public
|
| 391 |
+
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
| 392 |
+
its public licenses to material it publishes and in those instances
|
| 393 |
+
will be considered the “Licensor.” The text of the Creative Commons
|
| 394 |
+
public licenses is dedicated to the public domain under the CC0 Public
|
| 395 |
+
Domain Dedication. Except for the limited purpose of indicating that
|
| 396 |
+
material is shared under a Creative Commons public license or as
|
| 397 |
+
otherwise permitted by the Creative Commons policies published at
|
| 398 |
+
creativecommons.org/policies, Creative Commons does not authorize the
|
| 399 |
+
use of the trademark "Creative Commons" or any other trademark or logo
|
| 400 |
+
of Creative Commons without its prior written consent including,
|
| 401 |
+
without limitation, in connection with any unauthorized modifications
|
| 402 |
+
to any of its public licenses or any other arrangements,
|
| 403 |
+
understandings, or agreements concerning use of licensed material. For
|
| 404 |
+
the avoidance of doubt, this paragraph does not form part of the
|
| 405 |
+
public licenses.
|
| 406 |
+
|
| 407 |
+
Creative Commons may be contacted at creativecommons.org.
|
| 408 |
+
|
model/licenses/meta-apache-2.0.txt
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright [yyyy] [name of copyright owner]
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
model/magnitude.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Unit embeddings with a fixed radial constraint on native acoustic features."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
CURRENT_DIMENSION = 1664
|
| 9 |
+
RAW_DIMENSION = 1024
|
| 10 |
+
DIMENSION = CURRENT_DIMENSION + RAW_DIMENSION + 2
|
| 11 |
+
RAW_CAP = 8.0
|
| 12 |
+
PADDING_RADIUS = 8.00001
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def matrix(vectors: np.ndarray, dimension: int, name: str) -> np.ndarray:
|
| 16 |
+
values = np.asarray(vectors, dtype=np.float64)
|
| 17 |
+
if values.ndim != 2 or values.shape[1] != dimension or not np.isfinite(values).all():
|
| 18 |
+
raise ValueError(f"{name} embeddings require {dimension} finite coordinates per row")
|
| 19 |
+
return values
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def unit(vectors: np.ndarray) -> np.ndarray:
|
| 23 |
+
values = matrix(vectors, CURRENT_DIMENSION, "Current")
|
| 24 |
+
peaks = np.max(np.abs(values), axis=1, keepdims=True)
|
| 25 |
+
if np.any(peaks == 0):
|
| 26 |
+
raise ValueError("Current embeddings must have nonzero magnitude")
|
| 27 |
+
scaled = values / peaks
|
| 28 |
+
return scaled / np.linalg.norm(scaled, axis=1, keepdims=True)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def clip(vectors: np.ndarray) -> np.ndarray:
|
| 32 |
+
values = matrix(vectors, RAW_DIMENSION, "Raw PE")
|
| 33 |
+
peaks = np.max(np.abs(values), axis=1, keepdims=True)
|
| 34 |
+
# Scale before taking the norm so every finite input can be clipped in FP64.
|
| 35 |
+
scaled = values / np.maximum(1.0, peaks / RAW_CAP)
|
| 36 |
+
norms = np.linalg.norm(scaled, axis=1, keepdims=True)
|
| 37 |
+
return scaled * (RAW_CAP / np.maximum(RAW_CAP, norms))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class Geometry:
|
| 41 |
+
def __init__(self, u: float, v: float):
|
| 42 |
+
self.u = float(u)
|
| 43 |
+
self.v = float(v)
|
| 44 |
+
if not np.isfinite((self.u, self.v)).all() or not .001 <= self.u <= .9999 or self.v < 0:
|
| 45 |
+
raise ValueError("Calibration requires finite .001 <= u <= .9999 and v >= 0")
|
| 46 |
+
self.beta = float(np.sqrt(self.u ** -2 - 1) / PADDING_RADIUS)
|
| 47 |
+
self.alpha = self.v / (self.u * self.beta)
|
| 48 |
+
self.query_divisor = float(np.hypot(1.0, self.alpha * PADDING_RADIUS))
|
| 49 |
+
self.audio_divisor = float(np.hypot(1.0, self.beta * PADDING_RADIUS))
|
| 50 |
+
if not np.isfinite(tuple(self.coefficients().values())).all():
|
| 51 |
+
raise ValueError("Calibration produces nonfinite lift coefficients")
|
| 52 |
+
|
| 53 |
+
def coefficients(self) -> dict[str, float]:
|
| 54 |
+
return {"u": self.u, "v": self.v, "alpha": self.alpha, "beta": self.beta,
|
| 55 |
+
"query_divisor": self.query_divisor, "audio_divisor": self.audio_divisor}
|
| 56 |
+
|
| 57 |
+
def _lift(self, vectors: np.ndarray, raw: np.ndarray, scale: float,
|
| 58 |
+
divisor: float, padding: int) -> np.ndarray:
|
| 59 |
+
original = unit(vectors)
|
| 60 |
+
acoustic = clip(raw)
|
| 61 |
+
if len(acoustic) != len(original):
|
| 62 |
+
raise ValueError("Every current embedding requires its own raw PE feature")
|
| 63 |
+
squared = PADDING_RADIUS ** 2 - np.square(acoustic).sum(axis=1)
|
| 64 |
+
if np.any(squared < 0):
|
| 65 |
+
raise ValueError("Clipped PE features exceed the fixed padding radius")
|
| 66 |
+
result = np.zeros((len(original), DIMENSION), dtype=np.float64)
|
| 67 |
+
result[:, :CURRENT_DIMENSION] = original
|
| 68 |
+
result[:, CURRENT_DIMENSION:-2] = scale * acoustic
|
| 69 |
+
result[:, -2 + padding] = scale * np.sqrt(squared)
|
| 70 |
+
result /= divisor
|
| 71 |
+
return result.astype(np.float32)
|
| 72 |
+
|
| 73 |
+
def query(self, current: np.ndarray, raw: np.ndarray) -> np.ndarray:
|
| 74 |
+
return self._lift(current, raw, self.alpha, self.query_divisor, 0)
|
| 75 |
+
|
| 76 |
+
def audio(self, current: np.ndarray, raw: np.ndarray) -> np.ndarray:
|
| 77 |
+
return self._lift(current, raw, self.beta, self.audio_divisor, 1)
|
| 78 |
+
|
| 79 |
+
def document(self, current: np.ndarray) -> np.ndarray:
|
| 80 |
+
return np.pad(unit(current), ((0, 0), (0, RAW_DIMENSION + 2))).astype(np.float32)
|
model/manifest.json
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"runtime": "nanobe-mlx-full",
|
| 3 |
+
"component": "full",
|
| 4 |
+
"source_revision": "12949877f0092093f366c6450340011320152a05",
|
| 5 |
+
"source_weights_sha256": "3d61fc55e1a457a9fc7f338df1ea1c183494716a4d4a32e291edd662757d5e8e",
|
| 6 |
+
"model_sha256": "553386da770249a30a0ba85febba50c9e9d40ff58c266c8ea3253f40dfe58963",
|
| 7 |
+
"weight_shards": [
|
| 8 |
+
{
|
| 9 |
+
"role": "language",
|
| 10 |
+
"file": "language.safetensors",
|
| 11 |
+
"sha256": "fea1441e6a0e56aba49d3cf2d0b92b36ee8142b8f98ed54b0e3bd30b1e909702",
|
| 12 |
+
"tensors": 704,
|
| 13 |
+
"bytes": 631565822
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
"role": "omni",
|
| 17 |
+
"file": "omni.safetensors",
|
| 18 |
+
"sha256": "8fd64d888f00a1cef89dd950e59b10bc8d60460e8d1e3f754b41592317329b36",
|
| 19 |
+
"tensors": 1369,
|
| 20 |
+
"bytes": 949948427
|
| 21 |
+
}
|
| 22 |
+
],
|
| 23 |
+
"tokenizer_sha256": "8e08386ae43d4a5024a97dadbdc7291d6770135921c97e913644f991b3a87b8e",
|
| 24 |
+
"head_sha256": "3922c5b7cb892977d61cb3e6caaa025fece854d5be2fba0e047503e1c17f4ccf",
|
| 25 |
+
"model_geometry": {
|
| 26 |
+
"file": "geometry.json",
|
| 27 |
+
"sha256": "2c4a99d0c2b03c023ef29cc2cb15c1cf780fdf964e5b92b6bc095ca046000e92"
|
| 28 |
+
},
|
| 29 |
+
"audiovisual_runtime": {
|
| 30 |
+
"file": "nanobe_mlx_omni.py",
|
| 31 |
+
"sha256": "33fbaae67f93e8c9ae3816c0232ae51c8dbb2e1bbeb4ced3a69495bdd3d01773"
|
| 32 |
+
},
|
| 33 |
+
"media_adapter": {
|
| 34 |
+
"file": "media-adapter.safetensors",
|
| 35 |
+
"sha256": "f9812fd4f33aeaaa22854569753731e81115581df55a2337c72ba4655fe58a63",
|
| 36 |
+
"bytes": 82687236,
|
| 37 |
+
"tensors": 896,
|
| 38 |
+
"scale": 2.0,
|
| 39 |
+
"applies_to": [
|
| 40 |
+
"image",
|
| 41 |
+
"audio",
|
| 42 |
+
"video",
|
| 43 |
+
"text"
|
| 44 |
+
],
|
| 45 |
+
"per_modality": true
|
| 46 |
+
},
|
| 47 |
+
"quantization": {
|
| 48 |
+
"targets": [
|
| 49 |
+
"linear",
|
| 50 |
+
"embedding"
|
| 51 |
+
],
|
| 52 |
+
"bits": 6,
|
| 53 |
+
"group_size": 64,
|
| 54 |
+
"mode": "affine",
|
| 55 |
+
"overrides": {
|
| 56 |
+
"language_model": 8,
|
| 57 |
+
"audio_tower": 8,
|
| 58 |
+
"audio_projector": 8
|
| 59 |
+
},
|
| 60 |
+
"embedding_overrides": {
|
| 61 |
+
"language_model": 6
|
| 62 |
+
}
|
| 63 |
+
},
|
| 64 |
+
"package_weight_bytes": 2227193722,
|
| 65 |
+
"acoustic_encoder": {
|
| 66 |
+
"directory": "pe-audio",
|
| 67 |
+
"manifest_sha256": "790f80f549c70cb327b30c537017ca2ae6f47781b219ce8cd35f0144b1e058d7",
|
| 68 |
+
"parameters": 117389248,
|
| 69 |
+
"weight_bytes": 153819456
|
| 70 |
+
},
|
| 71 |
+
"acoustic_text_encoder": {
|
| 72 |
+
"directory": "pe-text",
|
| 73 |
+
"manifest_sha256": "97ad081b8044cb4308995a5d2d93d36bdec39a7ccfcf01e6591b7a5fd6b0eeff",
|
| 74 |
+
"parameters": 322288640,
|
| 75 |
+
"weight_bytes": 345597033
|
| 76 |
+
},
|
| 77 |
+
"page_merger": {
|
| 78 |
+
"file": "page-merger.safetensors",
|
| 79 |
+
"sha256": "db41396835aa23dd661711a39e9fa64c59957f46564741f2db26517e782d5a57",
|
| 80 |
+
"bytes": 41957888,
|
| 81 |
+
"tensors": 6,
|
| 82 |
+
"parameters": 20978688
|
| 83 |
+
},
|
| 84 |
+
"audio_pooler": {
|
| 85 |
+
"file": "audio-pooler.safetensors",
|
| 86 |
+
"sha256": "7bf8bc6420491454c3bb332bd243244d82efddfbcaa07cbc56dfb7603ab913f9",
|
| 87 |
+
"bytes": 5251732,
|
| 88 |
+
"tensors": 8,
|
| 89 |
+
"parameters": 1312769
|
| 90 |
+
}
|
| 91 |
+
}
|
model/media-adapter.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f9812fd4f33aeaaa22854569753731e81115581df55a2337c72ba4655fe58a63
|
| 3 |
+
size 82687236
|
model/model.py
ADDED
|
@@ -0,0 +1,878 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""MLX architecture derived from Jina's omni-small retrieval model.
|
| 2 |
+
|
| 3 |
+
The text backbone is a 28-layer Qwen3 causal decoder with 1,024 hidden
|
| 4 |
+
coordinates, grouped-query attention and per-head query/key normalization.
|
| 5 |
+
Qwen3VL vision layers encode spatial and temporal patches through a merger.
|
| 6 |
+
The Qwen2.5-Omni audio tower uses convolutional features, sinusoidal positions
|
| 7 |
+
and windowed attention. Its output projection maps into the text backbone.
|
| 8 |
+
|
| 9 |
+
The runtime loads the language and omni weight shards listed in manifest.json.
|
| 10 |
+
Embedding methods pool the last unmasked token; the runtime applies the
|
| 11 |
+
retrieval projections and normalization for each input type.
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import math
|
| 15 |
+
from dataclasses import dataclass, field
|
| 16 |
+
from itertools import accumulate
|
| 17 |
+
|
| 18 |
+
import mlx.core as mx
|
| 19 |
+
import mlx.nn as nn
|
| 20 |
+
|
| 21 |
+
# ---------------------------------------------------------------------------
|
| 22 |
+
# Config
|
| 23 |
+
# ---------------------------------------------------------------------------
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class TextConfig:
|
| 28 |
+
hidden_size: int = 1024
|
| 29 |
+
num_hidden_layers: int = 28
|
| 30 |
+
intermediate_size: int = 3072
|
| 31 |
+
num_attention_heads: int = 16
|
| 32 |
+
num_key_value_heads: int = 8
|
| 33 |
+
head_dim: int = 128
|
| 34 |
+
rms_norm_eps: float = 1e-6
|
| 35 |
+
vocab_size: int = 151672
|
| 36 |
+
max_position_embeddings: int = 32768
|
| 37 |
+
rope_theta: float = 3500000.0
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
@dataclass
|
| 41 |
+
class VisionConfig:
|
| 42 |
+
hidden_size: int = 1024
|
| 43 |
+
depth: int = 24
|
| 44 |
+
num_heads: int = 16
|
| 45 |
+
intermediate_size: int = 4096
|
| 46 |
+
patch_size: int = 16
|
| 47 |
+
spatial_merge_size: int = 2
|
| 48 |
+
temporal_patch_size: int = 2
|
| 49 |
+
in_channels: int = 3
|
| 50 |
+
out_hidden_size: int = 1024
|
| 51 |
+
num_position_embeddings: int = 2304
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
@dataclass
|
| 55 |
+
class AudioConfig:
|
| 56 |
+
"""Qwen2.5-Omni audio encoder config; only fields the MLX port uses.
|
| 57 |
+
|
| 58 |
+
The base repo's `audio_tower.proj` is fused into `audio_projector`
|
| 59 |
+
(Linear 1280 -> 1024), so `output_dim` (3584) is not used at inference
|
| 60 |
+
and the `audio_tower.proj` weights are absent from the safetensors.
|
| 61 |
+
"""
|
| 62 |
+
|
| 63 |
+
d_model: int = 1280
|
| 64 |
+
encoder_layers: int = 32
|
| 65 |
+
encoder_attention_heads: int = 20
|
| 66 |
+
encoder_ffn_dim: int = 5120
|
| 67 |
+
max_source_positions: int = 1500
|
| 68 |
+
n_window: int = 100
|
| 69 |
+
num_mel_bins: int = 128
|
| 70 |
+
scale_embedding: bool = False
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
IMAGE_TOKEN_ID = 151655
|
| 74 |
+
VIDEO_TOKEN_ID = 151656
|
| 75 |
+
AUDIO_TOKEN_ID = 151669 # <|AUDIO|> placeholder in the text sequence
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
@dataclass
|
| 79 |
+
class OmniSmallConfig:
|
| 80 |
+
text_config: TextConfig = field(default_factory=TextConfig)
|
| 81 |
+
vision_config: VisionConfig = field(default_factory=VisionConfig)
|
| 82 |
+
audio_config: AudioConfig = field(default_factory=AudioConfig)
|
| 83 |
+
image_token_id: int = IMAGE_TOKEN_ID
|
| 84 |
+
video_token_id: int = VIDEO_TOKEN_ID
|
| 85 |
+
audio_token_id: int = AUDIO_TOKEN_ID
|
| 86 |
+
|
| 87 |
+
@classmethod
|
| 88 |
+
def from_dict(cls, d: dict) -> "OmniSmallConfig":
|
| 89 |
+
tc = d.get("text_config", {})
|
| 90 |
+
vc = d.get("vision_config", {})
|
| 91 |
+
ac = d.get("audio_config", {})
|
| 92 |
+
rope_params = tc.get("rope_parameters", {})
|
| 93 |
+
return cls(
|
| 94 |
+
text_config=TextConfig(
|
| 95 |
+
hidden_size=tc.get("hidden_size", 1024),
|
| 96 |
+
num_hidden_layers=tc.get("num_hidden_layers", 28),
|
| 97 |
+
intermediate_size=tc.get("intermediate_size", 3072),
|
| 98 |
+
num_attention_heads=tc.get("num_attention_heads", 16),
|
| 99 |
+
num_key_value_heads=tc.get("num_key_value_heads", 8),
|
| 100 |
+
head_dim=tc.get("head_dim", 128),
|
| 101 |
+
rms_norm_eps=tc.get("rms_norm_eps", 1e-6),
|
| 102 |
+
vocab_size=tc.get("vocab_size", 151672),
|
| 103 |
+
max_position_embeddings=tc.get("max_position_embeddings", 32768),
|
| 104 |
+
rope_theta=rope_params.get(
|
| 105 |
+
"rope_theta", tc.get("rope_theta", 3500000.0)
|
| 106 |
+
),
|
| 107 |
+
),
|
| 108 |
+
vision_config=VisionConfig(
|
| 109 |
+
hidden_size=vc.get("hidden_size", 1024),
|
| 110 |
+
depth=vc.get("depth", 24),
|
| 111 |
+
num_heads=vc.get("num_heads", 16),
|
| 112 |
+
intermediate_size=vc.get("intermediate_size", 4096),
|
| 113 |
+
patch_size=vc.get("patch_size", 16),
|
| 114 |
+
spatial_merge_size=vc.get("spatial_merge_size", 2),
|
| 115 |
+
temporal_patch_size=vc.get("temporal_patch_size", 2),
|
| 116 |
+
in_channels=vc.get("in_channels", 3),
|
| 117 |
+
out_hidden_size=vc.get("out_hidden_size", 1024),
|
| 118 |
+
num_position_embeddings=vc.get("num_position_embeddings", 2304),
|
| 119 |
+
),
|
| 120 |
+
audio_config=AudioConfig(
|
| 121 |
+
d_model=ac.get("d_model", 1280),
|
| 122 |
+
encoder_layers=ac.get("encoder_layers", 32),
|
| 123 |
+
encoder_attention_heads=ac.get("encoder_attention_heads", 20),
|
| 124 |
+
encoder_ffn_dim=ac.get("encoder_ffn_dim", 5120),
|
| 125 |
+
max_source_positions=ac.get("max_source_positions", 1500),
|
| 126 |
+
n_window=ac.get("n_window", 100),
|
| 127 |
+
num_mel_bins=ac.get("num_mel_bins", 128),
|
| 128 |
+
scale_embedding=ac.get("scale_embedding", False),
|
| 129 |
+
),
|
| 130 |
+
image_token_id=d.get("image_token_id", IMAGE_TOKEN_ID),
|
| 131 |
+
video_token_id=d.get("video_token_id", VIDEO_TOKEN_ID),
|
| 132 |
+
audio_token_id=d.get("audio_token_id", AUDIO_TOKEN_ID),
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
# ---------------------------------------------------------------------------
|
| 137 |
+
# Text model (Qwen3 causal, GQA 16/8 with per-head q_norm / k_norm)
|
| 138 |
+
# ---------------------------------------------------------------------------
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
class TextAttention(nn.Module):
|
| 142 |
+
def __init__(self, cfg: TextConfig):
|
| 143 |
+
super().__init__()
|
| 144 |
+
self.n_heads = cfg.num_attention_heads
|
| 145 |
+
self.n_kv_heads = cfg.num_key_value_heads
|
| 146 |
+
self.head_dim = cfg.head_dim
|
| 147 |
+
self.scale = self.head_dim**-0.5
|
| 148 |
+
|
| 149 |
+
dim = cfg.hidden_size
|
| 150 |
+
self.q_proj = nn.Linear(dim, self.n_heads * self.head_dim, bias=False)
|
| 151 |
+
self.k_proj = nn.Linear(dim, self.n_kv_heads * self.head_dim, bias=False)
|
| 152 |
+
self.v_proj = nn.Linear(dim, self.n_kv_heads * self.head_dim, bias=False)
|
| 153 |
+
self.o_proj = nn.Linear(self.n_heads * self.head_dim, dim, bias=False)
|
| 154 |
+
# Normalize each query and key head with RMSNorm.
|
| 155 |
+
self.q_norm = nn.RMSNorm(self.head_dim, eps=cfg.rms_norm_eps)
|
| 156 |
+
self.k_norm = nn.RMSNorm(self.head_dim, eps=cfg.rms_norm_eps)
|
| 157 |
+
self.rope_theta = cfg.rope_theta
|
| 158 |
+
|
| 159 |
+
def __call__(self, x, mask=None):
|
| 160 |
+
B, L, _ = x.shape
|
| 161 |
+
q = (
|
| 162 |
+
self.q_proj(x)
|
| 163 |
+
.reshape(B, L, self.n_heads, self.head_dim)
|
| 164 |
+
.transpose(0, 2, 1, 3)
|
| 165 |
+
)
|
| 166 |
+
k = (
|
| 167 |
+
self.k_proj(x)
|
| 168 |
+
.reshape(B, L, self.n_kv_heads, self.head_dim)
|
| 169 |
+
.transpose(0, 2, 1, 3)
|
| 170 |
+
)
|
| 171 |
+
v = (
|
| 172 |
+
self.v_proj(x)
|
| 173 |
+
.reshape(B, L, self.n_kv_heads, self.head_dim)
|
| 174 |
+
.transpose(0, 2, 1, 3)
|
| 175 |
+
)
|
| 176 |
+
|
| 177 |
+
# Qwen3: RMSNorm per head BEFORE rope.
|
| 178 |
+
q = self.q_norm(q)
|
| 179 |
+
k = self.k_norm(k)
|
| 180 |
+
|
| 181 |
+
q = mx.fast.rope(
|
| 182 |
+
q,
|
| 183 |
+
self.head_dim,
|
| 184 |
+
traditional=False,
|
| 185 |
+
base=self.rope_theta,
|
| 186 |
+
scale=1.0,
|
| 187 |
+
offset=0,
|
| 188 |
+
)
|
| 189 |
+
k = mx.fast.rope(
|
| 190 |
+
k,
|
| 191 |
+
self.head_dim,
|
| 192 |
+
traditional=False,
|
| 193 |
+
base=self.rope_theta,
|
| 194 |
+
scale=1.0,
|
| 195 |
+
offset=0,
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
out = mx.fast.scaled_dot_product_attention(
|
| 199 |
+
q,
|
| 200 |
+
k,
|
| 201 |
+
v,
|
| 202 |
+
mask=mask.astype(q.dtype) if mask is not None else None,
|
| 203 |
+
scale=self.scale,
|
| 204 |
+
)
|
| 205 |
+
out = out.transpose(0, 2, 1, 3).reshape(B, L, -1)
|
| 206 |
+
return self.o_proj(out)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
class TextMLP(nn.Module):
|
| 210 |
+
def __init__(self, dim, hidden_dim):
|
| 211 |
+
super().__init__()
|
| 212 |
+
self.gate_proj = nn.Linear(dim, hidden_dim, bias=False)
|
| 213 |
+
self.down_proj = nn.Linear(hidden_dim, dim, bias=False)
|
| 214 |
+
self.up_proj = nn.Linear(dim, hidden_dim, bias=False)
|
| 215 |
+
|
| 216 |
+
def __call__(self, x):
|
| 217 |
+
return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x))
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
class TextBlock(nn.Module):
|
| 221 |
+
def __init__(self, cfg: TextConfig):
|
| 222 |
+
super().__init__()
|
| 223 |
+
self.self_attn = TextAttention(cfg)
|
| 224 |
+
self.mlp = TextMLP(cfg.hidden_size, cfg.intermediate_size)
|
| 225 |
+
self.input_layernorm = nn.RMSNorm(cfg.hidden_size, eps=cfg.rms_norm_eps)
|
| 226 |
+
self.post_attention_layernorm = nn.RMSNorm(
|
| 227 |
+
cfg.hidden_size, eps=cfg.rms_norm_eps
|
| 228 |
+
)
|
| 229 |
+
|
| 230 |
+
def __call__(self, x, mask=None):
|
| 231 |
+
x = x + self.self_attn(self.input_layernorm(x), mask)
|
| 232 |
+
x = x + self.mlp(self.post_attention_layernorm(x))
|
| 233 |
+
return x
|
| 234 |
+
|
| 235 |
+
|
| 236 |
+
class TextModel(nn.Module):
|
| 237 |
+
def __init__(self, cfg: TextConfig):
|
| 238 |
+
super().__init__()
|
| 239 |
+
self.embed_tokens = nn.Embedding(cfg.vocab_size, cfg.hidden_size)
|
| 240 |
+
self.layers = [TextBlock(cfg) for _ in range(cfg.num_hidden_layers)]
|
| 241 |
+
self.norm = nn.RMSNorm(cfg.hidden_size, eps=cfg.rms_norm_eps)
|
| 242 |
+
|
| 243 |
+
def __call__(self, input_ids=None, inputs_embeds=None, mask=None):
|
| 244 |
+
h = inputs_embeds if inputs_embeds is not None else self.embed_tokens(input_ids)
|
| 245 |
+
for layer in self.layers:
|
| 246 |
+
h = layer(h, mask)
|
| 247 |
+
return self.norm(h)
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
# ---------------------------------------------------------------------------
|
| 251 |
+
# Qwen3VL vision transformer
|
| 252 |
+
# ---------------------------------------------------------------------------
|
| 253 |
+
|
| 254 |
+
|
| 255 |
+
def rotate_half(x):
|
| 256 |
+
x1 = x[..., : x.shape[-1] // 2]
|
| 257 |
+
x2 = x[..., x.shape[-1] // 2 :]
|
| 258 |
+
return mx.concatenate([-x2, x1], axis=-1)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def apply_rotary_pos_emb_vision(tensor, freqs):
|
| 262 |
+
cos = mx.cos(freqs)
|
| 263 |
+
sin = mx.sin(freqs)
|
| 264 |
+
cos = mx.expand_dims(mx.tile(mx.expand_dims(cos, 1), (1, 1, 2)), 0)
|
| 265 |
+
sin = mx.expand_dims(mx.tile(mx.expand_dims(sin, 1), (1, 1, 2)), 0)
|
| 266 |
+
return (tensor * cos) + (rotate_half(tensor) * sin)
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
class VisionRotaryEmbedding(nn.Module):
|
| 270 |
+
def __init__(self, dim, theta=10000.0):
|
| 271 |
+
super().__init__()
|
| 272 |
+
self.dim = dim
|
| 273 |
+
self.theta = theta
|
| 274 |
+
|
| 275 |
+
def __call__(self, seqlen):
|
| 276 |
+
inv_freq = 1.0 / (
|
| 277 |
+
self.theta ** (mx.arange(0, self.dim, 2, dtype=mx.float32) / self.dim)
|
| 278 |
+
)
|
| 279 |
+
seq = mx.arange(seqlen, dtype=inv_freq.dtype)
|
| 280 |
+
return mx.outer(seq, inv_freq)
|
| 281 |
+
|
| 282 |
+
|
| 283 |
+
class VisionPatchEmbed(nn.Module):
|
| 284 |
+
def __init__(self, cfg: VisionConfig):
|
| 285 |
+
super().__init__()
|
| 286 |
+
self.patch_size = cfg.patch_size
|
| 287 |
+
self.temporal_patch_size = cfg.temporal_patch_size
|
| 288 |
+
self.in_channels = cfg.in_channels
|
| 289 |
+
self.hidden_size = cfg.hidden_size
|
| 290 |
+
self.proj = nn.Conv3d(
|
| 291 |
+
cfg.in_channels,
|
| 292 |
+
cfg.hidden_size,
|
| 293 |
+
kernel_size=[cfg.temporal_patch_size, cfg.patch_size, cfg.patch_size],
|
| 294 |
+
stride=[cfg.temporal_patch_size, cfg.patch_size, cfg.patch_size],
|
| 295 |
+
bias=True,
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
def __call__(self, x):
|
| 299 |
+
x = x.reshape(
|
| 300 |
+
-1,
|
| 301 |
+
self.in_channels,
|
| 302 |
+
self.temporal_patch_size,
|
| 303 |
+
self.patch_size,
|
| 304 |
+
self.patch_size,
|
| 305 |
+
)
|
| 306 |
+
x = x.moveaxis(1, 4)
|
| 307 |
+
x = self.proj(x)
|
| 308 |
+
return x.reshape(-1, self.hidden_size)
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
class VisionAttention(nn.Module):
|
| 312 |
+
def __init__(self, dim, num_heads):
|
| 313 |
+
super().__init__()
|
| 314 |
+
self.num_heads = num_heads
|
| 315 |
+
self.head_dim = dim // num_heads
|
| 316 |
+
self.scale = self.head_dim**-0.5
|
| 317 |
+
self.qkv = nn.Linear(dim, dim * 3, bias=True)
|
| 318 |
+
self.proj = nn.Linear(dim, dim)
|
| 319 |
+
|
| 320 |
+
def __call__(self, x, cu_seqlens, rotary_pos_emb=None):
|
| 321 |
+
seq_length = x.shape[0]
|
| 322 |
+
qkv = (
|
| 323 |
+
self.qkv(x).reshape(seq_length, 3, self.num_heads, -1).transpose(1, 0, 2, 3)
|
| 324 |
+
)
|
| 325 |
+
q, k, v = mx.split(qkv, 3)
|
| 326 |
+
if rotary_pos_emb is not None:
|
| 327 |
+
q = apply_rotary_pos_emb_vision(mx.expand_dims(q, 0), rotary_pos_emb)[0]
|
| 328 |
+
k = apply_rotary_pos_emb_vision(mx.expand_dims(k, 0), rotary_pos_emb)[0]
|
| 329 |
+
q = q.transpose(0, 2, 1, 3)
|
| 330 |
+
k = k.transpose(0, 2, 1, 3)
|
| 331 |
+
v = v.transpose(0, 2, 1, 3)
|
| 332 |
+
|
| 333 |
+
splits = [mx.split(t, cu_seqlens[1:-1].tolist(), axis=2) for t in (q, k, v)]
|
| 334 |
+
attn_outputs = []
|
| 335 |
+
for qi, ki, vi in zip(*splits):
|
| 336 |
+
attn_outputs.append(
|
| 337 |
+
mx.fast.scaled_dot_product_attention(qi, ki, vi, scale=self.scale)
|
| 338 |
+
)
|
| 339 |
+
output = mx.concatenate(attn_outputs, axis=2)
|
| 340 |
+
output = output.transpose(0, 2, 1, 3).reshape(seq_length, -1)
|
| 341 |
+
return self.proj(output)
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
class VisionMLP(nn.Module):
|
| 345 |
+
def __init__(self, dim, hidden_dim):
|
| 346 |
+
super().__init__()
|
| 347 |
+
self.linear_fc1 = nn.Linear(dim, hidden_dim, bias=True)
|
| 348 |
+
self.linear_fc2 = nn.Linear(hidden_dim, dim, bias=True)
|
| 349 |
+
self.act_fn = nn.GELU(approx="tanh")
|
| 350 |
+
|
| 351 |
+
def __call__(self, x):
|
| 352 |
+
return self.linear_fc2(self.act_fn(self.linear_fc1(x)))
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
class VisionBlock(nn.Module):
|
| 356 |
+
def __init__(self, cfg: VisionConfig):
|
| 357 |
+
super().__init__()
|
| 358 |
+
self.norm1 = nn.LayerNorm(cfg.hidden_size, eps=1e-6)
|
| 359 |
+
self.norm2 = nn.LayerNorm(cfg.hidden_size, eps=1e-6)
|
| 360 |
+
self.attn = VisionAttention(cfg.hidden_size, cfg.num_heads)
|
| 361 |
+
self.mlp = VisionMLP(cfg.hidden_size, cfg.intermediate_size)
|
| 362 |
+
|
| 363 |
+
def __call__(self, x, cu_seqlens, rotary_pos_emb):
|
| 364 |
+
x = x + self.attn(self.norm1(x), cu_seqlens, rotary_pos_emb)
|
| 365 |
+
x = x + self.mlp(self.norm2(x))
|
| 366 |
+
return x
|
| 367 |
+
|
| 368 |
+
|
| 369 |
+
class VisionModel(nn.Module):
|
| 370 |
+
def __init__(self, cfg: VisionConfig):
|
| 371 |
+
super().__init__()
|
| 372 |
+
self.spatial_merge_size = cfg.spatial_merge_size
|
| 373 |
+
self.patch_embed = VisionPatchEmbed(cfg)
|
| 374 |
+
head_dim = cfg.hidden_size // cfg.num_heads
|
| 375 |
+
self.rotary_pos_emb = VisionRotaryEmbedding(head_dim // 2)
|
| 376 |
+
self.pos_embed = nn.Embedding(cfg.num_position_embeddings, cfg.hidden_size)
|
| 377 |
+
self.num_grid_per_side = int(cfg.num_position_embeddings**0.5)
|
| 378 |
+
self.blocks = [VisionBlock(cfg) for _ in range(cfg.depth)]
|
| 379 |
+
|
| 380 |
+
def rot_pos_emb(self, grid_thw):
|
| 381 |
+
merge_size = self.spatial_merge_size
|
| 382 |
+
max_hw = int(mx.max(grid_thw[:, 1:]).item())
|
| 383 |
+
freq_table = self.rotary_pos_emb(max_hw)
|
| 384 |
+
pos_ids = []
|
| 385 |
+
for num_frames, height, width in grid_thw.tolist():
|
| 386 |
+
num_frames, height, width = int(num_frames), int(height), int(width)
|
| 387 |
+
merged_h, merged_w = height // merge_size, width // merge_size
|
| 388 |
+
block_rows = mx.arange(merged_h)
|
| 389 |
+
block_cols = mx.arange(merged_w)
|
| 390 |
+
intra_row = mx.arange(merge_size)
|
| 391 |
+
intra_col = mx.arange(merge_size)
|
| 392 |
+
row_idx = (
|
| 393 |
+
block_rows[:, None, None, None] * merge_size
|
| 394 |
+
+ intra_row[None, None, :, None]
|
| 395 |
+
)
|
| 396 |
+
col_idx = (
|
| 397 |
+
block_cols[None, :, None, None] * merge_size
|
| 398 |
+
+ intra_col[None, None, None, :]
|
| 399 |
+
)
|
| 400 |
+
row_idx = mx.broadcast_to(
|
| 401 |
+
row_idx, (merged_h, merged_w, merge_size, merge_size)
|
| 402 |
+
).reshape(-1)
|
| 403 |
+
col_idx = mx.broadcast_to(
|
| 404 |
+
col_idx, (merged_h, merged_w, merge_size, merge_size)
|
| 405 |
+
).reshape(-1)
|
| 406 |
+
coords = mx.stack([row_idx, col_idx], axis=-1)
|
| 407 |
+
if num_frames > 1:
|
| 408 |
+
coords = mx.tile(coords, (num_frames, 1))
|
| 409 |
+
pos_ids.append(coords)
|
| 410 |
+
pos_ids = mx.concatenate(pos_ids, axis=0)
|
| 411 |
+
h_emb = freq_table[pos_ids[:, 0]]
|
| 412 |
+
w_emb = freq_table[pos_ids[:, 1]]
|
| 413 |
+
return mx.concatenate([h_emb, w_emb], axis=-1)
|
| 414 |
+
|
| 415 |
+
def fast_pos_embed_interpolate(self, grid_thw):
|
| 416 |
+
grid_thw_list = grid_thw.tolist()
|
| 417 |
+
idx_list = [[] for _ in range(4)]
|
| 418 |
+
weight_list = [[] for _ in range(4)]
|
| 419 |
+
for t, h, w in grid_thw_list:
|
| 420 |
+
h, w, t = int(h), int(w), int(t)
|
| 421 |
+
h_idxs = mx.linspace(0, self.num_grid_per_side - 1, h)
|
| 422 |
+
w_idxs = mx.linspace(0, self.num_grid_per_side - 1, w)
|
| 423 |
+
h_floor = h_idxs.astype(mx.int32)
|
| 424 |
+
w_floor = w_idxs.astype(mx.int32)
|
| 425 |
+
h_ceil = mx.minimum(h_floor + 1, self.num_grid_per_side - 1)
|
| 426 |
+
w_ceil = mx.minimum(w_floor + 1, self.num_grid_per_side - 1)
|
| 427 |
+
dh = h_idxs - h_floor.astype(mx.float32)
|
| 428 |
+
dw = w_idxs - w_floor.astype(mx.float32)
|
| 429 |
+
base_h = h_floor * self.num_grid_per_side
|
| 430 |
+
base_h_ceil = h_ceil * self.num_grid_per_side
|
| 431 |
+
indices = [
|
| 432 |
+
(base_h[:, None] + w_floor[None, :]).flatten(),
|
| 433 |
+
(base_h[:, None] + w_ceil[None, :]).flatten(),
|
| 434 |
+
(base_h_ceil[:, None] + w_floor[None, :]).flatten(),
|
| 435 |
+
(base_h_ceil[:, None] + w_ceil[None, :]).flatten(),
|
| 436 |
+
]
|
| 437 |
+
weights = [
|
| 438 |
+
((1 - dh)[:, None] * (1 - dw)[None, :]).flatten(),
|
| 439 |
+
((1 - dh)[:, None] * dw[None, :]).flatten(),
|
| 440 |
+
(dh[:, None] * (1 - dw)[None, :]).flatten(),
|
| 441 |
+
(dh[:, None] * dw[None, :]).flatten(),
|
| 442 |
+
]
|
| 443 |
+
for i in range(4):
|
| 444 |
+
idx_list[i].extend(indices[i].tolist())
|
| 445 |
+
weight_list[i].extend(weights[i].tolist())
|
| 446 |
+
idx_tensor = mx.array(idx_list, dtype=mx.int32)
|
| 447 |
+
pos_embeds = self.pos_embed(idx_tensor)
|
| 448 |
+
# Packed embedding weights are integers; interpolation uses the decoded dtype.
|
| 449 |
+
weight_tensor = mx.array(weight_list, dtype=pos_embeds.dtype)
|
| 450 |
+
pos_embeds = pos_embeds * weight_tensor[:, :, None]
|
| 451 |
+
patch_pos_embeds = pos_embeds[0] + pos_embeds[1] + pos_embeds[2] + pos_embeds[3]
|
| 452 |
+
split_sizes = [int(h * w) for t, h, w in grid_thw_list]
|
| 453 |
+
if len(split_sizes) > 1:
|
| 454 |
+
split_indices = list(accumulate(split_sizes[:-1]))
|
| 455 |
+
parts = mx.split(patch_pos_embeds, split_indices, axis=0)
|
| 456 |
+
else:
|
| 457 |
+
parts = [patch_pos_embeds]
|
| 458 |
+
merge_size = self.spatial_merge_size
|
| 459 |
+
result = []
|
| 460 |
+
for pos_embed, (t, h, w) in zip(parts, grid_thw_list):
|
| 461 |
+
t, h, w = int(t), int(h), int(w)
|
| 462 |
+
fd = pos_embed.shape[-1]
|
| 463 |
+
pos_embed = mx.tile(pos_embed, (t, 1))
|
| 464 |
+
pos_embed = pos_embed.reshape(t, h, w, fd)
|
| 465 |
+
pos_embed = (
|
| 466 |
+
pos_embed.reshape(
|
| 467 |
+
t, h // merge_size, merge_size, w // merge_size, merge_size, fd
|
| 468 |
+
)
|
| 469 |
+
.transpose(0, 1, 3, 2, 4, 5)
|
| 470 |
+
.reshape(-1, fd)
|
| 471 |
+
)
|
| 472 |
+
result.append(pos_embed)
|
| 473 |
+
return mx.concatenate(result)
|
| 474 |
+
|
| 475 |
+
def __call__(self, pixel_values, grid_thw):
|
| 476 |
+
hidden = self.patch_embed(pixel_values)
|
| 477 |
+
pos_embeds = self.fast_pos_embed_interpolate(grid_thw)
|
| 478 |
+
hidden = hidden + pos_embeds
|
| 479 |
+
rotary = self.rot_pos_emb(grid_thw)
|
| 480 |
+
seq_len = hidden.shape[0]
|
| 481 |
+
hidden = hidden.reshape(seq_len, -1)
|
| 482 |
+
rotary = rotary.reshape(seq_len, -1)
|
| 483 |
+
|
| 484 |
+
cu_seqlens = []
|
| 485 |
+
for i in range(grid_thw.shape[0]):
|
| 486 |
+
seq_len_i = grid_thw[i, 1] * grid_thw[i, 2]
|
| 487 |
+
cu_seqlens.append(mx.repeat(seq_len_i, grid_thw[i, 0]))
|
| 488 |
+
cu_seqlens = mx.concatenate(cu_seqlens)
|
| 489 |
+
cu_seqlens = mx.cumsum(cu_seqlens.astype(mx.int32), axis=0)
|
| 490 |
+
cu_seqlens = mx.pad(cu_seqlens, (1, 0), mode="constant", constant_values=0)
|
| 491 |
+
|
| 492 |
+
for blk in self.blocks:
|
| 493 |
+
hidden = blk(hidden, cu_seqlens, rotary)
|
| 494 |
+
return hidden
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
# ---------------------------------------------------------------------------
|
| 498 |
+
# Spatial patch merger
|
| 499 |
+
# ---------------------------------------------------------------------------
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
class Merger(nn.Module):
|
| 503 |
+
def __init__(self, cfg: VisionConfig, text_hidden_size: int):
|
| 504 |
+
super().__init__()
|
| 505 |
+
self.hidden_size = cfg.hidden_size * (cfg.spatial_merge_size**2)
|
| 506 |
+
self.norm = nn.LayerNorm(cfg.hidden_size, eps=1e-6)
|
| 507 |
+
self.linear_fc1 = nn.Linear(self.hidden_size, self.hidden_size, bias=True)
|
| 508 |
+
self.act = nn.GELU()
|
| 509 |
+
self.linear_fc2 = nn.Linear(self.hidden_size, text_hidden_size, bias=True)
|
| 510 |
+
|
| 511 |
+
def __call__(self, x):
|
| 512 |
+
x = self.norm(x).reshape(-1, self.hidden_size)
|
| 513 |
+
return self.linear_fc2(self.act(self.linear_fc1(x)))
|
| 514 |
+
|
| 515 |
+
|
| 516 |
+
# ---------------------------------------------------------------------------
|
| 517 |
+
# Audio tower (Qwen2.5-Omni; fused proj collapsed into `audio_projector`)
|
| 518 |
+
# ---------------------------------------------------------------------------
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
def _sinusoids_position_embedding(length: int, channels: int, max_timescale: float = 10000.0) -> mx.array:
|
| 522 |
+
"""Whisper-style sinusoidal position embedding, computed (not learned)."""
|
| 523 |
+
assert channels % 2 == 0, "channels must be even for sinusoidal embeddings"
|
| 524 |
+
log_timescale_increment = math.log(max_timescale) / (channels // 2 - 1)
|
| 525 |
+
inv_timescales = mx.exp(
|
| 526 |
+
-log_timescale_increment * mx.arange(channels // 2, dtype=mx.float32)
|
| 527 |
+
)
|
| 528 |
+
t = mx.arange(length, dtype=mx.float32)
|
| 529 |
+
scaled_time = t[:, None] * inv_timescales[None, :]
|
| 530 |
+
return mx.concatenate([mx.sin(scaled_time), mx.cos(scaled_time)], axis=-1)
|
| 531 |
+
|
| 532 |
+
|
| 533 |
+
class AudioAttention(nn.Module):
|
| 534 |
+
"""Windowed attention over chunks defined by `cu_seqlens`.
|
| 535 |
+
|
| 536 |
+
Matches Qwen2_5OmniAudioAttention: only q/v/o have biases; k has no bias.
|
| 537 |
+
"""
|
| 538 |
+
|
| 539 |
+
def __init__(self, cfg: AudioConfig):
|
| 540 |
+
super().__init__()
|
| 541 |
+
self.num_heads = cfg.encoder_attention_heads
|
| 542 |
+
self.head_dim = cfg.d_model // cfg.encoder_attention_heads
|
| 543 |
+
self.scale = self.head_dim**-0.5
|
| 544 |
+
self.k_proj = nn.Linear(cfg.d_model, cfg.d_model, bias=False)
|
| 545 |
+
self.v_proj = nn.Linear(cfg.d_model, cfg.d_model, bias=True)
|
| 546 |
+
self.q_proj = nn.Linear(cfg.d_model, cfg.d_model, bias=True)
|
| 547 |
+
self.out_proj = nn.Linear(cfg.d_model, cfg.d_model, bias=True)
|
| 548 |
+
|
| 549 |
+
def __call__(self, x, cu_seqlens):
|
| 550 |
+
seq_length = x.shape[0]
|
| 551 |
+
q = self.q_proj(x).reshape(seq_length, self.num_heads, -1).transpose(1, 0, 2)
|
| 552 |
+
k = self.k_proj(x).reshape(seq_length, self.num_heads, -1).transpose(1, 0, 2)
|
| 553 |
+
v = self.v_proj(x).reshape(seq_length, self.num_heads, -1).transpose(1, 0, 2)
|
| 554 |
+
|
| 555 |
+
q = mx.expand_dims(q, 0)
|
| 556 |
+
k = mx.expand_dims(k, 0)
|
| 557 |
+
v = mx.expand_dims(v, 0)
|
| 558 |
+
|
| 559 |
+
splits = [mx.split(t, cu_seqlens[1:-1].tolist(), axis=2) for t in (q, k, v)]
|
| 560 |
+
attn_outputs = []
|
| 561 |
+
for qi, ki, vi in zip(*splits):
|
| 562 |
+
attn_outputs.append(
|
| 563 |
+
mx.fast.scaled_dot_product_attention(qi, ki, vi, scale=self.scale)
|
| 564 |
+
)
|
| 565 |
+
output = mx.concatenate(attn_outputs, axis=2)
|
| 566 |
+
output = output[0].transpose(1, 0, 2).reshape(seq_length, -1)
|
| 567 |
+
return self.out_proj(output)
|
| 568 |
+
|
| 569 |
+
|
| 570 |
+
class AudioEncoderLayer(nn.Module):
|
| 571 |
+
def __init__(self, cfg: AudioConfig):
|
| 572 |
+
super().__init__()
|
| 573 |
+
self.self_attn = AudioAttention(cfg)
|
| 574 |
+
self.self_attn_layer_norm = nn.LayerNorm(cfg.d_model)
|
| 575 |
+
self.fc1 = nn.Linear(cfg.d_model, cfg.encoder_ffn_dim, bias=True)
|
| 576 |
+
self.fc2 = nn.Linear(cfg.encoder_ffn_dim, cfg.d_model, bias=True)
|
| 577 |
+
self.final_layer_norm = nn.LayerNorm(cfg.d_model)
|
| 578 |
+
self.act_fn = nn.GELU()
|
| 579 |
+
|
| 580 |
+
def __call__(self, x, cu_seqlens):
|
| 581 |
+
x = x + self.self_attn(self.self_attn_layer_norm(x), cu_seqlens)
|
| 582 |
+
x = x + self.fc2(self.act_fn(self.fc1(self.final_layer_norm(x))))
|
| 583 |
+
return x
|
| 584 |
+
|
| 585 |
+
|
| 586 |
+
class AudioModel(nn.Module):
|
| 587 |
+
"""Qwen2.5-Omni audio encoder, with `proj` fused out.
|
| 588 |
+
|
| 589 |
+
Weights use the `audio_tower.*` names. The forward pass consumes audio
|
| 590 |
+
features without separate beginning or end token embeddings.
|
| 591 |
+
"""
|
| 592 |
+
|
| 593 |
+
def __init__(self, cfg: AudioConfig):
|
| 594 |
+
super().__init__()
|
| 595 |
+
self.cfg = cfg
|
| 596 |
+
self.conv1 = nn.Conv1d(cfg.num_mel_bins, cfg.d_model, kernel_size=3, padding=1)
|
| 597 |
+
self.conv2 = nn.Conv1d(cfg.d_model, cfg.d_model, kernel_size=3, stride=2, padding=1)
|
| 598 |
+
self.layers = [AudioEncoderLayer(cfg) for _ in range(cfg.encoder_layers)]
|
| 599 |
+
self.ln_post = nn.LayerNorm(cfg.d_model)
|
| 600 |
+
# self.proj is fused into audio_projector; represent as identity.
|
| 601 |
+
self.proj = nn.Identity()
|
| 602 |
+
self.act_fn = nn.GELU()
|
| 603 |
+
|
| 604 |
+
self._pos_table = _sinusoids_position_embedding(
|
| 605 |
+
cfg.max_source_positions, cfg.d_model
|
| 606 |
+
)
|
| 607 |
+
|
| 608 |
+
@staticmethod
|
| 609 |
+
def feat_extract_output_lengths(input_lengths: mx.array) -> tuple[mx.array, mx.array]:
|
| 610 |
+
"""Mirrors `Qwen2_5OmniAudioEncoder._get_feat_extract_output_lengths`."""
|
| 611 |
+
input_lengths = (input_lengths - 1) // 2 + 1
|
| 612 |
+
output_lengths = (input_lengths - 2) // 2 + 1
|
| 613 |
+
return input_lengths, output_lengths
|
| 614 |
+
|
| 615 |
+
def __call__(self, input_features: mx.array, feature_lens: mx.array) -> mx.array:
|
| 616 |
+
cfg = self.cfg
|
| 617 |
+
window_size = cfg.n_window * 2
|
| 618 |
+
|
| 619 |
+
feature_lens_list = feature_lens.tolist()
|
| 620 |
+
chunk_lengths: list[int] = []
|
| 621 |
+
for L in feature_lens_list:
|
| 622 |
+
L = int(L)
|
| 623 |
+
full_chunks, tail = divmod(L, window_size)
|
| 624 |
+
chunk_lengths.extend([window_size] * full_chunks)
|
| 625 |
+
if tail > 0:
|
| 626 |
+
chunk_lengths.append(tail)
|
| 627 |
+
elif L == 0:
|
| 628 |
+
chunk_lengths.append(0)
|
| 629 |
+
|
| 630 |
+
total_T = int(sum(chunk_lengths))
|
| 631 |
+
assert total_T == int(feature_lens.sum().item()), (
|
| 632 |
+
f"chunk sum {total_T} != feature sum {int(feature_lens.sum().item())}"
|
| 633 |
+
)
|
| 634 |
+
max_len = window_size
|
| 635 |
+
num_chunks = len(chunk_lengths)
|
| 636 |
+
C = cfg.num_mel_bins
|
| 637 |
+
padded = mx.zeros((num_chunks, max_len, C), dtype=input_features.dtype)
|
| 638 |
+
pad_mask = mx.zeros((num_chunks, max_len), dtype=mx.bool_)
|
| 639 |
+
|
| 640 |
+
features_tc = input_features.T
|
| 641 |
+
offset = 0
|
| 642 |
+
for i, L in enumerate(chunk_lengths):
|
| 643 |
+
if L > 0:
|
| 644 |
+
padded[i, :L, :] = features_tc[offset : offset + L, :]
|
| 645 |
+
pad_mask[i, :L] = True
|
| 646 |
+
offset += L
|
| 647 |
+
|
| 648 |
+
h = self.act_fn(self.conv1(padded)) * mx.expand_dims(
|
| 649 |
+
pad_mask.astype(padded.dtype), -1
|
| 650 |
+
)
|
| 651 |
+
h = self.act_fn(self.conv2(h))
|
| 652 |
+
|
| 653 |
+
chunk_len_arr = mx.array(chunk_lengths, dtype=mx.int32)
|
| 654 |
+
aftercnn_lengths, _ = self.feat_extract_output_lengths(chunk_len_arr)
|
| 655 |
+
aftercnn_lengths_list = aftercnn_lengths.tolist()
|
| 656 |
+
|
| 657 |
+
max_after_cnn = h.shape[1]
|
| 658 |
+
pos = self._pos_table[:max_after_cnn, :].astype(h.dtype)
|
| 659 |
+
h = h + mx.expand_dims(pos, 0)
|
| 660 |
+
|
| 661 |
+
flat_chunks = []
|
| 662 |
+
cu_seqlens_list = [0]
|
| 663 |
+
for i, L_after in enumerate(aftercnn_lengths_list):
|
| 664 |
+
L_after = int(L_after)
|
| 665 |
+
flat_chunks.append(h[i, :L_after, :])
|
| 666 |
+
cu_seqlens_list.append(cu_seqlens_list[-1] + L_after)
|
| 667 |
+
hidden = mx.concatenate(flat_chunks, axis=0)
|
| 668 |
+
cu_seqlens = mx.array(cu_seqlens_list, dtype=mx.int32)
|
| 669 |
+
|
| 670 |
+
for layer in self.layers:
|
| 671 |
+
hidden = layer(hidden, cu_seqlens)
|
| 672 |
+
|
| 673 |
+
audio_aftercnn_total = []
|
| 674 |
+
chunk_idx = 0
|
| 675 |
+
for L in feature_lens_list:
|
| 676 |
+
L = int(L)
|
| 677 |
+
full_chunks, tail = divmod(L, window_size)
|
| 678 |
+
n_chunks_for_this_audio = full_chunks + (1 if tail > 0 else 0)
|
| 679 |
+
if L == 0:
|
| 680 |
+
n_chunks_for_this_audio = max(n_chunks_for_this_audio, 1)
|
| 681 |
+
total_aftercnn = sum(
|
| 682 |
+
int(aftercnn_lengths_list[chunk_idx + k])
|
| 683 |
+
for k in range(n_chunks_for_this_audio)
|
| 684 |
+
)
|
| 685 |
+
audio_aftercnn_total.append(total_aftercnn)
|
| 686 |
+
chunk_idx += n_chunks_for_this_audio
|
| 687 |
+
|
| 688 |
+
per_audio = []
|
| 689 |
+
offset = 0
|
| 690 |
+
for total_after in audio_aftercnn_total:
|
| 691 |
+
seg = hidden[offset : offset + total_after, :]
|
| 692 |
+
T_even = (seg.shape[0] // 2) * 2
|
| 693 |
+
seg = seg[:T_even, :].reshape(T_even // 2, 2, seg.shape[1]).mean(axis=1)
|
| 694 |
+
seg = self.ln_post(seg)
|
| 695 |
+
seg = self.proj(seg)
|
| 696 |
+
per_audio.append(seg)
|
| 697 |
+
offset += total_after
|
| 698 |
+
return mx.concatenate(per_audio, axis=0)
|
| 699 |
+
|
| 700 |
+
|
| 701 |
+
# ---------------------------------------------------------------------------
|
| 702 |
+
# Pooling helpers
|
| 703 |
+
# ---------------------------------------------------------------------------
|
| 704 |
+
|
| 705 |
+
|
| 706 |
+
def _last_token_pool(hidden: mx.array, attention_mask: mx.array | None) -> mx.array:
|
| 707 |
+
"""Gather the last non-pad token per sequence."""
|
| 708 |
+
B = hidden.shape[0]
|
| 709 |
+
if attention_mask is not None:
|
| 710 |
+
idx = (attention_mask.sum(axis=1) - 1).astype(mx.int32)
|
| 711 |
+
else:
|
| 712 |
+
idx = mx.full((B,), hidden.shape[1] - 1, dtype=mx.int32)
|
| 713 |
+
pooled_rows = [hidden[b, int(idx[b].item()), :] for b in range(B)]
|
| 714 |
+
pooled = mx.stack(pooled_rows, axis=0)
|
| 715 |
+
return pooled / mx.linalg.norm(pooled, axis=-1, keepdims=True)
|
| 716 |
+
|
| 717 |
+
|
| 718 |
+
def _causal_pad_mask(
|
| 719 |
+
attention_mask: mx.array | None,
|
| 720 |
+
seq_len: int,
|
| 721 |
+
reference_param: mx.array,
|
| 722 |
+
) -> mx.array | None:
|
| 723 |
+
"""Build the additive attention mask for a causal decoder with right-padding.
|
| 724 |
+
|
| 725 |
+
Produces a `[B, 1, L, L]` float tensor with
|
| 726 |
+
- -inf on strictly upper-triangular positions (causal)
|
| 727 |
+
- -inf on pad key positions across all queries
|
| 728 |
+
- 0 elsewhere
|
| 729 |
+
|
| 730 |
+
Dtype matches `reference_param` so SDP ingests it directly.
|
| 731 |
+
"""
|
| 732 |
+
# Causal base: strictly upper triangular -inf (disallow future positions).
|
| 733 |
+
causal = mx.triu(
|
| 734 |
+
mx.full((seq_len, seq_len), -1e9, dtype=reference_param.dtype), k=1
|
| 735 |
+
)
|
| 736 |
+
causal = causal[None, None, :, :] # broadcast over batch + heads
|
| 737 |
+
|
| 738 |
+
if attention_mask is None:
|
| 739 |
+
return causal
|
| 740 |
+
|
| 741 |
+
pad = mx.where(attention_mask == 0, -1e9, 0.0)[:, None, None, :]
|
| 742 |
+
pad = pad.astype(reference_param.dtype)
|
| 743 |
+
return causal + pad
|
| 744 |
+
|
| 745 |
+
|
| 746 |
+
# ---------------------------------------------------------------------------
|
| 747 |
+
# Full omni-small model (MLX, text + image + video + audio)
|
| 748 |
+
# ---------------------------------------------------------------------------
|
| 749 |
+
|
| 750 |
+
|
| 751 |
+
class JinaOmniSmallEmbeddingModel(nn.Module):
|
| 752 |
+
def __init__(self, config: OmniSmallConfig):
|
| 753 |
+
super().__init__()
|
| 754 |
+
self.config = config
|
| 755 |
+
self.vision_tower = VisionModel(config.vision_config)
|
| 756 |
+
self.merger = Merger(config.vision_config, config.text_config.hidden_size)
|
| 757 |
+
self.audio_tower = AudioModel(config.audio_config)
|
| 758 |
+
self.audio_projector = nn.Linear(
|
| 759 |
+
config.audio_config.d_model,
|
| 760 |
+
config.text_config.hidden_size,
|
| 761 |
+
bias=True,
|
| 762 |
+
)
|
| 763 |
+
self.language_model = TextModel(config.text_config)
|
| 764 |
+
|
| 765 |
+
# ------------------------------------------------------------------ text
|
| 766 |
+
def encode_text(self, input_ids, attention_mask=None):
|
| 767 |
+
mask = _causal_pad_mask(
|
| 768 |
+
attention_mask,
|
| 769 |
+
input_ids.shape[1],
|
| 770 |
+
self.language_model.layers[0].input_layernorm.weight,
|
| 771 |
+
)
|
| 772 |
+
hidden = self.language_model(input_ids=input_ids, mask=mask)
|
| 773 |
+
mx.eval(hidden)
|
| 774 |
+
emb = _last_token_pool(hidden, attention_mask)
|
| 775 |
+
mx.eval(emb)
|
| 776 |
+
return emb
|
| 777 |
+
|
| 778 |
+
# ------------------------------------------------------------- image/video
|
| 779 |
+
def _inject_features(self, input_ids, inputs_embeds, placeholder_id, features):
|
| 780 |
+
"""Scatter `features` into `inputs_embeds` at `placeholder_id` slots."""
|
| 781 |
+
import numpy as np
|
| 782 |
+
|
| 783 |
+
special_mask = input_ids == placeholder_id
|
| 784 |
+
special_mask_expanded = mx.broadcast_to(
|
| 785 |
+
special_mask[..., None], inputs_embeds.shape
|
| 786 |
+
)
|
| 787 |
+
positions = mx.array(
|
| 788 |
+
np.where(mx.flatten(special_mask_expanded))[0], mx.uint32
|
| 789 |
+
)
|
| 790 |
+
flat_embeds = mx.flatten(inputs_embeds)
|
| 791 |
+
flat_embeds[positions] = mx.flatten(features)
|
| 792 |
+
return mx.reshape(flat_embeds, inputs_embeds.shape)
|
| 793 |
+
|
| 794 |
+
def encode_image(
|
| 795 |
+
self, pixel_values, image_grid_thw, input_ids, attention_mask=None
|
| 796 |
+
):
|
| 797 |
+
vision_hidden = self.vision_tower(pixel_values, image_grid_thw)
|
| 798 |
+
vision_features = self.merger(vision_hidden)
|
| 799 |
+
|
| 800 |
+
inputs_embeds = self.language_model.embed_tokens(input_ids)
|
| 801 |
+
inputs_embeds = self._inject_features(
|
| 802 |
+
input_ids, inputs_embeds, self.config.image_token_id, vision_features
|
| 803 |
+
)
|
| 804 |
+
|
| 805 |
+
mask = _causal_pad_mask(
|
| 806 |
+
attention_mask,
|
| 807 |
+
input_ids.shape[1],
|
| 808 |
+
self.language_model.layers[0].input_layernorm.weight,
|
| 809 |
+
)
|
| 810 |
+
hidden = self.language_model(inputs_embeds=inputs_embeds, mask=mask)
|
| 811 |
+
mx.eval(hidden)
|
| 812 |
+
emb = _last_token_pool(hidden, attention_mask)
|
| 813 |
+
mx.eval(emb)
|
| 814 |
+
return emb
|
| 815 |
+
|
| 816 |
+
def encode_video(
|
| 817 |
+
self, pixel_values_videos, video_grid_thw, input_ids, attention_mask=None
|
| 818 |
+
):
|
| 819 |
+
"""Encode video patches at video-token positions in the text sequence.
|
| 820 |
+
|
| 821 |
+
Images and video share the vision transformer and merger. The temporal
|
| 822 |
+
dimension of video_grid_thw controls temporal patch grouping.
|
| 823 |
+
"""
|
| 824 |
+
vision_hidden = self.vision_tower(pixel_values_videos, video_grid_thw)
|
| 825 |
+
vision_features = self.merger(vision_hidden)
|
| 826 |
+
|
| 827 |
+
inputs_embeds = self.language_model.embed_tokens(input_ids)
|
| 828 |
+
inputs_embeds = self._inject_features(
|
| 829 |
+
input_ids, inputs_embeds, self.config.video_token_id, vision_features
|
| 830 |
+
)
|
| 831 |
+
|
| 832 |
+
mask = _causal_pad_mask(
|
| 833 |
+
attention_mask,
|
| 834 |
+
input_ids.shape[1],
|
| 835 |
+
self.language_model.layers[0].input_layernorm.weight,
|
| 836 |
+
)
|
| 837 |
+
hidden = self.language_model(inputs_embeds=inputs_embeds, mask=mask)
|
| 838 |
+
mx.eval(hidden)
|
| 839 |
+
emb = _last_token_pool(hidden, attention_mask)
|
| 840 |
+
mx.eval(emb)
|
| 841 |
+
return emb
|
| 842 |
+
|
| 843 |
+
# ----------------------------------------------------------------- audio
|
| 844 |
+
def encode_audio(
|
| 845 |
+
self,
|
| 846 |
+
input_features,
|
| 847 |
+
feature_lens,
|
| 848 |
+
input_ids,
|
| 849 |
+
attention_mask=None,
|
| 850 |
+
):
|
| 851 |
+
audio_hidden = self.audio_tower(input_features, feature_lens)
|
| 852 |
+
audio_features = self.audio_projector(audio_hidden)
|
| 853 |
+
|
| 854 |
+
inputs_embeds = self.language_model.embed_tokens(input_ids)
|
| 855 |
+
inputs_embeds = self._inject_features(
|
| 856 |
+
input_ids, inputs_embeds, self.config.audio_token_id, audio_features
|
| 857 |
+
)
|
| 858 |
+
|
| 859 |
+
mask = _causal_pad_mask(
|
| 860 |
+
attention_mask,
|
| 861 |
+
input_ids.shape[1],
|
| 862 |
+
self.language_model.layers[0].input_layernorm.weight,
|
| 863 |
+
)
|
| 864 |
+
hidden = self.language_model(inputs_embeds=inputs_embeds, mask=mask)
|
| 865 |
+
mx.eval(hidden)
|
| 866 |
+
emb = _last_token_pool(hidden, attention_mask)
|
| 867 |
+
mx.eval(emb)
|
| 868 |
+
return emb
|
| 869 |
+
|
| 870 |
+
# ---------------------------------------------------------------- sanitize
|
| 871 |
+
def sanitize(self, weights):
|
| 872 |
+
"""Load converted weights, retaining float32 language computation."""
|
| 873 |
+
return {
|
| 874 |
+
name: value.astype(mx.float32)
|
| 875 |
+
if name.startswith("language_model.") and value.dtype == mx.bfloat16
|
| 876 |
+
else value
|
| 877 |
+
for name, value in weights.items()
|
| 878 |
+
}
|
model/nanobe_mlx_omni.py
ADDED
|
@@ -0,0 +1,988 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import importlib.util
|
| 4 |
+
import hashlib
|
| 5 |
+
import json
|
| 6 |
+
import math
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
from contextlib import contextmanager, nullcontext
|
| 10 |
+
from dataclasses import dataclass
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
import av
|
| 14 |
+
import mlx.core as mx
|
| 15 |
+
import mlx.nn as nn
|
| 16 |
+
import numpy as np
|
| 17 |
+
from av.audio.resampler import AudioResampler
|
| 18 |
+
from PIL import Image
|
| 19 |
+
from transformers import AutoProcessor, WhisperFeatureExtractor
|
| 20 |
+
from transformers.video_utils import VideoMetadata
|
| 21 |
+
from safetensors.numpy import load_file
|
| 22 |
+
|
| 23 |
+
from nanobe.query import AudioMagnitude
|
| 24 |
+
from nanobe.runtime import geometry_contract
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
# Frames sampled per video pass.
|
| 28 |
+
VIDEO_FRAMES = 16
|
| 29 |
+
# Limit tokens per position-wise feed-forward kernel to bound GPU scheduling latency.
|
| 30 |
+
SEQUENCE_CHUNK = 2048
|
| 31 |
+
IMAGE_PIXEL_SHARE = 0.5
|
| 32 |
+
VIDEO_MAX_PIXELS = 12_845_056
|
| 33 |
+
MEDIA_MIN_PIXELS = 262_144
|
| 34 |
+
MODALITIES = ("text", "image", "document", "audio", "video", "interleaved", "unknown")
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class MediaSwitch:
|
| 38 |
+
"""Select the active media or text-document adapter for one forward.
|
| 39 |
+
|
| 40 |
+
Queries and document pages use base weights.
|
| 41 |
+
"""
|
| 42 |
+
|
| 43 |
+
def __init__(self) -> None:
|
| 44 |
+
self.modality: str | None = None
|
| 45 |
+
|
| 46 |
+
@property
|
| 47 |
+
def on(self) -> bool:
|
| 48 |
+
return self.modality is not None
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class LoRALinear(nn.Module):
|
| 52 |
+
"""Add the selected low-rank delta to a base linear layer.
|
| 53 |
+
|
| 54 |
+
`deltas` maps adapter names to their (lora_a, lora_b) pairs.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
def __init__(self, base: nn.Module, deltas: dict, scale: float, switch: MediaSwitch):
|
| 58 |
+
super().__init__()
|
| 59 |
+
self.base = base
|
| 60 |
+
# cast once to the base layer's compute dtype and pre-transpose, so a
|
| 61 |
+
# media forward costs two small matmuls and nothing else
|
| 62 |
+
dtype = getattr(base, "scales", getattr(base, "weight", None)).dtype
|
| 63 |
+
self.deltas = {name: (lora_a.T.astype(dtype), lora_b.T.astype(dtype)) for name, (lora_a, lora_b) in deltas.items()}
|
| 64 |
+
self.scale = scale
|
| 65 |
+
self._switch = switch
|
| 66 |
+
|
| 67 |
+
def __call__(self, x: mx.array) -> mx.array:
|
| 68 |
+
value = self.base(x)
|
| 69 |
+
name = self._switch.modality
|
| 70 |
+
if name is None:
|
| 71 |
+
return value
|
| 72 |
+
a_t, b_t = self.deltas[name]
|
| 73 |
+
return value + ((x @ a_t) @ b_t) * self.scale
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def install_media_adapter(language_model: nn.Module, tensors: dict, scale: float, switch: MediaSwitch) -> int:
|
| 77 |
+
"""Wrap every language self-attention projection named in `tensors`.
|
| 78 |
+
Keys are `<modality>/language_model.layers.N.self_attn.P.lora_{a,b}`."""
|
| 79 |
+
installed = 0
|
| 80 |
+
for index, layer in enumerate(language_model.layers):
|
| 81 |
+
attention = layer.self_attn
|
| 82 |
+
for name in ("q_proj", "k_proj", "v_proj", "o_proj"):
|
| 83 |
+
key = f"language_model.layers.{index}.self_attn.{name}"
|
| 84 |
+
deltas = {}
|
| 85 |
+
for kind in ("image", "audio", "video", "text"):
|
| 86 |
+
if f"{kind}/{key}.lora_a" in tensors:
|
| 87 |
+
deltas[kind] = (tensors[f"{kind}/{key}.lora_a"], tensors[f"{kind}/{key}.lora_b"])
|
| 88 |
+
if not deltas:
|
| 89 |
+
continue
|
| 90 |
+
setattr(attention, name, LoRALinear(getattr(attention, name), deltas, scale, switch))
|
| 91 |
+
installed += 1
|
| 92 |
+
return installed
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@dataclass(frozen=True)
|
| 96 |
+
class VideoFrames:
|
| 97 |
+
images: list[Image.Image]
|
| 98 |
+
indices: list[int]
|
| 99 |
+
total: int
|
| 100 |
+
fps: float | None
|
| 101 |
+
width: int
|
| 102 |
+
height: int
|
| 103 |
+
duration: float | None
|
| 104 |
+
|
| 105 |
+
@property
|
| 106 |
+
def metadata(self) -> VideoMetadata:
|
| 107 |
+
return VideoMetadata(
|
| 108 |
+
total_num_frames=self.total,
|
| 109 |
+
fps=self.fps,
|
| 110 |
+
width=self.width,
|
| 111 |
+
height=self.height,
|
| 112 |
+
duration=self.duration,
|
| 113 |
+
video_backend="pyav",
|
| 114 |
+
frames_indices=self.indices,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
@dataclass(frozen=True)
|
| 119 |
+
class VideoInputs:
|
| 120 |
+
input_ids: np.ndarray
|
| 121 |
+
attention_mask: np.ndarray
|
| 122 |
+
pixel_values: np.ndarray
|
| 123 |
+
grid: np.ndarray
|
| 124 |
+
audio_features: np.ndarray | None
|
| 125 |
+
audio_length: int | None
|
| 126 |
+
sampled_frames: int
|
| 127 |
+
total_frames: int
|
| 128 |
+
has_soundtrack: bool
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@dataclass(frozen=True)
|
| 132 |
+
class AudioInputs:
|
| 133 |
+
input_ids: np.ndarray
|
| 134 |
+
attention_mask: np.ndarray
|
| 135 |
+
features: np.ndarray
|
| 136 |
+
length: int
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def chunk_feed_forward(module, limit: int = SEQUENCE_CHUNK) -> None:
|
| 140 |
+
"""Split position-wise feed-forward work into bounded sequence chunks.
|
| 141 |
+
|
| 142 |
+
Positions are independent, so chunking preserves the computation while
|
| 143 |
+
limiting the work submitted in each GPU kernel.
|
| 144 |
+
"""
|
| 145 |
+
|
| 146 |
+
def chunked(original):
|
| 147 |
+
def wrapper(self, x):
|
| 148 |
+
axis = x.ndim - 2
|
| 149 |
+
length = x.shape[axis]
|
| 150 |
+
if length <= limit:
|
| 151 |
+
return original(self, x)
|
| 152 |
+
pieces = [original(self, x[..., start : start + limit, :]) for start in range(0, length, limit)]
|
| 153 |
+
return mx.concatenate(pieces, axis=axis)
|
| 154 |
+
|
| 155 |
+
return wrapper
|
| 156 |
+
|
| 157 |
+
for name in ("TextMLP", "VisionMLP"):
|
| 158 |
+
block = getattr(module, name, None)
|
| 159 |
+
if block is not None and not getattr(block, "_chunked", False):
|
| 160 |
+
block.__call__ = chunked(block.__call__)
|
| 161 |
+
block._chunked = True
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def imported_module(repo: Path):
|
| 165 |
+
path = repo / "model.py"
|
| 166 |
+
spec = importlib.util.spec_from_file_location(f"nanobe_mlx_omni_{repo.name}", path)
|
| 167 |
+
if not spec or not spec.loader:
|
| 168 |
+
raise RuntimeError(f"cannot import {path}")
|
| 169 |
+
module = importlib.util.module_from_spec(spec)
|
| 170 |
+
sys.modules[spec.name] = module
|
| 171 |
+
spec.loader.exec_module(module)
|
| 172 |
+
return module
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def digest(path: Path) -> str:
|
| 176 |
+
value = hashlib.sha256()
|
| 177 |
+
with path.open("rb") as file:
|
| 178 |
+
while chunk := file.read(1024 * 1024):
|
| 179 |
+
value.update(chunk)
|
| 180 |
+
return value.hexdigest()
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
SEQUENTIAL_DECODE_SECONDS = 60.0
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def uniform_indices(total: int, limit: int) -> list[int]:
|
| 187 |
+
if total <= 0:
|
| 188 |
+
return []
|
| 189 |
+
return np.linspace(0, total - 1, min(limit, total), dtype=int).tolist()
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def stream_geometry(stream, container) -> tuple[float | None, float | None]:
|
| 193 |
+
fps = float(stream.average_rate) if stream.average_rate else None
|
| 194 |
+
duration = (
|
| 195 |
+
float(stream.duration * stream.time_base)
|
| 196 |
+
if stream.duration and stream.time_base
|
| 197 |
+
else float(container.duration / av.time_base)
|
| 198 |
+
if container.duration
|
| 199 |
+
else None
|
| 200 |
+
)
|
| 201 |
+
return fps, duration
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def decode_indices(path: Path, indices: list[int]) -> VideoFrames:
|
| 205 |
+
"""Single sequential pass keeping the requested frame positions."""
|
| 206 |
+
wanted = set(indices)
|
| 207 |
+
images: list[Image.Image] = []
|
| 208 |
+
total = 0
|
| 209 |
+
with av.open(path) as container:
|
| 210 |
+
stream = container.streams.video[0]
|
| 211 |
+
fps, duration = stream_geometry(stream, container)
|
| 212 |
+
width = stream.width
|
| 213 |
+
height = stream.height
|
| 214 |
+
for position, frame in enumerate(container.decode(stream)):
|
| 215 |
+
total = position + 1
|
| 216 |
+
if position in wanted:
|
| 217 |
+
images.append(frame.to_image().convert("RGB"))
|
| 218 |
+
if wanted and position >= max(wanted):
|
| 219 |
+
break
|
| 220 |
+
return VideoFrames(images, indices, total, fps, width, height, duration)
|
| 221 |
+
|
| 222 |
+
|
| 223 |
+
def seek_sampled_video(
|
| 224 |
+
path: Path,
|
| 225 |
+
limit: int,
|
| 226 |
+
fps: float | None,
|
| 227 |
+
duration: float,
|
| 228 |
+
) -> VideoFrames:
|
| 229 |
+
"""Sample frames via keyframe seeks instead of decoding the whole file."""
|
| 230 |
+
rate = fps or 24.0
|
| 231 |
+
count = min(limit, max(1, round(duration * rate)))
|
| 232 |
+
targets = np.linspace(0.0, max(duration - 1.0 / rate, 0.0), count)
|
| 233 |
+
images: list[Image.Image] = []
|
| 234 |
+
indices: list[int] = []
|
| 235 |
+
with av.open(path) as container:
|
| 236 |
+
stream = container.streams.video[0]
|
| 237 |
+
width = stream.width
|
| 238 |
+
height = stream.height
|
| 239 |
+
reached = -1.0
|
| 240 |
+
for target in targets:
|
| 241 |
+
stamp = int(target / stream.time_base) if stream.time_base else 0
|
| 242 |
+
if target > reached:
|
| 243 |
+
container.seek(stamp, stream=stream, backward=True, any_frame=False)
|
| 244 |
+
chosen = None
|
| 245 |
+
for frame in container.decode(stream):
|
| 246 |
+
if frame.time is None or frame.time >= target - 1e-3:
|
| 247 |
+
chosen = frame
|
| 248 |
+
break
|
| 249 |
+
if chosen is None:
|
| 250 |
+
continue
|
| 251 |
+
reached = chosen.time if chosen.time is not None else target
|
| 252 |
+
index = round((chosen.time if chosen.time is not None else target) * rate)
|
| 253 |
+
if indices and index <= indices[-1]:
|
| 254 |
+
index = indices[-1] + 1
|
| 255 |
+
images.append(chosen.to_image().convert("RGB"))
|
| 256 |
+
indices.append(index)
|
| 257 |
+
total = max(round(duration * rate), (indices[-1] + 1) if indices else 0)
|
| 258 |
+
return VideoFrames(images, indices, total, fps, width, height, duration)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def span_sampled_video(
|
| 262 |
+
path: Path,
|
| 263 |
+
limit: int,
|
| 264 |
+
span: tuple[float, float],
|
| 265 |
+
) -> VideoFrames:
|
| 266 |
+
"""One seek to the span start, then a short sequential decode across it.
|
| 267 |
+
|
| 268 |
+
Frame positions and timestamps are relative to the selected span.
|
| 269 |
+
"""
|
| 270 |
+
start, end = span
|
| 271 |
+
with av.open(path) as container:
|
| 272 |
+
stream = container.streams.video[0]
|
| 273 |
+
fps, _ = stream_geometry(stream, container)
|
| 274 |
+
rate = fps or 24.0
|
| 275 |
+
length = max(end - start, 1.0 / rate)
|
| 276 |
+
count = min(limit, max(1, round(length * rate)))
|
| 277 |
+
targets = np.linspace(start, max(end - 1.0 / rate, start), count)
|
| 278 |
+
stamp = int(start / stream.time_base) if stream.time_base else 0
|
| 279 |
+
container.seek(stamp, stream=stream, backward=True, any_frame=False)
|
| 280 |
+
images: list[Image.Image] = []
|
| 281 |
+
indices: list[int] = []
|
| 282 |
+
position = 0
|
| 283 |
+
for frame in container.decode(stream):
|
| 284 |
+
moment = frame.time
|
| 285 |
+
if moment is None:
|
| 286 |
+
continue
|
| 287 |
+
if moment >= end + 0.5 / rate or position >= len(targets):
|
| 288 |
+
break
|
| 289 |
+
while position < len(targets) and moment >= targets[position] - 1e-3:
|
| 290 |
+
index = round((moment - start) * rate)
|
| 291 |
+
if indices and index <= indices[-1]:
|
| 292 |
+
index = indices[-1] + 1
|
| 293 |
+
images.append(frame.to_image().convert("RGB"))
|
| 294 |
+
indices.append(index)
|
| 295 |
+
position += 1
|
| 296 |
+
width = stream.width
|
| 297 |
+
height = stream.height
|
| 298 |
+
total = max(round(length * rate), (indices[-1] + 1) if indices else 0)
|
| 299 |
+
return VideoFrames(images, indices, total, fps, width, height, length)
|
| 300 |
+
|
| 301 |
+
|
| 302 |
+
def uniformly_sampled_video(
|
| 303 |
+
path: Path,
|
| 304 |
+
limit: int = VIDEO_FRAMES,
|
| 305 |
+
span: tuple[float, float] | None = None,
|
| 306 |
+
) -> VideoFrames:
|
| 307 |
+
if span is not None:
|
| 308 |
+
return span_sampled_video(path, limit, span)
|
| 309 |
+
with av.open(path) as container:
|
| 310 |
+
stream = container.streams.video[0]
|
| 311 |
+
declared = int(stream.frames or 0)
|
| 312 |
+
fps, duration = stream_geometry(stream, container)
|
| 313 |
+
if duration and math.isfinite(duration) and duration > SEQUENTIAL_DECODE_SECONDS:
|
| 314 |
+
sampled = seek_sampled_video(path, limit, fps, duration)
|
| 315 |
+
if sampled.images:
|
| 316 |
+
return sampled
|
| 317 |
+
provisional = decode_indices(path, uniform_indices(declared, limit))
|
| 318 |
+
exact = uniform_indices(provisional.total, limit)
|
| 319 |
+
if exact == provisional.indices and len(provisional.images) == len(exact):
|
| 320 |
+
return provisional
|
| 321 |
+
return decode_indices(path, exact)
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def decode_soundtrack(
|
| 325 |
+
path: Path,
|
| 326 |
+
span: tuple[float, float] | None = None,
|
| 327 |
+
) -> np.ndarray | None:
|
| 328 |
+
with av.open(path) as container:
|
| 329 |
+
stream = next((stream for stream in container.streams if stream.type == "audio"), None)
|
| 330 |
+
if stream is None:
|
| 331 |
+
return None
|
| 332 |
+
if span is not None and stream.time_base:
|
| 333 |
+
container.seek(int(span[0] / stream.time_base), stream=stream, backward=True)
|
| 334 |
+
resampler = AudioResampler(format="flt", layout="mono", rate=16_000)
|
| 335 |
+
samples = []
|
| 336 |
+
for frame in container.decode(stream):
|
| 337 |
+
if span is not None and frame.time is not None and frame.time >= span[1]:
|
| 338 |
+
break
|
| 339 |
+
for output in resampler.resample(frame):
|
| 340 |
+
chunk = output.to_ndarray().flatten()
|
| 341 |
+
if span is not None and frame.time is not None and frame.time < span[0]:
|
| 342 |
+
ahead = span[0] - frame.time
|
| 343 |
+
skip = min(int(ahead * 16_000), len(chunk))
|
| 344 |
+
chunk = chunk[skip:]
|
| 345 |
+
if len(chunk):
|
| 346 |
+
samples.append(chunk)
|
| 347 |
+
samples.extend(output.to_ndarray().flatten() for output in resampler.resample(None))
|
| 348 |
+
samples = [chunk for chunk in samples if len(chunk)]
|
| 349 |
+
if not samples:
|
| 350 |
+
return None
|
| 351 |
+
waveform = np.concatenate(samples).astype(np.float32)
|
| 352 |
+
if span is not None:
|
| 353 |
+
waveform = waveform[: int((span[1] - span[0]) * 16_000)]
|
| 354 |
+
return waveform if len(waveform) else None
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def bounded_waveform(
|
| 358 |
+
waveform: np.ndarray,
|
| 359 |
+
budget_seconds: float | None,
|
| 360 |
+
slices: int = 8,
|
| 361 |
+
) -> np.ndarray:
|
| 362 |
+
"""Bound a whole-file waveform to a strided summary of budget_seconds.
|
| 363 |
+
|
| 364 |
+
Long soundtracks otherwise become tens of thousands of audio tokens in
|
| 365 |
+
one forward; windows already cover locality, so the whole-file lane
|
| 366 |
+
only needs a bounded overview.
|
| 367 |
+
"""
|
| 368 |
+
if budget_seconds is None:
|
| 369 |
+
return waveform
|
| 370 |
+
budget = int(budget_seconds * 16_000)
|
| 371 |
+
if len(waveform) <= budget:
|
| 372 |
+
return waveform
|
| 373 |
+
span = budget // slices
|
| 374 |
+
starts = np.linspace(0, len(waveform) - span, slices).astype(int)
|
| 375 |
+
return np.concatenate([waveform[start : start + span] for start in starts])
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def audio_tokens(frames: int) -> int:
|
| 379 |
+
after_first_convolution = (frames - 1) // 2 + 1
|
| 380 |
+
return max((after_first_convolution - 2) // 2 + 1, 1)
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def configure_processor(processor: object) -> None:
|
| 384 |
+
video = processor.video_processor
|
| 385 |
+
if hasattr(video, "do_sample_frames"):
|
| 386 |
+
video.do_sample_frames = False
|
| 387 |
+
for attribute in ("max_frames", "num_frames"):
|
| 388 |
+
if hasattr(video, attribute):
|
| 389 |
+
setattr(video, attribute, VIDEO_FRAMES)
|
| 390 |
+
if hasattr(video, "size") and isinstance(video.size, dict):
|
| 391 |
+
video.size = {
|
| 392 |
+
**video.size,
|
| 393 |
+
"longest_edge": VIDEO_MAX_PIXELS,
|
| 394 |
+
"shortest_edge": MEDIA_MIN_PIXELS,
|
| 395 |
+
}
|
| 396 |
+
if hasattr(video, "max_pixels"):
|
| 397 |
+
video.max_pixels = VIDEO_MAX_PIXELS
|
| 398 |
+
if hasattr(video, "min_pixels"):
|
| 399 |
+
video.min_pixels = MEDIA_MIN_PIXELS
|
| 400 |
+
|
| 401 |
+
# The image processor uses a pixel-area budget independent of video limits.
|
| 402 |
+
images = processor.image_processor
|
| 403 |
+
if hasattr(images, "size") and images.size is not None:
|
| 404 |
+
images.size = {
|
| 405 |
+
**dict(images.size),
|
| 406 |
+
"longest_edge": int(dict(images.size)["longest_edge"] * IMAGE_PIXEL_SHARE),
|
| 407 |
+
"shortest_edge": int(dict(images.size)["shortest_edge"] * IMAGE_PIXEL_SHARE),
|
| 408 |
+
}
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
def array(value: object) -> np.ndarray:
|
| 412 |
+
if hasattr(value, "detach"):
|
| 413 |
+
return value.detach().cpu().numpy()
|
| 414 |
+
return np.asarray(value)
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def normalize(value: np.ndarray) -> np.ndarray:
|
| 418 |
+
vector = np.asarray(value, dtype=np.float32).reshape(-1)
|
| 419 |
+
norm = float(np.linalg.norm(vector))
|
| 420 |
+
if not math.isfinite(norm) or norm <= 0:
|
| 421 |
+
raise RuntimeError("embedding has an invalid norm")
|
| 422 |
+
return vector / norm
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
def prepare_audio(
|
| 426 |
+
processor: object,
|
| 427 |
+
extractor: WhisperFeatureExtractor,
|
| 428 |
+
config: dict,
|
| 429 |
+
waveform: np.ndarray,
|
| 430 |
+
) -> AudioInputs:
|
| 431 |
+
if not np.isfinite(waveform).all():
|
| 432 |
+
waveform = np.nan_to_num(waveform, nan=0.0, posinf=0.0, neginf=0.0)
|
| 433 |
+
peak = float(np.max(np.abs(waveform))) if waveform.size else 0.0
|
| 434 |
+
if peak > 1:
|
| 435 |
+
waveform = waveform / peak
|
| 436 |
+
features = extractor(
|
| 437 |
+
waveform,
|
| 438 |
+
sampling_rate=16_000,
|
| 439 |
+
return_tensors="np",
|
| 440 |
+
padding=False,
|
| 441 |
+
truncation=False,
|
| 442 |
+
return_attention_mask=True,
|
| 443 |
+
)
|
| 444 |
+
mel = np.asarray(features["input_features"][0], dtype=np.float32)
|
| 445 |
+
length = int(features["attention_mask"][0].sum())
|
| 446 |
+
count = audio_tokens(length)
|
| 447 |
+
tokenizer = processor.tokenizer
|
| 448 |
+
start = tokenizer.convert_ids_to_tokens(config["audio_start_token_id"])
|
| 449 |
+
token = tokenizer.convert_ids_to_tokens(config["audio_token_id"])
|
| 450 |
+
end = tokenizer.convert_ids_to_tokens(config["audio_end_token_id"])
|
| 451 |
+
prompt = processor.apply_chat_template(
|
| 452 |
+
[{"role": "user", "content": start + token * count + end}],
|
| 453 |
+
tokenize=False,
|
| 454 |
+
add_generation_prompt=False,
|
| 455 |
+
)
|
| 456 |
+
encoded = processor(
|
| 457 |
+
text=[prompt],
|
| 458 |
+
return_tensors="pt",
|
| 459 |
+
padding=False,
|
| 460 |
+
truncation=False,
|
| 461 |
+
)
|
| 462 |
+
input_ids = array(encoded["input_ids"])
|
| 463 |
+
if int(np.sum(input_ids == config["audio_token_id"])) != count:
|
| 464 |
+
raise RuntimeError("audio placeholders do not match extracted features")
|
| 465 |
+
return AudioInputs(
|
| 466 |
+
input_ids=input_ids,
|
| 467 |
+
attention_mask=array(encoded["attention_mask"]),
|
| 468 |
+
features=mel,
|
| 469 |
+
length=length,
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
|
| 473 |
+
def prepare_video(
|
| 474 |
+
processor: object,
|
| 475 |
+
extractor: WhisperFeatureExtractor,
|
| 476 |
+
config: dict,
|
| 477 |
+
path: Path,
|
| 478 |
+
include_soundtrack: bool = True,
|
| 479 |
+
span: tuple[float, float] | None = None,
|
| 480 |
+
audio_budget: float | None = None,
|
| 481 |
+
) -> VideoInputs:
|
| 482 |
+
video = uniformly_sampled_video(path, span=span)
|
| 483 |
+
if not video.images:
|
| 484 |
+
raise RuntimeError(f"video decoded no frames: {path}")
|
| 485 |
+
|
| 486 |
+
soundtrack = decode_soundtrack(path, span=span) if include_soundtrack else None
|
| 487 |
+
if soundtrack is not None and span is None:
|
| 488 |
+
soundtrack = bounded_waveform(soundtrack, audio_budget)
|
| 489 |
+
features = None
|
| 490 |
+
length = None
|
| 491 |
+
audio = ""
|
| 492 |
+
if soundtrack is not None:
|
| 493 |
+
prepared = prepare_audio(processor, extractor, config, soundtrack)
|
| 494 |
+
features = prepared.features
|
| 495 |
+
length = prepared.length
|
| 496 |
+
count = audio_tokens(length)
|
| 497 |
+
tokenizer = processor.tokenizer
|
| 498 |
+
start = tokenizer.convert_ids_to_tokens(config["audio_start_token_id"])
|
| 499 |
+
token = tokenizer.convert_ids_to_tokens(config["audio_token_id"])
|
| 500 |
+
end = tokenizer.convert_ids_to_tokens(config["audio_end_token_id"])
|
| 501 |
+
audio = start + token * count + end
|
| 502 |
+
|
| 503 |
+
video_token = getattr(processor, "video_token", "<|video_pad|>")
|
| 504 |
+
content = f"{audio}<|vision_start|>{video_token}<|vision_end|>"
|
| 505 |
+
prompt = processor.apply_chat_template(
|
| 506 |
+
[{"role": "user", "content": content}],
|
| 507 |
+
tokenize=False,
|
| 508 |
+
add_generation_prompt=False,
|
| 509 |
+
)
|
| 510 |
+
sentinel = object()
|
| 511 |
+
previous_metadata = getattr(processor.video_processor, "video_metadata", sentinel)
|
| 512 |
+
processor.video_processor.video_metadata = [video.metadata]
|
| 513 |
+
try:
|
| 514 |
+
encoded = processor(
|
| 515 |
+
text=[prompt],
|
| 516 |
+
videos=[video.images],
|
| 517 |
+
return_tensors="pt",
|
| 518 |
+
padding=False,
|
| 519 |
+
truncation=False,
|
| 520 |
+
)
|
| 521 |
+
finally:
|
| 522 |
+
for image in video.images:
|
| 523 |
+
image.close()
|
| 524 |
+
if previous_metadata is sentinel:
|
| 525 |
+
delattr(processor.video_processor, "video_metadata")
|
| 526 |
+
else:
|
| 527 |
+
processor.video_processor.video_metadata = previous_metadata
|
| 528 |
+
|
| 529 |
+
input_ids = array(encoded["input_ids"])
|
| 530 |
+
grid = array(encoded["video_grid_thw"])
|
| 531 |
+
audio_count = int(np.sum(input_ids == config["audio_token_id"]))
|
| 532 |
+
video_count = int(np.sum(input_ids == config["video_token_id"]))
|
| 533 |
+
expected_audio = audio_tokens(length) if length is not None else 0
|
| 534 |
+
expected_video = int(np.prod(grid) // 4)
|
| 535 |
+
if audio_count != expected_audio:
|
| 536 |
+
raise RuntimeError(f"audio placeholders {audio_count} do not match features {expected_audio}")
|
| 537 |
+
if video_count != expected_video:
|
| 538 |
+
raise RuntimeError(f"video placeholders {video_count} do not match features {expected_video}")
|
| 539 |
+
if audio_count:
|
| 540 |
+
audio_positions = np.flatnonzero(input_ids == config["audio_token_id"])
|
| 541 |
+
video_positions = np.flatnonzero(input_ids == config["video_token_id"])
|
| 542 |
+
if int(audio_positions[-1]) >= int(video_positions[0]):
|
| 543 |
+
raise RuntimeError("audio placeholders must precede video placeholders")
|
| 544 |
+
|
| 545 |
+
return VideoInputs(
|
| 546 |
+
input_ids=input_ids,
|
| 547 |
+
attention_mask=array(encoded["attention_mask"]),
|
| 548 |
+
pixel_values=array(encoded["pixel_values_videos"]),
|
| 549 |
+
grid=grid,
|
| 550 |
+
audio_features=features,
|
| 551 |
+
audio_length=length,
|
| 552 |
+
sampled_frames=len(video.images),
|
| 553 |
+
total_frames=video.total,
|
| 554 |
+
has_soundtrack=soundtrack is not None,
|
| 555 |
+
)
|
| 556 |
+
|
| 557 |
+
|
| 558 |
+
def fuse_soundtrack(visual: np.ndarray, sound: np.ndarray, weight: float) -> np.ndarray:
|
| 559 |
+
"""A video's native: the unit sum of its own native and the weighted unit
|
| 560 |
+
native of its soundtrack through the audio path."""
|
| 561 |
+
return normalize(normalize(visual) + weight * normalize(sound))
|
| 562 |
+
|
| 563 |
+
|
| 564 |
+
class Runtime:
|
| 565 |
+
def __init__(self, model: Path, processor: Path | None = None):
|
| 566 |
+
started = time.perf_counter()
|
| 567 |
+
self.geometry = geometry_contract(model)
|
| 568 |
+
self.repo = model
|
| 569 |
+
self.module = imported_module(model)
|
| 570 |
+
chunk_feed_forward(self.module)
|
| 571 |
+
self.config_values = json.loads((model / "config.json").read_text())
|
| 572 |
+
config_name = next(
|
| 573 |
+
name for name in dir(self.module) if name.startswith("Omni") and name.endswith("Config")
|
| 574 |
+
)
|
| 575 |
+
model_name = next(
|
| 576 |
+
name
|
| 577 |
+
for name in dir(self.module)
|
| 578 |
+
if name.startswith("Jina") and name.endswith("EmbeddingModel")
|
| 579 |
+
)
|
| 580 |
+
config = getattr(self.module, config_name).from_dict(self.config_values)
|
| 581 |
+
self.model = getattr(self.module, model_name)(config)
|
| 582 |
+
manifest = json.loads((model / "manifest.json").read_text())
|
| 583 |
+
if manifest.get("component") != "full":
|
| 584 |
+
raise ValueError("audiovisual indexing requires the full one-model runtime")
|
| 585 |
+
self._quantize(manifest)
|
| 586 |
+
weights: dict[str, mx.array] = {}
|
| 587 |
+
for shard in manifest["weight_shards"]:
|
| 588 |
+
loaded = mx.load(str(model / shard["file"]))
|
| 589 |
+
overlap = weights.keys() & loaded.keys()
|
| 590 |
+
if overlap:
|
| 591 |
+
raise RuntimeError(f"runtime shards overlap at {sorted(overlap)[0]}")
|
| 592 |
+
weights.update(loaded)
|
| 593 |
+
self.model.load_weights(list(self.model.sanitize(weights).items()))
|
| 594 |
+
mx.eval(self.model.parameters())
|
| 595 |
+
self.media = MediaSwitch()
|
| 596 |
+
self._install_media_adapter(model, manifest["media_adapter"])
|
| 597 |
+
self._install_page_merger(model, manifest["page_merger"])
|
| 598 |
+
self._install_audio_pooler(model, manifest["audio_pooler"])
|
| 599 |
+
self.processor = AutoProcessor.from_pretrained(processor or model, trust_remote_code=True)
|
| 600 |
+
# Preserve the full image budget for rendered pages before reducing it for pictures.
|
| 601 |
+
self.page_pixels = dict(self.processor.image_processor.size)
|
| 602 |
+
configure_processor(self.processor)
|
| 603 |
+
self.extractor = WhisperFeatureExtractor(feature_size=128)
|
| 604 |
+
self.acoustic = None
|
| 605 |
+
checkpoint = model / "head"
|
| 606 |
+
if self.geometry["head_sha256"] != digest(checkpoint / "head.safetensors"):
|
| 607 |
+
raise RuntimeError("packaged model geometry and checkpoint hashes disagree")
|
| 608 |
+
self.head = load_file(checkpoint / "head.safetensors")
|
| 609 |
+
self.base_dimension = sum(self.head["document.weight"].shape)
|
| 610 |
+
self.magnitude = AudioMagnitude(model, self.geometry)
|
| 611 |
+
self.cold_load_ms = (time.perf_counter() - started) * 1000
|
| 612 |
+
|
| 613 |
+
def _quantize(self, manifest: dict) -> None:
|
| 614 |
+
quantization = manifest["quantization"]
|
| 615 |
+
targets = set(quantization["targets"])
|
| 616 |
+
|
| 617 |
+
def params(path: str, layer: nn.Module) -> bool | dict:
|
| 618 |
+
target = ("linear" in targets and isinstance(layer, nn.Linear)) or (
|
| 619 |
+
"embedding" in targets and isinstance(layer, nn.Embedding)
|
| 620 |
+
)
|
| 621 |
+
if not target:
|
| 622 |
+
return False
|
| 623 |
+
root = path.split(".", 1)[0]
|
| 624 |
+
bits = quantization.get("overrides", {}).get(root, quantization["bits"])
|
| 625 |
+
if isinstance(layer, nn.Embedding):
|
| 626 |
+
bits = quantization.get("embedding_overrides", {}).get(root, bits)
|
| 627 |
+
return {
|
| 628 |
+
"group_size": quantization["group_size"],
|
| 629 |
+
"bits": bits,
|
| 630 |
+
"mode": quantization["mode"],
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
nn.quantize(
|
| 634 |
+
self.model,
|
| 635 |
+
group_size=quantization["group_size"],
|
| 636 |
+
bits=quantization["bits"],
|
| 637 |
+
mode=quantization["mode"],
|
| 638 |
+
class_predicate=params,
|
| 639 |
+
)
|
| 640 |
+
|
| 641 |
+
def _install_media_adapter(self, model: Path, spec: dict) -> None:
|
| 642 |
+
path = model / spec["file"]
|
| 643 |
+
if path.parent != model or digest(path) != spec["sha256"]:
|
| 644 |
+
raise RuntimeError("model package media adapter failed integrity verification")
|
| 645 |
+
tensors = mx.load(str(path))
|
| 646 |
+
installed = install_media_adapter(self.model.language_model, tensors, float(spec["scale"]), self.media)
|
| 647 |
+
expected = int(spec["tensors"]) // 2 // len(spec["applies_to"])
|
| 648 |
+
if installed != expected:
|
| 649 |
+
raise RuntimeError(f"media adapter installed {installed} projections, manifest expects {expected}")
|
| 650 |
+
mx.eval(self.model.parameters())
|
| 651 |
+
|
| 652 |
+
def _install_page_merger(self, model: Path, spec: dict) -> None:
|
| 653 |
+
"""Load the unquantized merger used only for rendered document pages.
|
| 654 |
+
|
| 655 |
+
Pages share the base vision and language weights with other inputs.
|
| 656 |
+
"""
|
| 657 |
+
path = model / spec["file"]
|
| 658 |
+
if path.parent != model or digest(path) != spec["sha256"]:
|
| 659 |
+
raise RuntimeError("model package page merger failed integrity verification")
|
| 660 |
+
tensors = mx.load(str(path))
|
| 661 |
+
if len(tensors) != int(spec["tensors"]):
|
| 662 |
+
raise RuntimeError(f"page merger holds {len(tensors)} tensors, manifest expects {spec['tensors']}")
|
| 663 |
+
merger = self.module.Merger(self.model.config.vision_config, self.model.config.text_config.hidden_size)
|
| 664 |
+
dtype = self.model.merger.norm.weight.dtype
|
| 665 |
+
merger.load_weights([(name, value.astype(dtype)) for name, value in tensors.items()])
|
| 666 |
+
mx.eval(merger.parameters())
|
| 667 |
+
self.page_merger = merger
|
| 668 |
+
|
| 669 |
+
def _install_audio_pooler(self, model: Path, spec: dict) -> None:
|
| 670 |
+
"""Load attention and residual weights for pooling encoded audio windows.
|
| 671 |
+
|
| 672 |
+
The NumPy pooler adds one segment per recording at index time.
|
| 673 |
+
"""
|
| 674 |
+
path = model / spec["file"]
|
| 675 |
+
if path.parent != model or digest(path) != spec["sha256"]:
|
| 676 |
+
raise RuntimeError("model package audio pooler failed integrity verification")
|
| 677 |
+
from safetensors.numpy import load_file
|
| 678 |
+
|
| 679 |
+
tensors = load_file(str(path))
|
| 680 |
+
if len(tensors) != int(spec["tensors"]):
|
| 681 |
+
raise RuntimeError(f"audio pooler holds {len(tensors)} tensors, manifest expects {spec['tensors']}")
|
| 682 |
+
self.audio_pooler = {name: value.astype(np.float32) for name, value in tensors.items()}
|
| 683 |
+
|
| 684 |
+
def pool_audio_windows(self, whole: np.ndarray, windows: list[np.ndarray]) -> np.ndarray:
|
| 685 |
+
"""The pooled row: the whole-file vector with its current block replaced by the
|
| 686 |
+
pooler's output over the windows' native blocks, the raw acoustic block and the
|
| 687 |
+
magnitude tail left as the recording's own."""
|
| 688 |
+
head = self.audio_pooler
|
| 689 |
+
if not windows:
|
| 690 |
+
raise ValueError("audio pooling requires at least one window")
|
| 691 |
+
natives = np.stack([np.asarray(w, np.float32)[640:1664] for w in windows])
|
| 692 |
+
natives /= np.linalg.norm(natives, axis=1, keepdims=True)
|
| 693 |
+
hidden = np.tanh(natives @ head["attend.0.weight"].T + head["attend.0.bias"])
|
| 694 |
+
logits = hidden @ head["attend.2.weight"].T + head["attend.2.bias"]
|
| 695 |
+
weights = np.exp(logits - logits.max())
|
| 696 |
+
weights /= weights.sum()
|
| 697 |
+
pooled = (weights * natives).sum(axis=0)
|
| 698 |
+
gelu = lambda x: 0.5 * x * (1.0 + np.tanh(np.sqrt(2.0 / np.pi) * (x + 0.044715 * x ** 3)))
|
| 699 |
+
pooled = pooled + gelu(pooled @ head["refine.0.weight"].T + head["refine.0.bias"]) @ head["refine.2.weight"].T + head["refine.2.bias"]
|
| 700 |
+
pooled /= np.linalg.norm(pooled)
|
| 701 |
+
output = np.asarray(whole, np.float32).copy()
|
| 702 |
+
scale = float(np.linalg.norm(output[:1664]))
|
| 703 |
+
output[:640] = 0.0
|
| 704 |
+
output[640:1664] = pooled * scale
|
| 705 |
+
return output
|
| 706 |
+
|
| 707 |
+
@contextmanager
|
| 708 |
+
def page_forward(self):
|
| 709 |
+
"""The page's own budget and merger stand in for the shared ones inside this block."""
|
| 710 |
+
images = self.processor.image_processor
|
| 711 |
+
shared_size, shared_merger = images.size, self.model.merger
|
| 712 |
+
images.size = dict(self.page_pixels)
|
| 713 |
+
self.model.merger = self.page_merger
|
| 714 |
+
try:
|
| 715 |
+
yield
|
| 716 |
+
finally:
|
| 717 |
+
images.size = shared_size
|
| 718 |
+
self.model.merger = shared_merger
|
| 719 |
+
|
| 720 |
+
@contextmanager
|
| 721 |
+
def media_forward(self, modality: str):
|
| 722 |
+
"""The modality's adapter is live only inside this block."""
|
| 723 |
+
self.media.modality = modality
|
| 724 |
+
try:
|
| 725 |
+
yield
|
| 726 |
+
finally:
|
| 727 |
+
self.media.modality = None
|
| 728 |
+
|
| 729 |
+
@property
|
| 730 |
+
def audio_budget(self) -> float:
|
| 731 |
+
return float(self.geometry["audio_budget_seconds"])
|
| 732 |
+
|
| 733 |
+
def encode_video(
|
| 734 |
+
self,
|
| 735 |
+
path: Path,
|
| 736 |
+
include_soundtrack: bool = True,
|
| 737 |
+
span: tuple[float, float] | None = None,
|
| 738 |
+
) -> tuple[np.ndarray, VideoInputs]:
|
| 739 |
+
inputs = prepare_video(
|
| 740 |
+
self.processor,
|
| 741 |
+
self.extractor,
|
| 742 |
+
self.config_values,
|
| 743 |
+
path,
|
| 744 |
+
include_soundtrack,
|
| 745 |
+
span=span,
|
| 746 |
+
audio_budget=self.audio_budget,
|
| 747 |
+
)
|
| 748 |
+
ids = mx.array(inputs.input_ids)
|
| 749 |
+
mask = mx.array(inputs.attention_mask)
|
| 750 |
+
vision = self.model.vision_tower(mx.array(inputs.pixel_values), mx.array(inputs.grid))
|
| 751 |
+
vision = self.model.merger(vision)
|
| 752 |
+
embedded = self.model.language_model.embed_tokens(ids)
|
| 753 |
+
embedded = self.model._inject_features(
|
| 754 |
+
ids,
|
| 755 |
+
embedded,
|
| 756 |
+
self.model.config.video_token_id,
|
| 757 |
+
vision,
|
| 758 |
+
)
|
| 759 |
+
if inputs.audio_features is not None and inputs.audio_length is not None:
|
| 760 |
+
audio = self.model.audio_tower(
|
| 761 |
+
mx.array(inputs.audio_features),
|
| 762 |
+
mx.array([inputs.audio_length]),
|
| 763 |
+
)
|
| 764 |
+
audio = self.model.audio_projector(audio)
|
| 765 |
+
embedded = self.model._inject_features(
|
| 766 |
+
ids,
|
| 767 |
+
embedded,
|
| 768 |
+
self.model.config.audio_token_id,
|
| 769 |
+
audio,
|
| 770 |
+
)
|
| 771 |
+
attention = self.module._causal_pad_mask(
|
| 772 |
+
mask,
|
| 773 |
+
ids.shape[1],
|
| 774 |
+
self.model.language_model.layers[0].input_layernorm.weight,
|
| 775 |
+
)
|
| 776 |
+
hidden = self.model.language_model(inputs_embeds=embedded, mask=attention)
|
| 777 |
+
vector = self.module._last_token_pool(hidden, mask)[0]
|
| 778 |
+
mx.eval(vector)
|
| 779 |
+
return normalize(np.asarray(vector.astype(mx.float32), dtype=np.float32)), inputs
|
| 780 |
+
|
| 781 |
+
def encode_text(self, text: str, side: str) -> np.ndarray:
|
| 782 |
+
prefix = "Query" if side == "query" else "Document"
|
| 783 |
+
encoded = self.processor.tokenizer(
|
| 784 |
+
f"{prefix}: {text}",
|
| 785 |
+
return_tensors="np",
|
| 786 |
+
padding=False,
|
| 787 |
+
truncation=True,
|
| 788 |
+
max_length=32_768,
|
| 789 |
+
)
|
| 790 |
+
# Text documents use their adapter; queries use the base weights.
|
| 791 |
+
with self.media_forward("text") if side != "query" else nullcontext():
|
| 792 |
+
value = self.model.encode_text(
|
| 793 |
+
mx.array(encoded["input_ids"]),
|
| 794 |
+
mx.array(encoded["attention_mask"]),
|
| 795 |
+
)[0]
|
| 796 |
+
mx.eval(value)
|
| 797 |
+
return normalize(np.asarray(value.astype(mx.float32), dtype=np.float32))
|
| 798 |
+
|
| 799 |
+
def encode_image(self, image: Image.Image) -> np.ndarray:
|
| 800 |
+
token = getattr(self.processor, "image_token", "<|image_pad|>")
|
| 801 |
+
prompt = self.processor.apply_chat_template(
|
| 802 |
+
[{"role": "user", "content": f"Document: <|vision_start|>{token}<|vision_end|>"}],
|
| 803 |
+
tokenize=False,
|
| 804 |
+
add_generation_prompt=False,
|
| 805 |
+
)
|
| 806 |
+
encoded = self.processor(
|
| 807 |
+
images=[image.convert("RGB")],
|
| 808 |
+
text=[prompt],
|
| 809 |
+
return_tensors="pt",
|
| 810 |
+
padding=False,
|
| 811 |
+
truncation=False,
|
| 812 |
+
)
|
| 813 |
+
value = self.model.encode_image(
|
| 814 |
+
mx.array(array(encoded["pixel_values"])),
|
| 815 |
+
mx.array(array(encoded["image_grid_thw"])),
|
| 816 |
+
mx.array(array(encoded["input_ids"])),
|
| 817 |
+
mx.array(array(encoded["attention_mask"])),
|
| 818 |
+
)[0]
|
| 819 |
+
mx.eval(value)
|
| 820 |
+
return normalize(np.asarray(value.astype(mx.float32), dtype=np.float32))
|
| 821 |
+
|
| 822 |
+
def encode_audio(self, path: Path, span: tuple[float, float] | None = None) -> np.ndarray:
|
| 823 |
+
return self._audio_vectors(path, span)[0]
|
| 824 |
+
|
| 825 |
+
def _audio_vectors(
|
| 826 |
+
self, path: Path, span: tuple[float, float] | None = None,
|
| 827 |
+
) -> tuple[np.ndarray, np.ndarray | None]:
|
| 828 |
+
"""Return the current native and the raw PE feature already used to refine it."""
|
| 829 |
+
waveform = decode_soundtrack(path, span=span)
|
| 830 |
+
if waveform is None:
|
| 831 |
+
raise RuntimeError(f"audio file decoded no samples: {path}")
|
| 832 |
+
if span is None:
|
| 833 |
+
waveform = bounded_waveform(waveform, self.audio_budget)
|
| 834 |
+
inputs = prepare_audio(self.processor, self.extractor, self.config_values, waveform)
|
| 835 |
+
value = self.model.encode_audio(
|
| 836 |
+
mx.array(inputs.features),
|
| 837 |
+
mx.array([inputs.length]),
|
| 838 |
+
mx.array(inputs.input_ids),
|
| 839 |
+
mx.array(inputs.attention_mask),
|
| 840 |
+
)[0]
|
| 841 |
+
mx.eval(value)
|
| 842 |
+
current = normalize(np.asarray(value.astype(mx.float32), dtype=np.float32))
|
| 843 |
+
projection = self.geometry["audio_projection"]
|
| 844 |
+
if span is not None:
|
| 845 |
+
return current, None
|
| 846 |
+
pe = self.encode_acoustic(path, projection)
|
| 847 |
+
aligned = normalize(normalize(pe) @ self.head[projection["weight"]].T + self.head[projection["bias"]])
|
| 848 |
+
share = float(projection["share"])
|
| 849 |
+
return normalize((1 - share) * current + share * aligned), pe
|
| 850 |
+
|
| 851 |
+
def encode_acoustic(self, path: Path, projection: dict) -> np.ndarray:
|
| 852 |
+
if self.acoustic is None:
|
| 853 |
+
folder = self.repo / projection["encoder"]
|
| 854 |
+
manifest = folder / "manifest.json"
|
| 855 |
+
if folder.parent != self.repo or digest(manifest) != projection["encoder_sha256"]:
|
| 856 |
+
raise RuntimeError("packaged acoustic encoder failed integrity verification")
|
| 857 |
+
metadata = json.loads(manifest.read_text())
|
| 858 |
+
for name, checksum in metadata["files"].items():
|
| 859 |
+
source = folder / name
|
| 860 |
+
if source.parent != folder or digest(source) != checksum:
|
| 861 |
+
raise RuntimeError(f"packaged acoustic encoder file failed integrity verification: {name}")
|
| 862 |
+
spec = importlib.util.spec_from_file_location(f"nanobe_acoustic_{self.repo.name}", folder / "nanobe_mlx_pe.py")
|
| 863 |
+
if not spec or not spec.loader:
|
| 864 |
+
raise RuntimeError("cannot import the packaged acoustic encoder")
|
| 865 |
+
module = importlib.util.module_from_spec(spec)
|
| 866 |
+
sys.modules[spec.name] = module
|
| 867 |
+
spec.loader.exec_module(module)
|
| 868 |
+
self.acoustic = module.Runtime(folder)
|
| 869 |
+
with av.open(path) as container:
|
| 870 |
+
stream = next((stream for stream in container.streams if stream.type == "audio"), None)
|
| 871 |
+
if stream is None or not stream.codec_context.sample_rate:
|
| 872 |
+
raise RuntimeError(f"audio file has no sample rate: {path}")
|
| 873 |
+
rate = stream.codec_context.sample_rate
|
| 874 |
+
# PE uses the equal channel mean at the original rate. A mono
|
| 875 |
+
# downmix matrix changes stereo gain before its nonlinear encoder.
|
| 876 |
+
resampler = AudioResampler(format="fltp", layout=stream.layout, rate=rate)
|
| 877 |
+
chunks = [output.to_ndarray().mean(axis=0) for frame in container.decode(stream)
|
| 878 |
+
for output in resampler.resample(frame)]
|
| 879 |
+
chunks.extend(output.to_ndarray().mean(axis=0) for output in resampler.resample(None))
|
| 880 |
+
if not chunks or not sum(map(len, chunks)):
|
| 881 |
+
raise RuntimeError(f"audio file decoded no samples: {path}")
|
| 882 |
+
waveform = np.concatenate(chunks).astype(np.float32)
|
| 883 |
+
return self.acoustic.encode(waveform, rate)
|
| 884 |
+
|
| 885 |
+
def project_media(self, raw: np.ndarray, modality: str) -> np.ndarray:
|
| 886 |
+
native = np.asarray(raw, dtype=np.float32).reshape(-1)
|
| 887 |
+
raw = normalize(native)
|
| 888 |
+
if raw.shape != (self.head["document.weight"].shape[1],):
|
| 889 |
+
raise RuntimeError("media embedding does not match the projection head")
|
| 890 |
+
if modality in self.geometry["native_lane_media"]:
|
| 891 |
+
# Use the text-document scale for speech vectors in the native lane.
|
| 892 |
+
return self.project_document(raw)
|
| 893 |
+
modality_id = MODALITIES.index(modality)
|
| 894 |
+
head = raw @ self.head["document.weight"].T + self.head["document_modality.weight"][modality_id]
|
| 895 |
+
head = normalize(head)
|
| 896 |
+
output = np.zeros(self.base_dimension, dtype=np.float32)
|
| 897 |
+
output[: len(head)] = head
|
| 898 |
+
projection = self.geometry["image_projection"]
|
| 899 |
+
if modality == "image":
|
| 900 |
+
projected = native @ self.head[projection["weight"]].T + self.head[projection["bias"]]
|
| 901 |
+
output[len(head) :] = float(projection["epsilon"]) * normalize(projected)
|
| 902 |
+
return normalize(output)
|
| 903 |
+
return output
|
| 904 |
+
|
| 905 |
+
def project_video(self, raw: np.ndarray) -> np.ndarray:
|
| 906 |
+
return self.project_media(raw, "video")
|
| 907 |
+
|
| 908 |
+
def project_document(self, raw: np.ndarray, refine: bool = False) -> np.ndarray:
|
| 909 |
+
native = normalize(raw)
|
| 910 |
+
if refine:
|
| 911 |
+
# The projection requires unit native vectors; keep it after normalization.
|
| 912 |
+
residual = (native @ self.head["document_refine.down"].T) @ self.head["document_refine.up"].T
|
| 913 |
+
native = normalize(native + residual)
|
| 914 |
+
head_dimension = int(self.head["document.weight"].shape[0])
|
| 915 |
+
dimension = self.base_dimension
|
| 916 |
+
if head_dimension + len(native) != dimension:
|
| 917 |
+
raise RuntimeError("native embedding does not match the protected geometry")
|
| 918 |
+
output = np.zeros(dimension, dtype=np.float32)
|
| 919 |
+
output[head_dimension:] = native
|
| 920 |
+
return output
|
| 921 |
+
|
| 922 |
+
def encode_video_item(
|
| 923 |
+
self,
|
| 924 |
+
path: Path,
|
| 925 |
+
span: tuple[float, float] | None = None,
|
| 926 |
+
) -> tuple[np.ndarray, VideoInputs]:
|
| 927 |
+
with self.media_forward("video"):
|
| 928 |
+
raw, inputs = self.encode_video(path, include_soundtrack=True, span=span)
|
| 929 |
+
weight = self.soundtrack_weight
|
| 930 |
+
pe = None
|
| 931 |
+
whole_soundtrack = span is None and inputs.has_soundtrack
|
| 932 |
+
if whole_soundtrack:
|
| 933 |
+
# Combine the whole-file soundtrack before adding acoustic coordinates.
|
| 934 |
+
with self.media_forward("audio"):
|
| 935 |
+
sound, pe = self._audio_vectors(path)
|
| 936 |
+
raw = fuse_soundtrack(raw, sound, weight)
|
| 937 |
+
current = self.project_video(raw)
|
| 938 |
+
if whole_soundtrack:
|
| 939 |
+
if pe is None:
|
| 940 |
+
pe = self.encode_acoustic(path, self.geometry["audio_projection"])
|
| 941 |
+
return self.magnitude.audio(current, pe), inputs
|
| 942 |
+
return self.pad_document(current), inputs
|
| 943 |
+
|
| 944 |
+
@property
|
| 945 |
+
def soundtrack_weight(self) -> float:
|
| 946 |
+
"""Geometry `video_soundtrack_weight`: how much of a video's soundtrack
|
| 947 |
+
native (through the audio path) joins its whole-file composite native."""
|
| 948 |
+
return float(self.geometry["video_soundtrack_weight"])
|
| 949 |
+
|
| 950 |
+
def encode_text_item(self, text: str) -> np.ndarray:
|
| 951 |
+
return self.pad_document(self.project_document(self.encode_text(text, "document")))
|
| 952 |
+
|
| 953 |
+
def encode_document_page(self, image: Image.Image) -> np.ndarray:
|
| 954 |
+
with self.page_forward():
|
| 955 |
+
raw = self.encode_image(image)
|
| 956 |
+
return self.pad_document(self.project_document(raw, refine=True))
|
| 957 |
+
|
| 958 |
+
def encode_image_item(self, image: Image.Image) -> np.ndarray:
|
| 959 |
+
image = image.convert("RGB")
|
| 960 |
+
budget = self.processor.image_processor.size["longest_edge"]
|
| 961 |
+
if image.width * image.height > budget:
|
| 962 |
+
scale = math.sqrt(budget / (image.width * image.height))
|
| 963 |
+
image = image.resize((max(1, int(image.width * scale)), max(1, int(image.height * scale))))
|
| 964 |
+
with self.media_forward("image"):
|
| 965 |
+
raw = self.encode_image(image)
|
| 966 |
+
return self.pad_document(self.project_media(raw, "image"))
|
| 967 |
+
|
| 968 |
+
def encode_audio_item(self, path: Path, span: tuple[float, float] | None = None) -> np.ndarray:
|
| 969 |
+
with self.media_forward("audio"):
|
| 970 |
+
raw, pe = self._audio_vectors(path, span)
|
| 971 |
+
current = self.project_media(raw, "audio")
|
| 972 |
+
if span is None:
|
| 973 |
+
if pe is None:
|
| 974 |
+
raise RuntimeError("whole-file audio magnitude requires the raw PE feature")
|
| 975 |
+
return self.magnitude.audio(current, pe)
|
| 976 |
+
return self.pad_document(current)
|
| 977 |
+
|
| 978 |
+
def pad_document(self, current: np.ndarray) -> np.ndarray:
|
| 979 |
+
return self.magnitude.document(current)
|
| 980 |
+
|
| 981 |
+
def encode_query(self, text: str) -> np.ndarray:
|
| 982 |
+
native = self.encode_text(text, "query")
|
| 983 |
+
text_modality = MODALITIES.index("text")
|
| 984 |
+
head = native @ self.head["query.weight"].T + self.head["query_modality.weight"][text_modality]
|
| 985 |
+
head /= np.linalg.norm(head)
|
| 986 |
+
share = float(self.geometry["native_document_share"])
|
| 987 |
+
current = np.concatenate((math.sqrt(1 - share) * head, math.sqrt(share) * native))
|
| 988 |
+
return self.magnitude.query(current, text)
|
model/omni.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8fd64d888f00a1cef89dd950e59b10bc8d60460e8d1e3f754b41592317329b36
|
| 3 |
+
size 949948427
|
model/packaging.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"files": {
|
| 3 |
+
"LICENSE": "e258bbefaa6a50100cf835c0ef907b58d201712826ee28de5d2ec0b159ebcf07",
|
| 4 |
+
"README.md": "80148fca5285b7e5933c90665a5c6a9b526e8c3768088c605193a34dc196d126",
|
| 5 |
+
"audio-pooler.safetensors": "7bf8bc6420491454c3bb332bd243244d82efddfbcaa07cbc56dfb7603ab913f9",
|
| 6 |
+
"chat_template.jinja": "273d8e0e683b885071fb17e08d71e5f2a5ddfb5309756181681de4f5a1822d80",
|
| 7 |
+
"config.json": "841c7e432630ea31dbab9dd90aeedf07ba44e19144cbeb61ad41138c7d881e8e",
|
| 8 |
+
"geometry.json": "2c4a99d0c2b03c023ef29cc2cb15c1cf780fdf964e5b92b6bc095ca046000e92",
|
| 9 |
+
"head/head.json": "2b1b316b95a248d1280411197e8fb6f0e5089247a3671773c36475ca213dab39",
|
| 10 |
+
"head/head.safetensors": "3922c5b7cb892977d61cb3e6caaa025fece854d5be2fba0e047503e1c17f4ccf",
|
| 11 |
+
"lanes.py": "b85311d9b5fe5d593d4b04730eeb66af11d39d942b393ee57e63ab3062510320",
|
| 12 |
+
"language.safetensors": "fea1441e6a0e56aba49d3cf2d0b92b36ee8142b8f98ed54b0e3bd30b1e909702",
|
| 13 |
+
"licenses/jina-cc-by-nc-4.0.txt": "41003d4a74749c0220e33dd415042164b5a1093ed401f36277234f772d22d3d0",
|
| 14 |
+
"licenses/meta-apache-2.0.txt": "cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30",
|
| 15 |
+
"magnitude.py": "93f267d198dcd0755134e86011d85d78af4fd5d61b818f98f8202cab1684930e",
|
| 16 |
+
"manifest.json": "2cb347a52202cc95e6edccf6d9721f93be4cc96a4b18cb9a35a3995eea995aac",
|
| 17 |
+
"media-adapter.safetensors": "f9812fd4f33aeaaa22854569753731e81115581df55a2337c72ba4655fe58a63",
|
| 18 |
+
"model.py": "553386da770249a30a0ba85febba50c9e9d40ff58c266c8ea3253f40dfe58963",
|
| 19 |
+
"nanobe_mlx_omni.py": "33fbaae67f93e8c9ae3816c0232ae51c8dbb2e1bbeb4ced3a69495bdd3d01773",
|
| 20 |
+
"omni.safetensors": "8fd64d888f00a1cef89dd950e59b10bc8d60460e8d1e3f754b41592317329b36",
|
| 21 |
+
"page-merger.safetensors": "db41396835aa23dd661711a39e9fa64c59957f46564741f2db26517e782d5a57",
|
| 22 |
+
"pe-audio/config.json": "52cad1a78304268153c436b796477c3a1dd4a62817fce42ce66e2f9cb04359d3",
|
| 23 |
+
"pe-audio/manifest.json": "790f80f549c70cb327b30c537017ca2ae6f47781b219ce8cd35f0144b1e058d7",
|
| 24 |
+
"pe-audio/model.safetensors": "e424fb597caed67db9d794b3634909f06a36020970c1c31837382f17b8509a60",
|
| 25 |
+
"pe-audio/nanobe_mlx_pe.py": "be3d0cdd3f16a22a59379a65aa56b8f77e14adf63dad9d60eaf483686893508a",
|
| 26 |
+
"pe-text/config.json": "9402387f9eb47c9ba91bc6b62c13834d34a1bf31cafcee7e4211fd9997d834d7",
|
| 27 |
+
"pe-text/manifest.json": "97ad081b8044cb4308995a5d2d93d36bdec39a7ccfcf01e6591b7a5fd6b0eeff",
|
| 28 |
+
"pe-text/model.safetensors": "48ffbbea2595722fffcd320bc07a5af07149fafcf00f03f9b02b4f138262928e",
|
| 29 |
+
"pe-text/pe_text.py": "025bc642db25eddad124a289535acf3cbca1d7a88bf409c7b9fe1b4aecd6f4f7",
|
| 30 |
+
"pe-text/special_tokens_map.json": "ea97ecdbcc73713039d8d64dbb05e3689495c96657fbd9a18f5bed381be81049",
|
| 31 |
+
"pe-text/tokenizer.json": "43e9ea931bb0f359d16d0e435c26abbebd9a42a7298270b8ab7e5c9fa1be1dcb",
|
| 32 |
+
"pe-text/tokenizer_config.json": "adec72131bbba5a043594c1e3bf5930608546e46a51432b2502b392ec280551b",
|
| 33 |
+
"preprocessor_config.json": "e517dcd8eebc59981f55077e4c9684cf7f6b06adcc87bdfff25fd4c04e60c971",
|
| 34 |
+
"processor_config.json": "e2513e75f4f5eb89903209a8a146ac9e968a7501816238b748ebc08531326363",
|
| 35 |
+
"tokenizer.bin": "8e08386ae43d4a5024a97dadbdc7291d6770135921c97e913644f991b3a87b8e",
|
| 36 |
+
"tokenizer.json": "7bbf567d9e053a72f0bd8ea475e672d10d537578f6adb521ab42da2badfa8bef",
|
| 37 |
+
"tokenizer_config.json": "c3df92f019f31d77889b0a01baea0441d5f47400d99546f1f0190c5ff0b600ee",
|
| 38 |
+
"upstream-notices.md": "829342e56825c0fd034cf04c0e0e22d6fb56ddc9422adef8fb5bc1aad306af60",
|
| 39 |
+
"video_preprocessor_config.json": "266a4a3e4680853721f7f69c8147cdb32e2a1e6646b44de188d81a685faeedd4"
|
| 40 |
+
}
|
| 41 |
+
}
|
model/page-merger.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:db41396835aa23dd661711a39e9fa64c59957f46564741f2db26517e782d5a57
|
| 3 |
+
size 41957888
|
model/pe-audio/config.json
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"audio_config": {
|
| 3 |
+
"attention_bias": false,
|
| 4 |
+
"dac_config": {
|
| 5 |
+
"codebook_dim": 128,
|
| 6 |
+
"downsampling_ratios": [
|
| 7 |
+
2,
|
| 8 |
+
8,
|
| 9 |
+
10,
|
| 10 |
+
12
|
| 11 |
+
],
|
| 12 |
+
"encoder_hidden_size": 64,
|
| 13 |
+
"hidden_size": 1024
|
| 14 |
+
},
|
| 15 |
+
"head_dim": 128,
|
| 16 |
+
"hidden_act": "silu",
|
| 17 |
+
"hidden_size": 768,
|
| 18 |
+
"intermediate_size": 2048,
|
| 19 |
+
"num_attention_heads": 6,
|
| 20 |
+
"num_hidden_layers": 12,
|
| 21 |
+
"num_key_value_heads": 6,
|
| 22 |
+
"rms_norm_eps": 1e-05,
|
| 23 |
+
"rope_parameters": {
|
| 24 |
+
"rope_theta": 20000,
|
| 25 |
+
"rope_type": "default"
|
| 26 |
+
}
|
| 27 |
+
},
|
| 28 |
+
"output_dimension": 1024,
|
| 29 |
+
"quantization": {
|
| 30 |
+
"bits": 8,
|
| 31 |
+
"group_size": 64,
|
| 32 |
+
"modules": [
|
| 33 |
+
"audio_head.proj",
|
| 34 |
+
"audio_encoder.output",
|
| 35 |
+
"audio_encoder.layers.11.mlp.down_proj",
|
| 36 |
+
"audio_encoder.layers.11.mlp.up_proj",
|
| 37 |
+
"audio_encoder.layers.11.mlp.gate_proj",
|
| 38 |
+
"audio_encoder.layers.11.self_attn.o_proj",
|
| 39 |
+
"audio_encoder.layers.11.self_attn.v_proj",
|
| 40 |
+
"audio_encoder.layers.11.self_attn.k_proj",
|
| 41 |
+
"audio_encoder.layers.11.self_attn.q_proj",
|
| 42 |
+
"audio_encoder.layers.10.mlp.down_proj",
|
| 43 |
+
"audio_encoder.layers.10.mlp.up_proj",
|
| 44 |
+
"audio_encoder.layers.10.mlp.gate_proj",
|
| 45 |
+
"audio_encoder.layers.10.self_attn.o_proj",
|
| 46 |
+
"audio_encoder.layers.10.self_attn.v_proj",
|
| 47 |
+
"audio_encoder.layers.10.self_attn.k_proj",
|
| 48 |
+
"audio_encoder.layers.10.self_attn.q_proj",
|
| 49 |
+
"audio_encoder.layers.9.mlp.down_proj",
|
| 50 |
+
"audio_encoder.layers.9.mlp.up_proj",
|
| 51 |
+
"audio_encoder.layers.9.mlp.gate_proj",
|
| 52 |
+
"audio_encoder.layers.9.self_attn.o_proj",
|
| 53 |
+
"audio_encoder.layers.9.self_attn.v_proj",
|
| 54 |
+
"audio_encoder.layers.9.self_attn.k_proj",
|
| 55 |
+
"audio_encoder.layers.9.self_attn.q_proj",
|
| 56 |
+
"audio_encoder.layers.8.mlp.down_proj",
|
| 57 |
+
"audio_encoder.layers.8.mlp.up_proj",
|
| 58 |
+
"audio_encoder.layers.8.mlp.gate_proj",
|
| 59 |
+
"audio_encoder.layers.8.self_attn.o_proj",
|
| 60 |
+
"audio_encoder.layers.8.self_attn.v_proj",
|
| 61 |
+
"audio_encoder.layers.8.self_attn.k_proj",
|
| 62 |
+
"audio_encoder.layers.8.self_attn.q_proj",
|
| 63 |
+
"audio_encoder.layers.7.mlp.down_proj",
|
| 64 |
+
"audio_encoder.layers.7.mlp.up_proj",
|
| 65 |
+
"audio_encoder.layers.7.mlp.gate_proj",
|
| 66 |
+
"audio_encoder.layers.7.self_attn.o_proj",
|
| 67 |
+
"audio_encoder.layers.7.self_attn.v_proj",
|
| 68 |
+
"audio_encoder.layers.7.self_attn.k_proj",
|
| 69 |
+
"audio_encoder.layers.7.self_attn.q_proj",
|
| 70 |
+
"audio_encoder.layers.6.mlp.down_proj",
|
| 71 |
+
"audio_encoder.layers.6.mlp.up_proj",
|
| 72 |
+
"audio_encoder.layers.6.mlp.gate_proj",
|
| 73 |
+
"audio_encoder.layers.6.self_attn.o_proj",
|
| 74 |
+
"audio_encoder.layers.6.self_attn.v_proj",
|
| 75 |
+
"audio_encoder.layers.6.self_attn.k_proj",
|
| 76 |
+
"audio_encoder.layers.6.self_attn.q_proj",
|
| 77 |
+
"audio_encoder.layers.5.mlp.down_proj",
|
| 78 |
+
"audio_encoder.layers.5.mlp.up_proj",
|
| 79 |
+
"audio_encoder.layers.5.mlp.gate_proj",
|
| 80 |
+
"audio_encoder.layers.5.self_attn.o_proj",
|
| 81 |
+
"audio_encoder.layers.5.self_attn.v_proj",
|
| 82 |
+
"audio_encoder.layers.5.self_attn.k_proj",
|
| 83 |
+
"audio_encoder.layers.5.self_attn.q_proj",
|
| 84 |
+
"audio_encoder.layers.4.mlp.down_proj",
|
| 85 |
+
"audio_encoder.layers.4.mlp.up_proj",
|
| 86 |
+
"audio_encoder.layers.4.mlp.gate_proj",
|
| 87 |
+
"audio_encoder.layers.4.self_attn.o_proj",
|
| 88 |
+
"audio_encoder.layers.4.self_attn.v_proj",
|
| 89 |
+
"audio_encoder.layers.4.self_attn.k_proj",
|
| 90 |
+
"audio_encoder.layers.4.self_attn.q_proj",
|
| 91 |
+
"audio_encoder.layers.3.mlp.down_proj",
|
| 92 |
+
"audio_encoder.layers.3.mlp.up_proj",
|
| 93 |
+
"audio_encoder.layers.3.mlp.gate_proj",
|
| 94 |
+
"audio_encoder.layers.3.self_attn.o_proj",
|
| 95 |
+
"audio_encoder.layers.3.self_attn.v_proj",
|
| 96 |
+
"audio_encoder.layers.3.self_attn.k_proj",
|
| 97 |
+
"audio_encoder.layers.3.self_attn.q_proj",
|
| 98 |
+
"audio_encoder.layers.2.mlp.down_proj",
|
| 99 |
+
"audio_encoder.layers.2.mlp.up_proj",
|
| 100 |
+
"audio_encoder.layers.2.mlp.gate_proj",
|
| 101 |
+
"audio_encoder.layers.2.self_attn.o_proj",
|
| 102 |
+
"audio_encoder.layers.2.self_attn.v_proj",
|
| 103 |
+
"audio_encoder.layers.2.self_attn.k_proj",
|
| 104 |
+
"audio_encoder.layers.2.self_attn.q_proj",
|
| 105 |
+
"audio_encoder.layers.1.mlp.down_proj",
|
| 106 |
+
"audio_encoder.layers.1.mlp.up_proj",
|
| 107 |
+
"audio_encoder.layers.1.mlp.gate_proj",
|
| 108 |
+
"audio_encoder.layers.1.self_attn.o_proj",
|
| 109 |
+
"audio_encoder.layers.1.self_attn.v_proj",
|
| 110 |
+
"audio_encoder.layers.1.self_attn.k_proj",
|
| 111 |
+
"audio_encoder.layers.1.self_attn.q_proj",
|
| 112 |
+
"audio_encoder.layers.0.mlp.down_proj",
|
| 113 |
+
"audio_encoder.layers.0.mlp.up_proj",
|
| 114 |
+
"audio_encoder.layers.0.mlp.gate_proj",
|
| 115 |
+
"audio_encoder.layers.0.self_attn.o_proj",
|
| 116 |
+
"audio_encoder.layers.0.self_attn.v_proj",
|
| 117 |
+
"audio_encoder.layers.0.self_attn.k_proj",
|
| 118 |
+
"audio_encoder.layers.0.self_attn.q_proj",
|
| 119 |
+
"audio_encoder.embedder.data_proj"
|
| 120 |
+
],
|
| 121 |
+
"convolution_dtype": "float16"
|
| 122 |
+
}
|
| 123 |
+
}
|
model/pe-audio/manifest.json
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"parameters": 117389248,
|
| 3 |
+
"quantization": {
|
| 4 |
+
"bits": 8,
|
| 5 |
+
"group_size": 64,
|
| 6 |
+
"modules": [
|
| 7 |
+
"audio_head.proj",
|
| 8 |
+
"audio_encoder.output",
|
| 9 |
+
"audio_encoder.layers.11.mlp.down_proj",
|
| 10 |
+
"audio_encoder.layers.11.mlp.up_proj",
|
| 11 |
+
"audio_encoder.layers.11.mlp.gate_proj",
|
| 12 |
+
"audio_encoder.layers.11.self_attn.o_proj",
|
| 13 |
+
"audio_encoder.layers.11.self_attn.v_proj",
|
| 14 |
+
"audio_encoder.layers.11.self_attn.k_proj",
|
| 15 |
+
"audio_encoder.layers.11.self_attn.q_proj",
|
| 16 |
+
"audio_encoder.layers.10.mlp.down_proj",
|
| 17 |
+
"audio_encoder.layers.10.mlp.up_proj",
|
| 18 |
+
"audio_encoder.layers.10.mlp.gate_proj",
|
| 19 |
+
"audio_encoder.layers.10.self_attn.o_proj",
|
| 20 |
+
"audio_encoder.layers.10.self_attn.v_proj",
|
| 21 |
+
"audio_encoder.layers.10.self_attn.k_proj",
|
| 22 |
+
"audio_encoder.layers.10.self_attn.q_proj",
|
| 23 |
+
"audio_encoder.layers.9.mlp.down_proj",
|
| 24 |
+
"audio_encoder.layers.9.mlp.up_proj",
|
| 25 |
+
"audio_encoder.layers.9.mlp.gate_proj",
|
| 26 |
+
"audio_encoder.layers.9.self_attn.o_proj",
|
| 27 |
+
"audio_encoder.layers.9.self_attn.v_proj",
|
| 28 |
+
"audio_encoder.layers.9.self_attn.k_proj",
|
| 29 |
+
"audio_encoder.layers.9.self_attn.q_proj",
|
| 30 |
+
"audio_encoder.layers.8.mlp.down_proj",
|
| 31 |
+
"audio_encoder.layers.8.mlp.up_proj",
|
| 32 |
+
"audio_encoder.layers.8.mlp.gate_proj",
|
| 33 |
+
"audio_encoder.layers.8.self_attn.o_proj",
|
| 34 |
+
"audio_encoder.layers.8.self_attn.v_proj",
|
| 35 |
+
"audio_encoder.layers.8.self_attn.k_proj",
|
| 36 |
+
"audio_encoder.layers.8.self_attn.q_proj",
|
| 37 |
+
"audio_encoder.layers.7.mlp.down_proj",
|
| 38 |
+
"audio_encoder.layers.7.mlp.up_proj",
|
| 39 |
+
"audio_encoder.layers.7.mlp.gate_proj",
|
| 40 |
+
"audio_encoder.layers.7.self_attn.o_proj",
|
| 41 |
+
"audio_encoder.layers.7.self_attn.v_proj",
|
| 42 |
+
"audio_encoder.layers.7.self_attn.k_proj",
|
| 43 |
+
"audio_encoder.layers.7.self_attn.q_proj",
|
| 44 |
+
"audio_encoder.layers.6.mlp.down_proj",
|
| 45 |
+
"audio_encoder.layers.6.mlp.up_proj",
|
| 46 |
+
"audio_encoder.layers.6.mlp.gate_proj",
|
| 47 |
+
"audio_encoder.layers.6.self_attn.o_proj",
|
| 48 |
+
"audio_encoder.layers.6.self_attn.v_proj",
|
| 49 |
+
"audio_encoder.layers.6.self_attn.k_proj",
|
| 50 |
+
"audio_encoder.layers.6.self_attn.q_proj",
|
| 51 |
+
"audio_encoder.layers.5.mlp.down_proj",
|
| 52 |
+
"audio_encoder.layers.5.mlp.up_proj",
|
| 53 |
+
"audio_encoder.layers.5.mlp.gate_proj",
|
| 54 |
+
"audio_encoder.layers.5.self_attn.o_proj",
|
| 55 |
+
"audio_encoder.layers.5.self_attn.v_proj",
|
| 56 |
+
"audio_encoder.layers.5.self_attn.k_proj",
|
| 57 |
+
"audio_encoder.layers.5.self_attn.q_proj",
|
| 58 |
+
"audio_encoder.layers.4.mlp.down_proj",
|
| 59 |
+
"audio_encoder.layers.4.mlp.up_proj",
|
| 60 |
+
"audio_encoder.layers.4.mlp.gate_proj",
|
| 61 |
+
"audio_encoder.layers.4.self_attn.o_proj",
|
| 62 |
+
"audio_encoder.layers.4.self_attn.v_proj",
|
| 63 |
+
"audio_encoder.layers.4.self_attn.k_proj",
|
| 64 |
+
"audio_encoder.layers.4.self_attn.q_proj",
|
| 65 |
+
"audio_encoder.layers.3.mlp.down_proj",
|
| 66 |
+
"audio_encoder.layers.3.mlp.up_proj",
|
| 67 |
+
"audio_encoder.layers.3.mlp.gate_proj",
|
| 68 |
+
"audio_encoder.layers.3.self_attn.o_proj",
|
| 69 |
+
"audio_encoder.layers.3.self_attn.v_proj",
|
| 70 |
+
"audio_encoder.layers.3.self_attn.k_proj",
|
| 71 |
+
"audio_encoder.layers.3.self_attn.q_proj",
|
| 72 |
+
"audio_encoder.layers.2.mlp.down_proj",
|
| 73 |
+
"audio_encoder.layers.2.mlp.up_proj",
|
| 74 |
+
"audio_encoder.layers.2.mlp.gate_proj",
|
| 75 |
+
"audio_encoder.layers.2.self_attn.o_proj",
|
| 76 |
+
"audio_encoder.layers.2.self_attn.v_proj",
|
| 77 |
+
"audio_encoder.layers.2.self_attn.k_proj",
|
| 78 |
+
"audio_encoder.layers.2.self_attn.q_proj",
|
| 79 |
+
"audio_encoder.layers.1.mlp.down_proj",
|
| 80 |
+
"audio_encoder.layers.1.mlp.up_proj",
|
| 81 |
+
"audio_encoder.layers.1.mlp.gate_proj",
|
| 82 |
+
"audio_encoder.layers.1.self_attn.o_proj",
|
| 83 |
+
"audio_encoder.layers.1.self_attn.v_proj",
|
| 84 |
+
"audio_encoder.layers.1.self_attn.k_proj",
|
| 85 |
+
"audio_encoder.layers.1.self_attn.q_proj",
|
| 86 |
+
"audio_encoder.layers.0.mlp.down_proj",
|
| 87 |
+
"audio_encoder.layers.0.mlp.up_proj",
|
| 88 |
+
"audio_encoder.layers.0.mlp.gate_proj",
|
| 89 |
+
"audio_encoder.layers.0.self_attn.o_proj",
|
| 90 |
+
"audio_encoder.layers.0.self_attn.v_proj",
|
| 91 |
+
"audio_encoder.layers.0.self_attn.k_proj",
|
| 92 |
+
"audio_encoder.layers.0.self_attn.q_proj",
|
| 93 |
+
"audio_encoder.embedder.data_proj"
|
| 94 |
+
],
|
| 95 |
+
"convolution_dtype": "float16"
|
| 96 |
+
},
|
| 97 |
+
"weight_bytes": 153819456,
|
| 98 |
+
"files": {
|
| 99 |
+
"config.json": "52cad1a78304268153c436b796477c3a1dd4a62817fce42ce66e2f9cb04359d3",
|
| 100 |
+
"model.safetensors": "e424fb597caed67db9d794b3634909f06a36020970c1c31837382f17b8509a60",
|
| 101 |
+
"nanobe_mlx_pe.py": "be3d0cdd3f16a22a59379a65aa56b8f77e14adf63dad9d60eaf483686893508a"
|
| 102 |
+
}
|
| 103 |
+
}
|
model/pe-audio/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e424fb597caed67db9d794b3634909f06a36020970c1c31837382f17b8509a60
|
| 3 |
+
size 153819456
|
model/pe-audio/nanobe_mlx_pe.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""PE audio encoder for MLX, with complete waveform coverage and no text model.
|
| 2 |
+
|
| 3 |
+
Architecture follows Transformers 5.6 PeAudioEncoder. Its class token supplies
|
| 4 |
+
the pooled feature. Each waveform window has a fully valid padding mask,
|
| 5 |
+
including the publisher's right reflection padding.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import math
|
| 12 |
+
from pathlib import Path
|
| 13 |
+
from types import SimpleNamespace
|
| 14 |
+
|
| 15 |
+
import mlx.core as mx
|
| 16 |
+
import mlx.nn as nn
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class Snake(nn.Module):
|
| 21 |
+
def __init__(self, channels: int):
|
| 22 |
+
super().__init__()
|
| 23 |
+
self.alpha = mx.ones((1, 1, channels))
|
| 24 |
+
|
| 25 |
+
def __call__(self, value):
|
| 26 |
+
return value + mx.square(mx.sin(self.alpha * value)) / (self.alpha + 1e-9)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class Residual(nn.Module):
|
| 30 |
+
def __init__(self, channels: int, dilation: int):
|
| 31 |
+
super().__init__()
|
| 32 |
+
self.snake1 = Snake(channels)
|
| 33 |
+
self.conv1 = nn.Conv1d(channels, channels, 7, padding=3 * dilation, dilation=dilation)
|
| 34 |
+
self.snake2 = Snake(channels)
|
| 35 |
+
self.conv2 = nn.Conv1d(channels, channels, 1)
|
| 36 |
+
|
| 37 |
+
def __call__(self, value):
|
| 38 |
+
return value + self.conv2(self.snake2(self.conv1(self.snake1(value))))
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class Downsample(nn.Module):
|
| 42 |
+
def __init__(self, channels: int, stride: int):
|
| 43 |
+
super().__init__()
|
| 44 |
+
self.res_unit1 = Residual(channels, 1)
|
| 45 |
+
self.res_unit2 = Residual(channels, 3)
|
| 46 |
+
self.res_unit3 = Residual(channels, 9)
|
| 47 |
+
self.snake1 = Snake(channels)
|
| 48 |
+
self.conv1 = nn.Conv1d(channels, channels * 2, 2 * stride,
|
| 49 |
+
stride=stride, padding=math.ceil(stride / 2))
|
| 50 |
+
|
| 51 |
+
def __call__(self, value):
|
| 52 |
+
value = self.res_unit1(value)
|
| 53 |
+
value = self.res_unit2(value)
|
| 54 |
+
return self.conv1(self.snake1(self.res_unit3(value)))
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class Dac(nn.Module):
|
| 58 |
+
def __init__(self, config):
|
| 59 |
+
super().__init__()
|
| 60 |
+
channels = config.encoder_hidden_size
|
| 61 |
+
self.conv1 = nn.Conv1d(1, channels, 7, padding=3)
|
| 62 |
+
self.block = [Downsample(channels * 2**i, stride)
|
| 63 |
+
for i, stride in enumerate(config.downsampling_ratios)]
|
| 64 |
+
width = channels * 2**len(self.block)
|
| 65 |
+
self.snake1 = Snake(width)
|
| 66 |
+
self.conv2 = nn.Conv1d(width, config.hidden_size, 3, padding=1)
|
| 67 |
+
|
| 68 |
+
def __call__(self, value):
|
| 69 |
+
value = self.conv1(value)
|
| 70 |
+
mx.eval(value)
|
| 71 |
+
for block in self.block:
|
| 72 |
+
value = block(value)
|
| 73 |
+
mx.eval(value)
|
| 74 |
+
return self.conv2(self.snake1(value))
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
class Embedder(nn.Module):
|
| 78 |
+
def __init__(self, config):
|
| 79 |
+
super().__init__()
|
| 80 |
+
self.dac_encoder = Dac(config.dac_config)
|
| 81 |
+
self.bottleneck = nn.Conv1d(config.dac_config.hidden_size, config.dac_config.codebook_dim, 1)
|
| 82 |
+
self.data_proj = nn.Linear(config.dac_config.codebook_dim, config.hidden_size)
|
| 83 |
+
|
| 84 |
+
def __call__(self, value):
|
| 85 |
+
return self.data_proj(self.bottleneck(self.dac_encoder(value)))
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class Convolution(nn.Module):
|
| 89 |
+
def __init__(self, width: int):
|
| 90 |
+
super().__init__()
|
| 91 |
+
self.groupnorm = nn.GroupNorm(1, width, eps=1e-5, pytorch_compatible=True)
|
| 92 |
+
self.project = nn.Conv1d(width, width, 3, padding=1)
|
| 93 |
+
|
| 94 |
+
def __call__(self, value):
|
| 95 |
+
return self.project(nn.silu(self.groupnorm(value)))
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
class Resnet(nn.Module):
|
| 99 |
+
def __init__(self, width: int):
|
| 100 |
+
super().__init__()
|
| 101 |
+
self.block1 = Convolution(width)
|
| 102 |
+
self.block2 = Convolution(width)
|
| 103 |
+
|
| 104 |
+
def __call__(self, value):
|
| 105 |
+
return value + self.block2(self.block1(value))
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
class Patches(nn.Module):
|
| 109 |
+
def __init__(self, width: int):
|
| 110 |
+
super().__init__()
|
| 111 |
+
self.class_embedding = mx.zeros((1, 1, width))
|
| 112 |
+
self.resnet_block = Resnet(width)
|
| 113 |
+
|
| 114 |
+
def __call__(self, value):
|
| 115 |
+
return self.resnet_block(mx.concatenate((self.class_embedding, value), axis=1))
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
class Attention(nn.Module):
|
| 119 |
+
def __init__(self, config):
|
| 120 |
+
super().__init__()
|
| 121 |
+
self.heads = config.num_attention_heads
|
| 122 |
+
self.dimension = config.head_dim
|
| 123 |
+
self.q_proj = nn.Linear(config.hidden_size, self.heads * self.dimension, bias=False)
|
| 124 |
+
self.k_proj = nn.Linear(config.hidden_size, self.heads * self.dimension, bias=False)
|
| 125 |
+
self.v_proj = nn.Linear(config.hidden_size, self.heads * self.dimension, bias=False)
|
| 126 |
+
self.o_proj = nn.Linear(self.heads * self.dimension, config.hidden_size, bias=False)
|
| 127 |
+
self.q_norm = nn.RMSNorm(self.dimension, eps=config.rms_norm_eps)
|
| 128 |
+
self.k_norm = nn.RMSNorm(self.dimension, eps=config.rms_norm_eps)
|
| 129 |
+
self.rope = nn.RoPE(self.dimension, traditional=True, base=config.rope_parameters["rope_theta"])
|
| 130 |
+
|
| 131 |
+
def __call__(self, value):
|
| 132 |
+
shape = (*value.shape[:2], self.heads, self.dimension)
|
| 133 |
+
query = self.q_norm(self.q_proj(value).reshape(shape)).transpose(0, 2, 1, 3)
|
| 134 |
+
key = self.k_norm(self.k_proj(value).reshape(shape)).transpose(0, 2, 1, 3)
|
| 135 |
+
values = self.v_proj(value).reshape(shape).transpose(0, 2, 1, 3)
|
| 136 |
+
attended = mx.fast.scaled_dot_product_attention(self.rope(query), self.rope(key), values,
|
| 137 |
+
scale=self.dimension**-0.5)
|
| 138 |
+
return self.o_proj(attended.transpose(0, 2, 1, 3).reshape(value.shape))
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
class Mlp(nn.Module):
|
| 142 |
+
def __init__(self, config):
|
| 143 |
+
super().__init__()
|
| 144 |
+
self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
|
| 145 |
+
self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
|
| 146 |
+
self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
|
| 147 |
+
|
| 148 |
+
def __call__(self, value):
|
| 149 |
+
return self.down_proj(nn.silu(self.gate_proj(value)) * self.up_proj(value))
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class Layer(nn.Module):
|
| 153 |
+
def __init__(self, config):
|
| 154 |
+
super().__init__()
|
| 155 |
+
self.self_attn = Attention(config)
|
| 156 |
+
self.mlp = Mlp(config)
|
| 157 |
+
self.input_layernorm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 158 |
+
self.post_attention_layernorm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 159 |
+
|
| 160 |
+
def __call__(self, value):
|
| 161 |
+
value = value + self.self_attn(self.input_layernorm(value))
|
| 162 |
+
return value + self.mlp(self.post_attention_layernorm(value))
|
| 163 |
+
|
| 164 |
+
|
| 165 |
+
class Encoder(nn.Module):
|
| 166 |
+
def __init__(self, config):
|
| 167 |
+
super().__init__()
|
| 168 |
+
self.embedder = Embedder(config)
|
| 169 |
+
self.patch_embedder = Patches(config.hidden_size)
|
| 170 |
+
self.layers = [Layer(config) for _ in range(config.num_hidden_layers)]
|
| 171 |
+
self.norm = nn.RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
| 172 |
+
self.output = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
|
| 173 |
+
|
| 174 |
+
def __call__(self, value):
|
| 175 |
+
value = self.patch_embedder(self.embedder(value))
|
| 176 |
+
for layer in self.layers:
|
| 177 |
+
value = layer(value)
|
| 178 |
+
return self.output(self.norm(value[:, 0]))
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
class Head(nn.Module):
|
| 182 |
+
def __init__(self, width: int, output: int):
|
| 183 |
+
super().__init__()
|
| 184 |
+
self.layer_norm = nn.LayerNorm(width, eps=1e-6)
|
| 185 |
+
self.proj = nn.Linear(width, output, bias=False)
|
| 186 |
+
|
| 187 |
+
def __call__(self, value):
|
| 188 |
+
return self.proj(self.layer_norm(value))
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
class Model(nn.Module):
|
| 192 |
+
def __init__(self, configuration: dict):
|
| 193 |
+
super().__init__()
|
| 194 |
+
config = SimpleNamespace(**configuration["audio_config"])
|
| 195 |
+
config.dac_config = SimpleNamespace(**config.dac_config)
|
| 196 |
+
if config.num_attention_heads != config.num_key_value_heads or config.attention_bias:
|
| 197 |
+
raise ValueError("PE audio requires the validated full-head, unbiased attention configuration")
|
| 198 |
+
if config.hidden_act != "silu" or config.rope_parameters["rope_type"] != "default":
|
| 199 |
+
raise ValueError("PE audio activation or rotary configuration changed")
|
| 200 |
+
self.audio_encoder = Encoder(config)
|
| 201 |
+
self.audio_head = Head(config.hidden_size, configuration["output_dimension"])
|
| 202 |
+
|
| 203 |
+
def __call__(self, waveform):
|
| 204 |
+
if waveform.ndim != 3 or waveform.shape[0] != 1 or waveform.shape[2] != 1:
|
| 205 |
+
raise ValueError("PE expects one mono waveform in batch, time, channel order")
|
| 206 |
+
if not waveform.shape[1] or waveform.shape[1] % 1920:
|
| 207 |
+
raise ValueError("PE waveform length must be a positive multiple of 1920")
|
| 208 |
+
return self.audio_head(self.audio_encoder(waveform))
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def resample(waveform: np.ndarray, rate: int) -> np.ndarray:
|
| 212 |
+
"""Match torchaudio's default Hann-windowed sinc filter without importing Torch."""
|
| 213 |
+
if rate == 48000:
|
| 214 |
+
return waveform
|
| 215 |
+
divisor = math.gcd(rate, 48000)
|
| 216 |
+
source, target = rate // divisor, 48000 // divisor
|
| 217 |
+
cutoff = min(source, target) * 0.99
|
| 218 |
+
width = math.ceil(6 * source / cutoff)
|
| 219 |
+
indices = np.arange(-width, width + source, dtype=np.float32)[None] / source
|
| 220 |
+
phase = np.arange(0, -target, -1, dtype=np.float32)[:, None] / target + indices
|
| 221 |
+
phase = np.clip(phase * cutoff, -6, 6)
|
| 222 |
+
window = np.cos(phase * np.pi / 6 / 2) ** 2
|
| 223 |
+
angle = phase * np.pi
|
| 224 |
+
sinc = np.divide(np.sin(angle), angle, out=np.ones_like(angle), where=angle != 0)
|
| 225 |
+
kernel = sinc * window * (cutoff / source)
|
| 226 |
+
values = mx.array(np.pad(waveform, (width, width + source)))[None, :, None]
|
| 227 |
+
result = mx.conv1d(values, mx.array(kernel[:, :, None]), stride=source).reshape(-1)
|
| 228 |
+
# Torchaudio rounds the output length in float32 before taking its ceiling.
|
| 229 |
+
length = math.ceil(float(np.float32(target * len(waveform) / source)))
|
| 230 |
+
return np.asarray(result[:length], dtype=np.float32)
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
def windows(waveform: np.ndarray, rate: int):
|
| 234 |
+
"""Yield original sample bounds and each complete, resampled, padded window."""
|
| 235 |
+
waveform = np.asarray(waveform, dtype=np.float32)
|
| 236 |
+
if waveform.ndim != 1 or not len(waveform) or rate <= 0 or not np.isfinite(waveform).all():
|
| 237 |
+
raise ValueError("PE requires a finite, nonempty mono waveform and a positive sample rate")
|
| 238 |
+
count = math.ceil(len(waveform) / (30 * rate))
|
| 239 |
+
for i in range(count):
|
| 240 |
+
start, end = i * len(waveform) // count, (i + 1) * len(waveform) // count
|
| 241 |
+
value = resample(waveform[start:end], rate)
|
| 242 |
+
padding = -len(value) % 1920
|
| 243 |
+
if padding >= len(value):
|
| 244 |
+
raise ValueError("Audio is too short for the publisher's reflection padding")
|
| 245 |
+
yield start, end, np.pad(value, (0, padding), mode="reflect")
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
class Runtime:
|
| 249 |
+
def __init__(self, path: Path):
|
| 250 |
+
config = json.loads((path / "config.json").read_text())
|
| 251 |
+
self.model = Model(config)
|
| 252 |
+
quantization = config["quantization"]
|
| 253 |
+
nn.quantize(self.model, bits=quantization["bits"], group_size=quantization["group_size"],
|
| 254 |
+
class_predicate=lambda name, module: name in quantization["modules"])
|
| 255 |
+
self.model.load_weights(str(path / "model.safetensors"), strict=True)
|
| 256 |
+
self.model.eval()
|
| 257 |
+
mx.eval(self.model.parameters())
|
| 258 |
+
|
| 259 |
+
def encode(self, waveform: np.ndarray, rate: int) -> np.ndarray:
|
| 260 |
+
total = np.zeros(1024, dtype=np.float32)
|
| 261 |
+
dtype = self.model.audio_encoder.embedder.dac_encoder.conv1.weight.dtype
|
| 262 |
+
for start, end, value in windows(waveform, rate):
|
| 263 |
+
output = self.model(mx.array(value, dtype=dtype)[None, :, None]).astype(mx.float32)
|
| 264 |
+
mx.eval(output)
|
| 265 |
+
total += np.asarray(output[0]) * ((end - start) / len(waveform))
|
| 266 |
+
# Long DAC convolutions need large temporary buffers, not an idle cache.
|
| 267 |
+
mx.clear_cache()
|
| 268 |
+
if not np.isfinite(total).all() or np.linalg.norm(total) == 0:
|
| 269 |
+
raise ValueError("PE audio produced an invalid embedding")
|
| 270 |
+
return total
|
model/pe-text/config.json
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"text_config": {
|
| 3 |
+
"attention_bias": false,
|
| 4 |
+
"cls_token_id": 50281,
|
| 5 |
+
"hidden_activation": "gelu",
|
| 6 |
+
"hidden_size": 1024,
|
| 7 |
+
"intermediate_size": 2624,
|
| 8 |
+
"layer_types": [
|
| 9 |
+
"full_attention",
|
| 10 |
+
"sliding_attention",
|
| 11 |
+
"sliding_attention",
|
| 12 |
+
"full_attention",
|
| 13 |
+
"sliding_attention",
|
| 14 |
+
"sliding_attention",
|
| 15 |
+
"full_attention",
|
| 16 |
+
"sliding_attention",
|
| 17 |
+
"sliding_attention",
|
| 18 |
+
"full_attention",
|
| 19 |
+
"sliding_attention",
|
| 20 |
+
"sliding_attention",
|
| 21 |
+
"full_attention",
|
| 22 |
+
"sliding_attention",
|
| 23 |
+
"sliding_attention",
|
| 24 |
+
"full_attention",
|
| 25 |
+
"sliding_attention",
|
| 26 |
+
"sliding_attention",
|
| 27 |
+
"full_attention",
|
| 28 |
+
"sliding_attention",
|
| 29 |
+
"sliding_attention",
|
| 30 |
+
"full_attention"
|
| 31 |
+
],
|
| 32 |
+
"local_attention": 128,
|
| 33 |
+
"max_position_embeddings": 8192,
|
| 34 |
+
"mlp_bias": false,
|
| 35 |
+
"model_type": "modernbert",
|
| 36 |
+
"norm_bias": false,
|
| 37 |
+
"norm_eps": 1e-05,
|
| 38 |
+
"num_attention_heads": 16,
|
| 39 |
+
"num_hidden_layers": 22,
|
| 40 |
+
"rope_parameters": {
|
| 41 |
+
"full_attention": {
|
| 42 |
+
"rope_theta": 160000.0,
|
| 43 |
+
"rope_type": "default"
|
| 44 |
+
},
|
| 45 |
+
"sliding_attention": {
|
| 46 |
+
"rope_theta": 10000.0,
|
| 47 |
+
"rope_type": "default"
|
| 48 |
+
}
|
| 49 |
+
},
|
| 50 |
+
"sep_token_id": 50282,
|
| 51 |
+
"vocab_size": 50368
|
| 52 |
+
},
|
| 53 |
+
"quantization": {
|
| 54 |
+
"bits": 8,
|
| 55 |
+
"group_size": 64,
|
| 56 |
+
"modules": [
|
| 57 |
+
"text_model.layers.21.mlp.Wo",
|
| 58 |
+
"text_model.layers.21.mlp.Wi",
|
| 59 |
+
"text_model.layers.21.attn.Wo",
|
| 60 |
+
"text_model.layers.21.attn.Wqkv",
|
| 61 |
+
"text_model.layers.20.mlp.Wo",
|
| 62 |
+
"text_model.layers.20.mlp.Wi",
|
| 63 |
+
"text_model.layers.20.attn.Wo",
|
| 64 |
+
"text_model.layers.20.attn.Wqkv",
|
| 65 |
+
"text_model.layers.19.mlp.Wo",
|
| 66 |
+
"text_model.layers.19.mlp.Wi",
|
| 67 |
+
"text_model.layers.19.attn.Wo",
|
| 68 |
+
"text_model.layers.19.attn.Wqkv",
|
| 69 |
+
"text_model.layers.18.mlp.Wo",
|
| 70 |
+
"text_model.layers.18.mlp.Wi",
|
| 71 |
+
"text_model.layers.18.attn.Wo",
|
| 72 |
+
"text_model.layers.18.attn.Wqkv",
|
| 73 |
+
"text_model.layers.17.mlp.Wo",
|
| 74 |
+
"text_model.layers.17.mlp.Wi",
|
| 75 |
+
"text_model.layers.17.attn.Wo",
|
| 76 |
+
"text_model.layers.17.attn.Wqkv",
|
| 77 |
+
"text_model.layers.16.mlp.Wo",
|
| 78 |
+
"text_model.layers.16.mlp.Wi",
|
| 79 |
+
"text_model.layers.16.attn.Wo",
|
| 80 |
+
"text_model.layers.16.attn.Wqkv",
|
| 81 |
+
"text_model.layers.15.mlp.Wo",
|
| 82 |
+
"text_model.layers.15.mlp.Wi",
|
| 83 |
+
"text_model.layers.15.attn.Wo",
|
| 84 |
+
"text_model.layers.15.attn.Wqkv",
|
| 85 |
+
"text_model.layers.14.mlp.Wo",
|
| 86 |
+
"text_model.layers.14.mlp.Wi",
|
| 87 |
+
"text_model.layers.14.attn.Wo",
|
| 88 |
+
"text_model.layers.14.attn.Wqkv",
|
| 89 |
+
"text_model.layers.13.mlp.Wo",
|
| 90 |
+
"text_model.layers.13.mlp.Wi",
|
| 91 |
+
"text_model.layers.13.attn.Wo",
|
| 92 |
+
"text_model.layers.13.attn.Wqkv",
|
| 93 |
+
"text_model.layers.12.mlp.Wo",
|
| 94 |
+
"text_model.layers.12.mlp.Wi",
|
| 95 |
+
"text_model.layers.12.attn.Wo",
|
| 96 |
+
"text_model.layers.12.attn.Wqkv",
|
| 97 |
+
"text_model.layers.11.mlp.Wo",
|
| 98 |
+
"text_model.layers.11.mlp.Wi",
|
| 99 |
+
"text_model.layers.11.attn.Wo",
|
| 100 |
+
"text_model.layers.11.attn.Wqkv",
|
| 101 |
+
"text_model.layers.10.mlp.Wo",
|
| 102 |
+
"text_model.layers.10.mlp.Wi",
|
| 103 |
+
"text_model.layers.10.attn.Wo",
|
| 104 |
+
"text_model.layers.10.attn.Wqkv",
|
| 105 |
+
"text_model.layers.9.mlp.Wo",
|
| 106 |
+
"text_model.layers.9.mlp.Wi",
|
| 107 |
+
"text_model.layers.9.attn.Wo",
|
| 108 |
+
"text_model.layers.9.attn.Wqkv",
|
| 109 |
+
"text_model.layers.8.mlp.Wo",
|
| 110 |
+
"text_model.layers.8.mlp.Wi",
|
| 111 |
+
"text_model.layers.8.attn.Wo",
|
| 112 |
+
"text_model.layers.8.attn.Wqkv",
|
| 113 |
+
"text_model.layers.7.mlp.Wo",
|
| 114 |
+
"text_model.layers.7.mlp.Wi",
|
| 115 |
+
"text_model.layers.7.attn.Wo",
|
| 116 |
+
"text_model.layers.7.attn.Wqkv",
|
| 117 |
+
"text_model.layers.6.mlp.Wo",
|
| 118 |
+
"text_model.layers.6.mlp.Wi",
|
| 119 |
+
"text_model.layers.6.attn.Wo",
|
| 120 |
+
"text_model.layers.6.attn.Wqkv",
|
| 121 |
+
"text_model.layers.5.mlp.Wo",
|
| 122 |
+
"text_model.layers.5.mlp.Wi",
|
| 123 |
+
"text_model.layers.5.attn.Wo",
|
| 124 |
+
"text_model.layers.5.attn.Wqkv",
|
| 125 |
+
"text_model.layers.4.mlp.Wo",
|
| 126 |
+
"text_model.layers.4.mlp.Wi",
|
| 127 |
+
"text_model.layers.4.attn.Wo",
|
| 128 |
+
"text_model.layers.4.attn.Wqkv",
|
| 129 |
+
"text_model.layers.3.mlp.Wo",
|
| 130 |
+
"text_model.layers.3.mlp.Wi",
|
| 131 |
+
"text_model.layers.3.attn.Wo",
|
| 132 |
+
"text_model.layers.3.attn.Wqkv",
|
| 133 |
+
"text_model.layers.2.mlp.Wo",
|
| 134 |
+
"text_model.layers.2.mlp.Wi",
|
| 135 |
+
"text_model.layers.2.attn.Wo",
|
| 136 |
+
"text_model.layers.2.attn.Wqkv",
|
| 137 |
+
"text_model.layers.1.mlp.Wo",
|
| 138 |
+
"text_model.layers.1.mlp.Wi",
|
| 139 |
+
"text_model.layers.1.attn.Wo",
|
| 140 |
+
"text_model.layers.1.attn.Wqkv",
|
| 141 |
+
"text_model.layers.0.mlp.Wo",
|
| 142 |
+
"text_model.layers.0.mlp.Wi",
|
| 143 |
+
"text_model.layers.0.attn.Wo",
|
| 144 |
+
"text_model.layers.0.attn.Wqkv",
|
| 145 |
+
"text_model.embeddings.tok_embeddings"
|
| 146 |
+
],
|
| 147 |
+
"unquantized_dtype": "float16",
|
| 148 |
+
"float32_tensors": [
|
| 149 |
+
"text_audio_head.layer_norm.bias",
|
| 150 |
+
"text_audio_head.layer_norm.weight",
|
| 151 |
+
"text_model.final_norm.weight",
|
| 152 |
+
"text_audio_head.proj.weight"
|
| 153 |
+
]
|
| 154 |
+
}
|
| 155 |
+
}
|
model/pe-text/manifest.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema": "nanobe-pe-text-mlx-v1",
|
| 3 |
+
"complete": true,
|
| 4 |
+
"source": {
|
| 5 |
+
"model": "facebook/pe-av-small",
|
| 6 |
+
"revision": "dd050762bb9704ae9cd996ca45532a98f81d817e"
|
| 7 |
+
},
|
| 8 |
+
"parameters": 322288640,
|
| 9 |
+
"quantization": {
|
| 10 |
+
"bits": 8,
|
| 11 |
+
"group_size": 64,
|
| 12 |
+
"modules": [
|
| 13 |
+
"text_model.layers.21.mlp.Wo",
|
| 14 |
+
"text_model.layers.21.mlp.Wi",
|
| 15 |
+
"text_model.layers.21.attn.Wo",
|
| 16 |
+
"text_model.layers.21.attn.Wqkv",
|
| 17 |
+
"text_model.layers.20.mlp.Wo",
|
| 18 |
+
"text_model.layers.20.mlp.Wi",
|
| 19 |
+
"text_model.layers.20.attn.Wo",
|
| 20 |
+
"text_model.layers.20.attn.Wqkv",
|
| 21 |
+
"text_model.layers.19.mlp.Wo",
|
| 22 |
+
"text_model.layers.19.mlp.Wi",
|
| 23 |
+
"text_model.layers.19.attn.Wo",
|
| 24 |
+
"text_model.layers.19.attn.Wqkv",
|
| 25 |
+
"text_model.layers.18.mlp.Wo",
|
| 26 |
+
"text_model.layers.18.mlp.Wi",
|
| 27 |
+
"text_model.layers.18.attn.Wo",
|
| 28 |
+
"text_model.layers.18.attn.Wqkv",
|
| 29 |
+
"text_model.layers.17.mlp.Wo",
|
| 30 |
+
"text_model.layers.17.mlp.Wi",
|
| 31 |
+
"text_model.layers.17.attn.Wo",
|
| 32 |
+
"text_model.layers.17.attn.Wqkv",
|
| 33 |
+
"text_model.layers.16.mlp.Wo",
|
| 34 |
+
"text_model.layers.16.mlp.Wi",
|
| 35 |
+
"text_model.layers.16.attn.Wo",
|
| 36 |
+
"text_model.layers.16.attn.Wqkv",
|
| 37 |
+
"text_model.layers.15.mlp.Wo",
|
| 38 |
+
"text_model.layers.15.mlp.Wi",
|
| 39 |
+
"text_model.layers.15.attn.Wo",
|
| 40 |
+
"text_model.layers.15.attn.Wqkv",
|
| 41 |
+
"text_model.layers.14.mlp.Wo",
|
| 42 |
+
"text_model.layers.14.mlp.Wi",
|
| 43 |
+
"text_model.layers.14.attn.Wo",
|
| 44 |
+
"text_model.layers.14.attn.Wqkv",
|
| 45 |
+
"text_model.layers.13.mlp.Wo",
|
| 46 |
+
"text_model.layers.13.mlp.Wi",
|
| 47 |
+
"text_model.layers.13.attn.Wo",
|
| 48 |
+
"text_model.layers.13.attn.Wqkv",
|
| 49 |
+
"text_model.layers.12.mlp.Wo",
|
| 50 |
+
"text_model.layers.12.mlp.Wi",
|
| 51 |
+
"text_model.layers.12.attn.Wo",
|
| 52 |
+
"text_model.layers.12.attn.Wqkv",
|
| 53 |
+
"text_model.layers.11.mlp.Wo",
|
| 54 |
+
"text_model.layers.11.mlp.Wi",
|
| 55 |
+
"text_model.layers.11.attn.Wo",
|
| 56 |
+
"text_model.layers.11.attn.Wqkv",
|
| 57 |
+
"text_model.layers.10.mlp.Wo",
|
| 58 |
+
"text_model.layers.10.mlp.Wi",
|
| 59 |
+
"text_model.layers.10.attn.Wo",
|
| 60 |
+
"text_model.layers.10.attn.Wqkv",
|
| 61 |
+
"text_model.layers.9.mlp.Wo",
|
| 62 |
+
"text_model.layers.9.mlp.Wi",
|
| 63 |
+
"text_model.layers.9.attn.Wo",
|
| 64 |
+
"text_model.layers.9.attn.Wqkv",
|
| 65 |
+
"text_model.layers.8.mlp.Wo",
|
| 66 |
+
"text_model.layers.8.mlp.Wi",
|
| 67 |
+
"text_model.layers.8.attn.Wo",
|
| 68 |
+
"text_model.layers.8.attn.Wqkv",
|
| 69 |
+
"text_model.layers.7.mlp.Wo",
|
| 70 |
+
"text_model.layers.7.mlp.Wi",
|
| 71 |
+
"text_model.layers.7.attn.Wo",
|
| 72 |
+
"text_model.layers.7.attn.Wqkv",
|
| 73 |
+
"text_model.layers.6.mlp.Wo",
|
| 74 |
+
"text_model.layers.6.mlp.Wi",
|
| 75 |
+
"text_model.layers.6.attn.Wo",
|
| 76 |
+
"text_model.layers.6.attn.Wqkv",
|
| 77 |
+
"text_model.layers.5.mlp.Wo",
|
| 78 |
+
"text_model.layers.5.mlp.Wi",
|
| 79 |
+
"text_model.layers.5.attn.Wo",
|
| 80 |
+
"text_model.layers.5.attn.Wqkv",
|
| 81 |
+
"text_model.layers.4.mlp.Wo",
|
| 82 |
+
"text_model.layers.4.mlp.Wi",
|
| 83 |
+
"text_model.layers.4.attn.Wo",
|
| 84 |
+
"text_model.layers.4.attn.Wqkv",
|
| 85 |
+
"text_model.layers.3.mlp.Wo",
|
| 86 |
+
"text_model.layers.3.mlp.Wi",
|
| 87 |
+
"text_model.layers.3.attn.Wo",
|
| 88 |
+
"text_model.layers.3.attn.Wqkv",
|
| 89 |
+
"text_model.layers.2.mlp.Wo",
|
| 90 |
+
"text_model.layers.2.mlp.Wi",
|
| 91 |
+
"text_model.layers.2.attn.Wo",
|
| 92 |
+
"text_model.layers.2.attn.Wqkv",
|
| 93 |
+
"text_model.layers.1.mlp.Wo",
|
| 94 |
+
"text_model.layers.1.mlp.Wi",
|
| 95 |
+
"text_model.layers.1.attn.Wo",
|
| 96 |
+
"text_model.layers.1.attn.Wqkv",
|
| 97 |
+
"text_model.layers.0.mlp.Wo",
|
| 98 |
+
"text_model.layers.0.mlp.Wi",
|
| 99 |
+
"text_model.layers.0.attn.Wo",
|
| 100 |
+
"text_model.layers.0.attn.Wqkv",
|
| 101 |
+
"text_model.embeddings.tok_embeddings"
|
| 102 |
+
],
|
| 103 |
+
"unquantized_dtype": "float16",
|
| 104 |
+
"float32_tensors": [
|
| 105 |
+
"text_audio_head.layer_norm.bias",
|
| 106 |
+
"text_audio_head.layer_norm.weight",
|
| 107 |
+
"text_model.final_norm.weight",
|
| 108 |
+
"text_audio_head.proj.weight"
|
| 109 |
+
]
|
| 110 |
+
},
|
| 111 |
+
"weight_bytes": 345597033,
|
| 112 |
+
"files": {
|
| 113 |
+
"model.safetensors": "48ffbbea2595722fffcd320bc07a5af07149fafcf00f03f9b02b4f138262928e",
|
| 114 |
+
"config.json": "9402387f9eb47c9ba91bc6b62c13834d34a1bf31cafcee7e4211fd9997d834d7",
|
| 115 |
+
"tokenizer.json": "43e9ea931bb0f359d16d0e435c26abbebd9a42a7298270b8ab7e5c9fa1be1dcb",
|
| 116 |
+
"tokenizer_config.json": "adec72131bbba5a043594c1e3bf5930608546e46a51432b2502b392ec280551b",
|
| 117 |
+
"special_tokens_map.json": "ea97ecdbcc73713039d8d64dbb05e3689495c96657fbd9a18f5bed381be81049",
|
| 118 |
+
"pe_text.py": "025bc642db25eddad124a289535acf3cbca1d7a88bf409c7b9fe1b4aecd6f4f7"
|
| 119 |
+
}
|
| 120 |
+
}
|
model/pe-text/model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:48ffbbea2595722fffcd320bc07a5af07149fafcf00f03f9b02b4f138262928e
|
| 3 |
+
size 345597033
|
model/pe-text/pe_text.py
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Native PE text embeddings with the publisher's ModernBERT CLS readout."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from types import SimpleNamespace
|
| 8 |
+
|
| 9 |
+
import mlx.core as mx
|
| 10 |
+
import mlx.nn as nn
|
| 11 |
+
import numpy as np
|
| 12 |
+
from tokenizers import Tokenizer
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class Embeddings(nn.Module):
|
| 16 |
+
def __init__(self, config):
|
| 17 |
+
super().__init__()
|
| 18 |
+
self.tok_embeddings = nn.Embedding(config.vocab_size, config.hidden_size)
|
| 19 |
+
self.norm = nn.LayerNorm(config.hidden_size, eps=config.norm_eps, bias=False)
|
| 20 |
+
|
| 21 |
+
def __call__(self, tokens):
|
| 22 |
+
return self.norm(self.tok_embeddings(tokens))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
class Attention(nn.Module):
|
| 26 |
+
def __init__(self, config, kind: str):
|
| 27 |
+
super().__init__()
|
| 28 |
+
self.heads = config.num_attention_heads
|
| 29 |
+
self.dimension = config.hidden_size // self.heads
|
| 30 |
+
self.Wqkv = nn.Linear(config.hidden_size, 3 * config.hidden_size, bias=False)
|
| 31 |
+
self.Wo = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
|
| 32 |
+
self.rope = nn.RoPE(self.dimension, traditional=False,
|
| 33 |
+
base=config.rope_parameters[kind]["rope_theta"])
|
| 34 |
+
|
| 35 |
+
def __call__(self, value, mask):
|
| 36 |
+
shape = (*value.shape[:2], 3, self.heads, self.dimension)
|
| 37 |
+
packed = self.Wqkv(value).reshape(shape)
|
| 38 |
+
query = packed[:, :, 0].transpose(0, 2, 1, 3)
|
| 39 |
+
key = packed[:, :, 1].transpose(0, 2, 1, 3)
|
| 40 |
+
values = packed[:, :, 2].transpose(0, 2, 1, 3)
|
| 41 |
+
attended = mx.fast.scaled_dot_product_attention(
|
| 42 |
+
self.rope(query), self.rope(key), values, scale=self.dimension**-0.5, mask=mask)
|
| 43 |
+
return self.Wo(attended.transpose(0, 2, 1, 3).reshape(value.shape))
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
class Mlp(nn.Module):
|
| 47 |
+
def __init__(self, config):
|
| 48 |
+
super().__init__()
|
| 49 |
+
self.Wi = nn.Linear(config.hidden_size, 2 * config.intermediate_size, bias=False)
|
| 50 |
+
self.Wo = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
|
| 51 |
+
|
| 52 |
+
def __call__(self, value):
|
| 53 |
+
value, gate = mx.split(self.Wi(value), 2, axis=-1)
|
| 54 |
+
return self.Wo(nn.gelu(value) * gate)
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
class Layer(nn.Module):
|
| 58 |
+
def __init__(self, config, index: int):
|
| 59 |
+
super().__init__()
|
| 60 |
+
self.local = config.layer_types[index] == "sliding_attention"
|
| 61 |
+
self.attn_norm = nn.Identity() if index == 0 else nn.LayerNorm(
|
| 62 |
+
config.hidden_size, eps=config.norm_eps, bias=False)
|
| 63 |
+
self.attn = Attention(config, config.layer_types[index])
|
| 64 |
+
self.mlp_norm = nn.LayerNorm(config.hidden_size, eps=config.norm_eps, bias=False)
|
| 65 |
+
self.mlp = Mlp(config)
|
| 66 |
+
|
| 67 |
+
def __call__(self, value, mask):
|
| 68 |
+
value = value + self.attn(self.attn_norm(value), mask if self.local else None)
|
| 69 |
+
return value + self.mlp(self.mlp_norm(value))
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
class Text(nn.Module):
|
| 73 |
+
def __init__(self, config):
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.window = config.local_attention // 2
|
| 76 |
+
self.embeddings = Embeddings(config)
|
| 77 |
+
self.layers = [Layer(config, index) for index in range(config.num_hidden_layers)]
|
| 78 |
+
self.final_norm = nn.LayerNorm(config.hidden_size, eps=config.norm_eps, bias=False)
|
| 79 |
+
|
| 80 |
+
def __call__(self, tokens):
|
| 81 |
+
positions = mx.arange(tokens.shape[1])
|
| 82 |
+
# Transformers includes both boundaries of the bidirectional local window.
|
| 83 |
+
mask = mx.abs(positions[:, None] - positions[None, :]) <= self.window
|
| 84 |
+
value = self.embeddings(tokens)
|
| 85 |
+
for layer in self.layers:
|
| 86 |
+
value = layer(value, mask)
|
| 87 |
+
return self.final_norm(value[:, 0].astype(mx.float32))
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
class Head(nn.Module):
|
| 91 |
+
def __init__(self, width: int):
|
| 92 |
+
super().__init__()
|
| 93 |
+
self.layer_norm = nn.LayerNorm(width, eps=1e-6)
|
| 94 |
+
self.proj = nn.Linear(width, width, bias=False)
|
| 95 |
+
|
| 96 |
+
def __call__(self, value):
|
| 97 |
+
return self.proj(self.layer_norm(value))
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
class Model(nn.Module):
|
| 101 |
+
def __init__(self, configuration: dict):
|
| 102 |
+
super().__init__()
|
| 103 |
+
config = SimpleNamespace(**configuration["text_config"])
|
| 104 |
+
if (config.model_type != "modernbert" or config.hidden_activation != "gelu"
|
| 105 |
+
or config.attention_bias or config.mlp_bias or config.norm_bias):
|
| 106 |
+
raise ValueError("PE text requires the pinned unbiased ModernBERT GELU architecture")
|
| 107 |
+
if (config.hidden_size != 1024 or config.num_attention_heads != 16
|
| 108 |
+
or config.num_hidden_layers != 22 or config.intermediate_size != 2624
|
| 109 |
+
or config.vocab_size != 50368 or config.local_attention != 128
|
| 110 |
+
or config.max_position_embeddings != 8192):
|
| 111 |
+
raise ValueError("The pinned PE text dimensions changed")
|
| 112 |
+
layers = ["sliding_attention" if index % 3 else "full_attention" for index in range(22)]
|
| 113 |
+
if config.layer_types != layers or config.rope_parameters != {
|
| 114 |
+
"full_attention": {"rope_type": "default", "rope_theta": 160000.0},
|
| 115 |
+
"sliding_attention": {"rope_type": "default", "rope_theta": 10000.0},
|
| 116 |
+
}:
|
| 117 |
+
raise ValueError("The pinned PE text attention pattern changed")
|
| 118 |
+
self.text_model = Text(config)
|
| 119 |
+
self.text_audio_head = Head(config.hidden_size)
|
| 120 |
+
|
| 121 |
+
def __call__(self, tokens):
|
| 122 |
+
if tokens.ndim != 2 or tokens.shape[0] != 1 or not 2 <= tokens.shape[1] <= 8192:
|
| 123 |
+
raise ValueError("PE text expects one complete token sequence of length 2 through 8192")
|
| 124 |
+
return self.text_audio_head(self.text_model(tokens))
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
class Runtime:
|
| 128 |
+
def __init__(self, path: Path):
|
| 129 |
+
path = Path(path)
|
| 130 |
+
config = json.loads((path / "config.json").read_text())
|
| 131 |
+
self.limit = config["text_config"]["max_position_embeddings"]
|
| 132 |
+
self.cls = config["text_config"]["cls_token_id"]
|
| 133 |
+
self.sep = config["text_config"]["sep_token_id"]
|
| 134 |
+
self.tokenizer = Tokenizer.from_file(str(path / "tokenizer.json"))
|
| 135 |
+
self.tokenizer.no_truncation()
|
| 136 |
+
self.tokenizer.no_padding()
|
| 137 |
+
self.model = Model(config)
|
| 138 |
+
quantization = config["quantization"]
|
| 139 |
+
nn.quantize(self.model, bits=quantization["bits"], group_size=quantization["group_size"],
|
| 140 |
+
class_predicate=lambda name, module: name in quantization["modules"])
|
| 141 |
+
self.model.load_weights(str(path / "model.safetensors"), strict=True)
|
| 142 |
+
self.model.eval()
|
| 143 |
+
mx.eval(self.model.parameters())
|
| 144 |
+
|
| 145 |
+
def encode(self, text: str) -> np.ndarray:
|
| 146 |
+
if not isinstance(text, str):
|
| 147 |
+
raise TypeError("PE text expects a string")
|
| 148 |
+
tokens = self.tokenizer.encode(text, add_special_tokens=True).ids
|
| 149 |
+
if len(tokens) > self.limit:
|
| 150 |
+
raise ValueError(f"PE text has {len(tokens)} tokens; the maximum is {self.limit}")
|
| 151 |
+
if not tokens or tokens[0] != self.cls or tokens[-1] != self.sep:
|
| 152 |
+
raise ValueError("The PE tokenizer must preserve the publisher's CLS and SEP tokens")
|
| 153 |
+
output = self.model(mx.array([tokens], dtype=mx.int32))[0].astype(mx.float32)
|
| 154 |
+
mx.eval(output)
|
| 155 |
+
result = np.asarray(output, dtype=np.float32)
|
| 156 |
+
if result.shape != (1024,) or not np.isfinite(result).all():
|
| 157 |
+
raise ValueError("PE text produced an invalid raw embedding")
|
| 158 |
+
return result
|
model/pe-text/special_tokens_map.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cls_token": {
|
| 3 |
+
"content": "[CLS]",
|
| 4 |
+
"lstrip": false,
|
| 5 |
+
"normalized": false,
|
| 6 |
+
"rstrip": false,
|
| 7 |
+
"single_word": false
|
| 8 |
+
},
|
| 9 |
+
"mask_token": {
|
| 10 |
+
"content": "[MASK]",
|
| 11 |
+
"lstrip": true,
|
| 12 |
+
"normalized": false,
|
| 13 |
+
"rstrip": false,
|
| 14 |
+
"single_word": false
|
| 15 |
+
},
|
| 16 |
+
"pad_token": {
|
| 17 |
+
"content": "[PAD]",
|
| 18 |
+
"lstrip": false,
|
| 19 |
+
"normalized": false,
|
| 20 |
+
"rstrip": false,
|
| 21 |
+
"single_word": false
|
| 22 |
+
},
|
| 23 |
+
"sep_token": {
|
| 24 |
+
"content": "[SEP]",
|
| 25 |
+
"lstrip": false,
|
| 26 |
+
"normalized": false,
|
| 27 |
+
"rstrip": false,
|
| 28 |
+
"single_word": false
|
| 29 |
+
},
|
| 30 |
+
"unk_token": {
|
| 31 |
+
"content": "[UNK]",
|
| 32 |
+
"lstrip": false,
|
| 33 |
+
"normalized": false,
|
| 34 |
+
"rstrip": false,
|
| 35 |
+
"single_word": false
|
| 36 |
+
}
|
| 37 |
+
}
|
model/pe-text/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
model/pe-text/tokenizer_config.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": null,
|
| 3 |
+
"backend": "tokenizers",
|
| 4 |
+
"clean_up_tokenization_spaces": true,
|
| 5 |
+
"cls_token": "[CLS]",
|
| 6 |
+
"is_local": false,
|
| 7 |
+
"mask_token": "[MASK]",
|
| 8 |
+
"model_input_names": [
|
| 9 |
+
"input_ids",
|
| 10 |
+
"attention_mask"
|
| 11 |
+
],
|
| 12 |
+
"model_max_length": 8192,
|
| 13 |
+
"pad_token": "[PAD]",
|
| 14 |
+
"processor_class": "PeAudioVideoProcessor",
|
| 15 |
+
"sep_token": "[SEP]",
|
| 16 |
+
"tokenizer_class": "TokenizersBackend",
|
| 17 |
+
"unk_token": "[UNK]"
|
| 18 |
+
}
|
model/preprocessor_config.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"image_processor_type": "Qwen2VLImageProcessorFast",
|
| 3 |
+
"do_convert_rgb": true,
|
| 4 |
+
"do_normalize": true,
|
| 5 |
+
"do_rescale": true,
|
| 6 |
+
"do_resize": true,
|
| 7 |
+
"image_mean": [0.5, 0.5, 0.5],
|
| 8 |
+
"image_std": [0.5, 0.5, 0.5],
|
| 9 |
+
"min_pixels": 262144,
|
| 10 |
+
"max_pixels": 1310720,
|
| 11 |
+
"merge_size": 2,
|
| 12 |
+
"patch_size": 16,
|
| 13 |
+
"processor_class": "Qwen3VLProcessor",
|
| 14 |
+
"resample": 3,
|
| 15 |
+
"rescale_factor": 0.00392156862745098,
|
| 16 |
+
"temporal_patch_size": 2
|
| 17 |
+
}
|
model/processor_config.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"processor_class": "Qwen3VLProcessor",
|
| 3 |
+
"image_processor": {
|
| 4 |
+
"image_processor_type": "Qwen2VLImageProcessorFast",
|
| 5 |
+
"do_convert_rgb": true,
|
| 6 |
+
"do_normalize": true,
|
| 7 |
+
"do_rescale": true,
|
| 8 |
+
"do_resize": true,
|
| 9 |
+
"image_mean": [
|
| 10 |
+
0.5,
|
| 11 |
+
0.5,
|
| 12 |
+
0.5
|
| 13 |
+
],
|
| 14 |
+
"image_std": [
|
| 15 |
+
0.5,
|
| 16 |
+
0.5,
|
| 17 |
+
0.5
|
| 18 |
+
],
|
| 19 |
+
"min_pixels": 262144,
|
| 20 |
+
"max_pixels": 1310720,
|
| 21 |
+
"size": {
|
| 22 |
+
"longest_edge": 16777216,
|
| 23 |
+
"shortest_edge": 65536
|
| 24 |
+
},
|
| 25 |
+
"merge_size": 2,
|
| 26 |
+
"patch_size": 16,
|
| 27 |
+
"resample": 3,
|
| 28 |
+
"rescale_factor": 0.00392156862745098,
|
| 29 |
+
"temporal_patch_size": 2
|
| 30 |
+
},
|
| 31 |
+
"video_processor": {
|
| 32 |
+
"video_processor_type": "Qwen3VLVideoProcessor",
|
| 33 |
+
"do_convert_rgb": true,
|
| 34 |
+
"do_normalize": true,
|
| 35 |
+
"do_rescale": true,
|
| 36 |
+
"do_resize": true,
|
| 37 |
+
"do_sample_frames": true,
|
| 38 |
+
"fps": 2,
|
| 39 |
+
"image_mean": [
|
| 40 |
+
0.5,
|
| 41 |
+
0.5,
|
| 42 |
+
0.5
|
| 43 |
+
],
|
| 44 |
+
"image_std": [
|
| 45 |
+
0.5,
|
| 46 |
+
0.5,
|
| 47 |
+
0.5
|
| 48 |
+
],
|
| 49 |
+
"max_frames": 768,
|
| 50 |
+
"min_frames": 4,
|
| 51 |
+
"merge_size": 2,
|
| 52 |
+
"patch_size": 16,
|
| 53 |
+
"resample": 3,
|
| 54 |
+
"rescale_factor": 0.00392156862745098,
|
| 55 |
+
"size": {
|
| 56 |
+
"longest_edge": 25165824,
|
| 57 |
+
"shortest_edge": 4096
|
| 58 |
+
},
|
| 59 |
+
"temporal_patch_size": 2
|
| 60 |
+
}
|
| 61 |
+
}
|
model/tokenizer.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:8e08386ae43d4a5024a97dadbdc7291d6770135921c97e913644f991b3a87b8e
|
| 3 |
+
size 2189933
|
model/tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:7bbf567d9e053a72f0bd8ea475e672d10d537578f6adb521ab42da2badfa8bef
|
| 3 |
+
size 11423488
|
model/tokenizer_config.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_prefix_space": false,
|
| 3 |
+
"backend": "tokenizers",
|
| 4 |
+
"bos_token": null,
|
| 5 |
+
"clean_up_tokenization_spaces": false,
|
| 6 |
+
"eos_token": "<|im_end|>",
|
| 7 |
+
"errors": "replace",
|
| 8 |
+
"is_local": false,
|
| 9 |
+
"model_max_length": 131072,
|
| 10 |
+
"pad_token": "<|endoftext|>",
|
| 11 |
+
"processor_class": "Qwen3VLProcessor",
|
| 12 |
+
"split_special_tokens": false,
|
| 13 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 14 |
+
"unk_token": null
|
| 15 |
+
}
|
model/upstream-notices.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Model lineage
|
| 2 |
+
|
| 3 |
+
Nanobe builds on Jina AI's [jina-embeddings-v5-omni-small-retrieval](https://huggingface.co/jinaai/jina-embeddings-v5-omni-small-retrieval), revision `12949877f0092093f366c6450340011320152a05`, and Meta's [Perception Encoder AV](https://huggingface.co/facebook/pe-av-small). The Perception Encoder text component records revision `dd050762bb9704ae9cd996ca45532a98f81d817e`.
|
| 4 |
+
|
| 5 |
+
The original Jina model declares CC BY-NC 4.0. The original Meta model declares Apache 2.0. Their license texts are preserved in [licenses/jina-cc-by-nc-4.0.txt](licenses/jina-cc-by-nc-4.0.txt) and [licenses/meta-apache-2.0.txt](licenses/meta-apache-2.0.txt).
|
model/video_preprocessor_config.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"video_processor_type": "Qwen3VLVideoProcessor",
|
| 3 |
+
"do_convert_rgb": true,
|
| 4 |
+
"do_normalize": true,
|
| 5 |
+
"do_rescale": true,
|
| 6 |
+
"do_resize": true,
|
| 7 |
+
"do_sample_frames": true,
|
| 8 |
+
"fps": 2,
|
| 9 |
+
"image_mean": [0.5, 0.5, 0.5],
|
| 10 |
+
"image_std": [0.5, 0.5, 0.5],
|
| 11 |
+
"max_frames": 768,
|
| 12 |
+
"min_frames": 4,
|
| 13 |
+
"merge_size": 2,
|
| 14 |
+
"patch_size": 16,
|
| 15 |
+
"resample": 3,
|
| 16 |
+
"rescale_factor": 0.00392156862745098,
|
| 17 |
+
"size": {
|
| 18 |
+
"longest_edge": 25165824,
|
| 19 |
+
"shortest_edge": 4096
|
| 20 |
+
},
|
| 21 |
+
"temporal_patch_size": 2
|
| 22 |
+
}
|
source/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Artem G.
|
| 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.
|
source/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Nanobe
|
| 2 |
+
|
| 3 |
+
Nanobe is a local multimodal embedding model and a Python reference implementation for searching folders. It encodes text, document pages, images, audio and video into a shared vector space on Apple Silicon.
|
| 4 |
+
|
| 5 |
+
## Install
|
| 6 |
+
|
| 7 |
+
Use an Apple Silicon Mac with Python 3.12 and [uv](https://docs.astral.sh/uv/). This implementation uses MLX. Linux, Windows and Intel Macs are not supported.
|
| 8 |
+
|
| 9 |
+
Download the complete [Hugging Face release](https://huggingface.co/lokimorty/nanobe). The `source` directory contains the Python package, examples, and dependency lock. The `model` directory contains weights, tokenizers, configuration, and model helpers. Keep that model directory complete.
|
| 10 |
+
|
| 11 |
+
```sh
|
| 12 |
+
uvx --from huggingface-hub hf download lokimorty/nanobe --local-dir nanobe-release
|
| 13 |
+
cd nanobe-release/source
|
| 14 |
+
uv sync --frozen
|
| 15 |
+
export NANOBE_MODEL_DIR="$(cd ../model && pwd)"
|
| 16 |
+
uv run --frozen nanobe --help
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
`uv sync --frozen` installs the versions in the supplied lock and downloads Python 3.12 if needed. It does not download the model. Keep using `uv run --frozen` from this source directory. To run from elsewhere, activate its virtual environment or use the full path to `.venv/bin/nanobe`. Set `NANOBE_MODEL_DIR` in each new shell or pass `--model-dir /path/to/model` to `index` and `search`.
|
| 20 |
+
|
| 21 |
+
DOC and PPT additionally require [LibreOffice](https://www.libreoffice.org/download/download-libreoffice/). Install the Apple Silicon application in `/Applications`, or put `soffice` on PATH. Other supported formats do not require LibreOffice.
|
| 22 |
+
|
| 23 |
+
### Verify the installation
|
| 24 |
+
|
| 25 |
+
Create two small files and a reference copy in the source directory:
|
| 26 |
+
|
| 27 |
+
```sh
|
| 28 |
+
mkdir -p demo
|
| 29 |
+
printf '%s\n' 'The observatory telescope shows Saturn and its rings in the night sky.' > demo/observatory.txt
|
| 30 |
+
printf '%s\n' 'Sourdough bread uses flour, water and a fermented starter.' > demo/bread.txt
|
| 31 |
+
cp demo/observatory.txt reference.txt
|
| 32 |
+
uv run --frozen nanobe index demo --format json
|
| 33 |
+
uv run --frozen nanobe search demo "Saturn through a telescope" --format json
|
| 34 |
+
uv run --frozen nanobe search demo --file reference.txt --format json
|
| 35 |
+
uv run --frozen nanobe status demo --format json
|
| 36 |
+
uv run --frozen nanobe index demo --format json
|
| 37 |
+
```
|
| 38 |
+
|
| 39 |
+
The first index should report `embedded_files: 2`, `embedded_segments: 2` and `failed_files: 0`. Both searches should put `observatory.txt` first. Status should show `ready: 2` and `index_ready: true`. The second index should report `embedded_files: 0` and `unchanged_files: 2`.
|
| 40 |
+
|
| 41 |
+
Now run the Python reference example over the same files:
|
| 42 |
+
|
| 43 |
+
```sh
|
| 44 |
+
uv run --frozen python examples/search.py --query "Saturn through a telescope" demo/*.txt
|
| 45 |
+
uv run --frozen python examples/search.py --file reference.txt demo/*.txt
|
| 46 |
+
```
|
| 47 |
+
|
| 48 |
+
Both should rank `demo/observatory.txt` first. This example encodes every supplied file, applies the correct query and item calibration, and ranks by the best matching segment. It performs exact semantic scoring with full-precision embeddings. The folder CLI also stores an index and adds lexical matching for text queries, so its scores are not interchangeable with the example's cosine scores.
|
| 49 |
+
|
| 50 |
+
To search your own folder:
|
| 51 |
+
|
| 52 |
+
```sh
|
| 53 |
+
uv run --frozen nanobe index ~/Creative
|
| 54 |
+
uv run --frozen nanobe search ~/Creative "a presentation about sustainable packaging"
|
| 55 |
+
uv run --frozen nanobe search ~/Creative --file ~/Desktop/reference.jpg
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
See [Python inference](docs/model.md#python-inference) for the embedding, segmentation and scoring calls used by the example.
|
| 59 |
+
|
| 60 |
+
### Troubleshooting
|
| 61 |
+
|
| 62 |
+
- Missing model files or an integrity error: download the complete matching model directory again and point `NANOBE_MODEL_DIR` at the directory containing `manifest.json`.
|
| 63 |
+
- An index belongs to another model package: run `nanobe index` with the current model to rebuild its embeddings. Keep a separate `--state-dir` if you need both indexes.
|
| 64 |
+
- DOC/PPT conversion failure: check that LibreOffice is installed and the file opens there. Nanobe reports a per-file failure and continues indexing other files.
|
| 65 |
+
- A decoder error: check the reported file. `--debug` includes a traceback; `--format json` keeps results on stdout and warnings on stderr.
|
| 66 |
+
|
| 67 |
+
## Folder search
|
| 68 |
+
|
| 69 |
+
Indexing records files and their segments in `.nanobe` inside the selected folder. Repeating `index` updates changed files. `--rebuild` re-embeds supported files. `status --format json` reports catalog state. Hidden files, symbolic links and the index directory are excluded.
|
| 70 |
+
|
| 71 |
+
Text queries combine semantic retrieval with lexical matches over relative paths and extracted text. Lexical matching helps with names, symbols and exact phrases. A reference file uses semantic retrieval only. The catalog searches an int8 approximate index, reranks candidates using stored higher-precision vectors, then groups segments into file results. These retrieval steps belong to the reference application. They are not additional model outputs.
|
| 72 |
+
|
| 73 |
+
## Inputs and limits
|
| 74 |
+
|
| 75 |
+
| Input | Reader behavior |
|
| 76 |
+
| --- | --- |
|
| 77 |
+
| Text and source code, including TXT, Markdown, CSV, TSV, JSON, YAML, XML and HTML | UTF-8 text. CSV remains plain text. |
|
| 78 |
+
| DOCX, PPTX, EPUB | Extracted text. Embedded pictures and page layout are not encoded. |
|
| 79 |
+
| RTF | Text extraction with declared codepages and Unicode. Formatting and pictures are not encoded. |
|
| 80 |
+
| XLS, XLSX | Saved cell values serialized as CSV with sheet names and repeated first-row headers. Formulas are not recalculated. |
|
| 81 |
+
| PDF | Rendered pages through native vision, without OCR. |
|
| 82 |
+
| DOC, PPT | LibreOffice conversion to PDF, then the same rendered-page path. |
|
| 83 |
+
| JPG, JPEG, PNG, WebP, GIF, BMP, TIF, TIFF, AVIF | Pillow decoding to RGB. Animated inputs use the opened frame. |
|
| 84 |
+
| HEIC | Primary image with container orientation applied, HDR reduced to 8 bits, then RGB. |
|
| 85 |
+
| SVG | Visual rasterization on white within a 2048-by-2048 box, preserving aspect ratio. |
|
| 86 |
+
| WAV, MP3, FLAC, OGG, M4A, AAC, OPUS | Decoded audio, without a transcript requirement. |
|
| 87 |
+
| MP4, MOV, MKV, WebM, AVI, M4V | Sampled video frames with the decoded soundtrack when present. |
|
| 88 |
+
|
| 89 |
+
SVG text uses installed fonts and the renderer's platform-dependent fallback. No font is bundled. Results can differ between machines with different fonts.
|
| 90 |
+
|
| 91 |
+
DOC and PPT require LibreOffice. Nanobe finds `soffice` or `libreoffice` on PATH, or the standard `/Applications/LibreOffice.app` installation. Conversion uses a temporary profile, at most eight files per batch and a 120-second timeout per batch. Search results retain the original DOC/PPT path. Temporary PDFs are removed after processing and are not cataloged as separate files.
|
| 92 |
+
|
| 93 |
+
Text uses at most eight overlapping segments of 6,000 characters with 600-character overlap. Workbooks share eight segment slots across the first eight nonempty sheets, with at most 6,000 characters per segment. Short sheets can leave slots unused. Each segment repeats the sheet name and first row. Oversized cells and omitted rows or sheets produce a truncation warning on stderr. Spreadsheet styling, charts and recalculation are outside this reader.
|
| 94 |
+
|
| 95 |
+
PDFs and converted DOC/PPT files use the first 24 pages plus up to eight pages spread across the remainder. Audio retains a whole-file row and up to 24 windows of 45 seconds, plus a pooled row when windows exist. Video retains a whole-file row and up to eight windows of 24 seconds. Window starts are spread across the duration. Each video pass samples up to 16 frames. These limits mean some content in long files is not encoded as individual segments.
|
| 96 |
+
|
| 97 |
+
The native audio tower uses a strided summary capped at 120 seconds for a whole-file pass. The acoustic encoder also reads the whole recording in windows of up to 30 seconds. The 120-second summary is not a cap on total decoding work or recording length.
|
| 98 |
+
|
| 99 |
+
The installed decoders must support the file's actual contents. An extension alone does not guarantee decoding. Empty or unreadable files cannot produce an embedding. Missing LibreOffice, conversion timeouts and decoder errors are recorded as per-file failures during indexing, which continues with other files. A failed reference-file query reports an error. Unsupported extensions are skipped. Search requires a successfully built index and a matching model package.
|
| 100 |
+
|
| 101 |
+
## License
|
| 102 |
+
|
| 103 |
+
Nanobe is released under the [MIT License](LICENSE).
|
| 104 |
+
|
| 105 |
+
See [model lineage](docs/upstream-notices.md) for the Jina and Meta components used in Nanobe.
|
source/docs/licenses/jina-cc-by-nc-4.0.txt
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Attribution-NonCommercial 4.0 International
|
| 2 |
+
|
| 3 |
+
=======================================================================
|
| 4 |
+
|
| 5 |
+
Creative Commons Corporation ("Creative Commons") is not a law firm and
|
| 6 |
+
does not provide legal services or legal advice. Distribution of
|
| 7 |
+
Creative Commons public licenses does not create a lawyer-client or
|
| 8 |
+
other relationship. Creative Commons makes its licenses and related
|
| 9 |
+
information available on an "as-is" basis. Creative Commons gives no
|
| 10 |
+
warranties regarding its licenses, any material licensed under their
|
| 11 |
+
terms and conditions, or any related information. Creative Commons
|
| 12 |
+
disclaims all liability for damages resulting from their use to the
|
| 13 |
+
fullest extent possible.
|
| 14 |
+
|
| 15 |
+
Using Creative Commons Public Licenses
|
| 16 |
+
|
| 17 |
+
Creative Commons public licenses provide a standard set of terms and
|
| 18 |
+
conditions that creators and other rights holders may use to share
|
| 19 |
+
original works of authorship and other material subject to copyright
|
| 20 |
+
and certain other rights specified in the public license below. The
|
| 21 |
+
following considerations are for informational purposes only, are not
|
| 22 |
+
exhaustive, and do not form part of our licenses.
|
| 23 |
+
|
| 24 |
+
Considerations for licensors: Our public licenses are
|
| 25 |
+
intended for use by those authorized to give the public
|
| 26 |
+
permission to use material in ways otherwise restricted by
|
| 27 |
+
copyright and certain other rights. Our licenses are
|
| 28 |
+
irrevocable. Licensors should read and understand the terms
|
| 29 |
+
and conditions of the license they choose before applying it.
|
| 30 |
+
Licensors should also secure all rights necessary before
|
| 31 |
+
applying our licenses so that the public can reuse the
|
| 32 |
+
material as expected. Licensors should clearly mark any
|
| 33 |
+
material not subject to the license. This includes other CC-
|
| 34 |
+
licensed material, or material used under an exception or
|
| 35 |
+
limitation to copyright. More considerations for licensors:
|
| 36 |
+
wiki.creativecommons.org/Considerations_for_licensors
|
| 37 |
+
|
| 38 |
+
Considerations for the public: By using one of our public
|
| 39 |
+
licenses, a licensor grants the public permission to use the
|
| 40 |
+
licensed material under specified terms and conditions. If
|
| 41 |
+
the licensor's permission is not necessary for any reason--for
|
| 42 |
+
example, because of any applicable exception or limitation to
|
| 43 |
+
copyright--then that use is not regulated by the license. Our
|
| 44 |
+
licenses grant only permissions under copyright and certain
|
| 45 |
+
other rights that a licensor has authority to grant. Use of
|
| 46 |
+
the licensed material may still be restricted for other
|
| 47 |
+
reasons, including because others have copyright or other
|
| 48 |
+
rights in the material. A licensor may make special requests,
|
| 49 |
+
such as asking that all changes be marked or described.
|
| 50 |
+
Although not required by our licenses, you are encouraged to
|
| 51 |
+
respect those requests where reasonable. More considerations
|
| 52 |
+
for the public:
|
| 53 |
+
wiki.creativecommons.org/Considerations_for_licensees
|
| 54 |
+
|
| 55 |
+
=======================================================================
|
| 56 |
+
|
| 57 |
+
Creative Commons Attribution-NonCommercial 4.0 International Public
|
| 58 |
+
License
|
| 59 |
+
|
| 60 |
+
By exercising the Licensed Rights (defined below), You accept and agree
|
| 61 |
+
to be bound by the terms and conditions of this Creative Commons
|
| 62 |
+
Attribution-NonCommercial 4.0 International Public License ("Public
|
| 63 |
+
License"). To the extent this Public License may be interpreted as a
|
| 64 |
+
contract, You are granted the Licensed Rights in consideration of Your
|
| 65 |
+
acceptance of these terms and conditions, and the Licensor grants You
|
| 66 |
+
such rights in consideration of benefits the Licensor receives from
|
| 67 |
+
making the Licensed Material available under these terms and
|
| 68 |
+
conditions.
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
Section 1 -- Definitions.
|
| 72 |
+
|
| 73 |
+
a. Adapted Material means material subject to Copyright and Similar
|
| 74 |
+
Rights that is derived from or based upon the Licensed Material
|
| 75 |
+
and in which the Licensed Material is translated, altered,
|
| 76 |
+
arranged, transformed, or otherwise modified in a manner requiring
|
| 77 |
+
permission under the Copyright and Similar Rights held by the
|
| 78 |
+
Licensor. For purposes of this Public License, where the Licensed
|
| 79 |
+
Material is a musical work, performance, or sound recording,
|
| 80 |
+
Adapted Material is always produced where the Licensed Material is
|
| 81 |
+
synched in timed relation with a moving image.
|
| 82 |
+
|
| 83 |
+
b. Adapter's License means the license You apply to Your Copyright
|
| 84 |
+
and Similar Rights in Your contributions to Adapted Material in
|
| 85 |
+
accordance with the terms and conditions of this Public License.
|
| 86 |
+
|
| 87 |
+
c. Copyright and Similar Rights means copyright and/or similar rights
|
| 88 |
+
closely related to copyright including, without limitation,
|
| 89 |
+
performance, broadcast, sound recording, and Sui Generis Database
|
| 90 |
+
Rights, without regard to how the rights are labeled or
|
| 91 |
+
categorized. For purposes of this Public License, the rights
|
| 92 |
+
specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
| 93 |
+
Rights.
|
| 94 |
+
d. Effective Technological Measures means those measures that, in the
|
| 95 |
+
absence of proper authority, may not be circumvented under laws
|
| 96 |
+
fulfilling obligations under Article 11 of the WIPO Copyright
|
| 97 |
+
Treaty adopted on December 20, 1996, and/or similar international
|
| 98 |
+
agreements.
|
| 99 |
+
|
| 100 |
+
e. Exceptions and Limitations means fair use, fair dealing, and/or
|
| 101 |
+
any other exception or limitation to Copyright and Similar Rights
|
| 102 |
+
that applies to Your use of the Licensed Material.
|
| 103 |
+
|
| 104 |
+
f. Licensed Material means the artistic or literary work, database,
|
| 105 |
+
or other material to which the Licensor applied this Public
|
| 106 |
+
License.
|
| 107 |
+
|
| 108 |
+
g. Licensed Rights means the rights granted to You subject to the
|
| 109 |
+
terms and conditions of this Public License, which are limited to
|
| 110 |
+
all Copyright and Similar Rights that apply to Your use of the
|
| 111 |
+
Licensed Material and that the Licensor has authority to license.
|
| 112 |
+
|
| 113 |
+
h. Licensor means the individual(s) or entity(ies) granting rights
|
| 114 |
+
under this Public License.
|
| 115 |
+
|
| 116 |
+
i. NonCommercial means not primarily intended for or directed towards
|
| 117 |
+
commercial advantage or monetary compensation. For purposes of
|
| 118 |
+
this Public License, the exchange of the Licensed Material for
|
| 119 |
+
other material subject to Copyright and Similar Rights by digital
|
| 120 |
+
file-sharing or similar means is NonCommercial provided there is
|
| 121 |
+
no payment of monetary compensation in connection with the
|
| 122 |
+
exchange.
|
| 123 |
+
|
| 124 |
+
j. Share means to provide material to the public by any means or
|
| 125 |
+
process that requires permission under the Licensed Rights, such
|
| 126 |
+
as reproduction, public display, public performance, distribution,
|
| 127 |
+
dissemination, communication, or importation, and to make material
|
| 128 |
+
available to the public including in ways that members of the
|
| 129 |
+
public may access the material from a place and at a time
|
| 130 |
+
individually chosen by them.
|
| 131 |
+
|
| 132 |
+
k. Sui Generis Database Rights means rights other than copyright
|
| 133 |
+
resulting from Directive 96/9/EC of the European Parliament and of
|
| 134 |
+
the Council of 11 March 1996 on the legal protection of databases,
|
| 135 |
+
as amended and/or succeeded, as well as other essentially
|
| 136 |
+
equivalent rights anywhere in the world.
|
| 137 |
+
|
| 138 |
+
l. You means the individual or entity exercising the Licensed Rights
|
| 139 |
+
under this Public License. Your has a corresponding meaning.
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
Section 2 -- Scope.
|
| 143 |
+
|
| 144 |
+
a. License grant.
|
| 145 |
+
|
| 146 |
+
1. Subject to the terms and conditions of this Public License,
|
| 147 |
+
the Licensor hereby grants You a worldwide, royalty-free,
|
| 148 |
+
non-sublicensable, non-exclusive, irrevocable license to
|
| 149 |
+
exercise the Licensed Rights in the Licensed Material to:
|
| 150 |
+
|
| 151 |
+
a. reproduce and Share the Licensed Material, in whole or
|
| 152 |
+
in part, for NonCommercial purposes only; and
|
| 153 |
+
|
| 154 |
+
b. produce, reproduce, and Share Adapted Material for
|
| 155 |
+
NonCommercial purposes only.
|
| 156 |
+
|
| 157 |
+
2. Exceptions and Limitations. For the avoidance of doubt, where
|
| 158 |
+
Exceptions and Limitations apply to Your use, this Public
|
| 159 |
+
License does not apply, and You do not need to comply with
|
| 160 |
+
its terms and conditions.
|
| 161 |
+
|
| 162 |
+
3. Term. The term of this Public License is specified in Section
|
| 163 |
+
6(a).
|
| 164 |
+
|
| 165 |
+
4. Media and formats; technical modifications allowed. The
|
| 166 |
+
Licensor authorizes You to exercise the Licensed Rights in
|
| 167 |
+
all media and formats whether now known or hereafter created,
|
| 168 |
+
and to make technical modifications necessary to do so. The
|
| 169 |
+
Licensor waives and/or agrees not to assert any right or
|
| 170 |
+
authority to forbid You from making technical modifications
|
| 171 |
+
necessary to exercise the Licensed Rights, including
|
| 172 |
+
technical modifications necessary to circumvent Effective
|
| 173 |
+
Technological Measures. For purposes of this Public License,
|
| 174 |
+
simply making modifications authorized by this Section 2(a)
|
| 175 |
+
(4) never produces Adapted Material.
|
| 176 |
+
|
| 177 |
+
5. Downstream recipients.
|
| 178 |
+
|
| 179 |
+
a. Offer from the Licensor -- Licensed Material. Every
|
| 180 |
+
recipient of the Licensed Material automatically
|
| 181 |
+
receives an offer from the Licensor to exercise the
|
| 182 |
+
Licensed Rights under the terms and conditions of this
|
| 183 |
+
Public License.
|
| 184 |
+
|
| 185 |
+
b. No downstream restrictions. You may not offer or impose
|
| 186 |
+
any additional or different terms or conditions on, or
|
| 187 |
+
apply any Effective Technological Measures to, the
|
| 188 |
+
Licensed Material if doing so restricts exercise of the
|
| 189 |
+
Licensed Rights by any recipient of the Licensed
|
| 190 |
+
Material.
|
| 191 |
+
|
| 192 |
+
6. No endorsement. Nothing in this Public License constitutes or
|
| 193 |
+
may be construed as permission to assert or imply that You
|
| 194 |
+
are, or that Your use of the Licensed Material is, connected
|
| 195 |
+
with, or sponsored, endorsed, or granted official status by,
|
| 196 |
+
the Licensor or others designated to receive attribution as
|
| 197 |
+
provided in Section 3(a)(1)(A)(i).
|
| 198 |
+
|
| 199 |
+
b. Other rights.
|
| 200 |
+
|
| 201 |
+
1. Moral rights, such as the right of integrity, are not
|
| 202 |
+
licensed under this Public License, nor are publicity,
|
| 203 |
+
privacy, and/or other similar personality rights; however, to
|
| 204 |
+
the extent possible, the Licensor waives and/or agrees not to
|
| 205 |
+
assert any such rights held by the Licensor to the limited
|
| 206 |
+
extent necessary to allow You to exercise the Licensed
|
| 207 |
+
Rights, but not otherwise.
|
| 208 |
+
|
| 209 |
+
2. Patent and trademark rights are not licensed under this
|
| 210 |
+
Public License.
|
| 211 |
+
|
| 212 |
+
3. To the extent possible, the Licensor waives any right to
|
| 213 |
+
collect royalties from You for the exercise of the Licensed
|
| 214 |
+
Rights, whether directly or through a collecting society
|
| 215 |
+
under any voluntary or waivable statutory or compulsory
|
| 216 |
+
licensing scheme. In all other cases the Licensor expressly
|
| 217 |
+
reserves any right to collect such royalties, including when
|
| 218 |
+
the Licensed Material is used other than for NonCommercial
|
| 219 |
+
purposes.
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
Section 3 -- License Conditions.
|
| 223 |
+
|
| 224 |
+
Your exercise of the Licensed Rights is expressly made subject to the
|
| 225 |
+
following conditions.
|
| 226 |
+
|
| 227 |
+
a. Attribution.
|
| 228 |
+
|
| 229 |
+
1. If You Share the Licensed Material (including in modified
|
| 230 |
+
form), You must:
|
| 231 |
+
|
| 232 |
+
a. retain the following if it is supplied by the Licensor
|
| 233 |
+
with the Licensed Material:
|
| 234 |
+
|
| 235 |
+
i. identification of the creator(s) of the Licensed
|
| 236 |
+
Material and any others designated to receive
|
| 237 |
+
attribution, in any reasonable manner requested by
|
| 238 |
+
the Licensor (including by pseudonym if
|
| 239 |
+
designated);
|
| 240 |
+
|
| 241 |
+
ii. a copyright notice;
|
| 242 |
+
|
| 243 |
+
iii. a notice that refers to this Public License;
|
| 244 |
+
|
| 245 |
+
iv. a notice that refers to the disclaimer of
|
| 246 |
+
warranties;
|
| 247 |
+
|
| 248 |
+
v. a URI or hyperlink to the Licensed Material to the
|
| 249 |
+
extent reasonably practicable;
|
| 250 |
+
|
| 251 |
+
b. indicate if You modified the Licensed Material and
|
| 252 |
+
retain an indication of any previous modifications; and
|
| 253 |
+
|
| 254 |
+
c. indicate the Licensed Material is licensed under this
|
| 255 |
+
Public License, and include the text of, or the URI or
|
| 256 |
+
hyperlink to, this Public License.
|
| 257 |
+
|
| 258 |
+
2. You may satisfy the conditions in Section 3(a)(1) in any
|
| 259 |
+
reasonable manner based on the medium, means, and context in
|
| 260 |
+
which You Share the Licensed Material. For example, it may be
|
| 261 |
+
reasonable to satisfy the conditions by providing a URI or
|
| 262 |
+
hyperlink to a resource that includes the required
|
| 263 |
+
information.
|
| 264 |
+
|
| 265 |
+
3. If requested by the Licensor, You must remove any of the
|
| 266 |
+
information required by Section 3(a)(1)(A) to the extent
|
| 267 |
+
reasonably practicable.
|
| 268 |
+
|
| 269 |
+
4. If You Share Adapted Material You produce, the Adapter's
|
| 270 |
+
License You apply must not prevent recipients of the Adapted
|
| 271 |
+
Material from complying with this Public License.
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
Section 4 -- Sui Generis Database Rights.
|
| 275 |
+
|
| 276 |
+
Where the Licensed Rights include Sui Generis Database Rights that
|
| 277 |
+
apply to Your use of the Licensed Material:
|
| 278 |
+
|
| 279 |
+
a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
| 280 |
+
to extract, reuse, reproduce, and Share all or a substantial
|
| 281 |
+
portion of the contents of the database for NonCommercial purposes
|
| 282 |
+
only;
|
| 283 |
+
|
| 284 |
+
b. if You include all or a substantial portion of the database
|
| 285 |
+
contents in a database in which You have Sui Generis Database
|
| 286 |
+
Rights, then the database in which You have Sui Generis Database
|
| 287 |
+
Rights (but not its individual contents) is Adapted Material; and
|
| 288 |
+
|
| 289 |
+
c. You must comply with the conditions in Section 3(a) if You Share
|
| 290 |
+
all or a substantial portion of the contents of the database.
|
| 291 |
+
|
| 292 |
+
For the avoidance of doubt, this Section 4 supplements and does not
|
| 293 |
+
replace Your obligations under this Public License where the Licensed
|
| 294 |
+
Rights include other Copyright and Similar Rights.
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
| 298 |
+
|
| 299 |
+
a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
| 300 |
+
EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
| 301 |
+
AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
| 302 |
+
ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
| 303 |
+
IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
| 304 |
+
WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
| 305 |
+
PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
| 306 |
+
ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
| 307 |
+
KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
| 308 |
+
ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
| 309 |
+
|
| 310 |
+
b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
| 311 |
+
TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
| 312 |
+
NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
| 313 |
+
INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
| 314 |
+
COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
| 315 |
+
USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
| 316 |
+
ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
| 317 |
+
DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
| 318 |
+
IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
| 319 |
+
|
| 320 |
+
c. The disclaimer of warranties and limitation of liability provided
|
| 321 |
+
above shall be interpreted in a manner that, to the extent
|
| 322 |
+
possible, most closely approximates an absolute disclaimer and
|
| 323 |
+
waiver of all liability.
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
Section 6 -- Term and Termination.
|
| 327 |
+
|
| 328 |
+
a. This Public License applies for the term of the Copyright and
|
| 329 |
+
Similar Rights licensed here. However, if You fail to comply with
|
| 330 |
+
this Public License, then Your rights under this Public License
|
| 331 |
+
terminate automatically.
|
| 332 |
+
|
| 333 |
+
b. Where Your right to use the Licensed Material has terminated under
|
| 334 |
+
Section 6(a), it reinstates:
|
| 335 |
+
|
| 336 |
+
1. automatically as of the date the violation is cured, provided
|
| 337 |
+
it is cured within 30 days of Your discovery of the
|
| 338 |
+
violation; or
|
| 339 |
+
|
| 340 |
+
2. upon express reinstatement by the Licensor.
|
| 341 |
+
|
| 342 |
+
For the avoidance of doubt, this Section 6(b) does not affect any
|
| 343 |
+
right the Licensor may have to seek remedies for Your violations
|
| 344 |
+
of this Public License.
|
| 345 |
+
|
| 346 |
+
c. For the avoidance of doubt, the Licensor may also offer the
|
| 347 |
+
Licensed Material under separate terms or conditions or stop
|
| 348 |
+
distributing the Licensed Material at any time; however, doing so
|
| 349 |
+
will not terminate this Public License.
|
| 350 |
+
|
| 351 |
+
d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
| 352 |
+
License.
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
Section 7 -- Other Terms and Conditions.
|
| 356 |
+
|
| 357 |
+
a. The Licensor shall not be bound by any additional or different
|
| 358 |
+
terms or conditions communicated by You unless expressly agreed.
|
| 359 |
+
|
| 360 |
+
b. Any arrangements, understandings, or agreements regarding the
|
| 361 |
+
Licensed Material not stated herein are separate from and
|
| 362 |
+
independent of the terms and conditions of this Public License.
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
Section 8 -- Interpretation.
|
| 366 |
+
|
| 367 |
+
a. For the avoidance of doubt, this Public License does not, and
|
| 368 |
+
shall not be interpreted to, reduce, limit, restrict, or impose
|
| 369 |
+
conditions on any use of the Licensed Material that could lawfully
|
| 370 |
+
be made without permission under this Public License.
|
| 371 |
+
|
| 372 |
+
b. To the extent possible, if any provision of this Public License is
|
| 373 |
+
deemed unenforceable, it shall be automatically reformed to the
|
| 374 |
+
minimum extent necessary to make it enforceable. If the provision
|
| 375 |
+
cannot be reformed, it shall be severed from this Public License
|
| 376 |
+
without affecting the enforceability of the remaining terms and
|
| 377 |
+
conditions.
|
| 378 |
+
|
| 379 |
+
c. No term or condition of this Public License will be waived and no
|
| 380 |
+
failure to comply consented to unless expressly agreed to by the
|
| 381 |
+
Licensor.
|
| 382 |
+
|
| 383 |
+
d. Nothing in this Public License constitutes or may be interpreted
|
| 384 |
+
as a limitation upon, or waiver of, any privileges and immunities
|
| 385 |
+
that apply to the Licensor or You, including from the legal
|
| 386 |
+
processes of any jurisdiction or authority.
|
| 387 |
+
|
| 388 |
+
=======================================================================
|
| 389 |
+
|
| 390 |
+
Creative Commons is not a party to its public
|
| 391 |
+
licenses. Notwithstanding, Creative Commons may elect to apply one of
|
| 392 |
+
its public licenses to material it publishes and in those instances
|
| 393 |
+
will be considered the “Licensor.” The text of the Creative Commons
|
| 394 |
+
public licenses is dedicated to the public domain under the CC0 Public
|
| 395 |
+
Domain Dedication. Except for the limited purpose of indicating that
|
| 396 |
+
material is shared under a Creative Commons public license or as
|
| 397 |
+
otherwise permitted by the Creative Commons policies published at
|
| 398 |
+
creativecommons.org/policies, Creative Commons does not authorize the
|
| 399 |
+
use of the trademark "Creative Commons" or any other trademark or logo
|
| 400 |
+
of Creative Commons without its prior written consent including,
|
| 401 |
+
without limitation, in connection with any unauthorized modifications
|
| 402 |
+
to any of its public licenses or any other arrangements,
|
| 403 |
+
understandings, or agreements concerning use of licensed material. For
|
| 404 |
+
the avoidance of doubt, this paragraph does not form part of the
|
| 405 |
+
public licenses.
|
| 406 |
+
|
| 407 |
+
Creative Commons may be contacted at creativecommons.org.
|
| 408 |
+
|