agentmish commited on
Commit
0bdbce9
·
verified ·
1 Parent(s): 24b8cd3

Upload pplx-embed-v1-4b MLX embedding artifact

Browse files
README.md CHANGED
@@ -1,192 +1,89 @@
1
  ---
2
  license: mit
 
 
3
  pipeline_tag: feature-extraction
4
  tags:
 
 
5
  - feature-extraction
6
  - sentence-similarity
7
  - mteb
8
- - sentence-transformers
9
- language:
10
- - multilingual
11
  ---
12
 
 
13
 
14
- <p align="center">
15
- <img src="assets/logo.svg" alt="Perplexity Logo" width="400">
16
- </p>
17
 
18
- <p align="center">pplx-embed-v1: Diffusion-Pretrained Dense and Contextual Embeddings</p>
19
 
20
- `pplx-embed-v1` and `pplx-embed-context-v1` are state-of-the-art text embedding models optimized for real-world, web-scale retrieval tasks.
21
 
22
- - Use **`pplx-embed-v1`** for independent text embedding (queries, documents, semantic search)
23
- - Use **`pplx-embed-context-v1`** for document chunks in RAG systems where surrounding context matters
 
24
 
25
- > [!IMPORTANT]
26
- > `pplx-embed-v1` and `pplx-embed-context-v1` natively produce *unnormalized* int8-quantized embeddings. Ensure that you compare them via *cosine similarity*.
27
 
 
28
 
29
- ![diag.png](assets/diag.png)
30
-
31
- ## Models
32
-
33
- | Model | Dimensions | Context | MRL | Quantization | Instruction | Pooling |
34
- |:-----:|:----------:|:-------:|:---:|:------------:|:-----------:|:-------:|
35
- | `pplx-embed-v1-0.6B` | 1024 | 32K | Yes | INT8/BINARY | No | Mean |
36
- | `pplx-embed-v1-4B` | 2560 | 32K | Yes | INT8/BINARY | No | Mean |
37
- | `pplx-embed-context-v1-0.6B` | 1024 | 32K | Yes | INT8/BINARY | No | Mean |
38
- | `pplx-embed-context-v1-4B` | 2560 | 32K | Yes | INT8/BINARY | No | Mean |
39
-
40
- <sub>All models are built on diffusion continued pre-trained Qwen3 at Perplexity AI.</sub>
41
-
42
- <sub>Many modern embedding models rely on instruction tuning, where users prepend an instruction string to the text being embedded. This can yield a 2%-3% lift on benchmarks, but it also introduces prompt-selection overhead and can make indexing pipelines brittle (small instruction changes can shift embedding space). We deliberately **avoid** this requirement: you can embed the text you want to index directly, without having to choose or maintain an instruction prefix.</sub>
43
-
44
- ## Usage
45
-
46
- <details>
47
- <summary>Via API</summary>
48
 
49
  ```bash
50
- curl -X POST https://api.perplexity.ai/v1/embeddings \
51
- -H "Authorization: Bearer YOUR_API_KEY" \
52
- -H "Content-Type: application/json" \
53
- -d '{
54
- "input": [
55
- "Scientists explore the universe driven by curiosity.",
56
- "Children learn through curious exploration.",
57
- "Historical discoveries began with curious questions.",
58
- "Animals use curiosity to adapt and survive.",
59
- "Philosophy examines the nature of curiosity."
60
- ],
61
- "model": "pplx-embed-v1-4b"
62
- }'
63
- ```
64
-
65
- </details>
66
-
67
-
68
- <details>
69
- <summary>Using SentenceTransformers</summary>
70
-
71
- ```python
72
- from sentence_transformers import SentenceTransformer
73
-
74
- model = SentenceTransformer(
75
- "perplexity-ai/pplx-embed-v1-4B",
76
- trust_remote_code=True
77
- )
78
-
79
- texts = [
80
- "Scientists explore the universe driven by curiosity.",
81
- "Children learn through curious exploration.",
82
- "Historical discoveries began with curious questions.",
83
- "Animals use curiosity to adapt and survive.",
84
- "Philosophy examines the nature of curiosity.",
85
- ]
86
-
87
- embeddings = model.encode(texts) # Shape: (5, 2560), quantized to int8
88
- embeddings = model.encode(texts, quantization="binary") # Shape: (5, 2560), quantized to binary
89
  ```
90
 
91
- </details>
92
-
93
- <details>
94
- <summary> Using ONNX models </summary>
95
 
96
  ```python
 
 
97
 
98
- import onnxruntime as ort
99
- from transformers import AutoTokenizer
100
- import numpy as np
101
-
102
- tokenizer = AutoTokenizer.from_pretrained("perplexity-ai/pplx-embed-v1-4b", trust_remote_code=True)
103
- session = ort.InferenceSession("onnx/model.onnx")
104
 
 
105
 
 
106
  texts = [
107
  "Scientists explore the universe driven by curiosity.",
108
  "Children learn through curious exploration.",
109
  "Historical discoveries began with curious questions.",
110
- "Animals use curiosity to adapt and survive.",
111
- "Philosophy examines the nature of curiosity.",
112
  ]
113
 
114
- tokenized = tokenizer(
115
- texts,
116
- padding=True,
117
- truncation=True,
118
- return_tensors="np"
119
- )
120
-
121
- onnx_inputs = {
122
- "input_ids": tokenized["input_ids"].astype(np.int64),
123
- "attention_mask": tokenized["attention_mask"].astype(np.int64),
124
- }
125
-
126
- # Run inference
127
- onnx_embeddings = session.run([out.name for out in session.get_outputs()], onnx_inputs)
128
-
129
- # ONNX produces both int8 and binary precision embeddings:
130
- int8_embeddings = onnx_embeddings[2]
131
- binary_embeddings = onnx_embeddings[3]
132
- packed_embeddings = np.packbits(binary_embeddings != -1, axis=-1)
133
- ```
134
-
135
- </details>
136
-
137
- <details>
138
- <summary>Using Text Embeddings Inference (TEI)</summary>
139
-
140
- > [!NOTE]
141
- > Text Embeddings Inference v1.9.2+ is required.
142
-
143
- > [!IMPORTANT]
144
- > Currently, only int8-quantized embeddings are available via TEI. Remember to use cosine similarity with unnormalized int8 embeddings.
145
-
146
- - CPU w/ Candle:
147
-
148
- ```bash
149
- docker run -p 8080:80 ghcr.io/huggingface/text-embeddings-inference:cpu-1.9 --model-id perplexity-ai/pplx-embed-v1-4B --dtype float32
150
- ```
151
-
152
- - CPU w/ ORT (ONNX Runtime):
153
-
154
- ```bash
155
- docker run -p 8080:80 ghcr.io/huggingface/text-embeddings-inference:cpu-1.9 --model-id onnx-community/pplx-embed-v1-4B --dtype float32
156
  ```
157
 
158
- - GPU w/ CUDA:
 
 
159
 
160
- ```bash
161
- docker run --gpus all --shm-size 1g -p 8080:80 ghcr.io/huggingface/text-embeddings-inference:cuda-1.9 --model-id perplexity-ai/pplx-embed-v1-4B --dtype float32
162
- ```
163
 
164
- > If you hit OOM during warmup, lower --max-batch-tokens and --max-client-batch-size. Set --max-batch-tokens to max_sequence_length × batch_size (e.g., 2048 tokens × 8 sequences = 16384).
 
 
 
 
165
 
166
- > Alternatively, when running in CUDA you can use the architecture / compute capability specific
167
- > container instead of the `cuda-1.9`, as that includes the binaries for Turing, Ampere, Hopper and
168
- > Blackwell, so using a dedicated container will be lighter e.g., `ampere-1.9`.
169
 
170
- And then you can send requests to it via cURL to `/embed`:
 
171
 
172
- ```bash
173
- curl http://0.0.0.0:8080/embed \
174
- -H "Content-Type: application/json" \
175
- -d '{
176
- "inputs": [
177
- "Scientists explore the universe driven by curiosity.",
178
- "Children learn through curious exploration.",
179
- "Historical discoveries began with curious questions.",
180
- "Animals use curiosity to adapt and survive.",
181
- "Philosophy examines the nature of curiosity."
182
- ],
183
- "normalize": false
184
- }'
185
- ```
186
 
187
- </details>
 
188
 
189
- ## Technical Details
 
190
 
191
- For comprehensive technical details and evaluation results, see our paper on arXiv: https://arxiv.org/abs/2602.11151.
192
 
 
 
1
  ---
2
  license: mit
3
+ base_model: perplexity-ai/pplx-embed-v1-4b
4
+ library_name: mlx
5
  pipeline_tag: feature-extraction
6
  tags:
7
+ - mlx
8
+ - apple-silicon
9
  - feature-extraction
10
  - sentence-similarity
11
  - mteb
12
+ - perplexity
13
+ - qwen3
 
14
  ---
15
 
16
+ # pplx-embed-v1-4b-mlx
17
 
18
+ MLX conversion of [perplexity-ai/pplx-embed-v1-4b](https://huggingface.co/perplexity-ai/pplx-embed-v1-4b)
19
+ for Apple Silicon.
 
20
 
21
+ This is a standard embedding model. It takes a list of texts and returns one embedding matrix for the batch.
22
 
23
+ ## Important Loading Note
24
 
25
+ This artifact is not loadable through vanilla `mlx_lm.load()` because MLX-LM does not
26
+ natively support Perplexity's custom `bidirectional_pplx_qwen3` model type. The repository
27
+ includes a small `pplx_mlx_convert` loader package for this artifact.
28
 
29
+ ## Source Code
 
30
 
31
+ Conversion and validation code lives in [https://github.com/thehumanworks/pplx-mlx](https://github.com/thehumanworks/pplx-mlx).
32
 
33
+ ## Install
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  ```bash
36
+ pip install mlx mlx-lm transformers huggingface_hub numpy
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  ```
38
 
39
+ ## Usage
 
 
 
40
 
41
  ```python
42
+ import sys
43
+ from huggingface_hub import snapshot_download
44
 
45
+ repo_path = snapshot_download("agentmish/pplx-embed-v1-4b-mlx")
46
+ sys.path.insert(0, repo_path)
 
 
 
 
47
 
48
+ from pplx_mlx_convert import load_embedder
49
 
50
+ embedder = load_embedder(repo_path)
51
  texts = [
52
  "Scientists explore the universe driven by curiosity.",
53
  "Children learn through curious exploration.",
54
  "Historical discoveries began with curious questions.",
 
 
55
  ]
56
 
57
+ embeddings = embedder.encode(texts)
58
+ print(embeddings.shape) # (3, 2560)
59
+ print(embeddings.dtype) # int8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  ```
61
 
62
+ The model natively produces unnormalized int8 embeddings by default. Use cosine similarity
63
+ for comparison. `embedder.encode(..., quantization="none")` returns float32 pooled embeddings,
64
+ and `embedder.encode(..., quantization="binary")` returns binary tanh embeddings.
65
 
66
+ ## Conversion Details
 
 
67
 
68
+ - Source model: `perplexity-ai/pplx-embed-v1-4b`
69
+ - Source revision: see `conversion.json`
70
+ - Converted dtype: `bfloat16`
71
+ - Embedding dimension: `2560`
72
+ - Output root expected by this workspace: `artifacts/mlx/pplx-embed-v1-4b`
73
 
74
+ ## Validation
 
 
75
 
76
+ Local MLX smoke validation passed with finite raw float embeddings and int8 embedding output
77
+ shapes `[[2, 2560]]`.
78
 
79
+ Compared against the original Transformers remote-code float32 model on sample text inputs:
 
 
 
 
 
 
 
 
 
 
 
 
 
80
 
81
+ - cosine similarities: 0.9998998, 0.9998891, 0.9999021
82
+ - int8 delta: max absolute int8 delta 2; mean absolute int8 delta 0.259
83
 
84
+ The MLX artifact is bfloat16 while the reference path used float32, so int8 values are not
85
+ expected to be bit-identical.
86
 
87
+ ## License
88
 
89
+ The source model is MIT licensed. This conversion preserves the MIT license.
config.json CHANGED
@@ -1,79 +1,84 @@
1
  {
2
- "architectures": [
3
- "PPLXQwen3Model"
4
- ],
5
- "attention_bias": false,
6
- "attention_dropout": 0.0,
7
- "auto_map": {
8
- "AutoConfig": "configuration.PPLXQwen3Config",
9
- "AutoModel": "modeling.PPLXQwen3Model"
10
- },
11
- "bos_token_id": 151643,
12
- "dtype": "float32",
13
- "eos_token_id": 151643,
14
- "head_dim": 128,
15
- "hidden_act": "silu",
16
- "hidden_size": 2560,
17
- "initializer_range": 0.02,
18
- "intermediate_size": 9728,
19
- "layer_types": [
20
- "full_attention",
21
- "full_attention",
22
- "full_attention",
23
- "full_attention",
24
- "full_attention",
25
- "full_attention",
26
- "full_attention",
27
- "full_attention",
28
- "full_attention",
29
- "full_attention",
30
- "full_attention",
31
- "full_attention",
32
- "full_attention",
33
- "full_attention",
34
- "full_attention",
35
- "full_attention",
36
- "full_attention",
37
- "full_attention",
38
- "full_attention",
39
- "full_attention",
40
- "full_attention",
41
- "full_attention",
42
- "full_attention",
43
- "full_attention",
44
- "full_attention",
45
- "full_attention",
46
- "full_attention",
47
- "full_attention",
48
- "full_attention",
49
- "full_attention",
50
- "full_attention",
51
- "full_attention",
52
- "full_attention",
53
- "full_attention",
54
- "full_attention",
55
- "full_attention"
56
- ],
57
- "max_position_embeddings": 32768,
58
- "max_window_layers": 36,
59
- "model_type": "qwen3",
60
- "num_attention_heads": 32,
61
- "num_hidden_layers": 36,
62
- "num_key_value_heads": 8,
63
- "rms_norm_eps": 1e-06,
64
- "rope_parameters": {
 
 
 
 
 
 
 
 
 
 
 
65
  "rope_theta": 1000000,
66
- "rope_type": "default"
67
- },
68
- "rope_theta": 1000000,
69
- "sliding_window": null,
70
- "tie_word_embeddings": true,
71
- "transformers_version": "5.0.0.dev0",
72
- "use_cache": false,
73
- "use_sliding_window": false,
74
- "vocab_size": 151936,
75
- "attn_implementation": "sdpa",
76
- "use_bidirectional_attention": true,
77
- "source_model_type": "bidirectional_pplx_qwen3",
78
- "model_file": "mlx_pplx_qwen3.py"
79
- }
 
1
  {
2
+ "architectures": [
3
+ "PPLXQwen3Model"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "attn_implementation": "sdpa",
8
+ "auto_map": {
9
+ "AutoConfig": "configuration.PPLXQwen3Config",
10
+ "AutoModel": "modeling.PPLXQwen3Model"
11
+ },
12
+ "bos_token_id": 151643,
13
+ "dtype": "float32",
14
+ "eos_token_id": 151643,
15
+ "head_dim": 128,
16
+ "hidden_act": "silu",
17
+ "hidden_size": 2560,
18
+ "initializer_range": 0.02,
19
+ "intermediate_size": 9728,
20
+ "layer_types": [
21
+ "full_attention",
22
+ "full_attention",
23
+ "full_attention",
24
+ "full_attention",
25
+ "full_attention",
26
+ "full_attention",
27
+ "full_attention",
28
+ "full_attention",
29
+ "full_attention",
30
+ "full_attention",
31
+ "full_attention",
32
+ "full_attention",
33
+ "full_attention",
34
+ "full_attention",
35
+ "full_attention",
36
+ "full_attention",
37
+ "full_attention",
38
+ "full_attention",
39
+ "full_attention",
40
+ "full_attention",
41
+ "full_attention",
42
+ "full_attention",
43
+ "full_attention",
44
+ "full_attention",
45
+ "full_attention",
46
+ "full_attention",
47
+ "full_attention",
48
+ "full_attention",
49
+ "full_attention",
50
+ "full_attention",
51
+ "full_attention",
52
+ "full_attention",
53
+ "full_attention",
54
+ "full_attention",
55
+ "full_attention",
56
+ "full_attention"
57
+ ],
58
+ "max_position_embeddings": 32768,
59
+ "max_window_layers": 36,
60
+ "mlx_embedding": {
61
+ "source_repo": "perplexity-ai/pplx-embed-v1-4b",
62
+ "source_revision": "2cd0f789519b81eff4c1ea78cbc5d5e4b1224539",
63
+ "converter": "pplx-mlx-convert",
64
+ "dtype": "bfloat16",
65
+ "kind": "independent"
66
+ },
67
+ "model_type": "bidirectional_pplx_qwen3",
68
+ "num_attention_heads": 32,
69
+ "num_hidden_layers": 36,
70
+ "num_key_value_heads": 8,
71
+ "rms_norm_eps": 1e-06,
72
+ "rope_parameters": {
73
+ "rope_theta": 1000000,
74
+ "rope_type": "default"
75
+ },
76
  "rope_theta": 1000000,
77
+ "sliding_window": null,
78
+ "tie_word_embeddings": true,
79
+ "transformers_version": "5.0.0.dev0",
80
+ "use_bidirectional_attention": true,
81
+ "use_cache": false,
82
+ "use_sliding_window": false,
83
+ "vocab_size": 151936
84
+ }
 
 
 
 
 
 
conversion.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "slug": "pplx-embed-v1-4b",
3
+ "source_repo": "perplexity-ai/pplx-embed-v1-4b",
4
+ "source_revision": "2cd0f789519b81eff4c1ea78cbc5d5e4b1224539",
5
+ "dtype": "bfloat16",
6
+ "artifact_type": "mlx-independent-embedding",
7
+ "kind": "independent"
8
+ }
model-00001-of-00002.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:8f384893878795ee18bd9a351ef2f1173b1949a013d6c64afab71b353c564336
3
- size 5321131845
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2dc25292c764c45d20eec2abe4c80b9b879425fae0e42a38032dfa3c028eab43
3
+ size 5321132095
model-00002-of-00002.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:1e32d5e4c3d725972a8f4fc6938668d44c4dd096040483783b8e0efcb2cf5c4e
3
- size 2723847252
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6ab68bd4b99c7fec64fbc5a162238a432a6e442b093745fcd3385ff70219362c
3
+ size 2723847400
pplx_mlx_convert/__init__.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Perplexity model conversion helpers for MLX."""
2
+
3
+ from .embeddings import ContextualEmbedder, IndependentEmbedder, load_embedder
4
+ from .models import MODEL_SPECS, ModelKind, ModelSpec, get_model_spec
5
+
6
+ __all__ = [
7
+ "MODEL_SPECS",
8
+ "ContextualEmbedder",
9
+ "IndependentEmbedder",
10
+ "ModelKind",
11
+ "ModelSpec",
12
+ "get_model_spec",
13
+ "load_embedder",
14
+ ]
pplx_mlx_convert/architecture.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any
2
+
3
+ import mlx.core as mx
4
+ from mlx_lm.models.qwen3 import ModelArgs, Qwen3Model
5
+
6
+ SUPPORTED_MODEL_TYPES = frozenset({"bidirectional_pplx_qwen3", "qwen3"})
7
+
8
+
9
+ def make_bidirectional_padding_mask(attention_mask: mx.array | None) -> mx.array | None:
10
+ """Create a full-attention key padding mask from a tokenizer attention mask."""
11
+ if attention_mask is None:
12
+ return None
13
+
14
+ return attention_mask.astype(mx.bool_)[:, None, None, :]
15
+
16
+
17
+ class PPLXQwen3Model(Qwen3Model):
18
+ """Qwen3 encoder variant used by Perplexity contextual embedding models."""
19
+
20
+ def __call__(
21
+ self,
22
+ inputs: mx.array,
23
+ attention_mask: mx.array | None = None,
24
+ input_embeddings: mx.array | None = None,
25
+ ) -> mx.array:
26
+ if input_embeddings is not None:
27
+ hidden_states = input_embeddings
28
+ else:
29
+ hidden_states = self.embed_tokens(inputs)
30
+
31
+ mask = make_bidirectional_padding_mask(attention_mask)
32
+
33
+ for layer in self.layers:
34
+ hidden_states = layer(hidden_states, mask, None)
35
+
36
+ return self.norm(hidden_states)
37
+
38
+
39
+ def get_pplx_model_classes(config: dict[str, Any]) -> tuple[type[PPLXQwen3Model], type[ModelArgs]]:
40
+ model_type = config.get("model_type")
41
+ if model_type not in SUPPORTED_MODEL_TYPES:
42
+ supported = ", ".join(sorted(SUPPORTED_MODEL_TYPES))
43
+ msg = f"Unsupported Perplexity model type {model_type!r}; expected one of: {supported}."
44
+ raise ValueError(msg)
45
+
46
+ return PPLXQwen3Model, ModelArgs
pplx_mlx_convert/embeddings.py ADDED
@@ -0,0 +1,367 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from collections.abc import Sequence
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import Literal
6
+
7
+ import mlx.core as mx
8
+ import numpy as np
9
+ import numpy.typing as npt
10
+ from huggingface_hub import snapshot_download
11
+ from mlx_lm.utils import load_model
12
+ from transformers import PreTrainedTokenizerBase, Qwen2Tokenizer
13
+
14
+ from .architecture import get_pplx_model_classes
15
+
16
+ Quantization = Literal["int8", "binary", "ubinary", "none"]
17
+
18
+
19
+ def extract_chunk_token_spans(
20
+ *,
21
+ token_ids: npt.NDArray[np.integer],
22
+ attention_mask: npt.NDArray[np.integer],
23
+ sep_token_id: int,
24
+ ) -> list[tuple[int, int]]:
25
+ valid_positions = attention_mask.astype(bool)
26
+ sep_positions = np.flatnonzero((token_ids == sep_token_id) & valid_positions)
27
+ last_valid_pos = int(attention_mask.sum())
28
+
29
+ spans: list[tuple[int, int]] = []
30
+ start_pos = 0
31
+ for sep_pos in sep_positions:
32
+ spans.append((start_pos, int(sep_pos)))
33
+ start_pos = int(sep_pos) + 1
34
+
35
+ spans.append((start_pos, last_valid_pos))
36
+ return spans
37
+
38
+
39
+ def mean_pool(
40
+ token_embeddings: npt.NDArray[np.floating],
41
+ attention_mask: npt.NDArray[np.integer],
42
+ ) -> npt.NDArray[np.float32]:
43
+ if token_embeddings.shape[0] == 0:
44
+ return np.zeros(token_embeddings.shape[-1], dtype=np.float32)
45
+
46
+ mask = attention_mask.astype(np.float32)[:, None]
47
+ denominator = np.clip(mask.sum(axis=0), a_min=1e-9, a_max=None)
48
+ return ((token_embeddings * mask).sum(axis=0) / denominator).astype(np.float32)
49
+
50
+
51
+ def quantize_int8_tanh(values: npt.NDArray[np.floating]) -> npt.NDArray[np.int8]:
52
+ rounded = np.round(np.tanh(values) * 127)
53
+ return np.clip(rounded, -128, 127).astype(np.int8)
54
+
55
+
56
+ def quantize_binary_tanh(values: npt.NDArray[np.floating]) -> npt.NDArray[np.float32]:
57
+ return np.where(values >= 0, 1.0, -1.0).astype(np.float32)
58
+
59
+
60
+ def quantize_ubinary_tanh(values: npt.NDArray[np.floating]) -> npt.NDArray[np.uint8]:
61
+ return np.packbits(values >= 0, axis=-1)
62
+
63
+
64
+ @dataclass(frozen=True, slots=True)
65
+ class SmokeValidationResult:
66
+ documents: int
67
+ chunk_counts: tuple[int, ...]
68
+ shapes: tuple[tuple[int, ...], ...]
69
+ dtypes: tuple[str, ...]
70
+ raw_float_finite: bool
71
+
72
+
73
+ class ContextualEmbedder:
74
+ def __init__(self, model_path: Path | str, *, revision: str | None = None) -> None:
75
+ self.model_path = _resolve_model_path(model_path, revision=revision)
76
+ self.model, self.config = load_model(
77
+ self.model_path,
78
+ lazy=False,
79
+ get_model_classes=get_pplx_model_classes,
80
+ )
81
+ self.tokenizer: PreTrainedTokenizerBase = Qwen2Tokenizer.from_pretrained(self.model_path)
82
+
83
+ if self.tokenizer.sep_token_id is None:
84
+ raise ValueError("Tokenizer must define sep_token_id for contextual chunk extraction.")
85
+
86
+ def encode(
87
+ self,
88
+ documents: Sequence[Sequence[str]],
89
+ *,
90
+ batch_size: int = 4,
91
+ quantization: Quantization = "int8",
92
+ normalize_embeddings: bool = False,
93
+ dimensions: int | None = None,
94
+ ) -> list[npt.NDArray[np.generic]]:
95
+ _validate_documents(documents)
96
+ if quantization not in {"int8", "binary", "none"}:
97
+ msg = f"Unsupported quantization {quantization!r}."
98
+ raise ValueError(msg)
99
+
100
+ encoded: list[npt.NDArray[np.generic]] = []
101
+ for start in range(0, len(documents), batch_size):
102
+ batch_docs = documents[start : start + batch_size]
103
+ encoded.extend(
104
+ self._encode_batch(
105
+ batch_docs,
106
+ quantization=quantization,
107
+ normalize_embeddings=normalize_embeddings,
108
+ dimensions=dimensions,
109
+ )
110
+ )
111
+
112
+ return encoded
113
+
114
+ def smoke_validate(self) -> SmokeValidationResult:
115
+ sample_documents = [
116
+ [
117
+ "Curiosity begins in childhood with questions about the world.",
118
+ "Scientific breakthroughs often start with a curious question.",
119
+ ],
120
+ [
121
+ "The Mars rover searches for signs of ancient life.",
122
+ ],
123
+ ]
124
+ raw_embeddings = self.encode(sample_documents, quantization="none")
125
+ raw_float_finite = all(bool(np.isfinite(embedding).all()) for embedding in raw_embeddings)
126
+ embeddings = self.encode(sample_documents, quantization="int8")
127
+ return SmokeValidationResult(
128
+ documents=len(embeddings),
129
+ chunk_counts=tuple(int(embedding.shape[0]) for embedding in embeddings),
130
+ shapes=tuple(tuple(int(dim) for dim in embedding.shape) for embedding in embeddings),
131
+ dtypes=tuple(str(embedding.dtype) for embedding in embeddings),
132
+ raw_float_finite=raw_float_finite,
133
+ )
134
+
135
+ def _encode_batch(
136
+ self,
137
+ documents: Sequence[Sequence[str]],
138
+ *,
139
+ quantization: Quantization,
140
+ normalize_embeddings: bool,
141
+ dimensions: int | None,
142
+ ) -> list[npt.NDArray[np.generic]]:
143
+ sep_token = self.tokenizer.sep_token
144
+ if sep_token is None:
145
+ raise ValueError("Tokenizer must define sep_token for contextual chunk joining.")
146
+
147
+ doc_strings = [sep_token.join(chunks) for chunks in documents]
148
+ inputs = self.tokenizer(
149
+ doc_strings,
150
+ padding=True,
151
+ truncation=True,
152
+ return_tensors="np",
153
+ )
154
+ input_ids = np.asarray(inputs["input_ids"])
155
+ attention_mask = np.asarray(inputs["attention_mask"])
156
+
157
+ token_embeddings = self.model(
158
+ mx.array(input_ids),
159
+ attention_mask=mx.array(attention_mask),
160
+ )
161
+ token_embeddings = token_embeddings.astype(mx.float32)
162
+ mx.eval(token_embeddings)
163
+ token_embeddings_np = np.asarray(token_embeddings)
164
+
165
+ batch_embeddings: list[npt.NDArray[np.generic]] = []
166
+ for batch_index in range(len(documents)):
167
+ spans = extract_chunk_token_spans(
168
+ token_ids=input_ids[batch_index],
169
+ attention_mask=attention_mask[batch_index],
170
+ sep_token_id=int(self.tokenizer.sep_token_id),
171
+ )
172
+ chunk_embeddings = [
173
+ mean_pool(
174
+ token_embeddings_np[batch_index, span_start:span_end],
175
+ attention_mask[batch_index, span_start:span_end],
176
+ )
177
+ for span_start, span_end in spans
178
+ ]
179
+ stacked = np.stack(chunk_embeddings, axis=0)
180
+ if dimensions is not None:
181
+ stacked = stacked[..., :dimensions]
182
+ batch_embeddings.append(
183
+ _finalize_embeddings(
184
+ stacked,
185
+ quantization=quantization,
186
+ normalize_embeddings=normalize_embeddings,
187
+ )
188
+ )
189
+
190
+ return batch_embeddings
191
+
192
+
193
+ class IndependentEmbedder:
194
+ def __init__(self, model_path: Path | str, *, revision: str | None = None) -> None:
195
+ self.model_path = _resolve_model_path(model_path, revision=revision)
196
+ self.model, self.config = load_model(
197
+ self.model_path,
198
+ lazy=False,
199
+ get_model_classes=get_pplx_model_classes,
200
+ )
201
+ self.tokenizer: PreTrainedTokenizerBase = Qwen2Tokenizer.from_pretrained(self.model_path)
202
+
203
+ def encode(
204
+ self,
205
+ texts: Sequence[str],
206
+ *,
207
+ batch_size: int = 4,
208
+ quantization: Quantization = "int8",
209
+ normalize_embeddings: bool = False,
210
+ dimensions: int | None = None,
211
+ ) -> npt.NDArray[np.generic]:
212
+ _validate_texts(texts)
213
+ if quantization not in {"int8", "binary", "ubinary", "none"}:
214
+ msg = f"Unsupported quantization {quantization!r}."
215
+ raise ValueError(msg)
216
+
217
+ encoded_batches: list[npt.NDArray[np.generic]] = []
218
+ for start in range(0, len(texts), batch_size):
219
+ encoded_batches.append(
220
+ self._encode_batch(
221
+ texts[start : start + batch_size],
222
+ quantization=quantization,
223
+ normalize_embeddings=normalize_embeddings,
224
+ dimensions=dimensions,
225
+ )
226
+ )
227
+ return np.concatenate(encoded_batches, axis=0)
228
+
229
+ def smoke_validate(self) -> SmokeValidationResult:
230
+ sample_texts = [
231
+ "Scientists explore the universe driven by curiosity.",
232
+ "Children learn through curious exploration.",
233
+ ]
234
+ raw_embeddings = self.encode(sample_texts, quantization="none")
235
+ raw_float_finite = bool(np.isfinite(raw_embeddings).all())
236
+ embeddings = self.encode(sample_texts, quantization="int8")
237
+ return SmokeValidationResult(
238
+ documents=int(embeddings.shape[0]),
239
+ chunk_counts=(),
240
+ shapes=(tuple(int(dim) for dim in embeddings.shape),),
241
+ dtypes=(str(embeddings.dtype),),
242
+ raw_float_finite=raw_float_finite,
243
+ )
244
+
245
+ def _encode_batch(
246
+ self,
247
+ texts: Sequence[str],
248
+ *,
249
+ quantization: Quantization,
250
+ normalize_embeddings: bool,
251
+ dimensions: int | None,
252
+ ) -> npt.NDArray[np.generic]:
253
+ inputs = self.tokenizer(
254
+ list(texts),
255
+ padding=True,
256
+ truncation=True,
257
+ return_tensors="np",
258
+ )
259
+ input_ids = np.asarray(inputs["input_ids"])
260
+ attention_mask = np.asarray(inputs["attention_mask"])
261
+ token_embeddings = self.model(
262
+ mx.array(input_ids),
263
+ attention_mask=mx.array(attention_mask),
264
+ )
265
+ token_embeddings = token_embeddings.astype(mx.float32)
266
+ mx.eval(token_embeddings)
267
+ token_embeddings_np = np.asarray(token_embeddings)
268
+ pooled = np.stack(
269
+ [
270
+ mean_pool(token_embeddings_np[row_index], attention_mask[row_index])
271
+ for row_index in range(len(texts))
272
+ ],
273
+ axis=0,
274
+ )
275
+ if dimensions is not None:
276
+ pooled = pooled[..., :dimensions]
277
+ return _finalize_embeddings(
278
+ pooled,
279
+ quantization=quantization,
280
+ normalize_embeddings=normalize_embeddings,
281
+ )
282
+
283
+
284
+ Embedder = ContextualEmbedder | IndependentEmbedder
285
+
286
+
287
+ def load_embedder(model_path: Path | str, *, revision: str | None = None) -> Embedder:
288
+ resolved_path = _resolve_model_path(model_path, revision=revision)
289
+ config = json.loads((resolved_path / "config.json").read_text())
290
+ if _is_independent_embedding_config(config):
291
+ return IndependentEmbedder(resolved_path)
292
+ return ContextualEmbedder(resolved_path)
293
+
294
+
295
+ def _is_independent_embedding_config(config: dict[str, object]) -> bool:
296
+ metadata = config.get("mlx_embedding")
297
+ if isinstance(metadata, dict) and metadata.get("kind") == "independent":
298
+ return True
299
+
300
+ auto_map = config.get("auto_map")
301
+ if isinstance(auto_map, dict):
302
+ auto_model = auto_map.get("AutoModel")
303
+ return auto_model == "modeling.PPLXQwen3Model"
304
+
305
+ return False
306
+
307
+
308
+ def _resolve_model_path(model_path: Path | str, *, revision: str | None = None) -> Path:
309
+ path = Path(model_path).expanduser()
310
+ if path.exists():
311
+ return path
312
+
313
+ return Path(
314
+ snapshot_download(
315
+ str(model_path),
316
+ revision=revision,
317
+ allow_patterns=[
318
+ "*.json",
319
+ "*.safetensors",
320
+ "*.py",
321
+ "*.txt",
322
+ "pplx_mlx_convert/*.py",
323
+ ],
324
+ )
325
+ )
326
+
327
+
328
+ def _validate_documents(documents: Sequence[Sequence[str]]) -> None:
329
+ if not documents:
330
+ raise ValueError("documents must contain at least one document.")
331
+ for document in documents:
332
+ if not document:
333
+ raise ValueError("Each document must contain at least one chunk.")
334
+ for chunk in document:
335
+ if not isinstance(chunk, str) or not chunk:
336
+ raise ValueError("Each chunk must be a non-empty string.")
337
+
338
+
339
+ def _validate_texts(texts: Sequence[str]) -> None:
340
+ if not texts:
341
+ raise ValueError("texts must contain at least one text.")
342
+ for text in texts:
343
+ if not isinstance(text, str) or not text:
344
+ raise ValueError("Each text must be a non-empty string.")
345
+
346
+
347
+ def _finalize_embeddings(
348
+ embeddings: npt.NDArray[np.floating],
349
+ *,
350
+ quantization: Quantization,
351
+ normalize_embeddings: bool,
352
+ ) -> npt.NDArray[np.generic]:
353
+ if quantization == "int8":
354
+ finalized: npt.NDArray[np.generic] = quantize_int8_tanh(embeddings)
355
+ elif quantization == "binary":
356
+ finalized = quantize_binary_tanh(embeddings)
357
+ elif quantization == "ubinary":
358
+ finalized = quantize_ubinary_tanh(embeddings)
359
+ else:
360
+ finalized = embeddings.astype(np.float32)
361
+
362
+ if normalize_embeddings:
363
+ float_embeddings = finalized.astype(np.float32)
364
+ norms = np.linalg.norm(float_embeddings, axis=-1, keepdims=True)
365
+ finalized = float_embeddings / np.clip(norms, a_min=1e-12, a_max=None)
366
+
367
+ return finalized
pplx_mlx_convert/models.py ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dataclasses import dataclass
2
+ from typing import Final, Literal
3
+
4
+ ModelKind = Literal["independent", "contextual"]
5
+
6
+
7
+ @dataclass(frozen=True, slots=True)
8
+ class ModelSpec:
9
+ """Metadata for one source model that this workspace will convert."""
10
+
11
+ slug: str
12
+ huggingface_repo: str
13
+ parameter_count: str
14
+ embedding_dimension: int
15
+ kind: ModelKind
16
+
17
+ @property
18
+ def huggingface_url(self) -> str:
19
+ return f"https://huggingface.co/{self.huggingface_repo}"
20
+
21
+ @property
22
+ def default_output_dir(self) -> str:
23
+ return f"artifacts/mlx/{self.slug}"
24
+
25
+
26
+ MODEL_SPECS: Final[tuple[ModelSpec, ...]] = (
27
+ ModelSpec(
28
+ slug="pplx-embed-v1-4b",
29
+ huggingface_repo="perplexity-ai/pplx-embed-v1-4b",
30
+ parameter_count="4b",
31
+ embedding_dimension=2560,
32
+ kind="independent",
33
+ ),
34
+ ModelSpec(
35
+ slug="pplx-embed-v1-0.6b",
36
+ huggingface_repo="perplexity-ai/pplx-embed-v1-0.6b",
37
+ parameter_count="0.6b",
38
+ embedding_dimension=1024,
39
+ kind="independent",
40
+ ),
41
+ ModelSpec(
42
+ slug="pplx-embed-context-v1-4b",
43
+ huggingface_repo="perplexity-ai/pplx-embed-context-v1-4b",
44
+ parameter_count="4b",
45
+ embedding_dimension=2560,
46
+ kind="contextual",
47
+ ),
48
+ ModelSpec(
49
+ slug="pplx-embed-context-v1-0.6b",
50
+ huggingface_repo="perplexity-ai/pplx-embed-context-v1-0.6b",
51
+ parameter_count="0.6b",
52
+ embedding_dimension=1024,
53
+ kind="contextual",
54
+ ),
55
+ )
56
+
57
+ MODEL_SPECS_BY_SLUG: Final[dict[str, ModelSpec]] = {spec.slug: spec for spec in MODEL_SPECS}
58
+
59
+
60
+ def get_model_spec(slug: str) -> ModelSpec:
61
+ try:
62
+ return MODEL_SPECS_BY_SLUG[slug]
63
+ except KeyError as exc:
64
+ available = ", ".join(MODEL_SPECS_BY_SLUG)
65
+ msg = f"Unknown model slug {slug!r}. Available slugs: {available}."
66
+ raise ValueError(msg) from exc
tokenizer.json CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:db3914d7cce5125c42bbbf875116cef2697023ba144bda8264ad1368595dde2b
3
- size 11423107
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:32687b48a8d7da95d23b32a8f24677795496605001bddee04016bb78ebcc2e67
3
+ size 11422833
tokenizer_config.json CHANGED
@@ -1,244 +1,11 @@
1
  {
2
- "add_bos_token": false,
3
  "add_prefix_space": false,
4
- "added_tokens_decoder": {
5
- "151642": {
6
- "content": "â½Ĺ",
7
- "lstrip": false,
8
- "normalized": false,
9
- "rstrip": false,
10
- "single_word": false,
11
- "special": true
12
- },
13
- "151643": {
14
- "content": "<|endoftext|>",
15
- "lstrip": false,
16
- "normalized": false,
17
- "rstrip": false,
18
- "single_word": false,
19
- "special": true
20
- },
21
- "151644": {
22
- "content": "<|im_start|>",
23
- "lstrip": false,
24
- "normalized": false,
25
- "rstrip": false,
26
- "single_word": false,
27
- "special": true
28
- },
29
- "151645": {
30
- "content": "<|im_end|>",
31
- "lstrip": false,
32
- "normalized": false,
33
- "rstrip": false,
34
- "single_word": false,
35
- "special": true
36
- },
37
- "151646": {
38
- "content": "<|object_ref_start|>",
39
- "lstrip": false,
40
- "normalized": false,
41
- "rstrip": false,
42
- "single_word": false,
43
- "special": true
44
- },
45
- "151647": {
46
- "content": "<|object_ref_end|>",
47
- "lstrip": false,
48
- "normalized": false,
49
- "rstrip": false,
50
- "single_word": false,
51
- "special": true
52
- },
53
- "151648": {
54
- "content": "<|box_start|>",
55
- "lstrip": false,
56
- "normalized": false,
57
- "rstrip": false,
58
- "single_word": false,
59
- "special": true
60
- },
61
- "151649": {
62
- "content": "<|box_end|>",
63
- "lstrip": false,
64
- "normalized": false,
65
- "rstrip": false,
66
- "single_word": false,
67
- "special": true
68
- },
69
- "151650": {
70
- "content": "<|quad_start|>",
71
- "lstrip": false,
72
- "normalized": false,
73
- "rstrip": false,
74
- "single_word": false,
75
- "special": true
76
- },
77
- "151651": {
78
- "content": "<|quad_end|>",
79
- "lstrip": false,
80
- "normalized": false,
81
- "rstrip": false,
82
- "single_word": false,
83
- "special": true
84
- },
85
- "151652": {
86
- "content": "<|vision_start|>",
87
- "lstrip": false,
88
- "normalized": false,
89
- "rstrip": false,
90
- "single_word": false,
91
- "special": true
92
- },
93
- "151653": {
94
- "content": "<|vision_end|>",
95
- "lstrip": false,
96
- "normalized": false,
97
- "rstrip": false,
98
- "single_word": false,
99
- "special": true
100
- },
101
- "151654": {
102
- "content": "<|vision_pad|>",
103
- "lstrip": false,
104
- "normalized": false,
105
- "rstrip": false,
106
- "single_word": false,
107
- "special": true
108
- },
109
- "151655": {
110
- "content": "<|image_pad|>",
111
- "lstrip": false,
112
- "normalized": false,
113
- "rstrip": false,
114
- "single_word": false,
115
- "special": true
116
- },
117
- "151656": {
118
- "content": "<|video_pad|>",
119
- "lstrip": false,
120
- "normalized": false,
121
- "rstrip": false,
122
- "single_word": false,
123
- "special": true
124
- },
125
- "151657": {
126
- "content": "<tool_call>",
127
- "lstrip": false,
128
- "normalized": false,
129
- "rstrip": false,
130
- "single_word": false,
131
- "special": false
132
- },
133
- "151658": {
134
- "content": "</tool_call>",
135
- "lstrip": false,
136
- "normalized": false,
137
- "rstrip": false,
138
- "single_word": false,
139
- "special": false
140
- },
141
- "151659": {
142
- "content": "<|fim_prefix|>",
143
- "lstrip": false,
144
- "normalized": false,
145
- "rstrip": false,
146
- "single_word": false,
147
- "special": false
148
- },
149
- "151660": {
150
- "content": "<|fim_middle|>",
151
- "lstrip": false,
152
- "normalized": false,
153
- "rstrip": false,
154
- "single_word": false,
155
- "special": false
156
- },
157
- "151661": {
158
- "content": "<|fim_suffix|>",
159
- "lstrip": false,
160
- "normalized": false,
161
- "rstrip": false,
162
- "single_word": false,
163
- "special": false
164
- },
165
- "151662": {
166
- "content": "<|fim_pad|>",
167
- "lstrip": false,
168
- "normalized": false,
169
- "rstrip": false,
170
- "single_word": false,
171
- "special": false
172
- },
173
- "151663": {
174
- "content": "<|repo_name|>",
175
- "lstrip": false,
176
- "normalized": false,
177
- "rstrip": false,
178
- "single_word": false,
179
- "special": false
180
- },
181
- "151664": {
182
- "content": "<|file_sep|>",
183
- "lstrip": false,
184
- "normalized": false,
185
- "rstrip": false,
186
- "single_word": false,
187
- "special": false
188
- },
189
- "151665": {
190
- "content": "<tool_response>",
191
- "lstrip": false,
192
- "normalized": false,
193
- "rstrip": false,
194
- "single_word": false,
195
- "special": false
196
- },
197
- "151666": {
198
- "content": "</tool_response>",
199
- "lstrip": false,
200
- "normalized": false,
201
- "rstrip": false,
202
- "single_word": false,
203
- "special": false
204
- },
205
- "151667": {
206
- "content": "<think>",
207
- "lstrip": false,
208
- "normalized": false,
209
- "rstrip": false,
210
- "single_word": false,
211
- "special": false
212
- },
213
- "151668": {
214
- "content": "</think>",
215
- "lstrip": false,
216
- "normalized": false,
217
- "rstrip": false,
218
- "single_word": false,
219
- "special": false
220
- }
221
- },
222
- "additional_special_tokens": [
223
- "<|im_start|>",
224
- "<|im_end|>",
225
- "<|object_ref_start|>",
226
- "<|object_ref_end|>",
227
- "<|box_start|>",
228
- "<|box_end|>",
229
- "<|quad_start|>",
230
- "<|quad_end|>",
231
- "<|vision_start|>",
232
- "<|vision_end|>",
233
- "<|vision_pad|>",
234
- "<|image_pad|>",
235
- "<|video_pad|>"
236
- ],
237
  "bos_token": null,
238
  "clean_up_tokenization_spaces": false,
239
  "eos_token": "<|endoftext|>",
240
  "errors": "replace",
241
- "extra_special_tokens": {},
242
  "mask_token": "â½Ĺ",
243
  "model_max_length": 131072,
244
  "pad_token": "<|endoftext|>",
 
1
  {
 
2
  "add_prefix_space": false,
3
+ "backend": "tokenizers",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  "bos_token": null,
5
  "clean_up_tokenization_spaces": false,
6
  "eos_token": "<|endoftext|>",
7
  "errors": "replace",
8
+ "is_local": true,
9
  "mask_token": "â½Ĺ",
10
  "model_max_length": 131072,
11
  "pad_token": "<|endoftext|>",