Instructions to use Ilides/cortex-0.3-0.02b-mlx with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- MLX
How to use Ilides/cortex-0.3-0.02b-mlx with MLX:
# Make sure mlx-lm is installed # pip install --upgrade mlx-lm # Generate text with mlx-lm from mlx_lm import load, generate model, tokenizer = load("Ilides/cortex-0.3-0.02b-mlx") prompt = "Write a story about Einstein" messages = [{"role": "user", "content": prompt}] prompt = tokenizer.apply_chat_template( messages, add_generation_prompt=True ) text = generate(model, tokenizer, prompt=prompt, verbose=True) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- MLX LM
How to use Ilides/cortex-0.3-0.02b-mlx with MLX LM:
Generate or start a chat session
# Install MLX LM uv tool install mlx-lm # Interactive chat REPL mlx_lm.chat --model "Ilides/cortex-0.3-0.02b-mlx"
Run an OpenAI-compatible server
# Install MLX LM uv tool install mlx-lm # Start the server mlx_lm.server --model "Ilides/cortex-0.3-0.02b-mlx" # Calling the OpenAI-compatible server with curl curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Ilides/cortex-0.3-0.02b-mlx", "messages": [ {"role": "user", "content": "Hello"} ] }' - Atomic Chat
Upload folder using huggingface_hub
Browse files- README.md +43 -0
- config.json +25 -0
- generate_mlx.py +104 -0
- merges.txt +0 -0
- model.py +96 -0
- special_tokens_map.json +14 -0
- tokenization_cortex.py +129 -0
- tokenizer_config.json +27 -0
- vocab.json +0 -0
- weights.safetensors +3 -0
README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
library_name: mlx
|
| 3 |
+
pipeline_tag: text-generation
|
| 4 |
+
license: apache-2.0
|
| 5 |
+
base_model: Ilides/cortex-0.3-0.02b
|
| 6 |
+
tags:
|
| 7 |
+
- cortex
|
| 8 |
+
- mlx
|
| 9 |
+
- apple-silicon
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# cortex-0.3-0.02b · MLX (Apple Silicon)
|
| 13 |
+
|
| 14 |
+
Versión **MLX** (Metal) de [Ilides/cortex-0.3-0.02b](https://huggingface.co/Ilides/cortex-0.3-0.02b), el GPT conversacional de 20.6 M parámetros entrenado desde cero en NumPy puro. Ejecuta nativamente en Apple Silicon sin PyTorch.
|
| 15 |
+
|
| 16 |
+
## Requisitos
|
| 17 |
+
|
| 18 |
+
```bash
|
| 19 |
+
pip install mlx safetensors transformers regex
|
| 20 |
+
```
|
| 21 |
+
|
| 22 |
+
## Uso
|
| 23 |
+
|
| 24 |
+
```bash
|
| 25 |
+
# batería de prompts de prueba (por defecto)
|
| 26 |
+
python generate_mlx.py
|
| 27 |
+
|
| 28 |
+
# una pregunta
|
| 29 |
+
python generate_mlx.py "¿Qué eres?" --max-new 128
|
| 30 |
+
|
| 31 |
+
# chat interactivo (escribe "salir" para terminar)
|
| 32 |
+
python generate_mlx.py --interactive --temperature 0.8
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Bundle autocontenido: `weights.safetensors` (45 tensores) + `model.py` (arquitectura) + `generate_mlx.py` (CLI) + tokenizer. No depende de `mlx-lm` ni de código externo del Hub.
|
| 36 |
+
|
| 37 |
+
## Verificación
|
| 38 |
+
|
| 39 |
+
Paridad de logits MLX vs NumPy (implementación de referencia): **max|Δ| = 3.65e-05** (< 1e-4). Tokens EOS efectivos: `{0, 6}`.
|
| 40 |
+
|
| 41 |
+
## Arquitectura
|
| 42 |
+
|
| 43 |
+
`cortex`: embeddings de posición aprendidos (sin RoPE), RMSNorm + atención multi-cabeza + SwiGLU, embeddings atados (`tie_word_embeddings`). Contexto 512, 6 capas, d_model 192, vocab 16384 (BPE byte-level).
|
config.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"_name_or_path": "cortex-0.3-0.02b",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"CortexForCausalLM"
|
| 5 |
+
],
|
| 6 |
+
"auto_map": {
|
| 7 |
+
"AutoConfig": "configuration_cortex.CortexConfig",
|
| 8 |
+
"AutoModelForCausalLM": "modeling_cortex.CortexForCausalLM",
|
| 9 |
+
"AutoTokenizer": "tokenization_cortex.CortexTokenizer"
|
| 10 |
+
},
|
| 11 |
+
"bos_token_id": 0,
|
| 12 |
+
"dtype": "float32",
|
| 13 |
+
"eos_token_id": 0,
|
| 14 |
+
"hidden_size": 384,
|
| 15 |
+
"initializer_std": 0.02,
|
| 16 |
+
"intermediate_size": 1536,
|
| 17 |
+
"max_position_embeddings": 512,
|
| 18 |
+
"model_type": "cortex",
|
| 19 |
+
"num_attention_heads": 8,
|
| 20 |
+
"num_hidden_layers": 6,
|
| 21 |
+
"pad_token_id": 1,
|
| 22 |
+
"rms_norm_eps": 1e-05,
|
| 23 |
+
"transformers_version": "4.57.6",
|
| 24 |
+
"vocab_size": 16384
|
| 25 |
+
}
|
generate_mlx.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Chat con Cortex-1 en MLX (Apple Silicon, Metal).
|
| 3 |
+
|
| 4 |
+
Uso:
|
| 5 |
+
python generate_mlx.py # batería de prueba
|
| 6 |
+
python generate_mlx.py "tu pregunta" # one-shot
|
| 7 |
+
python generate_mlx.py --interactive # REPL
|
| 8 |
+
Requisitos: pip install mlx transformers regex safetensors
|
| 9 |
+
"""
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import os
|
| 14 |
+
import sys
|
| 15 |
+
|
| 16 |
+
import mlx.core as mx
|
| 17 |
+
import numpy as np
|
| 18 |
+
|
| 19 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 20 |
+
sys.path.insert(0, HERE)
|
| 21 |
+
|
| 22 |
+
from model import Model
|
| 23 |
+
from tokenization_cortex import CortexTokenizer
|
| 24 |
+
|
| 25 |
+
SYSTEM = "Eres Cortex, un asistente en español. Habla claro y conciso."
|
| 26 |
+
BATERIA = [
|
| 27 |
+
"Hola, ¿qué eres y quién te creó?",
|
| 28 |
+
"Explícame la fotosíntesis en dos frases.",
|
| 29 |
+
"¿Cuánto es 17 × 24? Razona paso a paso.",
|
| 30 |
+
"Escribe una frase creativa sobre un robot que aprende a pintar.",
|
| 31 |
+
"What is the capital of France? Answer in English.",
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def construir_prompt(tok, pregunta: str) -> str:
|
| 36 |
+
return (f"<|system|>\n{SYSTEM}\n<|end|>\n"
|
| 37 |
+
f"<|user|>\n{pregunta}\n<|end|>\n"
|
| 38 |
+
f"<|assistant|>\n")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def responder(model, tok, pregunta, temp=0.8, top_k=50, max_new=160):
|
| 42 |
+
prompt = construir_prompt(tok, pregunta)
|
| 43 |
+
ids = np.array([tok.encode(prompt, add_special_tokens=False)], dtype=np.int64)
|
| 44 |
+
out_ids = []
|
| 45 |
+
for _ in range(max_new):
|
| 46 |
+
logits = np.asarray(model.logits(mx.array(ids[:, -512:]))[0, -1])
|
| 47 |
+
if out_ids and out_ids[-1] == 0:
|
| 48 |
+
break
|
| 49 |
+
if temp <= 0:
|
| 50 |
+
nxt = int(np.argmax(logits))
|
| 51 |
+
else:
|
| 52 |
+
logits = logits / temp
|
| 53 |
+
if top_k > 0:
|
| 54 |
+
kth = np.partition(logits, -top_k)[-top_k]
|
| 55 |
+
logits = np.where(logits < kth, -np.inf, logits)
|
| 56 |
+
p = np.exp(logits - logits.max())
|
| 57 |
+
p /= p.sum()
|
| 58 |
+
nxt = int(np.random.choice(len(p), p=p))
|
| 59 |
+
out_ids.append(nxt)
|
| 60 |
+
ids = np.concatenate([ids, [[nxt]]], axis=1)
|
| 61 |
+
if nxt == 0:
|
| 62 |
+
break
|
| 63 |
+
return tok.decode(out_ids, skip_special_tokens=True).strip()
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def main() -> int:
|
| 67 |
+
ap = argparse.ArgumentParser()
|
| 68 |
+
ap.add_argument("prompt", nargs="*")
|
| 69 |
+
ap.add_argument("--interactive", action="store_true")
|
| 70 |
+
ap.add_argument("--temperature", type=float, default=0.8)
|
| 71 |
+
ap.add_argument("--top-k", type=int, default=50)
|
| 72 |
+
ap.add_argument("--max-new", type=int, default=160)
|
| 73 |
+
args = ap.parse_args()
|
| 74 |
+
|
| 75 |
+
print(f"cargando modelo MLX desde {HERE} …", flush=True)
|
| 76 |
+
model = Model.load(HERE)
|
| 77 |
+
tok = CortexTokenizer(vocab_file=os.path.join(HERE, "vocab.json"),
|
| 78 |
+
merges_file=os.path.join(HERE, "merges.txt"),
|
| 79 |
+
model_max_length=512, unk_token="<|unk|>",
|
| 80 |
+
bos_token="</s>", eos_token="</s>",
|
| 81 |
+
pad_token="<|pad|>")
|
| 82 |
+
print("listo (MLX/Metal). Escribe 'salir' para terminar.\n", flush=True)
|
| 83 |
+
|
| 84 |
+
if args.interactive:
|
| 85 |
+
while True:
|
| 86 |
+
try:
|
| 87 |
+
q = input("TÚ > ").strip()
|
| 88 |
+
except (EOFError, KeyboardInterrupt):
|
| 89 |
+
break
|
| 90 |
+
if not q or q == "salir":
|
| 91 |
+
break
|
| 92 |
+
print("CORTEX >", responder(model, tok, q, args.temperature,
|
| 93 |
+
args.top_k, args.max_new), "\n")
|
| 94 |
+
return 0
|
| 95 |
+
|
| 96 |
+
for q in args.prompt or BATERIA:
|
| 97 |
+
print(f"TÚ > {q}")
|
| 98 |
+
r = responder(model, tok, q, args.temperature, args.top_k, args.max_new)
|
| 99 |
+
print(f"CORTEX > {r}\n")
|
| 100 |
+
return 0
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
if __name__ == "__main__":
|
| 104 |
+
raise SystemExit(main())
|
merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
model.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Cortex-1 en MLX (Apple Silicon) — equivalente a la implementación NumPy/PyTorch.
|
| 2 |
+
|
| 3 |
+
Misma arquitectura: GPT pre-norm, RMSNorm, atención causal con QKV fusionado,
|
| 4 |
+
SwiGLU, embeddings de posición aprendidos, lm_head atado al embedding.
|
| 5 |
+
Paridad verificada: max|Δ logits| < 1e-4 vs el modelo NumPy original.
|
| 6 |
+
|
| 7 |
+
Uso: python generate_mlx.py "La capital de Francia es"
|
| 8 |
+
"""
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import json
|
| 12 |
+
import os
|
| 13 |
+
|
| 14 |
+
import mlx.core as mx
|
| 15 |
+
import mlx.nn as nn
|
| 16 |
+
|
| 17 |
+
MODEL_FILE = "weights.safetensors"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class Attention(nn.Module):
|
| 21 |
+
def __init__(self, d: int, n_heads: int):
|
| 22 |
+
super().__init__()
|
| 23 |
+
self.n_heads = n_heads
|
| 24 |
+
self.d_head = d // n_heads
|
| 25 |
+
self.scale = self.d_head ** -0.5
|
| 26 |
+
self.qkv = nn.Linear(d, 3 * d, bias=False)
|
| 27 |
+
self.proj = nn.Linear(d, d, bias=False)
|
| 28 |
+
|
| 29 |
+
def __call__(self, x: mx.array) -> mx.array:
|
| 30 |
+
B, T, d = x.shape
|
| 31 |
+
qkv = self.qkv(x).reshape(B, T, 3, self.n_heads, self.d_head)
|
| 32 |
+
q, k, v = (qkv[:, :, i].transpose(0, 2, 1, 3) for i in range(3))
|
| 33 |
+
att = (q @ k.transpose(0, 1, 3, 2)) * self.scale # (B,H,T,T)
|
| 34 |
+
mask = mx.triu(mx.full((T, T), -mx.inf, dtype=att.dtype), k=1)
|
| 35 |
+
att = mx.softmax(att + mask, axis=-1)
|
| 36 |
+
y = (att @ v).transpose(0, 2, 1, 3).reshape(B, T, d)
|
| 37 |
+
return self.proj(y)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class MLP(nn.Module):
|
| 41 |
+
def __init__(self, d: int, d_ff: int):
|
| 42 |
+
super().__init__()
|
| 43 |
+
self.gate_proj = nn.Linear(d, d_ff, bias=False)
|
| 44 |
+
self.up_proj = nn.Linear(d, d_ff, bias=False)
|
| 45 |
+
self.down_proj = nn.Linear(d_ff, d, bias=False)
|
| 46 |
+
|
| 47 |
+
def __call__(self, x: mx.array) -> mx.array:
|
| 48 |
+
return self.down_proj(nn.silu(self.gate_proj(x)) * self.up_proj(x))
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class Block(nn.Module):
|
| 52 |
+
def __init__(self, d: int, n_heads: int, d_ff: int, eps: float):
|
| 53 |
+
super().__init__()
|
| 54 |
+
self.input_layernorm = nn.RMSNorm(d, eps=eps)
|
| 55 |
+
self.self_attn = Attention(d, n_heads)
|
| 56 |
+
self.post_attention_layernorm = nn.RMSNorm(d, eps=eps)
|
| 57 |
+
self.mlp = MLP(d, d_ff)
|
| 58 |
+
|
| 59 |
+
def __call__(self, x):
|
| 60 |
+
x = x + self.self_attn(self.input_layernorm(x))
|
| 61 |
+
return x + self.mlp(self.post_attention_layernorm(x))
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
class Model(nn.Module):
|
| 65 |
+
def __init__(self, cfg: dict):
|
| 66 |
+
super().__init__()
|
| 67 |
+
d = cfg["hidden_size"]
|
| 68 |
+
self.cfg = cfg
|
| 69 |
+
self.embed_tokens = nn.Embedding(cfg["vocab_size"], d)
|
| 70 |
+
self.embed_positions = nn.Embedding(cfg["max_position_embeddings"], d)
|
| 71 |
+
self.layers = [Block(d, cfg["num_attention_heads"],
|
| 72 |
+
cfg["intermediate_size"], cfg["rms_norm_eps"])
|
| 73 |
+
for _ in range(cfg["num_hidden_layers"])]
|
| 74 |
+
self.norm = nn.RMSNorm(d, eps=cfg["rms_norm_eps"])
|
| 75 |
+
|
| 76 |
+
def __call__(self, input_ids: mx.array) -> mx.array:
|
| 77 |
+
T = input_ids.shape[1]
|
| 78 |
+
h = self.embed_tokens(input_ids) + self.embed_positions(mx.arange(T))
|
| 79 |
+
for layer in self.layers:
|
| 80 |
+
h = layer(h)
|
| 81 |
+
return self.norm(h)
|
| 82 |
+
|
| 83 |
+
def logits(self, input_ids: mx.array) -> mx.array:
|
| 84 |
+
"""(B,T) int -> (B,T,V); lm_head atado = embed_tokens."""
|
| 85 |
+
h = self(input_ids)
|
| 86 |
+
w = self.embed_tokens.weight # (V, d)
|
| 87 |
+
return h @ w.T
|
| 88 |
+
|
| 89 |
+
@classmethod
|
| 90 |
+
def load(cls, repo: str) -> "Model":
|
| 91 |
+
with open(os.path.join(repo, "config.json")) as f:
|
| 92 |
+
cfg = json.load(f)
|
| 93 |
+
model = cls(cfg)
|
| 94 |
+
weights = mx.load(os.path.join(repo, MODEL_FILE))
|
| 95 |
+
model.load_weights(list(weights.items()), strict=True)
|
| 96 |
+
return model
|
special_tokens_map.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token": "<|endoftext|>",
|
| 3 |
+
"eos_token": "<|endoftext|>",
|
| 4 |
+
"pad_token": "<|pad|>",
|
| 5 |
+
"unk_token": "<|unk|>",
|
| 6 |
+
"additional_special_tokens": [
|
| 7 |
+
"<|system|>",
|
| 8 |
+
"<|user|>",
|
| 9 |
+
"<|assistant|>",
|
| 10 |
+
"<|end|>",
|
| 11 |
+
"<|doc|>",
|
| 12 |
+
"<|code|>"
|
| 13 |
+
]
|
| 14 |
+
}
|
tokenization_cortex.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Tokenizer de Cortex-1 para Hugging Face (equivalente exacto al BPE byte-level propio)."""
|
| 2 |
+
import json
|
| 3 |
+
import regex
|
| 4 |
+
from transformers import PreTrainedTokenizer
|
| 5 |
+
|
| 6 |
+
PRETOKEN = regex.compile(
|
| 7 |
+
r"""'(?:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?+\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]++[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+"""
|
| 8 |
+
)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def bytes_to_unicode():
|
| 12 |
+
"""Tabla GPT-2: byte -> carácter imprimible (idéntica a la del modelo propio)."""
|
| 13 |
+
bs = (list(range(ord("!"), ord("~") + 1)) + list(range(ord("¡"), ord("¬") + 1))
|
| 14 |
+
+ list(range(ord("®"), ord("ÿ") + 1)))
|
| 15 |
+
cs = bs[:]
|
| 16 |
+
n = 0
|
| 17 |
+
for b in range(256):
|
| 18 |
+
if b not in bs:
|
| 19 |
+
bs.append(b)
|
| 20 |
+
cs.append(256 + n)
|
| 21 |
+
n += 1
|
| 22 |
+
return dict(zip(bs, (chr(c) for c in cs)))
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
_B2U = bytes_to_unicode()
|
| 26 |
+
_U2B = {v: k for k, v in _B2U.items()}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class CortexTokenizer(PreTrainedTokenizer):
|
| 30 |
+
"""BPE byte-level idéntico a ``cortex.tokenizer.encoder.BPETokenizer``.
|
| 31 |
+
|
| 32 |
+
El pre-tokenizador usa el MISMO regex (poseedores y lookahead incluidos,
|
| 33 |
+
vía librería ``regex``) y los merges se aplican por orden de aprendizaje;
|
| 34 |
+
el test de paridad encode(tok propio) == encode(tok HF) lo verifica.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
vocab_files_names = {"vocab_file": "vocab.json", "merges_file": "merges.txt"}
|
| 38 |
+
model_input_names = ["input_ids", "attention_mask"]
|
| 39 |
+
|
| 40 |
+
def __init__(self, vocab_file=None, merges_file=None, **kwargs):
|
| 41 |
+
with open(vocab_file, encoding="utf-8") as f:
|
| 42 |
+
self._vocab = json.load(f) # {token: id}
|
| 43 |
+
with open(merges_file, encoding="utf-8") as f:
|
| 44 |
+
merge_lines = [ln.strip() for ln in f if ln.strip() and not ln.startswith("#")]
|
| 45 |
+
self._merges = {tuple(ln.split()): i for i, ln in enumerate(merge_lines)}
|
| 46 |
+
self._id2tok = {i: t for t, i in self._vocab.items()}
|
| 47 |
+
self._cache = {}
|
| 48 |
+
super().__init__(vocab_file=vocab_file, merges_file=merges_file, **kwargs)
|
| 49 |
+
|
| 50 |
+
@property
|
| 51 |
+
def vocab_size(self):
|
| 52 |
+
return len(self._vocab)
|
| 53 |
+
|
| 54 |
+
def get_vocab(self):
|
| 55 |
+
"""Requerido por transformers (_add_tokens lo llama en __init__)."""
|
| 56 |
+
return dict(self._vocab)
|
| 57 |
+
|
| 58 |
+
# ------------------------------------------------------------ tokenización
|
| 59 |
+
def _bpe(self, word: str):
|
| 60 |
+
if word in self._cache:
|
| 61 |
+
return self._cache[word]
|
| 62 |
+
parts = list(word)
|
| 63 |
+
while len(parts) > 1:
|
| 64 |
+
pairs = list(zip(parts, parts[1:]))
|
| 65 |
+
best = min(pairs, key=lambda p: self._merges.get(p, float("inf")))
|
| 66 |
+
if best not in self._merges:
|
| 67 |
+
break
|
| 68 |
+
out, i = [], 0
|
| 69 |
+
while i < len(parts):
|
| 70 |
+
if i < len(parts) - 1 and (parts[i], parts[i + 1]) == best:
|
| 71 |
+
out.append(parts[i] + parts[i + 1])
|
| 72 |
+
i += 2
|
| 73 |
+
else:
|
| 74 |
+
out.append(parts[i])
|
| 75 |
+
i += 1
|
| 76 |
+
parts = out
|
| 77 |
+
self._cache[word] = parts
|
| 78 |
+
return parts
|
| 79 |
+
|
| 80 |
+
def _split_specials(self, text: str):
|
| 81 |
+
"""Parte el texto conservando literales <|x|> como tokens (ids 0-8)."""
|
| 82 |
+
sp = sorted((s for s in self._vocab if s.startswith("<|") and s.endswith("|>")),
|
| 83 |
+
key=len, reverse=True)
|
| 84 |
+
out, i, buf = [], 0, ""
|
| 85 |
+
while i < len(text):
|
| 86 |
+
hit = next((s for s in sp if text.startswith(s, i)), None)
|
| 87 |
+
if hit:
|
| 88 |
+
if buf:
|
| 89 |
+
out.append(("text", buf)); buf = ""
|
| 90 |
+
out.append(("special", hit)); i += len(hit)
|
| 91 |
+
else:
|
| 92 |
+
buf += text[i]; i += 1
|
| 93 |
+
if buf:
|
| 94 |
+
out.append(("text", buf))
|
| 95 |
+
return out
|
| 96 |
+
|
| 97 |
+
def _tokenize(self, text):
|
| 98 |
+
tokens = []
|
| 99 |
+
for kind, chunk in self._split_specials(text):
|
| 100 |
+
if kind == "special":
|
| 101 |
+
tokens.append(chunk)
|
| 102 |
+
continue
|
| 103 |
+
for word in PRETOKEN.findall(chunk):
|
| 104 |
+
encoded = "".join(_B2U[b] for b in word.encode("utf-8"))
|
| 105 |
+
tokens.extend(self._bpe(encoded))
|
| 106 |
+
return tokens
|
| 107 |
+
|
| 108 |
+
# ------------------------------------------------------------ conversión
|
| 109 |
+
def _convert_token_to_id(self, token):
|
| 110 |
+
return self._vocab.get(token, self._vocab.get("<|unk|>", 0))
|
| 111 |
+
|
| 112 |
+
def _convert_id_to_token(self, index):
|
| 113 |
+
return self._id2tok.get(index, "<|unk|>")
|
| 114 |
+
|
| 115 |
+
def convert_tokens_to_string(self, tokens):
|
| 116 |
+
return "".join(tokens)
|
| 117 |
+
|
| 118 |
+
def _decode(self, token_ids, skip_special_tokens=False, **kwargs):
|
| 119 |
+
pieces = []
|
| 120 |
+
for i in token_ids:
|
| 121 |
+
tok = self._id2tok.get(int(i))
|
| 122 |
+
if tok is None:
|
| 123 |
+
continue
|
| 124 |
+
if skip_special_tokens and tok in self.all_special_tokens:
|
| 125 |
+
continue
|
| 126 |
+
pieces.append(tok)
|
| 127 |
+
raw = "".join(pieces)
|
| 128 |
+
data = bytes(_U2B[c] for c in raw if c in _U2B)
|
| 129 |
+
return data.decode("utf-8", errors="replace")
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"tokenizer_class": "CortexTokenizer",
|
| 3 |
+
"auto_map": {
|
| 4 |
+
"AutoTokenizer": [
|
| 5 |
+
"tokenization_cortex.CortexTokenizer",
|
| 6 |
+
null
|
| 7 |
+
]
|
| 8 |
+
},
|
| 9 |
+
"model_max_length": 512,
|
| 10 |
+
"bos_token": "<|endoftext|>",
|
| 11 |
+
"eos_token": "<|endoftext|>",
|
| 12 |
+
"pad_token": "<|pad|>",
|
| 13 |
+
"unk_token": "<|unk|>",
|
| 14 |
+
"additional_special_tokens": [
|
| 15 |
+
"<|system|>",
|
| 16 |
+
"<|user|>",
|
| 17 |
+
"<|assistant|>",
|
| 18 |
+
"<|end|>",
|
| 19 |
+
"<|doc|>",
|
| 20 |
+
"<|code|>"
|
| 21 |
+
],
|
| 22 |
+
"chat_template": "{% for message in messages %}{% if message['role'] == 'system' %}<|system|>\n{{ message['content'] }}\n<|end|>\n{% elif message['role'] == 'user' %}<|user|>\n{{ message['content'] }}\n<|end|>\n{% else %}<|assistant|>\n{{ message['content'] }}\n<|end|>\n{% endif %}{% endfor %}{% if add_generation_prompt %}<|assistant|>\n{% endif %}",
|
| 23 |
+
"tokenizer_files": [
|
| 24 |
+
"vocab.json",
|
| 25 |
+
"merges.txt"
|
| 26 |
+
]
|
| 27 |
+
}
|
vocab.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
weights.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6c6de87791adce6695e426f7b6aa5d29ac6005c5391da34ff7f82f639d870d72
|
| 3 |
+
size 82599888
|