Sentence Similarity
MLX
Safetensors
ministral3
apple-silicon
embeddings
text-embeddings
feature-extraction
retrieval
semantic-search
rag
mteb
nemotron
quantized
4-bit precision
information-retrieval
dense-retrieval
vector-search
matryoshka
arxiv:2502.13595
Eval Results (legacy)
Instructions to use shadowrock-io/Nemotron-3-Embed-8B-Community-MLX-4bit with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use shadowrock-io/Nemotron-3-Embed-8B-Community-MLX-4bit with MLX:
# Download the model from the Hub pip install huggingface_hub[hf_xet] hf download shadowrock-io/Nemotron-3-Embed-8B-Community-MLX-4bit --local-dir Nemotron-3-Embed-8B-Community-MLX-4bit
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
add scripts/convert_8b.py
Browse files- scripts/convert_8b.py +108 -0
scripts/convert_8b.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""P3b — convert nvidia/Nemotron-3-Embed-8B-BF16 to a quantized MLX checkpoint.
|
| 2 |
+
|
| 3 |
+
The NVIDIA HF checkpoint's weight names (embed_tokens/layers.N/norm, no "model."
|
| 4 |
+
prefix) and config keys match the proven bidirectional MLX implementation bundled
|
| 5 |
+
with the mlx-community 1B port exactly (see runs/mlx_contract_1b_bf16.json: the
|
| 6 |
+
implementation is 0.9999-exact vs Transformers). Conversion is therefore:
|
| 7 |
+
mmap-load shards -> load into NemotronEmbedModel -> nn.quantize -> save.
|
| 8 |
+
|
| 9 |
+
One recipe per process invocation (24GB unified memory: 16GB bf16 mmap + the
|
| 10 |
+
quantized materialization must not coexist across recipes).
|
| 11 |
+
|
| 12 |
+
Usage:
|
| 13 |
+
source ~/.venvs/nq-mlx/bin/activate
|
| 14 |
+
python mlx/convert_8b.py --bits 4 --group-size 32 \
|
| 15 |
+
--src ~/.cache/nq-models/nv-8b-bf16 --out ~/.cache/nq-models/mlx-8b-4bit-gs32
|
| 16 |
+
python mlx/convert_8b.py --bits 4 --group-size 32 --mixed \
|
| 17 |
+
--out ~/.cache/nq-models/mlx-8b-mixed-gs32 # embeddings kept 8-bit
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
from __future__ import annotations
|
| 21 |
+
|
| 22 |
+
import argparse
|
| 23 |
+
import importlib.util
|
| 24 |
+
import json
|
| 25 |
+
import shutil
|
| 26 |
+
import sys
|
| 27 |
+
from pathlib import Path
|
| 28 |
+
|
| 29 |
+
MLX_1B_DIR = Path.home() / ".cache/nq-models/mlx-1b-4bit" # source of the impl
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def load_impl():
|
| 33 |
+
spec = importlib.util.spec_from_file_location(
|
| 34 |
+
"nemotron3_embed_mlx", MLX_1B_DIR / "nemotron3_embed_mlx.py"
|
| 35 |
+
)
|
| 36 |
+
mod = importlib.util.module_from_spec(spec)
|
| 37 |
+
spec.loader.exec_module(mod)
|
| 38 |
+
return mod
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def main():
|
| 42 |
+
ap = argparse.ArgumentParser()
|
| 43 |
+
ap.add_argument("--src", default=str(Path.home() / ".cache/nq-models/nv-8b-bf16"))
|
| 44 |
+
ap.add_argument("--out", required=True)
|
| 45 |
+
ap.add_argument("--bits", type=int, required=True)
|
| 46 |
+
ap.add_argument("--group-size", type=int, required=True)
|
| 47 |
+
ap.add_argument("--mixed", action="store_true",
|
| 48 |
+
help="keep embed_tokens at 8-bit (body at --bits)")
|
| 49 |
+
args = ap.parse_args()
|
| 50 |
+
|
| 51 |
+
import mlx.core as mx
|
| 52 |
+
import mlx.nn as nn
|
| 53 |
+
from mlx_lm.models.ministral3 import ModelArgs
|
| 54 |
+
|
| 55 |
+
src, out = Path(args.src).expanduser(), Path(args.out).expanduser()
|
| 56 |
+
out.mkdir(parents=True, exist_ok=True)
|
| 57 |
+
mod = load_impl()
|
| 58 |
+
|
| 59 |
+
cfg = json.loads((src / "config.json").read_text())
|
| 60 |
+
model = mod.NemotronEmbedModel(ModelArgs.from_dict(cfg))
|
| 61 |
+
|
| 62 |
+
weights = {}
|
| 63 |
+
for shard in sorted(src.glob("model-*.safetensors")):
|
| 64 |
+
weights.update(mx.load(str(shard))) # mmap — not materialized yet
|
| 65 |
+
print(f"loaded {len(weights)} tensors from {len(list(src.glob('model-*.safetensors')))} shards")
|
| 66 |
+
model.load_weights(list(weights.items()))
|
| 67 |
+
|
| 68 |
+
if args.mixed:
|
| 69 |
+
def class_predicate(path, module):
|
| 70 |
+
if not hasattr(module, "to_quantized"):
|
| 71 |
+
return False
|
| 72 |
+
if "embed_tokens" in path:
|
| 73 |
+
return {"group_size": args.group_size, "bits": 8}
|
| 74 |
+
return True
|
| 75 |
+
|
| 76 |
+
nn.quantize(model, group_size=args.group_size, bits=args.bits,
|
| 77 |
+
class_predicate=class_predicate)
|
| 78 |
+
quant_cfg = {"group_size": args.group_size, "bits": args.bits,
|
| 79 |
+
"mode": "affine",
|
| 80 |
+
"overrides": {"embed_tokens": {"bits": 8}}}
|
| 81 |
+
else:
|
| 82 |
+
nn.quantize(model, group_size=args.group_size, bits=args.bits)
|
| 83 |
+
quant_cfg = {"group_size": args.group_size, "bits": args.bits,
|
| 84 |
+
"mode": "affine"}
|
| 85 |
+
|
| 86 |
+
mx.eval(model.parameters())
|
| 87 |
+
|
| 88 |
+
from mlx.utils import tree_flatten
|
| 89 |
+
|
| 90 |
+
flat = dict(tree_flatten(model.parameters()))
|
| 91 |
+
mx.save_safetensors(str(out / "model.safetensors"), flat,
|
| 92 |
+
metadata={"format": "mlx"})
|
| 93 |
+
|
| 94 |
+
cfg_out = dict(cfg)
|
| 95 |
+
cfg_out["quantization"] = quant_cfg
|
| 96 |
+
cfg_out["quantization_config"] = quant_cfg
|
| 97 |
+
(out / "config.json").write_text(json.dumps(cfg_out, indent=2))
|
| 98 |
+
for f in ("tokenizer.json", "tokenizer_config.json"):
|
| 99 |
+
shutil.copy(src / f, out / f)
|
| 100 |
+
shutil.copy(MLX_1B_DIR / "nemotron3_embed_mlx.py", out / "nemotron3_embed_mlx.py")
|
| 101 |
+
|
| 102 |
+
size_gb = (out / "model.safetensors").stat().st_size / 1e9
|
| 103 |
+
print(json.dumps({"out": str(out), "quantization": quant_cfg,
|
| 104 |
+
"n_tensors": len(flat), "size_gb": round(size_gb, 2)}))
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
if __name__ == "__main__":
|
| 108 |
+
main()
|